sync: restore source from Google Drive
Sync changed files to Google Drive / sync (push) Successful in 1h45m13s
Sync changed files to Google Drive / sync (push) Successful in 1h45m13s
This commit is contained in:
1 parent
e2d8247e91
commit
7063d1b1c9
993 files changed
+949976
No files matched your search
+2589
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,37 @@
|
||||
unit KM_UnitGroupTypes;
|
||||
interface
|
||||
uses
|
||||
KM_Points;
|
||||
|
||||
type
|
||||
// Unit Group types
|
||||
TKMGroupInitialOrder = (gioNoOrder, gioSendGroup, gioAttackPosition);
|
||||
|
||||
TKMOrderWalkKind = (
|
||||
wtokNone,
|
||||
wtokPlayerOrder, //player order
|
||||
wtokHaltOrder, //could be player order, or AI order (as part of OrderRepeat)
|
||||
wtokFlagPoint, //after warrior get out of barracks/townhall
|
||||
wtokAISquad, wtokAIGotoDefencePos, wtokAIAttackCustomPos, //initiated by AI
|
||||
wtokMissionScript, wtokScript); //initiated by mission script or script
|
||||
TKMOrderWalkKindSet = set of TKMOrderWalkKind;
|
||||
|
||||
//* Group order
|
||||
TKMGroupOrder = (
|
||||
goNone, // Last order was executed and now we have nothing to do
|
||||
goWalkTo, // Ordered to walk somewhere or just change formation
|
||||
goAttackHouse, // Attack house
|
||||
goAttackUnit, // Attack specific unit
|
||||
goStorm // Run forward
|
||||
);
|
||||
|
||||
//MapEd allows to set order for a group that will be executed on mission start
|
||||
TKMMapEdOrder = record
|
||||
Order: TKMGroupInitialOrder;
|
||||
Pos: TKMPointDir;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
end.
|
||||
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
unit KM_UnitVisual;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
KM_ResTypes, KM_Points, KM_Defaults;
|
||||
|
||||
type
|
||||
TKMUnitVisualState = record
|
||||
public
|
||||
PositionF: TKMPointF; // Precise unit position
|
||||
Dir: TKMDirection;
|
||||
SlideX, SlideY: Single;
|
||||
Action: TKMUnitActionType;
|
||||
IsActGoInOutStarted: Boolean;
|
||||
InHouseType: TKMHouseType;
|
||||
AnimStep: Integer;
|
||||
AnimFraction: Single;
|
||||
|
||||
procedure SetFromUnit(aUnit: TObject);
|
||||
end;
|
||||
|
||||
// Purely visual thing. Split from TKMUnit to aviod mixup of game-logic and render Positions
|
||||
TKMUnitVisual = class
|
||||
private
|
||||
fUnit: TObject;
|
||||
fCurr: TKMUnitVisualState;
|
||||
fPrev: TKMUnitVisualState;
|
||||
fPrevPrev: TKMUnitVisualState;
|
||||
public
|
||||
constructor Create(aUnit: TObject);
|
||||
|
||||
function GetLerp(aLag: Single): TKMUnitVisualState;
|
||||
procedure UpdateState;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
Math, SysUtils,
|
||||
KromUtils,
|
||||
KM_Units, KM_UnitActionGoInOut,
|
||||
KM_Resource, KM_ResUnits, KM_ResHouses;
|
||||
|
||||
|
||||
{ TKMUnitVisualState }
|
||||
procedure TKMUnitVisualState.SetFromUnit(aUnit: TObject);
|
||||
var
|
||||
U: TKMUnit;
|
||||
slide: TKMPointF;
|
||||
begin
|
||||
U := TKMUnit(aUnit);
|
||||
PositionF := U.PositionF;
|
||||
Dir := U.Direction;
|
||||
slide := U.GetSlides;
|
||||
SlideX := slide.X;
|
||||
SlideY := slide.Y;
|
||||
AnimStep := U.AnimStep;
|
||||
AnimFraction := 0.0;
|
||||
|
||||
IsActGoInOutStarted := False;
|
||||
|
||||
if U.Action <> nil then
|
||||
begin
|
||||
Action := U.Action.ActionType;
|
||||
if (U.Action is TKMUnitActionGoInOut)
|
||||
and TKMUnitActionGoInOut(U.Action).IsStarted then
|
||||
IsActGoInOutStarted := True;
|
||||
end
|
||||
else
|
||||
Action := uaUnknown;
|
||||
|
||||
if U.InHouse <> nil then
|
||||
InHouseType := U.InHouse.HouseType
|
||||
else
|
||||
InHouseType := htNone;
|
||||
end;
|
||||
|
||||
|
||||
{ TKMUnitVisual }
|
||||
constructor TKMUnitVisual.Create(aUnit: TObject);
|
||||
begin
|
||||
inherited Create;
|
||||
|
||||
fUnit := TKMUnit(aUnit);
|
||||
fPrev.SetFromUnit(fUnit);
|
||||
fCurr.SetFromUnit(fUnit);
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitVisual.GetLerp(aLag: Single): TKMUnitVisualState;
|
||||
var
|
||||
animCount: Integer;
|
||||
prevSlideX, prevSlideY: Single;
|
||||
begin
|
||||
prevSlideX := fPrev.SlideX;
|
||||
prevSlideY := fPrev.SlideY;
|
||||
|
||||
// Special case for a unit who just started exiting the house
|
||||
// In that case fPrev slide was not calculated with door slide consideration
|
||||
// and thus fPrev.Slide would be 0 in the most cases (or in all cases)
|
||||
// That will make unit 'jump' from fPrev.PositionF to fCurr.PositionF + fCurr.Slide, which looks very bad
|
||||
if not fPrev.IsActGoInOutStarted
|
||||
and fCurr.IsActGoInOutStarted
|
||||
and (fPrev.InHouseType <> htNone) then
|
||||
begin
|
||||
// Just add doorway offset to the fPrev.Slide then
|
||||
prevSlideX := prevSlideX + gRes.Houses[fPrev.InHouseType].GetDoorwayOffset(axX);
|
||||
prevSlideY := prevSlideY + gRes.Houses[fPrev.InHouseType].GetDoorwayOffset(axY);
|
||||
end;
|
||||
|
||||
Result.PositionF := KMLerp(fCurr.PositionF, fPrev.PositionF, aLag);
|
||||
Result.SlideX := KromUtils.Lerp(fCurr.SlideX, prevSlideX, aLag);
|
||||
Result.SlideY := KromUtils.Lerp(fCurr.SlideY, prevSlideY, aLag);
|
||||
//If there's no lag, use the current state
|
||||
if aLag = 0.0 then
|
||||
begin
|
||||
Result.Dir := fCurr.Dir;
|
||||
Result.Action := fCurr.Action;
|
||||
Result.AnimStep := fCurr.AnimStep;
|
||||
Result.AnimFraction := 0.0;
|
||||
end
|
||||
else
|
||||
begin
|
||||
//Are we moving?
|
||||
if fCurr.PositionF <> fPrev.PositionF then
|
||||
begin
|
||||
//Always interpolate the animation if the unit is moving
|
||||
Result.AnimFraction := 1.0 - aLag;
|
||||
|
||||
//If we were still and just started moving
|
||||
if fPrevPrev.PositionF = fPrev.PositionF then
|
||||
begin
|
||||
//Since the unit starts moving without warning we need to backwards interpolate
|
||||
Result.Dir := fCurr.Dir;
|
||||
Result.Action := fCurr.Action;
|
||||
|
||||
//Find the previous anim step
|
||||
if fCurr.AnimStep = 0 then
|
||||
begin
|
||||
animCount := gRes.Units[TKMUnit(fUnit).UnitType].UnitAnim[Result.Action, Result.Dir].Count;
|
||||
Result.AnimStep := animCount - 1;
|
||||
end
|
||||
else
|
||||
Result.AnimStep := fCurr.AnimStep - 1;
|
||||
end
|
||||
else
|
||||
begin
|
||||
//Don't start a new action or change direction until the last one is 100% finished
|
||||
Result.Dir := fPrev.Dir;
|
||||
Result.Action := fPrev.Action;
|
||||
Result.AnimStep := fPrev.AnimStep;
|
||||
end;
|
||||
end
|
||||
else
|
||||
begin
|
||||
//Not moving. Don't start a new action or change direction until the last one is 100% finished
|
||||
Result.Dir := fPrev.Dir;
|
||||
Result.Action := fPrev.Action;
|
||||
Result.AnimStep := fPrev.AnimStep;
|
||||
//If action/dir/step is consistent we can interpolate the animation
|
||||
if (fCurr.Action = fPrev.Action) and (fCurr.Dir = fPrev.Dir) and (fCurr.AnimStep - fPrev.AnimStep = 1) then
|
||||
Result.AnimFraction := 1.0 - aLag
|
||||
else
|
||||
Result.AnimFraction := 0.0;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitVisual.UpdateState;
|
||||
begin
|
||||
fPrevPrev := fPrev;
|
||||
fPrev := fCurr;
|
||||
fCurr.SetFromUnit(fUnit);
|
||||
end;
|
||||
|
||||
end.
|
||||
+1161
File diff suppressed because it is too large.
Load diff
+690
@@ -0,0 +1,690 @@
|
||||
unit KM_UnitWorkPlan;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
KM_Defaults, KM_CommonClasses, KM_Points, KM_TerrainTypes, KM_Units,
|
||||
KM_ResTypes;
|
||||
|
||||
|
||||
const
|
||||
MAX_WORKPLAN = 24;
|
||||
|
||||
type
|
||||
TKMWorkPlanAllowedEvent = function(aProduct: TKMWareType): Boolean of object;
|
||||
|
||||
TKMUnitWorkPlan = class
|
||||
private
|
||||
fHome: TKMHouseType;
|
||||
fIssued: Boolean;
|
||||
function ChooseTree(const aLoc, aAvoid: TKMPoint; aRadius: Integer; aPlantAct: TKMPlantAct; aUnit: TKMUnit;
|
||||
out Tree: TKMPointDir; out PlantAct: TKMPlantAct): Boolean;
|
||||
procedure Clear;
|
||||
procedure WalkStyle(const aLoc2: TKMPointDir; aTo, aWork: TKMUnitActionType; aCycles, aDelay: Byte; aFrom: TKMUnitActionType; aScript: TKMGatheringScript);
|
||||
procedure SubActAdd(aAct: TKMHouseActionType; aCycles: Single);
|
||||
procedure ResourcePlan(Res1: TKMWareType; Qty1: Byte; Res2: TKMWareType; Qty2: Byte; Prod1: TKMWareType; Prod2: TKMWareType = wtNone);
|
||||
public
|
||||
HasToWalk: Boolean;
|
||||
Loc: TKMPoint;
|
||||
ActionWalkTo: TKMUnitActionType;
|
||||
ActionWorkType: TKMUnitActionType;
|
||||
WorkCyc: Integer;
|
||||
WorkDir: TKMDirection;
|
||||
GatheringScript: TKMGatheringScript;
|
||||
AfterWorkDelay: Integer;
|
||||
ActionWalkFrom: TKMUnitActionType;
|
||||
Resource1: TKMWareType; Count1: Byte;
|
||||
Resource2: TKMWareType; Count2: Byte;
|
||||
ActCount: Byte;
|
||||
HouseAct: array [0..MAX_WORKPLAN - 1] of record
|
||||
Act: TKMHouseActionType;
|
||||
TimeToWork: Word;
|
||||
end;
|
||||
Product1: TKMWareType; ProdCount1: Byte;
|
||||
Product2: TKMWareType; ProdCount2: Byte;
|
||||
AfterWorkIdle: Integer;
|
||||
ResourceDepleted: Boolean;
|
||||
public
|
||||
procedure FindPlan(aUnit: TKMUnit; aHome: TKMHouseType; aProduct: TKMWareType;
|
||||
aLoc: TKMPoint; aPlantAct: TKMPlantAct);
|
||||
function FindDifferentResource(aUnit: TKMUnit; aLoc: TKMPoint; const aAvoidLoc: TKMPoint): Boolean;
|
||||
property IsIssued: Boolean read fIssued;
|
||||
procedure Save(SaveStream: TKMemoryStream);
|
||||
procedure Load(LoadStream: TKMemoryStream);
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
SysUtils,
|
||||
KM_ResUnits, KM_Houses, KM_HouseWoodcutters,
|
||||
KM_ResHouses,
|
||||
KM_Terrain,
|
||||
KM_Resource, KM_CommonUtils;
|
||||
|
||||
|
||||
{Houses are only a place on map, they should not issue or perform tasks (except Training)
|
||||
Everything should be issued by units
|
||||
Where to go, which walking style, what to do on location, for how long
|
||||
How to go back in case success, incase bad luck
|
||||
What to take from supply, how much, take2, much2
|
||||
What to do, Work/Cycles, What resource to add to Output, how much
|
||||
E.g. CoalMine: Miner arrives at home and Idles for 5sec, then takes a work task (depending on ResOut count)
|
||||
Since Loc is 0,0 he immidietely skips to Phase X where he switches house to Work1 (and self busy for same framecount)
|
||||
Then Work2 and Work3 same way. Then adds resource to out and everything to Idle for 5sec.
|
||||
E.g. Farmer arrives at home and Idles for 5sec, then takes a work task (depending on ResOut count, HouseType and Need to sow corn)
|
||||
...... then switches house to Work1 (and self busy for same framecount)
|
||||
Then Work2 and Work3 same way. Then adds resource to out and everything to Idle for 5sec.}
|
||||
procedure TKMUnitWorkPlan.Clear;
|
||||
begin
|
||||
fIssued := False;
|
||||
HasToWalk := False;
|
||||
Loc := KMPOINT_ZERO;
|
||||
ActionWalkTo := uaWalk;
|
||||
ActionWorkType := uaWork;
|
||||
WorkCyc := 0;
|
||||
WorkDir := dirNA;
|
||||
GatheringScript := gsNone;
|
||||
AfterWorkDelay := 0;
|
||||
ActionWalkFrom := uaWalk;
|
||||
Resource1 := wtNone;
|
||||
Count1 := 0;
|
||||
Resource2 := wtNone;
|
||||
Count2 := 0;
|
||||
ActCount := 0;
|
||||
Product1 := wtNone;
|
||||
ProdCount1 := 0;
|
||||
Product2 := wtNone;
|
||||
ProdCount2 := 0;
|
||||
AfterWorkIdle := 0;
|
||||
ResourceDepleted := False;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitWorkPlan.WalkStyle(const aLoc2: TKMPointDir; aTo, aWork: TKMUnitActionType; aCycles,aDelay: Byte; aFrom: TKMUnitActionType; aScript: TKMGatheringScript);
|
||||
begin
|
||||
Loc := aLoc2.Loc;
|
||||
HasToWalk := True;
|
||||
ActionWalkTo := aTo;
|
||||
ActionWorkType := aWork;
|
||||
WorkCyc := aCycles;
|
||||
AfterWorkDelay := aDelay;
|
||||
GatheringScript := aScript;
|
||||
ActionWalkFrom := aFrom;
|
||||
WorkDir := aLoc2.Dir;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitWorkPlan.SubActAdd(aAct: TKMHouseActionType; aCycles: Single);
|
||||
begin
|
||||
HouseAct[ActCount].Act := aAct;
|
||||
HouseAct[ActCount].TimeToWork := Round(gRes.Houses[fHome].Anim[aAct].Count * aCycles);
|
||||
Inc(ActCount);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitWorkPlan.ResourcePlan(Res1: TKMWareType; Qty1: Byte; Res2: TKMWareType; Qty2: Byte; Prod1: TKMWareType; Prod2: TKMWareType = wtNone);
|
||||
begin
|
||||
Resource1 := Res1; Count1 := Qty1;
|
||||
Resource2 := Res2; Count2 := Qty2;
|
||||
Product1 := Prod1; ProdCount1 := gRes.Houses[fHome].ResProductionX;
|
||||
if Prod2=wtNone then exit;
|
||||
Product2 := Prod2; ProdCount2 := gRes.Houses[fHome].ResProductionX;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitWorkPlan.FindDifferentResource(aUnit: TKMUnit; aLoc: TKMPoint; const aAvoidLoc: TKMPoint): Boolean;
|
||||
var
|
||||
NewLoc: TKMPointDir;
|
||||
PlantAct: TKMPlantAct;
|
||||
Found: boolean;
|
||||
HW: TKMHouseWoodcutters;
|
||||
begin
|
||||
if (GatheringScript = gsWoodCutterCut) or (GatheringScript = gsWoodCutterPlant) then
|
||||
begin
|
||||
HW := TKMHouseWoodcutters(aUnit.Home);
|
||||
HW.ValidateFlagPoint; //Validate Cutting point. It will be set to a valid one if needed.
|
||||
|
||||
if HW.IsFlagPointSet then
|
||||
aLoc := HW.FlagPoint;
|
||||
end;
|
||||
|
||||
with gTerrain do
|
||||
case GatheringScript of
|
||||
gsStoneCutter: Found := FindStone(aLoc, gRes.Units[aUnit.UnitType].MiningRange, aAvoidLoc, False, NewLoc);
|
||||
gsFarmerSow: Found := FindCornField(aLoc, gRes.Units[aUnit.UnitType].MiningRange, aAvoidLoc, taPlant, PlantAct, NewLoc);
|
||||
gsFarmerCorn: begin
|
||||
Found := FindCornField(aLoc, gRes.Units[aUnit.UnitType].MiningRange, aAvoidLoc, taAny, PlantAct, NewLoc);
|
||||
if PlantAct = taPlant then
|
||||
begin
|
||||
GatheringScript := gsFarmerSow; //Switch to sowing corn rather than cutting
|
||||
ActionWalkFrom := uaWalkTool; //Carry our scythe back (without the corn) as the player saw us take it out
|
||||
ActionWorkType := uaWork1;
|
||||
WorkCyc := 10;
|
||||
Product1 := wtNone; //Don't produce corn
|
||||
ProdCount1 := 0;
|
||||
end;
|
||||
end;
|
||||
gsFarmerWine: begin
|
||||
Found := FindWineField(aLoc, gRes.Units[aUnit.UnitType].MiningRange, aAvoidLoc, NewLoc);
|
||||
NewLoc.Dir := dirN; //The animation for picking grapes is only defined for facing north
|
||||
end;
|
||||
gsFisherCatch: Found := FindFishWater(aLoc, gRes.Units[aUnit.UnitType].MiningRange, aAvoidLoc, False, NewLoc);
|
||||
gsWoodCutterCut: Found := ChooseTree(aLoc, KMGetVertexTile(aAvoidLoc, WorkDir), gRes.Units[aUnit.UnitType].MiningRange, taCut, aUnit, NewLoc, PlantAct);
|
||||
gsWoodCutterPlant: Found := ChooseTree(aLoc, aAvoidLoc, gRes.Units[aUnit.UnitType].MiningRange, taPlant, aUnit, NewLoc, PlantAct);
|
||||
else
|
||||
Found := False; //Can find a new resource for an unknown gathering script, so return with False
|
||||
end;
|
||||
|
||||
if Found then
|
||||
begin
|
||||
Loc := NewLoc.Loc;
|
||||
WorkDir := NewLoc.Dir;
|
||||
Result := True;
|
||||
end
|
||||
else
|
||||
Result := False;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitWorkPlan.ChooseTree(const aLoc, aAvoid: TKMPoint; aRadius: Integer; aPlantAct: TKMPlantAct; aUnit: TKMUnit; out Tree: TKMPointDir; out PlantAct: TKMPlantAct): Boolean;
|
||||
var
|
||||
I: Integer;
|
||||
T: TKMPoint;
|
||||
treeList: TKMPointDirCenteredList;
|
||||
bestToPlant, secondBestToPlant: TKMPointCenteredList;
|
||||
begin
|
||||
treeList := TKMPointDirCenteredList.Create(aLoc);
|
||||
bestToPlant := TKMPointCenteredList.Create(aLoc);
|
||||
secondBestToPlant := TKMPointCenteredList.Create(aLoc);
|
||||
|
||||
gTerrain.FindTree(aLoc, aRadius, aAvoid, aPlantAct, treeList, bestToPlant, secondBestToPlant);
|
||||
|
||||
//Convert taAny to either a Tree or a Spot
|
||||
if (aPlantAct in [taCut, taAny])
|
||||
and ((treeList.Count > 8) //Always chop the tree if there are many
|
||||
or (bestToPlant.Count + secondBestToPlant.Count = 0)
|
||||
or ((treeList.Count > 0) and (KaMRandom({$IFDEF DBG_RNG_SPY}'TKMUnitWorkPlan.ChooseTree'{$ENDIF}) < treeList.Count / (treeList.Count + (bestToPlant.Count + secondBestToPlant.Count)/15)))
|
||||
) then
|
||||
begin
|
||||
PlantAct := taCut;
|
||||
Result := treeList.GetWeightedRandom(Tree);
|
||||
end
|
||||
else
|
||||
begin
|
||||
PlantAct := taPlant;
|
||||
//First try stumps list
|
||||
for I := bestToPlant.Count - 1 downto 0 do
|
||||
if not TKMUnitCitizen(aUnit).CanWorkAt(bestToPlant[I], gsWoodCutterPlant) then
|
||||
bestToPlant.Delete(I);
|
||||
Result := bestToPlant.GetWeightedRandom(T);
|
||||
//Trees must always be planted facing north as that is the direction the animation uses
|
||||
if Result then
|
||||
Tree := KMPointDir(T, dirN)
|
||||
else
|
||||
begin
|
||||
//Try empty places list
|
||||
for I := secondBestToPlant.Count - 1 downto 0 do
|
||||
if not TKMUnitCitizen(aUnit).CanWorkAt(secondBestToPlant[I], gsWoodCutterPlant) then
|
||||
secondBestToPlant.Delete(I);
|
||||
Result := secondBestToPlant.GetWeightedRandom(T);
|
||||
//Trees must always be planted facing north as that is the direction the animation uses
|
||||
if Result then
|
||||
Tree := KMPointDir(T, dirN);
|
||||
end;
|
||||
end;
|
||||
|
||||
treeList.Free;
|
||||
bestToPlant.Free;
|
||||
secondBestToPlant.Free;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitWorkPlan.FindPlan(aUnit: TKMUnit; aHome: TKMHouseType; aProduct: TKMWareType; aLoc: TKMPoint; aPlantAct: TKMPlantAct);
|
||||
var
|
||||
I: Integer;
|
||||
tmp: TKMPointDir;
|
||||
plantAct: TKMPlantAct;
|
||||
HW: TKMHouseWoodcutters;
|
||||
begin
|
||||
Clear;
|
||||
|
||||
fHome := aHome;
|
||||
AfterWorkIdle := gRes.Houses[aHome].WorkerRest * 10;
|
||||
|
||||
//Now we need to fill only specific properties
|
||||
case aUnit.UnitType of
|
||||
utWoodcutter: if aHome = htWoodcutters then
|
||||
begin
|
||||
HW := TKMHouseWoodcutters(aUnit.Home);
|
||||
HW.ValidateFlagPoint; //Validate Cutting point. It will be set to a valid one if needed.
|
||||
|
||||
if HW.IsFlagPointSet then
|
||||
aLoc := HW.FlagPoint;
|
||||
|
||||
fIssued := ChooseTree(aLoc, KMPOINT_ZERO, gRes.Units[aUnit.UnitType].MiningRange, aPlantAct, aUnit, tmp, plantAct);
|
||||
if fIssued then
|
||||
begin
|
||||
case plantAct of
|
||||
taCut: begin //Cutting uses DirNW,DirSW,DirSE,DirNE (1,3,5,7) of uaWork
|
||||
ResourcePlan(wtNone,0,wtNone,0,wtTrunk);
|
||||
WalkStyle(tmp, uaWalkBooty,uaWork,15,20,uaWalkTool2,gsWoodCutterCut);
|
||||
end;
|
||||
taPlant: begin //Planting uses DirN (0) of uaWork
|
||||
WalkStyle(tmp, uaWalkTool,uaWork,12,0,uaWalk,gsWoodCutterPlant);
|
||||
end;
|
||||
else fIssued := False;
|
||||
end;
|
||||
end
|
||||
else
|
||||
case plantAct of
|
||||
taCut: if not gTerrain.CanFindTree(aLoc, gRes.Units[aUnit.UnitType].MiningRange) then
|
||||
ResourceDepleted := True; //No more trees to cut
|
||||
taPlant: if HW.WoodcutterMode = wmPlant then
|
||||
ResourceDepleted := True; //No place for trees to plant
|
||||
end;
|
||||
end;
|
||||
utMiner: if aHome = htCoalMine then
|
||||
begin
|
||||
fIssued := gTerrain.FindOre(aLoc, wtCoal, tmp.Loc);
|
||||
if fIssued then
|
||||
begin
|
||||
Loc := tmp.Loc;
|
||||
ResourcePlan(wtNone,0,wtNone,0,wtCoal);
|
||||
GatheringScript := gsCoalMiner;
|
||||
SubActAdd(haWork1,1);
|
||||
SubActAdd(haWork2,23);
|
||||
SubActAdd(haWork5,1);
|
||||
end else
|
||||
ResourceDepleted := True;
|
||||
end else
|
||||
if aHome = htIronMine then
|
||||
begin
|
||||
fIssued := gTerrain.FindOre(aLoc, wtIronOre, tmp.Loc);
|
||||
if fIssued then
|
||||
begin
|
||||
Loc := tmp.Loc;
|
||||
ResourcePlan(wtNone,0,wtNone,0,wtIronOre);
|
||||
GatheringScript := gsIronMiner;
|
||||
SubActAdd(haWork1,1);
|
||||
SubActAdd(haWork2,24);
|
||||
SubActAdd(haWork5,1);
|
||||
end else
|
||||
ResourceDepleted := True;
|
||||
end else
|
||||
if aHome = htGoldMine then
|
||||
begin
|
||||
fIssued := gTerrain.FindOre(aLoc, wtGoldOre, tmp.Loc);
|
||||
if fIssued then
|
||||
begin
|
||||
Loc := tmp.Loc;
|
||||
ResourcePlan(wtNone,0,wtNone,0,wtGoldOre);
|
||||
GatheringScript := gsGoldMiner;
|
||||
SubActAdd(haWork1,1);
|
||||
SubActAdd(haWork2,24);
|
||||
SubActAdd(haWork5,1);
|
||||
end else
|
||||
ResourceDepleted := True;
|
||||
end;
|
||||
utAnimalBreeder: if aHome = htSwine then
|
||||
begin
|
||||
ResourcePlan(wtCorn,1,wtNone,0,wtPig,wtSkin);
|
||||
GatheringScript := gsSwineBreeder;
|
||||
for I := 0 to 3 do
|
||||
begin
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork3,1);
|
||||
end;
|
||||
SubActAdd(haWork2,1);
|
||||
fIssued := True;
|
||||
end else
|
||||
|
||||
if aHome = htStables then
|
||||
begin
|
||||
ResourcePlan(wtCorn,1,wtNone,0,wtHorse);
|
||||
GatheringScript := gsHorseBreeder;
|
||||
SubActAdd(haWork1,1);
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork3,1);
|
||||
SubActAdd(haWork4,1);
|
||||
SubActAdd(haWork5,1);
|
||||
fIssued := True;
|
||||
end;
|
||||
utFarmer: if aHome = htFarm then
|
||||
begin
|
||||
fIssued := gTerrain.FindCornField(aLoc, gRes.Units[aUnit.UnitType].MiningRange, KMPOINT_ZERO, aPlantAct, plantAct, tmp);
|
||||
if fIssued then
|
||||
case plantAct of
|
||||
taCut: begin
|
||||
ResourcePlan(wtNone,0,wtNone,0,wtCorn);
|
||||
WalkStyle(tmp, uaWalkTool,uaWork,6,0,uaWalkBooty,gsFarmerCorn);
|
||||
end;
|
||||
taPlant: WalkStyle(tmp, uaWalk,uaWork1,10,0,uaWalk,gsFarmerSow);
|
||||
else fIssued := False;
|
||||
end;
|
||||
end else
|
||||
|
||||
if aHome = htVineyard then
|
||||
begin
|
||||
fIssued := gTerrain.FindWineField(aLoc, gRes.Units[aUnit.UnitType].MiningRange, KMPOINT_ZERO, tmp);
|
||||
if fIssued then
|
||||
begin
|
||||
ResourcePlan(wtNone,0,wtNone,0,wtWine);
|
||||
WalkStyle(KMPointDir(tmp.Loc,dirN), uaWalkTool2,uaWork2,5,0,uaWalkBooty2,gsFarmerWine); //The animation for picking grapes is only defined for facing north
|
||||
SubActAdd(haWork1,1);
|
||||
SubActAdd(haWork2,11);
|
||||
SubActAdd(haWork5,1);
|
||||
end;
|
||||
end;
|
||||
utCarpenter: if aHome = htSawmill then
|
||||
begin
|
||||
ResourcePlan(wtTrunk,1,wtNone,0,wtTimber);
|
||||
SubActAdd(haWork1,1);
|
||||
SubActAdd(haWork2,25);
|
||||
SubActAdd(haWork5,1);
|
||||
fIssued := True;
|
||||
end else
|
||||
|
||||
if (aHome = htArmorWorkshop) and (aProduct = wtLeatherArmor) then
|
||||
begin
|
||||
ResourcePlan(wtLeather,1,wtNone,0,wtLeatherArmor);
|
||||
for I := 0 to 3 do
|
||||
begin
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork3,1);
|
||||
SubActAdd(haWork4,1);
|
||||
end;
|
||||
SubActAdd(haWork2,0.25);
|
||||
fIssued := True;
|
||||
end else
|
||||
|
||||
if (aHome = htArmorWorkshop) and (aProduct = wtWoodenShield) then
|
||||
begin
|
||||
ResourcePlan(wtTimber,1,wtNone,0,wtWoodenShield);
|
||||
for I := 0 to 3 do
|
||||
begin
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork3,1);
|
||||
SubActAdd(haWork4,1);
|
||||
end;
|
||||
SubActAdd(haWork2,0.25);
|
||||
fIssued := True;
|
||||
end else
|
||||
|
||||
if (aHome = htWeaponWorkshop) and (aProduct = wtAxe) then
|
||||
begin
|
||||
ResourcePlan(wtTimber,2,wtNone,0,wtAxe);
|
||||
SubActAdd(haWork1,1);
|
||||
for I := 0 to 2 do
|
||||
begin
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork3,1);
|
||||
SubActAdd(haWork4,1);
|
||||
end;
|
||||
SubActAdd(haWork5,1);
|
||||
fIssued := True;
|
||||
end else
|
||||
|
||||
if (aHome = htWeaponWorkshop) and (aProduct = wtLance) then
|
||||
begin
|
||||
ResourcePlan(wtTimber,2,wtNone,0,wtLance);
|
||||
SubActAdd(haWork1,1);
|
||||
for I := 0 to 2 do
|
||||
begin
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork3,1);
|
||||
SubActAdd(haWork4,1);
|
||||
end;
|
||||
SubActAdd(haWork5,1);
|
||||
fIssued := True;
|
||||
end else
|
||||
|
||||
if (aHome = htWeaponWorkshop) and (aProduct = wtBow) then
|
||||
begin
|
||||
ResourcePlan(wtTimber,2,wtNone,0,wtBow);
|
||||
SubActAdd(haWork1,1);
|
||||
for I := 0 to 2 do
|
||||
begin
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork3,1);
|
||||
SubActAdd(haWork4,1);
|
||||
end;
|
||||
SubActAdd(haWork5,1);
|
||||
fIssued := True;
|
||||
end;
|
||||
utBaker: if aHome = htMill then
|
||||
begin
|
||||
ResourcePlan(wtCorn,1,wtNone,0,wtFlour);
|
||||
SubActAdd(haWork2,47);
|
||||
fIssued := True;
|
||||
end else
|
||||
|
||||
if aHome = htBakery then
|
||||
begin
|
||||
ResourcePlan(wtFlour,1,wtNone,0,wtBread);
|
||||
for I := 0 to 6 do
|
||||
begin
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork3,1);
|
||||
end;
|
||||
fIssued := True;
|
||||
end;
|
||||
utButcher: if aHome = htTannery then
|
||||
begin
|
||||
ResourcePlan(wtSkin,1,wtNone,0,wtLeather);
|
||||
SubActAdd(haWork1,1);
|
||||
SubActAdd(haWork2,29);
|
||||
fIssued := True;
|
||||
end else
|
||||
|
||||
if aHome = htButchers then
|
||||
begin
|
||||
ResourcePlan(wtPig,1,wtNone,0,wtSausage);
|
||||
SubActAdd(haWork1,1);
|
||||
for I := 0 to 5 do
|
||||
begin
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork4,1);
|
||||
SubActAdd(haWork3,1);
|
||||
end;
|
||||
fIssued := True;
|
||||
end;
|
||||
utFisher: if aHome = htFishermans then
|
||||
begin
|
||||
fIssued := gTerrain.FindFishWater(aLoc, gRes.Units[aUnit.UnitType].MiningRange, KMPOINT_ZERO, False, tmp);
|
||||
if fIssued then
|
||||
begin
|
||||
ResourcePlan(wtNone,0,wtNone,0,wtFish);
|
||||
WalkStyle(tmp,uaWalk,uaWork2,10,0,uaWalkTool,gsFisherCatch);
|
||||
end else
|
||||
//We must check again this time ignoring working units since they don't indicate the resource is depleted
|
||||
ResourceDepleted := not gTerrain.FindFishWater(aLoc, gRes.Units[aUnit.UnitType].MiningRange, KMPOINT_ZERO, True, tmp);
|
||||
end;
|
||||
utStonemason: if aHome = htQuarry then
|
||||
begin
|
||||
fIssued := gTerrain.FindStone(aLoc, gRes.Units[aUnit.UnitType].MiningRange, KMPOINT_ZERO, False, tmp);
|
||||
if fIssued then
|
||||
begin
|
||||
ResourcePlan(wtNone,0,wtNone,0,wtStone);
|
||||
WalkStyle(tmp, uaWalk,uaWork,8,0,uaWalkTool,gsStoneCutter);
|
||||
SubActAdd(haWork1,1);
|
||||
SubActAdd(haWork2,9);
|
||||
SubActAdd(haWork5,1);
|
||||
end else
|
||||
//We must check again this time ignoring working units since they don't indicate the resource is depleted
|
||||
ResourceDepleted := not gTerrain.FindStone(aLoc, gRes.Units[aUnit.UnitType].MiningRange, KMPOINT_ZERO, True, tmp);
|
||||
end;
|
||||
utSmith: if (aHome = htArmorSmithy) and (aProduct = wtIronShield) then
|
||||
begin
|
||||
ResourcePlan(wtIron,1,wtCoal,1,wtIronShield);
|
||||
for I := 0 to 3 do
|
||||
begin
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork3,1);
|
||||
SubActAdd(haWork4,1);
|
||||
end;
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork5,1);
|
||||
fIssued := True;
|
||||
end else
|
||||
|
||||
if (aHome = htArmorSmithy) and (aProduct = wtIronArmor) then
|
||||
begin
|
||||
ResourcePlan(wtIron,1,wtCoal,1,wtIronArmor);
|
||||
for I := 0 to 3 do
|
||||
begin
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork3,1);
|
||||
SubActAdd(haWork4,1);
|
||||
end;
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork5,1);
|
||||
fIssued := True;
|
||||
end else
|
||||
|
||||
if (aHome = htWeaponSmithy) and (aProduct = wtSword) then
|
||||
begin
|
||||
ResourcePlan(wtIron,1,wtCoal,1,wtSword);
|
||||
SubActAdd(haWork1,1);
|
||||
for I := 0 to 2 do
|
||||
begin
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork3,1);
|
||||
SubActAdd(haWork4,1);
|
||||
end;
|
||||
SubActAdd(haWork5,1);
|
||||
fIssued := True;
|
||||
end else
|
||||
|
||||
if (aHome = htWeaponSmithy) and (aProduct = wtPike) then
|
||||
begin
|
||||
ResourcePlan(wtIron,1,wtCoal,1,wtPike);
|
||||
SubActAdd(haWork1,1);
|
||||
for I := 0 to 2 do
|
||||
begin
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork3,1);
|
||||
SubActAdd(haWork4,1);
|
||||
end;
|
||||
SubActAdd(haWork5,1);
|
||||
fIssued := True;
|
||||
end else
|
||||
|
||||
if (aHome = htWeaponSmithy) and (aProduct = wtCrossbow) then
|
||||
begin
|
||||
ResourcePlan(wtIron,1,wtCoal,1,wtCrossbow);
|
||||
SubActAdd(haWork1,1);
|
||||
for I := 0 to 2 do
|
||||
begin
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork3,1);
|
||||
SubActAdd(haWork4,1);
|
||||
end;
|
||||
SubActAdd(haWork5,1);
|
||||
fIssued := True;
|
||||
end;
|
||||
utMetallurgist: if aHome = htIronSmithy then
|
||||
begin
|
||||
ResourcePlan(wtIronOre,1,wtCoal,1,wtIron);
|
||||
for I := 0 to 3 do
|
||||
begin
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork3,1);
|
||||
end;
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork3,0.25);
|
||||
fIssued := True;
|
||||
end else
|
||||
if aHome = htMetallurgists then
|
||||
begin
|
||||
ResourcePlan(wtGoldOre,1,wtCoal,1,wtGold);
|
||||
for I := 0 to 3 do
|
||||
begin
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork3,1);
|
||||
SubActAdd(haWork4,1);
|
||||
end;
|
||||
SubActAdd(haWork2,1);
|
||||
SubActAdd(haWork3,0.1);
|
||||
fIssued := True;
|
||||
end;
|
||||
else
|
||||
raise Exception.Create('No work plan for ' +
|
||||
gRes.Units[aUnit.UnitType].GUIName + ' in ' +
|
||||
gRes.Houses[aHome].HouseName);
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitWorkPlan.Load(LoadStream: TKMemoryStream);
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
LoadStream.CheckMarker('WorkPlan');
|
||||
LoadStream.Read(fHome, SizeOf(fHome));
|
||||
LoadStream.Read(fIssued);
|
||||
//public
|
||||
LoadStream.Read(HasToWalk);
|
||||
LoadStream.Read(Loc);
|
||||
LoadStream.Read(ActionWalkTo, SizeOf(ActionWalkTo));
|
||||
LoadStream.Read(ActionWorkType, SizeOf(ActionWorkType));
|
||||
LoadStream.Read(WorkCyc);
|
||||
LoadStream.Read(WorkDir);
|
||||
LoadStream.Read(GatheringScript, SizeOf(GatheringScript));
|
||||
LoadStream.Read(AfterWorkDelay);
|
||||
LoadStream.Read(ActionWalkFrom, SizeOf(ActionWalkFrom));
|
||||
LoadStream.Read(Resource1, SizeOf(Resource1));
|
||||
LoadStream.Read(Count1);
|
||||
LoadStream.Read(Resource2, SizeOf(Resource2));
|
||||
LoadStream.Read(Count2);
|
||||
LoadStream.Read(ActCount);
|
||||
for I := 0 to ActCount - 1 do //Write only assigned
|
||||
begin
|
||||
LoadStream.Read(HouseAct[I].Act, SizeOf(HouseAct[I].Act));
|
||||
LoadStream.Read(HouseAct[I].TimeToWork);
|
||||
end;
|
||||
LoadStream.Read(Product1, SizeOf(Product1));
|
||||
LoadStream.Read(ProdCount1);
|
||||
LoadStream.Read(Product2, SizeOf(Product2));
|
||||
LoadStream.Read(ProdCount2);
|
||||
LoadStream.Read(AfterWorkIdle);
|
||||
LoadStream.Read(ResourceDepleted);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitWorkPlan.Save(SaveStream: TKMemoryStream);
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
SaveStream.PlaceMarker('WorkPlan');
|
||||
SaveStream.Write(fHome, SizeOf(fHome));
|
||||
SaveStream.Write(fIssued);
|
||||
//public
|
||||
SaveStream.Write(HasToWalk);
|
||||
SaveStream.Write(Loc);
|
||||
SaveStream.Write(ActionWalkTo, SizeOf(ActionWalkTo));
|
||||
SaveStream.Write(ActionWorkType, SizeOf(ActionWorkType));
|
||||
SaveStream.Write(WorkCyc);
|
||||
SaveStream.Write(WorkDir);
|
||||
SaveStream.Write(GatheringScript, SizeOf(GatheringScript));
|
||||
SaveStream.Write(AfterWorkDelay);
|
||||
SaveStream.Write(ActionWalkFrom, SizeOf(ActionWalkFrom));
|
||||
SaveStream.Write(Resource1, SizeOf(Resource1));
|
||||
SaveStream.Write(Count1);
|
||||
SaveStream.Write(Resource2, SizeOf(Resource2));
|
||||
SaveStream.Write(Count2);
|
||||
SaveStream.Write(ActCount);
|
||||
for I := 0 to ActCount - 1 do //Write only assigned
|
||||
begin
|
||||
SaveStream.Write(HouseAct[I].Act, SizeOf(HouseAct[I].Act));
|
||||
SaveStream.Write(HouseAct[I].TimeToWork);
|
||||
end;
|
||||
SaveStream.Write(Product1, SizeOf(Product1));
|
||||
SaveStream.Write(ProdCount1);
|
||||
SaveStream.Write(Product2, SizeOf(Product2));
|
||||
SaveStream.Write(ProdCount2);
|
||||
SaveStream.Write(AfterWorkIdle);
|
||||
SaveStream.Write(ResourceDepleted);
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
+2876
File diff suppressed because it is too large.
Load diff
+415
@@ -0,0 +1,415 @@
|
||||
unit KM_UnitsCollection;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
Generics.Collections,
|
||||
KM_CommonClasses, KM_Defaults, KM_Points,
|
||||
KM_Units, KM_Houses;
|
||||
|
||||
type
|
||||
TKMUnitsCollection = class
|
||||
private
|
||||
fUnits: TKMList;
|
||||
function GetUnit(aIndex: Integer): TKMUnit; inline;
|
||||
function GetCount: Integer;
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
function AddUnit(aOwner: TKMHandID; aUnitType: TKMUnitType; const aLoc: TKMPointDir; aAutoPlace: Boolean = True;
|
||||
aRequiredWalkConnect: Byte = 0; aInHouse: TKMHouse = nil): TKMUnit;
|
||||
procedure AddUnitToList(aUnit: TKMUnit);
|
||||
property Count: Integer read GetCount;
|
||||
property Units[aIndex: Integer]: TKMUnit read GetUnit; default; //Use instead of Items[.]
|
||||
procedure RemoveUnit(aUnit: TKMUnit);
|
||||
procedure RemoveAllUnits;
|
||||
procedure RemoveUnitsOutOfBounds(const aInsetRect: TKMRect);
|
||||
procedure DeleteUnitFromList(aUnit: TKMUnit);
|
||||
procedure OwnerUpdate(aOwner: TKMHandID);
|
||||
function HitTest(X, Y: Integer; const UT: TKMUnitType = utAny): TKMUnit;
|
||||
function GetUnitByUID(aUID: Integer): TKMUnit;
|
||||
function GetClosestUnit(const aPoint: TKMPoint; aTypes: TKMUnitTypeSet = [Low(TKMUnitType)..High(TKMUnitType)]): TKMUnit;
|
||||
procedure GetUnitsInRect(const aRect: TKMRect; List: TList<TKMUnit>);
|
||||
function GetTotalPointers: Integer;
|
||||
procedure Clear;
|
||||
procedure Save(SaveStream: TKMemoryStream);
|
||||
procedure Load(LoadStream: TKMemoryStream);
|
||||
procedure SyncLoad;
|
||||
procedure UpdateState(aTick: Cardinal);
|
||||
procedure UpdateVisualState;
|
||||
procedure Paint(const aRect: TKMRect; aTickLag: Single);
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
Classes, Math, Types, SysUtils,
|
||||
KM_Entity, KM_Terrain,
|
||||
KM_Game, KM_GameParams, KM_HandsCollection, KM_Log, KM_Resource, KM_ResUnits, KM_UnitWarrior,
|
||||
KM_UnitActionWalkTo, KM_GameUIDTracker,
|
||||
KM_DevPerfLog, KM_DevPerfLogTypes,
|
||||
KM_CommonExceptions;
|
||||
|
||||
|
||||
{ TKMUnitsCollection }
|
||||
constructor TKMUnitsCollection.Create;
|
||||
begin
|
||||
inherited Create;
|
||||
|
||||
fUnits := TKMList.Create;
|
||||
end;
|
||||
|
||||
|
||||
destructor TKMUnitsCollection.Destroy;
|
||||
begin
|
||||
//No need to free units individually since they are Freed by TKMList.Clear command in destructor
|
||||
fUnits.Free;
|
||||
|
||||
inherited;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitsCollection.Clear;
|
||||
begin
|
||||
fUnits.Clear;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitsCollection.GetCount: Integer;
|
||||
begin
|
||||
Result := fUnits.Count;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitsCollection.GetUnit(aIndex: Integer): TKMUnit;
|
||||
begin
|
||||
Result := fUnits[aIndex];
|
||||
end;
|
||||
|
||||
|
||||
//AutoPlace means we should try to find a spot for this unit instead of just placing it where we were told to
|
||||
function TKMUnitsCollection.AddUnit(aOwner: TKMHandID; aUnitType: TKMUnitType; const aLoc: TKMPointDir;
|
||||
aAutoPlace: Boolean = True; aRequiredWalkConnect: Byte = 0; aInHouse: TKMHouse = nil): TKMUnit;
|
||||
var
|
||||
uid: Integer;
|
||||
placeTo: TKMPoint;
|
||||
pointDir: TKMPointDir;
|
||||
U: TKMUnit;
|
||||
begin
|
||||
if aAutoPlace then
|
||||
begin
|
||||
placeTo := KMPOINT_ZERO; // Will have 0:0 if no place found
|
||||
if aRequiredWalkConnect = 0 then
|
||||
aRequiredWalkConnect := gTerrain.GetWalkConnectID(aLoc.Loc);
|
||||
|
||||
var placeFound := gHands.FindPlaceForUnit(aLoc.Loc.X, aLoc.Loc.Y, aUnitType, placeTo, aRequiredWalkConnect);
|
||||
if not placeFound then
|
||||
begin
|
||||
gLog.AddTime('Unable to find a place for a unit around ' + KM_Points.TypeToString(aLoc.Loc));
|
||||
Exit(nil);
|
||||
end;
|
||||
end
|
||||
else
|
||||
placeTo := aLoc.Loc;
|
||||
|
||||
// Check if Pos is within map coords first, as other checks rely on this
|
||||
if not gTerrain.TileInMapCoords(placeTo.X, placeTo.Y) then
|
||||
begin
|
||||
gLog.AddTime('Unable to add unit to ' + KM_Points.TypeToString(placeTo));
|
||||
Exit(nil);
|
||||
end;
|
||||
|
||||
if (aInHouse = nil) and gTerrain.HasUnit(placeTo) then
|
||||
begin
|
||||
U := TKMUnit(gTerrain.Land^[placeTo.Y,placeTo.X].IsUnit);
|
||||
raise ELocError.Create(Format('No space for %s at %s, tile is already occupied by %s, ID = %d',
|
||||
[gRes.Units[aUnitType].GUIName, aLoc.ToString, gRes.Units[U.UnitType].GUIName, U.UID]),
|
||||
placeTo);
|
||||
end;
|
||||
|
||||
pointDir := KMPointDir(placeTo, aLoc.Dir);
|
||||
uid := gUIDTracker.GetNewUID;
|
||||
case aUnitType of
|
||||
utSerf: Result := TKMUnitSerf.Create(uid, aUnitType, pointDir, aOwner, aInHouse);
|
||||
utBuilder: Result := TKMUnitWorker.Create(uid, aUnitType, pointDir, aOwner, aInHouse);
|
||||
utWoodCutter..utFisher,
|
||||
{utBuilder,}
|
||||
utStonemason..utMetallurgist: Result := TKMUnitCitizen.Create(uid, aUnitType, pointDir, aOwner, aInHouse);
|
||||
utRecruit: Result := TKMUnitRecruit.Create(uid, aUnitType, pointDir, aOwner, aInHouse);
|
||||
WARRIOR_MIN..WARRIOR_MAX: Result := TKMUnitWarrior.Create(uid, aUnitType, pointDir, aOwner, aInHouse);
|
||||
// Do not specify aAddInHouse, we want to call TKMUnitAnimal constructor
|
||||
utWolf,
|
||||
utWatersnake..utDuck: Result := TKMUnitAnimal.Create(uid, aUnitType, pointDir, aOwner);
|
||||
utFish: Result := TKMUnitFish.Create(uid, pointDir, aOwner);
|
||||
else
|
||||
raise ELocError.Create('Add ' + gRes.Units[aUnitType].GUIName, pointDir.Loc);
|
||||
end;
|
||||
|
||||
if Result <> nil then
|
||||
fUnits.Add(Result);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitsCollection.AddUnitToList(aUnit: TKMUnit);
|
||||
begin
|
||||
Assert(gGameParams.IsMapEditor); // Allow to add existing Unit directly only in MapEd
|
||||
if aUnit <> nil then
|
||||
fUnits.Add(aUnit);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitsCollection.RemoveUnit(aUnit: TKMUnit);
|
||||
begin
|
||||
aUnit.CloseUnit; //Should free up the unit properly (freeing terrain usage and memory)
|
||||
fUnits.Remove(aUnit); //Will free the unit
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitsCollection.RemoveUnitsOutOfBounds(const aInsetRect: TKMRect);
|
||||
var
|
||||
I: Integer;
|
||||
newMapRect: TKMRect;
|
||||
begin
|
||||
Assert(gGameParams.IsMapEditor);
|
||||
if Count <= 0 then Exit;
|
||||
|
||||
newMapRect := KMRectGrow(gTerrain.MapRect, aInsetRect);
|
||||
|
||||
for I := 0 to Count - 1 do
|
||||
if not KMInRect(Units[I].Position, newMapRect) then
|
||||
Units[I].CloseUnit;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitsCollection.RemoveAllUnits;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
if Count <= 0 then Exit;
|
||||
|
||||
for I := 0 to Count - 1 do
|
||||
Units[I].CloseUnit;
|
||||
|
||||
fUnits.Clear;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitsCollection.DeleteUnitFromList(aUnit: TKMUnit);
|
||||
begin
|
||||
Assert(gGameParams.IsMapEditor); // Allow to delete existing Unit directly only in MapEd
|
||||
if (aUnit <> nil) then
|
||||
fUnits.Extract(aUnit); // use Extract instead of Delete, cause Delete nils inner objects somehow
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitsCollection.OwnerUpdate(aOwner: TKMHandID);
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
for I := 0 to Count - 1 do
|
||||
Units[I].Owner := aOwner;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitsCollection.HitTest(X, Y: Integer; const UT: TKMUnitType = utAny): TKMUnit;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Result := nil;
|
||||
for I := 0 to Count - 1 do
|
||||
if Units[I].HitTest(X,Y,UT) and not Units[I].IsDead then
|
||||
begin
|
||||
Result := Units[I];
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitsCollection.GetUnitByUID(aUID: Integer): TKMUnit;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Result := nil;
|
||||
for I := 0 to Count - 1 do
|
||||
if aUID = Units[I].UID then
|
||||
begin
|
||||
Result := Units[I];
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitsCollection.GetUnitsInRect(const aRect: TKMRect; List: TList<TKMUnit>);
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
for I := 0 to Count - 1 do
|
||||
if KMInRect(Units[I].PositionF, aRect) and not Units[I].IsDeadOrDying then
|
||||
List.Add(Units[I]);
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitsCollection.GetClosestUnit(const aPoint: TKMPoint; aTypes: TKMUnitTypeSet = [Low(TKMUnitType)..High(TKMUnitType)]): TKMUnit;
|
||||
var
|
||||
I: Integer;
|
||||
bestDist, dist: Single;
|
||||
begin
|
||||
Result := nil;
|
||||
bestDist := MaxSingle; //Any distance will be closer than that
|
||||
for I := 0 to Count - 1 do
|
||||
if not Units[I].IsDeadOrDying and Units[I].Visible and (Units[I].UnitType in aTypes) then
|
||||
begin
|
||||
dist := KMLengthSqr(Units[I].Position, aPoint);
|
||||
if dist < bestDist then
|
||||
begin
|
||||
bestDist := dist;
|
||||
Result := Units[I];
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitsCollection.GetTotalPointers: Integer;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Result := 0;
|
||||
for I := 0 to Count - 1 do
|
||||
Inc(Result, Units[I].PointerCount);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitsCollection.Save(SaveStream: TKMemoryStream);
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
SaveStream.PlaceMarker('Units');
|
||||
SaveStream.Write(Count);
|
||||
for I := 0 to Count - 1 do
|
||||
begin
|
||||
//We save unit type to know which unit class to load
|
||||
SaveStream.Write(Units[I].UnitType, SizeOf(Units[I].UnitType));
|
||||
Units[I].Save(SaveStream);
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitsCollection.Load(LoadStream: TKMemoryStream);
|
||||
var
|
||||
I, newCount: Integer;
|
||||
unitType: TKMUnitType;
|
||||
U: TKMUnit;
|
||||
begin
|
||||
LoadStream.CheckMarker('Units');
|
||||
LoadStream.Read(newCount);
|
||||
for I := 0 to newCount - 1 do
|
||||
begin
|
||||
LoadStream.Read(unitType, SizeOf(unitType));
|
||||
case unitType of
|
||||
utSerf: U := TKMUnitSerf.Load(LoadStream);
|
||||
utBuilder: U := TKMUnitWorker.Load(LoadStream);
|
||||
utWoodCutter..utFisher,
|
||||
{utWorker,}
|
||||
utStonemason..utMetallurgist:
|
||||
U := TKMUnitCitizen.Load(LoadStream);
|
||||
utRecruit: U := TKMUnitRecruit.Load(LoadStream);
|
||||
WARRIOR_MIN..WARRIOR_MAX: U := TKMUnitWarrior.Load(LoadStream);
|
||||
utWolf,
|
||||
utWatersnake..utDuck: U := TKMUnitAnimal.Load(LoadStream);
|
||||
utFish: U := TKMUnitFish.Load(LoadStream);
|
||||
else
|
||||
U := nil;
|
||||
end;
|
||||
|
||||
if U <> nil then
|
||||
fUnits.Add(U)
|
||||
else
|
||||
gLog.AddAssert('Unknown unit type in Savegame');
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitsCollection.SyncLoad;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
for I := 0 to Count - 1 do
|
||||
Units[I].SyncLoad;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitsCollection.UpdateVisualState;
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
Assert(gGameParams.IsMapEditor);
|
||||
|
||||
for I := Count - 1 downto 0 do
|
||||
if not Units[I].IsDead then
|
||||
Units[I].UpdateVisualState;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitsCollection.UpdateState(aTick: Cardinal);
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
{$IFDEF DBG_PERFLOG}
|
||||
gPerfLogs.SectionEnter(psUnits);
|
||||
{$ENDIF}
|
||||
try
|
||||
for I := Count - 1 downto 0 do
|
||||
if not Units[I].IsDead then
|
||||
begin
|
||||
Units[I].UpdateState;
|
||||
Units[I].UpdateVisualState;
|
||||
end
|
||||
else
|
||||
if FREE_POINTERS and (Units[I].PointerCount = 0) then
|
||||
fUnits.Delete(I);
|
||||
finally
|
||||
{$IFDEF DBG_PERFLOG}
|
||||
gPerfLogs.SectionLeave(psUnits);
|
||||
{$ENDIF}
|
||||
end;
|
||||
// -- POINTER FREEING SYSTEM - DESCRIPTION -- //
|
||||
// This system was implemented because unit and house objects cannot be freed until all pointers
|
||||
// to them (in tasks, delivery queue, etc.) have been freed, otherwise we have pointer integrity
|
||||
// issues.
|
||||
|
||||
// -- ROUGH OUTLINE -- //
|
||||
// - Units and houses have fPointerCount, which is the number of pointers to them. (e.g. tasks,
|
||||
// deliveries) This is kept up to date by the thing that is using the pointer. On create it uses
|
||||
// GetPointer to get the pointer and increase the pointer count and on destroy it decreases
|
||||
// it with ReleasePointer.
|
||||
// - When a unit dies, the object is not destroyed. Instead a flag (boolean) is set to say that we
|
||||
// want to destroy but can't because there still might be pointers to the unit. From then on
|
||||
// every update state it checks to see if the pointer count is 0 yet. If it is then the unit is
|
||||
// destroyed.
|
||||
// - For each place that contains a pointer, it should check everytime the pointer is used to see
|
||||
// if it has been destroy. If it has then we free the pointer and reduce the count.
|
||||
// (and do any other action nececary due to the unit/house dying)
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitsCollection.Paint(const aRect: TKMRect; aTickLag: Single);
|
||||
const
|
||||
MARGIN = 2;
|
||||
var
|
||||
I: Integer;
|
||||
growRect: TKMRect;
|
||||
begin
|
||||
//Add additional margin to compensate for units height
|
||||
growRect := KMRectGrow(aRect, MARGIN);
|
||||
|
||||
for I := 0 to Count - 1 do
|
||||
if (Units[I] <> nil)
|
||||
and not Units[I].IsDead
|
||||
and ((SHOW_UNIT_ROUTES
|
||||
and (Units[I].Action is TKMUnitActionWalkTo)
|
||||
and TKMUnitActionWalkTo(Units[I].Action).NeedToPaint(growRect))
|
||||
or KMInRect(Units[I].PositionF, growRect)) then
|
||||
Units[I].Paint(aTickLag);
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,130 @@
|
||||
unit KM_UnitActionAbandonWalk;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
Math,
|
||||
KM_CommonClasses, KM_Defaults, KM_Units, KM_Points;
|
||||
|
||||
|
||||
{Abandon the current walk, move onto next tile}
|
||||
type
|
||||
TKMUnitActionAbandonWalk = class(TKMUnitAction)
|
||||
private
|
||||
fWalkTo: TKMPoint;
|
||||
fVertexOccupied: TKMPoint;
|
||||
public
|
||||
constructor Create(aUnit: TKMUnit; const LocB, aVertexOccupied: TKMPoint; aActionType: TKMUnitActionType);
|
||||
constructor Load(LoadStream: TKMemoryStream); override;
|
||||
destructor Destroy; override;
|
||||
function ActName: TKMUnitActionName; override;
|
||||
function CanBeInterrupted(aForced: Boolean = True): Boolean; override;
|
||||
function GetExplanation: UnicodeString; override;
|
||||
function Execute: TKMActionResult; override;
|
||||
procedure Save(SaveStream: TKMemoryStream); override;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
KM_Resource, KM_ResUnits;
|
||||
|
||||
|
||||
{ TUnitActionAbandonWalk }
|
||||
constructor TKMUnitActionAbandonWalk.Create(aUnit: TKMUnit; const LocB, aVertexOccupied: TKMPoint; aActionType: TKMUnitActionType);
|
||||
begin
|
||||
Assert(LocB.X*LocB.Y <> 0, 'Illegal WalkTo 0:0');
|
||||
inherited Create(aUnit, aActionType, False);
|
||||
|
||||
fWalkTo := LocB;
|
||||
fVertexOccupied := aVertexOccupied;
|
||||
end;
|
||||
|
||||
|
||||
destructor TKMUnitActionAbandonWalk.Destroy;
|
||||
begin
|
||||
if (fUnit <> nil) and not KMSamePoint(fVertexOccupied, KMPOINT_ZERO) then
|
||||
begin
|
||||
fUnit.VertexRem(fVertexOccupied); //Unoccupy vertex
|
||||
fVertexOccupied := KMPOINT_ZERO;
|
||||
end;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
|
||||
constructor TKMUnitActionAbandonWalk.Load(LoadStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
|
||||
LoadStream.CheckMarker('UnitActionAbandonWalk');
|
||||
LoadStream.Read(fWalkTo);
|
||||
LoadStream.Read(fVertexOccupied);
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionAbandonWalk.ActName: TKMUnitActionName;
|
||||
begin
|
||||
Result := uanAbandonWalk;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionAbandonWalk.GetExplanation: UnicodeString;
|
||||
begin
|
||||
Result := 'Abandoning walk';
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionAbandonWalk.Execute: TKMActionResult;
|
||||
var
|
||||
dx, dy: ShortInt;
|
||||
walkX, walkY, distance: Single;
|
||||
begin
|
||||
Result := arActContinues;
|
||||
|
||||
//Execute the route in series of moves
|
||||
// Use umtWalk move type here, since we just want to evaluate if we are close enough
|
||||
distance := gRes.Units[fUnit.UnitType].GetEffectiveWalkSpeed(False);
|
||||
|
||||
//Check if unit has arrived on tile
|
||||
if KMSamePointF(fUnit.PositionF, KMPointF(fWalkTo), distance/2) then
|
||||
begin
|
||||
fUnit.PositionF := KMPointF(fWalkTo); //Set precise position to avoid rounding errors
|
||||
fUnit.IsExchanging := False; //Disable sliding (in case it was set in previous step)
|
||||
if not KMSamePoint(fVertexOccupied, KMPOINT_ZERO) then
|
||||
begin
|
||||
fUnit.VertexRem(fVertexOccupied); //Unoccupy vertex
|
||||
fVertexOccupied := KMPOINT_ZERO;
|
||||
end;
|
||||
StepDone := True;
|
||||
Exit(arActDone);
|
||||
end;
|
||||
|
||||
walkX := fWalkTo.X - fUnit.PositionF.X;
|
||||
walkY := fWalkTo.Y - fUnit.PositionF.Y;
|
||||
dx := sign(walkX); //-1,0,1
|
||||
dy := sign(walkY); //-1,0,1
|
||||
|
||||
distance := gRes.Units[fUnit.UnitType].GetEffectiveWalkSpeed((dx <> 0) and (dy <> 0));
|
||||
|
||||
fUnit.PositionF := KMPointF(fUnit.PositionF.X + dx*Math.min(distance,abs(walkX)),
|
||||
fUnit.PositionF.Y + dy*Math.min(distance,abs(walkY)));
|
||||
Inc(fUnit.AnimStep);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitActionAbandonWalk.Save(SaveStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
|
||||
SaveStream.PlaceMarker('UnitActionAbandonWalk');
|
||||
SaveStream.Write(fWalkTo);
|
||||
SaveStream.Write(fVertexOccupied);
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionAbandonWalk.CanBeInterrupted(aForced: Boolean = True): Boolean;
|
||||
begin
|
||||
Result := StepDone and not Locked; //Abandon walk should never be abandoned, it will exit within 1 step anyway
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,414 @@
|
||||
unit KM_UnitActionFight;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
Classes, KM_CommonClasses, KM_Defaults, KM_CommonUtils, KromUtils, Math, SysUtils, KM_Units, KM_Points;
|
||||
|
||||
|
||||
type
|
||||
// Fight until we die or the opponent dies
|
||||
TKMUnitActionFight = class(TKMUnitAction)
|
||||
private
|
||||
fFightDelay: Integer; //Pause for this many ticks before going onto the next Step
|
||||
fOpponent: TKMUnit; //Who we are fighting with
|
||||
fVertexOccupied: TKMPoint; //The diagonal vertex we are currently occupying
|
||||
|
||||
// Execute is broken up into multiple methods
|
||||
function ExecuteValidateOpponent(Step: Byte): TKMActionResult;
|
||||
function ExecuteProcessRanged(Step: Byte): Boolean;
|
||||
function ExecuteProcessMelee(Step: Byte): Boolean;
|
||||
|
||||
function UpdateVertexUsage(const aFrom, aTo: TKMPoint): Boolean;
|
||||
procedure IncVertex(const aFrom, aTo: TKMPoint);
|
||||
procedure DecVertex;
|
||||
procedure MakeSound(IsHit: Boolean);
|
||||
public
|
||||
constructor Create(aUnit: TKMUnit; aActionType: TKMUnitActionType; aOpponent: TKMUnit);
|
||||
constructor Load(LoadStream: TKMemoryStream); override;
|
||||
destructor Destroy; override;
|
||||
function ActName: TKMUnitActionName; override;
|
||||
function CanBeInterrupted(aForced: Boolean = True): Boolean; override;
|
||||
function GetExplanation: UnicodeString; override;
|
||||
procedure SyncLoad; override;
|
||||
property GetOpponent: TKMUnit read fOpponent;
|
||||
function Execute: TKMActionResult; override;
|
||||
procedure Save(SaveStream: TKMemoryStream); override;
|
||||
function ObjToStringShort(const aSeparator: String = '|'): String; override;
|
||||
function ObjToString(const aSeparator: String = ' '): String; override;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
KM_Entity,
|
||||
KM_HandsCollection, KM_HandTypes, KM_HandEntity,
|
||||
KM_ResSound, KM_Sound, KM_UnitWarrior, KM_Resource, KM_Projectiles,
|
||||
KM_ResUnits, KM_Hand;
|
||||
|
||||
|
||||
const
|
||||
STRIKE_STEP = 5; //Melee units place hit on step 5
|
||||
|
||||
MeleeSoundsHit: array [0..14] of TKMSoundEffectOriginal = (
|
||||
sfxMelee34, sfxMelee35, sfxMelee36, sfxMelee41, sfxMelee42,
|
||||
sfxMelee44, sfxMelee45, sfxMelee46, sfxMelee47, sfxMelee48,
|
||||
sfxMelee49, sfxMelee50, sfxMelee55, sfxMelee56, sfxMelee57);
|
||||
|
||||
MeleeSoundsMiss: array [0..8] of TKMSoundEffectOriginal = (
|
||||
sfxMelee37, sfxMelee38, sfxMelee39,
|
||||
sfxMelee40, sfxMelee43, sfxMelee51,
|
||||
sfxMelee52, sfxMelee53, sfxMelee54);
|
||||
|
||||
|
||||
{ TKMUnitActionFight }
|
||||
constructor TKMUnitActionFight.Create(aUnit: TKMUnit; aActionType: TKMUnitActionType; aOpponent: TKMUnit);
|
||||
begin
|
||||
inherited Create(aUnit, aActionType, True);
|
||||
|
||||
Assert(aUnit is TKMUnitWarrior, 'Can''t create fight action for not Warrior unit');
|
||||
|
||||
fFightDelay := -1;
|
||||
fOpponent := aOpponent.GetPointer;
|
||||
aUnit.Direction := KMGetDirection(fUnit.PositionF, fOpponent.PositionF); //Face the opponent from the beginning
|
||||
fVertexOccupied := KMPOINT_ZERO;
|
||||
if KMStepIsDiag(fUnit.Position, fOpponent.Position) and not TKMUnitWarrior(fUnit).IsRanged then
|
||||
IncVertex(fUnit.Position, fOpponent.Position);
|
||||
end;
|
||||
|
||||
|
||||
destructor TKMUnitActionFight.Destroy;
|
||||
begin
|
||||
gHands.CleanUpUnitPointer(fOpponent);
|
||||
if not KMSamePoint(fVertexOccupied, KMPOINT_ZERO) then
|
||||
DecVertex;
|
||||
|
||||
inherited;
|
||||
end;
|
||||
|
||||
|
||||
constructor TKMUnitActionFight.Load(LoadStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
|
||||
LoadStream.CheckMarker('UnitActionFight');
|
||||
LoadStream.Read(fOpponent, 4);
|
||||
LoadStream.Read(fFightDelay);
|
||||
LoadStream.Read(fVertexOccupied);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitActionFight.SyncLoad;
|
||||
begin
|
||||
inherited;
|
||||
|
||||
fOpponent := gHands.GetUnitByUID(Integer(fOpponent));
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionFight.ActName: TKMUnitActionName;
|
||||
begin
|
||||
Result := uanFight;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionFight.GetExplanation: UnicodeString;
|
||||
begin
|
||||
Result := 'Fighting';
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionFight.UpdateVertexUsage(const aFrom, aTo: TKMPoint): Boolean;
|
||||
begin
|
||||
Result := True;
|
||||
if KMStepIsDiag(aFrom, aTo) then
|
||||
begin
|
||||
//If the new target has the same vertex as the old one, no change is needed
|
||||
if KMSamePoint(KMGetDiagVertex(aFrom, aTo), fVertexOccupied) then Exit;
|
||||
//Otherwise the new target's vertex is different to the old one, so remove old vertex usage and add new
|
||||
DecVertex;
|
||||
if fUnit.VertexUsageCompatible(aFrom, aTo) then
|
||||
IncVertex(aFrom, aTo)
|
||||
else
|
||||
Result := False; //This vertex is being used so we can't fight
|
||||
end
|
||||
else
|
||||
//The new target is not diagonal, make sure any old vertex usage is removed
|
||||
DecVertex;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitActionFight.IncVertex(const aFrom, aTo: TKMPoint);
|
||||
begin
|
||||
//Tell gTerrain that this vertex is being used so no other unit walks over the top of us
|
||||
Assert(KMSamePoint(fVertexOccupied, KMPOINT_ZERO), 'Fight vertex in use');
|
||||
|
||||
fUnit.VertexAdd(aFrom, aTo);
|
||||
fVertexOccupied := KMGetDiagVertex(aFrom,aTo);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitActionFight.DecVertex;
|
||||
begin
|
||||
//Tell gTerrain that this vertex is not being used anymore
|
||||
if KMSamePoint(fVertexOccupied, KMPOINT_ZERO) then exit;
|
||||
|
||||
fUnit.VertexRem(fVertexOccupied);
|
||||
fVertexOccupied := KMPOINT_ZERO;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitActionFight.MakeSound(IsHit: Boolean);
|
||||
var
|
||||
//Battlecry is the most noticable random sound, we would like to repeat it exactly the same in each replay (?)
|
||||
makeBattleCry: Boolean;
|
||||
begin
|
||||
//Randomly make a battle cry. KaMRandom must always happen regardless of tile revelation
|
||||
makeBattleCry := KaMRandom(20{$IFDEF DBG_RNG_SPY}, 'TKMUnitActionFight.MakeSound'{$ENDIF}) = 0;
|
||||
|
||||
//Do not play sounds if unit is invisible to gMySpectator
|
||||
//We should not use KaMRandom below this line because sound playback depends on FOW and is individual for each player
|
||||
if gMySpectator.FogOfWar.CheckTileRevelation(fUnit.Position.X, fUnit.Position.Y) < 255 then Exit;
|
||||
|
||||
if makeBattleCry then
|
||||
gSoundPlayer.PlayWarrior(fUnit.UnitType, spBattleCry, fUnit.PositionF);
|
||||
|
||||
case fUnit.UnitType of
|
||||
utCrossbowman: gSoundPlayer.Play(sfxCrossbowDraw, fUnit.PositionF); // Aiming
|
||||
utBowman: gSoundPlayer.Play(sfxBowDraw, fUnit.PositionF); // Aiming
|
||||
utRogue: gSoundPlayer.Play(sfxSlingerShoot, fUnit.PositionF);
|
||||
else begin
|
||||
if IsHit then
|
||||
gSoundPlayer.Play(MeleeSoundsHit[Random(Length(MeleeSoundsHit))], fUnit.PositionF)
|
||||
else
|
||||
gSoundPlayer.Play(MeleeSoundsMiss[Random(Length(MeleeSoundsMiss))], fUnit.PositionF);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionFight.ExecuteValidateOpponent(Step: Byte): TKMActionResult;
|
||||
begin
|
||||
Result := arActContinues;
|
||||
//See if Opponent has walked away (i.e. Serf) or died
|
||||
if fOpponent.IsDeadOrDying //Don't continue to fight dead units
|
||||
or not fOpponent.Visible //Don't continue to fight units that have went into a house
|
||||
or (gHands[fUnit.Owner].Alliances[fOpponent.Owner] = atAlly) //Unit could become ally from script
|
||||
or not TKMUnitWarrior(fUnit).WithinFightRange(fOpponent.Position)
|
||||
or (TKMUnitWarrior(fUnit).IsRanged // For ranged - ignore opponents who are under FOW
|
||||
and (gHands[fUnit.Owner].FogOfWar.CheckTileRevelation(fOpponent.Position.X, fOpponent.Position.Y) <> 255))
|
||||
or not fUnit.CanWalkDiagonally(fUnit.Position, fOpponent.Position) then //Might be a tree between us now
|
||||
begin
|
||||
//After killing an opponent there is a very high chance that there is another enemy to be fought immediately
|
||||
//Try to start fighting that enemy by reusing this FightAction, rather than destroying it and making a new one
|
||||
Locked := False; //Fight can be interrupted by FindEnemy, otherwise it will always return nil!
|
||||
gHands.CleanUpUnitPointer(fOpponent); //We are finished with the old opponent
|
||||
fOpponent := TKMUnitWarrior(fUnit).FindEnemy; //Find a new opponent
|
||||
if fOpponent <> nil then
|
||||
begin
|
||||
//Start fighting this opponent by resetting the action
|
||||
fOpponent.GetPointer; //Add to pointer count
|
||||
TKMUnitWarrior(fUnit).OnPickedFight(TKMUnitWarrior(fUnit), fOpponent);
|
||||
Locked := True;
|
||||
fFightDelay := -1;
|
||||
//Ranged units should turn to face the new opponent immediately
|
||||
if TKMUnitWarrior(fUnit).IsRanged then
|
||||
fUnit.Direction := KMGetDirection(fUnit.PositionF, fOpponent.PositionF)
|
||||
else
|
||||
//Melee: If we haven't yet placed our strike, reset the animation step
|
||||
//Otherwise finish this strike then we can face the new opponent automatically
|
||||
if Step <= STRIKE_STEP then
|
||||
fUnit.AnimStep := 0; //Rest fight animation/sequence
|
||||
end
|
||||
else
|
||||
begin
|
||||
//No one else to fight, so we exit
|
||||
Result := arActDone;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
//A result of True means exit from Execute
|
||||
function TKMUnitActionFight.ExecuteProcessRanged(Step: Byte): Boolean;
|
||||
var
|
||||
W: TKMUnitWarrior;
|
||||
begin
|
||||
Result := False;
|
||||
|
||||
W := TKMUnitWarrior(fUnit);
|
||||
|
||||
if Step = 0 then
|
||||
begin
|
||||
if fFightDelay = -1 then //Initialize
|
||||
fFightDelay := W.AimingDelay;
|
||||
|
||||
if fFightDelay > 0 then
|
||||
begin
|
||||
Dec(fFightDelay);
|
||||
Result := True; //do not increment AnimStep, just exit;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
//Slingshot sound should happen a bit later
|
||||
if Step = W.AimSoundDelay then
|
||||
MakeSound(False);
|
||||
|
||||
if Step = W.FiringDelay then
|
||||
begin
|
||||
W.SetLastShootTime; //Record last time the warrior shot
|
||||
|
||||
// Fire the arrow
|
||||
gProjectiles.AimTargetUnit(fUnit.PositionF, fOpponent, W.ProjectileType, fUnit, W.RangeMax, W.RangeMin);
|
||||
|
||||
fFightDelay := -1; //Reset
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
//A result of True means exit from Execute
|
||||
function TKMUnitActionFight.ExecuteProcessMelee(Step: Byte): Boolean;
|
||||
var
|
||||
isHit: Boolean;
|
||||
damage: Word;
|
||||
begin
|
||||
Result := False;
|
||||
|
||||
if Step = 1 then
|
||||
begin
|
||||
//Tell the Opponent we are attacking him
|
||||
gHands[fOpponent.Owner].AI.UnitAttackNotification(fOpponent, TKMUnitWarrior(fUnit));
|
||||
|
||||
//Tell our AI that we are in a battle and might need assistance! (only for melee battles against warriors)
|
||||
if (fOpponent is TKMUnitWarrior) and not TKMUnitWarrior(fUnit).IsRanged then
|
||||
gHands[fUnit.Owner].AI.UnitAttackNotification(fUnit, TKMUnitWarrior(fOpponent), False);
|
||||
end;
|
||||
|
||||
//Melee units place hit on this step
|
||||
if Step = STRIKE_STEP then
|
||||
begin
|
||||
//Base damage is the unit attack strength + AttackHorse if the enemy is mounted
|
||||
damage := gRes.Units[fUnit.UnitType].Attack;
|
||||
if (fOpponent.UnitType in [Low(UNIT_TO_GROUP_TYPE) .. High(UNIT_TO_GROUP_TYPE)]) and (UNIT_TO_GROUP_TYPE[fOpponent.UnitType] = gtMounted) then
|
||||
damage := damage + gRes.Units[fUnit.UnitType].AttackHorse;
|
||||
|
||||
damage := damage * (GetDirModifier(fUnit.Direction, fOpponent.Direction) + 1); // Direction modifier
|
||||
//Defence modifier
|
||||
damage := damage div Math.max(gRes.Units[fOpponent.UnitType].Defence, 1); //Not needed, but animals have 0 defence
|
||||
|
||||
isHit := (damage >= KaMRandom(101{$IFDEF DBG_RNG_SPY}, 'TKMUnitActionFight.ExecuteProcessMelee'{$ENDIF})); //Damage is a % chance to hit
|
||||
if isHit then
|
||||
fOpponent.HitPointsDecrease(1, fUnit);
|
||||
|
||||
MakeSound(isHit); //Different sounds for hit and for miss
|
||||
end;
|
||||
|
||||
//In KaM melee units pause for 1 tick on Steps [0,3,6]. Made it random so troops are not striking in sync,
|
||||
//plus it adds randomness to battles
|
||||
if Step in [0,3,6] then
|
||||
begin
|
||||
if fFightDelay = -1 then //Initialize
|
||||
fFightDelay := KaMRandom(2{$IFDEF DBG_RNG_SPY}, 'TKMUnitActionFight.ExecuteProcessMelee 2'{$ENDIF});
|
||||
|
||||
if fFightDelay > 0 then
|
||||
begin
|
||||
dec(fFightDelay);
|
||||
Result := True; //Means exit from Execute
|
||||
Exit;
|
||||
end;
|
||||
|
||||
fFightDelay := -1; //Reset
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionFight.Execute: TKMActionResult;
|
||||
var
|
||||
cycle, step: Byte;
|
||||
W: TKMUnitWarrior;
|
||||
begin
|
||||
W := TKMUnitWarrior(fUnit);
|
||||
cycle := Max(gRes.Units[fUnit.UnitType].UnitAnim[ActionType, fUnit.Direction].Count, 1);
|
||||
step := fUnit.AnimStep mod cycle;
|
||||
|
||||
Result := ExecuteValidateOpponent(step);
|
||||
if Result = arActDone then
|
||||
begin
|
||||
// Face back to our order direction when we have done fighting with our enemy
|
||||
if W.IsRanged and (W.FaceDir <> dirNA) then
|
||||
fUnit.Direction := W.FaceDir;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
step := fUnit.AnimStep mod cycle; //Can be changed by ExecuteValidateOpponent, so recalculate it
|
||||
|
||||
//Opponent can walk next to us, keep facing him
|
||||
if step = 0 then //Only change direction between strikes, otherwise it looks odd
|
||||
fUnit.Direction := KMGetDirection(fUnit.PositionF, fOpponent.PositionF);
|
||||
|
||||
//If the vertex usage has changed we should update it
|
||||
if not W.IsRanged then //Ranged units do not use verticies
|
||||
if not UpdateVertexUsage(fUnit.Position, fOpponent.Position) then
|
||||
begin
|
||||
//The vertex is being used so we can't fight
|
||||
Result := arActDone;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if W.IsRanged then
|
||||
begin
|
||||
if ExecuteProcessRanged(step) then
|
||||
Exit;
|
||||
end
|
||||
else
|
||||
if ExecuteProcessMelee(step) then
|
||||
Exit;
|
||||
|
||||
//Aiming Archers and pausing melee may miss a few ticks, (exited above) so don't put anything critical below!
|
||||
|
||||
StepDone := (fUnit.AnimStep mod cycle = 0) or W.IsRanged; //Archers may abandon at any time as they need to walk off imediantly
|
||||
Inc(fUnit.AnimStep);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitActionFight.Save(SaveStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
|
||||
SaveStream.PlaceMarker('UnitActionFight');
|
||||
if fOpponent <> nil then
|
||||
SaveStream.Write(fOpponent.UID) //Store ID, then substitute it with reference on SyncLoad
|
||||
else
|
||||
SaveStream.Write(Integer(0));
|
||||
SaveStream.Write(fFightDelay);
|
||||
SaveStream.Write(fVertexOccupied);
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionFight.CanBeInterrupted(aForced: Boolean = True): Boolean;
|
||||
begin
|
||||
Result := (TKMUnitWarrior(fUnit).IsRanged and aForced) or not Locked; //Only allowed to interupt ranged fights
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionFight.ObjToStringShort(const aSeparator: String = '|'): String;
|
||||
begin
|
||||
Result := inherited ObjToStringShort(aSeparator) +
|
||||
Format('%sopponent = [%s]%svertexOccupied = %s',
|
||||
[aSeparator,
|
||||
fOpponent.ObjToStringBasic(aSeparator), aSeparator,
|
||||
fVertexOccupied.ToString]);
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionFight.ObjToString(const aSeparator: String = ' '): String;
|
||||
begin
|
||||
Result := inherited ObjToString(aSeparator) +
|
||||
Format('%sopponent = [%s]%svertexOccupied = %s',
|
||||
[aSeparator,
|
||||
fOpponent.ObjToStringBasic(aSeparator), aSeparator,
|
||||
fVertexOccupied.ToString]);
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,522 @@
|
||||
unit KM_UnitActionGoInOut;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
Classes, KromUtils, SysUtils,
|
||||
KM_CommonClasses, KM_CommonTypes, KM_Defaults, KM_Points,
|
||||
KM_Houses, KM_Units;
|
||||
|
||||
|
||||
type
|
||||
TKMBestExit = (beNone, beLeft, beCenter, beRight);
|
||||
|
||||
{This is a [fairly :P] simple action making unit go inside/outside of house}
|
||||
TKMUnitActionGoInOut = class(TKMUnitAction)
|
||||
private
|
||||
fStep: Single;
|
||||
fHouse: TKMHouse;
|
||||
fDirection: TKMGoInDirection;
|
||||
fDoor: TKMPoint;
|
||||
fStreet: TKMPoint;
|
||||
fInitiated: Boolean;
|
||||
fPushedUnit: TKMUnit;
|
||||
fWaitingForPush: Boolean;
|
||||
fUsedDoorway: Boolean;
|
||||
procedure IncDoorway;
|
||||
procedure DecDoorway;
|
||||
function FindBestExit(const aLoc: TKMPoint): TKMBestExit;
|
||||
function TileHasIdleUnitToPush(X,Y: Word): TKMUnit;
|
||||
function TileHasUnitOnHouseEntrance: Boolean;
|
||||
procedure WalkIn;
|
||||
procedure WalkOut;
|
||||
function GetIsStarted: Boolean;
|
||||
public
|
||||
OnWalkedOut: TKMEvent; //NOTE: Caller must sync these events after loading, used with caution
|
||||
OnWalkedIn: TKMEvent;
|
||||
constructor Create(aUnit: TKMUnit; aAction: TKMUnitActionType; aDirection: TKMGoInDirection; aHouse: TKMHouse);
|
||||
constructor Load(LoadStream: TKMemoryStream); override;
|
||||
procedure SyncLoad; override;
|
||||
destructor Destroy; override;
|
||||
function ActName: TKMUnitActionName; override;
|
||||
function CanBeInterrupted(aForced: Boolean = True): Boolean; override;
|
||||
function GetExplanation: UnicodeString; override;
|
||||
property IsStarted: Boolean read GetIsStarted; // Is unit actually started exiting or going inside?
|
||||
property Direction: TKMGoInDirection read fDirection;
|
||||
function GetDoorwaySlide(aCheck: TKMCheckAxis): Single;
|
||||
function GetDoorwaySlides: TKMPointF;
|
||||
function Execute: TKMActionResult; override;
|
||||
procedure Save(SaveStream: TKMemoryStream); override;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
KM_Entity,
|
||||
KM_HandsCollection, KM_HandTypes, KM_HandEntity,
|
||||
KM_Resource, KM_Terrain, KM_UnitActionStay, KM_UnitActionWalkTo,
|
||||
KM_HouseBarracks, KM_ResHouses, KM_ResUnits, KM_CommonUtils, KM_GameParams,
|
||||
KM_ResTypes;
|
||||
|
||||
|
||||
{ TKMUnitActionGoInOut }
|
||||
constructor TKMUnitActionGoInOut.Create(aUnit: TKMUnit; aAction: TKMUnitActionType; aDirection: TKMGoInDirection; aHouse: TKMHouse);
|
||||
begin
|
||||
inherited Create(aUnit, aAction, True);
|
||||
|
||||
//We might stuck trying to exit when house gets destroyed (1)
|
||||
//and we might be dying in destroyed house (2)
|
||||
fHouse := aHouse.GetPointer;
|
||||
fDirection := aDirection;
|
||||
fInitiated := False;
|
||||
fWaitingForPush := False;
|
||||
|
||||
if fDirection = gdGoInside then
|
||||
fStep := 1 //go Inside (one cell up)
|
||||
else
|
||||
fStep := 0; //go Outside (one cell down)
|
||||
end;
|
||||
|
||||
|
||||
constructor TKMUnitActionGoInOut.Load(LoadStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
|
||||
LoadStream.CheckMarker('UnitActionGoInOut');
|
||||
LoadStream.Read(fStep);
|
||||
LoadStream.Read(fHouse, 4);
|
||||
LoadStream.Read(fPushedUnit, 4);
|
||||
LoadStream.Read(fDirection, SizeOf(fDirection));
|
||||
LoadStream.Read(fDoor);
|
||||
LoadStream.Read(fStreet);
|
||||
LoadStream.Read(fInitiated);
|
||||
LoadStream.Read(fWaitingForPush);
|
||||
LoadStream.Read(fUsedDoorway);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitActionGoInOut.SyncLoad;
|
||||
begin
|
||||
inherited;
|
||||
|
||||
fHouse := gHands.GetHouseByUID(Integer(fHouse));
|
||||
fPushedUnit := gHands.GetUnitByUID(Integer(fPushedUnit));
|
||||
end;
|
||||
|
||||
|
||||
destructor TKMUnitActionGoInOut.Destroy;
|
||||
begin
|
||||
if fUsedDoorway then
|
||||
DecDoorway;
|
||||
|
||||
gHands.CleanUpHousePointer(fHouse);
|
||||
gHands.CleanUpUnitPointer(fPushedUnit);
|
||||
|
||||
|
||||
if (fUnit <> nil)
|
||||
and fInitiated
|
||||
and (gTerrain.Land^[fUnit.PositionNext.Y, fUnit.PositionNext.X].IsUnit = fUnit) then
|
||||
begin
|
||||
case fDirection of
|
||||
//Clear terrain lock for house entrance that made while unit was entering the house
|
||||
gdGoInside: gTerrain.UnitRem(fUnit.PositionNext);
|
||||
|
||||
//A bug can occur because this action is destroyed early when a unit is told to die.
|
||||
//If we are still invisible then TTaskDie assumes we are inside and creates a new
|
||||
//GoOut action. Therefore if we are invisible we do not occupy a tile.
|
||||
gdGoOutside: if not fUnit.Visible then
|
||||
begin
|
||||
gTerrain.UnitRem(fUnit.PositionNext);
|
||||
if not KMSamePoint(fDoor, KMPOINT_ZERO) then
|
||||
fUnit.PositionF := KMPointF(fDoor); //Put us back inside the house
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
if (fDirection = gdGoOutside) and fUnit.Visible then
|
||||
fUnit.InHouse := nil; //We are not in any house now
|
||||
|
||||
inherited;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionGoInOut.ActName: TKMUnitActionName;
|
||||
begin
|
||||
Result := uanGoInOut;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionGoInOut.GetExplanation: UnicodeString;
|
||||
begin
|
||||
Result := 'Walking in/out';
|
||||
end;
|
||||
|
||||
|
||||
// Is unit actually started exiting or going inside?
|
||||
function TKMUnitActionGoInOut.GetIsStarted: Boolean;
|
||||
begin
|
||||
Result := fInitiated and not fWaitingForPush;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitActionGoInOut.IncDoorway;
|
||||
begin
|
||||
Assert(not fUsedDoorway, 'Inc doorway when already in use?');
|
||||
|
||||
if fHouse <> nil then Inc(fHouse.DoorwayUse);
|
||||
fUsedDoorway := True;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitActionGoInOut.DecDoorway;
|
||||
begin
|
||||
Assert(fUsedDoorway, 'Dec doorway when not in use?');
|
||||
|
||||
if fHouse<>nil then dec(fHouse.DoorwayUse);
|
||||
fUsedDoorway := False;
|
||||
end;
|
||||
|
||||
|
||||
//Attempt to find a tile below the door (on the street) we can walk to
|
||||
//We can push idle units away. Check center first
|
||||
function TKMUnitActionGoInOut.FindBestExit(const aLoc: TKMPoint): TKMBestExit;
|
||||
|
||||
function ChooseBestExit(aL, aR: Boolean): TKMBestExit;
|
||||
begin
|
||||
//Choose randomly between left and right
|
||||
if (aL and aR) then
|
||||
begin
|
||||
if KaMRandom(2{$IFDEF DBG_RNG_SPY}, 'TKMUnitActionGoInOut.FindBestExit.ChooseBestExit'{$ENDIF}) = 0 then
|
||||
Result := beLeft
|
||||
else
|
||||
Result := beRight;
|
||||
end
|
||||
else
|
||||
if aL then
|
||||
Result := beLeft
|
||||
else
|
||||
if aR then
|
||||
Result := beRight
|
||||
else
|
||||
Result := beNone;
|
||||
end;
|
||||
|
||||
var
|
||||
U, UC, UL, UR: TKMUnit;
|
||||
L, R: Boolean;
|
||||
begin
|
||||
if fUnit.CanStepTo(aLoc.X, aLoc.Y, tpWalk) then
|
||||
Result := beCenter
|
||||
else
|
||||
begin
|
||||
L := fUnit.CanStepTo(aLoc.X-1, aLoc.Y, tpWalk);
|
||||
R := fUnit.CanStepTo(aLoc.X+1, aLoc.Y, tpWalk);
|
||||
|
||||
Result := ChooseBestExit(L, R);
|
||||
|
||||
if Result = beNone then
|
||||
begin
|
||||
U := nil;
|
||||
UL := nil;
|
||||
UR := nil;
|
||||
//U could be nil if tile is unwalkable for some reason
|
||||
UC := TileHasIdleUnitToPush(aLoc.X, aLoc.Y);
|
||||
if UC <> nil then
|
||||
Result := beCenter
|
||||
else
|
||||
begin
|
||||
UL := TileHasIdleUnitToPush(aLoc.X-1, aLoc.Y);
|
||||
L := UL <> nil;
|
||||
UR := TileHasIdleUnitToPush(aLoc.X+1, aLoc.Y);
|
||||
R := UR <> nil;
|
||||
Result := ChooseBestExit(L, R);
|
||||
end;
|
||||
|
||||
case Result of
|
||||
beCenter: U := UC;
|
||||
beLeft: U := UL;
|
||||
beRight: U := UR;
|
||||
end;
|
||||
|
||||
if U <> nil then
|
||||
begin
|
||||
fPushedUnit := U.GetPointer;
|
||||
fPushedUnit.SetActionWalkPushed(gTerrain.GetOutOfTheWay(U, KMPOINT_ZERO, tpWalk));
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
//Check that tile is walkable and there's no unit blocking it or that unit can be pushed away
|
||||
function TKMUnitActionGoInOut.TileHasIdleUnitToPush(X,Y: Word): TKMUnit;
|
||||
var
|
||||
U: TKMUnit;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
if gTerrain.TileInMapCoords(X,Y)
|
||||
and (gTerrain.CheckPassability(KMPoint(X,Y), fUnit.DesiredPassability))
|
||||
and (gTerrain.CanWalkDiagonally(fUnit.Position, X, Y))
|
||||
and (gTerrain.Land^[Y,X].IsUnit <> nil) then //If there's some unit we need to do a better check on him
|
||||
begin
|
||||
U := gTerrain.UnitsHitTest(X,Y); //Let's see who is standing there
|
||||
|
||||
//Check that the unit is idling and not an enemy, so that we can push it away
|
||||
if (U <> nil)
|
||||
and not U.IsAnimal // Can't push animals
|
||||
and (U.Action is TKMUnitActionStay)
|
||||
and not TKMUnitActionStay(U.Action).Locked
|
||||
and (gHands.CheckAlliance(U.Owner, fUnit.Owner) = atAlly) then
|
||||
Result := U;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionGoInOut.TileHasUnitOnHouseEntrance: Boolean;
|
||||
begin
|
||||
if fHouse.IsDestroyed then Exit(False);
|
||||
|
||||
// There could be a unit walking in the house already,
|
||||
// f.e. if serf was added at point below entrance by script and he went straight into the same house
|
||||
Result := (gTerrain.Land^[fHouse.Entrance.Y, fHouse.Entrance.X].IsUnit <> nil);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitActionGoInOut.WalkIn;
|
||||
begin
|
||||
fUnit.Direction := dirN; //one cell up
|
||||
fUnit.PositionNext := KMPointAbove(fUnit.Position);
|
||||
gTerrain.UnitRem(fUnit.Position); // Release tile at point below house entrance
|
||||
// Unit occupy a tile on a terrain while he is walking inside house
|
||||
// House could be destroyed while while unit was walking in it, so we need to have a tile occupied on terrain for that case
|
||||
// After house was destroyed other unit could go there / smth could be placed with as script,
|
||||
// but this unit is still walking into house so we have keep it consistent:
|
||||
// unit occupied tile if he is standing on that tile or he is going to get onto that tile
|
||||
gTerrain.UnitAdd(fUnit.PositionNext, fUnit);
|
||||
|
||||
//We are walking straight
|
||||
if fStreet.X = fDoor.X then
|
||||
IncDoorway;
|
||||
end;
|
||||
|
||||
|
||||
//Start walking out of the house. unit is no longer in the house
|
||||
procedure TKMUnitActionGoInOut.WalkOut;
|
||||
begin
|
||||
fUnit.Direction := KMGetDirection(fDoor, fStreet);
|
||||
fUnit.PositionNext := fStreet;
|
||||
gTerrain.UnitAdd(fUnit.PositionNext, fUnit); //Unit was not occupying tile while inside
|
||||
|
||||
//Use InHouse instead of Home, since Home could be cleared via ProceedHouseClosedForWorker (f.e. when wGoingForEating = True)
|
||||
if (fUnit.UnitType = utRecruit) //Recruit
|
||||
and(fUnit.InHouse <> nil) //In some house
|
||||
and (fUnit.InHouse.HouseType = htBarracks) //Recruit is in barracks
|
||||
and (fUnit.InHouse = fHouse) then //And is the house we are walking from
|
||||
TKMHouseBarracks(fHouse).RecruitsRemove(fUnit);
|
||||
|
||||
//We are walking straight
|
||||
if fStreet.X = fDoor.X then
|
||||
IncDoorway;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionGoInOut.GetDoorwaySlide(aCheck: TKMCheckAxis): Single;
|
||||
var
|
||||
offset: Single;
|
||||
begin
|
||||
if (fHouse = nil) or not fInitiated then
|
||||
Result := 0
|
||||
else
|
||||
begin
|
||||
offset := gRes.Houses[fHouse.HouseType].GetDoorwayOffset(aCheck);
|
||||
|
||||
Result := Mix(0, offset, fStep);
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionGoInOut.GetDoorwaySlides: TKMPointF;
|
||||
begin
|
||||
if (fHouse = nil) or not fInitiated then
|
||||
Result := KMPointF(0, 0)
|
||||
else
|
||||
begin
|
||||
Result.X := Mix(0, gRes.Houses[fHouse.HouseType].GetDoorwayOffset(axX), fStep);
|
||||
Result.Y := Mix(0, gRes.Houses[fHouse.HouseType].GetDoorwayOffset(axY), fStep);
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionGoInOut.Execute: TKMActionResult;
|
||||
var
|
||||
U: TKMUnit;
|
||||
distance: Single;
|
||||
begin
|
||||
Result := arActContinues;
|
||||
|
||||
if not fInitiated then
|
||||
begin
|
||||
//Set Door and Street locations
|
||||
fDoor := KMPoint(fUnit.Position.X, fUnit.Position.Y - Round(fStep));
|
||||
fStreet := KMPoint(fUnit.Position.X, fUnit.Position.Y + 1 - Round(fStep));
|
||||
|
||||
case fDirection of
|
||||
gdGoInside: // House could be destroyed on the same tick Action was created
|
||||
// So we can't do the check before this very moment
|
||||
|
||||
// Do not allow to enter already destroyed houses
|
||||
// Since we did not occupy entrance tile other units inside house could do that already
|
||||
if fHouse.IsDestroyed then
|
||||
Exit(arActCanNotStart)
|
||||
else
|
||||
// There could be a unit walking in the house already,
|
||||
// f.e. if serf was added at point below entrance by script and he went straight into the same house
|
||||
if TileHasUnitOnHouseEntrance then
|
||||
Exit
|
||||
else
|
||||
WalkIn;
|
||||
gdGoOutside: begin
|
||||
case FindBestExit(fStreet) of
|
||||
beLeft: fStreet.X := fStreet.X - 1;
|
||||
beCenter: ;
|
||||
beRight: fStreet.X := fStreet.X + 1;
|
||||
beNone: Exit; //All street tiles are blocked by busy units. Do not exit the house, just wait
|
||||
end;
|
||||
|
||||
//If we have pushed an idling unit, wait till it goes away
|
||||
//Wait until our push request is dealt with before we move out
|
||||
if (fPushedUnit <> nil) then
|
||||
begin
|
||||
fWaitingForPush := True;
|
||||
fInitiated := True;
|
||||
Exit;
|
||||
end
|
||||
else
|
||||
WalkOut; //Otherwise we can walk out now
|
||||
end;
|
||||
end;
|
||||
|
||||
fInitiated := True;
|
||||
end;
|
||||
|
||||
|
||||
if fWaitingForPush then
|
||||
begin
|
||||
U := gTerrain.Land^[fStreet.Y,fStreet.X].IsUnit;
|
||||
if (U = nil) then //Unit has walked away
|
||||
begin
|
||||
fWaitingForPush := False;
|
||||
gHands.CleanUpUnitPointer(fPushedUnit);
|
||||
WalkOut;
|
||||
end
|
||||
else
|
||||
begin //There's still some unit - we can't go outside
|
||||
if (U <> fPushedUnit) //The unit has switched places with another one, so we must start again
|
||||
or not (U.Action is TKMUnitActionWalkTo) //Unit was interupted (no longer pushed), so start again
|
||||
or not TKMUnitActionWalkTo(U.Action).WasPushed then
|
||||
begin
|
||||
fInitiated := False;
|
||||
fWaitingForPush := False;
|
||||
gHands.CleanUpUnitPointer(fPushedUnit);
|
||||
end;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
//IsExchanging can be updated while we have completed less than 20% of the move. If it is changed after that
|
||||
//the unit makes a noticable "jump". This needs to be updated after starting because we don't know about an
|
||||
//exchanging unit until they have also started walking (otherwise only 1 of the units will have IsExchanging = True)
|
||||
if (
|
||||
((fDirection = gdGoOutside) and (fStep < 0.2)) or
|
||||
((fDirection = gdGoInside) and (fStep > 0.8))
|
||||
)
|
||||
and (fStreet.X = fDoor.X) //We are walking straight
|
||||
and (fHouse <> nil) then
|
||||
fUnit.IsExchanging := (fHouse.DoorwayUse > 1);
|
||||
|
||||
Assert((fHouse = nil) or KMSamePoint(fDoor, fHouse.Entrance)); //Must always go in/out the entrance of the house
|
||||
|
||||
//Actual speed is slower if we are moving diagonally, due to the fact we are moving in X and Y
|
||||
distance := gRes.Units[fUnit.UnitType].GetEffectiveWalkSpeed(fStreet.X - fDoor.X <> 0);
|
||||
|
||||
fStep := fStep - distance * ShortInt(fDirection);
|
||||
fUnit.PositionF := KMLerp(fDoor, fStreet, fStep);
|
||||
fUnit.Visible := (fHouse = nil) or (fHouse.IsDestroyed) or (fStep > 0); //Make unit invisible when it's inside of House
|
||||
|
||||
if (fStep <= 0) or (fStep >= 1) then
|
||||
begin
|
||||
Result := arActDone;
|
||||
fUnit.IsExchanging := False;
|
||||
if fUsedDoorway then DecDoorway;
|
||||
if fDirection = gdGoInside then
|
||||
begin
|
||||
fUnit.PositionF := KMPointF(fDoor);
|
||||
if (fUnit.Home <> nil)
|
||||
and (fUnit.Home = fHouse)
|
||||
and (fUnit.Home.HouseType = htBarracks) //Unit home is barracks
|
||||
and not fUnit.Home.IsDestroyed then //And is the house we are walking into and it's not destroyed
|
||||
TKMHouseBarracks(fUnit.Home).RecruitsAdd(fUnit); //Add the recruit once it is inside, otherwise it can be equipped while still walking in!
|
||||
//Set us as inside even if the house is destroyed. In that case UpdateVisibility will sort things out.
|
||||
|
||||
//When any woodcutter returns home - add an Axe
|
||||
//(this might happen when he walks home or when mining is done)
|
||||
if (fUnit.UnitType = utWoodcutter)
|
||||
and (fUnit.Home <> nil)
|
||||
and (fUnit.Home.HouseType = htWoodcutters)
|
||||
and (fUnit.Home = fHouse) then //And is the house we are walking from
|
||||
fHouse.CurrentAction.SubActionAdd([haFlagpole]);
|
||||
|
||||
if Assigned(OnWalkedIn) then
|
||||
OnWalkedIn;
|
||||
|
||||
if fHouse <> nil then
|
||||
fUnit.InHouse := fHouse;
|
||||
|
||||
// Release tile, occupied under the house entrance
|
||||
// Unit occupied tile, when he was walking inside house, because he could interact with terrain
|
||||
// if house was destroyed while he was during walk-in process
|
||||
// But when unit entered house he is not on terrain anymore, but is somewhere in the house and he can't interact with terrain
|
||||
// Do not remove tile occupation if house was destroyed while unit was walking in it
|
||||
// we want to keep this place for exact this unit, so he will be not teleported to some other loc under the house
|
||||
// (do not allow other units inside house to get this place occasionaly on house destruction in FindPlaceForUnit method)
|
||||
if (fHouse <> nil) and not fHouse.IsDestroyed then
|
||||
gTerrain.UnitRem(fUnit.Position);
|
||||
end
|
||||
else
|
||||
begin
|
||||
fUnit.PositionF := KMPointF(fStreet);
|
||||
fUnit.InHouse := nil; //We are not in a house any longer
|
||||
if Assigned(OnWalkedOut) then
|
||||
OnWalkedOut;
|
||||
end;
|
||||
end
|
||||
else
|
||||
Inc(fUnit.AnimStep);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitActionGoInOut.Save(SaveStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
|
||||
SaveStream.PlaceMarker('UnitActionGoInOut');
|
||||
SaveStream.Write(fStep);
|
||||
SaveStream.Write(fHouse.UID); //Store ID, then substitute it with reference on SyncLoad
|
||||
SaveStream.Write(fPushedUnit.UID); //Store ID, then substitute it with reference on SyncLoad
|
||||
SaveStream.Write(fDirection, SizeOf(fDirection));
|
||||
SaveStream.Write(fDoor);
|
||||
SaveStream.Write(fStreet);
|
||||
SaveStream.Write(fInitiated);
|
||||
SaveStream.Write(fWaitingForPush);
|
||||
SaveStream.Write(fUsedDoorway);
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionGoInOut.CanBeInterrupted(aForced: Boolean = True): Boolean;
|
||||
begin
|
||||
Result := not Locked; //Never interupt leaving barracks
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
unit KM_UnitActionStay;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
Classes, KM_Defaults, KromUtils, KM_CommonClasses, KM_Units, SysUtils, Math, KM_Points;
|
||||
|
||||
type
|
||||
{Stay in place for set time}
|
||||
TKMUnitActionStay = class(TKMUnitAction)
|
||||
private
|
||||
StayStill:boolean;
|
||||
TimeToStay:integer;
|
||||
StillFrame:byte;
|
||||
procedure MakeSound(Cycle, Step: Byte);
|
||||
public
|
||||
constructor Create(aUnit: TKMUnit; aTimeToStay: Integer; aActionType: TKMUnitActionType; aStayStill: Boolean;
|
||||
aStillFrame: Byte; aLocked: Boolean);
|
||||
constructor Load(LoadStream: TKMemoryStream); override;
|
||||
function ActName: TKMUnitActionName; override;
|
||||
function CanBeInterrupted(aForced: Boolean = True): Boolean; override;
|
||||
function GetExplanation: UnicodeString; override;
|
||||
function Execute: TKMActionResult; override;
|
||||
procedure Save(SaveStream: TKMemoryStream); override;
|
||||
|
||||
function ObjToStringShort(const aSeparator: String = ' '): String; override;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
KM_HandsCollection, KM_Sound, KM_ResSound, KM_Resource, KM_ResUnits;
|
||||
|
||||
|
||||
{ TUnitActionStay }
|
||||
constructor TKMUnitActionStay.Create(aUnit: TKMUnit; aTimeToStay: Integer; aActionType: TKMUnitActionType; aStayStill: Boolean;
|
||||
aStillFrame: Byte; aLocked: Boolean);
|
||||
begin
|
||||
inherited Create(aUnit, aActionType, aLocked);
|
||||
|
||||
StayStill := aStayStill;
|
||||
TimeToStay := aTimeToStay;
|
||||
StillFrame := aStillFrame;
|
||||
end;
|
||||
|
||||
|
||||
constructor TKMUnitActionStay.Load(LoadStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
|
||||
LoadStream.CheckMarker('UnitActionStay');
|
||||
LoadStream.Read(StayStill);
|
||||
LoadStream.Read(TimeToStay);
|
||||
LoadStream.Read(StillFrame);
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionStay.ActName: TKMUnitActionName;
|
||||
begin
|
||||
Result := uanStay;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionStay.GetExplanation: UnicodeString;
|
||||
begin
|
||||
Result := 'Staying';
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitActionStay.MakeSound(Cycle, Step: Byte);
|
||||
begin
|
||||
if SKIP_SOUND then Exit;
|
||||
|
||||
//Do not play sounds if unit is invisible to gMySpectator
|
||||
if gMySpectator.FogOfWar.CheckTileRevelation(fUnit.Position.X, fUnit.Position.Y) < 255 then exit;
|
||||
|
||||
//Various UnitTypes and ActionTypes produce all the sounds
|
||||
case fUnit.UnitType of
|
||||
utBuilder: case ActionType of
|
||||
uaWork: if Step = 3 then gSoundPlayer.Play(sfxHouseBuild, fUnit.PositionF);
|
||||
uaWork1: if Step = 0 then gSoundPlayer.Play(sfxDig, fUnit.PositionF);
|
||||
uaWork2: if Step = 8 then gSoundPlayer.Play(sfxPave, fUnit.PositionF);
|
||||
end;
|
||||
utFarmer: case ActionType of
|
||||
uaWork: if Step = 8 then gSoundPlayer.Play(sfxCornCut, fUnit.PositionF);
|
||||
uaWork1: if Step = 0 then gSoundPlayer.Play(sfxCornSow, fUnit.PositionF,True,0.6);
|
||||
end;
|
||||
utStonemason: if ActionType = uaWork then
|
||||
if Step = 3 then gSoundPlayer.Play(sfxMinestone, fUnit.PositionF,True,1.4);
|
||||
utWoodCutter: case ActionType of
|
||||
uaWork: if (fUnit.AnimStep mod Cycle = 3) and (fUnit.Direction <> dirN) then gSoundPlayer.Play(sfxChopTree, fUnit.PositionF,True)
|
||||
else if (fUnit.AnimStep mod Cycle = 0) and (fUnit.Direction = dirN) then gSoundPlayer.Play(sfxWoodcutterDig, fUnit.PositionF,True);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionStay.Execute: TKMActionResult;
|
||||
var
|
||||
cycle, step: Byte;
|
||||
begin
|
||||
if not StayStill then
|
||||
begin
|
||||
cycle := Max(gRes.Units[fUnit.UnitType].UnitAnim[ActionType, fUnit.Direction].Count, 1);
|
||||
step := fUnit.AnimStep mod cycle;
|
||||
|
||||
StepDone := fUnit.AnimStep mod cycle = 0;
|
||||
|
||||
if TimeToStay >= 1 then MakeSound(cycle, step);
|
||||
|
||||
Inc(fUnit.AnimStep);
|
||||
end
|
||||
else
|
||||
begin
|
||||
fUnit.AnimStep := StillFrame;
|
||||
StepDone := True;
|
||||
end;
|
||||
|
||||
Dec(TimeToStay);
|
||||
if TimeToStay <= 0 then
|
||||
Result := arActDone
|
||||
else
|
||||
Result := arActContinues;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitActionStay.Save(SaveStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
|
||||
SaveStream.PlaceMarker('UnitActionStay');
|
||||
SaveStream.Write(StayStill);
|
||||
SaveStream.Write(TimeToStay);
|
||||
SaveStream.Write(StillFrame);
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionStay.CanBeInterrupted(aForced: Boolean = True): Boolean;
|
||||
begin
|
||||
Result := not Locked; //Initial pause before leaving barracks is locked
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionStay.ObjToStringShort(const aSeparator: String): String;
|
||||
begin
|
||||
Result := inherited + Format('%s[StayStill = %s%sTimeToStay = %d%sStillFrame = %d]', [
|
||||
aSeparator,
|
||||
BoolToStr(StayStill, True), aSeparator,
|
||||
TimeToStay, aSeparator,
|
||||
StillFrame]);
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,209 @@
|
||||
unit KM_UnitActionSteer;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
Classes, SysUtils, Math, KM_Defaults, KM_CommonClasses, KM_Units, KM_Points;
|
||||
|
||||
{Steer in place for set time}
|
||||
type
|
||||
TKMUnitActionSteer = class(TKMUnitAction)
|
||||
private
|
||||
fDesireToSteer, fStuckFor: Byte; //Likelihood of changing direction
|
||||
fVertexOccupied: TKMPoint; //The diagonal vertex we are currently occupying
|
||||
fNextPos: TKMPoint; //The tile we are currently walking to
|
||||
procedure IncVertex(const aFrom, aTo: TKMPoint);
|
||||
procedure DecVertex;
|
||||
function ChooseNextStep(out Point: TKMPoint): Boolean;
|
||||
public
|
||||
constructor Create(aUnit: TKMUnit; aActionType: TKMUnitActionType; aLocked: Boolean);
|
||||
constructor Load(LoadStream: TKMemoryStream); override;
|
||||
destructor Destroy; override;
|
||||
function ActName: TKMUnitActionName; override;
|
||||
function GetExplanation: UnicodeString; override;
|
||||
function Execute: TKMActionResult; override;
|
||||
procedure Save(SaveStream: TKMemoryStream); override;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
KM_CommonUtils, KM_Resource, KM_ResUnits;
|
||||
|
||||
|
||||
{ TUnitActionSteer }
|
||||
constructor TKMUnitActionSteer.Create(aUnit: TKMUnit; aActionType: TKMUnitActionType; aLocked: Boolean);
|
||||
begin
|
||||
inherited Create(aUnit, aActionType, aLocked);
|
||||
|
||||
Assert(aUnit is TKMUnitAnimal); //Only animals do steering
|
||||
fVertexOccupied := KMPOINT_ZERO;
|
||||
fNextPos := KMPOINT_ZERO;
|
||||
end;
|
||||
|
||||
|
||||
destructor TKMUnitActionSteer.Destroy;
|
||||
begin
|
||||
if not KMSamePoint(fVertexOccupied, KMPOINT_ZERO) then
|
||||
DecVertex;
|
||||
|
||||
inherited;
|
||||
end;
|
||||
|
||||
|
||||
constructor TKMUnitActionSteer.Load(LoadStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
|
||||
LoadStream.CheckMarker('UnitActionSteer');
|
||||
LoadStream.Read(fDesireToSteer);
|
||||
LoadStream.Read(fStuckFor);
|
||||
LoadStream.Read(fVertexOccupied);
|
||||
LoadStream.Read(fNextPos);
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionSteer.ActName: TKMUnitActionName;
|
||||
begin
|
||||
Result := uanSteer;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionSteer.GetExplanation: UnicodeString;
|
||||
begin
|
||||
Result := 'Steering';
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitActionSteer.IncVertex(const aFrom, aTo: TKMPoint);
|
||||
begin
|
||||
//Tell gTerrain that this vertex is being used so no other unit walks over the top of us
|
||||
Assert(KMSamePoint(fVertexOccupied, KMPOINT_ZERO), 'Steer vertex in use');
|
||||
|
||||
fUnit.VertexAdd(aFrom,aTo);
|
||||
fVertexOccupied := KMGetDiagVertex(aFrom,aTo);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitActionSteer.DecVertex;
|
||||
begin
|
||||
//Tell gTerrain that this vertex is not being used anymore
|
||||
Assert(not KMSamePoint(fVertexOccupied, KMPOINT_ZERO), 'DecVertex 0:0 Steer');
|
||||
|
||||
fUnit.VertexRem(fVertexOccupied);
|
||||
fVertexOccupied := KMPOINT_ZERO;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionSteer.ChooseNextStep(out Point: TKMPoint): Boolean;
|
||||
var
|
||||
I,K,J: Integer;
|
||||
loc: TKMPoint;
|
||||
list: TKMPointList;
|
||||
goodSpot: Boolean;
|
||||
begin
|
||||
Inc(fDesireToSteer);
|
||||
//Default is the next tile in the direction we're going
|
||||
loc := KMGetPointInDir(fUnit.Position, fUnit.Direction);
|
||||
//Decide whether we should change direction or not
|
||||
if (KaMRandom(10{$IFDEF DBG_RNG_SPY}, 'TKMUnitActionSteer.ChooseNextStep'{$ENDIF}) < fDesireToSteer)
|
||||
or not fUnit.CanStepTo(loc.X, loc.Y, fUnit.DesiredPassability) then
|
||||
begin
|
||||
fDesireToSteer := 0; //Reset it
|
||||
list := TKMPointList.Create;
|
||||
loc := fUnit.Position;
|
||||
for I:=-1 to 1 do
|
||||
for K:=-1 to 1 do
|
||||
if ((I<>0)or(K<>0)) and fUnit.CanStepTo(loc.X+I, loc.Y+K, fUnit.DesiredPassability) then
|
||||
begin
|
||||
//Directions next to our current one are preferable (looks nicer if animals don't make jarring direction changes often)
|
||||
goodSpot := KMGetDirection(I, K) in [KMNextDirection(fUnit.Direction), KMPrevDirection(fUnit.Direction)];
|
||||
for J:=0 to 5*Byte(goodSpot) do
|
||||
list.Add(KMPoint(loc.X+I, loc.Y+K));
|
||||
end;
|
||||
Result := list.GetRandom(Point);
|
||||
list.Free;
|
||||
end
|
||||
else
|
||||
begin
|
||||
Point := loc;
|
||||
Result := True;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionSteer.Execute: TKMActionResult;
|
||||
const
|
||||
STUCK_MAX_TIME = 200;
|
||||
var
|
||||
dx, dy: Shortint;
|
||||
walkX, walkY, distance: Single;
|
||||
firstStep: Boolean;
|
||||
begin
|
||||
if KMSamePoint(fNextPos, KMPOINT_ZERO) then
|
||||
begin
|
||||
fNextPos := fUnit.Position; //Set fNextPos to current pos so it initializes on the first run
|
||||
firstStep := True;
|
||||
end
|
||||
else
|
||||
firstStep := False;
|
||||
|
||||
// Use umtWalk move type here, since we just want to evaluate if we are close enough
|
||||
distance := gRes.Units[fUnit.UnitType].GetEffectiveWalkSpeed(False);
|
||||
if KMSamePointF(fUnit.PositionF, KMPointF(fNextPos), distance/2) then
|
||||
begin
|
||||
//Set precise position to avoid rounding errors
|
||||
fUnit.PositionF := KMPointF(fNextPos);
|
||||
|
||||
//No longer using previous vertex
|
||||
if KMStepIsDiag(fUnit.PositionPrev, fUnit.PositionNext) and not firstStep and (fStuckFor = 0) then
|
||||
DecVertex;
|
||||
|
||||
//Decide on next step
|
||||
if not ChooseNextStep(fNextPos) then
|
||||
begin
|
||||
Inc(fStuckFor);
|
||||
if fStuckFor > STUCK_MAX_TIME then
|
||||
Result := arActAborted //We have been stuck for a while so abort and TKMUnitAnimal.UpdateState will kill us
|
||||
else
|
||||
Result := arActContinues;
|
||||
Exit;
|
||||
end;
|
||||
fStuckFor := 0;
|
||||
|
||||
//Do some house keeping because we have now stepped on a new tile
|
||||
fUnit.PositionNext := fNextPos;
|
||||
fUnit.Walk(fUnit.PositionPrev, fUnit.PositionNext); //Pre-occupy next tile
|
||||
if KMStepIsDiag(fUnit.PositionPrev,fUnit.PositionNext) then
|
||||
IncVertex(fUnit.PositionPrev,fUnit.PositionNext);
|
||||
//Update unit direction so we are facing the way we are going
|
||||
fUnit.Direction := KMGetDirection(fUnit.PositionPrev, fUnit.PositionNext);
|
||||
end;
|
||||
|
||||
walkX := fNextPos.X - fUnit.PositionF.X;
|
||||
walkY := fNextPos.Y - fUnit.PositionF.Y;
|
||||
dx := sign(walkX); //-1,0,1
|
||||
dy := sign(walkY); //-1,0,1
|
||||
|
||||
distance := gRes.Units[fUnit.UnitType].GetEffectiveWalkSpeed((dx <> 0) and (dy <> 0));
|
||||
|
||||
fUnit.PositionF := KMPointF(fUnit.PositionF.X + dx*Math.min(distance,abs(walkX)),
|
||||
fUnit.PositionF.Y + dy*Math.min(distance,abs(walkY)));
|
||||
|
||||
Inc(fUnit.AnimStep);
|
||||
Result := arActContinues;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitActionSteer.Save(SaveStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
|
||||
SaveStream.PlaceMarker('UnitActionSteer');
|
||||
SaveStream.Write(fDesireToSteer);
|
||||
SaveStream.Write(fStuckFor);
|
||||
SaveStream.Write(fVertexOccupied);
|
||||
SaveStream.Write(fNextPos);
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,222 @@
|
||||
unit KM_UnitActionStormAttack;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
Classes, Math,
|
||||
KM_CommonClasses, KM_Defaults, KM_Points, KM_CommonUtils,
|
||||
KM_Units;
|
||||
|
||||
|
||||
{Charge forwards until we are tired or hit an obstacle}
|
||||
type
|
||||
TKMUnitActionStormAttack = class(TKMUnitAction)
|
||||
private
|
||||
fDelay: Integer; //Delay before action starts
|
||||
fTileSteps: Integer; //The number of tiles we have walked onto so far
|
||||
fStamina: Integer; //How much stamina to run do we have
|
||||
fNextPos: TKMPoint; //The tile we are currently walking to
|
||||
fVertexOccupied: TKMPoint; //The diagonal vertex we are currently occupying
|
||||
procedure IncVertex(const aFrom, aTo: TKMPoint);
|
||||
procedure DecVertex;
|
||||
public
|
||||
constructor Create(aUnit: TKMUnit; aActionType: TKMUnitActionType; aRow: Integer);
|
||||
constructor Load(LoadStream: TKMemoryStream); override;
|
||||
destructor Destroy; override;
|
||||
function ActName: TKMUnitActionName; override;
|
||||
function CanBeInterrupted(aForced: Boolean = True): Boolean; override;
|
||||
function GetExplanation: UnicodeString; override;
|
||||
function GetSpeed: Single;
|
||||
function Execute: TKMActionResult; override;
|
||||
procedure Save(SaveStream: TKMemoryStream); override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
uses
|
||||
KM_Resource, KM_ResUnits, KM_UnitWarrior;
|
||||
|
||||
|
||||
{ TUnitActionStormAttack }
|
||||
constructor TKMUnitActionStormAttack.Create(aUnit: TKMUnit; aActionType: TKMUnitActionType; aRow: Integer);
|
||||
const
|
||||
//Tiles traveled measured in KaM TPR: Min 8, maximum 13
|
||||
//We reduced the variation in order to make storm attack more useful
|
||||
MIN_STAMINA = 12;
|
||||
MAX_STAMINA = 13;
|
||||
begin
|
||||
inherited Create(aUnit, aActionType, True);
|
||||
fTileSteps := -1; //-1 so the first initializing step makes it 0
|
||||
fDelay := aRow * 5; //No delay for the first row
|
||||
fStamina := MIN_STAMINA + KaMRandom(MAX_STAMINA - MIN_STAMINA + 1{$IFDEF DBG_RNG_SPY}, 'TKMUnitActionStormAttack.Create'{$ENDIF});
|
||||
fNextPos := KMPOINT_ZERO;
|
||||
fVertexOccupied := KMPOINT_ZERO;
|
||||
end;
|
||||
|
||||
|
||||
destructor TKMUnitActionStormAttack.Destroy;
|
||||
begin
|
||||
if not KMSamePoint(fVertexOccupied, KMPOINT_ZERO) then
|
||||
DecVertex;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
|
||||
constructor TKMUnitActionStormAttack.Load(LoadStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
LoadStream.CheckMarker('UnitActionStormAttack');
|
||||
LoadStream.Read(fDelay);
|
||||
LoadStream.Read(fTileSteps);
|
||||
LoadStream.Read(fStamina);
|
||||
LoadStream.Read(fNextPos);
|
||||
LoadStream.Read(fVertexOccupied);
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionStormAttack.ActName: TKMUnitActionName;
|
||||
begin
|
||||
Result := uanStormAttack;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionStormAttack.GetExplanation: UnicodeString;
|
||||
begin
|
||||
Result := 'Storming';
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitActionStormAttack.IncVertex(const aFrom, aTo: TKMPoint);
|
||||
begin
|
||||
//Tell gTerrain that this vertex is being used so no other unit walks over the top of us
|
||||
Assert(KMSamePoint(fVertexOccupied, KMPOINT_ZERO), 'Storm vertex in use');
|
||||
//Assert(not gTerrain.HasVertexUnit(KMGetDiagVertex(aFrom,aTo)), 'Storm vertex blocked');
|
||||
|
||||
fUnit.VertexAdd(aFrom,aTo); //Running counts as walking
|
||||
fVertexOccupied := KMGetDiagVertex(aFrom,aTo);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitActionStormAttack.DecVertex;
|
||||
begin
|
||||
//Tell gTerrain that this vertex is not being used anymore
|
||||
Assert(not KMSamePoint(fVertexOccupied, KMPOINT_ZERO), 'DecVertex 0:0 Storm');
|
||||
|
||||
fUnit.VertexRem(fVertexOccupied);
|
||||
fVertexOccupied := KMPOINT_ZERO;
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionStormAttack.GetSpeed: Single;
|
||||
begin
|
||||
if (fTileSteps <= 0) or (fTileSteps >= fStamina-1) then
|
||||
Result := gRes.Units[fUnit.UnitType].GetEffectiveWalkSpeed(DIAG_DIRECTION[fUnit.Direction])
|
||||
else
|
||||
Result := gRes.Units[fUnit.UnitType].GetEffectiveStormSpeed(DIAG_DIRECTION[fUnit.Direction]);
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionStormAttack.Execute: TKMActionResult;
|
||||
var
|
||||
dx, dy: ShortInt;
|
||||
walkX, walkY, distance: Single;
|
||||
isWalking, isDiag: Boolean;
|
||||
begin
|
||||
if KMSamePoint(fNextPos, KMPOINT_ZERO) then
|
||||
fNextPos := fUnit.Position; //Set fNextPos to current pos so it initializes on the first run
|
||||
|
||||
//Walk for the first step before running
|
||||
if fDelay > 0 then
|
||||
begin
|
||||
Dec(fDelay);
|
||||
fUnit.AnimStep := UNIT_STILL_FRAMES[fUnit.Direction];
|
||||
Exit(arActContinues);
|
||||
end;
|
||||
|
||||
//Last step is walking, others are running (unit gets tired and slows at the end)
|
||||
//In KaM the first step was also walking, but this makes it less useful/surprising
|
||||
|
||||
isWalking := (fTileSteps >= fStamina - 1);
|
||||
if isWalking then
|
||||
begin
|
||||
// Use umtWalk move type here, since we just want to evaluate if we are close enough
|
||||
distance := gRes.Units[fUnit.UnitType].GetEffectiveWalkSpeed(False);
|
||||
fType := uaWalk;
|
||||
end else begin
|
||||
distance := gRes.Units[fUnit.UnitType].GetEffectiveStormSpeed(False);
|
||||
fType := uaSpec;
|
||||
end;
|
||||
|
||||
if KMSamePointF(fUnit.PositionF, KMPointF(fNextPos), distance/2) then
|
||||
begin
|
||||
Inc(fTileSteps); //We have stepped on a new tile
|
||||
//Set precise position to avoid rounding errors
|
||||
fUnit.PositionF := KMPointF(fNextPos);
|
||||
|
||||
//No longer using previous vertex
|
||||
if KMStepIsDiag(fUnit.PositionPrev, fUnit.PositionNext) and (fTileSteps > 0) then
|
||||
DecVertex;
|
||||
|
||||
//Check for units nearby to fight
|
||||
Locked := False; //Unlock during this check only so CheckForEnemy can abandon our action
|
||||
if (fUnit is TKMUnitWarrior) then
|
||||
if TKMUnitWarrior(fUnit).CheckForEnemy then
|
||||
begin
|
||||
//If we've picked a fight it means this action no longer exists,
|
||||
//so we must exit out (don't set ActDone as that will now apply to fight action)
|
||||
Exit(arActContinues);
|
||||
end;
|
||||
Locked := True; //Finished CheckForEnemy, so lock again
|
||||
|
||||
//Begin the next step
|
||||
fNextPos := KMGetPointInDir(fUnit.Position, fUnit.Direction);
|
||||
|
||||
//Action ends if: 1: Used up stamina. 2: There is an enemy to fight. 3: NextPos is an obsticle
|
||||
if (fTileSteps >= fStamina) or not fUnit.CanStepTo(fNextPos.X, fNextPos.Y, fUnit.DesiredPassability) then
|
||||
Exit(arActDone); //Finished run; Must exit right away as we might have changed this action to fight
|
||||
|
||||
//Do some house keeping because we have now stepped on a new tile
|
||||
fUnit.PositionNext := fNextPos;
|
||||
fUnit.Walk(fUnit.PositionPrev, fUnit.PositionNext); //Pre-occupy next tile
|
||||
if KMStepIsDiag(fUnit.PositionPrev,fUnit.PositionNext) then
|
||||
IncVertex(fUnit.PositionPrev,fUnit.PositionNext);
|
||||
end;
|
||||
|
||||
walkX := fNextPos.X - fUnit.PositionF.X;
|
||||
walkY := fNextPos.Y - fUnit.PositionF.Y;
|
||||
dx := Sign(walkX); //-1,0,1
|
||||
dy := Sign(walkY); //-1,0,1
|
||||
|
||||
isDiag := (dx <> 0) and (dy <> 0);
|
||||
if isWalking then
|
||||
distance := gRes.Units[fUnit.UnitType].GetEffectiveWalkSpeed(isDiag)
|
||||
else
|
||||
distance := gRes.Units[fUnit.UnitType].GetEffectiveStormSpeed(isDiag);
|
||||
|
||||
fUnit.PositionF := KMPointF(fUnit.PositionF.X + dx*Math.min(distance, Abs(walkX)),
|
||||
fUnit.PositionF.Y + dy*Math.min(distance, Abs(walkY)));
|
||||
|
||||
Inc(fUnit.AnimStep);
|
||||
StepDone := False; //We are not actually done because now we have just taken another step
|
||||
Result := arActContinues;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMUnitActionStormAttack.Save(SaveStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
|
||||
SaveStream.PlaceMarker('UnitActionStormAttack');
|
||||
SaveStream.Write(fDelay);
|
||||
SaveStream.Write(fTileSteps);
|
||||
SaveStream.Write(fStamina);
|
||||
SaveStream.Write(fNextPos);
|
||||
SaveStream.Write(fVertexOccupied);
|
||||
end;
|
||||
|
||||
|
||||
function TKMUnitActionStormAttack.CanBeInterrupted(aForced: Boolean = True): Boolean;
|
||||
begin
|
||||
Result := not Locked; //Never interupt storm attack
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,216 @@
|
||||
unit KM_UnitTaskAttackHouse;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
Classes, SysUtils, Math,
|
||||
KM_CommonClasses, KM_Defaults, KM_Houses, KM_Units, KM_UnitWarrior, KM_Points;
|
||||
|
||||
|
||||
type
|
||||
// Attack a house
|
||||
TKMTaskAttackHouse = class(TKMUnitTask)
|
||||
private
|
||||
fHouse: TKMHouse;
|
||||
public
|
||||
constructor Create(aWarrior: TKMUnitWarrior; aHouse: TKMHouse);
|
||||
constructor Load(LoadStream: TKMemoryStream); override;
|
||||
procedure SyncLoad; override;
|
||||
destructor Destroy; override;
|
||||
|
||||
property House: TKMHouse read fHouse;
|
||||
|
||||
function WalkShouldAbandon: Boolean; override;
|
||||
function Execute: TKMTaskResult; override;
|
||||
procedure Save(SaveStream: TKMemoryStream); override;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
KM_Entity,
|
||||
KM_HandsCollection,
|
||||
KM_Resource, KM_ResSound, KM_ResUnits,
|
||||
KM_Sound, KM_Projectiles, KM_GameParams;
|
||||
|
||||
|
||||
const
|
||||
MeleeSoundsHouse: array [0..12] of TKMSoundEffectOriginal = (
|
||||
sfxMelee37, sfxMelee38, sfxMelee39, sfxMelee40, sfxMelee41,
|
||||
sfxMelee42, sfxMelee43, sfxMelee47, sfxMelee51, sfxMelee52,
|
||||
sfxMelee53, sfxMelee54, sfxMelee57
|
||||
);
|
||||
|
||||
|
||||
{ TKMTaskAttackHouse }
|
||||
constructor TKMTaskAttackHouse.Create(aWarrior: TKMUnitWarrior; aHouse: TKMHouse);
|
||||
begin
|
||||
inherited Create(aWarrior);
|
||||
fType := uttAttackHouse;
|
||||
fHouse := aHouse.GetPointer;
|
||||
end;
|
||||
|
||||
|
||||
constructor TKMTaskAttackHouse.Load(LoadStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
LoadStream.CheckMarker('TaskAttackHouse');
|
||||
LoadStream.Read(fHouse, 4);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMTaskAttackHouse.SyncLoad;
|
||||
begin
|
||||
inherited;
|
||||
fHouse := gHands.GetHouseByUID(Integer(fHouse));
|
||||
end;
|
||||
|
||||
|
||||
destructor TKMTaskAttackHouse.Destroy;
|
||||
begin
|
||||
gHands.CleanUpHousePointer(fHouse);
|
||||
inherited;
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskAttackHouse.WalkShouldAbandon: Boolean;
|
||||
begin
|
||||
Result := fHouse.IsDestroyed;
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskAttackHouse.Execute: TKMTaskResult;
|
||||
var
|
||||
AnimLength: Integer;
|
||||
Delay, Cycle: Integer;
|
||||
closest: TKMPoint;
|
||||
begin
|
||||
Result := trTaskContinues;
|
||||
|
||||
//If the house is destroyed drop the task
|
||||
if WalkShouldAbandon then
|
||||
begin
|
||||
Result := trTaskDone;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
with TKMUnitWarrior(fUnit) do
|
||||
case fPhase of
|
||||
0: if IsRanged then
|
||||
if fHouse.GetDistance(Position) < GetFightMinRange then
|
||||
//Archer is too close, try to step back to the minimum range
|
||||
SetActionWalkFromHouse(fHouse, GetFightMinRange)
|
||||
else
|
||||
if fHouse.GetDistance(Position) > GetFightMaxRange then
|
||||
SetActionWalkToHouse(fHouse, GetFightMaxRange)
|
||||
else
|
||||
SetActionStay(0, uaWalk)
|
||||
else
|
||||
SetActionWalkToHouse(fHouse, 1);
|
||||
1: begin
|
||||
if IsRanged then
|
||||
begin
|
||||
//Check if the walk failed
|
||||
if (fHouse.GetDistance(Position) < GetFightMinRange) or (fHouse.GetDistance(Position) > GetFightMaxRange) then
|
||||
begin
|
||||
SetActionStay(0, uaWalk);
|
||||
Result := trTaskDone;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
//Calculate base aiming delay
|
||||
Delay := AimingDelay;
|
||||
|
||||
//Prevent rate of fire exploit by making archers pause for longer if they shot recently
|
||||
Cycle := Max(gRes.Units[UnitType].UnitAnim[uaWork, Direction].Count, 1) - FiringDelay;
|
||||
if NeedsToReload(Cycle) then
|
||||
Delay := Delay + Cycle - (gGameParams.Tick - LastShootTime);
|
||||
|
||||
SetActionLockedStay(Delay,uaWork); //Pretend to aim
|
||||
|
||||
if not KMSamePoint(Position, fHouse.GetClosestCell(Position)) then //Unbuilt houses can be attacked from within
|
||||
Direction := KMGetDirection(PositionNext, fHouse.Entrance); //Look at house
|
||||
|
||||
if gMySpectator.FogOfWar.CheckTileRevelation(Round(PositionF.X), Round(PositionF.Y)) >= 255 then
|
||||
case UnitType of
|
||||
utCrossbowman: gSoundPlayer.Play(sfxCrossbowDraw, PositionF); //Aiming
|
||||
utBowman: gSoundPlayer.Play(sfxBowDraw, PositionF); //Aiming
|
||||
utRogue: ;
|
||||
else raise Exception.Create('Unknown shooter');
|
||||
end;
|
||||
end
|
||||
else
|
||||
begin
|
||||
//Check if the walk failed
|
||||
if fHouse.GetDistance(Position) > GetFightMaxRange then
|
||||
begin
|
||||
SetActionStay(0, uaWalk);
|
||||
Result := trTaskDone;
|
||||
Exit;
|
||||
end;
|
||||
SetActionLockedStay(0,uaWork,False); //Melee units pause after the hit
|
||||
closest := fHouse.GetClosestCell(PositionNext);
|
||||
if not KMSamePoint(PositionNext, closest) then //Unbuilt houses can be attacked from within
|
||||
Direction := KMGetDirection(PositionNext, closest); //Look at house
|
||||
|
||||
end;
|
||||
end;
|
||||
2: begin
|
||||
//Special case - slingshot, he has AimSoundDelay
|
||||
if UnitType = utRogue then
|
||||
SetActionLockedStay(AimSoundDelay, uaWork, False) //Start shooting before sound
|
||||
else
|
||||
begin
|
||||
Inc(fPhase); //Skip slingshot special phase
|
||||
if IsRanged then
|
||||
SetActionLockedStay(FiringDelay, uaWork, False) //Start shooting
|
||||
else
|
||||
SetActionLockedStay(6, uaWork, False); //Start the hit
|
||||
end;
|
||||
end;
|
||||
3: begin
|
||||
gSoundPlayer.Play(sfxSlingerShoot, PositionF);
|
||||
SetActionLockedStay(FiringDelay - AimSoundDelay, uaWork, False, 0, AimSoundDelay) //Start shooting before sound
|
||||
end;
|
||||
4: begin
|
||||
if IsRanged then
|
||||
begin
|
||||
//Launch the missile and forget about it
|
||||
//Shooting range is not important now, houses don't walk (except Howl's Moving Castle perhaps)
|
||||
//todo -cComplicated: Slingers (rogues) should launch rock part on SLINGSHOT_FIRING_DELAY like they do in ActionFight (animation looks wrong now)
|
||||
gProjectiles.AimTargetHouse(PositionF, fHouse, ProjectileType, fUnit, RangeMax, RangeMin);
|
||||
|
||||
SetLastShootTime; //Record last time the warrior shot
|
||||
AnimLength := gRes.Units[UnitType].UnitAnim[uaWork, Direction].Count;
|
||||
SetActionLockedStay(AnimLength - FiringDelay, uaWork, False, 0, FiringDelay); //Reload for next attack
|
||||
fPhase := 0; //Go for another shot (will be 1 after inc below)
|
||||
end
|
||||
else
|
||||
begin
|
||||
SetActionLockedStay(6, uaWork, False, 0, 6); // Pause for next attack
|
||||
|
||||
//All melee units do 2 damage per strike
|
||||
fHouse.AddDamage(2, fUnit);
|
||||
|
||||
//Play a sound. We should not use KaMRandom here because sound playback depends on FOW and is individual for each player
|
||||
if gMySpectator.FogOfWar.CheckTileRevelation(Position.X, Position.Y) >= 255 then
|
||||
gSoundPlayer.Play(MeleeSoundsHouse[Random(Length(MeleeSoundsHouse))], PositionF);
|
||||
|
||||
fPhase := 1; //Go for another hit (will be 2 after inc below)
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
Inc(fPhase);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMTaskAttackHouse.Save(SaveStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
|
||||
SaveStream.PlaceMarker('TaskAttackHouse');
|
||||
SaveStream.Write(fHouse.UID);
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
+1029
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,607 @@
|
||||
unit KM_UnitTaskDelivery;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
Classes, SysUtils,
|
||||
KM_CommonClasses, KM_Defaults, KM_Points,
|
||||
KM_Houses, KM_Units,
|
||||
KM_ResTypes;
|
||||
|
||||
|
||||
type
|
||||
TKMDeliverKind = (dkToHouse, dkToConstruction, dkToUnit);
|
||||
TKMDeliverStage = (
|
||||
dsUnknown,
|
||||
dsToFromHouse, // Serf is walking to the offer house
|
||||
dsAtFromHouse, // Serf is getting in / out from offer house
|
||||
dsToDestination, // Serf is walking to destination (unit/house)
|
||||
dsAtDestination); // Serf is operating with destination
|
||||
|
||||
TKMTaskDeliver = class(TKMUnitTask)
|
||||
private
|
||||
fFrom: TKMHouse;
|
||||
fToHouse: TKMHouse;
|
||||
fToUnit: TKMUnit;
|
||||
fWareType: TKMWareType;
|
||||
fDeliverID: Integer;
|
||||
fDeliverKind: TKMDeliverKind;
|
||||
// Force delivery, even if fToHouse blocked ware from delivery.
|
||||
// Used in exceptional situation, when ware was carried by serf and delivery demand was destroyed and no one new was found
|
||||
fForceDelivery: Boolean;
|
||||
procedure CheckForBetterDestination;
|
||||
function FindBestDestination: Boolean;
|
||||
function GetDeliverStage: TKMDeliverStage;
|
||||
property FromHouse: TKMHouse read fFrom write fFrom;
|
||||
property ToHouse: TKMHouse read fToHouse write fToHouse;
|
||||
property ToUnit: TKMUnit read fToUnit write fToUnit;
|
||||
function CanAbandonWalk: Boolean;
|
||||
public
|
||||
constructor Create(aSerf: TKMUnitSerf; aFrom: TKMHouse; aToHouse: TKMHouse; aWare: TKMWareType; aID: Integer); overload;
|
||||
constructor Create(aSerf: TKMUnitSerf; aFrom: TKMHouse; aToUnit: TKMUnit; aWare: TKMWareType; aID: Integer); overload;
|
||||
constructor Load(LoadStream: TKMemoryStream); override;
|
||||
procedure SyncLoad; override;
|
||||
destructor Destroy; override;
|
||||
function WalkShouldAbandon: Boolean; override;
|
||||
property DeliverKind: TKMDeliverKind read fDeliverKind;
|
||||
property DeliverStage: TKMDeliverStage read GetDeliverStage;
|
||||
procedure DelegateToOtherSerf(aToSerf: TKMUnitSerf);
|
||||
function Execute: TKMTaskResult; override;
|
||||
function CouldBeCancelled: Boolean; override;
|
||||
function CanRestartAction(aLastActionResult: TKMActionResult): Boolean; override;
|
||||
procedure Save(SaveStream: TKMemoryStream); override;
|
||||
|
||||
function ObjToString(const aSeparator: String = ', '): String; override;
|
||||
|
||||
procedure Paint; override; // Used only for debug so far
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
Math, TypInfo,
|
||||
KM_Entity,
|
||||
KM_HandsCollection, KM_Hand, KM_HandTypes, KM_HandEntity,
|
||||
KM_UnitWarrior, KM_HouseInn,
|
||||
KM_UnitTaskBuild, KM_Log, KM_RenderAux;
|
||||
|
||||
|
||||
{ TKMTaskDeliver }
|
||||
constructor TKMTaskDeliver.Create(aSerf: TKMUnitSerf; aFrom: TKMHouse; aToHouse: TKMHouse; aWare: TKMWareType; aID: Integer);
|
||||
begin
|
||||
inherited Create(aSerf);
|
||||
fType := uttDeliver;
|
||||
|
||||
Assert((aFrom <> nil) and (aToHouse <> nil) and (aWare <> wtNone), 'Serf ' + IntToStr(fUnit.UID) + ': invalid delivery task');
|
||||
|
||||
if gLog.CanLogDelivery then
|
||||
gLog.LogDelivery('Serf ' + IntToStr(fUnit.UID) + ' created delivery task ' + IntToStr(fDeliverID));
|
||||
|
||||
FromHouse := aFrom.GetPointer; // Also will set fPointBelowFromHouse
|
||||
ToHouse := aToHouse.GetPointer; // Also will set fPointBelowToHouse
|
||||
// Check it once to begin with as the house could become complete before the task exits (in rare circumstances when the task
|
||||
// does not exit until long after the ware has been delivered due to walk interactions)
|
||||
if aToHouse.IsComplete then
|
||||
fDeliverKind := dkToHouse
|
||||
else
|
||||
fDeliverKind := dkToConstruction;
|
||||
|
||||
fWareType := aWare;
|
||||
fDeliverID := aID;
|
||||
end;
|
||||
|
||||
|
||||
constructor TKMTaskDeliver.Create(aSerf: TKMUnitSerf; aFrom: TKMHouse; aToUnit: TKMUnit; aWare: TKMWareType; aID: Integer);
|
||||
begin
|
||||
inherited Create(aSerf);
|
||||
fType := uttDeliver;
|
||||
|
||||
Assert((aFrom <> nil) and (aToUnit <> nil) and ((aToUnit is TKMUnitWarrior) or (aToUnit is TKMUnitWorker)) and (aWare <> wtNone), 'Serf '+inttostr(fUnit.UID)+': invalid delivery task');
|
||||
|
||||
if gLog.CanLogDelivery then
|
||||
gLog.LogDelivery('Serf ' + IntToStr(fUnit.UID) + ' created delivery task ' + IntToStr(fDeliverID));
|
||||
|
||||
FromHouse := aFrom.GetPointer;
|
||||
ToUnit := aToUnit.GetPointer;
|
||||
fDeliverKind := dkToUnit;
|
||||
fWareType := aWare;
|
||||
fDeliverID := aID;
|
||||
end;
|
||||
|
||||
|
||||
constructor TKMTaskDeliver.Load(LoadStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
LoadStream.CheckMarker('TaskDeliver');
|
||||
LoadStream.Read(fFrom, 4);
|
||||
LoadStream.Read(fToHouse, 4);
|
||||
LoadStream.Read(fToUnit, 4);
|
||||
LoadStream.Read(fForceDelivery);
|
||||
LoadStream.Read(fWareType, SizeOf(fWareType));
|
||||
LoadStream.Read(fDeliverID);
|
||||
LoadStream.Read(fDeliverKind, SizeOf(fDeliverKind));
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMTaskDeliver.Save(SaveStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
SaveStream.PlaceMarker('TaskDeliver');
|
||||
SaveStream.Write(fFrom.UID); // Store ID, then substitute it with reference on SyncLoad
|
||||
SaveStream.Write(fToHouse.UID); // Store ID, then substitute it with reference on SyncLoad
|
||||
SaveStream.Write(fToUnit.UID); // Store ID, then substitute it with reference on SyncLoad
|
||||
SaveStream.Write(fForceDelivery);
|
||||
SaveStream.Write(fWareType, SizeOf(fWareType));
|
||||
SaveStream.Write(fDeliverID);
|
||||
SaveStream.Write(fDeliverKind, SizeOf(fDeliverKind));
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMTaskDeliver.SyncLoad;
|
||||
begin
|
||||
inherited;
|
||||
fFrom := gHands.GetHouseByUID(Integer(fFrom));
|
||||
fToHouse := gHands.GetHouseByUID(Integer(fToHouse));
|
||||
fToUnit := gHands.GetUnitByUID(Integer(fToUnit));
|
||||
end;
|
||||
|
||||
|
||||
destructor TKMTaskDeliver.Destroy;
|
||||
begin
|
||||
if gLog.CanLogDelivery then
|
||||
gLog.LogDelivery('Serf ' + IntToStr(fUnit.UID) + ' abandoned delivery task ' + IntToStr(fDeliverID) + ' at phase ' + IntToStr(fPhase));
|
||||
|
||||
if fUnit <> nil then
|
||||
begin
|
||||
if fDeliverID <> DELIVERY_NO_ID then
|
||||
gHands[fUnit.Owner].Deliveries.Queue.AbandonDelivery(fDeliverID);
|
||||
|
||||
if TKMUnitSerf(fUnit).Carry <> wtNone then
|
||||
begin
|
||||
gHands[fUnit.Owner].Stats.WareConsumed(TKMUnitSerf(fUnit).Carry);
|
||||
TKMUnitSerf(fUnit).CarryTake; // empty hands
|
||||
end;
|
||||
end;
|
||||
|
||||
gHands.CleanUpHousePointer(fFrom);
|
||||
gHands.CleanUpHousePointer(fToHouse);
|
||||
gHands.CleanUpUnitPointer(fToUnit);
|
||||
inherited;
|
||||
end;
|
||||
|
||||
|
||||
// Note: Phase is -1 because it will have been increased at the end of last Execute
|
||||
function TKMTaskDeliver.WalkShouldAbandon: Boolean;
|
||||
begin
|
||||
Result := False;
|
||||
|
||||
if not CanAbandonWalk then
|
||||
Exit;
|
||||
|
||||
if not gHands[fUnit.Owner].Deliveries.Queue.IsDeliveryAlowed(fDeliverID) then
|
||||
Exit(True);
|
||||
|
||||
// After step 2 we don't care if From is destroyed or doesn't have the ware
|
||||
if fPhase <= 2 then
|
||||
Result := Result
|
||||
or fFrom.IsDestroyed
|
||||
or fFrom.ShouldAbandonDeliveryFrom(fWareType)
|
||||
or fFrom.ShouldAbandonDeliveryFromTo(fToHouse, fWareType, fPhase = 2); // Make immediate check only on Phase 2 (inside house)
|
||||
|
||||
Result := Result or CanRestartAction(fLastActionResult);
|
||||
|
||||
// do not abandon the delivery if target is destroyed/dead, we will find new target later
|
||||
case fDeliverKind of
|
||||
dkToHouse: Result := Result or fToHouse.IsDestroyed
|
||||
or (not fForceDelivery and fToHouse.ShouldAbandonDeliveryTo(fWareType));
|
||||
dkToConstruction: Result := Result or fToHouse.IsDestroyed;
|
||||
dkToUnit: Result := Result or (fToUnit = nil) or fToUnit.IsDeadOrDying;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
// We can restart some actions
|
||||
function TKMTaskDeliver.CanRestartAction(aLastActionResult: TKMActionResult): Boolean;
|
||||
begin
|
||||
Result := (aLastActionResult = arActCanNotStart)
|
||||
and (fDeliverKind = dkToHouse)
|
||||
and (fPhase - 1 = 6); // Serf tried to get inside destination house
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMTaskDeliver.CheckForBetterDestination;
|
||||
var
|
||||
NewToHouse: TKMHouse;
|
||||
NewToUnit: TKMUnit;
|
||||
begin
|
||||
gHands[fUnit.Owner].Deliveries.Queue.CheckForBetterDemand(fDeliverID, NewToHouse, NewToUnit, TKMUnitSerf(fUnit));
|
||||
|
||||
gHands.CleanUpHousePointer(fToHouse);
|
||||
gHands.CleanUpUnitPointer(fToUnit);
|
||||
if NewToHouse <> nil then
|
||||
begin
|
||||
ToHouse := NewToHouse.GetPointer; // Use Setter here to set up fPointBelowToHouse
|
||||
if fToHouse.IsComplete then
|
||||
fDeliverKind := dkToHouse
|
||||
else
|
||||
fDeliverKind := dkToConstruction;
|
||||
end
|
||||
else
|
||||
begin
|
||||
ToUnit := NewToUnit.GetPointer; // Use Setter here to clean up fPointBelowToHouse
|
||||
fDeliverKind := dkToUnit;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
// Try to find best destination
|
||||
function TKMTaskDeliver.FindBestDestination: Boolean;
|
||||
var
|
||||
NewToHouse: TKMHouse;
|
||||
NewToUnit: TKMUnit;
|
||||
begin
|
||||
if fPhase <= 2 then
|
||||
begin
|
||||
Result := False;
|
||||
Exit;
|
||||
end else
|
||||
if InRange(fPhase, 3, 3) then
|
||||
begin
|
||||
Result := True;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
fForceDelivery := False; // Reset ForceDelivery from previous runs
|
||||
gHands[fUnit.Owner].Deliveries.Queue.DeliveryFindBestDemand(TKMUnitSerf(fUnit), fDeliverID, fWareType, NewToHouse, NewToUnit, fForceDelivery);
|
||||
|
||||
gHands.CleanUpHousePointer(fToHouse);
|
||||
gHands.CleanUpUnitPointer(fToUnit);
|
||||
|
||||
// New House
|
||||
if (NewToHouse <> nil) and (NewToUnit = nil) then
|
||||
begin
|
||||
ToHouse := NewToHouse.GetPointer; // Use Setter here to set up fPointBelowToHouse
|
||||
if fToHouse.IsComplete then
|
||||
fDeliverKind := dkToHouse
|
||||
else
|
||||
fDeliverKind := dkToConstruction;
|
||||
Result := True;
|
||||
if fPhase > 4 then
|
||||
fPhase := 4;
|
||||
end
|
||||
else
|
||||
// New Unit
|
||||
if (NewToHouse = nil) and (NewToUnit <> nil) then
|
||||
begin
|
||||
ToUnit := NewToUnit.GetPointer; // Use Setter here to clean up fPointBelowToHouse
|
||||
fDeliverKind := dkToUnit;
|
||||
Result := True;
|
||||
if fPhase > 4 then
|
||||
fPhase := 4;
|
||||
end
|
||||
else
|
||||
// No alternative
|
||||
if (NewToHouse = nil) and (NewToUnit = nil) then
|
||||
Result := False
|
||||
else
|
||||
// Error
|
||||
raise Exception.Create('Both destinations could not be');
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskDeliver.CouldBeCancelled: Boolean;
|
||||
begin
|
||||
// Allow cancel task only at walking phases
|
||||
Result := ((fPhase - 1) // phase was increased at the end of execution
|
||||
<= 0) // <= because fPhase is 0 when task is just created
|
||||
or ((fPhase - 1) = 5);
|
||||
end;
|
||||
|
||||
|
||||
// Get Delivery stage
|
||||
function TKMTaskDeliver.GetDeliverStage: TKMDeliverStage;
|
||||
var
|
||||
Phase: Integer;
|
||||
begin
|
||||
Result := dsUnknown;
|
||||
Phase := fPhase - 1; // fPhase is increased at the phase end
|
||||
case Phase of
|
||||
-10..0,4: Result := dsToFromHouse;
|
||||
1..3: Result := dsAtFromHouse;
|
||||
else
|
||||
case fDeliverKind of
|
||||
dkToHouse: begin
|
||||
case Phase of
|
||||
5: Result := dsToDestination;
|
||||
else Result := dsAtDestination;
|
||||
end;
|
||||
end;
|
||||
dkToConstruction,
|
||||
dkToUnit: begin
|
||||
case Phase of
|
||||
5,6: Result := dsToDestination;
|
||||
else Result := dsAtDestination;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskDeliver.CanAbandonWalk: Boolean;
|
||||
begin
|
||||
case fDeliverKind of
|
||||
dkToHouse: Result := fPhase <= 8;
|
||||
dkToConstruction: Result := fPhase <= 7;
|
||||
dkToUnit: Result := fPhase <= 6;
|
||||
else
|
||||
raise Exception.Create('Unexpected type');
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
// Delegate delivery task to other serf
|
||||
procedure TKMTaskDeliver.DelegateToOtherSerf(aToSerf: TKMUnitSerf);
|
||||
begin
|
||||
// Allow to delegate task only while serf is walking to From House
|
||||
Assert(DeliverStage = dsToFromHouse, 'DeliverStage <> dsToFromHouse');
|
||||
|
||||
gHands.CleanUpUnitPointer(fUnit);
|
||||
fUnit := aToSerf.GetPointer;
|
||||
|
||||
InitDefaultAction; // InitDefaultAction, otherwise serf will not have any action
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskDeliver.Execute: TKMTaskResult;
|
||||
var
|
||||
Worker: TKMUnit;
|
||||
begin
|
||||
Result := trTaskContinues;
|
||||
|
||||
if WalkShouldAbandon and fUnit.Visible and not FindBestDestination then
|
||||
Exit(trTaskDone);
|
||||
|
||||
with TKMUnitSerf(fUnit) do
|
||||
case fPhase of
|
||||
0: begin
|
||||
SetActionWalkToSpot(fFrom.PointBelowEntrance);
|
||||
end;
|
||||
1: begin
|
||||
SetActionGoIn(uaWalk, gdGoInside, fFrom);
|
||||
end;
|
||||
2: begin
|
||||
// Serf is inside house now.
|
||||
// Barracks can consume the resource (by equipping) before we arrive
|
||||
// All houses can have resources taken away by script at any moment
|
||||
if fFrom.ShouldAbandonDeliveryFrom(fWareType)
|
||||
or fFrom.ShouldAbandonDeliveryFromTo(fToHouse, fWareType, True) then // For store evacuation
|
||||
begin
|
||||
SetActionLockedStay(5, uaWalk); // Wait a moment inside
|
||||
fPhase := 120; // Will get out of Barracks onthat Phase
|
||||
Exit;
|
||||
end;
|
||||
SetActionLockedStay(5,uaWalk); // Wait a moment inside
|
||||
CheckForBetterDestination; // Must run before TakenOffer and before taking ware so Offer is still valid
|
||||
fFrom.WareTakeFromOut(fWareType);
|
||||
CarryGive(fWareType);
|
||||
gHands[Owner].Deliveries.Queue.TakenOffer(fDeliverID);
|
||||
end;
|
||||
3: begin
|
||||
if fFrom.IsDestroyed then //We have the resource, so we don't care if house is destroyed
|
||||
SetActionLockedStay(0, uaWalk)
|
||||
else
|
||||
SetActionGoIn(uaWalk, gdGoOutside, fFrom);
|
||||
Inc(fPhase); // jump to phase 5 immediately
|
||||
end;
|
||||
4: begin
|
||||
SetActionStay(5, uaWalk); // used only from FindBestDestination
|
||||
Thought := thQuest;
|
||||
end;
|
||||
end;
|
||||
|
||||
if fPhase = 5 then
|
||||
TKMUnitSerf(fUnit).Thought := thNone; // Clear possible '?' thought after 4th phase
|
||||
|
||||
// Get out barracks (special case after wait phase inside house, if resource is not available anymore)
|
||||
with TKMUnitSerf(fUnit) do
|
||||
if fPhase = 120 then
|
||||
begin
|
||||
SetActionGoIn(uaWalk, gdGoOutside, fFrom); //Step back out
|
||||
fPhase := 99; // Exit next run
|
||||
Exit;
|
||||
end;
|
||||
|
||||
// Deliver into complete house
|
||||
if (fDeliverKind = dkToHouse) then
|
||||
with TKMUnitSerf(fUnit) do
|
||||
case fPhase of
|
||||
0..4:;
|
||||
5: SetActionWalkToSpot(fToHouse.PointBelowEntrance);
|
||||
6: SetActionGoIn(uaWalk, gdGoInside, fToHouse);
|
||||
7: SetActionLockedStay(5, uaWalk); // wait a bit inside
|
||||
8: begin
|
||||
fToHouse.WareAddToIn(Carry);
|
||||
CarryTake;
|
||||
|
||||
gHands[Owner].Deliveries.Queue.GaveDemand(fDeliverID);
|
||||
gHands[Owner].Deliveries.Queue.AbandonDelivery(fDeliverID);
|
||||
fDeliverID := DELIVERY_NO_ID; // So that it can't be abandoned if unit dies while trying to GoOut
|
||||
|
||||
// If serf bring smth into the Inn and he is hungry - let him eat immediately
|
||||
if fUnit.IsHungry
|
||||
and (fToHouse.HouseType = htInn)
|
||||
and TKMHouseInn(fToHouse).HasFood
|
||||
and TKMHouseInn(fToHouse).HasSpace
|
||||
and TKMUnitSerf(fUnit).GoEat(TKMHouseInn(fToHouse), True) then
|
||||
Exit // Exit immediately, since we created new task here and old task is destroyed!
|
||||
// Changing any task fields here (f.e. Phase) could affect new task!
|
||||
else
|
||||
// Now look for another delivery from inside this house
|
||||
// But only if we are not hungry!
|
||||
// Otherwise there is a possiblity when he will go between houses until death
|
||||
if not fUnit.IsHungry
|
||||
and TKMUnitSerf(fUnit).TryDeliverFrom(fToHouse) then
|
||||
Exit // Exit immediately, since we created new task here and old task is destroyed!
|
||||
// Changing any task fields here (f.e. Phase) could affect new task!
|
||||
else
|
||||
// No delivery found then just step outside
|
||||
SetActionGoIn(uaWalk, gdGoOutside, fToHouse);
|
||||
end;
|
||||
else Result := trTaskDone;
|
||||
end;
|
||||
|
||||
// Deliver into wip house
|
||||
if (fDeliverKind = dkToConstruction) then
|
||||
with TKMUnitSerf(fUnit) do
|
||||
case fPhase of
|
||||
0..4:;
|
||||
// First come close to point below house entrance
|
||||
5: SetActionWalkToSpot(fToHouse.PointBelowEntrance, uaWalk, 1.42);
|
||||
6: begin
|
||||
// Then check if there is a worker hitting house just from the entrance
|
||||
Worker := gHands[fUnit.Owner].UnitsHitTest(fToHouse.PointBelowEntrance, utBuilder);
|
||||
if (Worker <> nil) and (Worker.Task <> nil)
|
||||
and (Worker.Task is TKMTaskBuildHouse)
|
||||
and (Worker.Task.Phase >= 1) then
|
||||
// If so, then allow to bring resources diagonally
|
||||
SetActionWalkToSpot(fToHouse.Entrance, uaWalk, 1.42)
|
||||
else
|
||||
// else ask serf to bring resources from point below entrance (not diagonally)
|
||||
SetActionWalkToSpot(fToHouse.PointBelowEntrance);
|
||||
end;
|
||||
7: begin
|
||||
Direction := KMGetDirection(PositionNext, fToHouse.Entrance);
|
||||
fToHouse.WareAddToBuild(Carry);
|
||||
gHands[Owner].Stats.WareConsumed(Carry);
|
||||
CarryTake;
|
||||
gHands[Owner].Deliveries.Queue.GaveDemand(fDeliverID);
|
||||
gHands[Owner].Deliveries.Queue.AbandonDelivery(fDeliverID);
|
||||
fDeliverID := DELIVERY_NO_ID; // So that it can't be abandoned if unit dies while staying
|
||||
SetActionStay(1, uaWalk);
|
||||
end;
|
||||
else Result := trTaskDone;
|
||||
end;
|
||||
|
||||
// Deliver to builder or soldier
|
||||
if fDeliverKind = dkToUnit then
|
||||
with TKMUnitSerf(fUnit) do
|
||||
case fPhase of
|
||||
0..4:;
|
||||
5: SetActionWalkToUnit(fToUnit, 1.42, uaWalk); // When approaching from diagonal
|
||||
6: begin
|
||||
// See if the unit has moved. If so we must try again
|
||||
if KMLengthDiag(fUnit.Position, fToUnit.Position) > 1.5 then
|
||||
begin
|
||||
SetActionWalkToUnit(fToUnit, 1.42, uaWalk); //Walk to unit again
|
||||
fPhase := 6;
|
||||
Exit;
|
||||
end;
|
||||
// Worker
|
||||
if (fToUnit.UnitType = utBuilder) and (fToUnit.Task <> nil) then
|
||||
begin
|
||||
//todo -cPractical: Replace phase numbers with enums to avoid hardcoded magic numbers
|
||||
// Check if worker is still digging
|
||||
if ((fToUnit.Task is TKMTaskBuildWine) and (fToUnit.Task.Phase < 5))
|
||||
or ((fToUnit.Task is TKMTaskBuildRoad) and (fToUnit.Task.Phase < 4)) then
|
||||
begin
|
||||
SetActionLockedStay(5, uaWalk); // Wait until worker finish digging process
|
||||
fPhase := 6;
|
||||
Exit;
|
||||
end;
|
||||
fToUnit.Task.Phase := fToUnit.Task.Phase + 1;
|
||||
fToUnit.SetActionLockedStay(0, uaWork1); // Tell the worker to resume work by resetting his action (causes task to execute)
|
||||
end;
|
||||
// Warrior
|
||||
if (fToUnit is TKMUnitWarrior) then
|
||||
begin
|
||||
fToUnit.Feed(UNIT_MAX_CONDITION); // Feed the warrior
|
||||
TKMUnitWarrior(fToUnit).RequestedFood := False;
|
||||
end;
|
||||
gHands[Owner].Stats.WareConsumed(Carry);
|
||||
CarryTake;
|
||||
gHands[Owner].Deliveries.Queue.GaveDemand(fDeliverID);
|
||||
gHands[Owner].Deliveries.Queue.AbandonDelivery(fDeliverID);
|
||||
fDeliverID := DELIVERY_NO_ID; // So that it can't be abandoned if unit dies while staying
|
||||
SetActionLockedStay(5, uaWalk); // Pause breifly (like we are handing over the ware/food)
|
||||
end;
|
||||
7: begin
|
||||
// After feeding troops, serf should walk away, but ToUnit could be dead by now
|
||||
if (fToUnit is TKMUnitWarrior) then
|
||||
begin
|
||||
if TKMUnitSerf(fUnit).IsHungry then
|
||||
begin
|
||||
// Serf is hungry, try to go to the Inn
|
||||
if TKMUnitSerf(fUnit).CheckCondition then
|
||||
Exit // Exit immediately, since we created new task here and old task is destroyed!
|
||||
// Changing any task fields (f.e. Phase) here could affect new task!
|
||||
else
|
||||
// No Inn was found then just walk back to our From house
|
||||
// even if it's destroyed, its location is still valid
|
||||
SetActionWalkToHouse(fFrom, 5);
|
||||
end
|
||||
else
|
||||
if TKMUnitSerf(fUnit).TryDeliverFrom(nil) then
|
||||
Exit // Exit immediately, since we created new task here and old task is destroyed!
|
||||
// Changing any task fields (f.e. Phase) here could affect new task!
|
||||
else
|
||||
// No delivery found then just walk back to our From house
|
||||
// even if it's destroyed, its location is still valid
|
||||
// Don't walk to spot as it doesn't really matter
|
||||
SetActionWalkToHouse(fFrom, 5);
|
||||
end else
|
||||
SetActionStay(0, uaWalk); //If we're not feeding a warrior then ignore this step
|
||||
end;
|
||||
else Result := trTaskDone;
|
||||
end;
|
||||
|
||||
Inc(fPhase);
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskDeliver.ObjToString(const aSeparator: String = ', '): String;
|
||||
var
|
||||
FromStr, ToUStr, ToHStr: String;
|
||||
begin
|
||||
if Self = nil then Exit('nil');
|
||||
|
||||
FromStr := 'nil';
|
||||
ToHStr := 'nil';
|
||||
ToUStr := 'nil';
|
||||
|
||||
if fFrom <> nil then
|
||||
FromStr := fFrom.ObjToStringShort(',');
|
||||
|
||||
if fToHouse <> nil then
|
||||
ToHStr := fToHouse.ObjToStringShort(',');
|
||||
|
||||
if fToUnit <> nil then
|
||||
ToUStr := fToUnit.ObjToStringShort(',');
|
||||
|
||||
Result := inherited +
|
||||
Format('%s|FromH = [%s]%s|ToH = [%s]%sFromU = [%s]%s|WareT = %s',
|
||||
[aSeparator,
|
||||
FromStr, aSeparator,
|
||||
ToHStr, aSeparator,
|
||||
ToUStr, aSeparator,
|
||||
GetEnumName(TypeInfo(TKMWareType), Integer(fWareType)), aSeparator]);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMTaskDeliver.Paint;
|
||||
begin
|
||||
if SHOW_UNIT_ROUTES
|
||||
and (gMySpectator.LastSelected = fUnit) then
|
||||
begin
|
||||
if fFrom <> nil then
|
||||
gRenderAux.RenderWireTile(fFrom.PointBelowEntrance, icDarkBlue);
|
||||
|
||||
if fToHouse <> nil then
|
||||
gRenderAux.RenderWireTile(fToHouse.PointBelowEntrance, icLightRed);
|
||||
if fToUnit <> nil then
|
||||
gRenderAux.RenderWireTile(fToUnit.Position, icRed);
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,119 @@
|
||||
unit KM_UnitTaskDie;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
Classes, KM_CommonClasses, KM_Defaults, KM_Units, SysUtils;
|
||||
|
||||
type
|
||||
{Yep, this is a Task}
|
||||
TKMTaskDie = class(TKMUnitTask)
|
||||
private
|
||||
fShowAnimation: Boolean;
|
||||
public
|
||||
constructor Create(aUnit: TKMUnit; aShowAnimation: Boolean);
|
||||
constructor Load(LoadStream: TKMemoryStream); override;
|
||||
function Execute: TKMTaskResult; override;
|
||||
procedure Save(SaveStream: TKMemoryStream); override;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
KM_ResSound, KM_Sound,
|
||||
KM_HandsCollection, KM_HandTypes, KM_HandEntity,
|
||||
KM_Resource, KM_UnitWarrior, KM_ScriptingEvents,
|
||||
KM_ResUnits;
|
||||
|
||||
|
||||
{ TTaskDie }
|
||||
constructor TKMTaskDie.Create(aUnit: TKMUnit; aShowAnimation: Boolean);
|
||||
begin
|
||||
inherited Create(aUnit);
|
||||
fType := uttDie;
|
||||
fShowAnimation := aShowAnimation;
|
||||
//Shortcut to remove the pause before the dying animation which makes fights look odd
|
||||
if aUnit.Visible then
|
||||
begin
|
||||
fPhase := 1; //Phase 0 can be skipped when the unit is visible
|
||||
Execute;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
constructor TKMTaskDie.Load(LoadStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
LoadStream.CheckMarker('TaskDie');
|
||||
LoadStream.Read(fShowAnimation);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMTaskDie.Save(SaveStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
SaveStream.PlaceMarker('TaskDie');
|
||||
SaveStream.Write(fShowAnimation);
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskDie.Execute: TKMTaskResult;
|
||||
var
|
||||
SequenceLength: SmallInt;
|
||||
TempOwner: TKMHandID;
|
||||
TempUnitType: TKMUnitType;
|
||||
TempX, TempY: Word;
|
||||
begin
|
||||
Result := trTaskContinues;
|
||||
with fUnit do
|
||||
case fPhase of
|
||||
0: if Visible then
|
||||
SetActionLockedStay(0, uaWalk)
|
||||
else
|
||||
begin
|
||||
if (Home <> nil) and not Home.IsDestroyed then
|
||||
begin
|
||||
Home.SetState(hstIdle);
|
||||
Home.SetState(hstEmpty);
|
||||
end;
|
||||
SetActionGoIn(uaWalk, gdGoOutside, gHands.HousesHitTest(fUnit.PositionNext.X, fUnit.PositionNext.Y));
|
||||
end;
|
||||
1: begin
|
||||
if not fShowAnimation or (fUnit is TKMUnitAnimal) then //Animals don't have a dying sequence. Can be changed later.
|
||||
SetActionLockedStay(0, uaWalk, False)
|
||||
else
|
||||
begin
|
||||
SequenceLength := gRes.Units[UnitType].UnitAnim[uaDie, Direction].Count;
|
||||
SetActionLockedStay(SequenceLength, uaDie, False);
|
||||
//Do not play sounds if unit is invisible to gMySpectator
|
||||
//We should not use KaMRandom below this line because sound playback depends on FOW and is individual for each player
|
||||
if gMySpectator.FogOfWar.CheckTileRevelation(fUnit.Position.X, fUnit.Position.Y) >= 255 then
|
||||
begin
|
||||
if fUnit is TKMUnitWarrior then
|
||||
gSoundPlayer.PlayWarrior(fUnit.UnitType, spDeath, fUnit.PositionF)
|
||||
else
|
||||
gSoundPlayer.PlayCitizen(fUnit.UnitType, spDeath, fUnit.PositionF);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
else begin
|
||||
//Store them before they get lost forever
|
||||
TempOwner := fUnit.Owner;
|
||||
TempUnitType := fUnit.UnitType;
|
||||
TempX := fUnit.Position.X;
|
||||
TempY := fUnit.Position.Y;
|
||||
|
||||
fUnit.CloseUnit; //This will FreeAndNil the Task and mark unit as "closed"
|
||||
|
||||
//Can't run this in CloseUnit since it is used from other places like barracks equipping and we only want it for normal deaths
|
||||
//Notify the script that the unit is now gone from the game
|
||||
gScriptEvents.EventUnitAfterDied(TempUnitType, TempOwner, TempX, TempY);
|
||||
|
||||
Result := trTaskContinues; //Running UpdateState will exit without further changes
|
||||
Exit; //Next UpdateState won't happen cos unit is "closed"
|
||||
end;
|
||||
end;
|
||||
Inc(fPhase);
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,157 @@
|
||||
unit KM_UnitTaskDismiss;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
Classes, SysUtils,
|
||||
KM_Defaults, KM_Units, KM_Houses, KM_CommonClasses;
|
||||
|
||||
|
||||
type
|
||||
TKMTaskDismiss = class(TKMUnitTask)
|
||||
private
|
||||
fSchool: TKMHouse;
|
||||
protected
|
||||
procedure InitDefaultAction; override;
|
||||
public
|
||||
constructor Create(aUnit: TKMUnit);
|
||||
constructor Load(LoadStream: TKMemoryStream); override;
|
||||
destructor Destroy; override;
|
||||
procedure SyncLoad; override;
|
||||
procedure Save(SaveStream: TKMemoryStream); override;
|
||||
function ShouldBeCancelled: Boolean;
|
||||
function CouldBeCancelled: Boolean; override;
|
||||
|
||||
property School: TKMHouse read fSchool;
|
||||
function FindNewSchool: TKMHouse;
|
||||
|
||||
function Execute: TKMTaskResult; override;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
KM_Entity,
|
||||
KM_HandsCollection, KM_Hand, KM_HandTypes, KM_HandEntity,
|
||||
KM_ResTypes, KM_ScriptingEvents;
|
||||
|
||||
|
||||
{ TTaskDismiss }
|
||||
constructor TKMTaskDismiss.Create(aUnit: TKMUnit);
|
||||
begin
|
||||
Assert(aUnit is TKMCivilUnit, 'Only civil units are allowed to be dismissed');
|
||||
inherited;
|
||||
|
||||
gHands[fUnit.Owner].Stats.UnitDismissed(fUnit.UnitType);
|
||||
|
||||
fType := uttDismiss;
|
||||
FindNewSchool;
|
||||
end;
|
||||
|
||||
|
||||
constructor TKMTaskDismiss.Load(LoadStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
LoadStream.CheckMarker('TaskDismiss');
|
||||
LoadStream.Read(fSchool, 4);
|
||||
end;
|
||||
|
||||
|
||||
destructor TKMTaskDismiss.Destroy;
|
||||
begin
|
||||
gHands.CleanUpHousePointer(fSchool);
|
||||
fUnit.DismissInProgress := False; //Reset dismissInProgress Flag to show proper UI
|
||||
gHands[fUnit.Owner].Stats.UnitDismissCanceled(fUnit.UnitType);
|
||||
|
||||
inherited;
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskDismiss.ShouldBeCancelled: Boolean;
|
||||
begin
|
||||
Result := (fSchool = nil) or fSchool.IsDestroyed;
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskDismiss.CouldBeCancelled: Boolean;
|
||||
begin
|
||||
Result := fPhase <= 1; //Allow cancel dismiss only while walking to the school point below entrance
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMTaskDismiss.Save(SaveStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
SaveStream.PlaceMarker('TaskDismiss');
|
||||
if fSchool <> nil then
|
||||
SaveStream.Write(fSchool.UID) //Store ID, then substitute it with reference on SyncLoad
|
||||
else
|
||||
SaveStream.Write(Integer(0));
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMTaskDismiss.SyncLoad;
|
||||
begin
|
||||
inherited;
|
||||
fSchool := gHands[fUnit.Owner].Houses.GetHouseByUID(Integer(fSchool));
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskDismiss.FindNewSchool: TKMHouse;
|
||||
var
|
||||
S: TKMHouse;
|
||||
begin
|
||||
fSchool := nil;
|
||||
|
||||
S := gHands[fUnit.Owner].FindHouse(htSchool, fUnit.Position);
|
||||
|
||||
if (S <> nil) and fUnit.CanWalkTo(fUnit.Position, S.PointBelowEntrance, tpWalk, 0) then
|
||||
fSchool := S.GetPointer;
|
||||
|
||||
Result := fSchool;
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMTaskDismiss.InitDefaultAction;
|
||||
begin
|
||||
//Do nothing here, as we have to continue old action, until it could be interrupted
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskDismiss.Execute: TKMTaskResult;
|
||||
begin
|
||||
Result := trTaskContinues;
|
||||
|
||||
if (fSchool = nil) or fSchool.IsDestroyed then
|
||||
begin
|
||||
Result := trTaskDone;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
with fUnit do
|
||||
case fPhase of
|
||||
0: SetActionWalkToSpot(fSchool.PointBelowEntrance, uaWalk, 0, fUnit.AnimStep); // Preserv current AnimStep
|
||||
1: SetActionGoIn(uaWalk, gdGoInside, fSchool);
|
||||
2: begin
|
||||
//Note: we do not set trTaskDone here, as we are going to destroy this task and Close (delete) unit
|
||||
//Setting to trTaskDone will force Unit.UpadateState to find new task/action for this unit
|
||||
if gMySpectator.Selected = fUnit then
|
||||
gMySpectator.Selected := nil; //Reset view, in case we were watching dismissed unit
|
||||
|
||||
gHands[fUnit.Owner].Stats.UnitLost(fUnit.UnitType);
|
||||
gHands[fUnit.Owner].Stats.CitizenRetired(fUnit.UnitType);
|
||||
|
||||
gScriptEvents.ProcUnitDismissed(fUnit);
|
||||
|
||||
TKMCivilUnit(fUnit).KillInHouse; //Kill unit silently inside house
|
||||
Exit; //Exit immediately, since we destroyed current task!
|
||||
//Changing any task fields here (f.e. Phase) will try to change freed memory!
|
||||
end;
|
||||
else Result := trTaskDone;
|
||||
end;
|
||||
|
||||
Inc(fPhase);
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
unit KM_UnitTaskGoEat;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
Classes, KM_CommonClasses, KM_Defaults, KM_Units, KM_Houses, KM_HouseInn, SysUtils;
|
||||
|
||||
|
||||
type
|
||||
//Go to eat
|
||||
TKMTaskGoEat = class(TKMUnitTask)
|
||||
private
|
||||
fFeedCnt: Byte;
|
||||
fInn: TKMHouseInn; //Inn in which we are going to eat
|
||||
fPlace: ShortInt; //Units place in Inn
|
||||
public
|
||||
constructor Create(aInn: TKMHouseInn; aUnit: TKMUnit);
|
||||
constructor Load(LoadStream: TKMemoryStream); override;
|
||||
procedure SyncLoad; override;
|
||||
destructor Destroy; override;
|
||||
function Eating: Boolean;
|
||||
function Execute: TKMTaskResult; override;
|
||||
|
||||
function CouldBeCancelled: Boolean; override;
|
||||
function WalkShouldAbandon: Boolean; override;
|
||||
|
||||
procedure Save(SaveStream: TKMemoryStream); override;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
KM_Entity,
|
||||
KM_HandsCollection, KM_Hand, KM_HandTypes, KM_HandEntity,
|
||||
KM_ResTypes;
|
||||
|
||||
|
||||
{ TTaskGoEat }
|
||||
constructor TKMTaskGoEat.Create(aInn: TKMHouseInn; aUnit: TKMUnit);
|
||||
begin
|
||||
inherited Create(aUnit);
|
||||
|
||||
fType := uttGoEat;
|
||||
fInn := TKMHouseInn(aInn.GetPointer);
|
||||
fPlace := -1;
|
||||
fFeedCnt := 0;
|
||||
end;
|
||||
|
||||
|
||||
constructor TKMTaskGoEat.Load(LoadStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
|
||||
LoadStream.CheckMarker('TaskGoEat');
|
||||
LoadStream.Read(fInn, 4);
|
||||
LoadStream.Read(fPlace);
|
||||
LoadStream.Read(fFeedCnt);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMTaskGoEat.SyncLoad;
|
||||
begin
|
||||
inherited;
|
||||
|
||||
fInn := TKMHouseInn(gHands.GetHouseByUID(Integer(fInn)));
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskGoEat.WalkShouldAbandon: Boolean;
|
||||
begin
|
||||
Result := fInn.IsDestroyed;
|
||||
end;
|
||||
|
||||
|
||||
destructor TKMTaskGoEat.Destroy;
|
||||
begin
|
||||
//May happen when we die while desperatley trying to get some food
|
||||
if (fInn <> nil) and Eating then
|
||||
fInn.EatersGoesOut(fPlace);
|
||||
|
||||
gHands.CleanUpHousePointer(TKMHouse(fInn));
|
||||
|
||||
inherited;
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskGoEat.Eating: Boolean;
|
||||
begin
|
||||
Result := fPlace <> -1;
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskGoEat.CouldBeCancelled: Boolean;
|
||||
begin
|
||||
Result := (fPhase - 1) //phase was increased at the end of execution
|
||||
= 1; //Allow cancel task only at walking phases
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskGoEat.Execute: TKMTaskResult;
|
||||
const
|
||||
MAX_FEED_CNT = 2; // Max number of times unit can eat different food at Inn during one visit
|
||||
begin
|
||||
Result := trTaskContinues;
|
||||
|
||||
if fInn.IsDestroyed then
|
||||
Exit(trTaskDone);
|
||||
|
||||
with fUnit do
|
||||
case fPhase of
|
||||
0: begin
|
||||
Thought := thEat;
|
||||
if (Home <> nil) and not Home.IsDestroyed then
|
||||
Home.SetState(hstEmpty);
|
||||
if not Visible and (InHouse <> nil) and not InHouse.IsDestroyed then
|
||||
SetActionGoIn(uaWalk, gdGoOutside, InHouse) //Walk outside the house
|
||||
else
|
||||
SetActionLockedStay(0, uaWalk); //Skip this step
|
||||
end;
|
||||
1: SetActionWalkToSpot(fInn.PointBelowEntrance);
|
||||
2: SetActionGoIn(uaWalk, gdGoInside, fInn); //Enter Inn
|
||||
3: begin
|
||||
SetActionLockedStay(0, uaWalk);
|
||||
fPlace := fInn.EaterGetsRandomEmptySlot(UnitType);
|
||||
//If there's no free place in the Inn skip to the step where we go out hungry
|
||||
if fPlace = -1 then
|
||||
begin
|
||||
fPhase := 7;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
4: //Typically when unit comes to Inn he is at 13%
|
||||
//Order is Bread-Sausages-Wine-Fish
|
||||
//We allow unit to eat foods until he is over 90% condition
|
||||
if (Condition < UNIT_MAX_CONDITION * UNIT_CONDITION_FULL_THRESHOLD)
|
||||
and (fInn.CheckWareIn(wtBread) > 0) then
|
||||
begin
|
||||
fInn.WareTakeFromIn(wtBread);
|
||||
gHands[fUnit.Owner].Stats.WareConsumed(wtBread);
|
||||
SetActionLockedStay(29*4, uaEat);
|
||||
Feed(UNIT_MAX_CONDITION * BREAD_RESTORE);
|
||||
fInn.UpdateEater(fPlace, wtBread);
|
||||
Inc(fFeedCnt);
|
||||
end else
|
||||
SetActionLockedStay(0, uaWalk);
|
||||
5: if (Condition < UNIT_MAX_CONDITION * UNIT_CONDITION_FULL_THRESHOLD)
|
||||
and (fInn.CheckWareIn(wtSausage) > 0) then
|
||||
begin
|
||||
fInn.WareTakeFromIn(wtSausage);
|
||||
gHands[fUnit.Owner].Stats.WareConsumed(wtSausage);
|
||||
SetActionLockedStay(29*4, uaEat);
|
||||
Feed(UNIT_MAX_CONDITION * SAUSAGE_RESTORE);
|
||||
fInn.UpdateEater(fPlace, wtSausage);
|
||||
Inc(fFeedCnt);
|
||||
end else
|
||||
SetActionLockedStay(0, uaWalk);
|
||||
6: if (fFeedCnt < MAX_FEED_CNT) //Limit max number of times to eat in the Inn
|
||||
and (Condition < UNIT_MAX_CONDITION * UNIT_CONDITION_FULL_THRESHOLD)
|
||||
and (fInn.CheckWareIn(wtWine) > 0) then
|
||||
begin
|
||||
fInn.WareTakeFromIn(wtWine);
|
||||
gHands[fUnit.Owner].Stats.WareConsumed(wtWine);
|
||||
SetActionLockedStay(29*4, uaEat);
|
||||
Feed(UNIT_MAX_CONDITION * WINE_RESTORE);
|
||||
fInn.UpdateEater(fPlace, wtWine);
|
||||
Inc(fFeedCnt);
|
||||
end else
|
||||
SetActionLockedStay(0, uaWalk);
|
||||
7: if (fFeedCnt < MAX_FEED_CNT) //Limit max number of times to eat in the Inn
|
||||
and (Condition < UNIT_MAX_CONDITION * UNIT_CONDITION_FULL_THRESHOLD)
|
||||
and (fInn.CheckWareIn(wtFish) > 0) then
|
||||
begin
|
||||
fInn.WareTakeFromIn(wtFish);
|
||||
gHands[fUnit.Owner].Stats.WareConsumed(wtFish);
|
||||
SetActionLockedStay(29*4, uaEat);
|
||||
Feed(UNIT_MAX_CONDITION * FISH_RESTORE);
|
||||
fInn.UpdateEater(fPlace, wtFish);
|
||||
end else
|
||||
SetActionLockedStay(0, uaWalk);
|
||||
8: begin
|
||||
//Stop showing hungry if we no longer are,
|
||||
//but if we are then walk out of the inn thinking hungry
|
||||
//so that the player will know that we haven't been fed
|
||||
if Condition < UNIT_MIN_CONDITION then
|
||||
Thought := thEat
|
||||
else
|
||||
Thought := thNone;
|
||||
SetActionGoIn(uaWalk, gdGoOutside, fInn); //Exit Inn
|
||||
fInn.EatersGoesOut(fPlace);
|
||||
fPlace := -1;
|
||||
end;
|
||||
else
|
||||
Result := trTaskDone;
|
||||
end;
|
||||
|
||||
Inc(fPhase);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMTaskGoEat.Save(SaveStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
SaveStream.PlaceMarker('TaskGoEat');
|
||||
SaveStream.Write(fInn.UID); //Store ID, then substitute it with reference on SyncLoad
|
||||
SaveStream.Write(fPlace);
|
||||
SaveStream.Write(fFeedCnt);
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,68 @@
|
||||
unit KM_UnitTaskGoHome;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
Classes, SysUtils,
|
||||
KM_Defaults, KM_Units;
|
||||
|
||||
|
||||
type
|
||||
TKMTaskGoHome = class(TKMUnitTask)
|
||||
public
|
||||
constructor Create(aUnit: TKMUnit);
|
||||
|
||||
function Execute: TKMTaskResult; override;
|
||||
function CouldBeCancelled: Boolean; override;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
|
||||
|
||||
{ TTaskGoHome }
|
||||
constructor TKMTaskGoHome.Create(aUnit: TKMUnit);
|
||||
begin
|
||||
inherited;
|
||||
|
||||
fType := uttGoHome;
|
||||
aUnit.Thought := thHome;
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskGoHome.CouldBeCancelled: Boolean;
|
||||
begin
|
||||
Result := (fPhase - 1) //phase was increased at the end of execution
|
||||
<= 0; //Allow cancel task only at walking phases
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskGoHome.Execute: TKMTaskResult;
|
||||
begin
|
||||
Result := trTaskContinues;
|
||||
|
||||
if fUnit.Home.IsDestroyed then
|
||||
begin
|
||||
Result := trTaskDone;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
with fUnit do
|
||||
case fPhase of
|
||||
0: begin
|
||||
Thought := thHome;
|
||||
SetActionWalkToSpot(Home.PointBelowEntrance);
|
||||
end;
|
||||
1: SetActionGoIn(uaWalk, gdGoInside, Home);
|
||||
2: begin
|
||||
Thought := thNone; //Only stop thinking once we are right inside
|
||||
Home.SetState(hstIdle);
|
||||
SetActionStay(5, uaWalk);
|
||||
end;
|
||||
else Result := trTaskDone;
|
||||
end;
|
||||
|
||||
Inc(fPhase);
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,61 @@
|
||||
unit KM_UnitTaskGoOutShowHungry;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
Classes, KM_Defaults, KM_Units, SysUtils;
|
||||
|
||||
type
|
||||
TKMTaskGoOutShowHungry = class(TKMUnitTask)
|
||||
public
|
||||
constructor Create(aUnit: TKMUnit);
|
||||
function Execute: TKMTaskResult; override;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
KM_CommonUtils;
|
||||
|
||||
|
||||
{ TTaskGoOutShowHungry }
|
||||
constructor TKMTaskGoOutShowHungry.Create(aUnit: TKMUnit);
|
||||
begin
|
||||
inherited Create(aUnit);
|
||||
|
||||
fType := uttGoOutShowHungry;
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskGoOutShowHungry.Execute: TKMTaskResult;
|
||||
begin
|
||||
Result := trTaskContinues;
|
||||
if fUnit.Home.IsDestroyed then
|
||||
Exit(trTaskDone);
|
||||
|
||||
with fUnit do
|
||||
case fPhase of
|
||||
0: begin
|
||||
Thought := thEat;
|
||||
SetActionStay(20, uaWalk);
|
||||
end;
|
||||
1: begin
|
||||
SetActionGoIn(uaWalk,gdGoOutside, fUnit.Home);
|
||||
Home.SetState(hstEmpty);
|
||||
end;
|
||||
2: SetActionLockedStay(4,uaWalk);
|
||||
3: SetActionWalkToSpot(fUnit.Home.PointBelowEntrance);
|
||||
4: SetActionGoIn(uaWalk, gdGoInside, fUnit.Home);
|
||||
5: begin
|
||||
SetActionStay(20 + KaMRandom(10{$IFDEF DBG_RNG_SPY}, 'TKMTaskGoOutShowHungry.Execute'{$ENDIF}), uaWalk);
|
||||
Home.SetState(hstIdle);
|
||||
end;
|
||||
else begin
|
||||
Thought := thNone;
|
||||
Result := trTaskDone;
|
||||
end;
|
||||
end;
|
||||
inc(fPhase);
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,442 @@
|
||||
unit KM_UnitTaskMining;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
Math, SysUtils,
|
||||
KM_CommonClasses, KM_Defaults, KM_Points,
|
||||
KM_Units, KM_UnitWorkplan, KM_TerrainTypes,
|
||||
KM_ResTypes;
|
||||
|
||||
|
||||
type
|
||||
// Resource mining task
|
||||
TKMTaskMining = class(TKMUnitTask)
|
||||
private
|
||||
fBeastID: Byte;
|
||||
fWorkPlan: TKMUnitWorkPlan;
|
||||
fDistantResAcquired: Boolean; //Was distant resorce acquired ? (stone / fish / wood / corn / wine)
|
||||
function ResourceExists: Boolean;
|
||||
function ResourceTileIsLocked: Boolean;
|
||||
function ChooseToCutOrPlant: TKMPlantAct;
|
||||
procedure FindAnotherWorkPlan;
|
||||
public
|
||||
constructor Create(aUnit: TKMUnit; aWare: TKMWareType);
|
||||
destructor Destroy; override;
|
||||
function WalkShouldAbandon: Boolean; override;
|
||||
constructor Load(LoadStream: TKMemoryStream); override;
|
||||
function GetActivityText: UnicodeString;
|
||||
property WorkPlan: TKMUnitWorkPlan read fWorkPlan;
|
||||
function Execute: TKMTaskResult; override;
|
||||
function CouldBeCancelled: Boolean; override;
|
||||
procedure Save(SaveStream: TKMemoryStream); override;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
KM_Houses, KM_HouseWoodcutters, KM_HouseSwineStable,
|
||||
KM_HandsCollection, KM_HandTypes, KM_HandEntity,
|
||||
KM_Resource, KM_ResMapElements, KM_ResTexts,
|
||||
KM_Hand, KM_ResUnits, KM_ScriptingEvents, KM_Terrain;
|
||||
|
||||
|
||||
{ TTaskMining }
|
||||
constructor TKMTaskMining.Create(aUnit: TKMUnit; aWare: TKMWareType);
|
||||
begin
|
||||
inherited Create(aUnit);
|
||||
|
||||
fType := uttMining;
|
||||
fWorkPlan := TKMUnitWorkPlan.Create;
|
||||
fDistantResAcquired := False;
|
||||
fBeastID := 0;
|
||||
|
||||
fWorkPlan.FindPlan( fUnit,
|
||||
fUnit.Home.HouseType,
|
||||
aWare,
|
||||
aUnit.Home.PointBelowEntrance,
|
||||
ChooseToCutOrPlant
|
||||
);
|
||||
end;
|
||||
|
||||
|
||||
destructor TKMTaskMining.Destroy;
|
||||
begin
|
||||
// Make sure we don't abandon and leave our house with "working" animations
|
||||
if (fUnit <> nil)
|
||||
and not fUnit.Home.IsDestroyed
|
||||
and (fUnit.Home.GetState = hstWork) then
|
||||
fUnit.Home.SetState(hstIdle);
|
||||
|
||||
FreeAndNil(fWorkPlan);
|
||||
|
||||
inherited;
|
||||
end;
|
||||
|
||||
|
||||
//Note: Phase is -1 because it will have been increased at the end of last Execute
|
||||
function TKMTaskMining.WalkShouldAbandon: Boolean;
|
||||
begin
|
||||
Result := False;
|
||||
Assert(fUnit is TKMUnitCitizen);
|
||||
if fPhase = 2 then //Unit is walking to mine-position
|
||||
Result := ResourceTileIsLocked or //If someone takes our place
|
||||
not ResourceExists or //Resource has gone
|
||||
not TKMUnitCitizen(fUnit).CanWorkAt(WorkPlan.Loc, WorkPlan.GatheringScript);
|
||||
end;
|
||||
|
||||
|
||||
//Chose if we don't care or prefer specific activity
|
||||
//depending on orders or clogged output
|
||||
function TKMTaskMining.ChooseToCutOrPlant: TKMPlantAct;
|
||||
begin
|
||||
Result := taAny;
|
||||
|
||||
case fUnit.Home.HouseType of
|
||||
htWoodcutters: case TKMHouseWoodcutters(fUnit.Home).WoodcutterMode of
|
||||
wmChop: Result := taCut;
|
||||
wmPlant: Result := taPlant;
|
||||
wmChopAndPlant: if fUnit.Home.CheckWareOut(wtTrunk) >= MAX_WARES_IN_HOUSE then
|
||||
Result := taPlant
|
||||
else
|
||||
Result := taAny;
|
||||
end;
|
||||
htFarm: if fUnit.Home.CheckWareOut(wtCorn) >= MAX_WARES_IN_HOUSE then
|
||||
Result := taPlant
|
||||
else
|
||||
Result := taAny;
|
||||
else Result := taAny; //We don't care since other housetypes don't have concurent activities
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
constructor TKMTaskMining.Load(LoadStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
LoadStream.CheckMarker('TaskMining');
|
||||
fWorkPlan := TKMUnitWorkPlan.Create;
|
||||
fWorkPlan.Load(LoadStream);
|
||||
LoadStream.Read(fDistantResAcquired);
|
||||
LoadStream.Read(fBeastID);
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskMining.GetActivityText: UnicodeString;
|
||||
begin
|
||||
case WorkPlan.GatheringScript of
|
||||
gsStoneCutter: Result := gResTexts[TX_UNIT_TASK_STONE];
|
||||
gsFarmerSow: Result := gResTexts[TX_UNIT_TASK_SOW_CORN];
|
||||
gsFarmerCorn: Result := gResTexts[TX_UNIT_TASK_CUTTING_CORN];
|
||||
gsFarmerWine: Result := gResTexts[TX_UNIT_TASK_GRAPES];
|
||||
gsFisherCatch: Result := gResTexts[TX_UNIT_TASK_FISHING];
|
||||
gsWoodCutterCut: Result := gResTexts[TX_UNIT_TASK_CUT_TREE];
|
||||
gsWoodCutterPlant: Result := gResTexts[TX_UNIT_TASK_PLANT_TREE];
|
||||
else Result := 'Unknown';
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
//Try to find alternative target for our WorkPlan
|
||||
//Happens when we discover that resource is gone or is occupied by another busy unit
|
||||
//Return False if new plan could not be found
|
||||
procedure TKMTaskMining.FindAnotherWorkPlan;
|
||||
var OldLoc: TKMPoint; OldDir: TKMDirection;
|
||||
begin
|
||||
OldLoc := WorkPlan.Loc;
|
||||
OldDir := WorkPlan.WorkDir;
|
||||
|
||||
//Tell the work plan to find a new resource of the same gathering script
|
||||
if WorkPlan.FindDifferentResource(fUnit, fUnit.Home.PointBelowEntrance, OldLoc) then
|
||||
begin
|
||||
//Must always give us a new location (or same location but different direction)
|
||||
Assert((OldDir <> WorkPlan.WorkDir) or not KMSamePoint(OldLoc, WorkPlan.Loc));
|
||||
fPhase := 0; //Set the walk again (Will become 1 after this loop)
|
||||
fUnit.SetActionLockedStay(0, WorkPlan.ActionWalkTo);
|
||||
end else
|
||||
begin
|
||||
fPhase := 99; //Abandon as there is no other work plan available (Exit the task on next update)
|
||||
fUnit.SetActionLockedStay(0, WorkPlan.ActionWalkTo);
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskMining.ResourceTileIsLocked: Boolean;
|
||||
var
|
||||
P: TKMPoint;
|
||||
begin
|
||||
if WorkPlan.GatheringScript = gsWoodCutterCut then
|
||||
begin
|
||||
P := KMGetVertexTile(WorkPlan.Loc, WorkPlan.WorkDir);
|
||||
// Check all tiles around the tree, same as we do in TKMTerrain.FindTree
|
||||
Result := not gTerrain.CanCutTreeAtVertex(fUnit.PositionNext, P);
|
||||
end
|
||||
else
|
||||
Result := gTerrain.TileIsLocked(WorkPlan.Loc);
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskMining.ResourceExists: Boolean;
|
||||
var P: TKMPoint;
|
||||
begin
|
||||
with gTerrain do
|
||||
case WorkPlan.GatheringScript of
|
||||
gsStoneCutter: Result := TileHasStone(WorkPlan.Loc.X, WorkPlan.Loc.Y-1); //Check stone deposit above Loc, which is walkable tile
|
||||
gsFarmerSow: Result := TileIsCornField(WorkPlan.Loc) and (Land^[WorkPlan.Loc.Y, WorkPlan.Loc.X].FieldAge = 0);
|
||||
gsFarmerCorn: begin
|
||||
Result := TileIsCornField(WorkPlan.Loc) and (Land^[WorkPlan.Loc.Y, WorkPlan.Loc.X].FieldAge = CORN_AGE_MAX);
|
||||
if Result then exit; //Resource still exists so exit
|
||||
//If corn has been cut we can possibly plant new corn here to save time
|
||||
Result := TileIsCornField(WorkPlan.Loc) and (Land^[WorkPlan.Loc.Y, WorkPlan.Loc.X].FieldAge = 0);
|
||||
if Result then
|
||||
with WorkPlan do
|
||||
begin
|
||||
GatheringScript := gsFarmerSow; //Switch to sowing corn rather than cutting
|
||||
ActionWalkFrom := uaWalkTool; //Carry our scythe back (without the corn) as the player saw us take it out
|
||||
ActionWorkType := uaWork1;
|
||||
WorkCyc := 10;
|
||||
Product1 := wtNone; //Don't produce corn
|
||||
ProdCount1 := 0;
|
||||
end;
|
||||
end;
|
||||
gsFarmerWine: Result := TileIsWineField(WorkPlan.Loc) and (Land^[WorkPlan.Loc.Y, WorkPlan.Loc.X].FieldAge = CORN_AGE_MAX);
|
||||
gsFisherCatch: Result := CatchFish(KMPointDir(WorkPlan.Loc,WorkPlan.WorkDir),True);
|
||||
gsWoodCutterPlant: Result := TileGoodToPlantTree(WorkPlan.Loc.X, WorkPlan.Loc.Y);
|
||||
gsWoodCutterCut: begin
|
||||
P := KMGetVertexTile(WorkPlan.Loc, WorkPlan.WorkDir);
|
||||
Result := ObjectIsChopableTree(P, caAgeFull) and (Land^[P.Y, P.X].TreeAge >= TREE_AGE_FULL);
|
||||
end;
|
||||
else Result := True;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskMining.CouldBeCancelled: Boolean;
|
||||
begin
|
||||
Result := (fPhase - 1) //phase was increased at the end of execution
|
||||
in [1, 7]; //Allow cancel task only at walking phases
|
||||
end;
|
||||
|
||||
|
||||
{This is execution of Resource mining}
|
||||
function TKMTaskMining.Execute: TKMTaskResult;
|
||||
const
|
||||
// Shortcuts to skip certain Phases
|
||||
SkipWalk = 9;
|
||||
SKIP_WORK = 11 + MAX_WORKPLAN; //Skip to certain Phases
|
||||
var
|
||||
D: TKMDirection;
|
||||
TimeToWork, StillFrame: Integer;
|
||||
actWalkFrom: TKMUnitActionType;
|
||||
ResAcquired: Boolean;
|
||||
begin
|
||||
Result := trTaskContinues;
|
||||
|
||||
//there's no point in doing a task if we can't return home
|
||||
if (fUnit.Home <> nil) and fUnit.Home.IsDestroyed then
|
||||
begin
|
||||
Result := trTaskDone;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if fDistantResAcquired then
|
||||
actWalkFrom := WorkPlan.ActionWalkFrom
|
||||
else
|
||||
actWalkFrom := WorkPlan.ActionWalkTo;
|
||||
|
||||
with fUnit do
|
||||
case fPhase of
|
||||
0: if WorkPlan.HasToWalk then
|
||||
begin
|
||||
fDistantResAcquired := False; // we will set distant resource as acquired when we gather it
|
||||
Home.SetState(hstEmpty);
|
||||
SetActionGoIn(WorkPlan.ActionWalkTo, gdGoOutside, Home); //Walk outside the house
|
||||
|
||||
//Woodcutter takes his axe with him when going to chop trees
|
||||
if (WorkPlan.GatheringScript = gsWoodCutterCut) then
|
||||
Home.CurrentAction.SubActionRem([haFlagpole]);
|
||||
end
|
||||
else
|
||||
begin
|
||||
fDistantResAcquired := True; // there is no distant resource, so its acquired by default
|
||||
fPhase := SkipWalk; //Skip walking part if there's no need in it, e.g. CoalMiner or Baker
|
||||
SetActionLockedStay(0, uaWalk);
|
||||
Exit;
|
||||
end;
|
||||
|
||||
1: //We cannot assume that the walk is still valid because the terrain could have changed while we were walking out of the house.
|
||||
SetActionWalkToSpot(WorkPlan.Loc, WorkPlan.ActionWalkTo);
|
||||
|
||||
2: //Check if we are at the location. WalkTo could have failed or resource could have been exhausted
|
||||
if not KMSamePoint(PositionNext, WorkPlan.Loc) or not ResourceExists or
|
||||
not TKMUnitCitizen(fUnit).CanWorkAt(WorkPlan.Loc, WorkPlan.GatheringScript) then
|
||||
FindAnotherWorkPlan
|
||||
else
|
||||
SetActionLockedStay(0, WorkPlan.ActionWalkTo);
|
||||
|
||||
3: //Before work tasks for specific mining jobs
|
||||
if WorkPlan.GatheringScript = gsFisherCatch then
|
||||
begin
|
||||
Direction := WorkPlan.WorkDir;
|
||||
SetActionLockedStay(13, uaWork1, False); //Throw the line out
|
||||
end else
|
||||
SetActionLockedStay(0, WorkPlan.ActionWalkTo);
|
||||
|
||||
4: //Choose direction and time to work
|
||||
begin
|
||||
if WorkPlan.WorkDir <> dirNA then
|
||||
Direction := WorkPlan.WorkDir;
|
||||
|
||||
if gRes.Units[UnitType].UnitAnim[WorkPlan.ActionWorkType, Direction].Count < 1 then
|
||||
for D := dirN to dirNW do
|
||||
if gRes.Units[UnitType].UnitAnim[WorkPlan.ActionWorkType, D].Count > 1 then
|
||||
begin
|
||||
Direction := D;
|
||||
Break;
|
||||
end;
|
||||
|
||||
TimeToWork := WorkPlan.WorkCyc * Math.max(gRes.Units[UnitType].UnitAnim[WorkPlan.ActionWorkType, Direction].Count, 1);
|
||||
SetActionLockedStay(TimeToWork, WorkPlan.ActionWorkType, False);
|
||||
end;
|
||||
5: //After work tasks for specific mining jobs
|
||||
case WorkPlan.GatheringScript of
|
||||
gsWoodCutterCut: SetActionLockedStay(10, WorkPlan.ActionWorkType, True, 5, 5); //Wait for the tree to start falling down
|
||||
gsFisherCatch: SetActionLockedStay(15, uaWork, False); //Pull the line in
|
||||
else SetActionLockedStay(0, WorkPlan.ActionWorkType);
|
||||
end;
|
||||
6: begin
|
||||
StillFrame := 0;
|
||||
case WorkPlan.GatheringScript of //Perform special tasks if required
|
||||
gsStoneCutter: fDistantResAcquired := gTerrain.DecStoneDeposit(KMPoint(WorkPlan.Loc.X,WorkPlan.Loc.Y-1));
|
||||
gsFarmerSow: gTerrain.SowCorn(WorkPlan.Loc);
|
||||
gsFarmerCorn: fDistantResAcquired := gTerrain.CutCorn(WorkPlan.Loc);
|
||||
gsFarmerWine: fDistantResAcquired := gTerrain.CutGrapes(WorkPlan.Loc);
|
||||
gsFisherCatch: begin
|
||||
fDistantResAcquired := gTerrain.CatchFish(KMPointDir(WorkPlan.Loc,WorkPlan.WorkDir));
|
||||
if fDistantResAcquired then
|
||||
WorkPlan.ActionWorkType := uaWalkTool;
|
||||
end;
|
||||
gsWoodCutterPlant: //If the player placed a house plan here while we were digging don't place the
|
||||
//tree so the house plan isn't canceled. This is actually the same as TSK/TPR IIRC
|
||||
if TKMUnitCitizen(fUnit).CanWorkAt(WorkPlan.Loc, gsWoodCutterPlant) then
|
||||
begin
|
||||
gTerrain.SetObject(WorkPlan.Loc, gTerrain.ChooseTreeToPlant(WorkPlan.Loc));
|
||||
fDistantResAcquired := True;
|
||||
end;
|
||||
gsWoodCutterCut: begin
|
||||
fDistantResAcquired := gTerrain.FallTree(KMGetVertexTile(WorkPlan.Loc, WorkPlan.WorkDir));
|
||||
StillFrame := 5;
|
||||
end;
|
||||
end;
|
||||
SetActionLockedStay(WorkPlan.AfterWorkDelay, WorkPlan.ActionWorkType, True, StillFrame, StillFrame);
|
||||
end;
|
||||
7: begin
|
||||
//Removing the tree and putting a stump is handled in gTerrain.UpdateState from FallingTrees list
|
||||
SetActionWalkToSpot(Home.PointBelowEntrance, actWalkFrom); //Go home
|
||||
Thought := thHome;
|
||||
end;
|
||||
8: SetActionGoIn(actWalkFrom, gdGoInside, Home); //Go inside
|
||||
|
||||
{Unit back at home and can process its booty now}
|
||||
9: begin
|
||||
Thought := thNone;
|
||||
fPhase2 := 0;
|
||||
Home.SetState(hstWork);
|
||||
|
||||
//Take required resources
|
||||
if WorkPlan.Resource1 <> wtNone then
|
||||
Home.WareTakeFromIn(WorkPlan.Resource1, WorkPlan.Count1);
|
||||
if WorkPlan.Resource2 <> wtNone then
|
||||
Home.WareTakeFromIn(WorkPlan.Resource2, WorkPlan.Count2);
|
||||
|
||||
gHands[fUnit.Owner].Stats.WareConsumed(WorkPlan.Resource1, WorkPlan.Count1);
|
||||
gHands[fUnit.Owner].Stats.WareConsumed(WorkPlan.Resource2, WorkPlan.Count2);
|
||||
|
||||
Home.CurrentAction.SubActionAdd([haSmoke]);
|
||||
|
||||
if fDistantResAcquired and (fPhase2 < WorkPlan.ActCount) then
|
||||
begin
|
||||
Home.CurrentAction.SubActionWork(WorkPlan.HouseAct[fPhase2].Act);
|
||||
//Keep unit idling till next Phase, Idle time is -1 to compensate TaskExecution Phase
|
||||
SetActionLockedStay(WorkPlan.HouseAct[fPhase2].TimeToWork-1, uaWalk);
|
||||
end
|
||||
else
|
||||
begin
|
||||
fPhase := SKIP_WORK; //Skip to work complete
|
||||
SetActionLockedStay(0, uaWalk);
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
10..10 + MAX_WORKPLAN:
|
||||
begin
|
||||
Inc(fPhase2);
|
||||
|
||||
//Keep on working
|
||||
if fDistantResAcquired and (fPhase2 < WorkPlan.ActCount) then
|
||||
begin
|
||||
Home.CurrentAction.SubActionWork(WorkPlan.HouseAct[fPhase2].Act);
|
||||
if fPhase < WorkPlan.ActCount then
|
||||
SetActionLockedStay(WorkPlan.HouseAct[fPhase2].TimeToWork-1, uaWalk) //-1 to compensate units UpdateState run
|
||||
else
|
||||
SetActionLockedStay(WorkPlan.HouseAct[fPhase2].TimeToWork-2, uaWalk) //-2 to compensate 2 UpdateStates of a unit in last Act
|
||||
end
|
||||
else
|
||||
begin
|
||||
fPhase := SKIP_WORK; //Skip to step 31
|
||||
SetActionLockedStay(0, uaWalk);
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
11 + MAX_WORKPLAN:
|
||||
begin
|
||||
|
||||
//Feed and take horses/swines after animal breeder will finish working to prevent abusing.
|
||||
//If swine/horse will appear at start player can make animal breeder go out from house and let him in again and speed up house production.
|
||||
if (WorkPlan.GatheringScript = gsSwineBreeder) or (WorkPlan.GatheringScript = gsHorseBreeder) then
|
||||
begin
|
||||
fBeastID := TKMHouseSwineStable(Home).FeedBeasts;
|
||||
TKMHouseSwineStable(Home).TakeBeast(fBeastID);
|
||||
end;
|
||||
|
||||
case WorkPlan.GatheringScript of
|
||||
gsWoodCutterCut,
|
||||
gsFarmerCorn,
|
||||
gsFarmerWine,
|
||||
gsStoneCutter,
|
||||
gsFisherCatch: ResAcquired := fDistantResAcquired; // fResAcquired was set earlier when we tried to get resource
|
||||
gsCoalMiner: ResAcquired := gTerrain.DecOreDeposit(WorkPlan.Loc, wtCoal);
|
||||
gsGoldMiner: ResAcquired := gTerrain.DecOreDeposit(WorkPlan.Loc, wtGoldOre);
|
||||
gsIronMiner: ResAcquired := gTerrain.DecOreDeposit(WorkPlan.Loc, wtIronOre);
|
||||
gsSwineBreeder: ResAcquired := fBeastID <> 0;
|
||||
gsHorseBreeder: ResAcquired := fBeastID <> 0;
|
||||
else ResAcquired := True;
|
||||
end;
|
||||
|
||||
if ResAcquired then
|
||||
begin
|
||||
Home.WareAddToOut(WorkPlan.Product1, WorkPlan.ProdCount1);
|
||||
Home.WareAddToOut(WorkPlan.Product2, WorkPlan.ProdCount2);
|
||||
gHands[fUnit.Owner].Stats.WareProduced(WorkPlan.Product1, WorkPlan.ProdCount1);
|
||||
gHands[fUnit.Owner].Stats.WareProduced(WorkPlan.Product2, WorkPlan.ProdCount2);
|
||||
gScriptEvents.ProcWareProduced(fUnit.Home, WorkPlan.Product1, WorkPlan.ProdCount1);
|
||||
gScriptEvents.ProcWareProduced(fUnit.Home, WorkPlan.Product2, WorkPlan.ProdCount2);
|
||||
end;
|
||||
|
||||
Home.SetState(hstIdle);
|
||||
SetActionLockedStay(WorkPlan.AfterWorkIdle-1, uaWalk);
|
||||
end;
|
||||
else Result := trTaskDone;
|
||||
end;
|
||||
Inc(fPhase);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMTaskMining.Save(SaveStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
SaveStream.PlaceMarker('TaskMining');
|
||||
fWorkPlan.Save(SaveStream);
|
||||
SaveStream.Write(fDistantResAcquired);
|
||||
SaveStream.Write(fBeastID);
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,129 @@
|
||||
unit KM_UnitTaskSelfTrain;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
Classes, SysUtils,
|
||||
KM_CommonClasses, KM_Defaults, KM_Houses, KM_HouseSchool, KM_Units;
|
||||
|
||||
|
||||
//Train citizen in school
|
||||
type
|
||||
TKMTaskSelfTrain = class(TKMUnitTask)
|
||||
private
|
||||
fSchool: TKMHouseSchool;
|
||||
public
|
||||
constructor Create(aUnit: TKMUnit; aSchool: TKMHouseSchool);
|
||||
constructor Load(LoadStream: TKMemoryStream); override;
|
||||
procedure SyncLoad; override;
|
||||
destructor Destroy; override;
|
||||
function Execute: TKMTaskResult; override;
|
||||
procedure Save(SaveStream: TKMemoryStream); override;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
KM_Entity,
|
||||
KM_HandsCollection, KM_Game;
|
||||
|
||||
|
||||
{ TTaskSelfTrain }
|
||||
constructor TKMTaskSelfTrain.Create(aUnit: TKMUnit; aSchool: TKMHouseSchool);
|
||||
begin
|
||||
inherited Create(aUnit);
|
||||
|
||||
fType := uttSelfTrain;
|
||||
fSchool := TKMHouseSchool(aSchool.GetPointer);
|
||||
fUnit.Visible := False;
|
||||
end;
|
||||
|
||||
|
||||
constructor TKMTaskSelfTrain.Load(LoadStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
LoadStream.CheckMarker('TaskSelfTrain');
|
||||
LoadStream.Read(fSchool, 4);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMTaskSelfTrain.SyncLoad;
|
||||
begin
|
||||
inherited;
|
||||
fSchool := TKMHouseSchool(gHands.GetHouseByUID(Integer(fSchool)));
|
||||
end;
|
||||
|
||||
|
||||
destructor TKMTaskSelfTrain.Destroy;
|
||||
begin
|
||||
if (gGame = nil) or gGame.IsExiting then Exit; //fSchool will already be freed
|
||||
|
||||
// If we abandon for some reason, clear the school animation
|
||||
if (fSchool <> nil) and (fPhase <= 5) and not fSchool.IsDestroyed then
|
||||
fSchool.SetState(hstIdle);
|
||||
|
||||
gHands.CleanUpHousePointer(TKMHouse(fSchool));
|
||||
|
||||
inherited;
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskSelfTrain.Execute:TKMTaskResult;
|
||||
begin
|
||||
Result := trTaskContinues;
|
||||
|
||||
//If the school has been destroyed then this task should not be running (school frees it on CloseHouse)
|
||||
//However, if we are past phase 6 (task ends on phase 7) then the school does not know about us (we have stepped outside)
|
||||
if fSchool.IsDestroyed and (fPhase <= 6) then
|
||||
//School will cancel the training on own destruction
|
||||
raise Exception.Create('Unexpected error. Destoyed school erases the task');
|
||||
|
||||
with fUnit do
|
||||
case fPhase of
|
||||
0: begin
|
||||
fSchool.SetState(hstWork);
|
||||
fSchool.CurrentAction.SubActionWork(haWork1);
|
||||
SetActionLockedStay(29, uaWalk);
|
||||
end;
|
||||
1: begin
|
||||
fSchool.CurrentAction.SubActionWork(haWork2);
|
||||
SetActionLockedStay(29, uaWalk);
|
||||
end;
|
||||
2: begin
|
||||
fSchool.CurrentAction.SubActionWork(haWork3);
|
||||
SetActionLockedStay(29, uaWalk);
|
||||
end;
|
||||
3: begin
|
||||
fSchool.CurrentAction.SubActionWork(haWork4);
|
||||
SetActionLockedStay(29, uaWalk);
|
||||
end;
|
||||
4: begin
|
||||
fSchool.CurrentAction.SubActionWork(haWork5);
|
||||
SetActionLockedStay(29, uaWalk);
|
||||
end;
|
||||
5: begin
|
||||
fSchool.SetState(hstIdle);
|
||||
SetActionLockedStay(9, uaWalk);
|
||||
end;
|
||||
6: begin
|
||||
// Put him in the school, so if it is destroyed while he is looking for place to exit he is placed somewhere
|
||||
fUnit.InHouse := fSchool;
|
||||
SetActionGoIn(uaWalk, gdGoOutside, fSchool);
|
||||
fSchool.UnitTrainingComplete(fUnit);
|
||||
if Assigned(fUnit.OnUnitTrained) then
|
||||
fUnit.OnUnitTrained(fUnit);
|
||||
end;
|
||||
else Result := trTaskDone;
|
||||
end;
|
||||
Inc(fPhase);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMTaskSelfTrain.Save(SaveStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
SaveStream.PlaceMarker('TaskSelfTrain');
|
||||
SaveStream.Write(fSchool.UID);
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,118 @@
|
||||
unit KM_UnitTaskThrowRock;
|
||||
{$I KaM_Remake.inc}
|
||||
interface
|
||||
uses
|
||||
Classes, SysUtils,
|
||||
KM_CommonClasses, KM_Defaults, KM_Units;
|
||||
|
||||
|
||||
type
|
||||
// Throw a rock
|
||||
TKMTaskThrowRock = class(TKMUnitTask)
|
||||
private
|
||||
fTarget: TKMUnit;
|
||||
fFlightTime: Word; //Thats how long it will take a stone to hit it's target
|
||||
public
|
||||
constructor Create(aUnit, aTarget: TKMUnit);
|
||||
destructor Destroy; override;
|
||||
constructor Load(LoadStream: TKMemoryStream); override;
|
||||
procedure SyncLoad; override;
|
||||
function Execute: TKMTaskResult; override;
|
||||
procedure Save(SaveStream: TKMemoryStream); override;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
KM_Entity,
|
||||
KM_HandsCollection, KM_Hand, KM_HandTypes, KM_HandEntity,
|
||||
KM_Projectiles,
|
||||
KM_CommonGameTypes, KM_ResTypes;
|
||||
|
||||
|
||||
{ TKMTaskThrowRock }
|
||||
constructor TKMTaskThrowRock.Create(aUnit, aTarget: TKMUnit);
|
||||
begin
|
||||
inherited Create(aUnit);
|
||||
fType := uttThrowRock;
|
||||
fTarget := aTarget.GetPointer;
|
||||
end;
|
||||
|
||||
|
||||
destructor TKMTaskThrowRock.Destroy;
|
||||
begin
|
||||
if (fUnit <> nil)
|
||||
and not fUnit.Home.IsDestroyed
|
||||
and (fUnit.Home.GetState = hstWork) then
|
||||
fUnit.Home.SetState(hstIdle); //Make sure we don't abandon and leave our tower with "working" animations
|
||||
|
||||
gHands.CleanUpUnitPointer(fTarget);
|
||||
inherited;
|
||||
end;
|
||||
|
||||
|
||||
constructor TKMTaskThrowRock.Load(LoadStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
LoadStream.CheckMarker('TaskThrowRock');
|
||||
LoadStream.Read(fTarget, 4);
|
||||
LoadStream.Read(fFlightTime);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMTaskThrowRock.SyncLoad;
|
||||
begin
|
||||
inherited;
|
||||
fTarget := gHands.GetUnitByUID(Integer(fTarget));
|
||||
end;
|
||||
|
||||
|
||||
function TKMTaskThrowRock.Execute: TKMTaskResult;
|
||||
begin
|
||||
Result := trTaskContinues;
|
||||
|
||||
//Target could have been killed by another Tower or in a fight
|
||||
if fUnit.Home.IsDestroyed or ((fTarget<>nil) and fTarget.IsDeadOrDying) then
|
||||
begin
|
||||
Result := trTaskDone;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
with fUnit do
|
||||
case fPhase of
|
||||
0: begin
|
||||
Home.SetState(hstWork); //Set house to Work state
|
||||
Home.CurrentAction.SubActionWork(haWork2); //show Recruits back
|
||||
SetActionStay(2, uaWalk); //pretend to be taking the stone
|
||||
end;
|
||||
1: begin
|
||||
Home.WareTakeFromIn(wtStone, 1);
|
||||
gHands[Owner].Stats.WareConsumed(wtStone);
|
||||
fFlightTime := gProjectiles.AimTargetUnit(PositionF, fTarget, ptTowerRock, fUnit, WATCHTOWER_RANGE_MAX, WATCHTOWER_RANGE_MIN);
|
||||
gHands.CleanUpUnitPointer(fTarget); //We don't need it anymore
|
||||
SetActionLockedStay(1, uaWalk);
|
||||
end;
|
||||
2: SetActionLockedStay(fFlightTime, uaWalk); //Pretend to look how it goes
|
||||
3: begin
|
||||
Home.SetState(hstIdle);
|
||||
SetActionStay(20, uaWalk); //Idle before throwing another rock
|
||||
end;
|
||||
else Result := trTaskDone;
|
||||
end;
|
||||
Inc(fPhase);
|
||||
end;
|
||||
|
||||
|
||||
procedure TKMTaskThrowRock.Save(SaveStream: TKMemoryStream);
|
||||
begin
|
||||
inherited;
|
||||
SaveStream.PlaceMarker('TaskThrowRock');
|
||||
if fTarget <> nil then
|
||||
SaveStream.Write(fTarget.UID) //Store ID, then substitute it with reference on SyncLoad
|
||||
else
|
||||
SaveStream.Write(Integer(0));
|
||||
SaveStream.Write(fFlightTime);
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
Reference in new issue
Block a user