Made twitch-combat evadventure combat work

This commit is contained in:
Griatch 2023-03-26 00:07:59 +01:00
parent 979a79e832
commit 586eedf40d
3 changed files with 130 additions and 64 deletions

View file

@ -272,14 +272,9 @@ class CombatActionStunt(CombatAction):
attacker = self.combatant attacker = self.combatant
recipient = self.recipient # the one to receive the effect of the stunt recipient = self.recipient # the one to receive the effect of the stunt
target = self.target # the affected by the stunt (can be the same as recipient/combatant) target = self.target # the affected by the stunt (can be the same as recipient/combatant)
is_success = False
txt = "" txt = ""
if target == self.combatant: if recipient == target:
# can always grant dis/advantage against yourself
defender = attacker
is_success = True
elif recipient == target:
# grant another entity dis/advantage against themselves # grant another entity dis/advantage against themselves
defender = recipient defender = recipient
else: else:
@ -287,31 +282,42 @@ class CombatActionStunt(CombatAction):
# to give. # to give.
defender = target if self.advantage else recipient defender = target if self.advantage else recipient
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( attacker,
attacker, defender,
defender, attack_type=self.stunt_type,
attack_type=self.stunt_type, defense_type=self.defense_type,
defense_type=self.defense_type, advantage=self.has_advantage(attacker, defender),
advantage=self.has_advantage(attacker, defender), disadvantage=self.has_disadvantage(attacker, defender),
disadvantage=self.has_disadvantage(attacker, defender), )
)
self.msg(f"$You() $conj(attempt) stunt on $You({defender.key}). {txt}")
# deal with results # deal with results
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)
if recipient == self.combatant:
self.msg(
f"$You() $conj(gain) {'advantage' if self.advantage else 'disadvantage'} "
f"against $You({target.key})!"
)
else:
self.msg(
f"$You() $conj(cause) $You({recipient.key}) "
f"to gain {'advantage' if self.advantage else 'disadvantage'} "
f"against $You({target.key})!"
)
self.msg( self.msg(
f"$You() $conj(cause) $You({recipient.key}) " "|yHaving succeeded, you hold back to plan your next move.|n [hold]",
f"to gain {'advantage' if self.advantage else 'disadvantage'} " broadcast=False,
f"against $You({target.key})!"
) )
self.combathandler.queue_action(attacker, {"key": "hold"})
else: else:
self.msg(f"$You({target.key}) $conj(resist)! $You() $conj(fail) the stunt.") self.msg(f"$You({defender.key}) $conj(resist)! $You() $conj(fail) the stunt.")
class CombatActionUseItem(CombatAction): class CombatActionUseItem(CombatAction):
@ -345,6 +351,8 @@ class CombatActionUseItem(CombatAction):
disadvantage=self.has_disadvantage(user, target), disadvantage=self.has_disadvantage(user, target),
) )
item.at_post_use(user, target) item.at_post_use(user, target)
# to back to idle after this
self.combathandler.queue_action(self.combatant, {"key": "hold"})
class CombatActionWield(CombatAction): class CombatActionWield(CombatAction):
@ -364,6 +372,7 @@ class CombatActionWield(CombatAction):
def execute(self): def execute(self):
self.combatant.equipment.move(self.item) self.combatant.equipment.move(self.item)
self.combathandler.queue_action(self.combatant, {"key": "hold"})
class CombatActionFlee(CombatAction): class CombatActionFlee(CombatAction):
@ -847,7 +856,7 @@ class _CmdCombatBase(Command):
combathandler = getattr(self, "_combathandler", None) combathandler = getattr(self, "_combathandler", None)
if not combathandler: if not combathandler:
self._combathandler = combathandler = get_or_create_combathandler( self._combathandler = combathandler = get_or_create_combathandler(
self.caller.location, combat_tick=2 self.caller.location, combat_tick=self.combat_tick
) )
return combathandler return combathandler
@ -989,17 +998,17 @@ class CmdStunt(_CmdCombatBase):
foils an enemy, giving them disadvantage against an ally. foils an enemy, giving them disadvantage against an ally.
Usage: Usage:
boost [ability] [of] <recipient> vs <target> boost [ability] <recipient> <target>
foil [ability] [of] <recipient> vs <target> foil [ability] <recipient> <target>
boost [ability] [vs] <target> (same as boost me vs target) boost [ability] <target> (same as boost me <target>)
foil [ability] [of] <target> (same as foil <target> vs me) foil [ability] <target> (same as foil <target> me)
Example: Example:
boost STR of me vs Goblin boost STR me Goblin
boost DEX vs Goblin boost DEX Goblin
foil STR Goblin me foil STR Goblin me
foil INT Goblin foil INT Goblin
boost INT Wizard vs Goblin boost INT Wizard Goblin
""" """
@ -1014,28 +1023,48 @@ class CmdStunt(_CmdCombatBase):
super().parse() super().parse()
args = self.args args = self.args
if not args: if not args or " " not in args:
self.msg("Usage: [ability] of <recipient> vs <target>") self.msg("Usage: <ability> [of] <recipient> [vs] <target>")
raise InterruptCommand() raise InterruptCommand()
if "of" in args: advantage = self.cmdname != "foil"
self.stunt_type, args = (part.strip() for part in args.split("of", 1))
else:
self.stunt_type, args = (part.strip() for part in args.split(None, 1))
# convert stunt-type to an Ability, like Ability.STR etc # extract data from the input
if not self.stunt_type in ABILITY_REVERSE_MAP:
stunt_type, recipient, target = None, None, None
stunt_type, *args = args.split(None, 1)
args = args[0] if args else ""
recipient, *args = args.split(None, 1)
target = args[0] if args else None
# validate input and try to guess if not given
# ability is requried
if stunt_type.strip() not in ABILITY_REVERSE_MAP:
self.msg("That's not a valid ability.") self.msg("That's not a valid ability.")
raise InterruptCommand() raise InterruptCommand()
self.stunt_type = ABILITY_REVERSE_MAP[self.stunt_type]
if " vs " in args: if not recipient:
self.recipient, self.target = (part.strip() for part in args.split(" vs ")) self.msg("Must give at least a recipient or target.")
elif self.cmdname == "foil": raise InterruptCommand()
self.recipient, self.target = "me", args.strip()
else: if not target:
self.recipient, self.target = args.strip(), "me" # something like `boost str target`
self.advantage = self.cmdname != "foil" target = recipient if advantage else "me"
recipient = "me" if advantage else recipient
# if we still have None:s at this point, we can't continue
if None in (stunt_type, recipient, target):
self.msg("Both ability, recipient and target of stunt must be given.")
raise InterruptCommand()
# save what we found so it can be accessed from func()
self.advantage = advantage
self.stunt_type = ABILITY_REVERSE_MAP[stunt_type.strip()]
self.recipient = recipient.strip()
self.target = target.strip()
def func(self): def func(self):
@ -1105,7 +1134,9 @@ class CmdUseItem(_CmdCombatBase):
if not target: if not target:
return return
self.combathandler.queue_action(self.caller, {"key": "use", "item": item, "target": target}) self.combathandler.queue_action(
self.caller, {"key": "use", "item": item, "target": self.target}
)
self.msg(f"You prepare to use {item.get_display_name(self.caller)}!") self.msg(f"You prepare to use {item.get_display_name(self.caller)}!")
@ -1225,7 +1256,7 @@ def _step_wizard(caller, raw_string, **kwargs):
# forward (default) # forward (default)
if istep >= nsteps - 1: if istep >= nsteps - 1:
# we are already at end of wizard - queue action! # we are already at end of wizard - queue action!
return _queue_action, kwargs return _queue_action(caller, raw_string, **kwargs)
else: else:
# step forward # step forward
istep = kwargs["istep"] = min(nsteps - 1, istep + 1) istep = kwargs["istep"] = min(nsteps - 1, istep + 1)
@ -1460,7 +1491,19 @@ 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 CmdTurnAttack(Command): class _CmdTurnCombatBase(_CmdCombatBase):
"""
Base combat class for combat. Change the combat-tick to determine
how quickly the combat will 'tick'.
"""
combathandler_name = "combathandler"
combat_tick = 30
flee_timeout = 2
class CmdTurnAttack(_CmdTurnCombatBase):
""" """
Start or join combat. Start or join combat.
@ -1478,13 +1521,29 @@ class CmdTurnAttack(Command):
def func(self): def func(self):
if self.args: if not self.args:
target = self.caller.search(self.args) self.msg("What are you attacking?")
if not target: return
return
combathandler = get_or_create_combathandler(self.caller.location, combat_tick=30) target = self.caller.search(self.args)
combathandler.add_combatant(self.caller) if not target:
return
if not hasattr(target, "hp"):
self.msg(f"You can't attack that.")
return
elif target.hp <= 0:
self.msg(f"{target.get_display_name(self.caller)} is already down.")
return
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
self.combathandler.add_combatant(self.caller)
self.combathandler.add_combatant(target)
self.combathandler.start_combat()
# build and start the menu # build and start the menu
evmenu.EvMenu( evmenu.EvMenu(
@ -1493,16 +1552,17 @@ class CmdTurnAttack(Command):
"node_choose_enemy_target": node_choose_enemy_target, "node_choose_enemy_target": node_choose_enemy_target,
"node_choose_allied_target": node_choose_allied_target, "node_choose_allied_target": node_choose_allied_target,
"node_choose_ability": node_choose_ability, "node_choose_ability": node_choose_ability,
"node_choose_use_item": node_chooose_use_item, "node_choose_use_item": node_choose_use_item,
"node_choose_wield_item": node_choose_wield_item, "node_choose_wield_item": node_choose_wield_item,
"node_combat": node_combat, "node_combat": node_combat,
}, },
startnode="node_combat", startnode="node_combat",
combathandler=combathandler, combathandler=self.combathandler,
cmdset_mergetype="Union",
) )
class TurnCombatCmdset(CmdSet): class TurnAttackCmdSet(CmdSet):
""" """
CmdSet for the turn-based combat. CmdSet for the turn-based combat.
""" """

View file

@ -357,7 +357,8 @@ class EquipmentHandler:
return [ return [
obj obj
for obj in self.slots[WieldLocation.BACKPACK] for obj in self.slots[WieldLocation.BACKPACK]
if obj.inventory_use_slot if obj
and obj.inventory_use_slot
in (WieldLocation.WEAPON_HAND, WieldLocation.TWO_HANDS, WieldLocation.SHIELD_HAND) in (WieldLocation.WEAPON_HAND, WieldLocation.TWO_HANDS, WieldLocation.SHIELD_HAND)
] ]
@ -375,7 +376,7 @@ class EquipmentHandler:
return [ return [
obj obj
for obj in self.slots[WieldLocation.BACKPACK] for obj in self.slots[WieldLocation.BACKPACK]
if obj.inventory_use_slot in (WieldLocation.BODY, WieldLocation.HEAD) if obj and obj.inventory_use_slot in (WieldLocation.BODY, WieldLocation.HEAD)
] ]
def get_usable_objects_from_backpack(self): def get_usable_objects_from_backpack(self):
@ -388,7 +389,9 @@ class EquipmentHandler:
""" """
character = self.obj character = self.obj
return [obj for obj in self.slots[WieldLocation.BACKPACK] if obj.at_pre_use(character)] return [
obj for obj in self.slots[WieldLocation.BACKPACK] if obj and obj.at_pre_use(character)
]
def all(self, only_objs=False): def all(self, only_objs=False):
""" """

View file

@ -139,12 +139,15 @@ class EvAdventureConsumable(EvAdventureObject):
size = AttributeProperty(0.25) size = AttributeProperty(0.25)
uses = AttributeProperty(1) uses = AttributeProperty(1)
def use(self, user, *args, **kwargs): def use(self, user, target, *args, **kwargs):
""" """
Use the consumable. Use the consumable.
""" """
raise NotImplementedError if user.location:
user.location.msg_contents(
f"$You() $conj(use) {self.get_display_name(user)}.", from_obj=user
)
def at_post_use(self, user, *args, **kwargs): def at_post_use(self, user, *args, **kwargs):
""" """
@ -157,7 +160,7 @@ class EvAdventureConsumable(EvAdventureObject):
""" """
self.uses -= 1 self.uses -= 1
if self.uses <= 0: if self.uses <= 0:
user.msg(f"{self.key} was used up.") user.msg(f"|w{self.key} was used up.|n")
self.delete() self.delete()
@ -235,8 +238,8 @@ class EvAdventureWeapon(EvAdventureObject):
# a miss # a miss
message = f" $You() $conj(miss) $You({target.key})." message = f" $You() $conj(miss) $You({target.key})."
if quality is Ability.CRITICAL_FAILURE: if quality is Ability.CRITICAL_FAILURE:
self.quality -= 1
message += ".. it's a |rcritical miss!|n, damaging the weapon." message += ".. it's a |rcritical miss!|n, damaging the weapon."
self.quality -= 1
location.msg_contents(message, from_obj=attacker, mapping={target.key: target}) location.msg_contents(message, from_obj=attacker, mapping={target.key: target})
def at_post_use(self, user, *args, **kwargs): def at_post_use(self, user, *args, **kwargs):