freenukum/
lib.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// SPDX-FileCopyrightText: Wolfgang Silbermayr <wolfgang@silbermayr.at>
3
4#[macro_use]
5extern crate serde_derive;
6
7pub mod actor;
8pub mod backdrop;
9pub mod borders;
10pub mod data;
11pub mod episodes;
12pub mod event;
13pub mod game;
14pub mod gamemenu;
15pub mod geometry;
16pub mod graphics;
17pub mod hero;
18pub mod infobox;
19pub mod inputbox;
20pub mod inputfield;
21pub mod level;
22pub mod mainmenu;
23pub mod menu;
24pub mod messagebox;
25pub mod picture;
26pub mod rendering;
27pub mod savegame;
28pub mod settings;
29pub mod shot;
30pub mod sound;
31pub mod text;
32pub mod tile;
33pub mod tilecache;
34pub mod tileprovider;
35
36use anyhow::Result;
37use hero::Hero;
38use sound::SoundIndex;
39use tileprovider::TileProvider;
40
41pub const GAME_INTERVAL: u32 = 80;
42
43pub const HALFTILE_WIDTH: u32 = 8;
44pub const HALFTILE_HEIGHT: u32 = 8;
45pub const TILE_WIDTH: u32 = HALFTILE_WIDTH * 2;
46pub const TILE_HEIGHT: u32 = HALFTILE_HEIGHT * 2;
47pub const MATILES_PER_FILE: usize = 50;
48pub const HEALTH_COUNT: usize = 8;
49pub const INVENTORY_WIDTH: usize = HEALTH_COUNT / 2;
50pub const FONT_WIDTH: u32 = 8;
51pub const FONT_HEIGHT: u32 = 8;
52pub const WINDOW_WIDTH: u32 = 320;
53pub const WINDOW_HEIGHT: u32 = 200;
54pub const PICTURE_WIDTH: u32 = 40;
55pub const PICTURE_HEIGHT: u32 = 200;
56pub const BACKDROP_WIDTH: u32 = 13;
57pub const BACKDROP_HEIGHT: u32 = 10;
58pub const MAX_LIFE: usize = 8;
59pub const MAX_FIREPOWER: usize = 4;
60pub const SCORE_DIGITS: usize = 8;
61pub const LEVELWINDOW_WIDTH: u32 = 13;
62pub const LEVELWINDOW_HEIGHT: u32 = 10;
63
64/// The height of the level in full tiles
65pub const LEVEL_HEIGHT: u32 = 90;
66/// The width of the level in full tiles
67pub const LEVEL_WIDTH: u32 = 128;
68
69const BACKGROUND_START: usize = 0;
70const BACKGROUND_LIGHT_GREY: usize = BACKGROUND_START + 70;
71
72const FONT_START: usize = 19 * 48 + 3 * 50;
73const FONT_ASCII_UPPERCASE: usize = FONT_START + 10;
74const FONT_ASCII_LOWERCASE: usize = FONT_START + 69;
75const FONT_QUESTIONMARK: usize = FONT_START + 67;
76const BORDER_START: usize = 19 * 48 + 5 * 50;
77const BORDER_BLUE_MIDDLE: usize = BORDER_START + 17;
78const BORDER_BLUE_TOPLEFT: usize = BORDER_START + 18;
79const BORDER_BLUE_TOPRIGHT: usize = BORDER_START + 19;
80const BORDER_BLUE_BOTTOMLEFT: usize = BORDER_START + 20;
81const BORDER_BLUE_BOTTOMRIGHT: usize = BORDER_START + 21;
82const BORDER_BLUE_LEFT: usize = BORDER_START + 22;
83const BORDER_BLUE_RIGHT: usize = BORDER_START + 23;
84const BORDER_BLUE_TOP: usize = BORDER_START + 24;
85const BORDER_BLUE_BOTTOM: usize = BORDER_START + 25;
86const BORDER_GREY_START: usize = BORDER_START;
87
88const NUMBER_START: usize = BORDER_START + 48;
89
90const NUMBER_100: usize = NUMBER_START;
91const NUMBER_200: usize = NUMBER_START + 2;
92const NUMBER_500: usize = NUMBER_START + 4;
93const NUMBER_1000: usize = NUMBER_START + 6;
94const NUMBER_2000: usize = NUMBER_START + 8;
95const NUMBER_5000: usize = NUMBER_START + 10;
96const NUMBER_10000: usize = NUMBER_START + 12;
97const NUMBER_BONUS_1_LEFT: usize = NUMBER_START + 14;
98const NUMBER_BONUS_1_RIGHT: usize = NUMBER_START + 16;
99const NUMBER_BONUS_2_LEFT: usize = NUMBER_START + 18;
100const NUMBER_BONUS_2_RIGHT: usize = NUMBER_START + 20;
101const NUMBER_BONUS_3_LEFT: usize = NUMBER_START + 22;
102const NUMBER_BONUS_3_RIGHT: usize = NUMBER_START + 24;
103const NUMBER_BONUS_4_LEFT: usize = NUMBER_START + 26;
104const NUMBER_BONUS_4_RIGHT: usize = NUMBER_START + 28;
105const NUMBER_BONUS_5_LEFT: usize = NUMBER_START + 30;
106const NUMBER_BONUS_5_RIGHT: usize = NUMBER_START + 32;
107const NUMBER_BONUS_6_LEFT: usize = NUMBER_START + 34;
108const NUMBER_BONUS_6_RIGHT: usize = NUMBER_START + 36;
109const NUMBER_BONUS_7_LEFT: usize = NUMBER_START + 38;
110const NUMBER_BONUS_7_RIGHT: usize = NUMBER_START + 40;
111
112const SOLID_START: usize = 4 * 48;
113const SOLID_SHOOTABLE_WALL_BRICKS: usize = SOLID_START;
114const SOLID_ELEVATOR: usize = SOLID_START + 23;
115const SOLID_BLACK: usize = SOLID_START + 65;
116const SOLID_EXPANDINGFLOOR: usize = SOLID_START + 191;
117const SOLID_CONVEYORBELT: usize = SOLID_START + 0x1C;
118const SOLID_CONVEYORBELT_LEFTEND: usize = SOLID_CONVEYORBELT;
119const SOLID_CONVEYORBELT_CENTER: usize = SOLID_CONVEYORBELT + 4;
120const SOLID_CONVEYORBELT_RIGHTEND: usize = SOLID_CONVEYORBELT + 6;
121
122const SOLID_END: usize = SOLID_START + 4 * 48;
123const ANIMATION_START: usize = SOLID_END;
124
125const ANIMATION_JUMPBOT: usize = ANIMATION_START + 10;
126const ANIMATION_CARBOT: usize = ANIMATION_START + 34;
127const ANIMATION_EXPLOSION: usize = ANIMATION_START + 42;
128const ANIMATION_FIREWHEEL_OFF: usize = ANIMATION_START + 48;
129const ANIMATION_FIREWHEEL_ON: usize = ANIMATION_START + 64;
130const ANIMATION_ROBOT: usize = ANIMATION_START + 80;
131const ANIMATION_BOMBFIRE: usize = ANIMATION_START + 90;
132const ANIMATION_EXITDOOR: usize = ANIMATION_START + 96;
133const ANIMATION_BOMB: usize = ANIMATION_START + 112;
134const ANIMATION_BALL: usize = ANIMATION_START + 120;
135const ANIMATION_SODA: usize = ANIMATION_START + 128;
136const ANIMATION_SODAFLY: usize = ANIMATION_START + 132;
137const ANIMATION_WALLCRAWLERBOT_LEFT: usize = ANIMATION_START + 136;
138const ANIMATION_WALLCRAWLERBOT_RIGHT: usize = ANIMATION_START + 140;
139const ANIMATION_FAN: usize = ANIMATION_START + 156;
140const ANIMATION_CAMERA_LEFT: usize = ANIMATION_START + 200;
141const ANIMATION_CAMERA_CENTER: usize = ANIMATION_START + 201;
142const ANIMATION_CAMERA_RIGHT: usize = ANIMATION_START + 202;
143const ANIMATION_BROKENWALLBG: usize = ANIMATION_START + 203;
144const ANIMATION_STONEWINDOWBG: usize = ANIMATION_START + 206;
145const ANIMATION_TELEPORTER1: usize = ANIMATION_START + 212;
146const ANIMATION_MINE: usize = ANIMATION_START + 223;
147const ANIMATION_WINDOWBG: usize = ANIMATION_START + 253;
148const ANIMATION_BADGUYSCREEN: usize = ANIMATION_START + 260;
149
150const OBJECT_START: usize = ANIMATION_START + 6 * 48;
151const OBJECT_BOX_GREY: usize = OBJECT_START;
152const OBJECT_SPARK_PINK: usize = OBJECT_START + 1;
153const OBJECT_SPARK_BLUE: usize = OBJECT_START + 2;
154const OBJECT_SPARK_WHITE: usize = OBJECT_START + 3;
155const OBJECT_SPARK_GREEN: usize = OBJECT_START + 4;
156const OBJECT_ELEVATOR_TOP: usize = OBJECT_START + 5;
157const OBJECT_SHOT: usize = OBJECT_START + 6;
158const OBJECT_BOOT: usize = OBJECT_START + 10;
159const OBJECT_ROCKET: usize = OBJECT_START + 11;
160const OBJECT_CLAMP: usize = OBJECT_START + 18;
161const OBJECT_DUSTCLOUD: usize = OBJECT_START + 19;
162const OBJECT_FIRERIGHT: usize = OBJECT_START + 24;
163const OBJECT_FIRELEFT: usize = OBJECT_START + 29;
164const OBJECT_STEAM: usize = OBJECT_START + 34;
165const OBJECT_HOSTILESHOT: usize = OBJECT_START + 39;
166const OBJECT_GUN: usize = OBJECT_START + 43;
167const OBJECT_CHICKEN_SINGLE: usize = OBJECT_START + 44;
168const OBJECT_CHICKEN_DOUBLE: usize = OBJECT_START + 45;
169const OBJECT_HERO_GUNFIRE_LEFT: usize = OBJECT_START + 46;
170const OBJECT_HERO_GUNFIRE_RIGHT: usize = OBJECT_START + 47;
171const OBJECT_ENEMY_GUNFIRE_LEFT: usize = OBJECT_START + 48;
172const OBJECT_ENEMY_GUNFIRE_RIGHT: usize = OBJECT_START + 49;
173const OBJECT_ELECTRIC_ARC: usize = OBJECT_START + 50;
174const OBJECT_ELECTRIC_ARC_HURTING: usize = OBJECT_START + 54;
175const OBJECT_FOOTBALL: usize = OBJECT_START + 58;
176const OBJECT_JOYSTICK: usize = OBJECT_START + 59;
177const OBJECT_DISK: usize = OBJECT_START + 60;
178const OBJECT_HEALTH: usize = OBJECT_START + 61;
179const OBJECT_NONHEALTH: usize = OBJECT_START + 62;
180const OBJECT_GLOVE: usize = OBJECT_START + 63;
181const OBJECT_LASERBEAM: usize = OBJECT_START + 65;
182const OBJECT_ACCESS_CARD: usize = OBJECT_START + 64;
183const OBJECT_BALLOON: usize = OBJECT_START + 69;
184const OBJECT_NUCLEARMOLECULE: usize = OBJECT_START + 74;
185const OBJECT_FALLINGBLOCK: usize = OBJECT_START + 83;
186const OBJECT_POINT: usize = OBJECT_START + 85;
187const OBJECT_ROTATINGCYLINDER: usize = OBJECT_START + 90;
188const OBJECT_SPIKE: usize = OBJECT_START + 95;
189const OBJECT_FLAG: usize = OBJECT_START + 97;
190const OBJECT_BOX_BLUE: usize = OBJECT_START + 100;
191const OBJECT_BOX_RED: usize = OBJECT_START + 101;
192const OBJECT_RADIO: usize = OBJECT_START + 102;
193const OBJECT_ACCESS_CARD_SLOT: usize = OBJECT_START + 105;
194const OBJECT_GLOVE_SLOT: usize = OBJECT_START + 114;
195const OBJECT_LETTER_D: usize = OBJECT_START + 118;
196const OBJECT_LETTER_U: usize = OBJECT_START + 119;
197const OBJECT_LETTER_K: usize = OBJECT_START + 120;
198const OBJECT_LETTER_E: usize = OBJECT_START + 121;
199const OBJECT_NOTEBOOK: usize = OBJECT_START + 123;
200const OBJECT_KEY_RED: usize = OBJECT_START + 124;
201const OBJECT_KEY_GREEN: usize = OBJECT_START + 125;
202const OBJECT_KEY_BLUE: usize = OBJECT_START + 126;
203const OBJECT_KEY_PINK: usize = OBJECT_START + 127;
204const OBJECT_DOOR: usize = OBJECT_START + 128;
205const OBJECT_KEYHOLE_BLACK: usize = OBJECT_START + 136;
206const OBJECT_KEYHOLE_RED: usize = OBJECT_START + 137;
207const OBJECT_KEYHOLE_GREEN: usize = OBJECT_START + 138;
208const OBJECT_KEYHOLE_BLUE: usize = OBJECT_START + 139;
209const OBJECT_KEYHOLE_PINK: usize = OBJECT_START + 140;
210const OBJECT_SPIKES_UP: usize = OBJECT_START + 148;
211const OBJECT_SPIKES_DOWN: usize = OBJECT_START + 149;
212
213const HERO_START: usize = OBJECT_START + 150;
214
215const HERO_NUM_WALKING: usize = 4;
216const HERO_WALKING_LEFT: usize = HERO_START;
217const HERO_STANDING_LEFT_SHOOTING: usize = HERO_START + 0x0C;
218const HERO_WALKING_RIGHT: usize = HERO_START + 0x10;
219const HERO_STANDING_RIGHT_SHOOTING: usize = HERO_START + 0x1C;
220const HERO_JUMPING_LEFT: usize = HERO_START + 0x20;
221const HERO_JUMPING_RIGHT: usize = HERO_START + 0x24;
222const HERO_FALLING_LEFT: usize = HERO_START + 0x28;
223const HERO_FALLING_RIGHT: usize = HERO_START + 0x2C;
224const HERO_STANDING_LEFT: usize = HERO_START + 0x30;
225const HERO_STANDING_RIGHT: usize = HERO_START + 0x34;
226const HERO_JUMPING_LEFT_SOMERSAULT: usize = HERO_START + 0x38;
227const HERO_JUMPING_RIGHT_SOMERSAULT: usize = HERO_START + 0x54;
228const HERO_SKELETON_LEFT: usize = HERO_START + 0xB0;
229const HERO_SKELETON_RIGHT: usize = HERO_START + 0xB4;
230
231#[derive(
232    Debug,
233    Clone,
234    Copy,
235    PartialEq,
236    Eq,
237    PartialOrd,
238    Ord,
239    Serialize,
240    Deserialize,
241    Hash,
242)]
243pub enum KeyColor {
244    Red,
245    Blue,
246    Pink,
247    Green,
248}
249
250impl std::string::ToString for KeyColor {
251    fn to_string(&self) -> String {
252        match *self {
253            KeyColor::Red => "red",
254            KeyColor::Blue => "blue",
255            KeyColor::Pink => "pink",
256            KeyColor::Green => "green",
257        }
258        .to_string()
259    }
260}
261
262fn directories() -> directories_next::ProjectDirs {
263    directories_next::ProjectDirs::from("", "", "freenukum").unwrap()
264}
265
266fn config_dir() -> std::path::PathBuf {
267    directories().config_dir().to_path_buf()
268}
269
270pub fn data_dir() -> std::path::PathBuf {
271    directories().data_dir().to_path_buf()
272}
273
274fn collision_bounds_color() -> sdl2::pixels::Color {
275    sdl2::pixels::Color::RGB(182, 6, 0)
276}
277
278#[derive(Hash, Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
279pub enum HorizontalDirection {
280    Left,
281    Right,
282}
283
284impl HorizontalDirection {
285    pub fn opposite(self) -> Self {
286        match self {
287            HorizontalDirection::Left => HorizontalDirection::Right,
288            HorizontalDirection::Right => HorizontalDirection::Left,
289        }
290    }
291
292    pub fn reverse(&mut self) {
293        *self = self.opposite();
294    }
295
296    pub fn as_factor_i32(&self) -> i32 {
297        match self {
298            HorizontalDirection::Left => -1,
299            HorizontalDirection::Right => 1,
300        }
301    }
302
303    pub fn map<T>(&self, left: T, right: T) -> T {
304        match self {
305            HorizontalDirection::Left => left,
306            HorizontalDirection::Right => right,
307        }
308    }
309}
310
311#[derive(Debug, Eq, PartialEq)]
312pub enum VerticalDirection {
313    Up,
314    Down,
315}
316
317#[derive(Debug, Eq, PartialEq)]
318pub enum UserEvent {
319    Timer,
320    Redraw,
321}
322
323pub trait Sizes {
324    fn width(&self) -> u32;
325    fn height(&self) -> u32;
326    fn half_width(&self) -> u32;
327    fn half_height(&self) -> u32;
328}
329
330pub struct DefaultSizes;
331
332impl Sizes for DefaultSizes {
333    fn width(&self) -> u32 {
334        TILE_WIDTH
335    }
336    fn height(&self) -> u32 {
337        TILE_HEIGHT
338    }
339    fn half_width(&self) -> u32 {
340        HALFTILE_WIDTH
341    }
342    fn half_height(&self) -> u32 {
343        HALFTILE_HEIGHT
344    }
345}
346
347#[derive(Debug, PartialEq, Eq)]
348struct RangedIterator {
349    current: usize,
350    max: usize,
351    finished_cycles: usize,
352}
353
354impl RangedIterator {
355    pub fn new(max: usize) -> Self {
356        RangedIterator {
357            current: 0,
358            max,
359            finished_cycles: 0,
360        }
361    }
362
363    pub fn reset(&mut self, max: usize) {
364        self.current = 0;
365        self.max = max;
366        self.finished_cycles = 0;
367    }
368
369    pub fn is_first(&self) -> bool {
370        self.current == 0
371    }
372
373    pub fn current(&self) -> usize {
374        self.current
375    }
376
377    pub fn current_reverse(&self) -> usize {
378        self.max - 1 - self.current
379    }
380
381    pub fn finished_cycles(&self) -> usize {
382        self.finished_cycles
383    }
384
385    pub fn max_value(&self) -> usize {
386        self.max
387    }
388
389    fn enforce_range(&mut self) {
390        let old = self.current;
391        self.current %= self.max;
392        if self.current != old {
393            self.finished_cycles =
394                self.finished_cycles.overflowing_add(1).0
395        }
396    }
397
398    fn rewind(&mut self) {
399        if self.current == 0 {
400            self.current = self.max;
401            if self.current > 0 {
402                self.current -= 1;
403            }
404            if self.finished_cycles > 0 {
405                self.finished_cycles -= 1;
406            } else {
407                self.finished_cycles = usize::MAX;
408            }
409        } else {
410            self.current -= 1;
411        }
412    }
413}
414
415impl Iterator for RangedIterator {
416    type Item = usize;
417
418    fn next(&mut self) -> Option<Self::Item> {
419        self.current += 1;
420        self.enforce_range();
421        Some(self.current)
422    }
423}