mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Add non-random building AI
-lets building arrows target the closest unit by default. -adds player control of building arrows. -adds a tooltip to explain how to control arrows. -Uses "F" for focus fire and concurrently use "F" for force-attack, since "C" is capture.
This commit is contained in:
parent
bae50fe4f7
commit
7c95b6700b
12 changed files with 183 additions and 27 deletions
|
|
@ -237,7 +237,7 @@ quickload = "Shift+F8"
|
|||
|
||||
[hotkey.camera]
|
||||
reset = "R" ; Reset camera rotation to default.
|
||||
follow = "F" ; Follow the first unit in the selection
|
||||
follow = "" ; Follow the first unit in the selection
|
||||
rallypointfocus = "" ; Focus the camera on the rally point of the selected building
|
||||
lastattackfocus = "Space" ; Focus the camera on the last notified attack
|
||||
zoom.in = Plus, NumPlus ; Zoom camera in (continuous control)
|
||||
|
|
@ -351,7 +351,7 @@ unloadturrets = "U" ; Unload turreted units.
|
|||
leaveturret = "U" ; Leave turret point.
|
||||
move = "" ; Modifier to move to a point instead of another action (e.g. gather)
|
||||
capture = "C" ; Modifier to capture instead of another action (e.g. attack)
|
||||
attack = "" ; Modifier to attack instead of another action (e.g. capture)
|
||||
attack = "F" ; Modifier to attack instead of another action (e.g. capture)
|
||||
attackmove = Ctrl ; Modifier to attackmove when clicking on a point
|
||||
attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when clicking on a point
|
||||
garrison = Ctrl ; Modifier to garrison when clicking on building
|
||||
|
|
@ -377,6 +377,7 @@ toggledefaultformation = "" ; Switch between null default formation and the las
|
|||
flare = K ; Modifier to send a flare to your allies
|
||||
flareactivate = "" ; Modifier to activate the mode to send a flare to your allies
|
||||
calltoarms = "" ; Modifier to call the selected units to the arms.
|
||||
focusfire = "F" ; Modifier to control exclusively a building's arrows if it can attack
|
||||
; Overlays
|
||||
showstatusbars = Tab ; Toggle display of status bars
|
||||
devcommands.toggle = "Alt+D" ; Toggle developer commands panel
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5095290f717f89b2f00a45dac4e4bbafc2c12879e3b50897e57e384e7efec3ee
|
||||
size 1690
|
||||
|
|
@ -0,0 +1 @@
|
|||
1 1
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:88f38275bfb85bb5e724d974e639d71f94c5625f2e21ec0a9fbbe1636399fbe2
|
||||
size 522853
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="no" ?>
|
||||
<SoundGroup>
|
||||
<Gain>10</Gain>
|
||||
<Priority>1</Priority>
|
||||
<Looping>0</Looping>
|
||||
<RandOrder>1</RandOrder>
|
||||
<RandPitch>1</RandPitch>
|
||||
<PitchUpper>1.2</PitchUpper>
|
||||
<PitchLower>0.8</PitchLower>
|
||||
<Threshold>500</Threshold>
|
||||
<Path>audio/attack/weapon/</Path>
|
||||
<Sound>sling_210.ogg</Sound>
|
||||
</SoundGroup>
|
||||
|
|
@ -182,6 +182,10 @@
|
|||
"session.calltoarms": {
|
||||
"name": "Call to arms",
|
||||
"desc": "Send the selected units on attack move to the specified location after dropping resources."
|
||||
},
|
||||
"session.focusfire": {
|
||||
"name": "Focus Fire",
|
||||
"desc": "Exclusively control a building's arrows without setting a rallypoint."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
CONTROLLING BUILDINGS
|
||||
You can control a building's arrows independently from its rally point:
|
||||
Right-click with the hotkey "Focus Fire" ("F" by default) to direct all arrow fire to a target unit or building. (Left)
|
||||
Right-click alone to set the rally point on a unit. (Right)
|
||||
|
|
@ -41,6 +41,12 @@
|
|||
"briton_war_dog.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"textFile": "building_control.txt",
|
||||
"imageFiles": [
|
||||
"building_control.png"
|
||||
]
|
||||
},
|
||||
{
|
||||
"textFile": "carth_sacred_band.txt",
|
||||
"imageFiles": [
|
||||
|
|
|
|||
|
|
@ -1027,6 +1027,86 @@ var g_UnitActions =
|
|||
"specificness": 41,
|
||||
},
|
||||
|
||||
"focus-fire":
|
||||
{
|
||||
"execute": function(position, action, selection, queued, pushFront)
|
||||
{
|
||||
Engine.PostNetworkCommand({
|
||||
"type": "focus-fire",
|
||||
"entities": selection,
|
||||
"x": position.x,
|
||||
"z": position.z,
|
||||
"target": action.target,
|
||||
"data": action.data,
|
||||
"queued": queued,
|
||||
"pushFront": pushFront
|
||||
});
|
||||
|
||||
if (action.data.sound)
|
||||
Engine.GuiInterfaceCall("PlaySound", {
|
||||
"name": "focus_fire",
|
||||
"entity": action.firstAbleEntity
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
"getActionInfo": function(entState, targetState)
|
||||
{
|
||||
if (!entState.rallyPoint)
|
||||
return false;
|
||||
|
||||
let tooltip;
|
||||
let data = { "command": "walk" };
|
||||
let cursor = "";
|
||||
data.sound = false;
|
||||
|
||||
if (entState.attack && Engine.HotkeyIsPressed("session.focusfire"))
|
||||
{
|
||||
cursor = "action-target";
|
||||
data.command = "attack-only";
|
||||
if (targetState && playerCheck(entState, targetState, ["Enemy"]) && !targetState.resourceSupply)
|
||||
{
|
||||
data.target = targetState.id;
|
||||
data.sound = true;
|
||||
}
|
||||
return {
|
||||
"possible": true,
|
||||
"data": data,
|
||||
"position": targetState && targetState.position,
|
||||
"cursor": cursor,
|
||||
"tooltip": tooltip
|
||||
};
|
||||
}
|
||||
},
|
||||
"hotkeyActionCheck": function(target, selection)
|
||||
{
|
||||
// Hotkeys are checked in the actionInfo.
|
||||
return this.actionCheck(target, selection);
|
||||
},
|
||||
"actionCheck": function(target, selection)
|
||||
{
|
||||
// We want commands to units take precedence.
|
||||
if (selection.some(ent => {
|
||||
let entState = GetEntityState(ent);
|
||||
return entState && !!entState.unitAI;
|
||||
}))
|
||||
return false;
|
||||
|
||||
let actionInfo = getActionInfo("focus-fire", target, selection);
|
||||
|
||||
return actionInfo.possible && {
|
||||
"type": "focus-fire",
|
||||
"cursor": actionInfo.cursor,
|
||||
"data": actionInfo.data,
|
||||
"target": target,
|
||||
"tooltip": actionInfo.tooltip,
|
||||
"position": actionInfo.position,
|
||||
"firstAbleEntity": actionInfo.entity
|
||||
};
|
||||
},
|
||||
"specificness": 6,
|
||||
},
|
||||
|
||||
"set-rallypoint":
|
||||
{
|
||||
"execute": function(position, action, selection, queued, pushFront)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Number of rounds of firing per 2 seconds.
|
||||
const roundCount = 10;
|
||||
const roundCount = 20;
|
||||
const attackType = "Ranged";
|
||||
|
||||
function BuildingAI() {}
|
||||
|
|
@ -28,6 +28,7 @@ BuildingAI.prototype.Init = function()
|
|||
this.archersGarrisoned = 0;
|
||||
this.arrowsLeft = 0;
|
||||
this.targetUnits = [];
|
||||
this.focusTargets = [];
|
||||
};
|
||||
|
||||
BuildingAI.prototype.OnGarrisonedUnitsChanged = function(msg)
|
||||
|
|
@ -50,6 +51,7 @@ BuildingAI.prototype.OnGarrisonedUnitsChanged = function(msg)
|
|||
BuildingAI.prototype.OnOwnershipChanged = function(msg)
|
||||
{
|
||||
this.targetUnits = [];
|
||||
this.focusTargets = [];
|
||||
this.SetupRangeQuery();
|
||||
this.SetupGaiaRangeQuery();
|
||||
};
|
||||
|
|
@ -266,6 +268,22 @@ BuildingAI.prototype.SetUnitAITarget = function(ent)
|
|||
this.StartTimer();
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds index to keep track of the user-targeted units supporting a queue
|
||||
* @param {ent} - Target of focus-fire from unit-actions if the selection is an enemy.
|
||||
*/
|
||||
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}];
|
||||
};
|
||||
|
||||
/**
|
||||
* Fire arrows with random temporal distribution on prefered targets.
|
||||
* Called 'roundCount' times every 'RepeatTime' seconds when there are units in the range.
|
||||
|
|
@ -298,8 +316,9 @@ BuildingAI.prototype.FireArrows = function()
|
|||
arrowsToFire = this.arrowsLeft;
|
||||
else
|
||||
arrowsToFire = Math.min(
|
||||
randIntInclusive(0, 2 * this.GetArrowCount() / roundCount),
|
||||
this.arrowsLeft
|
||||
// shooting arrows in the first quarter of rounds results in a burst.
|
||||
this.GetArrowCount() / (roundCount / 4),
|
||||
this.arrowsLeft
|
||||
);
|
||||
|
||||
if (arrowsToFire <= 0)
|
||||
|
|
@ -308,25 +327,38 @@ BuildingAI.prototype.FireArrows = function()
|
|||
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 = (cmpAttack.GetPreference(target) ?? 49);
|
||||
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);
|
||||
|
||||
else if (this.unitAITarget && this.targetUnits.indexOf(this.unitAITarget) != -1)
|
||||
this.focusTargets = [{"entityId": 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) => {
|
||||
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;
|
||||
});
|
||||
}
|
||||
else
|
||||
targets = this.focusTargets;
|
||||
|
||||
// The obstruction manager performs approximate range checks.
|
||||
// so we need to verify them here.
|
||||
|
|
@ -335,10 +367,12 @@ BuildingAI.prototype.FireArrows = function()
|
|||
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;
|
||||
while (firedArrows < arrowsToFire && targetIndex < targets.length)
|
||||
{
|
||||
|
||||
let selectedTarget = targets[targetIndex].entityId;
|
||||
if (this.CheckTargetVisible(selectedTarget) && cmpObstructionManager.IsInTargetParabolicRange(
|
||||
this.entity,
|
||||
selectedTarget,
|
||||
|
|
@ -350,13 +384,11 @@ BuildingAI.prototype.FireArrows = function()
|
|||
cmpAttack.PerformAttack(attackType, selectedTarget);
|
||||
PlaySound("attack_" + attackType.toLowerCase(), this.entity);
|
||||
++firedArrows;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Could not attack target, try a different target.
|
||||
targets.remove(selectedTarget);
|
||||
else
|
||||
++targetIndex;// Could not attack target, try a different target.
|
||||
}
|
||||
|
||||
targets.splice(0, targetIndex);
|
||||
this.arrowsLeft -= firedArrows;
|
||||
++this.currentRound;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -431,6 +431,14 @@ var g_Commands = {
|
|||
}
|
||||
},
|
||||
|
||||
"focus-fire": function (player, cmd, data)
|
||||
{
|
||||
for (let ent of data.entities)
|
||||
{
|
||||
Engine.QueryInterface(ent, IID_BuildingAI)?.AddFocusTarget(cmd.target, cmd.queued, cmd.pushFront);
|
||||
}
|
||||
},
|
||||
|
||||
"set-rallypoint": function(player, cmd, data)
|
||||
{
|
||||
for (let ent of data.entities)
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@
|
|||
<attacked_gaia>interface/alarm/alarm_attacked_gaia.xml</attacked_gaia>
|
||||
<attacked_capture>interface/alarm/alarm_attackplayer.xml</attacked_capture>
|
||||
<attacked_capture_gaia>interface/alarm/alarm_attacked_gaia.xml</attacked_capture_gaia>
|
||||
<focus_fire>attack/weapon/bow_launch_building.xml</focus_fire>
|
||||
</SoundGroups>
|
||||
</Sound>
|
||||
<StatusBars>
|
||||
|
|
|
|||
Loading…
Reference in a new issue