Fixed bugs and allowed for logging in using one character. Added a simple command for creating a new character.

This commit is contained in:
Griatch 2013-02-03 17:00:46 +01:00
parent f1767251c6
commit b26c3ab872
11 changed files with 204 additions and 111 deletions

View file

@ -23,6 +23,7 @@ class OOCCmdSet(CmdSet):
self.add(general.CmdOOCLook())
self.add(general.CmdIC())
self.add(general.CmdOOC())
self.add(general.CmdCharCreate())
self.add(general.CmdEncoding())
self.add(general.CmdQuit())
self.add(general.CmdPassword())
@ -34,6 +35,7 @@ class OOCCmdSet(CmdSet):
self.add(system.CmdReload())
self.add(system.CmdReset())
self.add(system.CmdShutdown())
self.add(system.CmdPy())
# Admin commands
self.add(admin.CmdDelPlayer())

View file

@ -5,7 +5,7 @@ now.
import time
from django.conf import settings
from src.server.sessionhandler import SESSIONS
from src.utils import utils, search
from src.utils import utils, search, create
from src.objects.models import ObjectNick as Nick
from src.commands.default.muxcommand import MuxCommand, MuxCommandOOC
@ -573,7 +573,7 @@ class CmdEncoding(MuxCommand):
Common encodings are utf-8 (default), latin-1, ISO-8859-1 etc.
If you don't submit an encoding, the current encoding will be displayed instead.
"""
"""
key = "@encoding"
aliases = "@encode"
@ -649,7 +649,62 @@ class CmdAccess(MuxCommand):
string += "\nPlayer {c%s{n: %s" % (caller.player.key, pperms)
caller.msg(string)
class CmdColorTest(MuxCommand):
"""
testing colors
Usage:
@color ansi|xterm256
Print a color map along with in-mud color codes, while testing what is supported in your client.
Choices are 16-color ansi (supported in most muds) or the 256-color xterm256 standard.
No checking is done to determine your client supports color - if not you will
see rubbish appear.
"""
key = "@color"
locks = "cmd:all()"
help_category = "General"
def func(self):
"Show color tables"
if not self.args or not self.args in ("ansi", "xterm256"):
self.caller.msg("Usage: @color ansi|xterm256")
return
if self.args == "ansi":
from src.utils import ansi
ap = ansi.ANSI_PARSER
# ansi colors
# show all ansi color-related codes
col1 = ["%s%s{n" % (code, code.replace("{","{{")) for code, _ in ap.ext_ansi_map[:-1]]
hi = "%ch"
col2 = ["%s%s{n" % (code, code.replace("%", "%%")) for code, _ in ap.mux_ansi_map[:-2]]
col3 = ["%s%s{n" % (hi+code, (hi+code).replace("%", "%%")) for code, _ in ap.mux_ansi_map[:-2]]
table = utils.format_table([col1, col2, col3], extra_space=1)
string = "ANSI colors:"
for row in table:
string += "\n" + "".join(row)
print string
self.caller.msg(string)
self.caller.msg("{{X and %%cx are black-on-black)")
elif self.args == "xterm256":
table = [[],[],[],[],[],[],[],[],[],[],[],[]]
for ir in range(6):
for ig in range(6):
for ib in range(6):
# foreground table
table[ir].append("%%c%i%i%i%s{n" % (ir,ig,ib, "{{%i%i%i" % (ir,ig,ib)))
# background table
table[6+ir].append("%%cb%i%i%i%%c%i%i%i%s{n" % (ir,ig,ib,
5-ir,5-ig,5-ib,
"{{b%i%i%i" % (ir,ig,ib)))
table = utils.format_table(table)
string = "Xterm256 colors:"
for row in table:
string += "\n" + "".join(row)
self.caller.msg(string)
self.caller.msg("(e.g. %%c123 and %%cb123 also work)")
#------------------------------------------------------------
@ -724,6 +779,49 @@ class CmdOOCLook(MuxCommandOOC, CmdLook):
else:
self.no_look_target()
class CmdCharCreate(MuxCommandOOC):
"""
Create a character
Usage:
@charcreate <charname> [= desc]
Create a new character, optionally giving it a description.
"""
key = "@charcreate"
locks = "cmd:all()"
help_category = "General"
MAX_NR_CHARACTERS = 2
def func(self):
"create the new character"
player = self.caller
if not self.args:
player.msg("Usage: @charcreate <charname> [= description]")
return
key = self.lhs
desc = self.rhs
if not player.db._created_chars:
lockstring = "attrread:perm(Admins);attredit:perm(Admins);attrcreate:perm(Admins)"
player.set_attribute("_created_chars", [], lockstring=lockstring)
if len(player.db._created_chars) >= self.MAX_NR_CHARACTERS:
player.msg("You may only create a maximum of %i characters." % self.MAX_NR_CHARACTERS)
return
# create the character
from src.objects.models import ObjectDB
default_home = ObjectDB.objects.get_id(settings.CHARACTER_DEFAULT_HOME)
typeclass = settings.BASE_CHARACTER_TYPECLASS
permissions = settings.PERMISSION_PLAYER_DEFAULT
new_character = create.create_object(typeclass, key=key, location=default_home,
home=default_home, permissions=permissions)
player.db._created_chars.append(new_character)
if desc:
new_character.db.desc = desc
player.msg("Created new character %s." % new_character.key)
class CmdIC(MuxCommandOOC):
"""
@ -753,6 +851,7 @@ class CmdIC(MuxCommandOOC):
Simple puppet method
"""
caller = self.caller
sessid = self.sessid
old_character = self.character
new_character = None
@ -769,16 +868,21 @@ class CmdIC(MuxCommandOOC):
else:
# the search method handles error messages etc.
return
if new_character.player:
if new_character.player == caller:
caller.msg("{RYou already are {c%s{n." % new_character.name)
else:
caller.msg("{c%s{r is already acted by another player.{n" % new_character.name)
# permission checks
if caller.get_character(sessid=sessid, character=new_character):
caller.msg("{RYou already act as {c%s{n." % new_character.name)
return
if new_character.player:
if new_character.sessid == sessid:
caller.msg("{RYou already act as {c%s{n from another session." % new_character.name)
return
elif not caller.get_character(character=new_character):
caller.msg("{c%s{r is already acted by another player.{n" % new_character.name)
return
if not new_character.access(caller, "puppet"):
caller.msg("{rYou may not become %s.{n" % new_character.name)
return
if caller.swap_character(new_character):
if caller.connect_character(new_character, sessid=sessid):
new_character.msg("\n{gYou become {c%s{n.\n" % new_character.name)
caller.db.last_puppet = old_character
if not new_character.location:
@ -819,79 +923,22 @@ class CmdOOC(MuxCommandOOC):
if utils.inherits_from(caller, "src.objects.objects.Object"):
caller = self.caller.player
if not caller.character:
old_char = caller.get_character(sessid=self.sessid)
if not old_char:
string = "You are already OOC."
caller.msg(string)
return
caller.db.last_puppet = caller.character
caller.db.last_puppet = old_char
# save location as if we were disconnecting from the game entirely.
if caller.character.location:
caller.character.location.msg_contents("%s has left the game." % caller.character.key, exclude=[caller.character])
caller.character.db.prelogout_location = caller.character.location
caller.character.location = None
if old_char.location:
old_char.location.msg_contents("%s has left the game." % old_char.key, exclude=[old_char])
old_char.db.prelogout_location = old_char.location
old_char.location = None
# disconnect
caller.character.player = None
caller.character = None
caller.disconnect_character(caller)
caller.msg("\n{GYou go OOC.{n\n")
caller.execute_cmd("look")
class CmdColorTest(MuxCommand):
"""
testing colors
Usage:
@color ansi|xterm256
Print a color map along with in-mud color codes, while testing what is supported in your client.
Choices are 16-color ansi (supported in most muds) or the 256-color xterm256 standard.
No checking is done to determine your client supports color - if not you will
see rubbish appear.
"""
key = "@color"
locks = "cmd:all()"
help_category = "General"
def func(self):
"Show color tables"
if not self.args or not self.args in ("ansi", "xterm256"):
self.caller.msg("Usage: @color ansi|xterm256")
return
if self.args == "ansi":
from src.utils import ansi
ap = ansi.ANSI_PARSER
# ansi colors
# show all ansi color-related codes
col1 = ["%s%s{n" % (code, code.replace("{","{{")) for code, _ in ap.ext_ansi_map[:-1]]
hi = "%ch"
col2 = ["%s%s{n" % (code, code.replace("%", "%%")) for code, _ in ap.mux_ansi_map[:-2]]
col3 = ["%s%s{n" % (hi+code, (hi+code).replace("%", "%%")) for code, _ in ap.mux_ansi_map[:-2]]
table = utils.format_table([col1, col2, col3], extra_space=1)
string = "ANSI colors:"
for row in table:
string += "\n" + "".join(row)
print string
self.caller.msg(string)
self.caller.msg("{{X and %%cx are black-on-black)")
elif self.args == "xterm256":
table = [[],[],[],[],[],[],[],[],[],[],[],[]]
for ir in range(6):
for ig in range(6):
for ib in range(6):
# foreground table
table[ir].append("%%c%i%i%i%s{n" % (ir,ig,ib, "{{%i%i%i" % (ir,ig,ib)))
# background table
table[6+ir].append("%%cb%i%i%i%%c%i%i%i%s{n" % (ir,ig,ib,
5-ir,5-ig,5-ib,
"{{b%i%i%i" % (ir,ig,ib)))
table = utils.format_table(table)
string = "Xterm256 colors:"
for row in table:
string += "\n" + "".join(row)
self.caller.msg(string)
self.caller.msg("(e.g. %%c123 and %%cb123 also work)")

View file

@ -189,6 +189,6 @@ class MuxCommandOOC(MuxCommand):
self.caller = self.caller.player
elif hasattr(self.caller, "character"):
# caller was already a Player
self.character = self.caller.character
self.character = self.caller.get_character(sessid=self.sessid)
else:
self.character = None

View file

@ -146,11 +146,13 @@ class CmdPy(MuxCommand):
caller.msg(string)
return
# check if caller is a player
# import useful variables
import ev
available_vars = {'self':caller,
'me':caller,
'here':caller.location,
'here':hasattr(caller, "location") and caller.location or None,
'ev':ev,
'inherits_from':utils.inherits_from}

View file

@ -365,3 +365,12 @@ class ObjectManager(TypedObjectManager):
ScriptDB.objects.copy_script(script, new_obj=new_object.dbobj)
return new_object
def clear_all_sessids(self):
"""
Clear the db_sessid field of all objects having also the db_player field
set.
"""
self.filter(db_sessid__isnull=False).update(db_sessid=None)

View file

@ -201,7 +201,7 @@ class Object(TypeClass):
ignore_errors=ignore_errors,
player=player)
def execute_cmd(self, raw_string):
def execute_cmd(self, raw_string, sessid=None):
"""
Do something as this object. This command transparently
lets its typeclass execute the command. Evennia also calls
@ -209,6 +209,7 @@ class Object(TypeClass):
Argument:
raw_string (string) - raw command input
sessid (int) - id of session executing the command. This sets the sessid property on the command.
Returns Deferred - this is an asynchronous Twisted object that will
not fire until the command has actually finished executing. To overload
@ -219,7 +220,7 @@ class Object(TypeClass):
This return is not used at all by Evennia by default, but might be useful
for coders intending to implement some sort of nested command structure.
"""
return self.dbobj.execute_cmd(raw_string)
return self.dbobj.execute_cmd(raw_string, sessid=sessid)
def msg(self, message, from_obj=None, data=None):
"""

View file

@ -391,7 +391,7 @@ class PlayerDB(TypedObject):
if sessid:
session = _GA(self, "get_session")(sessid)
if session:
char = _GA(self, "get_character")(sessid)
char = _GA(self, "get_character")(sessid=sessid)
if char and not char.at_msg_receive(outgoing_string, from_obj=from_obj, data=data):
# if hook returns false, cancel send
return
@ -412,7 +412,7 @@ class PlayerDB(TypedObject):
data - dictionary of optional data
session - session sending this data
"""
character = _GA(self, "get_character")(sessid)
character = _GA(self, "get_character")(sessid=sessid)
if character:
# execute command on character
_GA(character, "execute_cmd")(ingoing_string, sessid=sessid)
@ -427,9 +427,11 @@ class PlayerDB(TypedObject):
linked to the player using self.connect_character().
force - drop existing connection to other character
Returns True if connection was successful, False otherwise
"""
# first check if we already have a character tied to this session
char = _GA(self, "get_character")(sessid=sessid)
char = _GA(self, "get_character")(sessid=sessid, return_dbobj=True)
if char:
if force and char != character:
_GA(self, "disconnect_session_from_character")(sessid)
@ -452,22 +454,27 @@ class PlayerDB(TypedObject):
del character.db.FIRST_LOGIN
character.at_pre_login()
character.at_post_login()
return True
def disconnect_session_from_character(self, sessid):
"""
Disconnect a session from the characterm (still keeping the
connection to the Player)
returns the newly disconnected character, if it existed
"""
char = _GA(self, "get_character")(sessid=sessid)
if not sessid:
return
char = _GA(self, "get_character")(sessid=sessid, return_dbobj=True)
if char:
# call hook before disconnecting
character.at_disconnect()
_GA(char.typeclass, "at_disconnect")()
del char.sessid
# update cache
cache = get_prop_cache(self, "_characters") or {}
if sessid in cache:
del cache[sessid]
set_prop_cache(self, "_characters", cache)
# update cache
cache = get_prop_cache(self, "_characters") or {}
if sessid in cache:
del cache[sessid]
set_prop_cache(self, "_characters", cache)
return char
def get_session(self, sessid):
"""
@ -479,9 +486,9 @@ class PlayerDB(TypedObject):
"Return all sessions connected to this player"
return SESSIONS.sessions_from_player(self)
def get_character(self, sessid=None, character=None):
def get_character(self, sessid=None, character=None, return_dbobj=False):
"""
Get the character connected to this player
Get the character connected to this player and sessid
sessid - return character connected to this sessid,
character - return character if connected to this player, else None.
@ -497,17 +504,18 @@ class PlayerDB(TypedObject):
if not char:
char = _GA(self, "db_objs").filter(db_player=self, db_sessid=sessid) or None
if char:
cache[sessid] = char[0]
char = char[0]
cache[sessid] = char
set_prop_cache(self, "_characters", cache)
if character:
return char == character.dbobj or None
return char
return char and (char == character.dbobj and (return_dbobj and char or char.typeclass)) or None
return char and (return_dbobj and char or char.typeclass) or None
elif character:
char = _GA(self, "db_objs").filter(character)
return char and char[0] or None
char = _GA(self, "db_objs").filter(id=_GA(character.dbobj, "id"))
return char and (return_dbobj and char[0] or char[0].typeclass) or None
else:
# no sessid given - return all available characters
return list(self.db_objs.all())
return list(return_dbobj and o or o.typeclass for o in self.db_objs.all())
def get_all_characters(self):
"""
@ -515,30 +523,37 @@ class PlayerDB(TypedObject):
"""
return _GA(self, "get_character")(sessid=None, character=None)
def connect_character(self, char):
def connect_character(self, character, sessid=None):
"""
Use the Player to connect a Character to a session. Note that
we don't do any access checks at this point. Note that if the
game was fully restarted (including the Portal), this must be
used, since sessids will have changed as players reconnect.
if sessid is given, also connect the sessid to the character.
"""
# first disconnect any other character from this session
char = char.dbobj
char = character.dbobj
_GA(self, "disconnect_character")(char)
char = char.dbobj
char.player = self
_GA(self, "db_objs").add(char)
_GA(self, "save")()
if sessid:
return _GA(self, "connect_session_to_character")(sessid=sessid, character=char)
return True
def disconnect_character(self, char):
def disconnect_character(self, character):
"""
Disconnect a character from this player, either based
on sessid or by giving the character object directly
Returns newly disconnected character.
"""
char = char.dbobj
key = char and char.key or None
char = _GA(self, "get_character")(key=key)
if not character:
return
char = _GA(self, "get_character")(character=character, return_dbobj=True)
if char:
_GA(self, "disconnect_session_from_character")(char.sessid)
_GA(self, "db_objs").remove(char)
del char.player
del char.sessid
@ -547,16 +562,23 @@ class PlayerDB(TypedObject):
cache = get_prop_cache(self, "_characters") or {}
[cache.pop(sessid) for sessid,stored_char in cache.items() if stored_char==char]
set_prop_cache(self, "_characters", cache)
return char
def disconnect_all_characters(self):
for char in self.db_objs.all():
_GA(self, "disconnect_character")(char)
def swap_character(self, new_character, delete_old_character=False):
def swap_character(self, old_character, new_character):
"""
Swaps character, if possible
Swaps character between sessions, if possible
"""
return _GA(self, "__class__").objects.swap_character(self, new_character, delete_old_character=delete_old_character)
this_sessid = old_character.sessid
other_sessd = new_character.sessid
this_char = _GA(self, "disconnect_session_from_character")(this_sessid)
other_char = _GA(self, "disconnect_session_from_character")(other_sessid)
_GA(self, "connect_session_to_character")(this_sessid, other_char)
_GA(self, "connect_session_to_character")(other_sessid, this_char)
def delete(self, *args, **kwargs):
"Make sure to delete user also when deleting player - the two may never exist separately."

View file

@ -102,7 +102,7 @@ class Player(TypeClass):
a command on a Character, the character automatically stores and
handles the sessid).
"""
self.dbobj.msg(outgoing_string, from_obj=from_obj, data=data)
self.dbobj.msg(outgoing_string, from_obj=from_obj, data=data, sessid=sessid)
def swap_character(self, new_character, delete_old_character=False):
"""
@ -115,7 +115,7 @@ class Player(TypeClass):
"""
return self.dbobj.swap_character(new_character, delete_old_character=delete_old_character)
def execute_cmd(self, raw_string):
def execute_cmd(self, raw_string, sessid=None):
"""
Do something as this object. This command transparently
lets its typeclass execute the command. Evennia also calls
@ -123,6 +123,7 @@ class Player(TypeClass):
Argument:
raw_string (string) - raw command input
sessid (int) - id of session executing the command. This sets the sessid property on the command
Returns Deferred - this is an asynchronous Twisted object that will
not fire until the command has actually finished executing. To overload
@ -133,7 +134,7 @@ class Player(TypeClass):
This return is not used at all by Evennia by default, but might be useful
for coders intending to implement some sort of nested command structure.
"""
return self.dbobj.execute_cmd(raw_string)
return self.dbobj.execute_cmd(raw_string, sessid=sessid)
def search(self, ostring, return_character=False):
"""

View file

@ -190,6 +190,9 @@ class Evennia(object):
from src.objects.models import ObjectDB
#from src.players.models import PlayerDB
# clear eventual lingering session storages
ObjectDB.objects.clear_all_sessids()
#update eventual changed defaults
self.update_defaults()
@ -288,7 +291,7 @@ class Evennia(object):
yield [(p.typeclass, p.at_server_shutdown()) for p in PlayerDB.get_all_cached_instances()]
yield [(s.typeclass, s.at_server_shutdown()) for s in ScriptDB.get_all_cached_instances()]
yield ObjectDB.objects.clear_all_sessids()
ServerConfig.objects.conf("server_restart_mode", "reset")
if SERVER_STARTSTOP_MODULE:

View file

@ -178,7 +178,7 @@ class ServerSession(Session):
else:
# we are not logged in. Use the session directly
# (it uses the settings.UNLOGGEDIN cmdset)
cmdhandler.cmdhandler(self, command_string)
cmdhandler.cmdhandler(self, command_string, sessid=self.sessid)
def data_out(self, msg, data=None):
"""

View file

@ -1311,7 +1311,7 @@ class TypedObject(SharedMemoryModel):
return False
return True
def set_attribute(self, attribute_name, new_value=None):
def set_attribute(self, attribute_name, new_value=None, lockstring=""):
"""
Sets an attribute on an object. Creates the attribute if need
be.
@ -1319,6 +1319,10 @@ class TypedObject(SharedMemoryModel):
attribute_name: (str) The attribute's name.
new_value: (python obj) The value to set the attribute to. If this is not
a str, the object will be stored as a pickle.
lockstring - this sets an access restriction on the attribute object. Note that
this is normally NOT checked - use the secureattr() access method
below to perform access-checked modification of attributes. Lock
types checked by secureattr are 'attrread','attredit','attrcreate'.
"""
attrib_obj = get_attr_cache(self, attribute_name)
if not attrib_obj:
@ -1332,6 +1336,8 @@ class TypedObject(SharedMemoryModel):
else:
# no match; create new attribute
attrib_obj = attrclass(db_key=attribute_name, db_obj=self)
if lockstring:
attrib_obj.locks.add(lockstring)
# re-set an old attribute value
try:
attrib_obj.value = new_value