Merged script/scripts commands. Resolve #1502
This commit is contained in:
parent
8497dfa933
commit
1196196ed8
5 changed files with 374 additions and 408 deletions
|
|
@ -80,6 +80,8 @@ Up requirements to Django 3.2+
|
||||||
`Command.retain_instance` flag for reusing the same command instance.
|
`Command.retain_instance` flag for reusing the same command instance.
|
||||||
- The `typeclass` command will now correctly search the correct database-table for the target
|
- The `typeclass` command will now correctly search the correct database-table for the target
|
||||||
obj (avoids mistakenly assigning an AccountDB-typeclass to a Character etc).
|
obj (avoids mistakenly assigning an AccountDB-typeclass to a Character etc).
|
||||||
|
- Merged `script` and `scripts` commands into one, for both managing global- and
|
||||||
|
on-object Scripts. Moved `CmdScripts` and `CmdObjects` to `commands/default/building.py`.
|
||||||
|
|
||||||
### Evennia 0.9.5 (2019-2020)
|
### Evennia 0.9.5 (2019-2020)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@
|
||||||
Building and world design commands
|
Building and world design commands
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
from django.core.paginator import Paginator
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import Q, Min, Max
|
from django.db.models import Q, Min, Max
|
||||||
from evennia import InterruptCommand
|
from evennia import InterruptCommand
|
||||||
|
from evennia.scripts.models import ScriptDB
|
||||||
from evennia.objects.models import ObjectDB
|
from evennia.objects.models import ObjectDB
|
||||||
from evennia.locks.lockhandler import LockException
|
from evennia.locks.lockhandler import LockException
|
||||||
from evennia.commands.cmdhandler import get_and_merge_cmdsets
|
from evennia.commands.cmdhandler import get_and_merge_cmdsets
|
||||||
|
|
@ -14,13 +16,14 @@ from evennia.utils.utils import (
|
||||||
class_from_module,
|
class_from_module,
|
||||||
get_all_typeclasses,
|
get_all_typeclasses,
|
||||||
variable_from_module,
|
variable_from_module,
|
||||||
dbref,
|
dbref, crop,
|
||||||
interactive,
|
interactive,
|
||||||
list_to_string,
|
list_to_string,
|
||||||
display_len,
|
display_len,
|
||||||
)
|
)
|
||||||
from evennia.utils.eveditor import EvEditor
|
from evennia.utils.eveditor import EvEditor
|
||||||
from evennia.utils.evmore import EvMore
|
from evennia.utils.evmore import EvMore
|
||||||
|
from evennia.utils.evtable import EvTable
|
||||||
from evennia.prototypes import spawner, prototypes as protlib, menus as olc_menus
|
from evennia.prototypes import spawner, prototypes as protlib, menus as olc_menus
|
||||||
from evennia.utils.ansi import raw as ansi_raw
|
from evennia.utils.ansi import raw as ansi_raw
|
||||||
|
|
||||||
|
|
@ -53,7 +56,8 @@ __all__ = (
|
||||||
"CmdExamine",
|
"CmdExamine",
|
||||||
"CmdFind",
|
"CmdFind",
|
||||||
"CmdTeleport",
|
"CmdTeleport",
|
||||||
"CmdScript",
|
"CmdScripts",
|
||||||
|
"CmdObjects",
|
||||||
"CmdTag",
|
"CmdTag",
|
||||||
"CmdSpawn",
|
"CmdSpawn",
|
||||||
)
|
)
|
||||||
|
|
@ -2983,6 +2987,330 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
|
||||||
caller.msg(string.strip())
|
caller.msg(string.strip())
|
||||||
|
|
||||||
|
|
||||||
|
class ScriptEvMore(EvMore):
|
||||||
|
"""
|
||||||
|
Listing 1000+ Scripts can be very slow and memory-consuming. So
|
||||||
|
we use this custom EvMore child to build en EvTable only for
|
||||||
|
each page of the list.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def init_pages(self, scripts):
|
||||||
|
"""Prepare the script list pagination"""
|
||||||
|
script_pages = Paginator(scripts, max(1, int(self.height / 2)))
|
||||||
|
super().init_pages(script_pages)
|
||||||
|
|
||||||
|
def page_formatter(self, scripts):
|
||||||
|
"""Takes a page of scripts and formats the output
|
||||||
|
into an EvTable."""
|
||||||
|
|
||||||
|
if not scripts:
|
||||||
|
return "<No scripts>"
|
||||||
|
|
||||||
|
table = EvTable(
|
||||||
|
"|wdbref|n",
|
||||||
|
"|wobj|n",
|
||||||
|
"|wkey|n",
|
||||||
|
"|wintval|n",
|
||||||
|
"|wnext|n",
|
||||||
|
"|wrept|n",
|
||||||
|
"|wtypeclass|n",
|
||||||
|
"|wdesc|n",
|
||||||
|
align="r",
|
||||||
|
border="tablecols",
|
||||||
|
width=self.width,
|
||||||
|
)
|
||||||
|
|
||||||
|
for script in scripts:
|
||||||
|
|
||||||
|
nextrep = script.time_until_next_repeat()
|
||||||
|
if nextrep is None:
|
||||||
|
nextrep = script.db._paused_time
|
||||||
|
nextrep = f"PAUSED {int(nextrep)}s" if nextrep else "--"
|
||||||
|
else:
|
||||||
|
nextrep = f"{nextrep}s"
|
||||||
|
|
||||||
|
maxrepeat = script.repeats
|
||||||
|
remaining = script.remaining_repeats() or 0
|
||||||
|
if maxrepeat:
|
||||||
|
rept = "%i/%i" % (maxrepeat - remaining, maxrepeat)
|
||||||
|
else:
|
||||||
|
rept = "-/-"
|
||||||
|
|
||||||
|
table.add_row(
|
||||||
|
f"#{script.id}",
|
||||||
|
f"{script.obj.key}({script.obj.dbref})"
|
||||||
|
if (hasattr(script, "obj") and script.obj)
|
||||||
|
else "<Global>",
|
||||||
|
script.key,
|
||||||
|
script.interval if script.interval > 0 else "--",
|
||||||
|
nextrep,
|
||||||
|
rept,
|
||||||
|
script.typeclass_path.rsplit(".", 1)[-1],
|
||||||
|
crop(script.desc, width=20),
|
||||||
|
)
|
||||||
|
|
||||||
|
return str(table)
|
||||||
|
|
||||||
|
|
||||||
|
class CmdScripts(COMMAND_DEFAULT_CLASS):
|
||||||
|
"""
|
||||||
|
List and manage all running scripts. Allows for creating new global
|
||||||
|
scripts.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
script[/switches] [script-#dbref, key, script.path or <obj>]
|
||||||
|
script[/start||stop] <obj> = <script.path or script-key>
|
||||||
|
|
||||||
|
Switches:
|
||||||
|
start - start/unpause an existing script's timer.
|
||||||
|
stop - stops an existing script's timer
|
||||||
|
pause - pause a script's timer
|
||||||
|
delete - deletes script. This will also stop the timer as needed
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
script - list scripts
|
||||||
|
script myobj - list all scripts on object
|
||||||
|
script foo.bar.Script - create a new global Script
|
||||||
|
script scriptname - examine named existing global script
|
||||||
|
script myobj = foo.bar.Script - create and assign script to object
|
||||||
|
script/stop myobj = scriptname - stop script on object
|
||||||
|
script/pause foo.Bar.Script - pause global script
|
||||||
|
script/delete myobj - delete ALL scripts on object
|
||||||
|
|
||||||
|
When given with an `<obj>` as left-hand-side, this creates and
|
||||||
|
assigns a new script to that object. Without an `<obj>`, this
|
||||||
|
manages and inspects global scripts
|
||||||
|
|
||||||
|
If no switches are given, this command just views all active
|
||||||
|
scripts. The argument can be either an object, at which point it
|
||||||
|
will be searched for all scripts defined on it, or a script name
|
||||||
|
or #dbref. For using the /stop switch, a unique script #dbref is
|
||||||
|
required since whole classes of scripts often have the same name.
|
||||||
|
|
||||||
|
Use the `script` build-level command for managing scripts attached to
|
||||||
|
objects.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = "scripts"
|
||||||
|
aliases = ["script"]
|
||||||
|
switch_options = ("create", "start", "stop", "pause", "delete")
|
||||||
|
locks = "cmd:perm(scripts) or perm(Builder)"
|
||||||
|
help_category = "System"
|
||||||
|
|
||||||
|
excluded_typeclass_paths = ["evennia.prototypes.prototypes.DbPrototype"]
|
||||||
|
|
||||||
|
switch_mapping = {
|
||||||
|
"create": "|gCreated|n",
|
||||||
|
"start": "|gStarted|n",
|
||||||
|
"stop": "|RStopped|n",
|
||||||
|
"pause": "|Paused|n",
|
||||||
|
"delete": "|rDeleted|n"
|
||||||
|
}
|
||||||
|
|
||||||
|
def _search_script(self, args):
|
||||||
|
# test first if this is a script match
|
||||||
|
scripts = ScriptDB.objects.get_all_scripts(key=args)
|
||||||
|
if scripts:
|
||||||
|
return scripts
|
||||||
|
# try typeclass path
|
||||||
|
scripts = ScriptDB.objects.filter(db_typeclass_path__iendswith=args)
|
||||||
|
if scripts:
|
||||||
|
return scripts
|
||||||
|
|
||||||
|
def func(self):
|
||||||
|
"""implement method"""
|
||||||
|
|
||||||
|
caller = self.caller
|
||||||
|
|
||||||
|
if not self.args:
|
||||||
|
# show all scripts
|
||||||
|
scripts = ScriptDB.objects.all()
|
||||||
|
if not scripts:
|
||||||
|
caller.msg("No scripts found.")
|
||||||
|
return
|
||||||
|
ScriptEvMore(caller, scripts.order_by("id"), session=self.session)
|
||||||
|
return
|
||||||
|
|
||||||
|
# find script or object to operate on
|
||||||
|
scripts, obj = None, None
|
||||||
|
if self.rhs:
|
||||||
|
obj_query = self.lhs
|
||||||
|
script_query = self.rhs
|
||||||
|
else:
|
||||||
|
obj_query = script_query = self.args
|
||||||
|
|
||||||
|
scripts = self._search_script(script_query)
|
||||||
|
objects = ObjectDB.objects.object_search(obj_query)
|
||||||
|
obj = objects[0] if objects else None
|
||||||
|
|
||||||
|
if not self.switches:
|
||||||
|
# creation / view mode
|
||||||
|
if obj:
|
||||||
|
# we have an object
|
||||||
|
if self.rhs:
|
||||||
|
# creation mode
|
||||||
|
if obj.scripts.add(self.rhs, autostart=True):
|
||||||
|
caller.msg(
|
||||||
|
f"Script |w{self.rhs}|n successfully added and "
|
||||||
|
f"started on {obj.get_display_name(caller)}.")
|
||||||
|
else:
|
||||||
|
caller.msg(f"Script {self.rhs} could not be added and/or started "
|
||||||
|
f"on {obj.get_display_name(caller)} (or it started and "
|
||||||
|
"immediately shut down).")
|
||||||
|
else:
|
||||||
|
# just show all scripts on object
|
||||||
|
scripts = ScriptDB.objects.filter(db_obj=obj)
|
||||||
|
if scripts:
|
||||||
|
ScriptEvMore(caller, scripts.order_by("id"), session=self.session)
|
||||||
|
else:
|
||||||
|
caller.msg(f"No scripts defined on {obj}")
|
||||||
|
|
||||||
|
elif scripts:
|
||||||
|
# show found script(s)
|
||||||
|
ScriptEvMore(caller, scripts.order_by("id"), session=self.session)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# create global script
|
||||||
|
try:
|
||||||
|
new_script = create.create_script(self.args)
|
||||||
|
except ImportError:
|
||||||
|
logger.log_trace()
|
||||||
|
new_script = None
|
||||||
|
|
||||||
|
if new_script:
|
||||||
|
caller.msg(f"Global Script Created - "
|
||||||
|
f"{new_script.key} ({new_script.typeclass_path})")
|
||||||
|
ScriptEvMore(caller, [new_script], session=self.session)
|
||||||
|
else:
|
||||||
|
caller.msg(f"Global Script |rNOT|n Created |r(see log)|n - "
|
||||||
|
f"arguments: {self.args}")
|
||||||
|
|
||||||
|
elif scripts or obj:
|
||||||
|
# modification switches - must operate on existing scripts
|
||||||
|
|
||||||
|
if not scripts:
|
||||||
|
scripts = ScriptDB.objects.filter(db_obj=obj)
|
||||||
|
|
||||||
|
if scripts.count() > 1:
|
||||||
|
ret = yield(f"Multiple scripts found: {scripts}. Are you sure you want to "
|
||||||
|
"operate on all of them? [Y]/N? ")
|
||||||
|
if ret.lower() in ('n', 'no'):
|
||||||
|
caller.msg("Aborted.")
|
||||||
|
return
|
||||||
|
|
||||||
|
for script in scripts:
|
||||||
|
script_key = script.key
|
||||||
|
script_typeclass_path = script.typeclass_path
|
||||||
|
scripttype = f"Script on {obj}" if obj else "Global Script"
|
||||||
|
|
||||||
|
for switch in self.switches:
|
||||||
|
verb = self.switch_mapping[switch]
|
||||||
|
msgs = []
|
||||||
|
try:
|
||||||
|
getattr(script, switch)()
|
||||||
|
except Exception:
|
||||||
|
logger.log_trace()
|
||||||
|
msgs.append(f"{scripttype} |rNOT|n {verb} |r(see log)|n - "
|
||||||
|
f"{script_key} ({script_typeclass_path})|n")
|
||||||
|
else:
|
||||||
|
msgs.append(f"{scripttype} {verb} - "
|
||||||
|
f"{script_key} ({script_typeclass_path})")
|
||||||
|
caller.msg("\n".join(msgs))
|
||||||
|
if "delete" not in self.switches:
|
||||||
|
ScriptEvMore(caller, [script], session=self.session)
|
||||||
|
else:
|
||||||
|
caller.msg("No scripts found.")
|
||||||
|
|
||||||
|
|
||||||
|
class CmdObjects(COMMAND_DEFAULT_CLASS):
|
||||||
|
"""
|
||||||
|
statistics on objects in the database
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
objects [<nr>]
|
||||||
|
|
||||||
|
Gives statictics on objects in database as well as
|
||||||
|
a list of <nr> latest objects in database. If not
|
||||||
|
given, <nr> defaults to 10.
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = "objects"
|
||||||
|
aliases = ["listobjects", "listobjs", "stats", "db"]
|
||||||
|
locks = "cmd:perm(listobjects) or perm(Builder)"
|
||||||
|
help_category = "System"
|
||||||
|
|
||||||
|
def func(self):
|
||||||
|
"""Implement the command"""
|
||||||
|
|
||||||
|
caller = self.caller
|
||||||
|
nlim = int(self.args) if self.args and self.args.isdigit() else 10
|
||||||
|
nobjs = ObjectDB.objects.count()
|
||||||
|
Character = class_from_module(settings.BASE_CHARACTER_TYPECLASS)
|
||||||
|
nchars = Character.objects.all_family().count()
|
||||||
|
Room = class_from_module(settings.BASE_ROOM_TYPECLASS)
|
||||||
|
nrooms = Room.objects.all_family().count()
|
||||||
|
Exit = class_from_module(settings.BASE_EXIT_TYPECLASS)
|
||||||
|
nexits = Exit.objects.all_family().count()
|
||||||
|
nother = nobjs - nchars - nrooms - nexits
|
||||||
|
nobjs = nobjs or 1 # fix zero-div error with empty database
|
||||||
|
|
||||||
|
# total object sum table
|
||||||
|
totaltable = self.styled_table(
|
||||||
|
"|wtype|n", "|wcomment|n", "|wcount|n", "|w%|n", border="table", align="l"
|
||||||
|
)
|
||||||
|
totaltable.align = "l"
|
||||||
|
totaltable.add_row(
|
||||||
|
"Characters",
|
||||||
|
"(BASE_CHARACTER_TYPECLASS + children)",
|
||||||
|
nchars,
|
||||||
|
"%.2f" % ((float(nchars) / nobjs) * 100),
|
||||||
|
)
|
||||||
|
totaltable.add_row(
|
||||||
|
"Rooms",
|
||||||
|
"(BASE_ROOM_TYPECLASS + children)",
|
||||||
|
nrooms,
|
||||||
|
"%.2f" % ((float(nrooms) / nobjs) * 100),
|
||||||
|
)
|
||||||
|
totaltable.add_row(
|
||||||
|
"Exits",
|
||||||
|
"(BASE_EXIT_TYPECLASS + children)",
|
||||||
|
nexits,
|
||||||
|
"%.2f" % ((float(nexits) / nobjs) * 100),
|
||||||
|
)
|
||||||
|
totaltable.add_row("Other", "", nother, "%.2f" % ((float(nother) / nobjs) * 100))
|
||||||
|
|
||||||
|
# typeclass table
|
||||||
|
typetable = self.styled_table(
|
||||||
|
"|wtypeclass|n", "|wcount|n", "|w%|n", border="table", align="l"
|
||||||
|
)
|
||||||
|
typetable.align = "l"
|
||||||
|
dbtotals = ObjectDB.objects.get_typeclass_totals()
|
||||||
|
for stat in dbtotals:
|
||||||
|
typetable.add_row(
|
||||||
|
stat.get("typeclass", "<error>"),
|
||||||
|
stat.get("count", -1),
|
||||||
|
"%.2f" % stat.get("percent", -1),
|
||||||
|
)
|
||||||
|
|
||||||
|
# last N table
|
||||||
|
objs = ObjectDB.objects.all().order_by("db_date_created")[max(0, nobjs - nlim) :]
|
||||||
|
latesttable = self.styled_table(
|
||||||
|
"|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", align="l", border="table"
|
||||||
|
)
|
||||||
|
latesttable.align = "l"
|
||||||
|
for obj in objs:
|
||||||
|
latesttable.add_row(
|
||||||
|
utils.datetime_format(obj.date_created), obj.dbref, obj.key, obj.path
|
||||||
|
)
|
||||||
|
|
||||||
|
string = "\n|wObject subtype totals (out of %i Objects):|n\n%s" % (nobjs, totaltable)
|
||||||
|
string += "\n|wObject typeclass distribution:|n\n%s" % typetable
|
||||||
|
string += "\n|wLast %s Objects created:|n\n%s" % (min(nobjs, nlim), latesttable)
|
||||||
|
caller.msg(string)
|
||||||
|
|
||||||
|
|
||||||
class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
teleport object to another location
|
teleport object to another location
|
||||||
|
|
@ -3107,113 +3435,6 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
||||||
caller.msg("Teleportation failed.")
|
caller.msg("Teleportation failed.")
|
||||||
|
|
||||||
|
|
||||||
class CmdScript(COMMAND_DEFAULT_CLASS):
|
|
||||||
"""
|
|
||||||
attach a script to an object
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
addscript[/switch] <obj> [= script_path or <scriptkey>]
|
|
||||||
|
|
||||||
Switches:
|
|
||||||
start - start all non-running scripts on object, or a given script only
|
|
||||||
stop - stop all scripts on objects, or a given script only
|
|
||||||
|
|
||||||
If no script path/key is given, lists all scripts active on the given
|
|
||||||
object.
|
|
||||||
Script path can be given from the base location for scripts as given in
|
|
||||||
settings. If adding a new script, it will be started automatically
|
|
||||||
(no /start switch is needed). Using the /start or /stop switches on an
|
|
||||||
object without specifying a script key/path will start/stop ALL scripts on
|
|
||||||
the object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
key = "addscript"
|
|
||||||
aliases = ["attachscript"]
|
|
||||||
switch_options = ("start", "stop")
|
|
||||||
locks = "cmd:perm(script) or perm(Builder)"
|
|
||||||
help_category = "Building"
|
|
||||||
|
|
||||||
def func(self):
|
|
||||||
"""Do stuff"""
|
|
||||||
|
|
||||||
caller = self.caller
|
|
||||||
|
|
||||||
if not self.args:
|
|
||||||
string = "Usage: script[/switch] <obj> [= script_path or <script key>]"
|
|
||||||
caller.msg(string)
|
|
||||||
return
|
|
||||||
|
|
||||||
if not self.lhs:
|
|
||||||
caller.msg("To create a global script you need |wscripts/add <typeclass>|n.")
|
|
||||||
return
|
|
||||||
|
|
||||||
obj = caller.search(self.lhs)
|
|
||||||
if not obj:
|
|
||||||
return
|
|
||||||
|
|
||||||
result = []
|
|
||||||
if not self.rhs:
|
|
||||||
# no rhs means we want to operate on all scripts
|
|
||||||
scripts = obj.scripts.all()
|
|
||||||
if not scripts:
|
|
||||||
result.append("No scripts defined on %s." % obj.get_display_name(caller))
|
|
||||||
elif not self.switches:
|
|
||||||
# view all scripts
|
|
||||||
from evennia.commands.default.system import ScriptEvMore
|
|
||||||
|
|
||||||
ScriptEvMore(self.caller, scripts.order_by("id"), session=self.session)
|
|
||||||
return
|
|
||||||
elif "start" in self.switches:
|
|
||||||
num = sum([obj.scripts.start(script.key) for script in scripts])
|
|
||||||
result.append("%s scripts started on %s." % (num, obj.get_display_name(caller)))
|
|
||||||
elif "stop" in self.switches:
|
|
||||||
for script in scripts:
|
|
||||||
result.append(
|
|
||||||
"Stopping script %s on %s."
|
|
||||||
% (script.get_display_name(caller), obj.get_display_name(caller))
|
|
||||||
)
|
|
||||||
script.stop()
|
|
||||||
else: # rhs exists
|
|
||||||
if not self.switches:
|
|
||||||
# adding a new script, and starting it
|
|
||||||
ok = obj.scripts.add(self.rhs, autostart=True)
|
|
||||||
if not ok:
|
|
||||||
result.append(
|
|
||||||
"\nScript %s could not be added and/or started on %s "
|
|
||||||
"(or it started and immediately shut down)."
|
|
||||||
% (self.rhs, obj.get_display_name(caller))
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
result.append(
|
|
||||||
"Script |w%s|n successfully added and started on %s."
|
|
||||||
% (self.rhs, obj.get_display_name(caller))
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
paths = [self.rhs] + [
|
|
||||||
"%s.%s" % (prefix, self.rhs) for prefix in settings.TYPECLASS_PATHS
|
|
||||||
]
|
|
||||||
if "stop" in self.switches:
|
|
||||||
# we are stopping an already existing script
|
|
||||||
for path in paths:
|
|
||||||
ok = obj.scripts.stop(path)
|
|
||||||
if not ok:
|
|
||||||
result.append("\nScript %s could not be stopped. Does it exist?" % path)
|
|
||||||
else:
|
|
||||||
result = ["Script stopped and removed from object."]
|
|
||||||
break
|
|
||||||
if "start" in self.switches:
|
|
||||||
# we are starting an already existing script
|
|
||||||
for path in paths:
|
|
||||||
ok = obj.scripts.start(path)
|
|
||||||
if not ok:
|
|
||||||
result.append("\nScript %s could not be (re)started." % path)
|
|
||||||
else:
|
|
||||||
result = ["Script started successfully."]
|
|
||||||
break
|
|
||||||
|
|
||||||
EvMore(caller, "".join(result).strip())
|
|
||||||
|
|
||||||
|
|
||||||
class CmdTag(COMMAND_DEFAULT_CLASS):
|
class CmdTag(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,6 @@ class CharacterCmdSet(CmdSet):
|
||||||
|
|
||||||
# System commands
|
# System commands
|
||||||
self.add(system.CmdPy())
|
self.add(system.CmdPy())
|
||||||
self.add(system.CmdScripts())
|
|
||||||
self.add(system.CmdObjects())
|
|
||||||
self.add(system.CmdAccounts())
|
self.add(system.CmdAccounts())
|
||||||
self.add(system.CmdService())
|
self.add(system.CmdService())
|
||||||
self.add(system.CmdAbout())
|
self.add(system.CmdAbout())
|
||||||
|
|
@ -83,10 +81,11 @@ class CharacterCmdSet(CmdSet):
|
||||||
self.add(building.CmdExamine())
|
self.add(building.CmdExamine())
|
||||||
self.add(building.CmdTypeclass())
|
self.add(building.CmdTypeclass())
|
||||||
self.add(building.CmdLock())
|
self.add(building.CmdLock())
|
||||||
self.add(building.CmdScript())
|
|
||||||
self.add(building.CmdSetHome())
|
self.add(building.CmdSetHome())
|
||||||
self.add(building.CmdTag())
|
self.add(building.CmdTag())
|
||||||
self.add(building.CmdSpawn())
|
self.add(building.CmdSpawn())
|
||||||
|
self.add(building.CmdScripts())
|
||||||
|
self.add(building.CmdObjects())
|
||||||
|
|
||||||
# Batchprocessor commands
|
# Batchprocessor commands
|
||||||
self.add(batchprocess.CmdBatchCommands())
|
self.add(batchprocess.CmdBatchCommands())
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,13 @@ import twisted
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.paginator import Paginator
|
|
||||||
from evennia.server.sessionhandler import SESSIONS
|
from evennia.server.sessionhandler import SESSIONS
|
||||||
from evennia.scripts.models import ScriptDB
|
|
||||||
from evennia.objects.models import ObjectDB
|
|
||||||
from evennia.accounts.models import AccountDB
|
from evennia.accounts.models import AccountDB
|
||||||
from evennia.utils import logger, utils, gametime, create, search
|
from evennia.utils import logger, utils, gametime, search
|
||||||
from evennia.utils.eveditor import EvEditor
|
from evennia.utils.eveditor import EvEditor
|
||||||
from evennia.utils.evtable import EvTable
|
from evennia.utils.evtable import EvTable
|
||||||
from evennia.utils.evmore import EvMore
|
|
||||||
from evennia.utils.evmenu import ask_yes_no
|
from evennia.utils.evmenu import ask_yes_no
|
||||||
from evennia.utils.utils import crop, class_from_module, iter_to_str
|
from evennia.utils.utils import class_from_module, iter_to_str
|
||||||
from evennia.scripts.taskhandler import TaskHandlerTask
|
from evennia.scripts.taskhandler import TaskHandlerTask
|
||||||
|
|
||||||
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||||
|
|
@ -41,8 +37,6 @@ __all__ = (
|
||||||
"CmdReset",
|
"CmdReset",
|
||||||
"CmdShutdown",
|
"CmdShutdown",
|
||||||
"CmdPy",
|
"CmdPy",
|
||||||
"CmdScripts",
|
|
||||||
"CmdObjects",
|
|
||||||
"CmdService",
|
"CmdService",
|
||||||
"CmdAbout",
|
"CmdAbout",
|
||||||
"CmdTime",
|
"CmdTime",
|
||||||
|
|
@ -412,278 +406,6 @@ class CmdPy(COMMAND_DEFAULT_CLASS):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ScriptEvMore(EvMore):
|
|
||||||
"""
|
|
||||||
Listing 1000+ Scripts can be very slow and memory-consuming. So
|
|
||||||
we use this custom EvMore child to build en EvTable only for
|
|
||||||
each page of the list.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def init_pages(self, scripts):
|
|
||||||
"""Prepare the script list pagination"""
|
|
||||||
script_pages = Paginator(scripts, max(1, int(self.height / 2)))
|
|
||||||
super().init_pages(script_pages)
|
|
||||||
|
|
||||||
def page_formatter(self, scripts):
|
|
||||||
"""Takes a page of scripts and formats the output
|
|
||||||
into an EvTable."""
|
|
||||||
|
|
||||||
if not scripts:
|
|
||||||
return "<No scripts>"
|
|
||||||
|
|
||||||
table = EvTable(
|
|
||||||
"|wdbref|n",
|
|
||||||
"|wobj|n",
|
|
||||||
"|wkey|n",
|
|
||||||
"|wintval|n",
|
|
||||||
"|wnext|n",
|
|
||||||
"|wrept|n",
|
|
||||||
"|wtypeclass|n",
|
|
||||||
"|wdesc|n",
|
|
||||||
align="r",
|
|
||||||
border="tablecols",
|
|
||||||
width=self.width,
|
|
||||||
)
|
|
||||||
|
|
||||||
for script in scripts:
|
|
||||||
|
|
||||||
nextrep = script.time_until_next_repeat()
|
|
||||||
if nextrep is None:
|
|
||||||
nextrep = script.db._paused_time
|
|
||||||
nextrep = f"PAUSED {int(nextrep)}s" if nextrep else "--"
|
|
||||||
else:
|
|
||||||
nextrep = f"{nextrep}s"
|
|
||||||
|
|
||||||
maxrepeat = script.repeats
|
|
||||||
remaining = script.remaining_repeats() or 0
|
|
||||||
if maxrepeat:
|
|
||||||
rept = "%i/%i" % (maxrepeat - remaining, maxrepeat)
|
|
||||||
else:
|
|
||||||
rept = "-/-"
|
|
||||||
|
|
||||||
table.add_row(
|
|
||||||
f"#{script.id}",
|
|
||||||
f"{script.obj.key}({script.obj.dbref})"
|
|
||||||
if (hasattr(script, "obj") and script.obj)
|
|
||||||
else "<Global>",
|
|
||||||
script.key,
|
|
||||||
script.interval if script.interval > 0 else "--",
|
|
||||||
nextrep,
|
|
||||||
rept,
|
|
||||||
script.typeclass_path.rsplit(".", 1)[-1],
|
|
||||||
crop(script.desc, width=20),
|
|
||||||
)
|
|
||||||
|
|
||||||
return str(table)
|
|
||||||
|
|
||||||
|
|
||||||
class CmdScripts(COMMAND_DEFAULT_CLASS):
|
|
||||||
"""
|
|
||||||
List and manage all running scripts. Allows for creating new global
|
|
||||||
scripts.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
script[/switches] [#dbref, key, script.path or <obj>]
|
|
||||||
|
|
||||||
Switches:
|
|
||||||
create - create a new global script of given typeclass path. This will
|
|
||||||
auto-start the script's timer if it has one.
|
|
||||||
start - start/unpause an existing script's timer.
|
|
||||||
stop - stops an existing script's timer
|
|
||||||
pause - pause a script's timer
|
|
||||||
delete - deletes script. This will also stop the timer as needed
|
|
||||||
|
|
||||||
If no switches are given, this command just views all active
|
|
||||||
scripts. The argument can be either an object, at which point it
|
|
||||||
will be searched for all scripts defined on it, or a script name
|
|
||||||
or #dbref. For using the /stop switch, a unique script #dbref is
|
|
||||||
required since whole classes of scripts often have the same name.
|
|
||||||
|
|
||||||
Use the `script` build-level command for managing scripts attached to
|
|
||||||
objects.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
key = "scripts"
|
|
||||||
aliases = ["scripts"]
|
|
||||||
switch_options = ("create", "start", "stop", "pause", "delete")
|
|
||||||
locks = "cmd:perm(listscripts) or perm(Admin)"
|
|
||||||
help_category = "System"
|
|
||||||
|
|
||||||
excluded_typeclass_paths = ["evennia.prototypes.prototypes.DbPrototype"]
|
|
||||||
|
|
||||||
switch_mapping = {
|
|
||||||
"create": "|gCreated|n",
|
|
||||||
"start": "|gStarted|n",
|
|
||||||
"stop": "|RStopped|n",
|
|
||||||
"pause": "|Paused|n",
|
|
||||||
"delete": "|rDeleted|n"
|
|
||||||
}
|
|
||||||
|
|
||||||
def _search_script(self, args):
|
|
||||||
# test first if this is a script match
|
|
||||||
scripts = ScriptDB.objects.get_all_scripts(key=args)
|
|
||||||
if scripts:
|
|
||||||
return scripts
|
|
||||||
# try typeclass path
|
|
||||||
scripts = ScriptDB.objects.filter(db_typeclass_path__iendswith=args)
|
|
||||||
if scripts:
|
|
||||||
return scripts
|
|
||||||
# try to find an object instead.
|
|
||||||
objects = ObjectDB.objects.object_search(args)
|
|
||||||
if objects:
|
|
||||||
scripts = ScriptDB.objects.filter(db_obj__in=objects)
|
|
||||||
return scripts
|
|
||||||
|
|
||||||
def func(self):
|
|
||||||
"""implement method"""
|
|
||||||
|
|
||||||
caller = self.caller
|
|
||||||
args = self.args
|
|
||||||
|
|
||||||
if "create" in self.switches:
|
|
||||||
# global script-start mode
|
|
||||||
verb = self.switch_mapping['create']
|
|
||||||
if not args:
|
|
||||||
caller.msg("Usage script/create <key or typeclass>")
|
|
||||||
return
|
|
||||||
new_script = create.create_script(args)
|
|
||||||
if new_script:
|
|
||||||
caller.msg(f"Global Script {verb} - {new_script.key} ({new_script.typeclass_path})")
|
|
||||||
ScriptEvMore(caller, [new_script], session=self.session)
|
|
||||||
else:
|
|
||||||
caller.msg(f"Global Script |rNOT|n {verb} |r(see log)|n - arguments: {args}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# all other switches require existing scripts
|
|
||||||
if args:
|
|
||||||
scripts = self._search_script(args)
|
|
||||||
if not scripts:
|
|
||||||
caller.msg(f"No scripts found matching '{args}'.")
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
scripts = ScriptDB.objects.all()
|
|
||||||
if not scripts:
|
|
||||||
caller.msg("No scripts found.")
|
|
||||||
return
|
|
||||||
|
|
||||||
if args and self.switches:
|
|
||||||
# global script-modifying mode
|
|
||||||
if scripts.count() > 1:
|
|
||||||
caller.msg("Multiple script matches. Please refine your search.")
|
|
||||||
return
|
|
||||||
script = scripts[0]
|
|
||||||
script_key = script.key
|
|
||||||
script_typeclass_path = script.typeclass_path
|
|
||||||
for switch in self.switches:
|
|
||||||
verb = self.switch_mapping[switch]
|
|
||||||
msgs = []
|
|
||||||
try:
|
|
||||||
getattr(script, switch)()
|
|
||||||
except Exception:
|
|
||||||
logger.log_trace()
|
|
||||||
msgs.append(f"Global Script |rNOT|n {verb} |r(see log)|n - "
|
|
||||||
f"{script_key} ({script_typeclass_path})|n")
|
|
||||||
else:
|
|
||||||
msgs.append(f"Global Script {verb} - "
|
|
||||||
f"{script_key} ({script_typeclass_path})")
|
|
||||||
caller.msg("\n".join(msgs))
|
|
||||||
if "delete" not in self.switches:
|
|
||||||
ScriptEvMore(caller, [script], session=self.session)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
# simply show the found scripts
|
|
||||||
ScriptEvMore(caller, scripts.order_by("id"), session=self.session)
|
|
||||||
|
|
||||||
|
|
||||||
class CmdObjects(COMMAND_DEFAULT_CLASS):
|
|
||||||
"""
|
|
||||||
statistics on objects in the database
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
objects [<nr>]
|
|
||||||
|
|
||||||
Gives statictics on objects in database as well as
|
|
||||||
a list of <nr> latest objects in database. If not
|
|
||||||
given, <nr> defaults to 10.
|
|
||||||
"""
|
|
||||||
|
|
||||||
key = "objects"
|
|
||||||
aliases = ["listobjects", "listobjs", "stats", "db"]
|
|
||||||
locks = "cmd:perm(listobjects) or perm(Builder)"
|
|
||||||
help_category = "System"
|
|
||||||
|
|
||||||
def func(self):
|
|
||||||
"""Implement the command"""
|
|
||||||
|
|
||||||
caller = self.caller
|
|
||||||
nlim = int(self.args) if self.args and self.args.isdigit() else 10
|
|
||||||
nobjs = ObjectDB.objects.count()
|
|
||||||
Character = class_from_module(settings.BASE_CHARACTER_TYPECLASS)
|
|
||||||
nchars = Character.objects.all_family().count()
|
|
||||||
Room = class_from_module(settings.BASE_ROOM_TYPECLASS)
|
|
||||||
nrooms = Room.objects.all_family().count()
|
|
||||||
Exit = class_from_module(settings.BASE_EXIT_TYPECLASS)
|
|
||||||
nexits = Exit.objects.all_family().count()
|
|
||||||
nother = nobjs - nchars - nrooms - nexits
|
|
||||||
nobjs = nobjs or 1 # fix zero-div error with empty database
|
|
||||||
|
|
||||||
# total object sum table
|
|
||||||
totaltable = self.styled_table(
|
|
||||||
"|wtype|n", "|wcomment|n", "|wcount|n", "|w%|n", border="table", align="l"
|
|
||||||
)
|
|
||||||
totaltable.align = "l"
|
|
||||||
totaltable.add_row(
|
|
||||||
"Characters",
|
|
||||||
"(BASE_CHARACTER_TYPECLASS + children)",
|
|
||||||
nchars,
|
|
||||||
"%.2f" % ((float(nchars) / nobjs) * 100),
|
|
||||||
)
|
|
||||||
totaltable.add_row(
|
|
||||||
"Rooms",
|
|
||||||
"(BASE_ROOM_TYPECLASS + children)",
|
|
||||||
nrooms,
|
|
||||||
"%.2f" % ((float(nrooms) / nobjs) * 100),
|
|
||||||
)
|
|
||||||
totaltable.add_row(
|
|
||||||
"Exits",
|
|
||||||
"(BASE_EXIT_TYPECLASS + children)",
|
|
||||||
nexits,
|
|
||||||
"%.2f" % ((float(nexits) / nobjs) * 100),
|
|
||||||
)
|
|
||||||
totaltable.add_row("Other", "", nother, "%.2f" % ((float(nother) / nobjs) * 100))
|
|
||||||
|
|
||||||
# typeclass table
|
|
||||||
typetable = self.styled_table(
|
|
||||||
"|wtypeclass|n", "|wcount|n", "|w%|n", border="table", align="l"
|
|
||||||
)
|
|
||||||
typetable.align = "l"
|
|
||||||
dbtotals = ObjectDB.objects.get_typeclass_totals()
|
|
||||||
for stat in dbtotals:
|
|
||||||
typetable.add_row(
|
|
||||||
stat.get("typeclass", "<error>"),
|
|
||||||
stat.get("count", -1),
|
|
||||||
"%.2f" % stat.get("percent", -1),
|
|
||||||
)
|
|
||||||
|
|
||||||
# last N table
|
|
||||||
objs = ObjectDB.objects.all().order_by("db_date_created")[max(0, nobjs - nlim) :]
|
|
||||||
latesttable = self.styled_table(
|
|
||||||
"|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", align="l", border="table"
|
|
||||||
)
|
|
||||||
latesttable.align = "l"
|
|
||||||
for obj in objs:
|
|
||||||
latesttable.add_row(
|
|
||||||
utils.datetime_format(obj.date_created), obj.dbref, obj.key, obj.path
|
|
||||||
)
|
|
||||||
|
|
||||||
string = "\n|wObject subtype totals (out of %i Objects):|n\n%s" % (nobjs, totaltable)
|
|
||||||
string += "\n|wObject typeclass distribution:|n\n%s" % typetable
|
|
||||||
string += "\n|wLast %s Objects created:|n\n%s" % (min(nobjs, nlim), latesttable)
|
|
||||||
caller.msg(string)
|
|
||||||
|
|
||||||
|
|
||||||
class CmdAccounts(COMMAND_DEFAULT_CLASS):
|
class CmdAccounts(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
Manage registered accounts
|
Manage registered accounts
|
||||||
|
|
|
||||||
|
|
@ -565,10 +565,10 @@ class TestSystem(CommandTest):
|
||||||
self.call(system.CmdPy(), "/clientraw 1+2", ">>> 1+2|3")
|
self.call(system.CmdPy(), "/clientraw 1+2", ">>> 1+2|3")
|
||||||
|
|
||||||
def test_scripts(self):
|
def test_scripts(self):
|
||||||
self.call(system.CmdScripts(), "", "dbref ")
|
self.call(building.CmdScripts(), "", "dbref ")
|
||||||
|
|
||||||
def test_objects(self):
|
def test_objects(self):
|
||||||
self.call(system.CmdObjects(), "", "Object subtype totals")
|
self.call(building.CmdObjects(), "", "Object subtype totals")
|
||||||
|
|
||||||
def test_about(self):
|
def test_about(self):
|
||||||
self.call(system.CmdAbout(), "", None)
|
self.call(system.CmdAbout(), "", None)
|
||||||
|
|
@ -1573,36 +1573,58 @@ class TestBuilding(CommandTest):
|
||||||
self.call(building.CmdFind(), f"=#{id1}-{id2}", f"{mdiff} Matches(#{id1}-#{id2}):")
|
self.call(building.CmdFind(), f"=#{id1}-{id2}", f"{mdiff} Matches(#{id1}-#{id2}):")
|
||||||
|
|
||||||
def test_script(self):
|
def test_script(self):
|
||||||
self.call(building.CmdScript(), "Obj = ", "No scripts defined on Obj")
|
self.call(building.CmdScripts(), "Obj", "No scripts defined on Obj")
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdScript(), "Obj = scripts.Script", "Script scripts.Script successfully added"
|
building.CmdScripts(),
|
||||||
|
"Obj = scripts.Script",
|
||||||
|
"Script scripts.Script successfully added"
|
||||||
)
|
)
|
||||||
self.call(building.CmdScript(), "", "Usage: ")
|
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdScript(),
|
building.CmdScripts(),
|
||||||
"= Obj",
|
"evennia.Dummy",
|
||||||
"To create a global script you need scripts/add <typeclass>.",
|
"Global Script NOT Created "
|
||||||
)
|
)
|
||||||
self.call(building.CmdScript(), "Obj ", "dbref ")
|
self.call(
|
||||||
|
building.CmdScripts(),
|
||||||
|
"evennia.scripts.scripts.DoNothing",
|
||||||
|
"Global Script Created - sys_do_nothing "
|
||||||
|
)
|
||||||
|
self.call(building.CmdScripts(), "Obj ", "dbref ")
|
||||||
|
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdScript(), "/start Obj", "1 scripts started on Obj"
|
building.CmdScripts(), "/start Obj", "Script on Obj Started "
|
||||||
) # we allow running start again; this should still happen
|
) # we allow running start again; this should still happen
|
||||||
self.call(building.CmdScript(), "/stop Obj", "Stopping script")
|
self.call(building.CmdScripts(), "/stop Obj", "Script on Obj Stopped - ")
|
||||||
|
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdScript(), "Obj = scripts.Script", "Script scripts.Script successfully added"
|
building.CmdScripts(), "Obj = scripts.Script",
|
||||||
|
"Script scripts.Script successfully added",
|
||||||
|
inputs=["Y"]
|
||||||
)
|
)
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdScript(),
|
building.CmdScripts(),
|
||||||
"/start Obj = scripts.Script",
|
"/start Obj = scripts.Script",
|
||||||
"Script scripts.Script could not be (re)started.",
|
"Script on Obj Started ",
|
||||||
|
inputs=["Y"]
|
||||||
)
|
)
|
||||||
self.call(
|
self.call(
|
||||||
building.CmdScript(),
|
building.CmdScripts(),
|
||||||
"/stop Obj = scripts.Script",
|
"/stop Obj = scripts.Script",
|
||||||
"Script stopped and removed from object.",
|
"Script on Obj Stopped ",
|
||||||
|
inputs=["Y"]
|
||||||
)
|
)
|
||||||
|
self.call(
|
||||||
|
building.CmdScripts(),
|
||||||
|
"/delete Obj = scripts.Script",
|
||||||
|
"Script on Obj Deleted ",
|
||||||
|
inputs=["Y"]
|
||||||
|
)
|
||||||
|
self.call(
|
||||||
|
building.CmdScripts(),
|
||||||
|
"/delete evennia.scripts.scripts.DoNothing",
|
||||||
|
"Global Script Deleted -"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_teleport(self):
|
def test_teleport(self):
|
||||||
oid = self.obj1.id
|
oid = self.obj1.id
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue