Skip to main content

eldiron_shared/
context.rs

1use crate::prelude::*;
2use rusterix::DungeonTileKind;
3use rusterix::Surface;
4pub use rusterix::{Value, VertexBlendPreset, map::*};
5use scenevm::GeoId;
6use theframework::prelude::*;
7
8/// Identifies which texture is currently being edited by editor tools.
9/// This abstraction allows the same draw/fill/pick tools to work on
10/// any paintable texture source (tiles, avatar frames, future types).
11/// Each variant also determines how colors are resolved for painting.
12#[derive(PartialEq, Clone, Copy, Debug)]
13pub enum PixelEditingContext {
14    /// No active editing target.
15    None,
16    /// Editing a tile texture: (tile_id, frame_index).
17    Tile(Uuid, usize),
18    /// Editing an avatar animation frame: (avatar_id, anim_id, perspective_index, frame_index).
19    AvatarFrame(Uuid, Uuid, usize, usize),
20}
21
22impl Default for PixelEditingContext {
23    fn default() -> Self {
24        Self::None
25    }
26}
27
28#[derive(PartialEq, Clone, Copy, Debug)]
29pub enum AvatarAnchorEditSlot {
30    None,
31    Main,
32    Off,
33}
34
35impl Default for AvatarAnchorEditSlot {
36    fn default() -> Self {
37        Self::None
38    }
39}
40
41impl PixelEditingContext {
42    /// Returns the [r, g, b, a] color to paint with for this context.
43    /// For tiles: uses the palette color with opacity applied.
44    /// For avatar frames: uses the selected body marker color.
45    pub fn get_draw_color(
46        &self,
47        palette: &ThePalette,
48        opacity: f32,
49        body_marker_color: Option<[u8; 4]>,
50    ) -> Option<[u8; 4]> {
51        match self {
52            PixelEditingContext::None => None,
53            PixelEditingContext::Tile(..) => {
54                if let Some(color) = palette.get_current_color() {
55                    let mut arr = color.to_u8_array();
56                    arr[3] = (arr[3] as f32 * opacity) as u8;
57                    Some(arr)
58                } else {
59                    None
60                }
61            }
62            PixelEditingContext::AvatarFrame(..) => body_marker_color,
63        }
64    }
65
66    /// Returns the number of animation frames for this context.
67    pub fn get_frame_count(&self, project: &Project) -> usize {
68        match self {
69            PixelEditingContext::None => 0,
70            PixelEditingContext::Tile(tile_id, _) => {
71                project.tiles.get(tile_id).map_or(0, |t| t.textures.len())
72            }
73            PixelEditingContext::AvatarFrame(avatar_id, anim_id, persp_index, _) => project
74                .avatars
75                .get(avatar_id)
76                .and_then(|a| a.animations.iter().find(|anim| anim.id == *anim_id))
77                .and_then(|anim| anim.perspectives.get(*persp_index))
78                .map_or(0, |p| p.frames.len()),
79        }
80    }
81
82    /// Returns a copy of this context with the frame index replaced.
83    pub fn with_frame(&self, frame_index: usize) -> Self {
84        match *self {
85            PixelEditingContext::None => PixelEditingContext::None,
86            PixelEditingContext::Tile(tile_id, _) => {
87                PixelEditingContext::Tile(tile_id, frame_index)
88            }
89            PixelEditingContext::AvatarFrame(avatar_id, anim_id, persp_index, _) => {
90                PixelEditingContext::AvatarFrame(avatar_id, anim_id, persp_index, frame_index)
91            }
92        }
93    }
94}
95
96#[derive(PartialEq, Clone, Copy, Debug)]
97pub enum GizmoMode {
98    XZ,
99    XY,
100    YZ,
101}
102
103#[derive(PartialEq, Clone, Copy, Debug)]
104pub enum GeometryEditMode {
105    Geometry,
106    Detail,
107}
108
109#[derive(PartialEq, Clone, Copy, Debug)]
110pub enum EditorViewMode {
111    D2,
112    Orbit,
113    Iso,
114    FirstP,
115}
116
117impl EditorViewMode {
118    pub fn to_index(&self) -> i32 {
119        match self {
120            EditorViewMode::D2 => 0,
121            EditorViewMode::Orbit => 1,
122            EditorViewMode::Iso => 2,
123            EditorViewMode::FirstP => 3,
124        }
125    }
126    pub fn from_index(idx: i32) -> Self {
127        match idx {
128            1 => EditorViewMode::Orbit,
129            2 => EditorViewMode::Iso,
130            3 => EditorViewMode::FirstP,
131            _ => EditorViewMode::D2,
132        }
133    }
134
135    pub fn is_3d(&self) -> bool {
136        match self {
137            EditorViewMode::D2 => false,
138            _ => true,
139        }
140    }
141}
142
143#[derive(PartialEq, Clone, Copy, Debug)]
144pub enum EditingGeoFilter {
145    All,
146    DungeonOnly,
147}
148
149impl EditingGeoFilter {
150    pub fn to_index(&self) -> i32 {
151        match self {
152            EditingGeoFilter::All => 0,
153            EditingGeoFilter::DungeonOnly => 1,
154        }
155    }
156
157    pub fn from_index(idx: i32) -> Self {
158        match idx {
159            1 => EditingGeoFilter::DungeonOnly,
160            _ => EditingGeoFilter::All,
161        }
162    }
163}
164
165#[derive(PartialEq, Clone, Copy, Debug)]
166pub enum ContentContext {
167    Unknown,
168    CharacterInstance(Uuid),
169    ItemInstance(Uuid),
170    Sector(Uuid),
171    CharacterTemplate(Uuid),
172    ItemTemplate(Uuid),
173    Shader(Uuid),
174}
175
176#[derive(PartialEq, Clone, Copy, Debug)]
177pub enum ProjectContext {
178    Unknown,
179    Region(Uuid),
180    RegionSettings(Uuid),
181    RegionCharacterInstance(Uuid, Uuid),
182    RegionItemInstance(Uuid, Uuid),
183    Character(Uuid),
184    CharacterVisualCode(Uuid),
185    CharacterCode(Uuid),
186    CharacterData(Uuid),
187    CharacterPreviewRigging(Uuid),
188    Item(Uuid),
189    ItemVisualCode(Uuid),
190    ItemCode(Uuid),
191    ItemData(Uuid),
192    Tilemap(Uuid),
193    Screen(Uuid),
194    ScreenWidget(Uuid, Uuid),
195    Asset(Uuid),
196    Avatar(Uuid),
197    AvatarAnimation(Uuid, Uuid, usize),
198    ProjectSettings,
199    GameRules,
200    GameLocales,
201    GameAudioFx,
202    GameAuthoring,
203    DebugLog,
204    Console,
205}
206
207impl ProjectContext {
208    pub fn id(self) -> Option<Uuid> {
209        match self {
210            ProjectContext::Unknown
211            | ProjectContext::ProjectSettings
212            | ProjectContext::GameRules
213            | ProjectContext::GameLocales
214            | ProjectContext::GameAudioFx
215            | ProjectContext::GameAuthoring
216            | ProjectContext::DebugLog
217            | ProjectContext::Console => None,
218            ProjectContext::Region(id)
219            | ProjectContext::RegionSettings(id)
220            | ProjectContext::RegionCharacterInstance(id, _)
221            | ProjectContext::RegionItemInstance(id, _)
222            | ProjectContext::Character(id)
223            | ProjectContext::CharacterVisualCode(id)
224            | ProjectContext::CharacterCode(id)
225            | ProjectContext::CharacterData(id)
226            | ProjectContext::CharacterPreviewRigging(id)
227            | ProjectContext::Item(id)
228            | ProjectContext::ItemVisualCode(id)
229            | ProjectContext::ItemCode(id)
230            | ProjectContext::ItemData(id)
231            | ProjectContext::Tilemap(id)
232            | ProjectContext::Screen(id)
233            | ProjectContext::ScreenWidget(id, _)
234            | ProjectContext::Asset(id)
235            | ProjectContext::Avatar(id)
236            | ProjectContext::AvatarAnimation(id, _, _) => Some(id),
237        }
238    }
239
240    pub fn is_region(&self) -> bool {
241        match self {
242            ProjectContext::Region(_)
243            | ProjectContext::RegionSettings(_)
244            | ProjectContext::RegionCharacterInstance(_, _)
245            | ProjectContext::RegionItemInstance(_, _) => true,
246            _ => false,
247        }
248    }
249
250    pub fn get_region_character_instance_id(&self) -> Option<Uuid> {
251        match self {
252            ProjectContext::RegionCharacterInstance(_, instance_id) => Some(*instance_id),
253            _ => None,
254        }
255    }
256
257    pub fn get_region_item_instance_id(&self) -> Option<Uuid> {
258        match self {
259            ProjectContext::RegionItemInstance(_, instance_id) => Some(*instance_id),
260            _ => None,
261        }
262    }
263
264    pub fn is_character(&self) -> bool {
265        match self {
266            ProjectContext::Character(_)
267            | ProjectContext::CharacterVisualCode(_)
268            | ProjectContext::CharacterCode(_)
269            | ProjectContext::CharacterData(_)
270            | ProjectContext::CharacterPreviewRigging(_) => true,
271            _ => false,
272        }
273    }
274
275    pub fn is_item(&self) -> bool {
276        match self {
277            ProjectContext::Item(_)
278            | ProjectContext::ItemVisualCode(_)
279            | ProjectContext::ItemCode(_)
280            | ProjectContext::ItemData(_) => true,
281            _ => false,
282        }
283    }
284
285    pub fn is_tilemap(&self) -> bool {
286        match self {
287            ProjectContext::Tilemap(_) => true,
288            _ => false,
289        }
290    }
291
292    pub fn is_screen(&self) -> bool {
293        match self {
294            ProjectContext::Screen(_) | ProjectContext::ScreenWidget(_, _) => true,
295            _ => false,
296        }
297    }
298
299    pub fn get_screen_widget_id(&self) -> Option<Uuid> {
300        match self {
301            ProjectContext::ScreenWidget(_, widget_id) => Some(*widget_id),
302            _ => None,
303        }
304    }
305
306    pub fn is_asset(&self) -> bool {
307        match self {
308            ProjectContext::Asset(_) => true,
309            _ => false,
310        }
311    }
312
313    pub fn is_project_settings(&self) -> bool {
314        match self {
315            ProjectContext::ProjectSettings => true,
316            _ => false,
317        }
318    }
319
320    pub fn is_game_rules(&self) -> bool {
321        match self {
322            ProjectContext::GameRules => true,
323            _ => false,
324        }
325    }
326
327    pub fn is_game_locales(&self) -> bool {
328        match self {
329            ProjectContext::GameLocales => true,
330            _ => false,
331        }
332    }
333
334    pub fn is_game_audio_fx(&self) -> bool {
335        match self {
336            ProjectContext::GameAudioFx => true,
337            _ => false,
338        }
339    }
340
341    pub fn is_game_authoring(&self) -> bool {
342        match self {
343            ProjectContext::GameAuthoring => true,
344            _ => false,
345        }
346    }
347
348    pub fn has_custom_map(&self) -> bool {
349        match self {
350            ProjectContext::Screen(_) => true,
351            _ => false,
352        }
353    }
354}
355
356#[derive(PartialEq, Clone, Copy, Debug)]
357pub enum MapContext {
358    Region,
359    Screen,
360    Character,
361    Item,
362}
363
364/// This gives context to the server of the editing state for live highlighting.
365pub struct ServerContext {
366    // Tree Ids
367    pub tree_regions_id: Uuid,
368    pub tree_characters_id: Uuid,
369    pub tree_items_id: Uuid,
370    pub tree_tilemaps_id: Uuid,
371    pub tree_screens_id: Uuid,
372    pub tree_avatars_id: Uuid,
373    pub tree_assets_id: Uuid,
374    pub tree_assets_fonts_id: Uuid,
375    pub tree_assets_audio_id: Uuid,
376    pub tree_palette_id: Uuid,
377    pub tree_settings_id: Uuid,
378
379    /// The currently selected region in the editor.
380    pub curr_region: Uuid,
381
382    /// The current region content.
383    pub curr_region_content: ContentContext,
384
385    /// The currently selected character in the editor.
386    pub curr_character: ContentContext,
387
388    /// The currently selected item in the editor.
389    pub curr_item: ContentContext,
390
391    /// The current content context.
392    pub cc: ContentContext,
393
394    /// The current project context.
395    pub pc: ProjectContext,
396
397    /// The currently selected codegrid in the code editor.
398    pub curr_grid_id: Option<Uuid>,
399
400    /// The currently selected screen.
401    pub curr_screen: Uuid,
402
403    /// The logged interactions of the characters.
404    pub interactions: FxHashMap<Uuid, Vec<Interaction>>,
405
406    /// The currently selected tile
407    pub curr_tile_id: Option<Uuid>,
408
409    /// The currently selected assignable tile source.
410    pub curr_tile_source: Option<TileSource>,
411
412    /// The currently opened node-backed tile group editor, if any.
413    pub tile_node_group_id: Option<Uuid>,
414
415    /// The currently selected builder graph asset, if any.
416    pub curr_builder_graph_id: Option<Uuid>,
417
418    /// The currently selected conceptual dungeon paint archetype.
419    pub curr_dungeon_tile: DungeonTileKind,
420
421    /// Previous dock before switching to the dungeon dock.
422    pub prev_dungeon_dock: Option<String>,
423
424    /// Default floor base for dungeon painting.
425    pub curr_dungeon_floor_base: f32,
426
427    /// Default wall / room height for dungeon painting.
428    pub curr_dungeon_height: f32,
429
430    /// Whether generated dungeon geometry should create floor surfaces.
431    pub curr_dungeon_create_floor: bool,
432
433    /// Whether generated dungeon geometry should create ceiling surfaces.
434    pub curr_dungeon_create_ceiling: bool,
435
436    /// Whether newly painted dungeon cells should stay standalone and avoid merging.
437    pub curr_dungeon_standalone: bool,
438
439    /// Door span in tiles for newly painted dungeon door cells.
440    pub curr_dungeon_tile_span: i32,
441
442    /// Conceptual door depth hint for newly painted dungeon door cells.
443    pub curr_dungeon_tile_depth: f32,
444
445    /// Conceptual door opening height for newly painted dungeon door cells.
446    pub curr_dungeon_tile_height: f32,
447
448    /// Conceptual door opening mode for newly painted dungeon door cells.
449    pub curr_dungeon_tile_open_mode: i32,
450
451    /// Item handler class for newly painted dungeon door cells.
452    pub curr_dungeon_tile_item: String,
453
454    /// Target floor base for newly painted stair tiles.
455    pub curr_dungeon_stair_target_floor_base: f32,
456
457    /// Number of generated steps for newly painted stair tiles.
458    pub curr_dungeon_stair_steps: i32,
459
460    /// Default tile id for newly painted stair tiles.
461    pub curr_dungeon_stair_tile_id: String,
462
463    /// Default tile mode for newly painted stair tiles.
464    pub curr_dungeon_stair_tile_mode: i32,
465
466    /// Raw dungeon render override TOML block.
467    pub curr_dungeon_render_toml: String,
468
469    /// Whether dungeon-specific render overrides are enabled in gameplay.
470    pub curr_dungeon_render_enabled: bool,
471
472    /// Transition duration in seconds for dungeon render overrides.
473    pub curr_dungeon_render_transition_seconds: f32,
474
475    /// Whether sunlight is enabled while inside dungeon-generated sectors.
476    pub curr_dungeon_render_sun_enabled: bool,
477
478    /// Whether sun shadows are enabled while inside dungeon-generated sectors.
479    pub curr_dungeon_render_shadow_enabled: bool,
480
481    /// Fog density override for dungeon-generated sectors (same percent scale as [render]).
482    pub curr_dungeon_render_fog_density: f32,
483
484    /// Fog color override for dungeon-generated sectors.
485    pub curr_dungeon_render_fog_color: String,
486
487    /// Previous subdivision setting before entering Dungeon Tool.
488    pub prev_dungeon_subdivisions: Option<f32>,
489
490    /// Whether the Builder tool is currently active.
491    pub builder_tool_active: bool,
492    /// Whether the Palette tool is currently active.
493    pub palette_tool_active: bool,
494
495    /// The current frame/texture index being edited in tile editor
496    pub curr_tile_frame_index: usize,
497
498    /// The palette opacity for drawing tools
499    pub palette_opacity: f32,
500
501    /// The currently selected model
502    pub curr_model_id: Option<Uuid>,
503
504    /// The currently selected material
505    pub curr_material_id: Option<Uuid>,
506
507    /// The screen editor drawing mode.
508    pub screen_editor_mode_foreground: bool,
509
510    /// Hover geometry info
511    pub hover: (Option<u32>, Option<u32>, Option<u32>),
512
513    /// The current grid hover position
514    pub hover_cursor: Option<Vec2<f32>>,
515
516    /// The current 3d hover position
517    pub hover_cursor_3d: Option<Vec3<f32>>,
518    /// Ray direction for the current 3D hover hit.
519    pub hover_ray_dir_3d: Option<Vec3<f32>>,
520
521    /// The hovered surface resolved from the raw 3D scene hit, if any.
522    pub hover_surface: Option<Surface>,
523    /// World-space hit position for the hovered surface, if any.
524    pub hover_surface_hit_pos: Option<Vec3<f32>>,
525    /// The currently active surface for a 3D detail edit chain.
526    pub active_detail_surface: Option<Surface>,
527
528    /// The current grid hover height
529    pub hover_height: Option<f32>,
530
531    /// Current Tool Type
532    pub curr_map_tool_type: MapToolType,
533
534    /// Current Map Context
535    curr_map_context: MapContext,
536
537    /// A click on map content originated from the map
538    pub content_click_from_map: bool,
539
540    /// Dont show rect based geometry on map
541    pub no_rect_geo_on_map: bool,
542
543    /// Show wall profile in linedef mode.
544    pub profile_view: Option<u32>,
545
546    /// The current editing surface
547    pub editing_surface: Option<Surface>,
548    /// Persistent 3D hit position associated with the current editing surface.
549    pub editing_surface_hit_pos: Option<Vec3<f32>>,
550
551    /// The currently selected action
552    pub curr_action_id: Option<Uuid>,
553    /// Automatially apply actions
554    pub auto_action: bool,
555
556    /// Pending entity position changes: (from, to)
557    pub moved_entities: FxHashMap<Uuid, (Vec3<f32>, Vec3<f32>)>,
558
559    /// Pending item position changes: (from, to)
560    pub moved_items: FxHashMap<Uuid, (Vec3<f32>, Vec3<f32>)>,
561
562    /// Pending character orientation changes: (from, to)
563    pub rotated_entities: FxHashMap<Uuid, (Vec2<f32>, Vec2<f32>)>,
564
565    /// Selected wall row, set by the linedef Hud
566    pub selected_wall_row: Option<i32>,
567
568    /// View mode of the editor
569    pub editor_view_mode: EditorViewMode,
570    /// Previous editor view mode before entering Dungeon Tool.
571    pub prev_dungeon_view_mode: Option<EditorViewMode>,
572
573    /// World mode is active
574    pub world_mode: bool,
575
576    /// Game server is running
577    pub game_mode: bool,
578
579    /// Map clipboard
580    pub clipboard: Map,
581
582    /// Map clipboard which is currently being pasted
583    pub paste_clipboard: Option<Map>,
584
585    /// Background Progress Text
586    pub background_progress: Option<String>,
587
588    /// Character Region Override
589    pub character_region_override: bool,
590
591    /// Item Region Override
592    pub item_region_override: bool,
593
594    /// Animation counter for tile previews
595    pub animation_counter: usize,
596
597    /// The current 3D hover hit
598    pub geo_hit: Option<GeoId>,
599
600    /// The current geometry hover hit position
601    pub geo_hit_pos: Vec3<f32>,
602
603    /// Temporary storage for the editing positon
604    pub editing_pos_buffer: Option<Vec3<f32>>,
605    /// Per-map, per-3D-view camera anchor (editing position).
606    pub editing_view_pos_by_map: FxHashMap<(Uuid, i32), Vec3<f32>>,
607    /// Per-map, per-3D-view look target.
608    pub editing_view_look_by_map: FxHashMap<(Uuid, i32), Vec3<f32>>,
609    /// Per-map 2D camera state: (offset, grid_size).
610    pub editing_view_2d_by_map: FxHashMap<Uuid, (Vec2<f32>, f32)>,
611    /// Per-map Iso camera scale.
612    pub editing_view_iso_scale_by_map: FxHashMap<Uuid, f32>,
613
614    /// The index of the selected icon in the hud
615    pub selected_hud_icon_index: i32,
616
617    ///Switch for showing 3D editing geometry
618    pub show_editing_geometry: bool,
619
620    /// Editor-only visibility filter for geometry.
621    pub editing_geo_filter: EditingGeoFilter,
622    /// Hide dungeon ceilings while dungeon-only filtering is active.
623    pub dungeon_no_ceiling: bool,
624
625    /// Position of the 2D editing slice.
626    pub editing_slice: f32,
627    /// Height/thickness of the 2D editing slice.
628    pub editing_slice_height: f32,
629
630    /// The current plane for 3D movement
631    pub gizmo_mode: GizmoMode,
632    /// Geometry edits work in world/gizmo space, detail edits on hovered surfaces.
633    pub geometry_edit_mode: GeometryEditMode,
634
635    // For the Rect tool, identify the current sector and tile for preview
636    pub rect_sector_id_3d: Option<u32>,
637    pub rect_tile_id_3d: (i32, i32),
638    pub rect_terrain_id: Option<(i32, i32)>,
639    pub rect_blend_preset: VertexBlendPreset,
640
641    /// Game input mode
642    pub game_input_mode: bool,
643
644    /// Text gameplay mode inside the Creator Game Tool
645    pub text_game_mode: bool,
646
647    /// Help mode state
648    pub help_mode: bool,
649
650    /// The current editing context for texture editor tools.
651    pub editing_ctx: PixelEditingContext,
652
653    /// The selected body marker color for avatar painting.
654    pub body_marker_color: Option<[u8; 4]>,
655    /// Active avatar anchor edit slot for pixel editor clicks.
656    pub avatar_anchor_slot: AvatarAnchorEditSlot,
657}
658
659impl Default for ServerContext {
660    fn default() -> Self {
661        Self::new()
662    }
663}
664
665impl ServerContext {
666    pub fn new() -> Self {
667        Self {
668            curr_region: Uuid::nil(),
669
670            tree_regions_id: Uuid::new_v4(),
671            tree_characters_id: Uuid::new_v4(),
672            tree_items_id: Uuid::new_v4(),
673            tree_tilemaps_id: Uuid::new_v4(),
674            tree_screens_id: Uuid::new_v4(),
675            tree_avatars_id: Uuid::new_v4(),
676            tree_assets_id: Uuid::new_v4(),
677            tree_assets_fonts_id: Uuid::new_v4(),
678            tree_assets_audio_id: Uuid::new_v4(),
679            tree_palette_id: Uuid::new_v4(),
680            tree_settings_id: Uuid::new_v4(),
681
682            curr_region_content: ContentContext::Unknown,
683            curr_character: ContentContext::Unknown,
684            curr_item: ContentContext::Unknown,
685            cc: ContentContext::Unknown,
686
687            pc: ProjectContext::Unknown,
688
689            curr_grid_id: None,
690
691            curr_screen: Uuid::nil(),
692
693            interactions: FxHashMap::default(),
694
695            curr_tile_id: None,
696            curr_tile_source: None,
697            tile_node_group_id: None,
698            curr_builder_graph_id: None,
699            curr_dungeon_tile: DungeonTileKind::FLOOR,
700            prev_dungeon_dock: None,
701            curr_dungeon_floor_base: 0.0,
702            curr_dungeon_height: 4.0,
703            curr_dungeon_create_floor: true,
704            curr_dungeon_create_ceiling: true,
705            curr_dungeon_standalone: false,
706            curr_dungeon_tile_span: 2,
707            curr_dungeon_tile_depth: 0.5,
708            curr_dungeon_tile_height: 2.25,
709            curr_dungeon_tile_open_mode: 0,
710            curr_dungeon_tile_item: "Door Handler".to_string(),
711            curr_dungeon_stair_target_floor_base: -1.0,
712            curr_dungeon_stair_steps: 4,
713            curr_dungeon_stair_tile_id: String::new(),
714            curr_dungeon_stair_tile_mode: 1,
715            curr_dungeon_render_toml: "[render]\ntransition_seconds = 1.0\nsun_enabled = false\nshadow_enabled = true\nfog_density = 5.0\nfog_color = \"#000000\"\n".to_string(),
716            curr_dungeon_render_enabled: true,
717            curr_dungeon_render_transition_seconds: 1.0,
718            curr_dungeon_render_sun_enabled: false,
719            curr_dungeon_render_shadow_enabled: true,
720            curr_dungeon_render_fog_density: 5.0,
721            curr_dungeon_render_fog_color: "#000000".to_string(),
722            prev_dungeon_subdivisions: None,
723            builder_tool_active: false,
724            palette_tool_active: false,
725            curr_tile_frame_index: 0,
726
727            palette_opacity: 1.0,
728
729            curr_model_id: None,
730            curr_material_id: None,
731
732            screen_editor_mode_foreground: false,
733
734            hover: (None, None, None),
735            hover_cursor: None,
736            hover_cursor_3d: None,
737            hover_ray_dir_3d: None,
738            hover_surface: None,
739            hover_surface_hit_pos: None,
740            active_detail_surface: None,
741            hover_height: None,
742
743            curr_map_tool_type: MapToolType::Linedef,
744            curr_map_context: MapContext::Region,
745
746            content_click_from_map: false,
747            no_rect_geo_on_map: true,
748            profile_view: None,
749
750            editing_surface: None,
751            editing_surface_hit_pos: None,
752            curr_action_id: None,
753            auto_action: false,
754
755            moved_entities: FxHashMap::default(),
756            moved_items: FxHashMap::default(),
757            rotated_entities: FxHashMap::default(),
758
759            selected_wall_row: Some(0),
760
761            editor_view_mode: EditorViewMode::D2,
762            prev_dungeon_view_mode: None,
763
764            world_mode: false,
765            game_mode: false,
766
767            clipboard: Map::default(),
768            paste_clipboard: None,
769
770            background_progress: None,
771
772            character_region_override: true,
773            item_region_override: true,
774
775            animation_counter: 0,
776
777            geo_hit: None,
778            geo_hit_pos: Vec3::zero(),
779
780            editing_pos_buffer: None,
781            editing_view_pos_by_map: FxHashMap::default(),
782            editing_view_look_by_map: FxHashMap::default(),
783            editing_view_2d_by_map: FxHashMap::default(),
784            editing_view_iso_scale_by_map: FxHashMap::default(),
785
786            selected_hud_icon_index: 0,
787            show_editing_geometry: true,
788            editing_geo_filter: EditingGeoFilter::All,
789            dungeon_no_ceiling: false,
790
791            gizmo_mode: GizmoMode::XZ,
792            geometry_edit_mode: GeometryEditMode::Geometry,
793
794            editing_slice: 0.0,
795            editing_slice_height: 2.0,
796            rect_sector_id_3d: None,
797            rect_tile_id_3d: (0, 0),
798            rect_terrain_id: None,
799            rect_blend_preset: VertexBlendPreset::Solid,
800
801            game_input_mode: false,
802            text_game_mode: false,
803            help_mode: false,
804
805            editing_ctx: PixelEditingContext::None,
806            body_marker_color: None,
807            avatar_anchor_slot: AvatarAnchorEditSlot::None,
808        }
809    }
810
811    /// Checks if the PolyView has focus.
812    pub fn polyview_has_focus(&self, ctx: &TheContext) -> bool {
813        if let Some(focus) = &ctx.ui.focus {
814            if focus.name == "PolyView" {
815                return true;
816            }
817        }
818        false
819    }
820
821    /// Returns the current map context
822    pub fn get_map_context(&self) -> MapContext {
823        if self.pc.is_screen() {
824            return MapContext::Screen;
825        }
826
827        MapContext::Region
828
829        /*
830        if (self.curr_map_context == MapContext::Character && self.character_region_override)
831            || (self.curr_map_context == MapContext::Item && self.item_region_override)
832            || (self.curr_map_context == MapContext::Shader)
833        {
834            MapContext::Region
835        } else {
836            self.curr_map_context
837        }*/
838    }
839
840    pub fn set_map_context(&mut self, map_context: MapContext) {
841        self.curr_map_context = map_context;
842    }
843
844    #[inline]
845    fn view_key(map_id: Uuid, mode: EditorViewMode) -> (Uuid, i32) {
846        (map_id, mode.to_index())
847    }
848
849    pub fn store_edit_view_for_map(
850        &mut self,
851        map_id: Uuid,
852        mode: EditorViewMode,
853        pos: Vec3<f32>,
854        look: Vec3<f32>,
855    ) {
856        if mode == EditorViewMode::D2 {
857            return;
858        }
859        let key = Self::view_key(map_id, mode);
860        self.editing_view_pos_by_map.insert(key, pos);
861        self.editing_view_look_by_map.insert(key, look);
862    }
863
864    pub fn load_edit_view_for_map(
865        &self,
866        map_id: Uuid,
867        mode: EditorViewMode,
868    ) -> Option<(Vec3<f32>, Vec3<f32>)> {
869        if mode == EditorViewMode::D2 {
870            return None;
871        }
872        let key = Self::view_key(map_id, mode);
873        let pos = self.editing_view_pos_by_map.get(&key).copied()?;
874        let look = self.editing_view_look_by_map.get(&key).copied()?;
875        Some((pos, look))
876    }
877
878    pub fn store_edit_view_2d_for_map(&mut self, map_id: Uuid, offset: Vec2<f32>, grid_size: f32) {
879        self.editing_view_2d_by_map
880            .insert(map_id, (offset, grid_size));
881    }
882
883    pub fn load_edit_view_2d_for_map(&self, map_id: Uuid) -> Option<(Vec2<f32>, f32)> {
884        self.editing_view_2d_by_map.get(&map_id).copied()
885    }
886
887    pub fn store_edit_view_iso_scale_for_map(&mut self, map_id: Uuid, scale: f32) {
888        self.editing_view_iso_scale_by_map.insert(map_id, scale);
889    }
890
891    pub fn load_edit_view_iso_scale_for_map(&self, map_id: Uuid) -> Option<f32> {
892        self.editing_view_iso_scale_by_map.get(&map_id).copied()
893    }
894
895    /// Clears all state data.
896    pub fn clear(&mut self) {
897        self.curr_region_content = ContentContext::Unknown;
898        self.curr_character = ContentContext::Unknown;
899        self.curr_item = ContentContext::Unknown;
900        self.cc = ContentContext::Unknown;
901
902        self.curr_region = Uuid::nil();
903        self.curr_grid_id = None;
904        self.curr_screen = Uuid::nil();
905        self.tile_node_group_id = None;
906        self.curr_builder_graph_id = None;
907        self.builder_tool_active = false;
908        self.palette_tool_active = false;
909        self.interactions.clear();
910        self.moved_entities.clear();
911        self.moved_items.clear();
912        self.editing_view_pos_by_map.clear();
913        self.editing_view_look_by_map.clear();
914        self.editing_view_2d_by_map.clear();
915        self.editing_view_iso_scale_by_map.clear();
916    }
917
918    pub fn clear_interactions(&mut self) {
919        self.interactions.clear();
920    }
921
922    /// Convert local screen position to a map grid position
923    pub fn local_to_map_grid(
924        &self,
925        screen_size: Vec2<f32>,
926        coord: Vec2<f32>,
927        map: &Map,
928        subdivisions: f32,
929    ) -> Vec2<f32> {
930        let grid_space_pos = coord - screen_size / 2.0 - Vec2::new(map.offset.x, -map.offset.y);
931        let snapped = grid_space_pos / map.grid_size;
932        let rounded = snapped.map(|x| x.round());
933
934        if subdivisions > 1.0 {
935            let subdivision_size = 1.0 / subdivisions;
936            // Calculate fractional part of the snapped position
937            let fractional = snapped - rounded;
938            // Snap the fractional part to the nearest subdivision
939            rounded + fractional.map(|x| (x / subdivision_size).round() * subdivision_size)
940        } else {
941            rounded
942        }
943    }
944
945    fn snap_scalar(value: f32, subdivisions: f32) -> f32 {
946        let step = 1.0 / subdivisions.max(1.0);
947        (value / step).round() * step
948    }
949
950    pub fn snap_world_point_for_edit(&self, map: &Map, point: Vec3<f32>) -> Vec3<f32> {
951        if self.geometry_edit_mode == GeometryEditMode::Detail
952            && let Some(surface) = self.active_detail_surface.as_ref().or(self
953                .hover_surface
954                .as_ref()
955                .or(self.editing_surface.as_ref()))
956        {
957            let uv = surface.world_to_uv(point);
958            let snapped_uv = Vec2::new(
959                Self::snap_scalar(uv.x, map.subdivisions),
960                Self::snap_scalar(uv.y, map.subdivisions),
961            );
962            return surface.uv_to_world(snapped_uv);
963        }
964
965        Vec3::new(
966            Self::snap_scalar(point.x, map.subdivisions),
967            Self::snap_scalar(point.y, map.subdivisions),
968            Self::snap_scalar(point.z, map.subdivisions),
969        )
970    }
971
972    /// Snap to a grid cell
973    pub fn local_to_map_cell(
974        &self,
975        screen_size: Vec2<f32>,
976        coord: Vec2<f32>,
977        map: &Map,
978        subdivisions: f32,
979    ) -> Vec2<f32> {
980        let grid_space_pos = coord - screen_size / 2.0 - Vec2::new(map.offset.x, -map.offset.y);
981        let grid_cell = (grid_space_pos / map.grid_size).map(|x| x.floor());
982
983        if subdivisions > 1.0 {
984            let sub_cell_size = map.grid_size / subdivisions;
985            let sub_index = grid_space_pos
986                .map(|x| x.rem_euclid(map.grid_size) / sub_cell_size)
987                .map(|x| x.floor());
988            grid_cell + sub_index / subdivisions
989        } else {
990            grid_cell
991        }
992    }
993
994    /// Convert a map grid position to a local screen position
995    pub fn map_grid_to_local(screen_size: Vec2<f32>, grid_pos: Vec2<f32>, map: &Map) -> Vec2<f32> {
996        let grid_space_pos = grid_pos * map.grid_size;
997        grid_space_pos + Vec2::new(map.offset.x, -map.offset.y) + screen_size / 2.0
998    }
999
1000    /// Centers the map at the given grid position.
1001    pub fn center_map_at_grid_pos(
1002        &mut self,
1003        _screen_size: Vec2<f32>,
1004        grid_pos: Vec2<f32>,
1005        map: &mut Map,
1006    ) {
1007        let pixel_pos = grid_pos * map.grid_size;
1008        map.offset.x = -(pixel_pos.x);
1009        map.offset.y = pixel_pos.y;
1010    }
1011
1012    /// Returns the geometry at the given screen_position
1013    pub fn geometry_at(
1014        &self,
1015        screen_size: Vec2<f32>,
1016        screen_pos: Vec2<f32>,
1017        map: &Map,
1018    ) -> (Option<u32>, Option<u32>, Option<u32>) {
1019        let mut selection: (Option<u32>, Option<u32>, Option<u32>) = (None, None, None);
1020        let hover_threshold = 6.0;
1021
1022        // Check the vertices
1023        for vertex in &map.vertices {
1024            if vertex.intersects_vertical_slice(self.editing_slice, self.editing_slice_height) {
1025                if let Some(vertex_pos) = map.get_vertex(vertex.id) {
1026                    let vertex_pos = Self::map_grid_to_local(screen_size, vertex_pos, map);
1027                    if (screen_pos - vertex_pos).magnitude() <= hover_threshold {
1028                        selection.0 = Some(vertex.id);
1029                        break;
1030                    }
1031                }
1032            }
1033        }
1034
1035        // Check the lines
1036        for linedef in &map.linedefs {
1037            if linedef.intersects_vertical_slice(map, self.editing_slice, self.editing_slice_height)
1038            {
1039                if self.no_rect_geo_on_map && map.is_linedef_in_rect(linedef.id) {
1040                    continue;
1041                }
1042
1043                let start_vertex = map.get_vertex(linedef.start_vertex);
1044                let end_vertex = map.get_vertex(linedef.end_vertex);
1045
1046                if let Some(start_vertex) = start_vertex {
1047                    if let Some(end_vertex) = end_vertex {
1048                        let start_pos = Self::map_grid_to_local(screen_size, start_vertex, map);
1049                        let end_pos = Self::map_grid_to_local(screen_size, end_vertex, map);
1050
1051                        // Compute the perpendicular distance from the point to the line
1052                        let line_vec = end_pos - start_pos;
1053                        let mouse_vec = screen_pos - start_pos;
1054                        let line_vec_length = line_vec.magnitude();
1055                        let projection =
1056                            mouse_vec.dot(line_vec) / (line_vec_length * line_vec_length);
1057                        let closest_point = start_pos + projection.clamp(0.0, 1.0) * line_vec;
1058                        let distance = (screen_pos - closest_point).magnitude();
1059
1060                        if distance <= hover_threshold {
1061                            selection.1 = Some(linedef.id);
1062                            break;
1063                        }
1064                    }
1065                }
1066            }
1067        }
1068
1069        // Check the sectors
1070
1071        /// Point-in-polygon test using the ray-casting method
1072        fn point_in_polygon(point: Vec2<f32>, polygon: &[Vec2<f32>]) -> bool {
1073            let mut inside = false;
1074            let mut j = polygon.len() - 1;
1075
1076            for i in 0..polygon.len() {
1077                if (polygon[i].y > point.y) != (polygon[j].y > point.y)
1078                    && point.x
1079                        < (polygon[j].x - polygon[i].x) * (point.y - polygon[i].y)
1080                            / (polygon[j].y - polygon[i].y)
1081                            + polygon[i].x
1082                {
1083                    inside = !inside;
1084                }
1085                j = i;
1086            }
1087
1088            inside
1089        }
1090
1091        // Reverse on sorted sectors by area (to allow to pick small sectors first)
1092        let ordered = map.sorted_sectors_by_area();
1093        for sector in ordered.iter().rev() {
1094            if sector.intersects_vertical_slice(map, self.editing_slice, self.editing_slice_height)
1095            {
1096                if self.no_rect_geo_on_map
1097                    && sector.properties.contains("rect")
1098                    && sector.name.is_empty()
1099                {
1100                    continue;
1101                }
1102                let mut vertices = Vec::new();
1103                for &linedef_id in &sector.linedefs {
1104                    if let Some(linedef) = map.find_linedef(linedef_id) {
1105                        if let Some(start_vertex) = map.find_vertex(linedef.start_vertex) {
1106                            let vertex =
1107                                Self::map_grid_to_local(screen_size, start_vertex.as_vec2(), map);
1108
1109                            // Add the vertex to the list if it isn't already there
1110                            if vertices.last() != Some(&vertex) {
1111                                vertices.push(vertex);
1112                            }
1113                        }
1114                    }
1115                }
1116
1117                if point_in_polygon(screen_pos, &vertices) {
1118                    selection.2 = Some(sector.id);
1119                    return selection;
1120                }
1121            }
1122        }
1123
1124        selection
1125    }
1126
1127    /// Returns all geometry within the given rectangle.
1128    pub fn geometry_in_rectangle(
1129        &self,
1130        top_left: Vec2<f32>,
1131        bottom_right: Vec2<f32>,
1132        map: &Map,
1133    ) -> (Vec<u32>, Vec<u32>, Vec<u32>) {
1134        let mut selection: (Vec<u32>, Vec<u32>, Vec<u32>) = (Vec::new(), Vec::new(), Vec::new());
1135
1136        // Define helper to check if a point is within the rectangle
1137        fn point_in_rectangle(
1138            point: Vec2<f32>,
1139            top_left: Vec2<f32>,
1140            bottom_right: Vec2<f32>,
1141        ) -> bool {
1142            point.x >= top_left.x
1143                && point.x <= bottom_right.x
1144                && point.y >= top_left.y
1145                && point.y <= bottom_right.y
1146        }
1147
1148        /// Check if a line segment intersects a rectangle
1149        fn line_intersects_rectangle(
1150            a: Vec2<f32>,
1151            b: Vec2<f32>,
1152            top_left: Vec2<f32>,
1153            bottom_right: Vec2<f32>,
1154        ) -> bool {
1155            // fn between(x: f32, min: f32, max: f32) -> bool {
1156            //     x >= min && x <= max
1157            // }
1158
1159            // Axis-Aligned Bounding Box (AABB) test for the line segment
1160            let (min_x, max_x) = (a.x.min(b.x), a.x.max(b.x));
1161            let (min_y, max_y) = (a.y.min(b.y), a.y.max(b.y));
1162
1163            if min_x > bottom_right.x
1164                || max_x < top_left.x
1165                || min_y > bottom_right.y
1166                || max_y < top_left.y
1167            {
1168                return false; // Line is outside the rectangle
1169            }
1170
1171            // Check if either endpoint is inside the rectangle
1172            if point_in_rectangle(a, top_left, bottom_right)
1173                || point_in_rectangle(b, top_left, bottom_right)
1174            {
1175                return true;
1176            }
1177
1178            // Check edge intersections
1179            let rect_edges = [
1180                (top_left, Vec2::new(bottom_right.x, top_left.y)), // Top
1181                (Vec2::new(bottom_right.x, top_left.y), bottom_right), // Right
1182                (bottom_right, Vec2::new(top_left.x, bottom_right.y)), // Bottom
1183                (Vec2::new(top_left.x, bottom_right.y), top_left), // Left
1184            ];
1185
1186            rect_edges
1187                .iter()
1188                .any(|&(p1, p2)| line_segments_intersect(a, b, p1, p2))
1189        }
1190
1191        /// Check if two line segments intersect
1192        fn line_segments_intersect(
1193            p1: Vec2<f32>,
1194            p2: Vec2<f32>,
1195            q1: Vec2<f32>,
1196            q2: Vec2<f32>,
1197        ) -> bool {
1198            fn ccw(a: Vec2<f32>, b: Vec2<f32>, c: Vec2<f32>) -> bool {
1199                (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x)
1200            }
1201
1202            ccw(p1, q1, q2) != ccw(p2, q1, q2) && ccw(p1, p2, q1) != ccw(p1, p2, q2)
1203        }
1204
1205        // Check vertices
1206        for vertex in &map.vertices {
1207            if vertex.intersects_vertical_slice(self.editing_slice, self.editing_slice_height) {
1208                if let Some(vertex_pos) = map.get_vertex(vertex.id) {
1209                    if point_in_rectangle(vertex_pos, top_left, bottom_right) {
1210                        selection.0.push(vertex.id);
1211                    }
1212                }
1213            }
1214        }
1215
1216        // Check linedefs
1217        for linedef in &map.linedefs {
1218            if linedef.intersects_vertical_slice(map, self.editing_slice, self.editing_slice_height)
1219            {
1220                let start_vertex = map.get_vertex(linedef.start_vertex);
1221                let end_vertex = map.get_vertex(linedef.end_vertex);
1222
1223                if let (Some(start_vertex), Some(end_vertex)) = (start_vertex, end_vertex) {
1224                    let start_pos = start_vertex;
1225                    let end_pos = end_vertex;
1226
1227                    // Check if either endpoint is inside the rectangle
1228                    if point_in_rectangle(start_pos, top_left, bottom_right)
1229                        || point_in_rectangle(end_pos, top_left, bottom_right)
1230                    {
1231                        selection.1.push(linedef.id);
1232                    }
1233                }
1234            }
1235        }
1236
1237        // Check sectors
1238        // fn point_in_polygon(point: Vec2f, polygon: &[Vec2f]) -> bool {
1239        //     let mut inside = false;
1240        //     let mut j = polygon.len() - 1;
1241
1242        //     for i in 0..polygon.len() {
1243        //         if (polygon[i].y > point.y) != (polygon[j].y > point.y)
1244        //             && point.x
1245        //                 < (polygon[j].x - polygon[i].x) * (point.y - polygon[i].y)
1246        //                     / (polygon[j].y - polygon[i].y)
1247        //                     + polygon[i].x
1248        //         {
1249        //             inside = !inside;
1250        //         }
1251        //         j = i;
1252        //     }
1253
1254        //     inside
1255        // }
1256
1257        for sector in &map.sectors {
1258            if sector.intersects_vertical_slice(map, self.editing_slice, self.editing_slice_height)
1259            {
1260                let mut vertices = Vec::new();
1261                for &linedef_id in &sector.linedefs {
1262                    if let Some(linedef) = map.find_linedef(linedef_id) {
1263                        if let Some(start_vertex) = map.find_vertex(linedef.start_vertex) {
1264                            let vertex = start_vertex.as_vec2();
1265
1266                            // Add the vertex to the list if it isn't already there
1267                            if vertices.last() != Some(&vertex) {
1268                                vertices.push(vertex);
1269                            }
1270                        }
1271                    }
1272                }
1273
1274                // Check if any part of the sector polygon is in the rectangle
1275                if vertices
1276                    .iter()
1277                    .any(|v| point_in_rectangle(*v, top_left, bottom_right))
1278                    || vertices.windows(2).any(|pair| {
1279                        // For edges, check if they intersect the rectangle
1280                        let (a, b) = (pair[0], pair[1]);
1281                        line_intersects_rectangle(a, b, top_left, bottom_right)
1282                    })
1283                {
1284                    selection.2.push(sector.id);
1285                }
1286            }
1287        }
1288
1289        selection
1290    }
1291
1292    /// Returns false if the hover is empty
1293    pub fn hover_is_empty(&self) -> bool {
1294        self.hover.0.is_none() && self.hover.1.is_none() && self.hover.2.is_none()
1295    }
1296
1297    /// Converts the hover into arrays.
1298    pub fn hover_to_arrays(&self) -> (Vec<u32>, Vec<u32>, Vec<u32>) {
1299        let mut arrays: (Vec<u32>, Vec<u32>, Vec<u32>) = (vec![], vec![], vec![]);
1300        if let Some(v) = self.hover.0 {
1301            arrays.0.push(v);
1302        }
1303        if let Some(l) = self.hover.1 {
1304            arrays.1.push(l);
1305        }
1306        if let Some(s) = self.hover.2 {
1307            arrays.2.push(s);
1308        }
1309        arrays
1310    }
1311
1312    /// Adds the given interactions provided by a server tick to the context.
1313    pub fn add_interactions(&mut self, interactions: Vec<Interaction>) {
1314        for interaction in interactions {
1315            if let Some(interactions) = self.interactions.get_mut(&interaction.to) {
1316                interactions.push(interaction);
1317            } else {
1318                self.interactions.insert(interaction.to, vec![interaction]);
1319            }
1320        }
1321    }
1322}