mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 12:11:33 -06:00
Multiworld core implementation By Bonta0
Does not include the server/client code or the rom writes specific to it. Indeed it cannot write multiworld roms at all right now, pending addition future updates to support the official ALTTPR Multiworld client. Includes some GUI changes by Alaszun Co-authored-by: Alaszun <koelze@google.com>
This commit is contained in:

committed by
Kevin Cathcart

parent
d44d194de7
commit
1a62b1da28
162
Fill.py
162
Fill.py
@@ -1,6 +1,9 @@
|
||||
import random
|
||||
import logging
|
||||
|
||||
from BaseClasses import CollectionState
|
||||
|
||||
|
||||
class FillError(RuntimeError):
|
||||
pass
|
||||
|
||||
@@ -167,31 +170,41 @@ def fill_restrictive(world, base_state, locations, itempool):
|
||||
return new_state
|
||||
|
||||
while itempool and locations:
|
||||
item_to_place = itempool.pop()
|
||||
items_to_place = []
|
||||
nextpool = []
|
||||
placing_players = set()
|
||||
for item in reversed(itempool):
|
||||
if item.player not in placing_players:
|
||||
placing_players.add(item.player)
|
||||
items_to_place.append(item)
|
||||
else:
|
||||
nextpool.insert(0, item)
|
||||
itempool = nextpool
|
||||
|
||||
maximum_exploration_state = sweep_from_pool()
|
||||
|
||||
perform_access_check = True
|
||||
if world.check_beatable_only:
|
||||
perform_access_check = not world.has_beaten_game(maximum_exploration_state)
|
||||
|
||||
for item_to_place in items_to_place:
|
||||
spot_to_fill = None
|
||||
for location in locations:
|
||||
if location.can_fill(maximum_exploration_state, item_to_place, perform_access_check):
|
||||
spot_to_fill = location
|
||||
break
|
||||
|
||||
spot_to_fill = None
|
||||
for location in locations:
|
||||
if location.can_fill(maximum_exploration_state, item_to_place, perform_access_check):
|
||||
spot_to_fill = location
|
||||
break
|
||||
if spot_to_fill is None:
|
||||
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
||||
if world.can_beat_game():
|
||||
if not world.check_beatable_only:
|
||||
logging.getLogger('').warning('Not all items placed. Game beatable anyway. (Could not place %s)' % item_to_place)
|
||||
continue
|
||||
raise FillError('No more spots to place %s' % item_to_place)
|
||||
|
||||
if spot_to_fill is None:
|
||||
# we filled all reachable spots. Maybe the game can be beaten anyway?
|
||||
if world.can_beat_game():
|
||||
if not world.check_beatable_only:
|
||||
logging.getLogger('').warning('Not all items placed. Game beatable anyway.')
|
||||
break
|
||||
raise FillError('No more spots to place %s' % item_to_place)
|
||||
|
||||
world.push_item(spot_to_fill, item_to_place, False)
|
||||
locations.remove(spot_to_fill)
|
||||
spot_to_fill.event = True
|
||||
world.push_item(spot_to_fill, item_to_place, False)
|
||||
locations.remove(spot_to_fill)
|
||||
spot_to_fill.event = True
|
||||
|
||||
|
||||
def distribute_items_restrictive(world, gftower_trash_count=0, fill_locations=None):
|
||||
@@ -207,20 +220,25 @@ def distribute_items_restrictive(world, gftower_trash_count=0, fill_locations=No
|
||||
restitempool = [item for item in world.itempool if not item.advancement and not item.priority]
|
||||
|
||||
# fill in gtower locations with trash first
|
||||
if world.ganonstower_vanilla:
|
||||
gtower_locations = [location for location in fill_locations if 'Ganons Tower' in location.name]
|
||||
random.shuffle(gtower_locations)
|
||||
trashcnt = 0
|
||||
while gtower_locations and restitempool and trashcnt < gftower_trash_count:
|
||||
spot_to_fill = gtower_locations.pop()
|
||||
item_to_place = restitempool.pop()
|
||||
world.push_item(spot_to_fill, item_to_place, False)
|
||||
fill_locations.remove(spot_to_fill)
|
||||
trashcnt += 1
|
||||
for player in range(1, world.players + 1):
|
||||
if world.ganonstower_vanilla[player]:
|
||||
gtower_locations = [location for location in fill_locations if 'Ganons Tower' in location.name and location.player == player]
|
||||
random.shuffle(gtower_locations)
|
||||
trashcnt = 0
|
||||
while gtower_locations and restitempool and trashcnt < gftower_trash_count:
|
||||
spot_to_fill = gtower_locations.pop()
|
||||
item_to_place = restitempool.pop()
|
||||
world.push_item(spot_to_fill, item_to_place, False)
|
||||
fill_locations.remove(spot_to_fill)
|
||||
trashcnt += 1
|
||||
|
||||
random.shuffle(fill_locations)
|
||||
fill_locations.reverse()
|
||||
|
||||
# Make sure the escape small key is placed first in standard keysanity to prevent running out of spots
|
||||
if world.keysanity and world.mode == 'standard':
|
||||
progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' else 0)
|
||||
|
||||
fill_restrictive(world, world.state, fill_locations, progitempool)
|
||||
|
||||
random.shuffle(fill_locations)
|
||||
@@ -297,3 +315,93 @@ def flood_items(world):
|
||||
world.push_item(location, item_to_place, True)
|
||||
itempool.remove(item_to_place)
|
||||
break
|
||||
|
||||
def balance_multiworld_progression(world):
|
||||
state = CollectionState(world)
|
||||
checked_locations = []
|
||||
unchecked_locations = world.get_locations().copy()
|
||||
random.shuffle(unchecked_locations)
|
||||
|
||||
reachable_locations_count = {}
|
||||
for player in range(1, world.players + 1):
|
||||
reachable_locations_count[player] = 0
|
||||
|
||||
def get_sphere_locations(sphere_state, locations):
|
||||
if not world.keysanity:
|
||||
sphere_state.sweep_for_events(key_only=True, locations=locations)
|
||||
return [loc for loc in locations if sphere_state.can_reach(loc)]
|
||||
|
||||
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 checked_locations:
|
||||
average_reachable_locations = sum(reachable_locations_count.values()) / world.players
|
||||
threshold = ((average_reachable_locations + max(reachable_locations_count.values())) / 2) * 0.8 #todo: probably needs some tweaking
|
||||
|
||||
balancing_players = [player for player, reachables in reachable_locations_count.items() if reachables < threshold]
|
||||
if balancing_players:
|
||||
balancing_state = state.copy()
|
||||
balancing_unchecked_locations = unchecked_locations.copy()
|
||||
balancing_reachables = reachable_locations_count.copy()
|
||||
balancing_sphere = sphere_locations.copy()
|
||||
candidate_items = []
|
||||
while True:
|
||||
for location in balancing_sphere:
|
||||
if location.event:
|
||||
balancing_state.collect(location.item, True, location)
|
||||
if location.item.player in balancing_players:
|
||||
candidate_items.append(location)
|
||||
balancing_sphere = get_sphere_locations(balancing_state, balancing_unchecked_locations)
|
||||
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()]):
|
||||
break
|
||||
|
||||
unlocked_locations = [l for l in unchecked_locations if l not in balancing_unchecked_locations]
|
||||
items_to_replace = []
|
||||
for player in balancing_players:
|
||||
locations_to_test = [l for l in unlocked_locations if l.player == player]
|
||||
items_to_test = [l for l in candidate_items if l.item.player == player and l.player != player]
|
||||
while items_to_test:
|
||||
testing = items_to_test.pop()
|
||||
reducing_state = state.copy()
|
||||
for location in [*[l for l in items_to_replace if l.item.player == player], *items_to_test]:
|
||||
reducing_state.collect(location.item, True, location)
|
||||
|
||||
reducing_state.sweep_for_events(locations=locations_to_test)
|
||||
|
||||
if world.has_beaten_game(balancing_state):
|
||||
if not world.has_beaten_game(reducing_state):
|
||||
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:
|
||||
items_to_replace.append(testing)
|
||||
|
||||
replaced_items = False
|
||||
locations_for_replacing = [l for l in checked_locations if not l.event]
|
||||
while locations_for_replacing and items_to_replace:
|
||||
new_location = locations_for_replacing.pop()
|
||||
old_location = items_to_replace.pop()
|
||||
new_location.item, old_location.item = old_location.item, new_location.item
|
||||
new_location.event = True
|
||||
old_location.event = False
|
||||
state.collect(new_location.item, True, new_location)
|
||||
replaced_items = True
|
||||
if replaced_items:
|
||||
for location in get_sphere_locations(state, [l for l in unlocked_locations if l.player in balancing_players]):
|
||||
unchecked_locations.remove(location)
|
||||
reachable_locations_count[location.player] += 1
|
||||
sphere_locations.append(location)
|
||||
|
||||
for location in sphere_locations:
|
||||
if location.event:
|
||||
state.collect(location.item, True, location)
|
||||
checked_locations.extend(sphere_locations)
|
||||
|
||||
if world.has_beaten_game(state):
|
||||
break
|
||||
|
Reference in New Issue
Block a user