SMZ3: Fix forced fill behaviors (GT junk fill, initial Super/PB front fill) (#5361)
* SMZ3: Make GT fill behave like upstream SMZ3 multiworld GT fill This means: All items local, 50% guaranteed filler, followed by possible useful items, never progression. * Fix item links * SMZ3: Ensure in all cases, we remove the right item from the pool Previously front fill would cause erratic errors on frozen, with the cause immediately revealed by, on source, tripping the assert that was added in #5109 * SMZ3: Truly, *properly* fix GT junk fill After hours of diving deep into the upstream SMZ3 randomizer, it finally behaves identically to how it does there
This commit is contained in:
@@ -424,7 +424,6 @@ class Item:
|
|||||||
]
|
]
|
||||||
|
|
||||||
for item in itemPool:
|
for item in itemPool:
|
||||||
item.Progression = True
|
|
||||||
item.World = world
|
item.World = world
|
||||||
|
|
||||||
return itemPool
|
return itemPool
|
||||||
@@ -439,7 +438,6 @@ class Item:
|
|||||||
]
|
]
|
||||||
|
|
||||||
for item in itemPool:
|
for item in itemPool:
|
||||||
item.Progression = True
|
|
||||||
item.World = world
|
item.World = world
|
||||||
|
|
||||||
return itemPool
|
return itemPool
|
||||||
|
|||||||
@@ -145,8 +145,12 @@ class GanonsTower(Z3Region):
|
|||||||
|
|
||||||
def CanFill(self, item: Item):
|
def CanFill(self, item: Item):
|
||||||
if (self.Config.Multiworld):
|
if (self.Config.Multiworld):
|
||||||
|
# changed for AP becuase upstream only uses CanFill for filling progression-related items
|
||||||
|
# note that item.Progression does not include all items with progression classification
|
||||||
# item.World will be None for item created by create_item for item links
|
# item.World will be None for item created by create_item for item links
|
||||||
if (item.World is not None and (item.World != self.world or item.Progression)):
|
if (item.World is not None and item.World != self.world and (item.Progression or item.IsDungeonItem() or item.IsKeycard() or item.IsSmMap())):
|
||||||
|
return False
|
||||||
|
if (item.World is not None and item.World == self.world and item.Progression):
|
||||||
return False
|
return False
|
||||||
if (self.Config.Keysanity and not ((item.Type == ItemType.BigKeyGT or item.Type == ItemType.KeyGT) and item.World == self.world) and (item.IsKey() or item.IsBigKey() or item.IsKeycard())):
|
if (self.Config.Keysanity and not ((item.Type == ItemType.BigKeyGT or item.Type == ItemType.KeyGT) and item.World == self.world) and (item.IsKey() or item.IsBigKey() or item.IsKeycard())):
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -260,13 +260,19 @@ class SMZ3World(World):
|
|||||||
l.always_allow = lambda state, item, loc=loc: \
|
l.always_allow = lambda state, item, loc=loc: \
|
||||||
item.game == "SMZ3" and \
|
item.game == "SMZ3" and \
|
||||||
loc.alwaysAllow(item.item, state.smz3state[self.player])
|
loc.alwaysAllow(item.item, state.smz3state[self.player])
|
||||||
old_rule = l.item_rule
|
l.item_rule = lambda item, loc=loc, region=region, old_rule=l.item_rule: (\
|
||||||
l.item_rule = lambda item, loc=loc, region=region: (\
|
|
||||||
item.game != "SMZ3" or \
|
item.game != "SMZ3" or \
|
||||||
loc.allow(item.item, None) and \
|
loc.allow(item.item, None) and \
|
||||||
region.CanFill(item.item)) and old_rule(item)
|
region.CanFill(item.item)) and old_rule(item)
|
||||||
set_rule(l, lambda state, loc=loc: loc.Available(state.smz3state[self.player]))
|
set_rule(l, lambda state, loc=loc: loc.Available(state.smz3state[self.player]))
|
||||||
|
|
||||||
|
# In multiworlds, GT is disallowed from having progression items.
|
||||||
|
# This item rule replicates this behavior for non-SMZ3 games
|
||||||
|
for loc in self.smz3World.GetRegion("Ganon's Tower").Locations:
|
||||||
|
l = self.locations[loc.Name]
|
||||||
|
l.item_rule = lambda item, old_rule=l.item_rule: \
|
||||||
|
(item.game == "SMZ3" or not item.advancement) and old_rule(item)
|
||||||
|
|
||||||
def create_regions(self):
|
def create_regions(self):
|
||||||
self.create_locations(self.player)
|
self.create_locations(self.player)
|
||||||
startRegion = self.create_region(self.multiworld, self.player, 'Menu')
|
startRegion = self.create_region(self.multiworld, self.player, 'Menu')
|
||||||
@@ -589,29 +595,18 @@ class SMZ3World(World):
|
|||||||
]))
|
]))
|
||||||
|
|
||||||
def JunkFillGT(self, factor):
|
def JunkFillGT(self, factor):
|
||||||
poolLength = len(self.multiworld.itempool)
|
junkPoolIdx = [idx for idx, i in enumerate(self.multiworld.itempool) if i.excludable]
|
||||||
junkPoolIdx = [i for i in range(0, poolLength)
|
self.random.shuffle(junkPoolIdx)
|
||||||
if self.multiworld.itempool[i].classification in (ItemClassification.filler, ItemClassification.trap)]
|
junkLocations = [loc for loc in self.locations.values() if loc.name in self.locationNamesGT and loc.item is None]
|
||||||
|
self.random.shuffle(junkLocations)
|
||||||
toRemove = []
|
toRemove = []
|
||||||
for loc in self.locations.values():
|
for loc in junkLocations:
|
||||||
# commenting this for now since doing a partial GT pre fill would allow for non SMZ3 progression in GT
|
# Note: Upstream GT junk fill uses FastFill, which ignores item rules
|
||||||
# which isnt desirable (SMZ3 logic only filters for SMZ3 items). Having progression in GT can only happen in Single Player.
|
if len(junkPoolIdx) == 0 or len(toRemove) >= int(len(junkLocations) * factor * self.smz3World.TowerCrystals / 7):
|
||||||
# if len(toRemove) >= int(len(self.locationNamesGT) * factor * self.smz3World.TowerCrystals / 7):
|
break
|
||||||
# break
|
itemFromPool = self.multiworld.itempool[junkPoolIdx[0]]
|
||||||
if loc.name in self.locationNamesGT and loc.item is None:
|
toRemove.append(junkPoolIdx.pop(0))
|
||||||
poolLength = len(junkPoolIdx)
|
loc.place_locked_item(itemFromPool)
|
||||||
# start looking at a random starting index and loop at start if no match found
|
|
||||||
start = self.multiworld.random.randint(0, poolLength)
|
|
||||||
itemFromPool = None
|
|
||||||
for off in range(0, poolLength):
|
|
||||||
i = (start + off) % poolLength
|
|
||||||
candidate = self.multiworld.itempool[junkPoolIdx[i]]
|
|
||||||
if junkPoolIdx[i] not in toRemove and loc.can_fill(self.multiworld.state, candidate, False):
|
|
||||||
itemFromPool = candidate
|
|
||||||
toRemove.append(junkPoolIdx[i])
|
|
||||||
break
|
|
||||||
assert itemFromPool is not None, "Can't find anymore item(s) to pre fill GT"
|
|
||||||
self.multiworld.push_item(loc, itemFromPool, False)
|
|
||||||
toRemove.sort(reverse = True)
|
toRemove.sort(reverse = True)
|
||||||
for i in toRemove:
|
for i in toRemove:
|
||||||
self.multiworld.itempool.pop(i)
|
self.multiworld.itempool.pop(i)
|
||||||
@@ -622,15 +617,15 @@ class SMZ3World(World):
|
|||||||
raise Exception(f"Tried to place item {itemType} at {location.Name}, but there is no such item in the item pool")
|
raise Exception(f"Tried to place item {itemType} at {location.Name}, but there is no such item in the item pool")
|
||||||
else:
|
else:
|
||||||
location.Item = itemToPlace
|
location.Item = itemToPlace
|
||||||
itemFromPool = next((i for i in self.multiworld.itempool if i.player == self.player and i.name == itemToPlace.Type.name), None)
|
itemPoolIdx = next((idx for idx, i in enumerate(self.multiworld.itempool) if i.player == self.player and i.name == itemToPlace.Type.name), None)
|
||||||
if itemFromPool is not None:
|
if itemPoolIdx is not None:
|
||||||
|
itemFromPool = self.multiworld.itempool.pop(itemPoolIdx)
|
||||||
self.multiworld.get_location(location.Name, self.player).place_locked_item(itemFromPool)
|
self.multiworld.get_location(location.Name, self.player).place_locked_item(itemFromPool)
|
||||||
self.multiworld.itempool.remove(itemFromPool)
|
|
||||||
else:
|
else:
|
||||||
itemFromPool = next((i for i in self.smz3DungeonItems if i.player == self.player and i.name == itemToPlace.Type.name), None)
|
itemPoolIdx = next((idx for idx, i in enumerate(self.smz3DungeonItems) if i.player == self.player and i.name == itemToPlace.Type.name), None)
|
||||||
if itemFromPool is not None:
|
if itemPoolIdx is not None:
|
||||||
|
itemFromPool = self.smz3DungeonItems.pop(itemPoolIdx)
|
||||||
self.multiworld.get_location(location.Name, self.player).place_locked_item(itemFromPool)
|
self.multiworld.get_location(location.Name, self.player).place_locked_item(itemFromPool)
|
||||||
self.smz3DungeonItems.remove(itemFromPool)
|
|
||||||
itemPool.remove(itemToPlace)
|
itemPool.remove(itemToPlace)
|
||||||
|
|
||||||
def FrontFillItemInOwnWorld(self, itemPool, itemType):
|
def FrontFillItemInOwnWorld(self, itemPool, itemType):
|
||||||
@@ -640,10 +635,10 @@ class SMZ3World(World):
|
|||||||
raise Exception(f"Tried to front fill {item.Name} in, but no location was available")
|
raise Exception(f"Tried to front fill {item.Name} in, but no location was available")
|
||||||
|
|
||||||
location.Item = item
|
location.Item = item
|
||||||
itemFromPool = next((i for i in self.multiworld.itempool if i.player == self.player and i.name == item.Type.name and i.advancement == item.Progression), None)
|
itemPoolIdx = next((idx for idx, i in enumerate(self.multiworld.itempool) if i.player == self.player and i.name == item.Type.name and i.advancement == item.Progression), None)
|
||||||
if itemFromPool is not None:
|
if itemPoolIdx is not None:
|
||||||
|
itemFromPool = self.multiworld.itempool.pop(itemPoolIdx)
|
||||||
self.multiworld.get_location(location.Name, self.player).place_locked_item(itemFromPool)
|
self.multiworld.get_location(location.Name, self.player).place_locked_item(itemFromPool)
|
||||||
self.multiworld.itempool.remove(itemFromPool)
|
|
||||||
itemPool.remove(item)
|
itemPool.remove(item)
|
||||||
|
|
||||||
def InitialFillInOwnWorld(self):
|
def InitialFillInOwnWorld(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user