319 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			319 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | from typing import Optional | ||
|  | from ..locations.items import * | ||
|  | 
 | ||
|  | 
 | ||
|  | class OR: | ||
|  |     __slots__ = ('__items', '__children') | ||
|  | 
 | ||
|  |     def __new__(cls, *args): | ||
|  |         if True in args: | ||
|  |             return True | ||
|  |         return super().__new__(cls) | ||
|  | 
 | ||
|  |     def __init__(self, *args): | ||
|  |         self.__items = [item for item in args if isinstance(item, str)] | ||
|  |         self.__children = [item for item in args if type(item) not in (bool, str) and item is not None] | ||
|  | 
 | ||
|  |         assert self.__items or self.__children, args | ||
|  | 
 | ||
|  |     def __repr__(self) -> str: | ||
|  |         return "or%s" % (self.__items+self.__children) | ||
|  | 
 | ||
|  |     def remove(self, item) -> None: | ||
|  |         if item in self.__items: | ||
|  |             self.__items.remove(item) | ||
|  | 
 | ||
|  |     def hasConsumableRequirement(self) -> bool: | ||
|  |         for item in self.__items: | ||
|  |             if isConsumable(item): | ||
|  |                 print("Consumable OR requirement? %r" % self) | ||
|  |                 return True | ||
|  |         for child in self.__children: | ||
|  |             if child.hasConsumableRequirement(): | ||
|  |                 print("Consumable OR requirement? %r" % self) | ||
|  |                 return True | ||
|  |         return False | ||
|  | 
 | ||
|  |     def test(self, inventory) -> bool: | ||
|  |         for item in self.__items: | ||
|  |             if item in inventory: | ||
|  |                 return True | ||
|  |         for child in self.__children: | ||
|  |             if child.test(inventory): | ||
|  |                 return True | ||
|  |         return False | ||
|  | 
 | ||
|  |     def consume(self, inventory) -> bool: | ||
|  |         for item in self.__items: | ||
|  |             if item in inventory: | ||
|  |                 if isConsumable(item): | ||
|  |                     inventory[item] -= 1 | ||
|  |                     if inventory[item] == 0: | ||
|  |                         del inventory[item] | ||
|  |                     inventory["%s_USED" % item] = inventory.get("%s_USED" % item, 0) + 1 | ||
|  |                 return True | ||
|  |         for child in self.__children: | ||
|  |             if child.consume(inventory): | ||
|  |                 return True | ||
|  |         return False | ||
|  | 
 | ||
|  |     def getItems(self, inventory, target_set) -> None: | ||
|  |         if self.test(inventory): | ||
|  |             return | ||
|  |         for item in self.__items: | ||
|  |             target_set.add(item) | ||
|  |         for child in self.__children: | ||
|  |             child.getItems(inventory, target_set) | ||
|  | 
 | ||
|  |     def copyWithModifiedItemNames(self, f) -> "OR": | ||
|  |         return OR(*(f(item) for item in self.__items), *(child.copyWithModifiedItemNames(f) for child in self.__children)) | ||
|  | 
 | ||
|  | 
 | ||
|  | class AND: | ||
|  |     __slots__ = ('__items', '__children') | ||
|  | 
 | ||
|  |     def __new__(cls, *args): | ||
|  |         if False in args: | ||
|  |             return False | ||
|  |         return super().__new__(cls) | ||
|  | 
 | ||
|  |     def __init__(self, *args): | ||
|  |         self.__items = [item for item in args if isinstance(item, str)] | ||
|  |         self.__children = [item for item in args if type(item) not in (bool, str) and item is not None] | ||
|  | 
 | ||
|  |     def __repr__(self) -> str: | ||
|  |         return "and%s" % (self.__items+self.__children) | ||
|  | 
 | ||
|  |     def remove(self, item) -> None: | ||
|  |         if item in self.__items: | ||
|  |             self.__items.remove(item) | ||
|  | 
 | ||
|  |     def hasConsumableRequirement(self) -> bool: | ||
|  |         for item in self.__items: | ||
|  |             if isConsumable(item): | ||
|  |                 return True | ||
|  |         for child in self.__children: | ||
|  |             if child.hasConsumableRequirement(): | ||
|  |                 return True | ||
|  |         return False | ||
|  | 
 | ||
|  |     def test(self, inventory) -> bool: | ||
|  |         for item in self.__items: | ||
|  |             if item not in inventory: | ||
|  |                 return False | ||
|  |         for child in self.__children: | ||
|  |             if not child.test(inventory): | ||
|  |                 return False | ||
|  |         return True | ||
|  | 
 | ||
|  |     def consume(self, inventory) -> bool: | ||
|  |         for item in self.__items: | ||
|  |             if isConsumable(item): | ||
|  |                 inventory[item] -= 1 | ||
|  |                 if inventory[item] == 0: | ||
|  |                     del inventory[item] | ||
|  |                 inventory["%s_USED" % item] = inventory.get("%s_USED" % item, 0) + 1 | ||
|  |         for child in self.__children: | ||
|  |             if not child.consume(inventory): | ||
|  |                 return False | ||
|  |         return True | ||
|  | 
 | ||
|  |     def getItems(self, inventory, target_set) -> None: | ||
|  |         if self.test(inventory): | ||
|  |             return | ||
|  |         for item in self.__items: | ||
|  |             target_set.add(item) | ||
|  |         for child in self.__children: | ||
|  |             child.getItems(inventory, target_set) | ||
|  | 
 | ||
|  |     def copyWithModifiedItemNames(self, f) -> "AND": | ||
|  |         return AND(*(f(item) for item in self.__items), *(child.copyWithModifiedItemNames(f) for child in self.__children)) | ||
|  | 
 | ||
|  | 
 | ||
|  | class COUNT: | ||
|  |     __slots__ = ('__item', '__amount') | ||
|  | 
 | ||
|  |     def __init__(self, item: str, amount: int) -> None: | ||
|  |         self.__item = item | ||
|  |         self.__amount = amount | ||
|  | 
 | ||
|  |     def __repr__(self) -> str: | ||
|  |         return "<%dx%s>" % (self.__amount, self.__item) | ||
|  | 
 | ||
|  |     def hasConsumableRequirement(self) -> bool: | ||
|  |         if isConsumable(self.__item): | ||
|  |             return True | ||
|  |         return False | ||
|  | 
 | ||
|  |     def test(self, inventory) -> bool: | ||
|  |         return inventory.get(self.__item, 0) >= self.__amount | ||
|  | 
 | ||
|  |     def consume(self, inventory) -> None: | ||
|  |         if isConsumable(self.__item): | ||
|  |             inventory[self.__item] -= self.__amount | ||
|  |             if inventory[self.__item] == 0: | ||
|  |                 del inventory[self.__item] | ||
|  |             inventory["%s_USED" % self.__item] = inventory.get("%s_USED" % self.__item, 0) + self.__amount | ||
|  | 
 | ||
|  |     def getItems(self, inventory, target_set) -> None: | ||
|  |         if self.test(inventory): | ||
|  |             return | ||
|  |         target_set.add(self.__item) | ||
|  | 
 | ||
|  |     def copyWithModifiedItemNames(self, f) -> "COUNT": | ||
|  |         return COUNT(f(self.__item), self.__amount) | ||
|  | 
 | ||
|  | 
 | ||
|  | class COUNTS: | ||
|  |     __slots__ = ('__items', '__amount') | ||
|  | 
 | ||
|  |     def __init__(self, items, amount): | ||
|  |         self.__items = items | ||
|  |         self.__amount = amount | ||
|  | 
 | ||
|  |     def __repr__(self) -> str: | ||
|  |         return "<%dx%s>" % (self.__amount, self.__items) | ||
|  | 
 | ||
|  |     def hasConsumableRequirement(self) -> bool: | ||
|  |         for item in self.__items: | ||
|  |             if isConsumable(item): | ||
|  |                 print("Consumable COUNTS requirement? %r" % (self)) | ||
|  |                 return True | ||
|  |         return False | ||
|  | 
 | ||
|  |     def test(self, inventory) -> bool: | ||
|  |         count = 0 | ||
|  |         for item in self.__items: | ||
|  |             count += inventory.get(item, 0) | ||
|  |         return count >= self.__amount | ||
|  | 
 | ||
|  |     def consume(self, inventory) -> None: | ||
|  |         for item in self.__items: | ||
|  |             if isConsumable(item): | ||
|  |                 inventory[item] -= self.__amount | ||
|  |                 if inventory[item] == 0: | ||
|  |                     del inventory[item] | ||
|  |                 inventory["%s_USED" % item] = inventory.get("%s_USED" % item, 0) + self.__amount | ||
|  | 
 | ||
|  |     def getItems(self, inventory, target_set) -> None: | ||
|  |         if self.test(inventory): | ||
|  |             return | ||
|  |         for item in self.__items: | ||
|  |             target_set.add(item) | ||
|  | 
 | ||
|  |     def copyWithModifiedItemNames(self, f) -> "COUNTS": | ||
|  |         return COUNTS([f(item) for item in self.__items], self.__amount) | ||
|  | 
 | ||
|  | 
 | ||
|  | class FOUND: | ||
|  |     __slots__ = ('__item', '__amount') | ||
|  | 
 | ||
|  |     def __init__(self, item: str, amount: int) -> None: | ||
|  |         self.__item = item | ||
|  |         self.__amount = amount | ||
|  | 
 | ||
|  |     def __repr__(self) -> str: | ||
|  |         return "{%dx%s}" % (self.__amount, self.__item) | ||
|  | 
 | ||
|  |     def hasConsumableRequirement(self) -> bool: | ||
|  |         return False | ||
|  | 
 | ||
|  |     def test(self, inventory) -> bool: | ||
|  |         return inventory.get(self.__item, 0) + inventory.get("%s_USED" % self.__item, 0) >= self.__amount | ||
|  | 
 | ||
|  |     def consume(self, inventory) -> None: | ||
|  |         pass | ||
|  | 
 | ||
|  |     def getItems(self, inventory, target_set) -> None: | ||
|  |         if self.test(inventory): | ||
|  |             return | ||
|  |         target_set.add(self.__item) | ||
|  | 
 | ||
|  |     def copyWithModifiedItemNames(self, f) -> "FOUND": | ||
|  |         return FOUND(f(self.__item), self.__amount) | ||
|  | 
 | ||
|  | 
 | ||
|  | def hasConsumableRequirement(requirements) -> bool: | ||
|  |     if isinstance(requirements, str): | ||
|  |         return isConsumable(requirements) | ||
|  |     if requirements is None: | ||
|  |         return False | ||
|  |     return requirements.hasConsumableRequirement() | ||
|  | 
 | ||
|  | 
 | ||
|  | def isConsumable(item) -> bool: | ||
|  |     if item is None: | ||
|  |         return False | ||
|  |     #if item.startswith("RUPEES_") or item == "RUPEES": | ||
|  |     #    return True | ||
|  |     if item.startswith("KEY"): | ||
|  |         return True | ||
|  |     return False | ||
|  | 
 | ||
|  | 
 | ||
|  | class RequirementsSettings: | ||
|  |     def __init__(self, options): | ||
|  |         self.bush = OR(SWORD, MAGIC_POWDER, MAGIC_ROD, POWER_BRACELET, BOOMERANG) | ||
|  |         self.attack = OR(SWORD, BOMB, BOW, MAGIC_ROD, BOOMERANG) | ||
|  |         self.attack_hookshot = OR(SWORD, BOMB, BOW, MAGIC_ROD, BOOMERANG, HOOKSHOT) # switches, hinox, shrouded stalfos | ||
|  |         self.attack_hookshot_powder = OR(SWORD, BOMB, BOW, MAGIC_ROD, BOOMERANG, HOOKSHOT, MAGIC_POWDER) # zols, keese, moldorm | ||
|  |         self.attack_no_bomb = OR(SWORD, BOW, MAGIC_ROD, BOOMERANG, HOOKSHOT) # ? | ||
|  |         self.attack_hookshot_no_bomb = OR(SWORD, BOW, MAGIC_ROD, BOOMERANG, HOOKSHOT) # vire | ||
|  |         self.attack_no_boomerang = OR(SWORD, BOMB, BOW, MAGIC_ROD, HOOKSHOT) # teleporting owls | ||
|  |         self.attack_skeleton = OR(SWORD, BOMB, BOW, BOOMERANG, HOOKSHOT)  # cannot kill skeletons with the fire rod | ||
|  |         self.rear_attack = OR(SWORD, BOMB) # mimic | ||
|  |         self.rear_attack_range = OR(MAGIC_ROD, BOW) # mimic | ||
|  |         self.fire = OR(MAGIC_POWDER, MAGIC_ROD) # torches | ||
|  |         self.push_hardhat = OR(SHIELD, SWORD, HOOKSHOT, BOOMERANG) | ||
|  | 
 | ||
|  |         self.boss_requirements = [ | ||
|  |             SWORD,  # D1 boss | ||
|  |             AND(OR(SWORD, MAGIC_ROD), POWER_BRACELET),  # D2 boss | ||
|  |             AND(PEGASUS_BOOTS, SWORD),  # D3 boss | ||
|  |             AND(FLIPPERS, OR(SWORD, MAGIC_ROD, BOW)),  # D4 boss | ||
|  |             AND(HOOKSHOT, SWORD),  # D5 boss | ||
|  |             BOMB,  # D6 boss | ||
|  |             AND(OR(MAGIC_ROD, SWORD, HOOKSHOT), COUNT(SHIELD, 2)),  # D7 boss | ||
|  |             MAGIC_ROD,  # D8 boss | ||
|  |             self.attack_hookshot_no_bomb,  # D9 boss | ||
|  |         ] | ||
|  |         self.miniboss_requirements = { | ||
|  |             "ROLLING_BONES":    self.attack_hookshot, | ||
|  |             "HINOX":            self.attack_hookshot, | ||
|  |             "DODONGO":          BOMB, | ||
|  |             "CUE_BALL":         SWORD, | ||
|  |             "GHOMA":            OR(BOW, HOOKSHOT), | ||
|  |             "SMASHER":          POWER_BRACELET, | ||
|  |             "GRIM_CREEPER":     self.attack_hookshot_no_bomb, | ||
|  |             "BLAINO":           SWORD, | ||
|  |             "AVALAUNCH":        self.attack_hookshot, | ||
|  |             "GIANT_BUZZ_BLOB":  MAGIC_POWDER, | ||
|  |             "MOBLIN_KING":      SWORD, | ||
|  |             "ARMOS_KNIGHT":     OR(BOW, MAGIC_ROD, SWORD), | ||
|  |         } | ||
|  | 
 | ||
|  |         # Adjust for options | ||
|  |         if options.bowwow != 'normal': | ||
|  |             # We cheat in bowwow mode, we pretend we have the sword, as bowwow can pretty much do all what the sword ca$            # Except for taking out bushes (and crystal pillars are removed) | ||
|  |             self.bush.remove(SWORD) | ||
|  |         if options.logic == "casual": | ||
|  |             # In casual mode, remove the more complex kill methods | ||
|  |             self.bush.remove(MAGIC_POWDER) | ||
|  |             self.attack_hookshot_powder.remove(MAGIC_POWDER) | ||
|  |             self.attack.remove(BOMB) | ||
|  |             self.attack_hookshot.remove(BOMB) | ||
|  |             self.attack_hookshot_powder.remove(BOMB) | ||
|  |             self.attack_no_boomerang.remove(BOMB) | ||
|  |             self.attack_skeleton.remove(BOMB) | ||
|  |         if options.logic == "hard": | ||
|  |             self.boss_requirements[3] = AND(FLIPPERS, OR(SWORD, MAGIC_ROD, BOW, BOMB))  # bomb angler fish | ||
|  |             self.boss_requirements[6] = OR(MAGIC_ROD, AND(BOMB, BOW), COUNT(SWORD, 2), AND(OR(SWORD, HOOKSHOT, BOW), SHIELD))  # evil eagle 3 cycle magic rod / bomb arrows / l2 sword, and bow kill | ||
|  |         if options.logic == "glitched": | ||
|  |             self.boss_requirements[3] = AND(FLIPPERS, OR(SWORD, MAGIC_ROD, BOW, BOMB))  # bomb angler fish | ||
|  |             self.boss_requirements[6] = OR(MAGIC_ROD, BOMB, BOW, HOOKSHOT, COUNT(SWORD, 2), AND(SWORD, SHIELD))  # evil eagle off screen kill or 3 cycle with bombs | ||
|  |         if options.logic == "hell": | ||
|  |             self.boss_requirements[3] = AND(FLIPPERS, OR(SWORD, MAGIC_ROD, BOW, BOMB))  # bomb angler fish | ||
|  |             self.boss_requirements[6] = OR(MAGIC_ROD, BOMB, BOW, HOOKSHOT, COUNT(SWORD, 2), AND(SWORD, SHIELD))  # evil eagle off screen kill or 3 cycle with bombs | ||
|  |             self.boss_requirements[7] = OR(MAGIC_ROD, COUNT(SWORD, 2)) # hot head sword beams | ||
|  |             self.miniboss_requirements["GIANT_BUZZ_BLOB"] = OR(MAGIC_POWDER, COUNT(SWORD,2)) # use sword beams to damage buzz blob |