gdlib 0.4.0

Rust library for editing Geometry Dash savefiles
Documentation
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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
//! This module contains all object metadata descriptors.

use std::fmt::{Debug, Display, Write};

use bitflags::bitflags;

use crate::cclocallevels::gdobj::{
    ids::properties::*,
    structs::{ColourChannel, Group, GroupType, ZLayer},
};
/// Object config, used for defining general properties of an object
#[derive(Clone, Debug, PartialEq)]
#[must_use]
pub struct GDObjConfig {
    /// Position of this object
    pub pos: (f64, f64),
    /// Scale of this object
    pub scale: (f64, f64),
    /// Angle of rotation
    pub angle: f64,
    /// Groups (both parents and regular)
    pub groups: Vec<Group>,
    /// Trigger activation config
    pub trigger_cfg: TriggerConfig,
    /// Z order of this object
    pub z_order: i32,
    /// Z layer of this object
    pub z_layer: ZLayer,
    /// Editor layers of this object
    pub editor_layers: (i16, i16),
    /// Main and detail colour channels respectively
    pub colour_channels: (ColourChannel, ColourChannel),
    /// Enter effect channel
    pub enter_effect_channel: i16,
    /// Material ID
    pub material_id: i16,
    /// Control ID
    pub control_id: i16,
    /// Common attributes
    pub attributes: GDObjAttributes,
}

impl Default for GDObjConfig {
    fn default() -> Self {
        GDObjConfig {
            pos: (0.0, 0.0),
            scale: (1.0, 1.0),
            angle: 0.0,
            groups: vec![],
            trigger_cfg: TriggerConfig {
                touchable: false,
                spawnable: false,
                multitriggerable: false,
            },
            z_layer: ZLayer::T1,
            z_order: 0,
            editor_layers: (0, 0),
            colour_channels: (ColourChannel::Object, ColourChannel::Channel(1)),
            enter_effect_channel: 0,
            material_id: 0,
            control_id: 0,
            attributes: GDObjAttributes::new(),
        }
    }
}

impl GDObjConfig {
    /// Alias for default
    #[inline]
    pub fn new() -> Self {
        Self::default()
    }

    /// Serialises this config struct to a string
    #[must_use]
    pub fn serialise_to_string(&self) -> String {
        let mut properties = String::with_capacity(64);
        let _ = write!(
            properties,
            ",2,{},3,{}{}",
            self.pos.0,
            self.pos.1,
            self.attributes.get_property_str()
        );

        // bools
        serialise_bools(
            &[
                ("11", self.trigger_cfg.touchable),
                ("62", self.trigger_cfg.spawnable),
                ("87", self.trigger_cfg.multitriggerable),
            ],
            &mut properties,
        );

        // f64
        serialise_fields(
            &[
                ("6", self.angle, 0.0),
                ("128", self.scale.0, 1.0),
                ("129", self.scale.1, 1.0),
            ],
            &mut properties,
        );

        // i16
        serialise_fields(
            &[
                ("20", self.editor_layers.0, 0),
                ("61", self.editor_layers.1, 0),
                (
                    "21",
                    self.colour_channels.0.into(),
                    ColourChannel::Object.into(),
                ),
                ("22", self.colour_channels.1.into(), 1),
                ("24", self.z_layer as i16, ZLayer::T1 as i16),
                ("343", self.enter_effect_channel, 0),
                ("446", self.material_id, 0),
                ("534", self.control_id, 0),
            ],
            &mut properties,
        );

        serialise_fields(&[("25", self.z_order, 0)], &mut properties);

        if !self.groups.is_empty() {
            properties.push_str(",57,");
            let mut i_buf = itoa::Buffer::new();
            for (idx, group) in self.groups.iter().enumerate() {
                if idx != 0 {
                    properties.push('.');
                }
                properties.push_str(i_buf.format(group.id()));
            }
        }

        properties
    }

    fn dedup_groups(&mut self) {
        self.groups.sort();
        self.groups.dedup_by(|a, b| a.id() == b.id());
    }

    /// Sets groups of this object
    #[inline]
    pub fn groups<T: IntoIterator<Item = I>, I: Into<Group>>(mut self, groups: T) -> Self {
        self.groups = groups.into_iter().map(std::convert::Into::into).collect();
        self.dedup_groups();
        self
    }
    /// Adds groups to this object's groups
    #[inline]
    pub fn add_groups<T: AsRef<[Group]>>(&mut self, groups: T) {
        self.groups.extend_from_slice(groups.as_ref());
        self.dedup_groups();
    }
    /// Adds group to this object's groups
    #[inline]
    pub fn add_group(&mut self, group: Group) {
        self.groups.push(group);
        self.dedup_groups();
    }
    /// Removes this group from this object's groups
    #[inline]
    pub fn remove_group(&mut self, group: Group) {
        if let Some(idx) = self.groups.iter().position(|&g| g == group) {
            self.groups.swap_remove(idx);
        }
    }
    /// Clears all groups from this object
    #[inline]
    pub fn clear_groups(&mut self) {
        self.groups.clear();
    }
    /// Sets x position of this object
    #[inline]
    pub fn x(mut self, x: f64) -> Self {
        self.pos.0 = x;
        self
    }
    /// Sets y position of this object
    #[inline]
    pub fn y(mut self, y: f64) -> Self {
        self.pos.1 = y;
        self
    }

    /// Applies a translation to this object's position
    #[inline]
    pub fn translate(mut self, x: f64, y: f64) -> Self {
        self.pos.0 += x;
        self.pos.1 += y;
        self
    }

    /// Sets x and y position of this object
    #[inline]
    pub fn pos(mut self, x: f64, y: f64) -> Self {
        self.pos = (x, y);
        self
    }
    /// Sets x scale of this object
    #[inline]
    pub fn xscale(mut self, xscale: f64) -> Self {
        self.scale.0 = xscale;
        self
    }
    /// Sets y scale of this object
    #[inline]
    pub fn yscale(mut self, yscale: f64) -> Self {
        self.scale.1 = yscale;
        self
    }
    /// Sets x and y scale of this object
    #[inline]
    pub fn scale(mut self, x: f64, y: f64) -> Self {
        self.scale = (x, y);
        self
    }
    /// Sets rotation angle of this object
    #[inline]
    pub fn angle(mut self, angle: f64) -> Self {
        self.angle = angle;
        self
    }
    /// Makes this object touch triggerable
    #[inline]
    pub fn touchable(mut self, touchable: bool) -> Self {
        self.trigger_cfg.touchable = touchable;
        self
    }
    /// Makes this object spawn triggerable
    #[inline]
    pub fn spawnable(mut self, spawnable: bool) -> Self {
        self.trigger_cfg.spawnable = spawnable;
        self
    }
    /// Makes this object multi-triggerable
    #[inline]
    pub fn multitrigger(mut self, multi: bool) -> Self {
        self.trigger_cfg.multitriggerable = multi;
        self
    }
    /// Sets this object's base colour channel
    #[inline]
    pub fn set_base_colour(mut self, channel: ColourChannel) -> Self {
        self.colour_channels.0 = channel;
        self
    }
    /// Sets this object's detail colour channel
    #[inline]
    pub fn set_detail_colour(mut self, channel: ColourChannel) -> Self {
        self.colour_channels.1 = channel;
        self
    }
    /// Sets this object's Z-layer
    #[inline]
    pub fn set_z_layer(mut self, z: ZLayer) -> Self {
        self.z_layer = z;
        self
    }
    /// Sets this object's Z-order
    #[inline]
    pub fn set_z_order(mut self, z: i32) -> Self {
        self.z_order = z;
        self
    }
    /// Sets editor layer 1 of this object
    #[inline]
    pub fn editor_layer_1(mut self, l: i16) -> Self {
        self.editor_layers.0 = l;
        self
    }
    /// Sets editor layer 2 of this object
    #[inline]
    pub fn editor_layer_2(mut self, l: i16) -> Self {
        self.editor_layers.1 = l;
        self
    }
    /// Sets this object's material id
    #[inline]
    pub fn set_material_id(mut self, material_id: i16) -> Self {
        self.material_id = material_id;
        self
    }
    /// Sets this object's enter effect channel
    #[inline]
    pub fn set_enter_channel(mut self, channel: i16) -> Self {
        self.enter_effect_channel = channel;
        self
    }
    /// Sets this object's control ID
    #[inline]
    pub fn set_control_id(mut self, id: i16) -> Self {
        self.control_id = id;
        self
    }

    /// Gets the value of a set attribute flag.  
    /// The flag is only true if it has been set as such. Unset flags return false.
    #[inline]
    #[must_use]
    pub fn get_attribute_flag(&self, flag: GDObjAttributes) -> bool {
        self.attributes.contains(flag)
    }

    /// Sets the attribute of the specified flag. Function is useable in builder syntax.
    #[inline]
    pub fn set_attribute_flag(mut self, flag: GDObjAttributes, toggle: bool) -> Self {
        self.attributes.set(flag, toggle);
        self
    }
}

bitflags! {
    #[allow(missing_docs)]
    #[derive(Debug, Copy, Clone, PartialEq, Default, Eq, Hash)]
    #[must_use]
    pub struct GDObjAttributes: u32 {
        /// @nodoc
        const dont_fade          = 1;
        /// @nodoc
        const dont_enter         = 1 << 1;
        /// @nodoc
        const no_effects         = 1 << 2;
        /// @nodoc
        const is_group_parent    = 1 << 3;
        /// @nodoc
        const is_area_parent     = 1 << 4;
        /// @nodoc
        const dont_boost_x       = 1 << 5;
        /// @nodoc
        const dont_boost_y       = 1 << 6;
        /// @nodoc
        const high_detail        = 1 << 7;
        /// @nodoc
        const no_touch           = 1 << 8;
        /// @nodoc
        const passable           = 1 << 9;
        /// @nodoc
        const hidden             = 1 << 10;
        /// @nodoc
        const non_stick_x        = 1 << 11;
        /// @nodoc
        const non_stick_y        = 1 << 12;
        /// @nodoc
        const extra_sticky       = 1 << 13;
        /// @nodoc
        const extended_collision = 1 << 14;
        /// @nodoc
        const is_ice_block       = 1 << 15;
        /// @nodoc
        const grip_slope         = 1 << 16;
        /// @nodoc
        const no_glow            = 1 << 17;
        /// @nodoc
        const no_particles       = 1 << 18;
        /// @nodoc
        const scale_stick        = 1 << 19;
        /// @nodoc
        const no_audio_scale     = 1 << 20;
        /// @nodoc
        const single_ptouch      = 1 << 21;
        /// @nodoc
        const center_effect      = 1 << 22;
        /// @nodoc
        const reverse            = 1 << 23;
    }
}

const GDOBJ_ATTR_FIELDS: &[(u16, GDObjAttributes)] = &[
    (DONT_FADE, GDObjAttributes::dont_fade),
    (DONT_ENTER, GDObjAttributes::dont_enter),
    (NO_OBJECT_EFFECTS, GDObjAttributes::no_effects),
    (IS_GROUP_PARENT, GDObjAttributes::is_group_parent),
    (IS_AREA_PARENT, GDObjAttributes::is_area_parent),
    (DONT_BOOST_X, GDObjAttributes::dont_boost_x),
    (DONT_BOOST_Y, GDObjAttributes::dont_boost_y),
    (IS_HIGH_DETAIL, GDObjAttributes::high_detail),
    (NO_TOUCH, GDObjAttributes::no_touch),
    (PASSABLE, GDObjAttributes::passable),
    (HIDDEN, GDObjAttributes::hidden),
    (NONSTICK_X, GDObjAttributes::non_stick_x),
    (NONSTICK_Y, GDObjAttributes::non_stick_y),
    (EXTRA_STICKY, GDObjAttributes::extra_sticky),
    (HAS_EXTENDED_COLLISION, GDObjAttributes::extended_collision),
    (IS_ICE_BLOCK, GDObjAttributes::is_ice_block),
    (GRIP_SLOPE, GDObjAttributes::grip_slope),
    (NO_GLOW, GDObjAttributes::no_glow),
    (NO_PARTICLES, GDObjAttributes::no_particles),
    (SCALE_STICK, GDObjAttributes::scale_stick),
    (NO_AUDIO_SCALE, GDObjAttributes::no_audio_scale),
    (SINGLE_PLAYER_TOUCH, GDObjAttributes::single_ptouch),
    (CENTER_EFFECT, GDObjAttributes::center_effect),
    (REVERSES_GAMEPLAY, GDObjAttributes::reverse),
];

const GDOBJ_ATTR_PROPSTR_ALLOCSIZE: usize = GDOBJ_ATTR_FIELDS.len() * 6;

impl GDObjAttributes {
    #[inline]
    /// Makes a default instance of this object
    pub fn new() -> Self {
        Self::default()
    }

    /// Serialises this object to a string
    #[inline]
    #[must_use]
    pub fn get_property_str(&self) -> String {
        let mut properties_str = String::with_capacity(GDOBJ_ATTR_PROPSTR_ALLOCSIZE);

        for (id, flag) in GDOBJ_ATTR_FIELDS {
            if self.contains(*flag) {
                let _ = write!(properties_str, ",{id},1");
            }
        }
        properties_str
    }
}

/// Trigger config, used for defining general properties of a trigger object
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TriggerConfig {
    /// is touch triggerable?
    pub touchable: bool,
    /// is spawn triggerable?
    pub spawnable: bool,
    /// is multitriggerable?
    pub multitriggerable: bool,
}

fn serialise_fields<T: PartialEq + Display>(fields: &[(&str, T, T)], buf: &mut String) {
    for (id, field, default) in fields {
        if field != default {
            let _ = write!(buf, ",{id},{field}");
        }
    }
}

/// Function is separate from [`serialise_fields`] to optimise boolean serialising
fn serialise_bools(fields: &[(&str, bool)], buf: &mut String) {
    for (id, field) in fields {
        if *field {
            let _ = write!(buf, ",{id},1");
        }
    }
}

// for sorting a list of groups
impl Ord for Group {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        // check ids first
        // check the types only if equal
        match self.id().cmp(&other.id()) {
            std::cmp::Ordering::Equal => self.get_type().cmp(&other.get_type()),
            o => o,
        }
    }
}

impl PartialOrd for Group {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for GroupType {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        if self == other {
            std::cmp::Ordering::Equal
        } else if *self == Self::Regular {
            // other is parent, so is less
            std::cmp::Ordering::Greater
        } else {
            std::cmp::Ordering::Less
        }
    }
}

impl PartialOrd for GroupType {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}