feat: MW progresssion balancing tweaked to be percentage based instead of raw count. Tries to keep each player's locations in each sphere within 80% of the player with the most locations available. (Measured with percentage instead of raw count.) Old algo tried to keep everyone within 20 locations of each other. Difficult if one player has a lot more locations than another.
fix: Potential fix for early Trinexx start
This commit is contained in:
@@ -512,7 +512,7 @@ class World(object):
|
||||
if not sphere:
|
||||
# ran out of places and did not finish yet, quit
|
||||
if log_error:
|
||||
missing_locations = ", ".join([x.name for x in prog_locations])
|
||||
missing_locations = ", ".join([f'{x.name} (#{x.player})' for x in prog_locations])
|
||||
logging.getLogger('').error(f'Cannot reach the following locations: {missing_locations}')
|
||||
return False
|
||||
|
||||
@@ -547,7 +547,7 @@ class CollectionState(object):
|
||||
self.opened_doors = {player: set() for player in range(1, parent.players + 1)}
|
||||
self.dungeons_to_check = {player: defaultdict(dict) for player in range(1, parent.players + 1)}
|
||||
self.dungeon_limits = None
|
||||
self.placing_item = None
|
||||
self.placing_items = None
|
||||
# self.trace = None
|
||||
|
||||
def update_reachable_regions(self, player):
|
||||
@@ -833,7 +833,7 @@ class CollectionState(object):
|
||||
return door_candidates
|
||||
door_candidates, skip = [], set()
|
||||
if (state.world.accessibility[player] != 'locations' and remaining_keys == 0 and dungeon_name != 'Universal'
|
||||
and state.placing_item and state.placing_item.name == small_key_name):
|
||||
and state.placing_items and any(i.name == small_key_name and i.player == player for i in state.placing_items)):
|
||||
key_logic = state.world.key_logic[player][dungeon_name]
|
||||
for door, paired in key_logic.sm_doors.items():
|
||||
if door.name in key_logic.door_rules:
|
||||
@@ -878,7 +878,7 @@ class CollectionState(object):
|
||||
player: defaultdict(dict, {name: copy.copy(checklist)
|
||||
for name, checklist in self.dungeons_to_check[player].items()})
|
||||
for player in range(1, self.world.players + 1)}
|
||||
ret.placing_item = self.placing_item
|
||||
ret.placing_items = self.placing_items
|
||||
return ret
|
||||
|
||||
def apply_dungeon_exploration(self, rrp, player, dungeon_name, checklist):
|
||||
|
||||
46
Fill.py
46
Fill.py
@@ -3,6 +3,7 @@ import collections
|
||||
import itertools
|
||||
import logging
|
||||
import math
|
||||
from collections import Counter
|
||||
from contextlib import suppress
|
||||
|
||||
from BaseClasses import CollectionState, FillError, LocationType
|
||||
@@ -71,13 +72,13 @@ def fill_dungeons_restrictive(world, shuffled_locations):
|
||||
|
||||
def fill_restrictive(world, base_state, locations, itempool, key_pool=None, single_player_placement=False,
|
||||
vanilla=False):
|
||||
def sweep_from_pool(placing_item=None):
|
||||
def sweep_from_pool(placing_items=None):
|
||||
new_state = base_state.copy()
|
||||
for item in itempool:
|
||||
new_state.collect(item, True)
|
||||
new_state.placing_item = placing_item
|
||||
new_state.placing_items = placing_items
|
||||
new_state.sweep_for_events()
|
||||
new_state.placing_item = None
|
||||
new_state.placing_items = None
|
||||
return new_state
|
||||
|
||||
unplaced_items = []
|
||||
@@ -94,7 +95,7 @@ def fill_restrictive(world, base_state, locations, itempool, key_pool=None, sing
|
||||
while any(player_items.values()) and locations:
|
||||
items_to_place = [[itempool.remove(items[-1]), items.pop()][-1] for items in player_items.values() if items]
|
||||
|
||||
maximum_exploration_state = sweep_from_pool(placing_item=items_to_place[0])
|
||||
maximum_exploration_state = sweep_from_pool(placing_items=items_to_place)
|
||||
has_beaten_game = world.has_beaten_game(maximum_exploration_state)
|
||||
|
||||
for item_to_place in items_to_place:
|
||||
@@ -703,24 +704,44 @@ def balance_multiworld_progression(world):
|
||||
checked_locations = set()
|
||||
unchecked_locations = set(world.get_locations())
|
||||
|
||||
total_locations_count = Counter(location.player for location in world.get_locations() if not location.locked and not location.forced_item)
|
||||
|
||||
reachable_locations_count = {}
|
||||
for player in range(1, world.players + 1):
|
||||
reachable_locations_count[player] = 0
|
||||
sphere_num = 1
|
||||
moved_item_count = 0
|
||||
|
||||
def get_sphere_locations(sphere_state, locations):
|
||||
sphere_state.sweep_for_events(key_only=True, locations=locations)
|
||||
return {loc for loc in locations if sphere_state.can_reach(loc) and sphere_state.not_flooding_a_key(sphere_state.world, loc)}
|
||||
|
||||
def item_percentage(player, num):
|
||||
return num / total_locations_count[player]
|
||||
|
||||
while True:
|
||||
sphere_locations = get_sphere_locations(state, unchecked_locations)
|
||||
for location in sphere_locations:
|
||||
unchecked_locations.remove(location)
|
||||
reachable_locations_count[location.player] += 1
|
||||
if not location.locked and not location.forced_item:
|
||||
reachable_locations_count[location.player] += 1
|
||||
|
||||
logging.debug(f'Sphere {sphere_num}')
|
||||
logging.debug(f'Reachable locations: {reachable_locations_count}')
|
||||
debug_percentages = {
|
||||
player: round(item_percentage(player, num), 2)
|
||||
for player, num in reachable_locations_count.items()
|
||||
}
|
||||
logging.debug(f'Reachable percentages: {debug_percentages}\n')
|
||||
sphere_num += 1
|
||||
|
||||
if checked_locations:
|
||||
threshold = max(reachable_locations_count.values()) - 20
|
||||
max_percentage = max(map(lambda p: item_percentage(p, reachable_locations_count[p]), reachable_locations_count))
|
||||
threshold_percentages = {player: max_percentage * .8 for player in range(1, world.players + 1)}
|
||||
logging.debug(f'Thresholds: {threshold_percentages}')
|
||||
|
||||
balancing_players = {player for player, reachables in reachable_locations_count.items() if reachables < threshold}
|
||||
balancing_players = {player for player, reachables in reachable_locations_count.items()
|
||||
if item_percentage(player, reachables) < threshold_percentages[player]}
|
||||
if balancing_players:
|
||||
balancing_state = state.copy()
|
||||
balancing_unchecked_locations = unchecked_locations.copy()
|
||||
@@ -738,7 +759,8 @@ def balance_multiworld_progression(world):
|
||||
for location in balancing_sphere:
|
||||
balancing_unchecked_locations.remove(location)
|
||||
balancing_reachables[location.player] += 1
|
||||
if world.has_beaten_game(balancing_state) or all(reachables >= threshold for reachables in balancing_reachables.values()):
|
||||
if world.has_beaten_game(balancing_state) or all(item_percentage(player, reachables) >= threshold_percentages[player]
|
||||
for player, reachables in balancing_reachables.items()):
|
||||
break
|
||||
elif not balancing_sphere:
|
||||
raise RuntimeError('Not all required items reachable. Something went terribly wrong here.')
|
||||
@@ -765,7 +787,8 @@ def balance_multiworld_progression(world):
|
||||
items_to_replace.append(testing)
|
||||
else:
|
||||
reduced_sphere = get_sphere_locations(reducing_state, locations_to_test)
|
||||
if reachable_locations_count[player] + len(reduced_sphere) < threshold:
|
||||
p = item_percentage(player, reachable_locations_count[player] + len(reduced_sphere))
|
||||
if p < threshold_percentages[player]:
|
||||
items_to_replace.append(testing)
|
||||
|
||||
replaced_items = False
|
||||
@@ -790,6 +813,7 @@ def balance_multiworld_progression(world):
|
||||
new_location.event, old_location.event = True, False
|
||||
logging.debug(f"Progression balancing moved {new_location.item} to {new_location}, "
|
||||
f"displacing {old_location.item} into {old_location}")
|
||||
moved_item_count += 1
|
||||
state.collect(new_location.item, True, new_location)
|
||||
replaced_items = True
|
||||
break
|
||||
@@ -797,6 +821,7 @@ def balance_multiworld_progression(world):
|
||||
logging.warning(f"Could not Progression Balance {old_location.item}")
|
||||
|
||||
if replaced_items:
|
||||
logging.debug(f'Moved {moved_item_count} items so far\n')
|
||||
unlocked = {fresh for player in balancing_players for fresh in unlocked_locations[player]}
|
||||
for location in get_sphere_locations(state, unlocked):
|
||||
unchecked_locations.remove(location)
|
||||
@@ -811,7 +836,8 @@ def balance_multiworld_progression(world):
|
||||
if world.has_beaten_game(state):
|
||||
break
|
||||
elif not sphere_locations:
|
||||
raise RuntimeError('Not all required items reachable. Something went terribly wrong here.')
|
||||
logging.warning('Progression Balancing ran out of paths.')
|
||||
break
|
||||
|
||||
|
||||
def check_shop_swap(l):
|
||||
|
||||
2
Main.py
2
Main.py
@@ -37,7 +37,7 @@ from source.enemizer.DamageTables import DamageTable
|
||||
from source.enemizer.Enemizer import randomize_enemies
|
||||
from source.rom.DataTables import init_data_tables
|
||||
|
||||
version_number = '1.3.0.7'
|
||||
version_number = '1.3.0.8'
|
||||
version_branch = '-v'
|
||||
__version__ = f'{version_number}{version_branch}'
|
||||
|
||||
|
||||
@@ -141,6 +141,14 @@ These are now independent of retro mode and have three options: None, Random, an
|
||||
|
||||
# Bug Fixes and Notes
|
||||
|
||||
* 1.3.0.8v
|
||||
* Enemizer: Red Mimics correctly banned from challenge rooms in appropriate logic setting
|
||||
* No Logic Standard ER: Rain doors aren't blocked if no logic is enabled.
|
||||
* Trinexx: attempt to fix early start
|
||||
* MW Progression Balancing: Change to be percentage based instead of raw count. (80% threshold)
|
||||
* Take anys: Good Bee cave chosen as take any should no longer prevent generation
|
||||
* Money balancing: Fixed generation issue
|
||||
* Enemizer: various enemy bans
|
||||
* 1.3.0.7v
|
||||
* Fix for Mimic Cave enemy drops
|
||||
* Fix for Spectacle Rock Cave enemy drops (the mini-moldorms)
|
||||
|
||||
2
Rom.py
2
Rom.py
@@ -40,7 +40,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings
|
||||
|
||||
|
||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||
RANDOMIZERBASEHASH = '5661a616546e7dc0ee4bdfa9b152bc68'
|
||||
RANDOMIZERBASEHASH = '4d1f3e36e316077823a3e2eb5359ca17'
|
||||
|
||||
|
||||
class JsonRom(object):
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user