flatland_client_lib/
audio.rs1use crate::game::GameState;
7use crate::social::AudioCue;
8
9pub fn ui_overlay_signature(state: &GameState) -> u64 {
11 let mut s = 0u64;
12 macro_rules! bit {
13 ($n:expr, $cond:expr) => {
14 if $cond {
15 s |= 1u64 << $n;
16 }
17 };
18 }
19 bit!(0, state.show_stats);
20 bit!(1, state.show_inventory_menu);
21 bit!(2, state.show_craft_menu);
22 bit!(3, state.show_plot_build_menu);
23 bit!(4, state.show_shop_menu);
24 bit!(5, state.bank_panel.is_some());
25 bit!(6, state.storage_panel.is_some());
26 bit!(7, state.market_panel.is_some());
27 bit!(8, state.show_npc_verb_menu);
28 bit!(9, state.show_npc_chat);
29 bit!(10, state.player_verbs.open);
30 bit!(11, state.trade_ui.panel.is_some());
31 bit!(12, state.whisper_pouch_ui.open);
32 bit!(13, state.social_chat.input_focused);
33 bit!(14, state.social_chat.picking_stone);
34 bit!(15, state.show_move_picker);
35 bit!(16, state.show_grant_picker);
36 bit!(17, state.show_destroy_picker);
37 bit!(18, state.show_rename_prompt);
38 bit!(19, state.show_worker_rename);
39 bit!(20, state.show_loadout_menu);
40 bit!(21, state.show_keychain_menu);
41 bit!(22, state.show_rotation_editor);
42 bit!(23, state.show_quest_offer);
43 bit!(24, state.show_quest_menu);
44 bit!(25, state.show_workers_menu);
45 bit!(26, state.show_worker_give_picker);
46 bit!(27, state.show_worker_give_target_picker);
47 bit!(28, state.show_worker_take_picker);
48 bit!(29, state.show_worker_teach_picker);
49 bit!(30, state.worker_route_editor.is_some());
50 bit!(31, state.show_plant_menu);
51 bit!(32, state.show_farm_access);
52 bit!(33, state.show_equip_menu);
53 bit!(34, state.claim_mode.is_some());
54 bit!(35, state.relocate_mode.is_some());
55 bit!(36, state.show_deconstruct_picker);
56 s
57}
58
59const UI_BIT_SHOP: u64 = 1 << 4;
60const UI_BIT_STORAGE: u64 = 1 << 6;
61const UI_BIT_NPC: u64 = (1 << 8) | (1 << 9);
62
63fn cue_for_ui_open(opened: u64) -> Option<AudioCue> {
64 if opened == 0 {
65 return None;
66 }
67 if opened & UI_BIT_SHOP != 0 {
68 return Some(AudioCue::ShopOpen);
69 }
70 if opened & UI_BIT_STORAGE != 0 {
71 return Some(AudioCue::ChestOpen);
72 }
73 if opened & UI_BIT_NPC != 0 {
74 return Some(AudioCue::NpcGreet);
75 }
76 Some(AudioCue::UiOpen)
77}
78
79pub fn sync_ui_audio(state: &mut GameState) {
81 let sig = ui_overlay_signature(state);
82 let audio = &mut state.social_chat;
83 if !audio.audio_ui_bootstrapped {
84 audio.audio_ui_sig = sig;
85 audio.audio_ui_bootstrapped = true;
86 return;
87 }
88 let prev = audio.audio_ui_sig;
89 if sig == prev {
90 return;
91 }
92 let opened = sig & !prev;
93 let closed = prev & !sig;
94 if let Some(cue) = cue_for_ui_open(opened) {
95 audio.push_cue(cue);
96 }
97 if closed != 0 {
98 audio.push_cue(AudioCue::UiClose);
99 }
100 audio.audio_ui_sig = sig;
101}
102
103pub fn sync_world_audio(state: &mut GameState) {
105 let inside = state.effective_inside_building();
106 let audio = &mut state.social_chat;
107
108 if !audio.audio_world_bootstrapped {
109 audio.audio_inside_building = inside.clone();
110 for door in &state.doors {
111 audio.audio_door_open.insert(door.id.clone(), door.open);
112 audio.audio_door_locked.insert(door.id.clone(), door.locked);
113 }
114 for chest in &state.placed_containers {
115 audio
116 .audio_chest_locked
117 .insert(chest.id.clone(), chest.locked);
118 }
119 audio.audio_world_bootstrapped = true;
120 return;
121 }
122
123 match (&audio.audio_inside_building, &inside) {
124 (None, Some(_)) => audio.push_cue(AudioCue::BuildingEnter),
125 (Some(_), None) => audio.push_cue(AudioCue::BuildingExit),
126 _ => {}
127 }
128 audio.audio_inside_building = inside;
129
130 for door in &state.doors {
131 let id = &door.id;
132 if let Some(was_open) = audio.audio_door_open.get(id).copied() {
133 if !was_open && door.open {
134 audio.push_cue(AudioCue::DoorOpen);
135 } else if was_open && !door.open {
136 audio.push_cue(AudioCue::DoorClose);
137 }
138 }
139 if let Some(was_locked) = audio.audio_door_locked.get(id).copied() {
140 if !was_locked && door.locked {
141 audio.push_cue(AudioCue::DoorLock);
142 } else if was_locked && !door.locked {
143 audio.push_cue(AudioCue::DoorUnlock);
144 }
145 }
146 audio.audio_door_open.insert(id.clone(), door.open);
147 audio.audio_door_locked.insert(id.clone(), door.locked);
148 }
149
150 audio
152 .audio_door_open
153 .retain(|id, _| state.doors.iter().any(|d| &d.id == id));
154 audio
155 .audio_door_locked
156 .retain(|id, _| state.doors.iter().any(|d| &d.id == id));
157
158 for chest in &state.placed_containers {
159 let id = &chest.id;
160 if let Some(was_locked) = audio.audio_chest_locked.get(id).copied() {
161 if !was_locked && chest.locked {
162 audio.push_cue(AudioCue::ChestLock);
163 } else if was_locked && !chest.locked {
164 audio.push_cue(AudioCue::ChestUnlock);
165 }
166 }
167 audio.audio_chest_locked.insert(id.clone(), chest.locked);
168 }
169 audio
170 .audio_chest_locked
171 .retain(|id, _| state.placed_containers.iter().any(|c| &c.id == id));
172}
173
174pub fn sync_frame_audio(state: &mut GameState) {
176 sync_ui_audio(state);
177 sync_world_audio(state);
178}