Merged in DR v1.2.0.1
This commit is contained in:
+20
-7
@@ -128,7 +128,8 @@ class World(object):
|
|||||||
set_player_attr('can_access_trock_front', None)
|
set_player_attr('can_access_trock_front', None)
|
||||||
set_player_attr('can_access_trock_big_chest', None)
|
set_player_attr('can_access_trock_big_chest', None)
|
||||||
set_player_attr('can_access_trock_middle', None)
|
set_player_attr('can_access_trock_middle', None)
|
||||||
set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'nologic'] or shuffle[player] in ['lean', 'crossed', 'insanity'])
|
set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'nologic']
|
||||||
|
or shuffle[player] in ['lean', 'crossed', 'insanity'])
|
||||||
set_player_attr('mapshuffle', False)
|
set_player_attr('mapshuffle', False)
|
||||||
set_player_attr('compassshuffle', False)
|
set_player_attr('compassshuffle', False)
|
||||||
set_player_attr('keyshuffle', 'none')
|
set_player_attr('keyshuffle', 'none')
|
||||||
@@ -174,7 +175,9 @@ class World(object):
|
|||||||
def finish_init(self):
|
def finish_init(self):
|
||||||
for player in range(1, self.players + 1):
|
for player in range(1, self.players + 1):
|
||||||
if self.mode[player] == 'retro':
|
if self.mode[player] == 'retro':
|
||||||
self.mode[player] == 'open'
|
self.mode[player] = 'open'
|
||||||
|
if self.goal[player] == 'completionist':
|
||||||
|
self.accessibility[player] = 'locations'
|
||||||
|
|
||||||
def get_name_string_for_object(self, obj):
|
def get_name_string_for_object(self, obj):
|
||||||
return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})'
|
return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})'
|
||||||
@@ -547,7 +550,10 @@ class World(object):
|
|||||||
if self.has_beaten_game(state):
|
if self.has_beaten_game(state):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
prog_locations = [location for location in self.get_locations() if location.item is not None and (location.item.advancement or location.event) and location not in state.locations_checked]
|
prog_locations = [location for location in self.get_locations() if location.item is not None
|
||||||
|
and (location.item.advancement or location.event
|
||||||
|
or self.goal[location.player] == 'completionist')
|
||||||
|
and location not in state.locations_checked]
|
||||||
|
|
||||||
while prog_locations:
|
while prog_locations:
|
||||||
sphere = []
|
sphere = []
|
||||||
@@ -1129,6 +1135,12 @@ class CollectionState(object):
|
|||||||
def item_count(self, item, player):
|
def item_count(self, item, player):
|
||||||
return self.prog_items[item, player]
|
return self.prog_items[item, player]
|
||||||
|
|
||||||
|
def everything(self, player):
|
||||||
|
all_locations = self.world.get_filled_locations(player)
|
||||||
|
all_locations.remove(self.world.get_location('Ganon', player))
|
||||||
|
return (len([x for x in self.locations_checked if x.player == player])
|
||||||
|
>= len(all_locations))
|
||||||
|
|
||||||
def has_crystals(self, count, player):
|
def has_crystals(self, count, player):
|
||||||
crystals = ['Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7']
|
crystals = ['Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7']
|
||||||
return len([crystal for crystal in crystals if self.has(crystal, player)]) >= count
|
return len([crystal for crystal in crystals if self.has(crystal, player)]) >= count
|
||||||
@@ -1260,7 +1272,7 @@ class CollectionState(object):
|
|||||||
|
|
||||||
def can_flute(self, player):
|
def can_flute(self, player):
|
||||||
if self.world.mode[player] == 'standard' and not self.has('Zelda Delivered', player):
|
if self.world.mode[player] == 'standard' and not self.has('Zelda Delivered', player):
|
||||||
return False
|
return False # can't flute in rain state
|
||||||
if any(map(lambda i: i.name in ['Ocarina', 'Ocarina (Activated)'], self.world.precollected_items)):
|
if any(map(lambda i: i.name in ['Ocarina', 'Ocarina (Activated)'], self.world.precollected_items)):
|
||||||
return True
|
return True
|
||||||
lw = self.world.get_region('Kakariko Area', player)
|
lw = self.world.get_region('Kakariko Area', player)
|
||||||
@@ -3014,7 +3026,7 @@ class Spoiler(object):
|
|||||||
outfile.write('Mode:'.ljust(line_width) + '%s\n' % self.metadata['mode'][player])
|
outfile.write('Mode:'.ljust(line_width) + '%s\n' % self.metadata['mode'][player])
|
||||||
outfile.write('Swords:'.ljust(line_width) + '%s\n' % self.metadata['weapons'][player])
|
outfile.write('Swords:'.ljust(line_width) + '%s\n' % self.metadata['weapons'][player])
|
||||||
outfile.write('Goal:'.ljust(line_width) + '%s\n' % self.metadata['goal'][player])
|
outfile.write('Goal:'.ljust(line_width) + '%s\n' % self.metadata['goal'][player])
|
||||||
if self.metadata['goal'][player] in ['triforcehunt', 'trinity']:
|
if self.metadata['goal'][player] in ['triforcehunt', 'trinity', 'ganonhunt']:
|
||||||
outfile.write('Triforce Pieces Required:'.ljust(line_width) + '%s\n' % self.metadata['triforcegoal'][player])
|
outfile.write('Triforce Pieces Required:'.ljust(line_width) + '%s\n' % self.metadata['triforcegoal'][player])
|
||||||
outfile.write('Triforce Pieces Total:'.ljust(line_width) + '%s\n' % self.metadata['triforcepool'][player])
|
outfile.write('Triforce Pieces Total:'.ljust(line_width) + '%s\n' % self.metadata['triforcepool'][player])
|
||||||
outfile.write('Crystals Required for GT:'.ljust(line_width) + '%s\n' % str(self.world.crystals_gt_orig[player]))
|
outfile.write('Crystals Required for GT:'.ljust(line_width) + '%s\n' % str(self.world.crystals_gt_orig[player]))
|
||||||
@@ -3343,7 +3355,8 @@ world_mode = {"open": 0, "standard": 1, "inverted": 2}
|
|||||||
sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3}
|
sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3}
|
||||||
|
|
||||||
# byte 2: GGGD DFFH (goal, diff, item_func, hints)
|
# byte 2: GGGD DFFH (goal, diff, item_func, hints)
|
||||||
goal_mode = {"ganon": 0, "pedestal": 1, "dungeons": 2, "triforcehunt": 3, "crystals": 4, "trinity": 5}
|
goal_mode = {'ganon': 0, 'pedestal': 1, 'dungeons': 2, 'triforcehunt': 3, 'crystals': 4, 'trinity': 5,
|
||||||
|
'ganonhunt': 6, 'completionist': 7}
|
||||||
diff_mode = {"normal": 0, "hard": 1, "expert": 2}
|
diff_mode = {"normal": 0, "hard": 1, "expert": 2}
|
||||||
func_mode = {"normal": 0, "hard": 1, "expert": 2}
|
func_mode = {"normal": 0, "hard": 1, "expert": 2}
|
||||||
|
|
||||||
@@ -3391,7 +3404,7 @@ flutespot_mode = {"vanilla": 0, "balanced": 1, "random": 2}
|
|||||||
flute_mode = {'normal': 0, 'active': 1}
|
flute_mode = {'normal': 0, 'active': 1}
|
||||||
keyshuffle_mode = {'none': 0, 'wild': 1, 'universal': 2} # reserved 8 modes?
|
keyshuffle_mode = {'none': 0, 'wild': 1, 'universal': 2} # reserved 8 modes?
|
||||||
take_any_mode = {'none': 0, 'random': 1, 'fixed': 2}
|
take_any_mode = {'none': 0, 'random': 1, 'fixed': 2}
|
||||||
bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silver': 3}
|
bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silvers': 3}
|
||||||
|
|
||||||
# additions
|
# additions
|
||||||
# psuedoboots does not effect code
|
# psuedoboots does not effect code
|
||||||
|
|||||||
+18
-11
@@ -6,7 +6,7 @@ from enum import unique, Flag
|
|||||||
from typing import DefaultDict, Dict, List
|
from typing import DefaultDict, Dict, List
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
from BaseClasses import RegionType, Region, Door, DoorType, Direction, Sector, CrystalBarrier, DungeonInfo, dungeon_keys
|
from BaseClasses import RegionType, Region, Door, DoorType, Sector, CrystalBarrier, DungeonInfo, dungeon_keys
|
||||||
from BaseClasses import PotFlags, LocationType, Direction
|
from BaseClasses import PotFlags, LocationType, Direction
|
||||||
from Doors import reset_portals
|
from Doors import reset_portals
|
||||||
from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts
|
from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts
|
||||||
@@ -15,13 +15,12 @@ from Items import ItemFactory
|
|||||||
from RoomData import DoorKind, PairedDoor, reset_rooms
|
from RoomData import DoorKind, PairedDoor, reset_rooms
|
||||||
from source.dungeon.DungeonStitcher import GenerationException, generate_dungeon
|
from source.dungeon.DungeonStitcher import GenerationException, generate_dungeon
|
||||||
from source.dungeon.DungeonStitcher import ExplorationState as ExplorationState2
|
from source.dungeon.DungeonStitcher import ExplorationState as ExplorationState2
|
||||||
# from DungeonGenerator import generate_dungeon
|
from DungeonGenerator import ExplorationState, convert_regions, determine_required_paths, drop_entrances
|
||||||
from DungeonGenerator import ExplorationState, convert_regions, pre_validate, determine_required_paths, drop_entrances
|
|
||||||
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances
|
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances
|
||||||
from DungeonGenerator import dungeon_portals, dungeon_drops, connect_doors, count_reserved_locations
|
from DungeonGenerator import dungeon_portals, dungeon_drops, connect_doors, count_reserved_locations
|
||||||
from DungeonGenerator import valid_region_to_explore
|
from DungeonGenerator import valid_region_to_explore
|
||||||
from KeyDoorShuffle import analyze_dungeon, build_key_layout, validate_key_layout, determine_prize_lock
|
from KeyDoorShuffle import analyze_dungeon, build_key_layout, validate_key_layout, determine_prize_lock
|
||||||
from KeyDoorShuffle import validate_bk_layout, check_bk_special
|
from KeyDoorShuffle import validate_bk_layout
|
||||||
from Utils import ncr, kth_combination
|
from Utils import ncr, kth_combination
|
||||||
|
|
||||||
|
|
||||||
@@ -1895,6 +1894,7 @@ def shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, world,
|
|||||||
|
|
||||||
|
|
||||||
def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, world, player):
|
def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, world, player):
|
||||||
|
max_computation = 11 # this is around 6 billion worse case factorial don't want to exceed this much
|
||||||
for pool, door_type_pool in door_type_pools:
|
for pool, door_type_pool in door_type_pools:
|
||||||
ttl = 0
|
ttl = 0
|
||||||
suggestion_map, small_map, flex_map = {}, {}, {}
|
suggestion_map, small_map, flex_map = {}, {}, {}
|
||||||
@@ -1921,27 +1921,28 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, worl
|
|||||||
calculated = int(round(builder.key_doors_num*total_keys/ttl))
|
calculated = int(round(builder.key_doors_num*total_keys/ttl))
|
||||||
max_keys = max(0, builder.location_cnt - calc_used_dungeon_items(builder, world, player))
|
max_keys = max(0, builder.location_cnt - calc_used_dungeon_items(builder, world, player))
|
||||||
cand_len = max(0, len(builder.candidates.small) - builder.key_drop_cnt)
|
cand_len = max(0, len(builder.candidates.small) - builder.key_drop_cnt)
|
||||||
limit = min(max_keys, cand_len)
|
limit = min(max_keys, cand_len, max_computation)
|
||||||
suggested = min(calculated, limit)
|
suggested = min(calculated, limit)
|
||||||
combo_size = ncr(len(builder.candidates.small), suggested + builder.key_drop_cnt)
|
key_door_num = min(suggested + builder.key_drop_cnt, max_computation)
|
||||||
|
combo_size = ncr(len(builder.candidates.small), key_door_num)
|
||||||
while combo_size > 500000 and suggested > 0:
|
while combo_size > 500000 and suggested > 0:
|
||||||
suggested -= 1
|
suggested -= 1
|
||||||
combo_size = ncr(len(builder.candidates.small), suggested + builder.key_drop_cnt)
|
combo_size = ncr(len(builder.candidates.small), key_door_num)
|
||||||
suggestion_map[dungeon] = builder.key_doors_num = suggested + builder.key_drop_cnt
|
suggestion_map[dungeon] = builder.key_doors_num = key_door_num
|
||||||
remaining -= suggested + builder.key_drop_cnt
|
remaining -= suggested + builder.key_drop_cnt
|
||||||
builder.combo_size = combo_size
|
builder.combo_size = combo_size
|
||||||
flex_map[dungeon] = (limit - suggested) if suggested < limit else 0
|
flex_map[dungeon] = (limit - suggested) if suggested < limit else 0
|
||||||
for dungeon in pool:
|
for dungeon in pool:
|
||||||
builder = world.dungeon_layouts[player][dungeon]
|
builder = world.dungeon_layouts[player][dungeon]
|
||||||
if total_adjustable:
|
if total_adjustable:
|
||||||
builder.total_keys = suggestion_map[dungeon]
|
builder.total_keys = max(suggestion_map[dungeon], builder.key_drop_cnt)
|
||||||
valid_doors, small_number = find_valid_combination(builder, suggestion_map[dungeon],
|
valid_doors, small_number = find_valid_combination(builder, suggestion_map[dungeon],
|
||||||
start_regions_map[dungeon], world, player)
|
start_regions_map[dungeon], world, player)
|
||||||
small_map[dungeon] = valid_doors
|
small_map[dungeon] = valid_doors
|
||||||
actual_chest_keys = small_number - builder.key_drop_cnt
|
actual_chest_keys = small_number - builder.key_drop_cnt
|
||||||
if actual_chest_keys < suggestion_map[dungeon]:
|
if actual_chest_keys < suggestion_map[dungeon]:
|
||||||
if total_adjustable:
|
if total_adjustable:
|
||||||
builder.total_keys = actual_chest_keys
|
builder.total_keys = actual_chest_keys + builder.key_drop_cnt
|
||||||
flex_map[dungeon] = 0
|
flex_map[dungeon] = 0
|
||||||
remaining += suggestion_map[dungeon] - actual_chest_keys
|
remaining += suggestion_map[dungeon] - actual_chest_keys
|
||||||
suggestion_map[dungeon] = small_number
|
suggestion_map[dungeon] = small_number
|
||||||
@@ -1952,6 +1953,8 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, worl
|
|||||||
builder = queue.popleft()
|
builder = queue.popleft()
|
||||||
dungeon = builder.name
|
dungeon = builder.name
|
||||||
increased = suggestion_map[dungeon] + 1
|
increased = suggestion_map[dungeon] + 1
|
||||||
|
if increased > max_computation:
|
||||||
|
continue
|
||||||
builder.key_doors_num = increased
|
builder.key_doors_num = increased
|
||||||
valid_doors, small_number = find_valid_combination(builder, increased, start_regions_map[dungeon],
|
valid_doors, small_number = find_valid_combination(builder, increased, start_regions_map[dungeon],
|
||||||
world, player)
|
world, player)
|
||||||
@@ -1961,7 +1964,7 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, worl
|
|||||||
suggestion_map[dungeon] = increased
|
suggestion_map[dungeon] = increased
|
||||||
flex_map[dungeon] -= 1
|
flex_map[dungeon] -= 1
|
||||||
if total_adjustable:
|
if total_adjustable:
|
||||||
builder.total_keys = actual_chest_keys
|
builder.total_keys = max(increased, builder.key_drop_cnt)
|
||||||
if flex_map[dungeon] > 0:
|
if flex_map[dungeon] > 0:
|
||||||
builder.combo_size = ncr(len(builder.candidates.small), builder.key_doors_num)
|
builder.combo_size = ncr(len(builder.candidates.small), builder.key_doors_num)
|
||||||
queue.append(builder)
|
queue.append(builder)
|
||||||
@@ -2691,8 +2694,12 @@ def find_valid_bd_combination(builder, suggested, world, player):
|
|||||||
test = random.choice([True, False])
|
test = random.choice([True, False])
|
||||||
if test:
|
if test:
|
||||||
bomb_doors_needed -= 1
|
bomb_doors_needed -= 1
|
||||||
|
if bomb_doors_needed < 0:
|
||||||
|
bomb_doors_needed = 0
|
||||||
else:
|
else:
|
||||||
dash_doors_needed -= 1
|
dash_doors_needed -= 1
|
||||||
|
if dash_doors_needed < 0:
|
||||||
|
dash_doors_needed = 0
|
||||||
bomb_proposal = random.sample(bd_door_pool, k=bomb_doors_needed)
|
bomb_proposal = random.sample(bd_door_pool, k=bomb_doors_needed)
|
||||||
bomb_proposal.extend(custom_bomb_doors)
|
bomb_proposal.extend(custom_bomb_doors)
|
||||||
dash_pool = [x for x in bd_door_pool if x not in bomb_proposal]
|
dash_pool = [x for x in bd_door_pool if x not in bomb_proposal]
|
||||||
|
|||||||
+6
-5
@@ -12,7 +12,7 @@ from typing import List
|
|||||||
from BaseClasses import DoorType, Direction, CrystalBarrier, RegionType, Polarity, PolSlot, flooded_keys, Sector
|
from BaseClasses import DoorType, Direction, CrystalBarrier, RegionType, Polarity, PolSlot, flooded_keys, Sector
|
||||||
from BaseClasses import Hook, hook_from_door, Door
|
from BaseClasses import Hook, hook_from_door, Door
|
||||||
from Regions import dungeon_events, flooded_keys_reverse
|
from Regions import dungeon_events, flooded_keys_reverse
|
||||||
from Dungeons import dungeon_regions, split_region_starts
|
from Dungeons import split_region_starts
|
||||||
from RoomData import DoorKind
|
from RoomData import DoorKind
|
||||||
|
|
||||||
from source.dungeon.DungeonStitcher import generate_dungeon_find_proposal
|
from source.dungeon.DungeonStitcher import generate_dungeon_find_proposal
|
||||||
@@ -860,7 +860,7 @@ class ExplorationState(object):
|
|||||||
self.crystal = exp_door.crystal
|
self.crystal = exp_door.crystal
|
||||||
return exp_door
|
return exp_door
|
||||||
|
|
||||||
def visit_region(self, region, key_region=None, key_checks=False, bk_Flag=False):
|
def visit_region(self, region, key_region=None, key_checks=False, bk_flag=False):
|
||||||
if region.type != RegionType.Dungeon:
|
if region.type != RegionType.Dungeon:
|
||||||
self.crystal = CrystalBarrier.Orange
|
self.crystal = CrystalBarrier.Orange
|
||||||
if self.crystal == CrystalBarrier.Either:
|
if self.crystal == CrystalBarrier.Either:
|
||||||
@@ -881,7 +881,7 @@ class ExplorationState(object):
|
|||||||
self.ttl_locations += 1
|
self.ttl_locations += 1
|
||||||
if location not in self.found_locations:
|
if location not in self.found_locations:
|
||||||
self.found_locations.append(location)
|
self.found_locations.append(location)
|
||||||
if not bk_Flag and (not location.forced_item or 'Big Key' in location.item.name):
|
if not bk_flag and (not location.forced_item or 'Big Key' in location.item.name):
|
||||||
self.bk_found.add(location)
|
self.bk_found.add(location)
|
||||||
if location.name in dungeon_events and location.name not in self.events:
|
if location.name in dungeon_events and location.name not in self.events:
|
||||||
if self.flooded_key_check(location):
|
if self.flooded_key_check(location):
|
||||||
@@ -1335,8 +1335,9 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, dunge
|
|||||||
sector = find_sector(r_name, all_sectors)
|
sector = find_sector(r_name, all_sectors)
|
||||||
reverse_d_map[sector] = key
|
reverse_d_map[sector] = key
|
||||||
if world.mode[player] == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
current_dungeon = dungeon_map['Hyrule Castle']
|
if 'Hyrule Castle' in dungeon_map:
|
||||||
standard_stair_check(dungeon_map, current_dungeon, candidate_sectors, global_pole)
|
current_dungeon = dungeon_map['Hyrule Castle']
|
||||||
|
standard_stair_check(dungeon_map, current_dungeon, candidate_sectors, global_pole)
|
||||||
|
|
||||||
complete_dungeons = {x: y for x, y in dungeon_map.items() if sum(len(sector.outstanding_doors) for sector in y.sectors) <= 0}
|
complete_dungeons = {x: y for x, y in dungeon_map.items() if sum(len(sector.outstanding_doors) for sector in y.sectors) <= 0}
|
||||||
[dungeon_map.pop(key) for key in complete_dungeons.keys()]
|
[dungeon_map.pop(key) for key in complete_dungeons.keys()]
|
||||||
|
|||||||
@@ -406,7 +406,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
|||||||
else:
|
else:
|
||||||
max_trash = gt_count
|
max_trash = gt_count
|
||||||
scaled_trash = math.floor(max_trash * scale_factor)
|
scaled_trash = math.floor(max_trash * scale_factor)
|
||||||
if world.goal[player] in ['triforcehunt', 'trinity']:
|
if world.goal[player] in ['triforcehunt', 'trinity', 'ganonhunt']:
|
||||||
gftower_trash_count = random.randint(scaled_trash, max_trash)
|
gftower_trash_count = random.randint(scaled_trash, max_trash)
|
||||||
else:
|
else:
|
||||||
gftower_trash_count = random.randint(0, scaled_trash)
|
gftower_trash_count = random.randint(0, scaled_trash)
|
||||||
|
|||||||
+17
-12
@@ -182,8 +182,12 @@ def get_custom_array_key(item):
|
|||||||
|
|
||||||
|
|
||||||
def generate_itempool(world, player):
|
def generate_itempool(world, player):
|
||||||
if (world.difficulty[player] not in ['normal', 'hard', 'expert'] or world.goal[player] not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'trinity', 'crystals']
|
if (world.difficulty[player] not in ['normal', 'hard', 'expert']
|
||||||
or world.mode[player] not in ['open', 'standard', 'inverted'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']):
|
or world.goal[player] not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'trinity', 'crystals',
|
||||||
|
'ganonhunt', 'completionist']
|
||||||
|
or world.mode[player] not in ['open', 'standard', 'inverted']
|
||||||
|
or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown']
|
||||||
|
or world.progressive not in ['on', 'off', 'random']):
|
||||||
raise NotImplementedError('Not supported yet')
|
raise NotImplementedError('Not supported yet')
|
||||||
|
|
||||||
if world.timer in ['ohko', 'timed-ohko']:
|
if world.timer in ['ohko', 'timed-ohko']:
|
||||||
@@ -364,7 +368,7 @@ def generate_itempool(world, player):
|
|||||||
world.clock_mode = clock_mode
|
world.clock_mode = clock_mode
|
||||||
|
|
||||||
goal = world.goal[player]
|
goal = world.goal[player]
|
||||||
if goal in ['triforcehunt', 'trinity']:
|
if goal in ['triforcehunt', 'trinity', 'ganonhunt']:
|
||||||
g, t = set_default_triforce(goal, world.treasure_hunt_count[player], world.treasure_hunt_total[player])
|
g, t = set_default_triforce(goal, world.treasure_hunt_count[player], world.treasure_hunt_total[player])
|
||||||
world.treasure_hunt_count[player], world.treasure_hunt_total[player] = g, t
|
world.treasure_hunt_count[player], world.treasure_hunt_total[player] = g, t
|
||||||
world.treasure_hunt_icon[player] = 'Triforce Piece'
|
world.treasure_hunt_icon[player] = 'Triforce Piece'
|
||||||
@@ -978,14 +982,15 @@ def add_bonkdrop_contents(world, player):
|
|||||||
count -= 1
|
count -= 1
|
||||||
|
|
||||||
|
|
||||||
def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, bombbag,
|
def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords,
|
||||||
door_shuffle, logic, flute_activated):
|
bombbag, door_shuffle, logic, flute_activated):
|
||||||
pool = []
|
pool = []
|
||||||
placed_items = {}
|
placed_items = {}
|
||||||
precollected_items = []
|
precollected_items = []
|
||||||
clock_mode = None
|
clock_mode = None
|
||||||
if treasure_hunt_total == 0 and goal in ['triforcehunt', 'trinity']:
|
if goal in ['triforcehunt', 'trinity', 'ganonhunt']:
|
||||||
treasure_hunt_total = 30 if goal == 'triforcehunt' else 10
|
if treasure_hunt_total == 0:
|
||||||
|
treasure_hunt_total = 30 if goal in ['triforcehunt', 'ganonhunt'] else 10
|
||||||
# triforce pieces max out
|
# triforce pieces max out
|
||||||
triforcepool = ['Triforce Piece'] * min(treasure_hunt_total, max_goal)
|
triforcepool = ['Triforce Piece'] * min(treasure_hunt_total, max_goal)
|
||||||
|
|
||||||
@@ -1088,7 +1093,7 @@ def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt
|
|||||||
elif timer == 'timed-ohko':
|
elif timer == 'timed-ohko':
|
||||||
pool.extend(diff.timedohko)
|
pool.extend(diff.timedohko)
|
||||||
clock_mode = 'countdown-ohko'
|
clock_mode = 'countdown-ohko'
|
||||||
if goal in ['triforcehunt', 'trinity']:
|
if goal in ['triforcehunt', 'trinity', 'ganonhunt']:
|
||||||
pool.extend(triforcepool)
|
pool.extend(triforcepool)
|
||||||
|
|
||||||
for extra in diff.extras:
|
for extra in diff.extras:
|
||||||
@@ -1146,7 +1151,7 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer
|
|||||||
customitemarray["triforce"] = total_items_to_place
|
customitemarray["triforce"] = total_items_to_place
|
||||||
|
|
||||||
# Triforce Pieces
|
# Triforce Pieces
|
||||||
if goal in ['triforcehunt', 'trinity']:
|
if goal in ['triforcehunt', 'trinity', 'ganonhunt']:
|
||||||
g, t = set_default_triforce(goal, customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"])
|
g, t = set_default_triforce(goal, customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"])
|
||||||
customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"] = g, t
|
customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"] = g, t
|
||||||
|
|
||||||
@@ -1184,8 +1189,8 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer
|
|||||||
treasure_hunt_count = max(min(customitemarray["triforcepiecesgoal"], max_goal), 1)
|
treasure_hunt_count = max(min(customitemarray["triforcepiecesgoal"], max_goal), 1)
|
||||||
treasure_hunt_icon = 'Triforce Piece'
|
treasure_hunt_icon = 'Triforce Piece'
|
||||||
# Ensure game is always possible to complete here, force sufficient pieces if the player is unwilling.
|
# Ensure game is always possible to complete here, force sufficient pieces if the player is unwilling.
|
||||||
if ((customitemarray["triforcepieces"] < treasure_hunt_count) and (goal in ['triforcehunt', 'trinity'])
|
if ((customitemarray["triforcepieces"] < treasure_hunt_count)
|
||||||
and (customitemarray["triforce"] == 0)):
|
and (goal in ['triforcehunt', 'trinity', 'ganonhunt']) and (customitemarray["triforce"] == 0)):
|
||||||
extrapieces = treasure_hunt_count - customitemarray["triforcepieces"]
|
extrapieces = treasure_hunt_count - customitemarray["triforcepieces"]
|
||||||
pool.extend(['Triforce Piece'] * extrapieces)
|
pool.extend(['Triforce Piece'] * extrapieces)
|
||||||
itemtotal = itemtotal + extrapieces
|
itemtotal = itemtotal + extrapieces
|
||||||
@@ -1385,7 +1390,7 @@ def get_player_dungeon_item_pool(world, player):
|
|||||||
# location pool doesn't support larger values at this time
|
# location pool doesn't support larger values at this time
|
||||||
def set_default_triforce(goal, custom_goal, custom_total):
|
def set_default_triforce(goal, custom_goal, custom_total):
|
||||||
triforce_goal, triforce_total = 0, 0
|
triforce_goal, triforce_total = 0, 0
|
||||||
if goal == 'triforcehunt':
|
if goal in ['triforcehunt', 'ganonhunt']:
|
||||||
triforce_goal, triforce_total = 20, 30
|
triforce_goal, triforce_total = 20, 30
|
||||||
elif goal == 'trinity':
|
elif goal == 'trinity':
|
||||||
triforce_goal, triforce_total = 8, 10
|
triforce_goal, triforce_total = 8, 10
|
||||||
|
|||||||
+1
-1
@@ -1445,7 +1445,7 @@ def validate_bk_layout(proposal, builder, start_regions, world, player):
|
|||||||
if loc.forced_big_key():
|
if loc.forced_big_key():
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return len(state.bk_found) > 0
|
return state.count_locations_exclude_specials(world, player) > 0
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new
|
|||||||
from source.tools.BPS import create_bps_from_data
|
from source.tools.BPS import create_bps_from_data
|
||||||
from source.classes.CustomSettings import CustomSettings
|
from source.classes.CustomSettings import CustomSettings
|
||||||
|
|
||||||
__version__ = '1.2.0.0-u'
|
__version__ = '1.2.0.1-u'
|
||||||
|
|
||||||
from source.classes.BabelFish import BabelFish
|
from source.classes.BabelFish import BabelFish
|
||||||
|
|
||||||
@@ -786,7 +786,8 @@ def create_playthrough(world):
|
|||||||
world = copy_world(world)
|
world = copy_world(world)
|
||||||
|
|
||||||
# get locations containing progress items
|
# get locations containing progress items
|
||||||
prog_locations = [location for location in world.get_filled_locations() if location.item.advancement]
|
prog_locations = [location for location in world.get_filled_locations() if location.item.advancement
|
||||||
|
or world.goal[location.player] == 'completionist']
|
||||||
optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop', 'Skull Star Tile']
|
optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop', 'Skull Star Tile']
|
||||||
optional_locations.extend(['Hyrule Castle Courtyard Tree Pull', 'Mountain Entry Area Tree Pull']) # adding pre-aga tree pulls
|
optional_locations.extend(['Hyrule Castle Courtyard Tree Pull', 'Mountain Entry Area Tree Pull']) # adding pre-aga tree pulls
|
||||||
optional_locations.extend(['Lumberjack Area Crab Drop', 'South Pass Area Crab Drop']) # adding pre-aga bush crabs
|
optional_locations.extend(['Lumberjack Area Crab Drop', 'South Pass Area Crab Drop']) # adding pre-aga bush crabs
|
||||||
@@ -825,6 +826,8 @@ def create_playthrough(world):
|
|||||||
for num, sphere in reversed(list(enumerate(collection_spheres))):
|
for num, sphere in reversed(list(enumerate(collection_spheres))):
|
||||||
to_delete = set()
|
to_delete = set()
|
||||||
for location in sphere:
|
for location in sphere:
|
||||||
|
if world.goal[location.player] == 'completionist':
|
||||||
|
continue # every location for that player is required
|
||||||
# we remove the item at location and check if game is still beatable
|
# we remove the item at location and check if game is still beatable
|
||||||
logging.getLogger('').debug('Checking if %s (Player %d) is required to beat the game.', location.item.name, location.item.player)
|
logging.getLogger('').debug('Checking if %s (Player %d) is required to beat the game.', location.item.name, location.item.player)
|
||||||
old_item = location.item
|
old_item = location.item
|
||||||
|
|||||||
+18
-4
@@ -53,7 +53,16 @@ This is similar to insanity mode in ER where door entrances and exits are not pa
|
|||||||
|
|
||||||
## Customizer
|
## Customizer
|
||||||
|
|
||||||
Please see [Customizer documentation](docs/Customizer.md) on how to create custom seeds.
|
Please see [Customizer documentation](docs/Customizer.md) on how to create custom seeds.
|
||||||
|
|
||||||
|
## New Goals
|
||||||
|
|
||||||
|
### Triforce Hunt + Ganon
|
||||||
|
Collect the requisite triforce pieces, then defeat Ganon. (Aga2 not required). Use `ganonhunt` on CLI
|
||||||
|
|
||||||
|
### Completionist
|
||||||
|
All dungeons not enough for you? You have to obtain every item in the game too. This option turns on the collection rate counter and forces accessibility to be 100% locations. Finish by defeating Ganon.
|
||||||
|
|
||||||
|
|
||||||
## Standard Generation Change
|
## Standard Generation Change
|
||||||
|
|
||||||
@@ -100,10 +109,15 @@ These are now independent of retro mode and have three options: None, Random, an
|
|||||||
|
|
||||||
# Bug Fixes and Notes
|
# Bug Fixes and Notes
|
||||||
|
|
||||||
None yet
|
* 1.2.0.1-u
|
||||||
|
* Added new ganonhunt and completionist goals
|
||||||
|
* Fixed the issue when defeating Agahnim and standing in the doorway can cause door state to linger.
|
||||||
|
* Fix for Inverted Lean/Lite ER
|
||||||
|
* Fix for vanilla Doors + Standard + ER
|
||||||
|
* Added a limit per dungeon on small key doors to ensure reasonable generation
|
||||||
|
* Fixed many small bugs
|
||||||
|
|
||||||
# Known Issues
|
# Known Issues
|
||||||
|
|
||||||
* Standing in the doorway when defeating Aga 1 and being teleported to the Dark World will not clear door state. It may cause issues requiring a Save & Quit to fix.
|
|
||||||
* Decoupled doors can lead to situations where you aren't logically supposed to go back through a door without a big key or small key, but you can if you press the correct direction back through the door first. There are some transitions where you may get stuck without a bomb. These problems are planned to be fixed.
|
* Decoupled doors can lead to situations where you aren't logically supposed to go back through a door without a big key or small key, but you can if you press the correct direction back through the door first. There are some transitions where you may get stuck without a bomb. These problems are planned to be fixed.
|
||||||
* Logic getting to Skull X room may be wrong if a trap door, big key door, or bombable wall. A bomb jump to get to those pot may be required if you don't have boots to bonk across.
|
* Logic getting to Skull X room may be wrong if a trap door, big key door, or bombable wall is shuffled there. A bomb jump to get to those pot may be required if you don't have boots to bonk across.
|
||||||
@@ -15,7 +15,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
raise Exception('Could not load BPS module')
|
raise Exception('Could not load BPS module')
|
||||||
|
|
||||||
from BaseClasses import ShopType, Region, Location, OWEdge, Door, DoorType, RegionType, LocationType, Item
|
from BaseClasses import ShopType, Region, Location, OWEdge, Door, DoorType, RegionType, LocationType
|
||||||
from DoorShuffle import compass_data, DROptions, boss_indicator, dungeon_portals
|
from DoorShuffle import compass_data, DROptions, boss_indicator, dungeon_portals
|
||||||
from Dungeons import dungeon_music_addresses, dungeon_table
|
from Dungeons import dungeon_music_addresses, dungeon_table
|
||||||
from Regions import location_table, shop_to_location_table, retro_shops
|
from Regions import location_table, shop_to_location_table, retro_shops
|
||||||
@@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127
|
|||||||
|
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = '7a6619830c3521f2a09c4c64cd8d7d75'
|
RANDOMIZERBASEHASH = '1cdb34399467efa346b713db54d6cb02'
|
||||||
|
|
||||||
|
|
||||||
class JsonRom(object):
|
class JsonRom(object):
|
||||||
@@ -869,7 +869,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' else DROptions.Town_Portal
|
dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' else DROptions.Town_Portal
|
||||||
if world.doorShuffle[player] not in ['vanilla', 'basic']:
|
if world.doorShuffle[player] not in ['vanilla', 'basic']:
|
||||||
dr_flags |= DROptions.Map_Info
|
dr_flags |= DROptions.Map_Info
|
||||||
if world.collection_rate[player] and world.goal[player] not in ['triforcehunt', 'trinity']:
|
if ((world.collection_rate[player] or world.goal[player] == 'completionist')
|
||||||
|
and world.goal[player] not in ['triforcehunt', 'trinity', 'ganonhunt']):
|
||||||
dr_flags |= DROptions.Debug
|
dr_flags |= DROptions.Debug
|
||||||
if world.doorShuffle[player] not in ['vanilla', 'basic'] and world.logic[player] != 'nologic'\
|
if world.doorShuffle[player] not in ['vanilla', 'basic'] and world.logic[player] != 'nologic'\
|
||||||
and world.mixed_travel[player] == 'prevent':
|
and world.mixed_travel[player] == 'prevent':
|
||||||
@@ -1339,7 +1340,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
|
|
||||||
# set up goals for treasure hunt
|
# set up goals for treasure hunt
|
||||||
rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon[player] == 'Triforce Piece' else [0x0D, 0x28])
|
rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon[player] == 'Triforce Piece' else [0x0D, 0x28])
|
||||||
if world.goal[player] in ['triforcehunt', 'trinity']:
|
if world.goal[player] in ['triforcehunt', 'trinity', 'ganonhunt']:
|
||||||
rom.write_bytes(0x180167, int16_as_bytes(world.treasure_hunt_count[player]))
|
rom.write_bytes(0x180167, int16_as_bytes(world.treasure_hunt_count[player]))
|
||||||
rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled)
|
rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled)
|
||||||
|
|
||||||
@@ -1411,6 +1412,10 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
rom.write_byte(0x18003E, 0x02) # make ganon invincible until all dungeons are beat
|
rom.write_byte(0x18003E, 0x02) # make ganon invincible until all dungeons are beat
|
||||||
elif world.goal[player] in ['crystals', 'trinity']:
|
elif world.goal[player] in ['crystals', 'trinity']:
|
||||||
rom.write_byte(0x18003E, 0x04) # make ganon invincible until all crystals
|
rom.write_byte(0x18003E, 0x04) # make ganon invincible until all crystals
|
||||||
|
elif world.goal[player] in ['ganonhunt']:
|
||||||
|
rom.write_byte(0x18003E, 0x05) # make ganon invincible until all triforce pieces collected
|
||||||
|
elif world.goal[player] in ['completionist']:
|
||||||
|
rom.write_byte(0x18003E, 0x0a) # make ganon invincible until everything is collected
|
||||||
else:
|
else:
|
||||||
rom.write_byte(0x18003E, 0x03) # make ganon invincible until all crystals and aga 2 are collected
|
rom.write_byte(0x18003E, 0x03) # make ganon invincible until all crystals and aga 2 are collected
|
||||||
|
|
||||||
@@ -2479,6 +2484,10 @@ def write_strings(rom, world, player, team):
|
|||||||
trinity_crystal_text = ('%d crystal to beat Ganon.' if world.crystals_needed_for_ganon[player] == 1 else '%d crystals to beat Ganon.') % world.crystals_needed_for_ganon[player]
|
trinity_crystal_text = ('%d crystal to beat Ganon.' if world.crystals_needed_for_ganon[player] == 1 else '%d crystals to beat Ganon.') % world.crystals_needed_for_ganon[player]
|
||||||
tt['sign_ganon'] = 'Three ways to victory! %s Get to it!' % trinity_crystal_text
|
tt['sign_ganon'] = 'Three ways to victory! %s Get to it!' % trinity_crystal_text
|
||||||
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % int(world.treasure_hunt_count[player])
|
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % int(world.treasure_hunt_count[player])
|
||||||
|
elif world.goal[player] == 'ganonhunt':
|
||||||
|
tt['sign_ganon'] = 'Go find the Triforce pieces to beat Ganon'
|
||||||
|
elif world.goal[player] == 'completionist':
|
||||||
|
tt['sign_ganon'] = 'Ganon only respects those who have done everything'
|
||||||
tt['ganon_fall_in'] = Ganon1_texts[random.randint(0, len(Ganon1_texts) - 1)]
|
tt['ganon_fall_in'] = Ganon1_texts[random.randint(0, len(Ganon1_texts) - 1)]
|
||||||
tt['ganon_fall_in_alt'] = 'You cannot defeat me until you finish your goal!'
|
tt['ganon_fall_in_alt'] = 'You cannot defeat me until you finish your goal!'
|
||||||
tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!'
|
tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!'
|
||||||
|
|||||||
@@ -65,6 +65,10 @@ def set_rules(world, player):
|
|||||||
for location in world.get_region('Hyrule Castle Courtyard', player).locations:
|
for location in world.get_region('Hyrule Castle Courtyard', player).locations:
|
||||||
if location.name == 'Murahdahla':
|
if location.name == 'Murahdahla':
|
||||||
add_rule(location, lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player]))
|
add_rule(location, lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player]))
|
||||||
|
elif world.goal[player] == 'ganonhunt':
|
||||||
|
add_rule(world.get_location('Ganon', player), lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player]))
|
||||||
|
elif world.goal[player] == 'completionist':
|
||||||
|
add_rule(world.get_location('Ganon', player), lambda state: state.everything(player))
|
||||||
|
|
||||||
# if swamp and dam have not been moved we require mirror for swamp palace
|
# if swamp and dam have not been moved we require mirror for swamp palace
|
||||||
if not world.swamp_patch_required[player]:
|
if not world.swamp_patch_required[player]:
|
||||||
@@ -2090,7 +2094,8 @@ def add_key_logic_rules(world, player):
|
|||||||
for chest in d_logic.bk_chests:
|
for chest in d_logic.bk_chests:
|
||||||
big_chest = world.get_location(chest.name, player)
|
big_chest = world.get_location(chest.name, player)
|
||||||
add_rule(big_chest, create_rule(d_logic.bk_name, player))
|
add_rule(big_chest, create_rule(d_logic.bk_name, player))
|
||||||
if len(d_logic.bk_doors) == 0 and len(d_logic.bk_chests) <= 1:
|
if (len(d_logic.bk_doors) == 0 and len(d_logic.bk_chests) <= 1
|
||||||
|
and world.accessibility[player] != 'locations'):
|
||||||
set_always_allow(big_chest, allow_big_key_in_big_chest(d_logic.bk_name, player))
|
set_always_allow(big_chest, allow_big_key_in_big_chest(d_logic.bk_name, player))
|
||||||
if world.keyshuffle[player] == 'universal':
|
if world.keyshuffle[player] == 'universal':
|
||||||
for d_name, layout in world.key_layout[player].items():
|
for d_name, layout in world.key_layout[player].items():
|
||||||
|
|||||||
Binary file not shown.
+3
-1
@@ -113,6 +113,8 @@
|
|||||||
pedestal: 2
|
pedestal: 2
|
||||||
triforce-hunt: 2
|
triforce-hunt: 2
|
||||||
trinity: 2
|
trinity: 2
|
||||||
|
ganonhunt: 2
|
||||||
|
completionist: 1
|
||||||
triforce_goal_min: 10
|
triforce_goal_min: 10
|
||||||
triforce_goal_max: 30
|
triforce_goal_max: 30
|
||||||
triforce_pool_min: 20
|
triforce_pool_min: 20
|
||||||
@@ -136,7 +138,7 @@
|
|||||||
mcu: 1 # map, compass, universal smalls
|
mcu: 1 # map, compass, universal smalls
|
||||||
# for use when you aren't using the dungeon_items above
|
# for use when you aren't using the dungeon_items above
|
||||||
# smallkey_shuffle:
|
# smallkey_shuffle:
|
||||||
# standard: 5
|
# none: 5
|
||||||
# wild: 1
|
# wild: 1
|
||||||
# universal: 1
|
# universal: 1
|
||||||
dungeon_counters:
|
dungeon_counters:
|
||||||
|
|||||||
@@ -67,7 +67,9 @@
|
|||||||
"dungeons",
|
"dungeons",
|
||||||
"triforcehunt",
|
"triforcehunt",
|
||||||
"trinity",
|
"trinity",
|
||||||
"crystals"
|
"crystals",
|
||||||
|
"ganonhunt",
|
||||||
|
"completionist"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"difficulty": {
|
"difficulty": {
|
||||||
|
|||||||
@@ -108,7 +108,10 @@
|
|||||||
"Triforce Hunt: Places 30 Triforce Pieces in the world, collect",
|
"Triforce Hunt: Places 30 Triforce Pieces in the world, collect",
|
||||||
" 20 of them to beat the game.",
|
" 20 of them to beat the game.",
|
||||||
"Trinity: Can beat the game by defeating Ganon, pulling",
|
"Trinity: Can beat the game by defeating Ganon, pulling",
|
||||||
" Pedestal, or delivering Triforce Pieces."
|
" Pedestal, or delivering Triforce Pieces.",
|
||||||
|
"Ganon Hunt: Places 30 Triforce Pieces in the world, collect",
|
||||||
|
" 20 of them then defeat Ganon.",
|
||||||
|
"Completionist: Find everything then defeat Ganon."
|
||||||
],
|
],
|
||||||
"difficulty": [
|
"difficulty": [
|
||||||
"Select game difficulty. Affects available itempool. (default: %(default)s)",
|
"Select game difficulty. Affects available itempool. (default: %(default)s)",
|
||||||
|
|||||||
@@ -279,6 +279,8 @@
|
|||||||
"randomizer.item.goal.triforcehunt": "Triforce Hunt",
|
"randomizer.item.goal.triforcehunt": "Triforce Hunt",
|
||||||
"randomizer.item.goal.trinity": "Trinity",
|
"randomizer.item.goal.trinity": "Trinity",
|
||||||
"randomizer.item.goal.crystals": "Crystals",
|
"randomizer.item.goal.crystals": "Crystals",
|
||||||
|
"randomizer.item.goal.ganonhunt": "Triforce Hunt + Ganon",
|
||||||
|
"randomizer.item.goal.completionist": "Completionist",
|
||||||
|
|
||||||
"randomizer.item.crystals_gt": "Crystals to open GT",
|
"randomizer.item.crystals_gt": "Crystals to open GT",
|
||||||
"randomizer.item.crystals_gt.0": "0",
|
"randomizer.item.crystals_gt.0": "0",
|
||||||
|
|||||||
@@ -36,7 +36,9 @@
|
|||||||
"dungeons",
|
"dungeons",
|
||||||
"triforcehunt",
|
"triforcehunt",
|
||||||
"trinity",
|
"trinity",
|
||||||
"crystals"
|
"crystals",
|
||||||
|
"ganonhunt",
|
||||||
|
"completionist"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"crystals_gt": {
|
"crystals_gt": {
|
||||||
|
|||||||
@@ -218,7 +218,6 @@ def modify_proposal(proposed_map, explored_state, doors_to_connect, hash_code_se
|
|||||||
else:
|
else:
|
||||||
logger.debug(f' Re-Linking {attempt.name} -> {new_door.name}')
|
logger.debug(f' Re-Linking {attempt.name} -> {new_door.name}')
|
||||||
logger.debug(f' Re-Linking {old_attempt.name} -> {old_target.name}')
|
logger.debug(f' Re-Linking {old_attempt.name} -> {old_target.name}')
|
||||||
hash_code_set.add(hash_code)
|
|
||||||
return proposed_map, hash_code
|
return proposed_map, hash_code
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+18
-12
@@ -189,26 +189,27 @@ def district_item_pool_config(world):
|
|||||||
if district.dungeon:
|
if district.dungeon:
|
||||||
adjustment = len([i for i in world.get_dungeon(name, p).all_items
|
adjustment = len([i for i in world.get_dungeon(name, p).all_items
|
||||||
if i.is_inside_dungeon_item(world)])
|
if i.is_inside_dungeon_item(world)])
|
||||||
dist_len = len(district.locations) - adjustment
|
dist_adj = adjustment
|
||||||
if name not in district_choices:
|
if name not in district_choices:
|
||||||
district_choices[name] = (district.sphere_one, dist_len)
|
district_choices[name] = (district.sphere_one, dist_adj)
|
||||||
else:
|
else:
|
||||||
so, amt = district_choices[name]
|
so, amt = district_choices[name]
|
||||||
district_choices[name] = (so or district.sphere_one, amt + dist_len)
|
district_choices[name] = (so or district.sphere_one, amt + dist_adj)
|
||||||
|
|
||||||
chosen_locations = defaultdict(set)
|
chosen_locations = defaultdict(set)
|
||||||
location_cnt = 0
|
adjustment_cnt = 0
|
||||||
|
|
||||||
# choose a sphere one district
|
# choose a sphere one district
|
||||||
sphere_one_choices = [d for d, info in district_choices.items() if info[0]]
|
sphere_one_choices = [d for d, info in district_choices.items() if info[0]]
|
||||||
sphere_one = random.choice(sphere_one_choices)
|
sphere_one = random.choice(sphere_one_choices)
|
||||||
so, amt = district_choices[sphere_one]
|
so, adj = district_choices[sphere_one]
|
||||||
location_cnt += amt
|
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
for location in world.districts[player][sphere_one].locations:
|
for location in world.districts[player][sphere_one].locations:
|
||||||
chosen_locations[location].add(player)
|
chosen_locations[location].add(player)
|
||||||
del district_choices[sphere_one]
|
del district_choices[sphere_one]
|
||||||
config.recorded_choices.append(sphere_one)
|
config.recorded_choices.append(sphere_one)
|
||||||
|
adjustment_cnt += adj
|
||||||
|
location_cnt = len(chosen_locations) - adjustment_cnt
|
||||||
|
|
||||||
scale_factors = defaultdict(int)
|
scale_factors = defaultdict(int)
|
||||||
scale_total = 0
|
scale_total = 0
|
||||||
@@ -217,8 +218,9 @@ def district_item_pool_config(world):
|
|||||||
dungeon = world.get_entrance(ent, p).connected_region.dungeon
|
dungeon = world.get_entrance(ent, p).connected_region.dungeon
|
||||||
if dungeon:
|
if dungeon:
|
||||||
scale = world.crystals_needed_for_gt[p]
|
scale = world.crystals_needed_for_gt[p]
|
||||||
scale_total += scale
|
if scale > 0:
|
||||||
scale_factors[dungeon.name] += scale
|
scale_total += scale
|
||||||
|
scale_factors[dungeon.name] += scale
|
||||||
scale_total = max(1, scale_total)
|
scale_total = max(1, scale_total)
|
||||||
scale_divisors = defaultdict(lambda: 1)
|
scale_divisors = defaultdict(lambda: 1)
|
||||||
scale_divisors.update(scale_factors)
|
scale_divisors.update(scale_factors)
|
||||||
@@ -226,13 +228,15 @@ def district_item_pool_config(world):
|
|||||||
while location_cnt < item_cnt:
|
while location_cnt < item_cnt:
|
||||||
weights = [scale_total / scale_divisors[d] for d in district_choices.keys()]
|
weights = [scale_total / scale_divisors[d] for d in district_choices.keys()]
|
||||||
choice = random.choices(list(district_choices.keys()), weights=weights, k=1)[0]
|
choice = random.choices(list(district_choices.keys()), weights=weights, k=1)[0]
|
||||||
so, amt = district_choices[choice]
|
so, adj = district_choices[choice]
|
||||||
location_cnt += amt
|
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
for location in world.districts[player][choice].locations:
|
for location in world.districts[player][choice].locations:
|
||||||
chosen_locations[location].add(player)
|
chosen_locations[location].add(player)
|
||||||
del district_choices[choice]
|
del district_choices[choice]
|
||||||
config.recorded_choices.append(choice)
|
config.recorded_choices.append(choice)
|
||||||
|
adjustment_cnt += adj
|
||||||
|
location_cnt = len(chosen_locations) - adjustment_cnt
|
||||||
config.placeholders = location_cnt - item_cnt
|
config.placeholders = location_cnt - item_cnt
|
||||||
config.location_groups[0].locations = chosen_locations
|
config.location_groups[0].locations = chosen_locations
|
||||||
|
|
||||||
@@ -383,7 +387,10 @@ def vanilla_fallback(item_to_place, locations, world):
|
|||||||
|
|
||||||
def filter_locations(item_to_place, locations, world, vanilla_skip=False, potion=False):
|
def filter_locations(item_to_place, locations, world, vanilla_skip=False, potion=False):
|
||||||
config = world.item_pool_config
|
config = world.item_pool_config
|
||||||
item_name = 'Bottle' if item_to_place.name.startswith('Bottle') else item_to_place.name
|
if not isinstance(item_to_place, str):
|
||||||
|
item_name = 'Bottle' if item_to_place.name.startswith('Bottle') else item_to_place.name
|
||||||
|
else:
|
||||||
|
item_name = item_to_place
|
||||||
if world.algorithm == 'vanilla_fill':
|
if world.algorithm == 'vanilla_fill':
|
||||||
filtered = []
|
filtered = []
|
||||||
item_name = 'Bottle' if item_to_place.name.startswith('Bottle') else item_to_place.name
|
item_name = 'Bottle' if item_to_place.name.startswith('Bottle') else item_to_place.name
|
||||||
@@ -443,7 +450,6 @@ def filter_pot_locations(locations, world):
|
|||||||
return locations
|
return locations
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
vanilla_mapping = {
|
vanilla_mapping = {
|
||||||
'Green Pendant': ['Eastern Palace - Prize'],
|
'Green Pendant': ['Eastern Palace - Prize'],
|
||||||
'Red Pendant': ['Desert Palace - Prize', 'Tower of Hera - Prize'],
|
'Red Pendant': ['Desert Palace - Prize', 'Tower of Hera - Prize'],
|
||||||
|
|||||||
@@ -774,12 +774,19 @@ def do_vanilla_connect(pool_def, avail):
|
|||||||
if avail.world.pottery[avail.player] not in ['none', 'keys', 'dungeon']:
|
if avail.world.pottery[avail.player] not in ['none', 'keys', 'dungeon']:
|
||||||
return
|
return
|
||||||
defaults = inverted_default_connections if avail.inverted else default_connections
|
defaults = inverted_default_connections if avail.inverted else default_connections
|
||||||
|
if avail.inverted:
|
||||||
|
if 'Dark Death Mountain Fairy' in pool_def['entrances']:
|
||||||
|
pool_def['entrances'].remove('Dark Death Mountain Fairy')
|
||||||
|
pool_def['entrances'].append('Bumper Cave (top)')
|
||||||
for entrance in pool_def['entrances']:
|
for entrance in pool_def['entrances']:
|
||||||
if entrance in avail.entrances:
|
if entrance in avail.entrances:
|
||||||
target = defaults[entrance]
|
target = defaults[entrance]
|
||||||
connect_simple(avail.world, entrance, target, avail.player)
|
if entrance in avail.default_map:
|
||||||
avail.entrances.remove(entrance)
|
connect_vanilla_two_way(entrance, avail.default_map[entrance], avail)
|
||||||
avail.exits.remove(target)
|
else:
|
||||||
|
connect_simple(avail.world, entrance, target, avail.player)
|
||||||
|
avail.entrances.remove(entrance)
|
||||||
|
avail.exits.remove(target)
|
||||||
|
|
||||||
|
|
||||||
def do_mandatory_connections(avail, entrances, cave_options, must_exit):
|
def do_mandatory_connections(avail, entrances, cave_options, must_exit):
|
||||||
@@ -1228,7 +1235,7 @@ modes = {
|
|||||||
'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy',
|
'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy',
|
||||||
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave',
|
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave',
|
||||||
'Dark Desert Hint',
|
'Dark Desert Hint',
|
||||||
'Links House', 'Inverted Links House', 'Tavern North']
|
'Links House', 'Tavern North']
|
||||||
},
|
},
|
||||||
'old_man_cave': { # have to do old man cave first so lw dungeon don't use up everything
|
'old_man_cave': { # have to do old man cave first so lw dungeon don't use up everything
|
||||||
'special': 'old_man_cave_east',
|
'special': 'old_man_cave_east',
|
||||||
@@ -1304,7 +1311,7 @@ modes = {
|
|||||||
'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy',
|
'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy',
|
||||||
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave',
|
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave',
|
||||||
'Dark Desert Hint',
|
'Dark Desert Hint',
|
||||||
'Links House', 'Inverted Links House', 'Tavern North']
|
'Links House', 'Tavern North'] # inverted links house gets substituted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1572,6 +1579,7 @@ entrance_map = {
|
|||||||
'Paradox Cave (Bottom)': 'Paradox Cave Exit (Bottom)',
|
'Paradox Cave (Bottom)': 'Paradox Cave Exit (Bottom)',
|
||||||
'Paradox Cave (Middle)': 'Paradox Cave Exit (Middle)',
|
'Paradox Cave (Middle)': 'Paradox Cave Exit (Middle)',
|
||||||
'Paradox Cave (Top)': 'Paradox Cave Exit (Top)',
|
'Paradox Cave (Top)': 'Paradox Cave Exit (Top)',
|
||||||
|
'Inverted Dark Sanctuary': 'Inverted Dark Sanctuary Exit',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1607,7 +1615,7 @@ single_entrance_map = {
|
|||||||
'Lake Hylia Fortune Teller': 'Lake Hylia Fortune Teller', 'Lake Hylia Fairy': 'Lake Hylia Healer Fairy',
|
'Lake Hylia Fortune Teller': 'Lake Hylia Fortune Teller', 'Lake Hylia Fairy': 'Lake Hylia Healer Fairy',
|
||||||
'Bonk Fairy (Light)': 'Bonk Fairy (Light)', 'Lumberjack House': 'Lumberjack House', 'Dam': 'Dam',
|
'Bonk Fairy (Light)': 'Bonk Fairy (Light)', 'Lumberjack House': 'Lumberjack House', 'Dam': 'Dam',
|
||||||
'Blinds Hideout': 'Blinds Hideout', 'Waterfall of Wishing': 'Waterfall of Wishing',
|
'Blinds Hideout': 'Blinds Hideout', 'Waterfall of Wishing': 'Waterfall of Wishing',
|
||||||
'Inverted Bomb Shop': 'Inverted Bomb Shop', 'Inverted Dark Sanctuary': 'Inverted Dark Sanctuary',
|
'Inverted Bomb Shop': 'Inverted Bomb Shop'
|
||||||
}
|
}
|
||||||
|
|
||||||
default_dw = {
|
default_dw = {
|
||||||
|
|||||||
@@ -122,7 +122,9 @@ def roll_settings(weights):
|
|||||||
'dungeons': 'dungeons',
|
'dungeons': 'dungeons',
|
||||||
'pedestal': 'pedestal',
|
'pedestal': 'pedestal',
|
||||||
'triforce-hunt': 'triforcehunt',
|
'triforce-hunt': 'triforcehunt',
|
||||||
'trinity': 'trinity'
|
'trinity': 'trinity',
|
||||||
|
'ganonhunt': 'ganonhunt',
|
||||||
|
'completionist': 'completionist'
|
||||||
}[goal]
|
}[goal]
|
||||||
|
|
||||||
ret.openpyramid = get_choice('open_pyramid') if 'open_pyramid' in weights else 'auto'
|
ret.openpyramid = get_choice('open_pyramid') if 'open_pyramid' in weights else 'auto'
|
||||||
@@ -215,5 +217,6 @@ def roll_settings(weights):
|
|||||||
ret.ow_palettes = get_choice('ow_palettes', romweights)
|
ret.ow_palettes = get_choice('ow_palettes', romweights)
|
||||||
ret.uw_palettes = get_choice('uw_palettes', romweights)
|
ret.uw_palettes = get_choice('uw_palettes', romweights)
|
||||||
ret.shuffle_sfx = get_choice('shuffle_sfx', romweights) == 'on'
|
ret.shuffle_sfx = get_choice('shuffle_sfx', romweights) == 'on'
|
||||||
|
ret.msu_resume = get_choice('msu_resume', romweights) == 'on'
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||||
import RaceRandom as random
|
import RaceRandom as random
|
||||||
import logging
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from collections import Counter, defaultdict
|
from collections import Counter, defaultdict
|
||||||
@@ -99,7 +101,7 @@ def test_loop(tests, entrance_set, exit_set, ctr, shuffle_mode, main_mode, links
|
|||||||
# seed = 635441530
|
# seed = 635441530
|
||||||
random.seed(seed)
|
random.seed(seed)
|
||||||
world = World(1, {1: shuffle_mode}, {1: 'vanilla'}, {1: 'noglitches'}, {1: main_mode}, {}, {}, {},
|
world = World(1, {1: shuffle_mode}, {1: 'vanilla'}, {1: 'noglitches'}, {1: main_mode}, {}, {}, {},
|
||||||
{}, {}, {}, {}, {}, True, {}, {}, [], {})
|
{}, {}, {}, {}, {}, True, {}, [], {})
|
||||||
world.customizer = False
|
world.customizer = False
|
||||||
world.shufflelinks = {1: links}
|
world.shufflelinks = {1: links}
|
||||||
create_regions(world, 1)
|
create_regions(world, 1)
|
||||||
|
|||||||
Reference in New Issue
Block a user