Made twitch-combat evadventure combat work
This commit is contained in:
parent
979a79e832
commit
586eedf40d
3 changed files with 130 additions and 64 deletions
|
|
@ -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.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue