Skip to main content

rustapi/actions/
create_fence.rs

1use crate::prelude::*;
2use rusterix::PixelSource;
3use std::str::FromStr;
4
5pub const CREATE_FENCE_ACTION_ID: &str = "1d4cc6df-66fd-4d28-a340-c702f696f7b8";
6
7pub struct CreateFence {
8    id: TheId,
9    nodeui: TheNodeUI,
10}
11
12impl CreateFence {
13    fn apply_linedef_feature(&self, map: &mut Map, linedef_id: u32) -> bool {
14        let spacing = self
15            .nodeui
16            .get_f32_value("actionLayoutSpacing")
17            .unwrap_or(1.5)
18            .max(0.1);
19        let post_size = self
20            .nodeui
21            .get_f32_value("actionPostSize")
22            .unwrap_or(0.18)
23            .max(0.02);
24        let post_shape = self.nodeui.get_i32_value("actionPostShape").unwrap_or(0);
25        let post_height = self.nodeui.get_f32_value("actionPostHeight").unwrap_or(2.0);
26        let round_segments = self
27            .nodeui
28            .get_i32_value("actionPostRoundSegments")
29            .unwrap_or(8)
30            .max(3);
31        let connector_count = self
32            .nodeui
33            .get_i32_value("actionConnectorCount")
34            .unwrap_or(2)
35            .max(0);
36        let connector_style = self
37            .nodeui
38            .get_i32_value("actionConnectorStyle")
39            .unwrap_or(0);
40        let connector_size = self
41            .nodeui
42            .get_f32_value("actionConnectorSize")
43            .unwrap_or(0.12)
44            .max(0.01);
45        let connector_drop = self
46            .nodeui
47            .get_f32_value("actionConnectorDrop")
48            .unwrap_or(1.2)
49            .max(0.0);
50        let lean_amount = self
51            .nodeui
52            .get_f32_value("actionLeanAmount")
53            .unwrap_or(0.0)
54            .max(0.0);
55        let lean_randomness = self
56            .nodeui
57            .get_f32_value("actionLeanRandomness")
58            .unwrap_or(1.0)
59            .clamp(0.0, 1.0);
60        let tile_id_text = self
61            .nodeui
62            .get_text_value("actionMaterialTileId")
63            .unwrap_or_default();
64        let tile_id = Uuid::parse_str(tile_id_text.trim()).ok();
65
66        let Some(linedef) = map.find_linedef_mut(linedef_id) else {
67            return false;
68        };
69
70        if post_height <= 0.0 {
71            linedef
72                .properties
73                .set("linedef_feature", Value::Str("None".to_string()));
74            return true;
75        }
76
77        linedef
78            .properties
79            .set("linedef_feature", Value::Str("Fence".to_string()));
80        linedef
81            .properties
82            .set("feature_layout_spacing", Value::Float(spacing));
83        linedef
84            .properties
85            .set("feature_post_size", Value::Float(post_size));
86        linedef
87            .properties
88            .set("feature_post_shape", Value::Int(post_shape));
89        linedef
90            .properties
91            .set("feature_height", Value::Float(post_height));
92        linedef
93            .properties
94            .set("feature_round_segments", Value::Int(round_segments));
95        linedef
96            .properties
97            .set("feature_connector_count", Value::Int(connector_count));
98        linedef
99            .properties
100            .set("feature_connector_style", Value::Int(connector_style));
101        linedef
102            .properties
103            .set("feature_connector_size", Value::Float(connector_size));
104        linedef
105            .properties
106            .set("feature_connector_drop", Value::Float(connector_drop));
107        linedef
108            .properties
109            .set("feature_lean_amount", Value::Float(lean_amount));
110        linedef
111            .properties
112            .set("feature_lean_randomness", Value::Float(lean_randomness));
113
114        if let Some(id) = tile_id {
115            linedef
116                .properties
117                .set("feature_source", Value::Source(PixelSource::TileId(id)));
118        } else {
119            linedef.properties.remove("feature_source");
120        }
121
122        true
123    }
124}
125
126impl Action for CreateFence {
127    fn new() -> Self
128    where
129        Self: Sized,
130    {
131        let mut nodeui = TheNodeUI::default();
132
133        nodeui.add_item(TheNodeUIItem::OpenTree("material".into()));
134        nodeui.add_item(TheNodeUIItem::Text(
135            "actionMaterialTileId".into(),
136            "".into(),
137            "".into(),
138            "".into(),
139            None,
140            false,
141        ));
142        nodeui.add_item(TheNodeUIItem::CloseTree);
143
144        nodeui.add_item(TheNodeUIItem::OpenTree("layout".into()));
145        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
146            "actionLayoutSpacing".into(),
147            "".into(),
148            "".into(),
149            1.5,
150            0.1..=8.0,
151            false,
152        ));
153        nodeui.add_item(TheNodeUIItem::CloseTree);
154
155        nodeui.add_item(TheNodeUIItem::OpenTree("posts".into()));
156        nodeui.add_item(TheNodeUIItem::Selector(
157            "actionPostShape".into(),
158            "".into(),
159            "".into(),
160            vec!["square".into(), "round".into()],
161            0,
162        ));
163        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
164            "actionPostSize".into(),
165            "".into(),
166            "".into(),
167            0.18,
168            0.02..=2.0,
169            false,
170        ));
171        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
172            "actionPostHeight".into(),
173            "".into(),
174            "".into(),
175            2.0,
176            0.0..=8.0,
177            false,
178        ));
179        nodeui.add_item(TheNodeUIItem::IntEditSlider(
180            "actionPostRoundSegments".into(),
181            "".into(),
182            "".into(),
183            8,
184            3..=24,
185            false,
186        ));
187        nodeui.add_item(TheNodeUIItem::CloseTree);
188
189        nodeui.add_item(TheNodeUIItem::OpenTree("connectors".into()));
190        nodeui.add_item(TheNodeUIItem::IntEditSlider(
191            "actionConnectorCount".into(),
192            "".into(),
193            "".into(),
194            2,
195            0..=8,
196            false,
197        ));
198        nodeui.add_item(TheNodeUIItem::Selector(
199            "actionConnectorStyle".into(),
200            "".into(),
201            "".into(),
202            vec!["plank".into(), "square".into(), "round".into()],
203            0,
204        ));
205        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
206            "actionConnectorSize".into(),
207            "".into(),
208            "".into(),
209            0.12,
210            0.01..=1.0,
211            false,
212        ));
213        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
214            "actionConnectorDrop".into(),
215            "".into(),
216            "".into(),
217            1.2,
218            0.0..=6.0,
219            false,
220        ));
221        nodeui.add_item(TheNodeUIItem::CloseTree);
222
223        nodeui.add_item(TheNodeUIItem::OpenTree("lean".into()));
224        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
225            "actionLeanAmount".into(),
226            "".into(),
227            "".into(),
228            0.0,
229            0.0..=1.5,
230            false,
231        ));
232        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
233            "actionLeanRandomness".into(),
234            "".into(),
235            "".into(),
236            1.0,
237            0.0..=1.0,
238            false,
239        ));
240        nodeui.add_item(TheNodeUIItem::CloseTree);
241
242        nodeui.add_item(TheNodeUIItem::Markdown("desc".into(), "".into()));
243
244        Self {
245            id: TheId::named_with_id(
246                "Create Fence",
247                Uuid::from_str(CREATE_FENCE_ACTION_ID).unwrap(),
248            ),
249            nodeui,
250        }
251    }
252
253    fn id(&self) -> TheId {
254        self.id.clone()
255    }
256
257    fn info(&self) -> String {
258        "Configure a non-destructive fence feature on selected linedefs.".to_string()
259    }
260
261    fn role(&self) -> ActionRole {
262        ActionRole::Editor
263    }
264
265    fn is_applicable(&self, map: &Map, _ctx: &mut TheContext, server_ctx: &ServerContext) -> bool {
266        if server_ctx.editor_view_mode == EditorViewMode::D2 {
267            return false;
268        }
269        map.selected_sectors.is_empty() && !map.selected_linedefs.is_empty()
270    }
271
272    fn load_params(&mut self, map: &Map) {
273        let Some(linedef_id) = map.selected_linedefs.first() else {
274            return;
275        };
276        let Some(linedef) = map.find_linedef(*linedef_id) else {
277            return;
278        };
279
280        self.nodeui.set_f32_value(
281            "actionLayoutSpacing",
282            linedef
283                .properties
284                .get_float_default("feature_layout_spacing", 1.5),
285        );
286        self.nodeui.set_i32_value(
287            "actionPostShape",
288            linedef.properties.get_int_default("feature_post_shape", 0),
289        );
290        self.nodeui.set_f32_value(
291            "actionPostSize",
292            linedef
293                .properties
294                .get_float_default("feature_post_size", 0.18),
295        );
296        self.nodeui.set_f32_value(
297            "actionPostHeight",
298            linedef.properties.get_float_default("feature_height", 2.0),
299        );
300        self.nodeui.set_i32_value(
301            "actionPostRoundSegments",
302            linedef
303                .properties
304                .get_int_default("feature_round_segments", 8),
305        );
306        self.nodeui.set_i32_value(
307            "actionConnectorCount",
308            linedef
309                .properties
310                .get_int_default("feature_connector_count", 2),
311        );
312        self.nodeui.set_i32_value(
313            "actionConnectorStyle",
314            linedef
315                .properties
316                .get_int_default("feature_connector_style", 0),
317        );
318        self.nodeui.set_f32_value(
319            "actionConnectorSize",
320            linedef
321                .properties
322                .get_float_default("feature_connector_size", 0.12),
323        );
324        self.nodeui.set_f32_value(
325            "actionConnectorDrop",
326            linedef
327                .properties
328                .get_float_default("feature_connector_drop", 1.2),
329        );
330        self.nodeui.set_f32_value(
331            "actionLeanAmount",
332            linedef
333                .properties
334                .get_float_default("feature_lean_amount", 0.0),
335        );
336        self.nodeui.set_f32_value(
337            "actionLeanRandomness",
338            linedef
339                .properties
340                .get_float_default("feature_lean_randomness", 1.0),
341        );
342
343        let tile_id_text = match linedef.properties.get("feature_source") {
344            Some(Value::Source(PixelSource::TileId(id))) => id.to_string(),
345            _ => String::new(),
346        };
347        self.nodeui
348            .set_text_value("actionMaterialTileId", tile_id_text);
349    }
350
351    fn apply(
352        &self,
353        map: &mut Map,
354        _ui: &mut TheUI,
355        _ctx: &mut TheContext,
356        server_ctx: &mut ServerContext,
357    ) -> Option<ProjectUndoAtom> {
358        let prev = map.clone();
359        let mut changed = false;
360
361        for linedef_id in map.selected_linedefs.clone() {
362            changed |= self.apply_linedef_feature(map, linedef_id);
363        }
364
365        if changed {
366            Some(ProjectUndoAtom::MapEdit(
367                server_ctx.pc,
368                Box::new(prev),
369                Box::new(map.clone()),
370            ))
371        } else {
372            None
373        }
374    }
375
376    fn params(&self) -> TheNodeUI {
377        self.nodeui.clone()
378    }
379
380    fn handle_event(
381        &mut self,
382        event: &TheEvent,
383        _project: &mut Project,
384        _ui: &mut TheUI,
385        _ctx: &mut TheContext,
386        _server_ctx: &mut ServerContext,
387    ) -> bool {
388        self.nodeui.handle_event(event)
389    }
390}