forked from mirror/Archipelago
Some checks failed
Analyze modified files / flake8 (push) Failing after 2m28s
Build / build-win (push) Has been cancelled
Build / build-ubuntu2204 (push) Has been cancelled
ctest / Test C++ ubuntu-latest (push) Has been cancelled
ctest / Test C++ windows-latest (push) Has been cancelled
Analyze modified files / mypy (push) Has been cancelled
Build and Publish Docker Images / Push Docker image to Docker Hub (push) Successful in 5m4s
Native Code Static Analysis / scan-build (push) Failing after 5m2s
type check / pyright (push) Successful in 1m7s
unittests / Test Python 3.11.2 ubuntu-latest (push) Failing after 16m23s
unittests / Test Python 3.12 ubuntu-latest (push) Failing after 28m19s
unittests / Test Python 3.13 ubuntu-latest (push) Failing after 14m49s
unittests / Test hosting with 3.13 on ubuntu-latest (push) Successful in 5m0s
unittests / Test Python 3.13 macos-latest (push) Has been cancelled
unittests / Test Python 3.11 windows-latest (push) Has been cancelled
unittests / Test Python 3.13 windows-latest (push) Has been cancelled
230 lines
7.7 KiB
Python
230 lines
7.7 KiB
Python
import math
|
|
from typing import TYPE_CHECKING, Callable, Union
|
|
|
|
from BaseClasses import CollectionState
|
|
|
|
if TYPE_CHECKING:
|
|
from . import D3DWorld
|
|
|
|
|
|
class Rule(object):
|
|
def __call__(self, state: CollectionState) -> bool:
|
|
raise NotImplementedError
|
|
|
|
def __or__(self, other: "Rule") -> "Rule":
|
|
# short circuit
|
|
if isinstance(other, RuleTrue):
|
|
return other
|
|
if isinstance(other, RuleFalse):
|
|
return self
|
|
return LambdaRule(lambda state: self(state) or other(state))
|
|
|
|
def __and__(self, other: "Rule") -> "Rule":
|
|
# short circuit
|
|
if isinstance(other, RuleTrue):
|
|
return self
|
|
if isinstance(other, RuleFalse):
|
|
return other
|
|
return LambdaRule(lambda state: self(state) and other(state))
|
|
|
|
|
|
RULETYPE = Union[Rule, Callable[[CollectionState], bool]]
|
|
|
|
|
|
class LambdaRule(Rule):
|
|
def __init__(self, func):
|
|
self._func = func
|
|
|
|
def __call__(self, state: dict) -> bool:
|
|
return self._func(state)
|
|
|
|
|
|
class RuleTrue(Rule):
|
|
def __call__(self, state: CollectionState) -> bool:
|
|
return True
|
|
|
|
def __or__(self, other: "Rule") -> "Rule":
|
|
return self
|
|
|
|
def __and__(self, other: "Rule") -> "Rule":
|
|
return other
|
|
|
|
|
|
class RuleFalse(Rule):
|
|
def __call__(self, state: CollectionState) -> bool:
|
|
return False
|
|
|
|
def __or__(self, other: "Rule") -> "Rule":
|
|
return other
|
|
|
|
def __and__(self, other: "Rule") -> "Rule":
|
|
return self
|
|
|
|
|
|
class Rules(object):
|
|
def __init__(self, world: "D3DWorld"):
|
|
player = world.player
|
|
|
|
self.true = RuleTrue()
|
|
self.false = RuleFalse()
|
|
|
|
class HasRule(Rule):
|
|
def __init__(self, prop: str):
|
|
self.prop = prop
|
|
|
|
def __call__(self, state: CollectionState) -> bool:
|
|
# something based on world, whatever
|
|
return state.has(self.prop, player)
|
|
|
|
self.has = HasRule
|
|
|
|
class HasGroupRule(Rule):
|
|
def __init__(self, prop: str):
|
|
self.prop = prop
|
|
|
|
def __call__(self, state: CollectionState) -> bool:
|
|
# something based on world, whatever
|
|
return state.has_group(self.prop, player)
|
|
|
|
self.has_group = HasGroupRule
|
|
|
|
class CountRule(Rule):
|
|
def __init__(self, prop: str, count: int):
|
|
self.prop = prop
|
|
self.count = count
|
|
|
|
def __call__(self, state: CollectionState) -> bool:
|
|
# something based on world, whatever
|
|
return state.has(self.prop, player, self.count)
|
|
|
|
self.count = CountRule
|
|
|
|
class CountGroupRule(Rule):
|
|
def __init__(self, prop: str, count: int):
|
|
self.prop = prop
|
|
self.count = count
|
|
|
|
def __call__(self, state: CollectionState) -> bool:
|
|
# something based on world, whatever
|
|
return state.has_group(self.prop, player, self.count)
|
|
|
|
self.count_group = CountGroupRule
|
|
|
|
if world.options.unlock_abilities:
|
|
self.can_jump = HasRule("Jump")
|
|
self.can_crouch = HasRule("Crouch")
|
|
self.can_dive = HasRule("Dive") | HasGroupRule("Scuba Gear")
|
|
self.can_sprint = HasRule("Sprint")
|
|
else:
|
|
self.can_jump = self.true
|
|
self.can_crouch = self.true
|
|
self.can_sprint = self.true
|
|
self.can_dive = self.true
|
|
if world.options.unlock_interact:
|
|
self.can_open = HasRule("Open")
|
|
self.can_use = HasRule("Use")
|
|
else:
|
|
self.can_open = self.true
|
|
self.can_use = self.true
|
|
self.can_shrink = (
|
|
self.true
|
|
) # Might make this an ability at some point, a bit narrow in scope
|
|
|
|
class CanJetPack(Rule):
|
|
def __init__(self, fuel: int):
|
|
self.fuel = fuel
|
|
self.required = math.ceil(
|
|
self.fuel / float(world.fuel_per_pickup["Jetpack"])
|
|
)
|
|
|
|
def __call__(self, state: CollectionState) -> bool:
|
|
return state.has_group("Jetpack", player) and state.has_group(
|
|
"Jetpack Capacity", player, self.required
|
|
)
|
|
|
|
self.jetpack = CanJetPack
|
|
|
|
self.jump = self.can_jump | self.jetpack(50)
|
|
"""Any simple jump sequence that doesn't consume a lot of jetpack"""
|
|
|
|
class CanDiveTo(Rule):
|
|
def __init__(self, fuel: int):
|
|
self.fuel = fuel
|
|
self.required = math.ceil(
|
|
self.fuel / float(world.fuel_per_pickup["Scuba Gear"])
|
|
)
|
|
|
|
def __call__(self, state: CollectionState) -> bool:
|
|
return state.has_group("Scuba Gear Capacity", player, self.required)
|
|
|
|
if world.options.unlock_abilities:
|
|
self.dive = lambda fuel: self.can_dive & CanDiveTo(fuel)
|
|
"""For chained sequences of dives, where scuba capacity matters for accessibility"""
|
|
else:
|
|
self.dive = lambda x: self.true
|
|
|
|
self.steroids = HasGroupRule("Steroids")
|
|
# Steroids act as an alternative source for sprinting
|
|
self.sprint = self.can_sprint | self.steroids
|
|
self.fast_sprint = self.can_sprint & self.steroids
|
|
|
|
difficulty_map = {"easy": 0, "medium": 1, "hard": 2, "extreme": 3}
|
|
self.difficulty = lambda difficulty: (
|
|
self.true
|
|
if difficulty_map.get(difficulty, 0) <= world.options.logic_difficulty
|
|
else self.false
|
|
)
|
|
|
|
self.sr50 = self.sprint | self.difficulty("hard")
|
|
|
|
self.explosives = self.has_group("Explosives")
|
|
# This is technically not correct because some of them provide more capacity, so this is stricter than it
|
|
# needs to be for now, ToDo
|
|
self.explosives_count = lambda count: self.count_group("Explosives", count)
|
|
|
|
# Glitched logic stuff
|
|
if world.options.glitch_logic:
|
|
self.glitched = RuleTrue()
|
|
else:
|
|
self.glitched = RuleFalse()
|
|
# Most clips require run speed
|
|
self.crouch_jump = self.glitched & self.can_jump & self.can_crouch & self.sprint
|
|
self.fast_crouch_jump = (
|
|
self.glitched
|
|
& self.can_jump
|
|
& self.can_crouch
|
|
& self.can_sprint
|
|
& self.steroids
|
|
)
|
|
# Kicks can activate walls with a lotag set, which is the case for multi-part doors. This allows bypassing
|
|
# some lock checks
|
|
# Kicks still require Use to activate switches
|
|
self.glitch_kick = self.glitched & self.can_use
|
|
|
|
# Some simplifications for progressive items
|
|
self.rpg = self.has_group("RPG")
|
|
self.pipebomb = self.has_group("Pipebomb")
|
|
self.devastator = self.has_group("Devastator")
|
|
self.tripmine = self.has_group("Tripmine")
|
|
|
|
# General Stuff
|
|
self.level = lambda level_cls: HasRule(level_cls.unlock)
|
|
|
|
# Boss kill logic, difficulty dependant
|
|
self.can_kill_boss_1 = self.rpg # Enough RPG Ammo for a kill in the room
|
|
self.can_kill_boss_2 = (
|
|
self.rpg & self.devastator
|
|
) # Should have enough ammo in the room, I think?
|
|
# can jump on goal post and have boss suicide on splash damage, or blimp spawns enough ammo
|
|
self.can_kill_boss_3 = (
|
|
self.rpg
|
|
| self.devastator
|
|
| (
|
|
self.difficulty("medium")
|
|
& ((self.can_jump & self.sprint) | self.jetpack(50))
|
|
)
|
|
)
|
|
# 30 RPG Ammo required. Giving the player some scuba gear requirement for this check as air can be tight
|
|
# Also requiring devastator access. Will need to figure out ammo later as the fight doesn't provide any
|
|
self.can_kill_boss_4 = self.dive(400) & self.rpg & self.devastator
|