Merge branch 'OverworldShuffleDev' into OverworldShuffle
This commit is contained in:
@@ -160,7 +160,7 @@ class World(object):
|
||||
set_player_attr('pot_contents', None)
|
||||
set_player_attr('pseudoboots', False)
|
||||
set_player_attr('collection_rate', False)
|
||||
set_player_attr('colorizepots', False)
|
||||
set_player_attr('colorizepots', True)
|
||||
set_player_attr('pot_pool', {})
|
||||
set_player_attr('decoupledoors', False)
|
||||
set_player_attr('door_type_mode', 'original')
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
# Changelog
|
||||
|
||||
## 0.3.0.1
|
||||
- \~Merged in DR v1.2.0.10~
|
||||
- Fixed some door landing issues
|
||||
- Added Customizer support for OWR Tile Flips
|
||||
- Added 'Standard+Inverted' template customizer yaml
|
||||
- Fixed some ER issues with placing Sanc Drop in non-crossed ER
|
||||
|
||||
## 0.3.0.0
|
||||
- Merged in all DR Customizer features since its initial release up to v1.2.0.9
|
||||
- \~Merged in all DR Customizer features since its initial release up to v1.2.0.9~
|
||||
- Major revamp of Aerinon's ER 2.0 to better support OWR modes
|
||||
- Fixed various incorrect logic issues (Inverted flute spots, Bomb Shop start, etc)
|
||||
- Flute is disabled in rain state except glitched modes
|
||||
|
||||
2
CLI.py
2
CLI.py
@@ -214,7 +214,7 @@ def parse_settings():
|
||||
"keydropshuffle": False,
|
||||
"dropshuffle": False,
|
||||
"pottery": "none",
|
||||
"colorizepots": False,
|
||||
"colorizepots": True,
|
||||
"shufflepots": False,
|
||||
"mapshuffle": False,
|
||||
"compassshuffle": False,
|
||||
|
||||
17
ItemList.py
17
ItemList.py
@@ -1175,8 +1175,19 @@ item_alternates = {
|
||||
|
||||
|
||||
def modify_pool_for_start_inventory(start_inventory, world, player):
|
||||
# skips custom item pools - these shouldn't be adjusted
|
||||
if (world.customizer and world.customizer.get_item_pool()) or world.custom:
|
||||
# custom item pools only adjust in dungeon items
|
||||
for item in start_inventory:
|
||||
if item.dungeon:
|
||||
d = world.get_dungeon(item.dungeon, item.player)
|
||||
match = next((i for i in d.all_items if i.name == item.name), None)
|
||||
if match:
|
||||
if match.map or match.compass:
|
||||
d.dungeon_items.remove(match)
|
||||
elif match.smallkey:
|
||||
d.small_keys.remove(match)
|
||||
elif match.bigkey and d.big_key == match:
|
||||
d.big_key = None
|
||||
return
|
||||
for item in start_inventory:
|
||||
if item.player == player:
|
||||
@@ -1202,8 +1213,8 @@ def modify_pool_for_start_inventory(start_inventory, world, player):
|
||||
d.dungeon_items.remove(match)
|
||||
elif match.smallkey:
|
||||
d.small_keys.remove(match)
|
||||
elif match.bigkey:
|
||||
d.big_key.remove(match)
|
||||
elif match.bigkey and d.big_key == match:
|
||||
d.big_key = None
|
||||
|
||||
|
||||
def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer, goal, mode, swords, bombbag, customitemarray):
|
||||
|
||||
@@ -2094,6 +2094,12 @@ def validate_key_placement(key_layout, world, player):
|
||||
if world.bigkeyshuffle[player]:
|
||||
max_counter = find_max_counter(key_layout)
|
||||
big_key_outside = bigkey_name not in (l.item.name for l in max_counter.free_locations if l.item)
|
||||
for i in world.precollected_items:
|
||||
if i.player == player and i.name == bigkey_name:
|
||||
big_key_outside = True
|
||||
break
|
||||
if i.player == player and i.name == smallkey_name:
|
||||
keys_outside += 1
|
||||
|
||||
for code, counter in key_layout.key_counters.items():
|
||||
if len(counter.child_doors) == 0:
|
||||
|
||||
2
Main.py
2
Main.py
@@ -36,7 +36,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new
|
||||
from source.tools.BPS import create_bps_from_data
|
||||
from source.classes.CustomSettings import CustomSettings
|
||||
|
||||
__version__ = '1.2.0.9-u'
|
||||
__version__ = '1.2.0.10u'
|
||||
|
||||
from source.classes.BabelFish import BabelFish
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType
|
||||
from OverworldGlitchRules import create_owg_connections
|
||||
from Utils import bidict
|
||||
|
||||
version_number = '0.3.0.0'
|
||||
version_number = '0.3.0.1'
|
||||
# branch indicator is intentionally different across branches
|
||||
version_branch = ''
|
||||
|
||||
@@ -632,17 +632,56 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player):
|
||||
parity[5] -= 1
|
||||
group_parity[group[0]] = parity
|
||||
|
||||
attempts = 1000
|
||||
# customizer adjustments
|
||||
undefined_chance = 50
|
||||
flipped_groups = list()
|
||||
always_removed = list()
|
||||
if world.customizer:
|
||||
if not do_grouped:
|
||||
custom_flips = world.customizer.get_owtileflips()
|
||||
if custom_flips:
|
||||
nonflipped_groups = list()
|
||||
forced_flips = list()
|
||||
forced_nonflips = list()
|
||||
player_key = player
|
||||
if 'undefined_chance' in custom_flips[player_key]:
|
||||
undefined_chance = custom_flips[player_key]['undefined_chance']
|
||||
if 'force_flip' in custom_flips[player_key]:
|
||||
forced_flips = custom_flips[player_key]['force_flip']
|
||||
if 'force_no_flip' in custom_flips[player_key]:
|
||||
forced_nonflips = custom_flips[player_key]['force_no_flip']
|
||||
|
||||
for group in groups:
|
||||
if any(owid in group[0] for owid in forced_nonflips):
|
||||
nonflipped_groups.append(group)
|
||||
if any(owid in group[0] for owid in forced_flips):
|
||||
flipped_groups.append(group)
|
||||
|
||||
# Check if there are any groups that appear in both sets
|
||||
if any(group in flipped_groups for group in nonflipped_groups):
|
||||
raise GenerationException('Conflict found when flipping tiles')
|
||||
|
||||
for g in nonflipped_groups:
|
||||
always_removed.append(g)
|
||||
if undefined_chance == 0:
|
||||
for g in [g for g in groups if g not in flipped_groups + always_removed]:
|
||||
always_removed.append(g)
|
||||
|
||||
attempts = 1
|
||||
if 0 < undefined_chance < 100:
|
||||
# do roughly 1000 attempts at a full list
|
||||
attempts = len(groups) - len(always_removed)
|
||||
attempts = (attempts ** 1.9) + (attempts * 10) + 1
|
||||
while True:
|
||||
if attempts == 0: # expected to only occur with custom flips
|
||||
raise GenerationException('Could not find valid tile flips')
|
||||
|
||||
# tile shuffle happens here
|
||||
removed = list()
|
||||
for group in groups:
|
||||
#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):
|
||||
removed.append(group)
|
||||
removed = copy.deepcopy(always_removed)
|
||||
if 0 < undefined_chance < 100:
|
||||
for group in [g for g in groups if g not in always_removed]:
|
||||
if group not in flipped_groups and random.randint(1, 100) > undefined_chance:
|
||||
removed.append(group)
|
||||
|
||||
# save shuffled tiles to list
|
||||
new_results = [[],[],[]]
|
||||
|
||||
@@ -108,6 +108,14 @@ These are now independent of retro mode and have three options: None, Random, an
|
||||
* Bonk Fairy (Dark)
|
||||
|
||||
# Bug Fixes and Notes
|
||||
* 1.2.0.10u
|
||||
* Fixed overrun issues with edge transitions
|
||||
* Better support for customized start_inventory with dungeon items
|
||||
* Colorized pots now available with lottery. Default is on.
|
||||
* Dungeon_only support pottery
|
||||
* Fix AllowAccidentalGlitches flag in OWG
|
||||
* Potential fix for mirror portal and entering cave on same frame
|
||||
* A few other minor issues, generation and graphical
|
||||
* 1.2.0.9-u
|
||||
* Disallowed standard exits (due to ER) are now graphically half blocked instead of missing
|
||||
* Graphical issues with Sanctuary and Swamp Hub lobbies are fixed
|
||||
|
||||
9
Rom.py
9
Rom.py
@@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127
|
||||
|
||||
|
||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||
RANDOMIZERBASEHASH = '695fe1ac7794b8fc2383454eb15cbdd5'
|
||||
RANDOMIZERBASEHASH = '7043e64e74b2452367de7cc146873524'
|
||||
|
||||
|
||||
class JsonRom(object):
|
||||
@@ -1586,7 +1586,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
||||
rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills
|
||||
rom.write_byte(0x1800A4, 0x01 if world.logic[player] != 'nologic' else 0x00) # enable POD EG fix
|
||||
rom.write_byte(0x180042, 0x01 if world.save_and_quit_from_boss else 0x00) # Allow Save and Quit after boss kill
|
||||
rom.write_byte(0x180358, 0x01 if world.logic[player] in ['owglitches', 'nologic'] else 0x00)
|
||||
rom.write_byte(0x180358, 0x01 if (world.logic[player] in ['owglitches', 'nologic']) else 0x00)
|
||||
|
||||
# remove shield from uncle
|
||||
rom.write_bytes(0x6D253, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
|
||||
@@ -1689,9 +1689,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
||||
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']))
|
||||
colorize_pots = (world.pottery[player] != 'vanilla'
|
||||
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)
|
||||
|
||||
2
Rules.py
2
Rules.py
@@ -1851,6 +1851,8 @@ def eval_small_key_door_main(state, door_name, dungeon, player):
|
||||
if state.is_door_open(door_name, player):
|
||||
return True
|
||||
key_logic = state.world.key_logic[player][dungeon]
|
||||
if door_name not in key_logic.door_rules:
|
||||
return False
|
||||
door_rule = key_logic.door_rules[door_name]
|
||||
door_openable = False
|
||||
for ruleType, number in door_rule.new_rules.items():
|
||||
|
||||
Binary file not shown.
@@ -86,7 +86,30 @@ You may define an item, and a list of locations. The locations may be weighted i
|
||||
#### NotPlacementGroup
|
||||
|
||||
You may define an item and a list of locations that an item should not be placed at. This will apply to all items of that type. The logic is considered for this. If it is otherwise impossible, the item will be considered for the listed locations. This is important for small key layouts mostly, but it will try other locations first.
|
||||
|
||||
|
||||
### ow-tileflips
|
||||
|
||||
This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_mixed: true` in the `settings` section in order for any values here to take effect. This section has three primary subsections: `force_flip`, `force_no_flip`, and `undefined`.
|
||||
|
||||
#### force_flip / force_no_flip
|
||||
|
||||
`force_flip` and `force_no_flip` should be used for tiles you want to flip or not flip. These sections are optional but must contain a list of OW Screen IDs. It is common to reference OW Screen IDs in hexadecimal (altho decimal is okay to use, if preferred), which range from:
|
||||
0x00 to 0x3f - Light World
|
||||
0x40 to 0x7f - Dark World
|
||||
0x80 - Pedestal/Hobo
|
||||
0x81 - Zoras Domain
|
||||
|
||||
Here is an example which forces Links House and Sanctuary screens to stay in their original worlds. Note: It is unnecessary to supply both worlds' IDs. Links House is 0x2c and Big Bomb Shop is 0x6c.
|
||||
```
|
||||
force_no_flip:
|
||||
- 0x2c
|
||||
- 0x13
|
||||
```
|
||||
|
||||
#### undefined_chance
|
||||
|
||||
`undefined_chance` should be used to determine how to handle all the remaining tiles that aren't explicitly defined in the earlier step. This represents the percent chance a tile will flip. This value can be set from 0 to 100 (default is 50). A value of 0 means there is a 0% chance it will be flipped.
|
||||
|
||||
### entrances
|
||||
|
||||
This must be defined by player. Each player number should be listed with the appropriate sections. This section has three primary subsections: `entrances`, `exits`, and `two-way`.
|
||||
@@ -111,10 +134,6 @@ This must be defined by player. Each player number should be listed with the app
|
||||
|
||||
`Chicken House: Kakariko Shop` if you walk into Chicken House door, you will in the Kakariko Shop.
|
||||
|
||||
##### Known Issues
|
||||
|
||||
Chris Houlihan and Links House should be specified together or not at all.
|
||||
|
||||
### doors
|
||||
|
||||
This must be defined by player. Each player number should be listed with the appropriate sections. This section has three primary subsections: `lobbies` and `doors`.
|
||||
|
||||
@@ -61,6 +61,14 @@ placements:
|
||||
Palace of Darkness - Big Chest: Hammer
|
||||
Capacity Upgrade - Left: Moon Pearl
|
||||
Turtle Rock - Pokey 2 Key Drop: Ice Rod
|
||||
ow-tileflips:
|
||||
1:
|
||||
force_flip:
|
||||
- 0x1b
|
||||
force_no_flip:
|
||||
- 0x2c
|
||||
- 0x18
|
||||
undefined_chance: 50
|
||||
entrances:
|
||||
1:
|
||||
entrances:
|
||||
|
||||
10
docs/standardinverted.yaml
Normal file
10
docs/standardinverted.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
meta:
|
||||
players: 1
|
||||
settings:
|
||||
1:
|
||||
mode: standard
|
||||
ow_mixed: true
|
||||
ow-tileflips:
|
||||
1:
|
||||
undefined_chance: 100
|
||||
|
||||
@@ -72,6 +72,7 @@ class CustomSettings(object):
|
||||
args.mystery = True
|
||||
else:
|
||||
settings = defaultdict(lambda: None, player_setting)
|
||||
args.ow_mixed[p] = get_setting(settings['ow_mixed'], args.ow_mixed[p])
|
||||
args.shuffle[p] = get_setting(settings['shuffle'], args.shuffle[p])
|
||||
args.door_shuffle[p] = get_setting(settings['door_shuffle'], args.door_shuffle[p])
|
||||
args.logic[p] = get_setting(settings['logic'], args.logic[p])
|
||||
@@ -176,6 +177,11 @@ class CustomSettings(object):
|
||||
return self.file_source['advanced_placements']
|
||||
return None
|
||||
|
||||
def get_owtileflips(self):
|
||||
if 'ow-tileflips' in self.file_source:
|
||||
return self.file_source['ow-tileflips']
|
||||
return None
|
||||
|
||||
def get_entrances(self):
|
||||
if 'entrances' in self.file_source:
|
||||
return self.file_source['entrances']
|
||||
|
||||
@@ -3,7 +3,7 @@ import logging
|
||||
from collections import defaultdict
|
||||
|
||||
from source.item.District import resolve_districts
|
||||
from BaseClasses import PotItem, PotFlags
|
||||
from BaseClasses import PotItem, PotFlags, LocationType
|
||||
from DoorShuffle import validate_vanilla_reservation
|
||||
from Dungeons import dungeon_table
|
||||
from Items import item_table, ItemFactory
|
||||
@@ -82,8 +82,8 @@ def create_item_pool_config(world):
|
||||
if pot.item not in [PotItem.Key, PotItem.Hole, PotItem.Switch]:
|
||||
item = pot_items[pot.item]
|
||||
descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
|
||||
location = f'{pot.room} {descriptor}'
|
||||
config.static_placement[player][item].append(location)
|
||||
loc = f'{pot.room} {descriptor}'
|
||||
config.static_placement[player][item].append(loc)
|
||||
if world.shopsanity[player]:
|
||||
for item, locs in shop_vanilla_mapping.items():
|
||||
config.static_placement[player][item].extend(locs)
|
||||
@@ -164,6 +164,10 @@ def create_item_pool_config(world):
|
||||
mode_grouping['Heart Containers'] + mode_grouping['GT Trash'] + mode_grouping['Small Keys'] +
|
||||
mode_grouping['Compasses'] + mode_grouping['Maps'] + mode_grouping['Key Drops'] +
|
||||
mode_grouping['Pot Keys'] + mode_grouping['Big Key Drops'])
|
||||
dungeon_set = set(dungeon_set)
|
||||
for loc in world.get_locations():
|
||||
if loc.parent_region.dungeon and loc.type in [LocationType.Pot, LocationType.Drop]:
|
||||
dungeon_set.add(loc.name)
|
||||
for player in range(1, world.players + 1):
|
||||
config.item_pool[player] = determine_major_items(world, player)
|
||||
config.location_groups[0].locations = set(dungeon_set)
|
||||
|
||||
@@ -442,14 +442,18 @@ def do_holes_and_linked_drops(entrances, exits, avail, cross_world, keep_togethe
|
||||
hole_targets.append((target_exit, target_drop))
|
||||
|
||||
random.shuffle(hole_entrances)
|
||||
if not cross_world and avail.is_sanc_forced_in_hc() and 'Sanctuary Grave' in holes_to_shuffle:
|
||||
lw_entrance = next(entrance for entrance in hole_entrances if entrance[0] in LW_Entrances)
|
||||
hole_entrances.remove(lw_entrance)
|
||||
if not cross_world and 'Sanctuary Grave' in holes_to_shuffle:
|
||||
hc = avail.world.get_entrance('Hyrule Castle Exit (South)', avail.player)
|
||||
if hc.connected_region and hc.connected_region.type == RegionType.DarkWorld:
|
||||
chosen_entrance = next(entrance for entrance in hole_entrances if entrance[0] in DW_Entrances)
|
||||
if not chosen_entrance:
|
||||
chosen_entrance = next(entrance for entrance in hole_entrances if entrance[0] in LW_Entrances)
|
||||
hole_entrances.remove(chosen_entrance)
|
||||
sanc_interior = next(target for target in hole_targets if target[0] == 'Sanctuary Exit')
|
||||
hole_targets.remove(sanc_interior)
|
||||
connect_two_way(lw_entrance[0], sanc_interior[0], avail) # two-way exit
|
||||
connect_entrance(lw_entrance[1], sanc_interior[1], avail) # hole
|
||||
remove_from_list(entrances, [lw_entrance[0], lw_entrance[1]])
|
||||
connect_two_way(chosen_entrance[0], sanc_interior[0], avail) # two-way exit
|
||||
connect_entrance(chosen_entrance[1], sanc_interior[1], avail) # hole
|
||||
remove_from_list(entrances, [chosen_entrance[0], chosen_entrance[1]])
|
||||
remove_from_list(exits, [sanc_interior[0], sanc_interior[1]])
|
||||
|
||||
random.shuffle(hole_targets)
|
||||
|
||||
@@ -111,7 +111,7 @@ def roll_settings(weights):
|
||||
ret.dropshuffle = get_choice('dropshuffle') == 'on' or keydropshuffle
|
||||
ret.pottery = get_choice('pottery') if 'pottery' in weights else 'none'
|
||||
ret.pottery = 'keys' if ret.pottery == 'none' and keydropshuffle else ret.pottery
|
||||
ret.colorizepots = get_choice('colorizepots') == 'on'
|
||||
ret.colorizepots = get_choice_default('colorizepots', default='on') == 'on'
|
||||
ret.shufflepots = get_choice('pot_shuffle') == 'on'
|
||||
ret.mixed_travel = get_choice('mixed_travel') if 'mixed_travel' in weights else 'prevent'
|
||||
ret.standardize_palettes = (get_choice('standardize_palettes') if 'standardize_palettes' in weights
|
||||
|
||||
Reference in New Issue
Block a user