DEADSOFTWARE

generalize warmup stuff
[d2df-sdl.git] / src / game / g_player.pas
index ddf4825b2ee994232eaf81bc22a58ff101935c65..30a71f18306d98e7b0b79486399b0323284ee28d 100644 (file)
@@ -2,8 +2,7 @@
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * the Free Software Foundation, version 3 of the License ONLY.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -97,6 +96,8 @@ const
   SUICIDE_DAMAGE  = 112;
   WEAPON_DELAY    = 5;
 
+  PLAYER_BURN_TIME = 110;
+
   PLAYER1_DEF_COLOR: TRGB = (R:64; G:175; B:48);
   PLAYER2_DEF_COLOR: TRGB = (R:96; G:96; B:96);
 
@@ -266,7 +267,9 @@ type
     FReady:     Boolean;
     FDummy:     Boolean;
     FFireTime:  Integer;
+    FSpawnInvul: Integer;
     FHandicap:  Integer;
+    FWaitForFirstSpawn: Boolean; // set to `true` in server, used to spawn a player on first full state request
 
     // debug: viewport offset
     viewPortX, viewPortY, viewPortW, viewPortH: Integer;
@@ -281,6 +284,7 @@ type
     procedure   ReleaseKeys();
     procedure   SetModel(ModelName: String);
     procedure   SetColor(Color: TRGB);
+    function    GetColor(): TRGB;
     procedure   SetWeapon(W: Byte);
     function    IsKeyPressed(K: Byte): Boolean;
     function    GetKeys(): Byte;
@@ -318,7 +322,7 @@ type
     procedure   DrawPickup();
     procedure   DrawRulez();
     procedure   DrawAim();
-    procedure   DrawIndicator();
+    procedure   DrawIndicator(Color: TRGB);
     procedure   DrawBubble();
     procedure   DrawGUI();
     procedure   Update(); virtual;
@@ -336,7 +340,7 @@ type
     procedure   FlamerOff;
     procedure   JetpackOn;
     procedure   JetpackOff;
-    procedure   CatchFire(Attacker: Word);
+    procedure   CatchFire(Attacker: Word; Timeout: Integer = PLAYER_BURN_TIME);
 
     //WARNING! this does nothing for now, but still call it!
     procedure positionChanged (); //WARNING! call this after entity position was changed, or coldet will not work right!
@@ -552,11 +556,12 @@ var
   gTeamStat: TTeamStat;
   gFly: Boolean = False;
   gAimLine: Boolean = False;
-  gChatBubble: Byte = 0;
-  gPlayerIndicator: Boolean = True;
+  gChatBubble: Integer = 0;
+  gPlayerIndicator: Integer = 1;
+  gPlayerIndicatorStyle: Integer = 0;
   gNumBots: Word = 0;
-  gLMSPID1: Word = 0;
-  gLMSPID2: Word = 0;
+  gSpectLatchPID1: Word = 0;
+  gSpectLatchPID2: Word = 0;
   MAX_RUNVEL: Integer = 8;
   VEL_JUMP: Integer = 10;
   SHELL_TIMEOUT: Cardinal = 60000;
@@ -609,7 +614,7 @@ uses
   g_holmes,
 {$ENDIF}
   e_log, g_map, g_items, g_console, g_gfx, Math,
-  g_options, g_triggers, g_menu, g_game, g_grid,
+  g_options, g_triggers, g_menu, g_game, g_grid, e_res,
   wadreader, g_main, g_monsters, CONFIG, g_language,
   g_net, g_netmsg, g_window,
   utils, xstreams;
@@ -1067,12 +1072,6 @@ begin
         Break;
       end;
 
-// Èìåíè íåò, çàäàåì ñëó÷àéíîå:
-  if _name = '' then
-    repeat
-      _name := Format('DFBOT%.2d', [Random(100)]);
-    until g_Player_ValidName(_name);
-
 // Âûáèðàåì ñëó÷àéíóþ ìîäåëü:
   _model := m[Random(Length(m))];
 
@@ -1083,7 +1082,11 @@ begin
                                          Min(Random(9)*32, 255)),
                                     Team, True)) as TBot do
   begin
-    Name := _name;
+  // Åñëè èìåíè íåò, äåëàåì åãî èç UID áîòà
+    if _name = '' then
+      Name := Format('DFBOT%.5d', [UID])
+    else
+      Name := _name;
 
     case Difficult of
       1: FDifficult := DIFFICULT_EASY;
@@ -1250,14 +1253,16 @@ var
   a, b: Integer;
   config: TConfig;
   sa: SSArray;
+  path: AnsiString;
 begin
   BotNames := nil;
 
-  if not FileExists(DataDir + BOTNAMES_FILENAME) then
+  path := BOTNAMES_FILENAME;
+  if e_FindResource(DataDirs, path) = false then
     Exit;
 
 // ×èòàåì âîçìîæíûå èìåíà áîòîâ èç ôàéëà:
-  AssignFile(F, DataDir + BOTNAMES_FILENAME);
+  AssignFile(F, path);
   Reset(F);
 
   while not EOF(F) do
@@ -1278,7 +1283,7 @@ begin
   g_Bot_MixNames();
 
 // ×èòàåì ôàéë ñ ïàðàìåòðàìè áîòîâ:
-  config := TConfig.CreateFile(DataDir + BOTLIST_FILENAME);
+  config := TConfig.CreateFile(path);
   BotList := nil;
   a := 0;
 
@@ -2033,6 +2038,11 @@ begin
     if FModel <> nil then FModel.Color := Color;
 end;
 
+function TPlayer.GetColor(): TRGB;
+begin
+  result := FModel.Color;
+end;
+
 procedure TPlayer.SwitchTeam;
 begin
   if g_Game_IsClient then
@@ -2170,6 +2180,8 @@ begin
   FJustTeleported := False;
   FNetTime := 0;
 
+  FWaitForFirstSpawn := false;
+
   resetWeaponQueue();
 end;
 
@@ -2216,6 +2228,7 @@ begin
     FMegaRulez[MR_SUIT] := 0;
     FMegaRulez[MR_INVUL] := 0;
     FMegaRulez[MR_INVIS] := 0;
+    FSpawnInvul := 0;
     FBerserk := 0;
   end;
 
@@ -2320,23 +2333,72 @@ begin
   inherited;
 end;
 
-procedure TPlayer.DrawIndicator();
+procedure TPlayer.DrawIndicator(Color: TRGB);
 var
   indX, indY: Integer;
   indW, indH: Word;
+  indA: Single;
+  a: TDFPoint;
+  nW, nH: Byte;
   ID: DWORD;
+  c: TRGB;
 begin
   if FAlive then
-    begin
-      indX := FObj.X+FObj.Rect.X;
-      indY := FObj.Y;
-      if g_Texture_Get('TEXTURE_PLAYER_INDICATOR', ID) then
+    case gPlayerIndicatorStyle of
+      0:
         begin
-          e_GetTextureSize(ID, @indW, @indH);
-          e_Draw(ID, indX + indW div 2, indY - indH, 0, True, False);
+          if g_Texture_Get('TEXTURE_PLAYER_INDICATOR', ID) then
+          begin
+            e_GetTextureSize(ID, @indW, @indH);
+            a.X := indW div 2;
+            a.Y := indH div 2;
+
+            if (FObj.X + FObj.Rect.X) < 0 then
+            begin
+              indA := 90;
+              indX := FObj.X + FObj.Rect.X + FObj.Rect.Width;
+              indY := FObj.Y + FObj.Rect.Y + (FObj.Rect.Height - indW) div 2;
+            end
+
+            else if (FObj.X + FObj.Rect.X + FObj.Rect.Width) > Max(gMapInfo.Width, gPlayerScreenSize.X) then
+            begin
+              indA := 270;
+              indX := FObj.X + FObj.Rect.X - indH;
+              indY := FObj.Y + FObj.Rect.Y + (FObj.Rect.Height - indW) div 2;
+            end
+
+            else if (fObj.Y - indH) < 0 then
+            begin
+              indA := 180;
+              indX := FObj.X + FObj.Rect.X + (FObj.Rect.Width - indW) div 2;
+              indY := FObj.Y + FObj.Rect.Y + FObj.Rect.Height;
+            end
+
+            else
+            begin
+              indA := 0;
+              indX := FObj.X + FObj.Rect.X + (FObj.Rect.Width - indW) div 2;
+              indY := FObj.Y - indH;
+            end;
+
+            indX := EnsureRange(indX, 0, Max(gMapInfo.Width, gPlayerScreenSize.X) - indW);
+            indY := EnsureRange(indY, 0, Max(gMapInfo.Height, gPlayerScreenSize.Y) - indH);
+
+            c := e_Colors;
+            e_Colors := Color;
+            e_DrawAdv(ID, indX, indY, 0, True, False, indA, @a);
+            e_Colors := c;
+          end;
+        end;
+
+      1:
+        begin
+          e_TextureFontGetSize(gStdFont, nW, nH);
+          indX := FObj.X + FObj.Rect.X + (FObj.Rect.Width - Length(FName) * nW) div 2;
+          indY := FObj.Y - nH;
+          e_TextureFontPrintEx(indX, indY, FName, gStdFont, Color.R, Color.G, Color.B, 1.0, True);
         end;
     end;
-  //e_TextureFontPrint(indX, indY, FName, gStdFont); // Shows player name overhead
 end;
 
 procedure TPlayer.DrawBubble();
@@ -2445,7 +2507,7 @@ begin
       end;
     end;
 
-    if (FMegaRulez[MR_INVUL] > gTime) and (gPlayerDrawn <> Self) then
+    if (FMegaRulez[MR_INVUL] > gTime) and ((gPlayerDrawn <> Self) or (FSpawnInvul >= gTime)) then
       if g_Texture_Get('TEXTURE_PLAYER_INVULPENTA', ID) then
       begin
         e_GetTextureSize(ID, @w, @h);
@@ -2704,7 +2766,14 @@ begin
       e_CharFont_PrintEx(gMenuSmallFont, X-16-tw, Y+32, s, _RGB(255, 0, 0));
     end;
 
-    if gShowLives and (gGameSettings.MaxLives > 0) then
+    if gLMSRespawn > LMS_RESPAWN_NONE then
+    begin
+      s := _lc[I_GAME_WARMUP];
+      e_CharFont_GetSize(gMenuFont, s, tw, th);
+      s := s + ': ' + IntToStr((gLMSRespawnTime - gTime) div 1000);
+      e_CharFont_PrintEx(gMenuFont, X-64-tw, SY-32, s, _RGB(0, 255, 0));
+    end
+    else if gShowLives and (gGameSettings.MaxLives > 0) then
     begin
       s := IntToStr(Lives);
       e_CharFont_GetSize(gMenuFont, s, tw, th);
@@ -2813,7 +2882,7 @@ var
   dr: Boolean;
 begin
   // Ïðè âçÿòèè íåóÿçâèìîñòè ðèñóåòñÿ èíâåðñèîííûé áåëûé ôîí
-  if FMegaRulez[MR_INVUL] >= gTime then
+  if (FMegaRulez[MR_INVUL] >= gTime) and (FSpawnInvul < gTime) then
   begin
     if (FMegaRulez[MR_INVUL]-gTime) <= 2100 then
       dr := not Odd((FMegaRulez[MR_INVUL]-gTime) div 300)
@@ -3189,13 +3258,17 @@ begin
   FJetSoundOff.PlayAt(FObj.X, FObj.Y);
 end;
 
-procedure TPlayer.CatchFire(Attacker: Word);
+procedure TPlayer.CatchFire(Attacker: Word; Timeout: Integer = PLAYER_BURN_TIME);
 begin
+  if Timeout <= 0 then
+    exit;
   if (FMegaRulez[MR_SUIT] > gTime) or (FMegaRulez[MR_INVUL] > gTime) then
     exit; // Íå çàãîðàåìñÿ êîãäà åñòü çàùèòà
+  if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2) then
+    exit; // Íå ïîäãîðàåì â âîäå íà âñÿêèé ñëó÷àé
   if FFireTime <= 0 then
     g_Sound_PlayExAt('SOUND_IGNITE', FObj.X, FObj.Y);
-  FFireTime := 100;
+  FFireTime := Timeout;
   FFireAttacker := Attacker;
   if g_Game_IsNet and g_Game_IsServer then
     MH_SEND_PlayerStats(FUID);
@@ -3473,7 +3546,8 @@ begin
       PushItem(ITEM_JETPACK);
 
 // Âûáðîñ êëþ÷åé:
-    if not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) then
+    if (not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) or
+       (not LongBool(gGameSettings.Options and GAME_OPTION_DMKEYS)) then
     begin
       if R_KEY_RED in FRulez then
         PushItem(ITEM_KEY_RED);
@@ -3593,7 +3667,7 @@ begin
     if srv and (OldLR = LMS_RESPAWN_NONE) and (gLMSRespawn > LMS_RESPAWN_NONE) then
     begin
       if NetMode = NET_SERVER then
-        MH_SEND_GameEvent(NET_EV_LMS_WARMUP, (gLMSRespawnTime - gTime) div 1000)
+        MH_SEND_GameEvent(NET_EV_LMS_WARMUP, gLMSRespawnTime - gTime)
       else
         g_Console_Add(Format(_lc[I_MSG_WARMUP_START], [(gLMSRespawnTime - gTime) div 1000]), True);
     end;
@@ -3660,8 +3734,8 @@ begin
   result := false;
   case weapon of
     WEAPON_KASTET, WEAPON_SAW: result := true;
-    WEAPON_SHOTGUN1, WEAPON_SHOTGUN2: result := (FAmmo[A_SHELLS] > 0);
-    WEAPON_PISTOL, WEAPON_CHAINGUN, WEAPON_SUPERPULEMET: result := (FAmmo[A_BULLETS] > 0);
+    WEAPON_SHOTGUN1, WEAPON_SHOTGUN2, WEAPON_SUPERPULEMET: result := (FAmmo[A_SHELLS] > 0);
+    WEAPON_PISTOL, WEAPON_CHAINGUN: result := (FAmmo[A_BULLETS] > 0);
     WEAPON_ROCKETLAUNCHER: result := (FAmmo[A_ROCKETS] > 0);
     WEAPON_PLASMA, WEAPON_BFG: result := (FAmmo[A_CELLS] > 0);
     WEAPON_FLAMETHROWER: result := (FAmmo[A_FUEL] > 0);
@@ -3851,9 +3925,9 @@ begin
 
   case ItemType of
     ITEM_MEDKIT_SMALL:
-      if FHealth < PLAYER_HP_SOFT then
+      if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
       begin
-        IncMax(FHealth, 10, PLAYER_HP_SOFT);
+        if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 10, PLAYER_HP_SOFT);
         Result := True;
         remove := True;
         FFireTime := 0;
@@ -3861,9 +3935,9 @@ begin
       end;
 
     ITEM_MEDKIT_LARGE:
-      if FHealth < PLAYER_HP_SOFT then
+      if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
       begin
-        IncMax(FHealth, 25, PLAYER_HP_SOFT);
+        if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 25, PLAYER_HP_SOFT);
         Result := True;
         remove := True;
         FFireTime := 0;
@@ -3889,9 +3963,9 @@ begin
       end;
 
     ITEM_SPHERE_BLUE:
-      if FHealth < PLAYER_HP_LIMIT then
+      if (FHealth < PLAYER_HP_LIMIT) or (FFireTime > 0) then
       begin
-        IncMax(FHealth, 100, PLAYER_HP_LIMIT);
+        if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 100, PLAYER_HP_LIMIT);
         Result := True;
         remove := True;
         FFireTime := 0;
@@ -3899,7 +3973,7 @@ begin
       end;
 
     ITEM_SPHERE_WHITE:
-      if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) then
+      if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) or (FFireTime > 0) then
       begin
         if FHealth < PLAYER_HP_LIMIT then
           FHealth := PLAYER_HP_LIMIT;
@@ -4104,7 +4178,7 @@ begin
             (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or
             (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or
             (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or
-            (FMaxAmmo[A_FUEL] < AmmoLimits[1, A_FUEL]) then
+            (FAmmo[A_FUEL] < FMaxAmmo[A_FUEL]) then
       begin
         FMaxAmmo[A_BULLETS] := AmmoLimits[1, A_BULLETS];
         FMaxAmmo[A_SHELLS] := AmmoLimits[1, A_SHELLS];
@@ -4120,6 +4194,8 @@ begin
           IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
         if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
           IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
+        if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then
+          IncMax(FAmmo[A_FUEL], 50, FMaxAmmo[A_FUEL]);
 
         FRulez := FRulez + [R_ITEM_BACKPACK];
         Result := True;
@@ -4197,9 +4273,9 @@ begin
           remove := True;
           FFireTime := 0;
         end;
-        if FHealth < PLAYER_HP_SOFT then
+        if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
         begin
-          FHealth := PLAYER_HP_SOFT;
+          if FHealth < PLAYER_HP_SOFT then FHealth := PLAYER_HP_SOFT;
           FBerserk := gTime+30000;
           Result := True;
           remove := True;
@@ -4211,15 +4287,16 @@ begin
       if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
       begin
         FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
+        FSpawnInvul := 0;
         Result := True;
         remove := True;
         if gFlash = 2 then Inc(FPickup, 5);
       end;
 
     ITEM_BOTTLE:
-      if FHealth < PLAYER_HP_LIMIT then
+      if (FHealth < PLAYER_HP_LIMIT) or (FFireTime > 0) then
       begin
-        IncMax(FHealth, 4, PLAYER_HP_LIMIT);
+        if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 4, PLAYER_HP_LIMIT);
         Result := True;
         remove := True;
         FFireTime := 0;
@@ -4296,6 +4373,7 @@ begin
   FMonsterKills := 0;
   FDeath := 0;
   FSecrets := 0;
+  FSpawnInvul := 0;
   FReady := False;
   if FNoRespawn then
   begin
@@ -4488,6 +4566,7 @@ begin
   FShellTimer := -1;
   FPain := 0;
   FLastHit := 0;
+  FSpawnInvul := 0;
 
   if not g_Game_IsServer then
     Exit;
@@ -4566,7 +4645,8 @@ begin
     FMaxAmmo[A_CELLS] := AmmoLimits[0, A_CELLS];
     FMaxAmmo[A_FUEL] := AmmoLimits[0, A_FUEL];
 
-    if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then
+    if (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) and
+       LongBool(gGameSettings.Options and GAME_OPTION_DMKEYS) then
       FRulez := [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE]
     else
       FRulez := [];
@@ -4602,6 +4682,13 @@ begin
   for a := Low(FMegaRulez) to High(FMegaRulez) do
     FMegaRulez[a] := 0;
 
+// Respawn invulnerability
+  if (gGameSettings.GameType <> GT_SINGLE) and (gGameSettings.SpawnInvul > 0) then
+  begin
+    FMegaRulez[MR_INVUL] := gTime + gGameSettings.SpawnInvul * 1000;
+    FSpawnInvul := FMegaRulez[MR_INVUL];
+  end;
+
   FDamageBuffer := 0;
   FJetpack := False;
   FCanJetpack := False;
@@ -4626,9 +4713,9 @@ begin
   FSpectatePlayer := -1;
   FSpawned := True;
 
-  if (gPlayer1 = nil) and (gLMSPID1 = FUID) then
+  if (gPlayer1 = nil) and (gSpectLatchPID1 = FUID) then
     gPlayer1 := self;
-  if (gPlayer2 = nil) and (gLMSPID2 = FUID) then
+  if (gPlayer2 = nil) and (gSpectLatchPID2 = FUID) then
     gPlayer2 := self;
 
   if g_Game_IsNet then
@@ -4665,12 +4752,12 @@ begin
   begin
     if Self = gPlayer1 then
     begin
-      gLMSPID1 := FUID;
+      gSpectLatchPID1 := FUID;
       gPlayer1 := nil;
-    end;
-    if Self = gPlayer2 then
+    end
+    else if Self = gPlayer2 then
     begin
-      gLMSPID2 := FUID;
+      gSpectLatchPID2 := FUID;
       gPlayer2 := nil;
     end;
   end;
@@ -5276,11 +5363,13 @@ begin
         if FFirePainTime <= 0 then
         begin
           if g_Game_IsServer then
-            Damage(5, FFireAttacker, 0, 0, HIT_FLAME);
-          FFirePainTime := 18;
+            Damage(2, FFireAttacker, 0, 0, HIT_FLAME);
+          FFirePainTime := 12 - FFireTime div 12;
         end;
         FFirePainTime := FFirePainTime - 1;
         FFireTime := FFireTime - 1;
+        if ((FFireTime mod 33) = 0) and (FMegaRulez[MR_INVUL] < gTime) then
+          FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
         if (FFireTime = 0) and g_Game_IsNet and g_Game_IsServer then
           MH_SEND_PlayerStats(FUID);
       end;
@@ -5312,7 +5401,7 @@ begin
             else if FHealth > -50 then Kill(K_HARDKILL, FLastSpawnerUID, FLastHit)
               else Kill(K_EXTRAHARDKILL, FLastSpawnerUID, FLastHit);
 
-      if FAlive then
+      if FAlive and ((FLastHit <> HIT_FLAME) or (FFireTime <= 0)) then
       begin
         if FDamageBuffer <= 20 then FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y)
           else if FDamageBuffer <= 55 then FModel.PlaySound(MODELSOUND_PAIN, 2, FObj.X, FObj.Y)
@@ -6221,6 +6310,7 @@ begin
       if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
       begin
         FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
+        FSpawnInvul := 0;
       end;
 
     ITEM_INVIS:
@@ -7878,4 +7968,6 @@ end;
 
 begin
   conRegVar('cheat_berserk_autoswitch', @gBerserkAutoswitch, 'autoswitch to fist when berserk pack taken', '',  true, true);
+  conRegVar('player_indicator', @gPlayerIndicator, 'Draw indicator only for current player, also for teammates, or not at all', 'Draw indicator only for current player, also for teammates, or not at all');
+  conRegVar('player_indicator_style', @gPlayerIndicatorStyle, 'Visual appearance of indicator', 'Visual appearance of indicator');
 end.