Implemented locks.

The main command to use is @lock, which accept three types of locks at the moment, and three types of keys:
 Locks: DefaultLock, UseLock, EnterLock
 Keys: ObjectIDs, Groups, Permissions
This offers the most useful functionality - stopping people from picking up things, blocking exits and stopping
anyone from using an object.
If the attributes lock_msg, use_lock_msg and enter_lock_msg are defined on the locked object, these will be used
as error messages instead of a standard one (so "the door is locked" instead of "you cannot traverse that exit").

Behind the scenes, there is a new module, src/locks.py that defines Keys and Locks. A Locks object is a collection
of Lock types. This is stored in the LOCKS attribute on objects. Each Lock contains a set of Keys that might be
of mixed type and which the player must match in order to pass the lock.
/Griatch
This commit is contained in:
Griatch 2009-10-05 20:04:15 +00:00
parent 7f7306a6e4
commit 66095a0b16
7 changed files with 491 additions and 26 deletions

View file

@ -278,7 +278,11 @@ def match_exits(command,test=False):
if targ_exit.get_home():
# SCRIPT: See if the player can traverse the exit
if not targ_exit.scriptlink.default_lock(source_object):
source_object.emit_to("You can't traverse that exit.")
lock_msg = targ_exit.get_attribute_value("lock_msg")
if lock_msg:
source_object.emit_to(lock_msg)
else:
source_object.emit_to("You can't traverse that exit.")
else:
source_object.move_to(targ_exit.get_home())
else:
@ -287,22 +291,33 @@ def match_exits(command,test=False):
raise ExitCommandHandler
def command_table_lookup(command, command_table, eval_perms=True,test=False):
def command_table_lookup(command, command_table, eval_perms=True,test=False,neighbor=None):
"""
Performs a command table lookup on the specified command table. Also
evaluates the permissions tuple.
The test flag only checks without manipulating the command
neighbor (object) If this is supplied, we are looking at a object table and
must check for locks.
"""
# Get the command's function reference (Or False)
cmdtuple = command_table.get_command_tuple(command.command_string)
if cmdtuple:
# Check if this is just a test.
if test:
return True
# Check locks
if neighbor and not neighbor.scriptlink.use_lock(command.source_object):
# send an locked error message only if lock_desc is defined
lock_msg = neighbor.get_attribute_value("use_lock_msg")
if lock_msg:
command.source_object.emit_to(lock_msg)
raise ExitCommandHandler
return False
# If there is a permissions element to the entry, check perms.
if eval_perms and cmdtuple[1]:
if not command.source_object.has_perm_list(cmdtuple[1]):
command.source_object.emit_to(defines_global.NOPERMS_MSG)
raise ExitCommandHandler
raise ExitCommandHandler
# If flow reaches this point, user has perms and command is ready.
command.command_function = cmdtuple[0]
command.extra_vars = cmdtuple[2]
@ -321,7 +336,9 @@ def match_neighbor_ctables(command,test=False):
neighbors = source_object.location.get_contents()
for neighbor in neighbors:
if command_table_lookup(command,
neighbor.scriptlink.command_table, test=test):
neighbor.scriptlink.command_table,
test=test, neighbor=neighbor):
# test for a use-lock
# If there was a command match, set the scripted_obj attribute
# for the script parent to pick up.
if test:

View file

@ -167,13 +167,22 @@ def cmd_get(command):
return
if not obj_is_staff and (target_obj.is_player() or target_obj.is_exit()):
source_object.emit_to("You can't get that.")
return
if target_obj.is_room() or target_obj.is_garbage() or target_obj.is_going():
source_object.emit_to("You can't get that.")
return
if not target_obj.scriptlink.default_lock(source_object):
lock_msg = target_obj.get_attribute_value("lock_msg")
if lock_msg:
source_object.emit_to(lock_msg)
else:
source_object.emit_to("You can't get that.")
return
target_obj.move_to(source_object, quiet=True)
source_object.emit_to("You pick up %s." % (target_obj.get_name(show_dbref=False),))
source_object.get_location().emit_to_contents("%s picks up %s." %
@ -282,11 +291,14 @@ def cmd_examine(command):
s += str(target_obj.get_name(fullname=True)) + newl
s += str("Type: %s Flags: %s" % (target_obj.get_type(),
target_obj.get_flags())) + newl
#s += str("Desc: %s" % target_obj.get_attribute_value('desc')) + newl
target_obj.get_flags())) + newl
s += str("Owner: %s " % target_obj.get_owner()) + newl
s += str("Zone: %s" % target_obj.get_zone()) + newl
s += str("Parent: %s " % target_obj.get_script_parent()) + newl
locks = target_obj.get_attribute_value("LOCKS")
if locks and "%s" % locks:
s += str("Locks: %s" % locks) + newl
# Contents container lists for sorting by type.
con_players = []
@ -313,6 +325,7 @@ def cmd_examine(command):
# This obviously isn't valid for rooms.
s += str("Location: %s" % target_obj.get_location()) + newl
# Render other attributes
for attribute in target_obj.get_all_attributes():
s += str(attribute.get_attrline()) + newl

View file

@ -1,9 +1,11 @@
"""
These commands typically are to do with building or modifying Objects.
"""
from django.contrib.auth.models import Permission, Group
from src.objects.models import Object, Attribute
# We'll import this as the full path to avoid local variable clashes.
import src.flags
from src import locks
from src import ansi
from src.cmdtable import GLOBAL_CMD_TABLE
from src import defines_global
@ -445,7 +447,7 @@ def cmd_create(command):
return
eq_args = command.command_argument.split(':', 1)
target_name = eq_args[0]
target_name = eq_args[0].strip()
#check if we want to set a custom parent
script_parent = None
@ -814,7 +816,7 @@ def cmd_dig(command):
where you are.
Usage:
@dig[/switches] roomname [:parent] [= exitthere [: parent][;alias]] [, exithere [: parent][;alias]]
@dig[/switches] roomname [:parent] [= exit_to_there [: parent][;alias]] [, exit_to_here [: parent][;alias]]
switches:
teleport - move yourself to the new room
@ -829,7 +831,7 @@ def cmd_dig(command):
switches = command.command_switches
if not args:
source_object.emit_to("Usage[/teleport]: @dig roomname [:parent][= exitthere [:parent] [;alias]] [, exithere [:parent] [;alias]]")
source_object.emit_to("Usage: @dig[/teleport] roomname [:parent][= exit_to_there [:parent] [;alias]] [, exit_to_here [:parent] [;alias]]")
return
room_name = None
@ -1158,3 +1160,148 @@ def cmd_destroy(command):
GLOBAL_CMD_TABLE.add_command("@destroy", cmd_destroy,
priv_tuple=("objects.create",),auto_help=True,staff_help=True)
def cmd_lock(command):
"""@lock
Usage:
@lock[/switch] <obj> [:type] [= <key>[,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)
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)
If no keys 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.
<<TOPIC: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.
Fail messages echoed to the player are stored in the attributes 'lock_msg',
'use_lock_msg' and 'enter_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).
"""
source_object = command.source_object
arg = command.command_argument
switches = command.command_switches
if not arg:
source_object.emit_to("Usage: @lock[/switch] <obj> [:type] [= <key>[,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"]:
source_object.emit_to("Lock type '%s' not recognized." % ltype)
return
obj = source_object.search_for_object(obj_name)
if not obj:
return
obj_locks = obj.get_attribute_value("LOCKS")
if "list" in switches or not switches:
if not obj_locks:
s = "There are no locks on %s." % obj.get_name()
else:
s = "Locks on %s:" % obj.get_name()
s += obj_locks.show()
source_object.emit_to(s)
return
# we are trying to change things. Check permissions.
if not source_object.controls_other(obj):
source_object.emit_to(defines_global.NOCONTROL_MSG)
return
if "del" in switches:
# clear a lock
if obj_locks:
if not obj_locks.has_type(ltype):
source_object.emit_to("No %s set on this object." % ltype)
else:
obj_locks.del_type(ltype)
obj.set_attribute("LOCKS", obj_locks)
source_object.emit_to("Cleared lock %s on %s." % (ltype, obj.get_name()))
else:
source_object.emit_to("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())
source_object.emit_to("Added impassable '%s' lock to %s." % (ltype, obj.get_name()))
else:
keys = [k.strip() for k in keys.split(",")]
okeys, gkeys, pkeys = [], [], []
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):
okeys.append(key)
elif key in allgroups:
gkeys.append(key)
elif key in allperms:
pkeys.append(key)
else:
source_object.emit_to("Key '%s' is not recognized as a valid dbref, group or permission." % key)
return
# Create actual key objects from the respective lists
keys = []
if okeys:
keys.append(locks.ObjKey(okeys))
if gkeys:
keys.append(locks.GroupKey(gkeys))
if pkeys:
keys.append(locks.PermKey(pkeys))
#store the keys in the lock
obj_locks.add_type(ltype, keys)
kstring = ""
for key in keys:
kstring += " %s" % key
source_object.emit_to("Added lock '%s' to %s with keys%s." % (ltype, obj.get_name(), kstring))
obj.set_attribute("LOCKS",obj_locks)
GLOBAL_CMD_TABLE.add_command("@lock", cmd_lock, priv_tuple=("objects.create",),auto_help=True, staff_help=True)

View file

@ -23,10 +23,10 @@ OBJECT_TYPES = (
# These attribute names can't be modified by players.
NOSET_ATTRIBS = ["MONEY", "ALIAS", "LASTPAGED", "__CHANLIST", "LAST",
"__PARENT", "LASTSITE"]
"__PARENT", "LASTSITE", "LOCKS"]
# These attributes don't show up on objects when examined.
HIDDEN_ATTRIBS = ["__CHANLIST", "__PARENT"]
HIDDEN_ATTRIBS = ["__CHANLIST", "__PARENT", "LOCKS"]
# Server version number.
REVISION = os.popen('svnversion .', 'r').readline().strip()

246
src/locks.py Normal file
View file

@ -0,0 +1,246 @@
"""
This module handles all in-game locks.
A lock object contains a set of criteria (keys). When queried, the
lock tries the tested object/player against these criteria and returns
a True/False result.
"""
import traceback
from src.objects.models import Object
from src import logger
class Key(object):
"""
This implements a lock key.
Normally the Key is of OR type; if an object matches any criterion in the key,
the entire key is considered a match. With the 'exact' criterion, all criteria
contained in the key (except the list of object dbrefs) must also exist in the
object.
With the NOT flag the key is inversed, that is, only objects which do not
match the criterias (exact or not) will be considered to have access.
Supplying no criteria will make the lock impassable (NOT flag results in an alvays open lock)
"""
def __init__(self, criteria=[], extra=[], NOT=False, exact=False):
"""
Defines the basic permission laws
permlist (list of strings) - permission definitions
grouplist (list of strings) - group names
objlist (list of obj or dbrefs) - match individual objects to the lock
NOT (bool) - invert the lock
exact (bool) - objects must match all criteria. Default is OR operation.
"""
self.criteria = criteria
self.extra = extra
#set the boolean operators
self.NOT = not NOT
self.exact = exact
# if we have no criteria, this is an impassable lock (or always open if NOT given).
self.impassable = not(criteria)
def __str__(self):
s = ""
if not self.criteria:
s += " <Impassable>"
for crit in self.criteria:
s += " <%s>" % crit
return s.strip()
def _result(self, result):
if self.exact:
result = result == len(self.criteria)
if result:
return self.NOT
else:
return not self.NOT
def check(self, object):
"""
Compare the object to see if the key matches.
"""
if self.NOT:
return not self.impassable
return self.impassable
class ObjKey(Key):
"""
This implements a Key matching against object id
"""
def check(self, object):
if self.impassable:
return self.NOT
if object.dbref() in self.criteria:
return self.NOT
else:
return not self.NOT
class GroupKey(Key):
"""
This key matches against group membership
"""
def check(self, object):
if self.impassable: return self.NOT
user = object.get_user_account()
if not user: return False
return self._result(len([g for g in user.groups.all() if str(g) in self.criteria]))
class PermKey(Key):
"""
This key matches against permissions
"""
def check(self, object):
if self.impassable: return self.NOT
user = object.get_user_account()
if not user: return False
return self._result(len([p for p in self.criteria if object.has_perm(p)]))
class FlagKey(Key):
"""
This key use a set of object flagss to define access.
"""
def check(self, object):
if self.impassable: return self.NOT
return self._result(len([f for f in self.criteria if object.has_flag(f)]))
class AttrKey(Key):
"""
This key use a list of arbitrary attributes to define access.
The attribute names are in the usual criteria. If there is a matching
list of values in the self.extra list we compare the values directly,
otherwise we just check for the existence of the attribute.
"""
def check(self, object):
if self.impassable: return self.NOT
val_list = self.extra
attr_list = self.criteria
if len(val_list) == len(attr_list):
return self._result(len([i for i in range(attr_list)
if object.get_attribute_value(attr_list[i])==val_list[i]]))
else:
return _result(len([a for a in attr_list if object.get_attribute_value(a)]))
class Locks(object):
"""
The Locks object defines an overall grouping of Locks based after type. Each lock
contains a set of keys to limit access to a certain action.
The Lock object is stored in the attribute LOCKS on the object in question and the
engine queries it during the relevant situations.
Below is a list copied from MUX. Currently Evennia only use 3 lock types:
Default, Use and Enter; it's not clear if any more are really needed.
Name: Affects: Effect:
-----------------------------------------------------------------------
DefaultLock: Exits: controls who may traverse the exit to
its destination.
Rooms: controls whether the player sees the SUCC
or FAIL message for the room following the
room description when looking at the room.
Players/Things: controls who may GET the object.
EnterLock: Players/Things: controls who may ENTER the object if the
object is ENTER_OK. Also, the enter lock
of an object being used as a Zone Master
Object determines control of that zone.
GetFromLock: All but Exits: controls who may gets things from a given
location.
GiveLock: Players/Things: controls who may give the object.
LeaveLock: Players/Things: controls who may LEAVE the object.
LinkLock: All but Exits: controls who may link to the location if the
location is LINK_OK (for linking exits or
setting drop-tos) or ABODE (for setting
homes)
MailLock: Players: controls who may @mail the player.
OpenLock: All but Exits: controls who may open an exit.
PageLock: Players: controls who may page the player.
ParentLock: All: controls who may make @parent links to the
object.
ReceiveLock: Players/Things: controls who may give things to the object.
SpeechLock: All but Exits: controls who may speak in that location
(only checked if AUDITORIUM flag is set
on that location)
TeloutLock: All but Exits: controls who may teleport out of the
location.
TportLock: Rooms/Things: controls who may teleport there if the
location is JUMP_OK.
UseLock: All but Exits: controls who may USE the object, GIVE the
object money and have the PAY attributes
run, have their messages heard and possibly
acted on by LISTEN and AxHEAR, and invoke
$-commands stored on the object.
DropLock: All but rooms: controls who may drop that object.
UserLock: All: Not used by MUX, is intended to be used
in MUX programming where a user-defined
lock is needed.
VisibleLock: All: Controls object visibility when the object
is not dark and the looker passes the lock.
In DARK locations, the object must also be
set LIGHT and the viewer must pass the
VisibleLock.
"""
def __init__(self):
"""
The Lock logic is strictly OR. If you want to make access restricted,
make it so in the respective Key.
"""
self.locks = {}
def __str__(self):
s = ""
for lock in self.locks.keys():
s += " %s" % lock
return s.strip()
def add_type(self, ltype, keys=[]):
"""
type (string) : the type pf lock, like DefaultLock, UseLock etc.
keylist = list of Key objects defining who have access.
"""
if type(keys) != type(list()):
keys = [keys]
self.locks[ltype] = keys
def del_type(self,ltype):
"""
Clears a lock.
"""
if self.has_type(ltype):
del self.locks[ltype]
def has_type(self,ltype):
return self.locks.has_key(ltype)
def show(self):
if not self.locks:
return "No locks."
s = ""
for lock, keys in self.locks.items():
s += "\n %s\n " % lock
for key in keys:
s += " %s" % key
return s
def check(self, ltype, object):
"""
This is called by the engine. It checks if this lock is of the right type,
and if so if there is access. If the type does not exist, there is no
lock for it and thus we return True.
"""
if not self.has_type(ltype):
return True
result = False
for key in self.locks[ltype]:
try:
result = result or key.check(object)
except KeyError:
pass
if not result and object.is_superuser():
object.emit_to("Lock '%s' - Superuser override." % ltype)
return True
return result

View file

@ -338,6 +338,22 @@ class Object(models.Model):
# Fall through to failure
return False
def has_group(self, group):
"""
Checks if a user is member of a particular user group.
"""
if not self.is_player():
return False
if self.is_superuser():
return True
if group in [g.name for g in self.get_user_account().groups.all()]:
return True
else:
return False
def owns_other(self, other_obj):
"""
See if the envoked object owns another object.
@ -367,7 +383,7 @@ class Object(models.Model):
# When builder_override is enabled, a builder permission means
# the object controls the other.
if builder_override and not other_obj.is_player() and self.has_perm('genperms.builder'):
if builder_override and not other_obj.is_player() and self.has_group('Builders'):
return True
# They've failed to meet any of the above conditions.
@ -883,9 +899,18 @@ class Object(models.Model):
quiet: (bool) If true, don't emit left/arrived messages.
force_look: (bool) If true and self is a player, make them 'look'.
"""
#first, check if we can enter that location at all.
if not target.scriptlink.enter_lock(self):
lock_desc = self.get_attribute_value("enter_lock_msg")
if lock_desc:
self.emit_to(lock_desc)
else:
self.emit_to("That destination is blocked from you.")
return
#before the move, call eventual pre-commands.
if self.scriptlink.at_before_move(target) != None:
if self.scriptlink.at_before_move(target) != None:
return
if not quiet:

View file

@ -135,16 +135,24 @@ class EvenniaBasicObject(object):
# This is the object being looked at.
target_obj = self.scripted_obj
# See if the envoker sees dbref numbers.
lock_msg = ""
if pobject:
show_dbrefs = pobject.sees_dbrefs()
show_dbrefs = pobject.sees_dbrefs()
#check for the defaultlock, this shows a lock message after the normal desc, if one is defined.
if target_obj.is_room() and \
not target_obj.scriptlink.default_lock(pobject):
temp = target_obj.get_attribute_value("lock_msg")
if temp:
lock_msg = "\n%s" % temp
else:
show_dbrefs = False
description = target_obj.get_attribute_value('desc')
if description is not None:
retval = "%s\r\n%s" % (
retval = "%s\r\n%s%s" % (
target_obj.get_name(show_dbref=show_dbrefs),
target_obj.get_attribute_value('desc'),
target_obj.get_attribute_value('desc'), lock_msg
)
else:
retval = "%s" % (
@ -192,8 +200,11 @@ class EvenniaBasicObject(object):
values:
* pobject: (Object) The object requesting the action.
"""
# Assume everyone passes the default lock by default.
return True
locks = self.scripted_obj.get_attribute_value("LOCKS")
if locks:
return locks.check("DefaultLock", pobject)
else:
return True
def use_lock(self, pobject):
"""
@ -204,8 +215,11 @@ class EvenniaBasicObject(object):
values:
* pobject: (Object) The object requesting the action.
"""
# Assume everyone passes the use lock by default.
return True
locks = self.scripted_obj.get_attribute_value("LOCKS")
if locks:
return locks.check("UseLock", pobject)
else:
return True
def enter_lock(self, pobject):
"""
@ -216,5 +230,8 @@ class EvenniaBasicObject(object):
values:
* pobject: (Object) The object requesting the action.
"""
# Assume everyone passes the enter lock by default.
return True
locks = self.scripted_obj.get_attribute_value("LOCKS")
if locks:
return locks.check("EnterLock", pobject)
else:
return True