unit l_player;

interface

uses
 OpenGL,
 l_gl, l_math, l_utils, l_input, l_model_v2, l_sound;

type
 // клавиша управления
 TKey = record
  ID   : Byte;
  Down : boolean;
 end;

 // игрок
 TPlayer = object
  protected
   ang  : single;
   GunA : single;
   GunW : single;
   norm : TVector;
   snd__start : integer;
   snd__stop  : integer;
   snd__fire  : integer;
   snd__step  : array [0..1] of integer;
   snd__curstart : integer;
   snd__curstop  : integer;
   snd__curfire  : integer;
   step_tick : integer;
   UseReload : integer;
   RedFX     : single; // хз не знал как обозвать :)
   step      : single;
   ShadowTex : TTexture;  // текстура тени
   Crosshair : TTexture;  // прицела
   HUDTex    : TTexture;  // статуса (здоровье, деньги, фраги)
  public
   Health : integer; // здоровье
   Money  : integer; // лавэ
   Frags  : integer; // фраги
   Pos    : TVector; // позиция
   Angle  : TVector; // угол поворота
   Speed  : TVector; // текущая скорость
   Acc    : TVector; // ускорение
   Keys   : array [0..5] of TKey; // клавиши
   procedure Init;
   procedure Free;
   procedure ResetKeys;
   procedure Start;
   procedure Hit(dmg: integer);  // покоцать
   procedure Update;
   procedure Render;
   procedure Draw_Hud;           // отрисовка статуса
 end;

 PPlayer = ^TPlayer;  // указатель на игрока (юзает только камера вроде)

const
 KEY_FORW  = 0;     // вперёд
 KEY_BACK  = 1;     // назад
 KEY_LEFT  = 2;     // влево
 KEY_RIGHT = 3;     // вправо
 KEY_FIRE  = 4;     // огонь
 KEY_USE   = 5;     // установка стационарки
 MaxAcc    = 0.02;     // Максимальное ускорение при ходьбе (скорость уравновешивается сопротивлением)
 GUN_MAX_W = 15;       // максимальная угловая скорость вращения барабана
 GUN_LEN   = 1.5;      // длина ствола

 ZENIT_SET = 150;      // время на установку стационарки

implementation

uses
 l_game, l_menu, l_map;

const
 FX_COLOR = 0.0;
 
procedure TPlayer.Init;
begin
Keys[KEY_FORW].ID  := Byte('W');
Keys[KEY_BACK].ID  := Byte('S');
Keys[KEY_LEFT].ID  := Byte('A');
Keys[KEY_RIGHT].ID := Byte('D');
Keys[KEY_FIRE].ID  := 0;
Keys[KEY_USE].ID   := Byte('F');
Health := 0; // труп

snd__start   := snd_Load('data\sounds\gun\start.wav');
snd__stop    := snd_Load('data\sounds\gun\stop.wav');
snd__fire    := snd_Load('data\sounds\gun\fire.wav');
snd__step[0] := snd_Load('data\sounds\\footsteps\dirt1.wav');
snd__step[1] := snd_Load('data\sounds\\footsteps\dirt2.wav');

ShadowTex := glTex_Load('data\textures\shadow.jpg');
Crosshair := glTex_Load('data\textures\crosshair.tga');
HUDTex    := glTex_Load('data\textures\hud.tga');
end;

procedure TPlayer.ResetKeys;
var
 i : integer;
begin
for i := 0 to HIGH(Keys) do
 Keys[i].Down := false;
snd__curstart := -1;
snd__curstop  := -1;
snd__curfire  := -1; 
end;

procedure TPlayer.Start;
begin
ResetKeys;
Health := 100;
Money  := 0;
Frags  := 0;
Angle  := Vector(0, pi/2, 0);
Acc    := Vector(0, 0, 0);
Speed  := Vector(0, 0, 0);
Norm   := Vector(0, 1, 0);
Pos    := Vector(64, 32, 64);
snd__curstart := -1;
snd__curstop  := -1;
snd__curfire  := -1;
UseReload := 0;
RedFX := FX_COLOR;
GunA  := 0;
GunW  := 0;
end;

procedure TPlayer.Free;
begin
snd_Free(snd__start);
snd_Free(snd__stop);
snd_Free(snd__fire);
glTex_Free(ShadowTex);
glTex_Free(Crosshair);
glTex_Free(HUDTex);
end;

procedure TPlayer.Hit(dmg: integer);
begin
if Health <> 0 then
 begin
 Health := max(0, Health - dmg);
 if Health = 0 then
  begin
  Pos.Y  := Pos.Y - 1.5;
  l_menu.Add_Score(Frags);  // впишем себя в доску позора! 
  end;
 RedFX  := 1;
 end;
end;

procedure TPlayer.Update;
var
 i      : integer;
 a      : single;
 ac     : single;
 offset : TVector;
begin
if Health = 0 then
 begin
 snd_Stop(snd__curfire);
 Angle.X := Angle.X + (pi/2 - Angle.X)/64;
 Exit;
 end;

Angle.Y := Angle.Y + MDelta.X/628;
Angle.X := min(max(Angle.X - MDelta.Y/628, -pi/2), pi/2);

// эффекты повреждения
RedFX := RedFX + (FX_COLOR - RedFX)/32;

// -1 т.к. последнюю клавишу обрабатываю вручную (см. конец процедуры)
for i := 0 to HIGH(Keys) do
 Keys[i].Down := KeyDown(Keys[i].ID);

if (Keys[KEY_FORW].Down xor Keys[KEY_BACK].Down) or
   (Keys[KEY_LEFT].Down xor Keys[KEY_RIGHT].Down) then
 ac := MaxAcc
else
 ac := 0.02 * sin(V_Angle(norm, Vector(1, 0, 0)));

a := Angle.Y;
Acc := Vector(0, 0, 0);

if Keys[KEY_FORW].Down  then Acc := V_Add(Acc, Vector(cos(a), 0, sin(a)));
if Keys[KEY_BACK].Down  then Acc := V_Add(Acc, Vector(-cos(a), 0, -sin(a)));
if Keys[KEY_LEFT].Down  then Acc := V_Add(Acc, Vector(cos(a - pi/2), 0, sin(a - pi/2)));
if Keys[KEY_RIGHT].Down then Acc := V_Add(Acc, Vector(cos(a + pi/2), 0, sin(a + pi/2)));

Acc.X := ac*(Acc.X) - Speed.X/10;
Acc.Z := ac*(Acc.Z) - Speed.Z/10;

Speed := V_Add(Speed, Acc);
Pos   := V_Add(Pos, Speed);

// Покачивание головой при ходьбе :)
if V_Length(Speed) > 0.02 then
 begin
 Angle.Z   := sin(ang)*pi/360;
 ang       := ang + 12 * deg2rad;
 inc(step_tick);
 if step_tick mod 15 = 0 then
  snd_Play(snd__step[Byte(step_tick mod 30 = 0)], false, Pos);
 end
else
 begin
 Angle.Z   := 0;
 ang       := 0;
 step_tick := 0;
 end;

// высота над уровнем мирового океана ;) 
pcollision := true; // далеко не уйдёшь цуко!
Map.Collision(Pos, Norm);
pcollision := false;

// Стрельба
offset.X := cos(Angle.Y) * cos(Angle.X) * GUN_LEN;
offset.Y := sin(Angle.X) * GUN_LEN + 2;
offset.Z := sin(Angle.Y) * cos(Angle.X) * GUN_LEN;
offset := V_Add(offset, Vector(cos(Angle.Y) * cos(Angle.X - pi/2) * 0.5,
                               sin(Angle.X - pi/2) * 0.5,
                               sin(Angle.Y) * cos(Angle.X - pi/2) * 0.5));
offset := V_Add(Pos, offset);


if Keys[KEY_FIRE].Down then
 if GunW = GUN_MAX_W then
  begin
  if trunc(GunA) div 15 mod 5 = 0 then // 8-ми ствольник
   begin
   // Сдвиг для ствола
   if snd__curfire < 0 then
    begin
    snd__curfire := snd_Play(snd__fire, true, offset);
    snd_Stop(snd__curstart);
    end;
   if Map.Fire(offset, Angle) then
    begin
    Money := min(Money + 1, 999);
    inc(Frags);
    end;
   end;
  end
 else
  begin
  snd_Stop(snd__curstop);
  if snd__curstart < 0 then
   snd__curstart := snd_Play(snd__start, false, Pos);
  GunW := min(GunW + 0.3, GUN_MAX_W)
  end
else
 begin
 if ((snd__curfire > -1) or (snd__curstart > -1)) and (snd__curstop = -1) then
  snd__curstop := snd_Play(snd__stop, false, offset);
 snd_Stop(snd__curfire);
 snd_Stop(snd__curstart);
 GunW := max(0, GunW - 0.3);
 end;
GunA := GunA + GunW;

snd_SetPos(snd__curstop,  offset);
snd_SetPos(snd__curstart, offset);
snd_SetPos(snd__curfire,  offset);

// Установка стационарки
if Keys[KEY_USE].Down and (Money > 100) then
 begin
 if UseReload < ZENIT_SET + 1 then
  UseReload := UseReload + 1;
 if UseReload = ZENIT_SET then
  with Map.Zenit_add^ do
   begin
   Pos    := self.Pos;
   Angle  := self.Angle;
   tAngle := Angle;
   dec(Money, 100);
   ReCalc;
   end;
 end
else
 UseReload := 0;
end;

procedure TPlayer.Render;
const
 CSIZE = 16;
var
 s, c  : single;
 VSIZE : single;
begin
if Health = 0 then
 Exit;
glClear(GL_DEPTH_BUFFER_BIT);

VSIZE := 64 + 256 * RedFX;

glDisable(GL_DEPTH_TEST);

// Рисуем тень (через одно место ;)
glTex_Enable(ShadowTex);
glBlend(3);

glPushMatrix;
glTranslatef(Pos.X, Pos.Y, Pos.Z);
glRotatef(-Angle.Y * rad2deg, 0, 1, 0);
glTranslatef(-Pos.X, -Pos.Y, -Pos.Z);
step := step + V_Length(Speed);
s := abs(sin(step/6 * pi)) * 0.3;
// Эх... Tomb Raider, где же ты сейчас ;)
glColor3f(1, 1, 1);
glBegin(GL_QUADS);
 glTexCoord2f(0, 0); glVertex3f(Pos.X - 0.7 - s, Pos.Y, Pos.Z - 1);
 glTexCoord2f(1, 0); glVertex3f(Pos.X + 0.7 + s, Pos.Y, Pos.Z - 1);
 glTexCoord2f(1, 1); glVertex3f(Pos.X + 0.7 + s, Pos.Y, Pos.Z + 1);
 glTexCoord2f(0, 1); glVertex3f(Pos.X - 0.7 - s, Pos.Y, Pos.Z + 1);
glEnd;
glPopMatrix;
glEnable(GL_DEPTH_TEST);

// Да это же... это... ПУЛЕМЁТ!!!
glBlend(0);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glPushMatrix;
glLoadIdentity;
// Расчёт положения источника света
glLight_Pos(GL_LIGHT0, M_Mult(Camera.m, Vector(100, 100, 100)));

// Вращение/сдвиг ствола
if Keys[KEY_FIRE].Down then
 begin
 s := 0;
 if GunW = GUN_MAX_W then
  c := sin((trunc(GunA) mod 90)/90 * pi)
 else
  c := 0; 
 end
else
 begin
 s := sin(ang);
 c := 0;
 end;
 
glTranslatef(s/10, -2 + abs(s/3), c/10);
glRotatef(GunA, 0, 0, 1);

// Врубим текстуру окружения (блясти как...)
glTex_Enable(Map.EnvTex);

// Включаем сферическую генерацию текстурных координат
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);

// Химичим с матрицой текстуры
glMatrixMode(GL_TEXTURE);
// при поворотах игрока - поворачивается и текстура
glRotatef(Angle.Y * rad2deg, 0, 1, 0);
glMatrixMode(GL_MODELVIEW);

// Расчёт цвета текстуры под ногами
with Map.TexData[512 * trunc(Pos.Z) * 2 + trunc(Pos.X) * 2] do
 s := (R + G + B)/(3 * 255);
// т.е. если под ногами тень - пушка будет выглядеть темнее :)
glColor3f(s, s, s);
// Это всё конечно фейк, но всё же... рисуем
gun_xmd.Render;

// похимичили и хватит
glMatrixMode(GL_TEXTURE);
glLoadIdentity;
glMatrixMode(GL_MODELVIEW);

glDisable(GL_TEXTURE_GEN_S);
glDisable(GL_TEXTURE_GEN_T);
glPopMatrix;
glDisable(GL_LIGHTING);

// установка ортогональной матрицы вида
glBlend(1);
glSetOrtho(0, 0, 640, 480);

// Отрисовка прицела
glTex_Enable(Crosshair);
glColor3f(1, 1, 1);
glBegin(GL_QUADS);
 glTexCoord2f(0, 0); glVertex2f(320 - CSIZE, 240 - CSIZE);
 glTexCoord2f(1, 0); glVertex2f(320 + CSIZE, 240 - CSIZE);
 glTexCoord2f(1, 1); glVertex2f(320 + CSIZE, 240 + CSIZE);
 glTexCoord2f(0, 1); glVertex2f(320 - CSIZE, 240 + CSIZE);
glEnd;

(**************************************************)
/// маленькая, дурацкая фигнюшка по краям экрана ///
glTex_Disable;
glBegin(GL_QUADS);
// левая сторона
glColor4f(RedFX, FX_COLOR, FX_COLOR, 1);
 glVertex2f(0,     0);
 glVertex2f(0,     480);
glColor4f(RedFX, FX_COLOR, FX_COLOR, 0);
 glVertex2f(VSIZE, 480);
 glVertex2f(VSIZE, 0);
// правая сторона
glColor4f(RedFX, FX_COLOR, FX_COLOR, 1);
 glVertex2f(640,         0);
 glVertex2f(640,         480);
glColor4f(RedFX, FX_COLOR, FX_COLOR, 0);
 glVertex2f(640 - VSIZE, 480);
 glVertex2f(640 - VSIZE, 0);
// верхняя сторона
glColor4f(RedFX, FX_COLOR, FX_COLOR, 1);
 glVertex2f(0,   0);
 glVertex2f(640, 0);
glColor4f(RedFX, FX_COLOR, FX_COLOR, 0);
 glVertex2f(640, VSIZE);
 glVertex2f(0,   VSIZE);
// нижняя сторона
glColor4f(RedFX, FX_COLOR, FX_COLOR, 1);
 glVertex2f(0,   480);
 glVertex2f(640, 480);
glColor4f(RedFX, FX_COLOR, FX_COLOR, 0);
 glVertex2f(640, 480 - VSIZE);
 glVertex2f(0,   480 - VSIZE);
glEnd;

Draw_Hud;
end;

procedure TPlayer.Draw_Hud;

 procedure Draw_Icon(X, Y, Frame : integer);
 var
  s, t : single;
 begin
 s := 0.25 * Frame;
 t := s + 0.25;
 glTex_Enable(HUDTex);
 glBegin(GL_QUADS);
  glTexCoord2f(s, 1); glVertex2f(X,      Y);
  glTexCoord2f(t, 1); glVertex2f(X + 32, Y);
  glTexCoord2f(t, 0); glVertex2f(X + 32, Y + 32);
  glTexCoord2f(s, 0); glVertex2f(X,      Y + 32);
 glEnd;
 glTex_Disable;
 end;

const
 lx = 128;
 ly = 5;
var
 lw : integer; 
begin
(**************************************************)
/// Отрисовка HUD'а                              ///
// Жизня
glColor3f(0, 1, 0);
Draw_Icon(32, 416, 0);
glTextOut(IntToStr(Health), 72, 440, arial);
// Деньга
glColor3f(1, 1, 0);
Draw_Icon(448, 416, 1);
glTextOut(IntToStr(Money), 480, 440, arial);
// Фрага
glColor3f(1, 1, 1);
Draw_Icon(32, 32, 2);
glTextOut(IntToStr(Frags), 72, 56, arial);

glBlend(0);
glColor3f(0.8, 0.5, 0);
if (UseReload > 0) and (UseReload < ZENIT_SET + 1) then
 begin
 glBegin(GL_LINE_LOOP);
  glVertex2f(320 - lx, 400 - ly);
  glVertex2f(320 + lx, 400 - ly);
  glVertex2f(320 + lx, 400 + ly);
  glVertex2f(320 - lx, 400 + ly);
 glEnd;

 lw := trunc(UseReload * lx * 2 / ZENIT_SET);
 glBegin(GL_QUADS);
  glVertex2f(320 - lx + 2,      400 - ly + 1);
  glVertex2f(320 - lx - 1 + lw, 400 - ly + 1);
  glVertex2f(320 - lx - 1 + lw, 400 + ly - 2);
  glVertex2f(320 - lx + 2,      400 + ly - 2);
 glEnd;
 end;
end;

end.
