Merge branch 'master' into develop
This commit is contained in:
commit
2585d33e7c
9 changed files with 131 additions and 57 deletions
|
|
@ -2324,6 +2324,7 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
||||||
Note that the only way to retrieve
|
Note that the only way to retrieve
|
||||||
an object from a None location is by direct #dbref
|
an object from a None location is by direct #dbref
|
||||||
reference. A puppeted object cannot be moved to None.
|
reference. A puppeted object cannot be moved to None.
|
||||||
|
loc - teleport object to the target's location instead of its contents
|
||||||
|
|
||||||
Teleports an object somewhere. If no object is given, you yourself
|
Teleports an object somewhere. If no object is given, you yourself
|
||||||
is teleported to the target location. """
|
is teleported to the target location. """
|
||||||
|
|
@ -2343,6 +2344,7 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
||||||
# setting switches
|
# setting switches
|
||||||
tel_quietly = "quiet" in switches
|
tel_quietly = "quiet" in switches
|
||||||
to_none = "tonone" in switches
|
to_none = "tonone" in switches
|
||||||
|
to_loc = "loc" in switches
|
||||||
|
|
||||||
if to_none:
|
if to_none:
|
||||||
# teleporting to None
|
# teleporting to None
|
||||||
|
|
@ -2368,7 +2370,7 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
# not teleporting to None location
|
# not teleporting to None location
|
||||||
if not args and not to_none:
|
if not args and not to_none:
|
||||||
caller.msg("Usage: teleport[/switches] [<obj> =] <target_loc>|home")
|
caller.msg("Usage: teleport[/switches] [<obj> =] <target_loc>||home")
|
||||||
return
|
return
|
||||||
|
|
||||||
if rhs:
|
if rhs:
|
||||||
|
|
@ -2384,6 +2386,11 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
||||||
if not destination:
|
if not destination:
|
||||||
caller.msg("Destination not found.")
|
caller.msg("Destination not found.")
|
||||||
return
|
return
|
||||||
|
if to_loc:
|
||||||
|
destination = destination.location
|
||||||
|
if not destination:
|
||||||
|
caller.msg("Destination has no location.")
|
||||||
|
return
|
||||||
if obj_to_teleport == destination:
|
if obj_to_teleport == destination:
|
||||||
caller.msg("You can't teleport an object inside of itself!")
|
caller.msg("You can't teleport an object inside of itself!")
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ from evennia.utils.utils import string_suggestions, class_from_module
|
||||||
|
|
||||||
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||||
HELP_MORE = settings.HELP_MORE
|
HELP_MORE = settings.HELP_MORE
|
||||||
|
CMD_IGNORE_PREFIXES = settings.CMD_IGNORE_PREFIXES
|
||||||
|
|
||||||
# limit symbol import for API
|
# limit symbol import for API
|
||||||
__all__ = ("CmdHelp", "CmdSetHelp")
|
__all__ = ("CmdHelp", "CmdSetHelp")
|
||||||
|
|
@ -231,6 +232,15 @@ class CmdHelp(Command):
|
||||||
|
|
||||||
# try an exact command auto-help match
|
# try an exact command auto-help match
|
||||||
match = [cmd for cmd in all_cmds if cmd == query]
|
match = [cmd for cmd in all_cmds if cmd == query]
|
||||||
|
|
||||||
|
if not match:
|
||||||
|
# try an inexact match with prefixes stripped from query and cmds
|
||||||
|
_query = query[1:] if query[0] in CMD_IGNORE_PREFIXES else query
|
||||||
|
|
||||||
|
match = [cmd for cmd in all_cmds
|
||||||
|
for m in cmd._matchset if m == _query or
|
||||||
|
m[0] in CMD_IGNORE_PREFIXES and m[1:] == _query]
|
||||||
|
|
||||||
if len(match) == 1:
|
if len(match) == 1:
|
||||||
formatted = self.format_help_entry(match[0].key,
|
formatted = self.format_help_entry(match[0].key,
|
||||||
match[0].get_help(caller, cmdset),
|
match[0].get_help(caller, cmdset),
|
||||||
|
|
|
||||||
|
|
@ -293,9 +293,9 @@ if WEBSERVER_ENABLED:
|
||||||
# create ajax client processes at /webclientdata
|
# create ajax client processes at /webclientdata
|
||||||
from evennia.server.portal import webclient_ajax
|
from evennia.server.portal import webclient_ajax
|
||||||
|
|
||||||
webclient = webclient_ajax.WebClient()
|
ajax_webclient = webclient_ajax.AjaxWebClient()
|
||||||
webclient.sessionhandler = PORTAL_SESSIONS
|
ajax_webclient.sessionhandler = PORTAL_SESSIONS
|
||||||
web_root.putChild("webclientdata", webclient)
|
web_root.putChild("webclientdata", ajax_webclient)
|
||||||
webclientstr = "\n + webclient (ajax only)"
|
webclientstr = "\n + webclient (ajax only)"
|
||||||
|
|
||||||
if WEBSOCKET_CLIENT_ENABLED and not websocket_started:
|
if WEBSOCKET_CLIENT_ENABLED and not websocket_started:
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,6 @@ class WebSocketClient(Protocol, Session):
|
||||||
csession = self.get_client_session()
|
csession = self.get_client_session()
|
||||||
|
|
||||||
if csession:
|
if csession:
|
||||||
print("In disconnect: csession uid=%s" % csession.get("webclient_authenticated_uid", None))
|
|
||||||
csession["webclient_authenticated_uid"] = None
|
csession["webclient_authenticated_uid"] = None
|
||||||
csession.save()
|
csession.save()
|
||||||
self.logged_in = False
|
self.logged_in = False
|
||||||
|
|
|
||||||
|
|
@ -53,11 +53,11 @@ def jsonify(obj):
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# WebClient resource - this is called by the ajax client
|
# AjaxWebClient resource - this is called by the ajax client
|
||||||
# using POST requests to /webclientdata.
|
# using POST requests to /webclientdata.
|
||||||
#
|
#
|
||||||
|
|
||||||
class WebClient(resource.Resource):
|
class AjaxWebClient(resource.Resource):
|
||||||
"""
|
"""
|
||||||
An ajax/comet long-polling transport
|
An ajax/comet long-polling transport
|
||||||
|
|
||||||
|
|
@ -163,13 +163,13 @@ class WebClient(resource.Resource):
|
||||||
remote_addr = request.getClientIP()
|
remote_addr = request.getClientIP()
|
||||||
host_string = "%s (%s:%s)" % (_SERVERNAME, request.getRequestHostname(), request.getHost().port)
|
host_string = "%s (%s:%s)" % (_SERVERNAME, request.getRequestHostname(), request.getHost().port)
|
||||||
|
|
||||||
sess = WebClientSession()
|
sess = AjaxWebClientSession()
|
||||||
sess.client = self
|
sess.client = self
|
||||||
sess.init_session("ajax/comet", remote_addr, self.sessionhandler)
|
sess.init_session("ajax/comet", remote_addr, self.sessionhandler)
|
||||||
|
|
||||||
sess.csessid = csessid
|
sess.csessid = csessid
|
||||||
csession = _CLIENT_SESSIONS(session_key=sess.csessid)
|
csession = _CLIENT_SESSIONS(session_key=sess.csessid)
|
||||||
uid = csession and csession.get("logged_in", False)
|
uid = csession and csession.get("webclient_authenticated_uid", False)
|
||||||
if uid:
|
if uid:
|
||||||
# the client session is already logged in
|
# the client session is already logged in
|
||||||
sess.uid = uid
|
sess.uid = uid
|
||||||
|
|
@ -292,14 +292,26 @@ class WebClient(resource.Resource):
|
||||||
# web client interface.
|
# web client interface.
|
||||||
#
|
#
|
||||||
|
|
||||||
class WebClientSession(session.Session):
|
class AjaxWebClientSession(session.Session):
|
||||||
"""
|
"""
|
||||||
This represents a session running in a webclient.
|
This represents a session running in an AjaxWebclient.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.protocol_name = "ajax/comet"
|
self.protocol_name = "ajax/comet"
|
||||||
super(WebClientSession, self).__init__(*args, **kwargs)
|
super(AjaxWebClientSession, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_client_session(self):
|
||||||
|
"""
|
||||||
|
Get the Client browser session (used for auto-login based on browser session)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
csession (ClientSession): This is a django-specific internal representation
|
||||||
|
of the browser session.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.csessid:
|
||||||
|
return _CLIENT_SESSIONS(session_key=self.csessid)
|
||||||
|
|
||||||
def disconnect(self, reason="Server disconnected."):
|
def disconnect(self, reason="Server disconnected."):
|
||||||
"""
|
"""
|
||||||
|
|
@ -308,10 +320,22 @@ class WebClientSession(session.Session):
|
||||||
Args:
|
Args:
|
||||||
reason (str): Motivation for the disconnect.
|
reason (str): Motivation for the disconnect.
|
||||||
"""
|
"""
|
||||||
|
csession = self.get_client_session()
|
||||||
|
|
||||||
|
if csession:
|
||||||
|
csession["webclient_authenticated_uid"] = None
|
||||||
|
csession.save()
|
||||||
|
self.logged_in = False
|
||||||
self.client.lineSend(self.csessid, ["connection_close", [reason], {}])
|
self.client.lineSend(self.csessid, ["connection_close", [reason], {}])
|
||||||
self.client.client_disconnect(self.csessid)
|
self.client.client_disconnect(self.csessid)
|
||||||
self.sessionhandler.disconnect(self)
|
self.sessionhandler.disconnect(self)
|
||||||
|
|
||||||
|
def at_login(self):
|
||||||
|
csession = self.get_client_session()
|
||||||
|
if csession:
|
||||||
|
csession["webclient_authenticated_uid"] = self.uid
|
||||||
|
csession.save()
|
||||||
|
|
||||||
def data_out(self, **kwargs):
|
def data_out(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Data Evennia -> User
|
Data Evennia -> User
|
||||||
|
|
|
||||||
|
|
@ -18,17 +18,28 @@ class TagAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
class TagForm(forms.ModelForm):
|
class TagForm(forms.ModelForm):
|
||||||
"""
|
"""
|
||||||
This form overrides the base behavior of the ModelForm that would be used for a Tag-through-model.
|
This form overrides the base behavior of the ModelForm that would be used for a
|
||||||
Since the through-models only have access to the foreignkeys of the Tag and the Object that they're
|
Tag-through-model. Since the through-models only have access to the foreignkeys of the Tag and
|
||||||
attached to, we need to spoof the behavior of it being a form that would correspond to its tag,
|
the Object that they're attached to, we need to spoof the behavior of it being a form that would
|
||||||
or the creation of a tag. Instead of being saved, we'll call to the Object's handler, which will handle
|
correspond to its tag, or the creation of a tag. Instead of being saved, we'll call to the
|
||||||
the creation, change, or deletion of a tag for us, as well as updating the handler's cache so that all
|
Object's handler, which will handle the creation, change, or deletion of a tag for us, as well
|
||||||
changes are instantly updated in-game.
|
as updating the handler's cache so that all changes are instantly updated in-game.
|
||||||
"""
|
"""
|
||||||
tag_key = forms.CharField(label='Tag Name')
|
tag_key = forms.CharField(label='Tag Name',
|
||||||
tag_category = forms.CharField(label="Category", required=False)
|
required=True,
|
||||||
tag_type = forms.CharField(label="Type", required=False)
|
help_text="This is the main key identifier")
|
||||||
tag_data = forms.CharField(label="Data", required=False)
|
tag_category = forms.CharField(label="Category",
|
||||||
|
help_text="Used for grouping tags. Unset (default) gives a category of None",
|
||||||
|
required=False)
|
||||||
|
tag_type = forms.CharField(label="Type",
|
||||||
|
help_text="Internal use. Either unset, \"alias\" or \"permission\"",
|
||||||
|
required=False)
|
||||||
|
tag_data = forms.CharField(label="Data",
|
||||||
|
help_text="Usually unused. Intended for eventual info about the tag itself",
|
||||||
|
required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = ("tag_key", "tag_category", "tag_data", "tag_type")
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -121,8 +132,8 @@ class TagInline(admin.TabularInline):
|
||||||
form = TagForm
|
form = TagForm
|
||||||
formset = TagFormSet
|
formset = TagFormSet
|
||||||
related_field = None # Must be 'objectdb', 'accountdb', 'msg', etc. Set when subclassing
|
related_field = None # Must be 'objectdb', 'accountdb', 'msg', etc. Set when subclassing
|
||||||
raw_id_fields = ('tag',)
|
# raw_id_fields = ('tag',)
|
||||||
readonly_fields = ('tag',)
|
# readonly_fields = ('tag',)
|
||||||
extra = 0
|
extra = 0
|
||||||
|
|
||||||
def get_formset(self, request, obj=None, **kwargs):
|
def get_formset(self, request, obj=None, **kwargs):
|
||||||
|
|
@ -150,13 +161,26 @@ class AttributeForm(forms.ModelForm):
|
||||||
changes are instantly updated in-game.
|
changes are instantly updated in-game.
|
||||||
"""
|
"""
|
||||||
attr_key = forms.CharField(label='Attribute Name', required=False, initial="Enter Attribute Name Here")
|
attr_key = forms.CharField(label='Attribute Name', required=False, initial="Enter Attribute Name Here")
|
||||||
attr_category = forms.CharField(label="Category", help_text="type of attribute, for sorting", required=False)
|
attr_category = forms.CharField(label="Category",
|
||||||
|
help_text="type of attribute, for sorting",
|
||||||
|
required=False,
|
||||||
|
max_length=4)
|
||||||
attr_value = PickledFormField(label="Value", help_text="Value to pickle/save", required=False)
|
attr_value = PickledFormField(label="Value", help_text="Value to pickle/save", required=False)
|
||||||
attr_type = forms.CharField(label="Type", help_text="nick for nickname, else leave blank", required=False)
|
attr_type = forms.CharField(label="Type",
|
||||||
|
help_text="Internal use. Either unset (normal Attribute) or \"nick\"",
|
||||||
|
required=False,
|
||||||
|
max_length=4)
|
||||||
attr_strvalue = forms.CharField(label="String Value",
|
attr_strvalue = forms.CharField(label="String Value",
|
||||||
help_text="Only enter this if value is blank and you want to save as a string",
|
help_text="Only set when using the Attribute as a string-only store",
|
||||||
required=False)
|
required=False,
|
||||||
attr_lockstring = forms.CharField(label="Locks", required=False, widget=forms.Textarea)
|
widget=forms.Textarea(attrs={"rows": 1, "cols": 6}))
|
||||||
|
attr_lockstring = forms.CharField(label="Locks",
|
||||||
|
required=False,
|
||||||
|
help_text="Lock string on the form locktype:lockdef;lockfunc:lockdef;...",
|
||||||
|
widget=forms.Textarea(attrs={"rows": 1, "cols": 8}))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = ("attr_key", "attr_value", "attr_category", "attr_strvalue", "attr_lockstring", "attr_type")
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -164,6 +188,7 @@ class AttributeForm(forms.ModelForm):
|
||||||
to have based on the Attribute. attr_key, attr_category, attr_value, attr_strvalue, attr_type,
|
to have based on the Attribute. attr_key, attr_category, attr_value, attr_strvalue, attr_type,
|
||||||
and attr_lockstring all refer to the corresponding Attribute fields. The initial data of the form fields will
|
and attr_lockstring all refer to the corresponding Attribute fields. The initial data of the form fields will
|
||||||
similarly be populated.
|
similarly be populated.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
super(AttributeForm, self).__init__(*args, **kwargs)
|
super(AttributeForm, self).__init__(*args, **kwargs)
|
||||||
attr_key = None
|
attr_key = None
|
||||||
|
|
@ -261,8 +286,8 @@ class AttributeInline(admin.TabularInline):
|
||||||
form = AttributeForm
|
form = AttributeForm
|
||||||
formset = AttributeFormSet
|
formset = AttributeFormSet
|
||||||
related_field = None # Must be 'objectdb', 'accountdb', 'msg', etc. Set when subclassing
|
related_field = None # Must be 'objectdb', 'accountdb', 'msg', etc. Set when subclassing
|
||||||
raw_id_fields = ('attribute',)
|
# raw_id_fields = ('attribute',)
|
||||||
readonly_fields = ('attribute',)
|
# readonly_fields = ('attribute',)
|
||||||
extra = 0
|
extra = 0
|
||||||
|
|
||||||
def get_formset(self, request, obj=None, **kwargs):
|
def get_formset(self, request, obj=None, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,13 @@ class PickledWidget(Textarea):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
final_attrs = self.build_attrs(attrs, name=name)
|
# fix since the signature of build_attrs changed in Django 1.11
|
||||||
|
if attrs is not None:
|
||||||
|
attrs["name"] = name
|
||||||
|
else:
|
||||||
|
attrs = {"name": name}
|
||||||
|
|
||||||
|
final_attrs = self.build_attrs(attrs)
|
||||||
return format_html('<textarea{0}>\r\n{1}</textarea>',
|
return format_html('<textarea{0}>\r\n{1}</textarea>',
|
||||||
flatatt(final_attrs),
|
flatatt(final_attrs),
|
||||||
value)
|
value)
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,12 @@ old and does not support websockets, it will instead fall back to a
|
||||||
long-polling (AJAX/COMET) type of connection (using
|
long-polling (AJAX/COMET) type of connection (using
|
||||||
evennia/server/portal/webclient_ajax.py)
|
evennia/server/portal/webclient_ajax.py)
|
||||||
|
|
||||||
All messages is a valid JSON array on single form:
|
All messages are valid JSON arrays on this single form:
|
||||||
|
|
||||||
["cmdname", args, kwargs],
|
["cmdname", args, kwargs],
|
||||||
|
|
||||||
where args is an JSON array and kwargs is a JSON object that will be
|
where args is an JSON array and kwargs is a JSON object. These will be both
|
||||||
used as argument to call the cmdname function.
|
used as arguments emitted to a callback named "cmdname" as cmdname(args, kwargs).
|
||||||
|
|
||||||
This library makes the "Evennia" object available. It has the
|
This library makes the "Evennia" object available. It has the
|
||||||
following official functions:
|
following official functions:
|
||||||
|
|
@ -45,7 +45,7 @@ An "emitter" object must have a function
|
||||||
relay the data to its correct gui element.
|
relay the data to its correct gui element.
|
||||||
- The default emitter also has the following methods:
|
- The default emitter also has the following methods:
|
||||||
- on(cmdname, listener) - this ties a listener to the backend. This function
|
- on(cmdname, listener) - this ties a listener to the backend. This function
|
||||||
should be called as listener(kwargs) when the backend calls emit.
|
should be called as listener(args, kwargs) when the backend calls emit.
|
||||||
- off(cmdname) - remove the listener for this cmdname.
|
- off(cmdname) - remove the listener for this cmdname.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -369,28 +369,29 @@ function onNewLine(text, originator) {
|
||||||
unread++;
|
unread++;
|
||||||
favico.badge(unread);
|
favico.badge(unread);
|
||||||
document.title = "(" + unread + ") " + originalTitle;
|
document.title = "(" + unread + ") " + originalTitle;
|
||||||
|
if ("Notification" in window){
|
||||||
|
if (("notification_popup" in options) && (options["notification_popup"])) {
|
||||||
|
Notification.requestPermission().then(function(result) {
|
||||||
|
if(result === "granted") {
|
||||||
|
var title = originalTitle === "" ? "Evennia" : originalTitle;
|
||||||
|
var options = {
|
||||||
|
body: text.replace(/(<([^>]+)>)/ig,""),
|
||||||
|
icon: "/static/website/images/evennia_logo.png"
|
||||||
|
}
|
||||||
|
|
||||||
if (("notification_popup" in options) && (options["notification_popup"])) {
|
var n = new Notification(title, options);
|
||||||
Notification.requestPermission().then(function(result) {
|
n.onclick = function(e) {
|
||||||
if(result === "granted") {
|
e.preventDefault();
|
||||||
var title = originalTitle === "" ? "Evennia" : originalTitle;
|
window.focus();
|
||||||
var options = {
|
this.close();
|
||||||
body: text.replace(/(<([^>]+)>)/ig,""),
|
}
|
||||||
icon: "/static/website/images/evennia_logo.png"
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
var n = new Notification(title, options);
|
}
|
||||||
n.onclick = function(e) {
|
if (("notification_sound" in options) && (options["notification_sound"])) {
|
||||||
e.preventDefault();
|
var audio = new Audio("/static/webclient/media/notification.wav");
|
||||||
window.focus();
|
audio.play();
|
||||||
this.close();
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (("notification_sound" in options) && (options["notification_sound"])) {
|
|
||||||
var audio = new Audio("/static/webclient/media/notification.wav");
|
|
||||||
audio.play();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -427,7 +428,9 @@ function doStartDragDialog(event) {
|
||||||
// Event when client finishes loading
|
// Event when client finishes loading
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
||||||
Notification.requestPermission();
|
if ("Notification" in window) {
|
||||||
|
Notification.requestPermission();
|
||||||
|
}
|
||||||
|
|
||||||
favico = new Favico({
|
favico = new Favico({
|
||||||
animation: 'none'
|
animation: 'none'
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue