Add combat summary stat
This commit is contained in:
parent
7971e6c2ff
commit
ceeebbdd79
5 changed files with 123 additions and 40 deletions
|
|
@ -83,17 +83,8 @@ You hang back, passively defending.
|
||||||
------------------- Disengage
|
------------------- Disengage
|
||||||
|
|
||||||
You retreat, getting ready to get out of combat. Use two times in a row to
|
You retreat, getting ready to get out of combat. Use two times in a row to
|
||||||
leave combat. You flee last in a round. If anyone Blocks your retreat, this counter resets.
|
leave combat. You flee last in a round.
|
||||||
|
|
||||||
------------------- Block Fleeing
|
|
||||||
|
|
||||||
You move to block the escape route of an opponent. If you win a DEX challenge,
|
|
||||||
you'll negate the target's disengage action(s).
|
|
||||||
|
|
||||||
Choose who to block:
|
|
||||||
1: <enemy 1>
|
|
||||||
2: <enemy 2>
|
|
||||||
3: ...
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -101,7 +92,7 @@ Choose who to block:
|
||||||
import random
|
import random
|
||||||
from collections import defaultdict, deque
|
from collections import defaultdict, deque
|
||||||
|
|
||||||
from evennia import CmdSet, Command, create_script
|
from evennia import CmdSet, Command, create_script, default_cmds
|
||||||
from evennia.commands.command import InterruptCommand
|
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
|
||||||
|
|
@ -571,7 +562,7 @@ class EvAdventureCombatHandler(DefaultScript):
|
||||||
self.combatants[combatant].append(action_dict)
|
self.combatants[combatant].append(action_dict)
|
||||||
|
|
||||||
# track who inserted actions this turn (non-persistent)
|
# track who inserted actions this turn (non-persistent)
|
||||||
did_action = set(self.nbd.did_action or ())
|
did_action = set(self.ndb.did_action or ())
|
||||||
did_action.add(combatant)
|
did_action.add(combatant)
|
||||||
if len(did_action) >= len(self.combatants):
|
if len(did_action) >= len(self.combatants):
|
||||||
# everyone has inserted an action. Start next turn without waiting!
|
# everyone has inserted an action. Start next turn without waiting!
|
||||||
|
|
@ -668,6 +659,15 @@ class EvAdventureCombatHandler(DefaultScript):
|
||||||
"""
|
"""
|
||||||
Get a 'battle report' - an overview of the current state of combat.
|
Get a 'battle report' - an overview of the current state of combat.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
combatant (EvAdventureCharacter, EvAdventureNPC): The combatant to get.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
EvTable: A table representing the current state of combat.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
::
|
||||||
|
|
||||||
Goblin shaman
|
Goblin shaman
|
||||||
Ally (hurt) Goblin brawler
|
Ally (hurt) Goblin brawler
|
||||||
Bob vs Goblin grunt 1 (hurt)
|
Bob vs Goblin grunt 1 (hurt)
|
||||||
|
|
@ -676,9 +676,40 @@ class EvAdventureCombatHandler(DefaultScript):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
allies, enemies = self.get_sides(combatant)
|
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)
|
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
|
# 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(combatant, combathandler_name="combathandler", combat_tick=5):
|
||||||
|
|
@ -767,24 +798,35 @@ class _CmdCombatBase(Command):
|
||||||
raise InterruptCommand()
|
raise InterruptCommand()
|
||||||
|
|
||||||
|
|
||||||
class CombatCmdSet(CmdSet):
|
class CmdLook(default_cmds.CmdLook):
|
||||||
"""
|
|
||||||
Commands to make available while in combat. Note that
|
|
||||||
the 'attack' command should also be added to the CharacterCmdSet,
|
|
||||||
in order for the user to attack things.
|
|
||||||
|
|
||||||
"""
|
key = "look"
|
||||||
|
aliases = ["l"]
|
||||||
|
|
||||||
priority = 1
|
template = """
|
||||||
mergetype = "Union" # use Replace to lock down all other commands
|
|c{room_name} |r(In Combat!)|n
|
||||||
no_exits = True # don't allow combatants to walk away
|
{room_desc}
|
||||||
|
⚔ ⚔ ⚔ ⚔ ⚔
|
||||||
|
{combat_summary}
|
||||||
|
""".strip()
|
||||||
|
|
||||||
def at_cmdset_creation(self):
|
def func(self):
|
||||||
self.add(CmdAttack())
|
if not self.args:
|
||||||
self.add(CmdStunt())
|
# when looking around with no argument, show the room description followed by the
|
||||||
self.add(CmdUseItem())
|
# current combat state.
|
||||||
self.add(CmdWield())
|
location = self.caller.location
|
||||||
self.add(CmdUseFlee())
|
combathandler = get_or_create_combathandler(self.caller)
|
||||||
|
|
||||||
|
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:
|
||||||
|
# use regular look to look at things
|
||||||
|
super().func()
|
||||||
|
|
||||||
|
|
||||||
class CmdAttack(_CmdCombatBase):
|
class CmdAttack(_CmdCombatBase):
|
||||||
|
|
@ -987,6 +1029,26 @@ class CmdFlee(_CmdCombatBase):
|
||||||
self.msg("You prepare to flee!")
|
self.msg("You prepare to flee!")
|
||||||
|
|
||||||
|
|
||||||
|
class CombatCmdSet(CmdSet):
|
||||||
|
"""
|
||||||
|
Commands to make available while in combat. Note that
|
||||||
|
the 'attack' command should also be added to the CharacterCmdSet,
|
||||||
|
in order for the user to attack things.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
priority = 1
|
||||||
|
mergetype = "Union" # use Replace to lock down all other commands
|
||||||
|
no_exits = True # don't allow combatants to walk away
|
||||||
|
|
||||||
|
def at_cmdset_creation(self):
|
||||||
|
self.add(CmdAttack())
|
||||||
|
self.add(CmdStunt())
|
||||||
|
self.add(CmdUseItem())
|
||||||
|
self.add(CmdWield())
|
||||||
|
self.add(CmdUseFlee())
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# Turn-based combat (Final Fantasy style), using a menu
|
# Turn-based combat (Final Fantasy style), using a menu
|
||||||
|
|
@ -1038,6 +1100,7 @@ def node_choose_target(caller, raw_string, **kwargs):
|
||||||
|
|
||||||
def node_combat(caller, raw_string, **kwargs):
|
def node_combat(caller, raw_string, **kwargs):
|
||||||
"""Base combat menu"""
|
"""Base combat menu"""
|
||||||
|
|
||||||
text = ""
|
text = ""
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ from evennia import CmdSet, Command, InterruptCommand
|
||||||
from evennia.utils.evmenu import EvMenu
|
from evennia.utils.evmenu import EvMenu
|
||||||
from evennia.utils.utils import inherits_from
|
from evennia.utils.utils import inherits_from
|
||||||
|
|
||||||
from .combat_turnbased import CombatFailure, join_combat
|
from .combat import CombatFailure, join_combat
|
||||||
from .enums import WieldLocation
|
from .enums import WieldLocation
|
||||||
from .equipment import EquipmentError
|
from .equipment import EquipmentError
|
||||||
from .npcs import EvAdventureTalkativeNPC
|
from .npcs import EvAdventureTalkativeNPC
|
||||||
|
|
|
||||||
|
|
@ -256,7 +256,7 @@ class EvAdventureMob(EvAdventureNPC):
|
||||||
combatant in the current combat handler.
|
combatant in the current combat handler.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from .combat_turnbased import CombatActionAttack, CombatActionDoNothing
|
from .combat import CombatActionAttack, CombatActionDoNothing
|
||||||
|
|
||||||
if self.is_idle:
|
if self.is_idle:
|
||||||
# mob just stands around
|
# mob just stands around
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,10 @@ from collections import deque
|
||||||
from unittest.mock import Mock, call, patch
|
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.test_resources import BaseEvenniaTest
|
from evennia.utils.test_resources import BaseEvenniaTest
|
||||||
|
|
||||||
from .. import combat_turnbased as combat
|
from .. import combat
|
||||||
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
|
||||||
|
|
@ -28,11 +29,11 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||||
|
|
||||||
# make sure to mock away all time-keeping elements
|
# make sure to mock away all time-keeping elements
|
||||||
@patch(
|
@patch(
|
||||||
"evennia.contrib.tutorials.evadventure.combat_turnbased.EvAdventureCombatHandler.interval",
|
"evennia.contrib.tutorials.evadventure.combat.EvAdventureCombatHandler.interval",
|
||||||
new=-1,
|
new=-1,
|
||||||
)
|
)
|
||||||
@patch(
|
@patch(
|
||||||
"evennia.contrib.tutorials.evadventure.combat_turnbased.delay",
|
"evennia.contrib.tutorials.evadventure.combat.delay",
|
||||||
new=Mock(return_value=None),
|
new=Mock(return_value=None),
|
||||||
)
|
)
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
@ -153,6 +154,25 @@ 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"""
|
||||||
|
|
||||||
|
|
@ -224,7 +244,7 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||||
|
|
||||||
self.combatant.msg.assert_not_called()
|
self.combatant.msg.assert_not_called()
|
||||||
|
|
||||||
@patch("evennia.contrib.tutorials.evadventure.combat_turnbased.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}
|
||||||
|
|
@ -233,7 +253,7 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||||
self._run_actions(actiondict)
|
self._run_actions(actiondict)
|
||||||
self.assertEqual(self.target.hp, 4)
|
self.assertEqual(self.target.hp, 4)
|
||||||
|
|
||||||
@patch("evennia.contrib.tutorials.evadventure.combat_turnbased.rules.randint")
|
@patch("evennia.contrib.tutorials.evadventure.combat.rules.randint")
|
||||||
def test_attack__success__still_alive(self, mock_randint):
|
def test_attack__success__still_alive(self, mock_randint):
|
||||||
actiondict = {"key": "attack", "target": self.target}
|
actiondict = {"key": "attack", "target": self.target}
|
||||||
|
|
||||||
|
|
@ -243,7 +263,7 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||||
self._run_actions(actiondict)
|
self._run_actions(actiondict)
|
||||||
self.assertEqual(self.target.hp, 9)
|
self.assertEqual(self.target.hp, 9)
|
||||||
|
|
||||||
@patch("evennia.contrib.tutorials.evadventure.combat_turnbased.rules.randint")
|
@patch("evennia.contrib.tutorials.evadventure.combat.rules.randint")
|
||||||
def test_attack__success__kill(self, mock_randint):
|
def test_attack__success__kill(self, mock_randint):
|
||||||
actiondict = {"key": "attack", "target": self.target}
|
actiondict = {"key": "attack", "target": self.target}
|
||||||
|
|
||||||
|
|
@ -253,7 +273,7 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||||
# after this the combat is over
|
# after this the combat is over
|
||||||
self.assertIsNone(self.combathandler.pk)
|
self.assertIsNone(self.combathandler.pk)
|
||||||
|
|
||||||
@patch("evennia.contrib.tutorials.evadventure.combat_turnbased.rules.randint")
|
@patch("evennia.contrib.tutorials.evadventure.combat.rules.randint")
|
||||||
def test_stunt_fail(self, mock_randint):
|
def test_stunt_fail(self, mock_randint):
|
||||||
action_dict = {
|
action_dict = {
|
||||||
"key": "stunt",
|
"key": "stunt",
|
||||||
|
|
@ -268,7 +288,7 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||||
self.assertEqual(self.combathandler.advantage_matrix[self.combatant], {})
|
self.assertEqual(self.combathandler.advantage_matrix[self.combatant], {})
|
||||||
self.assertEqual(self.combathandler.disadvantage_matrix[self.combatant], {})
|
self.assertEqual(self.combathandler.disadvantage_matrix[self.combatant], {})
|
||||||
|
|
||||||
@patch("evennia.contrib.tutorials.evadventure.combat_turnbased.rules.randint")
|
@patch("evennia.contrib.tutorials.evadventure.combat.rules.randint")
|
||||||
def test_stunt_advantage__success(self, mock_randint):
|
def test_stunt_advantage__success(self, mock_randint):
|
||||||
action_dict = {
|
action_dict = {
|
||||||
"key": "stunt",
|
"key": "stunt",
|
||||||
|
|
@ -284,7 +304,7 @@ class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||||
bool(self.combathandler.advantage_matrix[self.combatant][self.target]), True
|
bool(self.combathandler.advantage_matrix[self.combatant][self.target]), True
|
||||||
)
|
)
|
||||||
|
|
||||||
@patch("evennia.contrib.tutorials.evadventure.combat_turnbased.rules.randint")
|
@patch("evennia.contrib.tutorials.evadventure.combat.rules.randint")
|
||||||
def test_stunt_disadvantage__success(self, mock_randint):
|
def test_stunt_disadvantage__success(self, mock_randint):
|
||||||
action_dict = {
|
action_dict = {
|
||||||
"key": "stunt",
|
"key": "stunt",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue