Skip to main content

siglus_scene_vm/runtime/forms/
math.rs

1use anyhow::Result;
2
3use crate::runtime::forms::codes::elm_value;
4use crate::runtime::forms::prop_access;
5use crate::runtime::{CommandContext, Value};
6
7const TNM_ANGLE_UNIT: f64 = 10.0;
8
9fn round_half_away_from_zero(v: f64) -> i64 {
10    if v > 0.0 {
11        (v + 0.5) as i64
12    } else if v < 0.0 {
13        (v - 0.5) as i64
14    } else {
15        0
16    }
17}
18
19fn repeat_char(c: char, n: usize) -> String {
20    std::iter::repeat(c).take(n).collect()
21}
22
23fn tostr_pad(num: i64, len: i64, fill: char) -> String {
24    let len = len.max(0) as usize;
25
26    if num == 0 {
27        if len <= 1 {
28            return "0".to_string();
29        }
30        let mut out = repeat_char(fill, len - 1);
31        out.push('0');
32        return out;
33    }
34
35    if num > 0 {
36        let digits = num.to_string();
37        if len <= digits.len() {
38            return digits;
39        }
40        let mut out = repeat_char(fill, len - digits.len());
41        out.push_str(&digits);
42        return out;
43    }
44
45    // Negative number.
46    let abs = num.abs().to_string();
47    let total_digits = abs.len() + 1; // '-' + abs
48    if len <= total_digits {
49        return format!("-{}", abs);
50    }
51
52    let mut out = String::new();
53    out.push('-');
54    out.push_str(&repeat_char(fill, len - total_digits));
55    out.push_str(&abs);
56    out
57}
58
59fn to_zenkaku_ascii(s: &str) -> String {
60    s.chars()
61        .map(|ch| match ch {
62            '0'..='9' => char::from_u32('0' as u32 + (ch as u32 - '0' as u32)).unwrap(),
63            'A'..='Z' => char::from_u32('A' as u32 + (ch as u32 - 'A' as u32)).unwrap(),
64            'a'..='z' => char::from_u32('a' as u32 + (ch as u32 - 'a' as u32)).unwrap(),
65            ' ' => ' ',
66            '-' => '-',
67            '+' => '+',
68            '.' => '.',
69            ',' => ',',
70            ':' => ':',
71            ';' => ';',
72            '/' => '/',
73            '\\' => '\',
74            '(' => '(',
75            ')' => ')',
76            '[' => '[',
77            ']' => ']',
78            _ => ch,
79        })
80        .collect()
81}
82
83fn timetable_arg(v: &Value) -> Option<(f64, f64, f64, i64)> {
84    let Value::List(items) = v.unwrap_named() else {
85        return None;
86    };
87    if items.len() < 3 {
88        return None;
89    }
90    let start_time = items.first().and_then(Value::as_i64)? as f64;
91    let end_time = items.get(1).and_then(Value::as_i64)? as f64;
92    let end_value = items.get(2).and_then(Value::as_i64)? as f64;
93    let speed_type = items.get(3).and_then(Value::as_i64).unwrap_or(0);
94    Some((start_time, end_time, end_value, speed_type))
95}
96
97fn timetable_value(params: &[Value]) -> i64 {
98    let now_time = params.first().and_then(Value::as_i64).unwrap_or(0) as f64;
99    let rep_time = params.get(1).and_then(Value::as_i64).unwrap_or(0) as f64;
100    let mut start_value = params.get(2).and_then(Value::as_i64).unwrap_or(0) as f64;
101    let mut ret_value = start_value;
102    let now_time = now_time - rep_time;
103
104    for arg in params.iter().skip(3) {
105        let Some((start_time, end_time, end_value, speed_type)) = timetable_arg(arg) else {
106            continue;
107        };
108        if now_time < start_time {
109            ret_value = start_value;
110            break;
111        } else if now_time >= end_time {
112            ret_value = end_value;
113        } else {
114            let duration = end_time - start_time;
115            if duration == 0.0 {
116                ret_value = end_value;
117            } else if speed_type == 1 {
118                let t = now_time - start_time;
119                ret_value = (end_value - start_value) * t * t / duration / duration + start_value;
120            } else if speed_type == 2 {
121                let t = now_time - end_time;
122                ret_value = -(end_value - start_value) * t * t / duration / duration + end_value;
123            } else {
124                ret_value =
125                    (end_value - start_value) * (now_time - start_time) / duration + start_value;
126            }
127            break;
128        }
129        start_value = end_value;
130    }
131
132    round_half_away_from_zero(ret_value)
133}
134
135fn xorshift32(state: &mut u32) -> u32 {
136    if *state == 0 {
137        // Non-zero default seed.
138        *state = 0x1234_5678;
139    }
140    let mut x = *state;
141    x ^= x << 13;
142    x ^= x >> 17;
143    x ^= x << 5;
144    *state = x;
145    x
146}
147
148pub fn dispatch(ctx: &mut CommandContext, form_id: u32, args: &[Value]) -> Result<bool> {
149    let parsed = prop_access::parse_element_chain_ctx(ctx, form_id, args);
150    let mut chain_pos: Option<usize> = None;
151    let mut chain: Option<&[i32]> = None;
152    if let Some((pos, ch)) = parsed {
153        chain_pos = Some(pos);
154        chain = Some(ch);
155    }
156
157    let (op, params, al_id) = if let Some(pos) = chain_pos {
158        let ch = chain.unwrap();
159        let op = ch.get(1).copied();
160        let al_id = crate::runtime::forms::prop_access::current_vm_meta(ctx).0;
161        (
162            op,
163            crate::runtime::forms::prop_access::script_args(args, pos),
164            al_id,
165        )
166    } else {
167        let op = args.get(0).and_then(|v| v.as_i64()).map(|v| v as i32);
168        let params = if args.len() >= 2 { &args[1..] } else { &[] };
169        (op, params, None)
170    };
171
172    let p_int = |i: usize| -> i64 { params.get(i).and_then(|v| v.as_i64()).unwrap_or(0) };
173    let p_str = |i: usize| -> &str { params.get(i).and_then(|v| v.as_str()).unwrap_or("") };
174
175    let Some(op) = op else {
176        if let Some(direct_op) = args.get(0).and_then(|v| v.as_i64()) {
177            prop_access::store_or_push_direct_prop(ctx, form_id, direct_op as i32, args, 1);
178            return Ok(true);
179        }
180        ctx.push(Value::Int(0));
181        return Ok(true);
182    };
183
184    // MAX
185    if ctx.ids.math_max != 0 && op == ctx.ids.math_max {
186        let a = p_int(0);
187        let b = p_int(1);
188        ctx.push(Value::Int(a.max(b)));
189        return Ok(true);
190    }
191    // MIN
192    if ctx.ids.math_min != 0 && op == ctx.ids.math_min {
193        let a = p_int(0);
194        let b = p_int(1);
195        ctx.push(Value::Int(a.min(b)));
196        return Ok(true);
197    }
198    // LIMIT(min, value, max)
199    if ctx.ids.math_limit != 0 && op == ctx.ids.math_limit {
200        let a = p_int(0);
201        let v = p_int(1);
202        let b = p_int(2);
203        ctx.push(Value::Int(v.clamp(a.min(b), a.max(b))));
204        return Ok(true);
205    }
206    // ABS
207    if ctx.ids.math_abs != 0 && op == ctx.ids.math_abs {
208        ctx.push(Value::Int(p_int(0).abs()));
209        return Ok(true);
210    }
211    // RAND(min, max)
212    if ctx.ids.math_rand != 0 && op == ctx.ids.math_rand {
213        let a = p_int(0);
214        let b = p_int(1);
215        let lo = a.min(b);
216        let hi = a.max(b);
217        if lo == hi {
218            ctx.push(Value::Int(lo));
219            return Ok(true);
220        }
221        let span = (hi - lo + 1) as u64;
222        let r = xorshift32(&mut ctx.globals.rng_state) as u64;
223        ctx.push(Value::Int(lo + (r % span) as i64));
224        return Ok(true);
225    }
226
227    // SQRT(num, scale)
228    if ctx.ids.math_sqrt != 0 && op == ctx.ids.math_sqrt {
229        let num = p_int(0).max(0) as f64;
230        let scale = p_int(1) as f64;
231        let v = (num.sqrt() * scale) as i64;
232        ctx.push(Value::Int(v));
233        return Ok(true);
234    }
235    // LOG(num, scale)
236    if ctx.ids.math_log != 0 && op == ctx.ids.math_log {
237        let num = p_int(0).max(1) as f64;
238        let scale = p_int(1) as f64;
239        let v = (num.ln() * scale) as i64;
240        ctx.push(Value::Int(v));
241        return Ok(true);
242    }
243    // LOG2
244    if ctx.ids.math_log2 != 0 && op == ctx.ids.math_log2 {
245        let num = p_int(0).max(1) as f64;
246        let scale = p_int(1) as f64;
247        let v = (num.log2() * scale) as i64;
248        ctx.push(Value::Int(v));
249        return Ok(true);
250    }
251    // LOG10
252    if ctx.ids.math_log10 != 0 && op == ctx.ids.math_log10 {
253        let num = p_int(0).max(1) as f64;
254        let scale = p_int(1) as f64;
255        let v = (num.log10() * scale) as i64;
256        ctx.push(Value::Int(v));
257        return Ok(true);
258    }
259
260    // SIN(angle, scale)
261    if ctx.ids.math_sin != 0 && op == ctx.ids.math_sin {
262        let angle = p_int(0) as f64;
263        let scale = p_int(1) as f64;
264        let deg = angle / TNM_ANGLE_UNIT;
265        let rad = deg.to_radians();
266        ctx.push(Value::Int((rad.sin() * scale) as i64));
267        return Ok(true);
268    }
269    // COS(angle, scale)
270    if ctx.ids.math_cos != 0 && op == ctx.ids.math_cos {
271        let angle = p_int(0) as f64;
272        let scale = p_int(1) as f64;
273        let deg = angle / TNM_ANGLE_UNIT;
274        let rad = deg.to_radians();
275        ctx.push(Value::Int((rad.cos() * scale) as i64));
276        return Ok(true);
277    }
278    // TAN(angle, scale)
279    if ctx.ids.math_tan != 0 && op == ctx.ids.math_tan {
280        let angle = p_int(0) as f64;
281        let scale = p_int(1) as f64;
282        let deg = angle / TNM_ANGLE_UNIT;
283        let rad = deg.to_radians();
284        ctx.push(Value::Int((rad.tan() * scale) as i64));
285        return Ok(true);
286    }
287
288    // ARCSIN(num, denom)
289    if ctx.ids.math_arcsin != 0 && op == ctx.ids.math_arcsin {
290        let num = p_int(0) as f64;
291        let denom = p_int(1) as f64;
292        let ret = if denom == 0.0 {
293            0
294        } else {
295            let mut x = num / denom;
296            x = x.clamp(-1.0, 1.0);
297            round_half_away_from_zero(x.asin().to_degrees() * TNM_ANGLE_UNIT)
298        };
299        ctx.push(Value::Int(ret));
300        return Ok(true);
301    }
302    // ARCCOS
303    if ctx.ids.math_arccos != 0 && op == ctx.ids.math_arccos {
304        let num = p_int(0) as f64;
305        let denom = p_int(1) as f64;
306        let ret = if denom == 0.0 {
307            0
308        } else {
309            let mut x = num / denom;
310            x = x.clamp(-1.0, 1.0);
311            round_half_away_from_zero(x.acos().to_degrees() * TNM_ANGLE_UNIT)
312        };
313        ctx.push(Value::Int(ret));
314        return Ok(true);
315    }
316    // ARCTAN
317    if ctx.ids.math_arctan != 0 && op == ctx.ids.math_arctan {
318        let num = p_int(0) as f64;
319        let denom = p_int(1) as f64;
320        let ret = if denom == 0.0 {
321            0
322        } else {
323            round_half_away_from_zero((num / denom).atan().to_degrees() * TNM_ANGLE_UNIT)
324        };
325        ctx.push(Value::Int(ret));
326        return Ok(true);
327    }
328
329    // DISTANCE(x1,y1,x2,y2)
330    if ctx.ids.math_distance != 0 && op == ctx.ids.math_distance {
331        let x1 = p_int(0);
332        let y1 = p_int(1);
333        let x2 = p_int(2);
334        let y2 = p_int(3);
335        let dx = (x2 - x1) as f64;
336        let dy = (y2 - y1) as f64;
337        ctx.push(Value::Int(((dx * dx + dy * dy).sqrt()) as i64));
338        return Ok(true);
339    }
340
341    // ANGLE(x1,y1,x2,y2)
342    if ctx.ids.math_angle != 0 && op == ctx.ids.math_angle {
343        let x1 = p_int(0);
344        let y1 = p_int(1);
345        let x2 = p_int(2);
346        let y2 = p_int(3);
347        let dy = (y2 - y1) as f64;
348        let dx = (x2 - x1) as f64;
349        let mut ret = round_half_away_from_zero(dy.atan2(dx).to_degrees() * TNM_ANGLE_UNIT);
350        ret = (ret + (360.0 * TNM_ANGLE_UNIT) as i64).rem_euclid((360.0 * TNM_ANGLE_UNIT) as i64);
351        ctx.push(Value::Int(ret));
352        return Ok(true);
353    }
354
355    // LINEAR(x0,x1,y1,x2,y2)
356    if ctx.ids.math_linear != 0 && op == ctx.ids.math_linear {
357        let x0 = p_int(0);
358        let x1 = p_int(1);
359        let y1 = p_int(2);
360        let x2 = p_int(3);
361        let y2 = p_int(4);
362        if x1 == x2 {
363            ctx.push(Value::Int(y1));
364        } else {
365            let v = ((y2 - y1) as f64 * (x0 - x1) as f64 / (x2 - x1) as f64 + y1 as f64) as i64;
366            ctx.push(Value::Int(v));
367        }
368        return Ok(true);
369    }
370
371    // TIMETABLE(now_time, rep_time, start_value, [start_time,end_time,end_value,speed_type]...)
372    if op == elm_value::MATH_TIMETABLE {
373        let ret = timetable_value(params);
374        if std::env::var_os("SG_DEBUG").is_some()
375            && matches!(
376                ctx.current_scene_name.as_deref(),
377                Some("sys10_sm00") | Some("sys10_cf00") | Some("sys10_cf01")
378            )
379            && matches!(ctx.current_line_no, 140..=185 | 240..=285 | 700..=730 | 870..=895)
380        {
381            let scene = ctx.current_scene_name.as_deref().unwrap_or("<none>");
382            let scene_no = ctx
383                .current_scene_no
384                .map(|n| n.to_string())
385                .unwrap_or_else(|| "-".to_string());
386            eprintln!(
387                "[SG_DEBUG][CF_CONDITION_TRACE] scene={} scene_no={} line={} kind=MATH_TIMETABLE params={:?} result={}",
388                scene,
389                scene_no,
390                ctx.current_line_no,
391                params,
392                ret
393            );
394        }
395        ctx.push(Value::Int(ret));
396        return Ok(true);
397    }
398
399    // TOSTR
400    if ctx.ids.math_tostr != 0 && op == ctx.ids.math_tostr {
401        let s = if matches!(al_id, Some(1)) {
402            let num = p_int(0);
403            let len = p_int(1);
404            tostr_pad(num, len, ' ')
405        } else {
406            p_int(0).to_string()
407        };
408        ctx.push(Value::Str(s));
409        return Ok(true);
410    }
411
412    // TOSTR_ZERO
413    if ctx.ids.math_tostr_zero != 0 && op == ctx.ids.math_tostr_zero {
414        let num = p_int(0);
415        let len = p_int(1);
416        ctx.push(Value::Str(tostr_pad(num, len, '0')));
417        return Ok(true);
418    }
419
420    // TOSTR_ZEN
421    if op == elm_value::MATH_TOSTR_ZEN {
422        let s = if matches!(al_id, Some(1)) {
423            let num = p_int(0);
424            let len = p_int(1);
425            tostr_pad(num, len, ' ')
426        } else {
427            p_int(0).to_string()
428        };
429        ctx.push(Value::Str(to_zenkaku_ascii(&s)));
430        return Ok(true);
431    }
432
433    // TOSTR_ZEN_ZERO
434    if op == elm_value::MATH_TOSTR_ZEN_ZERO {
435        let num = p_int(0);
436        let len = p_int(1);
437        ctx.push(Value::Str(to_zenkaku_ascii(&tostr_pad(num, len, '0'))));
438        return Ok(true);
439    }
440
441    // TOSTR_BY_CODE
442    if op == elm_value::MATH_TOSTR_BY_CODE {
443        let code = (p_int(0) & 0xffff) as u32;
444        let s = char::from_u32(code)
445            .map(|c| c.to_string())
446            .unwrap_or_default();
447        ctx.push(Value::Str(s));
448        return Ok(true);
449    }
450
451    Ok(false)
452}