Cleanup combat syntax, add flake8 config for legacy compat

This commit is contained in:
Griatch 2023-04-09 19:22:39 +02:00
parent 049e4fbb35
commit e88e6d1b1b
5 changed files with 209 additions and 252 deletions

2
.flake8 Normal file
View file

@ -0,0 +1,2 @@
[flake8]
max-line-length = 100

View file

@ -12,24 +12,12 @@ This establishes the basic building blocks for combat:
""" """
import random from evennia import create_script
from collections import defaultdict, deque
from evennia import CmdSet, Command, create_script, default_cmds
from evennia.commands.command import InterruptCommand
from evennia.scripts.scripts import DefaultScript from evennia.scripts.scripts import DefaultScript
from evennia.typeclasses.attributes import AttributeProperty from evennia.typeclasses.attributes import AttributeProperty
from evennia.utils import dbserialize, delay, evmenu, evtable, logger from evennia.utils import evtable
from evennia.utils.utils import display_len, inherits_from, list_to_string, pad
from . import rules 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): class CombatFailure(RuntimeError):
@ -39,7 +27,7 @@ class CombatFailure(RuntimeError):
""" """
# Combat action classes # Combaw action classes
class CombatAction: class CombatAction:
@ -149,9 +137,9 @@ class CombatActionAttack(CombatAction):
class CombatActionStunt(CombatAction): class CombatActionStunt(CombatAction):
""" """
Perform a stunt the grants a beneficiary (can be self) advantage on their next action against a 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 target. Whenever performing a stunt that would affect another negatively (giving them
against an ally, or granting an advantage against them, we need to make a check first. We don't disadvantage against an ally, or granting an advantage against them, we need to make a check
do a check if giving an advantage to an ally or ourselves. first. We don't do a check if giving an advantage to an ally or ourselves.
action_dict = { action_dict = {
"key": "stunt", "key": "stunt",
@ -159,8 +147,8 @@ class CombatActionStunt(CombatAction):
"target": Character/NPC, "target": Character/NPC,
"advantage": bool, # if False, it's a disadvantage "advantage": bool, # if False, it's a disadvantage
"stunt_type": Ability, # what ability (like STR, DEX etc) to use to perform this stunt. "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 "defense_type": Ability, # what ability to use to defend against (negative) effects of
stunt. this stunt.
} }
Note: Note:
@ -298,7 +286,7 @@ class EvAdventureCombatHandlerBase(DefaultScript):
fallback_action_dict = AttributeProperty({"key": "hold"}, autocreate=False) fallback_action_dict = AttributeProperty({"key": "hold"}, autocreate=False)
@classmethod @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`. 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 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). a location (for turn-based combat) or a Character (for twitch-based combat).
Keyword Args: 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. **kwargs: Arguments to the Script, if it is created.
""" """
if not obj: if not obj:
raise CombatFailure("Cannot start combat without a place to do it!") raise CombatFailure("Cannot start combat without a place to do it!")
combathandler_key = kwargs.pop("key", "combathandler")
combathandler = obj.ndb.combathandler combathandler = obj.ndb.combathandler
if not combathandler: if not combathandler:
combathandler = obj.scripts.get(combathandler_name).first() combathandler = obj.scripts.get(combathandler_key).first()
if not combathandler: if not combathandler:
# have to create from scratch # have to create from scratch
persistent = kwargs.pop("persistent", True) persistent = kwargs.pop("persistent", True)
combathandler = create_script( 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 return combathandler
def msg(self, message, combatant=None, broadcast=True): def msg(self, message, combatant=None, broadcast=True):
@ -438,7 +432,7 @@ class EvAdventureCombatHandlerBase(DefaultScript):
this with group mechanics). this with group mechanics).
""" """
raise NotImplemented raise NotImplementedError
def give_advantage(self, recipient, target): def give_advantage(self, recipient, target):
""" """
@ -452,7 +446,7 @@ class EvAdventureCombatHandlerBase(DefaultScript):
some future boost) some future boost)
""" """
raise NotImplemented raise NotImplementedError
def give_disadvantage(self, recipient, target): def give_disadvantage(self, recipient, target):
""" """
@ -460,10 +454,11 @@ class EvAdventureCombatHandlerBase(DefaultScript):
Args: Args:
recipient (Character or NPC): The one to get the disadvantage. 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): def has_advantage(self, combatant, target):
""" """
@ -474,7 +469,7 @@ class EvAdventureCombatHandlerBase(DefaultScript):
target (Character or NPC): The target to check advantage against. target (Character or NPC): The target to check advantage against.
""" """
raise NotImplemented raise NotImplementedError
def has_disadvantage(self, combatant, target): def has_disadvantage(self, combatant, target):
""" """
@ -485,7 +480,7 @@ class EvAdventureCombatHandlerBase(DefaultScript):
target (Character or NPC): The target to check disadvantage against. target (Character or NPC): The target to check disadvantage against.
""" """
raise NotImplemented raise NotImplementedError
def queue_action(self, combatant, action_dict): def queue_action(self, combatant, action_dict):
""" """
@ -503,7 +498,7 @@ class EvAdventureCombatHandlerBase(DefaultScript):
to make room. to make room.
""" """
raise NotImplemented raise NotImplementedError
def execute_next_action(self, combatant): def execute_next_action(self, combatant):
""" """
@ -514,14 +509,14 @@ class EvAdventureCombatHandlerBase(DefaultScript):
""" """
raise NotImplemented raise NotImplementedError
def start_combat(self): def start_combat(self):
""" """
Start combat. Start combat.
""" """
raise NotImplemented raise NotImplementedError
def check_stop_combat(self): def check_stop_combat(self):
""" """
@ -537,10 +532,10 @@ class EvAdventureCombatHandlerBase(DefaultScript):
bool: If `True`, the `stop_combat` method sho bool: If `True`, the `stop_combat` method sho
""" """
raise NotImplemented raise NotImplementedError
def stop_combat(self): def stop_combat(self):
""" """
Stop combat. This should also do all cleanup. Stop combat. This should also do all cleanup.
""" """
raise NotImplemented raise NotImplementedError

View file

@ -3,98 +3,35 @@ EvAdventure Turn-based combat
This implements a turn-based (Final Fantasy, etc) style of MUD 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 In this variation, all combatants are sharing the same combat handler, sitting on the current room.
will be repeated. This means that a 'twitch' style combat can be created using the same The user will receive a menu of combat options and each combatat has a certain time time (e.g. 30s)
mechanism, by just speeding up each 'turn'. 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 With this example, all chosen combat actions are considered to happen at the same time (so you are
of combat and handles all timing elements. 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 Unlike in twitch-like combat, there is no movement while in turn-based combat. Fleeing is a select
their turns simultaneously with minimum downtime. 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 ( from .combat_base import (
CombatAction, CombatAction,
CombatActionAttack,
CombatActionHold, CombatActionHold,
CombatActionStunt, CombatActionStunt,
CombatActionUserItem, CombatActionUseItem,
CombatActionWield, CombatActionWield,
EvAdventureCombatHandler, EvAdventureCombatHandlerBase,
) )
from .enums import Ability 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. A version of the combathandler, handling turn-based combat.
@ -175,31 +112,32 @@ class EvAdventureTurnbasedCombatHandler(EvAdventureCombatHandler):
# usable script properties # usable script properties
# .is_active - show if timer is running # .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. Let a benefiter gain advantage against the target.
Args: 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. 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 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 could (in principle) be the same as the benefiter (e.g. gaining advantage on
some future boost) 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. Let an affected party gain disadvantage against a target.
Args: Args:
recipient (Character or NPC): The one to get the disadvantage. 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.disadvantage_matrix[combatant][target] = True
self.combathandler.advantage_matrix[recipient][target] = False self.combathandler.advantage_matrix[combatant][target] = False
def has_advantage(self, combatant, target, **kwargs): def has_advantage(self, combatant, target, **kwargs):
""" """
@ -210,7 +148,7 @@ class EvAdventureTurnbasedCombatHandler(EvAdventureCombatHandler):
target (Character or NPC): The target to check advantage against. 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 target in self.combathandler.fleeing_combatants
) )
@ -223,11 +161,9 @@ class EvAdventureTurnbasedCombatHandler(EvAdventureCombatHandler):
target (Character or NPC): The target to check disadvantage against. target (Character or NPC): The target to check disadvantage against.
""" """
return bool(self.combathandler.disadvantage_matrix[combatant].pop(target, False)) or (
def has_disadvantage(self, recipient, target): combatant in self.combathandler.fleeing_combatants
return bool(self.combathandler.disadvantage_matrix[recipient].pop(target, False)) or ( )
recipient in self.combathandler.fleeing_combatants
)
def add_combatant(self, combatant): def add_combatant(self, combatant):
""" """
@ -370,7 +306,7 @@ class EvAdventureTurnbasedCombatHandler(EvAdventureCombatHandler):
""" """
# this gets the next dict and rotates the queue # 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 # use the action-dict to select and create an action from an action class
action_class = self.action_classes[action_dict["key"]] 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. # Add this command to the Character cmdset to make turn-based combat available.
class _CmdTurnCombatBase(_CmdCombatBase): class CmdTurnAttack(Command):
"""
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):
""" """
Start or join combat. Start or join combat.
@ -800,7 +725,7 @@ class CmdTurnAttack(_CmdTurnCombatBase):
return return
if not hasattr(target, "hp"): if not hasattr(target, "hp"):
self.msg(f"You can't attack that.") self.msg("You can't attack that.")
return return
elif target.hp <= 0: elif target.hp <= 0:
self.msg(f"{target.get_display_name(self.caller)} is already down.") 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!") self.msg("PvP combat is not allowed here!")
return return
combathandler = EvAdventureTurnbasedCombatHandler.get_or_create_combathandler(
self.caller.location
)
# add combatants to combathandler. this can be done safely over and over # add combatants to combathandler. this can be done safely over and over
self.combathandler.add_combatant(self.caller) combathandler.add_combatant(self.caller)
self.combathandler.queue_action(self.caller, {"key": "attack", "target": target}) combathandler.queue_action(self.caller, {"key": "attack", "target": target})
self.combathandler.add_combatant(target) combathandler.add_combatant(target)
self.combathandler.start_combat() combathandler.start_combat()
# build and start the menu # build and start the menu
evmenu.EvMenu( EvMenu(
self.caller, self.caller,
{ {
"node_choose_enemy_target": node_choose_enemy_target, "node_choose_enemy_target": node_choose_enemy_target,

View file

@ -4,21 +4,20 @@ EvAdventure Twitch-based combat
This implements a 'twitch' (aka DIKU or other traditional muds) style of MUD 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.commands.command import Command, InterruptCommand
from evennia.scripts.scripts import DefaultScript from evennia.utils.utils import display_len, inherits_from, list_to_string, pad, repeat, unrepeat
from evennia.utils.create import create_script
from evennia.utils.utils import repeat, unrepeat
from .combat import ( from .characters import EvAdventureCharacter
from .combat_base import (
CombatActionAttack, CombatActionAttack,
CombatActionHold, CombatActionHold,
CombatActionStunt, CombatActionStunt,
CombatActionUserItem, CombatActionUseItem,
CombatActionWield, CombatActionWield,
EvAdventureCombatHandlerBase, EvAdventureCombatHandlerBase,
) )
from .enums import ABILITY_REVERSE_MAP, Ability, ObjType from .enums import ABILITY_REVERSE_MAP
class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase): class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
@ -44,7 +43,7 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
disadvantages_against = AttributeProperty(dict) disadvantages_against = AttributeProperty(dict)
action_dict = 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 # stores the current ticker reference, so we can manipulate it later
current_ticker_ref = AttributeProperty(None) current_ticker_ref = AttributeProperty(None)
@ -107,7 +106,8 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
Args: Args:
recipient (Character or NPC): The one to get the disadvantage. 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 self.disadvantages_against[target] = True
@ -166,6 +166,7 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
""" """
Triggered after a delay by the command Triggered after a delay by the command
""" """
combatant = self.obj
action_dict = self.action_dict action_dict = self.action_dict
action_class = self.action_classes[action_dict["key"]] action_class = self.action_classes[action_dict["key"]]
action = action_class(self, combatant, action_dict) action = action_class(self, combatant, action_dict)
@ -176,8 +177,8 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
if not action_dict.get("repeat", True): if not action_dict.get("repeat", True):
# not a repeating action, use the fallback (normally the original attack) # not a repeating action, use the fallback (normally the original attack)
self.action_dict = fallback_action_dict self.action_dict = self.fallback_action_dict
self.queue_action(fallback_action_dict.get("dt", 0)) self.queue_action(self.fallback_action_dict.get("dt", 0))
def check_stop_combat(self): def check_stop_combat(self):
# check if one side won the battle. # check if one side won the battle.
@ -243,7 +244,7 @@ class _BaseTwitchCombatCommand(Command):
Get or create the combathandler assigned to this combatant. 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): class CmdAttack(_BaseTwitchCombatCommand):
@ -261,7 +262,7 @@ class CmdAttack(_BaseTwitchCombatCommand):
help_category = "combat" help_category = "combat"
def func(self): def func(self):
target = self.search(lhs) target = self.search(self.lhs)
if not target: if not target:
return 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 Wield a weapon or spell-rune. You will the wield the item, swapping with any other item(s) you
were wielded before. were wielded before.

View file

@ -8,37 +8,30 @@ from unittest.mock import Mock, call, patch
from evennia.utils import create from evennia.utils import create
from evennia.utils.ansi import strip_ansi 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 ..characters import EvAdventureCharacter
from ..enums import Ability, WieldLocation from ..enums import Ability, WieldLocation
from ..npcs import EvAdventureMob from ..npcs import EvAdventureMob
from ..objects import EvAdventureConsumable, EvAdventureRunestone, EvAdventureWeapon from ..objects import EvAdventureConsumable, EvAdventureRunestone, EvAdventureWeapon
from ..rooms import EvAdventureRoom 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): def setUp(self):
super().setUp()
self.location = create.create_object(EvAdventureRoom, key="testroom") self.location = create.create_object(EvAdventureRoom, key="testroom")
self.combatant = create.create_object( self.combatant = create.create_object(
EvAdventureCharacter, key="testchar", location=self.location EvAdventureCharacter, key="testchar", location=self.location
@ -58,8 +51,82 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
self.combatant.msg = Mock() self.combatant.msg = Mock()
self.target.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 # 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) self.combathandler.add_combatant(self.target)
def _get_action(self, action_dict={"key": "hold"}): def _get_action(self, action_dict={"key": "hold"}):
@ -86,16 +153,16 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
"""Testing all is set up correctly in the combathandler""" """Testing all is set up correctly in the combathandler"""
chandler = self.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( self.assertEqual(
dict(chandler.action_classes), dict(chandler.action_classes),
{ {
"hold": combat.CombatActionHold, "hold": combat_turnbased.CombatActionHold,
"attack": combat.CombatActionAttack, "attack": combat_turnbased.CombatActionAttack,
"stunt": combat.CombatActionStunt, "stunt": combat_turnbased.CombatActionStunt,
"use": combat.CombatActionUseItem, "use": combat_turnbased.CombatActionUseItem,
"wield": combat.CombatActionWield, "wield": combat_turnbased.CombatActionWield,
"flee": combat.CombatActionFlee, "flee": combat_turnbased.CombatActionFlee,
}, },
) )
self.assertEqual(chandler.flee_timeout, 1) self.assertEqual(chandler.flee_timeout, 1)
@ -104,20 +171,6 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
self.assertEqual(dict(chandler.fleeing_combatants), {}) self.assertEqual(dict(chandler.fleeing_combatants), {})
self.assertEqual(dict(chandler.defeated_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): def test_remove_combatant(self):
"""Remove a combatant.""" """Remove a combatant."""
@ -154,25 +207,6 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
allies, enemies = self.combathandler.get_sides(self.target) allies, enemies = self.combathandler.get_sides(self.target)
self.assertEqual((allies, enemies), ([target2], [self.combatant, combatant2])) 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): def test_queue_and_execute_action(self):
"""Queue actions and execute""" """Queue actions and execute"""
@ -210,31 +244,6 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
[call(self.combatant), call(self.target)], any_order=True [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): def test_action__hold(self):
"""Hold, doing nothing""" """Hold, doing nothing"""
@ -246,7 +255,6 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
@patch("evennia.contrib.tutorials.evadventure.combat.rules.randint") @patch("evennia.contrib.tutorials.evadventure.combat.rules.randint")
def test_attack__miss(self, mock_randint): def test_attack__miss(self, mock_randint):
actiondict = {"key": "attack", "target": self.target} actiondict = {"key": "attack", "target": self.target}
mock_randint.return_value = 8 # target has default armor 11, so 8+1 str will miss 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, "stunt_type": Ability.STR,
"defense_type": Ability.DEX, "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._run_actions(action_dict)
self.assertEqual( self.assertEqual(
bool(self.combathandler.advantage_matrix[self.combatant][self.target]), True bool(self.combathandler.advantage_matrix[self.combatant][self.target]), True
@ -314,7 +322,7 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
"stunt_type": Ability.STR, "stunt_type": Ability.STR,
"defense_type": Ability.DEX, "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._run_actions(action_dict)
self.assertEqual( self.assertEqual(
bool(self.combathandler.disadvantage_matrix[self.target][self.combatant]), True bool(self.combathandler.disadvantage_matrix[self.target][self.combatant]), True
@ -417,10 +425,32 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
from_obj=self.combatant, from_obj=self.combatant,
) )
# Check that enemies have advantage against you now # 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)) self.assertTrue(action.has_advantage(self.target, self.combatant))
# second flee should remove combatant # second flee should remove combatant
self._run_actions(action_dict) self._run_actions(action_dict)
# this ends combat, so combathandler should be gone # this ends combat, so combathandler should be gone
self.assertIsNone(self.combathandler.pk) 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):
""" """