1use std::collections::BTreeMap;
16
17use audio_code::prelude::*;
18
19struct Lobby {
20 name: String,
21 address: String,
22}
23
24impl Lobby {
25 fn new(ctx: &mut Context) -> Self {
26 Self {
27 name: ctx.storage.get_or("name", "Player".to_string()),
28 address: ctx.storage.get_or("address", "localhost".to_string()),
29 }
30 }
31
32 fn menu(&self) -> Menu {
33 Menu::new("lobby", "Online demo")
34 .text_field("name", "Your name", self.name.clone())
35 .button("host", "Host a game")
36 .text_field("address", "Server address", self.address.clone())
37 .button("join", "Join")
38 .button("quit", "Quit")
39 .closable(false)
40 }
41
42 fn start_game(ctx: &mut Context) {
43 ctx.ui.close();
44 let scene = Field::new(ctx);
45 ctx.switch_scene(scene);
46 }
47}
48
49impl Scene for Lobby {
50 fn start(&mut self, ctx: &mut Context) {
51 ctx.ui.open(self.menu());
52 }
53
54 fn on_ui(&mut self, event: &UiEvent, ctx: &mut Context) {
55 match event {
56 UiEvent::Changed { item, value, .. } if item == "name" => {
57 self.name = value.as_str().to_string();
58 ctx.storage.set("name", &self.name);
59 }
60 UiEvent::Changed { item, value, .. } if item == "address" => {
61 self.address = value.as_str().to_string();
62 ctx.storage.set("address", &self.address);
63 }
64 UiEvent::Activated { item, .. } => match item.as_str() {
65 "host" => {
66 ctx.net.set_name(&self.name);
67 match ctx.net.host(net::DEFAULT_PORT) {
68 Ok(()) => {
69 let address = Net::local_ip().map_or("unknown".to_string(), |ip| ip.to_string());
70 ctx.speak(format!("Hosting. Your address is {address}"));
71 Self::start_game(ctx);
72 }
73 Err(e) => ctx.ui.message("error", e.to_string()),
74 }
75 }
76 "join" => {
77 ctx.net.set_name(&self.name);
78 ctx.net.join(&self.address);
79 ctx.speak(format!("Connecting to {}", self.address));
80 }
81 "quit" => ctx.quit(),
82 _ => {}
83 },
84 _ => {}
85 }
86 }
87
88 fn on_net(&mut self, event: &NetEvent, ctx: &mut Context) {
89 match event {
90 NetEvent::Connected(_) => Self::start_game(ctx),
91 NetEvent::Disconnected(_, reason) => ctx.ui.message("error", ctx.lang.describe_reason(reason)),
92 NetEvent::Message(..) => {}
93 }
94 }
95
96 fn update(&mut self, _ctx: &mut Context) {}
97}
98
99struct Other {
101 name: String,
102 position: Vec3,
103 sound: Sound,
104}
105
106struct Field {
107 position: Vec3,
108 others: BTreeMap<PeerId, Other>,
109}
110
111impl Field {
112 fn new(ctx: &mut Context) -> Self {
113 ctx.audio.register("other", Synth::triangle(660.0).length(0.06).decay(40.0).then(Synth::silence(0.5)));
114 ctx.audio.register("step", Synth::noise().length(0.05).decay(80.0).volume(0.5));
115 ctx.audio.register("chat", Synth::sine(1200.0).length(0.05).volume(0.3));
116 Self { position: Vec3::ZERO, others: BTreeMap::new() }
117 }
118
119 fn add(&mut self, ctx: &mut Context, id: PeerId, name: &str, position: Vec3) {
120 let sound = ctx.audio.play_looped_at("other", position);
121 sound.set_pitch(1.0 + id.number() as f32 * 0.15);
122 self.others.insert(id, Other { name: name.to_string(), position, sound });
123 }
124
125 fn remove(&mut self, ctx: &mut Context, id: PeerId) {
126 if let Some(other) = self.others.remove(&id) {
127 other.sound.stop();
128 ctx.speak(format!("{} left", other.name));
129 }
130 }
131
132 fn moved(&mut self, id: PeerId, position: Vec3) {
133 if let Some(other) = self.others.get_mut(&id) {
134 other.position = position;
135 other.sound.set_position(position);
136 }
137 }
138
139 fn name(&self, ctx: &Context, id: PeerId) -> String {
140 if id == ctx.net.id() {
141 return ctx.net.name(id).to_string();
142 }
143 self.others.get(&id).map_or("Someone".to_string(), |o| o.name.clone())
144 }
145
146 fn host_events(&mut self, event: &NetEvent, ctx: &mut Context) {
148 match event {
149 NetEvent::Connected(id) => {
150 let name = ctx.net.name(*id).to_string();
151 ctx.speak(format!("{name} joined"));
152 ctx.net.send(*id, Packet::new("player").with(PeerId::SERVER).with(ctx.net.name(PeerId::SERVER)).with(self.position));
154 for (other, o) in &self.others {
155 ctx.net.send(*id, Packet::new("player").with(*other).with(&o.name).with(o.position));
156 }
157 ctx.net.send_all_except(*id, Packet::new("player").with(*id).with(&name).with(Vec3::ZERO));
158 self.add(ctx, *id, &name, Vec3::ZERO);
159 }
160 NetEvent::Message(from, packet) => match packet.kind() {
161 "move" => {
162 self.moved(*from, packet.vec3(0));
163 ctx.net.send_all_except(*from, Packet::new("pos").with(*from).with(packet.vec3(0)));
164 }
165 "chat" => {
166 let text = packet.text(0);
167 ctx.play("chat");
168 ctx.speak(format!("{}: {text}", self.name(ctx, *from)));
169 ctx.net.send_all_except(*from, Packet::new("chat").with(*from).with(text));
170 }
171 _ => {}
172 },
173 NetEvent::Disconnected(id, _) => {
174 self.remove(ctx, *id);
175 ctx.net.send_all(Packet::new("gone").with(*id));
176 }
177 }
178 }
179
180 fn client_events(&mut self, event: &NetEvent, ctx: &mut Context) {
182 match event {
183 NetEvent::Message(_, packet) => match packet.kind() {
184 "player" => self.add(ctx, packet.peer(0), packet.text(1), packet.vec3(2)),
185 "pos" => self.moved(packet.peer(0), packet.vec3(1)),
186 "gone" => self.remove(ctx, packet.peer(0)),
187 "chat" => {
188 ctx.play("chat");
189 ctx.speak(format!("{}: {}", self.name(ctx, packet.peer(0)), packet.text(1)));
190 }
191 _ => {}
192 },
193 NetEvent::Disconnected(_, reason) => {
194 ctx.speak(ctx.lang.describe_reason(reason));
195 ctx.audio.stop_all();
196 let scene = Lobby::new(ctx);
197 ctx.switch_scene(scene);
198 }
199 NetEvent::Connected(_) => {}
200 }
201 }
202}
203
204impl Scene for Field {
205 fn start(&mut self, ctx: &mut Context) {
206 ctx.speech.speak_queued("Arrows walk, Enter chats, Space finds the others, Escape leaves.");
207 }
208
209 fn on_net(&mut self, event: &NetEvent, ctx: &mut Context) {
210 if ctx.net.is_host() {
211 self.host_events(event, ctx);
212 } else {
213 self.client_events(event, ctx);
214 }
215 }
216
217 fn on_ui(&mut self, event: &UiEvent, ctx: &mut Context) {
218 if let UiEvent::TextSubmitted { id, text } = event
219 && id == "chat"
220 {
221 if ctx.net.is_host() {
223 ctx.net.send_all(Packet::new("chat").with(PeerId::SERVER).with(text));
224 } else {
225 ctx.net.send_all(Packet::new("chat").with(text));
226 }
227 ctx.play("chat");
228 }
229 }
230
231 fn update(&mut self, ctx: &mut Context) {
232 let step = vec3(
233 ctx.input.repeating(Key::Right, 0.25, 0.25) as i32 as f32 - ctx.input.repeating(Key::Left, 0.25, 0.25) as i32 as f32,
234 ctx.input.repeating(Key::Up, 0.25, 0.25) as i32 as f32 - ctx.input.repeating(Key::Down, 0.25, 0.25) as i32 as f32,
235 0.0,
236 );
237 if step != Vec3::ZERO {
238 self.position = (self.position + step).clamp(Vec3::splat(-20.0), Vec3::splat(20.0));
239 ctx.play("step");
240 if ctx.net.is_host() {
241 ctx.net.send_all(Packet::new("pos").with(PeerId::SERVER).with(self.position));
242 } else {
243 ctx.net.send_all(Packet::new("move").with(self.position));
244 }
245 }
246 ctx.audio.set_listener(self.position, 0.0);
247
248 if ctx.pressed(Key::Enter) {
249 ctx.ui.input("chat", "Message");
250 }
251 if ctx.pressed(Key::Space) {
252 let text = if self.others.is_empty() {
253 "Nobody else is here".to_string()
254 } else {
255 let places: Vec<String> = self
256 .others
257 .values()
258 .map(|o| format!("{}: {}", o.name, ctx.lang.describe_position(self.position, 0.0, o.position)))
259 .collect();
260 places.join(". ")
261 };
262 ctx.speak(text);
263 }
264 if ctx.pressed(Key::P) {
265 if ctx.net.is_host() {
266 ctx.speak(format!("You are the host, {} players connected", ctx.net.player_count()));
267 } else {
268 let ms = (ctx.net.ping(PeerId::SERVER) * 1000.0).round();
269 ctx.speak(format!("Ping {ms} milliseconds"));
270 }
271 }
272 if ctx.pressed(Key::Escape) {
273 ctx.net.close();
274 ctx.audio.stop_all();
275 ctx.speak("You left");
276 let scene = Lobby::new(ctx);
277 ctx.switch_scene(scene);
278 }
279 }
280}
281
282fn main() -> audio_code::Result<()> {
283 Engine::new("Online demo").run_with(Lobby::new)
284}