1use yog_data::{Ordered, Reader, Schema, Shape, Value, ValueRef};
4
5#[derive(Debug, Clone, PartialEq)]
9pub enum Texture {
10 Slot(String),
11 Ref(String),
13}
14
15pub const SLOT_MARK: char = '#';
17
18impl Schema for Texture {
19 fn shape() -> Shape {
20 Shape::Ref(&[yog_ids::kind::TEXTURE])
21 }
22 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
23 match v {
24 ValueRef::Str(s) if s.starts_with(SLOT_MARK) => {
25 if s.len() == SLOT_MARK.len_utf8() {
26 cx.problem("`#` needs a slot name");
27 return None;
28 }
29 Some(Texture::Slot(s[SLOT_MARK.len_utf8()..].to_string()))
30 }
31 ValueRef::Ref { kind, id } if *kind == yog_ids::kind::TEXTURE => {
32 Some(Texture::Ref(yog_ids::qualify(id)))
33 }
34 ValueRef::Str(s) if s.starts_with(yog_ids::sep::TYPED) => {
35 match yog_data::typed::classify(s) {
36 Ok(Value::Ref(r)) if r.kind == yog_ids::kind::TEXTURE => {
37 Some(Texture::Ref(yog_ids::qualify(&r.id)))
38 }
39 Ok(_) => {
40 cx.problem(format!("`{s}`: a texture is `#slot` or `@texture:<ns>:<path>` (a .ytexture, or the game's)"));
41 None
42 }
43 Err(e) => {
44 cx.problem(e);
45 None
46 }
47 }
48 }
49 other => {
50 cx.expected(
51 "a texture: `#slot` or `@texture:<ns>:<path>` (a .ytexture, or the game's)",
52 other,
53 );
54 None
55 }
56 }
57 }
58 fn write(&self) -> Value {
59 match self {
60 Texture::Slot(s) => Value::Str(format!("{SLOT_MARK}{s}")),
61 Texture::Ref(id) => Value::Ref(yog_data::Ref::new(yog_ids::kind::TEXTURE, id.clone())),
62 }
63 }
64}
65
66#[derive(Schema, Debug, Clone, Copy, PartialEq, Eq)]
67pub enum Axis {
68 X,
69 Y,
70 Z,
71}
72
73#[derive(Schema, Debug, Clone, Copy, PartialEq, Eq)]
74pub enum GuiLight {
75 Front,
76 Side,
77}
78
79#[derive(Schema, Debug, Clone)]
81pub struct ElementRotation {
82 #[data(range = "-16..=32")]
84 pub origin: Option<[f64; 3]>,
85 pub axis: Axis,
86 #[data(range = "-45..=45")]
88 pub angle: f64,
89 #[data(default)]
90 pub rescale: bool,
91}
92
93#[derive(Schema, Debug, Clone)]
95pub struct Face {
96 pub texture: String,
98 #[data(range = "0..=16")]
99 pub uv: Option<[f64; 4]>,
100 pub rotation: Option<u32>,
102 pub cullface: Option<yog_core::Face>,
104 #[data(range = "-1..=255")]
106 pub tint: Option<i64>,
107}
108
109#[derive(Schema, Debug, Clone, Default)]
110pub struct Faces {
111 pub down: Option<Face>,
112 pub up: Option<Face>,
113 pub north: Option<Face>,
114 pub south: Option<Face>,
115 pub west: Option<Face>,
116 pub east: Option<Face>,
117}
118
119impl Faces {
120 pub fn iter(&self) -> impl Iterator<Item = (&'static str, &Face)> {
121 [
122 ("down", &self.down),
123 ("up", &self.up),
124 ("north", &self.north),
125 ("south", &self.south),
126 ("west", &self.west),
127 ("east", &self.east),
128 ]
129 .into_iter()
130 .filter_map(|(d, f)| f.as_ref().map(|f| (d, f)))
131 }
132}
133
134#[derive(Schema, Debug, Clone)]
136pub struct Element {
137 pub name: Option<String>,
138 #[data(range = "-16..=32")]
139 pub from: [f64; 3],
140 #[data(range = "-16..=32")]
141 pub to: [f64; 3],
142 pub rotation: Option<ElementRotation>,
143 pub shade: Option<bool>,
144 pub faces: Option<Faces>,
145}
146
147#[derive(Schema, Debug, Clone)]
149pub struct Transform {
150 #[data(range = "-360..=360")]
151 pub rotation: Option<[f64; 3]>,
152 #[data(range = "-80..=80")]
153 pub translation: Option<[f64; 3]>,
154 #[data(range = "-4..=4")]
155 pub scale: Option<[f64; 3]>,
156}
157
158#[derive(Schema, Debug, Clone, Default)]
159pub struct Display {
160 pub thirdperson_righthand: Option<Transform>,
161 pub thirdperson_lefthand: Option<Transform>,
162 pub firstperson_righthand: Option<Transform>,
163 pub firstperson_lefthand: Option<Transform>,
164 pub gui: Option<Transform>,
165 pub head: Option<Transform>,
166 pub ground: Option<Transform>,
167 pub fixed: Option<Transform>,
168}
169
170impl Display {
171 pub fn iter(&self) -> impl Iterator<Item = (&'static str, &Transform)> {
172 [
173 ("thirdperson_righthand", &self.thirdperson_righthand),
174 ("thirdperson_lefthand", &self.thirdperson_lefthand),
175 ("firstperson_righthand", &self.firstperson_righthand),
176 ("firstperson_lefthand", &self.firstperson_lefthand),
177 ("gui", &self.gui),
178 ("head", &self.head),
179 ("ground", &self.ground),
180 ("fixed", &self.fixed),
181 ]
182 .into_iter()
183 .filter_map(|(d, t)| t.as_ref().map(|t| (d, t)))
184 }
185}
186
187#[derive(Schema, Debug, Clone)]
190pub struct Model {
191 pub parent: Option<String>,
192 #[data(with = "yog_data::with::Opt<yog_data::with::RefTo<yog_data::kinds::Material>>")]
195 pub material: Option<String>,
196 #[data(default)]
198 pub textures: Ordered<Texture>,
199 pub elements: Option<Vec<Element>>,
200 pub display: Option<Display>,
201 pub ambient_occlusion: Option<bool>,
202 pub gui_light: Option<GuiLight>,
203}
204
205#[derive(Schema, Debug, Clone)]
207pub struct Rotation {
208 pub x: Option<u32>,
210 pub y: Option<u32>,
212 #[data(range = "1..=1000000")]
213 pub weight: Option<f64>,
214 #[data(default)]
215 pub uvlock: bool,
216}
217
218#[derive(Schema, Debug, Clone)]
220pub struct Connect {
221 pub arm: Model,
223 #[data(default)]
224 pub uvlock: bool,
225}
226
227#[derive(Schema, Debug, Clone)]
228pub struct States {
229 pub rotations: Option<Vec<Rotation>>,
230 pub connect: Option<Connect>,
231 pub facing: Option<Facing>,
234 pub placement: Option<Placement>,
237 #[data(default)]
241 pub attach: bool,
242 #[data(default)]
244 pub waterlogged: bool,
245 #[data(default)]
248 pub uvlock: bool,
249 #[data(default)]
252 pub properties: Ordered<Vec<String>>,
253 #[data(default)]
255 pub models: Ordered<Model>,
256 #[data(default)]
262 pub variants: Ordered<ModelNames>,
263}
264
265pub type ModelNames = yog_data::OneOrList<String>;
267
268pub use yog_registry::{Facing, Placement};