Compare commits

...

14 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
35 changed files with 4843 additions and 289 deletions

View File

@@ -1,44 +1,27 @@
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"]
)
run_binary(
name = "game_player",
args = [
"game_player",
"96", # n sprites
"0", # key color
"$(location :art/game_player.png)",
"$(location :art/game_player.c)"
],
srcs = [":art/game_player.png"],
outs = [":art/game_player.c"],
tool = "//pytools:spritesheet",
)
run_binary(
name = "game_tiles",
args = [
"game_tiles",
"120", # n sprites
"0", # key color
"$(location :art/game_tiles.png)",
"$(location :art/game_tiles.c)"
],
srcs = [":art/game_tiles.png"],
outs = [":art/game_tiles.c"],
tool = "//pytools:spritesheet",
)
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",

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

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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 601 B

After

Width:  |  Height:  |  Size: 603 B

View File

@@ -15,6 +15,8 @@ const char* game_title() {
void game_init() {
sys_init();
map_game_map_create_entities();
game_palette_init();
game_player_init();
}
@@ -27,13 +29,29 @@ void game_update() {
sys_update();
game_frame += 1;
game_player_update();
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);
sys_map_draw(map_game_map, spr_game_tiles, 0, 0, 0, 0, 32, 18);
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();
}

View File

@@ -1,7 +1,12 @@
#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"

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

View File

@@ -16,9 +16,8 @@ bool game_collision_is_occlusive(sys_maptile tile) {
bool game_collision_can_move(game_bbox body, sys_i32 dx, sys_i32 dy) {
// First: place the body in space.
// Align its top-left corner to the pixel boundary
sys_i32 x = body.x + dx;
sys_i32 y = body.y + dy;
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
@@ -80,16 +79,17 @@ game_collision game_collision_move_to_contact(
}
// figure out where we are
sys_i32 body_x0 = body->x;
sys_i32 body_y0 = body->y;
sys_i32 body_x1 = body->x + body->w;
sys_i32 body_y1 = body->y + body->h;
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 tile_x1 = round_up(body_x1, TILE_SZ_MICROPIXEL);
sys_i32 tile_y1 = round_up(body_y1, 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; }

View File

@@ -8,6 +8,8 @@
#define PIXEL_SZ_MICROPIXEL 0x100
typedef struct {
sys_i32 x_origin;
sys_i32 y_origin;
sys_i32 x;
sys_i32 y;
sys_i32 w;

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

View File

@@ -1,3 +1,5 @@
#include <assert.h>
#include "art/game_hud.h"
#include "art/game_player.h"
#include "art/game_tiles.h"
#include "device/device.h"
@@ -5,14 +7,12 @@
#include "game.h"
#include "sys/sys.h"
#define GAME_PLAYER_ORIGIN_X 4
#define GAME_PLAYER_ORIGIN_Y 4
game_bbox game_player_bbox = {
.x_origin=0x0500,
.y_origin=0x0c00,
.x=0x9000,
// .y=0x5800,
.y=0x0000,
.w=0x0800,
.y=0x1000,
.w=0x0a00,
.h=0x0c00,
};
bool game_player_grounded = false;
@@ -22,6 +22,13 @@ 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,
@@ -35,20 +42,41 @@ 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() {
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, PIXEL_SZ_MICROPIXEL
game_player_bbox, 0, 1
);
{
@@ -58,13 +86,13 @@ void game_player_update() {
// round up
0x7f * sys_sgn_i32(game_player_dx)
) / 0x80;
if (sys_abs_i32(game_player_dx) > 0x200) {
game_player_dx = sys_sgn_i32(game_player_dx) * 0x200;
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 (sys_btn(DEVICE_BUTTON_L)) { wanted_dx -= 0x50; }
if (sys_btn(DEVICE_BUTTON_R)) { wanted_dx += 0x50; }
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 (
@@ -82,9 +110,9 @@ void game_player_update() {
}
if (game_player_grounded || game_player_coyote_frames > 0) {
if (sys_btnp(DEVICE_BUTTON_U, false)) {
game_player_dy = -0x300;
game_player_jump_frames = 15;
if (*allow_input && sys_btnp(DEVICE_BUTTON_U, false)) {
game_player_dy = -0x200;
game_player_jump_frames = 13;
game_player_grounded = false;
}
}
@@ -129,26 +157,55 @@ void game_player_update() {
}
}
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() {
int game_player_image;
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 = 2 + (game_player_anim_progress / 0x80) * 2;
game_player_image = base + 2 + (game_player_anim_progress / 0x80) * 2;
break;
case GAME_PLAYER_ANIM_FLOATING:
game_player_image = 6;
game_player_image = base + 6;
break;
default:
case GAME_PLAYER_ANIM_STANDING:
game_player_image = 0;
game_player_image = base + 0;
break;
}
sys_sprite_draw_ext(
spr_game_player,
game_player_image,
game_player_bbox.x / 0x100 - GAME_PLAYER_ORIGIN_X,
game_player_bbox.y / 0x100 - GAME_PLAYER_ORIGIN_Y,
game_player_bbox.x / 0x100 - 8,
game_player_bbox.y / 0x100 - 16,
2, 2,
game_player_faceleft, false
);
@@ -166,3 +223,67 @@ void game_player_anim_add_progress(uint8_t amt) {
(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);
}

View File

@@ -1,8 +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();
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",
)

View File

@@ -1,11 +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

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,9 +1,11 @@
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(",") }} };
@@ -12,10 +14,42 @@ sys_map map_{{map_name}} = {
.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 = load_mapdata(fname_ldtk)
width, height, tiles, entities, entity_types, entity_fields = load_mapdata(fname_ldtk)
print(entity_fields)
with open(fname_c, "wt") as output:
output.write(
@@ -24,6 +58,9 @@ def main(map_name, fname_ldtk, fname_c):
tiles=tiles,
width=width,
height=height,
entities=entities,
entity_types=entity_types,
entity_fields=entity_fields,
)
)
@@ -31,6 +68,8 @@ def main(map_name, fname_ldtk, fname_c):
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)
@@ -49,7 +88,28 @@ def load_mapdata(fname_ldtk):
if layer["__identifier"] == "entities":
for e in layer["entityInstances"]:
raise NotImplementedError()
# 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
@@ -66,9 +126,29 @@ def load_mapdata(fname_ldtk):
else:
dense_tiles.append(255)
return width, height, dense_tiles
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):

View File

@@ -57,7 +57,29 @@ 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];
@@ -135,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;
@@ -158,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) {
@@ -186,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; }
@@ -205,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());
@@ -254,6 +322,9 @@ void sys_sprite_draw_ext(
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++) {
@@ -283,6 +354,10 @@ void sys_map_draw(
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;

View File

@@ -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?
/**