├── 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 |
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 |
--------------------------------------------------------------------------------