Merge develop branch, resolve conflicts
This commit is contained in:
commit
54e351f296
22 changed files with 258 additions and 175 deletions
|
|
@ -1,13 +1,18 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## Evennia 0.9 (2019)
|
## Evennia 0.9 (2018-2019)
|
||||||
|
|
||||||
Update to Python 3
|
Update to Python 3
|
||||||
|
|
||||||
- Use `python3 -m venv <myenvname>`
|
- Use `python3 -m venv <myenvname>`
|
||||||
- Use `python3 -m pdb <script>` for debugging
|
- Use `python3 -m pdb <script>` for debugging
|
||||||
|
|
||||||
### Misc
|
### Commands
|
||||||
|
|
||||||
|
- Removed default `@delaccount` command, incorporating as `@account/delete` instead. Added confirmation
|
||||||
|
question.
|
||||||
|
|
||||||
|
### Utils
|
||||||
|
|
||||||
- Swap argument order of `evennia.set_trace` to `set_trace(term_size=(140, 40), debugger='auto')`
|
- Swap argument order of `evennia.set_trace` to `set_trace(term_size=(140, 40), debugger='auto')`
|
||||||
since the size is more likely to be changed on the command line.
|
since the size is more likely to be changed on the command line.
|
||||||
|
|
|
||||||
12
Dockerfile
12
Dockerfile
|
|
@ -7,7 +7,7 @@
|
||||||
# Usage:
|
# Usage:
|
||||||
# cd to a folder where you want your game data to be (or where it already is).
|
# cd to a folder where you want your game data to be (or where it already is).
|
||||||
#
|
#
|
||||||
# docker run -it -p 4000:4000 -p 4001:4001 -p 4005:4005 -v $PWD:/usr/src/game evennia/evennia
|
# docker run -it --rm -p 4000:4000 -p 4001:4001 -p 4005:4005 -v $PWD:/usr/src/game evennia/evennia
|
||||||
#
|
#
|
||||||
# (If your OS does not support $PWD, replace it with the full path to your current
|
# (If your OS does not support $PWD, replace it with the full path to your current
|
||||||
# folder).
|
# folder).
|
||||||
|
|
@ -15,6 +15,14 @@
|
||||||
# You will end up in a shell where the `evennia` command is available. From here you
|
# You will end up in a shell where the `evennia` command is available. From here you
|
||||||
# can install and run the game normally. Use Ctrl-D to exit the evennia docker container.
|
# can install and run the game normally. Use Ctrl-D to exit the evennia docker container.
|
||||||
#
|
#
|
||||||
|
# You can also start evennia directly by passing arguments to the folder:
|
||||||
|
#
|
||||||
|
# docker run -it --rm -p 4000:4000 -p 4001:4001 -p 4005:4005 -v $PWD:/usr/src/game evennia/evennia evennia start -l
|
||||||
|
#
|
||||||
|
# This will start Evennia running as the core process of the container. Note that you *must* use -l
|
||||||
|
# or one of the foreground modes (like evennia ipstart) since otherwise the container will immediately
|
||||||
|
# die since no foreground process keeps it up.
|
||||||
|
#
|
||||||
# The evennia/evennia base image is found on DockerHub and can also be used
|
# The evennia/evennia base image is found on DockerHub and can also be used
|
||||||
# as a base for creating your own custom containerized Evennia game. For more
|
# as a base for creating your own custom containerized Evennia game. For more
|
||||||
# info, see https://github.com/evennia/evennia/wiki/Running%20Evennia%20in%20Docker .
|
# info, see https://github.com/evennia/evennia/wiki/Running%20Evennia%20in%20Docker .
|
||||||
|
|
@ -58,7 +66,7 @@ WORKDIR /usr/src/game
|
||||||
ENV PS1 "evennia|docker \w $ "
|
ENV PS1 "evennia|docker \w $ "
|
||||||
|
|
||||||
# startup a shell when we start the container
|
# startup a shell when we start the container
|
||||||
ENTRYPOINT bash -c "source /usr/src/evennia/bin/unix/evennia-docker-start.sh"
|
ENTRYPOINT ["/usr/src/evennia/bin/unix/evennia-docker-start.sh"]
|
||||||
|
|
||||||
# expose the telnet, webserver and websocket client ports
|
# expose the telnet, webserver and websocket client ports
|
||||||
EXPOSE 4000 4001 4005
|
EXPOSE 4000 4001 4005
|
||||||
|
|
|
||||||
16
bin/unix/evennia-docker-start.sh
Normal file → Executable file
16
bin/unix/evennia-docker-start.sh
Normal file → Executable file
|
|
@ -1,10 +1,18 @@
|
||||||
#! /bin/bash
|
#! /bin/sh
|
||||||
|
|
||||||
# called by the Dockerfile to start the server in docker mode
|
# called by the Dockerfile to start the server in docker mode
|
||||||
|
|
||||||
# remove leftover .pid files (such as from when dropping the container)
|
# remove leftover .pid files (such as from when dropping the container)
|
||||||
rm /usr/src/game/server/*.pid >& /dev/null || true
|
rm /usr/src/game/server/*.pid >& /dev/null || true
|
||||||
|
|
||||||
# start evennia server; log to server.log but also output to stdout so it can
|
PS1="evennia|docker \w $ "
|
||||||
# be viewed with docker-compose logs
|
|
||||||
exec 3>&1; evennia start -l
|
cmd="$@"
|
||||||
|
output="Docker starting with argument '$cmd' ..."
|
||||||
|
if test -z $cmd; then
|
||||||
|
cmd="bash"
|
||||||
|
output="No argument given, starting shell ..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo $output
|
||||||
|
exec 3>&1; $cmd
|
||||||
|
|
|
||||||
|
|
@ -1000,7 +1000,10 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)):
|
||||||
|
|
||||||
if target and not is_iter(target):
|
if target and not is_iter(target):
|
||||||
# single target - just show it
|
# single target - just show it
|
||||||
|
if hasattr(target, "return_appearance"):
|
||||||
return target.return_appearance(self)
|
return target.return_appearance(self)
|
||||||
|
else:
|
||||||
|
return "{} has no in-game appearance.".format(target)
|
||||||
else:
|
else:
|
||||||
# list of targets - make list to disconnect from db
|
# list of targets - make list to disconnect from db
|
||||||
characters = list(tar for tar in target if tar) if target else []
|
characters = list(tar for tar in target if tar) if target else []
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
from mock import Mock
|
from mock import Mock, MagicMock
|
||||||
from random import randint
|
from random import randint
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from django.test import override_settings
|
||||||
from evennia.accounts.accounts import AccountSessionHandler
|
from evennia.accounts.accounts import AccountSessionHandler
|
||||||
from evennia.accounts.accounts import DefaultAccount
|
from evennia.accounts.accounts import DefaultAccount
|
||||||
from evennia.server.session import Session
|
from evennia.server.session import Session
|
||||||
|
|
@ -14,9 +15,15 @@ class TestAccountSessionHandler(TestCase):
|
||||||
"Check AccountSessionHandler class"
|
"Check AccountSessionHandler class"
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount)
|
self.account = create.create_account(
|
||||||
|
"TestAccount%s" % randint(0, 999999), email="test@test.com",
|
||||||
|
password="testpassword", typeclass=DefaultAccount)
|
||||||
self.handler = AccountSessionHandler(self.account)
|
self.handler = AccountSessionHandler(self.account)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
if hasattr(self, 'account'):
|
||||||
|
self.account.delete()
|
||||||
|
|
||||||
def test_get(self):
|
def test_get(self):
|
||||||
"Check get method"
|
"Check get method"
|
||||||
self.assertEqual(self.handler.get(), [])
|
self.assertEqual(self.handler.get(), [])
|
||||||
|
|
@ -24,24 +31,24 @@ class TestAccountSessionHandler(TestCase):
|
||||||
|
|
||||||
import evennia.server.sessionhandler
|
import evennia.server.sessionhandler
|
||||||
|
|
||||||
s1 = Session()
|
s1 = MagicMock()
|
||||||
s1.logged_in = True
|
s1.logged_in = True
|
||||||
s1.uid = self.account.uid
|
s1.uid = self.account.uid
|
||||||
evennia.server.sessionhandler.SESSIONS[s1.uid] = s1
|
evennia.server.sessionhandler.SESSIONS[s1.uid] = s1
|
||||||
|
|
||||||
s2 = Session()
|
s2 = MagicMock()
|
||||||
s2.logged_in = True
|
s2.logged_in = True
|
||||||
s2.uid = self.account.uid + 1
|
s2.uid = self.account.uid + 1
|
||||||
evennia.server.sessionhandler.SESSIONS[s2.uid] = s2
|
evennia.server.sessionhandler.SESSIONS[s2.uid] = s2
|
||||||
|
|
||||||
s3 = Session()
|
s3 = MagicMock()
|
||||||
s3.logged_in = False
|
s3.logged_in = False
|
||||||
s3.uid = self.account.uid + 2
|
s3.uid = self.account.uid + 2
|
||||||
evennia.server.sessionhandler.SESSIONS[s3.uid] = s3
|
evennia.server.sessionhandler.SESSIONS[s3.uid] = s3
|
||||||
|
|
||||||
self.assertEqual(self.handler.get(), [s1])
|
self.assertEqual([s.uid for s in self.handler.get()], [s1.uid])
|
||||||
self.assertEqual(self.handler.get(self.account.uid), [s1])
|
self.assertEqual([s.uid for s in [self.handler.get(self.account.uid)]], [s1.uid])
|
||||||
self.assertEqual(self.handler.get(self.account.uid + 1), [])
|
self.assertEqual([s.uid for s in self.handler.get(self.account.uid + 1)], [])
|
||||||
|
|
||||||
def test_all(self):
|
def test_all(self):
|
||||||
"Check all method"
|
"Check all method"
|
||||||
|
|
@ -56,9 +63,14 @@ class TestDefaultAccount(TestCase):
|
||||||
"Check DefaultAccount class"
|
"Check DefaultAccount class"
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.s1 = Session()
|
self.s1 = MagicMock()
|
||||||
self.s1.puppet = None
|
self.s1.puppet = None
|
||||||
self.s1.sessid = 0
|
self.s1.sessid = 0
|
||||||
|
self.s1.data_outj
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
if hasattr(self, "account"):
|
||||||
|
self.account.delete()
|
||||||
|
|
||||||
def test_password_validation(self):
|
def test_password_validation(self):
|
||||||
"Check password validators deny bad passwords"
|
"Check password validators deny bad passwords"
|
||||||
|
|
@ -71,7 +83,6 @@ class TestDefaultAccount(TestCase):
|
||||||
"Check validators allow sufficiently complex passwords"
|
"Check validators allow sufficiently complex passwords"
|
||||||
for better in ('Mxyzptlk', "j0hn, i'M 0n1y d4nc1nG"):
|
for better in ('Mxyzptlk', "j0hn, i'M 0n1y d4nc1nG"):
|
||||||
self.assertTrue(self.account.validate_password(better, account=self.account)[0])
|
self.assertTrue(self.account.validate_password(better, account=self.account)[0])
|
||||||
self.account.delete()
|
|
||||||
|
|
||||||
def test_password_change(self):
|
def test_password_change(self):
|
||||||
"Check password setting and validation is working as expected"
|
"Check password setting and validation is working as expected"
|
||||||
|
|
@ -109,7 +120,9 @@ class TestDefaultAccount(TestCase):
|
||||||
|
|
||||||
import evennia.server.sessionhandler
|
import evennia.server.sessionhandler
|
||||||
|
|
||||||
account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount)
|
account = create.create_account(
|
||||||
|
"TestAccount%s" % randint(0, 999999), email="test@test.com",
|
||||||
|
password="testpassword", typeclass=DefaultAccount)
|
||||||
self.s1.uid = account.uid
|
self.s1.uid = account.uid
|
||||||
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
|
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
|
||||||
|
|
||||||
|
|
@ -131,10 +144,7 @@ class TestDefaultAccount(TestCase):
|
||||||
self.s1.uid = account.uid
|
self.s1.uid = account.uid
|
||||||
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
|
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
|
||||||
|
|
||||||
self.s1.puppet = None
|
self.s1.data_out = MagicMock()
|
||||||
self.s1.logged_in = True
|
|
||||||
self.s1.data_out = Mock(return_value=None)
|
|
||||||
|
|
||||||
obj = Mock()
|
obj = Mock()
|
||||||
obj.access = Mock(return_value=False)
|
obj.access = Mock(return_value=False)
|
||||||
|
|
||||||
|
|
@ -143,6 +153,7 @@ class TestDefaultAccount(TestCase):
|
||||||
self.assertTrue(self.s1.data_out.call_args[1]['text'].startswith("You don't have permission to puppet"))
|
self.assertTrue(self.s1.data_out.call_args[1]['text'].startswith("You don't have permission to puppet"))
|
||||||
self.assertIsNone(obj.at_post_puppet.call_args)
|
self.assertIsNone(obj.at_post_puppet.call_args)
|
||||||
|
|
||||||
|
@override_settings(MULTISESSION_MODE=0)
|
||||||
def test_puppet_object_joining_other_session(self):
|
def test_puppet_object_joining_other_session(self):
|
||||||
"Check puppet_object method called, joining other session"
|
"Check puppet_object method called, joining other session"
|
||||||
|
|
||||||
|
|
@ -154,15 +165,16 @@ class TestDefaultAccount(TestCase):
|
||||||
|
|
||||||
self.s1.puppet = None
|
self.s1.puppet = None
|
||||||
self.s1.logged_in = True
|
self.s1.logged_in = True
|
||||||
self.s1.data_out = Mock(return_value=None)
|
self.s1.data_out = MagicMock()
|
||||||
|
|
||||||
obj = Mock()
|
obj = Mock()
|
||||||
obj.access = Mock(return_value=True)
|
obj.access = Mock(return_value=True)
|
||||||
obj.account = account
|
obj.account = account
|
||||||
|
obj.sessions.all = MagicMock(return_value=[self.s1])
|
||||||
|
|
||||||
account.puppet_object(self.s1, obj)
|
account.puppet_object(self.s1, obj)
|
||||||
# works because django.conf.settings.MULTISESSION_MODE is not in (1, 3)
|
# works because django.conf.settings.MULTISESSION_MODE is not in (1, 3)
|
||||||
self.assertTrue(self.s1.data_out.call_args[1]['text'].endswith("from another of your sessions."))
|
self.assertTrue(self.s1.data_out.call_args[1]['text'].endswith("from another of your sessions.|n"))
|
||||||
self.assertTrue(obj.at_post_puppet.call_args[1] == {})
|
self.assertTrue(obj.at_post_puppet.call_args[1] == {})
|
||||||
|
|
||||||
def test_puppet_object_already_puppeted(self):
|
def test_puppet_object_already_puppeted(self):
|
||||||
|
|
@ -171,6 +183,7 @@ class TestDefaultAccount(TestCase):
|
||||||
import evennia.server.sessionhandler
|
import evennia.server.sessionhandler
|
||||||
|
|
||||||
account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount)
|
account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount)
|
||||||
|
self.account = account
|
||||||
self.s1.uid = account.uid
|
self.s1.uid = account.uid
|
||||||
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
|
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import time
|
||||||
from codecs import lookup as codecs_lookup
|
from codecs import lookup as codecs_lookup
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia.server.sessionhandler import SESSIONS
|
from evennia.server.sessionhandler import SESSIONS
|
||||||
from evennia.utils import utils, create, search, evtable
|
from evennia.utils import utils, create, logger, search, evtable
|
||||||
|
|
||||||
COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||||
|
|
||||||
|
|
@ -172,6 +172,7 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
|
||||||
new_character.db.desc = "This is a character."
|
new_character.db.desc = "This is a character."
|
||||||
self.msg("Created new character %s. Use |w@ic %s|n to enter the game as this character."
|
self.msg("Created new character %s. Use |w@ic %s|n to enter the game as this character."
|
||||||
% (new_character.key, new_character.key))
|
% (new_character.key, new_character.key))
|
||||||
|
logger.log_sec('Character Created: %s (Caller: %s, IP: %s).' % (new_character, account, self.session.address))
|
||||||
|
|
||||||
|
|
||||||
class CmdCharDelete(COMMAND_DEFAULT_CLASS):
|
class CmdCharDelete(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -215,6 +216,7 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS):
|
||||||
caller.db._playable_characters = [pc for pc in caller.db._playable_characters if pc != delobj]
|
caller.db._playable_characters = [pc for pc in caller.db._playable_characters if pc != delobj]
|
||||||
delobj.delete()
|
delobj.delete()
|
||||||
self.msg("Character '%s' was permanently deleted." % key)
|
self.msg("Character '%s' was permanently deleted." % key)
|
||||||
|
logger.log_sec('Character Deleted: %s (Caller: %s, IP: %s).' % (key, account, self.session.address))
|
||||||
else:
|
else:
|
||||||
self.msg("Deletion was aborted.")
|
self.msg("Deletion was aborted.")
|
||||||
del caller.ndb._char_to_delete
|
del caller.ndb._char_to_delete
|
||||||
|
|
@ -280,8 +282,10 @@ class CmdIC(COMMAND_DEFAULT_CLASS):
|
||||||
try:
|
try:
|
||||||
account.puppet_object(session, new_character)
|
account.puppet_object(session, new_character)
|
||||||
account.db._last_puppet = new_character
|
account.db._last_puppet = new_character
|
||||||
|
logger.log_sec('Puppet Success: (Caller: %s, Target: %s, IP: %s).' % (account, new_character, self.session.address))
|
||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
self.msg("|rYou cannot become |C%s|n: %s" % (new_character.name, exc))
|
self.msg("|rYou cannot become |C%s|n: %s" % (new_character.name, exc))
|
||||||
|
logger.log_sec('Puppet Failed: %s (Caller: %s, Target: %s, IP: %s).' % (exc, account, new_character, self.session.address))
|
||||||
|
|
||||||
|
|
||||||
# note that this is inheriting from MuxAccountLookCommand,
|
# note that this is inheriting from MuxAccountLookCommand,
|
||||||
|
|
@ -642,6 +646,7 @@ class CmdPassword(COMMAND_DEFAULT_CLASS):
|
||||||
account.set_password(newpass)
|
account.set_password(newpass)
|
||||||
account.save()
|
account.save()
|
||||||
self.msg("Password changed.")
|
self.msg("Password changed.")
|
||||||
|
logger.log_sec('Password Changed: %s (Caller: %s, IP: %s).' % (account, account, self.session.address))
|
||||||
|
|
||||||
|
|
||||||
class CmdQuit(COMMAND_DEFAULT_CLASS):
|
class CmdQuit(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,14 @@ import re
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia.server.sessionhandler import SESSIONS
|
from evennia.server.sessionhandler import SESSIONS
|
||||||
from evennia.server.models import ServerConfig
|
from evennia.server.models import ServerConfig
|
||||||
from evennia.utils import evtable, search, class_from_module
|
from evennia.utils import evtable, logger, search, class_from_module
|
||||||
|
|
||||||
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||||
|
|
||||||
PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
|
PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
|
||||||
|
|
||||||
# limit members for API inclusion
|
# limit members for API inclusion
|
||||||
__all__ = ("CmdBoot", "CmdBan", "CmdUnban", "CmdDelAccount",
|
__all__ = ("CmdBoot", "CmdBan", "CmdUnban",
|
||||||
"CmdEmit", "CmdNewPassword", "CmdPerm", "CmdWall")
|
"CmdEmit", "CmdNewPassword", "CmdPerm", "CmdWall")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -96,6 +96,9 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
|
||||||
session.msg(feedback)
|
session.msg(feedback)
|
||||||
session.account.disconnect_session_from_account(session)
|
session.account.disconnect_session_from_account(session)
|
||||||
|
|
||||||
|
if pobj and boot_list:
|
||||||
|
logger.log_sec('Booted: %s (Reason: %s, Caller: %s, IP: %s).' % (pobj, reason, caller, self.session.address))
|
||||||
|
|
||||||
|
|
||||||
# regex matching IP addresses with wildcards, eg. 233.122.4.*
|
# regex matching IP addresses with wildcards, eg. 233.122.4.*
|
||||||
IPREGEX = re.compile(r"[0-9*]{1,3}\.[0-9*]{1,3}\.[0-9*]{1,3}\.[0-9*]{1,3}")
|
IPREGEX = re.compile(r"[0-9*]{1,3}\.[0-9*]{1,3}\.[0-9*]{1,3}\.[0-9*]{1,3}")
|
||||||
|
|
@ -130,7 +133,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
|
||||||
reason to be able to later remember why the ban was put in place.
|
reason to be able to later remember why the ban was put in place.
|
||||||
|
|
||||||
It is often preferable to ban an account from the server than to
|
It is often preferable to ban an account from the server than to
|
||||||
delete an account with @delaccount. If banned by name, that account
|
delete an account with @accounts/delete. If banned by name, that account
|
||||||
account can no longer be logged into.
|
account can no longer be logged into.
|
||||||
|
|
||||||
IP (Internet Protocol) address banning allows blocking all access
|
IP (Internet Protocol) address banning allows blocking all access
|
||||||
|
|
@ -203,6 +206,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
|
||||||
banlist.append(bantup)
|
banlist.append(bantup)
|
||||||
ServerConfig.objects.conf('server_bans', banlist)
|
ServerConfig.objects.conf('server_bans', banlist)
|
||||||
self.caller.msg("%s-Ban |w%s|n was added." % (typ, ban))
|
self.caller.msg("%s-Ban |w%s|n was added." % (typ, ban))
|
||||||
|
logger.log_sec('Banned %s: %s (Caller: %s, IP: %s).' % (typ, ban.strip(), self.caller, self.session.address))
|
||||||
|
|
||||||
|
|
||||||
class CmdUnban(COMMAND_DEFAULT_CLASS):
|
class CmdUnban(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -246,79 +250,10 @@ class CmdUnban(COMMAND_DEFAULT_CLASS):
|
||||||
ban = banlist[num - 1]
|
ban = banlist[num - 1]
|
||||||
del banlist[num - 1]
|
del banlist[num - 1]
|
||||||
ServerConfig.objects.conf('server_bans', banlist)
|
ServerConfig.objects.conf('server_bans', banlist)
|
||||||
|
value = " ".join([s for s in ban[:2]])
|
||||||
self.caller.msg("Cleared ban %s: %s" %
|
self.caller.msg("Cleared ban %s: %s" %
|
||||||
(num, " ".join([s for s in ban[:2]])))
|
(num, value))
|
||||||
|
logger.log_sec('Unbanned: %s (Caller: %s, IP: %s).' % (value.strip(), self.caller, self.session.address))
|
||||||
|
|
||||||
class CmdDelAccount(COMMAND_DEFAULT_CLASS):
|
|
||||||
"""
|
|
||||||
delete an account from the server
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
@delaccount[/switch] <name> [: reason]
|
|
||||||
|
|
||||||
Switch:
|
|
||||||
delobj - also delete the account's currently
|
|
||||||
assigned in-game object.
|
|
||||||
|
|
||||||
Completely deletes a user from the server database,
|
|
||||||
making their nick and e-mail again available.
|
|
||||||
"""
|
|
||||||
|
|
||||||
key = "@delaccount"
|
|
||||||
switch_options = ("delobj",)
|
|
||||||
locks = "cmd:perm(delaccount) or perm(Developer)"
|
|
||||||
help_category = "Admin"
|
|
||||||
|
|
||||||
def func(self):
|
|
||||||
"""Implements the command."""
|
|
||||||
|
|
||||||
caller = self.caller
|
|
||||||
args = self.args
|
|
||||||
|
|
||||||
if hasattr(caller, 'account'):
|
|
||||||
caller = caller.account
|
|
||||||
|
|
||||||
if not args:
|
|
||||||
self.msg("Usage: @delaccount <account/user name or #id> [: reason]")
|
|
||||||
return
|
|
||||||
|
|
||||||
reason = ""
|
|
||||||
if ':' in args:
|
|
||||||
args, reason = [arg.strip() for arg in args.split(':', 1)]
|
|
||||||
|
|
||||||
# We use account_search since we want to be sure to find also accounts
|
|
||||||
# that lack characters.
|
|
||||||
accounts = search.account_search(args)
|
|
||||||
|
|
||||||
if not accounts:
|
|
||||||
self.msg('Could not find an account by that name.')
|
|
||||||
return
|
|
||||||
|
|
||||||
if len(accounts) > 1:
|
|
||||||
string = "There were multiple matches:\n"
|
|
||||||
string += "\n".join(" %s %s" % (account.id, account.key) for account in accounts)
|
|
||||||
self.msg(string)
|
|
||||||
return
|
|
||||||
|
|
||||||
# one single match
|
|
||||||
|
|
||||||
account = accounts.first()
|
|
||||||
|
|
||||||
if not account.access(caller, 'delete'):
|
|
||||||
string = "You don't have the permissions to delete that account."
|
|
||||||
self.msg(string)
|
|
||||||
return
|
|
||||||
|
|
||||||
uname = account.username
|
|
||||||
# boot the account then delete
|
|
||||||
self.msg("Informing and disconnecting account ...")
|
|
||||||
string = "\nYour account '%s' is being *permanently* deleted.\n" % uname
|
|
||||||
if reason:
|
|
||||||
string += " Reason given:\n '%s'" % reason
|
|
||||||
account.msg(string)
|
|
||||||
account.delete()
|
|
||||||
self.msg("Account %s was successfully deleted." % uname)
|
|
||||||
|
|
||||||
|
|
||||||
class CmdEmit(COMMAND_DEFAULT_CLASS):
|
class CmdEmit(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -445,6 +380,7 @@ class CmdNewPassword(COMMAND_DEFAULT_CLASS):
|
||||||
if account.character != caller:
|
if account.character != caller:
|
||||||
account.msg("%s has changed your password to '%s'." % (caller.name,
|
account.msg("%s has changed your password to '%s'." % (caller.name,
|
||||||
newpass))
|
newpass))
|
||||||
|
logger.log_sec('Password Changed: %s (Caller: %s, IP: %s).' % (account, caller, self.session.address))
|
||||||
|
|
||||||
|
|
||||||
class CmdPerm(COMMAND_DEFAULT_CLASS):
|
class CmdPerm(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -526,6 +462,7 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
|
||||||
else:
|
else:
|
||||||
caller_result.append("\nPermission %s removed from %s (if they existed)." % (perm, obj.name))
|
caller_result.append("\nPermission %s removed from %s (if they existed)." % (perm, obj.name))
|
||||||
target_result.append("\n%s revokes the permission(s) %s from you." % (caller.name, perm))
|
target_result.append("\n%s revokes the permission(s) %s from you." % (caller.name, perm))
|
||||||
|
logger.log_sec('Permissions Deleted: %s, %s (Caller: %s, IP: %s).' % (perm, obj, caller, self.session.address))
|
||||||
else:
|
else:
|
||||||
# add a new permission
|
# add a new permission
|
||||||
permissions = obj.permissions.all()
|
permissions = obj.permissions.all()
|
||||||
|
|
@ -547,6 +484,8 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
|
||||||
caller_result.append("\nPermission '%s' given to %s (%s)." % (perm, obj.name, plystring))
|
caller_result.append("\nPermission '%s' given to %s (%s)." % (perm, obj.name, plystring))
|
||||||
target_result.append("\n%s gives you (%s, %s) the permission '%s'."
|
target_result.append("\n%s gives you (%s, %s) the permission '%s'."
|
||||||
% (caller.name, obj.name, plystring, perm))
|
% (caller.name, obj.name, plystring, perm))
|
||||||
|
logger.log_sec('Permissions Added: %s, %s (Caller: %s, IP: %s).' % (obj, perm, caller, self.session.address))
|
||||||
|
|
||||||
caller.msg("".join(caller_result).strip())
|
caller.msg("".join(caller_result).strip())
|
||||||
if target_result:
|
if target_result:
|
||||||
obj.msg("".join(target_result).strip())
|
obj.msg("".join(target_result).strip())
|
||||||
|
|
|
||||||
|
|
@ -737,12 +737,11 @@ class CmdDestroy(COMMAND_DEFAULT_CLASS):
|
||||||
confirm += ", ".join(["#{}".format(obj.id) for obj in objs])
|
confirm += ", ".join(["#{}".format(obj.id) for obj in objs])
|
||||||
confirm += " [yes]/no?" if self.default_confirm == 'yes' else " yes/[no]"
|
confirm += " [yes]/no?" if self.default_confirm == 'yes' else " yes/[no]"
|
||||||
answer = ""
|
answer = ""
|
||||||
while answer.strip().lower() not in ("y", "yes", "n", "no"):
|
|
||||||
answer = yield(confirm)
|
answer = yield(confirm)
|
||||||
answer = self.default_confirm if answer == '' else answer
|
answer = self.default_confirm if answer == '' else answer
|
||||||
|
|
||||||
if answer.strip().lower() in ("n", "no"):
|
if answer.strip().lower() in ("n", "no"):
|
||||||
caller.msg("Cancelled: no object was destroyed.")
|
caller.msg("Canceled: no object was destroyed.")
|
||||||
delete = False
|
delete = False
|
||||||
|
|
||||||
if delete:
|
if delete:
|
||||||
|
|
@ -2888,7 +2887,8 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
|
||||||
"use the 'exec' prototype key.")
|
"use the 'exec' prototype key.")
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
protlib.validate_prototype(prototype)
|
# we homogenize first, to be more lenient
|
||||||
|
protlib.validate_prototype(protlib.homogenize_prototype(prototype))
|
||||||
except RuntimeError as err:
|
except RuntimeError as err:
|
||||||
self.caller.msg(str(err))
|
self.caller.msg(str(err))
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,6 @@ class AccountCmdSet(CmdSet):
|
||||||
self.add(system.CmdPy())
|
self.add(system.CmdPy())
|
||||||
|
|
||||||
# Admin commands
|
# Admin commands
|
||||||
self.add(admin.CmdDelAccount())
|
|
||||||
self.add(admin.CmdNewPassword())
|
self.add(admin.CmdNewPassword())
|
||||||
|
|
||||||
# Comm commands
|
# Comm commands
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,15 @@ make sure to homogenize self.caller to always be the account object
|
||||||
for easy handling.
|
for easy handling.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
import hashlib
|
||||||
|
import time
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia.comms.models import ChannelDB, Msg
|
from evennia.comms.models import ChannelDB, Msg
|
||||||
from evennia.accounts.models import AccountDB
|
from evennia.accounts.models import AccountDB
|
||||||
from evennia.accounts import bots
|
from evennia.accounts import bots
|
||||||
from evennia.comms.channelhandler import CHANNELHANDLER
|
from evennia.comms.channelhandler import CHANNELHANDLER
|
||||||
from evennia.locks.lockhandler import LockException
|
from evennia.locks.lockhandler import LockException
|
||||||
from evennia.utils import create, utils, evtable
|
from evennia.utils import create, logger, utils, evtable
|
||||||
from evennia.utils.utils import make_iter, class_from_module
|
from evennia.utils.utils import make_iter, class_from_module
|
||||||
|
|
||||||
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||||
|
|
@ -367,6 +369,7 @@ class CmdCdestroy(COMMAND_DEFAULT_CLASS):
|
||||||
channel.delete()
|
channel.delete()
|
||||||
CHANNELHANDLER.update()
|
CHANNELHANDLER.update()
|
||||||
self.msg("Channel '%s' was destroyed." % channel_key)
|
self.msg("Channel '%s' was destroyed." % channel_key)
|
||||||
|
logger.log_sec('Channel Deleted: %s (Caller: %s, IP: %s).' % (channel_key, caller, self.session.address))
|
||||||
|
|
||||||
|
|
||||||
class CmdCBoot(COMMAND_DEFAULT_CLASS):
|
class CmdCBoot(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -432,6 +435,8 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS):
|
||||||
# disconnect account
|
# disconnect account
|
||||||
channel.disconnect(account)
|
channel.disconnect(account)
|
||||||
CHANNELHANDLER.update()
|
CHANNELHANDLER.update()
|
||||||
|
logger.log_sec('Channel Boot: %s (Channel: %s, Reason: %s, Caller: %s, IP: %s).' % (
|
||||||
|
account, channel, reason, self.caller, self.session.address))
|
||||||
|
|
||||||
|
|
||||||
class CmdCemit(COMMAND_DEFAULT_CLASS):
|
class CmdCemit(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -917,8 +922,9 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
self.msg("Account '%s' already exists and is not a bot." % botname)
|
self.msg("Account '%s' already exists and is not a bot." % botname)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
password = hashlib.md5(str(time.time())).hexdigest()[:11]
|
||||||
try:
|
try:
|
||||||
bot = create.create_account(botname, None, None, typeclass=botclass)
|
bot = create.create_account(botname, None, password, typeclass=botclass)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
self.msg("|rError, could not create the bot:|n '%s'." % err)
|
self.msg("|rError, could not create the bot:|n '%s'." % err)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ from evennia.server.sessionhandler import SESSIONS
|
||||||
from evennia.scripts.models import ScriptDB
|
from evennia.scripts.models import ScriptDB
|
||||||
from evennia.objects.models import ObjectDB
|
from evennia.objects.models import ObjectDB
|
||||||
from evennia.accounts.models import AccountDB
|
from evennia.accounts.models import AccountDB
|
||||||
from evennia.utils import logger, utils, gametime, create
|
from evennia.utils import logger, utils, gametime, create, search
|
||||||
from evennia.utils.eveditor import EvEditor
|
from evennia.utils.eveditor import EvEditor
|
||||||
from evennia.utils.evtable import EvTable
|
from evennia.utils.evtable import EvTable
|
||||||
from evennia.utils.utils import crop, class_from_module
|
from evennia.utils.utils import crop, class_from_module
|
||||||
|
|
@ -460,17 +460,22 @@ class CmdObjects(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
class CmdAccounts(COMMAND_DEFAULT_CLASS):
|
class CmdAccounts(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
list all registered accounts
|
Manage registered accounts
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@accounts [nr]
|
@accounts [nr]
|
||||||
|
@accounts/delete <name or #id> [: reason]
|
||||||
|
|
||||||
Lists statistics about the Accounts registered with the game.
|
Switches:
|
||||||
|
delete - delete an account from the server
|
||||||
|
|
||||||
|
By default, lists statistics about the Accounts registered with the game.
|
||||||
It will list the <nr> amount of latest registered accounts
|
It will list the <nr> amount of latest registered accounts
|
||||||
If not given, <nr> defaults to 10.
|
If not given, <nr> defaults to 10.
|
||||||
"""
|
"""
|
||||||
key = "@accounts"
|
key = "@accounts"
|
||||||
aliases = ["@listaccounts"]
|
aliases = ["@account", "@listaccounts"]
|
||||||
|
switch_options = ("delete", )
|
||||||
locks = "cmd:perm(listaccounts) or perm(Admin)"
|
locks = "cmd:perm(listaccounts) or perm(Admin)"
|
||||||
help_category = "System"
|
help_category = "System"
|
||||||
|
|
||||||
|
|
@ -478,6 +483,56 @@ class CmdAccounts(COMMAND_DEFAULT_CLASS):
|
||||||
"""List the accounts"""
|
"""List the accounts"""
|
||||||
|
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
|
args = self.args
|
||||||
|
|
||||||
|
if "delete" in self.switches:
|
||||||
|
account = getattr(caller, "account")
|
||||||
|
if not account or not account.check_permstring("Developer"):
|
||||||
|
caller.msg("You are not allowed to delete accounts.")
|
||||||
|
return
|
||||||
|
if not args:
|
||||||
|
caller.msg("Usage: @accounts/delete <name or #id> [: reason]")
|
||||||
|
return
|
||||||
|
reason = ""
|
||||||
|
if ":" in args:
|
||||||
|
args, reason = [arg.strip() for arg in args.split(":", 1)]
|
||||||
|
# We use account_search since we want to be sure to find also accounts
|
||||||
|
# that lack characters.
|
||||||
|
accounts = search.account_search(args)
|
||||||
|
if not accounts:
|
||||||
|
self.msg("Could not find an account by that name.")
|
||||||
|
return
|
||||||
|
if len(accounts) > 1:
|
||||||
|
string = "There were multiple matches:\n"
|
||||||
|
string += "\n".join(" %s %s" % (account.id, account.key) for account in accounts)
|
||||||
|
self.msg(string)
|
||||||
|
return
|
||||||
|
account = accounts.first()
|
||||||
|
if not account.access(caller, "delete"):
|
||||||
|
self.msg("You don't have the permissions to delete that account.")
|
||||||
|
return
|
||||||
|
username = account.username
|
||||||
|
# ask for confirmation
|
||||||
|
confirm = ("It is often better to block access to an account rather than to delete it. "
|
||||||
|
"|yAre you sure you want to permanently delete "
|
||||||
|
"account '|n{}|y'|n yes/[no]?".format(username))
|
||||||
|
answer = yield(confirm)
|
||||||
|
if answer.lower() not in ('y', 'yes'):
|
||||||
|
caller.msg("Canceled deletion.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Boot the account then delete it.
|
||||||
|
self.msg("Informing and disconnecting account ...")
|
||||||
|
string = "\nYour account '%s' is being *permanently* deleted.\n" % username
|
||||||
|
if reason:
|
||||||
|
string += " Reason given:\n '%s'" % reason
|
||||||
|
account.msg(string)
|
||||||
|
logger.log_sec("Account Deleted: %s (Reason: %s, Caller: %s, IP: %s)." % (account, reason, caller, self.session.address))
|
||||||
|
account.delete()
|
||||||
|
self.msg("Account %s was successfully deleted." % username)
|
||||||
|
return
|
||||||
|
|
||||||
|
# No switches, default to displaying a list of accounts.
|
||||||
if self.args and self.args.isdigit():
|
if self.args and self.args.isdigit():
|
||||||
nlim = int(self.args)
|
nlim = int(self.args)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -475,14 +475,14 @@ class CmdShiftRoot(Command):
|
||||||
root_pos["blue"] -= 1
|
root_pos["blue"] -= 1
|
||||||
self.caller.msg("The root with blue flowers gets in the way and is pushed to the left.")
|
self.caller.msg("The root with blue flowers gets in the way and is pushed to the left.")
|
||||||
else:
|
else:
|
||||||
self.caller.msg("You cannot move the root in that direction.")
|
self.caller.msg("The root hangs straight down - you can only move it left or right.")
|
||||||
elif color == "blue":
|
elif color == "blue":
|
||||||
if direction == "left":
|
if direction == "left":
|
||||||
root_pos[color] = max(-1, root_pos[color] - 1)
|
root_pos[color] = max(-1, root_pos[color] - 1)
|
||||||
self.caller.msg("You shift the root with small blue flowers to the left.")
|
self.caller.msg("You shift the root with small blue flowers to the left.")
|
||||||
if root_pos[color] != 0 and root_pos[color] == root_pos["red"]:
|
if root_pos[color] != 0 and root_pos[color] == root_pos["red"]:
|
||||||
root_pos["red"] += 1
|
root_pos["red"] += 1
|
||||||
self.caller.msg("The reddish root is to big to fit as well, so that one falls away to the left.")
|
self.caller.msg("The reddish root is too big to fit as well, so that one falls away to the left.")
|
||||||
elif direction == "right":
|
elif direction == "right":
|
||||||
root_pos[color] = min(1, root_pos[color] + 1)
|
root_pos[color] = min(1, root_pos[color] + 1)
|
||||||
self.caller.msg("You shove the root adorned with small blue flowers to the right.")
|
self.caller.msg("You shove the root adorned with small blue flowers to the right.")
|
||||||
|
|
@ -490,7 +490,7 @@ class CmdShiftRoot(Command):
|
||||||
root_pos["red"] -= 1
|
root_pos["red"] -= 1
|
||||||
self.caller.msg("The thick reddish root gets in the way and is pushed back to the left.")
|
self.caller.msg("The thick reddish root gets in the way and is pushed back to the left.")
|
||||||
else:
|
else:
|
||||||
self.caller.msg("You cannot move the root in that direction.")
|
self.caller.msg("The root hangs straight down - you can only move it left or right.")
|
||||||
|
|
||||||
# now the horizontal roots (yellow/green). They can be moved up/down
|
# now the horizontal roots (yellow/green). They can be moved up/down
|
||||||
elif color == "yellow":
|
elif color == "yellow":
|
||||||
|
|
@ -507,7 +507,7 @@ class CmdShiftRoot(Command):
|
||||||
root_pos["green"] -= 1
|
root_pos["green"] -= 1
|
||||||
self.caller.msg("The weedy green root is shifted upwards to make room.")
|
self.caller.msg("The weedy green root is shifted upwards to make room.")
|
||||||
else:
|
else:
|
||||||
self.caller.msg("You cannot move the root in that direction.")
|
self.caller.msg("The root hangs across the wall - you can only move it up or down.")
|
||||||
elif color == "green":
|
elif color == "green":
|
||||||
if direction == "up":
|
if direction == "up":
|
||||||
root_pos[color] = max(-1, root_pos[color] - 1)
|
root_pos[color] = max(-1, root_pos[color] - 1)
|
||||||
|
|
@ -522,7 +522,7 @@ class CmdShiftRoot(Command):
|
||||||
root_pos["yellow"] -= 1
|
root_pos["yellow"] -= 1
|
||||||
self.caller.msg("The root with yellow flowers gets in the way and is pushed upwards.")
|
self.caller.msg("The root with yellow flowers gets in the way and is pushed upwards.")
|
||||||
else:
|
else:
|
||||||
self.caller.msg("You cannot move the root in that direction.")
|
self.caller.msg("The root hangs across the wall - you can only move it up or down.")
|
||||||
|
|
||||||
# we have moved the root. Store new position
|
# we have moved the root. Store new position
|
||||||
self.obj.db.root_pos = root_pos
|
self.obj.db.root_pos = root_pos
|
||||||
|
|
|
||||||
|
|
@ -747,9 +747,16 @@ class CmdLookDark(Command):
|
||||||
"""
|
"""
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
|
|
||||||
if random.random() < 0.75:
|
# count how many searches we've done
|
||||||
|
nr_searches = caller.ndb.dark_searches
|
||||||
|
if nr_searches is None:
|
||||||
|
nr_searches = 0
|
||||||
|
caller.ndb.dark_searches = nr_searches
|
||||||
|
|
||||||
|
if nr_searches < 4 and random.random() < 0.90:
|
||||||
# we don't find anything
|
# we don't find anything
|
||||||
caller.msg(random.choice(DARK_MESSAGES))
|
caller.msg(random.choice(DARK_MESSAGES))
|
||||||
|
caller.ndb.dark_searches += 1
|
||||||
else:
|
else:
|
||||||
# we could have found something!
|
# we could have found something!
|
||||||
if any(obj for obj in caller.contents if utils.inherits_from(obj, LightSource)):
|
if any(obj for obj in caller.contents if utils.inherits_from(obj, LightSource)):
|
||||||
|
|
@ -791,7 +798,8 @@ class CmdDarkNoMatch(Command):
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""Implements the command."""
|
"""Implements the command."""
|
||||||
self.caller.msg("Until you find some light, there's not much you can do. Try feeling around.")
|
self.caller.msg("Until you find some light, there's not much you can do. "
|
||||||
|
"Try feeling around, maybe you'll find something helpful!")
|
||||||
|
|
||||||
|
|
||||||
class DarkCmdSet(CmdSet):
|
class DarkCmdSet(CmdSet):
|
||||||
|
|
@ -814,7 +822,9 @@ class DarkCmdSet(CmdSet):
|
||||||
self.add(CmdLookDark())
|
self.add(CmdLookDark())
|
||||||
self.add(CmdDarkHelp())
|
self.add(CmdDarkHelp())
|
||||||
self.add(CmdDarkNoMatch())
|
self.add(CmdDarkNoMatch())
|
||||||
self.add(default_cmds.CmdSay)
|
self.add(default_cmds.CmdSay())
|
||||||
|
self.add(default_cmds.CmdQuit())
|
||||||
|
self.add(default_cmds.CmdHome())
|
||||||
|
|
||||||
|
|
||||||
class DarkRoom(TutorialRoom):
|
class DarkRoom(TutorialRoom):
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ Handling storage of prototypes, both database-based ones (DBPrototypes) and thos
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import hashlib
|
||||||
|
import time
|
||||||
from ast import literal_eval
|
from ast import literal_eval
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia.scripts.scripts import DefaultScript
|
from evennia.scripts.scripts import DefaultScript
|
||||||
|
|
@ -13,7 +15,7 @@ from evennia.objects.models import ObjectDB
|
||||||
from evennia.utils.create import create_script
|
from evennia.utils.create import create_script
|
||||||
from evennia.utils.utils import (
|
from evennia.utils.utils import (
|
||||||
all_from_module, make_iter, is_iter, dbid_to_obj, callables_from_module,
|
all_from_module, make_iter, is_iter, dbid_to_obj, callables_from_module,
|
||||||
get_all_typeclasses, to_str, dbref, justify)
|
get_all_typeclasses, to_str, dbref, justify, class_from_module)
|
||||||
from evennia.locks.lockhandler import validate_lockstring, check_lockstring
|
from evennia.locks.lockhandler import validate_lockstring, check_lockstring
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
from evennia.utils import inlinefuncs, dbserialize
|
from evennia.utils import inlinefuncs, dbserialize
|
||||||
|
|
@ -47,8 +49,8 @@ class ValidationError(RuntimeError):
|
||||||
|
|
||||||
def homogenize_prototype(prototype, custom_keys=None):
|
def homogenize_prototype(prototype, custom_keys=None):
|
||||||
"""
|
"""
|
||||||
Homogenize the more free-form prototype (where undefined keys are non-category attributes)
|
Homogenize the more free-form prototype supported pre Evennia 0.7 into the stricter form.
|
||||||
into the stricter form using `attrs` required by the system.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
prototype (dict): Prototype.
|
prototype (dict): Prototype.
|
||||||
|
|
@ -56,18 +58,45 @@ def homogenize_prototype(prototype, custom_keys=None):
|
||||||
the default reserved keys.
|
the default reserved keys.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
homogenized (dict): Prototype where all non-identified keys grouped as attributes.
|
homogenized (dict): Prototype where all non-identified keys grouped as attributes and other
|
||||||
|
homogenizations like adding missing prototype_keys and setting a default typeclass.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
reserved = _PROTOTYPE_RESERVED_KEYS + (custom_keys or ())
|
reserved = _PROTOTYPE_RESERVED_KEYS + (custom_keys or ())
|
||||||
|
|
||||||
attrs = list(prototype.get('attrs', [])) # break reference
|
attrs = list(prototype.get('attrs', [])) # break reference
|
||||||
|
tags = make_iter(prototype.get('tags', []))
|
||||||
|
homogenized_tags = []
|
||||||
|
|
||||||
homogenized = {}
|
homogenized = {}
|
||||||
for key, val in prototype.items():
|
for key, val in prototype.items():
|
||||||
if key in reserved:
|
if key in reserved:
|
||||||
|
if key == 'tags':
|
||||||
|
for tag in tags:
|
||||||
|
if not is_iter(tag):
|
||||||
|
homogenized_tags.append((tag, None, None))
|
||||||
|
else:
|
||||||
|
homogenized_tags.append(tag)
|
||||||
|
else:
|
||||||
homogenized[key] = val
|
homogenized[key] = val
|
||||||
else:
|
else:
|
||||||
|
# unassigned keys -> attrs
|
||||||
attrs.append((key, val, None, ''))
|
attrs.append((key, val, None, ''))
|
||||||
if attrs:
|
if attrs:
|
||||||
homogenized['attrs'] = attrs
|
homogenized['attrs'] = attrs
|
||||||
|
if homogenized_tags:
|
||||||
|
homogenized['tags'] = homogenized_tags
|
||||||
|
|
||||||
|
# add required missing parts that had defaults before
|
||||||
|
|
||||||
|
if "prototype_key" not in prototype:
|
||||||
|
# assign a random hash as key
|
||||||
|
homogenized["prototype_key"] = "prototype-{}".format(
|
||||||
|
hashlib.md5(str(time.time())).hexdigest()[:7])
|
||||||
|
|
||||||
|
if "typeclass" not in prototype and "prototype_parent" not in prototype:
|
||||||
|
homogenized["typeclass"] = settings.BASE_OBJECT_TYPECLASS
|
||||||
|
|
||||||
return homogenized
|
return homogenized
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -76,9 +105,12 @@ def homogenize_prototype(prototype, custom_keys=None):
|
||||||
for mod in settings.PROTOTYPE_MODULES:
|
for mod in settings.PROTOTYPE_MODULES:
|
||||||
# to remove a default prototype, override it with an empty dict.
|
# to remove a default prototype, override it with an empty dict.
|
||||||
# internally we store as (key, desc, locks, tags, prototype_dict)
|
# internally we store as (key, desc, locks, tags, prototype_dict)
|
||||||
prots = [(prototype_key.lower(), homogenize_prototype(prot))
|
prots = []
|
||||||
for prototype_key, prot in all_from_module(mod).items()
|
for variable_name, prot in all_from_module(mod).items():
|
||||||
if prot and isinstance(prot, dict)]
|
if isinstance(prot, dict):
|
||||||
|
if "prototype_key" not in prot:
|
||||||
|
prot['prototype_key'] = variable_name.lower()
|
||||||
|
prots.append((prot['prototype_key'], homogenize_prototype(prot)))
|
||||||
# assign module path to each prototype_key for easy reference
|
# assign module path to each prototype_key for easy reference
|
||||||
_MODULE_PROTOTYPE_MODULES.update({prototype_key.lower(): mod for prototype_key, _ in prots})
|
_MODULE_PROTOTYPE_MODULES.update({prototype_key.lower(): mod for prototype_key, _ in prots})
|
||||||
# make sure the prototype contains all meta info
|
# make sure the prototype contains all meta info
|
||||||
|
|
@ -432,11 +464,13 @@ def validate_prototype(prototype, protkey=None, protparents=None,
|
||||||
_flags['warnings'].append("Prototype {} can only be used as a mixin since it lacks "
|
_flags['warnings'].append("Prototype {} can only be used as a mixin since it lacks "
|
||||||
"a typeclass or a prototype_parent.".format(protkey))
|
"a typeclass or a prototype_parent.".format(protkey))
|
||||||
|
|
||||||
if (strict and typeclass and typeclass not
|
if strict and typeclass:
|
||||||
in get_all_typeclasses("evennia.objects.models.ObjectDB")):
|
try:
|
||||||
|
class_from_module(typeclass)
|
||||||
|
except ImportError as err:
|
||||||
_flags['errors'].append(
|
_flags['errors'].append(
|
||||||
"Prototype {} is based on typeclass {}, which could not be imported!".format(
|
"{}: Prototype {} is based on typeclass {}, which could not be imported!".format(
|
||||||
protkey, typeclass))
|
err, protkey, typeclass))
|
||||||
|
|
||||||
# recursively traverese prototype_parent chain
|
# recursively traverese prototype_parent chain
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -259,11 +259,11 @@ def prototype_from_object(obj):
|
||||||
if aliases:
|
if aliases:
|
||||||
prot['aliases'] = aliases
|
prot['aliases'] = aliases
|
||||||
tags = [(tag.db_key, tag.db_category, tag.db_data)
|
tags = [(tag.db_key, tag.db_category, tag.db_data)
|
||||||
for tag in obj.tags.get(return_tagobj=True, return_list=True) if tag]
|
for tag in obj.tags.all(return_objs=True)]
|
||||||
if tags:
|
if tags:
|
||||||
prot['tags'] = tags
|
prot['tags'] = tags
|
||||||
attrs = [(attr.key, attr.value, attr.category, ';'.join(attr.locks.all()))
|
attrs = [(attr.key, attr.value, attr.category, ';'.join(attr.locks.all()))
|
||||||
for attr in obj.attributes.get(return_obj=True, return_list=True) if attr]
|
for attr in obj.attributes.all()]
|
||||||
if attrs:
|
if attrs:
|
||||||
prot['attrs'] = attrs
|
prot['attrs'] = attrs
|
||||||
|
|
||||||
|
|
@ -659,6 +659,10 @@ def spawn(*prototypes, **kwargs):
|
||||||
# get available protparents
|
# get available protparents
|
||||||
protparents = {prot['prototype_key'].lower(): prot for prot in protlib.search_prototype()}
|
protparents = {prot['prototype_key'].lower(): prot for prot in protlib.search_prototype()}
|
||||||
|
|
||||||
|
if not kwargs.get("only_validate"):
|
||||||
|
# homogenization to be more lenient about prototype format when entering the prototype manually
|
||||||
|
prototypes = [protlib.homogenize_prototype(prot) for prot in prototypes]
|
||||||
|
|
||||||
# overload module's protparents with specifically given protparents
|
# overload module's protparents with specifically given protparents
|
||||||
# we allow prototype_key to be the key of the protparent dict, to allow for module-level
|
# we allow prototype_key to be the key of the protparent dict, to allow for module-level
|
||||||
# prototype imports. We need to insert prototype_key in this case
|
# prototype imports. We need to insert prototype_key in this case
|
||||||
|
|
@ -711,8 +715,8 @@ def spawn(*prototypes, **kwargs):
|
||||||
|
|
||||||
val = prot.pop("tags", [])
|
val = prot.pop("tags", [])
|
||||||
tags = []
|
tags = []
|
||||||
for (tag, category, data) in tags:
|
for (tag, category, data) in val:
|
||||||
tags.append((init_spawn_value(val, str), category, data))
|
tags.append((init_spawn_value(tag, str), category, data))
|
||||||
|
|
||||||
prototype_key = prototype.get('prototype_key', None)
|
prototype_key = prototype.get('prototype_key', None)
|
||||||
if prototype_key:
|
if prototype_key:
|
||||||
|
|
@ -730,7 +734,7 @@ def spawn(*prototypes, **kwargs):
|
||||||
val = make_iter(prot.pop("attrs", []))
|
val = make_iter(prot.pop("attrs", []))
|
||||||
attributes = []
|
attributes = []
|
||||||
for (attrname, value, category, locks) in val:
|
for (attrname, value, category, locks) in val:
|
||||||
attributes.append((attrname, init_spawn_value(val), category, locks))
|
attributes.append((attrname, init_spawn_value(value), category, locks))
|
||||||
|
|
||||||
simple_attributes = []
|
simple_attributes = []
|
||||||
for key, value in ((key, value) for key, value in prot.items()
|
for key, value in ((key, value) for key, value in prot.items()
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,7 @@ class TestUtils(EvenniaTest):
|
||||||
'prototype_key': Something,
|
'prototype_key': Something,
|
||||||
'prototype_locks': 'spawn:all();edit:all()',
|
'prototype_locks': 'spawn:all();edit:all()',
|
||||||
'prototype_tags': [],
|
'prototype_tags': [],
|
||||||
|
'tags': [(u'footag', u'foocategory', None)],
|
||||||
'typeclass': 'evennia.objects.objects.DefaultObject'})
|
'typeclass': 'evennia.objects.objects.DefaultObject'})
|
||||||
|
|
||||||
self.assertEqual(old_prot,
|
self.assertEqual(old_prot,
|
||||||
|
|
@ -182,6 +183,7 @@ class TestUtils(EvenniaTest):
|
||||||
'typeclass': ('evennia.objects.objects.DefaultObject',
|
'typeclass': ('evennia.objects.objects.DefaultObject',
|
||||||
'evennia.objects.objects.DefaultObject', 'KEEP'),
|
'evennia.objects.objects.DefaultObject', 'KEEP'),
|
||||||
'aliases': {'foo': ('foo', None, 'REMOVE')},
|
'aliases': {'foo': ('foo', None, 'REMOVE')},
|
||||||
|
'tags': {u'footag': ((u'footag', u'foocategory', None), None, 'REMOVE')},
|
||||||
'prototype_desc': ('Built from Obj',
|
'prototype_desc': ('Built from Obj',
|
||||||
'New version of prototype', 'UPDATE'),
|
'New version of prototype', 'UPDATE'),
|
||||||
'permissions': {"Builder": (None, 'Builder', 'ADD')}
|
'permissions': {"Builder": (None, 'Builder', 'ADD')}
|
||||||
|
|
@ -200,6 +202,7 @@ class TestUtils(EvenniaTest):
|
||||||
'prototype_key': 'UPDATE',
|
'prototype_key': 'UPDATE',
|
||||||
'prototype_locks': 'KEEP',
|
'prototype_locks': 'KEEP',
|
||||||
'prototype_tags': 'KEEP',
|
'prototype_tags': 'KEEP',
|
||||||
|
'tags': 'REMOVE',
|
||||||
'typeclass': 'KEEP'}
|
'typeclass': 'KEEP'}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -384,6 +387,7 @@ class TestPrototypeStorage(EvenniaTest):
|
||||||
prot3 = protlib.create_prototype(**self.prot3)
|
prot3 = protlib.create_prototype(**self.prot3)
|
||||||
|
|
||||||
# partial match
|
# partial match
|
||||||
|
with mock.patch("evennia.prototypes.prototypes._MODULE_PROTOTYPES", {}):
|
||||||
self.assertEqual(list(protlib.search_prototype("prot")), [prot1b, prot2, prot3])
|
self.assertEqual(list(protlib.search_prototype("prot")), [prot1b, prot2, prot3])
|
||||||
self.assertEqual(list(protlib.search_prototype(tags="foo1")), [prot1b, prot2, prot3])
|
self.assertEqual(list(protlib.search_prototype(tags="foo1")), [prot1b, prot2, prot3])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -407,7 +407,7 @@ class ServerSession(Session):
|
||||||
else:
|
else:
|
||||||
self.data_out(**kwargs)
|
self.data_out(**kwargs)
|
||||||
|
|
||||||
def execute_cmd(self, raw_string, **kwargs):
|
def execute_cmd(self, raw_string, session=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Do something as this object. This method is normally never
|
Do something as this object. This method is normally never
|
||||||
called directly, instead incoming command instructions are
|
called directly, instead incoming command instructions are
|
||||||
|
|
@ -417,6 +417,9 @@ class ServerSession(Session):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
raw_string (string): Raw command input
|
raw_string (string): Raw command input
|
||||||
|
session (Session): This is here to make API consistent with
|
||||||
|
Account/Object.execute_cmd. If given, data is passed to
|
||||||
|
that Session, otherwise use self.
|
||||||
Kwargs:
|
Kwargs:
|
||||||
Other keyword arguments will be added to the found command
|
Other keyword arguments will be added to the found command
|
||||||
object instace as variables before it executes. This is
|
object instace as variables before it executes. This is
|
||||||
|
|
@ -426,7 +429,7 @@ class ServerSession(Session):
|
||||||
"""
|
"""
|
||||||
# inject instruction into input stream
|
# inject instruction into input stream
|
||||||
kwargs["text"] = ((raw_string,), {})
|
kwargs["text"] = ((raw_string,), {})
|
||||||
self.sessionhandler.data_in(self, **kwargs)
|
self.sessionhandler.data_in(session or self, **kwargs)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
"""Handle session comparisons"""
|
"""Handle session comparisons"""
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ always be sure of what you have changed and what is default behaviour.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from builtins import range
|
from builtins import range
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -697,9 +698,9 @@ ROOT_URLCONF = 'web.urls'
|
||||||
# Where users are redirected after logging in via contrib.auth.login.
|
# Where users are redirected after logging in via contrib.auth.login.
|
||||||
LOGIN_REDIRECT_URL = '/'
|
LOGIN_REDIRECT_URL = '/'
|
||||||
# Where to redirect users when using the @login_required decorator.
|
# Where to redirect users when using the @login_required decorator.
|
||||||
LOGIN_URL = '/accounts/login'
|
LOGIN_URL = reverse_lazy('login')
|
||||||
# Where to redirect users who wish to logout.
|
# Where to redirect users who wish to logout.
|
||||||
LOGOUT_URL = '/accounts/login'
|
LOGOUT_URL = reverse_lazy('logout')
|
||||||
# URL that handles the media served from MEDIA_ROOT.
|
# URL that handles the media served from MEDIA_ROOT.
|
||||||
# Example: "http://media.lawrence.com"
|
# Example: "http://media.lawrence.com"
|
||||||
MEDIA_URL = '/media/'
|
MEDIA_URL = '/media/'
|
||||||
|
|
|
||||||
|
|
@ -668,7 +668,7 @@ class AttributeHandler(object):
|
||||||
|
|
||||||
def all(self, accessing_obj=None, default_access=True):
|
def all(self, accessing_obj=None, default_access=True):
|
||||||
"""
|
"""
|
||||||
Return all Attribute objects on this object.
|
Return all Attribute objects on this object, regardless of category.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
accessing_obj (object, optional): Check the `attrread`
|
accessing_obj (object, optional): Check the `attrread`
|
||||||
|
|
|
||||||
|
|
@ -348,13 +348,14 @@ class TagHandler(object):
|
||||||
self._catcache = {}
|
self._catcache = {}
|
||||||
self._cache_complete = False
|
self._cache_complete = False
|
||||||
|
|
||||||
def all(self, return_key_and_category=False):
|
def all(self, return_key_and_category=False, return_objs=False):
|
||||||
"""
|
"""
|
||||||
Get all tags in this handler, regardless of category.
|
Get all tags in this handler, regardless of category.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
return_key_and_category (bool, optional): Return a list of
|
return_key_and_category (bool, optional): Return a list of
|
||||||
tuples `[(key, category), ...]`.
|
tuples `[(key, category), ...]`.
|
||||||
|
return_objs (bool, optional): Return tag objects.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
tags (list): A list of tag keys `[tagkey, tagkey, ...]` or
|
tags (list): A list of tag keys `[tagkey, tagkey, ...]` or
|
||||||
|
|
@ -368,6 +369,8 @@ class TagHandler(object):
|
||||||
if return_key_and_category:
|
if return_key_and_category:
|
||||||
# return tuple (key, category)
|
# return tuple (key, category)
|
||||||
return [(to_str(tag.db_key), to_str(tag.db_category)) for tag in tags]
|
return [(to_str(tag.db_key), to_str(tag.db_category)) for tag in tags]
|
||||||
|
elif return_objs:
|
||||||
|
return tags
|
||||||
else:
|
else:
|
||||||
return [to_str(tag.db_key) for tag in tags]
|
return [to_str(tag.db_key) for tag in tags]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ let defaultin_plugin = (function () {
|
||||||
//
|
//
|
||||||
// handle the default <enter> key triggering onSend()
|
// handle the default <enter> key triggering onSend()
|
||||||
var onKeydown = function (event) {
|
var onKeydown = function (event) {
|
||||||
|
$("#inputfield").focus();
|
||||||
if ( (event.which === 13) && (!event.shiftKey) ) { // Enter Key without shift
|
if ( (event.which === 13) && (!event.shiftKey) ) { // Enter Key without shift
|
||||||
var inputfield = $("#inputfield");
|
var inputfield = $("#inputfield");
|
||||||
var outtext = inputfield.val();
|
var outtext = inputfield.val();
|
||||||
|
|
|
||||||
|
|
@ -43,14 +43,6 @@ let history_plugin = (function () {
|
||||||
history_pos = 0;
|
history_pos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Go to the last history line
|
|
||||||
var end = function () {
|
|
||||||
// move to the end of the history stack
|
|
||||||
history_pos = 0;
|
|
||||||
return history[history.length -1];
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Add input to the scratch line
|
// Add input to the scratch line
|
||||||
var scratch = function (input) {
|
var scratch = function (input) {
|
||||||
|
|
@ -69,28 +61,17 @@ let history_plugin = (function () {
|
||||||
var history_entry = null;
|
var history_entry = null;
|
||||||
var inputfield = $("#inputfield");
|
var inputfield = $("#inputfield");
|
||||||
|
|
||||||
if (inputfield[0].selectionStart == inputfield.val().length) {
|
|
||||||
// Only process up/down arrow if cursor is at the end of the line.
|
|
||||||
if (code === 38) { // Arrow up
|
if (code === 38) { // Arrow up
|
||||||
history_entry = back();
|
history_entry = back();
|
||||||
}
|
}
|
||||||
else if (code === 40) { // Arrow down
|
else if (code === 40) { // Arrow down
|
||||||
history_entry = fwd();
|
history_entry = fwd();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (history_entry !== null) {
|
if (history_entry !== null) {
|
||||||
// Doing a history navigation; replace the text in the input.
|
// Doing a history navigation; replace the text in the input.
|
||||||
inputfield.val(history_entry);
|
inputfield.val(history_entry);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
// Save the current contents of the input to the history scratch area.
|
|
||||||
setTimeout(function () {
|
|
||||||
// Need to wait until after the key-up to capture the value.
|
|
||||||
scratch(inputfield.val());
|
|
||||||
end();
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -99,6 +80,7 @@ let history_plugin = (function () {
|
||||||
// Listen for onSend lines to add to history
|
// Listen for onSend lines to add to history
|
||||||
var onSend = function (line) {
|
var onSend = function (line) {
|
||||||
add(line);
|
add(line);
|
||||||
|
return null; // we are not returning an altered input line
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue