siglus_scene_vm/runtime/forms/
mask.rs1use 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}