mirror of
https://github.com/evennia/evennia.git
synced 2026-03-17 05:16:31 +01:00
Pretty sizable update.
* Finished the major work on the command interpreter. Still needs to do some stripping of funky characters for the sake of security, probably. * Account creation is now syncd up with the regular object list. * We can now determine our next free database reference number for the creation of new objects, very similar to MUX/MUSH. * Added some of the stuff we're going to need for efficient recycling of garbage objects. * Misc. tweaks and improvements across the board.
This commit is contained in:
parent
0ac644aef2
commit
2cd8c50f3d
6 changed files with 161 additions and 80 deletions
|
|
@ -2,7 +2,8 @@ from django.db import models
|
|||
|
||||
class CommandAlias(models.Model):
|
||||
"""
|
||||
Command aliases.
|
||||
Command aliases. If the player enters the value equal to user_input, the
|
||||
command denoted by equiv_command is used instead.
|
||||
"""
|
||||
user_input = models.CharField(maxlength=50)
|
||||
equiv_command = models.CharField(maxlength=50)
|
||||
|
|
|
|||
|
|
@ -40,21 +40,23 @@ class Object(models.Model):
|
|||
field. The different otypes denote an object's behaviors.
|
||||
"""
|
||||
|
||||
# Do not mess with the default types (0-4).
|
||||
# Do not mess with the default types (0-5).
|
||||
OBJECT_TYPES = (
|
||||
(0, 'NOTHING'),
|
||||
(1, 'PLAYER'),
|
||||
(2, 'ROOM'),
|
||||
(3, 'THING'),
|
||||
(4, 'EXIT'),
|
||||
(5, 'GARBAGE'),
|
||||
)
|
||||
|
||||
name = models.CharField(maxlength=255)
|
||||
type = models.SmallIntegerField(choices=OBJECT_TYPES)
|
||||
description = models.TextField(blank=True)
|
||||
location = models.ForeignKey('self', related_name="olocation", blank=True, null=True)
|
||||
contents = models.ManyToManyField("Object", related_name="object", blank=True, null=True)
|
||||
attributes = models.ManyToManyField(Attribute, related_name="attributes", blank=True, null=True)
|
||||
content = models.ManyToManyField("Object", related_name="contents", blank=True, null=True)
|
||||
attrib = models.ManyToManyField(Attribute, related_name="attributes", blank=True, null=True)
|
||||
attrib_list = {}
|
||||
|
||||
def __str__(self):
|
||||
return "%s(%d)" % (self.name, self.id,)
|
||||
|
|
|
|||
|
|
@ -13,15 +13,13 @@ class GenCommands:
|
|||
"""
|
||||
Generic command class. Pretty much every command should go here for
|
||||
now.
|
||||
"""
|
||||
def __init__(self): pass
|
||||
|
||||
"""
|
||||
def do_look(self, cdat):
|
||||
"""
|
||||
Handle looking at objects.
|
||||
"""
|
||||
session = cdat['session']
|
||||
server = session.server
|
||||
server = cdat['server']
|
||||
player_loc = session.player_loc
|
||||
player_loc_obj = server.object_list[player_loc]
|
||||
|
||||
|
|
@ -107,8 +105,8 @@ class StaffCommands:
|
|||
session = cdat['session']
|
||||
server = cdat['server']
|
||||
|
||||
nextfree = server.nextfree_objnum()
|
||||
retval = "Next free object number: %d" % (nextfree,)
|
||||
nextfree = server.get_nextfree_dbnum()
|
||||
retval = "Next free object number: %s\n\r" % (nextfree,)
|
||||
|
||||
session.push(retval)
|
||||
|
||||
|
|
@ -116,7 +114,6 @@ class UnLoggedInCommands:
|
|||
"""
|
||||
Commands that are available from the connect screen.
|
||||
"""
|
||||
def __init__(self): pass
|
||||
def do_connect(self, cdat):
|
||||
"""
|
||||
This is the connect command at the connection screen. Fairly simple,
|
||||
|
|
@ -143,17 +140,18 @@ class UnLoggedInCommands:
|
|||
Handle the creation of new accounts.
|
||||
"""
|
||||
session = cdat['session']
|
||||
server = cdat['server']
|
||||
uname = cdat['uinput']['splitted'][1]
|
||||
email = cdat['uinput']['splitted'][2]
|
||||
password = cdat['uinput']['splitted'][3]
|
||||
account = User.objects.filter(username=uname)
|
||||
|
||||
if not account.count() == 0:
|
||||
session.push("There is already a player with that name!")
|
||||
session.push("There is already a player with that name!\n\r")
|
||||
elif len(password) < 3:
|
||||
session.push("Your password must be 3 characters or longer.")
|
||||
session.push("Your password must be 3 characters or longer.\n\r")
|
||||
else:
|
||||
session.create_user(uname, email, password)
|
||||
server.create_user(session, uname, email, password)
|
||||
|
||||
def do_quit(self, cdat):
|
||||
"""
|
||||
|
|
@ -170,8 +168,12 @@ gencommands = GenCommands()
|
|||
staffcommands = StaffCommands()
|
||||
unloggedincommands = UnLoggedInCommands()
|
||||
|
||||
class UnknownCommand(Exception):
|
||||
"""
|
||||
Throw this when a user enters an an invalid command.
|
||||
"""
|
||||
|
||||
class Handler:
|
||||
def __init__(self): pass
|
||||
def handle(self, cdat):
|
||||
"""
|
||||
Use the spliced (list) uinput variable to retrieve the correct
|
||||
|
|
@ -183,40 +185,49 @@ class Handler:
|
|||
"""
|
||||
session = cdat['session']
|
||||
server = cdat['server']
|
||||
uinput = cdat['uinput'].split()
|
||||
|
||||
parsed_input = {}
|
||||
|
||||
# First we split the input up by spaces.
|
||||
parsed_input['splitted'] = uinput
|
||||
# Now we find the root command chunk (with switches attached).
|
||||
parsed_input['root_chunk'] = parsed_input['splitted'][0].split('/')
|
||||
# And now for the actual root command. It's the first entry in root_chunk.
|
||||
parsed_input['root_cmd'] = parsed_input['root_chunk'][0].lower()
|
||||
|
||||
# Now we'll see if the user is using an alias. We do a dictionary lookup,
|
||||
# if the key (the player's root command) doesn't exist on the dict, we
|
||||
# don't replace the existing root_cmd. If the key exists, its value
|
||||
# replaces the previously splitted root_cmd. For example, sa -> say.
|
||||
alias_list = server.cmd_alias_list
|
||||
parsed_input['root_cmd'] = alias_list.get(parsed_input['root_cmd'],parsed_input['root_cmd'])
|
||||
try:
|
||||
# TODO: Protect against non-standard characters.
|
||||
if cdat['uinput'] == '':
|
||||
raise UnknownCommand
|
||||
|
||||
uinput = cdat['uinput'].split()
|
||||
parsed_input = {}
|
||||
|
||||
# First we split the input up by spaces.
|
||||
parsed_input['splitted'] = uinput
|
||||
# Now we find the root command chunk (with switches attached).
|
||||
parsed_input['root_chunk'] = parsed_input['splitted'][0].split('/')
|
||||
# And now for the actual root command. It's the first entry in root_chunk.
|
||||
parsed_input['root_cmd'] = parsed_input['root_chunk'][0].lower()
|
||||
|
||||
# Now we'll see if the user is using an alias. We do a dictionary lookup,
|
||||
# if the key (the player's root command) doesn't exist on the dict, we
|
||||
# don't replace the existing root_cmd. If the key exists, its value
|
||||
# replaces the previously splitted root_cmd. For example, sa -> say.
|
||||
alias_list = server.cmd_alias_list
|
||||
parsed_input['root_cmd'] = alias_list.get(parsed_input['root_cmd'],parsed_input['root_cmd'])
|
||||
|
||||
if session.logged_in:
|
||||
# If it's prefixed by an '@', it's a staff command.
|
||||
if parsed_input['root_cmd'][0] != '@':
|
||||
cmdtable = gencommands
|
||||
if session.logged_in:
|
||||
# If it's prefixed by an '@', it's a staff command.
|
||||
if parsed_input['root_cmd'][0] != '@':
|
||||
cmdtable = gencommands
|
||||
else:
|
||||
parsed_input['root_cmd'] = parsed_input['root_cmd'][1:]
|
||||
cmdtable = staffcommands
|
||||
else:
|
||||
cmdtable = staffcommands
|
||||
else:
|
||||
cmdtable = unloggedincommands
|
||||
|
||||
# cmdtable now equals the command table we need to use. Do a command
|
||||
# lookup for a particular function based on the user's input.
|
||||
cmd = getattr(cmdtable, 'do_' + parsed_input['root_cmd'], None )
|
||||
|
||||
if callable(cmd):
|
||||
cdat['uinput'] = parsed_input
|
||||
cmd(cdat)
|
||||
else:
|
||||
cmdtable = unloggedincommands
|
||||
|
||||
# cmdtable now equals the command table we need to use. Do a command
|
||||
# lookup for a particular function based on the user's input.
|
||||
cmd = getattr(cmdtable, 'do_%s' % (parsed_input['root_cmd'],), None )
|
||||
|
||||
if callable(cmd):
|
||||
cdat['uinput'] = parsed_input
|
||||
cmd(cdat)
|
||||
else:
|
||||
raise UnknownCommand
|
||||
|
||||
except UnknownCommand:
|
||||
session.push("Unknown command.\n\r")
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
from apps.objects.models import Object, Attribute
|
||||
|
||||
def object_find_neighbor(searcher, target_string):
|
||||
pass
|
||||
|
|
@ -4,7 +4,8 @@ import socket, asyncore, time, sys
|
|||
from sessions import PlayerSession
|
||||
from django.db import models
|
||||
from apps.config.models import ConfigValue, CommandAlias
|
||||
from apps.objects.models import Object
|
||||
from apps.objects.models import Object, Attribute
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
#
|
||||
## Begin: Time Functions
|
||||
|
|
@ -34,7 +35,7 @@ def Timer(timer):
|
|||
#
|
||||
## End: Time Functions
|
||||
#
|
||||
|
||||
|
||||
class Server(dispatcher):
|
||||
"""
|
||||
The main server class from which everything branches.
|
||||
|
|
@ -45,10 +46,15 @@ class Server(dispatcher):
|
|||
self.cmd_alias_list = {}
|
||||
self.configvalue = {}
|
||||
self.game_running = True
|
||||
|
||||
print '-'*50
|
||||
# Load stuff up into memory for easy/quick access.
|
||||
self.load_configvalues()
|
||||
self.load_objects()
|
||||
self.load_attributes()
|
||||
self.load_cmd_aliases()
|
||||
|
||||
# Start accepting connections.
|
||||
dispatcher.__init__(self)
|
||||
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.set_reuse_addr()
|
||||
|
|
@ -56,18 +62,10 @@ class Server(dispatcher):
|
|||
self.listen(100)
|
||||
print ' %s started on port %s.' % (self.configvalue['site_name'], self.configvalue['site_port'],)
|
||||
print '-'*50
|
||||
|
||||
def announce_all(self, message, with_ann_prefix=True):
|
||||
"""
|
||||
Announces something to all connected players.
|
||||
"""
|
||||
if with_ann_prefix:
|
||||
prefix = 'Announcement:'
|
||||
else:
|
||||
prefix = ''
|
||||
|
||||
for session in self.session_list:
|
||||
session.push('%s %s' % (prefix, message,))
|
||||
|
||||
"""
|
||||
BEGIN SERVER STARTUP METHODS
|
||||
"""
|
||||
|
||||
def load_configvalues(self):
|
||||
"""
|
||||
|
|
@ -88,7 +86,16 @@ class Server(dispatcher):
|
|||
for object in object_list:
|
||||
dbnum = object.id
|
||||
self.object_list[dbnum] = object
|
||||
print ' Objects Loaded: %i' % (len(self.object_list),)
|
||||
print ' Objects Loaded: %d' % (len(self.object_list),)
|
||||
|
||||
def load_attributes(self):
|
||||
"""
|
||||
Load all of our attributes into memory.
|
||||
"""
|
||||
attribute_list = Attribute.objects.all()
|
||||
for attrib in attribute_list:
|
||||
attrib.object.attrib_list[attrib.name] = attrib.value
|
||||
print ' Attributes Loaded: %d' % (len(attribute_list),)
|
||||
|
||||
def load_cmd_aliases(self):
|
||||
"""
|
||||
|
|
@ -109,7 +116,76 @@ class Server(dispatcher):
|
|||
print 'Connection:', str(session)
|
||||
self.session_list.append(session)
|
||||
print 'Sessions active:', len(self.session_list)
|
||||
|
||||
"""
|
||||
BEGIN GENERAL METHODS
|
||||
"""
|
||||
def create_user(self, session, uname, email, password):
|
||||
"""
|
||||
Handles the creation of new users.
|
||||
"""
|
||||
start_room = int(self.get_configvalue('player_dbnum_start'))
|
||||
start_room_obj = self.object_list[start_room]
|
||||
|
||||
# The user's entry in the User table must match up to an object
|
||||
# on the object table. The id's are the same, we need to figure out
|
||||
# the next free unique ID to use and make sure the two entries are
|
||||
# the same number.
|
||||
uid = self.get_nextfree_dbnum()
|
||||
user = User.objects.create_user(uname, email, password)
|
||||
# It stinks to have to do this but it's the only trivial way now.
|
||||
user.id = uid
|
||||
user.save
|
||||
|
||||
# Create a player object of the same ID in the Objects table.
|
||||
user_object = Object(id=uid, type=1, name=uname, location=start_room_obj)
|
||||
user_object.save()
|
||||
|
||||
# Activate the player's session and set them loose.
|
||||
session.login(user)
|
||||
print 'Registration: %s' % (session,)
|
||||
session.push("Welcome to %s, %s.\n\r" % (self.get_configvalue('site_name'), session.name,))
|
||||
|
||||
def announce_all(self, message, with_ann_prefix=True):
|
||||
"""
|
||||
Announces something to all connected players.
|
||||
"""
|
||||
if with_ann_prefix:
|
||||
prefix = 'Announcement:'
|
||||
else:
|
||||
prefix = ''
|
||||
|
||||
for session in self.session_list:
|
||||
session.push('%s %s' % (prefix, message,))
|
||||
|
||||
def get_configvalue(self, configname):
|
||||
"""
|
||||
Retrieve a configuration value.
|
||||
"""
|
||||
return self.configvalue[configname]
|
||||
|
||||
def get_nextfree_dbnum(self):
|
||||
"""
|
||||
Figure out what our next free database reference number is.
|
||||
"""
|
||||
# First we'll see if there's an object of type 5 (GARBAGE) that we
|
||||
# can recycle.
|
||||
nextfree = Object.objects.filter(type__exact=5)
|
||||
if nextfree:
|
||||
# We've got at least one garbage object to recycle.
|
||||
#return nextfree.id
|
||||
return nextfree[0].id
|
||||
else:
|
||||
# No garbage to recycle, find the highest dbnum and increment it
|
||||
# for our next free.
|
||||
return Object.objects.order_by('-id')[0].id + 1
|
||||
"""
|
||||
END Server CLASS
|
||||
"""
|
||||
|
||||
"""
|
||||
BEGIN MAIN APPLICATION LOGIC
|
||||
"""
|
||||
if __name__ == '__main__':
|
||||
server = Server()
|
||||
|
||||
|
|
@ -119,5 +195,5 @@ if __name__ == '__main__':
|
|||
Timer(time.time())
|
||||
|
||||
except KeyboardInterrupt:
|
||||
server.announce_all('The server has been shutdown. Please check back soon.')
|
||||
server.announce_all('The server has been shutdown. Please check back soon.\n\r')
|
||||
print '--> Server killed by keystroke.'
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ from asyncore import dispatcher
|
|||
from asynchat import async_chat
|
||||
import socket, asyncore, time, sys
|
||||
from cmdhandler import *
|
||||
from apps.objects.models import Object
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
chandler = Handler()
|
||||
|
||||
|
|
@ -27,7 +29,7 @@ class PlayerSession(async_chat):
|
|||
# The time when the user connected.
|
||||
self.conn_time = time.time()
|
||||
# Player's room location. Move this to a player sub-class.
|
||||
self.player_loc = 4
|
||||
self.player_loc = 1
|
||||
|
||||
def collect_incoming_data(self, data):
|
||||
"""
|
||||
|
|
@ -85,22 +87,7 @@ class PlayerSession(async_chat):
|
|||
self.conn_time = time.time()
|
||||
self.push("Logging in as %s.\n\r" % (self.name,))
|
||||
print "Login: %s" % (self,)
|
||||
|
||||
def create_user(self, uname, email, password):
|
||||
"""
|
||||
Handles the creation of new users.
|
||||
"""
|
||||
# print uname, email, password
|
||||
user = User.objects.create_user(uname, email, password)
|
||||
self.login(user)
|
||||
print 'Registration: %s' % (self,)
|
||||
self.push("Welcome to the game, %s.\n\r" % (self.name,))
|
||||
|
||||
def nextfree_objnum(self):
|
||||
"""
|
||||
Returns the next free object number.
|
||||
"""
|
||||
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
String representation of the user session class. We use
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue