Initial work on new pottery modes

This commit is contained in:
aerinon
2022-04-19 13:56:39 -06:00
parent c560e0d275
commit 26e8e23fce
19 changed files with 1049 additions and 362 deletions

View File

@@ -16,6 +16,7 @@ from EntranceShuffle import door_addresses, indirect_connections
from Utils import int16_as_bytes
from Tables import normal_offset_table, spiral_offset_table, multiply_lookup, divisor_lookup
from RoomData import Room
from source.dungeon.RoomObject import RoomObject
class World(object):
@@ -139,6 +140,8 @@ class World(object):
set_player_attr('pot_contents', None)
set_player_attr('pseudoboots', False)
set_player_attr('collection_rate', False)
set_player_attr('colorizepots', False)
set_player_attr('pot_pool', {})
set_player_attr('shopsanity', False)
set_player_attr('mixed_travel', 'prevent')
@@ -2709,7 +2712,7 @@ class PotFlags(FastEnum):
class Pot(object):
def __init__(self, x, y, item, room, flags = PotFlags.Normal):
def __init__(self, x, y, item, room, flags=PotFlags.Normal, obj=None):
self.x = x
self.y = y
self.item = item
@@ -2717,9 +2720,12 @@ class Pot(object):
self.flags = flags
self.indicator = None # 0x80 for standing item, 0xC0 multiworld item
self.standing_item_code = None # standing item code if nay
self.obj_ref = obj
self.location = None # location back ref
def copy(self):
return Pot(self.x, self.y, self.item, self.room, self.flags)
obj_ref = RoomObject(self.obj_ref.address, self.obj_ref.data) if self.obj_ref else None
return Pot(self.x, self.y, self.item, self.room, self.flags, obj_ref)
def pot_data(self):
high_byte = self.y
@@ -2730,6 +2736,12 @@ class Pot(object):
item = self.item if not self.indicator else self.standing_item_code
return [self.x, high_byte, item]
def __eq__(self, other):
return self.x == other.x and self.y == other.y and self.room == other.room
def __hash__(self):
return hash((self.x, self.y, self.room))
# byte 0: DDDE EEEE (DR, ER)
dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0}
@@ -2753,7 +2765,8 @@ mixed_travel_mode = {"prevent": 0, "allow": 1, "force": 2}
# new byte 4: ?DDD PPPP (unused, drop, pottery)
# dropshuffle reserves 2 bits, pottery needs 2 but reserves 2 for future modes)
pottery_mode = {"none": 0, "shuffle": 1, "keys": 2, "lottery": 3, 'dungeon': 4, 'cave': 5}
pottery_mode = {'none': 0, 'keys': 2, 'lottery': 3, 'dungeon': 4, 'cave': 5, 'cavekeys': 6, 'reduced': 7,
'clustered': 8, 'nonempty': 9}
# byte 5: CCCC CTTX (crystals gt, ctr2, experimental)
counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3}

3
CLI.py
View File

@@ -108,7 +108,7 @@ def parse_cli(argv, no_defaults=False):
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle',
'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx',
'msu_resume', 'collection_rate']:
'msu_resume', 'collection_rate', 'colorizepots']:
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
if player == 1:
setattr(ret, name, {1: value})
@@ -165,6 +165,7 @@ def parse_settings():
'keydropshuffle': False,
'dropshuffle': False,
'pottery': 'none',
'colorizepots': False,
'shufflepots': False,
"mapshuffle": False,
"compassshuffle": False,

View File

@@ -764,7 +764,7 @@ def add_pot_contents(world, player):
for super_tile, pot_list in vanilla_pots.items():
for pot in pot_list:
if pot.item not in [PotItem.Hole, PotItem.Key, PotItem.Switch]:
if valid_pot_location(pot, world, player):
if valid_pot_location(pot, world.pot_pool[player], world, player):
world.itempool.append(ItemFactory(pot_items[pot.item], player))

View File

@@ -110,6 +110,7 @@ def main(args, seed=None, fish=None):
world.overworld_map = args.overworld_map.copy()
world.restrict_boss_items = args.restrict_boss_items.copy()
world.collection_rate = args.collection_rate.copy()
world.colorizepots = args.colorizepots.copy()
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
@@ -160,7 +161,7 @@ def main(args, seed=None, fish=None):
logger.info(world.fish.translate("cli", "cli", "shuffling.pots"))
for player in range(1, world.players + 1):
if world.potshuffle[player]:
if world.pottery[player] not in ['lottery', 'dungeon']:
if world.pottery[player] in ['none', 'cave', 'keys', 'cavekeys']:
shuffle_pots(world, player)
else:
shuffle_pot_switches(world, player)

View File

@@ -180,6 +180,7 @@ def roll_settings(weights):
ret.shopsanity = get_choice('shopsanity') == 'on'
ret.dropshuffle = get_choice('dropshuffle') == 'on'
ret.pottery = get_choice('pottery') if 'pottery' in weights else 'none'
ret.colorizepots = get_choice('colorizepots') == 'on'
ret.shufflepots = get_choice('pot_shuffle') == 'on'
ret.mixed_travel = get_choice('mixed_travel') if 'mixed_travel' in weights else 'prevent'
ret.standardize_palettes = get_choice('standardize_palettes') if 'standardize_palettes' in weights else 'standardize'

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
import collections
from Items import ItemFactory
from BaseClasses import Region, Location, Entrance, RegionType, Shop, ShopType, LocationType, PotItem, PotFlags
from PotShuffle import key_drop_data, vanilla_pots, PotSecretTable
from PotShuffle import key_drop_data, vanilla_pots, choose_pots, PotSecretTable
def create_regions(world, player):
@@ -1007,6 +1007,7 @@ def adjust_locations(world, player):
pot = pot.copy()
loc.address = pot_address(pot_index, datum[1])
loc.pot = pot
pot.location = loc
if (not world.dropshuffle[player] and drop_location)\
or (not drop_location and world.pottery[player] in ['none', 'cave']):
loc.skip = True
@@ -1023,6 +1024,7 @@ def adjust_locations(world, player):
dungeon.small_keys.append(key_item)
elif key_item.bigkey:
dungeon.big_key = key_item
world.pot_pool[player] = choose_pots(world, player)
for super_tile, pot_list in vanilla_pots.items():
for pot_index, pot_orig in enumerate(pot_list):
if pot_orig.item == PotItem.Key:
@@ -1032,7 +1034,7 @@ def adjust_locations(world, player):
pot = pot_orig.copy()
world.pot_contents[player].room_map[super_tile].append(pot)
if valid_pot_location(pot, world, player):
if valid_pot_location(pot, world.pot_pool[player], world, player):
create_pot_location(pot, pot_index, super_tile, world, player)
if world.shopsanity[player]:
index = 0
@@ -1055,12 +1057,16 @@ def adjust_locations(world, player):
location.skip = True
def valid_pot_location(pot, world, player):
def valid_pot_location(pot, pot_set, world, player):
if world.pottery[player] == 'lottery':
return True
if world.pottery[player] == 'nonempty' and pot.item != PotItem.Nothing:
return True
if world.pottery[player] in ['reduced', 'clustered'] and pot in pot_set:
return True
if world.pottery[player] == 'dungeon' and world.get_region(pot.room, player).type == RegionType.Dungeon:
return True
if world.pottery[player] == 'cave' and world.get_region(pot.room, player).type == RegionType.Cave:
if world.pottery[player] in ['cave', 'cavekeys'] and world.get_region(pot.room, player).type == RegionType.Cave:
return True
return False
@@ -1068,7 +1074,7 @@ def valid_pot_location(pot, world, player):
def create_pot_location(pot, pot_index, super_tile, world, player):
if (pot.item not in [PotItem.Key, PotItem.Hole]
and (pot.item != PotItem.Switch or (world.potshuffle[player]
and world.pottery[player] in ['lottery', 'dungeon']))):
and world.pottery[player] not in ['none', 'cave', 'keys', 'cavekeys']))):
address = pot_address(pot_index, super_tile)
region = pot.room
if world.mode[player] == 'inverted':
@@ -1084,6 +1090,7 @@ def create_pot_location(pot, pot_index, super_tile, world, player):
parent=parent)
world.dynamic_locations.append(pot_location)
pot_location.pot = pot
pot.location = pot_location
pot_location.type = LocationType.Pot
parent.locations.append(pot_location)

20
Rom.py
View File

@@ -32,10 +32,11 @@ from EntranceShuffle import door_addresses, exit_ids, ow_prize_table
from source.classes.SFX import randomize_sfx
from source.item.FillUtil import valid_pot_items
from source.dungeon.RoomList import Room0127
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '8d196e8024faebbbbe1304032158ccea'
RANDOMIZERBASEHASH = 'eb982135bdfe95a23b9068bd9cac4449'
class JsonRom(object):
@@ -651,9 +652,11 @@ 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]:
colorize_pots = (world.pottery[player] not in ['vanilla', 'lottery']
and (world.colorizepots[player] or world.pottery[player] in ['reduced', 'clustered']))
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)
world.pot_contents[player].write_pot_data_to_rom(rom, colorize_pots)
# fix for swamp drains if necessary
swamp1location = world.get_location('Swamp Palace - Trench 1 Pot Key', player)
if not swamp1location.pot.indicator:
@@ -893,14 +896,11 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
if world.pottery[player] not in ['none', 'keys']:
# Cuccos should not prevent kill rooms from opening
rom.write_byte(snes_to_pc(0x0DB457), 0x40)
if world.pottery[player] in ['none', 'keys']:
rom.write_byte(snes_to_pc(0x28AA56), 0)
elif world.pottery[player] == 'cave':
rom.write_byte(snes_to_pc(0x28AA56), 1)
elif world.pottery[player] == 'dungeon':
rom.write_byte(snes_to_pc(0x28AA56), 2)
elif world.pottery[player] == 'lottery':
rom.write_byte(snes_to_pc(0x28AA56), 3)
rom.write_byte(snes_to_pc(0x28AA56), 0 if world.pottery[player] == 'none' else 1)
if world.pottery[player] not in ['none']:
rom.write_bytes(snes_to_pc(0x1F8375), int32_as_bytes(0x2A8000))
# make hammer pegs use different tiles
Room0127.write_to_rom(snes_to_pc(0x2A8000), rom)
write_int16(rom, 0x187010, credits_total) # dynamic credits
if credits_total != 216:

View File

@@ -721,7 +721,7 @@ def bomb_rules(world, player):
def pot_rules(world, player):
if world.pottery[player] in ['lottery', 'cave']:
if world.pottery[player] != 'none':
blocks = [l for l in world.get_locations() if l.type == LocationType.Pot and l.pot.flags & PotFlags.Block]
for block_pot in blocks:
add_rule(block_pot, lambda state: state.can_lift_rocks(player))
@@ -748,8 +748,6 @@ def pot_rules(world, player):
or state.has('Cape', player)
or (state.has('Cane of Byrna', player)
and state.world.difficulty_adjustments[player] == 'normal'))
if world.pottery[player] in ['lottery', 'dungeon']:
for l in world.get_region('Ice Hammer Block', player).locations:
if l.type == LocationType.Pot:
add_rule(l, lambda state: state.has('Hammer', player) and state.can_lift_rocks(player))

View File

@@ -679,9 +679,22 @@ def extract_data_from_jp_rom(rom):
# print()
def check_pots():
from PotShuffle import vanilla_pots
for supertile, pot_list in vanilla_pots.items():
for i,pot in enumerate(pot_list):
if pot.obj_ref:
r = pot.obj_ref
secret_vram = pot.x | (pot.y << 8)
tile_vram = ((r.data[1] & 0xFC) << 5) | ((r.data[0] & 0xFC) >> 1)
if secret_vram != tile_vram:
print(f'{pot.room}#{i+1} secret: {hex(secret_vram)} tile: {hex(tile_vram)}')
if __name__ == '__main__':
# make_new_base2current()
# read_entrance_data(old_rom=sys.argv[1])
# room_palette_data(old_rom=sys.argv[1])
# extract_data_from_us_rom(sys.argv[1])
extract_data_from_jp_rom(sys.argv[1])
# extract_data_from_jp_rom(sys.argv[1])
check_pots()

Binary file not shown.

View File

@@ -78,9 +78,17 @@
"keys",
"dungeon",
"cave",
"cavekeys",
"reduced",
"clustered",
"nonempty",
"lottery"
]
},
"colorizepots" : {
"action": "store_true",
"type": "bool"
},
"shufflepots": {
"action": "store_true",
"type": "bool"

View File

@@ -258,9 +258,15 @@
"None: No pots are changed",
"Keys: Key pots are included in the location pool and other items can take their place",
"Cave: Only pots in houses and caves are included in the location pool",
"CaveKeys: Both pots in houses and caves and keys pots are included in the location pool",
"Reduced: Same as KeyCaves + 25% of Pots in dungeons (dynamic mode)",
"Clustered: Same as KeyCaves + 50% of Pots in dungeons, chosen by logical group (dynamic mode)",
"NonEmpty: All pots that are not originally empty are included in the location pool",
"Dungeon: Only pots in dungeons are included in the location pool",
"Lottery: All pots are part of the location pool"
],
"colorizepots": ["All pots chosen to be in location pool by the pottery setting are different.",
"Forced on in dynamic modes. Forced off in lottery"],
"shufflepots": [ "Pots and switches are shuffled on the supertile (legacy potshuffle) (default: %(default)s)"],
"mixed_travel": [
"How to handle potential traversal between dungeon in Crossed door shuffle",

View File

@@ -61,8 +61,13 @@
"randomizer.dungeon.pottery.none": "None",
"randomizer.dungeon.pottery.keys": "Key Pots",
"randomizer.dungeon.pottery.cave": "Cave Pots",
"randomizer.dungeon.pottery.cavekeys": "Cave+Key Pots",
"randomizer.dungeon.pottery.reduced": "Reduced Dungeon Pots (Dynamic)",
"randomizer.dungeon.pottery.clustered": "Clustered Dungeon Pots (Dynamic)",
"randomizer.dungeon.pottery.nonempty": "Excludes Empty Pots",
"randomizer.dungeon.pottery.dungeon": "Dungeon Pots",
"randomizer.dungeon.pottery.lottery": "Lottery (All Pots and Large Blocks)",
"randomizer.dungeon.colorizepots": "Colorize Randomized Pots",
"randomizer.dungeon.dungeondoorshuffle": "Dungeon Door Shuffle",
"randomizer.dungeon.dungeondoorshuffle.vanilla": "Vanilla",

View File

@@ -30,6 +30,10 @@
"none",
"keys",
"cave",
"cavekeys",
"reduced",
"clustered",
"nonempty",
"dungeon",
"lottery"
],
@@ -37,6 +41,7 @@
"width": 35
}
},
"colorizepots": { "type": "checkbox" },
"dropshuffle": { "type": "checkbox" },
"potshuffle": { "type": "checkbox" },
"experimental": { "type": "checkbox" },

View File

@@ -98,6 +98,7 @@ SETTINGSTOPROCESS = {
"keydropshuffle": "keydropshuffle",
"dropshuffle": "dropshuffle",
"pottery": "pottery",
"colorizepots": "colorizepots",
"potshuffle": "shufflepots",
"experimental": "experimental",
"dungeon_counters": "dungeon_counters",

View File

@@ -0,0 +1,58 @@
from RoomData import DoorKind, Position
from source.dungeon.RoomObject import RoomObject, DoorObject
class Room:
def __init__(self, layout, layer1, layer2, doors):
self.layout = layout
self.layer1 = layer1
self.layer2 = layer2
self.doors = doors
def write_to_rom(self, address, rom):
rom.write_bytes(address, self.layout)
address += 2
for obj in self.layer1:
rom.write_bytes(address, obj.data)
address += 3
rom.write_bytes(address, [0xFF, 0xFF])
address += 2
for obj in self.layer2:
rom.write_bytes(address, obj.data)
address += 3
rom.write_bytes(address, [0xFF, 0xFF, 0xF0, 0xFF])
address += 4
for door in self.doors:
rom.write_bytes(address, door.get_bytes())
address += 2
rom.write_bytes(address, [0xFF, 0xFF])
return address + 2 # where the data ended
Room0127 = Room([0xE1, 0x00],
[RoomObject(0x0AB600, [0xFE, 0x89, 0x00]),
RoomObject(0x0AB603, [0xA2, 0xA1, 0x61]),
RoomObject(0x0AB606, [0xFE, 0x8E, 0x81]),
RoomObject(0x0AB609, [0xFF, 0x49, 0x02]),
RoomObject(0x0AB60C, [0xD2, 0xA1, 0x62]),
RoomObject(0x0AB60F, [0xFF, 0x4E, 0x83]),
RoomObject(0x0AB612, [0x20, 0xB3, 0xDD]),
RoomObject(0x0AB615, [0x50, 0xB3, 0xDD]),
RoomObject(0x0AB618, [0x33, 0xCB, 0xFA]),
RoomObject(0x0AB61B, [0x3B, 0xCB, 0xFA]),
RoomObject(0x0AB61E, [0x43, 0xCB, 0xFA]),
RoomObject(0x0AB621, [0x4B, 0xCB, 0xFA]),
RoomObject(0x0AB624, [0xBF, 0x94, 0xF9]),
RoomObject(0x0AB627, [0xB3, 0xB3, 0xFA]),
RoomObject(0x0AB62A, [0xCB, 0xB3, 0xFA]),
RoomObject(0x0AB62D, [0xAD, 0xC8, 0xDF]),
RoomObject(0x0AB630, [0xC4, 0xC8, 0xDF]),
RoomObject(0x0AB633, [0xB3, 0xE3, 0xFA]),
RoomObject(0x0AB636, [0xCB, 0xE3, 0xFA]),
RoomObject(0x0AB639, [0x81, 0x93, 0xC0]),
RoomObject(0x0AB63C, [0x81, 0xD2, 0xC0]),
RoomObject(0x0AB63F, [0xE1, 0x93, 0xC0]),
RoomObject(0x0AB642, [0xE1, 0xD2, 0xC0])],
[], [DoorObject(Position.SouthW, DoorKind.CaveEntrance),
DoorObject(Position.SouthE, DoorKind.CaveEntrance)])

View File

@@ -0,0 +1,34 @@
from Utils import snes_to_pc
# Subtype 3 object (0x2xx by jpdasm id - see bank 01)
# B
Normal_Pot = (0xFA, 3, 3)
Shuffled_Pot = (0xFB, 0, 0) # formerly weird pot, or black diagonal thing
class RoomObject:
def __init__(self, address, data):
self.address = address
self.data = data
def change_type(self, new_type):
type_id, datum_a, datum_b = new_type
if 0xF8 <= type_id < 0xFC: # sub type 3
self.data = (self.data[0] & 0xFC) | datum_a, (self.data[1] & 0xFC) | datum_b, type_id
else:
pass # not yet implemented
def write_to_rom(self, rom):
rom.write_bytes(snes_to_pc(self.address), self.data)
class DoorObject:
def __init__(self, pos, kind):
self.pos = pos
self.kind = kind
def get_bytes(self):
return [self.pos.value, self.kind.value]

View File

@@ -70,7 +70,7 @@ def create_item_pool_config(world):
if world.dropshuffle[player]:
for item, locs in keydrop_vanilla_mapping.items():
config.static_placement[player][item].extend(locs)
if world.pottery[player] != 'none':
if world.pottery[player] not in ['none', 'cave']:
for item, locs in potkeys_vanilla_mapping.items():
config.static_placement[player][item].extend(locs)
if world.pottery[player] in ['lottery', 'cave', 'dungeon']: