Standing Items initial commit

This commit is contained in:
aerinon
2021-12-16 16:02:11 -07:00
parent c651d1d584
commit b2f2565271
26 changed files with 618 additions and 469 deletions

140
Rom.py
View File

@@ -1,6 +1,6 @@
import bisect
import collections
import io
import itertools
import json
import hashlib
import logging
@@ -15,7 +15,7 @@ try:
except ImportError:
raise Exception('Could not load BPS module')
from BaseClasses import CollectionState, ShopType, Region, Location, Door, DoorType, RegionType, PotItem
from BaseClasses import CollectionState, ShopType, Region, Location, Door, DoorType, RegionType, PotItem, LocationType
from DoorShuffle import compass_data, DROptions, boss_indicator, dungeon_portals
from Dungeons import dungeon_music_addresses, dungeon_table
from Regions import location_table, shop_to_location_table, retro_shops
@@ -23,16 +23,19 @@ from RoomData import DoorKind
from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable
from Text import Uncle_texts, Ganon1_texts, Ganon_Phase_3_No_Silvers_texts, TavernMan_texts, Sahasrahla2_texts
from Text import Triforce_texts, Blind_texts, BombShop2_texts, junk_texts
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts
from Text import LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts
from Text import Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes, snes_to_pc
from Items import ItemFactory
from ItemList import valid_pot_items
from EntranceShuffle import door_addresses, exit_ids, ow_prize_table
from source.classes.SFX import randomize_sfx
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '2cfa164d4b66a15406f53ca4750ef59a'
RANDOMIZERBASEHASH = '3ee527f67af25e860ffd4364d2369f0e'
class JsonRom(object):
@@ -547,6 +550,21 @@ class Sprite(object):
return array_chunk(palette_as_colors, 15)
def handle_native_dungeon(location, itemid):
# Keys in their native dungeon should use the original item code for keys
if location.parent_region.dungeon:
if location.parent_region.dungeon.is_dungeon_item(location.item):
if location.item.bigkey:
return 0x32
if location.item.smallkey:
return 0x24
if location.item.map:
return 0x33
if location.item.compass:
return 0x25
return itemid
def patch_rom(world, rom, player, team, enemized, is_mystery=False):
random.seed(world.rom_seeds[player])
@@ -558,28 +576,47 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
distinguished_prog_bow_loc.item.code = 0x65
# patch items
pot_mw_index = 0
for location in world.get_locations():
if location.player != player:
continue
itemid = location.item.code if location.item is not None else 0x5A
if location.type == LocationType.Pot:
if location.item.name in valid_pot_items and location.item.player == player:
location.pot.item = valid_pot_items[location.item.name]
else:
code = handle_native_dungeon(location, itemid)
standing_item_flag = 0x80
if location.item.player != player:
standing_item_flag |= 0x40
rom.write_byte(0x142800 + pot_mw_index * 2, code)
rom.write_byte(0x142800 + pot_mw_index * 2 + 1, location.item.player)
code = pot_mw_index
pot_mw_index += 1
location.pot.indicator = standing_item_flag
location.pot.standing_item_code = code
continue
elif location.type == LocationType.Drop:
if location.item.player != player:
code = 0xF9
else:
code = 0xF8
sprite_pointer = snes_to_pc(location.address)
if code != 0xE4:
rom.write_byte(sprite_pointer, handle_native_dungeon(location, itemid))
if code == 0xF9:
rom.write_byte(sprite_pointer+1, location.item.player)
else:
rom.write_byte(sprite_pointer+1, 0)
rom.write_byte(sprite_pointer+2, code)
continue
if location.address is None or (type(location.address) is int and location.address >= 0x400000):
continue
if not location.crystal:
if location.item is not None:
# Keys in their native dungeon should use the original item code for keys
if location.parent_region.dungeon:
if location.parent_region.dungeon.is_dungeon_item(location.item):
if location.item.bigkey:
itemid = 0x32
if location.item.smallkey:
itemid = 0x24
if location.item.map:
itemid = 0x33
if location.item.compass:
itemid = 0x25
itemid = handle_native_dungeon(location, itemid)
if world.remote_items[player]:
itemid = list(location_table.keys()).index(location.name) + 1
assert itemid < 0x100
@@ -608,7 +645,9 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
rom.write_byte(0x155C9, random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle
if world.pot_contents[player]:
write_pots_to_rom(rom, world.pot_contents[player])
if world.pot_contents[player].size() > 0x2800:
raise Exception('Pot table is too big for current area')
world.pot_contents[player].write_pot_data_to_rom(rom)
# patch entrance/exits/holes
for region in world.regions:
@@ -691,14 +730,19 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
if world.logic[player] != 'nologic':
dr_flags |= DROptions.Fix_EG
my_locations = world.get_filled_locations(player)
valid_locations = [l for l in my_locations if ((l.type == LocationType.Pot and l.pot.indicator)
or (l.type == LocationType.Drop and not l.forced_item)
or (l.type == LocationType.Normal and not l.forced_item)
or (l.type == LocationType.Shop and world.shopsanity[player]))]
# fix hc big key problems (map and compass too)
if world.doorShuffle[player] == 'crossed' or world.keydropshuffle[player]:
if world.doorShuffle[player] == 'crossed' or world.keydropshuffle[player] != 'none':
rom.write_byte(0x151f1, 2)
rom.write_byte(0x15270, 2)
sanctuary = world.get_region('Sanctuary', player)
rom.write_byte(0x1597b, sanctuary.dungeon.dungeon_id*2)
update_compasses(rom, world, player)
update_compasses(rom, valid_locations, world, player)
def should_be_bunny(region, mode):
if mode != 'inverted':
@@ -816,19 +860,12 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
# bot: $7A is 1, 7B is 2, etc so 7D=4, 82=9 (zero unknown...)
return 0x53+int(num), 0x79+int(num)
credits_total = 216
if world.keydropshuffle[player]:
credits_total += 33
if world.shopsanity[player]:
credits_total += 32
if world.retro[player]:
credits_total += 9 if world.shopsanity[player] else 5
credits_total = len(valid_locations)
if world.keydropshuffle[player]:
rom.write_byte(0x140000, 1)
multiClientFlags = ((0x1 if world.keydropshuffle[player] else 0) | (0x2 if world.shopsanity[player] else 0)
| (0x4 if world.retro[player] else 0))
rom.write_byte(0x140001, multiClientFlags)
if world.keydropshuffle[player] != 'none':
rom.write_byte(0x142A50, 1)
rom.write_byte(0x142A51, 1)
rom.write_byte(0x142A52, 216-credits_total) # todo: this is now a delta
write_int16(rom, 0x187010, credits_total) # dynamic credits
if credits_total != 216:
@@ -1153,7 +1190,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed
gametype = 0x04 # item
if world.shuffle[player] != 'vanilla' or world.doorShuffle[player] != 'vanilla' or world.keydropshuffle[player]:
if world.shuffle[player] != 'vanilla' or world.doorShuffle[player] != 'vanilla' or world.keydropshuffle[player] != 'none':
gametype |= 0x02 # entrance/door
if enemized:
gametype |= 0x01 # enemizer
@@ -1403,7 +1440,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
rom.write_byte(0x18003C, 0x00)
elif world.dungeon_counters[player] == 'on':
compass_mode = 0x02 # always on
elif world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla' or world.dungeon_counters[player] == 'pickup':
elif (world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla'
or world.dungeon_counters[player] == 'pickup' or world.keydropshuffle != 'none'):
compass_mode = 0x01 # show on pickup
if world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default':
compass_mode |= 0x80 # turn on locating dungeons
@@ -1577,7 +1615,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
rom.write_byte(0xFED31, 0x0E) # preopen bombable exit
rom.write_byte(0xFEE41, 0x0E) # preopen bombable exit
if world.doorShuffle[player] != 'vanilla' or world.keydropshuffle[player]:
if world.doorShuffle[player] != 'vanilla' or world.keydropshuffle[player] != 'none':
for room in world.rooms:
if room.player == player and room.modified:
rom.write_bytes(room.address(), room.rom_data())
@@ -2577,12 +2615,16 @@ def patch_shuffled_dark_sanc(world, rom, player):
rom.write_bytes(0x180262, [unknown_1, unknown_2, 0x00])
def update_compasses(rom, world, player):
def update_compasses(rom, valid_locations, world, player):
dungeon_locations = collections.defaultdict(set)
for l in valid_locations:
if l.parent_region.dungeon:
dungeon_locations[l.parent_region.dungeon.name].add(l)
layouts = world.dungeon_layouts[player]
provided_dungeon = False
for name, builder in layouts.items():
dungeon_id = compass_data[name][4]
rom.write_byte(0x187000 + dungeon_id//2, builder.location_cnt)
rom.write_byte(0x187000 + dungeon_id//2, len(dungeon_locations[name]))
if builder.bk_provided:
if provided_dungeon:
logging.getLogger('').warning('Multiple dungeons have forced BKs! Compass code might need updating?')
@@ -2851,29 +2893,3 @@ hash_alphabet = [
"Lamp", "Hammer", "Shovel", "Ocarina", "Bug Net", "Book", "Bottle", "Potion", "Cane", "Cape", "Mirror", "Boots",
"Gloves", "Flippers", "Pearl", "Shield", "Tunic", "Heart", "Map", "Compass", "Key"
]
pot_item_room_table_lookup = 0xDB67
###
# Pointer to pot location and contents for each non-empty pot in a supertile
# Format: [(x, y, item)] FF FF (Note: x,y are bit packed to include layer)
pot_item_table = 0xDDE7
pot_item_table_end = 0xE6B0
def write_pots_to_rom(rom, pot_contents):
n = pot_item_table
rom.write_bytes(n, [0xFF,0xFF])
n += 2
for i in range(0x140):
if i in pot_contents:
pots = [pot for pot in pot_contents[i] if pot.item != PotItem.Nothing]
if len(pots) > 0:
write_int16(rom, pot_item_room_table_lookup + 2*i, n)
rom.write_bytes(n, list(itertools.chain.from_iterable(((pot.x, pot.y, pot.item) for pot in pots))))
n += 3*len(pots) + 2
rom.write_bytes(n - 2, [0xFF,0xFF])
else:
write_int16(rom, pot_item_room_table_lookup + 2*i, n-2)
else:
write_int16(rom, pot_item_room_table_lookup + 2*i, n-2)
assert n <= pot_item_table_end