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
|