1use ponko::*;
16use serde::{Deserialize, Serialize};
17
18#[derive(Serialize, Deserialize, Default)]
21struct Pad {
22 dir: Vec2,
23 shove: bool,
24}
25
26fn read_keys(ctx: &mut Ctx) {
28 let mut pad = Pad::default();
29 for (key, d) in [
30 (Key::Left, -Vec2::X),
31 (Key::Right, Vec2::X),
32 (Key::Up, -Vec2::Y),
33 (Key::Down, Vec2::Y),
34 ] {
35 if ctx.key_down(key) {
36 pad.dir += d;
37 }
38 }
39 pad.shove = ctx.key_down(Key::Space);
40 ctx.net.set_input(&pad);
41}
42#[derive(Serialize, Deserialize, Clone, Copy)]
46struct Crab {
47 player: u32,
48 push: Vec2,
49}
50
51fn move_crabs(ctx: &mut Ctx) {
55 let pads: Vec<Pad> = (0..ctx.net.players())
56 .map(|p| ctx.net.input::<Pad>(p).unwrap_or_default())
57 .collect();
58 let dt = ctx.dt();
59 let crabs: Vec<(Entity, Vec2, u32)> = ctx
60 .world
61 .query::<(Entity, &Transform2D, &Crab)>()
62 .iter()
63 .map(|(e, t, c)| (e, t.position, c.player))
64 .collect();
65 for &(e, at, player) in &crabs {
66 let pad = &pads[player as usize];
67 if pad.shove {
68 for &(other, other_at, _) in &crabs {
69 if other != e && at.distance(other_at) < 120.0 {
70 ctx.world.get_mut::<Crab>(other).unwrap().push += (other_at - at).normalize_or_zero() * 900.0;
71 }
72 }
73 }
74 let crab = *ctx.world.get::<Crab>(e).unwrap();
75 let t = ctx.world.get_mut::<Transform2D>(e).unwrap();
76 t.position += (pad.dir.normalize_or_zero() * 250.0 + crab.push) * dt;
77 t.position = t.position.clamp(vec2(40.0, 40.0), vec2(760.0, 560.0));
78 ctx.world.get_mut::<Crab>(e).unwrap().push = crab.push * 0.9;
79 }
80}
81fn draw(ctx: &mut Ctx) {
84 ctx.clear(Color::hex(0x20304a));
85 let crabs: Vec<(Vec2, u32)> = ctx
86 .world
87 .query::<(&Transform2D, &Crab)>()
88 .iter()
89 .map(|(t, c)| (t.position, c.player))
90 .collect();
91 for (at, player) in crabs {
92 let color = [Color::hex(0xf56565), Color::hex(0x63b3ed)][player as usize % 2];
93 ctx.draw_circle(at, 30.0, color);
94 }
95 let net = &ctx.net;
96 let status = match net.state() {
97 NetState::Connecting => "waiting for the other player...".to_string(),
98 NetState::Running => format!(
99 "you are player {} ({}), tick {}{}",
100 net.player() + 1,
101 ["red", "blue"][net.player() % 2],
102 ctx.sim_tick(),
103 net.desync().map_or(String::new(), |d| format!(" DESYNC: {d}"))
104 ),
105 NetState::Lost => format!("connection lost: {}", net.error().unwrap_or("?")),
106 NetState::Offline => "offline".into(),
107 _ => String::new(),
108 };
109 ctx.draw_text(&status, vec2(20.0, 20.0), 20.0, Color::WHITE);
110 #[cfg(feature = "steam")]
111 if ctx.net.state() == NetState::Connecting
112 && let Some(lobby) = ctx.net.steam_lobby()
113 {
114 let text = format!("Steam lobby {}: press I to invite a friend", lobby.raw());
115 ctx.draw_text(&text, vec2(20.0, 50.0), 20.0, Color::WHITE);
116 if ctx.key_pressed(Key::I) {
117 ctx.net.steam_invite();
118 }
119 }
120 if ctx.key_pressed(Key::Escape) {
121 ctx.quit();
122 }
123}
124
125fn main() {
126 let args: Vec<String> = std::env::args().skip(1).collect();
127 #[cfg(feature = "steam")]
128 if let Some(net) = steam_session(&args) {
129 return play(net);
130 }
131 let net = match args.first().map(String::as_str) {
133 Some("host") => Net::host(format!("0.0.0.0:{}", args.get(1).map_or("7777", |p| p)), 2),
134 Some("join") => Net::join(args.get(1).map_or("127.0.0.1:7777", |a| a)),
135 _ => {
136 eprintln!("usage: lockstep host [port] | join <host:port> | steam | steam-join");
137 return;
138 }
139 }
140 .expect("could not open a socket");
141 play(net);
142}
143
144fn play(net: Net) {
145 Game::new("Lockstep", 800, 600)
146 .lockstep(net)
147 .system(read_keys) .sim_system(move_crabs) .system(draw)
150 .run(|ctx| {
151 ctx.world.reflect::<Crab>(); for player in 0..2 {
153 ctx.world.spawn((
154 Transform2D::at(vec2(250.0 + 300.0 * player as f32, 300.0)),
155 Crab {
156 player,
157 push: Vec2::ZERO,
158 },
159 ));
160 }
161 });
162 }
164
165#[cfg(feature = "steam")]
169fn steam_session(args: &[String]) -> Option<Net> {
170 let mode = args.first().map(String::as_str);
171 let invited = steam_lobby_from_args(); if !matches!(mode, Some("steam" | "steam-join")) && invited.is_none() {
173 return None;
174 }
175 let client = steamworks::Client::init_app(480).expect("Steam must be running and logged in");
177 if let Some(lobby) = invited {
178 return Some(Net::steam_join(&client, lobby));
179 }
180 if mode == Some("steam") {
181 return Some(Net::steam_host(&client, 2));
182 }
183 println!("waiting for you to accept a Steam invite...");
184 let lobby = steam_wait_for_invite(&client, std::time::Duration::from_secs(300))?;
185 Some(Net::steam_join(&client, lobby))
186}
187