Add combat summary stat

This commit is contained in:
Griatch 2023-03-13 22:13:32 +01:00
parent 7971e6c2ff
commit ceeebbdd79
5 changed files with 123 additions and 40 deletions

View file

@ -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 = ""

View file

@ -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

View file

@ -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

View file

@ -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",