diff --git a/build.py b/build.py
index a7ace7e2..55ac2f0a 100755
--- a/build.py
+++ b/build.py
@@ -202,8 +202,8 @@ def copytree(src, dst, symlinks=False, ignore=None):
# if we're building mobile too
if buildMobile:
- if buildMobile not in ['debug','release']:
- raise Exception("Error: buildMobile must be 'debug' or 'release'")
+ if buildMobile not in ['debug','release','copyonly']:
+ raise Exception("Error: buildMobile must be 'debug' or 'release' or 'copyonly'")
# compile the user location script
fn = "user-location.user.js"
@@ -232,13 +232,14 @@ if buildMobile:
copytree(os.path.join(outDir,"plugins"), "mobile/assets/plugins")
- # now launch 'ant' to build the mobile project
- retcode = os.system("ant -buildfile mobile/build.xml %s" % buildMobile)
+ if buildMobile != 'copyonly':
+ # now launch 'ant' to build the mobile project
+ retcode = os.system("ant -buildfile mobile/build.xml %s" % buildMobile)
- if retcode != 0:
- print ("Error: mobile app failed to build. ant returned %d" % retcode)
- else:
- shutil.copy("mobile/bin/IITC_Mobile-%s.apk" % buildMobile, os.path.join(outDir,"IITC_Mobile-%s.apk" % buildMobile) )
+ if retcode != 0:
+ print ("Error: mobile app failed to build. ant returned %d" % retcode)
+ else:
+ shutil.copy("mobile/bin/IITC_Mobile-%s.apk" % buildMobile, os.path.join(outDir,"IITC_Mobile-%s.apk" % buildMobile) )
# vim: ai si ts=4 sw=4 sts=4 et
diff --git a/code/boot.js b/code/boot.js
index 29b052cc..c08acf8b 100644
--- a/code/boot.js
+++ b/code/boot.js
@@ -160,6 +160,7 @@ window.setupMap = function() {
));
var addLayers = {};
+ var hiddenLayer = [];
portalsLayers = [];
for(var i = 0; i <= 8; i++) {
@@ -167,15 +168,21 @@ window.setupMap = function() {
map.addLayer(portalsLayers[i]);
var t = (i === 0 ? 'Unclaimed' : 'Level ' + i) + ' Portals';
addLayers[t] = portalsLayers[i];
+ // Store it in hiddenLayer to remove later
+ if(!isLayerGroupDisplayed(t)) hiddenLayer.push(portalsLayers[i]);
}
fieldsLayer = L.layerGroup([]);
map.addLayer(fieldsLayer, true);
addLayers['Fields'] = fieldsLayer;
+ // Store it in hiddenLayer to remove later
+ if(!isLayerGroupDisplayed('Fields')) hiddenLayer.push(fieldsLayer);
linksLayer = L.layerGroup([]);
map.addLayer(linksLayer, true);
addLayers['Links'] = linksLayer;
+ // Store it in hiddenLayer to remove later
+ if(!isLayerGroupDisplayed('Links')) hiddenLayer.push(linksLayer);
window.layerChooser = new L.Control.Layers({
'MapQuest OSM': views[0],
@@ -185,6 +192,10 @@ window.setupMap = function() {
'Google Hybrid': views[4],
'Google Terrain': views[5]
}, addLayers);
+ // Remove the hidden layer after layerChooser built, to avoid messing up ordering of layers
+ $.each(hiddenLayer, function(ind, layer){
+ map.removeLayer(layer);
+ });
map.addControl(window.layerChooser);
@@ -396,6 +407,11 @@ function boot() {
window.setupBackButton();
// read here ONCE, so the URL is only evaluated one time after the
// necessary data has been loaded.
+ urlPortalLL = getURLParam('pll');
+ if(urlPortalLL) {
+ urlPortalLL = urlPortalLL.split(",");
+ urlPortalLL = [parseFloat(urlPortalLL[0]) || 0.0, parseFloat(urlPortalLL[1]) || 0.0];
+ }
urlPortal = getURLParam('pguid');
// load only once
diff --git a/code/chat.js b/code/chat.js
index df42dd2a..69b36691 100644
--- a/code/chat.js
+++ b/code/chat.js
@@ -1,678 +1,678 @@
-window.chat = function() {};
-
-window.chat.handleTabCompletion = function() {
- var el = $('#chatinput input');
- var curPos = el.get(0).selectionStart;
- var text = el.val();
- var word = text.slice(0, curPos).replace(/.*\b([a-z0-9-_])/, '$1').toLowerCase();
-
- var list = $('#chat > div:visible mark');
- list = list.map(function(ind, mark) { return $(mark).text(); } );
- list = uniqueArray(list);
-
- var nick = null;
- for(var i = 0; i < list.length; i++) {
- if(!list[i].toLowerCase().startsWith(word)) continue;
- if(nick && nick !== list[i]) {
- console.log('More than one nick matches, aborting. ('+list[i]+' vs '+nick+')');
- return;
- }
- nick = list[i];
- }
- if(!nick) {
- console.log('No matches for ' + word);
- return;
- }
-
- var posStart = curPos - word.length;
- var newText = text.substring(0, posStart);
- var atPresent = text.substring(posStart-1, posStart) === '@';
- newText += (atPresent ? '' : '@') + nick + ' ';
- newText += text.substring(curPos);
- el.val(newText);
-}
-
-//
-// clear management
-//
-
-
-window.chat._oldBBox = null;
-window.chat.genPostData = function(isFaction, storageHash, getOlderMsgs) {
- if(typeof isFaction !== 'boolean') throw('Need to know if public or faction chat.');
-
- chat._localRangeCircle.setLatLng(map.getCenter());
- var b = map.getBounds().extend(chat._localRangeCircle.getBounds());
- var ne = b.getNorthEast();
- var sw = b.getSouthWest();
-
- // round bounds in order to ignore rounding errors
- var bbs = $.map([ne.lat, ne.lng, sw.lat, sw.lng], function(x) { return Math.round(x*1E4) }).join();
- if(chat._oldBBox && chat._oldBBox !== bbs) {
- $('#chat > div').data('needsClearing', true);
- console.log('Bounding Box changed, chat will be cleared (old: '+chat._oldBBox+' ; new: '+bbs+' )');
- // need to reset these flags now because clearing will only occur
- // after the request is finished – i.e. there would be one almost
- // useless request.
- chat._faction.data = {};
- chat._faction.oldestTimestamp = -1;
- chat._faction.newestTimestamp = -1;
-
- chat._public.data = {};
- chat._public.oldestTimestamp = -1;
- chat._public.newestTimestamp = -1;
- }
- chat._oldBBox = bbs;
-
- var ne = b.getNorthEast();
- var sw = b.getSouthWest();
- var data = {
- desiredNumItems: isFaction ? CHAT_FACTION_ITEMS : CHAT_PUBLIC_ITEMS ,
- minLatE6: Math.round(sw.lat*1E6),
- minLngE6: Math.round(sw.lng*1E6),
- maxLatE6: Math.round(ne.lat*1E6),
- maxLngE6: Math.round(ne.lng*1E6),
- minTimestampMs: -1,
- maxTimestampMs: -1,
- factionOnly: isFaction
- }
-
- if(getOlderMsgs) {
- // ask for older chat when scrolling up
- data = $.extend(data, {maxTimestampMs: storageHash.oldestTimestamp});
- } else {
- // ask for newer chat
- var min = storageHash.newestTimestamp;
- // the inital request will have both timestamp values set to -1,
- // thus we receive the newest desiredNumItems. After that, we will
- // only receive messages with a timestamp greater or equal to min
- // above.
- // After resuming from idle, there might be more new messages than
- // desiredNumItems. So on the first request, we are not really up to
- // date. We will eventually catch up, as long as there are less new
- // messages than desiredNumItems per each refresh cycle.
- // A proper solution would be to query until no more new results are
- // returned. Another way would be to set desiredNumItems to a very
- // large number so we really get all new messages since the last
- // request. Setting desiredNumItems to -1 does unfortunately not
- // work.
- // Currently this edge case is not handled. Let’s see if this is a
- // problem in crowded areas.
- $.extend(data, {minTimestampMs: min});
- }
- return data;
-}
-
-
-
-//
-// faction
-//
-
-window.chat._requestFactionRunning = false;
-window.chat.requestFaction = function(getOlderMsgs, isRetry) {
- if(chat._requestFactionRunning && !isRetry) return;
- if(isIdle()) return renderUpdateStatus();
- chat._requestFactionRunning = true;
-
- var d = chat.genPostData(true, chat._faction, getOlderMsgs);
- var r = window.postAjax(
- 'getPaginatedPlextsV2',
- d,
- chat.handleFaction,
- isRetry
- ? function() { window.chat._requestFactionRunning = false; }
- : function() { window.chat.requestFaction(getOlderMsgs, true) }
- );
-
- requests.add(r);
-}
-
-
-window.chat._faction = {data:{}, oldestTimestamp:-1, newestTimestamp:-1};
-window.chat.handleFaction = function(data, textStatus, jqXHR) {
- chat._requestFactionRunning = false;
-
- if(!data || !data.result) {
- window.failedRequestCount++;
- return console.warn('faction chat error. Waiting for next auto-refresh.');
- }
-
- if(data.result.length === 0) return;
-
- var old = chat._faction.oldestTimestamp;
- chat.writeDataToHash(data, chat._faction, false);
- var oldMsgsWereAdded = old !== chat._faction.oldestTimestamp;
-
- runHooks('factionChatDataAvailable', {raw: data, processed: chat._faction.data});
-
- window.chat.renderFaction(oldMsgsWereAdded);
-
- if(data.result.length >= CHAT_FACTION_ITEMS) chat.needMoreMessages();
-}
-
-window.chat.renderFaction = function(oldMsgsWereAdded) {
- chat.renderData(chat._faction.data, 'chatfaction', oldMsgsWereAdded);
-}
-
-
-//
-// public
-//
-
-window.chat._requestPublicRunning = false;
-window.chat.requestPublic = function(getOlderMsgs, isRetry) {
- if(chat._requestPublicRunning && !isRetry) return;
- if(isIdle()) return renderUpdateStatus();
- chat._requestPublicRunning = true;
-
- var d = chat.genPostData(false, chat._public, getOlderMsgs);
- var r = window.postAjax(
- 'getPaginatedPlextsV2',
- d,
- chat.handlePublic,
- isRetry
- ? function() { window.chat._requestPublicRunning = false; }
- : function() { window.chat.requestPublic(getOlderMsgs, true) }
- );
-
- requests.add(r);
-}
-
-window.chat._public = {data:{}, oldestTimestamp:-1, newestTimestamp:-1};
-window.chat.handlePublic = function(data, textStatus, jqXHR) {
- chat._requestPublicRunning = false;
-
- if(!data || !data.result) {
- window.failedRequestCount++;
- return console.warn('public chat error. Waiting for next auto-refresh.');
- }
-
- if(data.result.length === 0) return;
-
- var old = chat._public.oldestTimestamp;
- chat.writeDataToHash(data, chat._public, true);
- var oldMsgsWereAdded = old !== chat._public.oldestTimestamp;
-
- runHooks('publicChatDataAvailable', {raw: data, processed: chat._public.data});
-
- switch(chat.getActive()) {
- case 'public': window.chat.renderPublic(oldMsgsWereAdded); break;
- case 'compact': window.chat.renderCompact(oldMsgsWereAdded); break;
- case 'full': window.chat.renderFull(oldMsgsWereAdded); break;
- }
-
- if(data.result.length >= CHAT_PUBLIC_ITEMS) chat.needMoreMessages();
-}
-
-window.chat.renderPublic = function(oldMsgsWereAdded) {
- // only keep player data
- var data = $.map(chat._public.data, function(entry) {
- if(!entry[1]) return [entry];
- });
- chat.renderData(data, 'chatpublic', oldMsgsWereAdded);
-}
-
-window.chat.renderCompact = function(oldMsgsWereAdded) {
- var data = {};
- $.each(chat._public.data, function(guid, entry) {
- // skip player msgs
- if(!entry[1]) return true;
- var pguid = entry[3];
- // ignore if player has newer data
- if(data[pguid] && data[pguid][0] > entry[0]) return true;
- data[pguid] = entry;
- });
- // data keys are now player guids instead of message guids. However,
- // it is all the same to renderData.
- chat.renderData(data, 'chatcompact', oldMsgsWereAdded);
-}
-
-window.chat.renderFull = function(oldMsgsWereAdded) {
- // only keep automatically generated data
- var data = $.map(chat._public.data, function(entry) {
- if(entry[1]) return [entry];
- });
- chat.renderData(data, 'chatfull', oldMsgsWereAdded);
-}
-
-
-//
-// common
-//
-
-window.chat.nicknameClicked = function(event, nickname) {
- var hookData = { event: event, nickname: nickname };
-
- if (window.runHooks('nicknameClicked', hookData)) {
- window.chat.addNickname('@' + nickname);
- }
-}
-
-window.chat.writeDataToHash = function(newData, storageHash, isPublicChannel) {
- $.each(newData.result, function(ind, json) {
- // avoid duplicates
- if(json[0] in storageHash.data) return true;
-
- var isSecureMessage = false;
- var msgToPlayer = false;
-
- var time = json[1];
- var team = json[2].plext.team === 'ALIENS' ? TEAM_ENL : TEAM_RES;
- var auto = json[2].plext.plextType !== 'PLAYER_GENERATED';
- var systemNarrowcast = json[2].plext.plextType === 'SYSTEM_NARROWCAST';
-
- //track oldest + newest timestamps
- if (storageHash.oldestTimestamp === -1 || storageHash.oldestTimestamp > time) storageHash.oldestTimestamp = time;
- if (storageHash.newestTimestamp === -1 || storageHash.newestTimestamp < time) storageHash.newestTimestamp = time;
-
- //remove "Your X on Y was destroyed by Z" from the faction channel
- if (systemNarrowcast && !isPublicChannel) return true;
-
- var msg = '', nick = '', pguid;
- $.each(json[2].plext.markup, function(ind, markup) {
- switch(markup[0]) {
- case 'SENDER': // user generated messages
- nick = markup[1].plain.slice(0, -2); // cut “: ” at end
- pguid = markup[1].guid;
- break;
-
- case 'PLAYER': // automatically generated messages
- pguid = markup[1].guid;
- nick = markup[1].plain;
- team = markup[1].team === 'ALIENS' ? TEAM_ENL : TEAM_RES;
- if(ind > 0) msg += nick; // don’t repeat nick directly
- break;
-
- case 'TEXT':
- msg += $('
').text(markup[1].plain).html().autoLink();
- break;
-
- case 'AT_PLAYER':
- var thisToPlayer = (markup[1].plain == ('@'+window.PLAYER.nickname));
- var spanClass = thisToPlayer ? "pl_nudge_me" : (markup[1].team + " pl_nudge_player");
- var atPlayerName = markup[1].plain.replace(/^@/, "");
- msg += $('').html($('')
- .attr('class', spanClass)
- .attr('onclick',"window.chat.nicknameClicked(event, '"+atPlayerName+"')")
- .text(markup[1].plain)).html();
- msgToPlayer = msgToPlayer || thisToPlayer;
- break;
-
- case 'PORTAL':
- var latlng = [markup[1].latE6/1E6, markup[1].lngE6/1E6];
- var perma = '/intel?latE6='+markup[1].latE6+'&lngE6='+markup[1].lngE6+'&z=17&pguid='+markup[1].guid;
- var js = 'window.zoomToAndShowPortal(\''+markup[1].guid+'\', ['+latlng[0]+', '+latlng[1]+']);return false';
-
- msg += ''
- + window.chat.getChatPortalName(markup[1])
- + '';
- break;
-
- case 'SECURE':
- //NOTE: we won't add the '[secure]' string here - it'll be handled below instead
- isSecureMessage = true;
- break;
-
- default:
- //handle unknown types by outputting the plain text version, marked with it's type
- msg += $('').text(markup[0]+':<'+markup[1].plain+'>').html();
- break;
- }
- });
-
-
- //skip secure messages on the public channel
- if (isPublicChannel && isSecureMessage) return true;
-
- //skip public messages (e.g. @player mentions) on the secure channel
- if ((!isPublicChannel) && (!isSecureMessage)) return true;
-
-
- //NOTE: these two are currently redundant with the above two tests - but code can change...
- //from the server, private channel messages are flagged with a SECURE string '[secure] ', and appear in
- //both the public and private channels
- //we don't include this '[secure]' text above, as it's redundant in the faction-only channel
- //let's add it here though if we have a secure message in the public channel, or the reverse if a non-secure in the faction one
- if (isPublicChannel && isSecureMessage) msg = '[secure] ' + msg;
- //and, add the reverse - a 'public' marker to messages in the private channel
- if ((!isPublicChannel) && (!isSecureMessage)) msg = '[public] ' + msg;
-
-
- // format: timestamp, autogenerated, HTML message, player guid
- storageHash.data[json[0]] = [json[1], auto, chat.renderMsg(msg, nick, time, team, msgToPlayer, systemNarrowcast), pguid];
-
- window.setPlayerName(pguid, nick); // free nick name resolves
- });
-}
-
-// Override portal names that are used over and over, such as 'US Post Office'
-window.chat.getChatPortalName = function(markup) {
- var name = markup.name;
- if(name === 'US Post Office') {
- var address = markup.address.split(',');
- name = 'USPS: ' + address[0];
- }
- return name;
-}
-
-// renders data from the data-hash to the element defined by the given
-// ID. Set 3rd argument to true if it is likely that old data has been
-// added. Latter is only required for scrolling.
-window.chat.renderData = function(data, element, likelyWereOldMsgs) {
- var elm = $('#'+element);
- if(elm.is(':hidden')) return;
-
- // discard guids and sort old to new
- var vals = $.map(data, function(v, k) { return [v]; });
- vals = vals.sort(function(a, b) { return a[0]-b[0]; });
-
- // render to string with date separators inserted
- var msgs = '';
- var prevTime = null;
- $.each(vals, function(ind, msg) {
- var nextTime = new Date(msg[0]).toLocaleDateString();
- if(prevTime && prevTime !== nextTime)
- msgs += chat.renderDivider(nextTime);
- msgs += msg[2];
- prevTime = nextTime;
- });
-
- var scrollBefore = scrollBottom(elm);
- elm.html('
';
-}
-
-
-window.chat.renderMsg = function(msg, nick, time, team, msgToPlayer, systemNarrowcast) {
- var ta = unixTimeToHHmm(time);
- var tb = unixTimeToString(time, true);
- // help cursor via “#chat time”
- var t = '';
- if ( msgToPlayer )
- {
- t = '
' + t + '
';
- }
- if (systemNarrowcast)
- {
- msg = '
' + msg + '
';
- }
- var color = COLORS[team];
- if (nick === window.PLAYER.nickname) color = '#fd6'; //highlight things said/done by the player in a unique colour (similar to @player mentions from others in the chat text itself)
- var s = 'style="cursor:pointer; color:'+color+'"';
- var title = nick.length >= 8 ? 'title="'+nick+'" class="help"' : '';
- var i = ['<', '>'];
- return '
'+t+'
'+i[0]+''+ nick+''+i[1]+'
'+msg+'
';
-}
-
-window.chat.addNickname= function(nick){
- var c = document.getElementById("chattext");
- c.value = [c.value.trim(), nick].join(" ").trim() + " ";
- c.focus()
-}
-
-
-
-
-window.chat.getActive = function() {
- return $('#chatcontrols .active').text();
-}
-
-
-window.chat.toggle = function() {
- var c = $('#chat, #chatcontrols');
- if(c.hasClass('expand')) {
- $('#chatcontrols a:first').html('');
- c.removeClass('expand');
- var div = $('#chat > div:visible');
- div.data('ignoreNextScroll', true);
- div.scrollTop(99999999); // scroll to bottom
- $('.leaflet-control').css('margin-left', '13px');
- } else {
- $('#chatcontrols a:first').html('');
- c.addClass('expand');
- $('.leaflet-control').css('margin-left', '720px');
- chat.needMoreMessages();
- }
-}
-
-
-window.chat.request = function() {
- console.log('refreshing chat');
- chat.requestFaction(false);
- chat.requestPublic(false);
-}
-
-
-// checks if there are enough messages in the selected chat tab and
-// loads more if not.
-window.chat.needMoreMessages = function() {
- var activeTab = chat.getActive();
- if(activeTab === 'debug') return;
-
- var activeChat = $('#chat > :visible');
- if(activeChat.length === 0) return;
-
- var hasScrollbar = scrollBottom(activeChat) !== 0 || activeChat.scrollTop() !== 0;
- var nearTop = activeChat.scrollTop() <= CHAT_REQUEST_SCROLL_TOP;
- if(hasScrollbar && !nearTop) return;
-
- console.log('No scrollbar or near top in active chat. Requesting more data.');
-
- if(activeTab === 'faction')
- chat.requestFaction(true);
- else
- chat.requestPublic(true);
-}
-
-
-window.chat.chooser = function(event) {
- var t = $(event.target);
- var tt = t.text();
-
- var mark = $('#chatinput mark');
- var input = $('#chatinput input');
-
- $('#chatcontrols .active').removeClass('active');
- t.addClass('active');
-
- $('#chat > div').hide();
-
- var elm;
-
- switch(tt) {
- case 'faction':
- input.css('color', '');
- mark.css('color', '');
- mark.text('tell faction:');
- break;
-
- case 'public':
- input.css('cssText', 'color: #f66 !important');
- mark.css('cssText', 'color: #f66 !important');
- mark.text('broadcast:');
- break;
-
- case 'compact':
- case 'full':
- mark.css('cssText', 'color: #bbb !important');
- input.css('cssText', 'color: #bbb !important');
- mark.text('tell Jarvis:');
- break;
-
- default:
- throw('chat.chooser was asked to handle unknown button: ' + tt);
- }
-
- var elm = $('#chat' + tt);
- elm.show();
- eval('chat.render' + tt.capitalize() + '(false);');
- if(elm.data('needsScrollTop')) {
- elm.data('ignoreNextScroll', true);
- elm.scrollTop(elm.data('needsScrollTop'));
- elm.data('needsScrollTop', null);
- }
-
- chat.needMoreMessages();
-}
-
-
-// contains the logic to keep the correct scroll position.
-window.chat.keepScrollPosition = function(box, scrollBefore, isOldMsgs) {
- // If scrolled down completely, keep it that way so new messages can
- // be seen easily. If scrolled up, only need to fix scroll position
- // when old messages are added. New messages added at the bottom don’t
- // change the view and enabling this would make the chat scroll down
- // for every added message, even if the user wants to read old stuff.
-
- if(box.is(':hidden') && !isOldMsgs) {
- box.data('needsScrollTop', 99999999);
- return;
- }
-
- if(scrollBefore === 0 || isOldMsgs) {
- box.data('ignoreNextScroll', true);
- box.scrollTop(box.scrollTop() + (scrollBottom(box)-scrollBefore));
- }
-}
-
-
-
-
-//
-// setup
-//
-
-window.chat.setup = function() {
- window.chat._localRangeCircle = L.circle(map.getCenter(), CHAT_MIN_RANGE*1000);
-
- $('#chatcontrols, #chat, #chatinput').show();
-
- $('#chatcontrols a:first').click(window.chat.toggle);
- $('#chatcontrols a').each(function(ind, elm) {
- if($.inArray($(elm).text(), ['full', 'compact', 'public', 'faction']) !== -1)
- $(elm).click(window.chat.chooser);
- });
-
-
- $('#chatinput').click(function() {
- $('#chatinput input').focus();
- });
-
- window.chat.setupTime();
- window.chat.setupPosting();
-
- $('#chatfaction').scroll(function() {
- var t = $(this);
- if(t.data('ignoreNextScroll')) return t.data('ignoreNextScroll', false);
- if(t.scrollTop() < CHAT_REQUEST_SCROLL_TOP) chat.requestFaction(true);
- if(scrollBottom(t) === 0) chat.requestFaction(false);
- });
-
- $('#chatpublic, #chatfull, #chatcompact').scroll(function() {
- var t = $(this);
- if(t.data('ignoreNextScroll')) return t.data('ignoreNextScroll', false);
- if(t.scrollTop() < CHAT_REQUEST_SCROLL_TOP) chat.requestPublic(true);
- if(scrollBottom(t) === 0) chat.requestPublic(false);
- });
-
- chat.request();
- window.addResumeFunction(chat.request);
- window.requests.addRefreshFunction(chat.request);
-
- var cls = PLAYER.team === 'ALIENS' ? 'enl' : 'res';
- $('#chatinput mark').addClass(cls);
-
- $(window).on('click', '.nickname', function(event) {
- window.chat.nicknameClicked(event, $(this).text());
- });
-}
-
-
-window.chat.setupTime = function() {
- var inputTime = $('#chatinput time');
- var updateTime = function() {
- if(window.isIdle()) return;
- var d = new Date();
- var h = d.getHours() + ''; if(h.length === 1) h = '0' + h;
- var m = d.getMinutes() + ''; if(m.length === 1) m = '0' + m;
- inputTime.text(h+':'+m);
- // update ON the minute (1ms after)
- setTimeout(updateTime, (60 - d.getSeconds()) * 1000 + 1);
- };
- updateTime();
- window.addResumeFunction(updateTime);
-}
-
-
-//
-// posting
-//
-
-
-window.chat.setupPosting = function() {
- if (!isSmartphone()) {
- $('#chatinput input').keydown(function(event) {
- try {
- var kc = (event.keyCode ? event.keyCode : event.which);
- if(kc === 13) { // enter
- chat.postMsg();
- event.preventDefault();
- } else if (kc === 9) { // tab
- event.preventDefault();
- window.chat.handleTabCompletion();
- }
- } catch(error) {
- console.log(error);
- debug.printStackTrace();
- }
- });
- }
-
- $('#chatinput').submit(function(event) {
- event.preventDefault();
- chat.postMsg();
- });
-}
-
-
-window.chat.postMsg = function() {
- var c = chat.getActive();
- if(c === 'full' || c === 'compact')
- return alert('Jarvis: A strange game. The only winning move is not to play. How about a nice game of chess?');
-
- var msg = $.trim($('#chatinput input').val());
- if(!msg || msg === '') return;
-
- if(c === 'debug') return new Function (msg)();
-
- var publik = c === 'public';
- var latlng = map.getCenter();
-
- var data = {message: msg,
- latE6: Math.round(latlng.lat*1E6),
- lngE6: Math.round(latlng.lng*1E6),
- factionOnly: !publik};
-
- var errMsg = 'Your message could not be delivered. You can copy&' +
- 'paste it here and try again if you want:\n\n' + msg;
-
- window.postAjax('sendPlext', data,
- function(response) {
- if(response.error) alert(errMsg);
- if(publik) chat.requestPublic(false); else chat.requestFaction(false); },
- function() {
- alert(errMsg);
- }
- );
-
- $('#chatinput input').val('');
-}
+window.chat = function() {};
+
+window.chat.handleTabCompletion = function() {
+ var el = $('#chatinput input');
+ var curPos = el.get(0).selectionStart;
+ var text = el.val();
+ var word = text.slice(0, curPos).replace(/.*\b([a-z0-9-_])/, '$1').toLowerCase();
+
+ var list = $('#chat > div:visible mark');
+ list = list.map(function(ind, mark) { return $(mark).text(); } );
+ list = uniqueArray(list);
+
+ var nick = null;
+ for(var i = 0; i < list.length; i++) {
+ if(!list[i].toLowerCase().startsWith(word)) continue;
+ if(nick && nick !== list[i]) {
+ console.log('More than one nick matches, aborting. ('+list[i]+' vs '+nick+')');
+ return;
+ }
+ nick = list[i];
+ }
+ if(!nick) {
+ console.log('No matches for ' + word);
+ return;
+ }
+
+ var posStart = curPos - word.length;
+ var newText = text.substring(0, posStart);
+ var atPresent = text.substring(posStart-1, posStart) === '@';
+ newText += (atPresent ? '' : '@') + nick + ' ';
+ newText += text.substring(curPos);
+ el.val(newText);
+}
+
+//
+// clear management
+//
+
+
+window.chat._oldBBox = null;
+window.chat.genPostData = function(isFaction, storageHash, getOlderMsgs) {
+ if(typeof isFaction !== 'boolean') throw('Need to know if public or faction chat.');
+
+ chat._localRangeCircle.setLatLng(map.getCenter());
+ var b = map.getBounds().extend(chat._localRangeCircle.getBounds());
+ var ne = b.getNorthEast();
+ var sw = b.getSouthWest();
+
+ // round bounds in order to ignore rounding errors
+ var bbs = $.map([ne.lat, ne.lng, sw.lat, sw.lng], function(x) { return Math.round(x*1E4) }).join();
+ if(chat._oldBBox && chat._oldBBox !== bbs) {
+ $('#chat > div').data('needsClearing', true);
+ console.log('Bounding Box changed, chat will be cleared (old: '+chat._oldBBox+' ; new: '+bbs+' )');
+ // need to reset these flags now because clearing will only occur
+ // after the request is finished – i.e. there would be one almost
+ // useless request.
+ chat._faction.data = {};
+ chat._faction.oldestTimestamp = -1;
+ chat._faction.newestTimestamp = -1;
+
+ chat._public.data = {};
+ chat._public.oldestTimestamp = -1;
+ chat._public.newestTimestamp = -1;
+ }
+ chat._oldBBox = bbs;
+
+ var ne = b.getNorthEast();
+ var sw = b.getSouthWest();
+ var data = {
+ desiredNumItems: isFaction ? CHAT_FACTION_ITEMS : CHAT_PUBLIC_ITEMS ,
+ minLatE6: Math.round(sw.lat*1E6),
+ minLngE6: Math.round(sw.lng*1E6),
+ maxLatE6: Math.round(ne.lat*1E6),
+ maxLngE6: Math.round(ne.lng*1E6),
+ minTimestampMs: -1,
+ maxTimestampMs: -1,
+ factionOnly: isFaction
+ }
+
+ if(getOlderMsgs) {
+ // ask for older chat when scrolling up
+ data = $.extend(data, {maxTimestampMs: storageHash.oldestTimestamp});
+ } else {
+ // ask for newer chat
+ var min = storageHash.newestTimestamp;
+ // the inital request will have both timestamp values set to -1,
+ // thus we receive the newest desiredNumItems. After that, we will
+ // only receive messages with a timestamp greater or equal to min
+ // above.
+ // After resuming from idle, there might be more new messages than
+ // desiredNumItems. So on the first request, we are not really up to
+ // date. We will eventually catch up, as long as there are less new
+ // messages than desiredNumItems per each refresh cycle.
+ // A proper solution would be to query until no more new results are
+ // returned. Another way would be to set desiredNumItems to a very
+ // large number so we really get all new messages since the last
+ // request. Setting desiredNumItems to -1 does unfortunately not
+ // work.
+ // Currently this edge case is not handled. Let’s see if this is a
+ // problem in crowded areas.
+ $.extend(data, {minTimestampMs: min});
+ }
+ return data;
+}
+
+
+
+//
+// faction
+//
+
+window.chat._requestFactionRunning = false;
+window.chat.requestFaction = function(getOlderMsgs, isRetry) {
+ if(chat._requestFactionRunning && !isRetry) return;
+ if(isIdle()) return renderUpdateStatus();
+ chat._requestFactionRunning = true;
+
+ var d = chat.genPostData(true, chat._faction, getOlderMsgs);
+ var r = window.postAjax(
+ 'getPaginatedPlextsV2',
+ d,
+ chat.handleFaction,
+ isRetry
+ ? function() { window.chat._requestFactionRunning = false; }
+ : function() { window.chat.requestFaction(getOlderMsgs, true) }
+ );
+
+ requests.add(r);
+}
+
+
+window.chat._faction = {data:{}, oldestTimestamp:-1, newestTimestamp:-1};
+window.chat.handleFaction = function(data, textStatus, jqXHR) {
+ chat._requestFactionRunning = false;
+
+ if(!data || !data.result) {
+ window.failedRequestCount++;
+ return console.warn('faction chat error. Waiting for next auto-refresh.');
+ }
+
+ if(data.result.length === 0) return;
+
+ var old = chat._faction.oldestTimestamp;
+ chat.writeDataToHash(data, chat._faction, false);
+ var oldMsgsWereAdded = old !== chat._faction.oldestTimestamp;
+
+ runHooks('factionChatDataAvailable', {raw: data, processed: chat._faction.data});
+
+ window.chat.renderFaction(oldMsgsWereAdded);
+
+ if(data.result.length >= CHAT_FACTION_ITEMS) chat.needMoreMessages();
+}
+
+window.chat.renderFaction = function(oldMsgsWereAdded) {
+ chat.renderData(chat._faction.data, 'chatfaction', oldMsgsWereAdded);
+}
+
+
+//
+// public
+//
+
+window.chat._requestPublicRunning = false;
+window.chat.requestPublic = function(getOlderMsgs, isRetry) {
+ if(chat._requestPublicRunning && !isRetry) return;
+ if(isIdle()) return renderUpdateStatus();
+ chat._requestPublicRunning = true;
+
+ var d = chat.genPostData(false, chat._public, getOlderMsgs);
+ var r = window.postAjax(
+ 'getPaginatedPlextsV2',
+ d,
+ chat.handlePublic,
+ isRetry
+ ? function() { window.chat._requestPublicRunning = false; }
+ : function() { window.chat.requestPublic(getOlderMsgs, true) }
+ );
+
+ requests.add(r);
+}
+
+window.chat._public = {data:{}, oldestTimestamp:-1, newestTimestamp:-1};
+window.chat.handlePublic = function(data, textStatus, jqXHR) {
+ chat._requestPublicRunning = false;
+
+ if(!data || !data.result) {
+ window.failedRequestCount++;
+ return console.warn('public chat error. Waiting for next auto-refresh.');
+ }
+
+ if(data.result.length === 0) return;
+
+ var old = chat._public.oldestTimestamp;
+ chat.writeDataToHash(data, chat._public, true);
+ var oldMsgsWereAdded = old !== chat._public.oldestTimestamp;
+
+ runHooks('publicChatDataAvailable', {raw: data, processed: chat._public.data});
+
+ switch(chat.getActive()) {
+ case 'public': window.chat.renderPublic(oldMsgsWereAdded); break;
+ case 'compact': window.chat.renderCompact(oldMsgsWereAdded); break;
+ case 'full': window.chat.renderFull(oldMsgsWereAdded); break;
+ }
+
+ if(data.result.length >= CHAT_PUBLIC_ITEMS) chat.needMoreMessages();
+}
+
+window.chat.renderPublic = function(oldMsgsWereAdded) {
+ // only keep player data
+ var data = $.map(chat._public.data, function(entry) {
+ if(!entry[1]) return [entry];
+ });
+ chat.renderData(data, 'chatpublic', oldMsgsWereAdded);
+}
+
+window.chat.renderCompact = function(oldMsgsWereAdded) {
+ var data = {};
+ $.each(chat._public.data, function(guid, entry) {
+ // skip player msgs
+ if(!entry[1]) return true;
+ var pguid = entry[3];
+ // ignore if player has newer data
+ if(data[pguid] && data[pguid][0] > entry[0]) return true;
+ data[pguid] = entry;
+ });
+ // data keys are now player guids instead of message guids. However,
+ // it is all the same to renderData.
+ chat.renderData(data, 'chatcompact', oldMsgsWereAdded);
+}
+
+window.chat.renderFull = function(oldMsgsWereAdded) {
+ // only keep automatically generated data
+ var data = $.map(chat._public.data, function(entry) {
+ if(entry[1]) return [entry];
+ });
+ chat.renderData(data, 'chatfull', oldMsgsWereAdded);
+}
+
+
+//
+// common
+//
+
+window.chat.nicknameClicked = function(event, nickname) {
+ var hookData = { event: event, nickname: nickname };
+
+ if (window.runHooks('nicknameClicked', hookData)) {
+ window.chat.addNickname('@' + nickname);
+ }
+}
+
+window.chat.writeDataToHash = function(newData, storageHash, isPublicChannel) {
+ $.each(newData.result, function(ind, json) {
+ // avoid duplicates
+ if(json[0] in storageHash.data) return true;
+
+ var isSecureMessage = false;
+ var msgToPlayer = false;
+
+ var time = json[1];
+ var team = json[2].plext.team === 'ALIENS' ? TEAM_ENL : TEAM_RES;
+ var auto = json[2].plext.plextType !== 'PLAYER_GENERATED';
+ var systemNarrowcast = json[2].plext.plextType === 'SYSTEM_NARROWCAST';
+
+ //track oldest + newest timestamps
+ if (storageHash.oldestTimestamp === -1 || storageHash.oldestTimestamp > time) storageHash.oldestTimestamp = time;
+ if (storageHash.newestTimestamp === -1 || storageHash.newestTimestamp < time) storageHash.newestTimestamp = time;
+
+ //remove "Your X on Y was destroyed by Z" from the faction channel
+ if (systemNarrowcast && !isPublicChannel) return true;
+
+ var msg = '', nick = '', pguid;
+ $.each(json[2].plext.markup, function(ind, markup) {
+ switch(markup[0]) {
+ case 'SENDER': // user generated messages
+ nick = markup[1].plain.slice(0, -2); // cut “: ” at end
+ pguid = markup[1].guid;
+ break;
+
+ case 'PLAYER': // automatically generated messages
+ pguid = markup[1].guid;
+ nick = markup[1].plain;
+ team = markup[1].team === 'ALIENS' ? TEAM_ENL : TEAM_RES;
+ if(ind > 0) msg += nick; // don’t repeat nick directly
+ break;
+
+ case 'TEXT':
+ msg += $('').text(markup[1].plain).html().autoLink();
+ break;
+
+ case 'AT_PLAYER':
+ var thisToPlayer = (markup[1].plain == ('@'+window.PLAYER.nickname));
+ var spanClass = thisToPlayer ? "pl_nudge_me" : (markup[1].team + " pl_nudge_player");
+ var atPlayerName = markup[1].plain.replace(/^@/, "");
+ msg += $('').html($('')
+ .attr('class', spanClass)
+ .attr('onclick',"window.chat.nicknameClicked(event, '"+atPlayerName+"')")
+ .text(markup[1].plain)).html();
+ msgToPlayer = msgToPlayer || thisToPlayer;
+ break;
+
+ case 'PORTAL':
+ var latlng = [markup[1].latE6/1E6, markup[1].lngE6/1E6];
+ var perma = '/intel?ll='+latlng[0]+','+latlng[1]+'&z=17&pll='+latlng[0]+','+latlng[1];
+ var js = 'window.zoomToAndShowPortal(\''+markup[1].guid+'\', ['+latlng[0]+', '+latlng[1]+']);return false';
+
+ msg += ''
+ + window.chat.getChatPortalName(markup[1])
+ + '';
+ break;
+
+ case 'SECURE':
+ //NOTE: we won't add the '[secure]' string here - it'll be handled below instead
+ isSecureMessage = true;
+ break;
+
+ default:
+ //handle unknown types by outputting the plain text version, marked with it's type
+ msg += $('').text(markup[0]+':<'+markup[1].plain+'>').html();
+ break;
+ }
+ });
+
+
+ //skip secure messages on the public channel
+ if (isPublicChannel && isSecureMessage) return true;
+
+ //skip public messages (e.g. @player mentions) on the secure channel
+ if ((!isPublicChannel) && (!isSecureMessage)) return true;
+
+
+ //NOTE: these two are currently redundant with the above two tests - but code can change...
+ //from the server, private channel messages are flagged with a SECURE string '[secure] ', and appear in
+ //both the public and private channels
+ //we don't include this '[secure]' text above, as it's redundant in the faction-only channel
+ //let's add it here though if we have a secure message in the public channel, or the reverse if a non-secure in the faction one
+ if (isPublicChannel && isSecureMessage) msg = '[secure] ' + msg;
+ //and, add the reverse - a 'public' marker to messages in the private channel
+ if ((!isPublicChannel) && (!isSecureMessage)) msg = '[public] ' + msg;
+
+
+ // format: timestamp, autogenerated, HTML message, player guid
+ storageHash.data[json[0]] = [json[1], auto, chat.renderMsg(msg, nick, time, team, msgToPlayer, systemNarrowcast), pguid];
+
+ window.setPlayerName(pguid, nick); // free nick name resolves
+ });
+}
+
+// Override portal names that are used over and over, such as 'US Post Office'
+window.chat.getChatPortalName = function(markup) {
+ var name = markup.name;
+ if(name === 'US Post Office') {
+ var address = markup.address.split(',');
+ name = 'USPS: ' + address[0];
+ }
+ return name;
+}
+
+// renders data from the data-hash to the element defined by the given
+// ID. Set 3rd argument to true if it is likely that old data has been
+// added. Latter is only required for scrolling.
+window.chat.renderData = function(data, element, likelyWereOldMsgs) {
+ var elm = $('#'+element);
+ if(elm.is(':hidden')) return;
+
+ // discard guids and sort old to new
+ var vals = $.map(data, function(v, k) { return [v]; });
+ vals = vals.sort(function(a, b) { return a[0]-b[0]; });
+
+ // render to string with date separators inserted
+ var msgs = '';
+ var prevTime = null;
+ $.each(vals, function(ind, msg) {
+ var nextTime = new Date(msg[0]).toLocaleDateString();
+ if(prevTime && prevTime !== nextTime)
+ msgs += chat.renderDivider(nextTime);
+ msgs += msg[2];
+ prevTime = nextTime;
+ });
+
+ var scrollBefore = scrollBottom(elm);
+ elm.html('
';
+}
+
+
+window.chat.renderMsg = function(msg, nick, time, team, msgToPlayer, systemNarrowcast) {
+ var ta = unixTimeToHHmm(time);
+ var tb = unixTimeToString(time, true);
+ // help cursor via “#chat time”
+ var t = '';
+ if ( msgToPlayer )
+ {
+ t = '
' + t + '
';
+ }
+ if (systemNarrowcast)
+ {
+ msg = '
' + msg + '
';
+ }
+ var color = COLORS[team];
+ if (nick === window.PLAYER.nickname) color = '#fd6'; //highlight things said/done by the player in a unique colour (similar to @player mentions from others in the chat text itself)
+ var s = 'style="cursor:pointer; color:'+color+'"';
+ var title = nick.length >= 8 ? 'title="'+nick+'" class="help"' : '';
+ var i = ['<', '>'];
+ return '
'+t+'
'+i[0]+''+ nick+''+i[1]+'
'+msg+'
';
+}
+
+window.chat.addNickname= function(nick){
+ var c = document.getElementById("chattext");
+ c.value = [c.value.trim(), nick].join(" ").trim() + " ";
+ c.focus()
+}
+
+
+
+
+window.chat.getActive = function() {
+ return $('#chatcontrols .active').text();
+}
+
+
+window.chat.toggle = function() {
+ var c = $('#chat, #chatcontrols');
+ if(c.hasClass('expand')) {
+ $('#chatcontrols a:first').html('');
+ c.removeClass('expand');
+ var div = $('#chat > div:visible');
+ div.data('ignoreNextScroll', true);
+ div.scrollTop(99999999); // scroll to bottom
+ $('.leaflet-control').css('margin-left', '13px');
+ } else {
+ $('#chatcontrols a:first').html('');
+ c.addClass('expand');
+ $('.leaflet-control').css('margin-left', '720px');
+ chat.needMoreMessages();
+ }
+}
+
+
+window.chat.request = function() {
+ console.log('refreshing chat');
+ chat.requestFaction(false);
+ chat.requestPublic(false);
+}
+
+
+// checks if there are enough messages in the selected chat tab and
+// loads more if not.
+window.chat.needMoreMessages = function() {
+ var activeTab = chat.getActive();
+ if(activeTab === 'debug') return;
+
+ var activeChat = $('#chat > :visible');
+ if(activeChat.length === 0) return;
+
+ var hasScrollbar = scrollBottom(activeChat) !== 0 || activeChat.scrollTop() !== 0;
+ var nearTop = activeChat.scrollTop() <= CHAT_REQUEST_SCROLL_TOP;
+ if(hasScrollbar && !nearTop) return;
+
+ console.log('No scrollbar or near top in active chat. Requesting more data.');
+
+ if(activeTab === 'faction')
+ chat.requestFaction(true);
+ else
+ chat.requestPublic(true);
+}
+
+
+window.chat.chooser = function(event) {
+ var t = $(event.target);
+ var tt = t.text();
+
+ var mark = $('#chatinput mark');
+ var input = $('#chatinput input');
+
+ $('#chatcontrols .active').removeClass('active');
+ t.addClass('active');
+
+ $('#chat > div').hide();
+
+ var elm;
+
+ switch(tt) {
+ case 'faction':
+ input.css('color', '');
+ mark.css('color', '');
+ mark.text('tell faction:');
+ break;
+
+ case 'public':
+ input.css('cssText', 'color: #f66 !important');
+ mark.css('cssText', 'color: #f66 !important');
+ mark.text('broadcast:');
+ break;
+
+ case 'compact':
+ case 'full':
+ mark.css('cssText', 'color: #bbb !important');
+ input.css('cssText', 'color: #bbb !important');
+ mark.text('tell Jarvis:');
+ break;
+
+ default:
+ throw('chat.chooser was asked to handle unknown button: ' + tt);
+ }
+
+ var elm = $('#chat' + tt);
+ elm.show();
+ eval('chat.render' + tt.capitalize() + '(false);');
+ if(elm.data('needsScrollTop')) {
+ elm.data('ignoreNextScroll', true);
+ elm.scrollTop(elm.data('needsScrollTop'));
+ elm.data('needsScrollTop', null);
+ }
+
+ chat.needMoreMessages();
+}
+
+
+// contains the logic to keep the correct scroll position.
+window.chat.keepScrollPosition = function(box, scrollBefore, isOldMsgs) {
+ // If scrolled down completely, keep it that way so new messages can
+ // be seen easily. If scrolled up, only need to fix scroll position
+ // when old messages are added. New messages added at the bottom don’t
+ // change the view and enabling this would make the chat scroll down
+ // for every added message, even if the user wants to read old stuff.
+
+ if(box.is(':hidden') && !isOldMsgs) {
+ box.data('needsScrollTop', 99999999);
+ return;
+ }
+
+ if(scrollBefore === 0 || isOldMsgs) {
+ box.data('ignoreNextScroll', true);
+ box.scrollTop(box.scrollTop() + (scrollBottom(box)-scrollBefore));
+ }
+}
+
+
+
+
+//
+// setup
+//
+
+window.chat.setup = function() {
+ window.chat._localRangeCircle = L.circle(map.getCenter(), CHAT_MIN_RANGE*1000);
+
+ $('#chatcontrols, #chat, #chatinput').show();
+
+ $('#chatcontrols a:first').click(window.chat.toggle);
+ $('#chatcontrols a').each(function(ind, elm) {
+ if($.inArray($(elm).text(), ['full', 'compact', 'public', 'faction']) !== -1)
+ $(elm).click(window.chat.chooser);
+ });
+
+
+ $('#chatinput').click(function() {
+ $('#chatinput input').focus();
+ });
+
+ window.chat.setupTime();
+ window.chat.setupPosting();
+
+ $('#chatfaction').scroll(function() {
+ var t = $(this);
+ if(t.data('ignoreNextScroll')) return t.data('ignoreNextScroll', false);
+ if(t.scrollTop() < CHAT_REQUEST_SCROLL_TOP) chat.requestFaction(true);
+ if(scrollBottom(t) === 0) chat.requestFaction(false);
+ });
+
+ $('#chatpublic, #chatfull, #chatcompact').scroll(function() {
+ var t = $(this);
+ if(t.data('ignoreNextScroll')) return t.data('ignoreNextScroll', false);
+ if(t.scrollTop() < CHAT_REQUEST_SCROLL_TOP) chat.requestPublic(true);
+ if(scrollBottom(t) === 0) chat.requestPublic(false);
+ });
+
+ chat.request();
+ window.addResumeFunction(chat.request);
+ window.requests.addRefreshFunction(chat.request);
+
+ var cls = PLAYER.team === 'ALIENS' ? 'enl' : 'res';
+ $('#chatinput mark').addClass(cls);
+
+ $(window).on('click', '.nickname', function(event) {
+ window.chat.nicknameClicked(event, $(this).text());
+ });
+}
+
+
+window.chat.setupTime = function() {
+ var inputTime = $('#chatinput time');
+ var updateTime = function() {
+ if(window.isIdle()) return;
+ var d = new Date();
+ var h = d.getHours() + ''; if(h.length === 1) h = '0' + h;
+ var m = d.getMinutes() + ''; if(m.length === 1) m = '0' + m;
+ inputTime.text(h+':'+m);
+ // update ON the minute (1ms after)
+ setTimeout(updateTime, (60 - d.getSeconds()) * 1000 + 1);
+ };
+ updateTime();
+ window.addResumeFunction(updateTime);
+}
+
+
+//
+// posting
+//
+
+
+window.chat.setupPosting = function() {
+ if (!isSmartphone()) {
+ $('#chatinput input').keydown(function(event) {
+ try {
+ var kc = (event.keyCode ? event.keyCode : event.which);
+ if(kc === 13) { // enter
+ chat.postMsg();
+ event.preventDefault();
+ } else if (kc === 9) { // tab
+ event.preventDefault();
+ window.chat.handleTabCompletion();
+ }
+ } catch(error) {
+ console.log(error);
+ debug.printStackTrace();
+ }
+ });
+ }
+
+ $('#chatinput').submit(function(event) {
+ event.preventDefault();
+ chat.postMsg();
+ });
+}
+
+
+window.chat.postMsg = function() {
+ var c = chat.getActive();
+ if(c === 'full' || c === 'compact')
+ return alert('Jarvis: A strange game. The only winning move is not to play. How about a nice game of chess?');
+
+ var msg = $.trim($('#chatinput input').val());
+ if(!msg || msg === '') return;
+
+ if(c === 'debug') return new Function (msg)();
+
+ var publik = c === 'public';
+ var latlng = map.getCenter();
+
+ var data = {message: msg,
+ latE6: Math.round(latlng.lat*1E6),
+ lngE6: Math.round(latlng.lng*1E6),
+ factionOnly: !publik};
+
+ var errMsg = 'Your message could not be delivered. You can copy&' +
+ 'paste it here and try again if you want:\n\n' + msg;
+
+ window.postAjax('sendPlext', data,
+ function(response) {
+ if(response.error) alert(errMsg);
+ if(publik) chat.requestPublic(false); else chat.requestFaction(false); },
+ function() {
+ alert(errMsg);
+ }
+ );
+
+ $('#chatinput input').val('');
+}
diff --git a/code/map_data.js b/code/map_data.js
index 419534f0..2c956a95 100644
--- a/code/map_data.js
+++ b/code/map_data.js
@@ -99,6 +99,7 @@ window.handleDataResponse = function(data, textStatus, jqXHR) {
if(!window.getPaddedBounds().contains(latlng)
&& selectedPortal !== ent[0]
&& urlPortal !== ent[0]
+ && !(urlPortalLL && urlPortalLL[0] === latlng[0] && urlPortalLL[1] === latlng[1])
) return;
if('imageByUrl' in ent[2] && 'imageUrl' in ent[2].imageByUrl) {
@@ -175,11 +176,15 @@ window.handlePortalsRender = function(portals) {
runHooks('portalDataLoaded', {portals : portals});
$.each(portals, function(ind, portal) {
//~ if(selectedPortal === portal[0]) portalUpdateAvailable = true;
- if(urlPortal && portal[0] === urlPortal) portalInUrlAvailable = true;
+ if(urlPortalLL && urlPortalLL[0] === portal[2].locationE6.latE6/1E6 && urlPortalLL[1] === portal[2].locationE6.lngE6/1E6) {
+ urlPortal = portal[0];
+ portalInUrlAvailable = true;
+ urlPortalLL = null;
+ }
if(window.portals[portal[0]]) {
highlightPortal(window.portals[portal[0]]);
}
- renderPortal(portal);
+ renderPortal(portal);
});
// restore selected portal if still available
diff --git a/code/portal_detail_display.js b/code/portal_detail_display.js
index 636230ed..205b80c5 100644
--- a/code/portal_detail_display.js
+++ b/code/portal_detail_display.js
@@ -45,11 +45,11 @@ window.renderPortalDetails = function(guid) {
setPortalIndicators(d);
var img = d.imageByUrl.imageUrl;
- var lat = d.locationE6.latE6;
- var lng = d.locationE6.lngE6;
- var perma = '/intel?latE6='+lat+'&lngE6='+lng+'&z=17&pguid='+guid;
+ var lat = d.locationE6.latE6/1E6;
+ var lng = d.locationE6.lngE6/1E6;
+ var perma = '/intel?ll='+lat+','+lng+'&z=17&pll='+lat+','+lng;
var imgTitle = 'title="'+getPortalDescriptionFromDetails(d)+'\n\nClick to show full image."';
- var poslinks = 'window.showPortalPosLinks('+lat/1E6+','+lng/1E6+',\'' + d.portalV2.descriptiveText.TITLE + '\')';
+ var poslinks = 'window.showPortalPosLinks('+lat+','+lng+',\'' + d.portalV2.descriptiveText.TITLE + '\')';
var postcard = 'Send in a postcard. Will put it online after receiving. Address:\\n\\nStefan Breunig\\nINF 305 – R045\\n69120 Heidelberg\\nGermany';
$('#portaldetails')
diff --git a/code/utils_misc.js b/code/utils_misc.js
index e4d0f5c5..dede2f01 100644
--- a/code/utils_misc.js
+++ b/code/utils_misc.js
@@ -288,9 +288,9 @@ window.prettyEnergy = function(nrg) {
window.setPermaLink = function(elm) {
var c = map.getCenter();
- var lat = Math.round(c.lat*1E6);
- var lng = Math.round(c.lng*1E6);
- var qry = 'latE6='+lat+'&lngE6='+lng+'&z=' + map.getZoom();
+ var lat = Math.round(c.lat*1E6)/1E6;
+ var lng = Math.round(c.lng*1E6)/1E6;
+ var qry = 'll='+lat+','+lng+'&z=' + map.getZoom();
$(elm).attr('href', '/intel?' + qry);
}