Skip to main content

rustapi/actions/
edit_linedef.rs

1use crate::prelude::*;
2use rusterix::PixelSource;
3use std::str::FromStr;
4
5pub const EDIT_LINEDEF_ACTION_ID: &str = "284638fa-5769-442a-a55e-88121a37f193";
6
7pub struct EditLinedef {
8    id: TheId,
9    nodeui: TheNodeUI,
10    show_terrain: bool,
11}
12
13impl EditLinedef {
14    fn build_nodeui(show_terrain: bool) -> TheNodeUI {
15        let mut nodeui: TheNodeUI = TheNodeUI::default();
16
17        let item = TheNodeUIItem::Text(
18            "actionLinedefName".into(),
19            "".into(),
20            "".into(),
21            "".into(),
22            None,
23            false,
24        );
25        nodeui.add_item(item);
26
27        if show_terrain {
28            nodeui.add_item(TheNodeUIItem::OpenTree("terrain".into()));
29            nodeui.add_item(TheNodeUIItem::Checkbox(
30                "actionTerrainSmooth".into(),
31                "".into(),
32                "".into(),
33                false,
34            ));
35            nodeui.add_item(TheNodeUIItem::FloatEditSlider(
36                "actionTerrainWidth".into(),
37                "".into(),
38                "".into(),
39                2.0,
40                0.0..=128.0,
41                false,
42            ));
43            nodeui.add_item(TheNodeUIItem::FloatEditSlider(
44                "actionTerrainFalloffDistance".into(),
45                "".into(),
46                "".into(),
47                3.0,
48                0.0..=128.0,
49                false,
50            ));
51            nodeui.add_item(TheNodeUIItem::FloatEditSlider(
52                "actionTerrainFalloffSteepness".into(),
53                "".into(),
54                "".into(),
55                2.0,
56                0.1..=16.0,
57                false,
58            ));
59            nodeui.add_item(TheNodeUIItem::Icons(
60                "actionTerrainTile".into(),
61                "".into(),
62                "".into(),
63                vec![(
64                    TheRGBABuffer::new(TheDim::sized(36, 36)),
65                    "".to_string(),
66                    Uuid::nil(),
67                )],
68            ));
69            nodeui.add_item(TheNodeUIItem::Text(
70                "actionTileId".into(),
71                "".into(),
72                "".into(),
73                "".into(),
74                None,
75                false,
76            ));
77            nodeui.add_item(TheNodeUIItem::FloatEditSlider(
78                "actionTerrainTileFalloff".into(),
79                "".into(),
80                "".into(),
81                1.0,
82                0.0..=16.0,
83                false,
84            ));
85            nodeui.add_item(TheNodeUIItem::CloseTree);
86        }
87
88        let item = TheNodeUIItem::Markdown("desc".into(), "".into());
89        nodeui.add_item(item);
90
91        nodeui
92    }
93}
94
95impl Action for EditLinedef {
96    fn new() -> Self
97    where
98        Self: Sized,
99    {
100        let nodeui = Self::build_nodeui(true);
101
102        Self {
103            id: TheId::named_with_id(
104                &fl!("action_edit_linedef"),
105                Uuid::from_str(EDIT_LINEDEF_ACTION_ID).unwrap(),
106            ),
107            nodeui,
108            show_terrain: true,
109        }
110    }
111
112    fn id(&self) -> TheId {
113        self.id.clone()
114    }
115
116    fn info(&self) -> String {
117        fl!("action_edit_linedef_desc")
118    }
119
120    fn role(&self) -> ActionRole {
121        ActionRole::Editor
122    }
123
124    fn accel(&self) -> Option<TheAccelerator> {
125        None
126    }
127
128    fn is_applicable(&self, map: &Map, _ctx: &mut TheContext, _server_ctx: &ServerContext) -> bool {
129        !map.selected_linedefs.is_empty()
130    }
131
132    fn load_params(&mut self, map: &Map) {
133        if let Some(linedef_id) = map.selected_linedefs.first() {
134            if let Some(linedef) = map.find_linedef(*linedef_id) {
135                self.nodeui
136                    .set_text_value("actionLinedefName", linedef.name.clone());
137                self.nodeui.set_bool_value(
138                    "actionTerrainSmooth",
139                    linedef.properties.get_bool_default("terrain_smooth", false),
140                );
141                self.nodeui.set_f32_value(
142                    "actionTerrainWidth",
143                    linedef.properties.get_float_default("terrain_width", 2.0),
144                );
145                self.nodeui.set_f32_value(
146                    "actionTerrainFalloffDistance",
147                    linedef
148                        .properties
149                        .get_float_default("terrain_falloff_distance", 3.0),
150                );
151                self.nodeui.set_f32_value(
152                    "actionTerrainFalloffSteepness",
153                    linedef
154                        .properties
155                        .get_float_default("terrain_falloff_steepness", 2.0),
156                );
157                self.nodeui.set_f32_value(
158                    "actionTerrainTileFalloff",
159                    linedef
160                        .properties
161                        .get_float_default("terrain_tile_falloff", 1.0),
162                );
163                let terrain_tile_id = if let Some(Value::Source(PixelSource::TileId(id))) =
164                    linedef.properties.get("terrain_source")
165                {
166                    *id
167                } else {
168                    Uuid::nil()
169                };
170                self.nodeui.set_text_value(
171                    "actionTileId",
172                    if terrain_tile_id == Uuid::nil() {
173                        String::new()
174                    } else {
175                        terrain_tile_id.to_string()
176                    },
177                );
178                if let Some(item) = self.nodeui.get_item_mut("actionTerrainTile")
179                    && let TheNodeUIItem::Icons(_, _, _, items) = item
180                    && items.len() == 1
181                {
182                    items[0].2 = terrain_tile_id;
183                }
184            }
185        }
186    }
187
188    fn load_params_project(&mut self, project: &Project, server_ctx: &mut ServerContext) {
189        let show_terrain = server_ctx.get_map_context() == MapContext::Region;
190        if show_terrain != self.show_terrain {
191            let name = self
192                .nodeui
193                .get_text_value("actionLinedefName")
194                .unwrap_or_default();
195            let terrain_smooth = self
196                .nodeui
197                .get_bool_value("actionTerrainSmooth")
198                .unwrap_or(false);
199            let terrain_width = self
200                .nodeui
201                .get_f32_value("actionTerrainWidth")
202                .unwrap_or(2.0);
203            let terrain_falloff_distance = self
204                .nodeui
205                .get_f32_value("actionTerrainFalloffDistance")
206                .unwrap_or(3.0);
207            let terrain_falloff_steepness = self
208                .nodeui
209                .get_f32_value("actionTerrainFalloffSteepness")
210                .unwrap_or(2.0);
211            let terrain_tile_falloff = self
212                .nodeui
213                .get_f32_value("actionTerrainTileFalloff")
214                .unwrap_or(1.0);
215            let tile_id_text = self
216                .nodeui
217                .get_text_value("actionTileId")
218                .unwrap_or_default();
219            let terrain_tile_id = self
220                .nodeui
221                .get_tile_id("actionTerrainTile", 0)
222                .unwrap_or(Uuid::nil());
223
224            self.nodeui = Self::build_nodeui(show_terrain);
225            self.show_terrain = show_terrain;
226
227            self.nodeui.set_text_value("actionLinedefName", name);
228            self.nodeui
229                .set_bool_value("actionTerrainSmooth", terrain_smooth);
230            self.nodeui
231                .set_f32_value("actionTerrainWidth", terrain_width);
232            self.nodeui
233                .set_f32_value("actionTerrainFalloffDistance", terrain_falloff_distance);
234            self.nodeui
235                .set_f32_value("actionTerrainFalloffSteepness", terrain_falloff_steepness);
236            self.nodeui
237                .set_f32_value("actionTerrainTileFalloff", terrain_tile_falloff);
238            self.nodeui.set_text_value("actionTileId", tile_id_text);
239            if let Some(item) = self.nodeui.get_item_mut("actionTerrainTile")
240                && let TheNodeUIItem::Icons(_, _, _, items) = item
241                && items.len() == 1
242            {
243                items[0].2 = terrain_tile_id;
244            }
245        }
246
247        let mut tile_icon = TheRGBABuffer::new(TheDim::sized(36, 36));
248        let mut tile_id = Uuid::nil();
249        if let Some(map) = project.get_map(server_ctx)
250            && let Some(linedef_id) = map.selected_linedefs.first()
251            && let Some(linedef) = map.find_linedef(*linedef_id)
252            && let Some(Value::Source(PixelSource::TileId(id))) =
253                linedef.properties.get("terrain_source")
254            && let Some(tile) = project.tiles.get(id)
255            && !tile.is_empty()
256        {
257            tile_icon = tile.textures[0].to_rgba();
258            tile_id = *id;
259        }
260
261        if let Some(item) = self.nodeui.get_item_mut("actionTerrainTile")
262            && let TheNodeUIItem::Icons(_, _, _, items) = item
263            && items.len() == 1
264        {
265            items[0].0 = tile_icon;
266            items[0].2 = tile_id;
267        }
268    }
269
270    fn apply(
271        &self,
272        map: &mut Map,
273        _ui: &mut TheUI,
274        _ctx: &mut TheContext,
275        server_ctx: &mut ServerContext,
276    ) -> Option<ProjectUndoAtom> {
277        let mut changed = false;
278        let prev = map.clone();
279
280        let name = self
281            .nodeui
282            .get_text_value("actionLinedefName")
283            .unwrap_or(String::new());
284        let terrain_smooth = self
285            .nodeui
286            .get_bool_value("actionTerrainSmooth")
287            .unwrap_or(false);
288        let terrain_width = self
289            .nodeui
290            .get_f32_value("actionTerrainWidth")
291            .unwrap_or(2.0);
292        let terrain_falloff_distance = self
293            .nodeui
294            .get_f32_value("actionTerrainFalloffDistance")
295            .unwrap_or(3.0);
296        let terrain_falloff_steepness = self
297            .nodeui
298            .get_f32_value("actionTerrainFalloffSteepness")
299            .unwrap_or(2.0);
300        let terrain_tile_falloff = self
301            .nodeui
302            .get_f32_value("actionTerrainTileFalloff")
303            .unwrap_or(1.0);
304        let terrain_tile_id = self.nodeui.get_tile_id("actionTerrainTile", 0);
305        let tile_id_text = self
306            .nodeui
307            .get_text_value("actionTileId")
308            .unwrap_or_default();
309        let terrain_tile_id = if let Ok(id) = Uuid::parse_str(tile_id_text.trim()) {
310            Some(id)
311        } else {
312            terrain_tile_id
313        };
314
315        for linedef_id in map.selected_linedefs.clone() {
316            if let Some(linedef) = map.find_linedef_mut(linedef_id) {
317                if name != linedef.name {
318                    linedef.name = name.clone();
319                    changed = true;
320                }
321                if linedef.properties.get_bool_default("terrain_smooth", false) != terrain_smooth {
322                    linedef
323                        .properties
324                        .set("terrain_smooth", Value::Bool(terrain_smooth));
325                    changed = true;
326                }
327                if (linedef.properties.get_float_default("terrain_width", 2.0) - terrain_width)
328                    .abs()
329                    > 0.0001
330                {
331                    linedef
332                        .properties
333                        .set("terrain_width", Value::Float(terrain_width));
334                    changed = true;
335                }
336                if (linedef
337                    .properties
338                    .get_float_default("terrain_falloff_distance", 3.0)
339                    - terrain_falloff_distance)
340                    .abs()
341                    > 0.0001
342                {
343                    linedef.properties.set(
344                        "terrain_falloff_distance",
345                        Value::Float(terrain_falloff_distance),
346                    );
347                    changed = true;
348                }
349                if (linedef
350                    .properties
351                    .get_float_default("terrain_falloff_steepness", 2.0)
352                    - terrain_falloff_steepness)
353                    .abs()
354                    > 0.0001
355                {
356                    linedef.properties.set(
357                        "terrain_falloff_steepness",
358                        Value::Float(terrain_falloff_steepness),
359                    );
360                    changed = true;
361                }
362                if (linedef
363                    .properties
364                    .get_float_default("terrain_tile_falloff", 1.0)
365                    - terrain_tile_falloff)
366                    .abs()
367                    > 0.0001
368                {
369                    linedef.properties.set(
370                        "terrain_tile_falloff",
371                        Value::Float(terrain_tile_falloff.max(0.0)),
372                    );
373                    changed = true;
374                }
375                match terrain_tile_id {
376                    Some(id) if id != Uuid::nil() => {
377                        let has_changed = match linedef.properties.get("terrain_source") {
378                            Some(Value::Source(PixelSource::TileId(existing))) => *existing != id,
379                            _ => true,
380                        };
381                        if has_changed {
382                            linedef
383                                .properties
384                                .set("terrain_source", Value::Source(PixelSource::TileId(id)));
385                            changed = true;
386                        }
387                    }
388                    _ => {
389                        if linedef.properties.contains("terrain_source") {
390                            linedef.properties.remove("terrain_source");
391                            changed = true;
392                        }
393                    }
394                }
395            }
396        }
397
398        if changed {
399            Some(ProjectUndoAtom::MapEdit(
400                server_ctx.pc,
401                Box::new(prev),
402                Box::new(map.clone()),
403            ))
404        } else {
405            None
406        }
407    }
408
409    fn params(&self) -> TheNodeUI {
410        self.nodeui.clone()
411    }
412
413    fn handle_event(
414        &mut self,
415        event: &TheEvent,
416        project: &mut Project,
417        _ui: &mut TheUI,
418        ctx: &mut TheContext,
419        _server_ctx: &mut ServerContext,
420    ) -> bool {
421        if let TheEvent::TileDropped(id, tile_id, index) = event
422            && let Some(item) = self.nodeui.get_item_mut(&id.name)
423            && let TheNodeUIItem::Icons(_, _, _, items) = item
424            && *index < items.len()
425            && let Some(tile) = project.tiles.get(tile_id)
426            && !tile.is_empty()
427        {
428            items[*index].0 = tile.textures[0].to_rgba();
429            items[*index].2 = *tile_id;
430            self.nodeui
431                .set_text_value("actionTileId", tile_id.to_string());
432            ctx.ui.send(TheEvent::Custom(
433                TheId::named("Update Action List"),
434                TheValue::Empty,
435            ));
436            return true;
437        }
438        self.nodeui.handle_event(event)
439    }
440}