mirror of
https://github.com/evennia/evennia.git
synced 2026-03-29 03:57:17 +02:00
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
|
||||
an object from a None location is by direct #dbref
|
||||
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
|
||||
is teleported to the target location. """
|
||||
|
|
@ -2343,6 +2344,7 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
|||
# setting switches
|
||||
tel_quietly = "quiet" in switches
|
||||
to_none = "tonone" in switches
|
||||
to_loc = "loc" in switches
|
||||
|
||||
if to_none:
|
||||
# teleporting to None
|
||||
|
|
@ -2368,7 +2370,7 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
# not teleporting to None location
|
||||
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
|
||||
|
||||
if rhs:
|
||||
|
|
@ -2384,6 +2386,11 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
|||
if not destination:
|
||||
caller.msg("Destination not found.")
|
||||
return
|
||||
if to_loc:
|
||||
destination = destination.location
|
||||
if not destination:
|
||||
caller.msg("Destination has no location.")
|
||||
return
|
||||
if obj_to_teleport == destination:
|
||||
caller.msg("You can't teleport an object inside of itself!")
|
||||
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)
|
||||
HELP_MORE = settings.HELP_MORE
|
||||
CMD_IGNORE_PREFIXES = settings.CMD_IGNORE_PREFIXES
|
||||
|
||||
# limit symbol import for API
|
||||
__all__ = ("CmdHelp", "CmdSetHelp")
|
||||
|
|
@ -231,6 +232,15 @@ class CmdHelp(Command):
|
|||
|
||||
# try an exact command auto-help match
|
||||
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:
|
||||
formatted = self.format_help_entry(match[0].key,
|
||||
match[0].get_help(caller, cmdset),
|
||||
|
|
|
|||
|
|
@ -293,9 +293,9 @@ if WEBSERVER_ENABLED:
|
|||
# create ajax client processes at /webclientdata
|
||||
from evennia.server.portal import webclient_ajax
|
||||
|
||||
webclient = webclient_ajax.WebClient()
|
||||
webclient.sessionhandler = PORTAL_SESSIONS
|
||||
web_root.putChild("webclientdata", webclient)
|
||||
ajax_webclient = webclient_ajax.AjaxWebClient()
|
||||
ajax_webclient.sessionhandler = PORTAL_SESSIONS
|
||||
web_root.putChild("webclientdata", ajax_webclient)
|
||||
webclientstr = "\n + webclient (ajax only)"
|
||||
|
||||
if WEBSOCKET_CLIENT_ENABLED and not websocket_started:
|
||||
|
|
|
|||
|
|
@ -93,7 +93,6 @@ class WebSocketClient(Protocol, Session):
|
|||
csession = self.get_client_session()
|
||||
|
||||
if csession:
|
||||
print("In disconnect: csession uid=%s" % csession.get("webclient_authenticated_uid", None))
|
||||
csession["webclient_authenticated_uid"] = None
|
||||
csession.save()
|
||||
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.
|
||||
#
|
||||
|
||||
class WebClient(resource.Resource):
|
||||
class AjaxWebClient(resource.Resource):
|
||||
"""
|
||||
An ajax/comet long-polling transport
|
||||
|
||||
|
|
@ -163,13 +163,13 @@ class WebClient(resource.Resource):
|
|||
remote_addr = request.getClientIP()
|
||||
host_string = "%s (%s:%s)" % (_SERVERNAME, request.getRequestHostname(), request.getHost().port)
|
||||
|
||||
sess = WebClientSession()
|
||||
sess = AjaxWebClientSession()
|
||||
sess.client = self
|
||||
sess.init_session("ajax/comet", remote_addr, self.sessionhandler)
|
||||
|
||||
sess.csessid = 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:
|
||||
# the client session is already logged in
|
||||
sess.uid = uid
|
||||
|
|
@ -292,14 +292,26 @@ class WebClient(resource.Resource):
|
|||
# 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):
|
||||
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."):
|
||||
"""
|
||||
|
|
@ -308,10 +320,22 @@ class WebClientSession(session.Session):
|
|||
Args:
|
||||
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.client_disconnect(self.csessid)
|
||||
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):
|
||||
"""
|
||||
Data Evennia -> User
|
||||
|
|
|
|||
|
|
@ -18,17 +18,28 @@ class TagAdmin(admin.ModelAdmin):
|
|||
|
||||
class TagForm(forms.ModelForm):
|
||||
"""
|
||||
This form overrides the base behavior of the ModelForm that would be used for a Tag-through-model.
|
||||
Since the through-models only have access to the foreignkeys of the Tag and the Object that they're
|
||||
attached to, we need to spoof the behavior of it being a form that would correspond to its tag,
|
||||
or the creation of a tag. Instead of being saved, we'll call to the Object's handler, which will handle
|
||||
the creation, change, or deletion of a tag for us, as well as updating the handler's cache so that all
|
||||
changes are instantly updated in-game.
|
||||
This form overrides the base behavior of the ModelForm that would be used for a
|
||||
Tag-through-model. Since the through-models only have access to the foreignkeys of the Tag and
|
||||
the Object that they're attached to, we need to spoof the behavior of it being a form that would
|
||||
correspond to its tag, or the creation of a tag. Instead of being saved, we'll call to the
|
||||
Object's handler, which will handle the creation, change, or deletion of a tag for us, as well
|
||||
as updating the handler's cache so that all changes are instantly updated in-game.
|
||||
"""
|
||||
tag_key = forms.CharField(label='Tag Name')
|
||||
tag_category = forms.CharField(label="Category", required=False)
|
||||
tag_type = forms.CharField(label="Type", required=False)
|
||||
tag_data = forms.CharField(label="Data", required=False)
|
||||
tag_key = forms.CharField(label='Tag Name',
|
||||
required=True,
|
||||
help_text="This is the main key identifier")
|
||||
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):
|
||||
"""
|
||||
|
|
@ -121,8 +132,8 @@ class TagInline(admin.TabularInline):
|
|||
form = TagForm
|
||||
formset = TagFormSet
|
||||
related_field = None # Must be 'objectdb', 'accountdb', 'msg', etc. Set when subclassing
|
||||
raw_id_fields = ('tag',)
|
||||
readonly_fields = ('tag',)
|
||||
# raw_id_fields = ('tag',)
|
||||
# readonly_fields = ('tag',)
|
||||
extra = 0
|
||||
|
||||
def get_formset(self, request, obj=None, **kwargs):
|
||||
|
|
@ -150,13 +161,26 @@ class AttributeForm(forms.ModelForm):
|
|||
changes are instantly updated in-game.
|
||||
"""
|
||||
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_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",
|
||||
help_text="Only enter this if value is blank and you want to save as a string",
|
||||
required=False)
|
||||
attr_lockstring = forms.CharField(label="Locks", required=False, widget=forms.Textarea)
|
||||
help_text="Only set when using the Attribute as a string-only store",
|
||||
required=False,
|
||||
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):
|
||||
"""
|
||||
|
|
@ -164,6 +188,7 @@ class AttributeForm(forms.ModelForm):
|
|||
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
|
||||
similarly be populated.
|
||||
|
||||
"""
|
||||
super(AttributeForm, self).__init__(*args, **kwargs)
|
||||
attr_key = None
|
||||
|
|
@ -261,8 +286,8 @@ class AttributeInline(admin.TabularInline):
|
|||
form = AttributeForm
|
||||
formset = AttributeFormSet
|
||||
related_field = None # Must be 'objectdb', 'accountdb', 'msg', etc. Set when subclassing
|
||||
raw_id_fields = ('attribute',)
|
||||
readonly_fields = ('attribute',)
|
||||
# raw_id_fields = ('attribute',)
|
||||
# readonly_fields = ('attribute',)
|
||||
extra = 0
|
||||
|
||||
def get_formset(self, request, obj=None, **kwargs):
|
||||
|
|
|
|||
|
|
@ -126,7 +126,13 @@ class PickledWidget(Textarea):
|
|||
except ValueError:
|
||||
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>',
|
||||
flatatt(final_attrs),
|
||||
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
|
||||
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],
|
||||
|
||||
where args is an JSON array and kwargs is a JSON object that will be
|
||||
used as argument to call the cmdname function.
|
||||
where args is an JSON array and kwargs is a JSON object. These will be both
|
||||
used as arguments emitted to a callback named "cmdname" as cmdname(args, kwargs).
|
||||
|
||||
This library makes the "Evennia" object available. It has the
|
||||
following official functions:
|
||||
|
|
@ -45,7 +45,7 @@ An "emitter" object must have a function
|
|||
relay the data to its correct gui element.
|
||||
- The default emitter also has the following methods:
|
||||
- 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.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -369,28 +369,29 @@ function onNewLine(text, originator) {
|
|||
unread++;
|
||||
favico.badge(unread);
|
||||
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"])) {
|
||||
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"
|
||||
var n = new Notification(title, options);
|
||||
n.onclick = function(e) {
|
||||
e.preventDefault();
|
||||
window.focus();
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
var n = new Notification(title, options);
|
||||
n.onclick = function(e) {
|
||||
e.preventDefault();
|
||||
window.focus();
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (("notification_sound" in options) && (options["notification_sound"])) {
|
||||
var audio = new Audio("/static/webclient/media/notification.wav");
|
||||
audio.play();
|
||||
});
|
||||
}
|
||||
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
|
||||
$(document).ready(function() {
|
||||
|
||||
Notification.requestPermission();
|
||||
if ("Notification" in window) {
|
||||
Notification.requestPermission();
|
||||
}
|
||||
|
||||
favico = new Favico({
|
||||
animation: 'none'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue