siglus_scene_vm/runtime/commands/
misc.rs1use anyhow::Result;
2
3use crate::runtime::commands::util;
4use crate::runtime::forms::stage;
5use crate::runtime::forms::syscom as syscom_form;
6use crate::runtime::globals::WipeState;
7use crate::runtime::{Command, CommandContext, Value};
8use std::path::{Path, PathBuf};
9
10fn is_noop_cmd(name: &str) -> bool {
11 matches!(name, "NOP" | "VIBRATE")
12}
13
14fn is_clear_cmd(name: &str) -> bool {
15 matches!(
16 name,
17 "CLS" | "CLEAR" | "CLEARALL" | "ALL_CLEAR" | "ALLCLEAR" | "RESET"
18 )
19}
20
21pub fn handle(ctx: &mut CommandContext, cmd: &Command) -> Result<bool> {
23 let name = cmd.name.to_ascii_uppercase();
24 let args = util::strip_vm_meta(&cmd.args);
25
26 let mut pos: Vec<&Value> = Vec::new();
27 let mut named: Vec<(i32, &Value)> = Vec::new();
28 for a in args {
29 if let Value::NamedArg { id, value } = a {
30 named.push((*id, value.as_ref()));
31 } else {
32 pos.push(a);
33 }
34 }
35
36 let parse_i32 = |v: &Value| -> Option<i32> { v.as_i64().and_then(|x| i32::try_from(x).ok()) };
37
38 let parse_bool = |v: &Value| -> Option<bool> { parse_i32(v).map(|x| x != 0) };
39
40 let parse_list_i32 = |v: &Value| -> Vec<i32> {
41 match v {
42 Value::List(xs) => xs
43 .iter()
44 .filter_map(|x| x.as_i64().and_then(|n| i32::try_from(n).ok()))
45 .collect(),
46 _ => Vec::new(),
47 }
48 };
49
50 match name.as_str() {
52 "WIPE" | "WIPE_ALL" | "MASK_WIPE" | "MASK_WIPE_ALL" => {
56 let is_mask = matches!(name.as_str(), "MASK_WIPE" | "MASK_WIPE_ALL");
57 let is_all = matches!(name.as_str(), "WIPE_ALL" | "MASK_WIPE_ALL");
58
59 let mut mask_file: Option<String> = None;
60 let mut wipe_type: i32 = 0;
61 let mut wipe_time: i32 = 500;
62 let mut speed_mode: i32 = 0;
63 let mut start_time: i32 = 0;
64 let mut option: Vec<i32> = Vec::new();
65
66 let mut begin_order: i32 = 0;
67 let mut end_order: i32 = if is_all { i32::MAX } else { 0 };
68 let mut begin_layer: i32 = i32::MIN;
69 let mut end_layer: i32 = i32::MAX;
70 let mut wait_flag: bool = true;
71 let mut key_wait_mode: i32 = -1;
72 let mut with_low_order: i32 = 0;
73
74 if is_mask {
75 mask_file = pos.get(0).and_then(|v| v.as_str()).map(|s| s.to_string());
76 if let Some(v) = pos.get(1).and_then(|v| parse_i32(v)) {
77 wipe_type = v;
78 }
79 if let Some(v) = pos.get(2).and_then(|v| parse_i32(v)) {
80 wipe_time = v;
81 }
82 if let Some(v) = pos.get(3).and_then(|v| parse_i32(v)) {
83 speed_mode = v;
84 }
85 if let Some(v) = pos.get(4) {
86 option = parse_list_i32(v);
87 }
88 } else {
89 if let Some(v) = pos.get(0).and_then(|v| parse_i32(v)) {
90 wipe_type = v;
91 }
92 if let Some(v) = pos.get(1).and_then(|v| parse_i32(v)) {
93 wipe_time = v;
94 }
95 if let Some(v) = pos.get(2).and_then(|v| parse_i32(v)) {
96 speed_mode = v;
97 }
98 if let Some(v) = pos.get(3) {
99 option = parse_list_i32(v);
100 }
101 }
102
103 for &(id, v) in &named {
105 match id {
106 0 => {
107 if let Some(x) = parse_i32(v) {
108 wipe_type = x;
109 }
110 }
111 1 => {
112 if let Some(x) = parse_i32(v) {
113 wipe_time = x;
114 }
115 }
116 2 => {
117 if let Some(x) = parse_i32(v) {
118 speed_mode = x;
119 }
120 }
121 3 => {
122 option = parse_list_i32(v);
123 }
124 4 => {
125 if let Some(x) = parse_i32(v) {
126 begin_order = x;
127 }
128 }
129 5 => {
130 if let Some(x) = parse_i32(v) {
131 end_order = x;
132 }
133 }
134 6 => {
135 if let Some(x) = parse_i32(v) {
136 begin_layer = x;
137 }
138 }
139 7 => {
140 if let Some(x) = parse_i32(v) {
141 end_layer = x;
142 }
143 }
144 8 => {
145 if let Some(x) = parse_bool(v) {
146 wait_flag = x;
147 }
148 }
149 9 => {
150 if let Some(x) = parse_i32(v) {
151 key_wait_mode = x;
152 }
153 }
154 10 => {
155 if let Some(x) = parse_i32(v) {
156 with_low_order = x;
157 }
158 }
159 11 => {
160 if let Some(x) = parse_i32(v) {
161 start_time = x;
162 }
163 }
164 _ => {}
165 }
166 }
167
168 if is_all {
169 end_order = i32::MAX;
170 }
171
172 let mask_image_id = if let Some(ref f) = mask_file {
173 resolve_mask_path(&ctx.project_dir, f)
174 .and_then(|p| ctx.images.load_file(&p, 0).ok())
175 } else {
176 None
177 };
178 stage::apply_stage_wipe(ctx, begin_order, end_order, begin_layer, end_layer);
179 ctx.globals.start_wipe(WipeState::new(
180 mask_file,
181 mask_image_id,
182 wipe_type,
183 wipe_time,
184 start_time,
185 speed_mode,
186 option,
187 begin_order,
188 end_order,
189 begin_layer,
190 end_layer,
191 wait_flag,
192 key_wait_mode,
193 with_low_order,
194 ));
195
196 if wait_flag {
197 let key_skip = match key_wait_mode {
198 0 => false,
199 1 => true,
200 _ => {
201 ctx.globals
202 .syscom
203 .config_int
204 .get(&197)
205 .copied()
206 .unwrap_or(0)
207 != 0
208 }
209 };
210 ctx.wait.wait_wipe(key_skip);
211 }
212 return Ok(true);
213 }
214 "WAIT_WIPE" | "WAITWIPE" => {
215 let mut key_wait_mode: i32 = -1;
216 for &(id, v) in &named {
217 if id == 0 {
218 if let Some(x) = parse_i32(v) {
219 key_wait_mode = x;
220 }
221 }
222 }
223 let key_skip = match key_wait_mode {
224 0 => false,
225 1 => true,
226 _ => {
227 ctx.globals
228 .syscom
229 .config_int
230 .get(&197)
231 .copied()
232 .unwrap_or(0)
233 != 0
234 }
235 };
236 ctx.wait.wait_wipe(key_skip);
237 return Ok(true);
238 }
239
240 "WAIT" | "SLEEP" => {
241 let ms = args
243 .iter()
244 .rev()
245 .find_map(|v| match v {
246 Value::Int(x) => u64::try_from(*x).ok(),
247 _ => None,
248 })
249 .unwrap_or(0);
250 if ms > 0 {
251 ctx.wait.wait_ms(ms);
252 }
253 return Ok(true);
254 }
255 "WAITKEY" | "WAIT_KEY" | "WAIT_KEYDOWN" | "WAITCLICK" | "WAIT_CLICK" => {
256 ctx.wait.wait_key();
258 return Ok(true);
259 }
260 "FADE" | "FADEIN" | "FADE_OUT" | "FADEOUT" | "TRANS" | "TRANSITION" | "DISSOLVE"
262 | "CROSSFADE" => {
263 let ms = args
264 .iter()
265 .rev()
266 .find_map(|v| match v {
267 Value::Int(x) => u64::try_from(*x).ok(),
268 _ => None,
269 })
270 .unwrap_or(0);
271 if ms > 0 {
272 ctx.wait.wait_ms(ms);
273 }
274 return Ok(true);
275 }
276 _ => {}
277 }
278
279 match name.as_str() {
280 "PAUSE" | "YIELD" => {
281 if let Some(ms) = pos
282 .first()
283 .and_then(|v| parse_i32(v))
284 .map(|v| v.max(0) as u64)
285 {
286 if ms > 0 {
287 ctx.wait.wait_ms(ms);
288 } else {
289 ctx.wait.wait_key();
290 }
291 } else {
292 ctx.wait.wait_key();
293 }
294 return Ok(true);
295 }
296 "AUTO" | "AUTO_ON" => {
297 ctx.globals.script.auto_mode_flag = true;
298 ctx.globals.script.skip_trigger = false;
299 return Ok(true);
300 }
301 "AUTO_OFF" => {
302 ctx.globals.script.auto_mode_flag = false;
303 return Ok(true);
304 }
305 "SKIP" | "SKIP_ON" => {
306 if !ctx.globals.script.skip_disable {
307 ctx.globals.script.skip_trigger = true;
308 ctx.globals.script.auto_mode_flag = false;
309 }
310 return Ok(true);
311 }
312 "SKIP_OFF" => {
313 ctx.globals.script.skip_trigger = false;
314 return Ok(true);
315 }
316 "SOUND" | "SOUND_ON" => {
317 for key in [212, 213, 214, 215, 216, 217, 218] {
318 ctx.globals.syscom.config_int.insert(key, 1);
319 }
320 syscom_form::apply_audio_config(ctx);
321 return Ok(true);
322 }
323 "SOUND_OFF" => {
324 for key in [212, 213, 214, 215, 216, 217, 218] {
325 ctx.globals.syscom.config_int.insert(key, 0);
326 }
327 syscom_form::apply_audio_config(ctx);
328 return Ok(true);
329 }
330 "LOG" | "PRINT" | "DEBUG" | "TRACE" => {
331 let mut parts: Vec<String> = Vec::new();
332 for v in &pos {
333 parts.push(match v {
334 Value::Int(x) => x.to_string(),
335 Value::Str(s) => s.clone(),
336 Value::List(xs) => format!("{:?}", xs),
337 _ => String::new(),
338 });
339 }
340 if parts.is_empty() {
341 parts.push(name.clone());
342 }
343 ctx.globals.system.debug_logs.push(parts.join(" "));
344 return Ok(true);
345 }
346 _ => {}
347 }
348
349 if is_noop_cmd(name.as_str()) {
350 return Ok(true);
351 }
352
353 if is_clear_cmd(name.as_str()) {
354 ctx.layers.clear_all();
356 ctx.gfx = crate::runtime::graphics::GfxRuntime::new();
357 return Ok(true);
358 }
359
360 Ok(false)
361}
362
363fn resolve_mask_path(project_dir: &Path, raw: &str) -> Option<PathBuf> {
364 if raw.is_empty() {
365 return None;
366 }
367 let norm = raw.replace('\\', "/");
368 let p = Path::new(&norm);
369 if p.is_absolute() && p.is_file() {
370 return Some(p.to_path_buf());
371 }
372 let mut candidates = Vec::new();
373 candidates.push(project_dir.join(&norm));
374 candidates.push(project_dir.join("dat").join(&norm));
375 if p.extension().is_none() {
376 for ext in ["png", "bmp", "jpg"] {
377 candidates.push(project_dir.join(format!("{}.{}", norm, ext)));
378 candidates.push(project_dir.join("dat").join(format!("{}.{}", norm, ext)));
379 }
380 }
381 for c in candidates {
382 if c.is_file() {
383 return Some(c);
384 }
385 }
386 None
387}