Skip to main content

rustapi/actions/
create_prop.rs

1use crate::prelude::*;
2use rusterix::{PixelSource, Value};
3
4pub struct CreateProp {
5    id: TheId,
6    nodeui: TheNodeUI,
7}
8
9fn parse_source_from_text_or_tile(text: &str, fallback_tile_id: Uuid) -> Option<PixelSource> {
10    let trimmed = text.trim();
11    if !trimmed.is_empty() {
12        if let Ok(id) = Uuid::parse_str(trimmed) {
13            return Some(PixelSource::TileId(id));
14        }
15        if let Ok(index) = trimmed.parse::<u16>() {
16            return Some(PixelSource::PaletteIndex(index));
17        }
18    }
19    if fallback_tile_id != Uuid::nil() {
20        Some(PixelSource::TileId(fallback_tile_id))
21    } else {
22        None
23    }
24}
25
26fn source_to_text_and_uuid(source: Option<&Value>) -> (String, Uuid) {
27    if let Some(Value::Source(ps)) = source {
28        match ps {
29            PixelSource::TileId(id) => (id.to_string(), *id),
30            PixelSource::PaletteIndex(i) => (i.to_string(), Uuid::nil()),
31            _ => (String::new(), Uuid::nil()),
32        }
33    } else {
34        (String::new(), Uuid::nil())
35    }
36}
37
38impl Action for CreateProp {
39    fn new() -> Self
40    where
41        Self: Sized,
42    {
43        let mut nodeui: TheNodeUI = TheNodeUI::default();
44
45        nodeui.add_item(TheNodeUIItem::OpenTree("prop".into()));
46        nodeui.add_item(TheNodeUIItem::Selector(
47            "actionPropType".into(),
48            "".into(),
49            "".into(),
50            vec![
51                "table".into(),
52                "bookcase".into(),
53                "crate".into(),
54                "barrel".into(),
55                "bed".into(),
56            ],
57            0,
58        ));
59        nodeui.add_item(TheNodeUIItem::CloseTree);
60
61        nodeui.add_item(TheNodeUIItem::OpenTree("table".into()));
62        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
63            "actionTableHeight".into(),
64            "".into(),
65            "".into(),
66            0.75,
67            0.0..=10.0,
68            false,
69        ));
70        nodeui.add_item(TheNodeUIItem::Checkbox(
71            "actionTableChairs".into(),
72            "".into(),
73            "".into(),
74            false,
75        ));
76        nodeui.add_item(TheNodeUIItem::IntEditSlider(
77            "actionTableChairCount".into(),
78            "".into(),
79            "".into(),
80            4,
81            0..=8,
82            false,
83        ));
84        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
85            "actionTableChairOffset".into(),
86            "".into(),
87            "".into(),
88            0.45,
89            0.0..=3.0,
90            false,
91        ));
92        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
93            "actionTableChairWidth".into(),
94            "".into(),
95            "".into(),
96            0.85,
97            0.20..=3.0,
98            false,
99        ));
100        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
101            "actionTableChairBackHeight".into(),
102            "".into(),
103            "".into(),
104            1.0,
105            0.25..=3.0,
106            false,
107        ));
108        nodeui.add_item(TheNodeUIItem::Icons(
109            "actionTableChairTile".into(),
110            "".into(),
111            "".into(),
112            vec![(
113                TheRGBABuffer::new(TheDim::sized(36, 36)),
114                "".to_string(),
115                Uuid::nil(),
116            )],
117        ));
118        nodeui.add_item(TheNodeUIItem::Text(
119            "actionTableChairTileId".into(),
120            "".into(),
121            "".into(),
122            "".into(),
123            None,
124            false,
125        ));
126        nodeui.add_item(TheNodeUIItem::CloseTree);
127
128        nodeui.add_item(TheNodeUIItem::OpenTree("bookcase".into()));
129        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
130            "actionBookcaseHeight".into(),
131            "".into(),
132            "".into(),
133            2.0,
134            0.2..=8.0,
135            false,
136        ));
137        nodeui.add_item(TheNodeUIItem::IntEditSlider(
138            "actionBookcaseShelves".into(),
139            "".into(),
140            "".into(),
141            4,
142            1..=12,
143            false,
144        ));
145        nodeui.add_item(TheNodeUIItem::Checkbox(
146            "actionBookcaseBooks".into(),
147            "".into(),
148            "".into(),
149            true,
150        ));
151        nodeui.add_item(TheNodeUIItem::CloseTree);
152
153        nodeui.add_item(TheNodeUIItem::OpenTree("crate".into()));
154        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
155            "actionCrateHeight".into(),
156            "".into(),
157            "".into(),
158            1.0,
159            0.2..=8.0,
160            false,
161        ));
162        nodeui.add_item(TheNodeUIItem::CloseTree);
163
164        nodeui.add_item(TheNodeUIItem::OpenTree("barrel".into()));
165        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
166            "actionBarrelHeight".into(),
167            "".into(),
168            "".into(),
169            1.0,
170            0.2..=8.0,
171            false,
172        ));
173        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
174            "actionBarrelBulge".into(),
175            "".into(),
176            "".into(),
177            1.12,
178            1.0..=1.5,
179            false,
180        ));
181        nodeui.add_item(TheNodeUIItem::IntEditSlider(
182            "actionBarrelSegments".into(),
183            "".into(),
184            "".into(),
185            12,
186            6..=32,
187            false,
188        ));
189        nodeui.add_item(TheNodeUIItem::CloseTree);
190
191        nodeui.add_item(TheNodeUIItem::OpenTree("bed".into()));
192        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
193            "actionBedHeight".into(),
194            "".into(),
195            "".into(),
196            0.55,
197            0.2..=3.0,
198            false,
199        ));
200        nodeui.add_item(TheNodeUIItem::Checkbox(
201            "actionBedHeadboard".into(),
202            "".into(),
203            "".into(),
204            true,
205        ));
206        nodeui.add_item(TheNodeUIItem::Selector(
207            "actionBedHeadboardSide".into(),
208            "".into(),
209            "".into(),
210            vec!["start".into(), "end".into()],
211            0,
212        ));
213        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
214            "actionBedHeadboardHeight".into(),
215            "".into(),
216            "".into(),
217            0.7,
218            0.2..=2.5,
219            false,
220        ));
221        nodeui.add_item(TheNodeUIItem::Icons(
222            "actionBedMattressTile".into(),
223            "".into(),
224            "".into(),
225            vec![(
226                TheRGBABuffer::new(TheDim::sized(36, 36)),
227                "".to_string(),
228                Uuid::nil(),
229            )],
230        ));
231        nodeui.add_item(TheNodeUIItem::Text(
232            "actionBedMattressTileId".into(),
233            "".into(),
234            "".into(),
235            "".into(),
236            None,
237            false,
238        ));
239        nodeui.add_item(TheNodeUIItem::CloseTree);
240
241        nodeui.add_item(TheNodeUIItem::OpenTree("material".into()));
242        nodeui.add_item(TheNodeUIItem::Icons(
243            "actionMaterialTile".into(),
244            "".into(),
245            "".into(),
246            vec![(
247                TheRGBABuffer::new(TheDim::sized(36, 36)),
248                "".to_string(),
249                Uuid::nil(),
250            )],
251        ));
252        nodeui.add_item(TheNodeUIItem::Text(
253            "actionMaterialTileId".into(),
254            "".into(),
255            "".into(),
256            "".into(),
257            None,
258            false,
259        ));
260        nodeui.add_item(TheNodeUIItem::CloseTree);
261
262        Self {
263            id: TheId::named("Create Prop"),
264            nodeui,
265        }
266    }
267
268    fn id(&self) -> TheId {
269        self.id.clone()
270    }
271
272    fn info(&self) -> String {
273        "Create or edit parametric props on selected sectors.".to_string()
274    }
275
276    fn role(&self) -> ActionRole {
277        ActionRole::Editor
278    }
279
280    fn is_applicable(&self, map: &Map, _ctx: &mut TheContext, server_ctx: &ServerContext) -> bool {
281        !map.selected_sectors.is_empty()
282            && server_ctx.editor_view_mode == EditorViewMode::D2
283            && server_ctx.editing_surface.is_some()
284    }
285
286    fn load_params(&mut self, map: &Map) {
287        if let Some(sector_id) = map.selected_sectors.first()
288            && let Some(sector) = map.find_sector(*sector_id)
289        {
290            let prop_kind = sector.properties.get_int_default("profile_prop_kind", 0);
291            self.nodeui
292                .set_i32_value("actionPropType", prop_kind.clamp(0, 4));
293            self.nodeui.set_f32_value(
294                "actionTableHeight",
295                sector.properties.get_float_default("profile_amount", 0.75),
296            );
297            self.nodeui.set_bool_value(
298                "actionTableChairs",
299                sector.properties.get_bool_default("table_chairs", false),
300            );
301            self.nodeui.set_i32_value(
302                "actionTableChairCount",
303                sector.properties.get_int_default("table_chair_count", 4),
304            );
305            self.nodeui.set_f32_value(
306                "actionTableChairOffset",
307                sector
308                    .properties
309                    .get_float_default("table_chair_offset", 0.45),
310            );
311            self.nodeui.set_f32_value(
312                "actionTableChairWidth",
313                sector
314                    .properties
315                    .get_float_default("table_chair_width", 0.85),
316            );
317            self.nodeui.set_f32_value(
318                "actionTableChairBackHeight",
319                sector
320                    .properties
321                    .get_float_default("table_chair_back_height", 1.0),
322            );
323            self.nodeui.set_f32_value(
324                "actionBookcaseHeight",
325                sector.properties.get_float_default("profile_amount", 2.0),
326            );
327            self.nodeui.set_f32_value(
328                "actionCrateHeight",
329                sector.properties.get_float_default("profile_amount", 1.0),
330            );
331            self.nodeui.set_f32_value(
332                "actionBarrelHeight",
333                sector.properties.get_float_default("profile_amount", 1.0),
334            );
335            self.nodeui.set_f32_value(
336                "actionBarrelBulge",
337                sector.properties.get_float_default("barrel_bulge", 1.12),
338            );
339            self.nodeui.set_i32_value(
340                "actionBarrelSegments",
341                sector.properties.get_int_default("barrel_segments", 12),
342            );
343            self.nodeui.set_f32_value(
344                "actionBedHeight",
345                sector.properties.get_float_default("profile_amount", 0.55),
346            );
347            self.nodeui.set_bool_value(
348                "actionBedHeadboard",
349                sector.properties.get_bool_default("bed_headboard", true),
350            );
351            self.nodeui.set_i32_value(
352                "actionBedHeadboardSide",
353                sector
354                    .properties
355                    .get_int_default("bed_headboard_side", 0)
356                    .clamp(0, 1),
357            );
358            self.nodeui.set_f32_value(
359                "actionBedHeadboardHeight",
360                sector
361                    .properties
362                    .get_float_default("bed_headboard_height", 0.7),
363            );
364            self.nodeui.set_i32_value(
365                "actionBookcaseShelves",
366                sector.properties.get_int_default("bookcase_shelves", 4),
367            );
368            self.nodeui.set_bool_value(
369                "actionBookcaseBooks",
370                sector.properties.get_bool_default("bookcase_books", true),
371            );
372
373            let (tile_text, tile_id) = source_to_text_and_uuid(sector.properties.get("cap_source"));
374            self.nodeui
375                .set_text_value("actionMaterialTileId", tile_text);
376            if let Some(item) = self.nodeui.get_item_mut("actionMaterialTile")
377                && let TheNodeUIItem::Icons(_, _, _, items) = item
378                && items.len() == 1
379            {
380                items[0].2 = tile_id;
381            }
382
383            let (chair_tile_text, chair_tile_id) =
384                source_to_text_and_uuid(sector.properties.get("chair_source"));
385            self.nodeui
386                .set_text_value("actionTableChairTileId", chair_tile_text);
387            if let Some(item) = self.nodeui.get_item_mut("actionTableChairTile")
388                && let TheNodeUIItem::Icons(_, _, _, items) = item
389                && items.len() == 1
390            {
391                items[0].2 = chair_tile_id;
392            }
393
394            let (bed_mattress_tile_text, bed_mattress_tile_id) =
395                source_to_text_and_uuid(sector.properties.get("bed_mattress_source"));
396            self.nodeui
397                .set_text_value("actionBedMattressTileId", bed_mattress_tile_text);
398            if let Some(item) = self.nodeui.get_item_mut("actionBedMattressTile")
399                && let TheNodeUIItem::Icons(_, _, _, items) = item
400                && items.len() == 1
401            {
402                items[0].2 = bed_mattress_tile_id;
403            }
404        }
405    }
406
407    fn load_params_project(&mut self, project: &Project, server_ctx: &mut ServerContext) {
408        let mut tile_icon = TheRGBABuffer::new(TheDim::sized(36, 36));
409        let mut tile_id = Uuid::nil();
410        let mut chair_tile_icon = TheRGBABuffer::new(TheDim::sized(36, 36));
411        let mut chair_tile_id = Uuid::nil();
412        let mut bed_mattress_tile_icon = TheRGBABuffer::new(TheDim::sized(36, 36));
413        let mut bed_mattress_tile_id = Uuid::nil();
414
415        if let Some(map) = project.get_map(server_ctx)
416            && let Some(sector_id) = map.selected_sectors.first()
417            && let Some(sector) = map.find_sector(*sector_id)
418            && let Some(Value::Source(PixelSource::TileId(id))) =
419                sector.properties.get("cap_source")
420            && let Some(tile) = project.tiles.get(id)
421            && !tile.is_empty()
422        {
423            tile_icon = tile.textures[0].to_rgba();
424            tile_id = *id;
425        }
426        if let Some(map) = project.get_map(server_ctx)
427            && let Some(sector_id) = map.selected_sectors.first()
428            && let Some(sector) = map.find_sector(*sector_id)
429            && let Some(Value::Source(PixelSource::TileId(id))) =
430                sector.properties.get("chair_source")
431            && let Some(tile) = project.tiles.get(id)
432            && !tile.is_empty()
433        {
434            chair_tile_icon = tile.textures[0].to_rgba();
435            chair_tile_id = *id;
436        }
437        if let Some(map) = project.get_map(server_ctx)
438            && let Some(sector_id) = map.selected_sectors.first()
439            && let Some(sector) = map.find_sector(*sector_id)
440            && let Some(Value::Source(PixelSource::TileId(id))) =
441                sector.properties.get("bed_mattress_source")
442            && let Some(tile) = project.tiles.get(id)
443            && !tile.is_empty()
444        {
445            bed_mattress_tile_icon = tile.textures[0].to_rgba();
446            bed_mattress_tile_id = *id;
447        }
448
449        if let Some(item) = self.nodeui.get_item_mut("actionMaterialTile")
450            && let TheNodeUIItem::Icons(_, _, _, items) = item
451            && items.len() == 1
452        {
453            items[0].0 = tile_icon;
454            items[0].2 = tile_id;
455        }
456        if let Some(item) = self.nodeui.get_item_mut("actionTableChairTile")
457            && let TheNodeUIItem::Icons(_, _, _, items) = item
458            && items.len() == 1
459        {
460            items[0].0 = chair_tile_icon;
461            items[0].2 = chair_tile_id;
462        }
463        if let Some(item) = self.nodeui.get_item_mut("actionBedMattressTile")
464            && let TheNodeUIItem::Icons(_, _, _, items) = item
465            && items.len() == 1
466        {
467            items[0].0 = bed_mattress_tile_icon;
468            items[0].2 = bed_mattress_tile_id;
469        }
470    }
471
472    fn apply(
473        &self,
474        map: &mut Map,
475        _ui: &mut TheUI,
476        _ctx: &mut TheContext,
477        server_ctx: &mut ServerContext,
478    ) -> Option<ProjectUndoAtom> {
479        let prop_type = self.nodeui.get_i32_value("actionPropType").unwrap_or(0);
480
481        let prev = map.clone();
482
483        let height = self
484            .nodeui
485            .get_f32_value("actionTableHeight")
486            .unwrap_or(0.75)
487            .max(0.0);
488        let chairs_enabled = self
489            .nodeui
490            .get_bool_value("actionTableChairs")
491            .unwrap_or(false);
492        let chair_count = self
493            .nodeui
494            .get_i32_value("actionTableChairCount")
495            .unwrap_or(4)
496            .clamp(0, 8);
497        let chair_offset = self
498            .nodeui
499            .get_f32_value("actionTableChairOffset")
500            .unwrap_or(0.45)
501            .max(0.0);
502        let chair_width = self
503            .nodeui
504            .get_f32_value("actionTableChairWidth")
505            .unwrap_or(0.85)
506            .max(0.20);
507        let chair_back_height = self
508            .nodeui
509            .get_f32_value("actionTableChairBackHeight")
510            .unwrap_or(1.0)
511            .max(0.25);
512        let bookcase_height = self
513            .nodeui
514            .get_f32_value("actionBookcaseHeight")
515            .unwrap_or(2.0)
516            .max(0.2);
517        let crate_height = self
518            .nodeui
519            .get_f32_value("actionCrateHeight")
520            .unwrap_or(1.0)
521            .max(0.2);
522        let barrel_height = self
523            .nodeui
524            .get_f32_value("actionBarrelHeight")
525            .unwrap_or(1.0)
526            .max(0.2);
527        let barrel_bulge = self
528            .nodeui
529            .get_f32_value("actionBarrelBulge")
530            .unwrap_or(1.12)
531            .clamp(1.0, 1.5);
532        let barrel_segments = self
533            .nodeui
534            .get_i32_value("actionBarrelSegments")
535            .unwrap_or(12)
536            .clamp(6, 32);
537        let bed_height = self
538            .nodeui
539            .get_f32_value("actionBedHeight")
540            .unwrap_or(0.55)
541            .clamp(0.2, 3.0);
542        let bed_headboard = self
543            .nodeui
544            .get_bool_value("actionBedHeadboard")
545            .unwrap_or(true);
546        let bed_headboard_side = self
547            .nodeui
548            .get_i32_value("actionBedHeadboardSide")
549            .unwrap_or(0)
550            .clamp(0, 1);
551        let bed_headboard_height = self
552            .nodeui
553            .get_f32_value("actionBedHeadboardHeight")
554            .unwrap_or(0.7)
555            .clamp(0.2, 2.5);
556        let bookcase_shelves = self
557            .nodeui
558            .get_i32_value("actionBookcaseShelves")
559            .unwrap_or(4)
560            .clamp(1, 12);
561        let bookcase_books = self
562            .nodeui
563            .get_bool_value("actionBookcaseBooks")
564            .unwrap_or(true);
565
566        let tile_id = self
567            .nodeui
568            .get_tile_id("actionMaterialTile", 0)
569            .unwrap_or(Uuid::nil());
570        let tile_text = self
571            .nodeui
572            .get_text_value("actionMaterialTileId")
573            .unwrap_or_default();
574        let table_source = parse_source_from_text_or_tile(&tile_text, tile_id);
575        let chair_tile_id = self
576            .nodeui
577            .get_tile_id("actionTableChairTile", 0)
578            .unwrap_or(Uuid::nil());
579        let chair_tile_text = self
580            .nodeui
581            .get_text_value("actionTableChairTileId")
582            .unwrap_or_default();
583        let chair_source = parse_source_from_text_or_tile(&chair_tile_text, chair_tile_id);
584        let bed_mattress_tile_id = self
585            .nodeui
586            .get_tile_id("actionBedMattressTile", 0)
587            .unwrap_or(Uuid::nil());
588        let bed_mattress_tile_text = self
589            .nodeui
590            .get_text_value("actionBedMattressTileId")
591            .unwrap_or_default();
592        let bed_mattress_source =
593            parse_source_from_text_or_tile(&bed_mattress_tile_text, bed_mattress_tile_id);
594
595        let mut changed = false;
596        for sector_id in &map.selected_sectors.clone() {
597            if let Some(sector) = map.find_sector_mut(*sector_id) {
598                sector.properties.set("profile_op", Value::Int(1));
599                sector
600                    .properties
601                    .set("profile_prop_kind", Value::Int(prop_type));
602                sector.properties.set(
603                    "profile_amount",
604                    Value::Float(match prop_type {
605                        1 => bookcase_height,
606                        2 => crate_height,
607                        3 => barrel_height,
608                        4 => bed_height,
609                        _ => height,
610                    }),
611                );
612                sector.properties.set("profile_table", Value::Bool(true));
613                if prop_type == 0 {
614                    sector
615                        .properties
616                        .set("table_chairs", Value::Bool(chairs_enabled));
617                    sector
618                        .properties
619                        .set("table_chair_count", Value::Int(chair_count));
620                    sector
621                        .properties
622                        .set("table_chair_offset", Value::Float(chair_offset));
623                    sector
624                        .properties
625                        .set("table_chair_width", Value::Float(chair_width));
626                    sector
627                        .properties
628                        .set("table_chair_back_height", Value::Float(chair_back_height));
629                    sector.properties.remove("bookcase_shelves");
630                    sector.properties.remove("bookcase_books");
631                    sector.properties.remove("book_source");
632                    sector.properties.remove("barrel_bulge");
633                    sector.properties.remove("barrel_segments");
634                    sector.properties.remove("bed_headboard");
635                    sector.properties.remove("bed_headboard_side");
636                    sector.properties.remove("bed_headboard_height");
637                    sector.properties.remove("bed_mattress_source");
638                } else if prop_type == 1 {
639                    sector
640                        .properties
641                        .set("bookcase_shelves", Value::Int(bookcase_shelves));
642                    sector
643                        .properties
644                        .set("bookcase_books", Value::Bool(bookcase_books));
645                    sector.properties.remove("table_chairs");
646                    sector.properties.remove("table_chair_count");
647                    sector.properties.remove("table_chair_offset");
648                    sector.properties.remove("table_chair_width");
649                    sector.properties.remove("table_chair_back_height");
650                    sector.properties.remove("chair_source");
651                    sector.properties.remove("barrel_bulge");
652                    sector.properties.remove("barrel_segments");
653                    sector.properties.remove("bed_headboard");
654                    sector.properties.remove("bed_headboard_side");
655                    sector.properties.remove("bed_headboard_height");
656                    sector.properties.remove("bed_mattress_source");
657
658                    if bookcase_books {
659                        let palette_index = (8 + (sector.id % 24)) as u16;
660                        sector.properties.set(
661                            "book_source",
662                            Value::Source(PixelSource::PaletteIndex(palette_index)),
663                        );
664                    } else {
665                        sector.properties.remove("book_source");
666                    }
667                } else if prop_type == 3 {
668                    sector
669                        .properties
670                        .set("barrel_bulge", Value::Float(barrel_bulge));
671                    sector
672                        .properties
673                        .set("barrel_segments", Value::Int(barrel_segments));
674                    sector.properties.remove("table_chairs");
675                    sector.properties.remove("table_chair_count");
676                    sector.properties.remove("table_chair_offset");
677                    sector.properties.remove("table_chair_width");
678                    sector.properties.remove("table_chair_back_height");
679                    sector.properties.remove("chair_source");
680                    sector.properties.remove("bookcase_shelves");
681                    sector.properties.remove("bookcase_books");
682                    sector.properties.remove("book_source");
683                    sector.properties.remove("bed_headboard");
684                    sector.properties.remove("bed_headboard_side");
685                    sector.properties.remove("bed_headboard_height");
686                    sector.properties.remove("bed_mattress_source");
687                } else if prop_type == 4 {
688                    sector
689                        .properties
690                        .set("bed_headboard", Value::Bool(bed_headboard));
691                    sector
692                        .properties
693                        .set("bed_headboard_side", Value::Int(bed_headboard_side));
694                    sector
695                        .properties
696                        .set("bed_headboard_height", Value::Float(bed_headboard_height));
697                    sector.properties.remove("table_chairs");
698                    sector.properties.remove("table_chair_count");
699                    sector.properties.remove("table_chair_offset");
700                    sector.properties.remove("table_chair_width");
701                    sector.properties.remove("table_chair_back_height");
702                    sector.properties.remove("chair_source");
703                    sector.properties.remove("bookcase_shelves");
704                    sector.properties.remove("bookcase_books");
705                    sector.properties.remove("book_source");
706                    sector.properties.remove("barrel_bulge");
707                    sector.properties.remove("barrel_segments");
708                    if let Some(source) = bed_mattress_source.clone().or(table_source.clone()) {
709                        sector
710                            .properties
711                            .set("bed_mattress_source", Value::Source(source));
712                    } else {
713                        sector.properties.remove("bed_mattress_source");
714                    }
715                } else {
716                    // Crate and any unknown future prop kinds: clear unrelated settings.
717                    sector.properties.remove("table_chairs");
718                    sector.properties.remove("table_chair_count");
719                    sector.properties.remove("table_chair_offset");
720                    sector.properties.remove("table_chair_width");
721                    sector.properties.remove("table_chair_back_height");
722                    sector.properties.remove("chair_source");
723                    sector.properties.remove("bookcase_shelves");
724                    sector.properties.remove("bookcase_books");
725                    sector.properties.remove("book_source");
726                    sector.properties.remove("barrel_bulge");
727                    sector.properties.remove("barrel_segments");
728                    sector.properties.remove("bed_headboard");
729                    sector.properties.remove("bed_headboard_side");
730                    sector.properties.remove("bed_headboard_height");
731                    sector.properties.remove("bed_mattress_source");
732                }
733
734                if let Some(source) = table_source.clone() {
735                    let src = Value::Source(source);
736                    sector.properties.set("cap_source", src.clone());
737                    sector.properties.set("jamb_source", src);
738                } else {
739                    sector.properties.remove("cap_source");
740                    sector.properties.remove("jamb_source");
741                }
742                if let Some(source) = chair_source.clone() {
743                    let src = Value::Source(source);
744                    sector.properties.set("chair_source", src);
745                } else {
746                    sector.properties.remove("chair_source");
747                }
748                changed = true;
749            }
750        }
751
752        if changed {
753            Some(ProjectUndoAtom::MapEdit(
754                server_ctx.pc,
755                Box::new(prev),
756                Box::new(map.clone()),
757            ))
758        } else {
759            None
760        }
761    }
762
763    fn params(&self) -> TheNodeUI {
764        self.nodeui.clone()
765    }
766
767    fn handle_event(
768        &mut self,
769        event: &TheEvent,
770        project: &mut Project,
771        _ui: &mut TheUI,
772        ctx: &mut TheContext,
773        _server_ctx: &mut ServerContext,
774    ) -> bool {
775        if let TheEvent::TileDropped(id, tile_id, index) = event
776            && let Some(item) = self.nodeui.get_item_mut(&id.name)
777            && let TheNodeUIItem::Icons(_, _, _, items) = item
778            && *index < items.len()
779            && let Some(tile) = project.tiles.get(tile_id)
780            && !tile.is_empty()
781        {
782            items[*index].0 = tile.textures[0].to_rgba();
783            items[*index].2 = *tile_id;
784            if id.name == "actionMaterialTile" {
785                self.nodeui
786                    .set_text_value("actionMaterialTileId", tile_id.to_string());
787            } else if id.name == "actionTableChairTile" {
788                self.nodeui
789                    .set_text_value("actionTableChairTileId", tile_id.to_string());
790            } else if id.name == "actionBedMattressTile" {
791                self.nodeui
792                    .set_text_value("actionBedMattressTileId", tile_id.to_string());
793            }
794            ctx.ui.send(TheEvent::Custom(
795                TheId::named("Update Action List"),
796                TheValue::Empty,
797            ));
798            return true;
799        }
800        self.nodeui.handle_event(event)
801    }
802}