Merge branch 'OverworldShuffleDev' into OverworldShuffle
This commit is contained in:
@@ -62,6 +62,8 @@ class World(object):
|
|||||||
self.aga_randomness = True
|
self.aga_randomness = True
|
||||||
self.lock_aga_door_in_escape = False
|
self.lock_aga_door_in_escape = False
|
||||||
self.save_and_quit_from_boss = True
|
self.save_and_quit_from_boss = True
|
||||||
|
self.override_bomb_check = False
|
||||||
|
self.is_copied_world = False
|
||||||
self.accessibility = accessibility.copy()
|
self.accessibility = accessibility.copy()
|
||||||
self.fix_skullwoods_exit = {}
|
self.fix_skullwoods_exit = {}
|
||||||
self.fix_palaceofdarkness_exit = {}
|
self.fix_palaceofdarkness_exit = {}
|
||||||
@@ -1104,7 +1106,7 @@ class CollectionState(object):
|
|||||||
region = self.world.get_region(regionname, player)
|
region = self.world.get_region(regionname, player)
|
||||||
return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has('Pearl', player))
|
return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has('Pearl', player))
|
||||||
|
|
||||||
for region in rupee_farms:
|
for region in rupee_farms if self.world.pottery[player] in ['none', 'keys', 'dungeon'] else ['Archery Game']:
|
||||||
if can_reach_non_bunny(region):
|
if can_reach_non_bunny(region):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -1186,7 +1188,7 @@ class CollectionState(object):
|
|||||||
return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has('Pearl', player))
|
return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has('Pearl', player))
|
||||||
|
|
||||||
# bomb pickups
|
# bomb pickups
|
||||||
for region in bush_bombs + bomb_caves:
|
for region in bush_bombs + (bomb_caves if self.world.pottery[player] in ['none', 'keys', 'dungeon'] else []):
|
||||||
if can_reach_non_bunny(region):
|
if can_reach_non_bunny(region):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -1306,7 +1308,7 @@ class CollectionState(object):
|
|||||||
|
|
||||||
# In the future, this can be used to check if the player starts without bombs
|
# In the future, this can be used to check if the player starts without bombs
|
||||||
def can_use_bombs(self, player):
|
def can_use_bombs(self, player):
|
||||||
return (not self.world.bombbag[player] or self.has('Bomb Upgrade (+10)', player) or self.has('Bomb Upgrade (+5)', player, 2)) and ((hasattr(self.world,"override_bomb_check") and self.world.override_bomb_check) or self.can_farm_bombs(player))
|
return (not self.world.bombbag[player] or self.has('Bomb Upgrade (+10)', player) or self.has('Bomb Upgrade (+5)', player, 2)) and (self.world.override_bomb_check or self.can_farm_bombs(player))
|
||||||
|
|
||||||
def can_hit_crystal(self, player):
|
def can_hit_crystal(self, player):
|
||||||
return (self.can_use_bombs(player)
|
return (self.can_use_bombs(player)
|
||||||
@@ -1335,16 +1337,16 @@ class CollectionState(object):
|
|||||||
return self.has('Bow', player) and (self.can_buy_unlimited('Single Arrow', player) or self.has('Single Arrow', player))
|
return self.has('Bow', player) and (self.can_buy_unlimited('Single Arrow', player) or self.has('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):
|
||||||
cave = self.world.get_region('Good Bee Cave', player)
|
# cave = self.world.get_region('Good Bee Cave', player)
|
||||||
return (
|
# return (
|
||||||
self.can_use_bombs(player) and
|
# self.can_use_bombs(player) and
|
||||||
self.has_bottle(player) and
|
# self.has_bottle(player) and
|
||||||
self.has('Bug Catching Net', player) and
|
# self.has('Bug Catching Net', player) and
|
||||||
(self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player))) and
|
# (self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player))) and
|
||||||
cave.can_reach(self) and
|
# cave.can_reach(self) and
|
||||||
self.is_not_bunny(cave, player)
|
# self.is_not_bunny(cave, player)
|
||||||
)
|
# )
|
||||||
|
|
||||||
def has_beaten_aga(self, player):
|
def has_beaten_aga(self, player):
|
||||||
return self.has('Beat Agahnim 1', player) and (self.world.mode[player] != 'standard' or self.has('Zelda Delivered', player))
|
return self.has('Beat Agahnim 1', player) and (self.world.mode[player] != 'standard' or self.has('Zelda Delivered', player))
|
||||||
|
|||||||
@@ -61,8 +61,7 @@ def MothulaDefeatRule(state, player):
|
|||||||
# TODO: Not sure how much (if any) extend magic is needed for these two, since they only apply
|
# TODO: Not sure how much (if any) extend magic is needed for these two, since they only apply
|
||||||
# to non-vanilla locations, so are harder to test, so sticking with what VT has for now:
|
# to non-vanilla locations, so are harder to test, so sticking with what VT has for now:
|
||||||
(state.has('Cane of Somaria', player) and state.can_extend_magic(player, 16)) or
|
(state.has('Cane of Somaria', 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('Cane of Byrna', player) and state.can_extend_magic(player, 16))
|
||||||
state.can_get_good_bee(player)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def BlindDefeatRule(state, player):
|
def BlindDefeatRule(state, player):
|
||||||
@@ -202,13 +201,14 @@ def place_bosses(world, player):
|
|||||||
place_boss(boss, level, loc, loc_text, world, player)
|
place_boss(boss, level, loc, loc_text, world, player)
|
||||||
elif world.boss_shuffle[player] == 'unique':
|
elif world.boss_shuffle[player] == 'unique':
|
||||||
bosses = list(placeable_bosses)
|
bosses = list(placeable_bosses)
|
||||||
gt_bosses = list()
|
gt_bosses = []
|
||||||
|
|
||||||
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:
|
||||||
if level:
|
if level:
|
||||||
boss = random.choice([b for b in placeable_bosses if can_place_boss(world, player, b, loc, level) and b not in gt_bosses])
|
boss = random.choice([b for b in placeable_bosses if can_place_boss(world, player, b, loc, level)
|
||||||
|
and b not in gt_bosses])
|
||||||
gt_bosses.append(boss)
|
gt_bosses.append(boss)
|
||||||
else:
|
else:
|
||||||
boss = random.choice([b for b in bosses if can_place_boss(world, player, b, loc, level)])
|
boss = random.choice([b for b in bosses if can_place_boss(world, player, b, loc, level)])
|
||||||
|
|||||||
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,5 +1,19 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.2.10.0
|
||||||
|
- Merged DR v1.0.1.1-1.0.1.2
|
||||||
|
- Removed text color from hint tiles
|
||||||
|
- Removed Good Bee requirement from Mothula
|
||||||
|
- Some keylogic/generation fixes
|
||||||
|
- Fixed a Pottery logic issue in the playthru
|
||||||
|
- Fixed a generation error in Mixed OWR, resulting in more possible Mixed scenarios (thanks Catobat)
|
||||||
|
- Added more scenarios where OW Map Checks in Mixed OWR show dungeon prizes in their respective worlds
|
||||||
|
- Fixed rupee logic to consider Pottery option and lack of early rupees
|
||||||
|
- Changed Lean ER + Inverted Dark Chapel start is guaranteed to be in DW
|
||||||
|
- Fixed graphical issue with Hammerpeg Cave
|
||||||
|
- Fixed logic rule with HC Main Gate to not require mirror if screen is swapped
|
||||||
|
- Removed Crossed OWR option: "None (Allowed)"
|
||||||
|
|
||||||
### 0.2.9.1
|
### 0.2.9.1
|
||||||
- Lite/Lean ER now includes Cave Pot locations with various Pottery options
|
- Lite/Lean ER now includes Cave Pot locations with various Pottery options
|
||||||
- Changed Unique Boss Shuffle so that GT Bosses are unique amongst themselves
|
- Changed Unique Boss Shuffle so that GT Bosses are unique amongst themselves
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ def vanilla_key_logic(world, player):
|
|||||||
key_layout = build_key_layout(builder, start_regions, doors, world, player)
|
key_layout = build_key_layout(builder, start_regions, doors, 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('').info('Vanilla key layout not valid %s', builder.name)
|
||||||
builder.key_door_proposal = doors
|
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] = {}
|
||||||
@@ -381,7 +381,7 @@ def choose_portals(world, player):
|
|||||||
if world.doorShuffle[player] in ['basic', 'crossed']:
|
if world.doorShuffle[player] in ['basic', 'crossed']:
|
||||||
cross_flag = world.doorShuffle[player] == 'crossed'
|
cross_flag = world.doorShuffle[player] == 'crossed'
|
||||||
# key drops allow the big key in the right place in Desert Tiles 2
|
# key drops allow the big key in the right place in Desert Tiles 2
|
||||||
bk_shuffle = world.bigkeyshuffle[player] or world.dropshuffle[player]
|
bk_shuffle = world.bigkeyshuffle[player] or world.pottery[player] not in ['none', 'cave']
|
||||||
std_flag = world.mode[player] == 'standard'
|
std_flag = world.mode[player] == 'standard'
|
||||||
# roast incognito doors
|
# roast incognito doors
|
||||||
world.get_room(0x60, player).delete(5)
|
world.get_room(0x60, player).delete(5)
|
||||||
@@ -989,11 +989,16 @@ def cross_dungeon(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)
|
||||||
|
|
||||||
|
hc_compass = ItemFactory('Compass (Escape)', player)
|
||||||
|
at_compass = ItemFactory('Compass (Agahnims Tower)', player)
|
||||||
|
at_map = ItemFactory('Map (Agahnims Tower)', player)
|
||||||
|
if world.restrict_boss_items[player] != 'none':
|
||||||
|
hc_compass.advancement = at_compass.advancement = at_map.advancement = True
|
||||||
hc = world.get_dungeon('Hyrule Castle', player)
|
hc = world.get_dungeon('Hyrule Castle', player)
|
||||||
hc.dungeon_items.append(ItemFactory('Compass (Escape)', player))
|
hc.dungeon_items.append(hc_compass)
|
||||||
at = world.get_dungeon('Agahnims Tower', player)
|
at = world.get_dungeon('Agahnims Tower', player)
|
||||||
at.dungeon_items.append(ItemFactory('Compass (Agahnims Tower)', player))
|
at.dungeon_items.append(at_compass)
|
||||||
at.dungeon_items.append(ItemFactory('Map (Agahnims Tower)', player))
|
at.dungeon_items.append(at_map)
|
||||||
|
|
||||||
assign_cross_keys(dungeon_builders, world, player)
|
assign_cross_keys(dungeon_builders, world, player)
|
||||||
all_dungeon_items_cnt = len(list(y for x in world.dungeons if x.player == player for y in x.all_items))
|
all_dungeon_items_cnt = len(list(y for x in world.dungeons if x.player == player for y in x.all_items))
|
||||||
@@ -1896,16 +1901,18 @@ def find_inaccessible_regions(world, player):
|
|||||||
if any(x for x in ledge.exits if x.connected_region and x.connected_region.name == 'Agahnims Tower Portal'):
|
if any(x for x in ledge.exits if x.connected_region and x.connected_region.name == 'Agahnims Tower Portal'):
|
||||||
world.inaccessible_regions[player].append('Hyrule Castle Ledge')
|
world.inaccessible_regions[player].append('Hyrule Castle Ledge')
|
||||||
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]:
|
||||||
logger.debug('%s', r)
|
# logger.debug('%s', r)
|
||||||
|
|
||||||
|
|
||||||
def find_accessible_entrances(world, player, builder):
|
def find_accessible_entrances(world, player, builder):
|
||||||
entrances = [region.name for region in (portal.door.entrance.parent_region for portal in world.dungeon_portals[player]) if region.dungeon.name == builder.name]
|
entrances = [region.name for region in (portal.door.entrance.parent_region for portal in world.dungeon_portals[player]) if region.dungeon.name == builder.name]
|
||||||
entrances.extend(drop_entrances[builder.name])
|
entrances.extend(drop_entrances[builder.name])
|
||||||
|
hc_std = False
|
||||||
|
|
||||||
if world.mode[player] == 'standard' and builder.name == 'Hyrule Castle':
|
if world.mode[player] == 'standard' and builder.name == 'Hyrule Castle':
|
||||||
|
hc_std = True
|
||||||
start_regions = ['Hyrule Castle Courtyard']
|
start_regions = ['Hyrule Castle Courtyard']
|
||||||
else:
|
else:
|
||||||
start_regions = ['Links House' if not world.is_bombshop_start(player) else 'Big Bomb Shop', 'Sanctuary' if world.mode[player] != 'inverted' else 'Dark Sanctuary Hint']
|
start_regions = ['Links House' if not world.is_bombshop_start(player) else 'Big Bomb Shop', 'Sanctuary' if world.mode[player] != 'inverted' else 'Dark Sanctuary Hint']
|
||||||
@@ -1930,6 +1937,8 @@ def find_accessible_entrances(world, player, builder):
|
|||||||
if connect not in queue and connect not in visited_regions:
|
if connect not in queue and connect not in visited_regions:
|
||||||
queue.append(connect)
|
queue.append(connect)
|
||||||
for ext in next_region.exits:
|
for ext in next_region.exits:
|
||||||
|
if hc_std and ext.name == 'Hyrule Castle Main Gate (North)': # just skip it
|
||||||
|
continue
|
||||||
connect = ext.connected_region
|
connect = ext.connected_region
|
||||||
if connect is None or ext.door and ext.door.blocked:
|
if connect is None or ext.door and ext.door.blocked:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from collections import defaultdict, OrderedDict
|
|||||||
import RaceRandom as random
|
import RaceRandom as random
|
||||||
from BaseClasses import CollectionState, RegionType
|
from BaseClasses import CollectionState, RegionType
|
||||||
from OverworldShuffle import build_accessible_region_list
|
from OverworldShuffle import build_accessible_region_list
|
||||||
|
from DoorShuffle import find_inaccessible_regions
|
||||||
from OWEdges import OWTileRegions
|
from OWEdges import OWTileRegions
|
||||||
from Utils import stack_size3a
|
from Utils import stack_size3a
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ def link_entrances(world, player):
|
|||||||
Cave_Three_Exits = Cave_Three_Exits_Base.copy()
|
Cave_Three_Exits = Cave_Three_Exits_Base.copy()
|
||||||
|
|
||||||
from OverworldShuffle import build_sectors
|
from OverworldShuffle import build_sectors
|
||||||
if not world.owsectors[player]:
|
if not world.owsectors[player] and world.shuffle[player] != 'vanilla':
|
||||||
world.owsectors[player] = build_sectors(world, player)
|
world.owsectors[player] = build_sectors(world, player)
|
||||||
|
|
||||||
# modifications to lists
|
# modifications to lists
|
||||||
@@ -832,8 +833,6 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player, must
|
|||||||
random.shuffle(entrances)
|
random.shuffle(entrances)
|
||||||
random.shuffle(caves)
|
random.shuffle(caves)
|
||||||
|
|
||||||
from DoorShuffle import find_inaccessible_regions
|
|
||||||
|
|
||||||
used_caves = []
|
used_caves = []
|
||||||
required_entrances = 0 # Number of entrances reserved for used_caves
|
required_entrances = 0 # Number of entrances reserved for used_caves
|
||||||
skip_remaining = False
|
skip_remaining = False
|
||||||
@@ -1274,7 +1273,6 @@ def full_shuffle_dungeons(world, Dungeon_Exits, player):
|
|||||||
dw_entrances.extend([e for e in dungeon_owid_map[owid][0] if e in entrance_pool])
|
dw_entrances.extend([e for e in dungeon_owid_map[owid][0] if e in entrance_pool])
|
||||||
|
|
||||||
# determine must-exit entrances
|
# determine must-exit entrances
|
||||||
from DoorShuffle import find_inaccessible_regions
|
|
||||||
find_inaccessible_regions(world, player)
|
find_inaccessible_regions(world, player)
|
||||||
|
|
||||||
lw_must_exit = list()
|
lw_must_exit = list()
|
||||||
@@ -1442,13 +1440,12 @@ def place_old_man(world, pool, player, ignore_list=[]):
|
|||||||
|
|
||||||
|
|
||||||
def junk_fill_inaccessible(world, player):
|
def junk_fill_inaccessible(world, player):
|
||||||
from Main import copy_world
|
from Main import copy_world_limited
|
||||||
from DoorShuffle import find_inaccessible_regions
|
|
||||||
find_inaccessible_regions(world, player)
|
find_inaccessible_regions(world, player)
|
||||||
|
|
||||||
for p in range(1, world.players + 1):
|
for p in range(1, world.players + 1):
|
||||||
world.key_logic[p] = {}
|
world.key_logic[p] = {}
|
||||||
base_world = copy_world(world, True)
|
base_world = copy_world_limited(world)
|
||||||
base_world.override_bomb_check = True
|
base_world.override_bomb_check = True
|
||||||
|
|
||||||
# remove regions that have a dungeon entrance
|
# remove regions that have a dungeon entrance
|
||||||
@@ -1488,7 +1485,6 @@ def connect_inaccessible_regions(world, lw_entrances, dw_entrances, caves, playe
|
|||||||
random.shuffle(lw_entrances)
|
random.shuffle(lw_entrances)
|
||||||
random.shuffle(dw_entrances)
|
random.shuffle(dw_entrances)
|
||||||
|
|
||||||
from DoorShuffle import find_inaccessible_regions
|
|
||||||
find_inaccessible_regions(world, player)
|
find_inaccessible_regions(world, player)
|
||||||
|
|
||||||
# remove regions that have a dungeon entrance
|
# remove regions that have a dungeon entrance
|
||||||
@@ -1611,12 +1607,12 @@ def unbias_dungeons(Dungeon_Exits):
|
|||||||
|
|
||||||
|
|
||||||
def build_accessible_entrance_list(world, start_region, player, assumed_inventory=[], cross_world=False, region_rules=True, exit_rules=True, include_one_ways=False):
|
def build_accessible_entrance_list(world, start_region, player, assumed_inventory=[], cross_world=False, region_rules=True, exit_rules=True, include_one_ways=False):
|
||||||
from Main import copy_world
|
from Main import copy_world_limited
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
|
|
||||||
for p in range(1, world.players + 1):
|
for p in range(1, world.players + 1):
|
||||||
world.key_logic[p] = {}
|
world.key_logic[p] = {}
|
||||||
base_world = copy_world(world, True)
|
base_world = copy_world_limited(world)
|
||||||
base_world.override_bomb_check = True
|
base_world.override_bomb_check = True
|
||||||
|
|
||||||
connect_simple(base_world, 'Links House S&Q', start_region, player)
|
connect_simple(base_world, 'Links House S&Q', start_region, player)
|
||||||
@@ -1719,13 +1715,12 @@ def get_distant_entrances(world, start_entrance, player):
|
|||||||
|
|
||||||
|
|
||||||
def can_reach(world, entrance_name, region_name, player):
|
def can_reach(world, entrance_name, region_name, player):
|
||||||
from Main import copy_world
|
from Main import copy_world_limited
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
from DoorShuffle import find_inaccessible_regions
|
|
||||||
|
|
||||||
for p in range(1, world.players + 1):
|
for p in range(1, world.players + 1):
|
||||||
world.key_logic[p] = {}
|
world.key_logic[p] = {}
|
||||||
base_world = copy_world(world, True)
|
base_world = copy_world_limited(world)
|
||||||
base_world.override_bomb_check = True
|
base_world.override_bomb_check = True
|
||||||
|
|
||||||
entrance = world.get_entrance(entrance_name, player)
|
entrance = world.get_entrance(entrance_name, player)
|
||||||
|
|||||||
99
Fill.py
99
Fill.py
@@ -3,6 +3,7 @@ import collections
|
|||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
|
from contextlib import suppress
|
||||||
|
|
||||||
from BaseClasses import CollectionState, FillError, LocationType
|
from BaseClasses import CollectionState, FillError, LocationType
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
@@ -35,17 +36,6 @@ def dungeon_tracking(world):
|
|||||||
|
|
||||||
def fill_dungeons_restrictive(world, shuffled_locations):
|
def fill_dungeons_restrictive(world, shuffled_locations):
|
||||||
dungeon_tracking(world)
|
dungeon_tracking(world)
|
||||||
all_state_base = world.get_all_state()
|
|
||||||
|
|
||||||
# for player in range(1, world.players + 1):
|
|
||||||
# pinball_room = world.get_location('Skull Woods - Pinball Room', player)
|
|
||||||
# if world.retro[player]:
|
|
||||||
# world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False)
|
|
||||||
# else:
|
|
||||||
# world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False)
|
|
||||||
# pinball_room.event = True
|
|
||||||
# pinball_room.locked = True
|
|
||||||
# shuffled_locations.remove(pinball_room)
|
|
||||||
|
|
||||||
# with shuffled dungeon items they are distributed as part of the normal item pool
|
# with shuffled dungeon items they are distributed as part of the normal item pool
|
||||||
for item in world.get_items():
|
for item in world.get_items():
|
||||||
@@ -55,17 +45,32 @@ def fill_dungeons_restrictive(world, shuffled_locations):
|
|||||||
item.priority = True
|
item.priority = True
|
||||||
|
|
||||||
dungeon_items = [item for item in get_dungeon_item_pool(world) if item.is_inside_dungeon_item(world)]
|
dungeon_items = [item for item in get_dungeon_item_pool(world) if item.is_inside_dungeon_item(world)]
|
||||||
|
bigs, smalls, others = [], [], []
|
||||||
|
for i in dungeon_items:
|
||||||
|
(bigs if i.bigkey else smalls if i.smallkey else others).append(i)
|
||||||
|
unplaced_smalls = list(smalls)
|
||||||
|
for i in world.itempool:
|
||||||
|
if i.smallkey and world.keyshuffle[i.player]:
|
||||||
|
unplaced_smalls.append(i)
|
||||||
|
|
||||||
# sort in the order Big Key, Small Key, Other before placing dungeon items
|
def fill(base_state, items, key_pool):
|
||||||
sort_order = {"BigKey": 3, "SmallKey": 2}
|
fill_restrictive(world, base_state, shuffled_locations, items, key_pool, True)
|
||||||
dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1))
|
|
||||||
|
|
||||||
fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items,
|
all_state_base = world.get_all_state()
|
||||||
keys_in_itempool={player: not world.keyshuffle[player] for player in range(1, world.players+1)},
|
big_state_base = all_state_base.copy()
|
||||||
single_player_placement=True)
|
for x in smalls + others:
|
||||||
|
big_state_base.collect(x, True)
|
||||||
|
fill(big_state_base, bigs, unplaced_smalls)
|
||||||
|
random.shuffle(shuffled_locations)
|
||||||
|
small_state_base = all_state_base.copy()
|
||||||
|
for x in others:
|
||||||
|
small_state_base.collect(x, True)
|
||||||
|
fill(small_state_base, smalls, unplaced_smalls)
|
||||||
|
random.shuffle(shuffled_locations)
|
||||||
|
fill(all_state_base, others, None)
|
||||||
|
|
||||||
|
|
||||||
def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=None, single_player_placement=False,
|
def fill_restrictive(world, base_state, locations, itempool, key_pool=None, single_player_placement=False,
|
||||||
vanilla=False):
|
vanilla=False):
|
||||||
def sweep_from_pool():
|
def sweep_from_pool():
|
||||||
new_state = base_state.copy()
|
new_state = base_state.copy()
|
||||||
@@ -101,8 +106,7 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No
|
|||||||
item_locations = filter_locations(item_to_place, locations, world, vanilla)
|
item_locations = filter_locations(item_to_place, locations, world, vanilla)
|
||||||
for location in item_locations:
|
for location in item_locations:
|
||||||
spot_to_fill = verify_spot_to_fill(location, item_to_place, maximum_exploration_state,
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, maximum_exploration_state,
|
||||||
single_player_placement, perform_access_check, itempool,
|
single_player_placement, perform_access_check, key_pool, world)
|
||||||
keys_in_itempool, world)
|
|
||||||
if spot_to_fill:
|
if spot_to_fill:
|
||||||
break
|
break
|
||||||
if spot_to_fill is None:
|
if spot_to_fill is None:
|
||||||
@@ -111,7 +115,7 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No
|
|||||||
continue
|
continue
|
||||||
spot_to_fill = recovery_placement(item_to_place, locations, world, maximum_exploration_state,
|
spot_to_fill = recovery_placement(item_to_place, locations, world, maximum_exploration_state,
|
||||||
base_state, itempool, perform_access_check, item_locations,
|
base_state, itempool, perform_access_check, item_locations,
|
||||||
keys_in_itempool, single_player_placement)
|
key_pool, single_player_placement)
|
||||||
if spot_to_fill is None:
|
if spot_to_fill is None:
|
||||||
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
||||||
unplaced_items.insert(0, item_to_place)
|
unplaced_items.insert(0, item_to_place)
|
||||||
@@ -123,6 +127,9 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No
|
|||||||
raise FillError('No more spots to place %s' % item_to_place)
|
raise FillError('No more spots to place %s' % item_to_place)
|
||||||
|
|
||||||
world.push_item(spot_to_fill, item_to_place, False)
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
|
if item_to_place.smallkey:
|
||||||
|
with suppress(ValueError):
|
||||||
|
key_pool.remove(item_to_place)
|
||||||
track_outside_keys(item_to_place, spot_to_fill, world)
|
track_outside_keys(item_to_place, spot_to_fill, world)
|
||||||
track_dungeon_items(item_to_place, spot_to_fill, world)
|
track_dungeon_items(item_to_place, spot_to_fill, world)
|
||||||
locations.remove(spot_to_fill)
|
locations.remove(spot_to_fill)
|
||||||
@@ -132,7 +139,7 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No
|
|||||||
|
|
||||||
|
|
||||||
def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_placement, perform_access_check,
|
def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_placement, perform_access_check,
|
||||||
itempool, keys_in_itempool, world):
|
key_pool, world):
|
||||||
if item_to_place.smallkey or item_to_place.bigkey: # a better test to see if a key can go there
|
if item_to_place.smallkey or item_to_place.bigkey: # a better test to see if a key can go there
|
||||||
location.item = item_to_place
|
location.item = item_to_place
|
||||||
test_state = max_exp_state.copy()
|
test_state = max_exp_state.copy()
|
||||||
@@ -141,8 +148,7 @@ def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_pl
|
|||||||
test_state = max_exp_state
|
test_state = max_exp_state
|
||||||
if not single_player_placement or location.player == item_to_place.player:
|
if not single_player_placement or location.player == item_to_place.player:
|
||||||
if location.can_fill(test_state, item_to_place, perform_access_check):
|
if location.can_fill(test_state, item_to_place, perform_access_check):
|
||||||
test_pool = itempool if (keys_in_itempool and keys_in_itempool[item_to_place.player]) else world.itempool
|
if valid_key_placement(item_to_place, location, key_pool, world):
|
||||||
if valid_key_placement(item_to_place, location, test_pool, world):
|
|
||||||
if item_to_place.crystal or valid_dungeon_placement(item_to_place, location, world):
|
if item_to_place.crystal or valid_dungeon_placement(item_to_place, location, world):
|
||||||
return location
|
return location
|
||||||
if item_to_place.smallkey or item_to_place.bigkey:
|
if item_to_place.smallkey or item_to_place.bigkey:
|
||||||
@@ -150,7 +156,7 @@ def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_pl
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def valid_key_placement(item, location, itempool, world):
|
def valid_key_placement(item, location, key_pool, world):
|
||||||
if not valid_reserved_placement(item, location, world):
|
if not valid_reserved_placement(item, location, world):
|
||||||
return False
|
return False
|
||||||
if ((not item.smallkey and not item.bigkey) or item.player != location.player
|
if ((not item.smallkey and not item.bigkey) or item.player != location.player
|
||||||
@@ -161,7 +167,7 @@ def valid_key_placement(item, location, itempool, world):
|
|||||||
if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name):
|
if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name):
|
||||||
return True
|
return True
|
||||||
key_logic = world.key_logic[item.player][dungeon.name]
|
key_logic = world.key_logic[item.player][dungeon.name]
|
||||||
unplaced_keys = len([x for x in itempool if x.name == key_logic.small_key_name and x.player == item.player])
|
unplaced_keys = len([x for x in key_pool if x.name == key_logic.small_key_name and x.player == item.player])
|
||||||
prize_loc = None
|
prize_loc = None
|
||||||
if key_logic.prize_location:
|
if key_logic.prize_location:
|
||||||
prize_loc = world.get_location(key_logic.prize_location, location.player)
|
prize_loc = world.get_location(key_logic.prize_location, location.player)
|
||||||
@@ -216,16 +222,16 @@ def is_dungeon_item(item, world):
|
|||||||
|
|
||||||
|
|
||||||
def recovery_placement(item_to_place, locations, world, state, base_state, itempool, perform_access_check, attempted,
|
def recovery_placement(item_to_place, locations, world, state, base_state, itempool, perform_access_check, attempted,
|
||||||
keys_in_itempool=None, single_player_placement=False):
|
key_pool=None, single_player_placement=False):
|
||||||
logging.getLogger('').debug(f'Could not place {item_to_place} attempting recovery')
|
logging.getLogger('').debug(f'Could not place {item_to_place} attempting recovery')
|
||||||
if world.algorithm in ['balanced', 'equitable']:
|
if world.algorithm in ['balanced', 'equitable']:
|
||||||
return last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, keys_in_itempool,
|
return last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, key_pool,
|
||||||
single_player_placement)
|
single_player_placement)
|
||||||
elif world.algorithm == 'vanilla_fill':
|
elif world.algorithm == 'vanilla_fill':
|
||||||
if item_to_place.type == 'Crystal':
|
if item_to_place.type == 'Crystal':
|
||||||
possible_swaps = [x for x in state.locations_checked if x.item.type == 'Crystal']
|
possible_swaps = [x for x in state.locations_checked if x.item.type == 'Crystal']
|
||||||
return try_possible_swaps(possible_swaps, item_to_place, locations, world, base_state, itempool,
|
return try_possible_swaps(possible_swaps, item_to_place, locations, world, base_state, itempool,
|
||||||
keys_in_itempool, single_player_placement)
|
key_pool, single_player_placement)
|
||||||
else:
|
else:
|
||||||
i, config = 0, world.item_pool_config
|
i, config = 0, world.item_pool_config
|
||||||
tried = set(attempted)
|
tried = set(attempted)
|
||||||
@@ -235,7 +241,7 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp
|
|||||||
other_locs = [x for x in locations if x.name in fallback_locations]
|
other_locs = [x for x in locations if x.name in fallback_locations]
|
||||||
for location in other_locs:
|
for location in other_locs:
|
||||||
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
||||||
perform_access_check, itempool, keys_in_itempool, world)
|
perform_access_check, key_pool, world)
|
||||||
if spot_to_fill:
|
if spot_to_fill:
|
||||||
return spot_to_fill
|
return spot_to_fill
|
||||||
i += 1
|
i += 1
|
||||||
@@ -244,14 +250,14 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp
|
|||||||
other_locations = vanilla_fallback(item_to_place, locations, world)
|
other_locations = vanilla_fallback(item_to_place, locations, world)
|
||||||
for location in other_locations:
|
for location in other_locations:
|
||||||
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
||||||
perform_access_check, itempool, keys_in_itempool, world)
|
perform_access_check, key_pool, world)
|
||||||
if spot_to_fill:
|
if spot_to_fill:
|
||||||
return spot_to_fill
|
return spot_to_fill
|
||||||
tried.update(other_locations)
|
tried.update(other_locations)
|
||||||
other_locations = [x for x in locations if x not in tried]
|
other_locations = [x for x in locations if x not in tried]
|
||||||
for location in other_locations:
|
for location in other_locations:
|
||||||
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
||||||
perform_access_check, itempool, keys_in_itempool, world)
|
perform_access_check, key_pool, world)
|
||||||
if spot_to_fill:
|
if spot_to_fill:
|
||||||
return spot_to_fill
|
return spot_to_fill
|
||||||
return None
|
return None
|
||||||
@@ -259,14 +265,14 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp
|
|||||||
other_locations = [x for x in locations if x not in attempted]
|
other_locations = [x for x in locations if x not in attempted]
|
||||||
for location in other_locations:
|
for location in other_locations:
|
||||||
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
|
||||||
perform_access_check, itempool, keys_in_itempool, world)
|
perform_access_check, key_pool, world)
|
||||||
if spot_to_fill:
|
if spot_to_fill:
|
||||||
return spot_to_fill
|
return spot_to_fill
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def last_ditch_placement(item_to_place, locations, world, state, base_state, itempool,
|
def last_ditch_placement(item_to_place, locations, world, state, base_state, itempool,
|
||||||
keys_in_itempool=None, single_player_placement=False):
|
key_pool=None, single_player_placement=False):
|
||||||
def location_preference(loc):
|
def location_preference(loc):
|
||||||
if not loc.item.advancement:
|
if not loc.item.advancement:
|
||||||
return 1
|
return 1
|
||||||
@@ -284,21 +290,21 @@ def last_ditch_placement(item_to_place, locations, world, state, base_state, ite
|
|||||||
if x.item.type not in ['Event', 'Crystal'] and not x.forced_item]
|
if x.item.type not in ['Event', 'Crystal'] and not x.forced_item]
|
||||||
swap_locations = sorted(possible_swaps, key=location_preference)
|
swap_locations = sorted(possible_swaps, key=location_preference)
|
||||||
return try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool,
|
return try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool,
|
||||||
keys_in_itempool, single_player_placement)
|
key_pool, single_player_placement)
|
||||||
|
|
||||||
|
|
||||||
def try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool,
|
def try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool,
|
||||||
keys_in_itempool=None, single_player_placement=False):
|
key_pool=None, single_player_placement=False):
|
||||||
for location in swap_locations:
|
for location in swap_locations:
|
||||||
old_item = location.item
|
old_item = location.item
|
||||||
new_pool = list(itempool) + [old_item]
|
new_pool = list(itempool) + [old_item]
|
||||||
new_spot = find_spot_for_item(item_to_place, [location], world, base_state, new_pool,
|
new_spot = find_spot_for_item(item_to_place, [location], world, base_state, new_pool,
|
||||||
keys_in_itempool, single_player_placement)
|
key_pool, single_player_placement)
|
||||||
if new_spot:
|
if new_spot:
|
||||||
restore_item = new_spot.item
|
restore_item = new_spot.item
|
||||||
new_spot.item = item_to_place
|
new_spot.item = item_to_place
|
||||||
swap_spot = find_spot_for_item(old_item, locations, world, base_state, itempool,
|
swap_spot = find_spot_for_item(old_item, locations, world, base_state, itempool,
|
||||||
keys_in_itempool, single_player_placement)
|
key_pool, single_player_placement)
|
||||||
if swap_spot:
|
if swap_spot:
|
||||||
logging.getLogger('').debug(f'Swapping {old_item} for {item_to_place}')
|
logging.getLogger('').debug(f'Swapping {old_item} for {item_to_place}')
|
||||||
world.push_item(swap_spot, old_item, False)
|
world.push_item(swap_spot, old_item, False)
|
||||||
@@ -420,13 +426,13 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
|||||||
# Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots
|
# Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots
|
||||||
# todo: crossed
|
# todo: crossed
|
||||||
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)
|
||||||
keys_in_pool = {player: world.keyshuffle[player] or world.algorithm != 'balanced' for player in range(1, world.players + 1)}
|
key_pool = [x for x in progitempool if x.smallkey]
|
||||||
|
|
||||||
# sort maps and compasses to the back -- this may not be viable in equitable & ambrosia
|
# sort maps and compasses to the back -- this may not be viable in equitable & ambrosia
|
||||||
progitempool.sort(key=lambda item: 0 if item.map or item.compass else 1)
|
progitempool.sort(key=lambda item: 0 if item.map or item.compass else 1)
|
||||||
if world.algorithm == 'vanilla_fill':
|
if world.algorithm == 'vanilla_fill':
|
||||||
fill_restrictive(world, world.state, fill_locations, progitempool, keys_in_pool, vanilla=True)
|
fill_restrictive(world, world.state, fill_locations, progitempool, key_pool, vanilla=True)
|
||||||
fill_restrictive(world, world.state, fill_locations, progitempool, keys_in_pool)
|
fill_restrictive(world, world.state, fill_locations, progitempool, key_pool)
|
||||||
random.shuffle(fill_locations)
|
random.shuffle(fill_locations)
|
||||||
if world.algorithm == 'balanced':
|
if world.algorithm == 'balanced':
|
||||||
fast_fill(world, prioitempool, fill_locations)
|
fast_fill(world, prioitempool, fill_locations)
|
||||||
@@ -750,12 +756,14 @@ def balance_multiworld_progression(world):
|
|||||||
raise RuntimeError('Not all required items reachable. Something went terribly wrong here.')
|
raise RuntimeError('Not all required items reachable. Something went terribly wrong here.')
|
||||||
|
|
||||||
|
|
||||||
def check_shop_swap(l):
|
def check_shop_swap(l, make_item_free=False):
|
||||||
if l.parent_region.name in shop_to_location_table:
|
if l.parent_region.name in shop_to_location_table:
|
||||||
if l.name in shop_to_location_table[l.parent_region.name]:
|
if l.name in shop_to_location_table[l.parent_region.name]:
|
||||||
idx = shop_to_location_table[l.parent_region.name].index(l.name)
|
idx = shop_to_location_table[l.parent_region.name].index(l.name)
|
||||||
inv_slot = l.parent_region.shop.inventory[idx]
|
inv_slot = l.parent_region.shop.inventory[idx]
|
||||||
inv_slot['item'] = l.item.name
|
inv_slot['item'] = l.item.name
|
||||||
|
if make_item_free:
|
||||||
|
inv_slot['price'] = 0
|
||||||
elif l.parent_region in retro_shops:
|
elif l.parent_region in retro_shops:
|
||||||
idx = retro_shops[l.parent_region.name].index(l.name)
|
idx = retro_shops[l.parent_region.name].index(l.name)
|
||||||
inv_slot = l.parent_region.shop.inventory[idx]
|
inv_slot = l.parent_region.shop.inventory[idx]
|
||||||
@@ -921,12 +929,13 @@ def balance_money_progression(world):
|
|||||||
if len(increase_targets) == 0:
|
if len(increase_targets) == 0:
|
||||||
raise Exception('No early sphere swaps for rupees - money grind would be required - bailing for now')
|
raise Exception('No early sphere swaps for rupees - money grind would be required - bailing for now')
|
||||||
best_target = min(increase_targets, key=lambda t: rupee_chart[t.item.name] if t.item.name in rupee_chart else 0)
|
best_target = min(increase_targets, key=lambda t: rupee_chart[t.item.name] if t.item.name in rupee_chart else 0)
|
||||||
old_value = rupee_chart[best_target.item.name] if best_target.item.name in rupee_chart else 0
|
make_item_free = wallet[target_player] < 20
|
||||||
|
old_value = 0 if make_item_free else (rupee_chart[best_target.item.name] if best_target.item.name in rupee_chart else 0)
|
||||||
if best_swap is None:
|
if best_swap is None:
|
||||||
logger.debug(f'Upgrading {best_target.item.name} @ {best_target.name} for 300 Rupees')
|
logger.debug(f'Upgrading {best_target.item.name} @ {best_target.name} for 300 Rupees')
|
||||||
best_target.item = ItemFactory('Rupees (300)', best_target.item.player)
|
best_target.item = ItemFactory('Rupees (300)', best_target.item.player)
|
||||||
best_target.item.location = best_target
|
best_target.item.location = best_target
|
||||||
check_shop_swap(best_target.item.location)
|
check_shop_swap(best_target.item.location, make_item_free)
|
||||||
else:
|
else:
|
||||||
old_item = best_target.item
|
old_item = best_target.item
|
||||||
logger.debug(f'Swapping {best_target.item.name} @ {best_target.name} for {best_swap.item.name} @ {best_swap.name}')
|
logger.debug(f'Swapping {best_target.item.name} @ {best_target.name} for {best_swap.item.name} @ {best_swap.name}')
|
||||||
@@ -934,7 +943,7 @@ def balance_money_progression(world):
|
|||||||
best_target.item.location = best_target
|
best_target.item.location = best_target
|
||||||
best_swap.item = old_item
|
best_swap.item = old_item
|
||||||
best_swap.item.location = best_swap
|
best_swap.item.location = best_swap
|
||||||
check_shop_swap(best_target.item.location)
|
check_shop_swap(best_target.item.location, make_item_free)
|
||||||
check_shop_swap(best_swap.item.location)
|
check_shop_swap(best_swap.item.location)
|
||||||
increase = best_value - old_value
|
increase = best_value - old_value
|
||||||
difference -= increase
|
difference -= increase
|
||||||
|
|||||||
24
ItemList.py
24
ItemList.py
@@ -1155,27 +1155,3 @@ def test():
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
test()
|
test()
|
||||||
|
|
||||||
|
|
||||||
def fill_specific_items(world):
|
|
||||||
keypool = [item for item in world.itempool if item.smallkey]
|
|
||||||
cage = world.get_location('Tower of Hera - Basement Cage', 1)
|
|
||||||
c_dungeon = cage.parent_region.dungeon
|
|
||||||
key_item = next(x for x in keypool if c_dungeon.name in x.name or (c_dungeon.name == 'Hyrule Castle' and 'Escape' in x.name))
|
|
||||||
world.itempool.remove(key_item)
|
|
||||||
all_state = world.get_all_state(True)
|
|
||||||
fill_restrictive(world, all_state, [cage], [key_item])
|
|
||||||
|
|
||||||
location = world.get_location('Tower of Hera - Map Chest', 1)
|
|
||||||
key_item = next(x for x in world.itempool if 'Byrna' in x.name)
|
|
||||||
world.itempool.remove(key_item)
|
|
||||||
fast_fill(world, [key_item], [location])
|
|
||||||
|
|
||||||
|
|
||||||
# somaria = next(item for item in world.itempool if item.name == 'Cane of Somaria')
|
|
||||||
# shooter = world.get_location('Palace of Darkness - Shooter Room', 1)
|
|
||||||
# world.itempool.remove(somaria)
|
|
||||||
# all_state = world.get_all_state(True)
|
|
||||||
# fill_restrictive(world, all_state, [shooter], [somaria])
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
135
Main.py
135
Main.py
@@ -32,7 +32,7 @@ from Utils import output_path, parse_player_names
|
|||||||
from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config
|
from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config
|
||||||
from source.tools.BPS import create_bps_from_data
|
from source.tools.BPS import create_bps_from_data
|
||||||
|
|
||||||
__version__ = '1.0.1.0-u'
|
__version__ = '1.0.1.2-u'
|
||||||
|
|
||||||
from source.classes.BabelFish import BabelFish
|
from source.classes.BabelFish import BabelFish
|
||||||
|
|
||||||
@@ -396,7 +396,7 @@ def main(args, seed=None, fish=None):
|
|||||||
return world
|
return world
|
||||||
|
|
||||||
|
|
||||||
def copy_world(world, partial_copy=False):
|
def copy_world(world):
|
||||||
# ToDo: Not good yet
|
# ToDo: Not good yet
|
||||||
ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
|
ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
|
||||||
world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm,
|
world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm,
|
||||||
@@ -543,7 +543,6 @@ def copy_world(world, partial_copy=False):
|
|||||||
ret.dungeon_layouts = world.dungeon_layouts
|
ret.dungeon_layouts = world.dungeon_layouts
|
||||||
ret.key_logic = world.key_logic
|
ret.key_logic = world.key_logic
|
||||||
ret.dungeon_portals = world.dungeon_portals
|
ret.dungeon_portals = world.dungeon_portals
|
||||||
if not partial_copy:
|
|
||||||
for player, portals in world.dungeon_portals.items():
|
for player, portals in world.dungeon_portals.items():
|
||||||
for portal in portals:
|
for portal in portals:
|
||||||
connect_portal(portal, ret, player)
|
connect_portal(portal, ret, player)
|
||||||
@@ -554,9 +553,127 @@ def copy_world(world, partial_copy=False):
|
|||||||
categorize_world_regions(ret, player)
|
categorize_world_regions(ret, player)
|
||||||
set_rules(ret, player)
|
set_rules(ret, player)
|
||||||
|
|
||||||
if partial_copy:
|
return ret
|
||||||
# undo some of the things that unintentionally affect the original world object
|
|
||||||
world.key_logic = {}
|
|
||||||
|
def copy_world_limited(world):
|
||||||
|
# ToDo: Not good yet
|
||||||
|
ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, 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.player_names = copy.deepcopy(world.player_names)
|
||||||
|
ret.remote_items = world.remote_items.copy()
|
||||||
|
ret.required_medallions = world.required_medallions.copy()
|
||||||
|
ret.bottle_refills = world.bottle_refills.copy()
|
||||||
|
ret.swamp_patch_required = world.swamp_patch_required.copy()
|
||||||
|
ret.ganon_at_pyramid = world.ganon_at_pyramid.copy()
|
||||||
|
ret.powder_patch_required = world.powder_patch_required.copy()
|
||||||
|
ret.ganonstower_vanilla = world.ganonstower_vanilla.copy()
|
||||||
|
ret.treasure_hunt_count = world.treasure_hunt_count.copy()
|
||||||
|
ret.treasure_hunt_icon = world.treasure_hunt_icon.copy()
|
||||||
|
ret.sewer_light_cone = world.sewer_light_cone.copy()
|
||||||
|
ret.light_world_light_cone = world.light_world_light_cone
|
||||||
|
ret.dark_world_light_cone = world.dark_world_light_cone
|
||||||
|
ret.seed = world.seed
|
||||||
|
ret.can_access_trock_eyebridge = world.can_access_trock_eyebridge.copy()
|
||||||
|
ret.can_access_trock_front = world.can_access_trock_front.copy()
|
||||||
|
ret.can_access_trock_big_chest = world.can_access_trock_big_chest.copy()
|
||||||
|
ret.can_access_trock_middle = world.can_access_trock_middle.copy()
|
||||||
|
ret.can_take_damage = world.can_take_damage
|
||||||
|
ret.difficulty_requirements = world.difficulty_requirements.copy()
|
||||||
|
ret.fix_fake_world = world.fix_fake_world.copy()
|
||||||
|
ret.lamps_needed_for_dark_rooms = world.lamps_needed_for_dark_rooms
|
||||||
|
ret.mapshuffle = world.mapshuffle.copy()
|
||||||
|
ret.compassshuffle = world.compassshuffle.copy()
|
||||||
|
ret.keyshuffle = world.keyshuffle.copy()
|
||||||
|
ret.bigkeyshuffle = world.bigkeyshuffle.copy()
|
||||||
|
ret.bombbag = world.bombbag.copy()
|
||||||
|
ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy()
|
||||||
|
ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy()
|
||||||
|
ret.crystals_ganon_orig = world.crystals_ganon_orig.copy()
|
||||||
|
ret.crystals_gt_orig = world.crystals_gt_orig.copy()
|
||||||
|
ret.owKeepSimilar = world.owKeepSimilar.copy()
|
||||||
|
ret.owWhirlpoolShuffle = world.owWhirlpoolShuffle.copy()
|
||||||
|
ret.owFluteShuffle = world.owFluteShuffle.copy()
|
||||||
|
ret.shuffle_bonk_drops = world.shuffle_bonk_drops.copy()
|
||||||
|
ret.open_pyramid = world.open_pyramid.copy()
|
||||||
|
ret.boss_shuffle = world.boss_shuffle.copy()
|
||||||
|
ret.enemy_shuffle = world.enemy_shuffle.copy()
|
||||||
|
ret.enemy_health = world.enemy_health.copy()
|
||||||
|
ret.enemy_damage = world.enemy_damage.copy()
|
||||||
|
ret.beemizer = world.beemizer.copy()
|
||||||
|
ret.intensity = world.intensity.copy()
|
||||||
|
ret.experimental = world.experimental.copy()
|
||||||
|
ret.shopsanity = world.shopsanity.copy()
|
||||||
|
ret.dropshuffle = world.dropshuffle.copy()
|
||||||
|
ret.pottery = world.pottery.copy()
|
||||||
|
ret.potshuffle = world.potshuffle.copy()
|
||||||
|
ret.mixed_travel = world.mixed_travel.copy()
|
||||||
|
ret.standardize_palettes = world.standardize_palettes.copy()
|
||||||
|
ret.owswaps = world.owswaps.copy()
|
||||||
|
ret.owflutespots = world.owflutespots.copy()
|
||||||
|
ret.prizes = world.prizes.copy()
|
||||||
|
ret.restrict_boss_items = world.restrict_boss_items.copy()
|
||||||
|
|
||||||
|
ret.is_copied_world = True
|
||||||
|
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
create_regions(ret, player)
|
||||||
|
update_world_regions(ret, player)
|
||||||
|
if world.logic[player] in ('owglitches', 'nologic'):
|
||||||
|
create_owg_connections(ret, player)
|
||||||
|
create_flute_exits(ret, player)
|
||||||
|
create_dungeon_regions(ret, player)
|
||||||
|
create_owedges(ret, player)
|
||||||
|
create_shops(ret, player)
|
||||||
|
create_doors(ret, player)
|
||||||
|
create_rooms(ret, player)
|
||||||
|
create_dungeons(ret, player)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# connect copied world
|
||||||
|
copied_locations = {(loc.name, loc.player): loc for loc in ret.get_locations()} # caches all locations
|
||||||
|
for region in world.regions:
|
||||||
|
copied_region = ret.get_region(region.name, region.player)
|
||||||
|
copied_region.is_light_world = region.is_light_world
|
||||||
|
copied_region.is_dark_world = region.is_dark_world
|
||||||
|
copied_region.dungeon = region.dungeon
|
||||||
|
copied_region.locations = [copied_locations[(location.name, location.player)] for location in region.locations if (location.name, location.player) in copied_locations]
|
||||||
|
for location in copied_region.locations:
|
||||||
|
location.parent_region = copied_region
|
||||||
|
for entrance in region.entrances:
|
||||||
|
ret.get_entrance(entrance.name, entrance.player).connect(copied_region)
|
||||||
|
|
||||||
|
for item in world.precollected_items:
|
||||||
|
ret.push_precollected(ItemFactory(item.name, item.player))
|
||||||
|
|
||||||
|
for edge in world.owedges:
|
||||||
|
copiededge = ret.check_for_owedge(edge.name, edge.player)
|
||||||
|
if copiededge is not None:
|
||||||
|
copiededge.dest = ret.check_for_owedge(edge.dest.name, edge.dest.player)
|
||||||
|
|
||||||
|
for door in world.doors:
|
||||||
|
entrance = ret.check_for_entrance(door.name, door.player)
|
||||||
|
if entrance is not None:
|
||||||
|
destdoor = ret.check_for_door(entrance.door.name, entrance.door.player)
|
||||||
|
entrance.door = destdoor
|
||||||
|
if destdoor is not None:
|
||||||
|
destdoor.entrance = entrance
|
||||||
|
|
||||||
|
ret.key_logic = world.key_logic.copy()
|
||||||
|
|
||||||
|
from OverworldShuffle import categorize_world_regions
|
||||||
|
for player in range(1, world.players + 1):
|
||||||
|
categorize_world_regions(ret, player)
|
||||||
|
set_rules(ret, player)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@@ -578,11 +695,7 @@ def copy_dynamic_regions_and_locations(world, ret):
|
|||||||
for location in world.dynamic_locations:
|
for location in world.dynamic_locations:
|
||||||
new_reg = ret.get_region(location.parent_region.name, location.parent_region.player)
|
new_reg = ret.get_region(location.parent_region.name, location.parent_region.player)
|
||||||
new_loc = Location(location.player, location.name, location.address, location.crystal, location.hint_text, new_reg)
|
new_loc = Location(location.player, location.name, location.address, location.crystal, location.hint_text, new_reg)
|
||||||
# todo: this is potentially dangerous. later refactor so we
|
new_loc.type = location.type
|
||||||
# can apply dynamic region rules on top of copied world like other rules
|
|
||||||
new_loc.access_rule = location.access_rule
|
|
||||||
new_loc.always_allow = location.always_allow
|
|
||||||
new_loc.item_rule = location.item_rule
|
|
||||||
new_reg.locations.append(new_loc)
|
new_reg.locations.append(new_loc)
|
||||||
|
|
||||||
ret.clear_location_cache()
|
ret.clear_location_cache()
|
||||||
|
|||||||
12
Mystery.py
12
Mystery.py
@@ -1,5 +1,7 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
import RaceRandom as random
|
import RaceRandom as random
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
@@ -106,13 +108,11 @@ def main():
|
|||||||
DRMain(erargs, seed, BabelFish())
|
DRMain(erargs, seed, BabelFish())
|
||||||
|
|
||||||
def get_weights(path):
|
def get_weights(path):
|
||||||
try:
|
if os.path.exists(Path(path)):
|
||||||
if urllib.parse.urlparse(path).scheme:
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
return yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader)
|
|
||||||
with open(path, 'r', encoding='utf-8') as f:
|
|
||||||
return yaml.load(f, Loader=yaml.SafeLoader)
|
return yaml.load(f, Loader=yaml.SafeLoader)
|
||||||
except Exception as e:
|
elif urllib.parse.urlparse(path).scheme in ['http', 'https']:
|
||||||
raise Exception(f'Failed to read weights file: {e}')
|
return yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader)
|
||||||
|
|
||||||
def roll_settings(weights):
|
def roll_settings(weights):
|
||||||
def get_choice(option, root=None):
|
def get_choice(option, root=None):
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from Regions import mark_dark_world_regions, mark_light_world_regions
|
|||||||
from OWEdges import OWTileRegions, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel
|
from OWEdges import OWTileRegions, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel
|
||||||
from Utils import bidict
|
from Utils import bidict
|
||||||
|
|
||||||
version_number = '0.2.9.1'
|
version_number = '0.2.10.0'
|
||||||
# branch indicator is intentionally different across branches
|
# branch indicator is intentionally different across branches
|
||||||
version_branch = ''
|
version_branch = ''
|
||||||
|
|
||||||
@@ -289,6 +289,8 @@ def link_overworld(world, player):
|
|||||||
for whirlpools in whirlpool_candidates:
|
for whirlpools in whirlpool_candidates:
|
||||||
random.shuffle(whirlpools)
|
random.shuffle(whirlpools)
|
||||||
while len(whirlpools):
|
while len(whirlpools):
|
||||||
|
if len(whirlpools) % 2 == 1:
|
||||||
|
x=0
|
||||||
from_owid, from_whirlpool, from_region = whirlpools.pop()
|
from_owid, from_whirlpool, from_region = whirlpools.pop()
|
||||||
to_owid, to_whirlpool, to_region = whirlpools.pop()
|
to_owid, to_whirlpool, to_region = whirlpools.pop()
|
||||||
connect_simple(world, from_whirlpool, to_region, player)
|
connect_simple(world, from_whirlpool, to_region, player)
|
||||||
@@ -329,7 +331,7 @@ def link_overworld(world, player):
|
|||||||
# layout shuffle
|
# layout shuffle
|
||||||
groups = adjust_edge_groups(world, trimmed_groups, edges_to_swap, player)
|
groups = adjust_edge_groups(world, trimmed_groups, edges_to_swap, player)
|
||||||
|
|
||||||
tries = 20
|
tries = 100
|
||||||
valid_layout = False
|
valid_layout = False
|
||||||
connected_edge_cache = connected_edges.copy()
|
connected_edge_cache = connected_edges.copy()
|
||||||
while not valid_layout and tries > 0:
|
while not valid_layout and tries > 0:
|
||||||
@@ -423,10 +425,15 @@ def link_overworld(world, player):
|
|||||||
if not ignore_proximity and random.randint(0, 31) != 0 and new_ignored.intersection(ignored_regions):
|
if not ignore_proximity and random.randint(0, 31) != 0 and new_ignored.intersection(ignored_regions):
|
||||||
return False
|
return False
|
||||||
ignored_regions.update(new_ignored)
|
ignored_regions.update(new_ignored)
|
||||||
|
if owid in flute_pool:
|
||||||
flute_pool.remove(owid)
|
flute_pool.remove(owid)
|
||||||
if ignore_proximity:
|
if ignore_proximity:
|
||||||
logging.getLogger('').warning(f'Warning: Adding flute spot within proximity: {hex(owid)}')
|
logging.getLogger('').warning(f'Warning: Adding flute spot within proximity: {hex(owid)}')
|
||||||
|
logging.getLogger('').debug(f'Placing flute at: {hex(owid)}')
|
||||||
new_spots.append(owid)
|
new_spots.append(owid)
|
||||||
|
else:
|
||||||
|
# TODO: Inspect later, seems to happen only with 'random' flute shuffle
|
||||||
|
logging.getLogger('').warning(f'Warning: Attempted to place flute spot not in pool: {hex(owid)}')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# determine sectors (isolated groups of regions) to place flute spots
|
# determine sectors (isolated groups of regions) to place flute spots
|
||||||
@@ -442,6 +449,7 @@ def link_overworld(world, player):
|
|||||||
sector_total -= 1
|
sector_total -= 1
|
||||||
spots_to_place = min(flute_spots - sector_total, max(1, round((sector[0] * (flute_spots - sector_total) / region_total) + 0.5)))
|
spots_to_place = min(flute_spots - sector_total, max(1, round((sector[0] * (flute_spots - sector_total) / region_total) + 0.5)))
|
||||||
target_spots = len(new_spots) + spots_to_place
|
target_spots = len(new_spots) + spots_to_place
|
||||||
|
logging.getLogger('').debug(f'Sector of {sector[0]} regions gets {spots_to_place} spot(s)')
|
||||||
|
|
||||||
if 'Desert Palace Teleporter Ledge' in sector[1] or 'Misery Mire Teleporter Ledge' in sector[1]:
|
if 'Desert Palace Teleporter Ledge' in sector[1] or 'Misery Mire Teleporter Ledge' in sector[1]:
|
||||||
addSpot(0x38, False) # guarantee desert/mire access
|
addSpot(0x38, False) # guarantee desert/mire access
|
||||||
@@ -553,8 +561,8 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player):
|
|||||||
group_parity = {}
|
group_parity = {}
|
||||||
for group_data in groups:
|
for group_data in groups:
|
||||||
group = group_data[0]
|
group = group_data[0]
|
||||||
parity = [0, 0, 0, 0, 0]
|
parity = [0, 0, 0, 0, 0, 0]
|
||||||
# vertical land
|
# 0: vertical
|
||||||
if 0x00 in group:
|
if 0x00 in group:
|
||||||
parity[0] += 1
|
parity[0] += 1
|
||||||
if 0x0f in group:
|
if 0x0f in group:
|
||||||
@@ -563,40 +571,46 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player):
|
|||||||
parity[0] -= 1
|
parity[0] -= 1
|
||||||
if 0x81 in group:
|
if 0x81 in group:
|
||||||
parity[0] -= 1
|
parity[0] -= 1
|
||||||
# horizontal land
|
# 1: horizontal land single
|
||||||
if 0x1a in group:
|
if 0x1a in group:
|
||||||
parity[1] -= 1
|
parity[1] -= 1
|
||||||
if 0x1b in group:
|
if 0x1b in group:
|
||||||
parity[1] += 1
|
parity[1] += 1
|
||||||
if 0x28 in group:
|
if 0x28 in group:
|
||||||
parity[1] += 1
|
|
||||||
if 0x29 in group:
|
|
||||||
parity[1] -= 1
|
parity[1] -= 1
|
||||||
if 0x30 in group:
|
if 0x29 in group:
|
||||||
parity[1] -= 2
|
parity[1] += 1
|
||||||
if 0x3a in group:
|
# 2: horizontal land double
|
||||||
parity[1] += 2
|
if 0x28 in group:
|
||||||
# horizontal water
|
|
||||||
if 0x2d in group:
|
|
||||||
parity[2] += 1
|
parity[2] += 1
|
||||||
if 0x80 in group:
|
if 0x29 in group:
|
||||||
parity[2] -= 1
|
parity[2] -= 1
|
||||||
# whirlpool
|
if 0x30 in group:
|
||||||
|
parity[2] -= 1
|
||||||
|
if 0x3a in group:
|
||||||
|
parity[2] += 1
|
||||||
|
# 3: horizontal water
|
||||||
|
if 0x2d in group:
|
||||||
|
parity[3] += 1
|
||||||
|
if 0x80 in group:
|
||||||
|
parity[3] -= 1
|
||||||
|
# 4: whirlpool
|
||||||
if 0x0f in group:
|
if 0x0f in group:
|
||||||
parity[3] += 1
|
parity[4] += 1
|
||||||
if 0x12 in group:
|
if 0x12 in group:
|
||||||
parity[3] += 1
|
parity[4] += 1
|
||||||
if 0x33 in group:
|
if 0x33 in group:
|
||||||
parity[3] += 1
|
parity[4] += 1
|
||||||
if 0x35 in group:
|
if 0x35 in group:
|
||||||
parity[3] += 1
|
|
||||||
# dropdown exit
|
|
||||||
if 0x00 in group or 0x02 in group or 0x13 in group or 0x15 in group or 0x18 in group or 0x22 in group:
|
|
||||||
parity[4] += 1
|
parity[4] += 1
|
||||||
|
# 5: dropdown exit
|
||||||
|
for id in [0x00, 0x02, 0x13, 0x15, 0x18, 0x22]:
|
||||||
|
if id in group:
|
||||||
|
parity[5] += 1
|
||||||
if 0x1b in group and world.mode[player] != 'standard':
|
if 0x1b in group and world.mode[player] != 'standard':
|
||||||
parity[4] += 1
|
parity[5] += 1
|
||||||
if 0x1b in group and world.shuffle_ganon:
|
if 0x1b in group and world.shuffle_ganon:
|
||||||
parity[4] -= 1
|
parity[5] -= 1
|
||||||
group_parity[group[0]] = parity
|
group_parity[group[0]] = parity
|
||||||
|
|
||||||
attempts = 1000
|
attempts = 1000
|
||||||
@@ -607,7 +621,7 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player):
|
|||||||
# tile shuffle happens here
|
# tile shuffle happens here
|
||||||
removed = list()
|
removed = list()
|
||||||
for group in groups:
|
for group in groups:
|
||||||
# if 0x1b in group[0] or (0x1a in group[0] and world.owCrossed[player] == 'none'): # TODO: Standard + Inverted
|
#if 0x1b in group[0] or 0x13 in group[0] or (0x1a in group[0] and world.owCrossed[player] == 'none'): # TODO: Standard + Inverted
|
||||||
if random.randint(0, 1):
|
if random.randint(0, 1):
|
||||||
removed.append(group)
|
removed.append(group)
|
||||||
|
|
||||||
@@ -621,14 +635,20 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player):
|
|||||||
exist_lw_regions.extend(lw_regions)
|
exist_lw_regions.extend(lw_regions)
|
||||||
exist_dw_regions.extend(dw_regions)
|
exist_dw_regions.extend(dw_regions)
|
||||||
|
|
||||||
parity = [sum(group_parity[group[0][0]][i] for group in groups if group not in removed) for i in range(5)]
|
parity = [sum(group_parity[group[0][0]][i] for group in groups if group not in removed) for i in range(6)]
|
||||||
parity[3] %= 2 # actual parity
|
if not world.owKeepSimilar[player]:
|
||||||
if (world.owCrossed[player] == 'none' or do_grouped) and parity[:4] != [0, 0, 0, 0]:
|
parity[1] += 2*parity[2]
|
||||||
|
parity[2] = 0
|
||||||
|
# if crossed terrain:
|
||||||
|
# parity[1] += parity[3]
|
||||||
|
# parity[3] = 0
|
||||||
|
parity[4] %= 2 # actual parity
|
||||||
|
if (world.owCrossed[player] == 'none' or do_grouped) and parity[:5] != [0, 0, 0, 0, 0]:
|
||||||
attempts -= 1
|
attempts -= 1
|
||||||
continue
|
continue
|
||||||
# ensure sanc can be placed in LW in certain modes
|
# ensure sanc can be placed in LW in certain modes
|
||||||
if not do_grouped and world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lean', 'crossed', 'insanity'] and world.mode[player] != 'inverted' and (world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3 or world.mode[player] == 'standard'):
|
if not do_grouped and world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lean', 'crossed', 'insanity'] and world.mode[player] != 'inverted' and (world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3 or world.mode[player] == 'standard'):
|
||||||
free_dw_drops = parity[4] + (1 if world.shuffle_ganon else 0)
|
free_dw_drops = parity[5] + (1 if world.shuffle_ganon else 0)
|
||||||
free_drops = 6 + (1 if world.mode[player] != 'standard' else 0) + (1 if world.shuffle_ganon else 0)
|
free_drops = 6 + (1 if world.mode[player] != 'standard' else 0) + (1 if world.shuffle_ganon else 0)
|
||||||
if free_dw_drops == free_drops:
|
if free_dw_drops == free_drops:
|
||||||
attempts -= 1
|
attempts -= 1
|
||||||
@@ -681,7 +701,7 @@ def define_tile_groups(world, player, do_grouped):
|
|||||||
# sanctuary/chapel should not be swapped if S+Q guaranteed to output on that screen
|
# sanctuary/chapel should not be swapped if S+Q guaranteed to output on that screen
|
||||||
if 0x13 in group and ((world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] \
|
if 0x13 in group and ((world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] \
|
||||||
and (world.mode[player] in ['standard', 'inverted'] or world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3)) \
|
and (world.mode[player] in ['standard', 'inverted'] or world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3)) \
|
||||||
or (world.shuffle[player] == 'lite' and world.mode[player] == 'inverted')):
|
or (world.shuffle[player] in ['lite', 'lean'] and world.mode[player] == 'inverted')):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -708,7 +728,7 @@ def define_tile_groups(world, player, do_grouped):
|
|||||||
if world.owShuffle[player] == 'vanilla' and (world.owCrossed[player] == 'none' or do_grouped):
|
if world.owShuffle[player] == 'vanilla' and (world.owCrossed[player] == 'none' or do_grouped):
|
||||||
merge_groups([[0x00, 0x2d, 0x80], [0x0f, 0x81], [0x1a, 0x1b], [0x28, 0x29], [0x30, 0x3a]])
|
merge_groups([[0x00, 0x2d, 0x80], [0x0f, 0x81], [0x1a, 0x1b], [0x28, 0x29], [0x30, 0x3a]])
|
||||||
|
|
||||||
if world.owShuffle[player] == 'parallel' and world.owKeepSimilar[player] and world.owCrossed[player] == 'none':
|
if world.owShuffle[player] == 'parallel' and world.owKeepSimilar[player] and (world.owCrossed[player] == 'none' or do_grouped):
|
||||||
merge_groups([[0x28, 0x29]])
|
merge_groups([[0x28, 0x29]])
|
||||||
|
|
||||||
if not world.owWhirlpoolShuffle[player] and (world.owCrossed[player] == 'none' or do_grouped):
|
if not world.owWhirlpoolShuffle[player] and (world.owCrossed[player] == 'none' or do_grouped):
|
||||||
@@ -878,13 +898,13 @@ def can_reach_smith(world, player):
|
|||||||
return found
|
return found
|
||||||
|
|
||||||
def build_sectors(world, player):
|
def build_sectors(world, player):
|
||||||
from Main import copy_world
|
from Main import copy_world_limited
|
||||||
from OWEdges import OWTileRegions
|
from OWEdges import OWTileRegions
|
||||||
|
|
||||||
# perform accessibility check on duplicate world
|
# perform accessibility check on duplicate world
|
||||||
for p in range(1, world.players + 1):
|
for p in range(1, world.players + 1):
|
||||||
world.key_logic[p] = {}
|
world.key_logic[p] = {}
|
||||||
base_world = copy_world(world, True)
|
base_world = copy_world_limited(world)
|
||||||
|
|
||||||
# build lists of contiguous regions accessible with full inventory (excl portals/mirror/flute/entrances)
|
# build lists of contiguous regions accessible with full inventory (excl portals/mirror/flute/entrances)
|
||||||
regions = list(OWTileRegions.copy().keys())
|
regions = list(OWTileRegions.copy().keys())
|
||||||
@@ -928,11 +948,23 @@ def build_sectors(world, player):
|
|||||||
sectors2.append(explored_regions)
|
sectors2.append(explored_regions)
|
||||||
sectors[s] = sectors2
|
sectors[s] = sectors2
|
||||||
|
|
||||||
|
#TODO: Keep largest LW sector for Links House consideration, keep sector containing WDM for Old Man consideration
|
||||||
|
# sector_entrances = list()
|
||||||
|
# for sector in sectors:
|
||||||
|
# entrances = list()
|
||||||
|
# for s2 in sector:
|
||||||
|
# for region_name in s2:
|
||||||
|
# region = world.get_region(region_name, player)
|
||||||
|
# for exit in region.exits:
|
||||||
|
# if exit.spot_type == 'Entrance' and exit.name in entrance_pool:
|
||||||
|
# entrances.append(exit.name)
|
||||||
|
# sector_entrances.append(entrances)
|
||||||
|
|
||||||
return sectors
|
return sectors
|
||||||
|
|
||||||
def build_accessible_region_list(world, start_region, player, build_copy_world=False, cross_world=False, region_rules=True, ignore_ledges = False):
|
def build_accessible_region_list(world, start_region, player, build_copy_world=False, cross_world=False, region_rules=True, ignore_ledges = False):
|
||||||
from Main import copy_world
|
|
||||||
from BaseClasses import CollectionState
|
from BaseClasses import CollectionState
|
||||||
|
from Main import copy_world_limited
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
from Utils import stack_size3a
|
from Utils import stack_size3a
|
||||||
|
|
||||||
@@ -959,7 +991,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F
|
|||||||
if build_copy_world:
|
if build_copy_world:
|
||||||
for p in range(1, world.players + 1):
|
for p in range(1, world.players + 1):
|
||||||
world.key_logic[p] = {}
|
world.key_logic[p] = {}
|
||||||
base_world = copy_world(world, True)
|
base_world = copy_world_limited(world)
|
||||||
base_world.override_bomb_check = True
|
base_world.override_bomb_check = True
|
||||||
else:
|
else:
|
||||||
base_world = world
|
base_world = world
|
||||||
@@ -1015,7 +1047,7 @@ def validate_layout(world, player):
|
|||||||
entrance_connectors['Bumper Cave Entrance'] = ['West Dark Death Mountain (Bottom)']
|
entrance_connectors['Bumper Cave Entrance'] = ['West Dark Death Mountain (Bottom)']
|
||||||
entrance_connectors['Mountain Entry Entrance'] = ['Mountain Entry Ledge']
|
entrance_connectors['Mountain Entry Entrance'] = ['Mountain Entry Ledge']
|
||||||
|
|
||||||
from Main import copy_world
|
from Main import copy_world_limited
|
||||||
from Utils import stack_size3a
|
from Utils import stack_size3a
|
||||||
from EntranceShuffle import default_dungeon_connections, default_connector_connections, default_item_connections, default_shop_connections, default_drop_connections, default_dropexit_connections
|
from EntranceShuffle import default_dungeon_connections, default_connector_connections, default_item_connections, default_shop_connections, default_drop_connections, default_dropexit_connections
|
||||||
|
|
||||||
@@ -1048,7 +1080,7 @@ def validate_layout(world, player):
|
|||||||
|
|
||||||
for p in range(1, world.players + 1):
|
for p in range(1, world.players + 1):
|
||||||
world.key_logic[p] = {}
|
world.key_logic[p] = {}
|
||||||
base_world = copy_world(world, True)
|
base_world = copy_world_limited(world)
|
||||||
explored_regions = list()
|
explored_regions = list()
|
||||||
|
|
||||||
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] or not world.shufflelinks[player]:
|
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] or not world.shufflelinks[player]:
|
||||||
|
|||||||
@@ -787,12 +787,13 @@ vanilla_pots = {
|
|||||||
Pot(230, 27, PotItem.Bomb, 'Light World Bomb Hut', obj=RoomObject(0x03EF5E, [0xCF, 0xDF, 0xFA]))],
|
Pot(230, 27, PotItem.Bomb, 'Light World Bomb Hut', obj=RoomObject(0x03EF5E, [0xCF, 0xDF, 0xFA]))],
|
||||||
0x108: [Pot(166, 19, PotItem.Chicken, 'Chicken House', obj=RoomObject(0x03EFA9, [0x4F, 0x9F, 0xFA]))],
|
0x108: [Pot(166, 19, PotItem.Chicken, 'Chicken House', obj=RoomObject(0x03EFA9, [0x4F, 0x9F, 0xFA]))],
|
||||||
0x10C: [Pot(88, 14, PotItem.Heart, 'Hookshot Fairy', obj=RoomObject(0x03F329, [0xB3, 0x73, 0xFA]))],
|
0x10C: [Pot(88, 14, PotItem.Heart, 'Hookshot Fairy', obj=RoomObject(0x03F329, [0xB3, 0x73, 0xFA]))],
|
||||||
0x114: [Pot(92, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7A0, [0xBB, 0x23, 0xFA])),
|
# note: these addresses got moved thanks to waterfall fairy edit
|
||||||
Pot(96, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7A3, [0xC3, 0x23, 0xFA])),
|
0x114: [Pot(92, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F79A, [0xBB, 0x23, 0xFA])),
|
||||||
Pot(92, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A6, [0xBB, 0x2B, 0xFA])),
|
Pot(96, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F79D, [0xC3, 0x23, 0xFA])),
|
||||||
Pot(96, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A9, [0xC3, 0x2B, 0xFA])),
|
Pot(92, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A0, [0xBB, 0x2B, 0xFA])),
|
||||||
Pot(92, 10, PotItem.FiveArrows, 'Dark Desert Hint', obj=RoomObject(0x03F7AC, [0xBB, 0x53, 0xFA])),
|
Pot(96, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A3, [0xC3, 0x2B, 0xFA])),
|
||||||
Pot(96, 10, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7AF, [0xC3, 0x53, 0xFA]))],
|
Pot(92, 10, PotItem.FiveArrows, 'Dark Desert Hint', obj=RoomObject(0x03F7A6, [0xBB, 0x53, 0xFA])),
|
||||||
|
Pot(96, 10, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7A9, [0xC3, 0x53, 0xFA]))],
|
||||||
0x117: [Pot(138, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB2, [0x17, 0x1F, 0xFA])), # 0x38A -> 38A
|
0x117: [Pot(138, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB2, [0x17, 0x1F, 0xFA])), # 0x38A -> 38A
|
||||||
Pot(142, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB8, [0x1F, 0x1F, 0xFA])),
|
Pot(142, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB8, [0x1F, 0x1F, 0xFA])),
|
||||||
Pot(166, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCC1, [0x4F, 0x1F, 0xFA])),
|
Pot(166, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCC1, [0x4F, 0x1F, 0xFA])),
|
||||||
|
|||||||
@@ -68,8 +68,6 @@ OW Transitions are shuffled within each world separately.
|
|||||||
|
|
||||||
This allows OW connections to be shuffled cross-world.
|
This allows OW connections to be shuffled cross-world.
|
||||||
|
|
||||||
'None (Allowed)' allows entrance connectors and whirlpools to result in cross-world behavior, but edge transitions will not. This isn't a recommended option.
|
|
||||||
|
|
||||||
Polar and Grouped both are guaranteed to result in two separated planes of tiles. To navigate to the other plane, you have the following methods: 1) Normal portals 2) Mirroring on DW tiles 3) Fluting to a LW tile that was previously unreachable
|
Polar and Grouped both are guaranteed to result in two separated planes of tiles. To navigate to the other plane, you have the following methods: 1) Normal portals 2) Mirroring on DW tiles 3) Fluting to a LW tile that was previously unreachable
|
||||||
|
|
||||||
Limited and Chaos are not bound to follow a two-plane framework. This means that it could be possible to travel on foot to every tile without entering a normal portal.
|
Limited and Chaos are not bound to follow a two-plane framework. This means that it could be possible to travel on foot to every tile without entering a normal portal.
|
||||||
|
|||||||
@@ -183,6 +183,21 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o
|
|||||||
|
|
||||||
#### Unstable
|
#### Unstable
|
||||||
|
|
||||||
|
* 1.0.1.2
|
||||||
|
* Removed "good bee" as an in-logic way of killing Mothula
|
||||||
|
* Fixed an issue with Mystery generation and Windows path
|
||||||
|
* Fixed an issue with small key bias rework
|
||||||
|
* Fixed an issue where trinity goal would open pyramid unexpectedly. (No longer does so if ER mdoe is shuffling holes). Crystals goal updated to match that behavior.
|
||||||
|
* Fixed a playthrough issue that was not respecting pot rules
|
||||||
|
* Fixed an issue that was conflicting with downstream OWR project
|
||||||
|
* Fixed an issue with inverted and certain pottery settings
|
||||||
|
* Fixed an issue with small keys being shuffled and big keys not (key distribution)
|
||||||
|
* 1.0.1.1
|
||||||
|
* Fixed the pots in Mire Storyteller/ Dark Desert Hint to be colorized when they should be
|
||||||
|
* Certain pot items no longer reload when reloading the supertile (matches original pot behavior better)
|
||||||
|
* Changed the key distribution that made small keys placement more random when keys are in their own dungeon
|
||||||
|
* Unique boss shuffle no longer allows repeat bosses in GT (e.g. only one Trinexx in GT, so exactly 3 bosses are repeated in the seed. This is a difference process than full which does affect the probability distribution.)
|
||||||
|
* Removed text color in hints due to vanilla bug
|
||||||
* 1.0.1.0
|
* 1.0.1.0
|
||||||
* Large features
|
* Large features
|
||||||
* New pottery modes - see notes above
|
* New pottery modes - see notes above
|
||||||
@@ -206,7 +221,6 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o
|
|||||||
* Refactored spoiler to generate in stages for better error collection. A meta file will be generated additionally for mystery seeds. Some random settings moved later in the spoiler to have the meta section at the top not spoil certain things. (GT/Ganon requirements.) Thanks to codemann and OWR for most of this work.
|
* Refactored spoiler to generate in stages for better error collection. A meta file will be generated additionally for mystery seeds. Some random settings moved later in the spoiler to have the meta section at the top not spoil certain things. (GT/Ganon requirements.) Thanks to codemann and OWR for most of this work.
|
||||||
* Updated tourney winners (included Doors Async League winners)
|
* Updated tourney winners (included Doors Async League winners)
|
||||||
* Some textual changes for hints (capitalization standardization)
|
* Some textual changes for hints (capitalization standardization)
|
||||||
* Item will be highlighted in red if experimental is on. This will likely be removed.
|
|
||||||
* Reworked GT Trash Fill. Base rate is 0-75% of locations fill with 7 crystals entrance requirements. Triforce hunt is 75%-100% of locations. The 75% number will decrease based on the crystal entrance requirement. Dungeon_only algorithm caps it based on how many items need to be placed in dungeons. Cross dungeon shuffle will now work with the trash fill.
|
* Reworked GT Trash Fill. Base rate is 0-75% of locations fill with 7 crystals entrance requirements. Triforce hunt is 75%-100% of locations. The 75% number will decrease based on the crystal entrance requirement. Dungeon_only algorithm caps it based on how many items need to be placed in dungeons. Cross dungeon shuffle will now work with the trash fill.
|
||||||
* Expanded Mystery logic options (e.g. owglitches)
|
* Expanded Mystery logic options (e.g. owglitches)
|
||||||
* Updated indicators on keysanity menu for overworld map option
|
* Updated indicators on keysanity menu for overworld map option
|
||||||
|
|||||||
58
Rom.py
58
Rom.py
@@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127
|
|||||||
|
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = '92a390672efafb652774c1514ac66c4b'
|
RANDOMIZERBASEHASH = '831beb6f60c3c99467552493b3ce6f19'
|
||||||
|
|
||||||
|
|
||||||
class JsonRom(object):
|
class JsonRom(object):
|
||||||
@@ -672,18 +672,6 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
if world.mapshuffle[player]:
|
if world.mapshuffle[player]:
|
||||||
rom.write_byte(0x155C9, random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle
|
rom.write_byte(0x155C9, random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle
|
||||||
|
|
||||||
if world.pottery[player] not in ['none']:
|
|
||||||
rom.write_bytes(snes_to_pc(0x1F8375), int32_as_bytes(0x2A8000))
|
|
||||||
# make hammer pegs use different tiles
|
|
||||||
Room0127.write_to_rom(snes_to_pc(0x2A8000), rom)
|
|
||||||
|
|
||||||
if world.pot_contents[player]:
|
|
||||||
colorize_pots = is_mystery or (world.pottery[player] not in ['vanilla', 'lottery']
|
|
||||||
and (world.colorizepots[player]
|
|
||||||
or world.pottery[player] in ['reduced', 'clustered']))
|
|
||||||
if world.pot_contents[player].size() > 0x2800:
|
|
||||||
raise Exception('Pot table is too big for current area')
|
|
||||||
world.pot_contents[player].write_pot_data_to_rom(rom, colorize_pots)
|
|
||||||
# fix for swamp drains if necessary
|
# fix for swamp drains if necessary
|
||||||
swamp1location = world.get_location('Swamp Palace - Trench 1 Pot Key', player)
|
swamp1location = world.get_location('Swamp Palace - Trench 1 Pot Key', player)
|
||||||
if not swamp1location.pot.indicator:
|
if not swamp1location.pot.indicator:
|
||||||
@@ -1496,8 +1484,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
elif (world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla' or world.dropshuffle[player]
|
elif (world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla' or world.dropshuffle[player]
|
||||||
or world.dungeon_counters[player] == 'pickup' or world.pottery[player] not in ['none', 'cave']):
|
or world.dungeon_counters[player] == 'pickup' or world.pottery[player] not in ['none', 'cave']):
|
||||||
compass_mode = 0x01 # show on pickup
|
compass_mode = 0x01 # show on pickup
|
||||||
if (world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default') \
|
if (world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default') or world.owMixed[player]:
|
||||||
or (world.owMixed[player] and not (world.shuffle[player] != 'vanilla' and world.overworld_map[player] == 'default')):
|
|
||||||
compass_mode |= 0x80 # turn on locating dungeons
|
compass_mode |= 0x80 # turn on locating dungeons
|
||||||
if world.overworld_map[player] == 'compass':
|
if world.overworld_map[player] == 'compass':
|
||||||
compass_mode |= 0x20 # show icon if compass is collected, 0x00 for maps
|
compass_mode |= 0x20 # show icon if compass is collected, 0x00 for maps
|
||||||
@@ -1512,14 +1499,23 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
for idx, x_map in enumerate(x_map_position_generic):
|
for idx, x_map in enumerate(x_map_position_generic):
|
||||||
rom.write_bytes(0x53df6+idx*2, int16_as_bytes(x_map))
|
rom.write_bytes(0x53df6+idx*2, int16_as_bytes(x_map))
|
||||||
rom.write_bytes(0x53e16+idx*2, int16_as_bytes(0xFC0))
|
rom.write_bytes(0x53e16+idx*2, int16_as_bytes(0xFC0))
|
||||||
elif world.shuffle[player] == 'vanilla':
|
elif world.overworld_map[player] == 'default':
|
||||||
# disable HC/AT/GT icons
|
# disable HC/AT/GT icons
|
||||||
# rom.write_bytes(0x53E8A, int16_as_bytes(0xFF00)) # GT
|
if not world.owMixed[player]:
|
||||||
# rom.write_bytes(0x53E8C, int16_as_bytes(0xFF00)) # AT
|
rom.write_bytes(0x53E8A, int16_as_bytes(0xFF00)) # GT
|
||||||
|
rom.write_bytes(0x53E8C, int16_as_bytes(0xFF00)) # AT
|
||||||
rom.write_bytes(0x53E8E, int16_as_bytes(0xFF00)) # HC
|
rom.write_bytes(0x53E8E, int16_as_bytes(0xFF00)) # HC
|
||||||
for dungeon, portal_list in dungeon_portals.items():
|
for dungeon, portal_list in dungeon_portals.items():
|
||||||
ow_map_index = dungeon_table[dungeon].map_index
|
ow_map_index = dungeon_table[dungeon].map_index
|
||||||
if world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default':
|
if world.shuffle[player] != 'vanilla' and world.overworld_map[player] == 'default':
|
||||||
|
vanilla_entrances = { 'Hyrule Castle': 'Hyrule Castle Entrance (South)',
|
||||||
|
'Desert Palace': 'Desert Palace Entrance (North)',
|
||||||
|
'Skull Woods': 'Skull Woods Final Section'
|
||||||
|
}
|
||||||
|
entrance_name = vanilla_entrances[dungeon] if dungeon in vanilla_entrances else dungeon
|
||||||
|
entrance = world.get_entrance(entrance_name, player)
|
||||||
|
else:
|
||||||
|
if world.shuffle[player] != 'vanilla':
|
||||||
if len(portal_list) == 1:
|
if len(portal_list) == 1:
|
||||||
portal_idx = 0
|
portal_idx = 0
|
||||||
else:
|
else:
|
||||||
@@ -1541,10 +1537,11 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
world_indicator = 0x01 if entrance.parent_region.type == RegionType.DarkWorld else 0x00
|
world_indicator = 0x01 if entrance.parent_region.type == RegionType.DarkWorld else 0x00
|
||||||
coords = ow_prize_table[entrance.name]
|
coords = ow_prize_table[entrance.name]
|
||||||
# figure out compass entrances and what world (light/dark)
|
# figure out compass entrances and what world (light/dark)
|
||||||
if world.shuffle[player] == 'vanilla' or world.overworld_map[player] != 'default':
|
if world.overworld_map[player] != 'default' or world.owMixed[player]:
|
||||||
rom.write_bytes(0x53E36+ow_map_index*2, int16_as_bytes(coords[0]))
|
rom.write_bytes(0x53E36+ow_map_index*2, int16_as_bytes(coords[0]))
|
||||||
rom.write_bytes(0x53E56+ow_map_index*2, int16_as_bytes(coords[1]))
|
rom.write_bytes(0x53E56+ow_map_index*2, int16_as_bytes(coords[1]))
|
||||||
rom.write_byte(0x53EA6+ow_map_index, world_indicator)
|
rom.write_byte(0x53EA6+ow_map_index, world_indicator)
|
||||||
|
|
||||||
# in crossed doors - flip the compass exists flags
|
# in crossed doors - flip the compass exists flags
|
||||||
if world.doorShuffle[player] == 'crossed':
|
if world.doorShuffle[player] == 'crossed':
|
||||||
for dungeon, portal_list in dungeon_portals.items():
|
for dungeon, portal_list in dungeon_portals.items():
|
||||||
@@ -1706,6 +1703,19 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
if room.player == player and room.modified:
|
if room.player == player and room.modified:
|
||||||
rom.write_bytes(room.address(), room.rom_data())
|
rom.write_bytes(room.address(), room.rom_data())
|
||||||
|
|
||||||
|
if world.pottery[player] not in ['none']:
|
||||||
|
rom.write_bytes(snes_to_pc(0x1F8375), int32_as_bytes(0x2B8000))
|
||||||
|
# make hammer pegs use different tiles
|
||||||
|
Room0127.write_to_rom(snes_to_pc(0x2B8000), rom)
|
||||||
|
|
||||||
|
if world.pot_contents[player]:
|
||||||
|
colorize_pots = is_mystery or (world.pottery[player] not in ['vanilla', 'lottery']
|
||||||
|
and (world.colorizepots[player]
|
||||||
|
or world.pottery[player] in ['reduced', 'clustered']))
|
||||||
|
if world.pot_contents[player].size() > 0x2800:
|
||||||
|
raise Exception('Pot table is too big for current area')
|
||||||
|
world.pot_contents[player].write_pot_data_to_rom(rom, colorize_pots)
|
||||||
|
|
||||||
write_strings(rom, world, player, team)
|
write_strings(rom, world, player, team)
|
||||||
|
|
||||||
# write initial sram
|
# write initial sram
|
||||||
@@ -2137,8 +2147,6 @@ def write_strings(rom, world, player, team):
|
|||||||
else:
|
else:
|
||||||
if isinstance(dest, Region) and dest.type == RegionType.Dungeon and dest.dungeon:
|
if isinstance(dest, Region) and dest.type == RegionType.Dungeon and dest.dungeon:
|
||||||
hint = dest.dungeon.name
|
hint = dest.dungeon.name
|
||||||
elif isinstance(dest, Item) and world.experimental[player]:
|
|
||||||
hint = f'{{C:RED}}{dest.hint_text}{{C:WHITE}}' if dest.hint_text else 'something'
|
|
||||||
else:
|
else:
|
||||||
hint = dest.hint_text if dest.hint_text else "something"
|
hint = dest.hint_text if dest.hint_text else "something"
|
||||||
if dest.player != player:
|
if dest.player != player:
|
||||||
@@ -2325,8 +2333,7 @@ def write_strings(rom, world, player, team):
|
|||||||
if this_location:
|
if this_location:
|
||||||
item_name = this_location[0].item.hint_text
|
item_name = this_location[0].item.hint_text
|
||||||
item_name = item_name[0].upper() + item_name[1:]
|
item_name = item_name[0].upper() + item_name[1:]
|
||||||
item_format = f'{{C:RED}}{item_name}{{C:WHITE}}' if world.experimental[player] else item_name
|
this_hint = f'{item_name} can be found {hint_text(this_location[0])}.'
|
||||||
this_hint = f'{item_format} can be found {hint_text(this_location[0])}.'
|
|
||||||
tt[hint_locations.pop(0)] = this_hint
|
tt[hint_locations.pop(0)] = this_hint
|
||||||
hint_count -= 1
|
hint_count -= 1
|
||||||
|
|
||||||
@@ -2380,8 +2387,7 @@ def write_strings(rom, world, player, team):
|
|||||||
elif hint_type == 'path':
|
elif hint_type == 'path':
|
||||||
if item_count == 1:
|
if item_count == 1:
|
||||||
the_item = text_for_item(next(iter(choice_set)), world, player, team)
|
the_item = text_for_item(next(iter(choice_set)), world, player, team)
|
||||||
item_format = f'{{C:RED}}{the_item}{{C:WHITE}}' if world.experimental[player] else the_item
|
hint_candidates.append((hint_type, f'{name} conceals only {the_item}'))
|
||||||
hint_candidates.append((hint_type, f'{name} conceals only {item_format}'))
|
|
||||||
else:
|
else:
|
||||||
hint_candidates.append((hint_type, f'{name} conceals {item_count} {item_type} items'))
|
hint_candidates.append((hint_type, f'{name} conceals {item_count} {item_type} items'))
|
||||||
district_hints = min(len(hint_candidates), len(hint_locations))
|
district_hints = min(len(hint_candidates), len(hint_locations))
|
||||||
|
|||||||
23
Rules.py
23
Rules.py
@@ -21,12 +21,12 @@ def set_rules(world, player):
|
|||||||
|
|
||||||
global_rules(world, player)
|
global_rules(world, player)
|
||||||
default_rules(world, player)
|
default_rules(world, player)
|
||||||
ow_rules(world, player)
|
ow_inverted_rules(world, player)
|
||||||
|
|
||||||
ow_bunny_rules(world, player)
|
ow_bunny_rules(world, player)
|
||||||
|
|
||||||
if world.mode[player] == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
if world.get_region('Big Bomb Shop', player).entrances: # just some location that is placed late in the ER algorithm, prevent standard rules from applying when trying to search reachability in the overworld
|
if not world.is_copied_world:
|
||||||
standard_rules(world, player)
|
standard_rules(world, player)
|
||||||
elif world.mode[player] == 'open' or world.mode[player] == 'inverted':
|
elif world.mode[player] == 'open' or world.mode[player] == 'inverted':
|
||||||
open_rules(world, player)
|
open_rules(world, player)
|
||||||
@@ -356,8 +356,10 @@ def global_rules(world, player):
|
|||||||
# byrna could work with sufficient magic
|
# byrna could work with sufficient magic
|
||||||
set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player))
|
set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player))
|
||||||
loc = world.get_location('Misery Mire - Spikes Pot Key', player)
|
loc = world.get_location('Misery Mire - Spikes Pot Key', player)
|
||||||
if loc.pot is not None and loc.pot.x == 48 and loc.pot.y == 28: # pot shuffled to spike area
|
if loc.pot:
|
||||||
set_rule(loc, lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player))
|
if loc.pot.x == 48 and loc.pot.y == 28: # pot shuffled to spike area
|
||||||
|
set_rule(loc, lambda state: (state.world.can_take_damage and state.has_hearts(player, 4))
|
||||||
|
or state.has('Cane of Byrna', player) or state.has('Cape', player))
|
||||||
set_rule(world.get_entrance('Mire Left Bridge Hook Path', player), lambda state: state.has('Hookshot', player))
|
set_rule(world.get_entrance('Mire Left Bridge Hook Path', player), lambda state: state.has('Hookshot', player))
|
||||||
set_rule(world.get_entrance('Mire Tile Room NW', player), lambda state: state.has_fire_source(player))
|
set_rule(world.get_entrance('Mire Tile Room NW', player), lambda state: state.has_fire_source(player))
|
||||||
set_rule(world.get_entrance('Mire Attic Hint Hole', player), lambda state: state.has_fire_source(player))
|
set_rule(world.get_entrance('Mire Attic Hint Hole', player), lambda state: state.has_fire_source(player))
|
||||||
@@ -805,7 +807,6 @@ def pot_rules(world, player):
|
|||||||
add_rule(l, lambda state: state.can_hit_crystal(player))
|
add_rule(l, lambda state: state.can_hit_crystal(player))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def default_rules(world, player):
|
def default_rules(world, player):
|
||||||
set_rule(world.get_entrance('Other World S&Q', player), lambda state: state.has_Mirror(player) and state.has_beaten_aga(player))
|
set_rule(world.get_entrance('Other World S&Q', player), lambda state: state.has_Mirror(player) and state.has_beaten_aga(player))
|
||||||
|
|
||||||
@@ -825,7 +826,7 @@ def default_rules(world, player):
|
|||||||
|
|
||||||
# Bonk Item Access
|
# Bonk Item Access
|
||||||
if world.shuffle_bonk_drops[player]:
|
if world.shuffle_bonk_drops[player]:
|
||||||
if world.get_region('Big Bomb Shop', player).entrances: # just some location that is placed late in the ER algorithm, prevent standard rules from applying when trying to search reachability in the overworld
|
if not world.is_copied_world:
|
||||||
from Regions import bonk_prize_table
|
from Regions import bonk_prize_table
|
||||||
for location_name, (_, _, aga_required, _, _, _) in bonk_prize_table.items():
|
for location_name, (_, _, aga_required, _, _, _) in bonk_prize_table.items():
|
||||||
loc = world.get_location(location_name, player)
|
loc = world.get_location(location_name, player)
|
||||||
@@ -873,8 +874,6 @@ def default_rules(world, player):
|
|||||||
set_rule(world.get_entrance('Potion Shop Rock (North)', player), lambda state: state.can_lift_rocks(player))
|
set_rule(world.get_entrance('Potion Shop Rock (North)', player), lambda state: state.can_lift_rocks(player))
|
||||||
set_rule(world.get_entrance('Zora Approach Rocks (West)', player), lambda state: state.can_lift_heavy_rocks(player) or state.has_Boots(player))
|
set_rule(world.get_entrance('Zora Approach Rocks (West)', player), lambda state: state.can_lift_heavy_rocks(player) or state.has_Boots(player))
|
||||||
set_rule(world.get_entrance('Zora Approach Rocks (East)', player), lambda state: state.can_lift_heavy_rocks(player) or state.has_Boots(player))
|
set_rule(world.get_entrance('Zora Approach Rocks (East)', player), lambda state: state.can_lift_heavy_rocks(player) or state.has_Boots(player))
|
||||||
set_rule(world.get_entrance('Hyrule Castle Main Gate (South)', player), lambda state: state.has_Mirror(player))
|
|
||||||
set_rule(world.get_entrance('Hyrule Castle Main Gate (North)', player), lambda state: state.has_Mirror(player))
|
|
||||||
set_rule(world.get_entrance('Hyrule Castle Inner East Rock', player), lambda state: state.can_lift_rocks(player))
|
set_rule(world.get_entrance('Hyrule Castle Inner East Rock', player), lambda state: state.can_lift_rocks(player))
|
||||||
set_rule(world.get_entrance('Hyrule Castle Outer East Rock', player), lambda state: state.can_lift_rocks(player))
|
set_rule(world.get_entrance('Hyrule Castle Outer East Rock', player), lambda state: state.can_lift_rocks(player))
|
||||||
set_rule(world.get_entrance('Bat Cave Ledge Peg', player), lambda state: state.has('Hammer', player))
|
set_rule(world.get_entrance('Bat Cave Ledge Peg', player), lambda state: state.has('Hammer', player))
|
||||||
@@ -958,7 +957,7 @@ def default_rules(world, player):
|
|||||||
swordless_rules(world, player)
|
swordless_rules(world, player)
|
||||||
|
|
||||||
|
|
||||||
def ow_rules(world, player):
|
def ow_inverted_rules(world, player):
|
||||||
if world.is_atgt_swapped(player):
|
if world.is_atgt_swapped(player):
|
||||||
set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player))
|
set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player))
|
||||||
else:
|
else:
|
||||||
@@ -1129,6 +1128,8 @@ def ow_rules(world, player):
|
|||||||
set_rule(world.get_entrance('HC East Entry Mirror Spot', player), lambda state: state.has_Mirror(player))
|
set_rule(world.get_entrance('HC East Entry Mirror Spot', player), lambda state: state.has_Mirror(player))
|
||||||
set_rule(world.get_entrance('HC Courtyard Left Mirror Spot', player), lambda state: state.has_Mirror(player))
|
set_rule(world.get_entrance('HC Courtyard Left Mirror Spot', player), lambda state: state.has_Mirror(player))
|
||||||
set_rule(world.get_entrance('HC Area South Mirror Spot', player), lambda state: state.has_Mirror(player))
|
set_rule(world.get_entrance('HC Area South Mirror Spot', player), lambda state: state.has_Mirror(player))
|
||||||
|
set_rule(world.get_entrance('Hyrule Castle Main Gate (South)', player), lambda state: state.has_Mirror(player))
|
||||||
|
set_rule(world.get_entrance('Hyrule Castle Main Gate (North)', player), lambda state: state.has_Mirror(player))
|
||||||
set_rule(world.get_entrance('Top of Pyramid', player), lambda state: state.has_beaten_aga(player))
|
set_rule(world.get_entrance('Top of Pyramid', player), lambda state: state.has_beaten_aga(player))
|
||||||
set_rule(world.get_entrance('Top of Pyramid (Inner)', player), lambda state: state.has_beaten_aga(player))
|
set_rule(world.get_entrance('Top of Pyramid (Inner)', player), lambda state: state.has_beaten_aga(player))
|
||||||
else:
|
else:
|
||||||
@@ -1481,7 +1482,7 @@ def no_glitches_rules(world, player):
|
|||||||
# add_rule(world.get_location(location, player), lambda state: state.has('Hookshot', player))
|
# add_rule(world.get_location(location, player), lambda state: state.has('Hookshot', player))
|
||||||
set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: False) # no glitches does not require block override
|
set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: False) # no glitches does not require block override
|
||||||
forbid_bomb_jump_requirements(world, player)
|
forbid_bomb_jump_requirements(world, player)
|
||||||
if world.get_region('Big Bomb Shop', player).entrances: # just some location that is placed late in the ER algorithm, prevent underworld rules from applying when trying to search reachability in the overworld
|
if not world.is_copied_world:
|
||||||
add_conditional_lamps(world, player)
|
add_conditional_lamps(world, player)
|
||||||
|
|
||||||
|
|
||||||
@@ -1744,7 +1745,7 @@ def standard_rules(world, player):
|
|||||||
add_rule(world.get_entrance('Bonk Fairy (Light)', player), lambda state: state.has('Zelda Delivered', player))
|
add_rule(world.get_entrance('Bonk Fairy (Light)', player), lambda state: state.has('Zelda Delivered', player))
|
||||||
|
|
||||||
if world.shuffle_bonk_drops[player]:
|
if world.shuffle_bonk_drops[player]:
|
||||||
if world.get_region('Big Bomb Shop', player).entrances: # just some location that is placed late in the ER algorithm, prevent standard rules from applying when trying to search reachability in the overworld
|
if not world.is_copied_world:
|
||||||
add_rule(world.get_location('Hyrule Castle Tree', player), lambda state: state.has('Zelda Delivered', player))
|
add_rule(world.get_location('Hyrule Castle Tree', player), lambda state: state.has('Zelda Delivered', player))
|
||||||
add_rule(world.get_location('Central Bonk Rocks Tree', player), lambda state: state.has('Zelda Delivered', player))
|
add_rule(world.get_location('Central Bonk Rocks Tree', player), lambda state: state.has('Zelda Delivered', player))
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ def main(args=None):
|
|||||||
|
|
||||||
test("Vanilla ", "--shuffle vanilla")
|
test("Vanilla ", "--shuffle vanilla")
|
||||||
test("Retro ", "--retro --shuffle vanilla")
|
test("Retro ", "--retro --shuffle vanilla")
|
||||||
test("Keysanity ", "--shuffle vanilla --keydropshuffle drops_only --keysanity")
|
test("Keysanity ", "--shuffle vanilla --dropshuffle --keysanity")
|
||||||
test("Shopsanity", "--shuffle vanilla --shopsanity")
|
test("Shopsanity", "--shuffle vanilla --shopsanity")
|
||||||
test("Simple ", "--shuffle simple")
|
test("Simple ", "--shuffle simple")
|
||||||
test("Full ", "--shuffle full")
|
test("Full ", "--shuffle full")
|
||||||
|
|||||||
Binary file not shown.
@@ -148,7 +148,6 @@
|
|||||||
"ow_crossed": {
|
"ow_crossed": {
|
||||||
"choices": [
|
"choices": [
|
||||||
"none",
|
"none",
|
||||||
"allowed",
|
|
||||||
"polar",
|
"polar",
|
||||||
"grouped",
|
"grouped",
|
||||||
"limited",
|
"limited",
|
||||||
|
|||||||
@@ -217,7 +217,6 @@
|
|||||||
"ow_crossed": [
|
"ow_crossed": [
|
||||||
"This allows cross-world connections to occur on the overworld.",
|
"This allows cross-world connections to occur on the overworld.",
|
||||||
"None: No transitions are cross-world connections.",
|
"None: No transitions are cross-world connections.",
|
||||||
"Allowed: Only entrances/whirlpools can end up cross-world.",
|
|
||||||
"Polar: Only used when Mixed is enabled. This retains original",
|
"Polar: Only used when Mixed is enabled. This retains original",
|
||||||
" connections even when overworld tiles are swapped.",
|
" connections even when overworld tiles are swapped.",
|
||||||
"Limited: Exactly nine transitions are randomly chosen as",
|
"Limited: Exactly nine transitions are randomly chosen as",
|
||||||
|
|||||||
@@ -133,7 +133,6 @@
|
|||||||
|
|
||||||
"randomizer.overworld.crossed": "Crossed",
|
"randomizer.overworld.crossed": "Crossed",
|
||||||
"randomizer.overworld.crossed.none": "None",
|
"randomizer.overworld.crossed.none": "None",
|
||||||
"randomizer.overworld.crossed.allowed": "None (Allowed)",
|
|
||||||
"randomizer.overworld.crossed.polar": "Polar",
|
"randomizer.overworld.crossed.polar": "Polar",
|
||||||
"randomizer.overworld.crossed.grouped": "Grouped",
|
"randomizer.overworld.crossed.grouped": "Grouped",
|
||||||
"randomizer.overworld.crossed.limited": "Limited",
|
"randomizer.overworld.crossed.limited": "Limited",
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
"default": "vanilla",
|
"default": "vanilla",
|
||||||
"options": [
|
"options": [
|
||||||
"none",
|
"none",
|
||||||
"allowed",
|
|
||||||
"polar",
|
"polar",
|
||||||
"grouped",
|
"grouped",
|
||||||
"limited",
|
"limited",
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ if os.path.isdir("build") and not sys.platform.find("mac") and not sys.platform.
|
|||||||
subprocess.run(" ".join([f"pyinstaller {SPEC_FILE} ",
|
subprocess.run(" ".join([f"pyinstaller {SPEC_FILE} ",
|
||||||
upx_string,
|
upx_string,
|
||||||
"-y ",
|
"-y ",
|
||||||
"--onefile ",
|
|
||||||
f"--distpath {DEST_DIRECTORY} ",
|
f"--distpath {DEST_DIRECTORY} ",
|
||||||
]),
|
]),
|
||||||
shell=True)
|
shell=True)
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ if os.path.isdir("build") and not sys.platform.find("mac") and not sys.platform.
|
|||||||
subprocess.run(" ".join([f"pyinstaller {SPEC_FILE} ",
|
subprocess.run(" ".join([f"pyinstaller {SPEC_FILE} ",
|
||||||
upx_string,
|
upx_string,
|
||||||
"-y ",
|
"-y ",
|
||||||
"--onefile ",
|
|
||||||
f"--distpath {DEST_DIRECTORY} ",
|
f"--distpath {DEST_DIRECTORY} ",
|
||||||
]),
|
]),
|
||||||
shell=True)
|
shell=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user