From 2cd8c50f3dc37d0e99826d280bb8db02e281c9be Mon Sep 17 00:00:00 2001 From: Greg Taylor Date: Tue, 28 Nov 2006 21:51:36 +0000 Subject: [PATCH] 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. --- evennia/trunk/apps/config/models.py | 3 +- evennia/trunk/apps/objects/models.py | 8 +- evennia/trunk/cmdhandler.py | 97 +++++++++++++----------- evennia/trunk/commonfuncs.py | 4 + evennia/trunk/server.py | 108 +++++++++++++++++++++++---- evennia/trunk/sessions.py | 21 +----- 6 files changed, 161 insertions(+), 80 deletions(-) diff --git a/evennia/trunk/apps/config/models.py b/evennia/trunk/apps/config/models.py index 06d0829b38..42668d3a33 100755 --- a/evennia/trunk/apps/config/models.py +++ b/evennia/trunk/apps/config/models.py @@ -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) diff --git a/evennia/trunk/apps/objects/models.py b/evennia/trunk/apps/objects/models.py index f1913ad301..7ca62a39ac 100755 --- a/evennia/trunk/apps/objects/models.py +++ b/evennia/trunk/apps/objects/models.py @@ -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,) diff --git a/evennia/trunk/cmdhandler.py b/evennia/trunk/cmdhandler.py index 5bd3063eaa..f27b217cf1 100755 --- a/evennia/trunk/cmdhandler.py +++ b/evennia/trunk/cmdhandler.py @@ -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") diff --git a/evennia/trunk/commonfuncs.py b/evennia/trunk/commonfuncs.py index e69de29bb2..d2c628de4d 100755 --- a/evennia/trunk/commonfuncs.py +++ b/evennia/trunk/commonfuncs.py @@ -0,0 +1,4 @@ +from apps.objects.models import Object, Attribute + +def object_find_neighbor(searcher, target_string): + pass diff --git a/evennia/trunk/server.py b/evennia/trunk/server.py index 3a9039555b..81d335e77b 100755 --- a/evennia/trunk/server.py +++ b/evennia/trunk/server.py @@ -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.' diff --git a/evennia/trunk/sessions.py b/evennia/trunk/sessions.py index 76523c1d36..0e286fe1f7 100755 --- a/evennia/trunk/sessions.py +++ b/evennia/trunk/sessions.py @@ -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