mirror of
https://github.com/evennia/evennia.git
synced 2025-10-29 03:12:08 +00:00
Cleanup combat syntax, add flake8 config for legacy compat
This commit is contained in:
parent
049e4fbb35
commit
e88e6d1b1b
@ -12,24 +12,12 @@ This establishes the basic building blocks for combat:
|
||||
|
||||
"""
|
||||
|
||||
import random
|
||||
from collections import defaultdict, deque
|
||||
|
||||
from evennia import CmdSet, Command, create_script, default_cmds
|
||||
from evennia.commands.command import InterruptCommand
|
||||
from evennia import create_script
|
||||
from evennia.scripts.scripts import DefaultScript
|
||||
from evennia.typeclasses.attributes import AttributeProperty
|
||||
from evennia.utils import dbserialize, delay, evmenu, evtable, logger
|
||||
from evennia.utils.utils import display_len, inherits_from, list_to_string, pad
|
||||
from evennia.utils import evtable
|
||||
|
||||
from . import rules
|
||||
from .characters import EvAdventureCharacter
|
||||
from .enums import ABILITY_REVERSE_MAP, Ability, ObjType
|
||||
from .npcs import EvAdventureNPC
|
||||
from .objects import EvAdventureObject
|
||||
|
||||
COMBAT_HANDLER_KEY = "evadventure_turnbased_combathandler"
|
||||
COMBAT_HANDLER_INTERVAL = 30
|
||||
|
||||
|
||||
class CombatFailure(RuntimeError):
|
||||
@ -39,7 +27,7 @@ class CombatFailure(RuntimeError):
|
||||
"""
|
||||
|
||||
|
||||
# Combat action classes
|
||||
# Combaw action classes
|
||||
|
||||
|
||||
class CombatAction:
|
||||
@ -149,9 +137,9 @@ class CombatActionAttack(CombatAction):
|
||||
class CombatActionStunt(CombatAction):
|
||||
"""
|
||||
Perform a stunt the grants a beneficiary (can be self) advantage on their next action against a
|
||||
target. Whenever performing a stunt that would affect another negatively (giving them disadvantage
|
||||
against an ally, or granting an advantage against them, we need to make a check first. We don't
|
||||
do a check if giving an advantage to an ally or ourselves.
|
||||
target. Whenever performing a stunt that would affect another negatively (giving them
|
||||
disadvantage against an ally, or granting an advantage against them, we need to make a check
|
||||
first. We don't do a check if giving an advantage to an ally or ourselves.
|
||||
|
||||
action_dict = {
|
||||
"key": "stunt",
|
||||
@ -159,8 +147,8 @@ class CombatActionStunt(CombatAction):
|
||||
"target": Character/NPC,
|
||||
"advantage": bool, # if False, it's a disadvantage
|
||||
"stunt_type": Ability, # what ability (like STR, DEX etc) to use to perform this stunt.
|
||||
"defense_type": Ability, # what ability to use to defend against (negative) effects of this
|
||||
stunt.
|
||||
"defense_type": Ability, # what ability to use to defend against (negative) effects of
|
||||
this stunt.
|
||||
}
|
||||
|
||||
Note:
|
||||
@ -298,7 +286,7 @@ class EvAdventureCombatHandlerBase(DefaultScript):
|
||||
fallback_action_dict = AttributeProperty({"key": "hold"}, autocreate=False)
|
||||
|
||||
@classmethod
|
||||
def get_or_create_combathandler(cls, obj, combathandler_key="combathandler", **kwargs):
|
||||
def get_or_create_combathandler(cls, obj, **kwargs):
|
||||
"""
|
||||
Get or create a combathandler on `obj`.
|
||||
|
||||
@ -306,23 +294,29 @@ class EvAdventureCombatHandlerBase(DefaultScript):
|
||||
obj (any): The Typeclassed entity to store the CombatHandler Script on. This could be
|
||||
a location (for turn-based combat) or a Character (for twitch-based combat).
|
||||
Keyword Args:
|
||||
combathandler_key (str): They key name for the script. Will be 'combathandler' by default.
|
||||
combathandler_key (str): They key name for the script. Will be 'combathandler' by
|
||||
default.
|
||||
**kwargs: Arguments to the Script, if it is created.
|
||||
|
||||
"""
|
||||
if not obj:
|
||||
raise CombatFailure("Cannot start combat without a place to do it!")
|
||||
|
||||
combathandler_key = kwargs.pop("key", "combathandler")
|
||||
combathandler = obj.ndb.combathandler
|
||||
if not combathandler:
|
||||
combathandler = obj.scripts.get(combathandler_name).first()
|
||||
combathandler = obj.scripts.get(combathandler_key).first()
|
||||
if not combathandler:
|
||||
# have to create from scratch
|
||||
persistent = kwargs.pop("persistent", True)
|
||||
combathandler = create_script(
|
||||
cls, key=combathandler_key, obj=obj, persistent=persistent, **kwargs
|
||||
cls,
|
||||
key=combathandler_key,
|
||||
obj=obj,
|
||||
persistent=persistent,
|
||||
**kwargs,
|
||||
)
|
||||
self.caller.ndb.combathandler = combathandler
|
||||
obj.ndb.combathandler = combathandler
|
||||
return combathandler
|
||||
|
||||
def msg(self, message, combatant=None, broadcast=True):
|
||||
@ -438,7 +432,7 @@ class EvAdventureCombatHandlerBase(DefaultScript):
|
||||
this with group mechanics).
|
||||
|
||||
"""
|
||||
raise NotImplemented
|
||||
raise NotImplementedError
|
||||
|
||||
def give_advantage(self, recipient, target):
|
||||
"""
|
||||
@ -452,7 +446,7 @@ class EvAdventureCombatHandlerBase(DefaultScript):
|
||||
some future boost)
|
||||
|
||||
"""
|
||||
raise NotImplemented
|
||||
raise NotImplementedError
|
||||
|
||||
def give_disadvantage(self, recipient, target):
|
||||
"""
|
||||
@ -460,10 +454,11 @@ class EvAdventureCombatHandlerBase(DefaultScript):
|
||||
|
||||
Args:
|
||||
recipient (Character or NPC): The one to get the disadvantage.
|
||||
target (Character or NPC): The one against which the target gains disadvantage, usually an enemy.
|
||||
target (Character or NPC): The one against which the target gains disadvantage, usually
|
||||
an enemy.
|
||||
|
||||
"""
|
||||
raise NotImplemented
|
||||
raise NotImplementedError
|
||||
|
||||
def has_advantage(self, combatant, target):
|
||||
"""
|
||||
@ -474,7 +469,7 @@ class EvAdventureCombatHandlerBase(DefaultScript):
|
||||
target (Character or NPC): The target to check advantage against.
|
||||
|
||||
"""
|
||||
raise NotImplemented
|
||||
raise NotImplementedError
|
||||
|
||||
def has_disadvantage(self, combatant, target):
|
||||
"""
|
||||
@ -485,7 +480,7 @@ class EvAdventureCombatHandlerBase(DefaultScript):
|
||||
target (Character or NPC): The target to check disadvantage against.
|
||||
|
||||
"""
|
||||
raise NotImplemented
|
||||
raise NotImplementedError
|
||||
|
||||
def queue_action(self, combatant, action_dict):
|
||||
"""
|
||||
@ -503,7 +498,7 @@ class EvAdventureCombatHandlerBase(DefaultScript):
|
||||
to make room.
|
||||
|
||||
"""
|
||||
raise NotImplemented
|
||||
raise NotImplementedError
|
||||
|
||||
def execute_next_action(self, combatant):
|
||||
"""
|
||||
@ -514,14 +509,14 @@ class EvAdventureCombatHandlerBase(DefaultScript):
|
||||
|
||||
|
||||
"""
|
||||
raise NotImplemented
|
||||
raise NotImplementedError
|
||||
|
||||
def start_combat(self):
|
||||
"""
|
||||
Start combat.
|
||||
|
||||
"""
|
||||
raise NotImplemented
|
||||
raise NotImplementedError
|
||||
|
||||
def check_stop_combat(self):
|
||||
"""
|
||||
@ -537,10 +532,10 @@ class EvAdventureCombatHandlerBase(DefaultScript):
|
||||
bool: If `True`, the `stop_combat` method sho
|
||||
|
||||
"""
|
||||
raise NotImplemented
|
||||
raise NotImplementedError
|
||||
|
||||
def stop_combat(self):
|
||||
"""
|
||||
Stop combat. This should also do all cleanup.
|
||||
"""
|
||||
raise NotImplemented
|
||||
raise NotImplementedError
|
||||
|
||||
@ -3,98 +3,35 @@ EvAdventure Turn-based combat
|
||||
|
||||
This implements a turn-based (Final Fantasy, etc) style of MUD combat.
|
||||
|
||||
choose their next action. If they don't react before a timer runs out, the previous action
|
||||
will be repeated. This means that a 'twitch' style combat can be created using the same
|
||||
mechanism, by just speeding up each 'turn'.
|
||||
In this variation, all combatants are sharing the same combat handler, sitting on the current room.
|
||||
The user will receive a menu of combat options and each combatat has a certain time time (e.g. 30s)
|
||||
to select their next action or do nothing. To speed up play, as soon as everyone in combat selected
|
||||
their next action, the next turn runs immediately, regardless of the timeout.
|
||||
|
||||
The combat is handled with a `Script` shared between all combatants; this tracks the state
|
||||
of combat and handles all timing elements.
|
||||
With this example, all chosen combat actions are considered to happen at the same time (so you are
|
||||
able to kill and be killed in the same turn).
|
||||
|
||||
Unlike in base _Knave_, the MUD version's combat is simultaneous; everyone plans and executes
|
||||
their turns simultaneously with minimum downtime.
|
||||
Unlike in twitch-like combat, there is no movement while in turn-based combat. Fleeing is a select
|
||||
action that takes several vulnerable turns to complete.
|
||||
|
||||
This version is simplified to not worry about things like optimal range etc. So a bow can be used
|
||||
the same as a sword in battle. One could add a 1D range mechanism to add more strategy by requiring
|
||||
optimizal positioning.
|
||||
|
||||
The combat is controlled through a menu:
|
||||
|
||||
------------------- main menu
|
||||
Combat
|
||||
|
||||
You have 30 seconds to choose your next action. If you don't decide, you will hesitate and do
|
||||
nothing. Available actions:
|
||||
|
||||
1. [A]ttack/[C]ast spell at <target> using your equipped weapon/spell
|
||||
3. Make [S]tunt <target/yourself> (gain/give advantage/disadvantage for future attacks)
|
||||
4. S[W]ap weapon / spell rune
|
||||
5. [U]se <item>
|
||||
6. [F]lee/disengage (takes one turn, during which attacks have advantage against you)
|
||||
8. [H]esitate/Do nothing
|
||||
|
||||
You can also use say/emote between rounds.
|
||||
As soon as all combatants have made their choice (or time out), the round will be resolved
|
||||
simultaneusly.
|
||||
|
||||
-------------------- attack/cast spell submenu
|
||||
|
||||
Choose the target of your attack/spell:
|
||||
0: Yourself 3: <enemy 3> (wounded)
|
||||
1: <enemy 1> (hurt)
|
||||
2: <enemy 2> (unharmed)
|
||||
|
||||
------------------- make stunt submenu
|
||||
|
||||
Stunts are special actions that don't cause damage but grant advantage for you or
|
||||
an ally for future attacks - or grant disadvantage to your enemy's future attacks.
|
||||
The effects of stunts start to apply *next* round. The effect does not stack, can only
|
||||
be used once and must be taken advantage of within 5 rounds.
|
||||
|
||||
Choose stunt:
|
||||
1: Trip <target> (give disadvantage DEX)
|
||||
2: Feint <target> (get advantage DEX against target)
|
||||
3: ...
|
||||
|
||||
-------------------- make stunt target submenu
|
||||
|
||||
Choose the target of your stunt:
|
||||
0: Yourself 3: <combatant 3> (wounded)
|
||||
1: <combatant 1> (hurt)
|
||||
2: <combatant 2> (unharmed)
|
||||
|
||||
------------------- swap weapon or spell run
|
||||
|
||||
Choose the item to wield.
|
||||
1: <item1>
|
||||
2: <item2> (two hands)
|
||||
3: <item3>
|
||||
4: ...
|
||||
|
||||
------------------- use item
|
||||
|
||||
Choose item to use.
|
||||
1: Healing potion (+1d6 HP)
|
||||
2: Magic pebble (gain advantage, 1 use)
|
||||
3: Potion of glue (give disadvantage to target)
|
||||
|
||||
------------------- Hesitate/Do nothing
|
||||
|
||||
You hang back, passively defending.
|
||||
|
||||
------------------- Disengage
|
||||
|
||||
You retreat, getting ready to get out of combat. Use two times in a row to
|
||||
leave combat. You flee last in a round.
|
||||
"""
|
||||
|
||||
|
||||
import random
|
||||
from collections import defaultdict
|
||||
|
||||
from evennia import AttributeProperty, CmdSet, Command, EvMenu
|
||||
from evennia.utils import inherits_from, list_to_string
|
||||
|
||||
from .characters import EvAdventureCharacter
|
||||
from .combat_base import (
|
||||
CombatAction,
|
||||
CombatActionAttack,
|
||||
CombatActionHold,
|
||||
CombatActionStunt,
|
||||
CombatActionUserItem,
|
||||
CombatActionUseItem,
|
||||
CombatActionWield,
|
||||
EvAdventureCombatHandler,
|
||||
EvAdventureCombatHandlerBase,
|
||||
)
|
||||
from .enums import Ability
|
||||
|
||||
@ -133,7 +70,7 @@ class CombatActionFlee(CombatAction):
|
||||
)
|
||||
|
||||
|
||||
class EvAdventureTurnbasedCombatHandler(EvAdventureCombatHandler):
|
||||
class EvAdventureTurnbasedCombatHandler(EvAdventureCombatHandlerBase):
|
||||
"""
|
||||
A version of the combathandler, handling turn-based combat.
|
||||
|
||||
@ -175,31 +112,32 @@ class EvAdventureTurnbasedCombatHandler(EvAdventureCombatHandler):
|
||||
# usable script properties
|
||||
# .is_active - show if timer is running
|
||||
|
||||
def give_advantage(self, recipient, target):
|
||||
def give_advantage(self, combatant, target):
|
||||
"""
|
||||
Let a benefiter gain advantage against the target.
|
||||
|
||||
Args:
|
||||
recipient (Character or NPC): The one to gain the advantage. This may or may not
|
||||
combatant (Character or NPC): The one to gain the advantage. This may or may not
|
||||
be the same entity that creates the advantage in the first place.
|
||||
target (Character or NPC): The one against which the target gains advantage. This
|
||||
could (in principle) be the same as the benefiter (e.g. gaining advantage on
|
||||
some future boost)
|
||||
|
||||
"""
|
||||
self.advantage_matrix[recipient][target] = True
|
||||
self.advantage_matrix[combatant][target] = True
|
||||
|
||||
def give_disadvantage(self, recipient, target, **kwargs):
|
||||
def give_disadvantage(self, combatant, target, **kwargs):
|
||||
"""
|
||||
Let an affected party gain disadvantage against a target.
|
||||
|
||||
Args:
|
||||
recipient (Character or NPC): The one to get the disadvantage.
|
||||
target (Character or NPC): The one against which the target gains disadvantage, usually an enemy.
|
||||
target (Character or NPC): The one against which the target gains disadvantage, usually
|
||||
an enemy.
|
||||
|
||||
"""
|
||||
self.disadvantage_matrix[recipient][target] = True
|
||||
self.combathandler.advantage_matrix[recipient][target] = False
|
||||
self.disadvantage_matrix[combatant][target] = True
|
||||
self.combathandler.advantage_matrix[combatant][target] = False
|
||||
|
||||
def has_advantage(self, combatant, target, **kwargs):
|
||||
"""
|
||||
@ -210,7 +148,7 @@ class EvAdventureTurnbasedCombatHandler(EvAdventureCombatHandler):
|
||||
target (Character or NPC): The target to check advantage against.
|
||||
|
||||
"""
|
||||
return bool(self.combathandler.advantage_matrix[recipient].pop(target, False)) or (
|
||||
return bool(self.combathandler.advantage_matrix[combatant].pop(target, False)) or (
|
||||
target in self.combathandler.fleeing_combatants
|
||||
)
|
||||
|
||||
@ -223,11 +161,9 @@ class EvAdventureTurnbasedCombatHandler(EvAdventureCombatHandler):
|
||||
target (Character or NPC): The target to check disadvantage against.
|
||||
|
||||
"""
|
||||
|
||||
def has_disadvantage(self, recipient, target):
|
||||
return bool(self.combathandler.disadvantage_matrix[recipient].pop(target, False)) or (
|
||||
recipient in self.combathandler.fleeing_combatants
|
||||
)
|
||||
return bool(self.combathandler.disadvantage_matrix[combatant].pop(target, False)) or (
|
||||
combatant in self.combathandler.fleeing_combatants
|
||||
)
|
||||
|
||||
def add_combatant(self, combatant):
|
||||
"""
|
||||
@ -370,7 +306,7 @@ class EvAdventureTurnbasedCombatHandler(EvAdventureCombatHandler):
|
||||
|
||||
"""
|
||||
# this gets the next dict and rotates the queue
|
||||
action_dict = self.combatants.get(combatants, self.fallback_action_dict)
|
||||
action_dict = self.combatants.get(combatant, self.fallback_action_dict)
|
||||
|
||||
# use the action-dict to select and create an action from an action class
|
||||
action_class = self.action_classes[action_dict["key"]]
|
||||
@ -763,18 +699,7 @@ def node_combat(caller, raw_string, **kwargs):
|
||||
# Add this command to the Character cmdset to make turn-based combat available.
|
||||
|
||||
|
||||
class _CmdTurnCombatBase(_CmdCombatBase):
|
||||
"""
|
||||
Override parent class to slow down the tick for more clearly turn-based play.
|
||||
|
||||
"""
|
||||
|
||||
combathandler_name = "combathandler"
|
||||
combat_tick = 30
|
||||
flee_timeout = 2
|
||||
|
||||
|
||||
class CmdTurnAttack(_CmdTurnCombatBase):
|
||||
class CmdTurnAttack(Command):
|
||||
"""
|
||||
Start or join combat.
|
||||
|
||||
@ -800,7 +725,7 @@ class CmdTurnAttack(_CmdTurnCombatBase):
|
||||
return
|
||||
|
||||
if not hasattr(target, "hp"):
|
||||
self.msg(f"You can't attack that.")
|
||||
self.msg("You can't attack that.")
|
||||
return
|
||||
elif target.hp <= 0:
|
||||
self.msg(f"{target.get_display_name(self.caller)} is already down.")
|
||||
@ -810,14 +735,18 @@ class CmdTurnAttack(_CmdTurnCombatBase):
|
||||
self.msg("PvP combat is not allowed here!")
|
||||
return
|
||||
|
||||
combathandler = EvAdventureTurnbasedCombatHandler.get_or_create_combathandler(
|
||||
self.caller.location
|
||||
)
|
||||
|
||||
# add combatants to combathandler. this can be done safely over and over
|
||||
self.combathandler.add_combatant(self.caller)
|
||||
self.combathandler.queue_action(self.caller, {"key": "attack", "target": target})
|
||||
self.combathandler.add_combatant(target)
|
||||
self.combathandler.start_combat()
|
||||
combathandler.add_combatant(self.caller)
|
||||
combathandler.queue_action(self.caller, {"key": "attack", "target": target})
|
||||
combathandler.add_combatant(target)
|
||||
combathandler.start_combat()
|
||||
|
||||
# build and start the menu
|
||||
evmenu.EvMenu(
|
||||
EvMenu(
|
||||
self.caller,
|
||||
{
|
||||
"node_choose_enemy_target": node_choose_enemy_target,
|
||||
|
||||
@ -4,21 +4,20 @@ EvAdventure Twitch-based combat
|
||||
This implements a 'twitch' (aka DIKU or other traditional muds) style of MUD combat.
|
||||
|
||||
"""
|
||||
from evennia import AttributeProperty
|
||||
from evennia import AttributeProperty, CmdSet, default_cmds
|
||||
from evennia.commands.command import Command, InterruptCommand
|
||||
from evennia.scripts.scripts import DefaultScript
|
||||
from evennia.utils.create import create_script
|
||||
from evennia.utils.utils import repeat, unrepeat
|
||||
from evennia.utils.utils import display_len, inherits_from, list_to_string, pad, repeat, unrepeat
|
||||
|
||||
from .combat import (
|
||||
from .characters import EvAdventureCharacter
|
||||
from .combat_base import (
|
||||
CombatActionAttack,
|
||||
CombatActionHold,
|
||||
CombatActionStunt,
|
||||
CombatActionUserItem,
|
||||
CombatActionUseItem,
|
||||
CombatActionWield,
|
||||
EvAdventureCombatHandlerBase,
|
||||
)
|
||||
from .enums import ABILITY_REVERSE_MAP, Ability, ObjType
|
||||
from .enums import ABILITY_REVERSE_MAP
|
||||
|
||||
|
||||
class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
|
||||
@ -44,7 +43,7 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
|
||||
disadvantages_against = AttributeProperty(dict)
|
||||
|
||||
action_dict = AttributeProperty(dict)
|
||||
fallback_action_dict = AttributePropety({"key": "hold", "dt": 0})
|
||||
fallback_action_dict = AttributeProperty({"key": "hold", "dt": 0})
|
||||
|
||||
# stores the current ticker reference, so we can manipulate it later
|
||||
current_ticker_ref = AttributeProperty(None)
|
||||
@ -107,7 +106,8 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
|
||||
|
||||
Args:
|
||||
recipient (Character or NPC): The one to get the disadvantage.
|
||||
target (Character or NPC): The one against which the target gains disadvantage, usually an enemy.
|
||||
target (Character or NPC): The one against which the target gains disadvantage, usually
|
||||
an enemy.
|
||||
|
||||
"""
|
||||
self.disadvantages_against[target] = True
|
||||
@ -166,6 +166,7 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
|
||||
"""
|
||||
Triggered after a delay by the command
|
||||
"""
|
||||
combatant = self.obj
|
||||
action_dict = self.action_dict
|
||||
action_class = self.action_classes[action_dict["key"]]
|
||||
action = action_class(self, combatant, action_dict)
|
||||
@ -176,8 +177,8 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
|
||||
|
||||
if not action_dict.get("repeat", True):
|
||||
# not a repeating action, use the fallback (normally the original attack)
|
||||
self.action_dict = fallback_action_dict
|
||||
self.queue_action(fallback_action_dict.get("dt", 0))
|
||||
self.action_dict = self.fallback_action_dict
|
||||
self.queue_action(self.fallback_action_dict.get("dt", 0))
|
||||
|
||||
def check_stop_combat(self):
|
||||
# check if one side won the battle.
|
||||
@ -243,7 +244,7 @@ class _BaseTwitchCombatCommand(Command):
|
||||
Get or create the combathandler assigned to this combatant.
|
||||
|
||||
"""
|
||||
return EvAdventureCombatHandlerBase.get_or_create_combathandler(self.caller)
|
||||
return EvAdventureCombatTwitchHandler.get_or_create_combathandler(self.caller)
|
||||
|
||||
|
||||
class CmdAttack(_BaseTwitchCombatCommand):
|
||||
@ -261,7 +262,7 @@ class CmdAttack(_BaseTwitchCombatCommand):
|
||||
help_category = "combat"
|
||||
|
||||
def func(self):
|
||||
target = self.search(lhs)
|
||||
target = self.search(self.lhs)
|
||||
if not target:
|
||||
return
|
||||
|
||||
@ -448,7 +449,7 @@ class CmdUseItem(_BaseTwitchCombatCommand):
|
||||
)
|
||||
|
||||
|
||||
class CmdWield(_CmdCombatBase):
|
||||
class CmdWield(_BaseTwitchCombatCommand):
|
||||
"""
|
||||
Wield a weapon or spell-rune. You will the wield the item, swapping with any other item(s) you
|
||||
were wielded before.
|
||||
|
||||
@ -8,37 +8,30 @@ from unittest.mock import Mock, call, patch
|
||||
|
||||
from evennia.utils import create
|
||||
from evennia.utils.ansi import strip_ansi
|
||||
from evennia.utils.test_resources import BaseEvenniaTest
|
||||
from evennia.utils.test_resources import EvenniaCommandTestMixin, EvenniaTestCase
|
||||
|
||||
from .. import combat
|
||||
from .. import combat_base, combat_turnbased, combat_twitch
|
||||
from ..characters import EvAdventureCharacter
|
||||
from ..enums import Ability, WieldLocation
|
||||
from ..npcs import EvAdventureMob
|
||||
from ..objects import EvAdventureConsumable, EvAdventureRunestone, EvAdventureWeapon
|
||||
from ..rooms import EvAdventureRoom
|
||||
from .mixins import EvAdventureMixin
|
||||
|
||||
|
||||
class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||
class _CombatTestBase(EvenniaTestCase):
|
||||
"""
|
||||
Test methods on the turn-based combat handler
|
||||
Set up common entities for testing combat:
|
||||
|
||||
- `location` (key=testroom)
|
||||
- `combatant` (key=testchar)
|
||||
- `target` (key=testmonster)`
|
||||
|
||||
We also mock the `.msg` method of both `combatant` and `target` so we can
|
||||
see what was sent.
|
||||
|
||||
"""
|
||||
|
||||
maxDiff = None
|
||||
|
||||
# make sure to mock away all time-keeping elements
|
||||
@patch(
|
||||
"evennia.contrib.tutorials.evadventure.combat.EvAdventureCombatHandler.interval",
|
||||
new=-1,
|
||||
)
|
||||
@patch(
|
||||
"evennia.contrib.tutorials.evadventure.combat.delay",
|
||||
new=Mock(return_value=None),
|
||||
)
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.location = create.create_object(EvAdventureRoom, key="testroom")
|
||||
self.combatant = create.create_object(
|
||||
EvAdventureCharacter, key="testchar", location=self.location
|
||||
@ -58,8 +51,82 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||
self.combatant.msg = Mock()
|
||||
self.target.msg = Mock()
|
||||
|
||||
self.combathandler = combat.get_or_create_combathandler(self.combatant)
|
||||
|
||||
class TestEvAdventureCombatHandlerBase(_CombatTestBase):
|
||||
"""
|
||||
Test the base functionality of the base combat handler.
|
||||
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""This also tests the `get_or_create_combathandler` classfunc"""
|
||||
super().setUp()
|
||||
self.combathandler = combat_base.EvAdventureCombatHandlerBase.get_or_create_combathandler(
|
||||
self.location, key="combathandler"
|
||||
)
|
||||
|
||||
def test_combathandler_msg(self):
|
||||
"""Test sending messages to all in handler"""
|
||||
|
||||
self.location.msg_contents = Mock()
|
||||
|
||||
self.combathandler.msg("test_message")
|
||||
|
||||
self.location.msg_contents.assert_called_with(
|
||||
"test_message",
|
||||
exclude=[],
|
||||
from_obj=None,
|
||||
mapping={"testchar": self.combatant, "testmonster": self.target},
|
||||
)
|
||||
|
||||
def test_get_combat_summary(self):
|
||||
"""Test combat summary"""
|
||||
|
||||
self.combathandler.get_sides = Mock(return_value=(self.combatant, self.target))
|
||||
|
||||
# as seen from one side
|
||||
result = str(self.combathandler.get_combat_summary(self.combatant))
|
||||
|
||||
self.assertEqual(
|
||||
strip_ansi(result),
|
||||
" testchar (Perfect) vs testmonster (Perfect) ",
|
||||
)
|
||||
|
||||
# as seen from other side
|
||||
result = str(self.combathandler.get_combat_summary(self.target))
|
||||
|
||||
self.assertEqual(
|
||||
strip_ansi(result),
|
||||
" testmonster (Perfect) vs testchar (Perfect) ",
|
||||
)
|
||||
|
||||
|
||||
class EvAdventureTurnbasedCombatHandlerTest(_CombatTestBase):
|
||||
"""
|
||||
Test methods on the turn-based combat handler and actions
|
||||
|
||||
"""
|
||||
|
||||
maxDiff = None
|
||||
|
||||
# make sure to mock away all time-keeping elements
|
||||
@patch(
|
||||
"evennia.contrib.tutorials.evadventure.combat_turnbased.EvAdventureTurnbasedCombatHandler.interval", # noqa
|
||||
new=-1,
|
||||
)
|
||||
@patch(
|
||||
"evennia.contrib.tutorials.evadventure.combat_turnbased.delay",
|
||||
new=Mock(return_value=None),
|
||||
)
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# add target to combat
|
||||
self.combathandler = (
|
||||
combat_turnbased.EvAdventureTurnebasedCombatHandler.get_or_create_combathandler(
|
||||
self.location, key="combathandler"
|
||||
)
|
||||
)
|
||||
self.combathandler.add_combatant(self.combatant)
|
||||
self.combathandler.add_combatant(self.target)
|
||||
|
||||
def _get_action(self, action_dict={"key": "hold"}):
|
||||
@ -86,16 +153,16 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||
"""Testing all is set up correctly in the combathandler"""
|
||||
|
||||
chandler = self.combathandler
|
||||
self.assertEqual(dict(chandler.combatants), {self.combatant: deque(), self.target: deque()})
|
||||
self.assertEqual(dict(chandler.combatants), {self.combatant: {}, self.target: {}})
|
||||
self.assertEqual(
|
||||
dict(chandler.action_classes),
|
||||
{
|
||||
"hold": combat.CombatActionHold,
|
||||
"attack": combat.CombatActionAttack,
|
||||
"stunt": combat.CombatActionStunt,
|
||||
"use": combat.CombatActionUseItem,
|
||||
"wield": combat.CombatActionWield,
|
||||
"flee": combat.CombatActionFlee,
|
||||
"hold": combat_turnbased.CombatActionHold,
|
||||
"attack": combat_turnbased.CombatActionAttack,
|
||||
"stunt": combat_turnbased.CombatActionStunt,
|
||||
"use": combat_turnbased.CombatActionUseItem,
|
||||
"wield": combat_turnbased.CombatActionWield,
|
||||
"flee": combat_turnbased.CombatActionFlee,
|
||||
},
|
||||
)
|
||||
self.assertEqual(chandler.flee_timeout, 1)
|
||||
@ -104,20 +171,6 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||
self.assertEqual(dict(chandler.fleeing_combatants), {})
|
||||
self.assertEqual(dict(chandler.defeated_combatants), {})
|
||||
|
||||
def test_combathandler_msg(self):
|
||||
"""Test sending messages to all in handler"""
|
||||
|
||||
self.location.msg_contents = Mock()
|
||||
|
||||
self.combathandler.msg("test_message")
|
||||
|
||||
self.location.msg_contents.assert_called_with(
|
||||
"test_message",
|
||||
exclude=[],
|
||||
from_obj=None,
|
||||
mapping={"testchar": self.combatant, "testmonster": self.target},
|
||||
)
|
||||
|
||||
def test_remove_combatant(self):
|
||||
"""Remove a combatant."""
|
||||
|
||||
@ -154,25 +207,6 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||
allies, enemies = self.combathandler.get_sides(self.target)
|
||||
self.assertEqual((allies, enemies), ([target2], [self.combatant, combatant2]))
|
||||
|
||||
def test_get_combat_summary(self):
|
||||
"""Test combat summary"""
|
||||
|
||||
# as seen from one side
|
||||
result = str(self.combathandler.get_combat_summary(self.combatant))
|
||||
|
||||
self.assertEqual(
|
||||
strip_ansi(result),
|
||||
" testchar (Perfect) vs testmonster (Perfect) ",
|
||||
)
|
||||
|
||||
# as seen from other side
|
||||
result = str(self.combathandler.get_combat_summary(self.target))
|
||||
|
||||
self.assertEqual(
|
||||
strip_ansi(result),
|
||||
" testmonster (Perfect) vs testchar (Perfect) ",
|
||||
)
|
||||
|
||||
def test_queue_and_execute_action(self):
|
||||
"""Queue actions and execute"""
|
||||
|
||||
@ -210,31 +244,6 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||
[call(self.combatant), call(self.target)], any_order=True
|
||||
)
|
||||
|
||||
def test_combat_action(self):
|
||||
"""General tests of action functionality"""
|
||||
|
||||
combatant = self.combatant
|
||||
target = self.target
|
||||
|
||||
action = self._get_action({"key": "hold"})
|
||||
|
||||
self.assertTrue(action.can_use())
|
||||
|
||||
action.give_advantage(combatant, target)
|
||||
action.give_disadvantage(combatant, target)
|
||||
|
||||
self.assertTrue(action.has_advantage(combatant, target))
|
||||
self.assertTrue(action.has_disadvantage(combatant, target))
|
||||
|
||||
action.lose_advantage(combatant, target)
|
||||
action.lose_disadvantage(combatant, target)
|
||||
|
||||
self.assertFalse(action.has_advantage(combatant, target))
|
||||
self.assertFalse(action.has_disadvantage(combatant, target))
|
||||
|
||||
action.msg(f"$You() attack $You({target.key}).")
|
||||
combatant.msg.assert_called_with(text=("You attack testmonster.", {}), from_obj=combatant)
|
||||
|
||||
def test_action__hold(self):
|
||||
"""Hold, doing nothing"""
|
||||
|
||||
@ -246,7 +255,6 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||
|
||||
@patch("evennia.contrib.tutorials.evadventure.combat.rules.randint")
|
||||
def test_attack__miss(self, mock_randint):
|
||||
|
||||
actiondict = {"key": "attack", "target": self.target}
|
||||
|
||||
mock_randint.return_value = 8 # target has default armor 11, so 8+1 str will miss
|
||||
@ -298,7 +306,7 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||
"stunt_type": Ability.STR,
|
||||
"defense_type": Ability.DEX,
|
||||
}
|
||||
mock_randint.return_value = 11 # 11+1 dex vs DEX 11 defence is success
|
||||
mock_randint.return_value = 11 # 11+1 dex vs DEX 11 defence is success
|
||||
self._run_actions(action_dict)
|
||||
self.assertEqual(
|
||||
bool(self.combathandler.advantage_matrix[self.combatant][self.target]), True
|
||||
@ -314,7 +322,7 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||
"stunt_type": Ability.STR,
|
||||
"defense_type": Ability.DEX,
|
||||
}
|
||||
mock_randint.return_value = 11 # 11+1 dex vs DEX 11 defence is success
|
||||
mock_randint.return_value = 11 # 11+1 dex vs DEX 11 defence is success
|
||||
self._run_actions(action_dict)
|
||||
self.assertEqual(
|
||||
bool(self.combathandler.disadvantage_matrix[self.target][self.combatant]), True
|
||||
@ -417,10 +425,32 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||
from_obj=self.combatant,
|
||||
)
|
||||
# Check that enemies have advantage against you now
|
||||
action = combat.CombatAction(self.combathandler, self.target, {"key": "hold"})
|
||||
action = combat_turnbased.CombatAction(self.combathandler, self.target, {"key": "hold"})
|
||||
self.assertTrue(action.has_advantage(self.target, self.combatant))
|
||||
|
||||
# second flee should remove combatant
|
||||
self._run_actions(action_dict)
|
||||
# this ends combat, so combathandler should be gone
|
||||
self.assertIsNone(self.combathandler.pk)
|
||||
|
||||
|
||||
class TestEvAdventureTwitchCombatHandler(EvenniaCommandTestMixin, _CombatTestBase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.combatant_combathandler = (
|
||||
combat_twitch.EvAdventureCombatTwitchHandler.get_or_create_combathandler(
|
||||
self.combatant, key="combathandler"
|
||||
)
|
||||
)
|
||||
self.target_combathandler = (
|
||||
combat_twitch.EvAdventureCombatTwitchHandler.get_or_create_combathandler(
|
||||
self.target, key="combathandler"
|
||||
)
|
||||
)
|
||||
|
||||
def test_get_sides(self):
|
||||
""" """
|
||||
|
||||
def test_queue_and_execute_action(self):
|
||||
""" """
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user