DEADSOFTWARE

implemented weapon queue (no network code for it yet)
[d2df-sdl.git] / src / game / g_player.pas
index a8ddec74f3634655089671cf4a1ed81cbb018f4f..989f11a095e2055a116a8a9316742798ac7ff684 100644 (file)
@@ -1,3 +1,19 @@
+(* Copyright (C)  DooM 2D:Forever Developers
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *)
+{$MODE DELPHI}
 unit g_player;
 
 interface
@@ -96,6 +112,7 @@ type
     Air:        Integer;
     JetFuel:    Integer;
     CurrWeap:   Byte;
+    NextWeap:   WORD;
     Ammo:       Array [A_BULLETS..A_CELLS] of Word;
     MaxAmmo:    Array [A_BULLETS..A_CELLS] of Word;
     Weapon:     Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Boolean;
@@ -135,6 +152,7 @@ type
     FFlag:      Byte;
     FSecrets:   Integer;
     FCurrWeap:  Byte;
+    FNextWeap:  WORD;
     FBFGFireCounter: SmallInt;
     FLastSpawnerUID: Word;
     FLastHit:   Byte;
@@ -189,6 +207,10 @@ type
     procedure   Jump();
     procedure   Use();
 
+    procedure cycleWeapon (dir: Integer);
+    function getNextWeaponIndex (): Byte; // return 255 for "no switch"
+    procedure resetWeaponQueue ();
+
   public
     FDamageBuffer:   Integer;
 
@@ -243,6 +265,7 @@ type
     procedure   SetFlag(Flag: Byte);
     function    DropFlag(): Boolean;
     procedure   AllRulez(Health: Boolean);
+    procedure   RestoreHealthArmor();
     procedure   FragCombo();
     procedure   GiveItem(ItemType: Byte);
     procedure   Damage(value: Word; SpawnerUID: Word; vx, vy: Integer; t: Byte); virtual;
@@ -270,6 +293,8 @@ type
     procedure   NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1);
     procedure   DoLerp(Level: Integer = 2);
     procedure   SetLerp(XTo, YTo: Integer);
+    procedure   QueueWeaponSwitch(Weapon: Byte);
+    procedure   RealizeCurrentWeapon();
     procedure   JetpackOn;
     procedure   JetpackOff;
 
@@ -470,7 +495,7 @@ implementation
 uses
   e_log, g_map, g_items, g_console, SysUtils, g_gfx, Math,
   g_options, g_triggers, g_menu, MAPDEF, g_game,
-  WADEDITOR, g_main, g_monsters, CONFIG, g_language, g_net, g_netmsg;
+  wadreader, g_main, g_monsters, CONFIG, g_language, g_net, g_netmsg;
 
 type
   TBotProfile = record
@@ -514,11 +539,14 @@ const
   TEAMCOLOR: Array [TEAM_RED..TEAM_BLUE] of TRGB = ((R:255; G:0; B:0),
                                                    (R:0; G:0; B:255));
   DIFFICULT_EASY: TDifficult = (DiagFire: 32; InvisFire: 32; DiagPrecision: 32;
-                                FlyPrecision: 32; Cover: 32; CloseJump: 32);
+                                FlyPrecision: 32; Cover: 32; CloseJump: 32;
+                                WeaponPrior:(0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0));
   DIFFICULT_MEDIUM: TDifficult = (DiagFire: 127; InvisFire: 127; DiagPrecision: 127;
-                                  FlyPrecision: 127; Cover: 127; CloseJump: 127);
+                                  FlyPrecision: 127; Cover: 127; CloseJump: 127;
+                                WeaponPrior:(0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0));
   DIFFICULT_HARD: TDifficult = (DiagFire: 255; InvisFire: 255; DiagPrecision: 255;
-                                FlyPrecision: 255; Cover: 255; CloseJump: 255);
+                                FlyPrecision: 255; Cover: 255; CloseJump: 255;
+                                WeaponPrior:(0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0));
   WEAPON_PRIOR1: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte =
                                 (WEAPON_SUPERPULEMET, WEAPON_SHOTGUN2, WEAPON_SHOTGUN1,
                                  WEAPON_CHAINGUN, WEAPON_PLASMA, WEAPON_ROCKETLAUNCHER,
@@ -777,6 +805,8 @@ begin
   Mem.ReadInt(gPlayers[a].FSecrets);
 // Òåêóùåå îðóæèå:
   Mem.ReadByte(gPlayers[a].FCurrWeap);
+// Ñëåäóþùåå æåëàåìîå îðóæèå:
+  Mem.ReadWord(gPlayers[a].FNextWeap);
 // Âðåìÿ çàðÿäêè BFG:
   Mem.ReadSmallInt(gPlayers[a].FBFGFireCounter);
 // Áóôåð óðîíà:
@@ -2156,6 +2186,7 @@ procedure TPlayer.Draw();
 var
   ID: DWORD;
   w, h: Word;
+  dr: Boolean;
 begin
   if FLive then
   begin
@@ -2175,7 +2206,16 @@ begin
     begin
       if (gPlayerDrawn <> nil) and ((Self = gPlayerDrawn) or
          ((FTeam = gPlayerDrawn.Team) and (gGameSettings.GameMode <> GM_DM))) then
-        FModel.Draw(FObj.X, FObj.Y, 200)
+      begin
+        if (FMegaRulez[MR_INVIS] - gTime) <= 2100 then
+          dr := not Odd((FMegaRulez[MR_INVIS] - gTime) div 300)
+        else
+          dr := True;
+        if dr then
+          FModel.Draw(FObj.X, FObj.Y, 200)
+        else
+          FModel.Draw(FObj.X, FObj.Y);
+      end
       else
         FModel.Draw(FObj.X, FObj.Y, 254);
     end
@@ -3220,87 +3260,105 @@ begin
               150, 0, 0);
 end;
 
-procedure TPlayer.NextWeapon();
-var
-  i: Byte;
-  ok: Boolean;
+procedure TPlayer.QueueWeaponSwitch(Weapon: Byte);
 begin
   if g_Game_IsClient then Exit;
-  if FBFGFireCounter <> -1 then Exit;
-
-  if FTime[T_SWITCH] > gTime then Exit;
-
-  for i := WEAPON_KASTET to WEAPON_SUPERPULEMET do
-    if FReloading[i] > 0 then Exit;
+  if Weapon > High(FWeapon) then Exit;
+  FNextWeap := FNextWeap or (1 shl Weapon);
+end;
 
-  ok := False;
+procedure TPlayer.resetWeaponQueue ();
+begin
+  FNextWeap := 0;
+end;
 
-  for i := FCurrWeap+1 to WEAPON_SUPERPULEMET do
-    if FWeapon[i] then
+// return 255 for "no switch"; resets `FNextWeap`
+function TPlayer.getNextWeaponIndex (): Byte;
+var
+  i: Word;
+  wantThisWeapon: array[0..64] of Boolean;
+begin
+  result := 255; // default result: "no switch"
+  for i := 0 to High(wantThisWeapon) do wantThisWeapon[i] := false;
+  for i := 0 to High(FWeapon) do if (FNextWeap and (1 shl i)) <> 0 then wantThisWeapon[i] := true;
+  if wantThisWeapon[FCurrWeap] then
+  begin
+    // these hacks implements alternating between SG and SSG; sorry
+    if FCurrWeap = WEAPON_SHOTGUN1 then wantThisWeapon[WEAPON_SHOTGUN2] := true;
+    if FCurrWeap = WEAPON_SHOTGUN2 then wantThisWeapon[WEAPON_SHOTGUN1] := true;
+    // these hacks implements alternating between knuckles and chainsaw; sorry
+    if FCurrWeap = WEAPON_KASTET then wantThisWeapon[WEAPON_SAW] := true;
+    if FCurrWeap = WEAPON_SAW then wantThisWeapon[WEAPON_KASTET] := true;
+  end;
+  // now exclude currently selected weapon from the set
+  wantThisWeapon[FCurrWeap] := false;
+  // no more hacks (yet)
+  // do not reset weapon queue, it will be done in `RealizeCurrentWeapon()`
+  // now try weapons in descending order
+  for i := High(FWeapon) downto 0 do
+  begin
+    if wantThisWeapon[i] and FWeapon[i] then
     begin
-      FCurrWeap := i;
-      ok := True;
-      Break;
+      // i found her!
+      result := Byte(i);
+      exit;
     end;
-
-  if not ok then
-    for i := WEAPON_KASTET to FCurrWeap-1 do
-      if FWeapon[i] then
-      begin
-        FCurrWeap := i;
-        Break;
-      end;
-
-  FTime[T_SWITCH] := gTime+156;
-
-  if FCurrWeap = WEAPON_SAW then
-    FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
-
-  FModel.SetWeapon(FCurrWeap);
-
-  if g_Game_IsNet then MH_SEND_PlayerStats(FUID);
+  end;
 end;
 
-procedure TPlayer.PrevWeapon();
+procedure TPlayer.RealizeCurrentWeapon();
 var
-  i: Byte;
-  ok: Boolean;
+  i, nw: Byte;
 begin
-  if g_Game_IsClient then Exit;
-  if FBFGFireCounter <> -1 then Exit;
-
-  if FTime[T_SWITCH] > gTime then Exit;
-
-  for i := WEAPON_KASTET to WEAPON_SUPERPULEMET do
-    if FReloading[i] > 0 then Exit;
-
-  ok := False;
+  nw := getNextWeaponIndex();
+  if nw > High(FWeapon) then begin resetWeaponQueue(); exit; end; // don't forget to reset queue here!
 
-  if FCurrWeap > 0 then
-    for i := FCurrWeap-1 downto WEAPON_KASTET do
-      if FWeapon[i] then
-      begin
-        FCurrWeap := i;
-        ok := True;
-        Break;
-      end;
+  if FBFGFireCounter <> -1 then exit;
+  if FTime[T_SWITCH] > gTime then exit;
 
-  if not ok then
-    for i := WEAPON_SUPERPULEMET downto FCurrWeap+1 do
-      if FWeapon[i] then
-      begin
-        FCurrWeap := i;
-        Break;
-      end;
+  for i := WEAPON_KASTET to WEAPON_SUPERPULEMET do if FReloading[i] > 0 then exit;
 
-  FTime[T_SWITCH] := gTime+156;
+  if FWeapon[nw] then
+  begin
+    FCurrWeap := nw;
+    FTime[T_SWITCH] := gTime+156;
+    if FCurrWeap = WEAPON_SAW then FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
+    FModel.SetWeapon(FCurrWeap);
+    if g_Game_IsNet then MH_SEND_PlayerStats(FUID);
+  end;
+  // reset weapon queue; `getNextWeaponIndex()` guarantees to not select a weapon player don't have
+  resetWeaponQueue();
+end;
 
-  if FCurrWeap = WEAPON_SAW then
-    FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
+procedure TPlayer.cycleWeapon (dir: Integer);
+var
+  i, cwi: Integer;
+begin
+  if dir < 0 then dir := 1 else if dir > 0 then dir := 1 else exit;
+  cwi := FCurrWeap;
+  for i := 0 to High(FWeapon) do
+  begin
+    cwi := cwi+dir;
+         if cwi < 0 then cwi += length(FWeapon)
+    else if cwi > High(FWeapon) then cwi := cwi-length(FWeapon);
+    if FWeapon[cwi] then
+    begin
+      QueueWeaponSwitch(Byte(cwi));
+      exit;
+    end;
+  end;
+end;
 
-  FModel.SetWeapon(FCurrWeap);
+procedure TPlayer.NextWeapon();
+begin
+  if g_Game_IsClient then Exit;
+  cycleWeapon(1);
+end;
 
-  if g_Game_IsNet then MH_SEND_PlayerStats(FUID);
+procedure TPlayer.PrevWeapon();
+begin
+  if g_Game_IsClient then Exit;
+  cycleWeapon(-1);
 end;
 
 procedure TPlayer.SetWeapon(W: Byte);
@@ -3311,6 +3369,7 @@ begin
 
   FCurrWeap := W;
   FModel.SetWeapon(CurrWeap);
+  resetWeaponQueue();
 end;
 
 function TPlayer.PickItem(ItemType: Byte; respawn: Boolean; var remove: Boolean): Boolean;
@@ -3631,6 +3690,7 @@ begin
           if FBFGFireCounter = -1 then
           begin
             FCurrWeap := WEAPON_KASTET;
+            resetWeaponQueue();
             FModel.SetWeapon(WEAPON_KASTET);
           end;
           if gFlash <> 0 then
@@ -3985,6 +4045,7 @@ begin
     FWeapon[WEAPON_PISTOL] := True;
     FWeapon[WEAPON_KASTET] := True;
     FCurrWeap := WEAPON_PISTOL;
+    resetWeaponQueue();
 
     FModel.SetWeapon(FCurrWeap);
 
@@ -5138,6 +5199,7 @@ begin
   FSavedState.Air := FAir;
   FSavedState.JetFuel := FJetFuel;
   FSavedState.CurrWeap := FCurrWeap;
+  FSavedState.NextWeap := FNextWeap;
 
   for i := 0 to 3 do
     FSavedState.Ammo[i] := FAmmo[i];
@@ -5159,6 +5221,7 @@ begin
   FAir := FSavedState.Air;
   FJetFuel := FSavedState.JetFuel;
   FCurrWeap := FSavedState.CurrWeap;
+  FNextWeap := FSavedState.NextWeap;
 
   for i := 0 to 3 do
     FAmmo[i] := FSavedState.Ammo[i];
@@ -5237,6 +5300,8 @@ begin
   Mem.WriteInt(FSecrets);
 // Òåêóùåå îðóæèå:
   Mem.WriteByte(FCurrWeap);
+// Æåëàåìîå îðóæèå:
+  Mem.WriteWord(FNextWeap);
 // Âðåìÿ çàðÿäêè BFG:
   Mem.WriteSmallInt(FBFGFireCounter);
 // Áóôåð óðîíà:
@@ -5373,6 +5438,8 @@ begin
   Mem.ReadInt(FSecrets);
 // Òåêóùåå îðóæèå:
   Mem.ReadByte(FCurrWeap);
+// Æåëàåìîå îðóæèå:
+  Mem.ReadWord(FNextWeap);
 // Âðåìÿ çàðÿäêè BFG:
   Mem.ReadSmallInt(FBFGFireCounter);
 // Áóôåð óðîíà:
@@ -5461,6 +5528,12 @@ begin
   FRulez := FRulez+[R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE];
 end;
 
+procedure TPlayer.RestoreHealthArmor();
+begin
+  FHealth := PLAYER_HP_LIMIT;
+  FArmor := PLAYER_AP_LIMIT;
+end;
+
 procedure TPlayer.FragCombo();
 var
   Param: Integer;
@@ -5513,6 +5586,7 @@ begin
           if FBFGFireCounter < 1 then
           begin
             FCurrWeap := WEAPON_KASTET;
+            resetWeaponQueue();
             FModel.SetWeapon(WEAPON_KASTET);
           end;
           if gFlash <> 0 then
@@ -5544,6 +5618,50 @@ begin
         FJetFuel := JET_MAX;
       end;
 
+    ITEM_WEAPON_SAW: FWeapon[WEAPON_SAW] := True;
+    ITEM_WEAPON_SHOTGUN1: FWeapon[WEAPON_SHOTGUN1] := True;
+    ITEM_WEAPON_SHOTGUN2: FWeapon[WEAPON_SHOTGUN2] := True;
+    ITEM_WEAPON_CHAINGUN: FWeapon[WEAPON_CHAINGUN] := True;
+    ITEM_WEAPON_ROCKETLAUNCHER: FWeapon[WEAPON_ROCKETLAUNCHER] := True;
+    ITEM_WEAPON_PLASMA: FWeapon[WEAPON_PLASMA] := True;
+    ITEM_WEAPON_BFG: FWeapon[WEAPON_BFG] := True;
+    ITEM_WEAPON_SUPERPULEMET: FWeapon[WEAPON_SUPERPULEMET] := True;
+
+    ITEM_AMMO_BULLETS: if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
+    ITEM_AMMO_BULLETS_BOX: if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
+    ITEM_AMMO_SHELLS: if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
+    ITEM_AMMO_SHELLS_BOX: if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 25, FMaxAmmo[A_SHELLS]);
+    ITEM_AMMO_ROCKET: if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
+    ITEM_AMMO_ROCKET_BOX: if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 5, FMaxAmmo[A_ROCKETS]);
+    ITEM_AMMO_CELL: if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
+    ITEM_AMMO_CELL_BIG: if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 100, FMaxAmmo[A_CELLS]);
+
+    ITEM_AMMO_BACKPACK:
+      if (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or
+         (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or
+         (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or
+         (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) then
+      begin
+        FMaxAmmo[A_BULLETS] := 400;
+        FMaxAmmo[A_SHELLS] := 100;
+        FMaxAmmo[A_ROCKETS] := 100;
+        FMaxAmmo[A_CELLS] := 600;
+
+        if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
+        if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
+        if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
+        if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
+
+        FRulez := FRulez + [R_ITEM_BACKPACK];
+      end;
+
+    ITEM_KEY_RED: if not (R_KEY_RED in FRulez) then Include(FRulez, R_KEY_RED);
+    ITEM_KEY_GREEN: if not (R_KEY_GREEN in FRulez) then Include(FRulez, R_KEY_GREEN);
+    ITEM_KEY_BLUE: if not (R_KEY_BLUE in FRulez) then Include(FRulez, R_KEY_BLUE);
+
+    ITEM_BOTTLE: if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 4, PLAYER_HP_LIMIT);
+    ITEM_HELMET: if FArmor < PLAYER_AP_LIMIT then IncMax(FArmor, 5, PLAYER_AP_LIMIT);
+
     else
       Exit;
   end;
@@ -5642,8 +5760,9 @@ begin
         g_Player_CreateGibs(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
                             FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
                             FModelName, FColor);
+        // Çâóê ìÿñà îò òðóïà:
         pm := g_PlayerModel_Get(FModelName);
-        pm.PlaySound(MODELSOUND_DIE, 3, FObj.X, FObj.Y);
+        pm.PlaySound(MODELSOUND_DIE, 5, FObj.X, FObj.Y);
         pm.Free;
       end;
     end
@@ -5828,6 +5947,7 @@ begin
 
   FAIFlags := nil;
   FSelectedWeapon := FCurrWeap;
+  resetWeaponQueue();
   FTargetUID := 0;
 end;
 
@@ -6254,6 +6374,9 @@ begin
             end;
         end;
 
+  //HACK! (does it belongs there?)
+  RealizeCurrentWeapon();
+
 // Åñëè åñòü âîçìîæíûå öåëè:
 // (Ñòðåëÿåì ïî íàïðàâëåíèþ ê öåëÿì)
   if (targets <> nil) and (GetAIFlag('NEEDFIRE') <> '') then