aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--server/src/game.rs26
-rw-r--r--server/src/main.rs4
-rw-r--r--server/src/protocol.rs1
-rw-r--r--server/src/state.rs25
-rw-r--r--test-client/main.ts4
-rw-r--r--test-client/protocol.ts2
-rw-r--r--test-client/visual.ts5
7 files changed, 48 insertions, 19 deletions
diff --git a/server/src/game.rs b/server/src/game.rs
index 96cfa93a..599aadbd 100644
--- a/server/src/game.rs
+++ b/server/src/game.rs
@@ -28,7 +28,7 @@ use std::{
collections::{HashMap, VecDeque},
ops::Deref,
sync::Arc,
- time::Instant,
+ time::{Duration, Instant},
};
#[derive(Debug, PartialEq)]
@@ -65,7 +65,8 @@ pub struct Game {
players: HashMap<PlayerID, Player>,
packet_out: VecDeque<PacketC>,
demand: Option<DemandState>,
- points: i64,
+ pub points: i64,
+ end: Option<Instant>,
}
impl Game {
@@ -76,6 +77,7 @@ impl Game {
players: Default::default(),
tiles: Default::default(),
demand: None,
+ end: None,
points: 0,
}
}
@@ -95,7 +97,7 @@ impl Game {
}
self.demand = None;
}
- pub fn load(&mut self, gamedata: Gamedata) {
+ pub fn load(&mut self, gamedata: Gamedata, timer: Option<Duration>) {
let players = self
.players
.iter()
@@ -107,6 +109,7 @@ impl Game {
self.data = gamedata.into();
self.points = 0;
+ self.end = timer.map(|dur| Instant::now() + dur);
for (&p, (tile, item)) in &self.data.initial_map {
self.tiles.insert(
@@ -201,6 +204,7 @@ impl Game {
pub fn score(&self) -> PacketC {
PacketC::Score {
+ time_remaining: self.end.map(|t| (t - Instant::now()).as_secs_f32()),
points: self.points,
demands_failed: self.demand.as_ref().map(|d| d.failed).unwrap_or_default(),
demands_completed: self
@@ -375,6 +379,9 @@ impl Game {
}
self.packet_out.push_back({
PacketC::Score {
+ time_remaining: self
+ .end
+ .map(|t| (t - Instant::now()).as_secs_f32()),
points: self.points,
demands_failed: self
.demand
@@ -423,7 +430,8 @@ impl Game {
Ok(())
}
- pub fn tick(&mut self, dt: f32) {
+ /// Returns true if the game should end
+ pub fn tick(&mut self, dt: f32) -> bool {
if let Some(demand) = &mut self.demand {
let mut packet_out = Vec::new();
if let Err(err) = demand.tick(
@@ -436,12 +444,8 @@ impl Game {
warn!("demand tick {err}");
}
if demand.score_changed {
- self.packet_out.push_back(PacketC::Score {
- points: self.points,
- demands_failed: demand.failed,
- demands_completed: demand.completed,
- });
- demand.score_changed = false
+ demand.score_changed = false;
+ self.packet_out.push_back(self.score());
}
for (player, packet) in packet_out {
if let Err(err) = self.packet_in(player, packet) {
@@ -473,6 +477,8 @@ impl Game {
}
}
}
+
+ return self.end.map(|t| t < Instant::now()).unwrap_or_default();
}
}
diff --git a/server/src/main.rs b/server/src/main.rs
index 68927eaa..a144c704 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -90,7 +90,9 @@ async fn run() -> anyhow::Result<()> {
let mut tick = interval(Duration::from_secs_f32(dt));
loop {
tick.tick().await;
- state.write().await.tick(dt).await;
+ if let Err(e) = state.write().await.tick(dt).await {
+ warn!("tick failed: {e}");
+ }
}
});
}
diff --git a/server/src/protocol.rs b/server/src/protocol.rs
index 39bc0887..176eb31b 100644
--- a/server/src/protocol.rs
+++ b/server/src/protocol.rs
@@ -141,6 +141,7 @@ pub enum PacketC {
points: i64,
demands_failed: usize,
demands_completed: usize,
+ time_remaining: Option<f32>,
},
SetIngame {
state: bool,
diff --git a/server/src/state.rs b/server/src/state.rs
index 41ffce20..ffcc8fa2 100644
--- a/server/src/state.rs
+++ b/server/src/state.rs
@@ -1,3 +1,5 @@
+use std::time::Duration;
+
use crate::{
data::DataIndex,
game::Game,
@@ -20,6 +22,8 @@ enum Command {
Start {
#[arg(default_value = "small-default-default")]
spec: String,
+ #[arg(default_value = "300")]
+ timer: u64,
},
Effect {
name: String,
@@ -33,17 +37,26 @@ impl State {
index.reload()?;
let mut game = Game::new();
- game.load(index.generate("lobby-none-none".to_string())?);
+ game.load(index.generate("lobby-none-none".to_string())?, None);
Ok(Self { game, index, tx })
}
- pub async fn tick(&mut self, dt: f32) {
- self.game.tick(dt);
+ pub async fn tick(&mut self, dt: f32) -> anyhow::Result<()> {
+ if self.game.tick(dt) {
+ self.tx
+ .send(PacketC::ServerMessage {
+ text: format!("Game finished. You reached {} points.", self.game.points),
+ })
+ .ok();
+ self.game
+ .load(self.index.generate("lobby-none-none".to_string())?, None);
+ }
while let Some(p) = self.game.packet_out() {
debug!("-> {p:?}");
let _ = self.tx.send(p);
}
+ Ok(())
}
pub async fn packet_in(&mut self, player: PlayerID, packet: PacketS) -> Result<Vec<PacketC>> {
match &packet {
@@ -81,13 +94,13 @@ impl State {
async fn handle_command(&mut self, player: PlayerID, command: Command) -> Result<()> {
match command {
- Command::Start { spec } => {
+ Command::Start { spec, timer } => {
let data = self.index.generate(spec)?;
- self.game.load(data);
+ self.game.load(data, Some(Duration::from_secs(timer)));
}
Command::End => {
self.game
- .load(self.index.generate("lobby-none-none".to_string())?);
+ .load(self.index.generate("lobby-none-none".to_string())?, None);
}
Command::Effect { name } => {
self.tx
diff --git a/test-client/main.ts b/test-client/main.ts
index 38b9e730..aeca8084 100644
--- a/test-client/main.ts
+++ b/test-client/main.ts
@@ -99,6 +99,7 @@ export const items_removed = new Set<ItemData>()
export let data: Gamedata = { item_names: [], tile_names: [], spawn: [0, 0], tile_collide: [], tile_interact: [] }
+export let time_remaining: number | null = null
export let global_message: MessageData | undefined = undefined
let global_message_clear: number | undefined = undefined
export let my_id: PlayerID = -1
@@ -204,6 +205,7 @@ function packet(p: PacketC) {
demands_completed = p.demands_completed
demands_failed = p.demands_failed
points = p.points
+ time_remaining = p.time_remaining ?? null
break;
case "error":
if (global_message_clear) clearTimeout(global_message_clear)
@@ -288,6 +290,8 @@ function frame_update(dt: number) {
const p = players.get(my_id)
if (!p) return
+ if (time_remaining != null) time_remaining -= dt
+
const input = normalize({
x: (+keys_down.has(KEY_RIGHT) - +keys_down.has(KEY_LEFT)),
y: (+keys_down.has(KEY_DOWN) - +keys_down.has(KEY_UP))
diff --git a/test-client/protocol.ts b/test-client/protocol.ts
index 81a0d1a4..1e76700a 100644
--- a/test-client/protocol.ts
+++ b/test-client/protocol.ts
@@ -49,7 +49,7 @@ export type PacketC =
| { type: "update_map", tile: Vec2, kind: TileIndex | null, neighbors: [TileIndex | null] } // A map tile was changed
| { type: "communicate", player: PlayerID, message?: Message, persist: boolean } // A player wants to communicate something, message is null when cleared
| { type: "server_message", text: string } // Text message from the server
- | { type: "score", points: number, demands_failed: number, demands_completed: number, } // Supplies information for score OSD
+ | { type: "score", points: number, demands_failed: number, demands_completed: number, time_remaining?: number } // Supplies information for score OSD
| { type: "set_ingame", state: boolean } // Set to false when entering the game or switching maps
| { type: "error", message: string } // Your client did something wrong.
diff --git a/test-client/visual.ts b/test-client/visual.ts
index e966b5cf..dd676435 100644
--- a/test-client/visual.ts
+++ b/test-client/visual.ts
@@ -15,7 +15,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { ItemData, MessageData, PlayerData, TileData, camera, camera_scale, canvas, ctx, data, demands_completed, demands_failed, get_interact_target, global_message, interact_active_anim, interact_possible_anim, interact_target_anim, items_removed, keys_down, my_id, players, points, tiles } from "./main.ts";
+import { ItemData, MessageData, PlayerData, TileData, camera, camera_scale, canvas, ctx, data, demands_completed, demands_failed, get_interact_target, global_message, interact_active_anim, interact_possible_anim, interact_target_anim, items_removed, keys_down, my_id, players, points, tiles, time_remaining } from "./main.ts";
import { PLAYER_SIZE } from "./movement.ts";
import { FALLBACK_TILE, ITEMS, TILES, FALLBACK_ITEM } from "./tiles.ts";
import { V2, ceil_v2, floor_v2 } from "./util.ts";
@@ -79,6 +79,9 @@ export function draw_ingame() {
ctx.fillStyle = "white"
ctx.textAlign = "left"
ctx.textBaseline = "bottom"
+ ctx.font = "20px sans-serif"
+ if (time_remaining != undefined)
+ ctx.fillText(`Time remaining: ${time_remaining?.toFixed(2)}`, 10, canvas.height - 90)
ctx.font = "30px sans-serif"
ctx.fillText(`Points: ${points}`, 10, canvas.height - 60)
ctx.font = "20px sans-serif"