siglus_scene_vm/runtime/forms/
math.rs1use 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 let abs = num.abs().to_string();
47 let total_digits = abs.len() + 1; 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 *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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}