diff --git a/worlds/sm/variaRandomizer/logic/smbool.py b/worlds/sm/variaRandomizer/logic/smbool.py index b7f596cb..25f20b55 100644 --- a/worlds/sm/variaRandomizer/logic/smbool.py +++ b/worlds/sm/variaRandomizer/logic/smbool.py @@ -66,6 +66,14 @@ class SMBool: def __copy__(self): return SMBool(self.bool, self.difficulty, self._knows, self._items) + def __deepcopy__(self, memodict): + # `bool` and `difficulty` are a `bool` and `int`, so do not need to be copied. + # The `_knows` list is never mutated, so does not need to be copied. + # The `_items` list is a `list[str | list[str]]` (copied to a flat `list[str]` when accessed through the `items` + # property) that is mutated by code in helpers.py, so needs to be copied. Because there could be lists within + # the list, it is copied using the `flatten()` helper function. + return SMBool(self.bool, self.difficulty, self._knows, flatten(self._items)) + def json(self): # as we have slots instead of dict return {'bool': self.bool, 'difficulty': self.difficulty, 'knows': self.knows, 'items': self.items} diff --git a/worlds/sm/variaRandomizer/logic/smboolmanager.py b/worlds/sm/variaRandomizer/logic/smboolmanager.py index 16f90307..27abb0d3 100644 --- a/worlds/sm/variaRandomizer/logic/smboolmanager.py +++ b/worlds/sm/variaRandomizer/logic/smboolmanager.py @@ -8,6 +8,7 @@ from ..utils.doorsmanager import DoorsManager from ..utils.objectives import Objectives from ..utils.parameters import Knows, isKnows import logging +from copy import deepcopy import sys class SMBoolManager(object): @@ -34,6 +35,46 @@ class SMBoolManager(object): self.createFacadeFunctions() self.createKnowsFunctions(player) self.resetItems() + self.itemsPositions = {} + + def __deepcopy__(self, memodict): + # Use __new__ to avoid calling __init__ like copy.deepcopy without __deepcopy__ implemented. + new = object.__new__(type(self)) + + # Copy everything over in the same order as __init__, ensuring that mutable attributes are deeply copied. + + # SMBool instances contain mutable lists, so must be deep-copied. + new._items = {i: deepcopy(v, memodict) for i, v in self._items.items()} + # `_counts` is a dict[str, int], so the dict can be copied because its keys and values are immutable. + new._counts = self._counts.copy() + # `player` is an int. + new.player = self.player + # `maxDiff` is an int. + new.maxDiff = self.maxDiff + # `onlyBossLeft` is a bool. + new.onlyBossLeft = self.onlyBossLeft + # The HelpersGraph keeps reference to the instance, so a new HelpersGraph is required. + new.helpers = Logic.HelpersGraph(new) + # DoorsManager is stateless, so the same instance can be used. + new.doorsManager = self.doorsManager + # Objectives are cached by self.player, so will be the same instance for the copy. + new.objectives = self.objectives + # Copy the facade functions from new.helpers into new.__dict__. + new.createFacadeFunctions() + # Copying the existing 'knows' functions from `self` to `new` is faster than re-creating all the lambdas with + # `new.createKnowsFunctions(player)`. + for key in Knows.__dict__.keys(): + if isKnows(key): + attribute_name = "knows"+key + knows_func = getattr(self, attribute_name) + setattr(new, attribute_name, knows_func) + # There is no need to call `new.resetItems()` because `_items` and `_counts` have been copied over. + # new.resetItems() + # itemsPositions is a `dict[str, tuple[int, int]]`, so the dict can be copied because the keys and values are + # immutable. + new.itemsPositions = self.itemsPositions.copy() + + return new def computeItemsPositions(self): # compute index in cache key for each items @@ -245,6 +286,9 @@ class SMBoolManagerPlando(SMBoolManager): def __init__(self): super(SMBoolManagerPlando, self).__init__() + def __deepcopy__(self, memodict): + return super().__deepcopy__(memodict) + def addItem(self, item): # a new item is available already = self.haveItem(item)