Skip to main content

siglus_scene_vm/runtime/forms/
system.rs

1use anyhow::Result;
2use std::fs::{self, OpenOptions};
3use std::io::Write;
4use std::path::{Path, PathBuf};
5
6use crate::runtime::{CommandContext, Value};
7
8use super::prop_access;
9use super::syscom;
10
11const CHECK_ACTIVE: i32 = 0;
12const SHELL_OPEN_FILE: i32 = 1;
13const CHECK_DUMMY_FILE_ONCE: i32 = 2;
14const OPEN_DIALOG_FOR_CHIHAYA_BENCH: i32 = 3;
15const GET_SPEC_INFO_FOR_CHIHAYA_BENCH: i32 = 4;
16const SHELL_OPEN_WEB: i32 = 5;
17const CHECK_FILE_EXIST: i32 = 6;
18const DEBUG_MESSAGEBOX_OK: i32 = 7;
19const DEBUG_MESSAGEBOX_OKCANCEL: i32 = 8;
20const DEBUG_MESSAGEBOX_YESNO: i32 = 9;
21const DEBUG_MESSAGEBOX_YESNOCANCEL: i32 = 10;
22const DEBUG_WRITE_LOG: i32 = 11;
23const CHECK_FILE_EXIST_SAVE_DIR: i32 = 12;
24const CHECK_DEBUG_FLAG: i32 = 13;
25const GET_CALENDAR: i32 = 14;
26const GET_UNIX_TIME: i32 = 15;
27const GET_LANGUAGE: i32 = 16;
28const MESSAGEBOX_OK: i32 = 17;
29const MESSAGEBOX_OKCANCEL: i32 = 18;
30const MESSAGEBOX_YESNO: i32 = 19;
31const MESSAGEBOX_YESNOCANCEL: i32 = 20;
32const CLEAR_DUMMY_FILE: i32 = 21;
33
34struct Call<'a> {
35    op: i32,
36    params: &'a [Value],
37}
38
39fn parse_call<'a>(ctx: &CommandContext, form_id: u32, args: &'a [Value]) -> Option<Call<'a>> {
40    let (chain_pos, chain) = prop_access::parse_element_chain_ctx(ctx, form_id, args)?;
41    if chain.len() < 2 {
42        return None;
43    }
44    let params = prop_access::script_args(args, chain_pos);
45    Some(Call {
46        op: chain[1],
47        params,
48    })
49}
50
51fn p_str(params: &[Value], idx: usize) -> &str {
52    params.get(idx).and_then(|v| v.as_str()).unwrap_or("")
53}
54
55fn join_game_path(base: &Path, raw: &str) -> PathBuf {
56    if raw.is_empty() {
57        return base.to_path_buf();
58    }
59    let norm = raw.replace('\\', "/");
60    let p = Path::new(&norm);
61    if p.is_absolute() {
62        p.to_path_buf()
63    } else {
64        base.join(p)
65    }
66}
67
68pub fn dispatch(ctx: &mut CommandContext, form_id: u32, args: &[Value]) -> Result<bool> {
69    let Some(call) = parse_call(ctx, form_id, args) else {
70        return Ok(false);
71    };
72
73    match call.op {
74        GET_CALENDAR => {
75            let tm = local_time_fields();
76            let vals = [tm.0, tm.1, tm.2, tm.3, tm.4, tm.5, tm.6, tm.7];
77            for (idx, value) in vals.iter().enumerate() {
78                if let Some(Value::Element(chain)) = call.params.get(idx) {
79                    prop_access::assign_to_chain(ctx, chain, Value::Int(*value));
80                }
81            }
82            return Ok(true);
83        }
84        GET_UNIX_TIME => {
85            let t = crate::platform_time::unix_time_secs() as i64;
86            ctx.push(Value::Int(t));
87            return Ok(true);
88        }
89        CHECK_ACTIVE => {
90            ctx.push(Value::Int(if ctx.globals.system.active_flag {
91                1
92            } else {
93                0
94            }));
95            return Ok(true);
96        }
97        CHECK_DEBUG_FLAG => {
98            ctx.push(Value::Int(if ctx.globals.system.debug_flag {
99                1
100            } else {
101                0
102            }));
103            return Ok(true);
104        }
105        SHELL_OPEN_FILE => {
106            let path = join_game_path(&ctx.project_dir, p_str(call.params, 0));
107            if path.exists() {
108                let _ = ctx.net.open_file(&path);
109            }
110            ctx.globals
111                .system
112                .debug_logs
113                .push(format!("shell_open_file:{}", path.display()));
114            return Ok(true);
115        }
116        SHELL_OPEN_WEB => {
117            let url = p_str(call.params, 0);
118            let _ = ctx.net.open_url(url);
119            ctx.globals
120                .system
121                .debug_logs
122                .push(format!("shell_open_web:{url}"));
123            return Ok(true);
124        }
125        CHECK_FILE_EXIST => {
126            let path = join_game_path(&ctx.project_dir, p_str(call.params, 0));
127            ctx.push(Value::Int(if path.exists() { 1 } else { 0 }));
128            return Ok(true);
129        }
130        CHECK_FILE_EXIST_SAVE_DIR => {
131            let save_dir = syscom::save_dir(&ctx.project_dir);
132            let path = join_game_path(&save_dir, p_str(call.params, 0));
133            ctx.push(Value::Int(if path.exists() { 1 } else { 0 }));
134            return Ok(true);
135        }
136        CHECK_DUMMY_FILE_ONCE => {
137            let name = p_str(call.params, 0);
138            let key = call.params.get(1).and_then(|v| v.as_i64()).unwrap_or(0);
139            let code = p_str(call.params, 2);
140            let sig = format!("{name}:{key}:{code}");
141            ctx.globals.system.dummy_checks.insert(sig);
142            return Ok(true);
143        }
144        CLEAR_DUMMY_FILE => {
145            ctx.globals.system.dummy_checks.clear();
146            return Ok(true);
147        }
148        MESSAGEBOX_OK | MESSAGEBOX_OKCANCEL | MESSAGEBOX_YESNO | MESSAGEBOX_YESNOCANCEL => {
149            let text = messagebox_text(ctx, call.params);
150            if let Some(ret) = handle_messagebox(ctx, call.op, false, text) {
151                ctx.push(Value::Int(ret));
152            }
153            return Ok(true);
154        }
155        DEBUG_MESSAGEBOX_OK
156        | DEBUG_MESSAGEBOX_OKCANCEL
157        | DEBUG_MESSAGEBOX_YESNO
158        | DEBUG_MESSAGEBOX_YESNOCANCEL => {
159            let text = messagebox_text(ctx, call.params);
160            if ctx.globals.system.debug_flag {
161                if let Some(ret) = handle_messagebox(ctx, call.op, true, text) {
162                    ctx.push(Value::Int(ret));
163                }
164            } else {
165                ctx.push(Value::Int(0));
166            }
167            return Ok(true);
168        }
169        DEBUG_WRITE_LOG => {
170            if ctx.globals.system.debug_flag {
171                let s = match call.params.get(0) {
172                    Some(Value::Int(v)) => v.to_string(),
173                    Some(Value::Str(s)) => s.clone(),
174                    _ => String::new(),
175                };
176                write_debug_log(
177                    &ctx.project_dir,
178                    &s,
179                    ctx.current_scene_name.as_deref(),
180                    ctx.current_line_no,
181                );
182                ctx.globals.system.debug_logs.push(s);
183            }
184            return Ok(true);
185        }
186        GET_SPEC_INFO_FOR_CHIHAYA_BENCH => {
187            ctx.push(Value::Str(ctx.globals.system.spec_info.clone()));
188            return Ok(true);
189        }
190        OPEN_DIALOG_FOR_CHIHAYA_BENCH => {
191            ctx.globals
192                .system
193                .bench_dialogs
194                .push(p_str(call.params, 0).to_string());
195            return Ok(true);
196        }
197        GET_LANGUAGE => {
198            ctx.push(Value::Str(ctx.globals.system.language_code.clone()));
199            return Ok(true);
200        }
201        _ => {}
202    }
203
204    Ok(false)
205}
206
207fn messagebox_text(ctx: &CommandContext, params: &[Value]) -> String {
208    match params.first() {
209        Some(Value::Int(v)) => v.to_string(),
210        Some(Value::Str(s)) => s.clone(),
211        Some(v) => v.as_str().unwrap_or("").to_string(),
212        None => {
213            if let Some(name) = ctx.current_scene_name.as_deref() {
214                format!("{name}:{}", ctx.current_line_no)
215            } else {
216                String::new()
217            }
218        }
219    }
220}
221
222fn handle_messagebox(
223    ctx: &mut CommandContext,
224    kind: i32,
225    debug_only: bool,
226    text: String,
227) -> Option<i64> {
228    ctx.globals
229        .system
230        .messagebox_history
231        .push(crate::runtime::globals::SystemMessageBoxRecord {
232            kind,
233            text: text.clone(),
234            debug_only,
235        });
236
237    let buttons = messagebox_buttons(kind);
238    let max_value = buttons.iter().map(|b| b.value).max().unwrap_or(0);
239    if !ctx.globals.system.messagebox_response_queue.is_empty() {
240        let v = ctx.globals.system.messagebox_response_queue.remove(0);
241        return Some(v.clamp(0, max_value));
242    }
243
244    ctx.request_system_messagebox(kind, debug_only, text, buttons);
245    None
246}
247
248fn messagebox_buttons(kind: i32) -> Vec<crate::runtime::globals::SystemMessageBoxButton> {
249    let raw: &[(&str, i64)] = match kind {
250        MESSAGEBOX_OK | DEBUG_MESSAGEBOX_OK => &[("OK", 0)],
251        MESSAGEBOX_OKCANCEL | DEBUG_MESSAGEBOX_OKCANCEL => &[("OK", 0), ("CANCEL", 1)],
252        MESSAGEBOX_YESNO | DEBUG_MESSAGEBOX_YESNO => &[("YES", 0), ("NO", 1)],
253        MESSAGEBOX_YESNOCANCEL | DEBUG_MESSAGEBOX_YESNOCANCEL => {
254            &[("YES", 0), ("NO", 1), ("CANCEL", 2)]
255        }
256        _ => &[("OK", 0)],
257    };
258    raw.iter()
259        .map(
260            |(label, value)| crate::runtime::globals::SystemMessageBoxButton {
261                label: (*label).to_string(),
262                value: *value,
263            },
264        )
265        .collect()
266}
267
268fn local_time_fields() -> (i64, i64, i64, i64, i64, i64, i64, i64) {
269    let now = crate::platform_time::local_time_fields();
270    (
271        now.year as i64,
272        now.month as i64,
273        now.day as i64,
274        now.weekday_sunday0 as i64,
275        now.hour as i64,
276        now.minute as i64,
277        now.second as i64,
278        now.millisecond as i64,
279    )
280}
281
282fn write_debug_log(project_dir: &Path, msg: &str, scene_name: Option<&str>, line_no: i64) {
283    if msg.is_empty() {
284        return;
285    }
286    let dir = project_dir.join("__DEBUG_LOG");
287    let _ = fs::create_dir_all(&dir);
288    let path = dir.join("debug_log.txt");
289    let stamp = crate::platform_time::local_log_timestamp();
290    if let Ok(mut f) = OpenOptions::new().create(true).append(true).open(&path) {
291        if let Some(scene) = scene_name {
292            let _ = writeln!(f, "{}\t({}.ss line={})\t{}", stamp, scene, line_no, msg);
293        } else {
294            let _ = writeln!(f, "{}\t{}", stamp, msg);
295        }
296    }
297}