|
| 1 | +/** |
| 2 | + * The main.js file is supposed to contain logic specific to the website, like those modifying the HTML elements, and the event listeners. |
| 3 | + * To reduce size of the main.js file, this file includes the logic of the game engine-specific logic, logic for the 'Caret' class, or any other simple logic. |
| 4 | + */ |
| 5 | + |
| 6 | +const TYPES = Object.freeze({ |
| 7 | + 'BUILDING': {colour: '#ff0000', type: 'BUILDING', name: 'Building'}, |
| 8 | + 'UNIT': {colour: '#ff0000', type: 'UNIT', name: 'Unit'}, |
| 9 | + 'UNIQUEUNIT': {colour: '#703b7a', type: 'UNIQUEUNIT', name: 'Unique Unit'}, |
| 10 | + 'TECHNOLOGY': {colour: '#2c5729', type: 'TECHNOLOGY', name: 'Technology'} |
| 11 | +}); |
| 12 | + |
| 13 | +const PREFIX = Object.freeze({ |
| 14 | + 'BUILDING': 'building_', |
| 15 | + 'UNIT': 'unit_', |
| 16 | + 'UNIQUEUNIT': 'unit_', |
| 17 | + 'TECHNOLOGY': 'tech_' |
| 18 | +}); |
| 19 | + |
| 20 | +const armorClasses = { |
| 21 | + 0: 'Wonders', |
| 22 | + 1: 'Infantry', |
| 23 | + 2: 'Heavy Warships', |
| 24 | + 3: 'Base Pierce', |
| 25 | + 4: 'Base Melee', |
| 26 | + 5: 'Elephants', |
| 27 | + 8: '<abbr title="except Camels">Mounted Units</abbr>', |
| 28 | + 11: 'All Buildings', |
| 29 | + 13: '<abbr title="except Castles and Kreposts">Stone Defense & Harbors</abbr>', |
| 30 | + 14: 'Predator Animals', |
| 31 | + 15: 'All Archers', |
| 32 | + 16: '<abbr title="except Fishing Ships">Ships</abbr>', |
| 33 | + 17: 'High Pierce Armor Siege Units', |
| 34 | + 18: 'Trees', |
| 35 | + 19: 'Unique Units', |
| 36 | + 20: 'Siege Units', |
| 37 | + 21: '<abbr title="All buildings except Wonders">Standard Buildings</abbr>', |
| 38 | + 22: 'Walls & Gates', |
| 39 | + 23: 'Gunpowder Units', |
| 40 | + 24: 'Aggressive Huntable Animals', |
| 41 | + 25: 'Monastery Units', |
| 42 | + 26: 'Castles & Kreposts', |
| 43 | + 27: 'Spearmen', |
| 44 | + 28: 'Mounted Archers', |
| 45 | + 29: 'Shock Infantry', |
| 46 | + 30: 'Camels', |
| 47 | + 31: '<abbr title="Armor-ignoring melee attack against units, but not against buildings">Unblockable Melee</abbr>', |
| 48 | + 32: 'Condottieri', |
| 49 | + 34: 'Fishing Ships', |
| 50 | + 35: 'Mamelukes', |
| 51 | + 36: 'Heroes & Kings', |
| 52 | + 37: 'Heavy Siege', |
| 53 | + 38: 'Skirmishers', |
| 54 | + 39: 'Cavalry Resistance', |
| 55 | + 40: 'Houses', |
| 56 | + 60: 'Long-Range Warships' |
| 57 | +}; |
| 58 | + |
| 59 | +const animation_duration = 50; |
| 60 | + |
| 61 | +class Caret { |
| 62 | + constructor(type, name, id, colour = null) { |
| 63 | + this.type = type; |
| 64 | + this.name = name; |
| 65 | + this.id = PREFIX[type.type] + formatId(id); |
| 66 | + this.width = 100; |
| 67 | + this.height = 100; |
| 68 | + this.x = 0; |
| 69 | + this.y = 0; |
| 70 | + this.colour = colour; |
| 71 | + } |
| 72 | + |
| 73 | + isBuilding() { |
| 74 | + return this.type === TYPES.BUILDING; |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +function imagePrefix(name) { |
| 79 | + return name.replace('_copy', '') |
| 80 | + .replace('building_', 'Buildings/') |
| 81 | + .replace('unit_', 'Units/') |
| 82 | + .replace('tech_', 'Techs/'); |
| 83 | +} |
| 84 | + |
| 85 | +function cost(cost_object) { |
| 86 | + let value = ''; |
| 87 | + if (cost_object.Food) { |
| 88 | + value += `<span class="cost food" title="${cost_object.Food} Food">${cost_object.Food}</span>`; |
| 89 | + } |
| 90 | + if (cost_object.Wood) { |
| 91 | + value += `<span class="cost wood" title="${cost_object.Wood} Wood">${cost_object.Wood}</span>`; |
| 92 | + } |
| 93 | + if (cost_object.Gold) { |
| 94 | + value += `<span class="cost gold" title="${cost_object.Gold} Gold">${cost_object.Gold}</span>`; |
| 95 | + } |
| 96 | + if (cost_object.Stone) { |
| 97 | + value += `<span class="cost stone" title="${cost_object.Stone} Stone">${cost_object.Stone}</span>`; |
| 98 | + } |
| 99 | + if (value === '') return 'free'; |
| 100 | + return value; |
| 101 | +} |
| 102 | + |
| 103 | +function formatId(string) { |
| 104 | + return string.toString().replace(/\s/g, '_').replace(/\//g, '_').toLowerCase(); |
| 105 | +} |
| 106 | + |
| 107 | +function checkIdUnique(tree) { |
| 108 | + let ids = new Set(); |
| 109 | + for (let lane of tree.lanes) { |
| 110 | + for (let r of Object.keys(lane.rows)) { |
| 111 | + for (let caret of lane.rows[r]) { |
| 112 | + if (ids.has(caret.id)) { |
| 113 | + console.error('ID ' + caret.id + ' is not unique!'); |
| 114 | + } |
| 115 | + ids.add(caret.id); |
| 116 | + } |
| 117 | + } |
| 118 | + } |
| 119 | +} |
| 120 | + |
| 121 | +function getColourForNodeType(nodeType) { |
| 122 | + switch (nodeType) { |
| 123 | + case 'BuildingTech': |
| 124 | + case 'BuildingNonTech': |
| 125 | + return '#b54e18'; |
| 126 | + case 'RegionalBuilding': |
| 127 | + return '#cc4422'; |
| 128 | + case 'UniqueBuilding': |
| 129 | + return '#d43652'; |
| 130 | + case 'Unit': |
| 131 | + case 'UnitUpgrade': |
| 132 | + return '#00739c'; |
| 133 | + case 'RegionalUnit': |
| 134 | + return '#515ae3'; |
| 135 | + case 'UniqueUnit': |
| 136 | + return '#703b7a'; |
| 137 | + case 'Technology': |
| 138 | + return '#397139'; |
| 139 | + default: |
| 140 | + return '#ff0000'; |
| 141 | + } |
| 142 | +} |
| 143 | + |
| 144 | +function chargeText(type) { |
| 145 | + switch (type) { |
| 146 | + case 1: return 'Charge Attack: '; |
| 147 | + case 2: return 'Charge Hit Points: '; |
| 148 | + case 3: return 'Charged Area Attack: '; |
| 149 | + case 4: return 'Projectile Dodging: '; |
| 150 | + case 5: return 'Melee Attack Blocking: '; |
| 151 | + case 6: return 'Charged Ranged Attack (type 1): '; |
| 152 | + case 7: return 'Charged Ranged Attack (type 2): '; |
| 153 | + default: return 'Unknown Charge: '; |
| 154 | + } |
| 155 | +} |
| 156 | + |
| 157 | +function getEntityType(type) { |
| 158 | + switch (type) { |
| 159 | + case 'TECHNOLOGY': |
| 160 | + return 'techs'; |
| 161 | + case 'UNIT': |
| 162 | + case 'UNIQUEUNIT': |
| 163 | + return 'units'; |
| 164 | + } |
| 165 | + return 'buildings'; |
| 166 | +} |
| 167 | + |
| 168 | +/** |
| 169 | + * @param {number} trait |
| 170 | + * @return {number[]} |
| 171 | + */ |
| 172 | +function splitTrait(trait) { |
| 173 | + const traits = []; |
| 174 | + for (let x of [1, 2, 4, 8, 16, 32, 64, 128]) { |
| 175 | + if ((trait & x) > 0) { |
| 176 | + traits.push(x); |
| 177 | + } |
| 178 | + } |
| 179 | + return traits; |
| 180 | +} |
| 181 | + |
| 182 | +/** |
| 183 | + * @param {number} traitId |
| 184 | + * @param {number} traitPiece |
| 185 | + * @return {string} |
| 186 | + */ |
| 187 | +function getTraitDefinition(traitId, traitPiece) { |
| 188 | + switch (traitId) { |
| 189 | + case 1: return 'Garrison Unit'; |
| 190 | + case 2: return 'Ship Unit'; |
| 191 | + case 4: return 'Builds: ' + data.strings[data.data['buildings'][traitPiece]['LanguageNameId']]; |
| 192 | + case 8: return 'Transforms into: ' + data.strings[(data.data['buildings'][traitPiece] || data.data['units'][traitPiece])['LanguageNameId']]; |
| 193 | + case 16: return '<abbr title="has auto-scout behaviour">Scout Unit</abbr>'; |
| 194 | + default: return 'Unknown Trait: ' + traitId; |
| 195 | + } |
| 196 | +} |
| 197 | + |
| 198 | +/** |
| 199 | + * @param {number} trait |
| 200 | + * @param {string} traitPiece |
| 201 | + * @return {*[]|boolean} |
| 202 | + */ |
| 203 | +function traitsIfDefined(trait, traitPiece) { |
| 204 | + let traitdescriptions = []; |
| 205 | + if (trait === undefined || trait === 0) { |
| 206 | + return false; |
| 207 | + } |
| 208 | + const traits = splitTrait(trait); |
| 209 | + for (let singleTrait of traits) { |
| 210 | + traitdescriptions.push(getTraitDefinition(singleTrait, Number(traitPiece))); |
| 211 | + } |
| 212 | + return traitdescriptions; |
| 213 | +} |
| 214 | + |
| 215 | +function getName(id, itemtype) { |
| 216 | + // ToDo handle unique stuff properly |
| 217 | + if (id.toString().startsWith('UNIQUE')) { |
| 218 | + return id; |
| 219 | + } |
| 220 | + try { |
| 221 | + const languageNameId = data['data'][itemtype][id]['LanguageNameId']; |
| 222 | + return data['strings'][languageNameId]; |
| 223 | + } catch { |
| 224 | + console.log(id, itemtype); |
| 225 | + } |
| 226 | +} |
| 227 | + |
| 228 | +function getColour(id, itemtype) { |
| 229 | + let nodeType = data['data']?.['node_types']?.[itemtype]?.[id]; |
| 230 | + if (!nodeType) { |
| 231 | + nodeType = itemtype === 'units' ? 'Unit' : 'BuildingTech'; |
| 232 | + } |
| 233 | + return getColourForNodeType(nodeType); |
| 234 | +} |
| 235 | + |
| 236 | +function building(id) { |
| 237 | + return new Caret(TYPES.BUILDING, getName(id, 'buildings'), id, getColour(id, 'buildings')); |
| 238 | +} |
| 239 | + |
| 240 | +function unit(id) { |
| 241 | + return new Caret(TYPES.UNIT, getName(id, 'units'), id, getColour(id, 'units')); |
| 242 | +} |
| 243 | + |
| 244 | +function tech(id) { |
| 245 | + return new Caret(TYPES.TECHNOLOGY, getName(id, 'techs'), id); |
| 246 | +} |
| 247 | + |
| 248 | +function u(unit) { |
| 249 | + return 'unit_' + formatId(unit); |
| 250 | +} |
| 251 | + |
| 252 | +function b(building) { |
| 253 | + return 'building_' + formatId(building); |
| 254 | +} |
| 255 | + |
| 256 | +function t(tech) { |
| 257 | + return 'tech_' + formatId(tech); |
| 258 | +} |
| 259 | + |
| 260 | +function formatName(originalname) { |
| 261 | + if (!originalname) return ""; |
| 262 | + let name = originalname.toString().replace(/<br>/g, '\n').replace(/\n+/g, '\n'); |
| 263 | + const items = name.split('\n'); |
| 264 | + for (let i = 0; i < items.length; i++) { |
| 265 | + const item = items[i]; |
| 266 | + if (items[i].length > 10) { |
| 267 | + let space = item.indexOf(' '); |
| 268 | + if (space !== -1) { |
| 269 | + items[i] = item.slice(0, space) + '\n' + item.slice(space + 1); |
| 270 | + let alternativeSpace = space + 1 + item.slice(space + 1).indexOf(' '); |
| 271 | + if (alternativeSpace !== -1) { |
| 272 | + if (Math.abs((item.length / 2) - alternativeSpace) < Math.abs((item.length / 2) - space)) { |
| 273 | + items[i] = item.slice(0, alternativeSpace) + '\n' + item.slice(alternativeSpace + 1); |
| 274 | + } |
| 275 | + } |
| 276 | + } else { |
| 277 | + let hyphen = item.indexOf('-'); |
| 278 | + if (hyphen !== -1) { |
| 279 | + items[i] = item.slice(0, hyphen) + '-\n' + item.slice(hyphen + 1); |
| 280 | + let alternativeHyphen = hyphen + 1 + item.slice(hyphen + 1).indexOf('-'); |
| 281 | + if (alternativeHyphen !== -1) { |
| 282 | + if (Math.abs((item.length / 2) - alternativeHyphen) < Math.abs((item.length / 2) - hyphen)) { |
| 283 | + items[i] = item.slice(0, alternativeHyphen) + '-\n' + item.slice(alternativeHyphen + 1); |
| 284 | + } |
| 285 | + } |
| 286 | + } |
| 287 | + } |
| 288 | + } |
| 289 | + } |
| 290 | + return items.join('\n'); |
| 291 | +} |
| 292 | + |
| 293 | +function getConnectionPoints(tree) { |
| 294 | + let points = new Map(); |
| 295 | + for (let lane of tree.lanes) { |
| 296 | + for (let r of Object.keys(lane.rows)) { |
| 297 | + for (let caret of lane.rows[r]) { |
| 298 | + points.set(caret.id, { |
| 299 | + x: caret.x + (caret.width / 2), |
| 300 | + y: caret.y + (caret.height / 2) |
| 301 | + }); |
| 302 | + } |
| 303 | + } |
| 304 | + } |
| 305 | + return points; |
| 306 | +} |
0 commit comments