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):
|
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)
|
user_input = models.CharField(maxlength=50)
|
||||||
equiv_command = 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.
|
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 = (
|
OBJECT_TYPES = (
|
||||||
(0, 'NOTHING'),
|
(0, 'NOTHING'),
|
||||||
(1, 'PLAYER'),
|
(1, 'PLAYER'),
|
||||||
(2, 'ROOM'),
|
(2, 'ROOM'),
|
||||||
(3, 'THING'),
|
(3, 'THING'),
|
||||||
(4, 'EXIT'),
|
(4, 'EXIT'),
|
||||||
|
(5, 'GARBAGE'),
|
||||||
)
|
)
|
||||||
|
|
||||||
name = models.CharField(maxlength=255)
|
name = models.CharField(maxlength=255)
|
||||||
type = models.SmallIntegerField(choices=OBJECT_TYPES)
|
type = models.SmallIntegerField(choices=OBJECT_TYPES)
|
||||||
description = models.TextField(blank=True)
|
description = models.TextField(blank=True)
|
||||||
location = models.ForeignKey('self', related_name="olocation", blank=True, null=True)
|
location = models.ForeignKey('self', related_name="olocation", blank=True, null=True)
|
||||||
contents = models.ManyToManyField("Object", related_name="object", blank=True, null=True)
|
content = models.ManyToManyField("Object", related_name="contents", blank=True, null=True)
|
||||||
attributes = models.ManyToManyField(Attribute, related_name="attributes", blank=True, null=True)
|
attrib = models.ManyToManyField(Attribute, related_name="attributes", blank=True, null=True)
|
||||||
|
attrib_list = {}
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s(%d)" % (self.name, self.id,)
|
return "%s(%d)" % (self.name, self.id,)
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,13 @@ class GenCommands:
|
||||||
"""
|
"""
|
||||||
Generic command class. Pretty much every command should go here for
|
Generic command class. Pretty much every command should go here for
|
||||||
now.
|
now.
|
||||||
"""
|
"""
|
||||||
def __init__(self): pass
|
|
||||||
|
|
||||||
def do_look(self, cdat):
|
def do_look(self, cdat):
|
||||||
"""
|
"""
|
||||||
Handle looking at objects.
|
Handle looking at objects.
|
||||||
"""
|
"""
|
||||||
session = cdat['session']
|
session = cdat['session']
|
||||||
server = session.server
|
server = cdat['server']
|
||||||
player_loc = session.player_loc
|
player_loc = session.player_loc
|
||||||
player_loc_obj = server.object_list[player_loc]
|
player_loc_obj = server.object_list[player_loc]
|
||||||
|
|
||||||
|
|
@ -107,8 +105,8 @@ class StaffCommands:
|
||||||
session = cdat['session']
|
session = cdat['session']
|
||||||
server = cdat['server']
|
server = cdat['server']
|
||||||
|
|
||||||
nextfree = server.nextfree_objnum()
|
nextfree = server.get_nextfree_dbnum()
|
||||||
retval = "Next free object number: %d" % (nextfree,)
|
retval = "Next free object number: %s\n\r" % (nextfree,)
|
||||||
|
|
||||||
session.push(retval)
|
session.push(retval)
|
||||||
|
|
||||||
|
|
@ -116,7 +114,6 @@ class UnLoggedInCommands:
|
||||||
"""
|
"""
|
||||||
Commands that are available from the connect screen.
|
Commands that are available from the connect screen.
|
||||||
"""
|
"""
|
||||||
def __init__(self): pass
|
|
||||||
def do_connect(self, cdat):
|
def do_connect(self, cdat):
|
||||||
"""
|
"""
|
||||||
This is the connect command at the connection screen. Fairly simple,
|
This is the connect command at the connection screen. Fairly simple,
|
||||||
|
|
@ -143,17 +140,18 @@ class UnLoggedInCommands:
|
||||||
Handle the creation of new accounts.
|
Handle the creation of new accounts.
|
||||||
"""
|
"""
|
||||||
session = cdat['session']
|
session = cdat['session']
|
||||||
|
server = cdat['server']
|
||||||
uname = cdat['uinput']['splitted'][1]
|
uname = cdat['uinput']['splitted'][1]
|
||||||
email = cdat['uinput']['splitted'][2]
|
email = cdat['uinput']['splitted'][2]
|
||||||
password = cdat['uinput']['splitted'][3]
|
password = cdat['uinput']['splitted'][3]
|
||||||
account = User.objects.filter(username=uname)
|
account = User.objects.filter(username=uname)
|
||||||
|
|
||||||
if not account.count() == 0:
|
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:
|
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:
|
else:
|
||||||
session.create_user(uname, email, password)
|
server.create_user(session, uname, email, password)
|
||||||
|
|
||||||
def do_quit(self, cdat):
|
def do_quit(self, cdat):
|
||||||
"""
|
"""
|
||||||
|
|
@ -170,8 +168,12 @@ gencommands = GenCommands()
|
||||||
staffcommands = StaffCommands()
|
staffcommands = StaffCommands()
|
||||||
unloggedincommands = UnLoggedInCommands()
|
unloggedincommands = UnLoggedInCommands()
|
||||||
|
|
||||||
|
class UnknownCommand(Exception):
|
||||||
|
"""
|
||||||
|
Throw this when a user enters an an invalid command.
|
||||||
|
"""
|
||||||
|
|
||||||
class Handler:
|
class Handler:
|
||||||
def __init__(self): pass
|
|
||||||
def handle(self, cdat):
|
def handle(self, cdat):
|
||||||
"""
|
"""
|
||||||
Use the spliced (list) uinput variable to retrieve the correct
|
Use the spliced (list) uinput variable to retrieve the correct
|
||||||
|
|
@ -183,40 +185,49 @@ class Handler:
|
||||||
"""
|
"""
|
||||||
session = cdat['session']
|
session = cdat['session']
|
||||||
server = cdat['server']
|
server = cdat['server']
|
||||||
uinput = cdat['uinput'].split()
|
|
||||||
|
|
||||||
parsed_input = {}
|
try:
|
||||||
|
# TODO: Protect against non-standard characters.
|
||||||
# First we split the input up by spaces.
|
if cdat['uinput'] == '':
|
||||||
parsed_input['splitted'] = uinput
|
raise UnknownCommand
|
||||||
# Now we find the root command chunk (with switches attached).
|
|
||||||
parsed_input['root_chunk'] = parsed_input['splitted'][0].split('/')
|
uinput = cdat['uinput'].split()
|
||||||
# And now for the actual root command. It's the first entry in root_chunk.
|
parsed_input = {}
|
||||||
parsed_input['root_cmd'] = parsed_input['root_chunk'][0].lower()
|
|
||||||
|
# First we split the input up by spaces.
|
||||||
# Now we'll see if the user is using an alias. We do a dictionary lookup,
|
parsed_input['splitted'] = uinput
|
||||||
# if the key (the player's root command) doesn't exist on the dict, we
|
# Now we find the root command chunk (with switches attached).
|
||||||
# don't replace the existing root_cmd. If the key exists, its value
|
parsed_input['root_chunk'] = parsed_input['splitted'][0].split('/')
|
||||||
# replaces the previously splitted root_cmd. For example, sa -> say.
|
# And now for the actual root command. It's the first entry in root_chunk.
|
||||||
alias_list = server.cmd_alias_list
|
parsed_input['root_cmd'] = parsed_input['root_chunk'][0].lower()
|
||||||
parsed_input['root_cmd'] = alias_list.get(parsed_input['root_cmd'],parsed_input['root_cmd'])
|
|
||||||
|
# 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 session.logged_in:
|
||||||
# If it's prefixed by an '@', it's a staff command.
|
# If it's prefixed by an '@', it's a staff command.
|
||||||
if parsed_input['root_cmd'][0] != '@':
|
if parsed_input['root_cmd'][0] != '@':
|
||||||
cmdtable = gencommands
|
cmdtable = gencommands
|
||||||
|
else:
|
||||||
|
parsed_input['root_cmd'] = parsed_input['root_cmd'][1:]
|
||||||
|
cmdtable = staffcommands
|
||||||
else:
|
else:
|
||||||
cmdtable = staffcommands
|
cmdtable = unloggedincommands
|
||||||
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.
|
||||||
# cmdtable now equals the command table we need to use. Do a command
|
cmd = getattr(cmdtable, 'do_%s' % (parsed_input['root_cmd'],), None )
|
||||||
# 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
|
||||||
if callable(cmd):
|
cmd(cdat)
|
||||||
cdat['uinput'] = parsed_input
|
else:
|
||||||
cmd(cdat)
|
raise UnknownCommand
|
||||||
else:
|
|
||||||
|
except UnknownCommand:
|
||||||
session.push("Unknown command.\n\r")
|
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 sessions import PlayerSession
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from apps.config.models import ConfigValue, CommandAlias
|
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
|
## Begin: Time Functions
|
||||||
|
|
@ -34,7 +35,7 @@ def Timer(timer):
|
||||||
#
|
#
|
||||||
## End: Time Functions
|
## End: Time Functions
|
||||||
#
|
#
|
||||||
|
|
||||||
class Server(dispatcher):
|
class Server(dispatcher):
|
||||||
"""
|
"""
|
||||||
The main server class from which everything branches.
|
The main server class from which everything branches.
|
||||||
|
|
@ -45,10 +46,15 @@ class Server(dispatcher):
|
||||||
self.cmd_alias_list = {}
|
self.cmd_alias_list = {}
|
||||||
self.configvalue = {}
|
self.configvalue = {}
|
||||||
self.game_running = True
|
self.game_running = True
|
||||||
|
|
||||||
print '-'*50
|
print '-'*50
|
||||||
|
# Load stuff up into memory for easy/quick access.
|
||||||
self.load_configvalues()
|
self.load_configvalues()
|
||||||
self.load_objects()
|
self.load_objects()
|
||||||
|
self.load_attributes()
|
||||||
self.load_cmd_aliases()
|
self.load_cmd_aliases()
|
||||||
|
|
||||||
|
# Start accepting connections.
|
||||||
dispatcher.__init__(self)
|
dispatcher.__init__(self)
|
||||||
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
self.set_reuse_addr()
|
self.set_reuse_addr()
|
||||||
|
|
@ -56,18 +62,10 @@ class Server(dispatcher):
|
||||||
self.listen(100)
|
self.listen(100)
|
||||||
print ' %s started on port %s.' % (self.configvalue['site_name'], self.configvalue['site_port'],)
|
print ' %s started on port %s.' % (self.configvalue['site_name'], self.configvalue['site_port'],)
|
||||||
print '-'*50
|
print '-'*50
|
||||||
|
|
||||||
def announce_all(self, message, with_ann_prefix=True):
|
"""
|
||||||
"""
|
BEGIN SERVER STARTUP METHODS
|
||||||
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 load_configvalues(self):
|
def load_configvalues(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -88,7 +86,16 @@ class Server(dispatcher):
|
||||||
for object in object_list:
|
for object in object_list:
|
||||||
dbnum = object.id
|
dbnum = object.id
|
||||||
self.object_list[dbnum] = object
|
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):
|
def load_cmd_aliases(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -109,7 +116,76 @@ class Server(dispatcher):
|
||||||
print 'Connection:', str(session)
|
print 'Connection:', str(session)
|
||||||
self.session_list.append(session)
|
self.session_list.append(session)
|
||||||
print 'Sessions active:', len(self.session_list)
|
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__':
|
if __name__ == '__main__':
|
||||||
server = Server()
|
server = Server()
|
||||||
|
|
||||||
|
|
@ -119,5 +195,5 @@ if __name__ == '__main__':
|
||||||
Timer(time.time())
|
Timer(time.time())
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
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.'
|
print '--> Server killed by keystroke.'
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ from asyncore import dispatcher
|
||||||
from asynchat import async_chat
|
from asynchat import async_chat
|
||||||
import socket, asyncore, time, sys
|
import socket, asyncore, time, sys
|
||||||
from cmdhandler import *
|
from cmdhandler import *
|
||||||
|
from apps.objects.models import Object
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
chandler = Handler()
|
chandler = Handler()
|
||||||
|
|
||||||
|
|
@ -27,7 +29,7 @@ class PlayerSession(async_chat):
|
||||||
# The time when the user connected.
|
# The time when the user connected.
|
||||||
self.conn_time = time.time()
|
self.conn_time = time.time()
|
||||||
# Player's room location. Move this to a player sub-class.
|
# 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):
|
def collect_incoming_data(self, data):
|
||||||
"""
|
"""
|
||||||
|
|
@ -85,22 +87,7 @@ class PlayerSession(async_chat):
|
||||||
self.conn_time = time.time()
|
self.conn_time = time.time()
|
||||||
self.push("Logging in as %s.\n\r" % (self.name,))
|
self.push("Logging in as %s.\n\r" % (self.name,))
|
||||||
print "Login: %s" % (self,)
|
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):
|
def __str__(self):
|
||||||
"""
|
"""
|
||||||
String representation of the user session class. We use
|
String representation of the user session class. We use
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue