1use crate::map::MapTrait;
9use crate::tileset::TilesetTrait;
10
11use serde::{Deserialize, Serialize};
12use std::fmt::Debug;
13use std::hash::Hash;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
19pub enum Direction {
20 Down,
21 Up,
22 Left,
23 Right,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
28pub enum TransportMode {
29 Walking,
30 Biking,
31 Surfing,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
36pub enum MovementState {
37 Idle,
38 Walking,
39 Jumping,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46#[non_exhaustive]
47pub struct MapConnection<M: MapTrait> {
48 pub direction: Direction,
49 pub target_map: M,
50 pub offset: i8,
52}
53
54impl<M: MapTrait> MapConnection<M> {
55 pub fn new(direction: Direction, target_map: M, offset: i8) -> Self {
57 Self {
58 direction,
59 target_map,
60 offset,
61 }
62 }
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67#[non_exhaustive]
68pub struct MapConnections<M: MapTrait> {
69 pub north: Option<MapConnection<M>>,
70 pub south: Option<MapConnection<M>>,
71 pub west: Option<MapConnection<M>>,
72 pub east: Option<MapConnection<M>>,
73}
74
75impl<M: MapTrait> Default for MapConnections<M> {
76 fn default() -> Self {
77 Self {
78 north: None,
79 south: None,
80 west: None,
81 east: None,
82 }
83 }
84}
85
86impl<M: MapTrait> MapConnections<M> {
87 pub fn count(&self) -> usize {
89 self.north.is_some() as usize
90 + self.south.is_some() as usize
91 + self.west.is_some() as usize
92 + self.east.is_some() as usize
93 }
94
95 pub fn get(&self, dir: Direction) -> Option<&MapConnection<M>> {
97 match dir {
98 Direction::Up => self.north.as_ref(),
99 Direction::Down => self.south.as_ref(),
100 Direction::Left => self.west.as_ref(),
101 Direction::Right => self.east.as_ref(),
102 }
103 }
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
110#[non_exhaustive]
111pub struct WarpPoint<M: MapTrait> {
112 pub x: u8,
114 pub y: u8,
115 pub target_map: M,
117 pub target_warp_id: u8,
119 pub is_last_map: bool,
121}
122
123impl<M: MapTrait> WarpPoint<M> {
124 pub fn new(x: u8, y: u8, target_map: M, target_warp_id: u8) -> Self {
126 Self {
127 x,
128 y,
129 target_map,
130 target_warp_id,
131 is_last_map: false,
132 }
133 }
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
140#[non_exhaustive]
141pub struct Sign {
142 pub x: u8,
143 pub y: u8,
144 pub text_id: u8,
146}
147
148impl Sign {
149 pub fn new(x: u8, y: u8, text_id: u8) -> Self {
151 Self { x, y, text_id }
152 }
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
159pub enum NpcMovementType {
160 Stationary,
162 Wander,
164 FixedPath,
166 FacePlayer,
168}
169
170#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
176#[non_exhaustive]
177pub struct NpcDefinition {
178 pub sprite_id: u8,
180 pub x: u8,
182 pub y: u8,
183 pub movement: NpcMovementType,
185 pub facing: Direction,
187 pub range: u8,
189 pub text_id: u8,
191}
192
193impl NpcDefinition {
194 #[allow(clippy::too_many_arguments)]
196 pub fn new(
197 sprite_id: u8,
198 x: u8,
199 y: u8,
200 movement: NpcMovementType,
201 facing: Direction,
202 range: u8,
203 text_id: u8,
204 ) -> Self {
205 Self {
206 sprite_id,
207 x,
208 y,
209 movement,
210 facing,
211 range,
212 text_id,
213 }
214 }
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize)]
221#[non_exhaustive]
222pub struct MapData<M: MapTrait, T: TilesetTrait, Mus> {
223 pub id: M,
224 pub width: u8,
225 pub height: u8,
226 pub tileset: T,
227 pub music: Mus,
228 pub blocks: Vec<u8>,
231 pub warps: Vec<WarpPoint<M>>,
232 pub npcs: Vec<NpcDefinition>,
233 pub signs: Vec<Sign>,
234 pub connections: MapConnections<M>,
235}
236
237impl<M: MapTrait, T: TilesetTrait, Mus> MapData<M, T, Mus> {
238 #[allow(clippy::too_many_arguments)]
240 pub fn new(
241 id: M,
242 width: u8,
243 height: u8,
244 tileset: T,
245 music: Mus,
246 blocks: Vec<u8>,
247 warps: Vec<WarpPoint<M>>,
248 npcs: Vec<NpcDefinition>,
249 signs: Vec<Sign>,
250 connections: MapConnections<M>,
251 ) -> Self {
252 Self {
253 id,
254 width,
255 height,
256 tileset,
257 music,
258 blocks,
259 warps,
260 npcs,
261 signs,
262 connections,
263 }
264 }
265
266 pub fn set_block(&mut self, block_x: u8, block_y: u8, block_id: u8) -> bool {
274 let (w, h) = (self.width as usize, self.height as usize);
275 let (bx, by) = (block_x as usize, block_y as usize);
276 if bx >= w || by >= h {
277 return false;
278 }
279 let idx = by * w + bx;
280 if idx < self.blocks.len() {
281 self.blocks[idx] = block_id;
282 true
283 } else {
284 false
285 }
286 }
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize)]
293#[non_exhaustive]
294pub struct PlayerState {
295 pub x: u16,
296 pub y: u16,
297 pub facing: Direction,
298 pub movement_state: MovementState,
299 pub transport: TransportMode,
300 #[serde(default = "default_bike_speedup")]
305 pub bike_speedup_active: bool,
306}
307
308fn default_bike_speedup() -> bool {
309 true
310}
311
312impl Default for PlayerState {
313 fn default() -> Self {
314 Self {
315 x: 0,
316 y: 0,
317 facing: Direction::Down,
318 movement_state: MovementState::Idle,
319 transport: TransportMode::Walking,
320 bike_speedup_active: true,
321 }
322 }
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize)]
329#[non_exhaustive]
330pub struct OverworldState<M: MapTrait> {
331 pub current_map: M,
332 pub player: PlayerState,
333 pub walk_counter: u8,
335 pub encounter_cooldown: u8,
337 pub repel_steps: u16,
339 pub standing_on_warp: bool,
341 pub standing_on_door: bool,
343 pub exiting_door: bool,
345}
346
347impl<M: MapTrait> OverworldState<M> {
348 pub fn new(start_map: M) -> Self {
350 Self {
351 current_map: start_map,
352 player: PlayerState::default(),
353 walk_counter: 0,
354 encounter_cooldown: 0,
355 repel_steps: 0,
356 standing_on_warp: false,
357 standing_on_door: false,
358 exiting_door: false,
359 }
360 }
361}
362
363#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
367#[non_exhaustive]
368pub struct OverworldInput {
369 pub up: bool,
370 pub down: bool,
371 pub left: bool,
372 pub right: bool,
373 pub a: bool,
374 pub b: bool,
375 pub start: bool,
376 pub select: bool,
377}
378
379impl OverworldInput {
380 pub fn new(
382 up: bool,
383 down: bool,
384 left: bool,
385 right: bool,
386 a: bool,
387 b: bool,
388 start: bool,
389 select: bool,
390 ) -> Self {
391 Self {
392 up,
393 down,
394 left,
395 right,
396 a,
397 b,
398 start,
399 select,
400 }
401 }
402
403 pub fn none() -> Self {
405 Self {
406 up: false,
407 down: false,
408 left: false,
409 right: false,
410 a: false,
411 b: false,
412 start: false,
413 select: false,
414 }
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421
422 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
423 struct TestMap;
424 impl MapTrait for TestMap {}
425
426 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
427 struct TestTileset;
428 impl TilesetTrait for TestTileset {
429 fn id(&self) -> u8 {
430 0
431 }
432 fn name(&self) -> &'static str {
433 "test"
434 }
435 }
436
437 fn make_map() -> MapData<TestMap, TestTileset, u8> {
439 MapData::new(
440 TestMap,
441 3,
442 2,
443 TestTileset,
444 0u8,
445 vec![0, 1, 2, 3, 4, 5],
446 Vec::new(),
447 Vec::new(),
448 Vec::new(),
449 MapConnections::default(),
450 )
451 }
452
453 #[test]
454 fn set_block_in_bounds_mutates_and_returns_true() {
455 let mut map = make_map();
456 assert!(map.set_block(2, 1, 99));
458 assert_eq!(map.blocks[5], 99);
459 assert_eq!(map.blocks, vec![0, 1, 2, 3, 4, 99]);
461
462 assert!(map.set_block(0, 0, 42));
464 assert_eq!(map.blocks[0], 42);
465 }
466
467 #[test]
468 fn set_block_out_of_bounds_returns_false_and_no_change() {
469 let mut map = make_map();
470 let before = map.blocks.clone();
471
472 assert!(!map.set_block(3, 0, 99));
474 assert!(!map.set_block(0, 2, 99));
476 assert!(!map.set_block(10, 10, 99));
478
479 assert_eq!(map.blocks, before, "out-of-bounds writes must not mutate");
480 }
481}