aboutsummaryrefslogtreecommitdiff
path: root/src
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
downloadkitsunes-curse-2fbdf974338bde8576efdae40a819a76b2391033.tar.gz
kitsunes-curse-2fbdf974338bde8576efdae40a819a76b2391033.zip
Initial import of the open source release
Diffstat (limited to 'src')
-rw-r--r--src/Makefile107
-rw-r--r--src/cpcfirm.inc227
-rw-r--r--src/crt0.s37
-rw-r--r--src/data.c22
-rw-r--r--src/data.h45
-rw-r--r--src/effects.z80351
-rw-r--r--src/entities.c534
-rw-r--r--src/entities.h70
-rw-r--r--src/et_cloud.c82
-rw-r--r--src/et_cloud.h25
-rw-r--r--src/et_common.c39
-rw-r--r--src/et_common.h24
-rw-r--r--src/et_config.h70
-rw-r--r--src/et_demon.c156
-rw-r--r--src/et_demon.h28
-rw-r--r--src/et_door.c56
-rw-r--r--src/et_door.h25
-rw-r--r--src/et_effect.c125
-rw-r--r--src/et_effect.h35
-rw-r--r--src/et_flame.c55
-rw-r--r--src/et_flame.h25
-rw-r--r--src/et_ninja.c127
-rw-r--r--src/et_ninja.h25
-rw-r--r--src/et_oni.c84
-rw-r--r--src/et_oni.h25
-rw-r--r--src/et_pickups.c191
-rw-r--r--src/et_pickups.h28
-rw-r--r--src/et_platform.c75
-rw-r--r--src/et_platform.h25
-rw-r--r--src/et_player.c551
-rw-r--r--src/et_player.h65
-rw-r--r--src/et_spider.c102
-rw-r--r--src/et_spider.h25
-rw-r--r--src/et_spirit.c55
-rw-r--r--src/et_spirit.h25
-rw-r--r--src/et_switch.c103
-rw-r--r--src/et_switch.h27
-rw-r--r--src/et_vampire.c83
-rw-r--r--src/et_vampire.h25
-rw-r--r--src/int.h45
-rw-r--r--src/int.z80262
-rw-r--r--src/loader.s252
-rw-r--r--src/main.c754
-rw-r--r--src/main.h63
-rw-r--r--src/maps.c239
-rw-r--r--src/maps.h66
-rw-r--r--src/songs.z801421
-rw-r--r--src/sound.h57
-rw-r--r--src/sound.z808
-rw-r--r--src/splib.c2031
-rw-r--r--src/splib.h82
-rw-r--r--src/turboload.s250
52 files changed, 9309 insertions, 0 deletions
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..eec64ea
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,107 @@
+TARGET=kitcurs
+
+LOADER_ADDR=512
+TMP_ADDR=3072
+
+# see splib.h: BUF_ADDR + (TW * TH / 2) * (sprite th * 2 + 2) * max sprites
+# 0x0100 + (8 * 8 / 2) * (3 * 2 + 2) * 10
+APP_ADDR=2816
+PAK_SLOP=32
+
+LOADER_ADDR_HEX=$(shell printf "%x" $(LOADER_ADDR))
+TMP_ADDR_HEX=$(shell printf "%x" $(TMP_ADDR))
+
+CC=sdcc
+AS=sdasz80
+AR=sdcclib
+CFLAGS=-mz80 --Werror -I../lib -I../generated --fsigned-char --std-sdcc99 --opt-code-speed
+LDFLAGS=-L../lib -L. --data-loc 0 --no-std-crt0 --fomit-frame-pointer
+
+OUTPUT = ../build
+OBJS = $(patsubst %.c,$(OUTPUT)/%.rel,$(wildcard *.c)) $(OUTPUT)/int.rel $(OUTPUT)/sound.rel
+LIBS = ../lib/cpcrslib/*.lib ../lib/plw.lib ../lib/aplib.lib
+
+all: CFLAGS := $(CFLAGS) #-DDEBUG -DFENCE_DEBUG -DET_DEBUG
+all: $(OUTPUT)/$(TARGET).dsk $(OUTPUT)/$(TARGET).cdt
+ chksize $(APP_ADDR) $(OUTPUT)/main.map
+
+release: CFLAGS := $(CFLAGS) --max-allocs-per-node 2000000
+release: $(OUTPUT)/$(TARGET).dsk $(OUTPUT)/$(TARGET).cdt
+ test -d release || mkdir release
+ cp $(OUTPUT)/$(TARGET).dsk $(OUTPUT)/$(TARGET).cdt release
+ chksize $(APP_ADDR) $(OUTPUT)/main.map
+
+cpce: all
+ cpce $(OUTPUT)/$(TARGET).dsk
+
+cpcec: all
+ cpcec $(OUTPUT)/$(TARGET).dsk
+
+winape: all
+ winape $(OUTPUT)/$(TARGET).dsk
+
+clk: all
+ clk $(OUTPUT)/$(TARGET).dsk
+
+rvm: all
+ rvm -b=cpc6128 -w -p -ns -i $(shell realpath $(OUTPUT)/$(TARGET).dsk) -c='run"$(TARGET)\n'
+
+$(OUTPUT)/%.rel: %.c
+ $(CC) $(CFLAGS) $(LDFLAGS) -c $< -o $@
+
+$(OUTPUT)/%.rel: %.z80
+ $(AS) -g -o $@ $<
+
+$(OUTPUT)/sound.rel: sound.z80 effects.z80 songs.z80
+
+$(OUTPUT)/main.ap: crt0.s $(wildcard *.h) $(LIBS) $(OBJS) ../lib/plw_player.rel
+ rm -f $(OUTPUT)/main.map
+ $(AS) -g -o $(OUTPUT)/crt0.rel crt0.s
+ $(CC) $(CFLAGS) $(LDFLAGS) -laplib -lcpcrslib -lplw --code-loc $(APP_ADDR) $(OUTPUT)/crt0.rel ../lib/plw_player.rel $(OBJS) -o $(OUTPUT)/main.ihx
+ hex2bin -p 00 $(OUTPUT)/main.ihx
+ apultra -w 8192 -v $(OUTPUT)/main.bin $@
+
+$(OUTPUT)/loader.bin: loader.s turboload.s $(OUTPUT)/loading.bin $(OUTPUT)/main.ap
+ echo "DISK = 1" > $(OUTPUT)/loader.opt
+ echo "APP_EP = 0x$(shell awk ' /_main_init/ { print $$1 } ' $(OUTPUT)/main.map)" >> $(OUTPUT)/loader.opt
+ echo "TMP_ADDR = $(TMP_ADDR)" >> $(OUTPUT)/loader.opt
+ echo "SCRX_SIZE = $(shell stat -c '%s' $(OUTPUT)/loading.bin)" >> $(OUTPUT)/loader.opt
+ echo "APP_ADDR = $(APP_ADDR)" >> $(OUTPUT)/loader.opt
+ echo "APP_SIZE = $(shell stat -c '%s' $(OUTPUT)/main.bin)" >> $(OUTPUT)/loader.opt
+ echo "APP_SIZE_PAK = $(shell stat -c '%s' $(OUTPUT)/main.ap)" >> $(OUTPUT)/loader.opt
+ echo "LOADER_ADDR = $(LOADER_ADDR)" >> $(OUTPUT)/loader.opt
+ echo "PAK_SLOP = $(PAK_SLOP)" >> $(OUTPUT)/loader.opt
+ $(AS) -g -o $(OUTPUT)/loader.rel $<
+ $(CC) $(CFLAGS) $(LDFLAGS) --code-loc $(LOADER_ADDR) -laplib -o $(OUTPUT)/loader.ihx $(OUTPUT)/loader.rel
+ hex2bin -p 00 $(OUTPUT)/loader.ihx
+ echo "DISK = 0" >> $(OUTPUT)/loader.opt
+ $(AS) -g -o $(OUTPUT)/loader.rel $<
+ $(CC) $(CFLAGS) $(LDFLAGS) --code-loc $(LOADER_ADDR) -laplib -o $(OUTPUT)/loader_disk.ihx $(OUTPUT)/loader.rel
+ hex2bin -p 00 $(OUTPUT)/loader_disk.ihx
+
+$(OUTPUT)/$(TARGET).dsk: $(OUTPUT)/loader.bin
+ cp $(OUTPUT)/loader_disk.bin $(OUTPUT)/$(TARGET)
+ idsk $@ -n -t 1 -i $(OUTPUT)/$(TARGET) -e $(LOADER_ADDR_HEX) -c $(LOADER_ADDR_HEX) > /dev/null
+ rm -f $(OUTPUT)/$(TARGET)
+ cp $(OUTPUT)/loading.bin $(OUTPUT)/main.bi0
+ idsk $@ -t 1 -i $(OUTPUT)/main.bi0 -c $(TMP_ADDR_HEX) -s > /dev/null
+ rm -f $(OUTPUT)/main.bi0
+ cp $(OUTPUT)/main.ap $(OUTPUT)/main.bi1
+ idsk $@ -t 1 -i $(OUTPUT)/main.bi1 -c $(shell printf "%x" $(shell expr $(LOADER_ADDR) + 1024)) -s > /dev/null
+ rm -f $(OUTPUT)/main.bi1
+
+$(OUTPUT)/$(TARGET).cdt: $(OUTPUT)/loader.bin
+ 2cdt -s 0 -n -X $(LOADER_ADDR) -L $(LOADER_ADDR) -r $(TARGET) $< $@ > /dev/null
+ 2cdt -m 2 $(OUTPUT)/loading.bin $@ > /dev/null
+ 2cdt -m 2 $(OUTPUT)/main.ap $@ > /dev/null
+
+.PHONY: clean all cleanall release
+clean:
+ rm -f $(OUTPUT)/*
+
+cleanall:
+ make clean
+ make -C ../tools clean
+ make -C ../lib clean
+
+include Makefile.deps
diff --git a/src/cpcfirm.inc b/src/cpcfirm.inc
new file mode 100644
index 0000000..88a9f1f
--- /dev/null
+++ b/src/cpcfirm.inc
@@ -0,0 +1,227 @@
+kl_probe_rom = 0xb915
+kl_choke_off = 0xbcc8
+kl_rom_walk = 0xbccb
+kl_init_back = 0xbcce
+kl_log_ext = 0xbcd1
+kl_find_command = 0xbcd4
+kl_new_framefly = 0xbcd7
+kl_add_framefly = 0xbcda
+kl_del_framefly = 0xbcdd
+kl_new_fast_ticker = 0xbce0
+kl_add_fast_ticker = 0xbce3
+kl_del_fast_ticker = 0xbce6
+kl_add_ticker = 0xbce9
+kl_del_ticker = 0xbcec
+kl_init_event = 0xbcef
+kl_event = 0xbcf2
+kl_sync_reset = 0xbcf5
+kl_del_synchronous = 0xbcf8
+kl_next_sync = 0xbcfb
+kl_do_sync = 0xbcfe
+kl_done_sync = 0xbd01
+kl_event_disable = 0xbd04
+kl_event_enable = 0xbd07
+kl_disarm_event = 0xbd0a
+kl_time_please = 0xbd0d
+kl_time_set = 0xbd10
+
+km_initialise = 0xbb00
+km_reset = 0xbb03
+km_wait_char = 0xbb06
+km_read_char = 0xbb09
+km_char_return = 0xbb0c
+km_set_expand = 0xbb0f
+km_get_expand = 0xbb12
+km_exp_buffer = 0xbb15
+km_wait_key = 0xbb18
+km_read_key = 0xbb1b
+km_test_key = 0xbb1e
+km_get_state = 0xbb21
+km_get_joystick = 0xbb24
+km_set_translate = 0xbb27
+km_get_translate = 0xbb2a
+km_set_shift = 0xbb2d
+km_get_shift = 0xbb30
+km_set_control = 0xbb33
+km_get_control = 0xbb36
+km_set_repeat = 0xbb39
+km_get_repeat = 0xbb3c
+km_set_delay = 0xbb3f
+km_get_delay = 0xbb42
+km_arm_break = 0xbb45
+km_disarm_break = 0xbb48
+km_break_event = 0xbb4b
+
+txt_initialise = 0xbb4e
+txt_reset = 0xbb51
+txt_vdu_enable = 0xbb54
+txt_vdu_disable = 0xbb57
+txt_output = 0xbb5a
+txt_wr_char = 0xbb5d
+txt_rd_char = 0xbb60
+txt_set_graphic = 0xbb63
+txt_win_enable = 0xbb66
+txt_get_window = 0xbb69
+txt_clear_window = 0xbb6c
+txt_set_column = 0xbb6f
+txt_set_row = 0xbb72
+txt_set_cursor = 0xbb75
+txt_get_cursor = 0xbb78
+txt_cur_enable = 0xbb7b
+txt_cur_disable = 0xbb7e
+txt_cur_on = 0xbb81
+txt_cur_off = 0xbb84
+txt_validate = 0xbb87
+txt_place_cursor = 0xbb8a
+txt_remove_cursor = 0xbb8d
+txt_set_pen = 0xbb90
+txt_get_pen = 0xbb93
+txt_set_paper = 0xbb96
+txt_get_paper = 0xbb99
+txt_inverse = 0xbb9c
+txt_set_back = 0xbb9f
+txt_get_back = 0xbba2
+txt_get_matrix = 0xbba5
+txt_set_matrix = 0xbba8
+txt_set_m_table = 0xbbab
+txt_get_m_table = 0xbbae
+txt_get_controls = 0xbbb1
+txt_str_select = 0xbbb4
+txt_swap_streams = 0xbbb7
+
+gra_initialise = 0xbbba
+gra_reset = 0xbbbd
+gra_move_absolute = 0xbbc0
+gra_move_relative = 0xbbc3
+gra_ask_cursor = 0xbbc6
+gra_set_origin = 0xbbc9
+gra_get_origin = 0xbbcc
+gra_win_width = 0xbbcf
+gra_win_height = 0xbbd2
+gra_get_w_width = 0xbbd5
+gra_get_w_height = 0xbbd8
+gra_clear_window = 0xbbdb
+gra_set_pen = 0xbbde
+gra_get_pen = 0xbbe1
+gra_set_paper = 0xbbe4
+gra_get_paper = 0xbbe7
+gra_plot_absolute = 0xbbea
+gra_plot_relative = 0xbbed
+gra_test_absolute = 0xbbf0
+gra_test_relative = 0xbbf3
+gra_line_absolute = 0xbbf6
+gra_line_relative = 0xbbf9
+gra_wr_char = 0xbbfc
+
+
+scr_initialise = 0xbbff
+scr_reset = 0xbc02
+scr_set_offset = 0xbc05
+scr_set_base = 0xbc08
+scr_get_location = 0xbc0b
+scr_set_mode = 0xbc0e
+scr_get_mode = 0xbc11
+scr_clear = 0xbc14
+scr_char_limits = 0xbc17
+scr_char_position = 0xbc1a
+scr_dot_position = 0xbc1d
+scr_next_byte = 0xbc20
+scr_prev_byte = 0xbc23
+scr_next_line = 0xbc26
+scr_prev_line = 0xbc29
+scr_ink_encode = 0xbc2c
+scr_ink_decode = 0xbc2f
+scr_set_ink = 0xbc32
+scr_get_ink = 0xbc35
+scr_set_border = 0xbc38
+scr_get_border = 0xbc3b
+scr_set_flashing = 0xbc3e
+scr_get_flashing = 0xbc41
+scr_fill_box = 0xbc44
+scr_flood_box = 0xbc17
+scr_char_invert = 0xbc4a
+scr_hw_roll = 0xbc4d
+scr_sw_roll = 0xbc50
+scr_unpack = 0xbc53
+scr_repack = 0xbc56
+scr_access = 0xbc59
+scr_pixels = 0xbc5c
+scr_horizontal = 0xbc5f
+scr_vertical = 0xbc62
+
+
+cas_initialise = 0xbc65
+cas_set_speed = 0xbc68
+cas_noisy = 0xbc6b
+cas_start_motor = 0xbc6e
+cas_stop_motor = 0xbc71
+cas_restore_motor = 0xbc74
+cas_in_open = 0xbc77
+cas_in_close = 0xbc7a
+cas_in_abandon = 0xbc7d
+cas_in_char = 0xbc80
+cas_in_direct = 0xbc83
+cas_return = 0xbc86
+cas_test_eof = 0xbc89
+cas_out_open = 0xbc8c
+cas_out_close = 0xbc8f
+cas_out_abandon = 0xbc92
+cas_out_char = 0xbc95
+cas_out_direct = 0xbc98
+cas_catalog = 0xbc9b
+cas_write = 0xbc9e
+cas_read = 0xbca1
+cas_check = 0xbca4
+
+sound_reset = 0xbca7
+sound_queue = 0xbcaa
+sound_check = 0xbcad
+sound_arm_event = 0xbcb0
+sound_release = 0xbcb3
+sound_hold = 0xbcb6
+sound_continue = 0xbcb9
+sound_ampl_envelope = 0xbcbc
+sound_tone_envelope = 0xbcbf
+sound_a_address = 0xbcc2
+sound_t_address = 0xbcc5
+
+
+mc_boot_program = 0xbd13
+mc_start_program = 0xbd16
+mc_wait_flyback = 0xbd19
+mc_set_mode = 0xbd1c
+mc_screen_offset = 0xbd1f
+mc_clear_inks = 0xbd22
+mc_set_inks = 0xbd25
+mc_reset_printer = 0xbd28
+mc_print_char = 0xbd2b
+mc_busy_printer = 0xbd2e
+mc_send_printer = 0xbd31
+mc_sound_register = 0xbd34
+mc_jump_restore = 0xbd37
+
+bios_set_message = 0xc033
+bios_setup_disc = 0xc036
+bios_select_format = 0xc039
+bios_read_sector = 0xc03c
+bios_write_sector = 0xc03f
+bios_format_track = 0xc042
+bios_move_track = 0xc045
+bios_get_status = 0xc048
+bios_set_retry_count = 0xc04b
+bios_get_sector_data = 0xc56c
+
+; 664 + 6128 only
+km_set_locks = 0xbd3a
+km_flush = 0xbd3d
+txt_ask_state = 0xbd40
+gra_default = 0xbd43
+gra_set_back = 0xbd46
+gra_set_first = 0xbd49
+gra_set_line_mask = 0xbd4c
+gra_from_user = 0xbd4f
+gra_fill = 0xbd52
+scr_set_position = 0xbd55
+mc_print_translation = 0xbd58
+kl_bank_switch = 0xbd5b ; 6128 only
+
diff --git a/src/crt0.s b/src/crt0.s
new file mode 100644
index 0000000..e35d57c
--- /dev/null
+++ b/src/crt0.s
@@ -0,0 +1,37 @@
+;;
+;; 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/>.
+;;
+.module crt0
+.globl _main
+.globl _main_init
+
+ .area _HOME
+ .area _CODE
+ .area _DATA
+ .area _BSEG
+ .area _BSS
+ .area _HEAP
+ .area _INITIALIZER
+
+ .area _CODE
+
+_main_init::
+ call _main
+
+halt0:
+ halt
+ jr halt0
diff --git a/src/data.c b/src/data.c
new file mode 100644
index 0000000..1a751b9
--- /dev/null
+++ b/src/data.c
@@ -0,0 +1,22 @@
+/*
+ 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/>.
+ */
+// only data
+
+#define LOCAL
+#include "data.h"
+
diff --git a/src/data.h b/src/data.h
new file mode 100644
index 0000000..ad65986
--- /dev/null
+++ b/src/data.h
@@ -0,0 +1,45 @@
+/*
+ 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/>.
+ */
+#ifndef _DATA_H
+#define _DATA_H
+
+#include "songs_pak.h"
+#include "palette.h"
+#include "font.h"
+#include "menubg.h"
+#include "tiles.h"
+#include "stage.h"
+#include "player.h"
+#include "explo.h"
+#include "splash.h"
+#include "items.h"
+#include "doors.h"
+#include "platform.h"
+#include "switch.h"
+#include "oni.h"
+#include "ninja.h"
+#include "spirit.h"
+#include "flame.h"
+#include "vampire.h"
+#include "spider.h"
+#include "demon.h"
+#include "fireball.h"
+#include "cloud.h"
+
+#endif // _DATA_H
+
diff --git a/src/effects.z80 b/src/effects.z80
new file mode 100644
index 0000000..2b0129c
--- /dev/null
+++ b/src/effects.z80
@@ -0,0 +1,351 @@
+; Sound Effects for song effects, version 2.0, generated by Arkos Tracker 2.
+
+effects_SoundEffects:
+effects_SoundEffectsDisarkGenerateExternalLabel:
+
+; The sound effects, starting at 1.
+effects_SoundEffectsDisarkPointerRegionStart0:
+ .dw effects_SoundEffects_Sound1 ; Sound effect 1.
+ .dw effects_SoundEffects_Sound2 ; Sound effect 2.
+ .dw effects_SoundEffects_Sound3 ; Sound effect 3.
+ .dw effects_SoundEffects_Sound4 ; Sound effect 4.
+ .dw effects_SoundEffects_Sound5 ; Sound effect 5.
+ .dw effects_SoundEffects_Sound6 ; Sound effect 6.
+ .dw effects_SoundEffects_Sound7 ; Sound effect 7.
+ .dw effects_SoundEffects_Sound8 ; Sound effect 8.
+ .dw effects_SoundEffects_Sound9 ; Sound effect 9.
+ .dw effects_SoundEffects_Sound10 ; Sound effect 10.
+effects_SoundEffectsDisarkPointerRegionEnd0:
+
+effects_SoundEffectsDisarkByteRegionStart1:
+; Sound effect 1.
+effects_SoundEffects_Sound1:
+ .db 0 ; Speed
+
+effects_SoundEffects_Sound1_Loop: .db 57 ; Soft only. Volume: 14.
+ .dw 1280 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 1024 ; Software period.
+
+ .db 25 ; Soft only. Volume: 6.
+ .dw 1024 ; Software period.
+
+ .db 4 ; End of the sound effect.
+
+; Sound effect 2.
+effects_SoundEffects_Sound2:
+ .db 0 ; Speed
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 512 ; Software period.
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 512 ; Software period.
+
+ .db 53 ; Soft only. Volume: 13.
+ .dw 256 ; Software period.
+
+ .db 45 ; Soft only. Volume: 11.
+ .dw 32 ; Software period.
+
+effects_SoundEffects_Sound2_Loop: .db 41 ; Soft only. Volume: 10.
+ .dw 32 ; Software period.
+
+ .db 4 ; End of the sound effect.
+
+; Sound effect 3.
+effects_SoundEffects_Sound3:
+ .db 0 ; Speed
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 96 ; Software period.
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 98 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 100 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 104 ; Software period.
+
+effects_SoundEffects_Sound3_Loop: .db 41 ; Soft only. Volume: 10.
+ .dw 100 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 104 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 100 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 104 ; Software period.
+
+ .db 33 ; Soft only. Volume: 8.
+ .dw 104 ; Software period.
+
+ .db 33 ; Soft only. Volume: 8.
+ .dw 104 ; Software period.
+
+ .db 4 ; End of the sound effect.
+
+; Sound effect 4.
+effects_SoundEffects_Sound4:
+ .db 0 ; Speed
+
+effects_SoundEffects_Sound4_Loop: .db 41 ; Soft only. Volume: 10.
+ .dw 18 ; Software period.
+
+ .db 33 ; Soft only. Volume: 8.
+ .dw 16 ; Software period.
+
+ .db 17 ; Soft only. Volume: 4.
+ .dw 16 ; Software period.
+
+ .db 9 ; Soft only. Volume: 2.
+ .dw 16 ; Software period.
+
+ .db 4 ; End of the sound effect.
+
+; Sound effect 5.
+effects_SoundEffects_Sound5:
+ .db 0 ; Speed
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 768 ; Software period.
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 768 ; Software period.
+
+effects_SoundEffects_Sound5_Loop: .db 53 ; Soft only. Volume: 13.
+ .dw 1536 ; Software period.
+
+ .db 53 ; Soft only. Volume: 13.
+ .dw 1536 ; Software period.
+
+ .db 1 ; Soft only. Volume: 0.
+ .dw 768 ; Software period.
+
+ .db 1 ; Soft only. Volume: 0.
+ .dw 768 ; Software period.
+
+ .db 53 ; Soft only. Volume: 13.
+ .dw 1536 ; Software period.
+
+ .db 53 ; Soft only. Volume: 13.
+ .dw 1536 ; Software period.
+
+ .db 4 ; End of the sound effect.
+
+; Sound effect 6.
+effects_SoundEffects_Sound6:
+ .db 0 ; Speed
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 2048 ; Software period.
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 1792 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 1280 ; Software period.
+
+effects_SoundEffects_Sound6_Loop: .db 41 ; Soft only. Volume: 10.
+ .dw 768 ; Software period.
+
+ .db 33 ; Soft only. Volume: 8.
+ .dw 1280 ; Software period.
+
+ .db 33 ; Soft only. Volume: 8.
+ .dw 512 ; Software period.
+
+ .db 25 ; Soft only. Volume: 6.
+ .dw 256 ; Software period.
+
+ .db 17 ; Soft only. Volume: 4.
+ .dw 256 ; Software period.
+
+ .db 4 ; End of the sound effect.
+
+; Sound effect 7.
+effects_SoundEffects_Sound7:
+ .db 0 ; Speed
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 128 ; Software period.
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 128 ; Software period.
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 64 ; Software period.
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 96 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 128 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 128 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 64 ; Software period.
+
+effects_SoundEffects_Sound7_Loop: .db 41 ; Soft only. Volume: 10.
+ .dw 96 ; Software period.
+
+ .db 1 ; Soft only. Volume: 0.
+ .dw 64 ; Software period.
+
+ .db 1 ; Soft only. Volume: 0.
+ .dw 64 ; Software period.
+
+ .db 1 ; Soft only. Volume: 0.
+ .dw 64 ; Software period.
+
+ .db 1 ; Soft only. Volume: 0.
+ .dw 64 ; Software period.
+
+ .db 33 ; Soft only. Volume: 8.
+ .dw 128 ; Software period.
+
+ .db 33 ; Soft only. Volume: 8.
+ .dw 128 ; Software period.
+
+ .db 33 ; Soft only. Volume: 8.
+ .dw 64 ; Software period.
+
+ .db 33 ; Soft only. Volume: 8.
+ .dw 64 ; Software period.
+
+ .db 25 ; Soft only. Volume: 6.
+ .dw 64 ; Software period.
+
+ .db 17 ; Soft only. Volume: 4.
+ .dw 64 ; Software period.
+
+ .db 4 ; End of the sound effect.
+
+; Sound effect 8.
+effects_SoundEffects_Sound8:
+ .db 0 ; Speed
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 512 ; Software period.
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 512 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 1024 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 1024 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 1280 ; Software period.
+
+effects_SoundEffects_Sound8_Loop: .db 33 ; Soft only. Volume: 8.
+ .dw 1536 ; Software period.
+
+ .db 33 ; Soft only. Volume: 8.
+ .dw 1792 ; Software period.
+
+ .db 25 ; Soft only. Volume: 6.
+ .dw 1792 ; Software period.
+
+ .db 17 ; Soft only. Volume: 4.
+ .dw 1792 ; Software period.
+
+ .db 4 ; End of the sound effect.
+
+; Sound effect 9.
+effects_SoundEffects_Sound9:
+ .db 0 ; Speed
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 512 ; Software period.
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 512 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 1024 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 1024 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 1280 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 1536 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 1792 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 1536 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 1792 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 1280 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 1536 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 1024 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 1024 ; Software period.
+
+ .db 33 ; Soft only. Volume: 8.
+ .dw 1536 ; Software period.
+
+effects_SoundEffects_Sound9_Loop: .db 33 ; Soft only. Volume: 8.
+ .dw 1536 ; Software period.
+
+ .db 25 ; Soft only. Volume: 6.
+ .dw 1536 ; Software period.
+
+ .db 17 ; Soft only. Volume: 4.
+ .dw 1536 ; Software period.
+
+ .db 4 ; End of the sound effect.
+
+; Sound effect 10.
+effects_SoundEffects_Sound10:
+ .db 0 ; Speed
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 496 ; Software period.
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 496 ; Software period.
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 384 ; Software period.
+
+ .db 57 ; Soft only. Volume: 14.
+ .dw 384 ; Software period.
+
+ .db 41 ; Soft only. Volume: 10.
+ .dw 352 ; Software period.
+
+effects_SoundEffects_Sound10_Loop: .db 41 ; Soft only. Volume: 10.
+ .dw 336 ; Software period.
+
+ .db 33 ; Soft only. Volume: 8.
+ .dw 336 ; Software period.
+
+ .db 17 ; Soft only. Volume: 4.
+ .dw 336 ; Software period.
+
+ .db 4 ; End of the sound effect.
+
+effects_SoundEffectsDisarkByteRegionEnd1:
diff --git a/src/entities.c b/src/entities.c
new file mode 100644
index 0000000..47f68b6
--- /dev/null
+++ b/src/entities.c
@@ -0,0 +1,534 @@
+/*
+ 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 <string.h>
+
+#include "main.h"
+
+#define LOCAL
+#include "entities.h"
+
+//#define ET_DEBUG
+
+struct st_entity *sp_free;
+struct st_entity entities[MAX_ENTITIES];
+
+void init_entities()
+{
+ uint8_t i;
+
+ memset(entities, 0, sizeof(struct st_entity) * MAX_ENTITIES);
+
+ for (i = 0; i < MAX_ENTITIES - 1; ++i)
+ entities[i].n = entities + i + 1;
+
+ sp_used = NULL;
+ sp_free = entities;
+ sp_collect = 0;
+}
+
+#pragma save
+#pragma disable_warning 59
+uint8_t new_entity()
+{
+ // new entity on sp_new on success
+
+ /*
+ if (!sp_free)
+ return 0;
+
+ sp_new = sp_free;
+ sp_free = sp_free->n;
+ sp_new->n = sp_used;
+ sp_used = sp_new;
+
+ return 1;
+ */
+
+ // *INDENT-OFF*
+ __asm;
+
+ ; check MSB
+ ld a, (_sp_free + 1)
+ ld l, a
+ or a
+#ifndef ET_DEBUG
+ ret z
+#else
+ jr nz, new_entity_free
+
+ ld hl, #0x4a
+ call _set_hw_border
+
+ xor a
+ ld l, a
+ ret
+new_entity_free:
+#endif
+
+ ld hl, (_sp_free)
+ ld (_sp_new), hl
+
+ ld bc, #ET_SIZE_PT
+
+ add hl, bc
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ ex de, hl
+ ld (_sp_free), hl
+
+ ld hl, (_sp_new)
+ add hl, bc
+ ld a, (_sp_used)
+ ld (hl), a
+ inc hl
+ ld a, (_sp_used + 1)
+ ld (hl), a
+
+ ld hl, (_sp_new)
+ ld (_sp_used), hl
+
+ ld l, #1
+ __endasm;
+ // *INDENT-ON*
+}
+#pragma restore
+
+void destroy_entity()
+{
+ // entity on sp_it
+ sp_it->type = ET_UNUSED;
+ sp_collect++;
+}
+
+void free_entities()
+{
+ /*
+ if (!sp_used)
+ return;
+
+ // current, previous
+ for (sp_it = sp_it2 = sp_used; sp_it && sp_collect;)
+ {
+ if (sp_it->type == ET_UNUSED)
+ {
+ sp_collect--;
+
+ if (sp_it == sp_used)
+ {
+ sp_it2 = sp_free;
+ sp_free = sp_used;
+ sp_used = sp_used->n;
+ sp_free->n = sp_it2;
+ sp_it = sp_it2 = sp_used;
+ continue;
+ }
+ else
+ {
+ sp_it2->n = sp_it->n;
+ sp_it->n = sp_free;
+ sp_free = sp_it;
+ sp_it = sp_it2->n;
+ continue;
+ }
+ }
+ sp_it2 = sp_it;
+ sp_it = sp_it->n;
+ }
+ */
+
+ // *INDENT-OFF*
+ __asm;
+
+ ; check MSB
+ ld a, (_sp_used + 1)
+ or a
+ ret z
+
+ ld hl, (_sp_used)
+ ld (_sp_it2), hl
+ ld (_sp_it), hl
+
+ ld bc, #ET_SIZE_PT
+
+free_entities_loop:
+ ; hl holds it
+
+ ; check MSB
+ ld a, (_sp_collect)
+ or a
+ ret z
+ ld a, h
+ or a
+ ret z
+
+ ld a, (hl)
+ or a
+ jr z, free_entities_unused
+
+ ; next
+
+ ; it2 is it
+ ld (_sp_it2), hl
+
+ ; it to it->n
+ add hl, bc
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ ex de, hl
+ ld (_sp_it), hl
+
+ jr free_entities_loop
+
+free_entities_unused:
+ ld a, (_sp_collect)
+ dec a
+ ld (_sp_collect), a
+
+ ld a, (_sp_used)
+ cp l
+ jr nz, free_entities_not_used
+ ld a, (_sp_used + 1)
+ cp h
+ jr nz, free_entities_not_used
+
+ ; it2 = free
+ ld hl, (_sp_free)
+ ld (_sp_it2), hl
+
+ ; free = used
+ ld hl, (_sp_used)
+ ld (_sp_free), hl
+
+ ; used = used->n
+ add hl, bc
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ ex de, hl
+ ld (_sp_used), hl
+
+ ; free->n = it2
+ ld hl, (_sp_it2)
+ ex de, hl
+ ld hl, (_sp_free)
+ add hl, bc
+ ld (hl), e
+ inc hl
+ ld (hl), d
+
+ ; it = it2 = used
+ ld hl, (_sp_used)
+ ld (_sp_it2), hl
+ ld (_sp_it), hl
+
+ jr free_entities_loop
+
+free_entities_not_used:
+ ; it2->n = it->n
+ add hl, bc
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ ld hl, (_sp_it2)
+ add hl, bc
+ ld (hl), e
+ inc hl
+ ld (hl), d
+
+ ; it->n = free
+ ld hl, (_sp_free)
+ ex de, hl
+ ld hl, (_sp_it)
+ add hl, bc
+ ld (hl), e
+ inc hl
+ ld (hl), d
+
+ ; free = it
+ ld hl, (_sp_it)
+ ld (_sp_free), hl
+
+ ; it = it2->n
+ ld hl, (_sp_it2)
+ add hl, bc
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ ex de, hl
+ ld (_sp_it), hl
+
+ jp free_entities_loop
+ __endasm;
+ // *INDENT-ON*
+}
+
+#pragma save
+#pragma disable_warning 85
+#pragma disable_warning 59
+uint8_t check_for_point(struct st_entity *s, uint8_t x, uint8_t y, uint8_t w, uint8_t h)
+{
+ // return (y < s->y + h && y >= s->y && x < s->x + w && x >= s->x);
+ // *INDENT-OFF*
+ __asm;
+
+ ld hl, #2
+ add hl, sp
+
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+
+ ex de, hl
+ ld bc, #4
+ add hl, bc
+ ld a, (hl)
+ inc hl
+ inc hl
+ ld h, (hl)
+ ld l, a
+ ex de, hl
+
+ inc hl
+ ld c, (hl)
+ inc hl
+ ld b, (hl)
+ inc hl
+ ld a, (hl)
+ inc hl
+ ld h, (hl)
+ ld l, a
+
+ ; de s (x, y)
+ ; bc (x, y)
+ ; hl (w, h)
+
+ ld a, b
+ cp d
+ jr c, check_for_point_fail
+
+ ld a, d
+ add h
+ cp b
+ jr c, check_for_point_fail
+
+ ld a, c
+ cp e
+ jr c, check_for_point_fail
+
+ ld a, e
+ add l
+ cp c
+ jr c, check_for_point_fail
+
+ ld l, #1
+ ret
+
+check_for_point_fail:
+ ld l, #0
+
+ __endasm;
+ // *INDENT-ON*
+}
+#pragma restore
+
+void draw_entities()
+{
+ /*
+ draw_player();
+
+ for (sp_it = sp_used; sp_it; sp_it = sp_it->n)
+ {
+ it_x = &sp_it->x;
+ it_y = &sp_it->y;
+ it_ox = &sp_it->ox;
+ it_oy = &sp_it->oy;
+ it_frame = &sp_it->frame;
+ it_param = &sp_it->param;
+ it_extra = &sp_it->extra;
+
+ sp_it->draw();
+ }
+ */
+
+ // *INDENT-OFF*
+ __asm;
+ call _draw_player
+
+ ld hl, (_sp_used)
+
+ ld a, h
+ or l
+ jr z, draw_entities_done
+
+draw_entities_loop:
+
+ ld (_sp_it), hl
+
+ ld bc, #2
+
+ add hl, bc
+ ld (_it_frame), hl
+ add hl, bc
+ ld (_it_x), hl
+ inc hl
+ ld (_it_ox), hl
+ inc hl
+ ld (_it_y), hl
+ inc hl
+ ld (_it_oy), hl
+ inc hl
+ ld (_it_param), hl
+ inc hl
+ ld (_it_extra), hl
+ inc hl
+ add hl, bc
+
+ push hl
+ ld a, (hl)
+ inc hl
+ ld h, (hl)
+ ld l, a
+ call ___sdcc_call_hl
+ pop hl
+
+ inc hl
+ inc hl
+
+ ld a, (hl)
+ inc hl
+ ld h, (hl)
+ ld l, a
+ or h
+ jr nz, draw_entities_loop
+
+draw_entities_done:
+
+ __endasm;
+ // *INDENT-ON*
+}
+
+void update_entities()
+{
+ /*
+ for (sp_it = sp_used; sp_it; sp_it = sp_it->n)
+ {
+ if (sp_it->type == ET_UNUSED)
+ continue;
+
+ it_frame = &sp_it->frame;
+ it_x = &sp_it->x;
+ it_y = &sp_it->y;
+
+ sp_it->ox = *it_x;
+ it_ox = &sp_it->ox;
+ sp_it->oy = *it_y;
+ it_oy = &sp_it->oy;
+
+ it_delay = &sp_it->delay;
+ it_param = &sp_it->param;
+ it_extra = &sp_it->extra;
+
+ sp_it->update();
+ }
+
+ if (sp_collect)
+ free_entities();
+
+ update_player();
+ */
+ // *INDENT-OFF*
+ __asm;
+
+ ld hl, (_sp_used)
+
+ ld a, h
+ or l
+ jr z, update_entities_done
+
+update_entities_loop:
+
+ ld a, (hl)
+ or a
+ jr nz, update_entities_no_skip
+
+ ld bc, #ET_SIZE_PT
+ jr update_entities_next
+
+update_entities_no_skip:
+ ld (_sp_it), hl
+
+ inc hl
+ inc hl
+ ld (_it_frame), hl
+ inc hl
+ ld (_it_delay), hl
+ inc hl
+ ld (_it_x), hl
+ ld a, (hl)
+ inc hl
+ ld (_it_ox), hl
+ ld (hl), a
+ inc hl
+ ld (_it_y), hl
+ ld a, (hl)
+ inc hl
+ ld (_it_oy), hl
+ ld (hl), a
+ inc hl
+ ld (_it_param), hl
+ inc hl
+ ld (_it_extra), hl
+ inc hl
+
+ push hl
+ ld a, (hl)
+ inc hl
+ ld h, (hl)
+ ld l, a
+ call ___sdcc_call_hl
+ pop hl
+
+ ld bc, #4
+
+update_entities_next:
+ add hl, bc
+
+ ld a, (hl)
+ inc hl
+ ld h, (hl)
+ ld l, a
+ or h
+ jr nz, update_entities_loop
+
+update_entities_done:
+ ld a, (_sp_collect)
+ or a
+ jr z, update_entities_exit
+
+ call _free_entities
+
+update_entities_exit:
+ call _update_player
+
+ __endasm;
+ // *INDENT-ON*
+}
+
diff --git a/src/entities.h b/src/entities.h
new file mode 100644
index 0000000..4469896
--- /dev/null
+++ b/src/entities.h
@@ -0,0 +1,70 @@
+/*
+ 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/>.
+ */
+#ifndef _ENTITIES_H
+#define _ENTITIES_H
+
+#include <stdint.h>
+
+#ifndef LOCAL
+#define LOCAL extern
+#endif
+
+struct st_entity
+{
+ uint8_t type;
+ uint8_t id;
+ uint8_t frame;
+ uint8_t delay;
+ uint8_t x;
+ uint8_t ox;
+ uint8_t y;
+ uint8_t oy;
+ uint8_t param;
+ uint8_t extra;
+ void (*update)();
+ void (*draw)();
+ struct st_entity *n;
+};
+
+// entitiy structure size to *n
+#define ET_SIZE_PT 14
+
+// define MAX_ENTITIES and entity types
+#include "et_config.h"
+
+void init_entities();
+uint8_t new_entity();
+void destroy_entity();
+void free_entities();
+
+uint8_t check_for_point(struct st_entity *s, uint8_t x, uint8_t y, uint8_t w, uint8_t h);
+
+void draw_entities();
+void update_entities();
+
+LOCAL struct st_entity *sp_used, *sp_new;
+
+LOCAL struct st_entity *sp_it, *sp_it2;
+LOCAL uint8_t sp_collect;
+
+LOCAL uint8_t *it_x, *it_y, *it_ox, *it_oy, *it_param, *it_frame, *it_delay, *it_extra, it_k;
+
+#undef LOCAL
+
+#endif // _ENTITIES_H
+
diff --git a/src/et_cloud.c b/src/et_cloud.c
new file mode 100644
index 0000000..651fa1f
--- /dev/null
+++ b/src/et_cloud.c
@@ -0,0 +1,82 @@
+/*
+ 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 "splib.h"
+#include "main.h"
+
+#include "maps.h"
+
+// generated
+#include "cloud.h"
+
+#include "entities.h"
+#include "et_cloud.h"
+#include "et_player.h"
+
+void draw_cloud()
+{
+ it_k = *it_x & 1;
+ if (*it_param)
+ it_k ^= 1;
+
+ put_sprite4(cloud[it_k], *it_x, *it_y, 3, *it_param & 128);
+ erase_sprite(*it_ox, *it_oy, 3);
+}
+
+void update_cloud()
+{
+ if (*it_param)
+ {
+ if (is_map_blocked(*it_x + 11, *it_y + 22)
+ || is_map_blocked(*it_x + 11, *it_y + 7)
+ || is_map_blocked(*it_x + 11, *it_y + 2)
+ || *it_x > TW * TMW - 8 - 4)
+ goto cloud_change_dir;
+ else
+ *it_x += 1;
+ }
+ else
+ {
+ if (is_map_blocked(*it_x - 4, *it_y + 22)
+ || is_map_blocked(*it_x - 4, *it_y + 7)
+ || is_map_blocked(*it_x - 4, *it_y + 2)
+ || *it_x < 4)
+ goto cloud_change_dir;
+ else
+ *it_x -= 1;
+ }
+
+ if (!magic && lives
+ && abs_sub(px, *it_x) < 64 && py != *it_y
+ && (
+ (*it_param && px > *it_x) || (*it_param == 0 && px < *it_x)
+ ))
+ {
+ if (py > *it_y && !is_map_blocked(*it_x + 3, *it_y + 23))
+ *it_y += 1;
+
+ if (py < *it_y && !is_map_blocked(*it_x + 3, *it_y + 1))
+ *it_y -= 1;
+ }
+
+ if (check_for_player(24))
+ {
+ player_hit(2);
+cloud_change_dir:
+ *it_param ^= 128;
+ }
+}
diff --git a/src/et_cloud.h b/src/et_cloud.h
new file mode 100644
index 0000000..d7641da
--- /dev/null
+++ b/src/et_cloud.h
@@ -0,0 +1,25 @@
+/*
+ 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/>.
+ */
+#ifndef _CLOUD_H
+#define _CLOUD_H
+
+void draw_cloud();
+void update_cloud();
+
+#endif // _CLOUD_H
+
diff --git a/src/et_common.c b/src/et_common.c
new file mode 100644
index 0000000..d361807
--- /dev/null
+++ b/src/et_common.c
@@ -0,0 +1,39 @@
+/*
+ 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 "splib.h"
+#include "main.h"
+
+#include "entities.h"
+#include "et_player.h"
+
+void update_fixed_common()
+{
+ // limit and change direction
+ if ((*it_param & 127) == *it_extra)
+ {
+ *it_param &= 128;
+ *it_param ^= 128;
+ }
+
+ ++(*it_param);
+
+ if (*it_param & 128)
+ *it_x += 1;
+ else
+ *it_x -= 1;
+}
diff --git a/src/et_common.h b/src/et_common.h
new file mode 100644
index 0000000..cf13028
--- /dev/null
+++ b/src/et_common.h
@@ -0,0 +1,24 @@
+/*
+ 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/>.
+ */
+#ifndef _COMMON_H
+#define _COMMON_H
+
+void update_fixed_common();
+
+#endif // _COMMON_H
+
diff --git a/src/et_config.h b/src/et_config.h
new file mode 100644
index 0000000..d5dc8fd
--- /dev/null
+++ b/src/et_config.h
@@ -0,0 +1,70 @@
+/*
+ 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/>.
+ */
+#ifndef __ET_CONFIG_H
+#define __ET_CONFIG_H
+
+#define MAX_ENTITIES 10
+
+#define ET_IS_ENEMY(x) (x->type > ET_PLATFORM && x->type < ET_POTION)
+
+// id for non persistent entities
+#define ETID_NP 255
+
+// first ET in map
+#define ET_FIRST ET_DOOR
+
+enum entity_type
+{
+ ET_UNUSED = 0,
+
+ // adds extra info to the map, not real ETs
+ ET_FILL,
+ // links up/down maps
+ ET_LINK,
+
+ // affect is_map_blocked
+ ET_DOOR,
+ ET_PLATFORM,
+
+ // enemies
+ ET_SPIRIT,
+ ET_FLAME,
+ ET_VAMPIRE,
+ ET_ONI,
+ ET_NINJA,
+ ET_SPIDER,
+ ET_DEMON,
+ ET_CLOUD,
+
+ // bg
+ ET_TORCH,
+
+ // statics
+ ET_SWITCH,
+ ET_GEM,
+ ET_KEY,
+ ET_POTION,
+ ET_GTAIL,
+
+ // not in map
+ ET_EXPLO,
+ ET_SPLASH,
+};
+
+#endif // __ET_CONFIG_H
+
diff --git a/src/et_demon.c b/src/et_demon.c
new file mode 100644
index 0000000..fd54f8e
--- /dev/null
+++ b/src/et_demon.c
@@ -0,0 +1,156 @@
+/*
+ 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 <string.h>
+
+#include "splib.h"
+#include "plw.h"
+#include "sound.h"
+#include "main.h"
+
+#include "maps.h"
+
+// generated
+#include "demon.h"
+#include "fireball.h"
+
+#include "entities.h"
+#include "et_demon.h"
+#include "et_player.h"
+
+static const struct st_entity base = {
+ ET_EXPLO,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0, // param
+ 0, // extra
+ update_fireball,
+ draw_fireball,
+ 0
+};
+
+void draw_fireball()
+{
+ put_sprite4(fireball[player_frames[*it_frame]], *it_x, *it_y, 1, 0);
+ erase_sprite(*it_ox, *it_oy, 1);
+}
+
+void update_fireball()
+{
+ if ((*it_delay)++ == 1)
+ {
+ *it_delay = 0;
+ if (++(*it_frame) > WALK_CYCLE)
+ *it_frame = WALK;
+ }
+
+ if (*it_param)
+ {
+ if (is_map_blocked(*it_x + 11, *it_y + 8)
+ || *it_x > TW * TMW - 8 - 4)
+ goto fireball_done;
+ else
+ *it_x += 2;
+ }
+ else
+ {
+ if (is_map_blocked(*it_x - 4, *it_y + 8)
+ || *it_x < 4)
+ goto fireball_done;
+ else
+ *it_x -= 2;
+ }
+
+ if (check_for_player(8))
+ {
+ player_hit(2);
+fireball_done:
+ erase_sprite(*it_ox, *it_oy, 1);
+ destroy_entity();
+ }
+}
+
+void new_fireball()
+{
+ if (!new_entity())
+ return;
+
+ memcpy(sp_new, &base, sizeof(base) - 2);
+ if (*it_param)
+ sp_new->x = *it_x + 8;
+ else
+ sp_new->x = *it_x - 8;
+ sp_new->param = *it_param;
+ sp_new->y = *it_y + 8;
+
+ PLW_PlaySoundEffectP(EFX_MAGIC);
+}
+
+void draw_demon()
+{
+ put_sprite4(demon[player_frames[*it_frame]], *it_x, *it_y, 3, *it_param & 128);
+ erase_sprite(*it_ox, *it_oy, 3);
+}
+
+void update_demon()
+{
+ if ((*it_delay)++ == 2 || *it_delay == 11)
+ {
+ *it_delay = 0;
+ if (++(*it_frame) > WALK_CYCLE)
+ *it_frame = WALK;
+ }
+
+ if (*it_extra < 72)
+ *it_extra += 1;
+ else if (abs_sub(py, *it_y) < 8 &&
+ (*it_param && px > *it_x || !*it_param && px < *it_x))
+ {
+ new_fireball();
+ *it_extra = 0;
+
+ *it_frame = WALK_CYCLE;
+ *it_delay = 3;
+ }
+
+ if (*it_param)
+ {
+ if (is_map_blocked(*it_x + 11, *it_y + 8)
+ || !is_map_blocked(*it_x + 11, *it_y + 24)
+ || *it_x > TW * TMW - 8 - 4)
+ goto demon_change_dir;
+ else
+ *it_x += 1;
+ }
+ else
+ {
+ if (is_map_blocked(*it_x - 4, *it_y + 8)
+ || !is_map_blocked(*it_x - 4, *it_y + 24)
+ || *it_x < 4)
+ goto demon_change_dir;
+ else
+ *it_x -= 1;
+ }
+
+ if (check_for_player(24))
+ {
+ player_hit(1);
+demon_change_dir:
+ *it_param ^= 128;
+ *it_delay = 0;
+ *it_frame = WALK;
+ }
+}
diff --git a/src/et_demon.h b/src/et_demon.h
new file mode 100644
index 0000000..4c62811
--- /dev/null
+++ b/src/et_demon.h
@@ -0,0 +1,28 @@
+/*
+ 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/>.
+ */
+#ifndef _DEMON_H
+#define _DEMON_H
+
+void draw_demon();
+void update_demon();
+
+void draw_fireball();
+void update_fireball();
+
+#endif // _DEMON_H
+
diff --git a/src/et_door.c b/src/et_door.c
new file mode 100644
index 0000000..e764715
--- /dev/null
+++ b/src/et_door.c
@@ -0,0 +1,56 @@
+/*
+ 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 "splib.h"
+#include "plw.h"
+#include "sound.h"
+#include "main.h"
+
+#include "maps.h"
+
+// generated
+#include "doors.h"
+
+#include "entities.h"
+#include "et_player.h"
+#include "et_door.h"
+
+void draw_door() {
+ if (is_invalid_tile2(get_tile_xy(*it_x >> 3, *it_y >> 3)))
+ for (it_k = 0; it_k < 16; it_k += 8)
+ put_sprite4(doors[*it_frame], *it_x, it_k + *it_y, 1, 0);
+
+ if (is_invalid_tile2(get_tile_xy(*it_x >> 3, 2 + (*it_y >> 3))))
+ for (it_k = 16; it_k < 32; it_k += 8)
+ put_sprite4(doors[*it_frame], *it_x, it_k + *it_y, 1, 0);
+}
+
+void update_door() {
+ // is_map_blocked prevents this if no keys
+ if (!check_for_player(32))
+ return;
+
+ erase_sprite(*it_x, *it_y, 4);
+ destroy_entity();
+
+ update_persistence(sp_it->id);
+ --keys;
+
+ draw_hud();
+
+ PLW_PlaySoundEffectP(EFX_DOOR);
+}
diff --git a/src/et_door.h b/src/et_door.h
new file mode 100644
index 0000000..a1f39fd
--- /dev/null
+++ b/src/et_door.h
@@ -0,0 +1,25 @@
+/*
+ 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/>.
+ */
+#ifndef _DOOR_H
+#define _DOOR_H
+
+void draw_door();
+void update_door();
+
+#endif // _DOOR_H
+
diff --git a/src/et_effect.c b/src/et_effect.c
new file mode 100644
index 0000000..02f40dc
--- /dev/null
+++ b/src/et_effect.c
@@ -0,0 +1,125 @@
+/*
+ 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 <string.h>
+
+#include "splib.h"
+#include "plw.h"
+#include "sound.h"
+#include "main.h"
+
+// generated
+#include "explo.h"
+#include "splash.h"
+#include "tiles.h"
+
+#include "entities.h"
+#include "et_effect.h"
+
+static const struct st_entity base = {
+ ET_EXPLO,
+ 0, 0, 0, 0, 0, 0, 0,
+ 1, // param
+ 0, // extra
+ update_effect,
+ draw_explo,
+ 0
+};
+
+void update_effect()
+{
+ if (++*it_delay > 1)
+ {
+ *it_delay = 0;
+ if (++*it_frame > *it_param)
+ {
+ destroy_entity();
+ erase_sprite(*it_x, *it_y, 2);
+ }
+ }
+}
+
+void draw_explo()
+{
+ put_sprite4(explo[*it_frame], *it_x, *it_y, 2, 0);
+}
+
+void draw_splash()
+{
+ put_sprite4(splash[*it_frame], *it_x, *it_y, 2, 0);
+}
+
+void new_explo()
+{
+ if (!new_entity())
+ return;
+
+ memcpy(sp_new, &base, sizeof(base) - 2);
+ sp_new->x = px;
+ sp_new->y = py + 6;
+
+ if (player_h)
+ {
+ sp_new->y += 4;
+ if (sp_new->y > TH * TMH - 16)
+ sp_new->y = (uint8_t)(TH * TMH - 16);
+ }
+
+ PLW_PlaySoundEffectP(EFX_MAGIC);
+}
+
+void new_explo_et()
+{
+ if (!new_entity())
+ return;
+
+ memcpy(sp_new, &base, sizeof(base) - 2);
+ sp_new->x = *it_x;
+ sp_new->y = *it_y + 6;
+
+ PLW_PlaySoundEffectP(EFX_MAGIC);
+}
+
+void new_splash()
+{
+ if (!new_entity())
+ return;
+
+ memcpy(sp_new, &base, sizeof(base) - 2);
+ sp_new->draw = draw_splash;
+ sp_new->param = 2;
+ sp_new->x = px;
+ sp_new->y = py + 8;
+
+ PLW_PlaySoundEffectP(EFX_SPLASH);
+}
+
+void draw_torch()
+{
+ if (!*it_param)
+ put_tile(bgtiles[TORCH_BASE_TILE + *it_frame], get_tile_xy(*it_x, *it_y));
+}
+
+void update_tile_anim()
+{
+ if ((*it_param)++ > 2)
+ {
+ *it_param = 0;
+ if (++(*it_frame) == 3)
+ *it_frame = 0;
+ }
+}
diff --git a/src/et_effect.h b/src/et_effect.h
new file mode 100644
index 0000000..a21d593
--- /dev/null
+++ b/src/et_effect.h
@@ -0,0 +1,35 @@
+/*
+ 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/>.
+ */
+#ifndef _EXPLO_H
+#define _EXPLO_H
+
+#define TORCH_BASE_TILE 55
+
+void draw_explo();
+void draw_splash();
+void update_explo();
+
+void new_explo();
+void new_explo_et();
+void new_splash();
+
+void draw_torch();
+void update_tile_anim();
+
+#endif // _EXPLO_H
+
diff --git a/src/et_flame.c b/src/et_flame.c
new file mode 100644
index 0000000..7f53667
--- /dev/null
+++ b/src/et_flame.c
@@ -0,0 +1,55 @@
+/*
+ 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 "splib.h"
+#include "main.h"
+
+// generated
+#include "flame.h"
+
+#include "entities.h"
+#include "et_flame.h"
+#include "et_player.h"
+#include "et_common.h"
+
+void draw_flame()
+{
+ put_sprite4(flame[*it_frame], *it_x, *it_y, 2, *it_param & 128);
+ erase_sprite(*it_ox, *it_oy, 2);
+}
+
+void update_flame()
+{
+ if ((*it_delay)++ == 2)
+ {
+ *it_delay = 0;
+ if (++(*it_frame) > 2)
+ *it_frame = 0;
+ }
+
+ update_fixed_common();
+
+ if (check_for_player(16))
+ {
+ player_hit(1);
+ it_k = *it_param & 128;
+ *it_param = *it_extra - (*it_param & 127);
+ *it_param |= it_k ^ 128;
+ *it_delay = 0;
+ *it_frame = WALK;
+ }
+}
diff --git a/src/et_flame.h b/src/et_flame.h
new file mode 100644
index 0000000..b65806d
--- /dev/null
+++ b/src/et_flame.h
@@ -0,0 +1,25 @@
+/*
+ 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/>.
+ */
+#ifndef _FLAME_H
+#define _FLAME_H
+
+void draw_flame();
+void update_flame();
+
+#endif // _FLAME_H
+
diff --git a/src/et_ninja.c b/src/et_ninja.c
new file mode 100644
index 0000000..e1e8bb1
--- /dev/null
+++ b/src/et_ninja.c
@@ -0,0 +1,127 @@
+/*
+ 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 "splib.h"
+#include "main.h"
+
+#include "maps.h"
+
+// generated
+#include "ninja.h"
+
+#include "entities.h"
+#include "et_ninja.h"
+#include "et_player.h"
+#include "et_effect.h"
+
+void draw_ninja()
+{
+ if (*it_extra)
+ return;
+
+ put_sprite4(ninja[player_frames[*it_frame]], *it_x, *it_y, 3, *it_param);
+ erase_sprite(*it_ox, *it_oy, 3);
+}
+
+uint8_t try_magic()
+{
+ uint8_t target = *it_x;
+
+ if (*it_x == TW * TMW - 8 - 2
+ || *it_x == 2)
+ return 0;
+
+ for (it_k = 0; it_k < 6; it_k++)
+ {
+ if (*it_param)
+ target += 8;
+ else
+ target -= 8;
+
+ if (is_map_blocked(target, *it_y + 8)
+ || is_map_deadly(target, *it_y + 24)
+ || target < 4
+ || target > TW * TMW - 8 - 4)
+ break;
+
+ if (is_map_blocked(target, *it_y + 24))
+ {
+ erase_sprite(*it_ox, *it_oy, 3);
+ new_explo_et();
+ *it_x = (target / TW) * TW;
+ *it_extra = COOL_DOWN + 2;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void update_ninja()
+{
+ if ((*it_delay)++ == 2)
+ {
+ *it_delay = 0;
+ if (++(*it_frame) > WALK_CYCLE)
+ *it_frame = WALK;
+ }
+
+ if (*it_extra)
+ {
+ --(*it_extra);
+ if (*it_extra == 0)
+ new_explo_et();
+ return;
+ }
+
+ if (*it_param)
+ {
+ if (is_map_blocked(*it_x + 8, *it_y + 8) || *it_x == TW * TMW - 8 - 2
+ || !is_map_blocked(*it_x + 8, *it_y + 24)
+ || is_map_deadly(*it_x + 8, *it_y + 24))
+ {
+ if (try_magic())
+ return;
+ goto ninja_change_dir;
+ }
+ else
+ *it_x += 1;
+ }
+ else
+ {
+ if (is_map_blocked(*it_x - 1, *it_y + 8) || *it_x == 2
+ || !is_map_blocked(*it_x - 1, *it_y + 24)
+ || is_map_deadly(*it_x - 1, *it_y + 24))
+ {
+ if (try_magic())
+ return;
+ goto ninja_change_dir;
+ }
+ else
+ *it_x -= 1;
+ }
+
+ if (*it_extra == 0 && check_for_player(24))
+ {
+ player_hit(1);
+
+ninja_change_dir:
+ *it_param ^= 128;
+ *it_delay = 0;
+ *it_frame = WALK;
+ }
+}
diff --git a/src/et_ninja.h b/src/et_ninja.h
new file mode 100644
index 0000000..5544374
--- /dev/null
+++ b/src/et_ninja.h
@@ -0,0 +1,25 @@
+/*
+ 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/>.
+ */
+#ifndef _NINJA_H
+#define _NINJA_H
+
+void draw_ninja();
+void update_ninja();
+
+#endif // _NINJA_H
+
diff --git a/src/et_oni.c b/src/et_oni.c
new file mode 100644
index 0000000..5a6a899
--- /dev/null
+++ b/src/et_oni.c
@@ -0,0 +1,84 @@
+/*
+ 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 "splib.h"
+#include "plw.h"
+#include "sound.h"
+#include "main.h"
+
+#include "maps.h"
+
+// generated
+#include "oni.h"
+
+#include "entities.h"
+#include "et_oni.h"
+#include "et_player.h"
+
+void draw_oni() {
+ put_sprite4(oni[*it_frame], *it_x, *it_y, 2, *it_param);
+ erase_sprite(*it_ox, *it_oy, 2);
+}
+
+void update_oni() {
+ if (++(*it_delay) > 32)
+ {
+ if ((*it_param && *it_x < px)
+ || (!*it_param && *it_x > px))
+ {
+ *it_extra = 4;
+ *it_frame = 1;
+
+ if (*it_delay == 33)
+ PLW_PlaySoundEffectP(EFX_ONI);
+
+ if (*it_delay > 48)
+ {
+ *it_delay = 0;
+ *it_frame = 0;
+ *it_extra = 2;
+ }
+ }
+ else
+ --(*it_delay);
+ }
+
+ if (*it_param)
+ {
+ if (is_map_blocked(*it_x + 11, *it_y + 8) || *it_x > TW * TMW - 8 - 4)
+ goto oni_change_dir;
+ else
+ *it_x += *it_extra;
+ }
+ else
+ {
+ if (is_map_blocked(*it_x - 4, *it_y + 8) || *it_x < 4)
+ goto oni_change_dir;
+ else
+ *it_x -= *it_extra;
+ }
+
+ if (check_for_player(16))
+ {
+ player_hit(1);
+ *it_frame = 0;
+ *it_extra = 2;
+ *it_delay = 0;
+oni_change_dir:
+ *it_param ^= 128;
+ }
+}
diff --git a/src/et_oni.h b/src/et_oni.h
new file mode 100644
index 0000000..ac09bc5
--- /dev/null
+++ b/src/et_oni.h
@@ -0,0 +1,25 @@
+/*
+ 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/>.
+ */
+#ifndef _ONI_H
+#define _ONI_H
+
+void draw_oni();
+void update_oni();
+
+#endif // _ONI_H
+
diff --git a/src/et_pickups.c b/src/et_pickups.c
new file mode 100644
index 0000000..58e3b25
--- /dev/null
+++ b/src/et_pickups.c
@@ -0,0 +1,191 @@
+/*
+ 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 "splib.h"
+#include "plw.h"
+#include "sound.h"
+#include "main.h"
+#include "int.h"
+
+#include "maps.h"
+
+// generated
+#include "items.h"
+#include "tiles.h"
+
+#include "entities.h"
+#include "et_player.h"
+#include "et_pickups.h"
+
+void draw_pickup()
+{
+ if (!is_invalid_tile2(get_tile_xy(*it_x >> 3, *it_y >> 3)))
+ return;
+
+ put_sprite4(items[sp_it->type - ET_GEM], *it_x, *it_y, 2, 0);
+}
+
+void draw_gtail()
+{
+ put_sprite4(items[0], *it_x, *it_y, 2, 0);
+ erase_sprite(*it_ox, *it_oy, 2);
+}
+
+static const int8_t gtail_y[9] = {
+ 0, 0, -1, -1, -2, -3, -2, -1, -1
+};
+
+static const int8_t gtails[16] = {
+ -1, 0,
+ -1, -1,
+ 0, -1,
+ 1, -1,
+ 1, 0,
+ 1, 1,
+ 0, 1,
+ -1, 1,
+ };
+
+void _gtail_effect()
+{
+ uint8_t i;
+ int8_t x, y;
+
+ for (i = 0; i < 5; ++i)
+ {
+ for (it_k = 0; it_k < 16; it_k += 2)
+ {
+ x = (gtails[it_k] << i);
+ y = (gtails[it_k + 1] << i);
+
+ put_sprite4(items[0], *it_x + x, *it_y + y, 2, 0);
+
+ if (i)
+ {
+ x >>= 1;
+ y >>= 1;
+ erase_sprite(*it_x + x, *it_y + y, 2);
+ }
+ }
+
+ if (i == 2)
+ set_hw_ink(0, 0x4b);
+
+ update_screen();
+ wait();
+ }
+
+ wait_for(8);
+ set_hw_ink(0, 0x54);
+ draw_map();
+}
+
+void update_gtail()
+{
+ if ((*it_delay)++ == 2)
+ {
+ *it_delay = 0;
+ if (++(*it_frame) == 9)
+ {
+ *it_param ^= 128;
+ if (*it_param)
+ *it_frame = 2;
+ else
+ *it_frame = 0;
+ }
+
+ if (*it_param)
+ *it_y -= gtail_y[*it_frame];
+ else
+ *it_y += gtail_y[*it_frame];
+ }
+
+ if (!check_for_player(16))
+ return;
+
+ PLW_PlaySoundEffectP(EFX_MAGIC);
+ PLW_Init(songs, SONG_SPLIT);
+
+ fill_screen(bgtiles[9]);
+
+ // extra live
+ ++lives;
+ life = MAX_LIFE;
+
+ gems = 30;
+ gtail = 1;
+ draw_hud_bg();
+ draw_hud();
+ wait();
+
+ *it_oy = *it_y;
+
+ for (it_k = 0; it_k < 60; ++it_k)
+ {
+ if (it_k & 1)
+ {
+ --gems;
+ draw_hud();
+ }
+
+ *it_ox = *it_x;
+ *it_x ^= 2;
+ put_sprite4(items[0], *it_x, *it_y, 2, 0);
+ erase_sprite(*it_ox, *it_oy, 2);
+
+ update_screen();
+ wait();
+ }
+
+
+ destroy_entity();
+ update_persistence(sp_it->id);
+
+ _gtail_effect();
+
+ PLW_Init(songs, SONG_INGAME);
+}
+
+void update_pickup()
+{
+ if (!check_for_player(16))
+ return;
+
+ switch (sp_it->type)
+ {
+ case ET_POTION:
+ if (life == MAX_LIFE)
+ return;
+ life = MAX_LIFE;
+ break;
+ case ET_KEY:
+ ++keys;
+ break;
+ case ET_GEM:
+ ++gems;
+ break;
+ }
+
+ erase_sprite(*it_x, *it_y, 2);
+ destroy_entity();
+
+ update_persistence(sp_it->id);
+
+ PLW_PlaySoundEffectP(EFX_PICKUP);
+
+ draw_hud();
+}
diff --git a/src/et_pickups.h b/src/et_pickups.h
new file mode 100644
index 0000000..1711833
--- /dev/null
+++ b/src/et_pickups.h
@@ -0,0 +1,28 @@
+/*
+ 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/>.
+ */
+#ifndef _PICKUPS_H
+#define _PICKUPS_H
+
+void draw_pickup();
+void update_pickup();
+
+void draw_gtail();
+void update_gtail();
+
+#endif // _PICKUPS_H
+
diff --git a/src/et_platform.c b/src/et_platform.c
new file mode 100644
index 0000000..0eab3cc
--- /dev/null
+++ b/src/et_platform.c
@@ -0,0 +1,75 @@
+/*
+ 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 "splib.h"
+#include "main.h"
+
+#include "maps.h"
+
+// generated
+#include "platform.h"
+
+#include "entities.h"
+#include "et_player.h"
+#include "et_platform.h"
+
+void draw_platform() {
+ put_sprite4(platform[*it_frame], *it_x, *it_y, 1, 0);
+ put_sprite4(platform[*it_frame], *it_x + 8, *it_y, 1, 0);
+ erase_sprite(*it_ox, *it_oy, 1);
+ erase_sprite(*it_ox + 8, *it_oy, 1);
+}
+
+void update_platform() {
+
+ // check limit and change direction
+ if ((*it_delay)++ >= *it_extra)
+ {
+ *it_delay = 1;
+ *it_param ^= 128;
+ }
+
+ // track player if on the platform
+ if (check_for_point(sp_it, px + 1, py + 24, 16, 8)
+ || check_for_point(sp_it, px + 6, py + 24, 16, 8))
+ {
+ py = *it_y - 24;
+
+ if (*it_delay & 1)
+ return;
+
+ if (*it_param)
+ {
+ if (!is_map_blocked(px + 8, py + (player_h ? 6 : 0))
+ && !is_map_blocked(px + 8, py + 23))
+ px += 2;
+ }
+ else
+ {
+ if (!is_map_blocked(px - 1, py + (player_h ? 6 : 0))
+ && !is_map_blocked(px - 1, py + 23))
+ px -= 2;
+ }
+ }
+ else if (*it_delay & 1)
+ return;
+
+ if (*it_param)
+ *it_x += 2;
+ else
+ *it_x -= 2;
+}
diff --git a/src/et_platform.h b/src/et_platform.h
new file mode 100644
index 0000000..b63961d
--- /dev/null
+++ b/src/et_platform.h
@@ -0,0 +1,25 @@
+/*
+ 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/>.
+ */
+#ifndef _PLATFORM_H
+#define _PLATFORM_H
+
+void draw_platform();
+void update_platform();
+
+#endif // _PLATFORM_H
+
diff --git a/src/et_player.c b/src/et_player.c
new file mode 100644
index 0000000..d58d656
--- /dev/null
+++ b/src/et_player.c
@@ -0,0 +1,551 @@
+/*
+ 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 <stdlib.h>
+
+#include "cpcrslib/cpcrslib.h"
+#include "splib.h"
+#include "plw.h"
+#include "sound.h"
+#include "main.h"
+
+#include "maps.h"
+#include "stage.h"
+
+// generated
+#include "player.h"
+#include "explo.h"
+
+#include "entities.h"
+#include "et_effect.h"
+
+#define LOCAL
+#include "et_player.h"
+
+void player_checkpoint()
+{
+#ifdef DEBUG
+ char b[4];
+#endif
+
+ // don't do it if not solid or platform
+ if (is_map_blocked(px + 3, py + 24) == 1)
+ {
+ smap = cmap;
+ spx = px;
+ spy = py;
+ sdir = dir;
+
+#ifdef DEBUG
+ set_text_ink(15, 15, 15);
+ put_text("C", 10, 192);
+ pad_numbers(b, 3, px);
+ put_text(b, 13, 192);
+ pad_numbers(b, 3, py);
+ put_text(b, 20, 192);
+#endif
+ }
+}
+
+void player_hit(uint8_t h)
+{
+ life = life > h ? life - h : 0;
+ if (!life)
+ {
+ // dead
+
+ // instant death cases control the frame
+ // otherwise set it here
+ if (h != MAX_LIFE)
+ frame = WALK + 1;
+
+ erase_sprite(px, py, 3);
+ was_hit = 0;
+ respawn_delay = RESPAWN_DELAY;
+
+ // not water
+ if (last_deadly != DEADLY_TILE_BASE)
+ PLW_PlaySoundEffectP(EFX_DEATH);
+
+ if (lives == 1)
+ PLW_Init(songs, SONG_SILENCE);
+ }
+ else
+ {
+ // only hit
+ was_hit = WAS_HIT;
+
+ if (*it_x > px)
+ dir = DIR_RIGHT;
+ else
+ dir = DIR_LEFT;
+
+ PLW_PlaySoundEffectP(EFX_DAMAGE);
+ }
+
+ draw_hud();
+}
+
+void draw_player()
+{
+ if ((!was_hit || was_hit & 2) && !magic && life)
+ put_sprite4(player[player_frames[frame]], px, py, 3, dir);
+ else if (magic)
+ put_sprite4(explo[2], px, py + 6, 2, magic & 1);
+
+ if (!life && frame && respawn_delay > 2 && respawn_delay & 2)
+ put_sprite4(player[player_frames[frame]], px, py, 3, dir);
+
+ erase_sprite(opx, opy, 3);
+
+ opx = px;
+ opy = py;
+}
+
+void update_player()
+{
+ // help is_map_blocked
+ sp_it = NULL;
+ moved = 0;
+
+ if (respawn_delay)
+ {
+ if (--respawn_delay)
+ return;
+
+ --lives;
+ life = lives ? MAX_LIFE : 0;
+ draw_hud();
+
+ if (!lives)
+ {
+ gameover_delay = GAMEOVER_DELAY;
+ return;
+ }
+
+ if (cmap != smap)
+ init_map(smap);
+ px = spx;
+ py = spy;
+ dir = sdir;
+ frame = WALK;
+ player_h = 0;
+ was_hit = RESPAWN_DELAY;
+ return;
+ }
+
+ if (!lives)
+ return;
+
+ if (was_hit)
+ {
+ --was_hit;
+
+ // jump after being hit
+ if (was_hit == WAS_HIT - 1)
+ {
+ jump_flag = 1;
+ player_h = 2;
+ }
+
+ // jump back after being hit
+ if (was_hit > RESPAWN_DELAY)
+ {
+ moved = 1;
+ frame = WALK + 1;
+
+ if (dir)
+ {
+ if (px < TW * TMW - 8
+ && !is_map_blocked(px + 8, py + 23)
+ && !is_map_blocked(px + 8, py + (player_h ? 6 : 0)))
+ px += 2;
+
+ if (px == TW * TMW - 8 && exit_map_right())
+ return;
+ }
+ else
+ {
+ if (px > 0
+ && !is_map_blocked(px - 1, py + 23)
+ && !is_map_blocked(px - 1, py + (player_h ? 6 : 0)))
+ px -= 2;
+
+ if (!px && exit_map_left())
+ return;
+ }
+ }
+ }
+
+ // check for gravity
+ if (!player_h
+ && !is_map_blocked(px + 1, py + 24)
+ && !is_map_blocked(px + 6, py + 24))
+ {
+ // start falling
+ player_h = JUMP_SEQ - 2;
+ frame = JUMP;
+ moved = 1;
+ }
+
+ if (cpc_TestKeyF(KEY_DOWN) && frame != JUMP)
+ {
+ if (frame != CROUCH)
+ frame = CROUCH;
+ }
+ else
+ {
+ // wake up
+ if (frame == CROUCH)
+ frame = WALK;
+ }
+
+ if (cpc_TestKeyF(KEY_UP))
+ {
+ if (!player_h && !jump_flag
+ // no magic or magic just started
+ && (!magic || magic > COOL_DOWN - 2))
+ {
+ jump_flag = 1;
+ player_h = 1;
+ frame = JUMP;
+ }
+ }
+ else
+ {
+ // faster than always set to 0
+ if (jump_flag)
+ jump_flag = 0;
+ }
+
+ if (cpc_TestKeyF(KEY_RIGHT) && !cpc_TestKeyF(KEY_LEFT))
+ {
+ if (dir)
+ dir = DIR_RIGHT;
+
+ if (frame != CROUCH)
+ {
+ moved = 1;
+
+ if (px < TW * TMW - 8
+ && !is_map_blocked(px + 8, py + 23)
+ && !is_map_blocked(px + 8, py + (player_h ? 6 : 0)))
+ px += 2;
+
+ if (magic && px < TW * TMW - 8
+ && !is_map_blocked(px + 8, py + 23)
+ && !is_map_blocked(px + 8, py + (player_h ? 6 : 0)))
+ px += 2;
+
+ if (px == TW * TMW - 8 && exit_map_right())
+ return;
+ }
+ }
+
+ if (cpc_TestKeyF(KEY_LEFT) && !cpc_TestKeyF(KEY_RIGHT))
+ {
+ if (!dir)
+ dir = DIR_LEFT;
+
+ if (frame != CROUCH)
+ {
+ moved = 1;
+
+ if (px > 0
+ && !is_map_blocked(px - 1, py + 23)
+ && !is_map_blocked(px - 1, py + (player_h ? 6 : 0)))
+ px -= 2;
+
+ if (magic && px > 0
+ && !is_map_blocked(px - 1, py + 23)
+ && !is_map_blocked(px - 1, py + (player_h ? 6 : 0)))
+ px -= 2;
+
+ if (!px && exit_map_left())
+ return;
+ }
+ }
+
+ if (magic)
+ {
+ if (magic-- == 2)
+ new_explo();
+
+ if (!magic
+ && (is_map_deadly(px + 3, py + 24)
+ || is_map_deadly(px + 3, py + (frame == CROUCH ? 6 : 0))
+ ))
+ {
+ if (last_deadly == DEADLY_TILE_BASE)
+ {
+ frame = 0;
+ new_splash();
+ }
+ else
+ frame = WALK + 1;
+ player_hit(MAX_LIFE);
+ return;
+ }
+ }
+ else
+ {
+ if (cool_down)
+ --cool_down;
+
+ if (cpc_TestKeyF(KEY_FIRE) && !cool_down)
+ {
+ magic = COOL_DOWN;
+ cool_down = COOL_DOWN + 2;
+ new_explo();
+ }
+ }
+
+ if (player_h)
+ {
+ // jumping
+ it_k = jump_seq[player_h - 1];
+
+ if (IS_GOING_DOWN(player_h))
+ {
+ // down
+ while (it_k && py < TH * TMH - 24)
+ {
+ // potentially hit floor
+ if ((is_map_blocked(px + 1, py + 24)
+ || is_map_blocked(px + 6, py + 24)))
+ {
+ // process deadly first
+ if (is_map_deadly(px + 1, py + 24)
+ || is_map_deadly(px + 6, py + 24))
+ {
+ if (!magic)
+ {
+ if (last_deadly == DEADLY_TILE_BASE)
+ {
+ frame = 0;
+ new_splash();
+ }
+ else
+ frame = WALK + 1;
+ player_hit(MAX_LIFE);
+ }
+ break;
+ }
+
+ // avoid getting *inside* the platform
+ if (!is_map_blocked(px + 1, py + 22)
+ && !is_map_blocked(px + 6, py + 22))
+ {
+ // hit floor
+ player_h = 0;
+ frame = WALK;
+
+ player_checkpoint();
+
+ if (!magic)
+ PLW_PlaySoundEffectP(EFX_HIT);
+ break;
+ }
+ }
+
+ py += 2;
+ it_k -= 2;
+ }
+
+ if (py == TH * TMH - 24 && exit_map_down())
+ return;
+ }
+ else
+ {
+ // up
+ while (it_k && py)
+ {
+ // jumping back after being hit
+ if (was_hit > RESPAWN_DELAY)
+ {
+ if (is_map_blocked(px + 1, py)
+ || is_map_blocked(px + 6, py))
+ break;
+ }
+
+ if (is_map_blocked(px + 1, py + 4)
+ || is_map_blocked(px + 6, py + 4))
+ {
+ if (!magic)
+ {
+ if (is_map_deadly(px + 3, py + 4))
+ {
+ frame = WALK + 1;
+ player_hit(MAX_LIFE);
+ }
+ else if (jump_flag == 1)
+ {
+ jump_flag = 2;
+ PLW_PlaySoundEffectP(EFX_HIT);
+ }
+ }
+ break;
+ }
+
+ py -= 2;
+ it_k -= 2;
+ }
+
+ if (py == 0 && exit_map_up())
+ return;
+ }
+
+ if (player_h && player_h < JUMP_SEQ
+ // extra boost with magic but not first step
+ && (!(magic & 1) || player_h == 1))
+ player_h++;
+ }
+ else
+ {
+ // not jumping
+ if (moved)
+ {
+ if (!magic && (is_map_deadly(px + 3, py + 24)
+ || is_map_deadly(px + 3, py)))
+ {
+ if (last_deadly == DEADLY_TILE_BASE)
+ {
+ frame = 0;
+ new_splash();
+ }
+ else
+ frame = WALK + 1;
+ player_hit(MAX_LIFE);
+ return;
+ }
+
+ if (wdelay++)
+ {
+ wdelay = 0;
+ // frame increase
+ if (frame != JUMP && ++frame > WALK_CYCLE)
+ frame = WALK;
+ }
+ }
+ else
+ {
+ // frame stopped
+ if (frame > WALK && frame < CROUCH)
+ {
+ frame = WALK;
+ wdelay = 0;
+ }
+ }
+ }
+}
+
+#pragma save
+#pragma disable_warning 85
+#pragma disable_warning 59
+uint8_t check_for_player(uint8_t h) __z88dk_fastcall
+{
+ /*
+ if (magic || was_hit || !life)
+ return 0;
+
+ if (frame == CROUCH)
+ {
+ if (py + 8 < sp_it->y + h && sp_it->y < py + 24
+ && px + 2 < sp_it->x + w && sp_it->x < px + 6)
+ return 1;
+ }
+ else
+ {
+ if (py < sp_it->y + h && sp_it->y < py + 24
+ && px + 2 < sp_it->x + w && sp_it->x < px + 6)
+ return 1;
+ }
+
+ return 0;
+ */
+
+ // *INDENT-OFF*
+ __asm;
+ ld e, #8 ; w
+ ld d, l ; h
+
+ ld a, (_magic)
+ or a
+ jr nz, check_for_player_false
+ ld a, (_was_hit)
+ or a
+ jr nz, check_for_player_false
+ ld a, (_life)
+ or a
+ jr z, check_for_player_false
+
+ ld hl, (_sp_it)
+ ld bc, #6
+ add hl, bc
+ ld a, (hl) ; y
+ add d
+ ld c, a
+
+ ; b is 0
+ ld a, (_frame)
+ cp #CROUCH
+ jr nz, not_crouching
+
+ ld b, #8
+
+ not_crouching:
+ ld a, (_py)
+ add a, b
+ cp c
+ jr nc, check_for_player_false
+
+ ld a, (_py)
+ add #24
+ ld c, a
+
+ ld a, (hl) ; y
+ cp c
+ jr nc, check_for_player_false
+
+ dec hl
+ dec hl
+ ld a, (hl) ; x
+ add e
+ ld c, a
+
+ ld a, (_px)
+ add #2
+ cp c
+ jr nc, check_for_player_false
+
+ ld a, (_px)
+ add #6
+ ld c, a
+
+ ld a, (hl) ; x
+ cp c
+ jr nc, check_for_player_false
+
+ ld l, #1
+ ret
+
+ check_for_player_false:
+ ld l, #0
+ __endasm;
+// *INDENT-ON*
+}
+#pragma restore
diff --git a/src/et_player.h b/src/et_player.h
new file mode 100644
index 0000000..a72aa28
--- /dev/null
+++ b/src/et_player.h
@@ -0,0 +1,65 @@
+/*
+ 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/>.
+ */
+#ifndef _PLAYER_H
+#define _PLAYER_H
+
+#include <stdint.h>
+
+#define WALK 0
+#define WALK_CYCLE 3
+#define CROUCH 4
+#define JUMP 5
+
+#define DIR_RIGHT 0
+#define DIR_LEFT 1
+#define DIR_DOWN 2
+#define DIR_UP 3
+
+#ifndef LOCAL
+#define LOCAL extern
+extern const uint8_t player_frames[];
+extern const uint8_t jump_seq[];
+#else
+const uint8_t player_frames[6] = { 1, 0, 1, 2, 3, 3 };
+const uint8_t jump_seq[8] = { 14, 12, 4, 2, 0, 4, 8, 12 };
+#endif
+
+#define JUMP_SEQ 8
+#define IS_GOING_DOWN(x) ((x) > 5)
+
+
+#define COOL_DOWN 10
+#define RESPAWN_DELAY 16
+#define WAS_HIT 24
+#define GAMEOVER_DELAY 48
+
+#define MAX_LIFE 5
+
+void player_checkpoint();
+
+void draw_player();
+void update_player();
+
+void player_hit(uint8_t h);
+
+uint8_t check_for_player(uint8_t h) __z88dk_fastcall;
+
+#undef LOCAL
+
+#endif // _PLAYER_H
+
diff --git a/src/et_spider.c b/src/et_spider.c
new file mode 100644
index 0000000..8373d79
--- /dev/null
+++ b/src/et_spider.c
@@ -0,0 +1,102 @@
+/*
+ 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 "splib.h"
+#include "plw.h"
+#include "sound.h"
+#include "main.h"
+
+#include "maps.h"
+
+// generated
+#include "spider.h"
+
+#include "entities.h"
+#include "et_spider.h"
+#include "et_player.h"
+
+void draw_spider()
+{
+ if (!*it_frame)
+ put_sprite4(spider[1], *it_x, *it_y, 1, 0);
+ else
+ {
+ // draws the thread
+ if (!(*it_param & 128))
+ it_k = *it_param & 127;
+ else
+ it_k = *it_extra - (*it_param & 127);
+
+ it_k >>= 3;
+ while (it_k)
+ {
+ put_sprite4(spider[2], *it_x, *it_y - (it_k << 3), 1, 0);
+ --it_k;
+ }
+
+ put_sprite4(spider[0], *it_x, *it_y, 2, 0);
+ erase_sprite(*it_ox, *it_oy, 2);
+ }
+}
+
+void update_spider()
+{
+ // waiting
+ if (!*it_frame)
+ {
+ if (!magic && life && py > *it_y && abs_sub(*it_x, px) < 9 && abs_sub(*it_y, py) < 48)
+ {
+ *it_frame = 1;
+ PLW_PlaySoundEffectP(EFX_SPIDER);
+ }
+ else
+ return;
+ }
+
+ // limit and change direction
+ if ((*it_param & 127) == *it_extra)
+ {
+ // back up
+ if (*it_param & 128)
+ {
+ erase_sprite(*it_x, *it_y + 8, 1);
+ *it_frame = 0;
+ *it_param = 0;
+ goto spider_done;
+ }
+ else
+ // touch bottom
+ *it_param = 128;
+ }
+
+ if (*it_param & 128)
+ {
+ *it_y -= 1;
+ *it_param += 1;
+ }
+ else
+ {
+ *it_y += 8;
+ *it_param += 8;
+ }
+
+spider_done:
+
+ // only going down
+ if (!(*it_param & 128) && check_for_player(16))
+ player_hit(1);
+}
diff --git a/src/et_spider.h b/src/et_spider.h
new file mode 100644
index 0000000..d18dc21
--- /dev/null
+++ b/src/et_spider.h
@@ -0,0 +1,25 @@
+/*
+ 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/>.
+ */
+#ifndef _SPIDER_H
+#define _SPIDER_H
+
+void draw_spider();
+void update_spider();
+
+#endif // _SPIDER_H
+
diff --git a/src/et_spirit.c b/src/et_spirit.c
new file mode 100644
index 0000000..ff2fac6
--- /dev/null
+++ b/src/et_spirit.c
@@ -0,0 +1,55 @@
+/*
+ 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 "splib.h"
+#include "main.h"
+
+// generated
+#include "spirit.h"
+
+#include "entities.h"
+#include "et_spirit.h"
+#include "et_player.h"
+#include "et_common.h"
+
+void draw_spirit()
+{
+ put_sprite4(spirit[player_frames[*it_frame]], *it_x, *it_y, 3, *it_param & 128);
+ erase_sprite(*it_ox, *it_oy, 3);
+}
+
+void update_spirit()
+{
+ if ((*it_delay)++ == 2)
+ {
+ *it_delay = 0;
+ if (++(*it_frame) > WALK_CYCLE)
+ *it_frame = WALK;
+ }
+
+ update_fixed_common();
+
+ if (check_for_player(24))
+ {
+ player_hit(1);
+ it_k = *it_param & 128;
+ *it_param = *it_extra - (*it_param & 127);
+ *it_param |= it_k ^ 128;
+ *it_delay = 0;
+ *it_frame = WALK;
+ }
+}
diff --git a/src/et_spirit.h b/src/et_spirit.h
new file mode 100644
index 0000000..5bb5852
--- /dev/null
+++ b/src/et_spirit.h
@@ -0,0 +1,25 @@
+/*
+ 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/>.
+ */
+#ifndef _SPIRIT_H
+#define _SPIRIT_H
+
+void draw_spirit();
+void update_spirit();
+
+#endif // _SPIRIT_H
+
diff --git a/src/et_switch.c b/src/et_switch.c
new file mode 100644
index 0000000..543ba25
--- /dev/null
+++ b/src/et_switch.c
@@ -0,0 +1,103 @@
+/*
+ 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 "cpcrslib/cpcrslib.h"
+#include "splib.h"
+#include "plw.h"
+#include "sound.h"
+#include "main.h"
+
+#include "maps.h"
+
+// generated
+#include "switch.h"
+#include "tiles.h"
+
+#include "entities.h"
+#include "et_player.h"
+#include "et_switch.h"
+
+void update_switch_door(uint8_t x, uint8_t y, uint8_t t)
+{
+ x >>= 3;
+ y >>= 3;
+
+ for (it_k = 0; it_k < 4; ++it_k)
+ {
+ put_tile(bgtiles[t], get_tile_xy(x, y));
+ set_map_tile(x, y++, t);
+ }
+
+ put_tile(bgtiles[BLOCKED_OPEN_DOOR_TILE], get_tile_xy(x, y));
+ set_map_tile(x, y++, BLOCKED_OPEN_DOOR);
+}
+
+void draw_switch()
+{
+ if (is_invalid_tile2(get_tile_xy(*it_x >> 3, *it_y >> 3)))
+ put_sprite4(switch_sprite[0], *it_x, *it_y, 2, *it_frame & 1);
+}
+
+void update_switch()
+{
+ if (*it_delay)
+ {
+ *it_delay -= 1;
+
+ if (*it_delay & 1)
+ {
+ if (!(*it_frame & 254))
+ {
+ *it_frame += 6;
+ if (*it_frame & 2)
+ PLW_PlaySoundEffectP(EFX_TICK);
+ }
+ else
+ *it_frame -= 2;
+ }
+
+ if (!*it_delay)
+ {
+ erase_sprite(*it_x, *it_y, 2);
+
+ if (!magic && life
+ && py < *it_extra + 32 && *it_extra < py + 24
+ && px + 2 < *it_param + 8 && *it_param < px + 6)
+ {
+ frame = WALK + 1;
+ player_hit(MAX_LIFE);
+ }
+
+ goto switch_toggle;
+ }
+ }
+
+ if (!check_for_player(16) || (*it_frame & 1))
+ return;
+
+ if (!player_h && cpc_TestKeyF(KEY_DOWN))
+ {
+ *it_delay = 64;
+
+switch_toggle:
+ *it_frame &= 1;
+ *it_frame ^= 1;
+
+ update_switch_door(*it_param, *it_extra, *it_frame & 1 ? 42 : SWITCH_DOOR_TILE);
+ PLW_PlaySoundEffectP(EFX_DOOR);
+ }
+}
diff --git a/src/et_switch.h b/src/et_switch.h
new file mode 100644
index 0000000..e15eedc
--- /dev/null
+++ b/src/et_switch.h
@@ -0,0 +1,27 @@
+/*
+ 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/>.
+ */
+#ifndef _SWITCH_H
+#define _SWITCH_H
+
+void draw_switch();
+void update_switch();
+
+void update_switch_door(uint8_t x, uint8_t y, uint8_t t);
+
+#endif // _SWITCH_H
+
diff --git a/src/et_vampire.c b/src/et_vampire.c
new file mode 100644
index 0000000..eb62699
--- /dev/null
+++ b/src/et_vampire.c
@@ -0,0 +1,83 @@
+/*
+ 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 "splib.h"
+#include "main.h"
+
+#include "maps.h"
+
+// generated
+#include "vampire.h"
+
+#include "entities.h"
+#include "et_vampire.h"
+#include "et_player.h"
+
+void draw_vampire()
+{
+ put_sprite4(vampire[player_frames[*it_frame]], *it_x, *it_y, 2, 0);
+ erase_sprite(*it_ox, *it_oy, 2);
+}
+
+void update_vampire()
+{
+ // waiting
+ if (*it_frame == 4)
+ {
+ if (!magic && py > *it_y && abs_sub(*it_x, px) < 16 && abs_sub(*it_y, py) < 48)
+ *it_frame = 0;
+ else
+ return;
+ }
+
+ if ((*it_delay)++)
+ {
+ *it_delay = 0;
+ if (++(*it_frame) > WALK_CYCLE)
+ *it_frame = 0;
+ }
+
+ // don't chase the player if dead
+ if (!life)
+ return;
+
+ if (*it_x > px && !is_map_blocked(*it_x - 1, *it_y + 8))
+ *it_x -= 1;
+ if (*it_x < px && !is_map_blocked(*it_x + 8, *it_y + 8))
+ *it_x += 1;
+
+ if (*it_extra)
+ {
+ --(*it_extra);
+ if (*it_y && !is_map_blocked(*it_x + 4, *it_y - 1))
+ *it_y -= 1;
+ }
+ else
+ if (abs_sub(*it_x, px) < 64)
+ {
+ if (*it_y > py && !is_map_blocked(*it_x + 4, *it_y - 1))
+ *it_y -= 2;
+ if (*it_y < py && !is_map_blocked(*it_x + 4, *it_y + 16))
+ *it_y += 2;
+ }
+
+ if (check_for_player(16))
+ {
+ player_hit(1);
+ *it_extra = WAS_HIT << 1;
+ }
+}
diff --git a/src/et_vampire.h b/src/et_vampire.h
new file mode 100644
index 0000000..ecdb975
--- /dev/null
+++ b/src/et_vampire.h
@@ -0,0 +1,25 @@
+/*
+ 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/>.
+ */
+#ifndef _VAMPIRE_H
+#define _VAMPIRE_H
+
+void draw_vampire();
+void update_vampire();
+
+#endif // _VAMPIRE_H
+
diff --git a/src/int.h b/src/int.h
new file mode 100644
index 0000000..8b779b3
--- /dev/null
+++ b/src/int.h
@@ -0,0 +1,45 @@
+/*
+ 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/>.
+ */
+#ifndef _INT_H
+
+#include <stdint.h>
+
+extern uint8_t int6;
+extern uint8_t tick;
+extern uint8_t plw_frame;
+extern uint8_t wframes;
+
+void wait();
+void wait_for(uint8_t t) __z88dk_fastcall;
+void wait_int6();
+void setup_int(uint8_t frames) __z88dk_fastcall;
+
+// must be 0xabab
+#define MAX_CLOCK 0x0e0e
+
+void pause_player();
+void resume_player();
+
+void reset_clock();
+uint16_t get_clock();
+
+#undef LOCAL
+
+#endif // _INT_H
+
+
diff --git a/src/int.z80 b/src/int.z80
new file mode 100644
index 0000000..9290d72
--- /dev/null
+++ b/src/int.z80
@@ -0,0 +1,262 @@
+;;
+;; 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/>.
+;;
+
+.globl _wait
+.globl _wait_for
+.globl _wait_int6
+.globl _setup_int
+.globl _wait_vsync
+.globl _wframes
+
+.globl _pause_player
+.globl _resume_player
+
+.globl _reset_clock
+.globl _get_clock
+
+.globl _paused
+
+MAX_CLOCK_BYTE = 0x0e
+
+_wait::
+ ld hl, #_tick
+
+ ld a, (_wframes)
+ ld c, a
+wait_loop:
+ ld a, (hl)
+ cp c
+ jr nc, wait_done
+ halt
+ jr wait_loop
+
+wait_done:
+ xor a
+ ld (hl), a
+ ret
+
+_wait_for::
+ ld b, l
+wait_for_loop:
+ call _wait
+ djnz wait_for_loop
+ ret
+
+_wait_int6::
+ ld a, (_int6)
+ or a
+ ret nz
+ halt
+ jr _wait_int6
+ ret
+
+_setup_int::
+ ld a, l
+ ld (_wframes), a
+
+ xor a
+ ld (_int6), a
+ ld (_tick), a
+ ld (paused_player), a
+
+ ld (_paused), a
+ call _reset_clock
+
+ ; sync
+ call _wait_vsync
+ halt
+ halt
+ call _wait_vsync
+
+ di
+
+ ld ix, #0x0038
+ ld hl, #isr1
+ ld (ix), #0xc3
+ ld 1(ix), l
+ ld 2(ix), h
+ im 1
+
+ ei
+ ret
+
+isr1:
+ ex af,af
+ push hl
+
+ ld hl, #isr2
+ ld (#0x39), hl
+
+ ; int6 ends
+ xor a
+ ld (_int6), a
+
+ ld a, (paused_player)
+ or a
+ jr nz, player_is_paused
+
+ push ix
+ push iy
+ push bc
+ push de
+
+ call _PLW_Play
+
+ pop de
+ pop bc
+ pop iy
+ pop ix
+
+player_is_paused:
+ pop hl
+ ex af,af
+ ei
+ ret
+
+isr2:
+ exx
+ ex af,af
+ ld hl, #isr3
+ ld (#0x39), hl
+ exx
+ ex af,af
+ ei
+ ret
+
+isr3:
+ exx
+ ex af,af
+ ld hl, #isr4
+ ld (#0x39), hl
+ ex af,af
+ exx
+ ei
+ ret
+
+isr4:
+ exx
+ ex af,af
+ ld hl, #isr5
+ ld (#0x39), hl
+
+ ; update clock
+ ld a, (_paused)
+ or a
+ jr nz, clock_is_paused
+ ld a, (int_cnt)
+ inc a
+ cp #50
+ jr nz, not_full_sec
+
+ ld hl, (clock)
+ ld a, h
+ cp #(MAX_CLOCK_BYTE)
+ jr nz, inc_clock
+ cp l
+ jr z, clock_limit
+
+inc_clock:
+ inc hl
+ ld (clock), hl
+
+clock_limit:
+ xor a
+
+not_full_sec:
+ ld (int_cnt), a
+
+clock_is_paused:
+ ex af,af
+ exx
+ ei
+ ret
+
+isr5:
+ exx
+ ex af,af
+ ld hl, #isr6
+ ld (#0x39), hl
+
+ ; count a tick (50Hz)
+ ld a, (_tick)
+ inc a
+ ld (_tick), a
+
+ ex af,af
+ exx
+ ei
+ ret
+
+isr6:
+ exx
+ ex af,af
+ ld hl, #isr1
+ ld (#0x39), hl
+
+ ; int6 starts
+ ld a, #1
+ ld (_int6), a
+
+ exx
+ ex af,af
+ ei
+ ret
+
+_pause_player::
+ di
+ ld a, #1
+ ld (paused_player), a
+ ei
+ call _wait_int6
+ jp _PLW_Stop
+
+_resume_player::
+ di
+ xor a
+ ld (paused_player), a
+ ei
+ ret
+
+_reset_clock::
+ di
+ xor a
+ ld (int_cnt), a
+ ld (clock), a
+ ld (clock + 1), a
+ ei
+ ret
+
+_get_clock::
+ ld hl, (clock)
+ ret
+
+_wframes:
+ .ds 1
+
+_int6:
+ .ds 1
+_tick:
+ .ds 1
+
+int_cnt:
+ .ds 1
+clock:
+ .ds 2
+
+paused_player:
+ .ds 1
diff --git a/src/loader.s b/src/loader.s
new file mode 100644
index 0000000..a41d3ba
--- /dev/null
+++ b/src/loader.s
@@ -0,0 +1,252 @@
+;;
+;; 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/>.
+;;
+;;
+;; TAPE LOADER
+;;
+
+.include "cpcfirm.inc"
+.include "../build/loader.opt"
+
+loader:
+
+.ifeq DISK
+ ; loading from disk
+
+ ld hl, (#0xbe7d) ; save current drive
+ ld a, (hl)
+ ld (#drive+1), a
+.endif ; end disk code
+
+ ld c, #0xff
+ ld hl, #start
+ call mc_start_program
+
+start:
+ call kl_rom_walk
+
+.ifeq DISK
+drive:
+ ld a, #0 ; restore drive
+ ld hl, (#0xbe7d)
+ ld (hl), a
+.endif ; end disk code
+
+ ld bc, #0 ; set border
+ call scr_set_border
+
+ ld bc, #0 ; bg color
+ xor a
+ call scr_set_ink
+
+ xor a ; set mode 0
+ call scr_set_mode
+
+ ld a, #0xff
+ call cas_noisy ; disable tape texts
+
+.ifeq DISK
+ ; first file is the SCRX
+ call load_file
+
+.else ; tape code
+
+ ld ix, #TMP_ADDR
+ ld de, #SCRX_SIZE
+ call turboload
+
+.endif ; end tape code
+
+ ; setup the palette
+ ld hl, #TMP_ADDR + #4
+ call set_palette
+
+ ; border is already 0
+
+ ld hl, #TMP_ADDR + #0x14 ; compressed data
+ ld de, #0xc000 ; uncompress into the screen
+ call aplib_depack
+
+ ; loader size limited to 1024 bytes!
+LOADER_END = LOADER_ADDR + 1024
+
+.ifeq DISK
+
+ ld hl, #fname_end - #1
+ inc (hl)
+ ; load the code
+ call load_file
+
+.else ; tape code
+
+ ; change stripes
+ ld a, #20
+ ld (#turbo_col + 1), a
+
+ ; load the compressed app
+ ld ix, #LOADER_END
+ ld de, #APP_SIZE_PAK
+ call turboload
+
+.endif ; tape code ends
+
+ di
+ ; disable the firmware
+ im 1
+ ld hl, #0x38
+ ld (hl), #0xfb
+ inc hl
+ ld (hl), #0xc9
+
+ ; put the stack as high as we can
+ ld sp, #0xc000
+ ei
+
+ ; disable upper/lower roms
+ ld bc, #0x7f8c
+ out (c), c
+
+ ; relocate the compressed data
+ ld bc, #APP_SIZE_PAK
+ ld hl, #LOADER_END + #APP_SIZE_PAK
+ ld de, #LOADER_END + #APP_SIZE + #PAK_SLOP
+data_relocation_loop:
+ ld a, (hl)
+ ld (de), a
+ dec hl
+ dec de
+ ld a, b
+ or c
+ dec bc
+ jr nz, data_relocation_loop
+
+ ; uncompress after the loader
+ ld hl, #LOADER_END + #APP_SIZE - (#APP_SIZE_PAK - #PAK_SLOP)
+ ld de, #LOADER_END
+ call aplib_depack
+
+ ; relocate app to its final address
+ ld bc, #APP_SIZE
+ ld hl, #LOADER_END + #APP_SIZE
+ ld de, #APP_ADDR + #APP_SIZE
+relocation_loop:
+ ld a, (hl)
+ ld (de), a
+ dec hl
+ dec de
+ ld a, b
+ or c
+ dec bc
+ jr nz, relocation_loop
+
+ ; jp to the app entry point
+ .db #0xc3
+ .dw #APP_EP
+
+set_palette:
+ push hl
+ call wait_vsync
+
+ halt
+ halt
+
+ call wait_vsync
+ pop hl
+
+ xor a
+set_palette_loop:
+ push hl
+ push af
+ ld c, (hl)
+ ld b, c
+ call scr_set_ink
+ pop af
+ pop hl
+ inc hl
+ inc a
+ cp #0x10
+ jr nz, set_palette_loop
+
+ halt
+ halt
+ halt
+ halt
+ halt
+ halt
+ ret
+
+wait_vsync:
+ ld b, #0xf5
+ keep_waiting:
+ in a, (c)
+ rra
+ jr nc, keep_waiting
+ ret
+
+.ifeq DISK
+
+load_file:
+ ld hl, #fname
+ ld b, #fname_end-#fname
+
+ ld de, #0x400 ; temp mem (only used in tape mode)
+ call cas_in_open
+
+ push de
+ pop hl
+ call cas_in_direct
+
+ call cas_in_close
+ ret
+
+fname:
+ .str "MAIN.BI0"
+fname_end:
+
+.else ; tape code
+
+turboload:
+ di
+ ex af, af'
+ push af
+ ex af, af'
+ exx
+ push de
+ push bc
+ push hl
+ exx
+ xor a
+ ld r, a
+ dec a
+ call _turboload
+ jp nc, 0
+ exx
+ pop hl
+ pop bc
+ pop de
+ exx
+ ex af, af'
+ pop af
+ ex af, af'
+ ei
+ ret
+
+.include "turboload.s"
+.endif ; end tape code
+
+.area _DATA
+
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();
+ }
+}
diff --git a/src/main.h b/src/main.h
new file mode 100644
index 0000000..7d367c8
--- /dev/null
+++ b/src/main.h
@@ -0,0 +1,63 @@
+/*
+ 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/>.
+ */
+#ifndef _MAIN_H
+#define _MAIN_H
+
+//#define DEBUG
+#define VERSION "B5"
+
+#ifndef LOCAL
+#define LOCAL extern
+#endif
+
+#define KEY_RIGHT 0
+#define KEY_LEFT 1
+#define KEY_UP 2
+#define KEY_DOWN 3
+#define KEY_FIRE 4
+#define KEY_PAUSE 5
+#define KEY_1 8
+#define KEY_2 9
+#define KEY_3 10
+#define KEY_QUIT 11
+
+#define MAX_GEMS 30
+
+void draw_hud();
+void draw_hud_bg();
+
+void init_persistence();
+void update_persistence(uint8_t id);
+uint8_t check_persistence(uint8_t id);
+void spawn_entities(const uint8_t *ents);
+
+void screen_black();
+void set_colors();
+
+LOCAL uint8_t frame, px, py, opx, opy, spx, spy, smap, sdir, dir;
+LOCAL uint8_t moved;
+
+// all these will be zeroed in one go
+LOCAL uint8_t jump_flag, player_h, magic, wdelay, paused, was_hit, cool_down, respawn_delay, gameover_delay, keys, gems, gtail;
+#define ZERO_ALL 12
+
+LOCAL uint8_t lives, life;
+
+#undef LOCAL
+
+#endif // _MAIN_H
diff --git a/src/maps.c b/src/maps.c
new file mode 100644
index 0000000..87928b2
--- /dev/null
+++ b/src/maps.c
@@ -0,0 +1,239 @@
+/*
+ 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 "aplib.h"
+#include "main.h"
+#include "splib.h"
+
+#include "tiles.h"
+
+#define LOCAL
+#include "maps.h"
+#include "stage.h"
+
+#include "entities.h"
+
+// working map (using 4-bit per tile), 8x8
+static uint8_t ml;
+static const unsigned char *map_ents;
+static uint8_t blocked;
+
+void _expand_map()
+{
+ uint8_t *b = (uint8_t *)BUFF_ADDR;
+ uint16_t i;
+
+ // uncompress at the end of the working area
+ aplib_uncompress(b, (uint8_t *)(map[cmap] + 2));
+
+ // expand from 4-bpt to 8-bpt
+ for (i = 0; i < TMW * TMH; i += 2)
+ {
+ wmap[i] = (*b >> 4) + ts;
+ wmap[i + 1] = (*b & 0x0f) + ts;
+ b++;
+ }
+}
+
+void draw_map()
+{
+ uint8_t i, j, k, c, m;
+
+ validate_screen();
+
+ // draw the bottom to top; invalidate is LIFO
+ for (j = TMH; j; --j)
+ {
+ k = j - 1;
+ for (i = 0; i < TMW; ++i)
+ {
+ c = wmap[i + k * TMW];
+
+ // only if is part of a tileset
+ if (c < LAST_TILE_TILESET)
+ {
+ m = c & 15;
+
+ // special tiles: bricks
+ if (!m || m == 12) {
+ if (k & 1)
+ ++c;
+ }
+ } else if (c == BLOCKED_OPEN_DOOR)
+ c = BLOCKED_OPEN_DOOR_TILE;
+
+ put_tile(bgtiles[c], get_tile_xy(i, k));
+ }
+ }
+}
+
+void init_map(uint8_t m)
+{
+#ifdef DEBUG
+ pad_numbers(wmap, 2, m);
+ set_text_ink(15, 15, 15);
+ put_text(wmap, 0, 192);
+#endif
+
+ cmap = m;
+ // map length (not including tileset or flags)
+ ml = map[cmap][0];
+ // tileset
+ ts = (map[cmap][1] & 0x0f) << 4;
+ blocked = map[cmap][1] & 0xf0;
+ // map_ents
+ map_ents = map[cmap] + 2 + ml;
+
+ _expand_map();
+
+ exit_up = cmap - WMAPS;
+ offset_up = 0;
+ exit_down = cmap + WMAPS;
+ offset_down = 0;
+
+ init_entities();
+ spawn_entities(map_ents);
+
+ draw_map();
+}
+
+void set_map(uint8_t m)
+{
+ init_map(m);
+
+ if (!player_h)
+ {
+ // IMPORTANT: set new player coords before
+ // calling to set_map!
+
+ // checkpoint
+ smap = cmap;
+ spx = px;
+ spy = py;
+ sdir = dir;
+ }
+
+ draw_entities();
+ update_screen();
+}
+
+void set_map_tile(uint8_t x, uint8_t y, uint8_t tile)
+{
+ wmap[x + (y * TMW)] = tile;
+}
+
+static struct st_entity *sp_mit;
+static uint8_t group, last_blocked;
+
+uint8_t is_map_blocked(uint8_t x, uint8_t y)
+{
+ last_blocked = wmap[(x >> 3) + ((y >> 3) * TMW)];
+
+ if (last_blocked == BLOCKED_OPEN_DOOR)
+ return BLOCKED_OPEN_DOOR;
+
+ if (last_blocked < LAST_TILE_TILESET)
+ {
+ if((last_blocked & 15) > LAST_NON_SOLID)
+ return 1;
+ }
+ else
+ // from this on, all solids
+ if (last_blocked > SWITCH_DOOR_TILE - 1)
+ return 1;
+
+ group = 0;
+ for (sp_mit = sp_used; sp_mit; sp_mit = sp_mit->n)
+ {
+ // this requires ET_DOOR and ET_PLATFORM to go grouped
+ switch (sp_mit->type)
+ {
+ case ET_PLATFORM:
+ group = 1;
+ if (check_for_point(sp_mit, x, y, 16, 8))
+ return BLOCKED_PLATFORM;
+ break;
+
+ case ET_DOOR:
+ group = 1;
+ if (check_for_point(sp_mit, x, y, 8, 32) && (sp_it || !keys || magic))
+ return 1;
+ break;
+
+ default:
+ if (group)
+ return 0;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+uint8_t is_map_deadly(uint8_t x, uint8_t y)
+{
+ last_deadly = wmap[(x >> 3) + ((y >> 3) * TMW)];
+
+ // deadly tiles are at the end of the tile table
+ return (last_deadly < 64 && last_deadly > DEADLY_TILE_BASE - 1);
+}
+
+uint8_t exit_map_left()
+{
+ if (blocked & BLOCKED_LEFT)
+ return 0;
+
+ opx = px = (uint8_t)(TW * TMW - 8);
+ set_map(cmap - 1);
+ return 1;
+}
+
+uint8_t exit_map_right()
+{
+ if (blocked & BLOCKED_RIGHT)
+ return 0;
+
+ opx = px = 0;
+ set_map(cmap + 1);
+ return 1;
+}
+
+uint8_t exit_map_up()
+{
+ if (blocked & BLOCKED_UP)
+ return 0;
+
+ px += offset_up;
+ opx = px;
+ opy = py = (uint8_t)(TH * TMH - 24);
+ set_map(exit_up);
+ return 1;
+}
+
+uint8_t exit_map_down()
+{
+ if (blocked & BLOCKED_DOWN)
+ return 0;
+
+ px += offset_down;
+ opx = px;
+ opy = py = 0;
+ set_map(exit_down);
+ return 1;
+}
diff --git a/src/maps.h b/src/maps.h
new file mode 100644
index 0000000..8a3dad1
--- /dev/null
+++ b/src/maps.h
@@ -0,0 +1,66 @@
+/*
+ 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/>.
+ */
+#ifndef _MAPS_H
+#define _MAPS_H
+
+#include <stdint.h>
+
+#ifndef LOCAL
+#define LOCAL extern
+#endif
+
+void init_map(uint8_t m);
+void set_map(uint8_t m);
+void draw_map();
+
+uint8_t exit_map_left();
+uint8_t exit_map_right();
+uint8_t exit_map_up();
+uint8_t exit_map_down();
+
+void set_map_tile(uint8_t x, uint8_t y, uint8_t tile);
+uint8_t is_map_blocked(uint8_t x, uint8_t y);
+uint8_t is_map_deadly(uint8_t x, uint8_t y);
+
+LOCAL uint8_t cmap, ts;
+LOCAL uint8_t exit_up, offset_up, exit_down, offset_down;
+
+LOCAL uint8_t last_deadly;
+
+LOCAL uint8_t wmap[TMW * TMH];
+
+// highest bit denotes deadly tile
+#define DEADLY_TILE_BASE 59
+
+#define BLOCKED_PLATFORM 128
+#define BLOCKED_OPEN_DOOR 143
+#define BLOCKED_OPEN_DOOR_TILE 43
+
+#define SWITCH_DOOR_TILE 58
+
+#define BLOCKED_UP (1<<4)
+#define BLOCKED_DOWN (1<<5)
+#define BLOCKED_LEFT (1<<6)
+#define BLOCKED_RIGHT (1<<7)
+
+#define LAST_TILE_TILESET 48
+#define LAST_NON_SOLID 10
+
+#undef LOCAL
+
+#endif // _MAPS_H
diff --git a/src/songs.z80 b/src/songs.z80
new file mode 100644
index 0000000..1d14dcd
--- /dev/null
+++ b/src/songs.z80
@@ -0,0 +1,1421 @@
+; Song Kitsune's Curse in Lightweight format (V1).
+; Generated by Arkos Tracker 2.
+
+KitsunesCurse_Start:
+KitsunesCurse_StartDisarkGenerateExternalLabel:
+
+KitsunesCurse_DisarkByteRegionStart0:
+ .str "ATLW" ; Format marker (LightWeight).
+ .db 1 ; Format version.
+KitsunesCurse_DisarkByteRegionEnd0:
+KitsunesCurse_DisarkPointerRegionStart1:
+ .dw KitsunesCurse_FmInstrumentTable
+ .dw KitsunesCurse_ArpeggioTable
+ .dw KitsunesCurse_PitchTable
+; Table of the Subsongs.
+ .dw KitsunesCurse_Subsong0
+ .dw KitsunesCurse_Subsong1
+ .dw KitsunesCurse_Subsong2
+KitsunesCurse_DisarkPointerRegionEnd1:
+
+; The Arpeggio table.
+KitsunesCurse_ArpeggioTable:
+KitsunesCurse_DisarkWordRegionStart2:
+ .dw 0
+KitsunesCurse_DisarkWordRegionEnd2:
+KitsunesCurse_DisarkPointerRegionStart3:
+KitsunesCurse_DisarkPointerRegionEnd3:
+
+; The Pitch table.
+KitsunesCurse_PitchTable:
+KitsunesCurse_DisarkWordRegionStart4:
+ .dw 0
+KitsunesCurse_DisarkWordRegionEnd4:
+KitsunesCurse_DisarkPointerRegionStart5:
+KitsunesCurse_DisarkPointerRegionEnd5:
+
+; The FM Instrument table.
+KitsunesCurse_FmInstrumentTable:
+KitsunesCurse_DisarkPointerRegionStart6:
+ .dw KitsunesCurse_FmInstrument0
+ .dw KitsunesCurse_FmInstrument1
+ .dw KitsunesCurse_FmInstrument2
+ .dw KitsunesCurse_FmInstrument3
+ .dw KitsunesCurse_FmInstrument4
+ .dw KitsunesCurse_FmInstrument5
+ .dw KitsunesCurse_FmInstrument6
+KitsunesCurse_DisarkPointerRegionEnd6:
+
+KitsunesCurse_DisarkByteRegionStart7:
+KitsunesCurse_FmInstrument0:
+ .db 255 ; Speed.
+
+KitsunesCurse_FmInstrument0Loop: .db 0 ; Volume: 0.
+
+ .db 4 ; End the instrument.
+KitsunesCurse_DisarkPointerRegionStart8:
+ .dw KitsunesCurse_FmInstrument0Loop ; Loops.
+KitsunesCurse_DisarkPointerRegionEnd8:
+
+KitsunesCurse_FmInstrument1:
+ .db 0 ; Speed.
+
+ .db 101 ; Volume: 9.
+ .dw 2 ; Pitch: 2.
+
+ .db 37 ; Volume: 9.
+
+ .db 97 ; Volume: 8.
+ .dw -2 ; Pitch: -2.
+
+ .db 33 ; Volume: 8.
+
+ .db 29 ; Volume: 7.
+
+ .db 25 ; Volume: 6.
+
+ .db 21 ; Volume: 5.
+
+ .db 17 ; Volume: 4.
+
+ .db 13 ; Volume: 3.
+
+ .db 4 ; End the instrument.
+KitsunesCurse_DisarkPointerRegionStart9:
+ .dw KitsunesCurse_FmInstrument0Loop ; Loop to silence.
+KitsunesCurse_DisarkPointerRegionEnd9:
+
+KitsunesCurse_FmInstrument2:
+ .db 0 ; Speed.
+
+ .db 41 ; Volume: 10.
+
+ .db 41 ; Volume: 10.
+
+ .db 41 ; Volume: 10.
+
+ .db 25 ; Volume: 6.
+
+ .db 25 ; Volume: 6.
+
+ .db 25 ; Volume: 6.
+
+ .db 4 ; End the instrument.
+KitsunesCurse_DisarkPointerRegionStart10:
+ .dw KitsunesCurse_FmInstrument0Loop ; Loop to silence.
+KitsunesCurse_DisarkPointerRegionEnd10:
+
+KitsunesCurse_FmInstrument3:
+ .db 0 ; Speed.
+
+ .db 173 ; Volume: 11.
+ .db 1 ; Arpeggio: 0.
+ .db 15 ; Noise: 15.
+
+ .db 169 ; Volume: 10.
+ .db 233 ; Arpeggio: -12.
+ .db 15 ; Noise: 15.
+
+ .db 165 ; Volume: 9.
+ .db 1 ; Arpeggio: 0.
+ .db 15 ; Noise: 15.
+
+ .db 161 ; Volume: 8.
+ .db 232 ; Arpeggio: -12.
+
+ .db 4 ; End the instrument.
+KitsunesCurse_DisarkPointerRegionStart11:
+ .dw KitsunesCurse_FmInstrument0Loop ; Loop to silence.
+KitsunesCurse_DisarkPointerRegionEnd11:
+
+KitsunesCurse_FmInstrument4:
+ .db 0 ; Speed.
+
+ .db 173 ; Volume: 11.
+ .db 232 ; Arpeggio: -12.
+
+ .db 41 ; Volume: 10.
+
+ .db 165 ; Volume: 9.
+ .db 24 ; Arpeggio: 12.
+
+ .db 33 ; Volume: 8.
+
+ .db 4 ; End the instrument.
+KitsunesCurse_DisarkPointerRegionStart12:
+ .dw KitsunesCurse_FmInstrument0Loop ; Loop to silence.
+KitsunesCurse_DisarkPointerRegionEnd12:
+
+KitsunesCurse_FmInstrument5:
+ .db 0 ; Speed.
+
+ .db 41 ; Volume: 10.
+
+ .db 41 ; Volume: 10.
+
+ .db 41 ; Volume: 10.
+
+ .db 41 ; Volume: 10.
+
+KitsunesCurse_FmInstrument5Loop: .db 97 ; Volume: 8.
+ .dw 4 ; Pitch: 4.
+
+ .db 33 ; Volume: 8.
+
+ .db 97 ; Volume: 8.
+ .dw -4 ; Pitch: -4.
+
+ .db 33 ; Volume: 8.
+
+ .db 4 ; End the instrument.
+KitsunesCurse_DisarkPointerRegionStart13:
+ .dw KitsunesCurse_FmInstrument5Loop ; Loops.
+KitsunesCurse_DisarkPointerRegionEnd13:
+
+KitsunesCurse_FmInstrument6:
+ .db 0 ; Speed.
+
+ .db 165 ; Volume: 9.
+ .db 24 ; Arpeggio: 12.
+
+ .db 37 ; Volume: 9.
+
+ .db 165 ; Volume: 9.
+ .db 232 ; Arpeggio: -12.
+
+ .db 37 ; Volume: 9.
+
+ .db 165 ; Volume: 9.
+ .db 24 ; Arpeggio: 12.
+
+ .db 25 ; Volume: 6.
+
+ .db 153 ; Volume: 6.
+ .db 232 ; Arpeggio: -12.
+
+ .db 25 ; Volume: 6.
+
+ .db 145 ; Volume: 4.
+ .db 24 ; Arpeggio: 12.
+
+ .db 17 ; Volume: 4.
+
+ .db 145 ; Volume: 4.
+ .db 232 ; Arpeggio: -12.
+
+ .db 17 ; Volume: 4.
+
+ .db 137 ; Volume: 2.
+ .db 24 ; Arpeggio: 12.
+
+ .db 9 ; Volume: 2.
+
+ .db 137 ; Volume: 2.
+ .db 232 ; Arpeggio: -12.
+
+ .db 9 ; Volume: 2.
+
+ .db 137 ; Volume: 2.
+ .db 24 ; Arpeggio: 12.
+
+ .db 9 ; Volume: 2.
+
+ .db 137 ; Volume: 2.
+ .db 232 ; Arpeggio: -12.
+
+ .db 9 ; Volume: 2.
+
+ .db 4 ; End the instrument.
+KitsunesCurse_DisarkPointerRegionStart14:
+ .dw KitsunesCurse_FmInstrument0Loop ; Loop to silence.
+KitsunesCurse_DisarkPointerRegionEnd14:
+
+KitsunesCurse_DisarkByteRegionEnd7:
+KitsunesCurse_Subsong0DisarkByteRegionStart0:
+; Song Kitsune's Curse, Subsong 0 - Silence - in Lightweight format (V1).
+; Generated by Arkos Tracker 2.
+
+KitsunesCurse_Subsong0:
+ .db 6 ; Initial speed.
+
+; The Linker.
+; Pattern 0
+ .db 5 ; State byte.
+ .db 21 ; New height.
+KitsunesCurse_Subsong0DisarkPointerRegionStart1:
+ .dw KitsunesCurse_Subsong0_Track0, KitsunesCurse_Subsong0_Track0, KitsunesCurse_Subsong0_Track1
+KitsunesCurse_Subsong0DisarkPointerRegionEnd1:
+; The tracks.
+
+; Pattern 1
+KitsunesCurse_Subsong0loop:
+ .db 1 ; State byte.
+KitsunesCurse_Subsong0DisarkPointerRegionStart2:
+ .dw KitsunesCurse_Subsong0_Track0, KitsunesCurse_Subsong0_Track0, KitsunesCurse_Subsong0_Track0
+KitsunesCurse_Subsong0DisarkPointerRegionEnd2:
+; The tracks.
+
+ .db 0 ; End of the subsong.
+KitsunesCurse_Subsong0DisarkPointerRegionStart3:
+ .dw KitsunesCurse_Subsong0loop
+KitsunesCurse_Subsong0DisarkPointerRegionEnd3:
+
+; The Tracks.
+KitsunesCurse_Subsong0_Track0:
+ .db 61, 21 ; Long wait: 22.
+
+
+KitsunesCurse_Subsong0_Track1:
+ .db 152 ; Note: 48.
+ .db 0 ; New instrument: 0.
+ .db 61, 20 ; Long wait: 21.
+
+
+KitsunesCurse_Subsong0DisarkByteRegionEnd0:
+KitsunesCurse_Subsong1DisarkByteRegionStart0:
+; Song Kitsune's Curse, Subsong 1 - Split the stone - in Lightweight format (V1).
+; Generated by Arkos Tracker 2.
+
+KitsunesCurse_Subsong1:
+ .db 6 ; Initial speed.
+
+; The Linker.
+; Pattern 0
+KitsunesCurse_Subsong1loop:
+ .db 5 ; State byte.
+ .db 47 ; New height.
+KitsunesCurse_Subsong1DisarkPointerRegionStart1:
+ .dw KitsunesCurse_Subsong1_Track1, KitsunesCurse_Subsong1_Track0, KitsunesCurse_Subsong1_Track2
+KitsunesCurse_Subsong1DisarkPointerRegionEnd1:
+; The tracks.
+
+ .db 0 ; End of the subsong.
+KitsunesCurse_Subsong1DisarkPointerRegionStart2:
+ .dw KitsunesCurse_Subsong1loop
+KitsunesCurse_Subsong1DisarkPointerRegionEnd2:
+
+; The Tracks.
+KitsunesCurse_Subsong1_Track0:
+ .db 128 ; Note: 24.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 0 ; Note: 24.
+ .db 61, 4 ; Long wait: 5.
+
+ .db 0 ; Note: 24.
+ .db 62 ; Short wait: 1.
+
+ .db 0 ; Note: 24.
+ .db 61, 4 ; Long wait: 5.
+
+ .db 0 ; Note: 24.
+ .db 62 ; Short wait: 1.
+
+ .db 0 ; Note: 24.
+ .db 61, 4 ; Long wait: 5.
+
+ .db 3 ; Note: 27.
+ .db 62 ; Short wait: 1.
+
+ .db 3 ; Note: 27.
+ .db 190 ; Short wait: 3.
+
+ .db 3 ; Note: 27.
+ .db 62 ; Short wait: 1.
+
+ .db 0 ; Note: 24.
+ .db 61, 14 ; Long wait: 15.
+
+
+KitsunesCurse_Subsong1_Track1:
+ .db 152 ; Note: 48.
+ .db 6 ; New instrument: 3.
+ .db 190 ; Short wait: 3.
+
+ .db 172 ; Note: 68.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 24 ; Note: 48.
+ .db 61, 4 ; Long wait: 5.
+
+ .db 172 ; Note: 68.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 6 ; New instrument: 3.
+ .db 190 ; Short wait: 3.
+
+ .db 172 ; Note: 68.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 27 ; Note: 51.
+ .db 190 ; Short wait: 3.
+
+ .db 27 ; Note: 51.
+ .db 62 ; Short wait: 1.
+
+ .db 27 ; Note: 51.
+ .db 62 ; Short wait: 1.
+
+ .db 164 ; Note: 60.
+ .db 8 ; New instrument: 4.
+ .db 61, 14 ; Long wait: 15.
+
+
+KitsunesCurse_Subsong1_Track2:
+ .db 152 ; Note: 48.
+ .db 2 ; New instrument: 1.
+ .db 62 ; Short wait: 1.
+
+ .db 39 ; Note: 63.
+ .db 62 ; Short wait: 1.
+
+ .db 24 ; Note: 48.
+ .db 62 ; Short wait: 1.
+
+ .db 41 ; Note: 65.
+ .db 62 ; Short wait: 1.
+
+ .db 24 ; Note: 48.
+ .db 62 ; Short wait: 1.
+
+ .db 43 ; Note: 67.
+ .db 62 ; Short wait: 1.
+
+ .db 24 ; Note: 48.
+ .db 62 ; Short wait: 1.
+
+ .db 44 ; Note: 68.
+ .db 62 ; Short wait: 1.
+
+ .db 24 ; Note: 48.
+ .db 62 ; Short wait: 1.
+
+ .db 43 ; Note: 67.
+ .db 62 ; Short wait: 1.
+
+ .db 24 ; Note: 48.
+ .db 62 ; Short wait: 1.
+
+ .db 39 ; Note: 63.
+ .db 62 ; Short wait: 1.
+
+ .db 38 ; Note: 62.
+ .db 62 ; Short wait: 1.
+
+ .db 24 ; Note: 48.
+ .db 62 ; Short wait: 1.
+
+ .db 38 ; Note: 62.
+ .db 62 ; Short wait: 1.
+
+ .db 39 ; Note: 63.
+ .db 62 ; Short wait: 1.
+
+ .db 36 ; Note: 60.
+ .db 61, 14 ; Long wait: 15.
+
+
+KitsunesCurse_Subsong1DisarkByteRegionEnd0:
+KitsunesCurse_Subsong2DisarkByteRegionStart0:
+; Song Kitsune's Curse, Subsong 2 - The Curse - in Lightweight format (V1).
+; Generated by Arkos Tracker 2.
+
+KitsunesCurse_Subsong2:
+ .db 6 ; Initial speed.
+
+; The Linker.
+; Pattern 0
+KitsunesCurse_Subsong2loop:
+ .db 5 ; State byte.
+ .db 31 ; New height.
+KitsunesCurse_Subsong2DisarkPointerRegionStart1:
+ .dw KitsunesCurse_Subsong2_Track0, KitsunesCurse_Subsong2_Track1, KitsunesCurse_Subsong2_Track2
+KitsunesCurse_Subsong2DisarkPointerRegionEnd1:
+; The tracks.
+
+; Pattern 1
+ .db 1 ; State byte.
+KitsunesCurse_Subsong2DisarkPointerRegionStart2:
+ .dw KitsunesCurse_Subsong2_Track8, KitsunesCurse_Subsong2_Track5, KitsunesCurse_Subsong2_Track2
+KitsunesCurse_Subsong2DisarkPointerRegionEnd2:
+; The tracks.
+
+; Pattern 2
+ .db 5 ; State byte.
+ .db 63 ; New height.
+KitsunesCurse_Subsong2DisarkPointerRegionStart3:
+ .dw KitsunesCurse_Subsong2_Track3, KitsunesCurse_Subsong2_Track4, KitsunesCurse_Subsong2_Track2
+KitsunesCurse_Subsong2DisarkPointerRegionEnd3:
+; The tracks.
+
+; Pattern 3
+ .db 1 ; State byte.
+KitsunesCurse_Subsong2DisarkPointerRegionStart4:
+ .dw KitsunesCurse_Subsong2_Track3, KitsunesCurse_Subsong2_Track4, KitsunesCurse_Subsong2_Track2
+KitsunesCurse_Subsong2DisarkPointerRegionEnd4:
+; The tracks.
+
+; Pattern 4
+ .db 1 ; State byte.
+KitsunesCurse_Subsong2DisarkPointerRegionStart5:
+ .dw KitsunesCurse_Subsong2_Track6, KitsunesCurse_Subsong2_Track7, KitsunesCurse_Subsong2_Track2
+KitsunesCurse_Subsong2DisarkPointerRegionEnd5:
+; The tracks.
+
+; Pattern 5
+ .db 1 ; State byte.
+KitsunesCurse_Subsong2DisarkPointerRegionStart6:
+ .dw KitsunesCurse_Subsong2_Track6, KitsunesCurse_Subsong2_Track4, KitsunesCurse_Subsong2_Track2
+KitsunesCurse_Subsong2DisarkPointerRegionEnd6:
+; The tracks.
+
+; Pattern 6
+ .db 1 ; State byte.
+KitsunesCurse_Subsong2DisarkPointerRegionStart7:
+ .dw KitsunesCurse_Subsong2_Track9, KitsunesCurse_Subsong2_Track7, KitsunesCurse_Subsong2_Track2
+KitsunesCurse_Subsong2DisarkPointerRegionEnd7:
+; The tracks.
+
+; Pattern 7
+ .db 1 ; State byte.
+KitsunesCurse_Subsong2DisarkPointerRegionStart8:
+ .dw KitsunesCurse_Subsong2_Track10, KitsunesCurse_Subsong2_Track11, KitsunesCurse_Subsong2_Track2
+KitsunesCurse_Subsong2DisarkPointerRegionEnd8:
+; The tracks.
+
+ .db 0 ; End of the subsong.
+KitsunesCurse_Subsong2DisarkPointerRegionStart9:
+ .dw KitsunesCurse_Subsong2loop
+KitsunesCurse_Subsong2DisarkPointerRegionEnd9:
+
+; The Tracks.
+KitsunesCurse_Subsong2_Track0:
+ .db 161 ; Note: 57.
+ .db 2 ; New instrument: 1.
+ .db 34 ; Note: 58.
+ .db 33 ; Note: 57.
+ .db 31 ; Note: 55.
+ .db 33 ; Note: 57.
+ .db 34 ; Note: 58.
+ .db 33 ; Note: 57.
+ .db 31 ; Note: 55.
+ .db 33 ; Note: 57.
+ .db 34 ; Note: 58.
+ .db 33 ; Note: 57.
+ .db 31 ; Note: 55.
+ .db 33 ; Note: 57.
+ .db 34 ; Note: 58.
+ .db 33 ; Note: 57.
+ .db 31 ; Note: 55.
+ .db 28 ; Note: 52.
+ .db 29 ; Note: 53.
+ .db 28 ; Note: 52.
+ .db 26 ; Note: 50.
+ .db 28 ; Note: 52.
+ .db 29 ; Note: 53.
+ .db 28 ; Note: 52.
+ .db 26 ; Note: 50.
+ .db 28 ; Note: 52.
+ .db 29 ; Note: 53.
+ .db 28 ; Note: 52.
+ .db 26 ; Note: 50.
+ .db 28 ; Note: 52.
+ .db 29 ; Note: 53.
+ .db 28 ; Note: 52.
+ .db 26 ; Note: 50.
+ .db 61, 31 ; Long wait: 32.
+
+
+KitsunesCurse_Subsong2_Track1:
+ .db 142 ; Note: 38.
+ .db 8 ; New instrument: 4.
+ .db 130 ; Note: 26.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 2 ; Note: 26.
+ .db 154 ; Note: 50.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 130 ; Note: 26.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 142 ; Note: 38.
+ .db 8 ; New instrument: 4.
+ .db 130 ; Note: 26.
+ .db 4 ; New instrument: 2.
+ .db 126 ; Short wait: 2.
+
+ .db 154 ; Note: 50.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 26 ; Note: 50.
+ .db 140 ; Note: 36.
+ .db 8 ; New instrument: 4.
+ .db 128 ; Note: 24.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 0 ; Note: 24.
+ .db 152 ; Note: 48.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 128 ; Note: 24.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 8 ; New instrument: 4.
+ .db 128 ; Note: 24.
+ .db 4 ; New instrument: 2.
+ .db 126 ; Short wait: 2.
+
+ .db 152 ; Note: 48.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 129 ; Note: 25.
+ .db 4 ; New instrument: 2.
+ .db 152 ; Note: 48.
+ .db 6 ; New instrument: 3.
+ .db 61, 31 ; Long wait: 32.
+
+
+KitsunesCurse_Subsong2_Track2:
+ .db 61, 63 ; Long wait: 64.
+
+
+KitsunesCurse_Subsong2_Track3:
+ .db 159 ; Note: 55.
+ .db 12 ; New instrument: 6.
+ .db 62 ; Short wait: 1.
+
+ .db 33 ; Note: 57.
+ .db 62 ; Short wait: 1.
+
+ .db 29 ; Note: 53.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 190 ; Short wait: 3.
+
+ .db 33 ; Note: 57.
+ .db 190 ; Short wait: 3.
+
+ .db 29 ; Note: 53.
+ .db 62 ; Short wait: 1.
+
+ .db 28 ; Note: 52.
+ .db 62 ; Short wait: 1.
+
+ .db 29 ; Note: 53.
+ .db 62 ; Short wait: 1.
+
+ .db 31 ; Note: 55.
+ .db 62 ; Short wait: 1.
+
+ .db 28 ; Note: 52.
+ .db 190 ; Short wait: 3.
+
+ .db 24 ; Note: 48.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 28 ; Note: 52.
+ .db 62 ; Short wait: 1.
+
+ .db 29 ; Note: 53.
+ .db 62 ; Short wait: 1.
+
+ .db 28 ; Note: 52.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 33 ; Note: 57.
+ .db 61, 4 ; Long wait: 5.
+
+ .db 29 ; Note: 53.
+ .db 62 ; Short wait: 1.
+
+ .db 28 ; Note: 52.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 33 ; Note: 57.
+ .db 61, 4 ; Long wait: 5.
+
+ .db 31 ; Note: 55.
+ .db 62 ; Short wait: 1.
+
+ .db 33 ; Note: 57.
+ .db 62 ; Short wait: 1.
+
+ .db 29 ; Note: 53.
+ .db 62 ; Short wait: 1.
+
+ .db 28 ; Note: 52.
+ .db 62 ; Short wait: 1.
+
+
+KitsunesCurse_Subsong2_Track4:
+ .db 142 ; Note: 38.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 142 ; Note: 38.
+ .db 4 ; New instrument: 2.
+ .db 14 ; Note: 38.
+ .db 154 ; Note: 50.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 142 ; Note: 38.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 142 ; Note: 38.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 142 ; Note: 38.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 154 ; Note: 50.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 142 ; Note: 38.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 4 ; New instrument: 2.
+ .db 12 ; Note: 36.
+ .db 152 ; Note: 48.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 145 ; Note: 41.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 145 ; Note: 41.
+ .db 4 ; New instrument: 2.
+ .db 17 ; Note: 41.
+ .db 157 ; Note: 53.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 145 ; Note: 41.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 145 ; Note: 41.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 145 ; Note: 41.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 157 ; Note: 53.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 145 ; Note: 41.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 147 ; Note: 43.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 147 ; Note: 43.
+ .db 4 ; New instrument: 2.
+ .db 19 ; Note: 43.
+ .db 159 ; Note: 55.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 147 ; Note: 43.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 147 ; Note: 43.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 147 ; Note: 43.
+ .db 4 ; New instrument: 2.
+ .db 159 ; Note: 55.
+ .db 6 ; New instrument: 3.
+ .db 31 ; Note: 55.
+ .db 62 ; Short wait: 1.
+
+ .db 147 ; Note: 43.
+ .db 4 ; New instrument: 2.
+ .db 159 ; Note: 55.
+ .db 6 ; New instrument: 3.
+
+KitsunesCurse_Subsong2_Track5:
+ .db 142 ; Note: 38.
+ .db 8 ; New instrument: 4.
+ .db 130 ; Note: 26.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 2 ; Note: 26.
+ .db 154 ; Note: 50.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 130 ; Note: 26.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 142 ; Note: 38.
+ .db 8 ; New instrument: 4.
+ .db 130 ; Note: 26.
+ .db 4 ; New instrument: 2.
+ .db 126 ; Short wait: 2.
+
+ .db 154 ; Note: 50.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 26 ; Note: 50.
+ .db 140 ; Note: 36.
+ .db 8 ; New instrument: 4.
+ .db 128 ; Note: 24.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 0 ; Note: 24.
+ .db 152 ; Note: 48.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 128 ; Note: 24.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 8 ; New instrument: 4.
+ .db 152 ; Note: 48.
+ .db 6 ; New instrument: 3.
+ .db 24 ; Note: 48.
+ .db 62 ; Short wait: 1.
+
+ .db 24 ; Note: 48.
+ .db 62 ; Short wait: 1.
+
+ .db 25 ; Note: 49.
+ .db 24 ; Note: 48.
+ .db 61, 31 ; Long wait: 32.
+
+
+KitsunesCurse_Subsong2_Track6:
+ .db 161 ; Note: 57.
+ .db 10 ; New instrument: 5.
+ .db 190 ; Short wait: 3.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 0 ; New instrument: 0.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 10 ; New instrument: 5.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 0 ; New instrument: 0.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 10 ; New instrument: 5.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 24 ; Note: 48.
+ .db 62 ; Short wait: 1.
+
+ .db 29 ; Note: 53.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 0 ; New instrument: 0.
+ .db 190 ; Short wait: 3.
+
+ .db 161 ; Note: 57.
+ .db 10 ; New instrument: 5.
+ .db 190 ; Short wait: 3.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 0 ; New instrument: 0.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 10 ; New instrument: 5.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 0 ; New instrument: 0.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 10 ; New instrument: 5.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 24 ; Note: 48.
+ .db 62 ; Short wait: 1.
+
+ .db 29 ; Note: 53.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 0 ; New instrument: 0.
+ .db 190 ; Short wait: 3.
+
+
+KitsunesCurse_Subsong2_Track7:
+ .db 142 ; Note: 38.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 142 ; Note: 38.
+ .db 4 ; New instrument: 2.
+ .db 14 ; Note: 38.
+ .db 154 ; Note: 50.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 142 ; Note: 38.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 142 ; Note: 38.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 142 ; Note: 38.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 154 ; Note: 50.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 142 ; Note: 38.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 4 ; New instrument: 2.
+ .db 12 ; Note: 36.
+ .db 152 ; Note: 48.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 145 ; Note: 41.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 145 ; Note: 41.
+ .db 4 ; New instrument: 2.
+ .db 17 ; Note: 41.
+ .db 157 ; Note: 53.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 145 ; Note: 41.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 145 ; Note: 41.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 145 ; Note: 41.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 157 ; Note: 53.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 145 ; Note: 41.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 147 ; Note: 43.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 147 ; Note: 43.
+ .db 4 ; New instrument: 2.
+ .db 19 ; Note: 43.
+ .db 159 ; Note: 55.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 147 ; Note: 43.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 147 ; Note: 43.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 147 ; Note: 43.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 159 ; Note: 55.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 147 ; Note: 43.
+ .db 4 ; New instrument: 2.
+ .db 159 ; Note: 55.
+ .db 6 ; New instrument: 3.
+
+KitsunesCurse_Subsong2_Track8:
+ .db 161 ; Note: 57.
+ .db 2 ; New instrument: 1.
+ .db 34 ; Note: 58.
+ .db 33 ; Note: 57.
+ .db 31 ; Note: 55.
+ .db 33 ; Note: 57.
+ .db 34 ; Note: 58.
+ .db 33 ; Note: 57.
+ .db 31 ; Note: 55.
+ .db 33 ; Note: 57.
+ .db 34 ; Note: 58.
+ .db 33 ; Note: 57.
+ .db 31 ; Note: 55.
+ .db 33 ; Note: 57.
+ .db 34 ; Note: 58.
+ .db 33 ; Note: 57.
+ .db 31 ; Note: 55.
+ .db 28 ; Note: 52.
+ .db 29 ; Note: 53.
+ .db 28 ; Note: 52.
+ .db 26 ; Note: 50.
+ .db 28 ; Note: 52.
+ .db 29 ; Note: 53.
+ .db 28 ; Note: 52.
+ .db 26 ; Note: 50.
+ .db 28 ; Note: 52.
+ .db 190 ; Short wait: 3.
+
+ .db 28 ; Note: 52.
+ .db 62 ; Short wait: 1.
+
+ .db 28 ; Note: 52.
+ .db 26 ; Note: 50.
+ .db 61, 31 ; Long wait: 32.
+
+
+KitsunesCurse_Subsong2_Track9:
+ .db 161 ; Note: 57.
+ .db 10 ; New instrument: 5.
+ .db 190 ; Short wait: 3.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 0 ; New instrument: 0.
+ .db 62 ; Short wait: 1.
+
+ .db 161 ; Note: 57.
+ .db 2 ; New instrument: 1.
+ .db 34 ; Note: 58.
+ .db 33 ; Note: 57.
+ .db 31 ; Note: 55.
+ .db 33 ; Note: 57.
+ .db 190 ; Short wait: 3.
+
+ .db 152 ; Note: 48.
+ .db 10 ; New instrument: 5.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 24 ; Note: 48.
+ .db 62 ; Short wait: 1.
+
+ .db 29 ; Note: 53.
+ .db 190 ; Short wait: 3.
+
+ .db 154 ; Note: 50.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 28 ; Note: 52.
+ .db 62 ; Short wait: 1.
+
+ .db 161 ; Note: 57.
+ .db 10 ; New instrument: 5.
+ .db 190 ; Short wait: 3.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 0 ; New instrument: 0.
+ .db 62 ; Short wait: 1.
+
+ .db 161 ; Note: 57.
+ .db 2 ; New instrument: 1.
+ .db 34 ; Note: 58.
+ .db 33 ; Note: 57.
+ .db 31 ; Note: 55.
+ .db 33 ; Note: 57.
+ .db 190 ; Short wait: 3.
+
+ .db 152 ; Note: 48.
+ .db 10 ; New instrument: 5.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 24 ; Note: 48.
+ .db 62 ; Short wait: 1.
+
+ .db 31 ; Note: 55.
+ .db 62 ; Short wait: 1.
+
+ .db 157 ; Note: 53.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 28 ; Note: 52.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+
+KitsunesCurse_Subsong2_Track10:
+ .db 161 ; Note: 57.
+ .db 10 ; New instrument: 5.
+ .db 190 ; Short wait: 3.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 0 ; New instrument: 0.
+ .db 62 ; Short wait: 1.
+
+ .db 161 ; Note: 57.
+ .db 2 ; New instrument: 1.
+ .db 34 ; Note: 58.
+ .db 33 ; Note: 57.
+ .db 31 ; Note: 55.
+ .db 33 ; Note: 57.
+ .db 190 ; Short wait: 3.
+
+ .db 152 ; Note: 48.
+ .db 10 ; New instrument: 5.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 24 ; Note: 48.
+ .db 62 ; Short wait: 1.
+
+ .db 29 ; Note: 53.
+ .db 190 ; Short wait: 3.
+
+ .db 154 ; Note: 50.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 28 ; Note: 52.
+ .db 62 ; Short wait: 1.
+
+ .db 161 ; Note: 57.
+ .db 10 ; New instrument: 5.
+ .db 190 ; Short wait: 3.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 0 ; New instrument: 0.
+ .db 62 ; Short wait: 1.
+
+ .db 161 ; Note: 57.
+ .db 2 ; New instrument: 1.
+ .db 34 ; Note: 58.
+ .db 33 ; Note: 57.
+ .db 31 ; Note: 55.
+ .db 33 ; Note: 57.
+ .db 190 ; Short wait: 3.
+
+ .db 152 ; Note: 48.
+ .db 10 ; New instrument: 5.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 190 ; Short wait: 3.
+
+ .db 152 ; Note: 48.
+ .db 0 ; New instrument: 0.
+ .db 62 ; Short wait: 1.
+
+ .db 157 ; Note: 53.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 28 ; Note: 52.
+ .db 62 ; Short wait: 1.
+
+ .db 26 ; Note: 50.
+ .db 62 ; Short wait: 1.
+
+
+KitsunesCurse_Subsong2_Track11:
+ .db 142 ; Note: 38.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 142 ; Note: 38.
+ .db 4 ; New instrument: 2.
+ .db 14 ; Note: 38.
+ .db 154 ; Note: 50.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 142 ; Note: 38.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 142 ; Note: 38.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 142 ; Note: 38.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 154 ; Note: 50.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 142 ; Note: 38.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 4 ; New instrument: 2.
+ .db 12 ; Note: 36.
+ .db 152 ; Note: 48.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 152 ; Note: 48.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 140 ; Note: 36.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 145 ; Note: 41.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 145 ; Note: 41.
+ .db 4 ; New instrument: 2.
+ .db 17 ; Note: 41.
+ .db 157 ; Note: 53.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 145 ; Note: 41.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 145 ; Note: 41.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 145 ; Note: 41.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 157 ; Note: 53.
+ .db 6 ; New instrument: 3.
+ .db 62 ; Short wait: 1.
+
+ .db 145 ; Note: 41.
+ .db 4 ; New instrument: 2.
+ .db 62 ; Short wait: 1.
+
+ .db 147 ; Note: 43.
+ .db 8 ; New instrument: 4.
+ .db 62 ; Short wait: 1.
+
+ .db 147 ; Note: 43.
+ .db 4 ; New instrument: 2.
+ .db 19 ; Note: 43.
+ .db 159 ; Note: 55.
+ .db 6 ; New instrument: 3.
+ .db 61, 4 ; Long wait: 5.
+
+ .db 147 ; Note: 43.
+ .db 4 ; New instrument: 2.
+ .db 159 ; Note: 55.
+ .db 6 ; New instrument: 3.
+ .db 31 ; Note: 55.
+ .db 62 ; Short wait: 1.
+
+ .db 147 ; Note: 43.
+ .db 4 ; New instrument: 2.
+ .db 159 ; Note: 55.
+ .db 6 ; New instrument: 3.
+
+KitsunesCurse_Subsong2DisarkByteRegionEnd0:
diff --git a/src/sound.h b/src/sound.h
new file mode 100644
index 0000000..50a8822
--- /dev/null
+++ b/src/sound.h
@@ -0,0 +1,57 @@
+/*
+ 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/>.
+ */
+#ifndef _SOUND_H
+#define _SOUND_H
+
+#include <stdint.h>
+
+enum effect_number
+{
+ EFX_NONE = 0,
+ EFX_HIT,
+
+ EFX_SPIDER,
+ EFX_ONI,
+ EFX_TICK,
+ EFX_DOOR,
+
+ EFX_MAGIC,
+ EFX_PICKUP,
+ EFX_DAMAGE,
+ EFX_DEATH,
+ EFX_SPLASH,
+};
+
+enum song_number
+{
+ SONG_SILENCE = 0,
+ SONG_SPLIT,
+ SONG_INGAME,
+};
+
+enum song_pak_number
+{
+ SONG_INTRO = 0,
+ SONG_MENU,
+ SONG_GAMEOVER,
+};
+
+extern uint8_t songs[];
+extern uint8_t effects[];
+
+#endif // _SOUND_H
diff --git a/src/sound.z80 b/src/sound.z80
new file mode 100644
index 0000000..42cac08
--- /dev/null
+++ b/src/sound.z80
@@ -0,0 +1,8 @@
+.globl _songs
+.globl _effects
+
+_songs::
+.include "songs.z80"
+
+_effects::
+.include "effects.z80"
diff --git a/src/splib.c b/src/splib.c
new file mode 100644
index 0000000..1869123
--- /dev/null
+++ b/src/splib.c
@@ -0,0 +1,2031 @@
+/*
+ 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 <stdlib.h>
+#include <string.h>
+
+#include "splib.h"
+#include "font.h"
+
+//#define SPRITE_DEBUG
+//#define FENCE_DEBUG
+
+struct st_tile tiles[TMW * TMH];
+struct st_tile *dirty;
+uint8_t *mini_buffer;
+
+// faster
+struct st_tile *tile_p;
+
+#ifdef FENCE_DEBUG
+// (sprite th * 2 + 2) * msx sprites
+#define MAX_MINI_BUFFER (3 * 2 + 2) * 10
+uint8_t mini_buffer_cnt;
+#endif
+
+void pad_numbers(uint8_t *s, uint8_t limit, uint16_t number)
+{
+ s += limit;
+ *s = 0;
+
+ do
+ {
+ limit--;
+ *--s = (number % 10) + '0';
+ number /= 10;
+ } while (limit);
+}
+
+#pragma save
+#pragma disable_warning 85
+void set_hw_border(uint8_t c) __z88dk_fastcall
+{
+ // *INDENT-OFF*
+ __asm;
+ ld bc, #0x7f10
+ out (c), c
+ ld c, l
+ out (c), c
+ __endasm;
+ // *INDENT-ON*
+}
+#pragma restore
+
+#pragma save
+#pragma disable_warning 85
+void set_hw_ink(uint8_t ink, uint8_t c)
+{
+ // *INDENT-OFF*
+ __asm;
+ ld hl, #2
+ add hl, sp
+ ld a, (hl)
+ inc hl
+ ld e, (hl)
+ ld bc, #0x7f00
+ out (c), a
+ ld a, #0x40
+ or e
+ out (c), a
+ __endasm;
+ // *INDENT-ON*
+}
+#pragma restore
+
+void wait_vsync()
+{
+ // *INDENT-OFF*
+ __asm;
+ ld b, #0xf5
+keep_waiting:
+ in a, (c)
+ rra
+ jr nc, keep_waiting
+ __endasm;
+ // *INDENT-ON*
+}
+
+void clear_screen()
+{
+ memset((uint8_t*)0xc000, 0, 16383);
+}
+
+// this is quite slow, use it only where speed is not an issue
+uint16_t screen_addr(uint16_t x, uint16_t y)
+{
+ // up to 160 x 200
+ return (0xc000 + x + ((y / 8) * 80) + ((y % 8) * 2048));
+}
+
+void init_tiles()
+{
+ uint8_t i, j;
+
+ memset(tiles, 0, sizeof(struct st_tile) * TMW * TMH);
+
+ for (j = 0; j < TMH; ++j)
+ for (i = 0; i < TMW; ++i)
+ tiles[i + j * TMW].saddr = screen_addr((uint16_t)i * TW / 2, 32 + (uint16_t)j * TH);
+
+ dirty = NULL;
+ mini_buffer = (uint8_t *)BUFF_ADDR;
+
+#ifdef FENCE_DEBUG
+ mini_buffer_cnt = 0;
+#endif
+}
+
+void update_screen()
+{
+ // tiles are expected to be 8x8 pixels
+ // *INDENT-OFF*
+ __asm;
+ call _wait_int6
+
+ ld hl, (_dirty)
+
+update_loop:
+ ld a, h
+ or l
+ jr z, update_done
+
+ ; baddr in de; and reset it
+ xor a
+ ld e, (hl)
+ ld (hl), a
+ inc hl
+ ld d, (hl)
+ ld (hl), a
+ inc hl
+
+ ; clean dirty flag
+ res 7, d
+ ld a, d
+ or e
+ jr nz, linked_buffer
+
+ ; not linked, use the original tile
+ ; hl has *t
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ dec hl
+
+linked_buffer:
+ ; skip *t
+ inc hl
+ inc hl
+
+ ; saddr in a, c
+ ld c, (hl)
+ inc hl
+ ld a, (hl)
+ inc hl
+
+ ; pointing to next
+ push hl
+
+ ; baddr in hl
+ ex de, hl
+ ; saddr to de
+ ld e, c
+ ld d, a
+
+ ld a, #8
+put_tile0:
+ ldi
+ ldi
+ ldi
+ ldi
+
+ dec a
+ jp z, put_tile2
+
+ ; inc destination
+ ex de, hl
+ ld bc, #0x07fc
+ add hl, bc
+ jp nc, put_tile1
+ ld bc, #0xc048
+ add hl, bc
+put_tile1:
+ ex de, hl
+ jr put_tile0
+
+put_tile2:
+ ; next dirty tile
+ pop hl
+ ld a, (hl)
+ inc hl
+ ld h, (hl)
+ ld l, a
+
+ jp update_loop
+
+update_done:
+
+ ; a is 0
+ ld (_dirty), a
+ ld (_dirty + #1), a
+
+#ifdef FENCE_DEBUG
+ ld (_mini_buffer_cnt), a
+#endif
+
+ ; reset mini buffers (begin buffer at 0x0100)
+ ld (_mini_buffer), a
+ inc a
+ ld (_mini_buffer + #1), a
+ __endasm;
+ // *INDENT-ON*
+}
+
+void validate_screen()
+{
+ for (; dirty; dirty = dirty->n)
+ dirty->baddr = 0;
+
+ // dirty is NULL
+ mini_buffer = (uint8_t *)BUFF_ADDR;
+}
+
+void invalidate_screen()
+{
+ uint16_t i;
+
+ dirty = NULL;
+ mini_buffer = (uint8_t *)BUFF_ADDR;
+
+ for (i = TMW * TMH; i; i--)
+ {
+ tiles[i - 1].baddr &= ADDR_BITS;
+ invalidate_tile(&tiles[i - 1]);
+ }
+}
+
+void invalidate_tile(struct st_tile *st) __z88dk_fastcall
+{
+ /*
+ if (st->baddr & DIRTY_BIT)
+ return;
+
+ st->baddr |= DIRTY_BIT;
+ st->n = dirty;
+ dirty = st;
+ */
+ // *INDENT-OFF*
+ __asm;
+ ld e, l
+ ld d, h
+
+ ; test for dirty bit
+ inc hl
+ ld a, (hl)
+ bit #7, a
+ ret nz
+
+ ; set dirty bit
+ set #7, a
+ ld (hl), a
+
+ ; set *n to dirty
+ ld a, #5
+ add a, l
+ ld l, a
+ adc a, h
+ sub l
+ ld h, a
+
+ ld a, (#_dirty)
+ ld (hl), a
+ inc hl
+ ld a, (#_dirty + #1)
+ ld (hl), a
+
+ ; set dirty to *st
+ ld (_dirty), de
+ __endasm;
+ // *INDENT-ON*
+}
+
+struct st_tile *get_tile_xy(uint8_t x, uint8_t y)
+{
+ // x and y in tilemap coordinates
+
+#ifdef FENCE_DEBUG
+ if (x > TMW || y > TMH)
+ set_hw_border(0x4b);
+#endif
+
+ return &tiles[x + (uint16_t)y * TMW];
+}
+
+#pragma save
+#pragma disable_warning 59
+uint8_t is_invalid_tile(struct st_tile *st) __z88dk_fastcall
+{
+ // *INDENT-OFF*
+ __asm;
+ inc hl
+ ld a, (hl)
+ and #128
+ ld l, a
+ __endasm;
+ // *INDENT-ON*
+}
+#pragma restore
+
+#pragma save
+#pragma disable_warning 59
+uint8_t is_invalid_tile2(struct st_tile *st) __z88dk_fastcall
+{
+ // *INDENT-OFF*
+ __asm;
+ inc hl
+ ld a, (hl)
+ and #128
+ jr nz, is_invalid
+ ld bc, #(TMW * 8)
+ add hl, bc
+ ld a, (hl)
+ and #128
+is_invalid:
+ ld l, a
+ __endasm;
+ // *INDENT-ON*
+}
+#pragma restore
+
+void erase_tile(struct st_tile *st) __z88dk_fastcall
+{
+ // *INDENT-OFF*
+ __asm;
+ ; if it has a linked buffer, it was erased already
+ ; testing MSB only
+ inc hl
+ ld a, (hl)
+ or a
+ ret nz
+
+ dec hl
+
+ ; and if there is no linked buffer, it is not invalidated
+
+ ; set dirty bit (if not set already)
+ jp _invalidate_tile
+ __endasm;
+ // *INDENT-ON*
+}
+
+void link_buffer(struct st_tile *st) __z88dk_fastcall
+{
+ // *INDENT-OFF*
+ __asm;
+ ; check is not linked already
+ ; testing MSB only
+ inc hl
+ ld a, (hl)
+ and #127
+ ret nz
+
+ dec hl
+
+#ifdef FENCE_DEBUG
+ ld a, (_mini_buffer_cnt)
+ inc a
+ ld (_mini_buffer_cnt), a
+
+ cp #MAX_MINI_BUFFER
+ jp c, mini_buffers_ok
+
+ ld hl, #0x58
+ jp _set_hw_border
+
+mini_buffers_ok:
+#endif
+
+ ; save *st
+ push hl
+
+ ; allocate a new buffer
+ ex de, hl
+ ld hl, (_mini_buffer)
+ ld c, l
+ ld b, h
+ ex de, hl
+
+ ; new buffer to baddr, preserve dirty bit
+ ld (hl), c
+ inc hl
+ ld a, (hl)
+ and #128
+ or b
+ ld (hl), a
+ dec hl
+
+ ; move mini buffer pointer
+ ex de, hl
+ ld hl, #(TW * TH / 2)
+ add hl, bc
+ ld (_mini_buffer), hl
+ ex de, hl
+
+ ; baddr to de
+ ld e, c
+ ld d, b
+
+ ; *t to hl
+ inc hl
+ inc hl
+ ld a, (hl)
+ inc hl
+ ld h, (hl)
+ ld l, a
+
+#ifdef SPRITE_DEBUG
+ ld hl, #_bgtiles + 14 * 32
+#endif
+
+ ldi
+ ldi
+ ldi
+ ldi
+
+ ldi
+ ldi
+ ldi
+ ldi
+
+ ldi
+ ldi
+ ldi
+ ldi
+
+ ldi
+ ldi
+ ldi
+ ldi
+
+ ldi
+ ldi
+ ldi
+ ldi
+
+ ldi
+ ldi
+ ldi
+ ldi
+
+ ldi
+ ldi
+ ldi
+ ldi
+
+ ldi
+ ldi
+ ldi
+ ldi
+
+ ; *st
+ pop hl
+ jp _invalidate_tile
+ __endasm;
+ // *INDENT-ON*
+}
+
+void put_tile(const uint8_t *t, struct st_tile *st)
+{
+ st->t = t;
+ invalidate_tile(st);
+}
+
+#pragma save
+#pragma disable_warning 85
+void erase_sprite(uint8_t x, uint8_t y, uint8_t th)
+{
+ // for 8 pixels wide sprites, th in tiles
+ // *INDENT-OFF*
+ __asm;
+ ld hl, #2
+ add hl, sp
+ ld c, (hl) ; x
+ inc hl
+ ld e, (hl) ; y
+ inc hl
+ ld b, (hl) ; th
+
+ ld a, e
+
+ ; out of bounds check
+ cp #(TMH * TH - 8)
+ jr nc, no_inc_th
+
+ and #0x07
+ jr z, no_inc_th
+ inc b
+
+no_inc_th:
+ ld l, c ; x
+ srl l
+ srl l
+ srl l
+
+ ld a, e ; y
+ srl a
+ srl a
+ srl a
+ add a, b ; add th
+ ld h, a
+
+ push bc
+ push hl
+ call _get_tile_xy
+ pop af
+ pop bc
+ ; hl has the tile addr
+
+ .db 0xdd
+ ld l, b
+
+ ld a, c
+ and #0x07
+ jr z, erase_tile_loop_no_extra_x
+
+erase_tile_loop:
+ xor a
+ ld de, #(TMW * 8)
+ sbc hl, de
+ ; dec tile ptr 1 row as th decrements
+
+ push hl
+ call _erase_tile
+ pop hl
+
+ push hl
+ ld de, #8
+ add hl, de
+ ; tile ptr + 1
+ call _erase_tile
+ pop hl
+
+ .db 0xdd
+ dec l
+ jr nz, erase_tile_loop
+ ret
+
+erase_tile_loop_no_extra_x:
+ xor a
+ ld de, #(TMW * 8)
+ sbc hl, de
+ ; dec tile ptr 1 row as th decrements
+
+ push hl
+ call _erase_tile
+ pop hl
+
+ .db 0xdd
+ dec l
+ jr nz, erase_tile_loop_no_extra_x
+
+ __endasm;
+ // *INDENT-ON*
+}
+#pragma restore
+
+const uint8_t inv_table[] = {
+ 0, 2, 1, 3, 8, 10, 9, 11, 4, 6, 5, 7, 12, 14, 13, 15, 32, 34, 33, 35, 40,
+ 42, 41, 43, 36, 38, 37, 39, 44, 46, 45, 47, 16, 18, 17, 19, 24, 26, 25, 27,
+ 20, 22, 21, 23, 28, 30, 29, 31, 48, 50, 49, 51, 56, 58, 57, 59, 52, 54, 53,
+ 55, 60, 62, 61, 63, 128, 130, 129, 131, 136, 138, 137, 139, 132, 134, 133,
+ 135, 140, 142, 141, 143, 160, 162, 161, 163, 168, 170, 169, 171, 164, 166,
+ 165, 167, 172, 174, 173, 175, 144, 146, 145, 147, 152, 154, 153, 155, 148,
+ 150, 149, 151, 156, 158, 157, 159, 176, 178, 177, 179, 184, 186, 185, 187,
+ 180, 182, 181, 183, 188, 190, 189, 191, 64, 66, 65, 67, 72, 74, 73, 75, 68,
+ 70, 69, 71, 76, 78, 77, 79, 96, 98, 97, 99, 104, 106, 105, 107, 100, 102,
+ 101, 103, 108, 110, 109, 111, 80, 82, 81, 83, 88, 90, 89, 91, 84, 86, 85,
+ 87, 92, 94, 93, 95, 112, 114, 113, 115, 120, 122, 121, 123, 116, 118, 117,
+ 119, 124, 126, 125, 127, 192, 194, 193, 195, 200, 202, 201, 203, 196, 198,
+ 197, 199, 204, 206, 205, 207, 224, 226, 225, 227, 232, 234, 233, 235, 228,
+ 230, 229, 231, 236, 238, 237, 239, 208, 210, 209, 211, 216, 218, 217, 219,
+ 212, 214, 213, 215, 220, 222, 221, 223, 240, 242, 241, 243, 248, 250, 249,
+ 251, 244, 246, 245, 247, 252, 254, 253, 255
+};
+
+#pragma save
+#pragma disable_warning 85
+void put_sprite_rc_nm(uint8_t *d, uint8_t dox, uint8_t doy, uint8_t w, uint8_t h, const uint8_t *s, uint8_t sox, uint8_t soy)
+{
+ // x offset must be even
+
+ /*
+ uint8_t i, j;
+
+ d += dox + doy * TW / 2;
+ s += sox + soy * TW / 2;
+
+ for (j = 0; j < h; j++)
+ {
+ for (i = 0; i < w; i++)
+ *d++ = *s++;
+
+ s += (TW / 2) - w;
+ d += (TW / 2) - w;
+ }
+
+ */
+ // *INDENT-OFF*
+ __asm;
+
+ ld hl, #2
+ add hl, sp
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ inc hl
+ ; de has *d
+
+ ld b, #0
+ ld c, (hl)
+ ; bc has dox
+ inc hl
+ ld a, (hl)
+ sla a
+ sla a
+ ex de, hl
+ ; add dox
+ add hl, bc
+ ld c, a
+ ; bc has doy * TW / 2
+ add hl, bc
+ ex de, hl
+
+ ; d* is ready
+ push de
+
+ inc hl
+ inc hl
+ inc hl
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ inc hl
+ ; de has *s
+
+ ; b is 0
+ ld c, (hl)
+ ; bc has sox
+ inc hl
+ ld a, (hl)
+ sla a
+ sla a
+ ; a has soy * TW / 2
+ ex de, hl
+ ; add sox
+ add hl, bc
+ ld c, a
+ ; add soy * TW / 2
+ add hl, bc
+ ex de, hl
+
+ ld hl, #(6 + 2)
+ add hl, sp
+ ld c, (hl)
+ inc hl
+ ; c is w
+
+ ; the reminder
+ ld a, #4
+ sub c
+ ld (no_inv_sub_w_nm + 1), a
+
+ ; and build jump addr
+ sla a
+ ld (no_inv_inner_w_nm + 1), a
+
+
+ ld a, (hl)
+ ; h
+
+ ; hl has *s
+ ex de, hl
+
+ ; *d
+ pop de
+
+ ld b, #0
+no_inv_height_nm:
+
+no_inv_inner_w_nm:
+ jr no_inv_inner_w_nm
+
+ ldi
+ ldi
+ ldi
+ ldi
+
+ ; b is 0
+
+ ; inc *d
+no_inv_sub_w_nm:
+ ld c, #0
+
+ ex de, hl
+ add hl, bc
+ ex de, hl
+
+ ; inc *s
+ add hl, bc
+
+ dec a
+ jr nz, no_inv_height_nm
+
+ __endasm;
+ // *INDENT-ON*
+}
+#pragma restore
+
+#pragma save
+#pragma disable_warning 85
+void put_sprite_rc(uint8_t *d, uint8_t dox, uint8_t doy, uint8_t w, uint8_t h, const uint8_t *s, uint8_t sox, uint8_t soy, uint8_t inv)
+{
+ /*
+ uint8_t i, j;
+
+ d += dox + doy * TW / 2;
+
+ if (inv)
+ s += TW - 2 * (1 + sox) + soy * TW;
+ else
+ s += 2 * sox + soy * TW;
+
+ if (inv)
+ {
+ for (j = 0; j < h; j++)
+ {
+ for (i = 0; i < w; i++)
+ {
+ *d &= inv_table[*s++];
+ *d++ |= inv_table[*s];
+ s -= 3;
+ }
+ s += (TW / 2 + w) * 2;
+ d += TW / 2 - w;
+ }
+ }
+ else
+ {
+ for (j = 0; j < h; j++)
+ {
+ for (i = 0; i < w; i++)
+ {
+ *d &= *s++;
+ *d++ |= *s++;
+ }
+ s += (TW / 2 - w) * 2;
+ d += TW / 2 - w;
+ }
+ }
+ */
+ // *INDENT-OFF*
+ __asm;
+
+ ld hl, #2
+ add hl, sp
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ inc hl
+ ; de has *d
+
+ ld b, #0
+ ld c, (hl)
+ ; bc has dox
+ inc hl
+ ld a, (hl)
+ sla a
+ sla a
+ ex de, hl
+ add hl, bc
+ ld c, a
+ ; bc has doy * TW / 2
+ add hl, bc
+ ex de, hl
+
+ ; d* is ready
+ push de
+
+ inc hl
+ inc hl
+ inc hl
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ inc hl
+ ; de has *s
+
+ ; b is 0
+ inc hl
+ ld c, (hl)
+ sla c
+ sla c
+ sla c
+ ; bc has soy
+ ex de, hl
+ add hl, bc
+ ex de, hl
+
+ ; common *s part is ready
+ inc hl
+ ld a, (hl)
+ or a
+ jr z, setup_no_inv
+
+ ; blit inv
+
+ ld hl, #(10 + 2)
+ add hl, sp
+
+ ; b is 0
+ ld c, (hl)
+ inc c
+ sla c
+ xor a
+ sub a, c
+ ld c, a
+ ld a, #0
+ sbc b
+ ld b, a
+ ; bc has - 2 - 2 x sox
+ ex de, hl
+ add hl, bc
+ ld bc, #8
+ ; add TW
+ add hl, bc
+ ex de, hl
+
+ ; de has *s ready for inv
+
+ ld hl, #(6 + 2)
+ add hl, sp
+
+ ld a, (hl)
+ ; w
+
+ ; set this in the code
+ ld (inv_inner_w + 1), a
+
+ ld b, a
+ ld a, #4
+ sub b
+ ld (inv_sub_w + 1), a
+
+ ld a, #4
+ add b
+ sla a
+ ld (inv_add_w + 1), a
+
+ inc hl
+
+ ld a, (hl)
+
+ ; *d
+ pop hl
+ ex de, hl
+
+ push ix
+ .db 0xdd
+ ld h, a
+ ; h
+
+inv_height:
+
+ .db 0xdd
+inv_inner_w:
+ ld l, #0
+
+inv_width:
+
+ ; add 8-bit number to a 16-bit reg
+ ;
+ ; add a, l
+ ; ld l, a
+ ; adc a, h
+ ; sub l
+ ; ld h, a
+
+ ; first flip bg
+ ld a, (de)
+
+ ld bc, #_inv_table
+ add a, c
+ ld c, a
+ adc a, b
+ sub c
+ ld b, a
+
+ ld a, (bc)
+
+ ; and mask; or sprite
+ and (hl)
+ inc hl
+ or (hl)
+
+ ; flip again
+
+ ld bc, #_inv_table
+ add a, c
+ ld c, a
+ adc a, b
+ sub c
+ ld b, a
+
+ ld a, (bc)
+
+ ld (de), a
+ inc de
+
+ dec hl
+ dec hl
+ dec hl
+
+ .db 0xdd
+ dec l
+ jr nz, inv_width
+
+ ; inc *s
+inv_add_w:
+ ld a, #0
+ add a, l
+ ld l, a
+ adc a, h
+ sub l
+ ld h, a
+
+ ; inc *d
+inv_sub_w:
+ ld a, #0
+ add a, e
+ ld e, a
+ adc a, d
+ sub e
+ ld d, a
+
+ .db 0xdd
+ dec h
+ jr nz, inv_height
+
+ pop ix
+
+ ret
+
+setup_no_inv:
+
+ ld hl, #(10 + 2)
+ add hl, sp
+
+ ; b is 0
+ ld c, (hl)
+ sla c
+ ex de, hl
+ add hl, bc
+ ; add 2 x sox to *s
+ ex de, hl
+
+ ld hl, #(6 + 2)
+ add hl, sp
+ ld a, (hl)
+ inc hl
+ ; w
+ ld (no_inv_inner_w + 1), a
+
+ ld b, a
+ ld a, #4
+ sub b
+ ld (no_inv_sub_w + 1), a
+ sla a
+ ld (no_inv_sub_w2 + 1), a
+
+ ld c, (hl)
+ ; h
+
+ ; hl has *s
+ ex de, hl
+
+ ; *d
+ pop de
+
+no_inv_height:
+
+no_inv_inner_w:
+ ld b, #0
+
+no_inv_width:
+
+ ; and mask; or sprite
+ ld a, (de)
+ and (hl)
+ inc hl
+ or (hl)
+ inc hl
+ ld (de), a
+ inc de
+
+ djnz no_inv_width
+
+ ; inc *d
+no_inv_sub_w:
+ ld a, #0
+ add a, e
+ ld e, a
+ adc a, d
+ sub e
+ ld d, a
+
+ ; inc *s (x2)
+no_inv_sub_w2:
+ ld a, #0
+ add a, l
+ ld l, a
+ adc a, h
+ sub l
+ ld h, a
+
+ dec c
+ jr nz, no_inv_height
+
+ __endasm;
+ // *INDENT-ON*
+}
+#pragma restore
+
+void put_sprite4(const uint8_t *s, uint8_t x, uint8_t y, uint8_t th, uint8_t inv)
+{
+ /*
+ uint8_t xoffs, yoffs;
+
+ xoffs = (x % TW) / 2;
+ yoffs = y % TH;
+
+ tile_p = get_tile_xy(x >> 3, (y >> 3));
+
+ // top
+ link_buffer(tile_p);
+ put_sprite_rc((uint8_t *)(tile_p->baddr & ADDR_BITS), xoffs, yoffs, TW / 2 - xoffs, TH - yoffs, s, 0, 0, inv);
+ if (xoffs)
+ {
+ tile_p++;
+ link_buffer(tile_p);
+ put_sprite_rc((uint8_t *)(tile_p->baddr & ADDR_BITS), 0, yoffs, xoffs, TH - yoffs, s, TW / 2 - xoffs, 0, inv);
+ }
+
+ if (th > 1)
+ {
+ // center 1
+ tile_p = get_tile_xy(x >> 3, (y >> 3) + 1);
+
+ link_buffer(tile_p);
+ put_sprite_rc((uint8_t *)(tile_p->baddr & ADDR_BITS), xoffs, 0, TW / 2 - xoffs, TH, s, 0, TH - yoffs, inv);
+ if (xoffs)
+ {
+ tile_p++;
+ link_buffer(tile_p);
+ put_sprite_rc((uint8_t *)(tile_p->baddr & ADDR_BITS), 0, 0, xoffs, TH, s, TW / 2 - xoffs, TH - yoffs, inv);
+ }
+
+ if (th > 2)
+ {
+ // center 2
+ tile_p = get_tile_xy(x >> 3, (y >> 3) + 2);
+
+ link_buffer(tile_p);
+ put_sprite_rc((uint8_t *)(tile_p->baddr & ADDR_BITS), xoffs, 0, TW / 2 - xoffs, TH, s, 0, 2 * TH - yoffs, inv);
+ if (xoffs)
+ {
+ tile_p++;
+ link_buffer(tile_p);
+ put_sprite_rc((uint8_t *)(tile_p->baddr & ADDR_BITS), 0, 0, xoffs, TH, s, TW / 2 - xoffs, 2 * TH - yoffs, inv);
+ }
+
+ // reminder
+ if (yoffs) {
+ tile_p = get_tile_xy(x >> 3, (y >> 3) + 3);
+
+ link_buffer(tile_p);
+ put_sprite_rc((uint8_t *)(tile_p->baddr & ADDR_BITS), xoffs, 0, TW / 2 - xoffs, yoffs, s, 0, 3 * TH - yoffs, inv);
+ if (xoffs)
+ {
+ tile_p++;
+ link_buffer(tile_p);
+ put_sprite_rc((uint8_t *)(tile_p->baddr & ADDR_BITS), 0, 0, xoffs, yoffs, s, TW / 2 - xoffs, 3 * TH - yoffs, inv);
+ }
+ }
+ }
+ else
+ {
+ // reminder
+ if (yoffs) {
+ tile_p = get_tile_xy(x >> 3, (y >> 3) + 2);
+
+ link_buffer(tile_p);
+ put_sprite_rc((uint8_t *)(tile_p->baddr & ADDR_BITS), xoffs, 0, TW / 2 - xoffs, yoffs, s, 0, 2 * TH - yoffs, inv);
+ if (xoffs)
+ {
+ tile_p++;
+ link_buffer(tile_p);
+ put_sprite_rc((uint8_t *)(tile_p->baddr & ADDR_BITS), 0, 0, xoffs, yoffs, s, TW / 2 - xoffs, 2 * TH - yoffs, inv);
+ }
+ }
+ }
+ }
+ else
+ {
+ // reminder
+ if (yoffs) {
+ tile_p = get_tile_xy(x >> 3, (y >> 3) + 1);
+
+ link_buffer(tile_p);
+ put_sprite_rc((uint8_t *)(tile_p->baddr & ADDR_BITS), xoffs, 0, TW / 2 - xoffs, yoffs, s, 0, TH - yoffs, inv);
+ if (xoffs)
+ {
+ tile_p++;
+ link_buffer(tile_p);
+ put_sprite_rc((uint8_t *)(tile_p->baddr & ADDR_BITS), 0, 0, xoffs, yoffs, s, TW / 2 - xoffs, TH - yoffs, inv);
+ }
+ }
+ }
+ */
+
+ // *INDENT-OFF*
+ __asm;
+
+ ; for xoffs and yoffs
+ push af
+
+ ld ix, #0
+ add ix, sp
+
+ ld a, 2 + 4 (ix)
+ and #7
+ srl a
+ ld 0 (ix), a
+ ; (x mod 8) / 2 -> xoffs
+
+ ld a, 3 + 4 (ix)
+ and #7
+ ld 1 (ix), a
+ ; (y mod 8) -> yoffs
+
+ ld e, 2 + 4 (ix)
+ srl e
+ srl e
+ srl e
+ ld d, 3 + 4 (ix)
+ srl d
+ srl d
+ srl d
+ push de
+ ; x >> 3, y >> 3
+ call _get_tile_xy
+ pop af
+
+ ; *tile
+ push hl
+ ; fastcall, does not use ix
+ call _link_buffer
+ pop hl
+ push hl
+
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ res #7, d
+ ; de has baddr & ADDR_BITS
+
+ ld b, 5 + 4 (ix)
+ ; inv
+ ld c, #0
+ push bc
+ ; soy
+ ld b, #0
+ push bc
+ inc sp
+ ; soy
+ ld c, 0 + 4 (ix)
+ ld b, 1 + 4 (ix)
+ push bc
+ ; *s
+ ; xoffs & yoffs are on the stack
+ ld a, #4
+ sub 0 (ix)
+ ld c, a
+ ld a, #8
+ sub 1 (ix)
+ ld b, a
+ push bc
+ ; TH / 2 - xoffs, TH - yoffs
+ ld c, 0 (ix)
+ ld b, 1 (ix)
+ push bc
+ ; xoffs, yoffs
+ push de
+ ; baddr
+ call _put_sprite_rc
+
+ ld iy, #0
+ add iy, sp
+
+ ld a, 0 (ix)
+ or a
+ jr z, put_sprite4_xaligned0
+
+ ; *tile
+ ld l, 11 (iy)
+ ld h, 12 (iy)
+
+ ld bc, #8
+ add hl, bc
+ ; *tile + 1
+
+ push hl
+ ; fastcall, does not use ix or iy
+ call _link_buffer
+ pop hl
+
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ res #7, d
+ ; de has baddr & ADDR_BITS
+
+ pop af
+ push de
+ ; save parameter
+
+ ; update parameters that changed
+ xor a
+ ld 2 (iy), a
+ ; 0
+
+ ld c, 4 (iy)
+ ; TW / 2 - xoffs
+
+ ld a, 0 (ix)
+ ld 4 (iy), a
+ ; xoffs
+
+ ld 8 (iy), c
+ ; TW / 2 - xoffs
+
+ call _put_sprite_rc
+
+put_sprite4_xaligned0:
+
+ ld a, 4 + 4 (ix)
+ cp #1
+ jp z, put_sprite4_reminder_th1
+
+ ; *tile
+ ld l, 11 (iy)
+ ld h, 12 (iy)
+
+ ld bc, #(8 * 20)
+ add hl, bc
+ ; *tile + TMW
+
+ push hl
+ ; fastcall, does not use ix or iy
+ call _link_buffer
+ pop hl
+
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ res #7, d
+ ; de has baddr & ADDR_BITS
+
+ pop af
+ push de
+ ; save parameter
+
+ ; update parameters
+ ld a, 0 (ix)
+ ld 2 (iy), a
+ ; xoffs
+
+ xor a
+ ld 3 (iy), a
+ ld 8 (iy), a
+ ; 0
+
+ ld a, #4
+ sub 0 (ix)
+ ld 4 (iy), a
+ ; TW / 2 - xoffs
+
+ ld a, #8
+ ld 5 (iy), a
+ ; TH
+
+ ; a is 8
+ sub 1 (ix)
+ ld 9 (iy), a
+ ; TH - yoffs
+
+ call _put_sprite_rc
+
+ ld a, 0 (ix)
+ or a
+ jr z, put_sprite4_xaligned1
+
+ ; *tile
+ ld l, 11 (iy)
+ ld h, 12 (iy)
+
+ ld bc, #(8 * (20 + 1))
+ add hl, bc
+ ; *tile + TMW + 1
+
+ push hl
+ ; fastcall, does not use ix or iy
+ call _link_buffer
+ pop hl
+
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ res #7, d
+ ; de has baddr & ADDR_BITS
+
+ pop af
+ push de
+ ; save parameter
+
+ ; update parameters that changed
+ xor a
+ ld 2 (iy), a
+ ; 0
+
+ ld c, 4 (iy)
+ ; TW / 2 - xoffs
+
+ ld a, 0 (ix)
+ ld 4 (iy), a
+ ; xoffs
+
+ ld 8 (iy), c
+ ; TW / 2 - xoffs
+
+ call _put_sprite_rc
+
+put_sprite4_xaligned1:
+
+ ld a, 4 + 4 (ix)
+ cp #2
+ jp z, put_sprite4_reminder_th2
+
+ ; *tile
+ ld l, 11 (iy)
+ ld h, 12 (iy)
+
+ ld bc, #(8 * 20 * 2)
+ add hl, bc
+ ; *tile + TMW * 2
+
+ push hl
+ ; fastcall, does not use ix or iy
+ call _link_buffer
+ pop hl
+
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ res #7, d
+ ; de has baddr & ADDR_BITS
+
+ pop af
+ push de
+ ; save parameter
+
+ ; update parameters
+ ld a, 0 (ix)
+ ld 2 (iy), a
+ ; xoffs
+
+ xor a
+ ld 3 (iy), a
+ ld 8 (iy), a
+ ; 0
+
+ ld a, #4
+ sub 0 (ix)
+ ld 4 (iy), a
+ ; TW / 2 - xoffs
+
+ ld a, #8
+ ld 5 (iy), a
+ ; TH
+
+ sla a
+ ; TH * 2
+ sub 1 (ix)
+ ld 9 (iy), a
+ ; 2 * TH - yoffs
+
+ call _put_sprite_rc
+
+ ld a, 0 (ix)
+ or a
+ jr z, put_sprite4_reminder
+
+ ; *tile
+ ld l, 11 (iy)
+ ld h, 12 (iy)
+
+ ld bc, #(8 * (20 * 2 + 1))
+ add hl, bc
+ ; *tile + 2 * TMW + 1
+
+ push hl
+ ; fastcall, does not use ix or iy
+ call _link_buffer
+ pop hl
+
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ res #7, d
+ ; de has baddr & ADDR_BITS
+
+ pop af
+ push de
+ ; save parameter
+
+ ; update parameters that changed
+ xor a
+ ld 2 (iy), a
+ ; 0
+
+ ld c, 4 (iy)
+ ; TW / 2 - xoffs
+
+ ld a, 0 (ix)
+ ld 4 (iy), a
+ ; xoffs
+
+ ld 8 (iy), c
+ ; TW / 2 - xoffs
+
+ call _put_sprite_rc
+
+put_sprite4_reminder:
+ ld a, 1 (ix)
+ or a
+ ; yoffs
+ jp z, put_sprite4_done
+
+ ; *tile
+ ld l, 11 (iy)
+ ld h, 12 (iy)
+
+ ld bc, #(8 * 20 * 3)
+ add hl, bc
+ ; *tile + TMW * 3
+
+ push hl
+ ; fastcall, does not use ix or iy
+ call _link_buffer
+ pop hl
+
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ res #7, d
+ ; de has baddr & ADDR_BITS
+
+ pop af
+ push de
+ ; save parameter
+
+ ; update parameters
+ ld a, 0 (ix)
+ ld 2 (iy), a
+ ; xoffs
+
+ xor a
+ ld 3 (iy), a
+ ld 8 (iy), a
+ ; 0
+
+ ld a, #4
+ sub 0 (ix)
+ ld 4 (iy), a
+ ; TW / 2 - xoffs
+
+ ld a, 1 (ix)
+ ld 5 (iy), a
+ ; yoffs
+
+ ld a, #(8 * 3)
+ sub 1 (ix)
+ ld 9 (iy), a
+ ; 3 * TH - yoffs
+
+ call _put_sprite_rc
+
+ ld a, 0 (ix)
+ or a
+ jp z, put_sprite4_done
+
+ ; *tile
+ ld l, 11 (iy)
+ ld h, 12 (iy)
+
+ ld bc, #(8 * (20 * 3 + 1))
+ add hl, bc
+ ; *tile + 3 * TMW + 1
+
+ push hl
+ ; fastcall, does not use ix or iy
+ call _link_buffer
+ pop hl
+
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ res #7, d
+ ; de has baddr & ADDR_BITS
+
+ pop af
+ push de
+ ; save parameter
+
+ ; update parameters that changed
+ xor a
+ ld 2 (iy), a
+ ; 0
+
+ ld c, 4 (iy)
+ ; TW / 2 - xoffs
+
+ ld a, 0 (ix)
+ ld 4 (iy), a
+ ; xoffs
+
+ ld 8 (iy), c
+ ; TW / 2 - xoffs
+
+ ld a, #(8 * 3)
+ sub 1 (ix)
+ ld 9 (iy), a
+ ; 3 * TH - yoffs
+
+ call _put_sprite_rc
+
+ jr put_sprite4_done
+
+put_sprite4_reminder_th2:
+
+ ld a, 1 (ix)
+ or a
+ ; yoffs
+ jr z, put_sprite4_done
+
+ ; *tile
+ ld l, 11 (iy)
+ ld h, 12 (iy)
+
+ ld bc, #(8 * 20 * 2)
+ add hl, bc
+ ; *tile + TMW * 2
+
+ push hl
+ ; fastcall, does not use ix or iy
+ call _link_buffer
+ pop hl
+
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ res #7, d
+ ; de has baddr & ADDR_BITS
+
+ pop af
+ push de
+ ; save parameter
+
+ ; update parameters
+ ld a, 0 (ix)
+ ld 2 (iy), a
+ ; xoffs
+
+ xor a
+ ld 3 (iy), a
+ ld 8 (iy), a
+ ; 0
+
+ ld a, #4
+ sub 0 (ix)
+ ld 4 (iy), a
+ ; TW / 2 - xoffs
+
+ ld a, 1 (ix)
+ ld 5 (iy), a
+ ; yoffs
+
+ ld a, #(8 * 2)
+ sub 1 (ix)
+ ld 9 (iy), a
+ ; 2 * TH - yoffs
+
+ call _put_sprite_rc
+
+ ld a, 0 (ix)
+ or a
+ jr z, put_sprite4_done
+
+ ; *tile
+ ld l, 11 (iy)
+ ld h, 12 (iy)
+
+ ld bc, #(8 * (20 * 2 + 1))
+ add hl, bc
+ ; *tile + 2 * TMW + 1
+
+ push hl
+ ; fastcall, does not use ix or iy
+ call _link_buffer
+ pop hl
+
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ res #7, d
+ ; de has baddr & ADDR_BITS
+
+ pop af
+ push de
+ ; save parameter
+
+ ; update parameters that changed
+ xor a
+ ld 2 (iy), a
+ ; 0
+
+ ld c, 4 (iy)
+ ; TW / 2 - xoffs
+
+ ld a, 0 (ix)
+ ld 4 (iy), a
+ ; xoffs
+
+ ld 8 (iy), c
+ ; TW / 2 - xoffs
+
+ ld a, #(8 * 2)
+ sub 1 (ix)
+ ld 9 (iy), a
+ ; 2 * TH - yoffs
+
+ call _put_sprite_rc
+
+ put_sprite4_done:
+ ld hl, #15
+ add hl, sp
+ ld sp, hl
+
+ ret
+
+put_sprite4_reminder_th1:
+
+ ld a, 1 (ix)
+ or a
+ ; yoffs
+ jr z, put_sprite4_done
+
+ ; *tile
+ ld l, 11 (iy)
+ ld h, 12 (iy)
+
+ ld bc, #(8 * 20 * 1)
+ add hl, bc
+ ; *tile + TMW * 1
+
+ push hl
+ ; fastcall, does not use ix or iy
+ call _link_buffer
+ pop hl
+
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ res #7, d
+ ; de has baddr & ADDR_BITS
+
+ pop af
+ push de
+ ; save parameter
+
+ ; update parameters
+ ld a, 0 (ix)
+ ld 2 (iy), a
+ ; xoffs
+
+ xor a
+ ld 3 (iy), a
+ ld 8 (iy), a
+ ; 0
+
+ ld a, #4
+ sub 0 (ix)
+ ld 4 (iy), a
+ ; TW / 2 - xoffs
+
+ ld a, 1 (ix)
+ ld 5 (iy), a
+ ; yoffs
+
+ ld a, #8
+ sub 1 (ix)
+ ld 9 (iy), a
+ ; TH - yoffs
+
+ call _put_sprite_rc
+
+ ld a, 0 (ix)
+ or a
+ jr z, put_sprite4_done
+
+ ; *tile
+ ld l, 11 (iy)
+ ld h, 12 (iy)
+
+ ld bc, #(8 * (20 * 1 + 1))
+ add hl, bc
+ ; *tile + 1 * TMW + 1
+
+ push hl
+ ; fastcall, does not use ix or iy
+ call _link_buffer
+ pop hl
+
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ res #7, d
+ ; de has baddr & ADDR_BITS
+
+ pop af
+ push de
+ ; save parameter
+
+ ; update parameters that changed
+ xor a
+ ld 2 (iy), a
+ ; 0
+
+ ld c, 4 (iy)
+ ; TW / 2 - xoffs
+
+ ld a, 0 (ix)
+ ld 4 (iy), a
+ ; xoffs
+
+ ld 8 (iy), c
+ ; TW / 2 - xoffs
+
+ ld a, #8
+ sub 1 (ix)
+ ld 9 (iy), a
+ ; TH - yoffs
+
+ call _put_sprite_rc
+
+ jp put_sprite4_done
+
+ __endasm;
+ // *INDENT-ON*
+}
+
+uint8_t tink, fink, bink;
+uint8_t tfont[16];
+
+void _set_text_ink(uint8_t c, uint8_t *t)
+{
+ *t = ((c & 1) << 7) | ((c & 1) << 6) | ((c & 4) << 3) | ((c & 4) << 2)
+ | ((c & 2) << 2) | ((c & 2) << 1) | ((c & 8) >> 2) | ((c & 8) >> 3);
+}
+
+void set_text_ink(uint8_t c, uint8_t c2, uint8_t c3)
+{
+ _set_text_ink(c, &tink);
+ _set_text_ink(c2, &fink);
+ _set_text_ink(c3, &bink);
+}
+
+#include <string.h>
+void cpc_PutSp(char *sprite, char height, char width, int address);
+
+void tint_text()
+{
+ // *INDENT-OFF*
+ __asm;
+ ld de, #_tfont
+
+ ld hl, #_tink
+ ld c, (hl)
+
+ ld hl, #_fink
+ ld a, (hl)
+
+ ld hl, #_bink
+ ld h, (hl)
+ ld l, a
+
+ ld b, #6
+tint_loop1:
+ ld a, (de)
+ and c
+ ld (de), a
+ inc de
+
+ djnz tint_loop1
+
+ ld b, #6
+tint_loop2:
+ ld a, (de)
+ and l
+ ld (de), a
+ inc de
+
+ djnz tint_loop2
+
+ ld b, #4
+tint_loop3:
+ ld a, (de)
+ and h
+ ld (de), a
+ inc de
+
+ djnz tint_loop3
+
+ __endasm;
+ // *INDENT-ON*
+}
+
+#pragma save
+#pragma disable_warning 85
+void put_text(const char *s, uint8_t x, uint8_t y)
+{
+ /*
+ while(*s)
+ {
+ memcpy(tfont, spfont[*s - 31], 16);
+ tint_text();
+ cpc_PutSp(tfont, 8, 2, screen_addr(x, y));
+ x += 2;
+ s++;
+ }
+ */
+
+ // *INDENT-OFF*
+ __asm;
+ ld hl, #2
+ add hl, sp
+
+ ld e, (hl)
+ inc hl
+ ld d, (hl) ; s
+ inc hl
+ ld b, (hl) ; x
+ inc hl
+ ld c, (hl) ; y
+
+put_text_loop:
+ push de
+ push bc
+
+ ld hl, #16
+ push hl
+
+ ld a, (de)
+ sub #31
+ ld l, a ; h is 0
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ ld bc, #_spfont
+ add hl, bc
+ push hl
+
+ ld hl, #_tfont
+ push hl
+
+ call _memcpy
+ pop af
+ pop af
+ pop af
+
+ call _tint_text
+
+ pop hl
+ push hl
+
+ ld a, h
+ ld h, #0
+ push hl
+ ld l, a
+ push hl
+
+ call _screen_addr
+ pop af
+ pop af
+
+ push hl
+
+ ld hl, #0x0208
+ push hl
+
+ ld hl, #_tfont
+ push hl
+
+ call _cpc_PutSp
+ pop af
+ pop af
+ pop af
+
+ pop bc
+ pop de
+
+ inc b
+ inc b
+
+ inc de
+ ld a, (de)
+ or #0
+ jr nz, put_text_loop
+
+ __endasm;
+ // *INDENT-ON*
+}
+#pragma restore
+
+#pragma save
+#pragma disable_warning 85
+void fill_screen(const uint8_t *t) __z88dk_fastcall
+{
+ // *INDENT-OFF*
+ __asm;
+ ex de, hl
+
+ ld hl, #_tiles
+ ld bc, #(TMW * TMH)
+
+fill_screen_loop:
+ inc hl
+ inc hl
+
+ ld (hl), e
+ inc hl
+ ld (hl), d
+ inc hl
+
+ inc hl
+ inc hl
+ inc hl
+ inc hl
+
+ dec bc
+ ld a, c
+ or b
+ jr nz, fill_screen_loop
+
+ jp _invalidate_screen
+ __endasm;
+ // *INDENT-ON*
+}
+#pragma restore
+
+uint8_t abs_sub(uint8_t a, uint8_t b)
+{
+ /*
+ if (a > b)
+ return (a - b);
+ else
+ return (b - a);
+ */
+ // *INDENT-OFF*
+ __asm;
+ ld hl, #2
+ add hl, sp
+ ld b, (hl)
+ inc hl
+ ld c, (hl)
+ ld a, b
+ sub c
+ jr c, b_minus_a
+ ld l, a
+ ret
+b_minus_a:
+ ld a, c
+ sub b
+ ld l, a
+ __endasm;
+ // *INDENT-ON*
+}
+
+// EOF
diff --git a/src/splib.h b/src/splib.h
new file mode 100644
index 0000000..0fc4f1c
--- /dev/null
+++ b/src/splib.h
@@ -0,0 +1,82 @@
+/*
+ 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/>.
+ */
+#ifndef _SPLIB_H
+#define _SPLIB_H
+
+#include <stdint.h>
+
+// this is 160x160; 8x8 pixels tiles
+#define TMW 20
+#define TMH 20
+
+#define TW 8
+#define TH 8
+
+// All the buffer must be under 0x8000
+#define BUFF_ADDR 0x0100
+// CONFIGURE **
+
+#define SCREEN_ADDR(x, y) ((int)(0xc000 + x + ((y / 8) * 80) + ((y % 8) * 2048)))
+
+#define DIRTY_BIT 0x8000
+#define ADDR_BITS 0x7fff
+
+struct st_tile
+{
+ uint16_t baddr;
+ const uint8_t *t;
+ uint16_t saddr;
+ struct st_tile *n;
+};
+
+void init_tiles();
+void update_screen();
+void validate_screen();
+void invalidate_screen();
+
+struct st_tile *get_tile_xy(uint8_t x, uint8_t y);
+
+void invalidate_tile(struct st_tile *st) __z88dk_fastcall;
+uint8_t is_invalid_tile(struct st_tile *st) __z88dk_fastcall;
+uint8_t is_invalid_tile2(struct st_tile *st) __z88dk_fastcall;
+
+void erase_tile(struct st_tile *st) __z88dk_fastcall;
+void link_buffer(struct st_tile *st) __z88dk_fastcall;
+void link_buffer_xy(uint8_t x, uint8_t y);
+
+void put_tile(const uint8_t *t, struct st_tile *st);
+void put_sprite4(const uint8_t *s, uint8_t x, uint8_t y, uint8_t th, uint8_t inv);
+void erase_sprite(uint8_t x, uint8_t y, uint8_t th);
+
+void fill_screen(const uint8_t *t) __z88dk_fastcall;
+
+// misc
+void clear_screen();
+void wait_vsync();
+void set_hw_border(uint8_t c) __z88dk_fastcall;
+void set_hw_ink(uint8_t ink, uint8_t c);
+void pad_numbers(uint8_t *s, uint8_t limit, uint16_t number);
+uint16_t screen_addr(uint16_t x, uint16_t y);
+
+uint8_t abs_sub(uint8_t a, uint8_t b);
+
+// require spfont, 63 chars (starting on 31)
+void set_text_ink(uint8_t c, uint8_t c2, uint8_t c3);
+void put_text(const char *s, uint8_t x, uint8_t y);
+
+#endif // _SPLIB_H
diff --git a/src/turboload.s b/src/turboload.s
new file mode 100644
index 0000000..0b6b984
--- /dev/null
+++ b/src/turboload.s
@@ -0,0 +1,250 @@
+;; Modified Topo soft loader
+;;
+;; Multi-colour bars in the border, aka "Spanish" style Spectrum variant loader.
+;;
+;; This loader uses standard Spectrum ROM timings. In fact it is essentially a CPC version of the
+;; Spectrum ROM loader itself.
+;;
+;;
+;; Loader form on cassette:
+;; Pilot tone (min 256 pulses)
+;; Sync pulse
+;; 1 byte sync byte (on spectrum 0 for header and &ff for data)
+;; n bytes of data (defined by DE register)
+;; 1 byte parity checksum
+;;
+;; Original had the following bugs:
+;; 1. bit 7 of R register had to be 1, otherwise it could change ram/rom configuration while loading
+;; 2. flags were not returned, so you didn't know if the data was read incorrectly or not
+;;
+;; It also had extra code that was unnecessary:
+;; 1. turning cassette motor on/off was complex, when in previous instructions they had turned it on anyway.
+;; 2. code for changing border colour presumably on a read error. but didn't seem to be used.
+;;
+;;
+;; Entry conditions:
+;;
+;; IX = start
+;; DE = length (D must not be &ff)
+;; A = sync byte expected
+;;
+;; Interrupts must be disabled
+;;
+;; Exit Conditions:
+;; Alternative register set is corrupted.
+;;
+;; carry clear - load ok
+;; carry set, zero set - time up
+;; carry set, zero reset - if esc pressed
+;;
+;; Use 2CDT to write Spectrum ROM blocks to a CDT. Note that at this time it will only write a sync byte of &ff.
+
+_turboload:
+
+inc d ;; reset the zero flag (D cannot hold &ff)
+ex af,af' ;; A register holds sync byte.
+dec d ;; restore D
+exx
+;; we need B' to be so we can write to gate-array i/o port for colour change when loading
+
+ld bc,#0x7f00+#0x10 ;; Gate-Array + border
+out (c),c ;; select pen index to change while loading (&10 is border)
+ld c,#0x54 ;; set to black
+out (c),c
+exx
+
+ld bc,#0xf40e ;; select AY register 14 (for reading keyboard)
+out (c),c
+
+ld bc,#0xf600+#0xc0+#0x10 ;; "AY write register" and enable tape motor
+out (c),c
+
+ld c,#0x10 ;; "AY inactive" and enable tape motor (register is latched into AY)
+out (c),c
+
+ld bc,#0xf792 ;; set PPI port A to read (so we can read keyboard data)
+out (c),c ;; this will also write 0 to port C and port A on CPC.
+
+ld bc,#0xf600+#0x40+#0x10+#0x8 ;; tape motor enable, "AY read register", select keyboard row 8
+ ;; (keys on this row: z, caps lock, a, tab, q, esc, 2, 1)
+ ;; we are only interested in ESC
+out (c),c
+
+;; make an initial read of cassette input
+ld a,#0xf5 ;; PPI port B
+in a,(#0) ;; read port (tape read etc)
+and #0x80 ;; isolate tape read data
+
+ld c,a
+cp a ;; set the zero flag
+l8107:
+ret nz ;; returns if esc key is pressed (was RET NZ)
+l8108:
+call l817b
+jr nc,l8107
+
+;; the wait is meant to be almost one second
+ld hl,#0x415
+l8110:
+djnz l8110
+dec hl
+ld a,h
+or l
+jr nz,l8110
+;; continue if only two edges are found within this allowed time period
+call l8177
+jr nc,l8107
+
+;; only accept leader signal
+l811c:
+ld b,#0x9c
+call l8177
+jr nc,l8107
+ld a,#0xb9
+cp b
+jr nc,l8108
+inc h
+jr nz,l811c
+
+;; on/off parts of sync pulse
+l812b:
+ld b,#0xc9
+call l817b
+jr nc,l8107
+ld a,b
+cp #0xd1
+jr nc,l812b
+l8137:
+call l817b
+ret nc
+
+ld h,#0 ;; parity matching byte (checksum)
+ld b,#0xb0 ;; timing constant for flag byte and data
+jr l815d
+
+l8145:
+ex af,af' ;; fetch the flags
+jr nz,l814d ;; jump if we are handling first byte
+;; L = data byte read from cassette
+;; store to RAM
+ld 0(ix),l
+jr l8157
+
+l814d:
+rr c ;; keep carry in safe place
+ ;; NOTE: Bit 7 is cassette read previous state
+ ;; We need to do a right shift so that cassette read moves into bit 6,
+ ;; our stored carry then goes into bit 7
+xor l ;; check sync byte is as we expect
+ret nz
+
+ld a,c ;; restore carry flag now
+rla ;; bit 7 goes into carry restoring it, bit 6 goes back to bit 7 to restore tape input value
+ld c,a
+inc de ;; increase counter to compensate for it's decrease
+jr l8159
+
+l8157:
+inc ix ;; increase destination
+l8159:
+dec de ;; decrease counter
+ex af,af' ;; save the flags
+
+ld b,#0xb2 ;; timing constant
+l815d:
+ld l,#1 ;; marker bit (defines number of bits to read, and finally becomes value read)
+l815f:
+call l8177
+ret nc
+
+ld a,#0xc3 ;; compare the length against approx 2,400T states, resetting the carry flag for a '0' and setting it for a '1'
+cp b
+rl l ;; include the new bit in the register
+ld b,#0xb0 ;; set timing constant for next bit
+jr nc,l815f
+
+;; L = data byte read
+;; combine with checksum
+ld a,h
+xor l
+ld h,a
+
+;; DE = count
+ld a,d
+or e
+jr nz,l8145
+
+exx
+ld c,#0x54 ;; make border black
+out (c),c
+exx
+
+ld bc,#0xf782 ;; set PPI port A for output
+out (c),c
+ld bc,#0xf600 ;; tape motor off, "AY inactive"
+out (c),c
+
+;; H = checksum byte
+;; H = 0 means checksum ok
+ld a,h
+cp #1
+;; return with carry flag set if the result is good
+;; carry clear otherwise
+ret
+
+
+;;------------------------------------------------------------
+l8177:
+call l817b ;; in effect call ld-edge-1 twice returning in between if there is an error
+ret nc
+
+;; wait 358T states before entering sampling loop
+l817b:
+ld a,#0x16 ;; [2]
+l817d:
+dec a ;; [1]
+jr nz,l817d ;; ([3]+[1])*&15+[2]
+
+and a
+l8181:
+inc b ;; count each pass
+ret z ;; return carry reset and zero set if time up.
+
+;; read keyboard
+ld a,#0xf4 ;; PPI port A
+in a,(#0) ;; read port and read keyboard through AY register 14
+and #0x04 ;; isolate state of bit 2 (ESC key)
+ ;; bit will be 0 if key is pressed
+xor #0x04 ;; has key been pressed?
+ret nz ;; quit (carry reset, zero reset)
+
+ld a,#0xf5 ;; PPI port B
+in a,(#0) ;; read port (tape read etc)
+xor c
+and #0x80 ;; isolate tape read
+jr z,l8181
+ld a,c ;; effectively toggle bit 7 of C
+cpl ;; which is the tape read state we want to see next
+ld c,a
+
+exx ;; swap to alternative register set so that we can change colour
+;; this sets colour and gives us the multi colour border
+;ld a,r ;; get R register
+;and #0x1f ;; ensure it is in range of colour value
+
+nop
+nop
+nop
+turbo_col:
+and #4
+
+or #0x40 ;; bit 7 = 0, bit 6 = 1
+out (c),a ;; write colour value
+nop ;; this is to ensure the number of cycles is the same
+ ;; compared to the replaced instructions
+ ;; (timings from toposoft loader)
+nop
+exx
+scf
+ret
+