Skip to main content

siglus_scene_vm/runtime/forms/
screen.rs

1//! GLOBAL.SCREEN form handling aligned to the original screen command path.
2//!
3//! The original C++ `tnm_command_proc_screen()` dispatches strictly by the
4//! current element chain. It does not recover SCREEN selectors or setter RHS
5//! values from arbitrary argument positions.
6
7use anyhow::Result;
8
9use super::prop_access;
10use crate::runtime::forms::codes::int_event_op;
11use crate::runtime::globals::{ScreenEffectState, ScreenFormState, ScreenQuakeState};
12use crate::runtime::{CommandContext, Value};
13
14const EFFECTLIST_RESIZE_OP: i32 = 1;
15const EFFECTLIST_GET_SIZE_OP: i32 = 2;
16
17const QUAKE_START_OP: i32 = 0;
18const QUAKE_START_WAIT_OP: i32 = 1;
19const QUAKE_START_WAIT_KEY_OP: i32 = 2;
20const QUAKE_START_NOWAIT_OP: i32 = 3;
21const QUAKE_START_ALL_OP: i32 = 4;
22const QUAKE_START_ALL_WAIT_OP: i32 = 5;
23const QUAKE_START_ALL_WAIT_KEY_OP: i32 = 6;
24const QUAKE_START_ALL_NOWAIT_OP: i32 = 7;
25const QUAKE_END_OP: i32 = 8;
26const QUAKE_CHECK_OP: i32 = 9;
27const QUAKE_WAIT_OP: i32 = 10;
28const QUAKE_WAIT_KEY_OP: i32 = 11;
29
30fn as_i64(v: &Value) -> Option<i64> {
31    match v {
32        Value::Int(n) => Some(*n),
33        Value::NamedArg { value, .. } => value.as_i64(),
34        _ => None,
35    }
36}
37
38fn default_for_ret_form(ret_form: i64) -> Value {
39    if prop_access::ret_form_is_string(ret_form) {
40        Value::Str(String::new())
41    } else {
42        Value::Int(0)
43    }
44}
45
46fn anim_skip_trace_enabled() -> bool {
47    env_is_set!("SG_DEBUG")
48}
49
50fn screen_event_state(ev: &crate::runtime::int_event::IntEvent) -> String {
51    format!(
52        "value={} cur={} start={} end={} cur_time={} end_time={} delay={} loop_type={} speed={} real={} active={}",
53        ev.value,
54        ev.cur_value,
55        ev.start_value,
56        ev.end_value,
57        ev.cur_time,
58        ev.end_time,
59        ev.delay_time,
60        ev.loop_type,
61        ev.speed_type,
62        ev.real_flag,
63        ev.check_event()
64    )
65}
66
67fn effect_prop_ref_mut<'a>(
68    ids: &crate::runtime::constants::RuntimeConstants,
69    effect: &'a mut ScreenEffectState,
70    op: i32,
71) -> Option<&'a mut i32> {
72    match op {
73        s if s == ids.effect_wipe_copy => Some(&mut effect.wipe_copy),
74        s if s == ids.effect_wipe_erase => Some(&mut effect.wipe_erase),
75        s if s == ids.effect_begin_order => Some(&mut effect.begin_order),
76        s if s == ids.effect_begin_layer => Some(&mut effect.begin_layer),
77        s if s == ids.effect_end_order => Some(&mut effect.end_order),
78        s if s == ids.effect_end_layer => Some(&mut effect.end_layer),
79        _ => None,
80    }
81}
82
83fn effect_prop_value(
84    ids: &crate::runtime::constants::RuntimeConstants,
85    effect: &ScreenEffectState,
86    op: i32,
87) -> Option<i64> {
88    match op {
89        s if s == ids.effect_wipe_copy => Some(effect.wipe_copy as i64),
90        s if s == ids.effect_wipe_erase => Some(effect.wipe_erase as i64),
91        s if s == ids.effect_begin_order => Some(effect.begin_order as i64),
92        s if s == ids.effect_begin_layer => Some(effect.begin_layer as i64),
93        s if s == ids.effect_end_order => Some(effect.end_order as i64),
94        s if s == ids.effect_end_layer => Some(effect.end_layer as i64),
95        _ => None,
96    }
97}
98
99fn effect_event_mut<'a>(
100    ids: &crate::runtime::constants::RuntimeConstants,
101    effect: &'a mut ScreenEffectState,
102    op: i32,
103) -> Option<&'a mut crate::runtime::int_event::IntEvent> {
104    match op {
105        s if s == ids.effect_x || s == ids.effect_x_eve => Some(&mut effect.x),
106        s if s == ids.effect_y || s == ids.effect_y_eve => Some(&mut effect.y),
107        s if s == ids.effect_z || s == ids.effect_z_eve => Some(&mut effect.z),
108        s if s == ids.effect_mono || s == ids.effect_mono_eve => Some(&mut effect.mono),
109        s if s == ids.effect_reverse || s == ids.effect_reverse_eve => Some(&mut effect.reverse),
110        s if s == ids.effect_bright || s == ids.effect_bright_eve => Some(&mut effect.bright),
111        s if s == ids.effect_dark || s == ids.effect_dark_eve => Some(&mut effect.dark),
112        s if s == ids.effect_color_r || s == ids.effect_color_r_eve => Some(&mut effect.color_r),
113        s if s == ids.effect_color_g || s == ids.effect_color_g_eve => Some(&mut effect.color_g),
114        s if s == ids.effect_color_b || s == ids.effect_color_b_eve => Some(&mut effect.color_b),
115        s if s == ids.effect_color_rate || s == ids.effect_color_rate_eve => {
116            Some(&mut effect.color_rate)
117        }
118        s if s == ids.effect_color_add_r || s == ids.effect_color_add_r_eve => {
119            Some(&mut effect.color_add_r)
120        }
121        s if s == ids.effect_color_add_g || s == ids.effect_color_add_g_eve => {
122            Some(&mut effect.color_add_g)
123        }
124        s if s == ids.effect_color_add_b || s == ids.effect_color_add_b_eve => {
125            Some(&mut effect.color_add_b)
126        }
127        _ => None,
128    }
129}
130
131fn run_int_event_command(
132    ctx: &mut CommandContext,
133    ev: &mut crate::runtime::int_event::IntEvent,
134    form_id: u32,
135    effect_index: usize,
136    effect_op: i32,
137    subop: i32,
138    params: &[Value],
139    ret_form: i64,
140) {
141    match subop {
142        int_event_op::SET | int_event_op::SET_REAL => {
143            let value = params.first().and_then(as_i64).unwrap_or(0) as i32;
144            let total_time = params.get(1).and_then(as_i64).unwrap_or(0) as i32;
145            let delay_time = params.get(2).and_then(as_i64).unwrap_or(0) as i32;
146            let speed_type = params.get(3).and_then(as_i64).unwrap_or(0) as i32;
147            let real_flag = if subop == int_event_op::SET_REAL {
148                1
149            } else {
150                0
151            };
152            ev.set_event(value, total_time, delay_time, speed_type, real_flag);
153            if anim_skip_trace_enabled() {
154                eprintln!(
155                    "[SG_DEBUG][ANIM_SKIP_TRACE][SCREEN] INTEVENT.SET subop={} value={} total_time={} delay={} speed={} real={} state=[{}]",
156                    subop,
157                    value,
158                    total_time,
159                    delay_time,
160                    speed_type,
161                    real_flag,
162                    screen_event_state(ev)
163                );
164            }
165            ctx.stack.push(default_for_ret_form(ret_form));
166        }
167        int_event_op::LOOP | int_event_op::LOOP_REAL => {
168            let start_value = params.first().and_then(as_i64).unwrap_or(0) as i32;
169            let end_value = params.get(1).and_then(as_i64).unwrap_or(0) as i32;
170            let loop_time = params.get(2).and_then(as_i64).unwrap_or(0) as i32;
171            let delay_time = params.get(3).and_then(as_i64).unwrap_or(0) as i32;
172            let speed_type = params.get(4).and_then(as_i64).unwrap_or(0) as i32;
173            let real_flag = if subop == int_event_op::LOOP_REAL {
174                1
175            } else {
176                0
177            };
178            ev.loop_event(
179                start_value,
180                end_value,
181                loop_time,
182                delay_time,
183                speed_type,
184                real_flag,
185            );
186            if anim_skip_trace_enabled() {
187                eprintln!(
188                    "[SG_DEBUG][ANIM_SKIP_TRACE][SCREEN] INTEVENT.LOOP subop={} start={} end={} loop_time={} delay={} speed={} real={} state=[{}]",
189                    subop,
190                    start_value,
191                    end_value,
192                    loop_time,
193                    delay_time,
194                    speed_type,
195                    real_flag,
196                    screen_event_state(ev)
197                );
198            }
199            ctx.stack.push(default_for_ret_form(ret_form));
200        }
201        int_event_op::TURN | int_event_op::TURN_REAL => {
202            let start_value = params.first().and_then(as_i64).unwrap_or(0) as i32;
203            let end_value = params.get(1).and_then(as_i64).unwrap_or(0) as i32;
204            let loop_time = params.get(2).and_then(as_i64).unwrap_or(0) as i32;
205            let delay_time = params.get(3).and_then(as_i64).unwrap_or(0) as i32;
206            let speed_type = params.get(4).and_then(as_i64).unwrap_or(0) as i32;
207            let real_flag = if subop == int_event_op::TURN_REAL {
208                1
209            } else {
210                0
211            };
212            ev.turn_event(
213                start_value,
214                end_value,
215                loop_time,
216                delay_time,
217                speed_type,
218                real_flag,
219            );
220            if anim_skip_trace_enabled() {
221                eprintln!(
222                    "[SG_DEBUG][ANIM_SKIP_TRACE][SCREEN] INTEVENT.TURN subop={} start={} end={} loop_time={} delay={} speed={} real={} state=[{}]",
223                    subop,
224                    start_value,
225                    end_value,
226                    loop_time,
227                    delay_time,
228                    speed_type,
229                    real_flag,
230                    screen_event_state(ev)
231                );
232            }
233            ctx.stack.push(default_for_ret_form(ret_form));
234        }
235        int_event_op::END => {
236            if anim_skip_trace_enabled() {
237                eprintln!(
238                    "[SG_DEBUG][ANIM_SKIP_TRACE][SCREEN] INTEVENT.END before state=[{}]",
239                    screen_event_state(ev)
240                );
241            }
242            ev.end_event();
243            if anim_skip_trace_enabled() {
244                eprintln!(
245                    "[SG_DEBUG][ANIM_SKIP_TRACE][SCREEN] INTEVENT.END after state=[{}]",
246                    screen_event_state(ev)
247                );
248            }
249            ctx.stack.push(default_for_ret_form(ret_form));
250        }
251        int_event_op::WAIT => {
252            ctx.wait
253                .wait_screen_effect(form_id, effect_index, effect_op, false, false)
254        }
255        int_event_op::WAIT_KEY => {
256            ctx.wait
257                .wait_screen_effect(form_id, effect_index, effect_op, true, true)
258        }
259        int_event_op::CHECK => ctx
260            .stack
261            .push(Value::Int(if ev.check_event() { 1 } else { 0 })),
262        _ => ctx.stack.push(default_for_ret_form(ret_form)),
263    }
264}
265
266fn screen_to_effect_prop(
267    ids: &crate::runtime::constants::RuntimeConstants,
268    selector: i32,
269) -> Option<i32> {
270    match selector {
271        s if ids.screen_x != 0 && s == ids.screen_x => Some(ids.effect_x),
272        s if ids.screen_y != 0 && s == ids.screen_y => Some(ids.effect_y),
273        s if ids.screen_z != 0 && s == ids.screen_z => Some(ids.effect_z),
274        s if ids.screen_mono != 0 && s == ids.screen_mono => Some(ids.effect_mono),
275        s if ids.screen_reverse != 0 && s == ids.screen_reverse => Some(ids.effect_reverse),
276        s if ids.screen_bright != 0 && s == ids.screen_bright => Some(ids.effect_bright),
277        s if ids.screen_dark != 0 && s == ids.screen_dark => Some(ids.effect_dark),
278        s if ids.screen_color_r != 0 && s == ids.screen_color_r => Some(ids.effect_color_r),
279        s if ids.screen_color_g != 0 && s == ids.screen_color_g => Some(ids.effect_color_g),
280        s if ids.screen_color_b != 0 && s == ids.screen_color_b => Some(ids.effect_color_b),
281        s if ids.screen_color_rate != 0 && s == ids.screen_color_rate => {
282            Some(ids.effect_color_rate)
283        }
284        s if ids.screen_color_add_r != 0 && s == ids.screen_color_add_r => {
285            Some(ids.effect_color_add_r)
286        }
287        s if ids.screen_color_add_g != 0 && s == ids.screen_color_add_g => {
288            Some(ids.effect_color_add_g)
289        }
290        s if ids.screen_color_add_b != 0 && s == ids.screen_color_add_b => {
291            Some(ids.effect_color_add_b)
292        }
293        _ => None,
294    }
295}
296
297fn screen_to_effect_event(
298    ids: &crate::runtime::constants::RuntimeConstants,
299    selector: i32,
300) -> Option<i32> {
301    match selector {
302        s if ids.screen_x_eve != 0 && s == ids.screen_x_eve => Some(ids.effect_x_eve),
303        s if ids.screen_y_eve != 0 && s == ids.screen_y_eve => Some(ids.effect_y_eve),
304        s if ids.screen_z_eve != 0 && s == ids.screen_z_eve => Some(ids.effect_z_eve),
305        s if ids.screen_mono_eve != 0 && s == ids.screen_mono_eve => Some(ids.effect_mono_eve),
306        s if ids.screen_reverse_eve != 0 && s == ids.screen_reverse_eve => {
307            Some(ids.effect_reverse_eve)
308        }
309        s if ids.screen_bright_eve != 0 && s == ids.screen_bright_eve => {
310            Some(ids.effect_bright_eve)
311        }
312        s if ids.screen_dark_eve != 0 && s == ids.screen_dark_eve => Some(ids.effect_dark_eve),
313        s if ids.screen_color_r_eve != 0 && s == ids.screen_color_r_eve => {
314            Some(ids.effect_color_r_eve)
315        }
316        s if ids.screen_color_g_eve != 0 && s == ids.screen_color_g_eve => {
317            Some(ids.effect_color_g_eve)
318        }
319        s if ids.screen_color_b_eve != 0 && s == ids.screen_color_b_eve => {
320            Some(ids.effect_color_b_eve)
321        }
322        s if ids.screen_color_rate_eve != 0 && s == ids.screen_color_rate_eve => {
323            Some(ids.effect_color_rate_eve)
324        }
325        s if ids.screen_color_add_r_eve != 0 && s == ids.screen_color_add_r_eve => {
326            Some(ids.effect_color_add_r_eve)
327        }
328        s if ids.screen_color_add_g_eve != 0 && s == ids.screen_color_add_g_eve => {
329            Some(ids.effect_color_add_g_eve)
330        }
331        s if ids.screen_color_add_b_eve != 0 && s == ids.screen_color_add_b_eve => {
332            Some(ids.effect_color_add_b_eve)
333        }
334        _ => None,
335    }
336}
337
338struct ScreenCall {
339    chain: Vec<i32>,
340    al_id: i32,
341    ret_form: i64,
342    rhs: Option<Value>,
343    script_args: Vec<Value>,
344}
345
346fn parse_screen_call(ctx: &CommandContext, args: &[Value]) -> Option<ScreenCall> {
347    let vm_call = ctx.vm_call.as_ref()?;
348    if vm_call.element.is_empty() {
349        return None;
350    }
351    let (al_id, ret_form) = prop_access::current_vm_meta(ctx);
352    let al_id = al_id.unwrap_or(-1) as i32;
353    let ret_form = ret_form.unwrap_or(0);
354    let rhs = if al_id == 1 {
355        args.first().cloned()
356    } else {
357        None
358    };
359    Some(ScreenCall {
360        chain: vm_call.element.clone(),
361        al_id,
362        ret_form,
363        rhs,
364        script_args: args.to_vec(),
365    })
366}
367
368fn last_list_arg(script_args: &[Value]) -> Option<&Vec<Value>> {
369    script_args.last().and_then(|v| match v.unwrap_named() {
370        Value::List(list) => Some(list),
371        _ => None,
372    })
373}
374
375fn quake_start_flags(op: i32) -> Option<(bool, bool, bool)> {
376    match op {
377        QUAKE_START_OP => Some((false, false, false)),
378        QUAKE_START_WAIT_OP => Some((false, true, false)),
379        QUAKE_START_WAIT_KEY_OP => Some((false, true, true)),
380        QUAKE_START_NOWAIT_OP => Some((false, false, false)),
381        QUAKE_START_ALL_OP => Some((true, false, false)),
382        QUAKE_START_ALL_WAIT_OP => Some((true, true, false)),
383        QUAKE_START_ALL_WAIT_KEY_OP => Some((true, true, true)),
384        QUAKE_START_ALL_NOWAIT_OP => Some((true, false, false)),
385        _ => None,
386    }
387}
388
389fn parse_quake_command(
390    item: &mut ScreenQuakeState,
391    form_id: u32,
392    index: usize,
393    op: i32,
394    script_args: &[Value],
395    ctx: &mut CommandContext,
396) -> bool {
397    if let Some((all_range, wait_flag, key_flag)) = quake_start_flags(op) {
398        let quake_type = script_args.first().and_then(as_i64).unwrap_or(0) as i32;
399        let time = script_args.get(1).and_then(as_i64).unwrap_or(1000);
400        let cnt = script_args.get(2).and_then(as_i64).unwrap_or(0) as i32;
401        let end_cnt = script_args.get(3).and_then(as_i64).unwrap_or(0) as i32;
402        item.begin_order = if all_range {
403            script_args
404                .get(4)
405                .and_then(as_i64)
406                .unwrap_or(i32::MIN as i64) as i32
407        } else {
408            script_args.get(4).and_then(as_i64).unwrap_or(0) as i32
409        };
410        item.end_order = if all_range {
411            script_args
412                .get(5)
413                .and_then(as_i64)
414                .unwrap_or(i32::MAX as i64) as i32
415        } else {
416            script_args.get(5).and_then(as_i64).unwrap_or(0) as i32
417        };
418
419        let opt = last_list_arg(script_args);
420        item.power = opt
421            .and_then(|list| list.first())
422            .and_then(as_i64)
423            .unwrap_or(0) as i32;
424        if quake_type == 2 {
425            item.vec = 0;
426            item.center_x = opt
427                .and_then(|list| list.get(1))
428                .and_then(as_i64)
429                .unwrap_or(0) as i32;
430            item.center_y = opt
431                .and_then(|list| list.get(2))
432                .and_then(as_i64)
433                .unwrap_or(0) as i32;
434        } else {
435            item.vec = opt
436                .and_then(|list| list.get(1))
437                .and_then(as_i64)
438                .unwrap_or(0) as i32;
439            item.center_x = 0;
440            item.center_y = 0;
441        }
442        item.start_kind(quake_type, time, cnt, end_cnt);
443        if wait_flag {
444            ctx.wait.wait_quake(
445                crate::runtime::wait::QuakeWait::Screen { form_id, index },
446                key_flag,
447            );
448        }
449        ctx.stack.push(Value::Int(0));
450        return true;
451    }
452
453    match op {
454        QUAKE_END_OP => {
455            item.end_ms(script_args.first().and_then(as_i64).unwrap_or(0));
456            ctx.stack.push(Value::Int(0));
457            true
458        }
459        QUAKE_WAIT_OP => {
460            ctx.wait.wait_quake(
461                crate::runtime::wait::QuakeWait::Screen { form_id, index },
462                false,
463            );
464            ctx.stack.push(Value::Int(0));
465            true
466        }
467        QUAKE_WAIT_KEY_OP => {
468            ctx.wait.wait_quake(
469                crate::runtime::wait::QuakeWait::Screen { form_id, index },
470                true,
471            );
472            ctx.stack.push(Value::Int(0));
473            true
474        }
475        QUAKE_CHECK_OP => {
476            ctx.stack.push(Value::Int(item.check_value() as i64));
477            true
478        }
479        _ => false,
480    }
481}
482
483fn get_or_set_effect_scalar(
484    ctx: &mut CommandContext,
485    effect: &mut ScreenEffectState,
486    ids: &crate::runtime::constants::RuntimeConstants,
487    op: i32,
488    al_id: i32,
489    rhs: Option<&Value>,
490    ret_form: i64,
491) -> bool {
492    if let Some(ev) = effect_event_mut(ids, effect, op) {
493        match al_id {
494            0 => {
495                ctx.stack.push(Value::Int(ev.get_total_value() as i64));
496                true
497            }
498            1 => {
499                let value = crate::runtime::globals::normalize_screen_effect_scalar(
500                    ids,
501                    op,
502                    rhs.and_then(as_i64).unwrap_or(0) as i32,
503                );
504                ev.set_value(value);
505                ev.frame();
506                ctx.stack.push(default_for_ret_form(ret_form));
507                true
508            }
509            _ => false,
510        }
511    } else if let Some(slot) = effect_prop_ref_mut(ids, effect, op) {
512        match al_id {
513            0 => {
514                ctx.stack.push(Value::Int(*slot as i64));
515                true
516            }
517            1 => {
518                let value = rhs.and_then(as_i64).unwrap_or(0) as i32;
519                *slot = if op == ids.effect_wipe_copy || op == ids.effect_wipe_erase {
520                    if value != 0 { 1 } else { 0 }
521                } else {
522                    value
523                };
524                ctx.stack.push(default_for_ret_form(ret_form));
525                true
526            }
527            _ => false,
528        }
529    } else if let Some(value) = effect_prop_value(ids, effect, op) {
530        if al_id == 0 {
531            ctx.stack.push(Value::Int(value));
532            true
533        } else {
534            false
535        }
536    } else {
537        false
538    }
539}
540
541pub fn dispatch(ctx: &mut CommandContext, args: &[Value]) -> Result<bool> {
542    let Some(call) = parse_screen_call(ctx, args) else {
543        return Ok(false);
544    };
545    let chain = call.chain;
546    let form_id = chain[0] as u32;
547    let mut st = ctx
548        .globals
549        .screen_forms
550        .remove(&form_id)
551        .unwrap_or_default();
552    let result = (|| {
553        if chain.len() == 1 {
554            if call.ret_form != 0 {
555                ctx.stack.push(default_for_ret_form(call.ret_form));
556            }
557            return Ok(true);
558        }
559
560        let selector = chain[1];
561        let ids = ctx.ids.clone();
562
563        if selector == ids.screen_sel_effect {
564            if chain.len() == 3 {
565                match chain[2] {
566                    EFFECTLIST_RESIZE_OP => {
567                        st.ensure_effect_len(
568                            args.first().and_then(as_i64).unwrap_or(0).max(0) as usize
569                        );
570                        ctx.stack.push(Value::Int(0));
571                        return Ok(true);
572                    }
573                    EFFECTLIST_GET_SIZE_OP => {
574                        ctx.stack.push(Value::Int(st.effect_list.len() as i64));
575                        return Ok(true);
576                    }
577                    _ => return Ok(false),
578                }
579            }
580            if chain.len() < 4 || chain[2] != ids.elm_array {
581                return Ok(false);
582            }
583            let idx = chain[3].max(0) as usize;
584            st.ensure_effect_len(idx + 1);
585            let effect = &mut st.effect_list[idx];
586            if chain.len() == 4 {
587                if call.ret_form != 0 {
588                    ctx.stack.push(default_for_ret_form(call.ret_form));
589                }
590                return Ok(true);
591            }
592            let op = chain[4];
593            if op == ids.effect_init && call.script_args.is_empty() && call.ret_form == 0 {
594                effect.reinit();
595                ctx.stack.push(Value::Int(0));
596                return Ok(true);
597            }
598            if chain.len() >= 6 {
599                if let Some(ev) = effect_event_mut(&ids, effect, op) {
600                    run_int_event_command(
601                        ctx,
602                        ev,
603                        form_id,
604                        idx,
605                        op,
606                        chain[5],
607                        &call.script_args,
608                        call.ret_form,
609                    );
610                    return Ok(true);
611                }
612                return Ok(false);
613            }
614            return Ok(get_or_set_effect_scalar(
615                ctx,
616                effect,
617                &ids,
618                op,
619                call.al_id,
620                call.rhs.as_ref(),
621                call.ret_form,
622            ));
623        }
624
625        if selector == ids.screen_sel_quake {
626            if chain.len() < 4 || chain[2] != ids.elm_array {
627                return Ok(false);
628            }
629            let idx = chain[3].max(0) as usize;
630            st.ensure_quake_len(idx + 1);
631            let quake = &mut st.quake_list[idx];
632            if chain.len() == 4 {
633                if call.ret_form != 0 {
634                    ctx.stack.push(default_for_ret_form(call.ret_form));
635                }
636                return Ok(true);
637            }
638            return Ok(parse_quake_command(
639                quake,
640                form_id,
641                idx,
642                chain[4],
643                &call.script_args,
644                ctx,
645            ));
646        }
647
648        if selector == ids.screen_sel_shake {
649            if !call.script_args.is_empty() {
650                let shake_no = call.script_args[0].as_i64().unwrap_or(0);
651                let _ = st.shake.start(shake_no, ctx.tables.shake_templates.len());
652                ctx.stack.push(Value::Int(0));
653                return Ok(true);
654            }
655            return Ok(false);
656        }
657
658        if let Some(effect_op) = screen_to_effect_prop(&ids, selector) {
659            st.ensure_effect_len(1);
660            let effect = &mut st.effect_list[0];
661            return Ok(get_or_set_effect_scalar(
662                ctx,
663                effect,
664                &ids,
665                effect_op,
666                call.al_id,
667                call.rhs.as_ref(),
668                call.ret_form,
669            ));
670        }
671
672        if let Some(effect_event_op) = screen_to_effect_event(&ids, selector) {
673            if chain.len() != 3 {
674                return Ok(false);
675            }
676            st.ensure_effect_len(1);
677            let effect = &mut st.effect_list[0];
678            if let Some(ev) = effect_event_mut(&ids, effect, effect_event_op) {
679                run_int_event_command(
680                    ctx,
681                    ev,
682                    form_id,
683                    0,
684                    effect_event_op,
685                    chain[2],
686                    &call.script_args,
687                    call.ret_form,
688                );
689                return Ok(true);
690            }
691            return Ok(false);
692        }
693
694        Ok(false)
695    })();
696    ctx.globals.screen_forms.insert(form_id, st);
697    result
698}