sistemati spells

This commit is contained in:
Francesco Cappelli 2022-02-17 18:33:26 +01:00
parent 18c59755d4
commit 5ef1b78c42
13 changed files with 865 additions and 126 deletions

View file

@ -21,8 +21,6 @@ from typeclasses.rooms import IndoorRoom
from typeclasses.objects import Item, EquippableItem
from typeclasses.scripts import Script, CmdActionScript
from world import spells
CMD_SEARCH_TIME = 5
@ -574,62 +572,6 @@ class CmdInventory(Command):
self.caller.msg(string)
class CmdCast(Command):
"""
cast a spell.
Usage:
cast <spell> [at <target>]
Casts a spell.
"""
key = "cast"
aliases = ["cs"]
lock = "cmd:false()"
help_category = "General"
arg_regex = r"\s.+|$"
def parse(self):
"""
Handle parsing of:
::
<spell> [at|on <target>]
"""
self.args = args = self.args.strip().lower()
if " at " in args:
spell_name, *rest = args.split(" at ", 1)
elif " on " in args:
spell_name, *rest = args.split(" on ", 1)
else:
spell_name, rest = args, []
target_name = rest[0] if rest else ""
self.spell_name = spell_name.strip()
self.target_name = target_name.strip()
def func(self):
caller = self.caller
if not self.args or not self.spell_name:
caller.msg(self.get_help(caller, self.cmdset))
return
spell_id = self.spell_name.replace(' ', '_')
if self.spell_name not in caller.db.spells:
caller.msg("You cannot cast {}.".format(self.spell_name))
return
if hasattr(spells, "spell_" + spell_id):
spell = getattr(spells, "spell_" + spell_id)
else:
logger.log_err("Cannot find spell {}.".format("spell_" + spell_id))
return
spell(caller, self.target_name)
class CmdTestPy(Command):
key = "testpy"
aliases = ["testpy"]

View file

@ -24,7 +24,6 @@ from commands.command import CmdTestPy
from commands.command import CmdSearch
from commands.command import CmdEquip
from commands.command import CmdInventory
from commands.command import CmdCast
from commands.command import CmdPut
from commands.command import CmdOpenCloseDoor
@ -35,7 +34,10 @@ from commands.builder import CmdZone
from commands.builder import CmdAddToZone
from commands.builder import CmdPopen
from utils.crafting import CmdCraft
from commands.unloggedin import CmdUnconnectedLook
from utils.crafting import CraftingCmdSet
from utils.spells import CastingCmdSet
class CharacterCmdSet(default_cmds.CharacterCmdSet):
@ -64,7 +66,6 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
self.add(CmdSearch())
self.add(CmdEquip())
self.add(CmdInventory())
self.add(CmdCast())
self.add(CmdPut())
self.add(CmdOpenCloseDoor())
@ -75,7 +76,8 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
self.add(CmdAddToZone())
self.add(CmdPopen())
self.add(CmdCraft())
self.add(CraftingCmdSet())
self.add(CastingCmdSet())
class AccountCmdSet(default_cmds.AccountCmdSet):
@ -114,6 +116,7 @@ class UnloggedinCmdSet(default_cmds.UnloggedinCmdSet):
#
# any commands you add below will overload the default ones.
#
self.add(CmdUnconnectedLook())
class SessionCmdSet(default_cmds.SessionCmdSet):

36
commands/unloggedin.py Normal file
View file

@ -0,0 +1,36 @@
from evennia import Command
from evennia.utils.evmenu import EvMenu
from evennia.commands.cmdhandler import CMD_LOGINSTART
from menus.login import plain_node_formatter
class CmdUnconnectedLook(Command):
"""
look when in unlogged-in state
Usage:
look
This is an unconnected version of the look command for simplicity.
This is called by the server and kicks everything in gear.
All it does is display the connect screen.
"""
key = CMD_LOGINSTART
aliases = ["look", "l"]
locks = "cmd:all()"
def func(self):
"""Show the connect screen."""
EvMenu(
self.caller,
"menus.login",
startnode="node_enter_username",
auto_look=False,
auto_quit=False,
cmd_on_exit=None,
node_formatter=plain_node_formatter,
)

58
menus/char_manager.py Normal file
View file

@ -0,0 +1,58 @@
_ACCOUNT_HELP = (
"Enter the name you used to log into the game before, " "or a new account-name if you are new."
)
_PASSWORD_HELP = (
"Password should be a minimum of 8 characters (preferably longer) and "
"can contain a mix of letters, spaces, digits and @/./+/-/_/'/, only."
)
def _show_help(caller, raw_string, **kwargs):
"""Echo help message, then re-run node that triggered it"""
help_entry = kwargs["help_entry"]
caller.msg(help_entry)
return None # re-run calling node
def node_enter_char_management(caller, raw_string, **kwargs):
account = caller.ndb._menutree.account
if not account:
raise Exception("No account found!")
# TODO
# text = "{}".format(w_first_login)
#
# options = (
# {"key": "", "goto": "node_enter_char_management"},
# {"key": ("help", "h"), "goto": (_show_help, {"help_entry": _ACCOUNT_HELP, **kwargs})},
# {"key": "_default", "goto": "node_enter_char_management"},
# )
#
# return text, options
# TODO
character = account.db._last_puppet
character.db.health = 10
character.db.mana = 10
character.db.spells = []
character.db.spells.append('light')
character.db.recipes = []
character.db.recipes.append('vial of blood')
character.db.recipes.append('torch')
caller.msg("|gLogging in ...|n")
caller.sessionhandler.login(caller, account)
return "", {}
def plain_node_formatter(nodetext, optionstext, caller=None):
"""Do not display the options, only the text.
This function is used by EvMenu to format the text of nodes. The menu login
is just a series of prompts, so we disable all automatic display decoration
and let the nodes handle everything on their own.
"""
return nodetext

203
menus/login.py Normal file
View file

@ -0,0 +1,203 @@
from django.conf import settings
from evennia.utils import utils
from evennia.utils.evmenu import EvMenu
CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
_GUEST_ENABLED = settings.GUEST_ENABLED
_ACCOUNT = utils.class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
_GUEST = utils.class_from_module(settings.BASE_GUEST_TYPECLASS)
_ACCOUNT_HELP = (
"Enter the name you used to log into the game before, " "or a new account-name if you are new."
)
_PASSWORD_HELP = (
"Password should be a minimum of 8 characters (preferably longer) and "
"can contain a mix of letters, spaces, digits and @/./+/-/_/'/, only."
)
def _show_help(caller, raw_string, **kwargs):
"""Echo help message, then re-run node that triggered it"""
help_entry = kwargs["help_entry"]
caller.msg(help_entry)
return None # re-run calling node
def node_enter_username(caller, raw_text, **kwargs):
"""
Start node of menu
Start login by displaying the connection screen and ask for a user name.
"""
def _check_input(caller, username, **kwargs):
"""
'Goto-callable', set up to be called from the _default option below.
Called when user enters a username string. Check if this username already exists and set the flag
'new_user' if not. Will also directly login if the username is 'guest'
and GUEST_ENABLED is True.
The return from this goto-callable determines which node we go to next
and what kwarg it will be called with.
"""
username = username.rstrip("\n")
if username == "guest" and _GUEST_ENABLED:
# do an immediate guest login
session = caller
address = session.address
account, errors = _GUEST.authenticate(ip=address)
if account:
return "node_quit_or_login", {"login": True, "account": account}
else:
session.msg("|R{}|n".format("\n".join(errors)))
return None # re-run the username node
try:
_ACCOUNT.objects.get(username__iexact=username)
except _ACCOUNT.DoesNotExist:
new_user = True
else:
new_user = False
# pass username/new_user into next node as kwargs
return "node_enter_password", {"new_user": new_user, "username": username}
"""Show the connect screen."""
callables = utils.callables_from_module(CONNECTION_SCREEN_MODULE)
if "connection_screen" in callables:
connection_screen = callables["connection_screen"]()
else:
connection_screen = utils.random_string_from_module(CONNECTION_SCREEN_MODULE)
if not connection_screen:
connection_screen = "No connection screen found. Please contact an admin."
if _GUEST_ENABLED:
text = "Enter a new or existing user name to login (write 'guest' for a guest login):"
else:
text = "Enter a new or existing user name to login:"
text = "{}\n\n{}".format(connection_screen, text)
options = (
{"key": "", "goto": "node_enter_username"},
{"key": ("quit", "q"), "goto": "node_quit_or_login"},
{"key": ("help", "h"), "goto": (_show_help, {"help_entry": _ACCOUNT_HELP, **kwargs})},
{"key": "_default", "goto": _check_input},
)
return text, options
def node_enter_password(caller, raw_string, **kwargs):
"""
Handle password input.
"""
def _check_input(caller, password, **kwargs):
"""
'Goto-callable', set up to be called from the _default option below.
Called when user enters a password string. Check username + password
viability. If it passes, the account will have been created and login
will be initiated.
The return from this goto-callable determines which node we go to next
and what kwarg it will be called with.
"""
# these flags were set by the goto-callable
username = kwargs["username"]
new_user = kwargs["new_user"]
password = password.rstrip("\n")
session = caller
address = session.address
if new_user:
# create a new account
account, errors = _ACCOUNT.create(
username=username, password=password, ip=address, session=session
)
else:
# check password against existing account
account, errors = _ACCOUNT.authenticate(
username=username, password=password, ip=address, session=session
)
if account:
if new_user:
session.msg("|gA new account |c{}|g was created. Welcome!|n".format(username))
# pass login info to login node
return "node_quit_or_login", {"login": True, "account": account}
else:
# restart due to errors
session.msg("|R{}".format("\n".join(errors)))
kwargs["retry_password"] = True
return "node_enter_password", kwargs
def _restart_login(caller, *args, **kwargs):
caller.msg("|yCancelled login.|n")
return "node_enter_username"
username = kwargs["username"]
if kwargs["new_user"]:
if kwargs.get("retry_password"):
# Attempting to fix password
text = "Enter a new password:"
else:
text = "Creating a new account |c{}|n. " "Enter a password (empty to abort):".format(username)
else:
text = "Enter the password for account |c{}|n (empty to abort):".format(username)
options = (
{"key": "", "goto": _restart_login},
{"key": ("quit", "q"), "goto": "node_quit_or_login"},
{"key": ("help", "h"), "goto": (_show_help, {"help_entry": _PASSWORD_HELP, **kwargs})},
{"key": "_default", "goto": (_check_input, kwargs)},
)
return text, options
def node_quit_or_login(caller, raw_text, **kwargs):
"""
Exit menu, either by disconnecting or logging in.
"""
session = caller
if kwargs.get("login"):
account = kwargs.get("account")
# TODO
# session.msg("|gLogging in ...|n")
# session.sessionhandler.login(session, account)
# # go OOC
# account.unpuppet_object(session)
EvMenu(
caller,
"menus.char_manager",
startnode="node_enter_char_management",
auto_look=False,
auto_quit=False,
cmd_on_exit=None,
node_formatter=plain_node_formatter,
account=account,
)
else:
session.sessionhandler.disconnect(session, "Goodbye! Logging off.")
return "", {}
def plain_node_formatter(nodetext, optionstext, caller=None):
"""Do not display the options, only the text.
This function is used by EvMenu to format the text of nodes. The menu login
is just a series of prompts, so we disable all automatic display decoration
and let the nodes handle everything on their own.
"""
return nodetext

View file

@ -41,6 +41,7 @@ GLOBAL_SCRIPTS = {
PROTOTYPE_MODULES += ["world.tutorial_prototypes"]
CRAFT_RECIPE_MODULES = ['world.recipes_base']
SPELL_MODULES = ['world.spells']
######################################################################
# Settings given in secret_settings.py override those in this file.

View file

@ -56,6 +56,7 @@ class Character(DefaultCharacter):
}
self.db.spells = []
self.db.recipes = []
self.db.current_action = None
def get_health(self):

View file

@ -39,6 +39,13 @@ class Exit(DefaultExit):
defined, in which case that will simply be echoed.
"""
def at_init(self):
# check if this exit has directional alias
self.ndb.directional_alias = None
directional_aliases = [ele for ele in ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'] if (ele in self.aliases.all())]
if directional_aliases:
self.ndb.directional_alias = directional_aliases[0]
def at_traverse(self, traversing_object, target_location, **kwargs):
if has_effect(traversing_object, "is_busy"):
traversing_object.msg("You are already busy {}.".format(traversing_object.db.current_action.busy_msg()))

View file

@ -33,8 +33,8 @@ class Room(DefaultRoom):
def at_object_creation(self):
self.locks.add("light:false()")
self.db.x = 0
self.db.y = 0
self.db.x = -1
self.db.y = -1
self.db.map_icon = '|w⊡|n'
@ -68,6 +68,7 @@ class IndoorRoom(Room):
"""
Called when room is first recached (such as after a reload)
"""
super().at_init()
self.check_light_state()
def return_appearance(self, looker, **kwargs):
@ -88,7 +89,10 @@ class IndoorRoom(Room):
for con in visible:
key = con.get_display_name(looker)
if con.destination:
exits.append(key)
if con.ndb.directional_alias:
exits.append("|m{}|n|w)|n{}".format(con.ndb.directional_alias, key))
else:
exits.append(key)
elif con.has_account:
users.append("|c%s|n" % key)
elif con.db.feature_desc:
@ -326,3 +330,47 @@ class Zone(DefaultRoom):
for ex in room.exits: # iterate exits in the room
if not self.ndb.sp_graph.is_edge(room, ex.destination):
self.ndb.sp_graph.add_edge(room, ex.destination, 1, ex.name)
# if destination is already inserted in zone we can proceed, otherwise
# aliases are created later.
if ex.destination.db.x != - 1 and ex.destination.db.y != - 1:
self._update_exit_alias(ex)
if ex.db.return_exit:
self._update_exit_alias(ex.db.return_exit)
def _update_exit_alias(self, exit_obj):
xl, yl = exit_obj.location.db.x, exit_obj.location.db.y
xd, yd = exit_obj.destination.db.x, exit_obj.destination.db.y
# if aliases aren't already created, create them.
if not [ele for ele in ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'] if(ele in exit_obj.aliases.all())]:
aliases = []
dx = xd - xl
dy = yd - yl
if dx == 0 and dy == -1:
aliases.extend(["n", "north"])
exit_obj.ndb.directional_alias = "n"
elif dx == 0 and dy == 1:
aliases.extend(["s", "south"])
exit_obj.ndb.directional_alias = "s"
elif dx == -1 and dy == 0:
aliases.extend(["w", "west"])
exit_obj.ndb.directional_alias = "w"
elif dx == 1 and dy == 0:
aliases.extend(["e", "east"])
exit_obj.ndb.directional_alias = "e"
elif dx == 1 and dy == -1:
aliases.extend(["ne", "northeast"])
exit_obj.ndb.directional_alias = "ne"
elif dx == -1 and dy == -1:
aliases.extend(["nw", "northwest"])
exit_obj.ndb.directional_alias = "nw"
elif dx == 1 and dy == 1:
aliases.extend(["se", "southeast"])
exit_obj.ndb.directional_alias = "se"
elif dx == -1 and dy == 1:
aliases.extend(["sw", "southwest"])
exit_obj.ndb.directional_alias = "sw"
exit_obj.aliases.add(aliases)

View file

@ -121,9 +121,10 @@ a full example of the components for creating a sword from base components.
from copy import copy
from evennia import logger
from evennia.utils.utils import callables_from_module, inherits_from, make_iter, iter_to_string
from evennia.utils import evtable
from evennia.utils.utils import callables_from_module, inherits_from, make_iter, iter_to_string, list_to_string
from evennia.commands.cmdset import CmdSet
from evennia.commands.command import Command
from commands.command import Command
from evennia.prototypes.spawner import spawn
from evennia.utils.create import create_object, create_script
@ -677,7 +678,6 @@ class CraftingRecipe(CraftingRecipeBase):
tools = []
for itag, tag in enumerate(cls.tool_tags):
tools.append(
create_object(
key=tool_key or (cls.tool_names[itag] if cls.tool_names else tag.capitalize()),
@ -690,7 +690,7 @@ class CraftingRecipe(CraftingRecipeBase):
consumables.append(
create_object(
key=cons_key
or (cls.consumable_names[itag] if cls.consumable_names else tag.capitalize()),
or (cls.consumable_names[itag] if cls.consumable_names else tag.capitalize()),
tags=[(tag, cls.consumable_tag_category), *cons_tags],
**consumable_kwargs,
)
@ -717,14 +717,14 @@ class CraftingRecipe(CraftingRecipeBase):
"""
def _check_completeness(
tagmap,
taglist,
namelist,
exact_match,
exact_order,
error_missing_message,
error_order_message,
error_excess_message,
tagmap,
taglist,
namelist,
exact_match,
exact_order,
error_missing_message,
error_order_message,
error_excess_message,
):
"""Compare tagmap (inputs) to taglist (required)"""
valids = []
@ -771,17 +771,17 @@ class CraftingRecipe(CraftingRecipeBase):
obj: obj.tags.get(category=self.tool_tag_category, return_list=True)
for obj in self.inputs
if obj
and hasattr(obj, "tags")
and inherits_from(obj, "evennia.objects.models.ObjectDB")
and hasattr(obj, "tags")
and inherits_from(obj, "evennia.objects.models.ObjectDB")
}
tool_map = {obj: tags for obj, tags in tool_map.items() if tags}
consumable_map = {
obj: obj.tags.get(category=self.consumable_tag_category, return_list=True)
for obj in self.inputs
if obj
and hasattr(obj, "tags")
and obj not in tool_map
and inherits_from(obj, "evennia.objects.models.ObjectDB")
and hasattr(obj, "tags")
and obj not in tool_map
and inherits_from(obj, "evennia.objects.models.ObjectDB")
}
consumable_map = {obj: tags for obj, tags in consumable_map.items() if tags}
@ -1008,6 +1008,38 @@ class CraftingCmdSet(CmdSet):
def at_cmdset_creation(self):
self.add(CmdCraft())
self.add(CmdRecipes())
class CmdRecipes(Command):
"""
List known recipes.
Usage:
recipes
"""
key = "recipes"
locks = "cmd:all()"
help_category = "General"
arg_regex = r"\s|$"
def func(self):
# delayed loading/caching of recipes
_load_recipes()
caller = self.caller
table = evtable.EvTable("|wName|n", "|wMaterials", "|wTools|n",
border_left_char="|y|||n", border_right_char="|y|||n",
border_top_char="|y-|n", border_bottom_char="|y-|n",
corner_char="|y+|n")
for recipe in _RECIPE_CLASSES.values():
if (recipe.name in caller.db.recipes) or caller.is_superuser:
table.add_row("|W{}".format(recipe.name), "|M{}".format(list_to_string(recipe.tool_tags)), "|G{}".format(list_to_string(recipe.consumable_tags)))
caller.msg(table)
class CmdCraft(Command):
@ -1095,9 +1127,9 @@ class CmdCraft(Command):
if not obj:
return
if (
not inherits_from(obj, "evennia.objects.models.ObjectDB")
or obj.sessions.all()
or not obj.access(caller, "craft", default=True)
not inherits_from(obj, "evennia.objects.models.ObjectDB")
or obj.sessions.all()
or not obj.access(caller, "craft", default=True)
):
# We don't allow to include puppeted objects nor those with the
# 'negative' permission 'nocraft'.
@ -1139,7 +1171,9 @@ class CmdCraft(Command):
toggle_effect(caller, "is_busy")
caller.msg("You start crafting {} {}.".format(indefinite_article(recipe_cls.name), recipe_cls.name))
action_script = create_script("utils.crafting.CmdCraftComplete", obj=caller, interval=recipe_cls.crafting_time, attributes=[("recipe", recipe_cls.name), ("tools_and_ingredients", tools_and_ingredients)])
action_script = create_script("utils.crafting.CmdCraftComplete", obj=caller, interval=recipe_cls.crafting_time,
attributes=[("recipe", recipe_cls.name),
("tools_and_ingredients", tools_and_ingredients)])
caller.db.current_action = action_script
@ -1171,4 +1205,3 @@ class CmdCraftComplete(CmdActionScript):
def busy_msg(self):
return "crafting {} {}".format(indefinite_article(self.db.recipe), self.db.recipe)

385
utils/spells.py Normal file
View file

@ -0,0 +1,385 @@
from copy import copy
from evennia import logger
from evennia.utils import evtable
from evennia.utils.utils import callables_from_module, inherits_from, make_iter, iter_to_string, list_to_string
from evennia.commands.cmdset import CmdSet
from commands.command import Command
from typeclasses.scripts import CmdActionScript
from utils.utils import toggle_effect, has_effect
from evennia.utils.create import create_object, create_script
_SPELL_CLASSES = {}
def _load_spells():
"""
Delayed loading of recipe classes. This parses
`settings.SPELL_MODULES`.
"""
from django.conf import settings
global _SPELL_CLASSES
if not _SPELL_CLASSES:
paths = []
if hasattr(settings, "SPELL_MODULES"):
paths += make_iter(settings.SPELL_MODULES)
for path in paths:
for cls in callables_from_module(path).values():
if inherits_from(cls, Spell):
_SPELL_CLASSES[cls.name] = cls
class Spell:
name = "spell base"
casting_time = 1
duration = 1
target_type = None
# general cast-failure msg to show after other error-messages.
failure_message = ""
# show after a successful cast
success_message = "You cast {}.".format(name)
def __init__(self, caster, **kwargs):
self.caster = caster
self.target_obj = None
self.cast_kwargs = kwargs
self.allow_cast = True
def msg(self, message, **kwargs):
"""
Send message to caster. This is a central point to override if wanting
to change casting return style in some way.
Args:
message(str): The message to send.
**kwargs: Any optional properties relevant to this send.
"""
self.caster.msg(message, oob=({"type": "casting"}))
def pre_cast(self, **kwargs):
"""
Hook to override.
This is called just before casting operation.
Args:
**kwargs: Any optional extra kwargs passed during initialization of
the spell class.
Raises:
CastingError: If validation fails. At this point the caster
is expected to have been informed of the problem already.
"""
caster = self.caster
self.target_obj = None
if self.target_type:
if not self.cast_kwargs.get('target_name', None):
self.msg("You need a target to cast {} on.".format(self.name))
raise CastingError("You need a target to cast {} on.".format(self.name))
target_name = self.cast_kwargs.get('target_name')
self.target_obj = caster.search(target_name, location=[caster, caster.location])
if not self.target_obj:
self.msg("{} is not a valid target for {}.".format(target_name, self.name))
raise CastingError("{} is not a valid target for {}.".format(target_name, self.name))
# TODO
# gestire i vari tipi di target_type
def do_cast(self, **kwargs):
"""
Hook to override. This will not be called if validation in `pre_cast`
fails.
This performs the actual casting. At this point the inputs are
expected to have been verified already.
Returns:
any: The result of casting if any.
Notes:
This method should use `self.msg` to inform the user about the
specific reason of failure immediately.
"""
return True
def post_cast(self, cast_result, **kwargs):
"""
Hook to override.
This is called just after casting has finished.
"""
if cast_result:
self.msg(self.success_message)
elif self.failure_message:
self.msg(self.failure_message)
return cast_result
def cast(self, raise_exception=False, **kwargs):
"""
Main casting call method. Call this to cast a spell and make
sure all hooks run correctly.
Args:
raise_exception (bool): If casting would go wrong, raise
exception instead.
**kwargs (any): Any other parameters that is relevant
for this particular cast operation. This will temporarily
override same-named kwargs given at the creation of this recipe
and be passed into all the casting hooks.
Returns:
any: The result of the cast, or `None`.
Raises:
CastingError: If casting validation failed and
`raise_exception` is True.
"""
cast_result = None
if self.allow_cast:
# override/extend cast_kwargs from initialization.
cast_kwargs = copy(self.cast_kwargs)
cast_kwargs.update(kwargs)
try:
try:
# this assigns to self.validated_inputs
self.pre_cast(**cast_kwargs)
except CastingError:
if raise_exception:
raise
else:
cast_result = self.do_cast(**cast_kwargs)
finally:
cast_result = self.post_cast(cast_result, **cast_kwargs)
except CastingError:
if raise_exception:
raise
if cast_result is None and raise_exception:
raise CastingError(f"Casting of {self.name} failed.")
return cast_result
def cast(caster, spell_name, raise_exception=False, **kwargs):
# delayed loading/caching of spells
_load_spells()
SpellClass = search_spell(caster, spell_name)
if not SpellClass:
raise KeyError(
f"No spell in settings.SPELL_MODULES has a name matching {spell_name}"
)
spell = SpellClass(caster, **kwargs)
return spell.cast(raise_exception=raise_exception)
def can_cast(caster, spell_name, **kwargs):
"""
Access function.Check if crafter can craft a given recipe from a source recipe module.
Args:
caster (Object): The one doing the crafting.
spell_name (str): The `Spell.name` to use. This uses fuzzy-matching
if the result is unique.
**kwargs: Optional kwargs to pass into the casting.
Returns:
list: Error messages, if any.
"""
# delayed loading/caching of spells
_load_spells()
SpellClass = search_spell(caster, spell_name)
if not SpellClass:
raise KeyError(
f"No spell in settings.SPELL_MODULES has a name matching {spell_name}"
)
spell = SpellClass(caster, **kwargs)
if spell.allow_cast:
# override/extend craft_kwargs from initialization.
cast_kwargs = copy(spell.cast_kwargs)
cast_kwargs.update(kwargs)
try:
spell.pre_cast(**cast_kwargs)
except CastingError:
logger.log_err(CastingError.args)
return False
else:
return True
return False
def search_spell(caster, spell_name):
# delayed loading/caching of spells
_load_spells()
spell_class = _SPELL_CLASSES.get(spell_name, None)
if not spell_class:
# try a startswith fuzzy match
matches = [key for key in _SPELL_CLASSES if key.startswith(spell_name)]
if not matches:
# try in-match
matches = [key for key in _SPELL_CLASSES if spell_name in key]
if len(matches) == 1:
spell_class = _SPELL_CLASSES.get(matches[0], None)
return spell_class
class CastingCmdSet(CmdSet):
"""
Store Casting command.
"""
key = "Casting cmdset"
def at_cmdset_creation(self):
self.add(CmdCast())
self.add(CmdSpells())
class CmdSpells(Command):
"""
List known spells.
Usage:
spells
"""
key = "spells"
locks = "cmd:all()"
help_category = "General"
arg_regex = r"\s|$"
def func(self):
_load_spells()
caller = self.caller
table = evtable.EvTable("|wName|n", "|wTarget", "|wCasting time", "|wDuration",
border_left_char="|y|||n", border_right_char="|y|||n",
border_top_char="|y-|n", border_bottom_char="|y-|n",
corner_char="|y+|n")
for spell in _SPELL_CLASSES.values():
if (spell.name in caller.db.spells) or caller.is_superuser:
table.add_row("|W{}".format(spell.name), "|M{}".format(spell.target_type), "|G{}".format(spell.casting_time), "|M{}".format(spell.duration))
caller.msg(table)
class CmdCast(Command):
"""
cast a spell.
Usage:
cast <spell> [at <target>]
Casts a spell.
"""
key = "cast"
aliases = ["cs"]
lock = "cmd:false()"
help_category = "General"
arg_regex = r"\s.+|$"
def parse(self):
"""
Handle parsing of:
::
<spell> [at|on <target>]
"""
self.args = args = self.args.strip().lower()
if " at " in args:
spell_name, *rest = args.split(" at ", 1)
elif " on " in args:
spell_name, *rest = args.split(" on ", 1)
else:
spell_name, rest = args, []
target_name = rest[0] if rest else ""
self.spell_name = spell_name.strip()
self.target_name = target_name.strip()
def func(self):
caller = self.caller
if not self.args or not self.spell_name:
caller.msg(self.get_help(caller, self.cmdset))
return
if self.spell_name not in caller.db.spells:
caller.msg("You don't know how to cast {}.".format(self.spell_name))
return
if has_effect(caller, "is_busy"):
caller.msg("You are already busy {}.".format(caller.db.current_action.busy_msg()))
return
spell_cls = search_spell(caller, self.spell_name)
if not spell_cls:
caller.msg("You don't know how to cast {}.".format(self.spell_name))
return
if not can_cast(caller, spell_cls.name, target_name=self.target_name):
return
toggle_effect(caller, "is_busy")
caller.msg("You start casting {}.".format(spell_cls.name))
action_script = create_script("utils.spells.CastCompleteScript", obj=caller, interval=spell_cls.casting_time,
attributes=[("spell", spell_cls.name),
("target_name", self.target_name)])
caller.db.current_action = action_script
class CastCompleteScript(CmdActionScript):
def at_script_creation(self):
super().at_script_creation()
self.key = "cmd_cast_complete"
self.desc = ""
self.db.spell = ""
self.db.target_name = ""
def at_repeat(self):
caller = self.obj
if has_effect(caller, "is_busy"):
toggle_effect(caller, "is_busy")
# perform casting (the recipe handles all returns to caller).
result = cast(caller, self.db.spell, target_name=self.db.target_name)
if result and not isinstance(result, bool):
for obj in result:
if inherits_from(obj, "typeclasses.objects.Feature"):
obj.location = caller.location
else:
obj.location = caller
def busy_msg(self):
return "casting {}.".format(self.db.spell)
class CastingError(RuntimeError):
"""
Casting error.
"""

View file

@ -1,26 +1,30 @@
from evennia.prototypes import spawner
def has_tag(obj, key, category):
return obj.tags.get(key=key, category=category) != None;
return obj.tags.get(key=key, category=category) is not None;
def fmt_light(message):
return "|r\u2600|y {} |r\u2600|n".format(message)
def fmt_dark(message):
return "|w\u2600|=h {} |w\u2600|n".format(message)
def toggle_effect(obj, effect):
if has_tag(obj, effect, "effect"):
obj.tags.remove(effect, category="effect")
else:
obj.tags.add(effect, category="effect")
def has_effect(obj, effect):
return has_tag(obj, effect, "effect")
def has_effect_in(obj, effects):
return any(True for effect in effects if has_tag(obj, effect, "effect"))
def indefinite_article(name):
"""Return the right english indefinite article for given name."""
the_vowels = ["a","e","i","o","u"]
@ -30,4 +34,4 @@ def indefinite_article(name):
else:
value = "a"
return value
return value

View file

@ -1,45 +1,63 @@
from evennia import utils, create_script, logger
from evennia.utils import inherits_from
from evennia import create_script
from typeclasses import effects
from typeclasses.mobs import Mob
from utils.utils import has_effect
def spell_light(caller, target, **kwargs):
if not target:
target_obj = caller
else:
target_obj = caller.search(target, location=[caller, caller.location])
if not target_obj:
return
if has_effect(target_obj, "emit_magic_light"):
caller.msg("{} already has a magical light on itself.".format(target_obj.name))
return
light_script = create_script(effects.EffectMagicalLight, obj=target_obj)
caller.msg("You cast |wlight|n on {}.".format(target_obj.name))
from utils.spells import Spell
def spell_charm(caller, target, **kwargs):
if not target:
caller.msg("You need someone to place your charm on.")
return
class LightSpell(Spell):
"""Make the target emit magical light"""
name = "light" # name to refer to this recipe as
casting_time = 1
duration = 60
target_type = "SINGLE"
success_message = "You cast |wlight|n on {}."
target_obj = caller.search(target, location=[caller.location])
def do_cast(self, **kwargs):
target_obj = self.target_obj
if has_effect(target_obj, "emit_magic_light"):
self.msg("{} already has a magical light on itself.".format(target_obj.name))
return False
if not target_obj:
return
create_script(effects.EffectMagicalLight, obj=target_obj, interval=self.duration)
self.success_message = self.success_message.format(target_obj.name)
if not inherits_from(target_obj, Mob):
caller.msg("You cannot charm {}".format(target_obj.name))
return
return True
if has_effect(target_obj, "charm"):
caller.msg("{} is already charmed.".format(target_obj.name))
return
# def spell_light(caller, target, **kwargs):
# if not target:
# target_obj = caller
# else:
# target_obj = caller.search(target, location=[caller, caller.location])
#
# if not target_obj:
# return
#
# if has_effect(target_obj, "emit_magic_light"):
# caller.msg("{} already has a magical light on itself.".format(target_obj.name))
# return
#
# light_script = create_script(effects.EffectMagicalLight, obj=target_obj)
# caller.msg("You cast |wlight|n on {}.".format(target_obj.name))
charm_script = create_script(effects.EffectCharm, obj=target_obj, attributes=[("source", caller.dbref)])
caller.msg("You cast |wcharm|n on {}.".format(target_obj.name))
# def spell_charm(caller, target, **kwargs):
# if not target:
# caller.msg("You need someone to place your charm on.")
# return
#
# target_obj = caller.search(target, location=[caller.location])
#
# if not target_obj:
# return
#
# if not inherits_from(target_obj, Mob):
# caller.msg("You cannot charm {}".format(target_obj.name))
# return
#
# if has_effect(target_obj, "charm"):
# caller.msg("{} is already charmed.".format(target_obj.name))
# return
#
# charm_script = create_script(effects.EffectCharm, obj=target_obj, attributes=[("source", caller.dbref)])
# caller.msg("You cast |wcharm|n on {}.".format(target_obj.name))