Compare commits
28 Commits
c478ee23db
...
main
Author | SHA1 | Date | |
---|---|---|---|
6c25396d4b | |||
0a3a8cb8ab | |||
1a89e4f3b6 | |||
60e1a4ed43 | |||
c7d9f16515 | |||
7e67d09508 | |||
f5f5e2c20b | |||
567db0bd71 | |||
df1d1450e7 | |||
7649a2dc7e | |||
d084e4dba3 | |||
d6db2f3e5f | |||
b0f4106d51 | |||
84b7a09383 | |||
c5c677320b | |||
5f522abcb5 | |||
ab5c442433 | |||
26018facea | |||
407a984300 | |||
08f279e340 | |||
57e025e6aa | |||
d823220e90 | |||
9ef88fb34d | |||
055b3dd7b1 | |||
f6d942c614 | |||
79eb52e282 | |||
d05e382063 | |||
72599b7d00 |
@@ -19,6 +19,6 @@ pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
|
|||||||
pip.parse(
|
pip.parse(
|
||||||
hub_name = "pytools_deps",
|
hub_name = "pytools_deps",
|
||||||
python_version = "3.10",
|
python_version = "3.10",
|
||||||
requirements_lock = "//pytools:requirements.txt",
|
requirements_lock = "//pytools:requirements.lock",
|
||||||
)
|
)
|
||||||
use_repo(pip, "pytools_deps")
|
use_repo(pip, "pytools_deps")
|
62
MODULE.bazel.lock
generated
62
MODULE.bazel.lock
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"lockFileVersion": 3,
|
"lockFileVersion": 3,
|
||||||
"moduleFileHash": "f69bdb13b0df2f6d309d415437228ebef31fa559567a65ecfe60de77e328ae1d",
|
"moduleFileHash": "b7a605dff62d0b97615cf1feb669dc4d3ce630abf1e74374f624b71287684b40",
|
||||||
"flags": {
|
"flags": {
|
||||||
"cmdRegistries": [
|
"cmdRegistries": [
|
||||||
"https://bcr.bazel.build/"
|
"https://bcr.bazel.build/"
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
"attributeValues": {
|
"attributeValues": {
|
||||||
"hub_name": "pytools_deps",
|
"hub_name": "pytools_deps",
|
||||||
"python_version": "3.10",
|
"python_version": "3.10",
|
||||||
"requirements_lock": "//pytools:requirements.txt"
|
"requirements_lock": "//pytools:requirements.lock"
|
||||||
},
|
},
|
||||||
"devDependency": false,
|
"devDependency": false,
|
||||||
"location": {
|
"location": {
|
||||||
@@ -1741,10 +1741,60 @@
|
|||||||
"os:windows,arch:amd64": {
|
"os:windows,arch:amd64": {
|
||||||
"bzlTransitiveDigest": "EY+DGyyJhrqMHBCiplU6kCYiNtiUXl3Olt6+vF4JnfE=",
|
"bzlTransitiveDigest": "EY+DGyyJhrqMHBCiplU6kCYiNtiUXl3Olt6+vF4JnfE=",
|
||||||
"accumulatedFileDigests": {
|
"accumulatedFileDigests": {
|
||||||
"@@//pytools:requirements.txt": "ecb2c096486b1bb4dbe407a48fb3375ba496b6ea658c0cd7fc34571f381cd504"
|
"@@//pytools:requirements.lock": "5a2eed9c6e50e9034521853f6016123e3e526900ddf3ff81cc1dbaaa28683629"
|
||||||
},
|
},
|
||||||
"envVariables": {},
|
"envVariables": {},
|
||||||
"generatedRepoSpecs": {
|
"generatedRepoSpecs": {
|
||||||
|
"pytools_deps_310_jinja2": {
|
||||||
|
"bzlFile": "@@rules_python~0.31.0//python/pip_install:pip_repository.bzl",
|
||||||
|
"ruleClassName": "whl_library",
|
||||||
|
"attributes": {
|
||||||
|
"name": "rules_python~0.31.0~pip~pytools_deps_310_jinja2",
|
||||||
|
"requirement": "Jinja2==3.1.3",
|
||||||
|
"repo": "pytools_deps_310",
|
||||||
|
"repo_prefix": "pytools_deps_310_",
|
||||||
|
"whl_patches": {},
|
||||||
|
"experimental_target_platforms": [],
|
||||||
|
"python_interpreter": "",
|
||||||
|
"python_interpreter_target": "@@rules_python~0.31.0~python~python_3_10_host//:python",
|
||||||
|
"quiet": true,
|
||||||
|
"timeout": 600,
|
||||||
|
"isolated": true,
|
||||||
|
"extra_pip_args": [],
|
||||||
|
"download_only": false,
|
||||||
|
"pip_data_exclude": [],
|
||||||
|
"enable_implicit_namespace_pkgs": false,
|
||||||
|
"environment": {},
|
||||||
|
"envsubst": [],
|
||||||
|
"group_name": "",
|
||||||
|
"group_deps": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pytools_deps_310_markupsafe": {
|
||||||
|
"bzlFile": "@@rules_python~0.31.0//python/pip_install:pip_repository.bzl",
|
||||||
|
"ruleClassName": "whl_library",
|
||||||
|
"attributes": {
|
||||||
|
"name": "rules_python~0.31.0~pip~pytools_deps_310_markupsafe",
|
||||||
|
"requirement": "MarkupSafe==2.1.5",
|
||||||
|
"repo": "pytools_deps_310",
|
||||||
|
"repo_prefix": "pytools_deps_310_",
|
||||||
|
"whl_patches": {},
|
||||||
|
"experimental_target_platforms": [],
|
||||||
|
"python_interpreter": "",
|
||||||
|
"python_interpreter_target": "@@rules_python~0.31.0~python~python_3_10_host//:python",
|
||||||
|
"quiet": true,
|
||||||
|
"timeout": 600,
|
||||||
|
"isolated": true,
|
||||||
|
"extra_pip_args": [],
|
||||||
|
"download_only": false,
|
||||||
|
"pip_data_exclude": [],
|
||||||
|
"enable_implicit_namespace_pkgs": false,
|
||||||
|
"environment": {},
|
||||||
|
"envsubst": [],
|
||||||
|
"group_name": "",
|
||||||
|
"group_deps": []
|
||||||
|
}
|
||||||
|
},
|
||||||
"pytools_deps_310_pillow": {
|
"pytools_deps_310_pillow": {
|
||||||
"bzlFile": "@@rules_python~0.31.0//python/pip_install:pip_repository.bzl",
|
"bzlFile": "@@rules_python~0.31.0//python/pip_install:pip_repository.bzl",
|
||||||
"ruleClassName": "whl_library",
|
"ruleClassName": "whl_library",
|
||||||
@@ -1786,6 +1836,12 @@
|
|||||||
"name": "rules_python~0.31.0~pip~pytools_deps",
|
"name": "rules_python~0.31.0~pip~pytools_deps",
|
||||||
"repo_name": "pytools_deps",
|
"repo_name": "pytools_deps",
|
||||||
"whl_map": {
|
"whl_map": {
|
||||||
|
"jinja2": [
|
||||||
|
"3.10"
|
||||||
|
],
|
||||||
|
"markupsafe": [
|
||||||
|
"3.10"
|
||||||
|
],
|
||||||
"pillow": [
|
"pillow": [
|
||||||
"3.10"
|
"3.10"
|
||||||
]
|
]
|
||||||
|
@@ -14,4 +14,12 @@ $ bazel run -c opt sdl_host.exe
|
|||||||
|
|
||||||
```
|
```
|
||||||
$ bazel run sdl_host.exe
|
$ bazel run sdl_host.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dump requirements
|
||||||
|
|
||||||
|
(TODO: Automate)
|
||||||
|
|
||||||
|
```
|
||||||
|
$ pip freeze -r requirements.txt > requirements.lock
|
||||||
```
|
```
|
33
game/BUILD
33
game/BUILD
@@ -1,7 +1,36 @@
|
|||||||
|
load("@bazel_skylib//rules:run_binary.bzl", "run_binary")
|
||||||
|
load("helpers.bzl", "add_sprites")
|
||||||
|
|
||||||
cc_library(
|
cc_library(
|
||||||
name = "game",
|
name = "game",
|
||||||
srcs = glob(["*.c"]),
|
srcs = glob(["*.c"]) + [
|
||||||
hdrs = glob(["*.h"]),
|
"art/game_collectibles.c",
|
||||||
|
"art/game_hud.c",
|
||||||
|
"art/game_npcs.c",
|
||||||
|
"art/game_player.c",
|
||||||
|
"art/game_tiles.c",
|
||||||
|
"map/game_map.c",
|
||||||
|
"map/game_map_entities.c",
|
||||||
|
],
|
||||||
|
hdrs = glob(["*.h", "art/*.h", "map/*.h"]),
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = ["//device:device", "//sys:sys"]
|
deps = ["//device:device", "//sys:sys"]
|
||||||
|
)
|
||||||
|
|
||||||
|
add_sprites(name="game_collectibles", n=24)
|
||||||
|
add_sprites(name="game_hud", n=2)
|
||||||
|
add_sprites(name="game_player", n=96)
|
||||||
|
add_sprites(name="game_npcs", n=64)
|
||||||
|
add_sprites(name="game_tiles", n=120)
|
||||||
|
|
||||||
|
run_binary(
|
||||||
|
name = "game_map",
|
||||||
|
args = [
|
||||||
|
"game_map",
|
||||||
|
"$(location :map/game_map.ldtk)",
|
||||||
|
"$(location :map/game_map.c)",
|
||||||
|
],
|
||||||
|
srcs = [":map/game_map.ldtk"],
|
||||||
|
outs = [":map/game_map.c"],
|
||||||
|
tool = "//pytools:mapdata"
|
||||||
)
|
)
|
7
game/NOTES.md
Normal file
7
game/NOTES.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Some notes on the game
|
||||||
|
|
||||||
|
# Position and velocity
|
||||||
|
|
||||||
|
All positions are represented in fixed-point fashion, with a precision of 256.
|
||||||
|
|
||||||
|
For instance, a position or distance of 8 pixels is represented as `0x800`. (2048)
|
BIN
game/art/game_collectibles.aseprite
Normal file
BIN
game/art/game_collectibles.aseprite
Normal file
Binary file not shown.
8
game/art/game_collectibles.h
Normal file
8
game/art/game_collectibles.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#ifndef GAME_COLLECTIBLES_H
|
||||||
|
#define GAME_COLLECTIBLES_H
|
||||||
|
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
extern sys_spritesheet spr_game_collectibles;
|
||||||
|
|
||||||
|
#endif // GAME_COLLECTIBLES_H
|
BIN
game/art/game_collectibles.png
Normal file
BIN
game/art/game_collectibles.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 419 B |
BIN
game/art/game_demo_sprites.png
Normal file
BIN
game/art/game_demo_sprites.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
BIN
game/art/game_hud.aseprite
Normal file
BIN
game/art/game_hud.aseprite
Normal file
Binary file not shown.
8
game/art/game_hud.h
Normal file
8
game/art/game_hud.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#ifndef GAME_HUD_H
|
||||||
|
#define GAME_HUD_H
|
||||||
|
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
extern sys_spritesheet spr_game_hud;
|
||||||
|
|
||||||
|
#endif // ART_GAME_HUD_H
|
BIN
game/art/game_hud.png
Normal file
BIN
game/art/game_hud.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 218 B |
BIN
game/art/game_npcs.aseprite
Normal file
BIN
game/art/game_npcs.aseprite
Normal file
Binary file not shown.
8
game/art/game_npcs.h
Normal file
8
game/art/game_npcs.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#ifndef GAME_NPCS_H
|
||||||
|
#define GAME_NPCS_H
|
||||||
|
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
extern sys_spritesheet spr_game_npcs;
|
||||||
|
|
||||||
|
#endif // GAME_NPCS_H
|
BIN
game/art/game_npcs.png
Normal file
BIN
game/art/game_npcs.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 623 B |
BIN
game/art/game_player.aseprite
Normal file
BIN
game/art/game_player.aseprite
Normal file
Binary file not shown.
8
game/art/game_player.h
Normal file
8
game/art/game_player.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#ifndef CROCPARTY_GAME_PLAYER_H
|
||||||
|
#define CROCPARTY_GAME_PLAYER_H
|
||||||
|
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
extern sys_spritesheet spr_game_player;
|
||||||
|
|
||||||
|
#endif // CROCPARTY_GAME_PLAYER_H
|
BIN
game/art/game_player.png
Normal file
BIN
game/art/game_player.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 603 B |
BIN
game/art/game_tiles.aseprite
Normal file
BIN
game/art/game_tiles.aseprite
Normal file
Binary file not shown.
8
game/art/game_tiles.h
Normal file
8
game/art/game_tiles.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#ifndef CROCPARTY_GAME_TILES_H
|
||||||
|
#define CROCPARTY_GAME_TILES_H
|
||||||
|
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
extern sys_spritesheet spr_game_tiles;
|
||||||
|
|
||||||
|
#endif // CROCPARTY_GAME_TILES_H
|
BIN
game/art/game_tiles.png
Normal file
BIN
game/art/game_tiles.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 729 B |
89
game/game.c
89
game/game.c
@@ -1,19 +1,13 @@
|
|||||||
|
#include "art/game_player.h"
|
||||||
|
#include "art/game_tiles.h"
|
||||||
#include "device/device.h"
|
#include "device/device.h"
|
||||||
|
#include "map/game_map.h"
|
||||||
#include "game.h"
|
#include "game.h"
|
||||||
#include "sys/sys.h"
|
#include "sys/sys.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
uint32_t game_frame;
|
uint32_t game_frame;
|
||||||
|
|
||||||
int32_t ellipse_x0 = 32 * 4;
|
|
||||||
int32_t ellipse_y0 = 32 * 4;
|
|
||||||
int32_t ellipse_dx0 = 1;
|
|
||||||
int32_t ellipse_dy0 = 2;
|
|
||||||
int32_t ellipse_x1 = 96 * 4;
|
|
||||||
int32_t ellipse_y1 = 96 * 4;
|
|
||||||
int32_t ellipse_dx1 = 3;
|
|
||||||
int32_t ellipse_dy1 = 4;
|
|
||||||
|
|
||||||
|
|
||||||
const char* game_title() {
|
const char* game_title() {
|
||||||
return "Croc Party!";
|
return "Croc Party!";
|
||||||
}
|
}
|
||||||
@@ -21,22 +15,10 @@ const char* game_title() {
|
|||||||
void game_init() {
|
void game_init() {
|
||||||
sys_init();
|
sys_init();
|
||||||
|
|
||||||
game_frame = 0;
|
map_game_map_create_entities();
|
||||||
|
|
||||||
for (uint32_t x = 0; x < 8; x++) {
|
game_palette_init();
|
||||||
for (uint32_t y = 0; y < 8; y++) {
|
game_player_init();
|
||||||
for (uint32_t z = 0; z < 4; z++) {
|
|
||||||
sys_spal_set(
|
|
||||||
(x << 5)|(y << 2)|(z),
|
|
||||||
|
|
||||||
((x * 255)/7) << 24 |
|
|
||||||
((y * 255)/7) << 16 |
|
|
||||||
((z * 255)/3) << 8
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sys_spal_set(0xfe, 0xffffffff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void game_destroy() {
|
void game_destroy() {
|
||||||
@@ -44,51 +26,32 @@ void game_destroy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void game_update() {
|
void game_update() {
|
||||||
game_frame += 4;
|
sys_update();
|
||||||
|
|
||||||
ellipse_x0 += ellipse_dx0;
|
game_frame += 1;
|
||||||
if (ellipse_x0 < 0 || ellipse_x0 > DEVICE_W * 4) { ellipse_dx0 *= -1; }
|
|
||||||
ellipse_y0 += ellipse_dy0;
|
|
||||||
if (ellipse_y0 < 0 || ellipse_y0 > DEVICE_H * 4) { ellipse_dy0 *= -1; }
|
|
||||||
|
|
||||||
ellipse_x1 += ellipse_dx1;
|
bool allow_input = true;
|
||||||
if (ellipse_x1 < 0 || ellipse_x1 > DEVICE_W * 4) { ellipse_dx1 *= -1; }
|
|
||||||
ellipse_y1 += ellipse_dy1;
|
game_collectibles_update();
|
||||||
if (ellipse_y1 < 0 || ellipse_y1 > DEVICE_H * 4) { ellipse_dy1 *= -1; }
|
game_dialogue_update(&allow_input);
|
||||||
|
game_inflict_update(&allow_input);
|
||||||
|
game_npcs_update(&allow_input);
|
||||||
|
game_player_update(&allow_input);
|
||||||
}
|
}
|
||||||
|
|
||||||
void game_draw() {
|
void game_draw() {
|
||||||
sys_dpal_set(0xff, 0xfe); // map to a non-transparent color
|
sys_cls(9);
|
||||||
|
|
||||||
for (int x = 0; x < DEVICE_W; x++) {
|
game_player_set_camera();
|
||||||
for (int y = 0; y < DEVICE_H; y++) {
|
sys_map_draw(map_game_map, spr_game_tiles, 0, 0, 0, 0, map_game_map.width, map_game_map.height);
|
||||||
uint32_t r = (x * 255)/(DEVICE_W - 1);
|
|
||||||
uint32_t g = (y * 255)/(DEVICE_H - 1);
|
|
||||||
uint32_t b = game_frame & 0x100 ? 0xff - game_frame & 0xff : game_frame & 0xff;
|
|
||||||
if (x % 4 == 2 && y % 4 == 2) {
|
|
||||||
r = 255 - r;
|
|
||||||
g = 255 - g;
|
|
||||||
b = 255 - b;
|
|
||||||
}
|
|
||||||
sys_color color = (r >> 5) << 5 | (g >> 5) << 2 | (b >> 6);
|
|
||||||
sys_pixel_set(x, y, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < DEVICE_BUTTON_N; i++) {
|
game_collectibles_draw();
|
||||||
sys_pixel_set(i, 0, device_buttons[i] ? 0x00 : 0xff);
|
game_npcs_draw();
|
||||||
}
|
game_player_draw();
|
||||||
|
|
||||||
int x0 = ellipse_x0 / 4;
|
sys_camera_reset();
|
||||||
int y0 = ellipse_y0 / 4;
|
|
||||||
int x1 = ellipse_x1 / 4;
|
|
||||||
int y1 = ellipse_y1 / 4;
|
|
||||||
sys_oval_draw_ext(x0, y0, x1, y1, 10, true);
|
|
||||||
sys_oval_draw_ext(x0, y0, x1, y1, 248, false);
|
|
||||||
sys_circ_draw_ext(x0, y0, 4, 252, true);
|
|
||||||
sys_circ_draw_ext(x0, y0, 6, 248, false);
|
|
||||||
sys_circ_draw_ext(x0, y0, 8, 244, false);
|
|
||||||
sys_line_draw(x0, y0, x1, y1, 254);
|
|
||||||
|
|
||||||
sys_print("Hello, blood\nsources!", x1, y1, 224);
|
game_player_draw_hud();
|
||||||
|
game_inflict_draw();
|
||||||
|
game_dialogue_draw();
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,15 @@
|
|||||||
#ifndef CROCPARTY_GAME_H
|
#ifndef CROCPARTY_GAME_H
|
||||||
#define CROCPARTY_GAME_H
|
#define CROCPARTY_GAME_H
|
||||||
|
|
||||||
|
#include "game_collectible.h"
|
||||||
|
#include "game_collision.h"
|
||||||
|
#include "game_dialogue.h"
|
||||||
|
#include "game_inflict.h"
|
||||||
|
#include "game_math.h"
|
||||||
|
#include "game_npc.h"
|
||||||
|
#include "game_palette.h"
|
||||||
|
#include "game_player.h"
|
||||||
|
|
||||||
const char* game_title();
|
const char* game_title();
|
||||||
void game_init();
|
void game_init();
|
||||||
void game_destroy();
|
void game_destroy();
|
||||||
|
114
game/game_collectible.c
Normal file
114
game/game_collectible.c
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include "game.h"
|
||||||
|
#include "art/game_collectibles.h"
|
||||||
|
|
||||||
|
// If this isn't enough, raise the number
|
||||||
|
#define GAME_COLLECTIBLES_N 32
|
||||||
|
|
||||||
|
sys_i32 game_collectible_next = 0;
|
||||||
|
game_collectible game_collectibles[GAME_COLLECTIBLES_N];
|
||||||
|
|
||||||
|
struct {
|
||||||
|
sys_i32 x;
|
||||||
|
sys_i32 y;
|
||||||
|
sys_i32 r;
|
||||||
|
} game_collectible_bubble = {
|
||||||
|
.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
.r = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
sys_i32 game_collectible_wobb_frame = 0;
|
||||||
|
|
||||||
|
void game_collectible_create(sys_i32 x, sys_i32 y, game_collectible_type type) {
|
||||||
|
assert(game_collectible_next < GAME_COLLECTIBLES_N && "too many collectibles");
|
||||||
|
|
||||||
|
sys_i32 id = game_collectible_next++;
|
||||||
|
game_collectibles[id].x = x;
|
||||||
|
game_collectibles[id].y = y;
|
||||||
|
game_collectibles[id].type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_i32 game_collectible_wobb() {
|
||||||
|
return game_wobb3(game_collectible_wobb_frame & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_collectibles_update() {
|
||||||
|
game_collectible_wobb_frame += 1;
|
||||||
|
game_collectible_bubble.r = game_collectible_bubble.r * 7 / 8;
|
||||||
|
if (game_collectible_bubble.r < PIXEL_SZ_MICROPIXEL / 2) { game_collectible_bubble.r = 0; }
|
||||||
|
|
||||||
|
// check collision with player
|
||||||
|
sys_i32 player_x, player_y;
|
||||||
|
game_player_get_center(&player_x, &player_y);
|
||||||
|
|
||||||
|
for (sys_i32 i = 0; i < game_collectible_next; i++) {
|
||||||
|
sys_i32 r;
|
||||||
|
game_collectible* c = &game_collectibles[i];
|
||||||
|
|
||||||
|
if (c->collected) { continue; }
|
||||||
|
|
||||||
|
switch (c->type) {
|
||||||
|
case GAME_COLLECTIBLE_TYPE_CAKE: r = 16; break;
|
||||||
|
default: r = 8; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert to micropixels
|
||||||
|
// add approx radius of player
|
||||||
|
sys_i32 r_distcheck = (r + 5) * PIXEL_SZ_MICROPIXEL;
|
||||||
|
|
||||||
|
int64_t dx = player_x - c->x;
|
||||||
|
int64_t dy = player_y - c->y;
|
||||||
|
|
||||||
|
if (dx * dx + dy * dy <= r_distcheck * r_distcheck) {
|
||||||
|
c->collected = true;
|
||||||
|
// TODO: Spawn effect
|
||||||
|
game_collectible_bubble.x=c->x / PIXEL_SZ_MICROPIXEL;
|
||||||
|
game_collectible_bubble.y=c->y / PIXEL_SZ_MICROPIXEL + game_collectible_wobb();
|
||||||
|
game_collectible_bubble.r=r * PIXEL_SZ_MICROPIXEL;
|
||||||
|
|
||||||
|
switch (c->type) {
|
||||||
|
case GAME_COLLECTIBLE_TYPE_CAKE: game_player_collectibles.n_cake += 16; break;
|
||||||
|
case GAME_COLLECTIBLE_TYPE_MONEY_BIG: game_player_collectibles.n_dollars += 5; break;
|
||||||
|
case GAME_COLLECTIBLE_TYPE_MONEY_SMALL: game_player_collectibles.n_dollars += 1; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_collectibles_draw() {
|
||||||
|
if (game_collectible_bubble.r > 0) {
|
||||||
|
sys_circ_fill(
|
||||||
|
game_collectible_bubble.x,
|
||||||
|
game_collectible_bubble.y,
|
||||||
|
game_collectible_bubble.r / PIXEL_SZ_MICROPIXEL,
|
||||||
|
7
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (sys_i32 i = 0; i < game_collectible_next; i++) {
|
||||||
|
game_collectible* c = &game_collectibles[i];
|
||||||
|
|
||||||
|
if (c->collected) { continue; }
|
||||||
|
|
||||||
|
sys_i32 x = c->x / PIXEL_SZ_MICROPIXEL;
|
||||||
|
sys_i32 y = c->y / PIXEL_SZ_MICROPIXEL + game_collectible_wobb();
|
||||||
|
switch(c->type) {
|
||||||
|
case GAME_COLLECTIBLE_TYPE_CAKE:
|
||||||
|
sys_sprite_draw_ext(
|
||||||
|
spr_game_collectibles,0,x-16,y-16,4,4,false,false);
|
||||||
|
break;
|
||||||
|
case GAME_COLLECTIBLE_TYPE_MONEY_BIG:
|
||||||
|
sys_sprite_draw_ext(
|
||||||
|
spr_game_collectibles,4,x-8,y-8,2,2,false,false);
|
||||||
|
break;
|
||||||
|
case GAME_COLLECTIBLE_TYPE_MONEY_SMALL:
|
||||||
|
sys_sprite_draw_ext(
|
||||||
|
spr_game_collectibles,16,x-8,y-8,2,2,false,false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert(false&&"invalid collectible");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
25
game/game_collectible.h
Normal file
25
game/game_collectible.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#ifndef GAME_COLLECTIBLE_H
|
||||||
|
#define GAME_COLLECTIBLE_H
|
||||||
|
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GAME_COLLECTIBLE_TYPE_CAKE,
|
||||||
|
GAME_COLLECTIBLE_TYPE_MONEY_BIG,
|
||||||
|
GAME_COLLECTIBLE_TYPE_MONEY_SMALL,
|
||||||
|
GAME_COLLECTIBLE_TYPE_N
|
||||||
|
} game_collectible_type;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
sys_i32 x;
|
||||||
|
sys_i32 y;
|
||||||
|
game_collectible_type type;
|
||||||
|
bool collected;
|
||||||
|
} game_collectible;
|
||||||
|
|
||||||
|
void game_collectible_create(sys_i32 x, sys_i32 y, game_collectible_type type);
|
||||||
|
|
||||||
|
void game_collectibles_update();
|
||||||
|
void game_collectibles_draw();
|
||||||
|
|
||||||
|
#endif // GAME_COLLECTIBLE_H
|
143
game/game_collision.c
Normal file
143
game/game_collision.c
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include "sys/sys.h"
|
||||||
|
#include "game.h"
|
||||||
|
#include "map/game_map.h"
|
||||||
|
|
||||||
|
bool game_collision_is_occlusive(sys_maptile tile) {
|
||||||
|
if (tile >= 0 && tile < 30) { return true; }
|
||||||
|
if (tile >= 60 && tile < 90) { return true; }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// c's modulus can produce a negative result. don't allow that!
|
||||||
|
#define modulus(x, mod) ((x) % (mod) + (mod)) % (mod)
|
||||||
|
#define round_down(x, incr) (x) - modulus(x, incr)
|
||||||
|
#define round_up(x, incr) round_down((x) + (incr) - 1, incr)
|
||||||
|
|
||||||
|
bool game_collision_can_move(game_bbox body, sys_i32 dx, sys_i32 dy) {
|
||||||
|
// First: place the body in space.
|
||||||
|
sys_i32 x = body.x - body.x_origin + dx;
|
||||||
|
sys_i32 y = body.y - body.y_origin + dy;
|
||||||
|
|
||||||
|
// this is extremely similar to the logic in move_to_contact
|
||||||
|
// but has different rounding behavior
|
||||||
|
// (because a body at a fractional position must not actually collide
|
||||||
|
// with fractional pixels)
|
||||||
|
sys_i32 body_x0 = round_down(x, PIXEL_SZ_MICROPIXEL);
|
||||||
|
sys_i32 body_y0 = round_down(y, PIXEL_SZ_MICROPIXEL);
|
||||||
|
sys_i32 body_x1 = body_x0 + body.w;
|
||||||
|
sys_i32 body_y1 = body_y0 + body.h;
|
||||||
|
|
||||||
|
sys_i32 tile_x0 = round_down(body_x0, TILE_SZ_MICROPIXEL);
|
||||||
|
sys_i32 tile_y0 = round_down(body_y0, TILE_SZ_MICROPIXEL);
|
||||||
|
sys_i32 tile_x1 = round_up(body_x1, TILE_SZ_MICROPIXEL);
|
||||||
|
sys_i32 tile_y1 = round_up(body_y1, TILE_SZ_MICROPIXEL);
|
||||||
|
|
||||||
|
// now convert to tile coords instead of pixels
|
||||||
|
tile_x0 /= TILE_SZ_MICROPIXEL;
|
||||||
|
tile_y0 /= TILE_SZ_MICROPIXEL;
|
||||||
|
tile_x1 /= TILE_SZ_MICROPIXEL;
|
||||||
|
tile_y1 /= TILE_SZ_MICROPIXEL;
|
||||||
|
|
||||||
|
for (sys_i32 y = tile_y0; y < tile_y1; y++) {
|
||||||
|
for (sys_i32 x = tile_x0; x < tile_x1; x++) {
|
||||||
|
bool invalid;
|
||||||
|
sys_maptile tile = sys_map_get(map_game_map, x, y, &invalid);
|
||||||
|
if (invalid || game_collision_is_occlusive(tile)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
game_collision game_collision_move_to_contact(
|
||||||
|
game_bbox* body, sys_i32 total_dx, sys_i32 total_dy
|
||||||
|
) {
|
||||||
|
sys_i32 src_x = body->x;
|
||||||
|
sys_i32 src_y = body->y;
|
||||||
|
game_collision collision = {
|
||||||
|
.collided_x=false,
|
||||||
|
.collided_y=false,
|
||||||
|
.distance_x=0,
|
||||||
|
.distance_y=0,
|
||||||
|
};
|
||||||
|
sys_i32 tar_x = body->x + total_dx;
|
||||||
|
sys_i32 tar_y = body->y + total_dy;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
// calculate how much we still need to move
|
||||||
|
int32_t remaining_dx = tar_x - body->x;
|
||||||
|
int32_t remaining_dy = tar_y - body->y;
|
||||||
|
|
||||||
|
// if we don't need to move, stop
|
||||||
|
if (remaining_dx == 0 && remaining_dy == 0) {
|
||||||
|
collision.distance_x = body->x - src_x;
|
||||||
|
collision.distance_y = body->x - src_y;
|
||||||
|
return collision;
|
||||||
|
}
|
||||||
|
|
||||||
|
// figure out where we are
|
||||||
|
sys_i32 body_x0 = body->x - body->x_origin;
|
||||||
|
sys_i32 body_y0 = body->y - body->y_origin;
|
||||||
|
sys_i32 body_x1 = body_x0 + body->w;
|
||||||
|
sys_i32 body_y1 = body_y0 + body->h;
|
||||||
|
|
||||||
|
// figure out what tile we're still in
|
||||||
|
sys_i32 tile_x0 = round_down(body_x0, TILE_SZ_MICROPIXEL);
|
||||||
|
sys_i32 tile_y0 = round_down(body_y0, TILE_SZ_MICROPIXEL);
|
||||||
|
sys_i32 max_overlap = PIXEL_SZ_MICROPIXEL - 1;
|
||||||
|
sys_i32 tile_x1 = round_up(body_x1 - max_overlap, TILE_SZ_MICROPIXEL) + max_overlap;
|
||||||
|
sys_i32 tile_y1 = round_up(body_y1 - max_overlap, TILE_SZ_MICROPIXEL) + max_overlap;
|
||||||
|
|
||||||
|
// but we want the _next_ tile
|
||||||
|
if (tile_x0 == body_x0) { tile_x0 -= TILE_SZ_MICROPIXEL; }
|
||||||
|
if (tile_y0 == body_y0) { tile_y0 -= TILE_SZ_MICROPIXEL; }
|
||||||
|
if (tile_x1 == body_x1) { tile_x1 += TILE_SZ_MICROPIXEL; }
|
||||||
|
if (tile_y1 == body_y1) { tile_y1 += TILE_SZ_MICROPIXEL; }
|
||||||
|
|
||||||
|
// length of an x axis move
|
||||||
|
int step_dx =
|
||||||
|
remaining_dx < 0 ?
|
||||||
|
// line up our left side with the tile to our left
|
||||||
|
// not exceeding dx
|
||||||
|
sys_max_i32(remaining_dx, tile_x0 - body_x0) :
|
||||||
|
remaining_dx > 0 ?
|
||||||
|
// line up our right side with the tile to our right
|
||||||
|
// not exceeding dx
|
||||||
|
sys_min_i32(remaining_dx, tile_x1 - body_x1) :
|
||||||
|
// dx == 0
|
||||||
|
INT32_MAX ;
|
||||||
|
|
||||||
|
int step_dy =
|
||||||
|
remaining_dy < 0 ?
|
||||||
|
// line up our top side with the tile above us
|
||||||
|
// not exceeding dy
|
||||||
|
sys_max_i32(remaining_dy, tile_y0 - body_y0) :
|
||||||
|
remaining_dy > 0 ?
|
||||||
|
// line up our bottom side with the tile below us
|
||||||
|
// not exceeding dy
|
||||||
|
sys_min_i32(remaining_dy, tile_y1 - body_y1) :
|
||||||
|
INT32_MAX;
|
||||||
|
|
||||||
|
// there must be a viable move in some direction
|
||||||
|
assert(step_dx != INT32_MAX || step_dy != INT32_MAX);
|
||||||
|
|
||||||
|
if (sys_abs_i32(step_dx) < sys_abs_i32(step_dy)) {
|
||||||
|
if (game_collision_can_move(*body, step_dx, 0)) {
|
||||||
|
body->x += step_dx; // take the tiny move
|
||||||
|
} else {
|
||||||
|
tar_x = body->x; // stop trying to move
|
||||||
|
collision.collided_x = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (game_collision_can_move(*body, 0, step_dy)) {
|
||||||
|
body->y += step_dy; // take the tiny move
|
||||||
|
} else {
|
||||||
|
tar_y = body->y; // stop trying to move
|
||||||
|
collision.collided_y = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
game/game_collision.h
Normal file
30
game/game_collision.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#ifndef GAME_COLLISION_H
|
||||||
|
#define GAME_COLLISION_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
#define TILE_SZ_MICROPIXEL 0x800
|
||||||
|
#define PIXEL_SZ_MICROPIXEL 0x100
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
sys_i32 x_origin;
|
||||||
|
sys_i32 y_origin;
|
||||||
|
sys_i32 x;
|
||||||
|
sys_i32 y;
|
||||||
|
sys_i32 w;
|
||||||
|
sys_i32 h;
|
||||||
|
} game_bbox;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool collided_x;
|
||||||
|
bool collided_y;
|
||||||
|
sys_i32 distance_x;
|
||||||
|
sys_i32 distance_y;
|
||||||
|
} game_collision;
|
||||||
|
|
||||||
|
bool game_collision_is_occlusive(sys_maptile tile);
|
||||||
|
bool game_collision_can_move(game_bbox body, sys_i32 dx, sys_i32 dy);
|
||||||
|
game_collision game_collision_move_to_contact(game_bbox* body, sys_i32 x, sys_i32 y);
|
||||||
|
|
||||||
|
#endif // GAME_COLLISION_H
|
158
game/game_dialogue.c
Normal file
158
game/game_dialogue.c
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "device/device.h"
|
||||||
|
#include "sys/sys.h"
|
||||||
|
#include "game.h"
|
||||||
|
|
||||||
|
#define GAME_DIALOGUE_MAX_N_CHARS 256
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GAME_DIALOGUE_SCREEN_STATE_BOB_IN,
|
||||||
|
GAME_DIALOGUE_SCREEN_STATE_TYPEWRITER,
|
||||||
|
GAME_DIALOGUE_SCREEN_STATE_WAIT,
|
||||||
|
GAME_DIALOGUE_SCREEN_STATE_BOB_OUT,
|
||||||
|
} game_dialogue_screen_state;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool visible;
|
||||||
|
bool modal;
|
||||||
|
const char* dialogue;
|
||||||
|
|
||||||
|
sys_i32 w;
|
||||||
|
sys_i32 h;
|
||||||
|
|
||||||
|
game_dialogue_screen_state state;
|
||||||
|
sys_i32 state_progress;
|
||||||
|
sys_i32 n_chars_shown;
|
||||||
|
sys_i32 n_chars;
|
||||||
|
} game_dialogue_screen;
|
||||||
|
|
||||||
|
uint8_t game_dialogue_frame;
|
||||||
|
|
||||||
|
void game_dialogue_display(const char* text, bool modal) {
|
||||||
|
game_dialogue_screen.visible = true;
|
||||||
|
game_dialogue_screen.modal = modal;
|
||||||
|
game_dialogue_screen.dialogue = text;
|
||||||
|
sys_measure_text(text, &game_dialogue_screen.w, &game_dialogue_screen.h);
|
||||||
|
|
||||||
|
game_dialogue_screen.state = GAME_DIALOGUE_SCREEN_STATE_BOB_IN;
|
||||||
|
game_dialogue_screen.state_progress = 0;
|
||||||
|
game_dialogue_screen.n_chars_shown = 0;
|
||||||
|
game_dialogue_screen.n_chars = strlen(text);
|
||||||
|
assert(game_dialogue_screen.n_chars <= GAME_DIALOGUE_MAX_N_CHARS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_dialogue_update(bool* allow_input) {
|
||||||
|
game_dialogue_frame = ((uint32_t) game_dialogue_frame + 1) & 0xff;
|
||||||
|
|
||||||
|
if (
|
||||||
|
game_dialogue_screen.visible &&
|
||||||
|
game_dialogue_screen.state < GAME_DIALOGUE_SCREEN_STATE_BOB_OUT
|
||||||
|
) {
|
||||||
|
*allow_input = false;
|
||||||
|
|
||||||
|
if (sys_btnp(DEVICE_BUTTON_0, false)) {
|
||||||
|
if (game_dialogue_screen.state < GAME_DIALOGUE_SCREEN_STATE_WAIT) {
|
||||||
|
game_dialogue_screen.state = GAME_DIALOGUE_SCREEN_STATE_WAIT;
|
||||||
|
game_dialogue_screen.n_chars_shown = game_dialogue_screen.n_chars;
|
||||||
|
} else {
|
||||||
|
game_dialogue_screen.state = GAME_DIALOGUE_SCREEN_STATE_BOB_OUT;
|
||||||
|
game_dialogue_screen.state_progress = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (game_dialogue_screen.state) {
|
||||||
|
case GAME_DIALOGUE_SCREEN_STATE_BOB_IN:
|
||||||
|
game_dialogue_screen.state_progress += 25;
|
||||||
|
if (game_dialogue_screen.state_progress >= 0x100) {
|
||||||
|
game_dialogue_screen.state = GAME_DIALOGUE_SCREEN_STATE_TYPEWRITER;
|
||||||
|
game_dialogue_screen.state_progress = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GAME_DIALOGUE_SCREEN_STATE_TYPEWRITER:
|
||||||
|
if (game_dialogue_frame % 4 == 0) {
|
||||||
|
game_dialogue_screen.n_chars_shown = sys_min_i32(
|
||||||
|
game_dialogue_screen.n_chars_shown + 1,
|
||||||
|
game_dialogue_screen.n_chars
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (game_dialogue_screen.n_chars_shown >= game_dialogue_screen.n_chars) {
|
||||||
|
game_dialogue_screen.state = GAME_DIALOGUE_SCREEN_STATE_WAIT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GAME_DIALOGUE_SCREEN_STATE_WAIT:
|
||||||
|
break;
|
||||||
|
case GAME_DIALOGUE_SCREEN_STATE_BOB_OUT:
|
||||||
|
game_dialogue_screen.state_progress += 25;
|
||||||
|
if (game_dialogue_screen.state_progress >= 0x100) {
|
||||||
|
game_dialogue_screen.visible = false;
|
||||||
|
game_dialogue_screen.state_progress = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool game_dialogue_is_busy() {
|
||||||
|
return game_dialogue_screen.visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_dialogue_draw() {
|
||||||
|
if (!game_dialogue_screen.visible) { return; }
|
||||||
|
|
||||||
|
sys_i32 x = DEVICE_W / 2 - game_dialogue_screen.w / 2;
|
||||||
|
sys_i32 y = DEVICE_H - game_dialogue_screen.h - 8;
|
||||||
|
|
||||||
|
if (game_dialogue_screen.modal) {
|
||||||
|
y = DEVICE_H / 2 - game_dialogue_screen.h / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_i32 interp_amt = 0;
|
||||||
|
switch (game_dialogue_screen.state) {
|
||||||
|
case GAME_DIALOGUE_SCREEN_STATE_BOB_IN:
|
||||||
|
interp_amt = 255 - game_dialogue_screen.state_progress;
|
||||||
|
break;
|
||||||
|
case GAME_DIALOGUE_SCREEN_STATE_BOB_OUT:
|
||||||
|
interp_amt = game_dialogue_screen.state_progress;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x0 = x - 4;
|
||||||
|
int y0 = y - 4;
|
||||||
|
int x1 = x + game_dialogue_screen.w + 4;
|
||||||
|
int y1 = y + game_dialogue_screen.h + 4;
|
||||||
|
|
||||||
|
sys_i32 cx = (x0 + x1)/2;
|
||||||
|
sys_i32 cy = (y0 + y1)/2;
|
||||||
|
|
||||||
|
sys_oval_fill(
|
||||||
|
x0 + (cx - x0) * interp_amt / 256,
|
||||||
|
y0 + (cy - y0) * interp_amt / 256,
|
||||||
|
x1 + (cx - x1) * interp_amt / 256,
|
||||||
|
y1 + (cy - y1) * interp_amt / 256,
|
||||||
|
game_dialogue_screen.modal ? 0 : 1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (game_dialogue_screen.state == GAME_DIALOGUE_SCREEN_STATE_BOB_OUT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char chars[GAME_DIALOGUE_MAX_N_CHARS + 1];
|
||||||
|
strcpy(chars, game_dialogue_screen.dialogue);
|
||||||
|
chars[game_dialogue_screen.n_chars_shown] = 0;
|
||||||
|
|
||||||
|
if (game_dialogue_screen.modal) {
|
||||||
|
for (int dy = -2; dy <= 2; dy++) {
|
||||||
|
for (int dx = -2; dx <= 2; dx++) {
|
||||||
|
sys_print(chars, x+dx, y+dy, 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int dy = -1; dy <= 1; dy++) {
|
||||||
|
for (int dx = -1; dx <= 1; dx++) {
|
||||||
|
sys_print(chars, x+dx, y+dy, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sys_print(chars, x, y, 7);
|
||||||
|
}
|
12
game/game_dialogue.h
Normal file
12
game/game_dialogue.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef GAME_DIALOGUE_H
|
||||||
|
#define GAME_DIALOGUE_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
void game_dialogue_display(const char* text, bool modal);
|
||||||
|
bool game_dialogue_is_busy();
|
||||||
|
|
||||||
|
void game_dialogue_update(bool* allow_input);
|
||||||
|
void game_dialogue_draw();
|
||||||
|
|
||||||
|
#endif // GAME_DIALOGUE_H
|
119
game/game_inflict.c
Normal file
119
game/game_inflict.c
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
#include "game.h"
|
||||||
|
#include "device/device.h"
|
||||||
|
|
||||||
|
#define GAME_INFLICT_SCREEN_MAX_N_MODALS 4
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GAME_INFLICT_SCREEN_STATE_WAIT_FOR_DIALOGUE,
|
||||||
|
GAME_INFLICT_SCREEN_STATE_CLOSE_EYELIDS,
|
||||||
|
GAME_INFLICT_SCREEN_STATE_PERFORM_MODALS,
|
||||||
|
GAME_INFLICT_SCREEN_STATE_OPEN_EYELIDS,
|
||||||
|
GAME_INFLICT_SCREEN_STATE_INACTIVE
|
||||||
|
} game_inflict_screen_state_t;
|
||||||
|
|
||||||
|
game_inflict_screen_state_t game_inflict_screen_state =
|
||||||
|
GAME_INFLICT_SCREEN_STATE_INACTIVE;
|
||||||
|
|
||||||
|
game_inflict_type game_inflict_screen_type = 0;
|
||||||
|
sys_i32 game_inflict_screen_n_modals = 0;
|
||||||
|
sys_i32 game_inflict_screen_next_modal = 0;
|
||||||
|
sys_i32 game_inflict_screen_progress = 0;
|
||||||
|
|
||||||
|
const char* game_inflict_screen_modals[GAME_INFLICT_SCREEN_MAX_N_MODALS];
|
||||||
|
|
||||||
|
void game_inflict_apply();
|
||||||
|
|
||||||
|
void game_inflict_update(bool* allow_input) {
|
||||||
|
if (game_inflict_screen_state <= GAME_INFLICT_SCREEN_STATE_OPEN_EYELIDS) {
|
||||||
|
*allow_input = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (game_inflict_screen_state) {
|
||||||
|
case GAME_INFLICT_SCREEN_STATE_WAIT_FOR_DIALOGUE:
|
||||||
|
if (!game_dialogue_is_busy()) {
|
||||||
|
game_inflict_screen_state = GAME_INFLICT_SCREEN_STATE_CLOSE_EYELIDS;
|
||||||
|
game_inflict_screen_progress = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GAME_INFLICT_SCREEN_STATE_CLOSE_EYELIDS:
|
||||||
|
game_inflict_screen_progress += 10;
|
||||||
|
if (game_inflict_screen_progress >= 0x100) {
|
||||||
|
game_inflict_screen_state =
|
||||||
|
GAME_INFLICT_SCREEN_STATE_PERFORM_MODALS;
|
||||||
|
game_inflict_screen_progress = 0;
|
||||||
|
game_inflict_apply();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GAME_INFLICT_SCREEN_STATE_PERFORM_MODALS:
|
||||||
|
if (game_dialogue_is_busy()) {
|
||||||
|
// fine, don't do anything
|
||||||
|
} else if (game_inflict_screen_next_modal < game_inflict_screen_n_modals) {
|
||||||
|
game_dialogue_display(game_inflict_screen_modals[
|
||||||
|
game_inflict_screen_next_modal++
|
||||||
|
], true);
|
||||||
|
} else {
|
||||||
|
game_inflict_screen_state =
|
||||||
|
GAME_INFLICT_SCREEN_STATE_OPEN_EYELIDS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GAME_INFLICT_SCREEN_STATE_OPEN_EYELIDS:
|
||||||
|
game_inflict_screen_progress += 10;
|
||||||
|
if (game_inflict_screen_progress >= 0x100) {
|
||||||
|
game_inflict_screen_state =
|
||||||
|
GAME_INFLICT_SCREEN_STATE_INACTIVE;
|
||||||
|
game_inflict_screen_progress = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GAME_INFLICT_SCREEN_STATE_INACTIVE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_inflict_enqueue(int inflict_id) {
|
||||||
|
game_inflict_screen_state = GAME_INFLICT_SCREEN_STATE_WAIT_FOR_DIALOGUE;
|
||||||
|
|
||||||
|
switch (inflict_id) {
|
||||||
|
case GAME_INFLICT_TYPE_CHICKEN:
|
||||||
|
game_inflict_screen_type = GAME_INFLICT_TYPE_CHICKEN;
|
||||||
|
game_inflict_screen_n_modals = 2;
|
||||||
|
game_inflict_screen_next_modal = 0;
|
||||||
|
game_inflict_screen_modals[0] = "For your birthday...";
|
||||||
|
game_inflict_screen_modals[1] = "You're a chicken!";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_inflict_apply() {
|
||||||
|
switch (game_inflict_screen_type) {
|
||||||
|
case GAME_INFLICT_TYPE_CHICKEN:
|
||||||
|
game_player_collectibles.n_cake = 0;
|
||||||
|
game_player_phase = GAME_PLAYER_PHASE_CHICKEN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_inflict_draw_eyelids(uint8_t progress);
|
||||||
|
void game_inflict_draw() {
|
||||||
|
switch (game_inflict_screen_state) {
|
||||||
|
case GAME_INFLICT_SCREEN_STATE_WAIT_FOR_DIALOGUE:
|
||||||
|
// TODO: Screen distortion
|
||||||
|
break;
|
||||||
|
case GAME_INFLICT_SCREEN_STATE_CLOSE_EYELIDS:
|
||||||
|
game_inflict_draw_eyelids(game_inflict_screen_progress & 0xff);
|
||||||
|
break;
|
||||||
|
case GAME_INFLICT_SCREEN_STATE_PERFORM_MODALS:
|
||||||
|
game_inflict_draw_eyelids(255);
|
||||||
|
break;
|
||||||
|
case GAME_INFLICT_SCREEN_STATE_OPEN_EYELIDS:
|
||||||
|
game_inflict_draw_eyelids(255 - game_inflict_screen_progress & 0xff);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_inflict_draw_eyelids(uint8_t progress) {
|
||||||
|
sys_i32 offset = DEVICE_H / 2 -
|
||||||
|
((255 - progress) * (255 - progress) * DEVICE_H/2) / (255 * 255);
|
||||||
|
sys_rect_fill(0, 0, DEVICE_W, offset, 0);
|
||||||
|
sys_rect_fill(0, DEVICE_H - offset, DEVICE_W, DEVICE_H, 0);
|
||||||
|
}
|
19
game/game_inflict.h
Normal file
19
game/game_inflict.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#ifndef GAME_INFLICT_H
|
||||||
|
#define GAME_INFLICT_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "game.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
// GAME_INFLICT_TYPE_CROC=1,
|
||||||
|
GAME_INFLICT_TYPE_CHICKEN=2,
|
||||||
|
// GAME_INFLICT_TYPE_BAT=3,
|
||||||
|
// GAME_INFLICT_TYPE_ASLEEP=4,
|
||||||
|
} game_inflict_type;
|
||||||
|
|
||||||
|
void game_inflict_update(bool* allow_input);
|
||||||
|
void game_inflict_draw();
|
||||||
|
|
||||||
|
void game_inflict_enqueue(int inflict_id);
|
||||||
|
|
||||||
|
#endif // GAME_INFLICT_H
|
36
game/game_math.c
Normal file
36
game/game_math.c
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#include "game_math.h"
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
// prog runs from 0 to 255
|
||||||
|
|
||||||
|
// except for game_wobb1, all of these are
|
||||||
|
// generated with list(enumerate([round(sin(i/256 * pi * 2)*k) for i in range(256)]))
|
||||||
|
// for k = various values
|
||||||
|
|
||||||
|
sys_i32 game_wobb1(uint8_t prog) {
|
||||||
|
// return 0 or 1 in a roughly sinusoidal pattern
|
||||||
|
if (prog<128) return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_i32 game_wobb2(uint8_t prog) {
|
||||||
|
// return -1, 0, 1 in a roughly sinusoidal pattern
|
||||||
|
if (prog<22) return 0;
|
||||||
|
if (prog<107) return 1;
|
||||||
|
if (prog<150) return 0;
|
||||||
|
if (prog<235) return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_i32 game_wobb3(uint8_t prog) {
|
||||||
|
// return -2, -1, 0, 1, 2 in a roughly sinusoidal pattern
|
||||||
|
if (prog<11) return 0;
|
||||||
|
if (prog<35) return 1;
|
||||||
|
if (prog<94) return 2;
|
||||||
|
if (prog<118) return 1;
|
||||||
|
if (prog<139) return 0;
|
||||||
|
if (prog<163) return -1;
|
||||||
|
if (prog<222) return -2;
|
||||||
|
if (prog<246) return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
11
game/game_math.h
Normal file
11
game/game_math.h
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#ifndef GAME_MATH_H
|
||||||
|
#define GAME_MATH_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
sys_i32 game_wobb1(uint8_t prog);
|
||||||
|
sys_i32 game_wobb2(uint8_t prog);
|
||||||
|
sys_i32 game_wobb3(uint8_t prog);
|
||||||
|
|
||||||
|
#endif // GAME_MATH_H
|
171
game/game_npc.c
Normal file
171
game/game_npc.c
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include "art/game_hud.h"
|
||||||
|
#include "art/game_npcs.h"
|
||||||
|
#include "device/device.h"
|
||||||
|
#include "sys/sys.h"
|
||||||
|
#include "game.h"
|
||||||
|
|
||||||
|
// If this isn't enough, raise the number
|
||||||
|
#define GAME_NPCS_N 32
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t main;
|
||||||
|
// alt0 and alt1 are ramps
|
||||||
|
uint8_t alt0_0; uint8_t alt0_1;
|
||||||
|
uint8_t alt1_0; uint8_t alt1_1;
|
||||||
|
uint8_t eye_0; uint8_t eye_1;
|
||||||
|
uint8_t pupil;
|
||||||
|
uint8_t adornment;
|
||||||
|
uint8_t shine;
|
||||||
|
} game_npc_palette;
|
||||||
|
|
||||||
|
game_npc_palette game_npc_palettes[5] = {
|
||||||
|
{14, 13, 12, 6, 7, 6, 7, 0, 8, 7},
|
||||||
|
{4, 2, 3, 6, 7, 9, 10, 0, 4, 7},
|
||||||
|
{13, 0, 1, 1, 13, 11, 10, 0, 13, 7},
|
||||||
|
{7, 14, 7, 14, 7, 14, 7, 0, 14, 7},
|
||||||
|
{12, 1, 13, 12, 6, 8, 7, 0, 8, 7}
|
||||||
|
};
|
||||||
|
|
||||||
|
#define GAME_NPC_N_PALETTES (sizeof(game_npc_palettes)/sizeof(game_npc_palettes[0]))
|
||||||
|
|
||||||
|
sys_i32 game_npc_next = 0;
|
||||||
|
game_npc game_npcs[GAME_NPCS_N];
|
||||||
|
|
||||||
|
game_npc* game_npc_create(sys_i32 x, sys_i32 y) {
|
||||||
|
assert(game_npc_next < GAME_NPCS_N && "too many NPCs");
|
||||||
|
|
||||||
|
sys_i32 id = game_npc_next++;
|
||||||
|
game_npc npc = {
|
||||||
|
.x = x,
|
||||||
|
.y = y,
|
||||||
|
|
||||||
|
.sprite_id = 0,
|
||||||
|
.palette_id = 0,
|
||||||
|
.inflict_id = 0,
|
||||||
|
.face_left = false,
|
||||||
|
.flip = false,
|
||||||
|
|
||||||
|
.n_dialogues = 0,
|
||||||
|
.no_cake_dialogue = NULL,
|
||||||
|
/* .dialogues = { doesn't matter }, */
|
||||||
|
|
||||||
|
.present = true,
|
||||||
|
.n_dialogues_seen = 0,
|
||||||
|
.received_cake = false,
|
||||||
|
};
|
||||||
|
game_npcs[id] = npc;
|
||||||
|
return &game_npcs[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_npc_trigger_dialogue(sys_i32 npc_id);
|
||||||
|
void game_npcs_update(bool* allow_input) {
|
||||||
|
if (!*allow_input) { return; }
|
||||||
|
|
||||||
|
// check collision with player
|
||||||
|
sys_i32 player_x, player_y;
|
||||||
|
|
||||||
|
game_player_get_center(&player_x, &player_y);
|
||||||
|
for (sys_i32 i = 0; i < game_npc_next; i++) {
|
||||||
|
game_npc* n = &game_npcs[i];
|
||||||
|
|
||||||
|
if (!n->present) { continue; }
|
||||||
|
|
||||||
|
sys_i32 r_distcheck = 21 * PIXEL_SZ_MICROPIXEL;
|
||||||
|
|
||||||
|
int64_t dx = player_x - n->x;
|
||||||
|
int64_t dy = player_y - n->y;
|
||||||
|
|
||||||
|
if (
|
||||||
|
sys_btnp(DEVICE_BUTTON_0, false) &&
|
||||||
|
dx * dx + dy * dy <= r_distcheck * r_distcheck
|
||||||
|
) {
|
||||||
|
n->face_left = dx < 0;
|
||||||
|
game_npc_trigger_dialogue(i);
|
||||||
|
*allow_input = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_npc_trigger_dialogue(sys_i32 npc_id) {
|
||||||
|
const char* dialogue;
|
||||||
|
|
||||||
|
// TODO: Make it possible for the player to have the cake
|
||||||
|
game_npc* npc = &game_npcs[npc_id];
|
||||||
|
|
||||||
|
if (
|
||||||
|
!npc->received_cake &&
|
||||||
|
game_player_collectibles.n_cake <= 0 &&
|
||||||
|
npc->no_cake_dialogue != NULL
|
||||||
|
) {
|
||||||
|
dialogue = npc->no_cake_dialogue;
|
||||||
|
} else if (npc->n_dialogues_seen < npc->n_dialogues) {
|
||||||
|
dialogue = npc->dialogues[npc->n_dialogues_seen++];
|
||||||
|
} else if (npc->n_dialogues > 0) {
|
||||||
|
dialogue = npc->dialogues[npc->n_dialogues - 1];
|
||||||
|
} else {
|
||||||
|
// nothing to do with this npc
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// give the NPC cake if possible
|
||||||
|
if (!npc->received_cake && game_player_collectibles.n_cake > 0) {
|
||||||
|
npc->received_cake = true;
|
||||||
|
game_player_collectibles.n_cake -= 1;
|
||||||
|
|
||||||
|
if (npc->inflict_id != 0) {
|
||||||
|
game_inflict_enqueue(npc->inflict_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
game_dialogue_display(dialogue, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_npcs_draw() {
|
||||||
|
for (sys_i32 i = 0; i < game_npc_next; i++) {
|
||||||
|
game_npc* n = &game_npcs[i];
|
||||||
|
|
||||||
|
if (!n->present) { continue; }
|
||||||
|
|
||||||
|
game_npc_palette palette =
|
||||||
|
game_npc_palettes[n->palette_id % GAME_NPC_N_PALETTES];
|
||||||
|
|
||||||
|
if (n->received_cake) {
|
||||||
|
sys_sprite_draw_ext(
|
||||||
|
spr_game_hud,
|
||||||
|
0,
|
||||||
|
n->x / PIXEL_SZ_MICROPIXEL - 4,
|
||||||
|
n->y / PIXEL_SZ_MICROPIXEL - 18,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
!n->face_left,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_dpal_set(14, palette.main);
|
||||||
|
sys_dpal_set(13, palette.alt0_0);
|
||||||
|
sys_dpal_set(12, palette.alt0_1);
|
||||||
|
sys_dpal_set(6, palette.alt1_0);
|
||||||
|
sys_dpal_set(7, palette.alt1_1);
|
||||||
|
sys_dpal_set(9, palette.eye_0);
|
||||||
|
sys_dpal_set(10, palette.eye_1);
|
||||||
|
sys_dpal_set(1, palette.pupil);
|
||||||
|
sys_dpal_set(8, palette.adornment);
|
||||||
|
sys_dpal_set(11, palette.shine);
|
||||||
|
|
||||||
|
// TODO: Palette
|
||||||
|
sys_sprite_draw_ext(
|
||||||
|
spr_game_npcs,
|
||||||
|
n->sprite_id,
|
||||||
|
n->x / PIXEL_SZ_MICROPIXEL - 8,
|
||||||
|
n->y / PIXEL_SZ_MICROPIXEL - 8,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
n->face_left,
|
||||||
|
n->flip
|
||||||
|
);
|
||||||
|
|
||||||
|
sys_dpal_reset();
|
||||||
|
}
|
||||||
|
}
|
34
game/game_npc.h
Normal file
34
game/game_npc.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#ifndef GAME_NPC_H
|
||||||
|
#define GAME_NPC_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
#define GAME_NPC_MAX_N_DIALOGUES 16
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
sys_i32 x;
|
||||||
|
sys_i32 y;
|
||||||
|
|
||||||
|
uint8_t sprite_id;
|
||||||
|
uint8_t palette_id;
|
||||||
|
uint8_t inflict_id;
|
||||||
|
bool face_left;
|
||||||
|
bool flip;
|
||||||
|
|
||||||
|
uint8_t n_dialogues;
|
||||||
|
const char* no_cake_dialogue;
|
||||||
|
const char* dialogues[GAME_NPC_MAX_N_DIALOGUES];
|
||||||
|
|
||||||
|
bool present;
|
||||||
|
uint8_t n_dialogues_seen;
|
||||||
|
bool received_cake;
|
||||||
|
} game_npc;
|
||||||
|
|
||||||
|
// pointer goes to data section and will be valid forever
|
||||||
|
game_npc* game_npc_create(sys_i32 x, sys_i32 y);
|
||||||
|
|
||||||
|
void game_npcs_update(bool* allow_input);
|
||||||
|
void game_npcs_draw();
|
||||||
|
|
||||||
|
#endif // GAME_NPC_H
|
21
game/game_palette.c
Normal file
21
game/game_palette.c
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#include "game_palette.h"
|
||||||
|
|
||||||
|
void game_palette_init() {
|
||||||
|
// Pico 8 palette
|
||||||
|
sys_spal_set(0x00, 0x000000ff);
|
||||||
|
sys_spal_set(0x01, 0x1d2b53ff);
|
||||||
|
sys_spal_set(0x02, 0x7e2553ff);
|
||||||
|
sys_spal_set(0x03, 0x008751ff);
|
||||||
|
sys_spal_set(0x04, 0xab5236ff);
|
||||||
|
sys_spal_set(0x05, 0x5f574fff);
|
||||||
|
sys_spal_set(0x06, 0xc2c3c7ff);
|
||||||
|
sys_spal_set(0x07, 0xfff1e8ff);
|
||||||
|
sys_spal_set(0x08, 0xff004dff);
|
||||||
|
sys_spal_set(0x09, 0xffa300ff);
|
||||||
|
sys_spal_set(0x0a, 0xffec27ff);
|
||||||
|
sys_spal_set(0x0b, 0x00e436ff);
|
||||||
|
sys_spal_set(0x0c, 0x29adffff);
|
||||||
|
sys_spal_set(0x0d, 0x83769cff);
|
||||||
|
sys_spal_set(0x0e, 0xff77a8ff);
|
||||||
|
sys_spal_set(0x0f, 0xffccaaff);
|
||||||
|
}
|
6
game/game_palette.h
Normal file
6
game/game_palette.h
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#ifndef GAME_PALETTE_H
|
||||||
|
#define GAME_PALETTE_H
|
||||||
|
|
||||||
|
void game_palette_init();
|
||||||
|
|
||||||
|
#endif // GAME_PALETTE_H
|
289
game/game_player.c
Normal file
289
game/game_player.c
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include "art/game_hud.h"
|
||||||
|
#include "art/game_player.h"
|
||||||
|
#include "art/game_tiles.h"
|
||||||
|
#include "device/device.h"
|
||||||
|
#include "map/game_map.h"
|
||||||
|
#include "game.h"
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
game_bbox game_player_bbox = {
|
||||||
|
.x_origin=0x0500,
|
||||||
|
.y_origin=0x0c00,
|
||||||
|
.x=0x9000,
|
||||||
|
.y=0x1000,
|
||||||
|
.w=0x0a00,
|
||||||
|
.h=0x0c00,
|
||||||
|
};
|
||||||
|
bool game_player_grounded = false;
|
||||||
|
sys_i32 game_player_jump_frames = 0;
|
||||||
|
sys_i32 game_player_coyote_frames = 0;
|
||||||
|
sys_i32 game_player_dx = 0;
|
||||||
|
sys_i32 game_player_dy = 0;
|
||||||
|
bool game_player_faceleft = false;
|
||||||
|
|
||||||
|
game_player_collectibles_t game_player_collectibles = {
|
||||||
|
.n_cake=0,
|
||||||
|
.n_dollars=0
|
||||||
|
};
|
||||||
|
|
||||||
|
game_player_phase_t game_player_phase = GAME_PLAYER_PHASE_CROC;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GAME_PLAYER_ANIM_STANDING,
|
||||||
|
GAME_PLAYER_ANIM_WALKING,
|
||||||
|
GAME_PLAYER_ANIM_FLOATING,
|
||||||
|
} game_player_anim;
|
||||||
|
|
||||||
|
game_player_anim game_player_anim_state = GAME_PLAYER_ANIM_STANDING;
|
||||||
|
uint8_t game_player_anim_progress = 0;
|
||||||
|
|
||||||
|
void game_player_anim_transition(game_player_anim anim);
|
||||||
|
void game_player_anim_add_progress(uint8_t amt);
|
||||||
|
|
||||||
|
void game_player_init() {
|
||||||
|
game_player_bbox.x = game_map_player_start_x;
|
||||||
|
game_player_bbox.y = game_map_player_start_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_player_update(bool* allow_input) {
|
||||||
|
sys_i32 dx_max, ddx;
|
||||||
|
switch (game_player_phase) {
|
||||||
|
case GAME_PLAYER_PHASE_CROC:
|
||||||
|
dx_max = 0x140;
|
||||||
|
ddx = 0x50;
|
||||||
|
game_player_bbox.x_origin=0x500;
|
||||||
|
game_player_bbox.w = 0xa00;
|
||||||
|
game_player_bbox.y_origin = game_player_bbox.h = 0xa00; break;
|
||||||
|
break;
|
||||||
|
case GAME_PLAYER_PHASE_CHICKEN:
|
||||||
|
dx_max = 0xd0;
|
||||||
|
ddx = 0x30;
|
||||||
|
game_player_bbox.x_origin=0x300;
|
||||||
|
game_player_bbox.w = 0x600;
|
||||||
|
game_player_bbox.y_origin = game_player_bbox.h = 0x700; break;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
game_collision collision = game_collision_move_to_contact(
|
||||||
|
&game_player_bbox,
|
||||||
|
game_player_dx,
|
||||||
|
game_player_dy
|
||||||
|
);
|
||||||
|
|
||||||
|
if (collision.collided_x) { game_player_dx = 0; }
|
||||||
|
if (collision.collided_y) { game_player_dy = 0; }
|
||||||
|
|
||||||
|
game_player_grounded = !game_collision_can_move(
|
||||||
|
game_player_bbox, 0, 1
|
||||||
|
);
|
||||||
|
|
||||||
|
{
|
||||||
|
sys_i32 fric_numerator = game_player_grounded ? 0x70 : 0x7f;
|
||||||
|
game_player_dx = (
|
||||||
|
game_player_dx * fric_numerator +
|
||||||
|
// round up
|
||||||
|
0x7f * sys_sgn_i32(game_player_dx)
|
||||||
|
) / 0x80;
|
||||||
|
if (sys_abs_i32(game_player_dx) > dx_max) {
|
||||||
|
game_player_dx = sys_sgn_i32(game_player_dx) * dx_max;
|
||||||
|
}
|
||||||
|
|
||||||
|
int wanted_dx = game_player_dx;
|
||||||
|
if (*allow_input && sys_btn(DEVICE_BUTTON_L)) { wanted_dx -= ddx; }
|
||||||
|
if (*allow_input && sys_btn(DEVICE_BUTTON_R)) { wanted_dx += ddx; }
|
||||||
|
|
||||||
|
// allow this if grounded or if it's not a change of direction
|
||||||
|
if (
|
||||||
|
/*
|
||||||
|
game_player_grounded ||
|
||||||
|
(sys_sgn_i32(wanted_dx) == sys_sgn_i32(game_player_dx))
|
||||||
|
*/
|
||||||
|
true
|
||||||
|
) {
|
||||||
|
game_player_dx = wanted_dx;
|
||||||
|
} else {
|
||||||
|
// the smallest possible amount of movement without changing sign
|
||||||
|
game_player_dx = sys_sgn_i32(game_player_dx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game_player_grounded || game_player_coyote_frames > 0) {
|
||||||
|
if (*allow_input && sys_btnp(DEVICE_BUTTON_U, false)) {
|
||||||
|
game_player_dy = -0x200;
|
||||||
|
game_player_jump_frames = 13;
|
||||||
|
game_player_grounded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game_player_dx < 0) { game_player_faceleft = true; }
|
||||||
|
if (game_player_dx > 0) { game_player_faceleft = false; }
|
||||||
|
|
||||||
|
bool wants_to_go_higher = false;
|
||||||
|
if (game_player_grounded) {
|
||||||
|
game_player_coyote_frames = 5;
|
||||||
|
if (game_player_dy > 0) { game_player_dy = 0; }
|
||||||
|
if (sys_abs_i32(game_player_dx) < 0x20) { game_player_dx = 0; }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
game_player_coyote_frames =
|
||||||
|
sys_max_i32(0, game_player_coyote_frames - 1);
|
||||||
|
wants_to_go_higher = sys_btn(DEVICE_BUTTON_U);
|
||||||
|
bool is_going_higher = wants_to_go_higher && game_player_jump_frames > 0;
|
||||||
|
if (game_player_dy >= 0 || !is_going_higher) {
|
||||||
|
game_player_dy += 48;
|
||||||
|
} else {
|
||||||
|
game_player_jump_frames -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deduce animation state
|
||||||
|
if (game_player_grounded) {
|
||||||
|
if (game_player_dx == 0) {
|
||||||
|
game_player_anim_transition(GAME_PLAYER_ANIM_STANDING);
|
||||||
|
} else {
|
||||||
|
game_player_anim_transition(GAME_PLAYER_ANIM_WALKING);
|
||||||
|
game_player_anim_add_progress(
|
||||||
|
sys_min_i32(sys_abs_i32(game_player_dx) / 0x10, 0x20)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (wants_to_go_higher) {
|
||||||
|
game_player_anim_transition(GAME_PLAYER_ANIM_FLOATING);
|
||||||
|
} else {
|
||||||
|
game_player_anim_transition(GAME_PLAYER_ANIM_STANDING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void game_player_get_center(sys_i32* x, sys_i32* y) {
|
||||||
|
*x = game_player_bbox.x;
|
||||||
|
|
||||||
|
// center on me, not on feet
|
||||||
|
*y = game_player_bbox.y - 8 * PIXEL_SZ_MICROPIXEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_player_set_camera() {
|
||||||
|
sys_i32 x = game_player_bbox.x;
|
||||||
|
sys_i32 y = game_player_bbox.y;
|
||||||
|
|
||||||
|
// TODO: Use round_down et al
|
||||||
|
sys_i32 x_pixel = x / PIXEL_SZ_MICROPIXEL;
|
||||||
|
sys_i32 y_pixel = y / PIXEL_SZ_MICROPIXEL;
|
||||||
|
|
||||||
|
sys_i32 x_topcorner = x_pixel - x_pixel % DEVICE_W;
|
||||||
|
sys_i32 y_topcorner = y_pixel - y_pixel % DEVICE_H;
|
||||||
|
|
||||||
|
sys_camera_set(x_topcorner, y_topcorner);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_player_draw() {
|
||||||
|
uint8_t game_player_image;
|
||||||
|
|
||||||
|
uint8_t base = 0;
|
||||||
|
switch (game_player_phase) {
|
||||||
|
default: case GAME_PLAYER_PHASE_CROC: base = 0; break;
|
||||||
|
case GAME_PLAYER_PHASE_CHICKEN: base = 32; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (game_player_anim_state) {
|
||||||
|
case GAME_PLAYER_ANIM_WALKING:
|
||||||
|
game_player_image = base + 2 + (game_player_anim_progress / 0x80) * 2;
|
||||||
|
break;
|
||||||
|
case GAME_PLAYER_ANIM_FLOATING:
|
||||||
|
game_player_image = base + 6;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case GAME_PLAYER_ANIM_STANDING:
|
||||||
|
game_player_image = base + 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_sprite_draw_ext(
|
||||||
|
spr_game_player,
|
||||||
|
game_player_image,
|
||||||
|
game_player_bbox.x / 0x100 - 8,
|
||||||
|
game_player_bbox.y / 0x100 - 16,
|
||||||
|
2, 2,
|
||||||
|
game_player_faceleft, false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_player_anim_transition(game_player_anim anim) {
|
||||||
|
if (game_player_anim_state == anim) { return ;}
|
||||||
|
game_player_anim_state = anim;
|
||||||
|
game_player_anim_progress = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_player_anim_add_progress(uint8_t amt) {
|
||||||
|
game_player_anim_progress = (
|
||||||
|
(uint32_t) game_player_anim_progress +
|
||||||
|
(uint32_t) amt
|
||||||
|
) & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void game_player_draw_hud_number(sys_i32 number, sys_i32 x, sys_i32 y);
|
||||||
|
void game_player_draw_hud() {
|
||||||
|
sys_i32 y = 4;
|
||||||
|
if (game_player_collectibles.n_dollars > 0) {
|
||||||
|
sys_sprite_draw_ext(
|
||||||
|
spr_game_hud,
|
||||||
|
1,
|
||||||
|
4, y,
|
||||||
|
1, 1,
|
||||||
|
false, false
|
||||||
|
);
|
||||||
|
game_player_draw_hud_number(game_player_collectibles.n_dollars, 16, y);
|
||||||
|
y += 10;
|
||||||
|
}
|
||||||
|
if (game_player_collectibles.n_cake > 0) {
|
||||||
|
sys_sprite_draw_ext(
|
||||||
|
spr_game_hud,
|
||||||
|
0,
|
||||||
|
4, y - 1,
|
||||||
|
1, 1,
|
||||||
|
false, false
|
||||||
|
);
|
||||||
|
game_player_draw_hud_number(game_player_collectibles.n_cake, 16, y);
|
||||||
|
y += 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void game_player_draw_hud_number1(sys_i32 number, sys_i32 x, sys_i32 y, uint8_t color) {
|
||||||
|
number = sys_min_i32(sys_max_i32(number, 0), 255);
|
||||||
|
|
||||||
|
char s[2];
|
||||||
|
s[1] = 0;
|
||||||
|
|
||||||
|
if (number >= 100) {
|
||||||
|
sys_i32 top_digit = number / 100;
|
||||||
|
assert(0 <= top_digit && top_digit < 10);
|
||||||
|
s[0] = '0' + top_digit;
|
||||||
|
number = number % 100;
|
||||||
|
sys_print(s, x, y, color);
|
||||||
|
x += 8;
|
||||||
|
}
|
||||||
|
if (number >= 10) {
|
||||||
|
sys_i32 top_digit = number / 10;
|
||||||
|
assert(0 <= top_digit && top_digit < 10);
|
||||||
|
s[0] = '0' + top_digit;
|
||||||
|
number = number % 10;
|
||||||
|
sys_print(s, x, y, color);
|
||||||
|
x += 8;
|
||||||
|
}
|
||||||
|
sys_i32 top_digit = number;
|
||||||
|
assert(0 <= top_digit && top_digit < 10);
|
||||||
|
s[0] = '0' + top_digit;
|
||||||
|
sys_print(s, x, y, color);
|
||||||
|
}
|
||||||
|
void game_player_draw_hud_number(sys_i32 number, sys_i32 x_start, sys_i32 y_start) {
|
||||||
|
for (sys_i32 dy = -1; dy <= 1; dy++) {
|
||||||
|
for (sys_i32 dx = -1; dx <= 1; dx++) {
|
||||||
|
game_player_draw_hud_number1(number, x_start+dx, y_start+dy, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
game_player_draw_hud_number1(number, x_start, y_start, 7);
|
||||||
|
}
|
24
game/game_player.h
Normal file
24
game/game_player.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef GAME_PLAYER_H
|
||||||
|
#define GAME_PLAYER_H
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
sys_i32 n_cake;
|
||||||
|
sys_i32 n_dollars;
|
||||||
|
} game_player_collectibles_t;
|
||||||
|
extern game_player_collectibles_t game_player_collectibles;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GAME_PLAYER_PHASE_CROC = 0,
|
||||||
|
GAME_PLAYER_PHASE_CHICKEN = 1,
|
||||||
|
} game_player_phase_t;
|
||||||
|
extern game_player_phase_t game_player_phase;
|
||||||
|
|
||||||
|
void game_player_init();
|
||||||
|
void game_player_update(bool* allow_input);
|
||||||
|
void game_player_get_center(sys_i32* x, sys_i32* y);
|
||||||
|
|
||||||
|
void game_player_set_camera();
|
||||||
|
void game_player_draw();
|
||||||
|
void game_player_draw_hud();
|
||||||
|
|
||||||
|
#endif // GAME_PLAYER_H
|
16
game/helpers.bzl
Normal file
16
game/helpers.bzl
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
load("@bazel_skylib//rules:run_binary.bzl", "run_binary")
|
||||||
|
|
||||||
|
def add_sprites(name, n):
|
||||||
|
run_binary(
|
||||||
|
name = name,
|
||||||
|
args = [
|
||||||
|
name,
|
||||||
|
"{}".format(n), # n sprites
|
||||||
|
"0", # key color
|
||||||
|
"$(location :art/{}.png)".format(name),
|
||||||
|
"$(location :art/{}.c)".format(name)
|
||||||
|
],
|
||||||
|
srcs = [":art/{}.png".format(name)],
|
||||||
|
outs = [":art/{}.c".format(name)],
|
||||||
|
tool = "//pytools:spritesheet",
|
||||||
|
)
|
13
game/map/game_map.h
Normal file
13
game/map/game_map.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#ifndef CROCPARTY_GAME_MAP_H
|
||||||
|
#define CROCPARTY_GAME_MAP_H
|
||||||
|
|
||||||
|
#include "game/game_collision.h"
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
extern sys_i32 game_map_player_start_x;
|
||||||
|
extern sys_i32 game_map_player_start_y;
|
||||||
|
extern sys_map map_game_map;
|
||||||
|
|
||||||
|
void map_game_map_create_entities();
|
||||||
|
#endif
|
||||||
|
|
7120
game/map/game_map.ldtk
Normal file
7120
game/map/game_map.ldtk
Normal file
File diff suppressed because it is too large
Load Diff
86
game/map/game_map_entities.c
Normal file
86
game/map/game_map_entities.c
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include "sys/sys.h"
|
||||||
|
#include "game/game.h"
|
||||||
|
#include "game_map.h"
|
||||||
|
|
||||||
|
sys_i32 game_map_player_start_x;
|
||||||
|
sys_i32 game_map_player_start_y;
|
||||||
|
|
||||||
|
// called by map_game_map_create_entities
|
||||||
|
void map_game_map_player_spawn_create(sys_i32 x, sys_i32 y) {
|
||||||
|
// center, because the entity has top-left positioning
|
||||||
|
game_map_player_start_x = x * TILE_SZ_MICROPIXEL + PIXEL_SZ_MICROPIXEL * 8;
|
||||||
|
game_map_player_start_y = y * TILE_SZ_MICROPIXEL + PIXEL_SZ_MICROPIXEL * 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_collectible_cake_create(sys_i32 x, sys_i32 y) {
|
||||||
|
game_collectible_create(
|
||||||
|
(x + 2) * TILE_SZ_MICROPIXEL,
|
||||||
|
(y + 2) * TILE_SZ_MICROPIXEL,
|
||||||
|
GAME_COLLECTIBLE_TYPE_CAKE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_collectible_money_big_create(sys_i32 x, sys_i32 y) {
|
||||||
|
game_collectible_create(
|
||||||
|
(x + 1) * TILE_SZ_MICROPIXEL,
|
||||||
|
(y + 1) * TILE_SZ_MICROPIXEL,
|
||||||
|
GAME_COLLECTIBLE_TYPE_MONEY_BIG
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_collectible_money_small_create(sys_i32 x, sys_i32 y) {
|
||||||
|
game_collectible_create(
|
||||||
|
(x + 1) * TILE_SZ_MICROPIXEL,
|
||||||
|
(y + 1) * TILE_SZ_MICROPIXEL,
|
||||||
|
GAME_COLLECTIBLE_TYPE_MONEY_SMALL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
game_npc* game_map_npc_in_progress = NULL;
|
||||||
|
|
||||||
|
void map_game_map_npc_create(sys_i32 x, sys_i32 y) {
|
||||||
|
// center NPC
|
||||||
|
game_map_npc_in_progress = game_npc_create(
|
||||||
|
(x + 1) * TILE_SZ_MICROPIXEL,
|
||||||
|
(y + 1) * TILE_SZ_MICROPIXEL
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_npc_set_sprite_id(sys_i32 id) {
|
||||||
|
assert(game_map_npc_in_progress && "NPC has to be in progress");
|
||||||
|
game_map_npc_in_progress->sprite_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_npc_set_palette_id(sys_i32 id) {
|
||||||
|
assert(game_map_npc_in_progress && "NPC has to be in progress");
|
||||||
|
game_map_npc_in_progress->palette_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_npc_set_inflict_id(sys_i32 id) {
|
||||||
|
assert(game_map_npc_in_progress && "NPC has to be in progress");
|
||||||
|
game_map_npc_in_progress->inflict_id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_npc_set_face_left(bool face_left) {
|
||||||
|
assert(game_map_npc_in_progress && "NPC has to be in progress");
|
||||||
|
game_map_npc_in_progress->face_left = face_left;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_npc_set_flip(bool flip) {
|
||||||
|
assert(game_map_npc_in_progress && "NPC has to be in progress");
|
||||||
|
game_map_npc_in_progress->flip = flip;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_npc_set_no_cake_dialogue(const char* dialogue) {
|
||||||
|
assert(game_map_npc_in_progress && "NPC has to be in progress");
|
||||||
|
game_map_npc_in_progress->no_cake_dialogue = dialogue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map_game_map_npc_add_dialogue(const char* dialogue) {
|
||||||
|
assert(game_map_npc_in_progress && "NPC has to be in progress");
|
||||||
|
assert(game_map_npc_in_progress->n_dialogues < GAME_NPC_MAX_N_DIALOGUES &&
|
||||||
|
"NPC can't have too many dialogues");
|
||||||
|
sys_i32 ix = game_map_npc_in_progress->n_dialogues++;
|
||||||
|
game_map_npc_in_progress->dialogues[ix] = dialogue;
|
||||||
|
}
|
@@ -2,9 +2,30 @@ load("@pytools_deps//:requirements.bzl", "requirement")
|
|||||||
|
|
||||||
py_binary(
|
py_binary(
|
||||||
name = "font",
|
name = "font",
|
||||||
srcs = ["font.py"],
|
srcs = ["font.py", "shared.py"],
|
||||||
deps = [
|
deps = [
|
||||||
requirement("pillow"),
|
requirement("pillow"),
|
||||||
|
requirement("Jinja2"),
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
py_binary(
|
||||||
|
name = "spritesheet",
|
||||||
|
srcs = ["spritesheet.py", "shared.py"],
|
||||||
|
deps = [
|
||||||
|
requirement("pillow"),
|
||||||
|
requirement("Jinja2"),
|
||||||
|
],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
py_binary(
|
||||||
|
name = "mapdata",
|
||||||
|
srcs = ["mapdata.py", "shared.py"],
|
||||||
|
deps = [
|
||||||
|
requirement("pillow"),
|
||||||
|
requirement("Jinja2"),
|
||||||
],
|
],
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
)
|
)
|
@@ -1,6 +1,13 @@
|
|||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import sys
|
import sys
|
||||||
|
import shared
|
||||||
|
|
||||||
|
TEMPLATE = """
|
||||||
|
// generated code! be nice!
|
||||||
|
#include "sys/sys.h"
|
||||||
|
sys_glyph font_{{ font_name }}[{{ n_glyphs }}] = { {{- glyphs|join(", ") -}} };
|
||||||
|
""".lstrip()
|
||||||
|
|
||||||
|
|
||||||
def main(font_name, n_glyphs, fname_png, fname_c):
|
def main(font_name, n_glyphs, fname_png, fname_c):
|
||||||
@@ -8,37 +15,31 @@ def main(font_name, n_glyphs, fname_png, fname_c):
|
|||||||
assert(len(glyphs) == n_glyphs), f"must be exactly {n_glyphs} glyphs"
|
assert(len(glyphs) == n_glyphs), f"must be exactly {n_glyphs} glyphs"
|
||||||
|
|
||||||
with open(fname_c, "wt") as output:
|
with open(fname_c, "wt") as output:
|
||||||
output.writelines([
|
output.write(
|
||||||
"// generated code! be nice!\n",
|
shared.templates.from_string(TEMPLATE).render(
|
||||||
"#include \"sys/sys.h\"\n",
|
font_name=font_name,
|
||||||
f"sys_glyph {font_name}[{n_glyphs}] = {{{', '.join(str(g) for g in glyphs)}}};\n"
|
n_glyphs=n_glyphs,
|
||||||
])
|
glyphs=glyphs,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def load_glyphs(fname_png: str):
|
def load_glyphs(fname_png: str):
|
||||||
with Image.open(fname_png) as im:
|
width, height, data = shared.load_image(fname_png)
|
||||||
width = im.width
|
|
||||||
height = im.height
|
|
||||||
|
|
||||||
assert width % 8 == 0, "width must be a multiple of 8"
|
glyphs = []
|
||||||
assert height % 8 == 0, "height must be a multiple of 8"
|
for gy in range(0, height, 8):
|
||||||
|
for gx in range(0, width, 8):
|
||||||
|
glyph = 0
|
||||||
|
for py in range(0, 8):
|
||||||
|
for px in range(0, 8):
|
||||||
|
x = gx + px
|
||||||
|
y = gy + py
|
||||||
|
if pixel_to_monochrome(data[y * width + x]):
|
||||||
|
glyph |= 1 << (py * 8 + px)
|
||||||
|
glyphs.append(glyph)
|
||||||
|
|
||||||
data = list(im.convert("RGBA").getdata())
|
return glyphs
|
||||||
monochrome = [pixel_to_monochrome(p) for p in data]
|
|
||||||
|
|
||||||
glyphs = []
|
|
||||||
for gy in range(0, height, 8):
|
|
||||||
for gx in range(0, width, 8):
|
|
||||||
glyph = 0
|
|
||||||
for py in range(0, 8):
|
|
||||||
for px in range(0, 8):
|
|
||||||
x = gx + px
|
|
||||||
y = gy + py
|
|
||||||
if monochrome[y * width + x]:
|
|
||||||
glyph |= 1 << (py * 8 + px)
|
|
||||||
glyphs.append(glyph)
|
|
||||||
|
|
||||||
return glyphs
|
|
||||||
|
|
||||||
|
|
||||||
def pixel_to_monochrome(rgba: Tuple[int, int, int, int]):
|
def pixel_to_monochrome(rgba: Tuple[int, int, int, int]):
|
||||||
|
162
pytools/mapdata.py
Normal file
162
pytools/mapdata.py
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import shared
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
TEMPLATE = """
|
||||||
|
// generated code! be nice
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "sys/sys.h"
|
||||||
|
|
||||||
|
sys_maptile map_{{map_name}}_data[{{width * height}}] = { {{ tiles|join(",") }} };
|
||||||
|
sys_map map_{{map_name}} = {
|
||||||
|
.tiles=map_{{map_name}}_data,
|
||||||
|
.width={{width}},
|
||||||
|
.height={{height}},
|
||||||
|
};
|
||||||
|
|
||||||
|
{% for entity_type in entity_types %}
|
||||||
|
void map_{{map_name}}_{{entity_type}}_create(sys_i32 x, sys_i32 y);
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for entity_name, field_name, c_type, render_mode in entity_fields %}
|
||||||
|
{% if render_mode == "scalar" %}
|
||||||
|
void map_{{map_name}}_{{entity_name}}_set_{{field_name}}({{c_type}} value);
|
||||||
|
{% elif render_mode == "array" %}
|
||||||
|
void map_{{map_name}}_{{entity_name}}_add_{{field_name}}({{c_type}} value);
|
||||||
|
{% else %}
|
||||||
|
wtf; // {{ render_mode }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
void map_{{map_name}}_create_entities() {
|
||||||
|
{% for entity in entities %}
|
||||||
|
map_{{map_name}}_{{entity.type}}_create({{entity.x}}, {{entity.y}});
|
||||||
|
|
||||||
|
{% for field in entity.fields %}
|
||||||
|
{% if field.renderMode == "scalar" %}
|
||||||
|
map_{{map_name}}_{{entity.type}}_set_{{field.name}}({{field.value|safe}});
|
||||||
|
{% elif field.renderMode == "array" %}
|
||||||
|
{% for s in field.value %}
|
||||||
|
map_{{map_name}}_{{entity.type}}_add_{{field.name}}({{s|safe}});
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
}
|
||||||
|
""".lstrip()
|
||||||
|
|
||||||
|
def main(map_name, fname_ldtk, fname_c):
|
||||||
|
width, height, tiles, entities, entity_types, entity_fields = load_mapdata(fname_ldtk)
|
||||||
|
print(entity_fields)
|
||||||
|
|
||||||
|
with open(fname_c, "wt") as output:
|
||||||
|
output.write(
|
||||||
|
shared.templates.from_string(TEMPLATE).render(
|
||||||
|
map_name=map_name,
|
||||||
|
tiles=tiles,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
entities=entities,
|
||||||
|
entity_types=entity_types,
|
||||||
|
entity_fields=entity_fields,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_mapdata(fname_ldtk):
|
||||||
|
sparse_tiles = {}
|
||||||
|
entities = []
|
||||||
|
entity_types = set()
|
||||||
|
entity_fields = set()
|
||||||
|
|
||||||
|
with open(fname_ldtk, "rt") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
for level in data["levels"]:
|
||||||
|
level_x = level["worldX"] // 8
|
||||||
|
level_y = level["worldY"] // 8
|
||||||
|
for layer in level["layerInstances"]:
|
||||||
|
if layer["__identifier"] == "conceptual":
|
||||||
|
# for right now I use the vague layer to assign tiles
|
||||||
|
for tile in layer["autoLayerTiles"]:
|
||||||
|
x, y = tile["px"]
|
||||||
|
x //= 8
|
||||||
|
y //= 8
|
||||||
|
ix = tile["t"]
|
||||||
|
sparse_tiles[level_x + x, level_y + y] = ix
|
||||||
|
|
||||||
|
if layer["__identifier"] == "entities":
|
||||||
|
for e in layer["entityInstances"]:
|
||||||
|
# TODO: Other fields?
|
||||||
|
entity = {
|
||||||
|
"type": e["__identifier"],
|
||||||
|
"x": e["__worldX"] // 8,
|
||||||
|
"y": e["__worldY"] // 8,
|
||||||
|
"fields": []
|
||||||
|
}
|
||||||
|
for f in e["fieldInstances"]:
|
||||||
|
field = {
|
||||||
|
"name": f["__identifier"],
|
||||||
|
}
|
||||||
|
field["value"], field["renderMode"], rendered_type = format_field_value(f["__type"], f["__value"])
|
||||||
|
entity["fields"].append(field)
|
||||||
|
entity_fields.add((
|
||||||
|
entity["type"],
|
||||||
|
field["name"],
|
||||||
|
rendered_type,
|
||||||
|
field["renderMode"]
|
||||||
|
))
|
||||||
|
|
||||||
|
entities.append(entity)
|
||||||
|
entity_types.add(entity["type"])
|
||||||
|
|
||||||
|
x_min = 0
|
||||||
|
y_min = 0
|
||||||
|
assert not any(x for (x, _) in sparse_tiles if x < x_min), "level can't be left of (0, 0)"
|
||||||
|
assert not any(y for (_, y) in sparse_tiles if y < y_min), "level can't be up from (0, 0)"
|
||||||
|
width = max(x for (x, _) in sparse_tiles) + 1
|
||||||
|
height = max(y for (_, y) in sparse_tiles) + 1
|
||||||
|
dense_tiles = []
|
||||||
|
for y in range(height):
|
||||||
|
for x in range(width):
|
||||||
|
k = (x, y)
|
||||||
|
if k in sparse_tiles:
|
||||||
|
dense_tiles.append(sparse_tiles[k])
|
||||||
|
else:
|
||||||
|
dense_tiles.append(255)
|
||||||
|
|
||||||
|
return width, height, dense_tiles, entities, entity_types, entity_fields
|
||||||
|
|
||||||
|
|
||||||
|
def format_field_value(ty, val):
|
||||||
|
if ty == "Bool":
|
||||||
|
return ("true" if val else "false"), "scalar", "bool"
|
||||||
|
elif ty == "Int":
|
||||||
|
return str(val), "scalar", "sys_i32"
|
||||||
|
elif ty == "String":
|
||||||
|
if val is None:
|
||||||
|
return "NULL", "scalar", "const char*"
|
||||||
|
return json.dumps(wrap(val)), "scalar", "const char*" # this is close enough to being right in C
|
||||||
|
elif ty == "Array<Int>":
|
||||||
|
return [format_field_value("Int", i)[0] for i in val], "array", "sys_i32"
|
||||||
|
elif ty == "Array<String>":
|
||||||
|
return [format_field_value("String", i)[0] for i in val], "array", "const char*"
|
||||||
|
else:
|
||||||
|
assert False, f"unknown type: {ty}"
|
||||||
|
|
||||||
|
|
||||||
|
def wrap(s):
|
||||||
|
return textwrap.fill(s, width=28)
|
||||||
|
|
||||||
|
def annot_xy(lst, w, h):
|
||||||
|
assert len(lst) == w * h
|
||||||
|
for y in range(h):
|
||||||
|
for x in range(w):
|
||||||
|
yield x, y, lst[y * w + x]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
assert len(sys.argv) == 4, \
|
||||||
|
"there must be three args (map name, src ldtk, out c)"
|
||||||
|
main(sys.argv[1], sys.argv[2], sys.argv[3])
|
4
pytools/requirements.lock
Normal file
4
pytools/requirements.lock
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
pillow==10.2.0
|
||||||
|
Jinja2==3.1.3
|
||||||
|
## The following requirements were added by pip freeze:
|
||||||
|
MarkupSafe==2.1.5
|
@@ -1 +1,2 @@
|
|||||||
pillow==10.2.0
|
pillow==10.2.0
|
||||||
|
Jinja2==3.1.3
|
17
pytools/shared.py
Normal file
17
pytools/shared.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from PIL import Image
|
||||||
|
from jinja2 import Environment, BaseLoader, select_autoescape
|
||||||
|
|
||||||
|
templates = Environment(
|
||||||
|
loader=BaseLoader(),
|
||||||
|
autoescape=select_autoescape(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def load_image(fname):
|
||||||
|
with Image.open(fname) as im:
|
||||||
|
width = im.width
|
||||||
|
height = im.height
|
||||||
|
|
||||||
|
assert width % 8 == 0, "width must be a multiple of 8"
|
||||||
|
assert height % 8 == 0, "height must be a multiple of 8"
|
||||||
|
|
||||||
|
return width, height, list(im.convert("RGBA").getdata())
|
102
pytools/spritesheet.py
Normal file
102
pytools/spritesheet.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
from typing import Tuple
|
||||||
|
from PIL import Image
|
||||||
|
import sys
|
||||||
|
import shared
|
||||||
|
|
||||||
|
TEMPLATE = """
|
||||||
|
// generated code! be nice!
|
||||||
|
#include "sys/sys.h"
|
||||||
|
sys_sprite spr_{{spritesheet_name}}_data[{{n_sprites}}] = {
|
||||||
|
{% for sprite in sprites -%}
|
||||||
|
{ .pixels={
|
||||||
|
{% for row in sprite -%}
|
||||||
|
{ {{ row|join(",") }} }{% if not loop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
} }{% if not loop.last %},{% endif %}
|
||||||
|
{%- endfor %}
|
||||||
|
};
|
||||||
|
sys_spritesheet spr_{{spritesheet_name}} = {
|
||||||
|
.sprites=spr_{{spritesheet_name}}_data,
|
||||||
|
.width={{width}},
|
||||||
|
.height={{height}},
|
||||||
|
};
|
||||||
|
""".lstrip()
|
||||||
|
|
||||||
|
|
||||||
|
def main(spritesheet_name, n_sprites, key_color, fname_png, fname_c):
|
||||||
|
sprites, width, height = load_sprites(fname_png, key_color)
|
||||||
|
assert(len(sprites) == n_sprites), f"must be exactly {n_sprites} sprites, not {len(sprites)}"
|
||||||
|
|
||||||
|
with open(fname_c, "wt") as output:
|
||||||
|
output.write(
|
||||||
|
shared.templates.from_string(TEMPLATE).render(
|
||||||
|
spritesheet_name=spritesheet_name,
|
||||||
|
n_sprites=n_sprites,
|
||||||
|
sprites=sprites,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_sprites(fname_png: str, key_color: int):
|
||||||
|
width, height, data = shared.load_image(fname_png)
|
||||||
|
|
||||||
|
sprites = []
|
||||||
|
for gy in range(0, height, 8):
|
||||||
|
for gx in range(0, width, 8):
|
||||||
|
pixels = []
|
||||||
|
for py in range(0, 8):
|
||||||
|
row = []
|
||||||
|
for px in range(0, 8):
|
||||||
|
x = gx + px
|
||||||
|
y = gy + py
|
||||||
|
pal = pixel_to_palette(data[y * width + x], key_color, x, y)
|
||||||
|
row.append(pal)
|
||||||
|
pixels.append(row)
|
||||||
|
sprites.append(pixels)
|
||||||
|
|
||||||
|
return sprites, width//8, height//8
|
||||||
|
|
||||||
|
|
||||||
|
palette_colors = {
|
||||||
|
(0, 0, 0): 0,
|
||||||
|
(29, 43, 83): 1,
|
||||||
|
(126, 37, 83): 2,
|
||||||
|
(0, 135, 81): 3,
|
||||||
|
(171, 82, 54): 4,
|
||||||
|
(95, 87, 79): 5,
|
||||||
|
(194, 195, 199): 6,
|
||||||
|
(255, 241, 232): 7,
|
||||||
|
(255, 0, 77): 8,
|
||||||
|
(255, 163, 0): 9,
|
||||||
|
(255, 236, 39): 10,
|
||||||
|
(0, 228, 54): 11,
|
||||||
|
(41, 173, 255): 12,
|
||||||
|
(131, 118, 156): 13,
|
||||||
|
(255, 119, 168): 14,
|
||||||
|
(255, 204, 170): 15,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def pixel_to_palette(rgba, key_color, x, y):
|
||||||
|
(r, g, b, a) = rgba
|
||||||
|
|
||||||
|
if a < 128:
|
||||||
|
return 255
|
||||||
|
|
||||||
|
rgb = (r, g, b)
|
||||||
|
ix = palette_colors.get(rgb)
|
||||||
|
if ix is None:
|
||||||
|
raise ValueError(f"unrecognized color at ({x}, {y}): {rgb}")
|
||||||
|
|
||||||
|
if ix == key_color:
|
||||||
|
return 255
|
||||||
|
|
||||||
|
return ix
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
assert len(sys.argv) == 6, \
|
||||||
|
"there must be five args (spritesheet name, n sprites, key color, src png, out c)"
|
||||||
|
main(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), sys.argv[4], sys.argv[5])
|
@@ -65,16 +65,16 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
sdl_host_loop();
|
sdl_host_loop();
|
||||||
|
|
||||||
// renderer_cleanup:
|
|
||||||
SDL_DestroyRenderer(sdl_host_renderer);
|
|
||||||
sdl_host_renderer = NULL;
|
|
||||||
renderer_done: ;
|
|
||||||
|
|
||||||
// target_cleanup:
|
// target_cleanup:
|
||||||
SDL_DestroyTexture(sdl_host_target);
|
SDL_DestroyTexture(sdl_host_target);
|
||||||
sdl_host_target = NULL;
|
sdl_host_target = NULL;
|
||||||
target_done: ;
|
target_done: ;
|
||||||
|
|
||||||
|
// renderer_cleanup:
|
||||||
|
SDL_DestroyRenderer(sdl_host_renderer);
|
||||||
|
sdl_host_renderer = NULL;
|
||||||
|
renderer_done: ;
|
||||||
|
|
||||||
// window_cleanup:
|
// window_cleanup:
|
||||||
SDL_DestroyWindow(sdl_host_window);
|
SDL_DestroyWindow(sdl_host_window);
|
||||||
sdl_host_window = NULL;
|
sdl_host_window = NULL;
|
||||||
@@ -95,7 +95,7 @@ void sdl_host_suggest_dimensions(uint32_t* window_w, uint32_t* window_h) {
|
|||||||
dm.h = 0;
|
dm.h = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int scalar = 2; scalar >= 1; scalar--) {
|
for (int scalar = 4; scalar >= 1; scalar--) {
|
||||||
uint32_t w = DEVICE_W * scalar;
|
uint32_t w = DEVICE_W * scalar;
|
||||||
uint32_t h = DEVICE_H * scalar;
|
uint32_t h = DEVICE_H * scalar;
|
||||||
if (w <= dm.w && h <= dm.h) {
|
if (w <= dm.w && h <= dm.h) {
|
||||||
|
@@ -3,6 +3,6 @@
|
|||||||
|
|
||||||
#include "sys/sys.h"
|
#include "sys/sys.h"
|
||||||
|
|
||||||
extern sys_glyph sys_font_small[256];
|
extern sys_glyph font_sys_font_small[256];
|
||||||
|
|
||||||
#endif // CROCPARTY_SYS_FONT_SMALL_H
|
#endif // CROCPARTY_SYS_FONT_SMALL_H
|
@@ -15,6 +15,10 @@ void sys_init() {
|
|||||||
sys_dpal_reset();
|
sys_dpal_reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sys_update() {
|
||||||
|
sys_input_update();
|
||||||
|
}
|
||||||
|
|
||||||
bool sys_get_initialized() {
|
bool sys_get_initialized() {
|
||||||
return sys_initialized;
|
return sys_initialized;
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,11 @@
|
|||||||
*/
|
*/
|
||||||
void sys_init();
|
void sys_init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update sys -- usually for input state.
|
||||||
|
*/
|
||||||
|
void sys_update();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether sys was initialized.
|
* Return whether sys was initialized.
|
||||||
*/
|
*/
|
||||||
|
@@ -3,10 +3,29 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define SYS_COLOR_TRANSPARENT 255
|
||||||
|
#define SYS_COLOR_N 256
|
||||||
|
#define SYS_SPRITE_H 8
|
||||||
|
#define SYS_SPRITE_W 8
|
||||||
|
|
||||||
typedef int32_t sys_i32;
|
typedef int32_t sys_i32;
|
||||||
typedef uint8_t sys_color;
|
typedef uint8_t sys_color;
|
||||||
|
typedef uint8_t sys_maptile;
|
||||||
typedef uint32_t sys_screen_color;
|
typedef uint32_t sys_screen_color;
|
||||||
typedef uint64_t sys_glyph;
|
typedef uint64_t sys_glyph;
|
||||||
|
typedef struct {
|
||||||
|
sys_color pixels[SYS_SPRITE_H][SYS_SPRITE_W];
|
||||||
|
} sys_sprite;
|
||||||
|
typedef struct {
|
||||||
|
sys_sprite* sprites;
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
} sys_spritesheet;
|
||||||
|
typedef struct {
|
||||||
|
sys_maptile* tiles;
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
} sys_map;
|
||||||
|
|
||||||
sys_screen_color sys_make_screen_color(uint8_t r, uint8_t g, uint8_t b);
|
sys_screen_color sys_make_screen_color(uint8_t r, uint8_t g, uint8_t b);
|
||||||
|
|
||||||
@@ -15,7 +34,4 @@ sys_i32 sys_min_i32(sys_i32 x, sys_i32 y);
|
|||||||
sys_i32 sys_abs_i32(sys_i32 x);
|
sys_i32 sys_abs_i32(sys_i32 x);
|
||||||
sys_i32 sys_sgn_i32(sys_i32 x);
|
sys_i32 sys_sgn_i32(sys_i32 x);
|
||||||
|
|
||||||
#define SYS_COLOR_TRANSPARENT 255
|
#endif // CROCPARTY_SYS_DATA_H
|
||||||
#define SYS_COLOR_N 256
|
|
||||||
|
|
||||||
#endif // CROCPARTY_SYS_DATA_H
|
|
||||||
|
@@ -11,7 +11,8 @@ void sys_pixel_internal_set(sys_i32 x, sys_i32 y, sys_color c);
|
|||||||
void sys_scanline_internal_set(
|
void sys_scanline_internal_set(
|
||||||
sys_i32 x0, sys_i32 x1, sys_i32 y, sys_color c, bool fill
|
sys_i32 x0, sys_i32 x1, sys_i32 y, sys_color c, bool fill
|
||||||
);
|
);
|
||||||
void sys_glyph_draw(sys_i32 x, sys_i32 y, sys_glyph g, sys_color c);
|
void sys_glyph_internal_draw(sys_i32 x, sys_i32 y, sys_glyph g, sys_color c);
|
||||||
|
void sys_sprite_internal_draw(sys_i32 x, sys_i32 y, sys_sprite s, bool flip_x, bool flip_y);
|
||||||
|
|
||||||
// == public ==
|
// == public ==
|
||||||
void sys_clip_set(sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1) {
|
void sys_clip_set(sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1) {
|
||||||
@@ -56,14 +57,36 @@ sys_color sys_pixel_get(sys_i32 x, sys_i32 y) {
|
|||||||
return device_pixels[y][x];
|
return device_pixels[y][x];
|
||||||
}
|
}
|
||||||
|
|
||||||
void sys_print(char* str, sys_i32 x, sys_i32 y, sys_color col) {
|
void sys_measure_text(const char* str, sys_i32* w, sys_i32* h) {
|
||||||
|
sys_i32 x = 0;
|
||||||
|
sys_i32 y = 0;
|
||||||
|
|
||||||
|
sys_i32 max_x = 0;
|
||||||
|
|
||||||
|
for (sys_i32 i = 0;; i++) {
|
||||||
|
max_x = sys_max_i32(x, max_x);
|
||||||
|
uint8_t c = str[i];
|
||||||
|
if (c == 0) { break; }
|
||||||
|
if (c == '\n') { x = 0; y += 8; continue; }
|
||||||
|
if (c == '\r') { x = 0; continue; }
|
||||||
|
x += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
*w = max_x;
|
||||||
|
*h = y + 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_print(const char* str, sys_i32 x, sys_i32 y, sys_color col) {
|
||||||
|
x += sys_cam_dx;
|
||||||
|
y += sys_cam_dy;
|
||||||
|
|
||||||
sys_i32 x_orig = x;
|
sys_i32 x_orig = x;
|
||||||
for (sys_i32 i = 0;; i++) {
|
for (sys_i32 i = 0;; i++) {
|
||||||
uint8_t c = str[i];
|
uint8_t c = str[i];
|
||||||
if (c == 0) { break; }
|
if (c == 0) { break; }
|
||||||
if (c == '\n') { x = x_orig; y += 8; continue; }
|
if (c == '\n') { x = x_orig; y += 8; continue; }
|
||||||
if (c == '\r') { x = x_orig; continue; }
|
if (c == '\r') { x = x_orig; continue; }
|
||||||
sys_glyph_draw(x, y, sys_font_small[c], col);
|
sys_glyph_internal_draw(x, y, font_sys_font_small[c], col);
|
||||||
x += 8;
|
x += 8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,11 +157,17 @@ void sys_oval_fill(
|
|||||||
void sys_oval_draw_ext(
|
void sys_oval_draw_ext(
|
||||||
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c, bool fill
|
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c, bool fill
|
||||||
) {
|
) {
|
||||||
|
x0 += sys_cam_dx;
|
||||||
|
y0 += sys_cam_dy;
|
||||||
|
x1 += sys_cam_dx;
|
||||||
|
y1 += sys_cam_dy;
|
||||||
|
|
||||||
assert(sys_get_initialized());
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
if (x0 == x1 || y0 == y1) { return; }
|
if (x0 == x1 || y0 == y1) { return; }
|
||||||
if (x0 > x1) { sys_i32 tmp = x0; x0 = x1; x1 = tmp; }
|
if (x0 > x1) { sys_i32 tmp = x0; x0 = x1; x1 = tmp; }
|
||||||
if (y0 > y1) { sys_i32 tmp = y0; y0 = y1; y1 = tmp; }
|
if (y0 > y1) { sys_i32 tmp = y0; y0 = y1; y1 = tmp; }
|
||||||
|
// TODO: Offset by 1px so that x0/y0 is always included?
|
||||||
|
|
||||||
// alois' algorithm for this implies the bounds are inclusive
|
// alois' algorithm for this implies the bounds are inclusive
|
||||||
x1 -= 1; y1 -= 1;
|
x1 -= 1; y1 -= 1;
|
||||||
@@ -157,11 +186,9 @@ void sys_oval_draw_ext(
|
|||||||
int64_t bb8 = 8 * b * b;
|
int64_t bb8 = 8 * b * b;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if (!fill) {
|
// draw the points at the edge of the line no matter what
|
||||||
// draw the points at the edge of the line
|
sys_scanline_internal_set(x0, x1, y0, c, false);
|
||||||
sys_scanline_internal_set(x0, x1, y0, c, false);
|
sys_scanline_internal_set(x0, x1, y1, c, false);
|
||||||
sys_scanline_internal_set(x0, x1, y1, c, false);
|
|
||||||
}
|
|
||||||
int64_t e2 = 2 * err;
|
int64_t e2 = 2 * err;
|
||||||
if (e2 <= dy) {
|
if (e2 <= dy) {
|
||||||
if (fill) {
|
if (fill) {
|
||||||
@@ -185,6 +212,11 @@ void sys_oval_draw_ext(
|
|||||||
void sys_line_draw(
|
void sys_line_draw(
|
||||||
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c
|
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c
|
||||||
) {
|
) {
|
||||||
|
x0 += sys_cam_dx;
|
||||||
|
y0 += sys_cam_dy;
|
||||||
|
x1 += sys_cam_dx;
|
||||||
|
y1 += sys_cam_dy;
|
||||||
|
|
||||||
assert(sys_get_initialized());
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
if (x0 == x1 || y0 == y1) { return; }
|
if (x0 == x1 || y0 == y1) { return; }
|
||||||
@@ -204,6 +236,43 @@ void sys_line_draw(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sys_rect_draw(
|
||||||
|
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c
|
||||||
|
) {
|
||||||
|
sys_rect_draw_ext(x0, y0, x1, y1, c, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_rect_fill(
|
||||||
|
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c
|
||||||
|
) {
|
||||||
|
sys_rect_draw_ext(x0, y0, x1, y1, c, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_rect_draw_ext(
|
||||||
|
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c, bool fill
|
||||||
|
) {
|
||||||
|
x0 += sys_cam_dx;
|
||||||
|
y0 += sys_cam_dy;
|
||||||
|
x1 += sys_cam_dx;
|
||||||
|
y1 += sys_cam_dy;
|
||||||
|
|
||||||
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
|
if (x0 == x1 || y0 == y1) { return; }
|
||||||
|
|
||||||
|
if (x0 == x1 || y0 == y1) { return; }
|
||||||
|
if (x0 > x1) { sys_i32 tmp = x0; x0 = x1; x1 = tmp; }
|
||||||
|
if (y0 > y1) { sys_i32 tmp = y0; y0 = y1; y1 = tmp; }
|
||||||
|
// TODO: Offset by 1px so that x0/y0 is always included?
|
||||||
|
|
||||||
|
for (sys_i32 y = y0; y < y1; y++) {
|
||||||
|
bool whole_line = y == y0 || y == y1;
|
||||||
|
sys_scanline_internal_set(
|
||||||
|
x0, x1, y, c, whole_line || fill
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void sys_spal_set(sys_color c0, sys_screen_color rc1) {
|
void sys_spal_set(sys_color c0, sys_screen_color rc1) {
|
||||||
assert(sys_get_initialized());
|
assert(sys_get_initialized());
|
||||||
|
|
||||||
@@ -232,6 +301,93 @@ void sys_dpal_reset() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sys_sprite_draw(
|
||||||
|
sys_spritesheet spritesheet,
|
||||||
|
sys_i32 n,
|
||||||
|
sys_i32 x, sys_i32 y
|
||||||
|
) {
|
||||||
|
sys_sprite_draw_ext(
|
||||||
|
spritesheet,
|
||||||
|
n,
|
||||||
|
x, y,
|
||||||
|
1, 1,
|
||||||
|
false, false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_sprite_draw_ext(
|
||||||
|
sys_spritesheet spritesheet,
|
||||||
|
sys_i32 n,
|
||||||
|
sys_i32 x, sys_i32 y,
|
||||||
|
sys_i32 w, sys_i32 h,
|
||||||
|
bool flip_x, bool flip_y
|
||||||
|
) {
|
||||||
|
x += sys_cam_dx;
|
||||||
|
y += sys_cam_dy;
|
||||||
|
|
||||||
|
// map n to a specific entity on the spritesheet
|
||||||
|
// (this is necessary for w and h)
|
||||||
|
for (int sy = 0; sy < h; sy++) {
|
||||||
|
for (int sx = 0; sx < w; sx++) {
|
||||||
|
sys_i32 sx_adj = flip_x ? w - 1 - sx : sx;
|
||||||
|
sys_i32 sy_adj = flip_y ? h - 1 - sy : sy;
|
||||||
|
sys_i32 tile =
|
||||||
|
n + sx_adj + sy_adj * spritesheet.width;
|
||||||
|
if (tile < 0 || tile >= spritesheet.width * spritesheet.height) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
sys_sprite_internal_draw(
|
||||||
|
x + sx * 8,
|
||||||
|
y + sy * 8,
|
||||||
|
spritesheet.sprites[tile],
|
||||||
|
flip_x,
|
||||||
|
flip_y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_map_draw(
|
||||||
|
sys_map map,
|
||||||
|
sys_spritesheet spritesheet,
|
||||||
|
sys_i32 sx, sys_i32 sy,
|
||||||
|
sys_i32 tile_x, sys_i32 tile_y,
|
||||||
|
sys_i32 tile_w, sys_i32 tile_h
|
||||||
|
) {
|
||||||
|
// no need to do this, sys_sprite_draw does it
|
||||||
|
// sx += sys_cam_dx;
|
||||||
|
// sy += sys_cam_dy;
|
||||||
|
|
||||||
|
for (sys_i32 ty = 0; ty < tile_h; ty++) {
|
||||||
|
for (sys_i32 tx = 0; tx < tile_w; tx++) {
|
||||||
|
sys_i32 real_tx = tx + tile_x;
|
||||||
|
sys_i32 real_ty = ty + tile_y;
|
||||||
|
if (real_tx < 0 || real_tx >= map.width) { continue; }
|
||||||
|
if (real_ty < 0 || real_ty >= map.height) { continue; }
|
||||||
|
sys_maptile tile = map.tiles[real_tx + map.width * real_ty];
|
||||||
|
sys_sprite_draw(spritesheet, tile, sx + tx * 8, sy + ty * 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sys_maptile sys_map_get(
|
||||||
|
sys_map map,
|
||||||
|
sys_i32 tile_x, sys_i32 tile_y,
|
||||||
|
bool* flag_invalid
|
||||||
|
) {
|
||||||
|
if (flag_invalid != NULL) { *flag_invalid = false; }
|
||||||
|
|
||||||
|
if (
|
||||||
|
tile_x < 0 || tile_y < 0 ||
|
||||||
|
tile_x >= map.width || tile_y >= map.height
|
||||||
|
) {
|
||||||
|
if (flag_invalid != NULL) { *flag_invalid = true; }
|
||||||
|
return 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
return map.tiles[tile_x + map.width * tile_y];
|
||||||
|
}
|
||||||
|
|
||||||
// == internal primitives ==
|
// == internal primitives ==
|
||||||
void sys_pixel_internal_set(sys_i32 x, sys_i32 y, sys_color c) {
|
void sys_pixel_internal_set(sys_i32 x, sys_i32 y, sys_color c) {
|
||||||
sys_color realc = sys_dpal[c];
|
sys_color realc = sys_dpal[c];
|
||||||
@@ -265,7 +421,7 @@ void sys_scanline_internal_set(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sys_glyph_draw(sys_i32 x, sys_i32 y, sys_glyph g, sys_color c) {
|
void sys_glyph_internal_draw(sys_i32 x, sys_i32 y, sys_glyph g, sys_color c) {
|
||||||
// iterate through the bits of the glyph, and draw the character
|
// iterate through the bits of the glyph, and draw the character
|
||||||
// if that bit is set
|
// if that bit is set
|
||||||
for (int py = 0; py < 8; py++) {
|
for (int py = 0; py < 8; py++) {
|
||||||
@@ -276,4 +432,16 @@ void sys_glyph_draw(sys_i32 x, sys_i32 y, sys_glyph g, sys_color c) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_sprite_internal_draw(
|
||||||
|
sys_i32 x, sys_i32 y, sys_sprite s, bool flip_x, bool flip_y
|
||||||
|
) {
|
||||||
|
for (int py = 0; py < 8; py++) {
|
||||||
|
for (int px = 0; px < 8; px++) {
|
||||||
|
sys_i32 px_adj = flip_x ? 7 - px : px;
|
||||||
|
sys_i32 py_adj = flip_y ? 7 - py : py;
|
||||||
|
sys_pixel_internal_set(x + px, y + py, s.pixels[py_adj][px_adj]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@@ -54,10 +54,16 @@ sys_color sys_pixel_get(
|
|||||||
|
|
||||||
// TODO: SSET/SGET
|
// TODO: SSET/SGET
|
||||||
// TODO: FGET/FSET
|
// TODO: FGET/FSET
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output the dimensions of the text (x and y)
|
||||||
|
*/
|
||||||
|
void sys_measure_text(const char* str, sys_i32* x, sys_i32* y);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print a string `str` in the color `col`
|
* Print a string `str` in the color `col`
|
||||||
*/
|
*/
|
||||||
void sys_print(char* str, sys_i32 x, sys_i32 y, sys_color col);
|
void sys_print(const char* str, sys_i32 x, sys_i32 y, sys_color col);
|
||||||
// TODO: CURSOR? COLOR?
|
// TODO: CURSOR? COLOR?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -83,7 +89,7 @@ void sys_camera_reset();
|
|||||||
*
|
*
|
||||||
* If r is negative, the circle is not drawn.
|
* If r is negative, the circle is not drawn.
|
||||||
*
|
*
|
||||||
* This is a special case of sys_circ_oval_draw_ext.
|
* This is a special case of sys_oval_draw_ext.
|
||||||
*/
|
*/
|
||||||
void sys_circ_draw(sys_i32 x, sys_i32 y, sys_i32 r, sys_color c);
|
void sys_circ_draw(sys_i32 x, sys_i32 y, sys_i32 r, sys_color c);
|
||||||
void sys_circ_fill(sys_i32 x, sys_i32 y, sys_i32 r, sys_color c);
|
void sys_circ_fill(sys_i32 x, sys_i32 y, sys_i32 r, sys_color c);
|
||||||
@@ -146,9 +152,49 @@ void sys_dpal_set(sys_color c0, sys_color c1);
|
|||||||
*/
|
*/
|
||||||
void sys_dpal_reset();
|
void sys_dpal_reset();
|
||||||
|
|
||||||
// TODO: SPR
|
/**
|
||||||
|
* Draw a given sprite.
|
||||||
|
*
|
||||||
|
* w, h default to 1.
|
||||||
|
* flip_x and flip_y default to false.
|
||||||
|
*/
|
||||||
|
void sys_sprite_draw(
|
||||||
|
sys_spritesheet spritesheet,
|
||||||
|
sys_i32 n,
|
||||||
|
sys_i32 x, sys_i32 y
|
||||||
|
);
|
||||||
|
void sys_sprite_draw_ext(
|
||||||
|
sys_spritesheet spritesheet,
|
||||||
|
sys_i32 n,
|
||||||
|
sys_i32 x, sys_i32 y,
|
||||||
|
sys_i32 w, sys_i32 h,
|
||||||
|
bool flip_x, bool flip_y
|
||||||
|
);
|
||||||
// TODO: SSPR
|
// TODO: SSPR
|
||||||
// TODO: FILLP?
|
// TODO: FILLP?
|
||||||
|
|
||||||
|
void sys_map_draw(
|
||||||
|
// NOTE: not the same order of args
|
||||||
|
// as on pico 8
|
||||||
|
// but: more consistent!
|
||||||
|
sys_map map,
|
||||||
|
sys_spritesheet spritesheet,
|
||||||
|
sys_i32 sx, sys_i32 sy,
|
||||||
|
sys_i32 tile_x, sys_i32 tile_y,
|
||||||
|
sys_i32 tile_w, sys_i32 tile_h
|
||||||
|
// TODO: Layers?
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single map tile, putting whether the coords were invalid into
|
||||||
|
* flag_invalid.
|
||||||
|
*
|
||||||
|
* (flag_invalid may be null)
|
||||||
|
*/
|
||||||
|
sys_maptile sys_map_get(
|
||||||
|
sys_map map,
|
||||||
|
sys_i32 tile_x, sys_i32 tile_y,
|
||||||
|
bool* flag_invalid
|
||||||
|
);
|
||||||
|
|
||||||
#endif // CROCPARTY_SYS_GRAPHICS_H
|
#endif // CROCPARTY_SYS_GRAPHICS_H
|
||||||
|
37
sys/sys_input.c
Normal file
37
sys/sys_input.c
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#include "device/device.h"
|
||||||
|
#include "sys/sys.h"
|
||||||
|
#include "sys_input.h"
|
||||||
|
|
||||||
|
sys_i32 sys_input_down_frames[DEVICE_BUTTON_N];
|
||||||
|
|
||||||
|
void sys_input_update() {
|
||||||
|
for (DeviceButton db = 0; db < DEVICE_BUTTON_N; db++) {
|
||||||
|
if (device_buttons[db]) {
|
||||||
|
sys_input_down_frames[db] += 1;
|
||||||
|
} else {
|
||||||
|
sys_input_down_frames[db] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sys_input_reset() {
|
||||||
|
for (DeviceButton db = 0; db < DEVICE_BUTTON_N; db++) {
|
||||||
|
sys_input_down_frames[db] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sys_btn(DeviceButton button) {
|
||||||
|
if (button >= DEVICE_BUTTON_N) { return false; }
|
||||||
|
return sys_input_down_frames[button] >= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sys_btnp(DeviceButton button, bool repeat) {
|
||||||
|
if (button >= DEVICE_BUTTON_N) { return false; }
|
||||||
|
if (sys_input_down_frames[button] == 1) { return true; }
|
||||||
|
if (!repeat) { return false; }
|
||||||
|
// 31 and every 8 frames after that
|
||||||
|
if (sys_input_down_frames[button] >= 31 && (sys_input_down_frames[button] - 31) % 8 == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
34
sys/sys_input.h
Normal file
34
sys/sys_input.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#ifndef SYS_INPUT_H
|
||||||
|
#define SYS_INPUT_H
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the state of every button. Each button's btnp state depends
|
||||||
|
* on how long it's been down, and calling this function adds one frame.
|
||||||
|
*
|
||||||
|
* Usually this is called by sys_update(), at the start of frame.
|
||||||
|
*/
|
||||||
|
void sys_input_update();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the input state -- all buttons have been held for 0 frames.
|
||||||
|
*
|
||||||
|
* If the buttons are held next frame, this will lead to a btnp().
|
||||||
|
*
|
||||||
|
* There's rarely any reason for user code to call this.
|
||||||
|
*/
|
||||||
|
void sys_input_reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether a button is down.
|
||||||
|
*/
|
||||||
|
bool sys_btn(DeviceButton button);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether a button was just pressed.
|
||||||
|
*
|
||||||
|
* If `repeat`, then this repeats after 30 frames, returning true every 8
|
||||||
|
* frames after that.
|
||||||
|
*/
|
||||||
|
bool sys_btnp(DeviceButton button, bool repeat);
|
||||||
|
|
||||||
|
#endif // SYS_INPUT_H
|
Reference in New Issue
Block a user