diff --git a/README.md b/README.md
index bf1db66e..3fcfabbb 100644
--- a/README.md
+++ b/README.md
@@ -81,6 +81,7 @@ Contributors
[JasonMillward](https://github.com/JasonMillward),
[mledoze](https://github.com/mledoze),
[OshiHidra](https://github.com/OshiHidra),
+[Pirozek](https://github.com/Pirozek),
[Scrool](https://github.com/Scrool),
[sorgo](https://github.com/sorgo),
[Xelio](https://github.com/Xelio),
diff --git a/build.py b/build.py
index b690a2ed..611c86e3 100755
--- a/build.py
+++ b/build.py
@@ -1,10 +1,10 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import glob
import time
def readfile(fn):
- with open(fn, 'Ur') as f:
+ with open(fn, 'Ur', encoding='utf8') as f:
return f.read()
c = '\n\n'.join(map(readfile, glob.glob('code/*')))
@@ -14,7 +14,7 @@ m = m.split('@@INJECTHERE@@')
m.insert(1, c)
t = '\n\n'.join(m)
-with open('total-conversion-build.user.js', 'w') as f:
+with open('total-conversion-build.user.js', 'w', encoding='utf8') as f:
f.write(t)
# vim: ai si ts=4 sw=4 sts=4 et
diff --git a/code/boot.js b/code/boot.js
index b4d927ba..dbd9b265 100644
--- a/code/boot.js
+++ b/code/boot.js
@@ -27,17 +27,17 @@ window.setupLargeImagePreview = function() {
window.setupStyles = function() {
$('head').append('');
}
@@ -176,16 +176,15 @@ window.setupSidebarToggle = function() {
var sidebar = $('#sidebar');
if(sidebar.is(':visible')) {
sidebar.hide();
- $('#map').css('margin-right','0');
+ $('.leaflet-right').css('margin-right','0');
toggle.html('◢
◥');
toggle.css('right', '0');
} else {
sidebar.show();
- $('#map').css('margin-right', SIDEBAR_WIDTH+2+'px');
+ $('.leaflet-right').css('margin-right', SIDEBAR_WIDTH+1+'px');
toggle.html('◣
◤');
- toggle.css('right', SIDEBAR_WIDTH+'px');
+ toggle.css('right', SIDEBAR_WIDTH+1+'px');
}
- window.map.invalidateSize(false);
});
}
diff --git a/code/map_data.js b/code/map_data.js
index 34f9dcc4..bcd8ba11 100644
--- a/code/map_data.js
+++ b/code/map_data.js
@@ -216,9 +216,17 @@ window.renderPortal = function(ent) {
var team = getTeam(ent[2]);
// do nothing if portal did not change
- var old = window.portals[ent[0]];
- if(old && old.options.level === portalLevel && old.options.team === team)
- return;
+ var layerGroup = portalsLayers[parseInt(portalLevel)];
+ var old = findEntityInLeaflet(layerGroup, window.portals, ent[0]);
+ if(old) {
+ var oo = old.options;
+ var u = oo.team !== team;
+ u = u || oo.level !== portalLevel;
+ // nothing for the portal changed, so don’t update. Let resonators
+ // manage themselves if they want to be updated.
+ if(!u) return renderResonators(ent);
+ removeByGuid(ent[0]);
+ }
// there were changes, remove old portal
removeByGuid(ent[0]);
@@ -245,7 +253,7 @@ window.renderPortal = function(ent) {
details: ent[2],
guid: ent[0]});
- p.on('remove', function() {
+ p.on('remove', function() {
var portalGuid = this.options.guid
// remove attached resonators, skip if
@@ -261,13 +269,17 @@ window.renderPortal = function(ent) {
window.portalAccessIndicator = null;
}
});
- p.on('add', function() {
+
+ p.on('add', function() {
+ // enable for debugging
+ if(window.portals[this.options.guid]) throw('duplicate portal detected');
window.portals[this.options.guid] = this;
// handles the case where a selected portal gets removed from the
// map by hiding all portals with said level
if(window.selectedPortal != this.options.guid)
window.portalResetColor(this);
});
+
p.on('click', function() { window.renderPortalDetails(ent[0]); });
p.on('dblclick', function() {
window.renderPortalDetails(ent[0]);
@@ -279,11 +291,10 @@ window.renderPortal = function(ent) {
window.runHooks('portalAdded', {portal: p});
// portalLevel contains a float, need to round down
- p.addTo(portalsLayers[parseInt(portalLevel)]);
+ p.addTo(layerGroup);
}
window.renderResonators = function(ent) {
-
var portalLevel = getPortalLevel(ent[2]);
if(portalLevel < getMinPortalLevel() && ent[0] != selectedPortal) return;
@@ -344,8 +355,12 @@ window.portalResetColor = function(portal) {
// renders a link on the map from the given entity
window.renderLink = function(ent) {
- removeByGuid(ent[0]);
- if(Object.keys(links).length >= MAX_DRAWN_LINKS) return;
+ if(Object.keys(links).length >= MAX_DRAWN_LINKS)
+ return removeByGuid(ent[0]);
+
+ // assume that links never change. If they do, they will have a
+ // different ID.
+ if(findEntityInLeaflet(linksLayer, links, ent[0])) return;
var team = getTeam(ent[2]);
var edge = ent[2].edge;
@@ -366,6 +381,8 @@ window.renderLink = function(ent) {
poly.on('remove', function() { delete window.links[this.options.guid]; });
poly.on('add', function() {
+ // enable for debugging
+ if(window.links[this.options.guid]) throw('duplicate link detected');
window.links[this.options.guid] = this;
this.bringToBack();
});
@@ -374,8 +391,12 @@ window.renderLink = function(ent) {
// renders a field on the map from a given entity
window.renderField = function(ent) {
- window.removeByGuid(ent[0]);
- if(Object.keys(fields).length >= MAX_DRAWN_FIELDS) return;
+ if(Object.keys(fields).length >= MAX_DRAWN_FIELDS)
+ return window.removeByGuid(ent[0]);
+
+ // assume that fields never change. If they do, they will have a
+ // different ID.
+ if(findEntityInLeaflet(fieldsLayer, fields, ent[0])) return;
var team = getTeam(ent[2]);
var reg = ent[2].capturedRegion;
@@ -391,14 +412,37 @@ window.renderField = function(ent) {
clickable: false,
smoothFactor: 10,
vertices: ent[2].capturedRegion,
+ lastUpdate: ent[1],
guid: ent[0]});
if(!getPaddedBounds().intersects(poly.getBounds())) return;
poly.on('remove', function() { delete window.fields[this.options.guid]; });
poly.on('add', function() {
+ // enable for debugging
+ if(window.fields[this.options.guid]) console.warn('duplicate field detected');
window.fields[this.options.guid] = this;
this.bringToBack();
});
poly.addTo(fieldsLayer);
}
+
+
+// looks for the GUID in either the layerGroup or entityHash, depending
+// on which is faster. Will either return the Leaflet entity or null, if
+// it does not exist.
+// For example, to find a field use the function like this:
+// field = findEntityInLeaflet(fieldsLayer, fields, 'asdasdasd');
+window.findEntityInLeaflet = function(layerGroup, entityHash, guid) {
+ // fast way
+ if(map.hasLayer(layerGroup)) return entityHash[guid] || null;
+
+ // slow way in case the layer is currently hidden
+ var ent = null;
+ layerGroup.eachLayer(function(entity) {
+ if(entity.options.guid !== guid) return true;
+ ent = entity;
+ return false;
+ });
+ return ent;
+}
diff --git a/code/portal_detail_display.js b/code/portal_detail_display.js
index 30a24f83..98b33592 100644
--- a/code/portal_detail_display.js
+++ b/code/portal_detail_display.js
@@ -19,24 +19,23 @@ window.renderPortalDetails = function(guid) {
links[link.isOrigin ? 'outgoing' : 'incoming']++;
});
function linkExpl(t) { return ''+t+''; }
- var linksText = linkExpl('links')+':'+linkExpl(' ↳ ' + links.incoming+' • '+links.outgoing+' ↴');
+ var linksText = [linkExpl('links'), linkExpl(' ↳ ' + links.incoming+' • '+links.outgoing+' ↴')];
var player = d.captured && d.captured.capturingPlayerId
? getPlayerName(d.captured.capturingPlayerId)
: null;
- var playerText = player ? 'owner: ' + player : null;
+ var playerText = player ? ['owner', player] : null;
var time = d.captured ? unixTimeToString(d.captured.capturedTime) : null;
- var sinceText = time ? 'since: ' + time : null;
+ var sinceText = time ? ['since', time] : null;
- var linkedFields = 'fields: ' + d.portalV2.linkedFields.length;
+ var linkedFields = ['fields', d.portalV2.linkedFields.length];
// collect and html-ify random data
- var randDetails = [playerText, sinceText, getRangeText(d), getEnergyText(d), linksText, getAvgResoDistText(d), linkedFields];
+ var randDetails = [playerText, sinceText, getRangeText(d), getEnergyText(d), linksText, getAvgResoDistText(d), linkedFields, getDestroyAP(d)];
randDetails = randDetails.map(function(detail) {
if(!detail) return '';
- detail = detail.split(':');
- detail = '';
+ detail = '';
return detail;
}).join('\n');
diff --git a/code/portal_detail_display_tools.js b/code/portal_detail_display_tools.js
index 21fe3b91..94effc4b 100644
--- a/code/portal_detail_display_tools.js
+++ b/code/portal_detail_display_tools.js
@@ -6,12 +6,12 @@
// returns displayable text+link about portal range
window.getRangeText = function(d) {
var range = getPortalRange(d);
- return 'range: '
+ return ['range',
+ ''
+ (range > 1000
? Math.round(range/1000) + ' km'
: Math.round(range) + ' m')
- + '';
+ + ''];
}
// generates description text from details for portal
@@ -69,17 +69,15 @@ window.getEnergyText = function(d) {
var totalNrg = getTotalPortalEnergy(d);
var inf = currentNrg + ' / ' + totalNrg;
var fill = prettyEnergy(currentNrg) + ' / ' + prettyEnergy(totalNrg)
- var meter = 'energy: ' + fill + '';
- return meter;
+ return ['energy', '' + fill + ''];
}
window.getAvgResoDistText = function(d) {
var avgDist = Math.round(10*getAvgResoDist(d))/10;
- return '⌀ res dist: ' + avgDist + ' m';
+ return ['⌀ res dist', avgDist + ' m'];
}
window.getResonatorDetails = function(d) {
- console.log('rendering reso details');
var resoDetails = '';
// octant=slot: 0=E, 1=NE, 2=N, 3=NW, 4=W, 5=SW, 6=S, SE=7
// resos in the display should be ordered like this:
@@ -139,3 +137,33 @@ window.renderResonatorDetails = function(slot, level, nrg, dist, nick, isLeft) {
var text = ''+(nick||'')+'';
return (isLeft ? text+meter : meter+text) + '
';
}
+
+// calculate AP gain from destroying portal
+// so far it counts only resonators + links
+window.getDestroyAP = function(d) {
+ var resoCount = 0;
+
+ $.each(d.resonatorArray.resonators, function(ind, reso) {
+ if(!reso) return true;
+ resoCount += 1;
+ });
+
+ var linkCount = d.portalV2.linkedEdges ? d.portalV2.linkedEdges.length : 0;
+ var fieldCount = d.portalV2.linkedFields ? d.portalV2.linkedFields.length : 0;
+
+ var resoAp = resoCount * DESTROY_RESONATOR;
+ var linkAp = linkCount * DESTROY_LINK;
+ var fieldAp = fieldCount * DESTROY_FIELD;
+ var sum = resoAp + linkAp + fieldAp;
+
+ function tt(text) {
+ var t = 'Destroy:\n';
+ t += resoCount + '×\tResonators\t= ' + digits(resoAp) + '\n';
+ t += linkCount + '×\tLinks\t\t= ' + digits(linkAp) + '\n';
+ t += fieldCount + '×\tFields\t\t= ' + digits(fieldAp) + '\n';
+ t += 'Sum: ' + digits(sum) + ' AP';
+ return '' + digits(text) + '';
+ }
+
+ return [tt('AP Gain'), tt(sum)];
+}
diff --git a/code/request_handling.js b/code/request_handling.js
index 60aa00f6..042fc397 100644
--- a/code/request_handling.js
+++ b/code/request_handling.js
@@ -50,7 +50,7 @@ window.renderUpdateStatus = function() {
t += ' RENDER LIMIT '
if(window.failedRequestCount > 0)
- t += ' ' + window.failedRequestCount + ' requests failed.'
+ t += ' ' + window.failedRequestCount + ' failed.'
t += '
(';
var minlvl = getMinPortalLevel();
diff --git a/code/utils_misc.js b/code/utils_misc.js
index 2f982f7e..75cd1d72 100644
--- a/code/utils_misc.js
+++ b/code/utils_misc.js
@@ -160,6 +160,10 @@ window.getTypeByGuid = function(guid) {
// .c == player/creator
// .d == chat messages
//
+ // others, not used in web:
+ // .5 == resources (burster/resonator)
+ // .6 == XM
+ // .4 == media items, maybe all droppped resources (?)
// resonator guid is [portal guid]-resonator-[slot]
switch(guid.slice(33)) {
case '11':
diff --git a/main.js b/main.js
index 6f722903..31bd434e 100644
--- a/main.js
+++ b/main.js
@@ -49,7 +49,6 @@ for(var i = 0; i < d.length; i++) {
// player information is now available in a hash like this:
// window.PLAYER = {"ap": "123", "energy": 123, "available_invites": 123, "nickname": "somenick", "team": "ALIENS||RESISTANCE"};
-
// remove complete page. We only wanted the user-data and the page’s
// security context so we can access the API easily. Setup as much as
// possible without requiring scripts.
@@ -81,9 +80,9 @@ document.getElementsByTagName('body')[0].innerHTML = ''
+ ' '
+ '