diff --git a/assets/characters/enemies.chr b/assets/characters/enemies.chr index 1b3d14a..e54f31a 100644 Binary files a/assets/characters/enemies.chr and b/assets/characters/enemies.chr differ diff --git a/assets/dialogs.h b/assets/dialogs.h index 147d2d5..68f91f4 100644 --- a/assets/dialogs.h +++ b/assets/dialogs.h @@ -1,3 +1,5 @@ +#pragma rodata-name(push, "BANK1") + extern char dialog_beginning[]; extern char dialog_no_dlls[]; extern char dialog_gpu_dll[]; @@ -5,3 +7,5 @@ extern char dialog_disk_dll[]; extern char dialog_io_dll[]; extern char dialog_ram_dll[]; extern char dialog_the_end[]; + +#pragma rodata-name(pop) diff --git a/assets/dialogs.s b/assets/dialogs.s index fa993ce..b8196ad 100644 --- a/assets/dialogs.s +++ b/assets/dialogs.s @@ -1,5 +1,5 @@ .include "../src/charmap.inc" -.segment "RODATA" +.segment "BANK1" .export _dialog_beginning _dialog_beginning: .byte 1, "Welcome, Heroes of the System! I'm Queen Amda...", 2, "... and I'm Queen Intella. Please save our system from crashing.", 1, "Our Kingdom OS relies on four drivers: GPU.DLL, Disk.DLL, IO.DLL and RAM.DLL", 2, "Now the drivers are full of bugs. Dive into each driver, removing them. Cleaning the last sector will yield its crypto key. Return with it and the driver will be finally safe.", 1, "Now go, heroes! You're our last hope!", 0 .export _dialog_no_dlls diff --git a/assets/sprites.h b/assets/sprites.h index 738b41d..a97046e 100644 --- a/assets/sprites.h +++ b/assets/sprites.h @@ -6,6 +6,7 @@ extern const unsigned char menu_cursor_sprite[]; extern const unsigned char * melee_sprite[4]; extern const unsigned char * enemy_sprite[48]; extern const unsigned char * player_sprite[12]; +extern const unsigned char * item_sprite[1]; extern const unsigned char amda_sprite[]; extern const unsigned char intelle_sprite[]; diff --git a/assets/sprites.s b/assets/sprites.s index 155fdc8..e99d973 100644 --- a/assets/sprites.s +++ b/assets/sprites.s @@ -14,7 +14,7 @@ .export _intelle_sprite .segment "BANK1" .export _enemy_sprite - +.export _item_sprite .segment "RODATA" _default_cursor_sprite: @@ -281,3 +281,16 @@ _enemy_sprite: .word .ident (.concat("enemy_right_alt_", .string(enemy_index), "_p", .string(palette))) .endrepeat .endrepeat + +potion_sprite: +.byte 4, 7,$a0,1 +.byte $80 +ether_sprite: +.byte 4, 7,$a0,2 +.byte $80 +elixir_sprite: +.byte 4, 7,$a0,3 +.byte $80 + +_item_sprite: +.word potion_sprite, ether_sprite, elixir_sprite diff --git a/src/castle.c b/src/castle.c index 8cf3a18..288fc40 100644 --- a/src/castle.c +++ b/src/castle.c @@ -1,8 +1,10 @@ #include "lib/nesdoug.h" #include "lib/neslib.h" +#include "mmc3/mmc3_code.h" #include "charmap.h" #include "castle.h" #include "irq_buffer.h" +#include "main.h" #include "temp.h" #include "wram.h" #include "../assets/dialogs.h" @@ -39,8 +41,8 @@ unsigned char dialog_column; char * dialog_queue[5]; unsigned char dialog_queue_index; -#pragma code-name ("CODE") -#pragma rodata-name ("RODATA") +#pragma rodata-name ("BANK1") +#pragma code-name ("BANK1") void init_castle_cutscene() { player_y = START_Y; @@ -84,8 +86,6 @@ void init_castle_cutscene() { dialog_column = FIRST_DIALOG_COLUMN; } -void return_from_castle(void); - const char blank[] = " "; void clean_dialog_window() { @@ -186,7 +186,11 @@ void castle_handler() { } } +#pragma rodata-name ("RODATA") +#pragma code-name ("CODE") + void draw_castle_sprites() { + temp_bank = change_prg_8000(0); oam_meta_spr(0x70, 0x3e, amda_sprite); oam_meta_spr(0x80, 0x3e, intelle_sprite); oam_meta_spr(0x68, 0x88, player_sprite[(PLAYER_UP_SPR << 2) | 1]); @@ -223,4 +227,5 @@ void draw_castle_sprites() { } break; } + set_prg_8000(temp_bank); } diff --git a/src/castle.h b/src/castle.h index e926da2..ea10c21 100644 --- a/src/castle.h +++ b/src/castle.h @@ -1,8 +1,14 @@ #ifndef _CASTLE_H_ #define _CASTLE_H_ +#include "lib/farcall.h" + +#pragma wrapped-call (push, farcallax, bank) +#pragma code-name (push, "BANK1") void __fastcall__ init_castle_cutscene(void); -void __fastcall__ draw_castle_sprites(void); void __fastcall__ castle_handler(void); +#pragma code-name (pop) +#pragma wrapped-call (pop) +void __fastcall__ draw_castle_sprites(void); #endif diff --git a/src/enemies.h b/src/enemies.h index 16ecba6..af055a8 100644 --- a/src/enemies.h +++ b/src/enemies.h @@ -7,8 +7,10 @@ #define NUM_ENEMY_TYPES 5 #pragma wrapped-call (push, farcallax, bank) +#pragma code-name (push, "BANK1") unsigned char select_enemy_type (); void spawn_enemy (unsigned char entity_slot); +#pragma code-name (pop) #pragma wrapped-call (pop) #endif diff --git a/src/entities.c b/src/entities.c index 129daf0..0f1b258 100644 --- a/src/entities.c +++ b/src/entities.c @@ -24,7 +24,7 @@ unsigned char num_entities, num_enemies, num_players; unsigned char entity_aux; unsigned char temp_w, temp_h; -unsigned char menu_cursor_row, menu_cursor_col, menu_cursor_index; +unsigned char menu_cursor_row, menu_cursor_col, menu_cursor_index, menu_page; unsigned char *room_ptr; unsigned char current_entity; @@ -33,6 +33,8 @@ unsigned char entity_sprite_index; unsigned char turn_counter; +unsigned char entity_collision_index, item_index; + #pragma bss-name(pop) unsigned char entity_row[MAX_ENTITIES]; @@ -106,6 +108,24 @@ void refresh_skills_hud() { } } +void refresh_items_hud() { + temp_y = 0; + + for(temp = 0; temp < NumEntityTypes - Potion; temp++) { + temp_attr = player_items[temp]; + + temp_char = 0; + while (temp_attr >= 10) { + temp_attr -= 10; + ++temp_char; + } + one_vram_buffer(0x10 + temp_char, NTADR_C(11, 20 + temp_y)); + one_vram_buffer(0x10 + temp_attr, NTADR_C(12, 20 + temp_y)); + + temp_y++; + } +} + void refresh_moves_hud() { temp = current_entity_moves; temp_x = 0; @@ -150,7 +170,7 @@ void refresh_hp_sp_hud() { temp_int_y = entity_max_hp[current_entity]; refresh_gauge(0); - if (current_entity < 4) { + if (IS_PLAYER(current_entity)) { temp_int_x = player_sp[current_entity]; temp_int_y = player_max_sp[current_entity]; } else { @@ -199,6 +219,7 @@ void refresh_xp_hud() { void refresh_hud() { refresh_moves_hud(); refresh_hp_sp_hud(); + refresh_items_hud(); temp = entity_lv[current_entity]; one_vram_buffer(0x10 + (temp / 10), NTADR_A(12, 25)); @@ -302,8 +323,33 @@ unsigned char collides_with_map() { unsigned char entity_collides() { if (collides_with_map()) return 1; - for(i = 0; i < num_entities; i++) { - if (entity_hp[i] > 0 && entity_col[i] == temp_x && entity_row[i] == temp_y) return 1; + for(entity_collision_index = 0; + entity_collision_index < num_entities; + entity_collision_index++) { + if (entity_hp[entity_collision_index] > 0 && + entity_col[entity_collision_index] == temp_x && + entity_row[entity_collision_index] == temp_y) return 1; + } + + return 0; +} + +unsigned char entity_collides_except_item() { + item_index = 0xff; + if (collides_with_map()) return 1; + + for(entity_collision_index = 0; + entity_collision_index < num_entities; + entity_collision_index++) { + if (entity_hp[entity_collision_index] > 0 && + entity_col[entity_collision_index] == temp_x && + entity_row[entity_collision_index] == temp_y) { + if (IS_ITEM(entity_collision_index)) { + item_index = entity_collision_index; + return 0; + } + return 1; + } } return 0; @@ -317,7 +363,7 @@ unsigned char enemy_lock_on_melee_target() { skill_target_row[0] = entity_row[current_entity]; skill_target_col[0] = entity_col[current_entity]; if (set_melee_skill_target() && - ((skill_target_entity[0] < 4) == (temp_attr == 0))) { + (IS_PLAYER(skill_target_entity[0]) == (temp_attr == 0))) { return 1; } if (entity_direction[current_entity] == 3) entity_direction[current_entity] = 0; @@ -359,7 +405,7 @@ void entity_input_handler() { else if (pad1_new & PAD_RIGHT) { ++temp_x; temp = Right; } entity_direction[current_entity] = temp; - if (current_entity_moves > 0 && !entity_collides()) { + if (current_entity_moves > 0 && !entity_collides_except_item()) { entity_row[current_entity] = temp_y; entity_col[current_entity] = temp_x; --current_entity_moves; @@ -369,6 +415,7 @@ void entity_input_handler() { } } else if (pad1_new & PAD_A) { current_entity_state = EntityMenu; + menu_page = current_entity; menu_cursor_row = 0; menu_cursor_col = 0; menu_cursor_index = 0; @@ -404,6 +451,17 @@ void entity_movement_handler() { if (current_entity >= 4) return; + if (item_index != 0xff) { + if (player_items[entity_type[item_index] - Potion] < 99) { + player_items[entity_type[item_index] - Potion]++; + refresh_items_hud(); + ppu_wait_nmi(); + clear_vram_buffer(); + } + entity_hp[item_index] = 0; + item_index = 0xff; + } + if (!sector_locked && entity_row[current_entity] == sector_down_row && entity_col[current_entity] == sector_down_column) { oam_clear(); load_dungeon_sector(current_sector_index + 1); @@ -426,7 +484,7 @@ void entity_menu_handler() { double_buffer[double_buffer_index++] = MENU_SCANLINE - 1; double_buffer[double_buffer_index++] = 0xf6; double_buffer[double_buffer_index++] = 8; - temp_int = 0x28 * current_entity; + temp_int = 0x28 * menu_page; double_buffer[double_buffer_index++] = temp_int; double_buffer[double_buffer_index++] = 0; double_buffer[double_buffer_index++] = ((temp_int & 0xF8) << 2); @@ -443,45 +501,85 @@ void entity_menu_handler() { if (menu_cursor_col < 2) { ++menu_cursor_col; menu_cursor_index += 3; } } else if (pad1_new & PAD_B) { entity_aux = 0; - current_entity_state = EntityInput; - } else if (pad1_new & PAD_A) { - switch (current_entity_skill = player_skills[current_entity][menu_cursor_index]) { - case SkNone: - entity_aux = 0; + if (menu_page < 4) { current_entity_state = EntityInput; - break; - case SkAttack: - entity_aux = 0; - skill_target_row[0] = entity_row[current_entity]; - skill_target_col[0] = entity_col[current_entity]; - skill_target_direction = entity_direction[current_entity]; - if (set_melee_skill_target()) { - current_entity_state = EntityPlayAction; - } else { - next_entity(); - } - break; - case SkItem: - // TODO: item - break; - case SkPass: - next_entity(); - break; - default: - if (!have_enough_sp()) { break; } - skill_target_row[0] = entity_row[current_entity]; - skill_target_col[0] = entity_col[current_entity]; - skill_target_direction = entity_direction[current_entity]; - if (skill_is_targeted()) { - current_entity_state = EntityAskTarget; + } else { + menu_page = current_entity; + menu_cursor_row = 1; + menu_cursor_col = 0; + menu_cursor_index = 1; + } + } else if (pad1_new & PAD_A) { + if (menu_page < 4) { + switch (current_entity_skill = player_skills[current_entity][menu_cursor_index]) { + case SkNone: + entity_aux = 0; + current_entity_state = EntityInput; + break; + case SkAttack: + entity_aux = 0; + skill_target_row[0] = entity_row[current_entity]; + skill_target_col[0] = entity_col[current_entity]; + skill_target_direction = entity_direction[current_entity]; + if (set_melee_skill_target()) { + current_entity_state = EntityPlayAction; + } else { + next_entity(); + } break; - } else if (skill_can_hit()) { - consume_sp(); - refresh_hp_sp_hud(); + case SkItem: entity_aux = 0; - current_entity_state = EntityPlayAction; + menu_page = 4; + menu_cursor_row = 0; + menu_cursor_col = 0; + menu_cursor_index = 0; + break; + case SkPass: + next_entity(); + break; + default: + if (!have_enough_sp()) { break; } + skill_target_row[0] = entity_row[current_entity]; + skill_target_col[0] = entity_col[current_entity]; + skill_target_direction = entity_direction[current_entity]; + if (skill_is_targeted()) { + current_entity_state = EntityAskTarget; + break; + } else if (skill_can_hit()) { + consume_sp(); + refresh_hp_sp_hud(); + entity_aux = 0; + current_entity_state = EntityPlayAction; + } + break; + } + } + else { + if (player_items[menu_cursor_index] > 0) { + player_items[menu_cursor_index]--; + refresh_items_hud(); + ppu_wait_nmi(); + clear_vram_buffer(); + switch(Potion + menu_cursor_index) { + case Potion: + entity_hp[current_entity] += roll_dice(6, 4); + break; + case Ether: + player_sp[current_entity] += roll_dice(2, 6); + break; + case Elixir: + entity_hp[current_entity] += roll_dice(6, 8); + player_sp[current_entity] += roll_dice(4, 6); + break; + } + if (entity_hp[current_entity] > entity_max_hp[current_entity]) { + entity_hp[current_entity] = entity_max_hp[current_entity]; + } + if (player_sp[current_entity] > player_max_sp[current_entity]) { + player_sp[current_entity] = player_max_sp[current_entity]; + } + next_entity(); } - break; } } } @@ -533,7 +631,7 @@ unsigned char melee_to_hit() { if (entity_lv[current_entity] <= 2) ++to_hit_bonus; // TODO: AC - if (skill_target_entity[0] < 4) { + if (IS_PLAYER(skill_target_entity[0])) { // player AC to_hit_bonus += 7; } else { @@ -555,7 +653,7 @@ unsigned char ray_to_hit() { to_hit_bonus += 2; // dex // TODO: AC - if (skill_target_entity[0] < 4) { + if (IS_PLAYER(skill_target_entity[0])) { // player AC to_hit_bonus += 7; } else { @@ -573,7 +671,7 @@ unsigned char fire_to_hit() { signed char to_hit_bonus = 10; // TODO: AC - if (skill_target_entity[0] < 4) { + if (IS_PLAYER(skill_target_entity[0])) { // player AC to_hit_bonus += 7; } else { @@ -589,7 +687,6 @@ unsigned char fire_to_hit() { void gain_exp() { unsigned int exp, temp_exp, temp_goal; - if (current_entity >= 4 || skill_target_entity[0] < 4) return; exp = entity_lv[skill_target_entity[0]]; // ML * ML + 1 @@ -694,19 +791,33 @@ void gain_exp() { } } +void maybe_loot() { + // arg1 = entity that maybe will become item + unsigned char type; + + for(type = Potion; type < NumEntityTypes; type++) { + if (roll_die(20) <= 5) { + entity_hp[arg1] = 8; + entity_type[arg1] = type; + } + } +} + void skill_damage(unsigned char damage) { - if (entity_hp[skill_target_entity[0]] <= damage) { - entity_hp[skill_target_entity[0]] = 0; - if (skill_target_entity[0] < 4) num_players--; - else { + arg1 = skill_target_entity[0]; + if (entity_hp[arg1] <= damage) { + entity_hp[arg1] = 0; + if (IS_PLAYER(arg1)) num_players--; + else if (IS_ENEMY(arg1)) { num_enemies--; if (num_enemies == 0 && sector_locked) { unlock_sector(); } + maybe_loot(); + if (IS_PLAYER(current_entity)) gain_exp(); } - gain_exp(); } else { - entity_hp[skill_target_entity[0]] -= damage; + entity_hp[arg1] -= damage; } } @@ -921,7 +1032,7 @@ void next_entity() { } } - if (entity_hp[current_entity] == 0) continue; + if (entity_hp[current_entity] == 0 || IS_ITEM(current_entity)) continue; temp_attr = entity_status[current_entity]; @@ -958,7 +1069,7 @@ void next_entity() { entity_x = entity_col[current_entity] * 0x10 + 0x20; entity_y = entity_row[current_entity] * 0x10 + 0x20 - 1; - if (current_entity < 4) regen(); + if (IS_PLAYER(current_entity)) regen(); refresh_hud(); break; @@ -1047,6 +1158,15 @@ void draw_entities() { } oam_meta_spr(temp_x, temp_y, player_sprite[temp]); + break; + case Potion: + case Ether: + case Elixir: + temp_bank = change_prg_8000(1); + oam_meta_spr(temp_x, + temp_y, + item_sprite[entity_type[entity_sprite_index] - Potion]); + set_prg_8000(temp_bank); break; default: // enemies switch(entity_direction[entity_sprite_index]) { diff --git a/src/entities.h b/src/entities.h index e9d0baf..258408a 100644 --- a/src/entities.h +++ b/src/entities.h @@ -11,13 +11,21 @@ #define STATUS_LENGTH (2 + roll_die(4)) +#define IS_PLAYER(entity_index) ((entity_index < 4)) +#define IS_ENEMY(entity_index) ((entity_index >= 4) && entity_type[entity_index] < Player) +#define IS_ITEM(entity_index) ((entity_index >= 4) && entity_type[entity_index] > Player) + typedef enum { Eicar, Crypt, Buggy, Stega, Nefet, - Player + Player, + Potion, + Ether, + Elixir, + NumEntityTypes } entity_type_enum; typedef enum { diff --git a/src/lib/subrand.h b/src/lib/subrand.h index ea8e115..dfb1ce9 100644 --- a/src/lib/subrand.h +++ b/src/lib/subrand.h @@ -5,7 +5,9 @@ // Generates pseudorandom value <= upper_bound #pragma wrapped-call (push, farcallax, bank) +#pragma code-name (push, "BANK1") unsigned char __fastcall__ subrand8(unsigned char upper_bound); +#pragma code-name (pop) #pragma wrapped-call (pop) #endif diff --git a/src/main.c b/src/main.c index ca22013..d9a6878 100644 --- a/src/main.c +++ b/src/main.c @@ -1040,6 +1040,8 @@ void go_to_game_over () { // ::ETC:: void go_to_shutdown () { + unsigned char temp_bank; + current_game_state = GameOver; if (irq_array[0] != 0xff) { @@ -1053,6 +1055,7 @@ void go_to_shutdown () { pal_fade_to(4, 0); ppu_off(); vram_adr(NTADR_A(0,0)); + temp_bank = change_prg_8000(0); vram_unrle(shutdown_nametable); music_stop(); @@ -1071,6 +1074,8 @@ void go_to_shutdown () { pal_bg(shutdown_palette); pal_spr(shutdown_palette); + set_prg_8000(temp_bank); + pal_fade_to(0, 4); ppu_on_all(); // turn on screen @@ -1188,10 +1193,12 @@ void go_to_castle (void) { } void return_from_castle() { + unsigned char temp_bank; if (yendors == 0xff) { go_to_shutdown(); return; } + current_game_state = MainWindow; current_cursor_state = Default; oam_clear(); @@ -1203,10 +1210,12 @@ void return_from_castle() { set_chr_mode_5(BG_MAIN_3); set_chr_mode_0(SPRITE_0); set_chr_mode_1(SPRITE_1); + temp_bank = change_prg_8000(0); pal_bg(bg_palette); pal_spr(sprites_palette); vram_adr(NTADR_A(0,0)); vram_unrle(main_window_nametable); + set_prg_8000(temp_bank); set_scroll_x(0); set_scroll_y(0); current_screen = 0; diff --git a/src/main.h b/src/main.h index 4112cdd..4850695 100644 --- a/src/main.h +++ b/src/main.h @@ -1,6 +1,7 @@ #ifndef _MAIN_H_ #define _MAIN_H_ +void return_from_castle(void); void return_from_dungeon(); void go_to_game_over(); diff --git a/src/players.c b/src/players.c index 22b4ea6..18d95fa 100644 --- a/src/players.c +++ b/src/players.c @@ -128,15 +128,13 @@ void initialize_party() { for(i = 0; i < 4; i++) { player_xp[i] = 0; + for(temp = 0; temp < 9; temp++) { + player_skills[i][temp] = SkNone; + player_items[temp] = 0; + } player_skills[i][0] = SkAttack; player_skills[i][1] = SkItem; player_skills[i][2] = SkPass; - player_skills[i][3] = SkNone; - player_skills[i][4] = SkNone; - player_skills[i][5] = SkNone; - player_skills[i][6] = SkNone; - player_skills[i][7] = SkNone; - player_skills[i][8] = SkNone; entity_lv[i] = 1; entity_type[i] = Player; entity_speed[i] = NORMAL_SPEED; diff --git a/src/skills.h b/src/skills.h index 5bcccc3..273b39c 100644 --- a/src/skills.h +++ b/src/skills.h @@ -55,6 +55,8 @@ extern unsigned char skill_target_entity[MAX_SKILL_TARGETS]; extern direction skill_target_direction; extern unsigned char skill_target_count, skill_target_index; +#pragma code-name (push, "BANK2") + #pragma wrapped-call (push, farcallax, bank) unsigned char have_enough_sp (void); unsigned char skill_is_targeted (void); @@ -67,4 +69,6 @@ unsigned char skill_can_hit (void); void consume_sp (void); #pragma wrapped-call (pop) +#pragma code-name (pop) + #endif diff --git a/src/wram.c b/src/wram.c index 55a8d02..68a9a6f 100644 --- a/src/wram.c +++ b/src/wram.c @@ -8,7 +8,7 @@ #pragma bss-name(push, "XRAM") // extra RAM at $6000-$7fff -#define WRAM_VERSION 0x0019 +#define WRAM_VERSION 0x001a unsigned int wram_start; unsigned char dungeon_layout_initialized; @@ -23,6 +23,7 @@ unsigned int player_xp[4]; unsigned int player_sp[4]; unsigned int player_max_sp[4]; skill_type player_skills[4][9]; +unsigned char player_items[9]; unsigned char party_level; diff --git a/src/wram.h b/src/wram.h index 9b43ca9..5afff1c 100644 --- a/src/wram.h +++ b/src/wram.h @@ -18,6 +18,7 @@ extern unsigned int player_xp[]; extern unsigned int player_sp[]; extern unsigned int player_max_sp[4]; extern skill_type player_skills[4][9]; +extern unsigned char player_items[9]; extern unsigned char party_level; extern entity_type_enum entity_type[]; diff --git a/tools/compile-dialogs.rb b/tools/compile-dialogs.rb index 5dc17be..bad1a5a 100644 --- a/tools/compile-dialogs.rb +++ b/tools/compile-dialogs.rb @@ -27,7 +27,7 @@ File.open(output_file, 'wb') do |f| f.puts '.include "../src/charmap.inc"' - f.puts '.segment "RODATA"' + f.puts '.segment "BANK1"' dialog_table.each do |dialog_name, dialog_content| f.puts ".export _dialog_#{dialog_name}"