Skip to main content

siglus_scene_vm/runtime/forms/
object_event.rs

1use anyhow::{bail, Result};
2
3use crate::runtime::forms::codes::{elm_value, ELM_ARRAY, FM_OBJECTEVENT, FM_OBJECTEVENTLIST};
4use crate::runtime::globals::{ObjectEventTarget, ObjectState, StageFormState};
5use crate::runtime::{CommandContext, Value};
6
7use super::prop_access;
8
9fn as_i64(v: &Value) -> Option<i64> {
10    v.as_i64()
11}
12
13fn default_push(ctx: &mut CommandContext) {
14    ctx.push(Value::Int(0));
15}
16
17fn anim_skip_trace_enabled() -> bool {
18    std::env::var_os("SG_DEBUG").is_some()
19}
20
21fn anim_skip_trace(ctx: &CommandContext, msg: impl AsRef<str>) {
22    if anim_skip_trace_enabled() {
23        let scene = ctx.current_scene_name.as_deref().unwrap_or("<none>");
24        let scene_no = ctx
25            .current_scene_no
26            .map(|v| v.to_string())
27            .unwrap_or_else(|| "-".to_string());
28        eprintln!(
29            "[SG_DEBUG][ANIM_SKIP_TRACE][OBJECTEVENT] scene={} scene_no={} line={} {}",
30            scene,
31            scene_no,
32            ctx.current_line_no,
33            msg.as_ref()
34        );
35    }
36}
37
38fn int_event_state(ev: &crate::runtime::int_event::IntEvent) -> String {
39    format!(
40        "value={} cur={} start={} end={} cur_time={} end_time={} delay={} loop_type={} speed={} real={} active={}",
41        ev.value, ev.cur_value, ev.start_value, ev.end_value, ev.cur_time, ev.end_time,
42        ev.delay_time, ev.loop_type, ev.speed_type, ev.real_flag, ev.check_event()
43    )
44}
45
46fn parse_chain<'a>(ctx: &'a CommandContext, args: &'a [Value]) -> Option<(usize, &'a [i32])> {
47    prop_access::parse_element_chain_ctx(ctx, FM_OBJECTEVENT as u32, args)
48}
49
50fn object_runtime_slot(idx: usize, obj: &ObjectState) -> usize {
51    obj.runtime_slot_or(idx)
52}
53
54fn find_object_by_runtime_slot<'a>(
55    objects: &'a [ObjectState],
56    runtime_slot: usize,
57) -> Option<&'a ObjectState> {
58    for (idx, obj) in objects.iter().enumerate() {
59        if object_runtime_slot(idx, obj) == runtime_slot {
60            return Some(obj);
61        }
62        if let Some(found) = find_object_by_runtime_slot(&obj.runtime.child_objects, runtime_slot) {
63            return Some(found);
64        }
65    }
66    None
67}
68
69fn find_object_by_runtime_slot_mut<'a>(
70    mut objects: &'a mut [ObjectState],
71    runtime_slot: usize,
72) -> Option<&'a mut ObjectState> {
73    let mut idx = 0usize;
74    while let Some((obj, tail)) = objects.split_first_mut() {
75        if object_runtime_slot(idx, obj) == runtime_slot {
76            return Some(obj);
77        }
78        if let Some(found) =
79            find_object_by_runtime_slot_mut(&mut obj.runtime.child_objects, runtime_slot)
80        {
81            return Some(found);
82        }
83        objects = tail;
84        idx += 1;
85    }
86    None
87}
88
89fn object_by_runtime_slot<'a>(
90    st: &'a StageFormState,
91    stage_idx: i64,
92    runtime_slot: usize,
93) -> Option<&'a ObjectState> {
94    if let Some(obj) = st
95        .object_lists
96        .get(&stage_idx)
97        .and_then(|list| find_object_by_runtime_slot(list, runtime_slot))
98    {
99        return Some(obj);
100    }
101
102    if let Some(mwnds) = st.mwnd_lists.get(&stage_idx) {
103        for mwnd in mwnds {
104            if let Some(obj) = find_object_by_runtime_slot(&mwnd.button_list, runtime_slot) {
105                return Some(obj);
106            }
107            if let Some(obj) = find_object_by_runtime_slot(&mwnd.face_list, runtime_slot) {
108                return Some(obj);
109            }
110            if let Some(obj) = find_object_by_runtime_slot(&mwnd.object_list, runtime_slot) {
111                return Some(obj);
112            }
113        }
114    }
115
116    if let Some(items) = st.btnselitem_lists.get(&stage_idx) {
117        for item in items {
118            if let Some(obj) = find_object_by_runtime_slot(&item.object_list, runtime_slot) {
119                return Some(obj);
120            }
121        }
122    }
123
124    None
125}
126
127fn object_by_runtime_slot_mut<'a>(
128    st: &'a mut StageFormState,
129    stage_idx: i64,
130    runtime_slot: usize,
131) -> Option<&'a mut ObjectState> {
132    if let Some(list) = st.object_lists.get_mut(&stage_idx) {
133        if let Some(obj) = find_object_by_runtime_slot_mut(list, runtime_slot) {
134            return Some(obj);
135        }
136    }
137
138    if let Some(mwnds) = st.mwnd_lists.get_mut(&stage_idx) {
139        for mwnd in mwnds {
140            if let Some(obj) = find_object_by_runtime_slot_mut(&mut mwnd.button_list, runtime_slot) {
141                return Some(obj);
142            }
143            if let Some(obj) = find_object_by_runtime_slot_mut(&mut mwnd.face_list, runtime_slot) {
144                return Some(obj);
145            }
146            if let Some(obj) = find_object_by_runtime_slot_mut(&mut mwnd.object_list, runtime_slot) {
147                return Some(obj);
148            }
149        }
150    }
151
152    if let Some(items) = st.btnselitem_lists.get_mut(&stage_idx) {
153        for item in items {
154            if let Some(obj) = find_object_by_runtime_slot_mut(&mut item.object_list, runtime_slot) {
155                return Some(obj);
156            }
157        }
158    }
159
160    None
161}
162
163fn target_for_set_loop_turn_stop_wait(op: i32) -> Option<ObjectEventTarget> {
164    match op {
165        elm_value::OBJECTEVENT_SET_X
166        | elm_value::OBJECTEVENT_LOOP_X
167        | elm_value::OBJECTEVENT_TURN_X
168        | elm_value::OBJECTEVENT_STOP_X
169        | elm_value::OBJECTEVENT_WAIT_X => Some(ObjectEventTarget::X),
170        elm_value::OBJECTEVENT_SET_Y
171        | elm_value::OBJECTEVENT_LOOP_Y
172        | elm_value::OBJECTEVENT_TURN_Y
173        | elm_value::OBJECTEVENT_STOP_Y
174        | elm_value::OBJECTEVENT_WAIT_Y => Some(ObjectEventTarget::Y),
175        elm_value::OBJECTEVENT_SET_Z
176        | elm_value::OBJECTEVENT_LOOP_Z
177        | elm_value::OBJECTEVENT_TURN_Z
178        | elm_value::OBJECTEVENT_STOP_Z
179        | elm_value::OBJECTEVENT_WAIT_Z => Some(ObjectEventTarget::Z),
180        elm_value::OBJECTEVENT_SET_SCALE_X
181        | elm_value::OBJECTEVENT_STOP_SCALE_X
182        | elm_value::OBJECTEVENT_WAIT_SCALE_X => Some(ObjectEventTarget::ScaleX),
183        elm_value::OBJECTEVENT_SET_SCALE_Y
184        | elm_value::OBJECTEVENT_STOP_SCALE_Y
185        | elm_value::OBJECTEVENT_WAIT_SCALE_Y => Some(ObjectEventTarget::ScaleY),
186        elm_value::OBJECTEVENT_SET_SCALE_Z
187        | elm_value::OBJECTEVENT_STOP_SCALE_Z
188        | elm_value::OBJECTEVENT_WAIT_SCALE_Z => Some(ObjectEventTarget::ScaleZ),
189        elm_value::OBJECTEVENT_SET_ROTATE_X
190        | elm_value::OBJECTEVENT_STOP_ROTATE_X
191        | elm_value::OBJECTEVENT_WAIT_ROTATE_X => Some(ObjectEventTarget::RotateX),
192        elm_value::OBJECTEVENT_SET_ROTATE_Y
193        | elm_value::OBJECTEVENT_STOP_ROTATE_Y
194        | elm_value::OBJECTEVENT_WAIT_ROTATE_Y => Some(ObjectEventTarget::RotateY),
195        elm_value::OBJECTEVENT_SET_ROTATE_Z
196        | elm_value::OBJECTEVENT_STOP_ROTATE_Z
197        | elm_value::OBJECTEVENT_WAIT_ROTATE_Z => Some(ObjectEventTarget::RotateZ),
198        elm_value::OBJECTEVENT_SET_TR
199        | elm_value::OBJECTEVENT_LOOP_TR
200        | elm_value::OBJECTEVENT_TURN_TR
201        | elm_value::OBJECTEVENT_STOP_TR
202        | elm_value::OBJECTEVENT_WAIT_TR => Some(ObjectEventTarget::Tr),
203        _ => None,
204    }
205}
206
207fn event_prop_for_target(ctx: &CommandContext, target: ObjectEventTarget) -> i32 {
208    match target {
209        ObjectEventTarget::X => ctx.ids.obj_x_eve,
210        ObjectEventTarget::Y => ctx.ids.obj_y_eve,
211        ObjectEventTarget::Z => ctx.ids.obj_z_eve,
212        ObjectEventTarget::ScaleX => ctx.ids.obj_scale_x_eve,
213        ObjectEventTarget::ScaleY => ctx.ids.obj_scale_y_eve,
214        ObjectEventTarget::ScaleZ => ctx.ids.obj_scale_z_eve,
215        ObjectEventTarget::RotateX => ctx.ids.obj_rotate_x_eve,
216        ObjectEventTarget::RotateY => ctx.ids.obj_rotate_y_eve,
217        ObjectEventTarget::RotateZ => ctx.ids.obj_rotate_z_eve,
218        ObjectEventTarget::Tr => ctx.ids.obj_tr_eve,
219        _ => 0,
220    }
221}
222
223fn is_set_op(op: i32) -> bool {
224    matches!(
225        op,
226        elm_value::OBJECTEVENT_SET_X
227            | elm_value::OBJECTEVENT_SET_Y
228            | elm_value::OBJECTEVENT_SET_Z
229            | elm_value::OBJECTEVENT_SET_SCALE_X
230            | elm_value::OBJECTEVENT_SET_SCALE_Y
231            | elm_value::OBJECTEVENT_SET_SCALE_Z
232            | elm_value::OBJECTEVENT_SET_ROTATE_X
233            | elm_value::OBJECTEVENT_SET_ROTATE_Y
234            | elm_value::OBJECTEVENT_SET_ROTATE_Z
235            | elm_value::OBJECTEVENT_SET_TR
236    )
237}
238
239fn is_loop_op(op: i32) -> bool {
240    matches!(
241        op,
242        elm_value::OBJECTEVENT_LOOP_X
243            | elm_value::OBJECTEVENT_LOOP_Y
244            | elm_value::OBJECTEVENT_LOOP_Z
245            | elm_value::OBJECTEVENT_LOOP_TR
246    )
247}
248
249fn is_turn_op(op: i32) -> bool {
250    matches!(
251        op,
252        elm_value::OBJECTEVENT_TURN_X
253            | elm_value::OBJECTEVENT_TURN_Y
254            | elm_value::OBJECTEVENT_TURN_Z
255            | elm_value::OBJECTEVENT_TURN_TR
256    )
257}
258
259fn is_stop_op(op: i32) -> bool {
260    matches!(
261        op,
262        elm_value::OBJECTEVENT_STOP_X
263            | elm_value::OBJECTEVENT_STOP_Y
264            | elm_value::OBJECTEVENT_STOP_Z
265            | elm_value::OBJECTEVENT_STOP_SCALE_X
266            | elm_value::OBJECTEVENT_STOP_SCALE_Y
267            | elm_value::OBJECTEVENT_STOP_SCALE_Z
268            | elm_value::OBJECTEVENT_STOP_ROTATE_X
269            | elm_value::OBJECTEVENT_STOP_ROTATE_Y
270            | elm_value::OBJECTEVENT_STOP_ROTATE_Z
271            | elm_value::OBJECTEVENT_STOP_TR
272    )
273}
274
275fn is_wait_op(op: i32) -> bool {
276    matches!(
277        op,
278        elm_value::OBJECTEVENT_WAIT_X
279            | elm_value::OBJECTEVENT_WAIT_Y
280            | elm_value::OBJECTEVENT_WAIT_Z
281            | elm_value::OBJECTEVENT_WAIT_SCALE_X
282            | elm_value::OBJECTEVENT_WAIT_SCALE_Y
283            | elm_value::OBJECTEVENT_WAIT_SCALE_Z
284            | elm_value::OBJECTEVENT_WAIT_ROTATE_X
285            | elm_value::OBJECTEVENT_WAIT_ROTATE_Y
286            | elm_value::OBJECTEVENT_WAIT_ROTATE_Z
287            | elm_value::OBJECTEVENT_WAIT_TR
288    )
289}
290
291fn dispatch_object_event_on_runtime_slot(
292    ctx: &mut CommandContext,
293    stage_idx: i64,
294    runtime_slot: usize,
295    op: i32,
296    script_args: &[Value],
297) -> Result<bool> {
298    if op == elm_value::OBJECTEVENT_WAIT_ALL {
299        let active = {
300            let stage_form = ctx.ids.form_global_stage;
301            ctx.globals
302                .stage_forms
303                .get(&stage_form)
304                .and_then(|st| object_by_runtime_slot(st, stage_idx, runtime_slot))
305                .map(|o| o.any_event_active())
306                .unwrap_or(false)
307        };
308        anim_skip_trace(ctx, format!(
309            "OBJECTEVENT.WAIT_ALL stage={} slot={} active={}",
310            stage_idx, runtime_slot, active
311        ));
312        if active {
313            ctx.wait.wait_object_all_events(
314                ctx.ids.form_global_stage,
315                stage_idx,
316                runtime_slot,
317                false,
318            );
319        }
320        default_push(ctx);
321        return Ok(true);
322    }
323
324    if op == elm_value::OBJECTEVENT_STOP_ALL {
325        anim_skip_trace(ctx, format!(
326            "OBJECTEVENT.STOP_ALL stage={} slot={}",
327            stage_idx, runtime_slot
328        ));
329        let stage_form = ctx.ids.form_global_stage;
330        if let Some(st) = ctx.globals.stage_forms.get_mut(&stage_form) {
331            if let Some(obj) = object_by_runtime_slot_mut(st, stage_idx, runtime_slot) {
332                obj.end_all_events();
333            }
334        }
335        default_push(ctx);
336        return Ok(true);
337    }
338
339    let Some(target) = target_for_set_loop_turn_stop_wait(op) else {
340        bail!("unsupported OBJECTEVENT op {}", op);
341    };
342    let event_prop = event_prop_for_target(ctx, target);
343    if event_prop == 0 {
344        bail!("OBJECTEVENT op {} has no mapped object event property", op);
345    }
346
347    if is_wait_op(op) {
348        let active = {
349            let stage_form = ctx.ids.form_global_stage;
350            ctx.globals
351                .stage_forms
352                .get(&stage_form)
353                .and_then(|st| object_by_runtime_slot(st, stage_idx, runtime_slot))
354                .and_then(|obj| obj.runtime.prop_events.get(target))
355                .map(|ev| ev.check_event())
356                .unwrap_or(false)
357        };
358        anim_skip_trace(ctx, format!(
359            "OBJECTEVENT.WAIT target={:?} stage={} slot={} op={} event_prop={} active={}",
360            target, stage_idx, runtime_slot, op, event_prop, active
361        ));
362        if active {
363            ctx.wait.wait_object_event(
364                ctx.ids.form_global_stage,
365                stage_idx,
366                runtime_slot,
367                event_prop,
368                false,
369                false,
370            );
371        }
372        default_push(ctx);
373        return Ok(true);
374    }
375
376    let stage_form = ctx.ids.form_global_stage;
377    let st: &mut StageFormState = ctx.globals.stage_forms.entry(stage_form).or_default();
378    let Some(obj) = object_by_runtime_slot_mut(st, stage_idx, runtime_slot) else {
379        return Ok(false);
380    };
381    let Some(ev) = obj.runtime.prop_events.get_mut(target) else {
382        bail!(
383            "OBJECTEVENT target {:?} is not backed by an object IntEvent",
384            target
385        );
386    };
387
388    if is_set_op(op) {
389        let value = script_args.first().and_then(as_i64).unwrap_or(0) as i32;
390        let total_time = script_args.get(1).and_then(as_i64).unwrap_or(0) as i32;
391        let delay_time = script_args.get(2).and_then(as_i64).unwrap_or(0) as i32;
392        let speed_type = script_args.get(3).and_then(as_i64).unwrap_or(0) as i32;
393        ev.set_event(value, total_time, delay_time, speed_type, 0);
394        if anim_skip_trace_enabled() {
395            eprintln!(
396                "[SG_DEBUG][ANIM_SKIP_TRACE][OBJECTEVENT] OBJECTEVENT.SET target={:?} stage={} slot={} value={} total_time={} delay={} speed={} state=[{}]",
397                target, stage_idx, runtime_slot, value, total_time, delay_time, speed_type, int_event_state(ev)
398            );
399        }
400        default_push(ctx);
401        return Ok(true);
402    }
403
404    if is_loop_op(op) {
405        let start_value = script_args.first().and_then(as_i64).unwrap_or(0) as i32;
406        let end_value = script_args.get(1).and_then(as_i64).unwrap_or(0) as i32;
407        let loop_time = script_args.get(2).and_then(as_i64).unwrap_or(0) as i32;
408        let delay_time = script_args.get(3).and_then(as_i64).unwrap_or(0) as i32;
409        ev.loop_event(start_value, end_value, loop_time, delay_time, 0, 0);
410        if anim_skip_trace_enabled() {
411            eprintln!(
412                "[SG_DEBUG][ANIM_SKIP_TRACE][OBJECTEVENT] OBJECTEVENT.LOOP target={:?} stage={} slot={} start={} end={} loop_time={} delay={} state=[{}]",
413                target, stage_idx, runtime_slot, start_value, end_value, loop_time, delay_time, int_event_state(ev)
414            );
415        }
416        default_push(ctx);
417        return Ok(true);
418    }
419
420    if is_turn_op(op) {
421        let start_value = script_args.first().and_then(as_i64).unwrap_or(0) as i32;
422        let end_value = script_args.get(1).and_then(as_i64).unwrap_or(0) as i32;
423        let loop_time = script_args.get(2).and_then(as_i64).unwrap_or(0) as i32;
424        let delay_time = script_args.get(3).and_then(as_i64).unwrap_or(0) as i32;
425        ev.turn_event(start_value, end_value, loop_time, delay_time, 0, 0);
426        if anim_skip_trace_enabled() {
427            eprintln!(
428                "[SG_DEBUG][ANIM_SKIP_TRACE][OBJECTEVENT] OBJECTEVENT.TURN target={:?} stage={} slot={} start={} end={} loop_time={} delay={} state=[{}]",
429                target, stage_idx, runtime_slot, start_value, end_value, loop_time, delay_time, int_event_state(ev)
430            );
431        }
432        default_push(ctx);
433        return Ok(true);
434    }
435
436    if is_stop_op(op) {
437        if anim_skip_trace_enabled() {
438            eprintln!(
439                "[SG_DEBUG][ANIM_SKIP_TRACE][OBJECTEVENT] OBJECTEVENT.STOP before target={:?} stage={} slot={} state=[{}]",
440                target, stage_idx, runtime_slot, int_event_state(ev)
441            );
442        }
443        ev.end_event();
444        if anim_skip_trace_enabled() {
445            eprintln!(
446                "[SG_DEBUG][ANIM_SKIP_TRACE][OBJECTEVENT] OBJECTEVENT.STOP after target={:?} stage={} slot={} state=[{}]",
447                target, stage_idx, runtime_slot, int_event_state(ev)
448            );
449        }
450        default_push(ctx);
451        return Ok(true);
452    }
453
454    bail!("unsupported OBJECTEVENT op {}", op)
455}
456
457fn object_runtime_slot_by_stage_index(
458    st: &StageFormState,
459    stage_idx: i64,
460    object_idx: usize,
461) -> Option<usize> {
462    st.object_lists
463        .get(&stage_idx)
464        .and_then(|list| list.get(object_idx))
465        .map(|obj| obj.runtime_slot_or(object_idx))
466}
467
468pub fn dispatch(ctx: &mut CommandContext, args: &[Value]) -> Result<bool> {
469    let Some((chain_pos, chain)) = parse_chain(ctx, args) else {
470        return Ok(false);
471    };
472    if chain.len() < 2 {
473        return Ok(false);
474    }
475    let op = chain[1];
476    let script_args = prop_access::script_args(args, chain_pos);
477    let Some((stage_idx, runtime_slot)) = ctx.globals.current_stage_object else {
478        return Ok(false);
479    };
480
481    dispatch_object_event_on_runtime_slot(ctx, stage_idx, runtime_slot, op, script_args)
482}
483
484pub fn dispatch_list(ctx: &mut CommandContext, args: &[Value]) -> Result<bool> {
485    let Some((chain_pos, chain)) =
486        prop_access::parse_element_chain_ctx(ctx, FM_OBJECTEVENTLIST as u32, args)
487    else {
488        return Ok(false);
489    };
490    if chain.len() < 3 {
491        bail!("OBJECTEVENTLIST.ARRAY requires an index");
492    }
493    if chain[1] != ELM_ARRAY && chain[1] != elm_value::OBJECTEVENTLIST_ARRAY {
494        bail!("unsupported OBJECTEVENTLIST op {}", chain[1]);
495    }
496
497    if chain.len() == 3 {
498        ctx.push(Value::Element(chain.to_vec()));
499        return Ok(true);
500    }
501
502    if chain[2] < 0 {
503        bail!(
504            "OBJECTEVENTLIST.ARRAY index must be non-negative: {}",
505            chain[2]
506        );
507    }
508
509    let Some((stage_idx, _ambient_runtime_slot)) = ctx.globals.current_stage_object else {
510        return Ok(false);
511    };
512    let object_idx = chain[2] as usize;
513    let op = chain[3];
514    let script_args = prop_access::script_args(args, chain_pos);
515
516    let runtime_slot = {
517        let stage_form = ctx.ids.form_global_stage;
518        let Some(st) = ctx.globals.stage_forms.get(&stage_form) else {
519            return Ok(false);
520        };
521        let Some(runtime_slot) = object_runtime_slot_by_stage_index(st, stage_idx, object_idx)
522        else {
523            bail!(
524                "OBJECTEVENTLIST.ARRAY[{}] has no object in stage {}",
525                object_idx,
526                stage_idx
527            );
528        };
529        runtime_slot
530    };
531
532    dispatch_object_event_on_runtime_slot(ctx, stage_idx, runtime_slot, op, script_args)
533}