diff --git a/.gitignore b/.gitignore index bf02b011b..09fdf0670 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # IDE .idea +.vscode # Build artifacts /ocap-webserver @@ -9,6 +10,7 @@ data.db data/ maps/ +setting.json static/data static/images/maps diff --git a/README.md b/README.md index 3bbd75bfc..1926a9b0d 100644 --- a/README.md +++ b/README.md @@ -168,16 +168,19 @@ docker run --name ocap-web -d \ This Project is based on [Golang](https://golang.org/dl/) ### Windows + ```bash go build -o ocap-webserver.exe ./cmd ``` ### Linux + ```bash go build -o ocap-webserver ./cmd ``` ### Docker + ```bash docker build -t ocap-webserver . ``` diff --git a/setting.json b/setting.json deleted file mode 100644 index b7088097a..000000000 --- a/setting.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "listen": "127.0.0.1:5000", - "prefixURL": "/aar/", - "secret": "same-secret", - "logger": true, - "customize": { - "websiteURL": "", - "websiteLogo": "", - "disableKillCount": false - } -} diff --git a/setting.json.example b/setting.json.example new file mode 100644 index 000000000..22875a5ed --- /dev/null +++ b/setting.json.example @@ -0,0 +1,25 @@ +{ + "listen": "127.0.0.1:5000", + "prefixURL": "", + "secret": "change-me", + "db": "data.db", + "markers": "assets/markers", + "ammo": "assets/ammo", + "maps": "maps", + "data": "data", + "static": "static", + "logger": false, + "customize": { + "websiteURL": "", + "websiteLogo": "", + "websiteLogoSize": "32px", + "disableKillCount": false + }, + "conversion": { + "enabled": false, + "interval": "5m", + "batchSize": 1, + "chunkSize": 300, + "storageEngine": "protobuf" + } +} diff --git a/static/index.html b/static/index.html index b996016ce..b2c0ba670 100644 --- a/static/index.html +++ b/static/index.html @@ -8,12 +8,17 @@ + + + + + diff --git a/static/leaflet/L.Control.Basemaps.css b/static/leaflet/L.Control.Basemaps.css new file mode 100644 index 000000000..69921e1c3 --- /dev/null +++ b/static/leaflet/L.Control.Basemaps.css @@ -0,0 +1,28 @@ +.basemaps { + padding: 4px; +} +.basemaps.closed .basemap { + display: none; +} +.basemaps.closed .basemap.alt { + display: inline-block; +} +.basemaps.closed .basemap.alt h4 { + display: none; +} + +.basemap { + display: inline-block; /* todo: flexbox? */ + cursor: pointer; +} +.basemap.active img { + border-color: orange; + box-shadow: 2px 2px 4px #000; +} +.basemap img { + width: 64px; + border: 2px solid #FFF; + margin: 0 2px; + border-radius: 40px; + box-shadow: 0 1px 5px rgba(0,0,0,0.65) +} \ No newline at end of file diff --git a/static/leaflet/L.Control.Basemaps.js b/static/leaflet/L.Control.Basemaps.js new file mode 100644 index 000000000..8851f55dc --- /dev/null +++ b/static/leaflet/L.Control.Basemaps.js @@ -0,0 +1 @@ +L.Control.Basemaps=L.Control.extend({_map:null,includes:L.Evented?L.Evented.prototype:L.Mixin.Event,options:{position:"bottomright",tileX:0,tileY:0,tileZ:0,layers:[]},basemap:null,onAdd:function(e){this._map=e;var t=L.DomUtil.create("div","basemaps leaflet-control closed");return L.DomEvent.disableClickPropagation(t),L.Browser.touch||L.DomEvent.disableScrollPropagation(t),this.options.basemaps.forEach(function(s,o){var a,i="basemap";if(0===o?(this.basemap=s,this._map.addLayer(s),i+=" active"):1===o&&(i+=" alt"),s.options.iconURL)a=s.options.iconURL;else{var l={x:this.options.tileX,y:this.options.tileY};if(a=L.Util.template(s._url,L.extend({s:s._getSubdomain(l),x:l.x,y:s.options.tms?s._globalTileRange.max.y-l.y:l.y,z:this.options.tileZ},s.options)),s instanceof L.TileLayer.WMS){s._map=e;var n=s.options.crs||e.options.crs,r=L.extend({},s.wmsParams),m=parseFloat(r.version);r[m>=1.3?"crs":"srs"]=n.code;var p=L.point(l);p.z=this.options.tileZ;var c=s._tileCoordsToBounds(p),d=n.project(c.getNorthWest()),h=n.project(c.getSouthEast()),v=(m>=1.3&&n===L.CRS.EPSG4326?[h.y,d.x,d.y,h.x]:[d.x,h.y,h.x,d.y]).join(",");a+=L.Util.getParamString(r,a,s.options.uppercase)+(s.options.uppercase?"&BBOX=":"&bbox=")+v}}var b=L.DomUtil.create("div",i,t),u=L.DomUtil.create("img",null,b);u.src=a,s.options&&s.options.label&&(u.title=s.options.label),L.DomEvent.on(b,"click",function(){if(this.options.basemaps.length>2&&L.Browser.mobile&&L.DomUtil.hasClass(t,"closed"))L.DomUtil.removeClass(t,"closed");else if(s!=this.basemap){e.removeLayer(this.basemap),e.addLayer(s),s.bringToBack(),e.fire("baselayerchange",s),this.basemap=s,L.DomUtil.removeClass(t.getElementsByClassName("basemap active")[0],"active"),L.DomUtil.addClass(b,"active");var a=(o+1)%this.options.basemaps.length;L.DomUtil.removeClass(t.getElementsByClassName("basemap alt")[0],"alt"),L.DomUtil.addClass(t.getElementsByClassName("basemap")[a],"alt"),L.DomUtil.addClass(t,"closed")}},this)},this),this.options.basemaps.length>2&&!L.Browser.mobile&&(L.DomEvent.on(t,"mouseenter",function(){L.DomUtil.removeClass(t,"closed")},this),L.DomEvent.on(t,"mouseleave",function(){L.DomUtil.addClass(t,"closed")},this)),this._container=t,this._container}}),L.control.basemaps=function(e){return new L.Control.Basemaps(e)}; \ No newline at end of file diff --git a/static/leaflet/L.Control.Zoominfo.css b/static/leaflet/L.Control.Zoominfo.css new file mode 100644 index 000000000..d22339432 --- /dev/null +++ b/static/leaflet/L.Control.Zoominfo.css @@ -0,0 +1,124 @@ +/** Slider **/ +.leaflet-control-zoominfo-wrap { + padding-top: 5px; + padding-bottom: 5px; + background-color: #fff; + border-bottom: 1px solid #ccc; +} +.leaflet-control-zoominfo-body { + width: 2px; + border: solid #fff; + border-width: 0px 9px 0px 9px; + background-color: black; + margin: 0 auto; +} + +.leaflet-control-zoominfo-body:hover { + cursor: pointer; +} + +.leaflet-dragging .leaflet-control-zoominfo, +.leaflet-dragging .leaflet-control-zoominfo-wrap, +.leaflet-dragging .leaflet-control-zoominfo-body, +.leaflet-dragging .leaflet-control-zoominfo a, +.leaflet-dragging .leaflet-control-zoominfo a.leaflet-control-zoominfo-disabled { + cursor: move; + cursor: -webkit-grabbing; + cursor: -moz-grabbing; +} + +/** Leaflet Zoom Styles **/ +.leaflet-container .leaflet-control-zoominfo { + margin-left: 10px; + margin-top: 10px; +} +.leaflet-control-zoominfo a { + width: 26px; + height: 26px; + text-align: center; + text-decoration: none; + color: black; + display: block; +} +.leaflet-control-zoominfo a:hover { + background-color: #f4f4f4; +} +.leaflet-control-zoominfo-in { + font: bold 18px 'Lucida Console', Monaco, monospace; +} +.leaflet-control-zoominfo-in:after{ + content:"+" +} +.leaflet-control-zoominfo-out { + font: bold 22px 'Lucida Console', Monaco, monospace; +} +.leaflet-control-zoominfo-out:after{ + content:"−" +} +.leaflet-control-zoominfo a.leaflet-control-zoominfo-disabled { + cursor: default; + color: #bbb; +} + +/* Touch */ +.leaflet-touch .leaflet-control-zoominfo-body { + background-position: 10px 0px; +} +.leaflet-touch .leaflet-control-zoominfo a { + width: 30px; + line-height: 30px; +} +.leaflet-touch .leaflet-control-zoominfo a:hover { + width: 30px; + line-height: 30px; +} +.leaflet-touch .leaflet-control-zoominfo-in { + font-size: 24px; + line-height: 29px; +} +.leaflet-touch .leaflet-control-zoominfo-out { + font-size: 28px; + line-height: 30px; +} +.leaflet-touch .leaflet-control-zoominfo { + box-shadow: none; + border: 4px solid rgba(0,0,0,0.3); +} + +.leaflet-control-zoominfo-info { + margin-left: -13px; + background-color: #fff; + border: none; + width: 22px; + height: 22px; + display: block; + text-align: center; + text-decoration: none; + color: black; + padding: 4px 4px 0px 3px; + + border-radius: 4px; + cursor: default; +} + +.leaflet-control-zoominfoinfo h4 { + margin: 0 0 0px 0px; + color: #000; +} + +/* Old IE */ + +.leaflet-oldie .leaflet-control-zoominfo-wrap { + width: 26px; +} + +.leaflet-oldie .leaflet-control-zoominfo { + border: 1px solid #999; +} + +.leaflet-oldie .leaflet-control-zoominfo-in { + *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '+'); +} +.leaflet-oldie .leaflet-control-zoominfo-out { + *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '-'); +} diff --git a/static/leaflet/L.Control.Zoominfo.js b/static/leaflet/L.Control.Zoominfo.js new file mode 100644 index 000000000..c4f8b36ce --- /dev/null +++ b/static/leaflet/L.Control.Zoominfo.js @@ -0,0 +1,138 @@ +(function (factory) { + // Packaging/modules magic dance + var L; + if (typeof define === 'function' && define.amd) { + // AMD + define(['leaflet'], factory); + } else if (typeof module !== 'undefined') { + // Node/CommonJS + L = require('leaflet'); + module.exports = factory(L); + } else { + // Browser globals + if (typeof window.L === 'undefined') { + throw new Error('Leaflet must be loaded first'); + } + factory(window.L); + } +}(function (L) { + 'use strict'; + + L.Control.Zoominfo = (function () { + + var Zoominfo = L.Control.extend({ + options: { + position: 'topleft', + styleNS: 'leaflet-control-zoominfo' + }, + + onAdd: function (map) { + this._map = map; + this._ui = this._createUI(); + + map.whenReady(this._initInfo, this) + .whenReady(this._initEvents, this) + .whenReady(this._updateInfoValue, this) + .whenReady(this._updateDisabled, this); + return this._ui.bar; + }, + + onRemove: function (map) { + map.off('zoomlevelschange', this._updateSize, this) + .off('zoomend zoomlevelschange', this._updateInfoValue, this) + .off('zoomend zoomlevelschange', this._updateDisabled, this); + }, + + _createUI: function () { + var ui = {}, + ns = this.options.styleNS; + + ui.bar = L.DomUtil.create('div', ns + ' leaflet-bar'); + ui.zoomIn = this._createZoomBtn('in', 'top', ui.bar); + ui.wrap = L.DomUtil.create('div', ns + '-wrap leaflet-bar-part', ui.bar); + ui.zoomOut = this._createZoomBtn('out', 'bottom', ui.bar); + ui.body = L.DomUtil.create('div', ns + '-body', ui.wrap); + ui.info = L.DomUtil.create('div', ns + '-info'); + + L.DomEvent.disableClickPropagation(ui.bar); + L.DomEvent.disableClickPropagation(ui.info); + + return ui; + }, + _createZoomBtn: function (zoomDir, end, container) { + var classDef = this.options.styleNS + '-' + zoomDir + + ' leaflet-bar-part' + + ' leaflet-bar-part-' + end, + link = L.DomUtil.create('a', classDef, container); + + link.href = '#'; + link.title = 'Zoom ' + zoomDir; + + L.DomEvent.on(link, 'click', L.DomEvent.preventDefault); + + return link; + }, + + _initInfo: function () { + this._ui.body.appendChild(this._ui.info); + }, + _initEvents: function () { + this._map + .on('zoomend zoomlevelschange', this._updateInfoValue, this) + .on('zoomend zoomlevelschange', this._updateDisabled, this); + + L.DomEvent.on(this._ui.zoomIn, 'click', this._zoomIn, this); + L.DomEvent.on(this._ui.zoomOut, 'click', this._zoomOut, this); + }, + + _zoomIn: function (e) { + this._map.zoomIn(e.shiftKey ? 3 : 1); + }, + _zoomOut: function (e) { + this._map.zoomOut(e.shiftKey ? 3 : 1); + }, + + _zoomLevels: function () { + var zoomLevels = this._map.getMaxZoom() - this._map.getMinZoom() + 1; + return zoomLevels < Infinity ? zoomLevels : 0; + }, + _toZoomLevel: function (value) { + return value + this._map.getMinZoom(); + }, + _toValue: function (zoomLevel) { + return zoomLevel - this._map.getMinZoom(); + }, + + _updateInfoValue: function () { + this._ui.info.innerHTML = ''+ this._map.getZoom() + ''; + }, + _updateDisabled: function () { + var zoomLevel = this._map.getZoom(), + className = this.options.styleNS + '-disabled'; + + L.DomUtil.removeClass(this._ui.zoomIn, className); + L.DomUtil.removeClass(this._ui.zoomOut, className); + + if (zoomLevel === this._map.getMinZoom()) { + L.DomUtil.addClass(this._ui.zoomOut, className); + } + if (zoomLevel === this._map.getMaxZoom()) { + L.DomUtil.addClass(this._ui.zoomIn, className); + } + } + }); + + return Zoominfo; + })(); + + L.Map.addInitHook(function () { + if (this.options.zoominfoControl) { + this.zoominfoControl = new L.Control.Zoominfo(); + this.addControl(this.zoominfoControl); + } + }); + + L.control.zoominfo = function (options) { + return new L.Control.Zoominfo(options); + }; +})); diff --git a/static/scripts/localizable.js b/static/scripts/localizable.js index f1ee71f66..e2fb389e6 100644 --- a/static/scripts/localizable.js +++ b/static/scripts/localizable.js @@ -113,14 +113,14 @@ let _localizable = { }, "nickname": { "ru": "Никнеймы игроков и название техники ", - "en": "Player nicknames and vehicle names", - "de": "Spieler- und Fahrzeugnamen", + "en": "Player, vehicle, and projectile tags", + "de": "Spieler-, Fahrzeug und Projektilnamen", "cs": "Hráčské přezdívky a názvy vozidel", "it": "Nome giocatori e veicoli" }, "markers": { "ru": "Маркеры", - "en": "Markers", + "en": "Marker names", "de": "Markierungen", "cs": "Značení", "it": "Indicatori" @@ -260,15 +260,15 @@ let _localizable = { }, "time_mission": { "ru": "Время миссии", - "en": "Mission Time Elapsed", + "en": "In-Game World Time", "de": "Verstrichene Missionszeit", "cs": "Čas mise", "it": "Orario missione" }, "time_system": { "ru": "Системное время", - "en": "System time", - "de": "Systemzeit", + "en": "Server Time UTC", + "de": "Systemzeit (UTC)", "cs": "Čas systému", "it": "Orario sistema" }, @@ -292,6 +292,20 @@ let _localizable = { "de": " hat den Hack unterbrochen", "cs": " přerušil hackování", "it": " ha interrotto l'hackeraggio" + }, + "version-extension": { + "ru": "Pасширения bерсия : ", + "en": "Extension version: ", + "de": "Erweiterungs Version: ", + "cs": "Verze rozšíření: ", + "it": "Versione estensione: " + }, + "version-addon": { + "ru": "Aддона bерсия: ", + "en": "Addon version: ", + "de": "Addon Version: ", + "cs": "Verze addonu: ", + "it": "Versione addon: " } }; let localizableElement = []; @@ -316,6 +330,8 @@ function switchLocalizable(lang) { if (item.length != 0) localizable(item[0], item[1], item[2], item[3]); }); + document.getElementById("versionInfo-extension").innerHTML += this.extensionVersion; + document.getElementById("versionInfo-addon").innerHTML += this.addonVersion; } function deleteLocalizable(elem) { var id = elem.dataset.lbId; diff --git a/static/scripts/ocap.entity.js b/static/scripts/ocap.entity.js index 22313f41c..f6521d29b 100644 --- a/static/scripts/ocap.entity.js +++ b/static/scripts/ocap.entity.js @@ -60,9 +60,9 @@ class Entity { this._element = el; } - getElement() { - return this._element; - } + // getElement() { + // return this._element; + // } getName() { return this._name; @@ -77,7 +77,7 @@ class Entity { autoPan: false, autoClose: false, closeButton: false, - className: this._popupClassName + className: this._popupClassName, }); popup.setContent(content); return popup; @@ -88,7 +88,7 @@ class Entity { icon: this._realIcon, rotationOrigin: this._markerRotationOrigin }); - this._marker.addTo(map); + this._marker.addTo(entitiesLayerGroup); } // TODO: Optimise this. No need to remove marker (and recreate it later). @@ -97,7 +97,7 @@ class Entity { removeMarker() { let marker = this._marker; if (marker != null) { - map.removeLayer(marker); + entitiesLayerGroup.removeLayer(marker); this._marker = null; this.remove(); } @@ -136,7 +136,10 @@ class Entity { let popup = this._marker.getPopup(); if (popup != null) { - popup.getElement().style.opacity = opacity; + let element = popup.getElement() + if (element) { + element.style.opacity = opacity; + } } } @@ -146,8 +149,13 @@ class Entity { if (popup == null) { return } let element = popup.getElement(); + if (element == null) { return } let display = "inherit"; - if (bool || !ui.nicknameEnable) { display = "none" } + if (this.constructor.name == 'Unit' && !this.isPlayer) { + display = "none" + } else { + if (bool || !ui.nicknameEnable) {display = "none"}; + }; if (element.style.display !== display) { element.style.display = display; diff --git a/static/scripts/ocap.event.js b/static/scripts/ocap.event.js index 94faa152d..f8e1b4bf3 100644 --- a/static/scripts/ocap.event.js +++ b/static/scripts/ocap.event.js @@ -3,18 +3,18 @@ class GameEvents { this._events = []; } - addEvent(event) { + addEvent (event) { this._events.push(event); } - init() { + init () { for (const event of this._events) { event.init(); } } // Return an array of events that occured on the given frame - getEventsAtFrame(f) { + getEventsAtFrame (f) { var events = []; this._events.forEach((event) => { if (event.frameNum == f) { @@ -25,9 +25,9 @@ class GameEvents { return events; } - getEvents() { return this._events } + getEvents () { return this._events } - getActiveEvents() { + getActiveEvents () { return this._events.filter((event) => event.frameNum <= playbackFrame); } } @@ -44,30 +44,30 @@ class GameEvent { this.objectID = objectID; } - getElement() { return this._element }; + getElement () { return this._element }; // initialize event once all events are known - init() {} + init () { } // forces an update for the next frame - forceUpdate() { + forceUpdate () { this._forceUpdate = true; } // check if update() call is needed - needsUpdate(f, onlyVisible = true) { + needsUpdate (f, onlyVisible = true) { return ((!onlyVisible || f >= this.frameNum) && this.lastFrameNumUpdate !== f) || this._forceUpdate; } // update element - update(f) { + update (f) { this.lastFrameNumUpdate = f; this._forceUpdate = false; } // update time related to frameNum - updateTime() {} + updateTime () { } // get previous event for the same objectID based given constructor - getPreviousObjectEvent(type) { + getPreviousObjectEvent (type) { const events = gameEvents.getEvents(); const thisIndex = events.indexOf(this); if (thisIndex === -1) return null; @@ -82,7 +82,7 @@ class GameEvent { } // move the camera on position - focusOnPosition(position) { + focusOnPosition (position) { entityToFollow = null; map.setView(armaToLatLng(position), map.getZoom(), { animate: true }); } @@ -125,10 +125,10 @@ class HitKilledEvent extends GameEvent { causedBySpan.className = this.causedBy.getSideClass(); switch (this.type) { case "killed": - causedBySpan.textContent = `${this.causedBy.getName()} (${causedBy.killCount - (causedBy.teamKillCount*2)} kills)`; + causedBySpan.textContent = `${this.causedBy.getName()} (${causedBy.killCount - (causedBy.teamKillCount * 2)} kills)`; break; case "hit": - causedBySpan.textContent = `${this.causedBy.getName()} (${causedBy.killCount - (causedBy.teamKillCount*2)} kills)`; + causedBySpan.textContent = `${this.causedBy.getName()} (${causedBy.killCount - (causedBy.teamKillCount * 2)} kills)`; break; } } else { @@ -178,7 +178,7 @@ class HitKilledEvent extends GameEvent { this._element = li; }; - updateTime() { + updateTime () { this.detailsDiv.textContent = ui.getTimeString(this.frameNum) + " - " + this.distance + "m - " + this.weapon; } } @@ -203,7 +203,7 @@ class ConnectEvent extends GameEvent { this._element = li; }; - updateTime() { + updateTime () { this.detailsDiv.textContent = ui.getTimeString(this.frameNum); } } @@ -263,13 +263,13 @@ class CapturedEvent extends GameEvent { } }; - update(f) { + update (f) { if (!this.needsUpdate(f, false)) return; const markerVisible = this.markerIsVisible(f); if (!this._marker && markerVisible) { const color = "#" + getPulseMarkerColor(this.unitColor); - this._marker = L.marker.pulse(armaToLatLng(this.objectPosition), {iconSize: [50,50], color: color, fillColor: 'transparent', iterationCount: 1}).addTo(map); + this._marker = L.marker.pulse(armaToLatLng(this.objectPosition), { iconSize: [50, 50], color: color, fillColor: 'transparent', iterationCount: 1 }).addTo(map); } else if (this._marker && !markerVisible) { this._marker.remove(); this._marker = null; @@ -278,13 +278,13 @@ class CapturedEvent extends GameEvent { super.update(f); } - markerIsVisible(f) { + markerIsVisible (f) { if (!this.objectPosition) return false; return f >= this.frameNum; } - updateTime() { + updateTime () { this.detailsDiv.textContent = ui.getTimeString(this.frameNum); } } @@ -341,12 +341,12 @@ class TerminalHackStartEvent extends GameEvent { } }; - update(f) { + update (f) { if (!this.needsUpdate(f, false)) return; const markerVisible = this.markerIsVisible(f); if (!this._marker && markerVisible) { - this._marker = L.marker.pulse(armaToLatLng(this.terminalPosition), {iconSize: [50,50], color: 'red', fillColor: 'transparent'}).addTo(map); + this._marker = L.marker.pulse(armaToLatLng(this.terminalPosition), { iconSize: [50, 50], color: 'red', fillColor: 'transparent' }).addTo(map); } else if (this._marker && !markerVisible) { this._marker.remove(); this._marker = null; @@ -370,7 +370,7 @@ class TerminalHackStartEvent extends GameEvent { super.update(f); } - markerIsVisible(f) { + markerIsVisible (f) { if (!this.terminalPosition) return false; if (f < this.frameNum) return false; if (this._state !== "running") return false; @@ -379,12 +379,12 @@ class TerminalHackStartEvent extends GameEvent { return secondsLeft > 0; } - updateTime() { + updateTime () { this.detailsDiv.textContent = ui.getTimeString(this.frameNum); } - getState() { return this._state; } - setState(state) { + getState () { return this._state; } + setState (state) { this._state = state; this.forceUpdate(); } @@ -430,7 +430,7 @@ class TerminalHackUpdateEvent extends GameEvent { this._element = li; } - init() { + init () { this._parent = this.getPreviousObjectEvent(TerminalHackStartEvent); if (this._parent && this._parent.terminalPosition) { this._element.classList.add("action"); @@ -440,7 +440,7 @@ class TerminalHackUpdateEvent extends GameEvent { } } - update(f) { + update (f) { if (!this.needsUpdate(f, false)) return; if (f >= this.frameNum && !this._triggered) { @@ -459,11 +459,27 @@ class TerminalHackUpdateEvent extends GameEvent { super.update(f); } - updateTime() { + updateTime () { this.detailsDiv.textContent = ui.getTimeString(this.frameNum); } } +// [20, "generalEvent", "Mission has started!"] +class generalEvent extends GameEvent { + constructor(frameNum, type, msg) { + super(frameNum, type); + this.msg = msg; + this._element = null; + + var span = document.createElement("span"); + span.className = "medium"; + span.textContent = msg; + var li = document.createElement("li"); + li.appendChild(span) + this._element = li; + }; +}; + // [4639, "endMission", ["EAST", "Offar Factory зазахвачена. Победа Сил РФ."]] class endMissionEvent extends GameEvent { constructor(frameNum, type, side, msg) { diff --git a/static/scripts/ocap.group.js b/static/scripts/ocap.group.js index 8eefa6816..3e49c66c4 100644 --- a/static/scripts/ocap.group.js +++ b/static/scripts/ocap.group.js @@ -120,6 +120,23 @@ class Group { //liGroup.addEventListener("click", function() {console.log(group.getUnits())}); this.setElement(liGroup); targetList.appendChild(liGroup); + + var elements = [].slice.call(targetList.childNodes) + while (targetList.childNodes.length > 0) { + targetList.childNodes.forEach(a => { + targetList.removeChild(a) + }) + }; + + var sortedElements = elements.sort((a, b) => { + if (a.innerText < b.innerText) return -1; + if (a.innerText > b.innerText) return 1; + return 0; + }); + + sortedElements.forEach(a => { + targetList.appendChild(a) + }); }; isEmpty() { diff --git a/static/scripts/ocap.js b/static/scripts/ocap.js index c401f27cd..d02c1face 100644 --- a/static/scripts/ocap.js +++ b/static/scripts/ocap.js @@ -56,8 +56,22 @@ var trim = 0; // Number of pixels that were trimmed when cropping image (used to var mapMinZoom = null; var mapMaxNativeZoom = null; var mapMaxZoom = null; // mapMaxNativeZoom + 3; +var topoLayer = null; +var satLayer = null; +var terrainLayer = null; +var terrainDarkLayer = null; +var contourLayer = null; +var baseLayerControl = null; +var overlayLayerControl = null; +var entitiesLayerGroup = L.layerGroup([]); +var markersLayerGroup = L.layerGroup([]); +var systemMarkersLayerGroup = L.layerGroup([]); +var projectileMarkersLayerGroup = L.layerGroup([]); var map = null; var mapDiv = null; +var mapBounds = null; +var worldObject = null; +var mapAvailable = false; var frameCaptureDelay = 1000; // Delay between capture of each frame in-game (ms). Default: 1000 var playbackMultiplier = 10; // Playback speed. 1 = realtime. var maxPlaybackMultipler = 60; // Max speed user can set playback to @@ -88,17 +102,21 @@ var followColour = "#FFA81A"; var hitColour = "#FF0000"; var deadColour = "#000000"; -const skipAnimationDistance = 100; +const skipAnimationDistance = 222; // 800 kph at 1 sec frame delay, cruise for most planes - objects changing a larger distance than this would represent will be temporarily hidden between frames because it's assumed they're teleporting let requestedFrame; function getArguments () { - let args = new Object(); - window.location.search.replace("?", "").split("&").forEach(function (s) { - let values = s.split("="); - if (values.length > 1) { - args[values[0]] = values[1].replace(/%20/g, " "); - } - }); + // let args = new Object(); + // window.location.search.replace("?", "").split("&").forEach(function (s) { + // let values = s.split("="); + // if (values.length > 1) { + // args[values[0]] = values[1].replace(/%20/g, " "); + // } + // }); + + let args = new URLSearchParams(window.location.search); + + // console.log(args); return args; } @@ -115,85 +133,219 @@ function initOCAP () { Promise.all([ui.updateCustomize(), ui.setModalOpList()]) .then(() => { - /* - window.addEventListener("keypress", function (event) { - switch (event.charCode) { - case 32: // Spacebar - event.preventDefault(); // Prevent space from scrolling page on some browsers - break; - }; - }); - */ - if (args.file) { + /* + window.addEventListener("keypress", function (event) { + switch (event.charCode) { + case 32: // Spacebar + event.preventDefault(); // Prevent space from scrolling page on some browsers + break; + }; + }); + */ + if (args.get('file')) { document.addEventListener("mapInited", function (event) { let args = getArguments(); - if (args.x && args.y && args.zoom) { - let coords = [parseFloat(args.x), parseFloat(args.y)]; - let zoom = parseFloat(args.zoom); + if (args.get('x') && args.get('y') && args.get('zoom')) { + let coords = [parseFloat(args.get('x')), parseFloat(args.get('y'))]; + let zoom = parseFloat(args.get('zoom')); map.setView(coords, zoom); - } else { - map.setView([0, 0], mapMaxNativeZoom); } - if (args.frame) { - ui.setMissionCurTime(parseInt(args.frame)); + if (args.get('frame')) { + ui.setMissionCurTime(parseInt(args.get('frame'))); } }, false); - return loadOperationByFilename(args.file); + + document.addEventListener("operationProcessed", function (event) { + let bounds = getMapMarkerBounds(); + map.fitBounds(bounds); + }); + + return loadOperationByFilename(args.get('file')); } }) .catch((error) => { ui.showHint(error); }); - if (args.experimental) ui.showExperimental(); + if (args.get('experimental')) ui.showExperimental(); } -function getWorldByName (worldName) { +async function getWorldByName (worldName) { console.log("Getting world " + worldName); - let map = {}; + let defaultMap = { "name": "NOT FOUND", + "displayName": "NOT FOUND", "worldname": "NOT FOUND", "worldSize": 16384, "imageSize": 16384, "multiplier": 1, "maxZoom": 6, "minZoom": 0, + "hasTopo": true, + "hasTopoRelief": false, + "hasTopoDark": false, + "hasColorRelief": false, + "attribution": "Bohemia Interactive and 3rd Party Developers" }; - return fetch(`images/maps/${worldName}/map.json`) - .then((res) => res.json()) - .then((data) => { - map = data; - return Object.assign(defaultMap, map); - }) - .catch(() => { - ui.showHint(`Error: Map "${worldName}" is not installed`); - }); + // Check for, and return local map data if available + const localMapRes = await fetch( + 'images/maps/' + worldName + '/map.json', + { cache: "no-store" } + ); + if (localMapRes.status === 200) { + try { + return Object.assign(defaultMap, await localMapRes.json()); + } catch (error) { + //ui.showHint(`Error: parsing local map.json`); + console.error('Error parsing local map.json', error.message || error); + } + } + + // Fallback to cloud CDN if enabled + if (ui.useCloudTiles) { + let cloudMapRes; + try { + cloudMapRes = await fetch( + `https://maps.ocap2.com/${worldName}/map.json`, + { cache: "no-store" } + ); + } catch (error) { + // clone default map if not found + Object.assign(defaultMap, { + "imageSize": 30720, + "worldSize": 30720, + "multiplier": 1, + "worldName": worldName + }); + console.warn("World not found, using blank map") + alert(`The map for this mission (worldName: ${worldName}) is not available locally or in the cloud.\n\nA placeholder will be shown instead. Please report this issue on the OCAP2 Discord.\n\nhttps://discord.gg/wQusAQnrBP`); + worldName = ""; + + return Promise.resolve(defaultMap); + }; + if (cloudMapRes.status === 200) { + try { + return Object.assign(defaultMap, await cloudMapRes.json(), { _useCloudTiles: true }); + } catch (error) { + console.error('Error parsing cloud map.json', error.message || error); + return Promise.reject(`Cloud map "${worldName}" data parsing failed.`); + } + } else { + // clone default map if not found + Object.assign(defaultMap, { + "imageSize": 30720, + "worldSize": 30720, + "multiplier": 1, + "worldName": worldName + }); + worldName = ""; + console.warn("World not found, using blank map") + alert(`The map for this mission (worldName: ${worldName}) is not available locally or in the cloud.\n\nA placeholder will be shown instead. Please report this issue on the OCAP2 Discord.\n\nhttps://discord.gg/wQusAQnrBP`); + + return Promise.resolve(defaultMap); + // return Promise.reject(`Map "${worldName}" is not available on cloud (${cloudMapRes.status})`); + } + } else { + return Promise.reject(`Map "${worldName}" is not installed`); + } } function initMap (world) { // Bad mapMaxNativeZoom = world.maxZoom - mapMaxZoom = mapMaxNativeZoom + 3 + mapMaxZoom = mapMaxNativeZoom + 2 + + imageSize = world.imageSize; + multiplier = world.multiplier; + + var factorx = multiplier; + var factory = multiplier; + // var factorx = 1; + // var factory = 1; + + L.CRS.OCAP = L.extend({}, L.CRS.Simple, { + projection: L.Projection.LonLat, + transformation: new L.Transformation(factorx, 0, -factory, 0), + // Changing the transformation is the key part, everything else is the same. + // By specifying a factor, you specify what distance in meters one pixel occupies (as it still is CRS.Simple in all other regards). + // In this case, I have a tile layer with 256px pieces, so Leaflet thinks it's only 256 meters wide. + // I know the map is supposed to be 2048x2048 meters, so I specify a factor of 0.125 to multiply in both directions. + // In the actual project, I compute all that from the gdal2tiles tilemapresources.xml, + // which gives the necessary information about tilesizes, total bounds and units-per-pixel at different levels. + + + // Scale, zoom and distance are entirely unchanged from CRS.Simple + scale: function (zoom) { + return Math.pow(2, zoom); + }, + + zoom: function (scale) { + return Math.log(scale) / Math.LN2; + }, + + distance: function (latlng1, latlng2) { + var dx = latlng2.lng - latlng1.lng, + dy = latlng2.lat - latlng1.lat; + + return Math.sqrt(dx * dx + dy * dy); + }, + infinite: true + }); + // Create map map = L.map('map', { - //maxZoom: mapMaxZoom, + center: [0, 0], + zoom: 0, + maxNativeZoom: mapMaxNativeZoom, + maxZoom: mapMaxZoom, + minNativeZoom: 0, + minZoom: 0, + // zoominfoControl: true, // moved for custom position zoomControl: false, - zoomAnimation: true, scrollWheelZoom: true, + zoomAnimation: true, fadeAnimation: true, - crs: L.CRS.Simple, - attributionControl: false, - zoomSnap: 0.1, + crs: L.CRS.OCAP, + attributionControl: true, + zoomSnap: 1, zoomDelta: 1, closePopupOnClick: false, - preferCanvas: false + preferCanvas: true }); + // Hide marker popups once below a certain zoom level map.on("zoom", function () { ui.hideMarkerPopups = map.getZoom() <= 4; + // if (map.getZoom() <= 5 && geoJsonHouses != null) { + // geoJsonHouses.setStyle(function (geoJsonFeature) { + // return { + // color: "#4D4D4D", + // interactive: false, + // fill: true, + // opacity: 0, + // fillOpacity: 0, + // noClip: true, + // // renderer: L.canvas() + // // weight: geoJsonFeature.properties.width * window.multiplier, + // }; + // }); + // } else if (geoJsonHouses != null) { + // geoJsonHouses.setStyle(function (geoJsonFeature) { + // return { + // color: "#4D4D4D", + // interactive: false, + // fill: true, + // opacity: 1, + // fillOpacity: 1, + // noClip: true, + // // renderer: L.canvas() + // // weight: geoJsonFeature.properties.width * window.multiplier, + // }; + // }); + // } }); let playbackPausedBeforeZoom; @@ -221,32 +373,256 @@ function initMap (world) { } }); - console.log("Got world: ", world); - imageSize = world.imageSize; - multiplier = world.multiplier; - let args = getArguments(); - if (!args.x || !args.y || !args.zoom) { - map.setView(map.unproject([imageSize / 2, imageSize / 2]), mapMinZoom); + // Setup tile layers + var baseLayers = []; + + entitiesLayerGroup.addTo(map); + markersLayerGroup.addTo(map); + systemMarkersLayerGroup.addTo(map); + projectileMarkersLayerGroup.addTo(map); + + + // worldName = world.worldName; + + + let topoLayerUrl = ""; + let topoDarkLayerUrl = ""; + let topoReliefLayerUrl = ""; + let colorReliefLayerUrl = ""; + + + if (worldName === "") { + console.log("World name missing or not rendered. Using default map.") + // if default map is used as placeholder, use custom topo layer url + topoLayerUrl = 'https://maps.ocap2.com/missing_tiles.png'; + } else if (Boolean(world._useCloudTiles)) { + console.log("Streaming map tiles from the cloud (maps.ocap2.com).") + topoLayerUrl = ('https://maps.ocap2.com/' + worldName.toLowerCase() + '/{z}/{x}/{y}.png'); + topoDarkLayerUrl = ('https://maps.ocap2.com/' + worldName.toLowerCase() + '/topoDark/{z}/{x}/{y}.png'); + topoReliefLayerUrl = ('https://maps.ocap2.com/' + worldName.toLowerCase() + '/topoRelief/{z}/{x}/{y}.png'); + colorReliefLayerUrl = ('https://maps.ocap2.com/' + worldName.toLowerCase() + '/colorRelief/{z}/{x}/{y}.png'); + } else { + console.log("Streaming map tiles from the local OCAP2 installation.") + topoLayerUrl = ('images/maps/' + worldName + '/{z}/{x}/{y}.png'); + topoDarkLayerUrl = ('images/maps/' + worldName + '/topoDark/{z}/{x}/{y}.png'); + topoReliefLayerUrl = ('images/maps/' + worldName + '/topoRelief/{z}/{x}/{y}.png'); + colorReliefLayerUrl = ('images/maps/' + worldName + '/colorRelief/{z}/{x}/{y}.png'); } - var mapBounds = new L.LatLngBounds( - map.unproject([0, imageSize], mapMaxNativeZoom), - map.unproject([imageSize, 0], mapMaxNativeZoom) - ); - map.fitBounds(mapBounds); + console.log("Getting bounds for layers...") + mapBounds = getMapImageBounds() + + if (world.hasTopo) { + topoLayer = L.tileLayer(topoLayerUrl, { + maxNativeZoom: world.maxZoom, + // maxZoom: mapMaxZoom, + minNativeZoom: world.minZoom, + bounds: mapBounds, + label: "Topographic", + attribution: "Map Data © " + world.attribution, + noWrap: true, + tms: false, + keepBuffer: 4, + // opacity: 0.7, + errorTileUrl: 'https://maps.ocap2.com/missing_tiles.png' + }); + baseLayers.push(topoLayer); + } - // Setup tile layer - L.tileLayer('images/maps/' + worldName + '/{z}/{x}/{y}.png', { - maxNativeZoom: mapMaxNativeZoom, - maxZoom: mapMaxZoom, - minZoom: mapMinZoom, - bounds: mapBounds, - //attribution: 'MisterGoodson', - noWrap: true, - tms: false + if (world.hasTopoDark) { + topoDarkLayer = L.tileLayer(topoDarkLayerUrl, { + maxNativeZoom: world.maxZoom, + // maxZoom: mapMaxZoom, + minNativeZoom: world.minZoom, + bounds: mapBounds, + label: "Topographic Dark", + attribution: "Map Data © " + world.attribution, + noWrap: true, + tms: false, + keepBuffer: 4, + // opacity: 0.8, + errorTileUrl: 'https://maps.ocap2.com/missing_tiles.png' + }); + baseLayers.push(topoDarkLayer); + } + + if (world.hasTopoRelief) { + topoReliefLayer = L.tileLayer(topoReliefLayerUrl, { + maxNativeZoom: world.maxZoom, + // maxZoom: mapMaxZoom, + minNativeZoom: world.minZoom, + bounds: mapBounds, + label: "Topographic Relief", + attribution: "Map Data © " + world.attribution, + noWrap: true, + tms: false, + keepBuffer: 4, + // opacity: 0.9, + errorTileUrl: 'https://maps.ocap2.com/missing_tiles.png' + }); + baseLayers.push(topoReliefLayer); + } + + if (world.hasColorRelief) { + colorReliefLayer = L.tileLayer(colorReliefLayerUrl, { + maxNativeZoom: world.maxZoom, + // maxZoom: mapMaxZoom, + minNativeZoom: world.minZoom, + bounds: mapBounds, + attribution: "Map Data © " + world.attribution, + label: "Colored Relief", + noWrap: true, + tms: false, + keepBuffer: 4, + // opacity: 1, + errorTileUrl: 'https://maps.ocap2.com/missing_tiles.png' + }); + baseLayers.push(colorReliefLayer); + } + + + // setup controls + + overlayLayerControl = L.control.layers({}, { + // overlay layers + "Units and Vehicles": entitiesLayerGroup, + "Selected Side Markers": markersLayerGroup, + "Editor/Briefing Markers": systemMarkersLayerGroup, + "Projectile Markers": projectileMarkersLayerGroup + }, { + position: 'bottomright', + collapsed: false + }); + overlayLayerControl.addTo(map); + + + baseLayerControl = L.control.basemaps({ + basemaps: baseLayers, + tileX: 2, // tile X coordinate + tileY: 6, // tile Y coordinate + tileZ: 4 // tile zoom level + }, { + position: 'bottomright', + }); + baseLayerControl.addTo(map); + + + // Add zoom control + L.control.zoominfo({ + position: 'bottomright' }).addTo(map); + + function test () { + // Add marker to map on click + map.on("click", function (e) { + // latLng, layerPoint, containerPoint, originalEvent + console.debug("latLng"); + console.debug(e.latlng); + console.debug("LayerPoint"); + console.debug(e.layerPoint); + console.debug("Projected"); + console.debug(map.project(e.latlng, mapMaxNativeZoom)); + }) + } + + + map.on("baselayerchange", (event) => { + // console.log(event.name); // Print out the new active layer + // console.log(event); + // multiplier = event.name + }); + map.on("overlayadd", (event) => { + // console.log(event.name); // Print out the new active layer + // console.log(event); + switch (event.name) { + case "Units and Vehicles": { + if (ui.hideMarkerPopups == false) { + entitiesLayerGroup.eachLayer(layer => { + layer.openPopup(); + }); + } + break; + }; + case "Selected Side Markers": { + markersLayerGroup.eachLayer(layer => { + layer.remove() + }) + markers.forEach(marker => { + if (marker._player instanceof Unit) { + marker._marker = null; + } + }) + // for (const marker of markers) { + // marker.manageFrame(playbackFrame); + // } + break; + }; + case "Editor/Briefing Markers": { + if (ui.markersEnable == true) { + systemMarkersLayerGroup.eachLayer(layer => { + layer.openPopup(); + }) + } + break; + }; + case "Projectile Markers": { + projectileMarkersLayerGroup.getLayers().forEach(layer => { + layer.remove() + }) + markers.forEach(marker => { + if (marker.isMagIcon()) { + marker._marker = null; + } + }) + break; + }; + + default: { + break; + }; + }; + }); + map.on("overlayremove", (event) => { + // console.log(event.name); // Print out the new active layer + // console.log(event); + switch (event.name) { + case "Units and Vehicles": { + // ui.hideMarkerPopups = false; + // entitiesLayerGroup.eachLayer(layer => { + // layer.openPopup(); + // }); + break; + }; + case "Selected Side Markers": { + markersLayerGroup.eachLayer(layer => { + // layer.remove() + }) + break; + }; + case "Editor/Briefing Markers": { + // systemMarkersLayerGroup.eachLayer(layer => { + // layer.openPopup(); + // }) + break; + }; + case "Projectile Markers": { + projectileMarkersLayerGroup.getLayers().forEach(layer => { + layer.remove() + }) + + break; + }; + + default: { + break; + }; + }; + }); + + + // Add keypress event listener mapDiv.addEventListener("keypress", function (event) { //console.log(event); @@ -258,20 +634,12 @@ function initMap (world) { } }); + + createInitialMarkers(); - let boundaryMarks = markers.filter(item => { - return item._type === "moduleCoverMap" - }); - if (boundaryMarks.length === 4) { - let boundaryPoints = boundaryMarks.map(item => armaToLatLng(item._positions[0][1])); - let boundaryPolygon = L.polygon(boundaryPoints, { color: "#000000", fill: false, interactive: false, noClip: true }).addTo(map); - map.flyToBounds(boundaryPolygon.getBounds()); - } else { - map.flyToBounds(map.getBounds()); - } document.dispatchEvent(new Event("mapInited")); - //test(); + // test(); } function createInitialMarkers () { @@ -284,6 +652,57 @@ function createInitialMarkers () { }); } +function getMapImageBounds () { + console.debug("Calculating map bounds from map image size"); + mapBounds = new L.LatLngBounds( + map.unproject([0, worldObject.imageSize], mapMaxNativeZoom), + map.unproject([worldObject.imageSize, 0], mapMaxNativeZoom) + ); + return mapBounds; +} + +function getMapMarkerBounds () { + + let boundaryMarks = markers.filter(item => { + return item._type === "moduleCoverMap" + }); + + if (boundaryMarks.length === 4) { + console.debug("Found boundary marks from BIS_moduleCoverMap") + let boundaryPoints = boundaryMarks.map(item => armaToLatLng(item._positions[0][1])); + let boundaryPolygon = L.polygon(boundaryPoints, { color: "#000000", fill: false, interactive: false, noClip: true }).addTo(map); + + return boundaryPolygon.getBounds(); + } + + // calculate map bounds from markers + console.debug(`Calculating map bounds from ${markers.length} markers`) + var markerBounds = L.latLngBounds() + let invalidMarkers = []; + markers.forEach(item => { + if (item._positions[0] === undefined) { + return invalidMarkers.push(item) + } + if (item._positions[0][1] === undefined) { + return invalidMarkers.push(item) + } + + // some marker positions are nested in an array, account for this + if (Array.isArray(item._positions[0][1][0])) { + return markerBounds.extend(armaToLatLng(item._positions[0][1][0])); + } else { + return markerBounds.extend(armaToLatLng(item._positions[0][1])); + }; + }); + + if (invalidMarkers.length > 0) { + console.debug(`Found ${invalidMarkers.length} potentially invalid markers, ignoring them`, invalidMarkers) + } + + + return markerBounds; +} + function defineIcons () { icons = { man: {}, @@ -319,7 +738,7 @@ function defineIcons () { let imgPathUnknown = "images/markers/unknown/"; - let imgs = ["blufor", "opfor", "ind", "civ", "unknown", "dead", "hit", "follow", "unconscious"]; + let imgs = ["blufor", "opfor", "ind", "civ", "logic", "unknown", "dead", "hit", "follow", "unconscious"]; imgs.forEach((img, i) => { icons.man[img] = L.icon({ className: "animation", iconSize: [16, 16], iconUrl: `${imgPathMan}${img}.svg` }); // icons.manMG[img] = L.icon({ className: "animation", iconSize: [16, 16], iconUrl: `${imgPathManMG}${img}.svg` }); @@ -357,10 +776,11 @@ function goFullscreen () { element.msRequestFullscreen(); } } - +// http://127.0.0.1:5000/?file=2021_08_20__21_24_FNF_TheMountain_Youre_A_Towel_V2_Destroy_EU.json&frame=87&zoom=1&x=-134.6690319189602&y=78.0822715759277 // Converts Arma coordinates [x,y] to LatLng function armaToLatLng (coords) { - const pixelCoords = [(coords[0] * multiplier) + trim, (imageSize - (coords[1] * multiplier)) + trim]; + var pixelCoords; + pixelCoords = [(coords[0] * multiplier) + trim, (imageSize - (coords[1] * multiplier)) + trim]; return map.unproject(pixelCoords, mapMaxNativeZoom); } @@ -369,69 +789,7 @@ function dateToLittleEndianString (date) { return (date.getUTCDate() + "/" + (date.getUTCMonth() + 1) + "/" + date.getUTCFullYear()); } -function test () { - // Add marker to map on click - map.on("click", function (e) { - //console.log(e.latlng); - - console.log(map.project(e.latlng, mapMaxNativeZoom)); - - brushPattern = { - color: "#FF0000", - opacity: 1, - angle: 45, - weight: 2, - spaceWeight: 6 - }; - - var brushPatternObj = new L.StripePattern(brushPattern); - brushPatternObj.addTo(map) - - shapeOptions = { - color: "#FF0000", - stroke: true, - fill: true, - fillPattern: brushPatternObj - }; - - var circleMarker; - circleMarker = L.circle(e.latlng, shapeOptions); - L.Util.setOptions(circleMarker, { radius: 20, interactive: false }); - circleMarker.addTo(map); - - - let pos = e.latlng; - let startX = pos.lat; - let startY = pos.lng; - let sizeX = 75; - let sizeY = 75; - - let pointsRaw = [ - [startX - sizeX, startY + sizeY], // top left - [startX + sizeX, startY + sizeY], // top right - [startX + sizeX, startY - sizeY], // bottom right - [startX - sizeX, startY - sizeY] // bottom left - ]; - - const sqMarker = L.polygon(pointsRaw, {noClip: true, interactive: false}); - L.Util.setOptions(sqMarker, shapeOptions); - // if (brushPattern) { - // L.Util.setOptions(sqMarker, { fillPattern: brushPatternObj, fillOpacity: 1.0}); - // }; - sqMarker.addTo(map); - - // var marker = L.circleMarker(e.latlng).addTo(map); - // marker.setRadius(5); - }); - - // var marker = L.circleMarker(armaToLatLng([2438.21, 820])).addTo(map); - // marker.setRadius(5); - - // var marker = L.circleMarker(armaToLatLng([2496.58, 5709.34])).addTo(map); - // marker.setRadius(5); -} - -function dateToTimeString(date, isUtc = false) { +function dateToTimeString (date, isUtc = false) { let hours = date.getHours(); let minutes = date.getMinutes(); let seconds = date.getSeconds(); @@ -523,22 +881,57 @@ async function loadOperationByFilename(filename) { } // Read operation JSON data and create unit objects -function processOp (filepath) { +function processOp (filepath, opRecord) { console.log("Processing operation: (" + filepath + ")..."); const time = new Date(); fileName = filepath.substr(5, filepath.length); + let data; return fetch(filepath) .then((res) => res.json()) - .then((data) => { + .then((json) => { + data = json; worldName = data.worldName.toLowerCase(); - return Promise.all([data, getWorldByName(worldName)]); + return worldName; }) - .then(([data, world]) => { - var multiplier = world.multiplier; + .then((wn) => getWorldByName(wn)) + .then((world) => { + worldObject = world; + document.dispatchEvent(new Event("worldLoaded")) + multiplier = world.multiplier; missionName = data.missionName; - ui.setMissionName(missionName); + let playedDate; + if (opRecord) { + playedDate = opRecord.date; + } else { + // try to parse from filename + // if filename has "\d__\d" format, use that + // else no date, in the event a temp file is referenced + let dateMatch = fileName.match(/^\d{4}_\d{2}_\d{2}/); + if (dateMatch) { + playedDate = dateMatch[0].replace(/_/g, "-"); + } else { + playedDate = ""; + } + } + + let worldDisplayName; + if ([undefined, "NOT FOUND"].includes(world.displayName)) { + if (world.name == "NOT FOUND") { + worldDisplayName = world.worldName + } else { + worldDisplayName = world.name + } + } else { + worldDisplayName = world.displayName + } + ui.setMissionName(`${missionName} - Recorded ${playedDate} on ${worldDisplayName}`); + + extensionVersion = data.extensionVersion; + ui.setExtensionVersion(extensionVersion); + addonVersion = data.addonVersion; + ui.setAddonVersion(addonVersion); endFrame = data.endFrame; frameCaptureDelay = data.captureDelay * 1000; ui.setMissionEndTime(endFrame); @@ -667,7 +1060,7 @@ function processOp (filepath) { markers.push(marker); } } catch (err) { - console.error(`Failed to process ${markerJSON[9]} with text "${markerJSON[1]}"\nError: ${err}`); + console.error(`Failed to process ${markerJSON[9]} with type ${markerJSON[0]} and text "${markerJSON[1]}"\nError: ${err}\nMarkerJSON: ${JSON.stringify(markerJSON, null, 2)}`) } }); } @@ -703,11 +1096,13 @@ function processOp (filepath) { } // Loop through events + var invalidHitKilledEvents = []; data.events.forEach(function (eventJSON) { var frameNum = eventJSON[0]; var type = eventJSON[1]; var gameEvent = null; + switch (true) { case (type == "killed" || type == "hit"): const causedByInfo = eventJSON[3]; @@ -728,16 +1123,22 @@ function processOp (filepath) { // TODO: Find out why victim/causedBy can sometimes be null if (causedBy == null || victim == null) { - console.warn("unknown victim/causedBy", victim, causedBy); + invalidHitKilledEvents.push({ + "reason": "null/unknown victim/causedBy", + "victim": victim, + "causedBy": causedBy, + "event": eventJSON + }); } // Incrememt kill/death count for killer/victim if (type === "killed" && (causedBy != null)) { - if (causedBy !== victim && causedBy._side === victim._side) { - causedBy.teamKillCount++; - } if (causedBy !== victim) { - causedBy.killCount++; + if (causedBy._side === victim._side) { + causedBy.teamKillCount++; + } else { + causedBy.killCount++; + } } victim.deathCount++; } @@ -789,21 +1190,45 @@ function processOp (filepath) { case (type == "endMission"): gameEvent = new endMissionEvent(frameNum, type, eventJSON[2][0], eventJSON[2][1]); break; + case (type == "generalEvent"): + gameEvent = new generalEvent(frameNum, type, eventJSON[2]); + break; } + // Add event to gameEvents list if (gameEvent != null) { gameEvents.addEvent(gameEvent); } }); + if (invalidHitKilledEvents.length > 0) { + console.warn("WARNING: " + invalidHitKilledEvents.length + " hit/killed events will use 'something' as the victim/killer. See the debug stream for a full list."); + console.debug(invalidHitKilledEvents); + } + gameEvents.init(); console.log("Finished processing operation (" + (new Date() - time) + "ms)."); + console.debug("Addon version: " + data.addonVersion); + console.debug("Extension version: " + data.extensionVersion); + console.debug("Extension build: " + data.extensionBuild); + console.debug("Total frames: " + data.endFrame); + console.debug("Total entities: " + data.entities.length); + console.debug("Total markers: " + data.Markers.length); + console.debug("Total events: " + data.events.length); + if (data.Markers.length > 50000) { + console.warn("WARNING: This mission contains more than 50,000 markers. This may cause performance issues and indicate configured or malformed marker exclusion settings in the addon."); + } + console.log("Initializing map..."); + console.debug(JSON.stringify(world, null, 2)); initMap(world); startPlaybackLoop(); toggleHitEvents(false); // playPause(); ui.hideModal(); + + // fire event + document.dispatchEvent(new Event('operationLoaded')); }).catch((error) => { ui.modalBody.innerHTML = `Error: "${filepath}" failed to load.
${error}.`; console.error(error); @@ -947,10 +1372,19 @@ function startPlaybackLoop () { } for (const marker of markers) { marker.manageFrame(playbackFrame); - if (ui.markersEnable) { - marker.hideMarkerPopup(false); - } else { - marker.hideMarkerPopup(true); + if (!marker.isMagIcon()) { + if (ui.markersEnable) { + marker.hideMarkerPopup(false); + } else { + marker.hideMarkerPopup(true); + } + } + if (marker.isMagIcon()) { + if (ui.nicknameEnable) { + marker.hideMarkerPopup(false); + } else { + marker.hideMarkerPopup(true); + } } } @@ -993,7 +1427,7 @@ function startPlaybackLoop () { var playbackTimeout = setTimeout(playbackFunction, frameCaptureDelay / playbackMultiplier); } -function colorElement(element, color) { +function colorElement (element, color) { if (!color) { return; } @@ -1011,7 +1445,7 @@ function colorElement(element, color) { } } -function getMarkerColor(color, defaultColor = "ffffff") { +function getMarkerColor (color, defaultColor = "ffffff") { let hexColor = defaultColor; if (!color) { return hexColor; @@ -1033,12 +1467,12 @@ function getMarkerColor(color, defaultColor = "ffffff") { return hexColor; } -function colorMarkerIcon(element, icon, color) { +function colorMarkerIcon (element, icon, color) { element.src = `/images/markers/${icon}/${getMarkerColor(color)}.png`; } -function getPulseMarkerColor(color, defaultColor = "000000") { +function getPulseMarkerColor (color, defaultColor = "000000") { let hexColor = defaultColor; if (!color) { return hexColor; @@ -1067,7 +1501,7 @@ String.prototype.encodeHTMLEntities = function () { }); } -function closestEquivalentAngle(from, to) { +function closestEquivalentAngle (from, to) { const delta = ((((to - from) % 360) + 540) % 360) - 180; return from + delta; } @@ -1344,6 +1778,7 @@ async function processOpStreaming(operationId, format = 'protobuf') { // Get world info and init map const world = await getWorldByName(worldName); + worldObject = world; // Set global for getMapImageBounds() initMap(world); ui.updateLoadingProgress(4, 4, 'Starting playback...'); diff --git a/static/scripts/ocap.marker.js b/static/scripts/ocap.marker.js index c36f9bb1b..f9aec03df 100644 --- a/static/scripts/ocap.marker.js +++ b/static/scripts/ocap.marker.js @@ -65,22 +65,7 @@ class Marker { if (!(undefined === brush && undefined === shape)) { this._brush = brush; - - var brushPattern; - if (["Cross", "Grid", "DiagGrid"].includes(brush)) { - // var patternShape = new L.PatternPath({ d: 'M10 0 L7 20 L25 20 Z', fill: true }); - // brushPattern = new L.Pattern({ - // patternUnits: "objectBoundingBox", - // patternContentUnits: "objectBoundingBox", - // color: this._color, - // opacity: 1 - // }); - // brushPattern.addShape(patternShape); - brushPattern = new L.StripePattern({ renderer: L.svg() }); - } else if (["Horizontal", "Vertical", "FDiagonal", "BDiagonal"].includes(brush)) { - brushPattern = new L.StripePattern({ renderer: L.svg() }); - } - this._brushPattern = brushPattern; + this._brushPattern = null; this._brushPatternOptions = null; switch (brush) { case "solid": @@ -113,7 +98,7 @@ class Marker { color: this._color, stroke: false, fill: true, - fillOpacity: 1 + fillOpacity: 0.2 }; break; case "vertical": @@ -128,7 +113,7 @@ class Marker { color: this._color, stroke: false, fill: true, - fillOpacity: 1 + fillOpacity: 0.2 }; break; case "grid": @@ -144,7 +129,7 @@ class Marker { color: this._color, stroke: false, fill: true, - fillOpacity: 1 + fillOpacity: 0.2 }; break; case "fdiagonal": @@ -160,7 +145,7 @@ class Marker { color: this._color, stroke: false, fill: true, - fillOpacity: 1 + fillOpacity: 0.2 }; break; case "bdiagonal": @@ -176,7 +161,7 @@ class Marker { color: this._color, stroke: false, fill: true, - fillOpacity: 1 + fillOpacity: 0.2 }; break; case "diaggrid": @@ -186,13 +171,14 @@ class Marker { opacity: 0.8, angle: 45, weight: 1, - spaceWeight: 1 + spaceWeight: 3, + spaceOpacity: 0.0 }; this._shapeOptions = { color: this._color, stroke: false, fill: true, - fillOpacity: 1, + fillOpacity: 0.2, }; break; case "cross": @@ -208,7 +194,7 @@ class Marker { color: this._color, stroke: false, fill: true, - fillOpacity: 1 + fillOpacity: 0.2 }; break; case "border": @@ -232,6 +218,11 @@ class Marker { default: break; } + + // Create stripe pattern if brush options were set + if (this._brushPatternOptions) { + this._brushPattern = new L.StripePattern(this._brushPatternOptions); + } } else { this._shapeOptions = { color: this._color, @@ -247,7 +238,7 @@ class Marker { this._systemMarkers = ["ObjectMarker", "moduleCoverMap", "safeStart"]; } - updateRender(f) { + updateRender (f) { if (this._shape === "RECTANGLE") { const frameIndex = this._markerOnFrame(f); if (frameIndex >= 0 && (this._side === ui.currentSide || this._side === "GLOBAL")) { @@ -256,12 +247,21 @@ class Marker { } } + removeMarker () { + let marker = this._marker; + if (marker != null) { + marker.remove(); + this._marker = null; + } + } + manageFrame (f) { const frameIndex = this._markerOnFrame(f); if (frameIndex != null && (this._side === ui.currentSide || this._side === "GLOBAL")) { this._updateAtFrame(frameIndex); } else { - this.hide(); + // this.hide(); + this.removeMarker(); } } @@ -273,7 +273,7 @@ class Marker { let alpha = frameData[3]; if (this._shape === "RECTANGLE" && Array.isArray(pos[0])) { - console.warn("wrong RECTANGLE positions, converting to POLYLINE"); + console.debug("wrong RECTANGLE positions, converting to POLYLINE"); this._shape = "POLYLINE"; } @@ -368,14 +368,18 @@ class Marker { // check if update is needed let variance = 0; - let curMarkerCenter = this._marker.getCenter(); - variance = variance + Math.abs((Math.abs(curMarkerCenter.lat) - Math.abs(latLng.lat))); - variance = variance + Math.abs((Math.abs(curMarkerCenter.lng) - Math.abs(latLng.lng))); + try { + curMarkerCenter = this._marker.getCenter(); variance = variance + Math.abs((Math.abs(curMarkerCenter.lat) - Math.abs(latLng.lat))); + variance = variance + Math.abs((Math.abs(curMarkerCenter.lng) - Math.abs(latLng.lng))); + + // if (variance > 5) { + // process rotation around center + let pointsRotate = this._rotatePoints(armaToLatLng(pos), points, dir); + this._marker.setLatLngs(pointsRotate).redraw(); + } catch { + // If the layer is hidden, this will error because _marker.getCenter() will fail, but that's fine, we don't need to update it if it's hidden + }; - // if (variance > 5) { - // process rotation around center - let pointsRotate = this._rotatePoints(armaToLatLng(pos), points, dir); - this._marker.setLatLngs(pointsRotate).redraw(); // }; } else if (this._shape === "POLYLINE") { if (alpha === undefined || alpha === null) { alpha = 1 } @@ -405,10 +409,23 @@ class Marker { return res } + isMagIcon () { + if ( + // projectiles + ( + this._type.search("magIcons") > -1 || + this._type === "Minefield" || + this._type === "mil_triangle" + ) && + this._side === "GLOBAL" + ) { return true } else { return false }; + } + hide () { // if (this._isShow == true) { this._isShow = false; this.setMarkerOpacity(0); + this.hideMarkerPopup(true); // }; } @@ -429,20 +446,24 @@ class Marker { let marker; let popupText = ""; - if ((this._player === -1 || this._player === false) && this._shape === "ICON") { // objNull passed, no owner. system marker with basic popup let markerCustomText = ""; if (this._text) { markerCustomText = this._text.encodeHTMLEntities(); } - marker = L.marker(latLng, { icon: this._icon, interactive: false, rotationOrigin: "50% 50%" }).addTo(map); - let popup = this._createPopup(markerCustomText); - marker.bindPopup(popup).openPopup(); + + marker = L.marker(latLng, { icon: this._icon, interactive: false, rotationOrigin: "50% 50%" }) + marker.addTo(systemMarkersLayerGroup); + + if (markerCustomText != "") { + let popup = this._createPopup(markerCustomText); + marker.bindPopup(popup).openPopup(); + }; // Set direction marker.setRotationAngle(dir); - } else if (this._shape === "ICON") { + } else if (this._player instanceof Unit && this._shape === "ICON") { let interactiveVal = false; let markerCustomText = ""; @@ -478,7 +499,22 @@ class Marker { popupText = `${this._side} ${this._player.getName().encodeHTMLEntities()} ${markerCustomText}`; } - marker = L.marker(latLng, { icon: this._icon, interactive: interactiveVal, rotationOrigin: "50% 50%" }).addTo(map); + marker = L.marker(latLng, { icon: this._icon, interactive: interactiveVal, rotationOrigin: "50% 50%" }) + if ( + // projectiles + ( + this._type.search("magIcons") > -1 || + this._type === "Minefield" || + this._type === "mil_triangle" + ) && + this._side === "GLOBAL" + ) { + marker.addTo(projectileMarkersLayerGroup); + } else if (this._player instanceof Unit) { + marker.addTo(markersLayerGroup); + } else { + marker.addTo(systemMarkersLayerGroup); + } let popup = this._createPopup(popupText); marker.bindPopup(popup).openPopup(); @@ -490,28 +526,33 @@ class Marker { let rad = this._size[0] * 0.015 * window.multiplier; if (this._brushPattern) { - L.Util.setOptions(this._brushPattern, this._brushPatternOptions); this._brushPattern.addTo(map); marker = L.circle(latLng, { radius: rad, noClip: false, interactive: false, fillPattern: this._brushPattern }); L.Util.setOptions(marker, this._shapeOptions); } else { - marker = L.circle(latLng, { radius: rad, noClip: false, interactive: false/* , renderer: L.canvas() */ }); + marker = L.circle(latLng, { radius: rad, noClip: false, interactive: false }); L.Util.setOptions(marker, this._shapeOptions); } - marker.addTo(map); + marker.addTo(systemMarkersLayerGroup); } else if (this._shape === "RECTANGLE") { if (this._brushPattern) { - L.Util.setOptions(this._brushPattern, this._brushPatternOptions); this._brushPattern.addTo(map); marker = L.polygon(latLng, { noClip: false, interactive: false, fillPattern: this._brushPattern }); L.Util.setOptions(marker, this._shapeOptions); } else { - marker = L.polygon(latLng, { noClip: false, interactive: false/* , renderer: L.canvas() */ }); + marker = L.polygon(latLng, { noClip: false, interactive: false }); L.Util.setOptions(marker, this._shapeOptions); } - marker.addTo(map); + + marker.addTo(systemMarkersLayerGroup); } else if (this._shape === "POLYLINE") { - marker = L.polyline(latLng, { color: this._color, opacity: 1, noClip: true, lineCap: 'butt', lineJoin: 'round', interactive: false }).addTo(map); + marker = L.polyline(latLng, { color: this._color, opacity: 1, noClip: true, lineCap: 'butt', lineJoin: 'round', interactive: false }) + + if (this._player === -1 || this._player === false) { + marker.addTo(systemMarkersLayerGroup) + } else { + marker.addTo(markersLayerGroup); + } } this._marker = marker; @@ -579,10 +620,10 @@ class Marker { } if (this._shape == "ICON") { this._marker.setOpacity(opacity); - let popup = this._marker.getPopup(); - if (popup != null) { - popup.getElement().style.opacity = opacity; - } + // let popup = this._marker.getPopup(); + // if (popup != null) { + // if (opacity > 0) { popup.openPopup() } else { this.hideMarkerPopup(true) }; + // } } else if (this._shape == "ELLIPSE") { this._marker.setStyle({ opacity: strokeOpacity, fillOpacity: fillOpacity }); } else if (this._shape == "RECTANGLE") { @@ -594,17 +635,19 @@ class Marker { } hideMarkerPopup (bool) { - if (this._marker != null) { - let popup = this._marker.getPopup(); - if (popup == null) { return } + if (!this._marker) return; + let popup = this._marker.getPopup(); + if (popup == null) { return } - let element = popup.getElement(); + let element = popup.getElement(); + if (element) { let display = "inherit"; if (bool) { display = "none" } - if (element.style.display != display) { + if (element.style.display !== display) { element.style.display = display; } } + return true; } } diff --git a/static/scripts/ocap.ui.js b/static/scripts/ocap.ui.js index 335fa7e4c..ecefd6f2c 100644 --- a/static/scripts/ocap.ui.js +++ b/static/scripts/ocap.ui.js @@ -23,6 +23,8 @@ class UI { this.statsDialog = null; this.statsDialogBody = null; this.missionName = null; + this.extensionVersion = null; + this.addonVersion = null; //this.loadOpButton = null; this.playPauseButton = null; this.playbackSpeedSliderContainer = null; @@ -62,11 +64,12 @@ class UI { this.elapsedTime = null; this.disableKillCount = false; + this.useCloudTiles = true; this._init(); }; - _init() { + _init () { // Setup top panel this.missionName = document.getElementById("missionName"); @@ -130,7 +133,7 @@ class UI { // Change time view this.toggleTime = document.getElementById("toggleTime"); - for (const timeValue of ["elapsed","mission","system"]) { + for (const timeValue of ["elapsed", "mission", "system"]) { const option = document.createElement("option"); option.value = timeValue; localizable(option, `time_${timeValue}`, "", ""); @@ -249,8 +252,8 @@ class UI { // Hide/show ui on keypress var left_title = document.getElementById("leftPanel").getElementsByClassName("title")[0]; var right_title = document.getElementById("rightPanel").getElementsByClassName("title")[0]; - left_title.onclick = () => {this.toggleLeftPanel()}; - right_title.onclick = () => {this.toggleRightPanel()}; + left_title.onclick = () => { this.toggleLeftPanel() }; + right_title.onclick = () => { this.toggleRightPanel() }; mapDiv.addEventListener("keypress", (event) => { if (event.charCode === 101) this.toggleLeftPanel(); else if (event.charCode === 114) this.toggleRightPanel(); @@ -306,7 +309,7 @@ class UI { this.frameSliderWidthInPercent = (this.frameSlider.offsetWidth / this.frameSlider.parentElement.offsetWidth) * 100; }; - checkAvailableTimes() { + checkAvailableTimes () { for (const option of this.toggleTime.options) { if (option.value === "system") { option.disabled = !this.systemTime; @@ -316,7 +319,7 @@ class UI { } } - getTimeString(frame) { + getTimeString (frame) { let date = new Date(frame * frameCaptureDelay); let isUTC = true; if (this.timeToShow === "system") { @@ -329,7 +332,7 @@ class UI { return dateToTimeString(date, isUTC); } - showCursorTooltip(text) { + showCursorTooltip (text) { let tooltip = this.cursorTooltip; tooltip.textContent = text; tooltip.className = "cursorTooltip"; @@ -347,15 +350,23 @@ class UI { console.log(this.cursorTooltip); } - _moveCursorTooltip(event) { + _moveCursorTooltip (event) { ui.cursorTooltip.style.transform = `translate3d(${event.pageX}px, ${event.pageY}px, 0px)`; } - setMissionName(name) { + setMissionName (name) { this.missionName.textContent = name; } - detectTimes(times) { + setAddonVersion (version) { + this.addonVersion = version; + } + + setExtensionVersion (version) { + this.extensionVersion = version; + } + + detectTimes (times) { for (const time of times) { if (time.frameNum === 0) { this.systemTime = new Date(time.systemTimeUTC + "Z"); @@ -379,8 +390,8 @@ class UI { // Set mission time based on given frame // Move playback + slider to given frame in time - setMissionCurTime(f) { - missionCurDate.setTime(f*frameCaptureDelay); + setMissionCurTime (f) { + missionCurDate.setTime(f * frameCaptureDelay); this.updateCurrentTime(f); this.setFrameSliderVal(f); playbackFrame = +f; @@ -390,17 +401,17 @@ class UI { } } - updateCurrentTime(f = playbackFrame) { + updateCurrentTime (f = playbackFrame) { this.missionCurTime.textContent = ui.getTimeString(f); } - setMissionEndTime(f) { + setMissionEndTime (f) { this.updateEndTime(f); this.setFrameSliderMax(f); } - updateEndTime(f = this.frameSlider.max) { - let date = new Date(f*frameCaptureDelay); + updateEndTime (f = this.frameSlider.max) { + let date = new Date(f * frameCaptureDelay); let isUTC = true; if (this.systemTime && this.timeToShow === "system") { date = new Date(this.systemTime.getTime() + (this.frameSlider.max * frameCaptureDelay)); @@ -412,30 +423,34 @@ class UI { this.missionEndTime.textContent = dateToTimeString(date, isUTC); } - setFrameSliderMax(f) { + setFrameSliderMax (f) { this.frameSlider.max = f; }; - setFrameSliderVal(f) { + setFrameSliderVal (f) { this.frameSlider.value = f; }; - toggleLeftPanel() { + toggleLeftPanel () { if (this.leftPanel.style.display == "none") { this.leftPanel.style.display = "initial"; + // adjust customize logo to accomodate left panel + this.customizeLogo ? this.customizeLogo.style.left = "375px" : null; } else { this.leftPanel.style.display = "none"; + // adjust customize logo to accomodate left panel + this.customizeLogo ? this.customizeLogo.style.left = "15px" : null; } }; - updateTitleSide() { + updateTitleSide () { sideCiv.textContent = "CIV\n\r(" + countCiv + ")"; sideEast.textContent = "OPFOR\n\r(" + countEast + ")"; sideGuer.textContent = "IND\n\r(" + countGuer + ")"; sideWest.textContent = "BLUFOR\n\r(" + countWest + ")"; }; - switchSide(side) { + switchSide (side) { this.currentSide = side; this.sideEast.style.backgroundColor = "rgba(255, 183, 38,0.1)"; this.listEast.style.display = "none"; @@ -460,7 +475,7 @@ class UI { } }; - toggleRightPanel() { + toggleRightPanel () { if (this.rightPanel.style.display == "none") { this.rightPanel.style.display = "initial"; } else { @@ -468,32 +483,32 @@ class UI { } }; - showModalOpSelection() { + showModalOpSelection () { // Set header/body localizable(this.modalHeader, "select_mission"); localizable(this.modalBody, "list_compilation"); // Add buttons -/* var playButton = document.createElement("div"); - playButton.className = "modalButton"; - playButton.textContent = "Play"; - var cancelButton = document.createElement("div"); - cancelButton.className = "modalButton"; - cancelButton.textContent = "Cancel"; - var hideModal = this.hideModal; - cancelButton.addEventListener("click", function() { - this.hideModal(); - }); - - this.modalButtons.appendChild(cancelButton); - this.modalButtons.appendChild(playButton);*/ + /* var playButton = document.createElement("div"); + playButton.className = "modalButton"; + playButton.textContent = "Play"; + var cancelButton = document.createElement("div"); + cancelButton.className = "modalButton"; + cancelButton.textContent = "Cancel"; + var hideModal = this.hideModal; + cancelButton.addEventListener("click", function() { + this.hideModal(); + }); + + this.modalButtons.appendChild(cancelButton); + this.modalButtons.appendChild(playButton);*/ // Show modal this.showModal(); this.modalFilter.style.display = "inherit"; }; - setModalOpList() { + setModalOpList () { var OpList; var n = filterTagGameInput.options.selectedIndex; var tag = n != -1 ? filterTagGameInput.options[n].value : ""; @@ -533,7 +548,7 @@ class UI { var headerRow = document.createElement("tr"); var columnNames = ["mission", "map", "data", "durability", "tag"]; - columnNames.forEach(function(name) { + columnNames.forEach(function (name) { var th = document.createElement("th"); localizable(th, name); th.className = "medium"; @@ -552,7 +567,7 @@ class UI { secondsToTimeString(op.mission_duration), op.tag ]; - vals.forEach(function(val) { + vals.forEach(function (val) { var cell = document.createElement("td"); cell.textContent = val; row.appendChild(cell); @@ -569,7 +584,7 @@ class UI { }); }; - makeModalButton(text, func) { + makeModalButton (text, func) { var button = document.createElement("div"); button.className = "modalButton"; button.textContent = text; @@ -578,7 +593,7 @@ class UI { return button; }; - showModalStats() { + showModalStats () { // localizable(this.statsDialogHeader, "info"); const units = []; @@ -591,7 +606,9 @@ class UI { if (unit) { unit.killCount += entity.killCount; unit.teamKillCount += entity.teamKillCount; - unit.deathCount += entity.deathCount; + if (entity.deathCount > 0) { + unit.deathCount += entity.deathCount - 1; + } } else { units.push({ name: entity._name, @@ -602,7 +619,7 @@ class UI { } } } - units.sort((a,b) => { + units.sort((a, b) => { if (a.name < b.name) return -1; if (a.name > b.name) return 1; return 0; @@ -645,13 +662,15 @@ class UI { this.statsDialog.classList.remove("closed"); }; - showModalAbout() { + showModalAbout () { localizable(this.modalHeader, "info"); this.modalBody.innerHTML = ` OCAP

Operation Capture And Playback

- GitHub Link + GitHub Link
+
+



@@ -663,25 +682,29 @@ class UI { - + `; + localizable(document.getElementById("versionInfo-extension"), "version-extension"); + document.getElementById("versionInfo-extension").innerHTML += this.extensionVersion; + localizable(document.getElementById("versionInfo-addon"), "version-addon"); + document.getElementById("versionInfo-addon").innerHTML += this.addonVersion; localizable(document.getElementById("keyControl-playPause"), "play-pause"); localizable(document.getElementById("keyControl-leftPanel"), "show-hide-left-panel"); localizable(document.getElementById("keyControl-rightPanel"), "show-hide-right-panel"); localizable(document.getElementById("keyControl-experimental"), "show-experimental"); localizable(document.getElementById("keyControl-lang"), "language"); - document.getElementById("switchLang").onchange = function(){switchLocalizable(this.value)}; + document.getElementById("switchLang").onchange = function () { switchLocalizable(this.value) }; deleteLocalizable(this.modalBody); this.modalButtons.innerHTML = ""; - this.modalButtons.appendChild(this.makeModalButton("Close", function() { + this.modalButtons.appendChild(this.makeModalButton("Close", function () { ui.hideModal(); })); this.showModal(); }; - showModalShare() { + showModalShare () { this.modal.wasStopped = false; if (!playbackPaused) { this.modal.wasStopped = true; @@ -700,12 +723,12 @@ class UI { let line = document.getElementById("ShareLink"); line.value = text; - line.addEventListener("click", function(event) { + line.addEventListener("click", function (event) { this.select(); }); this.modalButtons.innerHTML = ""; - this.modalButtons.appendChild(this.makeModalButton(getLocalizable("close"), function() { + this.modalButtons.appendChild(this.makeModalButton(getLocalizable("close"), function () { ui.hideModal(); if (ui.modal.wasStopped) { playPause(); @@ -715,11 +738,11 @@ class UI { this.showModal(); }; - showModal() { + showModal () { this.modal.style.display = "inherit"; }; - hideModal() { + hideModal () { this.modal.style.display = "none"; this.modalFilter.style.display = "none"; }; @@ -732,11 +755,11 @@ class UI { this.playbackSpeedSlider.style.display = "inherit"; }; - hidePlaybackSpeedSlider() { + hidePlaybackSpeedSlider () { this.playbackSpeedSlider.style.display = "none"; }; - removeEvent(event) { + removeEvent (event) { var el = event.getElement(); el.classList.remove("reveal"); @@ -746,7 +769,7 @@ class UI { } }; - addEvent(event) { + addEvent (event) { if (typeof event.updateTime === "function") { event.updateTime(); } @@ -767,24 +790,24 @@ class UI { } } -/* if (event.type == "hit") { - if (this.showHitEvents) { - el.style.display = "inherit"; - } else { - el.style.display = "none"; - }; - };*/ + /* if (event.type == "hit") { + if (this.showHitEvents) { + el.style.display = "inherit"; + } else { + el.style.display = "none"; + }; + };*/ this.filterEvent(event); } - updateEventTimes() { + updateEventTimes () { for (const event of gameEvents.getActiveEvents()) { event.updateTime(); } } - showHint(text) { + showHint (text) { this.hint.textContent = text; this.hint.style.display = "inherit"; @@ -793,7 +816,7 @@ class UI { }, 5000); }; - addTickToTimeline(frameNum) { + addTickToTimeline (frameNum) { var frameWidth = this.frameSliderWidthInPercent / endFrame; var tick = document.createElement("div"); @@ -803,7 +826,7 @@ class UI { this.eventTimeline.appendChild(tick); }; - filterEvent(event) { + filterEvent (event) { var el = event.getElement(); var filterText = this.filterEventsInput.value.toLowerCase(); @@ -819,7 +842,7 @@ class UI { el.style.display = "none"; } else if (el.innerHTML.toLowerCase().includes(filterText)) { el.style.display = "inherit"; - //console.log("Matches filter (" + filterText + ")"); + //console.log("Matches filter (" + filterText + ")"); } else { el.style.display = "none"; } @@ -848,13 +871,16 @@ class UI { } else { container.prepend(logo); } + + this.customizeLogo = logo; } this.disableKillCount = data.disableKillCount; + // this.useCloudTiles = data.useCloudTiles; }); } - showExperimental() { + showExperimental () { this.statsButton.classList.remove("hiddenExperimental"); const container = document.getElementById("container"); container.classList.add("marker-transition"); diff --git a/static/scripts/ocap.unit.js b/static/scripts/ocap.unit.js index 7db82d432..61ac9c50a 100644 --- a/static/scripts/ocap.unit.js +++ b/static/scripts/ocap.unit.js @@ -45,6 +45,10 @@ class Unit extends Entity { sideClass = "unknown"; sideColour = "#b29900"; break; + case "LOGIC": + sideClass = "unknown"; + sideColour = "#C3B1E1"; + break; } this._sideClass = sideClass; @@ -54,7 +58,7 @@ class Unit extends Entity { this._markerRotationOrigin = "50% 60%"; } - updateName(position) { + updateName (position) { let content = ""; let name = position.name; if (position.isPlayer === 0) { @@ -62,12 +66,13 @@ class Unit extends Entity { } if (this._name !== name) { this._name = name; - this._marker.getPopup()._contentNode.textContent = name; + var popupNode = this._marker.getPopup()._contentNode + if (popupNode) { popupNode.textContent = name; } this.updateElementText(); } } - updateElementText() { + updateElementText () { let text = ""; if (this._role) { text = `(${this._role}) ${this._name}`; @@ -80,7 +85,7 @@ class Unit extends Entity { this._element.textContent = text; } - createMarker(latLng) { + createMarker (latLng) { super.createMarker(latLng); // Only create a nametag label (popup) for players @@ -93,14 +98,14 @@ class Unit extends Entity { this._marker.bindPopup(popup).openPopup(); } - _updateAtFrame(relativeFrameIndex) { + _updateAtFrame (relativeFrameIndex) { super._updateAtFrame(relativeFrameIndex); this.setIsInVehicle(this._positions[relativeFrameIndex].isInVehicle); this.addCountList(this); this.updateName(this._positions[relativeFrameIndex]); } - setIsInVehicle(isInVehicle) { + setIsInVehicle (isInVehicle) { this._isInVehicle = isInVehicle; if (isInVehicle) { @@ -148,7 +153,7 @@ class Unit extends Entity { // Check if unit fired on given frame // If true, return position of projectile impact - firedOnFrame(f) { + firedOnFrame (f) { for (let i = 0; i < (this._framesFired.length - 1); i++) { let frameNum = this._framesFired[i][0]; let projectilePos = this._framesFired[i][1]; @@ -157,16 +162,16 @@ class Unit extends Entity { return; } - remove() { + remove () { super.remove(); this._group.removeUnit(this); } - getSide() { + getSide () { return this._side; } - makeElement(liTarget) { // Make and add element to UI target list + makeElement (liTarget) { // Make and add element to UI target list let liUnit = document.createElement("li"); liUnit.className = "liUnit"; liUnit.addEventListener("click", () => { @@ -181,11 +186,11 @@ class Unit extends Entity { liTarget.appendChild(liUnit); } - getSideColour() { return this._sideColour } + getSideColour () { return this._sideColour } - getSideClass() { return this._sideClass } + getSideClass () { return this._sideClass } - setAlive(alive) { + setAlive (alive) { super.setAlive(alive); this._group.addUnit(this); switch (alive) { @@ -193,7 +198,7 @@ class Unit extends Entity { this._element.style.opacity = 0.4; break; case 1: - this._element.style.opacity = 1; + this._element.style.opacity = 1; break; case 2: this._element.style.opacity = 0.8; @@ -203,21 +208,21 @@ class Unit extends Entity { } } - addCountList(unit) { + addCountList (unit) { let side = unit._side; - if (unit._alive != 1) {return} + if (unit._alive != 1) { return } switch (side) { case "WEST": - countWest ++; + countWest++; break; case "EAST": - countEast ++; + countEast++; break; case "GUER": - countGuer ++; + countGuer++; break; case "CIV": - countCiv ++; + countCiv++; break; default: break; diff --git a/static/scripts/ocap.vehicle.js b/static/scripts/ocap.vehicle.js index 9c39b7cb3..733249d26 100644 --- a/static/scripts/ocap.vehicle.js +++ b/static/scripts/ocap.vehicle.js @@ -100,7 +100,7 @@ class Vehicle extends Entity { let content = ""; if (ui.nicknameEnable) { this._crew = crew; - //this._marker.getPopup().setContent(`Test`); // Very slow (no need to recalc layout), use ._content instead + // this._marker.getPopup().setContent(`Test`); // Very slow (no need to recalc layout), use ._content instead let crewLength = crew.length; content = `${this._name.encodeHTMLEntities()} (0)`; @@ -125,11 +125,16 @@ class Vehicle extends Entity { } } } - let popupNode = this._marker.getPopup()._contentNode; - - if (popupNode.innerHTML !== content) { - popupNode.innerHTML = content; + let popup = this._marker.getPopup(); + if (popup) { + // let popupContent = popup._content; + + // if (popupContent !== content) { + // popupContent = content; + // } + popup.setContent(content); } + } getCrew() { @@ -145,9 +150,9 @@ class Vehicle extends Entity { let unit = entities.getById(unitId); // Only include player names - if (unit.isPlayer) { + // if (unit.isPlayer) { str += (unit.getName().encodeHTMLEntities() + "
"); - } + // } //}; }); return str; diff --git a/static/style/index.css b/static/style/index.css index 200d7620f..1c9e52635 100644 --- a/static/style/index.css +++ b/static/style/index.css @@ -1,8 +1,10 @@ -html, body, #map { - width:100%; - height:100%; - margin:0; - padding:0; +html, +body, +#map { + width: 100%; + height: 100%; + margin: 0; + padding: 0; font-family: Roboto, Arial, sans-serif; color: #F2F2F2; overflow: hidden; @@ -19,8 +21,8 @@ html, body, #map { .customize.logo { position: absolute; - bottom: 65px; - right: 5px; + bottom: 85px; + left: 375px; z-index: 1; opacity: 0.8; } @@ -54,6 +56,7 @@ html, body, #map { background-repeat: no-repeat; float: right; } + #aboutButton { font-size: 30px; position: relative; @@ -63,6 +66,7 @@ html, body, #map { float: right; margin: 12px; } + #statsButton { font-size: 30px; position: relative; @@ -103,7 +107,8 @@ html, body, #map { display: inline-block; } -#leftPanel .panelContent, #rightPanel .panelContent { +#leftPanel .panelContent, +#rightPanel .panelContent { overflow-y: auto; overflow-x: auto; height: calc(100% - 10px - 10px - 50px - 40px); @@ -113,14 +118,17 @@ html, body, #map { #controlSide { height: 50px; width: 350px; - background-color: rgba(0,0,0,0.5); + background-color: rgba(0, 0, 0, 0.5); } -#sideWest, #sideEast, #sideGuer, #sideCiv { - display : inline-block; - width : calc(25% - 3px); - height : 50px; - cursor : pointer; +#sideWest, +#sideEast, +#sideGuer, +#sideCiv { + display: inline-block; + width: calc(25% - 3px); + height: 50px; + cursor: pointer; text-align: center; padding-top: 13px; vertical-align: top; @@ -130,8 +138,9 @@ html, body, #map { height: calc(100% - 40px - 60px); } -#leftPanel, #rightPanel { - background-color: rgba(0,0,0,0.75); +#leftPanel, +#rightPanel { + background-color: rgba(0, 0, 0, 0.75); position: absolute; top: 40px; font-size: 14px; @@ -149,8 +158,9 @@ html, body, #map { height: 50%; } -#leftPanel .title, #rightPanel .title { - background-color: rgba(0,0,0,0.5); +#leftPanel .title, +#rightPanel .title { + background-color: rgba(0, 0, 0, 0.5); width: 100%; height: 30px; text-align: center; @@ -159,13 +169,14 @@ html, body, #map { } .filterBox { - background-color: rgba(0,0,0,0.25); + background-color: rgba(0, 0, 0, 0.25); width: 100%; height: 25px; padding-top: 4px; } -.filterConnect, .filterHit { +.filterConnect, +.filterHit { float: left; display: inline-block; height: 18px; @@ -183,7 +194,8 @@ html, body, #map { margin-left: 5px; } -#filterEventsInput, #filterUnitsInput { +#filterEventsInput, +#filterUnitsInput { float: left; display: inline-block; padding-left: 2px; @@ -202,25 +214,31 @@ html, body, #map { width: 186px; } -#filterEventsInput, #filterUnitsInput::-webkit-input-placeholder { +#filterEventsInput, +#filterUnitsInput::-webkit-input-placeholder { color: #666666; } -#filterEventsInput, #filterUnitsInput:focus { +#filterEventsInput, +#filterUnitsInput:focus { outline: none; } -#leftPanel ul, #rightPanel ul { +#leftPanel ul, +#rightPanel ul { padding: 0; list-style-type: none; } -#listWest, #listGuer, #listEast, #listCiv { +#listWest, +#listGuer, +#listEast, +#listCiv { display: none; } .extraInfoBox { - background-color: rgba(0,0,0,0.75); + background-color: rgba(0, 0, 0, 0.75); position: absolute; right: 0; bottom: 40px; @@ -234,7 +252,7 @@ html, body, #map { } #bottomPanel { - background-color: rgba(0,0,0,0.8); + background-color: rgba(0, 0, 0, 0.8); position: absolute; bottom: 0; left: 0; @@ -249,14 +267,24 @@ html, body, #map { height: 100%; } -#timecodeContainer, #toggleFirelines, #toggleNickname, #toggleMapMarker, #versionInfoContainer, #playbackSpeedSliderContainer, #playbackSpeedSliderContainer, .fullscreenButton { +#timecodeContainer, +#toggleFirelines, +#toggleNickname, +#toggleMapMarker, +#versionInfoContainer, +#playbackSpeedSliderContainer, +#playbackSpeedSliderContainer, +.fullscreenButton { margin-top: 5px; margin-left: 10px; margin-right: 10px; display: inline-block; } -#toggleFirelines, #toggleMapMarker, #toggleNickname, .fullscreenButton { +#toggleFirelines, +#toggleMapMarker, +#toggleNickname, +.fullscreenButton { background-image: url("../images/bullets.svg"); background-size: auto 20px; height: 20px; @@ -265,6 +293,7 @@ html, body, #map { float: right; cursor: pointer; } + .toggleTime { float: right; height: 26px; @@ -312,13 +341,16 @@ html, body, #map { height: 16px; padding: 3px 0; } + #frameSliderContainer .frameSliderContainer2 { position: relative; bottom: 14px; } + #frameSliderContainer .frameSliderContainer2 input::-moz-range-track { background-color: transparent; } + #frameSliderContainer .frameSliderContainer2 input::-webkit-slider-runnable-track { background-color: transparent; } @@ -383,6 +415,7 @@ html, body, #map { #playbackSpeedSlider::-webkit-slider-thumb { background-color: #404040; } + #playbackSpeedSlider::-moz-range-thumb { background-color: #404040; } @@ -407,8 +440,10 @@ html, body, #map { } input[type=range] { - -webkit-appearance: none; /* Hides the slider so that custom slider can be made */ - width: 100%; /* Specific width is required for Firefox. */ + -webkit-appearance: none; + /* Hides the slider so that custom slider can be made */ + width: 100%; + /* Specific width is required for Firefox. */ background: transparent; } @@ -444,10 +479,12 @@ input[type=range]::-webkit-slider-runnable-track { border-bottom: 1px solid rgba(0, 0, 0, 0.3); opacity: 0; } + .liEvent.reveal { opacity: 1; transition: opacity 1s; } + .liEvent.action { cursor: pointer; } @@ -467,8 +504,8 @@ input[type=range]::-webkit-slider-runnable-track { } .liUnit:hover { - background-color: rgb(205,134,20); - background-color: rgba(205,134,20,0.9); + background-color: rgb(205, 134, 20); + background-color: rgba(205, 134, 20, 0.9); } .sideTitle { @@ -501,27 +538,30 @@ input[type=range]::-webkit-slider-runnable-track { width: 100%; height: 100%; overflow: auto; - background-color: rgba(0,0,0,0.4); + background-color: rgba(0, 0, 0, 0.4); } + .modalContent { background: none; margin: 10% auto auto; /* border: 1px solid black;*/ width: 1200px; - height: 65%; + height: 65%; } + .modalHeader { box-sizing: border-box; font-size: 16px; - background-color: rgba(155,0,0,0.9); + background-color: rgba(155, 0, 0, 0.9); width: 100%; margin-bottom: 3px; padding: 5px 10px; font-weight: 500; } + .modalFilter { box-sizing: border-box; - background-color: rgba(0,0,0,0.6); + background-color: rgba(0, 0, 0, 0.6); width: 100%; height: 50px; padding: 4px 16px; @@ -529,9 +569,10 @@ input[type=range]::-webkit-slider-runnable-track { overflow-y: auto; overflow-x: auto; } + .modalBody { box-sizing: border-box; - background-color: rgba(0,0,0,0.6); + background-color: rgba(0, 0, 0, 0.6); width: 100%; min-height: 200px; max-height: 400px; @@ -548,22 +589,25 @@ input[type=range]::-webkit-slider-runnable-track { top: 0; height: 100vh; width: 100vw; - background-color: rgba(0,0,0,0.4); + background-color: rgba(0, 0, 0, 0.4); display: flex; justify-content: center; align-items: center; } + .modalDialog.closed { display: none; } + .modalDialog .dialogBase { position: relative; z-index: 601; display: flex; flex-direction: column } + .modalDialog .dialogHeader { - background-color: rgba(155,0,0,0.9); + background-color: rgba(155, 0, 0, 0.9); margin-bottom: 3px; color: white; border-bottom: 1px solid black; @@ -572,32 +616,40 @@ input[type=range]::-webkit-slider-runnable-track { font-weight: 500; font-size: 16px; } + .modalDialog .dialogBody { background-color: #000000ed; overflow-y: scroll; min-width: 1300px; - min-height: 700px; /* title bar is 50 so we need the extra 70 here */ - max-height: calc(75vh - 50px); /* compensate for title bar */ + min-height: 700px; + /* title bar is 50 so we need the extra 70 here */ + max-height: calc(75vh - 50px); + /* compensate for title bar */ max-width: 75vw; - flex:1; + flex: 1; padding: 5px; } + .modalDialog .dialogFooter { margin-top: 3px; } + .modalDialog .dialogFooter .modalButton { float: right; } #ShareLink { - width:100%; - padding:10px 0; - color:white; - background-color:rgba(0,0,0,0.4); - border:1px solid black; + width: 100%; + padding: 10px 0; + color: white; + background-color: rgba(0, 0, 0, 0.4); + border: 1px solid black; } -.filterTagGameInput, #filterGameInput, #calendar1, #calendar2 { +.filterTagGameInput, +#filterGameInput, +#calendar1, +#calendar2 { margin: 4px; } @@ -639,22 +691,27 @@ input[type=range]::-webkit-slider-runnable-track { border: none; width: 100%; } + .modalBody th { - background-color: rgba(0,0,0,0.7); + background-color: rgba(0, 0, 0, 0.7); padding: 3px; font-weight: 500; } + .modalBody td { padding-right: 20px; padding: 3px; } + .modalBody tr { text-align: left; } + .modalBody tr:hover { - background-color: rgb(205,134,20); - background-color: rgba(205,134,20,0.9); + background-color: rgb(205, 134, 20); + background-color: rgba(205, 134, 20, 0.9); } + .modalButtons { box-sizing: border-box; width: 100%; @@ -663,13 +720,14 @@ input[type=range]::-webkit-slider-runnable-track { margin-top: 3px; height: 15px; } + .modalButton { display: inline-block; background-color: none; height: 100%; min-width: 100px; - background-color: rgba(0,0,0); - background-color: rgba(0,0,0,0.7); + background-color: rgba(0, 0, 0); + background-color: rgba(0, 0, 0, 0.7); margin-right: 10px; padding: 5px; text-transform: uppercase; @@ -687,8 +745,8 @@ input[type=range]::-webkit-slider-runnable-track { .hint { display: none; - background-color: rgb(0,0,0); - background-color: rgba(0,0,0,0.7); + background-color: rgb(0, 0, 0); + background-color: rgba(0, 0, 0, 0.7); position: absolute; margin: auto; left: 0; @@ -728,50 +786,138 @@ input[type=range]::-webkit-slider-runnable-track { /* Override leaflet styles */ /* */ -.leaflet-right { - right: 18%; - bottom: 4%; -} +/* .leaflet-top { + top: 55%; +} */ -.leaflet-tile { - /*border: solid black 1px;*/ +/* .leaflet-right { + right: 1%; +} */ + +.leaflet-left { + left: 360px; + top: 50px; } -.leaflet-tile-container { - image-rendering: pixelated; /* 'pixelated' preserves sharpness of tiles */ +.leaflet-bottom { + bottom: 65px; } +/* .leaflet-control-attribution { + right: 0px; + bottom: 0px; +} */ + +/* this was custom positioning code for the basemaps control */ +/* .basemaps { + bottom: 223px; +} */ + +/* .leaflet-tile { */ +/* border: solid black 1px; */ +/* } */ + +/* .leaflet-tile-container { */ +/* 'pixelated' preserves sharpness of tiles */ +/* image-rendering: pixelated; */ +/* } */ + + .leaflet-popup { pointer-events: none; } -#container.marker-transition:not(.zooming) .leaflet-marker-icon.animation { transition: transform .15s linear; } -#container.marker-transition.speed-9:not(.zooming) .leaflet-marker-icon.animation { transition: transform .2s linear; } -#container.marker-transition.speed-8:not(.zooming) .leaflet-marker-icon.animation { transition: transform .3s linear; } -#container.marker-transition.speed-7:not(.zooming) .leaflet-marker-icon.animation { transition: transform .4s linear; } -#container.marker-transition.speed-6:not(.zooming) .leaflet-marker-icon.animation { transition: transform .5s linear; } -#container.marker-transition.speed-5:not(.zooming) .leaflet-marker-icon.animation { transition: transform .6s linear; } -#container.marker-transition.speed-4:not(.zooming) .leaflet-marker-icon.animation { transition: transform .7s linear; } -#container.marker-transition.speed-3:not(.zooming) .leaflet-marker-icon.animation { transition: transform .8s linear; } -#container.marker-transition.speed-2:not(.zooming) .leaflet-marker-icon.animation { transition: transform .9s linear; } -#container.marker-transition.speed-1:not(.zooming) .leaflet-marker-icon.animation { transition: transform 1s linear; } -#container.marker-transition:not(.zooming) .leaflet-popup.animation { transition: transform .15s linear !important; } -#container.marker-transition.speed-9:not(.zooming) .leaflet-popup.animation { transition: transform .2s linear !important; } -#container.marker-transition.speed-8:not(.zooming) .leaflet-popup.animation { transition: transform .3s linear !important; } -#container.marker-transition.speed-7:not(.zooming) .leaflet-popup.animation { transition: transform .4s linear !important; } -#container.marker-transition.speed-6:not(.zooming) .leaflet-popup.animation { transition: transform .5s linear !important; } -#container.marker-transition.speed-5:not(.zooming) .leaflet-popup.animation { transition: transform .6s linear !important; } -#container.marker-transition.speed-4:not(.zooming) .leaflet-popup.animation { transition: transform .7s linear !important; } -#container.marker-transition.speed-3:not(.zooming) .leaflet-popup.animation { transition: transform .8s linear !important; } -#container.marker-transition.speed-2:not(.zooming) .leaflet-popup.animation { transition: transform .9s linear !important; } -#container.marker-transition.speed-1:not(.zooming) .leaflet-popup.animation { transition: transform 1s linear !important; } +#container.marker-transition:not(.zooming) .leaflet-marker-icon.animation { + transition: transform .15s linear; +} + +#container.marker-transition.speed-9:not(.zooming) .leaflet-marker-icon.animation { + transition: transform .2s linear; +} + +#container.marker-transition.speed-8:not(.zooming) .leaflet-marker-icon.animation { + transition: transform .3s linear; +} + +#container.marker-transition.speed-7:not(.zooming) .leaflet-marker-icon.animation { + transition: transform .4s linear; +} + +#container.marker-transition.speed-6:not(.zooming) .leaflet-marker-icon.animation { + transition: transform .5s linear; +} + +#container.marker-transition.speed-5:not(.zooming) .leaflet-marker-icon.animation { + transition: transform .6s linear; +} + +#container.marker-transition.speed-4:not(.zooming) .leaflet-marker-icon.animation { + transition: transform .7s linear; +} + +#container.marker-transition.speed-3:not(.zooming) .leaflet-marker-icon.animation { + transition: transform .8s linear; +} + +#container.marker-transition.speed-2:not(.zooming) .leaflet-marker-icon.animation { + transition: transform .9s linear; +} + +#container.marker-transition.speed-1:not(.zooming) .leaflet-marker-icon.animation { + transition: transform 1s linear; +} + +#container.marker-transition:not(.zooming) .leaflet-popup.animation { + transition: transform .15s linear !important; +} + +#container.marker-transition.speed-9:not(.zooming) .leaflet-popup.animation { + transition: transform .2s linear !important; +} + +#container.marker-transition.speed-8:not(.zooming) .leaflet-popup.animation { + transition: transform .3s linear !important; +} + +#container.marker-transition.speed-7:not(.zooming) .leaflet-popup.animation { + transition: transform .4s linear !important; +} + +#container.marker-transition.speed-6:not(.zooming) .leaflet-popup.animation { + transition: transform .5s linear !important; +} + +#container.marker-transition.speed-5:not(.zooming) .leaflet-popup.animation { + transition: transform .6s linear !important; +} + +#container.marker-transition.speed-4:not(.zooming) .leaflet-popup.animation { + transition: transform .7s linear !important; +} + +#container.marker-transition.speed-3:not(.zooming) .leaflet-popup.animation { + transition: transform .8s linear !important; +} + +#container.marker-transition.speed-2:not(.zooming) .leaflet-popup.animation { + transition: transform .9s linear !important; +} + +#container.marker-transition.speed-1:not(.zooming) .leaflet-popup.animation { + transition: transform 1s linear !important; +} .leaflet-popup-content-wrapper { + /* background: white; */ background: none; box-shadow: none; padding: 0; margin: 0; - margin-top: 10px; + /* background-blend-mode: screen; */ + /* box-shadow: inset; */ + /* opacity: 0.65; */ + /* padding-right: 2px; */ + /* margin-top: 10px; */ } .leaflet-popup-tip { @@ -779,10 +925,13 @@ input[type=range]::-webkit-slider-runnable-track { } .leaflet-popup-content { + color: white; + text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; padding: 0; margin: 0; font-weight: bold; font-size: 12px; + /* width: 100%; */ } .leaflet-div-icon { @@ -793,7 +942,10 @@ input[type=range]::-webkit-slider-runnable-track { .stats .name { width: 200px; } -.stats .kills, .stats .tkills, .stats .deaths { + +.stats .kills, +.stats .tkills, +.stats .deaths { width: 70px; text-align: right; }