function copyBuildInfo() { if(process.argv[3]==="dev") return this(); base.info("Copying latest .build.info file..."); fileUtil.copy(path.join(HOTS_PATH, ".build.info"), path.join(HOTS_DATA_PATH, ".build.info"), this); },
function clearOut() { if(process.argv[3]==="dev") return this(); base.info("Clearing 'out' directory..."); rimraf(OUT_PATH, this); },
function extractFiles() { if(process.argv[3]==="dev") return this(); base.info("Extracting %d needed files...", NEEDED_FILE_PATHS.length); NEEDED_FILE_PATHS.parallelForEach(function(NEEDED_FILE_PATH, subcb) { runUtil.run(CASCEXTRATOR_PATH, [HOTS_DATA_PATH, "-o", OUT_PATH, "-f", NEEDED_FILE_PATH], {silent:true}, subcb); }, this, 10); },
function finish(err) { if(err) { base.error(err); process.exit(1); } base.info("Done."); process.exit(0); }
xmlDoc.find("/Catalog/*").forEach(function(node) { var nodeType = NODE_MAP_TYPES.filter(function(NODE_MAP_TYPE) { return node.name()===("C" + NODE_MAP_TYPE); }).concat(NODE_MAP_PREFIX_TYPES.filter(function(NODE_MAP_PREFIX_TYPE) { return node.name().startsWith(NODE_MAP_PREFIX_TYPES); })).unique(); if(!nodeType || nodeType.length!==1) return; nodeType = nodeType[0]; if(node.attr("id") || attributeValue(node, "default")!=="1") return; if(DEFAULT_NODES.hasOwnProperty(nodeType)) { base.info(DEFAULT_NODES[nodeType].toString()); base.info(node.toString()); base.error("MORE THAN ONE DEFAULT! NOT GOOD!"); process.exit(1); } DEFAULT_NODES[nodeType] = node; });
function validateHero(hero) { var validator = jsen(C.HERO_JSON_SCHEMA); if(!validator(hero)) { base.warn("Hero %s (%s) has FAILED VALIDATION", hero.id, hero.name); base.info(validator.errors); } Object.forEach(hero.abilities, function(unitName, abilities) { if(abilities.length!==abilities.map(function(ability) { return ability.name; }).unique().length) base.warn("Hero %s has multiple abilities with the same name!", hero.name); }); }
function loadDataAndSaveJSON() { var xmlDocs = []; base.info("Loading data..."); NEEDED_FILE_PATHS.forEach(function(NEEDED_FILE_PATH) { var diskPath = path.join(OUT_PATH, NEEDED_FILE_PATH.replaceAll("\\\\", "/")); if(!fs.existsSync(diskPath)) { //base.info("Missing file: %s", NEEDED_FILE_PATH); return; } var fileData = fs.readFileSync(diskPath, {encoding:"utf8"}); if(diskPath.endsWith("GameStrings.txt")) { fileData.split("\n").forEach(function(line) { S[line.substring(0, line.indexOf("="))] = line.substring(line.indexOf("=")+1).trim(); }); } else if(diskPath.endsWith(".xml")) { xmlDocs.push(libxmljs.parseXml(fileData)); } }); loadMergedNodeMap(xmlDocs); mergeNodeParents(); base.info("\nProcessing heroes..."); var heroes = Object.values(NODE_MAPS["Hero"]).map(function(heroNode) { return processHeroNode(heroNode); }).filterEmpty(); heroes.sort(function(a, b) { return (a.name.startsWith("The ") ? a.name.substring(4) : a.name).localeCompare((b.name.startsWith("The ") ? b.name.substring(4) : b.name)); }); base.info("\nValidating %d heroes...", heroes.length); heroes.forEach(validateHero); base.info("\nProcessing mounts..."); var mounts = Object.values(NODE_MAPS["Mount"]).map(function(mountNode) { return processMountNode(mountNode); }).filterEmpty(); base.info("\nValidating %d mounts...", mounts.length); mounts.forEach(validateMount); mounts.sort(function(a, b) { return (a.name.startsWith("The ") ? a.name.substring(4) : a.name).localeCompare((b.name.startsWith("The ") ? b.name.substring(4) : b.name)); }); base.info("\nSaving JSON..."); fs.writeFile(HEROES_OUT_PATH, JSON.stringify(heroes), {encoding:"utf8"}, this.parallel()); fs.writeFile(MOUNTS_OUT_PATH, JSON.stringify(mounts), {encoding:"utf8"}, this.parallel()); },
function validateMount(mount, index, mounts) { var validator = jsen(C.MOUNT_JSON_SCHEMA); if(!validator(mount)) { // For every fail (usually a variation), copy it from the parent) validator.errors.forEach(function(elem) { var parent = findParentMount(mounts, "productid", mount.productid); for (var item in parent) { if (mount[item] === undefined) { mount[item] = parent[item]; } }; }); // WARNING: I may have a race condition here... // Revalidate if (!validator(mount)) { base.warn("Mount %s (%s) has FAILED VALIDATION", mount.id, mount.name); base.info(validator.errors); } } }
alternateUnitArrayNodes.forEach(function(alternateUnitArrayNode) { var alternateHeroid = attributeValue(alternateUnitArrayNode, "value"); base.info("Alternate: ", alternateHeroid); heroUnitids.push(alternateHeroid); });
function processHeroNode(heroNode) { var hero = {}; // Core hero data hero.id = attributeValue(heroNode, "id"); if(C.SKIP_HERO_IDS.contains(hero.id)) return; hero.attributeid = getValue(heroNode, "AttributeId"); hero.name = S["Unit/Name/" + getValue(heroNode, "Unit", "Hero" + hero.id)] || S[getValue(heroNode, "Name")]; if(!hero.name) { base.info(heroNode.toString()); throw new Error("Failed to get name for hero: " + hero.id); } base.info("Processing hero: %s (%s)", hero.name, hero.id); hero.title = S["Hero/Title/" + hero.id]; hero.description = S["Hero/Description/" + hero.id]; hero.icon = "ui_targetportrait_hero_" + (C.HERO_ID_TEXTURE_RENAMES.hasOwnProperty(hero.id) ? C.HERO_ID_TEXTURE_RENAMES[hero.id] : hero.id) + ".dds"; hero.role = getValue(heroNode, "Role"); if(hero.role==="Damage") hero.role = "Assassin"; if(!hero.role) hero.role = "Warrior"; hero.type = !!getValue(heroNode, "Melee") ? "Melee" : "Ranged"; hero.gender = getValue(heroNode, "Gender", "Male"); hero.franchise = getValue(heroNode, "Universe", "Starcraft"); hero.difficulty = getValue(heroNode, "Difficulty", "Easy"); if(hero.difficulty==="VeryHard") hero.difficulty = "Very Hard"; var ratingsNode = heroNode.get("Ratings"); if(ratingsNode) { hero.ratings = { damage : +getValue(ratingsNode, "Damage", attributeValue(ratingsNode, "Damage", 1)), utility : +getValue(ratingsNode, "Utility", attributeValue(ratingsNode, "Utility", 1)), survivability : +getValue(ratingsNode, "Survivability", attributeValue(ratingsNode, "Survivability", 1)), complexity : +getValue(ratingsNode, "Complexity", attributeValue(ratingsNode, "Complexity", 1)), }; } hero.releaseDate = processReleaseDate(heroNode.get("ReleaseDate")); var heroUnitids = [ hero.id ]; var alternateUnitArrayNodes = heroNode.find("AlternateUnitArray"); if(alternateUnitArrayNodes && alternateUnitArrayNodes.length>0) { alternateUnitArrayNodes.forEach(function(alternateUnitArrayNode) { var alternateHeroid = attributeValue(alternateUnitArrayNode, "value"); base.info("Alternate: ", alternateHeroid); heroUnitids.push(alternateHeroid); }); } heroUnitids = heroUnitids.concat(C.ADDITIONAL_HERO_SUBUNIT_IDS[hero.id] || []); base.info("Sub-units:", hero.id, heroUnitids); // Level Scaling Info HERO_LEVEL_SCALING_MODS[hero.id] = []; addHeroLevelScalingMods(hero.id, DEFAULT_NODES["Hero"]); addHeroLevelScalingMods(hero.id, heroNode); // Hero Stats hero.stats = {}; heroUnitids.forEach(function(heroUnitid) { hero.stats[heroUnitid] = getHeroStats(heroUnitid); if(Object.keys(hero.stats[heroUnitid]).length===0) delete hero.stats[heroUnitid]; }); // Abilities hero.abilities = getHeroAbilities(hero.id, hero.name, heroUnitids); // Talents hero.talents = {}; C.HERO_TALENT_LEVELS.forEach(function(HERO_TALENT_LEVEL) { hero.talents[HERO_TALENT_LEVEL] = []; }); var talentTreeNodes = heroNode.find("TalentTreeArray").filter(function(talentTreeNode) { return !!!attributeValue(talentTreeNode, "removed"); }); talentTreeNodes.sort(function(a, b) { return (+((+attributeValue(a, "Tier"))*10)+(+attributeValue(a, "Column")))-(+((+attributeValue(b, "Tier"))*10)+(+attributeValue(b, "Column"))); }); talentTreeNodes.forEach(function(talentTreeNode) { var talent = {}; talent.id = attributeValue(talentTreeNode, "Talent"); var talentNode = NODE_MAPS["Talent"][talent.id]; var faceid = getValue(talentNode, "Face"); var talentDescription = S["Button/Tooltip/" + faceid]; if(!talentDescription && faceid==="TyrandeHuntersMarkTrueshotAuraTalent") talentDescription = S["Button/Tooltip/TyrandeTrueshotBowTalent"]; if(!talentDescription) { base.warn("Missing talent description for hero [%s] and talentid [%s] and faceid [%s]", hero.id, talent.id, faceid); return; } if(talentDescription.contains("StandardTooltipHeader")) talent.name = talentDescription.replace(/<s val="StandardTooltipHeader">([^<]+)<.+/, "$1").replace(/<s\s*val\s*=\s*"StandardTooltip">/gm, "").trim(); else talent.name = S[getValue(NODE_MAPS["Button"][faceid], "Name")]; if(!talent.name) talent.name = S["Button/Name/" + faceid]; //if(hero.id==="L90ETC") { base.info("Talent: %s\n", talent.id); } talent.description = getFullDescription(talent.id, talentDescription, hero.id, 0); talent.icon = getValue(NODE_MAPS["Button"][faceid], "Icon"); if(!talent.icon) talent.icon = getValue(NODE_MAPS["Button"][attributeValue(NODE_MAPS["Button"][faceid], "parent")], "Icon"); if(!talent.icon) delete talent.icon; else talent.icon = talent.icon.replace(/Assets\\Textures\\/, ""); addCooldownInfo(talent, "description"); if(!talent.cooldown) talent.cooldown = getAbilityCooldown(NODE_MAPS["Abil"][getValue(talentNode, "Abil")]); var talentPrerequisiteNode = talentTreeNode.get("PrerequisiteTalentArray"); if(talentPrerequisiteNode) talent.prerequisite = attributeValue(talentPrerequisiteNode, "value"); hero.talents[C.HERO_TALENT_LEVELS[((+attributeValue(talentTreeNode, "Tier"))-1)]].push(talent); }); // Final modifications performHeroModifications(hero); return hero; }