diff --git a/game/gamesrc/commands/default/admin.py b/game/gamesrc/commands/default/admin.py new file mode 100644 index 0000000000..d5e7fa3e1f --- /dev/null +++ b/game/gamesrc/commands/default/admin.py @@ -0,0 +1,484 @@ +""" + +Admin commands + +""" + +from django.conf import settings +from django.contrib.auth.models import User +from src.players.models import PlayerDB +from game.gamesrc.commands.default.muxcommand import MuxCommand +from src.server import sessionhandler +from src.permissions.permissions import has_perm, has_perm_string +from src.permissions.models import PermissionGroup +from src.utils import utils + + +class CmdBoot(MuxCommand): + """ + @boot + + Usage + @boot[/switches] [: reason] + + Switches: + quiet - Silently boot without informing player + port - boot by port number instead of name or dbref + + Boot a player object from the server. If a reason is + supplied it will be echoed to the user unless /quiet is set. + """ + + key = "@boot" + permissions = "cmd:boot" + help_category = "Admin" + + def func(self): + "Implementing the function" + caller = self.caller + args = self.args + + if not args: + caller.msg("Usage: @boot[/switches] [:reason]") + return + + if ':' in args: + args, reason = [a.strip() for a in args.split(':', 1)] + boot_list = [] + reason = "" + + if 'port' in self.switches: + # Boot a particular port. + sessions = sessionhandler.get_session_list(True) + for sess in sessions: + # Find the session with the matching port number. + if sess.getClientAddress()[1] == int(args): + boot_list.append(sess) + break + else: + # Boot by player object + pobj = caller.search("*%s" % args, global_search=True) + if not pobj: + return + pobj = pobj + if pobj.has_player: + if not has_perm(caller, pobj, 'can_boot'): + string = "You don't have the permission to boot %s." + pobj.msg(string) + return + # we have a bootable object with a connected user + matches = sessionhandler.sessions_from_object(pobj) + for match in matches: + boot_list.append(match) + else: + caller.msg("That object has no connected player.") + return + + if not boot_list: + caller.msg("No matches found.") + return + + # Carry out the booting of the sessions in the boot list. + + feedback = None + if not 'quiet' in self.switches: + feedback = "You have been disconnected by %s.\n" % caller.name + if reason: + feedback += "\nReason given: %s" % reason + + for session in boot_list: + name = session.name + if feedback: + session.msg(feedback) + session.disconnectClient() + sessionhandler.remove_session(session) + caller.msg("You booted %s." % name) + + +class CmdDelPlayer(MuxCommand): + """ + delplayer - delete player from server + + Usage: + @delplayer[/switch] [: reason] + + Switch: + delobj - also delete the player's currently + assigned in-game object. + + Completely deletes a user from the server database, + making their nick and e-mail again available. + """ + + key = "@delplayer" + permissions = "cmd:delplayer" + help_category = "Admin" + + def func(self): + "Implements the command." + + caller = self.caller + args = self.args + + if not args: + caller.msg("Usage: @delplayer[/delobj] ") + return + + reason = "" + if ':' in args: + args, reason = [arg.strip() for arg in args.split(':', 1)] + + # We use player_search since we want to be sure to find also players + # that lack characters. + players = PlayerDB.objects.filter(db_key=args) + if not players: + try: + players = PlayerDB.objects.filter(id=args) + except ValueError: + pass + + if not players: + # try to find a user instead of a Player + try: + user = User.objects.get(id=args) + except Exception: + try: + user = User.objects.get(username__iexact=args) + except Exception: + string = "No Player nor User found matching '%s'." % args + caller.msg(string) + return + try: + player = user.get_profile() + except Exception: + player = None + + if not has_perm_string(caller, 'manage_players'): + string = "You don't have the permissions to delete this player." + caller.msg(string) + return + string = "" + name = user.username + user.delete() + if player: + name = player.name + player.delete() + string = "Player %s was deleted." % name + else: + string += "The User %s was deleted, but had no Player associated with it." % name + caller.msg(string) + return + + elif len(players) > 1: + string = "There where multiple matches:" + for player in players: + string += "\n %s %s" % (player.id, player.key) + return + + else: + # one single match + + player = players[0] + user = player.user + character = player.character + + if not has_perm(caller, player, 'manage_players'): + string = "You don't have the permissions to delete that player." + caller.msg(string) + return + + uname = user.username + # boot the player then delete + if character and character.has_player: + caller.msg("Booting and informing player ...") + string = "\nYour account '%s' is being *permanently* deleted.\n" % uname + if reason: + string += " Reason given:\n '%s'" % reason + character.msg(string) + caller.execute_cmd("@boot %s" % uname) + + player.delete() + user.delete() + caller.msg("Player %s was successfully deleted." % uname) + + +class CmdEmit(MuxCommand): + """ + @emit + + Usage: + @emit[/switches] [, , ... =] + @remit [, , ... =] + @pemit [, , ... =] + + Switches: + room : limit emits to rooms only + players : limit emits to players only + contents : send to the contents of matched objects too + + Emits a message to the selected objects or to + your immediate surroundings. If the object is a room, + send to its contents. @remit and @pemit are just + limited forms of @emit, for sending to rooms and + to players respectively. + """ + key = "@emit" + aliases = ["@pemit", "@remit"] + permissions = "cmd:emit" + help_category = "Admin" + + def func(self): + "Implement the command" + + caller = self.caller + args = self.args + + if not args: + string = "Usage: " + string += "\n@emit[/switches] [, , ... =] " + string += "\n@remit [, , ... =] " + string += "\n@pemit [, , ... =] " + caller.msg(string) + return + + rooms_only = 'rooms' in self.switches + players_only = 'players' in self.switches + send_to_contents = 'contents' in self.switches + + # we check which command was used to force the switches + if self.cmdstring == '@remit': + rooms_only = True + elif self.cmdstring == '@pemit': + players_only = True + + if not self.rhs: + message = self.args + objnames = [caller.location.key] + else: + message = self.rhs + objnames = self.lhslist + + # send to all objects + for objname in objnames: + obj = caller.search(objname, global_search=True) + if not obj: + return + if rooms_only and not obj.location == None: + caller.msg("%s is not a room. Ignored." % objname) + continue + if players_only and not obj.has_player: + caller.msg("%s has no active player. Ignored." % objname) + continue + if has_perm(caller, obj, 'send_to'): + obj.msg(message) + if send_to_contents: + for content in obj.contents: + content.msg(message) + caller.msg("Emitted to %s and its contents." % objname) + else: + caller.msg("Emitted to %s." % objname) + else: + caller.msg("You are not allowed to send to %s." % objname) + + + +class CmdNewPassword(MuxCommand): + """ + @setpassword + + Usage: + @userpassword = + + Set a player's password. + """ + + key = "@userpassword" + permissions = "cmd:newpassword" + help_category = "Admin" + + def func(self): + "Implement the function." + + caller = self.caller + + if not self.rhs: + caller.msg("Usage: @userpassword = ") + return + + # the player search also matches 'me' etc. + character = caller.search("*%s" % self.lhs, global_search=True) + if not character: + return + player = character.player + player.user.set_password(self.rhs) + player.user.save() + caller.msg("%s - new password set to '%s'." % (player.name, self.rhs)) + if character != caller: + player.msg("%s has changed your password to '%s'." % (caller.name, self.rhs)) + + +class CmdPerm(MuxCommand): + """ + @perm - set permissions + + Usage: + @perm[/switch] [] = [] + @perm[/switch] [*] = [] + + Switches: + del : delete the given permission from . + list : list all permissions, or those set on + + Use * before the search string to add permissions to a player. + This command sets/clears individual permission strings on an object. + Use /list without any arguments to see all available permissions + or those defined on the / argument. + """ + key = "@perm" + aliases = "@setperm" + permissions = "cmd:perm" + help_category = "Admin" + + def func(self): + "Implement function" + + caller = self.caller + switches = self.switches + lhs, rhs = self.lhs, self.rhs + + if not self.args: + + if "list" not in switches: + string = "Usage: @setperm[/switch] [object = permission]\n" + string += " @setperm[/switch] [*player = permission]" + caller.msg(string) + return + else: + #just print all available permissions + string = "\nAll defined permission groups and keys (i.e. not locks):" + pgroups = list(PermissionGroup.objects.all()) + pgroups.sort(lambda x, y: cmp(x.key, y.key)) # sort by group key + + for pgroup in pgroups: + string += "\n\n - {w%s{n (%s):" % (pgroup.key, pgroup.desc) + string += "\n%s" % \ + utils.fill(", ".join(sorted(pgroup.group_permissions))) + caller.msg(string) + return + + # locate the object/player + obj = caller.search(lhs, global_search=True) + if not obj: + return + + pstring = "" + if utils.inherits_from(obj, settings.BASE_PLAYER_TYPECLASS): + pstring = " Player " + + if not rhs: + string = "Permission string on %s{w%s{n: " % (pstring, obj.key) + if not obj.permissions: + string += "" + else: + string += ", ".join(obj.permissions) + if pstring and obj.is_superuser: + string += "\n(... But this player is a SUPERUSER! " + string += "All access checked are passed automatically.)" + elif obj.player and obj.player.is_superuser: + string += "\n(... But this object's player is a SUPERUSER! " + string += "All access checked are passed automatically.)" + caller.msg(string) + return + + # we supplied an argument on the form obj = perm + + cstring = "" + tstring = "" + if 'del' in switches: + # delete the given permission(s) from object. + for perm in self.rhslist: + try: + index = obj.permissions.index(perm) + except ValueError: + cstring += "\nPermission '%s' was not defined on %s%s." % (perm, pstring, lhs) + continue + permissions = obj.permissions + del permissions[index] + obj.permissions = permissions + cstring += "\nPermission '%s' was removed from %s%s." % (perm, pstring, obj.name) + tstring += "\n%s revokes the permission '%s' from you." % (caller.name, perm) + else: + # As an extra check, we warn the user if they customize the + # permission string (which is okay, and is used by the lock system) + permissions = obj.permissions + for perm in self.rhslist: + + if perm in permissions: + cstring += "\nPermission '%s' is already defined on %s%s." % (rhs, pstring, obj.name) + else: + permissions.append(perm) + obj.permissions = permissions + cstring += "\nPermission '%s' given to %s%s." % (rhs, pstring, obj.name) + tstring += "\n%s granted you the permission '%s'." % (caller.name, rhs) + caller.msg(cstring.strip()) + if tstring: + obj.msg(tstring.strip()) + + +class CmdPuppet(MuxCommand): + """ + Switch control to an object + + Usage: + @puppet + + This will attempt to "become" a different character. Note that this command does not check so that + the target object has the appropriate cmdset. You cannot puppet a character that is already "taken". + """ + + key = "@puppet" + permissions = "cmd:puppet" + help_category = "Admin" + + def func(self): + """ + Simple puppet method (does not check permissions) + """ + caller = self.caller + if not self.args: + caller.msg("Usage: @puppet ") + return + + player = caller.player + new_character = caller.search(self.args) + if not new_character: + return + if not utils.inherits_from(new_character, settings.BASE_CHARACTER_TYPECLASS): + caller.msg("%s is not a Character." % self.args) + return + if player.swap_character(new_character): + new_character.msg("You now control %s." % new_character.name) + else: + caller.msg("You couldn't control %s." % new_character.name) + + +class CmdWall(MuxCommand): + """ + @wall + + Usage: + @wall + + Announces a message to all connected players. + """ + key = "@wall" + permissions = "cmd:wall" + help_category = "Admin" + + def func(self): + "Implements command" + if not self.args: + self.caller.msg("Usage: @wall ") + return + message = "%s shouts \"%s\"" % (self.caller.name, self.args) + sessionhandler.announce_all(message) diff --git a/game/gamesrc/commands/default/objmanip.py b/game/gamesrc/commands/default/building.py similarity index 81% rename from game/gamesrc/commands/default/objmanip.py rename to game/gamesrc/commands/default/building.py index ea2f20fca0..3062a09942 100644 --- a/game/gamesrc/commands/default/objmanip.py +++ b/game/gamesrc/commands/default/building.py @@ -1,6 +1,9 @@ """ -These commands typically are to do with building or modifying Objects. + +Building and world design commands + """ + from django.conf import settings from src.permissions.permissions import has_perm, has_perm_string from src.objects.models import ObjectDB, ObjAttribute @@ -101,62 +104,6 @@ class ObjManipCommand(MuxCommand): self.rhs_objattr = rhs_objattr -class CmdTeleport(MuxCommand): - """ - teleport - - Usage: - teleport/switch [ =] - - Switches: - quiet - don't inform the source and target - locations about the move. - - Teleports an object somewhere. If no object is - given we are teleporting ourselves. - """ - key = "teleport" - aliases = "tel" - permissions = "cmd:teleport" - help_category = "Building" - - def func(self): - "Performs the teleport" - - caller = self.caller - args = self.args - lhs, rhs = self.lhs, self.rhs - switches = self.switches - - if not args: - caller.msg("Usage: teleport[/switches] [ =] |home") - return - # The quiet switch suppresses leaving and arrival messages. - if "quiet" in switches: - tel_quietly = True - else: - tel_quietly = False - - if rhs: - obj_to_teleport = caller.search(lhs, global_search=True) - destination = caller.search(rhs, global_search=True) - else: - obj_to_teleport = caller - destination = caller.search(args, global_search=True) - if not obj_to_teleport: - caller.msg("Did not find object to teleport.") - return - if not destination: - caller.msg("Destination not found.") - return - if obj_to_teleport == destination: - caller.msg("You can't teleport an object inside of itself!") - return - # try the teleport - if obj_to_teleport.move_to(destination, quiet=tel_quietly, - emit_to_obj=caller): - caller.msg("Teleported.") - class CmdSetObjAlias(MuxCommand): """ Adding permanent aliases @@ -203,163 +150,74 @@ class CmdSetObjAlias(MuxCommand): obj.aliases = aliases caller.msg("Aliases for '%s' are now set to %s." % (obj.name, aliases)) -class CmdName(ObjManipCommand): + +class CmdCopy(ObjManipCommand): """ - cname - change the name and/or aliases of an object + @copy - copy objects - Usage: - @name obj = name;alias1;alias2 - - Rename an object to something new. - - """ - - key = "@name" - aliases = ["@rename"] - permissions = "cmd:rename" - help_category = "Building" - - def func(self): - "change the name" - - caller = self.caller - if not self.args: - string = "Usage: @name = [;alias;alias;...]" - caller.msg(string) - return - - if self.lhs_objs: - objname = self.lhs_objs[0]['name'] - obj = caller.search(objname) - if not obj: - return - if self.rhs_objs: - newname = self.rhs_objs[0]['name'] - aliases = self.rhs_objs[0]['aliases'] - else: - newname = self.rhs - aliases = None - if not newname and not aliases: - caller.msg("No names or aliases defined!") - return - # change the name and set aliases: - if newname: - obj.name = newname - if aliases: - obj.aliases = aliases - caller.msg("Object's name changed to '%s' %s." % (newname, ", ".join(aliases))) - -class CmdWipe(ObjManipCommand): - """ - @wipe - clears attributes - Usage: - @wipe [/attribute[/attribute...]] + @copy[/reset] [= new_name][;alias;alias..][:new_location] [,new_name2 ...] - Example: - @wipe box - @wipe box/colour + switch: + reset - make a 'clean' copy off the object, thus + removing any changes that might have been made to the original + since it was first created. - Wipes all of an object's attributes, or optionally only those - matching the given attribute-wildcard search string. + Create one or more copies of an object. If you don't supply any targets, one exact copy + of the original object will be created with the name *_copy. """ - key = "@wipe" - permissions = "cmd:wipe" + + key = "@copy" + permissions = "cmd:copy" help_category = "Building" - + def func(self): - """ - inp is the dict produced in ObjManipCommand.parse() - """ + "Uses ObjManipCommand.parse()" caller = self.caller - - if not self.args: - caller.msg("Usage: @wipe [/attribute/attribute...]") - return - - # get the attributes set by our custom parser - objname = self.lhs_objattr[0]['name'] - attrs = self.lhs_objattr[0]['attrs'] - - obj = caller.search(objname) - if not obj: + args = self.args + if not args: + caller.msg("Usage: @copy [=new_name[;alias;alias..]][:new_location]") return - if not attrs: - # wipe everything - for attr in obj.get_all_attributes(): - attr.delete() - string = "Wiped all attributes on %s." % obj.name - else: - for attrname in attrs: - obj.attr(attrname, delete=True ) - string = "Wiped attributes %s on %s." - string = string % (",".join(attrs), obj.name) - caller.msg(string) - - -class CmdSetAttribute(ObjManipCommand): - """ - @set - set attributes - - Usage: - @set / = - @set / = - @set / - - Sets attributes on objects. The second form clears - a previously set attribute while the last form - inspects the current value of the attribute - (if any). - """ - - key = "@set" - permissions = "cmd:set" - help_category = "Building" - def func(self): - "Implement the set attribute - a limited form of @py." - - caller = self.caller - if not self.args: - caller.msg("Usage: @set obj/attr = value. Use empty value to clear.") - return - - # get values prepared by the parser - value = self.rhs - objname = self.lhs_objattr[0]['name'] - attrs = self.lhs_objattr[0]['attrs'] - - obj = caller.search(objname) - if not obj: - return - - string = "" - if not value: - if self.rhs == None: - # no = means we inspect the attribute(s) - for attr in attrs: - value = obj.attr(attr) - if value: - string += "\n%s.db.%s = %s" % (obj.name, attr, value) - else: - string += "\n%s has no attribute '%s'." % (obj.name, attr) - else: - # deleting the attribute(s) - for attr in attrs: - if obj.attr(attr): - obj.attr(attr, delete=True) - string += "\nAttribute %s.db.%s deleted." % (obj.name, attr) - else: - string += "\n%s has no attribute '%s'." % (obj.name, attr) + if not self.rhs: + # this has no target =, so an identical new object is created. + from_obj_name = self.args + from_obj = caller.search(from_obj_name) + if not from_obj: + return + to_obj_name = "%s_copy" % from_obj_name + to_obj_aliases = from_obj.aliases + to_obj_location = from_obj.location + copiedobj = ObjectDB.objects.copy_object(from_obj, to_obj_name, + to_obj_location, to_obj_aliases) + if copiedobj: + string = "Identical copy of %s, named '%s' was created." % (to_obj_name, to_obj_name) + else: + string = "There was an error copying %s." else: - # setting attribute(s) - for attr in attrs: - obj.attr(attr, value) - string += "\nAttribute %s.%s created." % (obj.name, attr) - # send feedback - caller.msg(string.strip('\n')) + # we have specified =. This might mean many object targets + from_obj_name = self.lhs_objs[0]['name'] + from_obj = caller.search(from_obj_name) + if not from_obj: + return + for objdef in self.lhs_objs: + # loop through all possible copy-to targets + to_obj_name = objdef['name'] + to_obj_aliases = objdef['aliases'] + to_obj_location = objdef['option'] + copiedobj = ObjectDB.objects.copy_object(from_obj, to_obj_name, + to_obj_location, to_obj_aliases) + if copiedobj: + string = "Copied %s to '%s' (aliases: %s)." % (from_obj_name, to_obj_name, + to_obj_aliases) + else: + string = "There was an error copying %s to '%s'." % (from_obj_name, + to_obj_name) + # we are done, echo to user + caller.msg(string) +# NOT YET INCLUDED IN SET. class CmdCpAttr(MuxCommand): """ @cpattr - copy attributes @@ -435,110 +293,8 @@ class CmdCpAttr(MuxCommand): string += "\nCopied %s.%s -> %s.%s." % (from_obj.name, from_attr, to_obj_name, to_attr) caller.msg(string) - -class CmdMvAttr(ObjManipCommand): - """ - @mvattr - move attributes - Usage: - @mvattr /attr[/attr/attr...] = [/attr/attr/...] - Moves attributes around. If the target object's attribute names are given, - the source attributes will be moved into those attributes instead. The - old attribute(s) will be deleted from the source object (unless source - and target are the same, in which case this is like a copy operation) - """ - key = "@mvattr" - permissions = "cmd:mvattr" - help_category = "Building" - - def func(self): - "We use the parsed values from ObjManipCommand.parse()." - - caller = self.caller - - if not self.lhs or not self.rhs: - caller.msg("Usage: @mvattr /attr[/attr/..] = [/attr/attr..]") - return - - from_obj_name = self.lhs_objattr[0]['name'] - from_obj_attrs = self.lhs_objattr[0]['attrs'] - to_obj_name = self.rhs_objattr[0]['name'] - to_obj_attrs = self.rhs_objattr[0]['name'] - - # find from-object - from_obj = caller.search(from_obj_name) - if not from_obj: - return - #find to-object - to_obj = caller.search_for_object(to_obj_name) - if not to_obj: - return - - # if we copy on the same object, we have to - # be more careful. - same_object = to_obj == from_obj - - #do the moving - string = "" - for inum, from_attr in enumerate(from_obj_attrs): - from_value = from_obj.attr(from_attr) - if not from_value: - string += "\nAttribute '%s' not found on source object %s." - string = string % (from_attr, from_obj.name) - else: - try: - to_attr = to_obj_attrs[inum] - except KeyError: - # too few attributes on the target, so we add the - # source attrname instead - if same_object: - # we can't do that on the same object though, - # it would be just copying to itself. - string += "\nToo few attribute names on target, and " - string += "can't copy same-named attribute to itself." - continue - to_attr = from_attr - # Do the move - to_obj.attr(to_attr, from_value) - from_obj.attr(from_attr, delete=True) - string += "\nMoved %s.%s -> %s.%s." % (from_obj_name, from_attr, - to_obj_name, to_attr) - caller.msg(string) - -class CmdFind(MuxCommand): - """ - find - - Usage: - find - - Searches for an object of a particular name. - """ - - key = "find" - permissions = "cmd:find" - help_category = "Building" - - def func(self): - "Search functionality" - caller = self.caller - arglist = self.arglist - - if not arglist: - caller.msg("Usage: @find ")# [,low [,high]]") - return - searchstring = arglist[0] - if len(arglist) > 1: - low = arglist[1] - if len(arglist) > 2: - high = arglist[2] - #TODO: Implement efficient db search with limits - result = caller.search(searchstring, global_search=True) - if not result: - return - string = "%s(#%s) - %s" % (result.name, result.id, result) - caller.msg(string) class CmdCreate(ObjManipCommand): """ @@ -615,72 +371,515 @@ class CmdCreate(ObjManipCommand): caller.msg(string) -class CmdCopy(ObjManipCommand): +#TODO: make @debug more clever with arbitrary hooks? +class CmdDebug(MuxCommand): """ - @copy - copy objects - + Debug game entities + Usage: - @copy[/reset] [= new_name][;alias;alias..][:new_location] [,new_name2 ...] + @debug[/switch] - switch: - reset - make a 'clean' copy off the object, thus - removing any changes that might have been made to the original - since it was first created. + Switches: + obj - debug an object + script - debug a script + + Examples: + @debug/script game.gamesrc.scripts.myscript.MyScript + @debug/script myscript.MyScript + @debug/obj examples.red_button.RedButton + + This command helps when debugging the codes of objects and scripts. + It creates the given object and runs tests on its hooks. You can + supply both full paths (starting from the evennia base directory), + otherwise the system will start from the defined root directory + for scripts and objects respectively (defined in settings file). - Create one or more copies of an object. If you don't supply any targets, one exact copy - of the original object will be created with the name *_copy. """ - key = "@copy" - permissions = "cmd:copy" + key = "@debug" + permissions = "cmd:debug" help_category = "Building" def func(self): - "Uses ObjManipCommand.parse()" + "Running the debug" + + if not self.args or not self.switches: + self.caller.msg("Usage: @debug[/obj][/script] ") + return + + path = self.args + + if 'obj' in self.switches or 'object' in self.switches: + # analyze path. If it starts at the evennia basedir, + # (i.e. starts with game or src) we let it be, otherwise we + # add a base path as defined in settings + if path and not (path.startswith('src.') or + path.startswith('game.')): + path = "%s.%s" % (settings.BASE_TYPECLASS_PATH, + path) + + # create and debug the object + self.caller.msg(debug.debug_object(path, self.caller)) + self.caller.msg(debug.debug_object_scripts(path, self.caller)) + + elif 'script' in self.switches: + # analyze path. If it starts at the evennia basedir, + # (i.e. starts with game or src) we let it be, otherwise we + # add a base path as defined in settings + if path and not (path.startswith('src.') or + path.startswith('game.')): + path = "%s.%s" % (settings.BASE_SCRIPT_PATH, + path) + + self.caller.msg(debug.debug_syntax_script(path)) + + +class CmdDesc(MuxCommand): + """ + @desc - describe an object or room + + Usage: + @desc [ =] >description> + + Setts the "desc" attribute on an + object. If an object is not given, + describe the current room. + """ + key = "@desc" + aliases = "@describe" + permissions = "cmd:desc" + help_category = "Building" + + def func(self): + "Define command" caller = self.caller - args = self.args - if not args: - caller.msg("Usage: @copy [=new_name[;alias;alias..]][:new_location]") + if not self.args: + caller.msg("Usage: @desc [ =] >description>") + return + + if self.rhs: + # We have an = + obj = caller.search(self.lhs) + if not obj: + return + desc = self.rhs + else: + obj = caller + desc = self.args + # storing the description + obj.db.desc = desc + caller.msg("The description was set on %s." % obj.key) + + +class CmdDestroy(MuxCommand): + """ + @destroy - remove objects from the game + + Usage: + @destroy[/] obj [,obj2, obj3, ...] + @delete '' + + switches: + override - The @destroy command will usually avoid accidentally destroying + player objects. This switch overrides this safety. + + Destroys one or many objects. + """ + + key = "@destroy" + aliases = "@delete" + permissions = "cmd:destroy" + help_category = "Building" + + def func(self): + "Implements the command." + + caller = self.caller + + if not self.args or not self.lhslist: + caller.msg("Usage: @destroy[/switches] obj [,obj2, obj3, ...]") + return + + string = "" + for objname in self.lhslist: + obj = caller.search(objname) + if not obj: + continue + objname = obj.name + if obj.player and not 'override' in self.switches: + string = "Object %s is a player object. Use /override to delete anyway." % objname + continue + if not has_perm(caller, obj, 'create'): + string = "You don't have permission to delete %s." % objname + continue + # do the deletion + okay = obj.delete() + if not okay: + string = "ERROR: %s NOT deleted, probably because at_obj_delete() returned False." % objname + else: + string = "%s was deleted." % objname + if string: + caller.msg(string.strip()) + + +class CmdDig(ObjManipCommand): + """ + @dig - build and connect new rooms to the current one + + Usage: + @dig[/switches] roomname[;alias;alias...][:typeclass] + [= exit_to_there[;alias][:typeclass]] + [, exit_to_here[;alias][:typeclass]] + + Switches: + teleport - move yourself to the new room + + Example: + @dig kitchen = north; n, south;s : big_scary_door + + This command is a convenient way to build rooms quickly; it creates the new room and you can optionally + set up exits back and forth between your current room and the new one. You can add as many aliases as you + like to the name of the room and the exits in question; an example would be 'north;no;n'. + """ + key = "@dig" + permissions = "cmd:dig" + help_category = "Building" + + def func(self): + "Do the digging. Inherits variables from ObjManipCommand.parse()" + + caller = self.caller + + if not self.lhs: + string = "Usage: @dig[/teleport] roomname[;alias;alias...][:parent] [= exit_there" + string += "[;alias;alias..][:parent]] " + string += "[, exit_back_here[;alias;alias..][:parent]]" + caller.msg(string) + return + + room = self.lhs_objs[0] + + if not room["name"]: + caller.msg("You must supply a new room name.") + return + location = caller.location + + # Create the new room + typeclass = room['option'] + if not typeclass: + typeclass = settings.BASE_ROOM_TYPECLASS + # analyze typeclass. If it starts at the evennia basedir, + # (i.e. starts with game or src) we let it be, otherwise we + # add a base path as defined in settings + if typeclass and not (typeclass.startswith('src.') or + typeclass.startswith('game.')): + typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH, + typeclass) + + new_room = create.create_object(typeclass, room["name"], + aliases=room["aliases"]) + room_string = "Created room '%s' of type %s." % (new_room.name, typeclass) + + exit_to_string = "" + exit_back_string = "" + + if self.rhs_objs: + to_exit = self.rhs_objs[0] + if not to_exit["name"]: + exit_to_string = \ + "\n\rYou didn't give a name for the exit to the new room." + elif not location: + exit_to_string = \ + "\n\rYou cannot create an exit from a None-location." + else: + # Build the exit to the new room from the current one + typeclass = to_exit["option"] + if not typeclass: + typeclass = settings.BASE_EXIT_TYPECLASS + # analyze typeclass. If it starts at the evennia basedir, + # (i.e. starts with game or src) we let it be, otherwise we + # add a base path as defined in settings + if typeclass and not (typeclass.startswith('src.') or + typeclass.startswith('game.')): + typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH, + typeclass) + new_to_exit = create.create_object(typeclass, to_exit["name"], + location, + aliases=to_exit["aliases"]) + new_to_exit.db._destination = new_room + exit_to_string = "\n\rCreated new Exit to new room: %s (aliases: %s)." + exit_to_string = exit_to_string % (new_to_exit.name, + new_to_exit.aliases) + + if len(self.rhs_objs) > 1: + # Building the exit back to the current room + back_exit = self.rhs_objs[1] + if not back_exit["name"]: + exit_back_string = \ + "\n\rYou didn't give a name for the exit back here." + elif not location: + exit_back_string = \ + "\n\rYou cannot create an exit back to a None-location." + else: + typeclass = back_exit["option"] + if not typeclass: + typeclass = settings.BASE_EXIT_TYPECLASS + # analyze typeclass. If it starts at the evennia basedir, + # (i.e. starts with game or src) we let it be, otherwise we + # add a base path as defined in settings + if typeclass and not (typeclass.startswith('src.') or + typeclass.startswith('game.')): + typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH, + typeclass) + new_back_exit = create.create_object(typeclass, back_exit["name"], + new_room, + aliases=back_exit["aliases"]) + new_back_exit.db._destination = location + exit_back_string = "\n\rExit back from new room: %s (aliases: %s)." + exit_back_string = exit_back_string % (new_back_exit.name, + new_back_exit.aliases) + caller.msg("%s%s%s" % (room_string, exit_to_string, exit_back_string)) + if new_room and 'teleport' in self.switches: + caller.move_to(new_room) + + +class CmdLink(MuxCommand): + """ + @link - connect objects + + Usage: + @link[/switches] = + @link[/switches] = + @link[/switches] + + Switches: + twoway - this is only useful when both + and are Exits. If so, a link back + from to will also be created. + + + If is an exit, set its destination. For all other object types, this + command sets the object's Home. + The second form sets the destination/home to None and the third form inspects + the current value of destination/home on . + """ + + key = "@link" + permissions = "cmd:link" + help_category = "Building" + + def func(self): + "Perform the link" + caller = self.caller + + if not self.args: + caller.msg("Usage: @link[/twoway] = ") return - if not self.rhs: - # this has no target =, so an identical new object is created. - from_obj_name = self.args - from_obj = caller.search(from_obj_name) - if not from_obj: - return - to_obj_name = "%s_copy" % from_obj_name - to_obj_aliases = from_obj.aliases - to_obj_location = from_obj.location - copiedobj = ObjectDB.objects.copy_object(from_obj, to_obj_name, - to_obj_location, to_obj_aliases) - if copiedobj: - string = "Identical copy of %s, named '%s' was created." % (to_obj_name, to_obj_name) - else: - string = "There was an error copying %s." - else: - # we have specified =. This might mean many object targets - from_obj_name = self.lhs_objs[0]['name'] - from_obj = caller.search(from_obj_name) - if not from_obj: - return - for objdef in self.lhs_objs: - # loop through all possible copy-to targets - to_obj_name = objdef['name'] - to_obj_aliases = objdef['aliases'] - to_obj_location = objdef['option'] - copiedobj = ObjectDB.objects.copy_object(from_obj, to_obj_name, - to_obj_location, to_obj_aliases) - if copiedobj: - string = "Copied %s to '%s' (aliases: %s)." % (from_obj_name, to_obj_name, - to_obj_aliases) + object_name = self.lhs + + # get object + obj = caller.search(object_name, global_search=True) + if not obj: + return + + string = "" + if self.rhs: + # this means a target name was given + target = caller.search(self.rhs, global_search=True) + if not target: + return + # if obj is an exit (has db attribute _destination), + # set that, otherwise set home. + if obj.db._destination: + obj.db._destination = target + if "twoway" in self.switches: + if target.db._destination: + target.db._destination = obj + string = "Link created %s <-> %s (two-way)." % (obj.name, target.name) + else: + string = "Cannot create two-way link to non-exit." + string += " Link created %s -> %s (one way)." % (obj.name, target.name) else: - string = "There was an error copying %s to '%s'." % (from_obj_name, - to_obj_name) - # we are done, echo to user + string = "Link created %s -> %s (one way)." % (obj.name, target.name) + else: + # obj is not an exit (has not attribute _destination), + # so set home instead + obj.home = target + string = "Set %s's home to %s." % (obj.name, target.name) + + elif self.rhs == None: + # this means that no = was given (otherwise rhs + # would have been an empty string). So we inspect + # the home/destination on object + dest = obj.db._destination + if dest: + "%s is an exit to %s." % (obj.name, dest.name) + else: + string = "%s has home %s." % (obj.name, obj.home) + else: + # We gave the command @link 'obj = ' which means we want to + # clear _destination or set home to None. + if obj.db._destination: + obj.db._destination = "None" # it can't be None, or _destination would + # be deleted and obj cease being an exit! + string = "Exit %s no longer links anywhere." % obj.name + else: + obj.home = None + string = "%s no longer has a home." % obj.name + # give feedback caller.msg(string) + + +class CmdListCmdSets(MuxCommand): + """ + list command sets on an object + + Usage: + @cmdsets [obj] + + This displays all cmdsets assigned + to a user. Defaults to yourself. + """ + key = "@cmdsets" + aliases = "@listcmsets" + permissions = "cmd:listcmdsets" + help_category = "Building" + def func(self): + "list the cmdsets" + + caller = self.caller + if self.arglist: + obj = caller.search(self.arglist[0]) + if not obj: + return + else: + obj = caller + string = "%s" % obj.cmdset + caller.msg(string) + + +class CmdMvAttr(ObjManipCommand): + """ + @mvattr - move attributes + + Usage: + @mvattr /attr[/attr/attr...] = [/attr/attr/...] + + Moves attributes around. If the target object's attribute names are given, + the source attributes will be moved into those attributes instead. The + old attribute(s) will be deleted from the source object (unless source + and target are the same, in which case this is like a copy operation) + """ + key = "@mvattr" + permissions = "cmd:mvattr" + help_category = "Building" + + def func(self): + "We use the parsed values from ObjManipCommand.parse()." + + caller = self.caller + + if not self.lhs or not self.rhs: + caller.msg("Usage: @mvattr /attr[/attr/..] = [/attr/attr..]") + return + + from_obj_name = self.lhs_objattr[0]['name'] + from_obj_attrs = self.lhs_objattr[0]['attrs'] + to_obj_name = self.rhs_objattr[0]['name'] + to_obj_attrs = self.rhs_objattr[0]['name'] + + # find from-object + from_obj = caller.search(from_obj_name) + if not from_obj: + return + #find to-object + to_obj = caller.search_for_object(to_obj_name) + if not to_obj: + return + + # if we copy on the same object, we have to + # be more careful. + same_object = to_obj == from_obj + + #do the moving + string = "" + for inum, from_attr in enumerate(from_obj_attrs): + from_value = from_obj.attr(from_attr) + if not from_value: + string += "\nAttribute '%s' not found on source object %s." + string = string % (from_attr, from_obj.name) + else: + try: + to_attr = to_obj_attrs[inum] + except KeyError: + # too few attributes on the target, so we add the + # source attrname instead + if same_object: + # we can't do that on the same object though, + # it would be just copying to itself. + string += "\nToo few attribute names on target, and " + string += "can't copy same-named attribute to itself." + continue + to_attr = from_attr + # Do the move + to_obj.attr(to_attr, from_value) + from_obj.attr(from_attr, delete=True) + string += "\nMoved %s.%s -> %s.%s." % (from_obj_name, from_attr, + to_obj_name, to_attr) + caller.msg(string) + + + +class CmdName(ObjManipCommand): + """ + cname - change the name and/or aliases of an object + + Usage: + @name obj = name;alias1;alias2 + + Rename an object to something new. + + """ + + key = "@name" + aliases = ["@rename"] + permissions = "cmd:rename" + help_category = "Building" + + def func(self): + "change the name" + + caller = self.caller + if not self.args: + string = "Usage: @name = [;alias;alias;...]" + caller.msg(string) + return + + if self.lhs_objs: + objname = self.lhs_objs[0]['name'] + obj = caller.search(objname) + if not obj: + return + if self.rhs_objs: + newname = self.rhs_objs[0]['name'] + aliases = self.rhs_objs[0]['aliases'] + else: + newname = self.rhs + aliases = None + if not newname and not aliases: + caller.msg("No names or aliases defined!") + return + # change the name and set aliases: + if newname: + obj.name = newname + if aliases: + obj.aliases = aliases + caller.msg("Object's name changed to '%s' %s." % (newname, ", ".join(aliases))) + + class CmdOpen(ObjManipCommand): """ @open - create new exit @@ -819,229 +1018,127 @@ class CmdOpen(ObjManipCommand): self.create_exit(back_exit_name, destination, location, back_exit_aliases, back_exit_typeclass) -## -## def cmd_chown(command): -## """ -## @chown - change ownerships -## Usage: -## @chown = - -## Changes the ownership of an object. The new owner specified must be a -## player object. -## """ -## caller = command.caller - -## if not command.command_argument: -## caller.msg("Usage: @chown = ") -## return - -## eq_args = command.command_argument.split('=', 1) -## target_name = eq_args[0] -## owner_name = eq_args[1] - -## if len(target_name) == 0: -## caller.msg("Change the ownership of what?") -## return - -## if len(eq_args) > 1: -## target_obj = caller.search_for_object(target_name) -## # Use search_for_object to handle duplicate/nonexistant results. -## if not target_obj: -## return - -## if not caller.controls_other(target_obj) and not caller.has_perm("objects.admin_ownership"): -## caller.msg(defines_global.NOCONTROL_MSG) -## return - -## owner_obj = caller.search_for_object(owner_name) -## # Use search_for_object to handle duplicate/nonexistant results. -## if not owner_obj: -## return -## if not owner_obj.is_player(): -## caller.msg("Only players may own objects.") -## return -## if target_obj.is_player(): -## caller.msg("You may not change the ownership of player objects.") -## return - -## target_obj.set_owner(owner_obj) -## caller.msg("%s now owns %s." % (owner_obj, target_obj)) -## else: -## # We haven't provided a target. -## caller.msg("Who should be the new owner of the object?") -## return -## GLOBAL_CMD_TABLE.add_command("@chown", cmd_chown, priv_tuple=("objects.modify_attributes", -## "objects.admin_ownership"), -## help_category="Building" ) - -class CmdLink(MuxCommand): +class CmdSetAttribute(ObjManipCommand): """ - @link - connect objects + @set - set attributes Usage: - @link[/switches] = - @link[/switches] = - @link[/switches] - - Switches: - twoway - this is only useful when both - and are Exits. If so, a link back - from to will also be created. - - - If is an exit, set its destination. For all other object types, this - command sets the object's Home. - The second form sets the destination/home to None and the third form inspects - the current value of destination/home on . + @set / = + @set / = + @set / + + Sets attributes on objects. The second form clears + a previously set attribute while the last form + inspects the current value of the attribute + (if any). """ - key = "@link" - permissions = "cmd:link" - - def func(self): - "Perform the link" - caller = self.caller - - if not self.args: - caller.msg("Usage: @link[/twoway] = ") - return + key = "@set" + permissions = "cmd:set" + help_category = "Building" - object_name = self.lhs + def func(self): + "Implement the set attribute - a limited form of @py." - # get object - obj = caller.search(object_name, global_search=True) + caller = self.caller + if not self.args: + caller.msg("Usage: @set obj/attr = value. Use empty value to clear.") + return + + # get values prepared by the parser + value = self.rhs + objname = self.lhs_objattr[0]['name'] + attrs = self.lhs_objattr[0]['attrs'] + + obj = caller.search(objname) if not obj: - return + return string = "" - if self.rhs: - # this means a target name was given - target = caller.search(self.rhs, global_search=True) - if not target: - return - # if obj is an exit (has db attribute _destination), - # set that, otherwise set home. - if obj.db._destination: - obj.db._destination = target - if "twoway" in self.switches: - if target.db._destination: - target.db._destination = obj - string = "Link created %s <-> %s (two-way)." % (obj.name, target.name) + if not value: + if self.rhs == None: + # no = means we inspect the attribute(s) + for attr in attrs: + value = obj.attr(attr) + if value: + string += "\n%s.db.%s = %s" % (obj.name, attr, value) else: - string = "Cannot create two-way link to non-exit." - string += " Link created %s -> %s (one way)." % (obj.name, target.name) - else: - string = "Link created %s -> %s (one way)." % (obj.name, target.name) - else: - # obj is not an exit (has not attribute _destination), - # so set home instead - obj.home = target - string = "Set %s's home to %s." % (obj.name, target.name) - - elif self.rhs == None: - # this means that no = was given (otherwise rhs - # would have been an empty string). So we inspect - # the home/destination on object - dest = obj.db._destination - if dest: - "%s is an exit to %s." % (obj.name, dest.name) - else: - string = "%s has home %s." % (obj.name, obj.home) + string += "\n%s has no attribute '%s'." % (obj.name, attr) + else: + # deleting the attribute(s) + for attr in attrs: + if obj.attr(attr): + obj.attr(attr, delete=True) + string += "\nAttribute %s.db.%s deleted." % (obj.name, attr) + else: + string += "\n%s has no attribute '%s'." % (obj.name, attr) else: - # We gave the command @link 'obj = ' which means we want to - # clear _destination or set home to None. - if obj.db._destination: - obj.db._destination = "None" # it can't be None, or _destination would - # be deleted and obj cease being an exit! - string = "Exit %s no longer links anywhere." % obj.name - else: - obj.home = None - string = "%s no longer has a home." % obj.name - # give feedback - caller.msg(string) - -class CmdUnLink(CmdLink): + # setting attribute(s) + for attr in attrs: + obj.attr(attr, value) + string += "\nAttribute %s.%s created." % (obj.name, attr) + # send feedback + caller.msg(string.strip('\n')) + + +class CmdTypeclass(MuxCommand): """ - @unlink - unconnect objects + @typeclass - set object typeclass - Usage: - @unlink + Usage: + @typclass[/switch] [= ] + @type '' + @parent '' - Unlinks an object, for example an exit, disconnecting - it from whatever it was connected to. - """ - # this is just a child of CmdLink - - key = "@unlink" - permissions = "cmd:unlink" - help_key = "Building" - - def func(self): - """ - All we need to do here is to set the right command - and call func in CmdLink - """ - - caller = self.caller - - if not self.args: - caller.msg("Usage: @unlink ") - return - - # This mimics '@link = ' which is the same as @unlink - self.rhs = "" - - # call the @link functionality - super(CmdUnLink, self).func() - - -class CmdDig(ObjManipCommand): - """ - @dig - build and connect new rooms to the current one - - Usage: - @dig[/switches] roomname[;alias;alias...][:typeclass] - [= exit_to_there[;alias][:typeclass]] - [, exit_to_here[;alias][:typeclass]] - - Switches: - teleport - move yourself to the new room + Switch: + reset - clean out *all* the attributes on the object - + basically making this a new clean object. Example: - @dig kitchen = north; n, south;s : big_scary_door + @type button = examples.red_button.RedButton + + Sets an object's typeclass. The typeclass must be identified + by its location using python dot-notation pointing to the correct + module and class. If no typeclass is given (or a wrong typeclass + is given), the object will be set to the default typeclass. + The location of the typeclass module is searched from + the default typeclass directory, as defined in the server settings. - This command is a convenient way to build rooms quickly; it creates the new room and you can optionally - set up exits back and forth between your current room and the new one. You can add as many aliases as you - like to the name of the room and the exits in question; an example would be 'north;no;n'. """ - key = "@dig" - permissions = "cmd:dig" + + key = "@typeclass" + aliases = "@type, @parent" + permissions = "cmd:typeclass" help_category = "Building" def func(self): - "Do the digging. Inherits variables from ObjManipCommand.parse()" - + "Implements command" + caller = self.caller - if not self.lhs: - string = "Usage: @dig[/teleport] roomname[;alias;alias...][:parent] [= exit_there" - string += "[;alias;alias..][:parent]] " - string += "[, exit_back_here[;alias;alias..][:parent]]" + if not self.args: + caller.msg("Usage: @type [= 1: - # Building the exit back to the current room - back_exit = self.rhs_objs[1] - if not back_exit["name"]: - exit_back_string = \ - "\n\rYou didn't give a name for the exit back here." - elif not location: - exit_back_string = \ - "\n\rYou cannot create an exit back to a None-location." - else: - typeclass = back_exit["option"] - if not typeclass: - typeclass = settings.BASE_EXIT_TYPECLASS - # analyze typeclass. If it starts at the evennia basedir, - # (i.e. starts with game or src) we let it be, otherwise we - # add a base path as defined in settings - if typeclass and not (typeclass.startswith('src.') or - typeclass.startswith('game.')): - typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH, - typeclass) - new_back_exit = create.create_object(typeclass, back_exit["name"], - new_room, - aliases=back_exit["aliases"]) - new_back_exit.db._destination = location - exit_back_string = "\n\rExit back from new room: %s (aliases: %s)." - exit_back_string = exit_back_string % (new_back_exit.name, - new_back_exit.aliases) - caller.msg("%s%s%s" % (room_string, exit_to_string, exit_back_string)) - if new_room and 'teleport' in self.switches: - caller.move_to(new_room) - -class CmdDesc(MuxCommand): - """ - @desc - describe an object or room - - Usage: - @desc [ =] >description> - - Setts the "desc" attribute on an - object. If an object is not given, - describe the current room. - """ - key = "@desc" - aliases = "@describe" - permissions = "cmd:desc" - help_category = "Building" - - def func(self): - "Define command" - - caller = self.caller - if not self.args: - caller.msg("Usage: @desc [ =] >description>") + if not has_perm(caller, obj, 'change_typeclass'): + caller.msg("You are not allowed to do that.") return - if self.rhs: - # We have an = - obj = caller.search(self.lhs) - if not obj: - return - desc = self.rhs - else: - obj = caller - desc = self.args - # storing the description - obj.db.desc = desc - caller.msg("The description was set on %s." % obj.key) + if not hasattr(obj, 'swap_typeclass') or not hasattr(obj, 'typeclass'): + caller.msg("This object cannot have a type at all!") + return + + reset = "reset" in self.switches + + old_typeclass = obj.typeclass + obj.swap_typeclass(typeclass, clean_attributes=reset) + new_typeclass = obj.typeclass + + string = "%s's type is now %s (instead of %s).\n\r" % (obj.name, + new_typeclass, + old_typeclass) + if reset: + string += "All attributes where reset." + else: + string += "Note that the new class type could have overwritten " + string += "same-named attributes on the existing object." + caller.msg(string) -class CmdDestroy(MuxCommand): +class CmdWipe(ObjManipCommand): """ - @destroy - remove objects from the game - - Usage: - @destroy[/] obj [,obj2, obj3, ...] - @delete '' + @wipe - clears attributes - switches: - override - The @destroy command will usually avoid accidentally destroying - player objects. This switch overrides this safety. + Usage: + @wipe [/attribute[/attribute...]] - Destroys one or many objects. + Example: + @wipe box + @wipe box/colour + + Wipes all of an object's attributes, or optionally only those + matching the given attribute-wildcard search string. """ - - key = "@destroy" - aliases = "@delete" - permissions = "cmd:destroy" + key = "@wipe" + permissions = "cmd:wipe" help_category = "Building" - + def func(self): - "Implements the command." + """ + inp is the dict produced in ObjManipCommand.parse() + """ caller = self.caller - if not self.args or not self.lhslist: - caller.msg("Usage: @destroy[/switches] obj [,obj2, obj3, ...]") - return + if not self.args: + caller.msg("Usage: @wipe [/attribute/attribute...]") + return - string = "" - for objname in self.lhslist: - obj = caller.search(objname) - if not obj: - continue - objname = obj.name - if obj.player and not 'override' in self.switches: - string = "Object %s is a player object. Use /override to delete anyway." % objname - continue - if not has_perm(caller, obj, 'create'): - string = "You don't have permission to delete %s." % objname - continue - # do the deletion - okay = obj.delete() - if not okay: - string = "ERROR: %s NOT deleted, probably because at_obj_delete() returned False." % objname - else: - string = "%s was deleted." % objname - if string: - caller.msg(string.strip()) - - -#NOT VALID IN NEW SYSTEM! -## def cmd_lock(command): -## """ -## @lock - limit use of objects - -## Usage: -## @lock[/switch] [:type] [= [,key2,key3,...]] - -## Switches: -## add - add a lock (default) from object -## del - remove a lock from object -## list - view all locks on object (default) -## type: -## DefaultLock - the default lock type (default) -## UseLock - prevents usage of objects' commands -## EnterLock - blocking objects from entering the object - -## Locks an object for everyone except those matching the keys. -## The keys can be of the following types (and searched in this order): -## - a user #dbref (#2, #45 etc) -## - a Group name (Builder, Immortal etc, case sensitive) -## - a Permission string (genperms.get, etc) -## - a Function():return_value pair. (ex: alliance():Red). The -## function() is called on the locked object (if it exists) and -## if its return value matches the Key is passed. If no -## return_value is given, matches against True. -## - an Attribute:return_value pair (ex: key:yellow_key). The -## Attribute is the name of an attribute defined on the locked -## object. If this attribute has a value matching return_value, -## the lock is passed. If no return_value is given, -## attributes will be searched, requiring a True -## value. + # get the attributes set by our custom parser + objname = self.lhs_objattr[0]['name'] + attrs = self.lhs_objattr[0]['attrs'] -## If no keys at all are given, the object is locked for everyone. -## When the lock blocks a user, you may customize which error is given by -## storing error messages in an attribute. For DefaultLocks, UseLocks and -## EnterLocks, these attributes are called lock_msg, use_lock_msg and -## enter_lock_msg respectively. - -## [[lock_types]] - -## Lock types: - -## Name: Affects: Effect: -## ----------------------------------------------------------------------- -## DefaultLock: Exits: controls who may traverse the exit to -## its destination. -## Rooms: controls whether the player sees a failure -## message after the room description when -## looking at the room. -## Players/Things: controls who may 'get' the object. - -## UseLock: All but Exits: controls who may use commands defined on -## the locked object. - -## EnterLock: Players/Things: controls who may enter/teleport into -## the object. -## VisibleLock: Players/Things: controls if the object is visible to -## someone using the look command. - -## Fail messages echoed to the player are stored in the attributes 'lock_msg', -## 'use_lock_msg', 'enter_lock_msg' and 'visible_lock_msg' on the locked object -## in question. If no such message is stored, a default will be used (or none at -## all in some cases). -## """ - -## caller = command.caller -## arg = command.command_argument -## switches = command.command_switches - -## if not arg: -## caller.msg("Usage: @lock[/switch] [:type] [= [,key2,key3,...]]") -## return -## keys = "" -## #deal with all possible arguments. -## try: -## lside, keys = arg.split("=",1) -## except ValueError: -## lside = arg -## lside, keys = lside.strip(), keys.strip() -## try: -## obj_name, ltype = lside.split(":",1) -## except: -## obj_name = lside -## ltype = "DefaultLock" -## obj_name, ltype = obj_name.strip(), ltype.strip() - -## if ltype not in ["DefaultLock","UseLock","EnterLock","VisibleLock"]: -## caller.msg("Lock type '%s' not recognized." % ltype) -## return - -## obj = caller.search_for_object(obj_name) -## if not obj: -## return - -## obj_locks = obj.LOCKS - -## if "list" in switches: -## if not obj_locks: -## s = "There are no locks on %s." % obj.name -## else: -## s = "Locks on %s:" % obj.name -## s += obj_locks.show() -## caller.msg(s) -## return - -## # we are trying to change things. Check permissions. -## if not caller.controls_other(obj): -## caller.msg(defines_global.NOCONTROL_MSG) -## return - -## if "del" in switches: -## # clear a lock -## if obj_locks: -## if not obj_locks.has_type(ltype): -## caller.msg("No %s set on this object." % ltype) -## else: -## obj_locks.del_type(ltype) -## obj.LOCKS = obj_locks -## caller.msg("Cleared lock %s on %s." % (ltype, obj.name)) -## else: -## caller.msg("No %s set on this object." % ltype) -## return -## else: -## #try to add a lock -## if not obj_locks: -## obj_locks = locks.Locks() -## if not keys: -## #add an impassable lock -## obj_locks.add_type(ltype, locks.Key()) -## caller.msg("Added impassable '%s' lock to %s." % (ltype, obj.name)) -## else: -## keys = [k.strip() for k in keys.split(",")] -## obj_keys, group_keys, perm_keys = [], [], [] -## func_keys, attr_keys = [], [] -## allgroups = [g.name for g in Group.objects.all()] -## allperms = ["%s.%s" % (p.content_type.app_label, p.codename) -## for p in Permission.objects.all()] -## for key in keys: -## #differentiate different type of keys -## if Object.objects.is_dbref(key): -## # this is an object key, like #2, #6 etc -## obj_keys.append(key) -## elif key in allgroups: -## # a group key -## group_keys.append(key) -## elif key in allperms: -## # a permission string -## perm_keys.append(key) -## elif '()' in key: -## # a function()[:returnvalue] tuple. -## # Check if we also request a return value -## funcname, rvalue = [k.strip() for k in key.split('()',1)] -## if not funcname: -## funcname = "lock_func" -## rvalue = rvalue.lstrip(':') -## if not rvalue: -## rvalue = True -## # pack for later adding. -## func_keys.append((funcname, rvalue)) -## elif ':' in key: -## # an attribute[:returnvalue] tuple. -## attr_name, rvalue = [k.strip() for k in key.split(':',1)] -## # pack for later adding -## attr_keys.append((attr_name, rvalue)) -## else: -## caller.msg("Key '%s' is not recognized as a valid dbref, group or permission." % key) -## return -## # Create actual key objects from the respective lists -## keys = [] -## if obj_keys: -## keys.append(locks.ObjKey(obj_keys)) -## if group_keys: -## keys.append(locks.GroupKey(group_keys)) -## if perm_keys: -## keys.append(locks.PermKey(perm_keys)) -## if func_keys: -## keys.append(locks.FuncKey(func_keys, obj.dbref)) -## if attr_keys: -## keys.append(locks.AttrKey(attr_keys)) - -## #store the keys in the lock -## obj_locks.add_type(ltype, keys) -## kstring = " " -## for key in keys: -## kstring += " %s," % key -## kstring = kstring[:-1] -## caller.msg("Added lock '%s' to %s with keys%s." % (ltype, obj.name, kstring)) -## obj.LOCKS = obj_locks -## GLOBAL_CMD_TABLE.add_command("@lock", cmd_lock, priv_tuple=("objects.create",), help_category="Building") + obj = caller.search(objname) + if not obj: + return + if not attrs: + # wipe everything + for attr in obj.get_all_attributes(): + attr.delete() + string = "Wiped all attributes on %s." % obj.name + else: + for attrname in attrs: + obj.attr(attrname, delete=True ) + string = "Wiped attributes %s on %s." + string = string % (",".join(attrs), obj.name) + caller.msg(string) class CmdExamine(ObjManipCommand): @@ -1408,8 +1232,8 @@ class CmdExamine(ObjManipCommand): object and optionally a specific attribute on it. If object is not specified, the current location is examined. """ - key = "examine" - aliases = ["ex","exam"] + key = "@examine" + aliases = ["@ex","ex", "exam"] permissions = "cmd:examine" help_category = "Building" @@ -1550,127 +1374,129 @@ class CmdExamine(ObjManipCommand): caller.msg(string) -class CmdTypeclass(MuxCommand): +class CmdFind(MuxCommand): """ - @typeclass - set object typeclass + find objects - Usage: - @typclass[/switch] [= ] - @type '' - @parent '' - - Switch: - reset - clean out *all* the attributes on the object - - basically making this a new clean object. - - Example: - @type button = examples.red_button.RedButton - - Sets an object's typeclass. The typeclass must be identified - by its location using python dot-notation pointing to the correct - module and class. If no typeclass is given (or a wrong typeclass - is given), the object will be set to the default typeclass. - The location of the typeclass module is searched from - the default typeclass directory, as defined in the server settings. + Usage: + @find + Searches for an object of a particular name. """ + + key = "@find" + aliases = "@locate, find, locate" + permissions = "cmd:find" + help_category = "Building" + + def func(self): + "Search functionality" + caller = self.caller + arglist = self.arglist - key = "@typeclass" - aliases = "@type, @parent" - permissions = "cmd:typeclass" + if not arglist: + caller.msg("Usage: @find ")# [,low [,high]]") + return + searchstring = arglist[0] + if len(arglist) > 1: + low = arglist[1] + if len(arglist) > 2: + high = arglist[2] + #TODO: Implement efficient db search with limits + result = caller.search(searchstring, global_search=True) + if not result: + return + string = "%s(#%s) - %s" % (result.name, result.id, result) + caller.msg(string) + + +class CmdTeleport(MuxCommand): + """ + teleport + + Usage: + @tel/switch [ =] + + Switches: + quiet - don't inform the source and target + locations about the move. + + Teleports an object somewhere. If no object is + given we are teleporting ourselves. + """ + key = "@tel" + aliases = "@teleport" + permissions = "cmd:teleport" help_category = "Building" def func(self): - "Implements command" + "Performs the teleport" caller = self.caller + args = self.args + lhs, rhs = self.lhs, self.rhs + switches = self.switches - if not self.args: - caller.msg("Usage: @type [= =] |home") + return + # The quiet switch suppresses leaving and arrival messages. + if "quiet" in switches: + tel_quietly = True + else: + tel_quietly = False + + if rhs: + obj_to_teleport = caller.search(lhs, global_search=True) + destination = caller.search(rhs, global_search=True) + else: + obj_to_teleport = caller + destination = caller.search(args, global_search=True) + if not obj_to_teleport: + caller.msg("Did not find object to teleport.") return + if not destination: + caller.msg("Destination not found.") + return + if obj_to_teleport == destination: + caller.msg("You can't teleport an object inside of itself!") + return + # try the teleport + if obj_to_teleport.move_to(destination, quiet=tel_quietly, + emit_to_obj=caller): + caller.msg("Teleported.") - # get object to swap on - obj = caller.search(self.lhs) - if not obj: - return - if not self.rhs: - # we did not supply a new typeclass, view the - # current one instead. - if hasattr(obj, "typeclass"): - string = "%s's current typeclass is '%s'." % (obj.name, obj.typeclass) - else: - string = "%s is not a typed object." % obj.name - caller.msg(string) - return - - # we have an =, a typeclass was supplied. - typeclass = self.rhs - - # analyze typeclass. If it starts at the evennia basedir, - # (i.e. starts with game or src) we let it be, otherwise we - # add a base path as defined in settings - if typeclass and not (typeclass.startswith('src.') or - typeclass.startswith('game.')): - typeclass = "%s.%s" % (settings.BASE_TYPECLASS_PATH, - typeclass) - - if not has_perm(caller, obj, 'change_typeclass'): - caller.msg("You are not allowed to do that.") - return - - if not hasattr(obj, 'swap_typeclass') or not hasattr(obj, 'typeclass'): - caller.msg("This object cannot have a type at all!") - return - - reset = "reset" in self.switches - - old_typeclass = obj.typeclass - obj.swap_typeclass(typeclass, clean_attributes=reset) - new_typeclass = obj.typeclass - - string = "%s's type is now %s (instead of %s).\n\r" % (obj.name, - new_typeclass, - old_typeclass) - if reset: - string += "All attributes where reset." - else: - string += "Note that the new class type could have overwritten " - string += "same-named attributes on the existing object." - caller.msg(string) - -class CmdPuppet(MuxCommand): +class CmdUnLink(CmdLink): """ - Switch control to an object - + @unlink - unconnect objects + Usage: - @puppet - - This will attempt to "become" a different character. Note that this command does not check so that - the target object has the appropriate cmdset. You cannot puppet a character that is already "taken". - """ + @unlink - key = "@puppet" - permissions = "cmd:puppet" - help_category = "Admin" + Unlinks an object, for example an exit, disconnecting + it from whatever it was connected to. + """ + # this is just a child of CmdLink + + key = "@unlink" + permissions = "cmd:unlink" + help_key = "Building" def func(self): """ - Simple puppet method (does not check permissions) + All we need to do here is to set the right command + and call func in CmdLink """ + caller = self.caller - if not self.args: - caller.msg("Usage: @puppet ") - return - - player = caller.player - new_character = caller.search(self.args) - if not new_character: - return - if not utils.inherits_from(new_character, settings.BASE_CHARACTER_TYPECLASS): - caller.msg("%s is not a Character." % self.args) + + if not self.args: + caller.msg("Usage: @unlink ") return - if player.swap_character(new_character): - new_character.msg("You now control %s." % new_character.name) - else: - caller.msg("You couldn't control %s." % new_character.name) + + # This mimics '@link = ' which is the same as @unlink + self.rhs = "" + + # call the @link functionality + super(CmdUnLink, self).func() diff --git a/game/gamesrc/commands/default/cmdset_default.py b/game/gamesrc/commands/default/cmdset_default.py index 169e7e566f..929960b1df 100644 --- a/game/gamesrc/commands/default/cmdset_default.py +++ b/game/gamesrc/commands/default/cmdset_default.py @@ -2,9 +2,9 @@ This module ties together all the commands of the default command set. """ from src.commands.cmdset import CmdSet -from game.gamesrc.commands.default import general, help, privileged -from game.gamesrc.commands.default import tests, comms, objmanip -from game.gamesrc.commands.default import info, batchprocess +from game.gamesrc.commands.default import general, help, admin, system +from game.gamesrc.commands.default import tests, comms, building +from game.gamesrc.commands.default import batchprocess class DefaultCmdSet(CmdSet): """ @@ -17,13 +17,12 @@ class DefaultCmdSet(CmdSet): # The general commands self.add(general.CmdLook()) + self.add(general.CmdHome()) self.add(general.CmdPassword()) - self.add(general.CmdWall()) self.add(general.CmdInventory()) self.add(general.CmdQuit()) self.add(general.CmdPose()) self.add(general.CmdNick()) - self.add(general.CmdEmit()) self.add(general.CmdGet()) self.add(general.CmdDrop()) self.add(general.CmdWho()) @@ -35,47 +34,49 @@ class DefaultCmdSet(CmdSet): self.add(help.CmdHelp()) self.add(help.CmdSetHelp()) - # Privileged commands - self.add(privileged.CmdReload()) - self.add(privileged.CmdPy()) - self.add(privileged.CmdListScripts()) - self.add(privileged.CmdListCmdSets()) - self.add(privileged.CmdListObjects()) - self.add(privileged.CmdBoot()) - self.add(privileged.CmdDelPlayer()) - self.add(privileged.CmdNewPassword()) - self.add(privileged.CmdHome()) - self.add(privileged.CmdService()) - self.add(privileged.CmdShutdown()) - self.add(privileged.CmdPerm()) + # System commands + self.add(system.CmdReload()) + self.add(system.CmdPy()) + self.add(system.CmdListScripts()) + self.add(system.CmdListObjects()) + self.add(system.CmdService()) + self.add(system.CmdShutdown()) + self.add(system.CmdVersion()) + self.add(system.CmdTime()) + self.add(system.CmdList()) + self.add(system.CmdPs()) + self.add(system.CmdStats()) - # Info commands - self.add(info.CmdVersion()) - self.add(info.CmdTime()) - self.add(info.CmdList()) - self.add(info.CmdPs()) - self.add(info.CmdStats()) + # Admin commands + self.add(admin.CmdBoot()) + self.add(admin.CmdDelPlayer()) + self.add(admin.CmdEmit()) + self.add(admin.CmdNewPassword()) + self.add(admin.CmdPerm()) + self.add(admin.CmdPuppet()) + self.add(admin.CmdWall()) - # Object manipulation commands - self.add(objmanip.CmdTeleport()) - self.add(objmanip.CmdSetObjAlias()) - self.add(objmanip.CmdWipe()) - self.add(objmanip.CmdSetAttribute()) - self.add(objmanip.CmdName()) - self.add(objmanip.CmdDesc()) - #self.add(objmanip.CmdCpAttr()) #TODO - need testing/debugging - #self.add(objmanip.CmdMvAttr()) #TODO - need testing/debugging - self.add(objmanip.CmdFind()) - self.add(objmanip.CmdCopy()) #TODO - need testing/debugging - self.add(objmanip.CmdOpen()) - self.add(objmanip.CmdLink()) - self.add(objmanip.CmdUnLink()) - self.add(objmanip.CmdCreate()) - self.add(objmanip.CmdDig()) - self.add(objmanip.CmdDestroy()) - self.add(objmanip.CmdExamine()) - self.add(objmanip.CmdTypeclass()) - self.add(objmanip.CmdPuppet()) + # Building and world manipulation + self.add(building.CmdTeleport()) + self.add(building.CmdSetObjAlias()) + self.add(building.CmdListCmdSets()) + self.add(building.CmdDebug()) + self.add(building.CmdWipe()) + self.add(building.CmdSetAttribute()) + self.add(building.CmdName()) + self.add(building.CmdDesc()) + #self.add(building.CmdCpAttr()) #TODO - need testing/debugging + #self.add(building.CmdMvAttr()) #TODO - need testing/debugging + self.add(building.CmdFind()) + self.add(building.CmdCopy()) #TODO - need testing/debugging + self.add(building.CmdOpen()) + self.add(building.CmdLink()) + self.add(building.CmdUnLink()) + self.add(building.CmdCreate()) + self.add(building.CmdDig()) + self.add(building.CmdDestroy()) + self.add(building.CmdExamine()) + self.add(building.CmdTypeclass()) # Comm commands self.add(comms.CmdAddCom()) @@ -95,4 +96,3 @@ class DefaultCmdSet(CmdSet): self.add(tests.CmdTest()) self.add(tests.CmdTestPerms()) self.add(tests.TestCom()) - self.add(tests.CmdDebug()) diff --git a/game/gamesrc/commands/default/comms.py b/game/gamesrc/commands/default/comms.py index 89e1225cf0..82f3b4034b 100644 --- a/game/gamesrc/commands/default/comms.py +++ b/game/gamesrc/commands/default/comms.py @@ -704,7 +704,6 @@ class CmdCdesc(MuxCommand): channel.save() caller.msg("Description of channel '%s' set to '%s'." % (channel.key, self.rhs)) - class CmdPage(MuxCommand): """ page - send private message diff --git a/game/gamesrc/commands/default/general.py b/game/gamesrc/commands/default/general.py index 7e273ccc7f..2ea474d758 100644 --- a/game/gamesrc/commands/default/general.py +++ b/game/gamesrc/commands/default/general.py @@ -12,6 +12,29 @@ from src.utils import utils from game.gamesrc.commands.default.muxcommand import MuxCommand +class CmdHome(MuxCommand): + """ + home + + Usage: + home + + Teleports the player to their home. + """ + + key = "home" + permissions = "cmd:home" + + def func(self): + "Implement the command" + caller = self.caller + home = caller.home + if not home: + caller.msg("You have no home set.") + else: + caller.move_to(home) + caller.msg("There's no place like home ...") + class CmdLook(MuxCommand): """ look @@ -107,8 +130,8 @@ class CmdNick(MuxCommand): if you want to change the inherent aliases of an object, use the @alias command instead. """ - key = "alias" - aliases = ["nick"] + key = "nickname" + aliases = ["nick, @nick, alias"] def func(self): "Create the nickname" @@ -164,104 +187,6 @@ class CmdNick(MuxCommand): err = "Set %salias '%s' = '%s'" % (atype, alias, rstring) caller.msg(err.capitalize()) -class CmdEmit(MuxCommand): - """ - @emit - - Usage: - @emit[/switches] [, , ... =] - @remit [, , ... =] - @pemit [, , ... =] - - Switches: - room : limit emits to rooms only - players : limit emits to players only - contents : send to the contents of matched objects too - - Emits a message to the selected objects or to - your immediate surroundings. If the object is a room, - send to its contents. @remit and @pemit are just - limited forms of @emit, for sending to rooms and - to players respectively. - """ - key = "@emit" - aliases = ["@pemit", "@remit"] - permissions = "cmd:emit" - help_category = "Comms" - - def func(self): - "Implement the command" - - caller = self.caller - args = self.args - - if not args: - string = "Usage: " - string += "\n@emit[/switches] [, , ... =] " - string += "\n@remit [, , ... =] " - string += "\n@pemit [, , ... =] " - caller.msg(string) - return - - rooms_only = 'rooms' in self.switches - players_only = 'players' in self.switches - send_to_contents = 'contents' in self.switches - - # we check which command was used to force the switches - if self.cmdstring == '@remit': - rooms_only = True - elif self.cmdstring == '@pemit': - players_only = True - - if not self.rhs: - message = self.args - objnames = [caller.location.key] - else: - message = self.rhs - objnames = self.lhslist - - # send to all objects - for objname in objnames: - obj = caller.search(objname, global_search=True) - if not obj: - return - if rooms_only and not obj.location == None: - caller.msg("%s is not a room. Ignored." % objname) - continue - if players_only and not obj.has_player: - caller.msg("%s has no active player. Ignored." % objname) - continue - if has_perm(caller, obj, 'send_to'): - obj.msg(message) - if send_to_contents: - for content in obj.contents: - content.msg(message) - caller.msg("Emitted to %s and its contents." % objname) - else: - caller.msg("Emitted to %s." % objname) - else: - caller.msg("You are not allowed to send to %s." % objname) - -class CmdWall(MuxCommand): - """ - @wall - - Usage: - @wall - - Announces a message to all connected players. - """ - key = "@wall" - permissions = "cmd:wall" - - def func(self): - "Implements command" - if not self.args: - self.caller.msg("Usage: @wall ") - return - message = "%s shouts \"%s\"" % (self.caller.name, self.args) - sessionhandler.announce_all(message) - class CmdInventory(MuxCommand): """ @@ -383,11 +308,11 @@ class CmdQuit(MuxCommand): quit Usage: - quit + @quit Gracefully disconnect from the game. """ - key = "quit" + key = "@quit" def func(self): "hook function" @@ -716,8 +641,8 @@ class CmdGroup(MuxCommand): This command shows you which user permission groups you are a member of, if any. """ - key = "group" - aliases = "groups" + key = "access" + aliases = "groups" def func(self): "Load the permission groups" diff --git a/game/gamesrc/commands/default/help.py b/game/gamesrc/commands/default/help.py index 257302acc7..d536376273 100644 --- a/game/gamesrc/commands/default/help.py +++ b/game/gamesrc/commands/default/help.py @@ -181,10 +181,10 @@ class CmdHelp(Command): class CmdSetHelp(MuxCommand): """ - @sethelp - edit the help database + @help - edit the help database Usage: - @sethelp[/switches] [,category[,permission,permission,...]] = + @help[/switches] [,category[,permission,permission,...]] = Switches: add - add or replace a new topic with text. @@ -201,7 +201,8 @@ class CmdSetHelp(MuxCommand): @sethelp/append pickpocketing, ,is_thief, is_staff) = This steals ... """ - key = "@sethelp" + key = "@help" + aliases = "@sethelp" permissions = "cmd:sethelp" help_category = "Building" diff --git a/game/gamesrc/commands/default/info.py b/game/gamesrc/commands/default/info.py deleted file mode 100644 index 4a571d9213..0000000000 --- a/game/gamesrc/commands/default/info.py +++ /dev/null @@ -1,261 +0,0 @@ -""" -Commands that are generally staff-oriented that show information regarding -the server instance. -""" -import os, datetime -import django, twisted -from django.contrib.auth.models import User -from src.objects.models import ObjectDB -from src.scripts.models import ScriptDB -from src.utils import utils -from src.utils import gametime -from game.gamesrc.commands.default.muxcommand import MuxCommand -from src.commands import cmdsethandler - -class CmdVersion(MuxCommand): - """ - @version - game version - - Usage: - @version - - Display the game version info. - """ - - key = "@version" - help_category = "System" - - def func(self): - "Show the version" - version = utils.get_evennia_version() - string = "-"*50 +"\n\r" - string += " {cEvennia{n %s\n\r" % version - string += " (Django %s, " % (django.get_version()) - string += " Twisted %s)\n\r" % (twisted.version.short()) - string += "-"*50 - self.caller.msg(string) - -class CmdTime(MuxCommand): - """ - @time - - Usage: - @time - - Server local time. - """ - key = "@time" - aliases = "@uptime" - permissions = "cmd:time" - help_category = "System" - - def func(self): - "Show times." - - string1 = "\nCurrent server uptime: \t" - string1 += "{w%s{n" % (utils.time_format(gametime.uptime(format=False), 2)) - - string2 = "\nTotal server running time: \t" - string2 += "{w%s{n" % (utils.time_format(gametime.runtime(format=False), 2)) - - string3 = "\nTotal in-game time (realtime x %g):\t" % (gametime.TIMEFACTOR) - string3 += "{w%s{n" % (utils.time_format(gametime.gametime(format=False), 2)) - - string4 = "\nServer time stamp: \t" - string4 += "{w%s{n" % (str(datetime.datetime.now())) - string5 = "" - if not utils.host_os_is('nt'): - # os.getloadavg() is not available on Windows. - loadavg = os.getloadavg() - string5 += "\nServer load (per minute): \t" - string5 += "{w%g%%{n" % (100 * loadavg[0]) - string = "%s%s%s%s%s" % (string1, string2, string3, string4, string5) - self.caller.msg(string) - -class CmdList(MuxCommand): - """ - @list - list info - - Usage: - @list