1784 lines
58 KiB
ObjectPascal
1784 lines
58 KiB
ObjectPascal
{
|
|||
|
|
Artificial intelligence
|
||
|
|
@author: Martin Toupal
|
||
|
|
@e-mail: poznamenany@gmail.com
|
||
|
|
}
|
||
|
|
unit KM_ArmyAttack;
|
||
|
|
{$I KaM_Remake.inc}
|
||
|
|
interface
|
||
|
|
uses
|
||
|
|
Classes, KromUtils, Math, SysUtils,
|
||
|
|
KM_Units, KM_UnitGroup,
|
||
|
|
KM_CommonClasses, KM_CommonTypes, KM_Defaults, KM_Points,
|
||
|
|
KM_Houses, KM_ResHouses, KM_ResWares, KM_NavMeshPathFinding,
|
||
|
|
KM_Eye;
|
||
|
|
|
||
|
|
type
|
||
|
|
TAISquad = class; // Combat group (orders to specific group: walk / attack)
|
||
|
|
TAICompany = class; // Company (selection of targets, positions etc.)
|
||
|
|
TKMArmyAttack = class; // Attack (time distribution, company initialization)
|
||
|
|
|
||
|
|
TKMSquadList = array[GROUP_TYPE_MIN..GROUP_TYPE_MAX] of TKMList;
|
||
|
|
TKMSquadsArray = array[GROUP_TYPE_MIN..GROUP_TYPE_MAX] of record
|
||
|
|
Count: Word;
|
||
|
|
Squads: array of TAISquad;
|
||
|
|
TargetUnit: TKMUnitArray;
|
||
|
|
TargetHouse: TKMHouseArray;
|
||
|
|
end;
|
||
|
|
TKMTargetSelection = array of record
|
||
|
|
Index: Word;
|
||
|
|
CenterPoint: TKMPoint;
|
||
|
|
CloseThreat, DistantThreat: Single;
|
||
|
|
end;
|
||
|
|
TKMCompanyMode = (cmAttack, cmDefence, cmDestruction);
|
||
|
|
TKMCompanyState = (csAttack, csWalking, csIdle);
|
||
|
|
|
||
|
|
TAISquad = class
|
||
|
|
private
|
||
|
|
fGroup: TKMUnitGroup;
|
||
|
|
fOnPlace, fTargetChanged: Boolean;
|
||
|
|
fFinalPosition: TKMPointDir;
|
||
|
|
fTargetHouse: TKMHouse;
|
||
|
|
fTargetUnit: TKMUnit;
|
||
|
|
fWalkTimeLimit, fAttackTimeLimit: Cardinal;
|
||
|
|
|
||
|
|
function SquadInFight(): Boolean; inline;
|
||
|
|
function GetGroupPosition(): TKMPoint; inline;
|
||
|
|
function PlanPath(aTick: Cardinal; var aActualPosition, aTargetPosition: TKMPoint; aOrderAttack: Boolean = False; aOrderDestroy: Boolean = False): Boolean;
|
||
|
|
|
||
|
|
procedure SetTargetHouse(aHouse: TKMHouse);
|
||
|
|
procedure SetTargetUnit(aUnit: TKMUnit);
|
||
|
|
procedure SetTargetPosition(aLoc: TKMPointDir);
|
||
|
|
public
|
||
|
|
{$IFDEF DEBUG_NewAI}
|
||
|
|
DEBUGPointPath: TKMPointArray;
|
||
|
|
{$ENDIF}
|
||
|
|
|
||
|
|
constructor Create(aGroup: TKMUnitGroup);
|
||
|
|
constructor Load(LoadStream: TKMemoryStream);
|
||
|
|
destructor Destroy; override;
|
||
|
|
procedure Save(SaveStream: TKMemoryStream);
|
||
|
|
procedure SyncLoad();
|
||
|
|
|
||
|
|
property Group: TKMUnitGroup read fGroup;
|
||
|
|
property OnPlace: Boolean read fOnPlace;
|
||
|
|
property InFight: Boolean read SquadInFight;
|
||
|
|
property FinalPosition: TKMPointDir read fFinalPosition write SetTargetPosition;
|
||
|
|
property Position: TKMPoint read GetGroupPosition;
|
||
|
|
property WalkTimeLimit: Cardinal read fWalkTimeLimit write fWalkTimeLimit;
|
||
|
|
property AttackTimeLimit: Cardinal read fAttackTimeLimit write fAttackTimeLimit;
|
||
|
|
property TargetHouse: TKMHouse read fTargetHouse write SetTargetHouse;
|
||
|
|
property TargetUnit: TKMUnit read fTargetUnit write SetTargetUnit;
|
||
|
|
|
||
|
|
procedure UpdateState(aTick: Cardinal);
|
||
|
|
end;
|
||
|
|
|
||
|
|
TAICompany = class
|
||
|
|
private
|
||
|
|
fOwner: TKMHandID;
|
||
|
|
fPathPosition: TKMPoint;
|
||
|
|
fScanPosition: TKMPoint;
|
||
|
|
fCompanyMode: TKMCompanyMode;
|
||
|
|
fTargetOwner: TKMHandID;
|
||
|
|
fTargetPoint: TKMPoint;
|
||
|
|
fTargetHouse: TKMHouse;
|
||
|
|
fTargetUnit: TKMUnit;
|
||
|
|
fState: TKMCompanyState;
|
||
|
|
fSquads: TKMSquadList;
|
||
|
|
|
||
|
|
function GetPosition(): TKMPoint; overload;
|
||
|
|
function GetPosition(var aSQRRadius: Single): TKMPoint; overload;
|
||
|
|
function GetTargetPosition(): TKMPoint;
|
||
|
|
function OrderToAttack(aActualPosition: TKMPoint; UA: TKMUnitArray; UGA: TKMUnitGroupArray; HA: TKMHouseArray): Boolean;
|
||
|
|
function OrderMove(aTick: Cardinal; aActualPosition: TKMPoint): Boolean;
|
||
|
|
public
|
||
|
|
{$IFDEF DEBUG_NewAI}
|
||
|
|
DEBUGScanRad: Single;
|
||
|
|
DEBUGTargetU: TKMTargetSelection;
|
||
|
|
DEBUGPointPath, DEBUG_UA_POINTS, DEBUG_UGA_POINTS: TKMPointArray;
|
||
|
|
{$ENDIF}
|
||
|
|
|
||
|
|
constructor Create(aOwner: TKMHandID; aCompanyMode: TKMCompanyMode);
|
||
|
|
constructor Load(LoadStream: TKMemoryStream);
|
||
|
|
destructor Destroy(); override;
|
||
|
|
procedure Save(SaveStream: TKMemoryStream);
|
||
|
|
procedure SyncLoad();
|
||
|
|
|
||
|
|
property Owner: TKMHandID read fOwner write fOwner;
|
||
|
|
property PathPosition: TKMPoint read fPathPosition;
|
||
|
|
property ScanPosition: TKMPoint read fScanPosition;
|
||
|
|
property CompanyMode: TKMCompanyMode read fCompanyMode write fCompanyMode;
|
||
|
|
property TargetPoint: TKMPoint read fTargetPoint write fTargetPoint;
|
||
|
|
property TargetPosition: TKMPoint read GetTargetPosition;
|
||
|
|
property State: TKMCompanyState read fState;
|
||
|
|
property Squads: TKMSquadList read fSquads;
|
||
|
|
|
||
|
|
procedure InitCompany();
|
||
|
|
procedure UpdateState(aTick: Cardinal);
|
||
|
|
procedure AddSquad(aGroup: TKMUnitGroup);
|
||
|
|
procedure DeleteSquad(aGT: TKMGroupType; aIdx: Integer);
|
||
|
|
function SquadCnt(aTypes: TKMGroupTypeSet = GROUP_TYPES_VALID): Word;
|
||
|
|
function WarriorCnt(aTypes: TKMGroupTypeSet = GROUP_TYPES_VALID): Word;
|
||
|
|
function IsGroupInCompany(aGroup: TKMUnitGroup): Boolean;
|
||
|
|
function SetTarget(aHouse: TKMHouse; aUnit: TKMUnit = nil): Boolean;
|
||
|
|
function ActualizeTarget(aInitPosition: TKMPoint; var aTargetHouse: TKMHouse; var aTargetUnit: TKMUnit; aAimCivilians: Boolean = True): Boolean;
|
||
|
|
end;
|
||
|
|
|
||
|
|
TKMArmyAttack = class
|
||
|
|
private
|
||
|
|
fOwner: TKMHandID;
|
||
|
|
fCompanies: TKMList;
|
||
|
|
|
||
|
|
function GetCount(): Integer;
|
||
|
|
function GetCompany(aIdx: Integer): TAICompany;
|
||
|
|
public
|
||
|
|
constructor Create(aOwner: TKMHandID);
|
||
|
|
destructor Destroy(); override;
|
||
|
|
procedure Save(SaveStream: TKMemoryStream);
|
||
|
|
procedure Load(LoadStream: TKMemoryStream);
|
||
|
|
procedure SyncLoad();
|
||
|
|
|
||
|
|
property Count: Integer read GetCount;
|
||
|
|
property Company[aIdx: Integer]: TAICompany read GetCompany; default;
|
||
|
|
|
||
|
|
procedure AfterMissionInit();
|
||
|
|
procedure UpdateState(aTick: Cardinal);
|
||
|
|
procedure OwnerUpdate(aPlayer: TKMHandID);
|
||
|
|
procedure CreateCompany(aTargetPoint: TKMPoint; aGroups: TKMUnitGroupArray; aCompanyMode: TKMCompanyMode = cmAttack);
|
||
|
|
function IsGroupInAction(aGroup: TKMUnitGroup): Boolean;
|
||
|
|
|
||
|
|
procedure LogStatus(var aBalanceText: UnicodeString);
|
||
|
|
procedure Paint();
|
||
|
|
end;
|
||
|
|
|
||
|
|
const
|
||
|
|
// Houses in TARGET_HOUSES will be selected as a primary target (so the company will come to this point but it will not attack it because of this list)
|
||
|
|
TARGET_HOUSES: TKMHouseTypeSet = [htBarracks, htStore, htSchool, htTownhall];
|
||
|
|
// Houses in SCAN_HOUSES will be destroyed when they are in radius (it should also contain TARGET_HOUSES)
|
||
|
|
SCAN_HOUSES: TKMHouseTypeSet = [htWatchTower, htBarracks, htStore, htSchool, htTownhall];
|
||
|
|
// All houses for final stage of attack algorithm
|
||
|
|
ALL_HOUSES: TKMHouseTypeSet = HOUSES_VALID;
|
||
|
|
|
||
|
|
implementation
|
||
|
|
uses
|
||
|
|
Types,
|
||
|
|
KM_HandsCollection, KM_Terrain, KM_AIFields,
|
||
|
|
KM_NavMesh, KM_RenderAux,
|
||
|
|
KM_UnitWarrior, KM_AIParameters;
|
||
|
|
|
||
|
|
|
||
|
|
{ TAISquad }
|
||
|
|
constructor TAISquad.Create(aGroup: TKMUnitGroup);
|
||
|
|
begin
|
||
|
|
inherited Create;
|
||
|
|
fGroup := aGroup.GetPointer();
|
||
|
|
fOnPlace := True;
|
||
|
|
fTargetChanged := True;
|
||
|
|
fWalkTimeLimit := 0;
|
||
|
|
fAttackTimeLimit := 0;
|
||
|
|
fTargetHouse := nil;
|
||
|
|
fTargetUnit := nil;
|
||
|
|
//fFinalPosition := KMPointDir(KMPOINT_ZERO, dirNA);
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
destructor TAISquad.Destroy();
|
||
|
|
begin
|
||
|
|
gHands.CleanUpGroupPointer(fGroup);
|
||
|
|
gHands.CleanUpUnitPointer(fTargetUnit);
|
||
|
|
gHands.CleanUpHousePointer(fTargetHouse);
|
||
|
|
inherited;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
constructor TAISquad.Load(LoadStream: TKMemoryStream);
|
||
|
|
begin
|
||
|
|
inherited Create;
|
||
|
|
LoadStream.CheckMarker('Squad');
|
||
|
|
LoadStream.Read(fOnPlace);
|
||
|
|
LoadStream.Read(fTargetChanged);
|
||
|
|
LoadStream.Read(fFinalPosition);
|
||
|
|
LoadStream.Read(fWalkTimeLimit, SizeOf(fWalkTimeLimit));
|
||
|
|
LoadStream.Read(fAttackTimeLimit, SizeOf(fAttackTimeLimit));
|
||
|
|
//Subst on syncload
|
||
|
|
LoadStream.Read(fGroup, 4);
|
||
|
|
LoadStream.Read(fTargetUnit, 4);
|
||
|
|
LoadStream.Read(fTargetHouse, 4);
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TAISquad.Save(SaveStream: TKMemoryStream);
|
||
|
|
begin
|
||
|
|
SaveStream.PlaceMarker('Squad');
|
||
|
|
SaveStream.Write(fOnPlace);
|
||
|
|
SaveStream.Write(fTargetChanged);
|
||
|
|
SaveStream.Write(fFinalPosition);
|
||
|
|
SaveStream.Write(fWalkTimeLimit, SizeOf(fWalkTimeLimit));
|
||
|
|
SaveStream.Write(fAttackTimeLimit, SizeOf(fAttackTimeLimit));
|
||
|
|
if (fGroup <> nil) then
|
||
|
|
SaveStream.Write(fGroup.UID) //Store ID
|
||
|
|
else
|
||
|
|
SaveStream.Write(Integer(0));
|
||
|
|
if (fTargetUnit <> nil) then
|
||
|
|
SaveStream.Write(fTargetUnit.UID) //Store ID
|
||
|
|
else
|
||
|
|
SaveStream.Write(Integer(0));
|
||
|
|
if (fTargetHouse <> nil) then
|
||
|
|
SaveStream.Write(fTargetHouse.UID) //Store ID
|
||
|
|
else
|
||
|
|
SaveStream.Write(Integer(0));
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TAISquad.SyncLoad();
|
||
|
|
begin
|
||
|
|
fGroup := gHands.GetGroupByUID(Integer(fGroup) );
|
||
|
|
fTargetUnit := gHands.GetUnitByUID(Integer(fTargetUnit) );
|
||
|
|
fTargetHouse := gHands.GetHouseByUID(Integer(fTargetHouse) );
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TAISquad.SetTargetUnit(aUnit: TKMUnit);
|
||
|
|
begin
|
||
|
|
if (fTargetUnit = aUnit) then
|
||
|
|
Exit;
|
||
|
|
// Archers will reaim (+ reset animation) only in case that new target is in specific distance from existing target
|
||
|
|
if (aUnit <> nil) AND not aUnit.IsDeadOrDying then
|
||
|
|
begin
|
||
|
|
if (fGroup.GroupType = gtRanged)
|
||
|
|
AND (fTargetUnit <> nil)
|
||
|
|
//AND not fTargetUnit.IsDeadOrDying
|
||
|
|
AND (
|
||
|
|
(
|
||
|
|
(fTargetUnit is TKMUnitWarrior)
|
||
|
|
AND (aUnit is TKMUnitWarrior)
|
||
|
|
AND (TKMUnitWarrior(fTargetUnit).Group = TKMUnitWarrior(aUnit).Group)
|
||
|
|
) OR (
|
||
|
|
(KMDistanceSqr(fTargetUnit.CurrPosition, aUnit.CurrPosition) < sqr(AI_Par[ATTACK_SQUAD_ChangeTarget_DistTolerance]))
|
||
|
|
)
|
||
|
|
) then
|
||
|
|
Exit;
|
||
|
|
fTargetChanged := True;
|
||
|
|
gHands.CleanUpUnitPointer(fTargetUnit);
|
||
|
|
fTargetUnit := aUnit.GetPointer;
|
||
|
|
end
|
||
|
|
else
|
||
|
|
gHands.CleanUpUnitPointer(fTargetUnit); // aUnit = nil case
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TAISquad.SetTargetPosition(aLoc: TKMPointDir);
|
||
|
|
begin
|
||
|
|
fFinalPosition := aLoc;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TAISquad.SetTargetHouse(aHouse: TKMHouse);
|
||
|
|
begin
|
||
|
|
if (aHouse <> nil) then
|
||
|
|
SetTargetUnit(nil); // House have lower priority so if there is request for attacking house unit must be nil
|
||
|
|
if (fTargetHouse = aHouse) then
|
||
|
|
Exit;
|
||
|
|
fTargetChanged := True;
|
||
|
|
gHands.CleanUpHousePointer(fTargetHouse);
|
||
|
|
if (aHouse <> nil) then
|
||
|
|
fTargetHouse := aHouse.GetPointer;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TAISquad.SquadInFight(): Boolean;
|
||
|
|
begin
|
||
|
|
Result := not fGroup.CanTakeOrders;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TAISquad.GetGroupPosition(): TKMPoint;
|
||
|
|
begin
|
||
|
|
Result := fGroup.Position;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
// Update state of squad (group orders)
|
||
|
|
procedure TAISquad.UpdateState(aTick: Cardinal);
|
||
|
|
function GetTargetPosition(): TKMPoint;
|
||
|
|
var
|
||
|
|
K: Integer;
|
||
|
|
Dist, BestDist: Single;
|
||
|
|
BestTgt: TKMUnit;
|
||
|
|
G: TKMUnitGroup;
|
||
|
|
begin
|
||
|
|
Result := fTargetUnit.CurrPosition;
|
||
|
|
// Get closest warrior in enemy group if the squad is ranged
|
||
|
|
if (fGroup.GroupType = gtRanged) AND (fTargetUnit is TKMUnitWarrior) then
|
||
|
|
begin
|
||
|
|
BestTgt := nil;
|
||
|
|
G := TKMUnitWarrior(fTargetUnit).Group;
|
||
|
|
//Group will be nil if we target a warrior still walking out of barracks
|
||
|
|
if G <> nil then
|
||
|
|
begin
|
||
|
|
BestDist := 1E10;
|
||
|
|
for K := 0 to G.Count - 1 do
|
||
|
|
if not G.Members[K].IsDeadOrDying then
|
||
|
|
begin
|
||
|
|
Dist := KMDistanceSqr(fGroup.Position,G.Members[K].CurrPosition);
|
||
|
|
if (Dist < BestDist) then
|
||
|
|
begin
|
||
|
|
BestTgt := G.Members[K];
|
||
|
|
BestDist := Dist;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
if (BestTgt <> nil) then
|
||
|
|
begin
|
||
|
|
gHands.CleanUpUnitPointer(fTargetUnit);
|
||
|
|
fTargetUnit := BestTgt.GetPointer;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
var
|
||
|
|
ActPos, FinPos: TKMPoint;
|
||
|
|
begin
|
||
|
|
fOnPlace := False;
|
||
|
|
// Check group status and possibility to give order
|
||
|
|
if (fGroup = nil) OR fGroup.IsDead OR InFight then
|
||
|
|
Exit;
|
||
|
|
|
||
|
|
// Check targets
|
||
|
|
if (fTargetHouse <> nil) AND (fTargetHouse.IsDestroyed) then
|
||
|
|
gHands.CleanUpHousePointer(fTargetHouse);
|
||
|
|
if (fTargetUnit <> nil) AND (fTargetUnit.IsDead) then
|
||
|
|
gHands.CleanUpUnitPointer(fTargetUnit);
|
||
|
|
|
||
|
|
// Do order
|
||
|
|
ActPos := fGroup.Position;
|
||
|
|
if (fTargetUnit <> nil) then
|
||
|
|
begin
|
||
|
|
|
||
|
|
FinPos := GetTargetPosition();
|
||
|
|
if PlanPath(aTick, ActPos, FinPos, True, False) then // Check if squad is in the place
|
||
|
|
Group.OrderWalk(FinPos, True, wtokAISquad, FinalPosition.Dir)
|
||
|
|
else if
|
||
|
|
fTargetChanged // Target has been changed
|
||
|
|
OR (fGroup.GroupType <> gtRanged) // If not ranged then send a command to attack every update
|
||
|
|
OR (KMDistanceSqr(FinPos,ActPos) > 12*12) // If ranged send attack command only until shoot distance to prevent reset of the shooting animation
|
||
|
|
OR (fGroup.Order in [goNone, goWalkTo]) // If ranged send attack command also if target is already in range but previous command was not attack something
|
||
|
|
OR (fAttackTimeLimit < aTick) // If ranged send attack command every X ticks to force at least first line of archers to shoot
|
||
|
|
then
|
||
|
|
begin
|
||
|
|
fAttackTimeLimit := aTick + Round(AI_Par[ATTACK_SQUAD_ChangeTarget_Delay]);
|
||
|
|
fTargetChanged := False;
|
||
|
|
Group.OrderAttackUnit(fTargetUnit, True);
|
||
|
|
end;
|
||
|
|
fOnPlace := True;
|
||
|
|
end
|
||
|
|
else if (fTargetHouse <> nil) then
|
||
|
|
begin
|
||
|
|
FinPos := fTargetHouse.Position;
|
||
|
|
if PlanPath(aTick, ActPos, FinPos, False, True) then
|
||
|
|
Group.OrderWalk(FinPos, True, wtokAISquad, FinalPosition.Dir)
|
||
|
|
else if (fGroup.GroupType <> gtRanged) OR fTargetChanged OR (fAttackTimeLimit < aTick) then
|
||
|
|
begin
|
||
|
|
fAttackTimeLimit := aTick + Round(AI_Par[ATTACK_SQUAD_ChangeTarget_Delay]);
|
||
|
|
fTargetChanged := False;
|
||
|
|
Group.OrderAttackHouse(fTargetHouse, True);
|
||
|
|
end;
|
||
|
|
fOnPlace := True;
|
||
|
|
end
|
||
|
|
else
|
||
|
|
begin
|
||
|
|
FinPos := FinalPosition.Loc;
|
||
|
|
if PlanPath(aTick, ActPos, FinPos, False, False) then
|
||
|
|
Group.OrderWalk(FinPos, True, wtokAISquad, FinalPosition.Dir)
|
||
|
|
else if not KMSamePoint(Group.Position, FinalPosition.Loc) then // Dont repeat order and let archers fire
|
||
|
|
Group.OrderWalk(FinalPosition.Loc, True, wtokAISquad, FinalPosition.Dir);
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TAISquad.PlanPath(aTick: Cardinal; var aActualPosition, aTargetPosition: TKMPoint; aOrderAttack: Boolean = False; aOrderDestroy: Boolean = False): Boolean;
|
||
|
|
var
|
||
|
|
InitPolygon, ClosestPolygon, Distance: Word;
|
||
|
|
I: Integer;
|
||
|
|
SQRDist: Single;
|
||
|
|
PointPath: TKMPointArray;
|
||
|
|
begin
|
||
|
|
Result := False;
|
||
|
|
fOnPlace := False;
|
||
|
|
SQRDist := KMDistanceSqr(aActualPosition, aTargetPosition);
|
||
|
|
// Time limit (time limit MUST be always set by higher rank (platoon))
|
||
|
|
if (not (aOrderAttack OR aOrderDestroy) AND (fWalkTimeLimit < aTick)) // Time limit is set to 0 in case that unit attack something
|
||
|
|
// Target position is reached
|
||
|
|
OR (SQRDist < sqr(AI_Par[ATTACK_SQUAD_TargetReached_Position]))
|
||
|
|
// Target unit is close
|
||
|
|
OR (aOrderAttack AND (SQRDist < sqr(AI_Par[ATTACK_SQUAD_TargetReached_Unit])))
|
||
|
|
// Target house is close
|
||
|
|
OR (aOrderDestroy AND (SQRDist < sqr(AI_Par[ATTACK_SQUAD_TargetReached_House])))
|
||
|
|
// Archers should start fire as soon as possible
|
||
|
|
OR ((aOrderAttack OR aOrderDestroy) AND (fGroup.GroupType = gtRanged) AND (SQRDist < sqr(AI_Par[ATTACK_SQUAD_TargetReached_RangedSquad]))) then
|
||
|
|
begin
|
||
|
|
fOnPlace := True;
|
||
|
|
Exit;
|
||
|
|
end;
|
||
|
|
// Plan path with respect to enemy presence
|
||
|
|
if gAIFields.NavMesh.Pathfinding.AvoidEnemyRoute(Group.Owner, Group.GroupType, aActualPosition, aTargetPosition, Distance, PointPath) then
|
||
|
|
begin
|
||
|
|
if (Distance < 5) then // Just to be sure (default value of pathfinding, if is group stuck it should not influence platoon)
|
||
|
|
begin
|
||
|
|
fOnPlace := True;
|
||
|
|
Exit;
|
||
|
|
end
|
||
|
|
else
|
||
|
|
begin
|
||
|
|
InitPolygon := gAIFields.NavMesh.KMPoint2Polygon[aActualPosition];
|
||
|
|
I := Length(PointPath)-2; // Skip next polygon -> fluent movement
|
||
|
|
repeat
|
||
|
|
aTargetPosition := PointPath[ Max(0, I) ];
|
||
|
|
ClosestPolygon := gAIFields.NavMesh.KMPoint2Polygon[ aTargetPosition ];
|
||
|
|
I := I - 1;
|
||
|
|
until (I < 0) OR ( (InitPolygon <> ClosestPolygon)
|
||
|
|
AND (tpWalk in gTerrain.Land^[aTargetPosition.Y, aTargetPosition.X].Passability)
|
||
|
|
AND (aOrderAttack OR (KMDistanceSqr(aActualPosition, aTargetPosition) > sqr(AI_Par[ATTACK_SQUAD_MinWalkingDistance]))) );
|
||
|
|
|
||
|
|
{$IFDEF DEBUG_NewAI}
|
||
|
|
DEBUGPointPath := PointPath;
|
||
|
|
{$ENDIF}
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
Result := True;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
{ TAICompany }
|
||
|
|
constructor TAICompany.Create(aOwner: TKMHandID; aCompanyMode: TKMCompanyMode);
|
||
|
|
var
|
||
|
|
GT: TKMGroupType;
|
||
|
|
begin
|
||
|
|
inherited Create;
|
||
|
|
fOwner := aOwner;
|
||
|
|
|
||
|
|
fCompanyMode := aCompanyMode;
|
||
|
|
fTargetPoint := KMPOINT_ZERO;
|
||
|
|
fTargetOwner := -1;
|
||
|
|
fTargetHouse := nil;
|
||
|
|
fTargetUnit := nil;
|
||
|
|
|
||
|
|
for GT := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
fSquads[GT] := TKMList.Create();
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
destructor TAICompany.Destroy();
|
||
|
|
var
|
||
|
|
GT: TKMGroupType;
|
||
|
|
begin
|
||
|
|
gHands.CleanUpUnitPointer(fTargetUnit);
|
||
|
|
gHands.CleanUpHousePointer(fTargetHouse);
|
||
|
|
for GT := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
fSquads[GT].Free;
|
||
|
|
inherited;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
constructor TAICompany.Load(LoadStream: TKMemoryStream);
|
||
|
|
var
|
||
|
|
I,Cnt: Integer;
|
||
|
|
GT: TKMGroupType;
|
||
|
|
begin
|
||
|
|
inherited Create;
|
||
|
|
LoadStream.CheckMarker('Company');
|
||
|
|
LoadStream.Read(fOwner);
|
||
|
|
LoadStream.Read(fPathPosition);
|
||
|
|
LoadStream.Read(fScanPosition);
|
||
|
|
LoadStream.Read(fCompanyMode, SizeOf(TKMCompanyMode));
|
||
|
|
LoadStream.Read(fTargetPoint);
|
||
|
|
LoadStream.Read(fTargetOwner);
|
||
|
|
LoadStream.Read(fTargetUnit, 4);
|
||
|
|
LoadStream.Read(fTargetHouse, 4);
|
||
|
|
|
||
|
|
for GT := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
begin
|
||
|
|
fSquads[GT] := TKMList.Create();
|
||
|
|
LoadStream.Read(Cnt);
|
||
|
|
for I := 0 to Cnt - 1 do
|
||
|
|
fSquads[GT].Add( TAISquad.Load(LoadStream) );
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TAICompany.Save(SaveStream: TKMemoryStream);
|
||
|
|
var
|
||
|
|
I,Cnt: Integer;
|
||
|
|
GT: TKMGroupType;
|
||
|
|
begin
|
||
|
|
SaveStream.PlaceMarker('Company');
|
||
|
|
SaveStream.Write(fOwner);
|
||
|
|
SaveStream.Write(fPathPosition);
|
||
|
|
SaveStream.Write(fScanPosition);
|
||
|
|
SaveStream.Write(fCompanyMode, SizeOf(TKMCompanyMode));
|
||
|
|
SaveStream.Write(fTargetPoint);
|
||
|
|
SaveStream.Write(fTargetOwner);
|
||
|
|
if (fTargetUnit <> nil) then
|
||
|
|
SaveStream.Write(fTargetUnit.UID) //Store ID
|
||
|
|
else
|
||
|
|
SaveStream.Write(Integer(0));
|
||
|
|
if (fTargetHouse <> nil) then
|
||
|
|
SaveStream.Write(fTargetHouse.UID) //Store ID
|
||
|
|
else
|
||
|
|
SaveStream.Write(Integer(0));
|
||
|
|
|
||
|
|
for GT := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
begin
|
||
|
|
Cnt := fSquads[GT].Count;
|
||
|
|
SaveStream.Write(Cnt);
|
||
|
|
for I := 0 to Cnt - 1 do
|
||
|
|
TAISquad( Squads[GT].Items[I] ).Save(SaveStream);
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TAICompany.SyncLoad();
|
||
|
|
var
|
||
|
|
I: Integer;
|
||
|
|
GT: TKMGroupType;
|
||
|
|
begin
|
||
|
|
fTargetUnit := gHands.GetUnitByUID(Integer(fTargetUnit) );
|
||
|
|
fTargetHouse := gHands.GetHouseByUID(Integer(fTargetHouse) );
|
||
|
|
for GT := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
for I := 0 to fSquads[GT].Count - 1 do
|
||
|
|
TAISquad( Squads[GT].Items[I] ).SyncLoad();
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TAICompany.SetTarget(aHouse: TKMHouse; aUnit: TKMUnit = nil): Boolean;
|
||
|
|
begin
|
||
|
|
Result := False;
|
||
|
|
if (aHouse = nil) AND (aUnit <> nil) then
|
||
|
|
begin
|
||
|
|
gHands.CleanUpUnitPointer(fTargetUnit);
|
||
|
|
fTargetUnit := aUnit.GetPointer;
|
||
|
|
fTargetOwner := aUnit.Owner;
|
||
|
|
Result := True;
|
||
|
|
end
|
||
|
|
else if (aHouse <> nil) AND (aUnit = nil) then
|
||
|
|
begin
|
||
|
|
gHands.CleanUpHousePointer(fTargetHouse);
|
||
|
|
fTargetHouse := aHouse.GetPointer;
|
||
|
|
fTargetOwner := aHouse.Owner;
|
||
|
|
Result := True;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TAICompany.GetTargetPosition(): TKMPoint;
|
||
|
|
begin
|
||
|
|
Result := KMPOINT_ZERO;
|
||
|
|
if (fTargetHouse <> nil) AND not fTargetHouse.IsDestroyed then
|
||
|
|
Result := fTargetHouse.Position
|
||
|
|
else if (fTargetUnit <> nil) AND not fTargetUnit.IsDead then
|
||
|
|
Result := fTargetUnit.CurrPosition;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
// Update state of company, detect targets, call functions for orders
|
||
|
|
procedure TAICompany.UpdateState(aTick: Cardinal);
|
||
|
|
// Check if target still exist
|
||
|
|
function CheckPrimaryTarget(): Boolean;
|
||
|
|
begin
|
||
|
|
Result := False;
|
||
|
|
if (fTargetHouse <> nil) then
|
||
|
|
if fTargetHouse.IsDestroyed then
|
||
|
|
gHands.CleanUpHousePointer(fTargetHouse)
|
||
|
|
else
|
||
|
|
Result := True;
|
||
|
|
if (fTargetUnit <> nil) then
|
||
|
|
if fTargetUnit.IsDead then
|
||
|
|
gHands.CleanUpUnitPointer(fTargetUnit)
|
||
|
|
else
|
||
|
|
Result := True;
|
||
|
|
end;
|
||
|
|
// Check if are all squads in position and we can continue
|
||
|
|
function SquadsInPosition(): Boolean;
|
||
|
|
var
|
||
|
|
I: Integer;
|
||
|
|
GT: TKMGroupType;
|
||
|
|
Squad: TAISquad;
|
||
|
|
begin
|
||
|
|
Result := True;
|
||
|
|
for GT := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
for I := fSquads[GT].Count - 1 downto 0 do
|
||
|
|
begin
|
||
|
|
Squad := fSquads[GT].Items[I];
|
||
|
|
if (Squad = nil) OR (Squad.Group = nil) OR Squad.Group.IsDead then
|
||
|
|
fSquads[GT].Delete(I)
|
||
|
|
else
|
||
|
|
Result := Result AND Squad.OnPlace;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
// Filter target houses according to house type and influence
|
||
|
|
procedure FilterTargetHouses(var aHA: TKMHouseArray);
|
||
|
|
var
|
||
|
|
K, Cnt: Integer;
|
||
|
|
begin
|
||
|
|
if (fCompanyMode = cmDestruction) then
|
||
|
|
Exit;
|
||
|
|
Cnt := 0;
|
||
|
|
for K := Low(aHA) to High(aHA) do
|
||
|
|
if (aHA[K].HouseType in SCAN_HOUSES) OR
|
||
|
|
(gAIFields.Influences.OwnPoint[fOwner, aHA[K].Position] > 100) OR
|
||
|
|
(gAIFields.Influences.GetBestAllianceOwnership(fOwner, aHA[K].Position.X, aHA[K].Position.Y, atAlly) > 50) then
|
||
|
|
begin
|
||
|
|
aHA[Cnt] := aHA[K];
|
||
|
|
Cnt := Cnt + 1;
|
||
|
|
end;
|
||
|
|
SetLength(aHA,Cnt);
|
||
|
|
end;
|
||
|
|
// Update state of Squads
|
||
|
|
procedure UpdateSquadsState();
|
||
|
|
var
|
||
|
|
I: Integer;
|
||
|
|
GT: TKMGroupType;
|
||
|
|
begin
|
||
|
|
for GT := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
for I := fSquads[GT].Count - 1 downto 0 do
|
||
|
|
TAISquad( fSquads[GT].Items[I] ).UpdateState(aTick);
|
||
|
|
end;
|
||
|
|
var
|
||
|
|
InPosition: Boolean;
|
||
|
|
{$IFDEF DEBUG_NewAI}
|
||
|
|
I: Integer;
|
||
|
|
{$ENDIF}
|
||
|
|
ScanRad, SQRRadius: Single;
|
||
|
|
HA: TKMHouseArray;// Target houses in radius
|
||
|
|
UA: TKMUnitArray; // Target closest soldiers in radius
|
||
|
|
UGA: TKMUnitGroupArray; // Group of closest soldiers in radius (reference to UA)
|
||
|
|
ClosestHouse: TKMHouse;
|
||
|
|
ClosestUnit: TKMUnit;
|
||
|
|
begin
|
||
|
|
if (SquadCnt = 0) then
|
||
|
|
Exit;
|
||
|
|
|
||
|
|
InPosition := SquadsInPosition();
|
||
|
|
|
||
|
|
// Check target
|
||
|
|
ClosestHouse := nil;
|
||
|
|
ClosestUnit := nil;
|
||
|
|
|
||
|
|
fScanPosition := GetPosition(SQRRadius);
|
||
|
|
|
||
|
|
ScanRad := sqr(AI_Par[ATTACK_COMPANY_AttackRadius]) + Min(50*50, SQRRadius);
|
||
|
|
HA := gHands.GetHousesInRadius(ScanPosition, sqr(AI_Par[ATTACK_COMPANY_AttackRadius]), fOwner, atEnemy, ALL_HOUSES, False);
|
||
|
|
UA := gHands.GetGroupsMemberInRadius(ScanPosition, ScanRad, fOwner, atEnemy, UGA);
|
||
|
|
FilterTargetHouses(HA);
|
||
|
|
|
||
|
|
{$IFDEF DEBUG_NewAI}
|
||
|
|
DEBUGScanRad := ScanRad;
|
||
|
|
SetLength(DEBUG_UA_POINTS, Length(UA));
|
||
|
|
SetLength(DEBUG_UGA_POINTS, Length(UA));
|
||
|
|
for I := 0 to Length(UA) - 1 do
|
||
|
|
begin
|
||
|
|
DEBUG_UA_POINTS[I] := UA[I].CurrPosition;
|
||
|
|
DEBUG_UGA_POINTS[I] := UGA[I].Position;
|
||
|
|
end;
|
||
|
|
{$ENDIF}
|
||
|
|
|
||
|
|
// Actualize target
|
||
|
|
if not CheckPrimaryTarget() then
|
||
|
|
begin
|
||
|
|
if (Length(UA) > 0) then
|
||
|
|
ClosestUnit := UA[0];
|
||
|
|
if (Length(HA) > 0) then
|
||
|
|
ClosestHouse := HA[0];
|
||
|
|
if not SetTarget(ClosestHouse, ClosestUnit) then
|
||
|
|
begin
|
||
|
|
if ActualizeTarget(fScanPosition, ClosestHouse, ClosestUnit, False) then
|
||
|
|
begin
|
||
|
|
if not gAIFields.Supervisor.FFA
|
||
|
|
OR ((ClosestHouse <> nil) AND (ClosestHouse.Owner = fTargetOwner))
|
||
|
|
OR ((ClosestUnit <> nil) AND (ClosestUnit.Owner = fTargetOwner)) then
|
||
|
|
SetTarget(ClosestHouse, ClosestUnit)
|
||
|
|
else
|
||
|
|
fState := csIdle;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
// Give new orders
|
||
|
|
if OrderToAttack(ScanPosition, UA, UGA, HA) then
|
||
|
|
fState := csAttack
|
||
|
|
else
|
||
|
|
begin
|
||
|
|
fState := csWalking;
|
||
|
|
if InPosition AND not OrderMove(aTick, PathPosition) then
|
||
|
|
fState := csIdle;
|
||
|
|
end;
|
||
|
|
|
||
|
|
UpdateSquadsState();
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TAICompany.OrderToAttack(aActualPosition: TKMPoint; UA: TKMUnitArray; UGA: TKMUnitGroupArray; HA: TKMHouseArray): Boolean;
|
||
|
|
type
|
||
|
|
TCompanyInfo = record
|
||
|
|
GTCnt: array[GROUP_TYPE_MIN..GROUP_TYPE_MAX] of Word; // Count of soldiers sorted by GroupType
|
||
|
|
end;
|
||
|
|
var
|
||
|
|
AvailableSquads: TKMSquadsArray;
|
||
|
|
CompanyInfo: TCompanyInfo;
|
||
|
|
TargetU: TKMTargetSelection;
|
||
|
|
|
||
|
|
// Find only available squads (we can give orders to them) and return if there are any
|
||
|
|
function FindAvailableSquads(var aSquadsInCombat: Boolean): Boolean;
|
||
|
|
var
|
||
|
|
Output: Boolean;
|
||
|
|
I: Integer;
|
||
|
|
GT: TKMGroupType;
|
||
|
|
Squad: TAISquad;
|
||
|
|
begin
|
||
|
|
Output := False;
|
||
|
|
aSquadsInCombat := False;
|
||
|
|
FillChar(CompanyInfo, SizeOf(CompanyInfo), #0);
|
||
|
|
for GT := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
begin
|
||
|
|
AvailableSquads[GT].Count := 0;
|
||
|
|
SetLength(AvailableSquads[GT].Squads, fSquads[GT].Count);
|
||
|
|
end;
|
||
|
|
for GT := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
for I := 0 to fSquads[GT].Count - 1 do
|
||
|
|
begin
|
||
|
|
Squad := fSquads[GT].Items[I];
|
||
|
|
Inc(CompanyInfo.GTCnt[GT], Squad.Group.Count);
|
||
|
|
if not Squad.InFight then
|
||
|
|
begin
|
||
|
|
Output := True;
|
||
|
|
// Make sure that unit will not hunt target over the whole map and better stay inside company
|
||
|
|
if (GT <> gtRanged) then // ranged units are fixed in Squad class(set / reset target cause that they dont shoot and just reset animation)
|
||
|
|
begin
|
||
|
|
Squad.TargetUnit := nil;
|
||
|
|
//Squad.TargetHouse := nil; // This cause same problem like with archers
|
||
|
|
end;
|
||
|
|
AvailableSquads[GT].Squads[ AvailableSquads[GT].Count ] := Squad;
|
||
|
|
Inc(AvailableSquads[GT].Count);
|
||
|
|
end
|
||
|
|
else
|
||
|
|
aSquadsInCombat := True;
|
||
|
|
end;
|
||
|
|
Result := Output;
|
||
|
|
end;
|
||
|
|
|
||
|
|
// Calculate evaluation to enemy groups in specific radius
|
||
|
|
procedure EvalEnemyGroupsInRadius();
|
||
|
|
const
|
||
|
|
INIT_DIST = 1000000;
|
||
|
|
SQR_MAX_RANGE_INTEREST = 12*12;
|
||
|
|
SQR_RANGE_OF_PROJECTILES = 11*11; // Sqr( Max( Max(RANGE_BOWMAN_MAX,RANGE_ARBALETMAN_MAX),RANGE_SLINGSHOT_MAX ) );
|
||
|
|
var
|
||
|
|
Polygon: Word;
|
||
|
|
I,K,L: Integer;
|
||
|
|
SqrDist, SqrClosestDist, SqrClosestDistToRanged, CloseCombatProtection: Single;
|
||
|
|
GT: TKMGroupType;
|
||
|
|
Squad: TAISquad;
|
||
|
|
GroupsInFightArr: TKMUnitGroupArray;
|
||
|
|
begin
|
||
|
|
SetLength(TargetU, Length(UA));
|
||
|
|
for I := 0 to Length(TargetU) - 1 do
|
||
|
|
begin
|
||
|
|
// Get closest distance to Ranged groups and all groups
|
||
|
|
SqrClosestDist := INIT_DIST;
|
||
|
|
SqrClosestDistToRanged := INIT_DIST;
|
||
|
|
for GT := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
for K := 0 to fSquads[GT].Count - 1 do
|
||
|
|
begin
|
||
|
|
Squad := fSquads[GT].Items[K];
|
||
|
|
L := 0;
|
||
|
|
while (L < Squad.Group.Count) do
|
||
|
|
begin
|
||
|
|
SqrDist := KMDistanceSqr(Squad.Group.Members[L].CurrPosition, UA[I].CurrPosition);
|
||
|
|
if (SqrDist < SqrClosestDist) then
|
||
|
|
SqrClosestDist := SqrDist;
|
||
|
|
if (GT = gtRanged) AND (SqrDist < SqrClosestDistToRanged) then
|
||
|
|
SqrClosestDistToRanged := SqrDist;
|
||
|
|
L := L + 3;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
// Calculate threat level
|
||
|
|
with TargetU[I] do
|
||
|
|
begin
|
||
|
|
Index := I;
|
||
|
|
CenterPoint := UGA[I].Position;
|
||
|
|
CloseThreat := 0;
|
||
|
|
DistantThreat := 0;
|
||
|
|
if (SqrClosestDist < SQR_MAX_RANGE_INTEREST) then
|
||
|
|
begin
|
||
|
|
CloseThreat := UGA[I].Count; // Threat level for close combat units
|
||
|
|
DistantThreat := CloseThreat; // Threat level for ranged group type
|
||
|
|
if (UGA[I].GroupType = gtRanged) then
|
||
|
|
begin
|
||
|
|
// Calculate distant threat level (determine whether archers are shooting at our troops)
|
||
|
|
CloseThreat := CloseThreat * AI_Par[ATTACK_COMPANY_AttackRangedGain];
|
||
|
|
DistantThreat := DistantThreat * Byte(SQR_RANGE_OF_PROJECTILES > SqrClosestDist) * AI_Par[ATTACK_COMPANY_AttackRangedGain];
|
||
|
|
// Close threat level is computed with using influences
|
||
|
|
CloseCombatProtection := 0;
|
||
|
|
Polygon := gAIFields.NavMesh.KMPoint2Polygon[ UGA[I].Position ];
|
||
|
|
for GT := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
if (GT <> gtRanged) then // Skip ranged units so AI have a tendency to walk through them if they are alone
|
||
|
|
CloseCombatProtection := CloseCombatProtection + gAIFields.Influences.EnemyGroupPresence[fOwner, Polygon, GT]; // Tune parameters
|
||
|
|
if (DistantThreat < CloseCombatProtection) then // Ranged units are well protected -> try shoot them
|
||
|
|
CloseThreat := Byte(SqrClosestDistToRanged <= SQR_RANGE_OF_PROJECTILES) // Lowest priority (=1) but dont ignore them (if we have no archers call infantry)
|
||
|
|
else // Ranged units are not protected -> attack with close combat units
|
||
|
|
DistantThreat := Byte(SqrClosestDist <= SQR_RANGE_OF_PROJECTILES); // Lowest priority but dont ignore them (if they are in range of archers shoot at them)
|
||
|
|
end
|
||
|
|
else
|
||
|
|
begin
|
||
|
|
// Close combat threat level (in case that group already fight agaist more soldiers there is 0 threat)
|
||
|
|
// In case that group kills ranged units threat must be increased
|
||
|
|
if UGA[I].InFightAgaistGroups(GroupsInFightArr) then
|
||
|
|
begin
|
||
|
|
for K := 0 to Length(GroupsInFightArr) - 1 do
|
||
|
|
if (GroupsInFightArr[K].GroupType = gtRanged) then
|
||
|
|
CloseThreat := CloseThreat + GroupsInFightArr[K].Count
|
||
|
|
else
|
||
|
|
CloseThreat := CloseThreat - GroupsInFightArr[K].Count;
|
||
|
|
end
|
||
|
|
// If group does not fight check how far is from ranged units
|
||
|
|
else
|
||
|
|
CloseThreat := CloseThreat + Max(0, (sqr(AI_Par[ATTACK_COMPANY_ProtectRangedRadius]) - SqrClosestDistToRanged) * AI_Par[ATTACK_COMPANY_ProtectRangedGain] );
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
// Distribute available groups agaist enemies
|
||
|
|
function SelectTargetGroups(): Boolean;
|
||
|
|
const
|
||
|
|
INIT_THREAT = -10000000;
|
||
|
|
INIT_DISTANCE = 10000000;
|
||
|
|
BEST_TARGET: array[GROUP_TYPE_MIN..GROUP_TYPE_MAX] of array[0..3] of TKMGroupType = (
|
||
|
|
(gtMelee, gtRanged, gtMounted, gtAntiHorse), // against gtMelee
|
||
|
|
(gtMelee, gtRanged, gtAntiHorse, gtMounted), // against gtAntiHorse
|
||
|
|
(gtMounted, gtRanged, gtMelee, gtAntiHorse), // against gtRanged
|
||
|
|
(gtAntiHorse, gtRanged, gtMounted, gtMelee) // against gtMounted
|
||
|
|
);
|
||
|
|
var
|
||
|
|
Output: Boolean;
|
||
|
|
I,K,L,BestPrioIdx, TargetIdx, ThreatIdx, Cnt: Integer;
|
||
|
|
Threat, HighestThreat, Dist, BestDist, BestPrioCoeff: Single;
|
||
|
|
GT, BestGT: TKMGroupType;
|
||
|
|
Squad: TAISquad;
|
||
|
|
begin
|
||
|
|
Output := False;
|
||
|
|
if (Length(TargetU) <= 0) then
|
||
|
|
Exit;
|
||
|
|
|
||
|
|
// Ranged groups view: our ranged unit -> select target => each unit should fire
|
||
|
|
GT := gtRanged;
|
||
|
|
ThreatIdx := 0; // Only for compiler
|
||
|
|
for I := AvailableSquads[GT].Count - 1 downto 0 do
|
||
|
|
begin
|
||
|
|
Squad := AvailableSquads[GT].Squads[I];
|
||
|
|
HighestThreat := INIT_THREAT;
|
||
|
|
//BestDist := 0;
|
||
|
|
Dist := 0;
|
||
|
|
for K := 0 to Length(TargetU) - 1 do
|
||
|
|
if (TargetU[K].DistantThreat > 0) then
|
||
|
|
begin
|
||
|
|
Dist := KMDistanceSqr(TAISquad(fSquads[GT].Items[I]).Position, UA[ TargetU[K].Index ].CurrPosition);
|
||
|
|
Threat := TargetU[K].DistantThreat - Dist;
|
||
|
|
if (Threat > HighestThreat) then
|
||
|
|
begin
|
||
|
|
//BestDist := Dist;
|
||
|
|
HighestThreat := Threat;
|
||
|
|
ThreatIdx := K;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
if (HighestThreat <> INIT_THREAT) then
|
||
|
|
begin
|
||
|
|
Output := True;
|
||
|
|
Squad.TargetUnit := UA[ TargetU[ThreatIdx].Index ];
|
||
|
|
if (Dist >= sqr(AI_Par[ATTACK_COMPANY_ProtectRangedAllInDist])) then // If is enemy too cloose aim him but dont decrease threat level so next part of code can call close combat support
|
||
|
|
TargetU[ThreatIdx].DistantThreat := TargetU[ThreatIdx].DistantThreat - Squad.Group.Count;
|
||
|
|
// Unit will be targeted by Ranged group -> if there was minimal priority for close combat then remove it
|
||
|
|
if (TargetU[ThreatIdx].CloseThreat = 1) then
|
||
|
|
TargetU[ThreatIdx].CloseThreat := 0;
|
||
|
|
Dec(AvailableSquads[GT].Count);
|
||
|
|
AvailableSquads[GT].Squads[I] := AvailableSquads[GT].Squads[ AvailableSquads[GT].Count ];
|
||
|
|
end
|
||
|
|
else
|
||
|
|
Squad.TargetUnit := nil;
|
||
|
|
end;
|
||
|
|
|
||
|
|
// Close combat groups view: enemy units -> select oponent => keep something in reserve
|
||
|
|
Cnt := Length(TargetU);
|
||
|
|
BestGT := gtMelee;
|
||
|
|
TargetIdx := 0; // For compiler
|
||
|
|
BestPrioIdx := 0; // For compiler
|
||
|
|
BestPrioCoeff := 1; // For compiler
|
||
|
|
repeat
|
||
|
|
// Find top threat
|
||
|
|
HighestThreat := INIT_THREAT;
|
||
|
|
for I := Cnt - 1 downto 0 do
|
||
|
|
begin
|
||
|
|
if (TargetU[I].CloseThreat <= 0) then
|
||
|
|
begin
|
||
|
|
Cnt := Cnt - 1;
|
||
|
|
TargetU[I] := TargetU[Cnt];
|
||
|
|
end
|
||
|
|
else if (TargetU[I].CloseThreat > HighestThreat) then
|
||
|
|
begin
|
||
|
|
HighestThreat := TargetU[I].CloseThreat;
|
||
|
|
ThreatIdx := I;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
if (Cnt <= 0) then
|
||
|
|
break;
|
||
|
|
// Find best close combat opponent
|
||
|
|
BestDist := INIT_DISTANCE;
|
||
|
|
for K := 0 to 3 do
|
||
|
|
begin
|
||
|
|
GT := BEST_TARGET[ UGA[ TargetU[ThreatIdx].Index ].GroupType, K ];
|
||
|
|
for L := 0 to AvailableSquads[GT].Count - 1 do
|
||
|
|
begin
|
||
|
|
Dist := KMDistanceSqr(AvailableSquads[GT].Squads[L].Position, UA[ TargetU[ThreatIdx].Index ].CurrPosition) + K * AI_Par[ATTACK_COMPANY_GroupTypePenalization];
|
||
|
|
if (Dist < BestDist) then
|
||
|
|
begin
|
||
|
|
BestDist := Dist;
|
||
|
|
TargetIdx := L;
|
||
|
|
BestGT := GT;
|
||
|
|
BestPrioIdx := K;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
// Set target
|
||
|
|
if (BestDist < INIT_DISTANCE) then
|
||
|
|
begin
|
||
|
|
Output := True;
|
||
|
|
Squad := AvailableSquads[BestGT].Squads[TargetIdx];
|
||
|
|
Squad.TargetUnit := UA[ TargetU[ThreatIdx].Index ];
|
||
|
|
// Update threat level and call another squad if needed
|
||
|
|
case BestPrioIdx of
|
||
|
|
0: BestPrioCoeff := AI_Par[ATTACK_COMPANY_DecreaseThreat_Prio1];
|
||
|
|
1: BestPrioCoeff := AI_Par[ATTACK_COMPANY_DecreaseThreat_Prio2];
|
||
|
|
2: BestPrioCoeff := AI_Par[ATTACK_COMPANY_DecreaseThreat_Prio3];
|
||
|
|
3: BestPrioCoeff := AI_Par[ATTACK_COMPANY_DecreaseThreat_Prio4];
|
||
|
|
end;
|
||
|
|
TargetU[ThreatIdx].CloseThreat := TargetU[ThreatIdx].CloseThreat - Squad.Group.Count * BestPrioCoeff;
|
||
|
|
Dec(AvailableSquads[BestGT].Count);
|
||
|
|
AvailableSquads[BestGT].Squads[TargetIdx] := AvailableSquads[BestGT].Squads[ AvailableSquads[BestGT].Count ];
|
||
|
|
end;
|
||
|
|
until (Cnt <= 0) OR (BestDist >= INIT_DISTANCE);
|
||
|
|
Result := Output;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function OrderAttackHouse(): Boolean;
|
||
|
|
const
|
||
|
|
INIT_THREAT = 1000000;
|
||
|
|
LIMIT_RANGED_DESTROY_ALL_HOUSES = 6; // Archers will start to shoot at other houses if there is not enough close combat units in company
|
||
|
|
CLOSE_COMBAT_DISTANCE_LIMIT = 12; // Order to attack house starts at this distance (close combat groups; watchtowers have exception)
|
||
|
|
MAX_SOLDIERS_VS_HOUSE = 12;
|
||
|
|
MAX_ARCHERS_VS_TOWER = 9;
|
||
|
|
ATTACK_WATCHTOWER_WITH_CLOSE_COMBAT_DIST = 5; // Attack watchtower with close combat units
|
||
|
|
MAX_CLOSE_COMBAT_VS_UNIT_DIST = 3; // Kill citizens in this radius around house (workers tries to repair house)
|
||
|
|
var
|
||
|
|
Output: Boolean;
|
||
|
|
TargetIdx: Word;
|
||
|
|
I,K: Integer;
|
||
|
|
Dist, BestDist: Single;
|
||
|
|
GT: TKMGroupType;
|
||
|
|
U: TKMUnit;
|
||
|
|
GroupAttackCnt: TKMWordArray;
|
||
|
|
begin
|
||
|
|
Output := False;
|
||
|
|
SetLength(GroupAttackCnt, Length(HA));
|
||
|
|
FillChar(GroupAttackCnt[0], SizeOf(GroupAttackCnt[0]) * Length(GroupAttackCnt), #0);
|
||
|
|
|
||
|
|
// Target watchtowers with archers
|
||
|
|
GT := gtRanged;
|
||
|
|
TargetIdx := 0; // Only for compiler
|
||
|
|
for I := AvailableSquads[GT].Count - 1 downto 0 do
|
||
|
|
begin
|
||
|
|
BestDist := INIT_THREAT;
|
||
|
|
for K := 0 to Length(HA) - 1 do
|
||
|
|
if (HA[K].HouseType = htWatchTower)
|
||
|
|
AND (GroupAttackCnt[K] < MAX_ARCHERS_VS_TOWER)
|
||
|
|
AND (HA[K].CheckWareIn(wtStone) > 1)
|
||
|
|
AND HA[K].HasOwner then // Ignore towers without stone and without recruit inside
|
||
|
|
begin
|
||
|
|
Dist := KMDistanceSqr(AvailableSquads[GT].Squads[I].Position, HA[K].Position);
|
||
|
|
if (Dist < BestDist) then
|
||
|
|
begin
|
||
|
|
BestDist := Dist;
|
||
|
|
TargetIdx := K;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
if (BestDist <> INIT_THREAT) then
|
||
|
|
begin
|
||
|
|
Output := True;
|
||
|
|
Inc(GroupAttackCnt[TargetIdx], AvailableSquads[GT].Squads[I].Group.Count);
|
||
|
|
// Find and kill workers who want to repair house
|
||
|
|
U := gAIFields.Eye.GetClosestUnitAroundHouse(HA[TargetIdx].HouseType, HA[TargetIdx].Position, AvailableSquads[GT].Squads[I].Position);
|
||
|
|
if (U <> nil) then
|
||
|
|
AvailableSquads[GT].Squads[I].TargetUnit := U
|
||
|
|
else
|
||
|
|
AvailableSquads[GT].Squads[I].TargetHouse := HA[TargetIdx];
|
||
|
|
Dec(AvailableSquads[GT].Count);
|
||
|
|
AvailableSquads[GT].Squads[I] := AvailableSquads[GT].Squads[ AvailableSquads[GT].Count ];
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
// Target everything else with close combat units
|
||
|
|
for GT := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
begin
|
||
|
|
if GT = gtRanged then // Ignore ranged units if we have enough close combat support (avoid friendly fire)
|
||
|
|
with CompanyInfo do
|
||
|
|
if (GTCnt[gtMelee] + GTCnt[gtAntiHorse] + GTCnt[gtMounted] > LIMIT_RANGED_DESTROY_ALL_HOUSES) then
|
||
|
|
continue;
|
||
|
|
for I := AvailableSquads[GT].Count - 1 downto 0 do
|
||
|
|
begin
|
||
|
|
BestDist := INIT_THREAT;
|
||
|
|
for K := 0 to Length(HA) - 1 do
|
||
|
|
if (
|
||
|
|
(HA[K].HouseType <> htWatchTower)
|
||
|
|
OR ( KMDistanceSqr(AvailableSquads[GT].Squads[I].Position, HA[K].Position) < sqr(ATTACK_WATCHTOWER_WITH_CLOSE_COMBAT_DIST))
|
||
|
|
)
|
||
|
|
AND (GroupAttackCnt[K] < MAX_SOLDIERS_VS_HOUSE) then
|
||
|
|
begin
|
||
|
|
Dist := KMDistanceSqr(AvailableSquads[GT].Squads[I].Position, HA[K].Position);
|
||
|
|
if (Dist < BestDist) AND (Dist <= sqr(CLOSE_COMBAT_DISTANCE_LIMIT)) then
|
||
|
|
begin
|
||
|
|
BestDist := Dist;
|
||
|
|
TargetIdx := K;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
if (BestDist <> INIT_THREAT) then
|
||
|
|
begin
|
||
|
|
Output := True;
|
||
|
|
GroupAttackCnt[TargetIdx] := GroupAttackCnt[TargetIdx] + AvailableSquads[GT].Squads[I].Group.Count;
|
||
|
|
// Find and kill workers who want to repair house
|
||
|
|
U := gAIFields.Eye.GetClosestUnitAroundHouse(HA[TargetIdx].HouseType, HA[TargetIdx].Position, AvailableSquads[GT].Squads[I].Position);
|
||
|
|
if (U <> nil)
|
||
|
|
AND ((GT = gtRanged) OR (KMDistanceSqr(U.CurrPosition, AvailableSquads[GT].Squads[I].Position) < sqr(MAX_CLOSE_COMBAT_VS_UNIT_DIST))) then
|
||
|
|
AvailableSquads[GT].Squads[I].TargetUnit := U
|
||
|
|
else
|
||
|
|
AvailableSquads[GT].Squads[I].TargetHouse := HA[TargetIdx];
|
||
|
|
Dec(AvailableSquads[GT].Count);
|
||
|
|
AvailableSquads[GT].Squads[I] := AvailableSquads[GT].Squads[ AvailableSquads[GT].Count ];
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
Result := Output;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure Regroup();
|
||
|
|
var
|
||
|
|
Cnt: Word;
|
||
|
|
I: Integer;
|
||
|
|
GT: TKMGroupType;
|
||
|
|
Squad: TAISquad;
|
||
|
|
Positions: TKMPointArray;
|
||
|
|
InitPolygons: TKMWordArray;
|
||
|
|
begin
|
||
|
|
Cnt := 0;
|
||
|
|
for GT := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
Cnt := Cnt + AvailableSquads[GT].Count;
|
||
|
|
|
||
|
|
if (Cnt = 0) then
|
||
|
|
Exit;
|
||
|
|
|
||
|
|
// Get positions around center of scan area
|
||
|
|
SetLength(InitPolygons,1);
|
||
|
|
InitPolygons[0] := gAIFields.NavMesh.KMPoint2Polygon[ fScanPosition ];
|
||
|
|
gAIFields.NavMesh.Positioning.FindPositions(Cnt, Round(AI_Par[ATTACK_COMPANY_MinCombatSpacing]), InitPolygons, Positions);
|
||
|
|
|
||
|
|
Cnt := 0;
|
||
|
|
for GT := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
for I := 0 to AvailableSquads[GT].Count - 1 do
|
||
|
|
begin
|
||
|
|
Squad := AvailableSquads[GT].Squads[I];
|
||
|
|
Squad.FinalPosition := KMPointDir( Positions[Cnt], KMGetDirection( Squad.Position, Positions[Cnt] ) );
|
||
|
|
// Time limit does not make sence for unit in combat
|
||
|
|
//Squad.TimeLimit := gGameParams.GameTick + KMDistanceAbs(Position, Squads[ClosestIdx].Position) * TIME_PER_A_TILE;
|
||
|
|
Cnt := Cnt + 1;
|
||
|
|
if (Cnt >= Length(Positions)) then
|
||
|
|
Exit;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
var
|
||
|
|
FreeSquads, SquadsInCombat, AttackGroup, AttackHouse: Boolean;
|
||
|
|
begin
|
||
|
|
// Find Available squads (groups who obey orders / are not in combat)
|
||
|
|
FreeSquads := FindAvailableSquads(SquadsInCombat);
|
||
|
|
AttackGroup := False;
|
||
|
|
AttackHouse := False;
|
||
|
|
|
||
|
|
// Attack groups in radius
|
||
|
|
if (Length(UA) > 0) AND FreeSquads then
|
||
|
|
begin
|
||
|
|
// Calculate threat level of each enemy group in radius
|
||
|
|
EvalEnemyGroupsInRadius();
|
||
|
|
|
||
|
|
// Attack hostile units in radius
|
||
|
|
AttackGroup := SelectTargetGroups();
|
||
|
|
end;
|
||
|
|
|
||
|
|
// Attack houses in radius
|
||
|
|
if (Length(HA) > 0) AND FreeSquads then
|
||
|
|
AttackHouse := OrderAttackHouse();
|
||
|
|
|
||
|
|
// Move troops in reserve into new center point
|
||
|
|
if AttackGroup OR AttackHouse then
|
||
|
|
Regroup();
|
||
|
|
|
||
|
|
Result := AttackGroup OR AttackHouse OR SquadsInCombat;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TAICompany.OrderMove(aTick: Cardinal; aActualPosition: TKMPoint): Boolean;
|
||
|
|
|
||
|
|
function GetInitPolygons(aCnt: Integer; var aPointPath: TKMPointArray): TKMWordArray;
|
||
|
|
var
|
||
|
|
InitPolygon: Word;
|
||
|
|
I, Idx: Integer;
|
||
|
|
InitPolygons: TKMWordArray;
|
||
|
|
begin
|
||
|
|
// Get initial point on the path (it must be in specific distance from actual position to secure smooth moving of the company)
|
||
|
|
I := Length(aPointPath)-1;
|
||
|
|
while (I >= 0) AND (KMDistanceAbs(aActualPosition, aPointPath[I]) < AI_Par[ATTACK_COMPANY_MinimumMovement]) do
|
||
|
|
I := I - 1;
|
||
|
|
// Make sure that platoon will not start in actual polygon but position will be moved forward
|
||
|
|
InitPolygon := gAIFields.NavMesh.KMPoint2Polygon[ aActualPosition ];
|
||
|
|
repeat
|
||
|
|
fPathPosition := aPointPath[ Max(0, I) ];
|
||
|
|
I := I - 1;
|
||
|
|
until (InitPolygon <> gAIFields.NavMesh.KMPoint2Polygon[ fPathPosition ]) OR (I < 0);
|
||
|
|
|
||
|
|
I := Max(0,I + 1); // I = 0 we are in polygon of our target
|
||
|
|
// Get several init polygons
|
||
|
|
SetLength(InitPolygons, Min(Length(aPointPath) - I, Max(1, Round(AI_Par[ATTACK_COMPANY_Positioning_InitPolyCnt]))) );
|
||
|
|
Idx := 0;
|
||
|
|
while (I >= 0) AND (Idx < Length(InitPolygons)) do
|
||
|
|
begin
|
||
|
|
InitPolygons[Idx] := gAIFields.NavMesh.KMPoint2Polygon[ aPointPath[I] ];
|
||
|
|
Idx := Idx + 1;
|
||
|
|
I := I - 2;
|
||
|
|
end;
|
||
|
|
if (Idx <> Length(InitPolygons)) then
|
||
|
|
SetLength(InitPolygons, Idx);
|
||
|
|
Result := InitPolygons;
|
||
|
|
end;
|
||
|
|
|
||
|
|
procedure SetOrders(aCnt: Integer; aTargetPosition: TKMPoint; var aPositions: TKMPointArray);
|
||
|
|
const
|
||
|
|
INFLUENCE_DANGER = 20;
|
||
|
|
INIT_DIST = 1000;
|
||
|
|
var
|
||
|
|
I, K, Dist, ClosestDist, ClosestIdx, SelectedTime: Integer;
|
||
|
|
Dir: TKMDirection;
|
||
|
|
Position: TKMPoint;
|
||
|
|
GT: TKMGroupType;
|
||
|
|
Squads: array of TAISquad;
|
||
|
|
AvailableSquads: TBooleanArray;
|
||
|
|
TagPositions: TKMPointTagList;
|
||
|
|
begin
|
||
|
|
// Get influence and set speed of move
|
||
|
|
SelectedTime := Round(AI_Par[ATTACK_COMPANY_TimePerATile_Slow]);
|
||
|
|
if (gAIFields.Influences.GetBestAllianceOwnership(fOwner, gAIFields.NavMesh.KMPoint2Polygon[aActualPosition], atEnemy) < INFLUENCE_DANGER) then
|
||
|
|
SelectedTime := Round(AI_Par[ATTACK_COMPANY_TimePerATile_Fast]);
|
||
|
|
// Get formations and set orders
|
||
|
|
// Sort points according to aTargetPosition (it should work better than aActualPosition)
|
||
|
|
TagPositions := TKMPointTagList.Create;
|
||
|
|
try
|
||
|
|
for I := 0 to Min(aCnt, High(aPositions)) do
|
||
|
|
TagPositions.Add(aPositions[I], KMDistanceAbs(aTargetPosition, aPositions[I]));
|
||
|
|
TagPositions.SortByTag();
|
||
|
|
//fScanPosition := TagPositions.Items[TagPositions.Count - 1];
|
||
|
|
//fPathPosition := TagPositions.Items[(TagPositions.Count - 1) div 2];
|
||
|
|
Dir := KMGetDirection( aActualPosition, fPathPosition );
|
||
|
|
|
||
|
|
SetLength(Squads, aCnt);
|
||
|
|
SetLength(AvailableSquads, aCnt);
|
||
|
|
K := 0;
|
||
|
|
for GT := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
for I := 0 to fSquads[GT].Count - 1 do
|
||
|
|
begin
|
||
|
|
Squads[K] := fSquads[GT].Items[I];
|
||
|
|
AvailableSquads[K] := True;
|
||
|
|
K := K + 1;
|
||
|
|
end;
|
||
|
|
ClosestIdx := 0; // Only for compiler
|
||
|
|
for I := TagPositions.Count - 1 downto 0 do
|
||
|
|
begin
|
||
|
|
Position := TagPositions.Items[I];
|
||
|
|
ClosestDist := INIT_DIST;
|
||
|
|
for K := 0 to Length(Squads) - 1 do
|
||
|
|
if AvailableSquads[K] then
|
||
|
|
begin
|
||
|
|
Dist := KMDistanceAbs(Position, Squads[K].Position);
|
||
|
|
if (Dist < ClosestDist) then
|
||
|
|
begin
|
||
|
|
ClosestDist := Dist;
|
||
|
|
ClosestIdx := K;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
if (ClosestDist = INIT_DIST) then
|
||
|
|
break;
|
||
|
|
|
||
|
|
Squads[ClosestIdx].TargetUnit := nil;
|
||
|
|
Squads[ClosestIdx].TargetHouse := nil;
|
||
|
|
Squads[ClosestIdx].FinalPosition := KMPointDir(Position, Dir);
|
||
|
|
Squads[ClosestIdx].WalkTimeLimit := aTick + KMDistanceAbs(Position, Squads[ClosestIdx].Position) * SelectedTime;
|
||
|
|
AvailableSquads[ClosestIdx] := False;
|
||
|
|
end;
|
||
|
|
finally
|
||
|
|
TagPositions.Free;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
var
|
||
|
|
Distance: Word;
|
||
|
|
Cnt: Integer;
|
||
|
|
TargetPoint: TKMPoint;
|
||
|
|
PointPath, Positions: TKMPointArray;
|
||
|
|
InitPolygons: TKMWordArray;
|
||
|
|
begin
|
||
|
|
if (fCompanyMode = cmDefence) then
|
||
|
|
begin
|
||
|
|
TargetPoint := fTargetPoint;
|
||
|
|
Result := not KMSamePoint(TargetPoint, aActualPosition);
|
||
|
|
end
|
||
|
|
else
|
||
|
|
begin
|
||
|
|
TargetPoint := GetTargetPosition();
|
||
|
|
Result := not KMSamePoint(TargetPoint, KMPOINT_ZERO);
|
||
|
|
end;
|
||
|
|
|
||
|
|
if not Result then
|
||
|
|
Exit;
|
||
|
|
|
||
|
|
// Try find path to the target point
|
||
|
|
//if gAIFields.NavMesh.Pathfinding.ShortestRoute(aActualPosition, TargetPoint, Distance, PointPath) then
|
||
|
|
if gAIFields.NavMesh.Pathfinding.AvoidTrafficRoute(fOwner, aActualPosition, TargetPoint, Distance, PointPath) then
|
||
|
|
begin
|
||
|
|
Cnt := SquadCnt(); // Count of groups in company
|
||
|
|
|
||
|
|
// Get init polygons -> polygons on road which are base for army positioning (groups will get order to walk there)
|
||
|
|
InitPolygons := GetInitPolygons(Cnt, PointPath);
|
||
|
|
|
||
|
|
// Get positions around init polygons
|
||
|
|
gAIFields.NavMesh.Positioning.FindPositions(Cnt, Round(AI_Par[ATTACK_COMPANY_MinWalkSpacing]), InitPolygons, Positions);
|
||
|
|
|
||
|
|
// Compute distance of new positions and select the closest group to walk there
|
||
|
|
SetOrders(Cnt, TargetPoint, Positions);
|
||
|
|
end;
|
||
|
|
|
||
|
|
{$IFDEF DEBUG_NewAI}
|
||
|
|
DEBUGPointPath := PointPath;
|
||
|
|
{$ENDIF}
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TAICompany.SquadCnt(aTypes: TKMGroupTypeSet = GROUP_TYPES_VALID): Word;
|
||
|
|
var
|
||
|
|
GT: TKMGroupType;
|
||
|
|
begin
|
||
|
|
Result := 0;
|
||
|
|
for GT in aTypes do
|
||
|
|
Result := Result + fSquads[GT].Count;
|
||
|
|
end;
|
||
|
|
|
||
|
|
function TAICompany.WarriorCnt(aTypes: TKMGroupTypeSet = GROUP_TYPES_VALID): Word;
|
||
|
|
var
|
||
|
|
K: Integer;
|
||
|
|
G: TKMUnitGroup;
|
||
|
|
GT: TKMGroupType;
|
||
|
|
begin
|
||
|
|
Result := 0;
|
||
|
|
for GT in aTypes do
|
||
|
|
for K := 0 to fSquads[GT].Count - 1 do
|
||
|
|
begin
|
||
|
|
G := TAISquad(fSquads[GT].Items[K]).Group;
|
||
|
|
if (G <> nil) then
|
||
|
|
Result := Result + G.Count;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TAICompany.AddSquad(aGroup: TKMUnitGroup);
|
||
|
|
begin
|
||
|
|
fSquads[ aGroup.GroupType ].Add( TAISquad.Create(aGroup) );
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TAICompany.DeleteSquad(aGT: TKMGroupType; aIdx: Integer);
|
||
|
|
var
|
||
|
|
Squad: TAISquad;
|
||
|
|
begin
|
||
|
|
Squad := fSquads[aGT].Items[aIdx];
|
||
|
|
Squad.Free;
|
||
|
|
fSquads[aGT].Delete(aIdx);
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TAICompany.InitCompany();
|
||
|
|
begin
|
||
|
|
fPathPosition := GetPosition();
|
||
|
|
fScanPosition := fPathPosition;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TAICompany.GetPosition(): TKMPoint;
|
||
|
|
var
|
||
|
|
I, Count: Integer;
|
||
|
|
Output: TKMPoint;
|
||
|
|
G: TKMGroupType;
|
||
|
|
begin
|
||
|
|
Output := KMPOINT_ZERO;
|
||
|
|
Count := 0;
|
||
|
|
for G := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
for I := 0 to fSquads[G].Count-1 do
|
||
|
|
begin
|
||
|
|
Output := KMPointAdd(Output, TAISquad(fSquads[G].Items[I]).Group.Position);
|
||
|
|
Count := Count + 1;
|
||
|
|
end;
|
||
|
|
|
||
|
|
if (Count > 0) then
|
||
|
|
begin
|
||
|
|
Output.X := Round( Output.X / Count );
|
||
|
|
Output.Y := Round( Output.Y / Count );
|
||
|
|
end;
|
||
|
|
|
||
|
|
// If we cannot walk there choose first group instead
|
||
|
|
I := gAIFields.NavMesh.KMPoint2Polygon[Output];
|
||
|
|
if not (gTerrain.CheckPassability(Output, tpWalk)
|
||
|
|
AND ((gAIFields.Influences.PresenceAllGroups[ fOwner, I ] > 0)
|
||
|
|
//OR (gAIFields.Influences.ArmyTraffic[ fOwner, I ] > 0)
|
||
|
|
)
|
||
|
|
) then
|
||
|
|
for G := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
if (fSquads[G].Count > 0) then
|
||
|
|
begin
|
||
|
|
Output := TAISquad(fSquads[G].Items[0]).Group.Position;
|
||
|
|
break;
|
||
|
|
end;
|
||
|
|
Result := Output;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TAICompany.GetPosition(var aSQRRadius: Single): TKMPoint;
|
||
|
|
var
|
||
|
|
I: Integer;
|
||
|
|
G: TKMGroupType;
|
||
|
|
begin
|
||
|
|
Result := GetPosition();
|
||
|
|
|
||
|
|
aSQRRadius := 0;
|
||
|
|
for G := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
for I := 0 to fSquads[G].Count-1 do
|
||
|
|
aSQRRadius := Max(aSQRRadius, KMDistanceSqr(Result, TAISquad(fSquads[G].Items[I]).Group.Position));
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TAICompany.ActualizeTarget(aInitPosition: TKMPoint; var aTargetHouse: TKMHouse; var aTargetUnit: TKMUnit; aAimCivilians: Boolean = True): Boolean;
|
||
|
|
var
|
||
|
|
TargetGroup: TKMUnitGroup;
|
||
|
|
begin
|
||
|
|
Result := False;
|
||
|
|
aTargetUnit := nil;
|
||
|
|
aTargetHouse := gHands.GetClosestHouse(aInitPosition, fOwner, atEnemy, TARGET_HOUSES, True);
|
||
|
|
if (aTargetHouse = nil) then
|
||
|
|
begin
|
||
|
|
TargetGroup := gHands.GetClosestGroup(aInitPosition, fOwner, atEnemy);
|
||
|
|
if (TargetGroup = nil) then
|
||
|
|
begin
|
||
|
|
if not aAimCivilians then
|
||
|
|
begin
|
||
|
|
aTargetHouse := gHands.GetClosestHouse(aInitPosition, fOwner, atEnemy, ALL_HOUSES, True);
|
||
|
|
if (aTargetHouse <> nil) then
|
||
|
|
fCompanyMode := cmDestruction;
|
||
|
|
end
|
||
|
|
else
|
||
|
|
begin
|
||
|
|
aTargetUnit := gHands.GetClosestUnit(aInitPosition, fOwner, atEnemy);
|
||
|
|
if (aTargetUnit = nil) then
|
||
|
|
Exit;
|
||
|
|
end;
|
||
|
|
end
|
||
|
|
else
|
||
|
|
aTargetUnit := TargetGroup.GetAliveMember;
|
||
|
|
end;
|
||
|
|
Result := True;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TAICompany.IsGroupInCompany(aGroup: TKMUnitGroup): Boolean;
|
||
|
|
var
|
||
|
|
I: Integer;
|
||
|
|
G: TKMGroupType;
|
||
|
|
begin
|
||
|
|
Result := True;
|
||
|
|
for G := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
for I := 0 to fSquads[G].Count-1 do
|
||
|
|
if (TAISquad(fSquads[G].Items[I]).Group = aGroup) then
|
||
|
|
Exit;
|
||
|
|
Result := False;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
{ TKMArmyAttack }
|
||
|
|
constructor TKMArmyAttack.Create(aOwner: TKMHandID);
|
||
|
|
begin
|
||
|
|
inherited Create;
|
||
|
|
fCompanies := TKMList.Create();
|
||
|
|
fOwner := aOwner;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
destructor TKMArmyAttack.Destroy;
|
||
|
|
begin
|
||
|
|
fCompanies.Free;
|
||
|
|
inherited;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TKMArmyAttack.Save(SaveStream: TKMemoryStream);
|
||
|
|
var
|
||
|
|
I: Integer;
|
||
|
|
begin
|
||
|
|
SaveStream.PlaceMarker('ArmyAttack');
|
||
|
|
SaveStream.Write(fOwner);
|
||
|
|
SaveStream.Write( Integer(Count) );
|
||
|
|
|
||
|
|
for I := 0 to Count - 1 do
|
||
|
|
Company[I].Save(SaveStream);
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TKMArmyAttack.Load(LoadStream: TKMemoryStream);
|
||
|
|
var
|
||
|
|
I, NewCount: Integer;
|
||
|
|
begin
|
||
|
|
LoadStream.CheckMarker('ArmyAttack');
|
||
|
|
LoadStream.Read(fOwner);
|
||
|
|
LoadStream.Read(NewCount);
|
||
|
|
|
||
|
|
for I := 0 to NewCount - 1 do
|
||
|
|
fCompanies.Add( TAICompany.Load(LoadStream) );
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TKMArmyAttack.SyncLoad();
|
||
|
|
var
|
||
|
|
I: Integer;
|
||
|
|
begin
|
||
|
|
for I := 0 to Count - 1 do
|
||
|
|
Company[I].SyncLoad();
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TKMArmyAttack.AfterMissionInit();
|
||
|
|
begin
|
||
|
|
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TKMArmyAttack.UpdateState(aTick: Cardinal);
|
||
|
|
function DetectEnemyPresence(aPoint: TKMPoint): Boolean;
|
||
|
|
var
|
||
|
|
Idx: Integer;
|
||
|
|
GT: TKMGroupType;
|
||
|
|
begin
|
||
|
|
Result := True;
|
||
|
|
Idx := gAIFields.NavMesh.KMPoint2Polygon[aPoint];
|
||
|
|
for GT := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
if (gAIFields.Influences.EnemyGroupPresence[ fOwner, Idx, GT ] > 0) then
|
||
|
|
Exit;
|
||
|
|
Result := False;
|
||
|
|
end;
|
||
|
|
var
|
||
|
|
I: Integer;
|
||
|
|
Company: TAICompany;
|
||
|
|
begin
|
||
|
|
for I := Count - 1 downto 0 do
|
||
|
|
begin
|
||
|
|
Company := fCompanies.Items[I];
|
||
|
|
Company.UpdateState(aTick);
|
||
|
|
if ( (Company.SquadCnt = 0) OR (Company.State = csIdle) ) then
|
||
|
|
//AND ( (Company.CompanyMode in [cmAttack, cmDestruction]) OR not DetectEnemyPresence(Company.TargetPoint) ) then
|
||
|
|
fCompanies.Remove( Company );
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TKMArmyAttack.OwnerUpdate(aPlayer: TKMHandID);
|
||
|
|
var
|
||
|
|
I: Integer;
|
||
|
|
begin
|
||
|
|
fOwner := aPlayer;
|
||
|
|
for I := Count - 1 downto 0 do
|
||
|
|
Company[I].Owner := aPlayer;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TKMArmyAttack.GetCount(): Integer;
|
||
|
|
begin
|
||
|
|
Result := fCompanies.Count;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TKMArmyAttack.GetCompany(aIdx: Integer): TAICompany;
|
||
|
|
begin
|
||
|
|
Result := fCompanies.Items[aIdx];
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
function TKMArmyAttack.IsGroupInAction(aGroup: TKMUnitGroup): Boolean;
|
||
|
|
var
|
||
|
|
I: Integer;
|
||
|
|
begin
|
||
|
|
Result := True;
|
||
|
|
for I := 0 to Count - 1 do
|
||
|
|
if Company[I].IsGroupInCompany(aGroup) then
|
||
|
|
Exit;
|
||
|
|
Result := False;
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TKMArmyAttack.CreateCompany(aTargetPoint: TKMPoint; aGroups: TKMUnitGroupArray; aCompanyMode: TKMCompanyMode = cmAttack);
|
||
|
|
procedure PrepareCompany(var aCompany: TAICompany; aTargetHouse: TKMHouse; aTargetUnit: TKMUnit);
|
||
|
|
var
|
||
|
|
I: Integer;
|
||
|
|
begin
|
||
|
|
aCompany.SetTarget(aTargetHouse, aTargetUnit);
|
||
|
|
for I := 0 to Length(aGroups) - 1 do
|
||
|
|
aCompany.AddSquad( aGroups[I] );
|
||
|
|
aCompany.InitCompany();
|
||
|
|
end;
|
||
|
|
var
|
||
|
|
Company: TAICompany;
|
||
|
|
TargetHouse: TKMHouse;
|
||
|
|
TargetUnit: TKMUnit;
|
||
|
|
begin
|
||
|
|
TargetHouse := nil;
|
||
|
|
TargetUnit := nil;
|
||
|
|
Company := TAICompany.Create(fOwner, aCompanyMode);
|
||
|
|
fCompanies.Add( Company );
|
||
|
|
if (aCompanyMode = cmAttack) AND Company.ActualizeTarget(aTargetPoint, TargetHouse, TargetUnit) then
|
||
|
|
begin
|
||
|
|
PrepareCompany(Company, TargetHouse, TargetUnit);
|
||
|
|
end
|
||
|
|
else if (aCompanyMode = cmDefence) then
|
||
|
|
begin
|
||
|
|
Company.TargetPoint := aTargetPoint;
|
||
|
|
PrepareCompany(Company, TargetHouse, TargetUnit);
|
||
|
|
end
|
||
|
|
else
|
||
|
|
fCompanies.Remove( Company );
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TKMArmyAttack.LogStatus(var aBalanceText: UnicodeString);
|
||
|
|
begin
|
||
|
|
//aBalanceText := '';
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
procedure TKMArmyAttack.Paint;
|
||
|
|
const
|
||
|
|
COLOR_WHITE = $FFFFFF;
|
||
|
|
COLOR_BLACK = $000000;
|
||
|
|
COLOR_GREEN = $00FF00;
|
||
|
|
COLOR_RED = $0000FF;
|
||
|
|
COLOR_YELLOW = $00FFFF;
|
||
|
|
COLOR_BLUE = $FF0000;
|
||
|
|
var
|
||
|
|
CompanySelected: Boolean;
|
||
|
|
K,L: Integer;
|
||
|
|
{$IFDEF DEBUG_NewAI}
|
||
|
|
M: Integer;
|
||
|
|
{$ENDIF}
|
||
|
|
Col, CompOpacity1, CompOpacity2, GroupOpacity1, GroupOpacity2: Cardinal;
|
||
|
|
Position: TKMPoint;
|
||
|
|
GT: TKMGroupType;
|
||
|
|
Company: TAICompany;
|
||
|
|
Squad: TAISquad;
|
||
|
|
begin
|
||
|
|
//if (fOwner <> gMySpectator.HandID) then // Show just 1 player (it prevents notification to be mess)
|
||
|
|
// Exit;
|
||
|
|
//if (fOwner <> 1) then // Show just 1 player (it prevents notification to be mess)
|
||
|
|
// Exit;
|
||
|
|
CompanySelected := False;
|
||
|
|
for K := 0 to Count - 1 do
|
||
|
|
begin
|
||
|
|
// Company status log
|
||
|
|
Company := fCompanies.Items[K];
|
||
|
|
CompOpacity1 := $09000000;
|
||
|
|
CompOpacity2 := $33000000;
|
||
|
|
// Check if company is selected
|
||
|
|
for GT := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
for L := Company.Squads[GT].Count - 1 downto 0 do
|
||
|
|
if ((gMySpectator.Selected is TKMUnitGroup) AND (gMySpectator.Selected = TAISquad(Company.Squads[GT].Items[L]).Group)) then
|
||
|
|
begin
|
||
|
|
CompanySelected := True;
|
||
|
|
CompOpacity1 := $20000000;
|
||
|
|
CompOpacity2 := $FF000000;
|
||
|
|
end;
|
||
|
|
|
||
|
|
// Pathfinding (squads) + targets
|
||
|
|
if CompanySelected then
|
||
|
|
for GT := GROUP_TYPE_MIN to GROUP_TYPE_MAX do
|
||
|
|
for L := Company.Squads[GT].Count - 1 downto 0 do
|
||
|
|
begin
|
||
|
|
Squad := Company.Squads[GT].Items[L];
|
||
|
|
// Highlight selected group and squad
|
||
|
|
GroupOpacity1 := $20000000;
|
||
|
|
GroupOpacity2 := $30000000;
|
||
|
|
if (gMySpectator.Selected is TKMUnitGroup) AND (gMySpectator.Selected = Squad.Group) then
|
||
|
|
begin
|
||
|
|
GroupOpacity1 := $99000000;
|
||
|
|
GroupOpacity2 := $FF000000;
|
||
|
|
end;
|
||
|
|
// Order position of group
|
||
|
|
Position := Squad.Group.OrderLoc.Loc;
|
||
|
|
gRenderAux.CircleOnTerrain(Position.X, Position.Y, 0.25, $FF000000 OR COLOR_YELLOW, $FF000000 OR COLOR_YELLOW);
|
||
|
|
// Position
|
||
|
|
Position := Squad.Position;
|
||
|
|
gRenderAux.CircleOnTerrain(Position.X, Position.Y, 0.75, GroupOpacity2 OR COLOR_GREEN, GroupOpacity2 OR COLOR_GREEN);
|
||
|
|
|
||
|
|
// Target unit
|
||
|
|
if (Squad.TargetUnit <> nil) then
|
||
|
|
begin
|
||
|
|
if not Squad.TargetUnit.IsDeadOrDying then
|
||
|
|
begin
|
||
|
|
gRenderAux.LineOnTerrain(Position, Squad.TargetUnit.CurrPosition, GroupOpacity2 OR COLOR_RED);
|
||
|
|
{$IFDEF DEBUG_NewAI}
|
||
|
|
if (Length(Squad.DEBUGPointPath) > 0) then
|
||
|
|
for M := Length(Squad.DEBUGPointPath)-2 downto 0 do
|
||
|
|
gRenderAux.LineOnTerrain(Squad.DEBUGPointPath[M+1], Squad.DEBUGPointPath[M], GroupOpacity1 OR COLOR_BLUE);
|
||
|
|
{$ENDIF}
|
||
|
|
end;
|
||
|
|
end
|
||
|
|
// Target house
|
||
|
|
else if (Squad.TargetHouse <> nil) then
|
||
|
|
if not Squad.TargetHouse.IsDestroyed then
|
||
|
|
begin
|
||
|
|
gRenderAux.LineOnTerrain(Position, Squad.TargetHouse.Position, GroupOpacity2 OR COLOR_RED);
|
||
|
|
{$IFDEF DEBUG_NewAI}
|
||
|
|
if (Length(Squad.DEBUGPointPath) > 0) then
|
||
|
|
for M := Length(Squad.DEBUGPointPath)-2 downto 0 do
|
||
|
|
gRenderAux.LineOnTerrain(Squad.DEBUGPointPath[M+1], Squad.DEBUGPointPath[M], GroupOpacity1 OR COLOR_BLUE);
|
||
|
|
{$ENDIF}
|
||
|
|
end;
|
||
|
|
// Pathfinding
|
||
|
|
if not KMSamePoint(Squad.FinalPosition.Loc,KMPOINT_ZERO) then
|
||
|
|
begin
|
||
|
|
//gRenderAux.LineOnTerrain(Position, Squad.FinalPosition.Loc, $AAFF5555);
|
||
|
|
gRenderAux.CircleOnTerrain(Squad.FinalPosition.Loc.X, Squad.FinalPosition.Loc.Y, 1, 0, GroupOpacity1 OR COLOR_BLUE);
|
||
|
|
{$IFDEF DEBUG_NewAI}
|
||
|
|
if (Length(Squad.DEBUGPointPath) > 0) then
|
||
|
|
for M := Length(Squad.DEBUGPointPath)-2 downto 0 do
|
||
|
|
gRenderAux.LineOnTerrain(Squad.DEBUGPointPath[M+1], Squad.DEBUGPointPath[M], GroupOpacity1 OR COLOR_BLUE);
|
||
|
|
{$ENDIF}
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
// Change color according to state
|
||
|
|
Col := 0; // For compiler
|
||
|
|
case Company.State of
|
||
|
|
csAttack: Col := COLOR_RED;
|
||
|
|
csWalking: Col := COLOR_BLUE;
|
||
|
|
csIdle: Col := COLOR_BLACK;
|
||
|
|
end;
|
||
|
|
|
||
|
|
// Mark company
|
||
|
|
Position := Company.ScanPosition;
|
||
|
|
{$IFDEF DEBUG_NewAI}
|
||
|
|
//gRenderAux.CircleOnTerrain(Position.X, Position.Y, Sqrt(Company.DEBUGScanRad), CompOpacity1 OR Col, CompOpacity2 OR Col);
|
||
|
|
{$ENDIF}
|
||
|
|
gRenderAux.CircleOnTerrain(Position.X, Position.Y, 3, CompOpacity1 OR Col, CompOpacity2 OR Col);
|
||
|
|
Position := Company.PathPosition;
|
||
|
|
gRenderAux.CircleOnTerrain(Position.X, Position.Y, 2, CompOpacity1 OR COLOR_GREEN, CompOpacity2 OR COLOR_GREEN);
|
||
|
|
|
||
|
|
// Target aim
|
||
|
|
{$IFDEF DEBUG_NewAI}
|
||
|
|
for L := 0 to Length(Company.DEBUGTargetU) - 1 do
|
||
|
|
with Company.DEBUGTargetU[L] do
|
||
|
|
begin
|
||
|
|
Position := CenterPoint;
|
||
|
|
// Close threath
|
||
|
|
Col := (Min($FF, Max(0,Round(CloseThreat)) ) shl 24) OR COLOR_RED;
|
||
|
|
gRenderAux.CircleOnTerrain(Position.X, Position.Y, 0.5, Col, $FF000000 OR COLOR_RED );
|
||
|
|
// Distant threath
|
||
|
|
Col := (Min($FF, Max(0,Round(DistantThreat)) ) shl 24) OR COLOR_BLACK;
|
||
|
|
gRenderAux.CircleOnTerrain(Position.X, Position.Y+1, 0.5, Col, $FF000000 OR COLOR_BLACK );
|
||
|
|
end;
|
||
|
|
{$ENDIF}
|
||
|
|
|
||
|
|
// Pathfinding (company)
|
||
|
|
{$IFDEF DEBUG_NewAI}
|
||
|
|
if (Length(Company.DEBUGPointPath) > 0) then
|
||
|
|
for L := Length(Company.DEBUGPointPath)-2 downto 0 do
|
||
|
|
gRenderAux.LineOnTerrain(Company.DEBUGPointPath[L+1], Company.DEBUGPointPath[L], CompOpacity2 OR COLOR_YELLOW);
|
||
|
|
{$ENDIF}
|
||
|
|
end;
|
||
|
|
//gRenderAux.LineOnTerrain(P1, P2, $CCFF2222);
|
||
|
|
//gRenderAux.CircleOnTerrain(CenterPlatoon.X, CenterPlatoon.Y, 5, $09FFFFFF, $99FFFFFF);
|
||
|
|
//gRenderAux.Quad(Loc.X, Loc.Y, Color);
|
||
|
|
end;
|
||
|
|
|
||
|
|
|
||
|
|
// JUNK
|
||
|
|
{
|
||
|
|
|
||
|
|
function SelectClosestTarget(aInitPoint: TKMPoint; var aTargetHouse: TKMHouse; var aTargetUnit: TKMUnit; var aTargetGroup: TKMUnitGroup): Boolean;
|
||
|
|
function TKMArmyAttack.SelectClosestTarget(aInitPoint: TKMPoint; var aTargetHouse: TKMHouse; var aTargetUnit: TKMUnit; var aTargetGroup: TKMUnitGroup): Boolean;
|
||
|
|
begin
|
||
|
|
Result := False;
|
||
|
|
aTargetHouse := nil;
|
||
|
|
aTargetUnit := nil;
|
||
|
|
aTargetGroup := nil;
|
||
|
|
// Compute common stuf first
|
||
|
|
aTargetHouse := gHands.GetClosestHouse(aInitPoint, fOwner, atEnemy, TARGET_HOUSES, False);
|
||
|
|
if (aTargetHouse = nil) then
|
||
|
|
begin
|
||
|
|
aTargetGroup := gHands.GetClosestGroup(aInitPoint, fOwner, atEnemy);
|
||
|
|
if (aTargetGroup = nil) then
|
||
|
|
begin
|
||
|
|
aTargetUnit := gHands.GetClosestUnit(aInitPoint, fOwner, atEnemy);
|
||
|
|
if (aTargetUnit = nil) then
|
||
|
|
Exit;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
|
||
|
|
Result := True;
|
||
|
|
end;
|
||
|
|
|
||
|
|
// Try find new target house in ownership area
|
||
|
|
function SelectNewTargetHouse(aPosition: TKMPoint): TKMHouse;
|
||
|
|
var
|
||
|
|
PLIdx: Word;
|
||
|
|
Enemies: TKMHandIndexArray;
|
||
|
|
HT: TKMHouseType;
|
||
|
|
H: TKMHouse;
|
||
|
|
begin
|
||
|
|
Result := nil;
|
||
|
|
Enemies := gAIFields.Influences.GetAllAllianceOwnership(fOwner, aPosition.X, aPosition.Y, atEnemy);
|
||
|
|
for PLIdx := 0 to Length(Enemies) - 1 do
|
||
|
|
for HT in TARGET_HOUSES do
|
||
|
|
begin
|
||
|
|
H := gHands[ Enemies[PLIdx] ].Houses.FindHouse(HT, aPosition.X, aPosition.Y);
|
||
|
|
if (H <> nil) then
|
||
|
|
begin
|
||
|
|
Result := H;
|
||
|
|
Exit;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
end;
|
||
|
|
//}
|
||
|
|
|
||
|
|
|
||
|
|
end.
|
||
|
|
|