Format code with black. Add makefile to run fmt/tests

This commit is contained in:
Griatch 2019-09-28 18:18:11 +02:00
parent d00bce9288
commit c2c7fa311a
299 changed files with 19037 additions and 11611 deletions

View file

@ -52,8 +52,8 @@ OPTIONS
----------------------------------------------------------------------------
"""
TURN_TIMEOUT = 30 # Time before turns automatically end, in seconds
ACTIONS_PER_TURN = 1 # Number of actions allowed per turn
TURN_TIMEOUT = 30 # Time before turns automatically end, in seconds
ACTIONS_PER_TURN = 1 # Number of actions allowed per turn
"""
----------------------------------------------------------------------------
@ -61,6 +61,7 @@ COMBAT FUNCTIONS START HERE
----------------------------------------------------------------------------
"""
def roll_init(character):
"""
Rolls a number between 1-1000 to determine initiative.
@ -175,6 +176,7 @@ def apply_damage(defender, damage):
if defender.db.hp <= 0:
defender.db.hp = 0
def at_defeat(defeated):
"""
Announces the defeat of a fighter in combat.
@ -190,6 +192,7 @@ def at_defeat(defeated):
"""
defeated.location.msg_contents("%s has been defeated!" % defeated)
def resolve_attack(attacker, defender, attack_value=None, defense_value=None):
"""
Resolves an attack and outputs the result.
@ -215,12 +218,15 @@ def resolve_attack(attacker, defender, attack_value=None, defense_value=None):
else:
damage_value = get_damage(attacker, defender) # Calculate damage value.
# Announce damage dealt and apply damage.
attacker.location.msg_contents("%s hits %s for %i damage!" % (attacker, defender, damage_value))
attacker.location.msg_contents(
"%s hits %s for %i damage!" % (attacker, defender, damage_value)
)
apply_damage(defender, damage_value)
# If defender HP is reduced to 0 or less, call at_defeat.
if defender.db.hp <= 0:
at_defeat(defender)
def combat_cleanup(character):
"""
Cleans up all the temporary combat-related attributes on a character.
@ -279,7 +285,7 @@ def spend_action(character, actions, action_name=None):
"""
if action_name:
character.db.combat_lastaction = action_name
if actions == 'all': # If spending all actions
if actions == "all": # If spending all actions
character.db.combat_actionsleft = 0 # Set actions to 0
else:
character.db.combat_actionsleft -= actions # Use up actions.
@ -341,6 +347,7 @@ class TBBasicCharacter(DefaultCharacter):
return False
return True
"""
----------------------------------------------------------------------------
SCRIPTS START HERE
@ -388,7 +395,7 @@ class TBBasicTurnHandler(DefaultScript):
# Announce the turn order.
self.obj.msg_contents("Turn order is: %s " % ", ".join(obj.key for obj in self.db.fighters))
# Start first fighter's turn.
self.start_turn(self.db.fighters[0])
@ -408,13 +415,17 @@ class TBBasicTurnHandler(DefaultScript):
"""
Called once every self.interval seconds.
"""
currentchar = self.db.fighters[self.db.turn] # Note the current character in the turn order.
currentchar = self.db.fighters[
self.db.turn
] # Note the current character in the turn order.
self.db.timer -= self.interval # Count down the timer.
if self.db.timer <= 0:
# Force current character to disengage if timer runs out.
self.obj.msg_contents("%s's turn timed out!" % currentchar)
spend_action(currentchar, 'all', action_name="disengage") # Spend all remaining actions.
spend_action(
currentchar, "all", action_name="disengage"
) # Spend all remaining actions.
return
elif self.db.timer <= 10 and not self.db.timeout_warning_given: # 10 seconds left
# Warn the current character if they're about to time out.
@ -429,8 +440,12 @@ class TBBasicTurnHandler(DefaultScript):
character (obj): Character to initialize for combat.
"""
combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case.
character.db.combat_actionsleft = 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0
character.db.combat_turnhandler = self # Add a reference to this turn handler script to the character
character.db.combat_actionsleft = (
0
) # Actions remaining - start of turn adds to this, turn ends when it reaches 0
character.db.combat_turnhandler = (
self
) # Add a reference to this turn handler script to the character
character.db.combat_lastaction = "null" # Track last action taken in combat
def start_turn(self, character):
@ -460,7 +475,9 @@ class TBBasicTurnHandler(DefaultScript):
# Check to see if every character disengaged as their last action. If so, end combat.
disengage_check = True
for fighter in self.db.fighters:
if fighter.db.combat_lastaction != "disengage": # If a character has done anything but disengage
if (
fighter.db.combat_lastaction != "disengage"
): # If a character has done anything but disengage
disengage_check = False
if disengage_check: # All characters have disengaged
self.obj.msg_contents("All fighters have disengaged! Combat is over!")
@ -472,7 +489,9 @@ class TBBasicTurnHandler(DefaultScript):
for fighter in self.db.fighters:
if fighter.db.HP == 0:
defeated_characters += 1 # Add 1 for every fighter with 0 HP left (defeated)
if defeated_characters == (len(self.db.fighters) - 1): # If only one character isn't defeated
if defeated_characters == (
len(self.db.fighters) - 1
): # If only one character isn't defeated
for fighter in self.db.fighters:
if fighter.db.HP != 0:
LastStanding = fighter # Pick the one fighter left with HP remaining
@ -516,7 +535,7 @@ class TBBasicTurnHandler(DefaultScript):
# Initialize the character like you do at the start.
self.initialize_for_combat(character)
"""
----------------------------------------------------------------------------
COMMANDS START HERE
@ -535,6 +554,7 @@ class CmdFight(Command):
fight is added to combat, and a turn order is randomly rolled.
When it's your turn, you can attack other characters.
"""
key = "fight"
help_category = "combat"
@ -643,8 +663,10 @@ class CmdPass(Command):
self.caller.msg("You can only do that on your turn.")
return
self.caller.location.msg_contents("%s takes no further action, passing the turn." % self.caller)
spend_action(self.caller, 'all', action_name="pass") # Spend all remaining actions.
self.caller.location.msg_contents(
"%s takes no further action, passing the turn." % self.caller
)
spend_action(self.caller, "all", action_name="pass") # Spend all remaining actions.
class CmdDisengage(Command):
@ -676,7 +698,7 @@ class CmdDisengage(Command):
return
self.caller.location.msg_contents("%s disengages, ready to stop fighting." % self.caller)
spend_action(self.caller, 'all', action_name="disengage") # Spend all remaining actions.
spend_action(self.caller, "all", action_name="disengage") # Spend all remaining actions.
"""
The action_name kwarg sets the character's last action to "disengage", which is checked by
the turn handler script to see if all fighters have disengaged.
@ -723,15 +745,18 @@ class CmdCombatHelp(CmdHelp):
This will search for help on commands and other
topics related to the game.
"""
# Just like the default help command, but will give quick
# tips on combat when used in a fight with no arguments.
def func(self):
if is_in_combat(self.caller) and not self.args: # In combat and entered 'help' alone
self.caller.msg("Available combat commands:|/" +
"|wAttack:|n Attack a target, attempting to deal damage.|/" +
"|wPass:|n Pass your turn without further action.|/" +
"|wDisengage:|n End your turn and attempt to end combat.|/")
self.caller.msg(
"Available combat commands:|/"
+ "|wAttack:|n Attack a target, attempting to deal damage.|/"
+ "|wPass:|n Pass your turn without further action.|/"
+ "|wDisengage:|n End your turn and attempt to end combat.|/"
)
else:
super().func() # Call the default help command
@ -740,6 +765,7 @@ class BattleCmdSet(default_cmds.CharacterCmdSet):
"""
This command set includes all the commmands used in the battle system.
"""
key = "DefaultCharacter"
def at_cmdset_creation(self):
@ -751,4 +777,4 @@ class BattleCmdSet(default_cmds.CharacterCmdSet):
self.add(CmdRest())
self.add(CmdPass())
self.add(CmdDisengage())
self.add(CmdCombatHelp())
self.add(CmdCombatHelp())

View file

@ -64,8 +64,8 @@ OPTIONS
----------------------------------------------------------------------------
"""
TURN_TIMEOUT = 30 # Time before turns automatically end, in seconds
ACTIONS_PER_TURN = 1 # Number of actions allowed per turn
TURN_TIMEOUT = 30 # Time before turns automatically end, in seconds
ACTIONS_PER_TURN = 1 # Number of actions allowed per turn
"""
----------------------------------------------------------------------------
@ -73,6 +73,7 @@ COMBAT FUNCTIONS START HERE
----------------------------------------------------------------------------
"""
def roll_init(character):
"""
Rolls a number between 1-1000 to determine initiative.
@ -186,7 +187,9 @@ def get_damage(attacker, defender):
damage_value = randint(weapon.db.damage_range[0], weapon.db.damage_range[1])
# Use attacker's unarmed damage otherwise
else:
damage_value = randint(attacker.db.unarmed_damage_range[0], attacker.db.unarmed_damage_range[1])
damage_value = randint(
attacker.db.unarmed_damage_range[0], attacker.db.unarmed_damage_range[1]
)
# If defender is armored, reduce incoming damage
if defender.db.worn_armor:
armor = defender.db.worn_armor
@ -211,6 +214,7 @@ def apply_damage(defender, damage):
if defender.db.hp <= 0:
defender.db.hp = 0
def at_defeat(defeated):
"""
Announces the defeat of a fighter in combat.
@ -226,6 +230,7 @@ def at_defeat(defeated):
"""
defeated.location.msg_contents("%s has been defeated!" % defeated)
def resolve_attack(attacker, defender, attack_value=None, defense_value=None):
"""
Resolves an attack and outputs the result.
@ -252,14 +257,21 @@ def resolve_attack(attacker, defender, attack_value=None, defense_value=None):
defense_value = get_defense(attacker, defender)
# If the attack value is lower than the defense value, miss. Otherwise, hit.
if attack_value < defense_value:
attacker.location.msg_contents("%s's %s misses %s!" % (attacker, attackers_weapon, defender))
attacker.location.msg_contents(
"%s's %s misses %s!" % (attacker, attackers_weapon, defender)
)
else:
damage_value = get_damage(attacker, defender) # Calculate damage value.
# Announce damage dealt and apply damage.
if damage_value > 0:
attacker.location.msg_contents("%s's %s strikes %s for %i damage!" % (attacker, attackers_weapon, defender, damage_value))
attacker.location.msg_contents(
"%s's %s strikes %s for %i damage!"
% (attacker, attackers_weapon, defender, damage_value)
)
else:
attacker.location.msg_contents("%s's %s bounces harmlessly off %s!" % (attacker, attackers_weapon, defender))
attacker.location.msg_contents(
"%s's %s bounces harmlessly off %s!" % (attacker, attackers_weapon, defender)
)
apply_damage(defender, damage_value)
# If defender HP is reduced to 0 or less, call at_defeat.
if defender.db.hp <= 0:
@ -324,7 +336,7 @@ def spend_action(character, actions, action_name=None):
"""
if action_name:
character.db.combat_lastaction = action_name
if actions == 'all': # If spending all actions
if actions == "all": # If spending all actions
character.db.combat_actionsleft = 0 # Set actions to 0
else:
character.db.combat_actionsleft -= actions # Use up actions.
@ -332,6 +344,7 @@ def spend_action(character, actions, action_name=None):
character.db.combat_actionsleft = 0 # Can't have fewer than 0 actions
character.db.combat_turnhandler.turn_end_check(character) # Signal potential end of turn.
"""
----------------------------------------------------------------------------
SCRIPTS START HERE
@ -379,7 +392,7 @@ class TBEquipTurnHandler(DefaultScript):
# Announce the turn order.
self.obj.msg_contents("Turn order is: %s " % ", ".join(obj.key for obj in self.db.fighters))
# Start first fighter's turn.
self.start_turn(self.db.fighters[0])
@ -399,13 +412,17 @@ class TBEquipTurnHandler(DefaultScript):
"""
Called once every self.interval seconds.
"""
currentchar = self.db.fighters[self.db.turn] # Note the current character in the turn order.
currentchar = self.db.fighters[
self.db.turn
] # Note the current character in the turn order.
self.db.timer -= self.interval # Count down the timer.
if self.db.timer <= 0:
# Force current character to disengage if timer runs out.
self.obj.msg_contents("%s's turn timed out!" % currentchar)
spend_action(currentchar, 'all', action_name="disengage") # Spend all remaining actions.
spend_action(
currentchar, "all", action_name="disengage"
) # Spend all remaining actions.
return
elif self.db.timer <= 10 and not self.db.timeout_warning_given: # 10 seconds left
# Warn the current character if they're about to time out.
@ -420,8 +437,12 @@ class TBEquipTurnHandler(DefaultScript):
character (obj): Character to initialize for combat.
"""
combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case.
character.db.combat_actionsleft = 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0
character.db.combat_turnhandler = self # Add a reference to this turn handler script to the character
character.db.combat_actionsleft = (
0
) # Actions remaining - start of turn adds to this, turn ends when it reaches 0
character.db.combat_turnhandler = (
self
) # Add a reference to this turn handler script to the character
character.db.combat_lastaction = "null" # Track last action taken in combat
def start_turn(self, character):
@ -451,7 +472,9 @@ class TBEquipTurnHandler(DefaultScript):
# Check to see if every character disengaged as their last action. If so, end combat.
disengage_check = True
for fighter in self.db.fighters:
if fighter.db.combat_lastaction != "disengage": # If a character has done anything but disengage
if (
fighter.db.combat_lastaction != "disengage"
): # If a character has done anything but disengage
disengage_check = False
if disengage_check: # All characters have disengaged
self.obj.msg_contents("All fighters have disengaged! Combat is over!")
@ -463,7 +486,9 @@ class TBEquipTurnHandler(DefaultScript):
for fighter in self.db.fighters:
if fighter.db.HP == 0:
defeated_characters += 1 # Add 1 for every fighter with 0 HP left (defeated)
if defeated_characters == (len(self.db.fighters) - 1): # If only one character isn't defeated
if defeated_characters == (
len(self.db.fighters) - 1
): # If only one character isn't defeated
for fighter in self.db.fighters:
if fighter.db.HP != 0:
LastStanding = fighter # Pick the one fighter left with HP remaining
@ -507,24 +532,30 @@ class TBEquipTurnHandler(DefaultScript):
# Initialize the character like you do at the start.
self.initialize_for_combat(character)
"""
----------------------------------------------------------------------------
TYPECLASSES START HERE
----------------------------------------------------------------------------
"""
class TBEWeapon(DefaultObject):
"""
A weapon which can be wielded in combat with the 'wield' command.
"""
def at_object_creation(self):
"""
Called once, when this object is first created. This is the
normal hook to overload for most object types.
"""
self.db.damage_range = (15, 25) # Minimum and maximum damage on hit
self.db.accuracy_bonus = 0 # Bonus to attack rolls (or penalty if negative)
self.db.weapon_type_name = "weapon" # Single word for weapon - I.E. "dagger", "staff", "scimitar"
self.db.damage_range = (15, 25) # Minimum and maximum damage on hit
self.db.accuracy_bonus = 0 # Bonus to attack rolls (or penalty if negative)
self.db.weapon_type_name = (
"weapon"
) # Single word for weapon - I.E. "dagger", "staff", "scimitar"
def at_drop(self, dropper):
"""
Stop being wielded if dropped.
@ -532,6 +563,7 @@ class TBEWeapon(DefaultObject):
if dropper.db.wielded_weapon == self:
dropper.db.wielded_weapon = None
dropper.location.msg_contents("%s stops wielding %s." % (dropper, self))
def at_give(self, giver, getter):
"""
Stop being wielded if given.
@ -539,18 +571,23 @@ class TBEWeapon(DefaultObject):
if giver.db.wielded_weapon == self:
giver.db.wielded_weapon = None
giver.location.msg_contents("%s stops wielding %s." % (giver, self))
class TBEArmor(DefaultObject):
"""
A set of armor which can be worn with the 'don' command.
"""
def at_object_creation(self):
"""
Called once, when this object is first created. This is the
normal hook to overload for most object types.
"""
self.db.damage_reduction = 4 # Amount of incoming damage reduced by armor
self.db.defense_modifier = -4 # Amount to modify defense value (pos = harder to hit, neg = easier)
self.db.damage_reduction = 4 # Amount of incoming damage reduced by armor
self.db.defense_modifier = (
-4
) # Amount to modify defense value (pos = harder to hit, neg = easier)
def at_before_drop(self, dropper):
"""
Can't drop in combat.
@ -559,6 +596,7 @@ class TBEArmor(DefaultObject):
dropper.msg("You can't doff armor in a fight!")
return False
return True
def at_drop(self, dropper):
"""
Stop being wielded if dropped.
@ -566,6 +604,7 @@ class TBEArmor(DefaultObject):
if dropper.db.worn_armor == self:
dropper.db.worn_armor = None
dropper.location.msg_contents("%s removes %s." % (dropper, self))
def at_before_give(self, giver, getter):
"""
Can't give away in combat.
@ -574,6 +613,7 @@ class TBEArmor(DefaultObject):
dropper.msg("You can't doff armor in a fight!")
return False
return True
def at_give(self, giver, getter):
"""
Stop being wielded if given.
@ -582,6 +622,7 @@ class TBEArmor(DefaultObject):
giver.db.worn_armor = None
giver.location.msg_contents("%s removes %s." % (giver, self))
class TBEquipCharacter(DefaultCharacter):
"""
A character able to participate in turn-based combat. Has attributes for current
@ -595,11 +636,11 @@ class TBEquipCharacter(DefaultCharacter):
"""
self.db.max_hp = 100 # Set maximum HP to 100
self.db.hp = self.db.max_hp # Set current HP to maximum
self.db.wielded_weapon = None # Currently used weapon
self.db.worn_armor = None # Currently worn armor
self.db.unarmed_damage_range = (5, 15) # Minimum and maximum unarmed damage
self.db.unarmed_accuracy = 30 # Accuracy bonus for unarmed attacks
self.db.wielded_weapon = None # Currently used weapon
self.db.worn_armor = None # Currently worn armor
self.db.unarmed_damage_range = (5, 15) # Minimum and maximum unarmed damage
self.db.unarmed_accuracy = 30 # Accuracy bonus for unarmed attacks
"""
Adds attributes for a character's current and maximum HP.
We're just going to set this value at '100' by default.
@ -652,6 +693,7 @@ class CmdFight(Command):
fight is added to combat, and a turn order is randomly rolled.
When it's your turn, you can attack other characters.
"""
key = "fight"
help_category = "combat"
@ -760,8 +802,10 @@ class CmdPass(Command):
self.caller.msg("You can only do that on your turn.")
return
self.caller.location.msg_contents("%s takes no further action, passing the turn." % self.caller)
spend_action(self.caller, 'all', action_name="pass") # Spend all remaining actions.
self.caller.location.msg_contents(
"%s takes no further action, passing the turn." % self.caller
)
spend_action(self.caller, "all", action_name="pass") # Spend all remaining actions.
class CmdDisengage(Command):
@ -793,7 +837,7 @@ class CmdDisengage(Command):
return
self.caller.location.msg_contents("%s disengages, ready to stop fighting." % self.caller)
spend_action(self.caller, 'all', action_name="disengage") # Spend all remaining actions.
spend_action(self.caller, "all", action_name="disengage") # Spend all remaining actions.
"""
The action_name kwarg sets the character's last action to "disengage", which is checked by
the turn handler script to see if all fighters have disengaged.
@ -840,18 +884,22 @@ class CmdCombatHelp(CmdHelp):
This will search for help on commands and other
topics related to the game.
"""
# Just like the default help command, but will give quick
# tips on combat when used in a fight with no arguments.
def func(self):
if is_in_combat(self.caller) and not self.args: # In combat and entered 'help' alone
self.caller.msg("Available combat commands:|/" +
"|wAttack:|n Attack a target, attempting to deal damage.|/" +
"|wPass:|n Pass your turn without further action.|/" +
"|wDisengage:|n End your turn and attempt to end combat.|/")
self.caller.msg(
"Available combat commands:|/"
+ "|wAttack:|n Attack a target, attempting to deal damage.|/"
+ "|wPass:|n Pass your turn without further action.|/"
+ "|wDisengage:|n End your turn and attempt to end combat.|/"
)
else:
super().func() # Call the default help command
class CmdWield(Command):
"""
Wield a weapon you are carrying
@ -866,10 +914,10 @@ class CmdWield(Command):
"unwield" command to stop wielding any weapon you are
currently wielding.
"""
key = "wield"
help_category = "combat"
def func(self):
"""
This performs the actual command.
@ -889,18 +937,21 @@ class CmdWield(Command):
self.caller.msg("That's not a weapon!")
# Remember to update the path to the weapon typeclass if you move this module!
return
if not self.caller.db.wielded_weapon:
self.caller.db.wielded_weapon = weapon
self.caller.location.msg_contents("%s wields %s." % (self.caller, weapon))
else:
old_weapon = self.caller.db.wielded_weapon
self.caller.db.wielded_weapon = weapon
self.caller.location.msg_contents("%s lowers %s and wields %s." % (self.caller, old_weapon, weapon))
self.caller.location.msg_contents(
"%s lowers %s and wields %s." % (self.caller, old_weapon, weapon)
)
# Spend an action if in combat.
if is_in_combat(self.caller):
spend_action(self.caller, 1, action_name="wield") # Use up one action.
class CmdUnwield(Command):
"""
Stop wielding a weapon.
@ -911,10 +962,10 @@ class CmdUnwield(Command):
After using this command, you will stop wielding any
weapon you are currently wielding and become unarmed.
"""
key = "unwield"
help_category = "combat"
def func(self):
"""
This performs the actual command.
@ -930,7 +981,8 @@ class CmdUnwield(Command):
old_weapon = self.caller.db.wielded_weapon
self.caller.db.wielded_weapon = None
self.caller.location.msg_contents("%s lowers %s." % (self.caller, old_weapon))
class CmdDon(Command):
"""
Don armor that you are carrying
@ -942,10 +994,10 @@ class CmdDon(Command):
command in the middle of a fight. Use the "doff"
command to remove any armor you are wearing.
"""
key = "don"
help_category = "combat"
def func(self):
"""
This performs the actual command.
@ -964,15 +1016,18 @@ class CmdDon(Command):
self.caller.msg("That's not armor!")
# Remember to update the path to the armor typeclass if you move this module!
return
if not self.caller.db.worn_armor:
self.caller.db.worn_armor = armor
self.caller.location.msg_contents("%s dons %s." % (self.caller, armor))
else:
old_armor = self.caller.db.worn_armor
self.caller.db.worn_armor = armor
self.caller.location.msg_contents("%s removes %s and dons %s." % (self.caller, old_armor, armor))
self.caller.location.msg_contents(
"%s removes %s and dons %s." % (self.caller, old_armor, armor)
)
class CmdDoff(Command):
"""
Stop wearing armor.
@ -984,10 +1039,10 @@ class CmdDoff(Command):
armor you are currently using and become unarmored.
You can't use this command in combat.
"""
key = "doff"
help_category = "combat"
def func(self):
"""
This performs the actual command.
@ -1002,13 +1057,13 @@ class CmdDoff(Command):
old_armor = self.caller.db.worn_armor
self.caller.db.worn_armor = None
self.caller.location.msg_contents("%s removes %s." % (self.caller, old_armor))
class BattleCmdSet(default_cmds.CharacterCmdSet):
"""
This command set includes all the commmands used in the battle system.
"""
key = "DefaultCharacter"
def at_cmdset_creation(self):
@ -1026,61 +1081,58 @@ class BattleCmdSet(default_cmds.CharacterCmdSet):
self.add(CmdDon())
self.add(CmdDoff())
"""
----------------------------------------------------------------------------
PROTOTYPES START HERE
----------------------------------------------------------------------------
"""
BASEWEAPON = {
"typeclass": "evennia.contrib.turnbattle.tb_equip.TBEWeapon",
}
BASEARMOR = {
"typeclass": "evennia.contrib.turnbattle.tb_equip.TBEArmor",
}
BASEWEAPON = {"typeclass": "evennia.contrib.turnbattle.tb_equip.TBEWeapon"}
BASEARMOR = {"typeclass": "evennia.contrib.turnbattle.tb_equip.TBEArmor"}
DAGGER = {
"prototype" : "BASEWEAPON",
"damage_range" : (10, 20),
"accuracy_bonus" : 30,
"key": "a thin steel dagger",
"weapon_type_name" : "dagger"
"prototype": "BASEWEAPON",
"damage_range": (10, 20),
"accuracy_bonus": 30,
"key": "a thin steel dagger",
"weapon_type_name": "dagger",
}
BROADSWORD = {
"prototype" : "BASEWEAPON",
"damage_range" : (15, 30),
"accuracy_bonus" : 15,
"key": "an iron broadsword",
"weapon_type_name" : "broadsword"
"prototype": "BASEWEAPON",
"damage_range": (15, 30),
"accuracy_bonus": 15,
"key": "an iron broadsword",
"weapon_type_name": "broadsword",
}
GREATSWORD = {
"prototype" : "BASEWEAPON",
"damage_range" : (20, 40),
"accuracy_bonus" : 0,
"key": "a rune-etched greatsword",
"weapon_type_name" : "greatsword"
"prototype": "BASEWEAPON",
"damage_range": (20, 40),
"accuracy_bonus": 0,
"key": "a rune-etched greatsword",
"weapon_type_name": "greatsword",
}
LEATHERARMOR = {
"prototype" : "BASEARMOR",
"damage_reduction" : 2,
"defense_modifier" : -2,
"key": "a suit of leather armor"
"prototype": "BASEARMOR",
"damage_reduction": 2,
"defense_modifier": -2,
"key": "a suit of leather armor",
}
SCALEMAIL = {
"prototype" : "BASEARMOR",
"damage_reduction" : 4,
"defense_modifier" : -4,
"key": "a suit of scale mail"
"prototype": "BASEARMOR",
"damage_reduction": 4,
"defense_modifier": -4,
"key": "a suit of scale mail",
}
PLATEMAIL = {
"prototype" : "BASEARMOR",
"damage_reduction" : 6,
"defense_modifier" : -6,
"key": "a suit of plate mail"
"prototype": "BASEARMOR",
"damage_reduction": 6,
"defense_modifier": -6,
"key": "a suit of plate mail",
}

View file

@ -79,22 +79,22 @@ OPTIONS
----------------------------------------------------------------------------
"""
TURN_TIMEOUT = 30 # Time before turns automatically end, in seconds
ACTIONS_PER_TURN = 1 # Number of actions allowed per turn
NONCOMBAT_TURN_TIME = 30 # Time per turn count out of combat
TURN_TIMEOUT = 30 # Time before turns automatically end, in seconds
ACTIONS_PER_TURN = 1 # Number of actions allowed per turn
NONCOMBAT_TURN_TIME = 30 # Time per turn count out of combat
# Condition options start here.
# If you need to make changes to how your conditions work later,
# it's best to put the easily tweakable values all in one place!
REGEN_RATE = (4, 8) # Min and max HP regen for Regeneration
POISON_RATE = (4, 8) # Min and max damage for Poisoned
ACC_UP_MOD = 25 # Accuracy Up attack roll bonus
ACC_DOWN_MOD = -25 # Accuracy Down attack roll penalty
DMG_UP_MOD = 5 # Damage Up damage roll bonus
DMG_DOWN_MOD = -5 # Damage Down damage roll penalty
DEF_UP_MOD = 15 # Defense Up defense bonus
DEF_DOWN_MOD = -15 # Defense Down defense penalty
REGEN_RATE = (4, 8) # Min and max HP regen for Regeneration
POISON_RATE = (4, 8) # Min and max damage for Poisoned
ACC_UP_MOD = 25 # Accuracy Up attack roll bonus
ACC_DOWN_MOD = -25 # Accuracy Down attack roll penalty
DMG_UP_MOD = 5 # Damage Up damage roll bonus
DMG_DOWN_MOD = -5 # Damage Down damage roll penalty
DEF_UP_MOD = 15 # Defense Up defense bonus
DEF_DOWN_MOD = -15 # Defense Down defense penalty
"""
----------------------------------------------------------------------------
@ -102,6 +102,7 @@ COMBAT FUNCTIONS START HERE
----------------------------------------------------------------------------
"""
def roll_init(character):
"""
Rolls a number between 1-1000 to determine initiative.
@ -227,6 +228,7 @@ def apply_damage(defender, damage):
if defender.db.hp <= 0:
defender.db.hp = 0
def at_defeat(defeated):
"""
Announces the defeat of a fighter in combat.
@ -242,8 +244,15 @@ def at_defeat(defeated):
"""
defeated.location.msg_contents("%s has been defeated!" % defeated)
def resolve_attack(attacker, defender, attack_value=None, defense_value=None,
damage_value=None, inflict_condition=[]):
def resolve_attack(
attacker,
defender,
attack_value=None,
defense_value=None,
damage_value=None,
inflict_condition=[],
):
"""
Resolves an attack and outputs the result.
@ -275,7 +284,9 @@ def resolve_attack(attacker, defender, attack_value=None, defense_value=None,
if not damage_value:
damage_value = get_damage(attacker, defender) # Calculate damage value.
# Announce damage dealt and apply damage.
attacker.location.msg_contents("%s hits %s for %i damage!" % (attacker, defender, damage_value))
attacker.location.msg_contents(
"%s hits %s for %i damage!" % (attacker, defender, damage_value)
)
apply_damage(defender, damage_value)
# Inflict conditions on hit, if any specified
for condition in inflict_condition:
@ -284,6 +295,7 @@ def resolve_attack(attacker, defender, attack_value=None, defense_value=None,
if defender.db.hp <= 0:
at_defeat(defender)
def combat_cleanup(character):
"""
Cleans up all the temporary combat-related attributes on a character.
@ -342,7 +354,7 @@ def spend_action(character, actions, action_name=None):
"""
if action_name:
character.db.combat_lastaction = action_name
if actions == 'all': # If spending all actions
if actions == "all": # If spending all actions
character.db.combat_actionsleft = 0 # Set actions to 0
else:
character.db.combat_actionsleft -= actions # Use up actions.
@ -350,6 +362,7 @@ def spend_action(character, actions, action_name=None):
character.db.combat_actionsleft = 0 # Can't have fewer than 0 actions
character.db.combat_turnhandler.turn_end_check(character) # Signal potential end of turn.
def spend_item_use(item, user):
"""
Spends one use on an item with limited uses.
@ -364,28 +377,29 @@ def spend_item_use(item, user):
spawn a new object as residue, using the value of item.db.item_consumable
as the name of the prototype to spawn.
"""
item.db.item_uses -= 1 # Spend one use
item.db.item_uses -= 1 # Spend one use
if item.db.item_uses > 0: # Has uses remaining
if item.db.item_uses > 0: # Has uses remaining
# Inform the player
user.msg("%s has %i uses remaining." % (item.key.capitalize(), item.db.item_uses))
else: # All uses spent
else: # All uses spent
if not item.db.item_consumable: # Item isn't consumable
if not item.db.item_consumable: # Item isn't consumable
# Just inform the player that the uses are gone
user.msg("%s has no uses remaining." % item.key.capitalize())
else: # If item is consumable
if item.db.item_consumable == True: # If the value is 'True', just destroy the item
else: # If item is consumable
if item.db.item_consumable == True: # If the value is 'True', just destroy the item
user.msg("%s has been consumed." % item.key.capitalize())
item.delete() # Delete the spent item
item.delete() # Delete the spent item
else: # If a string, use value of item_consumable to spawn an object in its place
residue = spawn({"prototype":item.db.item_consumable})[0] # Spawn the residue
residue.location = item.location # Move the residue to the same place as the item
else: # If a string, use value of item_consumable to spawn an object in its place
residue = spawn({"prototype": item.db.item_consumable})[0] # Spawn the residue
residue.location = item.location # Move the residue to the same place as the item
user.msg("After using %s, you are left with %s." % (item, residue))
item.delete() # Delete the spent item
item.delete() # Delete the spent item
def use_item(user, item, target):
"""
@ -413,7 +427,7 @@ def use_item(user, item, target):
# Match item_func string to function
try:
item_func = ITEMFUNCS[item.db.item_func]
except KeyError: # If item_func string doesn't match to a function in ITEMFUNCS
except KeyError: # If item_func string doesn't match to a function in ITEMFUNCS
user.msg("ERROR: %s not defined in ITEMFUNCS" % item.db.item_func)
return
@ -432,6 +446,7 @@ def use_item(user, item, target):
if is_in_combat(user):
spend_action(user, 1, action_name="item")
def condition_tickdown(character, turnchar):
"""
Ticks down the duration of conditions on a character at the start of a given character's turn.
@ -457,9 +472,12 @@ def condition_tickdown(character, turnchar):
character.db.conditions[key][0] -= 1
if character.db.conditions[key][0] <= 0:
# If the duration is brought down to 0, remove the condition and inform everyone.
character.location.msg_contents("%s no longer has the '%s' condition." % (str(character), str(key)))
character.location.msg_contents(
"%s no longer has the '%s' condition." % (str(character), str(key))
)
del character.db.conditions[key]
def add_condition(character, turnchar, condition, duration):
"""
Adds a condition to a fighter.
@ -471,10 +489,11 @@ def add_condition(character, turnchar, condition, duration):
duration (int or True): Number of turns the condition lasts, or True for indefinite
"""
# The first value is the remaining turns - the second value is whose turn to count down on.
character.db.conditions.update({condition:[duration, turnchar]})
character.db.conditions.update({condition: [duration, turnchar]})
# Tell everyone!
character.location.msg_contents("%s gains the '%s' condition." % (character, condition))
"""
----------------------------------------------------------------------------
CHARACTER TYPECLASS
@ -495,7 +514,7 @@ class TBItemsCharacter(DefaultCharacter):
"""
self.db.max_hp = 100 # Set maximum HP to 100
self.db.hp = self.db.max_hp # Set current HP to maximum
self.db.conditions = {} # Set empty dict for conditions
self.db.conditions = {} # Set empty dict for conditions
# Subscribe character to the ticker handler
tickerhandler.add(NONCOMBAT_TURN_TIME, self.at_update, idstring="update")
"""
@ -554,15 +573,15 @@ class TBItemsCharacter(DefaultCharacter):
"""
# Regeneration: restores 4 to 8 HP at the start of character's turn
if "Regeneration" in self.db.conditions:
to_heal = randint(REGEN_RATE[0], REGEN_RAGE[1]) # Restore HP
to_heal = randint(REGEN_RATE[0], REGEN_RAGE[1]) # Restore HP
if self.db.hp + to_heal > self.db.max_hp:
to_heal = self.db.max_hp - self.db.hp # Cap healing to max HP
to_heal = self.db.max_hp - self.db.hp # Cap healing to max HP
self.db.hp += to_heal
self.location.msg_contents("%s regains %i HP from Regeneration." % (self, to_heal))
# Poisoned: does 4 to 8 damage at the start of character's turn
if "Poisoned" in self.db.conditions:
to_hurt = randint(POISON_RATE[0], POISON_RATE[1]) # Deal damage
to_hurt = randint(POISON_RATE[0], POISON_RATE[1]) # Deal damage
apply_damage(self, to_hurt)
self.location.msg_contents("%s takes %i damage from being Poisoned." % (self, to_hurt))
if self.db.hp <= 0:
@ -584,7 +603,7 @@ class TBItemsCharacter(DefaultCharacter):
"""
Fires every 30 seconds.
"""
if not is_in_combat(self): # Not in combat
if not is_in_combat(self): # Not in combat
# Change all conditions to update on character's turn.
for key in self.db.conditions:
self.db.conditions[key][1] = self
@ -593,15 +612,17 @@ class TBItemsCharacter(DefaultCharacter):
# Tick down condition durations
condition_tickdown(self, self)
class TBItemsCharacterTest(TBItemsCharacter):
"""
Just like the TBItemsCharacter, but doesn't subscribe to the TickerHandler.
This makes it easier to run unit tests on.
"""
def at_object_creation(self):
self.db.max_hp = 100 # Set maximum HP to 100
self.db.hp = self.db.max_hp # Set current HP to maximum
self.db.conditions = {} # Set empty dict for conditions
self.db.conditions = {} # Set empty dict for conditions
"""
@ -671,13 +692,17 @@ class TBItemsTurnHandler(DefaultScript):
"""
Called once every self.interval seconds.
"""
currentchar = self.db.fighters[self.db.turn] # Note the current character in the turn order.
currentchar = self.db.fighters[
self.db.turn
] # Note the current character in the turn order.
self.db.timer -= self.interval # Count down the timer.
if self.db.timer <= 0:
# Force current character to disengage if timer runs out.
self.obj.msg_contents("%s's turn timed out!" % currentchar)
spend_action(currentchar, 'all', action_name="disengage") # Spend all remaining actions.
spend_action(
currentchar, "all", action_name="disengage"
) # Spend all remaining actions.
return
elif self.db.timer <= 10 and not self.db.timeout_warning_given: # 10 seconds left
# Warn the current character if they're about to time out.
@ -692,8 +717,12 @@ class TBItemsTurnHandler(DefaultScript):
character (obj): Character to initialize for combat.
"""
combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case.
character.db.combat_actionsleft = 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0
character.db.combat_turnhandler = self # Add a reference to this turn handler script to the character
character.db.combat_actionsleft = (
0
) # Actions remaining - start of turn adds to this, turn ends when it reaches 0
character.db.combat_turnhandler = (
self
) # Add a reference to this turn handler script to the character
character.db.combat_lastaction = "null" # Track last action taken in combat
def start_turn(self, character):
@ -723,7 +752,9 @@ class TBItemsTurnHandler(DefaultScript):
# Check to see if every character disengaged as their last action. If so, end combat.
disengage_check = True
for fighter in self.db.fighters:
if fighter.db.combat_lastaction != "disengage": # If a character has done anything but disengage
if (
fighter.db.combat_lastaction != "disengage"
): # If a character has done anything but disengage
disengage_check = False
if disengage_check: # All characters have disengaged
self.obj.msg_contents("All fighters have disengaged! Combat is over!")
@ -735,7 +766,9 @@ class TBItemsTurnHandler(DefaultScript):
for fighter in self.db.fighters:
if fighter.db.HP == 0:
defeated_characters += 1 # Add 1 for every fighter with 0 HP left (defeated)
if defeated_characters == (len(self.db.fighters) - 1): # If only one character isn't defeated
if defeated_characters == (
len(self.db.fighters) - 1
): # If only one character isn't defeated
for fighter in self.db.fighters:
if fighter.db.HP != 0:
LastStanding = fighter # Pick the one fighter left with HP remaining
@ -804,6 +837,7 @@ class CmdFight(Command):
fight is added to combat, and a turn order is randomly rolled.
When it's your turn, you can attack other characters.
"""
key = "fight"
help_category = "combat"
@ -866,7 +900,7 @@ class CmdAttack(Command):
self.caller.msg("You can't attack, you've been defeated.")
return
if "Frightened" in self.caller.db.conditions: # Can't attack if frightened
if "Frightened" in self.caller.db.conditions: # Can't attack if frightened
self.caller.msg("You're too frightened to attack!")
return
@ -916,8 +950,10 @@ class CmdPass(Command):
self.caller.msg("You can only do that on your turn.")
return
self.caller.location.msg_contents("%s takes no further action, passing the turn." % self.caller)
spend_action(self.caller, 'all', action_name="pass") # Spend all remaining actions.
self.caller.location.msg_contents(
"%s takes no further action, passing the turn." % self.caller
)
spend_action(self.caller, "all", action_name="pass") # Spend all remaining actions.
class CmdDisengage(Command):
@ -949,7 +985,7 @@ class CmdDisengage(Command):
return
self.caller.location.msg_contents("%s disengages, ready to stop fighting." % self.caller)
spend_action(self.caller, 'all', action_name="disengage") # Spend all remaining actions.
spend_action(self.caller, "all", action_name="disengage") # Spend all remaining actions.
"""
The action_name kwarg sets the character's last action to "disengage", which is checked by
the turn handler script to see if all fighters have disengaged.
@ -996,16 +1032,19 @@ class CmdCombatHelp(CmdHelp):
This will search for help on commands and other
topics related to the game.
"""
# Just like the default help command, but will give quick
# tips on combat when used in a fight with no arguments.
def func(self):
if is_in_combat(self.caller) and not self.args: # In combat and entered 'help' alone
self.caller.msg("Available combat commands:|/" +
"|wAttack:|n Attack a target, attempting to deal damage.|/" +
"|wPass:|n Pass your turn without further action.|/" +
"|wDisengage:|n End your turn and attempt to end combat.|/" +
"|wUse:|n Use an item you're carrying.")
self.caller.msg(
"Available combat commands:|/"
+ "|wAttack:|n Attack a target, attempting to deal damage.|/"
+ "|wPass:|n Pass your turn without further action.|/"
+ "|wDisengage:|n End your turn and attempt to end combat.|/"
+ "|wUse:|n Use an item you're carrying."
)
else:
super(CmdCombatHelp, self).func() # Call the default help command
@ -1047,12 +1086,12 @@ class CmdUse(MuxCommand):
self.caller.msg("You can only use items on your turn.")
return
if not item.db.item_func: # Object has no item_func, not usable
if not item.db.item_func: # Object has no item_func, not usable
self.caller.msg("'%s' is not a usable item." % item.key.capitalize())
return
if item.attributes.has("item_uses"): # Item has limited uses
if item.db.item_uses <= 0: # Limited uses are spent
if item.attributes.has("item_uses"): # Item has limited uses
if item.db.item_uses <= 0: # Limited uses are spent
self.caller.msg("'%s' has no uses remaining." % item.key.capitalize())
return
@ -1064,6 +1103,7 @@ class BattleCmdSet(default_cmds.CharacterCmdSet):
"""
This command set includes all the commmands used in the battle system.
"""
key = "DefaultCharacter"
def at_cmdset_creation(self):
@ -1078,6 +1118,7 @@ class BattleCmdSet(default_cmds.CharacterCmdSet):
self.add(CmdCombatHelp())
self.add(CmdUse())
"""
----------------------------------------------------------------------------
ITEM FUNCTIONS START HERE
@ -1101,6 +1142,7 @@ Each function below contains a description of what kwargs the function will
take and the effect they have on the result.
"""
def itemfunc_heal(item, user, target, **kwargs):
"""
Item function that heals HP.
@ -1110,11 +1152,11 @@ def itemfunc_heal(item, user, target, **kwargs):
max_healing(int): Maximum amount of HP recovered
"""
if not target:
target = user # Target user if none specified
target = user # Target user if none specified
if not target.attributes.has("max_hp"): # Has no HP to speak of
if not target.attributes.has("max_hp"): # Has no HP to speak of
user.msg("You can't use %s on that." % item)
return False # Returning false aborts the item use
return False # Returning false aborts the item use
if target.db.hp >= target.db.max_hp:
user.msg("%s is already at full health." % target)
@ -1128,13 +1170,14 @@ def itemfunc_heal(item, user, target, **kwargs):
min_healing = kwargs["healing_range"][0]
max_healing = kwargs["healing_range"][1]
to_heal = randint(min_healing, max_healing) # Restore 20 to 40 hp
to_heal = randint(min_healing, max_healing) # Restore 20 to 40 hp
if target.db.hp + to_heal > target.db.max_hp:
to_heal = target.db.max_hp - target.db.hp # Cap healing to max HP
to_heal = target.db.max_hp - target.db.hp # Cap healing to max HP
target.db.hp += to_heal
user.location.msg_contents("%s uses %s! %s regains %i HP!" % (user, item, target, to_heal))
def itemfunc_add_condition(item, user, target, **kwargs):
"""
Item function that gives the target one or more conditions.
@ -1150,11 +1193,11 @@ def itemfunc_add_condition(item, user, target, **kwargs):
conditions = [("Regeneration", 5)]
if not target:
target = user # Target user if none specified
target = user # Target user if none specified
if not target.attributes.has("max_hp"): # Is not a fighter
if not target.attributes.has("max_hp"): # Is not a fighter
user.msg("You can't use %s on that." % item)
return False # Returning false aborts the item use
return False # Returning false aborts the item use
# Retrieve condition / duration from kwargs, if present
if "conditions" in kwargs:
@ -1166,6 +1209,7 @@ def itemfunc_add_condition(item, user, target, **kwargs):
for condition in conditions:
add_condition(target, user, condition[0], condition[1])
def itemfunc_cure_condition(item, user, target, **kwargs):
"""
Item function that'll remove given conditions from a target.
@ -1176,11 +1220,11 @@ def itemfunc_cure_condition(item, user, target, **kwargs):
to_cure = ["Poisoned"]
if not target:
target = user # Target user if none specified
target = user # Target user if none specified
if not target.attributes.has("max_hp"): # Is not a fighter
if not target.attributes.has("max_hp"): # Is not a fighter
user.msg("You can't use %s on that." % item)
return False # Returning false aborts the item use
return False # Returning false aborts the item use
# Retrieve condition(s) to cure from kwargs, if present
if "to_cure" in kwargs:
@ -1196,6 +1240,7 @@ def itemfunc_cure_condition(item, user, target, **kwargs):
user.location.msg_contents(item_msg)
def itemfunc_attack(item, user, target, **kwargs):
"""
Item function that attacks a target.
@ -1213,7 +1258,7 @@ def itemfunc_attack(item, user, target, **kwargs):
"""
if not is_in_combat(user):
user.msg("You can only use that in combat.")
return False # Returning false aborts the item use
return False # Returning false aborts the item use
if not target:
user.msg("You have to specify a target to use %s! (use <item> = <target>)" % item)
@ -1223,7 +1268,7 @@ def itemfunc_attack(item, user, target, **kwargs):
user.msg("You can't attack yourself!")
return False
if not target.db.hp: # Has no HP
if not target.db.hp: # Has no HP
user.msg("You can't use %s on that." % item)
return False
@ -1252,17 +1297,23 @@ def itemfunc_attack(item, user, target, **kwargs):
attack_value -= 25
user.location.msg_contents("%s attacks %s with %s!" % (user, target, item))
resolve_attack(user, target, attack_value=attack_value,
damage_value=damage_value, inflict_condition=inflict_condition)
resolve_attack(
user,
target,
attack_value=attack_value,
damage_value=damage_value,
inflict_condition=inflict_condition,
)
# Match strings to item functions here. We can't store callables on
# prototypes, so we store a string instead, matching that string to
# a callable in this dictionary.
ITEMFUNCS = {
"heal":itemfunc_heal,
"attack":itemfunc_attack,
"add_condition":itemfunc_add_condition,
"cure_condition":itemfunc_cure_condition
"heal": itemfunc_heal,
"attack": itemfunc_attack,
"add_condition": itemfunc_add_condition,
"cure_condition": itemfunc_cure_condition,
}
"""
@ -1297,101 +1348,110 @@ specifying any of the following:
"""
MEDKIT = {
"key" : "a medical kit",
"aliases" : ["medkit"],
"desc" : "A standard medical kit. It can be used a few times to heal wounds.",
"item_func" : "heal",
"item_uses" : 3,
"item_consumable" : True,
"item_kwargs" : {"healing_range":(15, 25)}
"key": "a medical kit",
"aliases": ["medkit"],
"desc": "A standard medical kit. It can be used a few times to heal wounds.",
"item_func": "heal",
"item_uses": 3,
"item_consumable": True,
"item_kwargs": {"healing_range": (15, 25)},
}
GLASS_BOTTLE = {
"key" : "a glass bottle",
"desc" : "An empty glass bottle."
}
GLASS_BOTTLE = {"key": "a glass bottle", "desc": "An empty glass bottle."}
HEALTH_POTION = {
"key" : "a health potion",
"desc" : "A glass bottle full of a mystical potion that heals wounds when used.",
"item_func" : "heal",
"item_uses" : 1,
"item_consumable" : "GLASS_BOTTLE",
"item_kwargs" : {"healing_range":(35, 50)}
"key": "a health potion",
"desc": "A glass bottle full of a mystical potion that heals wounds when used.",
"item_func": "heal",
"item_uses": 1,
"item_consumable": "GLASS_BOTTLE",
"item_kwargs": {"healing_range": (35, 50)},
}
REGEN_POTION = {
"key" : "a regeneration potion",
"desc" : "A glass bottle full of a mystical potion that regenerates wounds over time.",
"item_func" : "add_condition",
"item_uses" : 1,
"item_consumable" : "GLASS_BOTTLE",
"item_kwargs" : {"conditions":[("Regeneration", 10)]}
"key": "a regeneration potion",
"desc": "A glass bottle full of a mystical potion that regenerates wounds over time.",
"item_func": "add_condition",
"item_uses": 1,
"item_consumable": "GLASS_BOTTLE",
"item_kwargs": {"conditions": [("Regeneration", 10)]},
}
HASTE_POTION = {
"key" : "a haste potion",
"desc" : "A glass bottle full of a mystical potion that hastens its user.",
"item_func" : "add_condition",
"item_uses" : 1,
"item_consumable" : "GLASS_BOTTLE",
"item_kwargs" : {"conditions":[("Haste", 10)]}
"key": "a haste potion",
"desc": "A glass bottle full of a mystical potion that hastens its user.",
"item_func": "add_condition",
"item_uses": 1,
"item_consumable": "GLASS_BOTTLE",
"item_kwargs": {"conditions": [("Haste", 10)]},
}
BOMB = {
"key" : "a rotund bomb",
"desc" : "A large black sphere with a fuse at the end. Can be used on enemies in combat.",
"item_func" : "attack",
"item_uses" : 1,
"item_consumable" : True,
"item_kwargs" : {"damage_range":(25, 40), "accuracy":25}
"key": "a rotund bomb",
"desc": "A large black sphere with a fuse at the end. Can be used on enemies in combat.",
"item_func": "attack",
"item_uses": 1,
"item_consumable": True,
"item_kwargs": {"damage_range": (25, 40), "accuracy": 25},
}
POISON_DART = {
"key" : "a poison dart",
"desc" : "A thin dart coated in deadly poison. Can be used on enemies in combat",
"item_func" : "attack",
"item_uses" : 1,
"item_consumable" : True,
"item_kwargs" : {"damage_range":(5, 10), "accuracy":25, "inflict_condition":[("Poisoned", 10)]}
"key": "a poison dart",
"desc": "A thin dart coated in deadly poison. Can be used on enemies in combat",
"item_func": "attack",
"item_uses": 1,
"item_consumable": True,
"item_kwargs": {
"damage_range": (5, 10),
"accuracy": 25,
"inflict_condition": [("Poisoned", 10)],
},
}
TASER = {
"key" : "a taser",
"desc" : "A device that can be used to paralyze enemies in combat.",
"item_func" : "attack",
"item_kwargs" : {"damage_range":(10, 20), "accuracy":0, "inflict_condition":[("Paralyzed", 1)]}
"key": "a taser",
"desc": "A device that can be used to paralyze enemies in combat.",
"item_func": "attack",
"item_kwargs": {
"damage_range": (10, 20),
"accuracy": 0,
"inflict_condition": [("Paralyzed", 1)],
},
}
GHOST_GUN = {
"key" : "a ghost gun",
"desc" : "A gun that fires scary ghosts at people. Anyone hit by a ghost becomes frightened.",
"item_func" : "attack",
"item_uses" : 6,
"item_kwargs" : {"damage_range":(5, 10), "accuracy":15, "inflict_condition":[("Frightened", 1)]}
"key": "a ghost gun",
"desc": "A gun that fires scary ghosts at people. Anyone hit by a ghost becomes frightened.",
"item_func": "attack",
"item_uses": 6,
"item_kwargs": {
"damage_range": (5, 10),
"accuracy": 15,
"inflict_condition": [("Frightened", 1)],
},
}
ANTIDOTE_POTION = {
"key" : "an antidote potion",
"desc" : "A glass bottle full of a mystical potion that cures poison when used.",
"item_func" : "cure_condition",
"item_uses" : 1,
"item_consumable" : "GLASS_BOTTLE",
"item_kwargs" : {"to_cure":["Poisoned"]}
"key": "an antidote potion",
"desc": "A glass bottle full of a mystical potion that cures poison when used.",
"item_func": "cure_condition",
"item_uses": 1,
"item_consumable": "GLASS_BOTTLE",
"item_kwargs": {"to_cure": ["Poisoned"]},
}
AMULET_OF_MIGHT = {
"key" : "The Amulet of Might",
"desc" : "The one who holds this amulet can call upon its power to gain great strength.",
"item_func" : "add_condition",
"item_selfonly" : True,
"item_kwargs" : {"conditions":[("Damage Up", 3), ("Accuracy Up", 3), ("Defense Up", 3)]}
"key": "The Amulet of Might",
"desc": "The one who holds this amulet can call upon its power to gain great strength.",
"item_func": "add_condition",
"item_selfonly": True,
"item_kwargs": {"conditions": [("Damage Up", 3), ("Accuracy Up", 3), ("Defense Up", 3)]},
}
AMULET_OF_WEAKNESS = {
"key" : "The Amulet of Weakness",
"desc" : "The one who holds this amulet can call upon its power to gain great weakness. It's not a terribly useful artifact.",
"item_func" : "add_condition",
"item_selfonly" : True,
"item_kwargs" : {"conditions":[("Damage Down", 3), ("Accuracy Down", 3), ("Defense Down", 3)]}
"key": "The Amulet of Weakness",
"desc": "The one who holds this amulet can call upon its power to gain great weakness. It's not a terribly useful artifact.",
"item_func": "add_condition",
"item_selfonly": True,
"item_kwargs": {"conditions": [("Damage Down", 3), ("Accuracy Down", 3), ("Defense Down", 3)]},
}

View file

@ -71,8 +71,8 @@ OPTIONS
----------------------------------------------------------------------------
"""
TURN_TIMEOUT = 30 # Time before turns automatically end, in seconds
ACTIONS_PER_TURN = 1 # Number of actions allowed per turn
TURN_TIMEOUT = 30 # Time before turns automatically end, in seconds
ACTIONS_PER_TURN = 1 # Number of actions allowed per turn
"""
----------------------------------------------------------------------------
@ -80,6 +80,7 @@ COMBAT FUNCTIONS START HERE
----------------------------------------------------------------------------
"""
def roll_init(character):
"""
Rolls a number between 1-1000 to determine initiative.
@ -194,6 +195,7 @@ def apply_damage(defender, damage):
if defender.db.hp <= 0:
defender.db.hp = 0
def at_defeat(defeated):
"""
Announces the defeat of a fighter in combat.
@ -209,6 +211,7 @@ def at_defeat(defeated):
"""
defeated.location.msg_contents("%s has been defeated!" % defeated)
def resolve_attack(attacker, defender, attack_value=None, defense_value=None):
"""
Resolves an attack and outputs the result.
@ -234,12 +237,15 @@ def resolve_attack(attacker, defender, attack_value=None, defense_value=None):
else:
damage_value = get_damage(attacker, defender) # Calculate damage value.
# Announce damage dealt and apply damage.
attacker.location.msg_contents("%s hits %s for %i damage!" % (attacker, defender, damage_value))
attacker.location.msg_contents(
"%s hits %s for %i damage!" % (attacker, defender, damage_value)
)
apply_damage(defender, damage_value)
# If defender HP is reduced to 0 or less, call at_defeat.
if defender.db.hp <= 0:
at_defeat(defender)
def combat_cleanup(character):
"""
Cleans up all the temporary combat-related attributes on a character.
@ -300,7 +306,7 @@ def spend_action(character, actions, action_name=None):
return
if action_name:
character.db.combat_lastaction = action_name
if actions == 'all': # If spending all actions
if actions == "all": # If spending all actions
character.db.combat_actionsleft = 0 # Set actions to 0
else:
character.db.combat_actionsleft -= actions # Use up actions.
@ -335,10 +341,9 @@ class TBMagicCharacter(DefaultCharacter):
"""
self.db.max_hp = 100 # Set maximum HP to 100
self.db.hp = self.db.max_hp # Set current HP to maximum
self.db.spells_known = [] # Set empty spells known list
self.db.max_mp = 20 # Set maximum MP to 20
self.db.mp = self.db.max_mp # Set current MP to maximum
self.db.spells_known = [] # Set empty spells known list
self.db.max_mp = 20 # Set maximum MP to 20
self.db.mp = self.db.max_mp # Set current MP to maximum
def at_before_move(self, destination):
"""
@ -365,6 +370,7 @@ class TBMagicCharacter(DefaultCharacter):
return False
return True
"""
----------------------------------------------------------------------------
SCRIPTS START HERE
@ -412,7 +418,7 @@ class TBMagicTurnHandler(DefaultScript):
# Announce the turn order.
self.obj.msg_contents("Turn order is: %s " % ", ".join(obj.key for obj in self.db.fighters))
# Start first fighter's turn.
self.start_turn(self.db.fighters[0])
@ -432,13 +438,17 @@ class TBMagicTurnHandler(DefaultScript):
"""
Called once every self.interval seconds.
"""
currentchar = self.db.fighters[self.db.turn] # Note the current character in the turn order.
currentchar = self.db.fighters[
self.db.turn
] # Note the current character in the turn order.
self.db.timer -= self.interval # Count down the timer.
if self.db.timer <= 0:
# Force current character to disengage if timer runs out.
self.obj.msg_contents("%s's turn timed out!" % currentchar)
spend_action(currentchar, 'all', action_name="disengage") # Spend all remaining actions.
spend_action(
currentchar, "all", action_name="disengage"
) # Spend all remaining actions.
return
elif self.db.timer <= 10 and not self.db.timeout_warning_given: # 10 seconds left
# Warn the current character if they're about to time out.
@ -453,8 +463,12 @@ class TBMagicTurnHandler(DefaultScript):
character (obj): Character to initialize for combat.
"""
combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case.
character.db.combat_actionsleft = 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0
character.db.combat_turnhandler = self # Add a reference to this turn handler script to the character
character.db.combat_actionsleft = (
0
) # Actions remaining - start of turn adds to this, turn ends when it reaches 0
character.db.combat_turnhandler = (
self
) # Add a reference to this turn handler script to the character
character.db.combat_lastaction = "null" # Track last action taken in combat
def start_turn(self, character):
@ -484,7 +498,9 @@ class TBMagicTurnHandler(DefaultScript):
# Check to see if every character disengaged as their last action. If so, end combat.
disengage_check = True
for fighter in self.db.fighters:
if fighter.db.combat_lastaction != "disengage": # If a character has done anything but disengage
if (
fighter.db.combat_lastaction != "disengage"
): # If a character has done anything but disengage
disengage_check = False
if disengage_check: # All characters have disengaged
self.obj.msg_contents("All fighters have disengaged! Combat is over!")
@ -496,7 +512,9 @@ class TBMagicTurnHandler(DefaultScript):
for fighter in self.db.fighters:
if fighter.db.HP == 0:
defeated_characters += 1 # Add 1 for every fighter with 0 HP left (defeated)
if defeated_characters == (len(self.db.fighters) - 1): # If only one character isn't defeated
if defeated_characters == (
len(self.db.fighters) - 1
): # If only one character isn't defeated
for fighter in self.db.fighters:
if fighter.db.HP != 0:
LastStanding = fighter # Pick the one fighter left with HP remaining
@ -540,7 +558,7 @@ class TBMagicTurnHandler(DefaultScript):
# Initialize the character like you do at the start.
self.initialize_for_combat(character)
"""
----------------------------------------------------------------------------
COMMANDS START HERE
@ -559,6 +577,7 @@ class CmdFight(Command):
fight is added to combat, and a turn order is randomly rolled.
When it's your turn, you can attack other characters.
"""
key = "fight"
help_category = "combat"
@ -667,8 +686,10 @@ class CmdPass(Command):
self.caller.msg("You can only do that on your turn.")
return
self.caller.location.msg_contents("%s takes no further action, passing the turn." % self.caller)
spend_action(self.caller, 'all', action_name="pass") # Spend all remaining actions.
self.caller.location.msg_contents(
"%s takes no further action, passing the turn." % self.caller
)
spend_action(self.caller, "all", action_name="pass") # Spend all remaining actions.
class CmdDisengage(Command):
@ -700,12 +721,13 @@ class CmdDisengage(Command):
return
self.caller.location.msg_contents("%s disengages, ready to stop fighting." % self.caller)
spend_action(self.caller, 'all', action_name="disengage") # Spend all remaining actions.
spend_action(self.caller, "all", action_name="disengage") # Spend all remaining actions.
"""
The action_name kwarg sets the character's last action to "disengage", which is checked by
the turn handler script to see if all fighters have disengaged.
"""
class CmdLearnSpell(Command):
"""
Learn a magic spell.
@ -731,10 +753,10 @@ class CmdLearnSpell(Command):
|wcactus conjuration|n (2 MP): Creates a cactus.
"""
key = "learnspell"
help_category = "magic"
def func(self):
"""
This performs the actual command.
@ -744,38 +766,39 @@ class CmdLearnSpell(Command):
args = args.strip(" ")
caller = self.caller
spell_to_learn = []
if not args or len(args) < 3: # No spell given
if not args or len(args) < 3: # No spell given
caller.msg("Usage: learnspell <spell name>")
return
for spell in spell_list: # Match inputs to spells
for spell in spell_list: # Match inputs to spells
if args in spell.lower():
spell_to_learn.append(spell)
if spell_to_learn == []: # No spells matched
if spell_to_learn == []: # No spells matched
caller.msg("There is no spell with that name.")
return
if len(spell_to_learn) > 1: # More than one match
matched_spells = ', '.join(spell_to_learn)
if len(spell_to_learn) > 1: # More than one match
matched_spells = ", ".join(spell_to_learn)
caller.msg("Which spell do you mean: %s?" % matched_spells)
return
if len(spell_to_learn) == 1: # If one match, extract the string
if len(spell_to_learn) == 1: # If one match, extract the string
spell_to_learn = spell_to_learn[0]
if spell_to_learn not in self.caller.db.spells_known: # If the spell isn't known...
caller.db.spells_known.append(spell_to_learn) # ...then add the spell to the character
if spell_to_learn not in self.caller.db.spells_known: # If the spell isn't known...
caller.db.spells_known.append(spell_to_learn) # ...then add the spell to the character
caller.msg("You learn the spell '%s'!" % spell_to_learn)
return
if spell_to_learn in self.caller.db.spells_known: # Already has the spell specified
if spell_to_learn in self.caller.db.spells_known: # Already has the spell specified
caller.msg("You already know the spell '%s'!" % spell_to_learn)
"""
You will almost definitely want to replace this with your own system
for learning spells, perhaps tied to character advancement or finding
items in the game world that spells can be learned from.
"""
class CmdCast(MuxCommand):
"""
Cast a magic spell that you know, provided you have the MP
@ -788,10 +811,10 @@ class CmdCast(MuxCommand):
on only yourself, and some don't need a target specified at all.
Typing 'cast' by itself will give you a list of spells you know.
"""
key = "cast"
help_category = "magic"
def func(self):
"""
This performs the actual command.
@ -804,155 +827,169 @@ class CmdCast(MuxCommand):
function.
"""
caller = self.caller
if not self.lhs or len(self.lhs) < 3: # No spell name given
if not self.lhs or len(self.lhs) < 3: # No spell name given
caller.msg("Usage: cast <spell name> = <target>, <target2>, ...")
if not caller.db.spells_known:
caller.msg("You don't know any spells.")
return
else:
caller.db.spells_known = sorted(caller.db.spells_known)
spells_known_msg = "You know the following spells:|/" + "|/".join(caller.db.spells_known)
caller.msg(spells_known_msg) # List the spells the player knows
spells_known_msg = "You know the following spells:|/" + "|/".join(
caller.db.spells_known
)
caller.msg(spells_known_msg) # List the spells the player knows
return
spellname = self.lhs.lower()
spell_to_cast = []
spell_targets = []
if not self.rhs:
spell_targets = []
elif self.rhs.lower() in ['me', 'self', 'myself']:
elif self.rhs.lower() in ["me", "self", "myself"]:
spell_targets = [caller]
elif len(self.rhs) > 2:
spell_targets = self.rhslist
for spell in caller.db.spells_known: # Match inputs to spells
for spell in caller.db.spells_known: # Match inputs to spells
if self.lhs in spell.lower():
spell_to_cast.append(spell)
if spell_to_cast == []: # No spells matched
if spell_to_cast == []: # No spells matched
caller.msg("You don't know a spell of that name.")
return
if len(spell_to_cast) > 1: # More than one match
matched_spells = ', '.join(spell_to_cast)
if len(spell_to_cast) > 1: # More than one match
matched_spells = ", ".join(spell_to_cast)
caller.msg("Which spell do you mean: %s?" % matched_spells)
return
if len(spell_to_cast) == 1: # If one match, extract the string
if len(spell_to_cast) == 1: # If one match, extract the string
spell_to_cast = spell_to_cast[0]
if spell_to_cast not in SPELLS: # Spell isn't defined
if spell_to_cast not in SPELLS: # Spell isn't defined
caller.msg("ERROR: Spell %s is undefined" % spell_to_cast)
return
# Time to extract some info from the chosen spell!
spelldata = SPELLS[spell_to_cast]
# Add in some default data if optional parameters aren't specified
if "combat_spell" not in spelldata:
spelldata.update({"combat_spell":True})
spelldata.update({"combat_spell": True})
if "noncombat_spell" not in spelldata:
spelldata.update({"noncombat_spell":True})
spelldata.update({"noncombat_spell": True})
if "max_targets" not in spelldata:
spelldata.update({"max_targets":1})
spelldata.update({"max_targets": 1})
# Store any superfluous options as kwargs to pass to the spell function
kwargs = {}
spelldata_opts = ["spellfunc", "target", "cost", "combat_spell", "noncombat_spell", "max_targets"]
spelldata_opts = [
"spellfunc",
"target",
"cost",
"combat_spell",
"noncombat_spell",
"max_targets",
]
for key in spelldata:
if key not in spelldata_opts:
kwargs.update({key:spelldata[key]})
kwargs.update({key: spelldata[key]})
# If caster doesn't have enough MP to cover the spell's cost, give error and return
if spelldata["cost"] > caller.db.mp:
caller.msg("You don't have enough MP to cast '%s'." % spell_to_cast)
return
# If in combat and the spell isn't a combat spell, give error message and return
if spelldata["combat_spell"] == False and is_in_combat(caller):
caller.msg("You can't use the spell '%s' in combat." % spell_to_cast)
return
# If not in combat and the spell isn't a non-combat spell, error ms and return.
if spelldata["noncombat_spell"] == False and is_in_combat(caller) == False:
caller.msg("You can't use the spell '%s' outside of combat." % spell_to_cast)
return
# If spell takes no targets and one is given, give error message and return
if len(spell_targets) > 0 and spelldata["target"] == "none":
caller.msg("The spell '%s' isn't cast on a target." % spell_to_cast)
return
# If no target is given and spell requires a target, give error message
if spelldata["target"] not in ["self", "none"]:
if len(spell_targets) == 0:
caller.msg("The spell '%s' requires a target." % spell_to_cast)
return
# If more targets given than maximum, give error message
if len(spell_targets) > spelldata["max_targets"]:
targplural = "target"
if spelldata["max_targets"] > 1:
targplural = "targets"
caller.msg("The spell '%s' can only be cast on %i %s." % (spell_to_cast, spelldata["max_targets"], targplural))
caller.msg(
"The spell '%s' can only be cast on %i %s."
% (spell_to_cast, spelldata["max_targets"], targplural)
)
return
# Set up our candidates for targets
target_candidates = []
# If spell targets 'any' or 'other', any object in caster's inventory or location
# can be targeted by the spell.
if spelldata["target"] in ["any", "other"]:
target_candidates = caller.location.contents + caller.contents
# If spell targets 'anyobj', only non-character objects can be targeted.
if spelldata["target"] == "anyobj":
prefilter_candidates = caller.location.contents + caller.contents
for thing in prefilter_candidates:
if not thing.attributes.has("max_hp"): # Has no max HP, isn't a fighter
if not thing.attributes.has("max_hp"): # Has no max HP, isn't a fighter
target_candidates.append(thing)
# If spell targets 'anychar' or 'otherchar', only characters can be targeted.
if spelldata["target"] in ["anychar", "otherchar"]:
prefilter_candidates = caller.location.contents
for thing in prefilter_candidates:
if thing.attributes.has("max_hp"): # Has max HP, is a fighter
if thing.attributes.has("max_hp"): # Has max HP, is a fighter
target_candidates.append(thing)
# Now, match each entry in spell_targets to an object in the search candidates
matched_targets = []
for target in spell_targets:
match = caller.search(target, candidates=target_candidates)
matched_targets.append(match)
spell_targets = matched_targets
# If no target is given and the spell's target is 'self', set target to self
if len(spell_targets) == 0 and spelldata["target"] == "self":
spell_targets = [caller]
# Give error message if trying to cast an "other" target spell on yourself
if spelldata["target"] in ["other", "otherchar"]:
if caller in spell_targets:
caller.msg("You can't cast '%s' on yourself." % spell_to_cast)
return
# Return if "None" in target list, indicating failed match
if None in spell_targets:
# No need to give an error message, as 'search' gives one by default.
return
# Give error message if repeats in target list
if len(spell_targets) != len(set(spell_targets)):
caller.msg("You can't specify the same target more than once!")
return
# Finally, we can cast the spell itself. Note that MP is not deducted here!
try:
spelldata["spellfunc"](caller, spell_to_cast, spell_targets, spelldata["cost"], **kwargs)
spelldata["spellfunc"](
caller, spell_to_cast, spell_targets, spelldata["cost"], **kwargs
)
except Exception:
log_trace("Error in callback for spell: %s." % spell_to_cast)
class CmdRest(Command):
"""
@ -979,7 +1016,8 @@ class CmdRest(Command):
self.caller.db.mp = self.caller.db.max_mp # Set current MP to maximum
self.caller.location.msg_contents("%s rests to recover HP and MP." % self.caller)
# You'll probably want to replace this with your own system for recovering HP and MP.
class CmdStatus(Command):
"""
Gives combat information.
@ -997,15 +1035,19 @@ class CmdStatus(Command):
def func(self):
"This performs the actual command."
char = self.caller
if not char.db.max_hp: # Character not initialized, IE in unit tests
if not char.db.max_hp: # Character not initialized, IE in unit tests
char.db.hp = 100
char.db.max_hp = 100
char.db.spells_known = []
char.db.max_mp = 20
char.db.mp = char.db.max_mp
char.msg("You have %i / %i HP and %i / %i MP." % (char.db.hp, char.db.max_hp, char.db.mp, char.db.max_mp))
char.msg(
"You have %i / %i HP and %i / %i MP."
% (char.db.hp, char.db.max_hp, char.db.mp, char.db.max_mp)
)
class CmdCombatHelp(CmdHelp):
"""
@ -1019,15 +1061,18 @@ class CmdCombatHelp(CmdHelp):
This will search for help on commands and other
topics related to the game.
"""
# Just like the default help command, but will give quick
# tips on combat when used in a fight with no arguments.
def func(self):
if is_in_combat(self.caller) and not self.args: # In combat and entered 'help' alone
self.caller.msg("Available combat commands:|/" +
"|wAttack:|n Attack a target, attempting to deal damage.|/" +
"|wPass:|n Pass your turn without further action.|/" +
"|wDisengage:|n End your turn and attempt to end combat.|/")
self.caller.msg(
"Available combat commands:|/"
+ "|wAttack:|n Attack a target, attempting to deal damage.|/"
+ "|wPass:|n Pass your turn without further action.|/"
+ "|wDisengage:|n End your turn and attempt to end combat.|/"
)
else:
super(CmdCombatHelp, self).func() # Call the default help command
@ -1036,6 +1081,7 @@ class BattleCmdSet(default_cmds.CharacterCmdSet):
"""
This command set includes all the commmands used in the battle system.
"""
key = "DefaultCharacter"
def at_cmdset_creation(self):
@ -1052,6 +1098,7 @@ class BattleCmdSet(default_cmds.CharacterCmdSet):
self.add(CmdCast())
self.add(CmdStatus())
"""
----------------------------------------------------------------------------
SPELL FUNCTIONS START HERE
@ -1072,6 +1119,7 @@ These functions also all accept **kwargs, and how these are used is specified
in the docstring for each function.
"""
def spell_healing(caster, spell_name, targets, cost, **kwargs):
"""
Spell that restores HP to a target or targets.
@ -1081,29 +1129,30 @@ def spell_healing(caster, spell_name, targets, cost, **kwargs):
each target. (20, 40) by default.
"""
spell_msg = "%s casts %s!" % (caster, spell_name)
min_healing = 20
max_healing = 40
# Retrieve healing range from kwargs, if present
if "healing_range" in kwargs:
min_healing = kwargs["healing_range"][0]
max_healing = kwargs["healing_range"][1]
for character in targets:
to_heal = randint(min_healing, max_healing) # Restore 20 to 40 hp
to_heal = randint(min_healing, max_healing) # Restore 20 to 40 hp
if character.db.hp + to_heal > character.db.max_hp:
to_heal = character.db.max_hp - character.db.hp # Cap healing to max HP
to_heal = character.db.max_hp - character.db.hp # Cap healing to max HP
character.db.hp += to_heal
spell_msg += " %s regains %i HP!" % (character, to_heal)
caster.db.mp -= cost # Deduct MP cost
caster.location.msg_contents(spell_msg) # Message the room with spell results
if is_in_combat(caster): # Spend action if in combat
caster.db.mp -= cost # Deduct MP cost
caster.location.msg_contents(spell_msg) # Message the room with spell results
if is_in_combat(caster): # Spend action if in combat
spend_action(caster, 1, action_name="cast")
def spell_attack(caster, spell_name, targets, cost, **kwargs):
"""
Spell that deals damage in combat. Similar to resolve_attack.
@ -1123,14 +1172,14 @@ def spell_attack(caster, spell_name, targets, cost, **kwargs):
attacked once.
"""
spell_msg = "%s casts %s!" % (caster, spell_name)
atkname_single = "The spell"
atkname_plural = "spells"
min_damage = 10
max_damage = 20
accuracy = 0
attack_count = 1
# Retrieve some variables from kwargs, if present
if "attack_name" in kwargs:
atkname_single = kwargs["attack_name"][0]
@ -1142,7 +1191,7 @@ def spell_attack(caster, spell_name, targets, cost, **kwargs):
accuracy = kwargs["accuracy"]
if "attack_count" in kwargs:
attack_count = kwargs["attack_count"]
to_attack = []
# If there are more attacks than targets given, attack first target multiple times
if len(targets) < attack_count:
@ -1152,24 +1201,23 @@ def spell_attack(caster, spell_name, targets, cost, **kwargs):
to_attack.insert(0, targets[0])
else:
to_attack = to_attack + targets
# Set up dictionaries to track number of hits and total damage
total_hits = {}
total_damage = {}
for fighter in targets:
total_hits.update({fighter:0})
total_damage.update({fighter:0})
total_hits.update({fighter: 0})
total_damage.update({fighter: 0})
# Resolve attack for each target
for fighter in to_attack:
attack_value = randint(1, 100) + accuracy # Spell attack roll
attack_value = randint(1, 100) + accuracy # Spell attack roll
defense_value = get_defense(caster, fighter)
if attack_value >= defense_value:
spell_dmg = randint(min_damage, max_damage) # Get spell damage
spell_dmg = randint(min_damage, max_damage) # Get spell damage
total_hits[fighter] += 1
total_damage[fighter] += spell_dmg
for fighter in targets:
# Construct combat message
if total_hits[fighter] == 0:
@ -1178,22 +1226,27 @@ def spell_attack(caster, spell_name, targets, cost, **kwargs):
attack_count_str = atkname_single + " hits"
if total_hits[fighter] > 1:
attack_count_str = "%i %s hit" % (total_hits[fighter], atkname_plural)
spell_msg += " %s %s for %i damage!" % (attack_count_str, fighter, total_damage[fighter])
caster.db.mp -= cost # Deduct MP cost
caster.location.msg_contents(spell_msg) # Message the room with spell results
spell_msg += " %s %s for %i damage!" % (
attack_count_str,
fighter,
total_damage[fighter],
)
caster.db.mp -= cost # Deduct MP cost
caster.location.msg_contents(spell_msg) # Message the room with spell results
for fighter in targets:
# Apply damage
apply_damage(fighter, total_damage[fighter])
# If fighter HP is reduced to 0 or less, call at_defeat.
if fighter.db.hp <= 0:
at_defeat(fighter)
if is_in_combat(caster): # Spend action if in combat
if is_in_combat(caster): # Spend action if in combat
spend_action(caster, 1, action_name="cast")
def spell_conjure(caster, spell_name, targets, cost, **kwargs):
"""
Spell that creates an object.
@ -1207,11 +1260,11 @@ def spell_conjure(caster, spell_name, targets, cost, **kwargs):
you may want to modify it to use the spawner (in evennia.utils.spawner)
instead of creating objects directly.
"""
obj_key = "a nondescript object"
obj_desc = "A perfectly generic object."
obj_typeclass = "evennia.objects.objects.DefaultObject"
# Retrieve some variables from kwargs, if present
if "obj_key" in kwargs:
obj_key = kwargs["obj_key"]
@ -1219,14 +1272,19 @@ def spell_conjure(caster, spell_name, targets, cost, **kwargs):
obj_desc = kwargs["obj_desc"]
if "obj_typeclass" in kwargs:
obj_typeclass = kwargs["obj_typeclass"]
conjured_obj = create_object(obj_typeclass, key=obj_key, location=caster.location) # Create object
conjured_obj.db.desc = obj_desc # Add object desc
caster.db.mp -= cost # Deduct MP cost
conjured_obj = create_object(
obj_typeclass, key=obj_key, location=caster.location
) # Create object
conjured_obj.db.desc = obj_desc # Add object desc
caster.db.mp -= cost # Deduct MP cost
# Message the room to announce the creation of the object
caster.location.msg_contents("%s casts %s, and %s appears!" % (caster, spell_name, conjured_obj))
caster.location.msg_contents(
"%s casts %s, and %s appears!" % (caster, spell_name, conjured_obj)
)
"""
----------------------------------------------------------------------------
@ -1272,19 +1330,44 @@ dictionary.
"""
SPELLS = {
"magic missile":{"spellfunc":spell_attack, "target":"otherchar", "cost":3, "noncombat_spell":False, "max_targets":3,
"attack_name":("A bolt", "bolts"), "damage_range":(4, 7), "accuracy":999, "attack_count":3},
"flame shot":{"spellfunc":spell_attack, "target":"otherchar", "cost":3, "noncombat_spell":False,
"attack_name":("A jet of flame", "jets of flame"), "damage_range":(25, 35)},
"cure wounds":{"spellfunc":spell_healing, "target":"anychar", "cost":5},
"mass cure wounds":{"spellfunc":spell_healing, "target":"anychar", "cost":10, "max_targets": 5},
"full heal":{"spellfunc":spell_healing, "target":"anychar", "cost":12, "healing_range":(100, 100)},
"cactus conjuration":{"spellfunc":spell_conjure, "target":"none", "cost":2, "combat_spell":False,
"obj_key":"a cactus", "obj_desc":"An ordinary green cactus with little spines."}
"magic missile": {
"spellfunc": spell_attack,
"target": "otherchar",
"cost": 3,
"noncombat_spell": False,
"max_targets": 3,
"attack_name": ("A bolt", "bolts"),
"damage_range": (4, 7),
"accuracy": 999,
"attack_count": 3,
},
"flame shot": {
"spellfunc": spell_attack,
"target": "otherchar",
"cost": 3,
"noncombat_spell": False,
"attack_name": ("A jet of flame", "jets of flame"),
"damage_range": (25, 35),
},
"cure wounds": {"spellfunc": spell_healing, "target": "anychar", "cost": 5},
"mass cure wounds": {
"spellfunc": spell_healing,
"target": "anychar",
"cost": 10,
"max_targets": 5,
},
"full heal": {
"spellfunc": spell_healing,
"target": "anychar",
"cost": 12,
"healing_range": (100, 100),
},
"cactus conjuration": {
"spellfunc": spell_conjure,
"target": "none",
"cost": 2,
"combat_spell": False,
"obj_key": "a cactus",
"obj_desc": "An ordinary green cactus with little spines.",
},
}

View file

@ -110,8 +110,8 @@ OPTIONS
----------------------------------------------------------------------------
"""
TURN_TIMEOUT = 30 # Time before turns automatically end, in seconds
ACTIONS_PER_TURN = 2 # Number of actions allowed per turn
TURN_TIMEOUT = 30 # Time before turns automatically end, in seconds
ACTIONS_PER_TURN = 2 # Number of actions allowed per turn
"""
----------------------------------------------------------------------------
@ -119,6 +119,7 @@ COMBAT FUNCTIONS START HERE
----------------------------------------------------------------------------
"""
def roll_init(character):
"""
Rolls a number between 1-1000 to determine initiative.
@ -241,6 +242,7 @@ def apply_damage(defender, damage):
if defender.db.hp <= 0:
defender.db.hp = 0
def at_defeat(defeated):
"""
Announces the defeat of a fighter in combat.
@ -256,6 +258,7 @@ def at_defeat(defeated):
"""
defeated.location.msg_contents("%s has been defeated!" % defeated)
def resolve_attack(attacker, defender, attack_type, attack_value=None, defense_value=None):
"""
Resolves an attack and outputs the result.
@ -278,16 +281,22 @@ def resolve_attack(attacker, defender, attack_type, attack_value=None, defense_v
defense_value = get_defense(attacker, defender, attack_type)
# If the attack value is lower than the defense value, miss. Otherwise, hit.
if attack_value < defense_value:
attacker.location.msg_contents("%s's %s attack misses %s!" % (attacker, attack_type, defender))
attacker.location.msg_contents(
"%s's %s attack misses %s!" % (attacker, attack_type, defender)
)
else:
damage_value = get_damage(attacker, defender) # Calculate damage value.
# Announce damage dealt and apply damage.
attacker.location.msg_contents("%s hits %s with a %s attack for %i damage!" % (attacker, defender, attack_type, damage_value))
attacker.location.msg_contents(
"%s hits %s with a %s attack for %i damage!"
% (attacker, defender, attack_type, damage_value)
)
apply_damage(defender, damage_value)
# If defender HP is reduced to 0 or less, call at_defeat.
if defender.db.hp <= 0:
at_defeat(defender)
def get_range(obj1, obj2):
"""
Gets the combat range between two objects.
@ -310,7 +319,8 @@ def get_range(obj1, obj2):
return None
# Return the range between the two objects.
return obj1.db.combat_range[obj2]
def distance_inc(mover, target):
"""
Function that increases distance in range field between mover and target.
@ -325,7 +335,8 @@ def distance_inc(mover, target):
if get_range(mover, target) > 2:
target.db.combat_range[mover] = 2
mover.db.combat_range[target] = 2
def approach(mover, target):
"""
Manages a character's whole approach, including changes in ranges to other characters.
@ -339,6 +350,7 @@ def approach(mover, target):
target than the mover is. The mover will also move away from anything they started
out close to.
"""
def distance_dec(mover, target):
"""
Helper function that decreases distance in range field between mover and target.
@ -360,7 +372,7 @@ def approach(mover, target):
thing.db.combat_range[mover] = thing.db.combat_range[target]
contents = mover.location.contents
for thing in contents:
if thing != mover and thing != target:
# Move closer to each object closer to the target than you.
@ -372,6 +384,7 @@ def approach(mover, target):
# Lastly, move closer to your target.
distance_dec(mover, target)
def withdraw(mover, target):
"""
Manages a character's whole withdrawal, including changes in ranges to other characters.
@ -387,11 +400,13 @@ def withdraw(mover, target):
"""
contents = mover.location.contents
for thing in contents:
if thing != mover and thing != target:
# Move away from each object closer to the target than you, if it's also closer to you than you are to the target.
if get_range(mover, thing) >= get_range(target, thing) and get_range(mover, thing) < get_range(mover, target):
if get_range(mover, thing) >= get_range(target, thing) and get_range(
mover, thing
) < get_range(mover, target):
distance_inc(mover, thing)
# Move away from anything your target is engaged with
if get_range(target, thing) == 0:
@ -402,6 +417,7 @@ def withdraw(mover, target):
# Then, move away from your target.
distance_inc(mover, target)
def combat_cleanup(character):
"""
Cleans up all the temporary combat-related attributes on a character.
@ -460,7 +476,7 @@ def spend_action(character, actions, action_name=None):
"""
if action_name:
character.db.combat_lastaction = action_name
if actions == 'all': # If spending all actions
if actions == "all": # If spending all actions
character.db.combat_actionsleft = 0 # Set actions to 0
else:
character.db.combat_actionsleft -= actions # Use up actions.
@ -468,6 +484,7 @@ def spend_action(character, actions, action_name=None):
character.db.combat_actionsleft = 0 # Can't have fewer than 0 actions
character.db.combat_turnhandler.turn_end_check(character) # Signal potential end of turn.
def combat_status_message(fighter):
"""
Sends a message to a player with their current HP and
@ -478,16 +495,16 @@ def combat_status_message(fighter):
fighter.db.hp = 100
fighter.db.max_hp = 100
status_msg = ("HP Remaining: %i / %i" % (fighter.db.hp, fighter.db.max_hp))
status_msg = "HP Remaining: %i / %i" % (fighter.db.hp, fighter.db.max_hp)
if not is_in_combat(fighter):
fighter.msg(status_msg)
return
engaged_obj = []
reach_obj = []
range_obj = []
for thing in fighter.db.combat_range:
if thing != fighter:
if fighter.db.combat_range[thing] == 0:
@ -496,16 +513,17 @@ def combat_status_message(fighter):
reach_obj.append(thing)
if fighter.db.combat_range[thing] > 1:
range_obj.append(thing)
if engaged_obj:
status_msg += "|/Engaged targets: %s" % ", ".join(obj.key for obj in engaged_obj)
if reach_obj:
status_msg += "|/Reach targets: %s" % ", ".join(obj.key for obj in reach_obj)
if range_obj:
status_msg += "|/Ranged targets: %s" % ", ".join(obj.key for obj in range_obj)
fighter.msg(status_msg)
"""
----------------------------------------------------------------------------
SCRIPTS START HERE
@ -545,7 +563,7 @@ class TBRangeTurnHandler(DefaultScript):
# Add a reference to this script to the room
self.obj.db.combat_turnhandler = self
# Initialize range field for all objects in the room
for thing in self.obj.contents:
self.init_range(thing)
@ -557,7 +575,7 @@ class TBRangeTurnHandler(DefaultScript):
# Announce the turn order.
self.obj.msg_contents("Turn order is: %s " % ", ".join(obj.key for obj in self.db.fighters))
# Start first fighter's turn.
self.start_turn(self.db.fighters[0])
@ -577,19 +595,23 @@ class TBRangeTurnHandler(DefaultScript):
"""
Called once every self.interval seconds.
"""
currentchar = self.db.fighters[self.db.turn] # Note the current character in the turn order.
currentchar = self.db.fighters[
self.db.turn
] # Note the current character in the turn order.
self.db.timer -= self.interval # Count down the timer.
if self.db.timer <= 0:
# Force current character to disengage if timer runs out.
self.obj.msg_contents("%s's turn timed out!" % currentchar)
spend_action(currentchar, 'all', action_name="disengage") # Spend all remaining actions.
spend_action(
currentchar, "all", action_name="disengage"
) # Spend all remaining actions.
return
elif self.db.timer <= 10 and not self.db.timeout_warning_given: # 10 seconds left
# Warn the current character if they're about to time out.
currentchar.msg("WARNING: About to time out!")
self.db.timeout_warning_given = True
def init_range(self, to_init):
"""
Initializes range values for an object at the start of a fight.
@ -603,14 +625,14 @@ class TBRangeTurnHandler(DefaultScript):
for thing in objectlist:
# Object always at distance 0 from itself
if thing == to_init:
rangedict.update({thing:0})
rangedict.update({thing: 0})
else:
if thing.destination or to_init.destination:
# Start exits at range 2 to put them at the 'edges'
rangedict.update({thing:2})
rangedict.update({thing: 2})
else:
# Start objects at range 1 from other objects
rangedict.update({thing:1})
rangedict.update({thing: 1})
to_init.db.combat_range = rangedict
def join_rangefield(self, to_init, anchor_obj=None, add_distance=0):
@ -630,15 +652,15 @@ class TBRangeTurnHandler(DefaultScript):
contents.remove(to_init)
# If no anchor object given, pick one in the room at random.
if not anchor_obj:
anchor_obj = contents[randint(0, (len(contents)-1))]
anchor_obj = contents[randint(0, (len(contents) - 1))]
# Copy the range values from the anchor object.
to_init.db.combat_range = anchor_obj.db.combat_range
# Add the new object to everyone else's ranges.
for thing in contents:
new_objects_range = thing.db.combat_range[anchor_obj]
thing.db.combat_range.update({to_init:new_objects_range})
thing.db.combat_range.update({to_init: new_objects_range})
# Set the new object's range to itself to 0.
to_init.db.combat_range.update({to_init:0})
to_init.db.combat_range.update({to_init: 0})
# Add additional distance from anchor object, if any.
for n in range(add_distance):
withdraw(to_init, anchor_obj)
@ -651,8 +673,12 @@ class TBRangeTurnHandler(DefaultScript):
character (obj): Character to initialize for combat.
"""
combat_cleanup(character) # Clean up leftover combat attributes beforehand, just in case.
character.db.combat_actionsleft = 0 # Actions remaining - start of turn adds to this, turn ends when it reaches 0
character.db.combat_turnhandler = self # Add a reference to this turn handler script to the character
character.db.combat_actionsleft = (
0
) # Actions remaining - start of turn adds to this, turn ends when it reaches 0
character.db.combat_turnhandler = (
self
) # Add a reference to this turn handler script to the character
character.db.combat_lastaction = "null" # Track last action taken in combat
def start_turn(self, character):
@ -681,7 +707,9 @@ class TBRangeTurnHandler(DefaultScript):
# Check to see if every character disengaged as their last action. If so, end combat.
disengage_check = True
for fighter in self.db.fighters:
if fighter.db.combat_lastaction != "disengage": # If a character has done anything but disengage
if (
fighter.db.combat_lastaction != "disengage"
): # If a character has done anything but disengage
disengage_check = False
if disengage_check: # All characters have disengaged
self.obj.msg_contents("All fighters have disengaged! Combat is over!")
@ -693,7 +721,9 @@ class TBRangeTurnHandler(DefaultScript):
for fighter in self.db.fighters:
if fighter.db.HP == 0:
defeated_characters += 1 # Add 1 for every fighter with 0 HP left (defeated)
if defeated_characters == (len(self.db.fighters) - 1): # If only one character isn't defeated
if defeated_characters == (
len(self.db.fighters) - 1
): # If only one character isn't defeated
for fighter in self.db.fighters:
if fighter.db.HP != 0:
LastStanding = fighter # Pick the one fighter left with HP remaining
@ -794,6 +824,7 @@ class TBRangeCharacter(DefaultCharacter):
return False
return True
class TBRangeObject(DefaultObject):
"""
An object that is assigned range values in combat. Getting, giving, and dropping
@ -801,6 +832,7 @@ class TBRangeObject(DefaultObject):
must be next to your target to give them something, and can only interact with
objects on your own turn.
"""
def at_before_drop(self, dropper):
"""
Called by the default `drop` command before this object has been
@ -865,7 +897,7 @@ class TBRangeObject(DefaultObject):
"""
# Restrictions for getting in combat
if is_in_combat(getter):
if not is_turn(getter): # Not your turn
if not is_turn(getter): # Not your turn
getter.msg("You can only get things on your turn!")
return False
if get_range(self, getter) > 0: # Too far away
@ -921,11 +953,13 @@ class TBRangeObject(DefaultObject):
"""
# Restrictions for giving in combat
if is_in_combat(giver):
if not is_turn(giver): # Not your turn
if not is_turn(giver): # Not your turn
giver.msg("You can only give things on your turn!")
return False
if get_range(giver, getter) > 0: # Too far away from target
giver.msg("You aren't close enough to give things to %s! (see: help approach)" % getter)
giver.msg(
"You aren't close enough to give things to %s! (see: help approach)" % getter
)
return False
return True
@ -949,6 +983,7 @@ class TBRangeObject(DefaultObject):
if is_in_combat(giver):
spend_action(giver, 1, action_name="give") # Use up one action.
"""
----------------------------------------------------------------------------
COMMANDS START HERE
@ -967,6 +1002,7 @@ class CmdFight(Command):
fight is added to combat, and a turn order is randomly rolled.
When it's your turn, you can attack other characters.
"""
key = "fight"
help_category = "combat"
@ -1044,15 +1080,19 @@ class CmdAttack(Command):
if attacker == defender: # Target and attacker are the same
self.caller.msg("You can't attack yourself!")
return
if not get_range(attacker, defender) == 0: # Target isn't in melee
self.caller.msg("%s is too far away to attack - you need to get closer! (see: help approach)" % defender)
self.caller.msg(
"%s is too far away to attack - you need to get closer! (see: help approach)"
% defender
)
return
"If everything checks out, call the attack resolving function."
resolve_attack(attacker, defender, "melee")
spend_action(self.caller, 1, action_name="attack") # Use up one action.
class CmdShoot(Command):
"""
Attacks another character from range.
@ -1099,22 +1139,26 @@ class CmdShoot(Command):
if attacker == defender: # Target and attacker are the same
self.caller.msg("You can't attack yourself!")
return
# Test to see if there are any nearby enemy targets.
in_melee = []
for target in attacker.db.combat_range:
# Object is engaged and has HP
if get_range(attacker, defender) == 0 and target.db.hp and target != self.caller:
in_melee.append(target) # Add to list of targets in melee
in_melee.append(target) # Add to list of targets in melee
if len(in_melee) > 0:
self.caller.msg("You can't shoot because there are fighters engaged with you (%s) - you need to retreat! (see: help withdraw)" % ", ".join(obj.key for obj in in_melee))
self.caller.msg(
"You can't shoot because there are fighters engaged with you (%s) - you need to retreat! (see: help withdraw)"
% ", ".join(obj.key for obj in in_melee)
)
return
"If everything checks out, call the attack resolving function."
resolve_attack(attacker, defender, "ranged")
spend_action(self.caller, 1, action_name="attack") # Use up one action.
class CmdApproach(Command):
"""
Approaches an object.
@ -1157,8 +1201,8 @@ class CmdApproach(Command):
if mover == target: # Target and mover are the same
self.caller.msg("You can't move toward yourself!")
return
if get_range(mover, target) <= 0: # Already engaged with target
if get_range(mover, target) <= 0: # Already engaged with target
self.caller.msg("You're already next to that target!")
return
@ -1166,7 +1210,8 @@ class CmdApproach(Command):
approach(mover, target)
mover.location.msg_contents("%s moves toward %s." % (mover, target))
spend_action(self.caller, 1, action_name="move") # Use up one action.
class CmdWithdraw(Command):
"""
Moves away from an object.
@ -1208,8 +1253,8 @@ class CmdWithdraw(Command):
if mover == target: # Target and mover are the same
self.caller.msg("You can't move away from yourself!")
return
if mover.db.combat_range[target] >= 3: # Already at maximum distance
if mover.db.combat_range[target] >= 3: # Already at maximum distance
self.caller.msg("You're as far as you can get from that target!")
return
@ -1246,8 +1291,10 @@ class CmdPass(Command):
self.caller.msg("You can only do that on your turn.")
return
self.caller.location.msg_contents("%s takes no further action, passing the turn." % self.caller)
spend_action(self.caller, 'all', action_name="pass") # Spend all remaining actions.
self.caller.location.msg_contents(
"%s takes no further action, passing the turn." % self.caller
)
spend_action(self.caller, "all", action_name="pass") # Spend all remaining actions.
class CmdDisengage(Command):
@ -1279,7 +1326,7 @@ class CmdDisengage(Command):
return
self.caller.location.msg_contents("%s disengages, ready to stop fighting." % self.caller)
spend_action(self.caller, 'all', action_name="disengage") # Spend all remaining actions.
spend_action(self.caller, "all", action_name="disengage") # Spend all remaining actions.
"""
The action_name kwarg sets the character's last action to "disengage", which is checked by
the turn handler script to see if all fighters have disengaged.
@ -1313,6 +1360,7 @@ class CmdRest(Command):
You'll probably want to replace this with your own system for recovering HP.
"""
class CmdStatus(Command):
"""
Gives combat information.
@ -1330,7 +1378,7 @@ class CmdStatus(Command):
def func(self):
"This performs the actual command."
combat_status_message(self.caller)
class CmdCombatHelp(CmdHelp):
"""
@ -1344,19 +1392,22 @@ class CmdCombatHelp(CmdHelp):
This will search for help on commands and other
topics related to the game.
"""
# Just like the default help command, but will give quick
# tips on combat when used in a fight with no arguments.
def func(self):
if is_in_combat(self.caller) and not self.args: # In combat and entered 'help' alone
self.caller.msg("Available combat commands:|/" +
"|wAttack:|n Attack an engaged target, attempting to deal damage.|/" +
"|wShoot:|n Attack from a distance, if not engaged with other fighters.|/" +
"|wApproach:|n Move one step cloer to a target.|/" +
"|wWithdraw:|n Move one step away from a target.|/" +
"|wPass:|n Pass your turn without further action.|/" +
"|wStatus:|n View current HP and ranges to other targets.|/" +
"|wDisengage:|n End your turn and attempt to end combat.|/")
self.caller.msg(
"Available combat commands:|/"
+ "|wAttack:|n Attack an engaged target, attempting to deal damage.|/"
+ "|wShoot:|n Attack from a distance, if not engaged with other fighters.|/"
+ "|wApproach:|n Move one step cloer to a target.|/"
+ "|wWithdraw:|n Move one step away from a target.|/"
+ "|wPass:|n Pass your turn without further action.|/"
+ "|wStatus:|n View current HP and ranges to other targets.|/"
+ "|wDisengage:|n End your turn and attempt to end combat.|/"
)
else:
super(CmdCombatHelp, self).func() # Call the default help command
@ -1365,6 +1416,7 @@ class BattleCmdSet(default_cmds.CharacterCmdSet):
"""
This command set includes all the commmands used in the battle system.
"""
key = "DefaultCharacter"
def at_cmdset_creation(self):
@ -1380,4 +1432,4 @@ class BattleCmdSet(default_cmds.CharacterCmdSet):
self.add(CmdApproach())
self.add(CmdWithdraw())
self.add(CmdStatus())
self.add(CmdCombatHelp())
self.add(CmdCombatHelp())