Skip to main content

siglus_scene_vm/runtime/forms/
mask.rs

1use anyhow::Result;
2
3use super::prop_access;
4use crate::runtime::constants;
5use crate::runtime::globals::MaskListState;
6use crate::runtime::int_event::IntEvent;
7use crate::runtime::{CommandContext, Value};
8
9fn default_for_ret_form(ret_form: i32) -> Value {
10    if prop_access::ret_form_is_string(ret_form as i64) {
11        Value::Str(String::new())
12    } else {
13        Value::Int(0)
14    }
15}
16
17fn mask_cnt(ctx: &CommandContext) -> usize {
18    ctx.tables
19        .gameexe
20        .as_ref()
21        .map(|cfg| cfg.indexed_count("MASK"))
22        .unwrap_or(0)
23}
24
25fn is_array_code(elm_array: i32, code: i32) -> bool {
26    code == elm_array
27        || code == crate::runtime::forms::codes::ELM_ARRAY
28        || code == constants::elm_value::MASKLIST_ARRAY
29}
30
31fn is_mask_like_chain(ctx: &CommandContext, form_id: u32, chain: &[i32]) -> bool {
32    if chain.is_empty() || chain[0] as u32 != form_id {
33        return false;
34    }
35    if chain.len() == 2 {
36        return !is_array_code(ctx.ids.elm_array, chain[1]);
37    }
38    chain.len() >= 3 && is_array_code(ctx.ids.elm_array, chain[1])
39}
40
41#[derive(Debug, Clone, Copy)]
42enum MaskPostAction {
43    None,
44    Wait(bool),
45}
46
47fn anim_skip_trace_enabled() -> bool {
48    env_is_set!("SG_DEBUG")
49}
50
51fn mask_event_state(ev: &IntEvent) -> String {
52    format!(
53        "value={} cur={} start={} end={} cur_time={} end_time={} delay={} loop_type={} speed={} real={} active={}",
54        ev.value,
55        ev.cur_value,
56        ev.start_value,
57        ev.end_value,
58        ev.cur_time,
59        ev.end_time,
60        ev.delay_time,
61        ev.loop_type,
62        ev.speed_type,
63        ev.real_flag,
64        ev.check_event()
65    )
66}
67
68fn dispatch_int_event_exact(
69    ev: &mut IntEvent,
70    sub_op: i32,
71    params: &[Value],
72    ret_form: i32,
73) -> (Option<Value>, MaskPostAction) {
74    match sub_op {
75        x @ (constants::elm_value::INTEVENT_SET | constants::elm_value::INTEVENT_SET_REAL) => {
76            let value = params.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
77            let total_time = params.get(1).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
78            let delay_time = params.get(2).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
79            let speed_type = params.get(3).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
80            let real_flag = if x == constants::elm_value::INTEVENT_SET_REAL {
81                1
82            } else {
83                0
84            };
85            ev.set_event(value, total_time, delay_time, speed_type, real_flag);
86            (None, MaskPostAction::None)
87        }
88        x @ (constants::elm_value::INTEVENT_LOOP | constants::elm_value::INTEVENT_LOOP_REAL) => {
89            let start_value = params.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
90            let end_value = params.get(1).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
91            let loop_time = params.get(2).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
92            let delay_time = params.get(3).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
93            let speed_type = params.get(4).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
94            let real_flag = if x == constants::elm_value::INTEVENT_LOOP_REAL {
95                1
96            } else {
97                0
98            };
99            ev.loop_event(
100                start_value,
101                end_value,
102                loop_time,
103                delay_time,
104                speed_type,
105                real_flag,
106            );
107            (None, MaskPostAction::None)
108        }
109        x @ (constants::elm_value::INTEVENT_TURN | constants::elm_value::INTEVENT_TURN_REAL) => {
110            let start_value = params.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
111            let end_value = params.get(1).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
112            let loop_time = params.get(2).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
113            let delay_time = params.get(3).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
114            let speed_type = params.get(4).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
115            let real_flag = if x == constants::elm_value::INTEVENT_TURN_REAL {
116                1
117            } else {
118                0
119            };
120            ev.turn_event(
121                start_value,
122                end_value,
123                loop_time,
124                delay_time,
125                speed_type,
126                real_flag,
127            );
128            (None, MaskPostAction::None)
129        }
130        constants::elm_value::INTEVENT_END => {
131            ev.end_event();
132            (None, MaskPostAction::None)
133        }
134        constants::elm_value::INTEVENT_WAIT => (None, MaskPostAction::Wait(false)),
135        constants::elm_value::INTEVENT_WAIT_KEY => (None, MaskPostAction::Wait(true)),
136        constants::elm_value::INTEVENT_CHECK => {
137            let v = if ret_form != 0 && ev.check_event() {
138                1
139            } else {
140                0
141            };
142            (Some(Value::Int(v)), MaskPostAction::None)
143        }
144        _ => (None, MaskPostAction::None),
145    }
146}
147
148pub fn dispatch(ctx: &mut CommandContext, form_id: u32, args: &[Value]) -> Result<bool> {
149    let Some((chain_pos, chain)) =
150        crate::runtime::forms::prop_access::parse_element_chain_ctx(ctx, form_id, args)
151            .map(|(i, ch)| (i, ch.to_vec()))
152    else {
153        return Ok(false);
154    };
155    if !is_mask_like_chain(ctx, form_id, &chain) {
156        return Ok(false);
157    }
158
159    let params = crate::runtime::forms::prop_access::script_args(args, chain_pos);
160    let (meta_al_id, meta_ret_form) = crate::runtime::forms::prop_access::current_vm_meta(ctx);
161    let al_id = meta_al_id.unwrap_or(0) as i32;
162    let ret_form = meta_ret_form.unwrap_or(0) as i32;
163    let elm_array = ctx.ids.elm_array;
164
165    let cnt = mask_cnt(ctx);
166    let mut wait_target: Option<(usize, i32)> = None;
167    let (handled, ret, post_action): (bool, Option<Value>, MaskPostAction) = 'blk: {
168        let ml = ctx
169            .globals
170            .mask_lists
171            .entry(form_id)
172            .or_insert_with(|| MaskListState::new(cnt));
173        if cnt > 0 {
174            ml.ensure_size(cnt);
175        }
176
177        if chain.len() == 2 && !is_array_code(elm_array, chain[1]) {
178            if chain[1] == constants::elm_value::MASKLIST_GET_SIZE && ret_form != 0 {
179                break 'blk (
180                    true,
181                    Some(Value::Int(ml.masks.len() as i64)),
182                    MaskPostAction::None,
183                );
184            }
185            break 'blk (true, None, MaskPostAction::None);
186        }
187
188        if chain.len() < 4 || !is_array_code(elm_array, chain[1]) {
189            let r = if ret_form != 0 {
190                Some(default_for_ret_form(ret_form))
191            } else {
192                None
193            };
194            break 'blk (true, r, MaskPostAction::None);
195        }
196
197        let idx = chain.get(2).copied().unwrap_or(0).max(0) as usize;
198        ml.ensure_size(cnt.max(idx + 1));
199        let mask = &mut ml.masks[idx];
200
201        let op = chain[3];
202        if chain.len() >= 5 {
203            let target_ev = match op {
204                constants::elm_value::MASK_X_EVE => &mut mask.x_event,
205                constants::elm_value::MASK_Y_EVE => &mut mask.y_event,
206                _ => {
207                    break 'blk (true, None, MaskPostAction::None);
208                }
209            };
210            let sub_op = chain[4];
211            let (r, action) = dispatch_int_event_exact(target_ev, sub_op, params, ret_form);
212            if matches!(action, MaskPostAction::Wait(_)) {
213                wait_target = Some((idx, op));
214            }
215            if anim_skip_trace_enabled() {
216                eprintln!(
217                    "[SG_DEBUG][ANIM_SKIP_TRACE][MASK] form={} idx={} op={} subop={} params={:?} action={} state=[{}]",
218                    form_id,
219                    idx,
220                    op,
221                    sub_op,
222                    params,
223                    match action {
224                        MaskPostAction::None => "None",
225                        MaskPostAction::Wait(true) => "WaitKey",
226                        MaskPostAction::Wait(false) => "Wait",
227                    },
228                    mask_event_state(target_ev)
229                );
230            }
231            break 'blk (true, r, action);
232        }
233
234        match (op, al_id) {
235            (constants::elm_value::MASK_INIT, _) => {
236                mask.reinit();
237                break 'blk (true, None, MaskPostAction::None);
238            }
239            (constants::elm_value::MASK_CREATE, _) => {
240                let name = params.first().and_then(|v| v.as_str()).map(str::to_string);
241                mask.reinit();
242                mask.name = name;
243                break 'blk (true, None, MaskPostAction::None);
244            }
245            (constants::elm_value::MASK_X, 0) => {
246                let r = if ret_form != 0 {
247                    Some(Value::Int(mask.x_event.get_total_value() as i64))
248                } else {
249                    None
250                };
251                break 'blk (true, r, MaskPostAction::None);
252            }
253            (constants::elm_value::MASK_X, 1) => {
254                let v = params.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
255                mask.x_event.set_value(v);
256                mask.x_event.frame();
257                break 'blk (true, None, MaskPostAction::None);
258            }
259            (constants::elm_value::MASK_Y, 0) => {
260                let r = if ret_form != 0 {
261                    Some(Value::Int(mask.y_event.get_total_value() as i64))
262                } else {
263                    None
264                };
265                break 'blk (true, r, MaskPostAction::None);
266            }
267            (constants::elm_value::MASK_Y, 1) => {
268                let v = params.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
269                mask.y_event.set_value(v);
270                mask.y_event.frame();
271                break 'blk (true, None, MaskPostAction::None);
272            }
273            (constants::elm_value::MASK_X | constants::elm_value::MASK_Y, _) => {
274                break 'blk (true, None, MaskPostAction::None);
275            }
276            _ => {
277                let r = if ret_form != 0 {
278                    Some(default_for_ret_form(ret_form))
279                } else {
280                    None
281                };
282                break 'blk (true, r, MaskPostAction::None);
283            }
284        }
285    };
286
287    if let Some(v) = ret {
288        ctx.push(v);
289    }
290    match post_action {
291        MaskPostAction::None => {}
292        MaskPostAction::Wait(key_skip) => {
293            if let Some((index, op)) = wait_target {
294                ctx.wait
295                    .wait_mask_event(form_id, index, op, key_skip, key_skip);
296            } else {
297                log::error!(
298                    "MASK INTEVENT wait target was lost: form_id={} key_skip={}",
299                    form_id,
300                    key_skip
301                );
302            }
303        }
304    }
305    Ok(handled)
306}