1use std::collections::HashMap;
12
13use glam::Vec2;
14
15use crate::save::serializer::{DeserializeError, Serialize, Deserialize, SerializedValue};
16use crate::save::snapshot::{SnapshotSerializer, WorldSnapshot};
17
18#[derive(Debug, Clone)]
24pub struct Checkpoint {
25 pub id: u64,
27 pub name: String,
29 pub position: Vec2,
31 pub snapshot: WorldSnapshot,
33 pub created_at: f64,
35 pub tags: HashMap<String, String>,
37}
38
39impl Checkpoint {
40 pub fn new(
41 id: u64,
42 name: impl Into<String>,
43 position: Vec2,
44 snapshot: WorldSnapshot,
45 created_at: f64,
46 ) -> Self {
47 Self {
48 id,
49 name: name.into(),
50 position,
51 snapshot,
52 created_at,
53 tags: HashMap::new(),
54 }
55 }
56
57 pub fn set_tag(&mut self, key: impl Into<String>, value: impl Into<String>) {
59 self.tags.insert(key.into(), value.into());
60 }
61
62 pub fn get_tag(&self, key: &str) -> Option<&str> {
64 self.tags.get(key).map(String::as_str)
65 }
66
67 pub fn distance_to(&self, pos: Vec2) -> f32 {
69 (self.position - pos).length()
70 }
71
72 pub fn to_serialized(&self) -> SerializedValue {
75 let mut map = HashMap::new();
76 map.insert("id".into(), SerializedValue::Int(self.id as i64));
77 map.insert("name".into(), SerializedValue::Str(self.name.clone()));
78 map.insert("position".into(), self.position.serialize());
79 map.insert("created_at".into(), SerializedValue::Float(self.created_at));
80
81 let snap_bytes = SnapshotSerializer::to_bytes(&self.snapshot);
83 let snap_str = String::from_utf8(snap_bytes).unwrap_or_default();
84 map.insert("snapshot".into(), SerializedValue::Str(snap_str));
85
86 let tags: HashMap<String, SerializedValue> = self.tags.iter()
87 .map(|(k, v)| (k.clone(), SerializedValue::Str(v.clone())))
88 .collect();
89 map.insert("tags".into(), SerializedValue::Map(tags));
90
91 SerializedValue::Map(map)
92 }
93
94 pub fn from_serialized(sv: &SerializedValue) -> Result<Self, DeserializeError> {
95 let id = sv.get("id")
96 .and_then(|v| v.as_int())
97 .ok_or_else(|| DeserializeError::MissingKey("id".into()))? as u64;
98 let name = sv.get("name")
99 .and_then(|v| v.as_str())
100 .unwrap_or("unnamed")
101 .to_string();
102 let position = sv.get("position")
103 .map(Vec2::deserialize)
104 .transpose()?
105 .unwrap_or(Vec2::ZERO);
106 let created_at = sv.get("created_at")
107 .and_then(|v| v.as_float())
108 .unwrap_or(0.0);
109
110 let snapshot_str = sv.get("snapshot")
111 .and_then(|v| v.as_str())
112 .unwrap_or("{}");
113 let snapshot = SnapshotSerializer::from_bytes(snapshot_str.as_bytes())
114 .unwrap_or_else(|_| WorldSnapshot::new());
115
116 let tags: HashMap<String, String> = sv.get("tags")
117 .and_then(|v| v.as_map())
118 .map(|m| {
119 m.iter()
120 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
121 .collect()
122 })
123 .unwrap_or_default();
124
125 Ok(Checkpoint { id, name, position, snapshot, created_at, tags })
126 }
127}
128
129pub struct CheckpointManager {
138 checkpoints: Vec<Checkpoint>,
139 pub max_checkpoints: usize,
140 id_counter: u64,
141}
142
143impl CheckpointManager {
144 pub fn new(max: usize) -> Self {
146 Self {
147 checkpoints: Vec::new(),
148 max_checkpoints: max.max(1),
149 id_counter: 0,
150 }
151 }
152
153 pub fn create(
159 &mut self,
160 name: impl Into<String>,
161 pos: Vec2,
162 snapshot: WorldSnapshot,
163 ) -> u64 {
164 self.create_with_time(name, pos, snapshot, 0.0)
165 }
166
167 pub fn create_with_time(
169 &mut self,
170 name: impl Into<String>,
171 pos: Vec2,
172 snapshot: WorldSnapshot,
173 created_at: f64,
174 ) -> u64 {
175 let id = self.next_id();
176 let checkpoint = Checkpoint::new(id, name, pos, snapshot, created_at);
177 self.checkpoints.push(checkpoint);
178 self.evict_if_over_cap();
179 id
180 }
181
182 fn next_id(&mut self) -> u64 {
183 let id = self.id_counter;
184 self.id_counter += 1;
185 id
186 }
187
188 fn evict_if_over_cap(&mut self) {
189 while self.checkpoints.len() > self.max_checkpoints {
190 let oldest_idx = self
192 .checkpoints
193 .iter()
194 .enumerate()
195 .min_by(|(_, a), (_, b)| a.created_at.partial_cmp(&b.created_at).unwrap())
196 .map(|(i, _)| i)
197 .unwrap_or(0);
198 self.checkpoints.remove(oldest_idx);
199 }
200 }
201
202 pub fn get(&self, id: u64) -> Option<&Checkpoint> {
206 self.checkpoints.iter().find(|c| c.id == id)
207 }
208
209 pub fn get_mut(&mut self, id: u64) -> Option<&mut Checkpoint> {
211 self.checkpoints.iter_mut().find(|c| c.id == id)
212 }
213
214 pub fn get_nearest(&self, pos: Vec2) -> Option<&Checkpoint> {
216 self.checkpoints
217 .iter()
218 .min_by(|a, b| {
219 a.distance_to(pos)
220 .partial_cmp(&b.distance_to(pos))
221 .unwrap_or(std::cmp::Ordering::Equal)
222 })
223 }
224
225 pub fn get_most_recent(&self) -> Option<&Checkpoint> {
227 self.checkpoints
228 .iter()
229 .max_by(|a, b| a.created_at.partial_cmp(&b.created_at).unwrap_or(std::cmp::Ordering::Equal))
230 }
231
232 pub fn get_within_radius(&self, pos: Vec2, radius: f32) -> Vec<&Checkpoint> {
234 let mut nearby: Vec<&Checkpoint> = self
235 .checkpoints
236 .iter()
237 .filter(|c| c.distance_to(pos) <= radius)
238 .collect();
239 nearby.sort_by(|a, b| {
240 a.distance_to(pos)
241 .partial_cmp(&b.distance_to(pos))
242 .unwrap_or(std::cmp::Ordering::Equal)
243 });
244 nearby
245 }
246
247 pub fn list(&self) -> &[Checkpoint] {
249 &self.checkpoints
250 }
251
252 pub fn remove(&mut self, id: u64) -> bool {
256 let before = self.checkpoints.len();
257 self.checkpoints.retain(|c| c.id != id);
258 self.checkpoints.len() < before
259 }
260
261 pub fn clear(&mut self) {
263 self.checkpoints.clear();
264 }
265
266 pub fn len(&self) -> usize {
270 self.checkpoints.len()
271 }
272
273 pub fn is_empty(&self) -> bool {
274 self.checkpoints.is_empty()
275 }
276
277 pub fn is_at_cap(&self) -> bool {
278 self.checkpoints.len() >= self.max_checkpoints
279 }
280
281 pub fn serialize_all(&self) -> Vec<u8> {
285 let list: Vec<SerializedValue> = self.checkpoints.iter()
286 .map(|c| c.to_serialized())
287 .collect();
288 let sv = SerializedValue::List(list);
289 sv.to_json_string().into_bytes()
290 }
291
292 pub fn deserialize_all(bytes: &[u8]) -> Result<Vec<Checkpoint>, DeserializeError> {
294 let s = std::str::from_utf8(bytes)
295 .map_err(|e| DeserializeError::ParseError(e.to_string()))?;
296 let sv = SerializedValue::from_json_str(s)?;
297 sv.as_list()
298 .ok_or(DeserializeError::Custom("expected list of checkpoints".into()))?
299 .iter()
300 .map(Checkpoint::from_serialized)
301 .collect()
302 }
303
304 pub fn load_from_bytes(&mut self, bytes: &[u8]) -> Result<(), DeserializeError> {
306 let checkpoints = Self::deserialize_all(bytes)?;
307 if let Some(max_id) = checkpoints.iter().map(|c| c.id).max() {
309 self.id_counter = max_id + 1;
310 }
311 self.checkpoints = checkpoints;
312 Ok(())
313 }
314}
315
316pub struct RespawnSystem {
326 pub last_checkpoint: Option<u64>,
328 pub respawn_count: u32,
330 pub activation_radius: f32,
332 respawn_history: Vec<RespawnEvent>,
334}
335
336#[derive(Debug, Clone)]
338pub struct RespawnEvent {
339 pub checkpoint_id: u64,
340 pub game_time: f64,
341 pub respawn_index: u32,
342}
343
344impl RespawnSystem {
345 pub fn new() -> Self {
347 Self {
348 last_checkpoint: None,
349 respawn_count: 0,
350 activation_radius: 2.0,
351 respawn_history: Vec::new(),
352 }
353 }
354
355 pub fn with_activation_radius(mut self, r: f32) -> Self {
356 self.activation_radius = r;
357 self
358 }
359
360 pub fn update_checkpoint(
368 &mut self,
369 manager: &CheckpointManager,
370 player_pos: Vec2,
371 ) -> Option<u64> {
372 let nearest = manager.get_nearest(player_pos)?;
373 if nearest.distance_to(player_pos) <= self.activation_radius {
374 let activated = self.last_checkpoint != Some(nearest.id);
375 self.last_checkpoint = Some(nearest.id);
376 if activated { Some(nearest.id) } else { None }
377 } else {
378 None
379 }
380 }
381
382 pub fn activate(&mut self, checkpoint_id: u64) {
384 self.last_checkpoint = Some(checkpoint_id);
385 }
386
387 pub fn deactivate(&mut self) {
389 self.last_checkpoint = None;
390 }
391
392 pub fn respawn<'a>(
398 &mut self,
399 manager: &'a CheckpointManager,
400 game_time: f64,
401 ) -> Option<&'a WorldSnapshot> {
402 let id = self.last_checkpoint?;
403 let checkpoint = manager.get(id)?;
404 self.respawn_count += 1;
405 self.respawn_history.push(RespawnEvent {
406 checkpoint_id: id,
407 game_time,
408 respawn_index: self.respawn_count,
409 });
410 Some(&checkpoint.snapshot)
411 }
412
413 pub fn peek_snapshot<'a>(&self, manager: &'a CheckpointManager) -> Option<&'a WorldSnapshot> {
415 let id = self.last_checkpoint?;
416 manager.get(id).map(|c| &c.snapshot)
417 }
418
419 pub fn has_checkpoint(&self) -> bool {
422 self.last_checkpoint.is_some()
423 }
424
425 pub fn respawn_history(&self) -> &[RespawnEvent] {
426 &self.respawn_history
427 }
428
429 pub fn clear_history(&mut self) {
430 self.respawn_history.clear();
431 }
432
433 pub fn checkpoint_position(&self, manager: &CheckpointManager) -> Option<Vec2> {
435 let id = self.last_checkpoint?;
436 manager.get(id).map(|c| c.position)
437 }
438}
439
440impl Default for RespawnSystem {
441 fn default() -> Self {
442 Self::new()
443 }
444}
445
446#[cfg(test)]
451mod tests {
452 use super::*;
453 use crate::save::serializer::SerializedValue;
454
455 fn make_snap(id: u64) -> WorldSnapshot {
456 let mut s = WorldSnapshot::new();
457 s.set_meta("source_entity", &id.to_string());
458 s
459 }
460
461 #[test]
462 fn create_and_get() {
463 let mut mgr = CheckpointManager::new(10);
464 let id = mgr.create("start", Vec2::ZERO, make_snap(1));
465 assert!(mgr.get(id).is_some());
466 assert_eq!(mgr.get(id).unwrap().name, "start");
467 }
468
469 #[test]
470 fn eviction_at_cap() {
471 let mut mgr = CheckpointManager::new(3);
472 let mut ids = vec![];
473 for i in 0..5u64 {
474 ids.push(mgr.create_with_time(format!("cp{i}"), Vec2::ZERO, make_snap(i), i as f64));
475 }
476 assert_eq!(mgr.len(), 3);
477 assert!(mgr.get(ids[0]).is_none());
479 assert!(mgr.get(ids[1]).is_none());
480 assert!(mgr.get(ids[4]).is_some());
481 }
482
483 #[test]
484 fn nearest_checkpoint() {
485 let mut mgr = CheckpointManager::new(10);
486 mgr.create("a", Vec2::new(0.0, 0.0), make_snap(1));
487 mgr.create("b", Vec2::new(100.0, 0.0), make_snap(2));
488 let near = mgr.get_nearest(Vec2::new(5.0, 0.0)).unwrap();
489 assert_eq!(near.name, "a");
490 }
491
492 #[test]
493 fn most_recent_checkpoint() {
494 let mut mgr = CheckpointManager::new(10);
495 mgr.create_with_time("first", Vec2::ZERO, make_snap(1), 1.0);
496 mgr.create_with_time("second", Vec2::ZERO, make_snap(2), 5.0);
497 mgr.create_with_time("third", Vec2::ZERO, make_snap(3), 3.0);
498 assert_eq!(mgr.get_most_recent().unwrap().name, "second");
499 }
500
501 #[test]
502 fn remove_checkpoint() {
503 let mut mgr = CheckpointManager::new(10);
504 let id = mgr.create("cp", Vec2::ZERO, make_snap(1));
505 assert!(mgr.remove(id));
506 assert!(!mgr.remove(id));
507 assert!(mgr.is_empty());
508 }
509
510 #[test]
511 fn serialize_deserialize_all() {
512 let mut mgr = CheckpointManager::new(10);
513 let mut s = make_snap(1);
514 s.timestamp = 42.0;
515 mgr.create("alpha", Vec2::new(1.0, 2.0), s);
516 mgr.create("beta", Vec2::new(3.0, 4.0), make_snap(2));
517
518 let bytes = mgr.serialize_all();
519 let restored = CheckpointManager::deserialize_all(&bytes).unwrap();
520 assert_eq!(restored.len(), 2);
521 assert_eq!(restored[0].name, "alpha");
522 assert!((restored[0].position.x - 1.0_f32).abs() < 1e-5);
523 }
524
525 #[test]
526 fn checkpoint_manager_load_from_bytes() {
527 let mut mgr = CheckpointManager::new(10);
528 mgr.create("cp1", Vec2::new(10.0, 20.0), make_snap(1));
529 let bytes = mgr.serialize_all();
530
531 let mut mgr2 = CheckpointManager::new(10);
532 mgr2.load_from_bytes(&bytes).unwrap();
533 assert_eq!(mgr2.len(), 1);
534 assert_eq!(mgr2.list()[0].name, "cp1");
535 }
536
537 #[test]
538 fn respawn_system_activate_and_respawn() {
539 let mut mgr = CheckpointManager::new(10);
540 let id = mgr.create("spawn", Vec2::ZERO, make_snap(99));
541
542 let mut respawn = RespawnSystem::new();
543 respawn.activate(id);
544 assert!(respawn.has_checkpoint());
545
546 let snap = respawn.respawn(&mgr, 0.0).unwrap();
547 assert_eq!(snap.get_meta("source_entity"), Some("99"));
548 assert_eq!(respawn.respawn_count, 1);
549 }
550
551 #[test]
552 fn respawn_system_update_by_proximity() {
553 let mut mgr = CheckpointManager::new(10);
554 mgr.create("near", Vec2::new(1.0, 0.0), make_snap(1));
555
556 let mut respawn = RespawnSystem::new().with_activation_radius(5.0);
557 let activated = respawn.update_checkpoint(&mgr, Vec2::new(0.5, 0.0));
558 assert!(activated.is_some());
559 assert!(respawn.has_checkpoint());
560 }
561
562 #[test]
563 fn respawn_system_no_checkpoint() {
564 let mgr = CheckpointManager::new(10);
565 let mut respawn = RespawnSystem::new();
566 assert!(respawn.respawn(&mgr, 0.0).is_none());
567 }
568
569 #[test]
570 fn respawn_history_tracks_events() {
571 let mut mgr = CheckpointManager::new(10);
572 let id = mgr.create("cp", Vec2::ZERO, make_snap(1));
573 let mut respawn = RespawnSystem::new();
574 respawn.activate(id);
575 respawn.respawn(&mgr, 10.0);
576 respawn.respawn(&mgr, 20.0);
577 assert_eq!(respawn.respawn_history().len(), 2);
578 assert_eq!(respawn.respawn_history()[1].game_time, 20.0);
579 }
580
581 #[test]
582 fn within_radius() {
583 let mut mgr = CheckpointManager::new(10);
584 mgr.create("close", Vec2::new(1.0, 0.0), make_snap(1));
585 mgr.create("far", Vec2::new(100.0, 0.0), make_snap(2));
586 let nearby = mgr.get_within_radius(Vec2::ZERO, 5.0);
587 assert_eq!(nearby.len(), 1);
588 assert_eq!(nearby[0].name, "close");
589 }
590
591 #[test]
592 fn checkpoint_tags() {
593 let mut mgr = CheckpointManager::new(10);
594 let id = mgr.create("tagged", Vec2::ZERO, make_snap(1));
595 mgr.get_mut(id).unwrap().set_tag("type", "boss_entrance");
596 assert_eq!(mgr.get(id).unwrap().get_tag("type"), Some("boss_entrance"));
597 }
598}