genie_scx/
triggers.rs

1use crate::Result;
2use crate::UnitTypeID;
3use byteorder::{ReadBytesExt, WriteBytesExt, LE};
4use genie_support::{read_opt_u32, read_str, write_i32_str, write_opt_i32_str, StringKey};
5use std::convert::TryInto;
6use std::io::{Read, Write};
7
8/// A trigger condition, describing when a trigger can fire.
9#[derive(Debug, Default, Clone)]
10pub struct TriggerCondition {
11    condition_type: i32,
12    properties: Vec<i32>,
13}
14
15impl TriggerCondition {
16    /// Read a trigger condition from an input stream, with the given trigger system version.
17    pub fn read_from(mut input: impl Read, version: f64) -> Result<Self> {
18        let condition_type = input.read_i32::<LE>()?;
19        let num_properties = if version > 1.0 {
20            input.read_i32::<LE>()?
21        } else {
22            13
23        };
24        let mut properties = Vec::with_capacity(num_properties as usize);
25        for _ in 0..num_properties {
26            properties.push(input.read_i32::<LE>()?);
27        }
28
29        while properties.len() < 18 {
30            properties.push(-1);
31        }
32
33        Ok(Self {
34            condition_type,
35            properties,
36        })
37    }
38
39    /// Write this trigger condition to an output stream, with the given trigger system version.
40    pub fn write_to(&self, mut output: impl Write, version: f64) -> Result<()> {
41        output.write_i32::<LE>(self.condition_type)?;
42        if version > 1.0 {
43            output.write_i32::<LE>(self.properties.len() as i32)?;
44            for value in &self.properties {
45                output.write_i32::<LE>(*value)?;
46            }
47        } else {
48            for i in 0..13 {
49                output.write_i32::<LE>(*self.properties.get(i).unwrap_or(&0))?;
50            }
51        }
52
53        Ok(())
54    }
55
56    /// Get the "amount" value for this trigger condition.
57    pub fn amount(&self) -> i32 {
58        self.properties[0]
59    }
60
61    /// Set the "amount" value for this trigger condition.
62    pub fn set_amount(&mut self, amount: i32) {
63        self.properties[0] = amount;
64    }
65
66    /// Get the "resource" value for this trigger condition.
67    pub fn resource(&self) -> i32 {
68        self.properties[1]
69    }
70
71    /// Set the "resource" value for this trigger condition.
72    pub fn set_resource(&mut self, resource: i32) {
73        self.properties[1] = resource;
74    }
75
76    /// Get the "primary object" value for this trigger condition.
77    pub fn primary_object(&self) -> i32 {
78        self.properties[2]
79    }
80
81    /// Set the "primary object" value for this trigger condition.
82    pub fn set_primary_object(&mut self, primary_object: i32) {
83        self.properties[2] = primary_object;
84    }
85
86    /// Get the "secondary object" value for this trigger condition.
87    pub fn secondary_object(&self) -> i32 {
88        self.properties[3]
89    }
90
91    /// Set the "secondary object" value for this trigger condition.
92    pub fn set_secondary_object(&mut self, secondary_object: i32) {
93        self.properties[3] = secondary_object;
94    }
95
96    /// Get the raw "unit type" value for this trigger condition.
97    pub fn raw_unit_type(&self) -> i32 {
98        self.properties[4]
99    }
100
101    /// Set the raw "unit type" value for this trigger condition.
102    pub fn set_raw_unit_type(&mut self, unit_type: i32) {
103        self.properties[4] = unit_type;
104    }
105
106    /// Get the "unit type" value for this trigger condition.
107    pub fn unit_type(&self) -> UnitTypeID {
108        self.properties[4].try_into().unwrap()
109    }
110
111    /// Set the "unit type" value for this trigger condition.
112    pub fn set_unit_type(&mut self, unit_type: UnitTypeID) {
113        self.properties[4] = unit_type.try_into().unwrap();
114    }
115
116    /// Get the "player ID" value for this trigger condition.
117    pub fn player_id(&self) -> i32 {
118        self.properties[5]
119    }
120
121    /// Set the "player ID" value for this trigger condition.
122    pub fn set_player_id(&mut self, player_id: i32) {
123        self.properties[5] = player_id;
124    }
125
126    /// Get the "Tech ID" value for this trigger condition.
127    pub fn technology_id(&self) -> i32 {
128        self.properties[6]
129    }
130
131    /// Set the "Tech ID" value for this trigger condition.
132    pub fn set_technology_id(&mut self, technology_id: i32) {
133        self.properties[6] = technology_id;
134    }
135
136    /// Get the "Timer" value for this trigger condition.
137    pub fn timer(&self) -> i32 {
138        self.properties[7]
139    }
140
141    /// Set the "Timer" value for this trigger condition.
142    pub fn set_timer(&mut self, timer: i32) {
143        self.properties[7] = timer;
144    }
145
146    /// Get the "Trigger ID" value for this trigger condition.
147    pub fn trigger_id(&self) -> i32 {
148        self.properties[8]
149    }
150
151    /// Set the "Trigger ID" value for this trigger condition.
152    pub fn set_trigger_id(&mut self, trigger_id: i32) {
153        self.properties[8] = trigger_id;
154    }
155
156    /// Get the area this trigger condition applies to.
157    ///
158    /// -1 values indicate no area is set.
159    pub fn area(&self) -> (i32, i32, i32, i32) {
160        (
161            self.properties[9],
162            self.properties[10],
163            self.properties[11],
164            self.properties[12],
165        )
166    }
167
168    /// Set the area this trigger condition applies to.
169    ///
170    /// -1 values indicate no area is set.
171    pub fn set_area(&mut self, area: (i32, i32, i32, i32)) {
172        self.properties[9] = area.0;
173        self.properties[10] = area.1;
174        self.properties[11] = area.2;
175        self.properties[12] = area.3;
176    }
177
178    /// Get the "unit group" value for this trigger condition.
179    pub fn unit_group(&self) -> i32 {
180        self.properties[13]
181    }
182
183    /// Set the "unit group" value for this trigger condition.
184    pub fn set_unit_group(&mut self, unit_group: i32) {
185        self.properties[13] = unit_group;
186    }
187
188    /// Get the "Object Type" value for this trigger condition.
189    pub fn object_type(&self) -> UnitTypeID {
190        self.properties[14].try_into().unwrap()
191    }
192
193    /// Set the "Object Type" value for this trigger condition.
194    pub fn set_object_type(&mut self, object_type: UnitTypeID) {
195        self.properties[14] = i32::from(object_type);
196    }
197
198    /// Get the "AI Signal" value for this trigger condition.
199    pub fn ai_signal(&self) -> i32 {
200        self.properties[15]
201    }
202
203    /// Set the "AI Signal" value for this trigger condition.
204    pub fn set_ai_signal(&mut self, ai_signal: i32) {
205        self.properties[15] = ai_signal;
206    }
207
208    /// Get the "Inverted" value for this trigger condition.
209    pub fn inverted(&self) -> bool {
210        self.properties[16] == 1
211    }
212
213    /// Set the "Inverted" value for this trigger condition.
214    pub fn set_inverted(&mut self, inverted: i32) {
215        self.properties[16] = inverted;
216    }
217}
218
219/// A trigger effect, describing the response when a trigger fires.
220#[derive(Debug, Default, Clone)]
221pub struct TriggerEffect {
222    effect_type: i32,
223    properties: Vec<i32>,
224    chat_text: Option<String>,
225    audio_file: Option<String>,
226    objects: Vec<i32>,
227}
228
229impl TriggerEffect {
230    /// Read a trigger effect from an input stream, with the given trigger system version.
231    pub fn read_from(mut input: impl Read, version: f64) -> Result<Self> {
232        let effect_type = input.read_i32::<LE>()?;
233        let num_properties = if version > 1.0 {
234            input.read_i32::<LE>()?
235        } else {
236            16
237        };
238        let mut properties = Vec::with_capacity(num_properties as usize);
239        for _ in 0..num_properties {
240            properties.push(input.read_i32::<LE>()?);
241        }
242
243        while properties.len() < 24 {
244            properties.push(-1);
245        }
246
247        let len = input.read_i32::<LE>()? as usize;
248        let chat_text = read_str(&mut input, len)?;
249        let len = input.read_i32::<LE>()? as usize;
250        let audio_file = read_str(&mut input, len)?;
251        let mut objects = vec![];
252
253        if version > 1.1 {
254            for _ in 0..properties[4] {
255                objects.push(input.read_i32::<LE>()?);
256            }
257        } else {
258            objects.push(properties[4]);
259            properties[4] = 1;
260        }
261
262        Ok(Self {
263            effect_type,
264            properties,
265            chat_text,
266            audio_file,
267            objects,
268        })
269    }
270
271    /// Write a trigger effect to an output stream, with the given trigger system version.
272    pub fn write_to(&self, mut output: impl Write, version: f64) -> Result<()> {
273        output.write_i32::<LE>(self.effect_type)?;
274        output.write_i32::<LE>(self.properties.len() as i32)?;
275        for value in &self.properties {
276            output.write_i32::<LE>(*value)?;
277        }
278
279        write_opt_i32_str(&mut output, &self.chat_text)?;
280        write_opt_i32_str(&mut output, &self.audio_file)?;
281
282        if version > 1.1 {
283            for i in 0..self.num_objects() {
284                output.write_i32::<LE>(*self.objects.get(i as usize).unwrap_or(&-1))?;
285            }
286        }
287
288        Ok(())
289    }
290
291    /// Get the "AI Goal" value for this trigger effect.
292    pub fn ai_goal(&self) -> i32 {
293        self.properties[0]
294    }
295
296    /// Set the "AI Goal" value for this trigger effect.
297    pub fn set_ai_goal(&mut self, ai_goal: i32) {
298        self.properties[0] = ai_goal;
299    }
300
301    /// Get the "Amount" value for this trigger effect.
302    pub fn amount(&self) -> i32 {
303        self.properties[1]
304    }
305
306    /// Set the "Amount" value for this trigger effect.
307    pub fn set_amount(&mut self, amount: i32) {
308        self.properties[1] = amount;
309    }
310
311    /// Get the "Resource" value for this trigger effect.
312    pub fn resource(&self) -> i32 {
313        self.properties[2]
314    }
315
316    /// Set the "Resource" value for this trigger effect.
317    pub fn set_resource(&mut self, resource: i32) {
318        self.properties[2] = resource;
319    }
320
321    /// Get the "Diplomacy" value for this trigger effect.
322    pub fn diplomacy(&self) -> i32 {
323        self.properties[3]
324    }
325
326    /// Set the "Diplomacy" value for this trigger effect.
327    pub fn set_diplomacy(&mut self, diplomacy: i32) {
328        self.properties[3] = diplomacy;
329    }
330
331    /// Get the "Number of Objects" value for this trigger effect.
332    pub fn num_objects(&self) -> i32 {
333        self.properties[4]
334    }
335
336    /// Set the "Number of Objects" value for this trigger effect.
337    pub fn set_num_objects(&mut self, num_objects: i32) {
338        self.properties[4] = num_objects;
339    }
340
341    /// Get the "Object ID" value for this trigger effect.
342    pub fn object_id(&self) -> i32 {
343        self.properties[5]
344    }
345
346    /// Set the "Object ID" value for this trigger effect.
347    pub fn set_object_id(&mut self, object_id: i32) {
348        self.properties[5] = object_id;
349    }
350
351    /// Get the "Unit Type" value for this trigger effect.
352    pub fn unit_type(&self) -> UnitTypeID {
353        self.properties[6].try_into().unwrap()
354    }
355
356    /// Set the "Unit Type" value for this trigger effect.
357    pub fn set_unit_type(&mut self, unit_type: UnitTypeID) {
358        self.properties[6] = i32::from(unit_type);
359    }
360
361    /// Get the "Source Player" value for this trigger effect.
362    pub fn source_player_id(&self) -> i32 {
363        self.properties[7]
364    }
365
366    /// Set the "Source Player" value for this trigger effect.
367    pub fn set_source_player_id(&mut self, source_player_id: i32) {
368        self.properties[7] = source_player_id;
369    }
370
371    /// Get the "Target Player" value for this trigger effect.
372    pub fn target_player_id(&self) -> i32 {
373        self.properties[8]
374    }
375
376    /// Set the "Target Player" value for this trigger effect.
377    pub fn set_target_player_id(&mut self, target_player_id: i32) {
378        self.properties[8] = target_player_id;
379    }
380
381    /// Get the "Tech ID" value for this trigger effect.
382    pub fn technology_id(&self) -> i32 {
383        self.properties[9]
384    }
385
386    /// Set the "Tech ID" value for this trigger effect.
387    pub fn set_technology_id(&mut self, technology_id: i32) {
388        self.properties[9] = technology_id;
389    }
390
391    /// Get the "Text ID" value for this trigger effect.
392    pub fn text_id(&self) -> i32 {
393        self.properties[10]
394    }
395
396    /// Set the "Text ID" value for this trigger effect.
397    pub fn set_text_id(&mut self, text_id: i32) {
398        self.properties[10] = text_id;
399    }
400
401    /// Get the "Sound ID" value for this trigger effect.
402    pub fn sound_id(&self) -> i32 {
403        self.properties[11]
404    }
405
406    /// Set the "Sound ID" value for this trigger effect.
407    pub fn set_sound_id(&mut self, sound_id: i32) {
408        self.properties[11] = sound_id;
409    }
410
411    /// Get the "Timer" value for this trigger effect.
412    pub fn timer(&self) -> i32 {
413        self.properties[12]
414    }
415
416    /// Set the "Timer" value for this trigger effect.
417    pub fn set_timer(&mut self, timer: i32) {
418        self.properties[12] = timer;
419    }
420
421    /// Get the "Trigger ID" value for this trigger effect.
422    pub fn trigger_id(&self) -> i32 {
423        self.properties[13]
424    }
425
426    /// Set the "Trigger ID" value for this trigger effect.
427    pub fn set_trigger_id(&mut self, trigger_id: i32) {
428        self.properties[13] = trigger_id;
429    }
430
431    /// Get the location this trigger effect applies to.
432    pub fn location(&self) -> (i32, i32) {
433        (self.properties[14], self.properties[15])
434    }
435
436    /// Set the location this trigger effect applies to.
437    pub fn set_location(&mut self, location: (i32, i32)) {
438        self.properties[14] = location.0;
439        self.properties[15] = location.1;
440    }
441
442    /// Get the area this trigger effect applies to.
443    pub fn area(&self) -> (i32, i32, i32, i32) {
444        (
445            self.properties[16],
446            self.properties[17],
447            self.properties[18],
448            self.properties[19],
449        )
450    }
451
452    /// Set the area this trigger effect applies to.
453    pub fn set_area(&mut self, area: (i32, i32, i32, i32)) {
454        self.properties[16] = area.0;
455        self.properties[17] = area.1;
456        self.properties[18] = area.2;
457        self.properties[19] = area.3;
458    }
459
460    /// Get the "Object Group" value for this trigger effect.
461    pub fn object_group(&self) -> i32 {
462        self.properties[20]
463    }
464
465    /// Set the "Object Group" value for this trigger effect.
466    pub fn set_object_group(&mut self, object_group: i32) {
467        self.properties[20] = object_group;
468    }
469
470    /// Get the "Object Type" value for this trigger effect.
471    pub fn object_type(&self) -> UnitTypeID {
472        self.properties[21].try_into().unwrap()
473    }
474
475    /// Set the "Object Type" value for this trigger effect.
476    pub fn set_object_type(&mut self, object_type: UnitTypeID) {
477        self.properties[21] = i32::from(object_type);
478    }
479
480    /// Get the "Line ID" value for this trigger effect.
481    pub fn line_id(&self) -> i32 {
482        self.properties[22]
483    }
484
485    /// Set the "Line ID" value for this trigger effect.
486    pub fn set_line_id(&mut self, line_id: i32) {
487        self.properties[22] = line_id;
488    }
489
490    /// Get the "Stance" value for this trigger effect.
491    pub fn stance(&self) -> i32 {
492        self.properties[23]
493    }
494
495    /// Set the "Stance" value for this trigger effect.
496    pub fn set_stance(&mut self, stance: i32) {
497        self.properties[23] = stance;
498    }
499}
500
501/// A trigger, describing automatic interactive behaviours in a scenario.
502#[derive(Debug, Clone)]
503pub struct Trigger {
504    enabled: bool,
505    looping: bool,
506    name_id: i32,
507    is_objective: bool,
508    objective_order: i32,
509    start_time: u32,
510    description: Option<String>,
511    short_description_id: Option<StringKey>,
512    short_description: Option<String>,
513    display_short_description: bool,
514    short_description_state: u8,
515    mute_objective: bool,
516    name: Option<String>,
517    effects: Vec<TriggerEffect>,
518    effect_order: Vec<i32>,
519    conditions: Vec<TriggerCondition>,
520    condition_order: Vec<i32>,
521    make_header: bool,
522}
523
524impl Trigger {
525    /// Read a trigger from an input stream, with the given trigger system version.
526    pub fn read_from(mut input: impl Read, version: f64) -> Result<Self> {
527        let enabled = input.read_i32::<LE>()? != 0;
528        let looping = input.read_i8()? != 0;
529        let name_id = input.read_i32::<LE>()?;
530        let is_objective = input.read_i8()? != 0;
531        let objective_order = input.read_i32::<LE>()?;
532
533        let mut make_header = false;
534        let mut short_description_id = None;
535        let mut short_description_state = 0;
536        let mut display_short_description = false;
537        let mut mute_objective = false;
538        let start_time;
539        if version >= 1.8 {
540            make_header = input.read_u8()? != 0;
541            short_description_id = read_opt_u32(&mut input)?;
542            display_short_description = input.read_u8()? != 0;
543            short_description_state = input.read_u8()?;
544            start_time = input.read_u32::<LE>()?;
545            mute_objective = input.read_u8()? != 0;
546        } else {
547            start_time = input.read_u32::<LE>()?;
548        }
549
550        let description = {
551            let len = input.read_u32::<LE>()? as usize;
552            read_str(&mut input, len)?
553        };
554
555        let name = {
556            let len = input.read_u32::<LE>()? as usize;
557            read_str(&mut input, len)?
558        };
559
560        let short_description = if version >= 1.8 {
561            let len = input.read_u32::<LE>()? as usize;
562            read_str(&mut input, len)?
563        } else {
564            None
565        };
566
567        let num_effects = input.read_i32::<LE>()?;
568        let mut effects = vec![];
569        let mut effect_order = vec![];
570        for _ in 0..num_effects {
571            effects.push(TriggerEffect::read_from(&mut input, version)?);
572        }
573        for _ in 0..num_effects {
574            effect_order.push(input.read_i32::<LE>()?);
575        }
576
577        let num_conditions = input.read_i32::<LE>()?;
578        let mut conditions = vec![];
579        let mut condition_order = vec![];
580        for _ in 0..num_conditions {
581            conditions.push(TriggerCondition::read_from(&mut input, version)?);
582        }
583        for _ in 0..num_conditions {
584            condition_order.push(input.read_i32::<LE>()?);
585        }
586
587        Ok(Trigger {
588            enabled,
589            looping,
590            name_id,
591            is_objective,
592            objective_order,
593            start_time,
594            description,
595            short_description,
596            short_description_id,
597            display_short_description,
598            short_description_state,
599            mute_objective,
600            name,
601            effects,
602            effect_order,
603            conditions,
604            condition_order,
605            make_header,
606        })
607    }
608
609    /// Write this trigger condition to an output stream, with the given trigger system version.
610    pub fn write_to(&self, mut output: impl Write, version: f64) -> Result<()> {
611        output.write_i32::<LE>(if self.enabled { 1 } else { 0 })?;
612        output.write_i8(if self.looping { 1 } else { 0 })?;
613        output.write_i32::<LE>(self.name_id)?;
614        output.write_i8(if self.is_objective { 1 } else { 0 })?;
615        output.write_i32::<LE>(self.objective_order)?;
616
617        if version >= 1.8 {
618            output.write_u8(if self.make_header { 1 } else { 0 })?;
619            write_opt_string_key(&mut output, &self.short_description_id)?;
620            output.write_u8(if self.display_short_description { 1 } else { 0 })?;
621            output.write_u8(self.short_description_state)?;
622            output.write_u32::<LE>(self.start_time)?;
623            output.write_u8(if self.mute_objective { 1 } else { 0 })?;
624        } else {
625            output.write_u32::<LE>(self.start_time)?;
626        }
627
628        write_opt_i32_str(&mut output, &self.description)?;
629        write_opt_i32_str(&mut output, &self.name)?;
630        if version >= 1.8 {
631            write_opt_i32_str(&mut output, &self.short_description)?;
632        }
633
634        output.write_u32::<LE>(self.effects.len() as u32)?;
635        for effect in &self.effects {
636            effect.write_to(&mut output, version)?;
637        }
638        for order in &self.effect_order {
639            output.write_i32::<LE>(*order)?;
640        }
641        output.write_u32::<LE>(self.conditions.len() as u32)?;
642        for condition in &self.conditions {
643            condition.write_to(&mut output, version)?;
644        }
645        for order in &self.condition_order {
646            output.write_i32::<LE>(*order)?;
647        }
648
649        Ok(())
650    }
651
652    /// Get the conditions in this trigger, in display order.
653    pub fn conditions(&self) -> impl Iterator<Item = &TriggerCondition> {
654        self.condition_order
655            .iter()
656            .map(move |index| &self.conditions[*index as usize])
657    }
658
659    /// Get the conditions in this trigger, unordered.
660    pub fn conditions_unordered_mut(&mut self) -> impl Iterator<Item = &mut TriggerCondition> {
661        self.conditions.iter_mut()
662    }
663
664    /// Get the effects in this trigger, in display order.
665    pub fn effects(&self) -> impl Iterator<Item = &TriggerEffect> {
666        self.effect_order
667            .iter()
668            .map(move |index| &self.effects[*index as usize])
669    }
670
671    /// Get the effects in this trigger, unordered.
672    pub fn effects_unordered_mut(&mut self) -> impl Iterator<Item = &mut TriggerEffect> {
673        self.effects.iter_mut()
674    }
675}
676
677/// The trigger system maintains an ordered list  of triggers.
678#[derive(Debug, Clone)]
679pub struct TriggerSystem {
680    version: f64,
681    objectives_state: i8,
682    triggers: Vec<Trigger>,
683    trigger_order: Vec<i32>,
684    enabled_techs: Vec<u32>,
685    variable_values: Vec<u32>,
686    variable_names: Vec<String>,
687}
688
689impl Default for TriggerSystem {
690    fn default() -> Self {
691        Self {
692            version: 1.6,
693            objectives_state: 0,
694            triggers: vec![],
695            trigger_order: vec![],
696            enabled_techs: vec![],
697            variable_values: vec![0; 256],
698            variable_names: vec![String::new(); 256],
699        }
700    }
701}
702
703impl TriggerSystem {
704    /// Read a trigger system from an input stream.
705    pub fn read_from(mut input: impl Read) -> Result<Self> {
706        let version = input.read_f64::<LE>()?;
707        log::debug!("Trigger system version {}", version);
708        let objectives_state = if version >= 1.5 { input.read_i8()? } else { 0 };
709
710        let num_triggers = input.read_i32::<LE>()?;
711        log::debug!("{} triggers", num_triggers);
712
713        let mut triggers = vec![];
714        let mut trigger_order = vec![];
715        for _ in 0..num_triggers {
716            triggers.push(Trigger::read_from(&mut input, version)?);
717        }
718        if version >= 1.4 {
719            for _ in 0..num_triggers {
720                trigger_order.push(input.read_i32::<LE>()?);
721            }
722        } else {
723            for i in 0..num_triggers {
724                trigger_order.push(i);
725            }
726        }
727
728        let mut variable_values = vec![];
729        let mut enabled_techs = vec![];
730        let mut variable_names = vec![];
731
732        if version >= 2.2 {
733            variable_values.resize(256, 0);
734            input.read_u32_into::<LE>(&mut variable_values)?;
735            enabled_techs = {
736                let num_enabled_techs = input.read_u32::<LE>()?;
737                let mut enabled_techs = vec![0; num_enabled_techs as usize];
738                input.read_u32_into::<LE>(&mut enabled_techs)?;
739                enabled_techs
740            };
741            variable_names = {
742                let num_var_names = input.read_u32::<LE>()?;
743                let mut variable_names = vec![String::new(); 256];
744                for _ in 0..num_var_names {
745                    let id = input.read_u32::<LE>()?;
746                    assert!(
747                        id < 256,
748                        "Unexpected variable number, this is probably a genie-scx bug"
749                    );
750                    let len = input.read_u32::<LE>()?;
751                    variable_names[id as usize] =
752                        read_str(&mut input, len as usize)?.unwrap_or_default();
753                }
754                variable_names
755            };
756        }
757
758        Ok(Self {
759            version,
760            objectives_state,
761            triggers,
762            trigger_order,
763            enabled_techs,
764            variable_values,
765            variable_names,
766        })
767    }
768
769    /// Write the trigger system to an output stream with the given system version.
770    pub fn write_to(&self, mut output: impl Write, version: f64) -> Result<()> {
771        output.write_f64::<LE>(version)?;
772        if version >= 1.5 {
773            output.write_i8(self.objectives_state)?;
774        }
775        output.write_u32::<LE>(self.triggers.len().try_into().unwrap())?;
776        for trigger in &self.triggers {
777            trigger.write_to(&mut output, version)?;
778        }
779        if version >= 1.4 {
780            for order in &self.trigger_order {
781                output.write_i32::<LE>(*order)?;
782            }
783        }
784        if version >= 2.2 {
785            let padded_values = self
786                .variable_values
787                .iter()
788                .cloned()
789                .chain(std::iter::repeat(0))
790                .take(256);
791            for value in padded_values {
792                output.write_u32::<LE>(value)?;
793            }
794            output.write_u32::<LE>(self.enabled_techs.len() as u32)?;
795            for id in &self.enabled_techs {
796                output.write_u32::<LE>(*id)?;
797            }
798
799            let custom_names = self
800                .variable_names
801                .iter()
802                .enumerate()
803                .filter(|(_index, name)| !name.is_empty());
804            let num_custom_names = custom_names.clone().count();
805            output.write_u32::<LE>(num_custom_names as u32)?;
806            for (index, name) in custom_names {
807                output.write_u32::<LE>(index as u32)?;
808                write_i32_str(&mut output, &name)?;
809            }
810        }
811        Ok(())
812    }
813
814    /// Get the version of the trigger system data.
815    pub fn version(&self) -> f64 {
816        self.version
817    }
818
819    /// Get the number of triggers in the trigger system.
820    pub fn num_triggers(&self) -> u32 {
821        self.triggers.len() as u32
822    }
823
824    /// Iterate over all triggers, in order.
825    pub fn triggers(&self) -> impl Iterator<Item = &Trigger> {
826        self.trigger_order
827            .iter()
828            .map(move |index| &self.triggers[*index as usize])
829    }
830
831    /// Iterate over all triggers, mutably and unordered.
832    pub fn triggers_unordered_mut(&mut self) -> impl Iterator<Item = &mut Trigger> {
833        self.triggers.iter_mut()
834    }
835}
836
837fn write_opt_string_key(mut output: impl Write, opt_key: &Option<StringKey>) -> Result<()> {
838    use std::io::{Error, ErrorKind};
839    output.write_u32::<LE>(if let Some(key) = opt_key {
840        key.try_into()
841            .map_err(|err| Error::new(ErrorKind::InvalidData, err))?
842    } else {
843        0xFFFF_FFFF
844    })?;
845    Ok(())
846}