mirror of
https://github.com/evennia/evennia.git
synced 2026-03-29 12:07:17 +02:00
Formatting and documentation
This commit is contained in:
parent
83579a2e06
commit
858494eebb
1 changed files with 160 additions and 46 deletions
|
|
@ -1,20 +1,38 @@
|
|||
"""
|
||||
Simple turn-based combat system
|
||||
Simple turn-based combat system with spell casting
|
||||
|
||||
Contrib - Tim Ashley Jenkins 2017
|
||||
|
||||
This is a framework for a simple turn-based combat system, similar
|
||||
to those used in D&D-style tabletop role playing games. It allows
|
||||
any character to start a fight in a room, at which point initiative
|
||||
is rolled and a turn order is established. Each participant in combat
|
||||
has a limited time to decide their action for that turn (30 seconds by
|
||||
default), and combat progresses through the turn order, looping through
|
||||
the participants until the fight ends.
|
||||
This is a version of the 'turnbattle' contrib that includes a basic,
|
||||
expandable framework for a 'magic system', whereby players can spend
|
||||
a limited resource (MP) to achieve a wide variety of effects, both in
|
||||
and out of combat. This does not have to strictly be a system for
|
||||
magic - it can easily be re-flavored to any other sort of resource
|
||||
based mechanic, like psionic powers, special moves and stamina, and
|
||||
so forth.
|
||||
|
||||
Only simple rolls for attacking are implemented here, but this system
|
||||
is easily extensible and can be used as the foundation for implementing
|
||||
the rules from your turn-based tabletop game of choice or making your
|
||||
own battle system.
|
||||
In this system, spells are learned by name with the 'learnspell'
|
||||
command, and then used with the 'cast' command. Spells can be cast in or
|
||||
out of combat - some spells can only be cast in combat, some can only be
|
||||
cast outside of combat, and some can be cast any time. However, if you
|
||||
are in combat, you can only cast a spell on your turn, and doing so will
|
||||
typically use an action (as specified in the spell's funciton).
|
||||
|
||||
Spells are defined at the end of the module in a database that's a
|
||||
dictionary of dictionaries - each spell is matched by name to a function,
|
||||
along with various parameters that restrict when the spell can be used and
|
||||
what the spell can be cast on. Included is a small variety of spells that
|
||||
damage opponents and heal HP, as well as one that creates an object.
|
||||
|
||||
Because a spell can call any function, a spell can be made to do just
|
||||
about anything at all. The SPELLS dictionary at the bottom of the module
|
||||
even allows kwargs to be passed to the spell function, so that the same
|
||||
function can be re-used for multiple similar spells.
|
||||
|
||||
Spells in this system work on a very basic resource: MP, which is spent
|
||||
when casting spells and restored by resting. It shouldn't be too difficult
|
||||
to modify this system to use spell slots, some physical fuel or resource,
|
||||
or whatever else your game requires.
|
||||
|
||||
To install and test, import this module's TBMagicCharacter object into
|
||||
your game's character.py module:
|
||||
|
|
@ -43,7 +61,7 @@ in your game and using it as-is.
|
|||
"""
|
||||
|
||||
from random import randint
|
||||
from evennia import DefaultCharacter, Command, default_cmds, DefaultScript
|
||||
from evennia import DefaultCharacter, Command, default_cmds, DefaultScript, create_object
|
||||
from evennia.commands.default.muxcommand import MuxCommand
|
||||
from evennia.commands.default.help import CmdHelp
|
||||
|
||||
|
|
@ -690,7 +708,12 @@ class CmdDisengage(Command):
|
|||
|
||||
class CmdLearnSpell(Command):
|
||||
"""
|
||||
Learn a magic spell
|
||||
Learn a magic spell.
|
||||
|
||||
Usage:
|
||||
learnspell <spell name>
|
||||
|
||||
Adds a spell by name to your list of spells known.
|
||||
"""
|
||||
|
||||
key = "learnspell"
|
||||
|
|
@ -706,7 +729,7 @@ class CmdLearnSpell(Command):
|
|||
caller = self.caller
|
||||
spell_to_learn = []
|
||||
|
||||
if not args or len(args) < 3:
|
||||
if not args or len(args) < 3: # No spell given
|
||||
caller.msg("Usage: learnspell <spell name>")
|
||||
return
|
||||
|
||||
|
|
@ -725,23 +748,29 @@ class CmdLearnSpell(Command):
|
|||
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:
|
||||
caller.db.spells_known.append(spell_to_learn)
|
||||
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:
|
||||
caller.msg("You already know the spell '%s'!" % spell_to_learn)
|
||||
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!
|
||||
Cast a magic spell that you know, provided you have the MP
|
||||
to spend on its casting.
|
||||
|
||||
Notes: This is a quite long command, since it has to cope with all
|
||||
the different circumstances in which you may or may not be able
|
||||
to cast a spell. None of the spell's effects are handled by the
|
||||
command - all the command does is verify that the player's input
|
||||
is valid for the spell being cast and then call the spell's
|
||||
function.
|
||||
Usage:
|
||||
cast <spellname> [= <target1>, <target2>, etc...]
|
||||
|
||||
Some spells can be cast on multiple targets, some can be cast
|
||||
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"
|
||||
|
|
@ -829,7 +858,7 @@ class CmdCast(MuxCommand):
|
|||
|
||||
# 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.")
|
||||
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
|
||||
|
|
@ -895,8 +924,16 @@ class CmdCast(MuxCommand):
|
|||
caller.msg("You can't specify the same target more than once!")
|
||||
return
|
||||
|
||||
# Finally, we can cast the spell itself
|
||||
# Finally, we can cast the spell itself. Note that MP is not deducted here!
|
||||
spelldata["spellfunc"](caller, spell_to_cast, spell_targets, spelldata["cost"], **kwargs)
|
||||
"""
|
||||
Note: This is a quite long command, since it has to cope with all
|
||||
the different circumstances in which you may or may not be able
|
||||
to cast a spell. None of the spell's effects are handled by the
|
||||
command - all the command does is verify that the player's input
|
||||
is valid for the spell being cast and then call the spell's
|
||||
function.
|
||||
"""
|
||||
|
||||
|
||||
class CmdRest(Command):
|
||||
|
|
@ -973,12 +1010,32 @@ class BattleCmdSet(default_cmds.CharacterCmdSet):
|
|||
self.add(CmdCast())
|
||||
|
||||
"""
|
||||
----------------------------------------------------------------------------
|
||||
SPELL FUNCTIONS START HERE
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
These are the functions that are called by the 'cast' command to perform the
|
||||
effects of various spells. Which spells execute which functions and what
|
||||
parameters are passed to them are specified at the bottom of the module, in
|
||||
the 'SPELLS' dictionary.
|
||||
|
||||
All of these functions take the same arguments:
|
||||
caster (obj): Character casting the spell
|
||||
spell_name (str): Name of the spell being cast
|
||||
targets (list): List of objects targeted by the spell
|
||||
cost (int): MP cost of casting the spell
|
||||
|
||||
These functions also all accept **kwargs, and how these are used is specified
|
||||
in the docstring for each function.
|
||||
"""
|
||||
|
||||
def spell_cure_wounds(caster, spell_name, targets, cost, **kwargs):
|
||||
def spell_healing(caster, spell_name, targets, cost, **kwargs):
|
||||
"""
|
||||
Spell that restores HP to a target.
|
||||
Spell that restores HP to a target or targets.
|
||||
|
||||
kwargs:
|
||||
healing_range (tuple): Minimum and maximum amount healed to
|
||||
each target. (20, 40) by default.
|
||||
"""
|
||||
spell_msg = "%s casts %s!" % (caster, spell_name)
|
||||
|
||||
|
|
@ -1007,6 +1064,20 @@ def spell_cure_wounds(caster, spell_name, targets, cost, **kwargs):
|
|||
def spell_attack(caster, spell_name, targets, cost, **kwargs):
|
||||
"""
|
||||
Spell that deals damage in combat. Similar to resolve_attack.
|
||||
|
||||
kwargs:
|
||||
attack_name (tuple): Single and plural describing the sort of
|
||||
attack or projectile that strikes each enemy.
|
||||
damage_range (tuple): Minimum and maximum damage dealt by the
|
||||
spell. (10, 20) by default.
|
||||
accuracy (int): Modifier to the spell's attack roll, determining
|
||||
an increased or decreased chance to hit. 0 by default.
|
||||
attack_count (int): How many individual attacks are made as part
|
||||
of the spell. If the number of attacks exceeds the number of
|
||||
targets, the first target specified will be attacked more
|
||||
than once. Just 1 by default - if the attack_count is less
|
||||
than the number targets given, each target will only be
|
||||
attacked once.
|
||||
"""
|
||||
spell_msg = "%s casts %s!" % (caster, spell_name)
|
||||
|
||||
|
|
@ -1030,7 +1101,6 @@ def spell_attack(caster, spell_name, targets, cost, **kwargs):
|
|||
attack_count = kwargs["attack_count"]
|
||||
|
||||
to_attack = []
|
||||
print targets
|
||||
# If there are more attacks than targets given, attack first target multiple times
|
||||
if len(targets) < attack_count:
|
||||
to_attack = to_attack + targets
|
||||
|
|
@ -1038,10 +1108,8 @@ def spell_attack(caster, spell_name, targets, cost, **kwargs):
|
|||
for n in range(extra_attacks):
|
||||
to_attack.insert(0, targets[0])
|
||||
else:
|
||||
to_attack = targets
|
||||
to_attack = to_attack + targets
|
||||
|
||||
print to_attack
|
||||
print targets
|
||||
|
||||
# Set up dictionaries to track number of hits and total damage
|
||||
total_hits = {}
|
||||
|
|
@ -1058,10 +1126,6 @@ def spell_attack(caster, spell_name, targets, cost, **kwargs):
|
|||
spell_dmg = randint(min_damage, max_damage) # Get spell damage
|
||||
total_hits[fighter] += 1
|
||||
total_damage[fighter] += spell_dmg
|
||||
|
||||
print total_hits
|
||||
print total_damage
|
||||
print targets
|
||||
|
||||
for fighter in targets:
|
||||
# Construct combat message
|
||||
|
|
@ -1087,11 +1151,54 @@ def spell_attack(caster, spell_name, targets, cost, **kwargs):
|
|||
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.
|
||||
|
||||
kwargs:
|
||||
obj_key (str): Key of the created object.
|
||||
obj_desc (str): Desc of the created object.
|
||||
obj_typeclass (str): Typeclass path of the object.
|
||||
|
||||
If you want to make more use of this particular spell funciton,
|
||||
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"]
|
||||
if "obj_desc" in 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
|
||||
|
||||
caster.location.msg_contents("%s casts %s, and %s appears!" % (caster, spell_name, conjured_obj))
|
||||
|
||||
"""
|
||||
----------------------------------------------------------------------------
|
||||
SPELL DEFINITIONS START HERE
|
||||
----------------------------------------------------------------------------
|
||||
In this section, each spell is matched to a function, and given parameters
|
||||
that determine its MP cost, valid type and number of targets, and what
|
||||
function casting the spell executes.
|
||||
|
||||
This data is given as a dictionary of dictionaries - the key of each entry
|
||||
is the spell's name, and the value is a dictionary of various options and
|
||||
parameters, some of which are required and others which are optional.
|
||||
|
||||
Required values for spells:
|
||||
|
||||
cost (int): MP cost of casting the spell
|
||||
cost (int): MP cost of casting the spell
|
||||
target (str): Valid targets for the spell. Can be any of:
|
||||
"none" - No target needed
|
||||
"self" - Self only
|
||||
|
|
@ -1101,8 +1208,8 @@ Required values for spells:
|
|||
"other" - Any object excluding the caster
|
||||
"otherchar" - Any character excluding the caster
|
||||
spellfunc (callable): Function that performs the action of the spell.
|
||||
Must take the following arguments: caster (obj), spell_name (str), targets (list),
|
||||
and cost (int), as well as **kwargs.
|
||||
Must take the following arguments: caster (obj), spell_name (str),
|
||||
targets (list), and cost (int), as well as **kwargs.
|
||||
|
||||
Optional values for spells:
|
||||
|
||||
|
|
@ -1115,14 +1222,21 @@ Any other values specified besides the above will be passed as kwargs to the spe
|
|||
You can use kwargs to effectively re-use the same function for different but similar
|
||||
spells.
|
||||
"""
|
||||
|
||||
|
||||
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_cure_wounds, "target":"anychar", "cost":5},
|
||||
"mass cure wounds":{"spellfunc":spell_cure_wounds, "target":"anychar", "cost":10, "max_targets": 5},
|
||||
"full heal":{"spellfunc":spell_cure_wounds, "target":"anychar", "cost":12, "healing_range":(100, 100)}
|
||||
"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."}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue