1use crate::prelude::*;
2use buildergraph::BuilderGraph;
3use codegridfx::Module;
4use indexmap::IndexMap;
5pub use rusterix::map::*;
6use theframework::prelude::*;
7use tilegraph::TileGraphPaletteSource;
8
9fn default_target_fps() -> u32 {
11 30
12}
13
14fn default_tick_ms() -> u32 {
16 250
17}
18
19fn default_rules() -> String {
20 String::new()
21}
22
23fn default_locales() -> String {
24 String::new()
25}
26
27fn default_audio_fx() -> String {
28 String::new()
29}
30
31fn default_authoring() -> String {
32 String::new()
33}
34
35fn default_world_module() -> Module {
36 Module::as_type(codegridfx::ModuleType::World)
37}
38
39fn default_tile_board_cols() -> i32 {
40 13
41}
42
43fn default_tile_board_rows() -> i32 {
44 9
45}
46
47fn default_tile_collection_name() -> String {
48 "New Collection".to_string()
49}
50
51fn default_tile_collection_version() -> String {
52 "0.1".to_string()
53}
54
55fn default_palette_material_slots() -> Vec<PaletteMaterial> {
56 vec![PaletteMaterial::default(); 256]
57}
58
59#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
60pub struct PaletteMaterial {
61 #[serde(default = "default_palette_roughness")]
62 pub roughness: f32,
63 #[serde(default = "default_palette_metallic")]
64 pub metallic: f32,
65 #[serde(default = "default_palette_opacity")]
66 pub opacity: f32,
67 #[serde(default = "default_palette_emissive")]
68 pub emissive: f32,
69}
70
71fn default_palette_roughness() -> f32 {
72 0.5
73}
74
75fn default_palette_metallic() -> f32 {
76 0.0
77}
78
79fn default_palette_opacity() -> f32 {
80 1.0
81}
82
83fn default_palette_emissive() -> f32 {
84 0.0
85}
86
87impl Default for PaletteMaterial {
88 fn default() -> Self {
89 Self {
90 roughness: default_palette_roughness(),
91 metallic: default_palette_metallic(),
92 opacity: default_palette_opacity(),
93 emissive: default_palette_emissive(),
94 }
95 }
96}
97
98#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
99pub struct BuilderGraphAsset {
100 pub id: Uuid,
101 pub graph_id: Uuid,
102 #[serde(default)]
103 pub graph_name: String,
104 #[serde(default)]
105 pub graph_data: String,
106}
107
108impl BuilderGraphAsset {
109 pub fn new_table(name: String) -> Self {
110 let graph_data = BuilderGraph::preset_table_script_named(name.clone());
111 let graph_name = if let Ok(document) = buildergraph::BuilderDocument::from_text(&graph_data)
112 {
113 document.name().to_string()
114 } else if name.is_empty() {
115 "Table".to_string()
116 } else {
117 name.clone()
118 };
119 Self {
120 id: Uuid::new_v4(),
121 graph_id: Uuid::new_v4(),
122 graph_name,
123 graph_data,
124 }
125 }
126
127 pub fn new_empty(name: String) -> Self {
128 let graph_data = BuilderGraph::empty_script_named(name.clone());
129 let graph_name = if let Ok(document) = buildergraph::BuilderDocument::from_text(&graph_data)
130 {
131 document.name().to_string()
132 } else if name.is_empty() {
133 "Empty".to_string()
134 } else {
135 name.clone()
136 };
137 Self {
138 id: Uuid::new_v4(),
139 graph_id: Uuid::new_v4(),
140 graph_name,
141 graph_data,
142 }
143 }
144
145 pub fn new_wall_torch(name: String) -> Self {
146 let graph_data = BuilderGraph::preset_wall_torch_script_named(name.clone());
147 let graph_name = if let Ok(document) = buildergraph::BuilderDocument::from_text(&graph_data)
148 {
149 document.name().to_string()
150 } else if name.is_empty() {
151 "Wall Torch".to_string()
152 } else {
153 name.clone()
154 };
155 Self {
156 id: Uuid::new_v4(),
157 graph_id: Uuid::new_v4(),
158 graph_name,
159 graph_data,
160 }
161 }
162
163 pub fn new_wall_lantern(name: String) -> Self {
164 let graph_data = BuilderGraph::preset_wall_lantern_script_named(name.clone());
165 let graph_name = if let Ok(document) = buildergraph::BuilderDocument::from_text(&graph_data)
166 {
167 document.name().to_string()
168 } else if name.is_empty() {
169 "Wall Lantern".to_string()
170 } else {
171 name.clone()
172 };
173 Self {
174 id: Uuid::new_v4(),
175 graph_id: Uuid::new_v4(),
176 graph_name,
177 graph_data,
178 }
179 }
180
181 pub fn new_campfire(name: String) -> Self {
182 let graph_data = BuilderGraph::preset_campfire_script_named(name.clone());
183 let graph_name = if let Ok(document) = buildergraph::BuilderDocument::from_text(&graph_data)
184 {
185 document.name().to_string()
186 } else if name.is_empty() {
187 "Campfire".to_string()
188 } else {
189 name.clone()
190 };
191 Self {
192 id: Uuid::new_v4(),
193 graph_id: Uuid::new_v4(),
194 graph_name,
195 graph_data,
196 }
197 }
198}
199
200#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
201pub struct NodeGroupAsset {
202 pub group_id: Uuid,
203 pub graph_id: Uuid,
204 #[serde(default)]
205 pub graph_name: String,
206 pub output_grid_width: u16,
207 pub output_grid_height: u16,
208 pub tile_pixel_width: u16,
209 pub tile_pixel_height: u16,
210 #[serde(default)]
211 pub palette_source: TileGraphPaletteSource,
212 #[serde(default)]
213 pub palette_colors: Vec<TheColor>,
214 #[serde(default)]
215 pub graph_data: String,
216}
217
218impl NodeGroupAsset {
219 pub fn new(
220 group_id: Uuid,
221 output_grid_width: u16,
222 output_grid_height: u16,
223 palette_colors: Vec<TheColor>,
224 ) -> Self {
225 Self {
226 group_id,
227 graph_id: Uuid::new_v4(),
228 graph_name: "New Node Graph".to_string(),
229 output_grid_width,
230 output_grid_height,
231 tile_pixel_width: 32,
232 tile_pixel_height: 32,
233 palette_source: TileGraphPaletteSource::Local,
234 palette_colors,
235 graph_data: String::new(),
236 }
237 }
238}
239
240#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
241pub enum TileCollectionEntry {
242 SingleTile(Uuid),
243 TileGroup(Uuid),
244}
245
246impl TileCollectionEntry {
247 pub fn matches_source(&self, source: rusterix::TileSource) -> bool {
248 match (self, source) {
249 (Self::SingleTile(a), rusterix::TileSource::SingleTile(b)) => *a == b,
250 (Self::TileGroup(a), rusterix::TileSource::TileGroup(b)) => *a == b,
251 _ => false,
252 }
253 }
254}
255
256#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
257pub struct TileCollectionAsset {
258 pub id: Uuid,
259 #[serde(default = "default_tile_collection_name")]
260 pub name: String,
261 #[serde(default)]
262 pub author: String,
263 #[serde(default = "default_tile_collection_version")]
264 pub version: String,
265 #[serde(default)]
266 pub description: String,
267 #[serde(default)]
268 pub entries: Vec<TileCollectionEntry>,
269 #[serde(default)]
270 pub tile_board_tiles: IndexMap<Uuid, Vec2<i32>>,
271 #[serde(default)]
272 pub tile_board_groups: IndexMap<Uuid, Vec2<i32>>,
273 #[serde(default)]
274 pub tile_board_empty_slots: Vec<Vec2<i32>>,
275}
276
277impl TileCollectionAsset {
278 pub fn new(name: String) -> Self {
279 Self {
280 id: Uuid::new_v4(),
281 name,
282 author: String::new(),
283 version: default_tile_collection_version(),
284 description: String::new(),
285 entries: Vec::new(),
286 tile_board_tiles: IndexMap::default(),
287 tile_board_groups: IndexMap::default(),
288 tile_board_empty_slots: Vec::new(),
289 }
290 }
291}
292
293#[derive(Serialize, Deserialize, Clone, Debug)]
294pub struct Project {
295 pub name: String,
296 pub regions: Vec<Region>,
297 pub tilemaps: Vec<Tilemap>,
298
299 #[serde(default)]
301 pub tiles: IndexMap<Uuid, rusterix::Tile>,
302
303 #[serde(default)]
305 pub tile_groups: IndexMap<Uuid, rusterix::TileGroup>,
306
307 #[serde(default)]
309 pub tile_node_groups: IndexMap<Uuid, NodeGroupAsset>,
310
311 #[serde(default)]
313 pub builder_graphs: IndexMap<Uuid, BuilderGraphAsset>,
314
315 #[serde(default)]
317 pub tile_collections: IndexMap<Uuid, TileCollectionAsset>,
318
319 #[serde(default)]
321 pub tile_board_tiles: IndexMap<Uuid, Vec2<i32>>,
322
323 #[serde(default)]
325 pub tile_board_groups: IndexMap<Uuid, Vec2<i32>>,
326
327 #[serde(default)]
329 pub tile_board_empty_slots: Vec<Vec2<i32>>,
330
331 #[serde(default = "default_tile_board_cols")]
333 pub tile_board_cols: i32,
334
335 #[serde(default = "default_tile_board_rows")]
337 pub tile_board_rows: i32,
338
339 #[serde(default)]
340 pub time: TheTime,
341
342 #[serde(default)]
343 pub characters: IndexMap<Uuid, Character>,
344 #[serde(default)]
345 pub items: IndexMap<Uuid, Item>,
346
347 #[serde(default)]
348 pub screens: IndexMap<Uuid, Screen>,
349
350 #[serde(default)]
351 pub assets: IndexMap<Uuid, Asset>,
352
353 #[serde(default)]
354 pub palette: ThePalette,
355
356 #[serde(default = "default_target_fps")]
357 pub target_fps: u32,
358
359 #[serde(default = "default_tick_ms")]
360 pub tick_ms: u32,
361
362 #[serde(default)]
363 pub config: String,
364
365 #[serde(default = "default_world_module")]
366 pub world_module: Module,
367
368 #[serde(default)]
369 pub world_source: String,
370
371 #[serde(default)]
372 pub world_source_debug: String,
373
374 #[serde(default = "default_rules")]
375 pub rules: String,
376
377 #[serde(default = "default_locales")]
378 pub locales: String,
379
380 #[serde(default = "default_audio_fx")]
381 pub audio_fx: String,
382
383 #[serde(default = "default_authoring")]
384 pub authoring: String,
385
386 #[serde(default)]
387 pub avatars: IndexMap<Uuid, Avatar>,
388
389 #[serde(default = "default_palette_material_slots")]
390 pub palette_materials: Vec<PaletteMaterial>,
391}
392
393impl Default for Project {
394 fn default() -> Self {
395 Self::new()
396 }
397}
398
399impl Project {
400 pub fn new() -> Self {
401 let region = Region::default();
402
403 Self {
404 name: String::new(),
405
406 regions: vec![region],
407 tilemaps: vec![],
408
409 tiles: IndexMap::default(),
410 tile_groups: IndexMap::default(),
411 tile_node_groups: IndexMap::default(),
412 builder_graphs: IndexMap::default(),
413 tile_collections: IndexMap::default(),
414 tile_board_tiles: IndexMap::default(),
415 tile_board_groups: IndexMap::default(),
416 tile_board_empty_slots: Vec::new(),
417 tile_board_cols: default_tile_board_cols(),
418 tile_board_rows: default_tile_board_rows(),
419
420 time: TheTime::default(),
421
422 characters: IndexMap::default(),
423 items: IndexMap::default(),
424
425 screens: IndexMap::default(),
426 assets: IndexMap::default(),
427
428 palette: ThePalette::default(),
429
430 target_fps: default_target_fps(),
431 tick_ms: default_tick_ms(),
432
433 avatars: IndexMap::default(),
434 palette_materials: default_palette_material_slots(),
435
436 config: String::new(),
437 world_module: default_world_module(),
438 world_source: String::new(),
439 world_source_debug: String::new(),
440 rules: default_rules(),
441 locales: default_locales(),
442 audio_fx: default_audio_fx(),
443 authoring: default_authoring(),
444 }
445 }
446
447 pub fn add_character(&mut self, character: Character) {
449 self.characters.insert(character.id, character);
450 }
451
452 pub fn ensure_palette_materials_len(&mut self) {
453 if self.palette_materials.len() < self.palette.colors.len() {
454 self.palette_materials
455 .resize(self.palette.colors.len(), PaletteMaterial::default());
456 } else if self.palette_materials.len() > self.palette.colors.len() {
457 self.palette_materials.truncate(self.palette.colors.len());
458 }
459 }
460
461 pub fn reset_palette_material(&mut self, index: usize) {
462 self.ensure_palette_materials_len();
463 if let Some(material) = self.palette_materials.get_mut(index) {
464 *material = PaletteMaterial::default();
465 }
466 }
467
468 pub fn reset_all_palette_materials(&mut self) {
469 self.palette_materials = default_palette_material_slots();
470 self.ensure_palette_materials_len();
471 }
472
473 pub fn remove_character(&mut self, id: &Uuid) {
475 self.characters.shift_remove(id);
476 }
477
478 pub fn sorted_character_list(&self) -> Vec<(Uuid, String)> {
480 let mut entries: Vec<(Uuid, String)> = self
481 .characters
482 .iter()
483 .map(|(uuid, data)| (*uuid, data.name.clone()))
484 .collect();
485
486 entries.sort_by(|a, b| a.1.cmp(&b.1));
487 entries
488 }
489
490 pub fn sorted_item_list(&self) -> Vec<(Uuid, String)> {
492 let mut entries: Vec<(Uuid, String)> = self
493 .items
494 .iter()
495 .map(|(uuid, data)| (*uuid, data.name.clone()))
496 .collect();
497
498 entries.sort_by(|a, b| a.1.cmp(&b.1));
499 entries
500 }
501
502 pub fn add_avatar(&mut self, avatar: Avatar) {
504 self.avatars.insert(avatar.id, avatar);
505 }
506
507 pub fn add_tile_group(&mut self, tile_group: rusterix::TileGroup) {
508 self.tile_groups.insert(tile_group.id, tile_group);
509 }
510
511 pub fn add_tile_node_group(&mut self, node_group: NodeGroupAsset) {
512 self.tile_node_groups
513 .insert(node_group.group_id, node_group);
514 }
515
516 pub fn add_builder_graph(&mut self, builder_graph: BuilderGraphAsset) {
517 self.builder_graphs.insert(builder_graph.id, builder_graph);
518 }
519
520 pub fn add_tile_collection(&mut self, collection: TileCollectionAsset) {
521 self.tile_collections.insert(collection.id, collection);
522 }
523
524 pub fn is_tile_node_group(&self, id: &Uuid) -> bool {
525 self.tile_node_groups.contains_key(id)
526 }
527
528 pub fn collection_contains_source(
529 &self,
530 collection_id: &Uuid,
531 source: rusterix::TileSource,
532 ) -> bool {
533 self.tile_collections
534 .get(collection_id)
535 .map(|collection| {
536 collection
537 .entries
538 .iter()
539 .any(|entry| entry.matches_source(source))
540 })
541 .unwrap_or(false)
542 }
543
544 pub fn add_source_to_collection(&mut self, collection_id: &Uuid, source: rusterix::TileSource) {
545 let Some(collection) = self.tile_collections.get_mut(collection_id) else {
546 return;
547 };
548 let entry = match source {
549 rusterix::TileSource::SingleTile(id) => TileCollectionEntry::SingleTile(id),
550 rusterix::TileSource::TileGroup(id) => TileCollectionEntry::TileGroup(id),
551 _ => return,
552 };
553 if !collection.entries.contains(&entry) {
554 collection.entries.push(entry);
555 }
556 }
557
558 pub fn remove_source_from_collections(&mut self, source: rusterix::TileSource) {
559 for collection in self.tile_collections.values_mut() {
560 collection
561 .entries
562 .retain(|entry| !entry.matches_source(source));
563 match source {
564 rusterix::TileSource::SingleTile(id) => {
565 collection.tile_board_tiles.shift_remove(&id);
566 }
567 rusterix::TileSource::TileGroup(id) => {
568 collection.tile_board_groups.shift_remove(&id);
569 }
570 _ => {}
571 }
572 }
573 }
574
575 pub fn remove_tile_group(&mut self, id: &Uuid) {
576 self.tile_groups.shift_remove(id);
577 self.tile_node_groups.shift_remove(id);
578 self.tile_board_groups.shift_remove(id);
579 self.remove_source_from_collections(rusterix::TileSource::TileGroup(*id));
580 }
581
582 pub fn tile_board_position(&self, source: rusterix::TileSource) -> Option<Vec2<i32>> {
583 match source {
584 rusterix::TileSource::SingleTile(id) => self.tile_board_tiles.get(&id).copied(),
585 rusterix::TileSource::TileGroup(id) => self.tile_board_groups.get(&id).copied(),
586 _ => None,
587 }
588 }
589
590 pub fn collection_tile_board_position(
591 &self,
592 collection_id: &Uuid,
593 source: rusterix::TileSource,
594 ) -> Option<Vec2<i32>> {
595 let collection = self.tile_collections.get(collection_id)?;
596 match source {
597 rusterix::TileSource::SingleTile(id) => collection.tile_board_tiles.get(&id).copied(),
598 rusterix::TileSource::TileGroup(id) => collection.tile_board_groups.get(&id).copied(),
599 _ => None,
600 }
601 }
602
603 pub fn tile_board_empty_slots(&self) -> &[Vec2<i32>] {
604 &self.tile_board_empty_slots
605 }
606
607 pub fn collection_tile_board_empty_slots(&self, collection_id: &Uuid) -> Option<&[Vec2<i32>]> {
608 self.tile_collections
609 .get(collection_id)
610 .map(|collection| collection.tile_board_empty_slots.as_slice())
611 }
612
613 pub fn set_tile_board_position(&mut self, source: rusterix::TileSource, pos: Vec2<i32>) {
614 self.clear_tile_board_empty_slot(pos);
615 match source {
616 rusterix::TileSource::SingleTile(id) => {
617 self.tile_board_tiles.insert(id, pos);
618 }
619 rusterix::TileSource::TileGroup(id) => {
620 self.tile_board_groups.insert(id, pos);
621 }
622 _ => {}
623 }
624 }
625
626 pub fn set_collection_tile_board_position(
627 &mut self,
628 collection_id: &Uuid,
629 source: rusterix::TileSource,
630 pos: Vec2<i32>,
631 ) {
632 let Some(collection) = self.tile_collections.get_mut(collection_id) else {
633 return;
634 };
635 if let Some(index) = collection
636 .tile_board_empty_slots
637 .iter()
638 .position(|p| *p == pos)
639 {
640 collection.tile_board_empty_slots.swap_remove(index);
641 }
642 match source {
643 rusterix::TileSource::SingleTile(id) => {
644 collection.tile_board_tiles.insert(id, pos);
645 }
646 rusterix::TileSource::TileGroup(id) => {
647 collection.tile_board_groups.insert(id, pos);
648 }
649 _ => {}
650 }
651 }
652
653 pub fn reserve_tile_board_empty_slot(&mut self, pos: Vec2<i32>) {
654 if !self.tile_board_empty_slots.contains(&pos) {
655 self.tile_board_empty_slots.push(pos);
656 }
657 }
658
659 pub fn reserve_collection_tile_board_empty_slot(
660 &mut self,
661 collection_id: &Uuid,
662 pos: Vec2<i32>,
663 ) {
664 let Some(collection) = self.tile_collections.get_mut(collection_id) else {
665 return;
666 };
667 if !collection.tile_board_empty_slots.contains(&pos) {
668 collection.tile_board_empty_slots.push(pos);
669 }
670 }
671
672 pub fn clear_tile_board_empty_slot(&mut self, pos: Vec2<i32>) {
673 if let Some(index) = self.tile_board_empty_slots.iter().position(|p| *p == pos) {
674 self.tile_board_empty_slots.swap_remove(index);
675 }
676 }
677
678 pub fn ensure_tile_board_space(&mut self, pos: Vec2<i32>) {
679 if pos.x >= self.tile_board_cols - 1 {
680 self.tile_board_cols = pos.x + 2;
681 }
682 if pos.y >= self.tile_board_rows - 1 {
683 self.tile_board_rows = pos.y + 2;
684 }
685 }
686
687 pub fn remove_avatar(&mut self, id: &Uuid) {
689 self.avatars.shift_remove(id);
690 }
691
692 pub fn find_avatar_for_animation(&self, animation_id: &Uuid) -> Option<&Avatar> {
694 self.avatars
695 .values()
696 .find(|a| a.animations.iter().any(|anim| anim.id == *animation_id))
697 }
698
699 pub fn get_editing_texture(
701 &self,
702 editing_ctx: &PixelEditingContext,
703 ) -> Option<&rusterix::Texture> {
704 match editing_ctx {
705 PixelEditingContext::None => None,
706 PixelEditingContext::Tile(tile_id, frame_index) => {
707 let tile = self.tiles.get(tile_id)?;
708 tile.textures.get(*frame_index)
709 }
710 PixelEditingContext::AvatarFrame(
711 avatar_id,
712 anim_id,
713 perspective_index,
714 frame_index,
715 ) => {
716 let avatar = self.avatars.get(avatar_id)?;
717 let anim = avatar.animations.iter().find(|a| a.id == *anim_id)?;
718 let perspective = anim.perspectives.get(*perspective_index)?;
719 perspective.frames.get(*frame_index).map(|f| &f.texture)
720 }
721 }
722 }
723
724 pub fn get_editing_texture_mut(
726 &mut self,
727 editing_ctx: &PixelEditingContext,
728 ) -> Option<&mut rusterix::Texture> {
729 match editing_ctx {
730 PixelEditingContext::None => None,
731 PixelEditingContext::Tile(tile_id, frame_index) => {
732 let tile = self.tiles.get_mut(tile_id)?;
733 tile.textures.get_mut(*frame_index)
734 }
735 PixelEditingContext::AvatarFrame(
736 avatar_id,
737 anim_id,
738 perspective_index,
739 frame_index,
740 ) => {
741 let avatar = self.avatars.get_mut(avatar_id)?;
742 let anim = avatar.animations.iter_mut().find(|a| a.id == *anim_id)?;
743 let perspective = anim.perspectives.get_mut(*perspective_index)?;
744 perspective
745 .frames
746 .get_mut(*frame_index)
747 .map(|f| &mut f.texture)
748 }
749 }
750 }
751
752 pub fn get_editing_avatar_frame(
754 &self,
755 editing_ctx: &PixelEditingContext,
756 ) -> Option<&rusterix::AvatarAnimationFrame> {
757 match editing_ctx {
758 PixelEditingContext::AvatarFrame(
759 avatar_id,
760 anim_id,
761 perspective_index,
762 frame_index,
763 ) => {
764 let avatar = self.avatars.get(avatar_id)?;
765 let anim = avatar.animations.iter().find(|a| a.id == *anim_id)?;
766 let perspective = anim.perspectives.get(*perspective_index)?;
767 perspective.frames.get(*frame_index)
768 }
769 _ => None,
770 }
771 }
772
773 pub fn get_editing_avatar_frame_mut(
775 &mut self,
776 editing_ctx: &PixelEditingContext,
777 ) -> Option<&mut rusterix::AvatarAnimationFrame> {
778 match editing_ctx {
779 PixelEditingContext::AvatarFrame(
780 avatar_id,
781 anim_id,
782 perspective_index,
783 frame_index,
784 ) => {
785 let avatar = self.avatars.get_mut(avatar_id)?;
786 let anim = avatar.animations.iter_mut().find(|a| a.id == *anim_id)?;
787 let perspective = anim.perspectives.get_mut(*perspective_index)?;
788 perspective.frames.get_mut(*frame_index)
789 }
790 _ => None,
791 }
792 }
793
794 pub fn get_editing_avatar_perspective(
796 &self,
797 editing_ctx: &PixelEditingContext,
798 ) -> Option<&rusterix::AvatarPerspective> {
799 match editing_ctx {
800 PixelEditingContext::AvatarFrame(avatar_id, anim_id, perspective_index, _) => {
801 let avatar = self.avatars.get(avatar_id)?;
802 let anim = avatar.animations.iter().find(|a| a.id == *anim_id)?;
803 anim.perspectives.get(*perspective_index)
804 }
805 _ => None,
806 }
807 }
808
809 pub fn get_editing_avatar_perspective_mut(
811 &mut self,
812 editing_ctx: &PixelEditingContext,
813 ) -> Option<&mut rusterix::AvatarPerspective> {
814 match editing_ctx {
815 PixelEditingContext::AvatarFrame(avatar_id, anim_id, perspective_index, _) => {
816 let avatar = self.avatars.get_mut(avatar_id)?;
817 let anim = avatar.animations.iter_mut().find(|a| a.id == *anim_id)?;
818 anim.perspectives.get_mut(*perspective_index)
819 }
820 _ => None,
821 }
822 }
823
824 pub fn add_item(&mut self, item: Item) {
826 self.items.insert(item.id, item);
827 }
828
829 pub fn remove_item(&mut self, id: &Uuid) {
831 self.items.shift_remove(id);
832 }
833
834 pub fn add_tilemap(&mut self, tilemap: Tilemap) {
836 self.tilemaps.push(tilemap)
837 }
838
839 pub fn get_tilemap(&self, uuid: Uuid) -> Option<&Tilemap> {
841 self.tilemaps.iter().find(|t| t.id == uuid)
842 }
843
844 pub fn get_tilemap_mut(&mut self, uuid: Uuid) -> Option<&mut Tilemap> {
846 self.tilemaps.iter_mut().find(|t| t.id == uuid)
847 }
848
849 pub fn remove_tilemap(&mut self, id: TheId) {
851 self.tilemaps.retain(|item| item.id != id.uuid);
852 }
853
854 pub fn contains_region(&self, uuid: &Uuid) -> bool {
856 self.regions.iter().find(|t| t.id == *uuid).is_some()
857 }
858
859 pub fn get_region(&self, uuid: &Uuid) -> Option<&Region> {
861 self.regions.iter().find(|t| t.id == *uuid)
862 }
863
864 pub fn get_region_mut(&mut self, uuid: &Uuid) -> Option<&mut Region> {
866 self.regions.iter_mut().find(|t| t.id == *uuid)
867 }
868
869 pub fn get_region_ctx(&self, ctx: &ServerContext) -> Option<&Region> {
871 self.regions.iter().find(|t| t.id == ctx.curr_region)
872 }
873
874 pub fn get_region_ctx_mut(&mut self, ctx: &ServerContext) -> Option<&mut Region> {
876 self.regions.iter_mut().find(|t| t.id == ctx.curr_region)
877 }
878
879 pub fn get_screen_ctx(&self, ctx: &ServerContext) -> Option<&Screen> {
881 self.screens.get(&ctx.curr_screen)
882 }
883
884 pub fn get_screen_ctx_mut(&mut self, ctx: &ServerContext) -> Option<&mut Screen> {
886 self.screens.get_mut(&ctx.curr_screen)
887 }
888
889 pub fn remove_region(&mut self, id: &Uuid) {
891 self.regions.retain(|item| item.id != *id);
892 }
893
894 pub fn get_map(&self, ctx: &ServerContext) -> Option<&Map> {
896 if ctx.editor_view_mode != EditorViewMode::D2 {
897 if let Some(region) = self.get_region(&ctx.curr_region) {
898 if ctx.geometry_edit_mode == GeometryEditMode::Detail {
899 if let Some(surface) = ctx.active_detail_surface.as_ref() {
900 if let Some(surface) = region.map.surfaces.get(&surface.id) {
901 if let Some(profile_id) = surface.profile {
902 return region.map.profiles.get(&profile_id);
903 }
904 }
905 }
906 }
907 return Some(®ion.map);
908 }
909 } else if ctx.get_map_context() == MapContext::Region {
910 let id = ctx.curr_region;
911 if let Some(surface) = &ctx.editing_surface {
913 if let Some(region) = self.regions.iter().find(|t| t.id == id) {
914 if let Some(surface) = region.map.surfaces.get(&surface.id) {
915 if let Some(profile_id) = surface.profile {
916 return region.map.profiles.get(&profile_id);
917 }
918 }
919 }
920 return None;
921 } else if let Some(region) = self.regions.iter().find(|t| t.id == id) {
922 return Some(®ion.map);
923 }
924 } else if ctx.get_map_context() == MapContext::Screen {
926 if let Some(id) = ctx.pc.id() {
927 if let Some(screen) = self.screens.get(&id) {
928 return Some(&screen.map);
929 }
930 }
931 } else if ctx.get_map_context() == MapContext::Character {
932 if let ContentContext::CharacterTemplate(id) = ctx.curr_character {
933 if let Some(character) = self.characters.get(&id) {
934 return Some(&character.map);
935 }
936 }
937 } else if ctx.get_map_context() == MapContext::Item {
938 if let ContentContext::ItemTemplate(id) = ctx.curr_item {
939 if let Some(item) = self.items.get(&id) {
940 return Some(&item.map);
941 }
942 }
943 }
944 None
945 }
946
947 pub fn get_map_mut(&mut self, ctx: &ServerContext) -> Option<&mut Map> {
949 if ctx.get_map_context() == MapContext::Region {
950 let id = ctx.curr_region;
951 if ctx.editor_view_mode != EditorViewMode::D2 {
953 if let Some(region) = self.get_region_mut(&ctx.curr_region) {
954 if ctx.geometry_edit_mode == GeometryEditMode::Detail {
955 if let Some(surface) = ctx.active_detail_surface.as_ref() {
956 if let Some(surface) = region.map.surfaces.get_mut(&surface.id) {
957 if let Some(profile_id) = surface.profile {
958 return region.map.profiles.get_mut(&profile_id);
959 }
960 }
961 }
962 }
963 return Some(&mut region.map);
964 }
965 } else if let Some(surface) = &ctx.editing_surface {
966 if let Some(region) = self.regions.iter_mut().find(|t| t.id == id) {
967 if let Some(surface) = region.map.surfaces.get_mut(&surface.id) {
968 if let Some(profile_id) = surface.profile {
969 return region.map.profiles.get_mut(&profile_id);
970 }
971 }
972 }
973 return None;
974 } else if let Some(region) = self.regions.iter_mut().find(|t| t.id == id) {
975 return Some(&mut region.map);
976 }
977 } else if ctx.get_map_context() == MapContext::Screen {
979 if let Some(id) = ctx.pc.id() {
980 if let Some(screen) = self.screens.get_mut(&id) {
981 return Some(&mut screen.map);
982 }
983 }
984 } else if ctx.get_map_context() == MapContext::Character {
985 if let ContentContext::CharacterTemplate(id) = ctx.curr_character {
986 if let Some(character) = self.characters.get_mut(&id) {
987 return Some(&mut character.map);
988 }
989 }
990 } else if ctx.get_map_context() == MapContext::Item {
991 if let ContentContext::ItemTemplate(id) = ctx.curr_item {
992 if let Some(item) = self.items.get_mut(&id) {
993 return Some(&mut item.map);
994 }
995 }
996 }
997 None
998 }
999
1000 pub fn add_screen(&mut self, screen: Screen) {
1002 self.screens.insert(screen.id, screen);
1003 }
1004
1005 pub fn remove_screen(&mut self, id: &Uuid) {
1007 self.screens.shift_remove(id);
1008 }
1009
1010 pub fn sorted_screens_list(&self) -> Vec<(Uuid, String)> {
1012 let mut entries: Vec<(Uuid, String)> = self
1013 .screens
1014 .iter()
1015 .map(|(uuid, data)| (*uuid, data.name.clone()))
1016 .collect();
1017
1018 entries.sort_by(|a, b| a.1.cmp(&b.1));
1019 entries
1020 }
1021
1022 pub fn add_asset(&mut self, asset: Asset) {
1024 self.assets.insert(asset.id, asset);
1025 }
1026
1027 pub fn remove_asset(&mut self, id: &Uuid) {
1029 self.assets.shift_remove(id);
1030 }
1031
1032 pub fn sorted_assets_list(&self) -> Vec<(Uuid, String)> {
1034 let mut entries: Vec<(Uuid, String)> = self
1035 .assets
1036 .iter()
1037 .map(|(uuid, data)| (*uuid, data.name.clone()))
1038 .collect();
1039
1040 entries.sort_by(|a, b| a.1.cmp(&b.1));
1041 entries
1042 }
1043
1044 pub fn remove_tile(&mut self, id: &Uuid) {
1046 self.tiles.shift_remove(id);
1047 }
1048
1049 pub fn get_tile(&self, id: &Uuid) -> Option<&rusterix::Tile> {
1051 self.tiles.get(id)
1052 }
1053
1054 pub fn get_tile_mut(&mut self, id: &Uuid) -> Option<&mut rusterix::Tile> {
1056 self.tiles.get_mut(id)
1057 }
1058
1059 pub fn find_tile_id_by_alias(&self, alias: &str) -> Option<Uuid> {
1060 let needle = alias.trim();
1061 if needle.is_empty() {
1062 return None;
1063 }
1064
1065 let matches_alias = |value: &str| {
1066 value
1067 .split([',', ';', '\n'])
1068 .map(str::trim)
1069 .any(|part| !part.is_empty() && part.eq_ignore_ascii_case(needle))
1070 };
1071
1072 for (id, tile) in &self.tiles {
1073 if matches_alias(&tile.alias) {
1074 return Some(*id);
1075 }
1076 }
1077
1078 None
1079 }
1080}