DEADSOFTWARE

Game: Fix soulsphere and megasphere being mixed up in the 'give' command
[d2df-sdl.git] / src / game / g_game.pas
index 7b4ae2ee0fa1958759e5166850cc184cbdb7c344..b675eb4612850e6f90020200656fd819ff4dcc1c 100644 (file)
@@ -33,6 +33,8 @@ type
     WarmupTime: Word;
     SpawnInvul: Word;
     ItemRespawnTime: Word;
+    RulezRespawnTime: Word;
+    RulezRespawnRandom: Word;
     MaxLives: Byte;
     Options: LongWord;
     WAD: String;
@@ -131,6 +133,7 @@ procedure g_Game_ChatSound(Text: String; Taunt: Boolean = True);
 procedure g_Game_Announce_GoodShot(SpawnerUID: Word);
 procedure g_Game_Announce_KillCombo(Param: Integer);
 procedure g_Game_Announce_BodyKill(SpawnerUID: Word);
+procedure g_Game_Effect_Bubbles(fX, fY: Integer; count: Word; devX, devY: Byte; Silent: Boolean = False);
 procedure g_Game_StartVote(Command, Initiator: string);
 procedure g_Game_CheckVote;
 procedure g_TakeScreenShot(Filename: string = '');
@@ -194,6 +197,7 @@ const
   GAME_OPTION_TEAMABSORBDAMAGE  = 1024;
   GAME_OPTION_ALLOWDROPFLAG     = 2048;
   GAME_OPTION_THROWFLAG         = 4096;
+  GAME_OPTION_RULEZRANDOM       = 8192;
 
   STATE_NONE        = 0;
   STATE_MENU        = 1;
@@ -667,7 +671,7 @@ end;
 
 // saves a shitty CSV containing the game stats passed to it
 procedure SaveGameStat(Stat: TEndCustomGameStat; Path: string);
-var 
+var
   s: TextFile;
   dir, fname, map, mode, etime: String;
   I: Integer;
@@ -709,7 +713,7 @@ begin
       //   if it's a coop game: monsters killed, monsters total, secrets found, secrets total
       //   otherwise nothing
       if Stat.GameMode in [GM_TDM, GM_CTF] then
-        WriteLn(s, 
+        WriteLn(s,
           Format('red_score,blue_score' + LineEnding + '%d,%d', [Stat.TeamStat[TEAM_RED].Score, Stat.TeamStat[TEAM_BLUE].Score]))
       else if Stat.GameMode in [GM_COOP, GM_SINGLE] then
         WriteLn(s,
@@ -728,6 +732,17 @@ begin
   CloseFile(s);
 end;
 
+procedure ClearDebugCvars();
+begin
+  g_debug_Sounds := False;
+  g_debug_Frames := False;
+  g_debug_WinMsgs := False;
+  g_debug_MonsterOff := False;
+  g_debug_BotAIOff := 0;
+  g_debug_HealthBar := False;
+  g_Debug_Player := False;
+end;
+
 function g_Game_ModeToText(Mode: Byte): string;
 begin
   Result := '';
@@ -4483,6 +4498,7 @@ begin
   gGameSettings.Options := gGameSettings.Options + GAME_OPTION_BOTVSMONSTER;
   gGameSettings.Options := gGameSettings.Options + GAME_OPTION_TEAMHITPROJECTILE;
   gGameSettings.Options := gGameSettings.Options + GAME_OPTION_TEAMHITTRACE;
+  gGameSettings.Options := gGameSettings.Options + GAME_OPTION_RULEZRANDOM;
   gSwitchGameMode := GM_SINGLE;
 
   gLMSRespawn := LMS_RESPAWN_NONE;
@@ -4673,6 +4689,8 @@ begin
 
   g_Game_ClearLoading();
 
+  ClearDebugCvars();
+
 // Íàñòðîéêè èãðû:
   gGameSettings.GameType := GT_SERVER;
   gGameSettings.GameMode := GameMode;
@@ -4806,6 +4824,8 @@ begin
 
   g_Game_ClearLoading();
 
+  ClearDebugCvars();
+
 // Íàñòðîéêè èãðû:
   gGameSettings.GameType := GT_CLIENT;
 
@@ -5179,7 +5199,7 @@ begin
           end;
         end;
 
-    g_Net_UnbanNonPermHosts();
+    g_Net_UnbanNonPerm();
   end;
 
   if gLastMap then
@@ -5515,6 +5535,7 @@ var
   a, b: Integer;
   stat: TPlayerStatArray;
   cmd: string;
+  it: PItem;
 
   procedure ParseGameFlag(Flag: LongWord; OffMsg, OnMsg: TStrings_Locale; OnMapChange: Boolean = False);
   var
@@ -5594,6 +5615,10 @@ begin
   begin
     ParseGameFlag(GAME_OPTION_TEAMHITPROJECTILE, I_MSG_FRIENDLY_PROJECT_TRACE_OFF, I_MSG_FRIENDLY_PROJECT_TRACE_ON);
   end
+  else if cmd = 'g_powerup_randomize_respawn' then
+  begin
+    ParseGameFlag(GAME_OPTION_RULEZRANDOM, I_MSG_RULEZ_RANDOM_OFF, I_MSG_RULEZ_RANDOM_ON, False);
+  end
   else if cmd = 'g_weaponstay' then
   begin
     ParseGameFlag(GAME_OPTION_WEAPONSTAY, I_MSG_WEAPONSTAY_OFF, I_MSG_WEAPONSTAY_ON);
@@ -5690,6 +5715,36 @@ begin
     g_Console_Add(Format('%s %d', [cmd, Integer(gsItemRespawnTime)]));
     if g_Game_IsServer then g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
   end
+  else if cmd = 'g_powerup_respawn_time' then
+  begin
+    if Length(P) > 1 then
+    begin
+      gsRulezRespawnTime := nclamp(StrToIntDef(P[1], gsRulezRespawnTime), 0, $FFFF);
+      if g_Game_IsServer then
+      begin
+        gGameSettings.RulezRespawnTime := gsRulezRespawnTime;
+        if g_Game_IsNet then MH_SEND_GameSettings;
+      end;
+    end;
+
+    g_Console_Add(Format('%s %d', [cmd, Integer(gsRulezRespawnTime)]));
+    if g_Game_IsServer then g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
+  end
+  else if cmd = 'g_powerup_time_random' then
+  begin
+    if Length(P) > 1 then
+    begin
+      gsRulezRespawnRandom := nclamp(StrToIntDef(P[1], gsRulezRespawnRandom), 0, $FFFF);
+      if g_Game_IsServer then
+      begin
+        gGameSettings.RulezRespawnRandom := gsRulezRespawnRandom;
+        if g_Game_IsNet then MH_SEND_GameSettings;
+      end;
+    end;
+
+    g_Console_Add(Format('%s %d', [cmd, Integer(gsRulezRespawnRandom)]));
+    if g_Game_IsServer then g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
+  end
   else if cmd = 'sv_intertime' then
   begin
     if (Length(P) > 1) then
@@ -5767,6 +5822,34 @@ begin
     begin
       a := StrToIntDef(p[1], 0);
       g_Force_Model_Set(a);
+      if (g_Force_Model_Get() <> 0) and (gPlayers <> nil) then
+      begin
+        for a := Low(gPlayers) to High(gPlayers) do
+        begin
+          if (gPlayers[a] <> nil) then
+          begin
+            if (gPlayers[a].UID = gPlayer1.UID) then
+              continue
+            else if (gPlayer2 <> nil) and (gPlayers[a].UID = gPlayer2.UID) then
+              continue;
+            gPlayers[a].setModel(g_Forced_Model_GetName());
+          end;
+        end
+      end
+      else if (g_Force_Model_Get() = 0) and (gPlayers <> nil) then
+      begin
+        for a := Low(gPlayers) to High(gPlayers) do
+        begin
+          if (gPlayers[a] <> nil) then
+          begin
+            if (gPlayers[a].UID = gPlayer1.UID) then
+              continue
+            else if (gPlayer2 <> nil) and (gPlayers[a].UID = gPlayer2.UID) then
+              continue;
+            gPlayers[a].setModel(gPlayers[a].FActualModelName);
+          end;
+        end
+      end
     end
   end
   else if cmd = 'g_force_model_name' then
@@ -5775,7 +5858,7 @@ begin
     begin
       cmd := b_Text_Unformat(P[1]);
       g_Forced_Model_SetName(cmd);
-      if (gGameSettings.GameType <> GT_SINGLE) and (g_Force_Model_Get() <> 0) and (gPlayers <> nil) then
+      if (g_Force_Model_Get() <> 0) and (gPlayers <> nil) then
       begin
         for a := Low(gPlayers) to High(gPlayers) do
         begin
@@ -5814,7 +5897,7 @@ begin
         // if someone has a higher score, set it to that instead
         gsScoreLimit := max(gsScoreLimit, b);
         gGameSettings.ScoreLimit := gsScoreLimit;
+
         if g_Game_IsNet then MH_SEND_GameSettings;
       end;
     end;
@@ -6027,13 +6110,13 @@ begin
     'p1_priority_kastet':
       begin
         if (Length(P) = 2) then
-          gPlayer1Settings.WeaponPreferences[WEAPON_KASTET] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1); 
+          gPlayer1Settings.WeaponPreferences[WEAPON_KASTET] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
         end;
     'p2_priority_kastet':
       begin
         if (Length(P) = 2) then
-          gPlayer2Settings.WeaponPreferences[WEAPON_KASTET] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1); 
-      end;        
+          gPlayer2Settings.WeaponPreferences[WEAPON_KASTET] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
     'p1_priority_saw':
       begin
         if (Length(P) = 2) then
@@ -6047,13 +6130,13 @@ begin
     'p1_priority_pistol':
       begin
         if (Length(P) = 2) then
-          gPlayer1Settings.WeaponPreferences[WEAPON_KASTET] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1); 
+          gPlayer1Settings.WeaponPreferences[WEAPON_KASTET] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
         end;
     'p2_priority_pistol':
       begin
         if (Length(P) = 2) then
-          gPlayer2Settings.WeaponPreferences[WEAPON_KASTET] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1); 
-      end;         
+          gPlayer2Settings.WeaponPreferences[WEAPON_KASTET] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
     'p1_priority_shotgun1':
       begin
         if (Length(P) = 2) then
@@ -6133,17 +6216,17 @@ begin
       begin
         if (Length(P) = 2) then
           gPlayer2Settings.WeaponPreferences[WEAPON_FLAMETHROWER] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
-      end;      
+      end;
     'p1_priority_berserk':
       begin
         if (Length(P) = 2) then
-          gPlayer1Settings.WeaponPreferences[WP_LAST+1] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1); 
+          gPlayer1Settings.WeaponPreferences[WP_LAST+1] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
         end;
     'p2_priority_berserk':
       begin
         if (Length(P) = 2) then
-          gPlayer2Settings.WeaponPreferences[WP_LAST+1] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1); 
-      end;                                                                                  
+          gPlayer2Settings.WeaponPreferences[WP_LAST+1] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
   end;
 end;
 
@@ -6357,8 +6440,8 @@ begin
       if cmd = 'greenarmor' then begin plr.GiveItem(ITEM_ARMOR_GREEN); g_Console_Add('player got a security armor'); continue; end;
       if cmd = 'bluearmor' then begin plr.GiveItem(ITEM_ARMOR_BLUE); g_Console_Add('player got a combat armor'); continue; end;
 
-      if (cmd = 'megasphere') or (cmd = 'mega') then begin plr.GiveItem(ITEM_SPHERE_BLUE); g_Console_Add('player got a megasphere'); continue; end;
-      if (cmd = 'soulsphere') or (cmd = 'soul')then begin plr.GiveItem(ITEM_SPHERE_WHITE); g_Console_Add('player got a soul sphere'); continue; end;
+      if (cmd = 'soulsphere') or (cmd = 'soul') then begin plr.GiveItem(ITEM_SPHERE_BLUE); g_Console_Add('player got a soul sphere'); continue; end;
+      if (cmd = 'megasphere') or (cmd = 'mega') then begin plr.GiveItem(ITEM_SPHERE_WHITE); g_Console_Add('player got a megasphere'); continue; end;
 
       if (cmd = 'invul') or (cmd = 'invulnerability') then begin plr.GiveItem(ITEM_INVUL); g_Console_Add('player got invulnerability'); continue; end;
       if (cmd = 'invis') or (cmd = 'invisibility') then begin plr.GiveItem(ITEM_INVIS); g_Console_Add('player got invisibility'); continue; end;
@@ -6516,6 +6599,7 @@ var
   nm: Boolean;
   listen: LongWord;
   found: Boolean;
+  t: Byte;
 begin
 // Îáùèå êîìàíäû:
   cmd := LowerCase(P[0]);
@@ -6560,7 +6644,7 @@ begin
       if (pl <> nil) then
       begin
         s := g_Net_ClientName_ByID(pl^.ID);
-        enet_peer_disconnect(pl^.Peer, NET_DISC_KICK);
+        g_Net_Host_Kick(pl^.ID, NET_DISC_KICK);
         g_Console_Add(Format(_lc[I_PLAYER_KICK], [s]));
         MH_SEND_GameEvent(NET_EV_PLAYER_KICK, 0, s);
         g_Net_Slist_ServerPlayerLeaves();
@@ -6604,7 +6688,7 @@ begin
         if NetClients[a].Used and (NetClients[a].Peer <> nil) then
         begin
           s := g_Net_ClientName_ByID(NetClients[a].ID);
-          enet_peer_disconnect(NetClients[a].Peer, NET_DISC_KICK);
+          g_Net_Host_Kick(NetClients[a].ID, NET_DISC_KICK);
           g_Console_Add(Format(_lc[I_PLAYER_KICK], [s]));
           MH_SEND_GameEvent(NET_EV_PLAYER_KICK, 0, s);
           g_Net_Slist_ServerPlayerLeaves();
@@ -6633,7 +6717,7 @@ begin
       if (pl <> nil) and pl^.Used and (pl^.Peer <> nil) then
       begin
         s := g_Net_ClientName_ByID(pl^.ID);
-        enet_peer_disconnect(pl^.Peer, NET_DISC_KICK);
+        g_Net_Host_Kick(pl^.ID, NET_DISC_KICK);
         g_Console_Add(Format(_lc[I_PLAYER_KICK], [s]));
         MH_SEND_GameEvent(NET_EV_PLAYER_KICK, 0, s);
         g_Net_Slist_ServerPlayerLeaves();
@@ -6658,14 +6742,8 @@ begin
 
       pl := g_Net_Client_ByName(P[1]);
       if (pl <> nil) then
-      begin
-        s := g_Net_ClientName_ByID(pl^.ID);
-        g_Net_BanHost(pl^.Peer^.address.host, False);
-        enet_peer_disconnect(pl^.Peer, NET_DISC_TEMPBAN);
-        g_Console_Add(Format(_lc[I_PLAYER_BAN], [s]));
-        MH_SEND_GameEvent(NET_EV_PLAYER_BAN, 0, s);
-        g_Net_Slist_ServerPlayerLeaves();
-      end else
+        g_Net_Host_Ban(pl, False)
+      else
         g_Console_Add(Format(_lc[I_NET_ERR_NAME404], [P[1]]));
     end else
       g_Console_Add(_lc[I_MSG_SERVERONLY]);
@@ -6688,14 +6766,7 @@ begin
       a := StrToIntDef(P[1], 0);
       if (NetClients <> nil) and (a <= High(NetClients)) then
         if NetClients[a].Used and (NetClients[a].Peer <> nil) then
-        begin
-          s := g_Net_ClientName_ByID(NetClients[a].ID);
-          g_Net_BanHost(NetClients[a].Peer^.address.host, False);
-          enet_peer_disconnect(NetClients[a].Peer, NET_DISC_TEMPBAN);
-          g_Console_Add(Format(_lc[I_PLAYER_BAN], [s]));
-          MH_SEND_GameEvent(NET_EV_PLAYER_BAN, 0, s);
-          g_Net_Slist_ServerPlayerLeaves();
-        end;
+          g_Net_Host_Ban(pl, False);
     end else
       g_Console_Add(_lc[I_MSG_SERVERONLY]);
   end
@@ -6717,14 +6788,7 @@ begin
       a := StrToIntDef(P[1], 0);
       pl := g_Net_Client_ByPlayer(a);
       if (pl <> nil) and pl^.Used and (pl^.Peer <> nil) then
-      begin
-        s := g_Net_ClientName_ByID(pl^.ID);
-        g_Net_BanHost(pl^.Peer^.address.host, False);
-        enet_peer_disconnect(pl^.Peer, NET_DISC_TEMPBAN);
-        g_Console_Add(Format(_lc[I_PLAYER_BAN], [s]));
-        MH_SEND_GameEvent(NET_EV_PLAYER_BAN, 0, s);
-        g_Net_Slist_ServerPlayerLeaves();
-      end;
+        g_Net_Host_Ban(pl, False);
     end else
       g_Console_Add(_lc[I_MSG_SERVERONLY]);
   end
@@ -6745,15 +6809,8 @@ begin
 
       pl := g_Net_Client_ByName(P[1]);
       if (pl <> nil) then
-      begin
-        s := g_Net_ClientName_ByID(pl^.ID);
-        g_Net_BanHost(pl^.Peer^.address.host);
-        enet_peer_disconnect(pl^.Peer, NET_DISC_BAN);
-        g_Net_SaveBanList();
-        g_Console_Add(Format(_lc[I_PLAYER_BAN], [s]));
-        MH_SEND_GameEvent(NET_EV_PLAYER_BAN, 0, s);
-        g_Net_Slist_ServerPlayerLeaves();
-      end else
+        g_Net_Host_Ban(pl, True)
+      else
         g_Console_Add(Format(_lc[I_NET_ERR_NAME404], [P[1]]));
     end else
       g_Console_Add(_lc[I_MSG_SERVERONLY]);
@@ -6776,15 +6833,7 @@ begin
       a := StrToIntDef(P[1], 0);
       if (NetClients <> nil) and (a <= High(NetClients)) then
         if NetClients[a].Used and (NetClients[a].Peer <> nil) then
-        begin
-          s := g_Net_ClientName_ByID(NetClients[a].ID);
-          g_Net_BanHost(NetClients[a].Peer^.address.host);
-          enet_peer_disconnect(NetClients[a].Peer, NET_DISC_BAN);
-          g_Net_SaveBanList();
-          g_Console_Add(Format(_lc[I_PLAYER_BAN], [s]));
-          MH_SEND_GameEvent(NET_EV_PLAYER_BAN, 0, s);
-          g_Net_Slist_ServerPlayerLeaves();
-        end;
+          g_Net_Host_Ban(@NetClients[a], True);
     end else
       g_Console_Add(_lc[I_MSG_SERVERONLY]);
   end
@@ -6806,15 +6855,7 @@ begin
       a := StrToIntDef(P[1], 0);
       pl := g_Net_Client_ByPlayer(a);
       if (pl <> nil) and pl^.Used and (pl^.Peer <> nil) then
-      begin
-        s := g_Net_ClientName_ByID(pl^.ID);
-        g_Net_BanHost(pl^.Peer^.address.host);
-        enet_peer_disconnect(pl^.Peer, NET_DISC_BAN);
-        g_Net_SaveBanList();
-        g_Console_Add(Format(_lc[I_PLAYER_BAN], [s]));
-        MH_SEND_GameEvent(NET_EV_PLAYER_BAN, 0, s);
-        g_Net_Slist_ServerPlayerLeaves();
-      end;
+        g_Net_Host_Ban(pl, True);
     end else
       g_Console_Add(_lc[I_MSG_SERVERONLY]);
   end
@@ -6833,7 +6874,7 @@ begin
         Exit;
       end;
 
-      g_Net_BanHost(P[1]);
+      g_Net_BanAddress(P[1]);
       g_Net_SaveBanList();
       g_Console_Add(Format(_lc[I_PLAYER_BAN], [P[1]]));
     end else
@@ -6854,7 +6895,7 @@ begin
         Exit;
       end;
 
-      if g_Net_UnbanHost(P[1]) then
+      if g_Net_UnbanAddress(P[1]) then
       begin
         g_Console_Add(Format(_lc[I_MSG_UNBAN_OK], [P[1]]));
         g_Net_SaveBanList();
@@ -6934,23 +6975,29 @@ begin
   else if (cmd = 'addbot') or
      (cmd = 'bot_add') then
   begin
-    if Length(P) > 2 then
-      g_Bot_Add(TEAM_NONE, StrToIntDef(P[1], 2), StrToIntDef(P[2], 100))
-    else if Length(P) > 1 then
-      g_Bot_Add(TEAM_NONE, StrToIntDef(P[1], 2))
+    case Length(P) of
+      1: g_Bot_Add(TEAM_NONE, 2);
+      2: g_Bot_Add(TEAM_NONE, StrToIntDef(P[1], 2));
     else
-      g_Bot_Add(TEAM_NONE, 2);
+      g_Bot_Add(TEAM_NONE, StrToIntDef(P[1], 2), StrToIntDef(P[2], 100));
+    end;
   end
   else if cmd = 'bot_addlist' then
   begin
-    if Length(P) > 1 then
-    begin
-      if Length(P) = 2 then
-        g_Bot_AddList(TEAM_NONE, P[1], StrToIntDef(P[1], -1))
-      else if Length(P) = 3 then
-        g_Bot_AddList(TEAM_NONE, P[1], StrToIntDef(P[1], -1), StrToIntDef(P[2], 100))
+    case Length(P) of
+      1: g_Bot_AddList(TEAM_NONE, '');
+      2: g_Bot_AddList(TEAM_NONE, P[1], StrToIntDef(P[1], -1));
+    else
+      if P[2] = 'red' then
+        t := TEAM_RED
+      else if P[2] = 'blue' then
+        t := TEAM_BLUE
       else
-        g_Bot_AddList(IfThen(P[2] = 'red', TEAM_RED, TEAM_BLUE), P[1], StrToIntDef(P[1], -1));
+        t := TEAM_NONE;
+
+      if Length(P) = 3
+        then g_Bot_AddList(t, P[1], StrToIntDef(P[1], -1))
+        else g_Bot_AddList(t, P[1], StrToIntDef(P[1], -1), StrToIntDef(P[3], 100));
     end;
   end
   else if cmd = 'bot_removeall' then
@@ -7168,6 +7215,10 @@ begin
         g_Console_Add(_lc[I_MSG_GM_UNAVAIL])
       end
     end
+    else if not e_IsValidResourceName(P[1]) then
+    begin
+      g_Console_Add('wad name must not be absolute or relative');
+    end
     else
     begin
       if g_Game_IsServer and (gGameSettings.GameType <> GT_SINGLE) then
@@ -7295,6 +7346,10 @@ begin
           g_Console_Add(_lc[I_MSG_GM_UNAVAIL]);
         end;
       end
+      else if not e_IsValidResourceName(P[1]) then
+      begin
+        g_Console_Add('wad name must not be absolute or relative');
+      end
       else
       begin
         nm := False;
@@ -7835,7 +7890,7 @@ begin
       DateTimeToString(date, 'yyyy-mm-dd-hh-nn-ss', t);
       Filename := 'screenshot-' + date;
     end;
-    
+
     name := e_CatPath(dir, Filename + '.png');
     s := createDiskFile(name);
     try
@@ -8187,6 +8242,14 @@ begin
   hahasnd[Random(3)].Play();
 end;
 
+procedure g_Game_Effect_Bubbles (fX, fY: Integer; count: Word; devX, devY: Byte; Silent: Boolean);
+begin
+  g_GFX_Bubbles(fX, fY, count, devX, devY);
+  if not Silent then if Random(2) = 0
+    then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', fX, fY)
+    else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', fX, fY);
+end;
+
 procedure g_Game_StartVote(Command, Initiator: string);
 var
   Need: Integer;