From 928ba973a1ed1eb4caf091bad65f2b1f123e5478 Mon Sep 17 00:00:00 2001 From: WooDose <59982861+WooDose@users.noreply.github.com> Date: Sun, 11 Sep 2022 14:22:27 -0600 Subject: [PATCH 1/6] Create option to skip drop chance randomization when randomizing enemy drops. Currently, if you select the randomize drops option, the drop chances will be randomized as well to fit the slider options in difficulty tab. While in a normal randomizer run this allows you to get drops more often, it causes races or special runs like ALL souls to feel off since the rates aren't vanilla. This fork proposes a small change to UI and code which allows for the randomization of drops without randomizing chances. I still have to test whether this has an impact on progress drops, and there's a possible change to allow players to randomize drop rates without randomizing drops. --- constants/options.rb | 3 +- randomizer.ui | 11 +++++ randomizers/drop_randomizer.rb | 83 +++++++++++++++++++--------------- 3 files changed, 60 insertions(+), 37 deletions(-) diff --git a/constants/options.rb b/constants/options.rb index e46a6230..f523234d 100644 --- a/constants/options.rb +++ b/constants/options.rb @@ -12,7 +12,8 @@ randomize_enemies: "Randomizes which non-boss enemies appear where.", randomize_bosses: "Randomizes which bosses appear where.", - randomize_enemy_drops: "Randomizes the items, souls, and glyphs dropped by non-boss enemies, as well as their drop chances.", + randomize_enemy_drops: "Randomizes the items, souls, and glyphs dropped by non-boss enemies.", + randomize_enemy_drop_chances: "Randomizes the chance of items, souls and glyps dropped by non-boss enemies.", randomize_enemy_stats: "Randomizes enemy attack, defense, HP, EXP given, and other stats.", randomize_enemy_anim_speed: "Randomizes the speed at which each enemy's animations play at, which affects their attack speed.", randomize_enemy_tolerances: "Randomizes enemy elemental weaknesses and resistances.", diff --git a/randomizer.ui b/randomizer.ui index 3b6b45de..e975e19b 100644 --- a/randomizer.ui +++ b/randomizer.ui @@ -225,6 +225,16 @@ + + + + Enemy Drop Chances + + + true + + + @@ -993,6 +1003,7 @@ randomize_enemies randomize_bosses randomize_enemy_drops + randomize_enemy_drop_chances randomize_enemy_stats randomize_enemy_anim_speed randomize_enemy_tolerances diff --git a/randomizers/drop_randomizer.rb b/randomizers/drop_randomizer.rb index cca3d7a5..12966c7d 100644 --- a/randomizers/drop_randomizer.rb +++ b/randomizers/drop_randomizer.rb @@ -64,44 +64,12 @@ def randomize_enemy_drops enemy["Item 2"] = 0 end - item_1_chance = named_rand_range_weighted(:item_drop_chance_range) - item_2_chance = named_rand_range_weighted(:item_drop_chance_range) - skill_chance = named_rand_range_weighted(:skill_drop_chance_range) + case GAME when "dos" - internal_item_chance = (item_1_chance/100.0*1024/3).floor - internal_item_chance = 1 if internal_item_chance < 1 - internal_item_chance = 255 if internal_item_chance > 255 - enemy["Item Chance"] = internal_item_chance - enemy["Soul"] = get_unplaced_non_progression_skill() - SKILL_GLOBAL_ID_RANGE.begin - - internal_soul_chance = (skill_chance/100.0*512).floor - internal_soul_chance = 1 if internal_soul_chance < 1 - internal_soul_chance = 255 if internal_soul_chance > 255 - enemy["Soul Chance"] = internal_soul_chance - when "por" - internal_item_1_chance = (item_1_chance*2.56).floor - internal_item_1_chance = 1 if internal_item_1_chance < 1 - internal_item_1_chance = 255 if internal_item_1_chance > 255 - enemy["Item 1 Chance"] = internal_item_1_chance - - internal_item_2_chance = (item_2_chance*2.56).floor - internal_item_2_chance = 1 if internal_item_2_chance < 1 - internal_item_2_chance = 255 if internal_item_2_chance > 255 - enemy["Item 2 Chance"] = internal_item_2_chance when "ooe" - internal_item_1_chance = item_1_chance - internal_item_1_chance = 1 if internal_item_1_chance < 1 - internal_item_1_chance = 255 if internal_item_1_chance > 255 - enemy["Item 1 Chance"] = internal_item_1_chance - - internal_item_2_chance = item_2_chance - internal_item_2_chance = 1 if internal_item_2_chance < 1 - internal_item_2_chance = 255 if internal_item_2_chance > 255 - enemy["Item 2 Chance"] = internal_item_2_chance - if enemy["Glyph"] != 0 # Only give glyph drops to enemies that original had a glyph drop. # Other enemies cannot drop a glyph anyway. @@ -113,17 +81,59 @@ def randomize_enemy_drops enemy["Glyph"] = get_unplaced_non_progression_skill() - SKILL_GLOBAL_ID_RANGE.begin + 1 end + + end + end + + + if options[:randomize_enemy_drop_chances] + puts "im random stuff" + item_1_chance = named_rand_range_weighted(:item_drop_chance_range) + item_2_chance = named_rand_range_weighted(:item_drop_chance_range) + skill_chance = named_rand_range_weighted(:skill_drop_chance_range) + case GAME + + when "dos" + internal_item_chance = (item_1_chance/100.0*1024/3).floor + internal_item_chance = 1 if internal_item_chance < 1 + internal_item_chance = 255 if internal_item_chance > 255 + enemy["Item Chance"] = internal_item_chance + + + internal_soul_chance = (skill_chance/100.0*512).floor + internal_soul_chance = 1 if internal_soul_chance < 1 + internal_soul_chance = 255 if internal_soul_chance > 255 + enemy["Soul Chance"] = internal_soul_chance + when "por" + internal_item_1_chance = (item_1_chance*2.56).floor + internal_item_1_chance = 1 if internal_item_1_chance < 1 + internal_item_1_chance = 255 if internal_item_1_chance > 255 + enemy["Item 1 Chance"] = internal_item_1_chance + + internal_item_2_chance = (item_2_chance*2.56).floor + internal_item_2_chance = 1 if internal_item_2_chance < 1 + internal_item_2_chance = 255 if internal_item_2_chance > 255 + enemy["Item 2 Chance"] = internal_item_2_chance + when "ooe" + internal_item_1_chance = item_1_chance + internal_item_1_chance = 1 if internal_item_1_chance < 1 + internal_item_1_chance = 255 if internal_item_1_chance > 255 + enemy["Item 1 Chance"] = internal_item_1_chance + + internal_item_2_chance = item_2_chance + internal_item_2_chance = 1 if internal_item_2_chance < 1 + internal_item_2_chance = 255 if internal_item_2_chance > 255 + enemy["Item 2 Chance"] = internal_item_2_chance if enemy["Glyph Chance"] != 100 # Don't set glyph chance if it was originally 100%, because it won't matter for those enemies. internal_skill_chance = skill_chance internal_skill_chance = 1 if internal_skill_chance < 1 internal_skill_chance = 255 if internal_skill_chance > 255 enemy["Glyph Chance"] = internal_skill_chance end + end end - - enemy.write_to_rom() - end + enemy.write_to_rom() end def remove_all_enemy_drops @@ -163,3 +173,4 @@ def remove_all_enemy_drops end end end +end \ No newline at end of file From 6cb9d5f8edc8325bee4f795643da057ba89d1f9b Mon Sep 17 00:00:00 2001 From: WooDose <59982861+WooDose@users.noreply.github.com> Date: Tue, 4 Jul 2023 18:55:12 -0600 Subject: [PATCH 2/6] Julius Skills possible removal -Removed Julius Skills from soul candles and normal enemy drops. Boss drops are still pending to check but should be gone as well. -Removed possibility of softlocks in all-souls runs that could happen if cryptid enemies were assigned the soul required to defeat them. This is mostly a tentative test release, as the Julius skills haven't been thoroughly tested in bosses. --- constants/options.rb | 4 ++- randomizer.ui | 8 ++++++ randomizers/boss_randomizer.rb | 13 +++++++++- randomizers/drop_randomizer.rb | 43 ++++++++++++++++++++++++++------ randomizers/pickup_randomizer.rb | 42 +++++++++++++++++++++++-------- 5 files changed, 90 insertions(+), 20 deletions(-) diff --git a/constants/options.rb b/constants/options.rb index f523234d..645b2caa 100644 --- a/constants/options.rb +++ b/constants/options.rb @@ -52,7 +52,7 @@ always_start_with_rare_ring: "Gives you the rewards for having AoS in the GBA slot even when AoS is not in the GBA slot.", menace_to_somacula: "Changes the final boss of Soma mode to be Somacula instead of Menace.", dos_new_style_map: "Makes the map in DoS look like the map in PoR and OoE - doors have a hole in them and the colors have better contrast.", - + remove_julius_skills: "Removes Julius skills from the pickup pool, except for Bat Form", por_short_mode: "Removes 4 random portrait areas from the game. Unlocking Brauner requires beating the bosses of the 4 portraits that remain (not counting Nest of Evil).", start_with_change_cube: "Uncheck this option if you want the Change Cube to be randomized (you won't be able to control Charlotte directly until you find it).", por_nerf_enemy_resistances: "Makes enemy resistances behave more like in DoS and OoE: An enemy must resist ALL elements of an attack to resist the attack, instead of just any one of the elements.", @@ -89,6 +89,8 @@ :always_start_with_rare_ring, :menace_to_somacula, :dos_new_style_map, + :remove_julius_skills, + ], "por" => [ :randomize_portraits, diff --git a/randomizer.ui b/randomizer.ui index e975e19b..f84b4a50 100644 --- a/randomizer.ui +++ b/randomizer.ui @@ -551,6 +551,13 @@ + + + + Remove Julius Skills + + + @@ -1041,6 +1048,7 @@ always_start_with_rare_ring menace_to_somacula dos_new_style_map + remove_julius_skills por_short_mode start_with_change_cube por_nerf_enemy_resistances diff --git a/randomizers/boss_randomizer.rb b/randomizers/boss_randomizer.rb index afe07bb2..9ae249ad 100644 --- a/randomizers/boss_randomizer.rb +++ b/randomizers/boss_randomizer.rb @@ -180,7 +180,18 @@ def change_boss_entities_according_to_mapping(boss_entities, old_boss_id_to_new_ # Some bosses such as Flying Armor won't open the boss doors until the player gets their soul drop. # So we have to make sure no bosses have no soul drop (FF). Just give them a non-progress soul so they drop something. non_progression_souls = SKILL_GLOBAL_ID_RANGE.to_a - checker.all_progression_pickups - NONRANDOMIZABLE_PICKUP_GLOBAL_IDS - queued_dna_changes[new_boss_id]["Soul"] = non_progression_souls.sample(random: rng) - SKILL_GLOBAL_ID_RANGE.begin + if options[:remove_julius_skills] + currentsoul = 000 + loop do + currentsoul = non_progression_souls.sample(random: rng) - SKILL_GLOBAL_ID_RANGE.begin + break if not (currentsoul.between?(45,52)) + end + queued_dna_changes[new_boss_id]["Soul"] = currentsoul = currentsoul + else + currentsoul = non_progression_souls.sample(random: rng) - SKILL_GLOBAL_ID_RANGE.begin + end + + queued_dna_changes[new_boss_id]["Soul"] = currentsoul end # Make the new boss have the stats of the old boss so it fits in at this point in the game. diff --git a/randomizers/drop_randomizer.rb b/randomizers/drop_randomizer.rb index 12966c7d..ac037734 100644 --- a/randomizers/drop_randomizer.rb +++ b/randomizers/drop_randomizer.rb @@ -1,5 +1,6 @@ module DropRandomizer + CRYPTID_IDS = [[28,218], [31,222], [80,223]] def randomize_enemy_drops if GAME == "ooe" [0x67, 0x72, 0x73].each do |enemy_id| @@ -11,15 +12,31 @@ def randomize_enemy_drops enemy.write_to_rom() end end - if GAME == "dos" # Doppelganger's description has 2 lines, but the enemy page only displays 1 line correctly. # Cut off the second line. description = game.text_database.text_list[TEXT_REGIONS["Soul Descriptions"].begin + 0x76] description.decoded_string = "Switch souls and equipment \\nusing the {BUTTON X}." game.text_database.write_to_rom() + # Set a non-requirement soul for cryptids to avoid softlock in all-souls + # Yeti - Waiter Skeleton, FH - Mandragora, Mothman - Rycud + CRYPTID_IDS.each do |(cryptid_id, global_soul_id)| #206,328 + enemy = game.enemy_dnas[cryptid_id] + if options[:remove_julius_skills] + currentsoul = 000 + loop do + currentsoul = get_unplaced_non_progression_skill(global_soul_id) - SKILL_GLOBAL_ID_RANGE.begin + break if not (currentsoul.between?(45,52)) + end + enemy["Soul"] = currentsoul + else + enemy["Soul"] = get_unplaced_non_progression_skill(global_soul_id) - SKILL_GLOBAL_ID_RANGE.begin + end + enemy.write_to_rom() + end + end - + COMMON_ENEMY_IDS.each do |enemy_id| enemy = game.enemy_dnas[enemy_id] @@ -63,12 +80,25 @@ def randomize_enemy_drops enemy["Item 1"] = 0 enemy["Item 2"] = 0 end - - - + case GAME when "dos" - enemy["Soul"] = get_unplaced_non_progression_skill() - SKILL_GLOBAL_ID_RANGE.begin + ### Don't set soul to cryptids because we already did + if [28,31,80].include?(enemy_id) + nil + else + if options[:remove_julius_skills] + currentsoul = 000 + loop do + currentsoul = get_unplaced_non_progression_skill() - SKILL_GLOBAL_ID_RANGE.begin + break if not (currentsoul.between?(45,52)) + end + enemy["Soul"] = currentsoul + else + enemy["Soul"] = get_unplaced_non_progression_skill() - SKILL_GLOBAL_ID_RANGE.begin + end + end + when "ooe" if enemy["Glyph"] != 0 # Only give glyph drops to enemies that original had a glyph drop. @@ -87,7 +117,6 @@ def randomize_enemy_drops if options[:randomize_enemy_drop_chances] - puts "im random stuff" item_1_chance = named_rand_range_weighted(:item_drop_chance_range) item_2_chance = named_rand_range_weighted(:item_drop_chance_range) skill_chance = named_rand_range_weighted(:skill_drop_chance_range) diff --git a/randomizers/pickup_randomizer.rb b/randomizers/pickup_randomizer.rb index 03e0e806..a7d82b1d 100644 --- a/randomizers/pickup_randomizer.rb +++ b/randomizers/pickup_randomizer.rb @@ -224,8 +224,13 @@ def place_progression_pickups_random_fill # Because DoS has two bat transformation souls, which both allow a ton of progression, at least one of them tends to be placed very early. # So we change it so that only one of the two is available to be randomly placed to reduce the chance of early bat. # (The remaining second one will be placed non-randomly later.) + # Always keep bat company if julius skills disabled if GAME == "dos" && pickups_available.include?(0x104) && pickups_available.include?(0xFC) - bat_to_keep = [0x104, 0xFC].sample(random: rng) + if options[:remove_julius_skills] + bat_to_keep = 0x104 + else + bat_to_keep = [0x104, 0xFC].sample(random: rng) + end bat_to_remove = (bat_to_keep == 0x104 ? 0xFC : 0x104) pickups_available.delete(bat_to_remove) elsif GAME == "dos" && pickups_available.include?(0xFC) @@ -1230,12 +1235,19 @@ def filter_locations_valid_for_pickup(locations, pickup_global_id) locations end - def get_unplaced_non_progression_pickup(valid_ids: PICKUP_GLOBAL_ID_RANGE.to_a) + def get_unplaced_non_progression_pickup(valid_ids: PICKUP_GLOBAL_ID_RANGE.to_a, id_to_exclude: -1) valid_possible_items = @unplaced_non_progression_pickups.select do |pickup_global_id| valid_ids.include?(pickup_global_id) end - - pickup_global_id = valid_possible_items.sample(random: rng) + if id_to_exclude > 0 + pickup_global_id = 000 + loop do + pickup_global_id = valid_possible_items.sample(random: rng) + break if pickup_global_id != id_to_exclude + end + else + pickup_global_id = valid_possible_items.sample(random: rng) + end if pickup_global_id.nil? # Ran out of unplaced pickups, so place a duplicate instead. @@ -1267,8 +1279,8 @@ def get_unplaced_non_progression_item_that_can_be_an_arm_shifted_immediate return get_unplaced_non_progression_pickup(valid_ids: valid_ids) end - def get_unplaced_non_progression_skill - return get_unplaced_non_progression_pickup(valid_ids: SKILL_GLOBAL_ID_RANGE.to_a) + def get_unplaced_non_progression_skill(id_to_exclude_= -1) + return get_unplaced_non_progression_pickup(valid_ids: SKILL_GLOBAL_ID_RANGE.to_a,id_to_exclude:id_to_exclude_) end def get_unplaced_non_progression_item_except_ooe_relics @@ -1560,18 +1572,26 @@ def change_entity_location_to_pickup_global_id(location, pickup_global_id) end item_type, item_index = game.get_item_type_and_index_by_global_id(pickup_global_id) - + + + if PICKUP_SUBTYPES_FOR_SKILLS.include?(item_type) case GAME when "dos" # Soul candle - entity.type = 2 - entity.subtype = 1 - entity.var_a = 0 - entity.var_b = item_index + if item_index.between?(45,52) && options[:remove_julius_skills] + nil + else + entity.type = 2 + entity.subtype = 1 + entity.var_a = 0 + entity.var_b = item_index + spoiler_log.puts item_index, item_type # We didn't use the pickup flag, so put it back @unused_pickup_flags << pickup_flag + end + when "por" # Skill if entity.is_hidden_pickup? From 9c99482845163ca4e7443650f4a7ddeb8ebb7ade Mon Sep 17 00:00:00 2001 From: WooDose <59982861+WooDose@users.noreply.github.com> Date: Tue, 4 Jul 2023 18:59:12 -0600 Subject: [PATCH 3/6] Update version.rb --- version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.rb b/version.rb index 6123f4e7..9d77b787 100644 --- a/version.rb +++ b/version.rb @@ -1,2 +1,2 @@ -DSVRANDOM_VERSION = "1.4.5" +DSVRANDOM_VERSION = "1.4.6-wf" From ad1238890182001fe96fbad051909a3d3ea6dbcd Mon Sep 17 00:00:00 2001 From: WooDose <59982861+WooDose@users.noreply.github.com> Date: Tue, 4 Jul 2023 19:13:21 -0600 Subject: [PATCH 4/6] Remove some spoiler log lines used for debugging --- randomizers/pickup_randomizer.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/randomizers/pickup_randomizer.rb b/randomizers/pickup_randomizer.rb index a7d82b1d..9ebd5988 100644 --- a/randomizers/pickup_randomizer.rb +++ b/randomizers/pickup_randomizer.rb @@ -1586,7 +1586,6 @@ def change_entity_location_to_pickup_global_id(location, pickup_global_id) entity.subtype = 1 entity.var_a = 0 entity.var_b = item_index - spoiler_log.puts item_index, item_type # We didn't use the pickup flag, so put it back @unused_pickup_flags << pickup_flag From 332070757587c45b260a02a2d83ffddc2bce60b1 Mon Sep 17 00:00:00 2001 From: WooDose <59982861+WooDose@users.noreply.github.com> Date: Wed, 5 Jul 2023 11:10:32 -0600 Subject: [PATCH 5/6] Julius Skills hotfix --- .gitignore | 6 + constants/dos_constants.rb | 1327 ++++++++++++++++++++++++++++++ randomizers/pickup_randomizer.rb | 6 +- 3 files changed, 1338 insertions(+), 1 deletion(-) create mode 100644 constants/dos_constants.rb diff --git a/.gitignore b/.gitignore index 169e22a0..28b9fccc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,9 @@ ui_*.rb /logs crashlog.txt /roomedits/Tilesets +armips/version.txt +armips/Readme.txt +armips/Readme.md +armips/LICENSE.txt +armips/armips64.exe +armips/armips.exe diff --git a/constants/dos_constants.rb b/constants/dos_constants.rb new file mode 100644 index 00000000..322b6e6f --- /dev/null +++ b/constants/dos_constants.rb @@ -0,0 +1,1327 @@ + +GAME = "dos" +REGION = :usa +LONG_GAME_NAME = "Dawn of Sorrow" + +AREA_LIST_RAM_START_OFFSET = 0x02006FC4 # Technically not a list, this points to code that has the the area hard coded, since DoS only has one area. + +# Overlays 6 to 22. +AREA_INDEX_TO_OVERLAY_INDEX = { + 0 => { + 0 => 14, # lost village + 1 => 11, # demon guest house + 2 => 6, # wizardry lab + 3 => 16, # garden of madness + 4 => 8, # dark chapel + 5 => 7, # condemned tower and mine of judgement + 6 => 17, # subterranean hell + 7 => 15, # silenced ruins + 8 => 9, # clock tower + 9 => 10, # the pinnacle + 10 => 12, # menace's room + 11 => 13, # the abyss + 12 => 18, # prologue area (in town) + 13 => 19, # epilogue area (overlooking castle) + 14 => 20, # boss rush + 15 => 21, # enemy set mode + 16 => 22, # final room of julius mode where you fight soma + } +} + +AREA_INDEX_TO_AREA_NAME = { + 0 => "Dracula's Castle" +} + +SECTOR_INDEX_TO_SECTOR_NAME = { + 0 => { + 0 => "The Lost Village", + 1 => "Demon Guest House", + 2 => "Wizardry Lab", + 3 => "Garden of Madness", + 4 => "The Dark Chapel", + 5 => "Condemned Tower & Mine of Judgment", + 6 => "Subterranean Hell", + 7 => "Silenced Ruins", + 8 => "Cursed Clock Tower", + 9 => "The Pinnacle", + 10 => "Menace", + 11 => "The Abyss", + 12 => "Prologue", + 13 => "Epilogue", + 14 => "Boss Rush", + 15 => "Enemy Set Mode", + 16 => "Throne Room", + } +} + +ENTITY_TYPE_DESCRIPTIONS = { + 0 => "Nothing", + 1 => "Enemy", + 2 => "Special object", + 3 => "Candle", + 4 => "Pickup", + 5 => "???", + 6 => "Entity hider", + 7 => "Font loader", +} + +CONSTANT_OVERLAYS = [0, 1, 2, 3, 4, 5] + +ROOM_OVERLAYS = (6..22) +MAX_ALLOWABLE_ROOM_OVERLAY_SIZE = 152864 + +AREAS_OVERLAY = nil +MAPS_OVERLAY = nil + +MAP_TILE_METADATA_LIST_START_OFFSET = nil +MAP_TILE_METADATA_START_OFFSET = 0x0207708C +MAP_TILE_LINE_DATA_LIST_START_OFFSET = nil +MAP_TILE_LINE_DATA_START_OFFSET = 0x02076AAC +MAP_LENGTH_DATA_START_OFFSET = nil +MAP_NUMBER_OF_TILES = 3008 +MAP_SECRET_DOOR_LIST_START_OFFSET = nil +MAP_SECRET_DOOR_DATA_START_OFFSET = 0x02076408 +MAP_SECRET_ROOM_DATA_START_OFFSET = 0x02079D44 +MAP_DRAW_X_OFFSET_LOCATION = 0x02024D5C +MAP_DRAW_Y_OFFSET_LOCATION = 0x02024D2C +ABYSS_MAP_TILE_METADATA_START_OFFSET = 0x020788F4 +ABYSS_MAP_TILE_LINE_DATA_START_OFFSET = 0x02078810 +ABYSS_MAP_NUMBER_OF_TILES = 448 +ABYSS_MAP_SECRET_DOOR_DATA_START_OFFSET = 0x0207880C +ABYSS_MAP_DRAW_X_OFFSET_LOCATION = 0x02024D64 +ABYSS_MAP_DRAW_Y_OFFSET_LOCATION = 0x02024D34 + +WARP_ROOM_LIST_START = 0x0222C478 +WARP_ROOM_ICON_POS_LIST_START = 0x0209A188 +WARP_ROOM_COUNT = 0xC + +MAP_FILL_COLOR = [160, 120, 88, 255] +MAP_SAVE_FILL_COLOR = [248, 0, 0, 255] +MAP_WARP_FILL_COLOR = [0, 0, 248, 255] +MAP_SECRET_FILL_COLOR = [0, 128, 0, 255] +MAP_ENTRANCE_FILL_COLOR = [248, 128, 0, 255] # Area entrances don't exist in vanilla DoS, but can be added via an asm patch. +MAP_LINE_COLOR = [248, 248, 248, 255] +MAP_DOOR_COLOR = [16, 216, 32, 255] +MAP_DOOR_CENTER_PIXEL_COLOR = MAP_DOOR_COLOR +MAP_SECRET_DOOR_COLOR = [248, 248, 0, 255] + +AREA_MUSIC_LIST_START_OFFSET = nil +SECTOR_MUSIC_LIST_START_OFFSET = 0x0209A634 +AVAILABLE_BGM_POOL_START_OFFSET = nil +SONG_INDEX_TO_TEXT_INDEX = [ + 0x3B1, + 0x3B2, + 0x3A7, + 0x3A8, + 0x3AF, + 0x3A9, + 0x3AC, + 0x396, + 0x3A5, + 0x3A6, + 0x3AD, + 0x397, + 0x398, + 0x399, + 0x39A, + 0x39B, + 0x39C, + 0x39D, + 0x39E, + 0x39F, + 0x3A0, + 0x3A1, + 0x3A2, + 0x3A3, + 0x3A4, + 0x3B0, + 0x3AA, + 0x3AB, + "Ambience", + 0x3AE, +] + +ASSET_LIST_START = 0x0208CC6C +ASSET_LIST_END = 0x0209A0C3 +ASSET_LIST_ENTRY_LENGTH = 0x28 + +COLOR_OFFSETS_PER_256_PALETTE_INDEX = 16 + +ENEMY_DNA_RAM_START_OFFSET = 0x02078CAC +ENEMY_DNA_FORMAT = [ + # length: 36 + [4, "Create Code"], + [4, "Update Code"], + [2, "Item 1"], + [2, "Item 2"], + [1, "Petrified Palette"], + [1, "Unknown 2"], + [2, "HP"], + [2, "MP"], + [2, "EXP"], + [1, "Soul Chance"], + [1, "Attack"], + [1, "Defense"], + [1, "Item Chance"], + [2, "Unknown 3"], + [1, "Soul"], + [1, "Enemy Set Cost"], + [4, "Weaknesses", :bitfield], + [4, "Resistances", :bitfield], +] +ENEMY_DNA_BITFIELD_ATTRIBUTES = { + "Weaknesses" => [ + "Strike", + "Stab", + "Slash", + "Fire", + "Ice", + "Lightning", + "Dark", + "Holy", + "Poison", + "Curse", + "Stone", + "Unknown 12", + "Unknown 13", + "Unknown 14", + "Unknown 15", + "Made of flesh", + "Unknown 17", + "Unknown 18", + "Unknown 19", + "Unknown 20", + "Unknown 21", + "Unknown 22", + "Unknown 23", + "Unknown 24", + "Weapons", + "Red souls", + "Blue souls", + "Unknown 28", + "Unknown 29", + "Unknown 30", + "Unknown 31", + "Unknown 32", + ], + "Resistances" => [ + "Strike", + "Stab", + "Slash", + "Fire", + "Ice", + "Lightning", + "Dark", + "Holy", + "Poison", + "Curse", + "Stone", + "Unknown 12", + "Time Stop", + "Unknown 14", + "Backstab", + "Unknown 16", + "Unknown 17", + "Unknown 18", + "Unknown 19", + "Unknown 20", + "Unknown 21", + "Unknown 22", + "Unknown 23", + "Unknown 24", + "Weapons", + "Red souls", + "Blue souls", + "Unknown 28", + "Unknown 29", + "Unknown 30", + "Unknown 31", + "Unknown 32", + ], +} + +# Overlays 23 to 40 used for enemies. +OVERLAY_FILE_FOR_ENEMY_AI = { + # Enemies not listed here use one of the constant overlays like 0. + 0x0E => 28, # golem + 0x12 => 27, # manticore + 0x19 => 32, # catoblepas + 0x22 => 28, # treant + 0x25 => 24, # great armor + 0x2F => 31, # devil + 0x4D => 32, # gorgon + 0x51 => 27, # musshusu + 0x5B => 31, # flame demon + 0x5D => 31, # arc demon + 0x61 => 24, # final guard + 0x64 => 28, # iron golem + 0x65 => 30, # flying armor + 0x66 => 23, # balore + 0x67 => 29, # malphas + 0x68 => 40, # dmitrii + 0x69 => 25, # dario + 0x6A => 25, # puppet master + 0x6B => 26, # rahab + 0x6C => 36, # gergoth + 0x6D => 33, # zephyr + 0x6E => 37, # bat company + 0x6F => 35, # paranoia + 0x71 => 34, # death + 0x72 => 39, # abaddon + 0x73 => 38, # menace +} + +REUSED_ENEMY_INFO = { + # Enemies that had parts of them reused from another enemy. + # init_code: The init code of the original enemy. This is where to look for gfx/palette/sprite data, not the reused enemy's init code. + # gfx_sheet_ptr_index: The reused enemy uses a different gfx sheet than the original enemy. This value is which one to use. + # palette_offset: The reused enemy uses different palettes than the original, but they're still in the same list of palettes. This is the offset of the new palette in the list. + # palette_list_ptr_index: The reused enemy uses a completely different palette list from the original. This value is which one to use. + 0x1A => {init_code: 0x0223266C, gfx_sheet_ptr_index: 0, palette_offset: 2, palette_list_ptr_index: 0}, # ghoul and zombie + 0x4D => {init_code: nil, gfx_sheet_ptr_index: 0, palette_offset: 0, palette_list_ptr_index: 1}, # gorgon and catoblepas + 0x54 => {init_code: 0x02270704, gfx_sheet_ptr_index: 0, palette_offset: 0, palette_list_ptr_index: 1, sprite_ptr_index: 1}, # erinys and valkyrie + 0x5C => {init_code: 0x02288FBC, gfx_sheet_ptr_index: 0, palette_offset: 0, palette_list_ptr_index: 0}, # tanjelly and slime + 0x5B => {init_code: 0x022FF9F0, gfx_sheet_ptr_index: 1, palette_offset: 0, palette_list_ptr_index: 1, sprite_ptr_index: 1}, # flame demon and devil + 0x5D => {init_code: nil, gfx_sheet_ptr_index: 2, palette_offset: 0, palette_list_ptr_index: 2, sprite_ptr_index: 2}, # arc demon and devil +} + +BEST_SPRITE_FRAME_FOR_ENEMY = { + # Enemies not listed here default to frame 0. + 0x00 => 0x08, # zombie + 0x05 => 0x26, # peeping eye + 0x09 => 0x11, # spin devil + 0x0E => 0x13, # golem + 0x10 => 0x07, # une + 0x12 => 0x4E, # manticore + 0x15 => 0x0E, # mandragora + 0x17 => 0x0D, # skeleton farmer + 0x19 => 0x24, # catoblepas + 0x1A => 0x08, # ghoul + 0x1B => 0x11, # corpseweed + 0x22 => 0x0F, # treant + 0x23 => 0x11, # amalaric sniper + 0x25 => 0x14, # great armor + 0x26 => 0x1B, # killer doll + 0x29 => 0x05, # witch + 0x2B => 0x08, # lilith + 0x2C => 0x03, # killer clown + 0x2E => 0x06, # fleaman + 0x30 => 0x25, # guillotiner + 0x31 => 0x11, # draghignazzo + 0x36 => 0x1B, # wakwak tree + 0x3C => 0x0A, # larva + 0x3D => 0x04, # heart eater + 0x40 => 0x02, # medusa head + 0x43 => 0x01, # mimic + 0x4A => 0x27, # bugbear + 0x4C => 0x2A, # bone ark + 0x4D => 0x24, # gorgon + 0x4E => 0x0A, # alura une + 0x4F => 0x06, # great axe armor + 0x51 => 0x4F, # mushussu + 0x53 => 0x05, # dead warrior + 0x55 => 0x09, # succubus + 0x56 => 0x10, # ripper + 0x5C => 0x0B, # tanjelly + 0x61 => 0x14, # final guard + 0x62 => 0x1F, # malacoda + 0x63 => 0x01, # alastor + 0x64 => 0x0F, # iron golem + 0x65 => 0x06, # flying armor + 0x66 => 0x05, # balore + 0x6B => 0x71, # rahab + 0x6C => 0x10, # gergoth + 0x6E => 0x05, # bat company + 0x6F => 0x17, # paranoia + 0x71 => 0x16, # death + 0x73 => 0x1F, # menace + 0x74 => 0x16, # soma +} +BEST_SPRITE_OFFSET_FOR_ENEMY = {} + +ENEMY_FILES_TO_LOAD_LIST = 0x0208CA90 + +COMMON_SPRITE = {desc: "Common", sprite: 0x0229EAD8, gfx_wrapper: 0x02079EAC, palette: 0x022B9B04} + +SPECIAL_OBJECT_IDS = (0..0x75) +SPECIAL_OBJECT_CREATE_CODE_LIST = 0x0222C714 +SPECIAL_OBJECT_UPDATE_CODE_LIST = 0x0222C8F0 +OVERLAY_FILE_FOR_SPECIAL_OBJECT = { + 0x2E => 25 +} +REUSED_SPECIAL_OBJECT_INFO = { + 0x00 => COMMON_SPRITE, # ice block + 0x01 => {init_code: 0x0222BAB8, ignore_files_to_load: true}, # destructible + 0x02 => {init_code: -1}, + 0x03 => {init_code: -1}, + 0x04 => {init_code: -1}, + 0x05 => {init_code: -1}, + 0x06 => {sprite: 0x0229A9FC, gfx_files: [0x022CA4B8, 0x022CA4C4, 0x022CA4D0, 0x022CA4DC, 0x022CA4E8, 0x022CA4F4], palette: 0x022C141C}, # area titles + 0x09 => {init_code: 0x0222BD04}, # chair + 0x0B => {init_code: -1}, + 0x0E => {init_code: -1}, + 0x11 => {init_code: -1}, + 0x13 => {init_code: -1}, + 0x1D => COMMON_SPRITE, # wooden door + 0x22 => {init_code: -1}, + 0x25 => COMMON_SPRITE, # boss door + 0x26 => {init_code: 0x021A8FC8}, # slot machine + 0x27 => {init_code: 0x021A8434}, # condemned tower gate + 0x29 => {init_code: 0x021A7FC4}, # dark chapel gate + 0x2A => {init_code: 0x021A7B44}, # flood gate + 0x2E => {init_code: 0x02304B98}, # iron maiden + 0x2F => {init_code: -1}, + 0x33 => {init_code: -1}, + 0x34 => {init_code: -1}, + 0x35 => {init_code: -1}, + 0x36 => {init_code: -1}, + 0x38 => {init_code: -1}, + 0x41 => {init_code: -1}, + 0x4C => {init_code: -1}, + 0x47 => {init_code: 0x0222CC10}, # hammer shopkeeper + 0x48 => {init_code: 0x0222CC00}, # yoko shopkeeper + 0x4A => {init_code: -1}, + 0x4F => {init_code: 0x0222CBE0}, # mina event actor + 0x50 => {init_code: 0x0222CC10}, # hammer event actor + 0x51 => {init_code: 0x0222CBF0}, # arikado event actor + 0x52 => {init_code: 0x0222CC20}, # julius event actor + 0x53 => {init_code: 0x0222CC30}, # celia event actor + 0x54 => {init_code: 0x0222CC40}, # dario event actor + 0x55 => {init_code: 0x0222CC50}, # dmitrii event actor + 0x5B => {init_code: 0x0222CC60}, # alucard event actor + 0x5D => {init_code: -1}, + 0x5E => {init_code: -1}, + 0x5F => {init_code: -1}, + 0x60 => {init_code: -1}, + 0x61 => {init_code: -1}, + 0x62 => {init_code: -1}, + 0x63 => {init_code: -1}, + 0x64 => {init_code: -1}, + 0x65 => {init_code: -1}, + 0x66 => {init_code: -1}, + 0x67 => {init_code: -1}, + 0x68 => {init_code: -1}, + 0x69 => {init_code: -1}, + 0x6A => {init_code: -1}, + 0x6B => {init_code: -1}, + 0x6C => {init_code: -1}, + 0x6D => {init_code: -1}, + 0x6E => {init_code: -1}, + 0x6F => {init_code: -1}, + 0x70 => {init_code: -1}, + 0x71 => {init_code: -1}, + 0x72 => {init_code: -1}, + 0x73 => {init_code: -1}, + 0x74 => {init_code: -1}, + 0x75 => {init_code: -1}, +} +BEST_SPRITE_FRAME_FOR_SPECIAL_OBJECT = { + 0x01 => 0x0F, + 0x0C => 0x01, + 0x0D => 0x17, + 0x0F => 0x04, + 0x16 => -1, + 0x18 => 0x11, + 0x1A => 0x03, + 0x1D => 0xED, + 0x25 => 0xDF, + 0x26 => 0x06, + 0x28 => 0x01, + 0x30 => 0x06, + 0x3A => 0x08, + 0x3B => 0x06, + 0x3D => 0x01, + 0x3E => 0x01, + 0x59 => 0x0A, + 0x5B => 0x10, + 0x5C => -1, +} +BEST_SPRITE_OFFSET_FOR_SPECIAL_OBJECT = { + 0x00 => {x: 8, y: 8}, + 0x17 => {x: 8, y: 8}, + 0x1D => {x: 8}, + 0x25 => {x: 8}, + 0x2A => {x: 8}, +} +SPECIAL_OBJECT_FILES_TO_LOAD_LIST = 0x0209B88C + +WEAPON_GFX_LIST_START = 0x0222EE24 +WEAPON_GFX_COUNT = 0x4A +WEAPON_SPRITES_LIST_START = nil +WEAPON_PALETTE_LIST = nil +SKILL_GFX_LIST_START = 0x0222E9D4 +SKILL_GFX_COUNT = 0x34 + +OTHER_SPRITES = [ + COMMON_SPRITE, + + {pointer: 0x0222E474, desc: "Soma player"}, + {pointer: 0x0222E4CC, desc: "Julius player"}, + {pointer: 0x0222E524, desc: "Yoko player"}, + {pointer: 0x0222E57C, desc: "Alucard player"}, + + {pointer: 0x0222BAAC, desc: "Destructibles 0"}, + {pointer: 0x0222BAB8, desc: "Destructibles 1"}, + {pointer: 0x0222BAC4, desc: "Destructibles 2"}, + {pointer: 0x0222BAD0, desc: "Destructibles 3"}, + {pointer: 0x0222BADC, desc: "Destructibles 4"}, + {pointer: 0x0222BAE8, desc: "Destructibles 5"}, + {pointer: 0x0222BAF4, desc: "Destructibles 6"}, + {pointer: 0x0222BB00, desc: "Destructibles 7"}, + {pointer: 0x0222BB0C, desc: "Destructibles 8"}, + {pointer: 0x0222BD04, desc: "Chair 1"}, + {pointer: 0x0222BD10, desc: "Chair 2"}, + {pointer: 0x0222BD1C, desc: "Chair 3"}, + {pointer: 0x0222BD28, desc: "Chair 4"}, + + {pointer: 0x02215AC4, desc: "Magic seal"}, + + {pointer: 0x0203D4A0, desc: "Nintendo splash screen"}, + {pointer: 0x0203D564, desc: "Konami splash screen"}, + {pointer: 0x0203D8A8, desc: "Main menu"}, + {pointer: 0x0203DAB0, desc: "Castlevania logo"}, + {pointer: 0x0203A4F8, desc: "Info screen", one_dimensional_mode: true}, + {pointer: 0x0203ED3C, desc: "Credits"}, + {pointer: 0x0203ED58, desc: "Characters used during credits"}, + {pointer: 0x0203ED70, desc: "BGs used during credits"}, + {pointer: 0x0203F410, desc: "Game over screen"}, + {pointer: 0x02046714, desc: "Name signing screen"}, + {pointer: 0x02046ACC, desc: "File select menu"}, + {pointer: 0x020489A4, desc: "Choose course - unused?"}, + {pointer: 0x02049078, desc: "Enemy set mode menu"}, + {pointer: 0x0204908C, desc: "Enemy set retry/complete"}, + {pointer: 0x020490A0, desc: "Wi-fi menu"}, + {desc: "Zephyr's clock hands", sprite: 0x0229B3F4, gfx_file_names: ["/sc/f_ctobj0.dat"], palette: 0x022C22C4}, + + + {desc: "Equip menu foreground", no_sprite: true, palette: 0x022C46E8, one_dimensional_mode: true, gfx_file_names: [ + "/sc/f_mainob.dat", + ]}, + {desc: "Weapon synthesis menu foreground", no_sprite: true, palette: 0x022C490C, one_dimensional_mode: true, gfx_file_names: [ + "/sc/f_mix_e.dat", + ]}, + {desc: "Yoko main menu and soul release menu foregrounds", no_sprite: true, palette: 0x022C490C, one_dimensional_mode: true, gfx_file_names: [ + "/sc/f_scrap.dat", + ]}, + + {desc: "Bestiary enemies 00-03", no_sprite: true, palette: 0x022C358C, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire00.dat"]}, + {desc: "Bestiary enemies 04-07", no_sprite: true, palette: 0x022C3610, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire01.dat"]}, + {desc: "Bestiary enemies 08-0B", no_sprite: true, palette: 0x022C3694, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire02.dat"]}, + {desc: "Bestiary enemies 0C-0F", no_sprite: true, palette: 0x022C3718, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire03.dat"]}, + {desc: "Bestiary enemies 10-13", no_sprite: true, palette: 0x022C379C, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire04.dat"]}, + {desc: "Bestiary enemies 14-17", no_sprite: true, palette: 0x022C3820, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire05.dat"]}, + {desc: "Bestiary enemies 18-1B", no_sprite: true, palette: 0x022C38A4, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire06.dat"]}, + {desc: "Bestiary enemies 1C-1F", no_sprite: true, palette: 0x022C3928, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire07.dat"]}, + {desc: "Bestiary enemies 20-23", no_sprite: true, palette: 0x022C39AC, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire08.dat"]}, + {desc: "Bestiary enemies 24-27", no_sprite: true, palette: 0x022C3BB0, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire09.dat"]}, + {desc: "Bestiary enemies 28-2B", no_sprite: true, palette: 0x022C3C34, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire10.dat"]}, + {desc: "Bestiary enemies 2C-2F", no_sprite: true, palette: 0x022C3CB8, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire11.dat"]}, + {desc: "Bestiary enemies 30-33", no_sprite: true, palette: 0x022C3D3C, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire12.dat"]}, + {desc: "Bestiary enemies 34-37", no_sprite: true, palette: 0x022C3DC0, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire13.dat"]}, + {desc: "Bestiary enemies 38-3B", no_sprite: true, palette: 0x022C3E44, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire14.dat"]}, + {desc: "Bestiary enemies 3C-3F", no_sprite: true, palette: 0x022C3EC8, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire15.dat"]}, + {desc: "Bestiary enemies 40-43", no_sprite: true, palette: 0x022C3F4C, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire16.dat"]}, + {desc: "Bestiary enemies 44-47", no_sprite: true, palette: 0x022C3FD0, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire17.dat"]}, + {desc: "Bestiary enemies 48-4B", no_sprite: true, palette: 0x022C4054, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire18.dat"]}, + {desc: "Bestiary enemies 4C-4F", no_sprite: true, palette: 0x022C40D8, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire19.dat"]}, + {desc: "Bestiary enemies 50-53", no_sprite: true, palette: 0x022C415C, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire20.dat"]}, + {desc: "Bestiary enemies 54-57", no_sprite: true, palette: 0x022C41E0, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire21.dat"]}, + {desc: "Bestiary enemies 58-5B", no_sprite: true, palette: 0x022C4264, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire22.dat"]}, + {desc: "Bestiary enemies 5C-5F", no_sprite: true, palette: 0x022C42E8, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire23.dat"]}, + {desc: "Bestiary enemies 60-63", no_sprite: true, palette: 0x022C436C, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire24.dat"]}, + {desc: "Bestiary enemies 64-67", no_sprite: true, palette: 0x022C43F0, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire25.dat"]}, + {desc: "Bestiary enemies 68-6B", no_sprite: true, palette: 0x022C4474, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire26.dat"]}, + {desc: "Bestiary enemies 6C-6F", no_sprite: true, palette: 0x022C44F8, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire27.dat"]}, + {desc: "Bestiary enemies 70-73", no_sprite: true, palette: 0x022C457C, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire28.dat"]}, + {desc: "Bestiary enemies 74-77", no_sprite: true, palette: 0x022C4600, one_dimensional_mode: true, gfx_file_names: ["/sc/f_rire29.dat"]}, + + {desc: "Face portraits 00-03 (Soma)", no_sprite: true, palette: 0x022C64D8, gfx_file_names: [ + "/sc/f_fsoma0.dat", + "/sc/f_fsoma1.dat", + "/sc/f_fsoma2.dat", + "/sc/f_fsoma3.dat", + ]}, + {desc: "Face portraits 04-05 (Soma)", no_sprite: true, palette: 0x022C64FC, gfx_file_names: [ + "/sc/f_fsoma4.dat", + "/sc/f_fsoma5.dat", + ]}, + {desc: "Face portrait 06 (Soma)", no_sprite: true, palette: 0x022C6520, gfx_file_names: [ + "/sc/f_fsoma6.dat", + ]}, + {desc: "Face portraits 07-09 (Mina)", no_sprite: true, palette: 0x022C6544, gfx_file_names: [ + "/sc/f_fmina0.dat", + "/sc/f_fmina1.dat", + "/sc/f_fmina2.dat", + ]}, + {desc: "Face portrait 0A (Mina)", no_sprite: true, palette: 0x022C65B0, gfx_file_names: [ + "/sc/f_fmina3.dat", + ]}, + {desc: "Face portrait 0B (Mina)", no_sprite: true, palette: 0x022C6568, gfx_file_names: [ + "/sc/f_fmina4.dat", + ]}, + {desc: "Face portrait 0C (Mina)", no_sprite: true, palette: 0x022C658C, gfx_file_names: [ + "/sc/f_fmina5.dat", + ]}, + {desc: "Face portrait 0D (Arikado)", no_sprite: true, palette: 0x022C65D4, gfx_file_names: [ + "/sc/f_farik0.dat", + ]}, + {desc: "Face portrait 0E (Arikado)", no_sprite: true, palette: 0x022C661C, gfx_file_names: [ + "/sc/f_farik1.dat", + ]}, + {desc: "Face portrait 0F (Alucard)", no_sprite: true, palette: 0x022C65F8, gfx_file_names: [ + "/sc/f_farik2.dat", + ]}, + {desc: "Face portraits 10-14 (Yoko)", no_sprite: true, palette: 0x022C6688, gfx_file_names: [ + "/sc/f_fyoko0.dat", + "/sc/f_fyoko1.dat", + "/sc/f_fyoko2.dat", + "/sc/f_fyoko3.dat", + "/sc/f_fyoko4.dat", + ]}, + {desc: "Face portraits 15-16 (Julius)", no_sprite: true, palette: 0x022C6640, gfx_file_names: [ + "/sc/f_fjuli0.dat", + "/sc/f_fjuli1.dat", + ]}, + {desc: "Face portrait 17 (Julius)", no_sprite: true, palette: 0x022C6664, gfx_file_names: [ + "/sc/f_fjuli2.dat", + ]}, + {desc: "Face portraits 18-1A (Hammer)", no_sprite: true, palette: 0x022C66AC, gfx_file_names: [ + "/sc/f_fhume0.dat", + "/sc/f_fhume1.dat", + "/sc/f_fhume2.dat", + ]}, + {desc: "Face portraits 1B-1E (Celia)", no_sprite: true, palette: 0x022C66D0, gfx_file_names: [ + "/sc/f_fseri0.dat", + "/sc/f_fseri1.dat", + "/sc/f_fseri2.dat", + "/sc/f_fseri3.dat", + ]}, + {desc: "Face portraits 1F-22 (Dario)", no_sprite: true, palette: 0x022C66F4, gfx_file_names: [ + "/sc/f_fdari0.dat", + "/sc/f_fdari1.dat", + "/sc/f_fdari2.dat", + "/sc/f_fdari3.dat", + ]}, + {desc: "Face portraits 23-25 (Dmitrii)", no_sprite: true, palette: 0x022C6718, gfx_file_names: [ + "/sc/f_fdomi0.dat", + "/sc/f_fdomi1.dat", + "/sc/f_fdomi2.dat", + ]}, + {desc: "Face portrait 26 (Dmitrii)", no_sprite: true, palette: 0x022C673C, gfx_file_names: [ + "/sc/f_fdomi3.dat", + ]}, + {desc: "Face portrait 27-28 (Soma)", no_sprite: true, palette: 0x022C64D8, gfx_file_names: [ + "/sc/f_fsoma7.dat", + "/sc/f_fsoma8.dat", + ]}, +] + +CANDLE_FRAME_IN_COMMON_SPRITE = 0xDB +MONEY_FRAME_IN_COMMON_SPRITE = 0xEF +CANDLE_SPRITE = COMMON_SPRITE +MONEY_SPRITE = COMMON_SPRITE + +OVERLAY_FILES_WITH_SPRITE_DATA = [2, 3] + +TEXT_LIST_START_OFFSET = 0x0222F300 +TEXT_RANGE = (0..0x50A) +TEXT_REGIONS = { + "Character Names" => (0..0xB), + "Item Names" => (0xC..0xD9), + "Item Descriptions" => (0xDA..0x1A7), + "Enemy Names" => (0x1A8..0x21D), + "Enemy Descriptions" => (0x21E..0x293), + "Soul Names" => (0x294..0x30E), + "Soul Descriptions" => (0x30F..0x389), + "Area Names" => (0x38A..0x395), + "Music Names" => (0x396..0x3B2), + "System" => (0x3B3..0x3D8), + "Menus" => (0x3D9..0x477), + "Library" => (0x478..0x4A5), + "Events" => (0x4A6..0x50A) +} +TEXT_REGIONS_OVERLAYS = { + "Character Names" => 0, + "Item Names" => 0, + "Item Descriptions" => 0, + "Enemy Names" => 0, + "Enemy Descriptions" => 0, + "Soul Names" => 0, + "Soul Descriptions" => 0, + "Area Names" => 0, + "Music Names" => 0, + "System" => 0, + "Menus" => 0, + "Library" => 0, + "Events" => 0 +} +STRING_DATABASE_START_OFFSET = 0x02217E14 +STRING_DATABASE_ORIGINAL_END_OFFSET = 0x0222B8CA +STRING_DATABASE_ALLOWABLE_END_OFFSET = STRING_DATABASE_ORIGINAL_END_OFFSET +TEXT_COLOR_NAMES = { + 0x00 => "TRANSPARENT", + 0x01 => "WHITE", + 0x02 => "GREY", + 0x03 => "PINK", + 0x04 => "BROWN", + 0x05 => "AZURE", + 0x06 => "DARKBLUE", + 0x07 => "YELLOW", + 0x08 => "ORANGE", + 0x09 => "LIGHTGREEN", + 0x0A => "GREEN", + 0x0B => "BRIGHTPINK", + 0x0C => "PURPLE", + 0x0D => "BROWN2", + 0x0E => "BLACK", + 0x0F => "BLACK2", +} + +NAMES_FOR_UNNAMED_SKILLS = { + 0x2E => "Bat Form", + 0x2F => "Holy Flame", + 0x30 => "Blue Splash", + 0x31 => "Holy Lightning", + 0x32 => "Cross", + 0x33 => "Holy Water", + 0x34 => "Grand Cross", +} + +ENEMY_IDS = (0x00..0x75).to_a +COMMON_ENEMY_IDS = (0x00..0x64).to_a +BOSS_IDS = (0x65..0x75).to_a + +BOSS_DOOR_SUBTYPE = 0x25 +BOSS_ID_TO_BOSS_INDEX = { + 0x65 => 0x01, # Flying Armor + 0x66 => 0x02, # Balore + 0x67 => 0x04, # Malphas + 0x68 => 0x03, # Dmitrii + 0x69 => 0x05, # Dario + 0x6A => 0x06, # Puppet Master + 0x6B => 0x08, # Rahab + 0x6C => 0x07, # Gergoth + 0x6D => 0x09, # Zephyr + 0x6E => 0x0A, # Bat Company + 0x6F => 0x0C, # Paranoia + 0x70 => 0x0B, # Aguni + 0x71 => 0x0D, # Death + 0x72 => 0x0F, # Abaddon +} + +WOODEN_DOOR_SUBTYPE = 0x1D + +AREA_NAME_SUBTYPE = 0x06 + +SAVE_POINT_SUBTYPE = 0x30 +WARP_POINT_SUBTYPE = 0x31 + +ITEM_LOCAL_ID_RANGES = { + 0x02 => (0x00..0x41), # consumable + 0x03 => (0x01..0x4E), # weapon + 0x04 => (0x00..0x3C), # armor +} +ITEM_GLOBAL_ID_RANGE = (0..0xCD) +SKILL_GLOBAL_ID_RANGE = (0xCE..0x148) +SKILL_GLOBAL_ID_RANGE_EXCLUDING_JULIUS = (0xCE...0xFB).to_a + (0x103..0x148).to_a + +SKILL_LOCAL_ID_RANGE = (0..0x7A) +PICKUP_GLOBAL_ID_RANGE = (0..0x148) +MAGIC_SEAL_GLOBAL_ID_RANGE = (0x3D..0x41) + +PICKUP_SUBTYPES_FOR_ITEMS = (0x02..0x04) +PICKUP_SUBTYPES_FOR_SKILLS = (0x05..0xFF) + +NEW_GAME_STARTING_AREA_INDEX_OFFSET = nil +NEW_GAME_STARTING_SECTOR_INDEX_OFFSET = 0x0202FB84 +NEW_GAME_STARTING_ROOM_INDEX_OFFSET = 0x0202FB90 +NEW_GAME_STARTING_X_POS_OFFSET = 0x0222DEC8 +NEW_GAME_STARTING_Y_POS_OFFSET = 0x0222DECC + +TRANSITION_ROOM_LIST_POINTER_HARDCODED_LOCATIONS = [0x02006FB0, 0x02025D64] +FAKE_TRANSITION_ROOMS = [] + +ITEM_ICONS_PALETTE_POINTER = 0x022C4684 +GLYPH_ICONS_PALETTE_POINTER = nil +ITEM_ICONS_GFX_POINTERS = nil + +CONSUMABLE_FORMAT = [ + # length: 16 + [2, "Item ID"], + [2, "Icon"], + [4, "Price"], + [1, "Type"], + [1, "Unknown 1"], + [2, "Var A"], + [4, "Unused"], +] +WEAPON_FORMAT = [ + # length: 28 + [2, "Item ID"], + [2, "Icon"], + [4, "Price"], + [1, "Swing Anim"], + [1, "Unknown 1"], + [1, "Attack"], + [1, "Defense"], + [1, "Strength"], + [1, "Constitution"], + [1, "Intelligence"], + [1, "Luck"], + [4, "Effects", :bitfield], + [1, "Sprite"], + [1, "Super Anim"], + [1, "Sprite Anim"], + [1, "IFrames"], + [2, "Swing Modifiers", :bitfield], + [2, "Swing Sound"], +] +ARMOR_FORMAT = [ + # length: 20 + [2, "Item ID"], + [2, "Icon"], + [4, "Price"], + [1, "Type"], + [1, "Unknown 1"], + [1, "Attack (unused)"], + [1, "Defense"], + [1, "Strength"], + [1, "Constitution"], + [1, "Intelligence"], + [1, "Luck"], + [4, "Resistances", :bitfield], +] +SOUL_FORMAT = [ + # length: 28 + [4, "Code"], + [2, "Sprite"], + [1, "Type"], + [1, "Input flags", :bitfield], + [2, "Soul Scaling"], + [2, "Mana cost"], + [2, "DMG multiplier"], + [2, "Unknown 3"], + [4, "Effects", :bitfield], + [4, "Unwanted States", :bitfield], + [2, "Var A"], + [2, "Var B"], +] +SOUL_EXTRA_DATA_FORMAT = [ + # length: 2 + [1, "Max at once"], + [1, "Bonus max at once"], +] +SUPER_ATTACK_DATA_FORMAT = [ + # length: 8 + [4, "Code"], + [4, "Mana cost"], +] +ITEM_TYPES = [ + { + name: "Consumables", + list_pointer: 0x0209BA68, + count: 66, + format: CONSUMABLE_FORMAT # length: 16 + }, + { + name: "Weapons", + list_pointer: 0x0209C34C, + count: 79, + format: WEAPON_FORMAT # length: 28 + }, + { + name: "Armor", + list_pointer: 0x0209BE88, + count: 61, + format: ARMOR_FORMAT # length: 20 + }, + { + name: "Souls", + list_pointer: 0x0209D190, + count: 123, + kind: :skill, + format: SOUL_FORMAT # length: 28 + }, + { + name: "Souls (extra data)", + list_pointer: 0x0209D124, + count: 53, + kind: :skill, + format: SOUL_EXTRA_DATA_FORMAT # length: 2 + }, + { + name: "Super attacks", + list_pointer: 0x0222EDAC, + count: 15, + kind: :special_attack, + format: SUPER_ATTACK_DATA_FORMAT # length: 8 + }, +] + +ITEM_BITFIELD_ATTRIBUTES = { + "Resistances" => [ + "Strike", + "Stab", + "Slash", + "Fire", + "Ice", + "Lightning", + "Dark", + "Holy", + "Poison", + "Curse", + "Stone", + "Unknown 12", + "Unknown 13", + "Unknown 14", + "Unknown 15", + "Unknown 16", + "Unknown 17", + "Unknown 18", + "Unknown 19", + "Unknown 20", + "Unknown 21", + "Unknown 22", + "Unknown 23", + "Unknown 24", + "Unknown 25", + "(Dmitrii's) Red souls", + "(Dmitrii's) Blue souls", + "Unknown 28", + "Unknown 29", + "Unknown 30", + "Unknown 31", + "Unknown 32", + ], + "Effects" => [ + "Strike", + "Stab", + "Slash", + "Fire", + "Ice", + "Lightning", + "Dark", + "Holy", + "Poison", + "Curse", + "Stone", + "Unknown 12", + "Unknown 13", + "Unknown 14", + "Unknown 15", + "Unknown 16", + "Unknown 17", + "Unknown 18", + "Unknown 19", + "Unknown 20", + "Unknown 21", + "Unknown 22", + "Unknown 23", + "Knuckles", + "Is a weapon", + "Is a red soul", + "Is a blue soul", + "Unknown 28", + "Unknown 29", + "Unknown 30", + "Unknown 31", + "Unknown 32", + ], + "Swing Modifiers" => [ + "No interrupt on land", + "Weapon floats in place", + "No flip on player turn", + "Player can move", + "No trail gradual fadeout", + "Unknown 6", + "Transparent weapon", + "Shaky weapon", + "No interrupt on ???", + "No interrupt on anim end", + "Unknown 11", + "Unknown 12", + "Unknown 13", + "Unknown 14", + "Unknown 15", + "Unknown 16", + ], + "Input flags" => [ + "Can't be copied", + "Unknown 2", + "Unknown 3", + "Unknown 4", + "Hold R", + "Hold R (2)", + "Hold R (3)", + "Hold R (4)", + ], + "Unwanted States" => [ + "Moving", + "Moving forward", + "Facing left", + "In air", + "Double jumping", + "Hippogryph jumping", + "Bouncing after a jumpkick", + "Crouching", + "Unknown 9", + "On jump-through platform", + "Unknown 11", + "Taking damage in the air", + "Unknown 13", + "Jumpkicking", + "Swinging melee weapon", + "Super attacking", + "Unknown 17", + "Using R-button soul", + "Backdashing", + "Unknown 20", + "Jumpkicking", + "Taking damage", + "Unknown 23", + "Unknown 24", + "Unknown 25", + "Unknown 26", + "Unknown 27", + "Floating in water", + "Unknown 29", + "Unknown 30", + "Unknown 31", + "Unknown 32", + ] +} + +ITEM_POOLS_LIST_POINTER = nil +ITEM_POOL_INDEXES_FOR_AREAS_LIST_POINTER = nil +NUMBER_OF_ITEM_POOLS = 0 + +PLAYER_LIST_POINTER = 0x0222E474 +PLAYER_COUNT = 4 +PLAYER_NAMES = [ + "Soma", + "Julius", + "Yoko", + "Alucard", +] +PLAYER_LIST_FORMAT = [ + # length: 88 + [4, "GFX list pointer"], + [4, "Sprite pointer"], + [4, "Palette pointer"], + [4, "State anims ptr"], + [4, "Filename pointer"], + [4, "Walking speed"], + [4, "Jump force"], + [4, "Actions", :bitfield], + [4, "??? bitfield", :bitfield], + [2, "Backdash duration"], + [2, "Unknown 10"], + [4, "Backdash force"], + [4, "Backdash friction"], + [4, "Number of trails"], + [4, "Trail start color"], + [4, "Trail end color"], + [4, "Unknown 16"], + [2, "Sprite Y offset"], + [2, "Unknown 17"], + [4, "Trail & width scale"], + [4, "Player height scale"], + [4, "Enable player scale"], + [2, "Outline color index"], + [2, "Outline color"], + [4, "Sprite asset index"], +] +PLAYER_BITFIELD_ATTRIBUTES = { + "Actions" => [ + "Can slide", + "Can use weapons", + "Can float in water", + "Can sit", + "Unknown 5", + "Can jumpkick", + "Can superjump", + "Unknown 8", + "Unknown 9", + "Unknown 10", + "Unknown 11", + "Unknown 12", + "Unknown 13", + "Unknown 14", + "Unknown 15", + "Unknown 16", + "Unknown 17", + "Unknown 18", + "Unknown 19", + "Unknown 20", + "Unknown 21", + "Unknown 22", + "Unknown 23", + "Unknown 24", + "Unknown 25", + "Unknown 26", + "Unknown 27", + "Unknown 28", + "Unknown 29", + "Unknown 30", + "Unknown 31", + "Unknown 32", + ], + "??? bitfield" => [ + "Horizontal flip", + "Can smash head", + "Unknown 3", + "Unknown 4", + "Unknown 5", + "Unknown 6", + "Unknown 7", + "Unknown 8", + "Unknown 9", + "Unknown 10", + "Unknown 11", + "Unknown 12", + "Unknown 13", + "Unknown 14", + "Unknown 15", + "Unknown 16", + "Unknown 17", + "Unknown 18", + "Unknown 19", + "Unknown 20", + "Unknown 21", + "Unknown 22", + "Unknown 23", + "Unknown 24", + "Unknown 25", + "Unknown 26", + "Unknown 27", + "Unknown 28", + "Unknown 29", + "Unknown 30", + "Unknown 31", + "Unknown 32", + ] +} + +NEW_OVERLAY_ID = 41 +NEW_OVERLAY_FREE_SPACE_START = 0x02308920 +NEW_OVERLAY_FREE_SPACE_MAX_SIZE = 0xC000 +ASSET_MEMORY_START_HARDCODED_LOCATION = 0x02060234 + +TEST_ROOM_SAVE_FILE_INDEX_LOCATION = 0x0203E520 +TEST_ROOM_AREA_INDEX_LOCATION = nil +TEST_ROOM_SECTOR_INDEX_LOCATION = 0x0203E540 +TEST_ROOM_ROOM_INDEX_LOCATION = 0x0203E548 +TEST_ROOM_X_POS_LOCATION = 0x0203E574 +TEST_ROOM_Y_POS_LOCATION = 0x0203E578 +TEST_ROOM_OVERLAY = nil + +WEAPON_SYNTH_CHAIN_LIST_START = 0x0209CCBC +WEAPON_SYNTH_CHAIN_NAMES = [ + "Swords", + "Great Swords", + "Rapiers", + "Polearms", + "Axes", + "Hammers", + "Katanas", + "Punchs", +] + +SHOP_ITEM_POOL_LIST = 0x02037264 +SHOP_ITEM_POOL_COUNT = 3 +SHOP_ITEM_POOL_LENGTH_HARDCODED_LOCATIONS = [0x020370F4, 0x0203711C, 0x02037144] +SHOP_ITEM_POOL_REQUIRED_EVENT_FLAG_HARDCODED_LOCATIONS = [nil, 0x0203710C, 0x02037134] + +FAKE_FREE_SPACES = [] + +MAGIC_SEAL_COUNT = 5 +MAGIC_SEAL_LIST_START = 0x0222F1DC +MAGIC_SEAL_FOR_BOSS_LIST_START = 0x0222F290 + +NUM_PLAYER_ANIM_STATES = 0x42 +PLAYER_ANIM_STATE_NAMES = [ + "Idle", + "Holding up", + "Starting to walk", + "Turning around", + "Walking", + "Stopping", + "Landing while moving", + "Starting to crouch", + "Crouching", + "Standing up", + "Jumping straight up", + "Jumping diagonally", + "Double jumping", + "Superjumping", + "Smashing head", + "Falling", + "Landing from small height", + "Landing from medium height", + "Hard landing straight down", + "Hard landing diagonally", + "Swimming", + "Turning around while swimming", + "Sitting", + "Using skill on ground", + "Using skill in air", + "Starting to use transformation skill", + "Using transformation skill", + "Using armor knight", + "Using malacoda", + "Using succubus", + "Using ghost", + "Using alura une", + "Using slaughterer", + "Using hell boar", + "Starting to charge skull archer", + "Charging skull archer", + "Firing skull archer", + "", + "Starting to use gergoth/white dragon/gorgon", + "Using gergoth/white dragon/gorgon", + "Stop using gergoth", + "Bat mode", + "Flying up in bat mode", + "Transforming into bat mode", + "Untransforming from bat mode", + "Using black panther", + "Using werewolf", + "Backdashing", + "Sliding on flat ground", + "Sliding on slope", + "Jumpkicking straight down", + "Jumpkicking diagonally down", + "Taking damage while standing/walking", + "Taking damage while backdashing", + "Taking damage while crouching", + "Taking damage in air from the front", + "Taking damage in air from behind", + "Petrified 1", + "Petrified 2", + "Dying", + "Attacking (unused for Soma)", + "Attacking while crouching (unused for Soma)", + "Attacking in air (unused for Soma)", + "Stop attacking (unused for Soma)", + "Stop attacking while crouching (unused for Soma)", + "", +] + +MENU_BG_LAYER_INFOS = [ + { + name: "Main menu (bottom screen)", + gfx_list_pointer: 0x02079F64, + palette_list_pointer: 0x022DA284, + layer_metadata_pointer: 0x0207C8CC, + }, + { + name: "Main menu (top screen)", + gfx_file_pointers: [0x022D9F48, 0x022D9F54], + palette_list_pointer: 0x022DA390, + layer_metadata_pointer: 0x0207BE7C, + }, + { + name: "Sound config", + gfx_file_pointers: [0x022D9F3C], + palette_list_pointer: 0x022D9F60, + layer_metadata_pointer: 0x0207C29C, + }, + { + name: "Sound mode", + gfx_file_pointers: [0x022D9F3C], + palette_list_pointer: 0x022D9F60, + layer_metadata_pointer: 0x0207C4AC, + }, + { + name: "Shop", + gfx_list_pointer: 0x02079EE4, + palette_list_pointer: 0x022D9FC4, + layer_metadata_pointer: 0x0207C6BC, + }, + { + name: "Weapon synthesis", + gfx_list_pointer: 0x02079EEC, + palette_list_pointer: 0x022D9FE8, + layer_metadata_pointer: 0x0207BA5C, + }, + { + name: "Info screen", + gfx_file_pointers: [0x022D7E40], + palette_list_pointer: 0x022DA30C, + layer_metadata_pointer: 0x0207C08C, + }, + { + name: "Pause screen", + gfx_list_pointer: 0x02079E70, + palette_list_pointer: 0x022DA3D4, + layer_metadata_pointer: 0x0207B63C, + }, + { + name: "Equip", + gfx_file_pointers: [0x022D9E70], + palette_list_pointer: 0x022DA2A8, + layer_metadata_pointer: 0x0207BC6C, + }, + { + name: "Use item", + gfx_file_pointers: [0x022D9EA0], + palette_list_pointer: 0x022DA240, + layer_metadata_pointer: 0x0207B00C, + }, + { + name: "Ability souls", + gfx_file_pointers: [0x022D9EAC], + palette_list_pointer: 0x022DA1DC, + layer_metadata_pointer: 0x0207A7CC, + }, + { + name: "Config", + gfx_file_pointers: [0x022D9EB8], + palette_list_pointer: 0x022DA0D4, + layer_metadata_pointer: 0x0207A9DC, + }, + { + name: "Bestiary list", + gfx_list_pointer: 0x02217CA8, + palette_list_pointer: 0x022DA138, + layer_metadata_pointer: 0x0207ABEC, + }, + { + name: "Bestiary entry", + gfx_list_pointer: 0x02217CA8, + palette_list_pointer: 0x022DA138, + layer_metadata_pointer: 0x0207ADFC, + }, + { + name: "Library list", + gfx_file_pointers: [0x022D9EDC], + palette_list_pointer: 0x022DA070, + layer_metadata_pointer: 0x0207B21C, + }, + { + name: "Library entry", + gfx_file_pointers: [0x022D9EDC], + palette_list_pointer: 0x022DA070, + layer_metadata_pointer: 0x0207B42C, + }, + { + name: "Magic Seal practice", + gfx_file_pointers: [0x022D9EE8], + palette_list_pointer: 0x022DA00C, + layer_metadata_pointer: 0x0207B84C, + }, +] + +FONTS = [ + { + font_path: "/font/LD_font_u8.DAT", + char_width: 8, + char_height: 8, + }, + { + font_path: "/font/LD_font_u12.DAT", + char_width: 16, + char_height: 12, + }, +] diff --git a/randomizers/pickup_randomizer.rb b/randomizers/pickup_randomizer.rb index 9ebd5988..cb8ff71a 100644 --- a/randomizers/pickup_randomizer.rb +++ b/randomizers/pickup_randomizer.rb @@ -1280,7 +1280,11 @@ def get_unplaced_non_progression_item_that_can_be_an_arm_shifted_immediate end def get_unplaced_non_progression_skill(id_to_exclude_= -1) - return get_unplaced_non_progression_pickup(valid_ids: SKILL_GLOBAL_ID_RANGE.to_a,id_to_exclude:id_to_exclude_) + if options[:remove_julius_skills] + return get_unplaced_non_progression_pickup(valid_ids: SKILL_GLOBAL_ID_RANGE_EXCLUDING_JULIUS,id_to_exclude:id_to_exclude_) + else + return get_unplaced_non_progression_pickup(valid_ids: SKILL_GLOBAL_ID_RANGE.to_a,id_to_exclude:id_to_exclude_) + end end def get_unplaced_non_progression_item_except_ooe_relics From 271967d543a6dde8b4129cfb9a04bfa58925f320 Mon Sep 17 00:00:00 2001 From: WooDose <59982861+WooDose@users.noreply.github.com> Date: Mon, 10 Jul 2023 15:51:33 -0600 Subject: [PATCH 6/6] Hotfix for Julius skills softlocking due to bat form always showing up before bat company --- .gitignore | 1 + randomizers/pickup_randomizer.rb | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 28b9fccc..e03536f4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ armips/Readme.md armips/LICENSE.txt armips/armips64.exe armips/armips.exe +Rakefile diff --git a/randomizers/pickup_randomizer.rb b/randomizers/pickup_randomizer.rb index cb8ff71a..8552bb24 100644 --- a/randomizers/pickup_randomizer.rb +++ b/randomizers/pickup_randomizer.rb @@ -227,15 +227,19 @@ def place_progression_pickups_random_fill # Always keep bat company if julius skills disabled if GAME == "dos" && pickups_available.include?(0x104) && pickups_available.include?(0xFC) if options[:remove_julius_skills] - bat_to_keep = 0x104 + bat_to_keep = 0xFC + bat_to_remove = 0xFC else bat_to_keep = [0x104, 0xFC].sample(random: rng) + bat_to_remove = (bat_to_keep == 0x104 ? 0xFC : 0x104) end - bat_to_remove = (bat_to_keep == 0x104 ? 0xFC : 0x104) pickups_available.delete(bat_to_remove) elsif GAME == "dos" && pickups_available.include?(0xFC) bat_to_keep = 0x104 bat_to_remove = 0xFC + if options[:remove_julius_skills] + bat_to_remove = 0xFC + end pickups_available.delete(bat_to_remove) else bat_to_keep = nil @@ -315,7 +319,7 @@ def place_progression_pickups_random_fill used_item_locations = @done_item_locations.keys - if bat_to_keep + if bat_to_keep && !options[:remove_julius_skills] # If we had to remove one of the two bats from being randomly placed, we now go and place it non-randomly. # We simply place it in the last possible progression sphere we can find. # (Which specific location within that sphere is still chosen randomly.)