Skip to main content

siglus_scene_vm/runtime/
globals.rs

1use std::collections::{HashMap, HashSet};
2
3use crate::assets::RgbaImage;
4use crate::runtime::gan::GanState;
5use crate::runtime::int_event::IntEvent;
6use crate::platform_time::{Duration, Instant};
7
8use crate::image_manager::ImageId;
9use crate::layer::{LayerId, SpriteId};
10
11/// Screen wipe transition state.
12///
13/// This models the timing and script-visible behavior of the original `Gp_wipe`
14/// subsystem. Rendering is handled elsewhere; here we only track parameters and
15/// completion.
16#[derive(Debug, Clone)]
17pub struct WipeState {
18    pub mask_file: Option<String>,
19    pub mask_image_id: Option<ImageId>,
20    pub wipe_type: i32,
21    pub wipe_time_ms: i32,
22    pub speed_mode: i32,
23    pub start_time_ms: i32,
24    pub option: Vec<i32>,
25
26    pub begin_order: i32,
27    pub end_order: i32,
28    pub begin_layer: i32,
29    pub end_layer: i32,
30
31    pub wait_flag: bool,
32    pub key_wait_mode: i32,
33    pub with_low_order: i32,
34
35    pub mask_cache: HashMap<(ImageId, u64, ImageId, u64, u16), ImageId>,
36
37    started_at: Instant,
38    end_at: Instant,
39}
40
41impl WipeState {
42    #[allow(clippy::too_many_arguments)]
43    pub fn new(
44        mask_file: Option<String>,
45        mask_image_id: Option<ImageId>,
46        wipe_type: i32,
47        wipe_time_ms: i32,
48        start_time_ms: i32,
49        speed_mode: i32,
50        option: Vec<i32>,
51        begin_order: i32,
52        end_order: i32,
53        begin_layer: i32,
54        end_layer: i32,
55        wait_flag: bool,
56        key_wait_mode: i32,
57        with_low_order: i32,
58    ) -> Self {
59        let now = Instant::now();
60        let wipe_time_ms_u = wipe_time_ms.max(0) as u64;
61        let start_ms_u = start_time_ms.max(0) as u64;
62        let start_adv = start_ms_u.min(wipe_time_ms_u);
63
64        let started_at = now
65            .checked_sub(Duration::from_millis(start_adv))
66            .unwrap_or(now);
67        let end_at = started_at + Duration::from_millis(wipe_time_ms_u);
68
69        Self {
70            mask_file,
71            mask_image_id,
72            wipe_type,
73            wipe_time_ms,
74            speed_mode,
75            start_time_ms,
76            option,
77            begin_order,
78            end_order,
79            begin_layer,
80            end_layer,
81            wait_flag,
82            key_wait_mode,
83            with_low_order,
84            mask_cache: HashMap::new(),
85            started_at,
86            end_at,
87        }
88    }
89
90    pub fn is_done(&self) -> bool {
91        Instant::now() >= self.end_at
92    }
93
94    pub fn progress(&self) -> f32 {
95        let total = self
96            .end_at
97            .saturating_duration_since(self.started_at)
98            .as_secs_f32();
99        if total <= 0.0 {
100            return 1.0;
101        }
102        let elapsed = Instant::now()
103            .saturating_duration_since(self.started_at)
104            .as_secs_f32();
105        (elapsed / total).clamp(0.0, 1.0)
106    }
107
108    #[allow(dead_code)]
109    pub fn remaining_ms(&self) -> u64 {
110        if self.is_done() {
111            0
112        } else {
113            self.end_at.duration_since(Instant::now()).as_millis() as u64
114        }
115    }
116}
117
118#[derive(Debug, Clone)]
119pub struct ScriptRuntimeState {
120    pub dont_set_save_point: bool,
121    pub skip_disable: bool,
122    pub ctrl_disable: bool,
123    pub not_stop_skip_by_click: bool,
124    pub not_skip_msg_by_click: bool,
125    pub skip_unread_message: bool,
126
127    pub auto_mode_flag: bool,
128    pub auto_mode_moji_wait: i64,
129    pub auto_mode_min_wait: i64,
130    pub auto_mode_moji_cnt: i64,
131
132    pub mouse_cursor_hide_onoff: i64,
133    pub mouse_cursor_hide_time: i64,
134
135    pub msg_speed: i64,
136    pub msg_nowait: bool,
137    pub async_msg_mode: bool,
138    pub async_msg_mode_once: bool,
139
140    pub hide_mwnd_disable: bool,
141    pub msg_back_disable: bool,
142    pub msg_back_off: bool,
143    pub msg_back_disp_off: bool,
144
145    pub cursor_disp_off: bool,
146    pub cursor_move_by_key_disable: bool,
147    pub cursor_runtime_visible: bool,
148    pub key_disable: HashSet<u8>,
149
150    pub mwnd_anime_off_flag: bool,
151    pub mwnd_anime_on_flag: bool,
152    pub mwnd_disp_off_flag: bool,
153
154    pub koe_dont_stop_on_flag: bool,
155    pub koe_dont_stop_off_flag: bool,
156
157    pub shortcut_disable: bool,
158    pub quake_stop_flag: bool,
159    pub emote_mouth_stop_flag: bool,
160    pub bgmfade_flag: bool,
161    pub wait_display_vsync_off_flag: bool,
162    pub skip_trigger: bool,
163    pub ignore_r_flag: bool,
164    pub cursor_no: i64,
165
166    pub time_stop_flag: bool,
167    pub counter_time_stop_flag: bool,
168    pub frame_action_time_stop_flag: bool,
169    pub stage_time_stop_flag: bool,
170
171    pub font_name: String,
172    pub font_bold: i64,
173    pub font_shadow: i64,
174}
175
176impl Default for ScriptRuntimeState {
177    fn default() -> Self {
178        Self {
179            dont_set_save_point: false,
180            skip_disable: false,
181            ctrl_disable: false,
182            not_stop_skip_by_click: false,
183            not_skip_msg_by_click: false,
184            skip_unread_message: false,
185            auto_mode_flag: false,
186            auto_mode_moji_wait: -1,
187            auto_mode_min_wait: -1,
188            auto_mode_moji_cnt: 0,
189            mouse_cursor_hide_onoff: -1,
190            mouse_cursor_hide_time: -1,
191            msg_speed: -1,
192            msg_nowait: false,
193            async_msg_mode: false,
194            async_msg_mode_once: false,
195            hide_mwnd_disable: false,
196            msg_back_disable: false,
197            msg_back_off: false,
198            msg_back_disp_off: false,
199            cursor_disp_off: false,
200            cursor_move_by_key_disable: false,
201            cursor_runtime_visible: true,
202            key_disable: HashSet::new(),
203            mwnd_anime_off_flag: false,
204            mwnd_anime_on_flag: false,
205            mwnd_disp_off_flag: false,
206            koe_dont_stop_on_flag: false,
207            koe_dont_stop_off_flag: false,
208            shortcut_disable: false,
209            quake_stop_flag: false,
210            emote_mouth_stop_flag: false,
211            bgmfade_flag: false,
212            wait_display_vsync_off_flag: false,
213            skip_trigger: false,
214            ignore_r_flag: false,
215            cursor_no: -1,
216            time_stop_flag: false,
217            counter_time_stop_flag: false,
218            frame_action_time_stop_flag: false,
219            stage_time_stop_flag: false,
220            font_name: String::new(),
221            font_bold: -1,
222            font_shadow: -1,
223        }
224    }
225}
226
227#[derive(Debug, Clone, Default)]
228pub struct SystemMessageBoxRecord {
229    pub kind: i32,
230    pub text: String,
231    pub debug_only: bool,
232}
233
234#[derive(Debug, Clone)]
235pub struct SystemMessageBoxButton {
236    pub label: String,
237    pub value: i64,
238}
239
240#[derive(Debug, Clone)]
241pub struct SystemMessageBoxModalState {
242    pub request_id: u64,
243    pub kind: i32,
244    pub text: String,
245    pub debug_only: bool,
246    pub buttons: Vec<SystemMessageBoxButton>,
247    pub cursor: usize,
248    pub native_pending: bool,
249    pub complete_wait_with_value: bool,
250}
251
252impl SystemMessageBoxModalState {
253    pub fn selected_value(&self) -> i64 {
254        self.buttons
255            .get(self.cursor.min(self.buttons.len().saturating_sub(1)))
256            .map(|b| b.value)
257            .unwrap_or(0)
258    }
259
260    pub fn cancel_value(&self) -> i64 {
261        self.buttons
262            .last()
263            .map(|b| b.value)
264            .unwrap_or_else(|| self.selected_value())
265    }
266}
267
268#[derive(Debug, Clone)]
269pub struct SystemRuntimeState {
270    pub active_flag: bool,
271    pub debug_flag: bool,
272    pub language_code: String,
273    pub debug_logs: Vec<String>,
274    pub dummy_checks: HashSet<String>,
275    pub bench_dialogs: Vec<String>,
276    pub messagebox_history: Vec<SystemMessageBoxRecord>,
277    pub messagebox_response_queue: Vec<i64>,
278    pub messagebox_modal: Option<SystemMessageBoxModalState>,
279    pub messagebox_modal_result: Option<i64>,
280    pub spec_info: String,
281}
282
283impl Default for SystemRuntimeState {
284    fn default() -> Self {
285        Self {
286            active_flag: true,
287            debug_flag: false,
288            language_code: std::env::var("SIGLUS_LANGUAGE").unwrap_or_else(|_| "JP".to_string()),
289            debug_logs: Vec::new(),
290            dummy_checks: HashSet::new(),
291            bench_dialogs: Vec::new(),
292            messagebox_history: Vec::new(),
293            messagebox_response_queue: Vec::new(),
294            messagebox_modal: None,
295            messagebox_modal_result: None,
296            spec_info: "siglus_scene_vm".to_string(),
297        }
298    }
299}
300
301#[derive(Debug, Clone, Copy, Default)]
302pub struct ToggleFeatureState {
303    pub onoff: bool,
304    pub enable: bool,
305    pub exist: bool,
306}
307
308impl ToggleFeatureState {
309    pub fn check_enabled(&self) -> i64 {
310        if self.enable && self.exist {
311            1
312        } else {
313            0
314        }
315    }
316}
317
318#[derive(Debug, Clone, Copy, Default)]
319pub struct ValueFeatureState {
320    pub value: i64,
321    pub enable: bool,
322    pub exist: bool,
323}
324
325impl ValueFeatureState {
326    pub fn check_enabled(&self) -> i64 {
327        if self.enable && self.exist {
328            1
329        } else {
330            0
331        }
332    }
333}
334
335#[derive(Debug, Clone, Default)]
336pub struct SaveSlotState {
337    pub exist: bool,
338    pub year: i64,
339    pub month: i64,
340    pub day: i64,
341    pub weekday: i64,
342    pub hour: i64,
343    pub minute: i64,
344    pub second: i64,
345    pub millisecond: i64,
346    pub title: String,
347    pub message: String,
348    pub full_message: String,
349    pub comment: String,
350    pub append_dir: String,
351    pub append_name: String,
352    pub values: HashMap<i32, i64>,
353}
354
355#[derive(Debug, Clone, Copy, PartialEq, Eq)]
356pub enum SyscomPendingProcKind {
357    EndGame,
358    ReturnToSel,
359    ReturnToMenu,
360    Save,
361    Load,
362    QuickSave,
363    QuickLoad,
364    BacklogLoad,
365    MsgBack,
366    OpenSyscomMenu,
367    OpenSave,
368    OpenLoad,
369    OpenConfig,
370}
371
372#[derive(Debug, Clone)]
373pub struct SyscomPendingProc {
374    pub kind: SyscomPendingProcKind,
375    pub warning: bool,
376    pub se_play: bool,
377    pub fade_out: bool,
378    pub leave_msgbk: bool,
379    pub save_id: i64,
380}
381
382#[derive(Debug, Clone)]
383pub struct SyscomRuntimeState {
384    pub syscom_menu_disable: bool,
385    pub menu_open: bool,
386    pub menu_kind: Option<i32>,
387    pub menu_result: Option<i64>,
388    pub menu_cursor: usize,
389    pub font_list: Vec<String>,
390    pub mwnd_btn_disable_all: bool,
391    pub mwnd_btn_touch_disable: bool,
392    pub mwnd_btn_disable: HashMap<i64, bool>,
393    pub read_skip: ToggleFeatureState,
394    pub auto_skip: ToggleFeatureState,
395    pub auto_mode: ToggleFeatureState,
396    pub hide_mwnd: ToggleFeatureState,
397    pub local_extra_switch: ToggleFeatureState,
398    pub local_extra_mode: ValueFeatureState,
399    pub local_extra_switches: [ToggleFeatureState; 4],
400    pub local_extra_modes: [ValueFeatureState; 4],
401    pub msg_back: ToggleFeatureState,
402    pub msg_back_open: bool,
403    pub msg_back_view_pos: usize,
404    pub msg_back_scroll_pos: i32,
405    pub msg_back_slider_pos: i32,
406    pub msg_back_target_no: isize,
407    pub msg_back_mouse_target_no: isize,
408    pub msg_back_msg_total_height: i32,
409    pub msg_back_proc_initialized: bool,
410    pub msg_back_slider_dragging: bool,
411    pub msg_back_slider_drag_start_mouse: i32,
412    pub msg_back_slider_drag_start_pos: i32,
413    pub msg_back_content_dragging: bool,
414    pub msg_back_content_drag_start_mouse: i32,
415    pub msg_back_content_drag_start_scroll_pos: i32,
416    pub return_to_sel: ToggleFeatureState,
417    pub return_to_menu: ToggleFeatureState,
418    pub end_game: ToggleFeatureState,
419    pub save_feature: ToggleFeatureState,
420    pub load_feature: ToggleFeatureState,
421    pub replay_koe: Option<(i64, i64)>,
422    pub current_save_scene_title: String,
423    pub current_save_message: String,
424    pub current_save_full_message: String,
425    pub total_play_time: i64,
426    pub save_slots: Vec<SaveSlotState>,
427    pub quick_save_slots: Vec<SaveSlotState>,
428    pub inner_save_exists: bool,
429    pub inner_save_streams: Vec<Vec<u8>>,
430    pub sel_save_stock_stream: Vec<u8>,
431    pub sel_save_ids: Vec<[u16; 7]>,
432    pub end_save_exists: bool,
433    pub last_menu_call: i32,
434    pub system_extra_int_value: i64,
435    pub system_extra_str_value: String,
436    pub config_int: HashMap<i32, i64>,
437    pub config_str: HashMap<i32, String>,
438    pub capture_buffer: Option<RgbaImage>,
439    pub capture_size: Option<(u32, u32)>,
440    pub return_scene_once: Option<(String, i64)>,
441    pub pending_proc: Option<SyscomPendingProc>,
442    pub msg_back_load_tid: i64,
443}
444
445
446impl Default for SyscomRuntimeState {
447    fn default() -> Self {
448        Self {
449            syscom_menu_disable: false,
450            menu_open: false,
451            menu_kind: None,
452            menu_result: None,
453            menu_cursor: 0,
454            font_list: Vec::new(),
455            mwnd_btn_disable_all: false,
456            mwnd_btn_touch_disable: false,
457            mwnd_btn_disable: HashMap::new(),
458            read_skip: ToggleFeatureState { onoff: false, enable: true, exist: true },
459            auto_skip: ToggleFeatureState { onoff: false, enable: true, exist: true },
460            auto_mode: ToggleFeatureState { onoff: false, enable: true, exist: true },
461            hide_mwnd: ToggleFeatureState { onoff: false, enable: true, exist: true },
462            local_extra_switch: ToggleFeatureState { onoff: false, enable: true, exist: true },
463            local_extra_mode: ValueFeatureState { value: 0, enable: true, exist: true },
464            local_extra_switches: [ToggleFeatureState { onoff: false, enable: true, exist: true }; 4],
465            local_extra_modes: [ValueFeatureState { value: 0, enable: true, exist: true }; 4],
466            msg_back: ToggleFeatureState { onoff: false, enable: true, exist: true },
467            msg_back_open: false,
468            msg_back_view_pos: 0,
469            msg_back_scroll_pos: 0,
470            msg_back_slider_pos: 0,
471            msg_back_target_no: -1,
472            msg_back_mouse_target_no: -1,
473            msg_back_msg_total_height: 0,
474            msg_back_proc_initialized: false,
475            msg_back_slider_dragging: false,
476            msg_back_slider_drag_start_mouse: 0,
477            msg_back_slider_drag_start_pos: 0,
478            msg_back_content_dragging: false,
479            msg_back_content_drag_start_mouse: 0,
480            msg_back_content_drag_start_scroll_pos: 0,
481            return_to_sel: ToggleFeatureState { onoff: false, enable: true, exist: true },
482            return_to_menu: ToggleFeatureState { onoff: false, enable: true, exist: true },
483            end_game: ToggleFeatureState { onoff: false, enable: true, exist: true },
484            save_feature: ToggleFeatureState { onoff: false, enable: true, exist: true },
485            load_feature: ToggleFeatureState { onoff: false, enable: true, exist: true },
486            replay_koe: None,
487            current_save_scene_title: String::new(),
488            current_save_message: String::new(),
489            current_save_full_message: String::new(),
490            total_play_time: 0,
491            save_slots: Vec::new(),
492            quick_save_slots: Vec::new(),
493            inner_save_exists: false,
494            inner_save_streams: Vec::new(),
495            sel_save_stock_stream: Vec::new(),
496            sel_save_ids: Vec::new(),
497            end_save_exists: false,
498            last_menu_call: 0,
499            system_extra_int_value: 0,
500            system_extra_str_value: String::new(),
501            config_int: HashMap::new(),
502            config_str: HashMap::new(),
503            capture_buffer: None,
504            capture_size: None,
505            return_scene_once: None,
506            pending_proc: None,
507            msg_back_load_tid: 0,
508        }
509    }
510}
511/// Global mutable state used by various "global element" (form) handlers.
512///
513/// This crate keeps these structures generic on purpose: many Siglus
514/// "global elements" are simple lists, counters, etc.
515#[derive(Debug, Clone, Copy, PartialEq, Eq)]
516pub enum LightType {
517    None = -1,
518    Directional = 0,
519    Point = 1,
520    Spot = 2,
521    ShadowMapSpot = 3,
522}
523
524pub const WORLD_LIGHT_MAX: usize = 128;
525pub const OBJ_DIRECTIONAL_LIGHT_MAX: usize = 4;
526pub const OBJ_POINT_LIGHT_MAX: usize = 4;
527pub const OBJ_SPOT_LIGHT_MAX: usize = 4;
528
529#[derive(Debug, Clone)]
530pub struct LightState {
531    pub id: i32,
532    pub kind: LightType,
533    pub diffuse: [f32; 4],
534    pub ambient: [f32; 4],
535    pub specular: [f32; 4],
536    pub pos: [f32; 3],
537    pub dir: [f32; 3],
538    pub attenuation0: f32,
539    pub attenuation1: f32,
540    pub attenuation2: f32,
541    pub range: f32,
542    pub theta_deg: f32,
543    pub phi_deg: f32,
544    pub falloff: f32,
545}
546
547impl LightState {
548    pub fn directional(id: i32, dir: [f32; 3]) -> Self {
549        Self {
550            id,
551            kind: LightType::Directional,
552            diffuse: [1.0, 1.0, 1.0, 1.0],
553            ambient: [0.18, 0.18, 0.18, 1.0],
554            specular: [0.0, 0.0, 0.0, 1.0],
555            pos: [0.0, 0.0, 0.0],
556            dir,
557            attenuation0: 1.0,
558            attenuation1: 0.0,
559            attenuation2: 0.0,
560            range: 5000.0,
561            theta_deg: 20.0,
562            phi_deg: 40.0,
563            falloff: 1.0,
564        }
565    }
566}
567
568impl Default for LightState {
569    fn default() -> Self {
570        Self::directional(0, [0.0, 0.0, -1.0])
571    }
572}
573
574#[derive(Debug, Clone)]
575pub struct FogGlobalState {
576    pub enabled: bool,
577    pub name: String,
578    pub near: f32,
579    pub far: f32,
580    pub color: [f32; 4],
581    pub scroll_x: f32,
582    pub x_event: IntEvent,
583    pub texture_image_id: Option<ImageId>,
584}
585
586impl Default for FogGlobalState {
587    fn default() -> Self {
588        Self {
589            enabled: false,
590            name: String::new(),
591            near: 0.0,
592            far: 0.0,
593            color: [0.62, 0.62, 0.62, 1.0],
594            scroll_x: 0.0,
595            x_event: IntEvent::new(0),
596            texture_image_id: None,
597        }
598    }
599}
600
601impl FogGlobalState {
602    pub fn set_x(&mut self, x: i32) {
603        self.x_event.set_value(x);
604        self.x_event.frame();
605        self.scroll_x = self.x_event.get_total_value() as f32;
606    }
607
608    pub fn update_time(&mut self, past_game_time: i32, past_real_time: i32) {
609        self.x_event.update_time(past_game_time, past_real_time);
610    }
611
612    pub fn frame(&mut self) {
613        self.x_event.frame();
614        self.scroll_x = self.x_event.get_total_value() as f32;
615    }
616}
617
618#[derive(Debug, Clone)]
619pub struct GlobalState {
620    /// Generic int-list storage keyed by the global form ID.
621    pub int_lists: HashMap<u32, Vec<i64>>,
622    /// Generic string-list storage keyed by the global form ID.
623    pub str_lists: HashMap<u32, Vec<String>>,
624    /// Counter-list storage keyed by the global form ID.
625    pub counter_lists: HashMap<u32, Vec<Counter>>,
626    /// PCM-event lists keyed by the global form ID.
627    pub pcm_event_lists: HashMap<u32, Vec<PcmEventState>>,
628
629    /// Generic integer-event roots keyed by the form ID.
630    pub int_event_roots: HashMap<u32, IntEvent>,
631    /// Generic integer-event lists keyed by the form ID.
632    pub int_event_lists: HashMap<u32, Vec<IntEvent>>,
633
634    /// Generic int properties keyed by (form_id -> op_id).
635    pub int_props: HashMap<u32, HashMap<i32, i64>>,
636    /// Generic string properties keyed by (form_id -> op_id).
637    pub str_props: HashMap<u32, HashMap<i32, String>>,
638
639    /// Learned bit-width selectors for int lists (form_id/op -> bit width).
640    pub intlist_bit_widths: HashMap<(u32, i32), i32>,
641    /// First-seen ordering of bit selectors per int list form.
642    pub intlist_bit_order: HashMap<u32, Vec<i32>>,
643
644    /// CGTABLE global disable flag.
645    pub cg_table_off: bool,
646
647    /// DATABASE global disable flag.
648    pub database_off: bool,
649
650    /// G00BUF slots. Each slot stores an ImageId loaded from the `g00/` directory.
651    pub g00buf: Vec<Option<ImageId>>,
652    /// Original C_elm_g00_buf persists file names, not texture handles.
653    pub g00buf_names: Vec<Option<String>>,
654
655    /// RNG state for MATH.RAND (xorshift32). 0 means "uninitialized".
656    pub rng_state: u32,
657
658    /// Mask subsystem state keyed by the (guessed or mapped) form id.
659    pub mask_lists: HashMap<u32, MaskListState>,
660    /// EditBox subsystem state keyed by the (guessed or mapped) form id.
661    pub editbox_lists: HashMap<u32, EditBoxListState>,
662    /// Currently focused editbox (form_id, index).
663    pub focused_editbox: Option<(u32, usize)>,
664    /// Display-mode transition counter used by editbox frame visibility.
665    pub change_display_mode_proc_cnt: i32,
666
667    /// Global frame-action roots keyed by the owning form id.
668    pub frame_actions: HashMap<u32, ObjectFrameActionState>,
669    /// Global frame-action channel lists keyed by the owning form id.
670    pub frame_action_lists: HashMap<u32, Vec<ObjectFrameActionState>>,
671    /// Finish callbacks queued by FRAMEACTION.START/START_REAL/END.
672    pub pending_frame_action_finishes: Vec<PendingFrameActionFinish>,
673    /// Button decided actions queued from C_elm_object::button_event semantics.
674    pub pending_button_actions: Vec<PendingButtonAction>,
675
676    /// Stage UI subsystem state keyed by the stage form ID.
677    pub stage_forms: HashMap<u32, StageFormState>,
678    /// Currently focused stage group selection (form_id, stage_idx, group_idx).
679    pub focused_stage_group: Option<(u32, i64, usize)>,
680    /// Currently focused message-window selection (form_id, stage_idx, mwnd_idx).
681    pub focused_stage_mwnd: Option<(u32, i64, usize)>,
682    /// Current message-window handles used by GLOBAL.GET_MWND/SET_MWND.
683    /// Original engine initializes these to FRONT.MWND[default_*].
684    pub current_mwnd_no: Option<usize>,
685    pub current_mwnd_stage_idx: i64,
686    pub current_sel_mwnd_no: Option<usize>,
687    pub current_sel_mwnd_stage_idx: i64,
688    pub last_mwnd_no: Option<usize>,
689    pub last_mwnd_stage_idx: i64,
690
691    /// Original C_tnm_timer local fields saved by C_tnm_eng::save_local().
692    pub local_real_time: i64,
693    pub local_game_time: i64,
694    pub local_wipe_time: i64,
695
696    /// Original extend-enable local flag lists H/I/J.
697    pub local_flag_h: Vec<i64>,
698    pub local_flag_i: Vec<i64>,
699    pub local_flag_j: Vec<i64>,
700    /// GLOBAL.SELBTN button-selection runtime state.
701    pub selbtn: BtnSelectRuntimeState,
702    /// Last object target touched by stage/object dispatch. Compact object-only chains in scene bytecode
703    /// use this as the ambient current-object context when they omit the object index.
704    pub current_stage_object: Option<(i64, usize)>,
705    pub current_object_chain: Option<Vec<i32>>,
706
707    /// Screen subsystem state keyed by the screen form ID.
708    pub screen_forms: HashMap<u32, ScreenFormState>,
709
710    /// Message backlog (MSGBK) subsystem state keyed by the form ID.
711    pub msgbk_forms: HashMap<u32, MsgBackState>,
712
713    /// Script/global runtime state translated from the original the original implementation command handlers.
714    pub script: ScriptRuntimeState,
715
716    /// System helper runtime state.
717    pub system: SystemRuntimeState,
718
719    /// System-command runtime state.
720    pub syscom: SyscomRuntimeState,
721    /// Active GLOBAL.MOV direct movie player.
722    pub mov: GlobalMovieState,
723
724    /// Last full-screen capture made by GLOBAL.CAPTURE / CAPTURE_FROM_FILE.
725    pub capture_image: Option<RgbaImage>,
726    /// Capture buffer used by OBJECT.CREATE_CAPTURE and thumb fallback paths.
727    pub capture_for_object_image: Option<RgbaImage>,
728    /// Save thumbnail capture prepared before entering the save UI.
729    pub save_thumb_capture_image: Option<RgbaImage>,
730
731    /// Currently selected append directory used by original file resolution helpers.
732    pub append_dir: String,
733    /// Display name for the currently selected append directory.
734    pub append_name: String,
735
736    /// BGM table listened flags keyed by registered name.
737    pub bgm_table_listened: HashMap<String, bool>,
738    /// BGM table flags indexed by original BGM registration number.
739    pub bgm_table_flags: Vec<bool>,
740    /// Default flag applied to names not seen yet via BGMTABLE.SET_ALL_FLAG.
741    pub bgm_table_all_flag: bool,
742
743    /// Active wipe transition (WIPE / MASK_WIPE).
744    pub wipe: Option<WipeState>,
745
746    /// Global light manager keyed by original engine light id.
747    pub lights: HashMap<i32, LightState>,
748    /// Global fog state.
749    pub fog_global: FogGlobalState,
750
751    /// Monotonic frame counter used by render effects.
752    pub render_frame: u64,
753}
754
755impl Default for GlobalState {
756    fn default() -> Self {
757        Self {
758            int_lists: HashMap::new(),
759            str_lists: HashMap::new(),
760            counter_lists: HashMap::new(),
761            pcm_event_lists: HashMap::new(),
762            int_event_roots: HashMap::new(),
763            int_event_lists: HashMap::new(),
764            int_props: HashMap::new(),
765            str_props: HashMap::new(),
766            intlist_bit_widths: HashMap::new(),
767            intlist_bit_order: HashMap::new(),
768            cg_table_off: false,
769            database_off: false,
770            g00buf: Vec::new(),
771            g00buf_names: Vec::new(),
772            rng_state: 0,
773            mask_lists: HashMap::new(),
774            editbox_lists: HashMap::new(),
775            focused_editbox: None,
776            change_display_mode_proc_cnt: 0,
777
778            frame_actions: HashMap::new(),
779            frame_action_lists: HashMap::new(),
780            pending_frame_action_finishes: Vec::new(),
781            pending_button_actions: Vec::new(),
782            stage_forms: HashMap::new(),
783            focused_stage_group: None,
784            focused_stage_mwnd: None,
785            current_mwnd_no: Some(0),
786            current_mwnd_stage_idx: 1,
787            current_sel_mwnd_no: Some(1),
788            current_sel_mwnd_stage_idx: 1,
789            last_mwnd_no: Some(0),
790            last_mwnd_stage_idx: 1,
791            local_real_time: 0,
792            local_game_time: 0,
793            local_wipe_time: 0,
794            local_flag_h: Vec::new(),
795            local_flag_i: Vec::new(),
796            local_flag_j: Vec::new(),
797            selbtn: BtnSelectRuntimeState::default(),
798            current_stage_object: None,
799            current_object_chain: None,
800
801            screen_forms: HashMap::new(),
802            msgbk_forms: HashMap::new(),
803
804            script: ScriptRuntimeState::default(),
805            system: SystemRuntimeState::default(),
806            syscom: SyscomRuntimeState::default(),
807            mov: GlobalMovieState::default(),
808            capture_image: None,
809            capture_for_object_image: None,
810            save_thumb_capture_image: None,
811            append_dir: String::new(),
812            append_name: String::new(),
813
814            bgm_table_listened: HashMap::new(),
815            bgm_table_flags: Vec::new(),
816            bgm_table_all_flag: false,
817
818            wipe: None,
819            lights: HashMap::new(),
820            fog_global: FogGlobalState::default(),
821            render_frame: 0,
822        }
823    }
824}
825
826#[derive(Debug, Clone, Default)]
827pub struct BtnSelectChoiceState {
828    pub text: String,
829    pub item_type: i64,
830    pub color: i64,
831    pub pos: (i64, i64),
832    pub size: (i64, i64),
833}
834
835#[derive(Debug, Clone, Default)]
836pub struct BtnSelectRuntimeState {
837    pub template_no: i64,
838    pub choices: Vec<BtnSelectChoiceState>,
839    pub cursor: usize,
840    pub cancel_enable: bool,
841    pub capture_flag: bool,
842    pub started: bool,
843    pub result: i64,
844    pub sync_type: i64,
845    pub read_flag_scene_no: i64,
846    pub read_flag_flag_no: i64,
847    pub sel_start_call_scn: String,
848    pub sel_start_call_z_no: i64,
849}
850
851#[derive(Debug, Clone, Default)]
852pub struct PendingFrameActionFinish {
853    pub frame_action_chain: Vec<i32>,
854    pub object_chain: Option<Vec<i32>>,
855    pub scn_name: String,
856    pub cmd_name: String,
857    pub end_time: i64,
858    pub args: Vec<crate::runtime::Value>,
859}
860
861#[derive(Debug, Clone)]
862pub enum PendingButtonActionKind {
863    UserCall {
864        scn_name: String,
865        cmd_name: String,
866        z_no: i64,
867    },
868    Syscom {
869        sys_type: i64,
870        sys_type_opt: i64,
871        mode: i64,
872    },
873}
874
875impl Default for PendingButtonActionKind {
876    fn default() -> Self {
877        Self::UserCall {
878            scn_name: String::new(),
879            cmd_name: String::new(),
880            z_no: -1,
881        }
882    }
883}
884
885#[derive(Debug, Clone, Default)]
886pub struct PendingButtonAction {
887    pub kind: PendingButtonActionKind,
888}
889
890/// Runtime state for the GLOBAL.MOV player.
891///
892/// Original Siglus MOV is not a stage OBJECT; it is a full-screen/direct movie
893/// player. The WGPU port renders it through a dedicated LayerManager sprite so
894/// MOV.PLAY/WAIT/STOP still produce visible frames when stage objects are hidden.
895#[derive(Debug, Clone)]
896pub struct GlobalMovieState {
897    pub file_name: Option<String>,
898    pub playing: bool,
899    pub key_skip_flag: bool,
900    pub timer_ms: u64,
901    pub total_ms: Option<u64>,
902    pub x: i32,
903    pub y: i32,
904    pub width: u32,
905    pub height: u32,
906    pub layer_id: Option<LayerId>,
907    pub sprite_id: Option<SpriteId>,
908    pub image_id: Option<ImageId>,
909    pub last_frame_idx: Option<usize>,
910    pub audio_id: Option<u64>,
911    pub audio_start_attempted: bool,
912}
913
914impl Default for GlobalMovieState {
915    fn default() -> Self {
916        Self {
917            file_name: None,
918            playing: false,
919            key_skip_flag: false,
920            timer_ms: 0,
921            total_ms: None,
922            x: 0,
923            y: 0,
924            width: 0,
925            height: 0,
926            layer_id: None,
927            sprite_id: None,
928            image_id: None,
929            last_frame_idx: None,
930            audio_id: None,
931            audio_start_attempted: false,
932        }
933    }
934}
935
936impl GlobalMovieState {
937    pub fn start(
938        &mut self,
939        file_name: String,
940        x: i32,
941        y: i32,
942        width: u32,
943        height: u32,
944        total_ms: Option<u64>,
945        key_skip_flag: bool,
946    ) {
947        self.file_name = Some(file_name);
948        self.playing = true;
949        self.key_skip_flag = key_skip_flag;
950        self.timer_ms = 0;
951        self.total_ms = total_ms;
952        self.x = x;
953        self.y = y;
954        self.width = width;
955        self.height = height;
956        self.last_frame_idx = None;
957        self.audio_id = None;
958        self.audio_start_attempted = false;
959    }
960
961    pub fn stop(&mut self) {
962        self.file_name = None;
963        self.playing = false;
964        self.key_skip_flag = false;
965        self.timer_ms = 0;
966        self.total_ms = None;
967        self.last_frame_idx = None;
968        self.audio_id = None;
969        self.audio_start_attempted = false;
970    }
971
972    pub fn tick(&mut self, past_real_time: i32) {
973        if !self.playing {
974            return;
975        }
976        let add = past_real_time.max(0) as u64;
977        if add == 0 {
978            return;
979        }
980        self.timer_ms = self.timer_ms.saturating_add(add);
981        if let Some(total) = self.total_ms {
982            if total > 0 && self.timer_ms >= total {
983                self.timer_ms = total;
984                self.playing = false;
985            }
986        }
987    }
988}
989
990#[derive(Debug, Clone, Copy)]
991pub struct Counter {
992    cur_time: i64,
993    is_running: bool,
994    real_flag: bool,
995    frame_mode: bool,
996    frame_loop_flag: bool,
997    frame_start_value: i64,
998    frame_end_value: i64,
999    frame_time: i64,
1000}
1001
1002impl Default for Counter {
1003    fn default() -> Self {
1004        Self {
1005            cur_time: 0,
1006            is_running: false,
1007            real_flag: false,
1008            frame_mode: false,
1009            frame_loop_flag: false,
1010            frame_start_value: 0,
1011            frame_end_value: 0,
1012            frame_time: 0,
1013        }
1014    }
1015}
1016
1017impl Counter {
1018    fn limit(min: i64, value: i64, max: i64) -> i64 {
1019        if value < min {
1020            min
1021        } else if value > max {
1022            max
1023        } else {
1024            value
1025        }
1026    }
1027
1028    pub fn reinit(&mut self) {
1029        *self = Self::default();
1030    }
1031
1032    pub fn reset(&mut self) {
1033        self.is_running = false;
1034        self.real_flag = false;
1035        self.frame_mode = false;
1036        self.cur_time = 0;
1037    }
1038
1039    pub fn set_count(&mut self, value: i64) {
1040        if self.frame_mode {
1041            if self.frame_end_value == self.frame_start_value {
1042                self.cur_time = 0;
1043                return;
1044            }
1045
1046            let denom = self.frame_end_value - self.frame_start_value;
1047            let frame_time = self.frame_time;
1048            let mut cur_time = (value - self.frame_start_value) * frame_time / denom;
1049            if self.frame_loop_flag {
1050                cur_time = Self::limit(0, cur_time, frame_time - 1);
1051            } else {
1052                cur_time = Self::limit(0, cur_time, frame_time);
1053            }
1054            self.cur_time = cur_time;
1055        } else {
1056            self.cur_time = value;
1057        }
1058    }
1059
1060    pub fn start(&mut self) {
1061        self.is_running = true;
1062        self.real_flag = false;
1063        self.frame_mode = false;
1064        self.cur_time = 0;
1065    }
1066
1067    pub fn start_real(&mut self) {
1068        self.is_running = true;
1069        self.real_flag = true;
1070        self.frame_mode = false;
1071        self.cur_time = 0;
1072    }
1073
1074    pub fn start_frame(&mut self, from: i64, to: i64, frame_time: i64) {
1075        self.is_running = true;
1076        self.real_flag = false;
1077        self.frame_mode = true;
1078        self.frame_loop_flag = false;
1079        self.frame_start_value = from;
1080        self.frame_end_value = to;
1081        self.frame_time = frame_time;
1082        self.cur_time = 0;
1083    }
1084
1085    pub fn start_frame_real(&mut self, from: i64, to: i64, frame_time: i64) {
1086        self.is_running = true;
1087        self.real_flag = true;
1088        self.frame_mode = true;
1089        self.frame_loop_flag = false;
1090        self.frame_start_value = from;
1091        self.frame_end_value = to;
1092        self.frame_time = frame_time;
1093        self.cur_time = 0;
1094    }
1095
1096    pub fn start_frame_loop(&mut self, from: i64, to: i64, frame_time: i64) {
1097        self.is_running = true;
1098        self.real_flag = false;
1099        self.frame_mode = true;
1100        self.frame_loop_flag = true;
1101        self.frame_start_value = from;
1102        self.frame_end_value = to;
1103        self.frame_time = frame_time;
1104        self.cur_time = 0;
1105    }
1106
1107    pub fn start_frame_loop_real(&mut self, from: i64, to: i64, frame_time: i64) {
1108        self.is_running = true;
1109        self.real_flag = true;
1110        self.frame_mode = true;
1111        self.frame_loop_flag = true;
1112        self.frame_start_value = from;
1113        self.frame_end_value = to;
1114        self.frame_time = frame_time;
1115        self.cur_time = 0;
1116    }
1117
1118    pub fn stop(&mut self) {
1119        self.is_running = false;
1120    }
1121
1122    pub fn resume(&mut self) {
1123        self.is_running = true;
1124    }
1125
1126    pub fn update_time(&mut self, past_game_time: i32, past_real_time: i32) {
1127        if self.is_running {
1128            let add = if self.real_flag {
1129                past_real_time
1130            } else {
1131                past_game_time
1132            };
1133            self.cur_time = self.cur_time.saturating_add(add as i64);
1134        }
1135
1136        if self.frame_mode && !self.frame_loop_flag && self.cur_time >= self.frame_time {
1137            self.is_running = false;
1138        }
1139    }
1140
1141    pub fn get_count(&self) -> i64 {
1142        if self.frame_mode {
1143            if self.frame_time <= 0 {
1144                return self.frame_end_value;
1145            }
1146            if self.frame_start_value == self.frame_end_value {
1147                return self.frame_end_value;
1148            }
1149
1150            let span = self.frame_end_value - self.frame_start_value;
1151            let mut value = span * self.cur_time / self.frame_time;
1152            if self.frame_loop_flag {
1153                value %= span;
1154                value += self.frame_start_value;
1155            } else {
1156                value += self.frame_start_value;
1157                if self.frame_start_value > self.frame_end_value {
1158                    value = Self::limit(self.frame_end_value, value, self.frame_start_value);
1159                } else {
1160                    value = Self::limit(self.frame_start_value, value, self.frame_end_value);
1161                }
1162            }
1163            value
1164        } else {
1165            self.cur_time
1166        }
1167    }
1168
1169    pub fn get_count_with_frame(&self, _current_frame: i64) -> i64 {
1170        self.get_count()
1171    }
1172
1173    pub fn is_running(&self) -> bool {
1174        self.is_running
1175    }
1176
1177    pub(crate) fn save_parts(&self) -> (bool, bool, bool, bool, i64, i64, i64, i64) {
1178        (
1179            self.is_running,
1180            self.real_flag,
1181            self.frame_mode,
1182            self.frame_loop_flag,
1183            self.frame_start_value,
1184            self.frame_end_value,
1185            self.frame_time,
1186            self.cur_time,
1187        )
1188    }
1189
1190    pub(crate) fn from_save_parts(
1191        is_running: bool,
1192        real_flag: bool,
1193        frame_mode: bool,
1194        frame_loop_flag: bool,
1195        frame_start_value: i64,
1196        frame_end_value: i64,
1197        frame_time: i64,
1198        cur_time: i64,
1199    ) -> Self {
1200        Self {
1201            cur_time,
1202            is_running,
1203            real_flag,
1204            frame_mode,
1205            frame_loop_flag,
1206            frame_start_value,
1207            frame_end_value,
1208            frame_time,
1209        }
1210    }
1211}
1212
1213#[derive(Debug, Clone, Default)]
1214pub struct PcmEventLine {
1215    pub file_name: String,
1216    pub probability: i32,
1217    pub min_time: i32,
1218    pub max_time: i32,
1219}
1220
1221#[derive(Debug, Clone, Default)]
1222pub struct PcmEventState {
1223    pub active: bool,
1224    pub looped: bool,
1225    pub random: bool,
1226    pub volume_type: i32,
1227    pub chara_no: i32,
1228    pub bgm_fade_target_flag: bool,
1229    pub bgm_fade2_target_flag: bool,
1230    pub bgm_fade2_source_flag: bool,
1231    pub real_flag: bool,
1232    pub time_type: bool,
1233    pub lines: Vec<PcmEventLine>,
1234}
1235
1236impl PcmEventState {
1237    pub fn reinit(&mut self) {
1238        *self = Self::default();
1239    }
1240}
1241
1242/// Mask state.
1243#[derive(Debug, Clone)]
1244pub struct MaskState {
1245    pub name: Option<String>,
1246    pub x_event: IntEvent,
1247    pub y_event: IntEvent,
1248    pub extra_int: HashMap<i32, i32>,
1249    pub script_events: HashMap<i32, IntEvent>,
1250}
1251
1252impl MaskState {
1253    pub fn new() -> Self {
1254        Self {
1255            name: None,
1256            x_event: IntEvent::new(0),
1257            y_event: IntEvent::new(0),
1258            extra_int: HashMap::new(),
1259            script_events: HashMap::new(),
1260        }
1261    }
1262
1263    pub fn reinit(&mut self) {
1264        self.name = None;
1265        self.x_event.reinit();
1266        self.y_event.reinit();
1267        self.extra_int.clear();
1268        self.script_events.clear();
1269    }
1270}
1271
1272#[derive(Debug, Clone)]
1273pub struct MaskListState {
1274    pub masks: Vec<MaskState>,
1275}
1276
1277#[derive(Debug, Clone)]
1278pub struct MaskedSpriteCache {
1279    pub base_image_id: ImageId,
1280    pub base_version: u64,
1281    pub mask_image_id: ImageId,
1282    pub mask_version: u64,
1283    pub mask_x: i32,
1284    pub mask_y: i32,
1285    pub masked_image_id: ImageId,
1286}
1287
1288pub const EDITBOX_ACTION_NOT_DECIDED: i32 = 0;
1289pub const EDITBOX_ACTION_DECIDED: i32 = 1;
1290pub const EDITBOX_ACTION_CANCELED: i32 = -1;
1291
1292#[derive(Debug, Clone)]
1293pub struct EditBoxState {
1294    pub created: bool,
1295    pub visible: bool,
1296    pub text: String,
1297    pub cursor_pos: usize,
1298    pub action_flag: i32,
1299    pub moji_size: i32,
1300    pub rect_x: i32,
1301    pub rect_y: i32,
1302    pub rect_w: i32,
1303    pub rect_h: i32,
1304    pub design_screen_w: i32,
1305    pub design_screen_h: i32,
1306    pub window_x: i32,
1307    pub window_y: i32,
1308    pub window_w: i32,
1309    pub window_h: i32,
1310    pub window_moji_size: i32,
1311}
1312
1313impl Default for EditBoxState {
1314    fn default() -> Self {
1315        Self {
1316            created: false,
1317            visible: false,
1318            text: String::new(),
1319            cursor_pos: 0,
1320            action_flag: EDITBOX_ACTION_NOT_DECIDED,
1321            moji_size: 0,
1322            rect_x: 0,
1323            rect_y: 0,
1324            rect_w: 0,
1325            rect_h: 0,
1326            design_screen_w: 0,
1327            design_screen_h: 0,
1328            window_x: 0,
1329            window_y: 0,
1330            window_w: 0,
1331            window_h: 0,
1332            window_moji_size: 0,
1333        }
1334    }
1335}
1336
1337impl EditBoxState {
1338    pub fn create_like(
1339        &mut self,
1340        x: i32,
1341        y: i32,
1342        w: i32,
1343        h: i32,
1344        moji_size: i32,
1345        design_screen_w: i32,
1346        design_screen_h: i32,
1347    ) {
1348        self.created = true;
1349        self.visible = false;
1350        self.text.clear();
1351        self.cursor_pos = 0;
1352        self.action_flag = EDITBOX_ACTION_NOT_DECIDED;
1353        self.rect_x = x;
1354        self.rect_y = y;
1355        self.rect_w = w;
1356        self.rect_h = h;
1357        self.moji_size = moji_size;
1358        self.design_screen_w = design_screen_w.max(1);
1359        self.design_screen_h = design_screen_h.max(1);
1360        self.window_x = 0;
1361        self.window_y = 0;
1362        self.window_w = 0;
1363        self.window_h = 0;
1364        self.window_moji_size = 0;
1365    }
1366
1367    pub fn destroy_like(&mut self) {
1368        self.created = false;
1369        self.visible = false;
1370        self.text.clear();
1371        self.cursor_pos = 0;
1372        self.action_flag = EDITBOX_ACTION_NOT_DECIDED;
1373        self.rect_x = 0;
1374        self.rect_y = 0;
1375        self.rect_w = 0;
1376        self.rect_h = 0;
1377        self.moji_size = 0;
1378        self.design_screen_w = 0;
1379        self.design_screen_h = 0;
1380        self.window_x = 0;
1381        self.window_y = 0;
1382        self.window_w = 0;
1383        self.window_h = 0;
1384        self.window_moji_size = 0;
1385    }
1386
1387    pub fn set_text_like(&mut self, text: String) {
1388        self.text = text;
1389        self.cursor_pos = self.text.len();
1390    }
1391
1392    pub fn insert_text_at_cursor(&mut self, text: &str) {
1393        if text.is_empty() {
1394            return;
1395        }
1396        let pos = self.cursor_pos.min(self.text.len());
1397        self.text.insert_str(pos, text);
1398        self.cursor_pos = pos.saturating_add(text.len()).min(self.text.len());
1399    }
1400
1401    pub fn backspace_like(&mut self) {
1402        if self.cursor_pos == 0 || self.text.is_empty() {
1403            return;
1404        }
1405        let mut prev = 0usize;
1406        for (i, _) in self.text.char_indices() {
1407            if i >= self.cursor_pos {
1408                break;
1409            }
1410            prev = i;
1411        }
1412        self.text.drain(prev..self.cursor_pos.min(self.text.len()));
1413        self.cursor_pos = prev;
1414    }
1415
1416    pub fn update_rect(&mut self, screen_w: i32, screen_h: i32) {
1417        let base_w = self.design_screen_w.max(1);
1418        let base_h = self.design_screen_h.max(1);
1419        let sw = screen_w.max(1);
1420        let sh = screen_h.max(1);
1421        self.window_x = self.rect_x.saturating_mul(sw) / base_w;
1422        self.window_y = self.rect_y.saturating_mul(sh) / base_h;
1423        self.window_w = self.rect_w.saturating_mul(sw) / base_w;
1424        self.window_h = self.rect_h.saturating_mul(sh) / base_h;
1425        self.window_moji_size = self.moji_size.saturating_mul(sh) / base_h;
1426    }
1427
1428    pub fn frame(&mut self, display_mode_change_proc_cnt: i32) {
1429        self.visible = self.created && display_mode_change_proc_cnt == 0;
1430    }
1431
1432    pub fn clear_input(&mut self) {
1433        self.action_flag = EDITBOX_ACTION_NOT_DECIDED;
1434    }
1435
1436    pub fn is_decided(&self) -> bool {
1437        self.action_flag == EDITBOX_ACTION_DECIDED
1438    }
1439
1440    pub fn is_canceled(&self) -> bool {
1441        self.action_flag == EDITBOX_ACTION_CANCELED
1442    }
1443
1444    pub fn contains_point(&self, x: i32, y: i32) -> bool {
1445        self.created
1446            && self.visible
1447            && self.window_w > 0
1448            && self.window_h > 0
1449            && x >= self.window_x
1450            && y >= self.window_y
1451            && x < self.window_x.saturating_add(self.window_w)
1452            && y < self.window_y.saturating_add(self.window_h)
1453    }
1454}
1455
1456#[derive(Debug, Clone)]
1457pub struct EditBoxListState {
1458    pub boxes: Vec<EditBoxState>,
1459}
1460
1461impl EditBoxListState {
1462    pub fn new(cnt: usize) -> Self {
1463        Self {
1464            boxes: vec![EditBoxState::default(); cnt],
1465        }
1466    }
1467
1468    pub fn ensure_size(&mut self, cnt: usize) {
1469        if self.boxes.len() < cnt {
1470            self.boxes
1471                .extend((0..(cnt - self.boxes.len())).map(|_| EditBoxState::default()));
1472        } else if self.boxes.len() > cnt {
1473            self.boxes.truncate(cnt);
1474        }
1475    }
1476}
1477
1478// -----------------------------------------------------------------------------
1479// Stage/MWND/Group state
1480// -----------------------------------------------------------------------------
1481
1482#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1483pub enum WorldListOpKind {
1484    GetSize,
1485    Create,
1486    Destroy,
1487    Unknown,
1488}
1489
1490#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1491pub enum WorldOpKind {
1492    Init,
1493    GetNo,
1494    CameraEyeX,
1495    CameraEyeY,
1496    CameraEyeZ,
1497    CameraPintX,
1498    CameraPintY,
1499    CameraPintZ,
1500    CameraUpX,
1501    CameraUpY,
1502    CameraUpZ,
1503    CameraEyeXEve,
1504    CameraEyeYEve,
1505    CameraEyeZEve,
1506    CameraPintXEve,
1507    CameraPintYEve,
1508    CameraPintZEve,
1509    CameraUpXEve,
1510    CameraUpYEve,
1511    CameraUpZEve,
1512    CameraViewAngle,
1513    SetCameraEye,
1514    CalcCameraEye,
1515    SetCameraPint,
1516    CalcCameraPint,
1517    SetCameraUp,
1518    Mono,
1519    SetCameraEveXzRotate,
1520    Order,
1521    Layer,
1522    WipeCopy,
1523    WipeErase,
1524    Unknown,
1525}
1526
1527#[derive(Debug, Clone, Copy)]
1528pub struct WorldRotateEvent {
1529    pub loop_type: i32,
1530    pub cur_time: i32,
1531    pub end_time: i32,
1532    pub delay_time: i32,
1533    pub speed_type: i32,
1534    pub start_x: i32,
1535    pub start_z: i32,
1536    pub end_x: i32,
1537    pub end_z: i32,
1538}
1539
1540impl WorldRotateEvent {
1541    pub fn new() -> Self {
1542        Self {
1543            loop_type: -1,
1544            cur_time: 0,
1545            end_time: 0,
1546            delay_time: 0,
1547            speed_type: 0,
1548            start_x: 0,
1549            start_z: 0,
1550            end_x: 0,
1551            end_z: 0,
1552        }
1553    }
1554
1555    pub fn is_active(&self) -> bool {
1556        self.loop_type != -1
1557    }
1558}
1559
1560#[derive(Debug, Clone)]
1561pub struct WorldState {
1562    pub world_no: i32,
1563    pub mode: i32,
1564    pub camera_eye_x: IntEvent,
1565    pub camera_eye_y: IntEvent,
1566    pub camera_eye_z: IntEvent,
1567    pub camera_pint_x: IntEvent,
1568    pub camera_pint_y: IntEvent,
1569    pub camera_pint_z: IntEvent,
1570    pub camera_up_x: IntEvent,
1571    pub camera_up_y: IntEvent,
1572    pub camera_up_z: IntEvent,
1573    pub camera_view_angle: i32,
1574    pub mono: i32,
1575    pub order: i32,
1576    pub layer: i32,
1577    pub wipe_copy: i32,
1578    pub wipe_erase: i32,
1579    pub camera_eye_xz_eve: WorldRotateEvent,
1580    pub script_events: HashMap<i32, IntEvent>,
1581    pub extra_int: HashMap<i32, i64>,
1582    pub extra_str: HashMap<i32, String>,
1583}
1584
1585impl WorldState {
1586    pub fn new(world_no: i32) -> Self {
1587        let mut out = Self {
1588            world_no,
1589            mode: 1,
1590            camera_eye_x: IntEvent::new(0),
1591            camera_eye_y: IntEvent::new(0),
1592            camera_eye_z: IntEvent::new(-1000),
1593            camera_pint_x: IntEvent::new(0),
1594            camera_pint_y: IntEvent::new(0),
1595            camera_pint_z: IntEvent::new(0),
1596            camera_up_x: IntEvent::new(0),
1597            camera_up_y: IntEvent::new(1),
1598            camera_up_z: IntEvent::new(0),
1599            camera_view_angle: 450,
1600            mono: 0,
1601            order: 0,
1602            layer: 0,
1603            wipe_copy: 0,
1604            wipe_erase: 0,
1605            camera_eye_xz_eve: WorldRotateEvent::new(),
1606            script_events: HashMap::new(),
1607            extra_int: HashMap::new(),
1608            extra_str: HashMap::new(),
1609        };
1610        out.reinit();
1611        out
1612    }
1613
1614    pub fn reinit(&mut self) {
1615        self.mode = 1;
1616        self.camera_eye_x = IntEvent::new(0);
1617        self.camera_eye_y = IntEvent::new(0);
1618        self.camera_eye_z = IntEvent::new(-1000);
1619        self.camera_pint_x = IntEvent::new(0);
1620        self.camera_pint_y = IntEvent::new(0);
1621        self.camera_pint_z = IntEvent::new(0);
1622        self.camera_up_x = IntEvent::new(0);
1623        self.camera_up_y = IntEvent::new(1);
1624        self.camera_up_z = IntEvent::new(0);
1625        self.camera_view_angle = 450;
1626        self.mono = 0;
1627        self.order = 0;
1628        self.layer = 0;
1629        self.wipe_copy = 0;
1630        self.wipe_erase = 0;
1631        self.camera_eye_xz_eve = WorldRotateEvent::new();
1632    }
1633
1634    pub fn update_time(&mut self, past_game_time: i32, past_real_time: i32) {
1635        self.camera_eye_x
1636            .update_time(past_game_time, past_real_time);
1637        self.camera_eye_y
1638            .update_time(past_game_time, past_real_time);
1639        self.camera_eye_z
1640            .update_time(past_game_time, past_real_time);
1641        self.camera_pint_x
1642            .update_time(past_game_time, past_real_time);
1643        self.camera_pint_y
1644            .update_time(past_game_time, past_real_time);
1645        self.camera_pint_z
1646            .update_time(past_game_time, past_real_time);
1647        self.camera_up_x.update_time(past_game_time, past_real_time);
1648        self.camera_up_y.update_time(past_game_time, past_real_time);
1649        self.camera_up_z.update_time(past_game_time, past_real_time);
1650        if self.camera_eye_xz_eve.is_active() {
1651            self.camera_eye_xz_eve.cur_time = self
1652                .camera_eye_xz_eve
1653                .cur_time
1654                .saturating_add(past_game_time);
1655        }
1656    }
1657
1658    pub fn frame(&mut self) {
1659        self.camera_eye_x.frame();
1660        self.camera_eye_y.frame();
1661        self.camera_eye_z.frame();
1662        self.camera_pint_x.frame();
1663        self.camera_pint_y.frame();
1664        self.camera_pint_z.frame();
1665        self.camera_up_x.frame();
1666        self.camera_up_y.frame();
1667        self.camera_up_z.frame();
1668
1669        if self.camera_eye_xz_eve.is_active() {
1670            self.frame_xz_rotate();
1671        }
1672    }
1673
1674    fn frame_xz_rotate(&mut self) {
1675        let mut cur_time = self.camera_eye_xz_eve.cur_time - self.camera_eye_xz_eve.delay_time;
1676        let end_time = self.camera_eye_xz_eve.end_time;
1677
1678        if self.camera_eye_xz_eve.loop_type == 0 && cur_time - end_time >= 0 {
1679            self.camera_eye_xz_eve.loop_type = -1;
1680            return;
1681        }
1682
1683        if cur_time <= 0 {
1684            self.camera_eye_x.cur_value = self.camera_eye_x.start_value;
1685            self.camera_eye_z.cur_value = self.camera_eye_z.start_value;
1686            return;
1687        }
1688
1689        if end_time <= 0 {
1690            return;
1691        }
1692
1693        if self.camera_eye_xz_eve.loop_type == 1 {
1694            cur_time %= end_time;
1695        }
1696        if self.camera_eye_xz_eve.loop_type == 2 {
1697            cur_time %= end_time * 2;
1698            if cur_time - end_time > 0 {
1699                cur_time = end_time - (cur_time - end_time);
1700            }
1701        }
1702
1703        match self.camera_eye_xz_eve.speed_type {
1704            1 => {
1705                cur_time = (cur_time as f64 * cur_time as f64 / end_time as f64) as i32;
1706            }
1707            2 => {
1708                let ct = (cur_time - end_time) as f64;
1709                let et = end_time as f64;
1710                cur_time = (-ct * ct / et + et) as i32;
1711            }
1712            _ => {}
1713        }
1714
1715        let px = self.camera_pint_x.get_total_value() as f64;
1716        let pz = self.camera_pint_z.get_total_value() as f64;
1717        let sx = self.camera_eye_x.start_value as f64;
1718        let sz = self.camera_eye_z.start_value as f64;
1719        let ex = self.camera_eye_x.end_value as f64;
1720        let ez = self.camera_eye_z.end_value as f64;
1721
1722        let sdx = sx - px;
1723        let sdz = sz - pz;
1724        let edx = ex - px;
1725        let edz = ez - pz;
1726
1727        let s_len = (sdx * sdx + sdz * sdz).sqrt();
1728        let e_len = (edx * edx + edz * edz).sqrt();
1729        let t_len = linear(cur_time, s_len, end_time, e_len);
1730
1731        let mut s_theta = sdz.atan2(sdx);
1732        let mut e_theta = edz.atan2(edx);
1733        if (s_theta - e_theta).abs() > std::f64::consts::PI {
1734            if e_theta < 0.0 {
1735                e_theta += std::f64::consts::PI * 2.0;
1736            } else {
1737                e_theta -= std::f64::consts::PI * 2.0;
1738            }
1739        }
1740        let t_theta = linear(cur_time, s_theta, end_time, e_theta);
1741
1742        let tmp_x = t_len * t_theta.cos() + px;
1743        let tmp_z = t_len * t_theta.sin() + pz;
1744
1745        self.camera_eye_x.cur_value = tmp_x as i32;
1746        self.camera_eye_z.cur_value = tmp_z as i32;
1747    }
1748}
1749
1750fn linear(cur: i32, start_value: f64, end_time: i32, end_value: f64) -> f64 {
1751    if end_time <= 0 {
1752        return end_value;
1753    }
1754    let t = cur as f64 / end_time as f64;
1755    start_value + (end_value - start_value) * t
1756}
1757
1758#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1759pub enum ObjectListOpKind {
1760    GetSize,
1761    Resize,
1762    Unknown,
1763}
1764
1765#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1766pub enum ObjectOpKind {
1767    Init,
1768    Free,
1769    InitParam,
1770    CreatePct,
1771    CreateRect,
1772    CreateString,
1773    /// CREATE_COPY_FROM
1774    CreateCopyFrom,
1775    /// SET_POS (2 or 3 ints)
1776    SetPos,
1777    /// SET_CENTER (2 or 3 ints)
1778    SetCenter,
1779    /// SET_SCALE (2 or 3 ints)
1780    SetScale,
1781    /// SET_ROTATE (2 or 3 ints)
1782    SetRotate,
1783    /// SET_CLIP (4 ints)
1784    SetClip,
1785    /// SET_SRC_CLIP (4 ints)
1786    SetSrcClip,
1787    /// CLEAR_BUTTON
1788    ClearButton,
1789    /// SET_BUTTON (1..4 ints, al_id=0..2)
1790    SetButton,
1791    /// SET_BUTTON_GROUP (int or element)
1792    SetButtonGroup,
1793    /// Int-list sub-element (X_REP/Y_REP/Z_REP/TR_REP/F, etc.).
1794    RepIntList,
1795    Unknown,
1796}
1797
1798#[derive(Debug, Clone, PartialEq, Eq)]
1799pub enum ObjectBackend {
1800    None,
1801    /// Uses the engine's GfxRuntime object pipeline.
1802    Gfx,
1803    /// Rectangle backed by a standalone LayerManager sprite.
1804    Rect {
1805        layer_id: LayerId,
1806        sprite_id: SpriteId,
1807        width: u32,
1808        height: u32,
1809    },
1810    /// STRING object backend: a single sprite with rendered text.
1811    String {
1812        layer_id: LayerId,
1813        sprite_id: SpriteId,
1814        image_id: Option<ImageId>,
1815        width: u32,
1816        height: u32,
1817    },
1818    /// NUMBER object backend: a fixed sprite list (16) with per-digit sprites.
1819    Number {
1820        layer_id: LayerId,
1821        sprite_ids: Vec<SpriteId>,
1822    },
1823    /// WEATHER object backend: sprite list owned by the weather object runtime.
1824    Weather {
1825        layer_id: LayerId,
1826        sprite_ids: Vec<SpriteId>,
1827    },
1828    /// MOVIE object backend: a single sprite updated with video frames.
1829    Movie {
1830        layer_id: LayerId,
1831        sprite_id: SpriteId,
1832        image_id: Option<ImageId>,
1833        width: u32,
1834        height: u32,
1835    },
1836}
1837
1838impl Default for ObjectBackend {
1839    fn default() -> Self {
1840        Self::None
1841    }
1842}
1843
1844pub const OBJECT_NESTED_SLOT_KEY: i32 = i32::MIN + 1;
1845
1846#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1847pub enum ObjectEventTarget {
1848    X,
1849    Y,
1850    XRep,
1851    YRep,
1852    ZRep,
1853    Alpha,
1854    Patno,
1855    Order,
1856    Layer,
1857    Z,
1858    CenterX,
1859    CenterY,
1860    CenterZ,
1861    CenterRepX,
1862    CenterRepY,
1863    CenterRepZ,
1864    ScaleX,
1865    ScaleY,
1866    ScaleZ,
1867    RotateX,
1868    RotateY,
1869    RotateZ,
1870    TrRep,
1871    ClipLeft,
1872    ClipTop,
1873    ClipRight,
1874    ClipBottom,
1875    SrcClipLeft,
1876    SrcClipTop,
1877    SrcClipRight,
1878    SrcClipBottom,
1879    Tr,
1880    Mono,
1881    Reverse,
1882    Bright,
1883    Dark,
1884    ColorRate,
1885    ColorAddR,
1886    ColorAddG,
1887    ColorAddB,
1888    ColorR,
1889    ColorG,
1890    ColorB,
1891    Unknown,
1892}
1893
1894#[derive(Debug, Clone)]
1895pub struct ObjectButtonState {
1896    pub enabled: bool,
1897    pub button_no: i64,
1898    pub group_no: i64,
1899    /// Additional cut offset applied after OBJECT.PATNO for button rendering.
1900    ///
1901    /// Original C_elm_object::frame()/create_trp() submits
1902    /// `obp.pat_no + button.cut_no`, and button action templates add their
1903    /// `rep_pat_no` on top of that submitted cut.
1904    pub cut_no: i64,
1905    /// Optional override derived from SET_BUTTON_GROUP(element).
1906    pub group_idx_override: Option<usize>,
1907    pub action_no: i64,
1908    pub se_no: i64,
1909    pub sys_type: i64,
1910    pub sys_type_opt: i64,
1911    pub mode: i64,
1912    pub push_keep: bool,
1913    pub alpha_test: bool,
1914    /// Button state constants: 0=normal, 1=hit, 2=push, 3=select, 4=disable.
1915    pub state: i64,
1916    pub hit: bool,
1917    pub pushed: bool,
1918
1919    // Decided action (set_button_decided_action)
1920    pub decided_action_scn_name: String,
1921    pub decided_action_cmd_name: String,
1922    pub decided_action_z_no: i64,
1923    /// Previous-frame hit flag used to model *_this_frame button transitions.
1924    pub last_hit: bool,
1925    /// Previous-frame pushed flag used to model *_this_frame button transitions.
1926    pub last_pushed: bool,
1927}
1928
1929impl Default for ObjectButtonState {
1930    fn default() -> Self {
1931        Self {
1932            enabled: false,
1933            button_no: 0,
1934            group_no: -1,
1935            cut_no: 0,
1936            group_idx_override: None,
1937            action_no: -1,
1938            se_no: -1,
1939            sys_type: 0,
1940            sys_type_opt: 0,
1941            mode: 0,
1942            push_keep: false,
1943            alpha_test: false,
1944            state: 0,
1945            hit: false,
1946            pushed: false,
1947            decided_action_scn_name: String::new(),
1948            decided_action_cmd_name: String::new(),
1949            decided_action_z_no: -1,
1950            last_hit: false,
1951            last_pushed: false,
1952        }
1953    }
1954}
1955
1956impl ObjectButtonState {
1957    pub fn clear(&mut self) {
1958        *self = Self::default();
1959    }
1960
1961    pub fn group_idx(&self) -> Option<usize> {
1962        if !self.enabled {
1963            return None;
1964        }
1965        if let Some(i) = self.group_idx_override {
1966            return Some(i);
1967        }
1968        if self.group_no < 0 {
1969            return None;
1970        }
1971        Some(self.group_no as usize)
1972    }
1973
1974    pub fn is_disabled(&self) -> bool {
1975        self.enabled && self.state == 4
1976    }
1977}
1978
1979#[derive(Debug, Clone)]
1980pub struct ObjectStringParam {
1981    pub moji_size: i64,
1982    pub moji_space_x: i64,
1983    pub moji_space_y: i64,
1984    pub moji_cnt: i64,
1985    pub moji_color: i64,
1986    pub shadow_color: i64,
1987    pub fuchi_color: i64,
1988    /// -1: auto/default in original engine
1989    pub shadow_mode: i64,
1990}
1991
1992impl Default for ObjectStringParam {
1993    fn default() -> Self {
1994        Self {
1995            moji_size: 12,
1996            moji_space_x: 0,
1997            moji_space_y: 0,
1998            moji_cnt: 0,
1999            moji_color: 0,
2000            shadow_color: 1,
2001            fuchi_color: 1,
2002            shadow_mode: -1,
2003        }
2004    }
2005}
2006
2007#[derive(Debug, Default, Clone)]
2008pub struct ObjectNumberParam {
2009    pub keta_max: i64,
2010    pub disp_zero: i64,
2011    pub disp_sign: i64,
2012    pub tumeru_sign: i64,
2013    pub space_mod: i64,
2014    pub space: i64,
2015}
2016
2017#[derive(Debug, Default, Clone)]
2018pub struct ObjectWeatherParam {
2019    /// TNM_OBJECT_WEATHER_TYPE_* (0=none, 1=type A, 2=type B)
2020    pub weather_type: i64,
2021    pub cnt: i64,
2022    pub pat_mode: i64,
2023    pub pat_no_00: i64,
2024    pub pat_no_01: i64,
2025    pub pat_time: i64,
2026    pub move_time_x: i64,
2027    pub move_time_y: i64,
2028    pub sin_time_x: i64,
2029    pub sin_power_x: i64,
2030    pub sin_time_y: i64,
2031    pub sin_power_y: i64,
2032    pub center_x: i64,
2033    pub center_y: i64,
2034    pub appear_range: i64,
2035    pub move_time: i64,
2036    pub center_rotate: i64,
2037    pub zoom_min: i64,
2038    pub zoom_max: i64,
2039    pub scale_x: i64,
2040    pub scale_y: i64,
2041    pub active_time: i64,
2042    pub real_time_flag: bool,
2043}
2044
2045#[derive(Debug, Default, Clone)]
2046pub struct ObjectWeatherWorkSub {
2047    pub state: i64,
2048    pub state_cur_time: i64,
2049    pub state_time_len: i64,
2050    pub move_start_pos_x: i64,
2051    pub move_start_pos_y: i64,
2052    pub move_start_distance: i64,
2053    pub move_start_degree: i64,
2054    pub move_time_x: i64,
2055    pub move_time_y: i64,
2056    pub move_cur_time: i64,
2057    pub sin_time_x: i64,
2058    pub sin_time_y: i64,
2059    pub sin_power_x: i64,
2060    pub sin_power_y: i64,
2061    pub sin_cur_time: i64,
2062    pub center_rotate: i64,
2063    pub zoom_min: i64,
2064    pub zoom_max: i64,
2065    pub scale_x: i64,
2066    pub scale_y: i64,
2067    pub active_time_len: i64,
2068    pub real_time_flag: bool,
2069    pub restruct_flag: bool,
2070}
2071
2072#[derive(Debug, Clone)]
2073pub struct ObjectWeatherWorkState {
2074    pub cnt_max: usize,
2075    pub sub: Vec<ObjectWeatherWorkSub>,
2076    rand_seed: u32,
2077}
2078
2079impl Default for ObjectWeatherWorkState {
2080    fn default() -> Self {
2081        Self {
2082            cnt_max: 0,
2083            sub: Vec::new(),
2084            rand_seed: 0x1234_abcd,
2085        }
2086    }
2087}
2088
2089impl ObjectWeatherWorkState {
2090    fn next_rand(&mut self) -> i64 {
2091        self.rand_seed = self.rand_seed.wrapping_mul(1103515245).wrapping_add(12345);
2092        ((self.rand_seed >> 16) & 0x7fff) as i64
2093    }
2094
2095    pub fn rand_mod(&mut self, modulo: i64) -> i64 {
2096        if modulo <= 0 {
2097            0
2098        } else {
2099            self.next_rand() % modulo
2100        }
2101    }
2102}
2103
2104#[derive(Debug, Clone)]
2105pub struct ObjectMovieState {
2106    pub loop_flag: bool,
2107    pub auto_free_flag: bool,
2108    pub real_time_flag: bool,
2109    pub pause_flag: bool,
2110
2111    /// Current playback timer in milliseconds (the original implementation: m_omv_timer).
2112    pub timer_ms: u64,
2113    /// Total movie time in milliseconds if known.
2114    pub total_ms: Option<u64>,
2115
2116    pub playing: bool,
2117    pub last_tick: Option<Instant>,
2118    pub last_frame_idx: Option<usize>,
2119    pub audio_id: Option<u64>,
2120    pub audio_started_once: bool,
2121    // Two reusable image ids for object movie frames. This keeps movie playback
2122    // changing visible GPU textures without allocating a new image every frame.
2123    pub frame_image_ids: [Option<ImageId>; 2],
2124    pub frame_image_cursor: usize,
2125    pub just_finished: bool,
2126    pub just_looped: bool,
2127    pub seeked: bool,
2128}
2129
2130impl Default for ObjectMovieState {
2131    fn default() -> Self {
2132        Self {
2133            loop_flag: false,
2134            auto_free_flag: true,
2135            real_time_flag: true,
2136            pause_flag: false,
2137            timer_ms: 0,
2138            total_ms: None,
2139            playing: false,
2140            last_tick: None,
2141            last_frame_idx: None,
2142            audio_id: None,
2143            audio_started_once: false,
2144            frame_image_ids: [None, None],
2145            frame_image_cursor: 0,
2146            just_finished: false,
2147            just_looped: false,
2148            seeked: false,
2149        }
2150    }
2151}
2152
2153impl ObjectMovieState {
2154    pub fn reset(&mut self) {
2155        *self = Self::default();
2156    }
2157
2158    pub fn start(
2159        &mut self,
2160        total_ms: Option<u64>,
2161        loop_flag: bool,
2162        auto_free_flag: bool,
2163        real_time_flag: bool,
2164        ready_only: bool,
2165    ) {
2166        self.total_ms = total_ms;
2167        self.loop_flag = loop_flag;
2168        self.auto_free_flag = auto_free_flag;
2169        self.real_time_flag = real_time_flag;
2170        self.pause_flag = ready_only;
2171        self.timer_ms = 0;
2172        self.playing = !ready_only;
2173        self.last_tick = Some(Instant::now());
2174        self.last_frame_idx = None;
2175        self.audio_id = None;
2176        self.audio_started_once = false;
2177        self.frame_image_ids = [None, None];
2178        self.frame_image_cursor = 0;
2179        self.just_finished = false;
2180        self.just_looped = false;
2181        self.seeked = false;
2182    }
2183
2184    pub fn tick(&mut self, past_game_time: i32, past_real_time: i32) {
2185        self.just_finished = false;
2186        self.just_looped = false;
2187        if !self.playing || self.pause_flag {
2188            return;
2189        }
2190        let add = if self.real_time_flag {
2191            past_real_time.max(0) as u64
2192        } else {
2193            past_game_time.max(0) as u64
2194        };
2195        if add == 0 {
2196            return;
2197        }
2198        self.timer_ms = self.timer_ms.saturating_add(add);
2199        if let Some(total) = self.total_ms {
2200            if total > 0 && self.timer_ms >= total {
2201                if self.loop_flag {
2202                    self.timer_ms %= total;
2203                    self.just_looped = true;
2204                } else {
2205                    self.playing = false;
2206                    self.just_finished = true;
2207                }
2208            }
2209        }
2210    }
2211
2212    pub fn seek(&mut self, time_ms: u64) {
2213        self.timer_ms = time_ms;
2214        if let Some(total) = self.total_ms {
2215            if total > 0 {
2216                self.timer_ms %= total;
2217            }
2218        }
2219        self.last_tick = Some(Instant::now());
2220        self.last_frame_idx = None;
2221        self.audio_started_once = false;
2222        self.seeked = true;
2223    }
2224
2225    pub fn get_seek_time(&self) -> u64 {
2226        if let Some(total) = self.total_ms {
2227            if total > 0 {
2228                return self.timer_ms % total;
2229            }
2230        }
2231        0
2232    }
2233
2234    pub fn check_movie(&self) -> bool {
2235        self.playing
2236    }
2237}
2238
2239#[derive(Debug, Default, Clone)]
2240pub struct ObjectEmoteParam {
2241    pub width: i64,
2242    pub height: i64,
2243    pub file_name: Option<String>,
2244    pub rep_x: i64,
2245    pub rep_y: i64,
2246}
2247
2248#[derive(Debug, Clone, Default)]
2249pub struct ObjectFrameActionState {
2250    pub scn_name: String,
2251    pub cmd_name: String,
2252    pub counter: Counter,
2253    pub end_time: i64,
2254    pub real_time_flag: bool,
2255    pub end_flag: bool,
2256    pub args: Vec<crate::runtime::Value>,
2257}
2258
2259#[derive(Debug, Clone)]
2260pub struct ObjectBaseState {
2261    pub wipe_copy: i64,
2262    pub wipe_erase: i64,
2263    pub click_disable: i64,
2264    pub disp: i64,
2265    pub patno: i64,
2266    pub world: i64,
2267    pub order: i64,
2268    pub layer: i64,
2269    pub x: i64,
2270    pub y: i64,
2271    pub z: i64,
2272    pub center_x: i64,
2273    pub center_y: i64,
2274    pub center_z: i64,
2275    pub center_rep_x: i64,
2276    pub center_rep_y: i64,
2277    pub center_rep_z: i64,
2278    pub scale_x: i64,
2279    pub scale_y: i64,
2280    pub scale_z: i64,
2281    pub rotate_x: i64,
2282    pub rotate_y: i64,
2283    pub rotate_z: i64,
2284    pub clip_use: i64,
2285    pub clip_left: i64,
2286    pub clip_top: i64,
2287    pub clip_right: i64,
2288    pub clip_bottom: i64,
2289    pub src_clip_use: i64,
2290    pub src_clip_left: i64,
2291    pub src_clip_top: i64,
2292    pub src_clip_right: i64,
2293    pub src_clip_bottom: i64,
2294    pub alpha: i64,
2295    pub tr: i64,
2296    pub mono: i64,
2297    pub reverse: i64,
2298    pub bright: i64,
2299    pub dark: i64,
2300    pub color_r: i64,
2301    pub color_g: i64,
2302    pub color_b: i64,
2303    pub color_rate: i64,
2304    pub color_add_r: i64,
2305    pub color_add_g: i64,
2306    pub color_add_b: i64,
2307    pub mask_no: i64,
2308    pub tonecurve_no: i64,
2309    pub light_no: i64,
2310    pub fog_use: i64,
2311    pub culling: i64,
2312    pub alpha_test: i64,
2313    pub alpha_blend: i64,
2314    pub blend: i64,
2315    pub child_sort_type: i64,
2316    pub no_event_hint: bool,
2317}
2318
2319impl Default for ObjectBaseState {
2320    fn default() -> Self {
2321        Self {
2322            wipe_copy: 0,
2323            wipe_erase: 0,
2324            click_disable: 0,
2325            disp: 0,
2326            patno: 0,
2327            world: -1,
2328            order: 0,
2329            layer: 0,
2330            x: 0,
2331            y: 0,
2332            z: 0,
2333            center_x: 0,
2334            center_y: 0,
2335            center_z: 0,
2336            center_rep_x: 0,
2337            center_rep_y: 0,
2338            center_rep_z: 0,
2339            scale_x: 1000,
2340            scale_y: 1000,
2341            scale_z: 1000,
2342            rotate_x: 0,
2343            rotate_y: 0,
2344            rotate_z: 0,
2345            clip_use: 0,
2346            clip_left: 0,
2347            clip_top: 0,
2348            clip_right: 0,
2349            clip_bottom: 0,
2350            src_clip_use: 0,
2351            src_clip_left: 0,
2352            src_clip_top: 0,
2353            src_clip_right: 0,
2354            src_clip_bottom: 0,
2355            alpha: 255,
2356            tr: 255,
2357            mono: 0,
2358            reverse: 0,
2359            bright: 0,
2360            dark: 0,
2361            color_r: 0,
2362            color_g: 0,
2363            color_b: 0,
2364            color_rate: 0,
2365            color_add_r: 0,
2366            color_add_g: 0,
2367            color_add_b: 0,
2368            mask_no: -1,
2369            tonecurve_no: -1,
2370            light_no: -1,
2371            fog_use: 0,
2372            culling: 0,
2373            alpha_test: 1,
2374            alpha_blend: 1,
2375            blend: 0,
2376            child_sort_type: 0,
2377            no_event_hint: false,
2378        }
2379    }
2380}
2381
2382#[derive(Debug, Clone)]
2383pub struct ObjectPropEvents {
2384    pub patno: IntEvent,
2385    pub x: IntEvent,
2386    pub y: IntEvent,
2387    pub z: IntEvent,
2388    pub center_x: IntEvent,
2389    pub center_y: IntEvent,
2390    pub center_z: IntEvent,
2391    pub center_rep_x: IntEvent,
2392    pub center_rep_y: IntEvent,
2393    pub center_rep_z: IntEvent,
2394    pub scale_x: IntEvent,
2395    pub scale_y: IntEvent,
2396    pub scale_z: IntEvent,
2397    pub rotate_x: IntEvent,
2398    pub rotate_y: IntEvent,
2399    pub rotate_z: IntEvent,
2400    pub clip_left: IntEvent,
2401    pub clip_top: IntEvent,
2402    pub clip_right: IntEvent,
2403    pub clip_bottom: IntEvent,
2404    pub src_clip_left: IntEvent,
2405    pub src_clip_top: IntEvent,
2406    pub src_clip_right: IntEvent,
2407    pub src_clip_bottom: IntEvent,
2408    pub tr: IntEvent,
2409    pub mono: IntEvent,
2410    pub reverse: IntEvent,
2411    pub bright: IntEvent,
2412    pub dark: IntEvent,
2413    pub color_r: IntEvent,
2414    pub color_g: IntEvent,
2415    pub color_b: IntEvent,
2416    pub color_rate: IntEvent,
2417    pub color_add_r: IntEvent,
2418    pub color_add_g: IntEvent,
2419    pub color_add_b: IntEvent,
2420}
2421
2422impl Default for ObjectPropEvents {
2423    fn default() -> Self {
2424        Self {
2425            patno: IntEvent::new(0),
2426            x: IntEvent::new(0),
2427            y: IntEvent::new(0),
2428            z: IntEvent::new(0),
2429            center_x: IntEvent::new(0),
2430            center_y: IntEvent::new(0),
2431            center_z: IntEvent::new(0),
2432            center_rep_x: IntEvent::new(0),
2433            center_rep_y: IntEvent::new(0),
2434            center_rep_z: IntEvent::new(0),
2435            scale_x: IntEvent::new(1000),
2436            scale_y: IntEvent::new(1000),
2437            scale_z: IntEvent::new(1000),
2438            rotate_x: IntEvent::new(0),
2439            rotate_y: IntEvent::new(0),
2440            rotate_z: IntEvent::new(0),
2441            clip_left: IntEvent::new(0),
2442            clip_top: IntEvent::new(0),
2443            clip_right: IntEvent::new(0),
2444            clip_bottom: IntEvent::new(0),
2445            src_clip_left: IntEvent::new(0),
2446            src_clip_top: IntEvent::new(0),
2447            src_clip_right: IntEvent::new(0),
2448            src_clip_bottom: IntEvent::new(0),
2449            tr: IntEvent::new(255),
2450            mono: IntEvent::new(0),
2451            reverse: IntEvent::new(0),
2452            bright: IntEvent::new(0),
2453            dark: IntEvent::new(0),
2454            color_r: IntEvent::new(0),
2455            color_g: IntEvent::new(0),
2456            color_b: IntEvent::new(0),
2457            color_rate: IntEvent::new(0),
2458            color_add_r: IntEvent::new(0),
2459            color_add_g: IntEvent::new(0),
2460            color_add_b: IntEvent::new(0),
2461        }
2462    }
2463}
2464
2465impl ObjectPropEvents {
2466    pub fn clear(&mut self) {
2467        self.patno.reinit();
2468        self.x.reinit();
2469        self.y.reinit();
2470        self.z.reinit();
2471        self.center_x.reinit();
2472        self.center_y.reinit();
2473        self.center_z.reinit();
2474        self.center_rep_x.reinit();
2475        self.center_rep_y.reinit();
2476        self.center_rep_z.reinit();
2477        self.scale_x.reinit();
2478        self.scale_y.reinit();
2479        self.scale_z.reinit();
2480        self.rotate_x.reinit();
2481        self.rotate_y.reinit();
2482        self.rotate_z.reinit();
2483        self.clip_left.reinit();
2484        self.clip_top.reinit();
2485        self.clip_right.reinit();
2486        self.clip_bottom.reinit();
2487        self.src_clip_left.reinit();
2488        self.src_clip_top.reinit();
2489        self.src_clip_right.reinit();
2490        self.src_clip_bottom.reinit();
2491        self.tr.reinit();
2492        self.mono.reinit();
2493        self.reverse.reinit();
2494        self.bright.reinit();
2495        self.dark.reinit();
2496        self.color_r.reinit();
2497        self.color_g.reinit();
2498        self.color_b.reinit();
2499        self.color_rate.reinit();
2500        self.color_add_r.reinit();
2501        self.color_add_g.reinit();
2502        self.color_add_b.reinit();
2503    }
2504
2505    pub fn update_time(&mut self, past_game_time: i32, past_real_time: i32) {
2506        self.patno.update_time(past_game_time, past_real_time);
2507        self.x.update_time(past_game_time, past_real_time);
2508        self.y.update_time(past_game_time, past_real_time);
2509        self.z.update_time(past_game_time, past_real_time);
2510        self.center_x.update_time(past_game_time, past_real_time);
2511        self.center_y.update_time(past_game_time, past_real_time);
2512        self.center_z.update_time(past_game_time, past_real_time);
2513        self.center_rep_x
2514            .update_time(past_game_time, past_real_time);
2515        self.center_rep_y
2516            .update_time(past_game_time, past_real_time);
2517        self.center_rep_z
2518            .update_time(past_game_time, past_real_time);
2519        self.scale_x.update_time(past_game_time, past_real_time);
2520        self.scale_y.update_time(past_game_time, past_real_time);
2521        self.scale_z.update_time(past_game_time, past_real_time);
2522        self.rotate_x.update_time(past_game_time, past_real_time);
2523        self.rotate_y.update_time(past_game_time, past_real_time);
2524        self.rotate_z.update_time(past_game_time, past_real_time);
2525        self.clip_left.update_time(past_game_time, past_real_time);
2526        self.clip_top.update_time(past_game_time, past_real_time);
2527        self.clip_right.update_time(past_game_time, past_real_time);
2528        self.clip_bottom.update_time(past_game_time, past_real_time);
2529        self.src_clip_left
2530            .update_time(past_game_time, past_real_time);
2531        self.src_clip_top
2532            .update_time(past_game_time, past_real_time);
2533        self.src_clip_right
2534            .update_time(past_game_time, past_real_time);
2535        self.src_clip_bottom
2536            .update_time(past_game_time, past_real_time);
2537        self.tr.update_time(past_game_time, past_real_time);
2538        self.mono.update_time(past_game_time, past_real_time);
2539        self.reverse.update_time(past_game_time, past_real_time);
2540        self.bright.update_time(past_game_time, past_real_time);
2541        self.dark.update_time(past_game_time, past_real_time);
2542        self.color_r.update_time(past_game_time, past_real_time);
2543        self.color_g.update_time(past_game_time, past_real_time);
2544        self.color_b.update_time(past_game_time, past_real_time);
2545        self.color_rate.update_time(past_game_time, past_real_time);
2546        self.color_add_r.update_time(past_game_time, past_real_time);
2547        self.color_add_g.update_time(past_game_time, past_real_time);
2548        self.color_add_b.update_time(past_game_time, past_real_time);
2549    }
2550
2551    pub fn frame(&mut self) {
2552        self.patno.frame();
2553        self.x.frame();
2554        self.y.frame();
2555        self.z.frame();
2556        self.center_x.frame();
2557        self.center_y.frame();
2558        self.center_z.frame();
2559        self.center_rep_x.frame();
2560        self.center_rep_y.frame();
2561        self.center_rep_z.frame();
2562        self.scale_x.frame();
2563        self.scale_y.frame();
2564        self.scale_z.frame();
2565        self.rotate_x.frame();
2566        self.rotate_y.frame();
2567        self.rotate_z.frame();
2568        self.clip_left.frame();
2569        self.clip_top.frame();
2570        self.clip_right.frame();
2571        self.clip_bottom.frame();
2572        self.src_clip_left.frame();
2573        self.src_clip_top.frame();
2574        self.src_clip_right.frame();
2575        self.src_clip_bottom.frame();
2576        self.tr.frame();
2577        self.mono.frame();
2578        self.reverse.frame();
2579        self.bright.frame();
2580        self.dark.frame();
2581        self.color_r.frame();
2582        self.color_g.frame();
2583        self.color_b.frame();
2584        self.color_rate.frame();
2585        self.color_add_r.frame();
2586        self.color_add_g.frame();
2587        self.color_add_b.frame();
2588    }
2589
2590    pub fn tick(&mut self, delta: i32) {
2591        self.update_time(delta, delta);
2592        self.frame();
2593    }
2594
2595    pub fn any_active(&self) -> bool {
2596        self.patno.check_event()
2597            || self.x.check_event()
2598            || self.y.check_event()
2599            || self.z.check_event()
2600            || self.center_x.check_event()
2601            || self.center_y.check_event()
2602            || self.center_z.check_event()
2603            || self.center_rep_x.check_event()
2604            || self.center_rep_y.check_event()
2605            || self.center_rep_z.check_event()
2606            || self.scale_x.check_event()
2607            || self.scale_y.check_event()
2608            || self.scale_z.check_event()
2609            || self.rotate_x.check_event()
2610            || self.rotate_y.check_event()
2611            || self.rotate_z.check_event()
2612            || self.clip_left.check_event()
2613            || self.clip_top.check_event()
2614            || self.clip_right.check_event()
2615            || self.clip_bottom.check_event()
2616            || self.src_clip_left.check_event()
2617            || self.src_clip_top.check_event()
2618            || self.src_clip_right.check_event()
2619            || self.src_clip_bottom.check_event()
2620            || self.tr.check_event()
2621            || self.mono.check_event()
2622            || self.reverse.check_event()
2623            || self.bright.check_event()
2624            || self.dark.check_event()
2625            || self.color_r.check_event()
2626            || self.color_g.check_event()
2627            || self.color_b.check_event()
2628            || self.color_rate.check_event()
2629            || self.color_add_r.check_event()
2630            || self.color_add_g.check_event()
2631            || self.color_add_b.check_event()
2632    }
2633
2634    pub fn end_all(&mut self) {
2635        self.patno.end_event();
2636        self.x.end_event();
2637        self.y.end_event();
2638        self.z.end_event();
2639        self.center_x.end_event();
2640        self.center_y.end_event();
2641        self.center_z.end_event();
2642        self.center_rep_x.end_event();
2643        self.center_rep_y.end_event();
2644        self.center_rep_z.end_event();
2645        self.scale_x.end_event();
2646        self.scale_y.end_event();
2647        self.scale_z.end_event();
2648        self.rotate_x.end_event();
2649        self.rotate_y.end_event();
2650        self.rotate_z.end_event();
2651        self.clip_left.end_event();
2652        self.clip_top.end_event();
2653        self.clip_right.end_event();
2654        self.clip_bottom.end_event();
2655        self.src_clip_left.end_event();
2656        self.src_clip_top.end_event();
2657        self.src_clip_right.end_event();
2658        self.src_clip_bottom.end_event();
2659        self.tr.end_event();
2660        self.mono.end_event();
2661        self.reverse.end_event();
2662        self.bright.end_event();
2663        self.dark.end_event();
2664        self.color_r.end_event();
2665        self.color_g.end_event();
2666        self.color_b.end_event();
2667        self.color_rate.end_event();
2668        self.color_add_r.end_event();
2669        self.color_add_g.end_event();
2670        self.color_add_b.end_event();
2671    }
2672
2673    pub fn get(&self, target: ObjectEventTarget) -> Option<&IntEvent> {
2674        match target {
2675            ObjectEventTarget::Patno => Some(&self.patno),
2676            ObjectEventTarget::X => Some(&self.x),
2677            ObjectEventTarget::Y => Some(&self.y),
2678            ObjectEventTarget::Z => Some(&self.z),
2679            ObjectEventTarget::CenterX => Some(&self.center_x),
2680            ObjectEventTarget::CenterY => Some(&self.center_y),
2681            ObjectEventTarget::CenterZ => Some(&self.center_z),
2682            ObjectEventTarget::CenterRepX => Some(&self.center_rep_x),
2683            ObjectEventTarget::CenterRepY => Some(&self.center_rep_y),
2684            ObjectEventTarget::CenterRepZ => Some(&self.center_rep_z),
2685            ObjectEventTarget::ScaleX => Some(&self.scale_x),
2686            ObjectEventTarget::ScaleY => Some(&self.scale_y),
2687            ObjectEventTarget::ScaleZ => Some(&self.scale_z),
2688            ObjectEventTarget::RotateX => Some(&self.rotate_x),
2689            ObjectEventTarget::RotateY => Some(&self.rotate_y),
2690            ObjectEventTarget::RotateZ => Some(&self.rotate_z),
2691            ObjectEventTarget::ClipLeft => Some(&self.clip_left),
2692            ObjectEventTarget::ClipTop => Some(&self.clip_top),
2693            ObjectEventTarget::ClipRight => Some(&self.clip_right),
2694            ObjectEventTarget::ClipBottom => Some(&self.clip_bottom),
2695            ObjectEventTarget::SrcClipLeft => Some(&self.src_clip_left),
2696            ObjectEventTarget::SrcClipTop => Some(&self.src_clip_top),
2697            ObjectEventTarget::SrcClipRight => Some(&self.src_clip_right),
2698            ObjectEventTarget::SrcClipBottom => Some(&self.src_clip_bottom),
2699            ObjectEventTarget::Tr => Some(&self.tr),
2700            ObjectEventTarget::Mono => Some(&self.mono),
2701            ObjectEventTarget::Reverse => Some(&self.reverse),
2702            ObjectEventTarget::Bright => Some(&self.bright),
2703            ObjectEventTarget::Dark => Some(&self.dark),
2704            ObjectEventTarget::ColorR => Some(&self.color_r),
2705            ObjectEventTarget::ColorG => Some(&self.color_g),
2706            ObjectEventTarget::ColorB => Some(&self.color_b),
2707            ObjectEventTarget::ColorRate => Some(&self.color_rate),
2708            ObjectEventTarget::ColorAddR => Some(&self.color_add_r),
2709            ObjectEventTarget::ColorAddG => Some(&self.color_add_g),
2710            ObjectEventTarget::ColorAddB => Some(&self.color_add_b),
2711            ObjectEventTarget::XRep
2712            | ObjectEventTarget::YRep
2713            | ObjectEventTarget::ZRep
2714            | ObjectEventTarget::TrRep
2715            | ObjectEventTarget::Alpha
2716            | ObjectEventTarget::Order
2717            | ObjectEventTarget::Layer
2718            | ObjectEventTarget::Unknown => None,
2719        }
2720    }
2721
2722    pub fn get_mut(&mut self, target: ObjectEventTarget) -> Option<&mut IntEvent> {
2723        match target {
2724            ObjectEventTarget::Patno => Some(&mut self.patno),
2725            ObjectEventTarget::X => Some(&mut self.x),
2726            ObjectEventTarget::Y => Some(&mut self.y),
2727            ObjectEventTarget::Z => Some(&mut self.z),
2728            ObjectEventTarget::CenterX => Some(&mut self.center_x),
2729            ObjectEventTarget::CenterY => Some(&mut self.center_y),
2730            ObjectEventTarget::CenterZ => Some(&mut self.center_z),
2731            ObjectEventTarget::CenterRepX => Some(&mut self.center_rep_x),
2732            ObjectEventTarget::CenterRepY => Some(&mut self.center_rep_y),
2733            ObjectEventTarget::CenterRepZ => Some(&mut self.center_rep_z),
2734            ObjectEventTarget::ScaleX => Some(&mut self.scale_x),
2735            ObjectEventTarget::ScaleY => Some(&mut self.scale_y),
2736            ObjectEventTarget::ScaleZ => Some(&mut self.scale_z),
2737            ObjectEventTarget::RotateX => Some(&mut self.rotate_x),
2738            ObjectEventTarget::RotateY => Some(&mut self.rotate_y),
2739            ObjectEventTarget::RotateZ => Some(&mut self.rotate_z),
2740            ObjectEventTarget::ClipLeft => Some(&mut self.clip_left),
2741            ObjectEventTarget::ClipTop => Some(&mut self.clip_top),
2742            ObjectEventTarget::ClipRight => Some(&mut self.clip_right),
2743            ObjectEventTarget::ClipBottom => Some(&mut self.clip_bottom),
2744            ObjectEventTarget::SrcClipLeft => Some(&mut self.src_clip_left),
2745            ObjectEventTarget::SrcClipTop => Some(&mut self.src_clip_top),
2746            ObjectEventTarget::SrcClipRight => Some(&mut self.src_clip_right),
2747            ObjectEventTarget::SrcClipBottom => Some(&mut self.src_clip_bottom),
2748            ObjectEventTarget::Tr => Some(&mut self.tr),
2749            ObjectEventTarget::Mono => Some(&mut self.mono),
2750            ObjectEventTarget::Reverse => Some(&mut self.reverse),
2751            ObjectEventTarget::Bright => Some(&mut self.bright),
2752            ObjectEventTarget::Dark => Some(&mut self.dark),
2753            ObjectEventTarget::ColorR => Some(&mut self.color_r),
2754            ObjectEventTarget::ColorG => Some(&mut self.color_g),
2755            ObjectEventTarget::ColorB => Some(&mut self.color_b),
2756            ObjectEventTarget::ColorRate => Some(&mut self.color_rate),
2757            ObjectEventTarget::ColorAddR => Some(&mut self.color_add_r),
2758            ObjectEventTarget::ColorAddG => Some(&mut self.color_add_g),
2759            ObjectEventTarget::ColorAddB => Some(&mut self.color_add_b),
2760            ObjectEventTarget::XRep
2761            | ObjectEventTarget::YRep
2762            | ObjectEventTarget::ZRep
2763            | ObjectEventTarget::TrRep
2764            | ObjectEventTarget::Alpha
2765            | ObjectEventTarget::Order
2766            | ObjectEventTarget::Layer
2767            | ObjectEventTarget::Unknown => None,
2768        }
2769    }
2770}
2771
2772#[derive(Debug, Default, Clone)]
2773pub struct ObjectPropEventLists {
2774    pub x_rep: Vec<IntEvent>,
2775    pub y_rep: Vec<IntEvent>,
2776    pub z_rep: Vec<IntEvent>,
2777    pub tr_rep: Vec<IntEvent>,
2778}
2779
2780impl ObjectPropEventLists {
2781    pub fn clear(&mut self) {
2782        self.x_rep.clear();
2783        self.y_rep.clear();
2784        self.z_rep.clear();
2785        self.tr_rep.clear();
2786    }
2787
2788    pub fn update_time(&mut self, past_game_time: i32, past_real_time: i32) {
2789        for ev in &mut self.x_rep {
2790            ev.update_time(past_game_time, past_real_time);
2791        }
2792        for ev in &mut self.y_rep {
2793            ev.update_time(past_game_time, past_real_time);
2794        }
2795        for ev in &mut self.z_rep {
2796            ev.update_time(past_game_time, past_real_time);
2797        }
2798        for ev in &mut self.tr_rep {
2799            ev.update_time(past_game_time, past_real_time);
2800        }
2801    }
2802
2803    pub fn frame(&mut self) {
2804        for ev in &mut self.x_rep {
2805            ev.frame();
2806        }
2807        for ev in &mut self.y_rep {
2808            ev.frame();
2809        }
2810        for ev in &mut self.z_rep {
2811            ev.frame();
2812        }
2813        for ev in &mut self.tr_rep {
2814            ev.frame();
2815        }
2816    }
2817
2818    pub fn tick(&mut self, delta: i32) {
2819        self.update_time(delta, delta);
2820        self.frame();
2821    }
2822
2823    pub fn any_active(&self) -> bool {
2824        self.x_rep.iter().any(|e| e.check_event())
2825            || self.y_rep.iter().any(|e| e.check_event())
2826            || self.z_rep.iter().any(|e| e.check_event())
2827            || self.tr_rep.iter().any(|e| e.check_event())
2828    }
2829
2830    pub fn end_all(&mut self) {
2831        for ev in &mut self.x_rep {
2832            ev.end_event();
2833        }
2834        for ev in &mut self.y_rep {
2835            ev.end_event();
2836        }
2837        for ev in &mut self.z_rep {
2838            ev.end_event();
2839        }
2840        for ev in &mut self.tr_rep {
2841            ev.end_event();
2842        }
2843    }
2844}
2845
2846#[derive(Debug, Clone)]
2847pub struct ObjectPropLists {
2848    pub x_rep: Vec<i64>,
2849    pub y_rep: Vec<i64>,
2850    pub z_rep: Vec<i64>,
2851    pub tr_rep: Vec<i64>,
2852    pub f: Vec<i64>,
2853}
2854
2855impl Default for ObjectPropLists {
2856    fn default() -> Self {
2857        Self {
2858            x_rep: Vec::new(),
2859            y_rep: Vec::new(),
2860            z_rep: Vec::new(),
2861            tr_rep: Vec::new(),
2862            f: vec![0; 32],
2863        }
2864    }
2865}
2866
2867impl ObjectPropLists {
2868    pub fn clear(&mut self) {
2869        self.x_rep.clear();
2870        self.y_rep.clear();
2871        self.z_rep.clear();
2872        self.tr_rep.clear();
2873        self.f.fill(0);
2874    }
2875}
2876
2877#[derive(Debug, Default, Clone)]
2878pub struct ObjectRuntimeState {
2879    pub explicit_int_props: HashSet<i32>,
2880    pub explicit_str_props: HashSet<i32>,
2881    pub prop_events: ObjectPropEvents,
2882    pub prop_event_lists: ObjectPropEventLists,
2883    pub prop_lists: ObjectPropLists,
2884    pub child_objects: Vec<ObjectState>,
2885}
2886
2887#[derive(Debug, Default, Clone)]
2888pub struct ObjectState {
2889    pub used: bool,
2890    pub backend: ObjectBackend,
2891    pub file_name: Option<String>,
2892    pub string_value: Option<String>,
2893
2894    /// TNM_OBJECT_TYPE_* (0=none, 1=rect, 2=pct, 3=string, 4=weather, 5=number, ...).
2895    pub object_type: i64,
2896
2897    /// For NUMBER objects, stores the current number value.
2898    pub number_value: i64,
2899
2900    /// For STRING objects.
2901    pub string_param: ObjectStringParam,
2902
2903    /// For NUMBER objects.
2904    pub number_param: ObjectNumberParam,
2905
2906    /// For WEATHER objects (type A/B).
2907    pub weather_param: ObjectWeatherParam,
2908    pub weather_work: ObjectWeatherWorkState,
2909
2910    /// For SAVE_THUMB / THUMB objects.
2911    pub thumb_save_no: i64,
2912
2913    /// For MOVIE objects.
2914    pub movie: ObjectMovieState,
2915
2916    /// For E-mote objects.
2917    pub emote: ObjectEmoteParam,
2918
2919    /// Last loaded GAN file.
2920    pub gan_file: Option<String>,
2921    /// GAN runtime state.
2922    pub gan: GanState,
2923
2924    /// OBJECT.FRAME_ACTION state.
2925    pub frame_action: ObjectFrameActionState,
2926    /// OBJECT.FRAME_ACTION_CH state.
2927    pub frame_action_ch: Vec<ObjectFrameActionState>,
2928
2929    /// Cached masked sprite images keyed by (layer_id, sprite_id).
2930    pub mask_cache: HashMap<(LayerId, SpriteId), MaskedSpriteCache>,
2931
2932    pub base: ObjectBaseState,
2933
2934    pub button: ObjectButtonState,
2935
2936    pub runtime: ObjectRuntimeState,
2937
2938    pub mesh_animation_state: crate::mesh3d::MeshAnimationState,
2939    pub nested_runtime_slot: Option<usize>,
2940}
2941
2942impl ObjectState {
2943    fn sync_event_backed_prop_value(
2944        &mut self,
2945        ids: &crate::runtime::constants::RuntimeConstants,
2946        op: i32,
2947        value: i64,
2948    ) {
2949        let target = if ids.obj_patno != 0 && op == ids.obj_patno {
2950            ObjectEventTarget::Patno
2951        } else if ids.obj_x != 0 && op == ids.obj_x {
2952            ObjectEventTarget::X
2953        } else if ids.obj_y != 0 && op == ids.obj_y {
2954            ObjectEventTarget::Y
2955        } else if ids.obj_z != 0 && op == ids.obj_z {
2956            ObjectEventTarget::Z
2957        } else if ids.obj_center_x != 0 && op == ids.obj_center_x {
2958            ObjectEventTarget::CenterX
2959        } else if ids.obj_center_y != 0 && op == ids.obj_center_y {
2960            ObjectEventTarget::CenterY
2961        } else if ids.obj_center_z != 0 && op == ids.obj_center_z {
2962            ObjectEventTarget::CenterZ
2963        } else if ids.obj_center_rep_x != 0 && op == ids.obj_center_rep_x {
2964            ObjectEventTarget::CenterRepX
2965        } else if ids.obj_center_rep_y != 0 && op == ids.obj_center_rep_y {
2966            ObjectEventTarget::CenterRepY
2967        } else if ids.obj_center_rep_z != 0 && op == ids.obj_center_rep_z {
2968            ObjectEventTarget::CenterRepZ
2969        } else if ids.obj_scale_x != 0 && op == ids.obj_scale_x {
2970            ObjectEventTarget::ScaleX
2971        } else if ids.obj_scale_y != 0 && op == ids.obj_scale_y {
2972            ObjectEventTarget::ScaleY
2973        } else if ids.obj_scale_z != 0 && op == ids.obj_scale_z {
2974            ObjectEventTarget::ScaleZ
2975        } else if ids.obj_rotate_x != 0 && op == ids.obj_rotate_x {
2976            ObjectEventTarget::RotateX
2977        } else if ids.obj_rotate_y != 0 && op == ids.obj_rotate_y {
2978            ObjectEventTarget::RotateY
2979        } else if ids.obj_rotate_z != 0 && op == ids.obj_rotate_z {
2980            ObjectEventTarget::RotateZ
2981        } else if ids.obj_clip_left != 0 && op == ids.obj_clip_left {
2982            ObjectEventTarget::ClipLeft
2983        } else if ids.obj_clip_top != 0 && op == ids.obj_clip_top {
2984            ObjectEventTarget::ClipTop
2985        } else if ids.obj_clip_right != 0 && op == ids.obj_clip_right {
2986            ObjectEventTarget::ClipRight
2987        } else if ids.obj_clip_bottom != 0 && op == ids.obj_clip_bottom {
2988            ObjectEventTarget::ClipBottom
2989        } else if ids.obj_src_clip_left != 0 && op == ids.obj_src_clip_left {
2990            ObjectEventTarget::SrcClipLeft
2991        } else if ids.obj_src_clip_top != 0 && op == ids.obj_src_clip_top {
2992            ObjectEventTarget::SrcClipTop
2993        } else if ids.obj_src_clip_right != 0 && op == ids.obj_src_clip_right {
2994            ObjectEventTarget::SrcClipRight
2995        } else if ids.obj_src_clip_bottom != 0 && op == ids.obj_src_clip_bottom {
2996            ObjectEventTarget::SrcClipBottom
2997        } else if ids.obj_tr != 0 && op == ids.obj_tr {
2998            ObjectEventTarget::Tr
2999        } else if ids.obj_mono != 0 && op == ids.obj_mono {
3000            ObjectEventTarget::Mono
3001        } else if ids.obj_reverse != 0 && op == ids.obj_reverse {
3002            ObjectEventTarget::Reverse
3003        } else if ids.obj_bright != 0 && op == ids.obj_bright {
3004            ObjectEventTarget::Bright
3005        } else if ids.obj_dark != 0 && op == ids.obj_dark {
3006            ObjectEventTarget::Dark
3007        } else if ids.obj_color_r != 0 && op == ids.obj_color_r {
3008            ObjectEventTarget::ColorR
3009        } else if ids.obj_color_g != 0 && op == ids.obj_color_g {
3010            ObjectEventTarget::ColorG
3011        } else if ids.obj_color_b != 0 && op == ids.obj_color_b {
3012            ObjectEventTarget::ColorB
3013        } else if ids.obj_color_rate != 0 && op == ids.obj_color_rate {
3014            ObjectEventTarget::ColorRate
3015        } else if ids.obj_color_add_r != 0 && op == ids.obj_color_add_r {
3016            ObjectEventTarget::ColorAddR
3017        } else if ids.obj_color_add_g != 0 && op == ids.obj_color_add_g {
3018            ObjectEventTarget::ColorAddG
3019        } else if ids.obj_color_add_b != 0 && op == ids.obj_color_add_b {
3020            ObjectEventTarget::ColorAddB
3021        } else {
3022            self.event_target(ids, op)
3023        };
3024
3025        let Some(ev) = self.runtime.prop_events.get_mut(target) else {
3026            return;
3027        };
3028        ev.set_value(value as i32);
3029        if !ev.check_event() {
3030            ev.cur_value = value as i32;
3031        }
3032    }
3033
3034    /// Reset type-specific parameters (mirrors C_elm_object::init_type(true)).
3035    ///
3036    /// Important: this does NOT clear button/groups/events (those are part of init_param/reinit in the original implementation).
3037    pub fn init_type_like(&mut self) {
3038        self.backend = ObjectBackend::None;
3039        self.file_name = None;
3040        self.string_value = None;
3041        self.object_type = 0;
3042
3043        self.number_value = 0;
3044        self.string_param = ObjectStringParam::default();
3045        self.number_param = ObjectNumberParam::default();
3046        self.weather_param = ObjectWeatherParam::default();
3047        self.weather_work = ObjectWeatherWorkState::default();
3048        self.thumb_save_no = -1;
3049
3050        self.movie.reset();
3051        self.emote = ObjectEmoteParam::default();
3052
3053        self.gan_file = None;
3054        self.gan.reset();
3055        self.mask_cache.clear();
3056        self.mesh_animation_state = crate::mesh3d::MeshAnimationState::default();
3057    }
3058
3059    fn weather_rand_percent(&mut self, base: i64, span: i64) -> i64 {
3060        base + self.weather_work.rand_mod(span)
3061    }
3062
3063    fn setup_weather_sub(&mut self, idx: usize, init_state: i64, screen_w: i64, screen_h: i64) {
3064        if idx >= self.weather_work.sub.len() {
3065            return;
3066        }
3067        let param = self.weather_param.clone();
3068        let screen_w = screen_w.max(1);
3069        let screen_h = screen_h.max(1);
3070        let mut sub = ObjectWeatherWorkSub::default();
3071        sub.state = init_state;
3072
3073        if param.weather_type == 1 {
3074            sub.move_start_pos_x = self.weather_work.rand_mod(screen_w);
3075            sub.move_start_pos_y = self.weather_work.rand_mod(screen_h);
3076            sub.move_time_x = param.move_time_x * self.weather_rand_percent(90, 20) / 100;
3077            sub.move_time_y = param.move_time_y * self.weather_rand_percent(90, 20) / 100;
3078            sub.move_cur_time = self.weather_work.next_rand();
3079            sub.sin_time_x = param.sin_time_x * self.weather_rand_percent(90, 20) / 100;
3080            sub.sin_time_y = param.sin_time_y * self.weather_rand_percent(90, 20) / 100;
3081            sub.sin_power_x = param.sin_power_x * self.weather_rand_percent(90, 20) / 100;
3082            sub.sin_power_y = param.sin_power_y * self.weather_rand_percent(90, 20) / 100;
3083            sub.sin_cur_time = self.weather_work.next_rand();
3084            sub.scale_x = param.scale_x;
3085            sub.scale_y = param.scale_y;
3086            sub.active_time_len = param.active_time * self.weather_rand_percent(80, 40) / 100;
3087            sub.state_time_len = if init_state == 0 {
3088                self.weather_work.rand_mod(3000)
3089            } else {
3090                sub.active_time_len
3091            };
3092            sub.real_time_flag = param.real_time_flag;
3093        } else if param.weather_type == 2 {
3094            let max_distance_x = if param.center_x > screen_w / 2 {
3095                param.center_x
3096            } else {
3097                screen_w - param.center_x
3098            };
3099            let max_distance_y = if param.center_y > screen_h / 2 {
3100                param.center_y
3101            } else {
3102                screen_h - param.center_y
3103            };
3104            let max_distance =
3105                (((max_distance_x * max_distance_x + max_distance_y * max_distance_y) as f64)
3106                    .sqrt()) as i64;
3107            sub.move_start_distance =
3108                self.weather_work.rand_mod(max_distance.max(1)) * param.appear_range / 100;
3109            sub.move_start_degree = self.weather_work.rand_mod(3600);
3110            sub.move_time_x = param.move_time_x.abs() * self.weather_rand_percent(80, 40) / 100;
3111            sub.move_time_y = param.move_time_y.abs() * self.weather_rand_percent(80, 40) / 100;
3112            sub.sin_time_x = param.sin_time_x * self.weather_rand_percent(90, 20) / 100;
3113            sub.sin_time_y = param.sin_time_y * self.weather_rand_percent(90, 20) / 100;
3114            sub.sin_power_x = param.sin_power_x * self.weather_rand_percent(90, 20) / 100;
3115            sub.sin_power_y = param.sin_power_y * self.weather_rand_percent(90, 20) / 100;
3116            sub.sin_cur_time = self.weather_work.next_rand();
3117            sub.center_rotate = param.center_rotate * self.weather_rand_percent(90, 20) / 100;
3118            sub.zoom_min = param.zoom_min;
3119            sub.zoom_max = param.zoom_max;
3120            sub.scale_x = param.scale_x;
3121            sub.scale_y = param.scale_y;
3122            sub.active_time_len = param.move_time_x.abs() * max_distance / 1000 - 1000;
3123            sub.state_time_len = if init_state == 0 {
3124                self.weather_work
3125                    .rand_mod((1000 + sub.active_time_len + 1000).max(1))
3126            } else {
3127                sub.active_time_len
3128            };
3129            sub.move_cur_time = if sub.state_time_len == 0 {
3130                0
3131            } else {
3132                self.weather_work.rand_mod(sub.state_time_len.abs().max(1))
3133            };
3134            sub.real_time_flag = param.real_time_flag;
3135        }
3136        sub.restruct_flag = false;
3137        self.weather_work.sub[idx] = sub;
3138    }
3139
3140    pub fn weather_sprite_count(&self) -> usize {
3141        match self.weather_param.weather_type {
3142            1 => self.weather_work.cnt_max.saturating_mul(4),
3143            2 => self.weather_work.cnt_max,
3144            _ => 0,
3145        }
3146    }
3147
3148    pub fn restruct_weather_work(&mut self, screen_w: i64, screen_h: i64) {
3149        if self.object_type != 4 {
3150            return;
3151        }
3152        for sub in &mut self.weather_work.sub {
3153            sub.restruct_flag = true;
3154        }
3155        let cnt = self.weather_param.cnt.max(0) as usize;
3156        let old_cnt = self.weather_work.sub.len();
3157        if cnt > old_cnt {
3158            self.weather_work
3159                .sub
3160                .resize_with(cnt, ObjectWeatherWorkSub::default);
3161            for idx in old_cnt..cnt {
3162                self.setup_weather_sub(idx, 0, screen_w, screen_h);
3163            }
3164        }
3165        if cnt > self.weather_work.cnt_max {
3166            self.weather_work.cnt_max = cnt;
3167        }
3168    }
3169
3170    pub fn init_param_like(&mut self) {
3171        self.base = ObjectBaseState::default();
3172        self.button.clear();
3173        self.runtime.explicit_int_props.clear();
3174        self.runtime.explicit_str_props.clear();
3175        self.runtime.prop_events.clear();
3176        self.runtime.prop_lists.clear();
3177        self.runtime.prop_event_lists.clear();
3178        self.frame_action = ObjectFrameActionState::default();
3179        self.frame_action_ch.clear();
3180        self.gan_file = None;
3181        self.gan.reset();
3182    }
3183
3184    pub fn clear_runtime_only(&mut self) {
3185        self.runtime.explicit_int_props.clear();
3186        self.runtime.explicit_str_props.clear();
3187        self.runtime.prop_events.clear();
3188        self.runtime.prop_lists.clear();
3189        self.runtime.prop_event_lists.clear();
3190        self.frame_action = ObjectFrameActionState::default();
3191        self.frame_action_ch.clear();
3192    }
3193
3194    pub fn set_int_prop(
3195        &mut self,
3196        ids: &crate::runtime::constants::RuntimeConstants,
3197        op: i32,
3198        value: i64,
3199    ) {
3200        self.runtime.explicit_int_props.insert(op);
3201        let ok =
3202            self.sync_fixed_int_prop(ids, op, value) || self.sync_special_int_prop(ids, op, value);
3203        assert!(ok, "unknown object int property op {}", op);
3204    }
3205
3206    pub fn set_int_prop_from_event_frame(
3207        &mut self,
3208        ids: &crate::runtime::constants::RuntimeConstants,
3209        op: i32,
3210        value: i64,
3211    ) {
3212        self.runtime.explicit_int_props.insert(op);
3213        macro_rules! set_if {
3214            ($id:expr, $field:ident) => {
3215                if $id != 0 && op == $id {
3216                    self.base.$field = value;
3217                    return;
3218                }
3219            };
3220        }
3221        set_if!(ids.obj_patno, patno);
3222        set_if!(ids.obj_x, x);
3223        set_if!(ids.obj_y, y);
3224        set_if!(ids.obj_z, z);
3225        set_if!(ids.obj_center_x, center_x);
3226        set_if!(ids.obj_center_y, center_y);
3227        set_if!(ids.obj_center_z, center_z);
3228        set_if!(ids.obj_center_rep_x, center_rep_x);
3229        set_if!(ids.obj_center_rep_y, center_rep_y);
3230        set_if!(ids.obj_center_rep_z, center_rep_z);
3231        set_if!(ids.obj_scale_x, scale_x);
3232        set_if!(ids.obj_scale_y, scale_y);
3233        set_if!(ids.obj_scale_z, scale_z);
3234        set_if!(ids.obj_rotate_x, rotate_x);
3235        set_if!(ids.obj_rotate_y, rotate_y);
3236        set_if!(ids.obj_rotate_z, rotate_z);
3237        set_if!(ids.obj_clip_left, clip_left);
3238        set_if!(ids.obj_clip_top, clip_top);
3239        set_if!(ids.obj_clip_right, clip_right);
3240        set_if!(ids.obj_clip_bottom, clip_bottom);
3241        set_if!(ids.obj_src_clip_left, src_clip_left);
3242        set_if!(ids.obj_src_clip_top, src_clip_top);
3243        set_if!(ids.obj_src_clip_right, src_clip_right);
3244        set_if!(ids.obj_src_clip_bottom, src_clip_bottom);
3245        set_if!(ids.obj_alpha, alpha);
3246        set_if!(ids.obj_tr, tr);
3247        set_if!(ids.obj_mono, mono);
3248        set_if!(ids.obj_reverse, reverse);
3249        set_if!(ids.obj_bright, bright);
3250        set_if!(ids.obj_dark, dark);
3251        set_if!(ids.obj_color_r, color_r);
3252        set_if!(ids.obj_color_g, color_g);
3253        set_if!(ids.obj_color_b, color_b);
3254        set_if!(ids.obj_color_rate, color_rate);
3255        set_if!(ids.obj_color_add_r, color_add_r);
3256        set_if!(ids.obj_color_add_g, color_add_g);
3257        set_if!(ids.obj_color_add_b, color_add_b);
3258        assert!(false, "unknown object event-backed int property op {}", op);
3259    }
3260
3261    pub fn has_int_prop(&self, op: i32) -> bool {
3262        self.runtime.explicit_int_props.contains(&op)
3263    }
3264
3265    pub fn remove_int_prop(&mut self, op: i32) {
3266        self.runtime.explicit_int_props.remove(&op);
3267    }
3268
3269    pub fn set_str_prop(
3270        &mut self,
3271        ids: &crate::runtime::constants::RuntimeConstants,
3272        op: i32,
3273        value: String,
3274    ) {
3275        self.runtime.explicit_str_props.insert(op);
3276        let ok = self.sync_special_str_prop(ids, op, value);
3277        assert!(ok, "unknown object string property op {}", op);
3278    }
3279
3280    pub fn lookup_str_prop(
3281        &self,
3282        ids: &crate::runtime::constants::RuntimeConstants,
3283        op: i32,
3284    ) -> Option<String> {
3285        self.special_str_prop(ids, op)
3286    }
3287
3288    pub fn has_str_prop(&self, op: i32) -> bool {
3289        self.runtime.explicit_str_props.contains(&op)
3290    }
3291
3292    pub fn remove_str_prop(&mut self, ids: &crate::runtime::constants::RuntimeConstants, op: i32) {
3293        self.runtime.explicit_str_props.remove(&op);
3294        if ids.obj_mesh_anim_clip_name != 0 && op == ids.obj_mesh_anim_clip_name {
3295            let mut next = self.mesh_animation_state.clone();
3296            next.clip_name = None;
3297            self.set_mesh_animation_state(next);
3298        } else if ids.obj_mesh_anim_blend_clip_name != 0 && op == ids.obj_mesh_anim_blend_clip_name
3299        {
3300            let mut next = self.mesh_animation_state.clone();
3301            next.blend_clip_name = None;
3302            self.set_mesh_animation_state(next);
3303        }
3304    }
3305
3306    pub fn lookup_int_prop(
3307        &self,
3308        ids: &crate::runtime::constants::RuntimeConstants,
3309        op: i32,
3310    ) -> Option<i64> {
3311        self.fixed_int_prop(ids, op)
3312            .or_else(|| self.special_int_prop(ids, op))
3313    }
3314
3315    pub fn get_int_prop(&self, ids: &crate::runtime::constants::RuntimeConstants, op: i32) -> i64 {
3316        self.lookup_int_prop(ids, op).unwrap_or(0)
3317    }
3318
3319    pub fn runtime_slot_or(&self, fallback: usize) -> usize {
3320        self.nested_runtime_slot.unwrap_or(fallback)
3321    }
3322
3323    pub fn ensure_runtime_slot(&mut self, next_slot: &mut usize) -> usize {
3324        if let Some(slot) = self.nested_runtime_slot {
3325            return slot;
3326        }
3327        let slot = *next_slot;
3328        *next_slot += 1;
3329        self.nested_runtime_slot = Some(slot);
3330        slot
3331    }
3332
3333    pub fn int_list_by_op<'a>(
3334        &'a self,
3335        ids: &crate::runtime::constants::RuntimeConstants,
3336        op: i32,
3337    ) -> Option<&'a Vec<i64>> {
3338        if ids.obj_f != 0 && op == ids.obj_f {
3339            Some(&self.runtime.prop_lists.f)
3340        } else {
3341            None
3342        }
3343    }
3344
3345    pub fn int_list_by_op_mut<'a>(
3346        &'a mut self,
3347        ids: &crate::runtime::constants::RuntimeConstants,
3348        op: i32,
3349    ) -> Option<&'a mut Vec<i64>> {
3350        if ids.obj_f != 0 && op == ids.obj_f {
3351            Some(&mut self.runtime.prop_lists.f)
3352        } else {
3353            None
3354        }
3355    }
3356
3357    pub fn rep_int_event_list_by_rep_op<'a>(
3358        &'a self,
3359        ids: &crate::runtime::constants::RuntimeConstants,
3360        op: i32,
3361    ) -> Option<&'a Vec<IntEvent>> {
3362        if ids.obj_x_rep != 0 && op == ids.obj_x_rep {
3363            Some(&self.runtime.prop_event_lists.x_rep)
3364        } else if ids.obj_y_rep != 0 && op == ids.obj_y_rep {
3365            Some(&self.runtime.prop_event_lists.y_rep)
3366        } else if ids.obj_z_rep != 0 && op == ids.obj_z_rep {
3367            Some(&self.runtime.prop_event_lists.z_rep)
3368        } else if ids.obj_tr_rep != 0 && op == ids.obj_tr_rep {
3369            Some(&self.runtime.prop_event_lists.tr_rep)
3370        } else {
3371            None
3372        }
3373    }
3374
3375    pub fn rep_int_event_list_by_rep_op_mut<'a>(
3376        &'a mut self,
3377        ids: &crate::runtime::constants::RuntimeConstants,
3378        op: i32,
3379    ) -> Option<&'a mut Vec<IntEvent>> {
3380        if ids.obj_x_rep != 0 && op == ids.obj_x_rep {
3381            Some(&mut self.runtime.prop_event_lists.x_rep)
3382        } else if ids.obj_y_rep != 0 && op == ids.obj_y_rep {
3383            Some(&mut self.runtime.prop_event_lists.y_rep)
3384        } else if ids.obj_z_rep != 0 && op == ids.obj_z_rep {
3385            Some(&mut self.runtime.prop_event_lists.z_rep)
3386        } else if ids.obj_tr_rep != 0 && op == ids.obj_tr_rep {
3387            Some(&mut self.runtime.prop_event_lists.tr_rep)
3388        } else {
3389            None
3390        }
3391    }
3392
3393    pub fn int_event_by_op<'a>(
3394        &'a self,
3395        ids: &crate::runtime::constants::RuntimeConstants,
3396        op: i32,
3397    ) -> Option<&'a IntEvent> {
3398        self.runtime.prop_events.get(self.event_target(ids, op))
3399    }
3400
3401    pub fn int_event_by_op_mut<'a>(
3402        &'a mut self,
3403        ids: &crate::runtime::constants::RuntimeConstants,
3404        op: i32,
3405    ) -> Option<&'a mut IntEvent> {
3406        let target = self.event_target(ids, op);
3407        self.runtime.prop_events.get_mut(target)
3408    }
3409
3410    pub fn int_event_list_by_op<'a>(
3411        &'a self,
3412        ids: &crate::runtime::constants::RuntimeConstants,
3413        op: i32,
3414    ) -> Option<&'a Vec<IntEvent>> {
3415        if ids.obj_x_rep_eve != 0 && op == ids.obj_x_rep_eve {
3416            Some(&self.runtime.prop_event_lists.x_rep)
3417        } else if ids.obj_y_rep_eve != 0 && op == ids.obj_y_rep_eve {
3418            Some(&self.runtime.prop_event_lists.y_rep)
3419        } else if ids.obj_z_rep_eve != 0 && op == ids.obj_z_rep_eve {
3420            Some(&self.runtime.prop_event_lists.z_rep)
3421        } else if ids.obj_tr_rep_eve != 0 && op == ids.obj_tr_rep_eve {
3422            Some(&self.runtime.prop_event_lists.tr_rep)
3423        } else {
3424            None
3425        }
3426    }
3427
3428    pub fn int_event_list_by_op_mut<'a>(
3429        &'a mut self,
3430        ids: &crate::runtime::constants::RuntimeConstants,
3431        op: i32,
3432    ) -> Option<&'a mut Vec<IntEvent>> {
3433        if ids.obj_x_rep_eve != 0 && op == ids.obj_x_rep_eve {
3434            Some(&mut self.runtime.prop_event_lists.x_rep)
3435        } else if ids.obj_y_rep_eve != 0 && op == ids.obj_y_rep_eve {
3436            Some(&mut self.runtime.prop_event_lists.y_rep)
3437        } else if ids.obj_z_rep_eve != 0 && op == ids.obj_z_rep_eve {
3438            Some(&mut self.runtime.prop_event_lists.z_rep)
3439        } else if ids.obj_tr_rep_eve != 0 && op == ids.obj_tr_rep_eve {
3440            Some(&mut self.runtime.prop_event_lists.tr_rep)
3441        } else {
3442            None
3443        }
3444    }
3445
3446    fn sync_fixed_int_prop(
3447        &mut self,
3448        ids: &crate::runtime::constants::RuntimeConstants,
3449        op: i32,
3450        value: i64,
3451    ) -> bool {
3452        macro_rules! set_if {
3453            ($id:expr, $field:ident) => {
3454                if $id != 0 && op == $id {
3455                    self.base.$field = value;
3456                    self.sync_event_backed_prop_value(ids, op, value);
3457                    return true;
3458                }
3459            };
3460        }
3461        if op == ids.obj_disp {
3462            self.base.disp = value;
3463            return true;
3464        }
3465        set_if!(ids.obj_wipe_copy, wipe_copy);
3466        set_if!(ids.obj_wipe_erase, wipe_erase);
3467        set_if!(ids.obj_click_disable, click_disable);
3468        set_if!(ids.obj_patno, patno);
3469        set_if!(ids.obj_world, world);
3470        set_if!(ids.obj_order, order);
3471        set_if!(ids.obj_layer, layer);
3472        set_if!(ids.obj_x, x);
3473        set_if!(ids.obj_y, y);
3474        set_if!(ids.obj_z, z);
3475        set_if!(ids.obj_center_x, center_x);
3476        set_if!(ids.obj_center_y, center_y);
3477        set_if!(ids.obj_center_z, center_z);
3478        set_if!(ids.obj_center_rep_x, center_rep_x);
3479        set_if!(ids.obj_center_rep_y, center_rep_y);
3480        set_if!(ids.obj_center_rep_z, center_rep_z);
3481        set_if!(ids.obj_scale_x, scale_x);
3482        set_if!(ids.obj_scale_y, scale_y);
3483        set_if!(ids.obj_scale_z, scale_z);
3484        set_if!(ids.obj_rotate_x, rotate_x);
3485        set_if!(ids.obj_rotate_y, rotate_y);
3486        set_if!(ids.obj_rotate_z, rotate_z);
3487        set_if!(ids.obj_clip_use, clip_use);
3488        set_if!(ids.obj_clip_left, clip_left);
3489        set_if!(ids.obj_clip_top, clip_top);
3490        set_if!(ids.obj_clip_right, clip_right);
3491        set_if!(ids.obj_clip_bottom, clip_bottom);
3492        set_if!(ids.obj_src_clip_use, src_clip_use);
3493        set_if!(ids.obj_src_clip_left, src_clip_left);
3494        set_if!(ids.obj_src_clip_top, src_clip_top);
3495        set_if!(ids.obj_src_clip_right, src_clip_right);
3496        set_if!(ids.obj_src_clip_bottom, src_clip_bottom);
3497        set_if!(ids.obj_alpha, alpha);
3498        set_if!(ids.obj_tr, tr);
3499        set_if!(ids.obj_mono, mono);
3500        set_if!(ids.obj_reverse, reverse);
3501        set_if!(ids.obj_bright, bright);
3502        set_if!(ids.obj_dark, dark);
3503        set_if!(ids.obj_color_r, color_r);
3504        set_if!(ids.obj_color_g, color_g);
3505        set_if!(ids.obj_color_b, color_b);
3506        set_if!(ids.obj_color_rate, color_rate);
3507        set_if!(ids.obj_color_add_r, color_add_r);
3508        set_if!(ids.obj_color_add_g, color_add_g);
3509        set_if!(ids.obj_color_add_b, color_add_b);
3510        set_if!(ids.obj_mask_no, mask_no);
3511        set_if!(ids.obj_tonecurve_no, tonecurve_no);
3512        set_if!(ids.obj_light_no, light_no);
3513        set_if!(ids.obj_fog_use, fog_use);
3514        set_if!(ids.obj_culling, culling);
3515        set_if!(ids.obj_alpha_test, alpha_test);
3516        set_if!(ids.obj_alpha_blend, alpha_blend);
3517        set_if!(ids.obj_blend, blend);
3518        false
3519    }
3520
3521    fn sync_special_int_prop(
3522        &mut self,
3523        ids: &crate::runtime::constants::RuntimeConstants,
3524        op: i32,
3525        value: i64,
3526    ) -> bool {
3527        if op == OBJECT_NESTED_SLOT_KEY {
3528            self.nested_runtime_slot = (value >= 0).then_some(value as usize);
3529            return true;
3530        }
3531        if ids.obj_x_rep != 0 && op == ids.obj_x_rep {
3532            if self.runtime.prop_event_lists.x_rep.is_empty() {
3533                self.runtime.prop_event_lists.x_rep.push(IntEvent::new(0));
3534            }
3535            self.runtime.prop_event_lists.x_rep[0].set_value(value as i32);
3536            return true;
3537        }
3538        if ids.obj_y_rep != 0 && op == ids.obj_y_rep {
3539            if self.runtime.prop_event_lists.y_rep.is_empty() {
3540                self.runtime.prop_event_lists.y_rep.push(IntEvent::new(0));
3541            }
3542            self.runtime.prop_event_lists.y_rep[0].set_value(value as i32);
3543            return true;
3544        }
3545        if ids.obj_z_rep != 0 && op == ids.obj_z_rep {
3546            if self.runtime.prop_event_lists.z_rep.is_empty() {
3547                self.runtime.prop_event_lists.z_rep.push(IntEvent::new(0));
3548            }
3549            self.runtime.prop_event_lists.z_rep[0].set_value(value as i32);
3550            return true;
3551        }
3552        if ids.obj_tr_rep != 0 && op == ids.obj_tr_rep {
3553            if self.runtime.prop_event_lists.tr_rep.is_empty() {
3554                self.runtime
3555                    .prop_event_lists
3556                    .tr_rep
3557                    .push(IntEvent::new(255));
3558            }
3559            self.runtime.prop_event_lists.tr_rep[0].set_value(value as i32);
3560            return true;
3561        }
3562        if ids.obj_mesh_anim_clip != 0 && op == ids.obj_mesh_anim_clip {
3563            let mut next = self.mesh_animation_state.clone();
3564            next.change_animation_clip(None, (value >= 0).then_some(value as usize));
3565            self.set_mesh_animation_state(next);
3566            return true;
3567        }
3568        if ids.obj_mesh_anim_rate != 0 && op == ids.obj_mesh_anim_rate {
3569            let mut next = self.mesh_animation_state.clone();
3570            next.rate = (value as f32) / 1000.0;
3571            self.set_mesh_animation_state(next);
3572            return true;
3573        }
3574        if ids.obj_mesh_anim_time_offset != 0 && op == ids.obj_mesh_anim_time_offset {
3575            let mut next = self.mesh_animation_state.clone();
3576            next.time_offset_sec = (value as f32) / 1000.0;
3577            self.set_mesh_animation_state(next);
3578            return true;
3579        }
3580        if ids.obj_mesh_anim_pause != 0 && op == ids.obj_mesh_anim_pause {
3581            let mut next = self.mesh_animation_state.clone();
3582            next.paused = value != 0;
3583            next.is_anim = !next.paused;
3584            self.set_mesh_animation_state(next);
3585            return true;
3586        }
3587        if ids.obj_mesh_anim_hold_time != 0 && op == ids.obj_mesh_anim_hold_time {
3588            let mut next = self.mesh_animation_state.clone();
3589            next.hold_time_sec = ((value as f32) / 1000.0).max(0.0);
3590            next.time_sec = if next.rate > 0.0 {
3591                next.hold_time_sec / next.rate.max(0.000_001)
3592            } else {
3593                0.0
3594            };
3595            self.set_mesh_animation_state(next);
3596            return true;
3597        }
3598        if ids.obj_mesh_anim_shift_time != 0 && op == ids.obj_mesh_anim_shift_time {
3599            let mut next = self.mesh_animation_state.clone();
3600            next.set_anim_shift_time_sec(((value as f32) / 1000.0).max(0.0));
3601            self.set_mesh_animation_state(next);
3602            return true;
3603        }
3604        if ids.obj_mesh_anim_loop != 0 && op == ids.obj_mesh_anim_loop {
3605            let mut next = self.mesh_animation_state.clone();
3606            next.looped = value != 0;
3607            self.set_mesh_animation_state(next);
3608            return true;
3609        }
3610        if ids.obj_mesh_anim_blend_clip != 0 && op == ids.obj_mesh_anim_blend_clip {
3611            let mut next = self.mesh_animation_state.clone();
3612            next.blend_clip_index = (value >= 0).then_some(value as usize);
3613            next.blend_clip_name = None;
3614            self.set_mesh_animation_state(next);
3615            return true;
3616        }
3617        if ids.obj_mesh_anim_blend_weight != 0 && op == ids.obj_mesh_anim_blend_weight {
3618            let mut next = self.mesh_animation_state.clone();
3619            next.blend_weight = ((value as f32) / 1000.0).clamp(0.0, 1.0);
3620            self.set_mesh_animation_state(next);
3621            return true;
3622        }
3623        false
3624    }
3625
3626    fn special_int_prop(
3627        &self,
3628        ids: &crate::runtime::constants::RuntimeConstants,
3629        op: i32,
3630    ) -> Option<i64> {
3631        if op == OBJECT_NESTED_SLOT_KEY {
3632            return self.nested_runtime_slot.map(|v| v as i64);
3633        }
3634        if ids.obj_x_rep != 0 && op == ids.obj_x_rep {
3635            return self
3636                .runtime
3637                .prop_event_lists
3638                .x_rep
3639                .first()
3640                .map(|ev| ev.get_value() as i64);
3641        }
3642        if ids.obj_y_rep != 0 && op == ids.obj_y_rep {
3643            return self
3644                .runtime
3645                .prop_event_lists
3646                .y_rep
3647                .first()
3648                .map(|ev| ev.get_value() as i64);
3649        }
3650        if ids.obj_z_rep != 0 && op == ids.obj_z_rep {
3651            return self
3652                .runtime
3653                .prop_event_lists
3654                .z_rep
3655                .first()
3656                .map(|ev| ev.get_value() as i64);
3657        }
3658        if ids.obj_tr_rep != 0 && op == ids.obj_tr_rep {
3659            return self
3660                .runtime
3661                .prop_event_lists
3662                .tr_rep
3663                .first()
3664                .map(|ev| ev.get_value() as i64);
3665        }
3666        if ids.obj_mesh_anim_clip != 0 && op == ids.obj_mesh_anim_clip {
3667            return Some(
3668                self.mesh_animation_state
3669                    .clip_index
3670                    .map(|v| v as i64)
3671                    .unwrap_or(-1),
3672            );
3673        }
3674        if ids.obj_mesh_anim_rate != 0 && op == ids.obj_mesh_anim_rate {
3675            return Some((self.mesh_animation_state.rate * 1000.0).round() as i64);
3676        }
3677        if ids.obj_mesh_anim_time_offset != 0 && op == ids.obj_mesh_anim_time_offset {
3678            return Some((self.mesh_animation_state.time_offset_sec * 1000.0).round() as i64);
3679        }
3680        if ids.obj_mesh_anim_pause != 0 && op == ids.obj_mesh_anim_pause {
3681            return Some(if self.mesh_animation_state.paused {
3682                1
3683            } else {
3684                0
3685            });
3686        }
3687        if ids.obj_mesh_anim_hold_time != 0 && op == ids.obj_mesh_anim_hold_time {
3688            return Some((self.mesh_animation_state.hold_time_sec * 1000.0).round() as i64);
3689        }
3690        if ids.obj_mesh_anim_shift_time != 0 && op == ids.obj_mesh_anim_shift_time {
3691            return Some((self.mesh_animation_state.anim_shift_time_sec * 1000.0).round() as i64);
3692        }
3693        if ids.obj_mesh_anim_loop != 0 && op == ids.obj_mesh_anim_loop {
3694            return Some(if self.mesh_animation_state.looped {
3695                1
3696            } else {
3697                0
3698            });
3699        }
3700        if ids.obj_mesh_anim_blend_clip != 0 && op == ids.obj_mesh_anim_blend_clip {
3701            return Some(
3702                self.mesh_animation_state
3703                    .blend_clip_index
3704                    .map(|v| v as i64)
3705                    .unwrap_or(-1),
3706            );
3707        }
3708        if ids.obj_mesh_anim_blend_weight != 0 && op == ids.obj_mesh_anim_blend_weight {
3709            return Some((self.mesh_animation_state.blend_weight * 1000.0).round() as i64);
3710        }
3711        None
3712    }
3713
3714    fn sync_special_str_prop(
3715        &mut self,
3716        ids: &crate::runtime::constants::RuntimeConstants,
3717        op: i32,
3718        value: String,
3719    ) -> bool {
3720        if ids.obj_mesh_anim_clip_name != 0 && op == ids.obj_mesh_anim_clip_name {
3721            let mut next = self.mesh_animation_state.clone();
3722            next.change_animation_clip(Some(value), None);
3723            self.set_mesh_animation_state(next);
3724            return true;
3725        }
3726        if ids.obj_mesh_anim_blend_clip_name != 0 && op == ids.obj_mesh_anim_blend_clip_name {
3727            let mut next = self.mesh_animation_state.clone();
3728            next.blend_clip_name = Some(value);
3729            next.blend_clip_index = None;
3730            self.set_mesh_animation_state(next);
3731            return true;
3732        }
3733        false
3734    }
3735
3736    fn special_str_prop(
3737        &self,
3738        ids: &crate::runtime::constants::RuntimeConstants,
3739        op: i32,
3740    ) -> Option<String> {
3741        if ids.obj_mesh_anim_clip_name != 0 && op == ids.obj_mesh_anim_clip_name {
3742            return self.mesh_animation_state.clip_name.clone();
3743        }
3744        if ids.obj_mesh_anim_blend_clip_name != 0 && op == ids.obj_mesh_anim_blend_clip_name {
3745            return self.mesh_animation_state.blend_clip_name.clone();
3746        }
3747        None
3748    }
3749
3750    fn fixed_int_prop(
3751        &self,
3752        ids: &crate::runtime::constants::RuntimeConstants,
3753        op: i32,
3754    ) -> Option<i64> {
3755        macro_rules! get_base_if {
3756            ($id:expr, $field:ident) => {
3757                if $id != 0 && op == $id {
3758                    return Some(self.base.$field);
3759                }
3760            };
3761        }
3762        macro_rules! get_event_total_if {
3763            ($id:expr, $target:expr) => {
3764                if $id != 0 && op == $id {
3765                    return self
3766                        .runtime
3767                        .prop_events
3768                        .get($target)
3769                        .map(|ev| ev.get_total_value() as i64);
3770                }
3771            };
3772        }
3773        if op == ids.obj_disp {
3774            return Some(self.base.disp);
3775        }
3776        get_base_if!(ids.obj_wipe_copy, wipe_copy);
3777        get_base_if!(ids.obj_wipe_erase, wipe_erase);
3778        get_base_if!(ids.obj_click_disable, click_disable);
3779        get_event_total_if!(ids.obj_patno, ObjectEventTarget::Patno);
3780        get_base_if!(ids.obj_world, world);
3781        get_base_if!(ids.obj_order, order);
3782        get_base_if!(ids.obj_layer, layer);
3783        get_event_total_if!(ids.obj_x, ObjectEventTarget::X);
3784        get_event_total_if!(ids.obj_y, ObjectEventTarget::Y);
3785        get_event_total_if!(ids.obj_z, ObjectEventTarget::Z);
3786        get_event_total_if!(ids.obj_center_x, ObjectEventTarget::CenterX);
3787        get_event_total_if!(ids.obj_center_y, ObjectEventTarget::CenterY);
3788        get_event_total_if!(ids.obj_center_z, ObjectEventTarget::CenterZ);
3789        get_event_total_if!(ids.obj_center_rep_x, ObjectEventTarget::CenterRepX);
3790        get_event_total_if!(ids.obj_center_rep_y, ObjectEventTarget::CenterRepY);
3791        get_event_total_if!(ids.obj_center_rep_z, ObjectEventTarget::CenterRepZ);
3792        get_event_total_if!(ids.obj_scale_x, ObjectEventTarget::ScaleX);
3793        get_event_total_if!(ids.obj_scale_y, ObjectEventTarget::ScaleY);
3794        get_event_total_if!(ids.obj_scale_z, ObjectEventTarget::ScaleZ);
3795        get_event_total_if!(ids.obj_rotate_x, ObjectEventTarget::RotateX);
3796        get_event_total_if!(ids.obj_rotate_y, ObjectEventTarget::RotateY);
3797        get_event_total_if!(ids.obj_rotate_z, ObjectEventTarget::RotateZ);
3798        get_base_if!(ids.obj_clip_use, clip_use);
3799        get_event_total_if!(ids.obj_clip_left, ObjectEventTarget::ClipLeft);
3800        get_event_total_if!(ids.obj_clip_top, ObjectEventTarget::ClipTop);
3801        get_event_total_if!(ids.obj_clip_right, ObjectEventTarget::ClipRight);
3802        get_event_total_if!(ids.obj_clip_bottom, ObjectEventTarget::ClipBottom);
3803        get_base_if!(ids.obj_src_clip_use, src_clip_use);
3804        get_event_total_if!(ids.obj_src_clip_left, ObjectEventTarget::SrcClipLeft);
3805        get_event_total_if!(ids.obj_src_clip_top, ObjectEventTarget::SrcClipTop);
3806        get_event_total_if!(ids.obj_src_clip_right, ObjectEventTarget::SrcClipRight);
3807        get_event_total_if!(ids.obj_src_clip_bottom, ObjectEventTarget::SrcClipBottom);
3808        get_base_if!(ids.obj_alpha, alpha);
3809        get_event_total_if!(ids.obj_tr, ObjectEventTarget::Tr);
3810        get_event_total_if!(ids.obj_mono, ObjectEventTarget::Mono);
3811        get_event_total_if!(ids.obj_reverse, ObjectEventTarget::Reverse);
3812        get_event_total_if!(ids.obj_bright, ObjectEventTarget::Bright);
3813        get_event_total_if!(ids.obj_dark, ObjectEventTarget::Dark);
3814        get_event_total_if!(ids.obj_color_r, ObjectEventTarget::ColorR);
3815        get_event_total_if!(ids.obj_color_g, ObjectEventTarget::ColorG);
3816        get_event_total_if!(ids.obj_color_b, ObjectEventTarget::ColorB);
3817        get_event_total_if!(ids.obj_color_rate, ObjectEventTarget::ColorRate);
3818        get_event_total_if!(ids.obj_color_add_r, ObjectEventTarget::ColorAddR);
3819        get_event_total_if!(ids.obj_color_add_g, ObjectEventTarget::ColorAddG);
3820        get_event_total_if!(ids.obj_color_add_b, ObjectEventTarget::ColorAddB);
3821        get_base_if!(ids.obj_mask_no, mask_no);
3822        get_base_if!(ids.obj_tonecurve_no, tonecurve_no);
3823        get_base_if!(ids.obj_light_no, light_no);
3824        get_base_if!(ids.obj_fog_use, fog_use);
3825        get_base_if!(ids.obj_culling, culling);
3826        get_base_if!(ids.obj_alpha_test, alpha_test);
3827        get_base_if!(ids.obj_alpha_blend, alpha_blend);
3828        get_base_if!(ids.obj_blend, blend);
3829        None
3830    }
3831
3832    pub fn set_mesh_animation_state(&mut self, next: crate::mesh3d::MeshAnimationState) {
3833        self.apply_mesh_animation_state(next, None);
3834    }
3835
3836    fn apply_mesh_animation_state(
3837        &mut self,
3838        next: crate::mesh3d::MeshAnimationState,
3839        explicit_hold_override: Option<f32>,
3840    ) {
3841        let prev = self.mesh_animation_state.clone();
3842        let mut merged = next.sanitized();
3843        let clip_changed =
3844            prev.clip_name != merged.clip_name || prev.clip_index != merged.clip_index;
3845        let pause_enter = !prev.paused && merged.paused;
3846        let pause_exit = prev.paused && !merged.paused;
3847        let prev_base = prev.current_sample_base_sec();
3848
3849        merged.anim_track_no = prev.anim_track_no;
3850        merged.is_anim = !merged.paused;
3851        merged.time_sec = prev.time_sec.max(0.0);
3852        merged.hold_time_sec = prev.hold_time_sec.max(0.0);
3853        merged.prev_clip_name = prev.prev_clip_name.clone();
3854        merged.prev_clip_index = prev.prev_clip_index;
3855        merged.prev_time_sec = prev.prev_time_sec.max(0.0);
3856        merged.prev_time_offset_sec = prev.prev_time_offset_sec.max(0.0);
3857        merged.prev_rate = prev.prev_rate.max(0.0);
3858        merged.transition_elapsed_sec = prev.transition_elapsed_sec.max(0.0);
3859
3860        if clip_changed {
3861            merged.change_animation_clip(merged.clip_name.clone(), merged.clip_index);
3862        }
3863
3864        if let Some(hold_sec) = explicit_hold_override {
3865            let hold_sec = hold_sec.max(0.0);
3866            merged.hold_time_sec = hold_sec;
3867            merged.time_sec = if merged.rate > 0.0 {
3868                hold_sec / merged.rate.max(0.000_001)
3869            } else {
3870                0.0
3871            };
3872        } else if pause_enter {
3873            merged.hold_time_sec = prev_base;
3874        } else if pause_exit {
3875            merged.time_sec = if merged.rate > 0.0 {
3876                prev.hold_time_sec.max(0.0) / merged.rate.max(0.000_001)
3877            } else {
3878                prev.time_sec.max(0.0)
3879            };
3880        } else if merged.paused {
3881            merged.hold_time_sec = prev.hold_time_sec.max(0.0);
3882        } else if !clip_changed && (prev.rate - merged.rate).abs() > 0.000_001 {
3883            merged.time_sec = if merged.rate > 0.0 {
3884                prev_base / merged.rate.max(0.000_001)
3885            } else {
3886                prev.time_sec.max(0.0)
3887            };
3888        }
3889
3890        self.mesh_animation_state = merged.sanitized();
3891    }
3892
3893    pub fn sync_mesh_animation_state_from_props(
3894        &mut self,
3895        ids: &super::constants::RuntimeConstants,
3896    ) {
3897        let int_prop = |id: i32, default: i64| -> i64 {
3898            if id != 0 {
3899                self.lookup_int_prop(ids, id).unwrap_or(default)
3900            } else {
3901                default
3902            }
3903        };
3904        let str_prop = |id: i32| -> Option<String> {
3905            if id != 0 {
3906                self.lookup_str_prop(ids, id)
3907            } else {
3908                None
3909            }
3910        };
3911        let explicit_hold =
3912            ids.obj_mesh_anim_hold_time != 0 && self.has_int_prop(ids.obj_mesh_anim_hold_time);
3913        let requested_hold_sec = (int_prop(ids.obj_mesh_anim_hold_time, 0) as f32) / 1000.0;
3914        let requested_shift_sec = (int_prop(
3915            ids.obj_mesh_anim_shift_time,
3916            (self.mesh_animation_state.anim_shift_time_sec * 1000.0).round() as i64,
3917        ) as f32)
3918            / 1000.0;
3919        let next = crate::mesh3d::MeshAnimationState {
3920            clip_name: str_prop(ids.obj_mesh_anim_clip_name),
3921            clip_index: (int_prop(ids.obj_mesh_anim_clip, -1) >= 0).then_some(int_prop(
3922                ids.obj_mesh_anim_clip,
3923                -1,
3924            )
3925                as usize),
3926            blend_clip_name: str_prop(ids.obj_mesh_anim_blend_clip_name),
3927            blend_clip_index: (int_prop(ids.obj_mesh_anim_blend_clip, -1) >= 0)
3928                .then_some(int_prop(ids.obj_mesh_anim_blend_clip, -1) as usize),
3929            blend_weight: ((int_prop(ids.obj_mesh_anim_blend_weight, 0) as f32) / 1000.0)
3930                .clamp(0.0, 1.0),
3931            time_sec: self.mesh_animation_state.time_sec,
3932            rate: (int_prop(ids.obj_mesh_anim_rate, 1000) as f32) / 1000.0,
3933            time_offset_sec: (int_prop(ids.obj_mesh_anim_time_offset, 0) as f32) / 1000.0,
3934            hold_time_sec: if explicit_hold {
3935                requested_hold_sec
3936            } else {
3937                self.mesh_animation_state.hold_time_sec
3938            },
3939            paused: int_prop(ids.obj_mesh_anim_pause, 0) != 0,
3940            looped: int_prop(ids.obj_mesh_anim_loop, 1) != 0,
3941            anim_track_no: self.mesh_animation_state.anim_track_no,
3942            anim_shift_time_sec: requested_shift_sec.max(0.0),
3943            is_anim: !(int_prop(ids.obj_mesh_anim_pause, 0) != 0),
3944            prev_clip_name: self.mesh_animation_state.prev_clip_name.clone(),
3945            prev_clip_index: self.mesh_animation_state.prev_clip_index,
3946            prev_time_sec: self.mesh_animation_state.prev_time_sec,
3947            prev_time_offset_sec: self.mesh_animation_state.prev_time_offset_sec,
3948            prev_rate: self.mesh_animation_state.prev_rate,
3949            transition_elapsed_sec: self.mesh_animation_state.transition_elapsed_sec,
3950        };
3951        self.apply_mesh_animation_state(next, explicit_hold.then_some(requested_hold_sec));
3952    }
3953
3954    pub fn uses_mesh_animation_bridge_op(
3955        ids: &super::constants::RuntimeConstants,
3956        op: i32,
3957    ) -> bool {
3958        [
3959            ids.obj_mesh_anim_clip,
3960            ids.obj_mesh_anim_clip_name,
3961            ids.obj_mesh_anim_rate,
3962            ids.obj_mesh_anim_time_offset,
3963            ids.obj_mesh_anim_pause,
3964            ids.obj_mesh_anim_hold_time,
3965            ids.obj_mesh_anim_shift_time,
3966            ids.obj_mesh_anim_loop,
3967            ids.obj_mesh_anim_blend_clip,
3968            ids.obj_mesh_anim_blend_clip_name,
3969            ids.obj_mesh_anim_blend_weight,
3970        ]
3971        .into_iter()
3972        .any(|id| id != 0 && op == id)
3973    }
3974
3975    pub fn tick(&mut self, past_game_time: i32, past_real_time: i32) {
3976        let delta = past_game_time.max(0);
3977        self.runtime
3978            .prop_events
3979            .update_time(past_game_time, past_real_time);
3980        self.runtime.prop_events.frame();
3981        self.runtime
3982            .prop_event_lists
3983            .update_time(past_game_time, past_real_time);
3984        self.runtime.prop_event_lists.frame();
3985        self.frame_action
3986            .counter
3987            .update_time(past_game_time, past_real_time);
3988        for fa in &mut self.frame_action_ch {
3989            fa.counter.update_time(past_game_time, past_real_time);
3990        }
3991        for child in &mut self.runtime.child_objects {
3992            child.tick(past_game_time, past_real_time);
3993        }
3994        self.movie.tick(past_game_time, past_real_time);
3995        self.gan.update_time(past_game_time, past_real_time);
3996        if matches!(self.object_type, 6 | 7) {
3997            self.mesh_animation_state.advance_controller_frames(delta);
3998        }
3999
4000        if self.object_type == 9 && self.movie.just_finished && !self.movie.auto_free_flag {
4001            self.movie.pause_flag = true;
4002        }
4003    }
4004
4005    pub fn update_weather_time(
4006        &mut self,
4007        past_game_time: i32,
4008        past_real_time: i32,
4009        screen_w: i64,
4010        screen_h: i64,
4011    ) {
4012        if self.object_type != 4 || !matches!(self.weather_param.weather_type, 1 | 2) {
4013            return;
4014        }
4015        let cnt = self.weather_param.cnt.max(0) as usize;
4016        let cnt_max = self.weather_work.cnt_max.min(self.weather_work.sub.len());
4017        for idx in 0..cnt_max {
4018            let mut setup_after_sleep = false;
4019            {
4020                let sub = &mut self.weather_work.sub[idx];
4021                if idx >= cnt && sub.state == 0 {
4022                    continue;
4023                }
4024                let past_time = if sub.real_time_flag {
4025                    past_real_time.max(0) as i64
4026                } else {
4027                    past_game_time.max(0) as i64
4028                };
4029                sub.state_cur_time = sub.state_cur_time.saturating_add(past_time);
4030                sub.move_cur_time = sub.move_cur_time.saturating_add(past_time);
4031                sub.sin_cur_time = sub.sin_cur_time.saturating_add(past_time);
4032
4033                if (idx >= cnt || sub.restruct_flag)
4034                    && sub.state == 2
4035                    && sub.state_time_len - sub.state_cur_time >= 3000
4036                {
4037                    sub.state_cur_time = sub.state_time_len.saturating_sub(1500);
4038                }
4039
4040                while sub.state_cur_time - sub.state_time_len > 0 {
4041                    let amari_time = sub.state_cur_time - sub.state_time_len;
4042                    sub.state = (sub.state + 1) % 4;
4043                    if sub.state == 0 {
4044                        if idx >= cnt {
4045                            break;
4046                        }
4047                        setup_after_sleep = true;
4048                        break;
4049                    }
4050                    if sub.state == 1 {
4051                        sub.move_cur_time = amari_time;
4052                    }
4053                    sub.state_time_len = match sub.state {
4054                        1 => 1000,
4055                        2 => sub.active_time_len,
4056                        3 => 1000,
4057                        _ => sub.state_time_len,
4058                    };
4059                    sub.state_cur_time = amari_time;
4060                }
4061            }
4062            if setup_after_sleep {
4063                self.setup_weather_sub(idx, 1, screen_w, screen_h);
4064            }
4065        }
4066    }
4067
4068    pub fn any_event_active(&self) -> bool {
4069        self.runtime.prop_events.any_active() || self.runtime.prop_event_lists.any_active()
4070    }
4071
4072    pub fn end_all_events(&mut self) {
4073        self.runtime.prop_events.end_all();
4074        self.runtime.prop_event_lists.end_all();
4075    }
4076
4077    pub fn event_target(
4078        &self,
4079        ids: &super::constants::RuntimeConstants,
4080        op: i32,
4081    ) -> ObjectEventTarget {
4082        if ids.obj_x_eve != 0 && op == ids.obj_x_eve {
4083            ObjectEventTarget::X
4084        } else if ids.obj_y_eve != 0 && op == ids.obj_y_eve {
4085            ObjectEventTarget::Y
4086        } else if ids.obj_x_rep_eve != 0 && op == ids.obj_x_rep_eve {
4087            ObjectEventTarget::XRep
4088        } else if ids.obj_y_rep_eve != 0 && op == ids.obj_y_rep_eve {
4089            ObjectEventTarget::YRep
4090        } else if ids.obj_z_rep_eve != 0 && op == ids.obj_z_rep_eve {
4091            ObjectEventTarget::ZRep
4092        } else if ids.obj_tr_eve != 0 && op == ids.obj_tr_eve {
4093            ObjectEventTarget::Tr
4094        } else if ids.obj_tr_rep_eve != 0 && op == ids.obj_tr_rep_eve {
4095            ObjectEventTarget::TrRep
4096        } else if ids.obj_patno_eve != 0 && op == ids.obj_patno_eve {
4097            ObjectEventTarget::Patno
4098        } else if ids.obj_z_eve != 0 && op == ids.obj_z_eve {
4099            ObjectEventTarget::Z
4100        } else if ids.obj_center_x_eve != 0 && op == ids.obj_center_x_eve {
4101            ObjectEventTarget::CenterX
4102        } else if ids.obj_center_y_eve != 0 && op == ids.obj_center_y_eve {
4103            ObjectEventTarget::CenterY
4104        } else if ids.obj_center_z_eve != 0 && op == ids.obj_center_z_eve {
4105            ObjectEventTarget::CenterZ
4106        } else if ids.obj_center_rep_x_eve != 0 && op == ids.obj_center_rep_x_eve {
4107            ObjectEventTarget::CenterRepX
4108        } else if ids.obj_center_rep_y_eve != 0 && op == ids.obj_center_rep_y_eve {
4109            ObjectEventTarget::CenterRepY
4110        } else if ids.obj_center_rep_z_eve != 0 && op == ids.obj_center_rep_z_eve {
4111            ObjectEventTarget::CenterRepZ
4112        } else if ids.obj_scale_x_eve != 0 && op == ids.obj_scale_x_eve {
4113            ObjectEventTarget::ScaleX
4114        } else if ids.obj_scale_y_eve != 0 && op == ids.obj_scale_y_eve {
4115            ObjectEventTarget::ScaleY
4116        } else if ids.obj_scale_z_eve != 0 && op == ids.obj_scale_z_eve {
4117            ObjectEventTarget::ScaleZ
4118        } else if ids.obj_rotate_x_eve != 0 && op == ids.obj_rotate_x_eve {
4119            ObjectEventTarget::RotateX
4120        } else if ids.obj_rotate_y_eve != 0 && op == ids.obj_rotate_y_eve {
4121            ObjectEventTarget::RotateY
4122        } else if ids.obj_rotate_z_eve != 0 && op == ids.obj_rotate_z_eve {
4123            ObjectEventTarget::RotateZ
4124        } else if ids.obj_clip_left_eve != 0 && op == ids.obj_clip_left_eve {
4125            ObjectEventTarget::ClipLeft
4126        } else if ids.obj_clip_top_eve != 0 && op == ids.obj_clip_top_eve {
4127            ObjectEventTarget::ClipTop
4128        } else if ids.obj_clip_right_eve != 0 && op == ids.obj_clip_right_eve {
4129            ObjectEventTarget::ClipRight
4130        } else if ids.obj_clip_bottom_eve != 0 && op == ids.obj_clip_bottom_eve {
4131            ObjectEventTarget::ClipBottom
4132        } else if ids.obj_src_clip_left_eve != 0 && op == ids.obj_src_clip_left_eve {
4133            ObjectEventTarget::SrcClipLeft
4134        } else if ids.obj_src_clip_top_eve != 0 && op == ids.obj_src_clip_top_eve {
4135            ObjectEventTarget::SrcClipTop
4136        } else if ids.obj_src_clip_right_eve != 0 && op == ids.obj_src_clip_right_eve {
4137            ObjectEventTarget::SrcClipRight
4138        } else if ids.obj_src_clip_bottom_eve != 0 && op == ids.obj_src_clip_bottom_eve {
4139            ObjectEventTarget::SrcClipBottom
4140        } else if ids.obj_mono_eve != 0 && op == ids.obj_mono_eve {
4141            ObjectEventTarget::Mono
4142        } else if ids.obj_reverse_eve != 0 && op == ids.obj_reverse_eve {
4143            ObjectEventTarget::Reverse
4144        } else if ids.obj_bright_eve != 0 && op == ids.obj_bright_eve {
4145            ObjectEventTarget::Bright
4146        } else if ids.obj_dark_eve != 0 && op == ids.obj_dark_eve {
4147            ObjectEventTarget::Dark
4148        } else if ids.obj_color_rate_eve != 0 && op == ids.obj_color_rate_eve {
4149            ObjectEventTarget::ColorRate
4150        } else if ids.obj_color_add_r_eve != 0 && op == ids.obj_color_add_r_eve {
4151            ObjectEventTarget::ColorAddR
4152        } else if ids.obj_color_add_g_eve != 0 && op == ids.obj_color_add_g_eve {
4153            ObjectEventTarget::ColorAddG
4154        } else if ids.obj_color_add_b_eve != 0 && op == ids.obj_color_add_b_eve {
4155            ObjectEventTarget::ColorAddB
4156        } else if ids.obj_color_r_eve != 0 && op == ids.obj_color_r_eve {
4157            ObjectEventTarget::ColorR
4158        } else if ids.obj_color_g_eve != 0 && op == ids.obj_color_g_eve {
4159            ObjectEventTarget::ColorG
4160        } else if ids.obj_color_b_eve != 0 && op == ids.obj_color_b_eve {
4161            ObjectEventTarget::ColorB
4162        } else {
4163            ObjectEventTarget::Unknown
4164        }
4165    }
4166}
4167
4168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4169pub enum GroupListOpKind {
4170    Alloc,
4171    Free,
4172    Unknown,
4173}
4174
4175#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4176pub enum GroupOpKind {
4177    Sel,
4178    SelCancel,
4179    Init,
4180    Start,
4181    StartCancel,
4182    End,
4183    GetHitNo,
4184    GetPushedNo,
4185    GetDecidedNo,
4186    GetResult,
4187    GetResultButtonNo,
4188    Order,
4189    Layer,
4190    CancelPriority,
4191    Unknown,
4192}
4193
4194#[derive(Debug, Clone)]
4195pub struct GroupState {
4196    pub wait_flag: bool,
4197    pub cancel_flag: bool,
4198    pub cancel_se_no: i64,
4199    pub started: bool,
4200
4201    pub hit_button_no: i64,
4202    pub pushed_button_no: i64,
4203    pub decided_button_no: i64,
4204    pub hit_runtime_slot: Option<usize>,
4205    pub pushed_runtime_slot: Option<usize>,
4206
4207    pub result: i64,
4208    pub result_button_no: i64,
4209
4210    pub order: i64,
4211    pub layer: i64,
4212    pub cancel_priority: i64,
4213    pub props: HashMap<i32, i64>,
4214    pub aux_str_props: HashMap<i32, String>,
4215}
4216
4217impl Default for GroupState {
4218    fn default() -> Self {
4219        let mut state = Self {
4220            wait_flag: false,
4221            cancel_flag: false,
4222            cancel_se_no: -1,
4223            started: false,
4224            hit_button_no: -1,
4225            pushed_button_no: -1,
4226            decided_button_no: TNM_GROUP_NOT_DECIDED,
4227            hit_runtime_slot: None,
4228            pushed_runtime_slot: None,
4229            result: TNM_GROUP_RESULT_NONE,
4230            result_button_no: 0,
4231            order: 0,
4232            layer: 0,
4233            cancel_priority: 0,
4234            props: HashMap::new(),
4235            aux_str_props: HashMap::new(),
4236        };
4237        state.reinit();
4238        state
4239    }
4240}
4241
4242pub const TNM_GROUP_NOT_DECIDED: i64 = -2;
4243pub const TNM_GROUP_CANCELED: i64 = -1;
4244pub const TNM_GROUP_RESULT_DECIDED: i64 = 1;
4245pub const TNM_GROUP_RESULT_NONE: i64 = 0;
4246pub const TNM_GROUP_RESULT_CANCELLED: i64 = -1;
4247
4248impl GroupState {
4249    pub fn reinit(&mut self) {
4250        self.order = 0;
4251        self.layer = 0;
4252        self.cancel_priority = 0;
4253        self.cancel_se_no = -1;
4254        self.decided_button_no = TNM_GROUP_NOT_DECIDED;
4255        self.result = TNM_GROUP_RESULT_NONE;
4256        self.result_button_no = 0;
4257        self.started = false;
4258        self.wait_flag = false;
4259        self.cancel_flag = false;
4260        self.hit_button_no = -1;
4261        self.pushed_button_no = -1;
4262        self.hit_runtime_slot = None;
4263        self.pushed_runtime_slot = None;
4264    }
4265
4266    pub fn init_sel(&mut self) {
4267        self.cancel_priority = 0;
4268        self.cancel_se_no = -1;
4269        self.decided_button_no = TNM_GROUP_NOT_DECIDED;
4270        self.result = TNM_GROUP_RESULT_NONE;
4271        self.result_button_no = 0;
4272        self.started = false;
4273        self.wait_flag = false;
4274        self.cancel_flag = false;
4275        self.hit_button_no = -1;
4276        self.pushed_button_no = -1;
4277        self.hit_runtime_slot = None;
4278        self.pushed_runtime_slot = None;
4279    }
4280
4281    pub fn start(&mut self) {
4282        self.started = true;
4283        self.decided_button_no = TNM_GROUP_NOT_DECIDED;
4284    }
4285
4286    pub fn end(&mut self) {
4287        self.started = false;
4288        self.decided_button_no = TNM_GROUP_NOT_DECIDED;
4289    }
4290
4291    pub fn decide(&mut self, button_no: i64) -> bool {
4292        if !self.started {
4293            return false;
4294        }
4295        self.started = false;
4296        self.decided_button_no = button_no;
4297        self.result = TNM_GROUP_RESULT_DECIDED;
4298        self.result_button_no = button_no;
4299        self.hit_button_no = -1;
4300        self.pushed_button_no = -1;
4301        self.hit_runtime_slot = None;
4302        self.pushed_runtime_slot = None;
4303        true
4304    }
4305
4306    pub fn cancel(&mut self) -> Option<i64> {
4307        if !self.started {
4308            return None;
4309        }
4310        let hit_button_no = self.hit_button_no;
4311        self.started = false;
4312        self.decided_button_no = TNM_GROUP_CANCELED;
4313        self.result = TNM_GROUP_RESULT_CANCELLED;
4314        self.result_button_no = hit_button_no;
4315        self.hit_button_no = -1;
4316        self.pushed_button_no = -1;
4317        self.hit_runtime_slot = None;
4318        self.pushed_runtime_slot = None;
4319        Some(hit_button_no)
4320    }
4321}
4322
4323#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4324pub enum MwndListOpKind {
4325    CloseAll,
4326    CloseAllWait,
4327    CloseAllNowait,
4328    Unknown,
4329}
4330
4331#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4332pub enum MwndOpKind {
4333    MsgBlock,
4334    OpenWait,
4335    OpenNowait,
4336    CloseWait,
4337    CloseNowait,
4338    EndClose,
4339    CheckOpen,
4340    Clear,
4341    NovelClear,
4342    /// Append text to the current message buffer.
4343    Print,
4344    /// NL: line break without preserving indent.
4345    NewLineNoIndent,
4346    /// NLI: line break with indent path preserved.
4347    NewLineIndent,
4348    /// Wait for input while in message mode.
4349    WaitMsg,
4350    /// PP: wait for text completion, then wait for key.
4351    Pp,
4352    /// R: wait for text completion, then clear-ready + key wait.
4353    R,
4354    /// PAGE: wait for text completion, then page-clear + key wait.
4355    PageWait,
4356
4357    SetName,
4358    ClearName,
4359    GetName,
4360    NextMsg,
4361    MultiMsg,
4362    Ruby,
4363    Koe,
4364    KoePlayWait,
4365    KoePlayWaitKey,
4366    Layer,
4367    World,
4368    SetMojiSize,
4369    SetMojiColor,
4370    SetIndent,
4371    ClearIndent,
4372    StartSlideMsg,
4373    EndSlideMsg,
4374    SlideMsg,
4375    InitOpenAnimeType,
4376    InitOpenAnimeTime,
4377    InitCloseAnimeType,
4378    InitCloseAnimeTime,
4379    SetOpenAnimeType,
4380    SetOpenAnimeTime,
4381    SetCloseAnimeType,
4382    SetCloseAnimeTime,
4383    GetOpenAnimeType,
4384    GetOpenAnimeTime,
4385    GetCloseAnimeType,
4386    GetCloseAnimeTime,
4387    GetDefaultOpenAnimeType,
4388    GetDefaultOpenAnimeTime,
4389    GetDefaultCloseAnimeType,
4390    GetDefaultCloseAnimeTime,
4391    Sel,
4392    SelCancel,
4393    SelMsg,
4394    SelMsgCancel,
4395
4396    /// (bool new_line_flag) -> bool
4397    AddMsgCheck,
4398    /// (string) -> (string overflow_msg)
4399    AddMsg,
4400
4401    SetWaku,
4402    InitWakuFile,
4403    SetWakuFile,
4404    GetWakuFile,
4405    InitFilterFile,
4406    SetFilterFile,
4407    GetFilterFile,
4408
4409    ClearFace,
4410    SetFace,
4411    SetRepPos,
4412    MsgBtn,
4413    InitWindowPos,
4414    InitWindowSize,
4415    SetWindowPos,
4416    SetWindowSize,
4417    GetWindowPosX,
4418    GetWindowPosY,
4419    GetWindowSizeX,
4420    GetWindowSizeY,
4421    InitWindowMojiCnt,
4422    SetWindowMojiCnt,
4423    GetWindowMojiCntX,
4424    GetWindowMojiCntY,
4425    Unknown,
4426}
4427
4428#[derive(Debug, Default, Clone)]
4429pub struct MwndSelectionChoice {
4430    pub text: String,
4431    pub kind: i64,
4432    pub color: i64,
4433}
4434
4435#[derive(Debug, Default, Clone)]
4436pub struct MwndSelectionState {
4437    pub choices: Vec<MwndSelectionChoice>,
4438    pub cursor: usize,
4439    pub cancel_enable: bool,
4440    pub close_mwnd: bool,
4441    /// Conservative runtime result: selected entry index (1-based), 0 for none, -1 for cancel.
4442    pub result: i64,
4443}
4444
4445#[derive(Debug, Default, Clone)]
4446pub struct BtnSelItemState {
4447    pub generated_objects: Vec<ObjectState>,
4448    pub object_list: Vec<ObjectState>,
4449    pub strict: bool,
4450    pub text: String,
4451    pub item_type: i64,
4452    pub color: i64,
4453    pub pos: (i64, i64),
4454    pub size: (i64, i64),
4455    pub visible: bool,
4456    pub selected: bool,
4457    pub button_action_no: i64,
4458    pub button_state: i64,
4459}
4460
4461#[derive(Debug, Default, Clone)]
4462pub struct MwndState {
4463    pub initialized_from_gameexe: bool,
4464    pub open: bool,
4465    pub name_text: String,
4466    pub msg_text: String,
4467    pub msg_waku_no: Option<i64>,
4468    pub waku_file: String,
4469    pub filter_file: String,
4470    pub filter_margin: Option<(i64, i64, i64, i64)>,
4471    pub filter_color: Option<(u8, u8, u8, u8)>,
4472    pub filter_config_color: bool,
4473    pub filter_config_tr: bool,
4474    pub waku_extend_type: i64,
4475    pub icon_no: i64,
4476    pub page_icon_no: i64,
4477    pub key_icon_appear: bool,
4478    pub key_icon_mode: i64,
4479    pub key_icon_pos: Option<(i64, i64)>,
4480    pub icon_pos_type: i64,
4481    pub icon_pos_base: i64,
4482    pub icon_pos: Option<(i64, i64, i64)>,
4483    /// Per-button WAKU template placement: (pos_base, x, y).
4484    pub waku_button_layout: Vec<(i64, i64, i64)>,
4485    /// Per-face WAKU template placement.
4486    pub waku_face_pos: Vec<(i64, i64)>,
4487    pub face_file: String,
4488    pub face_no: i64,
4489    pub rep_pos: Option<(i64, i64)>,
4490    pub msgbtn: Option<(i64, i64, i64, i64)>,
4491    pub window_pos: Option<(i64, i64)>,
4492    pub window_size: Option<(i64, i64)>,
4493    pub message_pos: Option<(i64, i64)>,
4494    pub message_margin: Option<(i64, i64, i64, i64)>,
4495    pub window_moji_cnt: Option<(i64, i64)>,
4496    pub moji_space: Option<(i64, i64)>,
4497    pub mwnd_extend_type: i64,
4498    pub multi_msg: bool,
4499    pub ruby_text: Option<String>,
4500    pub koe: Option<(i64, i64)>,
4501    /// C++ C_elm_mwnd::get_sorter() uses an order/layer pair.
4502    /// There is no public MWND.ORDER script element in the recovered headers;
4503    /// this order is initialized from the engine MWND render defaults and is
4504    /// kept as runtime state so wipe/render code does not read a global table
4505    /// in place of the per-MWND sorter.
4506    pub order: i64,
4507    pub layer: i64,
4508    pub world: i64,
4509    pub moji_size: Option<i64>,
4510    pub moji_color: Option<i64>,
4511    pub shadow_color: Option<i64>,
4512    pub fuchi_color: Option<i64>,
4513    pub chara_color_mod: Option<i64>,
4514    pub chara_moji_color: Option<i64>,
4515    pub chara_shadow_color: Option<i64>,
4516    pub chara_fuchi_color: Option<i64>,
4517    pub name_moji_color: Option<i64>,
4518    pub name_shadow_color: Option<i64>,
4519    pub name_fuchi_color: Option<i64>,
4520    pub indent: bool,
4521    pub slide_msg: bool,
4522    pub slide_time: i64,
4523    pub open_anime_type: i64,
4524    pub open_anime_time: i64,
4525    pub close_anime_type: i64,
4526    pub close_anime_time: i64,
4527    pub selection: Option<MwndSelectionState>,
4528
4529    pub text_dirty: bool,
4530    pub clear_ready: bool,
4531    pub msg_block_started: bool,
4532
4533    pub button_list: Vec<ObjectState>,
4534    pub button_list_strict: bool,
4535    pub face_list: Vec<ObjectState>,
4536    pub face_list_strict: bool,
4537    pub object_list: Vec<ObjectState>,
4538    pub object_list_strict: bool,
4539    pub props: HashMap<i32, i64>,
4540    pub aux_str_props: HashMap<i32, String>,
4541}
4542
4543#[derive(Debug, Default, Clone)]
4544pub struct StageFormState {
4545    /// C++ C_elm_stage_list::init creates BACK/FRONT/NEXT sub stages eagerly.
4546    pub initialized_from_gameexe: bool,
4547    /// Group list storage per stage index.
4548    pub group_lists: HashMap<i64, Vec<GroupState>>,
4549    /// BTNSELITEM list storage per stage index.
4550    pub btnselitem_lists: HashMap<i64, Vec<BtnSelItemState>>,
4551    /// MWND list storage per stage index.
4552    pub mwnd_lists: HashMap<i64, Vec<MwndState>>,
4553    /// World list storage per stage index.
4554    pub world_lists: HashMap<i64, Vec<WorldState>>,
4555    /// Effect list storage per stage index. Mirrors C_elm_stage::m_effect_list.
4556    pub effect_lists: HashMap<i64, Vec<ScreenEffectState>>,
4557    /// Quake list storage per stage index. Mirrors C_elm_stage::m_quake_list.
4558    pub quake_lists: HashMap<i64, Vec<ScreenQuakeState>>,
4559    // --- OBJECT / OBJECTLIST ---
4560    /// Per-stage object state (string objects, rect objects, nested child objects, etc.).
4561    pub object_lists: HashMap<i64, Vec<ObjectState>>,
4562    /// Fixed per-slot C++ C_elm_object::is_use() flags, separated from
4563    /// ObjectState::used.  The latter is an active/runtime flag in this port;
4564    /// C++ stage wipe gates on the slot enable flag initialized by C_elm_object_list.
4565    pub object_slot_use: HashMap<i64, Vec<bool>>,
4566    /// Whether this stage's object list should enforce its current size (enabled after RESIZE).
4567    pub object_list_strict: HashMap<i64, bool>,
4568    /// Rectangle-object layer per stage (created lazily).
4569    pub rect_layers: HashMap<i64, LayerId>,
4570
4571    /// Stable slot assignment for embedded object lists and nested child objects.
4572    pub embedded_object_slots: HashMap<String, usize>,
4573    pub next_embedded_object_slot: HashMap<i64, usize>,
4574    pub next_nested_object_slot: HashMap<i64, usize>,
4575}
4576
4577// -----------------------------------------------------------------------------
4578// Screen (GLOBAL.SCREEN) state
4579// -----------------------------------------------------------------------------
4580
4581#[derive(Debug, Clone)]
4582pub struct ScreenEffectState {
4583    pub x: IntEvent,
4584    pub y: IntEvent,
4585    pub z: IntEvent,
4586    pub mono: IntEvent,
4587    pub reverse: IntEvent,
4588    pub bright: IntEvent,
4589    pub dark: IntEvent,
4590    pub color_r: IntEvent,
4591    pub color_g: IntEvent,
4592    pub color_b: IntEvent,
4593    pub color_rate: IntEvent,
4594    pub color_add_r: IntEvent,
4595    pub color_add_g: IntEvent,
4596    pub color_add_b: IntEvent,
4597    pub begin_order: i32,
4598    pub begin_layer: i32,
4599    pub end_order: i32,
4600    pub end_layer: i32,
4601    pub wipe_copy: i32,
4602    pub wipe_erase: i32,
4603}
4604
4605impl Default for ScreenEffectState {
4606    fn default() -> Self {
4607        Self {
4608            x: IntEvent::new(0),
4609            y: IntEvent::new(0),
4610            z: IntEvent::new(0),
4611            mono: IntEvent::new(0),
4612            reverse: IntEvent::new(0),
4613            bright: IntEvent::new(0),
4614            dark: IntEvent::new(0),
4615            color_r: IntEvent::new(0),
4616            color_g: IntEvent::new(0),
4617            color_b: IntEvent::new(0),
4618            color_rate: IntEvent::new(0),
4619            color_add_r: IntEvent::new(0),
4620            color_add_g: IntEvent::new(0),
4621            color_add_b: IntEvent::new(0),
4622            begin_order: 0,
4623            begin_layer: i32::MIN,
4624            end_order: 0,
4625            end_layer: i32::MAX,
4626            wipe_copy: 0,
4627            wipe_erase: 0,
4628        }
4629    }
4630}
4631impl ScreenEffectState {
4632    pub fn reinit(&mut self) {
4633        *self = Self::default();
4634    }
4635
4636    pub fn tick(&mut self, delta: i32) {
4637        self.x.tick(delta);
4638        self.y.tick(delta);
4639        self.z.tick(delta);
4640        self.mono.tick(delta);
4641        self.reverse.tick(delta);
4642        self.bright.tick(delta);
4643        self.dark.tick(delta);
4644        self.color_r.tick(delta);
4645        self.color_g.tick(delta);
4646        self.color_b.tick(delta);
4647        self.color_rate.tick(delta);
4648        self.color_add_r.tick(delta);
4649        self.color_add_g.tick(delta);
4650        self.color_add_b.tick(delta);
4651    }
4652}
4653
4654#[derive(Debug, Clone)]
4655pub struct ScreenQuakeState {
4656    pub until: Option<Instant>,
4657    pub quake_type: i32,
4658    pub power: i32,
4659    pub vec: i32,
4660    pub center_x: i32,
4661    pub center_y: i32,
4662    pub begin_order: i32,
4663    pub end_order: i32,
4664    pub ending: bool,
4665}
4666
4667impl Default for ScreenQuakeState {
4668    fn default() -> Self {
4669        Self {
4670            until: None,
4671            quake_type: -1,
4672            power: 0,
4673            vec: 0,
4674            center_x: 0,
4675            center_y: 0,
4676            begin_order: 0,
4677            end_order: 0,
4678            ending: false,
4679        }
4680    }
4681}
4682
4683impl ScreenQuakeState {
4684    pub fn reinit(&mut self) {
4685        *self = Self::default();
4686    }
4687
4688    pub fn start_kind(&mut self, quake_type: i32, time_ms: i64) {
4689        self.ending = false;
4690        self.quake_type = quake_type;
4691        let ms = time_ms.max(0) as u64;
4692        self.until = if ms == 0 {
4693            None
4694        } else {
4695            Some(Instant::now() + Duration::from_millis(ms))
4696        };
4697        if ms == 0 {
4698            self.reinit();
4699        }
4700    }
4701
4702    pub fn end_ms(&mut self, time_ms: i64) {
4703        self.ending = true;
4704        let ms = time_ms.max(0) as u64;
4705        self.until = if ms == 0 {
4706            None
4707        } else {
4708            Some(Instant::now() + Duration::from_millis(ms))
4709        };
4710        if ms == 0 {
4711            self.reinit();
4712        }
4713    }
4714
4715    pub fn check_value(&mut self) -> i32 {
4716        let _ = self.is_active();
4717        if self.quake_type < 0 {
4718            0
4719        } else if self.ending {
4720            2
4721        } else {
4722            1
4723        }
4724    }
4725
4726    pub fn is_active(&mut self) -> bool {
4727        if let Some(t) = self.until {
4728            if Instant::now() >= t {
4729                self.reinit();
4730                return false;
4731            }
4732        }
4733        self.quake_type >= 0 && self.until.is_some()
4734    }
4735
4736    pub fn remaining_ms(&mut self) -> u64 {
4737        let Some(t) = self.until else {
4738            return 0;
4739        };
4740        if Instant::now() >= t {
4741            self.reinit();
4742            return 0;
4743        }
4744        t.duration_since(Instant::now()).as_millis() as u64
4745    }
4746}
4747
4748#[derive(Debug, Default, Clone)]
4749pub struct ScreenShakeState {
4750    pub last_value: i64,
4751    pub until: Option<Instant>,
4752}
4753
4754impl ScreenShakeState {
4755    pub fn set_ms(&mut self, time_ms: i64) {
4756        self.last_value = time_ms;
4757        let ms = time_ms.max(0) as u64;
4758        self.until = if ms == 0 {
4759            None
4760        } else {
4761            Some(Instant::now() + Duration::from_millis(ms))
4762        };
4763    }
4764
4765    pub fn tick(&mut self) {
4766        if let Some(t) = self.until {
4767            if Instant::now() >= t {
4768                self.until = None;
4769            }
4770        }
4771    }
4772}
4773
4774#[derive(Debug, Default, Clone)]
4775pub struct ScreenFormState {
4776    pub effect_list: Vec<ScreenEffectState>,
4777    pub quake_list: Vec<ScreenQuakeState>,
4778    pub shake: ScreenShakeState,
4779}
4780
4781impl ScreenFormState {
4782    pub fn ensure_effect_len(&mut self, n: usize) {
4783        if self.effect_list.len() < n {
4784            self.effect_list
4785                .extend((0..(n - self.effect_list.len())).map(|_| ScreenEffectState::default()));
4786        } else if self.effect_list.len() > n {
4787            self.effect_list.truncate(n);
4788        }
4789    }
4790
4791    pub fn ensure_quake_len(&mut self, n: usize) {
4792        if self.quake_list.len() < n {
4793            self.quake_list
4794                .extend((0..(n - self.quake_list.len())).map(|_| ScreenQuakeState::default()));
4795        } else if self.quake_list.len() > n {
4796            self.quake_list.truncate(n);
4797        }
4798    }
4799
4800    pub fn tick(&mut self, delta: i32) {
4801        for effect in &mut self.effect_list {
4802            effect.tick(delta);
4803        }
4804        for quake in &mut self.quake_list {
4805            let _ = quake.is_active();
4806        }
4807        self.shake.tick();
4808    }
4809}
4810
4811// -----------------------------------------------------------------------------
4812// Message backlog (GLOBAL.MSGBK) state
4813// -----------------------------------------------------------------------------
4814
4815#[derive(Debug, Default, Clone)]
4816pub struct MsgBackEntry {
4817    pub pct_flag: bool,
4818    pub msg_str: String,
4819    pub original_name: String,
4820    pub disp_name: String,
4821    pub pct_pos_x: i32,
4822    pub pct_pos_y: i32,
4823    pub koe_no_list: Vec<i64>,
4824    pub chr_no_list: Vec<i64>,
4825    pub koe_play_no: i64,
4826    pub debug_msg: String,
4827    pub scn_no: i64,
4828    pub line_no: i64,
4829    pub save_id: i64,
4830    pub save_id_check_flag: bool,
4831}
4832
4833#[derive(Debug, Clone)]
4834pub struct MsgBackState {
4835    pub history: Vec<MsgBackEntry>,
4836    pub history_cnt_max: usize,
4837    pub history_cnt: usize,
4838    pub history_start_pos: usize,
4839    pub history_insert_pos: usize,
4840    pub history_last_pos: usize,
4841    pub new_msg_flag: bool,
4842}
4843
4844impl Default for MsgBackState {
4845    fn default() -> Self {
4846        let history_cnt_max = 256usize;
4847        Self {
4848            history: vec![MsgBackEntry { scn_no: -1, line_no: -1, ..MsgBackEntry::default() }; history_cnt_max],
4849            history_cnt_max,
4850            history_cnt: 0,
4851            history_start_pos: 0,
4852            history_insert_pos: 0,
4853            history_last_pos: 0,
4854            new_msg_flag: true,
4855        }
4856    }
4857}
4858
4859impl MsgBackState {
4860    fn reset_entry(entry: &mut MsgBackEntry) {
4861        *entry = MsgBackEntry {
4862            scn_no: -1,
4863            line_no: -1,
4864            ..MsgBackEntry::default()
4865        };
4866    }
4867
4868    fn ensure_capacity(&mut self) {
4869        if self.history_cnt_max == 0 {
4870            self.history_cnt_max = 256;
4871        }
4872        if self.history.len() != self.history_cnt_max {
4873            self.history.resize_with(self.history_cnt_max, || MsgBackEntry {
4874                scn_no: -1,
4875                line_no: -1,
4876                ..MsgBackEntry::default()
4877            });
4878        }
4879        self.history_insert_pos %= self.history_cnt_max;
4880        self.history_start_pos %= self.history_cnt_max;
4881        self.history_last_pos %= self.history_cnt_max;
4882    }
4883
4884    pub fn set_history_cnt_max(&mut self, max_count: usize) {
4885        let max_count = max_count.max(1);
4886        if max_count == self.history_cnt_max {
4887            self.ensure_capacity();
4888            return;
4889        }
4890        let mut ordered = self
4891            .ordered_history_indices()
4892            .into_iter()
4893            .filter_map(|idx| self.history.get(idx).cloned())
4894            .collect::<Vec<_>>();
4895        if ordered.len() > max_count {
4896            let drop_count = ordered.len() - max_count;
4897            ordered.drain(0..drop_count);
4898        }
4899        self.history_cnt_max = max_count;
4900        self.history = vec![MsgBackEntry { scn_no: -1, line_no: -1, ..MsgBackEntry::default() }; max_count];
4901        self.history_cnt = ordered.len();
4902        self.history_start_pos = 0;
4903        for (i, entry) in ordered.into_iter().enumerate() {
4904            self.history[i] = entry;
4905        }
4906        self.history_insert_pos = self.history_cnt % self.history_cnt_max;
4907        self.history_last_pos = self.history_cnt.saturating_sub(1).min(self.history_cnt_max - 1);
4908        self.new_msg_flag = true;
4909    }
4910
4911    fn ready_msg(&mut self) -> &mut MsgBackEntry {
4912        self.ensure_capacity();
4913        if self.new_msg_flag {
4914            if self.history_cnt < self.history_cnt_max {
4915                self.history_cnt += 1;
4916            } else {
4917                self.history_start_pos = (self.history_start_pos + 1) % self.history_cnt_max;
4918            }
4919            Self::reset_entry(&mut self.history[self.history_insert_pos]);
4920            self.new_msg_flag = false;
4921        }
4922        &mut self.history[self.history_insert_pos]
4923    }
4924
4925    pub fn clear(&mut self) {
4926        self.ensure_capacity();
4927        for entry in &mut self.history {
4928            Self::reset_entry(entry);
4929        }
4930        self.history_cnt = 0;
4931        self.history_start_pos = 0;
4932        self.history_insert_pos = 0;
4933        self.history_last_pos = 0;
4934        self.new_msg_flag = true;
4935    }
4936
4937    pub fn ordered_history_indices(&self) -> Vec<usize> {
4938        if self.history_cnt_max == 0 || self.history_cnt == 0 {
4939            return Vec::new();
4940        }
4941        (0..self.history_cnt)
4942            .map(|i| (self.history_start_pos + i) % self.history_cnt_max)
4943            .collect()
4944    }
4945
4946    pub fn next(&mut self) {
4947        self.ensure_capacity();
4948        if self.new_msg_flag {
4949            return;
4950        }
4951        let Some(cur) = self.history.get(self.history_insert_pos) else {
4952            self.new_msg_flag = true;
4953            return;
4954        };
4955        if !cur.pct_flag && cur.msg_str.is_empty() {
4956            return;
4957        }
4958        self.history_insert_pos = (self.history_insert_pos + 1) % self.history_cnt_max;
4959        self.new_msg_flag = true;
4960    }
4961
4962    pub fn add_koe(&mut self, koe_no: i64, chara_no: i64, scn_no: i64, line_no: i64) -> bool {
4963        if koe_no < 0 {
4964            return true;
4965        }
4966        let insert_pos = self.history_insert_pos;
4967        let entry = self.ready_msg();
4968        entry.koe_no_list.push(koe_no);
4969        entry.chr_no_list.push(chara_no);
4970        entry.scn_no = scn_no;
4971        entry.line_no = line_no;
4972        self.history_last_pos = insert_pos;
4973        true
4974    }
4975
4976    pub fn add_name(
4977        &mut self,
4978        original_name: &str,
4979        disp_name: &str,
4980        scn_no: i64,
4981        line_no: i64,
4982    ) -> bool {
4983        if disp_name.is_empty() {
4984            return true;
4985        }
4986        let insert_pos = self.history_insert_pos;
4987        let entry = self.ready_msg();
4988        entry.original_name.clear();
4989        entry.original_name.push_str(original_name);
4990        entry.disp_name.clear();
4991        entry.disp_name.push_str(disp_name);
4992        entry.scn_no = scn_no;
4993        entry.line_no = line_no;
4994        self.history_last_pos = insert_pos;
4995        true
4996    }
4997
4998    pub fn add_msg(&mut self, msg: &str, debug_msg: &str, scn_no: i64, line_no: i64) -> bool {
4999        if msg.is_empty() {
5000            return true;
5001        }
5002        let insert_pos = self.history_insert_pos;
5003        let entry = self.ready_msg();
5004        entry.msg_str.push_str(msg);
5005        entry.debug_msg.clear();
5006        entry.debug_msg.push_str(debug_msg);
5007        entry.scn_no = scn_no;
5008        entry.line_no = line_no;
5009        self.history_last_pos = insert_pos;
5010        true
5011    }
5012
5013    pub fn add_new_line_indent(&mut self, scn_no: i64, line_no: i64) -> bool {
5014        let insert_pos = self.history_insert_pos;
5015        let entry = self.ready_msg();
5016        entry.msg_str.push('\n');
5017        entry.scn_no = scn_no;
5018        entry.line_no = line_no;
5019        self.history_last_pos = insert_pos;
5020        true
5021    }
5022
5023    pub fn add_new_line_no_indent(&mut self, scn_no: i64, line_no: i64) -> bool {
5024        let insert_pos = self.history_insert_pos;
5025        let entry = self.ready_msg();
5026        entry.msg_str.push('\u{0007}');
5027        entry.scn_no = scn_no;
5028        entry.line_no = line_no;
5029        self.history_last_pos = insert_pos;
5030        true
5031    }
5032
5033    pub fn add_pct(&mut self, file_name: &str, x: i32, y: i32) -> bool {
5034        if file_name.is_empty() {
5035            return false;
5036        }
5037        self.next();
5038        let insert_pos = self.history_insert_pos;
5039        let entry = self.ready_msg();
5040        entry.pct_flag = true;
5041        entry.pct_pos_x = x;
5042        entry.pct_pos_y = y;
5043        entry.msg_str.clear();
5044        entry.msg_str.push_str(file_name);
5045        self.history_last_pos = insert_pos;
5046        self.next();
5047        true
5048    }
5049
5050    pub fn current_entry(&self) -> Option<&MsgBackEntry> {
5051        self.history.get(self.history_insert_pos)
5052    }
5053}
5054
5055impl StageFormState {
5056    pub fn ensure_group_list(&mut self, stage_idx: i64, cnt: usize) {
5057        let entry = self.group_lists.entry(stage_idx).or_default();
5058        if entry.len() < cnt {
5059            entry.extend((0..(cnt - entry.len())).map(|_| GroupState::default()));
5060        } else if entry.len() > cnt {
5061            entry.truncate(cnt);
5062        }
5063    }
5064
5065    pub fn clear_group_list(&mut self, stage_idx: i64) {
5066        self.group_lists.insert(stage_idx, Vec::new());
5067    }
5068
5069    pub fn ensure_mwnd_list(&mut self, stage_idx: i64, cnt: usize) {
5070        let entry = self.mwnd_lists.entry(stage_idx).or_default();
5071        if entry.len() < cnt {
5072            entry.extend((0..(cnt - entry.len())).map(|_| MwndState::default()));
5073        } else if entry.len() > cnt {
5074            entry.truncate(cnt);
5075        }
5076    }
5077
5078    pub fn ensure_effect_list(&mut self, stage_idx: i64, cnt: usize) {
5079        let entry = self.effect_lists.entry(stage_idx).or_default();
5080        if entry.len() < cnt {
5081            entry.extend((0..(cnt - entry.len())).map(|_| ScreenEffectState::default()));
5082        } else if entry.len() > cnt {
5083            entry.truncate(cnt);
5084        }
5085    }
5086
5087    pub fn ensure_quake_list(&mut self, stage_idx: i64, cnt: usize) {
5088        let entry = self.quake_lists.entry(stage_idx).or_default();
5089        if entry.len() < cnt {
5090            entry.extend((0..(cnt - entry.len())).map(|_| ScreenQuakeState::default()));
5091        } else if entry.len() > cnt {
5092            entry.truncate(cnt);
5093        }
5094    }
5095
5096    pub fn close_all_mwnd(&mut self, stage_idx: i64) {
5097        if let Some(list) = self.mwnd_lists.get_mut(&stage_idx) {
5098            for (idx, m) in list.iter_mut().enumerate() {
5099                let old_open = m.open;
5100                m.open = false;
5101                if std::env::var_os("SG_DEBUG").is_some() {
5102                    eprintln!(
5103                        "[SG_DEBUG][MWND_STATE_TRACE] scene=<runtime> scene_no=- line=- reason=STAGE_CLOSE_ALL_MWND stage={} mwnd={} old_open={} new_open={} buttons={} faces={} objects={} waku={} filter={} pos={:?} size={:?} open_anim=({}, {}) close_anim=({}, {}) selection={} msg_len={} name_len={}",
5104                        stage_idx,
5105                        idx,
5106                        old_open,
5107                        m.open,
5108                        m.button_list.len(),
5109                        m.face_list.len(),
5110                        m.object_list.len(),
5111                        if m.waku_file.is_empty() { "-" } else { m.waku_file.as_str() },
5112                        if m.filter_file.is_empty() { "-" } else { m.filter_file.as_str() },
5113                        m.window_pos,
5114                        m.window_size,
5115                        m.open_anime_type,
5116                        m.open_anime_time,
5117                        m.close_anime_type,
5118                        m.close_anime_time,
5119                        m.selection.is_some(),
5120                        m.msg_text.len(),
5121                        m.name_text.len(),
5122                    );
5123                }
5124            }
5125        }
5126    }
5127
5128    pub fn ensure_object_list(&mut self, stage_idx: i64, cnt: usize) {
5129        let entry = self.object_lists.entry(stage_idx).or_default();
5130        if entry.len() < cnt {
5131            entry.extend((0..(cnt - entry.len())).map(|_| ObjectState::default()));
5132        } else if entry.len() > cnt {
5133            entry.truncate(cnt);
5134        }
5135
5136        let slot_use = self.object_slot_use.entry(stage_idx).or_default();
5137        if slot_use.len() < cnt {
5138            slot_use.extend((0..(cnt - slot_use.len())).map(|_| true));
5139        } else if slot_use.len() > cnt {
5140            slot_use.truncate(cnt);
5141        }
5142    }
5143
5144    pub fn set_object_list_len_strict(&mut self, stage_idx: i64, cnt: usize) {
5145        self.ensure_object_list(stage_idx, cnt);
5146        self.object_list_strict.insert(stage_idx, true);
5147    }
5148
5149    pub fn object_list_len(&self, stage_idx: i64) -> usize {
5150        self.object_lists
5151            .get(&stage_idx)
5152            .map(|v| v.len())
5153            .unwrap_or(0)
5154    }
5155
5156    pub fn is_embedded_object_slot(&self, stage_idx: i64, slot: usize) -> bool {
5157        let prefix = format!("{stage_idx}:");
5158        self.embedded_object_slots
5159            .iter()
5160            .any(|(key, &mapped_slot)| mapped_slot == slot && key.starts_with(&prefix))
5161    }
5162}
5163
5164impl MaskListState {
5165    pub fn new(mask_cnt: usize) -> Self {
5166        let mut masks = Vec::with_capacity(mask_cnt);
5167        for _ in 0..mask_cnt {
5168            masks.push(MaskState::new());
5169        }
5170        Self { masks }
5171    }
5172
5173    pub fn ensure_size(&mut self, mask_cnt: usize) {
5174        if self.masks.len() < mask_cnt {
5175            self.masks.reserve(mask_cnt - self.masks.len());
5176            while self.masks.len() < mask_cnt {
5177                self.masks.push(MaskState::new());
5178            }
5179        } else if self.masks.len() > mask_cnt {
5180            self.masks.truncate(mask_cnt);
5181        }
5182    }
5183
5184    pub fn tick_frame(&mut self, delta: i32) {
5185        for m in &mut self.masks {
5186            m.x_event.tick(delta);
5187            m.y_event.tick(delta);
5188            for ev in m.script_events.values_mut() {
5189                ev.tick(delta);
5190            }
5191        }
5192    }
5193}
5194
5195impl GlobalState {
5196    pub fn start_wipe(&mut self, w: WipeState) {
5197        self.wipe = Some(w);
5198    }
5199
5200    pub fn finish_wipe(&mut self) {
5201        self.wipe = None;
5202    }
5203
5204    pub fn wipe_done(&self) -> bool {
5205        self.wipe.as_ref().map(|w| w.is_done()).unwrap_or(true)
5206    }
5207
5208    pub fn tick_frame(&mut self, past_game_time: i32, past_real_time: i32) {
5209        self.render_frame = self.render_frame.wrapping_add(1);
5210        self.local_real_time = self
5211            .local_real_time
5212            .saturating_add(past_real_time.max(0) as i64);
5213        self.local_game_time = self
5214            .local_game_time
5215            .saturating_add(past_game_time.max(0) as i64);
5216        self.local_wipe_time = self
5217            .local_wipe_time
5218            .saturating_add(past_game_time.max(0) as i64);
5219        if self.wipe_done() {
5220            self.wipe = None;
5221        }
5222        if self.change_display_mode_proc_cnt > 0 {
5223            self.change_display_mode_proc_cnt -= 1;
5224        }
5225        self.mov.tick(past_real_time);
5226        self.fog_global.update_time(past_game_time, past_real_time);
5227        self.fog_global.frame();
5228
5229        if !self.script.counter_time_stop_flag {
5230            let mut counter_ids: Vec<u32> = self.counter_lists.keys().copied().collect();
5231            counter_ids.sort_unstable();
5232            for counter_id in counter_ids {
5233                let Some(counters) = self.counter_lists.get_mut(&counter_id) else {
5234                    continue;
5235                };
5236                for counter in counters {
5237                    counter.update_time(past_game_time, past_real_time);
5238                }
5239            }
5240        }
5241
5242        if !self.script.frame_action_time_stop_flag {
5243            let mut frame_action_ids: Vec<u32> = self.frame_actions.keys().copied().collect();
5244            frame_action_ids.sort_unstable();
5245            for frame_action_id in frame_action_ids {
5246                let Some(fa) = self.frame_actions.get_mut(&frame_action_id) else {
5247                    continue;
5248                };
5249                fa.counter.update_time(past_game_time, past_real_time);
5250            }
5251            let mut frame_action_list_ids: Vec<u32> =
5252                self.frame_action_lists.keys().copied().collect();
5253            frame_action_list_ids.sort_unstable();
5254            for frame_action_list_id in frame_action_list_ids {
5255                let Some(list) = self.frame_action_lists.get_mut(&frame_action_list_id) else {
5256                    continue;
5257                };
5258                for fa in list {
5259                    fa.counter.update_time(past_game_time, past_real_time);
5260                }
5261            }
5262        }
5263
5264        let mut mask_list_ids: Vec<u32> = self.mask_lists.keys().copied().collect();
5265        mask_list_ids.sort_unstable();
5266        for mask_list_id in mask_list_ids {
5267            let Some(ml) = self.mask_lists.get_mut(&mask_list_id) else {
5268                continue;
5269            };
5270            ml.tick_frame(past_game_time.max(0));
5271        }
5272
5273        let mut screen_form_ids: Vec<u32> = self.screen_forms.keys().copied().collect();
5274        screen_form_ids.sort_unstable();
5275        for screen_form_id in screen_form_ids {
5276            let Some(sc) = self.screen_forms.get_mut(&screen_form_id) else {
5277                continue;
5278            };
5279            sc.tick(past_game_time.max(0));
5280        }
5281
5282        let mut int_event_root_ids: Vec<u32> = self.int_event_roots.keys().copied().collect();
5283        int_event_root_ids.sort_unstable();
5284        for int_event_root_id in int_event_root_ids {
5285            let Some(ev) = self.int_event_roots.get_mut(&int_event_root_id) else {
5286                continue;
5287            };
5288            ev.update_time(past_game_time, past_real_time);
5289            ev.frame();
5290        }
5291        let mut int_event_list_ids: Vec<u32> = self.int_event_lists.keys().copied().collect();
5292        int_event_list_ids.sort_unstable();
5293        for int_event_list_id in int_event_list_ids {
5294            let Some(events) = self.int_event_lists.get_mut(&int_event_list_id) else {
5295                continue;
5296            };
5297            for ev in events {
5298                ev.update_time(past_game_time, past_real_time);
5299                ev.frame();
5300            }
5301        }
5302
5303        let mut stage_form_ids: Vec<u32> = self.stage_forms.keys().copied().collect();
5304        stage_form_ids.sort_unstable();
5305        for stage_form_id in stage_form_ids {
5306            let Some(st) = self.stage_forms.get_mut(&stage_form_id) else {
5307                continue;
5308            };
5309            let mut object_stage_ids: Vec<i64> = st.object_lists.keys().copied().collect();
5310            object_stage_ids.sort_unstable();
5311            for object_stage_id in object_stage_ids {
5312                let embedded_prefix = format!("{object_stage_id}:");
5313                let embedded_slots: HashSet<usize> = st
5314                    .embedded_object_slots
5315                    .iter()
5316                    .filter_map(|(key, &slot)| key.starts_with(&embedded_prefix).then_some(slot))
5317                    .collect();
5318                let Some(objs) = st.object_lists.get_mut(&object_stage_id) else {
5319                    continue;
5320                };
5321                for (obj_idx, obj) in objs.iter_mut().enumerate() {
5322                    if embedded_slots.contains(&obj_idx) {
5323                        continue;
5324                    }
5325                    obj.tick(past_game_time, past_real_time);
5326                }
5327            }
5328
5329            let mut mwnd_stage_ids: Vec<i64> = st.mwnd_lists.keys().copied().collect();
5330            mwnd_stage_ids.sort_unstable();
5331            for mwnd_stage_id in mwnd_stage_ids {
5332                let Some(mwnds) = st.mwnd_lists.get_mut(&mwnd_stage_id) else {
5333                    continue;
5334                };
5335                for mwnd in mwnds {
5336                    for obj in &mut mwnd.button_list {
5337                        obj.tick(past_game_time, past_real_time);
5338                    }
5339                    for obj in &mut mwnd.face_list {
5340                        obj.tick(past_game_time, past_real_time);
5341                    }
5342                    for obj in &mut mwnd.object_list {
5343                        obj.tick(past_game_time, past_real_time);
5344                    }
5345                }
5346            }
5347            let mut world_stage_ids: Vec<i64> = st.world_lists.keys().copied().collect();
5348            world_stage_ids.sort_unstable();
5349            for world_stage_id in world_stage_ids {
5350                let Some(worlds) = st.world_lists.get_mut(&world_stage_id) else {
5351                    continue;
5352                };
5353                for w in worlds {
5354                    w.update_time(past_game_time, past_real_time);
5355                    w.frame();
5356                }
5357            }
5358
5359            let mut effect_stage_ids: Vec<i64> = st.effect_lists.keys().copied().collect();
5360            effect_stage_ids.sort_unstable();
5361            for effect_stage_id in effect_stage_ids {
5362                let Some(effects) = st.effect_lists.get_mut(&effect_stage_id) else {
5363                    continue;
5364                };
5365                for effect in effects {
5366                    effect.tick(past_game_time.max(0));
5367                }
5368            }
5369
5370            let mut quake_stage_ids: Vec<i64> = st.quake_lists.keys().copied().collect();
5371            quake_stage_ids.sort_unstable();
5372            for quake_stage_id in quake_stage_ids {
5373                let Some(quakes) = st.quake_lists.get_mut(&quake_stage_id) else {
5374                    continue;
5375                };
5376                for quake in quakes {
5377                    let _ = quake.is_active();
5378                }
5379            }
5380        }
5381    }
5382}