mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Add magic-crafting example to crafting readme. Resolve #2554
This commit is contained in:
parent
798fb7d92d
commit
fd2c0d9fb7
2 changed files with 244 additions and 9 deletions
|
|
@ -480,7 +480,7 @@ class CraftingRecipe(CraftingRecipeBase):
|
|||
# there should be multiple entries in this list.
|
||||
tool_tags = []
|
||||
# human-readable names for the tools. This will be used for informative messages
|
||||
# or when usage fails. If empty
|
||||
# or when usage fails. If empty, use tag-names.
|
||||
tool_names = []
|
||||
# if we must have exactly the right tools, no more
|
||||
exact_tools = True
|
||||
|
|
@ -628,20 +628,23 @@ class CraftingRecipe(CraftingRecipeBase):
|
|||
return message.format(**mapping)
|
||||
|
||||
@classmethod
|
||||
def seed(cls, tool_kwargs=None, consumable_kwargs=None):
|
||||
def seed(cls, tool_kwargs=None, consumable_kwargs=None, location=None):
|
||||
"""
|
||||
This is a helper class-method for easy testing and application of this
|
||||
recipe. When called, it will create simple dummy ingredients with names
|
||||
and tags needed by this recipe.
|
||||
|
||||
Args:
|
||||
tool_kwargs (dict, optional): Will be passed as `**tool_kwargs` into the `create_object`
|
||||
call for each tool. If not given, the matching
|
||||
`tool_name` or `tool_tag` will be used for key.
|
||||
consumable_kwargs (dict, optional): This will be passed as
|
||||
`**consumable_kwargs` into the `create_object` call for each consumable.
|
||||
If not given, matching `consumable_name` or `consumable_tag`
|
||||
will be used for key.
|
||||
tool_kwargs (dict, optional): Will be passed as `**tool_kwargs` into the `create_object`
|
||||
call for each tool. If not given, the matching
|
||||
`tool_name` or `tool_tag` will be used for key.
|
||||
location (Object, optional): If given, the created items will be created in this
|
||||
location. This is a shortcut for adding {"location": <obj>} to both the
|
||||
consumable/tool kwargs (and will *override* any such setting in those kwargs).
|
||||
|
||||
Returns:
|
||||
tuple: A tuple `(tools, consumables)` with newly created dummy
|
||||
|
|
@ -649,8 +652,7 @@ class CraftingRecipe(CraftingRecipeBase):
|
|||
|
||||
Example:
|
||||
::
|
||||
|
||||
tools, consumables = SwordRecipe.seed()
|
||||
tools, consumables = SwordRecipe.seed(location=caller)
|
||||
recipe = SwordRecipe(caller, *(tools + consumables))
|
||||
result = recipe.craft()
|
||||
|
||||
|
|
@ -663,6 +665,11 @@ class CraftingRecipe(CraftingRecipeBase):
|
|||
tool_kwargs = {}
|
||||
if not consumable_kwargs:
|
||||
consumable_kwargs = {}
|
||||
|
||||
if location:
|
||||
tool_kwargs['location'] = location
|
||||
consumable_kwargs['location'] = location
|
||||
|
||||
tool_key = tool_kwargs.pop("key", None)
|
||||
cons_key = consumable_kwargs.pop("key", None)
|
||||
tool_tags = tool_kwargs.pop("tags", [])
|
||||
|
|
|
|||
|
|
@ -41,13 +41,41 @@ around with them.
|
|||
sword = sword blade + sword guard + sword pommel
|
||||
+ sword handle + leather + knife[T] + hammer[T] + furnace[T]
|
||||
|
||||
|
||||
## Recipes used for spell casting
|
||||
|
||||
This is a simple example modifying the base Recipe to use as a way
|
||||
to describe magical spells instead. It combines tools with
|
||||
a skill (an attribute on the caster) in order to produce a magical effect.
|
||||
|
||||
The example `CmdCast` command can be added to the CharacterCmdset in
|
||||
`mygame/commands/default_cmdsets` to test it out. The 'effects' are
|
||||
just mocked for the example.
|
||||
|
||||
::
|
||||
# base tools (assumed to already exist)
|
||||
|
||||
spellbook[T], wand[T]
|
||||
|
||||
# skill (stored as Attribute on caster)
|
||||
|
||||
firemagic skill level3+
|
||||
|
||||
# recipe for fireball
|
||||
|
||||
fireball = spellbook[T] + wand[T] + [firemagic skill lvl3+]
|
||||
|
||||
----
|
||||
|
||||
"""
|
||||
|
||||
from random import random
|
||||
from .crafting import CraftingRecipe
|
||||
from random import random, randint
|
||||
from evennia.commands.command import Command, InterruptCommand
|
||||
from .crafting import craft, CraftingRecipe, CraftingValidationError
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Sword recipe
|
||||
#------------------------------------------------------------
|
||||
|
||||
class PigIronRecipe(CraftingRecipe):
|
||||
"""
|
||||
|
|
@ -300,3 +328,203 @@ class SwordRecipe(_SwordSmithingBaseRecipe):
|
|||
]
|
||||
# this requires more precision
|
||||
exact_consumable_order = True
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Recipes for spell casting
|
||||
#------------------------------------------------------------
|
||||
|
||||
|
||||
class _MagicRecipe(CraftingRecipe):
|
||||
"""
|
||||
A base 'recipe' to represent magical spells.
|
||||
|
||||
We *could* treat this just like the sword above - by combining the wand and spellbook to make a
|
||||
fireball object that the user can then throw with another command. For this example we instead
|
||||
generate 'magical effects' as strings+values that we would then supposedly inject into a
|
||||
combat system or other resolution system.
|
||||
|
||||
We also assume that the crafter has skills set on itself as plain Attributes.
|
||||
|
||||
"""
|
||||
name = ""
|
||||
# all spells require a spellbook and a wand (so there!)
|
||||
tool_tags = ["spellbook", "wand"]
|
||||
|
||||
error_tool_missing_message = "Cannot cast spells without {missing}."
|
||||
success_message = "You successfully cast the spell!"
|
||||
# custom properties
|
||||
skill_requirement = [] # this should be on the form [(skillname, min_level)]
|
||||
skill_roll = "" # skill to roll for success
|
||||
desired_effects = [] # on the form [(effect, value), ...]
|
||||
failure_effects = [] # ''
|
||||
error_too_low_skill_level = "Your skill {skill_name} is too low to cast {spell}."
|
||||
error_no_skill_roll = "You must have the skill {skill_name} to cast the spell {spell}."
|
||||
|
||||
def pre_craft(self, **kwargs):
|
||||
"""
|
||||
This is where we do input validation. We want to do the
|
||||
normal validation of the tools, but also check for a skill
|
||||
on the crafter. This must set the result on `self.validated_inputs`.
|
||||
We also set the crafter's relevant skill value on `self.skill_roll_value`.
|
||||
|
||||
Args:
|
||||
**kwargs: Any optional extra kwargs passed during initialization of
|
||||
the recipe class.
|
||||
|
||||
Raises:
|
||||
CraftingValidationError: If validation fails. At this point the crafter
|
||||
is expected to have been informed of the problem already.
|
||||
|
||||
"""
|
||||
# this will check so the spellbook and wand are at hand.
|
||||
super().pre_craft(**kwargs)
|
||||
|
||||
# at this point we have the items available, let's also check for the skill. We
|
||||
# assume the crafter has the skill available as an Attribute
|
||||
# on itself.
|
||||
|
||||
crafter = self.crafter
|
||||
for skill_name, min_value in self.skill_requirements:
|
||||
skill_value = crafter.attributes.get(skill_name)
|
||||
|
||||
if skill_value is None or skill_value < min_value:
|
||||
self.msg(self.error_too_low_skill_level.format(skill_name=skill_name,
|
||||
spell=self.name))
|
||||
raise CraftingValidationError
|
||||
|
||||
# get the value of the skill to roll
|
||||
self.skill_roll_value = self.crafter.attributes.get(self.skill_roll)
|
||||
if self.skill_roll_value is None:
|
||||
self.msg(self.error_no_skill_roll.format(skill_name=self.skill_roll,
|
||||
spell=self.name))
|
||||
raise CraftingValidationError
|
||||
|
||||
def do_craft(self, **kwargs):
|
||||
"""
|
||||
'Craft' the magical effect. When we get to this point we already know we have all the
|
||||
prequisite for creating the effect. In this example we will store the effect on the crafter;
|
||||
maybe this enhances the crafter or makes a new attack available to them in combat.
|
||||
|
||||
An alternative to this would of course be to spawn an actual object for the effect, like
|
||||
creating a potion or an actual fireball-object to throw (this depends on how your combat
|
||||
works).
|
||||
|
||||
"""
|
||||
# we do a simple skill check here.
|
||||
if randint(1, 18) <= self.skill_roll_value:
|
||||
# a success!
|
||||
return True, self.desired_effects
|
||||
else:
|
||||
# a failure!
|
||||
return False, self.failure_effects
|
||||
|
||||
def post_craft(self, craft_result, **kwargs):
|
||||
"""
|
||||
Always called at the end of crafting, regardless of successful or not.
|
||||
|
||||
Since we get a custom craft result (True/False, effects) we need to
|
||||
wrap the original post_craft to output the error messages for us
|
||||
correctly.
|
||||
|
||||
"""
|
||||
success = False
|
||||
if craft_result:
|
||||
success, _ = craft_result
|
||||
# default post_craft just checks if craft_result is truthy or not.
|
||||
# we don't care about its return value since we already have craft_result.
|
||||
super().post_craft(success, **kwargs)
|
||||
return craft_result
|
||||
|
||||
|
||||
class FireballRecipe(_MagicRecipe):
|
||||
"""
|
||||
A Fireball is a magical effect that can be thrown at a target to cause damage.
|
||||
|
||||
Note that the magic-effects are just examples, an actual rule system would
|
||||
need to be created to understand what they mean when used.
|
||||
|
||||
"""
|
||||
name = "fireball"
|
||||
skill_requirements = [('firemagic', 10)] # skill 'firemagic' lvl 10 or higher
|
||||
skill_roll = "firemagic"
|
||||
success_message = "A ball of flame appears!"
|
||||
desired_effects = [('target_fire_damage', 25), ('ranged_attack', -2), ('mana_cost', 12)]
|
||||
failure_effects = [('self_fire_damage', 5), ('mana_cost', 5)]
|
||||
|
||||
|
||||
class HealingRecipe(_MagicRecipe):
|
||||
"""
|
||||
Healing magic will restore a certain amount of health to the target over time.
|
||||
|
||||
Note that the magic-effects are just examples, an actual rule system would
|
||||
need to be created to understand what they mean.
|
||||
|
||||
"""
|
||||
name = "heal"
|
||||
skill_requirements = [('bodymagic', 5), ("empathy", 10)]
|
||||
skill_roll = "bodymagic"
|
||||
success_message = "You successfully extend your healing aura."
|
||||
desired_effects = [('healing', 15), ('mana_cost', 5)]
|
||||
failure_effects = []
|
||||
|
||||
|
||||
class CmdCast(Command):
|
||||
"""
|
||||
Cast a magical spell.
|
||||
|
||||
Usage:
|
||||
cast <spell> <target>
|
||||
|
||||
"""
|
||||
key = 'cast'
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
Simple parser, assuming spellname doesn't have spaces.
|
||||
Stores result in self.target and self.spellname.
|
||||
|
||||
"""
|
||||
args = self.args.strip().lower()
|
||||
target = None
|
||||
if ' ' in args:
|
||||
self.spellname, *target = args.split(' ', 1)
|
||||
else:
|
||||
self.spellname = args
|
||||
|
||||
if not self.spellname:
|
||||
self.caller.msg("You must specify a spell name.")
|
||||
raise InterruptCommand
|
||||
|
||||
if target:
|
||||
self.target = self.caller.search(target[0].strip())
|
||||
if not self.target:
|
||||
raise InterruptCommand
|
||||
else:
|
||||
self.target = self.caller
|
||||
|
||||
def func(self):
|
||||
|
||||
# all items carried by the caller could work
|
||||
possible_tools = self.caller.contents
|
||||
|
||||
try:
|
||||
# if this completes without an exception, the caster will have
|
||||
# a new magic_effect set on themselves, ready to use or apply in some way.
|
||||
success, effects = craft(self.caller, self.spellname, *possible_tools,
|
||||
raise_exception=True)
|
||||
except CraftingValidationError:
|
||||
return
|
||||
except KeyError:
|
||||
self.caller.msg(f"You don't know of a spell called '{self.spellname}'")
|
||||
return
|
||||
|
||||
# Applying the magical effect to target would happen below.
|
||||
# self.caller.db.active_spells[self.spellname] holds all the effects
|
||||
# of this particular prepared spell. For a fireball you could perform
|
||||
# an attack roll here and apply damage if you hit. For healing you would heal the target
|
||||
# (which could be yourself) by a number of health points given by the recipe.
|
||||
effect_txt = ", ".join(f"{eff[0]}({eff[1]})" for eff in effects)
|
||||
success_txt = "|gsucceeded|n" if success else "|rfailed|n"
|
||||
self.caller.msg(f"Casting the spell {self.spellname} on {self.target} {success_txt}, "
|
||||
f"causing the following effects: {effect_txt}.")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue