1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
use bevy::{math::Vec4, reflect::Reflect, render::color::Color, utils::HashMap};
use serde::{de::Visitor, Deserialize, Serialize};
use self::{definitions::Definitions, level::Level};
pub mod definitions;
pub mod field;
pub mod level;
pub mod macros;
#[derive(Serialize, Debug, Clone, Copy, Reflect)]
pub struct LdtkColor {
pub r: f32,
pub g: f32,
pub b: f32,
}
impl From<String> for LdtkColor {
fn from(value: String) -> Self {
let r = u8::from_str_radix(&value[1..3], 16).unwrap() as f32 / 255.;
let g = u8::from_str_radix(&value[3..5], 16).unwrap() as f32 / 255.;
let b = u8::from_str_radix(&value[5..7], 16).unwrap() as f32 / 255.;
Self { r, g, b }
}
}
impl Into<Color> for LdtkColor {
fn into(self) -> Color {
Color::rgb(self.r, self.g, self.b)
}
}
impl Into<Vec4> for LdtkColor {
fn into(self) -> Vec4 {
Vec4::new(self.r, self.g, self.b, 1.)
}
}
impl<'de> Deserialize<'de> for LdtkColor {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct LdtkColorVisitor;
impl<'de> Visitor<'de> for LdtkColorVisitor {
type Value = LdtkColor;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a color in the format #RRGGBB")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(LdtkColor::from(value.to_string()))
}
}
deserializer.deserialize_str(LdtkColorVisitor)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Reflect)]
#[serde(rename_all = "camelCase")]
pub struct LdtkJson {
/// Project background color
pub bg_color: LdtkColor,
/// A structure containing all the definitions of this project
pub defs: Definitions,
/// If TRUE, one file will be saved for the project (incl. all its definitions)
/// and one file in a sub-folder for each level.
pub external_levels: bool,
/// Unique project identifier
pub iid: String,
/// File format version
pub json_version: String,
/// All levels. The order of this array is only relevant in
/// `LinearHorizontal` and `linearVertical` world layouts (see `worldLayout` value).
///
/// Otherwise, you should refer to the `worldX`,`worldY` coordinates of each Level.
pub levels: Vec<Level>,
/// All instances of entities that have their `exportToToc`flag enabled
/// are listed in this array.
pub toc: Vec<Toc>,
/// ## WARNING:
/// this field will move to the `worlds` array after the "multi-worlds" update.
/// It will then be `null`. You can enable the Multi-worlds
/// advanced project option to enable the change immediately.
///
/// Height of the world grid in pixels.
pub world_grid_height: Option<i32>,
/// ## WARNING:
/// this field will move to the `worlds` array after the "multi-worlds" update.
/// It will then be `null`. You can enable the Multi-worlds
/// advanced project option to enable the change immediately.
///
/// Width of the world grid in pixels.
pub world_grid_width: Option<i32>,
/// ## WARNING:
/// this field will move to the `worlds` array after the "multi-worlds" update.
/// It will then be `null`. You can enable the Multi-worlds
/// advanced project option to enable the change immediately.
///
/// An enum that describes how levels are organized in this project (ie. linearly or in a 2D space).
pub world_layout: Option<WorldLayout>,
/// This array will be empty, unless you enable the Multi-Worlds in the project advanced settings.
/// - in current version, a LDtk project file can only contain a single world with
/// multiple levels in it. In this case, levels and world layout related settings
/// are stored in the root of the JSON.
/// - with "Multi-worlds" enabled, there will be a `worlds` array in root, each world
/// containing levels and layout settings. Basically, it's pretty much only about
/// moving the `levels` array to the `worlds` array, along with world layout related values
/// (eg. `worldGridWidth` etc).
///
/// If you want to start supporting this future update easily,
/// please refer to this documentation: https://github.com/deepnight/ldtk/issues/231
pub worlds: Vec<World>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Reflect)]
#[serde(rename_all = "camelCase")]
pub struct Toc {
pub identifier: String,
/// All instances of entities that have their `exportToToc` flag
/// enabled are listed in this array.
pub instances_data: Vec<TocInstance>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Reflect)]
#[serde(rename_all = "camelCase")]
pub struct TocInstance {
/// IID information of this instance
pub iids: EntityRef,
pub world_x: i32,
pub world_y: i32,
pub wid_px: i32,
pub hei_px: i32,
/// An object containing the values of all entity fields with the `exportToToc`
/// option enabled. This object typing depends on actual field value types.
#[serde(rename = "fields")]
pub untyped_fields: HashMap<String, TocField>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Reflect)]
#[serde(untagged)]
pub enum TocField {
Integer(i32),
Float(f32),
Bool(bool),
String(String),
Color(LdtkColor),
Point(GridPoint),
EntityRef(EntityRef),
IntegerArray(Vec<i32>),
FloatArray(Vec<f32>),
BoolArray(Vec<bool>),
StringArray(Vec<String>),
ColorArray(Vec<LdtkColor>),
PointArray(Vec<GridPoint>),
EntityRefArray(Vec<EntityRef>),
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Reflect)]
pub enum WorldLayout {
Free,
GridVania,
LinearHorizontal,
LinearVertical,
}
#[derive(Serialize, Deserialize, Debug, Clone, Reflect)]
#[serde(rename_all = "camelCase")]
pub struct World {
/// Width of the world grid in pixels.
pub world_grid_width: i32,
/// Unique instance identifer
pub iid: String,
/// Height of the world grid in pixels.
pub world_grid_height: i32,
/// An enum that describes how levels are organized in this project
/// (ie. linearly or in a 2D space).
/// Possible values: `Free`, `GridVania`, `LinearHorizontal`, `LinearVertical`
pub world_layout: Option<WorldLayout>,
/// All levels from this world.
/// The order of this array is only relevant in `LinearHorizontal` and
/// `linearVertical` world layouts (see `worldLayout` value). Otherwise,
/// you should refer to the `worldX`,`worldY` coordinates of each Level.
pub levels: Vec<Level>,
/// User defined unique identifier
pub identifier: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, Reflect, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
pub struct EntityRef {
/// IID of the refered EntityInstance
pub entity_iid: String,
/// IID of the LayerInstance containing the refered EntityInstance
pub layer_iid: String,
/// IID of the Level containing the refered EntityInstance
pub level_iid: String,
/// IID of the World containing the refered EntityInstance
pub world_iid: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, Reflect)]
#[serde(rename_all = "camelCase")]
pub struct GridPoint {
/// X grid-based coordinate
pub cx: i32,
/// Y grid-based coordinate
pub cy: i32,
}