Skip to main content

siglus_scene_vm/runtime/forms/
cgtable.rs

1use anyhow::Result;
2
3use crate::runtime::forms::prop_access;
4use crate::runtime::{CommandContext, Value};
5
6fn ensure_cg_flags_size(ctx: &mut CommandContext, size: usize) {
7    if let Some(cnt) = ctx.tables.cgtable_flag_cnt {
8        let want = size.max(cnt);
9        if ctx.tables.cg_flags.len() < want {
10            ctx.tables.cg_flags.resize(want, 0);
11        }
12    } else if ctx.tables.cg_flags.len() < size {
13        ctx.tables.cg_flags.resize(size, 0);
14    }
15}
16
17fn get_cg_flag(ctx: &CommandContext, flag_no: i32) -> i64 {
18    if flag_no < 0 {
19        return 0;
20    }
21    ctx.tables
22        .cg_flags
23        .get(flag_no as usize)
24        .copied()
25        .unwrap_or(0) as i64
26}
27
28fn set_cg_flag(ctx: &mut CommandContext, flag_no: i32, value: i64) {
29    if flag_no < 0 {
30        return;
31    }
32    let idx = flag_no as usize;
33    ensure_cg_flags_size(ctx, idx + 1);
34    // C_elm_int_list::set_value stores the complete signed int; CGTABLE.FLAG is not a bool.
35    ctx.tables.cg_flags[idx] = value as i32;
36}
37
38fn cg_flag_is_looked(value: i64) -> bool {
39    // C_tnm_cg_table::get_look_cnt() counts only flag.get_value(...) == 1.
40    value == 1
41}
42
43fn cgtable_look_cnt(ctx: &CommandContext) -> i64 {
44    let Some(table) = ctx.tables.cgtable.as_ref() else {
45        return 0;
46    };
47    table
48        .entries
49        .iter()
50        .filter(|e| e.flag_no >= 0 && cg_flag_is_looked(get_cg_flag(ctx, e.flag_no)))
51        .count() as i64
52}
53
54pub(crate) fn mark_look_by_name(ctx: &mut CommandContext, name: &str) {
55    if ctx.globals.cg_table_off {
56        return;
57    }
58    let flag_no = ctx
59        .tables
60        .cgtable
61        .as_ref()
62        .and_then(|t| t.get_sub_from_name(name))
63        .map(|e| e.flag_no);
64    if let Some(flag_no) = flag_no {
65        set_cg_flag(ctx, flag_no, 1);
66    }
67}
68
69pub fn dispatch(ctx: &mut CommandContext, form_id: u32, args: &[Value]) -> Result<bool> {
70    let parsed = prop_access::parse_element_chain_ctx(ctx, form_id, args);
71    let (chain_pos, chain) = match parsed {
72        Some((pos, ch @ [_, _, ..])) => (Some(pos), Some(ch)),
73        _ => (None, None),
74    };
75
76    if let Some(chain) = chain {
77        let op = chain[1];
78
79        if op == ctx.ids.cgtable_flag {
80            let rest = &chain[2..];
81            if rest.len() >= 2 && rest[0] == ctx.ids.elm_array {
82                let idx_i32 = rest[1];
83                if idx_i32 < 0 {
84                    ctx.push(Value::Int(0));
85                    return Ok(true);
86                }
87
88                let idx = idx_i32 as usize;
89                ensure_cg_flags_size(ctx, idx + 1);
90
91                let (al_id, _ret_form, rhs_value) =
92                    crate::runtime::forms::prop_access::infer_assign_and_ret_ctx(
93                        ctx,
94                        chain_pos.unwrap_or(args.len()),
95                        args,
96                    );
97                let rhs = rhs_value.as_ref().and_then(Value::as_i64);
98
99                if al_id == Some(1) {
100                    let v = rhs.unwrap_or(0);
101                    // tnm_command_proc_int_list(..., 32, ...) assigns p_ai->Int verbatim.
102                    ctx.tables.cg_flags[idx] = v as i32;
103                    ctx.push(Value::Int(0));
104                } else {
105                    ctx.push(Value::Int(ctx.tables.cg_flags[idx] as i64));
106                }
107                return Ok(true);
108            }
109
110            ctx.push(Value::Int(0));
111            return Ok(true);
112        }
113
114        let params = if let Some(pos) = chain_pos {
115            crate::runtime::forms::prop_access::script_args(args, pos)
116        } else {
117            &[]
118        };
119        let p_int = |i: usize| -> i64 { params.get(i).and_then(|v| v.as_i64()).unwrap_or(0) };
120        let p_str = |i: usize| -> &str { params.get(i).and_then(|v| v.as_str()).unwrap_or("") };
121
122        if ctx.ids.cgtable_set_disable != 0 && op == ctx.ids.cgtable_set_disable {
123            ctx.globals.cg_table_off = true;
124            ctx.push(Value::Int(0));
125            return Ok(true);
126        }
127        if ctx.ids.cgtable_set_enable != 0 && op == ctx.ids.cgtable_set_enable {
128            ctx.globals.cg_table_off = false;
129            ctx.push(Value::Int(0));
130            return Ok(true);
131        }
132        if ctx.ids.cgtable_set_all_flag != 0 && op == ctx.ids.cgtable_set_all_flag {
133            let v = p_int(0);
134            let flag_nos: Vec<i32> = ctx
135                .tables
136                .cgtable
137                .as_ref()
138                .map(|t| t.entries.iter().map(|e| e.flag_no).collect())
139                .unwrap_or_default();
140            for flag_no in flag_nos {
141                set_cg_flag(ctx, flag_no, v);
142            }
143            ctx.push(Value::Int(0));
144            return Ok(true);
145        }
146
147        if ctx.ids.cgtable_get_cg_cnt != 0 && op == ctx.ids.cgtable_get_cg_cnt {
148            let cnt = ctx
149                .tables
150                .cgtable
151                .as_ref()
152                .map(|t| t.get_cg_cnt())
153                .unwrap_or(0);
154            ctx.push(Value::Int(cnt as i64));
155            return Ok(true);
156        }
157
158        if ctx.ids.cgtable_get_look_cnt != 0 && op == ctx.ids.cgtable_get_look_cnt {
159            ctx.push(Value::Int(cgtable_look_cnt(ctx)));
160            return Ok(true);
161        }
162
163        if ctx.ids.cgtable_get_look_percent != 0 && op == ctx.ids.cgtable_get_look_percent {
164            let total = ctx
165                .tables
166                .cgtable
167                .as_ref()
168                .map(|t| t.get_cg_cnt())
169                .unwrap_or(0) as i64;
170            let looked = cgtable_look_cnt(ctx);
171            let percent = if total <= 0 || looked <= 0 {
172                0
173            } else {
174                let raw = looked * 100 / total;
175                if raw <= 0 { 1 } else { raw }
176            };
177            ctx.push(Value::Int(percent));
178            return Ok(true);
179        }
180
181        if ctx.ids.cgtable_get_flag_no_by_name != 0 && op == ctx.ids.cgtable_get_flag_no_by_name {
182            let name = p_str(0);
183            let res = ctx
184                .tables
185                .cgtable
186                .as_ref()
187                .and_then(|t| t.get_sub_from_name(name))
188                .map(|e| e.flag_no as i64)
189                .unwrap_or(-1);
190            ctx.push(Value::Int(res));
191            return Ok(true);
192        }
193
194        if ctx.ids.cgtable_get_look_by_name != 0 && op == ctx.ids.cgtable_get_look_by_name {
195            let name = p_str(0);
196            let res = if let Some(e) = ctx
197                .tables
198                .cgtable
199                .as_ref()
200                .and_then(|t| t.get_sub_from_name(name))
201            {
202                get_cg_flag(ctx, e.flag_no)
203            } else {
204                -1
205            };
206            ctx.push(Value::Int(res));
207            return Ok(true);
208        }
209
210        if ctx.ids.cgtable_set_look_by_name != 0 && op == ctx.ids.cgtable_set_look_by_name {
211            let name = p_str(0);
212            let v = p_int(1);
213            let flag_no = ctx
214                .tables
215                .cgtable
216                .as_ref()
217                .and_then(|t| t.get_sub_from_name(name))
218                .map(|e| e.flag_no);
219            if let Some(flag_no) = flag_no {
220                set_cg_flag(ctx, flag_no, v);
221            }
222            ctx.push(Value::Int(0));
223            return Ok(true);
224        }
225
226        if ctx.ids.cgtable_get_name_by_flag_no != 0 && op == ctx.ids.cgtable_get_name_by_flag_no {
227            let flag_no = p_int(0) as i32;
228            let name = ctx
229                .tables
230                .cgtable
231                .as_ref()
232                .and_then(|t| t.get_sub_from_flag_no(flag_no))
233                .map(|e| e.name.as_str())
234                .unwrap_or("");
235            ctx.push(Value::Str(name.to_string()));
236            return Ok(true);
237        }
238
239        prop_access::store_or_push_prop(ctx, form_id, op, chain_pos.unwrap(), args);
240        return Ok(true);
241    }
242
243    Ok(false)
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249    use crate::runtime::VmCallMeta;
250    use std::path::PathBuf;
251
252    fn flag_array_call(ctx: &mut CommandContext, index: i32, al_id: i64, ret_form: i64) {
253        ctx.vm_call = Some(VmCallMeta {
254            element: vec![
255                ctx.ids.form_global_cgtable as i32,
256                ctx.ids.cgtable_flag,
257                ctx.ids.elm_array,
258                index,
259            ],
260            al_id,
261            ret_form,
262        });
263    }
264
265    #[test]
266    fn flag_array_assignment_preserves_signed_i32_values() {
267        let mut ctx = CommandContext::new(PathBuf::from("."));
268        let form_id = ctx.ids.form_global_cgtable;
269
270        for value in [-7i64, 0, 1, 2, i32::MIN as i64, i32::MAX as i64] {
271            flag_array_call(&mut ctx, 2, 1, 0);
272            assert!(dispatch(&mut ctx, form_id, &[Value::Int(value)]).unwrap());
273            assert_eq!(ctx.tables.cg_flags[2], value as i32);
274
275            ctx.stack.clear();
276            flag_array_call(&mut ctx, 2, 0, 10);
277            assert!(dispatch(&mut ctx, form_id, &[]).unwrap());
278            assert_eq!(ctx.stack.pop().and_then(|v| v.as_i64()), Some(value));
279        }
280    }
281
282    #[test]
283    fn look_count_semantics_require_exactly_one() {
284        assert!(!cg_flag_is_looked(-7));
285        assert!(!cg_flag_is_looked(0));
286        assert!(cg_flag_is_looked(1));
287        assert!(!cg_flag_is_looked(2));
288        assert!(!cg_flag_is_looked(i32::MAX as i64));
289    }
290}