sistemati spells
This commit is contained in:
parent
18c59755d4
commit
5ef1b78c42
13 changed files with 865 additions and 126 deletions
|
@ -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"]
|
||||
|
|
|
@ -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
36
commands/unloggedin.py
Normal 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
58
menus/char_manager.py
Normal 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
203
menus/login.py
Normal 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
|
|
@ -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.
|
||||
|
|
|
@ -56,6 +56,7 @@ class Character(DefaultCharacter):
|
|||
}
|
||||
|
||||
self.db.spells = []
|
||||
self.db.recipes = []
|
||||
self.db.current_action = None
|
||||
|
||||
def get_health(self):
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
385
utils/spells.py
Normal 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.
|
||||
|
||||
"""
|
|
@ -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
|
||||
|
|
|
@ -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))
|
Loading…
Reference in a new issue