aboutsummaryrefslogtreecommitdiff
path: root/src/main.c
diff options
context:
space:
mode:
authorJuan J. Martinez <jjm@usebox.net>2023-11-05 11:22:55 +0000
committerJuan J. Martinez <jjm@usebox.net>2023-11-05 11:31:28 +0000
commit2fbdf974338bde8576efdae40a819a76b2391033 (patch)
tree64d41a37470143f142344f9a439d96de3e7918c2 /src/main.c
downloadkitsunes-curse-2fbdf974338bde8576efdae40a819a76b2391033.tar.gz
kitsunes-curse-2fbdf974338bde8576efdae40a819a76b2391033.zip
Initial import of the open source release
Diffstat (limited to 'src/main.c')
-rw-r--r--src/main.c754
1 files changed, 754 insertions, 0 deletions
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 <jjm@usebox.net>
+
+ 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 <https://www.gnu.org/licenses/>.
+ */
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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();
+ }
+}