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}