Compare commits

..

40 Commits

Author SHA1 Message Date
6c25396d4b The bat can inflict chicken TF 2024-02-29 14:31:44 -08:00
0a3a8cb8ab Zone 1 npcs 2024-02-29 13:20:18 -08:00
1a89e4f3b6 Deliver cake to NPCs 2024-02-29 12:08:50 -08:00
60e1a4ed43 NPCs talk to you when you push Z 2024-02-28 21:07:17 -08:00
c7d9f16515 Speech bubbles 2024-02-28 20:48:22 -08:00
7e67d09508 Fix a missing assertion 2024-02-28 19:49:49 -08:00
f5f5e2c20b Get NPCs showing up in-engine 2024-02-28 19:42:03 -08:00
567db0bd71 NPCs 1: tools to integrate properties 2024-02-28 19:05:52 -08:00
df1d1450e7 Collectibles can be collected 2024-02-28 17:36:06 -08:00
7649a2dc7e Collectibles 1 2024-02-28 16:10:09 -08:00
d084e4dba3 Player spawn point 2024-02-28 13:14:48 -08:00
d6db2f3e5f Bounding boxes: origins 2024-02-28 12:54:59 -08:00
b0f4106d51 Tighten croc controls, different starter level 2024-02-28 12:20:12 -08:00
84b7a09383 Move_to_contact: handle allowed overlap correctly 2024-02-28 12:03:23 -08:00
c5c677320b Fix misc math bugs 2024-02-27 21:49:20 -08:00
5f522abcb5 Player controller 2024-02-27 21:07:15 -08:00
ab5c442433 Movement 1 2024-02-27 19:57:37 -08:00
26018facea Add more sprites 2024-02-27 18:26:56 -08:00
407a984300 Use Game Maker conventions for resource names 2024-02-27 16:00:57 -08:00
08f279e340 Get the croc into the engine 2024-02-27 15:57:37 -08:00
57e025e6aa Fix flipping sprites, start work on player 2024-02-27 15:35:51 -08:00
d823220e90 First real map with real tiles 2024-02-27 15:09:51 -08:00
9ef88fb34d Map works! 2024-02-26 20:19:22 -08:00
055b3dd7b1 Import LDTK maps 2024-02-26 19:51:26 -08:00
f6d942c614 Load Ultimate Lizard Total Destruction spritesheet 2024-02-26 17:31:58 -08:00
79eb52e282 Load spritesheet 2024-02-26 17:04:37 -08:00
d05e382063 Move the image loader to shared 2024-02-26 16:05:07 -08:00
72599b7d00 Use Jinja2, start to support sprites 2024-02-26 16:01:47 -08:00
c478ee23db Bugfixes/testing changes 2024-02-26 15:27:05 -08:00
cb44cab4f8 Add font loader 2024-02-26 12:42:07 -08:00
e05f050e7d Tidy up Alois' confusing code 2024-02-25 23:17:05 -08:00
ff44a3436b Minor ellipse performance improvement 2024-02-25 23:15:10 -08:00
3440669227 Add lines 2024-02-25 22:23:43 -08:00
be9c443c58 Ellipses! 2024-02-25 22:10:21 -08:00
30e4d544fd Support significant parts of pico 8 graphics 2024-02-25 18:38:53 -08:00
95c03d25d5 Groundwork for sys, palette 2024-02-25 17:54:57 -08:00
56eaee9d08 Move device to a separate build target 2024-02-25 16:40:33 -08:00
4866226840 Keyboard input 2024-02-25 16:25:34 -08:00
3f837d7052 MORE BAZEL. VS Code only, by the way 2024-02-25 13:49:21 -08:00
e2d648b5fa Add bazel run configurations and stuff 2024-02-24 21:09:44 -08:00
85 changed files with 11492 additions and 363 deletions

View File

@ -1,17 +0,0 @@
directories:
# Add the directories you want added as source here
# By default, we've added your entire workspace ('.')
.
# Automatically includes all relevant targets under the 'directories' above
derive_targets_from_directories: true
targets:
# If source code isn't resolving, add additional targets that compile it here
additional_languages:
# Uncomment any additional languages you want supported
# dart
# javascript
# python
# typescript

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.system.id="Blaze" type="BLAZE_CPP_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$/../..">
<excludeFolder url="file://$MODULE_DIR$/.." />
<excludeFolder url="file://$MODULE_DIR$/../../.idea" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.system.id="Blaze" type="BLAZE_CPP_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$/../../..">
<sourceFolder url="file://$MODULE_DIR$/../../.." isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/../.." />
<excludeFolder url="file://$MODULE_DIR$/../../../bazel-CrocParty" />
<excludeFolder url="file://$MODULE_DIR$/../../../bazel-bin" />
<excludeFolder url="file://$MODULE_DIR$/../../../bazel-genfiles" />
<excludeFolder url="file://$MODULE_DIR$/../../../bazel-out" />
<excludeFolder url="file://$MODULE_DIR$/../../../bazel-testlogs" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.clwb/.idea/.gitignore generated vendored
View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

1
.clwb/.idea/.name generated
View File

@ -1 +0,0 @@
CrocParty

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.blaze/modules/.project-data-dir.iml" filepath="$PROJECT_DIR$/.blaze/modules/.project-data-dir.iml" />
<module fileurl="file://$PROJECT_DIR$/.blaze/modules/.workspace.iml" filepath="$PROJECT_DIR$/.blaze/modules/.workspace.iml" />
</modules>
</component>
</project>

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.jetbrains.cidr.cpp.execution.CMakeTargetRunConfigurationProducer" />
<option value="com.jetbrains.cidr.cpp.execution.debugger.CMakeRunConfigurationProducer" />
<option value="com.jetbrains.cidr.cpp.execution.testing.boost.CMakeBoostTestRunConfigurationProducer" />
<option value="com.jetbrains.cidr.cpp.execution.testing.google.CMakeGoogleTestRunConfigurationProducer" />
<option value="com.jetbrains.cidr.cpp.execution.testing.tcatch.CMakeCatchTestRunConfigurationProducer" />
<option value="com.jetbrains.cidr.cpp.runfile.CppFileTargetRunConfigurationProducer" />
<option value="com.jetbrains.python.run.PythonRunConfigurationProducer" />
<option value="com.jetbrains.python.testing.PyTestsConfigurationProducer" />
<option value="com.jetbrains.python.testing.PythonTestLegacyConfigurationProducer" />
<option value="com.jetbrains.python.testing.doctest.PythonDocTestConfigurationProducer" />
<option value="com.jetbrains.python.testing.tox.PyToxConfigurationProducer" />
</set>
</option>
</component>
</project>

6
.clwb/.idea/vcs.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

13
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"files.associations": {
"*.h": "c",
"stdbool.h": "c",
"chrono": "c",
"cmath": "c",
"cstdlib": "c",
"format": "c",
"ratio": "c",
"xlocnum": "c",
"xutility": "c"
}
}

View File

@ -1,6 +1,24 @@
###############################################################################
# Bazel now uses Bzlmod by default to manage external dependencies.
# Please consider migrating your external dependencies from WORKSPACE to MODULE.bazel.
#
# For more details, please check https://github.com/bazelbuild/bazel/issues/18958
###############################################################################
"""
CrocParty: a tiny platformer.
"""
module(
name="crocparty",
version="0.0",
)
bazel_dep(name="rules_python", version="0.31.0")
# Set up Python
python = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.toolchain(
python_version = "3.10",
is_default = True,
)
pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
pip.parse(
hub_name = "pytools_deps",
python_version = "3.10",
requirements_lock = "//pytools:requirements.lock",
)
use_repo(pip, "pytools_deps")

1659
MODULE.bazel.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,25 @@
# Croc Party!
A tiny platform game in C/SDL.
A tiny platform game in C/SDL.
# Commands
## Run (release)
```
$ bazel run -c opt sdl_host.exe
```
## Run
```
$ bazel run sdl_host.exe
```
## Dump requirements
(TODO: Automate)
```
$ pip freeze -r requirements.txt > requirements.lock
```

6
device/BUILD Normal file
View File

@ -0,0 +1,6 @@
cc_library(
name = "device",
srcs = glob(["*.c"]),
hdrs = glob(["*.h"]),
visibility = ["//visibility:public"]
)

7
device/device.c Normal file
View File

@ -0,0 +1,7 @@
#include <stdbool.h>
#include "device.h"
uint8_t device_pixels[DEVICE_H][DEVICE_W];
uint32_t device_palette[256];
bool device_buttons[DEVICE_BUTTON_N];

30
device/device.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef CROCPARTY_DEVICE_H
#define CROCPARTY_DEVICE_H
#include <stdint.h>
#include <stdbool.h>
// 256 x 144 is also cool
#define DEVICE_W 256
#define DEVICE_H 144
typedef enum {
DEVICE_BUTTON_L=0,
DEVICE_BUTTON_R=1,
DEVICE_BUTTON_U=2,
DEVICE_BUTTON_D=3,
DEVICE_BUTTON_0=4,
DEVICE_BUTTON_1=5,
DEVICE_BUTTON_2=6,
DEVICE_BUTTON_3=7,
DEVICE_BUTTON_N=8,
} DeviceButton;
extern uint8_t device_pixels[DEVICE_H][DEVICE_W];
extern uint32_t device_palette[256];
extern bool device_buttons[DEVICE_BUTTON_N];
#endif //CROCPARTY_DEVICE_H

36
game/BUILD Normal file
View File

@ -0,0 +1,36 @@
load("@bazel_skylib//rules:run_binary.bzl", "run_binary")
load("helpers.bzl", "add_sprites")
cc_library(
name = "game",
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
View 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)

Binary file not shown.

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
game/art/game_hud.aseprite Normal file

Binary file not shown.

8
game/art/game_hud.h Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

BIN
game/art/game_npcs.aseprite Normal file

Binary file not shown.

8
game/art/game_npcs.h Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

8
game/art/game_player.h Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

8
game/art/game_tiles.h Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 B

57
game/game.c Normal file
View File

@ -0,0 +1,57 @@
#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;
const char* game_title() {
return "Croc Party!";
}
void game_init() {
sys_init();
map_game_map_create_entities();
game_palette_init();
game_player_init();
}
void game_destroy() {
sys_destroy();
}
void game_update() {
sys_update();
game_frame += 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_cls(9);
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);
game_collectibles_draw();
game_npcs_draw();
game_player_draw();
sys_camera_reset();
game_player_draw_hud();
game_inflict_draw();
game_dialogue_draw();
}

20
game/game.h Normal file
View File

@ -0,0 +1,20 @@
#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();
void game_update();
void game_draw();
#endif //CROCPARTY_GAME_H

114
game/game_collectible.c Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

View 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;
}

View File

@ -1,8 +0,0 @@
cc_binary(
name = "game",
srcs = glob([
"*.c",
"*.h",
]),
deps = ["//sdl"],
)

View File

@ -1,3 +0,0 @@
#include "device.h"
uint32_t device_pixels[DEVICE_H][DEVICE_W];

View File

@ -1,12 +0,0 @@
#ifndef CROCPARTY_DEVICE_H
#define CROCPARTY_DEVICE_H
#include <stdint.h>
// 240 x 135 is also cool
#define DEVICE_W 128
#define DEVICE_H 128
extern uint32_t device_pixels[DEVICE_H][DEVICE_W];
#endif //CROCPARTY_DEVICE_H

View File

@ -1,29 +0,0 @@
#include "device.h"
#include "game.h"
uint32_t game_frame;
void game_init() {
game_frame = 0;
}
void game_update() {
game_frame += 1;
}
void game_draw() {
for (int x = 0; x < DEVICE_W; x++) {
for (int y = 0; y < DEVICE_H; y++) {
uint32_t r = (x * 255)/DEVICE_W;
uint32_t g = (y * 255)/DEVICE_H;
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;
}
uint32_t color = r << 24 | g << 16 | b << 8;
device_pixels[y][x] = color;
}
}
}

View File

@ -1,8 +0,0 @@
#ifndef CROCPARTY_GAME_H
#define CROCPARTY_GAME_H
void game_init();
void game_update();
void game_draw();
#endif //CROCPARTY_GAME_H

View File

@ -1,9 +0,0 @@
#include "sdl_host.h"
int main() {
// PYREX NOTE: You could, if you wanted, define another host with the same interface as sdl_host
// That would be pretty cool, right?
//
// (Such a host might be smaller and more platform-specific.)
return sdl_host_main();
}

View File

@ -1,6 +0,0 @@
#ifndef CROCPARTY_SDL_HOST_H
#define CROCPARTY_SDL_HOST_H
int sdl_host_main(void);
#endif //CROCPARTY_SDL_HOST_H

1
pytools/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
venv/

31
pytools/BUILD Normal file
View File

@ -0,0 +1,31 @@
load("@pytools_deps//:requirements.bzl", "requirement")
py_binary(
name = "font",
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"],
)

54
pytools/font.py Normal file
View File

@ -0,0 +1,54 @@
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):
glyphs = load_glyphs(fname_png)
assert(len(glyphs) == n_glyphs), f"must be exactly {n_glyphs} glyphs"
with open(fname_c, "wt") as output:
output.write(
shared.templates.from_string(TEMPLATE).render(
font_name=font_name,
n_glyphs=n_glyphs,
glyphs=glyphs,
)
)
def load_glyphs(fname_png: str):
width, height, data = shared.load_image(fname_png)
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)
return glyphs
def pixel_to_monochrome(rgba: Tuple[int, int, int, int]):
if rgba[3] < 128: return False
if (rgba[0] + rgba[1] + rgba[2])/3 < 128: return False
return True
if __name__ == "__main__":
assert len(sys.argv) == 5, \
"there must be four args (font name, n glyphs, src png, out c)"
main(sys.argv[1], int(sys.argv[2]), sys.argv[3], sys.argv[4])

162
pytools/mapdata.py Normal file
View 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])

View 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

2
pytools/requirements.txt Normal file
View File

@ -0,0 +1,2 @@
pillow==10.2.0
Jinja2==3.1.3

17
pytools/shared.py Normal file
View 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
View 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])

View File

@ -1,10 +1,7 @@
cc_library(
cc_import(
name = "sdl",
srcs = [
"x64_windows/SDL2.dll",
"x64_windows/SDL2.lib",
"x64_windows/SDL2main.lib",
],
hdrs = glob(["include/*.h"]),
visibility = ["//main:__pkg__"],
interface_library = "x64_windows/SDL2.lib",
shared_library = "x64_windows/SDL2.dll",
visibility = ["//sdl_host:__pkg__"],
)

5
sdl_host/BUILD Normal file
View File

@ -0,0 +1,5 @@
cc_binary(
name = "sdl_host",
srcs = glob(["*.c", "*.h"]),
deps = ["//device:device", "//game:game", "//sdl:sdl"],
)

View File

@ -1,19 +1,21 @@
#include <stdio.h>
#include <stdbool.h>
#include "sdl_host.h"
// don't use sdl's redefinition of main
#define SDL_MAIN_HANDLED
#include "sdl/include/SDL.h"
#include "game.h"
#include "device.h"
#include "device/device.h"
#include "game/game.h"
void sdl_host_suggest_dimensions(uint32_t* window_w, uint32_t* window_h);
void sdl_host_loop();
void sdl_host_handle_keyboard_event(SDL_KeyboardEvent event);
SDL_Window* sdl_host_window;
SDL_Renderer* sdl_host_renderer;
SDL_Texture* sdl_host_target;
int sdl_host_main(void) {
int main(int argc, char** argv) {
int result = 0;
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER)) {
printf("could not initialize SDL! sdl error: %s\n", SDL_GetError());
@ -26,7 +28,7 @@ int sdl_host_main(void) {
// create window
sdl_host_window = SDL_CreateWindow(
"Croc Party!",
game_title(),
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
(int) window_w, (int) window_h,
@ -63,16 +65,16 @@ int sdl_host_main(void) {
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;
@ -93,7 +95,7 @@ void sdl_host_suggest_dimensions(uint32_t* window_w, uint32_t* window_h) {
dm.h = 0;
}
for (int scalar = 5; 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) {
@ -119,6 +121,7 @@ void sdl_host_suggest_dimensions(uint32_t* window_w, uint32_t* window_h) {
void sdl_host_loop() {
uint32_t ticks_per_frame = 1000/60;
uint32_t real_pixels[DEVICE_H][DEVICE_W];
game_init();
while (true) {
@ -128,13 +131,22 @@ void sdl_host_loop() {
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_QUIT) { goto quit; }
if (e.type == SDL_KEYDOWN || e.type == SDL_KEYUP) {
sdl_host_handle_keyboard_event(e.key);
}
}
// trigger game logic
game_update();
game_draw();
SDL_UpdateTexture(sdl_host_target, NULL, &device_pixels, sizeof(device_pixels[0]));
for (int x = 0; x < DEVICE_W; x++) {
for (int y = 0; y < DEVICE_H; y++) {
real_pixels[y][x] = device_palette[device_pixels[y][x]];
}
}
SDL_UpdateTexture(sdl_host_target, NULL, &real_pixels, sizeof(real_pixels[0]));
SDL_RenderClear(sdl_host_renderer);
SDL_RenderCopy(sdl_host_renderer, sdl_host_target, NULL, NULL);
SDL_RenderPresent(sdl_host_renderer);
@ -145,4 +157,33 @@ void sdl_host_loop() {
}
}
quit: ;
}
void sdl_host_handle_keyboard_event(SDL_KeyboardEvent event) {
bool is_down;
switch (event.type) {
case SDL_KEYDOWN: is_down = true; break;
case SDL_KEYUP: is_down = false; break;
default: return;
}
if (event.repeat) {
return;
}
DeviceButton db;
switch (event.keysym.sym) {
case SDLK_LEFT: db = DEVICE_BUTTON_L; break;
case SDLK_RIGHT: db = DEVICE_BUTTON_R; break;
case SDLK_UP: db = DEVICE_BUTTON_U; break;
case SDLK_DOWN: db = DEVICE_BUTTON_D; break;
case SDLK_z: db = DEVICE_BUTTON_0; break;
case SDLK_x: db = DEVICE_BUTTON_1; break;
case SDLK_c: db = DEVICE_BUTTON_2; break;
case SDLK_v: db = DEVICE_BUTTON_3; break;
default: return;
}
device_buttons[db] = is_down;
}

22
sys/BUILD Normal file
View File

@ -0,0 +1,22 @@
load("@bazel_skylib//rules:run_binary.bzl", "run_binary")
cc_library(
name = "sys",
srcs = glob(["*.c"]) + [":fonts/sys_font_small.c"],
hdrs = glob(["*.h"]) + [":fonts/sys_font_small.h"],
visibility = ["//visibility:public"],
deps = ["//device:device"],
)
run_binary(
name = "sys_font_small",
args = [
"sys_font_small",
"256",
"$(location :fonts/sys_font_small.png)",
"$(location :fonts/sys_font_small.c)"
],
srcs = [":fonts/sys_font_small.png"],
outs = [":fonts/sys_font_small.c"],
tool = "//pytools:font",
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,8 @@
#ifndef CROCPARTY_SYS_FONT_SMALL_H
#define CROCPARTY_SYS_FONT_SMALL_H
#include "sys/sys.h"
extern sys_glyph font_sys_font_small[256];
#endif // CROCPARTY_SYS_FONT_SMALL_H

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

29
sys/sys.c Normal file
View File

@ -0,0 +1,29 @@
#include <assert.h>
#include <stdbool.h>
#include "sys.h"
bool sys_initialized = false;
void sys_init() {
assert(!sys_get_initialized());
sys_initialized = true;
// reset graphics
sys_clip_reset();
sys_camera_reset();
sys_spal_reset();
sys_dpal_reset();
}
void sys_update() {
sys_input_update();
}
bool sys_get_initialized() {
return sys_initialized;
}
void sys_destroy() {
assert(sys_get_initialized());
sys_initialized = false;
}

28
sys/sys.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef CROCPARTY_SYS_H
#define CROCPARTY_SYS_H
#include "sys_data.h"
#include "sys_graphics.h"
#include "fonts/sys_font_small.h"
/**
* Initialize sys.
*/
void sys_init();
/**
* Update sys -- usually for input state.
*/
void sys_update();
/**
* Return whether sys was initialized.
*/
bool sys_get_initialized();
/**
* Destroy sys.
*/
void sys_destroy();
#endif // CROCPARTY_SYS_H

20
sys/sys_data.c Normal file
View File

@ -0,0 +1,20 @@
#include "sys_data.h"
sys_i32 sys_max_i32(sys_i32 x, sys_i32 y) {
return x > y ? x : y;
}
sys_i32 sys_min_i32(sys_i32 x, sys_i32 y) {
return x < y ? x : y;
}
sys_i32 sys_abs_i32(sys_i32 x) {
if (x < 0) { return -x; }
return x;
}
sys_i32 sys_sgn_i32(sys_i32 x) {
if (x < 0) { return -1; }
if (x > 0) { return 1; }
return 0;
}

37
sys/sys_data.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef CROCPARTY_SYS_DATA_H
#define CROCPARTY_SYS_DATA_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 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);
sys_i32 sys_max_i32(sys_i32 x, sys_i32 y);
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);
#endif // CROCPARTY_SYS_DATA_H

447
sys/sys_graphics.c Normal file
View File

@ -0,0 +1,447 @@
#include <assert.h>
#include "sys.h"
#include "device/device.h"
sys_i32 sys_cam_dx, sys_cam_dy;
sys_i32 sys_clip_x0, sys_clip_y0, sys_clip_x1, sys_clip_y1;
sys_color sys_dpal[256];
// primitives (forward declare)
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_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) {
assert(sys_get_initialized());
sys_clip_set_ext(x0, y0, x1, y1, false);
}
void sys_clip_reset() {
assert(sys_get_initialized());
sys_clip_set(0, 0, DEVICE_W, DEVICE_H);
}
void sys_clip_set_ext(sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, bool clip_previous) {
assert(sys_get_initialized());
sys_clip_x0 = sys_max_i32(x0, clip_previous ? sys_clip_x0 : 0);
sys_clip_y0 = sys_max_i32(y0, clip_previous ? sys_clip_y0 : 0);
sys_clip_x1 = sys_min_i32(x1, clip_previous ? sys_clip_x1 : DEVICE_W);
sys_clip_y1 = sys_min_i32(y1, clip_previous ? sys_clip_y1 : DEVICE_H);
}
void sys_pixel_set(sys_i32 x, sys_i32 y, sys_color c) {
assert(sys_get_initialized());
x += sys_cam_dx;
y += sys_cam_dy;
sys_pixel_internal_set(x, y, c);
}
sys_color sys_pixel_get(sys_i32 x, sys_i32 y) {
assert(sys_get_initialized());
x += sys_cam_dx;
y += sys_cam_dy;
if (x < 0 || y < 0 || x >= DEVICE_W || y >= DEVICE_H) {
return SYS_COLOR_TRANSPARENT;
}
return device_pixels[y][x];
}
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_internal_draw(x, y, font_sys_font_small[c], col);
x += 8;
}
}
void sys_cls(sys_color c) {
assert(sys_get_initialized());
for (int x = 0; x < DEVICE_W; x++) {
for (int y = 0; y < DEVICE_W; y++) {
sys_pixel_internal_set(x, y, c);
}
}
}
void sys_camera_set(sys_i32 x, sys_i32 y) {
assert(sys_get_initialized());
sys_cam_dx = -x;
sys_cam_dy = -y;
}
void sys_camera_reset() {
assert(sys_get_initialized());
sys_camera_set(0, 0);
}
// Based on bresenham implementations by alois zingl
// https://zingl.github.io/bresenham.html
void sys_circ_draw(sys_i32 x, sys_i32 y, sys_i32 r, sys_color c) {
assert(sys_get_initialized());
sys_circ_draw_ext(x, y, r, c, false);
}
void sys_circ_fill(sys_i32 x, sys_i32 y, sys_i32 r, sys_color c) {
assert(sys_get_initialized());
sys_circ_draw_ext(x, y, r, c, true);
}
void sys_circ_draw_ext(
sys_i32 x, sys_i32 y, sys_i32 r, sys_color c, bool fill
) {
assert(sys_get_initialized());
if (r < 0) { return; }
sys_oval_draw_ext(x - r, y - r, x + r + 1, y + r + 1, c, fill);
}
void sys_oval_draw(
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c
) {
assert(sys_get_initialized());
sys_oval_draw_ext(x0, y0, x1, y1, c, false);
}
void sys_oval_fill(
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c
) {
assert(sys_get_initialized());
sys_oval_draw_ext(x0, y0, x1, y1, c, true);
}
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;
int64_t a = x1 - x0;
int64_t b = y1 - y0;
int64_t b1 = b & 1;
int64_t dx = 4 * (1 - a) * b * b;
int64_t dy = 4 * (b1 + 1) * a * a;
int64_t err = dx + dy + b1 * a * a;
y0 += (b + 1) / 2;
y1 = y0 - b1;
int64_t aa8 = 8 * a * a;
int64_t bb8 = 8 * b * b;
do {
// 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) {
// draw the whole line
sys_scanline_internal_set(x0, x1, y0, c, true);
sys_scanline_internal_set(x0, x1, y1, c, true);
}
y0++; y1--; dy += aa8; err += dy;
}
if (e2 >= dx || 2 * err > dy) {
x0++; x1--; dx += bb8; err += dx;
}
} while (x0 <= x1);
while (y0 - y1 < b) {
sys_scanline_internal_set(x0 - 1, x1 + 1, y0, c, fill); y0++;
sys_scanline_internal_set(x0 - 1, x1 + 1, y1, c, fill); y1--;
}
}
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; }
sys_i32 dx = sys_abs_i32(x1 - x0);
sys_i32 sx = sys_sgn_i32(x1 - x0);
sys_i32 dy = -sys_abs_i32(y1 - y0);
sys_i32 sy = sys_sgn_i32(y1 - y0);
sys_i32 err = dx + dy;
while (true) {
if (x0 == x1 || y0 == y1) { return; }
sys_pixel_internal_set(x0, y0, c);
sys_i32 e2 = 2 * err;
if (e2 >= dy) { err += dy; x0 += sx; }
if (e2 <= dx) { err += dx; y0 += sy; }
}
}
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());
device_palette[c0] = rc1;
}
void sys_spal_reset() {
assert(sys_get_initialized());
for (int i = 0; i < SYS_COLOR_N; i++) {
sys_spal_set(i, 0x000000ff);
}
}
void sys_dpal_set(sys_color c0, sys_color c1) {
assert(sys_get_initialized());
sys_dpal[c0] = c1;
}
void sys_dpal_reset() {
assert(sys_get_initialized());
for (int i = 0; i < SYS_COLOR_N; i++) {
sys_dpal_set(i, i);
}
}
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];
if (realc == SYS_COLOR_TRANSPARENT) { return; }
if (
x < sys_clip_x0 || y < sys_clip_y0 ||
x >= sys_clip_x1 || y >= sys_clip_y1
) {
return;
}
assert(x >= 0);
assert(y >= 0);
assert(x < DEVICE_W);
assert(y < DEVICE_H);
device_pixels[y][x] = realc;
}
void sys_scanline_internal_set(
sys_i32 x0, sys_i32 x1, sys_i32 y, sys_color c,
bool fill
) {
if (fill) {
for (sys_i32 x = x0; x <= x1; x++) {
sys_pixel_internal_set(x, y, c);
}
} else {
sys_pixel_internal_set(x0, y, c);
sys_pixel_internal_set(x1, y, 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++) {
for (int px = 0; px < 8; px++) {
uint64_t mask = ((uint64_t) 1) << (uint64_t) (py * 8 + px);
if ((g & mask) != 0) {
sys_pixel_internal_set(x + px, y + py, 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]);
}
}
}

200
sys/sys_graphics.h Normal file
View File

@ -0,0 +1,200 @@
// Corresponds roughly to section 6.2 of the Pico 8 manual
//
// However, all ranges are now half-open.
//
// https://www.lexaloffle.com/dl/docs/pico-8_manual.html
//
#ifndef CROCPARTY_SYS_GRAPHICS_H
#define CROCPARTY_SYS_GRAPHICS_H
#include <stdbool.h>
#include <stdint.h>
#include "sys_data.h"
/**
* Set the clipping rectangle in pixels.
*
* All drawing operations will be clipped to this rectangle.
*/
void sys_clip_set(sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1);
/**
* Reset the clipping rectangle.
*/
void sys_clip_reset();
/**
* Set the clipping rectangle in pixels.
*
* This is the same as `sys_clip_set`, but you can pass clip_previous in order
* to clip to the previous region.
*/
void sys_clip_set_ext(
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1,
bool clip_previous
);
/**
* Set the color of the pixel `(x, y)`.
*/
void sys_pixel_set(
sys_i32 x, sys_i32 y,
sys_color c
);
/**
* Get the color of the pixel `(x, y)`.
*
* Returns SYS_COLOR_TRANSPARENT if the pixel is out of bounds.
*/
sys_color sys_pixel_get(
sys_i32 x, sys_i32 y
);
// 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(const char* str, sys_i32 x, sys_i32 y, sys_color col);
// TODO: CURSOR? COLOR?
/**
* Fill the entire screen with color `c`.
*/
void sys_cls(sys_color c);
/**
* Create a screen offset of `(-x, -y)` for all drawing operations.
*/
void sys_camera_set(sys_i32 x, sys_i32 y);
/**
* Reset the camera's offset to `(0, 0)`.
*/
void sys_camera_reset();
/**
* Draw or fill a circle at `(x, y)` with radius `r`.
*
* The circle is centered at the center of the pixel, not at the junction
* between pixels.
*
* If r is negative, the circle is not drawn.
*
* 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);
void sys_circ_draw_ext(sys_i32 x, sys_i32 y, sys_i32 r, sys_color c, bool fill);
/**
* Draw or fill an oval in the rectangle from `(x0, y0)` to `(x1, y1)`
*/
void sys_oval_draw(sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c);
void sys_oval_fill(sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c);
void sys_oval_draw_ext(
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c, bool fill
);
/**
* Draw a line from x0 to y0.
*
* The point `(x1, y1)` is not drawn. (because the line is a half-open interval)
*/
void sys_line_draw(sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c);
/**
* Draw or fill a rectangle `(x, y, w, h)`
*/
void sys_rect_draw(sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c);
void sys_rect_fill(sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c);
void sys_rect_draw_ext(
sys_i32 x0, sys_i32 y0, sys_i32 x1, sys_i32 y1, sys_color c, bool fill
);
/**
* Set screen palette.
*
* After calling this function, virtual color `c0` will be rendered as
* real color `rc1`.
*/
void sys_spal_set(sys_color c0, sys_screen_color rc1);
/**
* Reset screen palette.
*
* After calling this function, all screen colors are (0, 0, 0).
*/
void sys_spal_reset();
/**
* Set draw palette.
*
* After calling this function, draws using color `c0` will be performed
* using color `c1` instead.
*
* Any color mapped to `SYS_COLOR_TRANSPARENT` will not actually be drawn.
*/
void sys_dpal_set(sys_color c0, sys_color c1);
/**
* Reset draw palette.
*
* All colors are mapped to themselves. SYS_COLOR_TRANSPARENT is transparent.
*/
void sys_dpal_reset();
/**
* 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
View 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
View 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