1use glam::Vec3;
4
5use crate::audio::Sound;
6use crate::engine::Context;
7use crate::input::Key;
8use crate::physics::{BodyId, PhysicsEvent};
9
10#[derive(Debug)]
12pub(crate) struct Attached {
13 body: BodyId,
14 sound: Sound,
15 last: Vec3,
16}
17
18impl Context {
19 pub fn speak(&mut self, text: impl AsRef<str>) {
21 self.speech.speak(text);
22 }
23
24 pub fn play(&mut self, name: &str) -> Sound {
26 self.audio.play(name)
27 }
28
29 pub fn play_at(&mut self, name: &str, position: Vec3) -> Sound {
31 self.audio.play_at(name, position)
32 }
33
34 pub fn pressed(&self, key: Key) -> bool {
36 self.input.pressed(key)
37 }
38
39 pub fn held(&self, key: Key) -> bool {
41 self.input.held(key)
42 }
43
44 pub fn once(&mut self, name: &str) -> bool {
56 self.once.insert(name.to_string())
57 }
58
59 pub fn follow(&mut self, body: BodyId) {
63 self.follow = Some(body);
64 self.sync_world();
65 }
66
67 pub fn unfollow(&mut self) {
69 self.follow = None;
70 }
71
72 pub fn followed(&self) -> Option<BodyId> {
74 self.follow
75 }
76
77 pub fn play_on(&mut self, body: BodyId, name: &str) -> Sound {
79 let sound = self.audio.play_at(name, self.physics.position(body));
80 self.attach(body, sound.clone());
81 sound
82 }
83
84 pub fn loop_on(&mut self, body: BodyId, name: &str) -> Sound {
95 let sound = self.audio.play_looped_at(name, self.physics.position(body));
96 self.attach(body, sound.clone());
97 sound
98 }
99
100 pub fn attach(&mut self, body: BodyId, sound: Sound) {
103 let position = self.physics.position(body);
104 sound.set_position(position);
105 self.attached.push(Attached { body, sound, last: position });
106 }
107
108 pub fn stop_sounds_on(&mut self, body: BodyId) {
110 self.attached.retain(|a| {
111 if a.body == body {
112 a.sound.stop();
113 false
114 } else {
115 true
116 }
117 });
118 }
119
120 pub fn drive(&mut self, vehicle: BodyId) {
135 let input = &self.input;
136 let throttle = (input.held(Key::Up) || input.held(Key::W)) as u8 as f32;
137 let brake = (input.held(Key::Down) || input.held(Key::S)) as u8 as f32;
138 let right = (input.held(Key::Right) || input.held(Key::D)) as i32;
139 let left = (input.held(Key::Left) || input.held(Key::A)) as i32;
140 let handbrake = input.held(Key::Space);
141 if let Some(v) = self.physics.vehicle_mut(vehicle) {
142 v.throttle = throttle;
143 v.brake = brake;
144 v.steer = (right - left) as f32;
145 v.handbrake = handbrake;
146 }
147 }
148
149 pub fn horn(&mut self, vehicle: BodyId) -> Sound {
152 match self.physics.vehicle(vehicle).and_then(|v| v.horn_sound.clone()) {
153 Some(name) => self.play_on(vehicle, &name),
154 None => Sound::none(),
155 }
156 }
157
158 pub fn start_engine(&mut self, vehicle: BodyId) -> bool {
161 let Some(v) = self.physics.vehicle_mut(vehicle) else {
162 return false;
163 };
164 if v.is_engine_on() || !v.start_engine() {
165 return false;
166 }
167 if let Some(name) = v.start_sound.clone() {
168 self.play_on(vehicle, &name);
169 }
170 true
171 }
172
173 pub fn stop_engine(&mut self, vehicle: BodyId) {
175 if let Some(v) = self.physics.vehicle_mut(vehicle) {
176 v.stop_engine();
177 }
178 }
179
180 pub(crate) fn sync_vehicles(&mut self) {
183 let (physics, audio) = (&self.physics, &mut self.audio);
184 self.engine_sounds.retain(|(id, sound)| {
186 let running = physics.vehicle(*id).is_some_and(|v| v.is_engine_on() && v.engine_sound.is_some());
187 if !running || !sound.is_playing() {
188 sound.fade_out(0.3);
189 }
190 running && sound.is_playing()
191 });
192 for (id, body) in physics.bodies() {
193 let Some(v) = &body.vehicle else {
194 continue;
195 };
196 let Some(name) = &v.engine_sound else {
197 continue;
198 };
199 if !v.is_engine_on() {
200 continue;
201 }
202 let (pitch, volume) = (v.engine_pitch(), 0.6 + 0.4 * v.throttle.clamp(0.0, 1.0));
203 match self.engine_sounds.iter().find(|(e, _)| *e == id) {
204 Some((_, sound)) => {
205 sound.set_position(body.position);
206 sound.set_pitch(pitch);
207 sound.set_volume(volume);
208 }
209 None => {
210 let sound = audio.sound(name).looped().at(body.position).pitch(pitch).volume(volume).play();
211 self.engine_sounds.push((id, sound));
212 }
213 }
214 }
215 for event in physics.events() {
216 let (vehicle, name, volume) = match *event {
217 PhysicsEvent::Crashed { vehicle, speed, .. } => {
218 (vehicle, physics.vehicle(vehicle).and_then(|v| v.crash_sound.as_deref()), (speed / 15.0).min(1.0))
219 }
220 PhysicsEvent::GearChanged { vehicle, gear } if gear != 0 => {
221 (vehicle, physics.vehicle(vehicle).and_then(|v| v.shift_sound.as_deref()), 1.0)
222 }
223 _ => continue,
224 };
225 if let Some(name) = name {
226 audio.sound(name).at(physics.position(vehicle)).volume(volume).play();
227 }
228 }
229 }
230
231 pub(crate) fn sync_world(&mut self) {
233 if let Some(id) = self.follow {
234 match self.physics.get(id) {
235 Some(body) => {
236 let (position, facing, pitch) = (body.position, body.facing, body.pitch);
237 if position != self.audio.listener_position()
238 || facing != self.audio.listener_yaw()
239 || pitch != self.audio.listener_pitch()
240 {
241 self.audio.set_listener_3d(position, facing, pitch);
242 }
243 }
244 None => self.follow = None,
245 }
246 }
247 let physics = &self.physics;
248 self.attached.retain_mut(|a| {
249 if !a.sound.is_playing() {
250 return false;
251 }
252 match physics.get(a.body) {
253 Some(body) => {
254 if body.position != a.last {
255 a.sound.set_position(body.position);
256 a.last = body.position;
257 }
258 true
259 }
260 None => {
261 a.sound.stop();
262 false
263 }
264 }
265 });
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use crate::prelude::*;
272
273 #[test]
274 fn follow_and_attach() {
275 let mut runner = Engine::new("t").headless().build_fn(|_| {});
276 let ctx = &mut runner.ctx;
277 let player = ctx.physics.add(Body::kinematic(vec3(1.0, 2.0, 0.0)).with_facing(90.0));
278 ctx.follow(player);
279 assert_eq!(ctx.audio.listener_position(), vec3(1.0, 2.0, 0.0));
280 assert_eq!(ctx.audio.listener_yaw(), 90.0);
281 ctx.physics.move_forward(player, 3.0);
282 ctx.sync_world();
283 assert!((ctx.audio.listener_position().x - 4.0).abs() < 1e-4);
284
285 assert!(ctx.once("a"));
286 assert!(!ctx.once("a"));
287 }
288}