Skip to main content

siglus_scene_vm/runtime/forms/
pcmevent.rs

1//! Global PCMEVENT form.
2//!
3//! Public C++ source routes `GLOBAL.PCMEVENT` through a list form and then an
4//! indexed `PCMEVENT[i]` item with explicit commands:
5//!   START_ONESHOT / START_LOOP / START_RANDOM / STOP / CHECK / WAIT / WAIT_KEY
6//!
7//! This Rust handler mirrors that structure directly instead of treating PCMEVENT
8//! as a generic global op bucket.
9
10use anyhow::Result;
11
12use crate::runtime::globals::{PcmEventLine, PcmEventState};
13use crate::runtime::wait::AudioWait;
14use crate::runtime::{CommandContext, Value};
15
16enum PcmEventOp {
17    StartOneShot,
18    StartLoop,
19    StartRandom,
20    Stop,
21    Check,
22    Wait,
23    WaitKey,
24    Unknown,
25}
26
27fn resolve_pcm_event_op(op: i32) -> PcmEventOp {
28    match op {
29        crate::runtime::constants::PCMEVENT_START_ONESHOT => PcmEventOp::StartOneShot,
30        crate::runtime::constants::PCMEVENT_START_LOOP => PcmEventOp::StartLoop,
31        crate::runtime::constants::PCMEVENT_START_RANDOM => PcmEventOp::StartRandom,
32        crate::runtime::constants::PCMEVENT_STOP => PcmEventOp::Stop,
33        crate::runtime::constants::PCMEVENT_CHECK => PcmEventOp::Check,
34        crate::runtime::constants::PCMEVENT_WAIT_KEY => PcmEventOp::WaitKey,
35        crate::runtime::constants::PCMEVENT_WAIT => PcmEventOp::Wait,
36        _ => PcmEventOp::Unknown,
37    }
38}
39
40fn named_int(args: &[Value], id: i32) -> Option<i64> {
41    args.iter().find_map(|v| match v {
42        Value::NamedArg { id: nid, value } if *nid == id => value.as_i64(),
43        _ => None,
44    })
45}
46
47fn collect_lines(args: &[Value], random: bool) -> Vec<PcmEventLine> {
48    let mut out = Vec::new();
49    for v in args {
50        match v {
51            Value::Str(s) => out.push(PcmEventLine {
52                file_name: s.clone(),
53                probability: if random { 1 } else { 0 },
54                min_time: 0,
55                max_time: 0,
56            }),
57            Value::List(items) if !items.is_empty() => {
58                let file_name = items
59                    .first()
60                    .and_then(Value::as_str)
61                    .unwrap_or("")
62                    .to_string();
63                if file_name.is_empty() {
64                    continue;
65                }
66                let mut line = PcmEventLine {
67                    file_name,
68                    probability: if random { 1 } else { 0 },
69                    min_time: 0,
70                    max_time: 0,
71                };
72                if random {
73                    if let Some(v) = items.get(1).and_then(Value::as_i64) {
74                        line.probability = v as i32;
75                    }
76                    if let Some(v) = items.get(2).and_then(Value::as_i64) {
77                        line.min_time = v as i32;
78                        line.max_time = v as i32;
79                    }
80                    if let Some(v) = items.get(3).and_then(Value::as_i64) {
81                        line.max_time = v as i32;
82                    }
83                } else {
84                    if let Some(v) = items.get(1).and_then(Value::as_i64) {
85                        line.min_time = v as i32;
86                        line.max_time = v as i32;
87                    }
88                    if let Some(v) = items.get(2).and_then(Value::as_i64) {
89                        line.max_time = v as i32;
90                    }
91                }
92                out.push(line);
93            }
94            _ => {}
95        }
96    }
97    out
98}
99
100pub fn dispatch(ctx: &mut CommandContext, args: &[Value]) -> Result<bool> {
101    let form_global_pcm_event = ctx.ids.form_global_pcm_event;
102    let elm_array = ctx.ids.elm_array;
103    let Some((chain_pos, chain)) = crate::runtime::forms::prop_access::parse_element_chain_ctx(
104        ctx,
105        form_global_pcm_event,
106        args,
107    ) else {
108        return Ok(false);
109    };
110    let chain = chain.to_vec();
111    if chain.len() < 3 || chain[1] != elm_array {
112        return Ok(false);
113    }
114    let idx = chain[2].max(0) as usize;
115
116    {
117        let list = ctx
118            .globals
119            .pcm_event_lists
120            .entry(form_global_pcm_event)
121            .or_insert_with(Vec::new);
122        if list.len() <= idx {
123            list.resize(idx + 1, PcmEventState::default());
124        }
125    }
126
127    // Public C++ list/item structure: bare [ARRAY, idx] returns the element itself.
128    if chain.len() == 3 {
129        return Ok(true);
130    }
131
132    let op = resolve_pcm_event_op(chain[3]);
133    let script_args = if chain_pos == args.len() {
134        crate::runtime::forms::prop_access::script_args(args, chain_pos)
135    } else {
136        &args[..chain_pos]
137    };
138    match op {
139        PcmEventOp::StartOneShot | PcmEventOp::StartLoop | PcmEventOp::StartRandom => {
140            let random = matches!(op, PcmEventOp::StartRandom);
141            let looped = matches!(op, PcmEventOp::StartLoop);
142            let lines = collect_lines(script_args, random);
143            let active = if let Some(line) = lines.first() {
144                let (pcm, audio) = (&mut ctx.pcm, &mut ctx.audio);
145                pcm.play_in_slot(audio, idx, &line.file_name, looped)
146                    .is_ok()
147            } else {
148                false
149            };
150            if let Some(st) = ctx
151                .globals
152                .pcm_event_lists
153                .get_mut(&form_global_pcm_event)
154                .and_then(|v| v.get_mut(idx))
155            {
156                st.reinit();
157                st.random = random;
158                st.looped = looped;
159                st.volume_type = named_int(script_args, 3).unwrap_or(0) as i32;
160                st.bgm_fade_target_flag = named_int(script_args, 4).unwrap_or(0) != 0;
161                st.bgm_fade2_target_flag = named_int(script_args, 5).unwrap_or(0) != 0;
162                st.chara_no = named_int(script_args, 6).unwrap_or(-1) as i32;
163                st.time_type = named_int(script_args, 11).unwrap_or(0) != 0;
164                st.bgm_fade2_source_flag = named_int(script_args, 12).unwrap_or(0) != 0;
165                st.real_flag = true;
166                st.lines = lines;
167                st.active = active;
168            }
169            ctx.push(Value::Int(0));
170            Ok(true)
171        }
172        PcmEventOp::Stop => {
173            let fade = script_args.first().and_then(Value::as_i64).unwrap_or(0);
174            let _ = ctx.pcm.stop_slot(idx, Some(fade));
175            if let Some(st) = ctx
176                .globals
177                .pcm_event_lists
178                .get_mut(&form_global_pcm_event)
179                .and_then(|v| v.get_mut(idx))
180            {
181                st.active = false;
182            }
183            ctx.push(Value::Int(0));
184            Ok(true)
185        }
186        PcmEventOp::Check => {
187            let playing = ctx.pcm.is_playing_slot(idx);
188            if let Some(st) = ctx
189                .globals
190                .pcm_event_lists
191                .get_mut(&form_global_pcm_event)
192                .and_then(|v| v.get_mut(idx))
193            {
194                st.active = playing;
195            }
196            ctx.push(Value::Int(if playing { 1 } else { 0 }));
197            Ok(true)
198        }
199        PcmEventOp::Wait => {
200            ctx.wait.wait_audio(AudioWait::PcmSlot(idx as u8), false);
201            Ok(true)
202        }
203        PcmEventOp::WaitKey => {
204            ctx.wait.wait_audio(AudioWait::PcmSlot(idx as u8), true);
205            Ok(true)
206        }
207        PcmEventOp::Unknown => Ok(false),
208    }
209}