{
  uGameUnit.pas
  Part of Cavern Defense - LD15	

  Handles a unit of the game

}
unit uGameUnit;

interface

uses 
  uBasic,
  uGlobal,
  ElysionLegacy,
  SysUtils,
  SDL_gfx;

const 
  UNITS_FILE = 'units.png';

type  

TGameUnit = class(TelObject)
private
  FAttack: Integer;
  FDefense: Single;
  
  FMovePoints, FAttackPoints: Byte;
  
  FEnemyUnit: Boolean;
  FShowHP: Boolean;
  FMarked: Boolean;
  FMoving: Boolean;
  
  FLevel: Byte;
  FHandicap: Single;
  
  FSprite, FSpriteShadow, FSpriteMarked: TelSprite;
  
  FMoveRect, FAttackRect: TelRect;

  FDestination: TelPoint;
  
  FRace: TRace;
  FUnitType: TUnitType;
  
  FGridPos: TelPoint;
  FMoveGrid: array[0..9, 0..7] of Boolean;
  
  FTimer: TelTimer;
  procedure MoveAnimation;  
  
  function GetCanMove: Boolean;
public
  Dead: Boolean;
  Hitpoints: TGauge;
  Experience: TGauge;
  Position: TelPoint;

  constructor Create(_Race: TRace; _UnitType: TUnitType; Level: Byte; Handicap: Single);
  destructor Destroy; Override;
  procedure Free;
  
  procedure Reset;
  
  procedure Draw;
  
  procedure SetGridPos(Point: TelPoint);
  
  procedure ShowHitPoints;
  
  function GetRect: TelRect;
  function GetMoveRect: TelRect;
  function GetAttackRect: TelRect;
  
  procedure AttackUnit(Enemy: TGameUnit);
  
  procedure MoveTo(Point: TelPoint);
  
  procedure SetLevel(Level: Byte);
  procedure SetHandicap(Handicap: Single);
  
  function OnMouseOver: Boolean;
  function OnClick: Boolean;
published  
  property Attack: Integer read FAttack write FAttack;
  property Defense: Single read FDefense write FDefense;
  
  property CanMove: Boolean read GetCanMove;
  
  property AttackPoints: Byte read FAttackPoints write FAttackPoints;
  property MovePoints: Byte read FMovePoints write FMovePoints;
  
  property EnemyUnit: Boolean read FEnemyUnit write FEnemyUnit;
  property Marked: Boolean read FMarked write FMarked;
  
  property Level: Byte read FLevel write SetLevel;
  property Handicap: Single read FHandicap write SetHandicap;
  
  property Moving: Boolean read FMoving;
  
  property ShowHP: Boolean read FShowHP write FShowHP;
  
  property Race: TRace read FRace;
  property UnitType: TUnitType read FUnitType;
end;

implementation

constructor TGameUnit.Create(_Race: TRace; _UnitType: TUnitType; Level: Byte; Handicap: Single);
var ClipY: Integer;
begin
  Randomize;

  FSprite := TelSprite.Create;
  FSpriteShadow := TelSprite.Create;
  FSpriteMarked := TelSprite.Create;
  
  FTimer := TelTimer.Create;
  FTimer.Interval := 5;
  
  ShowHP := false;
  FMoving := false;
  
  FRace := _Race;
  FUnitType := _UnitType;
  
  if Race = raHuman then ClipY := 0;
  if Race = raOrc then ClipY := 240;
  
  FSpriteMarked.LoadFromFile(GetResImgPath + 'marked.png');
  FSpriteMarked.Generate;
  
  case UnitType of
    utArcher:
	begin
	  FSprite.LoadFromFile(GetResImgPath + UNITS_FILE, makeRect(0, ClipY + 0, GRID_SIZE, GRID_SIZE));
	  FSprite.Transparent := true;
	  FSprite.Generate(gmRGB);
	  
	  FSpriteShadow.VirtualClone(FSprite);
	  FSpriteShadow.ClipImage(makeRect(GRID_SIZE, ClipY + 0, GRID_SIZE, GRID_SIZE));
	
	  AttackPoints := 2;
	  MovePoints := 2;
	  
	  Attack := 45;
	  Defense := 0.15;
	  
	  Hitpoints.Max := 60;
      Experience.Max := 40;
	  
	  Experience.Min := 0;
	  Hitpoints.Min := 0;
	  
	  SetLevel(Level);
	  SetHandicap(Handicap);
	end;
    utPikeman:
	begin
	  FSprite.LoadFromFile(GetResImgPath + UNITS_FILE, makeRect(0, ClipY + 80, GRID_SIZE, GRID_SIZE));
	  FSprite.Transparent := true;
	  FSprite.Generate;
	  
	  FSpriteShadow.VirtualClone(FSprite);
	  FSpriteShadow.ClipImage(makeRect(GRID_SIZE, ClipY + 80, GRID_SIZE, GRID_SIZE));
	
	  AttackPoints := 1;
	  MovePoints := 3;
	  
	  Attack := 33;
	  Defense := 0.4;
	  
	  Hitpoints.Max := 80;
      Experience.Max := 70;
	  
	  Experience.Min := 0;
	  Hitpoints.Min := 0;
	  
	  SetLevel(Level);
	  SetHandicap(Handicap);
	end;
    utSwordsman:
	begin
	  FSprite.LoadFromFile(GetResImgPath + UNITS_FILE, makeRect(0, ClipY + 160, GRID_SIZE, GRID_SIZE));
	  FSprite.Transparent := true;
	  FSprite.Generate;
	  
	  FSpriteShadow.VirtualClone(FSprite);
	  FSpriteShadow.ClipImage(makeRect(GRID_SIZE, ClipY + 160, GRID_SIZE, GRID_SIZE));
	
	  AttackPoints := 1;
	  MovePoints := 2;
	  
	  Attack := 25;
	  Defense := 0.21;
	  
	  Hitpoints.Max := 90;
      Experience.Max := 70;
	  
	  Experience.Min := 0;
	  Hitpoints.Min := 0;
	  
	  SetLevel(Level);
	  SetHandicap(Handicap);
	end;
  end;
  
  FSpriteShadow.Alpha := 200;
  
end;

destructor TGameUnit.Destroy;
begin
  if Self <> Nil then Free;
end;

procedure TGameUnit.Free;
begin
  FSprite.Free;
  FSpriteShadow.Free;
end;

procedure TGameUnit.MoveAnimation();
begin
  if (Position.X = FDestination.X) and
     (Position.Y = FDestination.Y) then
  begin
    FMoving := false;
	
	// Update Grid position
    {$IFDEF DEBUG} WriteLn(IntToStr(FDestination.X div GRID_SIZE) + ' - ' + IntToStr((FDestination.Y - TOP_MENU) div GRID_SIZE)); {$ENDIF}
	SetGridPos(makeP2D(FDestination.X div GRID_SIZE, (FDestination.Y - TOP_MENU) div GRID_SIZE));
	
	//FGridPos := makeP2D((Position.X div GRID_SIZE) * GRID_SIZE, ((Position.Y - TOP_MENU) div GRID_SIZE) * GRID_SIZE + TOP_MENU);
	FTimer.Stop();
  end else
  begin
    if Position.X <> FDestination.X then
    begin
      if FDestination.X > Position.X then Position.X := Position.X + Speed else Position.X := Position.X - Speed;
    end;
  
    if Position.Y <> FDestination.Y then
    begin
      if FDestination.Y > Position.Y then Position.Y := Position.Y + Speed else Position.Y := Position.Y - Speed;
    end;
  end;
end;

function TGameUnit.GetCanMove: Boolean;
begin
  if MovePoints > 0 then Result := true else Result := false;
end;

procedure TGameUnit.Reset;
var i, j: Integer;
begin
  Dead := false;

  for i := 0 to GetGridWidth do
  begin
    for j := 0 to GetGridHeight do FMoveGrid[i, j] := false;
  end;
end;

procedure TGameUnit.SetGridPos(Point: TelPoint);
begin
  FGridPos := Point;
  GridBlock[Point.X, Point.Y] := true;

  Position.X := Point.X * GRID_SIZE;
  Position.Y := Point.Y * GRID_SIZE + TOP_MENU;
end;

procedure TGameUnit.Draw;

  procedure MovementRect;
  var i, j, k, l: Integer;
  begin
    if MovePoints > 0 then
	begin
    
    // Set Movement Rect
    FMoveRect := GetMoveRect;
	
	// Show where unit can move  
    for i := 0 to GetGridWidth do
	begin
	  for j := 0 to GetGridHeight do
	  begin
	    if not OnPoint(makeP2D(i * GRID_SIZE, (j * GRID_SIZE) + TOP_MENU), FMoveRect) then
		  boxRGBA(ActiveSurface.SDL_Surface, i * GRID_SIZE, j * GRID_SIZE + TOP_MENU, (i + 1) * GRID_SIZE, (j + 1) * GRID_SIZE + TOP_MENU, 0, 0, 0, 48);
		//if GridBlock[i, j] then boxRGBA(ActiveSurface.SDL_Surface, i * GRID_SIZE, j * GRID_SIZE + TOP_MENU, (i + 1) * GRID_SIZE, (j + 1) * GRID_SIZE + TOP_MENU, 0, 0, 0, 48);
	  end;
	end;
	
	end else
	begin
	  // Show where unit can move  
      for i := 0 to GetGridWidth do
	  begin
	    for j := 0 to GetGridHeight do
	    begin
	      if (i <> FGridPos.X) or (j <> FGridPos.Y) then
		    boxRGBA(ActiveSurface.SDL_Surface, i * GRID_SIZE, j * GRID_SIZE + TOP_MENU, (i + 1) * GRID_SIZE, (j + 1) * GRID_SIZE + TOP_MENU, 0, 0, 0, 48);
	    end;
	  end;
	end;
  end;

  procedure AttackRect;
  begin
    if AttackPoints > 0 then
    begin
      FAttackRect := GetAttackRect;
      RectangleRGBA(ActiveSurface.SDL_Surface, FAttackRect.X, FAttackRect.Y, FAttackRect.X + FAttackRect.W, FAttackRect.Y + FAttackRect.H, 255, 0, 0, 192);	  
    end;
  end;
  
begin
  if Dead then 
  begin
    FSpriteShadow.Visible := false;
	FSprite.Visible := false;
  end;
  
  // Update sprite position
  FSprite.Position := Position;
  FSpriteShadow.Position := Position;
  FSpriteMarked.Position := makeP2D(Position.X, Position.Y + (GRID_SIZE div 2));
  
  
  if FTimer.Action then MoveAnimation();
  FSpriteShadow.Draw;
  if Marked then FSpriteMarked.Draw;
  FSprite.Draw;
  
  if Marked then
  begin
	if not Moving then
	begin
	  MovementRect;
	  AttackRect;
	end;
  end;
end;

procedure TGameUnit.ShowHitPoints;
var tmp, tmp2: Integer;
begin
  if Hitpoints.Value = Hitpoints.Max then
	begin
	  rectangleRGBA(ActiveSurface.SDL_Surface, FSprite.Position.X + 14, FSprite.Position.Y - 2, FSprite.Position.X + FSprite.Width - 14, FSprite.Position.Y + 2, 255, 255, 255, 200);
	  boxRGBA(ActiveSurface.SDL_Surface, FSprite.Position.X + 15, FSprite.Position.Y - 1, FSprite.Position.X + FSprite.Width - 15, FSprite.Position.Y + 1, 0, 255, 0, 200);
	end else
	begin
	  //tmp := ((Hitpoints.Value div 2) * Hitpoints.Max) div 100;
	  tmp := Hitpoints.Value div 2;
	  tmp2 := (Hitpoints.Max div 2) - tmp;
	
	  rectangleRGBA(ActiveSurface.SDL_Surface, FSprite.Position.X + 14, FSprite.Position.Y - 2, FSprite.Position.X + FSprite.Width - 14, FSprite.Position.Y + 2, 255, 255, 255, 200);
	  boxRGBA(ActiveSurface.SDL_Surface, FSprite.Position.X + 15, FSprite.Position.Y - 1, FSprite.Position.X + 15 + tmp, FSprite.Position.Y + 1, 0, 255, 0, 200);
	  boxRGBA(ActiveSurface.SDL_Surface, FSprite.Position.X + 15 + tmp, FSprite.Position.Y - 1, FSprite.Position.X + 15 + tmp + tmp2, FSprite.Position.Y + 1, 255, 0, 0, 200);
	end;
end;

function TGameUnit.GetRect: TelRect;
begin
  Result := makeRect(Position.X, Position.Y, GRID_SIZE, GRID_SIZE);
end;

function TGameUnit.GetMoveRect: TelRect;
var tmpRect: TelRect;
begin
  if MovePoints > 0  then
  begin
    if FGridPos.X - MovePoints < 0 then tmpRect.X := 0 else tmpRect.X := FGridPos.X - MovePoints;
	if FGridPos.Y - MovePoints < 0 then tmpRect.Y := 0 else tmpRect.Y := FGridPos.Y - MovePoints;
	
	tmpRect.W := 1 + (MovePoints * 2);
    tmpRect.H := 1 + (MovePoints * 2);
	
	tmpRect.X := tmpRect.X * GRID_SIZE;
    tmpRect.Y := (tmpRect.Y * GRID_SIZE) + TOP_MENU;
    tmpRect.W := tmpRect.W * GRID_SIZE;
    tmpRect.H := tmpRect.H * GRID_SIZE;
	
	if (tmpRect.X + tmpRect.W) > 800 then tmpRect.W := 800 - tmpRect.X; 
	if (tmpRect.Y + tmpRect.H) > 600 then tmpRect.H := 600 - tmpRect.Y; 

    Result := tmpRect;
  end;
end;

function TGameUnit.GetAttackRect: TelRect;
var tmpRect: TelRect;
begin
  if AttackPoints > 0 then 
  begin
	if FGridPos.X - AttackPoints < 0 then tmpRect.X := 0 else tmpRect.X := FGridPos.X - AttackPoints;
	if FGridPos.Y - AttackPoints < 0 then tmpRect.Y := 0 else tmpRect.Y := FGridPos.Y - AttackPoints;
	
	tmpRect.W := 1 + (AttackPoints * 2);
    tmpRect.H := 1 + (AttackPoints * 2);
	
	tmpRect.X := tmpRect.X * GRID_SIZE;
    tmpRect.Y := (tmpRect.Y * GRID_SIZE) + TOP_MENU;
    tmpRect.W := tmpRect.W * GRID_SIZE;
    tmpRect.H := tmpRect.H * GRID_SIZE;
	
	if (tmpRect.X + tmpRect.W) > 800 then tmpRect.W := 800 - tmpRect.X; 
	if (tmpRect.Y + tmpRect.H) > 600 then tmpRect.H := 600 - tmpRect.Y; 

    Result := tmpRect;
  end;
end;

procedure TGameUnit.AttackUnit(Enemy: TGameUnit);
var Damage: Integer;
    tmpEXP: Integer;
begin
  tmpEXP := Random(Enemy.Attack div 2);
  Damage := Enemy.Attack + tmpEXP - Round(Enemy.Attack * Enemy.Defense);
  Enemy.Hitpoints.Value := Enemy.Hitpoints.Value - Damage; 
  Self.Experience.Value := Self.Experience.Value + tmpEXP;
  
  AttackPoints := AttackPoints - 1;
  if MovePoints > 0 then MovePoints := MovePoints - 1;
end;

procedure TGameUnit.MoveTo(Point: TelPoint);
var tmp: TelPoint;
begin
  if (Position.X <> Point.X) or
     (Position.Y <> Point.Y) then
  begin
    FDestination := Point;
    tmp.X := Abs((FDestination.X - Position.X) div GRID_SIZE);
    tmp.Y := Abs((FDestination.Y - Position.Y) div GRID_SIZE);
  
    if tmp.X > tmp.Y then MovePoints := MovePoints - tmp.X else MovePoints := MovePoints - tmp.Y;
    if MovePoints < 0 then MovePoints := 0;
  
    FMoving := true;
    FTimer.Start();
  end;
end;

procedure TGameUnit.SetLevel(Level: Byte);
var Multiplier: Integer;
begin
  if Level > 1 then
  begin
	Multiplier := Round(Exp(Level) - Ln(Level - 1));
	  
	Hitpoints.Max := 100 * Multiplier;
    Experience.Max := 70 * Multiplier;
  end;
	  
  Hitpoints.Value := Hitpoints.Max;
  Experience.Value := Experience.Min;
end;

procedure TGameUnit.SetHandicap(Handicap: Single);
begin
  Attack := Round(Attack * Handicap);
  Defense := Defense * Handicap;
  Hitpoints.Max := Round(Hitpoints.Max * Handicap);
  Experience.Max := Round(Experience.Max * Handicap);
	  
  Hitpoints.Value := Hitpoints.Max;
  Experience.Value := Experience.Min;
end;

function TGameUnit.OnMouseOver: Boolean;
begin
  Result := FSprite.OnMouseHover;
end;

function TGameUnit.OnClick: Boolean;
begin
  Result := FSprite.OnClick;
end;

end.