Reshuffling the Evennia package into the new template paradigm.

This commit is contained in:
Griatch 2015-01-06 14:53:45 +01:00
parent 2846e64833
commit 2b3a32e447
371 changed files with 17250 additions and 304 deletions

0
lib/web/__init__.py Normal file
View file

View file

@ -0,0 +1,225 @@
/**************************************
* TITLE: Prosimii Print Stylesheet *
* URI : prosimii/prosimii-print.css *
* MODIF: 2003-Apr-30 19:15 +0800 *
**************************************/
/* ##### Common Styles ##### */
body {
color: black;
background-color: white;
font-family: "times new roman", times, roman, serif;
font-size: 12pt;
margin: 0;
padding: 0;
}
acronym, .titleTip {
font-style: italic;
border-bottom: none;
}
acronym:after, .titleTip:after { /* Prints titles after the acronyms/titletips. Doesn't work in MSIE */
content: "(" attr(title) ")";
font-size: 90%;
font-style: normal;
padding-left: 1ex;
}
a {
color: black;
background-color: transparent;
text-decoration: none;
}
a[href]:after { /* Prints the links' URIs after the links' texts. Doesn't work in MSIE */
content: "<" attr(href) ">";
font-size: 90%;
padding-left: 1ex;
}
ol {
margin: -0.25em 0 1em 0;
padding: 0;
}
ul {
list-style-type: square;
margin: -0.25em 0 1em 0;
padding: 0;
}
dl {
margin: 0 0 1em 0;
padding: 0;
}
ul li {
margin: 1ex 0 0 1.5em;
padding: 0;
}
ol li {
margin: 1ex 0 0 1.5em;
padding: 0;
}
dt {
font-weight: bold;
margin: 0;
padding: 0;
}
dd {
margin: 0 0 0 1.5em;
padding: 0;
}
.doNotPrint {
display: none;
}
/* ##### Header ##### */
#header {
}
.superHeader {
display: none;
}
.midHeader {
color: black;
background-color: transparent;
margin: 0;
padding: 0;
border-bottom: 1px solid black;
}
.headerTitle {
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
font-size: 200%;
font-weight: normal;
margin: 0;
padding: 0;
}
.headerSubTitle {
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
font-size: 110%;
font-weight: normal;
font-style: italic;
margin: 0;
padding: 0 0 1ex 0;
}
.headerLinks {
display: none;
}
.subHeader {
display: none;
}
/* ##### Side Menu ##### */
#side-bar {
display: none !important;
}
/* ##### Main Copy ##### */
#main-copy {
text-align: justify;
margin: 0 !important;
padding: 0;
}
#main-copy h1 {
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
font-size: 120%;
margin: 2ex 0 1ex 0;
padding: 0;
}
#main-copy h2 {
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
font-weight: normal;
font-size: 100%;
margin: 0;
padding: 2ex 0 0.5ex 0;
}
#main-copy h1 + h2 {
padding-top: 0;
}
#main-copy p {
margin: 0 0 2ex 0;
padding: 0;
}
h2 a:after {
content: "" !important;
}
.newsDate {
font-style: italic;
margin: 0;
padding: 0;
display: inline;
}
.newsDate:before { /* Prints an '[' before the news item's date. Doesn't work in MSIE */
content: "[";
font-style: normal;
}
.newsDate:after { /* Prints a ']' after the news item's date. Doesn't work in MSIE */
content: "]";
font-style: normal;
}
.newsSummary {
display: inline;
margin: 0 0 0 1ex !important;
padding: 0;
}
.more {
display: none;
}
.smallCaps {
font-variant: small-caps;
}
.quarter, .oneThird, .half, .twoThirds, .fullWidth {
margin: 0;
padding: 0;
}
/* ##### Footer ##### */
#footer {
color: black;
background-color: transparent;
font-size: 100%;
text-align: center;
margin: 2em 0 0 0;
padding: 1ex 0 0 0;
border-top: 1px solid black;
display: block;
}
#footer a {
color: black;
background-color: transparent;
text-decoration: none;
}

View file

@ -0,0 +1,342 @@
/*************************************************
* TITLE: Prosimii Alternative Screen Stylesheet *
* URI : prosimii/prosimii-screen-alt.css *
* MODIF: 2004-Apr-28 21:56 +0800 *
*************************************************/
/* ##### Common Styles ##### */
body {
font-family: verdana, helvetica, arial, sans-serif;
font-size: 73%; /* Enables font size scaling in MSIE */
margin: 0;
padding: 0;
}
html > body {
font-size: 9pt;
}
acronym, .titleTip {
border-bottom: 1px dotted rgb(61,92,122);
cursor: help;
margin: 0;
padding: 0 0 0.4px 0;
}
a {
color: rgb(61,92,122);
background-color: transparent;
text-decoration: underline;
margin: 0;
padding: 0 1px 2px 1px;
}
a:hover {
color: rgb(117,144,174);
text-decoration: none;
}
ol {
margin: 1em 0 1.5em 0;
padding: 0;
}
ul {
list-style-type: square;
margin: 1em 0 1.5em 0;
padding: 0;
}
dl {
margin: 1em 0 0.5em 0;
padding: 0;
}
ul li {
line-height: 1.5em;
margin: 1.25ex 0 0 1.5em;
padding: 0;
}
ol li {
line-height: 1.5em;
margin: 1.25ex 0 0 2em;
padding: 0;
}
dt {
font-weight: bold;
margin: 0;
padding: 0 0 1ex 0;
}
dd {
line-height: 1.75em;
margin: 0 0 1.5em 1.5em;
padding: 0;
}
.doNotDisplay {
display: none !important;
}
.smallCaps {
font-size: 117%;
font-variant: small-caps;
}
/* ##### Header ##### */
.superHeader {
color: rgb(130,128,154);
background-color: rgb(33,50,66);
text-align: right;
margin: 0;
padding: 0.5ex 10px;
}
.superHeader span {
color: rgb(195,196,210);
background-color: transparent;
font-weight: bold;
text-transform: uppercase;
}
.superHeader a {
color: rgb(195,196,210);
background-color: transparent;
text-decoration: none;
margin: 0;
padding: 0 0.25ex 0 0;
}
.superHeader a:hover {
color: rgb(193,102,90);
background-color: transparent;
text-decoration: none;
}
.midHeader {
color: white;
background-color: rgb(61,92,122);
margin: 0;
padding: 0.26ex 10px;
}
.headerTitle {
font-size: 300%;
margin: 0;
padding: 0;
}
.headerSubTitle {
font-size: 151%;
font-weight: normal;
font-style: italic;
margin: 0 0 1ex 0;
padding: 0;
}
.headerLinks {
text-align: right;
margin: 0;
padding: 0 0 2ex 0;
position: absolute;
right: 1.5em;
top: 3.5em;
}
.headerLinks a {
color: white;
background-color: transparent;
text-decoration: none;
margin: 0;
padding: 0 0 0.5ex 0;
display: block;
}
.headerLinks a:hover {
color: rgb(195,196,210);
background-color: transparent;
text-decoration: underline;
}
.subHeader {
color: white;
background-color: rgb(117,144,174);
margin: 0;
padding: 0.5ex 10px;
}
.subHeader a, .subHeader .highlight {
color: white;
background-color: transparent;
font-size: 110%;
font-weight: bold;
text-decoration: none;
margin: 0;
padding: 0 0.25ex 0 0;
}
.subHeader a:hover, .subHeader .highlight {
color: rgb(255,204,0);
background-color: transparent;
text-decoration: none;
}
/* ##### Side Menu ##### */
#side-bar {
color: rgb(204,204,204);
background-color: transparent;
list-style-type: square;
list-style-position: inside;
width: 10em;
margin: 0;
padding: 1ex 0;
border: 1px solid rgb(204,204,204);
position: absolute;
left: 1.5ex;
top: 12em;
}
[id="side-bar"] {
position: fixed !important; /* Makes the side menu scroll with the page. Doesn't work in MSIE */
}
#side-bar a {
text-decoration: none;
}
#side-bar:hover {
color: rgb(117,144,174);
background-color: transparent;
border-color: rgb(117,144,174);
}
#side-bar li {
margin: 0;
padding: 0.75ex 0 1ex 1.75ex;
}
#side-bar li:hover {
color: rgb(61,92,122);
background-color: transparent;
}
#side-bar li a:hover {
text-decoration: underline;
}
/* ##### Main Copy ##### */
#main-copy {
text-align: justify;
margin: -0.5ex 1em 1em 12.5em;
padding: 0.5em 10px;
clear: left;
}
#main-copy h1 {
color: rgb(117,144,174);
background-color: transparent;
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
font-size: 186%;
margin: 0;
padding: 1.5ex 0 0 0;
}
#main-copy h2 {
color: rgb(61,92,122);
background-color: transparent;
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
font-weight: normal;
font-size: 151%;
margin: 0;
padding: 1ex 0 0 0;
}
#main-copy p {
line-height: 1.75em;
margin: 1em 0 1.5em 0;
padding: 0;
}
.newsHeading {
color: rgb(61,92,122);
background-color: transparent;
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
font-size: 145%;
text-decoration: none;
margin: 0;
padding: 1ex 0 0 0;
display: block;
}
.newsHeading:hover {
color: rgb(117,144,174);
background-color: transparent;
text-decoration: underline;
}
.newsDate {
font-style: italic;
margin: 0 !important;
padding: 0;
}
.newsSummary {
margin: 1.5ex 0 2.5ex 0.75ex !important;
padding: 0;
}
.more {
text-align: right;
margin: 0;
padding: 0.5em 0;
}
.more a {
color: rgb(61,92,122);
background-color: transparent;
font-size: 92%;
text-decoration: underline;
margin: 0;
padding: 0.25ex 0.75ex;
}
.more a:hover {
color: rgb(117,144,174);
text-decoration: none;
}
/* ##### Footer ##### */
#footer {
color: rgb(51,51,102);
background-color: rgb(239,239,239);
font-size: 87%;
text-align: center;
line-height: 1.25em;
margin: 2em 0 0 0;
padding: 1ex 10px;
clear: left;
}
#footer a {
color: rgb(0,68,204);
background-color: transparent;
text-decoration: underline;
}
#footer a:hover {
text-decoration: none;
}

View file

@ -0,0 +1,359 @@
/***************************************
* TITLE: Prosimii Screen Stylesheet *
* URI : prosimii/prosimii-screen.css *
* MODIF: 2004-Apr-28 21:43 +0800 *
***************************************/
/* ##### Common Styles ##### */
body {
font-family: verdana, helvetica, arial, sans-serif;
font-size: 73%; /* Enables font size scaling in MSIE */
margin: 0;
padding: 0;
}
html > body {
font-size: 9pt;
}
acronym, .titleTip {
border-bottom: 1px dotted rgb(61,92,122);
cursor: help;
margin: 0;
padding: 0 0 0.4px 0;
}
a {
color: rgb(61,92,122);
background-color: transparent;
text-decoration: underline;
margin: 0;
padding: 0 1px 2px 1px;
}
a:hover {
color: rgb(117,144,174);
text-decoration: none;
}
ol {
margin: 1em 0 1.5em 0;
padding: 0;
}
ul {
list-style-type: square;
margin: 1em 0 1.5em 0;
padding: 0;
}
dl {
margin: 1em 0 0.5em 0;
padding: 0;
}
ul li {
line-height: 1.5em;
margin: 1.25ex 0 0 1.5em;
padding: 0;
}
ol li {
line-height: 1.5em;
margin: 1.25ex 0 0 2em;
padding: 0;
}
dt {
font-weight: bold;
margin: 0;
padding: 0 0 1ex 0;
}
dd {
line-height: 1.75em;
margin: 0 0 1.5em 1.5em;
padding: 0;
}
.doNotDisplay {
display: none !important;
}
.smallCaps {
font-size: 117%;
font-variant: small-caps;
}
/* ##### Header ##### */
.superHeader {
color: rgb(130,128,154);
background-color: rgb(33,50,66);
text-align: right;
margin: 0;
padding: 0.5ex 10px;
}
.superHeader span {
color: rgb(195,196,210);
background-color: transparent;
font-weight: bold;
text-transform: uppercase;
}
.superHeader a {
color: rgb(195,196,210);
background-color: transparent;
text-decoration: none;
margin: 0;
padding: 0 0.25ex 0 0;
}
.superHeader a:hover {
color: rgb(193,102,90);
background-color: transparent;
text-decoration: none;
}
.midHeader {
color: white;
background-color: rgb(61,92,122);
margin: 0;
padding: 0.26ex 10px;
}
.headerTitle {
font-size: 300%;
margin: 0;
padding: 0;
}
.headerSubTitle {
font-size: 151%;
font-weight: normal;
font-style: italic;
margin: 0 0 1ex 0;
padding: 0;
}
.headerLinks {
text-align: right;
margin: 0;
padding: 0 0 2ex 0;
position: absolute;
right: 1.5em;
top: 3.5em;
}
.headerLinks a {
color: white;
background-color: transparent;
text-decoration: none;
margin: 0;
padding: 0 0 0.5ex 0;
display: block;
}
.headerLinks a:hover {
color: rgb(195,196,210);
background-color: transparent;
text-decoration: underline;
}
.subHeader {
color: white;
background-color: rgb(117,144,174);
margin: 0;
padding: 0.5ex 10px;
}
.subHeader a, .subHeader .highlight {
color: white;
background-color: transparent;
font-size: 110%;
font-weight: bold;
text-decoration: none;
margin: 0;
padding: 0 0.25ex 0 0;
}
.subHeader a:hover, .subHeader .highlight {
color: rgb(255,204,0);
background-color: transparent;
text-decoration: none;
}
/* ##### Main Copy ##### */
#main-copy {
margin: 0;
padding: 0.5em 10px;
clear: left;
}
#main-copy h1 {
color: rgb(117,144,174);
background-color: transparent;
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
font-size: 200%;
margin: 0;
padding: 0;
}
#main-copy h2 {
color: rgb(61,92,122);
background-color: transparent;
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
font-weight: normal;
font-size: 151%;
margin: 0;
padding: 1ex 0 0 0;
}
#main-copy p {
line-height: 1.75em;
margin: 1em 0 1.5em 0;
padding: 0;
}
.newsHeading {
color: rgb(61,92,122);
background-color: transparent;
font-family: "trebuchet ms", verdana, helvetica, arial, sans-serif;
font-size: 145%;
text-decoration: none;
margin: 0;
padding: 1ex 0 0 0;
display: block;
}
.newsHeading:hover {
color: rgb(117,144,174);
background-color: transparent;
text-decoration: underline;
}
.newsDate {
font-style: italic;
margin: 0 !important;
padding: 0;
}
.newsSummary {
margin: 1.5ex 0 2.5ex 0.75ex !important;
padding: 0;
}
.more {
text-align: right;
margin: 0;
padding: 0.5em 0;
}
.more a {
color: rgb(61,92,122);
background-color: transparent;
font-size: 92%;
text-decoration: underline;
margin: 0;
padding: 0.25ex 0.75ex;
}
.more a:hover {
color: rgb(117,144,174);
text-decoration: none;
}
.rowOfBoxes {
clear: both;
}
.quarter, .oneThird, .half, .twoThirds, .fullWidth {
margin: 1em 0;
float: left;
border-left: 1px solid rgb(204,204,204);
}
.quarter {
width: 21%;
padding: 0 1.9%;
}
.oneThird {
width: 28%;
padding: 0 1.9%;
}
.half {
text-align: justify;
width: 46%;
padding: 0 1.9%;
}
.twoThirds {
text-align: justify;
width: 63%;
padding: 0 1.9%;
}
.fullWidth {
text-align: justify;
width: 96%;
padding: 0 1.2em;
border-left: none;
}
.filler { /* use with an empty <p> element to add padding to the end of a text box */
border: 1px solid white;
}
.noBorderOnLeft {
border-left: none;
}
.dividingBorderAbove {
border-top: 1px solid rgb(204,204,204);
}
/* More elegant alternatives to .noBorderOnLeft & .dividingBorderAbove
* that don't require the creation of new classes - but which are not
* supported by MSIE - are the following:
*
* .rowOfBoxes > div:first-child {
* border-left: none;
* }
*
* .rowOfBoxes + .rowOfBoxes {
* border-top: 1px solid rgb(204,204,204);
* }
*/
/* ##### Footer ##### */
#footer {
color: rgb(51,51,102);
background-color: rgb(239,239,239);
font-size: 87%;
text-align: center;
line-height: 1.25em;
margin: 2em 0 0 0;
padding: 1ex 10px;
clear: left;
}
#footer a {
color: rgb(0,68,204);
background-color: transparent;
text-decoration: underline;
}
#footer a:hover {
text-decoration: none;
}

View file

@ -0,0 +1,4 @@
The evennia logo (the python snaking a cogwheel-globe) was created in 2009
by Griatch (www.griatch-art.deviantart.com, www.griatch.com) using open-source software (of course).
The logo is released with the same licence as Evennia itself (look in evennia/LICENCE).

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block content %}
<h1>Admin</h1>
Welcome to the Evennia Admin Page. Here, you can edit many facets of players, characters, and other parts of the game.
<h2><a href="{% url "admin:players_playerdb_changelist" %}">Players</a></h2>
Players are user accounts. Players can have several characters under them. A user's login and password information can be changed here.
<h2><a href="{% url "admin:objects_objectdb_changelist" %}">Objects</a></h2>
Objects include everything from characters to rooms to exits.
<h2><a href="{% url "admin:scripts_scriptdb_changelist" %}">Scripts</a></h2>
Scripts are meta objects used to store game information, handle special functionality or perform timed actions.
<h2><a href="{% url "admin:comms_channeldb_changelist" %}">Channels</a></h2>
Channels are used for player communications.
<h2><a href="{% url "admin:help_helpentry_changelist" %}">Help Topics</a></h2>
<p>If you are an advanced user who needs access to the raw Django Admin, it is available <a href="{% url "django_admin" %}">here</a>.
You can make this the default my changing <code>EVENNIA_ADMIN</code> to <code>False</code> in <code>settings.py</code> and reload.</p>
{% endblock content %}

View file

@ -0,0 +1,73 @@
{% extends "base.html" %}
{% block sidebar %}
{% endblock %}
{% block header_ext %}
{% endblock %}
{% block content %}
<div class="rowOfBoxes">
<!--div class="twoThirds noBorderOnLeft"-->
<div class="noBorderOnLeft">
<h1>Welcome!</h1>
<p>Welcome to your new installation of <a href="http://evennia.com">Evennia</a>, your friendly
neighborhood next-generation MUD development system and server. You are looking at Evennia's web
presence, which can be expanded to a full-fledged site as
needed. Through the <a href="{% url 'admin:index' %}">admin interface</a> you can view and edit the
database without logging into the game.
{% if webclient_enabled %}
You can also connect to the game directly from your browser using our
<a href="{% url 'webclient:index' %}">online client</a>!<br></br>
{% endif %}
For more info, take your time to
peruse our extensive online <a href="https://github.com/evennia/evennia/wiki">documentation</a>.
<p>
Should you have any questions, concerns, bug reports, or
if you want to help out, don't hesitate to join the Evennia community to make your voice heard! Drop a mail to the
<a href="https://groups.google.com/forum/#!forum/evennia">mailing list</a> or to come say hi in the <a href="http://webchat.freenode.net/?channels=evennia">developer chatroom</a>. If you find bugs, please report them to our <a href="https://github.com/evennia/evennia/issues">Issue tracker</a>.
</p>
</div>
</div>
<div class="rowOfBoxes dividingBorderAbove">
<div class="quarter noBorderOnLeft">
<h1>Players</h1>
<p>
There's currently <strong>{{num_players_connected}}</strong> connected out of a total of <strong>{{num_players_registered}}</strong> players registered. Of these, <strong>{{num_players_registered_recent}}</strong> were created this week, and <strong>{{num_players_connected_recent}}</strong> have connected within the last seven days.
</p>
</div>
<div class="quarter">
<h1>Recently Connected</h1>
<ul>
{% for player in players_connected_recent %}
<li>{{player.username}} -- <em>{{player.last_login|timesince}} ago</em></li>
{% endfor %}
</ul>
<p class="filler"><!-- Filler para to extend left vertical line --></p>
</div>
<div class="quarter">
<h1>Database Stats</h1>
<ul>
<li>{{num_players_registered}} players (+ {{num_characters}} characters)</li>
<li>{{num_rooms}} rooms (+ {{num_exits}} exits)</li>
<li>{{num_others}} other objects</li>
</ul>
</div>
<div class="quarter">
<h1>Evennia</h1>
<p><a href="http://evennia.com">Evennia</a> is an open-source MUD server built in
<a href="http://python.org">Python</a>, on top of the
<a href="http://twistedmatrix.com">Twisted</a> and
<a href="http://djangoproject.com">Django</a> frameworks. This
combination of technologies allows for the quick and easy creation
of the game of your dreams - as simple or as complex as you like.</p>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,18 @@
{% extends "base.html" %}
{% block header_ext %}
{% endblock %}
{% block sidebar %}
{% endblock %}
{% block content %}
<h1 id="alt-layout">To Be Implemented...</h1>
<p>
This feature has yet to be implemented, but rest assured that it will be!
<br />
<br />
<br />
Eventually...
</p>
{% endblock %}

View file

@ -0,0 +1,11 @@
{% extends "base.html" %}
{% block sidebar %}
{% endblock %}
{% block header_ext %}
{% endblock %}
{% block content %}
<h1>Error 404 - Page not found</h1>
{% endblock %}

View file

@ -0,0 +1,10 @@
{% extends "base.html" %}
{% block sidebar %}
{% endblock %}
{% block header_ext %}
{% endblock %}
{% block content %}
<h1>Error 500 - A Server Error was encountered.</h1>
{% endblock %}

View file

@ -0,0 +1,95 @@
{% load staticfiles %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-AU">
<head>
<meta http-equiv="content-type" content="application/xhtml+xml; charset=UTF-8" />
<meta name="author" content="haran" />
<meta name="generator" content="haran" />
{% if sidebar %}
<link rel="stylesheet" type="text/css" href="{% static "evennia_general/css/prosimii-screen-alt.css" %}" media="screen" title="Prosimii (Sidebar)" />
{% else %}
<link rel="stylesheet" type="text/css" href="{% static "evennia_general/css/prosimii-screen.css" %}" media="screen" title="Prosimii" />
{% endif %}
<link rel="stylesheet alternative" type="text/css" href="{% static "evennia_general/css/prosimii-print.css" %}" media="screen" title="Print Preview" />
<link rel="stylesheet" type="text/css" href="{% static "evennia_general/css/prosimii-print.css" %}" media="print" />
{% block header_ext %}
{% endblock %}
<title>{{game_name}} - {% if flatpage %}{{flatpage.title}}{% else %}{% block titleblock %}{{page_title}}{% endblock %}{% endif %}</title>
</head>
<body>
<!-- For non-visual user agents: -->
<div id="top"><a href="#main-copy" class="doNotDisplay doNotPrint">Skip to main content.</a></div>
<!-- ##### Header ##### -->
<div id="header">
<div class="superHeader">
<!--span>Sites:</span-->
<a href="http://evennia.com" title="The Python-based MUD server">Evennia.com</a>
</div>
<div class="midHeader">
<img src="{% static "evennia_general/images/evennia_logo.png" %}" align='left'/> <h1 class="headerTitle" lang="la">{{game_name}}</h1>
<div class="headerSubTitle" title="Slogan">
<!-- Insert a slogan here if you want -->
{{game_slogan}} &nbsp;
</div>
<br class="doNotDisplay doNotPrint" />
<div class="headerLinks">
<span class="doNotDisplay">Tools:</span>
{% if user.is_authenticated %}
<a href="{% url 'logout' %}">Log Out &laquo;</a>
<span class="doNotDisplay">|</span>
Logged in as {{user.username}} &laquo;
{% else %}
<a href="{% url 'login' %}">Log In &laquo;</a>
<span class="doNotDisplay">|</span>
<a href="{% url 'to_be_implemented' %}">Register &laquo;</a>
{% endif %}
</div>
</div>
<div class="subHeader">
<!--span class="doNotDisplay">Navigation:</span-->
<a href="/">Home</a> |
<a href="https://github.com/evennia/evennia/wiki/Evennia-Introduction/">About</a> |
<a href="https://github.com/evennia/evennia/wiki">Documentation</a> |
<a href="{% url 'admin:index' %}">Admin Interface</a>
{% if webclient_enabled %}
| <a href="{% url 'webclient:index' %}">Play Online</a>
{% endif %}
</div>
</div>
<!-- ##### Side Menu ##### -->
{% block sidebar %}{% endblock %}
<!-- ##### Main Copy ##### -->
<div id="main-copy">
{% block content %}
{% endblock %}
</div>
<!-- ##### Footer ##### -->
<div id="footer">
<span class="doNotPrint">
Template design by
<a href="http://www.oswd.org/designs/search/designer/id/3013/"
title="Other designs by haran">haran</a>.
Powered by
<a href="http://evennia.com">Evennia.</a>
<br />
</span>
</div>
</body>
</html>

View file

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block header_ext %}
{% endblock %}
{% block sidebar %}
{% endblock %}
{% block content %}
<h1 id="alt-layout">{{flatpage.title}}</h1>
{{flatpage.content}}
{% endblock %}

View file

@ -0,0 +1,10 @@
{% extends "base.html" %}
{% block titleblock %}
Logged Out
{% endblock %}
{% block content %}
<h1 id="alt-layout">Logged Out</h1>
<p>You have been logged out.</p>
{% endblock %}

View file

@ -0,0 +1,32 @@
{% extends "base.html" %}
{% block titleblock %}
Login
{% endblock %}
{% block content %}
<h1 id="alt-layout">Login</h1>
{% if user.is_authenticated %}
<p>You are already logged in!</p>
{% else %}
{% if form.has_errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
<form method="post" action=".">
{% csrf_token %}
<table>
<tr>
<td><label for="id_username">Username:</label></td>
<td>{{ form.username }}</td>
</tr>
<tr>
<td><label for="id_password">Password:</label></td>
<td>{{ form.password }}</td>
</tr>
</table>
<input type="submit" value="Login" />
<input type="hidden" name="next" value="{{ next }}" />
</form>
{% endif %}
{% endblock %}

68
lib/web/urls.py Executable file
View file

@ -0,0 +1,68 @@
#
# File that determines what each URL points to. This uses _Python_ regular
# expressions, not Perl's.
#
# See:
# http://diveintopython.org/regular_expressions/street_addresses.html#re.matching.2.3
#
from django.conf import settings
from django.conf.urls import url, include
from django.contrib import admin
from django.views.generic import RedirectView
## fix to resolve lazy-loading bug
## https://code.djangoproject.com/ticket/10405#comment:11
#from django.db.models.loading import cache as model_cache
#if not model_cache.loaded:
# model_cache.get_models()
# loop over all settings.INSTALLED_APPS and execute code in
# files named admin.py in each such app (this will add those
# models to the admin site)
admin.autodiscover()
# Setup the root url tree from /
urlpatterns = [
# User Authentication
url(r'^accounts/login', 'django.contrib.auth.views.login', name="login"),
url(r'^accounts/logout', 'django.contrib.auth.views.logout', name="logout"),
# Page place-holder for things that aren't implemented yet.
url(r'^tbi/', 'src.web.views.to_be_implemented', name='to_be_implemented'),
# Admin interface
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
# favicon
url(r'^favicon\.ico$', RedirectView.as_view(url='/media/images/favicon.ico')),
# ajax stuff
url(r'^webclient/', include('src.web.webclient.urls', namespace='webclient', app_name='webclient')),
# Front page
url(r'^$', 'src.web.views.page_index', name="index"),
# Django original admin page. Make this URL is always available, whether
# we've chosen to use Evennia's custom admin or not.
url(r'django_admin/', 'src.web.views.admin_wrapper', name="django_admin")]
if settings.EVENNIA_ADMIN:
urlpatterns += [
# Our override for the admin.
url('^admin/$', 'src.web.views.evennia_admin', name="evennia_admin"),
# Makes sure that other admin pages get loaded.
url(r'^admin/', include(admin.site.urls))]
else:
# Just include the normal Django admin.
urlpatterns += [url(r'^admin/', include(admin.site.urls))]
# This sets up the server if the user want to run the Django
# test server (this should normally not be needed).
if settings.SERVE_MEDIA:
urlpatterns.extend([
url(r'^media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT}),
url(r'^static/(?P<path>.*)$', 'django.views.static.serve', {'document_root': settings.STATIC_ROOT})
])

View file

View file

@ -0,0 +1,16 @@
import os, sys
# Calculate the path based on the location of the WSGI script.
web_dir = os.path.dirname(os.path.dirname(__file__))
workspace = os.path.dirname(os.path.dirname(web_dir))
sys.path.insert(0, workspace)
os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
import django.core.handlers.wsgi
_application = django.core.handlers.wsgi.WSGIHandler()
# This handles apps mounted in places other than the root mount point
def application(environ, start_response):
environ['PATH_INFO'] = environ['SCRIPT_NAME'] + environ['PATH_INFO']
return _application(environ, start_response)

18
lib/web/utils/backends.py Normal file
View file

@ -0,0 +1,18 @@
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
class CaseInsensitiveModelBackend(ModelBackend):
"""
By default ModelBackend does case _sensitive_ username authentication, which isn't what is
generally expected. This backend supports case insensitive username authentication.
"""
def authenticate(self, username=None, password=None):
User = get_user_model()
try:
user = User.objects.get(username__iexact=username)
if user.check_password(password):
return user
else:
return None
except User.DoesNotExist:
return None

View file

@ -0,0 +1,45 @@
# WARNING: mod_python is no longer the recommended way to run Evennia's
# web front end. This file is no longer actively maintained and may
# no longer work. We suggest using mod_wsgi unless absolutely necessary.
# Add this vhost file to your Apache sites-enabled directory, typically found
# at /etc/apache2/sites-enabled. You'll need to go through and change the
# /home/evennia to point to the correct home directory, and the evennia
# subdir must be under that home directory.
# A NOTE ON IMAGES/CSS: These files must be handled separately from the actual
# dynamic content which is interpreted by Python. You may host these static
# images/css from the same server or a different one. The static media
# is served from /home/evennia/evennia/media. The easiest way to serve
# this stuff is by either hosting them remotely or symlinking
# the media directory into an existing Apache site's directory.
# A NOTE ON ADMIN IMAGES/CSS: You'll need to create a symlink called
# 'amedia' under the media directory if you want to use the admin interface.
# This can be found in the Django package in your Python path. For example,
# the default is: /usr/lib/python2.4/site-packages/django/contrib/admin/media/
# although your location may vary.
<VirtualHost *>
# Set ServerName and ServerAdmin appropriately
ServerName evennia.somewhere.com
ServerAdmin someone@somewhere.com
DocumentRoot /home/evennia/evennia
<Directory "/home/evennia/evennia">
SetHandler python-program
PythonHandler django.core.handlers.modpython
PythonPath "['/home/evennia/evennia'] + sys.path"
SetEnv DJANGO_SETTINGS_MODULE settings
PythonDebug On
</Directory>
Alias /media/ "/home/evennia/evennia/media/"
<Directory "/home/evennia/evennia/media/">
SetHandler None
</Directory>
<LocationMatch "\.(jpg|gif|png|css)$">
SetHandler None
</LocationMatch>
</VirtualHost>

View file

@ -0,0 +1,51 @@
<VirtualHost *>
# This is an example mod_wsgi apache2 vhost. There are a number of
# things you'll need to change. They are commented below. Make sure
# you read and understand the stuff below.
# Copy this file to your apache2 vhosts directory before editing.
# This file is merely a template. The vhost directory is usually
# something like /etc/apache2/sites-enabled/.
# You'll want to replace the following with your domain info.
ServerName dev.evennia.com
ServerAlias www.dev.evennia.com
#
## BEGIN EVENNIA WEB CONFIG
#
# evennia_web is just an internal name for mod_wsgi and may be left
# The user and group options are the user and the group that the
# python code will be executed under. This user must have permissions
# to the evennia source.
WSGIDaemonProcess evennia_web user=evennia group=evennia threads=25
WSGIProcessGroup evennia_web
# This needs to be changed to the appropriate path on your django
# install. It serves admin site media.
# For Python 2.6, this is:
# /usr/lib/python2.6/dist-packages/django/contrib/admin/media/
Alias /media/amedia/ "/usr/lib/python2.5/site-packages/django/contrib/admin/media/"
<Directory "/usr/lib/python2.5/site-packages/django/contrib/admin/media">
Order allow,deny
Options Indexes
Allow from all
IndexOptions FancyIndexing
</Directory>
# Media Directory. This needs to be set to your equivalent path.
Alias /media/ "/home/evennia/evennia/game/web/media/"
<Directory "/home/evennia/evennia/game/web/media">
Order allow,deny
Options Indexes FollowSymLinks
Allow from all
IndexOptions FancyIndexing
</Directory>
# WSGI Config File. You'll need to update this path as well.
WSGIScriptAlias / /home/evennia/evennia/game/web/utils/apache_wsgi.conf
#
## END EVENNIA WEB CONFIG
#
</VirtualHost>

View file

@ -0,0 +1,52 @@
#
# This file defines global variables that will always be
# available in a view context without having to repeatedly
# include it. For this to work, this file is included in
# the settings file, in the TEMPLATE_CONTEXT_PROCESSORS
# tuple.
#
from django.conf import settings
from src.utils.utils import get_evennia_version
# Determine the site name and server version
try:
GAME_NAME = settings.SERVERNAME.strip()
except AttributeError:
GAME_NAME = "Evennia"
SERVER_VERSION = get_evennia_version()
# Setup lists of the most relevant apps so
# the adminsite becomes more readable.
PLAYER_RELATED = ['Players']
GAME_ENTITIES = ['Objects', 'Scripts', 'Comms', 'Help']
GAME_SETUP = ['Permissions', 'Config']
CONNECTIONS = ['Irc', 'Imc2']
WEBSITE = ['Flatpages', 'News', 'Sites']
# The main context processor function
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
WEBSOCKET_CLIENT_ENABLED = settings.WEBSOCKET_CLIENT_ENABLED
WSURL = "%s:%s" % (settings.WEBSOCKET_CLIENT_URL, settings.WEBSOCKET_CLIENT_PORT)
def general_context(request):
"""
Returns common Evennia-related context stuff, which
is automatically added to context of all views.
"""
return {
'game_name': GAME_NAME,
'game_slogan': SERVER_VERSION,
'evennia_userapps': PLAYER_RELATED,
'evennia_entityapps': GAME_ENTITIES,
'evennia_setupapps': GAME_SETUP,
'evennia_connectapps': CONNECTIONS,
'evennia_websiteapps':WEBSITE,
"webclient_enabled" : WEBCLIENT_ENABLED,
"websocket_enabled" : WEBSOCKET_CLIENT_ENABLED,
"websocket_url" : WSURL
}

84
lib/web/views.py Normal file
View file

@ -0,0 +1,84 @@
"""
This file contains the generic, assorted views that don't fall under one of
the other applications. Views are django's way of processing e.g. html
templates on the fly.
"""
from django.contrib.admin.sites import site
from django.conf import settings
from django.contrib.admin.views.decorators import staff_member_required
from django.shortcuts import render
from src.objects.models import ObjectDB
from src.players.models import PlayerDB
_BASE_CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS
def page_index(request):
"""
Main root page.
"""
# Some misc. configurable stuff.
# TODO: Move this to either SQL or settings.py based configuration.
fpage_player_limit = 4
# A QuerySet of the most recently connected players.
recent_users = PlayerDB.objects.get_recently_connected_players()[:fpage_player_limit]
nplyrs_conn_recent = len(recent_users) or "none"
nplyrs = PlayerDB.objects.num_total_players() or "none"
nplyrs_reg_recent = len(PlayerDB.objects.get_recently_created_players()) or "none"
nsess = len(PlayerDB.objects.get_connected_players()) or "no one"
nobjs = ObjectDB.objects.all().count()
nrooms = ObjectDB.objects.filter(db_location__isnull=True).exclude(db_typeclass_path=_BASE_CHAR_TYPECLASS).count()
nexits = ObjectDB.objects.filter(db_location__isnull=False, db_destination__isnull=False).count()
nchars = ObjectDB.objects.filter(db_typeclass_path=_BASE_CHAR_TYPECLASS).count()
nothers = nobjs - nrooms - nchars - nexits
pagevars = {
"page_title": "Front Page",
"players_connected_recent": recent_users,
"num_players_connected": nsess or "noone",
"num_players_registered": nplyrs or "no",
"num_players_connected_recent": nplyrs_conn_recent or "no",
"num_players_registered_recent": nplyrs_reg_recent or "noone",
"num_rooms": nrooms or "none",
"num_exits": nexits or "no",
"num_objects" : nobjs or "none",
"num_characters": nchars or "no",
"num_others": nothers or "no"
}
return render(request, 'evennia_general/index.html', pagevars)
def to_be_implemented(request):
"""
A notice letting the user know that this particular feature hasn't been
implemented yet.
"""
pagevars = {
"page_title": "To Be Implemented...",
}
return render(request, 'evennia_general/tbi.html', pagevars)
@staff_member_required
def evennia_admin(request):
"""
Helpful Evennia-specific admin page.
"""
return render(
request, 'evennia_general/evennia_admin.html', {
'playerdb': PlayerDB})
def admin_wrapper(request):
"""
Wrapper that allows us to properly use the base Django admin site, if needed.
"""
return staff_member_required(site.index)(request)

View file

View file

@ -0,0 +1,7 @@
#
# Define database entities for the app.
#
from django.db import models

View file

@ -0,0 +1,150 @@
/* ---
Style sheet for Evennia's web client.
This should possibly somehow be incoorporated with the
overall website theme in the future?
--- */
/* Overall element look */
html, body { height: 100% }
body {
margin: 0;
padding: 0;
background: #000;
color: #ccc;
font-size: .9em;
font-family: 'DejaVu Sans Mono', Consolas, Inconsolata, 'Lucida Console', monospace;
line-height: 1.6em }
a:link, a:visited { color: #fff }
a:hover, a:active { color: #ccc }
/* Set this to e.g. bolder if wanting to have ansi-highlights bolden
* stand-alone text.*/
strong {font-weight:normal;}
div {margin:0px;}
/* Base style for new messages in the main message area */
/*.msg {
white-space: pre-wrap; }
padding: .5em .9em;} */
/*border-bottom: 1px dotted #222 } /*optional line between messages */
/* Utility messages (green) */
.sys { color: #0f0 }
/* Messages echoed back after input */
.inp { color: #555 }
/* Messages returned from the server (most messages) */
.out { color: #aaa }
/* Error messages (red) */
.err { color: #f00 }
/* Prompt base (white) */
.prompt {color: #fff }
/* Style specific classes corresponding to formatted, narative text. */
.red { color: red; }
.maroon { color: maroon; }
.lime { color: lime; }
.green { color: green; }
.yellow { color: yellow; }
.olive { color: olive; }
.blue { color: blue; }
.navy { color: navy; }
.magenta { color: #FF00FF; }
.purple { color: purple; }
.cyan { color: #00FFFF; }
.teal { color: teal; }
.white { color: white; }
.gray { color: gray; }
.dimgray {color: #696969;}
.black {color: black;}
.underline { text-decoration: underline; }
.bgred { background-color: red;}
.bgmaroon { background-color: maroon;}
.bglime { background-color: lime;}
.bggreen { background-color: green;}
.bgyellow { background-color: yellow;}
.bgolive { background-color: olive;}
.bgblue { background-color: blue;}
.bgnavy { background-color: navy;}
.bgmagenta { background-color: #FF00FF;}
.bgpurple { background-color: purple;}
.bgcyan { background-color: #00FFFF;}
.bgteal { background-color: teal;}
.bgwhite { background-color: white;}
.bggray { background-color: gray;}
.bgdimgray { background-color: #696969;}
.bgblack { background-color: black;}
/* Container surrounding entire chat */
#wrapper {
position: relative;
height: 100% }
/* Main scrolling message area */
#messagewindow {
height: 93%;
overflow: auto }
/* Input area containing input field and button */
#inputform {
position: absolute;
width: 100%;
padding: .8em 0;
bottom: 0 }
#inputcontrol {
padding: 0 .8em;
overflow: auto }
/* Input field */
#inputfield {
float: left;
width: 87%;}
#inputfield:focus {
outline: 0 }
/* Input 'send' button */
#inputsend {
float: left;
width: 8% }
#inputfield { margin-right: .5em }
#inputfield, #inputsend {
border: 1px solid #555;
background: #000;
color: #fff;
padding: .4em .45em;
font-size: 1.1em;
font-family: 'DejaVu Sans Mono', Consolas, Inconsolata, 'Lucida Console', monospace }
/* prompt area above input field */
#prompt {
margin-top: .8em;}
/* No javascript warning */
#connecting {
padding: .5em .9em }
/*Example player count display */
#playercount { margin-left: .8em }
/* Testing */
/*#inputform {
outline: 1px dotted blue }
#messagewindow {
outline: 1px dotted red }
#wrapper {
outline: 1px dotted green }*/

View file

@ -0,0 +1,298 @@
/*
Evennia ajax webclient (javascript component)
The client is composed of several parts:
templates/webclient.html - the main page
webclient/views.py - the django view serving the template (based on urls.py pattern)
src/server/webclient.py - the server component receiving requests from the client
this file - the javascript component handling dynamic ajax content
This implements an ajax mud client for use with Evennia, using jQuery
for simplicity. It communicates with the Twisted server on the address
/webclientdata through POST requests. Each request must at least
contain the 'mode' of the request to be handled by the protocol:
mode 'receive' - tell the server that we are ready to receive data. This is a
long-polling (comet-style) request since the server
will not reply until it actually has data available.
The returned data object has two variables 'msg' and 'data'
where msg should be output and 'data' is an arbitrary piece
of data the server and client understands (not used in default
client).
mode 'input' - the user has input data on some form. The POST request
should also contain variables 'msg' and 'data' where
the 'msg' is a string and 'data' is an arbitrary piece
of data from the client that the server knows how to
deal with (not used in this example client).
mode 'init' - starts the connection. All setup the server is requered to do
should happen at this point. The server returns a data object
with the 'msg' property containing the server address.
mode 'close' - closes the connection. The server closes the session and does
cleanup at this point.
*/
// jQuery must be imported by the calling html page before this script
// There are plenty of help on using the jQuery library on http://jquery.com/
$.fn.appendCaret = function() {
/* jQuery extension that will forward the caret to the end of the input, and
won't harm other elements (although calling this on multiple inputs might
not have the expected consequences).
Thanks to
http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
for the good starting point. */
return this.each(function() {
var range,
// Index at where to place the caret.
end,
self = this;
if (self.setSelectionRange) {
// other browsers
end = self.value.length;
self.focus();
// NOTE: Need to delay the caret movement until after the callstack.
setTimeout(function() {
self.setSelectionRange(end, end);
}, 0);
}
else if (self.createTextRange) {
// IE
end = self.value.length - 1;
range = self.createTextRange();
range.collapse(true);
range.moveEnd('character', end);
range.moveStart('character', end);
// NOTE: I haven't tested to see if IE has the same problem as
// W3C browsers seem to have in this context (needing to fire
// select after callstack).
range.select();
}
});
};
// Server communications
var CLIENT_HASH = '0'; // variable holding the client id
function webclient_receive(){
// This starts an asynchronous long-polling request. It will either timeout
// or receive data from the 'webclientdata' url. In both cases a new request will
// immediately be started.
$.ajax({
type: "POST",
url: "/webclientdata",
async: true, // Turns off browser loading indicator
cache: false, // Forces browser reload independent of cache
timeout:30000, // Timeout in ms. After this time a new long-poll will be started.
dataType:"json",
data: {mode:'receive', 'suid':CLIENT_HASH},
// callback methods
success: function(data){ // called when request to waitreceive completes
msg_display("out", data.msg); // Add response to the message area
webclient_receive(); // immediately start a new request
},
error: function(XMLHttpRequest, textStatus, errorThrown){
webclient_receive(); // A possible timeout. Resend request immediately
},
});
};
function webclient_input(arg, no_update){
// Send an input from the player to the server
// no_update is used for sending idle messages behind the scenes.
var outmsg = typeof(arg) != 'undefined' ? arg : $("#inputfield").val();
$.ajax({
type: "POST",
url: "/webclientdata",
async: true,
cache: false,
timeout: 30000,
data: {mode:'input', msg:outmsg, data:'NoData', 'suid':CLIENT_HASH},
//callback methods
success: function(data){
//if (outmsg.length > 0 ) msg_display("inp", outmsg) // echo input on command line
if (no_update == undefined) {
history_add(outmsg);
HISTORY_POS = 0;
$('#inputform')[0].reset(); // clear input field
}
},
error: function(XMLHttpRequest, textStatus, errorThrown){
msg_display("err", "Error: Server returned an error or timed out. Try resending or reloading the page.");
},
})
}
function webclient_init(){
// Start the connection by making sure the server is ready
$.ajax({
type: "POST",
url: "/webclientdata",
async: true,
cache: false,
timeout: 50000,
dataType:"json",
data: {mode:'init', 'suid':CLIENT_HASH},
// callback methods
success: function(data){ // called when request to initdata completes
$("#connecting").remove() // remove the "connecting ..." message.
CLIENT_HASH = data.suid // unique id hash given from server
// A small timeout to stop 'loading' indicator in Chrome
setTimeout(function () {
$("#playercount").fadeOut('slow', webclient_set_sizes);
}, 10000);
// Report success
msg_display('sys',"Connected to " + data.msg + ".");
// Wait for input
webclient_receive();
},
error: function(XMLHttpRequest, textStatus, errorThrown){
msg_display("err", "Connection error ..." + " (" + errorThrown + ")");
setTimeout('webclient_receive()', 15000); // try again after 15 seconds
},
});
}
function webclient_close(){
// Kill the connection and do house cleaning on the server.
$.ajax({
type: "POST",
url: "/webclientdata",
async: false,
cache: false,
timeout: 50000,
dataType: "json",
data: {mode: 'close', 'suid': CLIENT_HASH},
success: function(data){
CLIENT_HASH = '0';
alert("Mud client connection was closed cleanly.");
},
error: function(XMLHttpRequest, textStatus, errorThrown){
CLIENT_HASH = '0';
}
});
}
// Display messages
function msg_display(type, msg){
// Add a div to the message window.
// type gives the class of div to use.
$("#messagewindow").append(
"<div class='msg "+ type +"'>"+ msg +"</div>");
// scroll message window to bottom
$('#messagewindow').animate({scrollTop: $('#messagewindow')[0].scrollHeight});
}
// Input history mechanism
var HISTORY_MAX_LENGTH = 21
var HISTORY = new Array();
HISTORY[0] = '';
var HISTORY_POS = 0;
function history_step_back() {
// step backwards in history stack
HISTORY_POS = Math.min(++HISTORY_POS, HISTORY.length-1);
return HISTORY[HISTORY.length-1 - HISTORY_POS];
}
function history_step_fwd() {
// step forward in history stack
HISTORY_POS = Math.max(--HISTORY_POS, 0);
return HISTORY[HISTORY.length-1 - HISTORY_POS];
}
function history_add(input) {
// add an entry to history
if (input != HISTORY[HISTORY.length-1]) {
if (HISTORY.length >= HISTORY_MAX_LENGTH) {
HISTORY.shift(); // kill oldest history entry
}
HISTORY[HISTORY.length-1] = input;
HISTORY[HISTORY.length] = '';
}
}
// Catching keyboard shortcuts
$(document).keydown( function(event) {
// Get the pressed key (normalized by jQuery)
var code = event.which,
inputField = $("#inputfield");
// always focus input field no matter which key is pressed
inputField.focus();
// Special keys recognized by client
//msg_display("out", "key code pressed: " + code); // debug
if (code == 13) { // Enter Key
webclient_input();
event.preventDefault();
}
else {
if (code == 38) { // arrow up 38
inputField.val(history_step_back()).appendCaret();
}
else if (code == 40) { // arrow down 40
inputField.val(history_step_fwd()).appendCaret();
}
}
});
// handler to avoid double-clicks until the ajax request finishes
$("#inputsend").one("click", webclient_input)
function webclient_set_sizes() {
// Sets the size of the message window
var win_h = $(document).height();
//var win_w = $('#wrapper').width();
var inp_h = $('#inputform').outerHeight(true);
//var inp_w = $('#inputsend').outerWidth(true);
$("#messagewindow").css({'height': win_h - inp_h - 1});
//$("#inputfield").css({'width': win_w - inp_w - 20});
}
// Callback function - called when page has finished loading (gets things going)
$(document).ready(function(){
// remove the "no javascript" warning, since we obviously have javascript
$('#noscript').remove();
// set sizes of elements and reposition them
webclient_set_sizes();
// a small timeout to stop 'loading' indicator in Chrome
setTimeout(function () {
webclient_init();
}, 500);
// set an idle timer to avoid proxy servers to time out on us (every 3 minutes)
setInterval(function() {
webclient_input("idle", true);
}, 60000*3);
});
// Callback function - called when the browser window resizes
$(window).resize(webclient_set_sizes);
// Callback function - called when page is closed or moved away from.
$(window).bind("beforeunload", webclient_close);

View file

@ -0,0 +1,357 @@
/*
Evennia websocket webclient (javascript component)
The client is composed of two parts:
src/server/portal/websocket_client.py - the portal-side component
this file - the javascript component handling dynamic content
messages sent to the client is one of two modes:
OOB("func1",args, "func2",args, ...) - OOB command executions, this will
call unique javascript functions
func1(args), func2(args) etc.
text - any other text is considered a normal text output in the main output window.
*/
// If on, allows client user to send OOB messages to server by
// prepending with ##OOB{}, for example ##OOB{"echo":[1,2,3,4]}
var OOB_debug = true
//
// Custom OOB functions
// functions defined here can be called by name by the server. For
// example input OOB{"echo":(args),{kwargs}} will trigger a function named
// echo(args, kwargs). The commands the server understands is set by
// settings.OOB_PLUGIN_MODULES
function echo(args, kwargs) {
// example echo function.
doShow("out", "ECHO return: " + args) }
function list (args, kwargs) {
// show in main window
doShow("out", args) }
function send (args, kwargs) {
// show in main window. SEND returns kwargs {name:value}.
for (sendvalue in kwargs) {
doShow("out", sendvalue + " = " + kwargs[sendvalue]);}
}
function report (args, kwargs) {
// show in main window. REPORT returns kwargs
// {attrfieldname:value}
for (name in kwargs) {
doShow("out", name + " = " + kwargs[name]) }
}
function repeat (args, kwargs) {
// called by repeating oob funcs
doShow("out", args) }
function err (args, kwargs) {
// display error
doShow("err", args) }
//
// Webclient code
//
function webclient_init(){
// called when client is just initializing
websocket = new WebSocket(wsurl);
websocket.onopen = function(evt) { onOpen(evt) };
websocket.onclose = function(evt) { onClose(evt) };
websocket.onmessage = function(evt) { onMessage(evt) };
websocket.onerror = function(evt) { onError(evt) };
}
function onOpen(evt) {
// called when client is first connecting
$("#connecting").remove(); // remove the "connecting ..." message
doShow("sys", "Using websockets - connected to " + wsurl + ".")
setTimeout(function () {
$("#numplayers").fadeOut('slow', doSetSizes);
}, 10000);
}
function onClose(evt) {
// called when client is closing
CLIENT_HASH = 0;
alert("Mud client connection was closed cleanly.");
}
function onMessage(evt) {
// called when the Evennia is sending data to client
var inmsg = evt.data
if (inmsg.length > 3 && inmsg.substr(0, 3) == "OOB") {
// dynamically call oob methods, if available
try {
var oobarray = JSON.parse(inmsg.slice(3));} // everything after OOB }
catch(err) {
// not JSON packed - a normal text
doShow('out', inmsg);
return;
}
if (typeof oobarray != "undefined") {
for (var ind in oobarray) {
try {
window[oobarray[ind][0]](oobarray[ind][1], oobarray[ind][2]) }
catch(err) {
doShow("err", "Could not execute js OOB function '" + oobarray[ind][0] + "(" + oobarray[ind][1] + oobarray[ind][2] + ")'") }
}
}
}
else if (inmsg.length >= 6 && inmsg.substr(0, 6) == "PROMPT") {
// handle prompt
var game_prompt = inmsg.slice(6);
doPrompt("prompt", game_prompt);
}
else {
// normal message
doShow('out', inmsg); }
}
function onError(evt) {
// called on a server error
doShow('err', "Connection error trying to access websocket on " + wsurl + ". " + "Contact the admin and/or check settings.WEBSOCKET_CLIENT_URL.");
}
function doSend(){
// relays data from client to Evennia.
// If OOB_debug is set, allows OOB test data on the
// form ##OOB{func:args}
outmsg = $("#inputfield").val();
history_add(outmsg);
HISTORY_POS = 0;
$('#inputform')[0].reset(); // clear input field
if (OOB_debug && outmsg.length > 4 && outmsg.substr(0, 5) == "##OOB") {
if (outmsg == "##OOBUNITTEST") {
// unittest mode
doShow("out", "OOB testing mode ...");
doOOB(JSON.parse('{"ECHO":"Echo test"}'));
doOOB(JSON.parse('{"LIST":"COMMANDS"}'));
doOOB(JSON.parse('{"SEND":"CHARACTER_NAME"}'));
doOOB(JSON.parse('{"REPORT":"TEST"}'));
doOOB(JSON.parse('{"UNREPORT":"TEST"}'));
doOOB(JSON.parse('{"REPEAT": 1}'));
doOOB(JSON.parse('{"UNREPEAT": 1}'));
doShow("out", "... OOB testing mode done.");
return
}
// test OOB messaging
try {
doShow("out", "OOB input: " + outmsg.slice(5));
if (outmsg.length == 5) {
doShow("err", "OOB testing syntax: ##OOB{\"cmdname:args, ...}"); }
else {
doOOB(JSON.parse(outmsg.slice(5))); } }
catch(err) {
doShow("err", err) }
}
else {
// normal output
websocket.send(outmsg); }
}
function doOOB(oobdict){
// Send OOB data from client to Evennia.
// Takes input on form {funcname:[args], funcname: [args], ... }
var oobmsg = JSON.stringify(oobdict);
websocket.send("OOB" + oobmsg);
}
function doShow(type, msg){
// Add msg to the main output window.
// type gives the class of div to use.
// The default types are
// "out" (normal output) or "err" (red error message)
$("#messagewindow").append(
"<div class='msg "+ type +"'>"+ msg +"</div>");
// scroll message window to bottom
$('#messagewindow').animate({scrollTop: $('#messagewindow')[0].scrollHeight});
}
function doPrompt(type, msg){
// Display prompt
$('#prompt').replaceWith(
"<div id='prompt' class='msg "+ type +"'>" + msg + "</div>");
}
function doSetSizes() {
// Sets the size of the message window
var win_h = $(document).height();
//var win_w = $('#wrapper').width();
var inp_h = $('#inputform').outerHeight(true);
//var inp_w = $('#inputsend').outerWidth(true);
$("#messagewindow").css({'height': win_h - inp_h - 1});
//$("#inputfield").css({'width': win_w - inp_w - 20});
}
//
// Input code
//
// Input history
var HISTORY_MAX_LENGTH = 21
var HISTORY = new Array();
HISTORY[0] = '';
var HISTORY_POS = 0;
function history_step_back() {
// step backwards in history stack
HISTORY_POS = Math.min(++HISTORY_POS, HISTORY.length-1);
return HISTORY[HISTORY.length-1 - HISTORY_POS];
}
function history_step_fwd() {
// step forward in history stack
HISTORY_POS = Math.max(--HISTORY_POS, 0);
return HISTORY[HISTORY.length-1 - HISTORY_POS];
}
function history_add(input) {
// add an entry to history
if (input != HISTORY[HISTORY.length-1]) {
if (HISTORY.length >= HISTORY_MAX_LENGTH) {
HISTORY.shift(); // kill oldest history entry
}
HISTORY[HISTORY.length-1] = input;
HISTORY[HISTORY.length] = '';
}
}
// Catching keyboard shortcuts
$.fn.appendCaret = function() {
/* jQuery extension that will forward the caret to the end of the input, and
won't harm other elements (although calling this on multiple inputs might
not have the expected consequences).
Thanks to
http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
for the good starting point. */
return this.each(function() {
var range,
// Index at where to place the caret.
end,
self = this;
if (self.setSelectionRange) {
// other browsers
end = self.value.length;
self.focus();
// NOTE: Need to delay the caret movement until after the callstack.
setTimeout(function() {
self.setSelectionRange(end, end);
}, 0);
}
else if (self.createTextRange) {
// IE
end = self.value.length - 1;
range = self.createTextRange();
range.collapse(true);
range.moveEnd('character', end);
range.moveStart('character', end);
// NOTE: I haven't tested to see if IE has the same problem as
// W3C browsers seem to have in this context (needing to fire
// select after callstack).
range.select();
}
});
};
$.fn.appendCaret = function() {
/* jQuery extension that will forward the caret to the end of the input, and
won't harm other elements (although calling this on multiple inputs might
not have the expected consequences).
Thanks to
http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
for the good starting point. */
return this.each(function() {
var range,
// Index at where to place the caret.
end,
self = this;
if (self.setSelectionRange) {
// other browsers
end = self.value.length;
self.focus();
// NOTE: Need to delay the caret movement until after the callstack.
setTimeout(function() {
self.setSelectionRange(end, end);
}, 0);
}
else if (self.createTextRange) {
// IE
end = self.value.length - 1;
range = self.createTextRange();
range.collapse(true);
range.moveEnd('character', end);
range.moveStart('character', end);
// NOTE: I haven't tested to see if IE has the same problem as
// W3C browsers seem to have in this context (needing to fire
// select after callstack).
range.select();
}
});
};
// Input jQuery callbacks
$(document).keydown( function(event) {
// Get the pressed key (normalized by jQuery)
var code = event.which,
inputField = $("#inputfield");
// always focus input field no matter which key is pressed
inputField.focus();
// Special keys recognized by client
//doShow("out", "key code pressed: " + code); // debug
if (code == 13) { // Enter Key
doSend();
event.preventDefault();
}
else {
if (code == 38) { // arrow up 38
inputField.val(history_step_back()).appendCaret();
}
else if (code == 40) { // arrow down 40
inputField.val(history_step_fwd()).appendCaret();
}
}
});
// handler to avoid double-clicks until the ajax request finishes
//$("#inputsend").one("click", webclient_input)
// Callback function - called when the browser window resizes
$(window).resize(doSetSizes);
// Callback function - called when page is closed or moved away from.
//$(window).bind("beforeunload", webclient_close);
//
// Callback function - called when page has finished loading (kicks the client into gear)
$(document).ready(function(){
// remove the "no javascript" warning, since we obviously have javascript
$('#noscript').remove();
// set sizes of elements and reposition them
doSetSizes();
// a small timeout to stop 'loading' indicator in Chrome
setTimeout(function () {
webclient_init();
}, 500);
// set an idle timer to avoid proxy servers to time out on us (every 3 minutes)
setInterval(function() {
websocket.send("idle");
}, 60000*3);
});

View file

@ -0,0 +1,65 @@
<!DOCTYPE html>
{% load staticfiles %}
<html dir="ltr" lang="en">
<head>
<meta charset="utf-8" />
<title>Evennia web MUD client</title>
<!--CSS style sheet -->
<link rel='stylesheet' type="text/css" media="screen" href="{% static "webclient/css/webclient.css" %}">
<!-- Importing the online jQuery javascript library -->
<!--script src="http://code.jquery.com/jquery-1.6.1.js" type="text/javascript" charset="utf-8"></script-->
<script src="http://code.jquery.com/jquery-1.11.1.min.js" type="text/javascript" charset="utf-8"></script>
<!--for offline testing, download the jquery library from jquery.com-->
<!--script src="/media/javascript/jquery-1.11.1.js" type="text/javascript" charset="utf-8"></script-->
<script type="text/javascript" charset="utf-8">
if(!window.jQuery){document.write("<div class='err'>jQuery library not found or the online version could not be reached.</div>");}
</script>
{% if websocket_enabled %}
<script language="javascript" type="text/javascript">
if ("WebSocket" in window) {
<!-- Importing the Evennia websocket webclient component (requires jQuery) -->
var wsurl = "{{websocket_url}}";
document.write("\<script src=\"{% static "webclient/js/evennia_websocket_webclient.js" %}\" type=\"text/javascript\" charset=\"utf-8\"\>\</script\>")}
else {
<!-- No websocket support in browser. Importing the Evennia ajax webclient component (requires jQuery) -->
document.write("\<script src=\"{% static "webclient/js/evennia_ajax_webclient.js" %}\" type=\"text/javascript\" charset=\"utf-8\"\>\</script\>")}
</script>
{% else %}
<!-- websocket not enabled; use ajax -->
<script src="{% static "webclient/js/evennia_ajax_webclient.js" %}" type="text/javascript" charset="utf-8"></script>
{% endif %}
</head>
<body>
<div id="wrapper">
<div id="messagewindow">
<!--javascript kills this when page has finished loading: -->
<div id="connecting"> Connecting ...</div>
<!--this is supplied by django view - webclient/views.py: -->
<div id="noscript"><h3>Javascript Error: The Evennia MUD client requires that you have Javascript activated.</h3>
<p>Turn off eventual script blockers and/or switch to a web
browser supporting javascript.</p><p>For admins: The error
could also be due to not being able to access the online
jQuery javascript library. If you are testing the client
without an internet connection, you have to previously
download the jQuery library from http://code.jquery.com
(it's just one file) and then edit webclient.html to point
to the local copy.</p>
</div>
</div>
<form id="inputform" action="javascript:void(0);">
<div id="prompt"></div>
<div id="numplayers">Logged in Players: {{num_players_connected}}</div>
<div id="inputcontrol"><input type="text" id="inputfield" autocomplete="off"><input id="inputsend" type="button" value="send" onClick="doSend()"/></div>
</form>
</div>
</body>
</html>

View file

@ -0,0 +1,8 @@
"""
This structures the (simple) structure of the
webpage 'application'.
"""
from django.conf.urls import *
urlpatterns = [
url(r'^$', 'src.web.webclient.views.webclient', name="index")]

View file

@ -0,0 +1,27 @@
"""
This contains a simple view for rendering the webclient
page and serve it eventual static content.
"""
from django.shortcuts import render
from src.players.models import PlayerDB
def webclient(request):
"""
Webclient page template loading.
"""
# analyze request to find which port we are on
if int(request.META["SERVER_PORT"]) == 8000:
# we relay webclient to the portal port
print "Called from port 8000!"
#return redirect("http://localhost:8001/webclient/", permanent=True)
nsess = len(PlayerDB.objects.get_connected_players()) or "none"
# as an example we send the number of connected players to the template
pagevars = {'num_players_connected': nsess}
return render(request, 'webclient.html', pagevars)