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(
|
||||
hub_name = "pytools_deps",
|
||||
python_version = "3.10",
|
||||
requirements_lock = "//pytools:requirements.txt",
|
||||
requirements_lock = "//pytools:requirements.lock",
|
||||
)
|
||||
use_repo(pip, "pytools_deps")
|
62
MODULE.bazel.lock
generated
62
MODULE.bazel.lock
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"lockFileVersion": 3,
|
||||
"moduleFileHash": "f69bdb13b0df2f6d309d415437228ebef31fa559567a65ecfe60de77e328ae1d",
|
||||
"moduleFileHash": "b7a605dff62d0b97615cf1feb669dc4d3ce630abf1e74374f624b71287684b40",
|
||||
"flags": {
|
||||
"cmdRegistries": [
|
||||
"https://bcr.bazel.build/"
|
||||
@@ -72,7 +72,7 @@
|
||||
"attributeValues": {
|
||||
"hub_name": "pytools_deps",
|
||||
"python_version": "3.10",
|
||||
"requirements_lock": "//pytools:requirements.txt"
|
||||
"requirements_lock": "//pytools:requirements.lock"
|
||||
},
|
||||
"devDependency": false,
|
||||
"location": {
|
||||
@@ -1741,10 +1741,60 @@
|
||||
"os:windows,arch:amd64": {
|
||||
"bzlTransitiveDigest": "EY+DGyyJhrqMHBCiplU6kCYiNtiUXl3Olt6+vF4JnfE=",
|
||||
"accumulatedFileDigests": {
|
||||
"@@//pytools:requirements.txt": "ecb2c096486b1bb4dbe407a48fb3375ba496b6ea658c0cd7fc34571f381cd504"
|
||||
"@@//pytools:requirements.lock": "5a2eed9c6e50e9034521853f6016123e3e526900ddf3ff81cc1dbaaa28683629"
|
||||
},
|
||||
"envVariables": {},
|
||||
"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": {
|
||||
"bzlFile": "@@rules_python~0.31.0//python/pip_install:pip_repository.bzl",
|
||||
"ruleClassName": "whl_library",
|
||||
@@ -1786,6 +1836,12 @@
|
||||
"name": "rules_python~0.31.0~pip~pytools_deps",
|
||||
"repo_name": "pytools_deps",
|
||||
"whl_map": {
|
||||
"jinja2": [
|
||||
"3.10"
|
||||
],
|
||||
"markupsafe": [
|
||||
"3.10"
|
||||
],
|
||||
"pillow": [
|
||||
"3.10"
|
||||
]
|
||||
|
@@ -14,4 +14,12 @@ $ bazel run -c opt 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(
|
||||
name = "game",
|
||||
srcs = glob(["*.c"]),
|
||||
hdrs = glob(["*.h"]),
|
||||
srcs = glob(["*.c"]) + [
|
||||
"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"],
|
||||
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 "map/game_map.h"
|
||||
#include "game.h"
|
||||
#include "sys/sys.h"
|
||||
#include <stdio.h>
|
||||
|
||||
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() {
|
||||
return "Croc Party!";
|
||||
}
|
||||
@@ -21,22 +15,10 @@ const char* game_title() {
|
||||
void game_init() {
|
||||
sys_init();
|
||||
|
||||
game_frame = 0;
|
||||
map_game_map_create_entities();
|
||||
|
||||
for (uint32_t x = 0; x < 8; x++) {
|
||||
for (uint32_t y = 0; y < 8; y++) {
|
||||
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);
|
||||
game_palette_init();
|
||||
game_player_init();
|
||||
}
|
||||
|
||||
void game_destroy() {
|
||||
@@ -44,51 +26,32 @@ void game_destroy() {
|
||||
}
|
||||
|
||||
void game_update() {
|
||||
game_frame += 4;
|
||||
sys_update();
|
||||
|
||||
ellipse_x0 += ellipse_dx0;
|
||||
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; }
|
||||
game_frame += 1;
|
||||
|
||||
ellipse_x1 += ellipse_dx1;
|
||||
if (ellipse_x1 < 0 || ellipse_x1 > DEVICE_W * 4) { ellipse_dx1 *= -1; }
|
||||
ellipse_y1 += ellipse_dy1;
|
||||
if (ellipse_y1 < 0 || ellipse_y1 > DEVICE_H * 4) { ellipse_dy1 *= -1; }
|
||||
bool allow_input = true;
|
||||
|
||||
game_collectibles_update();
|
||||
game_dialogue_update(&allow_input);
|
||||
game_inflict_update(&allow_input);
|
||||
game_npcs_update(&allow_input);
|
||||
game_player_update(&allow_input);
|
||||
}
|
||||
|
||||
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++) {
|
||||
for (int y = 0; y < DEVICE_H; y++) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
game_player_set_camera();
|
||||
sys_map_draw(map_game_map, spr_game_tiles, 0, 0, 0, 0, map_game_map.width, map_game_map.height);
|
||||
|
||||
for (int i = 0; i < DEVICE_BUTTON_N; i++) {
|
||||
sys_pixel_set(i, 0, device_buttons[i] ? 0x00 : 0xff);
|
||||
}
|
||||
game_collectibles_draw();
|
||||
game_npcs_draw();
|
||||
game_player_draw();
|
||||
|
||||
int x0 = ellipse_x0 / 4;
|
||||
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_camera_reset();
|
||||
|
||||
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
|
||||
#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();
|
||||
void game_init();
|
||||
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(
|
||||
name = "font",
|
||||
srcs = ["font.py"],
|
||||
srcs = ["font.py", "shared.py"],
|
||||
deps = [
|
||||
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"],
|
||||
)
|
@@ -1,6 +1,13 @@
|
||||
from typing import Tuple
|
||||
from PIL import Image
|
||||
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):
|
||||
@@ -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"
|
||||
|
||||
with open(fname_c, "wt") as output:
|
||||
output.writelines([
|
||||
"// generated code! be nice!\n",
|
||||
"#include \"sys/sys.h\"\n",
|
||||
f"sys_glyph {font_name}[{n_glyphs}] = {{{', '.join(str(g) for g in glyphs)}}};\n"
|
||||
])
|
||||
output.write(
|
||||
shared.templates.from_string(TEMPLATE).render(
|
||||
font_name=font_name,
|
||||
n_glyphs=n_glyphs,
|
||||
glyphs=glyphs,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def load_glyphs(fname_png: str):
|
||||
with Image.open(fname_png) as im:
|
||||
width = im.width
|
||||
height = im.height
|
||||
width, height, data = shared.load_image(fname_png)
|
||||
|
||||
assert width % 8 == 0, "width must be a multiple of 8"
|
||||
assert height % 8 == 0, "height must be a multiple of 8"
|
||||
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 pixel_to_monochrome(data[y * width + x]):
|
||||
glyph |= 1 << (py * 8 + px)
|
||||
glyphs.append(glyph)
|
||||
|
||||
data = list(im.convert("RGBA").getdata())
|
||||
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
|
||||
return glyphs
|
||||
|
||||
|
||||
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();
|
||||
|
||||
// renderer_cleanup:
|
||||
SDL_DestroyRenderer(sdl_host_renderer);
|
||||
sdl_host_renderer = NULL;
|
||||
renderer_done: ;
|
||||
|
||||
// target_cleanup:
|
||||
SDL_DestroyTexture(sdl_host_target);
|
||||
sdl_host_target = NULL;
|
||||
target_done: ;
|
||||
|
||||
// renderer_cleanup:
|
||||
SDL_DestroyRenderer(sdl_host_renderer);
|
||||
sdl_host_renderer = NULL;
|
||||
renderer_done: ;
|
||||
|
||||
// window_cleanup:
|
||||
SDL_DestroyWindow(sdl_host_window);
|
||||
sdl_host_window = NULL;
|
||||
@@ -95,7 +95,7 @@ void sdl_host_suggest_dimensions(uint32_t* window_w, uint32_t* window_h) {
|
||||
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 h = DEVICE_H * scalar;
|
||||
if (w <= dm.w && h <= dm.h) {
|
||||
|
@@ -3,6 +3,6 @@
|
||||
|
||||
#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
|
@@ -15,6 +15,10 @@ void sys_init() {
|
||||
sys_dpal_reset();
|
||||
}
|
||||
|
||||
void sys_update() {
|
||||
sys_input_update();
|
||||
}
|
||||
|
||||
bool sys_get_initialized() {
|
||||
return sys_initialized;
|
||||
}
|
||||
|
@@ -10,6 +10,11 @@
|
||||
*/
|
||||
void sys_init();
|
||||
|
||||
/**
|
||||
* Update sys -- usually for input state.
|
||||
*/
|
||||
void sys_update();
|
||||
|
||||
/**
|
||||
* Return whether sys was initialized.
|
||||
*/
|
||||
|
@@ -3,10 +3,29 @@
|
||||
|
||||
#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 uint8_t sys_color;
|
||||
typedef uint8_t sys_maptile;
|
||||
typedef uint32_t sys_screen_color;
|
||||
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);
|
||||
|
||||
@@ -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_sgn_i32(sys_i32 x);
|
||||
|
||||
#define SYS_COLOR_TRANSPARENT 255
|
||||
#define SYS_COLOR_N 256
|
||||
|
||||
#endif // CROCPARTY_SYS_DATA_H
|
||||
#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(
|
||||
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 ==
|
||||
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];
|
||||
}
|
||||
|
||||
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;
|
||||
for (sys_i32 i = 0;; i++) {
|
||||
uint8_t c = str[i];
|
||||
if (c == 0) { break; }
|
||||
if (c == '\n') { x = x_orig; y += 8; 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;
|
||||
}
|
||||
}
|
||||
@@ -134,11 +157,17 @@ void sys_oval_fill(
|
||||
void sys_oval_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) { 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?
|
||||
|
||||
// alois' algorithm for this implies the bounds are inclusive
|
||||
x1 -= 1; y1 -= 1;
|
||||
@@ -157,11 +186,9 @@ void sys_oval_draw_ext(
|
||||
int64_t bb8 = 8 * b * b;
|
||||
|
||||
do {
|
||||
if (!fill) {
|
||||
// draw the points at the edge of the line
|
||||
sys_scanline_internal_set(x0, x1, y0, c, false);
|
||||
sys_scanline_internal_set(x0, x1, y1, c, false);
|
||||
}
|
||||
// draw the points at the edge of the line no matter what
|
||||
sys_scanline_internal_set(x0, x1, y0, c, false);
|
||||
sys_scanline_internal_set(x0, x1, y1, c, false);
|
||||
int64_t e2 = 2 * err;
|
||||
if (e2 <= dy) {
|
||||
if (fill) {
|
||||
@@ -185,6 +212,11 @@ void sys_oval_draw_ext(
|
||||
void sys_line_draw(
|
||||
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());
|
||||
|
||||
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) {
|
||||
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 ==
|
||||
void sys_pixel_internal_set(sys_i32 x, sys_i32 y, sys_color 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
|
||||
// if that bit is set
|
||||
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: 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`
|
||||
*/
|
||||
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?
|
||||
|
||||
/**
|
||||
@@ -83,7 +89,7 @@ void sys_camera_reset();
|
||||
*
|
||||
* 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_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();
|
||||
|
||||
// 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: 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
|
||||
|
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