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, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
175pub enum NpcWanderAxis {
176 #[default]
178 Any,
179 Vertical,
181 Horizontal,
183}
184
185#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
191#[non_exhaustive]
192pub struct NpcDefinition {
193 pub sprite_id: u8,
195 pub x: u8,
197 pub y: u8,
198 pub movement: NpcMovementType,
200 pub wander_axis: NpcWanderAxis,
203 pub facing: Direction,
205 pub range: u8,
207 pub text_id: u8,
209}
210
211impl NpcDefinition {
212 #[allow(clippy::too_many_arguments)]
214 pub fn new(
215 sprite_id: u8,
216 x: u8,
217 y: u8,
218 movement: NpcMovementType,
219 facing: Direction,
220 range: u8,
221 text_id: u8,
222 ) -> Self {
223 Self {
224 sprite_id,
225 x,
226 y,
227 movement,
228 wander_axis: NpcWanderAxis::Any,
229 facing,
230 range,
231 text_id,
232 }
233 }
234
235 #[allow(clippy::too_many_arguments)]
238 pub fn new_with_axis(
239 sprite_id: u8,
240 x: u8,
241 y: u8,
242 movement: NpcMovementType,
243 wander_axis: NpcWanderAxis,
244 facing: Direction,
245 range: u8,
246 text_id: u8,
247 ) -> Self {
248 Self {
249 sprite_id,
250 x,
251 y,
252 movement,
253 wander_axis,
254 facing,
255 range,
256 text_id,
257 }
258 }
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
265#[non_exhaustive]
266pub struct MapData<M: MapTrait, T: TilesetTrait, Mus> {
267 pub id: M,
268 pub width: u8,
269 pub height: u8,
270 pub tileset: T,
271 pub music: Mus,
272 pub blocks: Vec<u8>,
275 pub warps: Vec<WarpPoint<M>>,
276 pub npcs: Vec<NpcDefinition>,
277 pub signs: Vec<Sign>,
278 pub connections: MapConnections<M>,
279}
280
281impl<M: MapTrait, T: TilesetTrait, Mus> MapData<M, T, Mus> {
282 #[allow(clippy::too_many_arguments)]
284 pub fn new(
285 id: M,
286 width: u8,
287 height: u8,
288 tileset: T,
289 music: Mus,
290 blocks: Vec<u8>,
291 warps: Vec<WarpPoint<M>>,
292 npcs: Vec<NpcDefinition>,
293 signs: Vec<Sign>,
294 connections: MapConnections<M>,
295 ) -> Self {
296 Self {
297 id,
298 width,
299 height,
300 tileset,
301 music,
302 blocks,
303 warps,
304 npcs,
305 signs,
306 connections,
307 }
308 }
309
310 pub fn set_block(&mut self, block_x: u8, block_y: u8, block_id: u8) -> bool {
318 let (w, h) = (self.width as usize, self.height as usize);
319 let (bx, by) = (block_x as usize, block_y as usize);
320 if bx >= w || by >= h {
321 return false;
322 }
323 let idx = by * w + bx;
324 if idx < self.blocks.len() {
325 self.blocks[idx] = block_id;
326 true
327 } else {
328 false
329 }
330 }
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize)]
337#[non_exhaustive]
338pub struct PlayerState {
339 pub x: u16,
340 pub y: u16,
341 pub facing: Direction,
342 pub movement_state: MovementState,
343 pub transport: TransportMode,
344 #[serde(default = "default_bike_speedup")]
349 pub bike_speedup_active: bool,
350}
351
352fn default_bike_speedup() -> bool {
353 true
354}
355
356impl Default for PlayerState {
357 fn default() -> Self {
358 Self {
359 x: 0,
360 y: 0,
361 facing: Direction::Down,
362 movement_state: MovementState::Idle,
363 transport: TransportMode::Walking,
364 bike_speedup_active: true,
365 }
366 }
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize)]
373#[non_exhaustive]
374pub struct OverworldState<M: MapTrait> {
375 pub current_map: M,
376 pub player: PlayerState,
377 pub walk_counter: u8,
379 pub encounter_cooldown: u8,
381 pub repel_steps: u16,
383 pub standing_on_warp: bool,
385 pub standing_on_door: bool,
387 pub exiting_door: bool,
389}
390
391impl<M: MapTrait> OverworldState<M> {
392 pub fn new(start_map: M) -> Self {
394 Self {
395 current_map: start_map,
396 player: PlayerState::default(),
397 walk_counter: 0,
398 encounter_cooldown: 0,
399 repel_steps: 0,
400 standing_on_warp: false,
401 standing_on_door: false,
402 exiting_door: false,
403 }
404 }
405}
406
407#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
411#[non_exhaustive]
412pub struct OverworldInput {
413 pub up: bool,
414 pub down: bool,
415 pub left: bool,
416 pub right: bool,
417 pub a: bool,
418 pub b: bool,
419 pub start: bool,
420 pub select: bool,
421}
422
423impl OverworldInput {
424 pub fn new(
426 up: bool,
427 down: bool,
428 left: bool,
429 right: bool,
430 a: bool,
431 b: bool,
432 start: bool,
433 select: bool,
434 ) -> Self {
435 Self {
436 up,
437 down,
438 left,
439 right,
440 a,
441 b,
442 start,
443 select,
444 }
445 }
446
447 pub fn none() -> Self {
449 Self {
450 up: false,
451 down: false,
452 left: false,
453 right: false,
454 a: false,
455 b: false,
456 start: false,
457 select: false,
458 }
459 }
460}
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465
466 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
467 struct TestMap;
468 impl MapTrait for TestMap {}
469
470 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
471 struct TestTileset;
472 impl TilesetTrait for TestTileset {
473 fn id(&self) -> u8 {
474 0
475 }
476 fn name(&self) -> &'static str {
477 "test"
478 }
479 }
480
481 fn make_map() -> MapData<TestMap, TestTileset, u8> {
483 MapData::new(
484 TestMap,
485 3,
486 2,
487 TestTileset,
488 0u8,
489 vec![0, 1, 2, 3, 4, 5],
490 Vec::new(),
491 Vec::new(),
492 Vec::new(),
493 MapConnections::default(),
494 )
495 }
496
497 #[test]
498 fn set_block_in_bounds_mutates_and_returns_true() {
499 let mut map = make_map();
500 assert!(map.set_block(2, 1, 99));
502 assert_eq!(map.blocks[5], 99);
503 assert_eq!(map.blocks, vec![0, 1, 2, 3, 4, 99]);
505
506 assert!(map.set_block(0, 0, 42));
508 assert_eq!(map.blocks[0], 42);
509 }
510
511 #[test]
512 fn set_block_out_of_bounds_returns_false_and_no_change() {
513 let mut map = make_map();
514 let before = map.blocks.clone();
515
516 assert!(!map.set_block(3, 0, 99));
518 assert!(!map.set_block(0, 2, 99));
520 assert!(!map.set_block(10, 10, 99));
522
523 assert_eq!(map.blocks, before, "out-of-bounds writes must not mutate");
524 }
525}