Skip to main content

siglus_scene_vm/runtime/forms/
object_event.rs

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