Index: binaries/data/mods/public/gui/session/unit_actions.js =================================================================== --- binaries/data/mods/public/gui/session/unit_actions.js +++ binaries/data/mods/public/gui/session/unit_actions.js @@ -1041,8 +1041,10 @@ "entities": selection, "x": position.x, "z": position.z, + "target": action.target, "data": action.data, - "queued": queued + "queued": queued, + "pushFront": pushFront }); // Display rally point at the new coordinates, to avoid display lag @@ -1246,11 +1248,13 @@ "target": null, "tooltip": actionInfo.tooltip }; - + if (Engine.HotkeyIsPressed("session.autorallypoint")) + target = null; return actionInfo.possible && { "type": "set-rallypoint", "cursor": actionInfo.cursor, "data": actionInfo.data, + "target": target, "tooltip": actionInfo.tooltip, "position": actionInfo.position, "firstAbleEntity": actionInfo.entity Index: binaries/data/mods/public/simulation/components/BuildingAI.js =================================================================== --- binaries/data/mods/public/simulation/components/BuildingAI.js +++ binaries/data/mods/public/simulation/components/BuildingAI.js @@ -20,7 +20,7 @@ "" + ""; -BuildingAI.prototype.MAX_PREFERENCE_BONUS = 2; +BuildingAI.prototype.MIN_PREFERENCE = 50; BuildingAI.prototype.Init = function() { @@ -28,6 +28,7 @@ this.archersGarrisoned = 0; this.arrowsLeft = 0; this.targetUnits = []; + this.focusTargets = []; }; BuildingAI.prototype.OnGarrisonedUnitsChanged = function(msg) @@ -50,6 +51,7 @@ BuildingAI.prototype.OnOwnershipChanged = function(msg) { this.targetUnits = []; + this.focusTargets = []; this.SetupRangeQuery(); this.SetupGaiaRangeQuery(); }; @@ -267,6 +269,24 @@ }; /** + * Adds index to keep track of the user-targeted units supporting a queue + * @param {ent} - Target of rallypoint selection when selection is an enemy unit from unit_actions.js + */ +BuildingAI.prototype.AddFocusTarget = function(ent, queued, push) +{ + if (!ent || this.targetUnits.indexOf(ent) === -1) + return; + if (queued) + this.focusTargets.push({"entityId": ent}); + else if (push) + this.focusTargets.unshift({"entityId": ent}); + else + this.focusTargets = [{"entityId": ent}]; + +// targeted entities are added as objects to match the structure of targets in fireArrows. +}; + +/** * Fire arrows with random temporal distribution on prefered targets. * Called 'roundCount' times every 'RepeatTime' seconds when there are units in the range. */ @@ -308,26 +328,32 @@ return; } - // Add targets to a weighted list, to allow preferences. - let targets = new WeightedList(); - let maxPreference = this.MAX_PREFERENCE_BONUS; - let addTarget = function(target) + // Add targets to a list. + let minPreference = this.MIN_PREFERENCE; + let targets = []; + let addTarget = function(target) { - let preference = cmpAttack.GetPreference(target); - let weight = 1; - - if (preference !== null && preference !== undefined) - weight += maxPreference / (1 + preference); - - targets.push(target, weight); + const pref = cmpAttack.GetPreference(target) ?? minPreference; + targets.push({"entityId": target, "preference": pref}); }; // Add the UnitAI target separately, as the UnitMotion and RangeManager implementations differ. if (this.unitAITarget && this.targetUnits.indexOf(this.unitAITarget) == -1) - addTarget(this.unitAITarget); - for (let target of this.targetUnits) - addTarget(target); + addTarget(this.unitAITarget); + if (!this.focusTargets.length) + { + for (let target of this.targetUnits) + addTarget(target); + // Sort targets by preference and then by proximity. + targets.sort((a, b) => a.preference - b.preference || + PositionHelper.DistanceBetweenEntities(this.entity, a.entityId) - PositionHelper.DistanceBetweenEntities(this.entity, b.entityId) + ); + } + // If the user has targeted some units, skip preference and sorting, and use the focusTargets list for targeting. + else + targets = this.focusTargets; + // The obstruction manager performs approximate range checks. // so we need to verify them here. // TODO: perhaps an optional 'precise' mode to range queries would be more performant. @@ -335,10 +361,16 @@ const range = cmpAttack.GetRange(attackType); const yOrigin = cmpAttack.GetAttackYOrigin(attackType); - let firedArrows = 0; - while (firedArrows < arrowsToFire && targets.length()) - { - const selectedTarget = targets.randomItem(); + let firedArrows = 0; + let targetIndex = 0; + // targetIndex tracks the current target. + + while (firedArrows < arrowsToFire && targetIndex < targets.length) + { + // Select a target to attempt shooting. + let selectedTarget = targets[targetIndex].entityId; + + // Shooting at selectedTarget. if (this.CheckTargetVisible(selectedTarget) && cmpObstructionManager.IsInTargetParabolicRange( this.entity, selectedTarget, @@ -350,13 +382,16 @@ cmpAttack.PerformAttack(attackType, selectedTarget); PlaySound("attack_" + attackType.toLowerCase(), this.entity); ++firedArrows; - continue; } - - // Could not attack target, try a different target. - targets.remove(selectedTarget); + // If target is already killed or otherwise missed, increment targetIndex to shoot the next target. + else + { + // Could not attack target, try a different target. + ++targetIndex; + } } - + // After firing is complete, remove killed/missed targets. Targets are also removed from this.focusTargets. + targets.splice(0, targetIndex); this.arrowsLeft -= firedArrows; ++this.currentRound; }; Index: binaries/data/mods/public/simulation/helpers/Commands.js =================================================================== --- binaries/data/mods/public/simulation/helpers/Commands.js +++ binaries/data/mods/public/simulation/helpers/Commands.js @@ -435,6 +435,8 @@ { for (let ent of data.entities) { + if (cmd.data.command == "attack" && cmd.data.target) + Engine.QueryInterface(ent, IID_BuildingAI)?.AddFocusTarget(cmd.target, cmd.queued, cmd.pushFront); var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint); if (cmpRallyPoint) { Index: binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml +++ binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml @@ -17,7 +17,7 @@ 2000 100 - 1.5 + 2 50 false Index: binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml +++ binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml @@ -11,7 +11,7 @@ 2000 100 - 1.5 + 3 50 false