/** * @file Contains all helper functions that are needed only for selection_panels.js * and some that are needed for hotkeys, but not for anything inside input.js. */ const UPGRADING_NOT_STARTED = -2; const UPGRADING_CHOSEN_OTHER = -1; function canMoveSelectionIntoFormation(formationTemplate) { if (formationTemplate == NULL_FORMATION) return true; if (!(formationTemplate in g_canMoveIntoFormation)) g_canMoveIntoFormation[formationTemplate] = Engine.GuiInterfaceCall("CanMoveEntsIntoFormation", { "ents": g_Selection.toList(), "formationTemplate": formationTemplate }); return g_canMoveIntoFormation[formationTemplate]; } function hasSameRestrictionCategory(templateName1, templateName2) { let template1 = GetTemplateData(templateName1); let template2 = GetTemplateData(templateName2); if (template1.trainingRestrictions && template2.trainingRestrictions) return template1.trainingRestrictions.category == template2.trainingRestrictions.category; if (template1.buildRestrictions && template2.buildRestrictions) return template1.buildRestrictions.category == template2.buildRestrictions.category; return false; } /** * Returns a "color:255 0 0 Alpha" string based on how many resources are needed. */ function resourcesToAlphaMask(neededResources) { let totalCost = 0; for (let resource in neededResources) totalCost += +neededResources[resource]; return "color:255 0 0 " + Math.min(125, Math.round(+totalCost / 10) + 50); } function getStanceDisplayName(name) { switch (name) { case "violent": return translateWithContext("stance", "Violent"); case "aggressive": return translateWithContext("stance", "Aggressive"); case "defensive": return translateWithContext("stance", "Defensive"); case "passive": return translateWithContext("stance", "Passive"); case "standground": return translateWithContext("stance", "Standground"); default: warn("Internationalization: Unexpected stance found: " + name); return name; } } function getStanceTooltip(name) { switch (name) { case "violent": return translateWithContext("stance", "Attack nearby opponents, focus on attackers and chase while visible"); case "aggressive": return translateWithContext("stance", "Attack nearby opponents"); case "defensive": return translateWithContext("stance", "Attack nearby opponents, chase a short distance and return to the original location"); case "passive": return translateWithContext("stance", "Flee if attacked"); case "standground": return translateWithContext("stance", "Attack opponents in range, but don't move"); default: return ""; } } /** * Format entity count/limit message for the tooltip */ function formatLimitString(trainEntLimit, trainEntCount, trainEntLimitChangers) { if (trainEntLimit == undefined) return ""; var text = sprintf(translate("Current Count: %(count)s, Limit: %(limit)s."), { "count": trainEntCount, "limit": trainEntLimit }); if (trainEntCount >= trainEntLimit) text = objectionFont(text); for (var c in trainEntLimitChangers) { if (!trainEntLimitChangers[c]) continue; let string = trainEntLimitChangers[c] > 0 ? translate("%(changer)s enlarges the limit with %(change)s.") : translate("%(changer)s lessens the limit with %(change)s."); text += "\n" + sprintf(string, { "changer": translate(c), "change": trainEntLimitChangers[c] }); } return text; } /** * Format template match count/limit message for the tooltip. * * @param {number} matchEntLimit - The limit of the entity. * @param {number} matchEntCount - The count of the entity. * @param {string} type - The type of the action (i.e. "build" or "training"). * * @return {string} - The string to show the user with information regarding the limit of this template. */ function formatMatchLimitString(matchEntLimit, matchEntCount, type) { if (matchEntLimit == undefined) return ""; let passedLimit = matchEntCount >= matchEntLimit; let count = matchEntLimit - matchEntCount; let text; if (type == "build") { if (matchEntLimit == 1) text = translate("Can be constructed only once."); else if (passedLimit) text = sprintf(translatePlural( "Could only be constructed %(limit)s time.", "Could only be constructed %(limit)s times.", matchEntLimit), { "limit": matchEntLimit }); else text = sprintf(translatePlural("Can be constructed %(count)s more time.", "Can be constructed %(count)s more times.", count), { "count": count }); } else if (type == "training") { if (passedLimit) text = sprintf(translatePlural("Could only be trained once.", "Could only be trained %(limit)s times.", matchEntLimit), { "limit": matchEntLimit }); else if (matchEntLimit == 1) text = translate("Can be trained only once."); else text = sprintf(translatePlural("Can be trained %(count)s more time.", "Can be trained %(count)s more times.", count), { "count": count }); } else { if (passedLimit) text = sprintf(translatePlural("Could only be created once.", "Could only be created %(limit)s times.", matchEntLimit), { "limit": matchEntLimit }); else if (matchEntLimit == 1) text = translate("Can be created only once."); else text = sprintf(translatePlural("Can be created %(count)s more time.", "Can be created %(count)s more times.", count), { "count": count }); } return passedLimit ? objectionFont(text) : text; } /** * Format batch training string for the tooltip * Examples: * buildingsCountToTrainFullBatch = 1, fullBatchSize = 5, remainderBatch = 0: * "Shift-click to train 5" * buildingsCountToTrainFullBatch = 2, fullBatchSize = 5, remainderBatch = 0: * "Shift-click to train 10 (2*5)" * buildingsCountToTrainFullBatch = 1, fullBatchSize = 15, remainderBatch = 12: * "Shift-click to train 27 (15 + 12)" */ function formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch) { var totalBatchTrainingCount = buildingsCountToTrainFullBatch * fullBatchSize + remainderBatch; // Don't show the batch training tooltip if either units of this type can't be trained at all // or only one unit can be trained if (totalBatchTrainingCount < 2) return ""; let fullBatchesString = ""; if (buildingsCountToTrainFullBatch > 1) fullBatchesString = sprintf(translate("%(buildings)s*%(batchSize)s"), { "buildings": buildingsCountToTrainFullBatch, "batchSize": fullBatchSize }); else if (buildingsCountToTrainFullBatch == 1) fullBatchesString = fullBatchSize; // We need to display the batch details part if there is either more than // one structure with full batch or one structure with the full batch and // another with a partial batch let batchString; if (buildingsCountToTrainFullBatch > 1 || buildingsCountToTrainFullBatch == 1 && remainderBatch > 0) if (remainderBatch > 0) batchString = translate("%(action)s to train %(number)s (%(fullBatch)s + %(remainderBatch)s)."); else batchString = translate("%(action)s to train %(number)s (%(fullBatch)s)."); else batchString = translate("%(action)s to train %(number)s."); return "[font=\"sans-13\"]" + setStringTags( sprintf(batchString, { "action": "[font=\"sans-bold-13\"]" + translate("Shift-click") + "[/font]", "number": totalBatchTrainingCount, "fullBatch": fullBatchesString, "remainderBatch": remainderBatch }), g_HotkeyTags) + "[/font]"; } /** * Camera jumping: when the user presses a hotkey the current camera location is marked. * When pressing another camera jump hotkey the camera jumps back to that position. * When the camera is already roughly at that location, jump back to where it was previously. */ var g_JumpCameraPositions = []; var g_JumpCameraLast; function jumpCamera(index) { let position = g_JumpCameraPositions[index]; if (!position) return; let threshold = Engine.ConfigDB_GetValue("user", "gui.session.camerajump.threshold"); let cameraPivot = Engine.GetCameraPivot(); if (g_JumpCameraLast && Math.abs(cameraPivot.x - position.x) < threshold && Math.abs(cameraPivot.z - position.z) < threshold) { Engine.CameraMoveTo(g_JumpCameraLast.x, g_JumpCameraLast.z); } else { g_JumpCameraLast = cameraPivot; Engine.CameraMoveTo(position.x, position.z); } } function setJumpCamera(index) { g_JumpCameraPositions[index] = Engine.GetCameraPivot(); } /** * Called by GUI when user clicks a research button. */ function addResearchToQueue(entity, researchType) { Engine.PostNetworkCommand({ "type": "research", "entity": entity, "template": researchType, "pushFront": Engine.HotkeyIsPressed("session.pushorderfront") }); } /** * Called by GUI when user clicks a production queue item. */ function removeFromProductionQueue(entity, id) { Engine.PostNetworkCommand({ "type": "stop-production", "entity": entity, "id": id }); } /** * Called by unit selection buttons. */ function makePrimarySelectionGroup(templateName) { g_Selection.makePrimarySelection(templateName); } function removeFromSelectionGroup(templateName) { g_Selection.removeGroupFromSelection(templateName); } function performCommand(entStates, commandName) { if (!entStates.length) return; if (getCommandInfo(commandName, entStates)) g_EntityCommands[commandName].execute(entStates); } function performFormation(entities, formationTemplate) { if (!entities) return; Engine.PostNetworkCommand({ "type": "formation", "entities": entities, "formation": formationTemplate }); } function performStance(entities, stanceName) { if (!entities) return; Engine.PostNetworkCommand({ "type": "stance", "entities": entities, "name": stanceName }); } function lockGate(lock) { Engine.PostNetworkCommand({ "type": "lock-gate", "entities": g_Selection.toList(), "lock": lock }); } function packUnit(pack) { Engine.PostNetworkCommand({ "type": "pack", "entities": g_Selection.toList(), "pack": pack, "queued": false }); } function cancelPackUnit(pack) { Engine.PostNetworkCommand({ "type": "cancel-pack", "entities": g_Selection.toList(), "pack": pack, "queued": false }); } function upgradeEntity(Template, selection) { Engine.PostNetworkCommand({ "type": "upgrade", "entities": selection, "template": Template, "queued": false }); } function cancelUpgradeEntity() { Engine.PostNetworkCommand({ "type": "cancel-upgrade", "entities": g_Selection.toList(), "queued": false }); } /** * Set the camera to follow the given entity if it's a unit. * Otherwise stop following. */ function setCameraFollow(entity) { let entState = entity && GetEntityState(entity); if (entState && hasClass(entState, "Unit")) Engine.CameraFollow(entity); else Engine.CameraFollow(0); } function stopUnits(entities) { Engine.PostNetworkCommand({ "type": "stop", "entities": entities, "queued": false }); } function unloadTemplate(template, owner) { Engine.PostNetworkCommand({ "type": "unload-template", "all": Engine.HotkeyIsPressed("session.unloadtype"), "template": template, "owner": owner, // Filter out all entities that aren't garrisonable. "garrisonHolders": g_Selection.filter(ent => { let state = GetEntityState(ent); return state && !!state.garrisonHolder; }) }); } function unloadAll() { const garrisonHolders = g_Selection.filter(e => { let state = GetEntityState(e); return state && !!state.garrisonHolder; }); if (!garrisonHolders.length) return; let ownEnts = []; let otherEnts = []; for (let ent of garrisonHolders) { if (controlsPlayer(GetEntityState(ent).player)) ownEnts.push(ent); else otherEnts.push(ent); } if (ownEnts.length) Engine.PostNetworkCommand({ "type": "unload-all", "garrisonHolders": ownEnts }); if (otherEnts.length) Engine.PostNetworkCommand({ "type": "unload-all-by-owner", "garrisonHolders": otherEnts }); } function unloadAllTurrets() { const turretHolders = g_Selection.filter(e => { let state = GetEntityState(e); return state && !!state.turretHolder; }); if (!turretHolders.length) return; let ownedHolders = []; let ejectables = []; for (let ent of turretHolders) { let turretHolderState = GetEntityState(ent); if (controlsPlayer(turretHolderState.player)) ownedHolders.push(ent); else { for (let turret of turretHolderState.turretHolder.turretPoints.map(tp => tp.entity)) if (turret && controlsPlayer(GetEntityState(turret).player)) ejectables.push(turret); } } if (ejectables.length) Engine.PostNetworkCommand({ "type": "leave-turret", "entities": ejectables }); if (ownedHolders.length) Engine.PostNetworkCommand({ "type": "unload-turrets", "entities": ownedHolders }); } function leaveTurretPoints() { const entities = g_Selection.filter(entity => { let entState = GetEntityState(entity); return entState && entState.turretable && entState.turretable.holder != INVALID_ENTITY; }); Engine.PostNetworkCommand({ "type": "leave-turret", "entities": entities }); } function backToWork() { Engine.PostNetworkCommand({ "type": "back-to-work", // Filter out all entities that can't go back to work. "entities": g_Selection.filter(ent => { let state = GetEntityState(ent); return state && state.unitAI && state.unitAI.hasWorkOrders; }) }); } function removeGuard() { Engine.PostNetworkCommand({ "type": "remove-guard", // Filter out all entities that are currently guarding/escorting. "entities": g_Selection.filter(ent => { let state = GetEntityState(ent); return state && state.unitAI && state.unitAI.isGuarding; }) }); } function raiseAlert() { Engine.PostNetworkCommand({ "type": "alert-raise", "entities": g_Selection.filter(ent => { let state = GetEntityState(ent); return state && !!state.alertRaiser; }) }); } function endOfAlert() { Engine.PostNetworkCommand({ "type": "alert-end", "entities": g_Selection.filter(ent => { let state = GetEntityState(ent); return state && !!state.alertRaiser; }) }); } function turnAutoQueueOn() { Engine.PostNetworkCommand({ "type": "autoqueue-on", "entities": g_Selection.filter(ent => { let state = GetEntityState(ent); return !!state?.trainer?.entities?.length && !state.production.autoqueue; }) }); } function turnAutoQueueOff() { Engine.PostNetworkCommand({ "type": "autoqueue-off", "entities": g_Selection.filter(ent => { let state = GetEntityState(ent); return !!state?.trainer?.entities?.length && state.production.autoqueue; }) }); }