Merge pull request #63 from aerinon/DoorDevUnstable

Door dev unstable moving to stable
This commit is contained in:
aerinon
2020-09-17 14:58:25 -06:00
committed by GitHub
46 changed files with 3712 additions and 1678 deletions
+4 -4
View File
@@ -220,7 +220,7 @@ jobs:
body: ${{ steps.release_notes.outputs.body }} body: ${{ steps.release_notes.outputs.body }}
draft: true draft: true
prerelease: true prerelease: true
if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorDev') if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease')
# upload linux archive asset # upload linux archive asset
- name: Upload Linux Archive Asset - name: Upload Linux Archive Asset
id: upload-linux-asset id: upload-linux-asset
@@ -232,7 +232,7 @@ jobs:
asset_path: ../deploy/linux/ALttPDoorRandomizer.tar.gz asset_path: ../deploy/linux/ALttPDoorRandomizer.tar.gz
asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-linux-bionic.tar.gz asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-linux-bionic.tar.gz
asset_content_type: application/gzip asset_content_type: application/gzip
if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorDev') if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease')
# upload macos archive asset # upload macos archive asset
- name: Upload MacOS Archive Asset - name: Upload MacOS Archive Asset
id: upload-macos-asset id: upload-macos-asset
@@ -244,7 +244,7 @@ jobs:
asset_path: ../deploy/macos/ALttPDoorRandomizer.tar.gz asset_path: ../deploy/macos/ALttPDoorRandomizer.tar.gz
asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-osx.tar.gz asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-osx.tar.gz
asset_content_type: application/gzip asset_content_type: application/gzip
if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorDev') if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease')
# upload windows archive asset # upload windows archive asset
- name: Upload Windows Archive Asset - name: Upload Windows Archive Asset
id: upload-windows-asset id: upload-windows-asset
@@ -256,4 +256,4 @@ jobs:
asset_path: ../deploy/windows/ALttPDoorRandomizer.zip asset_path: ../deploy/windows/ALttPDoorRandomizer.zip
asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-windows.zip asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-windows.zip
asset_content_type: application/zip asset_content_type: application/zip
if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorDev') if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease')
+221 -160
View File
@@ -1,23 +1,31 @@
import copy import copy
from enum import Enum, unique, Flag
import logging
import json import json
from collections import OrderedDict, deque, defaultdict import logging
from collections import OrderedDict, Counter, deque, defaultdict
from enum import Enum, unique
try:
from fast_enum import FastEnum
except ImportError:
from enum import Flag
FastEnum = Flag
from source.classes.BabelFish import BabelFish from source.classes.BabelFish import BabelFish
from EntranceShuffle import door_addresses from EntranceShuffle import door_addresses, indirect_connections
from _vendor.collections_extended import bag
from Utils import int16_as_bytes from Utils import int16_as_bytes
from Tables import normal_offset_table, spiral_offset_table, multiply_lookup, divisor_lookup from Tables import normal_offset_table, spiral_offset_table, multiply_lookup, divisor_lookup
from RoomData import Room from RoomData import Room
class World(object): class World(object):
def __init__(self, players, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, progressive, goal, algorithm, accessibility, shuffle_ganon, retro, custom, customitemarray, hints): def __init__(self, players, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments,
timer, progressive, goal, algorithm, accessibility, shuffle_ganon, retro, custom, customitemarray, hints):
self.players = players self.players = players
self.teams = 1 self.teams = 1
self.shuffle = shuffle.copy() self.shuffle = shuffle.copy()
self.doorShuffle = doorShuffle.copy() self.doorShuffle = doorShuffle.copy()
self.intensity = {}
self.logic = logic.copy() self.logic = logic.copy()
self.mode = mode.copy() self.mode = mode.copy()
self.swords = swords.copy() self.swords = swords.copy()
@@ -126,6 +134,12 @@ class World(object):
for region in regions if regions else self.regions: for region in regions if regions else self.regions:
region.world = self region.world = self
self._region_cache[region.player][region.name] = region self._region_cache[region.player][region.name] = region
for exit in region.exits:
self._entrance_cache[(exit.name, exit.player)] = exit
def initialize_doors(self, doors):
for door in doors:
self._door_cache[(door.name, door.player)] = door
def get_regions(self, player=None): def get_regions(self, player=None):
return self.regions if player is None else self._region_cache[player].values() return self.regions if player is None else self._region_cache[player].values()
@@ -235,41 +249,41 @@ class World(object):
if ret.has('Golden Sword', item.player): if ret.has('Golden Sword', item.player):
pass pass
elif ret.has('Tempered Sword', item.player) and self.difficulty_requirements[item.player].progressive_sword_limit >= 4: elif ret.has('Tempered Sword', item.player) and self.difficulty_requirements[item.player].progressive_sword_limit >= 4:
ret.prog_items.add(('Golden Sword', item.player)) ret.prog_items['Golden Sword', item.player] += 1
elif ret.has('Master Sword', item.player) and self.difficulty_requirements[item.player].progressive_sword_limit >= 3: elif ret.has('Master Sword', item.player) and self.difficulty_requirements[item.player].progressive_sword_limit >= 3:
ret.prog_items.add(('Tempered Sword', item.player)) ret.prog_items['Tempered Sword', item.player] += 1
elif ret.has('Fighter Sword', item.player) and self.difficulty_requirements[item.player].progressive_sword_limit >= 2: elif ret.has('Fighter Sword', item.player) and self.difficulty_requirements[item.player].progressive_sword_limit >= 2:
ret.prog_items.add(('Master Sword', item.player)) ret.prog_items['Master Sword', item.player] += 1
elif self.difficulty_requirements[item.player].progressive_sword_limit >= 1: elif self.difficulty_requirements[item.player].progressive_sword_limit >= 1:
ret.prog_items.add(('Fighter Sword', item.player)) ret.prog_items['Fighter Sword', item.player] += 1
elif 'Glove' in item.name: elif 'Glove' in item.name:
if ret.has('Titans Mitts', item.player): if ret.has('Titans Mitts', item.player):
pass pass
elif ret.has('Power Glove', item.player): elif ret.has('Power Glove', item.player):
ret.prog_items.add(('Titans Mitts', item.player)) ret.prog_items['Titans Mitts', item.player] += 1
else: else:
ret.prog_items.add(('Power Glove', item.player)) ret.prog_items['Power Glove', item.player] += 1
elif 'Shield' in item.name: elif 'Shield' in item.name:
if ret.has('Mirror Shield', item.player): if ret.has('Mirror Shield', item.player):
pass pass
elif ret.has('Red Shield', item.player) and self.difficulty_requirements[item.player].progressive_shield_limit >= 3: elif ret.has('Red Shield', item.player) and self.difficulty_requirements[item.player].progressive_shield_limit >= 3:
ret.prog_items.add(('Mirror Shield', item.player)) ret.prog_items['Mirror Shield', item.player] += 1
elif ret.has('Blue Shield', item.player) and self.difficulty_requirements[item.player].progressive_shield_limit >= 2: elif ret.has('Blue Shield', item.player) and self.difficulty_requirements[item.player].progressive_shield_limit >= 2:
ret.prog_items.add(('Red Shield', item.player)) ret.prog_items['Red Shield', item.player] += 1
elif self.difficulty_requirements[item.player].progressive_shield_limit >= 1: elif self.difficulty_requirements[item.player].progressive_shield_limit >= 1:
ret.prog_items.add(('Blue Shield', item.player)) ret.prog_items['Blue Shield', item.player] += 1
elif 'Bow' in item.name: elif 'Bow' in item.name:
if ret.has('Silver Arrows', item.player): if ret.has('Silver Arrows', item.player):
pass pass
elif ret.has('Bow', item.player) and self.difficulty_requirements[item.player].progressive_bow_limit >= 2: elif ret.has('Bow', item.player) and self.difficulty_requirements[item.player].progressive_bow_limit >= 2:
ret.prog_items.add(('Silver Arrows', item.player)) ret.prog_items['Silver Arrows', item.player] += 1
elif self.difficulty_requirements[item.player].progressive_bow_limit >= 1: elif self.difficulty_requirements[item.player].progressive_bow_limit >= 1:
ret.prog_items.add(('Bow', item.player)) ret.prog_items['Bow', item.player] += 1
elif item.name.startswith('Bottle'): elif item.name.startswith('Bottle'):
if ret.bottle_count(item.player) < self.difficulty_requirements[item.player].progressive_bottle_limit: if ret.bottle_count(item.player) < self.difficulty_requirements[item.player].progressive_bottle_limit:
ret.prog_items.add((item.name, item.player)) ret.prog_items[item.name, item.player] += 1
elif item.advancement or item.smallkey or item.bigkey: elif item.advancement or item.smallkey or item.bigkey:
ret.prog_items.add((item.name, item.player)) ret.prog_items[item.name, item.player] += 1
for item in self.itempool: for item in self.itempool:
soft_collect(item) soft_collect(item)
@@ -384,7 +398,6 @@ class World(object):
prog_locations = [location for location in self.get_locations() if location.item is not None and (location.item.advancement or location.event) and location not in state.locations_checked] prog_locations = [location for location in self.get_locations() if location.item is not None and (location.item.advancement or location.event) and location not in state.locations_checked]
while prog_locations: while prog_locations:
state.sweep_for_crystal_access()
sphere = [] sphere = []
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres # build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
for location in prog_locations: for location in prog_locations:
@@ -408,11 +421,10 @@ class World(object):
class CollectionState(object): class CollectionState(object):
def __init__(self, parent): def __init__(self, parent):
self.prog_items = bag() self.prog_items = Counter()
self.world = parent self.world = parent
self.reachable_regions = {player: set() for player in range(1, parent.players + 1)} self.reachable_regions = {player: dict() for player in range(1, parent.players + 1)}
self.colored_regions = {player: {} for player in range(1, parent.players + 1)} self.blocked_connections = {player: dict() for player in range(1, parent.players + 1)}
self.blocked_color_regions = {player: set() for player in range(1, parent.players + 1)}
self.events = [] self.events = []
self.path = {} self.path = {}
self.locations_checked = set() self.locations_checked = set()
@@ -421,88 +433,71 @@ class CollectionState(object):
self.collect(item, True) self.collect(item, True)
def update_reachable_regions(self, player): def update_reachable_regions(self, player):
player_regions = self.world.get_regions(player)
self.stale[player] = False self.stale[player] = False
rrp = self.reachable_regions[player] rrp = self.reachable_regions[player]
ccr = self.colored_regions[player] bc = self.blocked_connections[player]
blocked = self.blocked_color_regions[player]
new_regions = True
reachable_regions_count = len(rrp)
while new_regions:
player_regions = [region for region in player_regions if region not in rrp]
for candidate in player_regions:
if candidate.can_reach_private(self):
rrp.add(candidate)
if candidate.type == RegionType.Dungeon:
c_switch_present = False
for ext in candidate.exits:
door = self.world.check_for_door(ext.name, player)
if door is not None and door.crystal == CrystalBarrier.Either:
c_switch_present = True
break
if c_switch_present:
ccr[candidate] = CrystalBarrier.Either
self.spread_crystal_access(candidate, CrystalBarrier.Either, rrp, ccr, player)
for ext in candidate.exits:
connect = ext.connected_region
if connect in rrp and not ext.can_reach(self):
blocked.add(candidate)
else:
color_type = CrystalBarrier.Null
for entrance in candidate.entrances:
if entrance.parent_region in rrp:
if entrance.can_reach(self):
door = self.world.check_for_door(entrance.name, player)
if door is None or entrance.parent_region.type != RegionType.Dungeon:
color_type |= CrystalBarrier.Orange
elif entrance.parent_region in ccr.keys():
color_type |= (ccr[entrance.parent_region] & (door.crystal or CrystalBarrier.Either))
else:
blocked.add(entrance.parent_region)
if color_type:
ccr[candidate] = color_type
for ext in candidate.exits:
connect = ext.connected_region
if connect in rrp and connect in ccr:
door = self.world.check_for_door(ext.name, player)
if door is not None and not door.blocked:
if ext.can_reach(self):
new_color = ccr[connect] | (ccr[candidate] & (door.crystal or CrystalBarrier.Either))
if new_color != ccr[connect]:
self.spread_crystal_access(candidate, new_color, rrp, ccr, player)
else:
blocked.add(candidate)
new_regions = len(rrp) > reachable_regions_count
reachable_regions_count = len(rrp)
def spread_crystal_access(self, region, crystal, rrp, ccr, player): # init on first call - this can't be done on construction since the regions don't exist yet
queue = deque([(region, crystal)]) start = self.world.get_region('Menu', player)
visited = set() if not start in rrp:
updated = False rrp[start] = CrystalBarrier.Orange
while len(queue) > 0: for exit in start.exits:
region, crystal = queue.popleft() bc[exit] = CrystalBarrier.Orange
visited.add(region)
for ext in region.exits: queue = deque(self.blocked_connections[player].items())
connect = ext.connected_region
if connect is not None and connect.type == RegionType.Dungeon: # run BFS on all connections, and keep track of those blocked by missing items
if connect not in visited and connect in rrp and connect in ccr: while True:
if ext.can_reach(self): try:
door = self.world.check_for_door(ext.name, player) connection, crystal_state = queue.popleft()
new_region = connection.connected_region
if new_region is None or new_region in rrp and (new_region.type != RegionType.Dungeon or (rrp[new_region] & crystal_state) == crystal_state):
bc.pop(connection, None)
elif connection.can_reach(self):
if new_region.type == RegionType.Dungeon:
new_crystal_state = crystal_state
for exit in new_region.exits:
door = exit.door
if door is not None and door.crystal == CrystalBarrier.Either:
new_crystal_state = CrystalBarrier.Either
break
if new_region in rrp:
new_crystal_state |= rrp[new_region]
rrp[new_region] = new_crystal_state
for exit in new_region.exits:
door = exit.door
if door is not None and not door.blocked: if door is not None and not door.blocked:
current_crystal = ccr[connect] door_crystal_state = new_crystal_state & (door.crystal or CrystalBarrier.Either)
new_crystal = current_crystal | (crystal & (door.crystal or CrystalBarrier.Either)) bc[exit] = door_crystal_state
if current_crystal != new_crystal: queue.append((exit, door_crystal_state))
updated = True elif door is None:
ccr[connect] = new_crystal queue.append((exit, new_crystal_state))
queue.append((connect, new_crystal)) else:
return updated new_crystal_state = CrystalBarrier.Orange
rrp[new_region] = new_crystal_state
bc.pop(connection, None)
for exit in new_region.exits:
bc[exit] = new_crystal_state
queue.append((exit, new_crystal_state))
self.path[new_region] = (new_region.name, self.path.get(connection, None))
# Retry connections if the new region can unblock them
if new_region.name in indirect_connections:
new_entrance = self.world.get_entrance(indirect_connections[new_region.name], player)
if new_entrance in bc and new_entrance not in queue and new_entrance.parent_region in rrp:
queue.append((new_entrance, rrp[new_entrance.parent_region]))
except IndexError:
break
def copy(self): def copy(self):
ret = CollectionState(self.world) ret = CollectionState(self.world)
ret.prog_items = self.prog_items.copy() ret.prog_items = self.prog_items.copy()
ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in range(1, self.world.players + 1)} ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in range(1, self.world.players + 1)}
ret.colored_regions = {player: copy.copy(self.colored_regions[player]) for player in range(1, self.world.players + 1)} ret.blocked_connections = {player: copy.copy(self.blocked_connections[player]) for player in range(1, self.world.players + 1)}
ret.blocked_color_regions = {player: copy.copy(self.blocked_color_regions[player]) for player in range(1, self.world.players + 1)}
ret.events = copy.copy(self.events) ret.events = copy.copy(self.events)
ret.path = copy.copy(self.path) ret.path = copy.copy(self.path)
ret.locations_checked = copy.copy(self.locations_checked) ret.locations_checked = copy.copy(self.locations_checked)
@@ -523,19 +518,6 @@ class CollectionState(object):
return spot.can_reach(self) return spot.can_reach(self)
def sweep_for_crystal_access(self):
for player, rrp in self.reachable_regions.items():
updated = True
while updated:
if self.stale[player]:
self.update_reachable_regions(player)
updated = False
dungeon_regions = self.blocked_color_regions[player]
ccr = self.colored_regions[player]
for region in dungeon_regions.copy():
if region in ccr.keys():
updated |= self.spread_crystal_access(region, ccr[region], rrp, ccr, player)
self.stale[player] = updated
def sweep_for_events(self, key_only=False, locations=None): def sweep_for_events(self, key_only=False, locations=None):
# this may need improvement # this may need improvement
@@ -554,18 +536,13 @@ class CollectionState(object):
self.collect(event.item, True, event) self.collect(event.item, True, event)
new_locations = len(reachable_events) > checked_locations new_locations = len(reachable_events) > checked_locations
checked_locations = len(reachable_events) checked_locations = len(reachable_events)
if new_locations:
self.sweep_for_crystal_access()
def can_reach_blue(self, region, player): def can_reach_blue(self, region, player):
if region not in self.colored_regions[player].keys(): return region in self.reachable_regions[player] and self.reachable_regions[player][region] in [CrystalBarrier.Blue, CrystalBarrier.Either]
return False
return self.colored_regions[player][region] in [CrystalBarrier.Blue, CrystalBarrier.Either]
def can_reach_orange(self, region, player): def can_reach_orange(self, region, player):
if region not in self.colored_regions[player].keys(): return region in self.reachable_regions[player] and self.reachable_regions[player][region] in [CrystalBarrier.Orange, CrystalBarrier.Either]
return False
return self.colored_regions[player][region] in [CrystalBarrier.Orange, CrystalBarrier.Either]
def _do_not_flood_the_keys(self, reachable_events): def _do_not_flood_the_keys(self, reachable_events):
adjusted_checks = list(reachable_events) adjusted_checks = list(reachable_events)
@@ -584,14 +561,14 @@ class CollectionState(object):
def has(self, item, player, count=1): def has(self, item, player, count=1):
if count == 1: if count == 1:
return (item, player) in self.prog_items return (item, player) in self.prog_items
return self.prog_items.count((item, player)) >= count return self.prog_items[item, player] >= count
def has_key(self, item, player, count=1): def has_key(self, item, player, count=1):
if self.world.retro[player]: if self.world.retro[player]:
return self.can_buy_unlimited('Small Key (Universal)', player) return self.can_buy_unlimited('Small Key (Universal)', player)
if count == 1: if count == 1:
return (item, player) in self.prog_items return (item, player) in self.prog_items
return self.prog_items.count((item, player)) >= count return self.prog_items[item, player] >= count
def can_buy_unlimited(self, item, player): def can_buy_unlimited(self, item, player):
for shop in self.world.shops: for shop in self.world.shops:
@@ -600,7 +577,7 @@ class CollectionState(object):
return False return False
def item_count(self, item, player): def item_count(self, item, player):
return self.prog_items.count((item, player)) return self.prog_items[item, player]
def has_crystals(self, count, player): def has_crystals(self, count, player):
crystals = ['Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'] crystals = ['Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7']
@@ -634,9 +611,9 @@ class CollectionState(object):
def can_extend_magic(self, player, smallmagic=16, fullrefill=False): #This reflects the total magic Link has, not the total extra he has. def can_extend_magic(self, player, smallmagic=16, fullrefill=False): #This reflects the total magic Link has, not the total extra he has.
basemagic = 8 basemagic = 8
if self.has('Quarter Magic', player): if self.has('Magic Upgrade (1/4)', player):
basemagic = 32 basemagic = 32
elif self.has('Half Magic', player): elif self.has('Magic Upgrade (1/2)', player):
basemagic = 16 basemagic = 16
if self.can_buy_unlimited('Green Potion', player) or self.can_buy_unlimited('Blue Potion', player): if self.can_buy_unlimited('Green Potion', player) or self.can_buy_unlimited('Blue Potion', player):
if self.world.difficulty_adjustments[player] == 'hard' and not fullrefill: if self.world.difficulty_adjustments[player] == 'hard' and not fullrefill:
@@ -657,9 +634,8 @@ class CollectionState(object):
def can_shoot_arrows(self, player): def can_shoot_arrows(self, player):
if self.world.retro[player]: if self.world.retro[player]:
#TODO: need to decide how we want to handle wooden arrows longer-term (a can-buy-a check, or via dynamic shop location) #todo: Non-progressive silvers grant wooden arrows, but progressive bows do not. Always require shop arrows to be safe
#FIXME: Should do something about hard+ ganon only silvers. For the moment, i believe they effective grant wooden, so we are safe return self.has('Bow', player) and self.can_buy_unlimited('Single Arrow', player)
return self.has('Bow', player) and (self.has('Silver Arrows', player) or self.can_buy_unlimited('Single Arrow', player))
return self.has('Bow', player) return self.has('Bow', player)
def can_get_good_bee(self, player): def can_get_good_bee(self, player):
@@ -734,63 +710,63 @@ class CollectionState(object):
if self.has('Golden Sword', item.player): if self.has('Golden Sword', item.player):
pass pass
elif self.has('Tempered Sword', item.player) and self.world.difficulty_requirements[item.player].progressive_sword_limit >= 4: elif self.has('Tempered Sword', item.player) and self.world.difficulty_requirements[item.player].progressive_sword_limit >= 4:
self.prog_items.add(('Golden Sword', item.player)) self.prog_items['Golden Sword', item.player] += 1
changed = True changed = True
elif self.has('Master Sword', item.player) and self.world.difficulty_requirements[item.player].progressive_sword_limit >= 3: elif self.has('Master Sword', item.player) and self.world.difficulty_requirements[item.player].progressive_sword_limit >= 3:
self.prog_items.add(('Tempered Sword', item.player)) self.prog_items['Tempered Sword', item.player] += 1
changed = True changed = True
elif self.has('Fighter Sword', item.player) and self.world.difficulty_requirements[item.player].progressive_sword_limit >= 2: elif self.has('Fighter Sword', item.player) and self.world.difficulty_requirements[item.player].progressive_sword_limit >= 2:
self.prog_items.add(('Master Sword', item.player)) self.prog_items['Master Sword', item.player] += 1
changed = True changed = True
elif self.world.difficulty_requirements[item.player].progressive_sword_limit >= 1: elif self.world.difficulty_requirements[item.player].progressive_sword_limit >= 1:
self.prog_items.add(('Fighter Sword', item.player)) self.prog_items['Fighter Sword', item.player] += 1
changed = True changed = True
elif 'Glove' in item.name: elif 'Glove' in item.name:
if self.has('Titans Mitts', item.player): if self.has('Titans Mitts', item.player):
pass pass
elif self.has('Power Glove', item.player): elif self.has('Power Glove', item.player):
self.prog_items.add(('Titans Mitts', item.player)) self.prog_items['Titans Mitts', item.player] += 1
changed = True changed = True
else: else:
self.prog_items.add(('Power Glove', item.player)) self.prog_items['Power Glove', item.player] += 1
changed = True changed = True
elif 'Shield' in item.name: elif 'Shield' in item.name:
if self.has('Mirror Shield', item.player): if self.has('Mirror Shield', item.player):
pass pass
elif self.has('Red Shield', item.player) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 3: elif self.has('Red Shield', item.player) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 3:
self.prog_items.add(('Mirror Shield', item.player)) self.prog_items['Mirror Shield', item.player] += 1
changed = True changed = True
elif self.has('Blue Shield', item.player) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 2: elif self.has('Blue Shield', item.player) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 2:
self.prog_items.add(('Red Shield', item.player)) self.prog_items['Red Shield', item.player] += 1
changed = True changed = True
elif self.world.difficulty_requirements[item.player].progressive_shield_limit >= 1: elif self.world.difficulty_requirements[item.player].progressive_shield_limit >= 1:
self.prog_items.add(('Blue Shield', item.player)) self.prog_items['Blue Shield', item.player] += 1
changed = True changed = True
elif 'Bow' in item.name: elif 'Bow' in item.name:
if self.has('Silver Arrows', item.player): if self.has('Silver Arrows', item.player):
pass pass
elif self.has('Bow', item.player): elif self.has('Bow', item.player):
self.prog_items.add(('Silver Arrows', item.player)) self.prog_items['Silver Arrows', item.player] += 1
changed = True changed = True
else: else:
self.prog_items.add(('Bow', item.player)) self.prog_items['Bow', item.player] += 1
changed = True changed = True
elif 'Armor' in item.name: elif 'Armor' in item.name:
if self.has('Red Mail', item.player): if self.has('Red Mail', item.player):
pass pass
elif self.has('Blue Mail', item.player): elif self.has('Blue Mail', item.player):
self.prog_items.add(('Red Mail', item.player)) self.prog_items['Red Mail', item.player] += 1
changed = True changed = True
else: else:
self.prog_items.add(('Blue Mail', item.player)) self.prog_items['Blue Mail', item.player] += 1
changed = True changed = True
elif item.name.startswith('Bottle'): elif item.name.startswith('Bottle'):
if self.bottle_count(item.player) < self.world.difficulty_requirements[item.player].progressive_bottle_limit: if self.bottle_count(item.player) < self.world.difficulty_requirements[item.player].progressive_bottle_limit:
self.prog_items.add((item.name, item.player)) self.prog_items[item.name, item.player] += 1
changed = True changed = True
elif event or item.advancement: elif event or item.advancement:
self.prog_items.add((item.name, item.player)) self.prog_items[item.name, item.player] += 1
changed = True changed = True
self.stale[item.player] = True self.stale[item.player] = True
@@ -839,13 +815,13 @@ class CollectionState(object):
to_remove = None to_remove = None
if to_remove is not None: if to_remove is not None:
try:
self.prog_items.remove((to_remove, item.player))
except ValueError:
return
self.prog_items[to_remove, item.player] -= 1
if self.prog_items[to_remove, item.player] < 1:
del (self.prog_items[to_remove, item.player])
# invalidate caches, nothing can be trusted anymore now # invalidate caches, nothing can be trusted anymore now
self.reachable_regions[item.player] = set() self.reachable_regions[item.player] = dict()
self.blocked_connections[item.player] = dict()
self.stale[item.player] = True self.stale[item.player] = True
def __getattr__(self, item): def __getattr__(self, item):
@@ -935,10 +911,11 @@ class Entrance(object):
self.access_rule = lambda state: True self.access_rule = lambda state: True
self.player = player self.player = player
self.door = None self.door = None
self.hide_path = False
def can_reach(self, state): def can_reach(self, state):
if self.parent_region.can_reach(state) and self.access_rule(state): if self.parent_region.can_reach(state) and self.access_rule(state):
if not self in state.path: if not self.hide_path and not self in state.path:
state.path[self] = (self.name, state.path.get(self.parent_region, (self.parent_region.name, None))) state.path[self] = (self.name, state.path.get(self.parent_region, (self.parent_region.name, None)))
return True return True
@@ -1031,6 +1008,31 @@ class Direction(Enum):
Down = 5 Down = 5
@unique
class Hook(Enum):
North = 0
West = 1
South = 2
East = 3
Stairs = 4
hook_dir_map = {
Direction.North: Hook.North,
Direction.South: Hook.South,
Direction.West: Hook.West,
Direction.East: Hook.East,
}
def hook_from_door(door):
if door.type == DoorType.SpiralStairs:
return Hook.Stairs
if door.type in [DoorType.Normal, DoorType.Open, DoorType.StraightStairs]:
return hook_dir_map[door.direction]
return None
class Polarity: class Polarity:
def __init__(self): def __init__(self):
self.vector = [0, 0, 0] self.vector = [0, 0, 0]
@@ -1058,6 +1060,16 @@ class Polarity:
return False return False
return True return True
def __hash__(self):
h = 17
spot = self.vector[0]
h *= 31 + (spot if spot >= 0 else spot + 100)
spot = self.vector[1]
h *= 43 + (spot if spot >= 0 else spot + 100)
spot = self.vector[2]
h *= 73 + (spot if spot >= 0 else spot + 100)
return h
def is_neutral(self): def is_neutral(self):
for i in range(len(self.vector)): for i in range(len(self.vector)):
if self.vector[i] != 0: if self.vector[i] != 0:
@@ -1076,6 +1088,12 @@ class Polarity:
result += abs(self.vector[i]) result += abs(self.vector[i])
return result return result
def __str__(self):
return str(self.__unicode__())
def __unicode__(self):
return f'{self.vector}'
pol_idx = { pol_idx = {
Direction.North: (0, 'Pos'), Direction.North: (0, 'Pos'),
@@ -1104,14 +1122,15 @@ pol_comp = {
'Mod': lambda x: 0 if x == 0 else 1 'Mod': lambda x: 0 if x == 0 else 1
} }
@unique @unique
class PolSlot(Enum): class PolSlot(Enum):
NorthSouth = 0 NorthSouth = 0
EastWest = 1 EastWest = 1
Stairs = 2 Stairs = 2
@unique
class CrystalBarrier(Flag): class CrystalBarrier(FastEnum):
Null = 0 # no special requirement Null = 0 # no special requirement
Blue = 1 # blue must be down and explore state set to Blue Blue = 1 # blue must be down and explore state set to Blue
Orange = 2 # orange must be down and explore state set to Orange Orange = 2 # orange must be down and explore state set to Orange
@@ -1161,23 +1180,25 @@ class Door(object):
entrance.door = self entrance.door = self
def getAddress(self): def getAddress(self):
if self.type == DoorType.Normal: if self.type in [DoorType.Normal, DoorType.StraightStairs]:
return 0x13A000 + normal_offset_table[self.roomIndex] * 24 + (self.doorIndex + self.direction.value * 3) * 2 return 0x13A000 + normal_offset_table[self.roomIndex] * 24 + (self.doorIndex + self.direction.value * 3) * 2
elif self.type == DoorType.SpiralStairs: elif self.type == DoorType.SpiralStairs:
return 0x13B000 + (spiral_offset_table[self.roomIndex] + self.doorIndex) * 4 return 0x13B000 + (spiral_offset_table[self.roomIndex] + self.doorIndex) * 4
elif self.type == DoorType.Open: elif self.type == DoorType.Open:
base_address = { base_address = {
Direction.North: 0x13C500, Direction.North: 0x13C500,
Direction.South: 0x13C533, Direction.South: 0x13C521,
Direction.West: 0x13C566, Direction.West: 0x13C542,
Direction.East: 0x13C581, Direction.East: 0x13C55D,
} }
return base_address[self.direction] + self.edge_id * 3 return base_address[self.direction] + self.edge_id * 3
def getTarget(self, src): def getTarget(self, src):
if self.type == DoorType.Normal: if self.type in [DoorType.Normal, DoorType.StraightStairs]:
bitmask = 4 * (self.layer ^ 1 if src.toggle else self.layer) bitmask = 4 * (self.layer ^ 1 if src.toggle else self.layer)
bitmask += 0x08 * int(self.trapFlag) bitmask += 0x08 * int(self.trapFlag)
if src.type == DoorType.StraightStairs:
bitmask += 0x40
return [self.roomIndex, bitmask + self.doorIndex] return [self.roomIndex, bitmask + self.doorIndex]
if self.type == DoorType.SpiralStairs: if self.type == DoorType.SpiralStairs:
bitmask = int(self.layer) << 2 bitmask = int(self.layer) << 2
@@ -1187,16 +1208,26 @@ class Door(object):
return [self.roomIndex, bitmask + self.quadrant, self.shiftX, self.shiftY] return [self.roomIndex, bitmask + self.quadrant, self.shiftX, self.shiftY]
if self.type == DoorType.Open: if self.type == DoorType.Open:
bitmask = self.edge_id bitmask = self.edge_id
bitmask += 0x10 * self.layer bitmask += 0x10 * (self.layer ^ 1 if src.toggle else self.layer)
bitmask += 0x20 * self.quadrant
bitmask += 0x80 bitmask += 0x80
if src.type == DoorType.StraightStairs:
bitmask += 0x40
if src.type == DoorType.Open: if src.type == DoorType.Open:
bitmask += 0x20 * self.quadrant
fraction = 0x10 * multiply_lookup[src.edge_width][self.edge_width] fraction = 0x10 * multiply_lookup[src.edge_width][self.edge_width]
fraction += divisor_lookup[src.edge_width][self.edge_width] fraction += divisor_lookup[src.edge_width][self.edge_width]
return [self.roomIndex, bitmask, fraction] return [self.roomIndex, bitmask, fraction]
else: else:
bitmask += 0x20 * self.quad_indicator()
return [self.roomIndex, bitmask] return [self.roomIndex, bitmask]
def quad_indicator(self):
if self.direction in [Direction.North, Direction.South]:
return self.quadrant & 0x1
elif self.direction in [Direction.East, Direction.West]:
return (self.quadrant & 0x2) >> 1
return 0
def dir(self, direction, room, doorIndex, layer): def dir(self, direction, room, doorIndex, layer):
self.direction = direction self.direction = direction
self.roomIndex = room self.roomIndex = room
@@ -1294,6 +1325,7 @@ class Sector(object):
self.branch_factor = None self.branch_factor = None
self.dead_end_cnt = None self.dead_end_cnt = None
self.entrance_sector = None self.entrance_sector = None
self.destination_entrance = False
self.equations = None self.equations = None
def region_set(self): def region_set(self):
@@ -1315,6 +1347,13 @@ class Sector(object):
magnitude[idx] = magnitude[idx] + 1 magnitude[idx] = magnitude[idx] + 1
return magnitude return magnitude
def hook_magnitude(self):
magnitude = [0] * len(Hook)
for door in self.outstanding_doors:
idx = hook_from_door(door).value
magnitude[idx] = magnitude[idx] + 1
return magnitude
def outflow(self): def outflow(self):
outflow = 0 outflow = 0
for door in self.outstanding_doors: for door in self.outstanding_doors:
@@ -1337,7 +1376,7 @@ class Sector(object):
self.branch_factor -= cnt_dead - 1 self.branch_factor -= cnt_dead - 1
for region in self.regions: for region in self.regions:
for ent in region.entrances: for ent in region.entrances:
if ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]: if ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld] or ent.parent_region.name == 'Sewer Drop':
# same sector as another entrance # same sector as another entrance
if region.name not in ['Skull Pot Circle', 'Skull Back Drop', 'Desert East Lobby', 'Desert West Lobby']: if region.name not in ['Skull Pot Circle', 'Skull Back Drop', 'Desert East Lobby', 'Desert West Lobby']:
self.branch_factor += 1 self.branch_factor += 1
@@ -1364,11 +1403,23 @@ class Sector(object):
self.entrance_sector = True self.entrance_sector = True
return self.entrance_sector return self.entrance_sector
def get_start_regions(self):
if self.is_entrance_sector():
starts = []
for region in self.regions:
for ent in region.entrances:
if ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld] or ent.parent_region.name == 'Sewer Drop':
starts.append(region)
return starts
return None
def __str__(self): def __str__(self):
return str(self.__unicode__()) return str(self.__unicode__())
def __unicode__(self): def __unicode__(self):
return '%s' % next(iter(self.region_set())) if len(self.regions) > 0:
return f'{self.regions[0].name}'
return f'{next(iter(self.region_set()))}'
class Boss(object): class Boss(object):
@@ -1619,7 +1670,7 @@ class Spoiler(object):
for index, item in enumerate(shop.inventory): for index, item in enumerate(shop.inventory):
if item is None: if item is None:
continue continue
shopdata['item_{}'.format(index)] = "{} {}".format(item['item'], item['price']) if item['price'] else item['item'] shopdata['item_{}'.format(index)] = "{} - {}".format(item['item'], item['price']) if item['price'] else item['item']
self.shops.append(shopdata) self.shops.append(shopdata)
for player in range(1, self.world.players + 1): for player in range(1, self.world.players + 1):
@@ -1654,6 +1705,7 @@ class Spoiler(object):
'goal': self.world.goal, 'goal': self.world.goal,
'shuffle': self.world.shuffle, 'shuffle': self.world.shuffle,
'door_shuffle': self.world.doorShuffle, 'door_shuffle': self.world.doorShuffle,
'intensity': self.world.intensity,
'item_pool': self.world.difficulty, 'item_pool': self.world.difficulty,
'item_functionality': self.world.difficulty_adjustments, 'item_functionality': self.world.difficulty_adjustments,
'gt_crystals': self.world.crystals_needed_for_gt, 'gt_crystals': self.world.crystals_needed_for_gt,
@@ -1716,6 +1768,7 @@ class Spoiler(object):
outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality'][player]) outfile.write('Item Functionality: %s\n' % self.metadata['item_functionality'][player])
outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'][player]) outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'][player])
outfile.write('Door Shuffle: %s\n' % self.metadata['door_shuffle'][player]) outfile.write('Door Shuffle: %s\n' % self.metadata['door_shuffle'][player])
outfile.write('Intensity: %s\n' % self.metadata['intensity'][player])
outfile.write('Crystals required for GT: %s\n' % self.metadata['gt_crystals'][player]) outfile.write('Crystals required for GT: %s\n' % self.metadata['gt_crystals'][player])
outfile.write('Crystals required for Ganon: %s\n' % self.metadata['ganon_crystals'][player]) outfile.write('Crystals required for Ganon: %s\n' % self.metadata['ganon_crystals'][player])
outfile.write('Pyramid hole pre-opened: %s\n' % ('Yes' if self.metadata['open_pyramid'][player] else 'No')) outfile.write('Pyramid hole pre-opened: %s\n' % ('Yes' if self.metadata['open_pyramid'][player] else 'No'))
@@ -1765,6 +1818,12 @@ class Spoiler(object):
outfile.write('\n\nShops:\n\n') outfile.write('\n\nShops:\n\n')
outfile.write('\n'.join("{} [{}]\n {}".format(self.world.fish.translate("meta","locations",shop['location']), shop['type'], "\n ".join(self.world.fish.translate("meta","items",item) for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops)) outfile.write('\n'.join("{} [{}]\n {}".format(self.world.fish.translate("meta","locations",shop['location']), shop['type'], "\n ".join(self.world.fish.translate("meta","items",item) for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops))
for player in range(1, self.world.players + 1):
if self.world.boss_shuffle[player] != 'none':
bossmap = self.bosses[player] if self.world.players > 1 else self.bosses
outfile.write(f'\n\nBosses ({self.world.get_player_names(player)}):\n\n')
outfile.write('\n'.join([f'{x}: {y}' for x, y in bossmap.items() if y not in ['Agahnim', 'Agahnim 2', 'Ganon']]))
# locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name # locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name
# items: Item names # items: Item names
outfile.write('\n\nPlaythrough:\n\n') outfile.write('\n\nPlaythrough:\n\n')
@@ -1773,7 +1832,9 @@ class Spoiler(object):
# locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name # locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name
# items: Item names # items: Item names
outfile.write('\n\nUnreachable Items:\n\n') outfile.write('\n\nUnreachable Items:\n\n')
outfile.write('\n'.join(['%s: %s' % (self.world.fish.translate("meta","items",unreachable.item), self.world.fish.translate("meta","locations",unreachable)) for unreachable in self.unreachables])) outfile.write('\n'.join(['%s: %s' % (self.world.fish.translate("meta", "items", unreachable.item.name),
self.world.fish.translate("meta", "locations", unreachable.name))
for unreachable in self.unreachables]))
# rooms: Change up room names; only if it's got no locations in it # rooms: Change up room names; only if it's got no locations in it
# entrances: To/From overworld; Checking w/ & w/out "Exit" and translating accordingly # entrances: To/From overworld; Checking w/ & w/out "Exit" and translating accordingly
+11 -5
View File
@@ -18,6 +18,7 @@ def ArmosKnightsDefeatRule(state, player):
# Magic amounts are probably a bit overkill # Magic amounts are probably a bit overkill
return ( return (
state.has_blunt_weapon(player) or state.has_blunt_weapon(player) or
state.can_shoot_arrows(player) or
(state.has('Cane of Somaria', player) and state.can_extend_magic(player, 10)) or (state.has('Cane of Somaria', player) and state.can_extend_magic(player, 10)) or
(state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16)) or (state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16)) or
(state.has('Ice Rod', player) and state.can_extend_magic(player, 32)) or (state.has('Ice Rod', player) and state.can_extend_magic(player, 32)) or
@@ -26,18 +27,19 @@ def ArmosKnightsDefeatRule(state, player):
state.has('Red Boomerang', player)) state.has('Red Boomerang', player))
def LanmolasDefeatRule(state, player): def LanmolasDefeatRule(state, player):
# TODO: Allow the canes here?
return ( return (
state.has_blunt_weapon(player) or state.has_blunt_weapon(player) or
state.has('Fire Rod', player) or state.has('Fire Rod', player) or
state.has('Ice Rod', player) or state.has('Ice Rod', player) or
state.has('Cane of Somaria', player) or
state.has('Cane of Byrna', player) or
state.can_shoot_arrows(player)) state.can_shoot_arrows(player))
def MoldormDefeatRule(state, player): def MoldormDefeatRule(state, player):
return state.has_blunt_weapon(player) return state.has_blunt_weapon(player)
def HelmasaurKingDefeatRule(state, player): def HelmasaurKingDefeatRule(state, player):
return state.has_blunt_weapon(player) or state.can_shoot_arrows(player) return state.has_sword(player) or state.can_shoot_arrows(player)
def ArrghusDefeatRule(state, player): def ArrghusDefeatRule(state, player):
if not state.has('Hookshot', player): if not state.has('Hookshot', player):
@@ -95,7 +97,11 @@ def VitreousDefeatRule(state, player):
def TrinexxDefeatRule(state, player): def TrinexxDefeatRule(state, player):
if not (state.has('Fire Rod', player) and state.has('Ice Rod', player)): if not (state.has('Fire Rod', player) and state.has('Ice Rod', player)):
return False return False
return state.has('Hammer', player) or state.has_beam_sword(player) or (state.has_sword(player) and state.can_extend_magic(player, 32)) return (state.has('Hammer', player) or
state.has('Golden Sword', player) or
state.has('Tempered Sword', player) or
(state.has('Master Sword', player) and state.can_extend_magic(player, 16)) or
(state.has_sword(player) and state.can_extend_magic(player, 32)))
def AgahnimDefeatRule(state, player): def AgahnimDefeatRule(state, player):
return state.has_sword(player) or state.has('Hammer', player) or state.has('Bug Catching Net', player) return state.has_sword(player) or state.has('Hammer', player) or state.has('Bug Catching Net', player)
@@ -160,7 +166,7 @@ def place_bosses(world, player):
all_bosses = sorted(boss_table.keys()) #s orted to be deterministic on older pythons all_bosses = sorted(boss_table.keys()) #s orted to be deterministic on older pythons
placeable_bosses = [boss for boss in all_bosses if boss not in ['Agahnim', 'Agahnim2', 'Ganon']] placeable_bosses = [boss for boss in all_bosses if boss not in ['Agahnim', 'Agahnim2', 'Ganon']]
if world.boss_shuffle[player] in ["basic", "normal"]: if world.boss_shuffle[player] in ["simple", "full"]:
# temporary hack for swordless kholdstare: # temporary hack for swordless kholdstare:
if world.swords[player] == 'swordless': if world.swords[player] == 'swordless':
world.get_dungeon('Ice Palace', player).boss = BossFactory('Kholdstare', player) world.get_dungeon('Ice Palace', player).boss = BossFactory('Kholdstare', player)
@@ -189,7 +195,7 @@ def place_bosses(world, player):
loc_text = loc + ' (' + level + ')' loc_text = loc + ' (' + level + ')'
logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text) logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text)
world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player) world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player)
elif world.boss_shuffle[player] == "chaos": #all bosses chosen at random elif world.boss_shuffle[player] == "random": #all bosses chosen at random
for [loc, level] in boss_locations: for [loc, level] in boss_locations:
loc_text = loc + (' ('+level+')' if level else '') loc_text = loc + (' ('+level+')' if level else '')
try: try:
+2 -1
View File
@@ -90,7 +90,7 @@ def parse_cli(argv, no_defaults=False):
playerargs = parse_cli(shlex.split(getattr(ret,f"p{player}")), True) playerargs = parse_cli(shlex.split(getattr(ret,f"p{player}")), True)
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality',
'shuffle', 'door_shuffle', 'crystals_ganon', 'crystals_gt', 'openpyramid', 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid',
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
@@ -141,6 +141,7 @@ def parse_settings():
"bigkeyshuffle": False, "bigkeyshuffle": False,
"keysanity": False, "keysanity": False,
"door_shuffle": "basic", "door_shuffle": "basic",
"intensity": 2,
"experimental": False, "experimental": False,
"dungeon_counters": "default", "dungeon_counters": "default",
+117 -311
View File
@@ -8,13 +8,12 @@ from enum import unique, Flag
from functools import reduce from functools import reduce
from BaseClasses import RegionType, Door, DoorType, Direction, Sector, CrystalBarrier from BaseClasses import RegionType, Door, DoorType, Direction, Sector, CrystalBarrier
from Regions import key_only_locations from Regions import key_only_locations
from Dungeons import hyrule_castle_regions, eastern_regions, desert_regions, hera_regions, tower_regions, pod_regions from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts, flexible_starts
from Dungeons import dungeon_regions, region_starts, split_region_starts, flexible_starts from Dungeons import dungeon_bigs, dungeon_keys, dungeon_hints
from Dungeons import drop_entrances, dungeon_bigs, dungeon_keys, dungeon_hints
from Items import ItemFactory from Items import ItemFactory
from RoomData import DoorKind, PairedDoor from RoomData import DoorKind, PairedDoor
from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, validate_tr from DungeonGenerator import ExplorationState, convert_regions, generate_dungeon, pre_validate, determine_required_paths
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances
from KeyDoorShuffle import analyze_dungeon, validate_vanilla_key_logic, build_key_layout, validate_key_layout from KeyDoorShuffle import analyze_dungeon, validate_vanilla_key_logic, build_key_layout, validate_key_layout
@@ -28,8 +27,6 @@ def link_doors(world, player):
connect_interior_doors(edge_a, edge_b, world, player) connect_interior_doors(edge_a, edge_b, world, player)
# These connections are here because they are currently unable to be shuffled # These connections are here because they are currently unable to be shuffled
for entrance, ext in straight_staircases:
connect_two_way(world, entrance, ext, player)
for exitName, regionName in falldown_pits: for exitName, regionName in falldown_pits:
connect_simple_door(world, exitName, regionName, player) connect_simple_door(world, exitName, regionName, player)
for exitName, regionName in dungeon_warps: for exitName, regionName in dungeon_warps:
@@ -37,9 +34,17 @@ def link_doors(world, player):
for ent, ext in ladders: for ent, ext in ladders:
connect_two_way(world, ent, ext, player) connect_two_way(world, ent, ext, player)
if world.intensity[player] < 2:
for entrance, ext in open_edges:
connect_two_way(world, entrance, ext, player)
for entrance, ext in straight_staircases:
connect_two_way(world, entrance, ext, player)
if world.doorShuffle[player] == 'vanilla': if world.doorShuffle[player] == 'vanilla':
for entrance, ext in open_edges: for entrance, ext in open_edges:
connect_two_way(world, entrance, ext, player) connect_two_way(world, entrance, ext, player)
for entrance, ext in straight_staircases:
connect_two_way(world, entrance, ext, player)
for exitName, regionName in vanilla_logical_connections: for exitName, regionName in vanilla_logical_connections:
connect_simple_door(world, exitName, regionName, player) connect_simple_door(world, exitName, regionName, player)
for entrance, ext in spiral_staircases: for entrance, ext in spiral_staircases:
@@ -50,13 +55,8 @@ def link_doors(world, player):
connect_one_way(world, ent, ext, player) connect_one_way(world, ent, ext, player)
vanilla_key_logic(world, player) vanilla_key_logic(world, player)
elif world.doorShuffle[player] == 'basic': elif world.doorShuffle[player] == 'basic':
# if not world.experimental[player]:
for entrance, ext in open_edges:
connect_two_way(world, entrance, ext, player)
within_dungeon(world, player) within_dungeon(world, player)
elif world.doorShuffle[player] == 'crossed': elif world.doorShuffle[player] == 'crossed':
for entrance, ext in open_edges:
connect_two_way(world, entrance, ext, player)
cross_dungeon(world, player) cross_dungeon(world, player)
else: else:
logging.getLogger('').error('Invalid door shuffle setting: %s' % world.doorShuffle[player]) logging.getLogger('').error('Invalid door shuffle setting: %s' % world.doorShuffle[player])
@@ -102,7 +102,8 @@ def create_door_spoiler(world, player):
for ext in next.exits: for ext in next.exits:
door_a = ext.door door_a = ext.door
connect = ext.connected_region connect = ext.connected_region
if door_a and door_a.type in [DoorType.Normal, DoorType.SpiralStairs] and door_a not in done: if door_a and door_a.type in [DoorType.Normal, DoorType.SpiralStairs, DoorType.Open,
DoorType.StraightStairs] and door_a not in done:
done.add(door_a) done.add(door_a)
door_b = door_a.dest door_b = door_a.dest
if door_b: if door_b:
@@ -143,13 +144,13 @@ def vanilla_key_logic(world, player):
while len(sector_queue) > 0: while len(sector_queue) > 0:
builder = sector_queue.popleft() builder = sector_queue.popleft()
split_dungeon = builder.name.startswith('Desert Palace') or builder.name.startswith('Skull Woods')
origin_list = list(entrances_map[builder.name]) origin_list = list(entrances_map[builder.name])
find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, builder.name) find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, builder.name)
origin_list_sans_drops = remove_drop_origins(origin_list) if len(origin_list) <= 0 or not pre_validate(builder, origin_list, split_dungeon, world, player):
if len(origin_list_sans_drops) <= 0:
if last_key == builder.name or loops > 1000: if last_key == builder.name or loops > 1000:
origin_name = world.get_region(origin_list[0], player).entrances[0].parent_region.name origin_name = world.get_region(origin_list[0], player).entrances[0].parent_region.name if len(origin_list) > 0 else 'no origin'
raise Exception('Infinite loop detected for "%s" located at %s' % builder.name, origin_name) raise Exception('Infinite loop detected for "%s" located at %s' % (builder.name, origin_name))
sector_queue.append(builder) sector_queue.append(builder)
last_key = builder.name last_key = builder.name
loops += 1 loops += 1
@@ -161,6 +162,7 @@ def vanilla_key_logic(world, player):
valid = validate_key_layout(key_layout, world, player) valid = validate_key_layout(key_layout, world, player)
if not valid: if not valid:
logging.getLogger('').warning('Vanilla key layout not valid %s', builder.name) logging.getLogger('').warning('Vanilla key layout not valid %s', builder.name)
builder.key_door_proposal = doors
if player not in world.key_logic.keys(): if player not in world.key_logic.keys():
world.key_logic[player] = {} world.key_logic[player] = {}
analyze_dungeon(key_layout, world, player) analyze_dungeon(key_layout, world, player)
@@ -318,25 +320,35 @@ def within_dungeon(world, player):
dungeon_builders[key] = simple_dungeon_builder(key, sector_list) dungeon_builders[key] = simple_dungeon_builder(key, sector_list)
dungeon_builders[key].entrance_list = list(entrances_map[key]) dungeon_builders[key].entrance_list = list(entrances_map[key])
recombinant_builders = {} recombinant_builders = {}
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, world.fish) builder_info = None, None, world, player
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, builder_info)
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player) main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
paths = determine_required_paths(world, player) paths = determine_required_paths(world, player)
check_required_paths(paths, world, player) check_required_paths(paths, world, player)
# shuffle_key_doors for dungeons # shuffle_key_doors for dungeons
logging.getLogger('').info(world.fish.translate("cli", "cli", "shuffling.keydoors"))
start = time.process_time() start = time.process_time()
for builder in world.dungeon_layouts[player].values(): for builder in world.dungeon_layouts[player].values():
shuffle_key_doors(builder, world, player) shuffle_key_doors(builder, world, player)
logging.getLogger('').info('%s: %s', world.fish.translate("cli","cli","keydoor.shuffle.time"), time.process_time()-start) logging.getLogger('').info('%s: %s', world.fish.translate("cli", "cli", "keydoor.shuffle.time"), time.process_time()-start)
smooth_door_pairs(world, player) smooth_door_pairs(world, player)
def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, fish): def handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, builder_info):
for name, split_list in split_region_starts.items(): dungeon_entrances, split_dungeon_entrances, world, player = builder_info
if dungeon_entrances is None:
dungeon_entrances = default_dungeon_entrances
if split_dungeon_entrances is None:
split_dungeon_entrances = split_region_starts
builder_info = dungeon_entrances, split_region_starts, world, player
for name, split_list in split_dungeon_entrances.items():
builder = dungeon_builders.pop(name) builder = dungeon_builders.pop(name)
recombinant_builders[name] = builder recombinant_builders[name] = builder
split_builders = split_dungeon_builder(builder, split_list, fish)
split_builders = split_dungeon_builder(builder, split_list, builder_info)
dungeon_builders.update(split_builders) dungeon_builders.update(split_builders)
for sub_name, split_entrances in split_list.items(): for sub_name, split_entrances in split_list.items():
sub_builder = dungeon_builders[name+' '+sub_name] sub_builder = dungeon_builders[name+' '+sub_name]
@@ -353,6 +365,7 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_
enabled_entrances = {} enabled_entrances = {}
sector_queue = deque(dungeon_builders.values()) sector_queue = deque(dungeon_builders.values())
last_key, loops = None, 0 last_key, loops = None, 0
logging.getLogger('').info(world.fish.translate("cli", "cli", "generating.dungeon"))
while len(sector_queue) > 0: while len(sector_queue) > 0:
builder = sector_queue.popleft() builder = sector_queue.popleft()
split_dungeon = builder.name.startswith('Desert Palace') or builder.name.startswith('Skull Woods') split_dungeon = builder.name.startswith('Desert Palace') or builder.name.startswith('Skull Woods')
@@ -361,17 +374,15 @@ def main_dungeon_generation(dungeon_builders, recombinant_builders, connections_
name = ' '.join(builder.name.split(' ')[:-1]) name = ' '.join(builder.name.split(' ')[:-1])
origin_list = list(builder.entrance_list) origin_list = list(builder.entrance_list)
find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, name) find_enabled_origins(builder.sectors, enabled_entrances, origin_list, entrances_map, name)
origin_list_sans_drops = remove_drop_origins(origin_list) if len(origin_list) <= 0 or not pre_validate(builder, origin_list, split_dungeon, world, player):
if len(origin_list_sans_drops) <= 0 or name == "Turtle Rock" and not validate_tr(builder, origin_list_sans_drops, world, player):
if last_key == builder.name or loops > 1000: if last_key == builder.name or loops > 1000:
origin_name = world.get_region(origin_list[0], player).entrances[0].parent_region.name origin_name = world.get_region(origin_list[0], player).entrances[0].parent_region.name if len(origin_list) > 0 else 'no origin'
raise Exception('Infinite loop detected for "%s" located at %s' % builder.name, origin_name) raise Exception('Infinite loop detected for "%s" located at %s' % (builder.name, origin_name))
sector_queue.append(builder) sector_queue.append(builder)
last_key = builder.name last_key = builder.name
loops += 1 loops += 1
else: else:
logging.getLogger('').info('%s: %s', world.fish.translate("cli","cli","generating.dungeon"), builder.name) ds = generate_dungeon(builder, origin_list, split_dungeon, world, player)
ds = generate_dungeon(builder, origin_list_sans_drops, split_dungeon, world, player)
find_new_entrances(ds, entrances_map, connections, potentials, enabled_entrances, world, player) find_new_entrances(ds, entrances_map, connections, potentials, enabled_entrances, world, player)
ds.name = name ds.name = name
builder.master_sector = ds builder.master_sector = ds
@@ -391,11 +402,13 @@ def determine_entrance_list(world, player):
connections = {} connections = {}
for key, r_names in region_starts.items(): for key, r_names in region_starts.items():
entrance_map[key] = [] entrance_map[key] = []
if world.mode[player] == 'standard' and key in standard_starts.keys():
r_names = standard_starts[key]
for region_name in r_names: for region_name in r_names:
region = world.get_region(region_name, player) region = world.get_region(region_name, player)
for ent in region.entrances: for ent in region.entrances:
parent = ent.parent_region parent = ent.parent_region
if parent.type != RegionType.Dungeon or parent.name == 'Sewer Drop': if (parent.type != RegionType.Dungeon and parent.name != 'Menu') or parent.name == 'Sewer Drop':
if parent.name not in world.inaccessible_regions[player]: if parent.name not in world.inaccessible_regions[player]:
entrance_map[key].append(region_name) entrance_map[key].append(region_name)
else: else:
@@ -406,11 +419,6 @@ def determine_entrance_list(world, player):
return entrance_map, potential_entrances, connections return entrance_map, potential_entrances, connections
# todo: kill drop exceptions
def drop_exception(name):
return name in ['Skull Pot Circle', 'Skull Back Drop']
def add_shuffled_entrances(sectors, region_list, entrance_list): def add_shuffled_entrances(sectors, region_list, entrance_list):
for sector in sectors: for sector in sectors:
for region in sector.regions: for region in sector.regions:
@@ -428,26 +436,20 @@ def find_enabled_origins(sectors, enabled, entrance_list, entrance_map, key):
if key not in entrance_map.keys(): if key not in entrance_map.keys():
key = ' '.join(key.split(' ')[:-1]) key = ' '.join(key.split(' ')[:-1])
entrance_map[key].append(region.name) entrance_map[key].append(region.name)
if drop_exception(region.name): # only because they have unique regions
entrance_list.append(region.name)
def remove_drop_origins(entrance_list):
return [x for x in entrance_list if x not in drop_entrances]
def find_new_entrances(sector, entrances_map, connections, potentials, enabled, world, player): def find_new_entrances(sector, entrances_map, connections, potentials, enabled, world, player):
for region in sector.regions: for region in sector.regions:
if region.name in connections.keys() and (connections[region.name] in potentials.keys() or connections[region.name].name in world.inaccessible_regions[player]): if region.name in connections.keys() and (connections[region.name] in potentials.keys() or connections[region.name].name in world.inaccessible_regions[player]):
enable_new_entrances(region, connections, potentials, enabled, world, player) enable_new_entrances(region, connections, potentials, enabled, world, player, region)
inverted_aga_check(entrances_map, connections, potentials, enabled, world, player) inverted_aga_check(entrances_map, connections, potentials, enabled, world, player)
def enable_new_entrances(region, connections, potentials, enabled, world, player): def enable_new_entrances(region, connections, potentials, enabled, world, player, region_enabler):
new_region = connections[region.name] new_region = connections[region.name]
if new_region in potentials.keys(): if new_region in potentials.keys():
for potential in potentials.pop(new_region): for potential in potentials.pop(new_region):
enabled[potential] = (region.name, region.dungeon) enabled[potential] = (region_enabler.name, region_enabler.dungeon)
# see if this unexplored region connects elsewhere # see if this unexplored region connects elsewhere
queue = deque(new_region.exits) queue = deque(new_region.exits)
visited = set() visited = set()
@@ -469,9 +471,10 @@ def inverted_aga_check(entrances_map, connections, potentials, enabled, world, p
if 'Agahnims Tower' in entrances_map.keys() or aga_tower_enabled(enabled): if 'Agahnims Tower' in entrances_map.keys() or aga_tower_enabled(enabled):
for region in list(potentials.keys()): for region in list(potentials.keys()):
if region.name == 'Hyrule Castle Ledge': if region.name == 'Hyrule Castle Ledge':
enabler = world.get_region('Tower Agahnim 1', player)
for r_name in potentials[region]: for r_name in potentials[region]:
new_region = world.get_region(r_name, player) new_region = world.get_region(r_name, player)
enable_new_entrances(new_region, connections, potentials, enabled, world, player) enable_new_entrances(new_region, connections, potentials, enabled, world, player, enabler)
def aga_tower_enabled(enabled): def aga_tower_enabled(enabled):
@@ -482,191 +485,6 @@ def aga_tower_enabled(enabled):
return False return False
def within_dungeon_legacy(world, player):
# TODO: The "starts" regions need access logic
# Aerinon's note: I think this is handled already by ER Rules - may need to check correct requirements
dungeon_region_starts_es = ['Hyrule Castle Lobby', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby', 'Sewers Secret Room']
dungeon_region_starts_ep = ['Eastern Lobby']
dungeon_region_starts_dp = ['Desert Back Lobby', 'Desert Main Lobby', 'Desert West Lobby', 'Desert East Lobby']
dungeon_region_starts_th = ['Hera Lobby']
dungeon_region_starts_at = ['Tower Lobby']
dungeon_region_starts_pd = ['PoD Lobby']
dungeon_region_lists = [
(dungeon_region_starts_es, hyrule_castle_regions),
(dungeon_region_starts_ep, eastern_regions),
(dungeon_region_starts_dp, desert_regions),
(dungeon_region_starts_th, hera_regions),
(dungeon_region_starts_at, tower_regions),
(dungeon_region_starts_pd, pod_regions),
]
for start_list, region_list in dungeon_region_lists:
shuffle_dungeon(world, player, start_list, region_list)
world.dungeon_layouts[player] = {}
for key in dungeon_regions.keys():
world.dungeon_layouts[player][key] = (key, region_starts[key])
def shuffle_dungeon(world, player, start_region_names, dungeon_region_names):
logger = logging.getLogger('')
# Part one - generate a random layout
available_regions = []
for name in [r for r in dungeon_region_names if r not in start_region_names]:
available_regions.append(world.get_region(name, player))
random.shuffle(available_regions)
# "Ugly" doors are doors that we don't want to see from the front, because of some
# sort of unsupported key door. To handle them, make a map of "ugly regions" and
# never link across them.
ugly_regions = {}
next_ugly_region = 1
# Add all start regions to the open set.
available_doors = []
for name in start_region_names:
logger.info("Starting in %s", name)
for door in get_doors(world, world.get_region(name, player), player):
ugly_regions[door.name] = 0
available_doors.append(door)
# Loop until all available doors are used
while len(available_doors) > 0:
# Pick a random available door to connect, prioritizing ones that aren't blocked.
# This makes them either get picked up through another door (so they head deeper
# into the dungeon), or puts them late in the dungeon (so they probably are part
# of a loop). Panic if neither of these happens.
random.shuffle(available_doors)
available_doors.sort(key=lambda door: 1 if door.blocked else 0 if door.ugly else 2)
door = available_doors.pop()
logger.info('Linking %s', door.name)
# Find an available region that has a compatible door
connect_region, connect_door = find_compatible_door_in_regions(world, door, available_regions, player)
# Also ignore compatible doors if they're blocked; these should only be used to
# create loops.
if connect_region is not None and not door.blocked:
logger.info(' Found new region %s via %s', connect_region.name, connect_door.name)
# Apply connection and add the new region's doors to the available list
maybe_connect_two_way(world, door, connect_door, player)
# Figure out the new room's ugliness region
new_room_ugly_region = ugly_regions[door.name]
if connect_door.ugly:
next_ugly_region += 1
new_room_ugly_region = next_ugly_region
is_new_region = connect_region in available_regions
# Add the doors
for door in get_doors(world, connect_region, player):
ugly_regions[door.name] = new_room_ugly_region
if is_new_region:
available_doors.append(door)
# If an ugly door is anything but the connect door, panic and die
if door != connect_door and door.ugly:
logger.info('Failed because of ugly door, trying again.')
shuffle_dungeon(world, player, start_region_names, dungeon_region_names)
return
# We've used this region and door, so don't use them again
if is_new_region:
available_regions.remove(connect_region)
if connect_door in available_doors:
available_doors.remove(connect_door)
else:
# If there's no available region with a door, use an internal connection
connect_door = find_compatible_door_in_list(ugly_regions, world, door, available_doors, player)
if connect_door is not None:
logger.info(' Adding loop via %s', connect_door.name)
maybe_connect_two_way(world, door, connect_door, player)
if connect_door in available_doors:
available_doors.remove(connect_door)
# Check that we used everything, and retry if we failed
if len(available_regions) > 0 or len(available_doors) > 0:
logger.info('Failed to add all regions to dungeon, trying again.')
shuffle_dungeon(world, player, start_region_names, dungeon_region_names)
return
# Connects a and b. Or don't if they're an unsupported connection type.
# TODO: This is gross, don't do it this way
def maybe_connect_two_way(world, a, b, player):
# Return on unsupported types.
if a.type in [DoorType.Open, DoorType.StraightStairs, DoorType.Hole, DoorType.Warp, DoorType.Ladder,
DoorType.Interior, DoorType.Logical]:
return
# Connect supported types
if a.type == DoorType.Normal or a.type == DoorType.SpiralStairs:
if a.blocked:
connect_one_way(world, b.name, a.name, player)
elif b.blocked:
connect_one_way(world, a.name, b.name, player)
else:
connect_two_way(world, a.name, b.name, player)
return
# If we failed to account for a type, panic
raise RuntimeError('Unknown door type ' + a.type.name)
# Finds a compatible door in regions, returns the region and door
def find_compatible_door_in_regions(world, door, regions, player):
if door.type in [DoorType.Hole, DoorType.Warp, DoorType.Logical]:
return door.dest, door
for region in regions:
for proposed_door in get_doors(world, region, player):
if doors_compatible(door, proposed_door):
return region, proposed_door
return None, None
def find_compatible_door_in_list(ugly_regions, world, door, doors, player):
if door.type in [DoorType.Hole, DoorType.Warp, DoorType.Logical]:
return door
for proposed_door in doors:
if ugly_regions[door.name] != ugly_regions[proposed_door.name]:
continue
if doors_compatible(door, proposed_door):
return proposed_door
def get_doors(world, region, player):
res = []
for exit in region.exits:
door = world.check_for_door(exit.name, player)
if door is not None:
res.append(door)
return res
def get_entrance_doors(world, region, player):
res = []
for exit in region.entrances:
door = world.check_for_door(exit.name, player)
if door is not None:
res.append(door)
return res
def doors_compatible(a, b):
if a.type != b.type:
return False
if a.type == DoorType.Open:
return doors_fit_mandatory_pair(open_edges, a, b)
if a.type == DoorType.StraightStairs:
return doors_fit_mandatory_pair(straight_staircases, a, b)
if a.type == DoorType.Interior:
return doors_fit_mandatory_pair(interior_doors, a, b)
if a.type == DoorType.Ladder:
return doors_fit_mandatory_pair(ladders, a, b)
if a.type == DoorType.Normal and (a.smallKey or b.smallKey or a.bigKey or b.bigKey):
return doors_fit_mandatory_pair(key_doors, a, b)
if a.type in [DoorType.Hole, DoorType.Warp, DoorType.Logical]:
return False # these aren't compatible with anything
return a.direction == switch_dir(b.direction)
def doors_fit_mandatory_pair(pair_list, a, b):
for pair_a, pair_b in pair_list:
if (a.name == pair_a and b.name == pair_b) or (a.name == pair_b and b.name == pair_a):
return True
return False
# goals: # goals:
# 1. have enough chests to be interesting (2 more than dungeon items) # 1. have enough chests to be interesting (2 more than dungeon items)
# 2. have a balanced amount of regions added (check) # 2. have a balanced amount of regions added (check)
@@ -683,10 +501,11 @@ def cross_dungeon(world, player):
entrances_map, potentials, connections = determine_entrance_list(world, player) entrances_map, potentials, connections = determine_entrance_list(world, player)
connections_tuple = (entrances_map, potentials, connections) connections_tuple = (entrances_map, potentials, connections)
all_sectors = [] all_sectors, all_regions = [], []
for key in dungeon_regions.keys(): for key in dungeon_regions.keys():
all_sectors.extend(convert_to_sectors(dungeon_regions[key], world, player)) all_regions += dungeon_regions[key]
dungeon_builders = create_dungeon_builders(all_sectors, world, player) all_sectors.extend(convert_to_sectors(all_regions, world, player))
dungeon_builders = create_dungeon_builders(all_sectors, connections_tuple, world, player)
for builder in dungeon_builders.values(): for builder in dungeon_builders.values():
builder.entrance_list = list(entrances_map[builder.name]) builder.entrance_list = list(entrances_map[builder.name])
dungeon_obj = world.get_dungeon(builder.name, player) dungeon_obj = world.get_dungeon(builder.name, player)
@@ -698,7 +517,8 @@ def cross_dungeon(world, player):
key_name = dungeon_keys[builder.name] if loc.name != 'Hyrule Castle - Big Key Drop' else dungeon_bigs[builder.name] key_name = dungeon_keys[builder.name] if loc.name != 'Hyrule Castle - Big Key Drop' else dungeon_bigs[builder.name]
loc.forced_item = loc.item = ItemFactory(key_name, player) loc.forced_item = loc.item = ItemFactory(key_name, player)
recombinant_builders = {} recombinant_builders = {}
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, world.fish) builder_info = None, None, world, player
handle_split_dungeons(dungeon_builders, recombinant_builders, entrances_map, builder_info)
main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player) main_dungeon_generation(dungeon_builders, recombinant_builders, connections_tuple, world, player)
@@ -730,6 +550,7 @@ def cross_dungeon(world, player):
def assign_cross_keys(dungeon_builders, world, player): def assign_cross_keys(dungeon_builders, world, player):
logging.getLogger('').info(world.fish.translate("cli", "cli", "shuffling.keydoors"))
start = time.process_time() start = time.process_time()
total_keys = remaining = 29 total_keys = remaining = 29
total_candidates = 0 total_candidates = 0
@@ -782,7 +603,7 @@ def assign_cross_keys(dungeon_builders, world, player):
while len(queue) > 0 and remaining > 0: while len(queue) > 0 and remaining > 0:
builder = queue.popleft() builder = queue.popleft()
name = builder.name name = builder.name
logger.info('Cross Dungeon: Increasing key count by 1 for %s', name) logger.debug('Cross Dungeon: Increasing key count by 1 for %s', name)
builder.key_doors_num += 1 builder.key_doors_num += 1
result = find_valid_combination(builder, start_regions_map[name], world, player, drop_keys=False) result = find_valid_combination(builder, start_regions_map[name], world, player, drop_keys=False)
if result: if result:
@@ -793,10 +614,10 @@ def assign_cross_keys(dungeon_builders, world, player):
queue.append(builder) queue.append(builder)
queue = deque(sorted(queue, key=lambda b: b.combo_size)) queue = deque(sorted(queue, key=lambda b: b.combo_size))
else: else:
logger.info('Cross Dungeon: Increase failed for %s', name) logger.debug('Cross Dungeon: Increase failed for %s', name)
builder.key_doors_num -= 1 builder.key_doors_num -= 1
builder.flex = 0 builder.flex = 0
logger.info('Cross Dungeon: Keys unable to assign in pool %s', remaining) logger.debug('Cross Dungeon: Keys unable to assign in pool %s', remaining)
# Last Step: Adjust Small Key Dungeon Pool # Last Step: Adjust Small Key Dungeon Pool
if not world.retro[player]: if not world.retro[player]:
@@ -809,7 +630,7 @@ def assign_cross_keys(dungeon_builders, world, player):
dungeon.small_keys = [] dungeon.small_keys = []
else: else:
dungeon.small_keys = [ItemFactory(dungeon_keys[name], player)] * actual_chest_keys dungeon.small_keys = [ItemFactory(dungeon_keys[name], player)] * actual_chest_keys
logging.getLogger('').info('%s: %s', world.fish.translate("cli","cli","keydoor.shuffle.time.crossed"), time.process_time()-start) logger.info('%s: %s', world.fish.translate("cli", "cli", "keydoor.shuffle.time.crossed"), time.process_time()-start)
def reassign_boss(boss_region, boss_key, builder, gt, world, player): def reassign_boss(boss_region, boss_key, builder, gt, world, player):
@@ -940,7 +761,7 @@ def find_small_key_door_candidates(builder, start_regions, world, player):
checked_doors = set() checked_doors = set()
for region in start_regions: for region in start_regions:
possible, checked = find_key_door_candidates(region, checked_doors, world, player) possible, checked = find_key_door_candidates(region, checked_doors, world, player)
candidates.extend(possible) candidates.extend([x for x in possible if x not in candidates])
checked_doors.update(checked) checked_doors.update(checked)
flat_candidates = [] flat_candidates = []
for candidate in candidates: for candidate in candidates:
@@ -966,7 +787,6 @@ def calc_used_dungeon_items(builder):
def find_valid_combination(builder, start_regions, world, player, drop_keys=True): def find_valid_combination(builder, start_regions, world, player, drop_keys=True):
logger = logging.getLogger('') logger = logging.getLogger('')
logger.info('%s %s', world.fish.translate("cli","cli","shuffling.keydoors"), builder.name)
# find valid combination of candidates # find valid combination of candidates
if len(builder.candidates) < builder.key_doors_num: if len(builder.candidates) < builder.key_doors_num:
if not drop_keys: if not drop_keys:
@@ -1069,6 +889,9 @@ def flatten_pair_list(paired_list):
return flat_list return flat_list
okay_normals = [DoorKind.Normal, DoorKind.SmallKey, DoorKind.Bombable, DoorKind.Dashable, DoorKind.DungeonChanger]
def find_key_door_candidates(region, checked, world, player): def find_key_door_candidates(region, checked, world, player):
dungeon = region.dungeon dungeon = region.dungeon
candidates = [] candidates = []
@@ -1093,11 +916,12 @@ def find_key_door_candidates(region, checked, world, player):
elif d.type == DoorType.Normal: elif d.type == DoorType.Normal:
d2 = d.dest d2 = d.dest
if d2 not in candidates: if d2 not in candidates:
room_b = world.get_room(d2.roomIndex, player) if d2.type == DoorType.Normal:
pos_b, kind_b = room_b.doorList[d2.doorListPos] room_b = world.get_room(d2.roomIndex, player)
okay_normals = [DoorKind.Normal, DoorKind.SmallKey, DoorKind.Bombable, pos_b, kind_b = room_b.doorList[d2.doorListPos]
DoorKind.Dashable, DoorKind.DungeonChanger] valid = kind in okay_normals and kind_b in okay_normals
valid = kind in okay_normals and kind_b in okay_normals else:
valid = kind in okay_normals
if valid and 0 <= d2.doorListPos < 4: if valid and 0 <= d2.doorListPos < 4:
candidates.append(d2) candidates.append(d2)
else: else:
@@ -1182,8 +1006,8 @@ def reassign_key_doors(builder, world, player):
dp.pair = False dp.pair = False
if not found: if not found:
world.paired_doors[player].append(PairedDoor(d1.name, d2.name)) world.paired_doors[player].append(PairedDoor(d1.name, d2.name))
change_door_to_small_key(d1, world, player) change_door_to_small_key(d1, world, player)
change_door_to_small_key(d2, world, player) change_door_to_small_key(d2, world, player)
world.spoiler.set_door_type(d1.name+' <-> '+d2.name, 'Key Door', player) world.spoiler.set_door_type(d1.name+' <-> '+d2.name, 'Key Door', player)
logger.debug('Key Door: %s', d1.name+' <-> '+d2.name) logger.debug('Key Door: %s', d1.name+' <-> '+d2.name)
else: else:
@@ -1214,10 +1038,13 @@ def smooth_door_pairs(world, player):
partner = door.dest partner = door.dest
skip.add(partner) skip.add(partner)
room_a = world.get_room(door.roomIndex, player) room_a = world.get_room(door.roomIndex, player)
room_b = world.get_room(partner.roomIndex, player)
type_a = room_a.kind(door) type_a = room_a.kind(door)
type_b = room_b.kind(partner) if partner.type in [DoorType.Normal, DoorType.Interior]:
valid_pair = stateful_door(door, type_a) and stateful_door(partner, type_b) room_b = world.get_room(partner.roomIndex, player)
type_b = room_b.kind(partner)
valid_pair = stateful_door(door, type_a) and stateful_door(partner, type_b)
else:
valid_pair, room_b, type_b = False, None, None
if door.type == DoorType.Normal: if door.type == DoorType.Normal:
if type_a == DoorKind.SmallKey or type_b == DoorKind.SmallKey: if type_a == DoorKind.SmallKey or type_b == DoorKind.SmallKey:
if valid_pair: if valid_pair:
@@ -1304,31 +1131,6 @@ def random_door_type(door, partner, world, player, type_a, type_b, room_a, room_
world.spoiler.set_door_type(door.name + ' <-> ' + partner.name, spoiler_type, player) world.spoiler.set_door_type(door.name + ' <-> ' + partner.name, spoiler_type, player)
def determine_required_paths(world, player):
paths = {
'Hyrule Castle': ['Hyrule Castle Lobby', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby'],
'Eastern Palace': ['Eastern Boss'],
'Desert Palace': ['Desert Main Lobby', 'Desert East Lobby', 'Desert West Lobby', 'Desert Boss'],
'Tower of Hera': ['Hera Boss'],
'Agahnims Tower': ['Tower Agahnim 1'],
'Palace of Darkness': ['PoD Boss'],
'Swamp Palace': ['Swamp Boss'],
'Skull Woods': ['Skull 1 Lobby', 'Skull 2 East Lobby', 'Skull 2 West Lobby', 'Skull Boss'],
'Thieves Town': ['Thieves Boss', ('Thieves Blind\'s Cell', 'Thieves Boss')],
'Ice Palace': ['Ice Boss'],
'Misery Mire': ['Mire Boss'],
'Turtle Rock': ['TR Main Lobby', 'TR Lazy Eyes', 'TR Big Chest Entrance', 'TR Eye Bridge', 'TR Boss'],
'Ganons Tower': ['GT Agahnim 2']
}
if world.mode[player] == 'standard':
paths['Hyrule Castle'].append('Hyrule Dungeon Cellblock')
# noinspection PyTypeChecker
paths['Hyrule Castle'].append(('Hyrule Dungeon Cellblock', 'Sanctuary'))
if world.doorShuffle[player] in ['basic']:
paths['Thieves Town'].append('Thieves Attic Window')
return paths
def overworld_prep(world, player): def overworld_prep(world, player):
find_inaccessible_regions(world, player) find_inaccessible_regions(world, player)
add_inaccessible_doors(world, player) add_inaccessible_doors(world, player)
@@ -1357,9 +1159,6 @@ def find_inaccessible_regions(world, player):
if connect and connect.type is not RegionType.Dungeon and connect not in queue and connect not in visited_regions: if connect and connect.type is not RegionType.Dungeon and connect not in queue and connect not in visited_regions:
queue.append(connect) queue.append(connect)
world.inaccessible_regions[player].extend([r.name for r in all_regions.difference(visited_regions) if valid_inaccessible_region(r)]) world.inaccessible_regions[player].extend([r.name for r in all_regions.difference(visited_regions) if valid_inaccessible_region(r)])
if world.mode[player] == 'standard':
world.inaccessible_regions[player].append('Hyrule Castle Ledge')
world.inaccessible_regions[player].append('Sewer Drop')
logger = logging.getLogger('') logger = logging.getLogger('')
logger.debug('Inaccessible Regions:') logger.debug('Inaccessible Regions:')
for r in world.inaccessible_regions[player]: for r in world.inaccessible_regions[player]:
@@ -1393,37 +1192,38 @@ def create_door(world, player, entName, region_name):
def check_required_paths(paths, world, player): def check_required_paths(paths, world, player):
for dungeon_name in paths.keys(): for dungeon_name in paths.keys():
builder = world.dungeon_layouts[player][dungeon_name] if dungeon_name in world.dungeon_layouts[player].keys():
if len(paths[dungeon_name]) > 0: builder = world.dungeon_layouts[player][dungeon_name]
states_to_explore = defaultdict(list) if len(paths[dungeon_name]) > 0:
for path in paths[dungeon_name]: states_to_explore = defaultdict(list)
if type(path) is tuple: for path in paths[dungeon_name]:
states_to_explore[tuple([path[0]])].append(path[1]) if type(path) is tuple:
else: states_to_explore[tuple([path[0]])].append(path[1])
states_to_explore[tuple(builder.path_entrances)].append(path) else:
cached_initial_state = None states_to_explore[tuple(builder.path_entrances)].append(path)
for start_regs, dest_regs in states_to_explore.items(): cached_initial_state = None
check_paths = convert_regions(dest_regs, world, player) for start_regs, dest_regs in states_to_explore.items():
start_regions = convert_regions(start_regs, world, player) check_paths = convert_regions(dest_regs, world, player)
initial = start_regs == tuple(builder.path_entrances) start_regions = convert_regions(start_regs, world, player)
if not initial or cached_initial_state is None: initial = start_regs == tuple(builder.path_entrances)
init = determine_init_crystal(initial, cached_initial_state, start_regions) if not initial or cached_initial_state is None:
state = ExplorationState(init, dungeon_name) init = determine_init_crystal(initial, cached_initial_state, start_regions)
for region in start_regions: state = ExplorationState(init, dungeon_name)
state.visit_region(region) for region in start_regions:
state.add_all_doors_check_unattached(region, world, player) state.visit_region(region)
explore_state(state, world, player) state.add_all_doors_check_unattached(region, world, player)
if initial and cached_initial_state is None:
cached_initial_state = state
else:
state = cached_initial_state
valid, bad_region = check_if_regions_visited(state, check_paths)
if not valid:
if check_for_pinball_fix(state, bad_region, world, player):
explore_state(state, world, player) explore_state(state, world, player)
valid, bad_region = check_if_regions_visited(state, check_paths) if initial and cached_initial_state is None:
if not valid: cached_initial_state = state
raise Exception('%s cannot reach %s' % (dungeon_name, bad_region.name)) else:
state = cached_initial_state
valid, bad_region = check_if_regions_visited(state, check_paths)
if not valid:
if check_for_pinball_fix(state, bad_region, world, player):
explore_state(state, world, player)
valid, bad_region = check_if_regions_visited(state, check_paths)
if not valid:
raise Exception('%s cannot reach %s' % (dungeon_name, bad_region.name))
def determine_init_crystal(initial, state, start_regions): def determine_init_crystal(initial, state, start_regions):
@@ -1466,7 +1266,7 @@ def check_if_regions_visited(state, check_paths):
def check_for_pinball_fix(state, bad_region, world, player): def check_for_pinball_fix(state, bad_region, world, player):
pinball_region = world.get_region('Skull Pinball', player) pinball_region = world.get_region('Skull Pinball', player)
if bad_region.name == 'Skull 2 West Lobby' and state.visited_at_all(pinball_region): #revisit this for entrance shuffle if bad_region.name == 'Skull 2 West Lobby' and state.visited_at_all(pinball_region): # revisit this for entrance shuffle
door = world.get_door('Skull Pinball WS', player) door = world.get_door('Skull Pinball WS', player)
room = world.get_room(door.roomIndex, player) room = world.get_room(door.roomIndex, player)
if room.doorList[door.doorListPos][1] == DoorKind.Trap: if room.doorList[door.doorListPos][1] == DoorKind.Trap:
@@ -1485,12 +1285,15 @@ class DROptions(Flag):
Eternal_Mini_Bosses = 0x01 # If on, GT minibosses marked as defeated when they try to spawn a heart Eternal_Mini_Bosses = 0x01 # If on, GT minibosses marked as defeated when they try to spawn a heart
Town_Portal = 0x02 # If on, Players will start with mirror scroll Town_Portal = 0x02 # If on, Players will start with mirror scroll
Map_Info = 0x04 Map_Info = 0x04
Debug = 0x08
Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required
# DATA GOES DOWN HERE
# DATA GOES DOWN HERE
logical_connections = [ logical_connections = [
('Hyrule Dungeon North Abyss Catwalk Dropdown', 'Hyrule Dungeon North Abyss'), ('Hyrule Dungeon North Abyss Catwalk Dropdown', 'Hyrule Dungeon North Abyss'),
('Hyrule Castle Throne Room Tapestry', 'Hyrule Castle Behind Tapestry'),
('Hyrule Castle Tapestry Backwards', 'Hyrule Castle Throne Room'),
('Sewers Secret Room Push Block', 'Sewers Secret Room Blocked Path'), ('Sewers Secret Room Push Block', 'Sewers Secret Room Blocked Path'),
('Eastern Hint Tile Push Block', 'Eastern Hint Tile'), ('Eastern Hint Tile Push Block', 'Eastern Hint Tile'),
('Eastern Map Balcony Hook Path', 'Eastern Map Room'), ('Eastern Map Balcony Hook Path', 'Eastern Map Room'),
@@ -1499,6 +1302,7 @@ logical_connections = [
('Desert Main Lobby Right Path', 'Desert Right Alcove'), ('Desert Main Lobby Right Path', 'Desert Right Alcove'),
('Desert Left Alcove Path', 'Desert Main Lobby'), ('Desert Left Alcove Path', 'Desert Main Lobby'),
('Desert Right Alcove Path', 'Desert Main Lobby'), ('Desert Right Alcove Path', 'Desert Main Lobby'),
('Hera Big Chest Hook Path', 'Hera Big Chest Landing'),
('Hera Big Chest Landing Exit', 'Hera 4F'), ('Hera Big Chest Landing Exit', 'Hera 4F'),
('PoD Pit Room Block Path N', 'PoD Pit Room Blocked'), ('PoD Pit Room Block Path N', 'PoD Pit Room Blocked'),
('PoD Pit Room Block Path S', 'PoD Pit Room'), ('PoD Pit Room Block Path S', 'PoD Pit Room'),
@@ -1553,6 +1357,8 @@ logical_connections = [
('Thieves Hellway Crystal Orange Barrier', 'Thieves Hellway'), ('Thieves Hellway Crystal Orange Barrier', 'Thieves Hellway'),
('Thieves Hellway Blue Barrier', 'Thieves Hellway N Crystal'), ('Thieves Hellway Blue Barrier', 'Thieves Hellway N Crystal'),
('Thieves Hellway Crystal Blue Barrier', 'Thieves Hellway'), ('Thieves Hellway Crystal Blue Barrier', 'Thieves Hellway'),
('Thieves Attic Orange Barrier', 'Thieves Attic Hint'),
('Thieves Attic Hint Orange Barrier', 'Thieves Attic'),
('Thieves Basement Block Path', 'Thieves Blocked Entry'), ('Thieves Basement Block Path', 'Thieves Blocked Entry'),
('Thieves Blocked Entry Path', 'Thieves Basement Block'), ('Thieves Blocked Entry Path', 'Thieves Basement Block'),
('Thieves Conveyor Bridge Block Path', 'Thieves Conveyor Block'), ('Thieves Conveyor Bridge Block Path', 'Thieves Conveyor Block'),
@@ -1710,8 +1516,8 @@ open_edges = [
('Desert Main Lobby E Edge', 'Desert East Wing W Edge'), ('Desert Main Lobby E Edge', 'Desert East Wing W Edge'),
('Desert East Wing N Edge', 'Desert Arrow Pot Corner S Edge'), ('Desert East Wing N Edge', 'Desert Arrow Pot Corner S Edge'),
('Desert Arrow Pot Corner W Edge', 'Desert North Hall E Edge'), ('Desert Arrow Pot Corner W Edge', 'Desert North Hall E Edge'),
('Desert North Hall W Edge', 'Desert Sandworm Corner S Edge'), ('Desert West Wing N Edge', 'Desert Sandworm Corner S Edge'),
('Desert Sandworm Corner E Edge', 'Desert West Wing N Edge'), ('Desert Sandworm Corner E Edge', 'Desert North Hall W Edge'),
('Thieves Lobby N Edge', 'Thieves Ambush S Edge'), ('Thieves Lobby N Edge', 'Thieves Ambush S Edge'),
('Thieves Lobby NE Edge', 'Thieves Ambush SE Edge'), ('Thieves Lobby NE Edge', 'Thieves Ambush SE Edge'),
('Thieves Ambush ES Edge', 'Thieves BK Corner WS Edge'), ('Thieves Ambush ES Edge', 'Thieves BK Corner WS Edge'),
+22 -9
View File
@@ -41,7 +41,7 @@ Intr = DoorType.Interior
def create_doors(world, player): def create_doors(world, player):
world.doors += [ doors = [
# hyrule castle # hyrule castle
create_door(player, 'Hyrule Castle Lobby W', Nrml).dir(We, 0x61, Mid, High).toggler().pos(0), create_door(player, 'Hyrule Castle Lobby W', Nrml).dir(We, 0x61, Mid, High).toggler().pos(0),
create_door(player, 'Hyrule Castle Lobby E', Nrml).dir(Ea, 0x61, Mid, High).toggler().pos(2), create_door(player, 'Hyrule Castle Lobby E', Nrml).dir(Ea, 0x61, Mid, High).toggler().pos(2),
@@ -61,6 +61,8 @@ def create_doors(world, player):
create_door(player, 'Hyrule Castle Back Hall W', Nrml).dir(We, 0x01, Top, Low).pos(0), create_door(player, 'Hyrule Castle Back Hall W', Nrml).dir(We, 0x01, Top, Low).pos(0),
create_door(player, 'Hyrule Castle Back Hall E', Nrml).dir(Ea, 0x01, Top, Low).pos(1), create_door(player, 'Hyrule Castle Back Hall E', Nrml).dir(Ea, 0x01, Top, Low).pos(1),
create_door(player, 'Hyrule Castle Back Hall Down Stairs', Sprl).dir(Dn, 0x01, 0, HTL).ss(A, 0x2a, 0x00), create_door(player, 'Hyrule Castle Back Hall Down Stairs', Sprl).dir(Dn, 0x01, 0, HTL).ss(A, 0x2a, 0x00),
create_door(player, 'Hyrule Castle Throne Room Tapestry', Lgcl),
create_door(player, 'Hyrule Castle Tapestry Backwards', Lgcl),
create_door(player, 'Hyrule Castle Throne Room N', Nrml).dir(No, 0x51, Mid, High).pos(1), create_door(player, 'Hyrule Castle Throne Room N', Nrml).dir(No, 0x51, Mid, High).pos(1),
create_door(player, 'Hyrule Castle Throne Room South Stairs', StrS).dir(So, 0x51, Mid, Low), create_door(player, 'Hyrule Castle Throne Room South Stairs', StrS).dir(So, 0x51, Mid, Low),
@@ -72,11 +74,11 @@ def create_doors(world, player):
create_door(player, 'Hyrule Dungeon North Abyss Catwalk Edge', Open).dir(So, 0x72, None, High).edge(1, Z, 0x08), create_door(player, 'Hyrule Dungeon North Abyss Catwalk Edge', Open).dir(So, 0x72, None, High).edge(1, Z, 0x08),
create_door(player, 'Hyrule Dungeon North Abyss Catwalk Dropdown', Lgcl), create_door(player, 'Hyrule Dungeon North Abyss Catwalk Dropdown', Lgcl),
create_door(player, 'Hyrule Dungeon South Abyss North Edge', Open).dir(No, 0x82, None, Low).edge(0, A, 0x10), create_door(player, 'Hyrule Dungeon South Abyss North Edge', Open).dir(No, 0x82, None, Low).edge(0, A, 0x10),
create_door(player, 'Hyrule Dungeon South Abyss West Edge', Open).dir(We, 0x82, None, Low).edge(3, Z, 0x10), create_door(player, 'Hyrule Dungeon South Abyss West Edge', Open).dir(We, 0x82, None, Low).edge(3, Z, 0x18),
create_door(player, 'Hyrule Dungeon South Abyss Catwalk North Edge', Open).dir(No, 0x82, None, High).edge(1, A, 0x08), create_door(player, 'Hyrule Dungeon South Abyss Catwalk North Edge', Open).dir(No, 0x82, None, High).edge(1, A, 0x08),
create_door(player, 'Hyrule Dungeon South Abyss Catwalk West Edge', Open).dir(We, 0x82, None, High).edge(4, A, 0x18), create_door(player, 'Hyrule Dungeon South Abyss Catwalk West Edge', Open).dir(We, 0x82, None, High).edge(4, A, 0x10),
create_door(player, 'Hyrule Dungeon Guardroom Catwalk Edge', Open).dir(Ea, 0x81, None, High).edge(3, S, 0x10), create_door(player, 'Hyrule Dungeon Guardroom Catwalk Edge', Open).dir(Ea, 0x81, None, High).edge(3, S, 0x10),
create_door(player, 'Hyrule Dungeon Guardroom Abyss Edge', Open).dir(Ea, 0x81, None, High).edge(4, X, 0x18), create_door(player, 'Hyrule Dungeon Guardroom Abyss Edge', Open).dir(Ea, 0x81, None, Low).edge(4, X, 0x18),
create_door(player, 'Hyrule Dungeon Guardroom N', Nrml).dir(No, 0x81, Left, Low).pos(0), create_door(player, 'Hyrule Dungeon Guardroom N', Nrml).dir(No, 0x81, Left, Low).pos(0),
create_door(player, 'Hyrule Dungeon Armory S', Nrml).dir(So, 0x71, Left, Low).trap(0x2).pos(1), create_door(player, 'Hyrule Dungeon Armory S', Nrml).dir(So, 0x71, Left, Low).trap(0x2).pos(1),
create_door(player, 'Hyrule Dungeon Armory ES', Intr).dir(Ea, 0x71, Left, Low).pos(2), create_door(player, 'Hyrule Dungeon Armory ES', Intr).dir(Ea, 0x71, Left, Low).pos(2),
@@ -201,8 +203,8 @@ def create_doors(world, player):
create_door(player, 'Desert Trap Room SW', Intr).dir(So, 0x75, Left, High).pos(0), create_door(player, 'Desert Trap Room SW', Intr).dir(So, 0x75, Left, High).pos(0),
create_door(player, 'Desert North Hall SE Edge', Open).dir(So, 0x74, None, High).edge(5, X, 0x20), create_door(player, 'Desert North Hall SE Edge', Open).dir(So, 0x74, None, High).edge(5, X, 0x20),
create_door(player, 'Desert North Hall SW Edge', Open).dir(So, 0x74, None, High).edge(3, Z, 0x20), create_door(player, 'Desert North Hall SW Edge', Open).dir(So, 0x74, None, High).edge(3, Z, 0x20),
create_door(player, 'Desert North Hall W Edge', Open).dir(We, 0x74, None, High).edge(2, Z, 0x20), create_door(player, 'Desert North Hall W Edge', Open).dir(We, 0x74, None, High).edge(1, Z, 0x20),
create_door(player, 'Desert North Hall E Edge', Open).dir(Ea, 0x74, None, High).edge(1, X, 0x20), create_door(player, 'Desert North Hall E Edge', Open).dir(Ea, 0x74, None, High).edge(2, X, 0x20),
create_door(player, 'Desert North Hall NW', Intr).dir(No, 0x74, Left, High).pos(1), create_door(player, 'Desert North Hall NW', Intr).dir(No, 0x74, Left, High).pos(1),
create_door(player, 'Desert Map SW', Intr).dir(So, 0x74, Left, High).pos(1), create_door(player, 'Desert Map SW', Intr).dir(So, 0x74, Left, High).pos(1),
create_door(player, 'Desert North Hall NE', Intr).dir(No, 0x74, Right, High).pos(0), create_door(player, 'Desert North Hall NE', Intr).dir(No, 0x74, Right, High).pos(0),
@@ -259,6 +261,7 @@ def create_doors(world, player):
create_door(player, 'Hera 4F Down Stairs', Sprl).dir(Dn, 0x27, 0, HTH).ss(S, 0x62, 0xc0), create_door(player, 'Hera 4F Down Stairs', Sprl).dir(Dn, 0x27, 0, HTH).ss(S, 0x62, 0xc0),
create_door(player, 'Hera 4F Up Stairs', Sprl).dir(Up, 0x27, 1, HTH).ss(A, 0x6b, 0x2c), create_door(player, 'Hera 4F Up Stairs', Sprl).dir(Up, 0x27, 1, HTH).ss(A, 0x6b, 0x2c),
create_door(player, 'Hera 4F Holes', Hole), create_door(player, 'Hera 4F Holes', Hole),
create_door(player, 'Hera Big Chest Hook Path', Lgcl),
create_door(player, 'Hera Big Chest Landing Exit', Lgcl), create_door(player, 'Hera Big Chest Landing Exit', Lgcl),
create_door(player, 'Hera Big Chest Landing Holes', Hole), create_door(player, 'Hera Big Chest Landing Holes', Hole),
create_door(player, 'Hera 5F Down Stairs', Sprl).dir(Dn, 0x17, 1, HTH).ss(A, 0x62, 0x40), create_door(player, 'Hera 5F Down Stairs', Sprl).dir(Dn, 0x17, 1, HTH).ss(A, 0x62, 0x40),
@@ -333,7 +336,7 @@ def create_doors(world, player):
create_door(player, 'PoD Basement Ledge Drop Down', Lgcl), create_door(player, 'PoD Basement Ledge Drop Down', Lgcl),
create_door(player, 'PoD Stalfos Basement Warp', Warp), create_door(player, 'PoD Stalfos Basement Warp', Warp),
create_door(player, 'PoD Arena Main SW', Nrml).dir(So, 0x2a, Left, High).pos(4), create_door(player, 'PoD Arena Main SW', Nrml).dir(So, 0x2a, Left, High).pos(4),
create_door(player, 'PoD Arena Bridge SE', Nrml).dir(So, 0x2a, Right, High).pos(5).kill(), create_door(player, 'PoD Arena Bridge SE', Nrml).dir(So, 0x2a, Right, High).pos(5),
create_door(player, 'PoD Arena Main NW', Nrml).dir(No, 0x2a, Left, High).small_key().pos(1), create_door(player, 'PoD Arena Main NW', Nrml).dir(No, 0x2a, Left, High).small_key().pos(1),
create_door(player, 'PoD Arena Main NE', Nrml).dir(No, 0x2a, Right, High).no_exit().trap(0x4).pos(0), create_door(player, 'PoD Arena Main NE', Nrml).dir(No, 0x2a, Right, High).no_exit().trap(0x4).pos(0),
create_door(player, 'PoD Arena Main Crystal Path', Lgcl), create_door(player, 'PoD Arena Main Crystal Path', Lgcl),
@@ -588,6 +591,8 @@ def create_doors(world, player):
create_door(player, 'Thieves Spike Switch Up Stairs', Sprl).dir(Up, 0xab, 0, HTH).ss(Z, 0x1a, 0x6c, True, True).small_key().pos(0), create_door(player, 'Thieves Spike Switch Up Stairs', Sprl).dir(Up, 0xab, 0, HTH).ss(Z, 0x1a, 0x6c, True, True).small_key().pos(0),
create_door(player, 'Thieves Attic Down Stairs', Sprl).dir(Dn, 0x64, 0, HTH).ss(Z, 0x11, 0x80, True, True), create_door(player, 'Thieves Attic Down Stairs', Sprl).dir(Dn, 0x64, 0, HTH).ss(Z, 0x11, 0x80, True, True),
create_door(player, 'Thieves Attic ES', Intr).dir(Ea, 0x64, Bot, High).pos(0), create_door(player, 'Thieves Attic ES', Intr).dir(Ea, 0x64, Bot, High).pos(0),
create_door(player, 'Thieves Attic Orange Barrier', Lgcl),
create_door(player, 'Thieves Attic Hint Orange Barrier', Lgcl),
create_door(player, 'Thieves Cricket Hall Left WS', Intr).dir(We, 0x64, Bot, High).pos(0), create_door(player, 'Thieves Cricket Hall Left WS', Intr).dir(We, 0x64, Bot, High).pos(0),
create_door(player, 'Thieves Cricket Hall Left Edge', Open).dir(Ea, 0x64, None, High).edge(0, X, 0x30), create_door(player, 'Thieves Cricket Hall Left Edge', Open).dir(Ea, 0x64, None, High).edge(0, X, 0x30),
create_door(player, 'Thieves Cricket Hall Right Edge', Open).dir(We, 0x65, None, High).edge(0, Z, 0x30), create_door(player, 'Thieves Cricket Hall Right Edge', Open).dir(We, 0x65, None, High).edge(0, Z, 0x30),
@@ -900,7 +905,7 @@ def create_doors(world, player):
create_door(player, 'TR Crystal Maze Blue Path', Lgcl), create_door(player, 'TR Crystal Maze Blue Path', Lgcl),
create_door(player, 'TR Crystal Maze Cane Path', Lgcl), create_door(player, 'TR Crystal Maze Cane Path', Lgcl),
create_door(player, 'TR Crystal Maze North Stairs', StrS).dir(No, 0xc4, Mid, High), create_door(player, 'TR Crystal Maze North Stairs', StrS).dir(No, 0xc4, Mid, High),
create_door(player, 'TR Final Abyss South Stairs', StrS).dir(No, 0xb4, Right, High), create_door(player, 'TR Final Abyss South Stairs', StrS).dir(So, 0xb4, Mid, High),
create_door(player, 'TR Final Abyss NW', Nrml).dir(No, 0xb4, Left, High).big_key().pos(0), create_door(player, 'TR Final Abyss NW', Nrml).dir(No, 0xb4, Left, High).big_key().pos(0),
create_door(player, 'TR Boss SW', Nrml).dir(So, 0xa4, Left, High).no_exit().trap(0x4).pos(0), create_door(player, 'TR Boss SW', Nrml).dir(So, 0xa4, Left, High).no_exit().trap(0x4).pos(0),
@@ -1067,6 +1072,10 @@ def create_doors(world, player):
create_door(player, 'GT Brightly Lit Hall NW', Nrml).dir(No, 0x1d, Left, High).big_key().pos(0), create_door(player, 'GT Brightly Lit Hall NW', Nrml).dir(No, 0x1d, Left, High).big_key().pos(0),
create_door(player, 'GT Agahnim 2 SW', Nrml).dir(So, 0x0d, Left, High).no_exit().trap(0x4).pos(0) create_door(player, 'GT Agahnim 2 SW', Nrml).dir(So, 0x0d, Left, High).no_exit().trap(0x4).pos(0)
] ]
world.doors += doors
world.initialize_doors(doors)
create_paired_doors(world, player) create_paired_doors(world, player)
# swamp events # swamp events
@@ -1084,7 +1093,8 @@ def create_doors(world, player):
world.get_door('Swamp Flooded Room Ladder', player).event('Swamp Drain') world.get_door('Swamp Flooded Room Ladder', player).event('Swamp Drain')
if world.mode[player] == 'standard': if world.mode[player] == 'standard':
world.get_door('Hyrule Castle Throne Room N', player).event('Zelda Pickup') world.get_door('Hyrule Castle Throne Room Tapestry', player).event('Zelda Pickup')
world.get_door('Hyrule Castle Tapestry Backwards', player).event('Zelda Pickup')
# crystal switches and barriers # crystal switches and barriers
world.get_door('Hera Lobby Down Stairs', player).c_switch() world.get_door('Hera Lobby Down Stairs', player).c_switch()
@@ -1136,6 +1146,9 @@ def create_doors(world, player):
world.get_door('Thieves Triple Bypass EN', player).barrier(CrystalBarrier.Blue) world.get_door('Thieves Triple Bypass EN', player).barrier(CrystalBarrier.Blue)
world.get_door('Thieves Hellway Orange Barrier', player).barrier(CrystalBarrier.Orange) world.get_door('Thieves Hellway Orange Barrier', player).barrier(CrystalBarrier.Orange)
world.get_door('Thieves Hellway Crystal Orange Barrier', player).barrier(CrystalBarrier.Orange) world.get_door('Thieves Hellway Crystal Orange Barrier', player).barrier(CrystalBarrier.Orange)
world.get_door('Thieves Hellway Crystal Orange Barrier', player).barrier(CrystalBarrier.Orange)
world.get_door('Thieves Attic Orange Barrier', player).barrier(CrystalBarrier.Orange)
world.get_door('Thieves Attic Hint Orange Barrier', player).barrier(CrystalBarrier.Orange)
world.get_door('Ice Bomb Drop SE', player).c_switch() world.get_door('Ice Bomb Drop SE', player).c_switch()
world.get_door('Ice Conveyor SW', player).c_switch() world.get_door('Ice Conveyor SW', player).c_switch()
+1898 -458
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -75,7 +75,7 @@ def start():
logger.warning('%s: %s', fish.translate("cli","cli","generation.failed"), err) logger.warning('%s: %s', fish.translate("cli","cli","generation.failed"), err)
seed = random.randint(0, 999999999) seed = random.randint(0, 999999999)
for fail in failures: for fail in failures:
logger.info('%s seed failed with: %s', fail[1], fail[0]) logger.info('%s\tseed failed with: %s', fail[1], fail[0])
fail_rate = 100 * len(failures) / args.count fail_rate = 100 * len(failures) / args.count
success_rate = 100 * (args.count - len(failures)) / args.count success_rate = 100 * (args.count - len(failures)) / args.count
fail_rate = str(fail_rate).split('.') fail_rate = str(fail_rate).split('.')
+12 -12
View File
@@ -162,13 +162,13 @@ dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A],
hyrule_castle_regions = [ hyrule_castle_regions = [
'Hyrule Castle Lobby', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby', 'Hyrule Castle East Hall', 'Hyrule Castle Lobby', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby', 'Hyrule Castle East Hall',
'Hyrule Castle West Hall', 'Hyrule Castle Back Hall', 'Hyrule Castle Throne Room', 'Hyrule Dungeon Map Room', 'Hyrule Castle West Hall', 'Hyrule Castle Back Hall', 'Hyrule Castle Throne Room', 'Hyrule Castle Behind Tapestry',
'Hyrule Dungeon North Abyss', 'Hyrule Dungeon North Abyss Catwalk', 'Hyrule Dungeon South Abyss', 'Hyrule Dungeon Map Room', 'Hyrule Dungeon North Abyss', 'Hyrule Dungeon North Abyss Catwalk',
'Hyrule Dungeon South Abyss Catwalk', 'Hyrule Dungeon Guardroom', 'Hyrule Dungeon Armory Main', 'Hyrule Dungeon South Abyss', 'Hyrule Dungeon South Abyss Catwalk', 'Hyrule Dungeon Guardroom',
'Hyrule Dungeon Armory Boomerang', 'Hyrule Dungeon Armory North Branch', 'Hyrule Dungeon Staircase', 'Hyrule Dungeon Armory Main', 'Hyrule Dungeon Armory Boomerang', 'Hyrule Dungeon Armory North Branch',
'Hyrule Dungeon Cellblock', 'Sewers Behind Tapestry', 'Sewers Rope Room', 'Sewers Dark Cross', 'Sewers Water', 'Hyrule Dungeon Staircase', 'Hyrule Dungeon Cellblock', 'Sewers Behind Tapestry', 'Sewers Rope Room',
'Sewers Key Rat', 'Sewers Rat Path', 'Sewers Secret Room Blocked Path', 'Sewers Secret Room', 'Sewers Dark Cross', 'Sewers Water', 'Sewers Key Rat', 'Sewers Rat Path', 'Sewers Secret Room Blocked Path',
'Sewers Yet More Rats', 'Sewers Pull Switch', 'Sanctuary' 'Sewers Secret Room', 'Sewers Yet More Rats', 'Sewers Pull Switch', 'Sanctuary'
] ]
eastern_regions = [ eastern_regions = [
@@ -238,7 +238,7 @@ thieves_regions = [
'Thieves Big Chest Nook', 'Thieves Hallway', 'Thieves Boss', 'Thieves Pot Alcove Mid', 'Thieves Pot Alcove Bottom', 'Thieves Big Chest Nook', 'Thieves Hallway', 'Thieves Boss', 'Thieves Pot Alcove Mid', 'Thieves Pot Alcove Bottom',
'Thieves Pot Alcove Top', 'Thieves Conveyor Maze', 'Thieves Spike Track', 'Thieves Hellway', 'Thieves Pot Alcove Top', 'Thieves Conveyor Maze', 'Thieves Spike Track', 'Thieves Hellway',
'Thieves Hellway N Crystal', 'Thieves Hellway S Crystal', 'Thieves Triple Bypass', 'Thieves Spike Switch', 'Thieves Hellway N Crystal', 'Thieves Hellway S Crystal', 'Thieves Triple Bypass', 'Thieves Spike Switch',
'Thieves Attic', 'Thieves Cricket Hall Left', 'Thieves Cricket Hall Right', 'Thieves Attic Window', 'Thieves Attic', 'Thieves Attic Hint', 'Thieves Cricket Hall Left', 'Thieves Cricket Hall Right', 'Thieves Attic Window',
'Thieves Basement Block', 'Thieves Blocked Entry', 'Thieves Lonely Zazak', 'Thieves Blind\'s Cell', 'Thieves Basement Block', 'Thieves Blocked Entry', 'Thieves Lonely Zazak', 'Thieves Blind\'s Cell',
'Thieves Conveyor Bridge', 'Thieves Conveyor Block', 'Thieves Big Chest Room', 'Thieves Trap' 'Thieves Conveyor Bridge', 'Thieves Conveyor Block', 'Thieves Big Chest Room', 'Thieves Trap'
] ]
@@ -331,6 +331,10 @@ region_starts = {
'Ganons Tower': ['GT Lobby'] 'Ganons Tower': ['GT Lobby']
} }
standard_starts = {
'Hyrule Castle': ['Hyrule Castle Lobby']
}
split_region_starts = { split_region_starts = {
'Desert Palace': { 'Desert Palace': {
'Back': ['Desert Back Lobby'], 'Back': ['Desert Back Lobby'],
@@ -347,10 +351,6 @@ flexible_starts = {
'Skull Woods': ['Skull Left Drop', 'Skull Pinball'] 'Skull Woods': ['Skull Left Drop', 'Skull Pinball']
} }
drop_entrances = [
'Sewers Rat Path', 'Skull Pinball', 'Skull Left Drop', 'Sanctuary' # Pot circle, Back drop have unique access
]
dungeon_keys = { dungeon_keys = {
'Hyrule Castle': 'Small Key (Escape)', 'Hyrule Castle': 'Small Key (Escape)',
'Eastern Palace': 'Small Key (Eastern Palace)', 'Eastern Palace': 'Small Key (Eastern Palace)',
+50 -9
View File
@@ -42,6 +42,8 @@ def link_entrances(world, player):
if world.mode[player] == 'standard': if world.mode[player] == 'standard':
# must connect front of hyrule castle to do escape # must connect front of hyrule castle to do escape
connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
elif world.doorShuffle[player] != 'vanilla':
lw_entrances.append('Hyrule Castle Entrance (South)')
else: else:
dungeon_exits.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')) dungeon_exits.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'))
lw_entrances.append('Hyrule Castle Entrance (South)') lw_entrances.append('Hyrule Castle Entrance (South)')
@@ -55,6 +57,11 @@ def link_entrances(world, player):
if world.mode[player] == 'standard': if world.mode[player] == 'standard':
# rest of hyrule castle must be in light world, so it has to be the one connected to east exit of desert # rest of hyrule castle must be in light world, so it has to be the one connected to east exit of desert
connect_mandatory_exits(world, lw_entrances, [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], list(LW_Dungeon_Entrances_Must_Exit), player) connect_mandatory_exits(world, lw_entrances, [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], list(LW_Dungeon_Entrances_Must_Exit), player)
elif world.doorShuffle[player] != 'vanilla':
# sanc is in light world, so must all of HC if door shuffle is on
connect_mandatory_exits(world, lw_entrances,
[('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)', 'Hyrule Castle Exit (South)')],
list(LW_Dungeon_Entrances_Must_Exit), player)
else: else:
connect_mandatory_exits(world, lw_entrances, dungeon_exits, list(LW_Dungeon_Entrances_Must_Exit), player) connect_mandatory_exits(world, lw_entrances, dungeon_exits, list(LW_Dungeon_Entrances_Must_Exit), player)
connect_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit), player) connect_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit), player)
@@ -1204,7 +1211,9 @@ def link_inverted_entrances(world, player):
sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in bomb_shop_doors] sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in bomb_shop_doors]
sanc_door = random.choice(sanc_doors) sanc_door = random.choice(sanc_doors)
bomb_shop_doors.remove(sanc_door) bomb_shop_doors.remove(sanc_door)
connect_doors(world, [sanc_door], ['Inverted Dark Sanctuary'], player)
connect_entrance(world, sanc_door, 'Inverted Dark Sanctuary', player)
world.get_entrance('Inverted Dark Sanctuary Exit', player).connect(world.get_entrance(sanc_door, player).parent_region)
lw_dm_entrances = ['Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'Paradox Cave (Top)', 'Old Man House (Bottom)', lw_dm_entrances = ['Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'Paradox Cave (Top)', 'Old Man House (Bottom)',
'Fairy Ascension Cave (Bottom)', 'Fairy Ascension Cave (Top)', 'Spiral Cave (Bottom)', 'Old Man Cave (East)', 'Fairy Ascension Cave (Bottom)', 'Fairy Ascension Cave (Top)', 'Spiral Cave (Bottom)', 'Old Man Cave (East)',
@@ -1279,7 +1288,8 @@ def link_inverted_entrances(world, player):
sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in dw_entrances] sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in dw_entrances]
sanc_door = random.choice(sanc_doors) sanc_door = random.choice(sanc_doors)
dw_entrances.remove(sanc_door) dw_entrances.remove(sanc_door)
connect_doors(world, [sanc_door], ['Inverted Dark Sanctuary'], player) connect_entrance(world, sanc_door, 'Inverted Dark Sanctuary', player)
world.get_entrance('Inverted Dark Sanctuary Exit', player).connect(world.get_entrance(sanc_door, player).parent_region)
# tavern back door cannot be shuffled yet # tavern back door cannot be shuffled yet
connect_doors(world, ['Tavern North'], ['Tavern'], player) connect_doors(world, ['Tavern North'], ['Tavern'], player)
@@ -1410,7 +1420,8 @@ def link_inverted_entrances(world, player):
sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in dw_entrances] sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in dw_entrances]
sanc_door = random.choice(sanc_doors) sanc_door = random.choice(sanc_doors)
dw_entrances.remove(sanc_door) dw_entrances.remove(sanc_door)
connect_doors(world, [sanc_door], ['Inverted Dark Sanctuary'], player) connect_entrance(world, sanc_door, 'Inverted Dark Sanctuary', player)
world.get_entrance('Inverted Dark Sanctuary Exit', player).connect(world.get_entrance(sanc_door, player).parent_region)
# place old man house # place old man house
# no dw must exits in inverted, but we randomize whether cave is in light or dark world # no dw must exits in inverted, but we randomize whether cave is in light or dark world
@@ -1547,7 +1558,8 @@ def link_inverted_entrances(world, player):
sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in entrances] sanc_doors = [door for door in Inverted_Dark_Sanctuary_Doors if door in entrances]
sanc_door = random.choice(sanc_doors) sanc_door = random.choice(sanc_doors)
entrances.remove(sanc_door) entrances.remove(sanc_door)
connect_doors(world, [sanc_door], ['Inverted Dark Sanctuary'], player) connect_entrance(world, sanc_door, 'Inverted Dark Sanctuary', player)
world.get_entrance('Inverted Dark Sanctuary Exit', player).connect(world.get_entrance(sanc_door, player).parent_region)
# tavern back door cannot be shuffled yet # tavern back door cannot be shuffled yet
connect_doors(world, ['Tavern North'], ['Tavern'], player) connect_doors(world, ['Tavern North'], ['Tavern'], player)
@@ -1680,7 +1692,8 @@ def link_inverted_entrances(world, player):
sanc_door = random.choice(sanc_doors) sanc_door = random.choice(sanc_doors)
entrances.remove(sanc_door) entrances.remove(sanc_door)
doors.remove(sanc_door) doors.remove(sanc_door)
connect_doors(world, [sanc_door], ['Inverted Dark Sanctuary'], player) connect_entrance(world, sanc_door, 'Inverted Dark Sanctuary', player)
world.get_entrance('Inverted Dark Sanctuary Exit', player).connect(world.get_entrance(sanc_door, player).parent_region)
# now let's deal with mandatory reachable stuff # now let's deal with mandatory reachable stuff
def extract_reachable_exit(cavelist): def extract_reachable_exit(cavelist):
@@ -2041,6 +2054,14 @@ def simple_shuffle_dungeons(world, player):
else: else:
hc_target = multi_dungeons[2] hc_target = multi_dungeons[2]
# door shuffle should restrict hyrule castle to the light world due to sanc being limited to the LW
if world.doorShuffle[player] != 'vanilla' and hc_target == 'Turtle Rock':
swap_w_dp = random.choice([True, False])
if swap_w_dp:
hc_target, dp_target = dp_target, hc_target
else:
hc_target, tr_target = tr_target, hc_target
# ToDo improve this? # ToDo improve this?
if world.mode[player] != 'inverted': if world.mode[player] != 'inverted':
@@ -2298,6 +2319,8 @@ Bomb_Shop_Multi_Cave_Doors = ['Hyrule Castle Entrance (South)',
'Death Mountain Return Cave (East)', 'Death Mountain Return Cave (East)',
'Death Mountain Return Cave (West)', 'Death Mountain Return Cave (West)',
'Spectacle Rock Cave Peak', 'Spectacle Rock Cave Peak',
'Spectacle Rock Cave',
'Spectacle Rock Cave (Bottom)',
'Paradox Cave (Bottom)', 'Paradox Cave (Bottom)',
'Paradox Cave (Middle)', 'Paradox Cave (Middle)',
'Paradox Cave (Top)', 'Paradox Cave (Top)',
@@ -2638,8 +2661,6 @@ Inverted_Bomb_Shop_Multi_Cave_Doors = ['Hyrule Castle Entrance (South)',
'Death Mountain Return Cave (East)', 'Death Mountain Return Cave (East)',
'Death Mountain Return Cave (West)', 'Death Mountain Return Cave (West)',
'Spectacle Rock Cave Peak', 'Spectacle Rock Cave Peak',
'Spectacle Rock Cave',
'Spectacle Rock Cave (Bottom)',
'Paradox Cave (Bottom)', 'Paradox Cave (Bottom)',
'Paradox Cave (Middle)', 'Paradox Cave (Middle)',
'Paradox Cave (Top)', 'Paradox Cave (Top)',
@@ -2814,7 +2835,10 @@ Isolated_LH_Doors = ['Kings Grave',
'Turtle Rock Isolated Ledge Entrance'] 'Turtle Rock Isolated Ledge Entrance']
# these are connections that cannot be shuffled and always exist. They link together separate parts of the world we need to divide into regions # these are connections that cannot be shuffled and always exist. They link together separate parts of the world we need to divide into regions
mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'), mandatory_connections = [('Links House S&Q', 'Links House'),
('Sanctuary S&Q', 'Sanctuary'),
('Old Man S&Q', 'Old Man House'),
('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'),
('Lake Hylia Central Island Teleporter', 'Dark Lake Hylia Central Island'), ('Lake Hylia Central Island Teleporter', 'Dark Lake Hylia Central Island'),
('Zoras River', 'Zoras River'), ('Zoras River', 'Zoras River'),
('Kings Grave Outer Rocks', 'Kings Grave Area'), ('Kings Grave Outer Rocks', 'Kings Grave Area'),
@@ -2923,7 +2947,11 @@ mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central
('Pyramid Drop', 'East Dark World') ('Pyramid Drop', 'East Dark World')
] ]
inverted_mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'), inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'),
('Dark Sanctuary S&Q', 'Inverted Dark Sanctuary'),
('Old Man S&Q', 'Old Man House'),
('Castle Ledge S&Q', 'Hyrule Castle Ledge'),
('Lake Hylia Central Island Pier', 'Lake Hylia Central Island'),
('Lake Hylia Island', 'Lake Hylia Island'), ('Lake Hylia Island', 'Lake Hylia Island'),
('Zoras River', 'Zoras River'), ('Zoras River', 'Zoras River'),
('Kings Grave Outer Rocks', 'Kings Grave Area'), ('Kings Grave Outer Rocks', 'Kings Grave Area'),
@@ -3352,6 +3380,7 @@ inverted_default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'
('Inverted Links House Exit', 'South Dark World'), ('Inverted Links House Exit', 'South Dark World'),
('Inverted Big Bomb Shop', 'Inverted Big Bomb Shop'), ('Inverted Big Bomb Shop', 'Inverted Big Bomb Shop'),
('Inverted Dark Sanctuary', 'Inverted Dark Sanctuary'), ('Inverted Dark Sanctuary', 'Inverted Dark Sanctuary'),
('Inverted Dark Sanctuary Exit', 'West Dark World'),
('Old Man Cave (West)', 'Bumper Cave'), ('Old Man Cave (West)', 'Bumper Cave'),
('Old Man Cave (East)', 'Death Mountain Return Cave'), ('Old Man Cave (East)', 'Death Mountain Return Cave'),
('Old Man Cave Exit (West)', 'West Dark World'), ('Old Man Cave Exit (West)', 'West Dark World'),
@@ -3484,12 +3513,24 @@ inverted_default_dungeon_connections = [('Desert Palace Entrance (South)', 'Dese
('Ice Palace Exit', 'Dark Lake Hylia') ('Ice Palace Exit', 'Dark Lake Hylia')
] ]
indirect_connections = {
'Turtle Rock (Top)': 'Turtle Rock',
'East Dark World': 'Pyramid Fairy',
'Big Bomb Shop': 'Pyramid Fairy',
'Dark Desert': 'Pyramid Fairy',
'West Dark World': 'Pyramid Fairy',
'South Dark World': 'Pyramid Fairy',
'Light World': 'Pyramid Fairy',
'Old Man Cave': 'Old Man S&Q'
}
# format: # format:
# Key=Name # Key=Name
# addr = (door_index, exitdata) # multiexit # addr = (door_index, exitdata) # multiexit
# | ([addr], None) # holes # | ([addr], None) # holes
# exitdata = (room_id, ow_area, vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x, unknown_1, unknown_2, door_1, door_2) # exitdata = (room_id, ow_area, vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x, unknown_1, unknown_2, door_1, door_2)
# ToDo somehow merge this with creation of the locations
# ToDo somehow merge this with creation of the locations # ToDo somehow merge this with creation of the locations
door_addresses = {'Links House': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0x0ae8, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfe, 0x0816, 0x0000)), door_addresses = {'Links House': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0x0ae8, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfe, 0x0816, 0x0000)),
'Inverted Big Bomb Shop': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0x0ae8, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfe, 0x0816, 0x0000)), 'Inverted Big Bomb Shop': (0x00, (0x0104, 0x2c, 0x0506, 0x0a9a, 0x0832, 0x0ae8, 0x08b8, 0x0b07, 0x08bf, 0x06, 0xfe, 0x0816, 0x0000)),
+14 -8
View File
@@ -76,8 +76,10 @@ def distribute_items_cutoff(world, cutoffrate=0.33):
world.push_item(spot_to_fill, item_to_place, True) world.push_item(spot_to_fill, item_to_place, True)
itempool.remove(item_to_place) itempool.remove(item_to_place)
fill_locations.remove(spot_to_fill) fill_locations.remove(spot_to_fill)
unplaced = [item.name for item in itempool]
logging.getLogger('').debug('Unplaced items: %s - Unfilled Locations: %s', [item.name for item in itempool], [location.name for location in fill_locations]) unfilled = [location.name for location in fill_locations]
if unplaced or unfilled:
logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
def distribute_items_staleness(world): def distribute_items_staleness(world):
@@ -158,8 +160,10 @@ def distribute_items_staleness(world):
itempool.remove(item_to_place) itempool.remove(item_to_place)
fill_locations.remove(spot_to_fill) fill_locations.remove(spot_to_fill)
logging.getLogger('').debug('Unplaced items: %s - Unfilled Locations: %s', [item.name for item in itempool], [location.name for location in fill_locations]) unplaced = [item.name for item in itempool]
unfilled = [location.name for location in fill_locations]
if unplaced or unfilled:
logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool = None, single_player_placement = False): def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool = None, single_player_placement = False):
def sweep_from_pool(): def sweep_from_pool():
@@ -226,7 +230,7 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool =
def valid_key_placement(item, location, itempool, world): def valid_key_placement(item, location, itempool, world):
if (not item.smallkey and not item.bigkey) or item.player != location.player or world.retro[item.player]: if (not item.smallkey and not item.bigkey) or item.player != location.player or world.retro[item.player] or world.logic[item.player] == 'nologic':
return True return True
dungeon = location.parent_region.dungeon dungeon = location.parent_region.dungeon
if dungeon: if dungeon:
@@ -291,7 +295,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.keyshuffle[item.player] and world.mode[item.player] == 'standard' else 0) progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.keyshuffle[item.player] and world.mode[item.player] == 'standard' else 0)
fill_restrictive(world, world.state, fill_locations, progitempool, fill_restrictive(world, world.state, fill_locations, progitempool,
keys_in_itempool={player: world.keyshuffle[player] for player in range(1, world.players+1)}) keys_in_itempool={player: world.keyshuffle[player] for player in range(1, world.players + 1)})
random.shuffle(fill_locations) random.shuffle(fill_locations)
@@ -299,8 +303,10 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
fast_fill(world, restitempool, fill_locations) fast_fill(world, restitempool, fill_locations)
logging.getLogger('').debug('Unplaced items: %s - Unfilled Locations: %s', [item.name for item in progitempool + prioitempool + restitempool], [location.name for location in fill_locations]) unplaced = [item.name for item in prioitempool + restitempool]
unfilled = [location.name for location in fill_locations]
if unplaced or unfilled:
logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
def fast_fill(world, item_pool, fill_locations): def fast_fill(world, item_pool, fill_locations):
while item_pool and fill_locations: while item_pool and fill_locations:
+2 -1
View File
@@ -6,6 +6,7 @@ from Regions import create_lw_region, create_dw_region, create_cave_region, crea
def create_inverted_regions(world, player): def create_inverted_regions(world, player):
world.regions += [ world.regions += [
create_dw_region(player, 'Menu', None, ['Links House S&Q', 'Dark Sanctuary S&Q', 'Old Man S&Q', 'Castle Ledge S&Q']),
create_lw_region(player, 'Light World', ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest', 'Bombos Tablet'], create_lw_region(player, 'Light World', ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest', 'Bombos Tablet'],
["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Kings Grave Outer Rocks', 'Dam', ["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Kings Grave Outer Rocks', 'Dam',
'Inverted Big Bomb Shop', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave', 'Inverted Big Bomb Shop', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave',
@@ -176,7 +177,7 @@ def create_inverted_regions(world, player):
create_cave_region(player, 'C-Shaped House', 'a house with a chest', ['C-Shaped House']), create_cave_region(player, 'C-Shaped House', 'a house with a chest', ['C-Shaped House']),
create_cave_region(player, 'Chest Game', 'a game of 16 chests', ['Chest Game']), create_cave_region(player, 'Chest Game', 'a game of 16 chests', ['Chest Game']),
create_cave_region(player, 'Red Shield Shop', 'the rare shop'), create_cave_region(player, 'Red Shield Shop', 'the rare shop'),
create_cave_region(player, 'Inverted Dark Sanctuary', 'a storyteller'), create_cave_region(player, 'Inverted Dark Sanctuary', 'a storyteller', None, ['Inverted Dark Sanctuary Exit']),
create_cave_region(player, 'Bumper Cave', 'a connector', None, ['Bumper Cave Exit (Bottom)', 'Bumper Cave Exit (Top)']), create_cave_region(player, 'Bumper Cave', 'a connector', None, ['Bumper Cave Exit (Bottom)', 'Bumper Cave Exit (Top)']),
create_dw_region(player, 'Skull Woods Forest', None, ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', create_dw_region(player, 'Skull Woods Forest', None, ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)',
'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)']), 'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)']),
+5 -2
View File
@@ -265,7 +265,7 @@ def generate_itempool(world, player):
if player in world.pool_adjustment.keys(): if player in world.pool_adjustment.keys():
amt = world.pool_adjustment[player] amt = world.pool_adjustment[player]
if amt < 0: if amt < 0:
for _ in range(0, amt): for _ in range(amt, 0):
pool.remove('Rupees (20)') pool.remove('Rupees (20)')
elif amt > 0: elif amt > 0:
for _ in range(0, amt): for _ in range(0, amt):
@@ -287,9 +287,12 @@ def generate_itempool(world, player):
if item in ['Progressive Bow', 'Bow'] and not found_bow: if item in ['Progressive Bow', 'Bow'] and not found_bow:
found_bow = True found_bow = True
possible_weapons.append(item) possible_weapons.append(item)
if item in ['Hammer', 'Bombs (10)', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']: if item in ['Hammer', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']:
if item not in possible_weapons: if item not in possible_weapons:
possible_weapons.append(item) possible_weapons.append(item)
if item in ['Bombs (10)']:
if item not in possible_weapons and world.doorShuffle[player] != 'crossed':
possible_weapons.append(item)
starting_weapon = random.choice(possible_weapons) starting_weapon = random.choice(possible_weapons)
placed_items["Link's Uncle"] = starting_weapon placed_items["Link's Uncle"] = starting_weapon
pool.remove(starting_weapon) pool.remove(starting_weapon)
+117 -28
View File
@@ -41,7 +41,8 @@ class KeyLogic(object):
def __init__(self, dungeon_name): def __init__(self, dungeon_name):
self.door_rules = {} self.door_rules = {}
self.bk_restricted = set() self.bk_restricted = set() # subset of free locations
self.bk_locked = set() # includes potentially other locations and key only locations
self.sm_restricted = set() self.sm_restricted = set()
self.small_key_name = dungeon_keys[dungeon_name] self.small_key_name = dungeon_keys[dungeon_name]
self.bk_name = dungeon_bigs[dungeon_name] self.bk_name = dungeon_bigs[dungeon_name]
@@ -50,7 +51,9 @@ class KeyLogic(object):
self.logic_min = {} self.logic_min = {}
self.logic_max = {} self.logic_max = {}
self.placement_rules = [] self.placement_rules = []
self.location_rules = {}
self.outside_keys = 0 self.outside_keys = 0
self.dungeon = dungeon_name
def check_placement(self, unplaced_keys, big_key_loc=None): def check_placement(self, unplaced_keys, big_key_loc=None):
for rule in self.placement_rules: for rule in self.placement_rules:
@@ -77,6 +80,18 @@ class DoorRules(object):
self.opposite = None self.opposite = None
class LocationRule(object):
def __init__(self):
self.small_key_num = 0
self.conditional_sets = []
class ConditionalLocationRule(object):
def __init__(self, conditional_set):
self.conditional_set = conditional_set
self.small_key_num = 0
class PlacementRule(object): class PlacementRule(object):
def __init__(self): def __init__(self):
@@ -88,6 +103,7 @@ class PlacementRule(object):
self.check_locations_w_bk = None self.check_locations_w_bk = None
self.check_locations_wo_bk = None self.check_locations_wo_bk = None
self.bk_relevant = True self.bk_relevant = True
self.key_reduced = False
def contradicts(self, rule, unplaced_keys, big_key_loc): def contradicts(self, rule, unplaced_keys, big_key_loc):
bk_blocked = big_key_loc in self.bk_conditional_set if self.bk_conditional_set else False bk_blocked = big_key_loc in self.bk_conditional_set if self.bk_conditional_set else False
@@ -208,6 +224,7 @@ def analyze_dungeon(key_layout, world, player):
find_bk_locked_sections(key_layout, world, player) find_bk_locked_sections(key_layout, world, player)
key_logic.bk_chests.update(find_big_chest_locations(key_layout.all_chest_locations)) key_logic.bk_chests.update(find_big_chest_locations(key_layout.all_chest_locations))
key_logic.bk_chests.update(find_big_key_locked_locations(key_layout.all_chest_locations))
if world.retro[player] and world.mode[player] != 'standard': if world.retro[player] and world.mode[player] != 'standard':
return return
@@ -297,7 +314,9 @@ def create_exhaustive_placement_rules(key_layout, world, player):
rule.check_locations_wo_bk = set(filter_big_chest(accessible_loc)) rule.check_locations_wo_bk = set(filter_big_chest(accessible_loc))
if valid_rule: if valid_rule:
key_logic.placement_rules.append(rule) key_logic.placement_rules.append(rule)
adjust_locations_rules(key_logic, rule, accessible_loc, key_layout, key_counter, max_ctr)
refine_placement_rules(key_layout, max_ctr) refine_placement_rules(key_layout, max_ctr)
refine_location_rules(key_layout)
def placement_self_lock_adjustment(rule, max_ctr, blocked_loc, ctr, world, player): def placement_self_lock_adjustment(rule, max_ctr, blocked_loc, ctr, world, player):
@@ -319,6 +338,37 @@ def check_sm_restriction_needed(key_layout, max_ctr, rule, blocked):
return False return False
def adjust_locations_rules(key_logic, rule, accessible_loc, key_layout, key_counter, max_ctr):
if rule.bk_conditional_set:
test_set = (rule.bk_conditional_set - key_logic.bk_locked) - set(max_ctr.key_only_locations.keys())
needed = rule.needed_keys_wo_bk if test_set else 0
else:
test_set = None
needed = rule.needed_keys_w_bk
if needed > 0:
accessible_loc.update(key_counter.other_locations)
blocked_loc = key_layout.all_locations-accessible_loc
for location in blocked_loc:
if location not in key_logic.location_rules.keys():
loc_rule = LocationRule()
key_logic.location_rules[location] = loc_rule
else:
loc_rule = key_logic.location_rules[location]
if test_set:
if location not in key_logic.bk_locked:
cond_rule = None
for other in loc_rule.conditional_sets:
if other.conditional_set == test_set:
cond_rule = other
break
if not cond_rule:
cond_rule = ConditionalLocationRule(test_set)
loc_rule.conditional_sets.append(cond_rule)
cond_rule.small_key_num = max(needed, cond_rule.small_key_num)
else:
loc_rule.small_key_num = max(needed, loc_rule.small_key_num)
def refine_placement_rules(key_layout, max_ctr): def refine_placement_rules(key_layout, max_ctr):
key_logic = key_layout.key_logic key_logic = key_layout.key_logic
changed = True changed = True
@@ -407,6 +457,20 @@ def refine_placement_rules(key_layout, max_ctr):
removed_rules[r2] = r1 removed_rules[r2] = r1
def refine_location_rules(key_layout):
locs_to_remove = []
for loc, rule in key_layout.key_logic.location_rules.items():
conditions_to_remove = []
for cond_rule in rule.conditional_sets:
if cond_rule.small_key_num <= rule.small_key_num:
conditions_to_remove.append(cond_rule)
rule.conditional_sets = [x for x in rule.conditional_sets if x not in conditions_to_remove]
if rule.small_key_num == 0 and len(rule.conditional_sets) == 0:
locs_to_remove.append(loc)
for loc in locs_to_remove:
del key_layout.key_logic.location_rules[loc]
def create_inclusive_rule(key_layout, max_ctr, code, key_counter, blocked_loc, accessible_loc, min_keys, world, player): def create_inclusive_rule(key_layout, max_ctr, code, key_counter, blocked_loc, accessible_loc, min_keys, world, player):
key_logic = key_layout.key_logic key_logic = key_layout.key_logic
rule = PlacementRule() rule = PlacementRule()
@@ -421,6 +485,7 @@ def create_inclusive_rule(key_layout, max_ctr, code, key_counter, blocked_loc, a
rule.check_locations_w_bk = accessible_loc rule.check_locations_w_bk = accessible_loc
check_sm_restriction_needed(key_layout, max_ctr, rule, blocked_loc) check_sm_restriction_needed(key_layout, max_ctr, rule, blocked_loc)
key_logic.placement_rules.append(rule) key_logic.placement_rules.append(rule)
adjust_locations_rules(key_logic, rule, accessible_loc, key_layout, key_counter, max_ctr)
def queue_sorter(queue_item): def queue_sorter(queue_item):
@@ -438,12 +503,10 @@ def queue_sorter_2(queue_item):
def find_bk_locked_sections(key_layout, world, player): def find_bk_locked_sections(key_layout, world, player):
if key_layout.big_key_special:
return
key_counters = key_layout.key_counters key_counters = key_layout.key_counters
key_logic = key_layout.key_logic key_logic = key_layout.key_logic
bk_key_not_required = set() bk_not_required = set()
big_chest_allowed_big_key = world.accessibility[player] != 'locations' big_chest_allowed_big_key = world.accessibility[player] != 'locations'
for counter in key_counters.values(): for counter in key_counters.values():
key_layout.all_chest_locations.update(counter.free_locations) key_layout.all_chest_locations.update(counter.free_locations)
@@ -452,10 +515,19 @@ def find_bk_locked_sections(key_layout, world, player):
if counter.big_key_opened and counter.important_location: if counter.big_key_opened and counter.important_location:
big_chest_allowed_big_key = False big_chest_allowed_big_key = False
if not counter.big_key_opened: if not counter.big_key_opened:
bk_key_not_required.update(counter.free_locations) bk_not_required.update(counter.free_locations)
key_logic.bk_restricted.update(dict.fromkeys(set(key_layout.all_chest_locations).difference(bk_key_not_required))) bk_not_required.update(counter.key_only_locations)
bk_not_required.update(counter.other_locations)
# todo?: handle bk special differently in cross dungeon
# notably: things behind bk doors - relying on the bk door logic atm
if not key_layout.big_key_special:
key_logic.bk_restricted.update(dict.fromkeys(set(key_layout.all_chest_locations).difference(bk_not_required)))
key_logic.bk_locked.update(dict.fromkeys(set(key_layout.all_locations) - bk_not_required))
if not big_chest_allowed_big_key: if not big_chest_allowed_big_key:
key_logic.bk_restricted.update(find_big_chest_locations(key_layout.all_chest_locations)) bk_required_locations = find_big_chest_locations(key_layout.all_chest_locations)
bk_required_locations += find_big_key_locked_locations(key_layout.all_chest_locations)
key_logic.bk_restricted.update(bk_required_locations)
key_logic.bk_locked.update(bk_required_locations)
def empty_counter(counter): def empty_counter(counter):
@@ -903,23 +975,26 @@ def filter_big_chest(locations):
def count_free_locations(state): def count_free_locations(state):
cnt = 0 cnt = 0
for loc in state.found_locations: for loc in state.found_locations:
if '- Prize' not in loc.name and loc.name not in dungeon_events and loc.name not in key_only_locations and loc.name not in ['Agahnim 1', 'Agahnim 2', 'Hyrule Castle - Big Key Drop']: if '- Prize' not in loc.name and loc.name not in dungeon_events and not loc.forced_item:
cnt += 1 if loc.name not in ['Agahnim 1', 'Agahnim 2']:
cnt += 1
return cnt return cnt
def count_locations_exclude_big_chest(state): def count_locations_exclude_big_chest(state):
cnt = 0 cnt = 0
for loc in state.found_locations: for loc in state.found_locations:
if '- Big Chest' not in loc.name and '- Prize' not in loc.name and loc.name not in dungeon_events and loc.name not in key_only_locations and loc.name not in ['Agahnim 1', 'Agahnim 2', 'Hyrule Castle - Big Key Drop']: if '- Big Chest' not in loc.name and '- Prize' not in loc.name and loc.name not in dungeon_events:
cnt += 1 if not loc.forced_item and loc.name not in ['Agahnim 1', 'Agahnim 2', "Hyrule Castle - Zelda's Chest",
"Thieves' Town - Blind's Cell"]:
cnt += 1
return cnt return cnt
def count_key_only_locations(state): def count_small_key_only_locations(state):
cnt = 0 cnt = 0
for loc in state.found_locations: for loc in state.found_locations:
if loc.name in key_only_locations: if loc.forced_item and loc.item.smallkey:
cnt += 1 cnt += 1
return cnt return cnt
@@ -936,6 +1011,14 @@ def find_big_chest_locations(locations):
return ret return ret
def find_big_key_locked_locations(locations):
ret = []
for loc in locations:
if loc.name in ["Thieves' Town - Blind's Cell", "Hyrule Castle - Zelda's Chest"]:
ret.append(loc)
return ret
def expand_key_state(state, flat_proposal, world, player): def expand_key_state(state, flat_proposal, world, player):
while len(state.avail_doors) > 0: while len(state.avail_doors) > 0:
exp_door = state.next_avail_door() exp_door = state.next_avail_door()
@@ -1149,7 +1232,7 @@ def set_paired_rules(key_logic, world, player):
# Soft lock stuff # Soft lock stuff
def validate_key_layout(key_layout, world, player): def validate_key_layout(key_layout, world, player):
# retro is all good - except for hyrule castle in standard mode # retro is all good - except for hyrule castle in standard mode
if world.retro[player] and (world.mode[player] != 'standard' or key_layout.sector.name != 'Hyrule Castle'): if (world.retro[player] and (world.mode[player] != 'standard' or key_layout.sector.name != 'Hyrule Castle')) or world.logic[player] == 'nologic':
return True return True
flat_proposal = key_layout.flat_prop flat_proposal = key_layout.flat_prop
state = ExplorationState(dungeon=key_layout.sector.name) state = ExplorationState(dungeon=key_layout.sector.name)
@@ -1169,8 +1252,8 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
return True # I think that's the end return True # I think that's the end
# todo: fix state to separate out these types # todo: fix state to separate out these types
ttl_locations = count_free_locations(state) if state.big_key_opened else count_locations_exclude_big_chest(state) ttl_locations = count_free_locations(state) if state.big_key_opened else count_locations_exclude_big_chest(state)
ttl_key_only = count_key_only_locations(state) ttl_small_key_only = count_small_key_only_locations(state)
available_small_locations = cnt_avail_small_locations(ttl_locations, ttl_key_only, state, world, player) available_small_locations = cnt_avail_small_locations(ttl_locations, ttl_small_key_only, state, world, player)
available_big_locations = cnt_avail_big_locations(ttl_locations, state, world, player) available_big_locations = cnt_avail_big_locations(ttl_locations, state, world, player)
if invalid_self_locking_key(key_layout, state, prev_state, prev_avail, world, player): if invalid_self_locking_key(key_layout, state, prev_state, prev_avail, world, player):
return False return False
@@ -1184,7 +1267,7 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
state_copy = state.copy() state_copy = state.copy()
open_a_door(exp_door.door, state_copy, flat_proposal) open_a_door(exp_door.door, state_copy, flat_proposal)
state_copy.used_smalls += 1 state_copy.used_smalls += 1
if state_copy.used_smalls > ttl_key_only: if state_copy.used_smalls > ttl_small_key_only:
state_copy.used_locations += 1 state_copy.used_locations += 1
code = state_id(state_copy, flat_proposal) code = state_id(state_copy, flat_proposal)
if code not in checked_states.keys(): if code not in checked_states.keys():
@@ -1260,7 +1343,10 @@ def create_key_counters(key_layout, world, player):
key_counters = {} key_counters = {}
flat_proposal = key_layout.flat_prop flat_proposal = key_layout.flat_prop
state = ExplorationState(dungeon=key_layout.sector.name) state = ExplorationState(dungeon=key_layout.sector.name)
state.key_locations = len(world.get_dungeon(key_layout.sector.name, player).small_keys) if world.doorShuffle[player] == 'vanilla':
state.key_locations = len(world.get_dungeon(key_layout.sector.name, player).small_keys)
else:
state.key_locations = world.dungeon_layouts[player][key_layout.sector.name].key_doors_num
state.big_key_special = world.get_region('Hyrule Dungeon Cellblock', player) in key_layout.sector.regions state.big_key_special = world.get_region('Hyrule Dungeon Cellblock', player) in key_layout.sector.regions
for region in key_layout.start_regions: for region in key_layout.start_regions:
state.visit_region(region, key_checks=True) state.visit_region(region, key_checks=True)
@@ -1294,8 +1380,10 @@ def create_key_counter(state, key_layout, world, player):
if important_location(loc, world, player): if important_location(loc, world, player):
key_counter.important_location = True key_counter.important_location = True
key_counter.other_locations[loc] = None key_counter.other_locations[loc] = None
elif loc.event and 'Small Key' in loc.item.name: elif loc.forced_item and loc.item.name == key_layout.key_logic.small_key_name:
key_counter.key_only_locations[loc] = None key_counter.key_only_locations[loc] = None
elif loc.forced_item and loc.item.name == key_layout.key_logic.bk_name:
key_counter.other_locations[loc] = None
elif loc.name not in dungeon_events: elif loc.name not in dungeon_events:
key_counter.free_locations[loc] = None key_counter.free_locations[loc] = None
else: else:
@@ -1306,11 +1394,6 @@ def create_key_counter(state, key_layout, world, player):
key_counter.big_key_opened = state.visited(world.get_region('Hyrule Dungeon Cellblock', player)) key_counter.big_key_opened = state.visited(world.get_region('Hyrule Dungeon Cellblock', player))
else: else:
key_counter.big_key_opened = state.big_key_opened key_counter.big_key_opened = state.big_key_opened
# if soft_lock_check:
# avail_chests = available_chest_small_keys(key_counter, key_counter.big_key_opened, world)
# avail_keys = avail_chests + len(key_counter.key_only_locations)
# if avail_keys <= key_counter.used_keys and avail_keys < key_layout.max_chests + key_layout.max_drops:
# raise SoftLockException()
return key_counter return key_counter
@@ -1322,13 +1405,14 @@ def imp_locations_factory(world, player):
if imp_locations: if imp_locations:
return imp_locations return imp_locations
imp_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden'] imp_locations = ['Agahnim 1', 'Agahnim 2', 'Attic Cracked Floor', 'Suspicious Maiden']
if world.mode[player] == 'standard' or world.doorShuffle[player] == 'crossed': if world.mode[player] == 'standard':
imp_locations.append('Hyrule Dungeon Cellblock') imp_locations.append('Zelda Pickup')
imp_locations.append('Zelda Dropoff')
return imp_locations return imp_locations
def important_location(loc, world, player): def important_location(loc, world, player):
return '- Prize' in loc.name or loc.name in imp_locations_factory(world, player) return '- Prize' in loc.name or loc.name in imp_locations_factory(world, player) or (loc.forced_item is not None and loc.item.bigkey)
def create_odd_key_counter(door, parent_counter, key_layout, world, player): def create_odd_key_counter(door, parent_counter, key_layout, world, player):
@@ -1390,6 +1474,8 @@ def find_counter_hint(opened_doors, bk_hint, key_layout):
def find_max_counter(key_layout): def find_max_counter(key_layout):
max_counter = find_counter_hint(dict.fromkeys(key_layout.flat_prop), False, key_layout) max_counter = find_counter_hint(dict.fromkeys(key_layout.flat_prop), False, key_layout)
if max_counter is None:
raise Exception("Max Counter is none - something is amiss")
if len(max_counter.child_doors) > 0: if len(max_counter.child_doors) > 0:
max_counter = find_counter_hint(dict.fromkeys(key_layout.flat_prop), True, key_layout) max_counter = find_counter_hint(dict.fromkeys(key_layout.flat_prop), True, key_layout)
return max_counter return max_counter
@@ -1604,7 +1690,10 @@ def validate_key_placement(key_layout, world, player):
for code, counter in key_layout.key_counters.items(): for code, counter in key_layout.key_counters.items():
if len(counter.child_doors) == 0: if len(counter.child_doors) == 0:
continue continue
big_found = any(i.item == dungeon.big_key for i in counter.free_locations if "- Big Chest" not in i.name) or big_key_outside if key_layout.big_key_special:
big_found = any(i.forced_item is not None and i.item.bigkey for i in counter.other_locations) or big_key_outside
else:
big_found = any(i.item is not None and i.item == dungeon.big_key for i in counter.free_locations if "- Big Chest" not in i.name) or big_key_outside
if counter.big_key_opened and not big_found: if counter.big_key_opened and not big_found:
continue # Can't get to this state continue # Can't get to this state
found_locations = set(i for i in counter.free_locations if big_found or "- Big Chest" not in i.name) found_locations = set(i for i in counter.free_locations if big_found or "- Big Chest" not in i.name)
+37 -21
View File
@@ -8,7 +8,7 @@ import random
import time import time
import zlib import zlib
from BaseClasses import World, CollectionState, Item, Region, Location, Shop from BaseClasses import World, CollectionState, Item, Region, Location, Shop, Entrance
from Items import ItemFactory from Items import ItemFactory
from KeyDoorShuffle import validate_key_placement from KeyDoorShuffle import validate_key_placement
from Regions import create_regions, create_shops, mark_light_world_regions, create_dungeon_regions from Regions import create_regions, create_shops, mark_light_world_regions, create_dungeon_regions
@@ -24,8 +24,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
from ItemList import generate_itempool, difficulties, fill_prizes from ItemList import generate_itempool, difficulties, fill_prizes
from Utils import output_path, parse_player_names from Utils import output_path, parse_player_names
__version__ = '0.0.21dev' __version__ = '0.1.0-dev'
class EnemizerError(RuntimeError): class EnemizerError(RuntimeError):
pass pass
@@ -39,7 +38,9 @@ def main(args, seed=None, fish=None):
start = time.perf_counter() start = time.perf_counter()
# initialize the world # initialize the world
world = World(args.multi, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, args.accessibility, args.shuffleganon, args.retro, args.custom, args.customitemarray, args.hints) world = World(args.multi, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords,
args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm,
args.accessibility, args.shuffleganon, args.retro, args.custom, args.customitemarray, args.hints)
logger = logging.getLogger('') logger = logging.getLogger('')
if seed is None: if seed is None:
random.seed(None) random.seed(None)
@@ -61,6 +62,7 @@ def main(args, seed=None, fish=None):
world.enemy_health = args.enemy_health.copy() world.enemy_health = args.enemy_health.copy()
world.enemy_damage = args.enemy_damage.copy() world.enemy_damage = args.enemy_damage.copy()
world.beemizer = args.beemizer.copy() world.beemizer = args.beemizer.copy()
world.intensity = {player: random.randint(1, 3) if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)}
world.experimental = args.experimental.copy() world.experimental = args.experimental.copy()
world.dungeon_counters = args.dungeon_counters.copy() world.dungeon_counters = args.dungeon_counters.copy()
world.fish = fish world.fish = fish
@@ -146,17 +148,18 @@ def main(args, seed=None, fish=None):
fill_dungeons(world) fill_dungeons(world)
for player in range(1, world.players+1): for player in range(1, world.players+1):
for key_layout in world.key_layout[player].values(): if world.logic[player] != 'nologic':
if not validate_key_placement(key_layout, world, player): for key_layout in world.key_layout[player].values():
raise RuntimeError( if not validate_key_placement(key_layout, world, player):
"%s: %s (%s %d)" % raise RuntimeError(
( "%s: %s (%s %d)" %
world.fish.translate("cli","cli","keylock.detected"), (
key_layout.sector.name, world.fish.translate("cli", "cli", "keylock.detected"),
world.fish.translate("cli","cli","player"), key_layout.sector.name,
player world.fish.translate("cli", "cli", "player"),
) player
) )
)
logger.info(world.fish.translate("cli","cli","fill.world")) logger.info(world.fish.translate("cli","cli","fill.world"))
@@ -287,7 +290,8 @@ def main(args, seed=None, fish=None):
"roms": rom_names, "roms": rom_names,
"remote_items": [player for player in range(1, world.players + 1) if world.remote_items[player]], "remote_items": [player for player in range(1, world.players + 1) if world.remote_items[player]],
"locations": [((location.address, location.player), (location.item.code, location.item.player)) "locations": [((location.address, location.player), (location.item.code, location.item.player))
for location in world.get_filled_locations() if type(location.address) is int] for location in world.get_filled_locations() if type(location.address) is int],
"tags" : ["DR"]
}).encode("utf-8")) }).encode("utf-8"))
if args.jsonout: if args.jsonout:
jsonout["multidata"] = list(multidata) jsonout["multidata"] = list(multidata)
@@ -328,7 +332,9 @@ def main(args, seed=None, fish=None):
def copy_world(world): def copy_world(world):
# ToDo: Not good yet # ToDo: Not good yet
ret = World(world.players, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.accessibility, world.shuffle_ganon, world.retro, world.custom, world.customitemarray, world.hints) ret = World(world.players, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm,
world.accessibility, world.shuffle_ganon, world.retro, world.custom, world.customitemarray, world.hints)
ret.teams = world.teams ret.teams = world.teams
ret.player_names = copy.deepcopy(world.player_names) ret.player_names = copy.deepcopy(world.player_names)
ret.remote_items = world.remote_items.copy() ret.remote_items = world.remote_items.copy()
@@ -363,6 +369,8 @@ def copy_world(world):
ret.enemy_health = world.enemy_health.copy() ret.enemy_health = world.enemy_health.copy()
ret.enemy_damage = world.enemy_damage.copy() ret.enemy_damage = world.enemy_damage.copy()
ret.beemizer = world.beemizer.copy() ret.beemizer = world.beemizer.copy()
ret.intensity = world.intensity.copy()
ret.experimental = world.experimental.copy()
for player in range(1, world.players + 1): for player in range(1, world.players + 1):
if world.mode[player] != 'inverted': if world.mode[player] != 'inverted':
@@ -371,11 +379,17 @@ def copy_world(world):
create_inverted_regions(ret, player) create_inverted_regions(ret, player)
create_dungeon_regions(ret, player) create_dungeon_regions(ret, player)
create_shops(ret, player) create_shops(ret, player)
create_doors(ret, player)
create_rooms(ret, player) create_rooms(ret, player)
create_dungeons(ret, player) create_dungeons(ret, player)
copy_dynamic_regions_and_locations(world, ret) copy_dynamic_regions_and_locations(world, ret)
for player in range(1, world.players + 1):
if world.mode[player] == 'standard':
parent = ret.get_region('Menu', player)
target = ret.get_region('Hyrule Castle Secret Entrance', player)
connection = Entrance(player, 'Uncle S&Q', parent)
parent.exits.append(connection)
connection.connect(target)
# copy bosses # copy bosses
for dungeon in world.dungeons: for dungeon in world.dungeons:
@@ -418,6 +432,10 @@ def copy_world(world):
ret.state.stale = {player: True for player in range(1, world.players + 1)} ret.state.stale = {player: True for player in range(1, world.players + 1)}
ret.doors = world.doors ret.doors = world.doors
for door in ret.doors:
entrance = ret.check_for_entrance(door.name, door.player)
if entrance is not None:
entrance.door = door
ret.paired_doors = world.paired_doors ret.paired_doors = world.paired_doors
ret.rooms = world.rooms ret.rooms = world.rooms
ret.inaccessible_regions = world.inaccessible_regions ret.inaccessible_regions = world.inaccessible_regions
@@ -469,7 +487,6 @@ def create_playthrough(world):
logging.getLogger('').debug(world.fish.translate("cli","cli","building.collection.spheres")) logging.getLogger('').debug(world.fish.translate("cli","cli","building.collection.spheres"))
while sphere_candidates: while sphere_candidates:
state.sweep_for_events(key_only=True) state.sweep_for_events(key_only=True)
state.sweep_for_crystal_access()
sphere = [] sphere = []
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres # build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
@@ -487,7 +504,7 @@ def create_playthrough(world):
logging.getLogger('').debug(world.fish.translate("cli","cli","building.calculating.spheres"), len(collection_spheres), len(sphere), len(prog_locations)) logging.getLogger('').debug(world.fish.translate("cli","cli","building.calculating.spheres"), len(collection_spheres), len(sphere), len(prog_locations))
if not sphere: if not sphere:
logging.getLogger('').debug(world.fish.translate("cli","cli","cannot.reach.items"), [world.fish.translate("cli","cli","cannot.reach.item") % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates]) logging.getLogger('').error(world.fish.translate("cli","cli","cannot.reach.items"), [world.fish.translate("cli","cli","cannot.reach.item") % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates])
if any([world.accessibility[location.item.player] != 'none' for location in sphere_candidates]): if any([world.accessibility[location.item.player] != 'none' for location in sphere_candidates]):
raise RuntimeError(world.fish.translate("cli","cli","cannot.reach.progression")) raise RuntimeError(world.fish.translate("cli","cli","cannot.reach.progression"))
else: else:
@@ -531,7 +548,6 @@ def create_playthrough(world):
collection_spheres = [] collection_spheres = []
while required_locations: while required_locations:
state.sweep_for_events(key_only=True) state.sweep_for_events(key_only=True)
state.sweep_for_crystal_access()
sphere = list(filter(lambda loc: state.can_reach(loc) and state.not_flooding_a_key(world, loc), required_locations)) sphere = list(filter(lambda loc: state.can_reach(loc) and state.not_flooding_a_key(world, loc), required_locations))
+3 -2
View File
@@ -149,9 +149,10 @@ def roll_settings(weights):
ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla' ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla'
door_shuffle = get_choice('door_shuffle') door_shuffle = get_choice('door_shuffle')
ret.door_shuffle = door_shuffle if door_shuffle != 'none' else 'vanilla' ret.door_shuffle = door_shuffle if door_shuffle != 'none' else 'vanilla'
ret.intensity = get_choice('intensity')
ret.experimental = get_choice('experimental') == 'on' ret.experimental = get_choice('experimental') == 'on'
ret.dungeon_counters = get_choice('dungeon_counters') ret.dungeon_counters = get_choice('dungeon_counters') if 'dungeon_counters' in weights else 'default'
if ret.dungeon_counters == 'default': if ret.dungeon_counters == 'default':
ret.dungeon_counters = 'pickup' if ret.door_shuffle != 'vanilla' or ret.compassshuffle == 'on' else 'off' ret.dungeon_counters = 'pickup' if ret.door_shuffle != 'vanilla' or ret.compassshuffle == 'on' else 'off'
@@ -166,7 +167,7 @@ def roll_settings(weights):
ret.crystals_gt = get_choice('tower_open') ret.crystals_gt = get_choice('tower_open')
ret.crystals_ganon = get_choice('ganon_open') ret.crystals_ganon = get_choice('ganon_open')
ret.mode = get_choice('world_state') ret.mode = get_choice('world_state')
if ret.mode == 'retro': if ret.mode == 'retro':
+16
View File
@@ -36,6 +36,16 @@ Doors are shuffled between dungeons as well.
Doors are not shuffled. Doors are not shuffled.
## Intensity
#### Level 1
Normal door and spiral staircases are shuffled
#### Level 2
Same as Level 1 plus open edges and straight staircases are shuffled.
#### Level 3 (Coming soon)
Same as Level 2 plus Dungeon Lobbies are shuffled
## Map/Compass/Small Key/Big Key shuffle (aka Keysanity) ## Map/Compass/Small Key/Big Key shuffle (aka Keysanity)
These settings allow dungeon specific items to be distributed anywhere in the world and not just in their native dungeon. These settings allow dungeon specific items to be distributed anywhere in the world and not just in their native dungeon.
@@ -75,3 +85,9 @@ Show the help message and exit.
``` ```
For specifying the door shuffle you want as above. (default: basic) For specifying the door shuffle you want as above. (default: basic)
```
--intensity
```
For specifying the door shuffle intensity level you want as above. (default: 2)
+29 -22
View File
@@ -1,31 +1,38 @@
# New Features # New Features
* Mirror Scroll no longer erases blocks, the real mirror still will. (Sorry!) * Crossed Dungeon generation improvements
* Standard+Crossed Dungeon now gives you a little magic, a few bombs, and a few arrows if you die or S&Q after meeting your uncle (also works with mirror/scroll) * Standard mode generation improvements
* Dungeon reminder added to hud for Crossed dungeons * Spoiler lists bosses (multiworld compatible)
* Blinking red square added to hud and it indicates a boss room is close by. Only appears if you have the compass. (Basic & Crossed) * Bombs escape not valid for Crossed Dungeon
* Agahnims dungeon items can be started with now * Graph algorithm speed improvement for placements and playthrough
* GUI updates courtesy of Mike T * TT Attic Hint tile should have a crystal switch accessible now
* Updated to v.31.0.5
## Map Features (Crossed only + Experimental) ### Experimental features
* Key counters added to hud. Indicates number of keys in chests (found/total). In small key shuffle, this count indicates how many smalls for that dungeon could be outside it. * Moved BK information and total chest keys per dungeon to keysanity menu. The info there requires compass for all info.
* Total key indicator added to hud if you have found the map. Counts down from the total number of keys in dungeon to 0 as you collect them. * Map still required for on-hud key counter.
* Big Key indicator added to hud. Indicates if BK is not in the dungeon, or if BnC guard has it (Probably will move away from hud if kept) * Added total counter to keysanity the compass/map screen when you have the compass for the dungeon.
* Open "Edge" transitions can now be linked with normal doors
* "Straight" staircases (the ones similar to normal doors) can be linked with both normal doors and edges
Note: Only one of the key indicator will probably become core at most. #### Couple of temporary debug features added:
## Experimental changes * Total item count displays where TFH's goal usually does
* A red square appears in the upper right corner of the hud if the castle gate is closed
* Mirror scroll is now core for non-vanilla Door Shuffle (no longer experimental)
* GT Bosses stay dead in non-vanilla Door Shuffle (no longer experimental)
* Map features listed above are now experimental
# Bug Fixes # Bug Fixes
* Splashing at hobo no longer prevents you from buying bomb capacity upgrades * Fix for Animated Tiles in crossed dungeon
* Small vitreous eyeballs will not drop items (DR basic and crossed only) * Stonewall hardlock no longer reachable from certain drops (Sewer Drop, some Skull Woods drops) that were previously possible
* In Vanilla doors the HC back hallway area was broken - should be better now - also Trap Doors * No logic uses less key door logic
* Firebar speed should now be consistent. Ice palace rooms have slow firebars even if shuffled to other dungeons. Others should have normal speed firebars. * Spoiler log encoding
* Red/Blue pendant swap fixed (originally ER bug) * Enemizer settings made consistent with website
* Compass shuffle vs map shuffle item menu fix (originally ER bug) * Swamp flooded ladders in the basement now requires Flippers
* PoD EG Glitch gets killed on transitions (Only when DR is on)
* Problem with standard logic fixed wanting you to pass through the tapestry backwards to rescue Zelda
* Fixed SRAM corruption issues
* Problem with the dungeons requiring you to take Blind through her attic fixed. (Maiden no longer despawns)
* Hyrule Castle will not be your DW access in various Entrance Shuffles: simple, restricted, dungeonssimple, dungeonsfull
(Also prevents getting stuck in TR opening)
* Beatable only (accessibility: none) no longer fails when there are unplaced items
+10 -6
View File
@@ -4,6 +4,7 @@ from BaseClasses import Region, Location, Entrance, RegionType, Shop, ShopType
def create_regions(world, player): def create_regions(world, player):
world.regions += [ world.regions += [
create_lw_region(player, 'Menu', None, ['Links House S&Q', 'Sanctuary S&Q', 'Old Man S&Q']),
create_lw_region(player, 'Light World', ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest'], create_lw_region(player, 'Light World', ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure', 'Purple Chest'],
["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Zoras River', 'Kings Grave Outer Rocks', 'Dam', ["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Zoras River', 'Kings Grave Outer Rocks', 'Dam',
'Links House', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave', 'Links House', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave',
@@ -177,7 +178,7 @@ def create_regions(world, player):
create_cave_region(player, 'Dark Desert Hint', 'a storyteller'), create_cave_region(player, 'Dark Desert Hint', 'a storyteller'),
create_dw_region(player, 'Dark Death Mountain (West Bottom)', None, ['Spike Cave', 'Spectacle Rock Mirror Spot', 'Dark Death Mountain Fairy']), create_dw_region(player, 'Dark Death Mountain (West Bottom)', None, ['Spike Cave', 'Spectacle Rock Mirror Spot', 'Dark Death Mountain Fairy']),
create_dw_region(player, 'Dark Death Mountain (Top)', None, ['Dark Death Mountain Drop (East)', 'Dark Death Mountain Drop (West)', 'Ganons Tower', 'Superbunny Cave (Top)', create_dw_region(player, 'Dark Death Mountain (Top)', None, ['Dark Death Mountain Drop (East)', 'Dark Death Mountain Drop (West)', 'Ganons Tower', 'Superbunny Cave (Top)',
'Hookshot Cave', 'East Death Mountain (Top) Mirror Spot', 'Turtle Rock']), 'Hookshot Cave', 'East Death Mountain (Top) Mirror Spot', 'Turtle Rock']),
create_dw_region(player, 'Dark Death Mountain Ledge', None, ['Dark Death Mountain Ledge (East)', 'Dark Death Mountain Ledge (West)', 'Mimic Cave Mirror Spot', 'Spiral Cave Mirror Spot']), create_dw_region(player, 'Dark Death Mountain Ledge', None, ['Dark Death Mountain Ledge (East)', 'Dark Death Mountain Ledge (West)', 'Mimic Cave Mirror Spot', 'Spiral Cave Mirror Spot']),
create_dw_region(player, 'Dark Death Mountain Isolated Ledge', None, ['Isolated Ledge Mirror Spot', 'Turtle Rock Isolated Ledge Entrance']), create_dw_region(player, 'Dark Death Mountain Isolated Ledge', None, ['Isolated Ledge Mirror Spot', 'Turtle Rock Isolated Ledge Entrance']),
create_dw_region(player, 'Dark Death Mountain (East Bottom)', None, ['Superbunny Cave (Bottom)', 'Cave Shop (Dark Death Mountain)', 'Fairy Ascension Mirror Spot']), create_dw_region(player, 'Dark Death Mountain (East Bottom)', None, ['Superbunny Cave (Bottom)', 'Cave Shop (Dark Death Mountain)', 'Fairy Ascension Mirror Spot']),
@@ -211,7 +212,8 @@ def create_dungeon_regions(world, player):
'Hyrule Castle East Hall SW']), 'Hyrule Castle East Hall SW']),
create_dungeon_region(player, 'Hyrule Castle West Hall', 'Hyrule Castle', None, ['Hyrule Castle West Hall E', 'Hyrule Castle West Hall S']), create_dungeon_region(player, 'Hyrule Castle West Hall', 'Hyrule Castle', None, ['Hyrule Castle West Hall E', 'Hyrule Castle West Hall S']),
create_dungeon_region(player, 'Hyrule Castle Back Hall', 'Hyrule Castle', None, ['Hyrule Castle Back Hall E', 'Hyrule Castle Back Hall W', 'Hyrule Castle Back Hall Down Stairs']), create_dungeon_region(player, 'Hyrule Castle Back Hall', 'Hyrule Castle', None, ['Hyrule Castle Back Hall E', 'Hyrule Castle Back Hall W', 'Hyrule Castle Back Hall Down Stairs']),
create_dungeon_region(player, 'Hyrule Castle Throne Room', 'Hyrule Castle', None, ['Hyrule Castle Throne Room N', 'Hyrule Castle Throne Room South Stairs']), create_dungeon_region(player, 'Hyrule Castle Throne Room', 'Hyrule Castle', None, ['Hyrule Castle Throne Room Tapestry', 'Hyrule Castle Throne Room South Stairs']),
create_dungeon_region(player, 'Hyrule Castle Behind Tapestry', 'Hyrule Castle', None, ['Hyrule Castle Throne Room N', 'Hyrule Castle Tapestry Backwards']),
create_dungeon_region(player, 'Hyrule Dungeon Map Room', 'Hyrule Castle', ['Hyrule Castle - Map Chest', 'Hyrule Castle - Map Guard Key Drop'], ['Hyrule Dungeon Map Room Key Door S', 'Hyrule Dungeon Map Room Up Stairs']), create_dungeon_region(player, 'Hyrule Dungeon Map Room', 'Hyrule Castle', ['Hyrule Castle - Map Chest', 'Hyrule Castle - Map Guard Key Drop'], ['Hyrule Dungeon Map Room Key Door S', 'Hyrule Dungeon Map Room Up Stairs']),
create_dungeon_region(player, 'Hyrule Dungeon North Abyss', 'Hyrule Castle', None, ['Hyrule Dungeon North Abyss South Edge', 'Hyrule Dungeon North Abyss Key Door N']), create_dungeon_region(player, 'Hyrule Dungeon North Abyss', 'Hyrule Castle', None, ['Hyrule Dungeon North Abyss South Edge', 'Hyrule Dungeon North Abyss Key Door N']),
@@ -314,7 +316,7 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'Hera Beetles', 'Tower of Hera', None, ['Hera Beetles Down Stairs', 'Hera Beetles WS', 'Hera Beetles Holes']), create_dungeon_region(player, 'Hera Beetles', 'Tower of Hera', None, ['Hera Beetles Down Stairs', 'Hera Beetles WS', 'Hera Beetles Holes']),
create_dungeon_region(player, 'Hera Startile Corner', 'Tower of Hera', None, ['Hera Startile Corner ES', 'Hera Startile Corner NW', 'Hera Startile Corner Holes']), create_dungeon_region(player, 'Hera Startile Corner', 'Tower of Hera', None, ['Hera Startile Corner ES', 'Hera Startile Corner NW', 'Hera Startile Corner Holes']),
create_dungeon_region(player, 'Hera Startile Wide', 'Tower of Hera', None, ['Hera Startile Wide SW', 'Hera Startile Wide Up Stairs', 'Hera Startile Wide Holes']), create_dungeon_region(player, 'Hera Startile Wide', 'Tower of Hera', None, ['Hera Startile Wide SW', 'Hera Startile Wide Up Stairs', 'Hera Startile Wide Holes']),
create_dungeon_region(player, 'Hera 4F', 'Tower of Hera', ['Tower of Hera - Compass Chest'], ['Hera 4F Down Stairs', 'Hera 4F Up Stairs', 'Hera 4F Holes']), create_dungeon_region(player, 'Hera 4F', 'Tower of Hera', ['Tower of Hera - Compass Chest'], ['Hera 4F Down Stairs', 'Hera 4F Up Stairs', 'Hera Big Chest Hook Path', 'Hera 4F Holes']),
create_dungeon_region(player, 'Hera Big Chest Landing', 'Tower of Hera', ['Tower of Hera - Big Chest'], ['Hera Big Chest Landing Exit', 'Hera Big Chest Landing Holes']), create_dungeon_region(player, 'Hera Big Chest Landing', 'Tower of Hera', ['Tower of Hera - Big Chest'], ['Hera Big Chest Landing Exit', 'Hera Big Chest Landing Holes']),
create_dungeon_region(player, 'Hera 5F', 'Tower of Hera', None, ['Hera 5F Down Stairs', 'Hera 5F Up Stairs', 'Hera 5F Star Hole', 'Hera 5F Pothole Chain', 'Hera 5F Normal Holes']), create_dungeon_region(player, 'Hera 5F', 'Tower of Hera', None, ['Hera 5F Down Stairs', 'Hera 5F Up Stairs', 'Hera 5F Star Hole', 'Hera 5F Pothole Chain', 'Hera 5F Normal Holes']),
create_dungeon_region(player, 'Hera Fairies', 'Tower of Hera', None, ['Hera Fairies\' Warp']), create_dungeon_region(player, 'Hera Fairies', 'Tower of Hera', None, ['Hera Fairies\' Warp']),
@@ -473,7 +475,8 @@ def create_dungeon_regions(world, player):
create_dungeon_region(player, 'Thieves Hellway S Crystal', 'Thieves\' Town', None, ['Thieves Hellway Crystal Orange Barrier', 'Thieves Hellway Crystal ES']), create_dungeon_region(player, 'Thieves Hellway S Crystal', 'Thieves\' Town', None, ['Thieves Hellway Crystal Orange Barrier', 'Thieves Hellway Crystal ES']),
create_dungeon_region(player, 'Thieves Triple Bypass', 'Thieves\' Town', None, ['Thieves Triple Bypass WN', 'Thieves Triple Bypass EN', 'Thieves Triple Bypass SE']), create_dungeon_region(player, 'Thieves Triple Bypass', 'Thieves\' Town', None, ['Thieves Triple Bypass WN', 'Thieves Triple Bypass EN', 'Thieves Triple Bypass SE']),
create_dungeon_region(player, 'Thieves Spike Switch', 'Thieves\' Town', ['Thieves\' Town - Spike Switch Pot Key'], ['Thieves Spike Switch SW', 'Thieves Spike Switch Up Stairs']), create_dungeon_region(player, 'Thieves Spike Switch', 'Thieves\' Town', ['Thieves\' Town - Spike Switch Pot Key'], ['Thieves Spike Switch SW', 'Thieves Spike Switch Up Stairs']),
create_dungeon_region(player, 'Thieves Attic', 'Thieves\' Town', None, ['Thieves Attic Down Stairs', 'Thieves Attic ES']), create_dungeon_region(player, 'Thieves Attic', 'Thieves\' Town', None, ['Thieves Attic Down Stairs', 'Thieves Attic ES', 'Thieves Attic Orange Barrier']),
create_dungeon_region(player, 'Thieves Attic Hint', 'Thieves\' Town', None, ['Thieves Attic Hint Orange Barrier']),
create_dungeon_region(player, 'Thieves Cricket Hall Left', 'Thieves\' Town', None, ['Thieves Cricket Hall Left WS', 'Thieves Cricket Hall Left Edge']), create_dungeon_region(player, 'Thieves Cricket Hall Left', 'Thieves\' Town', None, ['Thieves Cricket Hall Left WS', 'Thieves Cricket Hall Left Edge']),
create_dungeon_region(player, 'Thieves Cricket Hall Right', 'Thieves\' Town', None, ['Thieves Cricket Hall Right Edge', 'Thieves Cricket Hall Right ES']), create_dungeon_region(player, 'Thieves Cricket Hall Right', 'Thieves\' Town', None, ['Thieves Cricket Hall Right Edge', 'Thieves Cricket Hall Right ES']),
create_dungeon_region(player, 'Thieves Attic Window', 'Thieves\' Town', ['Thieves\' Town - Attic', 'Attic Cracked Floor'], ['Thieves Attic Window WS']), create_dungeon_region(player, 'Thieves Attic Window', 'Thieves\' Town', ['Thieves\' Town - Attic', 'Attic Cracked Floor'], ['Thieves Attic Window WS']),
@@ -727,7 +730,7 @@ def create_dungeon_regions(world, player):
world.get_region('Hera Tridorm', player).crystal_switch = True world.get_region('Hera Tridorm', player).crystal_switch = True
world.get_region('Hera Startile Wide', player).crystal_switch = True world.get_region('Hera Startile Wide', player).crystal_switch = True
world.get_region('PoD Arena Main', player).crystal_switch = True world.get_region('PoD Arena Main', player).crystal_switch = True
world.get_region('PoD Arena Bridge', player).crystal_switch = True world.get_region('PoD Arena Bridge', player).crystal_switch = True # RANGED Weapon Required
world.get_region('PoD Sexy Statue', player).crystal_switch = True world.get_region('PoD Sexy Statue', player).crystal_switch = True
world.get_region('PoD Bow Statue', player).crystal_switch = True # LADDER not accessible (maybe with cane) world.get_region('PoD Bow Statue', player).crystal_switch = True # LADDER not accessible (maybe with cane)
world.get_region('PoD Dark Pegs', player).crystal_switch = True world.get_region('PoD Dark Pegs', player).crystal_switch = True
@@ -747,7 +750,8 @@ def create_dungeon_regions(world, player):
world.get_region('TR Crystal Maze', player).crystal_switch = True world.get_region('TR Crystal Maze', player).crystal_switch = True
world.get_region('GT Crystal Conveyor', player).crystal_switch = True # INTERIOR not accessible world.get_region('GT Crystal Conveyor', player).crystal_switch = True # INTERIOR not accessible
world.get_region('GT Hookshot South Platform', player).crystal_switch = True world.get_region('GT Hookshot South Platform', player).crystal_switch = True
# world.get_region('GT Double Switch Switches', player).crystal_switch = True # this is not very relevant # Relevant to indicate north door can access c_switch
world.get_region('GT Double Switch Switches', player).crystal_switch = True
world.get_region('GT Spike Crystals', player).crystal_switch = True world.get_region('GT Spike Crystals', player).crystal_switch = True
world.get_region('GT Crystal Paths', player).crystal_switch = True world.get_region('GT Crystal Paths', player).crystal_switch = True
world.get_region('GT Hidden Spikes', player).crystal_switch = True world.get_region('GT Hidden Spikes', player).crystal_switch = True
+26 -17
View File
@@ -22,7 +22,7 @@ from EntranceShuffle import door_addresses, exit_ids
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '9ed3ae2d129faa1cd3858e2f72b11b62' RANDOMIZERBASEHASH = 'b9e578ef0af231041070bd9049a55646'
class JsonRom(object): class JsonRom(object):
@@ -172,14 +172,14 @@ def patch_enemizer(world, player, rom, baserom_path, enemizercli, shufflepots, r
options = { options = {
'RandomizeEnemies': world.enemy_shuffle[player] != 'none', 'RandomizeEnemies': world.enemy_shuffle[player] != 'none',
'RandomizeEnemiesType': 3, 'RandomizeEnemiesType': 3,
'RandomizeBushEnemyChance': world.enemy_shuffle[player] == 'chaos', 'RandomizeBushEnemyChance': world.enemy_shuffle[player] == 'random',
'RandomizeEnemyHealthRange': world.enemy_health[player] != 'default', 'RandomizeEnemyHealthRange': world.enemy_health[player] != 'default',
'RandomizeEnemyHealthType': {'default': 0, 'easy': 0, 'normal': 1, 'hard': 2, 'expert': 3}[world.enemy_health[player]], 'RandomizeEnemyHealthType': {'default': 0, 'easy': 0, 'normal': 1, 'hard': 2, 'expert': 3}[world.enemy_health[player]],
'OHKO': False, 'OHKO': False,
'RandomizeEnemyDamage': world.enemy_damage[player] != 'default', 'RandomizeEnemyDamage': world.enemy_damage[player] != 'default',
'AllowEnemyZeroDamage': True, 'AllowEnemyZeroDamage': True,
'ShuffleEnemyDamageGroups': world.enemy_damage[player] != 'default', 'ShuffleEnemyDamageGroups': world.enemy_damage[player] != 'default',
'EnemyDamageChaosMode': world.enemy_damage[player] == 'chaos', 'EnemyDamageChaosMode': world.enemy_damage[player] == 'random',
'EasyModeEscape': False, 'EasyModeEscape': False,
'EnemiesAbsorbable': False, 'EnemiesAbsorbable': False,
'AbsorbableSpawnRate': 10, 'AbsorbableSpawnRate': 10,
@@ -218,9 +218,9 @@ def patch_enemizer(world, player, rom, baserom_path, enemizercli, shufflepots, r
'SwordGraphics': "sword_gfx/normal.gfx", 'SwordGraphics': "sword_gfx/normal.gfx",
'BeeMizer': False, 'BeeMizer': False,
'BeesLevel': 0, 'BeesLevel': 0,
'RandomizeTileTrapPattern': world.enemy_shuffle[player] == 'chaos', 'RandomizeTileTrapPattern': world.enemy_shuffle[player] == 'random',
'RandomizeTileTrapFloorTile': False, 'RandomizeTileTrapFloorTile': False,
'AllowKillableThief': bool(random.randint(0,1)) if world.enemy_shuffle[player] == 'chaos' else world.enemy_shuffle[player] != 'none', 'AllowKillableThief': bool(random.randint(0, 1)) if world.enemy_shuffle[player] == 'random' else world.enemy_shuffle[player] != 'none',
'RandomizeSpriteOnHit': random_sprite_on_hit, 'RandomizeSpriteOnHit': random_sprite_on_hit,
'DebugMode': False, 'DebugMode': False,
'DebugForceEnemy': False, 'DebugForceEnemy': False,
@@ -319,6 +319,8 @@ def get_sprite_from_name(name):
name = name.lower() name = name.lower()
if name in ['random', 'randomonhit']: if name in ['random', 'randomonhit']:
return Sprite(random.choice(list(_sprite_table.values()))) return Sprite(random.choice(list(_sprite_table.values())))
if name == ('(default link)'):
name = 'link'
return Sprite(_sprite_table[name]) if name in _sprite_table else None return Sprite(_sprite_table[name]) if name in _sprite_table else None
class Sprite(object): class Sprite(object):
@@ -593,18 +595,21 @@ def patch_rom(world, rom, player, team, enemized):
patch_shuffled_dark_sanc(world, rom, player) patch_shuffled_dark_sanc(world, rom, player)
# setup dr option flags based on experimental, etc. # setup dr option flags based on experimental, etc.
dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' else DROptions.Town_Portal dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' else DROptions.Town_Portal
if world.experimental[player]: if world.experimental[player]:
dr_flags |= DROptions.Map_Info dr_flags |= DROptions.Map_Info
dr_flags |= DROptions.Debug
# patch doors # patch doors
if world.doorShuffle[player] == 'crossed': if world.doorShuffle[player] == 'crossed':
rom.write_byte(0x139004, 2) rom.write_byte(0x138002, 2)
for name, layout in world.key_layout[player].items(): for name, layout in world.key_layout[player].items():
offset = compass_data[name][4]//2 offset = compass_data[name][4]//2
rom.write_byte(0x13f01c+offset, layout.max_chests + layout.max_drops) rom.write_byte(0x13f01c+offset, layout.max_chests + layout.max_drops)
rom.write_byte(0x13f02a+offset, layout.max_chests) rom.write_byte(0x13f02a+offset, layout.max_chests)
builder = world.dungeon_layouts[player][name] builder = world.dungeon_layouts[player][name]
rom.write_byte(0x13f070+offset, builder.location_cnt % 10)
rom.write_byte(0x13f07e+offset, builder.location_cnt // 10)
bk_status = 1 if builder.bk_required else 0 bk_status = 1 if builder.bk_required else 0
bk_status = 2 if builder.bk_provided else bk_status bk_status = 2 if builder.bk_provided else bk_status
rom.write_byte(0x13f038+offset*2, bk_status) rom.write_byte(0x13f038+offset*2, bk_status)
@@ -616,9 +621,10 @@ def patch_rom(world, rom, player, team, enemized):
else: else:
logging.getLogger('').warning('Randomizer rom update! Compasses in crossed are borken') logging.getLogger('').warning('Randomizer rom update! Compasses in crossed are borken')
if world.doorShuffle[player] == 'basic': if world.doorShuffle[player] == 'basic':
rom.write_byte(0x139004, 1) rom.write_byte(0x138002, 1)
for door in world.doors: for door in world.doors:
if door.dest is not None and door.player == player and door.type in [DoorType.Normal, DoorType.SpiralStairs]: if door.dest is not None and door.player == player and door.type in [DoorType.Normal, DoorType.SpiralStairs,
DoorType.Open, DoorType.StraightStairs]:
rom.write_bytes(door.getAddress(), door.dest.getTarget(door)) rom.write_bytes(door.getAddress(), door.dest.getTarget(door))
for room in world.rooms: for room in world.rooms:
if room.player == player and room.modified: if room.player == player and room.modified:
@@ -638,9 +644,11 @@ def patch_rom(world, rom, player, team, enemized):
dungeon_name = opposite_door.entrance.parent_region.dungeon.name dungeon_name = opposite_door.entrance.parent_region.dungeon.name
dungeon_id = boss_indicator[dungeon_name][0] dungeon_id = boss_indicator[dungeon_name][0]
rom.write_byte(0x13f000+dungeon_id, opposite_door.roomIndex) rom.write_byte(0x13f000+dungeon_id, opposite_door.roomIndex)
rom.write_byte(0x139006, dr_flags.value) elif not opposite_door:
rom.write_byte(0x13f000+dungeon_id, 0) # no supertile preceeding boss
rom.write_byte(0x138004, dr_flags.value)
if dr_flags & DROptions.Town_Portal and world.mode[player] == 'inverted': if dr_flags & DROptions.Town_Portal and world.mode[player] == 'inverted':
rom.write_byte(0x139008, 1) rom.write_byte(0x138006, 1)
# fix skull woods exit, if not fixed during exit patching # fix skull woods exit, if not fixed during exit patching
if world.fix_skullwoods_exit[player] and world.shuffle[player] == 'vanilla': if world.fix_skullwoods_exit[player] and world.shuffle[player] == 'vanilla':
@@ -1318,8 +1326,8 @@ def patch_rom(world, rom, player, team, enemized):
rom.write_bytes(0x7FC0, rom.name) rom.write_bytes(0x7FC0, rom.name)
# set player names # set player names
for p in range(1, min(world.players, 64) + 1): for p in range(1, min(world.players, 255) + 1):
rom.write_bytes(0x186380 + ((p - 1) * 32), hud_format_text(world.player_names[p][team])) rom.write_bytes(0x195FFC + ((p - 1) * 32), hud_format_text(world.player_names[p][team]))
# Write title screen Code # Write title screen Code
hashint = int(rom.get_hash(), 16) hashint = int(rom.get_hash(), 16)
@@ -2118,7 +2126,8 @@ def set_inverted_mode(world, player, rom):
rom.write_bytes(snes_to_pc(0x06B2AB), [0xF0, 0xE1, 0x05]) rom.write_bytes(snes_to_pc(0x06B2AB), [0xF0, 0xE1, 0x05])
def patch_shuffled_dark_sanc(world, rom, player): def patch_shuffled_dark_sanc(world, rom, player):
dark_sanc_entrance = str(world.get_region('Inverted Dark Sanctuary', player).entrances[0].name) dark_sanc = world.get_region('Inverted Dark Sanctuary', player)
dark_sanc_entrance = str([i for i in dark_sanc.entrances if i.parent_region.name != 'Menu'][0].name)
room_id, ow_area, vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x, unknown_1, unknown_2, door_1, door_2 = door_addresses[dark_sanc_entrance][1] room_id, ow_area, vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x, unknown_1, unknown_2, door_1, door_2 = door_addresses[dark_sanc_entrance][1]
door_index = door_addresses[str(dark_sanc_entrance)][0] door_index = door_addresses[str(dark_sanc_entrance)][0]
@@ -2130,9 +2139,9 @@ def patch_shuffled_dark_sanc(world, rom, player):
rom.write_bytes(0x180262, [unknown_1, unknown_2, 0x00]) rom.write_bytes(0x180262, [unknown_1, unknown_2, 0x00])
# 24B116 and 20BAD8 # 24B118 and 20BB32
compass_r_addr = 0x123116 # a9 90 24 8f 9a c7 7e compass_r_addr = 0x123118 # a9 90 24 8f 9a c7 7e
compass_w_addr = 0x103ad8 # e2 20 ad 0c 04 c9 00 d0 compass_w_addr = 0x103b32 # e2 20 ad 0c 04 c9 00 d0
def compass_code_good(rom): def compass_code_good(rom):
+53 -44
View File
@@ -1,28 +1,19 @@
import logging import logging
from BaseClasses import CollectionState, RegionType, DoorType from collections import deque
from BaseClasses import CollectionState, RegionType, DoorType, Entrance
from Regions import key_only_locations from Regions import key_only_locations
from RoomData import DoorKind from RoomData import DoorKind
from collections import deque
def set_rules(world, player): def set_rules(world, player):
if world.logic[player] == 'nologic': if world.logic[player] == 'nologic':
logging.getLogger('').info('WARNING! Seeds generated under this logic often require major glitches and may be impossible!') logging.getLogger('').info('WARNING! Seeds generated under this logic often require major glitches and may be impossible!')
if world.mode[player] != 'inverted': world.get_region('Menu', player).can_reach_private = lambda state: True
world.get_region('Links House', player).can_reach_private = lambda state: True for exit in world.get_region('Menu', player).exits:
world.get_region('Sanctuary', player).can_reach_private = lambda state: True exit.hide_path = True
old_rule = world.get_region('Old Man House', player).can_reach return
world.get_region('Old Man House', player).can_reach_private = lambda state: state.can_reach('Old Man', 'Location', player) or old_rule(state)
return
else:
world.get_region('Inverted Links House', player).can_reach_private = lambda state: True
world.get_region('Inverted Dark Sanctuary', player).entrances[0].parent_region.can_reach_private = lambda state: True
if world.shuffle[player] != 'vanilla':
old_rule = world.get_region('Old Man House', player).can_reach
world.get_region('Old Man House', player).can_reach_private = lambda state: state.can_reach('Old Man', 'Location', player) or old_rule(state)
world.get_region('Hyrule Castle Ledge', player).can_reach_private = lambda state: True
return
global_rules(world, player) global_rules(world, player)
if world.mode[player] != 'inverted': if world.mode[player] != 'inverted':
@@ -113,8 +104,11 @@ def global_rules(world, player):
add_item_rule(world.get_location('Ganon', player), lambda item: item.name == 'Triforce' and item.player == player) add_item_rule(world.get_location('Ganon', player), lambda item: item.name == 'Triforce' and item.player == player)
# we can s&q to the old man house after we rescue him. This may be somewhere completely different if caves are shuffled! # we can s&q to the old man house after we rescue him. This may be somewhere completely different if caves are shuffled!
old_rule = world.get_region('Old Man House', player).can_reach_private world.get_region('Menu', player).can_reach_private = lambda state: True
world.get_region('Old Man House', player).can_reach_private = lambda state: state.can_reach('Old Man', 'Location', player) or old_rule(state) for exit in world.get_region('Menu', player).exits:
exit.hide_path = True
set_rule(world.get_entrance('Old Man S&Q', player), lambda state: state.can_reach('Old Man', 'Location', player))
set_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player)) set_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player))
set_rule(world.get_location('Dark Blacksmith Ruins', player), lambda state: state.has('Return Smith', player)) set_rule(world.get_location('Dark Blacksmith Ruins', player), lambda state: state.has('Return Smith', player))
@@ -165,9 +159,17 @@ def global_rules(world, player):
# Tower of Hera # Tower of Hera
set_rule(world.get_location('Tower of Hera - Big Key Chest', player), lambda state: state.has_fire_source(player)) set_rule(world.get_location('Tower of Hera - Big Key Chest', player), lambda state: state.has_fire_source(player))
set_rule(world.get_entrance('Hera Big Chest Hook Path', player), lambda state: state.has('Hookshot', player))
set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Boss', player))
set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Prize', player)) set_defeat_dungeon_boss_rule(world.get_location('Tower of Hera - Prize', player))
# Castle Tower
set_rule(world.get_entrance('Tower Gold Knights SW', player), lambda state: state.can_kill_most_things(player))
set_rule(world.get_entrance('Tower Gold Knights EN', player), lambda state: state.can_kill_most_things(player))
set_rule(world.get_entrance('Tower Dark Archers WN', player), lambda state: state.can_kill_most_things(player))
set_rule(world.get_entrance('Tower Red Spears WN', player), lambda state: state.can_kill_most_things(player))
set_rule(world.get_entrance('Tower Red Guards EN', player), lambda state: state.can_kill_most_things(player))
set_rule(world.get_entrance('Tower Red Guards SW', player), lambda state: state.can_kill_most_things(player))
set_rule(world.get_entrance('Tower Altar NW', player), lambda state: state.has_sword(player)) set_rule(world.get_entrance('Tower Altar NW', player), lambda state: state.has_sword(player))
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 1', player)) set_defeat_dungeon_boss_rule(world.get_location('Agahnim 1', player))
@@ -205,6 +207,8 @@ def global_rules(world, player):
set_rule(world.get_entrance('Swamp Flooded Room Ladder', player), lambda state: state.has('Drained Swamp', player)) set_rule(world.get_entrance('Swamp Flooded Room Ladder', player), lambda state: state.has('Drained Swamp', player))
set_rule(world.get_location('Swamp Palace - Flooded Room - Left', player), lambda state: state.has('Drained Swamp', player)) set_rule(world.get_location('Swamp Palace - Flooded Room - Left', player), lambda state: state.has('Drained Swamp', player))
set_rule(world.get_location('Swamp Palace - Flooded Room - Right', player), lambda state: state.has('Drained Swamp', player)) set_rule(world.get_location('Swamp Palace - Flooded Room - Right', player), lambda state: state.has('Drained Swamp', player))
set_rule(world.get_entrance('Swamp Flooded Spot Ladder', player), lambda state: state.has('Flippers', player) or state.has('Drained Swamp', player))
set_rule(world.get_entrance('Swamp Drain Left Up Stairs', player), lambda state: state.has('Flippers', player) or state.has('Drained Swamp', player))
set_rule(world.get_entrance('Swamp Waterway NW', player), lambda state: state.has('Flippers', player)) set_rule(world.get_entrance('Swamp Waterway NW', player), lambda state: state.has('Flippers', player))
set_rule(world.get_entrance('Swamp Waterway N', player), lambda state: state.has('Flippers', player)) set_rule(world.get_entrance('Swamp Waterway N', player), lambda state: state.has('Flippers', player))
set_rule(world.get_entrance('Swamp Waterway NE', player), lambda state: state.has('Flippers', player)) set_rule(world.get_entrance('Swamp Waterway NE', player), lambda state: state.has('Flippers', player))
@@ -270,7 +274,7 @@ def global_rules(world, player):
set_rule(world.get_entrance('TR Hub NE', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('TR Hub NE', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Torches NW', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player)) set_rule(world.get_entrance('TR Torches NW', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player))
set_rule(world.get_entrance('TR Big Chest Entrance Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player)) set_rule(world.get_entrance('TR Big Chest Entrance Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player))
set_rule(world.get_entrance('TR Big Chest Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player)) set_rule(world.get_entrance('TR Big Chest Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has_Boots(player))
set_rule(world.get_entrance('TR Dark Ride Up Stairs', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('TR Dark Ride Up Stairs', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Dark Ride SW', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('TR Dark Ride SW', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('TR Crystal Maze Cane Path', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('TR Crystal Maze Cane Path', player), lambda state: state.has('Cane of Somaria', player))
@@ -323,8 +327,6 @@ def global_rules(world, player):
set_rule(world.get_entrance('GT Moldorm Gap', player), lambda state: state.has('Hookshot', player) and world.get_region('GT Moldorm', player).dungeon.bosses['top'].can_defeat(state)) set_rule(world.get_entrance('GT Moldorm Gap', player), lambda state: state.has('Hookshot', player) and world.get_region('GT Moldorm', player).dungeon.bosses['top'].can_defeat(state))
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player)) set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player))
add_key_logic_rules(world, player)
# crystal switch rules # crystal switch rules
set_rule(world.get_entrance('PoD Arena Crystal Path', player), lambda state: state.can_reach_blue(world.get_region('PoD Arena Crystal', player), player)) set_rule(world.get_entrance('PoD Arena Crystal Path', player), lambda state: state.can_reach_blue(world.get_region('PoD Arena Crystal', player), player))
set_rule(world.get_entrance('Swamp Trench 2 Pots Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Swamp Trench 2 Pots', player), player)) set_rule(world.get_entrance('Swamp Trench 2 Pots Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Swamp Trench 2 Pots', player), player))
@@ -375,6 +377,8 @@ def global_rules(world, player):
set_rule(world.get_entrance('GT Double Switch Orange Path', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Switches', player), player)) set_rule(world.get_entrance('GT Double Switch Orange Path', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Switches', player), player))
set_rule(world.get_entrance('GT Double Switch Key Orange Path', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Key Spot', player), player)) set_rule(world.get_entrance('GT Double Switch Key Orange Path', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Key Spot', player), player))
add_key_logic_rules(world, player)
# End of door rando rules. # End of door rando rules.
add_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player)) add_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player))
@@ -384,16 +388,6 @@ def global_rules(world, player):
def default_rules(world, player): def default_rules(world, player):
if world.mode[player] == 'standard':
# Links house requires reaching Sanc so skipping that chest isn't a softlock.
world.get_region('Hyrule Castle Secret Entrance', player).can_reach_private = lambda state: True
old_rule = world.get_region('Links House', player).can_reach_private
world.get_region('Links House', player).can_reach_private = lambda state: state.has('Zelda Delivered', player) or old_rule(state)
else:
# these are default save&quit points and always accessible
world.get_region('Links House', player).can_reach_private = lambda state: True
world.get_region('Sanctuary', player).can_reach_private = lambda state: True
# overworld requirements # overworld requirements
set_rule(world.get_entrance('Kings Grave', player), lambda state: state.has_Boots(player)) set_rule(world.get_entrance('Kings Grave', player), lambda state: state.has_Boots(player))
set_rule(world.get_entrance('Kings Grave Outer Rocks', player), lambda state: state.can_lift_heavy_rocks(player)) set_rule(world.get_entrance('Kings Grave Outer Rocks', player), lambda state: state.can_lift_heavy_rocks(player))
@@ -506,12 +500,7 @@ def default_rules(world, player):
def inverted_rules(world, player): def inverted_rules(world, player):
# s&q regions. link's house entrance is set to true so the filler knows the chest inside can always be reached # s&q regions. link's house entrance is set to true so the filler knows the chest inside can always be reached
world.get_region('Inverted Links House', player).can_reach_private = lambda state: True set_rule(world.get_entrance('Castle Ledge S&Q', player), lambda state: state.has_Mirror(player) and state.has('Beat Agahnim 1', player))
world.get_region('Inverted Links House', player).entrances[0].can_reach = lambda state: True
world.get_region('Inverted Dark Sanctuary', player).entrances[0].parent_region.can_reach_private = lambda state: True
old_rule = world.get_region('Hyrule Castle Ledge', player).can_reach_private
world.get_region('Hyrule Castle Ledge', player).can_reach_private = lambda state: (state.has_Mirror(player) and state.has('Beat Agahnim 1', player) and state.can_reach_light_world(player)) or old_rule(state)
# overworld requirements # overworld requirements
set_rule(world.get_location('Maze Race', player), lambda state: state.has_Pearl(player)) set_rule(world.get_location('Maze Race', player), lambda state: state.has_Pearl(player))
@@ -824,7 +813,19 @@ std_kill_rooms = {
} # all trap rooms? } # all trap rooms?
def add_connection(parent_name, target_name, entrance_name, world, player):
parent = world.get_region(parent_name, player)
target = world.get_region(target_name, player)
connection = Entrance(player, entrance_name, parent)
parent.exits.append(connection)
connection.connect(target)
def standard_rules(world, player): def standard_rules(world, player):
add_connection('Menu', 'Hyrule Castle Secret Entrance', 'Uncle S&Q', world, player)
world.get_entrance('Uncle S&Q', player).hide_path = True
set_rule(world.get_entrance('Links House S&Q', player), lambda state: state.can_reach('Sanctuary', 'Region', player))
set_rule(world.get_entrance('Sanctuary S&Q', player), lambda state: state.can_reach('Sanctuary', 'Region', player))
# these are because of rails # these are because of rails
if world.shuffle[player] != 'vanilla': if world.shuffle[player] != 'vanilla':
set_rule(world.get_entrance('Hyrule Castle Exit (East)', player), lambda state: state.has('Zelda Delivered', player)) set_rule(world.get_entrance('Hyrule Castle Exit (East)', player), lambda state: state.has('Zelda Delivered', player))
@@ -837,24 +838,32 @@ def standard_rules(world, player):
copy_state.sweep_for_events() copy_state.sweep_for_events()
return copy_state.has('Zelda Delivered', player) return copy_state.has('Zelda Delivered', player)
def bomb_escape_rule():
loc = world.get_location("Link's Uncle", player)
return loc.item and loc.item.name == 'Bombs (10)'
def standard_escape_rule(state):
return state.can_kill_most_things(player) or bomb_escape_rule()
add_item_rule(world.get_location('Link\'s Uncle', player), uncle_item_rule) add_item_rule(world.get_location('Link\'s Uncle', player), uncle_item_rule)
# ensures the required weapon for escape lands on uncle (unless player has it pre-equipped) # ensures the required weapon for escape lands on uncle (unless player has it pre-equipped)
for location in ['Link\'s House', 'Sanctuary', 'Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle', for location in ['Link\'s House', 'Sanctuary', 'Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
'Sewers - Secret Room - Right']: 'Sewers - Secret Room - Right']:
add_rule(world.get_location(location, player), lambda state: state.can_kill_most_things(player)) add_rule(world.get_location(location, player), lambda state: standard_escape_rule(state))
add_rule(world.get_location('Secret Passage', player), lambda state: state.can_kill_most_things(player)) add_rule(world.get_location('Secret Passage', player), lambda state: standard_escape_rule(state))
escape_builder = world.dungeon_layouts[player]['Hyrule Castle'] escape_builder = world.dungeon_layouts[player]['Hyrule Castle']
for region in escape_builder.master_sector.regions: for region in escape_builder.master_sector.regions:
for loc in region.locations: for loc in region.locations:
add_rule(loc, lambda state: state.can_kill_most_things(player)) add_rule(loc, lambda state: standard_escape_rule(state))
if region.name in std_kill_rooms: if region.name in std_kill_rooms:
for ent in std_kill_rooms[region.name]: for ent in std_kill_rooms[region.name]:
add_rule(world.get_entrance(ent, player), lambda state: state.can_kill_most_things(player)) add_rule(world.get_entrance(ent, player), lambda state: standard_escape_rule(state))
set_rule(world.get_location('Zelda Pickup', player), lambda state: state.has('Big Key (Escape)', player)) set_rule(world.get_location('Zelda Pickup', player), lambda state: state.has('Big Key (Escape)', player))
set_rule(world.get_entrance('Hyrule Castle Throne Room N', player), lambda state: state.has('Zelda Herself', player)) set_rule(world.get_entrance('Hyrule Castle Throne Room Tapestry', player), lambda state: state.has('Zelda Herself', player))
set_rule(world.get_entrance('Hyrule Castle Tapestry Backwards', player), lambda state: state.has('Zelda Herself', player))
def check_rule_list(state, r_list): def check_rule_list(state, r_list):
return True if len(r_list) <= 0 else r_list[0](state) and check_rule_list(state, r_list[1:]) return True if len(r_list) <= 0 else r_list[0](state) and check_rule_list(state, r_list[1:])
@@ -1039,7 +1048,7 @@ def set_big_bomb_rules(world, player):
# the basic routes assume you can reach eastern light world with the bomb. # the basic routes assume you can reach eastern light world with the bomb.
# you can then use the southern teleporter, or (if you have beaten Aga1) the hyrule castle gate warp # you can then use the southern teleporter, or (if you have beaten Aga1) the hyrule castle gate warp
def basic_routes(state): def basic_routes(state):
return southern_teleporter(state) or state.can_reach('Top of Pyramid', 'Entrance', player) return southern_teleporter(state) or state.has('Beat Agahnim 1', player)
# Key for below abbreviations: # Key for below abbreviations:
# P = pearl # P = pearl
@@ -1072,7 +1081,7 @@ def set_big_bomb_rules(world, player):
#1. Mirror and enter via gate: Need mirror and Aga1 #1. Mirror and enter via gate: Need mirror and Aga1
#2. cross peg bridge: Need hammer and moon pearl #2. cross peg bridge: Need hammer and moon pearl
# -> CPB or (M and A) # -> CPB or (M and A)
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: cross_peg_bridge(state) or (state.has_Mirror(player) and state.can_reach('Top of Pyramid', 'Entrance', player))) add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: cross_peg_bridge(state) or (state.has_Mirror(player) and state.has('Beat Agahnim 1', player)))
elif bombshop_entrance.name in Isolated_DW_entrances: elif bombshop_entrance.name in Isolated_DW_entrances:
# 1. mirror then flute then basic routes # 1. mirror then flute then basic routes
# -> M and Flute and BR # -> M and Flute and BR
+18 -15
View File
@@ -19,7 +19,8 @@ normal_offset_table = {
0xb7: 0x79, 0xb8: 0x7A, 0xb9: 0x7B, 0xba: 0x7C, 0xbb: 0x7D, 0xbc: 0x7E, 0xbe: 0x7F, 0xbf: 0x80, 0xb7: 0x79, 0xb8: 0x7A, 0xb9: 0x7B, 0xba: 0x7C, 0xbb: 0x7D, 0xbc: 0x7E, 0xbe: 0x7F, 0xbf: 0x80,
0xc1: 0x81, 0xc2: 0x82, 0xc3: 0x83, 0xc4: 0x84, 0xc5: 0x85, 0xc6: 0x86, 0xc7: 0x87, 0xc8: 0x88, 0xc1: 0x81, 0xc2: 0x82, 0xc3: 0x83, 0xc4: 0x84, 0xc5: 0x85, 0xc6: 0x86, 0xc7: 0x87, 0xc8: 0x88,
0xc9: 0x89, 0xcb: 0x8A, 0xcc: 0x8B, 0xce: 0x8C, 0xd1: 0x8D, 0xd2: 0x8E, 0xd5: 0x8F, 0xd6: 0x90, 0xc9: 0x89, 0xcb: 0x8A, 0xcc: 0x8B, 0xce: 0x8C, 0xd1: 0x8D, 0xd2: 0x8E, 0xd5: 0x8F, 0xd6: 0x90,
0xd8: 0x91, 0xd9: 0x92, 0xda: 0x93, 0xdb: 0x94, 0xdc: 0x95 0xd8: 0x91, 0xd9: 0x92, 0xda: 0x93, 0xdb: 0x94, 0xdc: 0x95,
0x40: 0x96, 0x42: 0x97 # newcomers for str stairs
} }
@@ -61,23 +62,25 @@ door_pair_offset_table = {
0xd6: 0x01fa, 0xd8: 0x01fd, 0xd9: 0x0200, 0xda: 0x0203, 0xdb: 0x0204, 0xdc: 0x0206, 0xe0: 0x020 0xd6: 0x01fa, 0xd8: 0x01fd, 0xd9: 0x0200, 0xda: 0x0203, 0xdb: 0x0204, 0xdc: 0x0206, 0xe0: 0x020
} }
# Note: 0-7 correspond to 1,2,3,4,5,6,a,14 respectively, see doortables.asm : MultDivInfo
multiply_lookup = { multiply_lookup = {
0x08: {0x8: 1, 0x10: 2, 0x18: 3, 0x20: 4, 0x30: 6, 0x50: 0xa, 0xa0: 0x14}, 0x08: {0x8: 0, 0x10: 1, 0x18: 2, 0x20: 3, 0x30: 5, 0x50: 6, 0xa0: 7},
0x10: {0x8: 1, 0x10: 1, 0x18: 3, 0x20: 2, 0x30: 3, 0x50: 0x4, 0xa0: 0xa}, 0x10: {0x8: 0, 0x10: 0, 0x18: 2, 0x20: 1, 0x30: 2, 0x50: 3, 0xa0: 6},
0x18: {0x8: 1, 0x10: 2, 0x18: 1, 0x20: 4, 0x30: 2, 0x50: 0xa, 0xa0: 0x14}, 0x18: {0x8: 0, 0x10: 1, 0x18: 0, 0x20: 3, 0x30: 1, 0x50: 6, 0xa0: 7},
0x20: {0x8: 1, 0x10: 1, 0x18: 3, 0x20: 1, 0x30: 3, 0x50: 5, 0xa0: 5}, 0x20: {0x8: 0, 0x10: 0, 0x18: 2, 0x20: 0, 0x30: 2, 0x50: 4, 0xa0: 4},
0x30: {0x8: 1, 0x10: 1, 0x18: 1, 0x20: 2, 0x30: 1, 0x50: 5, 0xa0: 0xa}, 0x30: {0x8: 0, 0x10: 0, 0x18: 0, 0x20: 1, 0x30: 0, 0x50: 4, 0xa0: 6},
0x50: {0x8: 1, 0x10: 1, 0x18: 3, 0x20: 2, 0x30: 3, 0x50: 1, 0xa0: 2}, 0x50: {0x8: 0, 0x10: 0, 0x18: 2, 0x20: 1, 0x30: 2, 0x50: 0, 0xa0: 1},
0xa0: {0x8: 1, 0x10: 1, 0x18: 3, 0x20: 1, 0x30: 3, 0x50: 1, 0xa0: 1}, 0xa0: {0x8: 0, 0x10: 0, 0x18: 2, 0x20: 0, 0x30: 2, 0x50: 0, 0xa0: 0},
} }
divisor_lookup = { divisor_lookup = {
0x08: {0x8: 1, 0x10: 1, 0x18: 1, 0x20: 1, 0x30: 1, 0x50: 1, 0xa0: 1}, 0x08: {0x8: 0, 0x10: 0, 0x18: 0, 0x20: 0, 0x30: 0, 0x50: 0, 0xa0: 0},
0x10: {0x8: 2, 0x10: 1, 0x18: 2, 0x20: 1, 0x30: 1, 0x50: 1, 0xa0: 1}, 0x10: {0x8: 1, 0x10: 0, 0x18: 1, 0x20: 0, 0x30: 0, 0x50: 0, 0xa0: 0},
0x18: {0x8: 3, 0x10: 3, 0x18: 1, 0x20: 3, 0x30: 1, 0x50: 3, 0xa0: 3}, 0x18: {0x8: 2, 0x10: 2, 0x18: 0, 0x20: 2, 0x30: 0, 0x50: 2, 0xa0: 2},
0x20: {0x8: 4, 0x10: 2, 0x18: 4, 0x20: 1, 0x30: 2, 0x50: 2, 0xa0: 1}, 0x20: {0x8: 3, 0x10: 1, 0x18: 3, 0x20: 0, 0x30: 1, 0x50: 1, 0xa0: 0},
0x30: {0x8: 6, 0x10: 3, 0x18: 2, 0x20: 3, 0x30: 1, 0x50: 3, 0xa0: 3}, 0x30: {0x8: 5, 0x10: 2, 0x18: 1, 0x20: 2, 0x30: 0, 0x50: 2, 0xa0: 2},
0x50: {0x8: 0xa, 0x10: 4, 0x18: 0xa, 0x20: 5, 0x30: 5, 0x50: 1, 0xa0: 1}, 0x50: {0x8: 6, 0x10: 3, 0x18: 6, 0x20: 4, 0x30: 4, 0x50: 0, 0xa0: 0},
0xa0: {0x8: 0x14, 0x10: 0xa, 0x18: 0x14, 0x20: 5, 0x30: 0xa, 0x50: 2, 0xa0: 1}, 0xa0: {0x8: 7, 0x10: 6, 0x18: 7, 0x20: 4, 0x30: 6, 0x50: 1, 0xa0: 0},
} }
+22 -2
View File
@@ -187,6 +187,26 @@ entrance_data = {
} }
def read_layout_data(old_rom='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc'):
with open(old_rom, 'rb') as stream:
old_rom_data = bytearray(stream.read())
string = ''
for room in range(0, 0xff+1):
# print(ent)
pointer_start = 0xf8000+room*3
highbyte = old_rom_data[pointer_start+2]
midbyte = old_rom_data[pointer_start+1]
midbyte = midbyte - 0x80 if highbyte % 2 == 0 else midbyte
pointer = highbyte // 2 * 0x10000
pointer += midbyte * 0x100
pointer += old_rom_data[pointer_start]
layout_byte = old_rom_data[pointer+1]
layout = (layout_byte & 0x1c) >> 2
string += hex(room) + ':' + str(layout) + '\n'
print(string)
def read_entrance_data(old_rom='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc'): def read_entrance_data(old_rom='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc'):
with open(old_rom, 'rb') as stream: with open(old_rom, 'rb') as stream:
old_rom_data = bytearray(stream.read()) old_rom_data = bytearray(stream.read())
@@ -396,6 +416,6 @@ def print_graph(world):
if __name__ == '__main__': if __name__ == '__main__':
pass
# make_new_base2current() # make_new_base2current()
read_entrance_data(old_rom='C:\\Users\\Randall\\Documents\\kwyn\\orig\\z3.sfc') # read_entrance_data(old_rom=sys.argv[1])
read_layout_data(old_rom=sys.argv[1])
+15
View File
@@ -184,4 +184,19 @@ $a2 - MM 162 idx 0
$a8 - EP 168 idx 2 $a8 - EP 168 idx 2
$bc - TT 188 idx 1 $bc - TT 188 idx 1
;SRAM corruption investigation
;call stack
; 110c7 7.e.7 (Dungeon_SpiralStaircase_7)
; 112b1
; dw $8CE2 ; = $10CE2*
; dw $8E0F ; = $10E0F*
; dw $8E1D ; = $10E1D*
; dw $8D10 ; = $10D10*
; dw $90C7 ; = $110C7*
;bank2 line 3323 - x is d422 - comes from 048c
; called by 10CE2, (Dungeon_SpiralStaircase_3)
;122f0
+13 -35
View File
@@ -14,47 +14,25 @@ incsrc drhooks.asm
;Main Code ;Main Code
org $278000 ;138000 org $278000 ;138000
incsrc normal.asm db $44, $52 ;DR
incsrc spiral.asm
incsrc gfx.asm
incsrc keydoors.asm
incsrc overrides.asm
;incsrc edges.asm
;incsrc math.asm
incsrc hudadditions.asm
warnpc $279000
; Data Section
org $279000
OffsetTable:
dw -8, 8
DRMode: DRMode:
dw 0 dw 0
DRFlags: DRFlags:
dw 0 dw 0
DRScroll: DRScroll:
db 0 db 0
OffsetTable:
dw -8, 8
; Vert 0,6,0 Horz 2,0,8 incsrc normal.asm
org $279010 incsrc scroll.asm
CoordIndex: ; Horizontal 1st incsrc spiral.asm
db 2, 0 ; Coordinate Index $20-$23 incsrc gfx.asm
OppCoordIndex: incsrc keydoors.asm
db 0, 2 ; Swapped coordinate Index $20-$23 (minor optimization) incsrc overrides.asm
CameraIndex: ; Horizontal 1st incsrc edges.asm
db 0, 6 ; Camera Index $e2-$ea incsrc math.asm
CamQuadIndex: ; Horizontal 1st incsrc hudadditions.asm
db 8, 0 ; Camera quadrants $600-$60f warnpc $279700
ShiftQuadIndex:
db 2, 1 ; see ShiftQuad func (relates to $a9,$aa)
CamBoundIndex: ; Horizontal 1st
db 0, 4 ; Camera Bounds $0618-$61f
OppCamBoundIndex: ; Horizontal 1st
db 4, 0 ; Camera Bounds $0618-$61f
CamBoundBaseLine: ; X camera stuff is 1st column todo Y camera needs more testing
dw $007f, $0077 ; Left/Top camera bounds when at edge or layout frozen
dw $0007, $000b ; Left/Top camera bounds when not frozen + appropriate low byte $22/$20 (preadj. by #$78/#$6c)
dw $00ff, $010b ; Right/Bot camera bounds when not frozen + appropriate low byte $20/$22
dw $017f, $0187 ; Right/Bot camera bound when at edge or layout frozen
incsrc doortables.asm incsrc doortables.asm
+213 -160
View File
@@ -43,7 +43,7 @@ db $00,$01,$02,$00,$03,$00,$04,$00,$00,$00,$00,$00,$00,$05,$00,$00
db $00,$06,$07,$08,$09,$0A,$0B,$00,$00,$0C,$0D,$0E,$00,$0F,$10,$11 db $00,$06,$07,$08,$09,$0A,$0B,$00,$00,$0C,$0D,$0E,$00,$0F,$10,$11
db $12,$13,$14,$15,$16,$00,$17,$00,$00,$00,$18,$19,$00,$00,$1A,$00 db $12,$13,$14,$15,$16,$00,$17,$00,$00,$00,$18,$19,$00,$00,$1A,$00
db $1B,$00,$1C,$1D,$1E,$1F,$20,$21,$22,$23,$24,$25,$00,$26,$27,$00 db $1B,$00,$1C,$1D,$1E,$1F,$20,$21,$22,$23,$24,$25,$00,$26,$27,$00
db $00,$28,$00,$29,$2A,$2B,$2C,$00,$00,$2D,$2E,$2F,$30,$31,$32,$00 db $96,$28,$97,$29,$2A,$2B,$2C,$00,$00,$2D,$2E,$2F,$30,$31,$32,$00
db $33,$34,$35,$36,$00,$00,$37,$38,$39,$3A,$3B,$3C,$3D,$3E,$3F,$40 db $33,$34,$35,$36,$00,$00,$37,$38,$39,$3A,$3B,$3C,$3D,$3E,$3F,$40
db $41,$42,$43,$00,$00,$00,$44,$45,$46,$00,$47,$48,$49,$4A,$4B,$00 db $41,$42,$43,$00,$00,$00,$44,$45,$46,$00,$47,$48,$49,$4A,$4B,$00
db $00,$4C,$00,$00,$00,$4D,$4E,$00,$00,$00,$00,$4F,$50,$51,$52,$53 db $00,$4C,$00,$00,$00,$4D,$4E,$00,$00,$00,$00,$4F,$50,$51,$52,$53
@@ -58,164 +58,166 @@ db $00
org $27A000 org $27A000
DoorTable: DoorTable:
;; NW 00 N 01 N 02 WN 00 W 01 WS 02 SW 00 S 01 SE 02 EN 00 E 01 ES 02 - Door ruler ;; NW 00 N 01 N 02 WN 00 W 01 WS 02 SW 00 S 01 SE 02 EN 00 E 01 ES 02 - Door ruler
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Default/Garbage row dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Default/Garbage row
dw $8000, $8000, $8000, $0450, $8000, $8000, $8000, $8000, $8000, $0452, $8000, $8000 ; HC Back Hall (x01) dw $0003, $0003, $0003, $0450, $0003, $0003, $0003, $0003, $0003, $0452, $0003, $0003 ; HC Back Hall (x01)
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Sewer Switches (x02) dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Sewer Switches (x02)
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Crystaroller dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Crystaroller
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Arghus dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Arghus
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Aga 2 dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Aga 2
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Sewer Secret Room dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Sewer Secret Room
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Sanc dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Sanc
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TR Pokey dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TR Pokey
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TR Lava Pipe dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TR Lava Pipe
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TR Pipes n Ledge dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TR Pipes n Ledge
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Swap Canal dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Swap Canal
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Pod dark Maze dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Pod dark Maze
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Pod Bridge dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Pod Bridge
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Pod Eye Statue dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Pod Eye Statue
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; GT Pre Aga dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; GT Pre Aga
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Ice Cross dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Ice Cross
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Ice BK dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Ice BK
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; x20 Aga1 dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; x20 Aga1
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Sewer Key Rat dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Sewer Key Rat
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Sewer Waters dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Sewer Waters
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TR Eye Entrance dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TR Eye Entrance
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TR Chest Entrance dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TR Chest Entrance
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Swamp Statue dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Swamp Statue
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; PoD Arena (x2a) dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; PoD Arena (x2a)
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; PoD Statue (x2b) dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; PoD Statue (x2b)
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Ice Compass dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Ice Compass
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; x30 Aga's Altar dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; x30 Aga's Altar
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Dark Cross dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Dark Cross
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Lanmolas dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Lanmolas
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Swamp West Wing dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Swamp West Wing
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Flooded Key dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Flooded Key
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Swamp Main Hub (x36) dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Swamp Main Hub (x36)
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Swamp Hammer Time dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Swamp Hammer Time
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Swamp First Basement dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Swamp First Basement
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Drop to the Moth dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Drop to the Moth
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Pod 3 Catwalks dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Pod 3 Catwalks
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Pod Conveyor dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Pod Conveyor
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; GT Minihelma dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; GT Minihelma
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Ice Conveyor dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Ice Conveyor
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Sewers dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Sewers
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Desert Torches dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Desert Torches
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TT Big Chest dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TT Big Chest
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TT Cellblock dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TT Cellblock
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Swamp Compass Loop dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Swamp Compass Loop
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Skull3 Torches dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Skull3 Torches
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Pod Entrance dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Pod Entrance
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Pod Mimics 1 dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Pod Mimics 1
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; GT Conveyor Ice dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; GT Conveyor Ice
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; GT Moldorm dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; GT Moldorm
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; IPBJ dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; IPBJ
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $0401, $8000, $8000 ; HC West Hall (x50) dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0401, $0003, $0003 ; HC West Hall (x50)
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; HC Throne Room (x51) dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; HC Throne Room (x51)
dw $8000, $8000, $8000, $0401, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; HC East Hall (x52) dw $0003, $0003, $0003, $0401, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; HC East Hall (x52)
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Desert Tiles 1 dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Desert Tiles 1
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Skull 2 Left Entrance dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Skull 2 Left Entrance
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Skull 2 Right Entrance dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Skull 2 Right Entrance
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Skull 1 Entrance dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Skull 1 Entrance
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Skull 3 Entrance dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Skull 3 Entrance
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Helmasaur dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Helmasaur
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; GT Spike Switch dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; GT Spike Switch
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; GT Cannonball dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; GT Cannonball
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Gauntlet 1 dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Gauntlet 1
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Ice Choice Cross dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Ice Choice Cross
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Iced U dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Iced U
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; HC West Lobby (x60) dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; HC West Lobby (x60)
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; HC Main Lobby (x61) dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; HC Main Lobby (x61)
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; HC East Lobby (x62) dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; HC East Lobby (x62)
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; x66 Swamp Waterfall dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; x66 Swamp Waterfall
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; x67 Skull 1 Left Drop dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; x67 Skull 1 Left Drop
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; x68 Skull 1 Pinball dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; x68 Skull 1 Pinball
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; x6a Pod Rupees dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; x6a Pod Rupees
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; x6b GT Mimics dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; x6b GT Mimics
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; x6c GT Lanmolas dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; x6c GT Lanmolas
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; x6d Gauntlet 2 dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; x6d Gauntlet 2
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; x6e Ice Gators dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; x6e Ice Gators
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; HC Armory dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; HC Armory
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Desert BK Chest dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Desert BK Chest
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Swamp Flooded Chests dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Swamp Flooded Chests
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; GT DM's Tile dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; GT DM's Tile
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; GT Randoroom dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; GT Randoroom
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; GT Warp Maze dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; GT Warp Maze
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Ice Freezors dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Ice Freezors
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Ice Hookpit dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Ice Hookpit
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; HC Catawalk dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; HC Catawalk
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Desert Right Entrance dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Desert Right Entrance
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; GT Left dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; GT Left
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; GT Hopeful Torch dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; GT Hopeful Torch
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; GT Right dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; GT Right
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Ice Lonely Freezor dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Ice Lonely Freezor
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Vitreous (x90) dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Vitreous (x90)
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Mire Rain dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Mire Rain
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Mire Dark Crystals dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Mire Dark Crystals
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Mire Blockswitch dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Mire Blockswitch
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; GT Fallbridge dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; GT Fallbridge
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; GT Torch Cross dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; GT Torch Cross
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Eastern Darkness dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Eastern Darkness
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; GT Warp Maze 2 dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; GT Warp Maze 2
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; GT Invis Bridge dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; GT Invis Bridge
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; GT Compass Room dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; GT Compass Room
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Ice Big Chests dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Ice Big Chests
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Icy Pots dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Icy Pots
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Mire Pre-Vitreous (xa0) dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Mire Pre-Vitreous (xa0)
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Mire Fishbone dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Mire Fishbone
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Mire Bridges dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Mire Bridges
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Mire Corner dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Mire Corner
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Trinexx (xa4) dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Trinexx (xa4)
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; GT Wizzrobes dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; GT Wizzrobes
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Eastern Compass (xa8) dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Eastern Compass (xa8)
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Eastern Courtyard (xa9) dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Eastern Courtyard (xa9)
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Eastern Map (xaa) dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Eastern Map (xaa)
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TT Switch dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TT Switch
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Blind dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Blind
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Iced T dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Iced T
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Ice Slipway dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Ice Slipway
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Mire Warpzone dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Mire Warpzone
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Mire ???? dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Mire ????
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Mire Spikes dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Mire Spikes
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TR Refill dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TR Refill
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TR Dark Maze dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TR Dark Maze
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TR Chainchomp dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TR Chainchomp
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TR Rollers dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TR Rollers
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Eastern Big Key dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Eastern Big Key
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Easter Cannonball dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Easter Cannonball
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Eastern Dark Circle dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Eastern Dark Circle
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TT Hellway dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TT Hellway
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TT Bossway dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TT Bossway
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Ice Blockswitch dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Ice Blockswitch
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Ice Backtracker dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Ice Backtracker
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Mire Tiles dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Mire Tiles
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Mire Main Hub dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Mire Main Hub
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Mire Big Chest dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Mire Big Chest
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TR Switch Maze dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TR Switch Maze
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TR Narrow dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TR Narrow
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TR Early Hub dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TR Early Hub
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TR Floating Torches dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TR Floating Torches
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Armos dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Armos
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Eastern Entrance dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Eastern Entrance
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TT NW Quad dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TT NW Quad
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TT NE Quad dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TT NE Quad
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Ice Boss Drop dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Ice Boss Drop
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Mire BK dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Mire BK
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Mire 2 dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Mire 2
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TR Laser Bridge dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TR Laser Bridge
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TR Main Entrance dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TR Main Entrance
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Eastern Eyegores dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Eastern Eyegores
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Eastern Attic Switches dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Eastern Attic Switches
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; Eastern Attic Start dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Eastern Attic Start
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TT Entrance Quad dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TT Entrance Quad
dw $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000, $8000 ; TT SE Quad dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; TT SE Quad
; this should end at 27AE10 about dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Aga 6F
; some values you can hardcode dw $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003, $0003 ; Sewers Rope
; this should end at 27AE40 about (152 * 24 bytes = 3648 or E40)
; some values you can hardcode for spirals
;dw $0070, $36a0 ; ->HC Stairwell ;dw $0070, $36a0 ; ->HC Stairwell
;dw $0072, $4ff8 ; ->HC Map Room ;dw $0072, $4ff8 ; ->HC Map Room
;dw $0080, $1f50 ; ->zelda's cellblock ;dw $0080, $1f50 ; ->zelda's cellblock
org $27B000 org $27B000
SpiralTable: SpiralTable: ;113 4 byte entries - should end at 27B44C
dw $0203, $8080 ;null row dw $0203, $8080 ;null row
dw $0203, $8080 ;HC Backhallway dw $0203, $8080 ;HC Backhallway
dw $0203, $8080 ;Sewer Pull dw $0203, $8080 ;Sewer Pull
@@ -490,14 +492,15 @@ dw $0000
dw $0000,$0000,$0000,$0000 dw $0000,$0000,$0000,$0000
dw $ffff ; indicates the end - we can drop this dw $ffff ; indicates the end - we can drop this
; Edge Transition Table ; Edge Transition Table (Target Room, Flags, MultiDiv ratio for edges)
org $27C500 ;ends around 27C5F0 org $27C500 ;ends around 27C5F(9) 4 bytes would be 27C649
;I kind of want to split the 3rd byte into two
NorthOpenEdge: NorthOpenEdge:
db $00,$80,$11, $00,$80,$11, $00,$80,$11, $00,$80,$11 db $00,$80,$11, $00,$80,$11, $00,$80,$11, $00,$80,$11
db $00,$80,$11, $00,$80,$11, $00,$80,$11, $00,$80,$11 db $00,$80,$11, $00,$80,$11, $00,$80,$11, $00,$80,$11
db $00,$80,$11, $00,$80,$11, $00,$80,$11 db $00,$80,$11, $00,$80,$11, $00,$80,$11
SouthOpenEdge: SouthOpenEdge:
db $83,$a2,$11, $00,$80,$11, $00,$80,$11, $00,$80,$11 db $00,$80,$11, $00,$80,$11, $00,$80,$11, $00,$80,$11
db $00,$80,$11, $00,$80,$11, $00,$80,$11, $00,$80,$11 db $00,$80,$11, $00,$80,$11, $00,$80,$11, $00,$80,$11
db $00,$80,$11, $00,$80,$11, $00,$80,$11 db $00,$80,$11, $00,$80,$11, $00,$80,$11
WestOpenEdge: WestOpenEdge:
@@ -509,6 +512,7 @@ db $00,$80,$11, $00,$80,$11, $00,$80,$11
db $00,$80,$11, $00,$80,$11, $00,$80,$11 db $00,$80,$11, $00,$80,$11, $00,$80,$11
db $00,$80,$11, $00,$80,$11, $00,$80,$11 db $00,$80,$11, $00,$80,$11, $00,$80,$11
; Edge Info Table (Midpoint, Width, Min Coord) ; Edge Info Table (Midpoint, Width, Min Coord)
; I kind of want to add a fourth byte to help indicate quadrant info on min coord
NorthEdgeInfo: NorthEdgeInfo:
db $a8,$10,$a0, $2c,$08,$28 ;HC db $a8,$10,$a0, $2c,$08,$28 ;HC
db $b8,$20,$a8 ; DP West Wing db $b8,$20,$a8 ; DP West Wing
@@ -539,8 +543,9 @@ db $68,$10,$60, $84,$18,$78 ; HC Guards
db $a0,$a0,$50 ; DP Main Lobby db $a0,$a0,$50 ; DP Main Lobby
db $58,$50,$30, $98,$50,$70 ; TT Ambush db $58,$50,$30, $98,$50,$70 ; TT Ambush
db $58,$50,$30 ; TT Nook db $58,$50,$30 ; TT Nook
MultDivInfo: ; (1placeholder, 1, 2, 3, 4, 5, 6, 10, 20) MultDivInfo: ; (1, 2, 3, 4, 5, 6, 10, 20)
db $01, $01, $02, $03, $04, $05, $06, $0a, $14 db $01, $02, $03, $04, $05, $06, $0a, $14
; indices: 0-7
; dungeon tables ; dungeon tables
@@ -556,5 +561,53 @@ BigKeyStatus: ;27f038 (status 2 indicate BnC guard)
dw $0002, $0002, $0001, $0001, $0000, $0001, $0001, $0001, $0001, $0001, $0001, $0001, $0001, $0001 dw $0002, $0002, $0001, $0001, $0000, $0001, $0001, $0001, $0001, $0001, $0001, $0001, $0001, $0001
DungeonReminderTable: ;27f054 DungeonReminderTable: ;27f054
dw $2D50, $2D50, $2D51, $2D52, $2D54, $2D56, $2D55, $2D5A, $2D57, $2D59, $2D53, $2D58, $2D5B, $2D5C dw $2D50, $2D50, $2D51, $2D52, $2D54, $2D56, $2D55, $2D5A, $2D57, $2D59, $2D53, $2D58, $2D5B, $2D5C
;27f070 TotalLocationsLow: ;27f070
db $08, $08, $06, $06, $02, $00, $04, $08, $08, $08, $06, $08, $02, $07
TotalLocationsHigh: ;27f07e
db $00, $00, $00, $00, $00, $01, $01, $00, $00, $00, $00, $00, $01, $02
;27F08C
; Vert 0,6,0 Horz 2,0,8
org $27f090
CoordIndex: ; Horizontal 1st
db 2, 0 ; Coordinate Index $20-$23
OppCoordIndex:
db 0, 2 ; Swapped coordinate Index $20-$23 (minor optimization)
CameraIndex: ; Horizontal 1st
db 0, 6 ; Camera Index $e2-$ea
CamQuadIndex: ; Horizontal 1st
db 8, 0 ; Camera quadrants $600-$60f
ShiftQuadIndex:
db 2, 1 ; see ShiftQuad func (relates to $a9,$aa)
CamBoundIndex: ; Horizontal 1st
db 0, 4 ; Camera Bounds $0618-$61f
OppCamBoundIndex: ; Horizontal 1st
db 4, 0 ; Camera Bounds $0618-$61f
CamBoundBaseLine: ; X camera stuff is 1st column todo Y camera needs more testing
dw $007f, $0077 ; Left/Top camera bounds when at edge or layout frozen
dw $0007, $000b ; Left/Top camera bounds when not frozen + appropriate low byte $22/$20 (preadj. by #$78/#$6c)
dw $00ff, $010b ; Right/Bot camera bounds when not frozen + appropriate low byte $20/$22
dw $017f, $0187 ; Right/Bot camera bound when at edge or layout frozen
;27f0ae next free byte
org $27f100
TilesetTable:
; 0 1 2 3 4 5 6 7 8 9 a b c d e f --Offset Ruler
db $13,$04,$04,$06,$0d,$ff,$08,$05,$06,$07,$07,$07,$0e,$0e,$0b,$ff
db $13,$04,$04,$0d,$0d,$0d,$08,$05,$06,$07,$07,$07,$0e,$0e,$0b,$0b
db $04,$04,$04,$0d,$0d,$ff,$08,$05,$08,$09,$07,$07,$06,$ff,$0b,$06
db $04,$05,$04,$12,$08,$08,$08,$08,$08,$09,$07,$07,$06,$0e,$0b,$0b
db $04,$04,$04,$12,$0a,$0a,$08,$ff,$ff,$09,$07,$07,$0e,$0e,$0b,$0b
db $04,$04,$04,$12,$08,$01,$09,$09,$09,$09,$07,$0e,$0e,$0e,$0b,$0b
db $04,$04,$04,$12,$0a,$0a,$08,$09,$09,$ff,$07,$0e,$0e,$0e,$0b,$ff
db $04,$04,$04,$12,$12,$12,$08,$05,$ff,$ff,$ff,$0e,$0e,$0e,$0b,$0b
db $04,$04,$04,$12,$12,$12,$ff,$05,$ff,$05,$ff,$0e,$0e,$0e,$0b,$ff
db $0c,$0c,$0c,$0c,$ff,$0e,$0e,$0c,$0c,$05,$ff,$0e,$0e,$0e,$0b,$0b
db $0c,$0c,$0c,$0c,$0d,$0e,$0e,$05,$05,$05,$05,$0a,$0a,$ff,$0b,$0b
db $04,$0c,$0c,$0c,$0d,$0d,$0d,$0d,$05,$05,$05,$0a,$0a,$ff,$0b,$0b
db $04,$0c,$0c,$0c,$0d,$0d,$0d,$0d,$05,$05,$ff,$0a,$0a,$ff,$0b,$ff
db $04,$0c,$0c,$ff,$ff,$0d,$0d,$ff,$05,$05,$05,$0a,$0a,$ff,$0b,$06
db $04,$06,$06,$06,$06,$06,$06,$06,$06,$ff,$06,$06,$ff,$06,$06,$06
db $06,$06,$03,$03,$03,$03,$ff,$ff,$06,$06,$06,$06,$ff,$06,$06,$06
;27f200
+38 -6
View File
@@ -24,14 +24,34 @@ NotLinkDoor2:
; Staircase routine ; Staircase routine
org $01c3d4 ;(PC: c3d4) org $01c3d4 ; <- c3d4 - Bank01.asm : 9762-4 (Dungeon_DetectStaircase-> STA $A0 : LDA $063D, X)
jsl RecordStairType : nop jsl RecordStairType : nop
org $02a1e7 ;(PC: 121e7) org $02a1e7 ;(PC: 121e7)
jsl SpiralWarp jsl SpiralWarp
org $0291b3 ; <- Bank02.asm : 3303 (LDA $0462 : AND.b #$04)
jsl SpiralPriorityHack : nop
org $0290f9 ; <- Bank02.asm : 3188 (LDA $0462 : AND.b #$04)
jsl SpiralPriorityHack : nop
org $029369 ; <- 11369 - Bank02.asm : 3610 (STX $0464 : STY $012E)
jsl StraightStairsAdj : nop #2
org $029383 ; <- 11384 - Bank02.asm : 3629 (.walkingDownStaircase-> ADD $20 : STA $20)
jsl StraightStairsFix : nop
org $0293aa ; <- 113aa - Bank02.asm : 3653 (ADD $20 : STA $20)
jsl StraightStairsFix : nop
org $0293d1 ; <- 113d1 - Bank02.asm : 3683 (ADD $20 : STA $20 BRANCH_IOTA)
jsl StraightStairsFix : nop
org $029396 ; <- 11396 - Bank02.asm : 3641 (LDA $01C322, X)
jsl StraightStairLayerFix
org $02c06d ; <- Bank02.asm : 9874 (LDX $0418, CMP.b #$02)
jsl DoorToStraight : nop
org $02941a ; <- Bank02.asm : 3748 module 7.12.11 (LDA $0464 : BNE BRANCH_$11513 : INC $B0 : RTS)
jsl StraightStairsTrapDoor : rts
; Graphics fix ; Graphics fix
org $02895d org $02895d ; Bank 02 line 1812 (JSL Dungeon_LoadRoom : JSL Dungeon_InitStarTileChr : JSL $00D6F9 : INC $B0)
Splicer: Splicer:
jsl GfxFixer jsl GfxFixer
lda $b1 : beq .done lda $b1 : beq .done
@@ -39,14 +59,20 @@ rts
nop #5 nop #5
.done .done
org $00fda4 org $00d377 ;Bank 00 line 3185
DecompDungAnimatedTiles:
org $00fda4 ;Bank 00 line 8882
Dungeon_InitStarTileCh: Dungeon_InitStarTileCh:
org $00d6ae ;(PC: 56ae) org $00d6ae ;(PC: 56ae)
LoadTransAuxGfx: LoadTransAuxGfx:
org $00d739 ;
LoadTransAuxGfx_Alt:
org $00df5a ;(PC: 5f5a) org $00df5a ;(PC: 5f5a)
PrepTransAuxGfx: PrepTransAuxGfx:
org $0ffd65 ;(PC: 07fd65) org $0ffd65 ;(PC: 07fd65)
Dungeon_LoadCustomTileAttr: Dungeon_LoadCustomTileAttr:
org $01feb0
Dungeon_ApproachFixedColor:
;org $01fec1 ;org $01fec1
;Dungeon_ApproachFixedColor_variable: ;Dungeon_ApproachFixedColor_variable:
;org $a0f972 ; Rando version ;org $a0f972 ; Rando version
@@ -75,9 +101,9 @@ nop : stz $0dd0, X : rts
.not_in_ganons_tower .not_in_ganons_tower
org $2081f2 org $208206
jsl MirrorCheckOverride2 jsl MirrorCheckOverride2
org $20825c org $208270
jsl MirrorCheckOverride2 jsl MirrorCheckOverride2
org $07a955 ; <- Bank07.asm : around 6564 (JP is a bit different) (STZ $05FC : STZ $05FD) org $07a955 ; <- Bank07.asm : around 6564 (JP is a bit different) (STZ $05FC : STZ $05FD)
jsl BlockEraseFix jsl BlockEraseFix
@@ -94,9 +120,15 @@ jsl GuruguruFix : bra .next
nop #3 nop #3
.next .next
org $028fc9
nop #2 : jsl BlindAtticFix
; also rando's hooks.asm line 1360 ; also rando's hooks.asm line 1360
org $a0ee11 ; <- 6FC4C - headsup_display.asm : 836 (LDA $7EF36E : AND.w #$00FF : ADD.w #$0007 : AND.w #$FFF8 : TAX) ; 106e4e -> goes to a0ee4e
org $a0ee8a ; <- 6FC4C - headsup_display.asm : 836 (LDA $7EF36E : AND.w #$00FF : ADD.w #$0007 : AND.w #$FFF8 : TAX)
jsl DrHudOverride jsl DrHudOverride
org $0ded04 ; <- rando's hooks.asm line 2192 - 6ED04 - equipment.asm : 1963 (REP #$30)
jsl DrHudDungeonItemsAdditions
org $098638 ; rando's hooks.asm line 2192 org $098638 ; rando's hooks.asm line 2192
jsl CountChestKeys jsl CountChestKeys
org $06D192 ; rando's hooks.asm line 457 org $06D192 ; rando's hooks.asm line 457
+105 -103
View File
@@ -1,6 +1,6 @@
HorzEdge: HorzEdge:
cpy #$ff : beq + cpy #$ff : beq +
jsr DetectWestEdge : bra ++ jsr DetectWestEdge : ldy #$02 : bra ++
+ jsr DetectEastEdge + jsr DetectEastEdge
++ cmp #$ff : beq + ++ cmp #$ff : beq +
sta $00 : asl : !add $00 : tax sta $00 : asl : !add $00 : tax
@@ -28,154 +28,156 @@ VertEdge:
LoadEdgeRoomHorz: LoadEdgeRoomHorz:
lda $03 : sta $a0 lda $03 : sta $a0
sty $09 sty $06
and.b #$0f : asl a : !sub $23 : !add $09 : sta $02 and.b #$0f : asl a : !sub $23 : !add $06 : sta $02
ldy #$00 : jsr ShiftVariablesMainDir ldy #$00 : jsr ShiftVariablesMainDir
lda $a0 : and.b #$F0 : lsr #3 : sta $0603 : inc : sta $0607
lda $04 : and #$80 : bne .edge
lda $04 : sta $01 ; load up flags in $01
jsr PrepScrollToNormal
bra .scroll
lda $aa : asl : tax ; current quad as 0/4 .edge
lda $04 : and #$40 : bne + lda $04 : and #$10 : beq +
lda $603 : sta $00 : stz $01 : bra ++ lda #$01
+ lda $607 : sta $00 : lda #$02 : sta $01 + sta $ee ; layer stuff
++ ; $01 now contains 0 or 2
lda $00 : sta $21 : sta $0601 : sta $0605
lda $01 : sta $aa : lsr : sta $01 : stz $00
lda $0a : sta $20
stz $0e jsr MathHorz
rep #$30
lda $e8 : and #$01ff : sta $02
lda $0a : and #$00ff : !add $00 : sta $00
cmp #$006c : !bge + .scroll
lda #$0077 : bra ++ jsr ScrollY
+ cmp #$017c : !blt +
lda #$0187 : bra ++
+ !add #$000b
++ sta $0618 : inc #2 : sta $061a
lda $00 : cmp #$0078 : !bge +
lda #$0000 : bra ++
+ cmp #$0178 : !blt +
lda #$0100 : bra ++
+ !sub #$0078
++ sta $00
; figures out scroll amt
cmp $02 : bne +
lda #$0000 : bra .done
+ !blt +
!sub $02 : inc $0e : bra .done
+ lda $02 : !sub $00
.done sta $ab : sep #$30
lda $0e : asl : ora $ac : sta $ac
lda $0603, x : sta $e9
lda $04 : and #$80 : lsr #4 : sta $ee ; layer stuff
rts rts
LoadEdgeRoomVert: LoadEdgeRoomVert:
lda $03 : sta $a0 lda $03 : sta $a0
sty $09 sty $06
and.b #$f0 : lsr #3 : !sub $21 : !add $09 : sta $02 and.b #$f0 : lsr #3 : !sub $21 : !add $06 : sta $02
ldy #$01 : jsr ShiftVariablesMainDir ldy #$01 : jsr ShiftVariablesMainDir
lda $a0 : and.b #$0f : asl : sta $060b : inc : sta $060f
lda $a9 : asl #2 : tax ; current quad as 0/4 lda $04 : and #$80 : bne .edge
lda $04 : and #$20 : bne + lda $04 : sta $01 ; load up flags in $01
lda $60b : sta $00 : stz $01 : bra ++ jsr PrepScrollToNormal
+ lda $60f : sta $00 : lda #$01 : sta $01 bra .scroll
++ ; $01 now contains 0 or 1
lda $00 : sta $23 : sta $0609 : sta $060d
lda $01 : sta $a9 : stz $00 ; setup for 16 bit ops
lda $0a : sta $22
stz $0e ; pos/neg indicator .edge
rep #$30 lda $04 : and #$10 : beq +
lda $e2 : and #$01ff : sta $02 lda #$01
lda $0a : and #$00ff : !add $00 : sta $00 + sta $ee ; layer stuff
cmp #$0078 : !bge + jsr MathVert
lda #$007f : bra ++ lda $03
+ cmp #$0178 : !blt +
lda #$017f : bra ++
+ !add #$0007
++ sta $061c : inc #2 : sta $061e
lda $00 : cmp #$0078 : !bge + .scroll
lda #$0000 : bra ++ jsr ScrollX
+ cmp #$0178 : !blt +
lda #$0100 : bra ++
+ !sub #$0078
++ sta $00
; figures out scroll amt
cmp $02 : bne +
lda #$0000 : bra .done
+ !blt +
!sub $02 : inc $0e : bra .done
+ lda $02 : !sub $00
.done sta $ab : sep #$30
lda $0e : asl : ora $ac : sta $ac
lda $060b, x : sta $e3
lda $04 : and #$10 : lsr #4 : sta $ee ; layer stuff
rts rts
MathHorz:
jsr MathStart : lda $20
jsr MathMid : and #$0040
jsr MathEnd
rts
MathVert:
jsr MathStart : lda $22
jsr MathMid : and #$0020
jsr MathEnd
rts
MathStart:
rep #$30
lda $08 : and #$00ff : sta $00
rts
MathMid:
and #$01ff : !sub $00 : and #$00ff : sta $00
; nothing should be bigger than $a0 at this point
lda $05 : and #$00f0 : lsr #4 : tax
lda MultDivInfo, x : and #$00ff : tay
lda $00 : jsr MultiplyByY : sta $02
lda $07 : and #$00ff : jsr MultiplyByY : tax
lda $05 : and #$000f : tay
lda MultDivInfo, y : and #$00ff : tay
lda $02 : jsr DivideByY : sta $00
lda $0c : and #$00ff : sta $02
lda $04
rts
MathEnd:
beq +
lda #$0100
+ !add $02 : !add $00
sta $04
sep #$30
rts
; don't need midpoint of edge Link is leaving (formerly in $06 - used by dir indicator)
; don't need width of edge Link is going to (currently in $0b)
LoadNorthData: LoadNorthData:
lda NorthEdgeInfo, x : sta $06 ; not needed I think lda NorthOpenEdge, x : sta $03 : inx ; target room
lda NorthOpenEdge, x : sta $03 : inx lda NorthEdgeInfo, x : sta $07 ; needed for maths - (divide by 2 anyway)
lda NorthEdgeInfo, x : sta $07 ;probably needed for maths - unsure lda NorthOpenEdge, x : sta $04 : inx ; bit field
lda NorthOpenEdge, x : sta $04 : inx
lda NorthEdgeInfo, x : sta $08 ; needed for maths lda NorthEdgeInfo, x : sta $08 ; needed for maths
lda NorthOpenEdge, x : sta $05 lda NorthOpenEdge, x : sta $05 ; ratio
lda $04 : and #$0f : sta $00 : asl : !add $00 : tax lda $04 : jsr LoadSouthMidpoint : inx ; needed now, and for nrml transition
lda SouthEdgeInfo, x : sta $0a : inx ; needed now, and for nrml transition lda SouthEdgeInfo, x : sta $0b : inx ; probably not needed todo: remove
lda SouthEdgeInfo, x : sta $0b : inx ; probably not needed - unsure
lda SouthEdgeInfo, x : sta $0c ; needed for maths lda SouthEdgeInfo, x : sta $0c ; needed for maths
rts rts
LoadSouthMidpoint:
and #$0f : sta $00 : asl : !add $00 : tax
lda SouthEdgeInfo, x : sta $0a ; needed now, and for nrml transition
rts
LoadSouthData: LoadSouthData:
lda SouthEdgeInfo, x : sta $06
lda SouthOpenEdge, x : sta $03 : inx lda SouthOpenEdge, x : sta $03 : inx
lda SouthEdgeInfo, x : sta $07 lda SouthEdgeInfo, x : sta $07
lda SouthOpenEdge, x : sta $04 : inx lda SouthOpenEdge, x : sta $04 : inx
lda SouthEdgeInfo, x : sta $08 lda SouthEdgeInfo, x : sta $08
lda SouthOpenEdge, x : sta $05 lda SouthOpenEdge, x : sta $05
lda $04 : and #$0f : sta $00 : asl : !add $00 : tax lda $04 : jsr LoadNorthMidpoint : inx
lda NorthEdgeInfo, x : sta $0a : inx
lda NorthEdgeInfo, x : sta $0b : inx lda NorthEdgeInfo, x : sta $0b : inx
lda NorthEdgeInfo, x : sta $0c lda NorthEdgeInfo, x : sta $0c
rts rts
LoadNorthMidpoint:
and #$0f : sta $00 : asl : !add $00 : tax
lda NorthEdgeInfo, x : sta $0a ; needed now, and for nrml transition
rts
LoadWestData: LoadWestData:
lda WestEdgeInfo, x : sta $06
lda WestOpenEdge, x : sta $03 : inx lda WestOpenEdge, x : sta $03 : inx
lda WestEdgeInfo, x : sta $07 lda WestEdgeInfo, x : sta $07
lda WestOpenEdge, x : sta $04 : inx lda WestOpenEdge, x : sta $04 : inx
lda WestEdgeInfo, x : sta $08 lda WestEdgeInfo, x : sta $08
lda WestOpenEdge, x : sta $05 lda WestOpenEdge, x : sta $05
lda $04 : and #$0f : sta $00 : asl : !add $00 : tax lda $04 : jsr LoadEastMidpoint : inx
lda EastEdgeInfo, x : sta $0a : inx
lda EastEdgeInfo, x : sta $0b : inx lda EastEdgeInfo, x : sta $0b : inx
lda EastEdgeInfo, x : sta $0c lda EastEdgeInfo, x : sta $0c
rts rts
LoadEastMidpoint:
and #$0f : sta $00 : asl : !add $00 : tax
lda EastEdgeInfo, x : sta $0a ; needed now, and for nrml transition
rts
LoadEastData: LoadEastData:
lda EastEdgeInfo, x : sta $06
lda EastOpenEdge, x : sta $03 : inx lda EastOpenEdge, x : sta $03 : inx
lda EastEdgeInfo, x : sta $07 lda EastEdgeInfo, x : sta $07
lda EastOpenEdge, x : sta $04 : inx lda EastOpenEdge, x : sta $04 : inx
lda EastEdgeInfo, x : sta $08 lda EastEdgeInfo, x : sta $08
lda EastOpenEdge, x : sta $05 lda EastOpenEdge, x : sta $05
lda $04 : and #$0f : sta $00 : asl : !add $00 : tax lda $04 : jsr LoadWestMidpoint : inx
lda WestEdgeInfo, x : sta $0a : inx
lda WestEdgeInfo, x : sta $0b : inx lda WestEdgeInfo, x : sta $0b : inx
lda WestEdgeInfo, x : sta $0c lda WestEdgeInfo, x : sta $0c
LoadWestMidpoint:
and #$0f : sta $00 : asl : !add $00 : tax
lda WestEdgeInfo, x : sta $0a ; needed now, and for nrml transition
rts rts
@@ -258,8 +260,8 @@ DetectWestEdge:
ldx #$05 : bra .end ldx #$05 : bra .end
+ cmp #$cc : bne + + cmp #$cc : bne +
lda $aa : beq ++ lda $aa : beq ++
ldx #$07 : bra .end ldx #$06 : bra .end
++ ldx #$06 : bra .end ++ ldx #$07 : bra .end
+ cmp #$dc : bne .end + cmp #$dc : bne .end
ldx #$08 ldx #$08
.end txa : rts .end txa : rts
@@ -281,8 +283,8 @@ DetectEastEdge:
ldx #$05 : bra .end ldx #$05 : bra .end
+ cmp #$cb : bne + + cmp #$cb : bne +
lda $aa : beq ++ lda $aa : beq ++
ldx #$07 : bra .end ldx #$06 : bra .end
++ ldx #$06 : bra .end ++ ldx #$07 : bra .end
+ cmp #$db : bne .end + cmp #$db : bne .end
ldx #$08 ldx #$08
.end txa : rts .end txa : rts
+14 -1
View File
@@ -1,11 +1,24 @@
GfxFixer: GfxFixer:
{ {
lda $b1 : bne .stage2 lda.l DRMode : bne +
jsl LoadRoomHook ;this is the code we overwrote
jsl Dungeon_InitStarTileCh
jsl LoadTransAuxGfx_Alt
inc $b0
rtl
+ lda $b1 : bne .stage2
jsl LoadRoomHook ; this is the rando version - let's only call this guy once - may fix star tiles and slower loads jsl LoadRoomHook ; this is the rando version - let's only call this guy once - may fix star tiles and slower loads
jsl Dungeon_InitStarTileCh jsl Dungeon_InitStarTileCh
jsl LoadTransAuxGfx jsl LoadTransAuxGfx
jsl Dungeon_LoadCustomTileAttr jsl Dungeon_LoadCustomTileAttr
jsl PrepTransAuxGfx jsl PrepTransAuxGfx
lda.l DRMode : cmp #$02 : bne + ; only do this in crossed mode
ldx $a0 : lda.l TilesetTable, x
cmp $0aa1 : beq + ; already eq no need to decomp
sta $0aa1
tax : lda $02802e, x : tay
jsl DecompDungAnimatedTiles
+
lda #$09 : sta $17 : sta $0710 lda #$09 : sta $17 : sta $0710
jsl Palette_SpriteAux3 jsl Palette_SpriteAux3
jsl Palette_SpriteAux2 jsl Palette_SpriteAux2
+129 -23
View File
@@ -7,51 +7,124 @@ DrHudOverride:
HudAdditions: HudAdditions:
{ {
lda.l DRFlags : and #$0008 : beq ++
lda $7EF423 : and #$00ff
jsr HudHexToDec4DigitCopy
LDX.b $05 : TXA : ORA.w #$2400 : STA !GOAL_DRAW_ADDRESS+10 ; draw 100's digit
LDX.b $06 : TXA : ORA.w #$2400 : STA !GOAL_DRAW_ADDRESS+12 ; draw 10's digit
LDX.b $07 : TXA : ORA.w #$2400 : STA !GOAL_DRAW_ADDRESS+14 ; draw 1's digit
lda $7ef29b : and #$0020 : beq +
lda #$207f : bra .drawthing
+ lda #$345e
.drawthing STA !GOAL_DRAW_ADDRESS+16 ; castle gate indicator
++
ldx $040c : cpx #$ff : bne + : rts : + ldx $040c : cpx #$ff : bne + : rts : +
lda DRMode : bne + : rts : + lda.l DRMode : bne + : rts : +
phb : phk : plb phb : phk : plb
lda $7ef364 : and.l $0098c0, x : beq + lda $7ef364 : and.l $0098c0, x : beq +
lda CompassBossIndicator, x : and #$00ff : cmp $a0 : bne + lda.w CompassBossIndicator, x : and #$00ff : cmp $a0 : bne +
lda $1a : and #$0010 : beq + lda $1a : and #$0010 : beq +
lda #$345e : sta $7ec790 : bra .next lda #$345e : sta $7ec790 : bra .next
+ lda #$207f : sta $7ec790 + lda #$207f : sta $7ec790
.next lda DRMode : and #$0002 : bne + : plb : rts : + .next lda.w DRMode : and #$0002 : bne + : plb : rts : +
lda $7ef36d : and #$00ff : beq + lda $7ef36d : and #$00ff : beq +
lda DungeonReminderTable, x : bra .reminder lda.w DungeonReminderTable, x : bra .reminder
+ lda #$207f + lda #$207f
.reminder sta $7ec702 .reminder sta $7ec702
+ lda DRFlags : and #$0004 : beq .restore + lda.w DRFlags : and #$0004 : beq .restore
lda $7ef368 : and.l $0098c0, x : beq .restore lda $7ef368 : and.l $0098c0, x : beq .restore
lda #$2811 : sta $7ec740 ; lda #$2811 : sta $7ec740
lda $7ef366 : and.l $0098c0, x : bne .check ; lda $7ef366 : and.l $0098c0, x : bne .check
lda BigKeyStatus, x : bne + ; change this, if bk status changes to one byte ; lda.w BigKeyStatus, x : bne + ; change this, if bk status changes to one byte
lda #$2574 : bra ++ ; lda #$2574 : bra ++
+ cmp #$0002 : bne + ; + cmp #$0002 : bne +
lda #$2420 : bra ++ ; lda #$2420 : bra ++
+ lda #$207f : bra ++ ; + lda #$207f : bra ++
.check lda #$2826 ; .check lda #$2826
++ sta $7ec742 ; ++ sta $7ec742
txa : lsr : tax txa : lsr : tax
lda $7ef4e0, x : jsr ConvertToDisplay : sta $7ec7a2 lda $7ef4e0, x : jsr ConvertToDisplay : sta $7ec7a2
lda #$2830 : sta $7ec7a4 lda #$2830 : sta $7ec7a4
lda ChestKeys, x : jsr ConvertToDisplay : sta $7ec7a6 lda.w ChestKeys, x : jsr ConvertToDisplay : sta $7ec7a6
lda #$2871 : sta $7ec780 ; lda #$2871 : sta $7ec780
lda TotalKeys, x ; lda.w TotalKeys, x
sep #$20 : !sub $7ef4b0, x : rep #$20 ; sep #$20 : !sub $7ef4b0, x : rep #$20 ; todo 4b0 no longer in use
jsr ConvertToDisplay : sta $7ec782 ; jsr ConvertToDisplay : sta $7ec782
.restore .restore
plb : rts plb : rts
} }
HudOffsets:
; none hc east desert aga swamp pod mire skull ice hera tt tr gt
dw $fffe, $0000, $0006, $0008, $0002, $0010, $000e, $0018, $0012, $0016, $000a, $0014, $001a, $001e
DrHudDungeonItemsAdditions:
{
jsl DrawHUDDungeonItems
lda.l HUDDungeonItems : and #$ff : bne + : rtl : +
lda.l DRMode : cmp #$02 : beq + : rtl : +
phx : phy : php
rep #$30
lda !HUD_FLAG : and.w #$0020 : beq + : bra ++ : +
lda HUDDungeonItems : and.w #$0003 : bne + : bra ++ : +
lda.w #$2810 : sta $1684 ; small keys icon
lda.w #$2811 : sta $16c4 ; big key icon
lda.w #$2810 : sta $1704 ; small keys icon
ldx #$0002
- lda $7ef364 : and.l $0098c0, x : beq + ; must have compass
lda.l HudOffsets, x : tay
jsr BkStatus : sta $16C6, y ; big key status
phx
txa : lsr : tax
lda.l ChestKeys, x : jsr ConvertToDisplay2 : sta $1706, y ; small key totals
plx
+ inx #2 : cpx #$001b : bcc -
++
lda !HUD_FLAG : and.w #$0020 : bne + : bra ++ : +
lda HUDDungeonItems : and.w #$000c : bne + : bra ++ : +
lda.w #$24f5 : sta $1704 ; blank
ldx #$0002
- lda $7ef364 : and.l $0098c0, x : beq + ; must have compass
lda.l HudOffsets, x : tay
phx ; total chest counts
txa : lsr : tax
lda.l TotalLocationsLow, x : jsr ConvertToDisplay2 : sta $1706, y
lda.l TotalLocationsHigh, x : jsr ConvertToDisplay2 : sta $16c6, y
plx
+
+ inx #2 : cpx #$001b : bcc -
++
plp : ply : plx : rtl
}
BkStatus:
lda $7ef366 : and.l $0098c0, x : bne +++ ; has the bk already
lda.l BigKeyStatus, x : bne ++
lda #$2482 : rts ; 0/O for no BK
++ cmp #$0002 : bne +
lda #$2420 : rts ; symbol for BnC
+ lda #$24f5 : rts ; black otherwise
+++ lda #$2826 : rts ; check mark
ConvertToDisplay: ConvertToDisplay:
and #$00ff : cmp #$000a : !blt + and.w #$00ff : cmp #$000a : !blt +
!add #$2553 : rts !add #$2553 : rts
+ !add #$2490 : rts + !add #$2490 : rts
ConvertToDisplay2:
and.w #$00ff : beq ++
cmp #$000a : !blt +
!add #$2553 : rts
+ !add #$2816 : rts
++ lda #$2483 : rts ; 0/O for 0 or placeholder digit
CountChestKeys: CountChestKeys:
jsl ItemDowngradeFix jsl ItemDowngradeFix
@@ -92,6 +165,39 @@ CountBonkItem:
lda.l BonkKey_GTower lda.l BonkKey_GTower
bra ++ bra ++
+ lda.b #$24 ; default to small key + lda.b #$24 ; default to small key
++ ++ cmp #$24 : bne +
phy : tay : jsr CountChest : ply phy : tay : jsr CountChest : ply
rtl + rtl
;================================================================================
; 16-bit A, 8-bit X
; in: A(b) - Byte to Convert
; out: $04 - $07 (high - low)
;================================================================================
HudHexToDec4DigitCopy:
LDY.b #$90
-
CMP.w #1000 : !BLT +
INY
SBC.w #1000 : BRA -
+
STY $04 : LDY #$90 ; Store 1000s digit & reset Y
-
CMP.w #100 : !BLT +
INY
SBC.w #100 : BRA -
+
STY $05 : LDY #$90 ; Store 100s digit & reset Y
-
CMP.w #10 : !BLT +
INY
SBC.w #10 : BRA -
+
STY $06 : LDY #$90 ; Store 10s digit & reset Y
CMP.w #1 : !BLT +
-
INY
DEC : BNE -
+
STY $07 ; Store 1s digit
RTS
+3 -3
View File
@@ -12,7 +12,7 @@
CheckIfDoorsOpen: { CheckIfDoorsOpen: {
jsr TrapDoorFixer ; see normal.asm jsr TrapDoorFixer ; see normal.asm
; note we are 16bit mode right now ; note we are 16bit mode right now
lda DRMode : beq + lda.l DRMode : beq +
lda $040c : cmp #$00ff : bne .gtg lda $040c : cmp #$00ff : bne .gtg
+ lda $a0 : dec : tax : and #$000f ; hijacked code + lda $a0 : dec : tax : and #$000f ; hijacked code
sec : rtl ; set carry to indicate normal behavior sec : rtl ; set carry to indicate normal behavior
@@ -22,9 +22,9 @@ CheckIfDoorsOpen: {
stx $00 : ldy #$0000 stx $00 : ldy #$0000
.nextDoor .nextDoor
lda $a0 : asl : tax lda $a0 : asl : tax
lda KeyDoorOffset, x : beq .skipDoor lda.w KeyDoorOffset, x : beq .skipDoor
asl : sty $05 : !add $05 : tax asl : sty $05 : !add $05 : tax
lda PairedDoorTable, x : beq .skipDoor lda.w PairedDoorTable, x : beq .skipDoor
sta $02 : and #$00ff : asl a : tax sta $02 : and #$00ff : asl a : tax
lda $02 : and #$ff00 : sta $03 lda $02 : and #$ff00 : sta $03
lda $7ef000, x : and #$f000 : and $03 : beq .skipDoor lda $7ef000, x : and #$f000 : and $03 : beq .skipDoor
+12 -7
View File
@@ -15,15 +15,19 @@ cpy #$0003 : bne ++
++ asl : sta $00 : tya : lsr : tay : lda $00 : bra .loop ++ asl : sta $00 : tya : lsr : tay : lda $00 : bra .loop
.done rts .done rts
;todo -- width in X? ;Divisor in Y. Width of division is in X for rounding toward middle
DivideByY: DivideByY:
.loop cpy #$0001 : beq .done .loop
cpy #$0000 : beq .done
cpy #$0001 : beq .done
cpy #$0003 : bne ++ cpy #$0003 : bne ++
jsr DivideBy3 : bra .done jsr DivideBy3 : bra .done
++ cpy #$0005 : bne ++ ++ cpy #$0005 : bne ++
jsr DivideBy5 : bra .done jsr DivideBy5 : bra .done
; todo -- alter - width ++ jsr DivideBy2 : sta $00
++ tyx : jsr DivideBy2 : sta $00 : tya : lsr : tay : lda $00 : bra .loop tya : lsr : tay
txa : lsr : tax
lda $00 : bra .loop
.done rts .done rts
MultiBy3: MultiBy3:
@@ -34,12 +38,13 @@ MultiBy5:
sta $00 : asl #2 : !add $00 sta $00 : asl #2 : !add $00
rts rts
;width of divison in x ;width of divison in x: rounds toward X/2
DivideBy2: DivideBy2:
sta $00 sta $00
lsr : bcc .done lsr : bcc .done
sta $02 : txa : lsr : cmp $00 : !bge .done sta $02 : txa : lsr : cmp $00 : !blt +
lda $02 : inc lda $02 : inc : bra .done
+ lda $02
.done rts .done rts
DivideBy3: DivideBy3:
+151 -143
View File
@@ -1,5 +1,5 @@
WarpLeft: WarpLeft:
lda DRMode : beq .end lda.l DRMode : beq .end
lda $040c : cmp.b #$ff : beq .end lda $040c : cmp.b #$ff : beq .end
lda $20 : ldx $aa lda $20 : ldx $aa
jsr CalcIndex jsr CalcIndex
@@ -10,7 +10,7 @@ WarpLeft:
rtl rtl
WarpRight: WarpRight:
lda DRMode : beq .end lda.l DRMode : beq .end
lda $040c : cmp.b #$ff : beq .end lda $040c : cmp.b #$ff : beq .end
lda $20 : ldx $aa lda $20 : ldx $aa
jsr CalcIndex jsr CalcIndex
@@ -21,7 +21,7 @@ WarpRight:
rtl rtl
WarpUp: WarpUp:
lda DRMode : beq .end lda.l DRMode : beq .end
lda $040c : cmp.b #$ff : beq .end lda $040c : cmp.b #$ff : beq .end
lda $22 : ldx $a9 lda $22 : ldx $a9
jsr CalcIndex jsr CalcIndex
@@ -32,7 +32,7 @@ WarpUp:
rtl rtl
WarpDown: WarpDown:
lda DRMode : beq .end lda.l DRMode : beq .end
lda $040c : cmp.b #$ff : beq .end lda $040c : cmp.b #$ff : beq .end
lda $22 : ldx $a9 lda $22 : ldx $a9
jsr CalcIndex jsr CalcIndex
@@ -45,13 +45,13 @@ WarpDown:
; carry set = use link door like normal ; carry set = use link door like normal
; carry clear = we are in dr mode, never use linking doors ; carry clear = we are in dr mode, never use linking doors
CheckLinkDoorR: CheckLinkDoorR:
lda DRMode : bne + lda.l DRMode : bne +
lda $7ec004 : sta $a0 ; what we wrote over lda $7ec004 : sta $a0 ; what we wrote over
sec : rtl sec : rtl
+ clc : rtl + clc : rtl
CheckLinkDoorL: CheckLinkDoorL:
lda DRMode : bne + lda.l DRMode : bne +
lda $7ec003 : sta $a0 ; what we wrote over lda $7ec003 : sta $a0 ; what we wrote over
sec : rtl sec : rtl
+ clc : rtl + clc : rtl
@@ -61,10 +61,11 @@ TrapDoorFixer:
xba : asl #2 : sta $00 xba : asl #2 : sta $00
stz $0468 : lda $068c : ora $00 : sta $068c stz $0468 : lda $068c : ora $00 : sta $068c
.end .end
stz $fe ; clear our ab here because we don't need it anymore stz $fe ; clear our fe here because we don't need it anymore
rts rts
Cleanup: Cleanup:
stz $047a
inc $11 inc $11
lda $ef lda $ef
rts rts
@@ -88,29 +89,32 @@ LoadRoomHorz:
{ {
phb : phk : plb phb : phk : plb
sty $06 : sta $07 : lda $a0 : pha ; Store normal room on stack sty $06 : sta $07 : lda $a0 : pha ; Store normal room on stack
lda $07 : jsr LookupNewRoom ; New room is in A, Room Data is in $00 lda $07 : jsr LookupNewRoom ; New room is in A, Room Data is in $00-$01
lda $01 : and.b #$80 : cmp #$80 : bne .gtg lda $00 : cmp #$03 : bne .gtg
; jsr HorzEdge : pla : bcs .end jsr HorzEdge : pla : bcs .end
pla
sta $a0 : bra .end ; Restore normal room, abort (straight staircases and open edges can get in this routine) sta $a0 : bra .end ; Restore normal room, abort (straight staircases and open edges can get in this routine)
.gtg ;Good to Go! .gtg ;Good to Go!
pla ; Throw away normal room (don't fill up the stack) pla ; Throw away normal room (don't fill up the stack)
lda $a0 : and.b #$0F : asl a : !sub $23 : !add $06 : sta $02 lda $a0 : and.b #$0F : asl a : !sub $23 : !add $06 : sta $02
ldy #$00 : jsr ShiftVariablesMainDir ldy #$00 : jsr ShiftVariablesMainDir
lda $aa : lsr : sta $07
lda $a0 : and.b #$F0 : lsr #3 : !add $07 : !sub $21 : sta $02 : sta $03 lda $01 : and #$80 : beq .normal
jsr ShiftLowCoord ldy $06 : cpy #$ff : beq +
jsr ShiftQuad lda $01 : jsr LoadEastMidpoint : bra ++
jsr ShiftCameraBounds + lda $01 : jsr LoadWestMidpoint
ldy #$01 : jsr ShiftVariablesSubDir ; flip direction ++ jsr PrepScrollToEdge : bra .scroll
jsr SetupScrollIndicator
lda $01 : sta $fe : and #$04 : lsr #2 .normal
sta $ee jsr PrepScrollToNormal
lda $01 : and #$10 : beq .end : stz $0468 .scroll
lda $01 : and #$40 : pha
jsr ScrollY
pla : beq .end
ldy #$06 : jsr ApplyScroll
.end .end
plb ; restore db register plb ; restore db register
rts rts
} }
; Y is an adjustment for main direction of travel (stored at $06) ; Y is an adjustment for main direction of travel (stored at $06)
@@ -119,43 +123,42 @@ LoadRoomVert:
{ {
phb : phk : plb phb : phk : plb
sty $06 : sta $07 : lda $a0 : pha ; Store normal room on stack sty $06 : sta $07 : lda $a0 : pha ; Store normal room on stack
lda $07 : jsr LookupNewRoom ; New room is in A, Room Data is in $00 lda $07 : jsr LookupNewRoom ; New room is in A, Room Data is in $00-$01
lda $01 : and.b #$80 : cmp #$80 : bne .gtg lda $00 : cmp #$03 : bne .gtg
; jsr VertEdge : pla : bcs .end jsr VertEdge : pla : bcs .end
pla
sta $a0 : bra .end ; Restore normal room, abort (straight staircases and open edges can get in this routine) sta $a0 : bra .end ; Restore normal room, abort (straight staircases and open edges can get in this routine)
.gtg ;Good to Go! .gtg ;Good to Go!
pla ; Throw away normal room (don't fill up the stack) pla ; Throw away normal room (don't fill up the stack)
lda $a0 : and.b #$F0 : lsr #3 : !sub $21 : !add $06 : sta $02 lda $a0 : and.b #$F0 : lsr #3 : !sub $21 : !add $06 : sta $02
ldy #$01 : jsr ShiftVariablesMainDir ldy #$01 : jsr ShiftVariablesMainDir
lda $a0 : and.b #$0F : asl a : !add $a9 : !sub $23 : sta $02 : sta $03
jsr ShiftLowCoord
jsr ShiftQuad
jsr ShiftCameraBounds
ldy #$00 : jsr ShiftVariablesSubDir ; flip direction
jsr SetupScrollIndicator
lda $01 : sta $fe : and #$04 : lsr #2
sta $ee
.end
plb ; restore db register
rts
}
SetupScrollIndicator: lda $01 : and #$80 : beq .normal
lda $ab : and #$01 : asl : sta $ac ldy $06 : cpy #$ff : beq +
lda $ab : and #$40 : clc : rol #3 : ora $ac : sta $ac lda $01 : jsr LoadSouthMidpoint : bra ++
lda $ab : and #$20 : asl #2 : sta $ab + lda $01 : jsr LoadNorthMidpoint
++ jsr PrepScrollToEdge : bra .scroll
.normal
jsr PrepScrollToNormal
.scroll
lda $01 : and #$40 : pha
jsr ScrollX
pla : beq .end
ldy #$00 : jsr ApplyScroll
.end
plb ; restore db register
rts rts
}
LookupNewRoom: ; expects data offset to be in A LookupNewRoom: ; expects data offset to be in A
{ {
rep #$30 : and #$00FF ;sanitize A reg (who knows what is in the high byte) rep #$30 : and #$00FF ;sanitize A reg (who knows what is in the high byte)
sta $00 ; offset in 00 sta $00 ; offset in 00
lda $a2 : tax ; probably okay loading $a3 in the high byte lda $a2 : tax ; probably okay loading $a3 in the high byte
lda DoorOffset,x : and #$00FF ;we only want the low byte lda.w DoorOffset,x : and #$00FF ;we only want the low byte
asl #3 : sta $02 : !add $02 : !add $02 ;multiply by 24 (data size) asl #3 : sta $02 : !add $02 : !add $02 ;multiply by 24 (data size)
!add $00 ; should now have the offset of the address I want to load !add $00 ; should now have the offset of the address I want to load
tax : lda DoorTable,x : sta $00 tax : lda.w DoorTable,x : sta $00
and #$00FF : sta $a0 ; assign new room and #$00FF : sta $a0 ; assign new room
sep #$30 sep #$30
rts rts
@@ -165,11 +168,11 @@ LookupNewRoom: ; expects data offset to be in A
; Sets high bytes of various registers ; Sets high bytes of various registers
ShiftVariablesMainDir: ShiftVariablesMainDir:
{ {
lda CoordIndex,y : tax lda.w CoordIndex,y : tax
lda $21,x : !add $02 : sta $21,x ; coordinate update lda $21,x : !add $02 : sta $21,x ; coordinate update
lda CameraIndex,y : tax lda.w CameraIndex,y : tax
lda $e3,x : !add $02 : sta $e3,x ; scroll register high byte lda $e3,x : !add $02 : sta $e3,x ; scroll register high byte
lda CamQuadIndex,y : tax lda.w CamQuadIndex,y : tax
lda $0605,x : !add $02 : sta $0605,x ; high bytes of these guys lda $0605,x : !add $02 : sta $0605,x ; high bytes of these guys
lda $0607,x : !add $02 : sta $0607,x lda $0607,x : !add $02 : sta $0607,x
lda $0601,x : !add $02 : sta $0601,x lda $0601,x : !add $02 : sta $0601,x
@@ -177,117 +180,122 @@ ShiftVariablesMainDir:
rts rts
} }
ShiftLowCoord:
; Target pixel should be in A, other info in $01
; Sets $04 $05 and $ee
PrepScrollToEdge:
{ {
lda $01 : and.b #$03 ; high byte index sta $04 : lda $01 : and #$20 : beq +
jsr CalcOpposingShift lda #01
lda $ab : and.b #$f0 : cmp.b #$20 : bne .lowDone + sta $05
lda OppCoordIndex,y : tax lda $01 : and #$10 : beq +
lda #$80 : !add $20,x : sta $20,x lda #01
.lowDone + sta $ee
rts rts
} }
; expects A to be (0,1,2) (dest number) and (0,1,2) (src door number) to be stored in $04 ; Normal Flags should be in $01
; $ab will be set to a bitmask aaaa qxxf ; Sets $04 $05 and $ee, and $fe
; a - amount of adjust PrepScrollToNormal:
; f - flag, if set, then amount is pos, otherwise neg.
; q - quadrant, if set, then quadrant needs to be modified
CalcOpposingShift:
{ {
stz $ab : stz $ac ; set up lda $01 : sta $fe : and #$04 : lsr #2 : sta $ee ; trap door and layer
cmp.b $04 : beq .noOffset ; (equal, no shifts to do) stz $05 : lda #$78 : sta $04
phy : tay ; reserve these lda $01 : and #$03 : beq .end
lda $04 : tax : tya : !sub $04 : sta $04 : cmp.b #$00 : bpl .shiftPos cmp #$02 : !bge +
lda #$40 lda #$f8 : sta $04 : bra .end
cpx.b #$01 : beq .skipNegQuad + inc $05
ora #$08 .end rts
.skipNegQuad
sta $ab : lda $04 : cmp.b #$FE : beq .done ;already set $ab
lda $ab : eor #$60
bra .setDone
.shiftPos
lda #$41
cpy.b #$01 : beq .skipPosQuad
ora #$08
.skipPosQuad
sta $ab : lda $04 : cmp.b #$02 : bcs .done ;already set $ab
lda $ab : eor #$60
.setDone sta $ab
.done ply
.noOffset rts
} }
StraightStairsAdj:
ShiftQuad:
{ {
lda $ab : and #$08 : beq .quadDone stx $0464 : sty $012e ; what we wrote over
lda ShiftQuadIndex,y : tax ; X should be set to either 1 (vertical) or 2 (horizontal) (for a9,aa quadrant) lda.l DRMode : beq +
lda $ab : and #$01 : beq .decQuad jsr GetTileAttribute : tax
inc $02 lda $11 : cmp #$12 : beq .goingNorth
txa : sta $a8, x ; alter a9/aa lda $a2 : cmp #$51 : bne ++
bra .quadDone rep #$20 : lda #$0018 : !add $20 : sta $20 : sep #$20 ; special fix for throne room
.decQuad jsr GetTileAttribute : tax
dec $02 ++ lda.l StepAdjustmentDown, X : bra .end
lda #$00 : sta $a8, x ; alter a9/aa ; lda $ee : beq .end
.quadDone rts ; rep #$20 : lda #$ffe0 : !add $20 : sta $20 : sep #$20
.goingNorth
cpx #$00 : bne ++
lda $a0 : cmp #$51 : bne ++
lda #$36 : bra .end ; special fix for throne room
++ ldy $ee : cpy #$00 : beq ++
inx
++ lda.l StepAdjustmentUp, X
.end
pha : lda $0462 : and #$04 : bne ++
pla : !add #$f6 : pha
++ pla : !add $0464 : sta $0464
+ rtl
} }
ShiftVariablesSubDir: GetTileAttribute:
{ {
lda CoordIndex,y : tax phk : pea.w .jslrtsreturn-1
lda $21,x : !add $02 : sta $21,x ; coordinate update pea.w $02802c
lda CameraIndex,y : tax jml $02c11d ; mucks with x/y sets a to Tile Attribute, I think
lda $e3,x : !add $03 : sta $e3,x ; scroll register high byte .jslrtsreturn
lda CamQuadIndex,y : tax rts
lda $0601,x : !add $02 : sta $0601,x
lda $0605,x : !add $02 : sta $0605,x ; high bytes of these guys
lda $0603,x : !add $03 : sta $0603,x
lda $0607,x : !add $03 : sta $0607,x
rts
} }
ShiftCameraBounds: ; 0 open edge
; 1 nrm door high
; 2 straight str
; 3 nrm door low
; 4 trap door high
; 5 trap door low (none of these exist on North direction)
StepAdjustmentUp: ; really North Stairs
db $00, $f6, $1a, $18, $16, $38
StepAdjustmentDown: ; really South Stairs
db $d0, $f6, $10, $1a, $f0, $00
StraightStairsFix:
{ {
lda CamBoundIndex,y : tax ; should be 0 for horz travel (vert bounds) or 4 for vert travel (horz bounds) lda.l DRMode : bne +
rep #$30 !add $20 : sta $20 ;what we wrote over
lda $ab : and #$00f0 : asl #2 : sta $06 + rtl
lda $ab : and #$0001 : cmp #$0000 : beq .subIt
lda $0618, x : !add $06 : sta $0618, x
lda $061A, x : !add $06 : sta $061A, x
sep #$30
rts
.subIt
lda $0618, x : !sub $06 : sta $0618, x
lda $061A, x : !sub $06 : sta $061A, x
sep #$30
rts
} }
AdjustTransition: StraightStairLayerFix:
{ {
lda $ab : and #$01ff : beq .reset lda.l DRMode : beq +
phy : ldy #$06 ; operating on vertical registers during horizontal trans lda $ee : rtl
cpx.b #$02 : bcs .horizontalScrolling + lda $01c322, x : rtl ; what we wrote over
ldy #$00 ; operate on horizontal regs during vert trans
.horizontalScrolling
cmp #$0008 : bcs +
pha : lda $ab : and #$0200 : beq ++
pla : bra .add
++ pla : eor #$ffff : inc ; convert to negative
.add jsr AdjustCamAdd : ply : bra .reset
+ lda $ab : and #$0200 : xba : tax
lda.l OffsetTable,x : jsr AdjustCamAdd
lda $ab : !sub #$0008 : sta $ab
ply : bra .done
.reset ; clear the $ab variable so to not disturb intra-tile doors
stz $ab
.done
lda $00 : and #$01fc
rtl
} }
AdjustCamAdd: DoorToStraight:
!add $00E2,y : sta $00E2,y : sta $00E0,y : rts {
pha
lda.l DRMode : beq .skip
pla : bne .end
pha
lda $a0 : cmp #$51 : bne .skip
lda #$04 : sta $4e
.skip pla
.end ldx $0418 : cmp #$02 ;what we wrote over
rtl
}
StraightStairsTrapDoor:
{
lda $0464 : bne +
; reset function
phk : pea.w .jslrtsreturn-1
pea.w $02802c
jml $028c73 ; $10D71 .reset label of Bank02
.jslrtsreturn
lda $0468 : bne ++
lda $a0 : cmp.b #$ac : bne .animateTraps
lda $0403 : and.b #$20 : bne .animateTraps
lda $0403 : and.b #$10 : beq ++
.animateTraps
lda #$05 : sta $11
inc $0468 : stz $068e : stz $0690
++ rtl
+ jsl Dungeon_ApproachFixedColor ; what we wrote over
.end rtl
}
+12 -6
View File
@@ -26,7 +26,7 @@ GtBossHeartCheckOverride:
lda $a0 : cmp #$1c : beq ++ lda $a0 : cmp #$1c : beq ++
cmp #$6c : beq ++ cmp #$6c : beq ++
cmp #$4d : bne + cmp #$4d : bne +
++ lda DRFlags : and #$01 : bne ++ ;skip if flag on ++ lda.l DRFlags : and #$01 : bne ++ ;skip if flag on
lda $403 : ora #$80 : sta $403 lda $403 : ora #$80 : sta $403
++ clc ++ clc
rtl rtl
@@ -35,19 +35,19 @@ rtl
OnFileLoadOverride: OnFileLoadOverride:
jsl OnFileLoad ; what I wrote over jsl OnFileLoad ; what I wrote over
lda DRFlags : and #$80 : beq + ;flag is off lda.l DRFlags : and #$80 : beq + ;flag is off
lda $7ef086 : ora #$80 : sta $7ef086 lda $7ef086 : ora #$80 : sta $7ef086
+ lda DRFlags : and #$02 : beq + + lda.l DRFlags : and #$02 : beq +
lda $7ef353 : bne + lda $7ef353 : bne +
lda #$01 : sta $7ef353 lda #$01 : sta $7ef353
+ rtl + rtl
MirrorCheckOverride: MirrorCheckOverride:
lda DRFlags : and #$02 : beq ++ lda.l DRFlags : and #$02 : beq ++
lda $7ef353 : cmp #$01 : beq + lda $7ef353 : cmp #$01 : beq +
++ lda $8A : and #$40 ; what I wrote over ++ lda $8A : and #$40 ; what I wrote over
rtl rtl
+ lda DRScroll : rtl + lda.l DRScroll : rtl
MirrorCheckOverride2: MirrorCheckOverride2:
lda $7ef353 : and #$02 : rtl lda $7ef353 : and #$02 : rtl
@@ -64,7 +64,7 @@ FixShopCode:
+ rtl + rtl
VitreousKeyReset: VitreousKeyReset:
lda DRMode : beq + lda.l DRMode : beq +
stz $0cba, x stz $0cba, x
+ jsl $0db818 ;restore old code + jsl $0db818 ;restore old code
rtl rtl
@@ -74,3 +74,9 @@ GuruguruFix:
and #$0f : cmp #$0e : !blt + and #$0f : cmp #$0e : !blt +
iny #2 iny #2
+ rtl + rtl
BlindAtticFix:
lda.l DRMode : beq +
lda #$01 : rtl
+ lda $7EF3CC : cmp.b #$06
rtl
+206
View File
@@ -0,0 +1,206 @@
AdjustTransition:
{
lda $ab : and #$01ff : beq .reset
phy : ldy #$06 ; operating on vertical registers during horizontal trans
cpx.b #$02 : bcs .horizontalScrolling
ldy #$00 ; operate on horizontal regs during vert trans
.horizontalScrolling
cmp #$0008 : bcs +
pha : lda $ab : and #$0200 : beq ++
pla : bra .add
++ pla : eor #$ffff : inc ; convert to negative
.add jsr AdjustCamAdd : ply : bra .reset
+ lda $ab : and #$0200 : xba : tax
lda.l OffsetTable,x : jsr AdjustCamAdd
lda $ab : !sub #$0008 : sta $ab
ply : bra .done
.reset ; clear the $ab variable so to not disturb intra-tile doors
stz $ab
.done
lda $00 : and #$01fc
rtl
}
AdjustCamAdd:
!add $00E2,y : pha
and #$01ff : cmp #$0111 : !blt +
cmp #$01f8 : !bge ++
pla : and #$ff10 : pha : bra +
++ pla : and #$ff00 : !add #$0100 : pha
+ pla : sta $00E2,y : sta $00E0,y : rts
; expects target quad in $05 (either 0 or 1) and target pixel in $04, target room should be in $a0
; $06 is either $ff or $01/02
; uses $00-$03 and $0e for calculation
; also set up $ac
ScrollY: ;change the Y offset variables
lda $a0 : and.b #$f0 : lsr #3 : sta $0603 : inc : sta $0607
lda $05 : bne +
lda $603 : sta $00 : stz $01 : bra ++
+ lda $607 : sta $00 : lda #$02 : sta $01
++ ; $01 now contains 0 or 2 and $00 contains the correct lat
stz $0e
rep #$30
lda $00 : pha
lda $e8 : and #$01ff : sta $02
lda $04 : jsr LimitYCamera : sta $00
jsr CheckRoomLayoutY : bcc +
lda $00 : cmp #$0080 : !bge ++
cmp #$0010 : !blt .cmpSrll
lda #$0010 : bra .cmpSrll
++ cmp #$0100 : !bge .cmpSrll
lda #$0100
.cmpSrll sta $00
; figures out scroll amt
+ lda $00 : cmp $02 : bne +
lda #$0000 : bra .next
+ !blt +
!sub $02 : inc $0e : bra .next
+ lda $02 : !sub $00
.next
sta $ab
jsr AdjustCameraBoundsY
pla : sta $00
sep #$30
lda $04 : sta $20
lda $00 : sta $21 : sta $0601 : sta $0605
lda $01 : sta $aa
lda $0e : asl : ora $ac : sta $ac
lda $e9 : and #$01 : asl #2 : tax : lda $0603, x : sta $e9
rts
LimitYCamera:
cmp #$006c : !bge +
lda #$0000 : bra .end
+ cmp #$017d : !blt +
lda #$0110 : bra .end
+ !sub #$006c
.end rts
CheckRoomLayoutY:
jsr LoadRoomLayout ;switches to 8-bit
cmp #$00 : beq .lock
cmp #$07 : beq .free
cmp #$01 : beq .free
cmp #$04 : !bge .lock
cmp #$02 : bne +
lda $06 : cmp #$ff : beq .lock
+ cmp #$03 : bne .free
lda $06 : cmp #$ff : bne .lock
.free rep #$30 : clc : rts
.lock rep #$30 : sec : rts
AdjustCameraBoundsY:
jsr CheckRoomLayoutY : bcc .free
; layouts that are camera locked (quads only)
lda $04 : and #$00ff : cmp #$007d : !blt +
lda #$0088 : bra ++
+ cmp #$006d : !bge +
lda #$0078 : bra ++
+ !add #$000b
; I think we no longer need the $02 variable
++ sta $02 : lda $04 : and #$0100 : !add $02 : bra .setBounds
; layouts where the camera is free
.free lda $04 : cmp #$006c : !bge +
lda #$0077 : bra .setBounds
+ cmp #$017c : !blt +
lda #$0187 : bra .setBounds
+ !add #$000b
.setBounds sta $0618 : inc #2 : sta $061a
rts
LoadRoomLayout:
lda $a0 : asl : !add $a0 : tax
lda $1f8001, x : sta $b8
lda $1f8000, x : sta $b7
sep #$30
ldy #$01 : lda [$b7], y : and #$1c : lsr #2
rts
; expects target quad in $05 (either 0 or 1) and target pixel in $04, target room should be in $a0
; uses $00-$03 and $0e for calculation
; also set up $ac
ScrollX: ;change the X offset variables
lda $a0 : and.b #$0f : asl : sta $060b : inc : sta $060f
lda $05 : bne +
lda $60b : sta $00 : stz $01 : bra ++
+ lda $60f : sta $00 : lda #$01 : sta $01
++ ; $01 now contains 0 or 1 and $00 contains the correct long
stz $0e ; pos/neg indicator
rep #$30
lda $00 : pha
lda $e2 : and #$01ff : sta $02
lda $04 : jsr LimitXCamera : sta $00
jsr CheckRoomLayoutX : bcc +
lda $00 : cmp #$0080 : !bge ++
lda #$0000 : bra .cmpSrll
++ lda #$0100
.cmpSrll sta $00
;figures out scroll amt
+ lda $00 : cmp $02 : bne +
lda #$0000 : bra .next
+ !blt +
!sub $02 : inc $0e : bra .next
+ lda $02 : !sub $00
.next
sta $ab : lda $04
cmp #$0078 : !bge +
lda #$007f : bra ++
+ cmp #$0178 : !blt +
lda #$017f : bra ++
+ !add #$0007
++ sta $061c : inc #2 : sta $061e
pla : sta $00
sep #$30
lda $04 : sta $22
lda $00 : sta $23 : sta $0609 : sta $060d
lda $01 : sta $a9
lda $0e : asl : ora $ac : sta $ac
lda $e3 : and #$01 : asl #2 : tax : lda $060b, x : sta $e3
rts
LimitXCamera:
cmp #$0080 : !bge +
lda #$0000 : bra .end
+ cmp #$0181 : !blt +
lda #$0180
+ !sub #$0080
.end rts
CheckRoomLayoutX:
jsr LoadRoomLayout ;switches to 8-bit
cmp #$04 : !blt .lock
cmp #$05 : bne +
lda $06 : cmp #$ff : beq .lock
+ cmp #$06 : bne .free
lda $06 : cmp #$ff : bne .lock
.free rep #$30 : clc : rts
.lock rep #$30 : sec : rts
ApplyScroll:
rep #$30
lda $ab : and #$01ff : sta $00
lda $ab : and #$0200 : beq +
lda $00e2, y : !add $00 : bra .end
+ lda $00e2, y : !sub $00
.end
sta $00e2, y
sta $00e0, y
stz $ab : sep #$30 : rts
+43 -20
View File
@@ -1,11 +1,17 @@
RecordStairType: { RecordStairType: {
sta $a0 pha
lda $0e : sta $045e lda.l DRMode : beq .norm
lda $063d, x lda $040c : cmp #$ff : beq .norm
lda $0e : sta $045e
cmp #$26 : beq .norm ; skipping in-floor staircases
pla : bra +
.norm pla : sta $a0
+ lda $063d, x
rtl rtl
} }
SpiralWarp: { SpiralWarp: {
lda.l DRMode : beq .abort ; abort if not DR
lda $040c : cmp.b #$ff : beq .abort ; abort if not in dungeon lda $040c : cmp.b #$ff : beq .abort ; abort if not in dungeon
lda $045e : cmp #$5e : beq .gtg ; abort if not spiral - intended room is in A! lda $045e : cmp #$5e : beq .gtg ; abort if not spiral - intended room is in A!
cmp #$5f : beq .gtg cmp #$5f : beq .gtg
@@ -16,8 +22,8 @@ SpiralWarp: {
phb : phk : plb : phx : phy ; push stuff phb : phk : plb : phx : phy ; push stuff
jsr LookupSpiralOffset jsr LookupSpiralOffset
rep #$30 : and #$00FF : asl #2 : tax rep #$30 : and #$00FF : asl #2 : tax
lda SpiralTable, x : sta $00 lda.w SpiralTable, x : sta $00
lda SpiralTable+2, x : sta $02 lda.w SpiralTable+2, x : sta $02
sep #$30 sep #$30
lda $00 : sta $a0 lda $00 : sta $a0
; shift quadrant if necessary ; shift quadrant if necessary
@@ -65,6 +71,16 @@ SpiralWarp: {
ldy #$01 : jsr SetCamera ldy #$01 : jsr SetCamera
stz $045e ; clear the staircase flag stz $045e ; clear the staircase flag
; animated tiles fix
lda.l DRMode : cmp #$02 : bne + ; only do this in crossed mode
ldx $a0 : lda.l TilesetTable, x
cmp $0aa1 : beq + ; already eq no need to decomp
sta $0aa1
tax : lda $02802e, x : tay
jsl DecompDungAnimatedTiles
+
stz $047a
ply : plx : plb ; pull the stuff we pushed ply : plx : plb ; pull the stuff we pushed
lda $a2 : and #$0f ; this is the code we are hijacking lda $a2 : and #$0f ; this is the code we are hijacking
rtl rtl
@@ -117,17 +133,17 @@ LookupSpiralOffset: {
lda #$02 : sta $01 ; always 2 lda #$02 : sta $01 ; always 2
.done .done
lda $a2 : tax : lda SpiralOffset,x lda $a2 : tax : lda.w SpiralOffset,x
!add $01 ;add a thing (0 in easy case) !add $01 ;add a thing (0 in easy case)
rts rts
} }
ShiftQuadSimple: { ShiftQuadSimple: {
lda CoordIndex,y : tax lda.w CoordIndex,y : tax
lda $20,x : beq .skip lda $20,x : beq .skip
lda $21,x : !add $06 : sta $21,x ; coordinate update lda $21,x : !add $06 : sta $21,x ; coordinate update
.skip .skip
lda CamQuadIndex,y : tax lda.w CamQuadIndex,y : tax
lda $0601,x : !add $06 : sta $0601,x lda $0601,x : !add $06 : sta $0601,x
lda $0605,x : !add $06 : sta $0605,x ; high bytes of these guys lda $0605,x : !add $06 : sta $0605,x ; high bytes of these guys
rts rts
@@ -136,13 +152,13 @@ ShiftQuadSimple: {
SetCamera: { SetCamera: {
stz $04 stz $04
tyx : lda $a9,x : bne .nonZeroHalf tyx : lda $a9,x : bne .nonZeroHalf
lda CamQuadIndex,y : tax : lda $607,x : pha lda.w CamQuadIndex,y : tax : lda $607,x : pha
lda CameraIndex,y : tax : pla : cmp $e3, x : bne .noQuadAdj lda.w CameraIndex,y : tax : pla : cmp $e3, x : bne .noQuadAdj
dec $e3,x dec $e3,x
.noQuadAdj .noQuadAdj
lda $07 : bne .adj0 lda $07 : bne .adj0
lda CoordIndex,y : tax lda.w CoordIndex,y : tax
lda $20,x : beq .oddQuad lda $20,x : beq .oddQuad
cmp #$79 : bcc .adj0 cmp #$79 : bcc .adj0
!sub #$78 : sta $04 !sub #$78 : sta $04
@@ -154,21 +170,21 @@ SetCamera: {
.nonZeroHalf ;meaning either right half or bottom half .nonZeroHalf ;meaning either right half or bottom half
lda $07 : bne .setQuad lda $07 : bne .setQuad
lda CoordIndex,y : tax lda.w CoordIndex,y : tax
lda $20,x : cmp #$78 : bcs .setQuad lda $20,x : cmp #$78 : bcs .setQuad
!add #$78 : sta $04 !add #$78 : sta $04
lda CamQuadIndex,y : tax : lda $0603, x : pha lda.w CamQuadIndex,y : tax : lda $0603, x : pha
lda CameraIndex,y : tax : pla : sta $e3, x lda.w CameraIndex,y : tax : pla : sta $e3, x
.adj1 .adj1
tya : asl : !add #$08 : tax : jsr AdjCamBounds : bra .done tya : asl : !add #$08 : tax : jsr AdjCamBounds : bra .done
.setQuad .setQuad
lda CamQuadIndex,y : tax : lda $0607, x : pha lda.w CamQuadIndex,y : tax : lda $0607, x : pha
lda CameraIndex,y : tax : pla : sta $e3, x lda.w CameraIndex,y : tax : pla : sta $e3, x
tya : asl : !add #$0c : tax : jsr AdjCamBounds : bra .done tya : asl : !add #$0c : tax : jsr AdjCamBounds : bra .done
.done .done
lda CameraIndex,y : tax lda.w CameraIndex,y : tax
lda $04 : sta $e2, x lda $04 : sta $e2, x
rts rts
} }
@@ -176,13 +192,20 @@ SetCamera: {
; input, expects X to be an appropriate offset into the CamBoundBaseLine table ; input, expects X to be an appropriate offset into the CamBoundBaseLine table
; when $04 is 0 no coordinate are added ; when $04 is 0 no coordinate are added
AdjCamBounds: { AdjCamBounds: {
rep #$20 : lda CamBoundBaseLine, x : sta $05 rep #$20 : lda.w CamBoundBaseLine, x : sta $05
lda $04 : and #$00ff : beq .common lda $04 : and #$00ff : beq .common
lda CoordIndex,y : tax lda.w CoordIndex,y : tax
lda $20, x : and #$00ff : !add $05 : sta $05 lda $20, x : and #$00ff : !add $05 : sta $05
.common .common
lda OppCamBoundIndex,y : tax lda.w OppCamBoundIndex,y : tax
lda $05 : sta $0618, x lda $05 : sta $0618, x
inc #2 : sta $061A, x : sep #$20 inc #2 : sta $061A, x : sep #$20
rts rts
} }
SpiralPriorityHack: {
lda.l DRMode : beq +
lda #$01 : rtl ; always skip the priority code - until I figure out how to fix it
+ lda $0462 : and #$04 ; what we wrote over
rtl
}
File diff suppressed because one or more lines are too long
+12 -7
View File
@@ -110,6 +110,11 @@
"vanilla" "vanilla"
] ]
}, },
"intensity": {
"choices":[
"3", "2", "1", "random"
]
},
"experimental": { "experimental": {
"action": "store_true", "action": "store_true",
"type": "bool" "type": "bool"
@@ -124,12 +129,12 @@
}, },
"crystals_ganon": { "crystals_ganon": {
"choices": [ "choices": [
7, 6, 5, 4, 3, 2, 1, 0, "random" "7", "6", "5", "4", "3", "2", "1", "0", "random"
] ]
}, },
"crystals_gt": { "crystals_gt": {
"choices": [ "choices": [
7, 6, 5, 4, 3, 2, 1, 0, "random" "7", "6", "5", "4", "3", "2", "1", "0", "random"
] ]
}, },
"openpyramid": { "openpyramid": {
@@ -284,16 +289,16 @@
"shufflebosses": { "shufflebosses": {
"choices": [ "choices": [
"none", "none",
"basic", "simple",
"normal", "full",
"chaos" "random"
] ]
}, },
"shuffleenemies": { "shuffleenemies": {
"choices": [ "choices": [
"none", "none",
"shuffled", "shuffled",
"chaos" "random"
] ]
}, },
"enemy_health": { "enemy_health": {
@@ -309,7 +314,7 @@
"choices": [ "choices": [
"default", "default",
"shuffled", "shuffled",
"chaos" "random"
] ]
}, },
"shufflepots": { "shufflepots": {
+9 -2
View File
@@ -9,8 +9,8 @@
"shuffling.world": "Shuffling the World about", "shuffling.world": "Shuffling the World about",
"shuffling.dungeons": "Shuffling dungeons", "shuffling.dungeons": "Shuffling dungeons",
"basic.traversal": "--Basic Traversal", "basic.traversal": "--Basic Traversal",
"generating.dungeon": "Generating dungeon", "generating.dungeon": "Generating dungeons",
"shuffling.keydoors": "Shuffling Key doors for", "shuffling.keydoors": "Shuffling Key doors",
"lowering.keys.candidates": "Lowering key door count because not enough candidates", "lowering.keys.candidates": "Lowering key door count because not enough candidates",
"lowering.keys.layouts": "Lowering key door count because no valid layouts", "lowering.keys.layouts": "Lowering key door count because no valid layouts",
"keydoor.shuffle.time": "Key door shuffle time", "keydoor.shuffle.time": "Key door shuffle time",
@@ -198,6 +198,13 @@
"Vanilla: All doors are connected the same way they were in the", "Vanilla: All doors are connected the same way they were in the",
" base game." " base game."
], ],
"intensity" : [
"Door Shuffle Intensity Level (default: %(default)s)",
"1: Shuffles normal doors and spiral staircases",
"2: And shuffles open edges and straight staircases",
"3: (Coming soon) And shuffles dungeon lobbies",
"random: Picks one of those at random"
],
"experimental": [ "Enable experimental features. (default: %(default)s)" ], "experimental": [ "Enable experimental features. (default: %(default)s)" ],
"dungeon_counters": [ "Enable dungeon chest counters. (default: %(default)s)" ], "dungeon_counters": [ "Enable dungeon chest counters. (default: %(default)s)" ],
"crystals_ganon": [ "crystals_ganon": [
+16 -10
View File
@@ -58,6 +58,12 @@
"randomizer.dungeon.dungeondoorshuffle.basic": "Basic", "randomizer.dungeon.dungeondoorshuffle.basic": "Basic",
"randomizer.dungeon.dungeondoorshuffle.crossed": "Crossed", "randomizer.dungeon.dungeondoorshuffle.crossed": "Crossed",
"randomizer.dungeon.dungeonintensity": "Intensity Level",
"randomizer.dungeon.dungeonintensity.1": "1: Normal Supertile and Spiral Stairs",
"randomizer.dungeon.dungeonintensity.2": "2: Open Edges and Straight Stairs",
"randomizer.dungeon.dungeonintensity.3": "3: (Coming soon) Dungeon Lobbies",
"randomizer.dungeon.dungeonintensity.random": "Random",
"randomizer.dungeon.experimental": "Enable Experimental Features", "randomizer.dungeon.experimental": "Enable Experimental Features",
"randomizer.dungeon.dungeon_counters": "Dungeon Chest Counters", "randomizer.dungeon.dungeon_counters": "Dungeon Chest Counters",
@@ -70,25 +76,25 @@
"randomizer.enemizer.potshuffle": "Pot Shuffle", "randomizer.enemizer.potshuffle": "Pot Shuffle",
"randomizer.enemizer.enemyshuffle": "Enemy Shuffle", "randomizer.enemizer.enemyshuffle": "Enemy Shuffle",
"randomizer.enemizer.enemyshuffle.none": "Vanilla", "randomizer.enemizer.enemyshuffle.none": "None",
"randomizer.enemizer.enemyshuffle.shuffled": "Shuffled", "randomizer.enemizer.enemyshuffle.shuffled": "Shuffled",
"randomizer.enemizer.enemyshuffle.chaos": "Chaos", "randomizer.enemizer.enemyshuffle.random": "Random",
"randomizer.enemizer.bossshuffle": "Boss Shuffle", "randomizer.enemizer.bossshuffle": "Boss Shuffle",
"randomizer.enemizer.bossshuffle.none": "Vanilla", "randomizer.enemizer.bossshuffle.none": "None",
"randomizer.enemizer.bossshuffle.basic": "Basic", "randomizer.enemizer.bossshuffle.simple": "Simple",
"randomizer.enemizer.bossshuffle.shuffled": "Shuffled", "randomizer.enemizer.bossshuffle.full": "Full",
"randomizer.enemizer.bossshuffle.chaos": "Chaos", "randomizer.enemizer.bossshuffle.random": "Random",
"randomizer.enemizer.enemydamage": "Enemy Damage", "randomizer.enemizer.enemydamage": "Enemy Damage",
"randomizer.enemizer.enemydamage.default": "Vanilla", "randomizer.enemizer.enemydamage.default": "Default",
"randomizer.enemizer.enemydamage.shuffled": "Shuffled", "randomizer.enemizer.enemydamage.shuffled": "Shuffled",
"randomizer.enemizer.enemydamage.chaos": "Chaos", "randomizer.enemizer.enemydamage.random": "Random",
"randomizer.enemizer.enemyhealth": "Enemy Health", "randomizer.enemizer.enemyhealth": "Enemy Health",
"randomizer.enemizer.enemyhealth.default": "Vanilla", "randomizer.enemizer.enemyhealth.default": "Default",
"randomizer.enemizer.enemyhealth.easy": "Easy", "randomizer.enemizer.enemyhealth.easy": "Easy",
"randomizer.enemizer.enemyhealth.normal": "Normal", "randomizer.enemizer.enemyhealth.normal": "Medium",
"randomizer.enemizer.enemyhealth.hard": "Hard", "randomizer.enemizer.enemyhealth.hard": "Hard",
"randomizer.enemizer.enemyhealth.expert": "Expert", "randomizer.enemizer.enemyhealth.expert": "Expert",
@@ -9,6 +9,19 @@
"crossed" "crossed"
] ]
}, },
"dungeonintensity": {
"type": "selectbox",
"default": "2",
"options": [
"1",
"2",
"3",
"random"
],
"config": {
"width": 40
}
},
"experimental": { "type": "checkbox" }, "experimental": { "type": "checkbox" },
"dungeon_counters": { "dungeon_counters": {
"type": "selectbox", "type": "selectbox",
@@ -8,16 +8,16 @@
"options": [ "options": [
"none", "none",
"shuffled", "shuffled",
"chaos" "random"
] ]
}, },
"bossshuffle": { "bossshuffle": {
"type": "selectbox", "type": "selectbox",
"options": [ "options": [
"none", "none",
"basic", "simple",
"shuffled", "full",
"chaos" "random"
] ]
} }
}, },
@@ -27,7 +27,7 @@
"options": [ "options": [
"default", "default",
"shuffled", "shuffled",
"chaos" "random"
] ]
}, },
"enemyhealth": { "enemyhealth": {
@@ -1 +1,2 @@
aenum aenum
fast-enum
+1
View File
@@ -87,6 +87,7 @@ SETTINGSTOPROCESS = {
"smallkeyshuffle": "keyshuffle", "smallkeyshuffle": "keyshuffle",
"bigkeyshuffle": "bigkeyshuffle", "bigkeyshuffle": "bigkeyshuffle",
"dungeondoorshuffle": "door_shuffle", "dungeondoorshuffle": "door_shuffle",
"dungeonintensity": "intensity",
"experimental": "experimental", "experimental": "experimental",
"dungeon_counters": "dungeon_counters" "dungeon_counters": "dungeon_counters"
}, },
+2 -2
View File
@@ -78,8 +78,8 @@ def bottom_frame(self, parent, args=None):
argsDump = vars(guiargs) argsDump = vars(guiargs)
hasEnemizer = "enemizercli" in argsDump and os.path.isfile(argsDump["enemizercli"]) hasEnemizer = "enemizercli" in argsDump and os.path.isfile(argsDump["enemizercli"])
needEnemizer = False needEnemizer = False
if not hasEnemizer: if hasEnemizer:
falsey = [ "none", "default", "vanilla", False, 0 ] falsey = ["none", "default", False, 0]
for enemizerOption in [ "shufflepots", "shuffleenemies", "enemy_damage", "shufflebosses", "enemy_health" ]: for enemizerOption in [ "shufflepots", "shuffleenemies", "enemy_damage", "shufflebosses", "enemy_health" ]:
if enemizerOption in argsDump: if enemizerOption in argsDump:
if isinstance(argsDump[enemizerOption], dict): if isinstance(argsDump[enemizerOption], dict):
+7 -5
View File
@@ -33,7 +33,7 @@ def make_checkbox(self, parent, label, storageVar, manager, managerAttrs):
return self return self
# Make an OptionMenu with a label and pretty option labels # Make an OptionMenu with a label and pretty option labels
def make_selectbox(self, parent, label, options, storageVar, manager, managerAttrs): def make_selectbox(self, parent, label, options, storageVar, manager, managerAttrs, config=None):
self = Frame(parent) self = Frame(parent)
labels = options labels = options
@@ -96,7 +96,7 @@ def make_selectbox(self, parent, label, options, storageVar, manager, managerAtt
else: else:
self.label.pack(side=LEFT) self.label.pack(side=LEFT)
self.selectbox.config(width=20) self.selectbox.config(width=config['width'] if config and config['width'] else 20)
idx = 0 idx = 0
default = self.selectbox.options["values"][idx] default = self.selectbox.options["values"][idx]
if managerAttrs is not None and "default" in managerAttrs: if managerAttrs is not None and "default" in managerAttrs:
@@ -166,7 +166,8 @@ def make_textbox(self, parent, label, storageVar, manager, managerAttrs):
return widget return widget
# Make a generic widget # Make a generic widget
def make_widget(self, type, parent, label, storageVar=None, manager=None, managerAttrs=dict(), options=None): def make_widget(self, type, parent, label, storageVar=None, manager=None, managerAttrs=dict(),
options=None, config=None):
widget = None widget = None
if manager is None: if manager is None:
manager = "pack" manager = "pack"
@@ -184,7 +185,7 @@ def make_widget(self, type, parent, label, storageVar=None, manager=None, manage
elif type == "selectbox": elif type == "selectbox":
if thisStorageVar is None: if thisStorageVar is None:
thisStorageVar = StringVar() thisStorageVar = StringVar()
widget = make_selectbox(self, parent, label, options, thisStorageVar, manager, managerAttrs) widget = make_selectbox(self, parent, label, options, thisStorageVar, manager, managerAttrs, config)
elif type == "spinbox": elif type == "spinbox":
if thisStorageVar is None: if thisStorageVar is None:
thisStorageVar = StringVar() thisStorageVar = StringVar()
@@ -203,13 +204,14 @@ def make_widget_from_dict(self, defn, parent):
manager = defn["manager"] if "manager" in defn else None manager = defn["manager"] if "manager" in defn else None
managerAttrs = defn["managerAttrs"] if "managerAttrs" in defn else None managerAttrs = defn["managerAttrs"] if "managerAttrs" in defn else None
options = defn["options"] if "options" in defn else None options = defn["options"] if "options" in defn else None
config = defn["config"] if "config" in defn else None
if managerAttrs is None and "default" in defn: if managerAttrs is None and "default" in defn:
managerAttrs = {} managerAttrs = {}
if "default" in defn: if "default" in defn:
managerAttrs["default"] = defn["default"] managerAttrs["default"] = defn["default"]
widget = make_widget(self, type, parent, label, None, manager, managerAttrs, options) widget = make_widget(self, type, parent, label, None, manager, managerAttrs, options, config)
widget.type = type widget.type = type
return widget return widget