1use anyhow::Result;
2
3use std::path::{Path, PathBuf};
4
5use crate::runtime::forms::codes::int_event_op;
6use crate::runtime::globals::WipeState;
7use crate::runtime::{constants, forms, CommandContext, Value};
8
9use crate::runtime::forms::{
10 cgtable, counter, database, editbox, file, frame_action, frame_action_ch, g00buf, input,
11 int_event, int_list, key, keylist, mask, math, mouse, object_event, script, stage, steam,
12 str_list, syscom, system, timewait,
13};
14
15fn canonical_global_form_id(ctx: &CommandContext, form_id: u32) -> u32 {
16 let ids = &ctx.ids;
17 if constants::is_stage_global_form(form_id, ids.form_global_stage) {
18 return constants::global_form::STAGE_ALT;
19 }
20 if constants::matches_form_id(form_id, ids.form_global_mov, constants::global_form::MOV) {
21 return constants::global_form::MOV;
22 }
23 if constants::matches_form_id(form_id, ids.form_global_bgm, constants::global_form::BGM) {
24 return constants::global_form::BGM;
25 }
26 if constants::matches_form_id(
27 form_id,
28 ids.form_global_bgm_table,
29 constants::global_form::BGMTABLE,
30 ) {
31 return constants::global_form::BGMTABLE;
32 }
33 if constants::matches_form_id(form_id, ids.form_global_pcm, constants::global_form::PCM) {
34 return constants::global_form::PCM;
35 }
36 if constants::matches_form_id(
37 form_id,
38 ids.form_global_pcmch,
39 constants::global_form::PCMCH,
40 ) {
41 return constants::global_form::PCMCH;
42 }
43 if constants::matches_form_id(form_id, ids.form_global_se, constants::global_form::SE) {
44 return constants::global_form::SE;
45 }
46 if constants::matches_form_id(
47 form_id,
48 ids.form_global_pcm_event,
49 constants::global_form::PCMEVENT,
50 ) {
51 return constants::global_form::PCMEVENT;
52 }
53 if constants::matches_form_id(
54 form_id,
55 ids.form_global_excall,
56 constants::global_form::EXCALL,
57 ) {
58 return constants::global_form::EXCALL;
59 }
60 if constants::matches_form_id(
61 form_id,
62 ids.form_global_screen,
63 constants::global_form::SCREEN,
64 ) {
65 return constants::global_form::SCREEN;
66 }
67 if constants::matches_form_id(
68 form_id,
69 ids.form_global_msgbk,
70 constants::global_form::MSGBK,
71 ) {
72 return constants::global_form::MSGBK;
73 }
74 if constants::matches_form_id(
75 form_id,
76 ids.form_global_koe_st,
77 constants::global_form::KOE_ST,
78 ) {
79 return constants::global_form::KOE_ST;
80 }
81 if constants::matches_form_id(form_id, ids.form_global_key, constants::global_form::KEY) {
82 return constants::global_form::KEY;
83 }
84 if constants::matches_form_id(
85 form_id,
86 ids.form_global_frame_action,
87 constants::global_form::FRAME_ACTION,
88 ) {
89 return constants::global_form::FRAME_ACTION;
90 }
91 if form_id == constants::global_form::TIMEWAIT {
92 return constants::global_form::TIMEWAIT;
93 }
94 if form_id == constants::global_form::TIMEWAIT_KEY {
95 return constants::global_form::TIMEWAIT_KEY;
96 }
97 if form_id == constants::global_form::COUNTER {
98 return constants::global_form::COUNTER;
99 }
100 form_id
101}
102
103fn named_i64(args: &[Value], id: i32) -> Option<i64> {
104 args.iter().find_map(|v| match v {
105 Value::NamedArg { id: got, value } if *got == id => value.as_i64(),
106 _ => None,
107 })
108}
109
110fn positional_i64(args: &[Value], idx: usize) -> Option<i64> {
111 args.iter()
112 .filter(|v| !matches!(v, Value::NamedArg { .. }))
113 .filter_map(Value::as_i64)
114 .nth(idx)
115}
116
117fn global_stage_alias_to_index(form_id: i32) -> Option<i64> {
118 let form_id = form_id as u32;
119 if form_id == constants::global_form::BACK {
120 Some(0)
121 } else if form_id == constants::global_form::FRONT {
122 Some(1)
123 } else if form_id == constants::global_form::NEXT {
124 Some(2)
125 } else {
126 None
127 }
128}
129
130fn mwnd_ref_from_value(v: &Value) -> Option<(i64, usize)> {
131 match v.unwrap_named() {
132 Value::Int(n) if *n >= 0 => Some((1, *n as usize)),
133 Value::Element(chain) => {
134 let stage = chain
135 .first()
136 .and_then(|head| global_stage_alias_to_index(*head))
137 .unwrap_or(1);
138 let no = chain.windows(2).find_map(|w| {
139 (w[0] == forms::codes::ELM_ARRAY && w[1] >= 0).then_some(w[1] as usize)
140 })?;
141 Some((stage, no))
142 }
143 _ => None,
144 }
145}
146
147fn mwnd_no_from_value(v: &Value) -> Option<usize> {
148 mwnd_ref_from_value(v).map(|(_, no)| no)
149}
150
151const TNM_STAGE_FRONT_SELBTN: i64 = 1;
152const TNM_SEL_ITEM_TYPE_OFF: i64 = 0;
153const TNM_SEL_ITEM_TYPE_ON: i64 = 1;
154const TNM_SEL_ITEM_TYPE_READ: i64 = 2;
155
156fn parse_selbtn_choices(
157 args: &[Value],
158) -> (i64, Vec<crate::runtime::globals::BtnSelectChoiceState>) {
159 let mut template_no = 0i64;
160 let mut start = 0usize;
161 if args.first().is_some_and(|v| v.named_id().is_none() && v.as_i64().is_some()) {
162 template_no = args.first().and_then(Value::as_i64).unwrap_or(0);
163 start = 1;
164 }
165
166 let mut out = Vec::new();
167 let mut last: Option<usize> = None;
168 let mut arg_no = 0i32;
169 for v in args.iter().skip(start).filter(|v| v.named_id().is_none()).map(Value::unwrap_named) {
170 if let Some(s) = v.as_str() {
171 out.push(crate::runtime::globals::BtnSelectChoiceState {
172 text: s.to_string(),
173 item_type: TNM_SEL_ITEM_TYPE_ON,
174 color: -1,
175 pos: (0, 0),
176 size: (0, 0),
177 });
178 last = Some(out.len() - 1);
179 arg_no = 0;
180 } else if let Some(n) = v.as_i64() {
181 if let Some(i) = last {
182 match arg_no {
183 1 => out[i].item_type = n,
184 2 => out[i].color = n,
185 _ => {}
186 }
187 }
188 }
189 arg_no += 1;
190 }
191 (template_no, out)
192}
193
194fn selbtn_text_extent(text: &str, tmpl: &crate::runtime::tables::SelBtnTemplate) -> (i64, i64) {
195 let font_px = tmpl.moji_size.max(1);
196 let mut width = 0i64;
197 for ch in text.chars() {
198 let advance = if ch.is_ascii() || matches!(ch as u32, 0xFF61..=0xFF9F) {
199 (font_px + tmpl.moji_space.0) / 2
200 } else {
201 font_px + tmpl.moji_space.0
202 };
203 width = width.saturating_add(advance.max(1));
204 }
205 if !text.is_empty() {
206 width = width.saturating_sub(tmpl.moji_space.0);
207 }
208 (width.max(font_px), font_px)
209}
210
211fn load_selbtn_image_id(
212 ctx: &mut CommandContext,
213 file_name: &str,
214 patno: u32,
215) -> Option<crate::image_manager::ImageId> {
216 if file_name.is_empty() {
217 return None;
218 }
219 match ctx.images.load_g00(file_name, patno) {
220 Ok(id) => Some(id),
221 Err(_) => ctx.images.load_bg_frame(file_name, patno as usize).ok(),
222 }
223}
224
225fn selbtn_template_item_size(
226 ctx: &mut CommandContext,
227 choices: &[crate::runtime::globals::BtnSelectChoiceState],
228 tmpl: &crate::runtime::tables::SelBtnTemplate,
229) -> (i64, i64) {
230 if let Some(img_id) = load_selbtn_image_id(ctx, &tmpl.base_file, 0) {
231 if let Some(img) = ctx.images.get(img_id) {
232 return (img.width as i64, img.height as i64);
233 }
234 }
235 choices
236 .first()
237 .map(|choice| {
238 let (tw, th) = selbtn_text_extent(&choice.text, tmpl);
239 (
240 tw.saturating_add(tmpl.moji_pos.0.max(0)).max(1),
241 th.saturating_add(tmpl.moji_pos.1.max(0)).max(1),
242 )
243 })
244 .unwrap_or((tmpl.moji_size.max(1), tmpl.moji_size.max(1)))
245}
246
247fn layout_selbtn_choices(
248 choices: &mut [crate::runtime::globals::BtnSelectChoiceState],
249 tmpl: &crate::runtime::tables::SelBtnTemplate,
250 item_size: (i64, i64),
251) {
252 let rep_pos = if tmpl.rep_pos == (0, 0) {
253 (0, item_size.1.max(tmpl.moji_size.max(1)).max(1))
254 } else {
255 tmpl.rep_pos
256 };
257
258 let mut offset = (0i64, 0i64);
259 let mut max_offset = (0i64, 0i64);
260 let mut org_offset_x = 0i64;
261 let mut y_cnt = 0i64;
262 for choice in choices.iter_mut() {
263 if choice.item_type != TNM_SEL_ITEM_TYPE_OFF {
264 choice.pos = offset;
265 choice.size = item_size;
266 offset.0 = offset.0.saturating_add(rep_pos.0);
267 offset.1 = offset.1.saturating_add(rep_pos.1);
268 max_offset.0 = max_offset.0.max(offset.0);
269 max_offset.1 = max_offset.1.max(offset.1);
270 y_cnt += 1;
271 if tmpl.max_y_cnt > 0 && y_cnt >= tmpl.max_y_cnt {
272 offset.0 = org_offset_x.saturating_add(tmpl.line_width);
273 offset.1 = 0;
274 org_offset_x = offset.0;
275 y_cnt = 0;
276 }
277 }
278 }
279
280 let total_x = max_offset.0.saturating_sub(rep_pos.0).saturating_add(item_size.0);
281 let total_y = max_offset.1.saturating_sub(rep_pos.1).saturating_add(item_size.1);
282 let align_x = match tmpl.x_align {
283 1 => -total_x / 2,
284 2 => -total_x,
285 _ => 0,
286 };
287 let align_y = match tmpl.y_align {
288 1 => -total_y / 2,
289 2 => -total_y,
290 _ => 0,
291 };
292 for choice in choices.iter_mut() {
293 if choice.item_type != TNM_SEL_ITEM_TYPE_OFF {
294 choice.pos.0 = choice.pos.0.saturating_add(align_x).saturating_add(tmpl.base_pos.0);
295 choice.pos.1 = choice.pos.1.saturating_add(align_y).saturating_add(tmpl.base_pos.1);
296 }
297 }
298}
299
300fn selbtn_table_color(
301 tables: &crate::runtime::tables::AssetTables,
302 color_no: i64,
303 fallback: (u8, u8, u8),
304) -> (u8, u8, u8) {
305 if color_no < 0 {
306 return fallback;
307 }
308 tables
309 .color_table
310 .get(color_no as usize)
311 .copied()
312 .unwrap_or(fallback)
313}
314
315fn hide_selbtn_object_backing(ctx: &mut CommandContext, obj: &crate::runtime::globals::ObjectState) {
316 match obj.backend {
317 crate::runtime::globals::ObjectBackend::Rect { layer_id, sprite_id, .. }
318 | crate::runtime::globals::ObjectBackend::String { layer_id, sprite_id, .. }
319 | crate::runtime::globals::ObjectBackend::Movie { layer_id, sprite_id, .. } => {
320 if let Some(layer) = ctx.layers.layer_mut(layer_id) {
321 if let Some(sprite) = layer.sprite_mut(sprite_id) {
322 sprite.visible = false;
323 sprite.image_id = None;
324 }
325 }
326 }
327 crate::runtime::globals::ObjectBackend::Number { layer_id, ref sprite_ids }
328 | crate::runtime::globals::ObjectBackend::Weather { layer_id, ref sprite_ids } => {
329 if let Some(layer) = ctx.layers.layer_mut(layer_id) {
330 for &sprite_id in sprite_ids {
331 if let Some(sprite) = layer.sprite_mut(sprite_id) {
332 sprite.visible = false;
333 sprite.image_id = None;
334 }
335 }
336 }
337 }
338 _ => {}
339 }
340 for child in &obj.runtime.child_objects {
341 hide_selbtn_object_backing(ctx, child);
342 }
343}
344
345fn clear_existing_stage_btnselitems(ctx: &mut CommandContext) {
346 let old_items = {
347 let Some(st) = ctx.globals.stage_forms.get(&ctx.ids.form_global_stage) else {
348 return;
349 };
350 st.btnselitem_lists
351 .get(&TNM_STAGE_FRONT_SELBTN)
352 .cloned()
353 .unwrap_or_default()
354 };
355 for item in &old_items {
356 for obj in item.generated_objects.iter().chain(item.object_list.iter()) {
357 hide_selbtn_object_backing(ctx, obj);
358 }
359 }
360}
361
362fn make_selbtn_image_object(
363 ctx: &mut CommandContext,
364 file_name: &str,
365 patno: u32,
366 width: i64,
367 height: i64,
368 layer_rep: i64,
369 item_type: i64,
370 _selected: bool,
371) -> Option<crate::runtime::globals::ObjectState> {
372 let img_id = load_selbtn_image_id(ctx, file_name, patno)?;
373 let (img_w, img_h) = ctx
374 .images
375 .get(img_id)
376 .map(|img| (img.width.max(1), img.height.max(1)))
377 .unwrap_or((width.max(1) as u32, height.max(1) as u32));
378 let layer_id = ctx.layers.create_layer();
379 let sprite_id = ctx.layers.layer_mut(layer_id).map(|layer| layer.create_sprite())?;
380 if let Some(layer) = ctx.layers.layer_mut(layer_id) {
381 if let Some(sprite) = layer.sprite_mut(sprite_id) {
382 sprite.fit = crate::layer::SpriteFit::PixelRect;
383 sprite.size_mode = crate::layer::SpriteSizeMode::Intrinsic;
384 sprite.visible = item_type == TNM_SEL_ITEM_TYPE_ON || item_type == TNM_SEL_ITEM_TYPE_READ;
385 sprite.x = 0;
386 sprite.y = 0;
387 sprite.image_id = Some(img_id);
388 sprite.tr = 255;
389 }
390 }
391
392 let mut obj = crate::runtime::globals::ObjectState::default();
393 obj.used = true;
394 obj.backend = crate::runtime::globals::ObjectBackend::Rect {
395 layer_id,
396 sprite_id,
397 width: img_w,
398 height: img_h,
399 };
400 obj.object_type = 2;
401 obj.file_name = Some(file_name.to_string());
402 obj.base.disp = if item_type == TNM_SEL_ITEM_TYPE_ON || item_type == TNM_SEL_ITEM_TYPE_READ { 1 } else { 0 };
403 obj.base.x = 0;
404 obj.base.y = 0;
405 obj.base.patno = patno as i64;
406 obj.base.layer = layer_rep;
407 if ctx.ids.obj_disp != 0 {
408 obj.set_int_prop(&ctx.ids, ctx.ids.obj_disp, obj.base.disp);
409 }
410 if ctx.ids.obj_x != 0 {
411 obj.set_int_prop(&ctx.ids, ctx.ids.obj_x, 0);
412 }
413 if ctx.ids.obj_y != 0 {
414 obj.set_int_prop(&ctx.ids, ctx.ids.obj_y, 0);
415 }
416 if ctx.ids.obj_patno != 0 {
417 obj.set_int_prop(&ctx.ids, ctx.ids.obj_patno, patno as i64);
418 }
419 if ctx.ids.obj_layer != 0 {
420 obj.set_int_prop(&ctx.ids, ctx.ids.obj_layer, layer_rep);
421 }
422
423
424 Some(obj)
425}
426
427fn make_selbtn_text_object(
428 ctx: &mut CommandContext,
429 choice: &crate::runtime::globals::BtnSelectChoiceState,
430 tmpl: &crate::runtime::tables::SelBtnTemplate,
431 color_no: i64,
432 selected: bool,
433) -> Option<crate::runtime::globals::ObjectState> {
434 if !(choice.item_type == TNM_SEL_ITEM_TYPE_ON || choice.item_type == TNM_SEL_ITEM_TYPE_READ) || choice.text.is_empty() {
435 return None;
436 }
437 if !ctx.font_cache.is_loaded() {
438 let _ = ctx.font_cache.load_for_project(&ctx.project_dir);
439 }
440 let (tw, th) = selbtn_text_extent(&choice.text, tmpl);
441 let width = tw.max(1) as u32;
442 let height = th.max(tmpl.moji_size.max(1)) as u32;
443 let effective_color_no = if selected && choice.item_type == TNM_SEL_ITEM_TYPE_ON && tmpl.moji_hit_color >= 0 {
444 tmpl.moji_hit_color
445 } else {
446 color_no
447 };
448 let color = selbtn_table_color(&ctx.tables, effective_color_no, (255, 255, 255));
449 let shadow_color = selbtn_table_color(&ctx.tables, ctx.tables.mwnd_render.shadow_color, (0, 0, 0));
450 let fuchi_color = selbtn_table_color(&ctx.tables, ctx.tables.mwnd_render.fuchi_color, (0, 0, 0));
451 let style = crate::text_render::TextStyle {
452 color,
453 shadow_color,
454 fuchi_color,
455 shadow: ctx.tables.font_defaults.shadow == 1 || ctx.tables.font_defaults.shadow == 3,
456 fuchi: ctx.tables.font_defaults.shadow == 2 || ctx.tables.font_defaults.shadow == 3,
457 bold: ctx.tables.font_defaults.futoku != 0,
458 };
459 let img_id = ctx.font_cache.render_mwnd_text_styled(
460 &mut ctx.images,
461 &choice.text,
462 tmpl.moji_size.max(1) as f32,
463 width,
464 height,
465 Some(tmpl.moji_space),
466 style,
467 );
468 let layer_id = ctx.layers.create_layer();
469 let sprite_id = ctx.layers.layer_mut(layer_id).map(|layer| layer.create_sprite())?;
470 if let Some(layer) = ctx.layers.layer_mut(layer_id) {
471 if let Some(sprite) = layer.sprite_mut(sprite_id) {
472 sprite.fit = crate::layer::SpriteFit::PixelRect;
473 sprite.size_mode = if img_id.is_some() {
474 crate::layer::SpriteSizeMode::Intrinsic
475 } else {
476 crate::layer::SpriteSizeMode::Explicit { width, height }
477 };
478 sprite.visible = true;
479 sprite.x = tmpl.moji_pos.0 as i32;
480 sprite.y = tmpl.moji_pos.1 as i32;
481 sprite.image_id = img_id;
482 sprite.tr = 255;
483 }
484 }
485 let mut obj = crate::runtime::globals::ObjectState::default();
486 obj.used = true;
487 obj.backend = crate::runtime::globals::ObjectBackend::String {
488 layer_id,
489 sprite_id,
490 image_id: img_id,
491 width,
492 height,
493 };
494 obj.object_type = 3;
495 obj.string_value = Some(choice.text.clone());
496 obj.string_param.moji_size = tmpl.moji_size;
497 obj.string_param.moji_space_x = tmpl.moji_space.0;
498 obj.string_param.moji_space_y = tmpl.moji_space.1;
499 obj.string_param.moji_cnt = tmpl.moji_cnt;
500 obj.string_param.moji_color = effective_color_no;
501 obj.string_param.shadow_color = ctx.tables.mwnd_render.shadow_color;
502 obj.string_param.fuchi_color = ctx.tables.mwnd_render.fuchi_color;
503 obj.base.disp = 1;
504 obj.base.x = tmpl.moji_pos.0;
505 obj.base.y = tmpl.moji_pos.1;
506 obj.base.layer = ctx.tables.mwnd_render.moji_layer_rep;
507 if ctx.ids.obj_disp != 0 {
508 obj.set_int_prop(&ctx.ids, ctx.ids.obj_disp, 1);
509 }
510 if ctx.ids.obj_x != 0 {
511 obj.set_int_prop(&ctx.ids, ctx.ids.obj_x, tmpl.moji_pos.0);
512 }
513 if ctx.ids.obj_y != 0 {
514 obj.set_int_prop(&ctx.ids, ctx.ids.obj_y, tmpl.moji_pos.1);
515 }
516 if ctx.ids.obj_layer != 0 {
517 obj.set_int_prop(&ctx.ids, ctx.ids.obj_layer, ctx.tables.mwnd_render.moji_layer_rep);
518 }
519 Some(obj)
520}
521
522fn prepare_stage_btnselitems(ctx: &mut CommandContext) {
523 clear_existing_stage_btnselitems(ctx);
524 let template_no = ctx.globals.selbtn.template_no.max(0) as usize;
525 let tmpl = ctx
526 .tables
527 .sel_btn_templates
528 .get(template_no)
529 .cloned()
530 .unwrap_or_default();
531 let choices_for_size = ctx.globals.selbtn.choices.clone();
532 let item_size = selbtn_template_item_size(ctx, &choices_for_size, &tmpl);
533 layout_selbtn_choices(&mut ctx.globals.selbtn.choices, &tmpl, item_size);
534 let choices_snapshot = ctx.globals.selbtn.choices.clone();
535 let cursor = ctx.globals.selbtn.cursor;
536 let waku_layer_rep = ctx.tables.mwnd_render.waku_layer_rep;
537 let filter_layer_rep = ctx.tables.mwnd_render.filter_layer_rep;
538 let mut prepared = Vec::with_capacity(choices_snapshot.len());
539 for (idx, choice) in choices_snapshot.iter().enumerate() {
540 let mut item = crate::runtime::globals::BtnSelItemState::default();
541 item.text = choice.text.clone();
542 item.item_type = choice.item_type;
543 item.color = if choice.color >= 0 { choice.color } else { tmpl.moji_color };
544 item.pos = choice.pos;
545 item.size = choice.size;
546 item.visible = choice.item_type == TNM_SEL_ITEM_TYPE_ON || choice.item_type == TNM_SEL_ITEM_TYPE_READ;
547 item.selected = idx == cursor;
548 item.button_action_no = tmpl.btn_action_no;
549 item.button_state = if choice.item_type == TNM_SEL_ITEM_TYPE_READ {
550 4
551 } else if item.selected && choice.item_type == TNM_SEL_ITEM_TYPE_ON {
552 1
553 } else {
554 0
555 };
556
557 if let Some(obj) = make_selbtn_image_object(
558 ctx,
559 &tmpl.base_file,
560 0,
561 item.size.0,
562 item.size.1,
563 waku_layer_rep,
564 choice.item_type,
565 item.selected,
566 ) {
567 item.generated_objects.push(obj);
568 }
569 if let Some(obj) = make_selbtn_image_object(
570 ctx,
571 &tmpl.filter_file,
572 0,
573 item.size.0,
574 item.size.1,
575 filter_layer_rep,
576 choice.item_type,
577 item.selected,
578 ) {
579 item.generated_objects.push(obj);
580 }
581 if let Some(obj) = make_selbtn_text_object(ctx, choice, &tmpl, item.color, item.selected) {
582 item.generated_objects.push(obj);
583 }
584 prepared.push(item);
585 }
586 let st = ctx
587 .globals
588 .stage_forms
589 .entry(ctx.ids.form_global_stage)
590 .or_default();
591 st.btnselitem_lists
592 .insert(TNM_STAGE_FRONT_SELBTN, prepared);
593}
594
595fn first_selectable_selbtn_choice(choices: &[crate::runtime::globals::BtnSelectChoiceState]) -> usize {
596 choices
597 .iter()
598 .position(|choice| choice.item_type == TNM_SEL_ITEM_TYPE_ON)
599 .unwrap_or(0)
600}
601
602fn dispatch_selbtn_command(ctx: &mut CommandContext, form_id: u32, args: &[Value]) -> Result<bool> {
603 let op = form_id as i32;
604 let ready = op == constants::elm_value::GLOBAL_SELBTN_READY
605 || op == constants::elm_value::GLOBAL_SELBTN_CANCEL_READY;
606 let start_now = op == constants::elm_value::GLOBAL_SELBTN
607 || op == constants::elm_value::GLOBAL_SELBTN_CANCEL
608 || op == constants::elm_value::GLOBAL_SELBTN_START;
609 if !ready && !start_now {
610 return Ok(false);
611 }
612
613 if op != constants::elm_value::GLOBAL_SELBTN_START {
614 let (template_no, choices) = parse_selbtn_choices(args);
615 let capture_flag = named_i64(args, 1).unwrap_or(0) != 0;
616 let sel_start_call_scn = args
617 .iter()
618 .find(|v| v.named_id() == Some(2))
619 .and_then(Value::as_str)
620 .unwrap_or("")
621 .to_string();
622 let sel_start_call_z_no = named_i64(args, 3).unwrap_or(0);
623 ctx.globals.selbtn.template_no = template_no;
624 ctx.globals.selbtn.choices = choices;
625 ctx.globals.selbtn.cursor = first_selectable_selbtn_choice(&ctx.globals.selbtn.choices);
626 ctx.globals.selbtn.cancel_enable = op == constants::elm_value::GLOBAL_SELBTN_CANCEL
627 || op == constants::elm_value::GLOBAL_SELBTN_CANCEL_READY;
628 ctx.globals.selbtn.capture_flag = if ready { false } else { capture_flag };
629 ctx.globals.selbtn.sel_start_call_scn = if ready {
630 String::new()
631 } else {
632 sel_start_call_scn
633 };
634 ctx.globals.selbtn.sel_start_call_z_no = if ready { 0 } else { sel_start_call_z_no };
635 ctx.globals.selbtn.result = 0;
636 prepare_stage_btnselitems(ctx);
637 }
638
639 if start_now {
640 ctx.globals.selbtn.started = true;
641 ctx.globals.selbtn.sync_type = named_i64(args, 4).unwrap_or(0);
642 ctx.globals.selbtn.read_flag_scene_no = ctx.current_scene_no.unwrap_or(-1);
643 ctx.globals.selbtn.read_flag_flag_no = -1;
644 ctx.request_read_flag_no_for_selbtn();
645 ctx.wait.wait_key();
646 }
647 Ok(true)
648}
649
650fn global_koe_state_key(_ctx: &CommandContext) -> u32 {
651 constants::fm::GLOBAL as u32
652}
653
654fn remember_global_koe(ctx: &mut CommandContext, koe_no: i64, chara_no: i64, is_ex: bool) {
655 let key = global_koe_state_key(ctx);
656 let props = ctx.globals.int_props.entry(key).or_default();
657 props.insert(constants::elm_value::GLOBAL_KOE_CHECK_GET_KOE_NO, koe_no);
658 props.insert(
659 constants::elm_value::GLOBAL_KOE_CHECK_GET_CHARA_NO,
660 chara_no,
661 );
662 props.insert(
663 constants::elm_value::GLOBAL_KOE_CHECK_IS_EX_KOE,
664 if is_ex { 1 } else { 0 },
665 );
666}
667
668fn remembered_global_koe(ctx: &CommandContext, op: i32) -> i64 {
669 let key = global_koe_state_key(ctx);
670 ctx.globals
671 .int_props
672 .get(&key)
673 .and_then(|m| m.get(&op).copied())
674 .unwrap_or(0)
675}
676
677fn dispatch_global_koe_command(
678 ctx: &mut CommandContext,
679 form_id: u32,
680 args: &[Value],
681) -> Result<bool> {
682 let op = form_id as i32;
683 let ret_form: Option<i64> = crate::runtime::forms::prop_access::current_vm_meta(ctx).1;
684 match op {
685 constants::elm_value::GLOBAL_KOE | constants::elm_value::GLOBAL_EXKOE => {
686 ctx.request_read_flag_no();
687 let is_ex = op == constants::elm_value::GLOBAL_EXKOE;
688 let koe_no = if is_ex {
689 named_i64(args, 0).or_else(|| positional_i64(args, 0))
690 } else {
691 positional_i64(args, 0)
692 }
693 .unwrap_or(0);
694 let chara_no = if is_ex {
695 named_i64(args, 1).or_else(|| positional_i64(args, 1))
696 } else {
697 positional_i64(args, 1)
698 }
699 .unwrap_or(0);
700 remember_global_koe(ctx, koe_no, chara_no, is_ex);
701 if let Err(err) = {
702 let (koe, audio) = (&mut ctx.koe, &mut ctx.audio);
703 koe.play_koe_no(audio, koe_no)
704 } {
705 eprintln!("[SG_AUDIO] koe.play failed koe_no={koe_no}: {err:#}");
706 }
707 if is_ex && named_i64(args, 2).unwrap_or(0) != 0 {
708 let key_skip = named_i64(args, 3).unwrap_or(0) != 0;
709 ctx.wait
710 .wait_audio(crate::runtime::wait::AudioWait::KoeAny, key_skip);
711 }
712 if ret_form.unwrap_or(0) != 0 {
713 ctx.push(Value::Int(0));
714 }
715 Ok(true)
716 }
717 constants::elm_value::GLOBAL_KOE_PLAY_WAIT
718 | constants::elm_value::GLOBAL_KOE_PLAY_WAIT_KEY
719 | constants::elm_value::GLOBAL_EXKOE_PLAY_WAIT
720 | constants::elm_value::GLOBAL_EXKOE_PLAY_WAIT_KEY => {
721 ctx.request_read_flag_no();
722 let is_ex = op == constants::elm_value::GLOBAL_EXKOE_PLAY_WAIT
723 || op == constants::elm_value::GLOBAL_EXKOE_PLAY_WAIT_KEY;
724 let koe_no = if is_ex {
725 named_i64(args, 0).or_else(|| positional_i64(args, 0))
726 } else {
727 positional_i64(args, 0)
728 }
729 .unwrap_or(0);
730 let chara_no = if is_ex {
731 named_i64(args, 1).or_else(|| positional_i64(args, 1))
732 } else {
733 positional_i64(args, 1)
734 }
735 .unwrap_or(0);
736 remember_global_koe(ctx, koe_no, chara_no, is_ex);
737 if let Err(err) = {
738 let (koe, audio) = (&mut ctx.koe, &mut ctx.audio);
739 koe.play_koe_no(audio, koe_no)
740 } {
741 eprintln!("[SG_AUDIO] koe.play_wait failed koe_no={koe_no}: {err:#}");
742 }
743 let key_skip = op == constants::elm_value::GLOBAL_KOE_PLAY_WAIT_KEY
744 || op == constants::elm_value::GLOBAL_EXKOE_PLAY_WAIT_KEY;
745 ctx.wait
746 .wait_audio(crate::runtime::wait::AudioWait::KoeAny, key_skip);
747 if ret_form.unwrap_or(0) != 0 {
748 ctx.push(Value::Int(0));
749 }
750 Ok(true)
751 }
752 constants::elm_value::GLOBAL_KOE_STOP => {
753 let fade = args.get(0).and_then(Value::as_i64);
754 let _ = ctx.koe.stop(fade);
755 Ok(true)
756 }
757 constants::elm_value::GLOBAL_KOE_WAIT | constants::elm_value::GLOBAL_KOE_WAIT_KEY => {
758 let key_skip = op == constants::elm_value::GLOBAL_KOE_WAIT_KEY;
759 ctx.wait
760 .wait_audio(crate::runtime::wait::AudioWait::KoeAny, key_skip);
761 if ret_form.unwrap_or(0) != 0 {
762 ctx.push(Value::Int(0));
763 }
764 Ok(true)
765 }
766 constants::elm_value::GLOBAL_KOE_CHECK => {
767 let playing = ctx.koe.is_playing_any();
768 ctx.push(Value::Int(if playing { 1 } else { 0 }));
769 Ok(true)
770 }
771 constants::elm_value::GLOBAL_KOE_CHECK_GET_KOE_NO
772 | constants::elm_value::GLOBAL_KOE_CHECK_GET_CHARA_NO
773 | constants::elm_value::GLOBAL_KOE_CHECK_IS_EX_KOE => {
774 ctx.push(Value::Int(remembered_global_koe(ctx, op)));
775 Ok(true)
776 }
777 constants::elm_value::GLOBAL_KOE_SET_VOLUME => {
778 let vol = args
779 .get(0)
780 .and_then(Value::as_i64)
781 .unwrap_or(255)
782 .clamp(0, 255) as u8;
783 let fade = args.get(1).and_then(Value::as_i64).unwrap_or(0);
784 let _ = ctx.koe.set_volume_raw_fade(&mut ctx.audio, vol, fade);
785 Ok(true)
786 }
787 constants::elm_value::GLOBAL_KOE_SET_VOLUME_MAX => {
788 let fade = args.get(0).and_then(Value::as_i64).unwrap_or(0);
789 let _ = ctx.koe.set_volume_raw_fade(&mut ctx.audio, 255, fade);
790 Ok(true)
791 }
792 constants::elm_value::GLOBAL_KOE_SET_VOLUME_MIN => {
793 let fade = args.get(0).and_then(Value::as_i64).unwrap_or(0);
794 let _ = ctx.koe.set_volume_raw_fade(&mut ctx.audio, 0, fade);
795 Ok(true)
796 }
797 constants::elm_value::GLOBAL_KOE_GET_VOLUME => {
798 ctx.push(Value::Int(ctx.koe.volume_raw() as i64));
799 Ok(true)
800 }
801 _ => Ok(false),
802 }
803}
804
805fn parse_i32_value(v: &Value) -> Option<i32> {
806 v.unwrap_named()
807 .as_i64()
808 .and_then(|n| i32::try_from(n).ok())
809}
810
811fn parse_bool_value(v: &Value) -> Option<bool> {
812 parse_i32_value(v).map(|n| n != 0)
813}
814
815fn parse_list_i32_value(v: &Value) -> Vec<i32> {
816 match v.unwrap_named() {
817 Value::List(xs) => xs
818 .iter()
819 .filter_map(|x| x.as_i64().and_then(|n| i32::try_from(n).ok()))
820 .collect(),
821 _ => Vec::new(),
822 }
823}
824
825fn dispatch_global_fog_command(
826 ctx: &mut CommandContext,
827 form_id: u32,
828 args: &[Value],
829) -> Result<bool> {
830 let op = form_id as i32;
831 let ret_form = crate::runtime::forms::prop_access::current_vm_meta(ctx)
832 .1
833 .unwrap_or(0);
834
835 if op == constants::elm_value::GLOBAL___FOG_NAME {
836 match ret_form {
837 rf if rf == constants::fm::STR as i64 => {
838 ctx.push(Value::Str(ctx.globals.fog_global.name.clone()));
839 }
840 _ => {
841 let name = args
842 .first()
843 .and_then(|v| v.unwrap_named().as_str())
844 .unwrap_or("");
845 ctx.globals.fog_global = Default::default();
846 if !name.is_empty() {
847 match ctx.images.load_g00(name, 0) {
848 Ok(id) => {
849 ctx.globals.fog_global.enabled = true;
850 ctx.globals.fog_global.name = name.to_string();
851 ctx.globals.fog_global.texture_image_id = Some(id);
852 }
853 Err(e) => {
854 log::error!(
855 "GLOBAL.__FOG_NAME failed to load fog texture '{name}': {e}"
856 );
857 }
858 }
859 }
860 }
861 }
862 return Ok(true);
863 }
864
865 if op == constants::elm_value::GLOBAL___FOG_X {
866 if ret_form != 0 {
867 ctx.push(Value::Int(ctx.globals.fog_global.x_event.get_value() as i64));
868 } else {
869 let x = args
870 .first()
871 .and_then(|v| v.unwrap_named().as_i64())
872 .unwrap_or(0) as i32;
873 ctx.globals.fog_global.set_x(x);
874 }
875 return Ok(true);
876 }
877
878 if op == constants::elm_value::GLOBAL___FOG_NEAR {
879 if ret_form != 0 {
880 ctx.push(Value::Int(ctx.globals.fog_global.near as i64));
881 } else {
882 ctx.globals.fog_global.near = args
883 .first()
884 .and_then(|v| v.unwrap_named().as_i64())
885 .unwrap_or(0) as f32;
886 }
887 return Ok(true);
888 }
889
890 if op == constants::elm_value::GLOBAL___FOG_FAR {
891 if ret_form != 0 {
892 ctx.push(Value::Int(ctx.globals.fog_global.far as i64));
893 } else {
894 ctx.globals.fog_global.far = args
895 .first()
896 .and_then(|v| v.unwrap_named().as_i64())
897 .unwrap_or(0) as f32;
898 }
899 return Ok(true);
900 }
901
902 if op != constants::elm_value::GLOBAL___FOG_X_EVE {
903 return Ok(false);
904 }
905
906 let Some((chain_pos, chain)) =
907 crate::runtime::forms::prop_access::parse_element_chain_ctx(ctx, form_id, args)
908 .map(|(i, ch)| (i, ch.to_vec()))
909 else {
910 return Ok(true);
911 };
912 if chain.len() < 2 {
913 return Ok(true);
914 }
915 let params = &args[..chain_pos];
916 match chain[1] {
917 int_event_op::SET | int_event_op::SET_REAL => {
918 let value = params.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
919 let total_time = params.get(1).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
920 let delay_time = params.get(2).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
921 let speed_type = params.get(3).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
922 let real_flag = if chain[1] == int_event_op::SET_REAL {
923 1
924 } else {
925 0
926 };
927 ctx.globals
928 .fog_global
929 .x_event
930 .set_event(value, total_time, delay_time, speed_type, real_flag);
931 }
932 int_event_op::LOOP | int_event_op::LOOP_REAL => {
933 let start_value = params.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
934 let end_value = params.get(1).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
935 let loop_time = params.get(2).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
936 let delay_time = params.get(3).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
937 let speed_type = params.get(4).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
938 let real_flag = if chain[1] == int_event_op::LOOP_REAL {
939 1
940 } else {
941 0
942 };
943 ctx.globals.fog_global.x_event.loop_event(
944 start_value,
945 end_value,
946 loop_time,
947 delay_time,
948 speed_type,
949 real_flag,
950 );
951 }
952 int_event_op::TURN | int_event_op::TURN_REAL => {
953 let start_value = params.first().and_then(|v| v.as_i64()).unwrap_or(0) as i32;
954 let end_value = params.get(1).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
955 let loop_time = params.get(2).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
956 let delay_time = params.get(3).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
957 let speed_type = params.get(4).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
958 let real_flag = if chain[1] == int_event_op::TURN_REAL {
959 1
960 } else {
961 0
962 };
963 ctx.globals.fog_global.x_event.turn_event(
964 start_value,
965 end_value,
966 loop_time,
967 delay_time,
968 speed_type,
969 real_flag,
970 );
971 }
972 int_event_op::END => ctx.globals.fog_global.x_event.end_event(),
973 int_event_op::WAIT => ctx.wait.wait_fog_x_event(false, false),
974 int_event_op::WAIT_KEY => ctx.wait.wait_fog_x_event(true, true),
975 int_event_op::CHECK => {
976 ctx.push(Value::Int(
977 if ctx.globals.fog_global.x_event.check_event() {
978 1
979 } else {
980 0
981 },
982 ));
983 }
984 _ => {}
985 }
986 Ok(true)
987}
988
989fn resolve_wipe_mask_path(project_dir: &Path, raw: &str) -> Option<PathBuf> {
990 if raw.is_empty() {
991 return None;
992 }
993 let norm = raw.replace('\\', "/");
994 let p = Path::new(&norm);
995 if p.is_absolute() && p.is_file() {
996 return Some(p.to_path_buf());
997 }
998 let mut candidates = Vec::new();
999 candidates.push(project_dir.join(&norm));
1000 candidates.push(project_dir.join("dat").join(&norm));
1001 if p.extension().is_none() {
1002 for ext in ["png", "bmp", "jpg"] {
1003 candidates.push(project_dir.join(format!("{}.{}", norm, ext)));
1004 candidates.push(project_dir.join("dat").join(format!("{}.{}", norm, ext)));
1005 }
1006 }
1007 candidates.into_iter().find(|c| c.is_file())
1008}
1009
1010fn dispatch_global_wipe_command(
1011 ctx: &mut CommandContext,
1012 form_id: u32,
1013 args: &[Value],
1014) -> Result<bool> {
1015 let op = form_id as i32;
1016 let is_mask = matches!(
1017 op,
1018 constants::elm_value::GLOBAL_MASK_WIPE | constants::elm_value::GLOBAL_MASK_WIPE_ALL
1019 );
1020 let is_all = matches!(
1021 op,
1022 constants::elm_value::GLOBAL_WIPE_ALL | constants::elm_value::GLOBAL_MASK_WIPE_ALL
1023 );
1024
1025 if op == constants::elm_value::GLOBAL_WIPE_END {
1026 ctx.globals.finish_wipe();
1027 return Ok(true);
1028 }
1029 if op == constants::elm_value::GLOBAL_WAIT_WIPE {
1030 let key_wait_mode = args
1031 .iter()
1032 .find_map(|v| match v {
1033 Value::NamedArg { id, value } if *id == 0 => parse_i32_value(value),
1034 _ => None,
1035 })
1036 .unwrap_or(-1);
1037 let key_skip = match key_wait_mode {
1038 0 => false,
1039 1 => true,
1040 _ => {
1041 ctx.globals
1042 .syscom
1043 .config_int
1044 .get(&197)
1045 .copied()
1046 .unwrap_or(0)
1047 != 0
1048 }
1049 };
1050 ctx.wait.wait_wipe(key_skip);
1051 return Ok(true);
1052 }
1053 if op == constants::elm_value::GLOBAL_CHECK_WIPE {
1054 ctx.push(Value::Int(if ctx.globals.wipe_done() { 0 } else { 1 }));
1055 return Ok(true);
1056 }
1057
1058 if !matches!(
1059 op,
1060 constants::elm_value::GLOBAL_WIPE
1061 | constants::elm_value::GLOBAL_WIPE_ALL
1062 | constants::elm_value::GLOBAL_MASK_WIPE
1063 | constants::elm_value::GLOBAL_MASK_WIPE_ALL
1064 ) {
1065 return Ok(false);
1066 }
1067
1068 let mut positional: Vec<&Value> = Vec::new();
1069 let mut named: Vec<(i32, &Value)> = Vec::new();
1070 for a in args {
1071 match a {
1072 Value::NamedArg { id, value } => named.push((*id, value.as_ref())),
1073 _ => positional.push(a),
1074 }
1075 }
1076
1077 let mut mask_file: Option<String> = None;
1078 let mut wipe_type: i32 = 0;
1079 let mut wipe_time: i32 = 500;
1080 let mut speed_mode: i32 = 0;
1081 let mut start_time: i32 = 0;
1082 let mut option: Vec<i32> = Vec::new();
1083 let mut begin_order: i32 = 0;
1084 let mut end_order: i32 = if is_all { i32::MAX } else { 0 };
1085 let mut begin_layer: i32 = i32::MIN;
1086 let mut end_layer: i32 = i32::MAX;
1087 let mut wait_flag = true;
1088 let mut key_wait_mode: i32 = -1;
1089 let mut with_low_order: i32 = 0;
1090
1091 if is_mask {
1092 mask_file = positional
1093 .get(0)
1094 .and_then(|v| v.unwrap_named().as_str())
1095 .map(str::to_string);
1096 if let Some(v) = positional.get(1).and_then(|v| parse_i32_value(v)) {
1097 wipe_type = v;
1098 }
1099 if let Some(v) = positional.get(2).and_then(|v| parse_i32_value(v)) {
1100 wipe_time = v;
1101 }
1102 if let Some(v) = positional.get(3).and_then(|v| parse_i32_value(v)) {
1103 speed_mode = v;
1104 }
1105 if let Some(v) = positional.get(4) {
1106 option = parse_list_i32_value(v);
1107 }
1108 } else {
1109 if let Some(v) = positional.get(0).and_then(|v| parse_i32_value(v)) {
1110 wipe_type = v;
1111 }
1112 if let Some(v) = positional.get(1).and_then(|v| parse_i32_value(v)) {
1113 wipe_time = v;
1114 }
1115 if let Some(v) = positional.get(2).and_then(|v| parse_i32_value(v)) {
1116 speed_mode = v;
1117 }
1118 if let Some(v) = positional.get(3) {
1119 option = parse_list_i32_value(v);
1120 }
1121 }
1122
1123 for (id, v) in named {
1124 match id {
1125 0 => {
1126 if let Some(x) = parse_i32_value(v) {
1127 wipe_type = x;
1128 }
1129 }
1130 1 => {
1131 if let Some(x) = parse_i32_value(v) {
1132 wipe_time = x;
1133 }
1134 }
1135 2 => {
1136 if let Some(x) = parse_i32_value(v) {
1137 speed_mode = x;
1138 }
1139 }
1140 3 => option = parse_list_i32_value(v),
1141 4 => {
1142 if let Some(x) = parse_i32_value(v) {
1143 begin_order = x;
1144 }
1145 }
1146 5 => {
1147 if let Some(x) = parse_i32_value(v) {
1148 end_order = x;
1149 }
1150 }
1151 6 => {
1152 if let Some(x) = parse_i32_value(v) {
1153 begin_layer = x;
1154 }
1155 }
1156 7 => {
1157 if let Some(x) = parse_i32_value(v) {
1158 end_layer = x;
1159 }
1160 }
1161 8 => {
1162 if let Some(x) = parse_bool_value(v) {
1163 wait_flag = x;
1164 }
1165 }
1166 9 => {
1167 if let Some(x) = parse_i32_value(v) {
1168 key_wait_mode = x;
1169 }
1170 }
1171 10 => {
1172 if let Some(x) = parse_i32_value(v) {
1173 with_low_order = x;
1174 }
1175 }
1176 11 => {
1177 if let Some(x) = parse_i32_value(v) {
1178 start_time = x;
1179 }
1180 }
1181 _ => {}
1182 }
1183 }
1184 if is_all {
1185 end_order = i32::MAX;
1186 }
1187
1188 let mask_image_id = mask_file.as_ref().and_then(|f| {
1189 resolve_wipe_mask_path(&ctx.project_dir, f).and_then(|p| ctx.images.load_file(&p, 0).ok())
1190 });
1191
1192 stage::apply_stage_wipe(ctx, begin_order, end_order, begin_layer, end_layer);
1193 ctx.globals.start_wipe(WipeState::new(
1194 mask_file,
1195 mask_image_id,
1196 wipe_type,
1197 wipe_time,
1198 start_time,
1199 speed_mode,
1200 option,
1201 begin_order,
1202 end_order,
1203 begin_layer,
1204 end_layer,
1205 wait_flag,
1206 key_wait_mode,
1207 with_low_order,
1208 ));
1209
1210 if wait_flag {
1211 let key_skip = match key_wait_mode {
1212 0 => false,
1213 1 => true,
1214 _ => {
1215 ctx.globals
1216 .syscom
1217 .config_int
1218 .get(&197)
1219 .copied()
1220 .unwrap_or(0)
1221 != 0
1222 }
1223 };
1224 ctx.wait.wait_wipe(key_skip);
1225 }
1226 Ok(true)
1227}
1228
1229fn dispatch_capture_command(
1230 ctx: &mut CommandContext,
1231 form_id: u32,
1232 args: &[Value],
1233) -> Result<bool> {
1234 match form_id as i32 {
1235 constants::elm_value::GLOBAL_CAPTURE => {
1236 let img = ctx.capture_frame_rgba();
1237 ctx.globals.capture_image = Some(img.clone());
1238 crate::runtime::forms::syscom::prepare_runtime_save_thumb_capture(ctx);
1239 ctx.push(Value::Int(0));
1240 Ok(true)
1241 }
1242 constants::elm_value::GLOBAL_CAPTURE_FREE => {
1243 ctx.globals.capture_image = None;
1244 ctx.globals.save_thumb_capture_image = None;
1245 ctx.push(Value::Int(0));
1246 Ok(true)
1247 }
1248 constants::elm_value::GLOBAL_CAPTURE_FROM_FILE => {
1249 let Some(file) = args.get(0).and_then(|v| v.as_str()) else {
1250 panic!("GLOBAL.CAPTURE_FROM_FILE requires file name");
1251 };
1252 let Some(path) =
1253 stage::resolve_capture_file_path(&ctx.project_dir, &ctx.globals.append_dir, file)
1254 else {
1255 panic!("GLOBAL.CAPTURE_FROM_FILE cannot resolve file: {file}");
1256 };
1257 let img_id = ctx.images.load_file(&path, 0).unwrap_or_else(|e| {
1258 panic!(
1259 "GLOBAL.CAPTURE_FROM_FILE failed to load {}: {e}",
1260 path.display()
1261 )
1262 });
1263 let img = ctx
1264 .images
1265 .get(img_id)
1266 .map(|img| img.as_ref().clone())
1267 .unwrap_or_else(|| {
1268 panic!(
1269 "GLOBAL.CAPTURE_FROM_FILE image disappeared: {}",
1270 path.display()
1271 )
1272 });
1273 crate::runtime::forms::syscom::prepare_runtime_save_thumb_capture_from_image(ctx, &img);
1274 ctx.globals.capture_image = Some(img);
1275 ctx.push(Value::Int(0));
1276 Ok(true)
1277 }
1278 constants::elm_value::GLOBAL_CAPTURE_FOR_OBJECT => {
1279 let has_range = named_i64(args, 0).is_some() || named_i64(args, 1).is_some();
1280 let img = if has_range {
1281 let end_order = named_i64(args, 0).unwrap_or(i32::MAX as i64 / 1024);
1282 let end_layer = named_i64(args, 1).unwrap_or(1023);
1283 ctx.capture_frame_rgba_until(end_order, end_layer)
1284 } else {
1285 ctx.capture_frame_rgba()
1286 };
1287 ctx.globals.capture_for_object_image = Some(img);
1288 ctx.push(Value::Int(0));
1289 Ok(true)
1290 }
1291 constants::elm_value::GLOBAL_CAPTURE_FOR_OBJECT_FREE => {
1292 ctx.globals.capture_for_object_image = None;
1293 ctx.push(Value::Int(0));
1294 Ok(true)
1295 }
1296 constants::elm_value::GLOBAL_CAPTURE_FOR_LOCAL_SAVE => {
1297 let end_order = named_i64(args, 0).unwrap_or(i32::MAX as i64 / 1024);
1298 let end_layer = named_i64(args, 1).unwrap_or(1023);
1299 let width = named_i64(args, 3).unwrap_or(ctx.screen_w as i64).max(1) as u32;
1300 let height = named_i64(args, 4).unwrap_or(ctx.screen_h as i64).max(1) as u32;
1301 let img = ctx.capture_frame_rgba_until(end_order, end_layer);
1302 let resized = crate::runtime::forms::syscom::resize_capture_rgba_nearest(
1303 &img,
1304 width,
1305 height,
1306 );
1307 ctx.globals.capture_for_object_image = Some(resized);
1308 ctx.push(Value::Int(1));
1309 Ok(true)
1310 }
1311 constants::elm_value::GLOBAL_CAPTURE_FOR_TWEET => {
1312 let img = ctx.capture_frame_rgba();
1313 ctx.globals.capture_image = Some(img);
1314 ctx.push(Value::Int(0));
1315 Ok(true)
1316 }
1317 constants::elm_value::GLOBAL_CAPTURE_FREE_FOR_TWEET => {
1318 ctx.globals.capture_image = None;
1319 ctx.push(Value::Int(0));
1320 Ok(true)
1321 }
1322 _ => Ok(false),
1323 }
1324}
1325
1326fn push_global_message_ok(ctx: &mut CommandContext) {
1327 let ret_form = crate::runtime::forms::prop_access::current_vm_meta(ctx)
1328 .1
1329 .unwrap_or(0);
1330 if ret_form == 0 {
1331 return;
1332 }
1333 if ret_form == constants::fm::STR as i64 {
1334 ctx.push(Value::Str(String::new()));
1335 } else {
1336 ctx.push(Value::Int(0));
1337 }
1338}
1339
1340fn global_message_arg_str(args: &[Value]) -> Option<&str> {
1341 args.iter().rev().find_map(|v| v.unwrap_named().as_str())
1342}
1343
1344fn dispatch_global_message_command(
1345 ctx: &mut CommandContext,
1346 form_id: u32,
1347 args: &[Value],
1348) -> Result<bool> {
1349 match form_id as i32 {
1350 constants::elm_value::GLOBAL_MESSAGE_BOX => {
1351 let text = global_message_arg_str(args).unwrap_or("").to_string();
1352 ctx.request_system_messagebox_no_return(
1353 17,
1354 false,
1355 text,
1356 vec![crate::runtime::globals::SystemMessageBoxButton {
1357 label: "OK".to_string(),
1358 value: 0,
1359 }],
1360 );
1361 Ok(true)
1362 }
1363 constants::elm_value::GLOBAL_GET_LAST_SEL_MSG => {
1364 ctx.push(Value::Str(ctx.globals.syscom.system_extra_str_value.clone()));
1365 Ok(true)
1366 }
1367 constants::elm_value::GLOBAL_OPEN
1368 | constants::elm_value::GLOBAL_OPEN_WAIT
1369 | constants::elm_value::GLOBAL_OPEN_NOWAIT => {
1370 ctx.ui.show_message_bg(true);
1371 push_global_message_ok(ctx);
1372 Ok(true)
1373 }
1374 constants::elm_value::GLOBAL_CLOSE
1375 | constants::elm_value::GLOBAL_CLOSE_WAIT
1376 | constants::elm_value::GLOBAL_CLOSE_NOWAIT => {
1377 ctx.ui.show_message_bg(false);
1378 push_global_message_ok(ctx);
1379 Ok(true)
1380 }
1381 constants::elm_value::GLOBAL_END_CLOSE => {
1382 push_global_message_ok(ctx);
1386 Ok(true)
1387 }
1388 constants::elm_value::GLOBAL_MSG_BLOCK | constants::elm_value::GLOBAL_MSG_PP_BLOCK => {
1389 push_global_message_ok(ctx);
1393 Ok(true)
1394 }
1395 constants::elm_value::GLOBAL_CLEAR => {
1396 ctx.ui.clear_message();
1397 ctx.ui.clear_name();
1398 push_global_message_ok(ctx);
1399 Ok(true)
1400 }
1401 constants::elm_value::GLOBAL_CLEAR_MSGBK => {
1402 ctx.ui.clear_message();
1403 let form_id = ctx.ids.form_global_msgbk;
1404 if form_id != 0 {
1405 ctx.globals.msgbk_forms.entry(form_id).or_default().clear();
1406 }
1407 push_global_message_ok(ctx);
1408 Ok(true)
1409 }
1410 constants::elm_value::GLOBAL_PRINT => {
1411 ctx.request_read_flag_no();
1412 if let Some(s) = global_message_arg_str(args) {
1413 if !s.is_empty() {
1414 syscom::append_current_save_message(ctx, s);
1415 ctx.ui.show_message_bg(true);
1416 ctx.ui.append_message(s);
1417 }
1418 }
1419 push_global_message_ok(ctx);
1420 Ok(true)
1421 }
1422 constants::elm_value::GLOBAL_NL | constants::elm_value::GLOBAL_NLI => {
1423 ctx.ui.append_linebreak();
1424 push_global_message_ok(ctx);
1425 Ok(true)
1426 }
1427 constants::elm_value::GLOBAL_WAIT_MSG | constants::elm_value::GLOBAL_PP => {
1428 ctx.ui.begin_wait_message();
1429 ctx.wait.wait_key();
1430 ctx.request_message_wait_proc_boundary();
1431 push_global_message_ok(ctx);
1432 Ok(true)
1433 }
1434 constants::elm_value::GLOBAL_R | constants::elm_value::GLOBAL_PAGE => {
1435 if (form_id as i32) == constants::elm_value::GLOBAL_PAGE {
1436 ctx.ui.begin_wait_page_message();
1437 } else {
1438 ctx.ui.begin_wait_message();
1439 }
1440 ctx.ui.request_clear_message_on_wait_end();
1441 ctx.wait.wait_key();
1442 ctx.request_message_wait_proc_boundary();
1443 push_global_message_ok(ctx);
1444 Ok(true)
1445 }
1446 constants::elm_value::GLOBAL_SET_NAMAE => {
1447 let name = global_message_arg_str(args).unwrap_or("");
1448 if !stage::cd_name_current_mwnd(ctx, name) {
1449 ctx.ui.set_name(name.to_string());
1450 }
1451 push_global_message_ok(ctx);
1452 Ok(true)
1453 }
1454 constants::elm_value::GLOBAL_CLEAR_FACE
1455 | constants::elm_value::GLOBAL_SET_FACE
1456 | constants::elm_value::GLOBAL_SIZE
1457 | constants::elm_value::GLOBAL_COLOR
1458 | constants::elm_value::GLOBAL_RUBY
1459 | constants::elm_value::GLOBAL_MSGBTN
1460 | constants::elm_value::GLOBAL_MULTI_MSG
1461 | constants::elm_value::GLOBAL_NEXT_MSG
1462 | constants::elm_value::GLOBAL_START_SLIDE_MSG
1463 | constants::elm_value::GLOBAL_END_SLIDE_MSG
1464 | constants::elm_value::GLOBAL_INDENT
1465 | constants::elm_value::GLOBAL_CLEAR_INDENT
1466 | constants::elm_value::GLOBAL_REP_POS
1467 | constants::elm_value::GLOBAL_SET_WAKU => {
1468 push_global_message_ok(ctx);
1469 Ok(true)
1470 }
1471 _ => Ok(false),
1472 }
1473}
1474
1475pub fn dispatch_global_form(
1476 ctx: &mut CommandContext,
1477 form_id: u32,
1478 args: &[Value],
1479) -> Result<bool> {
1480 let form_id = canonical_global_form_id(ctx, form_id);
1481
1482 if dispatch_global_wipe_command(ctx, form_id, args)? {
1483 return Ok(true);
1484 }
1485 if dispatch_capture_command(ctx, form_id, args)? {
1486 return Ok(true);
1487 }
1488 if dispatch_global_fog_command(ctx, form_id, args)? {
1489 return Ok(true);
1490 }
1491 if dispatch_selbtn_command(ctx, form_id, args)? {
1492 return Ok(true);
1493 }
1494 if stage::dispatch_current_mwnd_global_op(ctx, form_id as i32, args) {
1495 return Ok(true);
1496 }
1497 if dispatch_global_koe_command(ctx, form_id, args)? {
1498 return Ok(true);
1499 }
1500 if dispatch_global_message_command(ctx, form_id, args)? {
1501 return Ok(true);
1502 }
1503
1504 if form_id == 24 {
1507 return keylist::dispatch(ctx, args);
1508 }
1509 if form_id == 40 {
1510 return counter::dispatch(ctx, form_id, args);
1511 }
1512 if form_id == 63 {
1513 if syscom::dispatch(ctx, form_id, args)? {
1514 return Ok(true);
1515 }
1516 }
1517 if form_id == 64 {
1518 if script::dispatch(ctx, form_id, args)? {
1519 return Ok(true);
1520 }
1521 }
1522 if form_id == 46 {
1523 return mouse::dispatch(ctx, args);
1524 }
1525 if form_id == 86 {
1526 if input::dispatch(ctx, form_id, args)? {
1527 return Ok(true);
1528 }
1529 }
1530 if form_id == 92 {
1531 if system::dispatch(ctx, form_id, args)? {
1532 return Ok(true);
1533 }
1534 }
1535 if form_id == constants::elm_value::GLOBAL_DISP as u32 {
1536 ctx.wait.wait_next_frame(ctx.globals.render_frame);
1537 ctx.request_disp_proc_boundary();
1538 return Ok(true);
1539 }
1540 if form_id == constants::elm_value::GLOBAL_FRAME as u32 {
1541 ctx.wait.wait_next_frame(ctx.globals.render_frame);
1542 ctx.request_proc_boundary(crate::runtime::ProcKind::Frame);
1543 return Ok(true);
1544 }
1545 if form_id == constants::elm_value::GLOBAL_SET_MWND as u32
1546 || form_id == constants::elm_value::GLOBAL_SET_SEL_MWND as u32
1547 {
1548 let next = args.iter().find_map(mwnd_ref_from_value);
1549 if form_id == constants::elm_value::GLOBAL_SET_SEL_MWND as u32 {
1550 if let Some((stage, no)) = next {
1551 ctx.globals.current_sel_mwnd_stage_idx = stage;
1552 ctx.globals.current_sel_mwnd_no = Some(no);
1553 }
1554 } else if let Some((stage, no)) = next {
1555 ctx.globals.current_mwnd_stage_idx = stage;
1556 ctx.globals.current_mwnd_no = Some(no);
1557 ctx.globals.last_mwnd_stage_idx = stage;
1558 ctx.globals.last_mwnd_no = Some(no);
1559 }
1560 return Ok(true);
1561 }
1562 if form_id == constants::elm_value::GLOBAL_GET_MWND as u32
1563 || form_id == constants::elm_value::GLOBAL_GET_SEL_MWND as u32
1564 {
1565 let no = if form_id == constants::elm_value::GLOBAL_GET_SEL_MWND as u32 {
1566 ctx.globals.current_sel_mwnd_no
1567 } else {
1568 ctx.globals.current_mwnd_no
1569 };
1570 ctx.push(Value::Int(no.map(|n| n as i64).unwrap_or(-1)));
1572 return Ok(true);
1573 }
1574 if form_id == constants::elm_value::GLOBAL_SET_TITLE as u32 {
1575 return Ok(true);
1579 }
1580
1581 if form_id == constants::global_form::STAGE_ALT {
1582 return stage::dispatch(ctx, args);
1583 }
1584 if form_id == constants::global_form::BGM {
1585 return forms::bgm::dispatch(ctx, args);
1586 }
1587 if form_id == constants::global_form::BGMTABLE {
1588 return forms::bgm_table::dispatch(ctx, args);
1589 }
1590 if form_id == constants::global_form::MOV {
1591 return forms::mov::dispatch(ctx, args);
1592 }
1593 if form_id == constants::global_form::PCM {
1594 return forms::pcm::dispatch(ctx, args);
1595 }
1596 if form_id == constants::global_form::PCMCH {
1597 return forms::pcmch::dispatch(ctx, form_id, args);
1598 }
1599 if form_id == constants::global_form::SE {
1600 return forms::se::dispatch(ctx, args);
1601 }
1602 if form_id == constants::global_form::PCMEVENT {
1603 return forms::pcmevent::dispatch(ctx, args);
1604 }
1605 if form_id == constants::global_form::EXCALL {
1606 return forms::excall::dispatch(ctx, args);
1607 }
1608 if form_id == constants::global_form::KOE_ST {
1609 return forms::koe_st::dispatch(ctx, args);
1610 }
1611 if form_id == ctx.ids.form_global_input {
1612 return input::dispatch(ctx, form_id, args);
1613 }
1614 if form_id == ctx.ids.form_global_mouse {
1615 return mouse::dispatch(ctx, args);
1616 }
1617 if form_id == ctx.ids.form_global_keylist {
1618 return keylist::dispatch(ctx, args);
1619 }
1620 if form_id == constants::global_form::KEY {
1621 return key::dispatch(ctx, args);
1622 }
1623 if form_id == constants::global_form::SCREEN {
1624 return forms::screen::dispatch(ctx, args);
1625 }
1626 if form_id == constants::global_form::MSGBK {
1627 return forms::msgbk::dispatch(ctx, args);
1628 }
1629 if ctx.ids.form_global_math != 0 && form_id == ctx.ids.form_global_math {
1630 return math::dispatch(ctx, form_id, args);
1631 }
1632 if ctx.ids.form_global_cgtable != 0 && form_id == ctx.ids.form_global_cgtable {
1633 return cgtable::dispatch(ctx, form_id, args);
1634 }
1635 if ctx.ids.form_global_database != 0 && form_id == ctx.ids.form_global_database {
1636 return database::dispatch(ctx, form_id, args);
1637 }
1638 if ctx.ids.form_global_g00buf != 0 && form_id == ctx.ids.form_global_g00buf {
1639 return g00buf::dispatch(ctx, form_id, args);
1640 }
1641 if ctx.ids.form_global_mask != 0 && form_id == ctx.ids.form_global_mask {
1642 return mask::dispatch(ctx, form_id, args);
1643 }
1644 if ctx.ids.form_global_editbox != 0 && form_id == ctx.ids.form_global_editbox {
1645 return editbox::dispatch(ctx, form_id, args);
1646 }
1647 if ctx.ids.form_global_file != 0 && form_id == ctx.ids.form_global_file {
1648 return file::dispatch(ctx, form_id, args);
1649 }
1650 if ctx.ids.form_global_steam != 0 && form_id == ctx.ids.form_global_steam {
1651 return steam::dispatch(ctx, form_id, args);
1652 }
1653 if ctx.ids.form_global_syscom != 0 && form_id == ctx.ids.form_global_syscom {
1654 return syscom::dispatch(ctx, form_id, args);
1655 }
1656 if ctx.ids.form_global_script != 0 && form_id == ctx.ids.form_global_script {
1657 return script::dispatch(ctx, form_id, args);
1658 }
1659 if ctx.ids.form_global_system != 0 && form_id == ctx.ids.form_global_system {
1660 return system::dispatch(ctx, form_id, args);
1661 }
1662 if form_id == constants::global_form::FRAME_ACTION {
1663 return frame_action::dispatch(ctx, form_id, args);
1664 }
1665 if ctx.ids.form_global_frame_action_ch != 0 && form_id == ctx.ids.form_global_frame_action_ch {
1666 return frame_action_ch::dispatch(ctx, form_id, args);
1667 }
1668
1669 match form_id {
1670 constants::global_form::BGM => forms::bgm::dispatch(ctx, args),
1671 constants::global_form::BGMTABLE => forms::bgm_table::dispatch(ctx, args),
1672 constants::global_form::MOV => forms::mov::dispatch(ctx, args),
1673 constants::global_form::PCM => forms::pcm::dispatch(ctx, args),
1674 constants::global_form::PCMCH => forms::pcmch::dispatch(ctx, form_id, args),
1675 constants::global_form::SE => forms::se::dispatch(ctx, args),
1676 constants::global_form::PCMEVENT => forms::pcmevent::dispatch(ctx, args),
1677 constants::global_form::EXCALL => forms::excall::dispatch(ctx, args),
1678 constants::global_form::KOE_ST => forms::koe_st::dispatch(ctx, args),
1679 constants::global_form::SCREEN => forms::screen::dispatch(ctx, args),
1680 constants::global_form::MSGBK => forms::msgbk::dispatch(ctx, args),
1681 constants::global_form::KEY => key::dispatch(ctx, args),
1682 _ => {
1683 if form_id == constants::global_form::TIMEWAIT {
1685 return timewait::dispatch(ctx, false, args);
1686 }
1687 if form_id == constants::global_form::TIMEWAIT_KEY {
1688 return timewait::dispatch(ctx, true, args);
1689 }
1690
1691 if form_id as i32 == constants::fm::INTEVENT
1692 || form_id as i32 == constants::fm::INTEVENTLIST
1693 {
1694 return int_event::dispatch(ctx, form_id, args);
1695 }
1696
1697 if form_id as i32 == constants::fm::OBJECTEVENT {
1698 return object_event::dispatch(ctx, args);
1699 }
1700 if form_id as i32 == crate::runtime::forms::codes::FM_OBJECTEVENTLIST {
1701 return object_event::dispatch_list(ctx, args);
1702 }
1703
1704 if constants::global_form::INT_LIST_FORMS.contains(&form_id) {
1705 return int_list::dispatch(ctx, form_id, args);
1706 }
1707 if constants::global_form::STR_LIST_FORMS.contains(&form_id) {
1708 return str_list::dispatch(ctx, form_id, args);
1709 }
1710
1711 if form_id == constants::global_form::COUNTER {
1712 return counter::dispatch(ctx, form_id, args);
1713 }
1714
1715 if form_id == constants::global_form::FRAME_ACTION {
1716 return int_list::dispatch(ctx, form_id, args);
1717 }
1718
1719 Ok(false)
1720 }
1721 }
1722}