unit uGmCreature;

interface

uses
  zglHeader, uGmData, uGmSprite, uGmBullet;

type
  TCrState = ( CS_STAND, CS_WALK, CS_ATACK, CS_DAMAGE, CS_DIE );

type
  TCreature = class
    X, Y      : single;
    TX, TY    : integer;
    Pat       : PCrPat;
    State     : TCrState;
    PrevState : TCrState;
    PlayerN   : byte;
    FrameN    : single;
    BckDAnim  : boolean;
    LookLeft  : boolean;
    WalkAng   : single;
    WalkLen   : single;
    WalkDist  : single;
    PathX     : array of integer;
    PathY     : array of integer;
    PathLen   : integer;
    PathPos   : integer;
    AtackCr   : TCreature;
    AtackByCr : TCreature;
    AtAlready : boolean;
    Pohodil   : boolean;
    Health    : integer;
    Damage    : integer;
    Sdacha    : boolean;
    SdachaCnt : integer;
    Shoot     : boolean;
    Bullet    : TBullet;

    constructor Create( ctx, cty : integer; cPat : PCrPat; pn : byte );
    destructor Destroy; override;
    procedure Draw;
    procedure Update;
    function IsNear( cr : TCreature ): boolean;
  end;

implementation

//==============================================================================
constructor TCreature.Create( ctx, cty : integer; cPat : PCrPat; pn : byte );
begin
  TX  := ctx;
  TY  := cty;
  X   := TX * 54 + 84 + 32;
  Y   := TY * 46 + 100 + 32;
  if TY mod 2 = 0 then X := X + 27;
  Pat       := cPat;
  State     := CS_STAND;
  PrevState := CS_STAND;
  PlayerN   := pn;
  FrameN    := 0;
  BckDAnim  := False;
  LookLeft  := ( pn = 2 );
  WalkAng   := 0;
  WalkLen   := 0;
  WalkDist  := 0;
  PathLen   := 0;
  PathPos   := 0;
  AtackCr   := nil;
  AtackByCr := nil;
  AtAlready := False;
  Pohodil   := False;
  Health    := Pat.Health;
  Damage    := Pat.Damage;  
  Sdacha    := False;
  SdachaCnt := Pat.SdachaCnt;
  Shoot     := False;
  Bullet    := nil;
end;

//==============================================================================
destructor TCreature.Destroy;
begin
  SetLength( PathX, 0 );
  SetLength( PathY, 0 );
  if Bullet <> nil then Bullet.Free;
  inherited;
end;

//==============================================================================
procedure TCreature.Draw;
var
  Spr     : PSprite;
  ox, oy  : integer;
begin
  X   := TX * 54 + 84 + 32 + WalkDist * Cos( WalkAng * deg2rad );
  Y   := TY * 46 + 100 + 32 + WalkDist * Sin( WalkAng * deg2rad );
  if TY mod 2 = 0 then X := X + 27;

  if WalkDist <> 0 then
  begin
    LookLeft := ( WalkAng > 90 ) and ( WalkAng < 270 );
    if ( WalkAng = 90 ) or ( WalkAng = 270 ) then LookLeft := ( PlayerN = 2 );
  end;

  Spr := nil;
  if State = CS_STAND  then Spr := @Pat.StandSpr;
  if State = CS_WALK   then Spr := @Pat.WalkSpr;
  if State = CS_ATACK  then Spr := @Pat.AtackSpr;
  if State = CS_DAMAGE then Spr := @Pat.DamageSpr;
  if State = CS_DIE then
  begin
    Spr := @Pat.DamageSpr;
    FrameN := Spr.FramesCnt - 1;
  end;

  ox := Pat.SprOffsetX;
  oy := Pat.SprOffsetY;
  if LookLeft then ox := -Pat.SprOffsetX;

  if Spr <> nil then Sprite_Draw( Spr, X - ox, Y - oy, Trunc( FrameN ), LookLeft );
end;

//==============================================================================
procedure TCreature.Update;
var
  Spr   : PSprite;
  bpat  : PBulletPat;
  x1, y1, x2, y2, ox : single;
begin
  if Bullet <> nil then
  begin
    Bullet.Update;
    if Bullet.Dist <= 0 then
    begin
      AtackCr.State  := CS_DAMAGE;
      AtackCr.Health := AtackCr.Health - Damage;
      if AtackCr.Pat.Ability = 'ArrowResistance' then AtackCr.Health := AtackCr.Health + Damage div 2;
      if Sdacha = False then
      begin
        Pohodil := True;
        if IsNear( AtackCr ) then
          if AtackCr.Health > 0 then
          begin
            AtackCr.AtackByCr := Self;
            if AtackCr.SdachaCnt > 0 then AtackCr.Sdacha := True;
          end;
      end;
      if Sdacha = True then Sdacha := False;
      Bullet.Free;
      Bullet := nil;
    end;
  end;

  Spr := nil;
  if State <> CS_DIE then
    if BckDAnim = False then FrameN := FrameN + 0.4
      else FrameN := FrameN - 0.4;

  PrevState := State;
  //--------- существо стоит ---------------------------------------------------
  if State = CS_STAND then
  begin
    Spr := @Pat.StandSpr;

    if ( AtackCr <> nil ) and ( Bullet = nil ) then
      if ( AtackCr.State = CS_STAND ) or ( AtackCr.State = CS_DIE ) then AtackCr := nil;
    if AtackByCr <> nil then
      if ( AtackByCr.State = CS_STAND ) or ( AtackByCr.State = CS_DIE ) then AtackByCr := nil;
  end;
  //--------- существо идет ----------------------------------------------------
  if State = CS_WALK then
  begin
    Spr := @Pat.WalkSpr;
    WalkDist := WalkDist + Pat.MoveSpeed;

    if Pat.CanFly = False then
    begin
      if ( PathX[ PathPos ] < PathX[ PathPos + 1 ] ) and ( PathY[ PathPos ] = PathY[ PathPos + 1 ] ) then WalkAng := 0;
      if ( PathX[ PathPos ] > PathX[ PathPos + 1 ] ) and ( PathY[ PathPos ] = PathY[ PathPos + 1 ] ) then WalkAng := 180;

      if PathY[ PathPos ] mod 2 = 0 then
      begin
        if ( PathX[ PathPos ] < PathX[ PathPos + 1 ] ) and ( PathY[ PathPos ] < PathY[ PathPos + 1 ] ) then WalkAng := 60;
        if ( PathX[ PathPos ] = PathX[ PathPos + 1 ] ) and ( PathY[ PathPos ] < PathY[ PathPos + 1 ] ) then WalkAng := 120;
        if ( PathX[ PathPos ] = PathX[ PathPos + 1 ] ) and ( PathY[ PathPos ] > PathY[ PathPos + 1 ] ) then WalkAng := 240;
        if ( PathX[ PathPos ] < PathX[ PathPos + 1 ] ) and ( PathY[ PathPos ] > PathY[ PathPos + 1 ] ) then WalkAng := 300;
      end else
      begin
        if ( PathX[ PathPos ] = PathX[ PathPos + 1 ] ) and ( PathY[ PathPos ] < PathY[ PathPos + 1 ] ) then WalkAng := 60;
        if ( PathX[ PathPos ] > PathX[ PathPos + 1 ] ) and ( PathY[ PathPos ] < PathY[ PathPos + 1 ] ) then WalkAng := 120;
        if ( PathX[ PathPos ] > PathX[ PathPos + 1 ] ) and ( PathY[ PathPos ] > PathY[ PathPos + 1 ] ) then WalkAng := 240;
        if ( PathX[ PathPos ] = PathX[ PathPos + 1 ] ) and ( PathY[ PathPos ] > PathY[ PathPos + 1 ] ) then WalkAng := 300;
      end;

      if ( WalkAng = 0 ) or ( WalkAng = 180 ) then WalkLen := 54 else WalkLen := 53;
    end else
    begin
      x1 := PathX[ 0 ] * 54 + 84;
      y1 := PathY[ 0 ] * 46 + 100;
      if PathY[ 0 ] mod 2 = 0 then x1 := x1 + 27;
      x2 := PathX[ 1 ] * 54 + 84;
      y2 := PathY[ 1 ] * 46 + 100;
      if PathY[ 1 ] mod 2 = 0 then x2 := x2 + 27;
      WalkAng := m_Angle( x2, y2, x1, y1 );
      WalkLen := m_Distance( x1, y1, x2, y2 );
    end;

    if WalkDist >= WalkLen then
    begin
      WalkDist := WalkDist - WalkLen;
      if WalkDist >= WalkLen then WalkDist := 0;

      PathPos := PathPos + 1;
      TX := PathX[ PathPos ];
      TY := PathY[ PathPos ];

      if ( PathPos = PathLen - 1 ) or ( PathPos >= Pat.Speed - 1 ) then
      begin
        State     := CS_STAND;
        WalkDist  := 0;
        PathPos   := 0;
        PathLen   := 0;
        SetLength( PathX, 0 );
        SetLength( PathY, 0 );
        if AtackCr <> nil then
          if AtackCr.State <> CS_DIE then
          begin
            State := CS_ATACK;
            FrameN := 0;
          end;
        if State = CS_STAND then
        begin
          LookLeft := ( PlayerN = 2 );
          Pohodil  := True;
        end;
      end;
    end;
  end;
  //--------- существо атакует -------------------------------------------------
  if State = CS_ATACK then
  begin
    Spr := @Pat.AtackSpr;
    if AtackCr <> nil then
    begin
      LookLeft := ( AtackCr.X < X );
      AtackCr.LookLeft := ( AtackCr.X > X );
      if ( FrameN >= Pat.AtackFrame ) and ( AtAlready = False ) then
      begin
        AtAlready := True;

        if Shoot = True then
        begin
          Shoot := False;
          bpat  := BulletPat_Get( Pat.Bullet );
          ox := bpat.OffsetX;
          if LookLeft then ox := -bpat.OffsetX;
          if bpat <> nil then Bullet := TBullet.Create( X + ox, Y + 1, AtackCr.X, AtackCr.Y + 1, bpat );
        end else
        begin
          AtackCr.State  := CS_DAMAGE;
          AtackCr.Health := AtackCr.Health - Damage;

          if Sdacha = False then
          begin
            Pohodil := True;
            if AtackCr.Health > 0 then
            begin
              AtackCr.AtackByCr := Self;
              if AtackCr.SdachaCnt > 0 then AtackCr.Sdacha := True;
            end;
          end;
          if Sdacha = True then Sdacha := False;
        end;
      end;
    end;
    if FrameN >= Spr.FramesCnt then
    begin
      State := CS_STAND;
      AtAlready := False;
    end;
  end;
  //--------- существо получает повреждение ------------------------------------
  if State = CS_DAMAGE then
  begin
    Spr := @Pat.DamageSpr;
    if Health > 0 then
    begin
      if Pat.BckDAnim then
      begin
        if ( BckDAnim = true ) and ( FrameN <= 0 ) then
        begin
          BckDAnim := False;
          State := CS_STAND;
          if Sdacha = True then
          begin
            SdachaCnt := SdachaCnt - 1;
            State     := CS_ATACK;
            AtackCr   := AtackByCr;
            Shoot     := Pat.CanShoot;
          end;
        end;
        if ( BckDAnim = False ) and ( FrameN >= Pat.DamageFrame ) then BckDAnim := True;
      end else
      begin
        if FrameN >= Pat.DamageFrame then
        begin
          State := CS_STAND;
          if Sdacha = True then
          begin
            SdachaCnt := SdachaCnt - 1;
            State     := CS_ATACK;
            AtackCr   := AtackByCr;
            Shoot     := Pat.CanShoot;
          end;
        end;
      end;
    end;
    if ( Health <= 0 ) and ( FrameN >= Spr.FramesCnt ) then
    begin
      State := CS_DIE;
      AtackCr := nil;
    end;
  end;
  //----------------------------------------------------------------------------

  if PrevState <> State then FrameN := 0;

  if Spr <> nil then
    if FrameN > Spr.FramesCnt then FrameN := 0;
end;

//==============================================================================
function TCreature.IsNear( cr : TCreature ): boolean;
begin
  Result := False;
  if ( TY = cr.TY ) and ( ( TX = cr.TX - 1 ) or ( TX = cr.TX + 1 ) ) then Result := True;
  if ( TY = cr.TY - 1 ) or ( TY = cr.TY + 1 ) then
  begin
    if TY mod 2 = 0 then
      if ( TX = cr.TX ) or ( TX = cr.TX - 1 ) then Result := True;
    if TY mod 2 <> 0 then
      if ( TX = cr.TX ) or ( TX = cr.TX + 1 ) then Result := True;
  end
end;

end.
