Former Site Admin
Posts: 3814
Joined: 08 Jan 2009, 23:00
Location: California - Pacific Time (UTC -8/-7 Summer Time)
Former Site Admin
Posts: 3814
Joined: 08 Jan 2009, 23:00
Location: California - Pacific Time (UTC -8/-7 Summer Time)
@Everyone
Esthlos made a very good script to fulfil guideline number 5.
Look at how it works:
Former Site Admin
Posts: 3814
Joined: 08 Jan 2009, 23:00
Location: California - Pacific Time (UTC -8/-7 Summer Time)
I did a test of this scripted TSK 08, and I don't think this is realistic. It just makes sucking the life out of the AI from within possible.
What? Where? How?I did a test of this scripted TSK 08, and I don't think this is realistic. It just makes sucking the life out of the AI from within possible.
I attached a replay in the very post you quoted. You didn't see it?What? Where? How?I did a test of this scripted TSK 08, and I don't think this is realistic. It just makes sucking the life out of the AI from within possible.
//Customizable settings
const
aDesistRange = 625; //It is a squared value; the distance from the original attack after which the guards will give up
aGuardsRatio = 2; //Number of dispatched guards per intruder; has to be an integer
aMountedIsBetter = 1.5; //Ratio of the advantage that mounted units have when looking for a new guard
aGuardsCooldown = 5; //Each how many skipped Ticks is the system managed; has to be an integer; different intruders are checked in different Ticks anyway if they are too many
aPreventAmbush = True; //If True, the AI will try to predict if the player infiltrated an ambush party too
aPreventAmbushRange = 625; //It is a squared value; the distance up to which a soldier will be considered part of an ambush party
//Global variables
var
iIntruders: array of Record aID, aOrigX, aOrigY, aAttackedPlayer: Integer; aDispGuards: array of Integer; end;
aInvolvedAIPlayers: array of Integer; //UnitIDs
iSkipTicks, aGuardsTotal, iCurrIntruder: Integer;
procedure SetAIs; //Which players will use this system
begin
aInvolvedAIPlayers := [1, 2, 3]; //Format: [Player1ID, Player2ID, Player3ID] if multiple players or [Player1ID] if only one player; PlayerID is (player number)-1; allies will defend each other if closest to the attack
end;
function aGetDistance(aX1, aX2, aY1, aY2: Integer): Integer; //Shared function
begin
//No point in doing the square root too
result := (aX1-aX2)*(aX1-aX2)+(aY1-aY2)*(aY1-aY2);
end;
function aCheckUnitIsMilitary(aUnitID: Integer): Boolean;
var
iHouses: array of Integer;
i: Integer;
begin
result := True;
if (States.UnitDead(aUnitID)) OR (States.UnitType(aUnitID) < 0) OR (States.UnitType(aUnitID) > 12) then Exit;
if (States.UnitType(aUnitID) = 0) OR (States.UnitType(aUnitID) = 9) then begin
iHouses := States.PlayerGetAllHouses(States.UnitOwner(aUnitID));
for i := 0 to Length(iHouses)-1 do if States.HouseType(iHouses[i]) = 17 then if aGetDistance(States.HousePositionX(iHouses[i]), States.UnitPositionX(aUnitID),States.HousePositionY(iHouses[i]), States.UnitPositionY(aUnitID)) <= 9 then Exit;
end;
result := False;
end;
function aCheckIsGuard(aWarrior: Integer): Boolean; //Check if the found soldier is already a guard
var
i, i2: Integer;
begin
result := False;
if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do begin
if Length(iIntruders[i].aDispGuards) > 0 then for i2 := 0 to Length(iIntruders[i].aDispGuards)-1 do if aWarrior = iIntruders[i].aDispGuards[i2] then begin
result := True;
Exit;
end;
end;
end;
procedure aRecruitGuards(aIndex: Integer);
var
i, i2, iDistance, iDistanceBest, iChosen, iCount, iCountNeeded: Integer;
iGroups: array of Integer;
begin
//Check assigned guards
iCountNeeded := aGuardsRatio-Length(iIntruders[aIndex].aDispGuards);
if iCountNeeded = 0 then Exit;
if iCountNeeded < 0 then begin
SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)+iCountNeeded);
aGuardsTotal := aGuardsTotal+iCountNeeded;
Exit;
end;
//Abort if the number if AI soldiers is too low; not really needed, but saves processing power
iCount := 0;
for i := 0 to Length(aInvolvedAIPlayers)-1 do if States.PlayerEnabled(aInvolvedAIPlayers[i]) AND States.PlayerAllianceCheck(aInvolvedAIPlayers[i], iIntruders[aIndex].aAttackedPlayer) then begin
iCount := iCount+States.StatUnitMultipleTypesCount(aInvolvedAIPlayers[i], [14, 15, 16, 19, 20, 21, 22, 23, 25, 26, 27]);
end;
if iCount < aGuardsTotal then Exit;
//Find closest group to the intruder
iChosen := -1;
iDistanceBest := -1;
for i := 0 to Length(aInvolvedAIPlayers)-1 do if States.PlayerEnabled(aInvolvedAIPlayers[i]) AND States.PlayerAllianceCheck(aInvolvedAIPlayers[i], iIntruders[aIndex].aAttackedPlayer) then begin
iGroups := States.PlayerGetAllGroups(aInvolvedAIPlayers[i]);
if Length(iGroups) > 0 then for i2 := 0 to Length(iGroups)-1 do if States.GroupType(iGroups[i2]) <> 2 then begin //Melee guards only; if no melee is found, then the AI has more pressing problems than those intruders
iDistance := aGetDistance(States.UnitPositionX(iIntruders[aIndex].aID), States.UnitPositionX(States.GroupMember(iGroups[i2], 0)), States.UnitPositionY(iIntruders[aIndex].aID), States.UnitPositionY(States.GroupMember(iGroups[i2], 0)));
if States.GroupType(iGroups[i2]) <> 3 then iDistance := Round(iDistance*aMountedIsBetter*aMountedIsBetter);
if (iDistanceBest < 0) OR (iDistance < iDistanceBest) then begin
if (not aCheckIsGuard(States.GroupMember(iGroups[i2], 0))) then begin
iDistanceBest := iDistance;
iChosen := iGroups[i2];
end;
end;
end;
end;
if iChosen > 0 then for i := 0 to iCountNeeded-1 do if i < States.GroupMemberCount(iChosen) then begin
SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)+1);
iIntruders[aIndex].aDispGuards[Length(iIntruders[aIndex].aDispGuards)-1] := States.GroupMember(iChosen, i);
Inc(aGuardsTotal);
end;
end;
procedure aAttackIntruder(aIndex: Integer);
var
i, i2, iCount, iGroup: Integer;
begin
if Length(iIntruders[aIndex].aDispGuards) > 0 then begin
iCount := 0;
for i := 0 to Length(iIntruders[aIndex].aDispGuards)-1 do if States.UnitDead(iIntruders[aIndex].aDispGuards[i-iCount]) then begin
for i2 := i-iCount to Length(iIntruders[aIndex].aDispGuards)-2 do iIntruders[aIndex].aDispGuards[i2] := iIntruders[aIndex].aDispGuards[i2+1];
Inc(iCount);
end else begin
iGroup := States.UnitsGroup(iIntruders[aIndex].aDispGuards[i-iCount]);
if States.GroupMemberCount(iGroup) > 1 then begin
iGroup := Actions.GroupOrderSplitUnit(iGroup, iIntruders[aIndex].aDispGuards[i-iCount]);
Actions.GroupOrderAttackUnit(iGroup, iIntruders[aIndex].aID);
end else if States.GroupIdle(iGroup) then Actions.GroupOrderAttackUnit(iGroup, iIntruders[aIndex].aID);
end;
if iCount > 0 then begin
aGuardsTotal := aGuardsTotal-iCount;
SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)-iCount);
end;
end;
end;
procedure aInitAIDispatchSystem; //To be placed OnMissionStart;
begin
SetLength(iIntruders, 0);
aGuardsTotal := 0;
iCurrIntruder := 0;
SetAIs;
end;
procedure aManageAIDispatchSystem; //To be placed OnTick;
var
i: Integer;
begin
if Length(iIntruders) > 0 then begin
if iSkipTicks < 0 then iSkipTicks := 0;
if iSkipTicks > 0 then begin
iSkipTicks := iSkipTicks-1;
Exit;
end;
if (iCurrIntruder > Length(iIntruders)-1) OR (iCurrIntruder < 0) then iCurrIntruder := 0;
//Check if Intruders have fled or are dead, and if there are enough guards
if (States.UnitDead(iIntruders[iCurrIntruder].aID)) OR (aGetDistance(iIntruders[iCurrIntruder].aOrigX, States.UnitPositionX(iIntruders[iCurrIntruder].aID), iIntruders[iCurrIntruder].aOrigY, States.UnitPositionY(iIntruders[iCurrIntruder].aID)) > aDesistRange) then begin
aGuardsTotal := aGuardsTotal-Length(iIntruders[iCurrIntruder].aDispGuards);
for i := 0 to Length(iIntruders[iCurrIntruder].aDispGuards)-1 do if not States.UnitDead(iIntruders[iCurrIntruder].aDispGuards[i]) then Actions.GroupOrderHalt(States.UnitsGroup(iIntruders[iCurrIntruder].aDispGuards[i]));
for i := iCurrIntruder to Length(iIntruders)-2 do iIntruders[i] := iIntruders[i+1];
SetLength(iIntruders, Length(iIntruders)-1);
iCurrIntruder := iCurrIntruder-1;
end else begin
aAttackIntruder(iCurrIntruder); //Also checks if the guards are dead; for this reason, it's better to do this before the recruiting
aRecruitGuards(iCurrIntruder); //Recruit or dismiss guards
end;
Inc(iCurrIntruder);
iSkipTicks := aGuardsCooldown-Length(iIntruders);
end;
end;
procedure aAlarm(aIntruder, aPlayer: Integer; aIsMilitary, aCheckForAmbush: Boolean); //To be placed OnUnitAttacked and OnHouseDamaged
var
i: Integer;
iUnits: array of Integer;
aAbort: Boolean;
begin
if aIsMilitary then Exit;
aAbort := True;
if Length(aInvolvedAIPlayers) > 0 then begin for i := 0 to Length(aInvolvedAIPlayers)-1 do if aInvolvedAIPlayers[i] = aPlayer then aAbort := False;
end else Exit;
if aAbort then Exit;
if States.UnitDead(aIntruder) then Exit;
if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do if iIntruders[i].aID = aIntruder then begin
iIntruders[i].aOrigX := States.UnitPositionX(aIntruder);
iIntruders[i].aOrigY := States.UnitPositionY(aIntruder);
Exit;
end;
SetLength(iIntruders, Length(iIntruders)+1);
iIntruders[Length(iIntruders)-1].aID := aIntruder;
iIntruders[Length(iIntruders)-1].aOrigX := States.UnitPositionX(aIntruder);
iIntruders[Length(iIntruders)-1].aOrigY := States.UnitPositionY(aIntruder);
iIntruders[Length(iIntruders)-1].aAttackedPlayer := aPlayer;
SetLength(iIntruders[Length(iIntruders)-1].aDispGuards, 0);
aRecruitGuards(Length(iIntruders)-1); //Recruit the first guards
aAttackIntruder(Length(iIntruders)-1); //And use them
if aCheckForAmbush then begin
iUnits := States.PlayerGetAllUnits(States.UnitOwner(aIntruder));
for i := 0 to Length(iUnits)-1 do if States.UnitType(iUnits[i]) >= 14 then if aGetDistance(States.UnitPositionX(aIntruder), States.UnitPositionX(iUnits[i]), States.UnitPositionY(aIntruder), States.UnitPositionY(iUnits[i])) <= aPreventAmbushRange then aAlarm(iUnits[i], aPlayer, False, False);
end;
end;
procedure OnMissionStart;
begin
aInitAIDispatchSystem;
end;
procedure OnUnitAttacked(aUnitID, AttackerID: Integer);
begin
aAlarm(AttackerID, States.UnitOwner(aUnitID), aCheckUnitIsMilitary(aUnitID), aPreventAmbush);
end;
procedure OnHouseDamaged(aHouseID, AttackerID: Integer);
begin
aAlarm(AttackerID, States.HouseOwner(aHouseID), (States.HouseType(aHouseID) = 17), aPreventAmbush);
end;
procedure OnTick;
begin
aManageAIDispatchSystem;
end;
1) adjusting old build order (build order like we all remember it)
2) allowing fisherman (if yes, then I'll edit the number of fish in each mission)
3) allowing market (with proper balancing and logic thinking - no knights in second mission TDL )
4) rebuilding AI cities (this is really needed, since AI cities are not working properly in Remake - iron distribution, farms, number of fields,...)
5) complete reworking of AI defence positions (I've already made some changes, but it's still not perfect)
6) pre-building AI cities (temporary)
7) script for infinite ores for AI (better than having it in storehouse - traffic jams, script will stop working if mine is destroyed)
8) changing colors to be close to original (I already changed colors in my 1.0 patch)
9) making impossible to sneak in and destroy AI city (with Esthlos's dynamic script)
10) making impossible to defeat any AI with starting army (normal strategy, not with cmowlas tactic, example is mission 4 where I added some troops to enemy)
11) changing number of starting AI troops
How is it not realistic that you can bring an ambush party and use a single assaliant to lure the guards and kill them in a dark alley?I attached a replay in the very post you quoted. You didn't see it?What? Where? How?
Anyway, if you prefer it this way, this code will detect the presence of an ambush party too:
(make sure to tell the script which players to use this script for, by editing "aInvolvedAIPlayers"; the following code is already set to apply to all of TSK08's AIs)
- Code:
//Customizable settings const aDesistRange = 625; //It is a squared value; the distance from the original attack after which the guards will give up aGuardsRatio = 2; //Number of dispatched guards per intruder; has to be an integer aMountedIsBetter = 1.5; //Ratio of the advantage that mounted units have when looking for a new guard aGuardsCooldown = 5; //Each how many skipped Ticks is the system managed; has to be an integer; different intruders are checked in different Ticks anyway if they are too many aPreventAmbush = True; //If True, the AI will try to predict if the player infiltrated an ambush party too aPreventAmbushRange = 625; //It is a squared value; the distance up to which a soldier will be considered part of an ambush party //Global variables var iIntruders: array of Record aID, aOrigX, aOrigY, aAttackedPlayer: Integer; aDispGuards: array of Integer; end; aInvolvedAIPlayers: array of Integer; //UnitIDs iSkipTicks, aGuardsTotal, iCurrIntruder: Integer; procedure SetAIs; //Which players will use this system begin aInvolvedAIPlayers := [1, 2, 3]; //Format: [Player1ID, Player2ID, Player3ID] if multiple players or [Player1ID] if only one player; PlayerID is (player number)-1; allies will defend each other if closest to the attack end; function aGetDistance(aX1, aX2, aY1, aY2: Integer): Integer; //Shared function begin //No point in doing the square root too result := (aX1-aX2)*(aX1-aX2)+(aY1-aY2)*(aY1-aY2); end; function aCheckUnitIsMilitary(aUnitID: Integer): Boolean; var iHouses: array of Integer; i: Integer; begin result := True; if (States.UnitDead(aUnitID)) OR (States.UnitType(aUnitID) < 0) OR (States.UnitType(aUnitID) > 12) then Exit; if (States.UnitType(aUnitID) = 0) OR (States.UnitType(aUnitID) = 9) then begin iHouses := States.PlayerGetAllHouses(States.UnitOwner(aUnitID)); for i := 0 to Length(iHouses)-1 do if States.HouseType(iHouses[i]) = 17 then if aGetDistance(States.HousePositionX(iHouses[i]), States.UnitPositionX(aUnitID),States.HousePositionY(iHouses[i]), States.UnitPositionY(aUnitID)) <= 9 then Exit; end; result := False; end; function aCheckIsGuard(aWarrior: Integer): Boolean; //Check if the found soldier is already a guard var i, i2: Integer; begin result := False; if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do begin if Length(iIntruders[i].aDispGuards) > 0 then for i2 := 0 to Length(iIntruders[i].aDispGuards)-1 do if aWarrior = iIntruders[i].aDispGuards[i2] then begin result := True; Exit; end; end; end; procedure aRecruitGuards(aIndex: Integer); var i, i2, iDistance, iDistanceBest, iChosen, iCount, iCountNeeded: Integer; iGroups: array of Integer; begin //Check assigned guards iCountNeeded := aGuardsRatio-Length(iIntruders[aIndex].aDispGuards); if iCountNeeded = 0 then Exit; if iCountNeeded < 0 then begin SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)+iCountNeeded); aGuardsTotal := aGuardsTotal+iCountNeeded; Exit; end; //Abort if the number if AI soldiers is too low; not really needed, but saves processing power iCount := 0; for i := 0 to Length(aInvolvedAIPlayers)-1 do if States.PlayerEnabled(aInvolvedAIPlayers[i]) AND States.PlayerAllianceCheck(aInvolvedAIPlayers[i], iIntruders[aIndex].aAttackedPlayer) then begin iCount := iCount+States.StatUnitMultipleTypesCount(aInvolvedAIPlayers[i], [14, 15, 16, 19, 20, 21, 22, 23, 25, 26, 27]); end; if iCount < aGuardsTotal then Exit; //Find closest group to the intruder iChosen := -1; iDistanceBest := -1; for i := 0 to Length(aInvolvedAIPlayers)-1 do if States.PlayerEnabled(aInvolvedAIPlayers[i]) AND States.PlayerAllianceCheck(aInvolvedAIPlayers[i], iIntruders[aIndex].aAttackedPlayer) then begin iGroups := States.PlayerGetAllGroups(aInvolvedAIPlayers[i]); if Length(iGroups) > 0 then for i2 := 0 to Length(iGroups)-1 do if States.GroupType(iGroups[i2]) <> 2 then begin //Melee guards only; if no melee is found, then the AI has more pressing problems than those intruders iDistance := aGetDistance(States.UnitPositionX(iIntruders[aIndex].aID), States.UnitPositionX(States.GroupMember(iGroups[i2], 0)), States.UnitPositionY(iIntruders[aIndex].aID), States.UnitPositionY(States.GroupMember(iGroups[i2], 0))); if States.GroupType(iGroups[i2]) <> 3 then iDistance := Round(iDistance*aMountedIsBetter*aMountedIsBetter); if (iDistanceBest < 0) OR (iDistance < iDistanceBest) then begin if (not aCheckIsGuard(States.GroupMember(iGroups[i2], 0))) then begin iDistanceBest := iDistance; iChosen := iGroups[i2]; end; end; end; end; if iChosen > 0 then for i := 0 to iCountNeeded-1 do if i < States.GroupMemberCount(iChosen) then begin SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)+1); iIntruders[aIndex].aDispGuards[Length(iIntruders[aIndex].aDispGuards)-1] := States.GroupMember(iChosen, i); Inc(aGuardsTotal); end; end; procedure aAttackIntruder(aIndex: Integer); var i, i2, iCount, iGroup: Integer; begin if Length(iIntruders[aIndex].aDispGuards) > 0 then begin iCount := 0; for i := 0 to Length(iIntruders[aIndex].aDispGuards)-1 do if States.UnitDead(iIntruders[aIndex].aDispGuards[i-iCount]) then begin for i2 := i-iCount to Length(iIntruders[aIndex].aDispGuards)-2 do iIntruders[aIndex].aDispGuards[i2] := iIntruders[aIndex].aDispGuards[i2+1]; Inc(iCount); end else begin iGroup := States.UnitsGroup(iIntruders[aIndex].aDispGuards[i-iCount]); if States.GroupMemberCount(iGroup) > 1 then begin iGroup := Actions.GroupOrderSplitUnit(iGroup, iIntruders[aIndex].aDispGuards[i-iCount]); Actions.GroupOrderAttackUnit(iGroup, iIntruders[aIndex].aID); end else if States.GroupIdle(iGroup) then Actions.GroupOrderAttackUnit(iGroup, iIntruders[aIndex].aID); end; if iCount > 0 then begin aGuardsTotal := aGuardsTotal-iCount; SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)-iCount); end; end; end; procedure aInitAIDispatchSystem; //To be placed OnMissionStart; begin SetLength(iIntruders, 0); aGuardsTotal := 0; iCurrIntruder := 0; SetAIs; end; procedure aManageAIDispatchSystem; //To be placed OnTick; var i: Integer; begin if Length(iIntruders) > 0 then begin if iSkipTicks < 0 then iSkipTicks := 0; if iSkipTicks > 0 then begin iSkipTicks := iSkipTicks-1; Exit; end; if (iCurrIntruder > Length(iIntruders)-1) OR (iCurrIntruder < 0) then iCurrIntruder := 0; //Check if Intruders have fled or are dead, and if there are enough guards if (States.UnitDead(iIntruders[iCurrIntruder].aID)) OR (aGetDistance(iIntruders[iCurrIntruder].aOrigX, States.UnitPositionX(iIntruders[iCurrIntruder].aID), iIntruders[iCurrIntruder].aOrigY, States.UnitPositionY(iIntruders[iCurrIntruder].aID)) > aDesistRange) then begin aGuardsTotal := aGuardsTotal-Length(iIntruders[iCurrIntruder].aDispGuards); for i := 0 to Length(iIntruders[iCurrIntruder].aDispGuards)-1 do if not States.UnitDead(iIntruders[iCurrIntruder].aDispGuards[i]) then Actions.GroupOrderHalt(States.UnitsGroup(iIntruders[iCurrIntruder].aDispGuards[i])); for i := iCurrIntruder to Length(iIntruders)-2 do iIntruders[i] := iIntruders[i+1]; SetLength(iIntruders, Length(iIntruders)-1); iCurrIntruder := iCurrIntruder-1; end else begin aAttackIntruder(iCurrIntruder); //Also checks if the guards are dead; for this reason, it's better to do this before the recruiting aRecruitGuards(iCurrIntruder); //Recruit or dismiss guards end; Inc(iCurrIntruder); iSkipTicks := aGuardsCooldown-Length(iIntruders); end; end; procedure aAlarm(aIntruder, aPlayer: Integer; aIsMilitary, aCheckForAmbush: Boolean); //To be placed OnUnitAttacked and OnHouseDamaged var i: Integer; iUnits: array of Integer; aAbort: Boolean; begin if aIsMilitary then Exit; aAbort := True; if Length(aInvolvedAIPlayers) > 0 then begin for i := 0 to Length(aInvolvedAIPlayers)-1 do if aInvolvedAIPlayers[i] = aPlayer then aAbort := False; end else Exit; if aAbort then Exit; if States.UnitDead(aIntruder) then Exit; if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do if iIntruders[i].aID = aIntruder then begin iIntruders[i].aOrigX := States.UnitPositionX(aIntruder); iIntruders[i].aOrigY := States.UnitPositionY(aIntruder); Exit; end; SetLength(iIntruders, Length(iIntruders)+1); iIntruders[Length(iIntruders)-1].aID := aIntruder; iIntruders[Length(iIntruders)-1].aOrigX := States.UnitPositionX(aIntruder); iIntruders[Length(iIntruders)-1].aOrigY := States.UnitPositionY(aIntruder); iIntruders[Length(iIntruders)-1].aAttackedPlayer := aPlayer; SetLength(iIntruders[Length(iIntruders)-1].aDispGuards, 0); aRecruitGuards(Length(iIntruders)-1); //Recruit the first guards aAttackIntruder(Length(iIntruders)-1); //And use them if aCheckForAmbush then begin iUnits := States.PlayerGetAllUnits(States.UnitOwner(aIntruder)); for i := 0 to Length(iUnits)-1 do if States.UnitType(iUnits[i]) >= 14 then if aGetDistance(States.UnitPositionX(aIntruder), States.UnitPositionX(iUnits[i]), States.UnitPositionY(aIntruder), States.UnitPositionY(iUnits[i])) <= aPreventAmbushRange then aAlarm(iUnits[i], aPlayer, False, False); end; end; procedure OnMissionStart; begin aInitAIDispatchSystem; end; procedure OnUnitAttacked(aUnitID, AttackerID: Integer); begin aAlarm(AttackerID, States.UnitOwner(aUnitID), aCheckUnitIsMilitary(aUnitID), aPreventAmbush); end; procedure OnHouseDamaged(aHouseID, AttackerID: Integer); begin aAlarm(AttackerID, States.HouseOwner(aHouseID), (States.HouseType(aHouseID) = 17), aPreventAmbush); end; procedure OnTick; begin aManageAIDispatchSystem; end;
To make things better against exploits, can you not update the Guard value by +1 for every guard killed and after some time this number start going down?
Oh, and the AI will react for killing serfs too? I will wait for another replay with your update script (I can't test myself right now).
You know, this is what was going through the back of my mind when I was producing that replay. I think this would make it much more realistic.To make things better against exploits, can you not update the Guard value by +1 for every guard killed and after some time this number start going down?
Excellent, excellent insight!
//Customizable settings
const
aDesistRange = 625; //It is a squared value; the distance from the original attack after which the guards will give up
aGuardsRatio = 2; //Number of dispatched guards per intruder
aReinforcementsRatio = 1.5; //By how many times does the number of dispatched guards change every time they get all killed; set to 1 to disable
aMountedIsBetter = 1.5; //Ratio of the advantage that mounted units have when looking for a new guard
aGuardsCooldown = 5; //Each how many skipped Ticks is the system managed; has to be an integer; different intruders are checked in different Ticks anyway if they are too many
aPreventAmbush = True; //If True, the AI will predict if the player infiltrated an ambush army too
aPreventAmbushRange = 625; //It is a squared value; the distance up to which a soldier will be considered an ambush army
aDefendAllies = True;
function SetAIs: array of Integer; //Which players will use this system
begin
//Format: [Player1ID, Player2ID, Player3ID] if multiple players or [Player1ID] if only one player; PlayerID is (player number)-1; allies will defend each other if closest to the attack
//Set it to [] to have the script work for all the AI players that are active when the mission starts
result := [];
end;
////////////////////////////////////Script starts here/////////////////////////////////////////////
//Global variables
var
iIntruders: array of Record aID, aOrigX, aOrigY, aAttackedPlayer, aKilledGuards, aGuardsNeeded: Integer; aDispGuards: array of Integer; end;
aInvolvedAIPlayers: array of Integer; //UnitIDs
iSkipTicks, aGuardsTotal, iCurrIntruder: Integer;
function aGetDistance(aX1, aX2, aY1, aY2: Integer): Integer; //Shared function
begin
//No point in doing the square root too
result := (aX1-aX2)*(aX1-aX2)+(aY1-aY2)*(aY1-aY2);
end;
function aCheckUnitIsMilitary(aUnitID: Integer): Boolean;
var
iHouses: array of Integer;
i: Integer;
begin
result := True;
if (States.UnitDead(aUnitID)) OR (States.UnitType(aUnitID) < 0) OR (States.UnitType(aUnitID) > 12) then Exit;
if (States.UnitType(aUnitID) = 0) OR (States.UnitType(aUnitID) = 9) then begin
iHouses := States.PlayerGetAllHouses(States.UnitOwner(aUnitID));
for i := 0 to Length(iHouses)-1 do if States.HouseType(iHouses[i]) = 17 then if aGetDistance(States.HousePositionX(iHouses[i]), States.UnitPositionX(aUnitID),States.HousePositionY(iHouses[i]), States.UnitPositionY(aUnitID)) <= 9 then Exit;
end;
result := False;
end;
function aCheckIsGuard(aWarrior: Integer): Boolean; //Check if the found soldier is already a guard
var
i, i2: Integer;
begin
result := False;
if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do begin
if Length(iIntruders[i].aDispGuards) > 0 then for i2 := 0 to Length(iIntruders[i].aDispGuards)-1 do if aWarrior = iIntruders[i].aDispGuards[i2] then begin
result := True;
Exit;
end;
end;
end;
function aIsPlayerAvailable(aPlayer1, aPlayer2: Integer): Boolean;
begin
result := False;
if States.PlayerEnabled(aPlayer1) AND States.PlayerEnabled(aPlayer2) then begin
if aDefendAllies then result := States.PlayerAllianceCheck(aPlayer1, aPlayer2)
else result := (aPlayer1 = aPlayer2);
end;
end;
procedure aRecruitGuards(aIndex: Integer);
var
i, i2, iDistance, iDistanceBest, iChosen, iCount, iCountNeeded: Integer;
iGroups: array of Integer;
begin
if Length(iIntruders)-1 < aIndex then Exit;
//Check assigned guards
iCountNeeded := iIntruders[aIndex].aGuardsNeeded-Length(iIntruders[aIndex].aDispGuards);
if iCountNeeded = 0 then Exit;
if iCountNeeded < 0 then begin
for i := Length(iIntruders[aIndex].aDispGuards)+iCountNeeded to Length(iIntruders[aIndex].aDispGuards)-1 do if not States.UnitDead(iIntruders[aIndex].aDispGuards[i]) then Actions.GroupOrderHalt(States.UnitsGroup(iIntruders[aIndex].aDispGuards[i]));
SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)+iCountNeeded);
aGuardsTotal := aGuardsTotal+iCountNeeded;
Exit;
end;
//Abort if the number if AI soldiers is too low; not really needed, but saves processing power
iCount := 0;
for i := 0 to Length(aInvolvedAIPlayers)-1 do if aIsPlayerAvailable(aInvolvedAIPlayers[i], iIntruders[aIndex].aAttackedPlayer) then begin
iCount := iCount+States.StatUnitMultipleTypesCount(aInvolvedAIPlayers[i], [14, 15, 16, 19, 20, 21, 22, 23, 25, 26, 27]);
end;
if iCount <= aGuardsTotal then begin
if aGuardsTotal = 0 then SetLength(iIntruders, 0);
Exit;
end;
//Find closest group to the intruder
iChosen := -1;
iDistanceBest := -1;
for i := 0 to Length(aInvolvedAIPlayers)-1 do if aIsPlayerAvailable(aInvolvedAIPlayers[i], iIntruders[aIndex].aAttackedPlayer) then begin
iGroups := States.PlayerGetAllGroups(aInvolvedAIPlayers[i]);
if Length(iGroups) > 0 then for i2 := 0 to Length(iGroups)-1 do if States.GroupType(iGroups[i2]) <> 2 then begin //Melee guards only; if no melee is found, then the AI has more pressing problems than those intruders
iDistance := aGetDistance(States.UnitPositionX(iIntruders[aIndex].aID), States.UnitPositionX(States.GroupMember(iGroups[i2], 0)), States.UnitPositionY(iIntruders[aIndex].aID), States.UnitPositionY(States.GroupMember(iGroups[i2], 0)));
if States.GroupType(iGroups[i2]) <> 3 then iDistance := Round(iDistance*aMountedIsBetter*aMountedIsBetter);
if (iDistanceBest < 0) OR (iDistance < iDistanceBest) then begin
if (not aCheckIsGuard(States.GroupMember(iGroups[i2], 0))) then begin
iDistanceBest := iDistance;
iChosen := iGroups[i2];
end;
end;
end;
end;
i2 := 0;
if iChosen > 0 then for i := 0 to iCountNeeded-1 do if i < States.GroupMemberCount(iChosen) then begin
SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)+1);
iIntruders[aIndex].aDispGuards[Length(iIntruders[aIndex].aDispGuards)-1] := States.GroupMember(iChosen, i);
Inc(aGuardsTotal);
Inc(i2);
end;
if i2 < iCountNeeded then aRecruitGuards(aIndex);
end;
procedure aAttackIntruder(aIndex: Integer);
var
i, i2, iCount, iGroup: Integer;
begin
if (Length(iIntruders)-1 < aIndex) OR (aIndex < 0) then Exit;
if Length(iIntruders[aIndex].aDispGuards) > 0 then begin
iCount := 0;
for i := 0 to Length(iIntruders[aIndex].aDispGuards)-1 do if States.UnitDead(iIntruders[aIndex].aDispGuards[i-iCount]) then begin
for i2 := i-iCount to Length(iIntruders[aIndex].aDispGuards)-2 do iIntruders[aIndex].aDispGuards[i2] := iIntruders[aIndex].aDispGuards[i2+1];
Inc(iCount);
end else begin
iGroup := States.UnitsGroup(iIntruders[aIndex].aDispGuards[i-iCount]);
if States.GroupMemberCount(iGroup) > 1 then begin
iGroup := Actions.GroupOrderSplitUnit(iGroup, iIntruders[aIndex].aDispGuards[i-iCount]);
Actions.GroupOrderAttackUnit(iGroup, iIntruders[aIndex].aID);
end else {if States.GroupIdle(iGroup) then} Actions.GroupOrderAttackUnit(iGroup, iIntruders[aIndex].aID);
end;
if iCount > 0 then begin
iIntruders[aIndex].aKilledGuards := iIntruders[aIndex].aKilledGuards+iCount;
if iIntruders[aIndex].aKilledGuards >= iIntruders[aIndex].aGuardsNeeded then begin
iIntruders[aIndex].aGuardsNeeded := Round(iIntruders[aIndex].aGuardsNeeded*aReinforcementsRatio);
iIntruders[aIndex].aKilledGuards := 0;
end;
aGuardsTotal := aGuardsTotal-iCount;
SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)-iCount);
end;
end;
end;
procedure aInitAIDispatchSystem; //To be placed OnMissionStart;
var
i: Integer;
begin
SetLength(iIntruders, 0);
aGuardsTotal := 0;
iCurrIntruder := 0;
aInvolvedAIPlayers := SetAIs;
if Length(aInvolvedAIPlayers) = 0 then for i := 0 to States.LocationCount-1 do if States.PlayerEnabled(i) AND States.PlayerIsAI(i) then begin
SetLength(aInvolvedAIPlayers, Length(aInvolvedAIPlayers)+1);
aInvolvedAIPlayers[Length(aInvolvedAIPlayers)-1] := i;
end;
end;
procedure aManageAIDispatchSystem; //To be placed OnTick;
var
i: Integer;
begin
if Length(iIntruders) > 0 then begin
if iSkipTicks < 0 then iSkipTicks := 0;
if iSkipTicks > 0 then begin
iSkipTicks := iSkipTicks-1;
Exit;
end;
if (iCurrIntruder > Length(iIntruders)-1) OR (iCurrIntruder < 0) then iCurrIntruder := 0;
//Check if Intruders have fled or are dead, and if there are enough guards
if (States.UnitDead(iIntruders[iCurrIntruder].aID)) OR (aGetDistance(iIntruders[iCurrIntruder].aOrigX, States.UnitPositionX(iIntruders[iCurrIntruder].aID), iIntruders[iCurrIntruder].aOrigY, States.UnitPositionY(iIntruders[iCurrIntruder].aID)) > aDesistRange) then begin
aGuardsTotal := aGuardsTotal-Length(iIntruders[iCurrIntruder].aDispGuards);
for i := 0 to Length(iIntruders[iCurrIntruder].aDispGuards)-1 do if not States.UnitDead(iIntruders[iCurrIntruder].aDispGuards[i]) then Actions.GroupOrderHalt(States.UnitsGroup(iIntruders[iCurrIntruder].aDispGuards[i]));
for i := iCurrIntruder to Length(iIntruders)-2 do iIntruders[i] := iIntruders[i+1];
SetLength(iIntruders, Length(iIntruders)-1);
iCurrIntruder := iCurrIntruder-1;
end else begin
aAttackIntruder(iCurrIntruder); //Also checks if the guards are dead; for this reason, it's better to do this before the recruiting
aRecruitGuards(iCurrIntruder); //Recruit or dismiss guards
end;
Inc(iCurrIntruder);
iSkipTicks := aGuardsCooldown-Length(iIntruders);
end;
end;
procedure aAlarm(aIntruder, aPlayer: Integer; aIsMilitary, aCheckForAmbush: Boolean); //To be placed OnUnitAttacked and OnHouseDamaged
var
i: Integer;
iUnits: array of Integer;
aAbort: Boolean;
begin
if aIsMilitary then Exit;
aAbort := True;
if Length(aInvolvedAIPlayers) > 0 then begin for i := 0 to Length(aInvolvedAIPlayers)-1 do if aInvolvedAIPlayers[i] = aPlayer then aAbort := False;
end else Exit;
if aAbort then Exit;
if States.UnitDead(aIntruder) then Exit;
if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do if iIntruders[i].aID = aIntruder then begin
iIntruders[i].aOrigX := States.UnitPositionX(aIntruder);
iIntruders[i].aOrigY := States.UnitPositionY(aIntruder);
Exit;
end;
SetLength(iIntruders, Length(iIntruders)+1);
iIntruders[Length(iIntruders)-1].aID := aIntruder;
iIntruders[Length(iIntruders)-1].aOrigX := States.UnitPositionX(aIntruder);
iIntruders[Length(iIntruders)-1].aOrigY := States.UnitPositionY(aIntruder);
iIntruders[Length(iIntruders)-1].aAttackedPlayer := aPlayer;
iIntruders[Length(iIntruders)-1].aGuardsNeeded := Round(aGuardsRatio);
iIntruders[Length(iIntruders)-1].aKilledGuards := 0;
SetLength(iIntruders[Length(iIntruders)-1].aDispGuards, 0);
aRecruitGuards(Length(iIntruders)-1); //Recruit the first guards
aAttackIntruder(Length(iIntruders)-1); //And use them
if aCheckForAmbush then begin
iUnits := States.PlayerGetAllUnits(States.UnitOwner(aIntruder));
for i := 0 to Length(iUnits)-1 do if States.UnitType(iUnits[i]) >= 14 then if aGetDistance(States.UnitPositionX(aIntruder), States.UnitPositionX(iUnits[i]), States.UnitPositionY(aIntruder), States.UnitPositionY(iUnits[i])) <= aPreventAmbushRange then aAlarm(iUnits[i], aPlayer, False, False);
end;
end;
////////////////////////////////////Script ends here/////////////////////////////////////////////
procedure OnMissionStart;
begin
aInitAIDispatchSystem;
end;
procedure OnUnitAttacked(aUnitID, AttackerID: Integer);
begin
aAlarm(AttackerID, States.UnitOwner(aUnitID), aCheckUnitIsMilitary(aUnitID), aPreventAmbush);
end;
procedure OnHouseDamaged(aHouseID, AttackerID: Integer);
begin
aAlarm(AttackerID, States.HouseOwner(aHouseID), (States.HouseType(aHouseID) = 17), aPreventAmbush);
end;
procedure OnTick;
begin
aManageAIDispatchSystem;
end;
You know, this is what was going through the back of my mind when I was producing that replay. I think this would make it much more realistic.To make things better against exploits, can you not update the Guard value by +1 for every guard killed and after some time this number start going down?
Excellent, excellent insight!
It should react to any attack to any building except Watch Towers, and to any attack to any unit except Warriors and Recruits.Oh, and the AI will react for killing serfs too? I will wait for another replay with your update script (I can't test myself right now).
It should also react to attacks to Serfs and Laborers, except if they are very close to a Watch Tower (this way it shouldn't trigger for Laborers repairing a Tower or Serfs resupplying it; it should also apply if the Tower is still under construction: after all, if you're attacking a Tower it is improbable you infiltrated the enemy, even if you caught it while under construction).
Good point indeed.You know, this is what was going through the back of my mind when I was producing that replay. I think this would make it much more realistic.To make things better against exploits, can you not update the Guard value by +1 for every guard killed and after some time this number start going down?
Excellent, excellent insight!
Is it better now?
(The number of sent guards should increase by 150% * every time you kill them all; the presence of an ambush party close to the first attacker is detected too)
*if you take a look at the first lines, you'll see that many of the actual values can easily be changed (text starting with // is a comment, and not part of the actually run script)
- Code:
//Customizable settings const aDesistRange = 625; //It is a squared value; the distance from the original attack after which the guards will give up aGuardsRatio = 2; //Number of dispatched guards per intruder aReinforcementsRatio = 1.5; //By how many times does the number of dispatched guards change every time they get all killed; set to 1 to disable aMountedIsBetter = 1.5; //Ratio of the advantage that mounted units have when looking for a new guard aGuardsCooldown = 5; //Each how many skipped Ticks is the system managed; has to be an integer; different intruders are checked in different Ticks anyway if they are too many aPreventAmbush = True; //If True, the AI will predict if the player infiltrated an ambush army too aPreventAmbushRange = 625; //It is a squared value; the distance up to which a soldier will be considered an ambush army aDefendAllies = True; function SetAIs: array of Integer; //Which players will use this system begin //Format: [Player1ID, Player2ID, Player3ID] if multiple players or [Player1ID] if only one player; PlayerID is (player number)-1; allies will defend each other if closest to the attack //Set it to [] to have the script work for all the AI players that are active when the mission starts result := []; end; ////////////////////////////////////Script starts here///////////////////////////////////////////// //Global variables var iIntruders: array of Record aID, aOrigX, aOrigY, aAttackedPlayer, aKilledGuards, aGuardsNeeded: Integer; aDispGuards: array of Integer; end; aInvolvedAIPlayers: array of Integer; //UnitIDs iSkipTicks, aGuardsTotal, iCurrIntruder: Integer; function aGetDistance(aX1, aX2, aY1, aY2: Integer): Integer; //Shared function begin //No point in doing the square root too result := (aX1-aX2)*(aX1-aX2)+(aY1-aY2)*(aY1-aY2); end; function aCheckUnitIsMilitary(aUnitID: Integer): Boolean; var iHouses: array of Integer; i: Integer; begin result := True; if (States.UnitDead(aUnitID)) OR (States.UnitType(aUnitID) < 0) OR (States.UnitType(aUnitID) > 12) then Exit; if (States.UnitType(aUnitID) = 0) OR (States.UnitType(aUnitID) = 9) then begin iHouses := States.PlayerGetAllHouses(States.UnitOwner(aUnitID)); for i := 0 to Length(iHouses)-1 do if States.HouseType(iHouses[i]) = 17 then if aGetDistance(States.HousePositionX(iHouses[i]), States.UnitPositionX(aUnitID),States.HousePositionY(iHouses[i]), States.UnitPositionY(aUnitID)) <= 9 then Exit; end; result := False; end; function aCheckIsGuard(aWarrior: Integer): Boolean; //Check if the found soldier is already a guard var i, i2: Integer; begin result := False; if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do begin if Length(iIntruders[i].aDispGuards) > 0 then for i2 := 0 to Length(iIntruders[i].aDispGuards)-1 do if aWarrior = iIntruders[i].aDispGuards[i2] then begin result := True; Exit; end; end; end; function aIsPlayerAvailable(aPlayer1, aPlayer2: Integer): Boolean; begin result := False; if States.PlayerEnabled(aPlayer1) AND States.PlayerEnabled(aPlayer2) then begin if aDefendAllies then result := States.PlayerAllianceCheck(aPlayer1, aPlayer2) else result := (aPlayer1 = aPlayer2); end; end; procedure aRecruitGuards(aIndex: Integer); var i, i2, iDistance, iDistanceBest, iChosen, iCount, iCountNeeded: Integer; iGroups: array of Integer; begin if Length(iIntruders)-1 < aIndex then Exit; //Check assigned guards iCountNeeded := iIntruders[aIndex].aGuardsNeeded-Length(iIntruders[aIndex].aDispGuards); if iCountNeeded = 0 then Exit; if iCountNeeded < 0 then begin for i := Length(iIntruders[aIndex].aDispGuards)+iCountNeeded to Length(iIntruders[aIndex].aDispGuards)-1 do if not States.UnitDead(iIntruders[aIndex].aDispGuards[i]) then Actions.GroupOrderHalt(States.UnitsGroup(iIntruders[aIndex].aDispGuards[i])); SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)+iCountNeeded); aGuardsTotal := aGuardsTotal+iCountNeeded; Exit; end; //Abort if the number if AI soldiers is too low; not really needed, but saves processing power iCount := 0; for i := 0 to Length(aInvolvedAIPlayers)-1 do if aIsPlayerAvailable(aInvolvedAIPlayers[i], iIntruders[aIndex].aAttackedPlayer) then begin iCount := iCount+States.StatUnitMultipleTypesCount(aInvolvedAIPlayers[i], [14, 15, 16, 19, 20, 21, 22, 23, 25, 26, 27]); end; if iCount <= aGuardsTotal then begin if aGuardsTotal = 0 then SetLength(iIntruders, 0); Exit; end; //Find closest group to the intruder iChosen := -1; iDistanceBest := -1; for i := 0 to Length(aInvolvedAIPlayers)-1 do if aIsPlayerAvailable(aInvolvedAIPlayers[i], iIntruders[aIndex].aAttackedPlayer) then begin iGroups := States.PlayerGetAllGroups(aInvolvedAIPlayers[i]); if Length(iGroups) > 0 then for i2 := 0 to Length(iGroups)-1 do if States.GroupType(iGroups[i2]) <> 2 then begin //Melee guards only; if no melee is found, then the AI has more pressing problems than those intruders iDistance := aGetDistance(States.UnitPositionX(iIntruders[aIndex].aID), States.UnitPositionX(States.GroupMember(iGroups[i2], 0)), States.UnitPositionY(iIntruders[aIndex].aID), States.UnitPositionY(States.GroupMember(iGroups[i2], 0))); if States.GroupType(iGroups[i2]) <> 3 then iDistance := Round(iDistance*aMountedIsBetter*aMountedIsBetter); if (iDistanceBest < 0) OR (iDistance < iDistanceBest) then begin if (not aCheckIsGuard(States.GroupMember(iGroups[i2], 0))) then begin iDistanceBest := iDistance; iChosen := iGroups[i2]; end; end; end; end; i2 := 0; if iChosen > 0 then for i := 0 to iCountNeeded-1 do if i < States.GroupMemberCount(iChosen) then begin SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)+1); iIntruders[aIndex].aDispGuards[Length(iIntruders[aIndex].aDispGuards)-1] := States.GroupMember(iChosen, i); Inc(aGuardsTotal); Inc(i2); end; if i2 < iCountNeeded then aRecruitGuards(aIndex); end; procedure aAttackIntruder(aIndex: Integer); var i, i2, iCount, iGroup: Integer; begin if (Length(iIntruders)-1 < aIndex) OR (aIndex < 0) then Exit; if Length(iIntruders[aIndex].aDispGuards) > 0 then begin iCount := 0; for i := 0 to Length(iIntruders[aIndex].aDispGuards)-1 do if States.UnitDead(iIntruders[aIndex].aDispGuards[i-iCount]) then begin for i2 := i-iCount to Length(iIntruders[aIndex].aDispGuards)-2 do iIntruders[aIndex].aDispGuards[i2] := iIntruders[aIndex].aDispGuards[i2+1]; Inc(iCount); end else begin iGroup := States.UnitsGroup(iIntruders[aIndex].aDispGuards[i-iCount]); if States.GroupMemberCount(iGroup) > 1 then begin iGroup := Actions.GroupOrderSplitUnit(iGroup, iIntruders[aIndex].aDispGuards[i-iCount]); Actions.GroupOrderAttackUnit(iGroup, iIntruders[aIndex].aID); end else {if States.GroupIdle(iGroup) then} Actions.GroupOrderAttackUnit(iGroup, iIntruders[aIndex].aID); end; if iCount > 0 then begin iIntruders[aIndex].aKilledGuards := iIntruders[aIndex].aKilledGuards+iCount; if iIntruders[aIndex].aKilledGuards >= iIntruders[aIndex].aGuardsNeeded then begin iIntruders[aIndex].aGuardsNeeded := Round(iIntruders[aIndex].aGuardsNeeded*aReinforcementsRatio); iIntruders[aIndex].aKilledGuards := 0; end; aGuardsTotal := aGuardsTotal-iCount; SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)-iCount); end; end; end; procedure aInitAIDispatchSystem; //To be placed OnMissionStart; var i: Integer; begin SetLength(iIntruders, 0); aGuardsTotal := 0; iCurrIntruder := 0; aInvolvedAIPlayers := SetAIs; if Length(aInvolvedAIPlayers) = 0 then for i := 0 to States.LocationCount-1 do if States.PlayerEnabled(i) AND States.PlayerIsAI(i) then begin SetLength(aInvolvedAIPlayers, Length(aInvolvedAIPlayers)+1); aInvolvedAIPlayers[Length(aInvolvedAIPlayers)-1] := i; end; end; procedure aManageAIDispatchSystem; //To be placed OnTick; var i: Integer; begin if Length(iIntruders) > 0 then begin if iSkipTicks < 0 then iSkipTicks := 0; if iSkipTicks > 0 then begin iSkipTicks := iSkipTicks-1; Exit; end; if (iCurrIntruder > Length(iIntruders)-1) OR (iCurrIntruder < 0) then iCurrIntruder := 0; //Check if Intruders have fled or are dead, and if there are enough guards if (States.UnitDead(iIntruders[iCurrIntruder].aID)) OR (aGetDistance(iIntruders[iCurrIntruder].aOrigX, States.UnitPositionX(iIntruders[iCurrIntruder].aID), iIntruders[iCurrIntruder].aOrigY, States.UnitPositionY(iIntruders[iCurrIntruder].aID)) > aDesistRange) then begin aGuardsTotal := aGuardsTotal-Length(iIntruders[iCurrIntruder].aDispGuards); for i := 0 to Length(iIntruders[iCurrIntruder].aDispGuards)-1 do if not States.UnitDead(iIntruders[iCurrIntruder].aDispGuards[i]) then Actions.GroupOrderHalt(States.UnitsGroup(iIntruders[iCurrIntruder].aDispGuards[i])); for i := iCurrIntruder to Length(iIntruders)-2 do iIntruders[i] := iIntruders[i+1]; SetLength(iIntruders, Length(iIntruders)-1); iCurrIntruder := iCurrIntruder-1; end else begin aAttackIntruder(iCurrIntruder); //Also checks if the guards are dead; for this reason, it's better to do this before the recruiting aRecruitGuards(iCurrIntruder); //Recruit or dismiss guards end; Inc(iCurrIntruder); iSkipTicks := aGuardsCooldown-Length(iIntruders); end; end; procedure aAlarm(aIntruder, aPlayer: Integer; aIsMilitary, aCheckForAmbush: Boolean); //To be placed OnUnitAttacked and OnHouseDamaged var i: Integer; iUnits: array of Integer; aAbort: Boolean; begin if aIsMilitary then Exit; aAbort := True; if Length(aInvolvedAIPlayers) > 0 then begin for i := 0 to Length(aInvolvedAIPlayers)-1 do if aInvolvedAIPlayers[i] = aPlayer then aAbort := False; end else Exit; if aAbort then Exit; if States.UnitDead(aIntruder) then Exit; if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do if iIntruders[i].aID = aIntruder then begin iIntruders[i].aOrigX := States.UnitPositionX(aIntruder); iIntruders[i].aOrigY := States.UnitPositionY(aIntruder); Exit; end; SetLength(iIntruders, Length(iIntruders)+1); iIntruders[Length(iIntruders)-1].aID := aIntruder; iIntruders[Length(iIntruders)-1].aOrigX := States.UnitPositionX(aIntruder); iIntruders[Length(iIntruders)-1].aOrigY := States.UnitPositionY(aIntruder); iIntruders[Length(iIntruders)-1].aAttackedPlayer := aPlayer; iIntruders[Length(iIntruders)-1].aGuardsNeeded := Round(aGuardsRatio); iIntruders[Length(iIntruders)-1].aKilledGuards := 0; SetLength(iIntruders[Length(iIntruders)-1].aDispGuards, 0); aRecruitGuards(Length(iIntruders)-1); //Recruit the first guards aAttackIntruder(Length(iIntruders)-1); //And use them if aCheckForAmbush then begin iUnits := States.PlayerGetAllUnits(States.UnitOwner(aIntruder)); for i := 0 to Length(iUnits)-1 do if States.UnitType(iUnits[i]) >= 14 then if aGetDistance(States.UnitPositionX(aIntruder), States.UnitPositionX(iUnits[i]), States.UnitPositionY(aIntruder), States.UnitPositionY(iUnits[i])) <= aPreventAmbushRange then aAlarm(iUnits[i], aPlayer, False, False); end; end; ////////////////////////////////////Script ends here///////////////////////////////////////////// procedure OnMissionStart; begin aInitAIDispatchSystem; end; procedure OnUnitAttacked(aUnitID, AttackerID: Integer); begin aAlarm(AttackerID, States.UnitOwner(aUnitID), aCheckUnitIsMilitary(aUnitID), aPreventAmbush); end; procedure OnHouseDamaged(aHouseID, AttackerID: Integer); begin aAlarm(AttackerID, States.HouseOwner(aHouseID), (States.HouseType(aHouseID) = 17), aPreventAmbush); end; procedure OnTick; begin aManageAIDispatchSystem; end;
And let's wait for cmowla replay to see if the Alarm 2.0 is perfect.
The game is a little "jumpy" with this script, so apart from checking for any possible bugs or code inefficiency, I think this should discourage town sneak attacks. I never liked sneaking in a village anyway.
//Customizable settings
const
aDesistRange = 625; //It is a squared value; the distance from the original attack after which the guards will give up
aGuardsRatio = 2; //Number of dispatched guards per intruder
aReinforcementsRatio = 1.5; //By how many times does the number of dispatched guards change every time they get all killed; set to 1 to disable
aMountedIsBetter = 2.25; //It is a squared value; ratio of the advantage that mounted units have when looking for a new guard
aPreventAmbush = True; //If True, the AI will predict if the player infiltrated an ambush army too
aPreventAmbushRange = 625; //It is a squared value; the distance up to which a soldier will be considered an ambush army
aDefendAllies = True;
function SetAIs: array of Integer; //Which players will use this system
begin
//Format: [Player1ID, Player2ID, Player3ID] if multiple players or [Player1ID] if only one player; PlayerID is (player number)-1; allies will defend each other if closest to the attack
//Set it to [] to have the script work for all the AI players that are active when the mission starts
result := [];
end;
//Instructions:
// aUpdateNumWarriors(States.UnitOwner(aUnitID)); needs to be placed OnWarriorEquipped
// aUpdateAlarmSystem(aUnitID); needs to be placed OnUnitDied
// aInitAlarmSystem; needs to be placed OnMissionStart
// aAlarm(AttackerID, States.UnitOwner(aUnitID), aCheckUnitIsMilitary(aUnitID), aPreventAmbush); needs to be placed OnUnitAttacked
// aAlarm(AttackerID, States.HouseOwner(aHouseID), (States.HouseType(aHouseID) = 17), aPreventAmbush); needs to be placed OnHouseDamaged
// aRecruitGuards(-1); needs to be placed OnTick
////////////////////////////////////Script starts here/////////////////////////////////////////////
//Global variables
type
tGuardToID = array[0..1] of Integer;
var
iIntruders: array of Record aID, aOrigX, aOrigY, aAttackedPlayer, aKilledGuards, aGuardsNeeded: Integer; aGuards: array of Integer; end;
aInvolvedAIPlayers: array of Record aPlayer, aWarriors: Integer; end;
aGuardsTotal, iCurrIntruder: Integer;
function aGetDistance(aX1, aX2, aY1, aY2: Integer): Integer; //Shared function
begin
//No point in doing the square root too
result := (aX1-aX2)*(aX1-aX2)+(aY1-aY2)*(aY1-aY2);
end;
function aPlayerToID(aPlayer: Integer): Integer; //Returns -1 if the player isn't involved
var
i: Integer;
begin
result := -1;
if Length(aInvolvedAIPlayers) > 0 then for i := 0 to Length(aInvolvedAIPlayers)-1 do if aInvolvedAIPlayers[i].aPlayer = aPlayer then begin
Result := i;
Break;
end;
end;
procedure aUpdateNumWarriors(aPlayer: Integer);
var
i: Integer;
begin
//Only melee units are considered for guard's duty
i := aPlayerToID(aPlayer);
if i >= 0 then aInvolvedAIPlayers[i].aWarriors := States.StatUnitMultipleTypesCount(aPlayer, [14, 15, 16, 19, 20, 21, 22, 23, 25, 26, 27]);
end;
procedure aUpdatePlayers;
var
i: Integer;
iP: array of Integer;
begin
iP := SetAIs;
if Length(iP) > 0 then begin
SetLength(aInvolvedAIPlayers, Length(iP));
for i := 0 to Length(iP)-1 do aInvolvedAIPlayers[i].aPlayer := iP[i];
end else begin
SetLength(aInvolvedAIPlayers, 0);
for i := 0 to States.LocationCount-1 do if States.PlayerEnabled(i) AND States.PlayerIsAI(i) then begin
SetLength(aInvolvedAIPlayers, Length(aInvolvedAIPlayers)+1);
aInvolvedAIPlayers[Length(aInvolvedAIPlayers)-1].aPlayer := i;
end;
end;
for i := 0 to Length(aInvolvedAIPlayers)-1 do aUpdateNumWarriors(aInvolvedAIPlayers[i].aPlayer);
end;
procedure aInitAlarmSystem;
begin
SetLength(iIntruders, 0);
aGuardsTotal := 0;
iCurrIntruder := 0;
aUpdatePlayers;
end;
function aCheckUnitIsMilitary(aUnitID: Integer): Boolean;
var
iHouses: array of Integer;
i: Integer;
begin
result := True;
if (States.UnitDead(aUnitID)) OR (States.UnitType(aUnitID) < 0) OR (States.UnitType(aUnitID) > 12) then Exit;
if (States.UnitType(aUnitID) = 0) OR (States.UnitType(aUnitID) = 9) then begin
iHouses := States.PlayerGetAllHouses(States.UnitOwner(aUnitID));
for i := 0 to Length(iHouses)-1 do if States.HouseType(iHouses[i]) = 17 then if aGetDistance(States.HousePositionX(iHouses[i]), States.UnitPositionX(aUnitID),States.HousePositionY(iHouses[i]), States.UnitPositionY(aUnitID)) <= 9 then Exit;
end;
result := False;
end;
function aIntruderToID(aUnitID: Integer): Integer; //Returns -1 if not an intruder
var
i: Integer;
begin
result := -1;
if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do if iIntruders[i].aID = aUnitID then begin
result := i;
Break;
end;
end;
function aGuardToID(aUnitID: Integer): tGuardToID; //Result[0] returns -1 if not a guard
var
i, i2: Integer;
begin
result[0] := -1;
if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do if Length(iIntruders[i].aGuards) >= 0 then for i2 := 0 to Length(iIntruders[i].aGuards)-1 do if iIntruders[i].aGuards[i2] = aUnitID then begin
result[0] := i;
result[1] := i2;
Break;
end;
end;
procedure aFreeIntruder(aIndex: Integer); //Assumes aIndex to be valid
var
i: Integer;
begin
aGuardsTotal := aGuardsTotal-Length(iIntruders[aIndex].aGuards);
for i := 0 to Length(iIntruders[aIndex].aGuards)-1 do if not States.UnitDead(iIntruders[aIndex].aGuards[i]) then begin
if States.GroupMemberCount(States.UnitsGroup(iIntruders[aIndex].aGuards[i])) > 1 then Actions.GroupOrderSplitUnit(States.UnitsGroup(iIntruders[aIndex].aGuards[i]), iIntruders[aIndex].aGuards[i])
else Actions.GroupOrderHalt(States.UnitsGroup(iIntruders[aIndex].aGuards[i]));
end;
for i := aIndex to Length(iIntruders)-2 do iIntruders[i] := iIntruders[i+1];
SetLength(iIntruders, Length(iIntruders)-1);
end;
function aIsPlayerAvailable(aPlayer1, aPlayer2: Integer): Boolean;
begin
result := False;
if States.PlayerEnabled(aPlayer1) AND States.PlayerEnabled(aPlayer2) then begin
if aDefendAllies then result := States.PlayerAllianceCheck(aPlayer1, aPlayer2)
else result := (aPlayer1 = aPlayer2);
end;
end;
procedure aRecruitGuards(aNum: Integer);
var
aIndex, i, i2, i3, iDistance, iDistanceBest, iChosen, iCount, iCountNeeded, iUnit, iUnit2: Integer;
iGroups: array of Integer;
iG: tGuardToID;
begin
if (aNum = -1) AND (Length(iIntruders) > 0) then begin
Inc(iCurrIntruder);
if iCurrIntruder >= Length(iIntruders) then iCurrIntruder := 0;
if States.UnitDead(iIntruders[iCurrIntruder].aID) then Exit; //This isn't managed here
//Check if the intruder fled
if aGetDistance(iIntruders[iCurrIntruder].aOrigX, States.UnitPositionX(iIntruders[iCurrIntruder].aID), iIntruders[iCurrIntruder].aOrigY, States.UnitPositionY(iIntruders[iCurrIntruder].aID)) > aDesistRange then begin
aFreeIntruder(iCurrIntruder);
iCurrIntruder := iCurrIntruder-1;
aRecruitGuards(-1); //Call self to repeat the distance check
Exit;
end;
aIndex := iCurrIntruder;
end else begin
if (aNum >= 0) AND (aNum < Length(iIntruders)) then begin
if States.UnitDead(iIntruders[aNum].aID) then Exit; //This isn't managed here
aIndex := aNum
end else Exit; //Also Exits if Length(iIntruders) = 0
end;
iCountNeeded := iIntruders[aIndex].aGuardsNeeded-Length(iIntruders[aIndex].aGuards);
if iCountNeeded > 0 then begin
//Abort if the number if AI soldiers is too low; not really needed, but saves processing power
iCount := 0;
for i := 0 to Length(aInvolvedAIPlayers)-1 do if aIsPlayerAvailable(aInvolvedAIPlayers[i].aPlayer, iIntruders[aIndex].aAttackedPlayer) then iCount := iCount+aInvolvedAIPlayers[i].aWarriors;
if iCount <= aGuardsTotal then begin
if aGuardsTotal = 0 then SetLength(iIntruders, 0); //No AI soldiers left
Exit;
end;
//Find closest group to the intruder
iChosen := -1;
iDistanceBest := -1;
for i := 0 to Length(aInvolvedAIPlayers)-1 do if aIsPlayerAvailable(aInvolvedAIPlayers[i].aPlayer, iIntruders[aIndex].aAttackedPlayer) then begin
iGroups := States.PlayerGetAllGroups(aInvolvedAIPlayers[i].aPlayer);
if Length(iGroups) > 0 then for i2 := 0 to Length(iGroups)-1 do if (States.GroupType(iGroups[i2]) <> 2) AND (not States.GroupDead(iGroups[i2])) then begin //Melee guards only; if no melee is found, then the AI has more pressing problems than those intruders
iUnit := States.GroupMember(iGroups[i2], 0);
iG := aGuardToID(iUnit);
if iG[0] < 0 then begin
iDistance := aGetDistance(States.UnitPositionX(iIntruders[aIndex].aID), States.UnitPositionX(iUnit), States.UnitPositionY(iIntruders[aIndex].aID), States.UnitPositionY(iUnit));
if States.GroupType(iGroups[i2]) <> 3 then iDistance := Round(iDistance*aMountedIsBetter);
if (iDistanceBest < 0) OR (iDistance < iDistanceBest) then begin
iDistanceBest := iDistance;
iChosen := iGroups[i2];
end;
end;
end;
end;
i2 := 0;
if iChosen > 0 then for i := 0 to iCountNeeded-1 do begin
SetLength(iIntruders[aIndex].aGuards, Length(iIntruders[aIndex].aGuards)+1);
iIntruders[aIndex].aGuards[Length(iIntruders[aIndex].aGuards)-1] := States.GroupMember(iChosen, 0);
Inc(aGuardsTotal);
Inc(i2);
iCount := States.GroupMemberCount(iChosen);
if iCount > 1 then iUnit := Actions.GroupOrderSplitUnit(iChosen, States.GroupMember(iChosen, 0))
else iUnit := iChosen;
//Link guards with the same target
for i3 := 0 to Length(iIntruders[aIndex].aGuards)-1 do begin
iUnit2 := States.UnitsGroup(iIntruders[aIndex].aGuards[i3]);
if (States.GroupType(iUnit2) = States.GroupType(iUnit)) AND (iUnit <> iUnit2) then begin
Actions.GroupOrderLink(iUnit, iUnit2);
iUnit := iUnit2;
Break;
end;
end;
Actions.GroupOrderAttackUnit(iUnit, iIntruders[aIndex].aID);
if iCount <= 1 then Break;
end;
if i2 < iCountNeeded then aRecruitGuards(aIndex);
end;
end;
procedure aAlarm(aIntruder, aPlayer: Integer; aIsMilitary, aCheckForAmbush: Boolean);
var
i: Integer;
iUnits: array of Integer;
begin
if aIsMilitary then Exit;
if aPlayerToID(aPlayer) < 0 then Exit; //Player not involved
if States.UnitDead(aIntruder) then Exit;
i := aIntruderToID(aIntruder);
if i >= 0 then begin //Already detected
iIntruders[i].aOrigX := States.UnitPositionX(aIntruder);
iIntruders[i].aOrigY := States.UnitPositionY(aIntruder);
Exit;
end;
SetLength(iIntruders, Length(iIntruders)+1);
with iIntruders[Length(iIntruders)-1] do begin
aID := aIntruder;
aOrigX := States.UnitPositionX(aIntruder);
aOrigY := States.UnitPositionY(aIntruder);
aAttackedPlayer := aPlayer;
aGuardsNeeded := Round(aGuardsRatio);
aKilledGuards := 0;
SetLength(aGuards, 0);
end;
aRecruitGuards(Length(iIntruders)-1); //Recruit the first guards
if aCheckForAmbush then begin
iUnits := States.PlayerGetAllUnits(States.UnitOwner(aIntruder));
for i := 0 to Length(iUnits)-1 do if States.UnitType(iUnits[i]) >= 14 then if aGetDistance(States.UnitPositionX(aIntruder), States.UnitPositionX(iUnits[i]), States.UnitPositionY(aIntruder), States.UnitPositionY(iUnits[i])) <= aPreventAmbushRange then aAlarm(iUnits[i], aPlayer, False, False);
end;
end;
procedure aUpdateAlarmSystem(aUnitID: Integer); //I'm assuming that someone died
var
i: Integer;
iG: tGuardToID;
begin
if (States.UnitType(aUnitID) >= 14) AND (States.UnitType(aUnitID) <= 27) then begin //Warriors only
aUpdateNumWarriors(States.UnitOwner(aUnitID));
i := aIntruderToID(aUnitID);
if i >= 0 then begin
aFreeIntruder(i);
end;
iG := aGuardToID(aUnitID);
if iG[0] >= 0 then begin
aGuardsTotal := aGuardsTotal-1;
Inc(iIntruders[iG[0]].aKilledGuards);
if iIntruders[iG[0]].aKilledGuards >= iIntruders[iG[0]].aGuardsNeeded then begin
iIntruders[iG[0]].aGuardsNeeded := Round(iIntruders[iG[0]].aGuardsNeeded*aReinforcementsRatio);
iIntruders[iG[0]].aKilledGuards := 0;
end;
for i := iG[1] to Length(iIntruders[iG[0]].aGuards)-2 do iIntruders[iG[0]].aGuards[i] := iIntruders[iG[0]].aGuards[i+1];
SetLength(iIntruders[iG[0]].aGuards, Length(iIntruders[iG[0]].aGuards)-1);
end;
end;
end;
////////////////////////////////////Script ends here/////////////////////////////////////////////
procedure OnMissionStart;
begin
aInitAlarmSystem;
end;
procedure OnUnitAttacked(aUnitID, AttackerID: Integer);
begin
aAlarm(AttackerID, States.UnitOwner(aUnitID), aCheckUnitIsMilitary(aUnitID), aPreventAmbush);
end;
procedure OnHouseDamaged(aHouseID, AttackerID: Integer);
begin
aAlarm(AttackerID, States.HouseOwner(aHouseID), (States.HouseType(aHouseID) = 17), aPreventAmbush);
end;
procedure OnUnitDied(aUnitID, aKillerID: Integer);
begin
aUpdateAlarmSystem(aUnitID);
end;
procedure OnWarriorEquipped(aUnitID, aGroupID: Integer);
begin
aUpdateNumWarriors(States.UnitOwner(aUnitID));
end;
procedure OnTick;
begin
aRecruitGuards(-1);
end;
Good work, Esthlos.
Users browsing this forum: Google [Bot] and 0 guests
Powered by phpBB® Forum Software © phpBB Group Designed by ST Software |