Finish Twitch-combat tutorial

This commit is contained in:
Griatch 2023-05-06 22:42:33 +02:00
parent d2551aaaa7
commit 5b2e9bd5a1
17 changed files with 1273 additions and 49 deletions

View file

@ -15,7 +15,7 @@ type self = evennia.contrib.tutorials.evadventure.characters.EvAdventureCharacte
# assign us the twitch combat cmdset (requires superuser/developer perms)
py self.cmdset.add("evennia.contrib.tutorials.evadventure.combat_twitch.TwitchAttackCmdSet", persistent=True)
py self.cmdset.add("evennia.contrib.tutorials.evadventure.combat_twitch.TwitchCombatCmdSet", persistent=True)
# Create and give us a weapons (this will use defaults on the class)

View file

@ -64,6 +64,13 @@ class LivingMixin:
else:
self.msg(f"You are healed for {healed} health.")
def at_attacked(self, attacker, **kwargs):
"""
Called when being attacked / combat starts.
"""
pass
def at_damage(self, damage, attacker=None):
"""
Called when attacked and taking damage.

View file

@ -204,7 +204,6 @@ class CombatActionStunt(CombatAction):
"|yHaving succeeded, you hold back to plan your next move.|n [hold]",
broadcast=False,
)
combathandler.queue_action(attacker, combathandler.fallback_action_dict)
else:
self.msg(f"$You({defender.key}) $conj(resist)! $You() $conj(fail) the stunt.")
@ -240,7 +239,6 @@ class CombatActionUseItem(CombatAction):
)
item.at_post_use(user, target)
# to back to idle after this
self.combathandler.queue_action(self.combatant, self.combathandler.fallback_action_dict)
class CombatActionWield(CombatAction):
@ -260,13 +258,12 @@ class CombatActionWield(CombatAction):
def execute(self):
self.combatant.equipment.move(self.item)
self.combathandler.queue_action(self.combatant, self.combathandler.fallback_action_dict)
# main combathandler
class EvAdventureCombatHandlerBase(DefaultScript):
class EvAdventureCombatBaseHandler(DefaultScript):
"""
This script is created when a combat starts. It 'ticks' the combat and tracks
all sides of it.
@ -481,13 +478,14 @@ class EvAdventureCombatHandlerBase(DefaultScript):
"""
raise NotImplementedError
def queue_action(self, combatant, action_dict):
def queue_action(self, action_dict, combatant=None):
"""
Queue an action by adding the new actiondict.
Args:
combatant (EvAdventureCharacter, EvAdventureNPC): A combatant queueing the action.
action_dict (dict): A dict describing the action class by name along with properties.
combatant (EvAdventureCharacter, EvAdventureNPC, optional): A combatant queueing the
action.
"""
raise NotImplementedError

View file

@ -31,7 +31,7 @@ from .combat_base import (
CombatActionStunt,
CombatActionUseItem,
CombatActionWield,
EvAdventureCombatHandlerBase,
EvAdventureCombatBaseHandler,
)
from .enums import Ability
@ -70,7 +70,7 @@ class CombatActionFlee(CombatAction):
)
class EvAdventureTurnbasedCombatHandler(EvAdventureCombatHandlerBase):
class EvAdventureTurnbasedCombatHandler(EvAdventureCombatBaseHandler):
"""
A version of the combathandler, handling turn-based combat.

View file

@ -15,12 +15,12 @@ from .combat_base import (
CombatActionStunt,
CombatActionUseItem,
CombatActionWield,
EvAdventureCombatHandlerBase,
EvAdventureCombatBaseHandler,
)
from .enums import ABILITY_REVERSE_MAP
class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
class EvAdventureCombatTwitchHandler(EvAdventureCombatBaseHandler):
"""
This is created on the combatant when combat starts. It tracks only the combatants
side of the combat and handles when the next action will happen.
@ -39,8 +39,8 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
# dynamic properties
advantages_against = AttributeProperty(dict)
disadvantages_against = AttributeProperty(dict)
advantage_against = AttributeProperty(dict)
disadvantage_against = AttributeProperty(dict)
action_dict = AttributeProperty(dict)
fallback_action_dict = AttributeProperty({"key": "hold", "dt": 0})
@ -48,7 +48,7 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
# stores the current ticker reference, so we can manipulate it later
current_ticker_ref = AttributeProperty(None)
def msg(self, message, broadcast=True):
def msg(self, message, broadcast=True, **kwargs):
"""
Central place for sending messages to combatants. This allows
for adding any combat-specific text-decoration in one place.
@ -124,7 +124,7 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
some future boost)
"""
self.advantages_against[target] = True
self.advantage_against[target] = True
def give_disadvantage(self, recipient, target):
"""
@ -136,7 +136,7 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
an enemy.
"""
self.disadvantages_against[target] = True
self.disadvantage_against[target] = True
def has_advantage(self, combatant, target):
"""
@ -147,7 +147,7 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
target (Character or NPC): The target to check advantage against.
"""
return self.advantages_against.get(target, False)
return self.advantage_against.get(target, False)
def has_disadvantage(self, combatant, target):
"""
@ -158,14 +158,15 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
target (Character or NPC): The target to check disadvantage against.
"""
return self.disadvantages_against.get(target, False)
return self.disadvantage_against.get(target, False)
def queue_action(self, action_dict):
def queue_action(self, action_dict, combatant=None):
"""
Schedule the next action to fire.
Args:
action_dict (dict): The new action-dict to initialize.
combatant: Unused.
"""
if action_dict["key"] not in self.action_classes:
@ -323,7 +324,7 @@ class CmdAttack(_BaseTwitchCombatCommand):
combathandler = self.get_or_create_combathandler(target)
# we use a fixed dt of 3 here, to mimic Diku style; one could also picture
# attacking at a different rate, depending on skills/weapon etc.
combathandler.queue_action({"key": "attack", "target": target, "dt": 3})
combathandler.queue_action({"key": "attack", "target": target, "dt": 3, "repeat": True})
combathandler.msg(f"$You() $conj(attack) $You({target.key})!", self.caller)
@ -435,8 +436,6 @@ class CmdStunt(_BaseTwitchCombatCommand):
self.target = target.strip()
def func(self):
combathandler = self.get_or_create_combathandler(self.target)
target = self.caller.search(self.target)
if not target:
return
@ -444,6 +443,8 @@ class CmdStunt(_BaseTwitchCombatCommand):
if not recipient:
return
combathandler = self.get_or_create_combathandler(target)
combathandler.queue_action(
{
"key": "stunt",
@ -452,6 +453,7 @@ class CmdStunt(_BaseTwitchCombatCommand):
"advantage": self.advantage,
"stunt_type": self.stunt_type,
"defense_type": self.stunt_type,
"dt": 3,
},
)
combathandler.msg("$You() prepare a stunt!", self.caller)
@ -498,7 +500,7 @@ class CmdUseItem(_BaseTwitchCombatCommand):
return
combathandler = self.get_or_create_combathandler(self.target)
combathandler.queue_action({"key": "use", "item": item, "target": target})
combathandler.queue_action({"key": "use", "item": item, "target": target, "dt": 3})
combathandler.msg(
f"$You() prepare to use {item.get_display_name(self.caller)}!", self.caller
)
@ -539,11 +541,11 @@ class CmdWield(_BaseTwitchCombatCommand):
self.msg("(You must carry the item to wield it.)")
return
combathandler = self.get_or_create_combathandler()
combathandler.queue_action({"key": "wield", "item": item})
combathandler.queue_action({"key": "wield", "item": item, "dt": 3})
combathandler.msg(f"$You() reach for {item.get_display_name(self.caller)}!", self.caller)
class TwitchAttackCmdSet(CmdSet):
class TwitchCombatCmdSet(CmdSet):
"""
Add to character, to be able to attack others in a twitch-style way.
"""

View file

@ -54,7 +54,8 @@ class EvAdventureNPC(LivingMixin, DefaultCharacter):
weapon = AttributeProperty(default=BARE_HANDS, autocreate=False) # instead of inventory
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.
# if this npc is attacked, everyone with the same tag in the current location will also be
# pulled into combat.
group = TagProperty("npcs")
@property
@ -91,8 +92,16 @@ class EvAdventureNPC(LivingMixin, DefaultCharacter):
"""
self.hp = self.hp_max
self.tags.add("npcs", category="group")
def ai_combat_next_action(self, **kwargs):
def at_attacked(self, attacker, **kwargs):
"""
Called when being attacked and combat starts.
"""
pass
def ai_next_action(self, **kwargs):
"""
The combat engine should ask this method in order to
get the next action the npc should perform in combat.
@ -247,7 +256,7 @@ class EvAdventureMob(EvAdventureNPC):
# chance (%) that this enemy will loot you when defeating you
loot_chance = AttributeProperty(75, autocreate=False)
def ai_combat_next_action(self, combathandler):
def ai_next_action(self, **kwargs):
"""
Called to get the next action in combat.

View file

@ -51,7 +51,7 @@ class _CombatTestBase(EvenniaTestCase):
self.target.msg = Mock()
class TestEvAdventureCombatHandlerBase(_CombatTestBase):
class TestEvAdventureCombatBaseHandler(_CombatTestBase):
"""
Test the base functionality of the base combat handler.
@ -60,7 +60,7 @@ class TestEvAdventureCombatHandlerBase(_CombatTestBase):
def setUp(self):
"""This also tests the `get_or_create_combathandler` classfunc"""
super().setUp()
self.combathandler = combat_base.EvAdventureCombatHandlerBase.get_or_create_combathandler(
self.combathandler = combat_base.EvAdventureCombatBaseHandler.get_or_create_combathandler(
self.location, key="combathandler"
)
@ -109,7 +109,7 @@ class TestCombatActionsBase(_CombatTestBase):
def setUp(self):
super().setUp()
self.combathandler = combat_base.EvAdventureCombatHandlerBase.get_or_create_combathandler(
self.combathandler = combat_base.EvAdventureCombatBaseHandler.get_or_create_combathandler(
self.location, key="combathandler"
)
# we need to mock all NotImplemented methods
@ -552,11 +552,11 @@ class TestEvAdventureTwitchCombatHandler(EvenniaCommandTestMixin, _CombatTestBas
def test_give_advantage(self):
self.combatant_combathandler.give_advantage(self.combatant, self.target)
self.assertTrue(self.combatant_combathandler.advantages_against[self.target])
self.assertTrue(self.combatant_combathandler.advantage_against[self.target])
def test_give_disadvantage(self):
self.combatant_combathandler.give_disadvantage(self.combatant, self.target)
self.assertTrue(self.combatant_combathandler.disadvantages_against[self.target])
self.assertTrue(self.combatant_combathandler.disadvantage_against[self.target])
@patch("evennia.contrib.tutorials.evadventure.combat_twitch.unrepeat", new=Mock())
@patch("evennia.contrib.tutorials.evadventure.combat_twitch.repeat", new=Mock(return_value=999))

View file

@ -0,0 +1,23 @@
"""
Test NPC classes.
"""
from evennia import create_object
from evennia.utils.test_resources import EvenniaTest
from .. import npcs
class TestNPCBase(EvenniaTest):
def test_npc_base(self):
npc = create_object(
npcs.EvAdventureNPC,
key="TestNPC",
attributes=[("hit_dice", 4), ("armor", 1), ("morale", 9)],
)
self.assertEqual(npc.hp_multiplier, 4)
self.assertEqual(npc.hp, 16)
self.assertEqual(npc.strength, 4)
self.assertEqual(npc.charisma, 4)