Skip to main content

rustapi/actions/
create_campfire.rs

1use crate::prelude::*;
2use rusterix::PixelSource;
3use std::str::FromStr;
4
5pub const CREATE_CAMPFIRE_ACTION_ID: &str = "0f3a940e-5f6d-4b82-b73d-123c49f9c8a1";
6
7pub struct CreateCampfire {
8    id: TheId,
9    nodeui: TheNodeUI,
10}
11
12impl CreateCampfire {
13    fn parse_tile_source(text: &str) -> Option<Value> {
14        let trimmed = text.trim();
15        if trimmed.is_empty() {
16            return None;
17        }
18        if let Ok(id) = Uuid::parse_str(trimmed) {
19            return Some(Value::Source(PixelSource::TileId(id)));
20        }
21        if let Ok(index) = trimmed.parse::<u16>() {
22            return Some(Value::Source(PixelSource::PaletteIndex(index)));
23        }
24        None
25    }
26
27    fn source_to_text(source: Option<&Value>) -> String {
28        match source {
29            Some(Value::Source(PixelSource::TileId(id))) => id.to_string(),
30            Some(Value::Source(PixelSource::PaletteIndex(i))) => i.to_string(),
31            _ => String::new(),
32        }
33    }
34
35    fn apply_sector_campfire(&self, map: &mut Map, sector_id: u32) -> bool {
36        let flame_height = self
37            .nodeui
38            .get_f32_value("actionCampfireFlameHeight")
39            .unwrap_or(0.8)
40            .max(0.0);
41        let flame_width = self
42            .nodeui
43            .get_f32_value("actionCampfireFlameWidth")
44            .unwrap_or(0.45)
45            .max(0.05);
46        let light_intensity = self
47            .nodeui
48            .get_f32_value("actionCampfireLightIntensity")
49            .unwrap_or(2.2)
50            .max(0.0);
51        let light_range = self
52            .nodeui
53            .get_f32_value("actionCampfireLightRange")
54            .unwrap_or(5.0)
55            .max(0.0);
56        let light_flicker = self
57            .nodeui
58            .get_f32_value("actionCampfireLightFlicker")
59            .unwrap_or(0.2)
60            .clamp(0.0, 1.0);
61        let light_lift = self
62            .nodeui
63            .get_f32_value("actionCampfireLightLift")
64            .unwrap_or(0.2)
65            .max(0.0);
66        let log_count = self
67            .nodeui
68            .get_i32_value("actionCampfireLogCount")
69            .unwrap_or(10)
70            .clamp(3, 24);
71        let log_length = self
72            .nodeui
73            .get_f32_value("actionCampfireLogLength")
74            .unwrap_or(0.55)
75            .max(0.05);
76        let log_thickness = self
77            .nodeui
78            .get_f32_value("actionCampfireLogThickness")
79            .unwrap_or(0.10)
80            .max(0.01);
81        let log_radius = self
82            .nodeui
83            .get_f32_value("actionCampfireLogRadius")
84            .unwrap_or(0.55)
85            .max(0.05);
86
87        let flame_tile_id_text = self
88            .nodeui
89            .get_text_value("actionCampfireFlameTileId")
90            .unwrap_or_default();
91        let base_tile_id_text = self
92            .nodeui
93            .get_text_value("actionCampfireBaseTileId")
94            .unwrap_or_default();
95
96        let Some(sector) = map.find_sector_mut(sector_id) else {
97            return false;
98        };
99
100        if flame_height <= 0.0 || light_range <= 0.0 {
101            sector
102                .properties
103                .set("sector_feature", Value::Str("None".to_string()));
104            sector.properties.remove("campfire_flame_height");
105            sector.properties.remove("campfire_flame_width");
106            sector.properties.remove("campfire_light_intensity");
107            sector.properties.remove("campfire_light_range");
108            sector.properties.remove("campfire_light_flicker");
109            sector.properties.remove("campfire_light_lift");
110            sector.properties.remove("campfire_log_count");
111            sector.properties.remove("campfire_log_length");
112            sector.properties.remove("campfire_log_thickness");
113            sector.properties.remove("campfire_log_radius");
114            sector.properties.remove("campfire_flame_source");
115            sector.properties.remove("campfire_base_source");
116            return true;
117        }
118
119        sector
120            .properties
121            .set("sector_feature", Value::Str("Campfire".to_string()));
122        sector
123            .properties
124            .set("campfire_flame_height", Value::Float(flame_height));
125        sector
126            .properties
127            .set("campfire_flame_width", Value::Float(flame_width));
128        sector
129            .properties
130            .set("campfire_light_intensity", Value::Float(light_intensity));
131        sector
132            .properties
133            .set("campfire_light_range", Value::Float(light_range));
134        sector
135            .properties
136            .set("campfire_light_flicker", Value::Float(light_flicker));
137        sector
138            .properties
139            .set("campfire_light_lift", Value::Float(light_lift));
140        sector
141            .properties
142            .set("campfire_log_count", Value::Int(log_count));
143        sector
144            .properties
145            .set("campfire_log_length", Value::Float(log_length));
146        sector
147            .properties
148            .set("campfire_log_thickness", Value::Float(log_thickness));
149        sector
150            .properties
151            .set("campfire_log_radius", Value::Float(log_radius));
152
153        if let Some(src) = Self::parse_tile_source(&flame_tile_id_text) {
154            sector.properties.set("campfire_flame_source", src);
155        } else {
156            sector.properties.remove("campfire_flame_source");
157        }
158        if let Some(src) = Self::parse_tile_source(&base_tile_id_text) {
159            sector.properties.set("campfire_base_source", src);
160        } else {
161            sector.properties.remove("campfire_base_source");
162        }
163
164        true
165    }
166}
167
168impl Action for CreateCampfire {
169    fn new() -> Self
170    where
171        Self: Sized,
172    {
173        let mut nodeui = TheNodeUI::default();
174
175        nodeui.add_item(TheNodeUIItem::OpenTree("campfire".into()));
176        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
177            "actionCampfireFlameHeight".into(),
178            "".into(),
179            "".into(),
180            0.8,
181            0.0..=4.0,
182            false,
183        ));
184        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
185            "actionCampfireFlameWidth".into(),
186            "".into(),
187            "".into(),
188            0.45,
189            0.05..=3.0,
190            false,
191        ));
192        nodeui.add_item(TheNodeUIItem::IntEditSlider(
193            "actionCampfireLogCount".into(),
194            "".into(),
195            "".into(),
196            10,
197            3..=24,
198            false,
199        ));
200        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
201            "actionCampfireLogLength".into(),
202            "".into(),
203            "".into(),
204            0.55,
205            0.05..=3.0,
206            false,
207        ));
208        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
209            "actionCampfireLogThickness".into(),
210            "".into(),
211            "".into(),
212            0.10,
213            0.01..=1.0,
214            false,
215        ));
216        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
217            "actionCampfireLogRadius".into(),
218            "".into(),
219            "".into(),
220            0.55,
221            0.05..=3.0,
222            false,
223        ));
224        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
225            "actionCampfireLightIntensity".into(),
226            "".into(),
227            "".into(),
228            2.2,
229            0.0..=12.0,
230            false,
231        ));
232        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
233            "actionCampfireLightRange".into(),
234            "".into(),
235            "".into(),
236            5.0,
237            0.0..=30.0,
238            false,
239        ));
240        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
241            "actionCampfireLightFlicker".into(),
242            "".into(),
243            "".into(),
244            0.2,
245            0.0..=1.0,
246            false,
247        ));
248        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
249            "actionCampfireLightLift".into(),
250            "".into(),
251            "".into(),
252            0.2,
253            0.0..=3.0,
254            false,
255        ));
256        nodeui.add_item(TheNodeUIItem::CloseTree);
257
258        nodeui.add_item(TheNodeUIItem::OpenTree("material".into()));
259        nodeui.add_item(TheNodeUIItem::Text(
260            "actionCampfireFlameTileId".into(),
261            "".into(),
262            "".into(),
263            "".into(),
264            None,
265            false,
266        ));
267        nodeui.add_item(TheNodeUIItem::Text(
268            "actionCampfireBaseTileId".into(),
269            "".into(),
270            "".into(),
271            "".into(),
272            None,
273            false,
274        ));
275        nodeui.add_item(TheNodeUIItem::CloseTree);
276        nodeui.add_item(TheNodeUIItem::Markdown("desc".into(), "".into()));
277
278        Self {
279            id: TheId::named_with_id(
280                "Create Campfire",
281                Uuid::from_str(CREATE_CAMPFIRE_ACTION_ID).unwrap(),
282            ),
283            nodeui,
284        }
285    }
286
287    fn id(&self) -> TheId {
288        self.id.clone()
289    }
290
291    fn info(&self) -> String {
292        "Configure a procedural campfire and light on selected sectors.".to_string()
293    }
294
295    fn role(&self) -> ActionRole {
296        ActionRole::Editor
297    }
298
299    fn is_applicable(&self, map: &Map, _ctx: &mut TheContext, server_ctx: &ServerContext) -> bool {
300        if server_ctx.editor_view_mode == EditorViewMode::D2 {
301            return false;
302        }
303        !map.selected_sectors.is_empty()
304    }
305
306    fn load_params(&mut self, map: &Map) {
307        let Some(sector_id) = map.selected_sectors.first() else {
308            return;
309        };
310        let Some(sector) = map.find_sector(*sector_id) else {
311            return;
312        };
313
314        self.nodeui.set_f32_value(
315            "actionCampfireFlameHeight",
316            sector
317                .properties
318                .get_float_default("campfire_flame_height", 0.8),
319        );
320        self.nodeui.set_f32_value(
321            "actionCampfireFlameWidth",
322            sector
323                .properties
324                .get_float_default("campfire_flame_width", 0.45),
325        );
326        self.nodeui.set_f32_value(
327            "actionCampfireLightIntensity",
328            sector
329                .properties
330                .get_float_default("campfire_light_intensity", 2.2),
331        );
332        self.nodeui.set_f32_value(
333            "actionCampfireLightRange",
334            sector
335                .properties
336                .get_float_default("campfire_light_range", 5.0),
337        );
338        self.nodeui.set_f32_value(
339            "actionCampfireLightFlicker",
340            sector
341                .properties
342                .get_float_default("campfire_light_flicker", 0.2),
343        );
344        self.nodeui.set_f32_value(
345            "actionCampfireLightLift",
346            sector
347                .properties
348                .get_float_default("campfire_light_lift", 0.2),
349        );
350        self.nodeui.set_i32_value(
351            "actionCampfireLogCount",
352            sector.properties.get_int_default("campfire_log_count", 10),
353        );
354        self.nodeui.set_f32_value(
355            "actionCampfireLogLength",
356            sector
357                .properties
358                .get_float_default("campfire_log_length", 0.55),
359        );
360        self.nodeui.set_f32_value(
361            "actionCampfireLogThickness",
362            sector
363                .properties
364                .get_float_default("campfire_log_thickness", 0.10),
365        );
366        self.nodeui.set_f32_value(
367            "actionCampfireLogRadius",
368            sector
369                .properties
370                .get_float_default("campfire_log_radius", 0.55),
371        );
372
373        self.nodeui.set_text_value(
374            "actionCampfireFlameTileId",
375            Self::source_to_text(sector.properties.get("campfire_flame_source")),
376        );
377        self.nodeui.set_text_value(
378            "actionCampfireBaseTileId",
379            Self::source_to_text(sector.properties.get("campfire_base_source")),
380        );
381    }
382
383    fn apply(
384        &self,
385        map: &mut Map,
386        _ui: &mut TheUI,
387        _ctx: &mut TheContext,
388        server_ctx: &mut ServerContext,
389    ) -> Option<ProjectUndoAtom> {
390        let prev = map.clone();
391        let mut changed = false;
392
393        for sector_id in map.selected_sectors.clone() {
394            changed |= self.apply_sector_campfire(map, sector_id);
395        }
396
397        if changed {
398            Some(ProjectUndoAtom::MapEdit(
399                server_ctx.pc,
400                Box::new(prev),
401                Box::new(map.clone()),
402            ))
403        } else {
404            None
405        }
406    }
407
408    fn params(&self) -> TheNodeUI {
409        self.nodeui.clone()
410    }
411
412    fn handle_event(
413        &mut self,
414        event: &TheEvent,
415        _project: &mut Project,
416        _ui: &mut TheUI,
417        _ctx: &mut TheContext,
418        _server_ctx: &mut ServerContext,
419    ) -> bool {
420        self.nodeui.handle_event(event)
421    }
422}