├── res ├── NOTICE ├── images │ ├── font.png │ ├── gui.png │ ├── logo.png │ ├── items.png │ ├── level.png │ ├── sparks.png │ ├── entities.png │ ├── level-light.png │ ├── player-light.png │ ├── text-particle.png │ └── player-lantern-light.png ├── palettes │ ├── items.png │ ├── sprites.png │ └── background.png ├── sounds │ ├── craft.raw │ ├── pickup.raw │ ├── start.raw │ ├── boss_death.raw │ ├── monster_hurt.raw │ ├── player_death.raw │ └── player_hurt.raw └── resources.json ├── cover ├── cover.png ├── img │ ├── logo.png │ ├── background.png │ └── gba-label.png └── cover.svg ├── .gitignore ├── .gitmodules ├── tools ├── header-checksum ├── verify-save-file ├── color-convert └── release ├── src ├── sounds.c ├── options.c ├── scene.c ├── header.s ├── scene │ ├── win.c │ ├── instructions.c │ ├── transition.c │ ├── about.c │ ├── prestart.c │ ├── options.c │ ├── pause.c │ ├── death.c │ ├── inventory.c │ ├── game.c │ ├── start.c │ ├── chest.c │ └── crafting.c ├── draw │ └── sort_entities.c ├── minicraft.c ├── entity │ ├── smash-particle.c │ ├── text-particle.c │ ├── spark.c │ ├── furniture.c │ ├── slime.c │ ├── zombie.c │ ├── item.c │ └── air-wizard.c ├── performance.c ├── inventory.c ├── tick │ └── tiles.c ├── debug │ └── map-viewer.c ├── mob.c ├── entity.c ├── item.c ├── crafting.c ├── screen.c └── storage.c ├── doc └── release-checklist.md ├── RESOURCES.md ├── include ├── generator.h ├── air-wizard.h ├── options.h ├── performance.h ├── storage.h ├── furniture.h ├── player.h ├── sound.h ├── util.h ├── minicraft.h ├── inventory.h ├── screen.h ├── crafting.h ├── mob.h ├── scene.h ├── tile.h ├── level.h ├── item.h └── entity.h ├── CHANGELOG.md ├── README.md └── Makefile /res/NOTICE: -------------------------------------------------------------------------------- 1 | The artwork and sounds were made by Markus Persson in December 2011. 2 | -------------------------------------------------------------------------------- /cover/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/cover/cover.png -------------------------------------------------------------------------------- /cover/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/cover/img/logo.png -------------------------------------------------------------------------------- /res/images/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/images/font.png -------------------------------------------------------------------------------- /res/images/gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/images/gui.png -------------------------------------------------------------------------------- /res/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/images/logo.png -------------------------------------------------------------------------------- /res/images/items.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/images/items.png -------------------------------------------------------------------------------- /res/images/level.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/images/level.png -------------------------------------------------------------------------------- /res/images/sparks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/images/sparks.png -------------------------------------------------------------------------------- /res/palettes/items.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/palettes/items.png -------------------------------------------------------------------------------- /res/sounds/craft.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/sounds/craft.raw -------------------------------------------------------------------------------- /res/sounds/pickup.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/sounds/pickup.raw -------------------------------------------------------------------------------- /res/sounds/start.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/sounds/start.raw -------------------------------------------------------------------------------- /cover/img/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/cover/img/background.png -------------------------------------------------------------------------------- /cover/img/gba-label.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/cover/img/gba-label.png -------------------------------------------------------------------------------- /res/images/entities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/images/entities.png -------------------------------------------------------------------------------- /res/palettes/sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/palettes/sprites.png -------------------------------------------------------------------------------- /res/images/level-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/images/level-light.png -------------------------------------------------------------------------------- /res/images/player-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/images/player-light.png -------------------------------------------------------------------------------- /res/palettes/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/palettes/background.png -------------------------------------------------------------------------------- /res/sounds/boss_death.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/sounds/boss_death.raw -------------------------------------------------------------------------------- /res/sounds/monster_hurt.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/sounds/monster_hurt.raw -------------------------------------------------------------------------------- /res/sounds/player_death.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/sounds/player_death.raw -------------------------------------------------------------------------------- /res/sounds/player_hurt.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/sounds/player_hurt.raw -------------------------------------------------------------------------------- /res/images/text-particle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/images/text-particle.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # binary 2 | /obj 3 | /bin 4 | 5 | # converted resources 6 | /src/res 7 | 8 | # release files 9 | /release 10 | -------------------------------------------------------------------------------- /res/images/player-lantern-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulcalien/minicraft-gba/HEAD/res/images/player-lantern-light.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libsimplegba"] 2 | path = lib/libsimplegba 3 | url = https://github.com/Vulcalien/libsimplegba.git 4 | [submodule "res2gba"] 5 | path = tools/res2gba 6 | url = https://github.com/Vulcalien/res2gba.git 7 | -------------------------------------------------------------------------------- /tools/header-checksum: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from sys import argv 4 | 5 | with open(argv[1], 'rb') as f: 6 | f.seek(0xa0) 7 | checksum = -(0x19 + sum(f.read(29))) & 0xff 8 | 9 | print(hex(checksum)) 10 | -------------------------------------------------------------------------------- /src/sounds.c: -------------------------------------------------------------------------------- 1 | #include "sound.h" 2 | 3 | u8 _sound_channel; 4 | 5 | #include "res/sounds/start.c" 6 | 7 | #include "res/sounds/pickup.c" 8 | #include "res/sounds/craft.c" 9 | 10 | #include "res/sounds/monster_hurt.c" 11 | #include "res/sounds/player_hurt.c" 12 | 13 | #include "res/sounds/player_death.c" 14 | #include "res/sounds/boss_death.c" 15 | -------------------------------------------------------------------------------- /doc/release-checklist.md: -------------------------------------------------------------------------------- 1 | Checklist of things to do before releasing a new version. 2 | 3 | - Make sure that the Copyright year in '/src/scene/prestart.c' is up to 4 | date. 5 | - Change the version number in '/src/scene/start.c'. 6 | - Change the 'Software Version' and 'Complement Check' in 'header.s'; 7 | then, make sure the ROM is valid and that the GBA BIOS accepts it. 8 | - Update the changelog file. 9 | -------------------------------------------------------------------------------- /cover/cover.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /tools/verify-save-file: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | 3 | from sys import argv 4 | 5 | with open(argv[1], 'rb') as f: 6 | checksum = 0 7 | for i in range(128 * 1024 - 4): 8 | checksum += int.from_bytes(f.read(1)) 9 | checksum &= 0xffffffff 10 | 11 | checksum_in_file = int.from_bytes(f.read(4), 'little') 12 | 13 | print('Calculated: ' + hex(checksum)) 14 | print('In save file: ' + hex(checksum_in_file)) 15 | print('Result: ' + str(checksum_in_file == checksum)) 16 | -------------------------------------------------------------------------------- /tools/color-convert: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | while True: 4 | try: 5 | col = int(input("input: "), 16) 6 | except ValueError: 7 | print('Error: invalid input') 8 | continue 9 | except KeyboardInterrupt: 10 | print() 11 | break 12 | 13 | r = (col >> 16) & 0xff 14 | g = (col >> 8) & 0xff 15 | b = col & 0xff 16 | 17 | col15 = ((b >> 3) << 10) | ((g >> 3) << 5) | (r >> 3) 18 | print('0x' + hex(col15)[2:].zfill(4)) 19 | -------------------------------------------------------------------------------- /RESOURCES.md: -------------------------------------------------------------------------------- 1 | GBATEK, an extensive documentation of the GBA hardware: 2 | https://problemkaputt.de/gbatek.htm 3 | 4 | Instructions on how to compile GBA programs on Linux using the GCC 5 | compiler: 6 | https://gist.github.com/JShorthouse/bfe49cdfad126e9163d9cb30fd3bf3c2 7 | 8 | Tonc, a guide for GBA programmers: 9 | https://www.coranac.com/tonc/text/toc.htm 10 | 11 | A guide explaining the basics of Audio Programming for the GBA, focusing 12 | on Direct Sound: 13 | https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/audio-programming-on-the-gameboy-advance-part-1-r1823/ 14 | -------------------------------------------------------------------------------- /src/options.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2024 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "options.h" 17 | 18 | struct Options options = { 19 | .keep_inventory = true 20 | }; 21 | -------------------------------------------------------------------------------- /src/scene.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "scene.h" 17 | 18 | const struct Scene *scene = NULL; 19 | 20 | u16 scene_death_timer = 0; 21 | u16 scene_win_timer = 0; 22 | -------------------------------------------------------------------------------- /include/generator.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #ifndef MINICRAFT_GENERATOR 17 | #define MINICRAFT_GENERATOR 18 | 19 | #include "minicraft.h" 20 | 21 | extern void generate_levels(void); 22 | 23 | #endif // MINICRAFT_GENERATOR 24 | -------------------------------------------------------------------------------- /include/air-wizard.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #ifndef MINICRAFT_AIR_WIZARD 17 | #define MINICRAFT_AIR_WIZARD 18 | 19 | #include "minicraft.h" 20 | 21 | extern u8 air_wizard_attack_delay; 22 | extern u8 air_wizard_attack_time; 23 | 24 | #endif // MINICRAFT_AIR_WIZARD 25 | -------------------------------------------------------------------------------- /include/options.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2024 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #ifndef MINICRAFT_OPTIONS 17 | #define MINICRAFT_OPTIONS 18 | 19 | #include "minicraft.h" 20 | 21 | struct Options { 22 | bool keep_inventory; 23 | }; 24 | 25 | extern struct Options options; 26 | 27 | #endif // MINICRAFT_OPTIONS 28 | -------------------------------------------------------------------------------- /include/performance.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2023 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #ifndef MINICRAFT_PERFORMANCE 17 | #define MINICRAFT_PERFORMANCE 18 | 19 | #include "minicraft.h" 20 | 21 | extern void performance_tick(void); 22 | extern void performance_draw(void); 23 | extern void performance_vblank(void); 24 | 25 | #endif // MINICRAFT_PERFORMANCE 26 | -------------------------------------------------------------------------------- /include/storage.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2022-2024 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #ifndef MINICRAFT_STORAGE 17 | #define MINICRAFT_STORAGE 18 | 19 | #include "minicraft.h" 20 | 21 | extern bool storage_check(void); 22 | extern bool storage_verify_checksum(void); 23 | 24 | extern void storage_srand(void); 25 | extern void storage_load_options(void); 26 | 27 | extern void storage_load(void); 28 | extern void storage_save(void); 29 | 30 | #endif // MINICRAFT_STORAGE 31 | -------------------------------------------------------------------------------- /tools/release: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -z "$1" ]; then 4 | echo "Usage: $0 " 5 | exit 6 | fi 7 | 8 | ROM_PATH="$1" 9 | ROM_NAME=$(basename "$ROM_PATH") 10 | 11 | RELEASE_DIR="release" 12 | 13 | # build ROM 14 | make clean 15 | make res 16 | make build-deps 17 | make build 18 | 19 | # create release directory and copy files into it 20 | rm -rf "$RELEASE_DIR" 21 | mkdir "$RELEASE_DIR" 22 | cp "$ROM_PATH" COPYING README.md "$RELEASE_DIR" 23 | 24 | cd "$RELEASE_DIR" 25 | 26 | # add padding to the ROM 27 | truncate -s +42 "$ROM_NAME" 28 | truncate -s %64K "$ROM_NAME" 29 | truncate -s -42 "$ROM_NAME" 30 | echo -n "Jeanne, Seigneur, est ton oeuvre splendide" >> "$ROM_NAME" 31 | 32 | # clone repository into 'source' 33 | git clone $(git remote get-url origin) source 34 | git -C source gc --aggressive --prune=now 35 | 36 | # clone all submodules inside 'source' 37 | git -C source submodule update --recursive --init 38 | git -C source submodule foreach --recursive\ 39 | "git gc --aggressive --prune=now" 40 | 41 | # generate checksums 42 | mkdir checksum 43 | md5sum $ROM_NAME > checksum/rom.md5 44 | find source -type f -exec md5sum "{}" \; > checksum/source.md5 45 | 46 | # create .zip file 47 | zip -r release.zip "$ROM_NAME" COPYING README.md source checksum 48 | -------------------------------------------------------------------------------- /include/furniture.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #ifndef MINICRAFT_FURNITURE 17 | #define MINICRAFT_FURNITURE 18 | 19 | #include "minicraft.h" 20 | 21 | #include "level.h" 22 | #include "inventory.h" 23 | 24 | #define CHEST_LIMIT (32) 25 | extern struct Inventory chest_inventories[CHEST_LIMIT]; 26 | extern u8 chest_count; 27 | 28 | extern u8 chest_opened_id; 29 | 30 | extern void furniture_take(struct entity_Data *data); 31 | 32 | extern void furniture_set_opened_chest(struct entity_Data *data); 33 | extern u8 furniture_new_chest_id(void); 34 | 35 | #endif // MINICRAFT_FURNITURE 36 | -------------------------------------------------------------------------------- /include/player.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #ifndef MINICRAFT_PLAYER 17 | #define MINICRAFT_PLAYER 18 | 19 | #include "minicraft.h" 20 | 21 | #include "inventory.h" 22 | #include "item.h" 23 | 24 | extern struct Inventory player_inventory; 25 | extern struct item_Data player_active_item; 26 | 27 | extern u8 player_stamina; 28 | extern u8 player_stamina_recharge_delay; 29 | 30 | extern u16 player_invulnerable_time; 31 | 32 | INLINE bool player_pay_stamina(u8 amount) { 33 | if(player_stamina >= amount) { 34 | player_stamina -= amount; 35 | return true; 36 | } 37 | return false; 38 | } 39 | 40 | #endif // MINICRAFT_PLAYER 41 | -------------------------------------------------------------------------------- /include/sound.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #ifndef MINICRAFT_SOUND 17 | #define MINICRAFT_SOUND 18 | 19 | #include "minicraft.h" 20 | 21 | extern u8 _sound_channel; 22 | 23 | #define SOUND_PLAY(sound) audio_play( \ 24 | _sound_channel ^= 1, \ 25 | (const i8 *) (sound), sizeof(sound) \ 26 | ) 27 | 28 | // Sound effects 29 | extern const u8 sound_start[1804]; 30 | 31 | extern const u8 sound_pickup[512]; 32 | extern const u8 sound_craft[1764]; 33 | 34 | extern const u8 sound_monster_hurt[876]; 35 | extern const u8 sound_player_hurt[888]; 36 | 37 | extern const u8 sound_player_death[13608]; 38 | extern const u8 sound_boss_death[17292]; 39 | 40 | #endif // MINICRAFT_SOUND 41 | -------------------------------------------------------------------------------- /include/util.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #ifndef MINICRAFT_UTIL 17 | #define MINICRAFT_UTIL 18 | 19 | #include "minicraft.h" 20 | 21 | INLINE void itoa(u32 number, u8 radix, char *array, u8 digits, bool zero_fill) { 22 | u32 pos = 0; 23 | for(u32 i = 0; i < digits; i++) { 24 | u32 digit = number; 25 | for(u32 j = 1; j < digits - i; j++) 26 | digit /= radix; 27 | digit %= radix; 28 | 29 | if(digit != 0 || pos != 0 || i == digits - 1 || zero_fill) { 30 | if(radix <= 10 || digit < 10) 31 | array[pos++] = '0' + digit; 32 | else 33 | array[pos++] = 'A' + (digit - 10); 34 | } 35 | } 36 | } 37 | 38 | #endif // MINICRAFT_UTIL 39 | -------------------------------------------------------------------------------- /include/minicraft.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2022-2023 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #ifndef MINICRAFT_CORE 17 | #define MINICRAFT_CORE 18 | 19 | #include 20 | 21 | #include "util.h" 22 | 23 | extern u32 tick_count; 24 | extern u32 expected_tickcount; 25 | 26 | extern u32 gametime; // measured in ticks 27 | extern u32 score; 28 | 29 | extern u8 current_level; 30 | 31 | // The behavior of right shift for negative values is 32 | // implementation-dependent, but Arithmetic Right Shift is required 33 | static_assert((-1) >> 1 == -1, "Arithmetic Right Shift is required"); 34 | 35 | typedef signed long long i64; 36 | typedef unsigned long long u64; 37 | 38 | static_assert(sizeof(u64) == 8, "size of u64 is incorrect"); 39 | static_assert(sizeof(i64) == 8, "size of i64 is incorrect"); 40 | 41 | #endif // MINICRAFT_CORE 42 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | ### Changed 5 | - Accept both *B* and *START* buttons to exit menus. 6 | - Store game images as PNG files instead of hex-code, so that they can 7 | be viewed and modified without special tools. 8 | - Use the *libsimplegba* library to handle many low-level and basic 9 | things. 10 | - Use more accurate sine and cosine functions when the Air Wizard shoots 11 | sparks. 12 | 13 | ### Fixed 14 | - Menus flickering when holding the menu's toggle buttons. 15 | 16 | ## [1.3] - 2024-02-09 17 | ### Added 18 | - Checksum verification to validate save files. 19 | - Options menu. 20 | - Respawn feature, with an option to keep the inventory. 21 | 22 | ### Changed 23 | - Use a faster algorithm to sort entities, reducing the time needed to 24 | update the screen. 25 | 26 | ### Fixed 27 | - Time displayed in pause, death and win menus, which was slightly lower 28 | than it should be. 29 | 30 | ## [1.2.1] - 2023-05-04 31 | ### Fixed 32 | - The linker script, which was ignoring some input sections. 33 | 34 | ## [1.2] - 2023-05-01 35 | ### Added 36 | - Frameskip to lower the fps when necessary. 37 | - TPS and FPS counters in the performance overlay. 38 | 39 | ### Fixed 40 | - Visual bug that caused trailing zeros to appear after the item count 41 | after losing a decimal digit. (Introduced in 1.1) 42 | - Visual bug that caused some hearts to be drawn even after the player 43 | dies in lava. 44 | 45 | ## [1.1] - 2023-03-10 46 | ### Fixed 47 | - Entities being drawn on top of the darkness layer when close to a 48 | player holding a lantern. 49 | 50 | ## [1.0] - 2022-11-19 51 | First release of the game. 52 | -------------------------------------------------------------------------------- /include/inventory.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #ifndef MINICRAFT_INVENTORY 17 | #define MINICRAFT_INVENTORY 18 | 19 | #include "minicraft.h" 20 | 21 | #include "item.h" 22 | 23 | #define INVENTORY_SIZE (128) 24 | 25 | struct Inventory { 26 | u8 size; 27 | struct item_Data items[INVENTORY_SIZE]; 28 | }; 29 | 30 | extern struct Inventory inventory; 31 | 32 | extern bool inventory_add(struct Inventory *inventory, 33 | struct item_Data *data, u8 slot); 34 | 35 | extern bool inventory_add_resource(struct Inventory *inventory, 36 | u8 item_type, u16 count, u8 slot); 37 | 38 | extern void inventory_remove(struct Inventory *inventory, 39 | struct item_Data *removed_item, u8 slot); 40 | 41 | extern void inventory_remove_resource(struct Inventory *inventory, 42 | u8 item_type, u16 count); 43 | 44 | #endif // MINICRAFT_INVENTORY 45 | -------------------------------------------------------------------------------- /src/header.s: -------------------------------------------------------------------------------- 1 | @ GBA ROM Header 2 | 3 | .section .header, "ax" 4 | 5 | .global _start 6 | .arm 7 | _start: 8 | 9 | @@@@@@@@@@@@@@@@@@@@@@ 10 | @ ROM Header @ 11 | @@@@@@@@@@@@@@@@@@@@@@ 12 | 13 | @ ROM Entry Point 14 | b start_vector 15 | 16 | @ Nintendo Logo 17 | .word 0x51aeff24, 0x21a29a69, 0x0a82843d 18 | .word 0xad09e484, 0x988b2411, 0x217f81c0, 0x19be52a3 19 | .word 0x20ce0993, 0x4a4a4610, 0xec3127f8, 0x33e8c758 20 | .word 0xbfcee382, 0x94dff485, 0xc1094bce, 0xc08a5694 21 | .word 0xfca77213, 0x734d849f, 0x619acaa3, 0x27a39758 22 | .word 0x769803fc, 0x61c71d23, 0x56ae0403, 0x008438bf 23 | .word 0xfd0ea740, 0x03fe52ff, 0xf130956f, 0x85c0fb97 24 | .word 0x2580d660, 0x03be63a9, 0xe2384e01, 0xff34a2f9 25 | .word 0x44033ebb, 0xcb900078, 0x943a1188, 0x637cc065 26 | .word 0xaf3cf087, 0x8be425d6, 0x72ac0a38, 0x07f8d421 27 | 28 | @ Game Title 29 | .ascii "MINICRAFTGBA" 30 | 31 | @ Game Code 32 | .ascii "ZMCE" 33 | 34 | @ Maker Code 35 | .byte 0x00, 0x00 36 | 37 | @ Fixed value 38 | .byte 0x96 39 | 40 | @ Main Unit Code 41 | .byte 0x00 42 | 43 | @ Device Type 44 | .byte 0x00 45 | 46 | @ Reserved (7 Bytes) 47 | .space 7, 0x00 48 | 49 | @ Software Version 50 | .byte 0x04 51 | 52 | @ Header Checksum 53 | .byte 0xb7 54 | 55 | @ Reserved (2 Bytes) 56 | .space 2, 0x00 57 | 58 | @@@@@@@@@@@@@@@@@@@@@@@@@@ 59 | @ ROM Header End @ 60 | @@@@@@@@@@@@@@@@@@@@@@@@@@ 61 | 62 | @ Cart Backup ID 63 | .ascii "FLASH1M_Vnnn" 64 | 65 | .end 66 | -------------------------------------------------------------------------------- /include/screen.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2022-2023 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #ifndef MINICRAFT_SCREEN 17 | #define MINICRAFT_SCREEN 18 | 19 | #include "minicraft.h" 20 | 21 | #define BG0_TILEMAP display_screenblock(16) 22 | #define BG1_TILEMAP display_screenblock(17) 23 | #define BG2_TILEMAP display_screenblock(18) 24 | #define BG3_TILEMAP display_screenblock(19) 25 | 26 | extern void screen_init(void); 27 | 28 | extern void screen_write(const char *text, u8 palette, u32 x, u32 y); 29 | extern void screen_draw_frame(const char *title, u32 x, u32 y, u32 w, u32 h); 30 | extern void screen_write_time(u32 ticks, u8 palette, u32 x, u32 y); 31 | 32 | #define SCREEN_WRITE_NUMBER(number, radix, digits, zero_fill, palette, x, y)\ 33 | do {\ 34 | char text[(digits) + 1] = { 0 };\ 35 | itoa((number), (radix), text, (digits), (zero_fill));\ 36 | screen_write(text, (palette), (x), (y));\ 37 | } while (0) 38 | 39 | extern void screen_set_bg_palette_color(u8 palette, u8 index, u16 color); 40 | extern void screen_load_active_item_palette(u8 palette); 41 | 42 | extern void screen_update_level_specific(void); 43 | 44 | #endif // MINICRAFT_SCREEN 45 | -------------------------------------------------------------------------------- /include/crafting.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #ifndef MINICRAFT_CRAFTING 17 | #define MINICRAFT_CRAFTING 18 | 19 | #include "minicraft.h" 20 | 21 | #include "inventory.h" 22 | 23 | #define CRAFTING_MAX_REQUIRED (3) 24 | 25 | struct crafting_Recipe { 26 | struct { 27 | u8 items[CRAFTING_MAX_REQUIRED]; 28 | u8 count[CRAFTING_MAX_REQUIRED]; 29 | } required; 30 | 31 | u8 result; 32 | 33 | u8 tool_level; 34 | }; 35 | 36 | #define WORKBENCH_RECIPES (16) 37 | #define FURNACE_RECIPES (3) 38 | #define OVEN_RECIPES (1) 39 | #define ANVIL_RECIPES (15) 40 | 41 | extern const struct crafting_Recipe workbench_recipes[WORKBENCH_RECIPES]; 42 | extern const struct crafting_Recipe furnace_recipes[FURNACE_RECIPES]; 43 | extern const struct crafting_Recipe oven_recipes[OVEN_RECIPES]; 44 | extern const struct crafting_Recipe anvil_recipes[ANVIL_RECIPES]; 45 | 46 | extern u8 crafting_current_recipes_size; 47 | extern const struct crafting_Recipe *crafting_current_recipes; 48 | 49 | extern bool crafting_craft(struct Inventory *inventory, 50 | const struct crafting_Recipe *recipe); 51 | 52 | #endif // MINICRAFT_CRAFTING 53 | -------------------------------------------------------------------------------- /include/mob.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #ifndef MINICRAFT_MOB 17 | #define MINICRAFT_MOB 18 | 19 | #include "minicraft.h" 20 | 21 | #include "level.h" 22 | #include "entity.h" 23 | 24 | struct mob_Data { 25 | u16 hp; 26 | u8 dir; 27 | 28 | struct { 29 | u8 val : 6; 30 | u8 dir : 2; 31 | } knockback; 32 | 33 | u8 walk_dist; 34 | u8 hurt_time; 35 | 36 | u8 data[2]; 37 | }; 38 | 39 | static_assert(sizeof(struct mob_Data) == 8, "struct mob_Data: wrong size"); 40 | 41 | extern void mob_tick(struct Level *level, struct entity_Data *data); 42 | 43 | extern bool mob_move(struct Level *level, struct entity_Data *data, 44 | i32 xm, i32 ym); 45 | 46 | extern void mob_hurt(struct Level *level, struct entity_Data *data, 47 | u8 damage, u8 knockback_dir); 48 | 49 | extern void mob_zombie_die(struct Level *level, struct entity_Data *data); 50 | extern void mob_slime_die(struct Level *level, struct entity_Data *data); 51 | extern void mob_air_wizard_die(struct Level *level, struct entity_Data *data); 52 | extern void mob_player_die(struct Level *level, struct entity_Data *data); 53 | 54 | #endif // MINICRAFT_MOB 55 | -------------------------------------------------------------------------------- /src/scene/win.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "scene.h" 17 | 18 | #include "screen.h" 19 | 20 | static u8 win_time; 21 | 22 | THUMB 23 | static void win_init(u8 flags) { 24 | win_time = 0; 25 | } 26 | 27 | THUMB 28 | static void win_tick(void) { 29 | win_time++; 30 | if(win_time > 60) { 31 | if(input_press(KEY_A) || input_press(KEY_B)) 32 | set_scene(&scene_game, 1); 33 | } 34 | } 35 | 36 | THUMB 37 | static void win_draw(void) { 38 | const u8 win_x = 6; 39 | const u8 win_y = 5; 40 | const u8 win_w = 18; 41 | const u8 win_h = 7; 42 | 43 | screen_draw_frame("", win_x, win_y, win_w, win_h); 44 | screen_write("YOU WON! YAY!", 6, win_x + 1, win_y + 1); 45 | 46 | screen_write("TIME:", 6, win_x + 1, win_y + 2); 47 | screen_write_time(gametime, 10, win_x + 6, win_y + 2); 48 | 49 | screen_write("SCORE:", 6, win_x + 1, win_y + 3); 50 | SCREEN_WRITE_NUMBER(score, 10, 10, false, 10, win_x + 7, win_y + 3); 51 | 52 | screen_write("PRESS A TO WIN", 8, win_x + 1, win_y + 5); 53 | } 54 | 55 | const struct Scene scene_win = { 56 | .init = win_init, 57 | 58 | .tick = win_tick, 59 | .draw = win_draw 60 | }; 61 | -------------------------------------------------------------------------------- /src/scene/instructions.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "scene.h" 17 | 18 | #include "screen.h" 19 | 20 | THUMB 21 | static void instructions_tick(void) { 22 | if(input_press(KEY_B) || input_press(KEY_START)) 23 | set_scene(&scene_start, 0); 24 | } 25 | 26 | THUMB 27 | static void instructions_draw(void) { 28 | // clear the screen 29 | for(u32 y = 0; y < 20; y++) 30 | for(u32 x = 0; x < 30; x++) 31 | BG3_TILEMAP[x + y * 32] = 32; 32 | 33 | screen_write("HOW TO PLAY", 0, 9, 1); 34 | 35 | screen_write( 36 | "MOVE YOUR CHARACTER USING\n" 37 | "THE DPAD.\n" 38 | "\n" 39 | "PRESS A TO ATTACK AND TO USE\n" 40 | "ITEMS AND B TO OPEN THE\n" 41 | "INVENTORY.\n" 42 | "\n" 43 | "SELECT AN ITEM IN THE\n" 44 | "INVENTORY TO EQUIP IT.\n" 45 | "\n" 46 | "KILL THE AIR WIZARD TO WIN\n" 47 | "THE GAME!\n" 48 | "\n" 49 | "\n" 50 | "PRESS START TO PAUSE AND TO\n" 51 | "SAVE THE GAME.", 52 | 2, 1, 3 53 | ); 54 | } 55 | 56 | const struct Scene scene_instructions = { 57 | .tick = instructions_tick, 58 | .draw = instructions_draw 59 | }; 60 | -------------------------------------------------------------------------------- /include/scene.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #ifndef MINICRAFT_SCENE 17 | #define MINICRAFT_SCENE 18 | 19 | #include "minicraft.h" 20 | 21 | struct Scene { 22 | void (*init)(u8 flags); 23 | void (*tick)(void); 24 | void (*draw)(void); 25 | }; 26 | 27 | extern const struct Scene *scene; 28 | 29 | extern const struct Scene scene_prestart; 30 | 31 | extern const struct Scene scene_start; 32 | extern const struct Scene scene_instructions; 33 | extern const struct Scene scene_about; 34 | extern const struct Scene scene_options; 35 | 36 | extern const struct Scene scene_game; 37 | extern const struct Scene scene_transition; 38 | 39 | extern const struct Scene scene_inventory; 40 | extern const struct Scene scene_chest; 41 | extern const struct Scene scene_crafting; 42 | 43 | extern const struct Scene scene_pause; 44 | 45 | extern const struct Scene scene_death; 46 | extern const struct Scene scene_win; 47 | 48 | extern u16 scene_death_timer; 49 | extern u16 scene_win_timer; 50 | 51 | INLINE void set_scene(const struct Scene *new_scene, u8 init_flags) { 52 | scene = new_scene; 53 | 54 | if((init_flags & 1) && scene->init) 55 | scene->init(init_flags); 56 | } 57 | 58 | #endif // MINICRAFT_SCENE 59 | -------------------------------------------------------------------------------- /src/draw/sort_entities.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2023 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "level.h" 17 | 18 | // Counting Sort implementation 19 | 20 | #define VALUE_RANGE (DISPLAY_HEIGHT + 16) 21 | 22 | static u8 count_array[VALUE_RANGE]; 23 | static u8 result_array[128]; 24 | 25 | #define VAL(id) (level->entities[entities[(id)]].y) 26 | 27 | IWRAM_SECTION 28 | static u8 *sort_entities(struct Level *level, u8 *entities, u32 n) { 29 | if(n <= 1) 30 | return entities; 31 | 32 | // clear count_array 33 | for(u32 i = 0; i < VALUE_RANGE; i++) 34 | count_array[i] = 0; 35 | 36 | // find the minimum value 37 | u16 min = VAL(0); 38 | for(u32 i = 1; i < n; i++) 39 | if(min > VAL(i)) 40 | min = VAL(i); 41 | 42 | // count the values 43 | for(u32 i = 0; i < n; i++) 44 | count_array[VAL(i) - min]++; 45 | 46 | // calculate indexes 47 | for(u32 i = 1; i < VALUE_RANGE; i++) 48 | count_array[i] += count_array[i - 1]; 49 | 50 | // set values in result_array 51 | for(i32 i = n - 1; i >= 0; i--) { 52 | u8 *index = &count_array[VAL(i) - min]; 53 | 54 | (*index)--; 55 | result_array[*index] = entities[i]; 56 | } 57 | 58 | return result_array; 59 | } 60 | -------------------------------------------------------------------------------- /src/scene/transition.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "scene.h" 17 | 18 | #include "screen.h" 19 | 20 | static u8 transition_time; 21 | static u8 old_level; 22 | 23 | THUMB 24 | static void transition_init(u8 flags) { 25 | transition_time = 0; 26 | old_level = current_level; 27 | } 28 | 29 | THUMB 30 | static void transition_tick(void) { 31 | gametime++; 32 | 33 | transition_time++; 34 | if(transition_time == 17) 35 | scene_game.init(3); 36 | else if(transition_time == 31) 37 | set_scene(&scene_game, 1); 38 | } 39 | 40 | IWRAM_SECTION 41 | static void transition_draw(void) { 42 | scene_game.draw(); 43 | 44 | for(u32 y = 0; y < 20; y++) { 45 | for(u32 x = 0; x < 30; x++) { 46 | i32 dd = (y + x % 2 * 2 + x / 3) - transition_time * 2; 47 | 48 | u16 ydraw = old_level < current_level ? y : 19 - y; 49 | if(dd > -33 && dd < 0) 50 | BG3_TILEMAP[x + ydraw * 32] = 32; 51 | else if(ydraw < 18) 52 | BG3_TILEMAP[x + ydraw * 32] = 0; 53 | } 54 | } 55 | } 56 | 57 | const struct Scene scene_transition = { 58 | .init = transition_init, 59 | 60 | .tick = transition_tick, 61 | .draw = transition_draw 62 | }; 63 | -------------------------------------------------------------------------------- /src/scene/about.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022-2023 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "scene.h" 17 | 18 | #include "screen.h" 19 | 20 | THUMB 21 | static void about_tick(void) { 22 | if(input_press(KEY_B) || input_press(KEY_START)) 23 | set_scene(&scene_start, 0); 24 | } 25 | 26 | THUMB 27 | static void about_draw(void) { 28 | // clear the screen 29 | for(u32 y = 0; y < 20; y++) 30 | for(u32 x = 0; x < 30; x++) 31 | BG3_TILEMAP[x + y * 32] = 32; 32 | 33 | screen_write("ABOUT MINICRAFT FOR GBA", 0, 3, 1); 34 | 35 | screen_write( 36 | "THIS GBA DEMAKE OF MINICRAFT\n" 37 | "WAS MADE BY VULCALIEN.\n" 38 | "\n" 39 | "I'VE ALWAYS LOVED MINICRAFT.\n" 40 | "IT HELPED ME LEARN TO CODE.", 41 | 2, 1, 3 42 | ); 43 | 44 | screen_write("ABOUT MINICRAFT", 0, 7, 9); 45 | screen_write("(NOTCH'S WORDS)", 2, 7, 10); 46 | 47 | screen_write( 48 | "MINICRAFT WAS MADE BY MARKUS\n" 49 | "PERSSON FOR THE 22'ND LUDUM\n" 50 | "DARE COMPETITION IN DECEMBER\n" 51 | "2011.\n" 52 | "\n" 53 | "IT IS DEDICATED TO MY FATHER.\n" 54 | "<3", 55 | 2, 1, 12 56 | ); 57 | } 58 | 59 | const struct Scene scene_about = { 60 | .tick = about_tick, 61 | .draw = about_draw 62 | }; 63 | -------------------------------------------------------------------------------- /src/minicraft.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022-2023 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "minicraft.h" 17 | 18 | #include "screen.h" 19 | #include "scene.h" 20 | #include "performance.h" 21 | 22 | u32 tick_count = 0; 23 | u32 expected_tickcount = 0; 24 | 25 | u32 gametime; 26 | u32 score; 27 | 28 | u8 current_level; 29 | 30 | static inline void tick(void) { 31 | audio_update(); 32 | input_update(); 33 | scene->tick(); 34 | 35 | performance_tick(); 36 | 37 | tick_count++; 38 | } 39 | 40 | static inline void draw(void) { 41 | scene->draw(); 42 | 43 | performance_draw(); 44 | } 45 | 46 | IWRAM_SECTION 47 | static void vblank(void) { 48 | expected_tickcount++; 49 | performance_vblank(); 50 | } 51 | 52 | void AgbMain(void) { 53 | // enable VBlank interrupt 54 | interrupt_toggle(IRQ_VBLANK, true); 55 | interrupt_set_isr(IRQ_VBLANK, vblank); 56 | 57 | backup_init(BACKUP_FLASH); 58 | audio_init(AUDIO_BASIC); 59 | input_init(30, 2); 60 | screen_init(); 61 | 62 | set_scene(&scene_prestart, 0); 63 | 64 | while(true) { 65 | tick(); 66 | 67 | // if necessary, skip a frame 68 | if(tick_count < expected_tickcount) { 69 | tick(); 70 | expected_tickcount = tick_count; // drop any remaining ticks 71 | } 72 | 73 | interrupt_wait(IRQ_VBLANK); 74 | draw(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/scene/prestart.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022-2023 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "scene.h" 17 | 18 | #include "screen.h" 19 | 20 | // considering that checksum verification takes around 40 ticks, 21 | // the text will be fully visible for about two seconds. 22 | #define ADDITIONAL_DELAY (80) 23 | 24 | static u16 counter = 0; 25 | 26 | THUMB 27 | static void prestart_tick(void) { 28 | counter++; 29 | 30 | if(counter == 0x20 + ADDITIONAL_DELAY) 31 | set_scene(&scene_start, 1); 32 | } 33 | 34 | THUMB 35 | static void prestart_draw(void) { 36 | if(counter < 0x20) 37 | display_brighten(NULL, (0x20 - counter) / 2); 38 | else 39 | display_disable_effects(); 40 | 41 | screen_write( 42 | "MINICRAFT WAS MADE BY MARKUS\n" 43 | "PERSSON FOR THE 22'ND LUDUM\n" 44 | "DARE COMPETITION IN DECEMBER\n" 45 | "2011.\n" 46 | "\n" 47 | "THIS GBA DEMAKE OF THE GAME\n" 48 | "WAS MADE BY VULCALIEN.\n" 49 | "\n" 50 | "THE ARTWORK AND SOUNDS WERE\n" 51 | "MADE BY MARKUS PERSSON.\n" 52 | "\n" 53 | "\n" 54 | "COPYRIGHT 2025 VULCALIEN\n" 55 | "\n" 56 | "THIS IS FREE SOFTWARE\n" 57 | "RELEASED UNDER THE\n" 58 | "GNU GENERAL PUBLIC LICENSE\n" 59 | "EITHER VERSION 3 OR LATER.", 60 | 0, 1, 1 61 | ); 62 | } 63 | 64 | const struct Scene scene_prestart = { 65 | .tick = prestart_tick, 66 | .draw = prestart_draw 67 | }; 68 | -------------------------------------------------------------------------------- /src/entity/smash-particle.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "entity.h" 17 | 18 | #include "sound.h" 19 | 20 | struct smash_Data { 21 | u8 time; 22 | 23 | u8 unused[7]; 24 | }; 25 | 26 | static_assert(sizeof(struct smash_Data) == 8, "struct smash_Data: wrong size"); 27 | 28 | THUMB 29 | void entity_add_smash_particle(struct Level *level, u8 xt, u8 yt) { 30 | SOUND_PLAY(sound_monster_hurt); 31 | 32 | u8 entity_id = level_new_entity(level, SMASH_PARTICLE_ENTITY); 33 | if(entity_id >= ENTITY_LIMIT) 34 | return; 35 | 36 | struct entity_Data *data = &level->entities[entity_id]; 37 | 38 | data->x = (xt << 4) + 8; 39 | data->y = (yt << 4) + 8; 40 | 41 | level_add_entity(level, entity_id); 42 | } 43 | 44 | ETICK(smash_particle_tick) { 45 | struct smash_Data *smash_data = (struct smash_Data *) &data->data; 46 | 47 | smash_data->time++; 48 | if(smash_data->time > 10) 49 | data->should_remove = true; 50 | } 51 | 52 | EDRAW(smash_particle_draw) { 53 | sprite_config(used_sprites, &(struct Sprite) { 54 | .x = data->x - 8 - level_x_offset, 55 | .y = data->y - 8 - level_y_offset, 56 | 57 | .priority = 2, 58 | 59 | .size = SPRITE_SIZE_16x16, 60 | .flip = 0, 61 | 62 | .tile = 172, 63 | .palette = 0 64 | }); 65 | return 1; 66 | } 67 | 68 | const struct Entity smash_particle_entity = { 69 | .tick = smash_particle_tick, 70 | .draw = smash_particle_draw 71 | }; 72 | -------------------------------------------------------------------------------- /include/tile.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #ifndef MINICRAFT_TILE 17 | #define MINICRAFT_TILE 18 | 19 | #include "minicraft.h" 20 | 21 | #include "level.h" 22 | #include "item.h" 23 | 24 | #define TILE_TYPES (22) 25 | 26 | #define GRASS_TILE (0) 27 | #define ROCK_TILE (1) 28 | #define LIQUID_TILE (2) 29 | #define FLOWER_TILE (3) 30 | #define TREE_TILE (4) 31 | #define DIRT_TILE (5) 32 | #define SAND_TILE (6) 33 | #define CACTUS_TILE (7) 34 | #define HOLE_TILE (8) 35 | #define TREE_SAPLING_TILE (9) 36 | #define CACTUS_SAPLING_TILE (10) 37 | #define FARMLAND_TILE (11) 38 | #define WHEAT_TILE (12) 39 | #define STAIRS_DOWN_TILE (13) 40 | #define STAIRS_UP_TILE (14) 41 | #define INFINITE_FALL_TILE (15) 42 | #define CLOUD_TILE (16) 43 | #define HARD_ROCK_TILE (17) 44 | #define IRON_ORE_TILE (18) 45 | #define GOLD_ORE_TILE (19) 46 | #define GEM_ORE_TILE (20) 47 | #define CLOUD_CACTUS_TILE (21) 48 | 49 | struct Tile { 50 | bool is_solid; 51 | u8 may_pass; 52 | 53 | u8 touch_damage; 54 | 55 | void (*stepped_on)(struct Level *level, u32 xt, u32 yt, 56 | struct entity_Data *entity_data); 57 | 58 | void (*interact)(struct Level *level, u32 xt, u32 yt, 59 | struct item_Data *item); 60 | 61 | struct { 62 | bool grass; 63 | bool sand; 64 | bool liquid; 65 | } connects_to; 66 | }; 67 | 68 | extern const struct Tile tile_list[TILE_TYPES]; 69 | 70 | #endif // MINICRAFT_TILE 71 | -------------------------------------------------------------------------------- /src/performance.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2023 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "performance.h" 17 | 18 | #include "level.h" 19 | #include "entity.h" 20 | #include "screen.h" 21 | 22 | #define OAM ((vu16 *) 0x07000000) 23 | 24 | static bool show_performance = false; 25 | static u16 tick_vcount; 26 | static u16 draw_vcount; 27 | 28 | static u16 ticks = 0, frames = 0; 29 | static u16 tps = 0, fps = 0; 30 | 31 | void performance_tick(void) { 32 | tick_vcount = display_vcount(); 33 | ticks++; 34 | 35 | if(input_down(KEY_L) && input_down(KEY_R) && 36 | input_press(KEY_SELECT)) { 37 | show_performance = !show_performance; 38 | } 39 | } 40 | 41 | void performance_draw(void) { 42 | draw_vcount = display_vcount(); 43 | frames++; 44 | 45 | if(!show_performance || tick_count % 15 != 0) 46 | return; 47 | 48 | SCREEN_WRITE_NUMBER(tick_vcount, 16, 2, true, 0, 0, 0); 49 | SCREEN_WRITE_NUMBER(draw_vcount, 16, 2, true, 0, 0, 1); 50 | 51 | SCREEN_WRITE_NUMBER(tps, 10, 2, true, 0, 0, 3); 52 | SCREEN_WRITE_NUMBER(fps, 10, 2, true, 0, 0, 4); 53 | 54 | // count entities 55 | u32 entity_count = 0; 56 | for(u32 i = 0; i < ENTITY_LIMIT; i++) 57 | if(levels[current_level].entities[i].type < ENTITY_TYPES) 58 | entity_count++; 59 | 60 | // count sprites 61 | u32 sprite_count = 0; 62 | for(u32 i = 0; i < 128; i++) 63 | if((OAM[i * 4] & (1 << 9)) == 0) 64 | sprite_count++; 65 | 66 | SCREEN_WRITE_NUMBER(entity_count, 16, 2, true, 0, 28, 0); 67 | SCREEN_WRITE_NUMBER(sprite_count, 16, 2, true, 0, 28, 1); 68 | } 69 | 70 | IWRAM_SECTION 71 | void performance_vblank(void) { 72 | static u32 vblanks = 0; 73 | vblanks++; 74 | 75 | if(vblanks == 60) { 76 | vblanks = 0; 77 | 78 | tps = ticks; 79 | fps = frames; 80 | 81 | ticks = 0; 82 | frames = 0; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minicraft for GBA 2 | 3 | This is a demake of Minicraft by Markus Persson (aka Notch), a game made 4 | for Ludum Dare 22 in two days. 5 | 6 | My aim is to make a version that is as close as possible to the 7 | original. Of course, the GBA has some hardware limitations but, since 8 | the game isn't too complex, I could port it without too many problems. 9 | When the limitations were impossible to overcome, I had to hack a few 10 | things, change others, put limits and so on. 11 | 12 | To improve the gameplay experience, I also added these features: 13 | - a pause menu 14 | - saving and loading the world 15 | - a way to respawn, with or without keeping the inventory 16 | 17 | ## Differences due to Hardware limitations 18 | | | Original | GBA Demake | 19 | | -------------- | :------: | :--------: | 20 | | World size | 128x128 | 112x112 | 21 | | Entity limit | ∞ | 255 | 22 | | Chest limit | ∞ | 32 | 23 | | Inventory size | ∞ | 128 | 24 | 25 | ### Game Light 26 | The light system is completely different, both visually and in how it 27 | works. The original Minicraft calculates the light for **each pixel**: 28 | that is, for various reasons, impractical to do on the GBA. 29 | 30 | So I had to find another way, and the best one seemed to use tiles, 31 | because the GBA is very good at handling them. That gives light a 32 | 'blocky' look, but it seems acceptable. 33 | 34 | ## Running 35 | Download or build the ROM (`.gba` extension). Then open it with your GBA 36 | emulator of choice. If you don't have one, I highly recommend mGBA. 37 | 38 | If you have any trouble with the save files, try to manually set the 39 | save format to `128 KB Flash ROM`. 40 | 41 | ### Performance Overlay 42 | By holding the `L` and `R` buttons down and then pressing `Select`, the 43 | performance overlay is enabled.\ 44 | Four hexadecimal values and two decimal values are written at the top of 45 | the screen: 46 | ``` 47 | FF <--- vcount after 'tick' entity count ---> FF 48 | FF <--- vcount after 'draw' sprite count ---> FF 49 | 50 | 60 <--- tps (ticks per second) 51 | 60 <--- fps (frames per second) 52 | ``` 53 | 54 | ## Building 55 | To build the game, I use the `Makefile` present in the files. 56 | You will need the gcc-arm-none-eabi compiler and Python 3. 57 | 58 | First, run `make res` to convert the necessary resources. Then, run 59 | `make` to build the ROM. 60 | 61 | ## License 62 | The original game `Minicraft` was made by Markus Persson in 2011. 63 | I do not own it, nor am I affiliated to it. 64 | 65 | This demake of the game is released under the GNU General Public 66 | License, either version 3 of the License or any later version. 67 | -------------------------------------------------------------------------------- /src/inventory.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "inventory.h" 17 | 18 | bool inventory_add(struct Inventory *inventory, 19 | struct item_Data *data, u8 slot) { 20 | if(inventory->size >= INVENTORY_SIZE) 21 | return false; 22 | 23 | // shift items down 24 | for(i32 i = inventory->size - 1; i >= slot; i--) 25 | inventory->items[i + 1] = inventory->items[i]; 26 | 27 | inventory->items[slot] = *data; 28 | 29 | inventory->size++; 30 | return true; 31 | } 32 | 33 | bool inventory_add_resource(struct Inventory *inventory, 34 | u8 item_type, u16 count, u8 slot) { 35 | // try to increase the count of an already present item 36 | for(u32 i = 0; i < inventory->size; i++) { 37 | struct item_Data *data = &inventory->items[i]; 38 | 39 | if(data->type == item_type) { 40 | data->count += count; 41 | 42 | return true; 43 | } 44 | } 45 | 46 | // the resource was not present: add a new item 47 | struct item_Data data = { .type = item_type, .count = count }; 48 | return inventory_add(inventory, &data, slot); 49 | } 50 | 51 | void inventory_remove(struct Inventory *inventory, 52 | struct item_Data *removed_item, u8 slot) { 53 | *removed_item = inventory->items[slot]; 54 | 55 | // shift items up 56 | for(u32 i = slot; i < inventory->size - 1; i++) 57 | inventory->items[i] = inventory->items[i + 1]; 58 | 59 | inventory->size--; 60 | } 61 | 62 | void inventory_remove_resource(struct Inventory *inventory, 63 | u8 item_type, u16 count) { 64 | for(u32 i = 0; i < inventory->size; i++) { 65 | struct item_Data *data = &inventory->items[i]; 66 | 67 | if(data->type == item_type && data->count >= count) { 68 | data->count -= count; 69 | if(data->count == 0) { 70 | struct item_Data removed; 71 | inventory_remove(inventory, &removed, i); 72 | } 73 | 74 | break; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/scene/options.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2024 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "scene.h" 17 | 18 | #include "options.h" 19 | #include "screen.h" 20 | 21 | #define KEEP_INVENTORY (0) 22 | #define EXIT (1) 23 | 24 | static i8 selected; 25 | 26 | THUMB 27 | static void options_init(u8 flags) { 28 | selected = 0; 29 | } 30 | 31 | THUMB 32 | static void options_tick(void) { 33 | if(input_repeat(KEY_UP)) 34 | selected--; 35 | if(input_repeat(KEY_DOWN)) 36 | selected++; 37 | 38 | if(selected < 0) 39 | selected = EXIT; 40 | else if(selected > EXIT) 41 | selected = 0; 42 | 43 | if(input_press(KEY_B) || input_press(KEY_START)) 44 | set_scene(&scene_start, 0); 45 | 46 | if(input_press(KEY_A)) { 47 | switch(selected) { 48 | case KEEP_INVENTORY: 49 | options.keep_inventory = !options.keep_inventory; 50 | break; 51 | 52 | case EXIT: 53 | set_scene(&scene_start, 0); 54 | break; 55 | } 56 | } 57 | } 58 | 59 | #define WRITE_OPTION(text, id, x, y) do {\ 60 | screen_write((text), selected == (id) ? 0 : 2, (x), (y));\ 61 | if(selected == (id)) {\ 62 | if((id) != EXIT) {\ 63 | screen_write(">", 0, 1, (y));\ 64 | screen_write("<", 0, 28, (y));\ 65 | } else {\ 66 | screen_write(">", 0, 11, (y));\ 67 | screen_write("<", 0, 18, (y));\ 68 | }\ 69 | }\ 70 | } while(0) 71 | 72 | THUMB 73 | static void options_draw(void) { 74 | // clear the screen 75 | for(u32 y = 0; y < 20; y++) 76 | for(u32 x = 0; x < 30; x++) 77 | BG3_TILEMAP[x + y * 32] = 32; 78 | 79 | screen_write("GAME OPTIONS", 0, 9, 1); 80 | 81 | WRITE_OPTION("KEEP INVENTORY", KEEP_INVENTORY, 3, 4); 82 | if(options.keep_inventory) 83 | screen_write("YES", 4, 24, 4); 84 | else 85 | screen_write("NO", 5, 25, 4); 86 | 87 | // write 'EXIT' 88 | WRITE_OPTION("EXIT", EXIT, 13, 18); 89 | } 90 | 91 | const struct Scene scene_options = { 92 | .init = options_init, 93 | 94 | .tick = options_tick, 95 | .draw = options_draw 96 | }; 97 | -------------------------------------------------------------------------------- /include/level.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #ifndef MINICRAFT_LEVEL 17 | #define MINICRAFT_LEVEL 18 | 19 | #include "minicraft.h" 20 | 21 | #define LEVEL_W 112 22 | #define LEVEL_H 112 23 | #define LEVEL_SIZE (LEVEL_W * LEVEL_H) 24 | 25 | #define DESPAWN_DISTANCE ((32 * 16) * (32 * 16)) 26 | 27 | #define ENTITY_LIMIT (255) 28 | 29 | struct entity_Data { 30 | u8 type; 31 | 32 | u8 should_remove : 1; 33 | u8 solid_id : 7; 34 | 35 | u16 x; 36 | u16 y; 37 | 38 | u8 data[8]; 39 | }; 40 | 41 | static_assert(sizeof(struct entity_Data) == 14, "struct entity_Data: wrong size"); 42 | 43 | struct Level { 44 | u8 tiles[LEVEL_SIZE]; 45 | u8 data[LEVEL_SIZE]; 46 | 47 | struct entity_Data entities[ENTITY_LIMIT]; 48 | }; 49 | 50 | extern struct Level levels[5]; 51 | 52 | #define SOLID_ENTITIES_IN_TILE (8) 53 | extern u8 level_solid_entities[LEVEL_SIZE][SOLID_ENTITIES_IN_TILE]; 54 | 55 | extern u32 level_x_offset; 56 | extern u32 level_y_offset; 57 | 58 | extern void level_load(struct Level *level); 59 | 60 | extern void level_tick(struct Level *level); 61 | extern void level_draw(struct Level *level); 62 | 63 | #define LEVEL_GET_TILE(level, xt, yt)\ 64 | (((xt) < 0 || (xt) >= LEVEL_W || (yt) < 0 || (yt) >= LEVEL_H) ?\ 65 | ROCK_TILE : (level)->tiles[(xt) + (yt) * LEVEL_W]) 66 | #define LEVEL_SET_TILE(level, xt, yt, val, data_val) do {\ 67 | if((xt) >= 0 && (xt) < LEVEL_W && (yt) >= 0 && (yt) < LEVEL_H) {\ 68 | (level)->tiles[(xt) + (yt) * LEVEL_W] = (val);\ 69 | (level)->data[(xt) + (yt) * LEVEL_W] = (data_val);\ 70 | }\ 71 | } while(0) 72 | 73 | // returns 'struct Tile *' instead of the ID 74 | #define LEVEL_GET_TILE_S(level, xt, yt)\ 75 | (&tile_list[LEVEL_GET_TILE((level), (xt), (yt))]) 76 | 77 | #define LEVEL_GET_DATA(level, xt, yt)\ 78 | (((xt) < 0 || (xt) >= LEVEL_W || (yt) < 0 || (yt) >= LEVEL_H) ?\ 79 | 0 : (level)->data[(xt) + (yt) * LEVEL_W]) 80 | #define LEVEL_SET_DATA(level, xt, yt, val) do {\ 81 | if((xt) >= 0 && (xt) < LEVEL_W && (yt) >= 0 && (yt) < LEVEL_H)\ 82 | (level)->data[(xt) + (yt) * LEVEL_W] = (val);\ 83 | } while(0) 84 | 85 | extern u8 level_new_entity(struct Level *level, u8 type); 86 | 87 | extern void level_add_entity(struct Level *level, u8 entity_id); 88 | 89 | extern void level_try_spawn(struct Level *level, u8 level_index); 90 | 91 | #endif // MINICRAFT_LEVEL 92 | -------------------------------------------------------------------------------- /src/scene/pause.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "scene.h" 17 | 18 | #include "screen.h" 19 | #include "storage.h" 20 | #include "sound.h" 21 | 22 | static bool ask_overwrite; 23 | static u8 selected_answer; 24 | 25 | static bool should_save = false; 26 | 27 | THUMB 28 | static void pause_init(u8 flags) { 29 | ask_overwrite = false; 30 | } 31 | 32 | THUMB 33 | static void pause_tick(void) { 34 | if(should_save) { 35 | storage_save(); 36 | SOUND_PLAY(sound_start); 37 | 38 | should_save = false; 39 | ask_overwrite = false; 40 | } 41 | 42 | if(input_press(KEY_START)) 43 | set_scene(&scene_game, 1); 44 | 45 | if(input_repeat(KEY_A)) { 46 | if(ask_overwrite) { 47 | if(selected_answer == 1) 48 | ask_overwrite = false; 49 | else 50 | should_save = true; 51 | } else if(storage_check()) { 52 | ask_overwrite = true; 53 | selected_answer = 0; 54 | } else { 55 | should_save = true; 56 | } 57 | } 58 | 59 | if(input_repeat(KEY_B)) 60 | ask_overwrite = false; 61 | 62 | if(ask_overwrite && (input_repeat(KEY_LEFT) || input_repeat(KEY_RIGHT))) 63 | selected_answer ^= 1; 64 | } 65 | 66 | #define WRITE_OPTION(text, id, x, y) do {\ 67 | screen_write((text), 7 - (selected_answer == id), (x), (y));\ 68 | \ 69 | if(selected_answer == id) {\ 70 | screen_write(">", 6, (x) - 2, (y));\ 71 | screen_write("<", 6, (x) + sizeof(text), (y));\ 72 | }\ 73 | } while(0) 74 | 75 | THUMB 76 | static void pause_draw(void) { 77 | const u8 pause_x = 6; 78 | const u8 pause_y = 5; 79 | const u8 pause_w = 18; 80 | const u8 pause_h = 8; 81 | 82 | screen_draw_frame("PAUSE", pause_x, pause_y, pause_w, pause_h); 83 | 84 | screen_write("TIME:", 6, pause_x + 1, pause_y + 1); 85 | screen_write_time(gametime, 10, pause_x + 6, pause_y + 1); 86 | 87 | screen_write("SCORE:", 6, pause_x + 1, pause_y + 2); 88 | SCREEN_WRITE_NUMBER(score, 10, 10, false, 10, pause_x + 7, pause_y + 2); 89 | 90 | if(should_save) { 91 | screen_write("SAVING...", 6, pause_x + 5, pause_y + 5); 92 | } else if(ask_overwrite) { 93 | screen_write("OVERWRITE FILE?", 6, pause_x + 1, pause_y + 4); 94 | 95 | WRITE_OPTION("YES", 0, pause_x + 4, pause_y + 6); 96 | WRITE_OPTION("NO", 1, pause_x + 12, pause_y + 6); 97 | } else { 98 | screen_write("> SAVE GAME <", 6, pause_x + 2, pause_y + 5); 99 | } 100 | } 101 | 102 | const struct Scene scene_pause = { 103 | .init = pause_init, 104 | 105 | .tick = pause_tick, 106 | .draw = pause_draw 107 | }; 108 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Vulcalien's GBA Makefile 2 | 3 | # === Detect OS === 4 | ifeq ($(OS),Windows_NT) 5 | CURRENT_OS := WINDOWS 6 | else 7 | CURRENT_OS := UNIX 8 | endif 9 | 10 | # === Basic Info === 11 | OUT_FILENAME := minicraft 12 | 13 | SRC_DIR := src 14 | OBJ_DIR := obj 15 | BIN_DIR := bin 16 | 17 | SRC_SUBDIRS := scene entity 18 | 19 | RES_DIR := res 20 | RES_OUT_DIRS := src/res src/res/images src/res/palettes src/res/sounds 21 | 22 | # === Compilation === 23 | CPPFLAGS := -Iinclude -MMD -MP 24 | CFLAGS := -O3 -fomit-frame-pointer -marm -mcpu=arm7tdmi\ 25 | -Wall -pedantic 26 | 27 | ASFLAGS := -mcpu=arm7tdmi 28 | 29 | LDFLAGS := -nostartfiles -Wl,--print-memory-usage 30 | LDLIBS := 31 | 32 | # libsimplegba 33 | CPPFLAGS += -Ilib/libsimplegba/include 34 | LDFLAGS += -Llib/libsimplegba/bin -Tlib/libsimplegba/lnkscript 35 | LDLIBS += -lsimplegba 36 | 37 | ifeq ($(CURRENT_OS),UNIX) 38 | CC := arm-none-eabi-gcc 39 | AS := arm-none-eabi-as 40 | OBJCOPY := arm-none-eabi-objcopy 41 | 42 | EMULATOR := mgba-qt 43 | else ifeq ($(CURRENT_OS),WINDOWS) 44 | CC := 45 | AS := 46 | OBJCOPY := 47 | 48 | EMULATOR := 49 | endif 50 | 51 | # if LINK_MAP=1, generate a link map 52 | ifeq ($(LINK_MAP),1) 53 | LDFLAGS += -Wl,-Map=$(BIN_DIR)/output.map 54 | endif 55 | 56 | # === Extensions & Commands === 57 | OBJ_EXT := o 58 | ELF_EXT := elf 59 | GBA_EXT := gba 60 | 61 | ifeq ($(CURRENT_OS),UNIX) 62 | MKDIR := mkdir -p 63 | RM := rm -rfv 64 | else ifeq ($(CURRENT_OS),WINDOWS) 65 | MKDIR := mkdir 66 | RM := rmdir /Q /S 67 | endif 68 | 69 | # === Resources === 70 | 71 | # list of source file extensions 72 | SRC_EXT := s c 73 | 74 | # list of source directories 75 | SRC_DIRS := $(SRC_DIR)\ 76 | $(foreach SUBDIR,$(SRC_SUBDIRS),$(SRC_DIR)/$(SUBDIR)) 77 | 78 | # list of source files 79 | SRC := $(foreach DIR,$(SRC_DIRS),\ 80 | $(foreach EXT,$(SRC_EXT),\ 81 | $(wildcard $(DIR)/*.$(EXT)))) 82 | 83 | # list of object directories 84 | OBJ_DIRS := $(SRC_DIRS:%=$(OBJ_DIR)/%) 85 | 86 | # list of object files 87 | OBJ := $(SRC:%=$(OBJ_DIR)/%.$(OBJ_EXT)) 88 | 89 | # output files 90 | OUT_ELF := $(BIN_DIR)/$(OUT_FILENAME).$(ELF_EXT) 91 | OUT := $(BIN_DIR)/$(OUT_FILENAME).$(GBA_EXT) 92 | 93 | # === Targets === 94 | 95 | .PHONY: all run build clean 96 | 97 | all: build-deps build 98 | 99 | run: 100 | $(EMULATOR) $(OUT) 101 | 102 | build: $(OUT) 103 | 104 | clean: clean-deps 105 | @$(RM) $(BIN_DIR) $(OBJ_DIR) $(RES_OUT_DIRS) 106 | 107 | # generate GBA file 108 | $(OUT): $(OUT_ELF) 109 | $(OBJCOPY) -O binary $^ $@ 110 | 111 | # generate ELF file 112 | $(OUT_ELF): $(OBJ) | $(BIN_DIR) 113 | $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@ 114 | 115 | # compile .s files 116 | $(OBJ_DIR)/%.s.$(OBJ_EXT): %.s | $(OBJ_DIRS) 117 | $(AS) $(ASFLAGS) -o $@ $< 118 | 119 | # compile .c files 120 | $(OBJ_DIR)/%.c.$(OBJ_EXT): %.c | $(OBJ_DIRS) 121 | $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ 122 | 123 | # create directories 124 | $(BIN_DIR) $(OBJ_DIRS) $(RES_OUT_DIRS): 125 | $(MKDIR) "$@" 126 | 127 | .PHONY: build-deps clean-deps 128 | build-deps: 129 | $(MAKE) -C lib/libsimplegba build 130 | 131 | clean-deps: 132 | $(MAKE) -C lib/libsimplegba clean 133 | 134 | .PHONY: res 135 | res: $(RES_OUT_DIRS) 136 | tools/res2gba/res2gba "$(RES_DIR)/resources.json" 137 | 138 | .PHONY: release 139 | release: 140 | tools/release "$(OUT)" 141 | 142 | -include $(OBJ:.$(OBJ_EXT)=.d) 143 | -------------------------------------------------------------------------------- /include/item.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #ifndef MINICRAFT_ITEM 17 | #define MINICRAFT_ITEM 18 | 19 | #include "minicraft.h" 20 | 21 | #define ITEM_TYPES (33) 22 | 23 | #define WOOD_ITEM (0) 24 | #define STONE_ITEM (1) 25 | #define GLASS_ITEM (2) 26 | #define WHEAT_ITEM (3) 27 | #define SLIME_ITEM (4) 28 | #define CLOTH_ITEM (5) 29 | #define COAL_ITEM (6) 30 | #define IRON_ORE_ITEM (7) 31 | #define GOLD_ORE_ITEM (8) 32 | #define IRON_INGOT_ITEM (9) 33 | #define GOLD_INGOT_ITEM (10) 34 | #define GEM_ITEM (11) 35 | 36 | #define FLOWER_ITEM (12) 37 | #define SEEDS_ITEM (13) 38 | #define ACORN_ITEM (14) 39 | #define CACTUS_ITEM (15) 40 | #define DIRT_ITEM (16) 41 | #define SAND_ITEM (17) 42 | #define CLOUD_ITEM (18) 43 | 44 | #define APPLE_ITEM (19) 45 | #define BREAD_ITEM (20) 46 | 47 | #define WORKBENCH_ITEM (21) 48 | #define FURNACE_ITEM (22) 49 | #define OVEN_ITEM (23) 50 | #define ANVIL_ITEM (24) 51 | #define CHEST_ITEM (25) 52 | #define LANTERN_ITEM (26) 53 | 54 | #define POWERGLOVE_ITEM (27) 55 | 56 | #define SWORD_ITEM (28) 57 | #define AXE_ITEM (29) 58 | #define PICK_ITEM (30) 59 | #define SHOVEL_ITEM (31) 60 | #define HOE_ITEM (32) 61 | 62 | #define ITEMCLASS_MATERIAL (0) 63 | #define ITEMCLASS_PLACEABLE (1) 64 | #define ITEMCLASS_FOOD (2) 65 | #define ITEMCLASS_FURNITURE (3) 66 | #define ITEMCLASS_POWERGLOVE (4) 67 | #define ITEMCLASS_TOOL (5) 68 | 69 | struct Item { 70 | u8 class; 71 | char *name; 72 | u8 palette; 73 | 74 | union { 75 | // ITEMCLASS_PLACEABLE 76 | struct { 77 | u8 placed_tile; 78 | u8 placeable_on[2]; 79 | }; 80 | 81 | // ITEMCLASS_FOOD 82 | struct { 83 | u8 hp_gain; 84 | }; 85 | 86 | // ITEMCLASS_FURNITURE 87 | struct { 88 | u8 furniture; 89 | }; 90 | }; 91 | }; 92 | 93 | struct item_Data { 94 | u8 type; 95 | 96 | union { 97 | u16 count; 98 | u8 tool_level; 99 | u8 chest_id; 100 | }; 101 | }; 102 | 103 | #define ITEM_S(data)\ 104 | (&item_list[(data)->type]) 105 | 106 | extern const struct Item item_list[ITEM_TYPES]; 107 | 108 | extern void item_write(struct item_Data *data, u8 palette, u32 x, u32 y); 109 | extern void item_write_name(struct item_Data *data, u8 palette, u32 x, u32 y); 110 | extern void item_draw_icon(struct item_Data *data, u32 x, u32 y, bool black_bg); 111 | 112 | INLINE bool item_is_resource(u8 type) { 113 | const struct Item *item = &item_list[type]; 114 | 115 | return item->class == ITEMCLASS_MATERIAL || 116 | item->class == ITEMCLASS_PLACEABLE || 117 | item->class == ITEMCLASS_FOOD; 118 | } 119 | 120 | #endif // MINICRAFT_ITEM 121 | -------------------------------------------------------------------------------- /src/entity/text-particle.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "entity.h" 17 | 18 | struct text_Data { 19 | u8 time : 6; 20 | u8 palette : 2; 21 | 22 | u8 number; 23 | 24 | // 64 xx = 1 x 25 | i8 xx; 26 | i8 xv; 27 | 28 | // 64 yy = 1 y 29 | i8 yy; 30 | i8 yv; 31 | 32 | // 6 zz = 1 z 33 | i16 zz : 10; 34 | i16 zv : 6; 35 | }; 36 | 37 | static_assert(sizeof(struct text_Data) == 8, "struct text_Data: wrong size"); 38 | 39 | THUMB 40 | void entity_add_text_particle(struct Level *level, u16 x, u16 y, 41 | u8 number, u8 palette) { 42 | u8 entity_id = level_new_entity(level, TEXT_PARTICLE_ENTITY); 43 | if(entity_id >= ENTITY_LIMIT) 44 | return; 45 | 46 | struct entity_Data *data = &level->entities[entity_id]; 47 | struct text_Data *text_data = (struct text_Data *) &data->data; 48 | 49 | data->x = x; 50 | data->y = y; 51 | 52 | text_data->xv = random(59) - 29; 53 | text_data->yv = random(39) - 19; 54 | 55 | text_data->zz = 12; 56 | text_data->zv = 12 + random(4); 57 | 58 | text_data->number = number; 59 | text_data->palette = palette; 60 | 61 | level_add_entity(level, entity_id); 62 | } 63 | 64 | ETICK(text_particle_tick) { 65 | struct text_Data *text_data = (struct text_Data *) &data->data; 66 | 67 | text_data->time++; 68 | if(text_data->time > 60) { 69 | data->should_remove = true; 70 | return; 71 | } 72 | 73 | // movement 74 | text_data->xx += text_data->xv; 75 | text_data->yy += text_data->yv; 76 | 77 | // this can overflow if xx or yy is negative, but it's not a problem 78 | data->x += (text_data->xx / 64); 79 | data->y += (text_data->yy / 64); 80 | 81 | text_data->xx %= 64; 82 | text_data->yy %= 64; 83 | 84 | text_data->zz += text_data->zv; 85 | if(text_data->zz < 0) { 86 | text_data->zz = 0; 87 | 88 | text_data->zv /= -2; 89 | 90 | text_data->xv = text_data->xv * 3 / 5; 91 | text_data->yv = text_data->yv * 3 / 5; 92 | } 93 | text_data->zv--; 94 | } 95 | 96 | EDRAW(text_particle_draw) { 97 | struct text_Data *text_data = (struct text_Data *) &data->data; 98 | 99 | const u8 length = 1 + (text_data->number >= 10); 100 | 101 | sprite_config(used_sprites, &(struct Sprite) { 102 | .x = data->x - length * 4 - level_x_offset, 103 | .y = data->y - (text_data->zz / 6) - level_y_offset, 104 | 105 | .priority = 2, 106 | 107 | .size = (length == 2 ? SPRITE_SIZE_16x8 : SPRITE_SIZE_8x8), 108 | .flip = 0, 109 | 110 | .tile = 640 + text_data->number * 2 + (length == 1), 111 | .palette = 4 + text_data->palette 112 | }); 113 | return 1; 114 | } 115 | 116 | const struct Entity text_particle_entity = { 117 | .tick = text_particle_tick, 118 | .draw = text_particle_draw 119 | }; 120 | -------------------------------------------------------------------------------- /src/tick/tiles.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "tile.h" 17 | 18 | #include "level.h" 19 | 20 | #define FTICK(name)\ 21 | static inline void name(struct Level *level, u32 xt, u32 yt) 22 | 23 | FTICK(damage_recover_tick) { 24 | u8 damage = LEVEL_GET_DATA(level, xt, yt); 25 | if(damage != 0) 26 | LEVEL_SET_DATA(level, xt, yt, damage - 1); 27 | } 28 | 29 | FTICK(grass_tick) { 30 | if(random(40) != 0) 31 | return; 32 | 33 | i32 xn = xt; 34 | i32 yn = yt; 35 | 36 | if(random(2)) 37 | xn += random(2) * 2 - 1; 38 | else 39 | yn += random(2) * 2 - 1; 40 | 41 | if(LEVEL_GET_TILE(level, xn, yn) == DIRT_TILE) 42 | LEVEL_SET_TILE(level, xn, yn, GRASS_TILE, 0); 43 | } 44 | 45 | FTICK(liquid_tick) { 46 | i32 xn = xt; 47 | i32 yn = yt; 48 | 49 | if(random(2)) 50 | xn += random(2) * 2 - 1; 51 | else 52 | yn += random(2) * 2 - 1; 53 | 54 | if(LEVEL_GET_TILE(level, xn, yn) == HOLE_TILE) 55 | LEVEL_SET_TILE(level, xn, yn, LIQUID_TILE, 0); 56 | } 57 | 58 | FTICK(tree_sapling_tick) { 59 | u8 age = LEVEL_GET_DATA(level, xt, yt) + 1; 60 | 61 | if(age > 100) 62 | LEVEL_SET_TILE(level, xt, yt, TREE_TILE, 0); 63 | else 64 | LEVEL_SET_DATA(level, xt, yt, age); 65 | } 66 | 67 | FTICK(cactus_sapling_tick) { 68 | u8 age = LEVEL_GET_DATA(level, xt, yt) + 1; 69 | 70 | if(age > 100) 71 | LEVEL_SET_TILE(level, xt, yt, CACTUS_TILE, 0); 72 | else 73 | LEVEL_SET_DATA(level, xt, yt, age); 74 | } 75 | 76 | FTICK(farmland_tick) { 77 | u8 age = LEVEL_GET_DATA(level, xt, yt); 78 | if(age < 5) 79 | LEVEL_SET_DATA(level, xt, yt, age + 1); 80 | } 81 | 82 | FTICK(wheat_tick) { 83 | if(random(2)) 84 | return; 85 | 86 | u8 age = LEVEL_GET_DATA(level, xt, yt); 87 | if(age < 50) 88 | LEVEL_SET_DATA(level, xt, yt, age + 1); 89 | } 90 | 91 | #define CALL(function)\ 92 | function(level, xt, yt) 93 | 94 | static inline void tick_tile(struct Level *level, u32 xt, u32 yt) { 95 | switch(LEVEL_GET_TILE(level, xt, yt)) { 96 | case ROCK_TILE: 97 | case TREE_TILE: 98 | case SAND_TILE: 99 | case CACTUS_TILE: 100 | case HARD_ROCK_TILE: 101 | CALL(damage_recover_tick); 102 | break; 103 | 104 | case GRASS_TILE: 105 | case FLOWER_TILE: 106 | CALL(grass_tick); 107 | break; 108 | 109 | case LIQUID_TILE: 110 | CALL(liquid_tick); 111 | break; 112 | 113 | case TREE_SAPLING_TILE: 114 | CALL(tree_sapling_tick); 115 | break; 116 | 117 | case CACTUS_SAPLING_TILE: 118 | CALL(cactus_sapling_tick); 119 | break; 120 | 121 | case FARMLAND_TILE: 122 | CALL(farmland_tick); 123 | break; 124 | 125 | case WHEAT_TILE: 126 | CALL(wheat_tick); 127 | break; 128 | } 129 | } 130 | 131 | #undef CALL 132 | -------------------------------------------------------------------------------- /src/scene/death.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022, 2024 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "scene.h" 17 | 18 | #include "screen.h" 19 | #include "options.h" 20 | #include "level.h" 21 | #include "entity.h" 22 | #include "tile.h" 23 | #include "sound.h" 24 | 25 | static u8 death_time; 26 | 27 | THUMB 28 | static void death_init(u8 flags) { 29 | death_time = 0; 30 | } 31 | 32 | static inline void get_spawn_location(struct Level *level, 33 | u32 *spawn_x, u32 *spawn_y) { 34 | // try to find a grass tile 35 | for(u32 i = 0; i < 65536; i++) { 36 | u32 x = random(LEVEL_W); 37 | u32 y = random(LEVEL_H); 38 | 39 | u32 tile = LEVEL_GET_TILE(level, x, y); 40 | 41 | if(tile == GRASS_TILE) { 42 | *spawn_x = x; 43 | *spawn_y = y; 44 | return; 45 | } 46 | } 47 | 48 | // Try to find any non-solid tile. This cannot fail, because at the 49 | // very least stairs down are present. 50 | for(u32 y = 0; y < LEVEL_H; y++) { 51 | for(u32 x = 0; x < LEVEL_W; x++) { 52 | const u32 tile_id = LEVEL_GET_TILE(level, x, y); 53 | const struct Tile *tile = &tile_list[tile_id]; 54 | 55 | if(tile->is_solid) 56 | continue; 57 | 58 | // ignore stairs up: the player could remain stuck inside 59 | // the surrounding box of hard rock 60 | if(tile_id == STAIRS_UP_TILE) 61 | continue; 62 | 63 | *spawn_x = x; 64 | *spawn_y = y; 65 | return; 66 | } 67 | } 68 | } 69 | 70 | static inline void respawn(void) { 71 | struct Level *level = &levels[3]; 72 | current_level = 3; 73 | 74 | // remove all hostile entities from the level 75 | for(u32 i = 1; i < ENTITY_LIMIT; i++) { 76 | struct entity_Data *data = &level->entities[i]; 77 | if(data->type == ZOMBIE_ENTITY || data->type == SLIME_ENTITY) 78 | data->type = -1; 79 | } 80 | 81 | // choose a new spawn position 82 | u32 spawn_x = 0, spawn_y = 0; 83 | get_spawn_location(level, &spawn_x, &spawn_y); 84 | 85 | entity_add_player( 86 | level, 87 | spawn_x, spawn_y, 88 | !options.keep_inventory 89 | ); 90 | } 91 | 92 | THUMB 93 | static void death_tick(void) { 94 | death_time++; 95 | if(death_time > 60) { 96 | if(input_press(KEY_A) || input_press(KEY_B)) { 97 | SOUND_PLAY(sound_start); 98 | respawn(); 99 | set_scene(&scene_game, 7); 100 | } 101 | } 102 | } 103 | 104 | THUMB 105 | static void death_draw(void) { 106 | const u8 death_x = 5; 107 | const u8 death_y = 5; 108 | const u8 death_w = 20; 109 | const u8 death_h = 7; 110 | 111 | screen_draw_frame("", death_x, death_y, death_w, death_h); 112 | screen_write("YOU DIED! AWW!", 6, death_x + 1, death_y + 1); 113 | 114 | screen_write("TIME:", 6, death_x + 1, death_y + 2); 115 | screen_write_time(gametime, 10, death_x + 6, death_y + 2); 116 | 117 | screen_write("SCORE:", 6, death_x + 1, death_y + 3); 118 | SCREEN_WRITE_NUMBER(score, 10, 10, false, 10, death_x + 7, death_y + 3); 119 | 120 | screen_write("PRESS A TO RESPAWN", 8, death_x + 1, death_y + 5); 121 | } 122 | 123 | const struct Scene scene_death = { 124 | .init = death_init, 125 | 126 | .tick = death_tick, 127 | .draw = death_draw 128 | }; 129 | -------------------------------------------------------------------------------- /src/scene/inventory.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "scene.h" 17 | 18 | #include "inventory.h" 19 | #include "screen.h" 20 | #include "item.h" 21 | #include "player.h" 22 | 23 | static i32 selected; 24 | static bool should_render_game = false; 25 | 26 | THUMB 27 | static void inventory_init(u8 flags) { 28 | selected = 0; 29 | 30 | // Bug in the original game: when opening the inventory menu, thus 31 | // adding the active item to the inventory, the item is always put 32 | // in slot 0, regardless of it being a resource item or not. 33 | // This means that if the inventory already contains that resource, 34 | // there will be two distinct items for the same resource. 35 | // 36 | // This is not a duplication bug, however it causes annoyances for 37 | // crafting. To merge the items, the player can use a chest. 38 | if(player_active_item.type < ITEM_TYPES) 39 | if(inventory_add(&player_inventory, &player_active_item, 0)) 40 | player_active_item.type = -1; 41 | 42 | should_render_game = true; 43 | } 44 | 45 | THUMB 46 | static void inventory_tick(void) { 47 | gametime++; 48 | 49 | if(input_press(KEY_B) || input_press(KEY_START)) 50 | set_scene(&scene_game, 1); 51 | 52 | if(player_inventory.size == 0) 53 | return; 54 | 55 | if(input_repeat(KEY_UP)) 56 | selected--; 57 | if(input_repeat(KEY_DOWN)) 58 | selected++; 59 | 60 | if(selected < 0) 61 | selected = player_inventory.size - 1; 62 | if(selected >= player_inventory.size) 63 | selected = 0; 64 | 65 | if(input_repeat(KEY_A)) { 66 | struct item_Data old_active_item = player_active_item; 67 | 68 | inventory_remove(&player_inventory, &player_active_item, selected); 69 | 70 | // If there was an active item, put it back into the inventory. 71 | // This only happens when the inventory is full. 72 | if(old_active_item.type < ITEM_TYPES) 73 | inventory_add(&player_inventory, &old_active_item, 0); 74 | 75 | set_scene(&scene_game, 1); 76 | } 77 | } 78 | 79 | THUMB 80 | static void inventory_draw(void) { 81 | if(should_render_game) { 82 | scene_game.draw(); 83 | should_render_game = false; 84 | } 85 | 86 | const u8 inv_x = 9; 87 | const u8 inv_y = 2; 88 | const u8 inv_w = 12; 89 | const u8 inv_h = 14; 90 | 91 | screen_draw_frame("INVENTORY", inv_x, inv_y, inv_w, inv_h); 92 | 93 | i8 item0 = selected - (inv_h - 2) / 2; 94 | if(item0 > player_inventory.size - (inv_h - 2)) 95 | item0 = player_inventory.size - (inv_h - 2); 96 | if(item0 < 0) 97 | item0 = 0; 98 | 99 | // draw inventory items 100 | for(u32 i = 0; i < inv_h - 2; i++) { 101 | if(item0 + i >= player_inventory.size) 102 | break; 103 | 104 | struct item_Data *data = &player_inventory.items[item0 + i]; 105 | 106 | item_draw_icon(data, inv_x + 1, inv_y + 1 + i, false); 107 | item_write(data, 6, inv_x + 2, inv_y + 1 + i); 108 | } 109 | 110 | // draw cursor arrows 111 | screen_write( 112 | ">", 6, inv_x , inv_y + 1 + (selected - item0) 113 | ); 114 | screen_write( 115 | "<", 6, inv_x + inv_w - 1, inv_y + 1 + (selected - item0) 116 | ); 117 | } 118 | 119 | const struct Scene scene_inventory = { 120 | .init = inventory_init, 121 | 122 | .tick = inventory_tick, 123 | .draw = inventory_draw 124 | }; 125 | -------------------------------------------------------------------------------- /src/debug/map-viewer.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "minicraft.h" 17 | 18 | #include "level.h" 19 | #include "tile.h" 20 | #include "generator.h" 21 | #include "screen.h" 22 | 23 | #define DISPLAY_CONTROL *((vu16 *) 0x04000000) 24 | #define DISPLAY_STATUS *((vu16 *) 0x04000004) 25 | 26 | #define VRAM ((vu16 *) 0x06000000) 27 | 28 | // To use the map viewer, just include this source file in minicraft.c 29 | 30 | int AgbMain(void) { 31 | input_init(30, 2); 32 | interrupt_toggle(IRQ_VBLANK, true); 33 | 34 | DISPLAY_CONTROL = 3 << 0 | // Video mode 35 | 1 << 10; // Enable BG 2 36 | 37 | generate_levels(); 38 | 39 | u32 displayed_level = 3; 40 | while(true) { 41 | interrupt_wait(IRQ_VBLANK); 42 | input_update(); 43 | 44 | if(input_repeat(KEY_DOWN)) { 45 | if(displayed_level != 0) 46 | displayed_level--; 47 | else 48 | displayed_level = 4; 49 | } 50 | 51 | if(input_repeat(KEY_UP)) { 52 | if(displayed_level != 4) 53 | displayed_level++; 54 | else 55 | displayed_level = 0; 56 | } 57 | 58 | if(input_repeat(KEY_START)) { 59 | generate_levels(); 60 | } 61 | 62 | struct Level *level = &levels[displayed_level]; 63 | for(u32 y = 0; y < LEVEL_H; y++) { 64 | for(u32 x = 0; x < LEVEL_W; x++) { 65 | u16 color = 0; 66 | switch(level->tiles[x + y * LEVEL_W]) { 67 | case GRASS_TILE: 68 | color = 0x03e0; 69 | break; 70 | case SAND_TILE: 71 | color = 0x1ab9; 72 | break; 73 | case FLOWER_TILE: 74 | color = 0x1b66; 75 | break; 76 | case LIQUID_TILE: 77 | color = 0x7c00; 78 | break; 79 | case TREE_TILE: 80 | case CACTUS_TILE: 81 | color = 0x01e0; 82 | break; 83 | case ROCK_TILE: 84 | color = 0x294a; 85 | break; 86 | case DIRT_TILE: 87 | color = 0x1993; 88 | break; 89 | case IRON_ORE_TILE: 90 | color = 0x4e7f; 91 | break; 92 | case GOLD_ORE_TILE: 93 | color = 0x1bff; 94 | break; 95 | case GEM_ORE_TILE: 96 | color = 0x667f; 97 | break; 98 | case CLOUD_TILE: 99 | color = 0x7fff; 100 | break; 101 | case CLOUD_CACTUS_TILE: 102 | color = 0x56b5; 103 | break; 104 | 105 | case STAIRS_DOWN_TILE: 106 | color = 0x7c1f; 107 | break; 108 | case STAIRS_UP_TILE: 109 | color = 0x7fe0; 110 | break; 111 | } 112 | 113 | VRAM[(64 + x) + (24 + y) * 240] = color; 114 | } 115 | } 116 | } 117 | } 118 | 119 | #define AgbMain __AgbMain__ 120 | -------------------------------------------------------------------------------- /src/mob.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "mob.h" 17 | 18 | #include "entity.h" 19 | #include "level.h" 20 | #include "tile.h" 21 | #include "player.h" 22 | #include "air-wizard.h" 23 | #include "item.h" 24 | #include "sound.h" 25 | 26 | static inline void mob_die(struct Level *level, struct entity_Data *data) { 27 | switch(data->type) { 28 | case ZOMBIE_ENTITY: 29 | mob_zombie_die(level, data); 30 | break; 31 | 32 | case SLIME_ENTITY: 33 | mob_slime_die(level, data); 34 | break; 35 | 36 | case AIR_WIZARD_ENTITY: 37 | mob_air_wizard_die(level, data); 38 | break; 39 | 40 | case PLAYER_ENTITY: 41 | mob_player_die(level, data); 42 | break; 43 | } 44 | data->should_remove = true; 45 | } 46 | 47 | IWRAM_SECTION 48 | void mob_tick(struct Level *level, struct entity_Data *data) { 49 | struct mob_Data *mob_data = (struct mob_Data *) &data->data; 50 | 51 | if(current_level == 0) 52 | if(LEVEL_GET_TILE(level, data->x >> 4, data->y >> 4) == LIQUID_TILE) 53 | mob_hurt(level, data, 4, mob_data->dir ^ 2); 54 | 55 | if(mob_data->hp <= 0) 56 | mob_die(level, data); 57 | 58 | if(mob_data->hurt_time > 0) 59 | mob_data->hurt_time--; 60 | } 61 | 62 | IWRAM_SECTION 63 | bool mob_move(struct Level *level, struct entity_Data *data, 64 | i32 xm, i32 ym) { 65 | struct mob_Data *mob_data = (struct mob_Data *) &data->data; 66 | 67 | if(mob_data->knockback.val > 0) { 68 | const u8 dir = mob_data->knockback.dir; 69 | i32 xk = (dir == 3) - (dir == 1); 70 | i32 yk = (dir == 2) - (dir == 0); 71 | 72 | entity_move2(level, data, xk, yk); 73 | mob_data->knockback.val--; 74 | } 75 | 76 | if(mob_data->hurt_time > 0) 77 | return true; 78 | 79 | if(xm != 0 || ym != 0) { 80 | mob_data->dir = (ym == 0) * ((xm < 0) * 1 + (xm > 0) * 3) + 81 | ((ym < 0) * 0 + (ym > 0) * 2); 82 | 83 | mob_data->walk_dist++; 84 | } 85 | 86 | return entity_move(level, data, xm, ym); 87 | } 88 | 89 | IWRAM_SECTION 90 | void mob_hurt(struct Level *level, struct entity_Data *data, 91 | u8 damage, u8 knockback_dir) { 92 | struct mob_Data *mob_data = (struct mob_Data *) &data->data; 93 | 94 | if(mob_data->hurt_time > 0) 95 | return; 96 | 97 | if(data->type == PLAYER_ENTITY && player_invulnerable_time > 0) 98 | return; 99 | 100 | mob_data->hp = (mob_data->hp - damage) * (mob_data->hp >= damage); 101 | 102 | mob_data->hurt_time = 10; 103 | 104 | mob_data->knockback.val = 6; 105 | mob_data->knockback.dir = knockback_dir; 106 | 107 | if(data->type == PLAYER_ENTITY) { 108 | player_invulnerable_time = 30; 109 | 110 | entity_add_text_particle(level, data->x, data->y, damage, 1); 111 | SOUND_PLAY(sound_player_hurt); 112 | } else { 113 | if(data->type == AIR_WIZARD_ENTITY) { 114 | if(air_wizard_attack_delay == 0 && air_wizard_attack_time == 0) 115 | air_wizard_attack_delay = 120; 116 | } 117 | 118 | entity_add_text_particle(level, data->x, data->y, damage, 0); 119 | 120 | struct entity_Data *player = &level->entities[0]; 121 | if(player->type < ENTITY_TYPES) { 122 | i32 xd = player->x - data->x; 123 | i32 yd = player->y - data->y; 124 | 125 | if(xd * xd + yd * yd < 80 * 80) 126 | SOUND_PLAY(sound_monster_hurt); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/entity/spark.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "entity.h" 17 | 18 | #include "mob.h" 19 | 20 | struct spark_Data { 21 | u16 time; 22 | 23 | // 64 xx = 1 x 24 | // 64 yy = 1 y 25 | i16 xx; 26 | i16 yy; 27 | 28 | // velocity 29 | i8 xv; 30 | i8 yv; 31 | }; 32 | 33 | static_assert(sizeof(struct spark_Data) == 8, "struct spark_Data: wrong size"); 34 | 35 | void entity_add_spark(struct Level *level, u16 x, u16 y, 36 | i8 xv, i8 yv) { 37 | u8 entity_id = level_new_entity(level, SPARK_ENTITY); 38 | if(entity_id >= ENTITY_LIMIT) 39 | return; 40 | 41 | struct entity_Data *data = &level->entities[entity_id]; 42 | struct spark_Data *spark_data = (struct spark_Data *) &data->data; 43 | 44 | data->x = x; 45 | data->y = y; 46 | 47 | spark_data->xv = xv; 48 | spark_data->yv = yv; 49 | 50 | spark_data->time = 10 * 60 + random(30); 51 | 52 | level_add_entity(level, entity_id); 53 | } 54 | 55 | ETICK(spark_tick) { 56 | struct spark_Data *spark_data = (struct spark_Data *) &data->data; 57 | 58 | spark_data->time--; 59 | if(spark_data->time == 0) { 60 | data->should_remove = true; 61 | return; 62 | } 63 | 64 | // movement 65 | spark_data->xx += spark_data->xv; 66 | spark_data->yy += spark_data->yv; 67 | 68 | // this can overflow if xx or yy is negative, but it's not a problem 69 | data->x += (spark_data->xx / 64); 70 | data->y += (spark_data->yy / 64); 71 | 72 | spark_data->xx %= 64; 73 | spark_data->yy %= 64; 74 | 75 | // despawn if too far 76 | struct entity_Data *player = &level->entities[0]; 77 | if(player->type < ENTITY_TYPES) { 78 | i32 xd = player->x - data->x; 79 | i32 yd = player->y - data->y; 80 | 81 | u32 dist = xd * xd + yd * yd; 82 | if(dist >= (20 * 16) * (20 * 16)) { 83 | data->should_remove = true; 84 | return; 85 | } 86 | } 87 | 88 | // hurt mobs 89 | const u16 x = data->x; 90 | const u16 y = data->y; 91 | 92 | u16 xt = (x >> 4); 93 | u16 yt = (y >> 4); 94 | 95 | if(xt < LEVEL_W && yt < LEVEL_H) { 96 | u8 *solid_entities = level_solid_entities[xt + yt * LEVEL_W]; 97 | 98 | for(u32 i = 0; i < SOLID_ENTITIES_IN_TILE; i++) { 99 | struct entity_Data *e_data = &level->entities[solid_entities[i]]; 100 | struct mob_Data *mob_data = (struct mob_Data *) &e_data->data; 101 | 102 | switch(e_data->type) { 103 | case ZOMBIE_ENTITY: 104 | case SLIME_ENTITY: 105 | case PLAYER_ENTITY: 106 | break; 107 | 108 | default: 109 | continue; 110 | } 111 | 112 | if(entity_intersects(e_data, x, y, x, y)) 113 | mob_hurt(level, e_data, 1, mob_data->dir ^ 2); 114 | } 115 | } 116 | } 117 | 118 | EDRAW(spark_draw) { 119 | struct spark_Data *spark_data = (struct spark_Data *) &data->data; 120 | 121 | if(spark_data->time < 2 * 60) 122 | if(((spark_data->time / 6) & 1) == 0) 123 | return 0; 124 | 125 | sprite_config(used_sprites, &(struct Sprite) { 126 | .x = data->x - 4 - level_x_offset, 127 | .y = data->y - 8 - level_y_offset, 128 | 129 | .priority = 2, 130 | 131 | .size = SPRITE_SIZE_8x16, 132 | .flip = 0, 133 | 134 | .tile = 192 + random(16) * 2, 135 | .palette = 5 136 | }); 137 | 138 | return 1; 139 | } 140 | 141 | const struct Entity spark_entity = { 142 | .tick = spark_tick, 143 | .draw = spark_draw 144 | }; 145 | -------------------------------------------------------------------------------- /src/entity/furniture.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "entity.h" 17 | #include "furniture.h" 18 | 19 | #include "level.h" 20 | #include "item.h" 21 | #include "mob.h" 22 | #include "player.h" 23 | 24 | SBSS_SECTION 25 | struct Inventory chest_inventories[CHEST_LIMIT]; 26 | u8 chest_count; 27 | 28 | u8 chest_opened_id; 29 | 30 | struct furniture_Data { 31 | i8 push_x; 32 | i8 push_y; 33 | 34 | u8 push_delay; 35 | 36 | u8 chest_id; 37 | 38 | u8 unused[4]; 39 | }; 40 | 41 | static_assert( 42 | sizeof(struct furniture_Data) == 8, 43 | "struct furniture_Data: wrong size" 44 | ); 45 | 46 | THUMB 47 | bool entity_add_furniture(struct Level *level, u8 xt, u8 yt, 48 | struct item_Data *item_data) { 49 | u8 entity_id = level_new_entity( 50 | level, WORKBENCH_ENTITY + (item_data->type - WORKBENCH_ITEM) 51 | ); 52 | if(entity_id >= ENTITY_LIMIT) 53 | return false; 54 | 55 | struct entity_Data *data = &level->entities[entity_id]; 56 | struct furniture_Data *furn_data = (struct furniture_Data *) &data->data; 57 | 58 | data->x = (xt << 4) + 8; 59 | data->y = (yt << 4) + 8; 60 | 61 | furn_data->chest_id = item_data->chest_id; 62 | 63 | level_add_entity(level, entity_id); 64 | return true; 65 | } 66 | 67 | ETICK(furniture_tick) { 68 | struct furniture_Data *furn_data = (struct furniture_Data *) &data->data; 69 | 70 | entity_move(level, data, furn_data->push_x, furn_data->push_y); 71 | furn_data->push_x = 0; 72 | furn_data->push_y = 0; 73 | 74 | if(furn_data->push_delay > 0) 75 | furn_data->push_delay--; 76 | } 77 | 78 | EDRAW(furniture_draw) { 79 | sprite_config(used_sprites, &(struct Sprite) { 80 | .x = data->x - 8 - level_x_offset, 81 | .y = data->y - 12 - level_y_offset, 82 | 83 | .priority = 2, 84 | 85 | .size = SPRITE_SIZE_16x16, 86 | .flip = 0, 87 | 88 | .tile = 148 + 4 * (data->type - WORKBENCH_ENTITY), 89 | .palette = 6 90 | }); 91 | return 1; 92 | } 93 | 94 | ETOUCH_PLAYER(furniture_touch_player) { 95 | struct furniture_Data *furn_data = (struct furniture_Data *) &data->data; 96 | 97 | if(furn_data->push_delay > 0) 98 | return; 99 | 100 | struct mob_Data *mob_data = (struct mob_Data *) &player->data; 101 | const u8 dir = mob_data->dir; 102 | 103 | furn_data->push_x = (dir == 3) - (dir == 1); 104 | furn_data->push_y = (dir == 2) - (dir == 0); 105 | 106 | furn_data->push_delay = 10; 107 | } 108 | 109 | #define GENERATE_STRUCT(name, yr_)\ 110 | const struct Entity name = {\ 111 | .tick = furniture_tick,\ 112 | .draw = furniture_draw,\ 113 | \ 114 | .xr = 3,\ 115 | .yr = yr_,\ 116 | \ 117 | .is_solid = true,\ 118 | .touch_player = furniture_touch_player\ 119 | } 120 | 121 | GENERATE_STRUCT(workbench_entity, 2); 122 | GENERATE_STRUCT(furnace_entity, 2); 123 | GENERATE_STRUCT(oven_entity, 2); 124 | GENERATE_STRUCT(anvil_entity, 2); 125 | GENERATE_STRUCT(chest_entity, 3); 126 | GENERATE_STRUCT(lantern_entity, 2); 127 | 128 | THUMB 129 | void furniture_take(struct entity_Data *data) { 130 | struct furniture_Data *furn_data = (struct furniture_Data *) &data->data; 131 | 132 | player_active_item = (struct item_Data) { 133 | .type = WORKBENCH_ITEM + (data->type - WORKBENCH_ENTITY), 134 | .chest_id = furn_data->chest_id 135 | }; 136 | data->should_remove = true; 137 | } 138 | 139 | THUMB 140 | void furniture_set_opened_chest(struct entity_Data *data) { 141 | struct furniture_Data *furn_data = (struct furniture_Data *) &data->data; 142 | 143 | chest_opened_id = furn_data->chest_id; 144 | } 145 | 146 | THUMB 147 | u8 furniture_new_chest_id(void) { 148 | if(chest_count >= CHEST_LIMIT) 149 | return -1; 150 | 151 | return chest_count++; 152 | } 153 | -------------------------------------------------------------------------------- /src/scene/game.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022-2023 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "scene.h" 17 | 18 | #include "level.h" 19 | #include "screen.h" 20 | #include "mob.h" 21 | #include "player.h" 22 | 23 | static struct Level *level = NULL; 24 | 25 | static bool should_clear = false; 26 | 27 | static inline void game_move_player(struct Level *old_level, 28 | struct Level *new_level) { 29 | struct entity_Data *new_player = &new_level->entities[0]; 30 | 31 | // adjust position 32 | *new_player = old_level->entities[0]; 33 | u16 old_player_x = new_player->x; 34 | u16 old_player_y = new_player->y; 35 | 36 | new_player->x = (old_player_x & 0xfff0) + 8; 37 | new_player->y = (old_player_y & 0xfff0) + 8; 38 | } 39 | 40 | static void game_init(u8 flags) { 41 | if(flags & 2) { 42 | struct Level *old_level = level; 43 | level = &levels[current_level]; 44 | 45 | // move the player to the new level 46 | if(old_level && !(flags & 4)) 47 | game_move_player(old_level, level); 48 | 49 | level_load(level); 50 | 51 | interrupt_wait(IRQ_VBLANK); 52 | screen_update_level_specific(); 53 | } 54 | 55 | should_clear = true; 56 | } 57 | 58 | IWRAM_SECTION 59 | static void game_tick(void) { 60 | if(scene_death_timer > 0) { 61 | scene_death_timer--; 62 | if(scene_death_timer == 0) 63 | set_scene(&scene_death, 1); 64 | } 65 | 66 | if(scene_win_timer > 0) { 67 | scene_win_timer--; 68 | if(scene_win_timer == 0) 69 | set_scene(&scene_win, 1); 70 | } 71 | 72 | level_tick(level); 73 | } 74 | 75 | static inline void clear_screen(void) { 76 | if(!should_clear) 77 | return; 78 | 79 | // clear level area (fully transparent) 80 | for(u32 y = 0; y < 18; y++) 81 | for(u32 x = 0; x < 30; x++) 82 | BG3_TILEMAP[x + y * 32] = 0; 83 | 84 | // clear status bar (black) 85 | for(u32 y = 18; y < 20; y++) 86 | for(u32 x = 0; x < 30; x++) 87 | BG3_TILEMAP[x + y * 32] = 32; 88 | 89 | should_clear = false; 90 | } 91 | 92 | static inline u16 get_player_hp(void) { 93 | struct entity_Data *player = &level->entities[0]; 94 | if(player->type < ENTITY_TYPES) { 95 | struct mob_Data *mob_data = (struct mob_Data *) &player->data; 96 | 97 | return mob_data->hp; 98 | } 99 | return 0; 100 | } 101 | 102 | static inline void draw_status_bar(void) { 103 | u16 player_hp = get_player_hp(); 104 | 105 | // draw hp and stamina 106 | for(u32 i = 0; i < 10; i++) { 107 | BG3_TILEMAP[i + 18 * 32] = (100 + (player_hp <= i)) | 10 << 12; 108 | BG3_TILEMAP[i + 19 * 32] = (102 + (player_stamina <= i)) | 10 << 12; 109 | } 110 | 111 | // set stamina blinking color 112 | { 113 | u16 color = 0x0cc6; 114 | if(player_stamina_recharge_delay != 0 && 115 | (player_stamina_recharge_delay & 4) == 0) { 116 | color = 0x7bde; 117 | } 118 | screen_set_bg_palette_color(10, 12, color); 119 | } 120 | 121 | // clear active item area 122 | for(u32 x = 20; x < 30; x++) 123 | BG3_TILEMAP[x + 18 * 32] = 32; 124 | 125 | // draw active item 126 | if(player_active_item.type < ITEM_TYPES) { 127 | // copy item palette 128 | const struct Item *item = ITEM_S(&player_active_item); 129 | screen_load_active_item_palette(item->palette); 130 | 131 | item_draw_icon(&player_active_item, 20, 18, true); 132 | item_write(&player_active_item, 0, 21, 18); 133 | } 134 | } 135 | 136 | IWRAM_SECTION 137 | static void game_draw(void) { 138 | clear_screen(); 139 | level_draw(level); 140 | draw_status_bar(); 141 | } 142 | 143 | const struct Scene scene_game = { 144 | .init = game_init, 145 | 146 | .tick = game_tick, 147 | .draw = game_draw 148 | }; 149 | -------------------------------------------------------------------------------- /src/scene/start.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022-2024 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "scene.h" 17 | 18 | #include "generator.h" 19 | #include "furniture.h" 20 | #include "screen.h" 21 | #include "storage.h" 22 | #include "sound.h" 23 | 24 | #define LOAD_GAME (0) 25 | #define NEW_GAME (1) 26 | #define OPTIONS (2) 27 | #define HOW_TO_PLAY (3) 28 | #define ABOUT (4) 29 | 30 | static i8 selected; 31 | static bool can_load; 32 | static bool checksum_verified; 33 | 34 | THUMB 35 | static void start_init(u8 flags) { 36 | can_load = storage_check(); 37 | 38 | if(can_load) { 39 | selected = LOAD_GAME; 40 | 41 | checksum_verified = storage_verify_checksum(); 42 | storage_srand(); 43 | 44 | storage_load_options(); 45 | } else { 46 | selected = NEW_GAME; 47 | } 48 | } 49 | 50 | THUMB 51 | static void start_tick(void) { 52 | if(input_repeat(KEY_UP)) 53 | selected--; 54 | if(input_repeat(KEY_DOWN)) 55 | selected++; 56 | 57 | if(selected < (can_load ? LOAD_GAME : NEW_GAME)) 58 | selected = ABOUT; 59 | else if(selected > ABOUT) 60 | selected = (can_load ? LOAD_GAME : NEW_GAME); 61 | 62 | if(input_press(KEY_A) || input_press(KEY_B)) { 63 | switch(selected) { 64 | case LOAD_GAME: 65 | SOUND_PLAY(sound_start); 66 | 67 | // add 'tick_count' to current random seed 68 | random_seed(tick_count + random_seed(0)); 69 | 70 | storage_load(); 71 | set_scene(&scene_game, 7); 72 | break; 73 | 74 | case NEW_GAME: 75 | SOUND_PLAY(sound_start); 76 | 77 | // add 'tick_count' to current random seed 78 | random_seed(tick_count + random_seed(0)); 79 | 80 | generate_levels(); 81 | 82 | gametime = 0; 83 | score = 0; 84 | 85 | current_level = 3; 86 | 87 | chest_count = 0; 88 | for(u32 i = 0; i < CHEST_LIMIT; i++) 89 | chest_inventories[i].size = 0; 90 | 91 | set_scene(&scene_game, 7); 92 | break; 93 | 94 | case OPTIONS: 95 | set_scene(&scene_options, 1); 96 | break; 97 | 98 | case HOW_TO_PLAY: 99 | set_scene(&scene_instructions, 0); 100 | break; 101 | 102 | case ABOUT: 103 | set_scene(&scene_about, 0); 104 | break; 105 | } 106 | } 107 | } 108 | 109 | #define START_WRITE(text, id, x, y) do {\ 110 | screen_write((text), selected == (id) ? 0 : 1, (x), (y));\ 111 | if(selected == (id)) {\ 112 | screen_write(">", 0, (x) - 2, (y));\ 113 | screen_write("<", 0, (x) + sizeof(text), (y));\ 114 | }\ 115 | } while(0) 116 | 117 | THUMB 118 | static void start_draw(void) { 119 | // clear the screen 120 | for(u32 y = 0; y < 20; y++) 121 | for(u32 x = 0; x < 30; x++) 122 | BG3_TILEMAP[x + y * 32] = 32; 123 | 124 | // draw logo 125 | for(u32 y = 0; y < 2; y++) { 126 | for(u32 x = 0; x < 14; x++) { 127 | const u16 tile = (1 + (x + y * 14)) | 2 << 12; 128 | BG3_TILEMAP[(x + 8) + (y + 4) * 32] = tile; 129 | } 130 | } 131 | 132 | if(can_load) { 133 | START_WRITE("LOAD GAME", LOAD_GAME, 10, 9); 134 | if(!checksum_verified) { 135 | screen_write("(!)", 2, 22, 9); 136 | 137 | screen_write("(!) INVALID CHECKSUM", 2, 1, 17); 138 | } 139 | } 140 | START_WRITE("NEW GAME", NEW_GAME, 10, 10); 141 | 142 | START_WRITE("OPTIONS", OPTIONS, 11, 12); 143 | 144 | START_WRITE("HOW TO PLAY", HOW_TO_PLAY, 9, 14); 145 | START_WRITE("ABOUT", ABOUT, 12, 15); 146 | 147 | screen_write("V1.3+", 1, 25, 19); 148 | } 149 | 150 | const struct Scene scene_start = { 151 | .init = start_init, 152 | 153 | .tick = start_tick, 154 | .draw = start_draw 155 | }; 156 | -------------------------------------------------------------------------------- /include/entity.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #ifndef MINICRAFT_ENTITIES 17 | #define MINICRAFT_ENTITIES 18 | 19 | #include "minicraft.h" 20 | 21 | #include "level.h" 22 | #include "item.h" 23 | 24 | #define ENTITY_TYPES (14) 25 | 26 | #define ZOMBIE_ENTITY (0) 27 | #define SLIME_ENTITY (1) 28 | #define AIR_WIZARD_ENTITY (2) 29 | #define PLAYER_ENTITY (3) 30 | 31 | #define WORKBENCH_ENTITY (4) 32 | #define FURNACE_ENTITY (5) 33 | #define OVEN_ENTITY (6) 34 | #define ANVIL_ENTITY (7) 35 | #define CHEST_ENTITY (8) 36 | #define LANTERN_ENTITY (9) 37 | 38 | #define ITEM_ENTITY (10) 39 | #define SPARK_ENTITY (11) 40 | 41 | #define TEXT_PARTICLE_ENTITY (12) 42 | #define SMASH_PARTICLE_ENTITY (13) 43 | 44 | #define ETICK(name)\ 45 | IWRAM_SECTION\ 46 | static void name(struct Level *level, struct entity_Data *data) 47 | 48 | #define EDRAW(name)\ 49 | IWRAM_SECTION\ 50 | static u32 name(struct Level *level, struct entity_Data *data,\ 51 | u32 used_sprites) 52 | 53 | #define ETOUCH_PLAYER(name)\ 54 | static void name(struct Level *level, struct entity_Data *data,\ 55 | struct entity_Data *player) 56 | 57 | struct Entity { 58 | void (*tick)(struct Level *level, struct entity_Data *data); 59 | 60 | u32 (*draw)(struct Level *level, struct entity_Data *data, 61 | u32 used_sprites); 62 | 63 | // entity radius 64 | u8 xr; 65 | u8 yr; 66 | 67 | bool is_solid; 68 | bool hurt_by_tiles; 69 | void (*touch_player)(struct Level *level, struct entity_Data *data, 70 | struct entity_Data *player); 71 | }; 72 | 73 | extern const struct Entity zombie_entity; 74 | 75 | extern const struct Entity zombie_entity; 76 | extern const struct Entity slime_entity; 77 | extern const struct Entity air_wizard_entity; 78 | extern const struct Entity player_entity; 79 | 80 | extern const struct Entity workbench_entity; 81 | extern const struct Entity furnace_entity; 82 | extern const struct Entity oven_entity; 83 | extern const struct Entity anvil_entity; 84 | extern const struct Entity chest_entity; 85 | extern const struct Entity lantern_entity; 86 | 87 | extern const struct Entity item_entity; 88 | extern const struct Entity spark_entity; 89 | 90 | extern const struct Entity text_particle_entity; 91 | extern const struct Entity smash_particle_entity; 92 | 93 | #define ENTITY_S(data)\ 94 | (entity_list[(data)->type]) 95 | 96 | extern const struct Entity * const entity_list[ENTITY_TYPES]; 97 | 98 | extern bool entity_move(struct Level *level, struct entity_Data *data, 99 | i32 xm, i32 ym); 100 | 101 | extern bool entity_move2(struct Level *level, struct entity_Data *data, 102 | i32 xm, i32 ym); 103 | 104 | INLINE bool entity_intersects(struct entity_Data *data, 105 | i32 x0, i32 y0, i32 x1, i32 y1) { 106 | const struct Entity *entity = ENTITY_S(data); 107 | 108 | return (data->x + entity->xr >= x0) && (data->y + entity->yr >= y0) && 109 | (data->x - entity->xr <= x1) && (data->y - entity->yr <= y1); 110 | } 111 | 112 | // entity generators 113 | 114 | extern void entity_add_zombie(struct Level *level, u16 x, u16 y, u8 lvl); 115 | extern void entity_add_slime(struct Level *level, u16 x, u16 y, u8 lvl); 116 | extern void entity_add_air_wizard(struct Level *level); 117 | extern void entity_add_player(struct Level *level, u8 xt, u8 yt, 118 | bool reset_inventory); 119 | 120 | extern bool entity_add_furniture(struct Level *level, u8 xt, u8 yt, 121 | struct item_Data *item_data); 122 | 123 | extern void entity_add_item(struct Level *level, u16 x, u16 y, 124 | u8 item, bool is_tile); 125 | extern void entity_add_spark(struct Level *level, u16 x, u16 y, 126 | i8 xv, i8 yv); 127 | 128 | extern void entity_add_text_particle(struct Level *level, u16 x, u16 y, 129 | u8 number, u8 palette); 130 | extern void entity_add_smash_particle(struct Level *level, u8 xt, u8 yt); 131 | 132 | #endif // MINICRAFT_ENTITIES 133 | -------------------------------------------------------------------------------- /src/entity/slime.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "entity.h" 17 | 18 | #include "mob.h" 19 | 20 | struct slime_Data { 21 | i8 xm : 2; 22 | i8 ym : 2; 23 | 24 | u8 level : 2; 25 | 26 | i8 jump_time; 27 | }; 28 | 29 | static_assert(sizeof(struct slime_Data) == 2, "struct slime_Data: wrong size"); 30 | 31 | THUMB 32 | void entity_add_slime(struct Level *level, u16 x, u16 y, u8 lvl) { 33 | u8 entity_id = level_new_entity(level, SLIME_ENTITY); 34 | if(entity_id >= ENTITY_LIMIT) 35 | return; 36 | 37 | struct entity_Data *data = &level->entities[entity_id]; 38 | struct mob_Data *mob_data = (struct mob_Data *) &data->data; 39 | struct slime_Data *slime_data = (struct slime_Data *) &mob_data->data; 40 | 41 | data->x = x; 42 | data->y = y; 43 | 44 | mob_data->hp = 5 * (1 + lvl) * (1 + lvl); 45 | 46 | slime_data->level = lvl; 47 | 48 | level_add_entity(level, entity_id); 49 | } 50 | 51 | ETICK(slime_tick) { 52 | struct mob_Data *mob_data = (struct mob_Data *) &data->data; 53 | struct slime_Data *slime_data = (struct slime_Data *) &mob_data->data; 54 | 55 | mob_tick(level, data); 56 | 57 | bool move_result = mob_move( 58 | level, data, 59 | slime_data->xm, 60 | slime_data->ym 61 | ); 62 | 63 | if(slime_data->jump_time == -10 && (!move_result || random(40) == 0)) { 64 | slime_data->xm = random(3) - 1; 65 | slime_data->ym = random(3) - 1; 66 | 67 | struct entity_Data *player = &level->entities[0]; 68 | if(player->type < ENTITY_TYPES) { 69 | i32 xd = player->x - data->x; 70 | i32 yd = player->y - data->y; 71 | 72 | u32 dist = xd * xd + yd * yd; 73 | 74 | if(dist < 50 * 50) { 75 | if(xd != 0) 76 | slime_data->xm = (xd > 0) - (xd < 0); 77 | 78 | if(yd != 0) 79 | slime_data->ym = (yd > 0) - (yd < 0); 80 | } else if(dist >= DESPAWN_DISTANCE) { 81 | data->should_remove = true; 82 | return; 83 | } 84 | } 85 | 86 | if(slime_data->xm != 0 || slime_data->ym != 0) 87 | slime_data->jump_time = 10; 88 | } 89 | 90 | if(slime_data->jump_time > -10) 91 | slime_data->jump_time--; 92 | 93 | if(slime_data->jump_time == 0) 94 | slime_data->xm = slime_data->ym = 0; 95 | } 96 | 97 | EDRAW(slime_draw) { 98 | struct mob_Data *mob_data = (struct mob_Data *) &data->data; 99 | struct slime_Data *slime_data = (struct slime_Data *) &mob_data->data; 100 | 101 | const u8 hurt_time = mob_data->hurt_time; 102 | u8 palette = (hurt_time > 0) * 5 + 103 | (hurt_time == 0) * slime_data->level; 104 | 105 | sprite_config(used_sprites, &(struct Sprite) { 106 | .x = data->x - 8 - level_x_offset, 107 | .y = data->y - 11 - level_y_offset, 108 | 109 | .priority = 2, 110 | 111 | .size = SPRITE_SIZE_16x16, 112 | .flip = 0, 113 | 114 | .tile = 140 + (slime_data->jump_time > 0) * 4, 115 | .palette = palette 116 | }); 117 | 118 | return 1; 119 | } 120 | 121 | ETOUCH_PLAYER(slime_touch_player) { 122 | struct mob_Data *mob_data = (struct mob_Data *) &data->data; 123 | struct slime_Data *slime_data = (struct slime_Data *) &mob_data->data; 124 | 125 | mob_hurt(level, player, 1 + slime_data->level, mob_data->dir); 126 | } 127 | 128 | const struct Entity slime_entity = { 129 | .tick = slime_tick, 130 | .draw = slime_draw, 131 | 132 | .xr = 4, 133 | .yr = 3, 134 | 135 | .is_solid = true, 136 | .hurt_by_tiles = true, 137 | .touch_player = slime_touch_player 138 | }; 139 | 140 | THUMB 141 | void mob_slime_die(struct Level *level, struct entity_Data *data) { 142 | u8 drop_count = 1 + random(2); 143 | for(u32 i = 0; i < drop_count; i++) 144 | entity_add_item(level, data->x, data->y, SLIME_ITEM, false); 145 | 146 | struct mob_Data *mob_data = (struct mob_Data *) &data->data; 147 | struct slime_Data *slime_data = (struct slime_Data *) &mob_data->data; 148 | 149 | struct entity_Data *player = &level->entities[0]; 150 | if(player->type < ENTITY_TYPES) 151 | score += 25 * (1 + slime_data->level); 152 | } 153 | -------------------------------------------------------------------------------- /src/scene/chest.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022, 2024 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "scene.h" 17 | 18 | #include "screen.h" 19 | #include "inventory.h" 20 | #include "item.h" 21 | #include "player.h" 22 | #include "furniture.h" 23 | 24 | static i32 selected[2] = { 0, 0 }; 25 | static u8 chest_window; 26 | 27 | static struct Inventory *chest_inventory; 28 | 29 | THUMB 30 | static void chest_init(u8 flags) { 31 | selected[0] = 0; 32 | selected[1] = 0; 33 | 34 | chest_window = 0; 35 | 36 | chest_inventory = &chest_inventories[chest_opened_id]; 37 | } 38 | 39 | THUMB 40 | static void chest_tick(void) { 41 | gametime++; 42 | 43 | if(input_press(KEY_B) || input_press(KEY_START)) 44 | set_scene(&scene_game, 1); 45 | 46 | if(input_repeat(KEY_LEFT)) 47 | chest_window = 0; 48 | if(input_repeat(KEY_RIGHT)) 49 | chest_window = 1; 50 | 51 | struct Inventory *inv[2]; 52 | inv[chest_window] = chest_inventory; 53 | inv[chest_window ^ 1] = &player_inventory; 54 | 55 | if(inv[0]->size == 0) 56 | return; 57 | 58 | if(input_repeat(KEY_UP)) 59 | selected[chest_window]--; 60 | if(input_repeat(KEY_DOWN)) 61 | selected[chest_window]++; 62 | 63 | if(selected[chest_window] < 0) 64 | selected[chest_window] = inv[0]->size - 1; 65 | if(selected[chest_window] >= inv[0]->size) 66 | selected[chest_window] = 0; 67 | 68 | if(input_repeat(KEY_A)) { 69 | struct item_Data removed; 70 | inventory_remove(inv[0], &removed, selected[chest_window]); 71 | 72 | bool could_add; 73 | if(item_is_resource(removed.type)) { 74 | could_add = inventory_add_resource( 75 | inv[1], 76 | removed.type, removed.count, 77 | selected[chest_window ^ 1] 78 | ); 79 | } else { 80 | could_add = inventory_add( 81 | inv[1], &removed, selected[chest_window ^ 1] 82 | ); 83 | } 84 | 85 | if(could_add) { 86 | if(selected[chest_window] == inv[0]->size && 87 | inv[0]->size > 0) { 88 | selected[chest_window]--; 89 | } 90 | } else { 91 | // put the item back into the current inventory 92 | inventory_add(inv[0], &removed, selected[chest_window]); 93 | } 94 | } 95 | } 96 | 97 | THUMB 98 | static void chest_draw(void) { 99 | // clear the screen (fully transparent) 100 | for(u32 y = 0; y < 18; y++) 101 | for(u32 x = 0; x < 30; x += 2) 102 | *((vu32 *) &BG3_TILEMAP[x + y * 32]) = 0; 103 | 104 | for(u32 frame = 0; frame < 2; frame++) { 105 | u8 frame_x; 106 | const u8 frame_y = 2; 107 | const u8 frame_w = 12; 108 | const u8 frame_h = 14; 109 | 110 | struct Inventory *inventory; 111 | 112 | if(frame == 0) { 113 | frame_x = 2 + (chest_window == 0) * 2; 114 | inventory = chest_inventory; 115 | } else { 116 | frame_x = 16 - (chest_window == 1) * 2; 117 | inventory = &player_inventory; 118 | } 119 | 120 | // draw frame 121 | screen_draw_frame( 122 | (frame == 0 ? "CHEST" : "INVENTORY"), 123 | frame_x, frame_y, frame_w, frame_h 124 | ); 125 | 126 | // draw items 127 | i8 item0 = selected[frame] - (frame_h - 2) / 2; 128 | if(item0 > inventory->size - (frame_h - 2)) 129 | item0 = inventory->size - (frame_h - 2); 130 | if(item0 < 0) 131 | item0 = 0; 132 | 133 | for(u32 i = 0; i < frame_h - 2; i++) { 134 | if(item0 + i >= inventory->size) 135 | break; 136 | 137 | struct item_Data *data = &inventory->items[item0 + i]; 138 | 139 | item_draw_icon(data, frame_x + 1, frame_y + 1 + i, false); 140 | item_write(data, 6, frame_x + 2, frame_y + 1 + i); 141 | } 142 | 143 | // draw cursor arrows 144 | if(chest_window == frame) { 145 | u32 cursor_y = frame_y + 1 + (selected[frame] - item0); 146 | screen_write(">", 6, frame_x, cursor_y); 147 | screen_write("<", 6, frame_x + frame_w - 1, cursor_y); 148 | } 149 | } 150 | } 151 | 152 | const struct Scene scene_chest = { 153 | .init = chest_init, 154 | 155 | .tick = chest_tick, 156 | .draw = chest_draw 157 | }; 158 | -------------------------------------------------------------------------------- /src/entity/zombie.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "entity.h" 17 | 18 | #include "mob.h" 19 | 20 | struct zombie_Data { 21 | i8 xm : 2; 22 | i8 ym : 2; 23 | 24 | u8 level : 2; 25 | u8 move_flag : 1; 26 | 27 | u8 random_walk_time; 28 | }; 29 | 30 | static_assert( 31 | sizeof(struct zombie_Data) == 2, 32 | "struct zombie_Data: wrong size" 33 | ); 34 | 35 | THUMB 36 | void entity_add_zombie(struct Level *level, u16 x, u16 y, u8 lvl) { 37 | u8 entity_id = level_new_entity(level, ZOMBIE_ENTITY); 38 | if(entity_id >= ENTITY_LIMIT) 39 | return; 40 | 41 | struct entity_Data *data = &level->entities[entity_id]; 42 | struct mob_Data *mob_data = (struct mob_Data *) &data->data; 43 | struct zombie_Data *zombie_data = (struct zombie_Data *) &mob_data->data; 44 | 45 | data->x = x; 46 | data->y = y; 47 | 48 | mob_data->hp = 10 * (1 + lvl) * (1 + lvl); 49 | mob_data->dir = 2; 50 | 51 | zombie_data->level = lvl; 52 | 53 | level_add_entity(level, entity_id); 54 | } 55 | 56 | ETICK(zombie_tick) { 57 | struct mob_Data *mob_data = (struct mob_Data *) &data->data; 58 | struct zombie_Data *zombie_data = (struct zombie_Data *) &mob_data->data; 59 | 60 | mob_tick(level, data); 61 | 62 | struct entity_Data *player = &level->entities[0]; 63 | if(player->type < ENTITY_TYPES && zombie_data->random_walk_time == 0) { 64 | i32 xd = player->x - data->x; 65 | i32 yd = player->y - data->y; 66 | 67 | u32 dist = xd * xd + yd * yd; 68 | 69 | if(dist < 50 * 50) { 70 | zombie_data->xm = (xd > 0) - (xd < 0); 71 | zombie_data->ym = (yd > 0) - (yd < 0); 72 | } else if(dist >= DESPAWN_DISTANCE) { 73 | data->should_remove = true; 74 | return; 75 | } 76 | } 77 | 78 | zombie_data->move_flag ^= 1; 79 | bool move_result = mob_move( 80 | level, data, 81 | zombie_data->xm * zombie_data->move_flag, 82 | zombie_data->ym * zombie_data->move_flag 83 | ); 84 | 85 | if(!move_result || random(200) == 0) { 86 | zombie_data->random_walk_time = 60; 87 | zombie_data->xm = random(2) * (random(3) - 1); 88 | zombie_data->ym = random(2) * (random(3) - 1); 89 | } 90 | 91 | if(zombie_data->random_walk_time > 0) 92 | zombie_data->random_walk_time--; 93 | } 94 | 95 | EDRAW(zombie_draw) { 96 | struct mob_Data *mob_data = (struct mob_Data *) &data->data; 97 | struct zombie_Data *zombie_data = (struct zombie_Data *) &mob_data->data; 98 | 99 | const u8 dir = mob_data->dir; 100 | const u8 walk_dist = mob_data->walk_dist; 101 | const u8 hurt_time = mob_data->hurt_time; 102 | 103 | u16 sprite = 0 + (dir == 0) * 4 + (dir & 1) * 8; 104 | sprite += (dir & 1) * ( 105 | ((walk_dist >> 3) & 1) * (4 + ((walk_dist >> 4) & 1) * 4) 106 | ); 107 | 108 | u8 palette = (hurt_time > 0) * 5 + 109 | (hurt_time == 0) * zombie_data->level; 110 | 111 | sprite_config(used_sprites, &(struct Sprite) { 112 | .x = data->x - 8 - level_x_offset, 113 | .y = data->y - 11 - level_y_offset, 114 | 115 | .priority = 2, 116 | 117 | .size = SPRITE_SIZE_16x16, 118 | .flip = ((dir & 1) == 0) * ((walk_dist >> 3) & 1) + (dir == 1), 119 | 120 | .tile = sprite, 121 | .palette = palette 122 | }); 123 | 124 | return 1; 125 | } 126 | 127 | ETOUCH_PLAYER(zombie_touch_player) { 128 | struct mob_Data *mob_data = (struct mob_Data *) &data->data; 129 | struct zombie_Data *zombie_data = (struct zombie_Data *) &mob_data->data; 130 | 131 | mob_hurt(level, player, 2 + zombie_data->level, mob_data->dir); 132 | } 133 | 134 | const struct Entity zombie_entity = { 135 | .tick = zombie_tick, 136 | .draw = zombie_draw, 137 | 138 | .xr = 4, 139 | .yr = 3, 140 | 141 | .is_solid = true, 142 | .hurt_by_tiles = true, 143 | .touch_player = zombie_touch_player 144 | }; 145 | 146 | THUMB 147 | void mob_zombie_die(struct Level *level, struct entity_Data *data) { 148 | u8 drop_count = 1 + random(2); 149 | for(u32 i = 0; i < drop_count; i++) 150 | entity_add_item(level, data->x, data->y, CLOTH_ITEM, false); 151 | 152 | struct mob_Data *mob_data = (struct mob_Data *) &data->data; 153 | struct zombie_Data *zombie_data = (struct zombie_Data *) &mob_data->data; 154 | 155 | struct entity_Data *player = &level->entities[0]; 156 | if(player->type < ENTITY_TYPES) 157 | score += 50 * (1 + zombie_data->level); 158 | } 159 | -------------------------------------------------------------------------------- /res/resources.json: -------------------------------------------------------------------------------- 1 | { 2 | "input_dir": "res", 3 | "output_dir": "src/res", 4 | 5 | "tilesets": [ 6 | { 7 | "name": "level_tileset", 8 | "input": "images/level.png", 9 | "output": "images/level.c", 10 | "static": true, 11 | 12 | "tile_size": 2, 13 | "palette": "res/palettes/background.png", 14 | "bpp": 4 15 | }, { 16 | "name": "logo_tileset", 17 | "input": "images/logo.png", 18 | "output": "images/logo.c", 19 | "static": true, 20 | 21 | "tile_size": 1, 22 | "palette": "res/palettes/background.png", 23 | "bpp": 4 24 | }, { 25 | "name": "font_tileset", 26 | "input": "images/font.png", 27 | "output": "images/font.c", 28 | "static": true, 29 | 30 | "tile_size": 1, 31 | "palette": "res/palettes/background.png", 32 | "bpp": 4 33 | }, { 34 | "name": "gui_tileset", 35 | "input": "images/gui.png", 36 | "output": "images/gui.c", 37 | "static": true, 38 | 39 | "tile_size": 2, 40 | "palette": "res/palettes/background.png", 41 | "bpp": 4 42 | }, { 43 | "name": "level_light_tileset", 44 | "input": "images/level-light.png", 45 | "output": "images/level-light.c", 46 | "static": true, 47 | 48 | "tile_size": 1, 49 | "colors": { "#ffffff": 0, "#000000": 15 }, 50 | "bpp": 4 51 | }, 52 | 53 | { 54 | "name": "entities_tileset", 55 | "input": "images/entities.png", 56 | "output": "images/entities.c", 57 | "static": true, 58 | 59 | "tile_size": 2, 60 | "palette": "res/palettes/sprites.png", 61 | "bpp": 4 62 | }, { 63 | "name": "sparks_tileset", 64 | "input": "images/sparks.png", 65 | "output": "images/sparks.c", 66 | "static": true, 67 | 68 | "tile_size": [ 1, 2 ], 69 | "palette": "res/palettes/sprites.png", 70 | "bpp": 4 71 | }, { 72 | "name": "player_light_tileset", 73 | "input": "images/player-light.png", 74 | "output": "images/player-light.c", 75 | "static": true, 76 | 77 | "tile_size": 1, 78 | "colors": { "#000000": 0, "#ffffff": 1 }, 79 | "bpp": 4 80 | }, { 81 | "name": "player_lantern_light_tileset", 82 | "input": "images/player-lantern-light.png", 83 | "output": "images/player-lantern-light.c", 84 | "static": true, 85 | 86 | "tile_size": 8, 87 | "colors": { "#000000": 0, "#ffffff": 1 }, 88 | "bpp": 4 89 | }, { 90 | "name": "text_particle_tileset", 91 | "input": "images/text-particle.png", 92 | "output": "images/text-particle.c", 93 | "static": true, 94 | 95 | "tile_size": 1, 96 | "palette": "res/palettes/sprites.png", 97 | "bpp": 4 98 | }, 99 | 100 | { 101 | "name": "items_bg_tileset", 102 | "input": "images/items.png", 103 | "output": "images/items_bg.c", 104 | "static": true, 105 | 106 | "tile_size": 1, 107 | "palette": "res/palettes/items.png", 108 | "colors": { "#ff00ff": 15 }, 109 | "bpp": 4 110 | }, { 111 | "name": "items_spr_tileset", 112 | "input": "images/items.png", 113 | "output": "images/items_spr.c", 114 | "static": true, 115 | 116 | "tile_size": 1, 117 | "palette": "res/palettes/items.png", 118 | "bpp": 4 119 | } 120 | ], 121 | 122 | "palettes": [ 123 | { 124 | "name": "background_palette", 125 | "input": "palettes/background.png", 126 | "output": "palettes/background.c", 127 | "static": true 128 | }, { 129 | "name": "sprites_palette", 130 | "input": "palettes/sprites.png", 131 | "output": "palettes/sprites.c", 132 | "static": true 133 | }, { 134 | "name": "items_palette", 135 | "input": "palettes/items.png", 136 | "output": "palettes/items.c", 137 | "static": true 138 | } 139 | ], 140 | 141 | "files": [ 142 | { 143 | "name": "sound_start", 144 | "input": "sounds/start.raw", 145 | "output": "sounds/start.c" 146 | }, { 147 | "name": "sound_pickup", 148 | "input": "sounds/pickup.raw", 149 | "output": "sounds/pickup.c" 150 | }, { 151 | "name": "sound_craft", 152 | "input": "sounds/craft.raw", 153 | "output": "sounds/craft.c" 154 | }, { 155 | "name": "sound_monster_hurt", 156 | "input": "sounds/monster_hurt.raw", 157 | "output": "sounds/monster_hurt.c" 158 | }, { 159 | "name": "sound_player_hurt", 160 | "input": "sounds/player_hurt.raw", 161 | "output": "sounds/player_hurt.c" 162 | }, { 163 | "name": "sound_player_death", 164 | "input": "sounds/player_death.raw", 165 | "output": "sounds/player_death.c" 166 | }, { 167 | "name": "sound_boss_death", 168 | "input": "sounds/boss_death.raw", 169 | "output": "sounds/boss_death.c" 170 | } 171 | ] 172 | } 173 | -------------------------------------------------------------------------------- /src/entity/item.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "entity.h" 17 | 18 | #include "tile.h" 19 | #include "player.h" 20 | #include "inventory.h" 21 | #include "sound.h" 22 | 23 | struct item_entity_Data { 24 | u16 item_type : 6; 25 | u16 time : 10; 26 | 27 | // 64 xx = 1 x 28 | i8 xx; 29 | i8 xv; 30 | 31 | // 64 yy = 1 y 32 | i8 yy; 33 | i8 yv; 34 | 35 | // 6 zz = 1 z 36 | i16 zz : 10; 37 | i16 zv : 6; 38 | }; 39 | 40 | static_assert( 41 | sizeof(struct item_entity_Data) == 8, 42 | "struct item_entity_Data: wrong size" 43 | ); 44 | 45 | void entity_add_item(struct Level *level, u16 x, u16 y, 46 | u8 item, bool is_tile) { 47 | u8 entity_id = level_new_entity(level, ITEM_ENTITY); 48 | if(entity_id >= ENTITY_LIMIT) 49 | return; 50 | 51 | struct entity_Data *data = &level->entities[entity_id]; 52 | struct item_entity_Data *item_entity_data = 53 | (struct item_entity_Data *) &data->data; 54 | 55 | if(is_tile) { 56 | data->x = (x << 4) + 3 + random(10); 57 | data->y = (y << 4) + 3 + random(10); 58 | } else { 59 | data->x = x - 5 + random(11); 60 | data->y = y - 5 + random(11); 61 | } 62 | 63 | item_entity_data->xv = random(59) - 29; 64 | item_entity_data->yv = random(39) - 19; 65 | 66 | item_entity_data->zz = 12; 67 | item_entity_data->zv = 6 + random(4); 68 | 69 | item_entity_data->item_type = item; 70 | item_entity_data->time = 10 * 60 + random(60); 71 | 72 | // use solid_id to store the take delay 73 | data->solid_id = 30; 74 | 75 | level_add_entity(level, entity_id); 76 | } 77 | 78 | ETICK(item_tick) { 79 | struct item_entity_Data *item_entity_data = 80 | (struct item_entity_Data *) &data->data; 81 | 82 | item_entity_data->time--; 83 | if(item_entity_data->time == 0) { 84 | data->should_remove = true; 85 | return; 86 | } 87 | 88 | // use solid_id to store the take delay 89 | if(data->solid_id > 0) 90 | data->solid_id--; 91 | 92 | // check if player can take 93 | struct entity_Data *player = &level->entities[0]; 94 | if(data->solid_id == 0 && player->type < ENTITY_TYPES) { 95 | const struct Entity *entity = ENTITY_S(data); 96 | 97 | i32 x0 = data->x - entity->xr; 98 | i32 y0 = data->y - entity->yr; 99 | i32 x1 = data->x + entity->xr; 100 | i32 y1 = data->y + entity->yr; 101 | 102 | if(entity_intersects(player, x0, y0, x1, y1)) { 103 | bool could_add = inventory_add_resource( 104 | &player_inventory, 105 | item_entity_data->item_type, 1, 106 | player_inventory.size 107 | ); 108 | 109 | if(could_add) { 110 | data->should_remove = true; 111 | 112 | score++; 113 | 114 | SOUND_PLAY(sound_pickup); 115 | } 116 | } 117 | } 118 | 119 | // movement 120 | item_entity_data->xx += item_entity_data->xv; 121 | item_entity_data->yy += item_entity_data->yv; 122 | 123 | item_entity_data->zz += item_entity_data->zv; 124 | if(item_entity_data->zz < 0) { 125 | item_entity_data->zz = 0; 126 | 127 | item_entity_data->zv /= -2; 128 | 129 | item_entity_data->xv = item_entity_data->xv * 3 / 5; 130 | item_entity_data->yv = item_entity_data->yv * 3 / 5; 131 | } 132 | item_entity_data->zv--; 133 | 134 | entity_move( 135 | level, data, 136 | item_entity_data->xx / 64, 137 | item_entity_data->yy / 64 138 | ); 139 | 140 | item_entity_data->xx %= 64; 141 | item_entity_data->yy %= 64; 142 | } 143 | 144 | EDRAW(item_draw) { 145 | struct item_entity_Data *item_entity_data = 146 | (struct item_entity_Data *) &data->data; 147 | 148 | if(item_entity_data->time < 2 * 60) 149 | if(((item_entity_data->time / 6) & 1) == 0) 150 | return 0; 151 | 152 | const struct Item *item = &item_list[item_entity_data->item_type]; 153 | const u16 sprite = 256 + item_entity_data->item_type; 154 | 155 | // draw item sprite 156 | sprite_config(used_sprites, &(struct Sprite) { 157 | .x = data->x - 4 - level_x_offset, 158 | .y = data->y - 4 - (item_entity_data->zz / 6) - level_y_offset, 159 | 160 | .priority = 2, 161 | 162 | .size = SPRITE_SIZE_8x8, 163 | .flip = 0, 164 | 165 | .tile = sprite, 166 | .palette = 12 + item->palette 167 | }); 168 | 169 | bool should_draw_shadow = (item_entity_data->zz != 0); 170 | if(should_draw_shadow && used_sprites < 128 - 1) { 171 | used_sprites++; 172 | 173 | // draw shadow sprite 174 | sprite_config(used_sprites, &(struct Sprite) { 175 | .x = data->x - 4 - level_x_offset, 176 | .y = data->y - 4 - level_y_offset, 177 | 178 | .priority = 2, 179 | 180 | .size = SPRITE_SIZE_8x8, 181 | .flip = 0, 182 | 183 | .tile = sprite, 184 | .palette = 7 185 | }); 186 | } 187 | 188 | return 1 + should_draw_shadow; 189 | } 190 | 191 | const struct Entity item_entity = { 192 | .tick = item_tick, 193 | .draw = item_draw, 194 | 195 | .xr = 3, 196 | .yr = 3 197 | }; 198 | -------------------------------------------------------------------------------- /src/entity.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "entity.h" 17 | 18 | #include "level.h" 19 | #include "tile.h" 20 | #include "mob.h" 21 | 22 | IWRAM_RODATA_SECTION 23 | const struct Entity * const entity_list[ENTITY_TYPES] = { 24 | &zombie_entity, 25 | &slime_entity, 26 | &air_wizard_entity, 27 | &player_entity, 28 | 29 | &workbench_entity, 30 | &furnace_entity, 31 | &oven_entity, 32 | &anvil_entity, 33 | &chest_entity, 34 | &lantern_entity, 35 | 36 | &item_entity, 37 | &spark_entity, 38 | 39 | &text_particle_entity, 40 | &smash_particle_entity 41 | }; 42 | 43 | IWRAM_SECTION 44 | bool entity_move(struct Level *level, struct entity_Data *data, 45 | i32 xm, i32 ym) { 46 | if(xm == 0 && ym == 0) 47 | return true; 48 | 49 | bool stopped = true; 50 | if(xm != 0 && entity_move2(level, data, xm, 0)) 51 | stopped = false; 52 | if(ym != 0 && entity_move2(level, data, 0, ym)) 53 | stopped = false; 54 | 55 | if(!stopped) { 56 | i32 xt = data->x >> 4; 57 | i32 yt = data->y >> 4; 58 | 59 | const struct Tile *tile = LEVEL_GET_TILE_S(level, xt, yt); 60 | if(tile->stepped_on) 61 | tile->stepped_on(level, xt, yt, data); 62 | } 63 | 64 | return !stopped; 65 | } 66 | 67 | IWRAM_SECTION 68 | bool entity_move2(struct Level *level, struct entity_Data *data, 69 | i32 xm, i32 ym) { 70 | const struct Entity *entity = ENTITY_S(data); 71 | 72 | u32 tiles_to_check; 73 | i32 tiles[2][2]; 74 | 75 | i32 xto0 = (data->x - entity->xr) >> 4; 76 | i32 yto0 = (data->y - entity->yr) >> 4; 77 | i32 xto1 = (data->x + entity->xr) >> 4; 78 | i32 yto1 = (data->y + entity->yr) >> 4; 79 | 80 | i32 xt0 = (data->x + xm - entity->xr) >> 4; 81 | i32 yt0 = (data->y + ym - entity->yr) >> 4; 82 | i32 xt1 = (data->x + xm + entity->xr) >> 4; 83 | i32 yt1 = (data->y + ym + entity->yr) >> 4; 84 | 85 | if(xm < 0) { 86 | tiles[0][0] = xt0; tiles[0][1] = yt0; 87 | tiles[1][0] = xt0; tiles[1][1] = yt1; 88 | 89 | tiles_to_check = (xt0 != xto0) * (1 + (yt0 != yt1)); 90 | } else if(xm > 0) { 91 | tiles[0][0] = xt1; tiles[0][1] = yt0; 92 | tiles[1][0] = xt1; tiles[1][1] = yt1; 93 | 94 | tiles_to_check = (xt1 != xto1) * (1 + (yt0 != yt1)); 95 | } else if(ym < 0) { 96 | tiles[0][0] = xt0; tiles[0][1] = yt0; 97 | tiles[1][0] = xt1; tiles[1][1] = yt0; 98 | 99 | tiles_to_check = (yt0 != yto0) * (1 + (xt0 != xt1)); 100 | } else { 101 | tiles[0][0] = xt0; tiles[0][1] = yt1; 102 | tiles[1][0] = xt1; tiles[1][1] = yt1; 103 | 104 | tiles_to_check = (yt1 != yto1) * (1 + (xt0 != xt1)); 105 | } 106 | 107 | for(u32 i = 0; i < tiles_to_check; i++) { 108 | const i32 *t = tiles[i]; 109 | const struct Tile *tile = LEVEL_GET_TILE_S(level, t[0], t[1]); 110 | 111 | if(tile->touch_damage && entity->hurt_by_tiles) { 112 | struct mob_Data *mob_data = (struct mob_Data *) &data->data; 113 | mob_hurt(level, data, tile->touch_damage, mob_data->dir ^ 2); 114 | } 115 | 116 | if(tile->is_solid && tile->may_pass != data->type) 117 | return false; 118 | } 119 | 120 | // solid entity collision 121 | xt0--; 122 | yt0--; 123 | xt1++; 124 | yt1++; 125 | 126 | if(xt0 < 0) xt0 = 0; 127 | if(yt0 < 0) yt0 = 0; 128 | if(xt1 >= LEVEL_W) xt1 = LEVEL_W - 1; 129 | if(yt1 >= LEVEL_H) yt1 = LEVEL_H - 1; 130 | 131 | i32 xo0 = data->x - entity->xr; 132 | i32 yo0 = data->y - entity->yr; 133 | i32 xo1 = data->x + entity->xr; 134 | i32 yo1 = data->y + entity->yr; 135 | 136 | i32 x0 = data->x + xm - entity->xr; 137 | i32 y0 = data->y + ym - entity->yr; 138 | i32 x1 = data->x + xm + entity->xr; 139 | i32 y1 = data->y + ym + entity->yr; 140 | 141 | bool blocked_by_entity = false; 142 | for(u32 yt = yt0; yt <= yt1; yt++) { 143 | for(u32 xt = xt0; xt <= xt1; xt++) { 144 | const u32 tile = xt + yt * LEVEL_W; 145 | 146 | for(u32 i = 0; i < SOLID_ENTITIES_IN_TILE; i++) { 147 | const u8 entity_id = level_solid_entities[tile][i]; 148 | if(entity_id >= ENTITY_LIMIT) 149 | continue; 150 | 151 | struct entity_Data *e_data = &level->entities[entity_id]; 152 | if(e_data == data) 153 | continue; 154 | 155 | if(entity_intersects(e_data, x0, y0, x1, y1)) { 156 | if(!blocked_by_entity) 157 | if(!entity_intersects(e_data, xo0, yo0, xo1, yo1)) 158 | blocked_by_entity = true; 159 | 160 | // item entity doesn't have a touch_player function 161 | if(data->type == ITEM_ENTITY) { 162 | if(blocked_by_entity) 163 | return false; 164 | else 165 | continue; 166 | } 167 | 168 | // touch player 169 | if(data->type == PLAYER_ENTITY) { 170 | const struct Entity *entity = ENTITY_S(e_data); 171 | entity->touch_player(level, e_data, data); 172 | } else if(e_data->type == PLAYER_ENTITY) { 173 | const struct Entity *entity = ENTITY_S(data); 174 | entity->touch_player(level, data, e_data); 175 | } 176 | } 177 | } 178 | } 179 | } 180 | 181 | if(blocked_by_entity) 182 | return false; 183 | 184 | data->x += xm; 185 | data->y += ym; 186 | return true; 187 | } 188 | -------------------------------------------------------------------------------- /src/entity/air-wizard.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "entity.h" 17 | #include "air-wizard.h" 18 | 19 | #include "mob.h" 20 | #include "player.h" 21 | #include "scene.h" 22 | #include "sound.h" 23 | 24 | struct wizard_Data { 25 | i8 xm : 2; 26 | i8 ym : 2; 27 | 28 | u8 move_flag : 2; 29 | u8 attack_type : 2; 30 | 31 | u8 random_walk_time; 32 | }; 33 | 34 | static_assert( 35 | sizeof(struct wizard_Data) == 2, 36 | "struct wizard_Data: wrong size" 37 | ); 38 | 39 | u8 air_wizard_attack_delay; 40 | u8 air_wizard_attack_time; 41 | 42 | THUMB 43 | void entity_add_air_wizard(struct Level *level) { 44 | u8 entity_id = level_new_entity(level, AIR_WIZARD_ENTITY); 45 | if(entity_id >= ENTITY_LIMIT) 46 | return; 47 | 48 | struct entity_Data *data = &level->entities[entity_id]; 49 | struct mob_Data *mob_data = (struct mob_Data *) &data->data; 50 | 51 | data->x = (LEVEL_W << 4) / 2; 52 | data->y = (LEVEL_W << 4) / 2; 53 | 54 | mob_data->hp = 2000; 55 | mob_data->dir = 2; 56 | 57 | air_wizard_attack_delay = 0; 58 | air_wizard_attack_time = 0; 59 | } 60 | 61 | ETICK(air_wizard_tick) { 62 | struct mob_Data *mob_data = (struct mob_Data *) &data->data; 63 | struct wizard_Data *wizard_data = (struct wizard_Data *) &mob_data->data; 64 | 65 | mob_tick(level, data); 66 | 67 | // spin 68 | if(air_wizard_attack_delay > 0) { 69 | if(air_wizard_attack_delay < 45) 70 | mob_data->dir = 2; 71 | else 72 | mob_data->dir = 3 - ((air_wizard_attack_delay - 41) / 4) % 4; 73 | 74 | air_wizard_attack_delay--; 75 | if(air_wizard_attack_delay == 0) { 76 | air_wizard_attack_time = 2 * 60; 77 | wizard_data->attack_type = (mob_data->hp < 1000) + 78 | (mob_data->hp < 200); 79 | } 80 | return; 81 | } 82 | 83 | if(air_wizard_attack_time > 0) { 84 | air_wizard_attack_time--; 85 | 86 | // angle = attack_time * 0.25 radians 87 | // = attack_time * 0.25 * math_brad(180) / PI 88 | // ~= attack_time * 2608 brad 89 | i32 angle = air_wizard_attack_time * 2608; 90 | if(air_wizard_attack_time % 2) 91 | angle *= -1; 92 | 93 | // speed = 0.7 + attack_type * 0.2, scaled by 256 94 | i32 speed = 179 + wizard_data->attack_type * 51; 95 | 96 | entity_add_spark( 97 | level, data->x, data->y, 98 | speed * math_cos(angle) / 0x10000, // fixed-point, 1 = 64 99 | speed * math_sin(angle) / 0x10000 // fixed-point, 1 = 64 100 | ); 101 | return; 102 | } 103 | 104 | struct entity_Data *player = &level->entities[0]; 105 | if(player->type < ENTITY_TYPES && wizard_data->random_walk_time == 0) { 106 | i32 xd = player->x - data->x; 107 | i32 yd = player->y - data->y; 108 | 109 | u32 dist = xd * xd + yd * yd; 110 | 111 | if(dist < 32 * 32) { 112 | // run away 113 | wizard_data->xm = (xd < 0) - (xd > 0); 114 | wizard_data->ym = (yd < 0) - (yd > 0); 115 | } else if(dist > 80 * 80) { 116 | // come closer 117 | wizard_data->xm = (xd > 0) - (xd < 0); 118 | wizard_data->ym = (yd > 0) - (yd < 0); 119 | } 120 | } 121 | 122 | wizard_data->move_flag++; 123 | bool move_result = mob_move( 124 | level, data, 125 | wizard_data->xm * (wizard_data->move_flag != 0), 126 | wizard_data->ym * (wizard_data->move_flag != 0) 127 | ); 128 | 129 | if(!move_result || random(100) == 0) { 130 | wizard_data->random_walk_time = 30; 131 | wizard_data->xm = random(3) - 1; 132 | wizard_data->ym = random(3) - 1; 133 | } 134 | 135 | if(wizard_data->random_walk_time > 0) { 136 | wizard_data->random_walk_time--; 137 | 138 | if(player->type < ENTITY_TYPES && wizard_data->random_walk_time == 0) { 139 | i32 xd = player->x - data->x; 140 | i32 yd = player->y - data->y; 141 | 142 | u32 dist = xd * xd + yd * yd; 143 | 144 | if(air_wizard_attack_delay == 0 && air_wizard_attack_time == 0) { 145 | if(dist < 50 * 50 && random(4) == 0) 146 | air_wizard_attack_delay = 2 * 60; 147 | } 148 | } 149 | } 150 | } 151 | 152 | EDRAW(air_wizard_draw) { 153 | struct mob_Data *mob_data = (struct mob_Data *) &data->data; 154 | 155 | const u8 dir = mob_data->dir; 156 | const u8 walk_dist = mob_data->walk_dist; 157 | const u8 hurt_time = mob_data->hurt_time; 158 | 159 | u16 sprite = 120 + (dir == 0) * 4 + (dir & 1) * 8; 160 | sprite += (dir & 1) * ( 161 | ((walk_dist >> 3) & 1) * (4 + ((walk_dist >> 4) & 1) * 4) 162 | ); 163 | 164 | static u32 damage_animation = 0; 165 | damage_animation++; 166 | 167 | u8 palette = 0; 168 | if(hurt_time > 0) { 169 | palette = 5; 170 | } else if(mob_data->hp < 200) { 171 | if((damage_animation / 3) & 1) 172 | palette = 1; 173 | } else if(mob_data->hp < 1000) { 174 | if((damage_animation / 50) & 1) 175 | palette = 1; 176 | } 177 | 178 | sprite_config(used_sprites, &(struct Sprite) { 179 | .x = data->x - 8 - level_x_offset, 180 | .y = data->y - 11 - level_y_offset, 181 | 182 | .priority = 2, 183 | 184 | .size = SPRITE_SIZE_16x16, 185 | .flip = ((dir & 1) == 0) * ((walk_dist >> 3) & 1) + (dir == 1), 186 | 187 | .tile = sprite, 188 | .palette = palette 189 | }); 190 | 191 | return 1; 192 | } 193 | 194 | ETOUCH_PLAYER(air_wizard_touch_player) { 195 | struct mob_Data *mob_data = (struct mob_Data *) &data->data; 196 | mob_hurt(level, player, 3, mob_data->dir); 197 | } 198 | 199 | const struct Entity air_wizard_entity = { 200 | .tick = air_wizard_tick, 201 | .draw = air_wizard_draw, 202 | 203 | .xr = 4, 204 | .yr = 3, 205 | 206 | .is_solid = true, 207 | .touch_player = air_wizard_touch_player 208 | }; 209 | 210 | THUMB 211 | void mob_air_wizard_die(struct Level *level, struct entity_Data *data) { 212 | struct entity_Data *player = &level->entities[0]; 213 | if(player->type < ENTITY_TYPES) { 214 | score += 1000; 215 | 216 | player_invulnerable_time = 5 * 60; 217 | scene_win_timer = 3 * 60; 218 | } 219 | SOUND_PLAY(sound_boss_death); 220 | } 221 | -------------------------------------------------------------------------------- /src/item.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "item.h" 17 | 18 | #include "tile.h" 19 | #include "screen.h" 20 | 21 | const struct Item item_list[ITEM_TYPES] = { 22 | // Wood 23 | { 24 | .class = ITEMCLASS_MATERIAL, 25 | .name = "WOOD", 26 | .palette = 0 27 | }, 28 | 29 | // Stone 30 | { 31 | .class = ITEMCLASS_MATERIAL, 32 | .name = "STONE", 33 | .palette = 1 34 | }, 35 | 36 | // Glass 37 | { 38 | .class = ITEMCLASS_MATERIAL, 39 | .name = "GLASS", 40 | .palette = 1 41 | }, 42 | 43 | // Wheat 44 | { 45 | .class = ITEMCLASS_MATERIAL, 46 | .name = "WHEAT", 47 | .palette = 2 48 | }, 49 | 50 | // Slime 51 | { 52 | .class = ITEMCLASS_MATERIAL, 53 | .name = "SLIME", 54 | .palette = 1 55 | }, 56 | 57 | // Cloth 58 | { 59 | .class = ITEMCLASS_MATERIAL, 60 | .name = "CLOTH", 61 | .palette = 2 62 | }, 63 | 64 | // Coal 65 | { 66 | .class = ITEMCLASS_MATERIAL, 67 | .name = "COAL", 68 | .palette = 1 69 | }, 70 | 71 | // Iron Ore 72 | { 73 | .class = ITEMCLASS_MATERIAL, 74 | .name = "I.ORE", 75 | .palette = 0 76 | }, 77 | 78 | // Gold Ore 79 | { 80 | .class = ITEMCLASS_MATERIAL, 81 | .name = "G.ORE", 82 | .palette = 2 83 | }, 84 | 85 | // Iron Ingot 86 | { 87 | .class = ITEMCLASS_MATERIAL, 88 | .name = "IRON", 89 | .palette = 0 90 | }, 91 | 92 | // Gold Ingot 93 | { 94 | .class = ITEMCLASS_MATERIAL, 95 | .name = "GOLD", 96 | .palette = 2 97 | }, 98 | 99 | // Gem 100 | { 101 | .class = ITEMCLASS_MATERIAL, 102 | .name = "GEM", 103 | .palette = 2 104 | }, 105 | 106 | // ----- 107 | 108 | // Flower 109 | { 110 | .class = ITEMCLASS_PLACEABLE, 111 | .name = "FLOWER", 112 | .palette = 1, 113 | 114 | .placed_tile = FLOWER_TILE, 115 | .placeable_on = { GRASS_TILE, -1 } 116 | }, 117 | 118 | // Seeds 119 | { 120 | .class = ITEMCLASS_PLACEABLE, 121 | .name = "SEEDS", 122 | .palette = 1, 123 | 124 | .placed_tile = WHEAT_TILE, 125 | .placeable_on = { FARMLAND_TILE, -1 } 126 | }, 127 | 128 | // Acorn 129 | { 130 | .class = ITEMCLASS_PLACEABLE, 131 | .name = "ACORN", 132 | .palette = 0, 133 | 134 | .placed_tile = TREE_SAPLING_TILE, 135 | .placeable_on = { GRASS_TILE, -1 } 136 | }, 137 | 138 | // Cactus 139 | { 140 | .class = ITEMCLASS_PLACEABLE, 141 | .name = "CACTUS", 142 | .palette = 1, 143 | 144 | .placed_tile = CACTUS_SAPLING_TILE, 145 | .placeable_on = { SAND_TILE, -1 } 146 | }, 147 | 148 | // Dirt 149 | { 150 | .class = ITEMCLASS_PLACEABLE, 151 | .name = "DIRT", 152 | .palette = 0, 153 | 154 | .placed_tile = DIRT_TILE, 155 | .placeable_on = { HOLE_TILE, LIQUID_TILE } 156 | }, 157 | 158 | // Sand 159 | { 160 | .class = ITEMCLASS_PLACEABLE, 161 | .name = "SAND", 162 | .palette = 2, 163 | 164 | .placed_tile = SAND_TILE, 165 | .placeable_on = { GRASS_TILE, DIRT_TILE } 166 | }, 167 | 168 | // Cloud 169 | { 170 | .class = ITEMCLASS_PLACEABLE, 171 | .name = "CLOUD", 172 | .palette = 1, 173 | 174 | .placed_tile = CLOUD_TILE, 175 | .placeable_on = { INFINITE_FALL_TILE, -1 } 176 | }, 177 | 178 | // ----- 179 | 180 | // Apple 181 | { 182 | .class = ITEMCLASS_FOOD, 183 | .name = "APPLE", 184 | .palette = 0, 185 | 186 | .hp_gain = 1 187 | }, 188 | 189 | // Bread 190 | { 191 | .class = ITEMCLASS_FOOD, 192 | .name = "BREAD", 193 | .palette = 2, 194 | 195 | .hp_gain = 2 196 | }, 197 | 198 | // ----- 199 | 200 | // Workbench 201 | { 202 | .class = ITEMCLASS_FURNITURE, 203 | .name = "WORKBENCH", 204 | .palette = 3 205 | }, 206 | 207 | // Furnace 208 | { 209 | .class = ITEMCLASS_FURNITURE, 210 | .name = "FURNACE", 211 | .palette = 1 212 | }, 213 | 214 | // Oven 215 | { 216 | .class = ITEMCLASS_FURNITURE, 217 | .name = "OVEN", 218 | .palette = 1 219 | }, 220 | 221 | // Anvil 222 | { 223 | .class = ITEMCLASS_FURNITURE, 224 | .name = "ANVIL", 225 | .palette = 1 226 | }, 227 | 228 | // Chest 229 | { 230 | .class = ITEMCLASS_FURNITURE, 231 | .name = "CHEST", 232 | .palette = 2 233 | }, 234 | 235 | // Lantern 236 | { 237 | .class = ITEMCLASS_FURNITURE, 238 | .name = "LANTERN", 239 | .palette = 1 240 | }, 241 | 242 | // ----- 243 | 244 | // Power Glove 245 | { 246 | .class = ITEMCLASS_POWERGLOVE, 247 | .name = "POW GLOVE", 248 | .palette = 0 249 | }, 250 | 251 | // ----- 252 | 253 | // Sword 254 | { 255 | .class = ITEMCLASS_TOOL, 256 | .name = "SWRD", 257 | .palette = 3 258 | }, 259 | 260 | // Axe 261 | { 262 | .class = ITEMCLASS_TOOL, 263 | .name = "AXE", 264 | .palette = 3 265 | }, 266 | 267 | // Pick 268 | { 269 | .class = ITEMCLASS_TOOL, 270 | .name = "PICK", 271 | .palette = 3 272 | }, 273 | 274 | // Shovel 275 | { 276 | .class = ITEMCLASS_TOOL, 277 | .name = "SHVL", 278 | .palette = 3 279 | }, 280 | 281 | // Hoe 282 | { 283 | .class = ITEMCLASS_TOOL, 284 | .name = "HOE", 285 | .palette = 3 286 | } 287 | }; 288 | 289 | THUMB 290 | void item_write(struct item_Data *data, u8 palette, u32 x, u32 y) { 291 | const struct Item *item = ITEM_S(data); 292 | 293 | if(item_is_resource(data->type)) { 294 | u16 count = data->count; 295 | if(count > 999) 296 | count = 999; 297 | 298 | SCREEN_WRITE_NUMBER(count, 10, 3, false, palette + 3, x, y); 299 | screen_write(item->name, palette, x + 3, y); 300 | } else { 301 | item_write_name(data, palette, x, y); 302 | } 303 | } 304 | 305 | static const char level_names[5][5] = { 306 | "WOOD", "ROCK", "IRON", "GOLD", "GEM" 307 | }; 308 | 309 | THUMB 310 | void item_write_name(struct item_Data *data, u8 palette, u32 x, u32 y) { 311 | const struct Item *item = ITEM_S(data); 312 | 313 | if(item->class == ITEMCLASS_TOOL) { 314 | const u8 level = data->tool_level; 315 | 316 | screen_write(level_names[level], palette, x, y); 317 | screen_write(item->name, palette, x + 4 + (level != 4), y); 318 | } else { 319 | screen_write(item->name, palette, x, y); 320 | } 321 | } 322 | 323 | THUMB 324 | void item_draw_icon(struct item_Data *data, u32 x, u32 y, bool black_bg) { 325 | const struct Item *item = ITEM_S(data); 326 | 327 | const u16 tile = 128 + data->type + 328 | (item->class == ITEMCLASS_TOOL) * (2 + data->tool_level * 5); 329 | const u8 palette = (black_bg == false) * (12 + item->palette) + 330 | (black_bg == true) * 11; 331 | 332 | BG3_TILEMAP[x + y * 32] = tile | palette << 12; 333 | } 334 | -------------------------------------------------------------------------------- /src/scene/crafting.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "scene.h" 17 | 18 | #include "screen.h" 19 | #include "inventory.h" 20 | #include "item.h" 21 | #include "crafting.h" 22 | #include "player.h" 23 | #include "sound.h" 24 | 25 | static i32 selected; 26 | 27 | static u8 sorted_recipes[16]; 28 | static bool can_craft[16]; 29 | 30 | static u16 crafting_count(struct Inventory *inventory, 31 | u8 item_type, u8 tool_level) { 32 | if(item_is_resource(item_type)) { 33 | for(u32 i = 0; i < inventory->size; i++) { 34 | struct item_Data *item_data = &inventory->items[i]; 35 | 36 | // Bug in the original game: the possibility that items 37 | // might be split in two different slots (due to another 38 | // bug in inventory menu) is ignored, so only the first 39 | // item's count is checked. 40 | if(item_data->type == item_type) 41 | return item_data->count; 42 | } 43 | } else { 44 | u16 count = 0; 45 | for(u32 i = 0; i < inventory->size; i++) { 46 | struct item_Data *item_data = &inventory->items[i]; 47 | 48 | // FIXED BUG - Inventory.java:63 49 | // all furniture items are considered the same item 50 | if(item_data->type == item_type) { 51 | if(ITEM_S(item_data)->class != ITEMCLASS_TOOL || 52 | item_data->tool_level == tool_level) 53 | count++; 54 | } 55 | } 56 | return count; 57 | } 58 | return 0; 59 | } 60 | 61 | static void crafting_check_craftable(void) { 62 | for(u32 r = 0; r < crafting_current_recipes_size; r++) { 63 | const struct crafting_Recipe *recipe = &crafting_current_recipes[r]; 64 | 65 | can_craft[r] = true; 66 | for(u32 i = 0; i < CRAFTING_MAX_REQUIRED; i++) { 67 | u8 item_type = recipe->required.items[i]; 68 | u8 count = recipe->required.count[i]; 69 | 70 | if(count == 0) 71 | break; 72 | 73 | u16 has_count = crafting_count(&player_inventory, item_type, 0); 74 | if(has_count < count) { 75 | can_craft[r] = false; 76 | break; 77 | } 78 | } 79 | } 80 | } 81 | 82 | THUMB 83 | static void crafting_init(u8 flags) { 84 | selected = 0; 85 | 86 | crafting_check_craftable(); 87 | 88 | u32 pos = 0; 89 | for(u32 i = 0; i < crafting_current_recipes_size; i++) 90 | if(can_craft[i]) 91 | sorted_recipes[pos++] = i; 92 | 93 | for(u32 i = 0; i < crafting_current_recipes_size; i++) 94 | if(!can_craft[i]) 95 | sorted_recipes[pos++] = i; 96 | } 97 | 98 | THUMB 99 | static void crafting_tick(void) { 100 | gametime++; 101 | 102 | if(input_press(KEY_B) || input_press(KEY_START)) 103 | set_scene(&scene_game, 1); 104 | 105 | if(input_repeat(KEY_UP)) 106 | selected--; 107 | if(input_repeat(KEY_DOWN)) 108 | selected++; 109 | 110 | if(selected < 0) 111 | selected = crafting_current_recipes_size - 1; 112 | if(selected >= crafting_current_recipes_size) 113 | selected = 0; 114 | 115 | if(input_repeat(KEY_A)) { 116 | u8 recipe_id = sorted_recipes[selected]; 117 | 118 | if(can_craft[recipe_id]) { 119 | const struct crafting_Recipe *recipe = &crafting_current_recipes[ 120 | recipe_id 121 | ]; 122 | 123 | if(crafting_craft(&player_inventory, recipe)) { 124 | crafting_check_craftable(); 125 | 126 | SOUND_PLAY(sound_craft); 127 | } 128 | } 129 | } 130 | } 131 | 132 | THUMB 133 | static void crafting_draw(void) { 134 | const u8 craft_x = 5; 135 | const u8 craft_y = 2; 136 | const u8 craft_w = 12; 137 | const u8 craft_h = 14; 138 | 139 | const u8 have_x = craft_x + craft_w; 140 | const u8 have_y = craft_y; 141 | const u8 have_w = 8; 142 | const u8 have_h = 3; 143 | 144 | const u8 cost_x = have_x; 145 | const u8 cost_y = have_y + have_h; 146 | const u8 cost_w = have_w; 147 | const u8 cost_h = craft_h - have_h; 148 | 149 | screen_draw_frame("CRAFTING", craft_x, craft_y, craft_w, craft_h); 150 | screen_draw_frame("HAVE", have_x, have_y, have_w, have_h); 151 | screen_draw_frame("COST", cost_x, cost_y, cost_w, cost_h); 152 | 153 | i8 item0 = selected - (craft_h - 2) / 2; 154 | if(item0 > crafting_current_recipes_size - (craft_h - 2)) 155 | item0 = crafting_current_recipes_size - (craft_h - 2); 156 | if(item0 < 0) 157 | item0 = 0; 158 | 159 | // draw items 160 | for(u32 i = 0; i < craft_h - 2; i++) { 161 | if(item0 + i >= crafting_current_recipes_size) 162 | break; 163 | 164 | u8 recipe_id = sorted_recipes[item0 + i]; 165 | u8 palette; 166 | if(can_craft[recipe_id]) 167 | palette = 6; 168 | else 169 | palette = 7; 170 | 171 | const struct crafting_Recipe *recipe = 172 | &crafting_current_recipes[recipe_id]; 173 | 174 | struct item_Data data = { 175 | .type = recipe->result, 176 | .tool_level = recipe->tool_level 177 | }; 178 | 179 | item_draw_icon(&data, craft_x + 1, craft_y + 1 + i, false); 180 | item_write_name(&data, palette, craft_x + 2, craft_y + 1 + i); 181 | } 182 | 183 | // draw cursor arrows 184 | { 185 | const u32 cursor_y = craft_y + 1 + (selected - item0); 186 | screen_write(">", 6, craft_x, cursor_y); 187 | screen_write("<", 6, craft_x + craft_w - 1, cursor_y); 188 | } 189 | 190 | const struct crafting_Recipe *selected_recipe = 191 | &crafting_current_recipes[sorted_recipes[selected]]; 192 | 193 | // draw 'HAVE' item and count 194 | { 195 | struct item_Data result = { 196 | .type = selected_recipe->result, 197 | .tool_level = selected_recipe->tool_level 198 | }; 199 | item_draw_icon(&result, have_x + 1, have_y + 1, false); 200 | 201 | u16 count = crafting_count( 202 | &player_inventory, result.type, result.tool_level 203 | ); 204 | 205 | SCREEN_WRITE_NUMBER(count, 10, 5, false, 6, have_x + 2, have_y + 1); 206 | } 207 | 208 | // draw 'COST' items and count 209 | for(u32 i = 0; i < CRAFTING_MAX_REQUIRED; i++) { 210 | u8 item_type = selected_recipe->required.items[i]; 211 | u8 required_count = selected_recipe->required.count[i]; 212 | 213 | if(required_count == 0) 214 | break; 215 | 216 | struct item_Data required_item = { .type = item_type }; 217 | item_draw_icon(&required_item, cost_x + 1, cost_y + 1 + i, false); 218 | 219 | u16 has_count = crafting_count(&player_inventory, item_type, 0); 220 | if(has_count > 99) 221 | has_count = 99; 222 | 223 | char cost_text[6] = { 0 }; 224 | itoa(required_count, 10, cost_text, 2, false); 225 | cost_text[1 + (required_count > 9)] = '/'; 226 | itoa(has_count, 10, cost_text + 2 + (required_count > 9), 2, false); 227 | 228 | u8 palette = 6 + (has_count < required_count); 229 | screen_write(cost_text, palette, cost_x + 2, cost_y + 1 + i); 230 | } 231 | } 232 | 233 | const struct Scene scene_crafting = { 234 | .init = crafting_init, 235 | 236 | .tick = crafting_tick, 237 | .draw = crafting_draw 238 | }; 239 | -------------------------------------------------------------------------------- /src/crafting.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "crafting.h" 17 | 18 | #include "inventory.h" 19 | #include "item.h" 20 | #include "furniture.h" 21 | 22 | const struct crafting_Recipe workbench_recipes[WORKBENCH_RECIPES] = { 23 | // Furniture 24 | 25 | // Lantern 26 | { 27 | .required.items = { WOOD_ITEM, SLIME_ITEM, GLASS_ITEM }, 28 | .required.count = { 5, 10, 4 }, 29 | 30 | .result = LANTERN_ITEM 31 | }, 32 | 33 | // Oven 34 | { 35 | .required.items = { STONE_ITEM }, 36 | .required.count = { 15 }, 37 | 38 | .result = OVEN_ITEM 39 | }, 40 | 41 | // Furnace 42 | { 43 | .required.items = { STONE_ITEM }, 44 | .required.count = { 20 }, 45 | 46 | .result = FURNACE_ITEM 47 | }, 48 | 49 | // Workbench 50 | { 51 | .required.items = { WOOD_ITEM }, 52 | .required.count = { 20 }, 53 | 54 | .result = WORKBENCH_ITEM 55 | }, 56 | 57 | // Chest 58 | { 59 | .required.items = { WOOD_ITEM }, 60 | .required.count = { 20 }, 61 | 62 | .result = CHEST_ITEM 63 | }, 64 | 65 | // Anvil 66 | { 67 | .required.items = { IRON_INGOT_ITEM }, 68 | .required.count = { 5 }, 69 | 70 | .result = ANVIL_ITEM 71 | }, 72 | 73 | // Wood Tools 74 | 75 | // Wood Sword 76 | { 77 | .required.items = { WOOD_ITEM }, 78 | .required.count = { 5 }, 79 | 80 | .result = SWORD_ITEM, 81 | .tool_level = 0 82 | }, 83 | 84 | // Wood Axe 85 | { 86 | .required.items = { WOOD_ITEM }, 87 | .required.count = { 5 }, 88 | 89 | .result = AXE_ITEM, 90 | .tool_level = 0 91 | }, 92 | 93 | // Wood Hoe 94 | { 95 | .required.items = { WOOD_ITEM }, 96 | .required.count = { 5 }, 97 | 98 | .result = HOE_ITEM, 99 | .tool_level = 0 100 | }, 101 | 102 | // Wood Pick 103 | { 104 | .required.items = { WOOD_ITEM }, 105 | .required.count = { 5 }, 106 | 107 | .result = PICK_ITEM, 108 | .tool_level = 0 109 | }, 110 | 111 | // Wood Shovel 112 | { 113 | .required.items = { WOOD_ITEM }, 114 | .required.count = { 5 }, 115 | 116 | .result = SHOVEL_ITEM, 117 | .tool_level = 0 118 | }, 119 | 120 | // Stone Tools 121 | 122 | // Stone Sword 123 | { 124 | .required.items = { WOOD_ITEM, STONE_ITEM }, 125 | .required.count = { 5, 5 }, 126 | 127 | .result = SWORD_ITEM, 128 | .tool_level = 1 129 | }, 130 | 131 | // Stone Axe 132 | { 133 | .required.items = { WOOD_ITEM, STONE_ITEM }, 134 | .required.count = { 5, 5 }, 135 | 136 | .result = AXE_ITEM, 137 | .tool_level = 1 138 | }, 139 | 140 | // Stone Hoe 141 | { 142 | .required.items = { WOOD_ITEM, STONE_ITEM }, 143 | .required.count = { 5, 5 }, 144 | 145 | .result = HOE_ITEM, 146 | .tool_level = 1 147 | }, 148 | 149 | // Stone Pick 150 | { 151 | .required.items = { WOOD_ITEM, STONE_ITEM }, 152 | .required.count = { 5, 5 }, 153 | 154 | .result = PICK_ITEM, 155 | .tool_level = 1 156 | }, 157 | 158 | // Stone Shovel 159 | { 160 | .required.items = { WOOD_ITEM, STONE_ITEM }, 161 | .required.count = { 5, 5 }, 162 | 163 | .result = SHOVEL_ITEM, 164 | .tool_level = 1 165 | } 166 | }; 167 | 168 | const struct crafting_Recipe furnace_recipes[FURNACE_RECIPES] = { 169 | // Iron Ingot 170 | { 171 | .required.items = { IRON_ORE_ITEM, COAL_ITEM }, 172 | .required.count = { 4, 1 }, 173 | 174 | .result = IRON_INGOT_ITEM 175 | }, 176 | 177 | // Gold Ingot 178 | { 179 | .required.items = { GOLD_ORE_ITEM, COAL_ITEM }, 180 | .required.count = { 4, 1 }, 181 | 182 | .result = GOLD_INGOT_ITEM 183 | }, 184 | 185 | // Glass 186 | { 187 | .required.items = { SAND_ITEM, COAL_ITEM }, 188 | .required.count = { 4, 1 }, 189 | 190 | .result = GLASS_ITEM 191 | } 192 | }; 193 | 194 | const struct crafting_Recipe oven_recipes[OVEN_RECIPES] = { 195 | // Bread 196 | { 197 | .required.items = { WHEAT_ITEM }, 198 | .required.count = { 4 }, 199 | 200 | .result = BREAD_ITEM 201 | } 202 | }; 203 | 204 | const struct crafting_Recipe anvil_recipes[ANVIL_RECIPES] = { 205 | // Iron Tools 206 | 207 | // Iron Sword 208 | { 209 | .required.items = { WOOD_ITEM, IRON_INGOT_ITEM }, 210 | .required.count = { 5, 5 }, 211 | 212 | .result = SWORD_ITEM, 213 | .tool_level = 2 214 | }, 215 | 216 | // Iron Axe 217 | { 218 | .required.items = { WOOD_ITEM, IRON_INGOT_ITEM }, 219 | .required.count = { 5, 5 }, 220 | 221 | .result = AXE_ITEM, 222 | .tool_level = 2 223 | }, 224 | 225 | // Iron Hoe 226 | { 227 | .required.items = { WOOD_ITEM, IRON_INGOT_ITEM }, 228 | .required.count = { 5, 5 }, 229 | 230 | .result = HOE_ITEM, 231 | .tool_level = 2 232 | }, 233 | 234 | // Iron Pick 235 | { 236 | .required.items = { WOOD_ITEM, IRON_INGOT_ITEM }, 237 | .required.count = { 5, 5 }, 238 | 239 | .result = PICK_ITEM, 240 | .tool_level = 2 241 | }, 242 | 243 | // Iron Shovel 244 | { 245 | .required.items = { WOOD_ITEM, IRON_INGOT_ITEM }, 246 | .required.count = { 5, 5 }, 247 | 248 | .result = SHOVEL_ITEM, 249 | .tool_level = 2 250 | }, 251 | 252 | // Gold Tools 253 | 254 | // Gold Sword 255 | { 256 | .required.items = { WOOD_ITEM, GOLD_INGOT_ITEM }, 257 | .required.count = { 5, 5 }, 258 | 259 | .result = SWORD_ITEM, 260 | .tool_level = 3 261 | }, 262 | 263 | // Gold Axe 264 | { 265 | .required.items = { WOOD_ITEM, GOLD_INGOT_ITEM }, 266 | .required.count = { 5, 5 }, 267 | 268 | .result = AXE_ITEM, 269 | .tool_level = 3 270 | }, 271 | 272 | // Gold Hoe 273 | { 274 | .required.items = { WOOD_ITEM, GOLD_INGOT_ITEM }, 275 | .required.count = { 5, 5 }, 276 | 277 | .result = HOE_ITEM, 278 | .tool_level = 3 279 | }, 280 | 281 | // Gold Pick 282 | { 283 | .required.items = { WOOD_ITEM, GOLD_INGOT_ITEM }, 284 | .required.count = { 5, 5 }, 285 | 286 | .result = PICK_ITEM, 287 | .tool_level = 3 288 | }, 289 | 290 | // Gold Shovel 291 | { 292 | .required.items = { WOOD_ITEM, GOLD_INGOT_ITEM }, 293 | .required.count = { 5, 5 }, 294 | 295 | .result = SHOVEL_ITEM, 296 | .tool_level = 3 297 | }, 298 | 299 | // Gem Tools 300 | 301 | // Gem Sword 302 | { 303 | .required.items = { WOOD_ITEM, GEM_ITEM }, 304 | .required.count = { 5, 50 }, 305 | 306 | .result = SWORD_ITEM, 307 | .tool_level = 4 308 | }, 309 | 310 | // Gem Axe 311 | { 312 | .required.items = { WOOD_ITEM, GEM_ITEM }, 313 | .required.count = { 5, 50 }, 314 | 315 | .result = AXE_ITEM, 316 | .tool_level = 4 317 | }, 318 | 319 | // Gem Hoe 320 | { 321 | .required.items = { WOOD_ITEM, GEM_ITEM }, 322 | .required.count = { 5, 50 }, 323 | 324 | .result = HOE_ITEM, 325 | .tool_level = 4 326 | }, 327 | 328 | // Gem Pick 329 | { 330 | .required.items = { WOOD_ITEM, GEM_ITEM }, 331 | .required.count = { 5, 50 }, 332 | 333 | .result = PICK_ITEM, 334 | .tool_level = 4 335 | }, 336 | 337 | // Gem Shovel 338 | { 339 | .required.items = { WOOD_ITEM, GEM_ITEM }, 340 | .required.count = { 5, 50 }, 341 | 342 | .result = SHOVEL_ITEM, 343 | .tool_level = 4 344 | } 345 | }; 346 | 347 | u8 crafting_current_recipes_size; 348 | const struct crafting_Recipe *crafting_current_recipes; 349 | 350 | bool crafting_craft(struct Inventory *inventory, 351 | const struct crafting_Recipe *recipe) { 352 | // add item to the inventory 353 | if(item_is_resource(recipe->result)) { 354 | if(!inventory_add_resource(inventory, recipe->result, 1, 0)) 355 | return false; 356 | } else { 357 | struct item_Data to_add = { .type = recipe->result }; 358 | 359 | if(ITEM_S(&to_add)->class == ITEMCLASS_TOOL) 360 | to_add.tool_level = recipe->tool_level; 361 | 362 | if(to_add.type == CHEST_ITEM) { 363 | to_add.chest_id = furniture_new_chest_id(); 364 | 365 | if(to_add.chest_id >= CHEST_LIMIT) 366 | return false; 367 | } 368 | 369 | if(!inventory_add(inventory, &to_add, 0)) 370 | return false; 371 | } 372 | 373 | // remove the required items 374 | // (assume that the inventory contains them) 375 | for(u32 i = 0; i < CRAFTING_MAX_REQUIRED; i++) { 376 | const u8 item_type = recipe->required.items[i]; 377 | const u8 count = recipe->required.count[i]; 378 | 379 | if(count == 0) 380 | continue; 381 | 382 | inventory_remove_resource(inventory, item_type, count); 383 | } 384 | 385 | return true; 386 | } 387 | -------------------------------------------------------------------------------- /src/screen.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022-2024 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "screen.h" 17 | 18 | #include "res/palettes/background.c" 19 | #include "res/palettes/sprites.c" 20 | #include "res/palettes/items.c" 21 | 22 | #include "res/images/level.c" 23 | #include "res/images/logo.c" 24 | #include "res/images/font.c" 25 | #include "res/images/gui.c" 26 | #include "res/images/items_bg.c" 27 | #include "res/images/level-light.c" 28 | 29 | #include "res/images/entities.c" 30 | #include "res/images/sparks.c" 31 | #include "res/images/items_spr.c" 32 | #include "res/images/player-light.c" 33 | #include "res/images/player-lantern-light.c" 34 | #include "res/images/text-particle.c" 35 | 36 | #define BG_PALETTE ((vu16 *) 0x05000000) 37 | #define SPR_PALETTE ((vu16 *) 0x05000200) 38 | 39 | #define LOAD_TILESET(charblock, offset, tileset) \ 40 | memory_copy_32( \ 41 | display_charblock(charblock) + (offset) * 16, \ 42 | tileset, \ 43 | sizeof(tileset) \ 44 | ) 45 | 46 | #define LOAD_PALETTE(dest, palette) \ 47 | memory_copy_32(dest, palette, sizeof(palette)) 48 | 49 | void screen_init(void) { 50 | display_config(0); 51 | 52 | window_config(WINDOW_OUT, NULL); 53 | 54 | // Filter out the light layer when inside OBJ Window 55 | window_config(WINDOW_SPR, &(struct Window) { 56 | .bg0 = true, 57 | .bg1 = true, 58 | .bg2 = false, 59 | .bg3 = true, 60 | 61 | .sprites = true, 62 | 63 | .effects = true 64 | }); 65 | window_toggle(WINDOW_SPR, true); 66 | 67 | // Sky Background 68 | background_config(BG0, &(struct Background) { 69 | .priority = 3, 70 | .tileset = 0, 71 | .tilemap = 16 72 | }); 73 | 74 | // Level Tiles 75 | background_config(BG1, &(struct Background) { 76 | .priority = 2, 77 | .tileset = 0, 78 | .tilemap = 17 79 | }); 80 | 81 | // Light system 82 | background_config(BG2, &(struct Background) { 83 | .priority = 1, 84 | .tileset = 1, 85 | .tilemap = 18 86 | }); 87 | 88 | // Text and GUI 89 | background_config(BG3, &(struct Background) { 90 | .priority = 0, 91 | .tileset = 1, 92 | .tilemap = 19 93 | }); 94 | 95 | // enable backgrounds 96 | background_toggle(BG1, true); // level tiles 97 | background_toggle(BG3, true); // text and GUI 98 | 99 | // load palettes 100 | LOAD_PALETTE(BG_PALETTE, background_palette); 101 | LOAD_PALETTE(SPR_PALETTE, sprites_palette); 102 | 103 | LOAD_PALETTE(BG_PALETTE + 12 * 16, items_palette); 104 | LOAD_PALETTE(SPR_PALETTE + 12 * 16, items_palette); 105 | 106 | // make the first tile of charblock 1 fully transparent 107 | memory_set_32(display_charblock(1), 0x00, 32); 108 | 109 | // load tilesets 110 | LOAD_TILESET(0, 0, level_tileset); 111 | LOAD_TILESET(1, 1, logo_tileset); 112 | LOAD_TILESET(1, 32, font_tileset); 113 | LOAD_TILESET(1, 96, gui_tileset); 114 | LOAD_TILESET(1, 128, items_bg_tileset); 115 | LOAD_TILESET(1, 192, level_light_tileset); 116 | 117 | LOAD_TILESET(4, 0, entities_tileset); 118 | LOAD_TILESET(4, 192, sparks_tileset); 119 | LOAD_TILESET(4, 256, items_spr_tileset); 120 | LOAD_TILESET(4, 320, player_light_tileset); 121 | LOAD_TILESET(4, 336, player_lantern_light_tileset); 122 | 123 | // load font sprites 124 | for(u32 i = 0; i <= 51; i++) { 125 | vu16 *dest = display_charblock(4) + (640 + i * 2) * 16; 126 | memory_copy_32( 127 | dest, 128 | (u8 *) (text_particle_tileset) + (i / 10) * 32, 129 | 32 130 | ); 131 | memory_copy_32( 132 | dest + 16, 133 | (u8 *) (text_particle_tileset) + (i % 10) * 32, 134 | 32 135 | ); 136 | } 137 | 138 | // set sky background 139 | for(u32 y = 0; y <= 18; y++) 140 | for(u32 x = 0; x <= 30; x++) 141 | BG0_TILEMAP[x + y * 32] = 0 | 9 << 12; 142 | 143 | // prepare prestart screen 144 | display_brighten(NULL, 16); 145 | for(u32 y = 0; y < 20; y++) 146 | for(u32 x = 0; x < 30; x++) 147 | BG3_TILEMAP[x + y * 32] = 32; 148 | 149 | sprite_hide(-1); 150 | 151 | // disable forced blank 152 | display_force_blank(false); 153 | } 154 | 155 | IWRAM_SECTION 156 | void screen_write(const char *text, u8 palette, u32 x, u32 y) { 157 | const u32 x0 = x; 158 | 159 | for(u32 i = 0; text[i] != '\0'; i++) { 160 | const char c = text[i]; 161 | 162 | if(c == '\n') { 163 | x = x0; 164 | y++; 165 | 166 | if(y >= 20) 167 | break; 168 | } else { 169 | if(x >= 30) 170 | continue; 171 | 172 | u16 tile = c; // font tiles match ASCII values 173 | BG3_TILEMAP[x + y * 32] = tile | palette << 12; 174 | 175 | x++; 176 | } 177 | } 178 | } 179 | 180 | THUMB 181 | void screen_draw_frame(const char *title, u32 x, u32 y, u32 w, u32 h) { 182 | w--; 183 | h--; 184 | 185 | // draw corners 186 | BG3_TILEMAP[(x) + (y) * 32] = 96 | 0 << 10 | 6 << 12; 187 | BG3_TILEMAP[(x + w) + (y) * 32] = 96 | 1 << 10 | 6 << 12; 188 | BG3_TILEMAP[(x) + (y + h) * 32] = 96 | 2 << 10 | 6 << 12; 189 | BG3_TILEMAP[(x + w) + (y + h) * 32] = 96 | 3 << 10 | 6 << 12; 190 | 191 | // draw vertical borders 192 | for(u32 yi = y + 1; yi <= y + h - 1; yi++) { 193 | BG3_TILEMAP[(x) + yi * 32] = 98 | 0 << 10 | 6 << 12; 194 | BG3_TILEMAP[(x + w) + yi * 32] = 98 | 1 << 10 | 6 << 12; 195 | 196 | // draw background 197 | for(u32 xi = x + 1; xi <= x + w - 1; xi++) 198 | BG3_TILEMAP[xi + yi * 32] = 99 | 6 << 12; 199 | } 200 | 201 | // draw horizontal borders 202 | for(u32 xi = x + 1; xi <= x + w - 1; xi++) { 203 | BG3_TILEMAP[xi + (y) * 32] = 97 | 0 << 10 | 6 << 12; 204 | BG3_TILEMAP[xi + (y + h) * 32] = 97 | 2 << 10 | 6 << 12; 205 | } 206 | 207 | screen_write(title, 10, x + 1, y); 208 | } 209 | 210 | static inline u32 ticks_to_seconds(u32 ticks) { 211 | // refresh time: 280_896 cycles = 4389 * 64 cycles 212 | // clock frequency: 16_777_216 Hz = 262144 * 64 Hz 213 | // 214 | // framerate = (clock frequency) / (refresh time) 215 | // time = ticks / framerate 216 | // = (ticks * 4389) / 262144 217 | // = (ticks * 4389) >> 18 218 | 219 | return (((u64) ticks) * 4389) >> 18; 220 | } 221 | 222 | void screen_write_time(u32 ticks, u8 palette, u32 x, u32 y) { 223 | // NOTE this is different from the original game: here, seconds are 224 | // displayed even if hours is not 0 and there is a space between the 225 | // hours and minutes values. 226 | 227 | u32 seconds = ticks_to_seconds(ticks); 228 | u32 minutes = seconds / 60; 229 | u32 hours = minutes / 60; 230 | 231 | seconds %= 60; 232 | minutes %= 60; 233 | 234 | char text[15] = { 0 }; 235 | 236 | u8 offset = 0; 237 | if(hours > 0) { 238 | itoa(hours, 10, text, 5, false); 239 | offset += 1 + (hours > 9) + (hours > 99) + 240 | (hours > 999) + (hours > 9999); 241 | text[offset++] = 'H'; 242 | text[offset++] = ' '; 243 | } 244 | 245 | itoa(minutes, 10, text + offset, 2, true); 246 | offset += 2; 247 | text[offset++] = 'M'; 248 | text[offset++] = ' '; 249 | 250 | itoa(seconds, 10, text + offset, 2, true); 251 | offset += 2; 252 | text[offset++] = 'S'; 253 | 254 | screen_write(text, palette, x, y); 255 | } 256 | 257 | void screen_set_bg_palette_color(u8 palette, u8 index, u16 color) { 258 | BG_PALETTE[palette * 16 + index] = color; 259 | } 260 | 261 | void screen_load_active_item_palette(u8 palette) { 262 | memory_copy_32(BG_PALETTE + 11 * 16, items_palette + 16 * palette, 32); 263 | screen_set_bg_palette_color(11, 15, 0x0421); 264 | } 265 | 266 | void screen_update_level_specific(void) { 267 | background_toggle(BG0, current_level == 4); // sky background 268 | background_toggle(BG2, current_level < 3); // light layer 269 | 270 | // dirt color 271 | const u16 dirt_colors[5][2] = { 272 | { 0x1ce7, 0x318c }, 273 | { 0x1ce7, 0x318c }, 274 | { 0x1ce7, 0x318c }, 275 | { 0x1cea, 0x35b0 }, 276 | { -1, 0x6318 } 277 | }; 278 | for(u32 i = 0; i <= 3; i++) { 279 | screen_set_bg_palette_color(i, 12, dirt_colors[current_level][0]); 280 | screen_set_bg_palette_color(i, 13, dirt_colors[current_level][1]); 281 | } 282 | screen_set_bg_palette_color(0, 0, dirt_colors[current_level][1]); 283 | 284 | screen_set_bg_palette_color(8, 2, dirt_colors[current_level][0]); 285 | screen_set_bg_palette_color(8, 1, dirt_colors[current_level][1]); 286 | 287 | // farmland 288 | const u16 farmland_colors[5][2] = { 289 | { 0x1cea, 0x1445 }, 290 | { 0x1cea, 0x1445 }, 291 | { 0x1cea, 0x1445 }, 292 | { 0x210e, 0x1869 }, 293 | { -1, -1 } 294 | }; 295 | screen_set_bg_palette_color(4, 4, farmland_colors[current_level][0]); 296 | screen_set_bg_palette_color(4, 5, farmland_colors[current_level][1]); 297 | 298 | // ore/cloud cactus 299 | const u16 ore_colors[5][3] = { 300 | { 0x733c, 0x44b1, 0x1445 }, // gem 301 | { 0x5fbd, 0x2ed6, 0x0cc6 }, // gold 302 | { 0x673b, 0x35b0, 0x0844 }, // iron 303 | { -1, -1, -1 }, 304 | { 0x7bde, 0x4a52, 0x1ce7 } // cloud cactus 305 | }; 306 | screen_set_bg_palette_color(3, 6, ore_colors[current_level][0]); 307 | screen_set_bg_palette_color(3, 5, ore_colors[current_level][1]); 308 | screen_set_bg_palette_color(3, 4, ore_colors[current_level][2]); 309 | 310 | // liquid 311 | const u16 liquid_colors[2][2] = { 312 | { 0x14b3, 0x21d7 }, 313 | { 0x4442, 0x4d08 } 314 | }; 315 | for(u32 i = 3; i <= 4; i++) { 316 | screen_set_bg_palette_color(i, 1, liquid_colors[current_level > 0][0]); 317 | screen_set_bg_palette_color(i, 2, liquid_colors[current_level > 0][1]); 318 | screen_set_bg_palette_color(i, 3, liquid_colors[current_level > 0][0]); 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /src/storage.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2022-2025 Vulcalien 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | */ 16 | #include "storage.h" 17 | 18 | #include "options.h" 19 | #include "level.h" 20 | #include "tile.h" 21 | #include "furniture.h" 22 | #include "inventory.h" 23 | #include "item.h" 24 | #include "player.h" 25 | #include "air-wizard.h" 26 | 27 | /* 28 | Storage Layout 29 | 30 | +----------------------+ 0 0000 31 | | Header .5 KB | 32 | |----------------------| 0 0200 33 | | | 34 | | Chests | 35 | | 12 KB | 36 | |----------------------| 0 3200 37 | | | 38 | | Entity Data | 39 | | 17.5 KB | 40 | |----------------------| 0 7800 41 | | | 42 | | | 43 | | Tile Types | 44 | | | 45 | | | 46 | | - - - - - - - | <-- this boundary is not at a fixed address 47 | | | 48 | | | 49 | | Tile Data | 50 | | | 51 | | 98 KB | 52 | +----------------------+ 2 0000 53 | 54 | * Header: 55 | 4 B - game code (ZMCE) 56 | 4 B - checksum 57 | 4 B - random seed 58 | 59 | 4 B - score 60 | 4 B - gametime 61 | 62 | 384 B - inventory 63 | 3 B - active item 64 | 1 B - stamina 65 | 1 B - stamina recharge delay 66 | 2 B - invulnerable time 67 | 68 | 1 B - current level 69 | 1 B - chest count 70 | 71 | 1 B - air wizard attack delay 72 | 1 B - air wizard attack time 73 | 74 | 1 B - keep inventory option 75 | 76 | 96 B - padding 77 | */ 78 | 79 | #define LEVEL_COUNT (sizeof(levels) / sizeof(struct Level)) 80 | #define BYTES_PER_ITEM 3 81 | 82 | THUMB 83 | bool storage_check(void) { 84 | backup_set_bank(0); 85 | 86 | // check if game code (ZMCE) is present 87 | return backup_read_byte(0) == 'Z' && 88 | backup_read_byte(1) == 'M' && 89 | backup_read_byte(2) == 'C' && 90 | backup_read_byte(3) == 'E'; 91 | } 92 | 93 | THUMB 94 | bool storage_verify_checksum(void) { 95 | u32 val = 0; 96 | backup_set_bank(0); 97 | 98 | u32 checksum_in_file; 99 | backup_read(0x00004, &checksum_in_file, 4); 100 | 101 | for(u32 i = 0x00008; i < 0x10000; i++) 102 | val += backup_read_byte(i); 103 | 104 | backup_set_bank(1); 105 | for(u32 i = 0x00000; i < 0x10000; i++) 106 | val += backup_read_byte(i); 107 | 108 | return (val == checksum_in_file); 109 | } 110 | 111 | THUMB 112 | void storage_srand(void) { 113 | backup_set_bank(0); 114 | 115 | u32 seed; 116 | backup_read(0x00008, &seed, 4); 117 | random_seed(seed); 118 | } 119 | 120 | THUMB 121 | void storage_load_options(void) { 122 | backup_set_bank(0); 123 | options.keep_inventory = backup_read_byte(0x001a0); 124 | } 125 | 126 | /* ================================================================== */ 127 | /* storage_load */ 128 | /* ================================================================== */ 129 | 130 | static INLINE void load_item(u32 offset, struct item_Data *data) { 131 | data->type = backup_read_byte(offset); 132 | backup_read(offset + 1, &data->count, 2); 133 | } 134 | 135 | THUMB 136 | static NO_INLINE void load_inventory(u32 offset, struct Inventory *inventory) { 137 | inventory->size = 0; 138 | 139 | for(u32 i = 0; i < INVENTORY_SIZE; i++) { 140 | struct item_Data *item = &inventory->items[i]; 141 | load_item(offset, item); 142 | if(item->type >= ITEM_TYPES) 143 | break; 144 | 145 | inventory->size++; 146 | offset += BYTES_PER_ITEM; 147 | } 148 | } 149 | 150 | static INLINE void load_header(void) { 151 | u32 offset = 0x0000c; // skip game code, checksum and seed 152 | 153 | backup_read(offset, &score, 4); 154 | offset += 4; 155 | 156 | backup_read(offset, &gametime, 4); 157 | offset += 4; 158 | 159 | load_inventory(offset, &player_inventory); 160 | offset += INVENTORY_SIZE * BYTES_PER_ITEM; 161 | 162 | load_item(offset, &player_active_item); 163 | offset += BYTES_PER_ITEM; 164 | 165 | backup_read(offset, &player_stamina, 1); 166 | offset += 1; 167 | 168 | backup_read(offset, &player_stamina_recharge_delay, 1); 169 | offset += 1; 170 | 171 | backup_read(offset, &player_invulnerable_time, 2); 172 | offset += 2; 173 | 174 | backup_read(offset, ¤t_level, 1); 175 | offset += 1; 176 | 177 | backup_read(offset, &chest_count, 1); 178 | offset += 1; 179 | 180 | backup_read(offset, &air_wizard_attack_delay, 1); 181 | offset += 1; 182 | 183 | backup_read(offset, &air_wizard_attack_time, 1); 184 | offset += 1; 185 | 186 | // stop here: do not load options again 187 | } 188 | 189 | static INLINE void load_chests(void) { 190 | u32 offset = 0x00200; 191 | for(u32 i = 0; i < CHEST_LIMIT; i++) { 192 | load_inventory(offset, &chest_inventories[i]); 193 | offset += INVENTORY_SIZE * BYTES_PER_ITEM; 194 | } 195 | } 196 | 197 | static INLINE void load_entities(void) { 198 | u32 offset = 0x03200; 199 | for(u32 i = 0; i < LEVEL_COUNT; i++) { 200 | struct Level *level = &levels[i]; 201 | 202 | backup_read(offset, level->entities, sizeof(level->entities)); 203 | offset += sizeof(level->entities); 204 | } 205 | } 206 | 207 | static INLINE u32 read_RLE_tuple(u32 offset, i32 *val, i32 *run_length) { 208 | *val = backup_read_byte(offset++); 209 | *run_length = 1 + backup_read_byte(offset++); 210 | 211 | // if offset is 64KB, switch to memory bank 1 212 | if(offset == 0x10000) 213 | backup_set_bank(1); 214 | 215 | return offset; 216 | } 217 | 218 | THUMB 219 | static NO_INLINE u32 read_array_RLE(u8 *array, u32 size, u32 offset) { 220 | // if out of memory, do nothing 221 | if(offset >= 0x20000) 222 | return offset; 223 | 224 | u32 index = 0; 225 | while(index < size) { 226 | i32 val, run_length; 227 | offset = read_RLE_tuple(offset, &val, &run_length); 228 | 229 | while(run_length > 0 && index < size) { 230 | array[index++] = val; 231 | run_length--; 232 | } 233 | 234 | // if out of memory, stop reading tuples 235 | if(offset >= 0x20000) 236 | return offset; 237 | } 238 | return offset; 239 | } 240 | 241 | static INLINE void load_tiles(void) { 242 | u32 offset = 0x07800; 243 | 244 | // load tile types 245 | for(u32 i = 0; i < LEVEL_COUNT; i++) { 246 | struct Level *level = &levels[i]; 247 | offset = read_array_RLE(level->tiles, LEVEL_SIZE, offset); 248 | } 249 | 250 | // load tile data 251 | for(u32 i = 0; i < LEVEL_COUNT; i++) { 252 | struct Level *level = &levels[i]; 253 | offset = read_array_RLE(level->data, LEVEL_SIZE, offset); 254 | } 255 | } 256 | 257 | THUMB 258 | void storage_load(void) { 259 | backup_set_bank(0); 260 | 261 | load_header(); 262 | load_chests(); 263 | load_entities(); 264 | load_tiles(); 265 | } 266 | 267 | /* ================================================================== */ 268 | /* storage_save */ 269 | /* ================================================================== */ 270 | 271 | static u32 checksum; 272 | 273 | static INLINE void write_8(u32 offset, u8 val) { 274 | checksum += val; 275 | backup_write_byte(offset, val); 276 | } 277 | 278 | static INLINE void write_16(u32 offset, u16 val) { 279 | checksum += val & 0xff; 280 | checksum += (val >> 8) & 0xff; 281 | backup_write(offset, &val, 2); 282 | } 283 | 284 | static INLINE void write_32(u32 offset, u32 val) { 285 | checksum += val & 0xff; 286 | checksum += (val >> 8) & 0xff; 287 | checksum += (val >> 16) & 0xff; 288 | checksum += (val >> 24) & 0xff; 289 | backup_write(offset, &val, 4); 290 | } 291 | 292 | static INLINE void store_item(u32 offset, struct item_Data *data) { 293 | write_8(offset, data->type); 294 | write_16(offset + 1, data->count); 295 | } 296 | 297 | THUMB 298 | static NO_INLINE void store_inventory(u32 offset, struct Inventory *inventory) { 299 | for(u32 i = 0; i < INVENTORY_SIZE; i++) { 300 | if(i < inventory->size) { 301 | store_item(offset, &inventory->items[i]); 302 | } else { 303 | write_8(offset, -1); 304 | write_16(offset + 1, 0); 305 | } 306 | offset += BYTES_PER_ITEM; 307 | } 308 | } 309 | 310 | static INLINE void store_header(void) { 311 | u32 offset = 0x00000; 312 | 313 | // write game code (without updating checksum) 314 | backup_write(offset, "ZMCE", 4); 315 | offset += 4; 316 | 317 | // skip checksum 318 | offset += 4; 319 | 320 | u32 seed = random(RANDOM_MAX + 1) | random(RANDOM_MAX + 1) << 16; 321 | write_32(offset, seed); 322 | offset += 4; 323 | 324 | write_32(offset, score); 325 | offset += 4; 326 | 327 | write_32(offset, gametime); 328 | offset += 4; 329 | 330 | store_inventory(offset, &player_inventory); 331 | offset += INVENTORY_SIZE * BYTES_PER_ITEM; 332 | 333 | store_item(offset, &player_active_item); 334 | offset += BYTES_PER_ITEM; 335 | 336 | write_8(offset, player_stamina); 337 | offset += 1; 338 | 339 | write_8(offset, player_stamina_recharge_delay); 340 | offset += 1; 341 | 342 | write_16(offset, player_invulnerable_time); 343 | offset += 2; 344 | 345 | write_8(offset, current_level); 346 | offset += 1; 347 | 348 | write_8(offset, chest_count); 349 | offset += 1; 350 | 351 | write_8(offset, air_wizard_attack_delay); 352 | offset += 1; 353 | 354 | write_8(offset, air_wizard_attack_time); 355 | offset += 1; 356 | 357 | write_8(offset, options.keep_inventory); 358 | offset += 1; 359 | 360 | // adjust checksum to consider padding 361 | checksum += 0xff * (0x00200 - offset); 362 | } 363 | 364 | static INLINE void store_chests(void) { 365 | u32 offset = 0x00200; 366 | for(u32 i = 0; i < CHEST_LIMIT; i++) { 367 | store_inventory(offset, &chest_inventories[i]); 368 | offset += INVENTORY_SIZE * BYTES_PER_ITEM; 369 | } 370 | } 371 | 372 | static INLINE void store_entities(void) { 373 | u32 offset = 0x03200; 374 | for(u32 i = 0; i < LEVEL_COUNT; i++) { 375 | struct Level *level = &levels[i]; 376 | u8 *data = (u8 *) level->entities; 377 | 378 | // add all bytes to checksum 379 | for(u32 b = 0; b < sizeof(level->entities); b++) 380 | checksum += data[b]; 381 | 382 | backup_write(offset, data, sizeof(level->entities)); 383 | offset += sizeof(level->entities); 384 | } 385 | 386 | // adjust checksum to consider padding 387 | checksum += 0xff * (0x07800 - offset); 388 | } 389 | 390 | static INLINE u32 write_RLE_tuple(u32 offset, i32 val, i32 run_length) { 391 | const u16 tuple = val | (run_length - 1) << 8; 392 | write_16(offset, tuple); 393 | offset += 2; 394 | 395 | // if offset is 64KB, switch to memory bank 1 396 | if(offset == 0x10000) 397 | backup_set_bank(1); 398 | 399 | return offset; 400 | } 401 | 402 | THUMB 403 | static NO_INLINE u32 write_array_RLE(u8 *array, u32 size, u32 offset) { 404 | // if out of memory, do nothing 405 | if(offset >= 0x20000) 406 | return offset; 407 | 408 | i32 run_length = 1; 409 | for(u32 i = 1; i < size; i++) { 410 | const i32 previous = array[i - 1]; 411 | const i32 current = array[i]; 412 | 413 | // if value changed, or max run-length reached, write a tuple 414 | if(current != previous || run_length > 255) { 415 | offset = write_RLE_tuple(offset, previous, run_length); 416 | 417 | // if out of memory, stop writing tuples 418 | if(offset >= 0x20000) 419 | return offset; 420 | 421 | run_length = 1; 422 | } else { 423 | run_length++; 424 | } 425 | } 426 | 427 | // write the last tuple and return (offset + bytes-used) 428 | return write_RLE_tuple(offset, array[size], run_length); 429 | } 430 | 431 | static INLINE void store_tiles(void) { 432 | u32 offset = 0x07800; 433 | 434 | // store tile types 435 | for(u32 i = 0; i < LEVEL_COUNT; i++) { 436 | struct Level *level = &levels[i]; 437 | offset = write_array_RLE(level->tiles, LEVEL_SIZE, offset); 438 | } 439 | 440 | // store tile data 441 | for(u32 i = 0; i < LEVEL_COUNT; i++) { 442 | struct Level *level = &levels[i]; 443 | offset = write_array_RLE(level->data, LEVEL_SIZE, offset); 444 | } 445 | 446 | // adjust checksum to consider padding 447 | checksum += 0xff * (0x20000 - offset); 448 | } 449 | 450 | THUMB 451 | void storage_save(void) { 452 | backup_set_bank(0); 453 | backup_erase_chip(); 454 | checksum = 0; 455 | 456 | store_header(); 457 | store_chests(); 458 | store_entities(); 459 | store_tiles(); 460 | 461 | backup_set_bank(0); 462 | backup_write(0x00004, &checksum, 4); 463 | } 464 | --------------------------------------------------------------------------------