From 2fbdf974338bde8576efdae40a819a76b2391033 Mon Sep 17 00:00:00 2001 From: "Juan J. Martinez" Date: Sun, 5 Nov 2023 11:22:55 +0000 Subject: Initial import of the open source release --- src/main.c | 754 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 754 insertions(+) create mode 100644 src/main.c (limited to 'src/main.c') diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..d344e9b --- /dev/null +++ b/src/main.c @@ -0,0 +1,754 @@ +/* + Kitsune's Curse + Copyright (C) 2020-2023 Juan J. Martinez + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +#include +#include +#include + +#include "aplib.h" +#include "cpcrslib/cpcrslib.h" +#include "plw.h" +#include "sound.h" +#include "splib.h" +#include "stage.h" +#include "maps.h" +#include "entities.h" + +// generated +#include "palette.h" +#include "menubg.h" +#include "player.h" +#include "items.h" +#include "tiles.h" +#include "songs_pak.h" + +#include "et_effect.h" +#include "et_pickups.h" +#include "et_player.h" +#include "et_door.h" +#include "et_platform.h" +#include "et_switch.h" +#include "et_spirit.h" +#include "et_flame.h" +#include "et_vampire.h" +#include "et_oni.h" +#include "et_ninja.h" +#include "et_spider.h" +#include "et_demon.h" +#include "et_cloud.h" + +#define LOCAL +#include "main.h" + +#include "int.h" + +#define MAX_KEYS 6 + +uint8_t joystick; +const uint16_t key_map[2][MAX_KEYS] = { + // right, left, up, down, fire, pause + { 0x4002, 0x4101, 0x4001, 0x4004, 0x4580, 0x4308 }, // keyboard + { 0x4908, 0x4904, 0x4901, 0x4902, 0x4910, 0x4308 } // joystick +}; +const char redefine[MAX_KEYS][6] = { + "RIGHT", "LEFT ", "UP ", "DOWN ", "FIRE ", "PAUSE" +}; + +static uint8_t *buffer; + +void _pak_song(uint8_t n) +{ + buffer = (uint8_t *)BUFF_ADDR; + aplib_uncompress(buffer, songs_pak); + PLW_Init(buffer, n); +} + +void _menu_bg() +{ + buffer = (uint8_t *)get_tile_xy(0, 0); + aplib_uncompress(buffer, menubg); + cpc_PutSp(buffer, 50, 44, SCREEN_ADDR(18, 0)); +} + +void screen_black() +{ + /* + uint8_t i; + + wait_int6(); + + // all black + for (i = 0; i < 16; i++) + set_hw_ink(i, 0x54); + */ + + // *INDENT-OFF* + __asm; + + call _wait_int6 + + ld hl, #0x540f + +screen_black_loop: + push hl + push hl + call _set_hw_ink + pop af + pop hl + dec l + jr nz, screen_black_loop + + __endasm; + // *INDENT-ON* +} + +void set_colors() +{ + uint8_t c; + + wait_int6(); + + for (c = 0; c < 16; c++) + set_hw_ink(c, pal_hw[c]); + + // black + set_hw_border(0x54); +} + + +const uint8_t fade_in_c[] = { 0, 1, 8, 12, 14 }; + +void fade_in() +{ + uint8_t c, i; + + for (i = 0; i < 5; i++) + { + wait_int6(); + for (c = 1; c < 16; c++) + set_hw_ink(c, pal_hw[fade_in_c[i]]); + wait(); + } + + set_colors(); +} + +void draw_controls() +{ + if (joystick) + set_text_ink(15, 14, 11); + else + set_text_ink(13, 8, 2); + put_text("1 JOYSTICK", 30, 67); + + if (joystick) + set_text_ink(13, 8, 2); + else + set_text_ink(15, 14, 11); + put_text("2 KEYBOARD", 30, 77); + + set_text_ink(13, 8, 2); + put_text("3 REDEFINE", 30, 87); +} + +void map_keys() +{ + uint8_t i; + + for (i = 0; i < MAX_KEYS; i++) + cpc_AssignKey(i, key_map[joystick][i]); +} + +void run_redefine() +{ + uint8_t i; + + // we need more space than is available in the main buffer, so use the + // st_tile array that is not used here and is ~2800 bytes currently + buffer = (uint8_t *)get_tile_xy(0, 0); + + // clear the area + memset(buffer, 0, 30 * 80); + + wait_int6(); + + cpc_PutSp(buffer, 30, 80, SCREEN_ADDR(0, 65)); + cpc_PutSp(buffer, 10, 80, SCREEN_ADDR(0, 110)); + + // be sure the keyboard is free + while (cpc_AnyKeyPressed()) + wait(); + + set_text_ink(13, 8, 2); + put_text("PRESS KEY FOR:", 20, 85); + + set_text_ink(15, 15, 14); + + // clean existing keys + for (i = 0; i < MAX_KEYS; i++) + cpc_AssignKey(i, (int)0xffff); + + for (i = 0; i < MAX_KEYS; i++) + { + wait_int6(); + put_text(redefine[i], 50, 85); + cpc_RedefineKey(i); + } + + // clear the area + cpc_PutSp(buffer, 10, 80, SCREEN_ADDR(0, 85)); + + // be sure the keyboard is free + while (cpc_AnyKeyPressed()) + wait(); +} + +void run_intro() +{ + + screen_black(); + clear_screen(); + + set_text_ink(3, 5, 11); + put_text("WHAT IS YOUR TRUE NATURE, KITSUNE?", 6, 90); + + fade_in(); + + buffer = (uint8_t *)BUFF_ADDR; + aplib_uncompress(buffer, songs_pak); + PLW_Init(buffer, SONG_INTRO); + wait_for(82); +} + +void run_gameover() +{ + PLW_Init(songs, SONG_SILENCE); + + screen_black(); + clear_screen(); + set_colors(); + + set_text_ink(3, 5, 11); + wait_int6(); + put_text("GAME", 30, 90); + put_text("OVER", 42, 90); + + if (gtail) + { + cpc_PutMaskSp(items[0], 16, 4, SCREEN_ADDR(35, 102)); + // we can use wmap as buffer, the map is not in use + wmap[0] = '*'; + pad_numbers(wmap + 1, 2, gems); + put_text(wmap, 38, 105); + } + + _pak_song(SONG_GAMEOVER); + wait_for(112); +} + +const uint8_t * const ranks[5] = { + "SAGE DRAGON", + "BRAVE TIGER", + "SILENT SNAKE", + "DRUNK MONKEY", + "LITTLE CRICKET" +}; + +void run_endgame() +{ + uint16_t clock = get_clock(); + uint8_t m, s; + + PLW_Init(songs, SONG_SILENCE); + + screen_black(); + clear_screen(); + + _menu_bg(); + + init_tiles(); + validate_screen(); + put_tile(bgtiles[48], get_tile_xy(0, 10)); + put_tile(bgtiles[49], get_tile_xy(0, 11)); + put_tile(bgtiles[50], get_tile_xy(0, 12)); + update_screen(); + + m = clock / 60; + s = clock - m * 60; + + set_text_ink(15, 14, 11); + put_text("CONGRATULATIONS!", 6, 70); + + set_text_ink(15, 13, 8); + put_text("THE CURSE IS BROKEN AND KITSUNE HAS", 6, 85); + put_text("NOW THE GOLDEN TAIL. HAPPY ENDING!", 6, 95); + + set_text_ink(5, 3, 4); + put_text("TIME:", 6, 118); + put_text("KY[:", 8, 128); + + // check max range + if (clock == MAX_CLOCK) + { + set_text_ink(6, 5, 3); + put_text("TOO SLOW!", 18, 118); + } + else + { + set_text_ink(13, 8, 2); + // we can use wmap as buffer, the map is not in use + pad_numbers(wmap, 2, m); + put_text(wmap, 18, 118); + wmap[0] = ':'; + pad_numbers(wmap + 1, 2, s); + put_text(wmap, 22, 118); + } + + for (it_k = 0, s = 25; it_k < 4; ++it_k) + { + if (m < s) + break; + s += 5; + } + put_text(ranks[it_k], 18, 128); + + fade_in(); + + _pak_song(SONG_MENU); + + wait_for(0); + wait_for(0); + wait_for(249); +} + +void draw_menu() +{ + screen_black(); + clear_screen(); + + _menu_bg(); + + draw_controls(); + + set_text_ink(5, 3, 4); + put_text("CODE, GRAPHICS & SOUND", 18, 135); + + set_text_ink(15, 6, 5); + put_text("JUAN J. MARTINEZ", 24, 145); + +#ifdef DEBUG + set_text_ink(15, 15, 15); + put_text(VERSION, 0, 175); +#endif + + set_text_ink(15, 13, 8); + put_text("\x1f""2020 USEBOX.NET", 24, 175); + + fade_in(); +} + +void draw_hud_bg() +{ + // lives + cpc_PutMaskSp(player[1] + 8, 10, 4, SCREEN_ADDR(2, 10)); + + // keys + cpc_PutMaskSp(items[1], 16, 4, SCREEN_ADDR(59, 8)); + + // gems + if (gtail) + cpc_PutMaskSp(items[0], 16, 4, SCREEN_ADDR(68, 8)); + + set_text_ink(14, 11, 3); + put_text("KITSUNE'S", 25, 12); + put_text("CURSE", 45, 12); +} + +void draw_hud() +{ + uint8_t i; + char b[4]; + + wait_int6(); + + b[0] = '='; + b[1] = 0; + set_text_ink(6, 5, 3); + for (i = 0; i < MAX_LIFE; i++) + { + if (life == i) + set_text_ink(2, 1, 1); + + put_text(b, 6 + i * 2, 12); + } + + set_text_ink(14, 12, 7); + b[0] = '*'; + pad_numbers(b + 1, 1, lives); + put_text(b, 16, 12); + + pad_numbers(b + 1, 1, keys); + put_text(b, 62, 12); + + if (gtail) + { + pad_numbers(b + 1, 2, gems); + put_text(b, 71, 12); + } +} + +void init_persistence() +{ + memset(&jump_flag, 0, ZERO_ALL); + memset(persistence, 0, PERSISTENCE_LEN); + + lives = 3; + life = MAX_LIFE; + opx = px = START_X; + opy = py = START_Y; + dir = DIR_RIGHT; + frame = WALK; + + init_tiles(); + init_map(START_MAP); + + // after setting cmap + player_checkpoint(); +} + +void update_persistence(uint8_t id) +{ + persistence[id >> 3] |= (1 << (id & 7)); +} + +uint8_t check_persistence(uint8_t id) +{ + return (persistence[id >> 3] & (1 << (id & 7))); +} + +void fill_map_row(const uint8_t *ent) +{ + uint8_t t, x, y, w; + + t = ent[1]; + x = ent[2] >> 3; + y = ent[3] >> 3; + w = ent[4]; + + while (w--) + set_map_tile(x++, y, t); +} + +// init table: update, draw +static void (* const init[])() = { + update_door, draw_door, + update_platform, draw_platform, + update_spirit, draw_spirit, + update_flame, draw_flame, + update_vampire, draw_vampire, + update_oni, draw_oni, + update_ninja, draw_ninja, + update_spider, draw_spider, + update_demon, draw_demon, + update_cloud, draw_cloud, + update_tile_anim, draw_torch, + update_switch, draw_switch, + update_pickup, draw_pickup, + update_pickup, draw_pickup, + update_pickup, draw_pickup, + update_gtail, draw_gtail, +}; + +void spawn_entities(const uint8_t *ents) +{ + uint8_t typ; + + // ents CAN'T BE NULL + while (*ents != 0xff) + { + // not really entities + if (*ents == ET_FILL) + { + fill_map_row(ents); + ents += 1; + goto next_et; + } + if ((*ents & 127) == ET_LINK) + { + // down + if (*ents & 128) + { + exit_down = ents[1]; + offset_down = ents[2]; + } + else + { + exit_up = ents[1]; + offset_up = ents[2]; + } + goto next_et; + } + + if (ents[1] != 255) + { + if (check_persistence(ents[1]) + || (!gtail && *ents == ET_GEM)) + goto next_et; + } + + if (!new_entity()) + // unlikely + return; + + // init to 0, skip *n + memset(sp_new, 0, sizeof(struct st_entity) - 2); + + typ = *ents & 127; + + sp_new->type = typ; + sp_new->param = ents[0] & 128; + sp_new->id = ents[1]; + sp_new->x = sp_new->ox = ents[2]; + sp_new->y = sp_new->oy = ents[3]; + + memcpy(&(sp_new->update), &init[(typ - ET_FIRST) << 1], 4); + + switch (sp_new->type) + { + case ET_DOOR: + if (sp_new->param) + sp_new->frame = 1; + break; + case ET_SPIDER: + sp_new->param = 0; + sp_new->extra = ents[4] - TH; + ++ents; + break; + case ET_SPIRIT: + case ET_FLAME: + // distance + sp_new->extra = ents[4]; + ++ents; + break; + case ET_VAMPIRE: + // start waiting + sp_new->frame = 4; + break; + case ET_ONI: + // speed + sp_new->extra = 2; + break; + case ET_DEMON: + // fire early + sp_new->extra = 64; + break; + case ET_PLATFORM: + if (ts == (1 << 4)) + sp_new->frame = 1; + sp_new->extra = ents[4]; + ++ents; + break; + case ET_TORCH: + // x and y expected unit is tiles + sp_new->x = ents[2] >> 3; + sp_new->y = ents[3] >> 3; + break; + case ET_SWITCH: + // x, y of the door; in tiles + sp_new->param = ents[4]; + sp_new->extra = ents[5]; + update_switch_door(ents[4], ents[5], SWITCH_DOOR_TILE); + ents += 2; + break; + } + +next_et: + ents += 4; + } +} + +void run_game() +{ + PLW_Init(songs, SONG_SILENCE); + + screen_black(); + clear_screen(); + + init_persistence(); + + draw_entities(); + update_screen(); + + draw_hud_bg(); + draw_hud(); + + set_colors(); + + reset_clock(); + + PLW_Init(songs, SONG_INGAME); + + while(1) + { + cpc_ScanKeyboard(); + + if (cpc_TestKeyF(KEY_PAUSE)) + { + if (paused) + { + invalidate_screen(); + draw_entities(); + update_screen(); + paused = 0; + resume_player(); + } + else + { + set_text_ink(15, 15, 13); + wait_int6(); + put_text("GAME", 28, 85); + put_text("PAUSED", 40, 85); + paused = 1; + pause_player(); + PLW_InitSoundEffects(effects); + } + + // be sure the keyboard is free + while (cpc_AnyKeyPressed()) + wait(); + } + + if (paused) + continue; + + if (cpc_TestKeyF(KEY_QUIT)) + { + PLW_Init(songs, SONG_SILENCE); + break; + } + + if (gtail && gems == MAX_GEMS && !gameover_delay) + { + run_endgame(); + run_gameover(); + return; + } + + if (gameover_delay) + { + gameover_delay--; + if (!gameover_delay) + { + run_gameover(); + return; + } + } + + update_entities(); + draw_entities(); + + wait(); + update_screen(); + } +} + +void main(void) +{ + uint8_t delay = 0, kdelay = 1; + + // before setting up the int! + PLW_InitSoundEffects(effects); + PLW_Init(songs, SONG_SILENCE); + + // init with wait at 16.6 FPS + setup_int(3); + + set_colors(); + + run_intro(); + + // defaults to joystick + joystick = 1; + map_keys(); + cpc_AssignKey(KEY_QUIT, 0x4804); // ESC + cpc_AssignKey(8, 0x4801); // 1 + cpc_AssignKey(9, 0x4802); // 2 + cpc_AssignKey(10, 0x4702); // 3 + +back_to_menu: + + draw_menu(); + + _pak_song(SONG_MENU); + + while (1) + { + cpc_ScanKeyboard(); + + if (!cpc_AnyKeyPressed()) + kdelay = 0; + + if (!kdelay) + { + if ((cpc_TestKeyF(KEY_1) && !joystick) + || (cpc_TestKeyF(KEY_2) && joystick)) + { + joystick = !joystick; + draw_controls(); + + kdelay = 1; + + map_keys(); + continue; + } + + if (cpc_TestKeyF(KEY_3) && !cpc_TestKeyF(8)) + { + joystick = 0; + + run_redefine(); + + draw_controls(); + continue; + } + + if (cpc_TestKey(KEY_FIRE)) + { + run_game(); + + kdelay = 1; + goto back_to_menu; + } + } + + delay += 0x10; + if (delay == 0x30) + { + set_text_ink(14, 12, 7); + goto fire_to_play; + } + if (!delay) + { + set_text_ink(0, 0, 0); +fire_to_play: + wait_int6(); + put_text("FIRE TO PLAY", 28, 110); + } + + wait(); + } +} -- cgit v1.2.3