Allow locations to be prioritized for progress item placement (#189)
This commit is contained in:
parent
ab4fb6e69c
commit
ec95ce8329
|
@ -787,7 +787,7 @@ class Region(object):
|
||||||
self.type = type_
|
self.type = type_
|
||||||
self.entrances = []
|
self.entrances = []
|
||||||
self.exits = []
|
self.exits = []
|
||||||
self.locations = []
|
self.locations: List[Location] = []
|
||||||
self.dungeon = None
|
self.dungeon = None
|
||||||
self.shop = None
|
self.shop = None
|
||||||
self.world = world
|
self.world = world
|
||||||
|
@ -908,6 +908,11 @@ class Boss():
|
||||||
return f"Boss({self.name})"
|
return f"Boss({self.name})"
|
||||||
|
|
||||||
|
|
||||||
|
class LocationProgressType(Enum):
|
||||||
|
DEFAULT = 1
|
||||||
|
PRIORITY = 2
|
||||||
|
EXCLUDED = 3
|
||||||
|
|
||||||
class Location():
|
class Location():
|
||||||
# If given as integer, then this is the shop's inventory index
|
# If given as integer, then this is the shop's inventory index
|
||||||
shop_slot: Optional[int] = None
|
shop_slot: Optional[int] = None
|
||||||
|
@ -919,6 +924,7 @@ class Location():
|
||||||
show_in_spoiler: bool = True
|
show_in_spoiler: bool = True
|
||||||
excluded: bool = False
|
excluded: bool = False
|
||||||
crystal: bool = False
|
crystal: bool = False
|
||||||
|
progress_type: LocationProgressType = LocationProgressType.DEFAULT
|
||||||
always_allow = staticmethod(lambda item, state: False)
|
always_allow = staticmethod(lambda item, state: False)
|
||||||
access_rule = staticmethod(lambda state: True)
|
access_rule = staticmethod(lambda state: True)
|
||||||
item_rule = staticmethod(lambda item: True)
|
item_rule = staticmethod(lambda item: True)
|
||||||
|
|
78
Fill.py
78
Fill.py
|
@ -5,7 +5,7 @@ import itertools
|
||||||
from collections import Counter, deque
|
from collections import Counter, deque
|
||||||
|
|
||||||
|
|
||||||
from BaseClasses import CollectionState, Location, MultiWorld, Item
|
from BaseClasses import CollectionState, Location, LocationProgressType, MultiWorld, Item
|
||||||
from worlds.generic import PlandoItem
|
from worlds.generic import PlandoItem
|
||||||
from worlds.AutoWorld import call_all
|
from worlds.AutoWorld import call_all
|
||||||
|
|
||||||
|
@ -106,8 +106,19 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations,
|
||||||
def distribute_items_restrictive(world: MultiWorld, fill_locations=None):
|
def distribute_items_restrictive(world: MultiWorld, fill_locations=None):
|
||||||
# If not passed in, then get a shuffled list of locations to fill in
|
# If not passed in, then get a shuffled list of locations to fill in
|
||||||
if not fill_locations:
|
if not fill_locations:
|
||||||
fill_locations = world.get_unfilled_locations()
|
fill_locations = world.get_locations()
|
||||||
world.random.shuffle(fill_locations)
|
|
||||||
|
world.random.shuffle(fill_locations)
|
||||||
|
|
||||||
|
locations: dict[LocationProgressType, list[Location]] = {
|
||||||
|
type: [] for type in LocationProgressType}
|
||||||
|
|
||||||
|
for loc in fill_locations:
|
||||||
|
locations[loc.progress_type].append(loc)
|
||||||
|
|
||||||
|
prioritylocations = locations[LocationProgressType.PRIORITY]
|
||||||
|
defaultlocations = locations[LocationProgressType.DEFAULT]
|
||||||
|
excludedlocations = locations[LocationProgressType.EXCLUDED]
|
||||||
|
|
||||||
# get items to distribute
|
# get items to distribute
|
||||||
world.random.shuffle(world.itempool)
|
world.random.shuffle(world.itempool)
|
||||||
|
@ -129,21 +140,42 @@ def distribute_items_restrictive(world: MultiWorld, fill_locations=None):
|
||||||
else:
|
else:
|
||||||
restitempool.append(item)
|
restitempool.append(item)
|
||||||
|
|
||||||
world.random.shuffle(fill_locations)
|
call_all(world, "fill_hook", progitempool, nonexcludeditempool,
|
||||||
call_all(world, "fill_hook", progitempool, nonexcludeditempool, localrestitempool, nonlocalrestitempool, restitempool, fill_locations)
|
localrestitempool, nonlocalrestitempool, restitempool, fill_locations)
|
||||||
|
|
||||||
fill_restrictive(world, world.state, fill_locations, progitempool)
|
locationDeficit = len(progitempool) - len(prioritylocations)
|
||||||
|
if locationDeficit > 0:
|
||||||
|
if locationDeficit > len(defaultlocations):
|
||||||
|
raise FillError(
|
||||||
|
f'Not enough locations for advancement items. There are {len(progitempool)} advancement items with {len(prioritylocations)} priority locations and {len(defaultlocations)} default locations')
|
||||||
|
prioritylocations += defaultlocations[:locationDeficit]
|
||||||
|
defaultlocations = defaultlocations[locationDeficit:]
|
||||||
|
|
||||||
|
fill_restrictive(world, world.state, prioritylocations, progitempool)
|
||||||
|
if prioritylocations:
|
||||||
|
defaultlocations = prioritylocations + defaultlocations
|
||||||
|
|
||||||
|
if progitempool:
|
||||||
|
fill_restrictive(world, world.state, defaultlocations, progitempool)
|
||||||
|
|
||||||
if nonexcludeditempool:
|
if nonexcludeditempool:
|
||||||
world.random.shuffle(fill_locations)
|
world.random.shuffle(defaultlocations)
|
||||||
fill_restrictive(world, world.state, fill_locations, nonexcludeditempool) # needs logical fill to not conflict with local items
|
# needs logical fill to not conflict with local items
|
||||||
|
nonexcludeditempool, defaultlocations = fast_fill(
|
||||||
|
world, nonexcludeditempool, defaultlocations)
|
||||||
|
if(len(nonexcludeditempool) > 0):
|
||||||
|
raise FillError(
|
||||||
|
f'Not enough locations for non-excluded items. There are {len(nonexcludeditempool)} more items than locations')
|
||||||
|
|
||||||
|
defaultlocations = defaultlocations + excludedlocations
|
||||||
|
world.random.shuffle(defaultlocations)
|
||||||
|
|
||||||
if any(localrestitempool.values()): # we need to make sure some fills are limited to certain worlds
|
if any(localrestitempool.values()): # we need to make sure some fills are limited to certain worlds
|
||||||
local_locations = {player: [] for player in world.player_ids}
|
local_locations = {player: [] for player in world.player_ids}
|
||||||
for location in fill_locations:
|
for location in defaultlocations:
|
||||||
local_locations[location.player].append(location)
|
local_locations[location.player].append(location)
|
||||||
for locations in local_locations.values():
|
for player_locations in local_locations.values():
|
||||||
world.random.shuffle(locations)
|
world.random.shuffle(player_locations)
|
||||||
|
|
||||||
for player, items in localrestitempool.items(): # items already shuffled
|
for player, items in localrestitempool.items(): # items already shuffled
|
||||||
player_local_locations = local_locations[player]
|
player_local_locations = local_locations[player]
|
||||||
|
@ -154,24 +186,27 @@ def distribute_items_restrictive(world: MultiWorld, fill_locations=None):
|
||||||
break
|
break
|
||||||
spot_to_fill = player_local_locations.pop()
|
spot_to_fill = player_local_locations.pop()
|
||||||
world.push_item(spot_to_fill, item_to_place, False)
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
fill_locations.remove(spot_to_fill)
|
defaultlocations.remove(spot_to_fill)
|
||||||
|
|
||||||
for item_to_place in nonlocalrestitempool:
|
for item_to_place in nonlocalrestitempool:
|
||||||
for i, location in enumerate(fill_locations):
|
for i, location in enumerate(defaultlocations):
|
||||||
if location.player != item_to_place.player:
|
if location.player != item_to_place.player:
|
||||||
world.push_item(fill_locations.pop(i), item_to_place, False)
|
world.push_item(defaultlocations.pop(i), item_to_place, False)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
logging.warning(f"Could not place non_local_item {item_to_place} among {fill_locations}, tossing.")
|
logging.warning(
|
||||||
|
f"Could not place non_local_item {item_to_place} among {defaultlocations}, tossing.")
|
||||||
|
|
||||||
world.random.shuffle(fill_locations)
|
world.random.shuffle(defaultlocations)
|
||||||
|
|
||||||
restitempool, fill_locations = fast_fill(world, restitempool, fill_locations)
|
restitempool, defaultlocations = fast_fill(
|
||||||
|
world, restitempool, defaultlocations)
|
||||||
unplaced = progitempool + restitempool
|
unplaced = progitempool + restitempool
|
||||||
unfilled = [location.name for location in fill_locations]
|
unfilled = [location.name for location in defaultlocations]
|
||||||
|
|
||||||
if unplaced or unfilled:
|
if unplaced or unfilled:
|
||||||
logging.warning(f'Unplaced items({len(unplaced)}): {unplaced} - Unfilled Locations({len(unfilled)}): {unfilled}')
|
logging.warning(
|
||||||
|
f'Unplaced items({len(unplaced)}): {unplaced} - Unfilled Locations({len(unfilled)}): {unfilled}')
|
||||||
|
|
||||||
|
|
||||||
def fast_fill(world: MultiWorld, item_pool: typing.List, fill_locations: typing.List) -> typing.Tuple[typing.List, typing.List]:
|
def fast_fill(world: MultiWorld, item_pool: typing.List, fill_locations: typing.List) -> typing.Tuple[typing.List, typing.List]:
|
||||||
|
@ -279,7 +314,10 @@ def balance_multiworld_progression(world: MultiWorld):
|
||||||
balancing_state.collect(location.item, True, location)
|
balancing_state.collect(location.item, True, location)
|
||||||
player = location.item.player
|
player = location.item.player
|
||||||
# only replace items that end up in another player's world
|
# only replace items that end up in another player's world
|
||||||
if not location.locked and player in balancing_players and location.player != player:
|
if(not location.locked and
|
||||||
|
player in balancing_players and
|
||||||
|
location.player != player and
|
||||||
|
location.progress_type != LocationProgressType.PRIORITY):
|
||||||
candidate_items[player].add(location)
|
candidate_items[player].add(location)
|
||||||
balancing_sphere = get_sphere_locations(balancing_state, balancing_unchecked_locations)
|
balancing_sphere = get_sphere_locations(balancing_state, balancing_unchecked_locations)
|
||||||
for location in balancing_sphere:
|
for location in balancing_sphere:
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
from typing import NamedTuple, List
|
from typing import List
|
||||||
import unittest
|
import unittest
|
||||||
from worlds.AutoWorld import World
|
from worlds.AutoWorld import World
|
||||||
from Fill import FillError, fill_restrictive
|
from Fill import FillError, balance_multiworld_progression, fill_restrictive, distribute_items_restrictive
|
||||||
from BaseClasses import MultiWorld, Region, RegionType, Item, Location
|
from BaseClasses import Entrance, LocationProgressType, MultiWorld, Region, RegionType, Item, Location
|
||||||
from worlds.generic.Rules import set_rule
|
from worlds.generic.Rules import CollectionRule, set_rule
|
||||||
|
|
||||||
|
|
||||||
def generate_multi_world(players: int = 1) -> MultiWorld:
|
def generate_multi_world(players: int = 1) -> MultiWorld:
|
||||||
|
@ -19,31 +19,87 @@ def generate_multi_world(players: int = 1) -> MultiWorld:
|
||||||
"Menu Region Hint", player_id, multi_world)
|
"Menu Region Hint", player_id, multi_world)
|
||||||
multi_world.regions.append(region)
|
multi_world.regions.append(region)
|
||||||
|
|
||||||
multi_world.set_seed()
|
multi_world.set_seed(0)
|
||||||
multi_world.set_default_common_options()
|
multi_world.set_default_common_options()
|
||||||
|
|
||||||
return multi_world
|
return multi_world
|
||||||
|
|
||||||
|
|
||||||
class PlayerDefinition(NamedTuple):
|
class PlayerDefinition(object):
|
||||||
|
world: MultiWorld
|
||||||
id: int
|
id: int
|
||||||
menu: Region
|
menu: Region
|
||||||
locations: List[Location]
|
locations: List[Location]
|
||||||
prog_items: List[Item]
|
prog_items: List[Item]
|
||||||
|
basic_items: List[Item]
|
||||||
|
regions: List[Region]
|
||||||
|
|
||||||
|
def __init__(self, world: MultiWorld, id: int, menu: Region, locations: List[Location] = [], prog_items: List[Item] = [], basic_items: List[Item] = []):
|
||||||
|
self.world = world
|
||||||
|
self.id = id
|
||||||
|
self.menu = menu
|
||||||
|
self.locations = locations
|
||||||
|
self.prog_items = prog_items
|
||||||
|
self.basic_items = basic_items
|
||||||
|
self.regions = [menu]
|
||||||
|
|
||||||
|
def generate_region(self, parent: Region, size: int, access_rule: CollectionRule = lambda state: True) -> Region:
|
||||||
|
region_tag = "_region" + str(len(self.regions))
|
||||||
|
region_name = "player" + str(self.id) + region_tag
|
||||||
|
region = Region("player" + str(self.id) + region_tag, RegionType.Generic,
|
||||||
|
"Region Hint", self.id, self.world)
|
||||||
|
self.locations += generate_locations(size,
|
||||||
|
self.id, None, region, region_tag)
|
||||||
|
|
||||||
|
entrance = Entrance(self.id, region_name + "_entrance", parent)
|
||||||
|
parent.exits.append(entrance)
|
||||||
|
entrance.connect(region)
|
||||||
|
entrance.access_rule = access_rule
|
||||||
|
|
||||||
|
self.regions.append(region)
|
||||||
|
self.world.regions.append(region)
|
||||||
|
|
||||||
|
return region
|
||||||
|
|
||||||
|
|
||||||
def generate_player_data(multi_world: MultiWorld, player_id: int, location_count: int, prog_item_count: int) -> PlayerDefinition:
|
def fillRegion(world: MultiWorld, region: Region, items: List[Item]) -> List[Item]:
|
||||||
|
items = items.copy()
|
||||||
|
while len(items) > 0:
|
||||||
|
location = region.locations.pop(0)
|
||||||
|
region.locations.append(location)
|
||||||
|
if location.item:
|
||||||
|
return items
|
||||||
|
item = items.pop(0)
|
||||||
|
world.push_item(location, item, False)
|
||||||
|
location.event = item.advancement
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def regionContains(region: Region, item: Item) -> bool:
|
||||||
|
for location in region.locations:
|
||||||
|
if location.item == item:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def generate_player_data(multi_world: MultiWorld, player_id: int, location_count: int = 0, prog_item_count: int = 0, basic_item_count: int = 0) -> PlayerDefinition:
|
||||||
menu = multi_world.get_region("Menu", player_id)
|
menu = multi_world.get_region("Menu", player_id)
|
||||||
locations = generate_locations(location_count, player_id, None, menu)
|
locations = generate_locations(location_count, player_id, None, menu)
|
||||||
prog_items = generate_items(prog_item_count, player_id, True)
|
prog_items = generate_items(prog_item_count, player_id, True)
|
||||||
|
multi_world.itempool += prog_items
|
||||||
|
basic_items = generate_items(basic_item_count, player_id, False)
|
||||||
|
multi_world.itempool += basic_items
|
||||||
|
|
||||||
return PlayerDefinition(player_id, menu, locations, prog_items)
|
return PlayerDefinition(multi_world, player_id, menu, locations, prog_items, basic_items)
|
||||||
|
|
||||||
|
|
||||||
def generate_locations(count: int, player_id: int, address: int = None, region: Region = None) -> List[Location]:
|
def generate_locations(count: int, player_id: int, address: int = None, region: Region = None, tag: str = "") -> List[Location]:
|
||||||
locations = []
|
locations = []
|
||||||
|
prefix = "player" + str(player_id) + tag + "_location"
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
name = "player" + str(player_id) + "_location" + str(i)
|
name = prefix + str(i)
|
||||||
location = Location(player_id, name, address, region)
|
location = Location(player_id, name, address, region)
|
||||||
locations.append(location)
|
locations.append(location)
|
||||||
region.locations.append(location)
|
region.locations.append(location)
|
||||||
|
@ -52,14 +108,15 @@ def generate_locations(count: int, player_id: int, address: int = None, region:
|
||||||
|
|
||||||
def generate_items(count: int, player_id: int, advancement: bool = False, code: int = None) -> List[Item]:
|
def generate_items(count: int, player_id: int, advancement: bool = False, code: int = None) -> List[Item]:
|
||||||
items = []
|
items = []
|
||||||
|
type = "prog" if advancement else ""
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
name = "player" + str(player_id) + "_item" + str(i)
|
name = "player" + str(player_id) + "_" + type + "item" + str(i)
|
||||||
items.append(Item(name, advancement, code, player_id))
|
items.append(Item(name, advancement, code, player_id))
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
||||||
class TestBase(unittest.TestCase):
|
class TestFillRestrictive(unittest.TestCase):
|
||||||
def test_basic_fill_restrictive(self):
|
def test_basic_fill(self):
|
||||||
multi_world = generate_multi_world()
|
multi_world = generate_multi_world()
|
||||||
player1 = generate_player_data(multi_world, 1, 2, 2)
|
player1 = generate_player_data(multi_world, 1, 2, 2)
|
||||||
|
|
||||||
|
@ -76,7 +133,7 @@ class TestBase(unittest.TestCase):
|
||||||
self.assertEqual([], player1.locations)
|
self.assertEqual([], player1.locations)
|
||||||
self.assertEqual([], player1.prog_items)
|
self.assertEqual([], player1.prog_items)
|
||||||
|
|
||||||
def test_ordered_fill_restrictive(self):
|
def test_ordered_fill(self):
|
||||||
multi_world = generate_multi_world()
|
multi_world = generate_multi_world()
|
||||||
player1 = generate_player_data(multi_world, 1, 2, 2)
|
player1 = generate_player_data(multi_world, 1, 2, 2)
|
||||||
items = player1.prog_items
|
items = player1.prog_items
|
||||||
|
@ -92,7 +149,7 @@ class TestBase(unittest.TestCase):
|
||||||
self.assertEqual(locations[0].item, items[0])
|
self.assertEqual(locations[0].item, items[0])
|
||||||
self.assertEqual(locations[1].item, items[1])
|
self.assertEqual(locations[1].item, items[1])
|
||||||
|
|
||||||
def test_fill_restrictive_remaining_locations(self):
|
def test_partial_fill(self):
|
||||||
multi_world = generate_multi_world()
|
multi_world = generate_multi_world()
|
||||||
player1 = generate_player_data(multi_world, 1, 3, 2)
|
player1 = generate_player_data(multi_world, 1, 3, 2)
|
||||||
|
|
||||||
|
@ -106,7 +163,7 @@ class TestBase(unittest.TestCase):
|
||||||
item0.name, player1.id) and state.has(item1.name, player1.id)
|
item0.name, player1.id) and state.has(item1.name, player1.id)
|
||||||
set_rule(loc1, lambda state: state.has(
|
set_rule(loc1, lambda state: state.has(
|
||||||
item0.name, player1.id))
|
item0.name, player1.id))
|
||||||
#forces a swap
|
# forces a swap
|
||||||
set_rule(loc2, lambda state: state.has(
|
set_rule(loc2, lambda state: state.has(
|
||||||
item0.name, player1.id))
|
item0.name, player1.id))
|
||||||
fill_restrictive(multi_world, multi_world.state,
|
fill_restrictive(multi_world, multi_world.state,
|
||||||
|
@ -117,7 +174,7 @@ class TestBase(unittest.TestCase):
|
||||||
self.assertEqual(1, len(player1.locations))
|
self.assertEqual(1, len(player1.locations))
|
||||||
self.assertEqual(player1.locations[0], loc2)
|
self.assertEqual(player1.locations[0], loc2)
|
||||||
|
|
||||||
def test_minimal_fill_restrictive(self):
|
def test_minimal_fill(self):
|
||||||
multi_world = generate_multi_world()
|
multi_world = generate_multi_world()
|
||||||
player1 = generate_player_data(multi_world, 1, 2, 2)
|
player1 = generate_player_data(multi_world, 1, 2, 2)
|
||||||
|
|
||||||
|
@ -137,7 +194,7 @@ class TestBase(unittest.TestCase):
|
||||||
# Unnecessary unreachable Item
|
# Unnecessary unreachable Item
|
||||||
self.assertEqual(locations[1].item, items[0])
|
self.assertEqual(locations[1].item, items[0])
|
||||||
|
|
||||||
def test_reversed_fill_restrictive(self):
|
def test_reversed_fill(self):
|
||||||
multi_world = generate_multi_world()
|
multi_world = generate_multi_world()
|
||||||
player1 = generate_player_data(multi_world, 1, 2, 2)
|
player1 = generate_player_data(multi_world, 1, 2, 2)
|
||||||
|
|
||||||
|
@ -155,7 +212,7 @@ class TestBase(unittest.TestCase):
|
||||||
self.assertEqual(loc0.item, item1)
|
self.assertEqual(loc0.item, item1)
|
||||||
self.assertEqual(loc1.item, item0)
|
self.assertEqual(loc1.item, item0)
|
||||||
|
|
||||||
def test_multi_step_fill_restrictive(self):
|
def test_multi_step_fill(self):
|
||||||
multi_world = generate_multi_world()
|
multi_world = generate_multi_world()
|
||||||
player1 = generate_player_data(multi_world, 1, 4, 4)
|
player1 = generate_player_data(multi_world, 1, 4, 4)
|
||||||
|
|
||||||
|
@ -179,7 +236,7 @@ class TestBase(unittest.TestCase):
|
||||||
self.assertEqual(locations[2].item, items[0])
|
self.assertEqual(locations[2].item, items[0])
|
||||||
self.assertEqual(locations[3].item, items[3])
|
self.assertEqual(locations[3].item, items[3])
|
||||||
|
|
||||||
def test_impossible_fill_restrictive(self):
|
def test_impossible_fill(self):
|
||||||
multi_world = generate_multi_world()
|
multi_world = generate_multi_world()
|
||||||
player1 = generate_player_data(multi_world, 1, 2, 2)
|
player1 = generate_player_data(multi_world, 1, 2, 2)
|
||||||
items = player1.prog_items
|
items = player1.prog_items
|
||||||
|
@ -195,7 +252,7 @@ class TestBase(unittest.TestCase):
|
||||||
self.assertRaises(FillError, fill_restrictive, multi_world, multi_world.state,
|
self.assertRaises(FillError, fill_restrictive, multi_world, multi_world.state,
|
||||||
player1.locations.copy(), player1.prog_items.copy())
|
player1.locations.copy(), player1.prog_items.copy())
|
||||||
|
|
||||||
def test_circular_fill_restrictive(self):
|
def test_circular_fill(self):
|
||||||
multi_world = generate_multi_world()
|
multi_world = generate_multi_world()
|
||||||
player1 = generate_player_data(multi_world, 1, 3, 3)
|
player1 = generate_player_data(multi_world, 1, 3, 3)
|
||||||
|
|
||||||
|
@ -215,7 +272,7 @@ class TestBase(unittest.TestCase):
|
||||||
self.assertRaises(FillError, fill_restrictive, multi_world, multi_world.state,
|
self.assertRaises(FillError, fill_restrictive, multi_world, multi_world.state,
|
||||||
player1.locations.copy(), player1.prog_items.copy())
|
player1.locations.copy(), player1.prog_items.copy())
|
||||||
|
|
||||||
def test_competing_fill_restrictive(self):
|
def test_competing_fill(self):
|
||||||
multi_world = generate_multi_world()
|
multi_world = generate_multi_world()
|
||||||
player1 = generate_player_data(multi_world, 1, 2, 2)
|
player1 = generate_player_data(multi_world, 1, 2, 2)
|
||||||
|
|
||||||
|
@ -231,7 +288,7 @@ class TestBase(unittest.TestCase):
|
||||||
self.assertRaises(FillError, fill_restrictive, multi_world, multi_world.state,
|
self.assertRaises(FillError, fill_restrictive, multi_world, multi_world.state,
|
||||||
player1.locations.copy(), player1.prog_items.copy())
|
player1.locations.copy(), player1.prog_items.copy())
|
||||||
|
|
||||||
def test_multiplayer_fill_restrictive(self):
|
def test_multiplayer_fill(self):
|
||||||
multi_world = generate_multi_world(2)
|
multi_world = generate_multi_world(2)
|
||||||
player1 = generate_player_data(multi_world, 1, 2, 2)
|
player1 = generate_player_data(multi_world, 1, 2, 2)
|
||||||
player2 = generate_player_data(multi_world, 2, 2, 2)
|
player2 = generate_player_data(multi_world, 2, 2, 2)
|
||||||
|
@ -251,7 +308,7 @@ class TestBase(unittest.TestCase):
|
||||||
self.assertEqual(player2.locations[0].item, player1.prog_items[0])
|
self.assertEqual(player2.locations[0].item, player1.prog_items[0])
|
||||||
self.assertEqual(player2.locations[1].item, player2.prog_items[0])
|
self.assertEqual(player2.locations[1].item, player2.prog_items[0])
|
||||||
|
|
||||||
def test_multiplayer_rules_fill_restrictive(self):
|
def test_multiplayer_rules_fill(self):
|
||||||
multi_world = generate_multi_world(2)
|
multi_world = generate_multi_world(2)
|
||||||
player1 = generate_player_data(multi_world, 1, 2, 2)
|
player1 = generate_player_data(multi_world, 1, 2, 2)
|
||||||
player2 = generate_player_data(multi_world, 2, 2, 2)
|
player2 = generate_player_data(multi_world, 2, 2, 2)
|
||||||
|
@ -273,3 +330,225 @@ class TestBase(unittest.TestCase):
|
||||||
self.assertEqual(player1.locations[1].item, player2.prog_items[1])
|
self.assertEqual(player1.locations[1].item, player2.prog_items[1])
|
||||||
self.assertEqual(player2.locations[0].item, player1.prog_items[0])
|
self.assertEqual(player2.locations[0].item, player1.prog_items[0])
|
||||||
self.assertEqual(player2.locations[1].item, player1.prog_items[1])
|
self.assertEqual(player2.locations[1].item, player1.prog_items[1])
|
||||||
|
|
||||||
|
|
||||||
|
class TestDistributeItemsRestrictive(unittest.TestCase):
|
||||||
|
def test_basic_distribute(self):
|
||||||
|
multi_world = generate_multi_world()
|
||||||
|
player1 = generate_player_data(
|
||||||
|
multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||||
|
locations = player1.locations
|
||||||
|
prog_items = player1.prog_items
|
||||||
|
basic_items = player1.basic_items
|
||||||
|
|
||||||
|
distribute_items_restrictive(multi_world)
|
||||||
|
|
||||||
|
self.assertEqual(locations[0].item, basic_items[0])
|
||||||
|
self.assertEqual(locations[1].item, prog_items[0])
|
||||||
|
self.assertEqual(locations[2].item, prog_items[1])
|
||||||
|
self.assertEqual(locations[3].item, basic_items[1])
|
||||||
|
|
||||||
|
def test_excluded_distribute(self):
|
||||||
|
multi_world = generate_multi_world()
|
||||||
|
player1 = generate_player_data(
|
||||||
|
multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||||
|
locations = player1.locations
|
||||||
|
|
||||||
|
locations[1].progress_type = LocationProgressType.EXCLUDED
|
||||||
|
locations[2].progress_type = LocationProgressType.EXCLUDED
|
||||||
|
|
||||||
|
distribute_items_restrictive(multi_world)
|
||||||
|
|
||||||
|
self.assertFalse(locations[1].item.advancement)
|
||||||
|
self.assertFalse(locations[2].item.advancement)
|
||||||
|
|
||||||
|
def test_non_excluded_item_distribute(self):
|
||||||
|
multi_world = generate_multi_world()
|
||||||
|
player1 = generate_player_data(
|
||||||
|
multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||||
|
locations = player1.locations
|
||||||
|
basic_items = player1.basic_items
|
||||||
|
|
||||||
|
locations[1].progress_type = LocationProgressType.EXCLUDED
|
||||||
|
basic_items[1].never_exclude = True
|
||||||
|
|
||||||
|
distribute_items_restrictive(multi_world)
|
||||||
|
|
||||||
|
self.assertEqual(locations[1].item, basic_items[0])
|
||||||
|
|
||||||
|
def test_too_many_excluded_distribute(self):
|
||||||
|
multi_world = generate_multi_world()
|
||||||
|
player1 = generate_player_data(
|
||||||
|
multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||||
|
locations = player1.locations
|
||||||
|
|
||||||
|
locations[0].progress_type = LocationProgressType.EXCLUDED
|
||||||
|
locations[1].progress_type = LocationProgressType.EXCLUDED
|
||||||
|
locations[2].progress_type = LocationProgressType.EXCLUDED
|
||||||
|
|
||||||
|
self.assertRaises(FillError, distribute_items_restrictive, multi_world)
|
||||||
|
|
||||||
|
def test_non_excluded_item_must_distribute(self):
|
||||||
|
multi_world = generate_multi_world()
|
||||||
|
player1 = generate_player_data(
|
||||||
|
multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||||
|
locations = player1.locations
|
||||||
|
basic_items = player1.basic_items
|
||||||
|
|
||||||
|
locations[1].progress_type = LocationProgressType.EXCLUDED
|
||||||
|
locations[2].progress_type = LocationProgressType.EXCLUDED
|
||||||
|
basic_items[0].never_exclude = True
|
||||||
|
basic_items[1].never_exclude = True
|
||||||
|
|
||||||
|
self.assertRaises(FillError, distribute_items_restrictive, multi_world)
|
||||||
|
|
||||||
|
def test_priority_distribute(self):
|
||||||
|
multi_world = generate_multi_world()
|
||||||
|
player1 = generate_player_data(
|
||||||
|
multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||||
|
locations = player1.locations
|
||||||
|
|
||||||
|
locations[0].progress_type = LocationProgressType.PRIORITY
|
||||||
|
locations[3].progress_type = LocationProgressType.PRIORITY
|
||||||
|
|
||||||
|
distribute_items_restrictive(multi_world)
|
||||||
|
|
||||||
|
self.assertTrue(locations[0].item.advancement)
|
||||||
|
self.assertTrue(locations[3].item.advancement)
|
||||||
|
|
||||||
|
def test_excess_priority_distribute(self):
|
||||||
|
multi_world = generate_multi_world()
|
||||||
|
player1 = generate_player_data(
|
||||||
|
multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||||
|
locations = player1.locations
|
||||||
|
|
||||||
|
locations[0].progress_type = LocationProgressType.PRIORITY
|
||||||
|
locations[1].progress_type = LocationProgressType.PRIORITY
|
||||||
|
locations[2].progress_type = LocationProgressType.PRIORITY
|
||||||
|
|
||||||
|
distribute_items_restrictive(multi_world)
|
||||||
|
|
||||||
|
self.assertFalse(locations[3].item.advancement)
|
||||||
|
|
||||||
|
def test_multiple_world_distribute(self):
|
||||||
|
multi_world = generate_multi_world(3)
|
||||||
|
player1 = generate_player_data(
|
||||||
|
multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||||
|
player2 = generate_player_data(
|
||||||
|
multi_world, 2, 4, prog_item_count=1, basic_item_count=3)
|
||||||
|
player3 = generate_player_data(
|
||||||
|
multi_world, 3, 6, prog_item_count=4, basic_item_count=2)
|
||||||
|
|
||||||
|
distribute_items_restrictive(multi_world)
|
||||||
|
|
||||||
|
self.assertEqual(player1.locations[0].item, player1.prog_items[1])
|
||||||
|
self.assertEqual(player1.locations[1].item, player3.prog_items[2])
|
||||||
|
self.assertEqual(player1.locations[2].item, player3.prog_items[1])
|
||||||
|
self.assertEqual(player1.locations[3].item, player2.prog_items[0])
|
||||||
|
|
||||||
|
self.assertEqual(player2.locations[0].item, player1.basic_items[0])
|
||||||
|
self.assertEqual(player2.locations[1].item, player2.basic_items[1])
|
||||||
|
self.assertEqual(player2.locations[2].item, player3.basic_items[1])
|
||||||
|
self.assertEqual(player2.locations[3].item, player2.basic_items[0])
|
||||||
|
|
||||||
|
self.assertEqual(player3.locations[0].item, player1.basic_items[1])
|
||||||
|
self.assertEqual(player3.locations[1].item, player3.prog_items[3])
|
||||||
|
self.assertEqual(player3.locations[2].item, player1.prog_items[0])
|
||||||
|
self.assertEqual(player3.locations[3].item, player3.basic_items[0])
|
||||||
|
self.assertEqual(player3.locations[4].item, player3.prog_items[0])
|
||||||
|
self.assertEqual(player3.locations[5].item, player2.basic_items[2])
|
||||||
|
|
||||||
|
def test_multiple_world_priority_distribute(self):
|
||||||
|
multi_world = generate_multi_world(3)
|
||||||
|
player1 = generate_player_data(
|
||||||
|
multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
|
||||||
|
player2 = generate_player_data(
|
||||||
|
multi_world, 2, 4, prog_item_count=1, basic_item_count=3)
|
||||||
|
player3 = generate_player_data(
|
||||||
|
multi_world, 3, 6, prog_item_count=4, basic_item_count=2)
|
||||||
|
|
||||||
|
player1.locations[2].progress_type = LocationProgressType.PRIORITY
|
||||||
|
player1.locations[3].progress_type = LocationProgressType.PRIORITY
|
||||||
|
|
||||||
|
player2.locations[1].progress_type = LocationProgressType.PRIORITY
|
||||||
|
|
||||||
|
player3.locations[0].progress_type = LocationProgressType.PRIORITY
|
||||||
|
player3.locations[1].progress_type = LocationProgressType.PRIORITY
|
||||||
|
player3.locations[2].progress_type = LocationProgressType.PRIORITY
|
||||||
|
player3.locations[3].progress_type = LocationProgressType.PRIORITY
|
||||||
|
|
||||||
|
distribute_items_restrictive(multi_world)
|
||||||
|
|
||||||
|
self.assertTrue(player1.locations[2].item.advancement)
|
||||||
|
self.assertTrue(player1.locations[3].item.advancement)
|
||||||
|
self.assertTrue(player2.locations[1].item.advancement)
|
||||||
|
self.assertTrue(player3.locations[0].item.advancement)
|
||||||
|
self.assertTrue(player3.locations[1].item.advancement)
|
||||||
|
self.assertTrue(player3.locations[2].item.advancement)
|
||||||
|
self.assertTrue(player3.locations[3].item.advancement)
|
||||||
|
|
||||||
|
|
||||||
|
class TestBalanceMultiworldProgression(unittest.TestCase):
|
||||||
|
def assertRegionContains(self, region: Region, item: Item):
|
||||||
|
for location in region.locations:
|
||||||
|
if location.item and location.item == item:
|
||||||
|
return True
|
||||||
|
|
||||||
|
self.fail("Expected " + region.name + " to contain " + item.name +
|
||||||
|
"\n Contains" + str(list(map(lambda location: location.item, region.locations))))
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
multi_world = generate_multi_world(2)
|
||||||
|
self.multi_world = multi_world
|
||||||
|
player1 = generate_player_data(
|
||||||
|
multi_world, 1, prog_item_count=2, basic_item_count=40)
|
||||||
|
self.player1 = player1
|
||||||
|
player2 = generate_player_data(
|
||||||
|
multi_world, 2, prog_item_count=2, basic_item_count=40)
|
||||||
|
self.player2 = player2
|
||||||
|
|
||||||
|
multi_world.completion_condition[player1.id] = lambda state: state.has(
|
||||||
|
player1.prog_items[0].name, player1.id) and state.has(
|
||||||
|
player1.prog_items[1].name, player1.id)
|
||||||
|
multi_world.completion_condition[player2.id] = lambda state: state.has(
|
||||||
|
player2.prog_items[0].name, player2.id) and state.has(
|
||||||
|
player2.prog_items[1].name, player2.id)
|
||||||
|
|
||||||
|
items = player1.basic_items + player2.basic_items
|
||||||
|
|
||||||
|
# Sphere 1
|
||||||
|
region = player1.generate_region(player1.menu, 20)
|
||||||
|
items = fillRegion(multi_world, region, [
|
||||||
|
player1.prog_items[0]] + items)
|
||||||
|
|
||||||
|
# Sphere 2
|
||||||
|
region = player1.generate_region(
|
||||||
|
player1.regions[1], 20, lambda state: state.has(player1.prog_items[0].name, player1.id))
|
||||||
|
items = fillRegion(
|
||||||
|
multi_world, region, [player1.prog_items[1], player2.prog_items[0]] + items)
|
||||||
|
|
||||||
|
# Sphere 3
|
||||||
|
region = player2.generate_region(
|
||||||
|
player2.menu, 20, lambda state: state.has(player2.prog_items[0].name, player2.id))
|
||||||
|
items = fillRegion(multi_world, region, [
|
||||||
|
player2.prog_items[1]] + items)
|
||||||
|
|
||||||
|
multi_world.progression_balancing[player1.id] = True
|
||||||
|
multi_world.progression_balancing[player2.id] = True
|
||||||
|
|
||||||
|
def test_balances_progression(self):
|
||||||
|
self.assertRegionContains(
|
||||||
|
self.player1.regions[2], self.player2.prog_items[0])
|
||||||
|
|
||||||
|
balance_multiworld_progression(self.multi_world)
|
||||||
|
|
||||||
|
self.assertRegionContains(
|
||||||
|
self.player1.regions[1], self.player2.prog_items[0])
|
||||||
|
|
||||||
|
def test_ignores_priority_locations(self):
|
||||||
|
self.player2.prog_items[0].location.progress_type = LocationProgressType.PRIORITY
|
||||||
|
|
||||||
|
balance_multiworld_progression(self.multi_world)
|
||||||
|
|
||||||
|
self.assertRegionContains(
|
||||||
|
self.player1.regions[2], self.player2.prog_items[0])
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
from BaseClasses import LocationProgressType
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
import BaseClasses
|
import BaseClasses
|
||||||
|
|
||||||
|
@ -31,6 +33,7 @@ def exclusion_rules(world, player: int, exclude_locations: typing.Set[str]):
|
||||||
else:
|
else:
|
||||||
add_item_rule(location, lambda i: not (i.advancement or i.never_exclude))
|
add_item_rule(location, lambda i: not (i.advancement or i.never_exclude))
|
||||||
location.excluded = True
|
location.excluded = True
|
||||||
|
location.progress_type = LocationProgressType.EXCLUDED
|
||||||
|
|
||||||
def set_rule(spot, rule: CollectionRule):
|
def set_rule(spot, rule: CollectionRule):
|
||||||
spot.access_rule = rule
|
spot.access_rule = rule
|
||||||
|
|
Loading…
Reference in New Issue