Initial import.
This commit is contained in:
parent
24ead8690c
commit
2421c23521
30 changed files with 773 additions and 0 deletions
74
evennia/ABOUT
Executable file
74
evennia/ABOUT
Executable file
|
|
@ -0,0 +1,74 @@
|
||||||
|
Evennia Proof-of-Concept
|
||||||
|
------------------------
|
||||||
|
Evennia is a proof-of-concept MUD server written entirely in Python, backed
|
||||||
|
by SQL. The project rises from a general dissatisfaction with the limitations
|
||||||
|
of softcode in MUX and MUSH, and the generally inflexible Diku-derivatives and
|
||||||
|
relatives.
|
||||||
|
|
||||||
|
Evennia represents a combination of several technologies, and most importantly
|
||||||
|
of all, my first venture into codebase design. You may find things within
|
||||||
|
the source that look strange to you, perhaps not ideally designed. I'm open
|
||||||
|
to suggestions, but this really is largely an experiment and a learning
|
||||||
|
experience.
|
||||||
|
|
||||||
|
Design Objectives
|
||||||
|
-----------------
|
||||||
|
1) To create a MU* server that serves as a great foundation for capable admins
|
||||||
|
to craft into their respective games. It is not my intention to provide a
|
||||||
|
full-fledged, ready-to-run base, I'm releasing the means to make such games.
|
||||||
|
|
||||||
|
2) Development of games on Evennia must be easy for anyone with some degree
|
||||||
|
of Python experience. Building needs to be easy, and per-room, per-object,
|
||||||
|
and environmental customizations need to be simple to do.
|
||||||
|
|
||||||
|
3) The server must utilize SQL as a storage back-end to allow for web->game
|
||||||
|
integration. See the details on Django later on in the document for more
|
||||||
|
details.
|
||||||
|
|
||||||
|
4) Any and all game-specific configuration must reside in SQL, not
|
||||||
|
external configuration files. The only exception is the settings.py file
|
||||||
|
containing the SQL information.
|
||||||
|
|
||||||
|
How it all Works
|
||||||
|
----------------
|
||||||
|
Python (Including the SQL driver of your choice)
|
||||||
|
|-asynchat (included with Python2)
|
||||||
|
|-SQL (MySQL, SQLite, Postgresql)
|
||||||
|
|-Django (http://djangoproject.com)
|
||||||
|
|
||||||
|
Evennia is built on top of asynchat, an asynchronous TCP conversation/chat
|
||||||
|
library. This makes the actual socket/connection handling an absolute
|
||||||
|
no-brainer.
|
||||||
|
|
||||||
|
Serving as our storage medium, SQL is one of the more important and unique
|
||||||
|
features of the codebase. It allows for very simple code in many cases, and
|
||||||
|
can lead to a game being a lot more scalable due to the inherent speed of
|
||||||
|
most modern SQL servers. Another extremely important benefit is that by
|
||||||
|
storing everything in SQL, we make the entire game accessible from other
|
||||||
|
means, such as a website. Which leads us to the next component.
|
||||||
|
|
||||||
|
Django is perhaps one of the most interesting introductions to the codebase,
|
||||||
|
since I'm not aware of any other server using it to run MU*'s. Django is
|
||||||
|
technically a Python web framework, but it also includes a great data modeling
|
||||||
|
and database abstraction module. This means that things like Players or
|
||||||
|
Objects can be represented by a very short class, then related to one another.
|
||||||
|
This allows us to add, remove, delete, and manipulate things in our database
|
||||||
|
very easily. Another huge benefit is the admin interface that Django more
|
||||||
|
or less automatically generates for us. Instead of a bunch of clunky admin
|
||||||
|
commands, you can fire up your web browser and administer pretty much
|
||||||
|
everything from there, although equivalent in-game commands may be offered.
|
||||||
|
The possibilities for developing your game's website are nearly endless with
|
||||||
|
this tandem of MU* server, SQL, and Django.
|
||||||
|
|
||||||
|
Support
|
||||||
|
-------
|
||||||
|
At this time, I am offering no formal support for Evennia. It is not ready for
|
||||||
|
use and is subject to change in a major way from week to week. I can't hope
|
||||||
|
to support such a young product. However, if you have questions or ideas,
|
||||||
|
please direct them to squishywaffle@gmail.com.
|
||||||
|
|
||||||
|
Reporting Bugs
|
||||||
|
--------------
|
||||||
|
Feel free to contact me by email at squishywaffle@gmail.com with as much
|
||||||
|
details on the bug that you can find. Copy/pasting server logs is generally
|
||||||
|
a good idea.
|
||||||
16
evennia/README
Executable file
16
evennia/README
Executable file
|
|
@ -0,0 +1,16 @@
|
||||||
|
Starting the Server
|
||||||
|
-------------------
|
||||||
|
Prior to starting up Evennia, you'll need the following environmental variable
|
||||||
|
set.
|
||||||
|
|
||||||
|
export DJANGO_SETTINGS_MODULE="settings"
|
||||||
|
|
||||||
|
You may wish to put this in your .bashrc file, or you can simple copy/paste
|
||||||
|
it before each startup. I'll fix this later so you don't have to, but it'll
|
||||||
|
do for now.
|
||||||
|
|
||||||
|
Once you've got the evar set, simply enter the following:
|
||||||
|
|
||||||
|
python server.py
|
||||||
|
|
||||||
|
The default port is 4000.
|
||||||
0
evennia/__init__.py
Executable file
0
evennia/__init__.py
Executable file
BIN
evennia/__init__.pyc
Executable file
BIN
evennia/__init__.pyc
Executable file
Binary file not shown.
36
evennia/ansi.py
Executable file
36
evennia/ansi.py
Executable file
|
|
@ -0,0 +1,36 @@
|
||||||
|
"""
|
||||||
|
ANSI related stuff.
|
||||||
|
"""
|
||||||
|
ansi = {}
|
||||||
|
ansi["beep"] = "\07"
|
||||||
|
ansi["escape"] = "\033"
|
||||||
|
ansi["normal"] = "\033[0m"
|
||||||
|
|
||||||
|
ansi["underline"] = "\033[4m"
|
||||||
|
ansi["hilite"] = "\033[1m"
|
||||||
|
ansi["blink"] = "\033[5m"
|
||||||
|
ansi["inverse"] = "\033[7m"
|
||||||
|
ansi["inv_hilite"] = "\033[1;7m"
|
||||||
|
ansi["inv_blink"] = "\033[7;5m"
|
||||||
|
ansi["blink_hilite"] = "\033[1;5m"
|
||||||
|
ansi["inv_blink_hilite"] = "\033[1;5;7m"
|
||||||
|
|
||||||
|
# Foreground colors
|
||||||
|
ansi["black"] = "\033[30m"
|
||||||
|
ansi["red"] = "\033[31m"
|
||||||
|
ansi["green"] = "\033[32m"
|
||||||
|
ansi["yellow"] = "\033[33m"
|
||||||
|
ansi["blue"] = "\033[34m"
|
||||||
|
ansi["magenta"] = "\033[35m"
|
||||||
|
ansi["cyan"] = "\033[36m"
|
||||||
|
ansi["white"] = "\033[37m"
|
||||||
|
|
||||||
|
# Background colors
|
||||||
|
ansi["back_black"] = "\033[40m"
|
||||||
|
ansi["back_red"] = "\033[41m"
|
||||||
|
ansi["back_green"] = "\033[42m"
|
||||||
|
ansi["back_yellow"] = "\033[43m"
|
||||||
|
ansi["back_blue"] = "\033[44m"
|
||||||
|
ansi["back_magenta"] = "\033[45m"
|
||||||
|
ansi["back_cyan"] = "\033[46m"
|
||||||
|
ansi["back_white"] = "\033[47m"
|
||||||
BIN
evennia/ansi.pyc
Executable file
BIN
evennia/ansi.pyc
Executable file
Binary file not shown.
0
evennia/apps/__init__.py
Executable file
0
evennia/apps/__init__.py
Executable file
BIN
evennia/apps/__init__.pyc
Executable file
BIN
evennia/apps/__init__.pyc
Executable file
Binary file not shown.
0
evennia/apps/config/__init__.py
Executable file
0
evennia/apps/config/__init__.py
Executable file
BIN
evennia/apps/config/__init__.pyc
Executable file
BIN
evennia/apps/config/__init__.pyc
Executable file
Binary file not shown.
27
evennia/apps/config/models.py
Executable file
27
evennia/apps/config/models.py
Executable file
|
|
@ -0,0 +1,27 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class CommandAlias(models.Model):
|
||||||
|
"""
|
||||||
|
Command aliases.
|
||||||
|
"""
|
||||||
|
user_input = models.CharField(maxlength=50)
|
||||||
|
equiv_command = models.CharField(maxlength=50)
|
||||||
|
|
||||||
|
class Admin:
|
||||||
|
list_display = ('user_input', 'equiv_command',)
|
||||||
|
|
||||||
|
class Config(models.Model):
|
||||||
|
"""
|
||||||
|
Although we technically have the ability to create more than one Config
|
||||||
|
object via the admin interface, we only really need one. This also leaves
|
||||||
|
the possibility for multiple games hosted on the same codebase or database
|
||||||
|
in the future, although this is not a priority. In any case, this model
|
||||||
|
contains most of the game-specific configuration.
|
||||||
|
"""
|
||||||
|
site_name = models.CharField(maxlength=100)
|
||||||
|
site_description = models.TextField(blank=True)
|
||||||
|
site_website = models.URLField(blank=True)
|
||||||
|
player_start_dbnum = models.IntegerField()
|
||||||
|
|
||||||
|
class Admin:
|
||||||
|
list_display = ('site_name', 'site_website',)
|
||||||
BIN
evennia/apps/config/models.pyc
Executable file
BIN
evennia/apps/config/models.pyc
Executable file
Binary file not shown.
1
evennia/apps/config/views.py
Executable file
1
evennia/apps/config/views.py
Executable file
|
|
@ -0,0 +1 @@
|
||||||
|
# Create your views here.
|
||||||
0
evennia/apps/objects/__init__.py
Executable file
0
evennia/apps/objects/__init__.py
Executable file
BIN
evennia/apps/objects/__init__.pyc
Executable file
BIN
evennia/apps/objects/__init__.pyc
Executable file
Binary file not shown.
92
evennia/apps/objects/models.py
Executable file
92
evennia/apps/objects/models.py
Executable file
|
|
@ -0,0 +1,92 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
class ObjectClass(models.Model):
|
||||||
|
"""
|
||||||
|
Each object class can have different behaviors to apply to it.
|
||||||
|
"""
|
||||||
|
name = models.CharField(maxlength=255)
|
||||||
|
description = models.TextField()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%s(%d)" % (self.name, self.id,)
|
||||||
|
|
||||||
|
class Admin:
|
||||||
|
list_display = ('name', 'description',)
|
||||||
|
|
||||||
|
class Attribute(models.Model):
|
||||||
|
"""
|
||||||
|
Attributes are things that are specific to different types of objects. For
|
||||||
|
example, a drink container needs to store its fill level, whereas an exit
|
||||||
|
needs to store its open/closed/locked/unlocked state. These are done via
|
||||||
|
attributes, rather than making different classes for each object type and
|
||||||
|
storing them directly. The added benefit is that we can add/remove attributes
|
||||||
|
on the fly as we like.
|
||||||
|
"""
|
||||||
|
name = models.CharField(maxlength=255)
|
||||||
|
value = models.CharField(maxlength=255)
|
||||||
|
object = models.ForeignKey("Object")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%s(%d)" % (self.name, self.id,)
|
||||||
|
|
||||||
|
class Admin:
|
||||||
|
list_display = ('name', 'value',)
|
||||||
|
|
||||||
|
class Object(models.Model):
|
||||||
|
"""
|
||||||
|
The Object class is very generic. We put all of our common attributes
|
||||||
|
here and anything very particular into the attribute field. Notice the otype
|
||||||
|
field. The different otypes denote an object's behaviors.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Do not mess with the default types (0-4).
|
||||||
|
OBJECT_TYPES = (
|
||||||
|
(0, 'NOTHING'),
|
||||||
|
(1, 'PLAYER'),
|
||||||
|
(2, 'ROOM'),
|
||||||
|
(3, 'THING'),
|
||||||
|
(4, 'EXIT'),
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%s(%d)" % (self.name, self.id,)
|
||||||
|
|
||||||
|
def is_type(self, typename):
|
||||||
|
"""
|
||||||
|
Do a string comparison of user's input and the object's type class object's
|
||||||
|
name.
|
||||||
|
"""
|
||||||
|
return self.type.name == typename
|
||||||
|
|
||||||
|
def set_type(self, typename):
|
||||||
|
"""
|
||||||
|
Sets a object's type.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Admin:
|
||||||
|
list_display = ('name',)
|
||||||
|
"""
|
||||||
|
class Player(models.Model):
|
||||||
|
#
|
||||||
|
# Model representation of our players.
|
||||||
|
#
|
||||||
|
# Link back to our Django User class for password, username, email, etc.
|
||||||
|
account = models.ForeignKey(User)
|
||||||
|
location = models.ForeignKey(Object, related_name="plocation")
|
||||||
|
is_connected = models.BooleanField()
|
||||||
|
last_connected = models.DateTimeField()
|
||||||
|
contents = models.ManyToManyField(Object)
|
||||||
|
attributes = models.ManyToManyField(Attribute)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%s(%d)" % (self.name, self.id,)
|
||||||
|
"""
|
||||||
BIN
evennia/apps/objects/models.pyc
Executable file
BIN
evennia/apps/objects/models.pyc
Executable file
Binary file not shown.
1
evennia/apps/objects/views.py
Executable file
1
evennia/apps/objects/views.py
Executable file
|
|
@ -0,0 +1 @@
|
||||||
|
# Create your views here.
|
||||||
207
evennia/cmdhandler.py
Executable file
207
evennia/cmdhandler.py
Executable file
|
|
@ -0,0 +1,207 @@
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from apps.objects.models import Object
|
||||||
|
import settings
|
||||||
|
import string
|
||||||
|
from ansi import *
|
||||||
|
"""
|
||||||
|
This is the command processing module. It is instanced once in the main
|
||||||
|
server module and the handle() function is hit every time a player sends
|
||||||
|
something.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
||||||
|
player_loc = session.player_loc
|
||||||
|
player_loc_obj = Object.objects.filter(id=player_loc)[0]
|
||||||
|
|
||||||
|
retval = "%s%s%s%s\n\r%s\n\r" % (
|
||||||
|
ansi["normal"],
|
||||||
|
ansi["hilite"],
|
||||||
|
player_loc_obj.name,
|
||||||
|
ansi["normal"],
|
||||||
|
player_loc_obj.description,
|
||||||
|
)
|
||||||
|
session.push(retval)
|
||||||
|
|
||||||
|
def do_quit(self, cdat):
|
||||||
|
"""
|
||||||
|
Gracefully disconnect the user as per his own request.
|
||||||
|
"""
|
||||||
|
session = cdat['session']
|
||||||
|
session.push("Quitting!\n\r")
|
||||||
|
session.handle_close()
|
||||||
|
|
||||||
|
def do_who(self, cdat):
|
||||||
|
"""
|
||||||
|
Generic WHO command.
|
||||||
|
"""
|
||||||
|
session_list = cdat['server'].session_list
|
||||||
|
session = cdat['session']
|
||||||
|
|
||||||
|
retval = "Player Name\n\r"
|
||||||
|
for player in session_list:
|
||||||
|
retval += '%s\n\r' % (player,)
|
||||||
|
retval += '%d Players logged in.\n\r' % (len(session_list),)
|
||||||
|
|
||||||
|
session.push(retval)
|
||||||
|
|
||||||
|
def do_say(self, cdat):
|
||||||
|
"""
|
||||||
|
Room-based speech command.
|
||||||
|
"""
|
||||||
|
session_list = cdat['server'].session_list
|
||||||
|
session = cdat['session']
|
||||||
|
speech = cdat['uinput'][1:]
|
||||||
|
players_present = [player for player in session_list if player.player_loc == session.player_loc and player != session]
|
||||||
|
|
||||||
|
retval = "You say, '%s'\n\r" % (''.join(speech),)
|
||||||
|
for player in players_present:
|
||||||
|
player.push("%s says, '%s'\n\r" % (session.name, speech,))
|
||||||
|
|
||||||
|
session.push(retval)
|
||||||
|
|
||||||
|
def do_sa(self, cdat):
|
||||||
|
"""
|
||||||
|
Temporary alias until we come up with a command alias system.
|
||||||
|
"""
|
||||||
|
self.do_say(cdat)
|
||||||
|
|
||||||
|
def do_version(self, cdat):
|
||||||
|
"""
|
||||||
|
Version info command.
|
||||||
|
"""
|
||||||
|
session = cdat['session']
|
||||||
|
retval = "-"*50 +"\n\r"
|
||||||
|
retval += "Evennia %s\n\r" % (settings.EVENNIA_VERSION,)
|
||||||
|
retval += "-"*50 +"\n\r"
|
||||||
|
session.push(retval)
|
||||||
|
|
||||||
|
class StaffCommands:
|
||||||
|
"""
|
||||||
|
Restricted staff commands.
|
||||||
|
"""
|
||||||
|
def do_dig(self, cdat):
|
||||||
|
"""
|
||||||
|
Digs a new room out.
|
||||||
|
"""
|
||||||
|
session = cdat['session']
|
||||||
|
uinput= cdat['uinput']
|
||||||
|
roomname = ''.join(uinput[1:])
|
||||||
|
|
||||||
|
if roomname == '':
|
||||||
|
session.push("You must supply a room name!")
|
||||||
|
else:
|
||||||
|
newroom = Object()
|
||||||
|
newroom.name = roomname
|
||||||
|
newroom.type = "Room"
|
||||||
|
|
||||||
|
def do_nextfree(self, cdat):
|
||||||
|
"""
|
||||||
|
Returns the next free object number.
|
||||||
|
"""
|
||||||
|
session = cdat['session']
|
||||||
|
server = cdat['server']
|
||||||
|
|
||||||
|
nextfree = server.nextfree_objnum()
|
||||||
|
retval = "Next free object number: %d" % (nextfree,)
|
||||||
|
|
||||||
|
session.push(retval)
|
||||||
|
|
||||||
|
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,
|
||||||
|
uses the Django database API and User model to make it extremely simple.
|
||||||
|
"""
|
||||||
|
session = cdat['session']
|
||||||
|
uname = cdat['uinput'][1]
|
||||||
|
password = cdat['uinput'][2]
|
||||||
|
|
||||||
|
account = User.objects.filter(username=uname)
|
||||||
|
user = account[0]
|
||||||
|
|
||||||
|
autherror = "Invalid username or password!\n\r"
|
||||||
|
if account.count() == 0:
|
||||||
|
session.push(autherror)
|
||||||
|
if not user.check_password(password):
|
||||||
|
session.push(autherror)
|
||||||
|
else:
|
||||||
|
uname = user.username
|
||||||
|
session.login(user)
|
||||||
|
|
||||||
|
def do_create(self, cdat):
|
||||||
|
"""
|
||||||
|
Handle the creation of new accounts.
|
||||||
|
"""
|
||||||
|
session = cdat['session']
|
||||||
|
uname = cdat['uinput'][1]
|
||||||
|
email = cdat['uinput'][2]
|
||||||
|
password = cdat['uinput'][3]
|
||||||
|
account = User.objects.filter(username=uname)
|
||||||
|
|
||||||
|
if not account.count() == 0:
|
||||||
|
session.push("There is already a player with that name!")
|
||||||
|
elif len(password) < 3:
|
||||||
|
session.push("Your password must be 3 characters or longer.")
|
||||||
|
else:
|
||||||
|
session.create_user(uname, email, password)
|
||||||
|
|
||||||
|
def do_quit(self, cdat):
|
||||||
|
"""
|
||||||
|
We're going to maintain a different version of the quit command
|
||||||
|
here for unconnected users for the sake of simplicity. The logged in
|
||||||
|
version will be a bit more complicated.
|
||||||
|
"""
|
||||||
|
session = cdat['session']
|
||||||
|
session.push("Disconnecting...\n\r")
|
||||||
|
session.handle_close()
|
||||||
|
|
||||||
|
# We'll use this for our getattr() in the Handler class.
|
||||||
|
gencommands = GenCommands()
|
||||||
|
staffcommands = StaffCommands()
|
||||||
|
unloggedincommands = UnLoggedInCommands()
|
||||||
|
|
||||||
|
class Handler:
|
||||||
|
def __init__(self): pass
|
||||||
|
def handle(self, cdat):
|
||||||
|
"""
|
||||||
|
Use the spliced (list) uinput variable to retrieve the correct
|
||||||
|
command, or return an invalid command error.
|
||||||
|
|
||||||
|
We're basically grabbing the player's command by tacking
|
||||||
|
their input on to 'do_' and looking it up in the GenCommands
|
||||||
|
class.
|
||||||
|
"""
|
||||||
|
session = cdat['session']
|
||||||
|
uinput = cdat['uinput']
|
||||||
|
|
||||||
|
if session.logged_in:
|
||||||
|
# If it's prefixed by an '@', it's a staff command.
|
||||||
|
if uinput[0].find('@') == -1:
|
||||||
|
cmdtable = gencommands
|
||||||
|
else:
|
||||||
|
cmdtable = staffcommands
|
||||||
|
else:
|
||||||
|
cmdtable = unloggedincommands
|
||||||
|
cmd = getattr(cmdtable, 'do_' + uinput[0].lower(), None)
|
||||||
|
|
||||||
|
if callable(cmd):
|
||||||
|
cmd(cdat)
|
||||||
|
else:
|
||||||
|
session.push("Unknown command.\n\r")
|
||||||
|
|
||||||
BIN
evennia/cmdhandler.pyc
Executable file
BIN
evennia/cmdhandler.pyc
Executable file
Binary file not shown.
0
evennia/commonfuncs.py
Executable file
0
evennia/commonfuncs.py
Executable file
11
evennia/manage.py
Executable file
11
evennia/manage.py
Executable file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
from django.core.management import execute_manager
|
||||||
|
try:
|
||||||
|
import settings # Assumed to be in the same directory.
|
||||||
|
except ImportError:
|
||||||
|
import sys
|
||||||
|
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
execute_manager(settings)
|
||||||
3
evennia/prepenv.sh
Executable file
3
evennia/prepenv.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/bash
|
||||||
|
export DJANGO_SETTINGS_MODULE="settings"
|
||||||
|
python server.py
|
||||||
94
evennia/server.py
Executable file
94
evennia/server.py
Executable file
|
|
@ -0,0 +1,94 @@
|
||||||
|
from asyncore import dispatcher
|
||||||
|
from asynchat import async_chat
|
||||||
|
import socket, asyncore, time, sys
|
||||||
|
from sessions import PlayerSession
|
||||||
|
from django.db import models
|
||||||
|
from apps.config.models import Config
|
||||||
|
from apps.objects.models import Object
|
||||||
|
|
||||||
|
#
|
||||||
|
## Begin: Time Functions
|
||||||
|
#
|
||||||
|
|
||||||
|
schedule = {'heal':100.0}
|
||||||
|
lastrun = {}
|
||||||
|
|
||||||
|
def heal():
|
||||||
|
pass
|
||||||
|
|
||||||
|
# The timer loop
|
||||||
|
def Timer(timer):
|
||||||
|
|
||||||
|
sched = schedule.iteritems()
|
||||||
|
for i in sched:
|
||||||
|
try: lastrun[i[0]]
|
||||||
|
except: lastrun[i[0]] = time.time()
|
||||||
|
|
||||||
|
diff = timer - lastrun[i[0]]
|
||||||
|
|
||||||
|
# Every 100 seconds, run heal(), defined above.
|
||||||
|
if diff >= schedule['heal']:
|
||||||
|
heal()
|
||||||
|
lastrun['heal'] = time.time()
|
||||||
|
|
||||||
|
#
|
||||||
|
## End: Time Functions
|
||||||
|
#
|
||||||
|
|
||||||
|
class Server(dispatcher):
|
||||||
|
"""
|
||||||
|
The main server class from which everything branches.
|
||||||
|
"""
|
||||||
|
def __init__(self, port):
|
||||||
|
dispatcher.__init__(self)
|
||||||
|
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.set_reuse_addr()
|
||||||
|
self.bind(('', port))
|
||||||
|
self.listen(100)
|
||||||
|
self.session_list = []
|
||||||
|
self.object_list = {}
|
||||||
|
self.game_running = True
|
||||||
|
print '-'*50
|
||||||
|
self.load_config()
|
||||||
|
self.load_objects()
|
||||||
|
print ' Server started on port %i.' % (port,)
|
||||||
|
print '-'*50
|
||||||
|
|
||||||
|
def load_config(self):
|
||||||
|
"""
|
||||||
|
Loads our site's configuration up for easy access.
|
||||||
|
"""
|
||||||
|
self.config = Config.objects.all()[0]
|
||||||
|
print ' Configuration Loaded.'
|
||||||
|
|
||||||
|
def load_objects(self):
|
||||||
|
"""
|
||||||
|
Load all of our objects into memory.
|
||||||
|
"""
|
||||||
|
object_list = Object.objects.all()
|
||||||
|
for object in object_list:
|
||||||
|
dbnum = object.id
|
||||||
|
self.object_list[dbnum] = object
|
||||||
|
print ' Objects Loaded: %i' % (len(self.object_list),)
|
||||||
|
|
||||||
|
def handle_accept(self):
|
||||||
|
"""
|
||||||
|
What to do when we get a connection.
|
||||||
|
"""
|
||||||
|
conn, addr = self.accept()
|
||||||
|
session = PlayerSession(self, conn, addr)
|
||||||
|
session.game_connect_screen(session)
|
||||||
|
print 'Connection:', str(session)
|
||||||
|
self.session_list.append(session)
|
||||||
|
print 'Sessions active:', len(self.session_list)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
server = Server(4000)
|
||||||
|
|
||||||
|
try:
|
||||||
|
while server.game_running:
|
||||||
|
asyncore.loop(timeout=5, count=1) # Timer() called every 5 seconds.
|
||||||
|
Timer(time.time())
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print 'Interrupted'
|
||||||
BIN
evennia/server.pyc
Executable file
BIN
evennia/server.pyc
Executable file
Binary file not shown.
117
evennia/sessions.py
Executable file
117
evennia/sessions.py
Executable file
|
|
@ -0,0 +1,117 @@
|
||||||
|
from asyncore import dispatcher
|
||||||
|
from asynchat import async_chat
|
||||||
|
import socket, asyncore, time, sys
|
||||||
|
from cmdhandler import *
|
||||||
|
|
||||||
|
chandler = Handler()
|
||||||
|
|
||||||
|
class PlayerSession(async_chat):
|
||||||
|
"""
|
||||||
|
This class represents a player's sesssion. From here we branch down into
|
||||||
|
other various classes, please try to keep this one tidy!
|
||||||
|
"""
|
||||||
|
def __init__(self, server, sock, addr):
|
||||||
|
async_chat.__init__(self, sock)
|
||||||
|
self.server = server
|
||||||
|
self.address = addr
|
||||||
|
self.set_terminator("\n")
|
||||||
|
self.name = None
|
||||||
|
self.data = []
|
||||||
|
self.sock = sock
|
||||||
|
self.logged_in = False
|
||||||
|
self.user = None
|
||||||
|
# The time the user last issued a command.
|
||||||
|
self.cmd_last = time.time()
|
||||||
|
# Total number of commands issued.
|
||||||
|
self.cmd_total = 0
|
||||||
|
# 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
|
||||||
|
|
||||||
|
def collect_incoming_data(self, data):
|
||||||
|
"""
|
||||||
|
Stuff any incoming data into our buffer, self.data
|
||||||
|
"""
|
||||||
|
self.data.append(data)
|
||||||
|
|
||||||
|
def found_terminator(self):
|
||||||
|
"""
|
||||||
|
Any line return indicates a command for the purpose of a MUD. So we take
|
||||||
|
the user input, split it up by spaces into a list, and pass it to our
|
||||||
|
command handler.
|
||||||
|
"""
|
||||||
|
line = (''.join(self.data))
|
||||||
|
line = line.strip('\r')
|
||||||
|
uinput = line.split(' ')
|
||||||
|
self.data = []
|
||||||
|
|
||||||
|
# Increment our user's command counter.
|
||||||
|
self.cmd_total += 1
|
||||||
|
# Store the timestamp of the user's last command.
|
||||||
|
self.cmd_last = time.time()
|
||||||
|
# Stuff anything we need to pass in this dictionary.
|
||||||
|
cdat = {"server": self.server, "uinput": uinput, "session": self}
|
||||||
|
chandler.handle(cdat)
|
||||||
|
|
||||||
|
def handle_close(self):
|
||||||
|
"""
|
||||||
|
Break the connection and do some accounting.
|
||||||
|
"""
|
||||||
|
async_chat.handle_close(self)
|
||||||
|
self.logged_in = False
|
||||||
|
self.server.session_list.remove(self)
|
||||||
|
print 'Sessions active:', len(self.server.session_list)
|
||||||
|
|
||||||
|
def game_connect_screen(self, session):
|
||||||
|
"""
|
||||||
|
Show our banner screen.
|
||||||
|
"""
|
||||||
|
buffer = '-'*50
|
||||||
|
buffer += ' \n\rWelcome to Evennia!\n\r'
|
||||||
|
buffer += '-'*50 + '\n\r'
|
||||||
|
buffer += """Please type one of the following to begin:\n\r
|
||||||
|
connect <username> <password>\n\r
|
||||||
|
create <username> <email> <password>\n\r"""
|
||||||
|
buffer += '-'*50 + '\n\r'
|
||||||
|
session.push(buffer)
|
||||||
|
|
||||||
|
def login(self, user):
|
||||||
|
"""
|
||||||
|
After the user has authenticated, handle logging him in.
|
||||||
|
"""
|
||||||
|
self.user = user
|
||||||
|
self.name = user.username
|
||||||
|
self.logged_in = True
|
||||||
|
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
|
||||||
|
this a lot in the server logs and stuff.
|
||||||
|
"""
|
||||||
|
if self.logged_in:
|
||||||
|
symbol = '#'
|
||||||
|
else:
|
||||||
|
symbol = '?'
|
||||||
|
return "<%s> %s@%s" % (symbol, self.name, self.address,)
|
||||||
|
|
||||||
|
# def handle_error(self):
|
||||||
|
# self.handle_close()
|
||||||
BIN
evennia/sessions.pyc
Executable file
BIN
evennia/sessions.pyc
Executable file
Binary file not shown.
85
evennia/settings.py
Executable file
85
evennia/settings.py
Executable file
|
|
@ -0,0 +1,85 @@
|
||||||
|
# Django settings for evennia project.
|
||||||
|
|
||||||
|
DEBUG = True
|
||||||
|
TEMPLATE_DEBUG = DEBUG
|
||||||
|
|
||||||
|
ADMINS = (
|
||||||
|
# ('Your Name', 'your_email@domain.com'),
|
||||||
|
)
|
||||||
|
|
||||||
|
MANAGERS = ADMINS
|
||||||
|
|
||||||
|
DATABASE_ENGINE = 'mysql' # 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
|
||||||
|
DATABASE_NAME = 'evennia' # Or path to database file if using sqlite3.
|
||||||
|
DATABASE_USER = 'evennia' # Not used with sqlite3.
|
||||||
|
DATABASE_PASSWORD = 'CvAPpy:FFRTmTMHf' # Not used with sqlite3.
|
||||||
|
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
|
||||||
|
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
|
||||||
|
|
||||||
|
# Local time zone for this installation. All choices can be found here:
|
||||||
|
# http://www.postgresql.org/docs/current/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
|
||||||
|
TIME_ZONE = 'America/New_York'
|
||||||
|
|
||||||
|
# Language code for this installation. All choices can be found here:
|
||||||
|
# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
|
||||||
|
# http://blogs.law.harvard.edu/tech/stories/storyReader$15
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
SITE_ID = 1
|
||||||
|
|
||||||
|
# If you set this to False, Django will make some optimizations so as not
|
||||||
|
# to load the internationalization machinery.
|
||||||
|
USE_I18N = False
|
||||||
|
|
||||||
|
# Absolute path to the directory that holds media.
|
||||||
|
# Example: "/home/media/media.lawrence.com/"
|
||||||
|
MEDIA_ROOT = '/home/gtaylor/dev/evennia/media'
|
||||||
|
|
||||||
|
# URL that handles the media served from MEDIA_ROOT.
|
||||||
|
# Example: "http://media.lawrence.com"
|
||||||
|
MEDIA_URL = '/media/'
|
||||||
|
|
||||||
|
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
|
||||||
|
# trailing slash.
|
||||||
|
# Examples: "http://foo.com/media/", "/media/".
|
||||||
|
ADMIN_MEDIA_PREFIX = '/media/amedia/'
|
||||||
|
|
||||||
|
# Make this unique, and don't share it with anybody.
|
||||||
|
SECRET_KEY = 'fsd&lkj^LKJ8398200(@)(38919#23892(*$*#(981'
|
||||||
|
|
||||||
|
# List of callables that know how to import templates from various sources.
|
||||||
|
TEMPLATE_LOADERS = (
|
||||||
|
'django.template.loaders.filesystem.load_template_source',
|
||||||
|
'django.template.loaders.app_directories.load_template_source',
|
||||||
|
# 'django.template.loaders.eggs.load_template_source',
|
||||||
|
)
|
||||||
|
|
||||||
|
MIDDLEWARE_CLASSES = (
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.middleware.doc.XViewMiddleware',
|
||||||
|
)
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'evennia.urls'
|
||||||
|
|
||||||
|
TEMPLATE_DIRS = (
|
||||||
|
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
|
||||||
|
# Always use forward slashes, even on Windows.
|
||||||
|
# Don't forget to use absolute paths, not relative paths.
|
||||||
|
)
|
||||||
|
|
||||||
|
INSTALLED_APPS = (
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.admin',
|
||||||
|
'evennia.apps.config',
|
||||||
|
'evennia.apps.objects',
|
||||||
|
# 'django.contrib.sites',
|
||||||
|
)
|
||||||
|
|
||||||
|
#
|
||||||
|
## Evennia Config
|
||||||
|
#
|
||||||
|
EVENNIA_VERSION = 'Pre-Alpha 0.0'
|
||||||
BIN
evennia/settings.pyc
Executable file
BIN
evennia/settings.pyc
Executable file
Binary file not shown.
9
evennia/urls.py
Executable file
9
evennia/urls.py
Executable file
|
|
@ -0,0 +1,9 @@
|
||||||
|
from django.conf.urls.defaults import *
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
# Example:
|
||||||
|
# (r'^evennia/', include('evennia.apps.foo.urls.foo')),
|
||||||
|
|
||||||
|
# Uncomment this for admin:
|
||||||
|
(r'^admin/', include('django.contrib.admin.urls')),
|
||||||
|
)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue