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 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}