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 @@ -251,6 +251,34 @@ "specificness": 9, }, + "building-attack": { + "execute": function(target, action, selection, queued, pushFront) + { + Engine.PostNetworkCommand({ + "type": "building-attack", + "entities": selection, + "target": target, + "queued": queued, + "pushFront": pushFront + }); + + Engine.GuiInterfaceCall("PlaySound", { + "name": "order_attack", + "entity": selection[0] + }); + return true; + }, + "getActionInfo": function(entState, targetState) + { + if (!entState.buildingAI || !targetState || !targetState.hitpoints || entState.speed) + return false; + return { + "possible": true + }; + }, + "specificness": 50, + }, + "call-to-arms": { "execute": function(position, action, selection, queued, pushFront) { @@ -1201,6 +1229,8 @@ } else if (targetState && playerCheck(entState, targetState, ["Enemy"])) { + if (entState.buildingAI && !Engine.HotkeyIsPressed("session.autorallypoint")) + return false; data.target = targetState.id; data.command = "attack"; if (targetState.hitpoints) 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 @@ -28,6 +28,7 @@ this.archersGarrisoned = 0; this.arrowsLeft = 0; this.targetUnits = []; + this.focusTarget = []; }; BuildingAI.prototype.OnGarrisonedUnitsChanged = function(msg) @@ -267,6 +268,25 @@ }; /** + * Sets the index to keep track of the user-targeted unit + * This value returns to 0 when the unit leaves + */ +BuildingAI.prototype.SetFocusTarget = function(ent, queued, push) +{ + if (ent !== false && this.targetUnits.indexOf(ent) !== -1) + { + if (queued) + this.focusTarget.push(ent); + else if (push) + this.focusTarget.unshift(ent); + else + this.focusTarget = [ent]; + } + else + this.focusTarget = []; +}; + +/** * Fire arrows with random temporal distribution on prefered targets. * Called 'roundCount' times every 'RepeatTime' seconds when there are units in the range. */ @@ -308,25 +328,20 @@ 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 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 = target === this.focusTarget[0] ? 0 : + (cmpAttack.GetPreference(target) ?? 49) + 1; + targets.push({"entityId": target, "preference": pref}); + //Need to make space for pref 0, which will be the user selected target. + }.bind(this); // Add the UnitAI target separately, as the UnitMotion and RangeManager implementations differ. if (this.unitAITarget && this.targetUnits.indexOf(this.unitAITarget) == -1) - addTarget(this.unitAITarget); + addTarget(this.unitAITarget); for (let target of this.targetUnits) - addTarget(target); + addTarget(target); // The obstruction manager performs approximate range checks. // so we need to verify them here. @@ -335,10 +350,18 @@ 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; + // Sort targets by preference and then by proximity. + targets.sort( (a,b) => { + if (a.preference > b.preference) return 1; + else if (a.preference < b.preference) return -1; + else if (PositionHelper.DistanceBetweenEntities(this.entity,a.entityId) > PositionHelper.DistanceBetweenEntities(this.entity,b.entityId)) return 1; + return -1; + }); + + while (firedArrows < arrowsToFire && targets.length > 0) + { + let selectedTarget = targets[0].entityId if (this.CheckTargetVisible(selectedTarget) && cmpObstructionManager.IsInTargetParabolicRange( this.entity, selectedTarget, @@ -352,11 +375,14 @@ ++firedArrows; continue; } - + else + { // Could not attack target, try a different target. - targets.remove(selectedTarget); + targets.splice(0,1); + if (this.focusTarget) + this.focusTarget.splice(0,1); + } } - 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 @@ -199,11 +199,25 @@ "attack": function(player, cmd, data) { + for (let ent of data.entities){ + let checkBuildingAI = Engine.QueryInterface(ent, IID_BuildingAI); + if (checkBuildingAI) + checkBuildingAI.SetFocusTarget(cmd.target, cmd.queued, cmd.pushFront); + } GetFormationUnitAIs(data.entities, player, cmd, data.formation).forEach(cmpUnitAI => { cmpUnitAI.Attack(cmd.target, cmd.allowCapture, cmd.queued, cmd.pushFront); }); }, + "building-attack": function(player, cmd, data) + { + for (let ent of data.entities){ + let checkBuildingAI = Engine.QueryInterface(ent, IID_BuildingAI); + if (checkBuildingAI) + checkBuildingAI.SetFocusTarget(cmd.target, cmd.queued, cmd.pushFront); + } + }, + "patrol": function(player, cmd, data) { GetFormationUnitAIs(data.entities, player, cmd, data.formation).forEach(cmpUnitAI => 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 + 3 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 + 4 50 false