/* 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(); } }