Skip to main content

rustapi/actions/
create_stairs.rs

1use crate::prelude::*;
2use rusterix::PixelSource;
3use std::str::FromStr;
4
5pub const CREATE_STAIRS_ACTION_ID: &str = "4f4d41d0-7f67-4c1f-a8d2-f0ab4a0be6a1";
6
7pub struct CreateStairs {
8    id: TheId,
9    nodeui: TheNodeUI,
10}
11
12impl CreateStairs {
13    fn parse_tile_source(text: &str) -> Option<Value> {
14        let id = Uuid::parse_str(text.trim()).ok()?;
15        Some(Value::Source(PixelSource::TileId(id)))
16    }
17
18    fn apply_sector_stairs(&self, map: &mut Map, sector_id: u32) -> bool {
19        let direction = self
20            .nodeui
21            .get_i32_value("actionStairsDirection")
22            .unwrap_or(0)
23            .clamp(0, 3);
24        let steps = self
25            .nodeui
26            .get_i32_value("actionStairsSteps")
27            .unwrap_or(6)
28            .max(1);
29        let total_height = self
30            .nodeui
31            .get_f32_value("actionStairsTotalHeight")
32            .unwrap_or(1.0)
33            .max(0.0);
34        let fill_sides = self
35            .nodeui
36            .get_bool_value("actionStairsFillSides")
37            .unwrap_or(true);
38
39        let tile_id_text = self
40            .nodeui
41            .get_text_value("actionStairsTileId")
42            .unwrap_or_default();
43        let tread_tile_id_text = self
44            .nodeui
45            .get_text_value("actionStairsTreadTileId")
46            .unwrap_or_default();
47        let riser_tile_id_text = self
48            .nodeui
49            .get_text_value("actionStairsRiserTileId")
50            .unwrap_or_default();
51        let side_tile_id_text = self
52            .nodeui
53            .get_text_value("actionStairsSideTileId")
54            .unwrap_or_default();
55
56        let Some(sector) = map.find_sector_mut(sector_id) else {
57            return false;
58        };
59
60        if total_height <= 0.0 {
61            sector
62                .properties
63                .set("sector_feature", Value::Str("None".to_string()));
64            return true;
65        }
66
67        sector
68            .properties
69            .set("sector_feature", Value::Str("Stairs".to_string()));
70        sector
71            .properties
72            .set("stairs_direction", Value::Int(direction));
73        sector.properties.set("stairs_steps", Value::Int(steps));
74        sector
75            .properties
76            .set("stairs_total_height", Value::Float(total_height));
77        sector
78            .properties
79            .set("stairs_fill_sides", Value::Bool(fill_sides));
80
81        if let Some(src) = Self::parse_tile_source(&tile_id_text) {
82            sector.properties.set("stairs_tile_source", src);
83        } else {
84            sector.properties.remove("stairs_tile_source");
85        }
86        if let Some(src) = Self::parse_tile_source(&tread_tile_id_text) {
87            sector.properties.set("stairs_tread_source", src);
88        } else {
89            sector.properties.remove("stairs_tread_source");
90        }
91        if let Some(src) = Self::parse_tile_source(&riser_tile_id_text) {
92            sector.properties.set("stairs_riser_source", src);
93        } else {
94            sector.properties.remove("stairs_riser_source");
95        }
96        if let Some(src) = Self::parse_tile_source(&side_tile_id_text) {
97            sector.properties.set("stairs_side_source", src);
98        } else {
99            sector.properties.remove("stairs_side_source");
100        }
101
102        true
103    }
104}
105
106impl Action for CreateStairs {
107    fn new() -> Self
108    where
109        Self: Sized,
110    {
111        let mut nodeui = TheNodeUI::default();
112
113        nodeui.add_item(TheNodeUIItem::OpenTree("stairs".into()));
114        nodeui.add_item(TheNodeUIItem::Selector(
115            "actionStairsDirection".into(),
116            "".into(),
117            "".into(),
118            vec!["north".into(), "east".into(), "south".into(), "west".into()],
119            0,
120        ));
121        nodeui.add_item(TheNodeUIItem::IntEditSlider(
122            "actionStairsSteps".into(),
123            "".into(),
124            "".into(),
125            6,
126            1..=64,
127            false,
128        ));
129        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
130            "actionStairsTotalHeight".into(),
131            "".into(),
132            "".into(),
133            1.0,
134            0.0..=16.0,
135            false,
136        ));
137        nodeui.add_item(TheNodeUIItem::Checkbox(
138            "actionStairsFillSides".into(),
139            "".into(),
140            "".into(),
141            true,
142        ));
143        nodeui.add_item(TheNodeUIItem::CloseTree);
144
145        nodeui.add_item(TheNodeUIItem::OpenTree("material".into()));
146        nodeui.add_item(TheNodeUIItem::Text(
147            "actionStairsTileId".into(),
148            "".into(),
149            "".into(),
150            "".into(),
151            None,
152            false,
153        ));
154        nodeui.add_item(TheNodeUIItem::Text(
155            "actionStairsTreadTileId".into(),
156            "".into(),
157            "".into(),
158            "".into(),
159            None,
160            false,
161        ));
162        nodeui.add_item(TheNodeUIItem::Text(
163            "actionStairsRiserTileId".into(),
164            "".into(),
165            "".into(),
166            "".into(),
167            None,
168            false,
169        ));
170        nodeui.add_item(TheNodeUIItem::Text(
171            "actionStairsSideTileId".into(),
172            "".into(),
173            "".into(),
174            "".into(),
175            None,
176            false,
177        ));
178        nodeui.add_item(TheNodeUIItem::CloseTree);
179
180        nodeui.add_item(TheNodeUIItem::Markdown("desc".into(), "".into()));
181
182        Self {
183            id: TheId::named_with_id(
184                "Create Stairs",
185                Uuid::from_str(CREATE_STAIRS_ACTION_ID).unwrap(),
186            ),
187            nodeui,
188        }
189    }
190
191    fn id(&self) -> TheId {
192        self.id.clone()
193    }
194
195    fn info(&self) -> String {
196        "Configure non-destructive stairs on selected sectors.".to_string()
197    }
198
199    fn role(&self) -> ActionRole {
200        ActionRole::Editor
201    }
202
203    fn is_applicable(&self, map: &Map, _ctx: &mut TheContext, server_ctx: &ServerContext) -> bool {
204        if server_ctx.editor_view_mode == EditorViewMode::D2 {
205            return false;
206        }
207        !map.selected_sectors.is_empty()
208    }
209
210    fn load_params(&mut self, map: &Map) {
211        let Some(sector_id) = map.selected_sectors.first() else {
212            return;
213        };
214        let Some(sector) = map.find_sector(*sector_id) else {
215            return;
216        };
217
218        self.nodeui.set_i32_value(
219            "actionStairsDirection",
220            sector.properties.get_int_default("stairs_direction", 0),
221        );
222        self.nodeui.set_i32_value(
223            "actionStairsSteps",
224            sector.properties.get_int_default("stairs_steps", 6),
225        );
226        self.nodeui.set_f32_value(
227            "actionStairsTotalHeight",
228            sector
229                .properties
230                .get_float_default("stairs_total_height", 1.0),
231        );
232        self.nodeui.set_bool_value(
233            "actionStairsFillSides",
234            sector
235                .properties
236                .get_bool_default("stairs_fill_sides", true),
237        );
238
239        let tile_id_text = match sector.properties.get("stairs_tile_source") {
240            Some(Value::Source(PixelSource::TileId(id))) => id.to_string(),
241            _ => String::new(),
242        };
243        let tread_tile_id_text = match sector.properties.get("stairs_tread_source") {
244            Some(Value::Source(PixelSource::TileId(id))) => id.to_string(),
245            _ => String::new(),
246        };
247        let riser_tile_id_text = match sector.properties.get("stairs_riser_source") {
248            Some(Value::Source(PixelSource::TileId(id))) => id.to_string(),
249            _ => String::new(),
250        };
251        let side_tile_id_text = match sector.properties.get("stairs_side_source") {
252            Some(Value::Source(PixelSource::TileId(id))) => id.to_string(),
253            _ => String::new(),
254        };
255
256        self.nodeui
257            .set_text_value("actionStairsTileId", tile_id_text);
258        self.nodeui
259            .set_text_value("actionStairsTreadTileId", tread_tile_id_text);
260        self.nodeui
261            .set_text_value("actionStairsRiserTileId", riser_tile_id_text);
262        self.nodeui
263            .set_text_value("actionStairsSideTileId", side_tile_id_text);
264    }
265
266    fn apply(
267        &self,
268        map: &mut Map,
269        _ui: &mut TheUI,
270        _ctx: &mut TheContext,
271        server_ctx: &mut ServerContext,
272    ) -> Option<ProjectUndoAtom> {
273        let prev = map.clone();
274        let mut changed = false;
275
276        for sector_id in map.selected_sectors.clone() {
277            changed |= self.apply_sector_stairs(map, sector_id);
278        }
279
280        if changed {
281            Some(ProjectUndoAtom::MapEdit(
282                server_ctx.pc,
283                Box::new(prev),
284                Box::new(map.clone()),
285            ))
286        } else {
287            None
288        }
289    }
290
291    fn params(&self) -> TheNodeUI {
292        self.nodeui.clone()
293    }
294
295    fn handle_event(
296        &mut self,
297        event: &TheEvent,
298        _project: &mut Project,
299        _ui: &mut TheUI,
300        _ctx: &mut TheContext,
301        _server_ctx: &mut ServerContext,
302    ) -> bool {
303        self.nodeui.handle_event(event)
304    }
305}