· 8 years ago · Jun 16, 2018, 10:36 PM
1// ==UserScript==
2// @name SuperHex.io ð’it ð“¥ision ð’Žod
3// @namespace nit
4// @version 20170809
5// @description Enhanced display for SuperHex.io
6// @author ᥒit
7// @match http://superhex.io/
8// @grant none
9// ==/UserScript==
10
11// Features:
12// 1: basic mod init and behavior
13// - new page title
14// - new logo text
15// - mod config available, settings can be toggled with hot keys
16// - default focus on play and play-again buttoms
17// - fix display of player names with HTML special characters
18// - hide taboo words in current player name, for consistency with handling of other player names
19// - added grid lines to minimap
20// - turn off scroll bars on window
21// 2: allow user to choose geographic region
22// 3: tag username to indicate that user is using a mod
23// 4: zoom: +, - or scroll wheel
24// 5: dead zone for mouse at center of screen
25// 6: reduce the delay between sending mouse directions from 10ms to 1ms
26// 7: option to set player diameter
27// 8: option to change font size for player names
28// 9: fix player name text offset for zoom
29// 10: allow play after victory
30// 11: show player location in status board
31// 12: show player locations on leaderboard and friends list, gray if last known loction, white if current location
32// - last known location from where seen and from where last saw their cells
33// 13: add player colors to leaderboard and friends list
34// 14: highlight current player on the leaderboard
35// 15: draw lines to nearby enemies
36// 16: draw view area of nearby enemies
37// 17: display player names on their visible regions, hidden when player is visible
38// 18: new player icon (needed to support additional colors)
39// 19: added new color set, allows player colors to be consisten between players
40// 20: borders to mark edge of player regions, helps to separate similar colored players
41// 21: decreased the time key animations take
42// 22: added limits for the direction the player can travel, to better align to the grid
43// 23: added support for fast turns
44// 24: auto movement circles
45// 25: fix player flickering bug introduced in superhex 'skins' update
46// 26: added sound effects and a volume control
47// 27: show player locations on minimap, for known players
48// 28: added display of known players
49// 29: track player kills, display in player list
50// 30: add option to increase the size of the minimap
51// 31: improve the player loc error smoothing, to increase correction speed for large offsets
52// 32: show killer and kill location on final match results
53// 33: allow player to select server port number, naw1 0-20/10, nae1, eu3 0-75/5, nae1 0-70/10
54// 34: added option for variegated colors
55// 35: added option for showing/hiding player names
56// 36: added option for showing/hiding player skins
57// 37: added option for showing/hiding leaderboard
58// 38: option to enable movement circles on spawn
59
60//: version # on main display
61//: should min and max comp apply to variegation?
62//: show hide minimap
63//: track down cause of location glitch on sharp turns
64//: when not maximized, nit vision tag overlaps minimap
65//: mod chat
66//: remap options using some capital letters
67
68//: option to avoid walls?
69//: option to avoid self?
70//: faster tiny circles?
71
72//: ? turn region borders into connected paths
73//: - in Match Results, display 'killed by' when killed, rank and best session rank
74//: - track new values: best Score, Kills, Map %, Blocks, and display in Match Results
75
76//: ! issue with zoom lag, or more lag when zoomed out, (testers are having this)
77
78// possible features
79// - option for min and max for the color saturation range
80// - option to toggle player size change
81// - option to toggle name size change
82// - volume controls for each sound effect
83
84//: look into properties to implement flags and mapping to orig code
85//: use promises to build the behavior of loading and checking these values
86//: improve error handling from failed http request
87//: improve error handline from failed hash value checks
88//: notes
89//: - could use npm acorn to parse javascript from inside javascript
90
91window.newSuperhex = function() {
92 "use strict";
93 var modName = "ð’it ð“¥ision",
94 modVersion = "20170809";
95
96 //!! code mapping
97 var
98 playerColor = e,
99 playerColors = function() { return dn; }, // array of available player colors, [0] is background
100 currentPlayerSkinId = function() { return rn; }, // id number for the current player's skin, -1 if not set
101 playerSkinId = function() { return nn; }, // array, indexed by player id, skin id for player, if set
102 playerDarkColor = t,
103 playerDarkColors = function() { return cn; }, // array of available player colors, in a dark shade, [0] is background
104 allocateHexArray = a, // create 2-D array that is size of lobby
105 // o()
106 hexMap = function() { return je; }, // array, indexed by [q+hexMapCenter()][r+hexMapCenter()], map data
107 hexMapCenter = function() { return st; }, // offset into hexMap for the center cell
108 getHexPid = r, // get player id of hex given q, r hex coord params
109 initContourColor = g, // sets the contour color for a cell, params: q, r, pid, aid
110 removeContourSprite = u, // removes contour sprite from lobby, params: q, r
111 hexToRectIndex = y, // converts from q, r params to { x: y: } obj
112 rectToHexIndex = v, // converts from x, y params to { q: r: } obj
113 setBufferSpriteTint = k, // set tint and alpha for sprite, params: color, ??, alpha, ??, ??, visible
114 setSpriteTint = w, // set tint and alpha for sprite, params: sprite, color, ??, alpha, ??
115 initCurrentPlayerGraphics = I, // initializes the graphics for the current player
116 skinNames = function() { return vn; }, // array, indexed by skin id, skin names
117 getCellSprite = b, // get/create hex sprite for cell in the lobby
118 cellSprites = function() { return yt; }, // array, indexed by q, r, cell sprites
119 createPlayerGraphics = E, // create icon and text for player, given player id param
120 playerIcons = function() { return wt; }, // array, indexed by player id, icons for all players, except the current player
121 playerXLoc = function() { return qt; }, // array, indexed by player id
122 playerYLoc = function() { return Et; }, // array, indexed by player id
123 playerText = function() { return It; }, // array, indexed by player id, text for all player names, except the current player
124 playerNames = function() { return tn; }, // array, indexed by player id
125 // M()
126 serverPing = function() { return mt; }, // array, ping time, indexed by server zone name
127 // C()
128 username = function() { return tt; },
129 setUsername = function( str ) { tt = str; },
130 // S()
131 playerDead = function() { return Nt; }, // current player is dead
132 // R()
133 gameActive = function() { return St; }, // game connection is active
134 leaderboard = function() { return $t; }, // array, indexed by rank-1
135 friends = function() { return en; }, // array, list of friends, sorted by rank
136 setFriends = function( l ) { en = l; },
137 // U()
138 rootLayer = function() { return Ue; }, // base layer everything is drawn on
139 playerLayer = function() { return We; }, // root container for player content
140 playerIconLayer = function() { return Ve; }, // player icons and text
141 currentPlayerGraphics = function() { return ze; }, // pixi sprite or graphics for current player
142 setCurrentPlayerGraphics = function( g ) { ze = g; },
143 currentPlayerText = function() { return Fe; }, // pixi text
144 resize = z,
145 pixelRatio = function() { return gn; },
146 scale = function() { return dt; }, // sets hex side length
147 setScale = function( n ) { dt = n; },
148 hexHeight = function() { return gt; }, // height of drawn hex cells
149 playerInnerRadius = function() { return ut; }, // in scale units, player radius is playerInnerRadius + playerExtraRadius
150 setPlayerInnerRadius = function( n ) { ut = n; },
151 playerExtraRadius = function() { return ft; },
152 setPlayerExtraRadius = function( n ) { ft = n; },
153 sendPlayerDirection = F,
154 currentPlayerDirection = function() { return ot; }, // current player direction, radians
155 setcurrentPlayerDirection = function( n ) { ot = n; },
156 // oe()
157 currentPlayer = function() { return Ft; }, // player id
158 hexAreaRadius = function() { return lt; },
159 // de()
160 trailGraphics = function() { return kt; }, // array, indexed by player id, graphics for player trails
161 // he()
162 currentPlayerDeathX = function() { return $e; }, // X location where player died
163 currentPlayerDeathY = function() { return et; }, // Y location where player died
164 htmlEncode = ke, // filter the string for special html characters
165 tabooStr = we, // filter the string for taboo words
166 // xe()
167 blockCount = function() { return Jt; }, // number of blocks the current player has
168 setBlockCount = function( n ) { Jt = n; },
169 victoryFlag = function() { return At; },
170 setVictoryFlag = function( b ) { At = b; },
171 showFriends = Ee, // populate and show the friends list
172 // var block
173 neighborOffset = function() { return fn; }, // array, indexed by neighbor offset, { q: r: } offsets to each neighbor
174 // keyup listener
175 friendsEnabled = function() { return Xt; }, // bool flag, indicates if friends list is displayed
176 setFriendsEnabled = function( b ) { Xt = b; };
177
178 //! mod config
179 var showOptionsEnabled = true, tagUserEnabled = true, zoomEnabled = true, deadZoneEnabled = false,
180 playAfterVictoryEnabled = true, playerLocEnabled = true, showColorEnabled = true, enemyLinesEnabled = true,
181 enemyViewsEnabled = true, regionNamesEnabled = true, playerIconEnabled = true, updateColorsEnabled = true,
182 variegationEnabled = true, regionBorderEnabled = false, fastTransitionsEnabled = true,
183 fixedAngleEnabled = false, fastTurnEnabled = true, showMovementCircleEnabled = false,
184 movementCircleEnabled = false, movementClockwise = true, soundEnabled = true, minimapMarkerEnabled = true,
185 playerListEnabled = false, locSmoothingEnabled = true, serverPortEnabled = false, playerNamesEnabled = true,
186 playerSkinsEnabled = true, leaderboardEnabled = true, spawnWithMovementEnabled = false, disableRightClickEnabled = true;
187 //! geographic region to connect to
188 var geoRegion = "auto", serverPort = "";
189 //! zoom settings
190 var zoom = 1, minZoom = 0.3, maxZoom = 1.0, zoomSteps = 4;
191 var zoomStepFactor = 1.0;
192 //! general settings
193 var playerSize = 1.0, // player diameter in scale() units
194 deadZoneSize = 100, deadZoneSizeSqr = 0, // deadzone circle radius, pixels
195 rawPlayerNames = []; // unencoded player names
196 //! player name size
197 var playerNameSize = 24, playerNameFontSize = function() { return playerNameSize / pixelRatio(); }; // pixels
198 //! last known player info
199 var playerLastLocX = [], playerLastLocY = [], lastBlockCount = 0;
200 //! overlay data
201 var enemyOverlayLayer, regionOverlay, enemyLines = new PIXI.Graphics, enemyViews = new PIXI.Graphics,
202 regionBorders = new PIXI.Graphics, markUp = new PIXI.Graphics, regionInfo = [], regionSmoothFactor = 0.995;
203 //! region name size
204 var regionNameSize = 36, regionNameFontSize = function() { return regionNameSize / pixelRatio(); }; // pixels
205 var regionBorderValue = 1; // color
206 //! new color sets
207 var colorCount = 56, maxColorComp = 1.0, minColorComp = 0.2, hueOffset = 0;
208 var darkFactor = 0.7, veryDarkFactor = 0.5;
209 var modPlayerColors = { normal: null, dark: null, veryDark: null };
210 var variegationFactor = 0.1, variegationTexture = null; // values are between -1 and +1
211 //! motion settings
212 var fixedAngle = 15, fastTurnMult = 1.5, locSmoothingExp = 2.6, locSmoothingMin = 0.05, locSmoothingMax = 3, locSmoothingExtent = locSmoothingMax - locSmoothingMin;
213 //! movement circle settings
214 var movementCircleTimer = undefined, movementCircleX = 0, movementCircleY = 0, movementCircleRadius = 1;
215 var lastMouseDirection = 0, mouseInDeadZone = false;
216 //! audio settings
217 var volume = 100;
218 //! minimap support
219 var minimap, minimapPos, minimapMarkers = [], minimapScale = 1.0, minimapBaseHeight, minimapBaseWidth;
220 //! player stats
221 var leaderboardRanks = [], friendRanks = [], killCount = [];
222
223 // audio
224 var killSound =
225 `data:audio/ogg;base64,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`,
226 captureSelfSound =
227 `data:audio/ogg;base64,T2dnUwACAAAAAAAAAABIUwAAAAAAAK28zJ0BHgF2b3JiaXMAAAAAAkSsAAAAAAAAAHECAAAAAAC4AU9nZ1MAAAAAAAAAAAAASFMAAAEAAACVNQGkEjv/////////////////////kQN2b3JiaXMrAAAAWGlwaC5PcmcgbGliVm9yYmlzIEkgMjAxMjAyMDMgKE9tbmlwcmVzZW50KQAAAAABBXZvcmJpcylCQ1YBAAgAAAAxTCDFgNCQVQAAEAAAYCQpDpNmSSmllKEoeZiUSEkppZTFMImYlInFGGOMMcYYY4wxxhhjjCA0ZBUAAAQAgCgJjqPmSWrOOWcYJ45yoDlpTjinIAeKUeA5CcL1JmNuprSma27OKSUIDVkFAAACAEBIIYUUUkghhRRiiCGGGGKIIYcccsghp5xyCiqooIIKMsggg0wy6aSTTjrpqKOOOuootNBCCy200kpMMdVWY669Bl18c84555xzzjnnnHPOCUJDVgEAIAAABEIGGWQQQgghhRRSiCmmmHIKMsiA0JBVAAAgAIAAAAAAR5EUSbEUy7EczdEkT/IsURM10TNFU1RNVVVVVXVdV3Zl13Z113Z9WZiFW7h9WbiFW9iFXfeFYRiGYRiGYRiGYfh93/d93/d9IDRkFQAgAQCgIzmW4ymiIhqi4jmiA4SGrAIAZAAABAAgCZIiKZKjSaZmaq5pm7Zoq7Zty7Isy7IMhIasAgAAAQAEAAAAAACgaZqmaZqmaZqmaZqmaZqmaZqmaZpmWZZlWZZlWZZlWZZlWZZlWZZlWZZlWZZlWZZlWZZlWZZlWZZlWUBoyCoAQAIAQMdxHMdxJEVSJMdyLAcIDVkFAMgAAAgAQFIsxXI0R3M0x3M8x3M8R3REyZRMzfRMDwgNWQUAAAIACAAAAAAAQDEcxXEcydEkT1It03I1V3M913NN13VdV1VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVWB0JBVAAAEAAAhnWaWaoAIM5BhIDRkFQCAAAAAGKEIQwwIDVkFAAAEAACIoeQgmtCa8805DprloKkUm9PBiVSbJ7mpmJtzzjnnnGzOGeOcc84pypnFoJnQmnPOSQyapaCZ0JpzznkSmwetqdKac84Z55wOxhlhnHPOadKaB6nZWJtzzlnQmuaouRSbc86JlJsntblUm3POOeecc84555xzzqlenM7BOeGcc86J2ptruQldnHPO+WSc7s0J4ZxzzjnnnHPOOeecc84JQkNWAQBAAAAEYdgYxp2CIH2OBmIUIaYhkx50jw6ToDHIKaQejY5GSqmDUFIZJ6V0gtCQVQAAIAAAhBBSSCGFFFJIIYUUUkghhhhiiCGnnHIKKqikkooqyiizzDLLLLPMMsusw84667DDEEMMMbTSSiw11VZjjbXmnnOuOUhrpbXWWiullFJKKaUgNGQVAAACAEAgZJBBBhmFFFJIIYaYcsopp6CCCggNWQUAAAIACAAAAPAkzxEd0REd0REd0REd0REdz/EcURIlURIl0TItUzM9VVRVV3ZtWZd127eFXdh139d939eNXxeGZVmWZVmWZVmWZVmWZVmWZQlCQ1YBACAAAABCCCGEFFJIIYWUYowxx5yDTkIJgdCQVQAAIACAAAAAAEdxFMeRHMmRJEuyJE3SLM3yNE/zNNETRVE0TVMVXdEVddMWZVM2XdM1ZdNVZdV2Zdm2ZVu3fVm2fd/3fd/3fd/3fd/3fd/XdSA0ZBUAIAEAoCM5kiIpkiI5juNIkgSEhqwCAGQAAAQAoCiO4jiOI0mSJFmSJnmWZ4maqZme6amiCoSGrAIAAAEABAAAAAAAoGiKp5iKp4iK54iOKImWaYmaqrmibMqu67qu67qu67qu67qu67qu67qu67qu67qu67qu67qu67qu67pAaMgqAEACAEBHciRHciRFUiRFciQHCA1ZBQDIAAAIAMAxHENSJMeyLE3zNE/zNNETPdEzPVV0RRcIDVkFAAACAAgAAAAAAMCQDEuxHM3RJFFSLdVSNdVSLVVUPVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVdU0TdM0gdCQlQAAGQAA5KSm1HoOEmKQOYlBaAhJxBzFXDrpnKNcjIeQI0ZJ7SFTzBAEtZjQSYUU1OJaah1zVIuNrWRIQS22xlIh5agHQkNWCAChGQAOxwEcTQMcSwMAAAAAAAAASdMATRQBzRMBAAAAAAAAwNE0QBM9QBNFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcTQM0UQQ0UQQAAAAAAAAATRQB0VQB0TQBAAAAAAAAQBNFwDNFQDRVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcTQM0UQQ0UQQAAAAAAAAATRQBUTUBTzQBAAAAAAAAQBNFQDRNQFRNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAQ4AAAEWQqEhKwKAOAEAh+NAkiBJ8DSAY1nwPHgaTBPgWBY8D5oH0wQAAAAAAAAAAABA8jR4HjwPpgmQNA+eB8+DaQIAAAAAAAAAAAAgeR48D54H0wRIngfPg+fBNAEAAAAAAAAAAADwTBOmCdGEagI804RpwjRhqgAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACAAQcAgAATykChISsCgDgBAIejSBIAADiSZFkAAKBIkmUBAIBlWZ4HAACSZXkeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAIABBwCAABPKQKEhKwGAKAAAh6JYFnAcywKOY1lAkiwLYFkATQN4GkAUAYAAAIACBwCAABs0JRYHKDRkJQAQBQDgcBTL0jRR5DiWpWmiyHEsS9NEkWVpmqaJIjRL00QRnud5pgnP8zzThCiKomkCUTRNAQAABQ4AAAE2aEosDlBoyEoAICQAwOE4luV5oiiKpmmaqspxLMvzRFEUTVNVXZfjWJbniaIomqaqui7L0jTPE0VRNE1VdV1omueJoiiapqq6LjRNFE3TNFVVVV0XmuaJpmmaqqqqrgvPE0XTNE1VdV3XBaJomqapqq7rukAUTdM0VdV1XReIomiapqq6rusC0zRNVVVd15VlgGmqqqq6riwDVFVVXdeVZRmgqqrquq4rywDXdV3ZlWVZBuC6rivLsiwAAODAAQAgwAg6yaiyCBtNuPAAFBqyIgCIAgAAjGFKMaUMYxJCCqFhTEJIIWRSUioppQpCKiWVUkFIpaRSMkotpZZSBSGVkkqpIKRSUikFAIAdOACAHVgIhYasBADyAAAIY5RizDnnJEJKMeaccxIhpRhzzjmpFGPOOeeclJIx55xzTkrJmHPOOSelZMw555yTUjrnnHMOSimldM4556SUUkLonHNSSimdc845AQBABQ4AAAE2imxOMBJUaMhKACAVAMDgOJalaZ4niqZpSZKmeZ4nmqZpapKkaZ4niqZpmjzP80RRFE1TVXme54miKJqmqnJdURRN0zRNVSXLoiiKpqmqqgrTNE3TVFVVhWmapmmqquvCtlVVVV3XdWHbqqqqruu6wHVd13VlGbiu67quLAsAAE9wAAAqsGF1hJOiscBCQ1YCABkAAIQxCCmEEFIGIaQQQkgphZAAAIABBwCAABPKQKEhKwGAcAAAgBCMMcYYY4wxNoxhjDHGGGOMMXEKY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHG2FprrbVWABjOhQNAWYSNM6wknRWOBhcashIACAkAAIxBiDHoJJSSSkoVQow5KCWVllqKrUKIMQilpNRabDEWzzkHoaSUWooptuI556Sk1FqMMcZaXAshpZRaiy22GJtsIaSUUmsxxlpjM0q1lFqLMcYYayxKuZRSa7HFGGuNRSibW2sxxlprrTUp5XNLsdVaY6y1JqOMkjHGWmustdYilFIyxhRTrLXWmoQwxvcYY6wx51qTEsL4HlMtsdVaa1JKKSNkjanGWnNOSglljI0t1ZRzzgUAQD04AEAlGEEnGVUWYaMJFx6AQkNWAgC5AQAIQkoxxphzzjnnnHMOUqQYc8w55yCEEEIIIaQIMcaYc85BCCGEEEJIGWPMOecghBBCCKGEklLKmHPOQQghhFJKKSWl1DnnIIQQQiillFJKSqlzzkEIIYRSSimllJRSCCGEEEIIpZRSSikppZRCCCGEEkoppZRSUkophRBCCKWUUkoppaSUUgohhBBKKaWUUkpJKaUUQgmllFJKKaWUklJKKaUQSimllFJKKSWllFJKpZRSSimllFJKSimllEoppZRSSimllJRSSimVUkoppZRSSikppZRSSqmUUkoppZRSUkoppZRSKaWUUkoppaSUUkoppVJKKaWUUkpJKaWUUkqllFJKKaWUklJKKaWUUiqllFJKKaUAAKADBwCAACMqLcROM648AkcUMkxAhYasBADIAAAQB7G01lqrjHLKSUmtQ0Ya5qCk2EkHIbVYS2UgQcpJSp2CCCkGqYWMKqWYk5ZCy5hSDGIrMXSMMUc55VRCxxgAAACCAAADETITCBRAgYEMADhASJACAAoLDB3DRUBALiGjwKBwTDgnnTYAAEGIzBCJiMUgMaEaKCqmA4DFBYZ8AMjQ2Ei7uIAuA1zQxV0HQghCEIJYHEABCTg44YYn3vCEG5ygU1TqQAAAAAAAHgDgAQAg2QAiIqKZ4+jw+AAJERkhKTE5QREAAAAAADsA+AAASFKAiIho5jg6PD5AQkRGSEpMTlACAAABBAAAAABAAAEICAgAAAAAAAQAAAAICE9nZ1MABMgIAAAAAAAASFMAAAIAAADEvXJcE05OSUA+Ok1QPkJPUlBRUT5CS0hs6QqFAEVxleLqcUtXKAQoiqsUV4+3TmaEzE0QVjMEjBURY4woqFqLWKtFbEUjtihiK2hBtaCi0eB2PK1RsMGLUTRotShWGwxEhLQoxgHs4Tw6ezuwb4Ea3MN5dPZ2YN8CNdjPNpugoGakAIsVxM4WQYsGVTSKakFVRSOqFbEVEI0GI9aqKDXXXqNYa4FA7tGIVkWs1lAFyk8PSgAM7nQISMwlwB/c6RCQmEuA3+8nGzlsghRgJ1YbO3srVnRgsWis06AqokU1iogOxYgVLKKxqLHyuz79tdraaHViFOvQIEKzBpQB7OkKafs0avfIbfLc0xXS9mnU7pHb5JkcG0NQEAYmQ8DezlCLidHleCpyExW0CFL6AxSthX2zRuussu0/BV7FAMwFd6kgpJQLtPicC+5SQUgpF2jxmRztXeTkMGBoEbAYdjZWGxtRO78atSRNx68v7NPfSdH6DnFQWocYoAkB9AFLoGdgfIcI6z5gCfQMjO8QYX0zqmQgJ8NYC4ARa1Wt6MAqgnR03Llqzc/yk9XIMR15f0KHRgFwE5wRr0UTFqu9BTrwzojXogmL1d4CHXi9MUEwY0EtKQB7sVpsUAsDA9PSmk/JGgUUHSLYWgTVVKxYM4QV21e+9WYhNmJVp3UoigCsGE0AtAmvxwlRw/2gHG1twutxQtRwPyhH260JUQaCvRJBQc0QEDUYUcU0DBUDtKJF1Wq477KqCjoMKthCVI2r2qjWqIjzuwOLRSsatbSGKEQpSADMAV+RHmRNB8pmDviK9CBrOlA2yRG5yMmxoCYETDHV3sbeqLtn9/PNUal3rcYv1ffUuZ6nUdFYUMRGUYASBLT5rqc8HwoTTVJrr813PeX5UJhoklp7t6ZURkIOcxgJUhCwRkWMUQ0ioNj1cF+I9EmFqIp1FZZ+dALAOhEXRXQlYJwBX88C45R8E2nNOwO+ngXGKfkm0pr3AVomy1oECkBSAHy+CGEFVVG0WoPNamIpokVBxGg1KKpVwNIGgT+t/fWBYGuMGNYNUdHoRIOhBQYcIssVe0Lq457Q9u8QWa7YE1If94S2f/3NsCUENQFgtcPG1k5EULVodGgQ0SiCiKLRKCKCqlOnFhFUi0axVgVVa6FxKiefYlU0GnRaN20wRNAE9DnLee02q49O7cL1Oct57Tarj07twl21SsmmSVBzez0AUDUiiljFzrAo2DoVC6qIVa2CIjYaVRCdaFSMtaIVrKhpYKOh0m1TmmYFEY21XwAULouYRq6CGw7JfWBSuCxiGrkKbjgk94HJGVkjZfQNCc1gNUcCgBprBWMVMUENK6IAhmmhmIY1raAqFmONTsUa097UNAxbionjuh6LMOjUqAL8Cf0Ua21bMcwoWP34E/op1tq2YphRsPpxproZwbSaBDUAgFoVK4iFgYF1SwVrjEVVRKuIqkEV0Yqirv4RsbRqYYOpT9t/qayCFRXrVHTqxADs8fx6bbdz7R4wuz2eX6/tdq7dA2Z3QJE1UiInI4PVAIBRxaiYwjkfkvZfcV018v6p+k12a5gYsaoxolUAAKz9yqP2o9vExi1YaGrtVx61H90mNm7BQlMH1BNlRiZtAgYMDQCoiKqI3M3VRositGsIKqIB23f+/QG1PemQHtFhAKz1yqGeI9hqSRfbWq8c6jmCrZZ0sd2ajJApkUgIkg6AiDFGjOpAUayqqoqi04jvv0PQYK2KpVunCDoUVKKe4kcpaESLotPC0mYTAGz1aF/QYwQSjrfVo31BjxFIOF5yTJslEmQaADDtDau9IeadaneKqqgaVVEVrQ6dGlWwolWMqF29E0TVqGIh8m3pfQucIjoVAw==`,
228 captureOtherSound =
229 `data:audio/ogg;base64,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`,
230 areaLossSound =
231 `data:audio/ogg;base64,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`;
232
233 //! configure document and mod state
234 function modInit()
235 {
236 // hide this ad related element, it is messing up page formatting
237 document.getElementById( "780560" ).style.display = 'none';
238
239 // update title
240 document.title = `Superhex.io - ${modName}`;
241 // update logo
242 document.getElementsByClassName( "logo" )[0].insertAdjacentHTML( 'beforeend', `<span class="logo-text">${modName} ð’Žod</span>` );
243 focusPlayButton();
244
245 // add location element for current player
246 document.getElementById( "score" ).insertAdjacentHTML( 'beforeend', `
247 <div id="playerLocDisplay">
248 <div class='scorecol1'>Location</div><div id='locValue' class='scorecol2'/>
249 </div>
250 ` );
251
252 // add location col for leaderboard players
253 var leaderboardRows = document.getElementById( "leaderboard" ).getElementsByTagName( "tr" );
254 for ( i = 0; i < leaderboardRows.length; i++ )
255 leaderboardRows[ i ].insertAdjacentHTML( 'beforeend', `<td id="lb${ i + 1 }Loc" class="lbcol4"></td>` );
256
257 // create a new match result table
258 var matchresultsTable = document.getElementsByClassName( "matchresults" )[0].getElementsByTagName( "table" )[0];
259 matchresultsTable.insertAdjacentHTML( 'afterend', `
260 <table>
261 <tr>
262 <td><div class="scoreTitle">Score</div></td>
263 <td><div class="scoreValue" id="finalScore"/></td>
264 </tr>
265 <tr>
266 <td><div class="scoreTitle">Map %</div></td>
267 <td><div class="scoreValue" id="finalMapPercent"/></td>
268 </tr>
269 <td><div class="scoreTitle">Kills</div></td>
270 <td><div class="scoreValue" id="finalKills"/></td>
271 <tr>
272 <td><div class="scoreTitle">Blocks</div></td>
273 <td><div class="scoreValue" id="finalBlocks"/></td>
274 </tr>
275 <tr>
276 <td><div class="scoreTitle">Killed By</div></td>
277 <td><div class="scoreValue" id="killedBy"/></td>
278 </tr>
279 <tr>
280 <td><div class="scoreTitle">Killed At</div></td>
281 <td><div class="scoreValue" id="killedAt"/></td>
282 </tr>
283 </table>
284 ` );
285 // remove the original table
286 matchresultsTable.remove();
287
288 // get location used to insert minimap markers
289 minimap = document.getElementById( "minimap" );
290 minimapPos = document.getElementById( "minimap-position" );
291 // draw gridlines on the minimap
292 var minimapSvg = minimap.getElementsByTagName( "svg" )[0];
293 var gridColor = "#303030";
294 for ( var pos = 0; pos < 300; pos += 100 )
295 {
296 var hexIndex = rectToHexIndex( pos * scale(), pos * scale() ),
297 x = hexIndex.q / 170 * .866,
298 y = ( hexIndex.r + hexIndex.q / 2 ) / 170,
299 yLimit = 1 - x / Math.sqrt( 3 ),
300 xLimit = Math.sqrt( 3 ) * ( 1 - x );
301 if ( xLimit > 0.866 ) xLimit = 0.866;
302 minimapSvg.insertAdjacentHTML( 'afterbegin', `
303 <line x1="${x}" y1="${yLimit}" x2="${x}" y2="${-yLimit}" stroke-width="0.03" stroke="${gridColor}"/>
304 <line x1="${-x}" y1="${yLimit}" x2="${-x}" y2="${-yLimit}" stroke-width="0.03" stroke="${gridColor}"/>
305 <line x1="${xLimit}" y1="${y}" x2="${-xLimit}" y2="${y}" stroke-width="0.03" stroke="${gridColor}"/>
306 <line x1="${xLimit}" y1="${-y}" x2="${-xLimit}" y2="${-y}" stroke-width="0.03" stroke="${gridColor}"/>
307 ` );
308 }
309
310 // player list
311 document.body.insertAdjacentHTML( 'beforeend', `<div id="player-list"><div id="player-detail"></div><div class="shortcut">['] Player List<span id="player-list-count"/></div></div>` );
312
313 // show mod config options
314 document.body.insertAdjacentHTML( 'beforeend', `
315 <div id="mod-config" class="mod-config-div">
316 <fieldset id="mod-config-set" class="mod-config-set">
317 <legend id="mod-config-title" class="mod-config-title">[~] ${modName} <span id="mod-config-version">(v${modVersion})</span></legend>
318 <div>
319 <select id="geoRegions" name="geoRegions">
320 <option value="">auto</option>
321 </select>
322 <label for="geoRegions">geographic region (ping)</label>
323 </div>
324 <div>
325 <input type="checkbox" id="serverPort" name="serverPort"/>
326 <label for="serverPort">[q]</label>
327 <input type="number" id="serverPortValue" name="serverPortValue"/>
328 <label for="serverPortValue">server port to connect to</label>
329 </div>
330 <div>
331 <input type="checkbox" id="tagUser" name="tagUser"/>
332 <label for="tagUser">[t] tag username, indicates mod use</label>
333 </div>
334 <div>
335 <input type="number" id="playerSize" name="playerSize"/>
336 <label for="playerSize">player diameter, 1.0 = size of hex side</label>
337 </div>
338 <div>
339 <input type="checkbox" id="playerNames" name="playerNames"/>
340 <label for="playerNames">[n]</label>
341 <input type="number" id="playerNameSize" name="playerNameSize"/>
342 <label for="playerNameSize">player names (font size)</label>
343 </div>
344 <div>
345 <input type="checkbox" id="playerSkins" name="playerSkins"/>
346 <label for="playerSkins">[j] player skins</label>
347 </div>
348 <div>
349 <input type="checkbox" id="regionNames" name="regionNames"/>
350 <label for="regionNames">[r]</label>
351 <input type="number" id="regionNameSize" name="regionNameSize"/>
352 <label for="regionNameSize">names on player regions (font size)</label>
353 </div>
354 <div>
355 <input type="checkbox" id="regionBorder" name="regionBorder"/>
356 <label for="regionBorder">[b]</label>
357 <input type="number" id="regionBorderValue" name="regionBorderValue"/>
358 <label for="regionBorderValue">player region border (lightness)</label>
359 </div>
360 <div>
361 <input type="checkbox" id="minimapMarker" name="minimapMarker"/>
362 <label for="minimapMarker">[k] players on minimap (last known loc)</label>
363 </div>
364 <div>
365 <input type="number" id="minimapScale" name="minimapScale"/>
366 <label for="minimapScale">minimap size multiplier</label>
367 </div>
368 <div>
369 <input type="checkbox" id="updateColors" name="updateColors"/>
370 <label for="updateColors">[o]</label>
371 <input type="number" id="colorCount" name="colorCount"/>
372 <label for="colorCount">alt color set (count); '[' & ']' to change</label>
373 </div>
374 <div>
375 <input type="checkbox" id="playerIcon" name="playerIcon"/>
376 <label id="playerIconLabel" for="playerIcon">[i] improved player icon</label>
377 </div>
378 <div>
379 <input type="checkbox" id="variegation" name="variegation"/>
380 <label for="variegation">[y]</label>
381 <input type="number" id="variegationFactor" name="variegationFactor"/>
382 <label for="variegationFactor">variegated colors (+/- range)</label>
383 </div>
384 <div>
385 <input type="number" id="minColorComp" name="minColorComp"/>
386 <label for="minColorComp">min </label>
387 <input type="number" id="maxColorComp" name="maxColorComp"/>
388 <label for="minColorComp">max; color component limits</label>
389 </div>
390 <div>
391 <input type="number" id="hueOffset" name="hueOffset"/>
392 <label for="hueOffset">hue offset for alt color set</label>
393 </div>
394 <div>
395 <input type="checkbox" id="leaderboardEnabled" name="leaderboardEnabled"/>
396 <label for="leaderboard">[l] leaderboard</label>
397 </div>
398 <div>
399 <input type="checkbox" id="playerLoc" name="playerLoc"/>
400 <label for="playerLoc">[w] player locations</label>
401 </div>
402 <div>
403 <input type="checkbox" id="showColor" name="showColor"/>
404 <label for="showColor">[c] player colors on leaderboard</label>
405 </div>
406 <div>
407 <input type="checkbox" id="enemyLines" name="enemyLines"/>
408 <label for="enemyLines">[e] lines to nearby enemies</label>
409 </div>
410 <div>
411 <input type="checkbox" id="enemyViews" name="enemyViews"/>
412 <label for="enemyViews">[v] standard view area for enemies</label>
413 </div>
414 <div>
415 <input type="checkbox" id="playAfterVictory" name="playAfterVictory"/>
416 <label for="playAfterVictory">[p] play after victory</label>
417 </div>
418 <div>
419 <input type="checkbox" id="fastTransitions" name="fastTransitions"/>
420 <label for="fastTransitions">[a] fast animated transitions</label>
421 </div>
422 <div>
423 <input type="checkbox" id="zoom" name="zoom"/>
424 <label for="zoom">[z]</label>
425 <input type="number" id="zoomSteps" name="zoomSteps"/>
426 <label for="zoomSteps">zoom; +/- or scroll wheel (steps)</label>
427 </div>
428 <div>
429 <input type="checkbox" id="deadZone" name="deadZone"/>
430 <label for="deadZone">[d]</label>
431 <input type="number" id="deadZoneSize" name="deadZoneSize"/>
432 <label for="deadZoneSize">mouse dead zone (radius in pixels)</label>
433 </div>
434 <div>
435 <input type="checkbox" id="fixedAngle" name="fixedAngle"/>
436 <label for="fixedAngle">[g]</label>
437 <input type="number" id="fixedAngleValue" name="fixedAngleValue"/>
438 <label for="fixedAngleValue"> limit motion to fixed angles (degrees)</label>
439 </div>
440 <div>
441 <input type="checkbox" id="fastTurn" name="fastTurn"/>
442 <label for="fastTurn">[u]</label>
443 <input type="number" id="fastTurnMult" name="fastTurnMult"/>
444 <label for="fastTurnMult">turning speed (multiplier)</label>
445 </div>
446 <div>
447 <input type="checkbox" id="locSmoothing" name="locSmoothing"/>
448 <label for="locSmoothing">[h]</label>
449 <input type="number" id="locSmoothingExp" name="locSmoothingExp"/>
450 <label for="locSmoothingExp">loc error smoothing (exp)</label>
451 </div>
452 <div>
453 <input type="checkbox" id="sound" name="sound"/>
454 <label for="sound">[x]</label>
455 <input type="range" id="volume" name="volume"/>
456 <label id="volumeLabel" for="volume">sfx volume</label>
457 </div>
458 <div>
459 <input type="checkbox" id="disableRightClick" name="disableRightClick"/>
460 <label for="disableRightClick">[;] disable right click</label>
461 </div>
462 <div>
463 <input type="checkbox" id="spawnWithMovement" name="spawnWithMovement"/>
464 <label for="spawnWithMovement">[M] movement circles on spawn</label>
465 </div>
466 <div>
467 <input type="text" id="movementCircleLoc" name="movementCircleLoc" disabled="disabled"/>
468 <label for="movementCircleLoc">[/] set loc of movement circle</label>
469 </div>
470 <div>
471 <input type="number" id="movementCircleRadius" name="movementCircleRadius" disabled="disabled"/>
472 <label for="movementCircleRadius">[<], [>] change radius of movement circle</label>
473 </div>
474 <div>[SPACE] toggle auto movement</div>
475 <div>[arrow keys] move location of movement circle</div>
476 <div>[SHIFT] hold for small changes in loc and size</div>
477 <div>[\\] toggle movement direction</div>
478 <div>[m] toggle display of movement circle</div>
479 </fieldset>
480 </div>
481 <div id="mod-config-shortcut">[~] ${modName}</div>
482 ` );
483
484 // add custom styles
485 document.head.insertAdjacentHTML( 'beforeend', `
486 <style>
487 body { overflow: hidden; }
488 .logo-text
489 {
490 color: #800000;
491 font-size: 56px;
492 position: absolute;
493 left: 150px;
494 top: -25px;
495 }
496 .mod-config-set
497 {
498 border-color: #800000;
499 border-style: solid;
500 border-radius: 4px;
501 margin: 8px;
502 }
503 .mod-config-set input[type=number], .mod-config-set input[type=text]
504 {
505 color: white;
506 background-color: rgba( 0, 0, 0, 0 );
507 border-color: #808080;
508 border-style: solid;
509 border-width: 1px;
510 }
511 .mod-config-set input[type=checkbox]
512 {
513 width: 16px;
514 height: 16px;
515 margin: 0;
516 vertical-align: middle;
517 }
518 #geoRegions
519 {
520 color: white;
521 background-color: rgba( 0, 0, 0, 0 );
522 border-color: #808080;
523 border-style: solid;
524 border-width: 1px;
525 }
526 #geoRegions option
527 {
528 background-color: #202020;
529 }
530 .mod-config-div
531 {
532 position: absolute;
533 width: 400px;
534 bottom: 12%;
535 left: 8px;
536 color: white;
537 background-color: rgba( 0, 0, 0, 0.4 );
538 border-radius: 4px;
539 z-index: 5000;
540 }
541 #mod-config-shortcut
542 {
543 color: white;
544 background-color: rgba( 0, 0, 0, 0.4 );
545 z-index: 5000;
546 position: absolute;
547 bottom: 12%;
548 left: 16px;
549 color: #a00000;
550 font-weight: bold;
551 font-size: 16px;
552 padding: 8px 8px 8px 8px;
553 }
554 .mod-config-title
555 {
556 color: #a00000;
557 font-weight: bold;
558 text-align: center;
559 font-size: 24px;
560 padding: 0px 16px 0px 16px;
561 }
562 #mod-config-version
563 {
564 font-size: 16px;
565 }
566 #serverPortValue { width: 5em; }
567 #zoomSteps { width: 3em; }
568 #deadZoneSize { width: 4em; }
569 #playerSize { width: 4em; }
570 #playerNameSize { width: 3em; }
571 #regionNameSize { width: 3em; }
572 #fixedAngleValue { width: 3em; }
573 #fastTurnMult { width: 4em; }
574 #minimapScale { width:3em; }
575 #variegationFactor { width: 4em; }
576 #locSmoothingExp { width: 4em; }
577 #colorCount { width: 3em; }
578 #minColorComp { width: 4em; }
579 #maxColorComp { width: 4em; }
580 #hueOffset { width: 4em; }
581 #regionBorderValue { width: 4em; }
582 #movementCircleLoc { width: 7em; color: #808080; }
583 #movementCircleRadius { width: 3em; color: #808080; }
584 .lbcol4
585 {
586 padding-left: 8px;
587 padding-right: 4px;
588 text-align: right;
589 vertical-align: middle;
590 min-width: 60px;
591 }
592 .floc
593 {
594 padding-left: 8px;
595 text-align: right;
596 min-width: 60px;
597 }
598 .leaderboard { border-spacing: 0px; }
599 label[disabled] { color: #808080; }
600 #volume { height: 8px; }
601 #volume:disabled { display: none; }
602 #player-list
603 {
604 display: none;
605 pointer-events: none;
606 z-index: 900;
607 color: white;
608 background-color: rgba( 0, 0, 0, 0.4 );
609 font-weight: bold;
610 position: absolute;
611 font-size: 12px;
612 bottom: 60px;
613 right: 16px;
614 padding: 8px 8px 8px 8px;
615 vertical-align: middle;
616 transition: width 1s linear;
617 }
618 #player-list #player-detail .separator
619 {
620 border-top: 1px solid #4f4f4f;
621 margin-top: 8px;
622 margin-bottom: 8px;
623 margin-left: 8px;
624 margin-right: 8px;
625 }
626 #player-list #player-detail { display: none; }
627 #player-list .shortcut { text-align: center; }
628 #player-list #player-detail table { border-collapse: collapse; }
629 #player-list #player-detail td { padding: 0; }
630 #player-list #player-detail .id
631 {
632 padding-right: 6px;
633 vertical-align: middle;
634 text-align: right;
635 }
636 #player-list #player-detail .name
637 {
638 text-align: left;
639 overflow: hidden;
640 vertical-align: middle;
641 max-width: 120px;
642 width: 120px;
643 }
644 #player-list #player-detail .rank
645 {
646 padding-left: 3px;
647 padding-right: 6px;
648 vertical-align: middle;
649 text-align: right;
650 }
651 #player-list #player-detail .kills
652 {
653 padding-left: 3px;
654 padding-right: 6px;
655 vertical-align: middle;
656 text-align: right;
657 min-width: 22px;
658 }
659 #player-list #player-detail .loc
660 {
661 text-align: right;
662 vertical-align: middle;
663 min-width: 50px;
664 }
665 #respawn .matchresults td
666 {
667 vertical-align: middle;
668 }
669 #respawn .matchresults .scoreTitle
670 {
671 white-space: nowrap;
672 margin-top: 0;
673 padding-right: 4px;
674 text-align: left;
675 }
676 #respawn .matchresults .scoreValue
677 {
678 white-space: nowrap;
679 text-align: right;
680 padding-left: 4px;
681 }
682 </style>
683 ` );
684
685 // setup the UI state for the mod config
686 setupUiModConfig();
687
688 // zoom on wheel input
689 window.addEventListener( "wheel", onWheel );
690 // don't allow keyboard events to propagate out of some input fields
691 restrictKeyboardInput( document.getElementById( "serverPortValue" ));
692 restrictKeyboardInput( document.getElementById( "username" ));
693 restrictKeyboardInput( document.getElementById( "zoomSteps" ));
694 restrictKeyboardInput( document.getElementById( "deadZoneSize" ));
695 restrictKeyboardInput( document.getElementById( "playerSize" ));
696 restrictKeyboardInput( document.getElementById( "playerNameSize" ));
697 restrictKeyboardInput( document.getElementById( "regionNameSize" ));
698 restrictKeyboardInput( document.getElementById( "fixedAngleValue" ));
699 restrictKeyboardInput( document.getElementById( "fastTurnMult" ));
700 restrictKeyboardInput( document.getElementById( "minimapScale" ));
701 restrictKeyboardInput( document.getElementById( "locSmoothingExp" ));
702 restrictKeyboardInput( document.getElementById( "variegationFactor" ));
703 restrictKeyboardInput( document.getElementById( "colorCount" ));
704 restrictKeyboardInput( document.getElementById( "minColorComp" ));
705 restrictKeyboardInput( document.getElementById( "maxColorComp" ));
706 restrictKeyboardInput( document.getElementById( "hueOffset" ));
707 restrictKeyboardInput( document.getElementById( "regionBorderValue" ));
708
709 // handle keypress events
710 var repeatKeyIntervals = {};
711 document.addEventListener( "keydown", function( e )
712 {
713 switch ( e.keyCode )
714 {
715 // pass arrow keys to onKeyPress()
716 case 37: case 38: case 39: case 40:
717 e.stopPropagation();
718 // ignore native repeat rate
719 if ( repeatKeyIntervals[ e.keyCode ] ) return;
720 onKeyPress( e );
721 // setup key repeat rate after short delay
722 repeatKeyIntervals[ e.keyCode ] = setTimeout( function() { repeatKeyIntervals[ e.keyCode ] = setInterval( function() { onKeyPress( e ); }, 100 ); }, 250 );
723 break;
724 }
725 });
726 document.addEventListener( "keyup", function( e )
727 {
728 switch ( e.keyCode )
729 {
730 // clear repeat timer for arrow keys
731 case 37: case 38: case 39: case 40:
732 e.stopPropagation();
733 clearInterval( repeatKeyIntervals[ e.keyCode ] );
734 repeatKeyIntervals[ e.keyCode ] = undefined;
735 break;
736 }
737 });
738 document.addEventListener( "keypress", onKeyPress );
739
740 // handle click on config title
741 document.getElementById( "mod-config-title" ).addEventListener( "click", function( event )
742 {
743 localStorage.setItem( "showOptions", showOptionsEnabled = false );
744 setupUiModConfig();
745 });
746 // handle click on shortcut
747 document.getElementById( "mod-config-shortcut" ).addEventListener( "click", function( event )
748 {
749 localStorage.setItem( "showOptions", showOptionsEnabled = true );
750 setupUiModConfig();
751 });
752
753 // initialize mod colors
754 initColors();
755 }
756
757 //! don't allow keyboard events within element to change mod config
758 function restrictKeyboardInput( element )
759 {
760 element.addEventListener( "keydown", function( e ) { e.stopPropagation(); });
761 element.addEventListener( "keyup", function( e ) { e.stopPropagation(); });
762 element.addEventListener( "keypress", function( e )
763 {
764 if ( e.key == "Enter" ) document.getElementById( this.name ).blur(), focusPlayButton();
765 e.stopPropagation();
766 });
767 }
768
769 //! configure UI to show current mod config
770 function setupUiModConfig()
771 {
772 // get the current config from local storage
773 getModConfig();
774
775 var optionsElem = document.getElementById( "mod-config" );
776 var optionsTagElem = document.getElementById( "mod-config-shortcut" );
777 if ( !gameActive() || showOptionsEnabled )
778 optionsElem.style.display = 'block',
779 optionsTagElem.style.display = 'none';
780 else
781 optionsElem.style.display = 'none',
782 optionsTagElem.style.display = 'block';
783
784 var geoRegionsElem = document.getElementById( "geoRegions" );
785 geoRegionsElem.onchange = function( event )
786 {
787 localStorage.setItem( "geoRegion", this.value == '' ? 'auto' : this.value );
788 // put the focus back on the play button
789 document.getElementById( this.name ).blur();
790 focusPlayButton();
791 // update based on the change
792 setupUiModConfig();
793 };
794
795 var serverPortElem = document.getElementById( "serverPort" );
796 serverPortElem.checked = serverPortEnabled;
797 serverPortElem.onchange = changeConfigCheckbox;
798
799 var serverPortValueElem = document.getElementById( "serverPortValue" );
800 serverPortValueElem.value = serverPort;
801 serverPortValueElem.onchange = changeConfigNumber;
802
803 var zoomElem = document.getElementById( "zoom" );
804 zoomElem.checked = zoomEnabled;
805 zoomElem.onchange = changeConfigCheckbox;
806
807 var tagUserElem = document.getElementById( "tagUser" );
808 tagUserElem.checked = tagUserEnabled;
809 tagUserElem.onchange = changeConfigCheckbox;
810
811 var zoomStepsElem = document.getElementById( "zoomSteps" );
812 zoomStepsElem.value = zoomSteps;
813 zoomStepsElem.onchange = changeConfigNumber;
814
815 var deadZoneElem = document.getElementById( "deadZone" );
816 deadZoneElem.checked = deadZoneEnabled;
817 deadZoneElem.onchange = changeConfigCheckbox;
818
819 var deadZoneSizeElem = document.getElementById( "deadZoneSize" );
820 deadZoneSizeElem.value = deadZoneSize;
821 deadZoneSizeElem.onchange = changeConfigNumber;
822
823 var playerSizeElem = document.getElementById( "playerSize" );
824 playerSizeElem.value = playerSize;
825 playerSizeElem.onchange = changeConfigNumber;
826
827 var playerNamesElem = document.getElementById( "playerNames" );
828 playerNamesElem.checked = playerNamesEnabled;
829 playerNamesElem.onchange = changeConfigCheckbox;
830
831 var playerNameSizeElem = document.getElementById( "playerNameSize" );
832 playerNameSizeElem.value = playerNameSize;
833 playerNameSizeElem.onchange = changeConfigNumber;
834
835 var playerSkinsElem = document.getElementById( "playerSkins" );
836 playerSkinsElem.checked = playerSkinsEnabled;
837 playerSkinsElem.onchange = changeConfigCheckbox;
838
839 var playAfterVictoryElem = document.getElementById( "playAfterVictory" );
840 playAfterVictoryElem.checked = playAfterVictoryEnabled;
841 playAfterVictoryElem.onchange = changeConfigCheckbox;
842
843 var disableRightClickElem = document.getElementById( "disableRightClick" );
844 disableRightClickElem.checked = disableRightClickEnabled;
845 disableRightClickElem.onchange = changeConfigCheckbox;
846
847 var playerLocElem = document.getElementById( "playerLoc" );
848 playerLocElem.checked = playerLocEnabled;
849 playerLocElem.onchange = changeConfigCheckbox;
850 document.getElementById( "playerLocDisplay" ).hidden = !playerLocEnabled;
851 var locCells = document.getElementById( "leaderboard" ).getElementsByClassName( "lbcol4" );
852 for ( i = 0; i < locCells.length; i++ ) locCells[ i ].hidden = !playerLocEnabled;
853
854 var showColorElem = document.getElementById( "showColor" );
855 showColorElem.checked = showColorEnabled;
856 showColorElem.onchange = changeConfigCheckbox;
857
858 var colorCountElem = document.getElementById( "colorCount" );
859 colorCountElem.value = colorCount;
860 colorCountElem.onchange = changeConfigNumber;
861
862 var enemyLinesElem = document.getElementById( "enemyLines" );
863 enemyLinesElem.checked = enemyLinesEnabled;
864 enemyLinesElem.onchange = changeConfigCheckbox;
865
866 var enemyViewsElem = document.getElementById( "enemyViews" );
867 enemyViewsElem.checked = enemyViewsEnabled;
868 enemyViewsElem.onchange = changeConfigCheckbox;
869
870 var regionNamesElem = document.getElementById( "regionNames" );
871 regionNamesElem.checked = regionNamesEnabled;
872 regionNamesElem.onchange = changeConfigCheckbox;
873
874 var regionNameSizeElem = document.getElementById( "regionNameSize" );
875 regionNameSizeElem.value = regionNameSize;
876 regionNameSizeElem.onchange = changeConfigNumber;
877
878 var updateColorsElem = document.getElementById( "updateColors" );
879 updateColorsElem.checked = updateColorsEnabled;
880 updateColorsElem.onchange = changeConfigCheckbox;
881
882 var playerIconElem = document.getElementById( "playerIcon" ),
883 playerIconLabelElem = document.getElementById( "playerIconLabel" );
884 playerIconElem.checked = playerIconEnabled;
885 playerIconElem.onchange = changeConfigCheckbox;
886 if ( updateColorsEnabled ) { playerIconElem.disabled = true; playerIconLabelElem.setAttribute( "disabled","disabled" ); }
887 else { playerIconElem.disabled = false; playerIconLabelElem.removeAttribute( "disabled" ); }
888
889 var regionBorderElem = document.getElementById( "regionBorder" );
890 regionBorderElem.checked = regionBorderEnabled;
891 regionBorderElem.onchange = changeConfigCheckbox;
892
893 var regionBorderValueElem = document.getElementById( "regionBorderValue" );
894 regionBorderValueElem.value = regionBorderValue;
895 regionBorderValueElem.onchange = changeConfigNumber;
896
897 var fastTransitionsElem = document.getElementById( "fastTransitions" );
898 fastTransitionsElem.checked = fastTransitionsEnabled;
899 fastTransitionsElem.onchange = changeConfigCheckbox;
900
901 var fixedAngleElem = document.getElementById( "fixedAngle" );
902 fixedAngleElem.checked = fixedAngleEnabled;
903 fixedAngleElem.onchange = changeConfigCheckbox;
904
905 var fixedAngleValueElem = document.getElementById( "fixedAngleValue" );
906 fixedAngleValueElem.value = fixedAngle;
907 fixedAngleValueElem.onchange = changeConfigNumber;
908
909 var fastTurnElem = document.getElementById( "fastTurn" );
910 fastTurnElem.checked = fastTurnEnabled;
911 fastTurnElem.onchange = changeConfigCheckbox;
912
913 var fastTurnMultElem = document.getElementById( "fastTurnMult" );
914 fastTurnMultElem.value = fastTurnMult;
915 fastTurnMultElem.onchange = changeConfigNumber;
916
917 var movementCircleLocElem = document.getElementById( "movementCircleLoc" );
918 movementCircleLocElem.value = Math.round( movementCircleX * 10 ) / 10 + ", " + Math.round( -movementCircleY * 10 ) / 10;
919
920 var movementCircleRadiusElem = document.getElementById( "movementCircleRadius" );
921 movementCircleRadiusElem.value = movementCircleRadius;
922
923 var soundElem = document.getElementById( "sound" );
924 soundElem.checked = soundEnabled;
925 soundElem.onchange = changeConfigCheckbox;
926
927 var volumeElem = document.getElementById( "volume" ),
928 volumeLabelElem = document.getElementById( "volumeLabel" );
929 soundEnabled ? volumeElem.value = volume : volumeElem.value = 0;
930 volumeElem.onchange = changeConfigNumber;
931 if ( soundEnabled ) { volumeElem.disabled = false; volumeLabelElem.innerHTML="sfx volume"; volumeLabelElem.removeAttribute( "disabled" ); }
932 else { volumeElem.disabled = true; volumeLabelElem.innerHTML="sfx volume (muted)"; volumeLabelElem.setAttribute( "disabled","disabled" ); }
933
934 var minimapMarkerElem = document.getElementById( "minimapMarker" );
935 minimapMarkerElem.checked = minimapMarkerEnabled;
936 minimapMarkerElem.onchange = changeConfigCheckbox;
937
938 var minimapScaleElem = document.getElementById( "minimapScale" );
939 minimapScaleElem.value = minimapScale;
940 minimapScaleElem.onchange = changeConfigNumber;
941 // update the size of the minimap
942 undefined != minimapBaseWidth && ( minimap.style.width = ( minimapScale * minimapBaseWidth ) + 'px' );
943 undefined != minimapBaseHeight && ( minimap.style.height = ( minimapScale * minimapBaseHeight ) + 'px' );
944 optionsTagElem.style.bottom = minimapScale > 1 ? '16px' : '12%';
945 minimap.style.bottom = minimapScale > 1 ? '60px' : '16px';
946
947 var locSmoothingElem = document.getElementById( "locSmoothing" );
948 locSmoothingElem.checked = locSmoothingEnabled;
949 locSmoothingElem.onchange = changeConfigCheckbox;
950
951 var locSmoothingExpElem = document.getElementById( "locSmoothingExp" );
952 locSmoothingExpElem.value = locSmoothingExp;
953 locSmoothingExpElem.onchange = changeConfigNumber;
954
955 var variegationElem = document.getElementById( "variegation" );
956 variegationElem.checked = variegationEnabled;
957 variegationElem.onchange = changeConfigCheckbox;
958
959 var variegationFactorElem = document.getElementById( "variegationFactor" );
960 variegationFactorElem.value = variegationFactor;
961 variegationFactorElem.onchange = changeConfigNumber;
962
963 var minColorCompElem = document.getElementById( "minColorComp" );
964 minColorCompElem.value = minColorComp;
965 minColorCompElem.onchange = changeConfigNumber;
966
967 var maxColorCompElem = document.getElementById( "maxColorComp" );
968 maxColorCompElem.value = maxColorComp;
969 maxColorCompElem.onchange = changeConfigNumber;
970
971 var hueOffsetElem = document.getElementById( "hueOffset" );
972 hueOffsetElem.value = hueOffset;
973 hueOffsetElem.onchange = changeConfigNumber;
974
975 var leaderboardEnabledElem = document.getElementById( "leaderboardEnabled" );
976 leaderboardEnabledElem.checked = leaderboardEnabled;
977 leaderboardEnabledElem.onchange = changeConfigCheckbox;
978
979 var spawnWithMovementElem = document.getElementById( "spawnWithMovement" );
980 spawnWithMovementElem.checked = spawnWithMovementEnabled;
981 spawnWithMovementElem.onchange = changeConfigCheckbox;
982 }
983
984 //! get mod config from local storage, handles changes from previous settings
985 function getModConfig()
986 {
987 if ( "undefined" == typeof Storage ) return;
988 localStorage.getItem( "geoRegion" ) && ( geoRegion = localStorage.getItem( "geoRegion" ));
989 localStorage.getItem( "serverPort" ) && ( serverPortEnabled = localStorage.getItem( "serverPort" ) == "true" );
990 localStorage.getItem( "serverPortValue" ) && ( serverPort = Number( localStorage.getItem( "serverPortValue" )));
991 if ( isNaN( serverPort ) || ( serverPort < 0 )) localStorage.setItem( "serverPortValue", serverPort = "" );
992 localStorage.getItem( "showOptions" ) && ( showOptionsEnabled = localStorage.getItem( "showOptions" ) == "true" );
993 localStorage.getItem( "tagUser" ) && ( tagUserEnabled = localStorage.getItem( "tagUser" ) == "true" );
994 localStorage.getItem( "zoom" ) && ( zoomEnabled = localStorage.getItem( "zoom" ) == "true" );
995 localStorage.getItem( "zoomSteps" ) && ( zoomSteps = Number( localStorage.getItem( "zoomSteps" )));
996 if ( isNaN( zoomSteps ) || ( zoomSteps < 1 )) localStorage.setItem( "zoomSteps", zoomSteps = 1 );
997 setZoomStepFactor();
998 !zoomEnabled && ( zoom = 1.0 ) && gameActive() && resize();
999 localStorage.getItem( "deadZone" ) && ( deadZoneEnabled = localStorage.getItem( "deadZone" ) == "true" );
1000 localStorage.getItem( "deadZoneSize" ) && ( deadZoneSize = Number( localStorage.getItem( "deadZoneSize" ))), setDeadZoneSizeSqr();
1001 if ( isNaN( deadZoneSize ) || ( deadZoneSize < 0 )) localStorage.setItem( "deadZoneSize", deadZoneSize = 0 );
1002 setDeadZoneSizeSqr();
1003 var lastPlayerSize = playerSize;
1004 localStorage.getItem( "playerSize" ) && ( playerSize = Number( localStorage.getItem( "playerSize" )));
1005 if ( isNaN( playerSize ) || ( playerSize < 0 )) localStorage.setItem( "playerSize", playerSize = 0 );
1006 if ( lastPlayerSize != playerSize ) updatePlayerIcons();
1007 var lastPlayerNamesEnabled = playerNamesEnabled;
1008 localStorage.getItem( "playerNames" ) && ( playerNamesEnabled = localStorage.getItem( "playerNames" ) == "true" );
1009 if ( lastPlayerNamesEnabled != playerNamesEnabled ) updatePlayerNameVisibility();
1010 var lastPlayerNameSize = playerNameSize;
1011 localStorage.getItem( "playerNameSize" ) && ( playerNameSize = Number( localStorage.getItem( "playerNameSize" )));
1012 if ( isNaN( playerNameSize ) || ( playerNameSize < 2 )) localStorage.setItem( "playerNameSize", playerNameSize = 2 );
1013 if ( lastPlayerNameSize != playerNameSize ) updatePlayerTextSize();
1014 var lastPlayerSkinsEnabled = playerSkinsEnabled;
1015 localStorage.getItem( "playerSkins" ) && ( playerSkinsEnabled = localStorage.getItem( "playerSkins" ) == "true" );
1016 if ( lastPlayerSkinsEnabled != playerSkinsEnabled ) updatePlayerIcons();
1017 localStorage.getItem( "playAfterVictory" ) && ( playAfterVictoryEnabled = localStorage.getItem( "playAfterVictory" ) == "true" );
1018 updateVictoryFlag();
1019 localStorage.getItem( "disableRightClick" ) && ( disableRightClickEnabled = localStorage.getItem( "disableRightClick" ) == "true" );
1020 var lastPlayerLocEnabled = playerLocEnabled;
1021 localStorage.getItem( "playerLoc" ) && ( playerLocEnabled = localStorage.getItem( "playerLoc" ) == "true" );
1022 if ( gameActive() && lastPlayerLocEnabled != playerLocEnabled ) updateLeaderBoard();
1023 var lastShowColorEnabled = showColorEnabled;
1024 localStorage.getItem( "showColor" ) && ( showColorEnabled = localStorage.getItem( "showColor" ) == "true" );
1025 if ( gameActive() && lastShowColorEnabled != showColorEnabled ) updateLeaderBoard();
1026 var lastMinColorComp = minColorComp;
1027 localStorage.getItem( "minColorComp" ) && ( minColorComp = Number( localStorage.getItem( "minColorComp" )));
1028 if ( isNaN( minColorComp ) || ( minColorComp < 0 )) localStorage.setItem( "minColorComp", minColorComp = 0 );
1029 var lastMaxColorComp = maxColorComp;
1030 localStorage.getItem( "maxColorComp" ) && ( maxColorComp = Number( localStorage.getItem( "maxColorComp" )));
1031 if ( isNaN( maxColorComp ) || ( maxColorComp > 1 )) localStorage.setItem( "maxColorComp", maxColorComp = 1 );
1032 var lastColorCount = colorCount;
1033 localStorage.getItem( "colorCount" ) && ( colorCount = Number( localStorage.getItem( "colorCount" )));
1034 if ( isNaN( colorCount ) || ( colorCount < 1 )) localStorage.setItem( "colorCount", colorCount = 1 );
1035 var lastHueOffset = hueOffset;
1036 localStorage.getItem( "hueOffset" ) && ( hueOffset = Number( localStorage.getItem( "hueOffset" )));
1037 if ( isNaN( hueOffset ) || ( hueOffset > 1 )) localStorage.setItem( "hueOffset", hueOffset = 1 );
1038 else if ( hueOffset < 0 ) localStorage.setItem( "hueOffset", hueOffset = 0 );
1039 if ( lastColorCount != colorCount || lastMinColorComp != minColorComp || lastMaxColorComp != maxColorComp || lastHueOffset != hueOffset )
1040 {
1041 // update the color set
1042 initColors();
1043 // refreshes player regions
1044 if ( gameActive() ) resize(), updateLeaderBoard(), updatePlayerList();
1045 // updates the color of the player trails
1046 updatePlayerTrails();
1047 // update the player icons
1048 updatePlayerIcons();
1049 // update the minimap marker colors
1050 updateMinimapMarkerColors();
1051 }
1052 localStorage.getItem( "enemyLines" ) && ( enemyLinesEnabled = localStorage.getItem( "enemyLines" ) == "true" );
1053 localStorage.getItem( "enemyViews" ) && ( enemyViewsEnabled = localStorage.getItem( "enemyViews" ) == "true" );
1054 localStorage.getItem( "regionNames" ) && ( regionNamesEnabled = localStorage.getItem( "regionNames" ) == "true" );
1055 var lastRegionNameSize = regionNameSize;
1056 localStorage.getItem( "regionNameSize" ) && ( regionNameSize = Number( localStorage.getItem( "regionNameSize" )));
1057 if ( isNaN( regionNameSize ) || ( regionNameSize < 2 )) localStorage.setItem( "regionNameSize", regionNameSize = 2 );
1058 if ( lastRegionNameSize != regionNameSize ) updateRegionTextSize();
1059 var lastUpdateColorsEnabled = updateColorsEnabled;
1060 localStorage.getItem( "updateColors" ) && ( updateColorsEnabled = localStorage.getItem( "updateColors" ) == "true" );
1061 if ( lastUpdateColorsEnabled != updateColorsEnabled )
1062 {
1063 // force use of player icon when using mod colors
1064 if ( updateColorsEnabled ) { localStorage.setItem( "playerIcon", true ); playerIconEnabled = true; }
1065 // refreshes player regions
1066 if ( gameActive() ) resize(), updateLeaderBoard(), updatePlayerList();
1067 // updates the color of the player trails
1068 updatePlayerTrails();
1069 // update the player icons
1070 updatePlayerIcons();
1071 // update the minimap marker colors
1072 updateMinimapMarkerColors();
1073 }
1074 var lastplayerIconEnabled = playerIconEnabled;
1075 localStorage.getItem( "playerIcon" ) && ( playerIconEnabled = localStorage.getItem( "playerIcon" ) == "true" );
1076 if ( lastplayerIconEnabled != playerIconEnabled ) updatePlayerIcons();
1077 localStorage.getItem( "regionBorder" ) && ( regionBorderEnabled = localStorage.getItem( "regionBorder" ) == "true" );
1078 localStorage.getItem( "regionBorderValue" ) && ( regionBorderValue = Number( localStorage.getItem( "regionBorderValue" )));
1079 if ( isNaN( regionBorderValue ) || ( regionBorderValue < 0 )) localStorage.setItem( "regionBorderValue", regionBorderValue = 0 );
1080 else if ( regionBorderValue > 1 ) localStorage.setItem( "regionBorderValue", regionBorderValue = 1 );
1081 localStorage.getItem( "fastTransitions" ) && ( fastTransitionsEnabled = localStorage.getItem( "fastTransitions" ) == "true" );
1082 localStorage.getItem( "fixedAngle" ) && ( fixedAngleEnabled = localStorage.getItem( "fixedAngle" ) == "true" );
1083 localStorage.getItem( "fixedAngleValue" ) && ( fixedAngle = Number( localStorage.getItem( "fixedAngleValue" )));
1084 if ( isNaN( fixedAngle ) || ( fixedAngle < 0 )) localStorage.setItem( "fixedAngleValue", fixedAngle = 1 );
1085 localStorage.getItem( "fastTurn" ) && ( fastTurnEnabled = localStorage.getItem( "fastTurn" ) == "true" );
1086 localStorage.getItem( "fastTurnMult" ) && ( fastTurnMult = Number( localStorage.getItem( "fastTurnMult" )));
1087 if ( isNaN( fastTurnMult ) || ( fastTurnMult < 0 )) localStorage.setItem( "fastTurnMult", fastTurnMult = 1 );
1088 localStorage.getItem( "sound" ) && ( soundEnabled = localStorage.getItem( "sound" ) == "true" );
1089 localStorage.getItem( "volume" ) && ( volume = Number( localStorage.getItem( "volume" )));
1090 if ( isNaN( volume ) || ( volume > 100 )) localStorage.setItem( "volume", volume = 100 );
1091 else if ( volume < 0 ) localStorage.setItem( "volume", volume = 0 );
1092 var lastMinimapMarkerEnabled = minimapMarkerEnabled;
1093 localStorage.getItem( "minimapMarker" ) && ( minimapMarkerEnabled = localStorage.getItem( "minimapMarker" ) == "true" );
1094 if ( lastMinimapMarkerEnabled != minimapMarkerEnabled ) updateMinimapMarkerVisible();
1095 localStorage.getItem( "minimapScale" ) && ( minimapScale = Number( localStorage.getItem( "minimapScale" )));
1096 if ( isNaN( minimapScale ) || ( minimapScale < 0 )) localStorage.setItem( "minimapScale", minimapScale = 0 );
1097 localStorage.getItem( "locSmoothing" ) && ( locSmoothingEnabled = localStorage.getItem( "locSmoothing" ) == "true" );
1098 localStorage.getItem( "locSmoothingExp" ) && ( locSmoothingExp = Number( localStorage.getItem( "locSmoothingExp" )));
1099 if ( isNaN( locSmoothingExp ) || ( locSmoothingExp < 1 )) localStorage.setItem( "locSmoothingExp", locSmoothingExp = 1 );
1100 var lastVariegationEnabled = variegationEnabled, lastVariegationFactor = variegationFactor;
1101 localStorage.getItem( "variegation" ) && ( variegationEnabled = localStorage.getItem( "variegation" ) == "true" );
1102 localStorage.getItem( "variegationFactor" ) && ( variegationFactor = Number( localStorage.getItem( "variegationFactor" )));
1103 if ( isNaN( variegationFactor ) || ( variegationFactor < 0 )) localStorage.setItem( "variegationFactor", variegationFactor = 0 );
1104 if (( lastVariegationEnabled != variegationEnabled || lastVariegationFactor != variegationFactor ) && gameActive() ) resize();
1105 var lastLeaderboardEnabled = leaderboardEnabled;
1106 localStorage.getItem( "leaderboard" ) && ( leaderboardEnabled = localStorage.getItem( "leaderboard" ) == "true" );
1107 if ( gameActive() && lastLeaderboardEnabled != leaderboardEnabled ) updateLeaderBoard();
1108 localStorage.getItem( "spawnWithMovement" ) && ( spawnWithMovementEnabled = localStorage.getItem( "spawnWithMovement" ) == "true" );
1109 }
1110
1111 //! get mod config item UI
1112 function changeConfigCheckbox( event )
1113 {
1114 // set the value in local storage
1115 localStorage.setItem( this.name, this.checked );
1116 // put the focus back on the play button
1117 document.getElementById( this.name ).blur();
1118 focusPlayButton();
1119 // update based on the change
1120 setupUiModConfig();
1121 }
1122
1123 //! get mod config item UI
1124 function changeConfigNumber( event )
1125 {
1126 // set the value in local storage
1127 localStorage.setItem( this.name, this.valueAsNumber );
1128 // put the focus back on the play button
1129 document.getElementById( this.name ).blur();
1130 focusPlayButton();
1131 // update based on the change
1132 setupUiModConfig();
1133 }
1134
1135 //! handle additional mod keys at document level
1136 function onKeyPress( event )
1137 {
1138 var setupUiConfig = true;
1139
1140 // Note: Can't use 's' or 'f', already in use
1141 switch ( event.key )
1142 {
1143 case '`': case '~': localStorage.setItem( "showOptions", showOptionsEnabled = !showOptionsEnabled ); break;
1144 // '+' zoom in, '-' zoom out
1145 case '+': case '=': doZoom( true ); setupUiConfig = false; break;
1146 case '-': case '_': doZoom( false ); setupUiConfig = false; break;
1147 case 'q': localStorage.setItem( "serverPort", !serverPortEnabled ); break;
1148 case 't': localStorage.setItem( "tagUser", !tagUserEnabled ); break;
1149 case 'z': localStorage.setItem( "zoom", !zoomEnabled ); break;
1150 case 'd': localStorage.setItem( "deadZone", !deadZoneEnabled ); break;
1151 case 'p': localStorage.setItem( "playAfterVictory", !playAfterVictoryEnabled ); break;
1152 case 'w': localStorage.setItem( "playerLoc", !playerLocEnabled ); break;
1153 case 'c': localStorage.setItem( "showColor", !showColorEnabled ); break;
1154 case 'e': localStorage.setItem( "enemyLines", !enemyLinesEnabled ); break;
1155 case 'v': localStorage.setItem( "enemyViews", !enemyViewsEnabled ); break;
1156 case 'n': localStorage.setItem( "playerNames", !playerNamesEnabled ); break;
1157 case 'j': localStorage.setItem( "playerSkins", !playerSkinsEnabled ); break;
1158 case 'r': localStorage.setItem( "regionNames", !regionNamesEnabled ); break;
1159 case 'o': localStorage.setItem( "updateColors", !updateColorsEnabled ); break;
1160 case 'i': if ( !updateColorsEnabled ) localStorage.setItem( "playerIcon", !playerIconEnabled ); break;
1161 case 'b': localStorage.setItem( "regionBorder", !regionBorderEnabled ); break;
1162 case 'a': localStorage.setItem( "fastTransitions", !fastTransitionsEnabled ); break;
1163 case 'g': localStorage.setItem( "fixedAngle", !fixedAngleEnabled ); break;
1164 case 'u': localStorage.setItem( "fastTurn", !fastTurnEnabled ); break;
1165 case 'm': showMovementCircleEnabled =! showMovementCircleEnabled; break;
1166 case ';': localStorage.setItem( "disableRightClick", !disableRightClickEnabled ); break;
1167 case ' ': movementCircleEnabled ? stopMovementCircles() : startMovementCircles(); break;
1168 case '\\': case '|': movementClockwise =! movementClockwise; break;
1169 case '0': movementCircleRadius = 0; break;
1170 case '1': movementCircleRadius = 1; break;
1171 case '2': movementCircleRadius = 2; break;
1172 case '3': movementCircleRadius = 4; break;
1173 case '4': movementCircleRadius = 8; break;
1174 case '5': movementCircleRadius = 16; break;
1175 case '<': case ',':
1176 var step = 0.5 * ( event.shiftKey ? 1/3 : 1 );
1177 movementCircleRadius = ( Math.ceil( movementCircleRadius / step - 0.000001 ) - 1 ) * step;
1178 if ( movementCircleRadius <= 0 ) movementCircleRadius = 0;
1179 break;
1180 case '>': case '.':
1181 var step = 0.5 * ( event.shiftKey ? 1/3 : 1 );
1182 movementCircleRadius = ( Math.floor( movementCircleRadius / step + 0.000001 ) + 1 ) * step;
1183 break;
1184 case 'ArrowRight':
1185 var step = 0.75 * ( event.shiftKey ? 1/3 : 1 );
1186 movementCircleX = ( Math.floor( movementCircleX / step + 0.000001 ) + 1 ) * step;
1187 break;
1188 case 'ArrowLeft':
1189 var step = 0.75 * ( event.shiftKey ? 1/3 : 1 );
1190 movementCircleX = ( Math.ceil( movementCircleX / step - 0.000001 ) - 1 ) * step;
1191 break;
1192 case 'ArrowDown':
1193 var step = ( 0.5 * hexHeight() / scale() ) * ( event.shiftKey ? 1/3 : 1 );
1194 movementCircleY = ( Math.floor( movementCircleY / step + 0.000001 ) + 1 ) * step;
1195 break;
1196 case 'ArrowUp':
1197 var step = ( 0.5 * hexHeight() / scale() ) * ( event.shiftKey ? 1/3 : 1 );
1198 movementCircleY = ( Math.ceil( movementCircleY / step - 0.000001 ) - 1 ) * step;
1199 break;
1200 case '/': setMoveCircleLoc(); break;
1201 case 'x': localStorage.setItem( "sound", !soundEnabled ); break;
1202 case 'k': localStorage.setItem( "minimapMarker", !minimapMarkerEnabled ); break;
1203 case 'f': setFriendsEnabled( !friendsEnabled() ); if ( friendsEnabled() ) playerListEnabled = false; updatePlayerList(); break;
1204 case '"': case "'": playerListEnabled = !playerListEnabled; if ( playerListEnabled ) setFriendsEnabled( false ); updatePlayerList(); break;
1205 case 'h': localStorage.setItem( "locSmoothing", !locSmoothingEnabled ); break;
1206 case 'y': localStorage.setItem( "variegation", !variegationEnabled ); break;
1207 case 'l': localStorage.setItem( "leaderboard", !leaderboardEnabled ); break;
1208 case 'M': localStorage.setItem( "spawnWithMovement", !spawnWithMovementEnabled ); break;
1209 case '[': localStorage.setItem( "colorCount", colorCount - 1 ); break;
1210 case ']': localStorage.setItem( "colorCount", colorCount + 1 ); break;
1211 default: setupUiConfig = false;
1212 }
1213
1214 // update based on the change
1215 if ( setupUiConfig ) setupUiModConfig();
1216 }
1217
1218 //! set focus on the play button
1219 function focusPlayButton()
1220 {
1221 if ( gameActive() ) document.getElementById( "button-play-again" ).focus();
1222 else document.getElementById( "button-play" ).focus();
1223 }
1224 //! update username to indicate that a mod is being used
1225 function tagUserName() { tagUserEnabled && ( setUsername(( "꙳" + username() ).substring( 0, 15 ))); }
1226
1227 //! calculate zoom step factor
1228 function setZoomStepFactor() { zoomStepFactor = Math.exp(( Math.log( minZoom ) - Math.log( maxZoom )) / zoomSteps ); }
1229
1230 //! set dead zone sqr value, used in actual comparison
1231 function setDeadZoneSizeSqr() { deadZoneSizeSqr = deadZoneSize * deadZoneSize / ( pixelRatio() * pixelRatio() ); }
1232
1233 //! clear victory state if needed
1234 function updateVictoryFlag()
1235 {
1236 if ( victoryFlag() && playAfterVictoryEnabled )
1237 {
1238 setVictoryFlag( false );
1239 document.getElementById("victory").style.display = "none";
1240 }
1241 }
1242
1243 //! perform zoom on wheel movement
1244 function onWheel( e )
1245 {
1246 if ( e.deltaY > 0 ) doZoom( false );
1247 else if ( e.deltaY < 0 ) doZoom( true );
1248 }
1249
1250 //! direction == 'true' zoom in
1251 function doZoom( direction )
1252 {
1253 if ( !zoomEnabled ) return;
1254
1255 var oldZoom = zoom;
1256 if ( direction ) zoom /= zoomStepFactor;
1257 else zoom *= zoomStepFactor;
1258 // restrict zoom
1259 if ( zoom > maxZoom ) zoom = maxZoom;
1260 else if ( zoom < minZoom ) zoom = minZoom;
1261 // resize with the new zoom factor
1262 if ( oldZoom != zoom ) resize();
1263 };
1264
1265 //! set last known location for all players
1266 function setLastKnownLoc()
1267 {
1268 for ( var player in playerXLoc() )
1269 {
1270 playerLastLocX[ player ] = playerXLoc()[ player ];
1271 playerLastLocY[ player ] = playerYLoc()[ player ];
1272 }
1273 }
1274
1275 //! function to produce location text for the player
1276 function locStr( player )
1277 {
1278 var xLoc = playerXLoc(), yLoc = playerYLoc();
1279 if (( player >= 0 ) && xLoc[ player ] )
1280 return Math.round( xLoc[ player ] ) + ", " + Math.round( -yLoc[ player ] )
1281 else if (( player >= 0 ) && playerLastLocX[ player ] )
1282 return Math.round( playerLastLocX[ player ] ) + ", " + Math.round( -playerLastLocY[ player ] );
1283 else
1284 return "";
1285 }
1286
1287 //! function to produce html color string for the player
1288 function locColor( player )
1289 {
1290 if ( player < 0 ) return "";
1291 else if ( playerXLoc()[ player ] ) return "white";
1292 else return "gray";
1293 }
1294
1295 //! set leaderboard location text
1296 function setLeaderLoc( index, player )
1297 {
1298 var locElem = document.getElementById( "lb" + index + "Loc" );
1299 locElem.innerHTML = locStr( player );
1300 // set color of location info
1301 locElem.style.color = locColor( player );
1302 }
1303
1304 //! set leaderboard background color to highlight current player
1305 function setLeaderBackground( index, player )
1306 {
1307 var nameElement = document.getElementById( "lb" + index + "Name" );
1308 // set the background color to highlight the current player
1309 if ( player == currentPlayer() ) nameElement.parentElement.style.backgroundColor = "rgba( 128, 128, 128, 0.4 )";
1310 // otherwise set to default
1311 else nameElement.parentElement.style.backgroundColor = "";
1312 }
1313
1314 //! set leaderboard color to the player's color
1315 function setLeaderColor( index, player )
1316 {
1317 var style = document.getElementById( "lb" + index + "Name" ).parentElement.style;
1318 if ( showColorEnabled && ( player >= 0 )) style.color = PIXI.utils.hex2string( playerColor( player ));
1319 else style.color = "";
1320 }
1321
1322 //! update leaderboard content
1323 function updateLeaderBoard()
1324 {
1325 // optionally display or hide the leaderboard
1326 document.getElementById("leaderboard").style.display = leaderboardEnabled ? "block" : "none";
1327 var lb = leaderboard();
1328 leaderboardRanks = [];
1329 for ( var i = 0; i < lb.length; i++ )
1330 {
1331 // track player rank
1332 leaderboardRanks[ lb[ i ].playerId ] = i + 1;
1333 // update the leaderboard entry
1334 var fieldIndex = i + 1, player = lb[i].playerId;
1335 if ( lb[ i ].playerId > 0 )
1336 {
1337 setLeaderLoc( fieldIndex, player );
1338 setLeaderBackground( fieldIndex, player );
1339 setLeaderColor( fieldIndex, player );
1340 }
1341 else
1342 {
1343 setLeaderLoc( fieldIndex, -1 );
1344 setLeaderBackground( fieldIndex, -1 );
1345 setLeaderColor( fieldIndex, -1 );
1346 }
1347 }
1348 }
1349
1350 //! create content for the list of friends, with support for color and location info
1351 //!! implementation needs to be kept in sync with the function it is called from, which it replaces
1352 function updateFriendsList()
1353 {
1354 var f = friends(), pn = playerNames();
1355 friendRanks = [];
1356 var table = '<table class="table">';
1357 for ( var i in f )
1358 {
1359 // track player rank
1360 friendRanks[ i ] = f[i].rank;
1361 // construct the friends content
1362 if ( f[i].rank > 0 )
1363 {
1364 var player = f[i].playerId,
1365 color = showColorEnabled ? `style="color:${PIXI.utils.hex2string( playerColor( player ))};"` : "",
1366 locColr = playerLocEnabled ? `style="color:${locColor( player )};"` : "",
1367 locCell = playerLocEnabled ? `<td class="floc" ${locColr}>${locStr( player )}</td>`: "";
1368 table += `
1369 <tr ${color}>
1370 <td class="rank">${f[i].rank}.</td>
1371 <td class="name">${pn[ player ]}</td>
1372 <td class="fscore">${f[i].score}</td>
1373 ${locCell}
1374 </tr>
1375 `;
1376 }
1377 }
1378 table += '</table><div class="separator"></div>';
1379 document.getElementById("friends-detail").innerHTML = table;
1380 }
1381
1382 //! add markup that sits on top of hexes
1383 function addBackgroundMarkup()
1384 {
1385 playerLayer().addChild( regionBorders );
1386 playerLayer().addChild( markUp );
1387 }
1388
1389 //! add enemy overlay to the player layer
1390 function addEnemyOverlay()
1391 {
1392 enemyOverlayLayer = new PIXI.Container;
1393 playerLayer().addChild( enemyOverlayLayer );
1394 enemyOverlayLayer.addChild( enemyViews );
1395 enemyOverlayLayer.addChild( enemyLines );
1396 }
1397
1398 //! add region overlay to the player layer
1399 function addRegionOverlay()
1400 {
1401 regionOverlay = new PIXI.Container;
1402 playerLayer().addChild( regionOverlay );
1403 }
1404
1405 //! draw graphics that mark up enemies
1406 function drawEnemyOverlay()
1407 {
1408 // skip if nothing to do
1409 if (( !enemyViewsEnabled && !enemyLinesEnabled ) || !gameActive )
1410 { enemyLines.clear(); enemyViews.clear(); return; }
1411 var xLoc = playerXLoc(), yLoc = playerYLoc(), thisPlayer = currentPlayer(), s = scale();
1412 // clear the overlay graphics
1413 enemyLines.clear();
1414 enemyViews.clear();
1415 for ( var player in xLoc )
1416 if ( thisPlayer != player )
1417 {
1418 var color = playerColor( player );
1419
1420 // draw view area of enemy
1421 if ( enemyViewsEnabled )
1422 {
1423 enemyViews.lineStyle( 5, 0, 0.5 );
1424 enemyViews.drawRect( xLoc[ player ] * s - zoom * window.innerWidth / 2, yLoc[ player ] * s - zoom * window.innerHeight / 2, window.innerWidth * zoom, window.innerHeight * zoom );
1425 enemyViews.lineStyle( 3, color, 0.3 );
1426 enemyViews.beginFill( color, 0.03 );
1427 enemyViews.drawRect( xLoc[ player ] * s - zoom * window.innerWidth / 2, yLoc[ player ] * s - zoom * window.innerHeight / 2, window.innerWidth * zoom, window.innerHeight * zoom );
1428 enemyViews.endFill();
1429 }
1430
1431 // draw line to each known enemy if current player is not dead
1432 if ( enemyLinesEnabled && !playerDead() )
1433 {
1434 enemyLines.lineStyle( s * 0.4, 0, 0.5 );
1435 enemyLines.moveTo( xLoc[ thisPlayer ] * s, yLoc[ thisPlayer ] * s );
1436 enemyLines.lineTo( xLoc[ player ] * s, yLoc[ player ] * s );
1437 enemyLines.lineStyle( s * 0.3, color, 0.3 );
1438 enemyLines.moveTo( xLoc[ thisPlayer ] * s, yLoc[ thisPlayer ] * s );
1439 enemyLines.lineTo( xLoc[ player ] * s, yLoc[ player ] * s );
1440 }
1441 }
1442 }
1443
1444 //! draw the graphics that mark up player regions
1445 function drawRegionOverlay()
1446 {
1447 var xLoc = playerXLoc(), yLoc = playerYLoc(), thisPlayer = currentPlayer(), s = scale(), h = hexAreaRadius();
1448 // don't consider at the edges of the view region, they often contain out-dated info
1449 h--;
1450 // don't display if the game isn't active
1451 if ( !regionNamesEnabled || !gameActive() )
1452 {
1453 for ( pid in regionInfo )
1454 {
1455 var region = regionInfo[ pid ];
1456 region.active = region.text.visible = false; delete region.point;
1457 }
1458 return;
1459 }
1460 // center search on the location of the current player
1461 var centerPoint = playerDead() ?
1462 new PIXI.Point( currentPlayerDeathX() * s, currentPlayerDeathY() * s ) :
1463 new PIXI.Point( xLoc[ thisPlayer ] * s, yLoc[ thisPlayer ] * s ),
1464 centerHexIndex = rectToHexIndex( centerPoint.x, centerPoint.y );
1465 // loop over the active area
1466 for ( var i = -h; i <= h; i++ )
1467 for ( var j = -h; j <= h; j++ )
1468 {
1469 // skip if outside the region
1470 if ( Math.abs( i + j ) > h ) continue;
1471 // hex location offset in area
1472 var q = centerHexIndex.q + i, r = centerHexIndex.r + j;
1473 // id for the player from the hex coord
1474 var pid = getHexPid( q, r );
1475 // skip cell if not owned by anyone, if owned by current player
1476 if (( pid <= 0 ) || ( pid == thisPlayer ) ) continue;
1477 // skip if player is currently visible
1478 if ( xLoc[ pid ] && Math.abs( centerPoint.x - xLoc[ pid ] * scale() ) < window.innerWidth / 2 &&
1479 Math.abs( centerPoint.y - yLoc[ pid ] * scale() ) < window.innerHeight / 2 )
1480 continue;
1481 // create new region entry if one doesn't exist for the player
1482 if ( !regionInfo[ pid ] )
1483 {
1484 var text = new PIXI.Text( "", {
1485 fontFamily: "Arial", fontSize: regionNameFontSize(), fontWeight: "bold",
1486 fill: 0xffffff, stroke: 0, strokeThickness: 1,
1487 dropShadow: true, dropShadowColor: 0, dropShadowDistance: 0, dropShadowBlur: regionNameFontSize() / 2
1488 });
1489 text.anchor.x = text.anchor.y = 0.5;
1490 regionOverlay.addChild( text );
1491 regionInfo[ pid ] = {
1492 active : false,
1493 // all cells within active area
1494 xSumAll: 0, ySumAll : 0, countAll : 0,
1495 // only those cells that can currently be seen
1496 xSumView: 0, ySumView : 0, countView : 0,
1497 text : text
1498 };
1499 }
1500 // activate the region for the player
1501 var region = regionInfo[ pid ];
1502 if ( !region.active )
1503 {
1504 region.active = true;
1505 region.text.text = rawPlayerNames[ pid ];
1506 region.text.style.fill = playerColor( pid );
1507 region.text.visible = true;
1508 }
1509 // get the point
1510 var point = hexToRectIndex( q, r );
1511 // sum the location info for all cells
1512 region.xSumAll += point.x;
1513 region.ySumAll += point.y;
1514 region.countAll++;
1515 // skip counting the hex cell if outside the current view
1516 if ( Math.abs( centerPoint.x - point.x ) >= window.innerWidth/ 2 ||
1517 Math.abs( centerPoint.y - point.y ) >= window.innerHeight / 2 )
1518 continue;
1519 // sum the location for cells that can be seen
1520 region.xSumView += point.x;
1521 region.ySumView += point.y;
1522 region.countView++;
1523 }
1524
1525 // display the final region text
1526 for ( pid in regionInfo )
1527 {
1528 var region = regionInfo[ pid ];
1529 // update last known player location based on the cells within the active area
1530 if ( region.countAll > 0 )
1531 {
1532 var pointAll = new PIXI.Point( region.xSumAll / region.countAll, region.ySumAll / region.countAll );
1533 playerLastLocX[ pid ] = pointAll.x / s, playerLastLocY[ pid ] = pointAll.y / s;
1534 }
1535 // skip display of name if not active or currently visible
1536 if ( !region.active || region.countView == 0 )
1537 {
1538 region.text.visible = false;
1539 delete region.point;
1540 region.xSumAll = region.ySumAll = region.countAll = 0;
1541 region.xSumView = region.ySumView = region.countView = 0;
1542 continue;
1543 }
1544 // calculate average location of region that can be seen
1545 var pointView = new PIXI.Point( region.xSumView / region.countView, region.ySumView / region.countView );
1546 // calculate the weighted sum of the new point and the previous point, to smooth text movement
1547 var prevPoint = region.point ? region.point : pointView;
1548 pointView.x = pointView.x * ( 1 - regionSmoothFactor ) + prevPoint.x * regionSmoothFactor;
1549 pointView.y = pointView.y * ( 1 - regionSmoothFactor ) + prevPoint.y * regionSmoothFactor;
1550 // remember the point for use in the next frame
1551 region.point = pointView;
1552 // position the text
1553 region.text.position = pointView;
1554 // reset region state
1555 region.xSumAll = region.ySumAll = region.countAll = 0;
1556 region.xSumView = region.ySumView = region.countView = 0;
1557 region.active = false;
1558 }
1559 }
1560
1561 //! draw the graphics that mark the region edges
1562 function drawRegionBorders()
1563 {
1564 // don't do any work if the borders are not enabled
1565 if ( !regionBorderEnabled || !gameActive()) { regionBorders.clear(); return; }
1566 // setup
1567 var xLoc = playerXLoc(), yLoc = playerYLoc(), thisPlayer = currentPlayer(), s = scale(), h = hexAreaRadius();
1568 var f = 2 * Math.sqrt( 3 );
1569 regionBorders.clear();
1570 var v = 255 * regionBorderValue;
1571 regionBorders.lineStyle( s * 0.2, ( v << 8 | v ) << 8 | v, 0.6 );
1572 // center search on the location of the current player
1573 var centerHexIndex = playerDead() ?
1574 rectToHexIndex( currentPlayerDeathX() * s, currentPlayerDeathY() * s ) :
1575 rectToHexIndex( xLoc[ thisPlayer ] * s, yLoc[ thisPlayer ] * s );
1576 // loop over the active area
1577 for ( var i = -h; i <= h; i++ )
1578 for ( var j = -h; j <= h; j++ )
1579 {
1580 // skip if outside the region
1581 if ( Math.abs( i + j ) > h ) continue;
1582 // hex location offset in area
1583 var q = centerHexIndex.q + i, r = centerHexIndex.r + j;
1584 // id for the player from the hex coord
1585 var pid = getHexPid( q, r );
1586 if ( pid == 0 ) continue;
1587 // get location for the current cell
1588 var cellPoint = hexToRectIndex( q, r );
1589 // draw borders between cells with different pids
1590 for ( var n = 0; n < 6; n++ )
1591 {
1592 // calculate q and r for neighbor
1593 var nQ = q + neighborOffset()[ n ].q, nR = r + neighborOffset()[ n ].r;
1594 // get pid of neighbor cell
1595 var nPid = getHexPid( nQ, nR );
1596 // if the pair of cells have different player ids, draw a border line between them
1597 if ( nPid != pid )
1598 {
1599 // x, y for neighbor cell
1600 var nPoint = hexToRectIndex( nQ, nR );
1601 // draw the border line
1602 var xOffset = ( nPoint.x - cellPoint.x ) / f, xCenter = ( nPoint.x + cellPoint.x ) / 2,
1603 yOffset = ( nPoint.y - cellPoint.y ) / f, yCenter = ( nPoint.y + cellPoint.y ) / 2;
1604 regionBorders.moveTo( xCenter - yOffset, yCenter + xOffset );
1605 regionBorders.lineTo( xCenter + yOffset, yCenter - xOffset );
1606 }
1607 }
1608 }
1609 }
1610
1611 //! produce the mod generated normal player color
1612 function modNormalColor( player )
1613 {
1614 return player <= 0 ? modPlayerColors.normal[ 0 ] : modPlayerColors.normal[ ( player - 1 ) % ( modPlayerColors.normal.length - 1 ) + 1 ];
1615 }
1616
1617 //! produce the mod generated dark player color
1618 function modDarkColor( player )
1619 {
1620 return player <= 0 ? modPlayerColors.dark[ 0 ] : modPlayerColors.dark[ ( player - 1 ) % ( modPlayerColors.dark.length - 1 ) + 1 ];
1621 }
1622
1623 //! return the current very dark color for the player
1624 function playerVeryDarkColor( player )
1625 {
1626 if ( !updateColorsEnabled ) return playerDarkColor( player );
1627 var colors = modPlayerColors.veryDark;
1628 return player <= 0 ? colors[ 0 ] : colors[ ( player - 1 ) % ( colors.length - 1 ) + 1 ];
1629 }
1630
1631 //! draw the player in a new style
1632 function drawModPlayer( graphics, player )
1633 {
1634 graphics.clear();
1635 graphics.lineStyle( 0.2, 0xffffff, 1 );
1636 graphics.beginFill( playerVeryDarkColor( player ), 1 );
1637 graphics.drawCircle( 0, 0, 1 );
1638 graphics.endFill();
1639 graphics.moveTo( 0, 0.2 );
1640 graphics.lineTo( 0, 1 );
1641 graphics.lineStyle( 0.1, 0xffffff, 1 );
1642 graphics.drawCircle( 0, 0, 0.2 );
1643 return graphics;
1644 }
1645
1646 //! configure mod colors
1647 function initColors()
1648 {
1649 // initialize arrays
1650 var normal = modPlayerColors.normal = [], dark = modPlayerColors.dark = [], veryDark = modPlayerColors.veryDark = [];
1651 // initialize the background color
1652 normal[0] = dark[0] = veryDark[0] = playerColors()[0];
1653 // generate the colors
1654 for ( var i = 0; i < colorCount; ++i )
1655 {
1656 // determine the hue from i, spreading the values out
1657 var v = hueOffset + ( i + 6 * i ) / colorCount;
1658 var hue = v - Math.floor( v );
1659 // generate a RGB values with for a specified hue
1660 var zone = Math.floor( hue * 6 ),
1661 f = hue * 6 - zone,
1662 q = 1 - f,
1663 r, g, b;
1664 switch ( zone % 6 )
1665 {
1666 case 0: r = maxColorComp, g = f, b = minColorComp; break;
1667 case 1: r = q, g = maxColorComp, b = minColorComp; break;
1668 case 2: r = minColorComp, g = maxColorComp, b = f; break;
1669 case 3: r = minColorComp, g = q, b = maxColorComp; break;
1670 case 4: r = f, g = minColorComp, b = maxColorComp; break;
1671 case 5: r = maxColorComp, g = minColorComp, b = q; break;
1672 }
1673 // convert to a range of 0-255
1674 r = Math.round( 255 * ( r * ( maxColorComp - minColorComp ) + minColorComp ));
1675 g = Math.round( 255 * ( g * ( maxColorComp - minColorComp ) + minColorComp ));
1676 b = Math.round( 255 * ( b * ( maxColorComp - minColorComp ) + minColorComp ));
1677 // generate and store the colors
1678 normal[ i + 1 ] = r << 16 | g << 8 | b;
1679 dark[ i + 1 ] = ( darkFactor * r ) << 16 | ( darkFactor * g ) << 8 | ( darkFactor * b );
1680 veryDark[ i + 1 ] = ( veryDarkFactor * r ) << 16 | ( veryDarkFactor * g ) << 8 | ( veryDarkFactor * b );
1681 }
1682 }
1683
1684 //! update the color value c, based on the current hex loc q,r for the player pid
1685 function variegateColor( pid, c, q, r )
1686 {
1687 // skip if not enabled, if processing background color
1688 if ( !variegationEnabled || 0 == pid ) return( c );
1689 // split color value into rgb components
1690 var rgb = PIXI.utils.hex2rgb( c );
1691 var texture = variegationTexture[ 1 + q + hexMapCenter() ][ 1 + r + hexMapCenter() ];
1692 // variegate each component
1693 for ( var i in rgb )
1694 {
1695 rgb[i] += texture[i] * variegationFactor;
1696 if ( rgb[i] < 0 ) rgb[i] = 0;
1697 if ( rgb[i] > 1 ) rgb[i] = 1;
1698 }
1699 // convert rgb components back to number, and return
1700 return PIXI.utils.rgb2hex( rgb );
1701 }
1702
1703 //! set the radius values for players based on the current player size
1704 function setPlayerRadius()
1705 {
1706 // full player radius is playerInnerRadius() + playerExtraRadius()
1707 // the +0.16 adjusts for an antialiased margin in the player sprite image
1708 // the 1/3 fraction is matched to the extra radius calculation in the resize() function
1709 setPlayerInnerRadius(( scale() * playerSize / 2 ) * ( 1 - 1/3 + 0.16 ));
1710 setPlayerExtraRadius( playerInnerRadius() * 1/3 );
1711 }
1712
1713 //! updates the icons for the players
1714 function updatePlayerIcons()
1715 {
1716 // skip if game isn't currently running
1717 if ( !gameActive() ) return;
1718 // update player radius
1719 setPlayerRadius();
1720 // update all player icons
1721 for ( var player in playerIcons() )
1722 {
1723 // remove icon
1724 playerIconLayer().removeChild( playerIcons()[ player ] );
1725 // remove text
1726 playerIconLayer().removeChild( playerText()[ player ] );
1727 // recreate player icon and text
1728 createPlayerGraphics( player );
1729 }
1730 // update current player icon, if player is alive
1731 if ( !playerDead() )
1732 {
1733 // recreate object for player icon
1734 rootLayer().removeChild( currentPlayerGraphics() );
1735 playerIconEnabled && ( currentPlayerSkinId() == -1 || !playerSkinsEnabled ) ? setCurrentPlayerGraphics( new PIXI.Graphics ) : setCurrentPlayerGraphics( new PIXI.Sprite );
1736 rootLayer().addChild( currentPlayerGraphics() );
1737 // init current player icon
1738 initCurrentPlayerGraphics();
1739 }
1740 }
1741
1742 //! show or hide player names
1743 function updatePlayerNameVisibility()
1744 {
1745 // skip if game isn't currently running
1746 if ( !gameActive() ) return;
1747 // set the text visibility
1748 for ( var player in playerText() ) playerText()[ player ].visible = playerNamesEnabled;
1749 }
1750
1751 //! updates the text size of the players
1752 function updatePlayerTextSize()
1753 {
1754 // skip if game isn't currently running
1755 if ( !gameActive() ) return;
1756 // set the text size for the current player
1757 if ( !playerDead() )
1758 {
1759 currentPlayerText().style.fontSize = playerNameFontSize();
1760 currentPlayerText().x = window.innerWidth / 2 - currentPlayerText().width / 2;
1761 currentPlayerText().y = window.innerHeight / 2 - playerSize * scale() / 2 - playerNameFontSize() * zoom;
1762 }
1763 // set the text size for the other players
1764 for ( var player in playerText() )
1765 {
1766 playerText()[ player ].style.fontSize = playerNameFontSize();
1767 playerText()[ player ].x = playerXLoc()[ player ] * scale() - playerText()[ player ].width / 2;
1768 playerText()[ player ].y = playerYLoc()[ player ] * scale() - playerSize * scale() / 2 - playerNameFontSize() * zoom;
1769 }
1770 }
1771
1772 //! updates the text size for names on regions
1773 function updateRegionTextSize()
1774 {
1775 // skip if game isn't currently running
1776 if ( !gameActive() ) return;
1777 // set the text size for the region names
1778 for ( var player in regionInfo ) regionInfo[ player ].text.style.fontSize = regionNameFontSize();
1779 }
1780
1781 //! updates the color of the trails for the players
1782 function updatePlayerTrails()
1783 {
1784 // skip if game isn't currently running
1785 if ( !gameActive() ) return;
1786 // update the player trail lines
1787 for ( var player in trailGraphics() ) trailGraphics()[ player ].tint = playerColor( player );
1788
1789 // setup for fixing the hex 'contours' under the player trails
1790 var xLoc = playerXLoc(), yLoc = playerYLoc(), thisPlayer = currentPlayer(), s = scale(), h = hexAreaRadius();
1791 var c = hexMapCenter();
1792 // center search on the location of the current player
1793 var centerHexIndex = playerDead() ?
1794 rectToHexIndex( currentPlayerDeathX() * s, currentPlayerDeathY() * s ) :
1795 rectToHexIndex( xLoc[ thisPlayer ] * s, yLoc[ thisPlayer ] * s );
1796 // loop over the active area
1797 for ( var i = -h; i <= h; i++ )
1798 for ( var j = -h; j <= h; j++ )
1799 {
1800 // skip if outside the region
1801 if ( Math.abs( i + j ) > h ) continue;
1802 // hex location offset in area
1803 var q = centerHexIndex.q + i, r = centerHexIndex.r + j;
1804 // update the contour color of each visible cell
1805 var cell = hexMap()[ q + c ] ? hexMap()[ q + c ][ r + c ] : undefined;
1806 cell && initContourColor( q, r, cell.pid, cell.aid );
1807 }
1808 }
1809
1810 //! draw the graphics that mark the location of the active movement circle
1811 function drawMovementCircle()
1812 {
1813 // clear any current graphics
1814 markUp.clear();
1815 // skip if nothing to do
1816 if ( !showMovementCircleEnabled ) return;
1817 // draw the circle
1818 if ( movementCircleEnabled ) markUp.lineStyle( 3, 0xffffff, 1 );
1819 else markUp.lineStyle( 3, 0xa0a0a0, 1 );
1820 markUp.drawCircle( movementCircleX * scale(), movementCircleY * scale(), movementCircleRadius * scale() );
1821 // generate a line to the tangent point on the movement circle
1822 var x = playerXLoc()[ currentPlayer() ] * scale(),
1823 y = playerYLoc()[ currentPlayer() ] * scale(),
1824 circleRadius = movementCircleRadius * scale();
1825 var circleI = movementCircleX * scale() - x,
1826 circleJ = movementCircleY * scale() - y;
1827 var circleDist = Math.sqrt( circleI * circleI + circleJ * circleJ );
1828 var tangentDist = Math.sqrt( ( circleDist - circleRadius ) * ( circleDist + circleRadius ));
1829 var tangentAngle = Math.atan2( circleJ, circleI ) + ( movementClockwise ? -1 : 1 ) * Math.asin( circleRadius / circleDist );
1830 var tX = x + Math.cos( tangentAngle ) * tangentDist,
1831 tY = y + Math.sin( tangentAngle ) * tangentDist;
1832 markUp.moveTo( x, y );
1833 markUp.lineTo( tX, tY );
1834 }
1835
1836 //! set movement circle location to current player loc
1837 function setMoveCircleLoc()
1838 {
1839 showMovementCircleEnabled = true;
1840 var hexI = rectToHexIndex( playerXLoc()[ currentPlayer() ] * scale(), playerYLoc()[ currentPlayer() ] * scale() );
1841 var rectI = hexToRectIndex( hexI.q, hexI.r );
1842 movementCircleX = rectI.x / scale();
1843 movementCircleY = rectI.y / scale();
1844 }
1845
1846 //! initialize the automatic circle motion
1847 function startMovementCircles( radius )
1848 {
1849 // clear timer if running
1850 clearInterval( movementCircleTimer );
1851 // enable circles and show current circle
1852 movementCircleEnabled = true;
1853 showMovementCircleEnabled = true;
1854 // change current radius if provided
1855 if ( radius != undefined ) movementCircleRadius = radius;
1856 // set timer that engages the movement
1857 movementCircleTimer = setInterval( function() {
1858 // generate an angle tangent to the movement circle
1859 var x = playerXLoc()[ currentPlayer() ] * scale(),
1860 y = playerYLoc()[ currentPlayer() ] * scale(),
1861 circleRadius = movementCircleRadius * scale();
1862 var circleI = movementCircleX * scale() - x,
1863 circleJ = movementCircleY * scale() - y;
1864 var circleDist = Math.sqrt( circleI * circleI + circleJ * circleJ );
1865 var tangentDistSqr = ( circleDist - circleRadius ) * ( circleDist + circleRadius );
1866 var tangentAngle = Math.atan2( circleJ, circleI ) +
1867 ( movementClockwise ? -1 : 1 ) * Math.asin( circleRadius / circleDist );
1868 // if outside the circle, direct the player
1869 if ( tangentDistSqr > 0 )
1870 {
1871 setcurrentPlayerDirection( tangentAngle );
1872 sendPlayerDirection();
1873 }
1874 }, 10 );
1875 }
1876
1877 //! stop the automatic circles
1878 function stopMovementCircles()
1879 {
1880 movementCircleEnabled = false;
1881 clearInterval( movementCircleTimer );
1882 // when turning off movement, move toward mouse pos, unless in dead zone
1883 if ( !mouseInDeadZone )
1884 setcurrentPlayerDirection( lastMouseDirection ),
1885 sendPlayerDirection();
1886 }
1887
1888 //! get current volume value
1889 function getVolume() { return soundEnabled ? volume / 100 : 0; }
1890
1891 //! update minimap markers
1892 function updateMinimapMarkers()
1893 {
1894 // skip if there is nothing to do
1895 if ( !minimapMarkerEnabled ) return;
1896 // update markers on minimap using last known location
1897 for ( var player in playerLastLocX )
1898 {
1899 // add new marker for player if needed
1900 if ( undefined == minimapMarkers[ player ] )
1901 {
1902 var id = `minimap-player-${player}`;
1903 minimapPos.insertAdjacentHTML( 'beforebegin', `<circle id="${id}" cx="0" cy="0" r="0.02" fill="${PIXI.utils.hex2string( playerColor( player ))}" />` );
1904 minimapMarkers[ player ] = document.getElementById( id );
1905 }
1906 // update location of the marker
1907 var hexIndex = rectToHexIndex( playerLastLocX[ player ] * scale(), playerLastLocY[ player ] * scale() ),
1908 cx = hexIndex.q / hexMapCenter() * .866,
1909 cy = ( hexIndex.r + hexIndex.q / 2 ) / hexMapCenter();
1910 minimapMarkers[ player ].setAttribute( "cx", cx );
1911 minimapMarkers[ player ].setAttribute( "cy", cy );
1912 }
1913 }
1914
1915 //! update the colors for the minimap markers
1916 function updateMinimapMarkerColors()
1917 {
1918 for ( var player in minimapMarkers ) minimapMarkers[ player ].setAttribute( "fill", PIXI.utils.hex2string( playerColor( player )) );
1919 }
1920
1921 //! show or hide the minimap markers depending on their enabled state
1922 function updateMinimapMarkerVisible()
1923 {
1924 for ( var player in minimapMarkers ) minimapMarkers[ player ].style.display = minimapMarkerEnabled ? '' : 'none';
1925 }
1926
1927 //! update the player list info
1928 function updatePlayerList()
1929 {
1930 // setup the display of the player list and and friends list
1931 var friendsElem = document.getElementById( "friendsScores" ),
1932 friendsDetailElem = document.getElementById("friends-detail"),
1933 playerListElem = document.getElementById( "player-list" ),
1934 playerListDetailElem = document.getElementById( "player-detail" ),
1935 playerListCountElem = document.getElementById( "player-list-count" );
1936 friendsDetailElem.style.display = friendsEnabled() ? "block" : "none";
1937 friendsElem.style.bottom = friendsEnabled() ? "60px" : "16px";
1938 playerListElem.style.bottom = friendsEnabled() || friends().length == 0 ? "16px" : "60px";
1939 playerListDetailElem.style.display = playerListEnabled ? "block" : "none";
1940 // format the player details
1941 var table = `<table><tr><th>id</th><th>name</th><th>rank</th><th>kills</th><th>loc</th></tr>`;
1942 for ( var player = 0, row = 0; player < playerLastLocX.length || player < rawPlayerNames.length; player++ )
1943 {
1944 // get encoded player name
1945 var playerName = rawPlayerNames[ player ] ? htmlEncode( tabooStr( rawPlayerNames[ player ] )) : undefined;
1946 // skip if we have no player info
1947 if ( !playerLastLocX[ player ] && !playerName ) continue;
1948 row++;
1949 // skip if not displaying the text
1950 if ( !playerListEnabled ) continue;
1951 table += `
1952 <tr style="color:${PIXI.utils.hex2string( playerColor( player ))};${row%2?'background-color: rgba( 255, 255, 255, 0.05 );':''}">
1953 <td class="id">${player}</td>
1954 <td class="name" ${playerName ? '' : 'style="opacity:0.5;padding-left:12px;"'}>${playerName ? playerName : "⸻"}</td>
1955 <td class="rank">${leaderboardRanks[player]?leaderboardRanks[player]:friendRanks[player]?friendRanks[player]:''}</td>
1956 <td class="kills">${killCount[player]?killCount[player]:''}</td>
1957 <td class="loc" style="color:${locColor( player )};">${locStr( player )}</td>
1958 </tr>
1959 `;
1960 }
1961 table += `</table><div class="separator"></div>`;
1962 playerListDetailElem.innerHTML = table;
1963 playerListCountElem.innerHTML = ` (${row})`;
1964 }
1965
1966 //###################################################################################################################
1967 function e(e) {
1968 //!! 19: local variables
1969 /**/var player = e;
1970 if (0 == e)
1971 //! 19: use mod colors if enabled
1972 /**/ if ( updateColorsEnabled ) return modNormalColor( player ); else
1973 return dn[0];
1974 if (an[e])
1975 //! 19: use mod colors if enabled
1976 /**/ if ( updateColorsEnabled ) return modNormalColor( player ); else
1977 return dn[an[e]];
1978 var t = [];
1979 e == Ft && rn >= 0 && (t = kn[rn]),
1980 void 0 !== nn[e] && nn[e] >= 0 && (t = kn[nn[e]]),
1981 an[Ft] && t.push(an[Ft]);
1982 do
1983 for (var n = !0, i = Math.floor(Math.random() * (dn.length - 1)) + 1, a = 0; a < t.length; a++)
1984 t[a] == i && (n = !1);
1985 while (!n);
1986 return an[e] = i,
1987 //! 19: use mod colors if enabled
1988 /**/ updateColorsEnabled ? modNormalColor( player ) :
1989 dn[i]
1990 }
1991
1992 function t(t) {
1993 //!! 19: local variables
1994 /**/var player = t;
1995 //! 19: use mod colors if enabled
1996 /**/if ( updateColorsEnabled ) return modDarkColor( player );
1997 return 0 == t ? cn[0] : (an[t] || e(t),
1998 cn[an[t]])
1999 }
2000
2001 function n() {
2002 var e = document.createElement("canvas").getContext("2d");
2003 return (window.devicePixelRatio || 1) / (e.webkitBackingStorePixelRatio || e.mozBackingStorePixelRatio || e.msBackingStorePixelRatio || e.oBackingStorePixelRatio || e.backingStorePixelRatio || 1)
2004 }
2005
2006 function i(e) {
2007 un = e,
2008 1 == e ? document.getElementById("button-quality-high").classList.remove("unselected") : document.getElementById("button-quality-high").classList.add("unselected"),
2009 .75 == e ? document.getElementById("button-quality-medium").classList.remove("unselected") : document.getElementById("button-quality-medium").classList.add("unselected"),
2010 .5 == e ? document.getElementById("button-quality-low").classList.remove("unselected") : document.getElementById("button-quality-low").classList.add("unselected"),
2011 "undefined" != typeof Storage && localStorage.setItem("quality", e)
2012 }
2013
2014 function a(e) {
2015 var t = new Array(e || 0),
2016 n = e;
2017 if (arguments.length > 1)
2018 for (var i = Array.prototype.slice.call(arguments, 1); n--;)
2019 t[e - 1 - n] = a.apply(this, i);
2020 return t
2021 }
2022
2023 function o(e, t) {
2024 je = a(2 * st + 1, 2 * st + 1),
2025 Dt = !0,
2026 q(e, t)
2027 }
2028
2029 function r(e, t) {
2030 return je[e + st] && je[e + st][t + st] && je[e + st][t + st].pid ? je[e + st][t + st].pid : 0
2031 }
2032
2033 function s(e, t) {
2034 je[e + st][t + st].pid = 0,
2035 yt[1 + e + st][1 + t + st] && w(b(e, t)),
2036 0 != je[e + st][t + st].aid && g(e, t, 0, je[e + st][t + st].aid)
2037 }
2038
2039 function l(e, t) {
2040 je[e + st][t + st] = {
2041 pid: 0,
2042 aid: 0
2043 },
2044 yt[1 + e + st][1 + t + st] && w(b(e, t)),
2045 u(e, t)
2046 }
2047
2048 function d(e, n, i) {
2049 //!! 34: local variables
2050 /**/var qIndex = e, rIndex = n;
2051 if (je[e + st] && je[e + st][n + st] || (je[e + st][n + st] = {
2052 pid: 0,
2053 aid: 0
2054 }),
2055 je[e + st][n + st].aid = i,
2056 yt[1 + e + st][1 + n + st]) {
2057 var a = je[e + st][n + st].pid;
2058 //!! 34: local variables
2059 /**/var player = a;
2060 //! 34: support variegated colors
2061 //w(b(e, n), t(a)),
2062 /**/setSpriteTint( getCellSprite( qIndex, rIndex ), variegateColor( player, playerDarkColor( player ), qIndex, rIndex )),
2063 c(e, n, a, i)
2064 }
2065 }
2066
2067 function c(t, n, i, a) {
2068 if (vt[1 + t + st][1 + n + st])
2069 return void g(t, n, i, a);
2070 var o = new PIXI.Sprite(nt.hexagon_white_contour);
2071 o.tint = e(a),
2072 0 == i && (o.alpha = .3),
2073 o.width = ct,
2074 o.height = gt,
2075 o.position = y(t, n),
2076 o.hexPosition = {
2077 q: t,
2078 r: n
2079 },
2080 Ne.addChild(o),
2081 vt[1 + t + st][1 + n + st] = o
2082 }
2083
2084 function g(t, n, i, a) {
2085 var o = vt[1 + t + st][1 + n + st];
2086 o && (o.tint = e(a),
2087 0 == i && (o.alpha = .3))
2088 }
2089
2090 function u(e, t) {
2091 vt[1 + e + st][1 + t + st] && (Ne.removeChild(vt[1 + e + st][1 + t + st]),
2092 vt[1 + e + st][1 + t + st] = null)
2093 }
2094
2095 function h(t, n) {
2096 //!! 34: local variables
2097 /**/var qIndex = t, rIndex = n, player = hexMap()[ qIndex + hexMapCenter() ][ rIndex + hexMapCenter() ].pid;
2098 je[t + st][n + st].aid = 0,
2099 //! 34: support variegated colors
2100 //yt[1 + t + st][1 + n + st] && w(b(t, n), e(je[t + st][n + st].pid)),
2101 /**/cellSprites()[1 + qIndex + hexMapCenter()][1 + rIndex + hexMapCenter()] && setSpriteTint( getCellSprite( qIndex, rIndex ), variegateColor( player, playerColor( player ), qIndex, rIndex )),
2102 u(t, n)
2103 }
2104
2105 function f(t, n, i) {
2106 //!! 34: local variables
2107 /**/var player = i, qIndex = t, rIndex = n;
2108 void 0 === i && (i = Ft),
2109 je[t + st][n + st] = {
2110 pid: i,
2111 aid: 0
2112 },
2113 //! 34: support variegated colors
2114 //yt[1 + t + st][1 + n + st] && w(b(t, n), e(i))
2115 /**/cellSprites()[1 + qIndex + hexMapCenter()][1 + rIndex + hexMapCenter()] && setSpriteTint( getCellSprite( qIndex, rIndex ), variegateColor( player, playerColor( player ), qIndex, rIndex ))
2116 }
2117
2118 function m(t, n, i, a) {
2119 //!! 34: local variables
2120 /**/var player = i, qIndex = t, rIndex = n;
2121 if (je[t + st][n + st] = {
2122 pid: i,
2123 aid: a
2124 },
2125 yt[1 + t + st][1 + n + st])
2126 if (0 == a)
2127 //! 34: support variegated colors
2128 //w(b(t, n), e(i)),
2129 /**/setSpriteTint( getCellSprite( qIndex, rIndex ), variegateColor( player, playerColor( player ), qIndex, rIndex )),
2130 u(t, n);
2131 else {
2132 //! 34: support variegated colors, also fixes bug with color used in orig code
2133 //var o = b(t, n);
2134 //w(o, e(i)),
2135 /**/setSpriteTint( getCellSprite( qIndex, rIndex ), variegateColor( player, playerDarkColor( player ), qIndex, rIndex )),
2136 c(t, n, i, a)
2137 }
2138 }
2139
2140 function p(e, t) {
2141 return je[e + st][t + st] && je[e + st][t + st].aid ? je[e + st][t + st].aid : 0
2142 }
2143
2144 function y(e, t) {
2145 var n = 3 * dt / 2 * e,
2146 i = dt * Math.sqrt(3) * (t + e / 2);
2147 return new PIXI.Point(n, i)
2148 }
2149
2150 function v(e, t) {
2151 var n = 2 * e / 3 / dt,
2152 i = (-e / 3 + Math.sqrt(3) / 3 * t) / dt,
2153 a = n,
2154 o = i,
2155 r = -a - o,
2156 s = Math.round(a),
2157 l = Math.round(r),
2158 d = Math.round(o),
2159 c = Math.abs(s - a),
2160 g = Math.abs(l - r),
2161 u = Math.abs(d - o);
2162 return c > g && c > u ? s = -l - d : g > u ? l = -s - d : d = -s - l, {
2163 q: s,
2164 r: d
2165 }
2166 }
2167
2168 function k(e, t, n, i, a, o) {
2169 if (void 0 === e && (e = 5197647),
2170 void 0 === t && (t = 4868682),
2171 void 0 === n && (n = 1),
2172 void 0 === i && (i = 1),
2173 void 0 === a && (a = .1 * dt),
2174 void 0 === o && (o = !1),
2175 pt.length > 0) {
2176 var r = pt.pop();
2177 r.tint = e,
2178 r.alpha = n
2179 } else {
2180 var r = new PIXI.Sprite(nt.hexagon_white_degrade);
2181 r.tint = e,
2182 r.alpha = n
2183 }
2184 return r.width = ct - .2 * dt,
2185 r.height = gt - .2 * dt,
2186 r.visible = o,
2187 r
2188 }
2189
2190 function w(e, t, n, i, a) {
2191 void 0 === t && (t = 5197647),
2192 void 0 === n && (n = 4868682),
2193 void 0 === i && (i = 1),
2194 void 0 === a && (a = 1),
2195 e.tint = t,
2196 e.alpha = i
2197 }
2198
2199 function I(t, n) {
2200 e(Ft),
2201 //! 18: 36: draw custom icon for current player if required
2202 /**/playerIconEnabled && ( currentPlayerSkinId() == -1 || !playerSkinsEnabled ) ? drawModPlayer( currentPlayerGraphics(), currentPlayer() ) :
2203 //!! split original line
2204 ze.texture =
2205 //! 36: don't use skins if disabled
2206 /**/!playerSkinsEnabled ||
2207 rn == -1 ? nt["player-" + an[Ft]] : nt[vn[rn]],
2208 rn == -1 ? (ze.width = 2 * ut + 2 * ft,
2209 ze.height = 2 * ut + 2 * ft) : (ze.width = 2 * ut * 96 / 63 * (1 + 1 / 3),
2210 ze.height = 2 * ut * 96 / 63 * (1 + 1 / 3))
2211 //! 18: 36: init size, anchor and location of the player icon
2212 /**/if ( playerIconEnabled && ( currentPlayerSkinId() == -1 || !playerSkinsEnabled )) currentPlayerGraphics().width = currentPlayerGraphics().height = playerSize * scale();
2213 /**/// if working with player icon sprite or player skin sprite, set anchor and potentially size
2214 /**/else if ( !playerIconEnabled || currentPlayerSkinId() != -1 )
2215 /**/{
2216 /**/ // set anchor when using sprites
2217 /**/ currentPlayerGraphics().anchor.set( 0.5 );
2218 /**/ // if player has skin, but it is disabled, then set icon size
2219 /**/ if ( currentPlayerSkinId() != -1 && !playerSkinsEnabled ) currentPlayerGraphics().width = currentPlayerGraphics().height = ( playerInnerRadius() + playerExtraRadius() ) * 2;
2220 /**/}
2221 /**/currentPlayerGraphics().x = 0.5 * window.innerWidth;
2222 /**/currentPlayerGraphics().y = 0.5 * window.innerHeight;
2223 }
2224
2225 function b(n, i) {
2226 //!! 34: local variables
2227 /**/var qIndex = n, rIndex = i;
2228 if (yt[1 + n + st][1 + i + st])
2229 return yt[1 + n + st][1 + i + st];
2230 var a;
2231 if (J(n, i))
2232 a = k(16711680),
2233 a.isBorder = !0;
2234 else {
2235 var o = r(n, i),
2236 s = p(n, i);
2237 //!! 34: local variables
2238 /**/var player = o, underTrail = s, sprite;
2239 //! 34: support variegated colors
2240 //0 == s ? (a = k(e(o)),
2241 // u(n, i)) : (a = k(t(o)),
2242 /**/0 == underTrail ? (sprite = setBufferSpriteTint( variegateColor( player, playerColor( player ), qIndex, rIndex )),
2243 /**/removeContourSprite( qIndex, rIndex )) : ( sprite = setBufferSpriteTint( variegateColor( player, playerDarkColor( player ), qIndex, rIndex )),
2244 c(n, i, o, s))
2245 //!! update use of local variable
2246 /**/;a = sprite;
2247 }
2248 return a.position = y(n, i),
2249 a.x += .1 * dt,
2250 a.y += .1 * dt,
2251 a.hexPosition = {
2252 q: n,
2253 r: i
2254 },
2255 yt[1 + n + st][1 + i + st] = a,
2256 Ge.addChild(a),
2257 a
2258 }
2259
2260 function x(e, t) {
2261 if (yt[1 + e + st][1 + t + st]) {
2262 var n = yt[1 + e + st][1 + t + st];
2263 Ge.removeChild(n),
2264 yt[1 + e + st][1 + t + st] = null,
2265 pt.push(n)
2266 }
2267 }
2268
2269 function q(e, t) {
2270 var n = (new Date).getTime();
2271 yt = a(2 * st + 1 + 2, 2 * st + 1 + 2),
2272 vt = a(2 * st + 1 + 2, 2 * st + 1 + 2);
2273 //! 34: support variegated colors, create random color texture for colors
2274 /**/variegationTexture = allocateHexArray( 2 * hexMapCenter() + 1 + 2, 2 * hexMapCenter() + 1 + 2 );
2275 /**/for ( var qIndex in variegationTexture ) for ( var rIndex = 0; rIndex < variegationTexture[qIndex].length; ++ rIndex ) variegationTexture[qIndex][rIndex] = [ 2 * Math.random() - 1, 2 * Math.random() - 1, 2 * Math.random() - 1 ];
2276 for (var i = -lt; i <= lt; i++)
2277 for (var o = -lt; o <= lt; o++)
2278 Math.abs(i + o) <= lt && K(e + i, t + o) && b(e + i, t + o);
2279 var r = (new Date).getTime();
2280 console.log("create map container in " + (r - n))
2281 }
2282
2283 function E(t) {
2284 //!! 1: local variables
2285 /**/var player = t;
2286 for (Ft > 0 && t != Ft && e(t) == e(Ft) && console.error("encountered player with same color", t, Ft),
2287 //! 36: show or hide player skin
2288 /**/playerSkinsEnabled &&
2289 void 0 !== nn[t] && nn[t] >= 0 && vn[nn[t]] ? (wt[t] = new PIXI.Sprite(nt[vn[nn[t]]]),
2290 wt[t].width = 2 * ut * 96 / 63 * (1 + 1 / 3),
2291 wt[t].height = 2 * ut * 96 / 63 * (1 + 1 / 3)) : (e(t),
2292 //! 18: use new player icon if requested
2293 /**/playerIconEnabled ? playerIcons()[ player ] = drawModPlayer( new PIXI.Graphics, player ) :
2294 wt[t] = new PIXI.Sprite(nt["player-" + an[t]]),
2295 //! 18: set correct with and height for the custom icon
2296 /**/playerIcons()[ player ].width = playerIcons()[ player ].height = playerIconEnabled ? playerSize * scale() : ( playerInnerRadius() + playerExtraRadius() ) * 2 ),
2297 //wt[t].width = 2 * ut + 2 * ft,
2298 //wt[t].height = 2 * ut + 2 * ft),
2299 //! 18: 36: only set anchor if using sprite
2300 /**/( !playerIconEnabled || ( undefined != playerSkinId()[ player ] && playerSkinId()[ player ] >= 0 && skinNames()[ playerSkinId()[ player ]] && playerSkinsEnabled )) &&
2301 wt[t].anchor.set(.5),
2302 //! 25: bug fix, fixes flickering of other player icons in some cases
2303 /**/playerIcons()[ player ].x = playerXLoc()[ player ] * scale(),
2304 /**/playerIcons()[ player ].y = playerYLoc()[ player ] * scale(),
2305 //wt[t].x = qt[t] * dt - wt[t].width / 2,
2306 //wt[t].y = Et[t] * dt - wt[t].height / 2,
2307 Ve.addChild(wt[t]),
2308 //! 1: use the raw player name
2309 /**/playerText()[ player ] = new PIXI.Text( rawPlayerNames[player], {
2310 //It[t] = new PIXI.Text(tn[t], {
2311 fontFamily: "Arial",
2312 //! 8: change player name size
2313 /**/fontSize: playerNameFontSize(),
2314 //fontSize: 16 / gn,
2315 fill: 16777215
2316 }); It[t].context.measureText(It[t].text).width > 160;)
2317 It[t].text = It[t].text.slice(0, -1);
2318 //! 9: add text anchor to support zoom
2319 /**/playerText()[ player ].anchor.y = 1.0;
2320 //! 35: set name visibility
2321 /**/playerText()[ player ].visible = playerNamesEnabled;
2322 Ve.addChild(It[t])
2323 }
2324
2325 function M(e) {
2326 //!! 2: local variables
2327 /**/var serverZone = e;
2328 hn[e.z] = new WebSocket("ws://" + e.u),
2329 hn[e.z].binaryType = "arraybuffer",
2330 hn[e.z].pings = [],
2331 hn[e.z].sendPing = function() {
2332 this.lastPingTime = (new Date).getTime();
2333 var e = new Uint8Array(1);
2334 e.set([99], 0),
2335 this.send(e)
2336 },
2337 hn[e.z].addEventListener("open", function(e) {
2338 e.target.sendPing()
2339 }),
2340 hn[e.z].addEventListener("close", function(e) {}),
2341 hn[e.z].addEventListener("message", function(t) {
2342 var n = new DataView(t.data),
2343 i = n.getUint8(0);
2344 99 == i ? (t.target.pings.push((new Date).getTime() - t.target.lastPingTime),
2345 console.log("zone ping", e.z, t.target.pings[t.target.pings.length - 1]),
2346 t.target.pings.length >= 3 ? (t.target.close(),
2347 delete hn[e.z],
2348 mt[e.z] = Math.min.apply(Math, t.target.pings),
2349 //! 2: populate the region drop down list
2350 /**/document.getElementById( "geoRegions" ).insertAdjacentHTML( 'beforeend', `<option ${ serverZone.z == geoRegion ? "selected='selected'" : "" } value="${serverZone.z}">${serverZone.z} (${serverPing()[serverZone.z]})</option>` ),
2351 console.log("kept zone ping", e.z, mt[e.z])) : t.target.sendPing()) : console.log("unexpected code from server", i)
2352 })
2353 }
2354
2355 function P() {
2356 var e = new XMLHttpRequest;
2357 e.onreadystatechange = function() {
2358 if (4 == e.readyState && 200 == e.status) {
2359 Ae = JSON.parse(e.responseText),
2360 console.log("got server list: ", Ae);
2361 for (var t = 0; t < Ae.length; t++)
2362 M(Ae[t])
2363 }
2364 },
2365 e.onerror = function() {
2366 document.getElementById("button-play-text").innerHTML = "Retrying",
2367 document.getElementById("button-play-again-text").innerHTML = "Retrying",
2368 setTimeout(P, 5e3)
2369 },
2370 e.open("GET", MM_URL + "/serversv2", !0),
2371 e.send(null)
2372 }
2373
2374 function B() {
2375 var e, t = Ae && 0 == Ae.length,
2376 n = null;
2377 if (Ae)
2378 for (var i = 0; i < Ae.length; i++) {
2379 var a = Ae[i].z;
2380 mt[a] && (t = !0,
2381 mt[a] != -1 && (!n || e > mt[a]) && (n = a,
2382 e = mt[a]))
2383 }
2384 if (!on || !t)
2385 return void setTimeout(B, 50);
2386 //! 2: use selected region
2387 //!! update use of local variable
2388 /**/if ( geoRegion != 'auto' ) n = geoRegion;
2389 console.log("best zone", n, "ping", e);
2390 for (var a in hn)
2391 hn[a].close();
2392 hn = [];
2393 var o = new XMLHttpRequest;
2394 o.onreadystatechange = function() {
2395 4 == o.readyState && 200 == o.status && (WSERVER_URL = o.responseText,
2396 //! 33: alter the port of the URL
2397 /**/serverPortEnabled && serverPort != '' && WSERVER_URL.indexOf( ":" ) > 0 && ( WSERVER_URL = `${ WSERVER_URL.substring( 0, WSERVER_URL.indexOf( ":" )) }:${ serverPort }` ),
2398 console.log("got server : ", WSERVER_URL),
2399 WSERVER_URL.startsWith("ERROR") ? (document.getElementById("button-play-text").innerHTML = "Retrying",
2400 document.getElementById("button-play-again-text").innerHTML = "Retrying",
2401 setTimeout(B, 5e3)) : X())
2402 },
2403 o.onerror = function() {
2404 document.getElementById("button-play-text").innerHTML = "Retrying",
2405 document.getElementById("button-play-again-text").innerHTML = "Retrying",
2406 setTimeout(B, 5e3)
2407 },
2408 o.open("GET", MM_URL + "?zone=" + (n ? n : "") + (it ? "&pk=" + it : ""), !0),
2409 o.send(null)
2410 }
2411
2412 function C() {
2413 Lt || (ln++,
2414 "undefined" != typeof Storage && localStorage.setItem("respawnCount", ln),
2415 tt = document.getElementById("username").value.substring(0, 15),
2416 "undefined" != typeof Storage && localStorage.setItem("username", tt),
2417 tt || (tt = "Guest"),
2418 //! 3: updates username to indicate mod is in use
2419 /**/tagUserName(),
2420 document.getElementById("networkError").style.display = "none",
2421 document.getElementById("button-play-text").innerHTML = "Connecting",
2422 Lt = !0,
2423 document.getElementById("button-play").classList.add("button-loading"),
2424 U(),
2425 B())
2426 }
2427
2428 function S() {
2429 if (Nt && !Lt) {
2430 ln++,
2431 "undefined" != typeof Storage && localStorage.setItem("respawnCount", ln),
2432 tt = document.getElementById("username").value.substring(0, 15),
2433 tt || (tt = "Guest"),
2434 //! 3: updates username to indicate mod is in use
2435 /**/tagUserName(),
2436 document.getElementById("networkError").style.display = "none",
2437 document.getElementById("button-play-again-text").innerHTML = "Connecting",
2438 Lt = !0,
2439 document.getElementById("button-play-again").classList.add("button-loading"),
2440 document.getElementById("friendsScores") && (document.getElementById("friendsScores").style.display = "none"),
2441 document.getElementById("fps") && (document.getElementById("fps").style.display = Rt ? "block" : "none"),
2442 Nt = !1,
2443 Ft = -1,
2444 Fe.alpha = 1,
2445 Ne.removeChildren(),
2446 Ge.removeChildren();
2447 for (var e = 0; e < kt.length; e++)
2448 Oe.removeChild(kt[e]);
2449 kt = [],
2450 Dt = !1,
2451 St = !1,
2452 Tt = !1,
2453 At = !1,
2454 zt = 0,
2455 Ze = null;
2456 for (var t = 0; t < qt.length; t++)
2457 qt[t] && (console.log("remove player ", t),
2458 fe(t));
2459 Vt = [],
2460 Ot = [],
2461 Mt = [],
2462 Pt = [],
2463 Ct = [],
2464 Bt = [],
2465 tn = [],
2466 nn = [],
2467 an = [],
2468 We.x = 1e4,
2469 B()
2470 }
2471 }
2472
2473 function T() {
2474 Dt ? S() : C(),
2475 document.getElementById("div-gpt-ad-1490263292583-0").style.display = "block"
2476 }
2477
2478 function L() {
2479 "undefined" != typeof ga && (it ? ga("send", "event", "Click", "Play", "InParty") : ga("send", "event", "Click", "Play", "NoParty"),
2480 ga("send", "event", "Click", "PlaySkin", rn >= 0 ? vn[rn] : "color")),
2481 "undefined" != typeof adplayer && (4 == ln || ln > 4 && (ln - 4) % 3 == 0) ? adplayer.startPreRoll() : C()
2482 }
2483
2484 function H() {
2485 "undefined" != typeof ga && (it ? ga("send", "event", "Click", "PlayAgain", "InParty") : ga("send", "event", "Click", "PlayAgain", "NoParty"),
2486 ga("send", "event", "Click", "PlayAgainSkin", rn >= 0 ? vn[rn] : "color")),
2487 _e && clearTimeout(_e),
2488 "undefined" != typeof adplayer && (4 == ln || ln > 4 && (ln - 4) % 3 == 0) ? (adplayer.startPreRoll(),
2489 document.getElementById("div-gpt-ad-1490263292583-0").style.display = "none") : S()
2490 }
2491
2492 function D() {
2493 "undefined" != typeof ga && ga("send", "event", "Click", "MainMenu"),
2494 _e && clearTimeout(_e),
2495 Re.close(),
2496 document.getElementById("button-main-menu").innerHTML = "Loading...",
2497 document.getElementById("button-main-menu").classList.add("button-loading"),
2498 window.location.reload()
2499 }
2500
2501 function A(e) {
2502 var t = (new Date).getTime();
2503 if (t - _t > 5e3) {
2504 var n = new DataView(new ArrayBuffer(7));
2505 n.setUint8(0, 50),
2506 n.setInt16(1, Ye, !0),
2507 n.setInt16(3, Wt, !0),
2508 n.setInt16(5, MainLoop.getFPS(), !0),
2509 Wt = 0,
2510 Ke = e,
2511 Je = e,
2512 Re.send(n.buffer),
2513 _t = t
2514 }
2515 Ye ? (Ye = .75 * Ye + .25 * e,
2516 e > Ke && (Ke = e),
2517 e < Je && (Je = e)) : (Ye = e,
2518 Ke = Ye,
2519 Je = Ye)
2520 }
2521
2522 function R(e) {
2523 var t = new DataView(e.data);
2524 //!! 1: local variables
2525 /**/var data = t;
2526 switch (t.getUint8(0)) {
2527 //#?
2528 case 1:
2529 oe(t),
2530 MainLoop.start(),
2531 ae(),
2532 document.getElementById("respawn").style.display = "none",
2533 document.getElementById("homepage").style.display = "none";
2534 var n = document.getElementById("div-gpt-ad-1490263292583-0");
2535 n && (n.parentElement.removeChild(n),
2536 document.getElementById("respawn-ad").appendChild(n));
2537 var i = document.getElementById("780560");
2538 i && (i.parentElement.removeChild(i),
2539 document.getElementById("respawn-ad").appendChild(i)),
2540 document.getElementById("button-play-again").classList.remove("button-loading"),
2541 Lt = !1,
2542 document.getElementById("button-play-again-text").innerHTML = "Play Again",
2543 Xe.view.style.display = "block";
2544 break;
2545 //#?
2546 case 2:
2547 //! 37: support optional display of leaderboard
2548 //document.getElementById("leaderboard").style.display = "block",
2549 /**/document.getElementById("leaderboard").style.display = leaderboardEnabled ? "block" : "none";
2550 document.getElementById("score").style.display = "block";
2551 var a = (new Date).getTime(),
2552 o = Ot.shift();
2553 Vt.shift(),
2554 A(a - o),
2555 console.log("first ping is ", a - o),
2556 zt = a,
2557 St = !0,
2558 se(Ft, qt[Ft], Et[Ft], bt[Ft], xt[Ft], 0),
2559 setTimeout(function() {
2560 var e = (new Date).getTime(),
2561 t = setInterval(function() {
2562 var n = (new Date).getTime();
2563 n - e > 1e3 ? (clearInterval(t),
2564 Fe.alpha = 0) : Fe.alpha = 1 - (n - e) / 1e3
2565 }, 20)
2566 }, 5e3);
2567 //! 24: set location of the movement circle to be spawn location
2568 /**/movementCircleX = playerXLoc()[ currentPlayer() ];
2569 /**/movementCircleY = playerYLoc()[ currentPlayer() ];
2570 //! 24: don't allow automatic movement when game starts
2571 /**/mouseInDeadZone = true; // don't set current direction
2572 /**/stopMovementCircles();
2573 //! 38: enable movement circles if requested
2574 /**/if ( spawnWithMovementEnabled ) startMovementCircles( 0.5 );
2575 //! 27: update the minimap marker colors
2576 /**/updateMinimapMarkerColors();
2577 //! 28: enable the display for known players
2578 /**/document.getElementById( "player-list" ).style.display = 'block';
2579 //! 1: refresh config display, hides config if neccesary
2580 /**/setupUiModConfig();
2581 break;
2582 //# current player direction in lobby
2583 case 3:
2584 if (St && Tt) {
2585 var r = (new Date).getTime(),
2586 o = Ot.shift();
2587 Vt.shift(),
2588 A(r - o),
2589 re(t)
2590 }
2591 break;
2592 //# player captures area
2593 case 4:
2594 Tt && ue(t);
2595 break;
2596 //# doesn't appear to be used
2597 case 5:
2598 Tt && updatePlayersPosition(t);
2599 break;
2600 //# player location
2601 case 13:
2602 Tt && le(t);
2603 break;
2604 //# leaderboard
2605 case 6:
2606 Jt = t.getUint32(1, !0),
2607 Yt = t.getUint32(5, !0),
2608 Kt = t.getInt16(9, !0),
2609 Zt = t.getInt16(11, !0);
2610 for (var s = (t.byteLength - 13) / 6, l = 0; l < s; l++)
2611 $t[l] = {
2612 playerId: t.getInt16(13 + 6 * l, !0),
2613 score: t.getUint32(13 + 6 * l + 2, !0)
2614 };
2615 xe(),
2616 qe();
2617 // 28: update the player list
2618 /**/updatePlayerList();
2619 break;
2620 //# friend info
2621 case 14:
2622 for (var d = t.getInt16(1, !0), c = [], l = 0; l < d; l++)
2623 c.push({
2624 playerId: t.getInt16(3 + 8 * l, !0),
2625 rank: t.getInt16(3 + 8 * l + 2, !0),
2626 score: t.getUint32(3 + 8 * l + 4, !0)
2627 });
2628 en = c,
2629 en.sort(function(e, t) {
2630 return e.rank - t.rank
2631 }),
2632 Ee();
2633 break;
2634 //# player death
2635 case 7:
2636 Tt && he(t);
2637 //! 1: setup local var
2638 /**/var player = data.getInt16( 1, true );
2639 //! 1: clear the player names when player dies
2640 /**/delete playerNames()[ player ];
2641 /**/delete rawPlayerNames[ player ];
2642 //! 11: remove last known location when player dies
2643 /**/delete playerLastLocX[ player ];
2644 /**/delete playerLastLocY[ player ];
2645 //! 26: reset block count on death of current player
2646 /**/if ( player == currentPlayer() ) lastBlockCount = 0;
2647 //! 27: remove minimap marker for the player
2648 /**/if ( undefined != minimapMarkers[ player ] ) minimapMarkers[ player ].remove(), delete minimapMarkers[ player ];
2649 //! 29: clear kill count
2650 /**/delete killCount[ player ];
2651 break;
2652 //# player joining game
2653 case 8:
2654 Tt && me(t.getInt16(1, !0), t.getInt16(3, !0), t.getInt16(5, !0));
2655 break;
2656 //#? provides loading of some stuff initially outside active region
2657 case 9:
2658 Tt && pe(t);
2659 break;
2660 //#? loads trail segments started while outside active region
2661 case 10:
2662 Tt && ve(t);
2663 break;
2664 //# player names
2665 case 11:
2666 Tt && Ie(t);
2667 break;
2668 //# skin id for player
2669 case 15:
2670 Tt && be(t);
2671 break;
2672 //#?
2673 case 12:
2674 document.getElementById("button-play-text").innerHTML = "Connecting",
2675 document.getElementById("button-play-again-text").innerHTML = "Connecting",
2676 console.log("server returned : map is full"),
2677 St = !1,
2678 Ht = !0,
2679 Re.close(),
2680 B();
2681 break;
2682 //# pings from server
2683 case 99:
2684 var g = (new Date).getTime() - Qe;
2685 if (A(g),
2686 console.log("ping is ", g),
2687 ++jt < 3)
2688 ae();
2689 else {
2690 var u = new Uint8Array(1);
2691 u.set([2], 0),
2692 Re.send(u),
2693 Ot.push((new Date).getTime()),
2694 Vt.push(xt[Ft])
2695 }
2696 }
2697 }
2698
2699 function X() {
2700 if (document.getElementById("button-play-text").innerHTML = "Joining",
2701 document.getElementById("button-play-again-text").innerHTML = "Joining",
2702 Re && Re.readyState <= 1 && Re.url.startsWith("ws://" + WSERVER_URL))
2703 return console.log("rejoining same map"),
2704 void ie();
2705 Re && Re.readyState <= 1 && (console.log("close previous connection"),
2706 Ht = !0,
2707 Re.close(),
2708 //! 1: clear server specific state connecting to new server
2709 /**/rawPlayerNames = [],
2710 /**/playerLastLocX = [],
2711 /**/playerLastLocY = [],
2712 /**/killCount = [],
2713 /**/function() { for ( var player in minimapMarkers ) minimapMarkers[ player ].remove() }(),
2714 /**/minimapMarkers = [],
2715 // 26: need to reset the block count on a server switch
2716 /**/setBlockCount( 0 ),
2717 jt = 0,
2718 _t = 0,
2719 Wt = 0,
2720 tn = [],
2721 nn = [],
2722 an = [],
2723 $t = [],
2724 en = []),
2725 Re = new WebSocket("ws://" + WSERVER_URL),
2726 Re.binaryType = "arraybuffer",
2727 Re.addEventListener("open", function(e) {
2728 console.log("socket connected"),
2729 ne()
2730 }),
2731 Re.addEventListener("close", function(e) {
2732 console.log("socket closed"),
2733 Ht || (document.getElementById("networkError").style.display = "block"),
2734 Ht = !1,
2735 St = !1
2736 });
2737 Re.addEventListener("message", R)
2738 }
2739
2740 function U() {
2741 PIXI.ticker.shared.stop();
2742 PIXI.utils.isWebGLSupported(),
2743 Xe = PIXI.autoDetectRenderer(256, 256, {
2744 antialias: !0,
2745 transparent: !1,
2746 resolution: un
2747 }),
2748 Xe.plugins.interaction && (Xe.plugins.interaction.destroy(),
2749 Xe.plugins.interaction = null),
2750 window.addEventListener("resize", z, !0),
2751 Xe.view.addEventListener("mousemove", j, !0),
2752 Xe.view.addEventListener("touchstart", _, !0),
2753 Xe.view.addEventListener("touchmove", _, !0),
2754 Xe.view.style.display = "none",
2755 Xe.backgroundColor = 4408131,
2756 Xe.view.style.position = "absolute",
2757 Xe.autoResize = !0,
2758 document.body.appendChild(Xe.view),
2759 //# root container
2760 Ue = new PIXI.Container,
2761 //# player content container
2762 We = new PIXI.Container,
2763 Ue.addChild(We),
2764 //# marking of hexes under player trail
2765 Ne = new PIXI.Container,
2766 We.addChild(Ne),
2767 //# background and foreground hexes
2768 Ge = new PIXI.Container,
2769 We.addChild(Ge),
2770 //! 20: 24: add markup that sits over hexes
2771 /**/addBackgroundMarkup(),
2772 //# player trails
2773 Oe = new PIXI.Container,
2774 We.addChild(Oe),
2775 //! 15: 16: add overlay for enemy markup
2776 /**/addEnemyOverlay(),
2777 //! 17: add overlay for region markup
2778 /**/addRegionOverlay();
2779 //# other player sprites and names
2780 Ve = new PIXI.Container,
2781 We.addChild(Ve),
2782 //! 18: 36: setup for new player icon if required
2783 /**/playerIconEnabled && ( currentPlayerSkinId() == -1 || !playerSkinsEnabled ) ? setCurrentPlayerGraphics( new PIXI.Graphics ) :
2784 //# current player sprite
2785 ze = new PIXI.Sprite,
2786 Ue.addChild(ze),
2787 //# current player name
2788 Fe = new PIXI.Text(tt, {
2789 fontFamily: "Arial",
2790 //! 8: change player name size
2791 /**/fontSize: playerNameFontSize(),
2792 //fontSize: 16 / gn,
2793 fill: 16777215
2794 }),
2795 //! 1: hide taboo words in name of current player
2796 /**/currentPlayerText().text = tabooStr( username() ),
2797 Ue.addChild(Fe),
2798 z(),
2799 MainLoop.setBegin(Z).setUpdate($).setDraw(ee).setEnd(te)
2800 }
2801
2802 function z() {
2803 //! 17: remove current region points on resize
2804 /**/for ( var pid in regionInfo ) delete regionInfo[pid].point;
2805 var i = dt;
2806 if (gn = n(),
2807 console.log("PIXEL RATIO", gn, "window size", window.innerWidth, window.innerHeight),
2808 dt = Math.max(window.innerWidth / 40 / 2 / .75, window.innerHeight / 40 / Math.sqrt(3)),
2809 //! 4: apply the zoom factor to the scale factor
2810 /**/setScale( scale() * zoom ),
2811 ct = 2 * dt,
2812 //# hex height
2813 gt = Math.sqrt(3) / 2 * ct,
2814 //# player inner radius
2815 ut = .6 * dt,
2816 //# trail thickness
2817 ht = 2 / 3 * ut,
2818 //# player extra radius
2819 ft = ut / 3,
2820 //! 7: update player radius values
2821 /**/setPlayerRadius(),
2822 Ge) {
2823 Ge.x = -ct / 2,
2824 Ge.y = -gt / 2;
2825 for (var a = 0; a < Ge.children.length; a++)
2826 if (Ge.children[a].width = ct - .2 * dt,
2827 Ge.children[a].height = gt - .2 * dt,
2828 Ge.children[a].position = y(Ge.children[a].hexPosition.q, Ge.children[a].hexPosition.r),
2829 Ge.children[a].x += .1 * dt,
2830 Ge.children[a].y += .1 * dt,
2831 Y(Ge.children[a].hexPosition.q, Ge.children[a].hexPosition.r)) {
2832 var o = r(Ge.children[a].hexPosition.q, Ge.children[a].hexPosition.r),
2833 s = p(Ge.children[a].hexPosition.q, Ge.children[a].hexPosition.r);
2834 //!! 34: local variables
2835 /**/var sprite = Ge.children[a], qIndex = sprite.hexPosition.q, rIndex = sprite.hexPosition.r, pid = o, underTrail = s;
2836 //0 == s ? w(Ge.children[a], e(o)) : w(Ge.children[a], t(o))
2837 0 == underTrail ? setSpriteTint( sprite, variegateColor( pid, playerColor( pid ), qIndex, rIndex )) : setSpriteTint( sprite, variegateColor( pid, playerDarkColor( pid ), qIndex, rIndex ))
2838 } else
2839 w(Ge.children[a], 16711680);
2840 Ne.x = -ct / 2,
2841 Ne.y = -gt / 2;
2842 for (var a = 0; a < Ne.children.length; a++)
2843 Ne.children[a].width = ct,
2844 Ne.children[a].height = gt,
2845 Ne.children[a].position = y(Ne.children[a].hexPosition.q, Ne.children[a].hexPosition.r);
2846 rn == -1 ? (ze.width = 2 * ut + 2 * ft,
2847 ze.height = 2 * ut + 2 * ft) : (ze.width = 2 * ut * 96 / 63 * (1 + 1 / 3),
2848 ze.height = 2 * ut * 96 / 63 * (1 + 1 / 3)),
2849 //! 18: 36: set size for current player icon
2850 /**/playerIconEnabled && ( currentPlayerSkinId() == -1 || !playerSkinsEnabled ) && ( currentPlayerGraphics().width = currentPlayerGraphics().height = playerSize * scale() ),
2851 //! 36: if not using mod icon, and player has skin, but it is disabled, then set icon size
2852 /**/!playerIconEnabled && currentPlayerSkinId() != -1 && !playerSkinsEnabled && ( currentPlayerGraphics().width = currentPlayerGraphics().height = ( playerInnerRadius() + playerExtraRadius() ) * 2 ),
2853 //! 18: 36: set anchor for current player icon, if sprite
2854 /**/( !playerIconEnabled || ( currentPlayerSkinId() >= 0 && playerSkinsEnabled )) &&
2855 ze.anchor.set(.5);
2856 for (l in wt)
2857 void 0 !== nn[l] && nn[l] >= 0 ? (wt[l].width = 2 * ut * 96 / 63 * (1 + 1 / 3),
2858 wt[l].height = 2 * ut * 96 / 63 * (1 + 1 / 3)) : (wt[l].width = 2 * ut + 2 * ft,
2859 wt[l].height = 2 * ut + 2 * ft);
2860 //! 8: change player name size
2861 /**/currentPlayerText().style.fontSize = playerNameFontSize();
2862 //Fe.style.fontSize = 16 / gn;
2863 for (l in It)
2864 //! 8: change player name size, update local var for player id
2865 //!! update use of local variable
2866 /**/playerText()[l].style.fontSize = playerNameFontSize();
2867 //It[l].style.fontSize = 16 / gn;
2868 for (var l in Pt)
2869 for (var d = dt / i, a = 0; a < Pt[l].length; a++)
2870 Pt[l][a] *= d;
2871 var c = ht / 20;
2872 for (var l in kt)
2873 kt[l].scale.x = c,
2874 kt[l].scale.y = c;
2875 Xe.resize(window.innerWidth, window.innerHeight),
2876 ze.x = window.innerWidth / 2,
2877 ze.y = window.innerHeight / 2,
2878 Fe.x = window.innerWidth / 2 - Fe.width / 2,
2879 //! 9: set text offset to support zoom
2880 /**/currentPlayerText().y = window.innerHeight / 2 - playerSize * scale() / 2 - playerNameFontSize() * zoom,
2881 //Fe.y = window.innerHeight / 2 - gt - ut,
2882 Nt ? (We.x = window.innerWidth / 2 - $e * dt,
2883 We.y = window.innerHeight / 2 - et * dt) : (We.x = window.innerWidth / 2 - qt[Ft] * dt,
2884 We.y = window.innerHeight / 2 - Et[Ft] * dt)
2885 }
2886 //! 15: 16: draw the graphics that mark up enemies, after resize
2887 /**/drawEnemyOverlay();
2888 //! 17: draw the graphics that mark up regions, after resize
2889 /**/drawRegionOverlay();
2890 //! 20: draw borders that mark the edge of player regions, after resize
2891 /**/drawRegionBorders();
2892 //! 24: draw markup for the current movement, after resize
2893 /**/drawMovementCircle();
2894 //! 9: update location of player text, after resize
2895 /**/for ( var player in playerText() )
2896 /**/{
2897 /**/ playerText()[ player ].x = playerXLoc()[ player ] * scale() - playerText()[ player ].width / 2;
2898 /**/ playerText()[ player ].y = playerYLoc()[ player ] * scale() - hexHeight() - playerInnerRadius();
2899 /**/}
2900 //! 18: 36: update location and size of player icons
2901 /**/for ( var player in playerIcons() )
2902 /**/{
2903 /**/ if ( playerIconEnabled && !( undefined != playerSkinId()[ player ] && playerSkinId()[ player ] >= 0 && skinNames()[ playerSkinId()[ player ]] && playerSkinsEnabled ))
2904 /**/ playerIcons()[ player ].width = playerIcons()[ player ].height = playerSize * scale();
2905 //! 36: if not using mod icon, and player has skin, but it is disabled, then set icon size
2906 /**/ if ( !playerIconEnabled && currentPlayerSkinId() != -1 && !playerSkinsEnabled ) playerIcons()[ player ].width = playerIcons()[ player ].height = ( playerInnerRadius() + playerExtraRadius() ) * 2;
2907 /**/ playerIcons()[ player ].x = playerXLoc()[ player ] * scale() - playerIcons()[ player ].width / 2;
2908 /**/ playerIcons()[ player ].y = playerYLoc()[ player ] * scale() - playerIcons()[ player ].height / 2;
2909 /**/}
2910 }
2911
2912 function F() {
2913 rt = null;
2914 var e = new DataView(new ArrayBuffer(5));
2915 //! 22: limit the angles that the player can move in
2916 /**/if ( fixedAngleEnabled )
2917 /**/{
2918 /**/ var fixedAngleRad = fixedAngle * Math.PI / 180;
2919 /**/ setcurrentPlayerDirection( Math.round(( currentPlayerDirection() + Math.PI / 2 ) / fixedAngleRad ) * fixedAngleRad - Math.PI / 2 );
2920 /**/}
2921 e.setUint8(0, 3),
2922 e.setFloat32(1, ot, !0),
2923 Re && 1 == Re.readyState && (Ot.push((new Date).getTime()),
2924 Vt.push(ot),
2925 Re.send(e.buffer))
2926 }
2927
2928 function j(e) {
2929 //!! 5: local variables
2930 /**/var event = e;
2931 if (e.clientY != window.innerHeight / 2 || e.clientX != window.innerWidth / 2) {
2932 var t = Math.atan2(e.clientY - window.innerHeight / 2, e.clientX - window.innerWidth / 2);
2933 //!! 24: local variables
2934 /**/var dir = t;
2935 //! 24: remember the last dirction input via mouse
2936 /**/lastMouseDirection = dir;
2937 //! 5: dead zone for mouse at center of screen
2938 /**/var distSqr = ( event.clientX - window.innerWidth / 2 ) * ( event.clientX - window.innerWidth / 2 ) + ( event.clientY - window.innerHeight / 2 ) * ( event.clientY - window.innerHeight / 2 );
2939 /**/mouseInDeadZone = deadZoneEnabled && distSqr < deadZoneSizeSqr;
2940 //! 5: 24: ignore mouse input if in dead zone or if movement is enabled
2941 /**/if ( mouseInDeadZone || movementCircleEnabled ) return;
2942 !St || Nt || At || (rt && clearTimeout(rt),
2943 ot = t,
2944 //! 6: reduced the delay between direction messages
2945 //!! update use of local vars
2946//: working here, testing removing this change
2947 /**/0 != Ot.length & (new Date).getTime() - Ot[Ot.length - 1] < 1 ? rt = setTimeout(F, 1) : F())
2948// 0 != Ot.length & (new Date).getTime() - Ot[Ot.length - 1] < 10 ? rt = setTimeout(F, 10) : F())
2949 }
2950 }
2951
2952 function _(e) {
2953 e.touches && 1 == e.touches.length && (j({
2954 clientX: e.touches[0].clientX,
2955 clientY: e.touches[0].clientY
2956 }),
2957 e.preventDefault())
2958 }
2959
2960 function W(e, t) {
2961 for (var n = t - e; n < -Math.PI;)
2962 n += 2 * Math.PI;
2963 for (; n > Math.PI;)
2964 n -= 2 * Math.PI;
2965 return n
2966 }
2967
2968 function N(e, t) {
2969 return (Math.abs(e.q - t.q) + Math.abs(e.q + e.r - t.q - t.r) + Math.abs(e.r - t.r)) / 2
2970 }
2971
2972 function G(e, t) {
2973 var n = fn[t];
2974 e.q += n.q,
2975 e.r += n.r
2976 }
2977
2978 function O(e) {
2979 for (var t = {
2980 minQ: e[0].q,
2981 maxQ: e[0].q,
2982 minR: e[0].r,
2983 maxR: e[0].r
2984 }, n = 1; n < e.length; n++)
2985 e[n].q < t.minQ && (t.minQ = e[n].q),
2986 e[n].q > t.maxQ && (t.maxQ = e[n].q),
2987 e[n].r < t.minR && (t.minR = e[n].r),
2988 e[n].r > t.maxR && (t.maxR = e[n].r);
2989 return t
2990 }
2991
2992 function V(e, t) {
2993 return e.r == t.r ? e.q > t.q ? 0 : 3 : e.q == t.q ? e.r > t.r ? 5 : 2 : e.q > t.q ? 1 : 4
2994 }
2995
2996 function Q(e) {
2997 var t = O(e),
2998 n = e[0],
2999 i = null;
3000 e.push(n);
3001 for (var a = 0; a < e.length; a++) {
3002 var o = e[a];
3003 if (null != i) {
3004 var r = V(i, o);
3005 o.angles || (o.angles = []),
3006 o.angles.push(r),
3007 i.angles || (i.angles = []),
3008 i.angles.push((r + 3) % 6)
3009 }
3010 i = o
3011 }
3012 e.pop();
3013 var s = [];
3014 for (var a in e) {
3015 var l = e[a].q;
3016 s[l] || (s[l] = []),
3017 s[l].push(e[a])
3018 }
3019 for (var a in s)
3020 s[a].sort(function(e, t) {
3021 return e.r - t.r
3022 });
3023 for (var d = [], l = t.minQ; l <= t.maxQ; l++) {
3024 var c = !1,
3025 g = 0,
3026 u = s[l],
3027 h = null;
3028 if (u)
3029 for (var a = 0; a < u.length; a++) {
3030 var f = u[a];
3031 if (c)
3032 for (var m = h.r + 1; m <= f.r - 1; m++)
3033 d.push({
3034 q: l,
3035 r: m
3036 });
3037 if (null != h && h.r - f.r > 1 && (g = 0),
3038 (3 == f.angles[1] || 4 == f.angles[1]) && f.angles[0] <= 1 || (3 == f.angles[0] || 4 == f.angles[0]) && f.angles[1] <= 1)
3039 c = !c,
3040 g = 0;
3041 else if (2 == f.angles[0] && 5 != f.angles[1]) {
3042 var p = f.angles[1] > 2 ? 1 : 2;
3043 g > 0 && g != p ? (c = !c,
3044 g = 0) : g = p
3045 } else if (2 == f.angles[1] && 5 != f.angles[0]) {
3046 var p = f.angles[0] > 2 ? 1 : 2;
3047 g > 0 && g != p ? (c = !c,
3048 g = 0) : g = p
3049 } else if (5 == f.angles[0] && 2 != f.angles[1]) {
3050 var p = f.angles[1] > 2 ? 1 : 2;
3051 g > 0 && g != p ? (c = !c,
3052 g = 0) : g = p
3053 } else if (5 == f.angles[1] && 2 != f.angles[0]) {
3054 var p = f.angles[0] > 2 ? 1 : 2;
3055 g > 0 && g != p ? (c = !c,
3056 g = 0) : g = p
3057 }
3058 h = f
3059 }
3060 }
3061 return d
3062 }
3063
3064 function Y(e, t) {
3065 return !(Math.abs(t) > st) && (t <= 0 ? e >= -st - t && e <= st : e >= -st && e <= st - t)
3066 }
3067
3068 function J(e, t) {
3069 return (Math.abs(e) + Math.abs(e + t) + Math.abs(t)) / 2 == st + 1
3070 }
3071
3072 function K(e, t) {
3073 return !(Math.abs(t) > st + 1) && (t <= 0 ? e >= -(st + 1) - t && e <= st + 1 : e >= -(st + 1) && e <= st + 1 - t)
3074 }
3075
3076 function Z() {}
3077
3078 function $(e) {
3079 for (var t = Gt.length - 1; t >= 0; t--) {
3080 var n = Gt[t];
3081 if (n.time += e,
3082 "CAPTURING" === n.type || "UNCAPTURING" === n.type) {
3083 for (var i = n.from + (n.to - n.from) * Math.min(1, n.time / n.length), a = 0; a < n.hexs.length; a++) {
3084 var o = n.hexs[a];
3085 o.width = (ct - .1 * dt * 2) * i,
3086 o.height = (gt - .1 * dt * 2) * i,
3087 o.position = y(o.hexPosition.q, o.hexPosition.r),
3088 o.x += .1 * dt + (ct - .1 * dt * 2) * (1 - i) / 2,
3089 o.y += .1 * dt + (gt - .1 * dt * 2) * (1 - i) / 2
3090 }
3091 if (n.time >= n.length)
3092 for (var a = 0; a < n.hexs.length; a++) {
3093 var o = n.hexs[a];
3094 Ge.removeChild(o),
3095 "CAPTURING" === n.type && f(o.hexPosition.q, o.hexPosition.r, n.playerId),
3096 o.destroy()
3097 }
3098 } else if ("EXPLOSION" == n.type)
3099 for (var a = 0; a < n.parts.length; a++) {
3100 var r = n.parts[a];
3101 r.graphicsData[0].fillAlpha = n.from + (n.to - n.from) * Math.min(1, n.time / n.length),
3102 r.graphicsData[0].lineAlpha = n.from + (n.to - n.from) * Math.min(1, n.time / n.length),
3103 r.dirty++,
3104 r.clearDirty++,
3105 n.time <= n.moveLength && (r.x += Math.cos(r.animationDirection) * r.animationSpeed,
3106 r.y += Math.sin(r.animationDirection) * r.animationSpeed),
3107 n.time >= n.length && (Ve.removeChild(r),
3108 r.destroy())
3109 }
3110 else if ("TRAIL_DISAPPEAR" == n.type) {
3111 var r = n.trailGraphics;
3112 r.alpha = n.from + (n.to - n.from) * Math.min(1, n.time / n.length),
3113 n.time >= n.length && Oe.removeChild(r)
3114 } else if (n.type = "IMPACT") {
3115 var i = n.from + (n.to - n.from) * Math.min(1, n.time / n.length);
3116 n.sprite.graphicsData[0].shape.radius = i * ut * 4,
3117 n.sprite.alpha = 1 * (1 - i),
3118 n.sprite.dirty++,
3119 n.sprite.clearDirty++,
3120 n.time >= n.length && Ve.removeChild(n.sprite)
3121 }
3122 n.time > n.length && Gt.splice(t, 1)
3123 }
3124 var s = (new Date).getTime();
3125 for (var l in Qt)
3126 for (var d = Qt[l]; d.length > 0 && d[0].time <= s;) {
3127 for (var c = d.shift(), t = 0; t < c.points.length; t++)
3128 ge(c.points[t][0], c.points[t][1], c.points[t][2], c.points[t][3]);
3129 Pt[l] && Bt[l] && Pt[l].splice(2 * (Bt[l] + 1)),
3130 se(c.playerId, c.x, c.y, c.dir, c.targetDir, c.time),
3131 ce(l)
3132 }
3133 if (at = s,
3134 St) {
3135 if (!Nt) {
3136 var g = xt[Ft];
3137 if (Ot.length > 0 && Ot[0] < s - Ye / 2) {
3138 for (var t = 0; t < Ot.length - 1 && Ot[t + 1] < s - Ye / 2;)
3139 t++;
3140 g = Vt[t]
3141 }
3142 var u = W(bt[Ft], g);
3143 //! 23: support for fast turning
3144 //!! update local variable name
3145 /**/if ( fastTurnEnabled ) u *= fastTurnMult;
3146 bt[Ft] += 5 * u * e / 1e3,
3147 qt[Ft] += 7 * Math.cos(bt[Ft]) * e / 1e3,
3148 Et[Ft] += 7 * Math.sin(bt[Ft]) * e / 1e3,
3149 ze.rotation = bt[Ft] - Math.PI / 2,
3150 We.x = window.innerWidth / 2 - qt[Ft] * dt,
3151 We.y = window.innerHeight / 2 - Et[Ft] * dt,
3152 ye(),
3153 Pe(),
3154 ce(Ft),
3155 document.getElementById("networkLag").style.display = s - Mt[l] > 500 ? "block" : "none"
3156 //! 11: update location display for current player
3157 /**/;document.getElementById( "locValue" ).innerHTML = locStr( currentPlayer() );
3158 }
3159 for (var l in qt)
3160 if (Nt || l != Ft) {
3161 var h = N(v(dt * (Nt ? $e : qt[Ft]), dt * (Nt ? et : Et[Ft])), v(dt * qt[l], dt * Et[l]));
3162 if (h > lt + 1 || s - Mt[l] > 2e3)
3163 console.log("removed player", l, "from view"),
3164 fe(l);
3165 else {
3166 //!! 9: local variables
3167 /**/var player = l;
3168 var u = W(bt[l], xt[l]);
3169 bt[l] += 5 * u * e / 1e3,
3170 qt[l] += 7 * Math.cos(bt[l]) * e / 1e3,
3171 Et[l] += 7 * Math.sin(bt[l]) * e / 1e3,
3172 wt[l].x = qt[l] * dt,
3173 wt[l].y = Et[l] * dt,
3174 wt[l].rotation = bt[l] - Math.PI / 2,
3175 It[l].x = qt[l] * dt - It[l].width / 2,
3176 //! 9: set text offset to support zoom
3177 /**/playerText()[ player ].y = playerYLoc()[ player ] * scale() - playerSize * scale() / 2 - playerNameFontSize() * zoom,
3178 //It[l].y = Et[l] * dt - gt - ut,
3179 ce(l)
3180 }
3181 }
3182 }
3183 //! 11: set last known location for all players
3184 /**/setLastKnownLoc();
3185 //! 15: 16: draw the graphics that mark up enemies
3186 /**/drawEnemyOverlay();
3187 //! 17: draw the graphics that mark up regions
3188 /**/drawRegionOverlay();
3189 //! 20: draw borders that mark the edge of player regions
3190 /**/drawRegionBorders();
3191 //! 24: draw markup for the current movement, after resize
3192 /**/drawMovementCircle();
3193 //! 27: update player locations on the minimap
3194 /**/updateMinimapMarkers();
3195 }
3196
3197 function ee() {
3198 (new Date).getTime();
3199 Xe.render(Ue)
3200 }
3201
3202 function te(e, t) {
3203 document.getElementById("fps").textContent = "fps : " + Math.round(e) + " - ping = " + Math.round(Ye) + " - jitter = " + Wt + " server = " + WSERVER_URL,
3204 t && console.log("panic: simulation is too late")
3205 }
3206
3207 function ne() {
3208 var e = new DataView(new ArrayBuffer(2 + 2 * tt.length + 1 + (it ? 2 * it.length : 0) + 2));
3209 e.setUint8(0, 1),
3210 e.setUint8(1, tt.length);
3211 for (var t = 0; t < tt.length; t++) {
3212 var n = tt.charCodeAt(t);
3213 e.setUint8(2 + 2 * t, 255 & n),
3214 e.setUint8(2 + 2 * t + 1, n >>> 8)
3215 }
3216 var i = 1 + 2 * tt.length + 1;
3217 if (it) {
3218 e.setUint8(i, it.length);
3219 for (var t = 0; t < it.length; t++) {
3220 var n = it.charCodeAt(t);
3221 e.setUint8(i + 1 + 2 * t, 255 & n),
3222 e.setUint8(i + 1 + 2 * t + 1, n >>> 8)
3223 }
3224 i += 2 * it.length
3225 } else
3226 e.setUint8(i, 0);
3227 i++,
3228 e.setInt16(i, rn, !0),
3229 Re.send(e.buffer),
3230 Tt = !0
3231 }
3232
3233 function ie() {
3234 Jt = 7,
3235 Yt = 0;
3236 var e = new DataView(new ArrayBuffer(1));
3237 e.setUint8(0, 4),
3238 Re.send(e.buffer),
3239 Tt = !0
3240 }
3241
3242 function ae() {
3243 Qe = (new Date).getTime();
3244 var e = new Uint8Array(1);
3245 e.set([99], 0),
3246 Re.send(e)
3247 }
3248
3249 function oe(n) {
3250 Ft = n.getInt16(1, !0),
3251 console.log("player id is ", Ft),
3252 //! 1: treat current player name like the names of other players
3253 /**/playerNames()[ currentPlayer() ] = htmlEncode( tabooStr( username() )),
3254 //tn[Ft] = tt,
3255 //! 1: store the current player name
3256 /**/rawPlayerNames[ currentPlayer() ] = tabooStr( username() ),
3257 st = n.getInt16(3, !0),
3258 lt = n.getInt16(5, !0),
3259 Zt = n.getInt16(7, !0),
3260 Kt = Zt;
3261 var i = n.getInt16(9, !0),
3262 a = n.getInt16(11, !0);
3263 console.log("Player starts at ", i, a),
3264 xt[Ft] = n.getFloat32(13, !0),
3265 bt[Ft] = xt[Ft],
3266 ze.rotation = bt[Ft] - Math.PI / 2,
3267 o(i, a);
3268 var r = y(i, a);
3269 qt[Ft] = r.x / dt,
3270 Et[Ft] = r.y / dt,
3271 We.x = window.innerWidth / 2 - r.x,
3272 We.y = window.innerHeight / 2 - r.y,
3273 ye(),
3274 I(e(Ft), t(Ft)),
3275 ze.visible = !0,
3276 Fe.visible = !0;
3277 var s = (n.byteLength - 17) / 4 / 2,
3278 l = n.getInt16(17, !0),
3279 d = n.getInt16(19, !0),
3280 c = n.getInt16(21, !0),
3281 g = n.getInt16(23, !0),
3282 u = 0,
3283 h = 0,
3284 f = -lt,
3285 p = -1,
3286 v = -1;
3287 do
3288 Y(i + h, a + f) && (l == i + h && d == a + f && (p = c,
3289 v = g,
3290 ++u < s && (l = n.getInt16(17 + 8 * u + 0, !0),
3291 d = n.getInt16(17 + 8 * u + 2, !0),
3292 c = n.getInt16(17 + 8 * u + 4, !0),
3293 g = n.getInt16(17 + 8 * u + 6, !0))),
3294 p == -1 && v == -1 || m(i + h, a + f, p, v)),
3295 h++,
3296 (f <= 0 && h > lt || f > 0 && h > lt - f) && (f++,
3297 h = f <= 0 ? -lt - f : -lt);
3298 while (f <= lt);
3299 xe(),
3300 document.getElementById("minimap").style.display = "block",
3301 //! 30: record the base size of the minimap to support resizing
3302 /**/undefined == minimapBaseWidth && ( minimapBaseWidth = minimap.clientWidth ),
3303 /**/undefined == minimapBaseHeight && ( minimapBaseHeight = minimap.clientHeight ),
3304 Pe()
3305 }
3306
3307 function re(e) {
3308 var t = (e.getFloat32(1, !0),
3309 e.getFloat32(5, !0),
3310 e.getFloat32(9, !0),
3311 e.getFloat32(13, !0));
3312 xt[Ft] = t
3313 }
3314
3315 function se(e, t, n, i, a, o) {
3316 //!! 9: 24: local variables
3317 /**/var player = e, xLoc = t, yLoc = n;
3318 if (o = 0,
3319 Nt || e != Ft)
3320 for (; o > 17;) {
3321 var r = W(i, a);
3322 i += 5 * r * 17 / 1e3,
3323 t += 7 * Math.cos(i) * 17 / 1e3,
3324 n += 7 * Math.sin(i) * 17 / 1e3,
3325 o -= 17
3326 }
3327 else
3328 for (var s = (new Date).getTime(), l = s - o, d = 0; o > 17;) {
3329 for (; d < Ot.length && Ot[d] < l - Ye / 2;)
3330 a = Vt[d],
3331 d++;
3332 var r = W(i, a);
3333 i += 5 * r * 17 / 1e3,
3334 t += 7 * Math.cos(i) * 17 / 1e3,
3335 n += 7 * Math.sin(i) * 17 / 1e3,
3336 o -= 17,
3337 l += 17
3338 }
3339 if (qt[e]) {
3340 var c = Math.sqrt(Math.pow(t - qt[e], 2) + Math.pow(n - Et[e], 2)),
3341 g = c > 3 ? c : c > 1 ? .2 : Math.min(c, .05);
3342 //! 31: if enabled use a different smoothing function
3343 //!! fix local var references
3344 /**/if ( locSmoothingEnabled ) g = ( c < locSmoothingMin || c > locSmoothingMax ? c : locSmoothingMin + locSmoothingExtent * Math.pow(( c - locSmoothingMin ) / locSmoothingExtent, locSmoothingExp ));
3345 c > 0 && g > 0 && (qt[e] += (t - qt[e]) * g / c,
3346 Et[e] += (n - Et[e]) * g / c,
3347 Nt || e != Ft || (We.x = window.innerWidth / 2 - qt[Ft] * dt,
3348 We.y = window.innerHeight / 2 - Et[Ft] * dt,
3349 ye()))
3350 } else
3351 console.log("set initial position for player", e, t, n),
3352 qt[e] = t,
3353 Et[e] = n;
3354 if ((Nt || Ft > 0 && e != Ft) && (wt[e] || E(e),
3355 //! 25: bug fix, fixes flickering of other player icons in some cases
3356 /**/playerIcons()[ player ].x = playerXLoc()[ player ] * scale(),
3357 /**/playerIcons()[ player ].y = playerYLoc()[ player ] * scale(),
3358 //wt[e].x = qt[e] * dt - wt[e].width / 2,
3359 //wt[e].y = Et[e] * dt - wt[e].height / 2,
3360 It[e].x = qt[e] * dt - It[e].width / 2,
3361 //! 9: set text offset to support zoom
3362 /**/playerText()[ player ].y = playerYLoc()[ player ] * scale() - playerSize * scale() / 2 - playerNameFontSize() * zoom ),
3363 //It[e].y = Et[e] * dt - gt - ut),
3364 bt[e]) {
3365 var u = W(bt[e], i);
3366 u > .1 && (u = .1),
3367 u < -.1 && (u = -.1),
3368 bt[e] += u
3369 } else
3370 bt[e] = i;
3371 xt[e] = a
3372 }
3373
3374 function le(e) {
3375 var t = (new Date).getTime(),
3376 n = e.getUint32(1, !0),
3377 i = zt + 17 * n,
3378 a = e.getInt16(5, !0);
3379 if (a == Ft && !Nt && Mt[Ft]) {
3380 var o = Math.abs((new Date).getTime() - Mt[Ft] - 51);
3381 o > Wt && (Wt = o)
3382 }
3383 Mt[a] = t;
3384 var r = e.getFloat32(7, !0),
3385 s = e.getFloat32(11, !0),
3386 l = e.getFloat32(15, !0),
3387 d = e.getFloat32(19, !0),
3388 c = [];
3389 if (e.byteLength > 23)
3390 for (var g = e.getInt16(23, !0), u = e.getInt16(25, !0), h = 0; h < g; h++) {
3391 var f = e.getFloat32(27 + 8 * h + 0, !0),
3392 m = e.getFloat32(27 + 8 * h + 4, !0);
3393 c.push([a, u + h, f, m])
3394 }
3395 if (i < t) {
3396 for (var h = 0; h < c.length; h++)
3397 ge(c[h][0], c[h][1], c[h][2], c[h][3]);
3398 Pt[a] && Bt[a] && Pt[a].splice(2 * (Bt[a] + 1)),
3399 se(a, r, s, l, d, t - i),
3400 ce(a)
3401 } else
3402 Qt[a] || (Qt[a] = []),
3403 Qt[a].push({
3404 playerId: a,
3405 x: r,
3406 y: s,
3407 dir: l,
3408 targetDir: d,
3409 time: i,
3410 points: c
3411 })
3412 }
3413
3414 function de(t, n, i) {
3415 var a = ht / 20;
3416 if (!kt[t]) {
3417 new PIXI.Point(qt[t] * dt, Et[t] * dt),
3418 new PIXI.Point(qt[t] * dt + 50, Et[t] * dt + 50);
3419 mn[t] = [],
3420 kt[t] = new PIXI.mesh.Rope(PIXI.loader.resources["img/traine.png"].texture, mn[t]),
3421 kt[t].tint = e(t),
3422 kt[t].scale.x = a,
3423 kt[t].scale.y = a,
3424 Oe.addChild(kt[t])
3425 }
3426 for (var o = 0; o < Pt[t].length / 2; o++)
3427 mn[t][o] ? (mn[t][o].x = Pt[t][2 * o] / a,
3428 mn[t][o].y = Pt[t][2 * o + 1] / a) : mn[t][o] = new PIXI.Point(Pt[t][2 * o] / a, Pt[t][2 * o + 1] / a);
3429 kt[t].refresh();
3430 for (var r = .034 * 7 * dt / PIXI.loader.resources["img/traine.png"].texture.width / a, s = kt[t].uvs, l = 0, o = 0; o < s.length / 4; o++)
3431 s[4 * o] = l,
3432 s[4 * o + 2] = l,
3433 l += r
3434 }
3435
3436 function ce(e) {
3437 var t = (new Date).getTime();
3438 if (Pt[e] && Pt[e].length >= 4) {
3439 var n = qt[e] * dt,
3440 i = Et[e] * dt,
3441 a = Pt[e][Pt[e].length - 1],
3442 o = Pt[e][Pt[e].length - 2];
3443 Math.sqrt(Math.pow(o - n, 2) + Math.pow(a - i, 2)) > ut && (Pt[e].push(n, i),
3444 Ct[e].push(t),
3445 de(e, Pt.length / 2 - 1, Pt.length / 2 - 1))
3446 }
3447 }
3448
3449 function ge(e, t, n, i, a) {
3450 if (void 0 === a && (a = (new Date).getTime()),
3451 Pt[e] || (Pt[e] = []),
3452 Ct[e] || (Ct[e] = []),
3453 Pt[e][2 * t] = n * dt,
3454 Pt[e][2 * t + 1] = i * dt,
3455 Ct[e][t] = a,
3456 (!Bt[e] || Bt[e] < t) && (Bt[e] = t),
3457 Dt) {
3458 de(e, t, t);
3459 var o = v(n * dt, i * dt),
3460 s = r(o.q, o.r),
3461 l = p(o.q, o.r);
3462 s != e && l != e && Y(o.q, o.r) && d(o.q, o.r, e)
3463 }
3464 }
3465
3466 function ue(t) {
3467 //! 26 cache, use to avoid duplicate entries in the server provided list, causes area loss sound to occur
3468 /**/var cache = [];
3469 for (var n = t.getInt16(1, !0), i = t.getInt16(3, !0), a = [], o = 0; o < i; o++) {
3470 var s = t.getInt16(5 + 4 * o, !0),
3471 l = t.getInt16(5 + 4 * o + 2, !0);
3472 //!! 26: local variables
3473 /**/var qIndex = s, rIndex = l;
3474 //! 26: don't insert entry if it already exists
3475 /**/if ( undefined == cache[qIndex] ) cache[qIndex] = []; if ( undefined == cache[qIndex][rIndex] )
3476 a.push({
3477 q: s,
3478 r: l
3479 })
3480 //! 26: mark new entry as existing
3481 /**/, cache[s][l] = true
3482 }
3483 var d = Q(a);
3484 console.log("read capture for player", n, "and path count :", i, "hexs to fill :", d.length),
3485 Array.prototype.push.apply(d, a);
3486 for (var c = Nt || !St ? v($e * dt, et * dt) : v(qt[Ft] * dt, Et[Ft] * dt), g = [], h = 0, o = 0; o < a.length; o++)
3487 u(a[o].q, a[o].r);
3488 //!! 26: 34: local variables
3489 /**/var player = n, hexList = d;
3490 //! 26 determine loation of capture
3491 /**/var qSum = 0, rSum = 0;
3492 /**/for ( var index = 0; index < hexList.length; index++ ) qSum += hexList[index].q, rSum += hexList[index].r;
3493 /**/var point = hexToRectIndex( qSum / hexList.length, rSum / hexList.length );
3494 //! 26: play capture sound
3495 /**/ var currXLoc = playerDead() ? currentPlayerDeathX() : playerXLoc()[ currentPlayer() ],
3496 /**/ currYLoc = playerDead() ? currentPlayerDeathY() : playerYLoc()[ currentPlayer() ],
3497 /**/ dist = Math.sqrt(( point.x / scale() - currXLoc ) * ( point.x / scale() - currXLoc ) + ( point.y / scale() - currYLoc ) * ( point.y / scale() - currYLoc )),
3498 /**/ sound = new Audio( player == currentPlayer() ? captureSelfSound : captureOtherSound );
3499//::: working here, debug code to help determine source of occasionally produces error
3500 if ( isNaN( dist ))
3501 {
3502 console.log( "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", playerDead() );
3503 console.log( "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", currentPlayerDeathX(), currentPlayerDeathY() );
3504 console.log( "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", playerXLoc()[ currentPlayer() ], playerYLoc()[ currentPlayer() ] );
3505 console.log( "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", currXLoc, currYLoc );
3506 console.log( "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", point.x, point.y );
3507 console.log( "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", dist );
3508 }
3509 /**/ sound.volume = 1 / ( 1 + dist / 6 ) * getVolume();
3510 /**/ sound.play();
3511 for (var o = 0; o < d.length; o++) {
3512 var f = r(d[o].q, d[o].r);
3513 //!! 26: local variables
3514 /**/var hexPid = f;
3515 //!! 34: local variables
3516 /**/var hexQIndex = d[o].q, hexRIndex = d[o].r;
3517 if (f != n && (f == Ft && Jt--,
3518 h++,
3519 N(d[o], c) <= lt)) {
3520 w(b(d[o].q, d[o].r), e(0)),
3521 u(d[o].q, d[o].r);
3522 //! 34: support variegated colors
3523 //!! update use of local variable
3524 //var m = k(e(n), void 0, void 0, void 0, 0, !0);
3525 /**/var m = setBufferSpriteTint( variegateColor( player, playerColor( player ), hexQIndex, hexRIndex ), void 0, void 0, void 0, 0, !0 );
3526 m.hexPosition = d[o],
3527 m.position = y(d[o].q, d[o].r),
3528 m.width = .5 * (ct - .1 * dt * 2),
3529 m.height = .5 * (gt - .1 * dt * 2),
3530 m.x += .1 * dt + .25 * (ct - .1 * dt * 2),
3531 m.y += .1 * dt + .25 * (gt - .1 * dt * 2),
3532 Ge.addChild(m),
3533 g.push(m)
3534 }
3535 //! 26: orig code should not have reduced current block count, if not updating map
3536 /**/else if ( hexPid != player && hexPid == currentPlayer() ) setBlockCount( blockCount() + 1 );
3537 }
3538 if (Gt.push({
3539 type: "CAPTURING",
3540 hexs: g,
3541 from: .5,
3542 to: 1,
3543 time: 0,
3544 //! 21: support fast transition
3545 /**/length: fastTransitionsEnabled ? 0 : 200,
3546 //length: 200,
3547 playerId: n
3548 }),
3549 n == Ft && (Jt += h,
3550 h >= 100 && Me(h)),
3551 xe(),
3552 delete Pt[n],
3553 delete Ct[n],
3554 delete mn[n],
3555 delete Bt[n],
3556 Oe.removeChild(kt[n]),
3557 delete kt[n],
3558 Qt[n])
3559 for (var o = 0; o < Qt[n].length; o++)
3560 Qt[n][o].points = []
3561 }
3562
3563 function he(n) {
3564 var i = n.getInt16(1, !0),
3565 a = n.getInt16(3, !0);
3566 //!! 34: local variables
3567 /**/var player = i;
3568 //!! 12: 26: 32: local variables
3569 /**/var data = n, killer = a,
3570 /**/ xLoc = data.byteLength >= 13 ? data.getFloat32( 5, true ) : undefined,
3571 /**/ yLoc = data.byteLength >= 13 ? data.getFloat32( 9, true ) : undefined;
3572 console.log("player is dead", i, "killer is ", a);
3573 for (var o = [], l = Nt || !St ? v($e * dt, et * dt) : v(qt[Ft] * dt, Et[Ft] * dt), d = {
3574 q: 0,
3575 r: 0
3576 }, c = -lt; c <= lt; c++)
3577 for (var g = -lt; g <= lt; g++)
3578 if (d.q = l.q + c,
3579 d.r = l.r + g,
3580 Y(d.q, d.r))
3581 if (r(d.q, d.r) == i) {
3582 if (s(d.q, d.r),
3583 N(l, d) <= lt) {
3584 //!! 34: local variables
3585 /**/var hexQIndex = d.q, hexRIndex = d.r;
3586 //! 34: support variegated colors
3587 //!! update use of local variable
3588 //var u = k(e(i), void 0, void 0, void 0, 0, !0);
3589 /**/var u = setBufferSpriteTint( variegateColor( player, playerColor( player ), hexQIndex, hexRIndex ), void 0, void 0, void 0, 0, !0 );
3590 u.hexPosition = {
3591 q: d.q,
3592 r: d.r
3593 },
3594 u.position = y(d.q, d.r),
3595 u.width = ct - .1 * dt * 2,
3596 u.height = gt - .1 * dt * 2,
3597 u.x += .1 * dt,
3598 u.y += .1 * dt,
3599 Ge.addChild(u),
3600 o.push(u)
3601 }
3602 } else
3603 p(d.q, d.r) == i && h(d.q, d.r);
3604 if (o.length > 0 && setTimeout(function() {
3605 Gt.push({
3606 type: "UNCAPTURING",
3607 hexs: o,
3608 from: 1,
3609 to: 0,
3610 time: 0,
3611 //! 21: support fast transition
3612 /**/length: fastTransitionsEnabled ? 60 : 500,
3613 //length: 500,
3614 playerId: i
3615 })
3616 //! 21: support fast transition
3617 /**/}, fastTransitionsEnabled ? 0 : 500 ),
3618 //}, 500),
3619 n.byteLength >= 13) {
3620 var f = n.getFloat32(5, !0),
3621 m = n.getFloat32(9, !0),
3622 w = new PIXI.Graphics,
3623 I = .01 * ut;
3624 //! 12: track last known location for the killer
3625 /**/if ( killer > 0 ) playerLastLocX[ killer ] = xLoc, playerLastLocY[ killer ] = yLoc;
3626 //! 29: count the kill
3627 /**/if ( killer > 0 ) killCount[ killer ] = 1 + ( killCount[ killer ] ? killCount[ killer ] : 0 );
3628 //! 26: play death sound
3629 /**/var currXLoc = playerDead() ? currentPlayerDeathX() : playerXLoc()[ currentPlayer() ],
3630 /**/ currYLoc = playerDead() ? currentPlayerDeathY() : playerYLoc()[ currentPlayer() ],
3631 /**/ dist = Math.sqrt(( xLoc - currXLoc ) * ( xLoc - currXLoc ) + ( yLoc - currYLoc ) * ( yLoc - currYLoc )),
3632 /**/ sound = new Audio( killSound );
3633 /**/sound.volume = 1 / ( 1 + dist / 6 ) * getVolume() * 0.5;
3634 /**/sound.play();
3635 w.beginFill(16777215),
3636 w.drawCircle(f * dt, m * dt, I),
3637 w.endFill(),
3638 w.alpha = 1,
3639 Ve.addChild(w),
3640 Gt.push({
3641 type: "IMPACT",
3642 from: 0,
3643 to: 1,
3644 time: 0,
3645 length: 300,
3646 playerId: i,
3647 impactX: f,
3648 impactY: m,
3649 sprite: w
3650 })
3651 }
3652 for (var b = [], x = 0; x < 9; x++) {
3653 var w = new PIXI.Graphics,
3654 I = dt / 6 + Math.random() * dt / 9;
3655 w.lineStyle(ft * I / ut, e(i), 1),
3656 w.beginFill(t(i)),
3657 w.drawCircle(qt[i] * dt, Et[i] * dt, I),
3658 w.endFill(),
3659 b.push(w),
3660 w.animationDirection = Math.random() * Math.PI * 2,
3661 w.animationSpeed = 2 * Math.random() + 2,
3662 Ve.addChild(w)
3663 }
3664 if (Gt.push({
3665 type: "EXPLOSION",
3666 parts: b,
3667 from: 1,
3668 to: 0,
3669 time: 0,
3670 length: 500,
3671 moveLength: 500,
3672 playerId: i
3673 }),
3674 kt[i] && (Gt.push({
3675 type: "TRAIL_DISAPPEAR",
3676 trailGraphics: kt[i],
3677 from: 1,
3678 to: 0,
3679 time: 0,
3680 //! 21: support fast transition
3681 /**/length: fastTransitionsEnabled ? 0 : 200,
3682 //length: 200,
3683 playerId: i
3684 }),
3685 delete kt[i],
3686 delete Pt[i],
3687 delete Ct[i],
3688 delete mn[i],
3689 delete Bt[i]),
3690 Nt || i != Ft)
3691 fe(i);
3692 else {
3693 //! 32: update the match results
3694 /**/document.getElementById( "killedBy" ).innerHTML = rawPlayerNames[ killer ] ? `${htmlEncode( tabooStr( rawPlayerNames[ killer ] ))} (id:${killer}${ killer == currentPlayer() ? ', yourself': '' })` : killer == 0 ? 'WALL' : `id:${killer}`;
3695 /**/document.getElementById( "killedAt" ).innerHTML = xLoc ? `${Math.round( xLoc )}, ${Math.round( -yLoc )}` : 'unknown';
3696 Nt = !0,
3697 $e = qt[Ft],
3698 et = Et[Ft],
3699 delete qt[Ft],
3700 delete Et[Ft],
3701 delete bt[Ft],
3702 delete xt[Ft],
3703 delete Qt[Ft],
3704 ze.visible = !1,
3705 Fe.visible = !1,
3706 document.getElementById("rankValue").innerHTML = "",
3707 document.getElementById("finalScore").innerHTML = Jt + 500 * Yt;
3708 var q = 100 * Jt / (1 + 3 * st * (st + 1));
3709 document.getElementById("finalMapPercent").innerHTML = q.toString().match(/^-?\d+(?:\.\d{0,1})?/)[0],
3710 document.getElementById("finalKills").innerHTML = Yt,
3711 document.getElementById("finalBlocks").innerHTML = Jt,
3712 setTimeout(function() {
3713 document.getElementById("fps") && (document.getElementById("fps").style.display = "none"),
3714 document.getElementById("respawn").style.display = "block",
3715 //! 1: set focus on play again button
3716 /**/document.getElementById( "button-play-again" ).focus(),
3717 document.getElementById("victory").style.display = "none",
3718 _e = setTimeout(function() {
3719 location.reload()
3720 }, 9e5),
3721 "undefined" != typeof ga && (ga("set", "page", "/respawn"),
3722 ga("send", "pageview"))
3723 //! 21: support fast transition
3724 /**/}, fastTransitionsEnabled ? 200 : 1000 )
3725 //}, 1e3),
3726 "undefined" != typeof googletag && googletag.pubads && googletag.pubads().refresh(),
3727 factorem && factorem.refreshAds([1], !0)
3728 }
3729 delete nn[i],
3730 delete tn[i],
3731 delete an[i],
3732 Nt || Zt--,
3733 Nt || i == Ft || a != Ft || (Yt++,
3734 Me(500)),
3735 xe()
3736 }
3737
3738 function fe(e) {
3739 Ve.removeChild(wt[e]),
3740 Ve.removeChild(It[e]),
3741 delete wt[e],
3742 delete It[e],
3743 delete qt[e],
3744 delete Et[e],
3745 delete bt[e],
3746 delete xt[e],
3747 delete Qt[e],
3748 delete Mt[e]
3749 }
3750
3751 function me(e, t, n) {
3752 console.log("player joins", e),
3753 f(t, n, e);
3754 for (var i = 0; i < 6; i++)
3755 Y(t + fn[i].q, n + fn[i].r) && f(t + fn[i].q, n + fn[i].r, e);
3756 Nt || Zt++
3757 }
3758
3759 function pe(e) {
3760 for (var t = e.getInt16(1, !0), n = e.getInt16(3, !0), i = e.getInt16(5, !0), a = e.getInt16(7, !0), o = V({
3761 q: i,
3762 r: a
3763 }, {
3764 q: t,
3765 r: n
3766 }), r = {
3767 q: i + fn[(o + 5) % 6].q * lt,
3768 r: a + fn[(o + 5) % 6].r * lt
3769 }, s = (o + 1) % 6, d = 0; d < lt; d++)
3770 Y(r.q, r.r) && l(r.q, r.r),
3771 G(r, s);
3772 for (var c = (o + 2) % 6, d = 0; d < lt; d++)
3773 Y(r.q, r.r) && l(r.q, r.r),
3774 G(r, c);
3775 Y(r.q, r.r) && l(r.q, r.r);
3776 for (var g = e.getInt16(9, !0), u = 0; u < g; u++) {
3777 m(e.getInt16(11 + 8 * u + 0, !0), e.getInt16(11 + 8 * u + 2, !0), e.getInt16(11 + 8 * u + 4, !0), e.getInt16(11 + 8 * u + 6, !0))
3778 }
3779 }
3780
3781 function ye() {
3782 var e = v(qt[Ft] * dt, Et[Ft] * dt);
3783 if (!Ze || Math.abs(e.q - Ze.q) > 4 || Math.abs(e.r - Ze.r) > 4) {
3784 b(e.q, e.r).visible = !0;
3785 for (var t = 1; t <= lt; t++)
3786 for (var n = {
3787 q: e.q + fn[4].q * t,
3788 r: e.r + fn[4].r * t
3789 }, i = 0; i < 6; i++)
3790 for (var a = 0; a < t; a++)
3791 K(n.q, n.r) && (b(n.q, n.r).visible = !0),
3792 G(n, i);
3793 Ze = e
3794 } else if (e.q != Ze.q || e.r != Ze.r)
3795 do {
3796 var o = {
3797 q: e.q - Ze.q,
3798 r: e.r - Ze.r
3799 };
3800 o.q = 0 == o.q ? 0 : o.q / Math.abs(o.q),
3801 o.r = 0 == o.r ? 0 : o.r / Math.abs(o.r);
3802 for (var r = {
3803 q: Ze.q + o.q,
3804 r: Ze.r + o.r
3805 }, s = V(r, Ze), n = {
3806 q: r.q + fn[(s + 5) % 6].q * lt,
3807 r: r.r + fn[(s + 5) % 6].r * lt
3808 }, l = (s + 1) % 6, a = 0; a < lt; a++)
3809 K(n.q, n.r) && (b(n.q, n.r).visible = !0),
3810 G(n, l);
3811 for (var d = (s + 2) % 6, a = 0; a < lt; a++)
3812 K(n.q, n.r) && (b(n.q, n.r).visible = !0),
3813 G(n, d);
3814 K(n.q, n.r) && (b(n.q, n.r).visible = !0),
3815 n = {
3816 q: Ze.q + fn[(s + 2) % 6].q * lt,
3817 r: Ze.r + fn[(s + 2) % 6].r * lt
3818 };
3819 for (var c = (s + 4) % 6, a = 0; a < lt; a++)
3820 K(n.q, n.r) && (x(n.q, n.r),
3821 u(n.q, n.r)),
3822 G(n, c);
3823 for (var g = (s + 5) % 6, a = 0; a < lt; a++)
3824 K(n.q, n.r) && (x(n.q, n.r),
3825 u(n.q, n.r)),
3826 G(n, g);
3827 K(n.q, n.r) && (x(n.q, n.r),
3828 u(n.q, n.r)),
3829 Ze = r
3830 } while (Ze.q != e.q || Ze.r != e.r)
3831 }
3832
3833 function ve(e) {
3834 for (var t = e.getInt16(1, !0), n = 3, i = 0; i < t; i++) {
3835 var a = e.getInt16(n, !0),
3836 o = e.getInt16(n + 2, !0),
3837 r = e.getInt16(n + 4, !0);
3838 console.log("part for player", a, "point from ", o, "to", r);
3839 for (var s = 0; s <= r - o; s++) {
3840 var l = e.getFloat32(n + 6 + 8 * s, !0),
3841 d = e.getFloat32(n + 6 + 8 * s + 4, !0);
3842 Pt[a] || (Pt[a] = []),
3843 Ct[a] || (Ct[a] = []),
3844 Pt[a][2 * (o + s)] = l * dt,
3845 Pt[a][2 * (o + s) + 1] = d * dt
3846 }
3847 n += 6 + 8 * (r - o + 1),
3848 Dt && de(a, o, r)
3849 }
3850 }
3851
3852 function ke(e) {
3853 return e.replace(/&/g, "&").replace(/>/g, ">").replace(/</g, "<").replace(/"/g, """)
3854 }
3855
3856 function we(e) {
3857 for (var t = e.split(" "), n = 0; n < t.length; n++) {
3858 var i = t[n].toUpperCase(),
3859 a = (i.match(/[a-zA-Z0-9]/) || []).pop();
3860 if (void 0 !== yn[a])
3861 for (var o = 0; o < yn[a].length; o++) {
3862 var r = yn[a][o];
3863 i.indexOf(r) != -1 && (t[n] = "â¤")
3864 }
3865 }
3866 e = t.join(" ");
3867 for (var o = 0; o < yn[" "].length; o++) {
3868 var r = yn[" "][o];
3869 e.toUpperCase().indexOf(r.toUpperCase()) !== -1 && (e = e.replace(new RegExp(yn[" "][o], "ig"), "â¤"))
3870 }
3871 return e
3872 }
3873
3874 function Ie(e) {
3875 for (var t = e.getInt16(1, !0), n = e.getUint8(3, !0), i = "", a = 0; a < n; a++) {
3876 var o = e.getUint8(4 + 2 * a, !0) | e.getUint8(4 + 2 * a + 1, !0) << 8;
3877 i += String.fromCharCode(o)
3878 }
3879 //!! 1: local variables
3880 /**/var playerId = t, playerName = i;
3881 console.log("username received for player", t, i),
3882 tn[t] = we(ke(i)),
3883 //! 1: keep track of the raw unencoded player name
3884 /**/rawPlayerNames[ playerId ] = tabooStr( playerName ),
3885 Nt || t != Ft || (tt = i,
3886 Ue.removeChild(Fe),
3887 Fe = new PIXI.Text(tt, {
3888 fontFamily: "Arial",
3889 //! 8: change player name size
3890 /**/fontSize: playerNameFontSize(),
3891 //fontSize: 16 / gn,
3892 fill: 16777215
3893 }),
3894 //! 1: hide taboo words in name of current player
3895 /**/currentPlayerText().text = tabooStr( username() ),
3896 Fe.x = window.innerWidth / 2 - Fe.width / 2,
3897 //! 9: set text offset to support zoom
3898 /**/currentPlayerText().anchor.y = 1.0,
3899 /**/currentPlayerText().y = window.innerHeight / 2 - playerSize * scale() / 2 - playerNameFontSize() * zoom,
3900 //Fe.y = window.innerHeight / 2 - gt - ut,
3901 Ue.addChild(Fe))
3902 }
3903
3904 function be(e) {
3905 var t = e.getInt16(1, !0),
3906 n = e.getInt16(3, !0);
3907 console.log("skin id received for player", t, n),
3908 nn[t] = n
3909 }
3910
3911 function xe() {
3912 //! 26: play area loss sound
3913 /**/if ( !playerDead() )
3914 /**/{
3915 /**/ if ( blockCount() < lastBlockCount )
3916 /**/ {
3917 /**/ var sound = new Audio( areaLossSound );
3918 /**/ sound.volume = 0.1 * getVolume();
3919 /**/ sound.play();
3920 /**/ }
3921 /**/ lastBlockCount = blockCount();
3922 /**/}
3923 document.getElementById("scoreValue").innerHTML = Jt + 500 * Yt;
3924 var e = 100 * Jt / (1 + 3 * st * (st + 1));
3925 document.getElementById("mapPercent").innerHTML = e.toString().match(/^-?\d+(?:\.\d{0,1})?/)[0],
3926 document.getElementById("blocksValue").innerHTML = Jt,
3927 document.getElementById("killsValue").innerHTML = Yt,
3928 document.getElementById("rankValue").innerHTML = Kt,
3929 document.getElementById("totalPlayers").innerHTML = Zt,
3930 //! 10: allow play even after current player completely fills the lobby
3931 /**/!playAfterVictoryEnabled &&
3932 St && !At && e >= 100 && (At = !0,
3933 Ut = (new Date).getTime(),
3934 document.getElementById("victory").style.display = "block")
3935 }
3936
3937 function qe() {
3938 for (var e = 0; e < $t.length; e++)
3939 $t[e].playerId > 0 ? (document.getElementById("lb" + (e + 1) + "Name").innerHTML = tn[$t[e].playerId],
3940 document.getElementById("lb" + (e + 1) + "Score").innerHTML = $t[e].score) : (document.getElementById("lb" + (e + 1) + "Name").innerHTML = "",
3941 document.getElementById("lb" + (e + 1) + "Score").innerHTML = "")
3942 //! 11: 12: 13: 14: customize the leaderboard
3943 /**/;updateLeaderBoard();
3944 }
3945
3946 function Ee() {
3947 document.getElementById("friendsScores").style.display = en.length > 0 ? "block" : "none";
3948 //! 12: 13: update the friends detail with mod content
3949 /**/updateFriendsList();return;
3950 for (var e = '<table class="table">', t = 0; t < en.length; t++)
3951 en[t].rank > 0 && (e += '<tr><td class="rank">' + en[t].rank + '.</td><td class="name">' + tn[en[t].playerId] + '</td><td class="fscore">' + en[t].score + "</td></tr>");
3952 e += '</table><div class="separator"></div>',
3953 document.getElementById("friends-detail").innerHTML = e
3954 }
3955
3956 function Me(e) {
3957 document.getElementById("bonusDisplay").innerHTML = "+" + e,
3958 document.getElementById("bonusDisplay").classList.remove("shown"),
3959 setTimeout(function() {
3960 document.getElementById("bonusDisplay").classList.add("shown")
3961 }, 100)
3962 }
3963
3964 function Pe() {
3965 var e = v(qt[Ft] * dt, Et[Ft] * dt),
3966 t = e.q / st * .866,
3967 n = (e.r + e.q / 2) / st;
3968 document.getElementById("minimap-position").setAttribute("cx", t),
3969 document.getElementById("minimap-position").setAttribute("cy", n)
3970 }
3971
3972 function Be() {
3973 document.getElementById("button-create-party").style.display = "none",
3974 document.getElementById("spinner-create-party").style.display = "block",
3975 "undefined" != typeof ga && ga("send", "event", "Click", "CreateParty");
3976 var e, t = Ae && 0 == Ae.length,
3977 n = null;
3978 if (Ae)
3979 for (var i = 0; i < Ae.length; i++) {
3980 var a = Ae[i].z;
3981 mt[a] && (t = !0,
3982 mt[a] != -1 && (!n || e > mt[a]) && (n = a,
3983 e = mt[a]))
3984 }
3985 if (!t)
3986 return void setTimeout(Be, 50);
3987 //! 2: use selected region
3988 //!! update use of local variable
3989 /**/if ( geoRegion != 'auto' ) n = geoRegion;
3990 var o = new XMLHttpRequest;
3991 o.onreadystatechange = function() {
3992 4 == o.readyState && 200 == o.status && (it = o.responseText,
3993 document.getElementById("create-party").style.display = "none",
3994 document.getElementById("in-party").style.display = "block",
3995 window.location.hash = it,
3996 document.getElementById("party-share-link").value = "http://" + window.location.hostname + (window.location.port ? ":" + window.location.port : "") + window.location.pathname + "#" + it)
3997 },
3998 o.onerror = function() {
3999 console.log("Error with createParty, retrying in 5s"),
4000 setTimeout(Be, 5e3)
4001 },
4002 o.open("GET", MM_URL + "/createParty" + (n ? "?zone=" + n : ""), !0),
4003 o.send(null)
4004 }
4005
4006 function Ce() {
4007 it = null,
4008 window.location.hash = "",
4009 document.getElementById("button-create-party").style.display = "block",
4010 document.getElementById("spinner-create-party").style.display = "none",
4011 document.getElementById("create-party").style.display = "block",
4012 document.getElementById("in-party").style.display = "none",
4013 "undefined" != typeof ga && ga("send", "event", "Click", "LeaveParty")
4014 }
4015
4016 function Se(e) {
4017 return 0 != e || "undefined" == typeof Storage || localStorage.getItem("followClicked") ? 1 != e || "undefined" == typeof Storage || localStorage.getItem("tweetClicked") ? 2 != e || "undefined" == typeof Storage || localStorage.getItem("likeClicked") ? 3 != e || "undefined" == typeof Storage || localStorage.getItem("subscribeClicked") ? 4 != e || "undefined" == typeof Storage || localStorage.getItem("shareClicked") ? (document.getElementById("skin-" + rn) && document.getElementById("skin-" + rn).childNodes.item(1).classList.remove("selected"),
4018 rn == e ? (rn = -1,
4019 document.getElementById("skin-" + e).childNodes.item(1).classList.remove("selected")) : (rn = e,
4020 document.getElementById("skin-" + e).childNodes.item(1).classList.add("selected")),
4021 document.getElementById("selected-skin-img").src = "img/bs/" + (rn == -1 ? "fox" : vn[rn]) + ".png",
4022 void("undefined" != typeof Storage && localStorage.setItem("selectedSkin", rn))) : (window.open("https://www.facebook.com/sharer?u=http%3A%2F%2Fsuperhex.io%2F", "_blank"),
4023 localStorage.setItem("shareClicked", 1),
4024 void Te(sn)) : (window.open("https://www.youtube.com/channel/UCOF4hltac1SF61qBE7mSgoQ?sub_confirmation=1", "_blank"),
4025 localStorage.setItem("subscribeClicked", 1),
4026 void Te(sn)) : (window.open("https://www.facebook.com/superhex.io/", "_blank"),
4027 localStorage.setItem("likeClicked", 1),
4028 void Te(sn)) : (window.open("https://twitter.com/intent/tweet?text=Come%20play%20with%20me!%20http%3A%2F%2Fsuperhex.io%2F%20%23superhexio", "_blank"),
4029 localStorage.setItem("tweetClicked", 1),
4030 void Te(sn)) : (window.open("https://twitter.com/intent/follow?screen_name=superhexio", "_blank"),
4031 localStorage.setItem("followClicked", 1),
4032 void Te(sn))
4033 }
4034
4035 function Te(e) {
4036 sn = e;
4037 for (var t = document.getElementById("skin-list"), n = "", i = 0; i < vn.length; i++)
4038 i >= e && i <= e + 3 && (n += '<div id="skin-' + i + '" class="card" onclick="superhex.selectSkin(' + i + ')"><div class="imgcontainer"><img src="img/' + vn[i] + '.png"></div>',
4039 n += 0 != i || "undefined" == typeof Storage || localStorage.getItem("followClicked") ? 1 != i || "undefined" == typeof Storage || localStorage.getItem("tweetClicked") ? 2 != i || "undefined" == typeof Storage || localStorage.getItem("likeClicked") ? 3 != i || "undefined" == typeof Storage || localStorage.getItem("subscribeClicked") ? 4 != i || "undefined" == typeof Storage || localStorage.getItem("shareClicked") ? '<div class="checkbox' + (i == rn ? " selected" : "") + '"></div>' : '<div class="skin-share"><div class="text">Share</div></div>' : '<div class="skin-subscribe"><div class="text">Subscribe</div></div>' : '<div class="skin-like"><div class="text">Like</div></div>' : '<div class="skin-tweet"><div class="text">Tweet</div></div>' : '<div class="skin-follow"><div class="text">Follow</div></div>',
4040 n += "</div>");
4041 t.innerHTML = n,
4042 document.getElementById("skin-left-arrow").style.visibility = sn > 0 ? "visible" : "hidden",
4043 document.getElementById("skin-right-arrow").style.visibility = sn + 4 < vn.length ? "visible" : "hidden"
4044 }
4045
4046 function Le() {
4047 Te(sn + 4)
4048 }
4049
4050 function He() {
4051 Te(sn - 4)
4052 }
4053
4054 function De() {
4055 if (wn || In || bn || xn)
4056 j({
4057 clientX: window.innerWidth / 2 + (xn ? 10 : 0) + (bn ? -10 : 0),
4058 clientY: window.innerHeight / 2 + (wn ? -10 : 0) + (In ? 10 : 0)
4059 });
4060 else {
4061 if (!bt[Ft])
4062 return;
4063 j({
4064 clientX: window.innerWidth / 2 + 10 * Math.cos(bt[Ft]),
4065 clientY: window.innerHeight / 2 + 10 * Math.sin(bt[Ft])
4066 })
4067 }
4068 }
4069 var WSERVER_URL, Ae, Re, Xe, Ue, ze, Fe, je, _e, We, Ne, Ge, Oe, Ve, Qe, Ye, Je, Ke, Ze, $e, et, tt, nt, it, at, ot, rt, st = 15,
4070 lt = 15,
4071 dt = Math.max(window.innerWidth / 40 / 2 / .75, window.innerHeight / 40 / Math.sqrt(3)),
4072 ct = 2 * dt,
4073 gt = Math.sqrt(3) / 2 * ct,
4074 ut = .6 * dt,
4075 ht = 2 / 3 * ut,
4076 ft = ut / 3,
4077 mt = [],
4078 pt = [],
4079 yt = [],
4080 vt = [],
4081 kt = [],
4082 wt = [],
4083 It = [],
4084 bt = [],
4085 xt = [],
4086 qt = [],
4087 Et = [],
4088 Mt = [],
4089 Pt = [],
4090 Bt = [],
4091 Ct = [],
4092 St = !1,
4093 Tt = !1,
4094 Lt = !1,
4095 Ht = !1,
4096 Dt = !1,
4097 At = !1,
4098 Rt = !1,
4099 Xt = !1,
4100 Ut = 0,
4101 zt = 0,
4102 Ft = -1,
4103 jt = 0,
4104 _t = 0,
4105 Wt = 0,
4106 Nt = !1,
4107 Gt = [],
4108 Ot = [],
4109 Vt = [],
4110 Qt = [],
4111 Yt = 0,
4112 Jt = 7,
4113 Kt = 0,
4114 Zt = 0,
4115 $t = [],
4116 en = [],
4117 tn = [],
4118 nn = [],
4119 an = [],
4120 on = !1,
4121 rn = -1,
4122 sn = 0,
4123 ln = "undefined" != typeof Storage && localStorage.getItem("respawnCount") ? localStorage.getItem("respawnCount") : 0,
4124 dn = [5197647, 16776518, 16594229, 16735861, 2948860, 16657620, 9764642, 16747050, 16762930, 16718941, 1295871, 16147199, 2293637, 15597449, 9043927, 10289033],
4125 cn = [5197647, 7631624, 13116195, 9389135, 1486766, 13836207, 6271494, 13133334, 9596687, 12849990, 38599, 11682745, 962133, 8556345, 3192970, 4832562],
4126 gn = n(),
4127 un = 1,
4128 hn = [],
4129 fn = [{
4130 q: 1,
4131 r: 0
4132 }, {
4133 q: 1,
4134 r: -1
4135 }, {
4136 q: 0,
4137 r: -1
4138 }, {
4139 q: -1,
4140 r: 0
4141 }, {
4142 q: -1,
4143 r: 1
4144 }, {
4145 q: 0,
4146 r: 1
4147 }];
4148 document.addEventListener("DOMContentLoaded", function() {
4149 if ("undefined" != typeof Storage) {
4150 localStorage.getItem("username") && (document.getElementById("username").value = localStorage.getItem("username")),
4151 localStorage.getItem("selectedSkin") && (rn = localStorage.getItem("selectedSkin")),
4152 document.getElementById("selected-skin-img").src = "img/bs/" + (rn == -1 ? "fox" : vn[rn]) + ".png";
4153 var e = localStorage.getItem("quality");
4154 e && i(e)
4155 }
4156 window.location.hash && (6 == window.location.hash.length || 7 == window.location.hash.length && window.location.hash.startsWith("#")) && (it = window.location.hash.startsWith("#") ? window.location.hash.substr(1) : window.location.hash,
4157 document.getElementById("create-party").style.display = "none",
4158 document.getElementById("in-party").style.display = "block",
4159 document.getElementById("party-share-link").value = "http://" + window.location.hostname + (window.location.port ? ":" + window.location.port : "") + window.location.pathname + "#" + it),
4160 Te(0)
4161 //! 1: initialize the mod
4162 /**/;modInit();
4163 }, !1),
4164 window.addEventListener("load", function() {
4165 PIXI.loader.add("img/texturepack2.json").add("img/traine.png").load(function(e, t) {
4166 on = !0,
4167 P();
4168 for (var n = 0; n < t.length; n++)
4169 t[n].error && (console.error("error loading asset", t[n]),
4170 alert("Error loading game images, please reload the page. Clear your browser cache if this happens repeatedly."));
4171 nt = PIXI.loader.resources["img/texturepack2.json"].textures,
4172 PIXI.loader.resources["img/traine.png"].texture.baseTexture.wrapMode = PIXI.WRAP_MODES.REPEAT
4173 })
4174 }, !1);
4175 var mn = [],
4176 pn = ["4r5e", "5h1t", "5hit", "a_s_s", "a55", "anal", "analconda", "anus", "ar5e", "arrse", "arse", "arsehole", "ass", "ass fuck", "asses", "assfucker", "ass-fucker", "assfukka", "asshole", "assholes", "assmucus", "asswhole", "b!tch", "b00bs", "b17ch", "b1tch", "ballbag", "balls", "ballsack", "bastard", "bastards", "beastial", "beastiality", "bellend", "bestial", "bestiality", "bi+ch", "biatch", "bitch", "bitcher", "bitchers", "bitches", "bitchin", "bitching", "bloody", "blow job", "blowjob", "blowjobs", "blumpkin", "boiolas", "bollock", "bollocks", "bollok", "boner", "boob", "boobs", "booobs", "boooobs", "booooobs", "booooooobs", "breasts", "buceta", "bugger", "buggery", "bum", "bunny fucker", "butt", "butthole", "buttmuch", "buttplug", "c0ck", "c0cksucker", "carpet muncher", "cawk", "chink", "cipa", "cl1t", "clit", "clitoris", "clits", "cnut", "cock", "cockface", "cockhead", "cockmunch", "cockmuncher", "cocks", "cocksuck", "cocksucked", "cocksucker", "cock-sucker", "cocksucker", "cocksucking", "cocksucks", "cocksuka", "cocksukka", "cok", "cokmuncher", "coksucka", "coon", "cox", "crap", "cum", "cum freak", "cumdump", "cumjunkie", "cummer", "cumming", "cums", "cumshot", "cunilingus", "cunillingus", "cunnilingus", "cunt", "cuntbag", "cuntlick", "cuntlicker", "cuntlicking", "cunts", "cuntsicle", "cyalis", "cyberfuc", "cyberfuck", "cyberfucked", "cyberfucker", "cyberfuckers", "cyberfucking", "d1ck", "damn", "darn", "dick", "dickhead", "dildo", "dildos", "dink", "dinks", "dirsa", "dlck", "dog-fucker", "doggin", "dogging", "donkeyribber", "doosh", "douche", "duche", "dyke", "ejaculate", "ejaculated", "ejaculates", "ejaculating", "ejaculatings", "ejaculation", "ejakulate", "f u c k", "f u c k e r", "f_u_c_k", "f4nny", "facialize", "fag", "fagging", "faggitt", "faggot", "faggs", "fagot", "fagots", "fags", "fanny", "fannyflaps", "fannyfucker", "fanyy", "fart", "fatass", "fcuk", "fcuker", "fcuking", "feck", "fecker", "felch", "felching", "fellate", "fellatio", "fingerfuck", "fingerfucked", "fingerfucker", "fingerfuckers", "fingerfucking", "fingerfucks", "fistfuck", "fistfucked", "fistfucker", "fistfuckers", "fistfucking", "fistfuckings", "fistfucks", "flange", "fook", "fooker", "fuck", "fucka", "fuck-bitch", "fucked", "fucker", "fuckers", "fuckhead", "fuckheads", "fuckin", "fucking", "fucking ", "fuckings", "fuckingshitmotherfucker", "fuckme", "fuckmeat", "fucks", "fucktoy", "fuckwhit", "fuckwit", "fudge packer", "fudgepacker", "fuk", "fuker", "fukker", "fukkin", "fuks", "fukwhit", "fukwit", "fux", "fux0r", "gangbang", "gangbang", "gangbanged", "gangbangs", "gaylord", "gaysex", "goatse", "God", "god-dam", "goddamn", "goddamned", "god-damned", "hardcoresex", "hell", "heshe", "hoar", "hoare", "hoer", "homo", "hore", "horniest", "horny", "hotsex", "jackoff", "jack-off", "jap", "jerk-off", "jism", "jiz", "jizm", "jizz", "kawk", "knob", "knobead", "knobed", "knobend", "knobhead", "knobjocky", "knobjokey", "kock", "kondum", "kondums", "kum", "kummer", "kumming", "kums", "kunilingus", "l3i+ch", "l3itch", "labia", "liter", "lust", "lusting", "m0f0", "m0fo", "m45terbate", "ma5terb8", "ma5terbate", "masochist", "masterb8", "masterbat*", "masterbat3", "masterbate", "master-bate", "masterbation", "masterbations", "masturbate", "mof0", "mofo", "mo-fo", "mothafuck", "mothafucka", "mothafuckas", "mothafuckaz", "mothafucked", "mothafucker", "mothafuckers", "mothafuckin", "mothafucking", "mothafuckings", "mothafucks", "mother fucker", "motherfuck", "motherfucked", "motherfucker", "motherfucker", "motherfuckers", "motherfuckin", "motherfucking", "motherfucking", "motherfuckings", "motherfuckka", "motherfucks", "muff", "mutha", "muthafecker", "muthafuckker", "muther", "mutherfucker", "n1gga", "n1gger", "nazi", "nigg3r", "nigg4h", "nigga", "niggah", "niggas", "niggaz", "nigger", "niggers", "nob", "nob jokey", "nobhead", "nobjocky", "nobjokey", "numbnuts", "nutsack", "orgasim", "orgasims", "orgasm", "orgasms", "p0rn", "pawn", "pecker", "penis", "penisfucker", "phonesex", "phuck", "phuk", "phuked", "phuking", "phukked", "phukking", "phuks", "phuq", "pigfucker", "pimpis", "piss", "pissed", "pisser", "pissers", "pisses", "pissflaps", "pissin", "pissing", "pissoff", "poop", "porn", "porno", "pornography", "pornos", "prick", "pricks", "pron", "pube", "pusse", "pussi", "pussies", "pussy", "pussys", "rapist", "rectum", "retard", "rimjaw", "rimming", "s hit", "s_h_i_t", "sadist", "schlong", "screwing", "scroat", "scrote", "scrotum", "semen", "sex", "sh!+", "sh!t", "sh1t", "shag", "shagger", "shaggin", "shagging", "shemale", "shi+", "shit", "shit ass", "shitass", "shitdick", "shite", "shited", "shitey", "shitfuck", "shitfull", "shithead", "shiting", "shitings", "shits", "shitted", "shitter", "shitters", "shitting", "shittings", "shitty", "skank", "skullfuck", "slaptard", "slich", "slut", "sluts", "smegma", "smut", "snatch", "sob", "son-of-a-bitch", "spac", "spunk", "sucker", "t1tt1e5", "t1tties", "teets", "teez", "testical", "testicle", "tit", "titfuck", "tits", "titt", "tittie5", "tittiefucker", "titties", "tittyfuck", "tittywank", "titwank", "tosser", "turd", "tw4t", "twat", "twathead", "twatty", "twunt", "twunter", "v14gra", "v1gra", "vagina", "viagra", "vulva", "w00se", "wang", "wank", "wanker", "wanky", "whoar", "whore", "willies", "willy", "xrated", "xxx"],
4177 yn = {};
4178 ! function() {
4179 for (var e = 0; e < pn.length; e++) {
4180 var t = pn[e].toUpperCase(),
4181 n = t.charAt(0);
4182 t.indexOf(" ") !== -1 && (n = " "),
4183 void 0 === yn[n] && (yn[n] = []),
4184 yn[n].push(t)
4185 }
4186 }();
4187 var vn = ["chick", "duck", "cow", "owls", "elephant", "fox", "panda", "cat", "pig", "frog"],
4188 kn = [
4189 [1, 8, 13],
4190 [4, 10, 14],
4191 [],
4192 [2, 3, 9],
4193 [4, 10, 14],
4194 [7, 8],
4195 [],
4196 [1, 8, 13],
4197 [2, 3, 9, 5],
4198 [6, 12, 15]
4199 ],
4200 wn = !1,
4201 In = !1,
4202 bn = !1,
4203 xn = !1;
4204 document.addEventListener("keydown", function(e) {
4205 switch (e.keyCode) {
4206 case 38:
4207 wn || (wn = !0,
4208 De());
4209 break;
4210 case 40:
4211 In || (In = !0,
4212 De());
4213 break;
4214 case 37:
4215 bn || (bn = !0,
4216 De());
4217 break;
4218 case 39:
4219 xn || (xn = !0,
4220 De())
4221 }
4222 }),
4223 document.addEventListener("keyup", function(e) {
4224 switch (e.keyCode) {
4225 case 38:
4226 wn = !1,
4227 De();
4228 break;
4229 case 40:
4230 In = !1,
4231 De();
4232 break;
4233 case 37:
4234 bn = !1,
4235 De();
4236 break;
4237 case 39:
4238 xn = !1,
4239 De()
4240 }
4241 }),
4242 document.addEventListener("keyup", function(e) {
4243 //!! 28: local variables
4244 /**/var event = e;
4245 //! 28: don't handle 'f' key here, let mod deal with it
4246 /**/if ( 'f' == event.key ) return;
4247 //# s and f key
4248 St && (St && 83 == e.keyCode ? (Rt = !Rt,
4249 document.getElementById("fps") && (document.getElementById("fps").style.display = Rt ? "block" : "none")) : St && 70 == e.keyCode && (Xt = !Xt,
4250 document.getElementById("friends-detail").style.display = Xt ? "block" : "none"))
4251 }),
4252 document.addEventListener("contextmenu", function(e){
4253 if (disableRightClickEnabled) {
4254 e.preventDefault();
4255 }
4256 }, false);
4257 var qn = {
4258 clickPlay: L,
4259 clickPlayAgain: H,
4260 clickMainMenu: D,
4261 aipComplete: T,
4262 setQuality: i,
4263 createParty: Be,
4264 leaveParty: Ce,
4265 selectSkin: Se,
4266 nextSkins: Le,
4267 previousSkins: He
4268 };
4269 return qn
4270 //###################################################################################################################
4271}; // newSuperhex()
4272
4273
4274(function() {
4275 'use strict';
4276
4277 // reload the original fresh page
4278 var origDoc;
4279 var xhttpDoc = new XMLHttpRequest( { mozAnon: true } );
4280 xhttpDoc.addEventListener( "load", function( event ) { origDoc = xhttpDoc.responseText; reloadModdedPage( 1 ); });
4281 xhttpDoc.open( "GET", "http://superhex.io" );
4282 xhttpDoc.send();
4283
4284 // load game.min.js
4285 var gameMinJs;
4286 var xhttpJs = new XMLHttpRequest( { mozAnon: true } );
4287 xhttpJs.addEventListener( "load", function( event ) { gameMinJs = xhttpJs.responseText; reloadModdedPage( 2 ); });
4288 xhttpJs.open( "GET", "http://superhex.io/game.min.js" );
4289 xhttpJs.send();
4290
4291 // hash and verify the source page, update it and reload it completely
4292 var flags = 0;
4293 function reloadModdedPage( flag )
4294 {
4295 // wait until all actions have completed
4296 flags |= flag;
4297 if ( flags != 3 ) return;
4298
4299 // update the document to strip out CloudFlare script, contains unique token values that messes up the hash code
4300 origDoc = origDoc.replace( /<script\b[^>]*>[\s\S]*?var CloudFlare=[\s\S]*?<\/script>\s*/gm, '' );
4301 // strip out reference to this ad provider, creates cross-site script warnings due to the document.write
4302 origDoc = origDoc.replace( /<script\b[^>]*\s+src="\/\/static\.criteo\.net[\s\S]*?>[\s\S]*?<\/script>\s*/gm, '' );
4303 // remove references to raven.js so that mod generated exceptions don't get reported
4304 origDoc = origDoc.replace( /<script\b[^>]*>[\s]*?Raven.config[\s\S]*?<\/script>\s*/gm, '' );
4305 origDoc = origDoc.replace( /<script\b[^>]*\s+src="[\S]*?raven.min.js[\s\S]*?>[\s\S]*?<\/script>\s*/gm, '' );
4306 // check the versions of the web page and game code
4307 if ( 0x2b1c497a != hashCode( origDoc )) alert( 'Wrong version of the web page' ), console.log( `web page hash code: 0x${hashCode( origDoc ).toString(16)}` );
4308 if ( 0x6e7de45d != hashCode( gameMinJs )) alert( 'Wrong version of game.min.js' ), console.log( `game.min.js hash code: 0x${hashCode( gameMinJs ).toString(16)}` );
4309
4310 // update the document with the new game code
4311 var newDoc = origDoc.replace( '<script type="text/javascript" src="game.min.js"></script>',
4312 '<script type="text/javascript">//<![CDATA[\nvar superhex = window.newSuperhex();\n//]]></script>' );
4313
4314 // reload the updated document
4315 document.open( 'text/html' );
4316 document.write( newDoc );
4317 document.close();
4318 }
4319
4320 // hash function for strings, based on https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript-jquery, updated convert to unsigned int
4321 function hashCode( str ) { return str.split('').reduce(( prevHash, currVal ) => ((( prevHash << 5 ) - prevHash ) + currVal.charCodeAt( 0 )) >>> 0, 0 ); }
4322})();