diff --git a/.gitignore b/.gitignore index 169e22a..e03536f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,10 @@ 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 +Rakefile diff --git a/constants/dos_constants.rb b/constants/dos_constants.rb new file mode 100644 index 0000000..322b6e6 --- /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/constants/options.rb b/constants/options.rb index e46a623..645b2ca 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.", @@ -51,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.", @@ -88,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 3b6b45d..f84b4a5 100644 --- a/randomizer.ui +++ b/randomizer.ui @@ -225,6 +225,16 @@ + + + + Enemy Drop Chances + + + true + + + @@ -541,6 +551,13 @@ + + + + Remove Julius Skills + + + @@ -993,6 +1010,7 @@ randomize_enemies randomize_bosses randomize_enemy_drops + randomize_enemy_drop_chances randomize_enemy_stats randomize_enemy_anim_speed randomize_enemy_tolerances @@ -1030,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 afe07bb..9ae249a 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 cca3d7a..ac03773 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,45 +80,26 @@ def randomize_enemy_drops enemy["Item 1"] = 0 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 + ### 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" - 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 +111,58 @@ 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] + 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 +202,4 @@ def remove_all_enemy_drops end end end +end \ No newline at end of file diff --git a/randomizers/pickup_randomizer.rb b/randomizers/pickup_randomizer.rb index 03e0e80..8552bb2 100644 --- a/randomizers/pickup_randomizer.rb +++ b/randomizers/pickup_randomizer.rb @@ -224,13 +224,22 @@ 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) - bat_to_remove = (bat_to_keep == 0x104 ? 0xFC : 0x104) + if options[:remove_julius_skills] + 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 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 @@ -310,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.) @@ -1230,12 +1239,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 +1283,12 @@ 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) + 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 @@ -1560,18 +1580,25 @@ 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 # 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? diff --git a/version.rb b/version.rb index 6123f4e..9d77b78 100644 --- a/version.rb +++ b/version.rb @@ -1,2 +1,2 @@ -DSVRANDOM_VERSION = "1.4.5" +DSVRANDOM_VERSION = "1.4.6-wf"