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 = '' + ' ' + '
permalink
' + '
' - + '
' - + ' '; - + ''; + + ' ' + + '' + + '
'; // putting everything in a wrapper function that in turn is placed in a // script tag on the website allows us to execute in the site’s context @@ -147,6 +146,9 @@ var MIN_AP_FOR_LEVEL = [0, 10000, 30000, 70000, 150000, 300000, 600000, 1200000] var HACK_RANGE = 40; // in meters, max. distance from portal to be able to access it var OCTANTS = ['E', 'NE', 'N', 'NW', 'W', 'SW', 'S', 'SE']; var DEFAULT_PORTAL_IMG = 'http://commondatastorage.googleapis.com/ingress/img/default-portal-image.png'; +var DESTROY_RESONATOR = 75; //AP for destroying portal +var DESTROY_LINK = 187; //AP for destroying link +var DESTROY_FIELD = 750; //AP for destroying field // OTHER MORE-OR-LESS CONSTANTS ////////////////////////////////////// var NOMINATIM = 'http://nominatim.openstreetmap.org/search?format=json&limit=1&q='; diff --git a/style.css b/style.css index 81147f30..49539198 100644 --- a/style.css +++ b/style.css @@ -10,39 +10,43 @@ body { margin: 0; } -#map { - margin-right:302px; -} - #scrollwrapper { - height: 100%; overflow: hidden; position: fixed; right: -38px; top: 0; width: 340px; + bottom: 45px; + z-index: 1001; + + /* + * NOTE: following 2 items are needed for back compatability + * between new css and old plugin versions (0.4) as of 2013-02-11 UTC. + */ + background-color: rgba(8, 48, 78, 1); } #sidebar { - background: #000; - border-left: 2px solid #c3c3c3; + background-color: rgba(8, 48, 78, 0.9); + border-left: 1px solid #20A8B1; color: #888; - height: 100%; position: relative; left: 0; top: 0; + max-height: 100%; overflow-y:scroll; overflow-x:hidden; + z-index: 3000; } #sidebartoggle { display: block; - padding: 10px 5px; + padding: 20px 5px; margin-top: -31px; line-height: 10px; position: absolute; top: 50%; - z-index: 3000; + z-index: 3001; background-color: rgba(8, 48, 78, 0.9); color: #FFCE00; border: 1px solid #20A8B1; @@ -257,7 +261,7 @@ summary { /* sidebar ************************************************************/ #sidebar > * { - border-bottom: 1px solid #c3c3c3; + border-bottom: 1px solid #20A8B1; } @@ -321,7 +325,7 @@ h2 sup, h2 sub { /* geosearch input, and others */ input { - background: #313131; + background-color: rgba(0, 0, 0, 0.3); color: #ffce00; height: 22px; line-height: 22px; @@ -369,8 +373,7 @@ h3 { font-size: 40px; position: absolute; right: 10px; - /* simulate an outline with multiple shadows */ - text-shadow: -2px -2px #000000, 2px -2px #000000, -2px 2px #000000, 2px 2px #000000; + text-shadow: -1px -1px #000, 1px -1px #000, -1px 1px #000, 1px 1px #000, 0 0 5px #fff; top: 100px; } @@ -382,7 +385,7 @@ h3 { } .mods span { - background: #313131; + background-color: rgba(0, 0, 0, 0.3); /* can’t use inline-block because Webkit’s implementation is buggy and * introduces additional margins in random cases. No clear necessary, * as that’s solved by setting height on .mods. */ @@ -527,30 +530,30 @@ aside:nth-child(odd) span { } #spacer { - /* cheap hack to prevent sidebar content being overlayed by the map - * status box */ - height: 55px; + height: 10px; } /* a common portal display takes this much space (prevents moving * content when first selecting a portal) */ #portaldetails { - min-height: 532px; + min-height: 553px; } /* update status */ #updatestatus { - background: #000; + background-color: rgba(8, 48, 78, 1); border-bottom: 0; - border-top: 1px solid #c3c3c3; + border-top: 1px solid #20A8B1; + border-left: 1px solid #20A8B1; bottom: 0; color: #ffce00; font-size:13px; padding: 4px; position: fixed; right: 0; + z-index:3002; }