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_n(c, 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.first().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.first().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 crate::runtime::constants::matches_element_id(op, ctx.ids.math_max, elm_value::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 crate::runtime::constants::matches_element_id(op, ctx.ids.math_min, elm_value::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 crate::runtime::constants::matches_element_id(op, ctx.ids.math_limit, elm_value::MATH_LIMIT)
200 {
201 let a = p_int(0);
202 let v = p_int(1);
203 let b = p_int(2);
204 ctx.push(Value::Int(v.clamp(a.min(b), a.max(b))));
205 return Ok(true);
206 }
207 if crate::runtime::constants::matches_element_id(op, ctx.ids.math_abs, elm_value::MATH_ABS) {
209 ctx.push(Value::Int(p_int(0).abs()));
210 return Ok(true);
211 }
212 if crate::runtime::constants::matches_element_id(op, ctx.ids.math_rand, elm_value::MATH_RAND) {
214 let a = p_int(0);
215 let b = p_int(1);
216 let lo = a.min(b);
217 let hi = a.max(b);
218 if lo == hi {
219 ctx.push(Value::Int(lo));
220 return Ok(true);
221 }
222 let span = (hi - lo + 1) as u64;
223 let r = xorshift32(&mut ctx.globals.rng_state) as u64;
224 ctx.push(Value::Int(lo + (r % span) as i64));
225 return Ok(true);
226 }
227
228 if crate::runtime::constants::matches_element_id(op, ctx.ids.math_sqrt, elm_value::MATH_SQRT) {
230 let num = p_int(0).max(0) as f64;
231 let scale = p_int(1) as f64;
232 let v = (num.sqrt() * scale) as i64;
233 ctx.push(Value::Int(v));
234 return Ok(true);
235 }
236 if crate::runtime::constants::matches_element_id(op, ctx.ids.math_log, elm_value::MATH_LOG) {
238 let num = p_int(0).max(1) as f64;
239 let scale = p_int(1) as f64;
240 let v = (num.ln() * scale) as i64;
241 ctx.push(Value::Int(v));
242 return Ok(true);
243 }
244 if crate::runtime::constants::matches_element_id(op, ctx.ids.math_log2, elm_value::MATH_LOG2) {
246 let num = p_int(0).max(1) as f64;
247 let scale = p_int(1) as f64;
248 let v = (num.log2() * scale) as i64;
249 ctx.push(Value::Int(v));
250 return Ok(true);
251 }
252 if crate::runtime::constants::matches_element_id(op, ctx.ids.math_log10, elm_value::MATH_LOG10)
254 {
255 let num = p_int(0).max(1) as f64;
256 let scale = p_int(1) as f64;
257 let v = (num.log10() * scale) as i64;
258 ctx.push(Value::Int(v));
259 return Ok(true);
260 }
261
262 if crate::runtime::constants::matches_element_id(op, ctx.ids.math_sin, elm_value::MATH_SIN) {
264 let angle = p_int(0) as f64;
265 let scale = p_int(1) as f64;
266 let deg = angle / TNM_ANGLE_UNIT;
267 let rad = deg.to_radians();
268 ctx.push(Value::Int((rad.sin() * scale) as i64));
269 return Ok(true);
270 }
271 if crate::runtime::constants::matches_element_id(op, ctx.ids.math_cos, elm_value::MATH_COS) {
273 let angle = p_int(0) as f64;
274 let scale = p_int(1) as f64;
275 let deg = angle / TNM_ANGLE_UNIT;
276 let rad = deg.to_radians();
277 ctx.push(Value::Int((rad.cos() * scale) as i64));
278 return Ok(true);
279 }
280 if crate::runtime::constants::matches_element_id(op, ctx.ids.math_tan, elm_value::MATH_TAN) {
282 let angle = p_int(0) as f64;
283 let scale = p_int(1) as f64;
284 let deg = angle / TNM_ANGLE_UNIT;
285 let rad = deg.to_radians();
286 ctx.push(Value::Int((rad.tan() * scale) as i64));
287 return Ok(true);
288 }
289
290 if crate::runtime::constants::matches_element_id(
292 op,
293 ctx.ids.math_arcsin,
294 elm_value::MATH_ARCSIN,
295 ) {
296 let num = p_int(0) as f64;
297 let denom = p_int(1) as f64;
298 let ret = if denom == 0.0 {
299 0
300 } else {
301 let mut x = num / denom;
302 x = x.clamp(-1.0, 1.0);
303 round_half_away_from_zero(x.asin().to_degrees() * TNM_ANGLE_UNIT)
304 };
305 ctx.push(Value::Int(ret));
306 return Ok(true);
307 }
308 if crate::runtime::constants::matches_element_id(
310 op,
311 ctx.ids.math_arccos,
312 elm_value::MATH_ARCCOS,
313 ) {
314 let num = p_int(0) as f64;
315 let denom = p_int(1) as f64;
316 let ret = if denom == 0.0 {
317 0
318 } else {
319 let mut x = num / denom;
320 x = x.clamp(-1.0, 1.0);
321 round_half_away_from_zero(x.acos().to_degrees() * TNM_ANGLE_UNIT)
322 };
323 ctx.push(Value::Int(ret));
324 return Ok(true);
325 }
326 if crate::runtime::constants::matches_element_id(
328 op,
329 ctx.ids.math_arctan,
330 elm_value::MATH_ARCTAN,
331 ) {
332 let num = p_int(0) as f64;
333 let denom = p_int(1) as f64;
334 let ret = if denom == 0.0 {
335 0
336 } else {
337 round_half_away_from_zero((num / denom).atan().to_degrees() * TNM_ANGLE_UNIT)
338 };
339 ctx.push(Value::Int(ret));
340 return Ok(true);
341 }
342
343 if crate::runtime::constants::matches_element_id(
345 op,
346 ctx.ids.math_distance,
347 elm_value::MATH_DISTANCE,
348 ) {
349 let x1 = p_int(0);
350 let y1 = p_int(1);
351 let x2 = p_int(2);
352 let y2 = p_int(3);
353 let dx = (x2 - x1) as f64;
354 let dy = (y2 - y1) as f64;
355 ctx.push(Value::Int(((dx * dx + dy * dy).sqrt()) as i64));
356 return Ok(true);
357 }
358
359 if crate::runtime::constants::matches_element_id(op, ctx.ids.math_angle, elm_value::MATH_ANGLE)
361 {
362 let x1 = p_int(0);
363 let y1 = p_int(1);
364 let x2 = p_int(2);
365 let y2 = p_int(3);
366 let dy = (y2 - y1) as f64;
367 let dx = (x2 - x1) as f64;
368 let mut ret = round_half_away_from_zero(dy.atan2(dx).to_degrees() * TNM_ANGLE_UNIT);
369 ret = (ret + (360.0 * TNM_ANGLE_UNIT) as i64).rem_euclid((360.0 * TNM_ANGLE_UNIT) as i64);
370 ctx.push(Value::Int(ret));
371 return Ok(true);
372 }
373
374 if crate::runtime::constants::matches_element_id(
376 op,
377 ctx.ids.math_linear,
378 elm_value::MATH_LINEAR,
379 ) {
380 let x0 = p_int(0);
381 let x1 = p_int(1);
382 let y1 = p_int(2);
383 let x2 = p_int(3);
384 let y2 = p_int(4);
385 if x1 == x2 {
386 ctx.push(Value::Int(y1));
387 } else {
388 let v = ((y2 - y1) as f64 * (x0 - x1) as f64 / (x2 - x1) as f64 + y1 as f64) as i64;
389 ctx.push(Value::Int(v));
390 }
391 return Ok(true);
392 }
393
394 if op == elm_value::MATH_TIMETABLE {
396 let ret = timetable_value(params);
397 if env_is_set!("SG_DEBUG")
398 && matches!(
399 ctx.current_scene_name.as_deref(),
400 Some("sys10_sm00") | Some("sys10_cf00") | Some("sys10_cf01")
401 )
402 && matches!(ctx.current_line_no, 140..=185 | 240..=285 | 700..=730 | 870..=895)
403 {
404 let scene = ctx.current_scene_name.as_deref().unwrap_or("<none>");
405 let scene_no = ctx
406 .current_scene_no
407 .map(|n| n.to_string())
408 .unwrap_or_else(|| "-".to_string());
409 eprintln!(
410 "[SG_DEBUG][CF_CONDITION_TRACE] scene={} scene_no={} line={} kind=MATH_TIMETABLE params={:?} result={}",
411 scene, scene_no, ctx.current_line_no, params, ret
412 );
413 }
414 ctx.push(Value::Int(ret));
415 return Ok(true);
416 }
417
418 if crate::runtime::constants::matches_element_id(op, ctx.ids.math_tostr, elm_value::MATH_TOSTR)
420 {
421 let s = if matches!(al_id, Some(1)) {
422 let num = p_int(0);
423 let len = p_int(1);
424 tostr_pad(num, len, ' ')
425 } else {
426 p_int(0).to_string()
427 };
428 ctx.push(Value::Str(s));
429 return Ok(true);
430 }
431
432 if crate::runtime::constants::matches_element_id(
434 op,
435 ctx.ids.math_tostr_zero,
436 elm_value::MATH_TOSTR_ZERO,
437 ) {
438 let num = p_int(0);
439 let len = p_int(1);
440 ctx.push(Value::Str(tostr_pad(num, len, '0')));
441 return Ok(true);
442 }
443
444 if op == elm_value::MATH_TOSTR_ZEN {
446 let s = if matches!(al_id, Some(1)) {
447 let num = p_int(0);
448 let len = p_int(1);
449 tostr_pad(num, len, ' ')
450 } else {
451 p_int(0).to_string()
452 };
453 ctx.push(Value::Str(to_zenkaku_ascii(&s)));
454 return Ok(true);
455 }
456
457 if op == elm_value::MATH_TOSTR_ZEN_ZERO {
459 let num = p_int(0);
460 let len = p_int(1);
461 ctx.push(Value::Str(to_zenkaku_ascii(&tostr_pad(num, len, '0'))));
462 return Ok(true);
463 }
464
465 if op == elm_value::MATH_TOSTR_BY_CODE {
467 let code = (p_int(0) & 0xffff) as u32;
468 let s = char::from_u32(code)
469 .map(|c| c.to_string())
470 .unwrap_or_default();
471 ctx.push(Value::Str(s));
472 return Ok(true);
473 }
474
475 Ok(false)
476}