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    std::env::var_os("SG_DEBUG").is_some()
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, ev.cur_value, ev.start_value, ev.end_value, ev.cur_time, ev.end_time,
55        ev.delay_time, ev.loop_type, ev.speed_type, ev.real_flag, ev.check_event()
56    )
57}
58
59fn dispatch_int_event_exact(
60    ev: &mut IntEvent,
61    sub_op: i32,
62    params: &[Value],
63    ret_form: i32,
64) -> (Option<Value>, MaskPostAction) {
65    match sub_op {
66        x if x == constants::elm_value::INTEVENT_SET
67            || x == constants::elm_value::INTEVENT_SET_REAL =>
68        {
69            let value = params.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
70            let total_time = params.get(1).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
71            let delay_time = params.get(2).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
72            let speed_type = params.get(3).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
73            let real_flag = if x == constants::elm_value::INTEVENT_SET_REAL {
74                1
75            } else {
76                0
77            };
78            ev.set_event(value, total_time, delay_time, speed_type, real_flag);
79            (None, MaskPostAction::None)
80        }
81        x if x == constants::elm_value::INTEVENT_LOOP
82            || x == constants::elm_value::INTEVENT_LOOP_REAL =>
83        {
84            let start_value = params.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
85            let end_value = params.get(1).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
86            let loop_time = params.get(2).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
87            let delay_time = params.get(3).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
88            let speed_type = params.get(4).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
89            let real_flag = if x == constants::elm_value::INTEVENT_LOOP_REAL {
90                1
91            } else {
92                0
93            };
94            ev.loop_event(
95                start_value,
96                end_value,
97                loop_time,
98                delay_time,
99                speed_type,
100                real_flag,
101            );
102            (None, MaskPostAction::None)
103        }
104        x if x == constants::elm_value::INTEVENT_TURN
105            || x == constants::elm_value::INTEVENT_TURN_REAL =>
106        {
107            let start_value = params.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
108            let end_value = params.get(1).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
109            let loop_time = params.get(2).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
110            let delay_time = params.get(3).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
111            let speed_type = params.get(4).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
112            let real_flag = if x == constants::elm_value::INTEVENT_TURN_REAL {
113                1
114            } else {
115                0
116            };
117            ev.turn_event(
118                start_value,
119                end_value,
120                loop_time,
121                delay_time,
122                speed_type,
123                real_flag,
124            );
125            (None, MaskPostAction::None)
126        }
127        x if x == constants::elm_value::INTEVENT_END => {
128            ev.end_event();
129            (None, MaskPostAction::None)
130        }
131        x if x == constants::elm_value::INTEVENT_WAIT => (None, MaskPostAction::Wait(false)),
132        x if x == constants::elm_value::INTEVENT_WAIT_KEY => (None, MaskPostAction::Wait(true)),
133        x if x == constants::elm_value::INTEVENT_CHECK => {
134            let v = if ret_form != 0 && ev.check_event() {
135                1
136            } else {
137                0
138            };
139            (Some(Value::Int(v)), MaskPostAction::None)
140        }
141        _ => (None, MaskPostAction::None),
142    }
143}
144
145pub fn dispatch(ctx: &mut CommandContext, form_id: u32, args: &[Value]) -> Result<bool> {
146    let Some((chain_pos, chain)) =
147        crate::runtime::forms::prop_access::parse_element_chain_ctx(ctx, form_id, args)
148            .map(|(i, ch)| (i, ch.to_vec()))
149    else {
150        return Ok(false);
151    };
152    if !is_mask_like_chain(ctx, form_id, &chain) {
153        return Ok(false);
154    }
155
156    let params = crate::runtime::forms::prop_access::script_args(args, chain_pos);
157    let (meta_al_id, meta_ret_form) = crate::runtime::forms::prop_access::current_vm_meta(ctx);
158    let al_id = meta_al_id.unwrap_or(0) as i32;
159    let ret_form = meta_ret_form.unwrap_or(0) as i32;
160    let elm_array = ctx.ids.elm_array;
161
162    let cnt = mask_cnt(ctx);
163    let (handled, ret, post_action): (bool, Option<Value>, MaskPostAction) = 'blk: {
164        let ml = ctx
165            .globals
166            .mask_lists
167            .entry(form_id)
168            .or_insert_with(|| MaskListState::new(cnt));
169        if cnt > 0 {
170            ml.ensure_size(cnt);
171        }
172
173        if chain.len() == 2 && !is_array_code(elm_array, chain[1]) {
174            if chain[1] == constants::elm_value::MASKLIST_GET_SIZE && ret_form != 0 {
175                break 'blk (
176                    true,
177                    Some(Value::Int(ml.masks.len() as i64)),
178                    MaskPostAction::None,
179                );
180            }
181            break 'blk (true, None, MaskPostAction::None);
182        }
183
184        if chain.len() < 4 || !is_array_code(elm_array, chain[1]) {
185            let r = if ret_form != 0 {
186                Some(default_for_ret_form(ret_form))
187            } else {
188                None
189            };
190            break 'blk (true, r, MaskPostAction::None);
191        }
192
193        let idx = chain.get(2).copied().unwrap_or(0).max(0) as usize;
194        ml.ensure_size(cnt.max(idx + 1));
195        let mask = &mut ml.masks[idx];
196
197        let op = chain[3];
198        if chain.len() >= 5 {
199            let target_ev = match op {
200                x if x == constants::elm_value::MASK_X_EVE => &mut mask.x_event,
201                x if x == constants::elm_value::MASK_Y_EVE => &mut mask.y_event,
202                _ => {
203                    break 'blk (true, None, MaskPostAction::None);
204                }
205            };
206            let sub_op = chain[4];
207            let (r, action) = dispatch_int_event_exact(target_ev, sub_op, params, ret_form);
208            if anim_skip_trace_enabled() {
209                eprintln!(
210                    "[SG_DEBUG][ANIM_SKIP_TRACE][MASK] form={} idx={} op={} subop={} params={:?} action={} state=[{}]",
211                    form_id,
212                    idx,
213                    op,
214                    sub_op,
215                    params,
216                    match action { MaskPostAction::None => "None", MaskPostAction::Wait(true) => "WaitKey", MaskPostAction::Wait(false) => "Wait" },
217                    mask_event_state(target_ev)
218                );
219            }
220            break 'blk (true, r, action);
221        }
222
223        match op {
224            x if x == constants::elm_value::MASK_INIT => {
225                mask.reinit();
226                break 'blk (true, None, MaskPostAction::None);
227            }
228            x if x == constants::elm_value::MASK_CREATE => {
229                let name = params.first().and_then(|v| v.as_str()).map(str::to_string);
230                mask.reinit();
231                mask.name = name;
232                break 'blk (true, None, MaskPostAction::None);
233            }
234            x if x == constants::elm_value::MASK_X => {
235                if al_id == 0 {
236                    let r = if ret_form != 0 {
237                        Some(Value::Int(mask.x_event.get_total_value() as i64))
238                    } else {
239                        None
240                    };
241                    break 'blk (true, r, MaskPostAction::None);
242                }
243                if al_id == 1 {
244                    let v = params.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
245                    mask.x_event.set_value(v);
246                    mask.x_event.frame();
247                    break 'blk (true, None, MaskPostAction::None);
248                }
249                break 'blk (true, None, MaskPostAction::None);
250            }
251            x if x == constants::elm_value::MASK_Y => {
252                if al_id == 0 {
253                    let r = if ret_form != 0 {
254                        Some(Value::Int(mask.y_event.get_total_value() as i64))
255                    } else {
256                        None
257                    };
258                    break 'blk (true, r, MaskPostAction::None);
259                }
260                if al_id == 1 {
261                    let v = params.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
262                    mask.y_event.set_value(v);
263                    mask.y_event.frame();
264                    break 'blk (true, None, MaskPostAction::None);
265                }
266                break 'blk (true, None, MaskPostAction::None);
267            }
268            _ => {
269                let r = if ret_form != 0 {
270                    Some(default_for_ret_form(ret_form))
271                } else {
272                    None
273                };
274                break 'blk (true, r, MaskPostAction::None);
275            }
276        }
277    };
278
279    if let Some(v) = ret {
280        ctx.push(v);
281    }
282    match post_action {
283        MaskPostAction::None => {}
284        MaskPostAction::Wait(key_skip) => {
285            if anim_skip_trace_enabled() {
286                eprintln!(
287                    "[SG_DEBUG][ANIM_SKIP_TRACE][MASK] wait_requested key_skip={} NOTE=current implementation routes through generic form_id=0",
288                    key_skip
289                );
290            }
291            ctx.wait.wait_generic_int_event(0, None, key_skip, key_skip)
292        }
293    }
294    Ok(handled)
295}