From 7697ccf9c8f6598b68144dde6131196533cc009c Mon Sep 17 00:00:00 2001 From: Saurav Tripathi Date: Mon, 3 Nov 2025 08:08:01 +0530 Subject: [PATCH 1/2] refactored code - added engine.js file, which contains logic common to AoE2 and RoR, similar to main.js added more charge types and armor classes --- .gitignore | 1 + index.html | 1 + js/engine.js | 306 +++++++++++++++ js/main.js | 180 ++------- js/techtree.js | 429 +++++----------------- ror/data/data.json | 78 ++++ ror/data/locales/fr/strings.json | 2 +- ror/img/Backgrounds/bg_aoe2_hd_paper.jpg | Bin 9297 -> 0 bytes ror/index.html | 1 + ror/js/techtree.js | 230 +----------- scripts/README.md | 8 + scripts/generateBuildingTechUnitImages.py | 4 +- scripts/generateDataFiles.py | 30 +- 13 files changed, 547 insertions(+), 723 deletions(-) create mode 100644 js/engine.js delete mode 100644 ror/img/Backgrounds/bg_aoe2_hd_paper.jpg diff --git a/.gitignore b/.gitignore index f4b15ba4..92a54bb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea +.vscode bin .directory /scripts/venv diff --git a/index.html b/index.html index 8c02c9eb..83f5cfaf 100644 --- a/index.html +++ b/index.html @@ -29,6 +29,7 @@ + diff --git a/js/engine.js b/js/engine.js new file mode 100644 index 00000000..0d26cbee --- /dev/null +++ b/js/engine.js @@ -0,0 +1,306 @@ +/** + * The main.js file is supposed to contain logic specific to the website, like those modifying the HTML elements, and the event listeners. + * 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. + */ + +const TYPES = Object.freeze({ + 'BUILDING': {colour: '#ff0000', type: 'BUILDING', name: 'Building'}, + 'UNIT': {colour: '#ff0000', type: 'UNIT', name: 'Unit'}, + 'UNIQUEUNIT': {colour: '#703b7a', type: 'UNIQUEUNIT', name: 'Unique Unit'}, + 'TECHNOLOGY': {colour: '#2c5729', type: 'TECHNOLOGY', name: 'Technology'} +}); + +const PREFIX = Object.freeze({ + 'BUILDING': 'building_', + 'UNIT': 'unit_', + 'UNIQUEUNIT': 'unit_', + 'TECHNOLOGY': 'tech_' +}); + +const armorClasses = { + 0: 'Wonders', + 1: 'Infantry', + 2: 'Heavy Warships', + 3: 'Base Pierce', + 4: 'Base Melee', + 5: 'Elephants', + 8: 'Mounted Units', + 11: 'All Buildings', + 13: 'Stone Defense & Harbors', + 14: 'Predator Animals', + 15: 'All Archers', + 16: 'Ships', + 17: 'High Pierce Armor Siege Units', + 18: 'Trees', + 19: 'Unique Units', + 20: 'Siege Units', + 21: 'Standard Buildings', + 22: 'Walls & Gates', + 23: 'Gunpowder Units', + 24: 'Aggressive Huntable Animals', + 25: 'Monastery Units', + 26: 'Castles & Kreposts', + 27: 'Spearmen', + 28: 'Mounted Archers', + 29: 'Shock Infantry', + 30: 'Camels', + 31: 'Unblockable Melee', + 32: 'Condottieri', + 34: 'Fishing Ships', + 35: 'Mamelukes', + 36: 'Heroes & Kings', + 37: 'Heavy Siege', + 38: 'Skirmishers', + 39: 'Cavalry Resistance', + 40: 'Houses', + 60: 'Long-Range Warships' +}; + +const animation_duration = 50; + +class Caret { + constructor(type, name, id, colour = null) { + this.type = type; + this.name = name; + this.id = PREFIX[type.type] + formatId(id); + this.width = 100; + this.height = 100; + this.x = 0; + this.y = 0; + this.colour = colour; + } + + isBuilding() { + return this.type === TYPES.BUILDING; + } +} + +function imagePrefix(name) { + return name.replace('_copy', '') + .replace('building_', 'Buildings/') + .replace('unit_', 'Units/') + .replace('tech_', 'Techs/'); +} + +function cost(cost_object) { + let value = ''; + if (cost_object.Food) { + value += `${cost_object.Food}`; + } + if (cost_object.Wood) { + value += `${cost_object.Wood}`; + } + if (cost_object.Gold) { + value += `${cost_object.Gold}`; + } + if (cost_object.Stone) { + value += `${cost_object.Stone}`; + } + if (value === '') return 'free'; + return value; +} + +function formatId(string) { + return string.toString().replace(/\s/g, '_').replace(/\//g, '_').toLowerCase(); +} + +function checkIdUnique(tree) { + let ids = new Set(); + for (let lane of tree.lanes) { + for (let r of Object.keys(lane.rows)) { + for (let caret of lane.rows[r]) { + if (ids.has(caret.id)) { + console.error('ID ' + caret.id + ' is not unique!'); + } + ids.add(caret.id); + } + } + } +} + +function getColourForNodeType(nodeType) { + switch (nodeType) { + case 'BuildingTech': + case 'BuildingNonTech': + return '#b54e18'; + case 'RegionalBuilding': + return '#cc4422'; + case 'UniqueBuilding': + return '#d43652'; + case 'Unit': + case 'UnitUpgrade': + return '#00739c'; + case 'RegionalUnit': + return '#515ae3'; + case 'UniqueUnit': + return '#703b7a'; + case 'Technology': + return '#397139'; + default: + return '#ff0000'; + } +} + +function chargeText(type) { + switch (type) { + case 1: return 'Charge Attack: '; + case 2: return 'Charge Hit Points: '; + case 3: return 'Charged Area Attack: '; + case 4: return 'Projectile Dodging: '; + case 5: return 'Melee Attack Blocking: '; + case 6: return 'Charged Ranged Attack (type 1): '; + case 7: return 'Charged Ranged Attack (type 2): '; + default: return 'Unknown Charge: '; + } +} + +function getEntityType(type) { + switch (type) { + case 'TECHNOLOGY': + return 'techs'; + case 'UNIT': + case 'UNIQUEUNIT': + return 'units'; + } + return 'buildings'; +} + +/** + * @param {number} trait + * @return {number[]} + */ +function splitTrait(trait) { + const traits = []; + for (let x of [1, 2, 4, 8, 16, 32, 64, 128]) { + if ((trait & x) > 0) { + traits.push(x); + } + } + return traits; +} + +/** + * @param {number} traitId + * @param {number} traitPiece + * @return {string} + */ +function getTraitDefinition(traitId, traitPiece) { + switch (traitId) { + case 1: return 'Garrison Unit'; + case 2: return 'Ship Unit'; + case 4: return 'Builds: ' + data.strings[data.data['buildings'][traitPiece]['LanguageNameId']]; + case 8: return 'Transforms into: ' + data.strings[(data.data['buildings'][traitPiece] || data.data['units'][traitPiece])['LanguageNameId']]; + case 16: return 'Scout Unit'; + default: return 'Unknown Trait: ' + traitId; + } +} + +/** + * @param {number} trait + * @param {string} traitPiece + * @return {*[]|boolean} + */ +function traitsIfDefined(trait, traitPiece) { + let traitdescriptions = []; + if (trait === undefined || trait === 0) { + return false; + } + const traits = splitTrait(trait); + for (let singleTrait of traits) { + traitdescriptions.push(getTraitDefinition(singleTrait, Number(traitPiece))); + } + return traitdescriptions; +} + +function getName(id, itemtype) { + // ToDo handle unique stuff properly + if (id.toString().startsWith('UNIQUE')) { + return id; + } + try { + const languageNameId = data['data'][itemtype][id]['LanguageNameId']; + return data['strings'][languageNameId]; + } catch { + console.log(id, itemtype); + } +} + +function getColour(id, itemtype) { + let nodeType = data['data']?.['node_types']?.[itemtype]?.[id]; + if (!nodeType) { + nodeType = itemtype === 'units' ? 'Unit' : 'BuildingTech'; + } + return getColourForNodeType(nodeType); +} + +function building(id) { + return new Caret(TYPES.BUILDING, getName(id, 'buildings'), id, getColour(id, 'buildings')); +} + +function unit(id) { + return new Caret(TYPES.UNIT, getName(id, 'units'), id, getColour(id, 'units')); +} + +function tech(id) { + return new Caret(TYPES.TECHNOLOGY, getName(id, 'techs'), id); +} + +function u(unit) { + return 'unit_' + formatId(unit); +} + +function b(building) { + return 'building_' + formatId(building); +} + +function t(tech) { + return 'tech_' + formatId(tech); +} + +function formatName(originalname) { + if (!originalname) return ""; + let name = originalname.toString().replace(/
/g, '\n').replace(/\n+/g, '\n'); + const items = name.split('\n'); + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (items[i].length > 10) { + let space = item.indexOf(' '); + if (space !== -1) { + items[i] = item.slice(0, space) + '\n' + item.slice(space + 1); + let alternativeSpace = space + 1 + item.slice(space + 1).indexOf(' '); + if (alternativeSpace !== -1) { + if (Math.abs((item.length / 2) - alternativeSpace) < Math.abs((item.length / 2) - space)) { + items[i] = item.slice(0, alternativeSpace) + '\n' + item.slice(alternativeSpace + 1); + } + } + } else { + let hyphen = item.indexOf('-'); + if (hyphen !== -1) { + items[i] = item.slice(0, hyphen) + '-\n' + item.slice(hyphen + 1); + let alternativeHyphen = hyphen + 1 + item.slice(hyphen + 1).indexOf('-'); + if (alternativeHyphen !== -1) { + if (Math.abs((item.length / 2) - alternativeHyphen) < Math.abs((item.length / 2) - hyphen)) { + items[i] = item.slice(0, alternativeHyphen) + '-\n' + item.slice(alternativeHyphen + 1); + } + } + } + } + } + } + return items.join('\n'); +} + +function getConnectionPoints(tree) { + let points = new Map(); + for (let lane of tree.lanes) { + for (let r of Object.keys(lane.rows)) { + for (let caret of lane.rows[r]) { + points.set(caret.id, { + x: caret.x + (caret.width / 2), + y: caret.y + (caret.height / 2) + }); + } + } + } + return points; +} diff --git a/js/main.js b/js/main.js index 46f89eed..0d687826 100644 --- a/js/main.js +++ b/js/main.js @@ -51,17 +51,6 @@ function updatePageTitle() { document.title = `${aoe2}${suffix} ${techtree}`; } -function getShieldForEarlierRow(row) { - const age = row.split('_')[0]; - for (let i = 1; i < AGE_IMAGES.length; i++) { - const ageimage = AGE_IMAGES[i]; - if (ageimage.includes(age)) { - return AGE_IMAGES[i - 1]; - } - } - return AGE_IMAGES[0]; -} - function getAgeNumber(row) { const age = row.split('_')[0]; for (let i = 0; i < AGE_IMAGES.length; i++) { @@ -118,11 +107,10 @@ function displayData() { let icon_width = 112; let vertical_spacing = (row_height - icon_height) / 2 - 10; let margin_left = 20; - let image_urls = AGE_IMAGES; let age_names = getAgeNames(data); - for (let i = 0; i < image_urls.length; i++) { + for (let i = 0; i < AGE_IMAGES.length; i++) { let age_image_group = draw.group().click(hideHelp); - let age_image = age_image_group.image('img/Ages/' + image_urls[i]) + let age_image = age_image_group.image('img/Ages/' + AGE_IMAGES[i]) .size(icon_width, icon_height) .x(margin_left) .y(row_height * i + vertical_spacing); @@ -130,8 +118,7 @@ function displayData() { .text(age_names[i]) .font({size: 16, weight: 'bold'}) /* Text-anchor: middle does not work. */ .cx(icon_width / 2 + margin_left) - .y(age_image.attr('y') + age_image.attr('height') + 5) - ; + .y(age_image.attr('y') + age_image.attr('height') + 5); } const connectionGroup = draw.group().attr({id: 'connection_lines'}); @@ -183,9 +170,9 @@ function displayData() { .attr({id: caret.id + '_x'}) .addClass('cross') .move(caret.x + caret.width * 0.15, caret.y - caret.height * 0.04); - const earlier_age_image = item.image('img/Ages/' + getShieldForEarlierRow(r)) + const earlier_age_image = item.image('img/missing.png') .size(caret.width * 0.3, caret.height * 0.3) - .attr({id: caret.id + '_earlier_age_img_' + ageNumber, 'opacity': 0}) + .attr({id: caret.id + '_age_img_' + ageNumber, 'opacity': 0}) .addClass('earlier-age') .move(caret.x + caret.width * 0.85, caret.y - caret.width * 0.15); const overlaytrigger = item.rect(caret.width, caret.height) @@ -256,13 +243,6 @@ function onAdvancedStatsStateUpdate() { } } -function imagePrefix(name) { - return name.replace('_copy', '') - .replace('building_', 'Buildings/') - .replace('unit_', 'Units/') - .replace('tech_', 'Techs/'); -} - function loadCiv() { const selectedCiv = document.getElementById('civselect').value; civ(selectedCiv, tree); @@ -401,61 +381,6 @@ function positionHelptextToLeftOrRight(caret, helptext) { helptext.style.left = destX + 'px'; } -function chargeText(type) { - switch (type) { - case 1: - return 'Charge Attack: '; - case 2: - return 'Charge Hit Points: '; - case 3: - return 'Charged Area Attack: '; - case 4: - return 'Projectile Dodging: '; - default: - return 'Unknown Charge: '; - } -} - -function splitTrait(trait) { - const traits = []; - for (let x of [1, 2, 4, 8, 16, 32, 64, 128]) { - if ((trait & x) > 0) { - traits.push(x); - } - } - return traits; -} - -function traitsIfDefined(trait, traitPiece) { - let traitdescriptions = []; - if (trait === undefined || trait === 0) { - return false; - } - const traits = splitTrait(trait); - for (let singleTrait of traits) { - switch (singleTrait) { - case 1: - traitdescriptions.push('Garrison Unit'); - break; - case 2: - traitdescriptions.push('Ship Unit'); - break; - case 4: - traitdescriptions.push('Builds: ' + data.strings[data.data['buildings'][traitPiece]['LanguageNameId']]); - break; - case 8: - traitdescriptions.push('Transforms into: ' + data.strings[(data.data['buildings'][traitPiece]||data.data['units'][traitPiece])['LanguageNameId']]); - break; - case 16: - traitdescriptions.push('Scout Unit'); - break; - default: - traitdescriptions.push('Unknown Trait: ' + trait); - } - } - return traitdescriptions; -} - function getHelpText(name, id, type) { let entitytype = getEntityType(type); const items = id.split('_', 1); @@ -470,7 +395,7 @@ function getHelpText(name, id, type) { '

$1

' + '

$2

' + '

 

'); - } else if (type === 'UNIT' || type === 'UNIQUEUNIT' ) { + } else if (type === 'UNIT' || type === 'UNIQUEUNIT') { text = text.replace(/(.+?\(‹cost›\))(.+?)\s*(.+?)<\/i>(.*)/m, '

$1

' + '

$2

' + @@ -534,11 +459,14 @@ function getHelpText(name, id, type) { stats.push(ifDefined(meta.Speed, 'Speed: ')); stats.push(secondsIfDefined(meta.TrainTime, 'Build Time: ')); stats.push(secondsIfDefined(meta.ResearchTime, 'Research Time: ')); - stats.push(ifDefined(meta.FrameDelay, 'Frame Delay: ', ranged)); + //stats.push(ifDefined(meta.FrameDelay, 'Frame Delay: ', ranged)); stats.push(ifDefinedAndGreaterZero(meta.BlastWidth, 'Blast Radius: ')); stats.push(traitsIfDefined(meta.Trait, meta.TraitPiece)); stats.push(ifDefinedAndGreaterZero(meta.MaxCharge, chargeText(meta.ChargeType))); - stats.push(ifDefinedAndGreaterZero(meta.RechargeRate, 'Recharge Rate: ')); + if ([6, 7].includes(meta.ChargeType) && meta.ChargeEvent) { + stats.push('Charge Attack Range: ' + (meta.ChargeEvent + meta.Range)); + } + //stats.push(ifDefinedAndGreaterZero(meta.RechargeRate, 'Recharge Rate: ')); stats.push(secondsIfDefined(meta.RechargeDuration, 'Recharge Duration: ')); if (displayAttack) { stats.push(secondsIfDefined(meta.AttackDelaySeconds, 'Attack Delay: ', ranged)); @@ -568,17 +496,6 @@ function getAdvancedStats(name, id, type) { return text; } -function getEntityType(type) { - let entitytype = 'buildings'; - if (type === 'UNIT' || type === 'UNIQUEUNIT') { - entitytype = 'units'; - } - if (type === 'TECHNOLOGY') { - entitytype = 'techs'; - } - return entitytype; -} - /** * Create the Cross-Reference badges. This is done at load time in order to avoid re-making the * badges at runtime per-click on a new unit. @@ -629,6 +546,7 @@ function styleXRefBadges(name, id, type) { if (civs[civ].techs.map((item) => `tech_${item.id}`).includes(id)) { found = true; } else if (`tech_${civs[civ]?.unique?.castleAgeUniqueTech}` === id || `tech_${civs[civ]?.unique?.imperialAgeUniqueTech}` === id) { + // Age of Empires II unique technologies found = true; } } else if (type === 'BUILDING') { @@ -682,7 +600,7 @@ function arrayIfDefinedAndNonEmpty(attacks, prefix) { const strings = []; for (let attack of attacks) { const amount = attack['Amount']; - const clazz = unitClasses[attack['Class']]; + const clazz = armorClasses[attack['Class']]; strings.push(`${amount} (${clazz})`); } return prefix + '

' + strings.join(', ') + '

'; @@ -697,23 +615,6 @@ function repeatableIfDefined(value) { return ''; } -function cost(cost_object) { - let value = ''; - if ('Food' in cost_object) { - value += `${cost_object.Food}`; - } - if ('Wood' in cost_object) { - value += `${cost_object.Wood}`; - } - if ('Gold' in cost_object) { - value += `${cost_object.Gold}`; - } - if ('Stone' in cost_object) { - value += `${cost_object.Stone}`; - } - return value; -} - function create_building_index() { const buildingIndexRowLength = 6; @@ -824,13 +725,10 @@ function fillLocaleSelector(currentLocale) { } function getCompareLocale() { - switch (currentLocale){ - case 'tw': - return 'zh-TW' - case 'jp': - return 'ja'; - default: - return currentLocale; + switch (currentLocale) { + case 'tw': return 'zh-TW' + case 'jp': return 'ja'; + default: return currentLocale; } } @@ -880,42 +778,16 @@ function civ(name) { makeSVGObjectOpaque(SVG('#' + this.id().replace('_x', '_disabled_gray')), 0.2); }); - SVG.find('.earlier-age').each(function () { - let {id, type, ageId} = parseSVGObjectId2(this.id()); - if (id === undefined || type === undefined || ageId === undefined) { - return; - } - - if (type === 'unit') { - if (selectedCiv.units.some((item) => item.id === id && item.age === ageId)) { - makeSVGObjectOpaque(this); - return; - } - } else if (type === 'building') { - if (selectedCiv.buildings.some((item) => item.id === id && item.age === ageId)) { - makeSVGObjectOpaque(this); - return; - } - } else if (type === 'tech') { - if (selectedCiv.techs.some((item) => item.id === id && item.age === ageId)) { - makeSVGObjectOpaque(this); - return; - } - } - if (SVGObjectIsOpaque(this)) { - makeSVGObjectOpaque(this, 0); - } - }); - applySelectedCiv(selectedCiv); } -function SVGObjectIsOpaque(svgObj) { - return svgObj.attr('opacity') === 1 +function getShieldForEarlierAge(svgObj, actualAge) { + makeSVGObjectOpaque(svgObj); + SVG('#' + svgObj.node.id).load('img/Ages/' + AGE_IMAGES[actualAge-1]); } -function SVGObjectIsTransparent(svgObj) { - return svgObj.attr('opacity') === 0 +function SVGObjectIsOpaque(svgObj) { + return svgObj.attr('opacity') === 1; } function makeSVGObjectOpaque(svgObj, opacity = 1) { @@ -932,11 +804,11 @@ function parseSVGObjectId(svgObjId) { let id = parseInt(found[2]); let type = found[1]; - return {id, type} + return {id, type}; } function parseSVGObjectId2(svgObjId) { - const id_regex = /(.+)_([\d]+)_earlier_age_img_([\d]+)/; + const id_regex = /^(unit|tech|building)_([\w]+)_age_img_(\d+)$/; const found = svgObjId.match(id_regex); if (!found) { @@ -946,7 +818,7 @@ function parseSVGObjectId2(svgObjId) { let type = found[1]; let ageId = parseInt(found[3]); - return {id, type, ageId} + return {id, type, ageId}; } function techtreeDoesNotHaveScrollbar() { @@ -1058,7 +930,7 @@ function main() { }); } -if('loading' === document.readyState) { +if ('loading' === document.readyState) { // Loading hasn't finished yet. document.addEventListener('DOMContentLoaded', main) } else { diff --git a/js/techtree.js b/js/techtree.js index a7377e44..268b8a99 100644 --- a/js/techtree.js +++ b/js/techtree.js @@ -1,17 +1,3 @@ -const TYPES = Object.freeze({ - 'BUILDING': {colour: '#ff0000', type: 'BUILDING', name: 'Building'}, - 'UNIT': {colour: '#ff0000', type: 'UNIT', name: 'Unit'}, - 'UNIQUEUNIT': {colour: '#703b7a', type: 'UNIQUEUNIT', name: 'Unique Unit'}, - 'TECHNOLOGY': {colour: '#2c5729', type: 'TECHNOLOGY', name: 'Technology'} -}); - -const PREFIX = Object.freeze({ - 'BUILDING': 'building_', - 'UNIT': 'unit_', - 'UNIQUEUNIT': 'unit_', - 'TECHNOLOGY': 'tech_' -}); - const AGE_IMAGES = ['dark_age_de.png', 'feudal_age_de.png', 'castle_age_de.png', 'imperial_age_de.png']; const getAgeNames = (data)=>{ @@ -23,52 +9,6 @@ const getAgeNames = (data)=>{ ]; } -const unitClasses = { - 0: 'Wonders', - 1: 'Infantry', - 2: 'Heavy Warships', - 3: 'Base Pierce', - 4: 'Base Melee', - 5: 'Elephants', - 6: 'Unused', - 7: 'Unused', - 8: 'Mounted Units', - 9: 'Unused', - 10: 'Unused', - 11: 'All Buildings', - 12: 'Unused', - 13: 'Stone Defense & Harbors', - 14: 'Wolves etc.', - 15: 'All Archers', - 16: 'Ships', - 17: 'High Pierce Armor Siege Units', - 18: 'Trees', - 19: 'Unique Units', - 20: 'Siege Units', - 21: 'Standard Buildings', - 22: 'Walls & Gates', - 23: 'Gunpowder Units', - 24: 'Boars etc.', - 25: 'Monks', - 26: 'Castles & Kreposts', - 27: 'Spear Units', - 28: 'Mounted Archers', - 29: 'Shock Infantry', - 30: 'Camels', - 31: 'Obsolete', - 32: 'Condottieri', - 33: 'Gunpowder units secondary projectile attack', - 34: 'Fishing Ships', - 35: 'Mamelukes', - 36: 'Heroes & Kings', - 37: 'Heavy Siege', - 38: 'Skirmishers', - 39: 'Cavalry Resistance', - 40: 'Houses' -}; - -const animation_duration = 50; - const UNIQUE_UNIT = 'UNIQUE UNIT'; const ELITE_UNIQUE_UNIT = 'ELITE UNIQUE UNIT'; const UNIQUE_TECH_1 = 'UNIQUE TECH 1'; @@ -305,38 +245,23 @@ const LIU_BEI = 1966; const JIAN_SWORDSMAN = 1974; const SUN_JIAN = 1978; -const YEOMEN = 3; -const EL_DORADO = 4; -const FUROR_CELTICA = 5; -const DRILL = 6; -const MAHOUTS = 7; const TOWN_WATCH = 8; -const ZEALOTRY = 9; -const ARTILLERY = 10; -const CRENELLATIONS = 11; const CROP_ROTATION = 12; const HEAVY_PLOW = 13; const HORSE_COLLAR = 14; const GUILDS = 15; -const ANARCHY = 16; const BANKING = 17; -const ATHEISM = 21; const LOOM = 22; const COINAGE = 23; -const GARLAND_WARS = 24; const HUSBANDRY = 39; const FAITH = 45; const DEVOTION = 46; const CHEMISTRY = 47; const CARAVAN = 48; -const BERSERKERGANG = 49; const MASONRY = 50; const ARCHITECTURE = 51; -const ROCKETRY = 52; const TREADMILL_CRANE = 54; const GOLD_MINING = 55; -const KATAPARUTO = 59; -const LOGISTICA = 61; const KEEP_TECH = 63; const BOMBARD_TOWER_TECH = 64; const GILLNETS = 65; @@ -349,7 +274,6 @@ const PLATE_MAIL_ARMOR = 77; const PLATE_BARDING_ARMOR = 80; const SCALE_BARDING_ARMOR = 81; const CHAIN_BARDING_ARMOR = 82; -const BEARDED_AXE = 83; const BALLISTICS = 93; const FEUDAL_AGE = 101; const CASTLE_AGE = 102; @@ -393,68 +317,9 @@ const PARTHIAN_TACTICS = 436; const THUMB_RING = 437; const THEOCRACY = 438; const HERESY = 439; -const SUPREMACY = 440; const HERBAL_MEDICINE = 441; -const SHINKICHON = 445; -const PERFUSION = 457; -const ATLATL = 460; -const WARWOLF = 461; -const GREAT_WALL = 462; -const CHIEFTAINS = 463; -const GREEK_FIRE = 464; -const STRONGHOLD = 482; -const MARAUDERS = 483; -const YASAMA = 484; -const OBSIDIAN_ARROWS = 485; -const PANOKSEON = 486; -const NOMADS = 487; -const KAMANDARAN = 488; -const IRONCLAD = 489; -const MADRASAH = 490; -const SIPAHI = 491; -const INQUISITION = 492; -const CHIVALRY = 493; -const PAVISE = 494; -const SILK_ROAD = 499; -const SULTANS = 506; -const SHATAGNI = 507; -const ORTHODOXY = 512; -const DRUZHINA = 513; -const CORVINIAN_ARMY = 514; -const RECURVE_BOW = 515; -const ANDEAN_SLING = 516; -const FABRIC_SHIELDS = 517; -const CARRACK = 572; -const ARQUEBUS = 573; -const ROYAL_HEIRS = 574; -const TORSION_ENGINES = 575; -const TIGUI = 576; -const FARIMBA = 577; -const KASBAH = 578; -const MAGHRABI_CAMELS = 579; const ARSON = 602; const ARROWSLITS = 608; -const TUSK_SWORDS = 622; -const DOUBLE_CROSSBOW = 623; -const THALASSOCRACY = 624; -const FORCED_LEVY = 625; -const HOWDAH = 626; -const MANIPUR_CAVALRY = 627; -const CHATRAS = 628; -const PAPER_MONEY = 629; -const STIRRUPS = 685; -const BAGAINS = 686; -const SILK_ARMOR = 687; -const TIMURID_SIEGECRAFT = 688; -const STEPPE_HUSBANDRY = 689; -const CUMAN_MERCENARIES = 690; -const HILL_FORTS = 691; -const TOWER_SHIELDS = 692; -const SUPPLIES = 716; -const BURGUNDIAN_VINEYARDS = 754; -const FLEMISH_REVOLUTION = 755; -const FIRST_CRUSADE = 756; -const SCUTAGE = 757; const GAMBESONS = 875; const DOMESTICATION = 1014; const PASTORALISM = 1013; @@ -610,57 +475,49 @@ class Lane { } } -class Caret { - constructor(type, name, id, colour = null) { - this.type = type; - this.name = name; - this.id = PREFIX[type.type] + formatId(id); - this.width = 100; - this.height = 100; - this.x = 0; - this.y = 0; - this.colour = colour; - } - - isBuilding() { - return this.type === TYPES.BUILDING; - } -} - -function formatId(string) { - return string.toString().replace(/\s/g, '_').replace(/\//g, '_').toLowerCase(); -} - -function checkIdUnique(tree) { - let ids = new Set(); - for (let lane of tree.lanes) { - for (let r of Object.keys(lane.rows)) { - for (let caret of lane.rows[r]) { - if (ids.has(caret.id)) { - console.error('ID ' + caret.id + ' is not unique!'); - } - ids.add(caret.id); - } - } - } -} - function enable(buildings, units, techs) { for (let item of buildings) { - SVG('#building_' + formatId(item.id) + '_x').attr({'opacity': 0}); - SVG('#building_' + formatId(item.id) + '_disabled_gray').attr({'opacity': 0}); + SVG('#' + b(item.id) + '_x').attr({'opacity': 0}); + SVG('#' + b(item.id) + '_disabled_gray').attr({'opacity': 0}); } for (let item of units) { - SVG('#unit_' + formatId(item.id) + '_x').attr({'opacity': 0}); - SVG('#unit_' + formatId(item.id) + '_disabled_gray').attr({'opacity': 0}); + SVG('#' + u(item.id) + '_x').attr({'opacity': 0}); + SVG('#' + u(item.id) + '_disabled_gray').attr({'opacity': 0}); } for (let item of techs) { - SVG('#tech_' + formatId(item.id) + '_x').attr({'opacity': 0}); - SVG('#tech_' + formatId(item.id) + '_disabled_gray').attr({'opacity': 0}); + SVG('#' + t(item.id) + '_x').attr({'opacity': 0}); + SVG('#' + t(item.id) + '_disabled_gray').attr({'opacity': 0}); } } +function applyEarlierAges(selectedCiv) { + SVG.find('.earlier-age').each(function () { + let {id, type, ageId} = parseSVGObjectId2(this.id()); + if (id === undefined || type === undefined || ageId === undefined) { + console.error("Could not process: ", this.id()); + return; + } + + let earlyItem; + switch (type) { + case 'unit': earlyItem = selectedCiv.units.find((item) => item.id === id && item.age <= ageId); + break; + case 'building': earlyItem = selectedCiv.buildings.find((item) => item.id === id && item.age <= ageId); + break; + case 'tech': earlyItem = selectedCiv.techs.find((item) => item.id === id && item.age <= ageId); + } + if (earlyItem) { + getShieldForEarlierAge(this, earlyItem.age); + return; + } + if (SVGObjectIsOpaque(this)) { + makeSVGObjectOpaque(this, 0); + } + }); +} + function applySelectedCiv(selectedCiv) { + applyEarlierAges(selectedCiv); enable(selectedCiv.buildings, [...selectedCiv.units, {id:UNIQUE_UNIT, age: 3}, {id: ELITE_UNIQUE_UNIT, age: 4}], [...selectedCiv.techs, {id: UNIQUE_TECH_1, age: 3}, {id: UNIQUE_TECH_2, age: 4}]); @@ -670,109 +527,21 @@ function applySelectedCiv(selectedCiv) { selectedCiv.unique.imperialAgeUniqueTech], selectedCiv.monkSuffix); } -function formatName(originalname) { - let name = originalname.toString().replace(/
/g, '\n').replace(/\n+/g, '\n'); - const items = name.split('\n'); - for (let i = 0; i < items.length; i++) { - const item = items[i]; - if (items[i].length > 10) { - let space = item.indexOf(' '); - if (space !== -1) { - items[i] = item.slice(0, space) + '\n' + item.slice(space + 1); - let alternativeSpace = space + 1 + item.slice(space + 1).indexOf(' '); - if (alternativeSpace !== -1) { - if (Math.abs((item.length / 2) - alternativeSpace) < Math.abs((item.length / 2) - space)) { - items[i] = item.slice(0, alternativeSpace) + '\n' + item.slice(alternativeSpace + 1); - } - } - } else { - let hyphen = item.indexOf('-'); - if (hyphen !== -1) { - items[i] = item.slice(0, hyphen) + '-\n' + item.slice(hyphen + 1); - let alternativeHyphen = hyphen + 1 + item.slice(hyphen + 1).indexOf('-'); - if (alternativeHyphen !== -1) { - if (Math.abs((item.length / 2) - alternativeHyphen) < Math.abs((item.length / 2) - hyphen)) { - items[i] = item.slice(0, alternativeHyphen) + '-\n' + item.slice(alternativeHyphen + 1); - } - } - } - } - } - } - return items.join('\n'); -} - function unique(ids, monk_suffix) { if (monk_suffix === undefined) { monk_suffix = MONK_SUFFIX_GENERIC; } - SVG('#unit_' + formatId(UNIQUE_UNIT) + '_text').text(formatName(data.strings[data.data.units[ids[0]].LanguageNameId])); - SVG('#unit_' + formatId(UNIQUE_UNIT) + '_overlay').data({'name': data.strings[data.data.units[ids[0]].LanguageNameId], 'id':'unit_'+ids[0]}); - SVG('#unit_' + formatId(ELITE_UNIQUE_UNIT) + '_text').text(formatName(data.strings[data.data.units[ids[1]].LanguageNameId])); - SVG('#unit_' + formatId(ELITE_UNIQUE_UNIT) + '_overlay').data({'name': data.strings[data.data.units[ids[1]].LanguageNameId], 'id':'unit_'+ids[1]}); - SVG('#tech_' + formatId(UNIQUE_TECH_1) + '_text').text(formatName(data.strings[data.data.techs[ids[2]].LanguageNameId])); - SVG('#tech_' + formatId(UNIQUE_TECH_1) + '_overlay').data({'name': data.strings[data.data.techs[ids[2]].LanguageNameId], 'id':'tech_'+ids[2]}); - SVG('#tech_' + formatId(UNIQUE_TECH_2) + '_text').text(formatName(data.strings[data.data.techs[ids[3]].LanguageNameId])); - SVG('#tech_' + formatId(UNIQUE_TECH_2) + '_overlay').data({'name': data.strings[data.data.techs[ids[3]].LanguageNameId], 'id':'tech_'+ids[3]}); - SVG('#unit_' + formatId(UNIQUE_UNIT) + '_img').load('img/Units/' + formatId(ids[0]) + '.png'); - SVG('#unit_' + formatId(ELITE_UNIQUE_UNIT) + '_img').load('img/Units/' + formatId(ids[1]) + '.png'); - SVG('#unit_' + formatId(MONK) + '_img').load('img/Units/' + '125' + monk_suffix + '.png'); -} - -function getName(id, itemtype) { - //ToDo handle unique stuff properly - if(id.toString().startsWith('UNIQUE')){ - return id; - } - const languageNameId = data['data'][itemtype][id]['LanguageNameId']; - return data['strings'][languageNameId]; -} - -function getColour(id, itemtype) { - const nodeType = data['data']['node_types'][itemtype][id]; - if (!nodeType) { - return null; - } - return getColourForNodeType(nodeType); -} - -function getColourForNodeType(nodeType) { - switch (nodeType) { - case 'BuildingTech': - case 'BuildingNonTech': - return '#b54e18'; - case 'RegionalBuilding': - return '#cc4422'; - case 'UniqueBuilding': - return '#d43652'; - case 'Unit': - case 'UnitUpgrade': - return '#00739c'; - case 'RegionalUnit': - return '#515ae3'; - case 'UniqueUnit': - return '#703b7a'; - case 'Technology': - return '#397139'; - default: - return '#ff0000'; - } -} - -function building(id) { - return new Caret(TYPES.BUILDING, getName(id, 'buildings'), id, getColour(id, 'buildings')); -} - -function unit(id) { - return new Caret(TYPES.UNIT, getName(id, 'units'), id, getColour(id, 'units')); -} - -function uniqueunit(id) { - return new Caret(TYPES.UNIQUEUNIT, getName(id, 'units'), id, getColour(id, 'units')); -} - -function tech(id) { - return new Caret(TYPES.TECHNOLOGY, getName(id, 'techs'), id); + SVG('#' + u(UNIQUE_UNIT) + '_text').text(formatName(data.strings[data.data.units[ids[0]].LanguageNameId])); + SVG('#' + u(UNIQUE_UNIT) + '_overlay').data({'name': data.strings[data.data.units[ids[0]].LanguageNameId], 'id':'unit_'+ids[0]}); + SVG('#' + u(ELITE_UNIQUE_UNIT) + '_text').text(formatName(data.strings[data.data.units[ids[1]].LanguageNameId])); + SVG('#' + u(ELITE_UNIQUE_UNIT) + '_overlay').data({'name': data.strings[data.data.units[ids[1]].LanguageNameId], 'id':'unit_'+ids[1]}); + SVG('#' + t(UNIQUE_TECH_1) + '_text').text(formatName(data.strings[data.data.techs[ids[2]].LanguageNameId])); + SVG('#' + t(UNIQUE_TECH_1) + '_overlay').data({'name': data.strings[data.data.techs[ids[2]].LanguageNameId], 'id':'tech_'+ids[2]}); + SVG('#' + t(UNIQUE_TECH_2) + '_text').text(formatName(data.strings[data.data.techs[ids[3]].LanguageNameId])); + SVG('#' + t(UNIQUE_TECH_2) + '_overlay').data({'name': data.strings[data.data.techs[ids[3]].LanguageNameId], 'id':'tech_'+ids[3]}); + SVG('#' + u(UNIQUE_UNIT) + '_img').load('img/Units/' + formatId(ids[0]) + '.png'); + SVG('#' + u(ELITE_UNIQUE_UNIT) + '_img').load('img/Units/' + formatId(ids[1]) + '.png'); + SVG('#' + u(MONK) + '_img').load('img/Units/' + '125' + monk_suffix + '.png'); } function getDefaultTree() { @@ -785,19 +554,19 @@ function getDefaultTree() { archerylane.rows.feudal_2.push(unit(SKIRMISHER)); archerylane.rows.castle_1.push(unit(CROSSBOWMAN)); archerylane.rows.castle_1.push(unit(ELITE_SKIRMISHER)); - archerylane.rows.castle_1.push(uniqueunit(SLINGER)); + archerylane.rows.castle_1.push(unit(SLINGER)); archerylane.rows.castle_1.push(unit(CAVALRY_ARCHER)); archerylane.rows.castle_1.push(unit(ELEPHANT_ARCHER)); - archerylane.rows.castle_1.push(uniqueunit(GENITOUR)); - archerylane.rows.castle_1.push(uniqueunit(GRENADIER)); - archerylane.rows.castle_1.push(uniqueunit(XIANBEI_RAIDER)); + archerylane.rows.castle_1.push(unit(GENITOUR)); + archerylane.rows.castle_1.push(unit(GRENADIER)); + archerylane.rows.castle_1.push(unit(XIANBEI_RAIDER)); archerylane.rows.castle_1.push(tech(THUMB_RING)); archerylane.rows.imperial_1.push(unit(ARBALESTER)); - archerylane.rows.imperial_1.push(uniqueunit(IMPERIAL_SKIRMISHER)); + archerylane.rows.imperial_1.push(unit(IMPERIAL_SKIRMISHER)); archerylane.rows.imperial_1.push(unit(HAND_CANNONEER)); archerylane.rows.imperial_1.push(unit(HEAVY_CAV_ARCHER)); archerylane.rows.imperial_1.push(unit(ELITE_ELEPHANT_ARCHER)); - archerylane.rows.imperial_1.push(uniqueunit(ELITE_GENITOUR)); + archerylane.rows.imperial_1.push(unit(ELITE_GENITOUR)); archerylane.rows.imperial_1.push(tech(PARTHIAN_TACTICS)); tree.lanes.push(archerylane); @@ -809,21 +578,21 @@ function getDefaultTree() { barrackslane.rows.feudal_1.push(tech(ARSON)); barrackslane.rows.feudal_1.push(unit(SPEARMAN)); barrackslane.rows.feudal_1.push(unit(EAGLE_SCOUT)); - barrackslane.rows.feudal_1.push(uniqueunit(FLEMISHPIKEMAN)); + barrackslane.rows.feudal_1.push(unit(FLEMISHPIKEMAN)); barrackslane.rows.castle_1.push(unit(LONG_SWORDSMAN)); barrackslane.rows.castle_1.push(tech(GAMBESONS)); barrackslane.rows.castle_1.push(unit(PIKEMAN)); barrackslane.rows.castle_1.push(unit(EAGLE_WARRIOR)); barrackslane.rows.castle_1.push(unit(FIRE_LANCER)); - barrackslane.rows.castle_1.push(uniqueunit(JIAN_SWORDSMAN)); + barrackslane.rows.castle_1.push(unit(JIAN_SWORDSMAN)); barrackslane.rows.castle_1.push(tech(SQUIRES)); barrackslane.rows.imperial_1.push(unit(TWO_HANDED_SWORDSMAN)); - barrackslane.rows.imperial_1.push(uniqueunit(LEGIONARY)); + barrackslane.rows.imperial_1.push(unit(LEGIONARY)); barrackslane.rows.imperial_2.push(unit(CHAMPION)); barrackslane.rows.imperial_1.push(unit(HALBERDIER)); barrackslane.rows.imperial_1.push(unit(ELITE_EAGLE_WARRIOR)); barrackslane.rows.imperial_1.push(unit(ELITE_FIRE_LANCER)); - barrackslane.rows.imperial_1.push(uniqueunit(CONDOTTIERO)); + barrackslane.rows.imperial_1.push(unit(CONDOTTIERO)); tree.lanes.push(barrackslane); @@ -831,26 +600,26 @@ function getDefaultTree() { stablelane.rows.feudal_1.push(building(STABLE)); stablelane.rows.feudal_2.push(unit(SCOUT_CAVALRY)); stablelane.rows.feudal_2.push(tech(BLOODLINES)); - stablelane.rows.feudal_2.push(uniqueunit(CAMEL_SCOUT)); + stablelane.rows.feudal_2.push(unit(CAMEL_SCOUT)); stablelane.rows.castle_1.push(unit(LIGHT_CAVALRY)); - stablelane.rows.castle_1.push(uniqueunit(XOLOTL_WARRIOR)); + stablelane.rows.castle_1.push(unit(XOLOTL_WARRIOR)); stablelane.rows.castle_1.push(unit(KNIGHT)); stablelane.rows.castle_1.push(unit(CAMEL_RIDER)); stablelane.rows.castle_1.push(unit(STEPPE_LANCER)); stablelane.rows.castle_1.push(unit(BATTLE_ELEPHANT)); stablelane.rows.castle_1.push(unit(HEI_GUANG_CAVALRY)); - stablelane.rows.castle_1.push(uniqueunit(SHRIVAMSHA_RIDER)); + stablelane.rows.castle_1.push(unit(SHRIVAMSHA_RIDER)); stablelane.rows.castle_1.push(tech(HUSBANDRY)); stablelane.rows.imperial_1.push(unit(HUSSAR)); - stablelane.rows.imperial_1.push(uniqueunit(WINGED_HUSSAR)); + stablelane.rows.imperial_1.push(unit(WINGED_HUSSAR)); stablelane.rows.imperial_1.push(unit(CAVALIER)); stablelane.rows.imperial_1.push(unit(HEAVY_CAMEL_RIDER)); stablelane.rows.imperial_1.push(unit(ELITE_STEPPE_LANCER)); stablelane.rows.imperial_1.push(unit(ELITE_BATTLE_ELEPHANT)); stablelane.rows.imperial_1.push(unit(HEAVY_HEI_GUANG_CAVALRY)); - stablelane.rows.imperial_1.push(uniqueunit(ELITE_SHRIVAMSHA_RIDER)); - stablelane.rows.imperial_2.push(uniqueunit(IMPERIAL_CAMEL_RIDER)); - stablelane.rows.imperial_2.push(uniqueunit(SAVAR)); + stablelane.rows.imperial_1.push(unit(ELITE_SHRIVAMSHA_RIDER)); + stablelane.rows.imperial_2.push(unit(IMPERIAL_CAMEL_RIDER)); + stablelane.rows.imperial_2.push(unit(SAVAR)); stablelane.rows.imperial_2.push(unit(PALADIN)); tree.lanes.push(stablelane); @@ -863,7 +632,7 @@ function getDefaultTree() { siegeworkshoplane.rows.castle_2.push(unit(ROCKET_CART)); siegeworkshoplane.rows.castle_2.push(unit(SCORPION)); siegeworkshoplane.rows.castle_2.push(unit(SIEGE_TOWER)); - siegeworkshoplane.rows.castle_2.push(uniqueunit(WAR_CHARIOT)); + siegeworkshoplane.rows.castle_2.push(unit(WAR_CHARIOT)); siegeworkshoplane.rows.imperial_1.push(unit(CAPPED_RAM)); siegeworkshoplane.rows.imperial_1.push(unit(SIEGE_ELEPHANT)); siegeworkshoplane.rows.imperial_1.push(unit(ONAGER)); @@ -871,11 +640,11 @@ function getDefaultTree() { siegeworkshoplane.rows.imperial_1.push(unit(HEAVY_SCORPION)); siegeworkshoplane.rows.imperial_1.push(unit(BOMBARD_CANNON)); siegeworkshoplane.rows.imperial_2.push(unit(SIEGE_RAM)); - siegeworkshoplane.rows.imperial_2.push(uniqueunit(FLAMING_CAMEL)); + siegeworkshoplane.rows.imperial_2.push(unit(FLAMING_CAMEL)); siegeworkshoplane.rows.imperial_2.push(unit(SIEGE_ONAGER)); - siegeworkshoplane.rows.imperial_2.push(uniqueunit(MOUNTED_TREBUCHET)); - siegeworkshoplane.rows.imperial_2.push(uniqueunit(TRACTION_TREBUCHET)); - siegeworkshoplane.rows.imperial_2.push(uniqueunit(HOUFNICE)); + siegeworkshoplane.rows.imperial_2.push(unit(MOUNTED_TREBUCHET)); + siegeworkshoplane.rows.imperial_2.push(unit(TRACTION_TREBUCHET)); + siegeworkshoplane.rows.imperial_2.push(unit(HOUFNICE)); tree.lanes.push(siegeworkshoplane); @@ -910,22 +679,22 @@ function getDefaultTree() { docklane.rows.castle_1.push(tech(GILLNETS)); docklane.rows.castle_1.push(unit(DEMOLITION_SHIP)); docklane.rows.castle_1.push(unit(WAR_GALLEY)); - docklane.rows.castle_1.push(uniqueunit(TURTLE_SHIP)); - docklane.rows.castle_1.push(uniqueunit(LONGBOAT)); - docklane.rows.castle_1.push(uniqueunit(CARAVEL)); + docklane.rows.castle_1.push(unit(TURTLE_SHIP)); + docklane.rows.castle_1.push(unit(LONGBOAT)); + docklane.rows.castle_1.push(unit(CARAVEL)); docklane.rows.castle_1.push(tech(CAREENING)); docklane.rows.imperial_1.push(unit(FAST_FIRE_SHIP)); - docklane.rows.imperial_1.push(uniqueunit(DRAGON_FIRE_SHIP)); + docklane.rows.imperial_1.push(unit(DRAGON_FIRE_SHIP)); docklane.rows.imperial_1.push(unit(HEAVY_DEMO_SHIP)); docklane.rows.imperial_1.push(unit(GALLEON)); docklane.rows.imperial_1.push(unit(CANNON_GALLEON)); - docklane.rows.imperial_1.push(uniqueunit(ELITE_TURTLE_SHIP)); - docklane.rows.imperial_1.push(uniqueunit(ELITE_LONGBOAT)); - docklane.rows.imperial_1.push(uniqueunit(ELITE_CARAVEL)); + docklane.rows.imperial_1.push(unit(ELITE_TURTLE_SHIP)); + docklane.rows.imperial_1.push(unit(ELITE_LONGBOAT)); + docklane.rows.imperial_1.push(unit(ELITE_CARAVEL)); docklane.rows.imperial_1.push(tech(DRY_DOCK)); docklane.rows.imperial_2.push(unit(DROMON)); - docklane.rows.imperial_2.push(uniqueunit(LOU_CHUAN)); - docklane.rows.imperial_2.push(uniqueunit(THIRISADAI)); + docklane.rows.imperial_2.push(unit(LOU_CHUAN)); + docklane.rows.imperial_2.push(unit(THIRISADAI)); docklane.rows.imperial_2.push(tech(SHIPWRIGHT)); docklane.rows.imperial_2.push(unit(ELITE_CANNON_GALLEON)); tree.lanes.push(docklane); @@ -993,17 +762,17 @@ function getDefaultTree() { let krepostlane = new Lane(); krepostlane.rows.castle_1.push(building(KREPOST)); - krepostlane.rows.castle_2.push(uniqueunit(KONNIK_2)); - krepostlane.rows.imperial_1.push(uniqueunit(ELITE_KONNIK_2)); + krepostlane.rows.castle_2.push(unit(KONNIK_2)); + krepostlane.rows.imperial_1.push(unit(ELITE_KONNIK_2)); tree.lanes.push(krepostlane); let donjonlane = new Lane(); donjonlane.rows.dark_1.push(building(DONJON)); - donjonlane.rows.feudal_1.push(uniqueunit(DSERJEANT)); + donjonlane.rows.feudal_1.push(unit(DSERJEANT)); donjonlane.rows.feudal_1.push(unit(DSPEARMAN)); donjonlane.rows.castle_1.push(unit(DPIKEMAN)); - donjonlane.rows.imperial_1.push(uniqueunit(ELITE_DSERJEANT)); + donjonlane.rows.imperial_1.push(unit(ELITE_DSERJEANT)); donjonlane.rows.imperial_1.push(unit(DHALBERDIER)); tree.lanes.push(donjonlane); @@ -1011,7 +780,7 @@ function getDefaultTree() { let monasterylane = new Lane(); monasterylane.rows.castle_1.push(building(MONASTERY)); monasterylane.rows.castle_2.push(unit(MONK)); - monasterylane.rows.castle_2.push(uniqueunit(MISSIONARY)); + monasterylane.rows.castle_2.push(unit(MISSIONARY)); monasterylane.rows.castle_2.push(tech(DEVOTION)); monasterylane.rows.castle_2.push(tech(REDEMPTION)); monasterylane.rows.castle_2.push(tech(ATONEMENT)); @@ -1027,7 +796,7 @@ function getDefaultTree() { let fortifiedchurchlane = new Lane(); fortifiedchurchlane.rows.castle_1.push(building(FORTIFIED_CHURCH)); - fortifiedchurchlane.rows.castle_2.push(uniqueunit(WARRIOR_PRIEST)); + fortifiedchurchlane.rows.castle_2.push(unit(WARRIOR_PRIEST)); tree.lanes.push(fortifiedchurchlane); @@ -1124,20 +893,8 @@ function getDefaultTree() { return tree; } -function u(unit) { - return 'unit_' + unit; -} - -function b(building) { - return 'building_' + building; -} - -function t(tech) { - return 'tech_' + tech; -} - function getConnections() { - let connections = [ + return [ [b(ARCHERY_RANGE), u(ARCHER)], [u(ARCHER), u(CROSSBOWMAN)], [u(CROSSBOWMAN), u(ARBALESTER)], @@ -1325,26 +1082,4 @@ function getConnections() { [b(ARCHERY_RANGE), u(XIANBEI_RAIDER)], [b(DOCK), u(LONGBOAT)], ]; - - let connection_ids = []; - for (let c of connections) { - connection_ids.push([formatId(c[0]), formatId(c[1])]); - } - return connection_ids; -} - - -function getConnectionPoints(tree) { - let points = new Map(); - for (let lane of tree.lanes) { - for (let r of Object.keys(lane.rows)) { - for (let caret of lane.rows[r]) { - points.set(caret.id, { - x: caret.x + (caret.width / 2), - y: caret.y + (caret.height / 2) - }); - } - } - } - return points; } diff --git a/ror/data/data.json b/ror/data/data.json index 1c2ebebd..965847b3 100644 --- a/ror/data/data.json +++ b/ror/data/data.json @@ -1058,6 +1058,84 @@ "internal_name": "PGTAA" } }, + "node_types": { + "buildings": { + "12": "BuildingTech", + "45": "BuildingTech", + "49": "BuildingTech", + "50": "BuildingNonTech", + "63": "BuildingNonTech", + "64": "BuildingNonTech", + "68": "BuildingTech", + "70": "BuildingNonTech", + "72": "BuildingNonTech", + "79": "BuildingNonTech", + "84": "BuildingTech", + "87": "BuildingTech", + "101": "BuildingTech", + "103": "BuildingTech", + "104": "BuildingTech", + "109": "BuildingTech", + "117": "BuildingNonTech", + "155": "BuildingNonTech", + "209": "BuildingTech", + "234": "BuildingNonTech", + "235": "BuildingNonTech", + "236": "BuildingNonTech", + "276": "BuildingNonTech", + "562": "BuildingTech", + "621": "BuildingNonTech", + "789": "BuildingNonTech" + }, + "units": { + "4": "Unit", + "5": "Unit", + "7": "Unit", + "11": "UnitUpgrade", + "13": "Unit", + "15": "UnitUpgrade", + "17": "Unit", + "21": "UnitUpgrade", + "24": "Unit", + "37": "UnitUpgrade", + "38": "Unit", + "39": "Unit", + "74": "Unit", + "75": "UnitUpgrade", + "77": "UnitUpgrade", + "83": "Unit", + "93": "Unit", + "125": "Unit", + "128": "Unit", + "279": "Unit", + "280": "Unit", + "281": "Unit", + "283": "UnitUpgrade", + "329": "Unit", + "358": "UnitUpgrade", + "359": "UnitUpgrade", + "420": "Unit", + "442": "UnitUpgrade", + "448": "Unit", + "473": "UnitUpgrade", + "474": "UnitUpgrade", + "492": "UnitUpgrade", + "531": "UnitUpgrade", + "539": "Unit", + "542": "UnitUpgrade", + "545": "Unit", + "550": "UnitUpgrade", + "569": "UnitUpgrade", + "588": "UnitUpgrade", + "691": "UnitUpgrade", + "873": "Unit", + "1103": "Unit", + "1132": "UnitUpgrade", + "1134": "UnitUpgrade", + "1370": "Unit", + "1372": "UnitUpgrade" + } + }, "techs": { "2": { "Cost": { diff --git a/ror/data/locales/fr/strings.json b/ror/data/locales/fr/strings.json index 0bf8ea73..db6bce8b 100644 --- a/ror/data/locales/fr/strings.json +++ b/ror/data/locales/fr/strings.json @@ -303,4 +303,4 @@ "326353": "Former un char à faux (‹cost›)
\nUnité montée rapide résistante à la conversion qui inflige des dégâts de piétinement à toutes les unités à proximité lorsqu'elle attaque. Vulnérable face aux chameliers.
\nAméliorations : attaque, armure (Puits de stockage) ; points de vie (Siège du gouvernement).‹DEFAULT›
\n‹hp› ‹attack› ‹armor› ‹piercearmor› ‹range›", "326354": "Former un éléphant en armure (‹cost›)
\nUnité montée lente et puissante qui inflige des dégâts de piétinement à toutes les unités à proximité lorsqu'elle attaque. Efficace contre les bâtiments et les unités à courte portée. Vulnérable face aux prêtres.
\nAméliorations : attaque (Puits de stockage).‹DEFAULT›
\n‹hp› ‹attack› ‹armor› ‹piercearmor› ‹range›", "326471": "Construire Charrette de commerce (‹cost›)
\nUnité de commerce utilisée pour générer de l'or au marché d'un autre joueur. Pour faire un échange, cliquez sur la charrette de commerce puis cliquez droit sur le marché d'un autre joueur. Elle reviendra avec de l'or sur votre marché.
\n‹hp› ‹attack› ‹armor› ‹piercearmor› ‹range›" -} \ No newline at end of file +} diff --git a/ror/img/Backgrounds/bg_aoe2_hd_paper.jpg b/ror/img/Backgrounds/bg_aoe2_hd_paper.jpg deleted file mode 100644 index c0e2f159a9f7fee7a90b79272fe834fc281081e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9297 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 diff --git a/ror/index.html b/ror/index.html index 1c491c0b..f3707df1 100644 --- a/ror/index.html +++ b/ror/index.html @@ -29,6 +29,7 @@ + diff --git a/ror/js/techtree.js b/ror/js/techtree.js index fa2f9791..c2e7b21e 100644 --- a/ror/js/techtree.js +++ b/ror/js/techtree.js @@ -1,19 +1,3 @@ -const TYPES = Object.freeze({ - 'BUILDING': {colour: '#922602', type: 'BUILDING', name: 'Building'}, - 'UNIT': {colour: '#3a6a80', type: 'UNIT', name: 'Unit'}, - 'UNIQUEUNIT': {colour: '#af30a3', type: 'UNIQUEUNIT', name: 'Unique Unit'}, - 'TECHNOLOGY': {colour: '#2c5729', type: 'TECHNOLOGY', name: 'Technology'} -}); - -const LEGEND = [TYPES.UNIT, TYPES.BUILDING, TYPES.TECHNOLOGY]; - -const PREFIX = Object.freeze({ - 'BUILDING': 'building_', - 'UNIT': 'unit_', - 'UNIQUEUNIT': 'unit_', - 'TECHNOLOGY': 'tech_' -}); - const AGE_IMAGES = ['techtree_stone_age.png', 'techtree_tool_age.png', 'techtree_bronze_age.png', 'techtree_iron_age.png']; const getAgeNames = (data) => { @@ -25,60 +9,6 @@ const getAgeNames = (data) => { ]; } -const unitClasses = { - 0: 'Wonders', - 1: 'Infantry', - 2: 'Heavy Warships', - 3: 'Base Pierce', - 4: 'Base Melee', - 5: 'Elephants', - 6: 'Unused', - 7: 'Unused', - 8: 'Mounted Units', - 9: 'Unused', - 10: 'Unused', - 11: 'All Buildings', - 12: 'Unused', - 13: 'Stone Walls & Gates & Towers', - 14: 'Wolves etc.', - 15: 'All Archers', - 16: 'Ships', - 17: 'High Pierce Armor Siege Units', - 18: 'Trees', - 19: 'Unique Units', - 20: 'Siege Units', - 21: 'Standard Buildings', - 22: 'Walls & Gates', - 23: 'Gunpowder Units', - 24: 'Boars etc.', - 25: 'Monks', - 26: 'Castles & Kreposts', - 27: 'Spear Units', - 28: 'Mounted Archers', - 29: 'Shock Infantry', - 30: 'Camels', - 31: 'Obsolete', - 32: 'Condottieri', - 33: 'Gunpowder units secondary projectile attack', - 34: 'Fishing Ships', - 35: 'Mamelukes', - 36: 'Heroes & Kings', - 37: 'Heavy Siege', - 38: 'Skirmishers', - 39: 'Cavalry Resistance', - 40: 'Houses' -}; - -const animation_duration = 50; - -const UNIQUE_UNIT = 'UNIQUE UNIT'; -const ELITE_UNIQUE_UNIT = 'ELITE UNIQUE UNIT'; -const UNIQUE_TECH_1 = 'UNIQUE TECH 1'; -const UNIQUE_TECH_2 = 'UNIQUE TECH 2'; -const MONK_PREFIX_MESO = 'meso_'; -const MONK_PREFIX_AFRICAN = 'african_'; -const MONK_PREFIX_ASIAN = 'asian_'; -const MONK_PREFIX_GENERIC = ''; const BUILDING_STYLE_GENERIC = ''; const BARRACKS = 12; const DOCK = 45; @@ -223,6 +153,7 @@ const BUILDING_INDEX = [ TEMPLE, DOCK, ]; + class Tree { constructor() { this.offsets = { @@ -358,52 +289,18 @@ class Lane { } } -class Caret { - constructor(type, name, id) { - this.type = type; - this.name = name; - this.id = PREFIX[type.type] + formatId(id); - this.width = 100; - this.height = 100; - this.x = 0; - this.y = 0; - } - - isBuilding() { - return this.type === TYPES.BUILDING; - } -} - -function formatId(string) { - return string.toString().replace(/\s/g, '_').replace(/\//g, '_').toLowerCase(); -} - -function checkIdUnique(tree) { - let ids = new Set(); - for (let lane of tree.lanes) { - for (let r of Object.keys(lane.rows)) { - for (let caret of lane.rows[r]) { - if (ids.has(caret.id)) { - console.error('ID ' + caret.id + ' is not unique!'); - } - ids.add(caret.id); - } - } - } -} - function enable(buildings, units, techs) { for (let item of buildings) { - SVG('#building_' + formatId(item.id) + '_x').attr({'opacity': 0}); - SVG('#building_' + formatId(item.id) + '_disabled_gray').attr({'opacity': 0}); + SVG('#' + b(item.id) + '_x').attr({'opacity': 0}); + SVG('#' + b(item.id) + '_disabled_gray').attr({'opacity': 0}); } for (let item of units) { - SVG('#unit_' + formatId(item.id) + '_x').attr({'opacity': 0}); - SVG('#unit_' + formatId(item.id) + '_disabled_gray').attr({'opacity': 0}); + SVG('#' + u(item.id) + '_x').attr({'opacity': 0}); + SVG('#' + u(item.id) + '_disabled_gray').attr({'opacity': 0}); } for (let item of techs) { - SVG('#tech_' + formatId(item.id) + '_x').attr({'opacity': 0}); - SVG('#tech_' + formatId(item.id) + '_disabled_gray').attr({'opacity': 0}); + SVG('#' + t(item.id) + '_x').attr({'opacity': 0}); + SVG('#' + t(item.id) + '_disabled_gray').attr({'opacity': 0}); } } @@ -416,77 +313,20 @@ function applySelectedCiv(selectedCiv) { document.getElementById('building_index_209_img').src = `./img/Buildings/209_${selectedCiv.buildingStyle}.png`; } -function formatName(originalname) { - let name = originalname.toString().replace(/
/g, '\n').replace(/\n+/g, '\n'); - const items = name.split('\n'); - for (let i = 0; i < items.length; i++) { - const item = items[i]; - if (items[i].length > 10) { - let space = item.indexOf(' '); - if (space !== -1) { - items[i] = item.slice(0, space) + '\n' + item.slice(space + 1); - let alternativeSpace = space + 1 + item.slice(space + 1).indexOf(' '); - if (alternativeSpace !== -1) { - if (Math.abs((item.length / 2) - alternativeSpace) < Math.abs((item.length / 2) - space)) { - items[i] = item.slice(0, alternativeSpace) + '\n' + item.slice(alternativeSpace + 1); - } - } - } else { - let hyphen = item.indexOf('-'); - if (hyphen !== -1) { - items[i] = item.slice(0, hyphen) + '-\n' + item.slice(hyphen + 1); - let alternativeHyphen = hyphen + 1 + item.slice(hyphen + 1).indexOf('-'); - if (alternativeHyphen !== -1) { - if (Math.abs((item.length / 2) - alternativeHyphen) < Math.abs((item.length / 2) - hyphen)) { - items[i] = item.slice(0, alternativeHyphen) + '-\n' + item.slice(alternativeHyphen + 1); - } - } - } - } - } - } - return items.join('\n'); -} - function unique(building_style) { if (building_style === undefined) { building_style = BUILDING_STYLE_GENERIC; } - SVG('#building_' + formatId(BARRACKS) + '_img').load('img/Buildings/' + BARRACKS + '_' + building_style + '.png'); - SVG('#building_' + formatId(ACADEMY) + '_img').load('img/Buildings/' + ACADEMY + '_' + building_style + '.png'); - SVG('#building_' + formatId(TOWN_CENTER) + '_img').load('img/Buildings/' + TOWN_CENTER + '_' + building_style + '.png'); - SVG('#building_' + formatId(TOWN_CENTER_2) + '_img').load('img/Buildings/' + TOWN_CENTER + '_' + building_style + '.png'); - SVG('#building_' + formatId(MEDIUM_WALL) + '_img').load('img/Buildings/' + MEDIUM_WALL + '_' + building_style + '.png'); - SVG('#building_' + formatId(FORTIFIED_WALL) + '_img').load('img/Buildings/' + FORTIFIED_WALL + '_' + building_style + '.png'); - SVG('#building_' + formatId(GOVERNMENT_CENTER) + '_img').load('img/Buildings/' + GOVERNMENT_CENTER + '_' + building_style + '.png'); - SVG('#building_' + formatId(GUARD_TOWER) + '_img').load('img/Buildings/' + GUARD_TOWER + '_' + building_style + '.png'); - SVG('#building_' + formatId(BALLISTA_TOWER) + '_img').load('img/Buildings/' + GUARD_TOWER + '_' + building_style + '.png'); - SVG('#building_' + formatId(WONDER) + '_img').load('img/Buildings/' + WONDER + '_' + building_style + '.png'); -} - -function getName(id, itemtype) { - //ToDo handle unique stuff properly - if(id.toString().startsWith('UNIQUE')){ - return id; - } - const languageNameId = data['data'][itemtype][id]['LanguageNameId']; - return data['strings'][languageNameId]; -} - -function building(id) { - return new Caret(TYPES.BUILDING, getName(id, 'buildings'), id); -} - -function unit(id) { - return new Caret(TYPES.UNIT, getName(id, 'units'), id); -} - -function uniqueunit(id) { - return new Caret(TYPES.UNIQUEUNIT, getName(id, 'units'), id); -} - -function tech(id) { - return new Caret(TYPES.TECHNOLOGY, getName(id, 'techs'), id); + SVG('#' + b(BARRACKS) + '_img').load('img/Buildings/' + BARRACKS + '_' + building_style + '.png'); + SVG('#' + b(ACADEMY) + '_img').load('img/Buildings/' + ACADEMY + '_' + building_style + '.png'); + SVG('#' + b(TOWN_CENTER) + '_img').load('img/Buildings/' + TOWN_CENTER + '_' + building_style + '.png'); + SVG('#' + b(TOWN_CENTER_2) + '_img').load('img/Buildings/' + TOWN_CENTER + '_' + building_style + '.png'); + SVG('#' + b(MEDIUM_WALL) + '_img').load('img/Buildings/' + MEDIUM_WALL + '_' + building_style + '.png'); + SVG('#' + b(FORTIFIED_WALL) + '_img').load('img/Buildings/' + FORTIFIED_WALL + '_' + building_style + '.png'); + SVG('#' + b(GOVERNMENT_CENTER) + '_img').load('img/Buildings/' + GOVERNMENT_CENTER + '_' + building_style + '.png'); + SVG('#' + b(GUARD_TOWER) + '_img').load('img/Buildings/' + GUARD_TOWER + '_' + building_style + '.png'); + SVG('#' + b(BALLISTA_TOWER) + '_img').load('img/Buildings/' + GUARD_TOWER + '_' + building_style + '.png'); + SVG('#' + b(WONDER) + '_img').load('img/Buildings/' + WONDER + '_' + building_style + '.png'); } function getDefaultTree() { @@ -714,20 +554,8 @@ function getDefaultTree() { return tree; } -function u(unit) { - return 'unit_' + unit; -} - -function b(building) { - return 'building_' + building; -} - -function t(tech) { - return 'tech_' + tech; -} - function getConnections() { - let connections = [ + return [ [b(ARCHERY_RANGE), b(SIEGE_WORKSHOP)], [b(ARCHERY_RANGE), u(BOWMAN)], // [b(ARCHERY_RANGE), u(CHARIOT_ARCHER)], @@ -846,26 +674,4 @@ function getConnections() { // [b(DOCK), u(CATAPULT_TRIREME)], [u(CATAPULT_TRIREME), u(JUGGERNAUT)], ]; - - let connection_ids = []; - for (let c of connections) { - connection_ids.push([formatId(c[0]), formatId(c[1])]); - } - return connection_ids; -} - - -function getConnectionPoints(tree) { - let points = new Map(); - for (let lane of tree.lanes) { - for (let r of Object.keys(lane.rows)) { - for (let caret of lane.rows[r]) { - points.set(caret.id, { - x: caret.x + (caret.width / 2), - y: caret.y + (caret.height / 2) - }); - } - } - } - return points; } diff --git a/scripts/README.md b/scripts/README.md index 6305494c..bb91b82e 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -8,12 +8,20 @@ and the tech tree data from the civTechTrees.json file from aoe2de. Create and activate a virtual environment with genieutils-py installed: +## For Linux ```sh python3 -m venv venv source venv/bin/activate pip install genieutils-py ``` +## For Windows +```sh +python3 -m venv venv +.\venv\Scripts\Activate +pip install genieutils-py +``` + Example invocation: ```sh diff --git a/scripts/generateBuildingTechUnitImages.py b/scripts/generateBuildingTechUnitImages.py index 1378eee5..4c74eee2 100755 --- a/scripts/generateBuildingTechUnitImages.py +++ b/scripts/generateBuildingTechUnitImages.py @@ -11,6 +11,8 @@ PLAYER_COLOUR = (0, 119, 228) # PLAYER_COLOUR = (236,9,9) +UPPER_CASE_CHRONICLES_CIV_NAMES = {"ACHAEMENIDS", "ATHENIANS", "SPARTANS", "MACEDONIANS", "PURU", "THRACIANS"} + BASE_PATH = Path.home() / 'aoe/Aoe2DE proton/widgetui/textures/ingame' TARGET_SIZE = (48, 48) @@ -126,7 +128,7 @@ def main(): techtrees = json.loads(ttfcontent) ids = {'Units': set(), 'Buildings': set(), 'Techs': set()} for civ in techtrees['civs']: - if civ['civ_id'] in ('ACHAEMENIDS','ATHENIANS','SPARTANS'): + if civ['civ_id'] in UPPER_CASE_CHRONICLES_CIV_NAMES: continue for item in (civ['civ_techs_buildings'] + civ['civ_techs_units']): if item['Use Type'] == 'Unit': diff --git a/scripts/generateDataFiles.py b/scripts/generateDataFiles.py index fb03fa40..ee148943 100755 --- a/scripts/generateDataFiles.py +++ b/scripts/generateDataFiles.py @@ -111,6 +111,17 @@ "Lac Viet": "310287", } +CHRONICLES_CIV_NAMES = { + "Achaemenids": "10316", + "Athenians": "10317", + "Spartans": "10318", + "Macedonians": "10324", + "Thracians": "10325", + "Puru": "10326", +} + +UPPER_CASE_CHRONICLES_CIV_NAMES = {key.upper() for key in CHRONICLES_CIV_NAMES} + CIV_HELPTEXTS = { "Britons": "120150", "Franks": "120151", @@ -274,10 +285,10 @@ def gather_language_data(resourcesdir, data, language): key_value[26768] = key_value[28314] # Gillnets key_value[42057] = key_value[26288] # Use Konnik for Dismounted Konnik key_value[42058] = key_value[26290] # Use Elite Konnik for Dismounted Elite Konnik - key_value[21104] = key_value[5414] # Fix Ratha name + key_value[21104] = key_value[5414] # Fix Ratha name key_value[42104] = key_value[26414] # Fix Ratha description key_value[42096] = key_value[26414] # Fix Ratha (melee) description - key_value[21105] = key_value[5420] # Fix Elite Ratha name + key_value[21105] = key_value[5420] # Fix Elite Ratha name key_value[42105] = key_value[26420] # Fix Elite Ratha description key_value[42097] = key_value[26420] # Fix Elite Ratha (melee) description key_value[42198] = key_value[26364] # Fix War Chariot (Barrage) description @@ -407,7 +418,7 @@ def gather_data(content: DatFile, civs, unit_upgrades, node_types): return data -def ror_gather_data(content: DatFile, civs, unit_upgrades): +def ror_gather_data(content: DatFile, civs, unit_upgrades, node_types): ages = list(ROR_AGE_NAMES.keys())[1:] building_ids = {b['id'] for c in civs.values() for b in c['buildings']} unit_ids = {u['id'] for c in civs.values() for u in c['units']} @@ -419,7 +430,7 @@ def ror_gather_data(content: DatFile, civs, unit_upgrades): ) gaia = content.civs[0] graphics = content.graphics - data = {"buildings": {}, "units": {}, "techs": {}, "unit_upgrades": {}} + data = {"buildings": {}, "units": {}, "techs": {}, "unit_upgrades": {}, "node_types": node_types} for unit in gaia.units: if not unit: continue @@ -600,7 +611,7 @@ def gather_civs(techtrees): unit_upgrades = {} node_types = {'buildings': {}, 'units':{}} for civ in techtrees['civs']: - if civ['civ_id'] in ('ACHAEMENIDS','ATHENIANS','SPARTANS', 'MACEDONIANS', 'THRACIANS', 'PURU'): + if civ['civ_id'] in UPPER_CASE_CHRONICLES_CIV_NAMES: continue current_civ = {'buildings': [], 'units': [], 'techs': [], 'unique': {}, 'monkSuffix': ''} for building in civ['civ_techs_buildings']: @@ -690,12 +701,15 @@ def ror_gather_civs(techtrees): unit_excludelist = () civs = {} unit_upgrades = {} + node_types = {'buildings': {}, 'units':{}} for civ in techtrees['civs']: current_civ = {'buildings': [], 'units': [], 'techs': []} for building in civ['civ_techs_buildings']: + node_types['buildings'][building['Node ID']] = building['Node Type'] if building['Node Status'] != 'NotAvailable': current_civ['buildings'].append({'id': building['Node ID'], 'age': building['Age ID']}) for unit in filter(ror_is_unit, civ['civ_techs_units']): + node_types['units'][unit['Node ID']] = unit['Node Type'] current_civ['units'].append({'id': unit['Node ID'], 'age': unit['Age ID']}) if unit['Trigger Tech ID'] > -1: unit_upgrades[unit['Node ID']] = unit['Trigger Tech ID'] @@ -715,7 +729,7 @@ def ror_gather_civs(techtrees): current_civ['buildingStyle'] = ROR_BUILDING_STYLES[civname] - return civs, unit_upgrades + return civs, unit_upgrades, node_types def ror_update_civ_techs(civs, data): @@ -771,10 +785,10 @@ def process_ror(args, outputdir): ttfcontent = techtreesfile.read_text() ttfcontent = re.sub(r',\n( +)\]', r'\n\1]', ttfcontent) techtrees = json.loads(ttfcontent) - civs, unit_upgrades = ror_gather_civs(techtrees) + civs, unit_upgrades, node_types = ror_gather_civs(techtrees) datafile = Path(args.programdir) / 'modes' / 'Pompeii' / 'resources' / '_common' / 'dat' / 'empires2_x2_p1.dat' content = DatFile.parse(datafile) - data = ror_gather_data(content, civs, unit_upgrades) + data = ror_gather_data(content, civs, unit_upgrades, node_types) ror_update_civ_techs(civs, data) ror_write_datafile(data, civs, outputdir) ror_write_language_files(args, data, outputdir) From cb678622d3c369c80c0ac99edcffa13d28b87624 Mon Sep 17 00:00:00 2001 From: Saurav Tripathi Date: Mon, 3 Nov 2025 08:27:40 +0530 Subject: [PATCH 2/2] minor fix --- js/techtree.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/techtree.js b/js/techtree.js index 268b8a99..c83280e1 100644 --- a/js/techtree.js +++ b/js/techtree.js @@ -752,9 +752,9 @@ function getDefaultTree() { castlelane.rows.imperial_1.push(tech(UNIQUE_TECH_2)); castlelane.rows.imperial_1.push(tech(HOARDINGS)); castlelane.rows.imperial_1.push(tech(SAPPERS)); - castlelane.rows.imperial_2.push(uniqueunit(LIU_BEI)); - castlelane.rows.imperial_2.push(uniqueunit(CAO_CAO)); - castlelane.rows.imperial_2.push(uniqueunit(SUN_JIAN)); + castlelane.rows.imperial_2.push(unit(LIU_BEI)); + castlelane.rows.imperial_2.push(unit(CAO_CAO)); + castlelane.rows.imperial_2.push(unit(SUN_JIAN)); castlelane.rows.imperial_2.push(tech(CONSCRIPTION)); castlelane.rows.imperial_2.push(tech(SPIES_TREASON)); tree.lanes.push(castlelane);