More work on evadventure combathandler
This commit is contained in:
parent
a7ced1dbfc
commit
ab2e84a40f
2 changed files with 315 additions and 41 deletions
|
|
@ -328,6 +328,20 @@ class EvAdventureCharacter(DefaultCharacter):
|
||||||
"""
|
"""
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
|
def heal(self, hp, healer=None):
|
||||||
|
"""
|
||||||
|
Heal the character by a certain amount of HP.
|
||||||
|
|
||||||
|
"""
|
||||||
|
damage = self.hp_max - self.hp
|
||||||
|
healed = min(damage, hp)
|
||||||
|
self.hp += healed
|
||||||
|
|
||||||
|
if healer is self:
|
||||||
|
self.msg(f"|gYou heal yourself for {healed} health.|n")
|
||||||
|
else:
|
||||||
|
self.msg(f"|g{healer.key} heals you for {healed} health.|n")
|
||||||
|
|
||||||
def at_pre_object_receive(self, moved_object, source_location, **kwargs):
|
def at_pre_object_receive(self, moved_object, source_location, **kwargs):
|
||||||
"""
|
"""
|
||||||
Hook called by Evennia before moving an object here. Return False to abort move.
|
Hook called by Evennia before moving an object here. Return False to abort move.
|
||||||
|
|
@ -378,13 +392,7 @@ class EvAdventureCharacter(DefaultCharacter):
|
||||||
self.equipment.remove(moved_object)
|
self.equipment.remove(moved_object)
|
||||||
|
|
||||||
|
|
||||||
def at_pre_damage(self, dmg, attacker=None):
|
def at_damage(self, dmg, attacker=None):
|
||||||
"""
|
|
||||||
Called when receiving damage for whatever reason. This
|
|
||||||
is called *before* hp is evaluated for defeat/death.
|
|
||||||
|
|
||||||
"""
|
|
||||||
def at_post_damage(self, dmg, attacker=None):
|
|
||||||
"""
|
"""
|
||||||
Called when receiving damage for whatever reason. This
|
Called when receiving damage for whatever reason. This
|
||||||
is called *before* hp is evaluated for defeat/death.
|
is called *before* hp is evaluated for defeat/death.
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,13 @@ from collections import defaultdict
|
||||||
from evennia.scripts.scripts import DefaultScript
|
from evennia.scripts.scripts import DefaultScript
|
||||||
from evennia.typeclasses.attributes import AttributeProperty
|
from evennia.typeclasses.attributes import AttributeProperty
|
||||||
from evennia.utils.utils import make_iter
|
from evennia.utils.utils import make_iter
|
||||||
|
from evennia.utils import evmenu
|
||||||
from . import rules
|
from . import rules
|
||||||
|
|
||||||
MIN_RANGE = 0
|
MIN_RANGE = 0
|
||||||
MAX_RANGE = 4
|
MAX_RANGE = 4
|
||||||
|
MAX_MOVE_RATE = 2
|
||||||
|
STUNT_DURATION = 2
|
||||||
|
|
||||||
RANGE_NAMES = {
|
RANGE_NAMES = {
|
||||||
0: "close", # melee, short weapons, fists. long weapons with disadvantage
|
0: "close", # melee, short weapons, fists. long weapons with disadvantage
|
||||||
|
|
@ -34,20 +37,66 @@ RANGE_NAMES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class AttackFailure(RuntimeError):
|
class CombatFailure(RuntimeError):
|
||||||
"""
|
"""
|
||||||
Cannot attack for some reason.
|
Some failure during actions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class CombatAction:
|
||||||
|
"""
|
||||||
|
This describes a combat-action, like 'attack'.
|
||||||
|
|
||||||
|
"""
|
||||||
|
key = 'action'
|
||||||
|
status_text = "{combatant} performs an action."
|
||||||
|
# move actions can be combined with other actions
|
||||||
|
is_move_action = False
|
||||||
|
|
||||||
|
def __init__(self, combathandler):
|
||||||
|
self.combathandler = combathandler
|
||||||
|
|
||||||
|
def can_use(self, combatant, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Determine if combatant can use this action.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
def use(self, combatant, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Use action
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.combathandler.msg(self.status_text.format(combatant=combatant))
|
||||||
|
|
||||||
|
|
||||||
|
class CombatActionDoNothing(CombatAction):
|
||||||
|
"""
|
||||||
|
Do nothing this turn.
|
||||||
|
|
||||||
|
"""
|
||||||
|
status_text = "{combatant} does nothing this turn."
|
||||||
|
|
||||||
|
|
||||||
|
class CombatActionStunt(CombatAction):
|
||||||
|
"""
|
||||||
|
Perform a stunt.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class EvAdventureCombat(DefaultScript):
|
|
||||||
|
|
||||||
|
class EvAdventureCombatHandler(DefaultScript):
|
||||||
"""
|
"""
|
||||||
This script is created when combat is initialized and stores a queue
|
This script is created when combat is initialized and stores a queue
|
||||||
of all active participants. It's also possible to join (or leave) the fray later.
|
of all active participants. It's also possible to join (or leave) the fray later.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
combatants = AttributeProperty(default=list())
|
combatants = AttributeProperty(list())
|
||||||
action_queue = AttributeProperty(default=dict())
|
action_queue = AttributeProperty(dict())
|
||||||
|
|
||||||
|
turn_stats = AttributeProperty(defaultdict(list))
|
||||||
|
|
||||||
# turn counter - abstract time
|
# turn counter - abstract time
|
||||||
turn = AttributeProperty(default=0)
|
turn = AttributeProperty(default=0)
|
||||||
|
|
@ -57,7 +106,10 @@ class EvAdventureCombat(DefaultScript):
|
||||||
advantage_matrix = AttributeProperty(defaultdict(dict))
|
advantage_matrix = AttributeProperty(defaultdict(dict))
|
||||||
disadvantage_matrix = AttributeProperty(defaultdict(dict))
|
disadvantage_matrix = AttributeProperty(defaultdict(dict))
|
||||||
|
|
||||||
stunt_duration = 2
|
disengaging_combatants = AttributeProperty(default=list())
|
||||||
|
|
||||||
|
# actions that will be performed before a normal action
|
||||||
|
move_actions = ("approach", "withdraw")
|
||||||
|
|
||||||
def _refresh_distance_matrix(self):
|
def _refresh_distance_matrix(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -103,6 +155,13 @@ class EvAdventureCombat(DefaultScript):
|
||||||
combatant1_distances[combatant2] = start_optimal
|
combatant1_distances[combatant2] = start_optimal
|
||||||
combatant2_distances[combatant1] = start_optimal
|
combatant2_distances[combatant1] = start_optimal
|
||||||
|
|
||||||
|
def _update_turn_stats(self, combatant, message):
|
||||||
|
"""
|
||||||
|
Store combat messages to display at the end of turn.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.turn_stats[combatant].append(message)
|
||||||
|
|
||||||
def _start_turn(self):
|
def _start_turn(self):
|
||||||
"""
|
"""
|
||||||
New turn events
|
New turn events
|
||||||
|
|
@ -110,21 +169,59 @@ class EvAdventureCombat(DefaultScript):
|
||||||
"""
|
"""
|
||||||
self.turn += 1
|
self.turn += 1
|
||||||
self.action_queue = {}
|
self.action_queue = {}
|
||||||
|
self.turn_stats = defaultdict(list)
|
||||||
|
|
||||||
def _end_turn(self):
|
def _end_turn(self):
|
||||||
"""
|
"""
|
||||||
End of turn cleanup.
|
End of turn operations.
|
||||||
|
|
||||||
|
1. Do all moves
|
||||||
|
2. Do all regular actions
|
||||||
|
3. Remove combatants that disengaged successfully
|
||||||
|
4. Timeout advantages/disadvantages set for longer than STUNT_DURATION
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
# first do all moves
|
||||||
|
for combatant in self.combatants:
|
||||||
|
action, args, kwargs = self.action_queue[combatant].get(
|
||||||
|
"move", ("do_nothing", (), {}))
|
||||||
|
getattr(self, f"action_{action}")(combatant, *args, **kwargs)
|
||||||
|
# next do all regular actions
|
||||||
|
for combatant in self.combatants:
|
||||||
|
action, args, kwargs = self.action_qeueue[combatant].get(
|
||||||
|
"action", ("do_nothing", (), {}))
|
||||||
|
getattr(self, f"action_{action}")(combatant, *args, **kwargs)
|
||||||
|
|
||||||
|
# handle disengaging combatants
|
||||||
|
|
||||||
|
to_remove = []
|
||||||
|
|
||||||
|
for combatant in self.combatants:
|
||||||
|
# check disengaging combatants (these are combatants that managed
|
||||||
|
# to stay at disengaging distance for a turn)
|
||||||
|
if combatant in self.disengaging_combatants:
|
||||||
|
self.disengaging_combatants.remove(combatant)
|
||||||
|
to_remove.append(combatant)
|
||||||
|
elif all(1 for distance in self.distance_matrix[combatant].values()
|
||||||
|
if distance == MAX_RANGE):
|
||||||
|
# if at max distance (disengaging) from everyone, they are disengaging
|
||||||
|
self.disengaging_combatants.append(combatant)
|
||||||
|
|
||||||
|
for combatant in to_remove:
|
||||||
|
# for clarity, we remove here rather than modifying the combatant list
|
||||||
|
# inside the previous loop
|
||||||
|
self.msg(f"{combatant.key} disengaged and left combat.")
|
||||||
|
self.remove_combatant(combatant)
|
||||||
|
|
||||||
# refresh stunt timeouts
|
# refresh stunt timeouts
|
||||||
oldest_stunt_age = self.turn - self.stunt_duration
|
|
||||||
|
oldest_stunt_age = self.turn - STUNT_DURATION
|
||||||
|
|
||||||
advantage_matrix = self.advantage_matrix
|
advantage_matrix = self.advantage_matrix
|
||||||
disadvantage_matrix = self.disadvantage_matrix
|
disadvantage_matrix = self.disadvantage_matrix
|
||||||
|
# rebuild advantages with the (possibly cropped) list of combatants
|
||||||
# to avoid modifying the dict while we iterate over it, we
|
# we make new matrices in order to make sure disengaged combatants are
|
||||||
# put the results in new dicts. This also avoids us having to
|
# not included.
|
||||||
# delete from the old dicts.
|
|
||||||
new_advantage_matrix = {}
|
new_advantage_matrix = {}
|
||||||
new_disadvantage_matrix = {}
|
new_disadvantage_matrix = {}
|
||||||
|
|
||||||
|
|
@ -137,9 +234,20 @@ class EvAdventureCombat(DefaultScript):
|
||||||
target: set_at_turn for target, turn in disadvantage_matrix.items()
|
target: set_at_turn for target, turn in disadvantage_matrix.items()
|
||||||
if set_at_turn > oldest_stunt_age
|
if set_at_turn > oldest_stunt_age
|
||||||
}
|
}
|
||||||
|
|
||||||
self.advantage_matrix = new_advantage_matrix
|
self.advantage_matrix = new_advantage_matrix
|
||||||
self.disadvantage_matrix = new_disadvantage_matrix
|
self.disadvantage_matrix = new_disadvantage_matrix
|
||||||
|
|
||||||
|
def add_combatant(self, combatant):
|
||||||
|
if combatant not in self.combatants:
|
||||||
|
self.combatants.append(combatant)
|
||||||
|
self._refresh_distance_matrix()
|
||||||
|
|
||||||
|
def remove_combatant(self, combatant):
|
||||||
|
if combatant in self.combatants:
|
||||||
|
self.combatants.remove(combatant)
|
||||||
|
self._refresh_distance_matrix()
|
||||||
|
|
||||||
def msg(self, message, targets=None):
|
def msg(self, message, targets=None):
|
||||||
"""
|
"""
|
||||||
Central place for sending messages to combatants. This allows
|
Central place for sending messages to combatants. This allows
|
||||||
|
|
@ -159,17 +267,8 @@ class EvAdventureCombat(DefaultScript):
|
||||||
for target in self.combatants:
|
for target in self.combatants:
|
||||||
target.msg(message)
|
target.msg(message)
|
||||||
|
|
||||||
def add_combatant(self, combatant):
|
def move_relative_to(self, combatant, target_combatant, change,
|
||||||
if combatant not in self.combatants:
|
min_dist=MIN_RANGE, max_dist=MAX_RANGE):
|
||||||
self.combatants.append(combatant)
|
|
||||||
self._refresh_distance_matrix()
|
|
||||||
|
|
||||||
def remove_combatant(self, combatant):
|
|
||||||
if combatant in self.combatants:
|
|
||||||
self.combatants.remove(combatant)
|
|
||||||
self._refresh_distance_matrix()
|
|
||||||
|
|
||||||
def move_relative_to(self, combatant, target_combatant, change):
|
|
||||||
"""
|
"""
|
||||||
Change the distance to a target.
|
Change the distance to a target.
|
||||||
|
|
||||||
|
|
@ -181,7 +280,9 @@ class EvAdventureCombat(DefaultScript):
|
||||||
"""
|
"""
|
||||||
current_dist = self.distance_matrix[combatant][target_combatant]
|
current_dist = self.distance_matrix[combatant][target_combatant]
|
||||||
|
|
||||||
new_dist = max(MIN_RANGE, min(MAX_RANGE, current_dist + change))
|
change = max(0, min(MAX_MOVE_RATE, change))
|
||||||
|
|
||||||
|
new_dist = max(min_dist, min(max_dist, current_dist + change))
|
||||||
|
|
||||||
self.distance_matrix[combatant][target_combatant] = new_dist
|
self.distance_matrix[combatant][target_combatant] = new_dist
|
||||||
self.distance_matrix[target_combatant][combatant] = new_dist
|
self.distance_matrix[target_combatant][combatant] = new_dist
|
||||||
|
|
@ -239,18 +340,41 @@ class EvAdventureCombat(DefaultScript):
|
||||||
# defender still alive
|
# defender still alive
|
||||||
self.msg(defender)
|
self.msg(defender)
|
||||||
|
|
||||||
def stunt(self, attacker, defender, attack_type="agility",
|
def register_action(self, combatant, action="do_nothing", *args, **kwargs):
|
||||||
defense_type="agility", optimal_distance=0, suboptimal_distance=1,
|
|
||||||
advantage=True, beneficiary=None):
|
|
||||||
"""
|
"""
|
||||||
Stunts does not hurt anyone, but are used to give advantage/disadvantage to combatants
|
Register an action by-name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
combatant (Object): The one performing the action.
|
||||||
|
action (str): An available action, will be prepended with `action_` and
|
||||||
|
used to call the relevant handler on this script.
|
||||||
|
*args: Will be passed to the action method `action_<action>`.
|
||||||
|
**kwargs: Will be passed into the action method `action_<action>`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if action in self.move_actions:
|
||||||
|
self.action_queue[combatant]["move"] = (action, args, kwargs)
|
||||||
|
else:
|
||||||
|
self.action_queue[combatant]["action"] = (action, args, kwargs)
|
||||||
|
|
||||||
|
# action verbs. All of these start with action_* and should also accept
|
||||||
|
# *args, **kwargs so that we can make the call-mechanism generic.
|
||||||
|
|
||||||
|
def action_do_nothing(self, combatant, *args, **kwargs):
|
||||||
|
"""Do nothing for a turn."""
|
||||||
|
|
||||||
|
def action_stunt(self, attacker, defender, attack_type="agility",
|
||||||
|
defense_type="agility", optimal_distance=0, suboptimal_distance=1,
|
||||||
|
advantage=True, beneficiary=None, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Stunts does not cause damage but are used to give advantage/disadvantage to combatants
|
||||||
for later turns. The 'attacker' here is the one attemting the stunt against the 'defender'.
|
for later turns. The 'attacker' here is the one attemting the stunt against the 'defender'.
|
||||||
If successful, advantage is given to attacker against defender and disadvantage to
|
If successful, advantage is given to attacker against defender and disadvantage to
|
||||||
defender againt attacker. It's also possible to replace the attacker with another combatant
|
defender againt attacker. It's also possible to replace the attacker with another combatant
|
||||||
against the defender - allowing to aid/hinder others on the battlefield.
|
against the defender - allowing to aid/hinder others on the battlefield.
|
||||||
|
|
||||||
Stunt-modifers last a maximum of two turns and are not additive. Advantages and
|
Stunt-modifers last a maximum of two turns and are not additive. Advantages and
|
||||||
disadvantages against the same target cancel each other out.
|
disadvantages relative to the same target cancel each other out.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
attacker (Object): The one attempting the stunt.
|
attacker (Object): The one attempting the stunt.
|
||||||
|
|
@ -270,12 +394,12 @@ class EvAdventureCombat(DefaultScript):
|
||||||
distance = self.distance_matrix[attacker][defender]
|
distance = self.distance_matrix[attacker][defender]
|
||||||
disadvantage = False
|
disadvantage = False
|
||||||
if suboptimal_distance == distance:
|
if suboptimal_distance == distance:
|
||||||
# fighting at the wrong range is not good
|
# stunts need to be within range
|
||||||
disadvantage = True
|
disadvantage = True
|
||||||
elif self._get_optimal_distance(attacker) != distance:
|
elif self._get_optimal_distance(attacker) != distance:
|
||||||
# if we are neither at optimal nor suboptimal distance, we can't do the stunt
|
# if we are neither at optimal nor suboptimal distance, we can't do the stunt
|
||||||
# from here.
|
# from here.
|
||||||
raise AttackFailure(f"You can't perform this stunt "
|
raise CombatFailure(f"You can't perform this stunt "
|
||||||
f"from {RANGE_NAMES[distance]} distance (must be "
|
f"from {RANGE_NAMES[distance]} distance (must be "
|
||||||
f"{RANGE_NAMES[suboptimal_distance]} or, even better, "
|
f"{RANGE_NAMES[suboptimal_distance]} or, even better, "
|
||||||
f"{RANGE_NAMES[optimal_distance]}).")
|
f"{RANGE_NAMES[optimal_distance]}).")
|
||||||
|
|
@ -295,9 +419,10 @@ class EvAdventureCombat(DefaultScript):
|
||||||
|
|
||||||
return is_success
|
return is_success
|
||||||
|
|
||||||
def attack(self, attacker, defender):
|
def action_attack(self, attacker, defender, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Make an attack against a defender. This takes into account distance.
|
Make an attack against a defender. This takes into account distance. The
|
||||||
|
attack type/defense depends on the weapon/spell/whatever used.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# check if attacker is at optimal distance
|
# check if attacker is at optimal distance
|
||||||
|
|
@ -314,7 +439,7 @@ class EvAdventureCombat(DefaultScript):
|
||||||
elif self._get_optimal_distance(attacker) != distance:
|
elif self._get_optimal_distance(attacker) != distance:
|
||||||
# if we are neither at optimal nor suboptimal distance, we can't
|
# if we are neither at optimal nor suboptimal distance, we can't
|
||||||
# attack from this range
|
# attack from this range
|
||||||
raise AttackFailure(f"You can't attack with {attacker.weapon.key} "
|
raise CombatFailure(f"You can't attack with {attacker.weapon.key} "
|
||||||
f"from {RANGE_NAMES[distance]} distance.")
|
f"from {RANGE_NAMES[distance]} distance.")
|
||||||
|
|
||||||
is_hit, quality = rules.EvAdventureRollEngine.opposed_saving_throw(
|
is_hit, quality = rules.EvAdventureRollEngine.opposed_saving_throw(
|
||||||
|
|
@ -327,3 +452,144 @@ class EvAdventureCombat(DefaultScript):
|
||||||
self.resolve_damage(attacker, defender, critical=quality == "critical success")
|
self.resolve_damage(attacker, defender, critical=quality == "critical success")
|
||||||
|
|
||||||
return is_hit
|
return is_hit
|
||||||
|
|
||||||
|
def action_heal(self, combatant, target, max_distance=1, healing_roll="1d6", *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Heal a target. Target can be the combatant itself.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
combatant (Object): The one performing the heal.
|
||||||
|
target (Object): The one to be healed (can be the same as combatant).
|
||||||
|
max_distance (int): Distances *up to* this range allow for healing.
|
||||||
|
healing_roll (str): The die roll for how many HP to heal.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
CombatFailure: If too far away to heal target.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if target is not combatant:
|
||||||
|
distance = self.distance_matrix[attacker][defender]
|
||||||
|
if distance > max_distance:
|
||||||
|
raise CombatFailure(f"Too far away to heal {target.key}.")
|
||||||
|
|
||||||
|
target.heal(rules.EvAdventureRollEngine.roll(healing_roll), healer=combatant)
|
||||||
|
|
||||||
|
def action_approach(self, combatant, other_combatant, change, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Approach target. Closest is 0. This can be combined with another action.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.move_relative_to(combatant, other_combatant, -abs(change), min_dist=MIN_RANGE)
|
||||||
|
|
||||||
|
def action_withdraw(self, combatant, other_combatant, change):
|
||||||
|
"""
|
||||||
|
Withdraw from target. Most distant is range 3 - further and you'll be disengaging.
|
||||||
|
This can be combined with another action.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.move_relative_to(combatant, other_combatant, abs(change), max_dist=3)
|
||||||
|
|
||||||
|
def action_flee(self, combatant, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Fleeing/disengaging from combat means moving towards 'disengaging' range from
|
||||||
|
everyone else and staying there for one turn.
|
||||||
|
|
||||||
|
"""
|
||||||
|
for other_combatant in self.combatants:
|
||||||
|
self.move_relative_to(combatant, other_combatant, MAX_MOVE_RATE, max_dist=MAX_RANGE)
|
||||||
|
|
||||||
|
def action_chase(self, combatant, fleeing_target, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Chasing is a way to counter a 'flee' action. It is a maximum movement towards the target
|
||||||
|
and will mean a DEX contest, if the fleeing target loses, they are moved back from
|
||||||
|
'disengaging' range and remain in combat at the new distance (likely 2 if max movement
|
||||||
|
is 2). Advantage/disadvantage are considered.
|
||||||
|
|
||||||
|
"""
|
||||||
|
ability = "dexterity"
|
||||||
|
|
||||||
|
advantage = bool(self.advantage_matrix[attacker].pop(fleeing_target, False))
|
||||||
|
disadvantage = bool(self.disadvantage_matrix[attacker].pop(fleeing_target, False))
|
||||||
|
|
||||||
|
is_success, _ = rules.EvAdventureRollEngine.opposed_saving_throw(
|
||||||
|
combatant, fleeing_target,
|
||||||
|
attack_type=ability, defense_type=ability,
|
||||||
|
advantage=advantage, disadvantage=disadvantage
|
||||||
|
)
|
||||||
|
|
||||||
|
if is_success:
|
||||||
|
# managed to stop the target from fleeing/disengaging - move closer
|
||||||
|
if fleeing_target in self.disengaging_combatants:
|
||||||
|
self.disengaging_combatants.remove(fleeing_target)
|
||||||
|
self.approach(combatant, fleeing_target, change=MAX_MOVE_RATE)
|
||||||
|
|
||||||
|
return is_success
|
||||||
|
|
||||||
|
# combat menu
|
||||||
|
|
||||||
|
def _register_action(caller, raw_string, **kwargs):
|
||||||
|
"""
|
||||||
|
Register action with handler.
|
||||||
|
|
||||||
|
"""
|
||||||
|
action = kwargs.get['action']
|
||||||
|
action_args = kwargs['action_args']
|
||||||
|
action_kwargs = kwargs['action_kwargs']
|
||||||
|
combat = caller.scripts.get("combathandler")
|
||||||
|
combat.register_action(
|
||||||
|
caller, action=action, *action_args, **action_kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def node_select_target(caller, raw_string, **kwargs):
|
||||||
|
"""
|
||||||
|
Menu node allowing for selecting a target among all combatants. This combines
|
||||||
|
with all other actions.
|
||||||
|
|
||||||
|
"""
|
||||||
|
action = kwargs.get('action')
|
||||||
|
action_args = kwargs.get('action_args')
|
||||||
|
action_kwargs = kwargs.get('action_kwargs')
|
||||||
|
combat = caller.scripts.get("combathandler")
|
||||||
|
text = "Select target for |w{action}|n."
|
||||||
|
|
||||||
|
combatants = [combatant for combatant in combat.combatants if combatant is not caller]
|
||||||
|
options = [
|
||||||
|
{
|
||||||
|
"desc": combatant.key,
|
||||||
|
"goto": (_register_action, {"action": action,
|
||||||
|
"args": action_args,
|
||||||
|
"kwargs": action_kwargs})
|
||||||
|
}
|
||||||
|
for combatant in combat.combatants]
|
||||||
|
# make the apply-self option always the last one
|
||||||
|
options.append(
|
||||||
|
{
|
||||||
|
"desc": "(yourself)",
|
||||||
|
"goto": (_register_action, {"action": action,
|
||||||
|
"args": action_args,
|
||||||
|
"kwargs": action_kwargs})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return text, options
|
||||||
|
|
||||||
|
def node_select_action(caller, raw_string, **kwargs):
|
||||||
|
"""
|
||||||
|
Menu node for selecting a combat action.
|
||||||
|
|
||||||
|
"""
|
||||||
|
combat = caller.scripts.get("combathandler")
|
||||||
|
text = combat.get_previous_turn_status(caller)
|
||||||
|
options = combat.get_available_options(caller)
|
||||||
|
|
||||||
|
# TODO - reshuffle options
|
||||||
|
|
||||||
|
options = {
|
||||||
|
"desc": action,
|
||||||
|
"goto": ("node_select_target", {"action": action,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return text, options
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue