<commit_before>unit KM_Units;
{$I KaM_Remake.inc}
interface
uses
  Classes, Math, SysUtils, KromUtils, Types,
  KM_CommonClasses, KM_CommonTypes, KM_Defaults, KM_Points, KM_CommonUtils, KM_UnitVisual,
  KM_Terrain, KM_ResHouses, KM_ResWares, KM_Houses, KM_HouseSchool, KM_HouseBarracks, KM_HouseInn;

//Memo on directives:
//Dynamic - declared and used (overriden) occasionally
//Virtual - declared and used (overriden) always
//Abstract - declared but must be overriden in child classes

type
  TKMUnit = class;
  TKMUnitWorker = class;
  TKMUnitEvent = procedure(aUnit: TKMUnit) of object;
  TKMUnitFromEvent = procedure(aUnit: TKMUnit; aFrom: TKMHandID) of object;

  TKMActionResult = (arActContinues, arActDone, arActAborted, arActCanNotStart);

  TKMUnitArray = array of TKMUnit;

  TKMUnitAction = class
  protected
    fType: TKMUnitActionType;
    fUnit: TKMUnit;
  public
    Locked: Boolean; //Means that unit can't take part in interaction, must stay on its tile
    StepDone: Boolean; //True when single action element is done (unit walked to new tile, single attack loop done)
    constructor Create(aUnit: TKMUnit; aActionType: TKMUnitActionType; aLocked: Boolean);
    constructor Load(LoadStream: TKMemoryStream); virtual;
    procedure SyncLoad; virtual;

    function CanBeInterrupted(aForced: Boolean = True): Boolean; virtual;
    function ActName: TKMUnitActionName; virtual; abstract;
    property ActionType: TKMUnitActionType read fType;
    function GetExplanation: UnicodeString; virtual; abstract;
    function Execute: TKMActionResult; virtual; abstract;
    procedure Save(SaveStream: TKMemoryStream); virtual;
    procedure Paint; virtual;
  end;

  TKMTaskResult = (trTaskContinues, trTaskDone); //There's no difference between Done and Aborted

  TKMUnitTask = class
  protected
    fType: TKMUnitTaskType;
    fUnit: TKMUnit; //Unit who's performing the Task
    fPhase: Byte;
    fPhase2: Byte;
    fLastActionResult: TKMActionResult;
    procedure InitDefaultAction; virtual;
  public
    constructor Create(aUnit: TKMUnit);
    constructor Load(LoadStream: TKMemoryStream); virtual;
    procedure SyncLoad; virtual;
    destructor Destroy; override;

    property Phase: Byte read fPhase write fPhase;
    property TaskType: TKMUnitTaskType read fType;
    function WalkShouldAbandon: Boolean; dynamic;

    function CouldBeCancelled: Boolean; virtual;
    function CanRestartAction(aLastActionResult: TKMActionResult): Boolean; virtual;

    function ObjToString(const aSeparator: String = ', '): String; virtual;

    function Execute: TKMTaskResult; virtual; abstract;
    procedure Save(SaveStream: TKMemoryStream); virtual;

    procedure Paint; virtual;
  end;


  TKMUnit = class
  protected //Accessible for child classes
    fUID: Integer; //unique unit ID, used for save/load to sync to
    fType: TKMUnitType;
    fTask: TKMUnitTask;
    fAction: TKMUnitAction;
    fThought: TKMUnitThought;
    fHitPoints: Byte;
    fHitPointCounter: Cardinal; //Counter for hit point restoration, separate cos it resets on first hit
    fCondition: Integer; //Unit condition, when it reaches zero unit should die (rarely can be negative due to WalkExchange)
    fStartWDefaultCondition: Boolean; //For MapEditor only. Shows if this unit conditions will be set to default at the start of the game
    fTicker: Cardinal; //ticks of life for the unit (allows to spread updates)
    fOwner: TKMHandID;
    fHome: TKMHouse;
    fPositionF: TKMPointF;
    fVisible: Boolean;
    fIsDead: Boolean;
    fKillASAP: Boolean;
    fDismissASAP: Boolean;
    fKillASAPShowAnimation: Boolean;
    fPointerCount: Word;
    fInHouse: TKMHouse; //House we are currently in
    fCurrPosition: TKMPoint; //Where we are now
    fPrevPosition: TKMPoint; //Where we were
    fNextPosition: TKMPoint; //Where we will be. Next tile in route or same tile if stay on place
    fDirection: TKMDirection; //Direction
    fLastTimeTrySetActionWalk: Cardinal; //LastTime we tried to set action walk

    //No saved fields, used only in players UI
    fDismissInProgress: Boolean; //Mark unit as waiting for Dismiss GIC cmd, to show proper UI

    fVisual: TKMUnitVisual;

    function GetDesiredPassability: TKMTerrainPassability;
    function GetHitPointsMax: Byte;
    procedure SetDirection(aValue: TKMDirection);
    procedure SetAction(aAction: TKMUnitAction; aStep: Integer = 0);
    procedure SetNextPosition(const aLoc: TKMPoint);
    procedure SetCurrPosition(const aLoc: TKMPoint);
    procedure SetCondition(aValue: Integer);
    function CanAccessHome: Boolean;
    procedure SetHome(aHome: TKMHouse);
    procedure SetOwner(aOwner: TKMHandID);

    procedure SetThought(aThought: TKMUnitThought);
    procedure SetInHouse(aInHouse: TKMHouse);
    procedure UpdateThoughts;
    function UpdateVisibility: Boolean;
    procedure UpdateHitPoints;
    procedure DoKill(aShowAnimation: Boolean);
    procedure DoDismiss;

    procedure UpdateLastTimeTrySetActionWalk;
  public
    AnimStep: Integer;
    IsExchanging: Boolean; //Current walk is an exchange, used for sliding
    OnUnitDied: TKMUnitFromEvent;
    OnUnitTrained: TKMUnitEvent;

    HitPointsInvulnerable: Boolean;
    Dismissable: Boolean; //Is it allowed to dismiss this unit ?

    constructor Create(aID: Cardinal; aUnitType: TKMUnitType; const aLoc: TKMPoint; aOwner: TKMHandID; aInHouse: Boolean);
    constructor Load(LoadStream: TKMemoryStream); dynamic;
    procedure SyncLoad; virtual;
    destructor Destroy; override;

    function GetUnitPointer: TKMUnit; //Returns self and adds one to the pointer counter
    procedure ReleaseUnitPointer;  //Decreases the pointer counter
    property GetPointerCount: Word read fPointerCount;

    //Creates TTaskDie which then will Close the unit from further access
    procedure Kill(aFrom: TKMHandID; aShowAnimation, aForceDelay: Boolean); virtual;

    //Creates TTaskDismiss
    procedure Dismiss; virtual;
    procedure DismissCancel; virtual;
    //Could be used only in UI
    procedure DismissStarted;
    property DismissInProgress: Boolean read fDismissInProgress write fDismissInProgress;

    procedure CloseUnit(aRemoveTileUsage: Boolean = True); dynamic;

    property UID: Integer read fUID;
    property CurrPosition: TKMPoint read fCurrPosition;
    property PositionF: TKMPointF read fPositionF write fPositionF;
    property PrevPosition: TKMPoint read fPrevPosition;
    property NextPosition: TKMPoint read fNextPosition write SetNextPosition;
    procedure SetPosition(aPos: TKMPoint);

    property Direction: TKMDirection read fDirection write SetDirection;
    property CurrentHitPoints: Byte read fHitPoints;

    function HitTest(X,Y: Integer; const UT: TKMUnitType = utAny): Boolean;

    procedure SetActionAbandonWalk(const aLocB: TKMPoint; aActionType: TKMUnitActionType = uaWalk);
    procedure SetActionGoIn(aAction: TKMUnitActionType; aGoDir: TKMGoInDirection; aHouse: TKMHouse); virtual;
    procedure SetActionStay(aTimeToStay: Integer; aAction: TKMUnitActionType; aStayStill: Boolean = True; aStillFrame: Byte = 0; aStep: Integer = 0);
    procedure SetActionStorm(aRow: Integer);
    procedure SetActionSteer;
    procedure SetActionLockedStay(aTimeToStay: Integer; aAction: TKMUnitActionType; aStayStill: Boolean=true; aStillFrame: Byte = 0; aStep: Integer=0);

    procedure SetActionWalk(const aLocB: TKMPoint; aActionType: TKMUnitActionType; aDistance: Single; aTargetUnit: TKMUnit;
                            aTargetHouse: TKMHouse; aAvoidLockedByMovementCost: Boolean = True);
    function TrySetActionWalk(const aLocB: TKMPoint; aActionType: TKMUnitActionType; aDistance: Single; aTargetUnit: TKMUnit;
                              aTargetHouse: TKMHouse; aAvoidLockedByMovementCost: Boolean = True;
                              aSilent: Boolean = True): Boolean;
    procedure SetActionWalkToHouse(aHouse: TKMHouse; aDistance: Single; aActionType: TKMUnitActionType = uaWalk);
    procedure SetActionWalkFromHouse(aHouse: TKMHouse; aDistance: Single; aActionType: TKMUnitActionType = uaWalk);
    procedure SetActionWalkToUnit(aUnit: TKMUnit; aDistance:single; aActionType: TKMUnitActionType = uaWalk);
    procedure SetActionWalkFromUnit(aUnit: TKMUnit; aDistance: Single; aActionType: TKMUnitActionType = uaWalk);
    procedure SetActionWalkToSpot(const aLocB: TKMPoint; aActionType: TKMUnitActionType = uaWalk; aDistance: Single = 0);
    procedure SetActionWalkToRoad(aActionType: TKMUnitActionType = uaWalk; aDistance: Single = 0;
                                         aTargetPassability: TKMTerrainPassability = tpWalkRoad; aTargetWalkConnectSet: TKMByteSet = []);
    procedure SetActionWalkPushed(const aLocB: TKMPoint; aActionType: TKMUnitActionType = uaWalk);

    procedure Feed(Amount: Single);
    function IsHungry: Boolean;
    procedure AbandonWalk;
    property  DesiredPassability: TKMTerrainPassability read GetDesiredPassability;
    property  Owner: TKMHandID read fOwner write SetOwner;

    property  Home: TKMHouse read fHome write SetHome;
    property  Action: TKMUnitAction read fAction;
    property  Task: TKMUnitTask read fTask;
    property  UnitType: TKMUnitType read fType;
    function  GetActionText: UnicodeString;
    property  Condition: Integer read fCondition write SetCondition;
    property  StartWDefaultCondition: Boolean read fStartWDefaultCondition write fStartWDefaultCondition;

    procedure OwnerUpdate(aOwner: TKMHandID; aMoveToNewOwner: Boolean = False);
    procedure HitPointsChangeFromScript(aAmount: Integer);
    procedure HitPointsDecrease(aAmount: Byte; aAttacker: TKMUnit);
    property  HitPointsMax: Byte read GetHitPointsMax;
    procedure CancelTask(aFreeTaskObject: Boolean = True);
    property  Visible: Boolean read fVisible write fVisible;
    property  InHouse: TKMHouse read fInHouse write SetInHouse;
    property  IsDead: Boolean read fIsDead;
    function  IsDeadOrDying: Boolean;
    function  IsDismissing: Boolean;
    function  IsDismissAvailable: Boolean;
    function  IsDismissCancelAvailable: Boolean;

    property  Thought: TKMUnitThought read fThought write SetThought;
    function  GetMovementVector: TKMPointF;
    function  IsIdle: Boolean;
    procedure TrainInHouse(aSchool: TKMHouseSchool);

    function CanStepTo(X,Y: Integer; aPass: TKMTerrainPassability): Boolean;
    function CanWalkTo(const aTo: TKMPoint; aDistance: Single): Boolean; overload;
    function CanWalkTo(const aTo: TKMPoint; aPass: TKMTerrainPassability; aDistance: Single): Boolean; overload;
    function CanWalkTo(const aFrom, aTo: TKMPoint; aDistance: Single): Boolean; overload;
    function CanWalkTo(const aFrom, aTo: TKMPoint; aPass: TKMTerrainPassability; aDistance: Single): Boolean; overload;
    function CanWalkTo(const aFrom: TKMPoint; aHouse: TKMHouse; aPass: TKMTerrainPassability; aDistance: Single): Boolean; overload;
    function CanWalkDiagonaly(const aFrom, aTo: TKMPoint): Boolean;
    procedure VertexRem(const aLoc: TKMPoint);
    function  VertexUsageCompatible(const aFrom, aTo: TKMPoint): Boolean;
    procedure VertexAdd(const aFrom, aTo: TKMPoint);
    procedure Walk(const aFrom, aTo: TKMPoint);
    function GetActivityText: UnicodeString; virtual;
    function GetSlide(aCheck: TKMCheckAxis): Single;
    function PathfindingShouldAvoid: Boolean; virtual;

    function ObjToString(const aSeparator: String = '|'): String; virtual;
    function ObjToStringShort(const aSeparator: String = '|'): String; virtual;

    procedure Save(SaveStream: TKMemoryStream); virtual;
    function UpdateState: Boolean; virtual;
    procedure Paint; virtual;

    class function GetDefaultCondition: Integer;
  end;

  //Common class for all civil units
  TKMCivilUnit = class(TKMUnit)
  public
    function GoEat(aInn: TKMHouseInn; aUnitAlreadyInsideInn: Boolean = False): Boolean;
    function CheckCondition: Boolean;
    procedure KillInHouse;
  end;

  //This is common class for all units, who can be an owner for house (recruits and all citizen workers)
  TKMSettledUnit = class(TKMCivilUnit)
  private
    procedure CleanHousePointer(aFreeAndNilTask: Boolean = False);
  protected
    function InitiateActivity: TKMUnitTask; virtual; abstract;
    function FindHome: Boolean;
    procedure ProceedHouseClosedForWorker;
  public
    function UpdateState: Boolean; override;
  end;

  //This is a common class for all units, who can work in house
  TKMUnitCitizen = class(TKMSettledUnit)
  protected
    function InitiateActivity: TKMUnitTask; override;
  public
    function CanWorkAt(aLoc: TKMPoint; aGatheringScript: TKMGatheringScript): Boolean;
    function GetActivityText: UnicodeString; override;
    procedure Paint; override;
  end;


  TKMUnitRecruit = class(TKMSettledUnit)
  protected
    function InitiateActivity: TKMUnitTask; override;
  public
    procedure Paint; override;
  end;

  //Serf - transports all wares between houses
  TKMUnitSerf = class(TKMCivilUnit)
  private
    fCarry: TKMWareType;
  public
    constructor Create(aID: Cardinal; aUnitType: TKMUnitType; const aLoc: TKMPoint; aOwner: TKMHandID; aInHouse: Boolean);
    constructor Load(LoadStream: TKMemoryStream); override;
    procedure Save(SaveStream: TKMemoryStream); override;

    procedure Deliver(aFrom: TKMHouse; toHouse: TKMHouse; Res: TKMWareType; aID: integer); overload;
    procedure Deliver(aFrom: TKMHouse; toUnit: TKMUnit; Res: TKMWareType; aID: integer); overload;
    function TryDeliverFrom(aFrom: TKMHouse): Boolean;
    procedure DelegateDelivery(aToSerf: TKMUnitSerf);

    property Carry: TKMWareType read fCarry;
    procedure CarryGive(Res: TKMWareType);
    procedure CarryTake;

    function ObjToString(const aSeparator: String = '|'): String; override;

    function UpdateState: Boolean; override;
    procedure Paint; override;
  end;

  //Worker class - builds everything in game
  TKMUnitWorker = class(TKMCivilUnit)
  public
    procedure BuildHouse(aHouse: TKMHouse; aIndex: Integer);
    procedure BuildHouseRepair(aHouse: TKMHouse; aIndex: Integer);
    procedure BuildField(aField: TKMFieldType; aLoc: TKMPoint; aIndex: Integer);
    procedure BuildHouseArea(aHouseType: TKMHouseType; aLoc: TKMPoint; aIndex: Integer);
    function PickRandomSpot(aList: TKMPointDirList; out Loc: TKMPointDir): Boolean;

    function UpdateState: Boolean; override;
    procedure Paint; override;
  end;


  //Animals
  TKMUnitAnimal = class(TKMUnit)
  private
    fFishCount: Byte; //1-5
  public
    constructor Create(aID: Cardinal; aUnitType: TKMUnitType; const aLoc: TKMPoint; aOwner: TKMHandID); overload;
    constructor Load(LoadStream: TKMemoryStream); override;
    property FishCount: byte read fFishCount;
    function ReduceFish: Boolean;
    procedure Save(SaveStream: TKMemoryStream); override;
    function UpdateState: Boolean; override;
    procedure Paint; override;
  end;

const
  TRY_SET_ACTION_WALK_FREQ = 40; //in ticks. How often do we allow to try set action walk


implementation
uses
  TypInfo,
  KM_Game, KM_GameApp, KM_RenderPool, KM_RenderAux, KM_ResTexts, KM_ScriptingEvents,
  KM_HandsCollection, KM_UnitWarrior, KM_Resource, KM_ResUnits,
  KM_Hand, KM_MapEditorHistory,

  KM_UnitActionAbandonWalk,
  KM_UnitActionFight,
  KM_UnitActionGoInOut,
  KM_UnitActionStay,
  KM_UnitActionSteer,
  KM_UnitActionStormAttack,
  KM_UnitActionWalkTo,

  KM_UnitTaskAttackHouse,
  KM_UnitTaskBuild,
  KM_UnitTaskDelivery,
  KM_UnitTaskDie,
  KM_UnitTaskGoEat,
  KM_UnitTaskGoHome,
  KM_UnitTaskDismiss,
  KM_UnitTaskGoOutShowHungry,
  KM_UnitTaskMining,
  KM_UnitTaskSelfTrain,
  KM_UnitTaskThrowRock,
  KM_GameTypes;


{ TKMCivilUnit }
function TKMCivilUnit.CheckCondition: Boolean;
var
  H: TKMHouseInn;
begin
  Result := False;
  if IsHungry then
  begin
    H := gHands[fOwner].FindInn(fCurrPosition, Self, InHouse <> nil);
    Result := GoEat(H);
  end;
end;


procedure TKMCivilUnit.KillInHouse;
begin
  Assert(fInHouse <> nil, 'Unit could be silently killed only inside some House');
  //Dispose of current action/task BEFORE we close the unit (action might need to check fPosition if recruit was about to walk out to eat)
  //Normally this isn't required because TTaskDie takes care of it all, but recruits in barracks don't use TaskDie.
  SetAction(nil);
  FreeAndNil(fTask);

  CloseUnit(False); //Don't remove tile usage, we are inside the barracks
end;


function TKMCivilUnit.GoEat(aInn: TKMHouseInn; aUnitAlreadyInsideInn: Boolean = False): Boolean;
begin
  Result := False;
  if aInn <> nil then
  begin
    FreeAndNil(fTask);
    fTask := TKMTaskGoEat.Create(aInn, Self);
    if aUnitAlreadyInsideInn then
      fTask.Phase := 3; //We are inside Inn already
    Result := True;
  end;
end;


{ TKMSettledUnit }
//Find home for settled unit
function TKMSettledUnit.FindHome: Boolean;
var H: TKMHouse;
begin
  Result:=false;
  H := gHands[fOwner].Houses.FindEmptyHouse(fType, fCurrPosition);
  if H <> nil then
  begin
    fHome  := H.GetHousePointer;
    Result := true;
  end;
end;


procedure TKMSettledUnit.CleanHousePointer(aFreeAndNilTask: Boolean = False);
begin
  if aFreeAndNilTask then
    FreeAndNil(fTask);
  fHome.HasOwner := False;
  gHands.CleanUpHousePointer(fHome);
end;


// Manage house is closed for worker process
procedure TKMSettledUnit.ProceedHouseClosedForWorker;
var
  wGoingInsideHouse: Boolean;
  wThrowingRock: Boolean;
  wGoingForEating: Boolean;
  wWentOutShowHungry: Boolean;
  wWantToGoOutShowHungry: Boolean;
  wWalkingOutside: Boolean;
  wWorkingOutsideHouse: Boolean;
  wHasNoTask: Boolean;
  wIsInsideHouse: Boolean;
begin
  if (fHome <> nil)
    and not fHome.IsDestroyed
    and (fHome.IsClosedForWorker or ((fHome.HouseType = htBarracks) and (TKMHouseBarracks(fHome).NotAcceptRecruitFlag)))
    and not (fTask is TKMTaskDie)
    and not (fTask is TKMTaskDismiss) then
    begin
      wGoingInsideHouse := (fAction is TKMUnitActionGoInOut) and ((TKMUnitActionGoInOut(fAction)).Direction = gdGoInside);
      // let recruits finish throwing animation
      wThrowingRock := (fTask is TKMTaskThrowRock) and (fHome.GetState in [hstWork]);
      // do not cancel eating task
      wGoingForEating := (fTask is TKMTaskGoEat);
      // Assume worker is inside the house if not Visible.
      wIsInsideHouse := not Visible;
      // cancel GoOutShowHungry task if we outside of the house
      wWentOutShowHungry := (fTask is TKMTaskGoOutShowHungry) and not wIsInsideHouse;
      // cancel GoOutShowHungry task and go out of the house, if we inside of it
      wWantToGoOutShowHungry := (fTask is TKMTaskGoOutShowHungry) and wIsInsideHouse;
      // We are on the way to somewhere. AbandonWalk 'n cancel task.
      wWalkingOutside := (fAction is TKMUnitActionWalkTo) and not TKMUnitActionWalkTo(fAction).DoingExchange;
      // Working outside
      wWorkingOutsideHouse := (fTask is TKMTaskMining) and not wIsInsideHouse;
      // Somehow no task
      wHasNoTask := (fTask = nil);
      if (not wThrowingRock) then       // Let recruit finish rock throwing
        if (wGoingForEating) then
        begin
          CleanHousePointer;            // Clean house pointer, do not cancel eating task
        end else begin
          if (wWalkingOutside) then begin
            AbandonWalk;                // Stop walking
            CleanHousePointer(True);    // Clean house pointer and free task
          end else
          if not wGoingInsideHouse and  // Let worker get into the house
            ((wWentOutShowHungry or wWorkingOutsideHouse) // When already outside the house
            // Not sure we need this
            or (wHasNoTask and not wIsInsideHouse)) then  // Or has no task outside the house.
          begin
            CleanHousePointer(True);    // Clean house pointer and free task
          end else
          if (wIsInsideHouse or wWantToGoOutShowHungry)
            and not (fHome.HouseType = htBarracks) then // Recruits should not go out of Barracks
          begin
            SetActionGoIn(uaWalk, gdGoOutside, fHome); //Walk outside the house
            // If working inside - first we need to set house state to Idle, then to Empty
            fHome.SetState(hstIdle);
            fHome.SetState(hstEmpty);
            CleanHousePointer(True)     // Clean house pointer and free task
          end;
        end;
    end;
end;


function TKMSettledUnit.UpdateState: Boolean;
begin
  Result := True;
  if fAction = nil then
    raise ELocError.Create(gRes.Units[UnitType].GUIName + ' has no action at start of TKMSettledUnit.UpdateState', fCurrPosition);

  //Reset unit activity if home was destroyed, except when unit is dying or eating (finish eating/dying first)
  if (fHome <> nil)
    and fHome.IsDestroyed
    and not(fTask is TKMTaskDie)
    and not(fTask is TKMTaskGoEat) then
  begin
    if (fAction is TKMUnitActionWalkTo)
      and not TKMUnitActionWalkTo(Action).DoingExchange then
      AbandonWalk;
    FreeAndNil(fTask);
    gHands.CleanUpHousePointer(fHome);
  end;

  ProceedHouseClosedForWorker;

  if inherited UpdateState then Exit;
  if IsDead then Exit; //Caused by SelfTrain.Abandoned

  fThought := thNone;

  if IsHungry
    and not CheckCondition
    and (fHome <> nil)
    and not fVisible then
  begin
    if fTask <> nil then
      FreeAndNil(fTask);
    fTask := TKMTaskGoOutShowHungry.Create(Self);
  end;

  if fTask = nil then //If Unit still got nothing to do, nevermind hunger
    if fHome = nil then
      if FindHome then
        fTask := TKMTaskGoHome.Create(Self) //Home found - go there
      else begin
        fThought := thQuest; //Always show quest when idle, unlike serfs who randomly show it
        SetActionStay(20, uaWalk) //There's no home, but there will hopefully be one soon so don't sleep too long
      end
    else
      if fVisible then //Unit is not at home, but it has one
      begin
        if CanAccessHome then
          fTask := TKMTaskGoHome.Create(Self)
        else
          SetActionStay(60, uaWalk) //Home can't be reached
      end else begin

        if not (fHome.HouseType in HOUSE_WORKSHOP)
          or (fHome.CheckResOut(wtAll) < MAX_WARES_OUT_WORKSHOP) then //Do not do anything if we have too many ready resources
          fTask := InitiateActivity; //Unit is at home, so go get a job

        if fTask = nil then //We didn't find any job to do - rest at home
          SetActionStay(Max(gRes.Houses[fHome.HouseType].WorkerRest,1)*10, uaWalk); //By default it's 0, don't scan that often
      end;

  if fAction = nil then
    raise ELocError.Create(gRes.Units[UnitType].GUIName + ' has no action at end of TKMSettledUnit.UpdateState',fCurrPosition);

  Result := False;
end;


{ TKMUnitCitizen }
procedure TKMUnitCitizen.Paint;
var
  Act: TKMUnitActionType;
  XPaintPos, YPaintPos: Single;
  ID: Integer;
begin
  inherited;
  if not fVisible then exit;
  if fAction = nil then exit;
  Act := fAction.fType;

  XPaintPos := fPositionF.X + UNIT_OFF_X + GetSlide(axX);
  YPaintPos := fPositionF.Y + UNIT_OFF_Y + GetSlide(axY);

  ID := fUID * Byte(not (fAction.fType in [uaDie, uaEat]));

  case fAction.fType of
    uaWalk:
      begin
        gRenderPool.AddUnit(fType, ID, uaWalk, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, true);
        if gRes.Units[fType].SupportsAction(uaWalkArm) then
          gRenderPool.AddUnit(fType, ID, uaWalkArm, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, false);
      end;
    uaWork..uaEat:
        gRenderPool.AddUnit(fType, ID, Act, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, true);
    uaWalkArm .. uaWalkBooty2:
      begin
        gRenderPool.AddUnit(fType, ID, uaWalk, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, true);
        gRenderPool.AddUnit(fType, ID, Act, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, false);
      end;
  end;

  if fThought <> thNone then
    gRenderPool.AddUnitThought(fType, Act, Direction, fThought, XPaintPos, YPaintPos);
end;


function TKMUnitCitizen.CanWorkAt(aLoc: TKMPoint; aGatheringScript: TKMGatheringScript): Boolean;
var
  I: Integer;
begin
  case aGatheringScript of
    gsWoodCutterPlant: begin
                          //Woodcutters should not plant trees on our own or our ally's house plans
                          //(it's very annoying if they do)
                          Result := True;
                          for I := 0 to gHands.Count - 1 do
                            if gHands[fOwner].Alliances[I] = atAlly then
                              Result := Result and not gHands[I].BuildList.HousePlanList.HasPlan(aLoc);
                        end;
    else Result := True;
  end;
end;


function TKMUnitCitizen.GetActivityText: UnicodeString;
begin
  Result := Inherited GetActivityText; //Default
  if fTask is TKMTaskMining then
    Result := TKMTaskMining(fTask).GetActivityText;
end;


function TKMUnitCitizen.InitiateActivity: TKMUnitTask;
var
  Res: Integer;
  TM: TKMTaskMining;
begin
  Result := nil;

  if not KMSamePoint(fCurrPosition, fHome.Entrance) then
    raise ELocError.Create('Mining from wrong spot', fCurrPosition);

  Res := 1;
  //Check if House has production orders
  //Ask the house what order we should make
  if gRes.Houses[fHome.HouseType].DoesOrders then
  begin
    Res := fHome.PickOrder;
    if Res = 0 then Exit;
  end;

  // Don't bother creating a task if there's no room for resulting ware
  // Saves us time on Fishers/Stonecutters/Woodcutters when they calculate routes to nearby deposits
  // Other houses where workers walk out can choose between cut/plant
  if (fHome.HouseType in [htFisherHut, htQuary, htWineyard])
  and (fHome.CheckResOut(gRes.Houses[fHome.HouseType].ResOutput[Res]) >= MAX_WARES_IN_HOUSE) then
    Exit;

  TM := TKMTaskMining.Create(Self, gRes.Houses[fHome.HouseType].ResOutput[Res]);

  if TM.WorkPlan.ResourceDepleted then
  begin
    if not fHome.ResourceDepleted then
      fHome.IssueResourceDepletedMsg;
  end
  else
    //Reset resource depleted state for home, if we find any resource
    //(it could appear again, f.e. by script)
    fHome.ResourceDepleted := False;

  if TM.WorkPlan.IsIssued
    and ((TM.WorkPlan.Resource1 = wtNone) or (fHome.CheckResIn(TM.WorkPlan.Resource1) >= TM.WorkPlan.Count1))
    and ((TM.WorkPlan.Resource2 = wtNone) or (fHome.CheckResIn(TM.WorkPlan.Resource2) >= TM.WorkPlan.Count2))
    and (fHome.CheckResOut(TM.WorkPlan.Product1) < MAX_WARES_IN_HOUSE)
    and (fHome.CheckResOut(TM.WorkPlan.Product2) < MAX_WARES_IN_HOUSE) then
  begin
    //if fResource.HouseDat[fHome.HouseType].DoesOrders then
      //Take order to production
      //fHome.ResOrder[Res] := fHome.ResOrder[Res] - 1;
    Result := TM;
  end
  else
  begin
    TM.Free;
    Result := nil;
  end;
end;


{ TKMUnitRecruit }
procedure TKMUnitRecruit.Paint;
var
  Act: TKMUnitActionType;
  XPaintPos, YPaintPos: Single;
  ID: Integer;
begin
  inherited;
  if not fVisible then exit;
  if fAction = nil then exit;
  Act := fAction.fType;

  XPaintPos := fPositionF.X + UNIT_OFF_X + GetSlide(axX);
  YPaintPos := fPositionF.Y + UNIT_OFF_Y + GetSlide(axY);

  ID := fUID * Byte(not (fAction.fType in [uaDie, uaEat]));

  case fAction.fType of
    uaWalk:
      begin
        gRenderPool.AddUnit(fType, ID, uaWalk, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, true);
        if gRes.Units[fType].SupportsAction(uaWalkArm) then
          gRenderPool.AddUnit(fType, ID, uaWalkArm, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, false);
      end;
    uaWork..uaEat:
        gRenderPool.AddUnit(fType, ID, Act, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, true);
    uaWalkArm .. uaWalkBooty2:
      begin
        gRenderPool.AddUnit(fType, ID, uaWalk, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, true);
        gRenderPool.AddUnit(fType, ID, Act, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, false);
      end;
  end;

  if fThought <> thNone then
    gRenderPool.AddUnitThought(fType, Act, Direction, fThought, XPaintPos, YPaintPos);
end;


function TKMUnitRecruit.InitiateActivity: TKMUnitTask;
var
  Enemy: TKMUnit;
begin
  Result := nil;

  //See if we are in a tower and have something to throw
  if not (fHome is TKMHouseTower) or (fHome.CheckResIn(wtStone) <= 0) then
    Exit;

  Enemy := gTerrain.UnitsHitTestWithinRad(fCurrPosition, RANGE_WATCHTOWER_MIN, RANGE_WATCHTOWER_MAX, fOwner, atEnemy, dirNA, not RANDOM_TARGETS);

  //Note: In actual game there might be two Towers nearby,
  //both throwing a stone into the same enemy. We should not
  //negate that fact, thats real-life situation.

  if Enemy <> nil then
    Result := TKMTaskThrowRock.Create(Self, Enemy);
end;


{ TKMSerf }
constructor TKMUnitSerf.Create(aID: Cardinal; aUnitType: TKMUnitType; const aLoc: TKMPoint; aOwner: TKMHandID; aInHouse: Boolean);
begin
  inherited;
  fCarry := wtNone;
end;


procedure TKMUnitSerf.Deliver(aFrom, toHouse: TKMHouse; Res: TKMWareType; aID: integer);
begin
  fThought := thNone; //Clear ? thought
  fTask := TKMTaskDeliver.Create(Self, aFrom, toHouse, Res, aID);
end;


procedure TKMUnitSerf.Deliver(aFrom: TKMHouse; toUnit: TKMUnit; Res: TKMWareType; aID: integer);
begin
  fThought := thNone; //Clear ? thought
  fTask := TKMTaskDeliver.Create(Self, aFrom, toUnit, Res, aID);
end;


function TKMUnitSerf.TryDeliverFrom(aFrom: TKMHouse): Boolean;
var
  T: TKMUnitTask;
begin
  Result := False;
  //Save current task
  T := fTask;
  //Try to get a new one
  if gHands[Owner].Deliveries.Queue.AskForDelivery(Self, aFrom) then
  begin
    Result := True;
    FreeAndNil(T); //Destroy old task, we created a new one
  end;

  //If we got ourselves a new task then skip to resource-taking part, as we are already in this house
  if Result and (aFrom <> nil) then
    fTask.Phase := 2; //Skip  of the new task
end;


//Delegate delivery to other serf
procedure TKMUnitSerf.DelegateDelivery(aToSerf: TKMUnitSerf);
begin
  Assert(Task is TKMTaskDeliver, 'UnitTask is not TKMTaskDeliver');

  TKMTaskDeliver(Task).DelegateToOtherSerf(aToSerf); //Update task to be used with new serf
  aToSerf.fTask := Task; //Set delivery task to new serf

  //AbandonWalk if needed and cleanup task pointer, but do not free task object, as it was delegated to other serf already
  CancelTask(False);
end;


constructor TKMUnitSerf.Load(LoadStream: TKMemoryStream);
begin
  inherited;
  LoadStream.Read(fCarry, SizeOf(fCarry));
end;


procedure TKMUnitSerf.Paint;
var
  Act: TKMUnitActionType;
  XPaintPos, YPaintPos: Single;
  ID: Integer;
begin
  inherited;
  if not fVisible then exit;
  if fAction = nil then exit;
  Act := fAction.fType;

  XPaintPos := fPositionF.X + UNIT_OFF_X + GetSlide(axX);
  YPaintPos := fPositionF.Y + UNIT_OFF_Y + GetSlide(axY);

  ID := fUID * Byte(not (fAction.fType in [uaDie, uaEat]));

  gRenderPool.AddUnit(UnitType, ID, Act, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, true);

  if fTask is TKMTaskDie then Exit; //Do not show unnecessary arms

  if Carry <> wtNone then
    gRenderPool.AddUnitCarry(Carry, ID, Direction, AnimStep, XPaintPos, YPaintPos)
  else
    gRenderPool.AddUnit(UnitType, ID, uaWalkArm, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, false);

  if fThought <> thNone then
    gRenderPool.AddUnitThought(fType, Act, Direction, fThought, XPaintPos, YPaintPos);
end;


procedure TKMUnitSerf.Save(SaveStream: TKMemoryStream);
begin
  inherited;
  SaveStream.Write(fCarry, SizeOf(fCarry));
end;


function TKMUnitSerf.ObjToString(const aSeparator: String = '|'): String;
begin
  Result := inherited ObjToString(aSeparator)
          + Format('%sCarry = %s', [aSeparator, GetEnumName(TypeInfo(TKMWareType), Integer(fCarry))]);
end;


function TKMUnitSerf.UpdateState: Boolean;
var
  OldThought: TKMUnitThought;
  WasIdle: Boolean;
begin
  Result := True; //Required for override compatibility
  WasIdle := IsIdle;
  if fAction = nil then raise ELocError.Create(gRes.Units[UnitType].GUIName+' has no action at start of TKMUnitSerf.UpdateState',fCurrPosition);
  if inherited UpdateState then
    Exit;

  OldThought := fThought;
  fThought := thNone;

  CheckCondition;

  //Only show quest thought if we have been idle since the last update (not HadTask)
  //and not thinking anything else (e.g. death)
  if fTask = nil then
  begin
    if WasIdle and (OldThought = thNone) and (KaMRandom(2, 'TKMUnitSerf.UpdateState') = 0) then
      fThought := thQuest;
    SetActionStay(60,uaWalk); //Stay idle
  end;

  if fAction = nil then
    raise ELocError.Create(gRes.Units[UnitType].GUIName+' has no action at end of TKMUnitSerf.UpdateState', fCurrPosition);
end;


procedure TKMUnitSerf.CarryGive(Res:TKMWareType);
begin
  Assert(fCarry = wtNone, 'Giving Serf another Carry');
  fCarry := Res;
end;


procedure TKMUnitSerf.CarryTake;
begin
  Assert(Carry <> wtNone, 'Taking wrong ware from Serf');
  fCarry := wtNone;
end;


{ TKMWorker }
procedure TKMUnitWorker.BuildHouse(aHouse: TKMHouse; aIndex: Integer);
begin
  fTask := TKMTaskBuildHouse.Create(Self, aHouse, aIndex);
end;


procedure TKMUnitWorker.BuildField(aField: TKMFieldType; aLoc: TKMPoint; aIndex: Integer);
begin
  case aField of
    ftRoad: fTask := TKMTaskBuildRoad.Create(Self, aLoc, aIndex);
    ftCorn: fTask := TKMTaskBuildField.Create(Self, aLoc, aIndex);
    ftWine: fTask := TKMTaskBuildWine.Create(Self, aLoc, aIndex);
  else
    raise Exception.Create('Unexpected TKMFieldType');
  end;
end;


procedure TKMUnitWorker.BuildHouseArea(aHouseType: TKMHouseType; aLoc: TKMPoint; aIndex: Integer);
begin
  fTask := TKMTaskBuildHouseArea.Create(Self, aHouseType, aLoc, aIndex);
end;


procedure TKMUnitWorker.BuildHouseRepair(aHouse: TKMHouse; aIndex: Integer);
begin
  fTask := TKMTaskBuildHouseRepair.Create(Self, aHouse, aIndex);
end;


//Given a list check the locations in it and pick those that can be walked to
//excluding current location. We assume that caller already made the list
//with only walkable valid tiles
function TKMUnitWorker.PickRandomSpot(aList: TKMPointDirList; out Loc: TKMPointDir): Boolean;
var
  I, MyCount: Integer;
  Spots: array of Word;
begin
  SetLength(Spots, aList.Count);

  //Scan the list and pick suitable locations
  MyCount := 0;
  for I := 0 to aList.Count - 1 do
  if not KMSamePoint(aList[I].Loc, CurrPosition)
  and CanWalkTo(aList[I].Loc, 0) then
  begin
    Spots[MyCount] := I;
    Inc(MyCount);
  end;

  Result := (MyCount > 0);
  if Result then
    Loc := aList[Spots[KaMRandom(MyCount, 'TKMUnitWorker.PickRandomSpot')]];
end;


procedure TKMUnitWorker.Paint;
var
  XPaintPos, YPaintPos: Single;
  ID: Integer;
begin
  inherited;
  if not fVisible then exit;
  if fAction = nil then exit;

  XPaintPos := fPositionF.X + UNIT_OFF_X + GetSlide(axX);
  YPaintPos := fPositionF.Y + UNIT_OFF_Y + GetSlide(axY);

  ID := fUID * Byte(not (fAction.fType in [uaDie, uaEat]));

  gRenderPool.AddUnit(UnitType, ID, fAction.fType, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, true);

  if fThought <> thNone then
    gRenderPool.AddUnitThought(fType, fAction.ActionType, Direction, fThought, XPaintPos, YPaintPos);
end;


function TKMUnitWorker.UpdateState: Boolean;
begin
  Result := True; //Required for override compatibility
  if fAction = nil then
    raise ELocError.Create(gRes.Units[UnitType].GUIName + ' has no action at start of TKMUnitWorker.UpdateState', fCurrPosition);
  if inherited UpdateState then Exit;

  CheckCondition;

  if (fThought = thBuild) and (fTask = nil) then
    fThought := thNone; //Remove build thought if we are no longer doing anything

  //If we are still stuck on a house for some reason, get off it ASAP
  Assert(gTerrain.Land[fCurrPosition.Y, fCurrPosition.X].TileLock <> tlHouse);

  if (fTask = nil) and (fAction = nil) then SetActionStay(20, uaWalk);

  if fAction=nil then raise ELocError.Create(gRes.Units[UnitType].GUIName+' has no action at end of TKMUnitWorker.UpdateState',fCurrPosition);
end;


{ TKMUnitAnimal }
constructor TKMUnitAnimal.Create(aID: Cardinal; aUnitType: TKMUnitType; const aLoc: TKMPoint; aOwner: TKMHandID);
begin
  inherited Create(aID, aUnitType, aLoc, aOwner, False);

  //Always start with 5 fish in the group
  if aUnitType = utFish then
    fFishCount := 5;
end;


constructor TKMUnitAnimal.Load(LoadStream: TKMemoryStream);
begin
  inherited;
  LoadStream.CheckMarker('UnitAnimal');
  LoadStream.Read(fFishCount);
end;


function TKMUnitAnimal.ReduceFish: Boolean;
begin
  Result := fType = utFish;
  if not Result then Exit;

  if fFishCount > 1 then
    Dec(fFishCount)
  else
    Kill(PLAYER_NONE, True, False);
end;


procedure TKMUnitAnimal.Save(SaveStream: TKMemoryStream);
begin
  inherited;
  SaveStream.PlaceMarker('UnitAnimal');
  SaveStream.Write(fFishCount);
end;


function TKMUnitAnimal.UpdateState: Boolean;
begin
  Result := True; //Required for override compatibility

  SetCurrPosition(KMPointRound(fPositionF));

  if fAction = nil then
    raise ELocError.Create(gRes.Units[UnitType].GUIName + ' has no action at start of TKMUnitAnimal.UpdateState', fCurrPosition);

  if fKillASAP then
  begin
    DoKill(fKillASAPShowAnimation);
    fKillASAP := False;
    Exit;
  end;

  case fAction.Execute of
    arActContinues: Exit;
    else            FreeAndNil(fAction);
  end;
  SetCurrPosition(KMPointRound(fPositionF));


  Assert((fTask = nil) or (fTask is TKMTaskDie));
  if fTask is TKMTaskDie then
  case fTask.Execute of
    trTaskContinues:  exit;
    trTaskDone:       raise Exception.Create('Unexpected fUnitTask.Execute value = trTaskDone'); //TTaskDie never returns trTaskDone yet
  end;

  //First make sure the animal isn't stuck (check passibility of our position)
  if (not gTerrain.CheckPassability(fCurrPosition, DesiredPassability))
  or gTerrain.CheckAnimalIsStuck(fCurrPosition, DesiredPassability) then
  begin
    Kill(PLAYER_NONE, True, False); //Animal is stuck so it dies
    Exit;
  end;

  SetActionSteer;

  if fAction = nil then
    raise ELocError.Create(gRes.Units[UnitType].GUIName + ' has no action at end of TKMUnitAnimal.UpdateState', fCurrPosition);
end;


//For fish the action is the number of fish in the group
procedure TKMUnitAnimal.Paint;
var
  Act: TKMUnitActionType;
  XPaintPos, YPaintPos: single;
begin
  inherited;
  if fAction = nil then exit;

  Assert((fType <> utFish) or (InRange(fFishCount, 1, 5)));

  if fType = utFish then
    Act := FishCountAct[fFishCount]
  else
    Act := fAction.fType;

  XPaintPos := fPositionF.X + UNIT_OFF_X + GetSlide(axX);
  YPaintPos := fPositionF.Y + UNIT_OFF_Y + GetSlide(axY);

  //Make fish/watersnakes more visible in the MapEd
  if (gGame.GameMode = gmMapEd) and (fType in [utFish, utWatersnake, utSeastar]) then
    gRenderAux.Circle(fPositionF.X - 0.5,
                      gTerrain.FlatToHeight(fPositionF.X - 0.5, fPositionF.Y - 0.5),
                      0.5, $30FF8000, $60FF8000);

  //Animals share the same WalkTo logic as other units and they exchange places if necessary
  //Animals can be picked only in MapEd
  gRenderPool.AddUnit(fType, fUID * Byte(gGame.IsMapEditor), Act, Direction, AnimStep, XPaintPos, YPaintPos, $FFFFFFFF, True);
end;


{ TKMUnit }
constructor TKMUnit.Create(aID: Cardinal; aUnitType: TKMUnitType; const aLoc: TKMPoint; aOwner: TKMHandID; aInHouse: Boolean);
begin
  inherited Create;
  fUID          := aID;
  fTicker       := fUID; //Units update states will be spread more evenly that way
  fPointerCount := 0;
  fIsDead       := false;
  fKillASAP     := false;
  fThought      := thNone;
  fHome         := nil;
  fInHouse      := nil;
  fPositionF     := KMPointF(aLoc);
  fCurrPosition := aLoc;
  fPrevPosition := aLoc; //Init values
  fNextPosition := aLoc; //Init values
  fOwner        := aOwner;
  fType     := aUnitType;
  fDirection    := dirS;
  fVisible      := True;
  IsExchanging  := False;
  AnimStep      := UnitStillFrames[fDirection]; //Use still frame at begining, so units don't all change frame on first tick
  Dismissable   := True;
  fLastTimeTrySetActionWalk := 0;

  //Units start with a random amount of condition ranging from 0.5 to 0.7 (KaM uses 0.6 for all units)
  //By adding the random amount they won't all go eat at the same time and cause crowding, blockages, food shortages and other problems.
  if (gGame <> nil) and (gGame.GameMode <> gmMapEd) then
    fCondition    := Round(UNIT_MAX_CONDITION * (UNIT_CONDITION_BASE + KaMRandomS2(UNIT_CONDITION_RANDOM, 'TKMUnit.Create')))
  else begin
    fCondition    := GetDefaultCondition;
    fStartWDefaultCondition := True;
  end;

  fHitPoints      := HitPointsMax;
  fHitPointCounter := 1;
  HitPointsInvulnerable := False;

  SetActionLockedStay(10, uaWalk); //Must be locked for this initial pause so animals don't get pushed

  fVisual := TKMUnitVisual.Create(Self);

  // Do not add units which are trained inside house
  if not aInHouse then
    gTerrain.UnitAdd(NextPosition, Self);

  //The area around the unit should be visible at the start of the mission
  if InRange(fOwner, 0, MAX_HANDS - 1) then //Not animals
    gHands.RevealForTeam(fOwner, fCurrPosition, gRes.Units[fType].Sight, FOG_OF_WAR_MAX);
end;


destructor TKMUnit.Destroy;
begin
  if not IsDead then gTerrain.UnitRem(NextPosition); //Happens only when removing player from map on GameStart (network)
  FreeAndNil(fAction);
  FreeAndNil(fTask);
  FreeAndNil(fVisual);
  SetInHouse(nil); //Free pointer
  inherited;
end;


constructor TKMUnit.Load(LoadStream: TKMemoryStream);
var
  HasTask, HasAct: Boolean;
  TaskName: TKMUnitTaskType;
  ActName: TKMUnitActionName;
begin
  inherited Create;
  LoadStream.CheckMarker('Unit');
  LoadStream.Read(fType, SizeOf(fType));
  LoadStream.Read(HasTask);
  if HasTask then
  begin
    LoadStream.Read(TaskName, SizeOf(TaskName));
    case TaskName of
      uttUnknown:         raise Exception.Create('TaskName can''t be handled');
      uttSelfTrain:       fTask := TKMTaskSelfTrain.Load(LoadStream);
      uttDeliver:         fTask := TKMTaskDeliver.Load(LoadStream);
      uttBuildRoad:       fTask := TKMTaskBuildRoad.Load(LoadStream);
      uttBuildWine:       fTask := TKMTaskBuildWine.Load(LoadStream);
      uttBuildField:      fTask := TKMTaskBuildField.Load(LoadStream);
      uttBuildHouseArea:  fTask := TKMTaskBuildHouseArea.Load(LoadStream);
      uttBuildHouse:      fTask := TKMTaskBuildHouse.Load(LoadStream);
      uttBuildHouseRepair:fTask := TKMTaskBuildHouseRepair.Load(LoadStream);
      uttGoHome:          fTask := TKMTaskGoHome.Load(LoadStream);
      uttDismiss:         fTask := TKMTaskDismiss.Load(LoadStream);
      uttAttackHouse:     fTask := TKMTaskAttackHouse.Load(LoadStream);
      uttThrowRock:       fTask := TKMTaskThrowRock.Load(LoadStream);
      uttGoEat:           fTask := TKMTaskGoEat.Load(LoadStream);
      uttMining:          fTask := TKMTaskMining.Load(LoadStream);
      uttDie:             fTask := TKMTaskDie.Load(LoadStream);
      uttGoOutShowHungry: fTask := TKMTaskGoOutShowHungry.Load(LoadStream);
    else
      raise Exception.Create('TaskName can''t be handled');
    end;
  end
  else
    fTask := nil;

  LoadStream.Read(HasAct);
  if HasAct then
  begin
    LoadStream.Read(ActName, SizeOf(ActName));
    case ActName of
      uanStay:        fAction := TKMUnitActionStay.Load(LoadStream);
      uanWalkTo:      fAction := TKMUnitActionWalkTo.Load(LoadStream);
      uanAbandonWalk: fAction := TKMUnitActionAbandonWalk.Load(LoadStream);
      uanGoInOut:     fAction := TKMUnitActionGoInOut.Load(LoadStream);
      uanFight:       fAction := TKMUnitActionFight.Load(LoadStream);
      uanStormAttack: fAction := TKMUnitActionStormAttack.Load(LoadStream);
      uanSteer:       fAction := TKMUnitActionSteer.Load(LoadStream);
    else
      raise Exception.Create('ActName can''t be handled');
    end;
  end
  else
    fAction := nil;

  LoadStream.Read(fThought, SizeOf(fThought));
  LoadStream.Read(fCondition);
  LoadStream.Read(fStartWDefaultCondition);
  LoadStream.Read(fTicker);
  LoadStream.Read(fHitPoints);
  LoadStream.Read(fHitPointCounter);
  LoadStream.Read(HitPointsInvulnerable);
  LoadStream.Read(fInHouse, 4);
  LoadStream.Read(fOwner, SizeOf(fOwner));
  LoadStream.Read(fHome, 4); //Substitute it with reference on SyncLoad
  LoadStream.Read(fPositionF);
  LoadStream.Read(fVisible);
  LoadStream.Read(fIsDead);
  LoadStream.Read(fKillASAP);
  LoadStream.Read(fKillASAPShowAnimation);
  LoadStream.Read(IsExchanging);
  LoadStream.Read(fPointerCount);
  LoadStream.Read(fUID);
  LoadStream.Read(AnimStep);
  LoadStream.Read(fDirection);
  LoadStream.Read(fCurrPosition);
  LoadStream.Read(fPrevPosition);
  LoadStream.Read(fNextPosition);
  LoadStream.Read(fDismissASAP);
  LoadStream.Read(Dismissable);
  LoadStream.Read(fLastTimeTrySetActionWalk);
end;


procedure TKMUnit.SyncLoad;
begin
  if fTask <> nil then
    fTask.SyncLoad;

  if fAction <> nil then
    fAction.SyncLoad;

  fHome := gHands.GetHouseByUID(cardinal(fHome));<commit_msg>Create fVisual in Load constructor too<commit_after>unit KM_Units;
{$I KaM_Remake.inc}
interface
uses
  Classes, Math, SysUtils, KromUtils, Types,
  KM_CommonClasses, KM_CommonTypes, KM_Defaults, KM_Points, KM_CommonUtils, KM_UnitVisual,
  KM_Terrain, KM_ResHouses, KM_ResWares, KM_Houses, KM_HouseSchool, KM_HouseBarracks, KM_HouseInn;

//Memo on directives:
//Dynamic - declared and used (overriden) occasionally
//Virtual - declared and used (overriden) always
//Abstract - declared but must be overriden in child classes

type
  TKMUnit = class;
  TKMUnitWorker = class;
  TKMUnitEvent = procedure(aUnit: TKMUnit) of object;
  TKMUnitFromEvent = procedure(aUnit: TKMUnit; aFrom: TKMHandID) of object;

  TKMActionResult = (arActContinues, arActDone, arActAborted, arActCanNotStart);

  TKMUnitArray = array of TKMUnit;

  TKMUnitAction = class
  protected
    fType: TKMUnitActionType;
    fUnit: TKMUnit;
  public
    Locked: Boolean; //Means that unit can't take part in interaction, must stay on its tile
    StepDone: Boolean; //True when single action element is done (unit walked to new tile, single attack loop done)
    constructor Create(aUnit: TKMUnit; aActionType: TKMUnitActionType; aLocked: Boolean);
    constructor Load(LoadStream: TKMemoryStream); virtual;
    procedure SyncLoad; virtual;

    function CanBeInterrupted(aForced: Boolean = True): Boolean; virtual;
    function ActName: TKMUnitActionName; virtual; abstract;
    property ActionType: TKMUnitActionType read fType;
    function GetExplanation: UnicodeString; virtual; abstract;
    function Execute: TKMActionResult; virtual; abstract;
    procedure Save(SaveStream: TKMemoryStream); virtual;
    procedure Paint; virtual;
  end;

  TKMTaskResult = (trTaskContinues, trTaskDone); //There's no difference between Done and Aborted

  TKMUnitTask = class
  protected
    fType: TKMUnitTaskType;
    fUnit: TKMUnit; //Unit who's performing the Task
    fPhase: Byte;
    fPhase2: Byte;
    fLastActionResult: TKMActionResult;
    procedure InitDefaultAction; virtual;
  public
    constructor Create(aUnit: TKMUnit);
    constructor Load(LoadStream: TKMemoryStream); virtual;
    procedure SyncLoad; virtual;
    destructor Destroy; override;

    property Phase: Byte read fPhase write fPhase;
    property TaskType: TKMUnitTaskType read fType;
    function WalkShouldAbandon: Boolean; dynamic;

    function CouldBeCancelled: Boolean; virtual;
    function CanRestartAction(aLastActionResult: TKMActionResult): Boolean; virtual;

    function ObjToString(const aSeparator: String = ', '): String; virtual;

    function Execute: TKMTaskResult; virtual; abstract;
    procedure Save(SaveStream: TKMemoryStream); virtual;

    procedure Paint; virtual;
  end;


  TKMUnit = class
  protected //Accessible for child classes
    fUID: Integer; //unique unit ID, used for save/load to sync to
    fType: TKMUnitType;
    fTask: TKMUnitTask;
    fAction: TKMUnitAction;
    fThought: TKMUnitThought;
    fHitPoints: Byte;
    fHitPointCounter: Cardinal; //Counter for hit point restoration, separate cos it resets on first hit
    fCondition: Integer; //Unit condition, when it reaches zero unit should die (rarely can be negative due to WalkExchange)
    fStartWDefaultCondition: Boolean; //For MapEditor only. Shows if this unit conditions will be set to default at the start of the game
    fTicker: Cardinal; //ticks of life for the unit (allows to spread updates)
    fOwner: TKMHandID;
    fHome: TKMHouse;
    fPositionF: TKMPointF;
    fVisible: Boolean;
    fIsDead: Boolean;
    fKillASAP: Boolean;
    fDismissASAP: Boolean;
    fKillASAPShowAnimation: Boolean;
    fPointerCount: Word;
    fInHouse: TKMHouse; //House we are currently in
    fCurrPosition: TKMPoint; //Where we are now
    fPrevPosition: TKMPoint; //Where we were
    fNextPosition: TKMPoint; //Where we will be. Next tile in route or same tile if stay on place
    fDirection: TKMDirection; //Direction
    fLastTimeTrySetActionWalk: Cardinal; //LastTime we tried to set action walk

    //No saved fields, used only in players UI
    fDismissInProgress: Boolean; //Mark unit as waiting for Dismiss GIC cmd, to show proper UI

    fVisual: TKMUnitVisual;

    function GetDesiredPassability: TKMTerrainPassability;
    function GetHitPointsMax: Byte;
    procedure SetDirection(aValue: TKMDirection);
    procedure SetAction(aAction: TKMUnitAction; aStep: Integer = 0);
    procedure SetNextPosition(const aLoc: TKMPoint);
    procedure SetCurrPosition(const aLoc: TKMPoint);
    procedure SetCondition(aValue: Integer);
    function CanAccessHome: Boolean;
    procedure SetHome(aHome: TKMHouse);
    procedure SetOwner(aOwner: TKMHandID);

    procedure SetThought(aThought: TKMUnitThought);
    procedure SetInHouse(aInHouse: TKMHouse);
    procedure UpdateThoughts;
    function UpdateVisibility: Boolean;
    procedure UpdateHitPoints;
    procedure DoKill(aShowAnimation: Boolean);
    procedure DoDismiss;

    procedure UpdateLastTimeTrySetActionWalk;
  public
    AnimStep: Integer;
    IsExchanging: Boolean; //Current walk is an exchange, used for sliding
    OnUnitDied: TKMUnitFromEvent;
    OnUnitTrained: TKMUnitEvent;

    HitPointsInvulnerable: Boolean;
    Dismissable: Boolean; //Is it allowed to dismiss this unit ?

    constructor Create(aID: Cardinal; aUnitType: TKMUnitType; const aLoc: TKMPoint; aOwner: TKMHandID; aInHouse: Boolean);
    constructor Load(LoadStream: TKMemoryStream); dynamic;
    procedure SyncLoad; virtual;
    destructor Destroy; override;

    function GetUnitPointer: TKMUnit; //Returns self and adds one to the pointer counter
    procedure ReleaseUnitPointer;  //Decreases the pointer counter
    property GetPointerCount: Word read fPointerCount;

    //Creates TTaskDie which then will Close the unit from further access
    procedure Kill(aFrom: TKMHandID; aShowAnimation, aForceDelay: Boolean); virtual;

    //Creates TTaskDismiss
    procedure Dismiss; virtual;
    procedure DismissCancel; virtual;
    //Could be used only in UI
    procedure DismissStarted;
    property DismissInProgress: Boolean read fDismissInProgress write fDismissInProgress;

    procedure CloseUnit(aRemoveTileUsage: Boolean = True); dynamic;

    property UID: Integer read fUID;
    property CurrPosition: TKMPoint read fCurrPosition;
    property PositionF: TKMPointF read fPositionF write fPositionF;
    property PrevPosition: TKMPoint read fPrevPosition;
    property NextPosition: TKMPoint read fNextPosition write SetNextPosition;
    procedure SetPosition(aPos: TKMPoint);

    property Direction: TKMDirection read fDirection write SetDirection;
    property CurrentHitPoints: Byte read fHitPoints;

    function HitTest(X,Y: Integer; const UT: TKMUnitType = utAny): Boolean;

    procedure SetActionAbandonWalk(const aLocB: TKMPoint; aActionType: TKMUnitActionType = uaWalk);
    procedure SetActionGoIn(aAction: TKMUnitActionType; aGoDir: TKMGoInDirection; aHouse: TKMHouse); virtual;
    procedure SetActionStay(aTimeToStay: Integer; aAction: TKMUnitActionType; aStayStill: Boolean = True; aStillFrame: Byte = 0; aStep: Integer = 0);
    procedure SetActionStorm(aRow: Integer);
    procedure SetActionSteer;
    procedure SetActionLockedStay(aTimeToStay: Integer; aAction: TKMUnitActionType; aStayStill: Boolean=true; aStillFrame: Byte = 0; aStep: Integer=0);

    procedure SetActionWalk(const aLocB: TKMPoint; aActionType: TKMUnitActionType; aDistance: Single; aTargetUnit: TKMUnit;
                            aTargetHouse: TKMHouse; aAvoidLockedByMovementCost: Boolean = True);
    function TrySetActionWalk(const aLocB: TKMPoint; aActionType: TKMUnitActionType; aDistance: Single; aTargetUnit: TKMUnit;
                              aTargetHouse: TKMHouse; aAvoidLockedByMovementCost: Boolean = True;
                              aSilent: Boolean = True): Boolean;
    procedure SetActionWalkToHouse(aHouse: TKMHouse; aDistance: Single; aActionType: TKMUnitActionType = uaWalk);
    procedure SetActionWalkFromHouse(aHouse: TKMHouse; aDistance: Single; aActionType: TKMUnitActionType = uaWalk);
    procedure SetActionWalkToUnit(aUnit: TKMUnit; aDistance:single; aActionType: TKMUnitActionType = uaWalk);
    procedure SetActionWalkFromUnit(aUnit: TKMUnit; aDistance: Single; aActionType: TKMUnitActionType = uaWalk);
    procedure SetActionWalkToSpot(const aLocB: TKMPoint; aActionType: TKMUnitActionType = uaWalk; aDistance: Single = 0);
    procedure SetActionWalkToRoad(aActionType: TKMUnitActionType = uaWalk; aDistance: Single = 0;
                                         aTargetPassability: TKMTerrainPassability = tpWalkRoad; aTargetWalkConnectSet: TKMByteSet = []);
    procedure SetActionWalkPushed(const aLocB: TKMPoint; aActionType: TKMUnitActionType = uaWalk);

    procedure Feed(Amount: Single);
    function IsHungry: Boolean;
    procedure AbandonWalk;
    property  DesiredPassability: TKMTerrainPassability read GetDesiredPassability;
    property  Owner: TKMHandID read fOwner write SetOwner;

    property  Home: TKMHouse read fHome write SetHome;
    property  Action: TKMUnitAction read fAction;
    property  Task: TKMUnitTask read fTask;
    property  UnitType: TKMUnitType read fType;
    function  GetActionText: UnicodeString;
    property  Condition: Integer read fCondition write SetCondition;
    property  StartWDefaultCondition: Boolean read fStartWDefaultCondition write fStartWDefaultCondition;

    procedure OwnerUpdate(aOwner: TKMHandID; aMoveToNewOwner: Boolean = False);
    procedure HitPointsChangeFromScript(aAmount: Integer);
    procedure HitPointsDecrease(aAmount: Byte; aAttacker: TKMUnit);
    property  HitPointsMax: Byte read GetHitPointsMax;
    procedure CancelTask(aFreeTaskObject: Boolean = True);
    property  Visible: Boolean read fVisible write fVisible;
    property  InHouse: TKMHouse read fInHouse write SetInHouse;
    property  IsDead: Boolean read fIsDead;
    function  IsDeadOrDying: Boolean;
    function  IsDismissing: Boolean;
    function  IsDismissAvailable: Boolean;
    function  IsDismissCancelAvailable: Boolean;

    property  Thought: TKMUnitThought read fThought write SetThought;
    function  GetMovementVector: TKMPointF;
    function  IsIdle: Boolean;
    procedure TrainInHouse(aSchool: TKMHouseSchool);

    function CanStepTo(X,Y: Integer; aPass: TKMTerrainPassability): Boolean;
    function CanWalkTo(const aTo: TKMPoint; aDistance: Single): Boolean; overload;
    function CanWalkTo(const aTo: TKMPoint; aPass: TKMTerrainPassability; aDistance: Single): Boolean; overload;
    function CanWalkTo(const aFrom, aTo: TKMPoint; aDistance: Single): Boolean; overload;
    function CanWalkTo(const aFrom, aTo: TKMPoint; aPass: TKMTerrainPassability; aDistance: Single): Boolean; overload;
    function CanWalkTo(const aFrom: TKMPoint; aHouse: TKMHouse; aPass: TKMTerrainPassability; aDistance: Single): Boolean; overload;
    function CanWalkDiagonaly(const aFrom, aTo: TKMPoint): Boolean;
    procedure VertexRem(const aLoc: TKMPoint);
    function  VertexUsageCompatible(const aFrom, aTo: TKMPoint): Boolean;
    procedure VertexAdd(const aFrom, aTo: TKMPoint);
    procedure Walk(const aFrom, aTo: TKMPoint);
    function GetActivityText: UnicodeString; virtual;
    function GetSlide(aCheck: TKMCheckAxis): Single;
    function PathfindingShouldAvoid: Boolean; virtual;

    function ObjToString(const aSeparator: String = '|'): String; virtual;
    function ObjToStringShort(const aSeparator: String = '|'): String; virtual;

    procedure Save(SaveStream: TKMemoryStream); virtual;
    function UpdateState: Boolean; virtual;
    procedure Paint; virtual;

    class function GetDefaultCondition: Integer;
  end;

  //Common class for all civil units
  TKMCivilUnit = class(TKMUnit)
  public
    function GoEat(aInn: TKMHouseInn; aUnitAlreadyInsideInn: Boolean = False): Boolean;
    function CheckCondition: Boolean;
    procedure KillInHouse;
  end;

  //This is common class for all units, who can be an owner for house (recruits and all citizen workers)
  TKMSettledUnit = class(TKMCivilUnit)
  private
    procedure CleanHousePointer(aFreeAndNilTask: Boolean = False);
  protected
    function InitiateActivity: TKMUnitTask; virtual; abstract;
    function FindHome: Boolean;
    procedure ProceedHouseClosedForWorker;
  public
    function UpdateState: Boolean; override;
  end;

  //This is a common class for all units, who can work in house
  TKMUnitCitizen = class(TKMSettledUnit)
  protected
    function InitiateActivity: TKMUnitTask; override;
  public
    function CanWorkAt(aLoc: TKMPoint; aGatheringScript: TKMGatheringScript): Boolean;
    function GetActivityText: UnicodeString; override;
    procedure Paint; override;
  end;


  TKMUnitRecruit = class(TKMSettledUnit)
  protected
    function InitiateActivity: TKMUnitTask; override;
  public
    procedure Paint; override;
  end;

  //Serf - transports all wares between houses
  TKMUnitSerf = class(TKMCivilUnit)
  private
    fCarry: TKMWareType;
  public
    constructor Create(aID: Cardinal; aUnitType: TKMUnitType; const aLoc: TKMPoint; aOwner: TKMHandID; aInHouse: Boolean);
    constructor Load(LoadStream: TKMemoryStream); override;
    procedure Save(SaveStream: TKMemoryStream); override;

    procedure Deliver(aFrom: TKMHouse; toHouse: TKMHouse; Res: TKMWareType; aID: integer); overload;
    procedure Deliver(aFrom: TKMHouse; toUnit: TKMUnit; Res: TKMWareType; aID: integer); overload;
    function TryDeliverFrom(aFrom: TKMHouse): Boolean;
    procedure DelegateDelivery(aToSerf: TKMUnitSerf);

    property Carry: TKMWareType read fCarry;
    procedure CarryGive(Res: TKMWareType);
    procedure CarryTake;

    function ObjToString(const aSeparator: String = '|'): String; override;

    function UpdateState: Boolean; override;
    procedure Paint; override;
  end;

  //Worker class - builds everything in game
  TKMUnitWorker = class(TKMCivilUnit)
  public
    procedure BuildHouse(aHouse: TKMHouse; aIndex: Integer);
    procedure BuildHouseRepair(aHouse: TKMHouse; aIndex: Integer);
    procedure BuildField(aField: TKMFieldType; aLoc: TKMPoint; aIndex: Integer);
    procedure BuildHouseArea(aHouseType: TKMHouseType; aLoc: TKMPoint; aIndex: Integer);
    function PickRandomSpot(aList: TKMPointDirList; out Loc: TKMPointDir): Boolean;

    function UpdateState: Boolean; override;
    procedure Paint; override;
  end;


  //Animals
  TKMUnitAnimal = class(TKMUnit)
  private
    fFishCount: Byte; //1-5
  public
    constructor Create(aID: Cardinal; aUnitType: TKMUnitType; const aLoc: TKMPoint; aOwner: TKMHandID); overload;
    constructor Load(LoadStream: TKMemoryStream); override;
    property FishCount: byte read fFishCount;
    function ReduceFish: Boolean;
    procedure Save(SaveStream: TKMemoryStream); override;
    function UpdateState: Boolean; override;
    procedure Paint; override;
  end;

const
  TRY_SET_ACTION_WALK_FREQ = 40; //in ticks. How often do we allow to try set action walk


implementation
uses
  TypInfo,
  KM_Game, KM_GameApp, KM_RenderPool, KM_RenderAux, KM_ResTexts, KM_ScriptingEvents,
  KM_HandsCollection, KM_UnitWarrior, KM_Resource, KM_ResUnits,
  KM_Hand, KM_MapEditorHistory,

  KM_UnitActionAbandonWalk,
  KM_UnitActionFight,
  KM_UnitActionGoInOut,
  KM_UnitActionStay,
  KM_UnitActionSteer,
  KM_UnitActionStormAttack,
  KM_UnitActionWalkTo,

  KM_UnitTaskAttackHouse,
  KM_UnitTaskBuild,
  KM_UnitTaskDelivery,
  KM_UnitTaskDie,
  KM_UnitTaskGoEat,
  KM_UnitTaskGoHome,
  KM_UnitTaskDismiss,
  KM_UnitTaskGoOutShowHungry,
  KM_UnitTaskMining,
  KM_UnitTaskSelfTrain,
  KM_UnitTaskThrowRock,
  KM_GameTypes;


{ TKMCivilUnit }
function TKMCivilUnit.CheckCondition: Boolean;
var
  H: TKMHouseInn;
begin
  Result := False;
  if IsHungry then
  begin
    H := gHands[fOwner].FindInn(fCurrPosition, Self, InHouse <> nil);
    Result := GoEat(H);
  end;
end;


procedure TKMCivilUnit.KillInHouse;
begin
  Assert(fInHouse <> nil, 'Unit could be silently killed only inside some House');
  //Dispose of current action/task BEFORE we close the unit (action might need to check fPosition if recruit was about to walk out to eat)
  //Normally this isn't required because TTaskDie takes care of it all, but recruits in barracks don't use TaskDie.
  SetAction(nil);
  FreeAndNil(fTask);

  CloseUnit(False); //Don't remove tile usage, we are inside the barracks
end;


function TKMCivilUnit.GoEat(aInn: TKMHouseInn; aUnitAlreadyInsideInn: Boolean = False): Boolean;
begin
  Result := False;
  if aInn <> nil then
  begin
    FreeAndNil(fTask);
    fTask := TKMTaskGoEat.Create(aInn, Self);
    if aUnitAlreadyInsideInn then
      fTask.Phase := 3; //We are inside Inn already
    Result := True;
  end;
end;


{ TKMSettledUnit }
//Find home for settled unit
function TKMSettledUnit.FindHome: Boolean;
var H: TKMHouse;
begin
  Result:=false;
  H := gHands[fOwner].Houses.FindEmptyHouse(fType, fCurrPosition);
  if H <> nil then
  begin
    fHome  := H.GetHousePointer;
    Result := true;
  end;
end;


procedure TKMSettledUnit.CleanHousePointer(aFreeAndNilTask: Boolean = False);
begin
  if aFreeAndNilTask then
    FreeAndNil(fTask);
  fHome.HasOwner := False;
  gHands.CleanUpHousePointer(fHome);
end;


// Manage house is closed for worker process
procedure TKMSettledUnit.ProceedHouseClosedForWorker;
var
  wGoingInsideHouse: Boolean;
  wThrowingRock: Boolean;
  wGoingForEating: Boolean;
  wWentOutShowHungry: Boolean;
  wWantToGoOutShowHungry: Boolean;
  wWalkingOutside: Boolean;
  wWorkingOutsideHouse: Boolean;
  wHasNoTask: Boolean;
  wIsInsideHouse: Boolean;
begin
  if (fHome <> nil)
    and not fHome.IsDestroyed
    and (fHome.IsClosedForWorker or ((fHome.HouseType = htBarracks) and (TKMHouseBarracks(fHome).NotAcceptRecruitFlag)))
    and not (fTask is TKMTaskDie)
    and not (fTask is TKMTaskDismiss) then
    begin
      wGoingInsideHouse := (fAction is TKMUnitActionGoInOut) and ((TKMUnitActionGoInOut(fAction)).Direction = gdGoInside);
      // let recruits finish throwing animation
      wThrowingRock := (fTask is TKMTaskThrowRock) and (fHome.GetState in [hstWork]);
      // do not cancel eating task
      wGoingForEating := (fTask is TKMTaskGoEat);
      // Assume worker is inside the house if not Visible.
      wIsInsideHouse := not Visible;
      // cancel GoOutShowHungry task if we outside of the house
      wWentOutShowHungry := (fTask is TKMTaskGoOutShowHungry) and not wIsInsideHouse;
      // cancel GoOutShowHungry task and go out of the house, if we inside of it
      wWantToGoOutShowHungry := (fTask is TKMTaskGoOutShowHungry) and wIsInsideHouse;
      // We are on the way to somewhere. AbandonWalk 'n cancel task.
      wWalkingOutside := (fAction is TKMUnitActionWalkTo) and not TKMUnitActionWalkTo(fAction).DoingExchange;
      // Working outside
      wWorkingOutsideHouse := (fTask is TKMTaskMining) and not wIsInsideHouse;
      // Somehow no task
      wHasNoTask := (fTask = nil);
      if (not wThrowingRock) then       // Let recruit finish rock throwing
        if (wGoingForEating) then
        begin
          CleanHousePointer;            // Clean house pointer, do not cancel eating task
        end else begin
          if (wWalkingOutside) then begin
            AbandonWalk;                // Stop walking
            CleanHousePointer(True);    // Clean house pointer and free task
          end else
          if not wGoingInsideHouse and  // Let worker get into the house
            ((wWentOutShowHungry or wWorkingOutsideHouse) // When already outside the house
            // Not sure we need this
            or (wHasNoTask and not wIsInsideHouse)) then  // Or has no task outside the house.
          begin
            CleanHousePointer(True);    // Clean house pointer and free task
          end else
          if (wIsInsideHouse or wWantToGoOutShowHungry)
            and not (fHome.HouseType = htBarracks) then // Recruits should not go out of Barracks
          begin
            SetActionGoIn(uaWalk, gdGoOutside, fHome); //Walk outside the house
            // If working inside - first we need to set house state to Idle, then to Empty
            fHome.SetState(hstIdle);
            fHome.SetState(hstEmpty);
            CleanHousePointer(True)     // Clean house pointer and free task
          end;
        end;
    end;
end;


function TKMSettledUnit.UpdateState: Boolean;
begin
  Result := True;
  if fAction = nil then
    raise ELocError.Create(gRes.Units[UnitType].GUIName + ' has no action at start of TKMSettledUnit.UpdateState', fCurrPosition);

  //Reset unit activity if home was destroyed, except when unit is dying or eating (finish eating/dying first)
  if (fHome <> nil)
    and fHome.IsDestroyed
    and not(fTask is TKMTaskDie)
    and not(fTask is TKMTaskGoEat) then
  begin
    if (fAction is TKMUnitActionWalkTo)
      and not TKMUnitActionWalkTo(Action).DoingExchange then
      AbandonWalk;
    FreeAndNil(fTask);
    gHands.CleanUpHousePointer(fHome);
  end;

  ProceedHouseClosedForWorker;

  if inherited UpdateState then Exit;
  if IsDead then Exit; //Caused by SelfTrain.Abandoned

  fThought := thNone;

  if IsHungry
    and not CheckCondition
    and (fHome <> nil)
    and not fVisible then
  begin
    if fTask <> nil then
      FreeAndNil(fTask);
    fTask := TKMTaskGoOutShowHungry.Create(Self);
  end;

  if fTask = nil then //If Unit still got nothing to do, nevermind hunger
    if fHome = nil then
      if FindHome then
        fTask := TKMTaskGoHome.Create(Self) //Home found - go there
      else begin
        fThought := thQuest; //Always show quest when idle, unlike serfs who randomly show it
        SetActionStay(20, uaWalk) //There's no home, but there will hopefully be one soon so don't sleep too long
      end
    else
      if fVisible then //Unit is not at home, but it has one
      begin
        if CanAccessHome then
          fTask := TKMTaskGoHome.Create(Self)
        else
          SetActionStay(60, uaWalk) //Home can't be reached
      end else begin

        if not (fHome.HouseType in HOUSE_WORKSHOP)
          or (fHome.CheckResOut(wtAll) < MAX_WARES_OUT_WORKSHOP) then //Do not do anything if we have too many ready resources
          fTask := InitiateActivity; //Unit is at home, so go get a job

        if fTask = nil then //We didn't find any job to do - rest at home
          SetActionStay(Max(gRes.Houses[fHome.HouseType].WorkerRest,1)*10, uaWalk); //By default it's 0, don't scan that often
      end;

  if fAction = nil then
    raise ELocError.Create(gRes.Units[UnitType].GUIName + ' has no action at end of TKMSettledUnit.UpdateState',fCurrPosition);

  Result := False;
end;


{ TKMUnitCitizen }
procedure TKMUnitCitizen.Paint;
var
  Act: TKMUnitActionType;
  XPaintPos, YPaintPos: Single;
  ID: Integer;
begin
  inherited;
  if not fVisible then exit;
  if fAction = nil then exit;
  Act := fAction.fType;

  XPaintPos := fPositionF.X + UNIT_OFF_X + GetSlide(axX);
  YPaintPos := fPositionF.Y + UNIT_OFF_Y + GetSlide(axY);

  ID := fUID * Byte(not (fAction.fType in [uaDie, uaEat]));

  case fAction.fType of
    uaWalk:
      begin
        gRenderPool.AddUnit(fType, ID, uaWalk, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, true);
        if gRes.Units[fType].SupportsAction(uaWalkArm) then
          gRenderPool.AddUnit(fType, ID, uaWalkArm, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, false);
      end;
    uaWork..uaEat:
        gRenderPool.AddUnit(fType, ID, Act, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, true);
    uaWalkArm .. uaWalkBooty2:
      begin
        gRenderPool.AddUnit(fType, ID, uaWalk, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, true);
        gRenderPool.AddUnit(fType, ID, Act, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, false);
      end;
  end;

  if fThought <> thNone then
    gRenderPool.AddUnitThought(fType, Act, Direction, fThought, XPaintPos, YPaintPos);
end;


function TKMUnitCitizen.CanWorkAt(aLoc: TKMPoint; aGatheringScript: TKMGatheringScript): Boolean;
var
  I: Integer;
begin
  case aGatheringScript of
    gsWoodCutterPlant: begin
                          //Woodcutters should not plant trees on our own or our ally's house plans
                          //(it's very annoying if they do)
                          Result := True;
                          for I := 0 to gHands.Count - 1 do
                            if gHands[fOwner].Alliances[I] = atAlly then
                              Result := Result and not gHands[I].BuildList.HousePlanList.HasPlan(aLoc);
                        end;
    else Result := True;
  end;
end;


function TKMUnitCitizen.GetActivityText: UnicodeString;
begin
  Result := Inherited GetActivityText; //Default
  if fTask is TKMTaskMining then
    Result := TKMTaskMining(fTask).GetActivityText;
end;


function TKMUnitCitizen.InitiateActivity: TKMUnitTask;
var
  Res: Integer;
  TM: TKMTaskMining;
begin
  Result := nil;

  if not KMSamePoint(fCurrPosition, fHome.Entrance) then
    raise ELocError.Create('Mining from wrong spot', fCurrPosition);

  Res := 1;
  //Check if House has production orders
  //Ask the house what order we should make
  if gRes.Houses[fHome.HouseType].DoesOrders then
  begin
    Res := fHome.PickOrder;
    if Res = 0 then Exit;
  end;

  // Don't bother creating a task if there's no room for resulting ware
  // Saves us time on Fishers/Stonecutters/Woodcutters when they calculate routes to nearby deposits
  // Other houses where workers walk out can choose between cut/plant
  if (fHome.HouseType in [htFisherHut, htQuary, htWineyard])
  and (fHome.CheckResOut(gRes.Houses[fHome.HouseType].ResOutput[Res]) >= MAX_WARES_IN_HOUSE) then
    Exit;

  TM := TKMTaskMining.Create(Self, gRes.Houses[fHome.HouseType].ResOutput[Res]);

  if TM.WorkPlan.ResourceDepleted then
  begin
    if not fHome.ResourceDepleted then
      fHome.IssueResourceDepletedMsg;
  end
  else
    //Reset resource depleted state for home, if we find any resource
    //(it could appear again, f.e. by script)
    fHome.ResourceDepleted := False;

  if TM.WorkPlan.IsIssued
    and ((TM.WorkPlan.Resource1 = wtNone) or (fHome.CheckResIn(TM.WorkPlan.Resource1) >= TM.WorkPlan.Count1))
    and ((TM.WorkPlan.Resource2 = wtNone) or (fHome.CheckResIn(TM.WorkPlan.Resource2) >= TM.WorkPlan.Count2))
    and (fHome.CheckResOut(TM.WorkPlan.Product1) < MAX_WARES_IN_HOUSE)
    and (fHome.CheckResOut(TM.WorkPlan.Product2) < MAX_WARES_IN_HOUSE) then
  begin
    //if fResource.HouseDat[fHome.HouseType].DoesOrders then
      //Take order to production
      //fHome.ResOrder[Res] := fHome.ResOrder[Res] - 1;
    Result := TM;
  end
  else
  begin
    TM.Free;
    Result := nil;
  end;
end;


{ TKMUnitRecruit }
procedure TKMUnitRecruit.Paint;
var
  Act: TKMUnitActionType;
  XPaintPos, YPaintPos: Single;
  ID: Integer;
begin
  inherited;
  if not fVisible then exit;
  if fAction = nil then exit;
  Act := fAction.fType;

  XPaintPos := fPositionF.X + UNIT_OFF_X + GetSlide(axX);
  YPaintPos := fPositionF.Y + UNIT_OFF_Y + GetSlide(axY);

  ID := fUID * Byte(not (fAction.fType in [uaDie, uaEat]));

  case fAction.fType of
    uaWalk:
      begin
        gRenderPool.AddUnit(fType, ID, uaWalk, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, true);
        if gRes.Units[fType].SupportsAction(uaWalkArm) then
          gRenderPool.AddUnit(fType, ID, uaWalkArm, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, false);
      end;
    uaWork..uaEat:
        gRenderPool.AddUnit(fType, ID, Act, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, true);
    uaWalkArm .. uaWalkBooty2:
      begin
        gRenderPool.AddUnit(fType, ID, uaWalk, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, true);
        gRenderPool.AddUnit(fType, ID, Act, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, false);
      end;
  end;

  if fThought <> thNone then
    gRenderPool.AddUnitThought(fType, Act, Direction, fThought, XPaintPos, YPaintPos);
end;


function TKMUnitRecruit.InitiateActivity: TKMUnitTask;
var
  Enemy: TKMUnit;
begin
  Result := nil;

  //See if we are in a tower and have something to throw
  if not (fHome is TKMHouseTower) or (fHome.CheckResIn(wtStone) <= 0) then
    Exit;

  Enemy := gTerrain.UnitsHitTestWithinRad(fCurrPosition, RANGE_WATCHTOWER_MIN, RANGE_WATCHTOWER_MAX, fOwner, atEnemy, dirNA, not RANDOM_TARGETS);

  //Note: In actual game there might be two Towers nearby,
  //both throwing a stone into the same enemy. We should not
  //negate that fact, thats real-life situation.

  if Enemy <> nil then
    Result := TKMTaskThrowRock.Create(Self, Enemy);
end;


{ TKMSerf }
constructor TKMUnitSerf.Create(aID: Cardinal; aUnitType: TKMUnitType; const aLoc: TKMPoint; aOwner: TKMHandID; aInHouse: Boolean);
begin
  inherited;
  fCarry := wtNone;
end;


procedure TKMUnitSerf.Deliver(aFrom, toHouse: TKMHouse; Res: TKMWareType; aID: integer);
begin
  fThought := thNone; //Clear ? thought
  fTask := TKMTaskDeliver.Create(Self, aFrom, toHouse, Res, aID);
end;


procedure TKMUnitSerf.Deliver(aFrom: TKMHouse; toUnit: TKMUnit; Res: TKMWareType; aID: integer);
begin
  fThought := thNone; //Clear ? thought
  fTask := TKMTaskDeliver.Create(Self, aFrom, toUnit, Res, aID);
end;


function TKMUnitSerf.TryDeliverFrom(aFrom: TKMHouse): Boolean;
var
  T: TKMUnitTask;
begin
  Result := False;
  //Save current task
  T := fTask;
  //Try to get a new one
  if gHands[Owner].Deliveries.Queue.AskForDelivery(Self, aFrom) then
  begin
    Result := True;
    FreeAndNil(T); //Destroy old task, we created a new one
  end;

  //If we got ourselves a new task then skip to resource-taking part, as we are already in this house
  if Result and (aFrom <> nil) then
    fTask.Phase := 2; //Skip  of the new task
end;


//Delegate delivery to other serf
procedure TKMUnitSerf.DelegateDelivery(aToSerf: TKMUnitSerf);
begin
  Assert(Task is TKMTaskDeliver, 'UnitTask is not TKMTaskDeliver');

  TKMTaskDeliver(Task).DelegateToOtherSerf(aToSerf); //Update task to be used with new serf
  aToSerf.fTask := Task; //Set delivery task to new serf

  //AbandonWalk if needed and cleanup task pointer, but do not free task object, as it was delegated to other serf already
  CancelTask(False);
end;


constructor TKMUnitSerf.Load(LoadStream: TKMemoryStream);
begin
  inherited;
  LoadStream.Read(fCarry, SizeOf(fCarry));
end;


procedure TKMUnitSerf.Paint;
var
  Act: TKMUnitActionType;
  XPaintPos, YPaintPos: Single;
  ID: Integer;
begin
  inherited;
  if not fVisible then exit;
  if fAction = nil then exit;
  Act := fAction.fType;

  XPaintPos := fPositionF.X + UNIT_OFF_X + GetSlide(axX);
  YPaintPos := fPositionF.Y + UNIT_OFF_Y + GetSlide(axY);

  ID := fUID * Byte(not (fAction.fType in [uaDie, uaEat]));

  gRenderPool.AddUnit(UnitType, ID, Act, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, true);

  if fTask is TKMTaskDie then Exit; //Do not show unnecessary arms

  if Carry <> wtNone then
    gRenderPool.AddUnitCarry(Carry, ID, Direction, AnimStep, XPaintPos, YPaintPos)
  else
    gRenderPool.AddUnit(UnitType, ID, uaWalkArm, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, false);

  if fThought <> thNone then
    gRenderPool.AddUnitThought(fType, Act, Direction, fThought, XPaintPos, YPaintPos);
end;


procedure TKMUnitSerf.Save(SaveStream: TKMemoryStream);
begin
  inherited;
  SaveStream.Write(fCarry, SizeOf(fCarry));
end;


function TKMUnitSerf.ObjToString(const aSeparator: String = '|'): String;
begin
  Result := inherited ObjToString(aSeparator)
          + Format('%sCarry = %s', [aSeparator, GetEnumName(TypeInfo(TKMWareType), Integer(fCarry))]);
end;


function TKMUnitSerf.UpdateState: Boolean;
var
  OldThought: TKMUnitThought;
  WasIdle: Boolean;
begin
  Result := True; //Required for override compatibility
  WasIdle := IsIdle;
  if fAction = nil then raise ELocError.Create(gRes.Units[UnitType].GUIName+' has no action at start of TKMUnitSerf.UpdateState',fCurrPosition);
  if inherited UpdateState then
    Exit;

  OldThought := fThought;
  fThought := thNone;

  CheckCondition;

  //Only show quest thought if we have been idle since the last update (not HadTask)
  //and not thinking anything else (e.g. death)
  if fTask = nil then
  begin
    if WasIdle and (OldThought = thNone) and (KaMRandom(2, 'TKMUnitSerf.UpdateState') = 0) then
      fThought := thQuest;
    SetActionStay(60,uaWalk); //Stay idle
  end;

  if fAction = nil then
    raise ELocError.Create(gRes.Units[UnitType].GUIName+' has no action at end of TKMUnitSerf.UpdateState', fCurrPosition);
end;


procedure TKMUnitSerf.CarryGive(Res:TKMWareType);
begin
  Assert(fCarry = wtNone, 'Giving Serf another Carry');
  fCarry := Res;
end;


procedure TKMUnitSerf.CarryTake;
begin
  Assert(Carry <> wtNone, 'Taking wrong ware from Serf');
  fCarry := wtNone;
end;


{ TKMWorker }
procedure TKMUnitWorker.BuildHouse(aHouse: TKMHouse; aIndex: Integer);
begin
  fTask := TKMTaskBuildHouse.Create(Self, aHouse, aIndex);
end;


procedure TKMUnitWorker.BuildField(aField: TKMFieldType; aLoc: TKMPoint; aIndex: Integer);
begin
  case aField of
    ftRoad: fTask := TKMTaskBuildRoad.Create(Self, aLoc, aIndex);
    ftCorn: fTask := TKMTaskBuildField.Create(Self, aLoc, aIndex);
    ftWine: fTask := TKMTaskBuildWine.Create(Self, aLoc, aIndex);
  else
    raise Exception.Create('Unexpected TKMFieldType');
  end;
end;


procedure TKMUnitWorker.BuildHouseArea(aHouseType: TKMHouseType; aLoc: TKMPoint; aIndex: Integer);
begin
  fTask := TKMTaskBuildHouseArea.Create(Self, aHouseType, aLoc, aIndex);
end;


procedure TKMUnitWorker.BuildHouseRepair(aHouse: TKMHouse; aIndex: Integer);
begin
  fTask := TKMTaskBuildHouseRepair.Create(Self, aHouse, aIndex);
end;


//Given a list check the locations in it and pick those that can be walked to
//excluding current location. We assume that caller already made the list
//with only walkable valid tiles
function TKMUnitWorker.PickRandomSpot(aList: TKMPointDirList; out Loc: TKMPointDir): Boolean;
var
  I, MyCount: Integer;
  Spots: array of Word;
begin
  SetLength(Spots, aList.Count);

  //Scan the list and pick suitable locations
  MyCount := 0;
  for I := 0 to aList.Count - 1 do
  if not KMSamePoint(aList[I].Loc, CurrPosition)
  and CanWalkTo(aList[I].Loc, 0) then
  begin
    Spots[MyCount] := I;
    Inc(MyCount);
  end;

  Result := (MyCount > 0);
  if Result then
    Loc := aList[Spots[KaMRandom(MyCount, 'TKMUnitWorker.PickRandomSpot')]];
end;


procedure TKMUnitWorker.Paint;
var
  XPaintPos, YPaintPos: Single;
  ID: Integer;
begin
  inherited;
  if not fVisible then exit;
  if fAction = nil then exit;

  XPaintPos := fPositionF.X + UNIT_OFF_X + GetSlide(axX);
  YPaintPos := fPositionF.Y + UNIT_OFF_Y + GetSlide(axY);

  ID := fUID * Byte(not (fAction.fType in [uaDie, uaEat]));

  gRenderPool.AddUnit(UnitType, ID, fAction.fType, Direction, AnimStep, XPaintPos, YPaintPos, gHands[fOwner].GameFlagColor, true);

  if fThought <> thNone then
    gRenderPool.AddUnitThought(fType, fAction.ActionType, Direction, fThought, XPaintPos, YPaintPos);
end;


function TKMUnitWorker.UpdateState: Boolean;
begin
  Result := True; //Required for override compatibility
  if fAction = nil then
    raise ELocError.Create(gRes.Units[UnitType].GUIName + ' has no action at start of TKMUnitWorker.UpdateState', fCurrPosition);
  if inherited UpdateState then Exit;

  CheckCondition;

  if (fThought = thBuild) and (fTask = nil) then
    fThought := thNone; //Remove build thought if we are no longer doing anything

  //If we are still stuck on a house for some reason, get off it ASAP
  Assert(gTerrain.Land[fCurrPosition.Y, fCurrPosition.X].TileLock <> tlHouse);

  if (fTask = nil) and (fAction = nil) then SetActionStay(20, uaWalk);

  if fAction=nil then raise ELocError.Create(gRes.Units[UnitType].GUIName+' has no action at end of TKMUnitWorker.UpdateState',fCurrPosition);
end;


{ TKMUnitAnimal }
constructor TKMUnitAnimal.Create(aID: Cardinal; aUnitType: TKMUnitType; const aLoc: TKMPoint; aOwner: TKMHandID);
begin
  inherited Create(aID, aUnitType, aLoc, aOwner, False);

  //Always start with 5 fish in the group
  if aUnitType = utFish then
    fFishCount := 5;
end;


constructor TKMUnitAnimal.Load(LoadStream: TKMemoryStream);
begin
  inherited;
  LoadStream.CheckMarker('UnitAnimal');
  LoadStream.Read(fFishCount);
end;


function TKMUnitAnimal.ReduceFish: Boolean;
begin
  Result := fType = utFish;
  if not Result then Exit;

  if fFishCount > 1 then
    Dec(fFishCount)
  else
    Kill(PLAYER_NONE, True, False);
end;


procedure TKMUnitAnimal.Save(SaveStream: TKMemoryStream);
begin
  inherited;
  SaveStream.PlaceMarker('UnitAnimal');
  SaveStream.Write(fFishCount);
end;


function TKMUnitAnimal.UpdateState: Boolean;
begin
  Result := True; //Required for override compatibility

  SetCurrPosition(KMPointRound(fPositionF));

  if fAction = nil then
    raise ELocError.Create(gRes.Units[UnitType].GUIName + ' has no action at start of TKMUnitAnimal.UpdateState', fCurrPosition);

  if fKillASAP then
  begin
    DoKill(fKillASAPShowAnimation);
    fKillASAP := False;
    Exit;
  end;

  case fAction.Execute of
    arActContinues: Exit;
    else            FreeAndNil(fAction);
  end;
  SetCurrPosition(KMPointRound(fPositionF));


  Assert((fTask = nil) or (fTask is TKMTaskDie));
  if fTask is TKMTaskDie then
  case fTask.Execute of
    trTaskContinues:  exit;
    trTaskDone:       raise Exception.Create('Unexpected fUnitTask.Execute value = trTaskDone'); //TTaskDie never returns trTaskDone yet
  end;

  //First make sure the animal isn't stuck (check passibility of our position)
  if (not gTerrain.CheckPassability(fCurrPosition, DesiredPassability))
  or gTerrain.CheckAnimalIsStuck(fCurrPosition, DesiredPassability) then
  begin
    Kill(PLAYER_NONE, True, False); //Animal is stuck so it dies
    Exit;
  end;

  SetActionSteer;

  if fAction = nil then
    raise ELocError.Create(gRes.Units[UnitType].GUIName + ' has no action at end of TKMUnitAnimal.UpdateState', fCurrPosition);
end;


//For fish the action is the number of fish in the group
procedure TKMUnitAnimal.Paint;
var
  Act: TKMUnitActionType;
  XPaintPos, YPaintPos: single;
begin
  inherited;
  if fAction = nil then exit;

  Assert((fType <> utFish) or (InRange(fFishCount, 1, 5)));

  if fType = utFish then
    Act := FishCountAct[fFishCount]
  else
    Act := fAction.fType;

  XPaintPos := fPositionF.X + UNIT_OFF_X + GetSlide(axX);
  YPaintPos := fPositionF.Y + UNIT_OFF_Y + GetSlide(axY);

  //Make fish/watersnakes more visible in the MapEd
  if (gGame.GameMode = gmMapEd) and (fType in [utFish, utWatersnake, utSeastar]) then
    gRenderAux.Circle(fPositionF.X - 0.5,
                      gTerrain.FlatToHeight(fPositionF.X - 0.5, fPositionF.Y - 0.5),
                      0.5, $30FF8000, $60FF8000);

  //Animals share the same WalkTo logic as other units and they exchange places if necessary
  //Animals can be picked only in MapEd
  gRenderPool.AddUnit(fType, fUID * Byte(gGame.IsMapEditor), Act, Direction, AnimStep, XPaintPos, YPaintPos, $FFFFFFFF, True);
end;


{ TKMUnit }
constructor TKMUnit.Create(aID: Cardinal; aUnitType: TKMUnitType; const aLoc: TKMPoint; aOwner: TKMHandID; aInHouse: Boolean);
begin
  inherited Create;
  fUID          := aID;
  fTicker       := fUID; //Units update states will be spread more evenly that way
  fPointerCount := 0;
  fIsDead       := false;
  fKillASAP     := false;
  fThought      := thNone;
  fHome         := nil;
  fInHouse      := nil;
  fPositionF     := KMPointF(aLoc);
  fCurrPosition := aLoc;
  fPrevPosition := aLoc; //Init values
  fNextPosition := aLoc; //Init values
  fOwner        := aOwner;
  fType     := aUnitType;
  fDirection    := dirS;
  fVisible      := True;
  IsExchanging  := False;
  AnimStep      := UnitStillFrames[fDirection]; //Use still frame at begining, so units don't all change frame on first tick
  Dismissable   := True;
  fLastTimeTrySetActionWalk := 0;

  //Units start with a random amount of condition ranging from 0.5 to 0.7 (KaM uses 0.6 for all units)
  //By adding the random amount they won't all go eat at the same time and cause crowding, blockages, food shortages and other problems.
  if (gGame <> nil) and (gGame.GameMode <> gmMapEd) then
    fCondition    := Round(UNIT_MAX_CONDITION * (UNIT_CONDITION_BASE + KaMRandomS2(UNIT_CONDITION_RANDOM, 'TKMUnit.Create')))
  else begin
    fCondition    := GetDefaultCondition;
    fStartWDefaultCondition := True;
  end;

  fHitPoints      := HitPointsMax;
  fHitPointCounter := 1;
  HitPointsInvulnerable := False;

  SetActionLockedStay(10, uaWalk); //Must be locked for this initial pause so animals don't get pushed

  fVisual := TKMUnitVisual.Create(Self);

  // Do not add units which are trained inside house
  if not aInHouse then
    gTerrain.UnitAdd(NextPosition, Self);

  //The area around the unit should be visible at the start of the mission
  if InRange(fOwner, 0, MAX_HANDS - 1) then //Not animals
    gHands.RevealForTeam(fOwner, fCurrPosition, gRes.Units[fType].Sight, FOG_OF_WAR_MAX);
end;


destructor TKMUnit.Destroy;
begin
  if not IsDead then gTerrain.UnitRem(NextPosition); //Happens only when removing player from map on GameStart (network)
  FreeAndNil(fAction);
  FreeAndNil(fTask);
  FreeAndNil(fVisual);
  SetInHouse(nil); //Free pointer
  inherited;
end;


constructor TKMUnit.Load(LoadStream: TKMemoryStream);
var
  HasTask, HasAct: Boolean;
  TaskName: TKMUnitTaskType;
  ActName: TKMUnitActionName;
begin
  inherited Create;
  LoadStream.CheckMarker('Unit');
  LoadStream.Read(fType, SizeOf(fType));
  LoadStream.Read(HasTask);
  if HasTask then
  begin
    LoadStream.Read(TaskName, SizeOf(TaskName));
    case TaskName of
      uttUnknown:         raise Exception.Create('TaskName can''t be handled');
      uttSelfTrain:       fTask := TKMTaskSelfTrain.Load(LoadStream);
      uttDeliver:         fTask := TKMTaskDeliver.Load(LoadStream);
      uttBuildRoad:       fTask := TKMTaskBuildRoad.Load(LoadStream);
      uttBuildWine:       fTask := TKMTaskBuildWine.Load(LoadStream);
      uttBuildField:      fTask := TKMTaskBuildField.Load(LoadStream);
      uttBuildHouseArea:  fTask := TKMTaskBuildHouseArea.Load(LoadStream);
      uttBuildHouse:      fTask := TKMTaskBuildHouse.Load(LoadStream);
      uttBuildHouseRepair:fTask := TKMTaskBuildHouseRepair.Load(LoadStream);
      uttGoHome:          fTask := TKMTaskGoHome.Load(LoadStream);
      uttDismiss:         fTask := TKMTaskDismiss.Load(LoadStream);
      uttAttackHouse:     fTask := TKMTaskAttackHouse.Load(LoadStream);
      uttThrowRock:       fTask := TKMTaskThrowRock.Load(LoadStream);
      uttGoEat:           fTask := TKMTaskGoEat.Load(LoadStream);
      uttMining:          fTask := TKMTaskMining.Load(LoadStream);
      uttDie:             fTask := TKMTaskDie.Load(LoadStream);
      uttGoOutShowHungry: fTask := TKMTaskGoOutShowHungry.Load(LoadStream);
    else
      raise Exception.Create('TaskName can''t be handled');
    end;
  end
  else
    fTask := nil;

  LoadStream.Read(HasAct);
  if HasAct then
  begin
    LoadStream.Read(ActName, SizeOf(ActName));
    case ActName of
      uanStay:        fAction := TKMUnitActionStay.Load(LoadStream);
      uanWalkTo:      fAction := TKMUnitActionWalkTo.Load(LoadStream);
      uanAbandonWalk: fAction := TKMUnitActionAbandonWalk.Load(LoadStream);
      uanGoInOut:     fAction := TKMUnitActionGoInOut.Load(LoadStream);
      uanFight:       fAction := TKMUnitActionFight.Load(LoadStream);
      uanStormAttack: fAction := TKMUnitActionStormAttack.Load(LoadStream);
      uanSteer:       fAction := TKMUnitActionSteer.Load(LoadStream);
    else
      raise Exception.Create('ActName can''t be handled');
    end;
  end
  else
    fAction := nil;

  LoadStream.Read(fThought, SizeOf(fThought));
  LoadStream.Read(fCondition);
  LoadStream.Read(fStartWDefaultCondition);
  LoadStream.Read(fTicker);
  LoadStream.Read(fHitPoints);
  LoadStream.Read(fHitPointCounter);
  LoadStream.Read(HitPointsInvulnerable);
  LoadStream.Read(fInHouse, 4);
  LoadStream.Read(fOwner, SizeOf(fOwner));
  LoadStream.Read(fHome, 4); //Substitute it with reference on SyncLoad
  LoadStream.Read(fPositionF);
  LoadStream.Read(fVisible);
  LoadStream.Read(fIsDead);
  LoadStream.Read(fKillASAP);
  LoadStream.Read(fKillASAPShowAnimation);
  LoadStream.Read(IsExchanging);
  LoadStream.Read(fPointerCount);
  LoadStream.Read(fUID);
  LoadStream.Read(AnimStep);
  LoadStream.Read(fDirection);
  LoadStream.Read(fCurrPosition);
  LoadStream.Read(fPrevPosition);
  LoadStream.Read(fNextPosition);
  LoadStream.Read(fDismissASAP);
  LoadStream.Read(Dismissable);
  LoadStream.Read(fLastTimeTrySetActionWalk);

  //Create last so it can initialise with loaded values
  fVisual := TKMUnitVisual.Create(Self);
end;


procedure TKMUnit.SyncLoad;
begin
  if fTask <> nil then
    fTask.SyncLoad;

  if fAction <> nil then
    fAction.SyncLoad;

  fHome := gHands.GetHouseByUID(cardinal(fHome));
{'fraction_non_alphanumeric': 0.08564371240903401, 'fraction_numerical': 0.001993774991415691, 'mean_word_length': 3.963822300417858, 'pattern_counts': {'":': 0, '<': 55, '<?xml version=': 0, '>': 57, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7021564', 'n_tokens_mistral': 30862, 'n_tokens_neox': 28733, 'n_words': 8231}