Testing out combat
This commit is contained in:
parent
97b11ccea7
commit
5c914eb8b0
7 changed files with 311 additions and 154 deletions
|
|
@ -19,6 +19,10 @@ py self.cmdset.add("evennia.contrib.tutorials.evadventure.combat.TwitchAttackCmd
|
||||||
|
|
||||||
create sword:evennia.contrib.tutorials.evadventure.objects.EvAdventureWeapon
|
create sword:evennia.contrib.tutorials.evadventure.objects.EvAdventureWeapon
|
||||||
|
|
||||||
|
# create a consumable to use
|
||||||
|
|
||||||
|
create potion:evennia.contrib.tutorials.evadventure.objects.EvAdventureConsumable
|
||||||
|
|
||||||
# dig a combat arena
|
# dig a combat arena
|
||||||
|
|
||||||
dig arena:evennia.contrib.tutorials.evadventure.rooms.EvAdventureRoom = arena,back
|
dig arena:evennia.contrib.tutorials.evadventure.rooms.EvAdventureRoom = arena,back
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ 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 dbserialize, delay, evmenu, evtable, logger
|
||||||
from evennia.utils.utils import inherits_from, list_to_string
|
from evennia.utils.utils import display_len, inherits_from, list_to_string, pad
|
||||||
|
|
||||||
from . import rules
|
from . import rules
|
||||||
from .characters import EvAdventureCharacter
|
from .characters import EvAdventureCharacter
|
||||||
|
|
@ -209,15 +209,15 @@ class CombatAction:
|
||||||
self.combathandler.fleeing_combatants.pop(self.combatant, None)
|
self.combathandler.fleeing_combatants.pop(self.combatant, None)
|
||||||
|
|
||||||
|
|
||||||
class CombatActionDoNothing(CombatAction):
|
class CombatActionHold(CombatAction):
|
||||||
"""
|
"""
|
||||||
Action that does nothing.
|
Action that does nothing.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
Refer to as 'nothing'
|
Refer to as 'hold'
|
||||||
|
|
||||||
action_dict = {
|
action_dict = {
|
||||||
"key": "nothing"
|
"key": "hold"
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -287,9 +287,6 @@ class CombatActionStunt(CombatAction):
|
||||||
# to give.
|
# to give.
|
||||||
defender = target if self.advantage else recipient
|
defender = target if self.advantage else recipient
|
||||||
|
|
||||||
self.stunt_type = ABILITY_REVERSE_MAP.get(self.stunt_type, self.stunt_type)
|
|
||||||
self.defense_type = ABILITY_REVERSE_MAP.get(self.defense_type, self.defense_type)
|
|
||||||
|
|
||||||
if not is_success:
|
if not is_success:
|
||||||
# trying to give advantage to recipient against target. Target defends against caller
|
# trying to give advantage to recipient against target. Target defends against caller
|
||||||
is_success, _, txt = rules.dice.opposed_saving_throw(
|
is_success, _, txt = rules.dice.opposed_saving_throw(
|
||||||
|
|
@ -302,19 +299,19 @@ class CombatActionStunt(CombatAction):
|
||||||
)
|
)
|
||||||
|
|
||||||
# deal with results
|
# deal with results
|
||||||
self.msg(f"$You() $conj(attempt) stunt on $You(defender.key). {txt}")
|
self.msg(f"$You() $conj(attempt) stunt on $You({defender.key}). {txt}")
|
||||||
if is_success:
|
if is_success:
|
||||||
if self.advantage:
|
if self.advantage:
|
||||||
self.give_advantage(recipient, target)
|
self.give_advantage(recipient, target)
|
||||||
else:
|
else:
|
||||||
self.give_disadvantage(recipient, target)
|
self.give_disadvantage(recipient, target)
|
||||||
self.msg(
|
self.msg(
|
||||||
f"%You() $conj(cause) $You({recipient.key}) "
|
f"$You() $conj(cause) $You({recipient.key}) "
|
||||||
f"to gain {'advantage' if self.advantage else 'disadvantage'} "
|
f"to gain {'advantage' if self.advantage else 'disadvantage'} "
|
||||||
f"against $You({target.key})!"
|
f"against $You({target.key})!"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.msg(f"$You({target.key}) resists! $You() $conj(fail) the stunt.")
|
self.msg(f"$You({target.key}) $conj(resist)! $You() $conj(fail) the stunt.")
|
||||||
|
|
||||||
|
|
||||||
class CombatActionUseItem(CombatAction):
|
class CombatActionUseItem(CombatAction):
|
||||||
|
|
@ -384,15 +381,23 @@ class CombatActionFlee(CombatAction):
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
|
|
||||||
if self.combatant not in self.combathandler.fleeing_combatants:
|
combathandler = self.combathandler
|
||||||
# we record the turn on which we started fleeing
|
|
||||||
self.combathandler.fleeing_combatants[self.combatant] = self.combathandler.turn
|
|
||||||
|
|
||||||
flee_timeout = self.combathandler.flee_timeout
|
if self.combatant not in combathandler.fleeing_combatants:
|
||||||
self.msg(
|
# we record the turn on which we started fleeing
|
||||||
"$You() $conj(retreat), leaving yourself exposed while doing so (will escape in "
|
combathandler.fleeing_combatants[self.combatant] = self.combathandler.turn
|
||||||
f"{flee_timeout} $pluralize(turn, {flee_timeout}))."
|
|
||||||
)
|
# show how many turns until successful flight
|
||||||
|
current_turn = combathandler.turn
|
||||||
|
started_fleeing = combathandler.fleeing_combatants[self.combatant]
|
||||||
|
flee_timeout = combathandler.flee_timeout
|
||||||
|
time_left = flee_timeout - (current_turn - started_fleeing)
|
||||||
|
|
||||||
|
if time_left > 0:
|
||||||
|
self.msg(
|
||||||
|
"$You() $conj(retreat), being exposed to attack while doing so (will escape in "
|
||||||
|
f"{time_left} $pluralize(turn, {time_left}))."
|
||||||
|
)
|
||||||
|
|
||||||
def post_execute(self):
|
def post_execute(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -410,7 +415,7 @@ class EvAdventureCombatHandler(DefaultScript):
|
||||||
|
|
||||||
# available actions in combat
|
# available actions in combat
|
||||||
action_classes = {
|
action_classes = {
|
||||||
"nothing": CombatActionDoNothing,
|
"hold": CombatActionHold,
|
||||||
"attack": CombatActionAttack,
|
"attack": CombatActionAttack,
|
||||||
"stunt": CombatActionStunt,
|
"stunt": CombatActionStunt,
|
||||||
"use": CombatActionUseItem,
|
"use": CombatActionUseItem,
|
||||||
|
|
@ -422,10 +427,10 @@ class EvAdventureCombatHandler(DefaultScript):
|
||||||
max_action_queue_size = 1
|
max_action_queue_size = 1
|
||||||
|
|
||||||
# fallback action if not selecting anything
|
# fallback action if not selecting anything
|
||||||
fallback_action_dict = {"key": "nothing"}
|
fallback_action_dict = {"key": "hold"}
|
||||||
|
|
||||||
# how many turns you must be fleeing before escaping
|
# how many turns you must be fleeing before escaping
|
||||||
flee_timeout = 1
|
flee_timeout = 5
|
||||||
|
|
||||||
# persistent storage
|
# persistent storage
|
||||||
|
|
||||||
|
|
@ -441,6 +446,9 @@ class EvAdventureCombatHandler(DefaultScript):
|
||||||
fleeing_combatants = AttributeProperty(dict)
|
fleeing_combatants = AttributeProperty(dict)
|
||||||
defeated_combatants = AttributeProperty(list)
|
defeated_combatants = AttributeProperty(list)
|
||||||
|
|
||||||
|
# usable script properties
|
||||||
|
# .is_active - show if timer is running
|
||||||
|
|
||||||
def msg(self, message, combatant=None, broadcast=True):
|
def msg(self, message, combatant=None, broadcast=True):
|
||||||
"""
|
"""
|
||||||
Central place for sending messages to combatants. This allows
|
Central place for sending messages to combatants. This allows
|
||||||
|
|
@ -475,11 +483,14 @@ class EvAdventureCombatHandler(DefaultScript):
|
||||||
|
|
||||||
def add_combatant(self, combatant):
|
def add_combatant(self, combatant):
|
||||||
"""
|
"""
|
||||||
Add a new combatant to the battle.
|
Add a new combatant to the battle. Can be called multiple times safely.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
*combatants (EvAdventureCharacter, EvAdventureNPC): Any number of combatants to add to
|
*combatants (EvAdventureCharacter, EvAdventureNPC): Any number of combatants to add to
|
||||||
the combat.
|
the combat.
|
||||||
|
Returns:
|
||||||
|
bool: If this combatant was newly added or not (it was already in combat).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if combatant not in self.combatants:
|
if combatant not in self.combatants:
|
||||||
self.combatants[combatant] = deque((), maxlen=self.max_action_queue_size)
|
self.combatants[combatant] = deque((), maxlen=self.max_action_queue_size)
|
||||||
|
|
@ -496,6 +507,18 @@ class EvAdventureCombatHandler(DefaultScript):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.combatants.pop(combatant, None)
|
self.combatants.pop(combatant, None)
|
||||||
|
# clean up twitch cmdset if it exists
|
||||||
|
combatant.cmdset.remove(TwitchCombatCmdSet)
|
||||||
|
# clean up menu if it exists
|
||||||
|
|
||||||
|
def start_combat(self, **kwargs):
|
||||||
|
"""
|
||||||
|
This actually starts the combat. It's safe to run this multiple times
|
||||||
|
since it will only start combat if it isn't already running.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not self.is_active:
|
||||||
|
self.start(**kwargs)
|
||||||
|
|
||||||
def stop_combat(self):
|
def stop_combat(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -543,6 +566,69 @@ class EvAdventureCombatHandler(DefaultScript):
|
||||||
enemies = pcs
|
enemies = pcs
|
||||||
return allies, enemies
|
return allies, enemies
|
||||||
|
|
||||||
|
def get_combat_summary(self, combatant):
|
||||||
|
"""
|
||||||
|
Get a 'battle report' - an overview of the current state of combat from the perspective
|
||||||
|
of one of the sides.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
combatant (EvAdventureCharacter, EvAdventureNPC): The combatant to get.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
EvTable: A table representing the current state of combat.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
::
|
||||||
|
|
||||||
|
Goblin shaman (Perfect)[attack]
|
||||||
|
Gregor (Hurt)[attack] Goblin brawler(Hurt)[attack]
|
||||||
|
Bob (Perfect)[stunt] vs Goblin grunt 1 (Hurt)[attack]
|
||||||
|
Goblin grunt 2 (Perfect)[hold]
|
||||||
|
Goblin grunt 3 (Wounded)[flee]
|
||||||
|
|
||||||
|
"""
|
||||||
|
allies, enemies = self.get_sides(combatant)
|
||||||
|
# we must include outselves at the top of the list (we are not returned from get_sides)
|
||||||
|
allies.insert(0, combatant)
|
||||||
|
nallies, nenemies = len(allies), len(enemies)
|
||||||
|
|
||||||
|
# prepare colors and hurt-levels
|
||||||
|
allies = [
|
||||||
|
f"{ally} ({ally.hurt_level})[{self.get_next_action_dict(ally)['key']}]"
|
||||||
|
for ally in allies
|
||||||
|
]
|
||||||
|
enemies = [
|
||||||
|
f"{enemy} ({enemy.hurt_level})[{self.get_next_action_dict(enemy)['key']}]"
|
||||||
|
for enemy in enemies
|
||||||
|
]
|
||||||
|
|
||||||
|
# the center column with the 'vs'
|
||||||
|
vs_column = ["" for _ in range(max(nallies, nenemies))]
|
||||||
|
vs_column[len(vs_column) // 2] = "|wvs|n"
|
||||||
|
|
||||||
|
# the two allies / enemies columns should be centered vertically
|
||||||
|
diff = abs(nallies - nenemies)
|
||||||
|
top_empty = diff // 2
|
||||||
|
bot_empty = diff - top_empty
|
||||||
|
topfill = ["" for _ in range(top_empty)]
|
||||||
|
botfill = ["" for _ in range(bot_empty)]
|
||||||
|
|
||||||
|
if nallies >= nenemies:
|
||||||
|
enemies = topfill + enemies + botfill
|
||||||
|
else:
|
||||||
|
allies = topfill + allies + botfill
|
||||||
|
|
||||||
|
# make a table with three columns
|
||||||
|
return evtable.EvTable(
|
||||||
|
table=[
|
||||||
|
evtable.EvColumn(*allies, align="l"),
|
||||||
|
evtable.EvColumn(*vs_column, align="c"),
|
||||||
|
evtable.EvColumn(*enemies, align="r"),
|
||||||
|
],
|
||||||
|
border=None,
|
||||||
|
maxwidth=78,
|
||||||
|
)
|
||||||
|
|
||||||
def queue_action(self, combatant, action_dict):
|
def queue_action(self, combatant, action_dict):
|
||||||
"""
|
"""
|
||||||
Queue an action by adding the new actiondict to the back of the queue. If the
|
Queue an action by adding the new actiondict to the back of the queue. If the
|
||||||
|
|
@ -568,10 +654,29 @@ class EvAdventureCombatHandler(DefaultScript):
|
||||||
# everyone has inserted an action. Start next turn without waiting!
|
# everyone has inserted an action. Start next turn without waiting!
|
||||||
self.force_repeat()
|
self.force_repeat()
|
||||||
|
|
||||||
|
def get_next_action_dict(self, combatant, rotate_queue=True):
|
||||||
|
"""
|
||||||
|
Give the action_dict for the next action that will be executed.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
combatant (EvAdventureCharacter, EvAdventureNPC): The combatant to get the action for.
|
||||||
|
rotate_queue (bool, optional): Rotate the queue after getting the action dict.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: The next action-dict in the queue.
|
||||||
|
|
||||||
|
"""
|
||||||
|
action_queue = self.combatants[combatant]
|
||||||
|
action_dict = action_queue[0] if action_queue else self.fallback_action_dict
|
||||||
|
if rotate_queue:
|
||||||
|
# rotate the queue to the left so that the first element is now the last one
|
||||||
|
action_queue.rotate(-1)
|
||||||
|
return action_dict
|
||||||
|
|
||||||
def execute_next_action(self, combatant):
|
def execute_next_action(self, combatant):
|
||||||
"""
|
"""
|
||||||
Perform a combatant's next queued action. Note that there is _always_ an action queued,
|
Perform a combatant's next queued action. Note that there is _always_ an action queued,
|
||||||
even if this action is 'do nothing'. We don't pop anything from the queue, instead we keep
|
even if this action is 'hold'. We don't pop anything from the queue, instead we keep
|
||||||
rotating the queue. When the queue has a length of one, this means just repeating the
|
rotating the queue. When the queue has a length of one, this means just repeating the
|
||||||
same action over and over.
|
same action over and over.
|
||||||
|
|
||||||
|
|
@ -584,10 +689,8 @@ class EvAdventureCombatHandler(DefaultScript):
|
||||||
queue will be rotated to the left and be `[b, c, a]` (so next time, `b` will be used).
|
queue will be rotated to the left and be `[b, c, a]` (so next time, `b` will be used).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
action_queue = self.combatants[combatant]
|
# this gets the next dict and rotates the queue
|
||||||
action_dict = action_queue[0] if action_queue else self.fallback_action_dict
|
action_dict = self.get_next_action_dict(combatant)
|
||||||
# rotate the queue to the left so that the first element is now the last one
|
|
||||||
action_queue.rotate(-1)
|
|
||||||
|
|
||||||
# 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"]]
|
||||||
|
|
@ -655,82 +758,38 @@ class EvAdventureCombatHandler(DefaultScript):
|
||||||
self.msg(txt)
|
self.msg(txt)
|
||||||
self.stop_combat()
|
self.stop_combat()
|
||||||
|
|
||||||
def get_combat_summary(self, combatant):
|
def at_repeat(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Get a 'battle report' - an overview of the current state of combat.
|
This is called every time the script ticks (how fast depends on if this handler runs a
|
||||||
|
twitch- or turn-based combat).
|
||||||
Args:
|
|
||||||
combatant (EvAdventureCharacter, EvAdventureNPC): The combatant to get.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
EvTable: A table representing the current state of combat.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
::
|
|
||||||
|
|
||||||
Goblin shaman
|
|
||||||
Ally (hurt) Goblin brawler
|
|
||||||
Bob vs Goblin grunt 1 (hurt)
|
|
||||||
Goblin grunt 2
|
|
||||||
Goblin grunt 3
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
allies, enemies = self.get_sides(combatant)
|
self.execute_full_turn()
|
||||||
# we must include outselves at the top of the list (we are not returned from get_sides)
|
|
||||||
allies.insert(0, combatant)
|
|
||||||
nallies, nenemies = len(allies), len(enemies)
|
|
||||||
|
|
||||||
# prepare colors and hurt-levels
|
|
||||||
allies = [f"{ally} ({ally.hurt_level})" for ally in allies]
|
|
||||||
enemies = [f"{enemy} ({enemy.hurt_level})" for enemy in enemies]
|
|
||||||
|
|
||||||
# the center column with the 'vs'
|
|
||||||
vs_column = ["" for _ in range(max(nallies, nenemies))]
|
|
||||||
vs_column[len(vs_column) // 2] = "vs"
|
|
||||||
|
|
||||||
# the two allies / enemies columns should be centered vertically
|
|
||||||
diff = abs(nallies - nenemies)
|
|
||||||
top_empty = diff // 2
|
|
||||||
bot_empty = diff - top_empty
|
|
||||||
topfill = ["" for _ in range(top_empty)]
|
|
||||||
botfill = ["" for _ in range(bot_empty)]
|
|
||||||
|
|
||||||
if nallies >= nenemies:
|
|
||||||
enemies = topfill + enemies + botfill
|
|
||||||
else:
|
|
||||||
allies = topfill + allies + botfill
|
|
||||||
|
|
||||||
# make a table with three columns
|
|
||||||
return evtable.EvTable(
|
|
||||||
table=[
|
|
||||||
evtable.EvColumn(*allies, align="l"),
|
|
||||||
evtable.EvColumn(*vs_column, align="c"),
|
|
||||||
evtable.EvColumn(*enemies, align="r"),
|
|
||||||
],
|
|
||||||
border=None,
|
|
||||||
width=78,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_or_create_combathandler(combatant, combathandler_name="combathandler", combat_tick=5):
|
def get_or_create_combathandler(location, combat_tick=3, combathandler_name="combathandler"):
|
||||||
"""
|
"""
|
||||||
Joins or continues combat. This is a access function that will either get the
|
Joins or continues combat. This is a access function that will either get the
|
||||||
combathandler on the current room or create a new one.
|
combathandler on the current room or create a new one.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
combatant (EvAdventureCharacter, EvAdventureNPC): The one to
|
location (EvAdventureRoom): Where to start the combat.
|
||||||
|
combat_tick (int): How often (in seconds) the combathandler will perform a tick. The
|
||||||
|
shorter this interval, the more 'twitch-like' the combat will be. E.g.
|
||||||
|
combathandler_name (str): If the combathandler should be stored with a different script
|
||||||
|
name. Changing this could allow multiple combats to coexist in the same location.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
CombatHandler: The new or created combathandler.
|
CombatHandler: The new or created combathandler.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
The combathandler starts disabled; one needs to run `.start` on it once all
|
||||||
|
(initial) combatants are added.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
location = combatant.location
|
|
||||||
|
|
||||||
if not location:
|
if not location:
|
||||||
raise CombatFailure("Cannot start combat without a location.")
|
raise CombatFailure("Cannot start combat without a location.")
|
||||||
|
|
||||||
combathandler = location.scripts.get(combathandler_name)
|
combathandler = location.scripts.get(combathandler_name).first()
|
||||||
if not combathandler:
|
if not combathandler:
|
||||||
combathandler = create_script(
|
combathandler = create_script(
|
||||||
EvAdventureCombatHandler,
|
EvAdventureCombatHandler,
|
||||||
|
|
@ -738,8 +797,8 @@ def get_or_create_combathandler(combatant, combathandler_name="combathandler", c
|
||||||
obj=location,
|
obj=location,
|
||||||
interval=combat_tick,
|
interval=combat_tick,
|
||||||
persistent=True,
|
persistent=True,
|
||||||
|
autostart=False,
|
||||||
)
|
)
|
||||||
combathandler.add_combatant(combatant)
|
|
||||||
return combathandler
|
return combathandler
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -766,9 +825,10 @@ Examples of commands:
|
||||||
- |yuse <item>|n - use/consume an item in your inventory
|
- |yuse <item>|n - use/consume an item in your inventory
|
||||||
- |yuse <item> on <target>|n - use an item on an enemy or ally
|
- |yuse <item> on <target>|n - use an item on an enemy or ally
|
||||||
|
|
||||||
|
- |yhold|n - hold your attack, doing nothing
|
||||||
- |yflee|n - start to flee or disengage from combat
|
- |yflee|n - start to flee or disengage from combat
|
||||||
|
|
||||||
Use |yhelp <command>|n for more info."""
|
Use |yhelp <command>|n for more info. Use |yhelp combat|n to re-show this list."""
|
||||||
|
|
||||||
|
|
||||||
class _CmdCombatBase(Command):
|
class _CmdCombatBase(Command):
|
||||||
|
|
@ -779,14 +839,16 @@ class _CmdCombatBase(Command):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
combathandler_name = "combathandler"
|
combathandler_name = "combathandler"
|
||||||
combat_tick = 2
|
combat_tick = 3
|
||||||
flee_timeout = 5
|
flee_timeout = 5
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def combathandler(self):
|
def combathandler(self):
|
||||||
combathandler = getattr(self, "combathandler", None)
|
combathandler = getattr(self, "_combathandler", None)
|
||||||
if not combathandler:
|
if not combathandler:
|
||||||
self.combathandler = combathandler = get_or_create_combathandler(self.caller)
|
self._combathandler = combathandler = get_or_create_combathandler(
|
||||||
|
self.caller.location, combat_tick=2
|
||||||
|
)
|
||||||
return combathandler
|
return combathandler
|
||||||
|
|
||||||
def parse(self):
|
def parse(self):
|
||||||
|
|
@ -805,16 +867,20 @@ class TwitchCombatCmdSet(CmdSet):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
name = "Twitchcombat cmdset"
|
||||||
priority = 1
|
priority = 1
|
||||||
mergetype = "Union" # use Replace to lock down all other commands
|
mergetype = "Union" # use Replace to lock down all other commands
|
||||||
no_exits = True # don't allow combatants to walk away
|
no_exits = True # don't allow combatants to walk away
|
||||||
|
|
||||||
def at_cmdset_creation(self):
|
def at_cmdset_creation(self):
|
||||||
self.add(CmdTwitchAttack())
|
self.add(CmdTwitchAttack())
|
||||||
|
self.add(CmdLook())
|
||||||
|
self.add(CmdHelpCombat())
|
||||||
|
self.add(CmdHold())
|
||||||
self.add(CmdStunt())
|
self.add(CmdStunt())
|
||||||
self.add(CmdUseItem())
|
self.add(CmdUseItem())
|
||||||
self.add(CmdWield())
|
self.add(CmdWield())
|
||||||
self.add(CmdUseFlee())
|
self.add(CmdFlee())
|
||||||
|
|
||||||
|
|
||||||
class CmdTwitchAttack(_CmdCombatBase):
|
class CmdTwitchAttack(_CmdCombatBase):
|
||||||
|
|
@ -852,47 +918,71 @@ class CmdTwitchAttack(_CmdCombatBase):
|
||||||
self.msg(f"{target.get_display_name(self.caller)} is already down.")
|
self.msg(f"{target.get_display_name(self.caller)} is already down.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# this can be done over and over
|
if target.is_pc and not target.location.allow_pvp:
|
||||||
|
self.msg("PvP combat is not allowed here!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# add combatants to combathandler. this can be done safely over and over
|
||||||
is_new = self.combathandler.add_combatant(self.caller)
|
is_new = self.combathandler.add_combatant(self.caller)
|
||||||
if is_new:
|
if is_new:
|
||||||
# just joined combat - add the combat cmdset
|
# just joined combat - add the combat cmdset
|
||||||
self.caller.cmdset.add(CombatCmdSet)
|
self.caller.cmdset.add(TwitchCombatCmdSet, persistent=True)
|
||||||
self.msg(_COMBAT_HELP)
|
self.msg(_COMBAT_HELP)
|
||||||
|
|
||||||
|
is_new = self.combathandler.add_combatant(target)
|
||||||
|
if is_new and target.is_pc:
|
||||||
|
# a pvp battle
|
||||||
|
target.cmdset.add(TwitchCombatCmdSet, persistent=True)
|
||||||
|
target.msg(_COMBAT_HELP)
|
||||||
|
|
||||||
self.combathandler.queue_action(self.caller, {"key": "attack", "target": target})
|
self.combathandler.queue_action(self.caller, {"key": "attack", "target": target})
|
||||||
self.msg("You prepare to attack!")
|
self.combathandler.start_combat()
|
||||||
|
self.msg(f"You attack {target.get_display_name(self.caller)}!")
|
||||||
|
|
||||||
|
|
||||||
class CmdLook(default_cmds.CmdLook):
|
class CmdLook(default_cmds.CmdLook):
|
||||||
|
|
||||||
key = "look"
|
|
||||||
aliases = ["l"]
|
|
||||||
|
|
||||||
template = """
|
|
||||||
|c{room_name} |r(In Combat!)|n
|
|
||||||
{room_desc}
|
|
||||||
⚔ ⚔ ⚔ ⚔ ⚔
|
|
||||||
{combat_summary}
|
|
||||||
""".strip()
|
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
if not self.args:
|
if not self.args:
|
||||||
# when looking around with no argument, show the room description followed by the
|
combathandler = get_or_create_combathandler(self.caller.location)
|
||||||
# current combat state.
|
txt = str(combathandler.get_combat_summary(self.caller))
|
||||||
location = self.caller.location
|
maxwidth = max(display_len(line) for line in txt.strip().split("\n"))
|
||||||
combathandler = get_or_create_combathandler(self.caller)
|
self.msg(f"|r{pad(' Combat Status ', width=maxwidth, fillchar='-')}|n\n{txt}")
|
||||||
|
|
||||||
self.caller.msg(
|
|
||||||
self.template.format(
|
|
||||||
room_name=location.get_display_name(self.caller),
|
|
||||||
room_desc=caller.at_look(location),
|
|
||||||
combat_summary=combathandler.get_combat_summary(self.caller),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
# use regular look to look at things
|
# use regular look to look at things
|
||||||
super().func()
|
super().func()
|
||||||
|
|
||||||
|
|
||||||
|
class CmdHelpCombat(_CmdCombatBase):
|
||||||
|
"""
|
||||||
|
Re-show the combat command summary.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
help combat
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = "help combat"
|
||||||
|
|
||||||
|
def func(self):
|
||||||
|
self.msg(_COMBAT_HELP)
|
||||||
|
|
||||||
|
|
||||||
|
class CmdHold(_CmdCombatBase):
|
||||||
|
"""
|
||||||
|
Hold back your blows, doing nothing.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
hold
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = "hold"
|
||||||
|
|
||||||
|
def func(self):
|
||||||
|
self.combathandler.queue_action(self.caller, {"key": "hold"})
|
||||||
|
self.msg("You hold, doing nothing.")
|
||||||
|
|
||||||
|
|
||||||
class CmdStunt(_CmdCombatBase):
|
class CmdStunt(_CmdCombatBase):
|
||||||
"""
|
"""
|
||||||
Perform a combat stunt, that boosts an ally against a target, or
|
Perform a combat stunt, that boosts an ally against a target, or
|
||||||
|
|
@ -923,26 +1013,46 @@ class CmdStunt(_CmdCombatBase):
|
||||||
def parse(self):
|
def parse(self):
|
||||||
super().parse()
|
super().parse()
|
||||||
args = self.args
|
args = self.args
|
||||||
|
|
||||||
|
if not args:
|
||||||
|
self.msg("Usage: [ability] of <recipient> vs <target>")
|
||||||
|
raise InterruptCommand()
|
||||||
|
|
||||||
if "of" in args:
|
if "of" in args:
|
||||||
self.stunt_type, args = (part.strip() for part in args.split("of", 1))
|
self.stunt_type, args = (part.strip() for part in args.split("of", 1))
|
||||||
else:
|
else:
|
||||||
self.stunt_type, args = (part.strip() for part in args.split(None, 1))
|
self.stunt_type, args = (part.strip() for part in args.split(None, 1))
|
||||||
|
|
||||||
|
# convert stunt-type to an Ability, like Ability.STR etc
|
||||||
|
if not self.stunt_type in ABILITY_REVERSE_MAP:
|
||||||
|
self.msg("That's not a valid ability.")
|
||||||
|
raise InterruptCommand()
|
||||||
|
self.stunt_type = ABILITY_REVERSE_MAP[self.stunt_type]
|
||||||
|
|
||||||
if " vs " in args:
|
if " vs " in args:
|
||||||
self.recipient, self.target = (part.strip() for part in args.split(" vs "))
|
self.recipient, self.target = (part.strip() for part in args.split(" vs "))
|
||||||
elif self.cmdname == "foil":
|
elif self.cmdname == "foil":
|
||||||
self.recipient, self.target = "me", args.strip()
|
self.recipient, self.target = "me", args.strip()
|
||||||
else:
|
else:
|
||||||
self.recipient, self.target = args.strip(), "me"
|
self.recipient, self.target = args.strip(), "me"
|
||||||
self.advantage = self.cmdname == "boost"
|
self.advantage = self.cmdname != "foil"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
||||||
|
combathandler = self.combathandler
|
||||||
|
target = self.caller.search(self.target, candidates=combathandler.combatants.keys())
|
||||||
|
if not target:
|
||||||
|
return
|
||||||
|
recipient = self.caller.search(self.recipient, candidates=combathandler.combatants.keys())
|
||||||
|
if not recipient:
|
||||||
|
return
|
||||||
|
|
||||||
self.combathandler.queue_action(
|
self.combathandler.queue_action(
|
||||||
self.caller,
|
self.caller,
|
||||||
{
|
{
|
||||||
"key": "stunt",
|
"key": "stunt",
|
||||||
"recipient": self.recipient,
|
"recipient": recipient,
|
||||||
"target": self.target,
|
"target": target,
|
||||||
"advantage": self.advantage,
|
"advantage": self.advantage,
|
||||||
"stunt_type": self.stunt_type,
|
"stunt_type": self.stunt_type,
|
||||||
"defense_type": self.stunt_type,
|
"defense_type": self.stunt_type,
|
||||||
|
|
@ -973,7 +1083,10 @@ class CmdUseItem(_CmdCombatBase):
|
||||||
super().parse()
|
super().parse()
|
||||||
args = self.args
|
args = self.args
|
||||||
|
|
||||||
if "on" in args:
|
if not args:
|
||||||
|
self.msg("What do you want to use?")
|
||||||
|
raise InterruptCommand()
|
||||||
|
elif "on" in args:
|
||||||
self.item, self.target = (part.strip() for part in args.split("on", 1))
|
self.item, self.target = (part.strip() for part in args.split("on", 1))
|
||||||
else:
|
else:
|
||||||
self.item, *target = args.split(None, 1)
|
self.item, *target = args.split(None, 1)
|
||||||
|
|
@ -1017,6 +1130,12 @@ class CmdWield(_CmdCombatBase):
|
||||||
key = "wield"
|
key = "wield"
|
||||||
help_category = "combat"
|
help_category = "combat"
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
if not self.args:
|
||||||
|
self.msg("What do you want to wield?")
|
||||||
|
raise InterruptCommand()
|
||||||
|
super().parse()
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
||||||
item = self.caller.search(
|
item = self.caller.search(
|
||||||
|
|
@ -1070,7 +1189,7 @@ class TwitchAttackCmdSet(CmdSet):
|
||||||
def _get_combathandler(caller):
|
def _get_combathandler(caller):
|
||||||
evmenu = caller.ndb._evmenu
|
evmenu = caller.ndb._evmenu
|
||||||
if not hasattr(evmenu, "combathandler"):
|
if not hasattr(evmenu, "combathandler"):
|
||||||
evmenu.combathandler = get_or_create_combathandler(caller)
|
evmenu.combathandler = get_or_create_combathandler(caller.location)
|
||||||
return evmenu.combathandler
|
return evmenu.combathandler
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1330,8 +1449,8 @@ def node_combat(caller, raw_string, **kwargs):
|
||||||
"goto": (_queue_action, {"flee": {"key": "flee"}}),
|
"goto": (_queue_action, {"flee": {"key": "flee"}}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"desc": "do nothing",
|
"desc": "hold, doing nothing",
|
||||||
"goto": (_queue_action, {"action_dict": {"key": "nothing"}}),
|
"goto": (_queue_action, {"action_dict": {"key": "hold"}}),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1364,7 +1483,7 @@ class CmdTurnAttack(Command):
|
||||||
if not target:
|
if not target:
|
||||||
return
|
return
|
||||||
|
|
||||||
combathandler = get_or_create_combathandler(self.caller, combat_tick=30)
|
combathandler = get_or_create_combathandler(self.caller.location, combat_tick=30)
|
||||||
combathandler.add_combatant(self.caller)
|
combathandler.add_combatant(self.caller)
|
||||||
|
|
||||||
# build and start the menu
|
# build and start the menu
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from random import choice
|
||||||
|
|
||||||
from evennia import DefaultCharacter
|
from evennia import DefaultCharacter
|
||||||
from evennia.typeclasses.attributes import AttributeProperty
|
from evennia.typeclasses.attributes import AttributeProperty
|
||||||
|
from evennia.typeclasses.tags import TagProperty
|
||||||
from evennia.utils.evmenu import EvMenu
|
from evennia.utils.evmenu import EvMenu
|
||||||
from evennia.utils.utils import make_iter
|
from evennia.utils.utils import make_iter
|
||||||
|
|
||||||
|
|
@ -53,6 +54,9 @@ class EvAdventureNPC(LivingMixin, DefaultCharacter):
|
||||||
weapon = AttributeProperty(default=WeaponEmptyHand, autocreate=False) # instead of inventory
|
weapon = AttributeProperty(default=WeaponEmptyHand, autocreate=False) # instead of inventory
|
||||||
coins = AttributeProperty(default=1, autocreate=False) # coin loot
|
coins = AttributeProperty(default=1, autocreate=False) # coin loot
|
||||||
|
|
||||||
|
# if this npc is attacked, everyone with the same tag in the current location will also be pulled into combat.
|
||||||
|
group = TagProperty("npcs")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def strength(self):
|
def strength(self):
|
||||||
return self.hit_dice
|
return self.hit_dice
|
||||||
|
|
@ -88,7 +92,7 @@ class EvAdventureNPC(LivingMixin, DefaultCharacter):
|
||||||
"""
|
"""
|
||||||
self.hp = self.hp_max
|
self.hp = self.hp_max
|
||||||
|
|
||||||
def ai_combat_next_action(self):
|
def ai_combat_next_action(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
The combat engine should ask this method in order to
|
The combat engine should ask this method in order to
|
||||||
get the next action the npc should perform in combat.
|
get the next action the npc should perform in combat.
|
||||||
|
|
|
||||||
|
|
@ -177,9 +177,30 @@ class EvAdventureWeapon(EvAdventureObject):
|
||||||
defense_type = AttributeProperty(Ability.ARMOR)
|
defense_type = AttributeProperty(Ability.ARMOR)
|
||||||
damage_roll = AttributeProperty("1d6")
|
damage_roll = AttributeProperty("1d6")
|
||||||
|
|
||||||
|
def get_display_name(self, looker=None, **kwargs):
|
||||||
|
quality = self.quality
|
||||||
|
|
||||||
|
quality_txt = ""
|
||||||
|
if quality <= 0:
|
||||||
|
quality_txt = "|r(broken!)|n"
|
||||||
|
elif quality < 2:
|
||||||
|
quality_txt = "|y(damaged)|n"
|
||||||
|
elif quality < 3:
|
||||||
|
quality_txt = "|Y(chipped)|n"
|
||||||
|
|
||||||
|
return super().get_display_name(looker=looker, **kwargs) + quality_txt
|
||||||
|
|
||||||
|
def at_pre_use(self, user, *args, **kwargs):
|
||||||
|
if self.quality <= 0:
|
||||||
|
user.msg(f"{self.get_display_name(user)} is broken and can't be used!")
|
||||||
|
return False
|
||||||
|
return super().at_pre_use(user, *args, **kwargs)
|
||||||
|
|
||||||
def use(self, attacker, target, *args, advantage=False, disadvantage=False, **kwargs):
|
def use(self, attacker, target, *args, advantage=False, disadvantage=False, **kwargs):
|
||||||
"""When a weapon is used, it attacks an opponent"""
|
"""When a weapon is used, it attacks an opponent"""
|
||||||
|
|
||||||
|
location = attacker.location
|
||||||
|
|
||||||
is_hit, quality, txt = rules.dice.opposed_saving_throw(
|
is_hit, quality, txt = rules.dice.opposed_saving_throw(
|
||||||
attacker,
|
attacker,
|
||||||
target,
|
target,
|
||||||
|
|
@ -188,7 +209,11 @@ class EvAdventureWeapon(EvAdventureObject):
|
||||||
advantage=advantage,
|
advantage=advantage,
|
||||||
disadvantage=disadvantage,
|
disadvantage=disadvantage,
|
||||||
)
|
)
|
||||||
self.msg(f"$You() $conj(attack) $You({target.key}) with {self.key}: {txt}")
|
location.msg_contents(
|
||||||
|
f"$You() $conj(attack) $You({target.key}) with {self.key}: {txt}",
|
||||||
|
from_obj=attacker,
|
||||||
|
mapping={target.key: target},
|
||||||
|
)
|
||||||
if is_hit:
|
if is_hit:
|
||||||
# enemy hit, calculate damage
|
# enemy hit, calculate damage
|
||||||
dmg = rules.dice.roll(self.damage_roll)
|
dmg = rules.dice.roll(self.damage_roll)
|
||||||
|
|
@ -201,8 +226,8 @@ class EvAdventureWeapon(EvAdventureObject):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
message = f" $You() $conj(hit) $You({target.key}) for |r{dmg}|n damage!"
|
message = f" $You() $conj(hit) $You({target.key}) for |r{dmg}|n damage!"
|
||||||
self.msg(message)
|
|
||||||
|
|
||||||
|
location.msg_contents(message, from_obj=attacker, mapping={target.key: target})
|
||||||
# call hook
|
# call hook
|
||||||
target.at_damage(dmg, attacker=attacker)
|
target.at_damage(dmg, attacker=attacker)
|
||||||
|
|
||||||
|
|
@ -212,7 +237,11 @@ class EvAdventureWeapon(EvAdventureObject):
|
||||||
if quality is Ability.CRITICAL_FAILURE:
|
if quality is Ability.CRITICAL_FAILURE:
|
||||||
self.quality -= 1
|
self.quality -= 1
|
||||||
message += ".. it's a |rcritical miss!|n, damaging the weapon."
|
message += ".. it's a |rcritical miss!|n, damaging the weapon."
|
||||||
self.msg(message)
|
location.msg_contents(message, from_obj=attacker, mapping={target.key: target})
|
||||||
|
|
||||||
|
def at_post_use(self, user, *args, **kwargs):
|
||||||
|
if self.quality <= 0:
|
||||||
|
user.msg(f"|r{self.get_display_name(user)} breaks and can no longer be used!")
|
||||||
|
|
||||||
|
|
||||||
class EvAdventureThrowable(EvAdventureWeapon, EvAdventureConsumable):
|
class EvAdventureThrowable(EvAdventureWeapon, EvAdventureConsumable):
|
||||||
|
|
|
||||||
|
|
@ -157,13 +157,13 @@ class EvAdventureRollEngine:
|
||||||
bontxt = f"(+{bonus})"
|
bontxt = f"(+{bonus})"
|
||||||
modtxt = ""
|
modtxt = ""
|
||||||
if modifier:
|
if modifier:
|
||||||
modtxt = f" + {modifier}" if modifier > 0 else f" - {abs(modifier)}"
|
modtxt = f"+ {modifier}" if modifier > 0 else f" - {abs(modifier)}"
|
||||||
qualtxt = f" ({quality.value}!)" if quality else ""
|
qualtxt = f" ({quality.value}!)" if quality else ""
|
||||||
|
|
||||||
txt = (
|
txt = (
|
||||||
f"rolled {dice_roll} on {rolltxt} "
|
f" rolled {dice_roll} on {rolltxt} "
|
||||||
f"+ {bonus_type.value}{bontxt}{modtxt} vs "
|
f"+ {bonus_type.value}{bontxt}{modtxt} vs "
|
||||||
f"{target} -> |w{result}{qualtxt}|n"
|
f"{target} -> |w{'|GSuccess|w' if result else '|RFail|w'}{qualtxt}|n"
|
||||||
)
|
)
|
||||||
|
|
||||||
return (dice_roll + bonus + modifier) > target, quality, txt
|
return (dice_roll + bonus + modifier) > target, quality, txt
|
||||||
|
|
@ -332,9 +332,11 @@ class EvAdventureRollEngine:
|
||||||
setattr(character, abi, current_abi)
|
setattr(character, abi, current_abi)
|
||||||
|
|
||||||
character.msg(
|
character.msg(
|
||||||
"~" * 78 + "\n|yYou survive your brush with death, "
|
"~" * 78
|
||||||
|
+ "\n|yYou survive your brush with death, "
|
||||||
f"but are |r{result.upper()}|y and permanently |rlose {loss} {abi}|y.|n\n"
|
f"but are |r{result.upper()}|y and permanently |rlose {loss} {abi}|y.|n\n"
|
||||||
f"|GYou recover |g{new_hp}|G health|.\n" + "~" * 78
|
f"|GYou recover |g{new_hp}|G health|.\n"
|
||||||
|
+ "~" * 78
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,12 +62,12 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||||
# add target to combat
|
# add target to combat
|
||||||
self.combathandler.add_combatant(self.target)
|
self.combathandler.add_combatant(self.target)
|
||||||
|
|
||||||
def _get_action(self, action_dict={"key": "nothing"}):
|
def _get_action(self, action_dict={"key": "hold"}):
|
||||||
action_class = self.combathandler.action_classes[action_dict["key"]]
|
action_class = self.combathandler.action_classes[action_dict["key"]]
|
||||||
return action_class(self.combathandler, self.combatant, action_dict)
|
return action_class(self.combathandler, self.combatant, action_dict)
|
||||||
|
|
||||||
def _run_actions(
|
def _run_actions(
|
||||||
self, action_dict, action_dict2={"key": "nothing"}, combatant_msg=None, target_msg=None
|
self, action_dict, action_dict2={"key": "hold"}, combatant_msg=None, target_msg=None
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Helper method to run an action and check so combatant saw the expected message.
|
Helper method to run an action and check so combatant saw the expected message.
|
||||||
|
|
@ -90,7 +90,7 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
dict(chandler.action_classes),
|
dict(chandler.action_classes),
|
||||||
{
|
{
|
||||||
"nothing": combat.CombatActionDoNothing,
|
"hold": combat.CombatActionHold,
|
||||||
"attack": combat.CombatActionAttack,
|
"attack": combat.CombatActionAttack,
|
||||||
"stunt": combat.CombatActionStunt,
|
"stunt": combat.CombatActionStunt,
|
||||||
"use": combat.CombatActionUseItem,
|
"use": combat.CombatActionUseItem,
|
||||||
|
|
@ -176,31 +176,31 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||||
def test_queue_and_execute_action(self):
|
def test_queue_and_execute_action(self):
|
||||||
"""Queue actions and execute"""
|
"""Queue actions and execute"""
|
||||||
|
|
||||||
donothing = {"key": "nothing"}
|
hold = {"key": "hold"}
|
||||||
|
|
||||||
self.combathandler.queue_action(self.combatant, donothing)
|
self.combathandler.queue_action(self.combatant, hold)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
dict(self.combathandler.combatants),
|
dict(self.combathandler.combatants),
|
||||||
{self.combatant: deque([donothing]), self.target: deque()},
|
{self.combatant: deque([hold]), self.target: deque()},
|
||||||
)
|
)
|
||||||
|
|
||||||
mock_action = Mock()
|
mock_action = Mock()
|
||||||
self.combathandler.action_classes["nothing"] = Mock(return_value=mock_action)
|
self.combathandler.action_classes["hold"] = Mock(return_value=mock_action)
|
||||||
|
|
||||||
self.combathandler.execute_next_action(self.combatant)
|
self.combathandler.execute_next_action(self.combatant)
|
||||||
|
|
||||||
self.combathandler.action_classes["nothing"].assert_called_with(
|
self.combathandler.action_classes["hold"].assert_called_with(
|
||||||
self.combathandler, self.combatant, donothing
|
self.combathandler, self.combatant, hold
|
||||||
)
|
)
|
||||||
mock_action.execute.assert_called_once()
|
mock_action.execute.assert_called_once()
|
||||||
|
|
||||||
def test_execute_full_turn(self):
|
def test_execute_full_turn(self):
|
||||||
"""Run a full (passive) turn"""
|
"""Run a full (passive) turn"""
|
||||||
|
|
||||||
donothing = {"key": "nothing"}
|
hold = {"key": "hold"}
|
||||||
|
|
||||||
self.combathandler.queue_action(self.combatant, donothing)
|
self.combathandler.queue_action(self.combatant, hold)
|
||||||
self.combathandler.queue_action(self.target, donothing)
|
self.combathandler.queue_action(self.target, hold)
|
||||||
|
|
||||||
self.combathandler.execute_next_action = Mock()
|
self.combathandler.execute_next_action = Mock()
|
||||||
|
|
||||||
|
|
@ -216,7 +216,7 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||||
combatant = self.combatant
|
combatant = self.combatant
|
||||||
target = self.target
|
target = self.target
|
||||||
|
|
||||||
action = self._get_action({"key": "nothing"})
|
action = self._get_action({"key": "hold"})
|
||||||
|
|
||||||
self.assertTrue(action.can_use())
|
self.assertTrue(action.can_use())
|
||||||
|
|
||||||
|
|
@ -235,10 +235,10 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||||
action.msg(f"$You() attack $You({target.key}).")
|
action.msg(f"$You() attack $You({target.key}).")
|
||||||
combatant.msg.assert_called_with(text=("You attack testmonster.", {}), from_obj=combatant)
|
combatant.msg.assert_called_with(text=("You attack testmonster.", {}), from_obj=combatant)
|
||||||
|
|
||||||
def test_action__do_nothing(self):
|
def test_action__hold(self):
|
||||||
"""Do nothing"""
|
"""Hold, doing nothing"""
|
||||||
|
|
||||||
actiondict = {"key": "nothing"}
|
actiondict = {"key": "hold"}
|
||||||
self._run_actions(actiondict, actiondict)
|
self._run_actions(actiondict, actiondict)
|
||||||
self.assertEqual(self.combathandler.turn, 1)
|
self.assertEqual(self.combathandler.turn, 1)
|
||||||
|
|
||||||
|
|
@ -417,7 +417,7 @@ 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": "nothing"})
|
action = combat.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
|
||||||
|
|
|
||||||
|
|
@ -34,13 +34,12 @@ from django.core.validators import validate_email as django_validate_email
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.html import strip_tags
|
from django.utils.html import strip_tags
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
from evennia.utils import logger
|
||||||
from simpleeval import simple_eval
|
from simpleeval import simple_eval
|
||||||
from twisted.internet import reactor, threads
|
from twisted.internet import reactor, threads
|
||||||
from twisted.internet.defer import returnValue # noqa - used as import target
|
from twisted.internet.defer import returnValue # noqa - used as import target
|
||||||
from twisted.internet.task import deferLater
|
from twisted.internet.task import deferLater
|
||||||
|
|
||||||
from evennia.utils import logger
|
|
||||||
|
|
||||||
_MULTIMATCH_TEMPLATE = settings.SEARCH_MULTIMATCH_TEMPLATE
|
_MULTIMATCH_TEMPLATE = settings.SEARCH_MULTIMATCH_TEMPLATE
|
||||||
_EVENNIA_DIR = settings.EVENNIA_DIR
|
_EVENNIA_DIR = settings.EVENNIA_DIR
|
||||||
_GAME_DIR = settings.GAME_DIR
|
_GAME_DIR = settings.GAME_DIR
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue