Fix game over restart logic and update README
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
#include <gb/gb.h>
|
||||
#include <gbdk/font.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <rand.h>
|
||||
@@ -7,6 +8,254 @@
|
||||
// Generated by png2asset
|
||||
#include "tiles.h"
|
||||
#include "player.h"
|
||||
#include "gameover.h"
|
||||
#include "stamina.h"
|
||||
#include "title_bg.h"
|
||||
|
||||
// Music variables
|
||||
static volatile uint8_t music_timer = 0;
|
||||
static volatile uint8_t music_step = 0;
|
||||
volatile uint8_t game_over = 0;
|
||||
volatile uint8_t game_over_timer = 0;
|
||||
|
||||
// Game Over creepy melody variables
|
||||
static volatile uint8_t gameover_music_timer = 0;
|
||||
static volatile uint8_t gameover_music_step = 0;
|
||||
|
||||
// Notes frequencies
|
||||
#define R 0
|
||||
#define N_E2 458
|
||||
#define N_F2 547
|
||||
#define N_G2 742
|
||||
#define N_A2 857
|
||||
#define N_AS2 907
|
||||
#define N_B2 961
|
||||
#define N_C3 1046
|
||||
#define N_CS3 1103
|
||||
#define N_D3 1155
|
||||
#define N_E3 1253
|
||||
#define N_F3 1298
|
||||
#define N_FS3 1340
|
||||
#define N_G3 1380
|
||||
#define N_GS3 1417
|
||||
#define N_A3 1453
|
||||
#define N_AS3 1486
|
||||
#define N_B3 1517
|
||||
#define N_C4 1547
|
||||
#define N_CS4 1575
|
||||
#define N_D4 1602
|
||||
#define N_E4 1651
|
||||
#define N_F4 1673
|
||||
#define N_FS4 1694
|
||||
#define N_G4 1714
|
||||
#define N_GS4 1732
|
||||
#define N_A4 1751
|
||||
#define N_AS4 1767
|
||||
#define N_B4 1783
|
||||
#define N_C5 1798
|
||||
#define N_CS5 1812
|
||||
#define N_D5 1825
|
||||
#define N_E5 1849
|
||||
#define N_F5 1860
|
||||
#define N_FS5 1871
|
||||
#define N_G5 1881
|
||||
#define N_GS5 1890
|
||||
#define N_A5 1899
|
||||
|
||||
const uint16_t ch1_seq[128] = {
|
||||
// 1 Dm
|
||||
N_D3, N_A3, N_D4, N_A3, N_D3, N_A3, N_D4, N_A3,
|
||||
// 2 A/C#
|
||||
N_CS3, N_A3, N_CS4, N_A3, N_CS3, N_A3, N_CS4, N_A3,
|
||||
// 3 F/C
|
||||
N_C3, N_A3, N_C4, N_A3, N_C3, N_A3, N_C4, N_A3,
|
||||
// 4 G/B
|
||||
N_B2, N_G3, N_B3, N_G3, N_B2, N_G3, N_B3, N_G3,
|
||||
// 5 Bb
|
||||
N_AS2, N_F3, N_AS3, N_F3, N_AS2, N_F3, N_AS3, N_F3,
|
||||
// 6 F/A
|
||||
N_A2, N_F3, N_A3, N_F3, N_A2, N_F3, N_A3, N_F3,
|
||||
// 7 Gm
|
||||
N_G2, N_D3, N_G3, N_D3, N_G2, N_D3, N_G3, N_D3,
|
||||
// 8 A7
|
||||
N_A2, N_E3, N_A3, N_CS4, N_A2, N_E3, N_A3, N_CS4,
|
||||
// 9 Dm (intensifies: higher arpeggio)
|
||||
N_D3, N_D4, N_F4, N_A4, N_D3, N_D4, N_F4, N_A4,
|
||||
// 10 A/C#
|
||||
N_CS3, N_CS4, N_E4, N_A4, N_CS3, N_CS4, N_E4, N_A4,
|
||||
// 11 F/C
|
||||
N_C3, N_C4, N_F4, N_A4, N_C3, N_C4, N_F4, N_A4,
|
||||
// 12 G/B
|
||||
N_B2, N_B3, N_D4, N_G4, N_B2, N_B3, N_D4, N_G4,
|
||||
// 13 Bb
|
||||
N_AS2, N_AS3, N_D4, N_F4, N_AS2, N_AS3, N_D4, N_F4,
|
||||
// 14 F/A
|
||||
N_A2, N_A3, N_C4, N_F4, N_A2, N_A3, N_C4, N_F4,
|
||||
// 15 Gm
|
||||
N_G2, N_G3, N_AS3, N_D4, N_G2, N_G3, N_AS3, N_D4,
|
||||
// 16 A7 -> Dm resolution
|
||||
N_A2, N_A3, N_CS4, N_E4, N_A2, R, N_D3, R
|
||||
};
|
||||
|
||||
const uint16_t ch2_seq[128] = {
|
||||
// 1 Dm
|
||||
N_F4, R, R, R, N_E4, R, R, R,
|
||||
// 2 A/C#
|
||||
N_E4, R, R, R, N_D4, R, R, R,
|
||||
// 3 F/C
|
||||
N_C4, R, R, R, N_D4, R, N_E4, R,
|
||||
// 4 G/B
|
||||
N_D4, R, R, R, N_F4, R, R, R,
|
||||
// 5 Bb
|
||||
N_D4, R, R, R, N_C4, R, R, R,
|
||||
// 6 F/A
|
||||
N_C4, R, R, R, N_AS3, R, R, R,
|
||||
// 7 Gm
|
||||
N_AS3, R, N_A3, N_AS3, N_C4, R, N_AS3, R,
|
||||
// 8 A7
|
||||
N_A3, R, R, R, R, R, R, R,
|
||||
// 9 Dm
|
||||
N_A4, R, R, R, N_G4, R, N_F4, N_E4,
|
||||
// 10 A/C#
|
||||
N_G4, R, R, R, N_F4, R, N_E4, N_D4,
|
||||
// 11 F/C
|
||||
N_F4, R, R, R, N_E4, R, N_C4, R,
|
||||
// 12 G/B
|
||||
N_D4, R, R, R, N_G4, R, R, R,
|
||||
// 13 Bb
|
||||
N_F4, R, R, R, N_E4, R, N_D4, N_CS4,
|
||||
// 14 F/A
|
||||
N_E4, R, R, R, N_D4, R, N_C4, N_B3,
|
||||
// 15 Gm
|
||||
N_D4, R, R, R, N_C4, R, N_AS3, R,
|
||||
// 16 A7 -> Dm resolution
|
||||
N_A3, R, R, R, R, R, N_D4, R
|
||||
};
|
||||
|
||||
// Eerie pulse note frequencies (96-note sequence for normal gameplay)
|
||||
const uint16_t eerie_reg_vals[] = {
|
||||
// Pattern 1 (A minor)
|
||||
1453, 1546, 1650, 1733, 1754, 1733, 1650, 1546,
|
||||
1453, 1546, 1650, 1733, 1754, 1733, 1650, 1546,
|
||||
1453, 1546, 1650, 1733, 1754, 1733, 1650, 1546,
|
||||
1453, 1546, 1650, 1733, 1754, 1733, 1650, 1546,
|
||||
|
||||
// Pattern 2 (D minor)
|
||||
1303, 1453, 1546, 1650, 1678, 1650, 1546, 1453,
|
||||
1303, 1453, 1546, 1650, 1678, 1650, 1546, 1453,
|
||||
1303, 1453, 1546, 1650, 1678, 1650, 1546, 1453,
|
||||
1303, 1453, 1546, 1650, 1678, 1650, 1546, 1453,
|
||||
|
||||
// Pattern 3 (E7 tension)
|
||||
1369, 1497, 1589, 1709, 1733, 1709, 1589, 1497,
|
||||
1369, 1497, 1589, 1709, 1733, 1709, 1589, 1497,
|
||||
1369, 1497, 1589, 1709, 1733, 1709, 1589, 1497,
|
||||
1369, 1497, 1589, 1709, 1733, 1709, 1589, 1497
|
||||
};
|
||||
|
||||
// Title Music Variables
|
||||
static volatile uint8_t title_music_timer = 0;
|
||||
static volatile uint8_t title_music_step = 0;
|
||||
|
||||
const uint16_t title_melody[32] = {
|
||||
N_A2, N_E3, N_A3, N_E3, N_C4, N_A3, N_E3, N_A2, // Am
|
||||
N_G2, N_D3, N_G3, N_D3, N_B3, N_G3, N_D3, N_G2, // G
|
||||
N_F2, N_C3, N_F3, N_C3, N_A3, N_F3, N_C3, N_F2, // F
|
||||
N_E2, N_B2, N_E3, N_B2, N_GS3, N_E3, N_B2, N_E2 // E
|
||||
};
|
||||
|
||||
static void play_note(uint16_t reg_val) {
|
||||
NR21_REG = 0x80; // 50% duty cycle
|
||||
NR22_REG = 0x73; // volume 7, fade envelope
|
||||
NR23_REG = reg_val & 0xFF;
|
||||
NR24_REG = (reg_val >> 8) | 0x80; // trigger note
|
||||
}
|
||||
|
||||
static void play_gameover_step(uint8_t step) {
|
||||
if (step < 128) {
|
||||
uint16_t n1 = ch1_seq[step];
|
||||
if (n1 != R) {
|
||||
NR10_REG = 0x00; // No sweep
|
||||
NR11_REG = 0x80; // 50% duty
|
||||
NR12_REG = 0x73; // volume 7, fast fade for arpeggio pluck
|
||||
NR13_REG = n1 & 0xFF;
|
||||
NR14_REG = (n1 >> 8) | 0x80;
|
||||
}
|
||||
|
||||
uint16_t n2 = ch2_seq[step];
|
||||
if (n2 != R) {
|
||||
NR21_REG = 0x80; // 50% duty
|
||||
NR22_REG = 0xA7; // volume 10, long fade for melody sustain
|
||||
NR23_REG = n2 & 0xFF;
|
||||
NR24_REG = (n2 >> 8) | 0x80;
|
||||
}
|
||||
|
||||
// Add dramatic percussion on the first beat of each chord (every 8 steps)
|
||||
if (step % 8 == 0) {
|
||||
NR41_REG = 0x01;
|
||||
NR42_REG = 0xB2; // Volume 11, quick fade
|
||||
// step < 64 gets a lower tympani, step >= 64 gets a crash
|
||||
if (step < 64) {
|
||||
NR43_REG = 0x68; // Low thud
|
||||
} else {
|
||||
NR43_REG = 0x42; // Crash
|
||||
}
|
||||
NR44_REG = 0x80;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void play_music_tick(void) {
|
||||
if (app_state == 0) {
|
||||
title_music_timer++;
|
||||
if (title_music_timer >= 30) { // 30 frames per note
|
||||
title_music_timer = 0;
|
||||
uint16_t n = title_melody[title_music_step];
|
||||
NR10_REG = 0;
|
||||
NR11_REG = 0x80;
|
||||
NR12_REG = 0x63; // soft fade
|
||||
NR13_REG = n & 0xFF;
|
||||
NR14_REG = (n >> 8) | 0x80;
|
||||
|
||||
// Add a slow bass note every 4 steps
|
||||
if (title_music_step % 4 == 0) {
|
||||
NR21_REG = 0x80;
|
||||
NR22_REG = 0x77;
|
||||
uint16_t bn = n / 2; // Octave lower
|
||||
NR23_REG = bn & 0xFF;
|
||||
NR24_REG = (bn >> 8) | 0x80;
|
||||
}
|
||||
|
||||
title_music_step++;
|
||||
if (title_music_step >= 32) title_music_step = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (game_over) {
|
||||
if (game_over_timer == 0) {
|
||||
gameover_music_timer++;
|
||||
if (gameover_music_timer >= 10) { // 10 frames per note (~6 notes/sec)
|
||||
gameover_music_timer = 0;
|
||||
if (gameover_music_step < 128) {
|
||||
play_gameover_step(gameover_music_step);
|
||||
gameover_music_step++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
music_timer++;
|
||||
if (music_timer >= 20) {
|
||||
music_timer = 0;
|
||||
play_note(eerie_reg_vals[music_step]);
|
||||
music_step++;
|
||||
if (music_step >= 96) {
|
||||
music_step = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#define MAP_SIZE 7
|
||||
|
||||
@@ -21,6 +270,9 @@ uint8_t scroll_y = 0;
|
||||
|
||||
// Movement transition variables
|
||||
uint8_t is_moving = 0;
|
||||
uint8_t is_jumping = 0;
|
||||
uint8_t stamina = 100;
|
||||
uint8_t stamina_recharge_timer = 0;
|
||||
uint8_t move_progress = 0;
|
||||
int8_t start_lx, start_ly;
|
||||
int8_t target_lx, target_ly;
|
||||
@@ -43,8 +295,8 @@ int8_t enemy_target_lx, enemy_target_ly;
|
||||
int16_t enemy_start_px, enemy_start_py;
|
||||
int16_t enemy_target_px, enemy_target_py;
|
||||
uint8_t enemy_cooldown = 0;
|
||||
uint8_t game_over = 0;
|
||||
uint8_t game_over_timer = 0;
|
||||
|
||||
|
||||
|
||||
const metasprite_t enemy_metasprite[] = {
|
||||
METASPR_ITEM(-8, -8, 0, S_PALETTE),
|
||||
@@ -132,6 +384,44 @@ static void generate_maze(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static void update_stamina_display(void) {
|
||||
if (game_over) {
|
||||
// Hide the 5 stamina bar sprites when game is over
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
move_sprite(18 + i, 0, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Position 5 sprites at top-right of the screen (fixed, completely ignores scroll)
|
||||
move_sprite(18, (uint8_t)(112 + 8), 16);
|
||||
move_sprite(19, (uint8_t)(120 + 8), 16);
|
||||
move_sprite(20, (uint8_t)(128 + 8), 16);
|
||||
move_sprite(21, (uint8_t)(136 + 8), 16);
|
||||
move_sprite(22, (uint8_t)(144 + 8), 16);
|
||||
|
||||
uint16_t temp = (uint16_t)stamina * 40;
|
||||
uint8_t num_pixels = temp / 100;
|
||||
|
||||
uint8_t base_tile = player_TILE_COUNT + gameover_TILE_COUNT; // 44
|
||||
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
uint8_t tile_idx;
|
||||
if (i == 0) {
|
||||
tile_idx = (num_pixels >= 8) ? 11 : 9; // Left Cap full (11) or empty (9)
|
||||
} else if (i == 4) {
|
||||
tile_idx = (num_pixels >= 40) ? 12 : 10; // Right Cap full (12) or empty (10)
|
||||
} else {
|
||||
int8_t seg_px = (int8_t)num_pixels - (i * 8);
|
||||
if (seg_px < 0) seg_px = 0;
|
||||
if (seg_px > 8) seg_px = 8;
|
||||
tile_idx = seg_px; // middle segments (0..8 px)
|
||||
}
|
||||
|
||||
set_sprite_tile(18 + i, base_tile + tile_idx * 2);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_map(uint8_t center_x, uint8_t center_y) {
|
||||
// Fill entire map with tile index 0 (completely black empty tile)
|
||||
memset(map_buffer, 0, sizeof(map_buffer));
|
||||
@@ -198,7 +488,9 @@ static void draw_map(uint8_t center_x, uint8_t center_y) {
|
||||
}
|
||||
}
|
||||
|
||||
set_bkg_tiles(0, 0, 32, 32, map_buffer);
|
||||
update_stamina_display();
|
||||
// Optimize: only write rows 2 to 17 (16 rows total) which contain the active isometric map, avoiding full 1024-byte copy lags
|
||||
set_bkg_tiles(0, 2, 32, 16, &map_buffer[2 * 32]);
|
||||
}
|
||||
|
||||
static void update_camera(void) {
|
||||
@@ -214,12 +506,66 @@ static void update_camera(void) {
|
||||
|
||||
static void update_player_sprite(void) {
|
||||
uint8_t frame_offset = is_moving ? ((move_progress >> 2) & 1) : 0;
|
||||
move_metasprite(player_metasprites[player_dir * 2 + frame_offset], 0, 0, 88, 88);
|
||||
uint8_t y_offset = 0;
|
||||
if (is_jumping) {
|
||||
// Parabolic arc for the jump height: peak is 16 pixels at frame 8
|
||||
y_offset = (move_progress * (16 - move_progress)) >> 2;
|
||||
}
|
||||
move_metasprite(player_metasprites[player_dir * 2 + frame_offset], 0, 0, 88, 88 - y_offset);
|
||||
}
|
||||
|
||||
void title_init(void) {
|
||||
// Hide all sprites
|
||||
for (uint8_t i = 0; i < 40; i++) move_sprite(i, 0, 0);
|
||||
|
||||
// Load title background tiles
|
||||
set_bkg_data(0, title_bg_TILE_COUNT, title_bg_tiles);
|
||||
// Draw title map
|
||||
set_bkg_tiles(0, 0, title_bg_WIDTH / 8, title_bg_HEIGHT / 8, title_bg_map);
|
||||
|
||||
// Set standard palette
|
||||
BGP_REG = 0xE4;
|
||||
|
||||
// Center screen
|
||||
move_bkg(0, 0);
|
||||
|
||||
// Initialize sound registers
|
||||
NR52_REG = 0x80; // Turn on sound
|
||||
NR50_REG = 0x77; // Max master volume
|
||||
NR51_REG = 0xFF; // Enable all sound channels
|
||||
|
||||
// Start title music
|
||||
title_music_timer = 0;
|
||||
title_music_step = 0;
|
||||
remove_VBL(play_music_tick);
|
||||
add_VBL(play_music_tick);
|
||||
}
|
||||
|
||||
void title_update(uint8_t keys, uint8_t prev_keys) {
|
||||
// Only handling music and waiting for start, which is handled in main.c and play_music_tick
|
||||
}
|
||||
|
||||
void engine_init(void) {
|
||||
SPRITES_8x16;
|
||||
|
||||
// Initialize sound registers
|
||||
NR52_REG = 0x80; // Turn on sound
|
||||
NR50_REG = 0x77; // Max master volume
|
||||
NR51_REG = 0xFF; // Enable all sound channels
|
||||
|
||||
// Load native GBDK IBM font (loads starting at VRAM index 0x80)
|
||||
font_init();
|
||||
font_t ibm_font = font_load(font_ibm);
|
||||
font_set(ibm_font);
|
||||
|
||||
// Reset music step counters
|
||||
music_timer = 0;
|
||||
music_step = 0;
|
||||
gameover_music_timer = 0;
|
||||
gameover_music_step = 0;
|
||||
remove_VBL(play_music_tick);
|
||||
add_VBL(play_music_tick);
|
||||
|
||||
// Seed random number generator with hardware divider register
|
||||
initrand(DIV_REG);
|
||||
|
||||
@@ -238,18 +584,38 @@ void engine_init(void) {
|
||||
// Load tiles
|
||||
set_bkg_data(0, tiles_TILE_COUNT, tiles_tiles);
|
||||
set_sprite_data(0, player_TILE_COUNT, player_tiles);
|
||||
set_sprite_data(player_TILE_COUNT, gameover_TILE_COUNT, gameover_tiles);
|
||||
set_sprite_data(player_TILE_COUNT + gameover_TILE_COUNT, stamina_TILE_COUNT * 2, stamina_tiles);
|
||||
|
||||
// Spawn enemy at a random path tile sufficiently far from the player
|
||||
player_lx = 1;
|
||||
player_ly = 1;
|
||||
// Safety check: if (1,1) is somehow a wall, scan the maze and start at the first path tile
|
||||
if (maze[player_ly][player_lx] == 0) {
|
||||
uint8_t found = 0;
|
||||
for (uint8_t y = 1; y < MAP_SIZE - 1; y++) {
|
||||
for (uint8_t x = 1; x < MAP_SIZE - 1; x++) {
|
||||
if (maze[y][x] == 1) {
|
||||
player_lx = x;
|
||||
player_ly = y;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn enemy at a random path tile sufficiently far from the player (Chebyshev distance >= 3)
|
||||
while (1) {
|
||||
uint8_t rx = rand() % MAP_SIZE;
|
||||
uint8_t ry = rand() % MAP_SIZE;
|
||||
if (maze[ry][rx] == 1) {
|
||||
// Distance (Manhattan) to player start (1, 1)
|
||||
int8_t dx = rx - 1;
|
||||
int8_t dy = ry - 1;
|
||||
int8_t dx = (int8_t)rx - (int8_t)player_lx;
|
||||
int8_t dy = (int8_t)ry - (int8_t)player_ly;
|
||||
if (dx < 0) dx = -dx;
|
||||
if (dy < 0) dy = -dy;
|
||||
if ((dx + dy) >= 5) {
|
||||
int8_t dist = (dx > dy) ? dx : dy;
|
||||
if (dist >= 3) {
|
||||
enemy_lx = rx;
|
||||
enemy_ly = ry;
|
||||
break;
|
||||
@@ -257,8 +623,11 @@ void engine_init(void) {
|
||||
}
|
||||
}
|
||||
enemy_is_moving = 0;
|
||||
enemy_cooldown = 30;
|
||||
enemy_cooldown = 60;
|
||||
game_over = 0;
|
||||
stamina = 100;
|
||||
stamina_recharge_timer = 0;
|
||||
is_jumping = 0;
|
||||
|
||||
draw_map(player_lx, player_ly);
|
||||
update_camera();
|
||||
@@ -270,11 +639,65 @@ void engine_update(uint8_t keys, uint8_t prev_keys) {
|
||||
if (game_over_timer > 0) {
|
||||
game_over_timer--;
|
||||
if (game_over_timer == 0) {
|
||||
BGP_REG = 0xFF; // Set screen to all black to signify Game Over
|
||||
// Clear map_buffer to 0 (completely black/empty)
|
||||
memset(map_buffer, 0, sizeof(map_buffer));
|
||||
set_bkg_tiles(0, 0, 32, 32, map_buffer);
|
||||
|
||||
// Initialize gameover music step variables
|
||||
gameover_music_timer = 45;
|
||||
gameover_music_step = 0;
|
||||
|
||||
// Play a sad defeat melody / note on Channel 2
|
||||
NR21_REG = 0x80;
|
||||
NR22_REG = 0xF3; // volume 15, fade
|
||||
NR23_REG = 0x2C; // low frequency
|
||||
NR24_REG = 0x84; // trigger note
|
||||
}
|
||||
} else {
|
||||
// Render the "GAME OVER" metasprite centered horizontally (88, due to sprite X-offset) and below player (120)
|
||||
move_metasprite(gameover_metasprites[0], player_TILE_COUNT, 8, 88, 120);
|
||||
|
||||
// Keep the player sprite visible at center screen (88, 88)
|
||||
update_player_sprite();
|
||||
|
||||
// Render enemy sprite at its final screen coordinate relative to camera scroll
|
||||
int16_t enemy_px = (enemy_lx - enemy_ly) * 16 + 96;
|
||||
int16_t enemy_py = (enemy_lx + enemy_ly) * 8 + 16;
|
||||
int16_t enemy_screen_x = ((enemy_px - scroll_x) & 255) + 24;
|
||||
int16_t enemy_screen_y = ((enemy_py - scroll_y) & 255) + 16;
|
||||
|
||||
int8_t edx = (int8_t)player_lx - (int8_t)enemy_lx;
|
||||
int8_t edy = (int8_t)player_ly - (int8_t)enemy_ly;
|
||||
if (edx < 0) edx = -edx;
|
||||
if (edy < 0) edy = -edy;
|
||||
uint8_t ep_dist = (edx > edy) ? edx : edy;
|
||||
|
||||
if (ep_dist <= 2 && enemy_screen_x >= -8 && enemy_screen_x <= 168 && enemy_screen_y >= -8 && enemy_screen_y <= 152) {
|
||||
move_metasprite(enemy_metasprite, 0, 4, enemy_screen_x, enemy_screen_y);
|
||||
} else {
|
||||
move_metasprite(enemy_metasprite, 0, 4, 0, 0);
|
||||
}
|
||||
|
||||
// Check for restart via J_START
|
||||
if ((keys & J_START) && !(prev_keys & J_START)) {
|
||||
// Hide game over metasprite by moving offscreen
|
||||
move_metasprite(gameover_metasprites[0], player_TILE_COUNT, 8, 0, 0);
|
||||
engine_init();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Stamina recharge: 1 point per second (every 60 frames)
|
||||
stamina_recharge_timer++;
|
||||
if (stamina_recharge_timer >= 60) {
|
||||
stamina_recharge_timer = 0;
|
||||
if (stamina < 100) {
|
||||
stamina++;
|
||||
update_stamina_display();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (is_moving) {
|
||||
move_progress++;
|
||||
@@ -297,6 +720,7 @@ void engine_update(uint8_t keys, uint8_t prev_keys) {
|
||||
|
||||
if (move_progress == 16) {
|
||||
is_moving = 0;
|
||||
is_jumping = 0;
|
||||
player_lx = target_lx;
|
||||
player_ly = target_ly;
|
||||
|
||||
@@ -322,7 +746,7 @@ void engine_update(uint8_t keys, uint8_t prev_keys) {
|
||||
enemy_is_moving = 0;
|
||||
enemy_lx = enemy_target_lx;
|
||||
enemy_ly = enemy_target_ly;
|
||||
enemy_cooldown = 24; // Delay between steps to make the enemy move slower than the player
|
||||
enemy_cooldown = 60; // Delay between steps to make the enemy move slower than the player
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,7 +764,7 @@ void engine_update(uint8_t keys, uint8_t prev_keys) {
|
||||
int8_t abs_dy = (dy < 0) ? -dy : dy;
|
||||
int8_t dist = (abs_dx > abs_dy) ? abs_dx : abs_dy;
|
||||
|
||||
if (dist <= 4) {
|
||||
if (dist <= 2) {
|
||||
int8_t best_nx = enemy_lx;
|
||||
int8_t best_ny = enemy_ly;
|
||||
// Initialize with current squared distance so we only move if we get strictly closer
|
||||
@@ -413,8 +837,8 @@ void engine_update(uint8_t keys, uint8_t prev_keys) {
|
||||
if (edy < 0) edy = -edy;
|
||||
uint8_t ep_dist = (edx > edy) ? edx : edy;
|
||||
|
||||
// Render only if within range (<= 3) to let player see the ghost coming from the edge of fog of war
|
||||
if (ep_dist <= 3 && enemy_screen_x >= -8 && enemy_screen_x <= 168 && enemy_screen_y >= -8 && enemy_screen_y <= 152) {
|
||||
// Render only if within range (<= 2) to let player see the ghost when visible in fog of war
|
||||
if (ep_dist <= 2 && enemy_screen_x >= -8 && enemy_screen_x <= 168 && enemy_screen_y >= -8 && enemy_screen_y <= 152) {
|
||||
move_metasprite(enemy_metasprite, 0, 4, enemy_screen_x, enemy_screen_y); // Sprite index offset 4
|
||||
} else {
|
||||
move_metasprite(enemy_metasprite, 0, 4, 0, 0); // Hide offscreen
|
||||
@@ -432,7 +856,14 @@ void engine_update(uint8_t keys, uint8_t prev_keys) {
|
||||
|
||||
if (dx_collision < 12 && dy_collision < 6) {
|
||||
game_over = 1;
|
||||
game_over_timer = 30; // 30 frames delay (0.5 seconds) to allow player to see the overlap
|
||||
game_over_timer = 45; // 45 frames delay
|
||||
update_stamina_display(); // Hide stamina immediately
|
||||
// Play defeat sweep SFX on Channel 1 (slide pitch down)
|
||||
NR10_REG = 0x1E; // sweep register: shift down
|
||||
NR11_REG = 0x10; // 25% duty cycle
|
||||
NR12_REG = 0xF3; // initial volume 15, envelope decrease
|
||||
NR13_REG = 0x00; // low frequency bits
|
||||
NR14_REG = 0xC6; // trigger note, frequency high bits
|
||||
}
|
||||
|
||||
uint8_t keys_pressed = keys & ~prev_keys;
|
||||
@@ -470,32 +901,71 @@ void engine_update(uint8_t keys, uint8_t prev_keys) {
|
||||
}
|
||||
|
||||
if (move_lx != 0 || move_ly != 0) {
|
||||
int8_t new_lx = player_lx + move_lx;
|
||||
int8_t new_ly = player_ly + move_ly;
|
||||
// If J_A is held down, attempt to jump over a wall
|
||||
if (keys & J_A) {
|
||||
int8_t land_lx = player_lx + move_lx * 2;
|
||||
int8_t land_ly = player_ly + move_ly * 2;
|
||||
|
||||
// Bounds check for intermediate and landing tiles
|
||||
if (land_lx >= 0 && land_lx < MAP_SIZE && land_ly >= 0 && land_ly < MAP_SIZE) {
|
||||
// intermediate tile must be a wall (0), landing tile must be a path (1), and we need >= 60 stamina
|
||||
if (maze[player_ly + move_ly][player_lx + move_lx] == 0 && maze[land_ly][land_lx] == 1 && stamina >= 60) {
|
||||
is_moving = 1;
|
||||
is_jumping = 1;
|
||||
move_progress = 0;
|
||||
stamina -= 60;
|
||||
|
||||
start_lx = player_lx;
|
||||
start_ly = player_ly;
|
||||
target_lx = land_lx;
|
||||
target_ly = land_ly;
|
||||
|
||||
start_px = (start_lx - start_ly) * 16 + 96;
|
||||
start_py = (start_lx + start_ly) * 8 + 16;
|
||||
target_px = (target_lx - target_ly) * 16 + 96;
|
||||
target_py = (target_lx + target_ly) * 8 + 16;
|
||||
|
||||
// Play jump SFX (sweep up on Channel 1)
|
||||
NR10_REG = 0x15; // sweep up
|
||||
NR11_REG = 0x80; // 50% duty cycle
|
||||
NR12_REG = 0xF3; // volume 15, fade
|
||||
NR13_REG = 0x00; // low frequency bits
|
||||
NR14_REG = 0xC3; // trigger note, frequency high bits
|
||||
|
||||
update_stamina_display();
|
||||
update_player_sprite();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If jump checks fail, just face direction
|
||||
update_player_sprite();
|
||||
} else {
|
||||
// Normal walk
|
||||
int8_t new_lx = player_lx + move_lx;
|
||||
int8_t new_ly = player_ly + move_ly;
|
||||
|
||||
// Bounds check & wall/empty area collision check
|
||||
if (new_lx >= 0 && new_lx < MAP_SIZE && new_ly >= 0 && new_ly < MAP_SIZE) {
|
||||
if (maze[new_ly][new_lx] == 1) {
|
||||
is_moving = 1;
|
||||
move_progress = 0;
|
||||
start_lx = player_lx;
|
||||
start_ly = player_ly;
|
||||
target_lx = new_lx;
|
||||
target_ly = new_ly;
|
||||
|
||||
start_px = (start_lx - start_ly) * 16 + 96;
|
||||
start_py = (start_lx + start_ly) * 8 + 16;
|
||||
target_px = (target_lx - target_ly) * 16 + 96;
|
||||
target_py = (target_lx + target_ly) * 8 + 16;
|
||||
|
||||
update_player_sprite();
|
||||
// Bounds check & wall/empty area collision check
|
||||
if (new_lx >= 0 && new_lx < MAP_SIZE && new_ly >= 0 && new_ly < MAP_SIZE) {
|
||||
if (maze[new_ly][new_lx] == 1) {
|
||||
is_moving = 1;
|
||||
move_progress = 0;
|
||||
start_lx = player_lx;
|
||||
start_ly = player_ly;
|
||||
target_lx = new_lx;
|
||||
target_ly = new_ly;
|
||||
|
||||
start_px = (start_lx - start_ly) * 16 + 96;
|
||||
start_py = (start_lx + start_ly) * 8 + 16;
|
||||
target_px = (target_lx - target_ly) * 16 + 96;
|
||||
target_py = (target_lx + target_ly) * 8 + 16;
|
||||
|
||||
update_player_sprite();
|
||||
} else {
|
||||
update_player_sprite();
|
||||
}
|
||||
} else {
|
||||
// If collision blocks, just update direction look frame
|
||||
update_player_sprite();
|
||||
}
|
||||
} else {
|
||||
// Out of bounds: update direction look frame
|
||||
update_player_sprite();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user