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}