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
@@ -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