1#[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
64pub const LEVEL_HEIGHT: u32 = 90;
66pub 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}