tools: fix build with sdl2
[d2df-sdl.git] / src / game / g_game.pas
blob47251204f2b021a78262a60f265d830d63308bda
1 (* Copyright (C)  Doom 2D: Forever Developers
2  *
3  * This program is free software: you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation, version 3 of the License ONLY.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
14  *)
15 {$INCLUDE ../shared/a_modes.inc}
16 unit g_game;
18 interface
20 uses
21   SysUtils, Classes,
22   MAPDEF,
23   g_basic, g_player, e_graphics, g_res_downloader,
24   g_sound, g_gui, utils, md5, mempool, xprofiler,
25   g_touch, g_weapons;
27 type
28   TGameSettings = record
29     GameType: Byte;
30     GameMode: Byte;
31     TimeLimit: Word;
32     GoalLimit: Word;
33     WarmupTime: Word;
34     SpawnInvul: Word;
35     ItemRespawnTime: Word;
36     MaxLives: Byte;
37     Options: LongWord;
38     WAD: String;
39   end;
41   TGameEvent = record
42     Name: String;
43     Command: String;
44   end;
46   TDelayedEvent = record
47     Pending: Boolean;
48     Time: LongWord;
49     DEType: Byte;
50     DENum: Integer;
51     DEStr: String;
52   end;
54   TChatSound = record
55     Sound: TPlayableSound;
56     Tags: Array of String;
57     FullWord: Boolean;
58   end;
60   TPlayerSettings = record
61     Name: String;
62     Model: String;
63     Color: TRGB;
64     Team: Byte;
65     // ones below are sent only to the server
66     WeaponSwitch: Byte;
67     WeaponPreferences: Array[WP_FIRST..WP_LAST+1] of Byte;
68     SwitchToEmpty: Byte;
69     SkipFist: Byte;
70   end;
72   TMegaWADInfo = record
73     Name: String;
74     Description: String;
75     Author: String;
76     Pic: String;
77   end;
79   THearPoint = record
80     Active: Boolean;
81     Coords: TDFPoint;
82   end;
84 function  g_Game_IsNet(): Boolean;
85 function  g_Game_IsServer(): Boolean;
86 function  g_Game_IsClient(): Boolean;
87 procedure g_Game_Init();
88 procedure g_Game_Free (freeTextures: Boolean=true);
89 procedure g_Game_LoadData();
90 procedure g_Game_FreeData();
91 procedure g_Game_Update();
92 procedure g_Game_PreUpdate();
93 procedure g_Game_Draw();
94 procedure g_Game_Quit();
95 procedure g_Game_SetupScreenSize();
96 procedure g_Game_ChangeResolution(newWidth, newHeight: Word; nowFull, nowMax: Boolean);
97 function  g_Game_ModeToText(Mode: Byte): string;
98 function  g_Game_TextToMode(Mode: string): Byte;
99 procedure g_Game_ExecuteEvent(Name: String);
100 function  g_Game_DelayEvent(DEType: Byte; Time: LongWord; Num: Integer = 0; Str: String = ''): Integer;
101 procedure g_Game_AddPlayer(Team: Byte = TEAM_NONE);
102 procedure g_Game_RemovePlayer();
103 procedure g_Game_Spectate();
104 procedure g_Game_SpectateCenterView();
105 procedure g_Game_StartSingle(Map: String; TwoPlayers: Boolean; nPlayers: Byte);
106 procedure g_Game_StartCustom(Map: String; GameMode: Byte; TimeLimit, GoalLimit: Word; MaxLives: Byte; Options: LongWord; nPlayers: Byte);
107 procedure g_Game_StartServer(Map: String; GameMode: Byte; TimeLimit, GoalLimit: Word; MaxLives: Byte; Options: LongWord; nPlayers: Byte; IPAddr: LongWord; Port: Word);
108 procedure g_Game_StartClient(Addr: String; Port: Word; PW: String);
109 procedure g_Game_Restart();
110 procedure g_Game_RestartLevel();
111 procedure g_Game_RestartRound(NoMapRestart: Boolean = False);
112 function  g_Game_ClientWAD (NewWAD: String; const WHash: TMD5Digest): AnsiString;
113 function  g_Game_StartMap(asMegawad: Boolean; Map: String; Force: Boolean = False; const oldMapPath: AnsiString=''): Boolean;
114 procedure g_Game_ChangeMap(const MapPath: String);
115 procedure g_Game_ExitLevel(const Map: AnsiString);
116 function  g_Game_GetFirstMap(WAD: String): String;
117 function  g_Game_GetNextMap(): String;
118 procedure g_Game_NextLevel();
119 procedure g_Game_Pause(Enable: Boolean);
120 procedure g_Game_HolmesPause(Enable: Boolean);
121 procedure g_Game_InGameMenu(Show: Boolean);
122 function  g_Game_IsWatchedPlayer(UID: Word): Boolean;
123 function  g_Game_IsWatchedTeam(Team: Byte): Boolean;
124 procedure g_Game_Message(Msg: String; Time: Word);
125 procedure g_Game_LoadMapList(FileName: String);
126 procedure g_Game_PauseAllSounds(Enable: Boolean);
127 procedure g_Game_StopAllSounds(all: Boolean);
128 procedure g_Game_UpdateTriggerSounds();
129 function  g_Game_GetMegaWADInfo(WAD: String): TMegaWADInfo;
130 procedure g_Game_ChatSound(Text: String; Taunt: Boolean = True);
131 procedure g_Game_Announce_GoodShot(SpawnerUID: Word);
132 procedure g_Game_Announce_KillCombo(Param: Integer);
133 procedure g_Game_Announce_BodyKill(SpawnerUID: Word);
134 procedure g_Game_StartVote(Command, Initiator: string);
135 procedure g_Game_CheckVote;
136 procedure g_TakeScreenShot(Filename: string = '');
137 procedure g_FatalError(Text: String);
138 procedure g_SimpleError(Text: String);
139 function  g_Game_IsTestMap(): Boolean;
140 procedure g_Game_DeleteTestMap();
141 procedure GameCVars(P: SSArray);
142 procedure PlayerSettingsCVars(P: SSArray);
143 procedure SystemCommands(P: SSArray);
144 procedure GameCommands(P: SSArray);
145 procedure GameCheats(P: SSArray);
146 procedure DebugCommands(P: SSArray);
147 procedure g_Game_Process_Params;
148 procedure g_Game_SetLoadingText(Text: String; Max: Integer; reWrite: Boolean);
149 procedure g_Game_StepLoading(Value: Integer = -1);
150 procedure g_Game_ClearLoading();
151 procedure g_Game_SetDebugMode();
152 procedure DrawLoadingStat();
153 procedure DrawMenuBackground(tex: AnsiString);
155 { procedure SetWinPause(Enable: Boolean); }
157 const
158   GAME_TICK = 28;
160   LOADING_SHOW_STEP = 100;
161   LOADING_INTERLINE = 20;
163   GT_NONE   = 0;
164   GT_SINGLE = 1;
165   GT_CUSTOM = 2;
166   GT_SERVER = 3;
167   GT_CLIENT = 4;
169   GM_NONE = 0;
170   GM_DM   = 1;
171   GM_TDM  = 2;
172   GM_CTF  = 3;
173   GM_COOP = 4;
174   GM_SINGLE = 5;
176   MESSAGE_DIKEY = WM_USER + 1;
178   EXIT_QUIT            = 1;
179   EXIT_SIMPLE          = 2;
180   EXIT_RESTART         = 3;
181   EXIT_ENDLEVELSINGLE  = 4;
182   EXIT_ENDLEVELCUSTOM  = 5;
184   GAME_OPTION_RESERVED          = 1;
185   GAME_OPTION_TEAMDAMAGE        = 2;
186   GAME_OPTION_ALLOWEXIT         = 4;
187   GAME_OPTION_WEAPONSTAY        = 8;
188   GAME_OPTION_MONSTERS          = 16;
189   GAME_OPTION_BOTVSPLAYER       = 32;
190   GAME_OPTION_BOTVSMONSTER      = 64;
191   GAME_OPTION_DMKEYS            = 128;
192   GAME_OPTION_TEAMHITTRACE      = 256;
193   GAME_OPTION_TEAMHITPROJECTILE = 512;
194   GAME_OPTION_TEAMABSORBDAMAGE  = 1024;
195   GAME_OPTION_ALLOWDROPFLAG     = 2048;
196   GAME_OPTION_THROWFLAG         = 4096;
198   STATE_NONE        = 0;
199   STATE_MENU        = 1;
200   STATE_FOLD        = 2;
201   STATE_INTERCUSTOM = 3;
202   STATE_INTERSINGLE = 4;
203   STATE_INTERTEXT   = 5;
204   STATE_INTERPIC    = 6;
205   STATE_ENDPIC      = 7;
206   STATE_SLIST       = 8;
208   LMS_RESPAWN_NONE   = 0;
209   LMS_RESPAWN_WARMUP = 1;
210   LMS_RESPAWN_FINAL  = 2;
212   SPECT_NONE    = 0;
213   SPECT_STATS   = 1;
214   SPECT_MAPVIEW = 2;
215   SPECT_PLAYERS = 3;
217   DE_GLOBEVENT = 0;
218   DE_BFGHIT    = 1;
219   DE_KILLCOMBO = 2;
220   DE_BODYKILL  = 3;
222   ANNOUNCE_NONE   = 0;
223   ANNOUNCE_ME     = 1;
224   ANNOUNCE_MEPLUS = 2;
225   ANNOUNCE_ALL    = 3;
227   CONFIG_FILENAME = 'Doom2DF.cfg';
229   TEST_MAP_NAME = '$$$_TEST_$$$';
231   STD_PLAYER_MODEL = 'Doomer';
233 {$IFDEF HEADLESS}
234   DEFAULT_PLAYERS = 0;
235 {$ELSE}
236   DEFAULT_PLAYERS = 1;
237 {$ENDIF}
239   STATFILE_VERSION = $03;
242   gStdFont: DWORD;
243   gGameSettings: TGameSettings;
244   gPlayer1Settings: TPlayerSettings;
245   gPlayer2Settings: TPlayerSettings;
246   gGameOn: Boolean;
247   gPlayerScreenSize: TDFPoint;
248   gPlayer1ScreenCoord: TDFPoint;
249   gPlayer2ScreenCoord: TDFPoint;
250   gPlayer1: TPlayer = nil;
251   gPlayer2: TPlayer = nil;
252   gPlayerDrawn: TPlayer = nil;
253   gTime: LongWord;
254   gLerpFactor: Single = 1.0;
255   gSwitchGameMode: Byte = GM_DM;
256   gHearPoint1, gHearPoint2: THearPoint;
257   gSoundEffectsDF: Boolean = False;
258   gSoundTriggerTime: Word = 0;
259   gAnnouncer: Integer = ANNOUNCE_NONE;
260   goodsnd: array[0..3] of TPlayableSound;
261   killsnd: array[0..3] of TPlayableSound;
262   hahasnd: array[0..2] of TPlayableSound;
263   sound_get_flag: array[0..1] of TPlayableSound;
264   sound_lost_flag: array[0..1] of TPlayableSound;
265   sound_ret_flag: array[0..1] of TPlayableSound;
266   sound_cap_flag: array[0..1] of TPlayableSound;
267   gBodyKillEvent: Integer = -1;
268   gDefInterTime: ShortInt = -1;
269   gInterEndTime: LongWord = 0;
270   gInterTime: LongWord = 0;
271   gServInterTime: Byte = 0;
272   gGameStartTime: LongWord = 0;
273   gTotalMonsters: Integer = 0;
274   gPauseMain: Boolean = false;
275   gPauseHolmes: Boolean = false;
276   gShowTime: Boolean = False;
277   gShowFPS: Boolean = False;
278   gShowGoals: Boolean = True;
279   gShowStat: Boolean = True;
280   gShowPIDs: Boolean = False;
281   gShowKillMsg: Boolean = True;
282   gShowLives: Boolean = True;
283   gShowPing: Boolean = False;
284   gShowMap: Boolean = False;
285   gExit: Byte = 0;
286   gState: Byte = STATE_NONE;
287   sX, sY: Integer;
288   sWidth, sHeight: Word;
289   gSpectMode: Byte = SPECT_NONE;
290   gSpectHUD: Boolean = True;
291   gSpectKeyPress: Boolean = False;
292   gSpectX: Integer = 0;
293   gSpectY: Integer = 0;
294   gSpectStep: Byte = 8;
295   gSpectViewTwo: Boolean = False;
296   gSpectPID1: Integer = -1;
297   gSpectPID2: Integer = -1;
298   gSpectAuto: Boolean = False;
299   gSpectAutoNext: LongWord;
300   gSpectAutoStepX: Integer;
301   gSpectAutoStepY: Integer;
302   gMusic: TMusic = nil;
303   gLoadGameMode: Boolean;
304   gCheats: Boolean = False;
305   gMapOnce: Boolean = False;
306   gMapToDelete: String;
307   gTempDelete: Boolean = False;
308   gLastMap: Boolean = False;
309   gScreenWidth: Word;
310   gScreenHeight: Word;
311   gResolutionChange: Boolean = False;
312   gRC_Width, gRC_Height: Integer;
313   gRC_FullScreen, gRC_Maximized: Boolean;
314   gLanguageChange: Boolean = False;
315   gDebugMode: Boolean = False;
316   g_debug_Sounds: Boolean = False;
317   g_debug_Frames: Boolean = False;
318   g_debug_WinMsgs: Boolean = False;
319   g_debug_MonsterOff: Boolean = False;
320   g_debug_BotAIOff: Byte = 0;
321   g_debug_HealthBar: Boolean = False;
322   g_Debug_Player: Boolean = False;
323   gCoopMonstersKilled: Word = 0;
324   gCoopSecretsFound: Word = 0;
325   gCoopTotalMonstersKilled: Word = 0;
326   gCoopTotalSecretsFound: Word = 0;
327   gCoopTotalMonsters: Word = 0;
328   gCoopTotalSecrets: Word = 0;
329   gStatsOff: Boolean = False;
330   gStatsPressed: Boolean = False;
331   gExitByTrigger: Boolean = False;
332   gNextMap: String = '';
333   gLMSRespawn: Byte = LMS_RESPAWN_NONE;
334   gLMSRespawnTime: Cardinal = 0;
335   gLMSSoftSpawn: Boolean = False;
336   gMissionFailed: Boolean = False;
337   gVoteInProgress: Boolean = False;
338   gVotePassed: Boolean = False;
339   gVoteCommand: string = '';
340   gVoteTimer: Cardinal = 0;
341   gVoteCmdTimer: Cardinal = 0;
342   gVoteCount: Integer = 0;
343   gVoteTimeout: Cardinal = 30;
344   gVoted: Boolean = False;
345   gVotesEnabled: Boolean = True;
346   gEvents: Array of TGameEvent;
347   gDelayedEvents: Array of TDelayedEvent;
348   gUseChatSounds: Boolean = True;
349   gChatSounds: Array of TChatSound;
350   gWeaponAction: Array [0..1, WP_FACT..WP_LACT] of Boolean; // [player, weapon_action]
351   gSelectWeapon: Array [0..1, WP_FIRST..WP_LAST] of Boolean; // [player, weapon]
352   gInterReadyCount: Integer = 0;
353   gMaxBots: Integer = 127;
355   g_dbg_ignore_bounds: Boolean = false;
356   r_smallmap_h: Integer = 0; // 0: left; 1: center; 2: right
357   r_smallmap_v: Integer = 2; // 0: top; 1: center; 2: bottom
359   // move button values:
360   // bits 0-1: l/r state:
361   //   0: neither left, nor right pressed
362   //   1: left pressed
363   //   2: right pressed
364   // bits 4-5: l/r state when strafe was pressed
365   P1MoveButton: Byte = 0;
366   P2MoveButton: Byte = 0;
368   g_profile_frame_update: Boolean = false;
369   g_profile_frame_draw: Boolean = false;
370   g_profile_collision: Boolean = false;
371   g_profile_los: Boolean = false;
372   g_profile_history_size: Integer = 1000;
374   g_rlayer_back: Boolean = true;
375   g_rlayer_step: Boolean = true;
376   g_rlayer_wall: Boolean = true;
377   g_rlayer_door: Boolean = true;
378   g_rlayer_acid1: Boolean = true;
379   g_rlayer_acid2: Boolean = true;
380   g_rlayer_water: Boolean = true;
381   g_rlayer_fore: Boolean = true;
384 procedure g_ResetDynlights ();
385 procedure g_AddDynLight (x, y, radius: Integer; r, g, b, a: Single);
386 procedure g_DynLightExplosion (x, y, radius: Integer; r, g, b: Single);
388 function conIsCheatsEnabled (): Boolean; inline;
389 function gPause (): Boolean; inline;
392 implementation
394 uses
395 {$INCLUDE ../nogl/noGLuses.inc}
396 {$IFDEF ENABLE_HOLMES}
397   g_holmes,
398 {$ENDIF}
399   e_texture, e_res, g_textures, g_window, g_menu,
400   e_input, e_log, g_console, g_items, g_map, g_panel,
401   g_playermodel, g_gfx, g_options, Math,
402   g_triggers, g_monsters, e_sound, CONFIG,
403   g_language, g_net, g_main, g_phys,
404   ENet, e_msg, g_netmsg, g_netmaster,
405   sfs, wadreader, g_system;
409   hasPBarGfx: Boolean = false;
412 // ////////////////////////////////////////////////////////////////////////// //
413 function gPause (): Boolean; inline; begin result := gPauseMain or gPauseHolmes; end;
416 // ////////////////////////////////////////////////////////////////////////// //
417 function conIsCheatsEnabled (): Boolean; inline;
418 begin
419   result := false;
420   if g_Game_IsNet then exit;
421   if not gDebugMode then
422   begin
423     //if not gCheats then exit;
424     if not (gGameSettings.GameType in [GT_SINGLE, GT_CUSTOM]) then exit;
425     if not (gGameSettings.GameMode in [GM_COOP, GM_SINGLE]) then exit;
426   end;
427   result := true;
428 end;
431 // ////////////////////////////////////////////////////////////////////////// //
433   profileFrameDraw: TProfiler = nil;
436 // ////////////////////////////////////////////////////////////////////////// //
437 type
438   TDynLight = record
439     x, y, radius: Integer;
440     r, g, b, a: Single;
441     exploCount: Integer;
442     exploRadius: Integer;
443   end;
446   g_dynLights: array of TDynLight = nil;
447   g_dynLightCount: Integer = 0;
448   g_playerLight: Boolean = false;
450 procedure g_ResetDynlights ();
452   lnum, idx: Integer;
453 begin
454   if not gwin_has_stencil then begin g_dynLightCount := 0; exit; end;
455   lnum := 0;
456   for idx := 0 to g_dynLightCount-1 do
457   begin
458     if g_dynLights[idx].exploCount = -666 then
459     begin
460       // skip it
461     end
462     else
463     begin
464       // explosion
465       Inc(g_dynLights[idx].exploCount);
466       if (g_dynLights[idx].exploCount < 10) then
467       begin
468         g_dynLights[idx].radius := g_dynLights[idx].exploRadius+g_dynLights[idx].exploCount*8;
469         g_dynLights[idx].a := 0.4+g_dynLights[idx].exploCount/10;
470         if (g_dynLights[idx].a > 0.8) then g_dynLights[idx].a := 0.8;
471         if lnum <> idx then g_dynLights[lnum] := g_dynLights[idx];
472         Inc(lnum);
473       end;
474     end;
475   end;
476   g_dynLightCount := lnum;
477 end;
479 procedure g_AddDynLight (x, y, radius: Integer; r, g, b, a: Single);
480 begin
481   if not gwin_has_stencil then exit;
482   if g_dynLightCount = length(g_dynLights) then SetLength(g_dynLights, g_dynLightCount+1024);
483   g_dynLights[g_dynLightCount].x := x;
484   g_dynLights[g_dynLightCount].y := y;
485   g_dynLights[g_dynLightCount].radius := radius;
486   g_dynLights[g_dynLightCount].r := r;
487   g_dynLights[g_dynLightCount].g := g;
488   g_dynLights[g_dynLightCount].b := b;
489   g_dynLights[g_dynLightCount].a := a;
490   g_dynLights[g_dynLightCount].exploCount := -666;
491   Inc(g_dynLightCount);
492 end;
494 procedure g_DynLightExplosion (x, y, radius: Integer; r, g, b: Single);
495 begin
496   if not gwin_has_stencil then exit;
497   if g_dynLightCount = length(g_dynLights) then SetLength(g_dynLights, g_dynLightCount+1024);
498   g_dynLights[g_dynLightCount].x := x;
499   g_dynLights[g_dynLightCount].y := y;
500   g_dynLights[g_dynLightCount].radius := 0;
501   g_dynLights[g_dynLightCount].exploRadius := radius;
502   g_dynLights[g_dynLightCount].r := r;
503   g_dynLights[g_dynLightCount].g := g;
504   g_dynLights[g_dynLightCount].b := b;
505   g_dynLights[g_dynLightCount].a := 0;
506   g_dynLights[g_dynLightCount].exploCount := 0;
507   Inc(g_dynLightCount);
508 end;
511 // ////////////////////////////////////////////////////////////////////////// //
512 function calcProfilesHeight (prof: TProfiler): Integer;
513 begin
514   result := 0;
515   if (prof = nil) then exit;
516   if (length(prof.bars) = 0) then exit;
517   result := length(prof.bars)*(16+2);
518 end;
520 // returns width
521 function drawProfiles (x, y: Integer; prof: TProfiler): Integer;
523   wdt, hgt: Integer;
524   yy: Integer;
525   ii: Integer;
526 begin
527   result := 0;
528   if (prof = nil) then exit;
529   // gScreenWidth
530   if (length(prof.bars) = 0) then exit;
531   wdt := 192;
532   hgt := calcProfilesHeight(prof);
533   if (x < 0) then x := gScreenWidth-(wdt-1)+x;
534   if (y < 0) then y := gScreenHeight-(hgt-1)+y;
535   // background
536   //e_DrawFillQuad(x, y, x+wdt-1, y+hgt-1, 255, 255, 255, 200, B_BLEND);
537   //e_DrawFillQuad(x, y, x+wdt-1, y+hgt-1, 20, 20, 20, 0, B_NONE);
538   e_DarkenQuadWH(x, y, wdt, hgt, 150);
539   // title
540   yy := y+2;
541   for ii := 0 to High(prof.bars) do
542   begin
543     e_TextureFontPrintEx(x+2+4*prof.bars[ii].level, yy, Format('%s: %d', [prof.bars[ii].name, prof.bars[ii].value]), gStdFont, 255, 255, 0, 1, false);
544     Inc(yy, 16+2);
545   end;
546   result := wdt;
547 end;
550 // ////////////////////////////////////////////////////////////////////////// //
551 type
552   TEndCustomGameStat = record
553     PlayerStat: TPlayerStatArray;
554     TeamStat: TTeamStat;
555     GameTime: LongWord;
556     GameMode: Byte;
557     Map, MapName: String;
558   end;
560   TEndSingleGameStat = record
561     PlayerStat: Array [0..1] of record
562       Kills: Integer;
563       Secrets: Integer;
564     end;
565     GameTime: LongWord;
566     TwoPlayers: Boolean;
567     TotalSecrets: Integer;
568   end;
570   TLoadingStat = record
571     CurValue: Integer;
572     MaxValue: Integer;
573     ShowCount: Integer;
574     Msgs: Array of String;
575     NextMsg: Word;
576     PBarWasHere: Boolean; // did we draw a progress bar for this message?
577   end;
579   TParamStrValue = record
580     Name: String;
581     Value: String;
582   end;
584   TParamStrValues = Array of TParamStrValue;
586 const
587   INTER_ACTION_TEXT = 1;
588   INTER_ACTION_PIC = 2;
589   INTER_ACTION_MUSIC = 3;
592   FPS, UPS: Word;
593   FPSCounter, UPSCounter: Word;
594   FPSTime, UPSTime: LongWord;
595   DataLoaded: Boolean = False;
596   IsDrawStat: Boolean = False;
597   CustomStat: TEndCustomGameStat;
598   SingleStat: TEndSingleGameStat;
599   LoadingStat: TLoadingStat;
600   EndingGameCounter: Byte = 0;
601   MessageText: String;
602   MessageTime: Word;
603   MessageLineLength: Integer = 80;
604   MapList: SSArray = nil;
605   MapIndex: Integer = -1;
606   InterReadyTime: Integer = -1;
607   StatShotDone: Boolean = False;
608   StatFilename: string = ''; // used by stat screenshot to save with the same name as the csv
609   StatDate: string = '';
610   MegaWAD: record
611     info: TMegaWADInfo;
612     endpic: String;
613     endmus: String;
614     res: record
615       text: Array of ShortString;
616       anim: Array of ShortString;
617       pic: Array of ShortString;
618       mus: Array of ShortString;
619     end;
620     triggers: Array of record
621       event: ShortString;
622       actions: Array of record
623         action, p1, p2: Integer;
624       end;
625     end;
626     cur_trigger: Integer;
627     cur_action: Integer;
628   end;
629   //InterPic: String;
630   InterText: record
631     lines: SSArray;
632     img: String;
633     cur_line: Integer;
634     cur_char: Integer;
635     counter: Integer;
636     endtext: Boolean;
637   end;
639 function Compare(a, b: TPlayerStat): Integer;
640 begin
641   if a.Spectator then Result := 1
642     else if b.Spectator then Result := -1
643       else if a.Frags < b.Frags then Result := 1
644         else if a.Frags > b.Frags then Result := -1
645           else if a.Deaths < b.Deaths then Result := -1
646             else if a.Deaths > b.Deaths then Result := 1
647               else if a.Kills < b.Kills then Result := -1
648                 else Result := 1;
649 end;
651 procedure SortGameStat(var stat: TPlayerStatArray);
653   I, J: Integer;
654   T: TPlayerStat;
655 begin
656   if stat = nil then Exit;
658   for I := High(stat) downto Low(stat) do
659     for J := Low(stat) to High(stat) - 1 do
660       if Compare(stat[J], stat[J + 1]) = 1  then
661       begin
662         T := stat[J];
663         stat[J] := stat[J + 1];
664         stat[J + 1] := T;
665       end;
666 end;
668 // saves a shitty CSV containing the game stats passed to it
669 procedure SaveGameStat(Stat: TEndCustomGameStat; Path: string);
670 var 
671   s: TextFile;
672   dir, fname, map, mode, etime: String;
673   I: Integer;
674 begin
675   try
676     dir := e_GetWriteableDir(StatsDirs);
677     // stats are placed in stats/yy/mm/dd/*.csv
678     fname := e_CatPath(dir, Path);
679     ForceDirectories(fname); // ensure yy/mm/dd exists within the stats dir
680     fname := e_CatPath(fname, StatFilename + '.csv');
681     AssignFile(s, fname);
682     try
683       Rewrite(s);
684       // line 1: stats ver, datetime, server name, map name, game mode, time limit, score limit, dmflags, game time, num players
685       if g_Game_IsNet then fname := NetServerName else fname := '';
686       map := g_ExtractWadNameNoPath(gMapInfo.Map) + ':/' + g_ExtractFileName(gMapInfo.Map);
687       mode := g_Game_ModeToText(Stat.GameMode);
688       etime := Format('%d:%.2d:%.2d', [
689         Stat.GameTime div 1000 div 3600,
690         (Stat.GameTime div 1000 div 60) mod 60,
691         Stat.GameTime div 1000 mod 60
692       ]);
693       WriteLn(s, 'stats_ver,datetime,server,map,mode,timelimit,scorelimit,dmflags,time,num_players');
694       WriteLn(s, Format('%d,%s,%s,%s,%s,%u,%u,%u,%s,%d', [
695         STATFILE_VERSION,
696         StatDate,
697         dquoteStr(fname),
698         dquoteStr(map),
699         mode,
700         gGameSettings.TimeLimit,
701         gGameSettings.GoalLimit,
702         gGameSettings.Options,
703         etime,
704         Length(Stat.PlayerStat)
705       ]));
706       // line 2: game specific shit
707       //   if it's a team game: red score, blue score
708       //   if it's a coop game: monsters killed, monsters total, secrets found, secrets total
709       //   otherwise nothing
710       if Stat.GameMode in [GM_TDM, GM_CTF] then
711         WriteLn(s, 
712           Format('red_score,blue_score' + LineEnding + '%d,%d', [Stat.TeamStat[TEAM_RED].Goals, Stat.TeamStat[TEAM_BLUE].Goals]))
713       else if Stat.GameMode in [GM_COOP, GM_SINGLE] then
714         WriteLn(s,
715           Format('mon_killed,mon_total,secrets_found,secrets_total' + LineEnding + '%d,%d,%d,%d',[gCoopMonstersKilled, gTotalMonsters, gCoopSecretsFound, gSecretsCount]));
716       // lines 3-...: team, player name, frags, deaths
717       WriteLn(s, 'team,name,frags,deaths');
718       for I := Low(Stat.PlayerStat) to High(Stat.PlayerStat) do
719         with Stat.PlayerStat[I] do
720           WriteLn(s, Format('%d,%s,%d,%d', [Team, dquoteStr(Name), Frags, Deaths]));
721     except
722       g_Console_Add(Format(_lc[I_CONSOLE_ERROR_WRITE], [fname]));
723     end;
724   except
725     g_Console_Add('could not create gamestats file "' + fname + '"');
726   end;
727   CloseFile(s);
728 end;
730 function g_Game_ModeToText(Mode: Byte): string;
731 begin
732   Result := '';
733   case Mode of
734     GM_DM:   Result := _lc[I_MENU_GAME_TYPE_DM];
735     GM_TDM:  Result := _lc[I_MENU_GAME_TYPE_TDM];
736     GM_CTF:  Result := _lc[I_MENU_GAME_TYPE_CTF];
737     GM_COOP: Result := _lc[I_MENU_GAME_TYPE_COOP];
738     GM_SINGLE: Result := _lc[I_MENU_GAME_TYPE_SINGLE];
739   end;
740 end;
742 function g_Game_TextToMode(Mode: string): Byte;
743 begin
744   Result := GM_NONE;
745   Mode := UpperCase(Mode);
746   if Mode = _lc[I_MENU_GAME_TYPE_DM] then
747   begin
748     Result := GM_DM;
749     Exit;
750   end;
751   if Mode = _lc[I_MENU_GAME_TYPE_TDM] then
752   begin
753     Result := GM_TDM;
754     Exit;
755   end;
756   if Mode = _lc[I_MENU_GAME_TYPE_CTF] then
757   begin
758     Result := GM_CTF;
759     Exit;
760   end;
761   if Mode = _lc[I_MENU_GAME_TYPE_COOP] then
762   begin
763     Result := GM_COOP;
764     Exit;
765   end;
766   if Mode = _lc[I_MENU_GAME_TYPE_SINGLE] then
767   begin
768     Result := GM_SINGLE;
769     Exit;
770   end;
771 end;
773 function g_Game_IsNet(): Boolean;
774 begin
775   Result := (gGameSettings.GameType in [GT_SERVER, GT_CLIENT]);
776 end;
778 function g_Game_IsServer(): Boolean;
779 begin
780   Result := (gGameSettings.GameType in [GT_SINGLE, GT_CUSTOM, GT_SERVER]);
781 end;
783 function g_Game_IsClient(): Boolean;
784 begin
785   Result := (gGameSettings.GameType = GT_CLIENT);
786 end;
788 function g_Game_GetMegaWADInfo(WAD: String): TMegaWADInfo;
790   w: TWADFile;
791   cfg: TConfig;
792   p: Pointer;
793   len: Integer;
794 begin
795   Result.name := ExtractFileName(WAD);
796   Result.description := '';
797   Result.author := '';
799   w := TWADFile.Create();
800   w.ReadFile(WAD);
802   if not w.GetResource('INTERSCRIPT', p, len) then
803   begin
804     w.Free();
805     Exit;
806   end;
808   cfg := TConfig.CreateMem(p, len);
809   Result.name := cfg.ReadStr('megawad', 'name', ExtractFileName(WAD));
810   Result.description := cfg.ReadStr('megawad', 'description', '');
811   Result.author := cfg.ReadStr('megawad', 'author', '');
812   Result.pic := cfg.ReadStr('megawad', 'pic', '');
813   cfg.Free();
815   FreeMem(p);
816 end;
818 procedure g_Game_FreeWAD();
820   a: Integer;
821 begin
822   for a := 0 to High(MegaWAD.res.pic) do
823     if MegaWAD.res.pic[a] <> '' then
824       g_Texture_Delete(MegaWAD.res.pic[a]);
826   for a := 0 to High(MegaWAD.res.mus) do
827     if MegaWAD.res.mus[a] <> '' then
828       g_Sound_Delete(MegaWAD.res.mus[a]);
830   MegaWAD.res.pic := nil;
831   MegaWAD.res.text := nil;
832   MegaWAD.res.anim := nil;
833   MegaWAD.res.mus := nil;
834   MegaWAD.triggers := nil;
836   g_Texture_Delete('TEXTURE_endpic');
837   g_Sound_Delete('MUSIC_endmus');
839   ZeroMemory(@MegaWAD, SizeOf(MegaWAD));
840   gGameSettings.WAD := '';
841 end;
843 procedure g_Game_LoadWAD(WAD: string);
845   w: TWADFile;
846   cfg: TConfig;
847   p: Pointer;
848   {b, }len: Integer;
849   s: AnsiString;
850 begin
851   g_Game_FreeWAD();
852   gGameSettings.WAD := WAD;
853   if not (gGameSettings.GameMode in [GM_COOP, GM_SINGLE]) then
854     Exit;
856   MegaWAD.info := g_Game_GetMegaWADInfo(WAD);
858   w := TWADFile.Create();
859   w.ReadFile(WAD);
861   if not w.GetResource('INTERSCRIPT', p, len) then
862   begin
863     w.Free();
864     Exit;
865   end;
867   cfg := TConfig.CreateMem(p, len);
869  {b := 1;
870  while True do
871  begin
872   s := cfg.ReadStr('pic', 'pic'+IntToStr(b), '');
873   if s = '' then Break;
874   b := b+1;
876   SetLength(MegaWAD.res.pic, Length(MegaWAD.res.pic)+1);
877   MegaWAD.res.pic[High(MegaWAD.res.pic)] := s;
879   g_Texture_CreateWADEx(s, s);
880  end;
882  b := 1;
883  while True do
884  begin
885   s := cfg.ReadStr('mus', 'mus'+IntToStr(b), '');
886   if s = '' then Break;
887   b := b+1;
889   SetLength(MegaWAD.res.mus, Length(MegaWAD.res.mus)+1);
890   MegaWAD.res.mus[High(MegaWAD.res.mus)] := s;
892   g_Music_CreateWADEx(s, s);
893  end;}
895   MegaWAD.endpic := cfg.ReadStr('megawad', 'endpic', '');
896   if MegaWAD.endpic <> '' then
897   begin
898     TEXTUREFILTER := GL_LINEAR;
899     s := e_GetResourcePath(WadDirs, MegaWAD.endpic, WAD);
900     g_Texture_CreateWADEx('TEXTURE_endpic', s);
901     TEXTUREFILTER := GL_NEAREST;
902   end;
903   MegaWAD.endmus := cfg.ReadStr('megawad', 'endmus', 'Standart.wad:D2DMUS\ÊÎÍÅÖ');
904   if MegaWAD.endmus <> '' then
905   begin
906     s := e_GetResourcePath(WadDirs, MegaWAD.endmus, WAD);
907     g_Sound_CreateWADEx('MUSIC_endmus', s, True);
908   end;
910   cfg.Free();
911   FreeMem(p);
912   w.Free();
913 end;
915 {procedure start_trigger(t: string);
916 begin
917 end;
919 function next_trigger(): Boolean;
920 begin
921 end;}
923 procedure DisableCheats();
924 begin
925   MAX_RUNVEL := 8;
926   VEL_JUMP := 10;
927   gFly := False;
929   if gPlayer1 <> nil then gPlayer1.GodMode := False;
930   if gPlayer2 <> nil then gPlayer2.GodMode := False;
931   if gPlayer1 <> nil then gPlayer1.NoTarget := False;
932   if gPlayer2 <> nil then gPlayer2.NoTarget := False;
934   {$IF DEFINED(D2F_DEBUG)}
935   if gPlayer1 <> nil then gPlayer1.NoTarget := True;
936   gAimLine := g_dbg_aimline_on;
937   {$ENDIF}
938 end;
940 procedure g_Game_ExecuteEvent(Name: String);
942   a: Integer;
943 begin
944   if Name = '' then
945     Exit;
946   if gEvents = nil then
947     Exit;
948   for a := 0 to High(gEvents) do
949     if gEvents[a].Name = Name then
950     begin
951       if gEvents[a].Command <> '' then
952         g_Console_Process(gEvents[a].Command, True);
953       break;
954     end;
955 end;
957 function g_Game_DelayEvent(DEType: Byte; Time: LongWord; Num: Integer = 0; Str: String = ''): Integer;
959   a, n: Integer;
960 begin
961   n := -1;
962   if gDelayedEvents <> nil then
963     for a := 0 to High(gDelayedEvents) do
964       if not gDelayedEvents[a].Pending then
965       begin
966         n := a;
967         break;
968       end;
969   if n = -1 then
970   begin
971     SetLength(gDelayedEvents, Length(gDelayedEvents) + 1);
972     n := High(gDelayedEvents);
973   end;
974   gDelayedEvents[n].Pending := True;
975   gDelayedEvents[n].DEType := DEType;
976   gDelayedEvents[n].DENum := Num;
977   gDelayedEvents[n].DEStr := Str;
978   if DEType = DE_GLOBEVENT then
979     gDelayedEvents[n].Time := (sys_GetTicks() {div 1000}) + Time
980   else
981     gDelayedEvents[n].Time := gTime + Time;
982   Result := n;
983 end;
985 procedure EndGame();
987   a: Integer;
988   FileName: string;
989   t: TDateTime;
990 begin
991   if g_Game_IsNet and g_Game_IsServer then
992     MH_SEND_GameEvent(NET_EV_MAPEND, Byte(gMissionFailed));
994 // Ñòîï èãðà:
995   gPauseMain := false;
996   gPauseHolmes := false;
997   gGameOn := false;
999   g_Game_StopAllSounds(False);
1001   MessageTime := 0;
1002   MessageText := '';
1004   EndingGameCounter := 0;
1005   g_ActiveWindow := nil;
1007   gLMSRespawn := LMS_RESPAWN_NONE;
1008   gLMSRespawnTime := 0;
1010   case gExit of
1011     EXIT_SIMPLE: // Âûõîä ÷åðåç ìåíþ èëè êîíåö òåñòà
1012       begin
1013         g_Game_Free();
1015         if gMapOnce  then
1016           begin // Ýòî áûë òåñò
1017             g_Game_Quit();
1018           end
1019         else
1020           begin // Âûõîä â ãëàâíîå ìåíþ
1021             gMusic.SetByName('MUSIC_MENU');
1022             gMusic.Play();
1023             if gState <> STATE_SLIST then
1024             begin
1025               g_GUI_ShowWindow('MainMenu');
1026               gState := STATE_MENU;
1027             end else
1028             begin
1029               // Îáíîâëÿåì ñïèñîê ñåðâåðîâ
1030               slReturnPressed := True;
1031               if g_Net_Slist_Fetch(slCurrent) then
1032               begin
1033                 if slCurrent = nil then
1034                   slWaitStr := _lc[I_NET_SLIST_NOSERVERS];
1035               end
1036               else
1037                 slWaitStr := _lc[I_NET_SLIST_ERROR];
1038               g_Serverlist_GenerateTable(slCurrent, slTable);
1039             end;
1041             g_Game_ExecuteEvent('ongameend');
1042           end;
1043       end;
1045     EXIT_RESTART: // Íà÷àòü óðîâåíü ñíà÷àëà
1046       begin
1047         if not g_Game_IsClient then g_Game_Restart();
1048       end;
1050     EXIT_ENDLEVELCUSTOM: // Çàêîí÷èëñÿ óðîâåíü â Ñâîåé èãðå
1051       begin
1052       // Ñòàòèñòèêà Ñâîåé èãðû:
1053         FileName := g_ExtractWadName(gMapInfo.Map);
1055         CustomStat.GameTime := gTime;
1056         CustomStat.Map := ExtractFileName(FileName)+':'+g_ExtractFileName(gMapInfo.Map); //ResName;
1057         CustomStat.MapName := gMapInfo.Name;
1058         CustomStat.GameMode := gGameSettings.GameMode;
1059         if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
1060           CustomStat.TeamStat := gTeamStat;
1062         CustomStat.PlayerStat := nil;
1064       // Ñòàòèñòèêà èãðîêîâ:
1065         if gPlayers <> nil then
1066         begin
1067           for a := 0 to High(gPlayers) do
1068             if gPlayers[a] <> nil then
1069             begin
1070               SetLength(CustomStat.PlayerStat, Length(CustomStat.PlayerStat)+1);
1071               with CustomStat.PlayerStat[High(CustomStat.PlayerStat)] do
1072               begin
1073                 Num := a;
1074                 Name := gPlayers[a].Name;
1075                 Frags := gPlayers[a].Frags;
1076                 Deaths := gPlayers[a].Death;
1077                 Kills := gPlayers[a].Kills;
1078                 Team := gPlayers[a].Team;
1079                 Color := gPlayers[a].Model.Color;
1080                 Spectator := gPlayers[a].FSpectator;
1081               end;
1082             end;
1084           SortGameStat(CustomStat.PlayerStat);
1086           if (gSaveStats or gScreenshotStats) and (Length(CustomStat.PlayerStat) > 1) then
1087           begin
1088             t := Now;
1089             if g_Game_IsNet then StatFilename := NetServerName else StatFilename := 'local';
1090             StatDate := FormatDateTime('yymmdd_hhnnss', t);
1091             StatFilename := StatFilename + '_' + CustomStat.Map + '_' + g_Game_ModeToText(CustomStat.GameMode);
1092             StatFilename := sanitizeFilename(StatFilename) + '_' + StatDate;
1093             if gSaveStats then
1094               SaveGameStat(CustomStat, FormatDateTime('yyyy"/"mm"/"dd', t));
1095           end;
1097           StatShotDone := False;
1098         end;
1100         g_Game_ExecuteEvent('onmapend');
1101         if not g_Game_IsClient then g_Player_ResetReady;
1102         gInterReadyCount := 0;
1104       // Çàòóõàþùèé ýêðàí:
1105         EndingGameCounter := 255;
1106         gState := STATE_FOLD;
1107         gInterTime := 0;
1108         if gDefInterTime < 0 then
1109           gInterEndTime := IfThen((gGameSettings.GameType = GT_SERVER) and (gPlayer1 = nil), 15000, 25000)
1110         else
1111           gInterEndTime := gDefInterTime * 1000;
1112       end;
1114     EXIT_ENDLEVELSINGLE: // Çàêîí÷èëñÿ óðîâåíü â Îäèíî÷íîé èãðå
1115       begin
1116       // Ñòàòèñòèêà Îäèíî÷íîé èãðû:
1117         SingleStat.GameTime := gTime;
1118         SingleStat.TwoPlayers := gPlayer2 <> nil;
1119         SingleStat.TotalSecrets := gSecretsCount;
1120       // Ñòàòèñòèêà ïåðâîãî èãðîêà:
1121         SingleStat.PlayerStat[0].Kills := gPlayer1.MonsterKills;
1122         SingleStat.PlayerStat[0].Secrets := gPlayer1.Secrets;
1123       // Ñòàòèñòèêà âòîðîãî èãðîêà (åñëè åñòü):
1124         if SingleStat.TwoPlayers then
1125         begin
1126           SingleStat.PlayerStat[1].Kills := gPlayer2.MonsterKills;
1127           SingleStat.PlayerStat[1].Secrets := gPlayer2.Secrets;
1128         end;
1130         g_Game_ExecuteEvent('onmapend');
1132       // Åñòü åùå êàðòû:
1133         if gNextMap <> '' then
1134           begin
1135             gMusic.SetByName('MUSIC_INTERMUS');
1136             gMusic.Play();
1137             gState := STATE_INTERSINGLE;
1138             e_UnpressAllKeys();
1140             g_Game_ExecuteEvent('oninter');
1141           end
1142         else // Áîëüøå íåò êàðò
1143           begin
1144           // Çàòóõàþùèé ýêðàí:
1145             EndingGameCounter := 255;
1146             gState := STATE_FOLD;
1147           end;
1148       end;
1149   end;
1151 // Îêîí÷àíèå îáðàáîòàíî:
1152   if gExit <> EXIT_QUIT then
1153     gExit := 0;
1154 end;
1156 procedure drawTime(X, Y: Integer); inline;
1157 begin
1158   e_TextureFontPrint(x, y,
1159                      Format('%d:%.2d:%.2d', [
1160                        gTime div 1000 div 3600,
1161                        (gTime div 1000 div 60) mod 60,
1162                        gTime div 1000 mod 60
1163                      ]),
1164                      gStdFont);
1165 end;
1167 procedure DrawStat();
1169   pc, x, y, w, h: Integer;
1170   w1, w2, w3, w4: Integer;
1171   a, aa: Integer;
1172   cw, ch, r, g, b, rr, gg, bb: Byte;
1173   s1, s2, s3: String;
1174   _y: Integer;
1175   stat: TPlayerStatArray;
1176   wad, map: string;
1177   mapstr: string;
1178   namestr: string;
1179 begin
1180   s1 := '';
1181   s2 := '';
1182   s3 := '';
1183   pc := g_Player_GetCount;
1184   e_TextureFontGetSize(gStdFont, cw, ch);
1186   w := gScreenWidth-(gScreenWidth div 5);
1187   if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
1188     h := 32+ch*(11+pc)
1189   else
1190     h := 40+ch*5+(ch+8)*pc;
1191   x := (gScreenWidth div 2)-(w div 2);
1192   y := (gScreenHeight div 2)-(h div 2);
1194   e_DrawFillQuad(x, y, x+w-1, y+h-1, 64, 64, 64, 32);
1195   e_DrawQuad(x, y, x+w-1, y+h-1, 255, 127, 0);
1197   drawTime(x+w-78, y+8);
1199   wad := g_ExtractWadNameNoPath(gMapInfo.Map);
1200   map := g_ExtractFileName(gMapInfo.Map);
1201   mapstr := wad + ':\' + map + ' - ' + gMapInfo.Name;
1203   case gGameSettings.GameMode of
1204     GM_DM:
1205     begin
1206       if gGameSettings.MaxLives = 0 then
1207         s1 := _lc[I_GAME_DM]
1208       else
1209         s1 := _lc[I_GAME_LMS];
1210       s2 := Format(_lc[I_GAME_FRAG_LIMIT], [gGameSettings.GoalLimit]);
1211       s3 := Format(_lc[I_GAME_TIME_LIMIT], [gGameSettings.TimeLimit div 3600, (gGameSettings.TimeLimit div 60) mod 60, gGameSettings.TimeLimit mod 60]);
1212     end;
1214     GM_TDM:
1215     begin
1216       if gGameSettings.MaxLives = 0 then
1217         s1 := _lc[I_GAME_TDM]
1218       else
1219         s1 := _lc[I_GAME_TLMS];
1220       s2 := Format(_lc[I_GAME_FRAG_LIMIT], [gGameSettings.GoalLimit]);
1221       s3 := Format(_lc[I_GAME_TIME_LIMIT], [gGameSettings.TimeLimit div 3600, (gGameSettings.TimeLimit div 60) mod 60, gGameSettings.TimeLimit mod 60]);
1222     end;
1224     GM_CTF:
1225     begin
1226       s1 := _lc[I_GAME_CTF];
1227       s2 := Format(_lc[I_GAME_SCORE_LIMIT], [gGameSettings.GoalLimit]);
1228       s3 := Format(_lc[I_GAME_TIME_LIMIT], [gGameSettings.TimeLimit div 3600, (gGameSettings.TimeLimit div 60) mod 60, gGameSettings.TimeLimit mod 60]);
1229     end;
1231     GM_COOP:
1232     begin
1233       if gGameSettings.MaxLives = 0 then
1234         s1 := _lc[I_GAME_COOP]
1235       else
1236         s1 := _lc[I_GAME_SURV];
1237       s2 := _lc[I_GAME_MONSTERS] + ' ' + IntToStr(gCoopMonstersKilled) + '/' + IntToStr(gTotalMonsters);
1238       s3 := _lc[I_GAME_SECRETS] + ' ' + IntToStr(gCoopSecretsFound) + '/' + IntToStr(gSecretsCount);
1239     end;
1241     else
1242     begin
1243       s1 := '';
1244       s2 := '';
1245     end;
1246   end;
1248   _y := y+8;
1249   e_TextureFontPrintEx(x+(w div 2)-(Length(s1)*cw div 2), _y, s1, gStdFont, 255, 255, 255, 1);
1250   _y := _y+ch+8;
1251   e_TextureFontPrintEx(x+(w div 2)-(Length(mapstr)*cw div 2), _y, mapstr, gStdFont, 200, 200, 200, 1);
1252   _y := _y+ch+8;
1253   e_TextureFontPrintEx(x+16, _y, s2, gStdFont, 200, 200, 200, 1);
1255   e_TextureFontPrintEx(x+w-16-(Length(s3))*cw, _y, s3,
1256                        gStdFont, 200, 200, 200, 1);
1258   if NetMode = NET_SERVER then
1259     e_TextureFontPrintEx(x+8, y + 8, _lc[I_NET_SERVER], gStdFont, 255, 255, 255, 1)
1260   else
1261     if NetMode = NET_CLIENT then
1262       e_TextureFontPrintEx(x+8, y + 8,
1263         NetClientIP + ':' + IntToStr(NetClientPort), gStdFont, 255, 255, 255, 1);
1265   if pc = 0 then
1266     Exit;
1267   stat := g_Player_GetStats();
1268   SortGameStat(stat);
1270   w2 := (w-16) div 6 + 48; // øèðèíà 2 ñòîëáöà
1271   w3 := (w-16) div 6; // øèðèíà 3 è 4 ñòîëáöîâ
1272   w4 := w3;
1273   w1 := w-16-w2-w3-w4; // îñòàâøååñÿ ïðîñòðàíñòâî - äëÿ öâåòà è èìåíè èãðîêà
1275   if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
1276   begin
1277     _y := _y+ch+ch;
1279     for a := TEAM_RED to TEAM_BLUE do
1280     begin
1281       if a = TEAM_RED then
1282       begin
1283         s1 := _lc[I_GAME_TEAM_RED];
1284         r := 255;
1285         g := 0;
1286         b := 0;
1287       end
1288       else
1289       begin
1290         s1 := _lc[I_GAME_TEAM_BLUE];
1291         r := 0;
1292         g := 0;
1293         b := 255;
1294       end;
1296       e_TextureFontPrintEx(x+16, _y, s1, gStdFont, r, g, b, 1);
1297       e_TextureFontPrintEx(x+w1+16, _y, IntToStr(gTeamStat[a].Goals),
1298                            gStdFont, r, g, b, 1);
1300       _y := _y+ch+(ch div 4);
1301       e_DrawLine(1, x+16, _y, x+w-16, _y, r, g, b);
1302       _y := _y+(ch div 4);
1304       for aa := 0 to High(stat) do
1305         if stat[aa].Team = a then
1306           with stat[aa] do
1307           begin
1308             if Spectator then
1309             begin
1310               rr := r div 2;
1311               gg := g div 2;
1312               bb := b div 2;
1313             end
1314             else
1315             begin
1316               rr := r;
1317               gg := g;
1318               bb := b;
1319             end;
1320             if gShowPIDs then
1321               namestr := Format('[%5d] %s', [UID, Name])
1322             else
1323               namestr := Name;
1324             // Èìÿ
1325             e_TextureFontPrintEx(x+16, _y, namestr, gStdFont, rr, gg, bb, 1);
1326             // Ïèíã/ïîòåðè
1327             e_TextureFontPrintEx(x+w1+16, _y, Format(_lc[I_GAME_PING_MS], [Ping, Loss]), gStdFont, rr, gg, bb, 1);
1328             // Ôðàãè
1329             e_TextureFontPrintEx(x+w1+w2+16, _y, IntToStr(Frags), gStdFont, rr, gg, bb, 1);
1330             // Ñìåðòè
1331             e_TextureFontPrintEx(x+w1+w2+w3+16, _y, IntToStr(Deaths), gStdFont, rr, gg, bb, 1);
1332             _y := _y+ch;
1333           end;
1335           _y := _y+ch;
1336     end;
1337   end
1338   else if gGameSettings.GameMode in [GM_DM, GM_COOP] then
1339   begin
1340     _y := _y+ch+ch;
1341     e_TextureFontPrintEx(x+16, _y, _lc[I_GAME_PLAYER_NAME], gStdFont, 255, 127, 0, 1);
1342     e_TextureFontPrintEx(x+16+w1, _y, _lc[I_GAME_PING], gStdFont, 255, 127, 0, 1);
1343     e_TextureFontPrintEx(x+16+w1+w2, _y, _lc[I_GAME_FRAGS], gStdFont, 255, 127, 0, 1);
1344     e_TextureFontPrintEx(x+16+w1+w2+w3, _y, _lc[I_GAME_DEATHS], gStdFont, 255, 127, 0, 1);
1346     _y := _y+ch+8;
1347     for aa := 0 to High(stat) do
1348       with stat[aa] do
1349       begin
1350         if Spectator then
1351         begin
1352           r := 127;
1353           g := 64;
1354         end
1355         else
1356         begin
1357           r := 255;
1358           g := 127;
1359         end;
1360         if gShowPIDs then
1361           namestr := Format('[%5d] %s', [UID, Name])
1362         else
1363           namestr := Name;
1364         // Öâåò èãðîêà
1365         e_DrawFillQuad(x+16, _y+4, x+32-1, _y+16+4-1, Color.R, Color.G, Color.B, 0);
1366         e_DrawQuad(x+16, _y+4, x+32-1, _y+16+4-1, 192, 192, 192);
1367         // Èìÿ
1368         e_TextureFontPrintEx(x+16+16+8, _y+4, namestr, gStdFont, r, g, 0, 1);
1369         // Ïèíã/ïîòåðè
1370         e_TextureFontPrintEx(x+w1+16, _y+4, Format(_lc[I_GAME_PING_MS], [Ping, Loss]), gStdFont, r, g, 0, 1);
1371         // Ôðàãè
1372         e_TextureFontPrintEx(x+w1+w2+16, _y+4, IntToStr(Frags), gStdFont, r, g, 0, 1);
1373         // Ñìåðòè
1374         e_TextureFontPrintEx(x+w1+w2+w3+16, _y+4, IntToStr(Deaths), gStdFont, r, g, 0, 1);
1375         _y := _y+ch+8;
1376       end;
1377   end
1378 end;
1380 procedure g_Game_Init();
1382   SR: TSearchRec;
1383   knownFiles: array of AnsiString = nil;
1384   found: Boolean;
1385   wext, s: AnsiString;
1386   f: Integer;
1387 begin
1388   gExit := 0;
1389   gMapToDelete := '';
1390   gTempDelete := False;
1392   sfsGCDisable(); // temporary disable removing of temporary volumes
1394   try
1395     TEXTUREFILTER := GL_LINEAR;
1396     g_Texture_CreateWADEx('MENU_BACKGROUND', GameWAD+':TEXTURES\TITLE');
1397     g_Texture_CreateWADEx('INTER', GameWAD+':TEXTURES\INTER');
1398     g_Texture_CreateWADEx('ENDGAME_EN', GameWAD+':TEXTURES\ENDGAME_EN');
1399     g_Texture_CreateWADEx('ENDGAME_RU', GameWAD+':TEXTURES\ENDGAME_RU');
1400     TEXTUREFILTER := GL_NEAREST;
1402     LoadStdFont('STDTXT', 'STDFONT', gStdFont);
1403     LoadFont('MENUTXT', 'MENUFONT', gMenuFont);
1404     LoadFont('SMALLTXT', 'SMALLFONT', gMenuSmallFont);
1406     g_Game_ClearLoading();
1407     g_Game_SetLoadingText(Format('Doom 2D: Forever %s', [GAME_VERSION]), 0, False);
1408     g_Game_SetLoadingText('', 0, False);
1410     g_Game_SetLoadingText(_lc[I_LOAD_CONSOLE], 0, False);
1411     g_Console_Init();
1413     g_Game_SetLoadingText(_lc[I_LOAD_MODELS], 0, False);
1414     g_PlayerModel_LoadData();
1416     // load models from all possible wad types, in all known directories
1417     // this does a loosy job (linear search, ooph!), but meh
1418     for wext in wadExtensions do
1419     begin
1420       for f := High(ModelDirs) downto Low(ModelDirs) do
1421       begin
1422         if (FindFirst(ModelDirs[f]+DirectorySeparator+'*'+wext, faAnyFile, SR) = 0) then
1423         begin
1424           repeat
1425             found := false;
1426             for s in knownFiles do
1427             begin
1428               if (strEquCI1251(forceFilenameExt(SR.Name, ''), forceFilenameExt(ExtractFileName(s), ''))) then
1429               begin
1430                 found := true;
1431                 break;
1432               end;
1433             end;
1434             if not found then
1435             begin
1436               SetLength(knownFiles, length(knownFiles)+1);
1437               knownFiles[High(knownFiles)] := ModelDirs[f]+DirectorySeparator+SR.Name;
1438             end;
1439           until (FindNext(SR) <> 0);
1440         end;
1441         FindClose(SR);
1442       end;
1443     end;
1445     if (length(knownFiles) = 0) then raise Exception.Create('no player models found!');
1447     if (length(knownFiles) = 1) then e_LogWriteln('1 player model found.', TMsgType.Notify) else e_LogWritefln('%d player models found.', [Integer(length(knownFiles))], TMsgType.Notify);
1448     for s in knownFiles do
1449     begin
1450       if not g_PlayerModel_Load(s) then e_LogWritefln('Error loading model "%s"', [s], TMsgType.Warning);
1451     end;
1453     gGameOn := false;
1454     gPauseMain := false;
1455     gPauseHolmes := false;
1456     gTime := 0;
1458     {e_MouseInfo.Accel := 1.0;}
1460     g_Game_SetLoadingText(_lc[I_LOAD_GAME_DATA], 0, False);
1461     g_Game_LoadData();
1463     g_Game_SetLoadingText(_lc[I_LOAD_MUSIC], 0, False);
1464     g_Sound_CreateWADEx('MUSIC_INTERMUS', GameWAD+':MUSIC\INTERMUS', True);
1465     g_Sound_CreateWADEx('MUSIC_MENU', GameWAD+':MUSIC\MENU', True);
1466     g_Sound_CreateWADEx('MUSIC_ROUNDMUS', GameWAD+':MUSIC\ROUNDMUS', True, True);
1467     g_Sound_CreateWADEx('MUSIC_STDENDMUS', GameWAD+':MUSIC\ENDMUS', True);
1469 {$IFNDEF HEADLESS}
1470     g_Game_SetLoadingText(_lc[I_LOAD_MENUS], 0, False);
1471     g_Menu_Init();
1472 {$ENDIF}
1474     gMusic := TMusic.Create();
1475     gMusic.SetByName('MUSIC_MENU');
1476     gMusic.Play();
1478     gGameSettings.WarmupTime := 30;
1480     gState := STATE_MENU;
1482     SetLength(gEvents, 6);
1483     gEvents[0].Name := 'ongamestart';
1484     gEvents[1].Name := 'ongameend';
1485     gEvents[2].Name := 'onmapstart';
1486     gEvents[3].Name := 'onmapend';
1487     gEvents[4].Name := 'oninter';
1488     gEvents[5].Name := 'onwadend';
1489   finally
1490     sfsGCEnable(); // enable releasing unused volumes
1491   end;
1492 end;
1494 procedure g_Game_Free(freeTextures: Boolean=true);
1495 begin
1496   if NetMode = NET_CLIENT then g_Net_Disconnect();
1497   if NetMode = NET_SERVER then g_Net_Host_Die();
1499   g_Map_Free(freeTextures);
1500   g_Player_Free();
1501   g_Player_RemoveAllCorpses();
1503   gGameSettings.GameType := GT_NONE;
1504   if gGameSettings.GameMode = GM_SINGLE then
1505     gGameSettings.GameMode := GM_DM;
1506   gSwitchGameMode := gGameSettings.GameMode;
1508   gChatShow := False;
1509   gExitByTrigger := False;
1510 end;
1512 function IsActivePlayer(p: TPlayer): Boolean;
1513 begin
1514   Result := False;
1515   if p = nil then
1516     Exit;
1517   Result := (not p.FDummy) and (not p.FSpectator);
1518 end;
1520 function GetActivePlayer_ByID(ID: Integer): TPlayer;
1522   a: Integer;
1523 begin
1524   Result := nil;
1525   if ID < 0 then
1526     Exit;
1527   if gPlayers = nil then
1528     Exit;
1529   for a := Low(gPlayers) to High(gPlayers) do
1530     if IsActivePlayer(gPlayers[a]) then
1531     begin
1532       if gPlayers[a].UID <> ID then
1533         continue;
1534       Result := gPlayers[a];
1535       break;
1536     end;
1537 end;
1539 function GetActivePlayerID_Next(Skip: Integer = -1): Integer;
1541   a, idx: Integer;
1542   ids: Array of Word;
1543 begin
1544   Result := -1;
1545   if gPlayers = nil then
1546     Exit;
1547   SetLength(ids, 0);
1548   idx := -1;
1549   for a := Low(gPlayers) to High(gPlayers) do
1550     if IsActivePlayer(gPlayers[a]) then
1551     begin
1552       SetLength(ids, Length(ids) + 1);
1553       ids[High(ids)] := gPlayers[a].UID;
1554       if gPlayers[a].UID = Skip then
1555         idx := High(ids);
1556     end;
1557   if Length(ids) = 0 then
1558     Exit;
1559   if idx = -1 then
1560     Result := ids[0]
1561   else
1562     Result := ids[(idx + 1) mod Length(ids)];
1563 end;
1565 function GetActivePlayerID_Prev(Skip: Integer = -1): Integer;
1567   a, idx: Integer;
1568   ids: Array of Word;
1569 begin
1570   Result := -1;
1571   if gPlayers = nil then
1572     Exit;
1573   SetLength(ids, 0);
1574   idx := -1;
1575   for a := Low(gPlayers) to High(gPlayers) do
1576     if IsActivePlayer(gPlayers[a]) then
1577     begin
1578       SetLength(ids, Length(ids) + 1);
1579       ids[High(ids)] := gPlayers[a].UID;
1580       if gPlayers[a].UID = Skip then
1581         idx := High(ids);
1582     end;
1583   if Length(ids) = 0 then
1584     Exit;
1585   if idx = -1 then
1586     Result := ids[Length(ids) - 1]
1587   else
1588     Result := ids[(Length(ids) - 1 + idx) mod Length(ids)];
1589 end;
1591 function GetActivePlayerID_Random(Skip: Integer = -1): Integer;
1593   a, idx: Integer;
1594   ids: Array of Word;
1595 begin
1596   Result := -1;
1597   if gPlayers = nil then
1598     Exit;
1599   SetLength(ids, 0);
1600   idx := -1;
1601   for a := Low(gPlayers) to High(gPlayers) do
1602     if IsActivePlayer(gPlayers[a]) then
1603     begin
1604       SetLength(ids, Length(ids) + 1);
1605       ids[High(ids)] := gPlayers[a].UID;
1606       if gPlayers[a].UID = Skip then
1607         idx := High(ids);
1608     end;
1609   if Length(ids) = 0 then
1610     Exit;
1611   if Length(ids) = 1 then
1612   begin
1613     Result := ids[0];
1614     Exit;
1615   end;
1616   Result := ids[Random(Length(ids))];
1617   a := 10;
1618   while (idx <> -1) and (Result = Skip) and (a > 0) do
1619   begin
1620     Result := ids[Random(Length(ids))];
1621     Dec(a);
1622   end;
1623 end;
1625 function GetRandomSpectMode(Current: Byte): Byte;
1626 label
1627   retry;
1628 begin
1629   Result := Current;
1630 retry:
1631   case Random(7) of
1632     0: Result := SPECT_STATS;
1633     1: Result := SPECT_MAPVIEW;
1634     2: Result := SPECT_MAPVIEW;
1635     3: Result := SPECT_PLAYERS;
1636     4: Result := SPECT_PLAYERS;
1637     5: Result := SPECT_PLAYERS;
1638     6: Result := SPECT_PLAYERS;
1639   end;
1640   if (Current in [SPECT_STATS, SPECT_MAPVIEW]) and (Current = Result) then
1641     goto retry;
1642 end;
1644 procedure ProcessPlayerControls (plr: TPlayer; p: Integer; var MoveButton: Byte);
1645   var
1646     time: Word;
1647     strafeDir: Byte;
1648     i: Integer;
1649 begin
1650   if (plr = nil) then exit;
1651   if (p = 2) then time := 1000 else time := 1;
1652   strafeDir := MoveButton shr 4;
1653   MoveButton := MoveButton and $0F;
1655   if gPlayerAction[p, ACTION_MOVELEFT] and (not gPlayerAction[p, ACTION_MOVERIGHT]) then
1656     MoveButton := 1 // Íàæàòà òîëüêî "Âëåâî"
1657   else if (not gPlayerAction[p, ACTION_MOVELEFT]) and gPlayerAction[p, ACTION_MOVERIGHT] then
1658     MoveButton := 2 // Íàæàòà òîëüêî "Âïðàâî"
1659   else if (not gPlayerAction[p, ACTION_MOVELEFT]) and (not gPlayerAction[p, ACTION_MOVERIGHT]) then
1660     MoveButton := 0; // Íå íàæàòû íè "Âëåâî", íè "Âïðàâî"
1662   // Ñåé÷àñ èëè ðàíüøå áûëè íàæàòû "Âëåâî"/"Âïðàâî" => ïåðåäàåì èãðîêó:
1663   if MoveButton = 1 then
1664     plr.PressKey(KEY_LEFT, time)
1665   else if MoveButton = 2 then
1666     plr.PressKey(KEY_RIGHT, time);
1668   // if we have "strafe" key, turn off old strafe mechanics
1669   if gPlayerAction[p, ACTION_STRAFE] then
1670   begin
1671     // new strafe mechanics
1672     if (strafeDir = 0) then
1673       strafeDir := MoveButton; // start strafing
1674     // now set direction according to strafe (reversed)
1675     if (strafeDir = 2) then
1676       plr.SetDirection(TDirection.D_LEFT)
1677     else if (strafeDir = 1) then
1678       plr.SetDirection(TDirection.D_RIGHT)
1679   end
1680   else
1681   begin
1682     strafeDir := 0; // not strafing anymore
1683     // Ðàíüøå áûëà íàæàòà "Âïðàâî", à ñåé÷àñ "Âëåâî" => áåæèì âïðàâî, ñìîòðèì âëåâî:
1684     if (MoveButton = 2) and gPlayerAction[p, ACTION_MOVELEFT] then
1685       plr.SetDirection(TDirection.D_LEFT)
1686     // Ðàíüøå áûëà íàæàòà "Âëåâî", à ñåé÷àñ "Âïðàâî" => áåæèì âëåâî, ñìîòðèì âïðàâî:
1687     else if (MoveButton = 1) and gPlayerAction[p, ACTION_MOVERIGHT] then
1688       plr.SetDirection(TDirection.D_RIGHT)
1689     // ×òî-òî áûëî íàæàòî è íå èçìåíèëîñü => êóäà áåæèì, òóäà è ñìîòðèì:
1690     else if MoveButton <> 0 then
1691       plr.SetDirection(TDirection(MoveButton-1))
1692   end;
1694   // fix movebutton state
1695   MoveButton := MoveButton or (strafeDir shl 4);
1697   // Îñòàëüíûå êëàâèøè:
1698   if gPlayerAction[p, ACTION_JUMP] then plr.PressKey(KEY_JUMP, time);
1699   if gPlayerAction[p, ACTION_LOOKUP] then plr.PressKey(KEY_UP, time);
1700   if gPlayerAction[p, ACTION_LOOKDOWN] then plr.PressKey(KEY_DOWN, time);
1701   if gPlayerAction[p, ACTION_ATTACK] then plr.PressKey(KEY_FIRE);
1702   if gPlayerAction[p, ACTION_ACTIVATE] then plr.PressKey(KEY_OPEN);
1704   for i := WP_FACT to WP_LACT do
1705   begin
1706     if gWeaponAction[p, i] then
1707     begin
1708       plr.ProcessWeaponAction(i);
1709       gWeaponAction[p, i] := False
1710     end
1711   end;
1713   for i := WP_FIRST to WP_LAST do
1714   begin
1715     if gSelectWeapon[p, i] then
1716     begin
1717       plr.QueueWeaponSwitch(i); // all choices are passed there, and god will take the best
1718       gSelectWeapon[p, i] := False
1719     end
1720   end;
1722   // HACK: add dynlight here
1723   if gwin_k8_enable_light_experiments then
1724   begin
1725     if e_KeyPressed(IK_F8) and gGameOn and (not gConsoleShow) and (g_ActiveWindow = nil) then
1726     begin
1727       g_playerLight := true;
1728     end;
1729     if e_KeyPressed(IK_F9) and gGameOn and (not gConsoleShow) and (g_ActiveWindow = nil) then
1730     begin
1731       g_playerLight := false;
1732     end;
1733   end;
1735   if gwin_has_stencil and g_playerLight then g_AddDynLight(plr.GameX+32, plr.GameY+40, 128, 1, 1, 0, 0.6);
1736 end;
1738 // HACK: don't have a "key was pressed" function
1739 procedure InterReady();
1740 begin
1741   if InterReadyTime > gTime then Exit;
1742   InterReadyTime := gTime + 3000;
1743   MC_SEND_CheatRequest(NET_CHEAT_READY);
1744 end;
1746 procedure g_Game_PreUpdate();
1747 begin
1748   // these are in separate PreUpdate functions because they can interact during Update()
1749   // and are synced over the net
1750   // we don't care that much about corpses and gibs
1751   g_Player_PreUpdate();
1752   g_Monsters_PreUpdate();
1753   g_Items_PreUpdate();
1754   g_Weapon_PreUpdate();
1755 end;
1757 procedure g_Game_Update();
1759   Msg: g_gui.TMessage;
1760   Time: Int64;
1761   a: Byte;
1762   w: Word;
1763   i, b: Integer;
1765   function sendMonsPos (mon: TMonster): Boolean;
1766   begin
1767     result := false; // don't stop
1768     // this will also reset "need-send" flag
1769     if mon.gncNeedSend then
1770     begin
1771       MH_SEND_MonsterPos(mon.UID);
1772     end
1773     else if (mon.MonsterType = MONSTER_BARREL) then
1774     begin
1775       if (mon.GameVelX <> 0) or (mon.GameVelY <> 0) then MH_SEND_MonsterPos(mon.UID);
1776     end
1777     else if (mon.MonsterState <> MONSTATE_SLEEP) then
1778     begin
1779       if (mon.MonsterState <> MONSTATE_DEAD) or (mon.GameVelX <> 0) or (mon.GameVelY <> 0) then MH_SEND_MonsterPos(mon.UID);
1780     end;
1781   end;
1783   function sendMonsPosUnexpected (mon: TMonster): Boolean;
1784   begin
1785     result := false; // don't stop
1786     // this will also reset "need-send" flag
1787     if mon.gncNeedSend then MH_SEND_MonsterPos(mon.UID);
1788   end;
1790   function sendItemPos (it: PItem): Boolean;
1791   begin
1792     result := false; // don't stop
1793     if it.needSend then
1794     begin
1795       MH_SEND_ItemPos(it.myId);
1796       it.needSend := False;
1797     end;
1798   end;
1801   reliableUpdate: Boolean;
1802 begin
1803   g_ResetDynlights();
1804   framePool.reset();
1806 // Ïîðà âûêëþ÷àòü èãðó:
1807   if gExit = EXIT_QUIT then
1808     Exit;
1809 // Èãðà çàêîí÷èëàñü - îáðàáàòûâàåì:
1810   if gExit <> 0 then
1811   begin
1812     EndGame();
1813     if gExit = EXIT_QUIT then
1814       Exit;
1815   end;
1817   // ×èòàåì êëàâèàòóðó è äæîéñòèê, åñëè îêíî àêòèâíî
1818   // no need to, as we'll do it in event handler
1820 // Îáíîâëÿåì êîíñîëü (äâèæåíèå è ñîîáùåíèÿ):
1821   g_Console_Update();
1823   if (NetMode = NET_NONE) and (g_Game_IsNet) and (gGameOn or (gState in [STATE_FOLD, STATE_INTERCUSTOM])) then
1824   begin
1825     gExit := EXIT_SIMPLE;
1826     EndGame();
1827     Exit;
1828   end;
1830   // process master server communications
1831   g_Net_Slist_Pulse();
1833   case gState of
1834     STATE_INTERSINGLE, // Ñòàòèñòêà ïîñëå ïðîõîæäåíèÿ óðîâíÿ â Îäèíî÷íîé èãðå
1835     STATE_INTERCUSTOM, // Ñòàòèñòêà ïîñëå ïðîõîæäåíèÿ óðîâíÿ â Ñâîåé èãðå
1836     STATE_INTERTEXT, // Òåêñò ìåæäó óðîâíÿìè
1837     STATE_INTERPIC: // Êàðòèíêà ìåæäó óðîâíÿìè
1838       begin
1839         if g_Game_IsNet and g_Game_IsServer then
1840         begin
1841           gInterTime := gInterTime + GAME_TICK;
1842           a := Min((gInterEndTime - gInterTime) div 1000 + 1, 255);
1843           if a <> gServInterTime then
1844           begin
1845             gServInterTime := a;
1846             MH_SEND_TimeSync(gServInterTime);
1847           end;
1848         end;
1850         if (not g_Game_IsClient) and
1851         (
1852           (
1853             (
1854               e_KeyPressed(IK_RETURN) or e_KeyPressed(IK_KPRETURN) or e_KeyPressed(IK_SPACE) or
1855               e_KeyPressed(VK_FIRE) or e_KeyPressed(VK_OPEN) or
1856               e_KeyPressed(JOY0_ATTACK) or e_KeyPressed(JOY1_ATTACK) or
1857               e_KeyPressed(JOY2_ATTACK) or e_KeyPressed(JOY3_ATTACK)
1858             )
1859             and (not gJustChatted) and (not gConsoleShow) and (not gChatShow)
1860             and (g_ActiveWindow = nil)
1861           )
1862           or (g_Game_IsNet and ((gInterTime > gInterEndTime) or ((gInterReadyCount >= NetClientCount) and (NetClientCount > 0))))
1863         )
1864         then
1865         begin // Íàæàëè <Enter>/<Ïðîáåë> èëè ïðîøëî äîñòàòî÷íî âðåìåíè:
1866           g_Game_StopAllSounds(True);
1868           if gMapOnce then // Ýòî áûë òåñò
1869             gExit := EXIT_SIMPLE
1870           else
1871             if gNextMap <> '' then // Ïåðåõîäèì íà ñëåäóþùóþ êàðòó
1872               g_Game_ChangeMap(gNextMap)
1873             else // Ñëåäóþùåé êàðòû íåò
1874             begin
1875               if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER] then
1876               begin
1877               // Âûõîä â ãëàâíîå ìåíþ:
1878                 g_Game_Free;
1879                 g_GUI_ShowWindow('MainMenu');
1880                 gMusic.SetByName('MUSIC_MENU');
1881                 gMusic.Play();
1882                 gState := STATE_MENU;
1883               end else
1884               begin
1885               // Ôèíàëüíàÿ êàðòèíêà:
1886                 g_Game_ExecuteEvent('onwadend');
1887                 g_Game_Free();
1888                 if not gMusic.SetByName('MUSIC_endmus') then
1889                   gMusic.SetByName('MUSIC_STDENDMUS');
1890                 gMusic.Play();
1891                 gState := STATE_ENDPIC;
1892               end;
1893               g_Game_ExecuteEvent('ongameend');
1894             end;
1896           Exit;
1897         end
1898         else if g_Game_IsClient and
1899         (
1900           (
1901             e_KeyPressed(IK_RETURN) or e_KeyPressed(IK_KPRETURN) or e_KeyPressed(IK_SPACE) or
1902             e_KeyPressed(VK_FIRE) or e_KeyPressed(VK_OPEN) or
1903             e_KeyPressed(JOY0_ATTACK) or e_KeyPressed(JOY1_ATTACK) or
1904             e_KeyPressed(JOY2_ATTACK) or e_KeyPressed(JOY3_ATTACK)
1905           )
1906           and (not gJustChatted) and (not gConsoleShow) and (not gChatShow)
1907           and (g_ActiveWindow = nil)
1908         )
1909         then
1910         begin
1911           // ready / unready
1912           InterReady();
1913         end;
1915         if gState = STATE_INTERTEXT then
1916           if InterText.counter > 0 then
1917             InterText.counter := InterText.counter - 1;
1918       end;
1920     STATE_FOLD: // Çàòóõàíèå ýêðàíà
1921       begin
1922         if EndingGameCounter = 0 then
1923           begin
1924           // Çàêîí÷èëñÿ óðîâåíü â Ñâîåé èãðå:
1925             if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
1926               begin
1927                 gState := STATE_INTERCUSTOM;
1928                 InterReadyTime := -1;
1929                 if gLastMap and (gGameSettings.GameMode = GM_COOP) then
1930                 begin
1931                   g_Game_ExecuteEvent('onwadend');
1932                   if not gMusic.SetByName('MUSIC_endmus') then
1933                     gMusic.SetByName('MUSIC_STDENDMUS');
1934                 end
1935                 else
1936                   gMusic.SetByName('MUSIC_ROUNDMUS');
1937                 gMusic.Play();
1938                 e_UnpressAllKeys();
1939               end
1940             else // Çàêîí÷èëàñü ïîñëåäíÿÿ êàðòà â Îäèíî÷íîé èãðå
1941               begin
1942                 gMusic.SetByName('MUSIC_INTERMUS');
1943                 gMusic.Play();
1944                 gState := STATE_INTERSINGLE;
1945                 e_UnpressAllKeys();
1946               end;
1947             g_Game_ExecuteEvent('oninter');
1948           end
1949         else
1950           DecMin(EndingGameCounter, 6, 0);
1951       end;
1953     STATE_ENDPIC: // Êàðòèíêà îêîí÷àíèÿ ìåãàÂàäà
1954       begin
1955         if gMapOnce then // Ýòî áûë òåñò
1956         begin
1957           gExit := EXIT_SIMPLE;
1958           Exit;
1959         end;
1960       end;
1962     STATE_SLIST:
1963         g_Serverlist_Control(slCurrent, slTable);
1964   end;
1966 // Ñòàòèñòèêà ïî Tab:
1967   if gGameOn then
1968     IsDrawStat := (not gConsoleShow) and (not gChatShow) and (gGameSettings.GameType <> GT_SINGLE) and g_Console_Action(ACTION_SCORES);
1970 // Èãðà èäåò:
1971   if gGameOn and not gPause and (gState <> STATE_FOLD) then
1972   begin
1973   // Âðåìÿ += 28 ìèëëèñåêóíä:
1974     gTime := gTime + GAME_TICK;
1976   // Ñîîáùåíèå ïîñåðåäèíå ýêðàíà:
1977     if MessageTime = 0 then
1978       MessageText := '';
1979     if MessageTime > 0 then
1980       MessageTime := MessageTime - 1;
1982     if (g_Game_IsServer) then
1983     begin
1984     // Áûë çàäàí ëèìèò âðåìåíè:
1985       if (gGameSettings.TimeLimit > 0) then
1986         if (gTime - gGameStartTime) div 1000 >= gGameSettings.TimeLimit then
1987         begin // Îí ïðîøåë => êîíåö óðîâíÿ
1988           g_Game_NextLevel();
1989           Exit;
1990         end;
1992     // Íàäî ðåñïàâíèòü èãðîêîâ â LMS:
1993       if (gLMSRespawn > LMS_RESPAWN_NONE) and (gLMSRespawnTime < gTime) then
1994         g_Game_RestartRound(gLMSSoftSpawn);
1996     // Ïðîâåðèì ðåçóëüòàò ãîëîñîâàíèÿ, åñëè âðåìÿ ïðîøëî
1997       if gVoteInProgress and (gVoteTimer < gTime) then
1998         g_Game_CheckVote
1999       else if gVotePassed and (gVoteCmdTimer < gTime) then
2000       begin
2001         g_Console_Process(gVoteCommand);
2002         gVoteCommand := '';
2003         gVotePassed := False;
2004       end;
2006     // Çàìåðÿåì âðåìÿ çàõâàòà ôëàãîâ
2007       if gFlags[FLAG_RED].State = FLAG_STATE_CAPTURED then
2008         gFlags[FLAG_RED].CaptureTime := gFlags[FLAG_RED].CaptureTime + GAME_TICK;
2009       if gFlags[FLAG_BLUE].State = FLAG_STATE_CAPTURED then
2010         gFlags[FLAG_BLUE].CaptureTime := gFlags[FLAG_BLUE].CaptureTime + GAME_TICK;
2012     // Áûë çàäàí ëèìèò ïîáåä:
2013       if (gGameSettings.GoalLimit > 0) then
2014       begin
2015         b := 0;
2017         if gGameSettings.GameMode = GM_DM then
2018           begin // Â DM èùåì èãðîêà ñ max ôðàãàìè
2019             for i := 0 to High(gPlayers) do
2020               if gPlayers[i] <> nil then
2021                 if gPlayers[i].Frags > b then
2022                   b := gPlayers[i].Frags;
2023           end
2024         else
2025           if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
2026           begin // Â CTF/TDM âûáèðàåì êîìàíäó ñ íàèáîëüøèì ñ÷åòîì
2027             b := Max(gTeamStat[TEAM_RED].Goals, gTeamStat[TEAM_BLUE].Goals);
2028           end;
2030       // Ëèìèò ïîáåä íàáðàí => êîíåö óðîâíÿ:
2031         if b >= gGameSettings.GoalLimit then
2032         begin
2033           g_Game_NextLevel();
2034           Exit;
2035         end;
2036       end;
2038     // Îáðàáàòûâàåì êëàâèøè èãðîêîâ:
2039       if gPlayer1 <> nil then gPlayer1.ReleaseKeys();
2040       if gPlayer2 <> nil then gPlayer2.ReleaseKeys();
2041       if (not gConsoleShow) and (not gChatShow) and (g_ActiveWindow = nil) then
2042       begin
2043         ProcessPlayerControls(gPlayer1, 0, P1MoveButton);
2044         ProcessPlayerControls(gPlayer2, 1, P2MoveButton);
2045       end  // if not console
2046       else
2047       begin
2048         if g_Game_IsNet and (gPlayer1 <> nil) then gPlayer1.PressKey(KEY_CHAT, 10000);
2049       end;
2050       // process weapon switch queue
2051     end; // if server
2053   // Íàáëþäàòåëü
2054     if (gPlayer1 = nil) and (gPlayer2 = nil) and
2055        (not gConsoleShow) and (not gChatShow) and (g_ActiveWindow = nil) then
2056     begin
2057       if not gSpectKeyPress then
2058       begin
2059         if gPlayerAction[0, ACTION_JUMP] and (not gSpectAuto) then
2060         begin
2061           // switch spect mode
2062           case gSpectMode of
2063             SPECT_NONE: ; // not spectator
2064             SPECT_STATS,
2065             SPECT_MAPVIEW: Inc(gSpectMode);
2066             SPECT_PLAYERS: gSpectMode := SPECT_STATS; // reset to 1
2067           end;
2068           gSpectKeyPress := True;
2069         end;
2070         if (gSpectMode = SPECT_MAPVIEW)
2071            and (not gSpectAuto) then
2072         begin
2073           if gPlayerAction[0, ACTION_MOVELEFT] then
2074             gSpectX := Max(gSpectX - gSpectStep, 0);
2075           if gPlayerAction[0, ACTION_MOVERIGHT] then
2076             gSpectX := Min(gSpectX + gSpectStep, gMapInfo.Width - gScreenWidth);
2077           if gPlayerAction[0, ACTION_LOOKUP] then
2078             gSpectY := Max(gSpectY - gSpectStep, 0);
2079           if gPlayerAction[0, ACTION_LOOKDOWN] then
2080             gSpectY := Min(gSpectY + gSpectStep, gMapInfo.Height - gScreenHeight);
2081           if gWeaponAction[0, WP_PREV] then
2082           begin
2083             // decrease step
2084             if gSpectStep > 4 then gSpectStep := gSpectStep shr 1;
2085             gWeaponAction[0, WP_PREV] := False;
2086           end;
2087           if gWeaponAction[0, WP_NEXT] then
2088           begin
2089             // increase step
2090             if gSpectStep < 64 then gSpectStep := gSpectStep shl 1;
2091             gWeaponAction[0, WP_NEXT] := False;
2092           end;
2093         end;
2094         if (gSpectMode = SPECT_PLAYERS)
2095            and (not gSpectAuto) then
2096         begin
2097           if gPlayerAction[0, ACTION_LOOKUP] then
2098           begin
2099             // add second view
2100             gSpectViewTwo := True;
2101             gSpectKeyPress := True;
2102           end;
2103           if gPlayerAction[0, ACTION_LOOKDOWN] then
2104           begin
2105             // remove second view
2106             gSpectViewTwo := False;
2107             gSpectKeyPress := True;
2108           end;
2109           if gPlayerAction[0, ACTION_MOVELEFT] then
2110           begin
2111             // prev player (view 1)
2112             gSpectPID1 := GetActivePlayerID_Prev(gSpectPID1);
2113             gSpectKeyPress := True;
2114           end;
2115           if gPlayerAction[0, ACTION_MOVERIGHT] then
2116           begin
2117             // next player (view 1)
2118             gSpectPID1 := GetActivePlayerID_Next(gSpectPID1);
2119             gSpectKeyPress := True;
2120           end;
2121           if gWeaponAction[0, WP_PREV] then
2122           begin
2123             // prev player (view 2)
2124             gSpectPID2 := GetActivePlayerID_Prev(gSpectPID2);
2125             gWeaponAction[0, WP_PREV] := False;
2126           end;
2127           if gWeaponAction[0, WP_NEXT] then
2128           begin
2129             // next player (view 2)
2130             gSpectPID2 := GetActivePlayerID_Next(gSpectPID2);
2131             gWeaponAction[0, WP_NEXT] := False;
2132           end;
2133         end;
2134         if gPlayerAction[0, ACTION_ATTACK] then
2135         begin
2136           if (gSpectMode = SPECT_STATS) and (not gSpectAuto) then
2137           begin
2138             gSpectAuto := True;
2139             gSpectAutoNext := 0;
2140             gSpectViewTwo := False;
2141             gSpectKeyPress := True;
2142           end
2143           else
2144             if gSpectAuto then
2145             begin
2146               gSpectMode := SPECT_STATS;
2147               gSpectAuto := False;
2148               gSpectKeyPress := True;
2149             end;
2150         end;
2151       end
2152       else
2153         if (not gPlayerAction[0, ACTION_JUMP]) and
2154            (not gPlayerAction[0, ACTION_ATTACK]) and
2155            (not gPlayerAction[0, ACTION_MOVELEFT]) and
2156            (not gPlayerAction[0, ACTION_MOVERIGHT]) and
2157            (not gPlayerAction[0, ACTION_LOOKUP]) and
2158            (not gPlayerAction[0, ACTION_LOOKDOWN]) then
2159           gSpectKeyPress := False;
2161       if gSpectAuto then
2162       begin
2163         if gSpectMode = SPECT_MAPVIEW then
2164         begin
2165           i := Min(Max(gSpectX + gSpectAutoStepX, 0), gMapInfo.Width - gScreenWidth);
2166           if i = gSpectX then
2167             gSpectAutoNext := gTime
2168           else
2169             gSpectX := i;
2170           i := Min(Max(gSpectY + gSpectAutoStepY, 0), gMapInfo.Height - gScreenHeight);
2171           if i = gSpectY then
2172             gSpectAutoNext := gTime
2173           else
2174             gSpectY := i;
2175         end;
2176         if gSpectAutoNext <= gTime then
2177         begin
2178           if gSpectAutoNext > 0 then
2179           begin
2180             gSpectMode := GetRandomSpectMode(gSpectMode);
2181             case gSpectMode of
2182               SPECT_MAPVIEW:
2183               begin
2184                 gSpectX := Random(gMapInfo.Width - gScreenWidth);
2185                 gSpectY := Random(gMapInfo.Height - gScreenHeight);
2186                 gSpectAutoStepX := Random(9) - 4;
2187                 gSpectAutoStepY := Random(9) - 4;
2188                 if ((gSpectX < 800) and (gSpectAutoStepX < 0)) or
2189                    ((gSpectX > gMapInfo.Width - gScreenWidth - 800) and (gSpectAutoStepX > 0)) then
2190                   gSpectAutoStepX := gSpectAutoStepX * -1;
2191                 if ((gSpectY < 800) and (gSpectAutoStepY < 0)) or
2192                    ((gSpectY > gMapInfo.Height - gScreenHeight - 800) and (gSpectAutoStepY > 0)) then
2193                   gSpectAutoStepY := gSpectAutoStepY * -1;
2194               end;
2195               SPECT_PLAYERS:
2196               begin
2197                 gSpectPID1 := GetActivePlayerID_Random(gSpectPID1);
2198               end;
2199             end;
2200           end;
2201           case gSpectMode of
2202             SPECT_STATS:   gSpectAutoNext := gTime + (Random(3) + 5) * 1000;
2203             SPECT_MAPVIEW: gSpectAutoNext := gTime + (Random(4) + 7) * 1000;
2204             SPECT_PLAYERS: gSpectAutoNext := gTime + (Random(7) + 8) * 1000;
2205           end;
2206         end;
2207       end;
2208     end;
2210   // Îáíîâëÿåì âñå îñòàëüíîå:
2211     g_Map_Update();
2212     g_Items_Update();
2213     g_Triggers_Update();
2214     g_Weapon_Update();
2215     g_Monsters_Update();
2216     g_GFX_Update();
2217     g_Player_UpdateAll();
2218     g_Player_UpdatePhysicalObjects();
2220     // server: send newly spawned monsters unconditionally
2221     if (gGameSettings.GameType = GT_SERVER) then
2222     begin
2223       if (Length(gMonstersSpawned) > 0) then
2224       begin
2225         for I := 0 to High(gMonstersSpawned) do MH_SEND_MonsterSpawn(gMonstersSpawned[I]);
2226         SetLength(gMonstersSpawned, 0);
2227       end;
2228     end;
2230     if (gSoundTriggerTime > 8) then
2231     begin
2232       g_Game_UpdateTriggerSounds();
2233       gSoundTriggerTime := 0;
2234     end
2235     else
2236     begin
2237       Inc(gSoundTriggerTime);
2238     end;
2240     if (NetMode = NET_SERVER) then
2241     begin
2242       Inc(NetTimeToUpdate);
2243       Inc(NetTimeToReliable);
2245       // send monster updates
2246       if (NetTimeToReliable >= NetRelupdRate) or (NetTimeToUpdate >= NetUpdateRate) then
2247       begin
2248         // send all monsters (periodic sync)
2249         reliableUpdate := (NetTimeToReliable >= NetRelupdRate);
2251         for I := 0 to High(gPlayers) do
2252         begin
2253           if (gPlayers[I] <> nil) then MH_SEND_PlayerPos(reliableUpdate, gPlayers[I].UID);
2254         end;
2256         g_Mons_ForEach(sendMonsPos);
2258         // update flags that aren't stationary
2259         if gGameSettings.GameMode = GM_CTF then
2260           for I := FLAG_RED to FLAG_BLUE do
2261             if gFlags[I].NeedSend then
2262             begin
2263               gFlags[I].NeedSend := False;
2264               MH_SEND_FlagPos(I);
2265             end;
2267         // update items that aren't stationary
2268         g_Items_ForEachAlive(sendItemPos);
2270         if reliableUpdate then
2271         begin
2272           NetTimeToReliable := 0;
2273           NetTimeToUpdate := NetUpdateRate;
2274         end
2275         else
2276         begin
2277           NetTimeToUpdate := 0;
2278         end;
2279       end
2280       else
2281       begin
2282         // send only mosters with some unexpected changes
2283         g_Mons_ForEach(sendMonsPosUnexpected);
2284       end;
2286       // send unexpected platform changes
2287       g_Map_NetSendInterestingPanels();
2289       g_Net_Slist_ServerUpdate();
2290       {
2291       if NetUseMaster then
2292       begin
2293         if (gTime >= NetTimeToMaster) or g_Net_Slist_IsConnectionInProgress then
2294         begin
2295           if (not g_Net_Slist_IsConnectionActive) then g_Net_Slist_Connect(false); // non-blocking connection to the master
2296           g_Net_Slist_Update;
2297           NetTimeToMaster := gTime + NetMasterRate;
2298         end;
2299       end;
2300       }
2301     end
2302     else if (NetMode = NET_CLIENT) then
2303     begin
2304       MC_SEND_PlayerPos();
2305     end;
2306   end; // if gameOn ...
2308 // Àêòèâíî îêíî èíòåðôåéñà - ïåðåäàåì êëàâèøè åìó:
2309   if g_ActiveWindow <> nil then
2310   begin
2311     w := e_GetFirstKeyPressed();
2313     if (w <> IK_INVALID) then
2314       begin
2315         Msg.Msg := MESSAGE_DIKEY;
2316         Msg.wParam := w;
2317         g_ActiveWindow.OnMessage(Msg);
2318       end;
2320   // Åñëè îíî îò ýòîãî íå çàêðûëîñü, òî îáíîâëÿåì:
2321     if g_ActiveWindow <> nil then
2322       g_ActiveWindow.Update();
2324   // Íóæíî ñìåíèòü ðàçðåøåíèå:
2325     if gResolutionChange then
2326     begin
2327       e_WriteLog('Changing resolution', TMsgType.Notify);
2328       g_Game_ChangeResolution(gRC_Width, gRC_Height, gRC_FullScreen, gRC_Maximized);
2329       gResolutionChange := False;
2330       g_ActiveWindow := nil;
2331     end;
2333   // Íóæíî ñìåíèòü ÿçûê:
2334     if gLanguageChange then
2335     begin
2336       //e_WriteLog('Read language file', MSG_NOTIFY);
2337       //g_Language_Load(DataDir + gLanguage + '.txt');
2338       g_Language_Set(gLanguage);
2339 {$IFNDEF HEADLESS}
2340       g_Menu_Reset();
2341 {$ENDIF}
2342       gLanguageChange := False;
2343     end;
2344   end;
2346 // Ãîðÿ÷àÿ êëàâèøà äëÿ âûçîâà ìåíþ âûõîäà èç èãðû (F10):
2347   if e_KeyPressed(IK_F10) and
2348      gGameOn and
2349      (not gConsoleShow) and
2350      (g_ActiveWindow = nil) then
2351   begin
2352     KeyPress(IK_F10);
2353   end;
2355   Time := sys_GetTicks() {div 1000};
2357 // Îáðàáîòêà îòëîæåííûõ ñîáûòèé:
2358   if gDelayedEvents <> nil then
2359     for a := 0 to High(gDelayedEvents) do
2360       if gDelayedEvents[a].Pending and
2361       (
2362         ((gDelayedEvents[a].DEType = DE_GLOBEVENT) and (gDelayedEvents[a].Time <= Time)) or
2363         ((gDelayedEvents[a].DEType > DE_GLOBEVENT) and (gDelayedEvents[a].Time <= gTime))
2364       ) then
2365       begin
2366         case gDelayedEvents[a].DEType of
2367           DE_GLOBEVENT:
2368             g_Game_ExecuteEvent(gDelayedEvents[a].DEStr);
2369           DE_BFGHIT:
2370             if gGameOn then
2371               g_Game_Announce_GoodShot(gDelayedEvents[a].DENum);
2372           DE_KILLCOMBO:
2373             if gGameOn then
2374             begin
2375               g_Game_Announce_KillCombo(gDelayedEvents[a].DENum);
2376               if g_Game_IsNet and g_Game_IsServer then
2377                 MH_SEND_GameEvent(NET_EV_KILLCOMBO, gDelayedEvents[a].DENum);
2378             end;
2379           DE_BODYKILL:
2380             if gGameOn then
2381               g_Game_Announce_BodyKill(gDelayedEvents[a].DENum);
2382         end;
2383         gDelayedEvents[a].Pending := False;
2384       end;
2386 // Êàæäóþ ñåêóíäó îáíîâëÿåì ñ÷åò÷èê îáíîâëåíèé:
2387   UPSCounter := UPSCounter + 1;
2388   if Time - UPSTime >= 1000 then
2389   begin
2390     UPS := UPSCounter;
2391     UPSCounter := 0;
2392     UPSTime := Time;
2393   end;
2395   if gGameOn then
2396   begin
2397     g_Weapon_AddDynLights();
2398     g_Items_AddDynLights();
2399   end;
2400 end;
2402 procedure g_Game_LoadChatSounds(Resource: string);
2404   WAD: TWADFile;
2405   FileName, Snd: string;
2406   p: Pointer;
2407   len, cnt, tags, i, j: Integer;
2408   cfg: TConfig;
2409 begin
2410   FileName := g_ExtractWadName(Resource);
2412   WAD := TWADFile.Create();
2413   WAD.ReadFile(FileName);
2415   if not WAD.GetResource(g_ExtractFilePathName(Resource), p, len) then
2416   begin
2417     gChatSounds := nil;
2418     WAD.Free();
2419     Exit;
2420   end;
2422   cfg := TConfig.CreateMem(p, len);
2423   cnt := cfg.ReadInt('ChatSounds', 'Count', 0);
2425   SetLength(gChatSounds, cnt);
2426   for i := 0 to Length(gChatSounds) - 1 do
2427   begin
2428     gChatSounds[i].Sound := nil;
2429     Snd := Trim(cfg.ReadStr(IntToStr(i), 'Sound', ''));
2430     tags := cfg.ReadInt(IntToStr(i), 'Tags', 0);
2431     if (Snd = '') or (Tags <= 0) then
2432       continue;
2433     g_Sound_CreateWADEx('SOUND_CHAT_MACRO' + IntToStr(i), GameWAD+':'+Snd);
2434     gChatSounds[i].Sound := TPlayableSound.Create();
2435     gChatSounds[i].Sound.SetByName('SOUND_CHAT_MACRO' + IntToStr(i));
2436     SetLength(gChatSounds[i].Tags, tags);
2437     for j := 0 to tags - 1 do
2438       gChatSounds[i].Tags[j] := toLowerCase1251(cfg.ReadStr(IntToStr(i), 'Tag' + IntToStr(j), ''));
2439     gChatSounds[i].FullWord := cfg.ReadBool(IntToStr(i), 'FullWord', False);
2440   end;
2442   cfg.Free();
2443   WAD.Free();
2444 end;
2446 procedure g_Game_FreeChatSounds();
2448   i: Integer;
2449 begin
2450   for i := 0 to Length(gChatSounds) - 1 do
2451   begin
2452     gChatSounds[i].Sound.Free();
2453     g_Sound_Delete('SOUND_CHAT_MACRO' + IntToStr(i));
2454   end;
2455   SetLength(gChatSounds, 0);
2456   gChatSounds := nil;
2457 end;
2459 procedure g_Game_LoadData();
2461   wl, hl: Integer;
2462   wr, hr: Integer;
2463   wb, hb: Integer;
2464   wm, hm: Integer;
2465 begin
2466   if DataLoaded then Exit;
2468   e_WriteLog('Loading game data...', TMsgType.Notify);
2470   g_Texture_CreateWADEx('NOTEXTURE', GameWAD+':TEXTURES\NOTEXTURE');
2471   g_Texture_CreateWADEx('TEXTURE_PLAYER_HUD', GameWAD+':TEXTURES\HUD');
2472   g_Texture_CreateWADEx('TEXTURE_PLAYER_HUDAIR', GameWAD+':TEXTURES\AIRBAR');
2473   g_Texture_CreateWADEx('TEXTURE_PLAYER_HUDJET', GameWAD+':TEXTURES\JETBAR');
2474   g_Texture_CreateWADEx('TEXTURE_PLAYER_HUDBG', GameWAD+':TEXTURES\HUDBG');
2475   g_Texture_CreateWADEx('TEXTURE_PLAYER_ARMORHUD', GameWAD+':TEXTURES\ARMORHUD');
2476   g_Texture_CreateWADEx('TEXTURE_PLAYER_REDFLAG', GameWAD+':TEXTURES\FLAGHUD_R_BASE');
2477   g_Texture_CreateWADEx('TEXTURE_PLAYER_REDFLAG_S', GameWAD+':TEXTURES\FLAGHUD_R_STOLEN');
2478   g_Texture_CreateWADEx('TEXTURE_PLAYER_REDFLAG_D', GameWAD+':TEXTURES\FLAGHUD_R_DROP');
2479   g_Texture_CreateWADEx('TEXTURE_PLAYER_BLUEFLAG', GameWAD+':TEXTURES\FLAGHUD_B_BASE');
2480   g_Texture_CreateWADEx('TEXTURE_PLAYER_BLUEFLAG_S', GameWAD+':TEXTURES\FLAGHUD_B_STOLEN');
2481   g_Texture_CreateWADEx('TEXTURE_PLAYER_BLUEFLAG_D', GameWAD+':TEXTURES\FLAGHUD_B_DROP');
2482   g_Texture_CreateWADEx('TEXTURE_PLAYER_TALKBUBBLE', GameWAD+':TEXTURES\TALKBUBBLE');
2483   g_Texture_CreateWADEx('TEXTURE_PLAYER_INVULPENTA', GameWAD+':TEXTURES\PENTA');
2484   g_Texture_CreateWADEx('TEXTURE_PLAYER_INDICATOR', GameWAD+':TEXTURES\PLRIND');
2486   hasPBarGfx := true;
2487   if not g_Texture_CreateWADEx('UI_GFX_PBAR_LEFT', GameWAD+':TEXTURES\LLEFT') then hasPBarGfx := false;
2488   if not g_Texture_CreateWADEx('UI_GFX_PBAR_MARKER', GameWAD+':TEXTURES\LMARKER') then hasPBarGfx := false;
2489   if not g_Texture_CreateWADEx('UI_GFX_PBAR_MIDDLE', GameWAD+':TEXTURES\LMIDDLE') then hasPBarGfx := false;
2490   if not g_Texture_CreateWADEx('UI_GFX_PBAR_RIGHT', GameWAD+':TEXTURES\LRIGHT') then hasPBarGfx := false;
2492   if hasPBarGfx then
2493   begin
2494     g_Texture_GetSize('UI_GFX_PBAR_LEFT', wl, hl);
2495     g_Texture_GetSize('UI_GFX_PBAR_RIGHT', wr, hr);
2496     g_Texture_GetSize('UI_GFX_PBAR_MIDDLE', wb, hb);
2497     g_Texture_GetSize('UI_GFX_PBAR_MARKER', wm, hm);
2498     if (wl > 0) and (hl > 0) and (wr > 0) and (hr = hl) and (wb > 0) and (hb = hl) and (wm > 0) and (hm > 0) and (hm <= hl) then
2499     begin
2500       // yay!
2501     end
2502     else
2503     begin
2504       hasPBarGfx := false;
2505     end;
2506   end;
2508   g_Frames_CreateWAD(nil, 'FRAMES_TELEPORT', GameWAD+':TEXTURES\TELEPORT', 64, 64, 10, False);
2509   g_Frames_CreateWAD(nil, 'FRAMES_PUNCH', GameWAD+':WEAPONS\PUNCH', 64, 64, 4, False);
2510   g_Frames_CreateWAD(nil, 'FRAMES_PUNCH_UP', GameWAD+':WEAPONS\PUNCH_UP', 64, 64, 4, False);
2511   g_Frames_CreateWAD(nil, 'FRAMES_PUNCH_DN', GameWAD+':WEAPONS\PUNCH_DN', 64, 64, 4, False);
2512   g_Frames_CreateWAD(nil, 'FRAMES_PUNCH_BERSERK', GameWAD+':WEAPONS\PUNCHB', 64, 64, 4, False);
2513   g_Frames_CreateWAD(nil, 'FRAMES_PUNCH_BERSERK_UP', GameWAD+':WEAPONS\PUNCHB_UP', 64, 64, 4, False);
2514   g_Frames_CreateWAD(nil, 'FRAMES_PUNCH_BERSERK_DN', GameWAD+':WEAPONS\PUNCHB_DN', 64, 64, 4, False);
2515   g_Sound_CreateWADEx('SOUND_GAME_TELEPORT', GameWAD+':SOUNDS\TELEPORT');
2516   g_Sound_CreateWADEx('SOUND_GAME_NOTELEPORT', GameWAD+':SOUNDS\NOTELEPORT');
2517   g_Sound_CreateWADEx('SOUND_GAME_SECRET', GameWAD+':SOUNDS\SECRET');
2518   g_Sound_CreateWADEx('SOUND_GAME_DOOROPEN', GameWAD+':SOUNDS\DOOROPEN');
2519   g_Sound_CreateWADEx('SOUND_GAME_DOORCLOSE', GameWAD+':SOUNDS\DOORCLOSE');
2520   g_Sound_CreateWADEx('SOUND_GAME_BULK1', GameWAD+':SOUNDS\BULK1');
2521   g_Sound_CreateWADEx('SOUND_GAME_BULK2', GameWAD+':SOUNDS\BULK2');
2522   g_Sound_CreateWADEx('SOUND_GAME_BUBBLE1', GameWAD+':SOUNDS\BUBBLE1');
2523   g_Sound_CreateWADEx('SOUND_GAME_BUBBLE2', GameWAD+':SOUNDS\BUBBLE2');
2524   g_Sound_CreateWADEx('SOUND_GAME_BURNING', GameWAD+':SOUNDS\BURNING');
2525   g_Sound_CreateWADEx('SOUND_GAME_SWITCH1', GameWAD+':SOUNDS\SWITCH1');
2526   g_Sound_CreateWADEx('SOUND_GAME_SWITCH0', GameWAD+':SOUNDS\SWITCH0');
2527   g_Sound_CreateWADEx('SOUND_GAME_RADIO', GameWAD+':SOUNDS\RADIO');
2528   g_Sound_CreateWADEx('SOUND_ANNOUNCER_GOOD1', GameWAD+':SOUNDS\GOOD1');
2529   g_Sound_CreateWADEx('SOUND_ANNOUNCER_GOOD2', GameWAD+':SOUNDS\GOOD2');
2530   g_Sound_CreateWADEx('SOUND_ANNOUNCER_GOOD3', GameWAD+':SOUNDS\GOOD3');
2531   g_Sound_CreateWADEx('SOUND_ANNOUNCER_GOOD4', GameWAD+':SOUNDS\GOOD4');
2532   g_Sound_CreateWADEx('SOUND_ANNOUNCER_KILL2X', GameWAD+':SOUNDS\KILL2X');
2533   g_Sound_CreateWADEx('SOUND_ANNOUNCER_KILL3X', GameWAD+':SOUNDS\KILL3X');
2534   g_Sound_CreateWADEx('SOUND_ANNOUNCER_KILL4X', GameWAD+':SOUNDS\KILL4X');
2535   g_Sound_CreateWADEx('SOUND_ANNOUNCER_KILLMX', GameWAD+':SOUNDS\KILLMX');
2536   g_Sound_CreateWADEx('SOUND_ANNOUNCER_MUHAHA1', GameWAD+':SOUNDS\MUHAHA1');
2537   g_Sound_CreateWADEx('SOUND_ANNOUNCER_MUHAHA2', GameWAD+':SOUNDS\MUHAHA2');
2538   g_Sound_CreateWADEx('SOUND_ANNOUNCER_MUHAHA3', GameWAD+':SOUNDS\MUHAHA3');
2539   g_Sound_CreateWADEx('SOUND_CTF_GET1', GameWAD+':SOUNDS\GETFLAG1');
2540   g_Sound_CreateWADEx('SOUND_CTF_GET2', GameWAD+':SOUNDS\GETFLAG2');
2541   g_Sound_CreateWADEx('SOUND_CTF_LOST1', GameWAD+':SOUNDS\LOSTFLG1');
2542   g_Sound_CreateWADEx('SOUND_CTF_LOST2', GameWAD+':SOUNDS\LOSTFLG2');
2543   g_Sound_CreateWADEx('SOUND_CTF_RETURN1', GameWAD+':SOUNDS\RETFLAG1');
2544   g_Sound_CreateWADEx('SOUND_CTF_RETURN2', GameWAD+':SOUNDS\RETFLAG2');
2545   g_Sound_CreateWADEx('SOUND_CTF_CAPTURE1', GameWAD+':SOUNDS\CAPFLAG1');
2546   g_Sound_CreateWADEx('SOUND_CTF_CAPTURE2', GameWAD+':SOUNDS\CAPFLAG2');
2548   goodsnd[0] := TPlayableSound.Create();
2549   goodsnd[1] := TPlayableSound.Create();
2550   goodsnd[2] := TPlayableSound.Create();
2551   goodsnd[3] := TPlayableSound.Create();
2553   goodsnd[0].SetByName('SOUND_ANNOUNCER_GOOD1');
2554   goodsnd[1].SetByName('SOUND_ANNOUNCER_GOOD2');
2555   goodsnd[2].SetByName('SOUND_ANNOUNCER_GOOD3');
2556   goodsnd[3].SetByName('SOUND_ANNOUNCER_GOOD4');
2558   killsnd[0] := TPlayableSound.Create();
2559   killsnd[1] := TPlayableSound.Create();
2560   killsnd[2] := TPlayableSound.Create();
2561   killsnd[3] := TPlayableSound.Create();
2563   killsnd[0].SetByName('SOUND_ANNOUNCER_KILL2X');
2564   killsnd[1].SetByName('SOUND_ANNOUNCER_KILL3X');
2565   killsnd[2].SetByName('SOUND_ANNOUNCER_KILL4X');
2566   killsnd[3].SetByName('SOUND_ANNOUNCER_KILLMX');
2568   hahasnd[0] := TPlayableSound.Create();
2569   hahasnd[1] := TPlayableSound.Create();
2570   hahasnd[2] := TPlayableSound.Create();
2572   hahasnd[0].SetByName('SOUND_ANNOUNCER_MUHAHA1');
2573   hahasnd[1].SetByName('SOUND_ANNOUNCER_MUHAHA2');
2574   hahasnd[2].SetByName('SOUND_ANNOUNCER_MUHAHA3');
2576   sound_get_flag[0] := TPlayableSound.Create();
2577   sound_get_flag[1] := TPlayableSound.Create();
2578   sound_lost_flag[0] := TPlayableSound.Create();
2579   sound_lost_flag[1] := TPlayableSound.Create();
2580   sound_ret_flag[0] := TPlayableSound.Create();
2581   sound_ret_flag[1] := TPlayableSound.Create();
2582   sound_cap_flag[0] := TPlayableSound.Create();
2583   sound_cap_flag[1] := TPlayableSound.Create();
2585   sound_get_flag[0].SetByName('SOUND_CTF_GET1');
2586   sound_get_flag[1].SetByName('SOUND_CTF_GET2');
2587   sound_lost_flag[0].SetByName('SOUND_CTF_LOST1');
2588   sound_lost_flag[1].SetByName('SOUND_CTF_LOST2');
2589   sound_ret_flag[0].SetByName('SOUND_CTF_RETURN1');
2590   sound_ret_flag[1].SetByName('SOUND_CTF_RETURN2');
2591   sound_cap_flag[0].SetByName('SOUND_CTF_CAPTURE1');
2592   sound_cap_flag[1].SetByName('SOUND_CTF_CAPTURE2');
2594   g_Game_LoadChatSounds(GameWAD+':CHATSND\SNDCFG');
2596   g_Game_SetLoadingText(_lc[I_LOAD_ITEMS_DATA], 0, False);
2597   g_Items_LoadData();
2599   g_Game_SetLoadingText(_lc[I_LOAD_WEAPONS_DATA], 0, False);
2600   g_Weapon_LoadData();
2602   g_Monsters_LoadData();
2604   DataLoaded := True;
2605 end;
2607 procedure g_Game_FreeData();
2608 begin
2609   if not DataLoaded then Exit;
2611   g_Items_FreeData();
2612   g_Weapon_FreeData();
2613   g_Monsters_FreeData();
2615   e_WriteLog('Releasing game data...', TMsgType.Notify);
2617   g_Texture_Delete('NOTEXTURE');
2618   g_Texture_Delete('TEXTURE_PLAYER_HUD');
2619   g_Texture_Delete('TEXTURE_PLAYER_HUDBG');
2620   g_Texture_Delete('TEXTURE_PLAYER_ARMORHUD');
2621   g_Texture_Delete('TEXTURE_PLAYER_REDFLAG');
2622   g_Texture_Delete('TEXTURE_PLAYER_REDFLAG_S');
2623   g_Texture_Delete('TEXTURE_PLAYER_REDFLAG_D');
2624   g_Texture_Delete('TEXTURE_PLAYER_BLUEFLAG');
2625   g_Texture_Delete('TEXTURE_PLAYER_BLUEFLAG_S');
2626   g_Texture_Delete('TEXTURE_PLAYER_BLUEFLAG_D');
2627   g_Texture_Delete('TEXTURE_PLAYER_TALKBUBBLE');
2628   g_Texture_Delete('TEXTURE_PLAYER_INVULPENTA');
2629   g_Frames_DeleteByName('FRAMES_TELEPORT');
2630   g_Frames_DeleteByName('FRAMES_PUNCH');
2631   g_Frames_DeleteByName('FRAMES_PUNCH_UP');
2632   g_Frames_DeleteByName('FRAMES_PUNCH_DN');
2633   g_Frames_DeleteByName('FRAMES_PUNCH_BERSERK');
2634   g_Frames_DeleteByName('FRAMES_PUNCH_BERSERK_UP');
2635   g_Frames_DeleteByName('FRAMES_PUNCH_BERSERK_DN');
2636   g_Sound_Delete('SOUND_GAME_TELEPORT');
2637   g_Sound_Delete('SOUND_GAME_NOTELEPORT');
2638   g_Sound_Delete('SOUND_GAME_SECRET');
2639   g_Sound_Delete('SOUND_GAME_DOOROPEN');
2640   g_Sound_Delete('SOUND_GAME_DOORCLOSE');
2641   g_Sound_Delete('SOUND_GAME_BULK1');
2642   g_Sound_Delete('SOUND_GAME_BULK2');
2643   g_Sound_Delete('SOUND_GAME_BUBBLE1');
2644   g_Sound_Delete('SOUND_GAME_BUBBLE2');
2645   g_Sound_Delete('SOUND_GAME_BURNING');
2646   g_Sound_Delete('SOUND_GAME_SWITCH1');
2647   g_Sound_Delete('SOUND_GAME_SWITCH0');
2649   goodsnd[0].Free();
2650   goodsnd[1].Free();
2651   goodsnd[2].Free();
2652   goodsnd[3].Free();
2654   g_Sound_Delete('SOUND_ANNOUNCER_GOOD1');
2655   g_Sound_Delete('SOUND_ANNOUNCER_GOOD2');
2656   g_Sound_Delete('SOUND_ANNOUNCER_GOOD3');
2657   g_Sound_Delete('SOUND_ANNOUNCER_GOOD4');
2659   killsnd[0].Free();
2660   killsnd[1].Free();
2661   killsnd[2].Free();
2662   killsnd[3].Free();
2664   g_Sound_Delete('SOUND_ANNOUNCER_KILL2X');
2665   g_Sound_Delete('SOUND_ANNOUNCER_KILL3X');
2666   g_Sound_Delete('SOUND_ANNOUNCER_KILL4X');
2667   g_Sound_Delete('SOUND_ANNOUNCER_KILLMX');
2669   hahasnd[0].Free();
2670   hahasnd[1].Free();
2671   hahasnd[2].Free();
2673   g_Sound_Delete('SOUND_ANNOUNCER_MUHAHA1');
2674   g_Sound_Delete('SOUND_ANNOUNCER_MUHAHA2');
2675   g_Sound_Delete('SOUND_ANNOUNCER_MUHAHA3');
2677   sound_get_flag[0].Free();
2678   sound_get_flag[1].Free();
2679   sound_lost_flag[0].Free();
2680   sound_lost_flag[1].Free();
2681   sound_ret_flag[0].Free();
2682   sound_ret_flag[1].Free();
2683   sound_cap_flag[0].Free();
2684   sound_cap_flag[1].Free();
2686   g_Sound_Delete('SOUND_CTF_GET1');
2687   g_Sound_Delete('SOUND_CTF_GET2');
2688   g_Sound_Delete('SOUND_CTF_LOST1');
2689   g_Sound_Delete('SOUND_CTF_LOST2');
2690   g_Sound_Delete('SOUND_CTF_RETURN1');
2691   g_Sound_Delete('SOUND_CTF_RETURN2');
2692   g_Sound_Delete('SOUND_CTF_CAPTURE1');
2693   g_Sound_Delete('SOUND_CTF_CAPTURE2');
2695   g_Game_FreeChatSounds();
2697   DataLoaded := False;
2698 end;
2700 procedure DrawCustomStat();
2702   pc, x, y, w, _y,
2703   w1, w2, w3,
2704   t, p, m: Integer;
2705   ww1, hh1: Word;
2706   ww2, hh2, r, g, b, rr, gg, bb: Byte;
2707   s1, s2, topstr: String;
2708 begin
2709   e_TextureFontGetSize(gStdFont, ww2, hh2);
2711   sys_HandleInput;
2713   if g_Console_Action(ACTION_SCORES) then
2714   begin
2715     if not gStatsPressed then
2716     begin
2717       gStatsOff := not gStatsOff;
2718       gStatsPressed := True;
2719     end;
2720   end
2721   else
2722     gStatsPressed := False;
2724   if gStatsOff then
2725   begin
2726     s1 := _lc[I_MENU_INTER_NOTICE_TAB];
2727     w := (Length(s1) * ww2) div 2;
2728     x := gScreenWidth div 2 - w;
2729     y := 8;
2730     e_TextureFontPrint(x, y, s1, gStdFont);
2731     Exit;
2732   end;
2734   if (gGameSettings.GameMode = GM_COOP) then
2735   begin
2736     if gMissionFailed then
2737       topstr := _lc[I_MENU_INTER_MISSION_FAIL]
2738     else
2739       topstr := _lc[I_MENU_INTER_LEVEL_COMPLETE];
2740   end
2741   else
2742     topstr := _lc[I_MENU_INTER_ROUND_OVER];
2744   e_CharFont_GetSize(gMenuFont, topstr, ww1, hh1);
2745   e_CharFont_Print(gMenuFont, (gScreenWidth div 2)-(ww1 div 2), 16, topstr);
2747   if g_Game_IsNet then
2748   begin
2749     topstr := Format(_lc[I_MENU_INTER_NOTICE_TIME], [gServInterTime]);
2750     if not gChatShow then
2751       e_TextureFontPrintEx((gScreenWidth div 2)-(Length(topstr)*ww2 div 2),
2752                            gScreenHeight-(hh2+4)*2, topstr, gStdFont, 255, 255, 255, 1);
2753   end;
2755   if g_Game_IsClient then
2756     topstr := _lc[I_MENU_INTER_NOTICE_MAP]
2757   else
2758     topstr := _lc[I_MENU_INTER_NOTICE_SPACE];
2759   if not gChatShow then
2760     e_TextureFontPrintEx((gScreenWidth div 2)-(Length(topstr)*ww2 div 2),
2761                          gScreenHeight-(hh2+4), topstr, gStdFont, 255, 255, 255, 1);
2763   x := 32;
2764   y := 16+hh1+16;
2766   w := gScreenWidth-x*2;
2768   w2 := (w-16) div 6;
2769   w3 := w2;
2770   w1 := w-16-w2-w3;
2772   e_DrawFillQuad(x, y, gScreenWidth-x-1, gScreenHeight-y-1, 64, 64, 64, 32);
2773   e_DrawQuad(x, y, gScreenWidth-x-1, gScreenHeight-y-1, 255, 127, 0);
2775   m := Max(Length(_lc[I_MENU_MAP])+1, Length(_lc[I_GAME_GAME_TIME])+1)*ww2;
2777   case CustomStat.GameMode of
2778     GM_DM:
2779     begin
2780       if gGameSettings.MaxLives = 0 then
2781         s1 := _lc[I_GAME_DM]
2782       else
2783         s1 := _lc[I_GAME_LMS];
2784     end;
2785     GM_TDM:
2786     begin
2787       if gGameSettings.MaxLives = 0 then
2788         s1 := _lc[I_GAME_TDM]
2789       else
2790         s1 := _lc[I_GAME_TLMS];
2791     end;
2792     GM_CTF: s1 := _lc[I_GAME_CTF];
2793     GM_COOP:
2794     begin
2795       if gGameSettings.MaxLives = 0 then
2796         s1 := _lc[I_GAME_COOP]
2797       else
2798         s1 := _lc[I_GAME_SURV];
2799     end;
2800     else s1 := '';
2801   end;
2803   _y := y+16;
2804   e_TextureFontPrintEx(x+(w div 2)-(Length(s1)*ww2 div 2), _y, s1, gStdFont, 255, 255, 255, 1);
2805   _y := _y+8;
2807   _y := _y+16;
2808   e_TextureFontPrintEx(x+8, _y, _lc[I_MENU_MAP], gStdFont, 255, 127, 0, 1);
2809   e_TextureFontPrint(x+8+m, _y, Format('%s - %s', [CustomStat.Map, CustomStat.MapName]), gStdFont);
2811   _y := _y+16;
2812   e_TextureFontPrintEx(x+8, _y, _lc[I_GAME_GAME_TIME], gStdFont, 255, 127, 0, 1);
2813   e_TextureFontPrint(x+8+m, _y, Format('%d:%.2d:%.2d', [CustomStat.GameTime div 1000 div 3600,
2814                                                        (CustomStat.GameTime div 1000 div 60) mod 60,
2815                                                         CustomStat.GameTime div 1000 mod 60]), gStdFont);
2817   pc := Length(CustomStat.PlayerStat);
2818   if pc = 0 then Exit;
2820   if CustomStat.GameMode = GM_COOP then
2821   begin
2822     m := Max(Length(_lc[I_GAME_MONSTERS])+1, Length(_lc[I_GAME_SECRETS])+1)*ww2;
2823     _y := _y+32;
2824     s2 := _lc[I_GAME_MONSTERS];
2825     e_TextureFontPrintEx(x+8, _y, s2, gStdFont, 255, 127, 0, 1);
2826     e_TextureFontPrintEx(x+8+m, _y, IntToStr(gCoopMonstersKilled) + '/' + IntToStr(gTotalMonsters), gStdFont, 255, 255, 255, 1);
2827     _y := _y+16;
2828     s2 := _lc[I_GAME_SECRETS];
2829     e_TextureFontPrintEx(x+8, _y, s2, gStdFont, 255, 127, 0, 1);
2830     e_TextureFontPrintEx(x+8+m, _y, IntToStr(gCoopSecretsFound) + '/' + IntToStr(gSecretsCount), gStdFont, 255, 255, 255, 1);
2831     if gLastMap then
2832     begin
2833       m := Max(Length(_lc[I_GAME_MONSTERS_TOTAL])+1, Length(_lc[I_GAME_SECRETS_TOTAL])+1)*ww2;
2834       _y := _y-16;
2835       s2 := _lc[I_GAME_MONSTERS_TOTAL];
2836       e_TextureFontPrintEx(x+250, _y, s2, gStdFont, 255, 127, 0, 1);
2837       e_TextureFontPrintEx(x+250+m, _y, IntToStr(gCoopTotalMonstersKilled) + '/' + IntToStr(gCoopTotalMonsters), gStdFont, 255, 255, 255, 1);
2838       _y := _y+16;
2839       s2 := _lc[I_GAME_SECRETS_TOTAL];
2840       e_TextureFontPrintEx(x+250, _y, s2, gStdFont, 255, 127, 0, 1);
2841       e_TextureFontPrintEx(x+250+m, _y, IntToStr(gCoopTotalSecretsFound) + '/' + IntToStr(gCoopTotalSecrets), gStdFont, 255, 255,  255, 1);
2842     end;
2843   end;
2845   if CustomStat.GameMode in [GM_TDM, GM_CTF] then
2846   begin
2847     _y := _y+16+16;
2849     with CustomStat do
2850       if TeamStat[TEAM_RED].Goals > TeamStat[TEAM_BLUE].Goals then s1 := _lc[I_GAME_WIN_RED]
2851         else if TeamStat[TEAM_BLUE].Goals > TeamStat[TEAM_RED].Goals then s1 := _lc[I_GAME_WIN_BLUE]
2852           else s1 := _lc[I_GAME_WIN_DRAW];
2854     e_TextureFontPrintEx(x+8+(w div 2)-(Length(s1)*ww2 div 2), _y, s1, gStdFont, 255, 255, 255, 1);
2855     _y := _y+40;
2857     for t := TEAM_RED to TEAM_BLUE do
2858     begin
2859       if t = TEAM_RED then
2860       begin
2861         e_TextureFontPrintEx(x+8, _y, _lc[I_GAME_TEAM_RED],
2862                              gStdFont, 255, 0, 0, 1);
2863         e_TextureFontPrintEx(x+w1+8, _y, IntToStr(CustomStat.TeamStat[TEAM_RED].Goals),
2864                              gStdFont, 255, 0, 0, 1);
2865         r := 255;
2866         g := 0;
2867         b := 0;
2868       end
2869       else
2870       begin
2871         e_TextureFontPrintEx(x+8, _y, _lc[I_GAME_TEAM_BLUE],
2872                              gStdFont, 0, 0, 255, 1);
2873         e_TextureFontPrintEx(x+w1+8, _y, IntToStr(CustomStat.TeamStat[TEAM_BLUE].Goals),
2874                              gStdFont, 0, 0, 255, 1);
2875         r := 0;
2876         g := 0;
2877         b := 255;
2878       end;
2880       e_DrawLine(1, x+8, _y+20, x-8+w, _y+20, r, g, b);
2881       _y := _y+24;
2883       for p := 0 to High(CustomStat.PlayerStat) do
2884         if CustomStat.PlayerStat[p].Team = t then
2885           with CustomStat.PlayerStat[p] do
2886           begin
2887             if Spectator then
2888             begin
2889               rr := r div 2;
2890               gg := g div 2;
2891               bb := b div 2;
2892             end
2893             else
2894             begin
2895               rr := r;
2896               gg := g;
2897               bb := b;
2898             end;
2899             if (gPlayers[Num] <> nil) and (gPlayers[Num].FReady) then
2900               e_TextureFontPrintEx(x+16, _y, Name + ' *', gStdFont, rr, gg, bb, 1)
2901             else
2902               e_TextureFontPrintEx(x+16, _y, Name, gStdFont, rr, gg, bb, 1);
2903             e_TextureFontPrintEx(x+w1+16, _y, IntToStr(Frags), gStdFont, rr, gg, bb, 1);
2904             e_TextureFontPrintEx(x+w1+w2+16, _y, IntToStr(Deaths), gStdFont, rr, gg, bb, 1);
2905             _y := _y+24;
2906           end;
2908       _y := _y+16+16;
2909     end;
2910   end
2911   else if CustomStat.GameMode in [GM_DM, GM_COOP] then
2912   begin
2913     _y := _y+40;
2914     e_TextureFontPrintEx(x+8, _y, _lc[I_GAME_PLAYER_NAME], gStdFont, 255, 127, 0, 1);
2915     e_TextureFontPrintEx(x+8+w1, _y, _lc[I_GAME_FRAGS], gStdFont, 255, 127, 0, 1);
2916     e_TextureFontPrintEx(x+8+w1+w2, _y, _lc[I_GAME_DEATHS], gStdFont, 255, 127, 0, 1);
2918     _y := _y+24;
2919     for p := 0 to High(CustomStat.PlayerStat) do
2920       with CustomStat.PlayerStat[p] do
2921       begin
2922         e_DrawFillQuad(x+8, _y+4, x+24-1, _y+16+4-1, Color.R, Color.G, Color.B, 0);
2924         if Spectator then
2925           r := 127
2926         else
2927           r := 255;
2929         if (gPlayers[Num] <> nil) and (gPlayers[Num].FReady) then
2930           e_TextureFontPrintEx(x+8+16+8, _y+4, Name + ' *', gStdFont, r, r, r, 1, True)
2931         else
2932           e_TextureFontPrintEx(x+8+16+8, _y+4, Name, gStdFont, r, r, r, 1, True);
2933         e_TextureFontPrintEx(x+w1+8+16+8, _y+4, IntToStr(Frags), gStdFont, r, r, r, 1, True);
2934         e_TextureFontPrintEx(x+w1+w2+8+16+8, _y+4, IntToStr(Deaths), gStdFont, r, r, r, 1, True);
2935         _y := _y+24;
2936       end;
2937   end;
2939   // HACK: take stats screenshot immediately after the first frame of the stats showing
2940   if gScreenshotStats and (not StatShotDone) and (Length(CustomStat.PlayerStat) > 1) then
2941   begin
2942     g_TakeScreenShot('stats/' + StatFilename);
2943     StatShotDone := True;
2944   end;
2945 end;
2947 procedure DrawSingleStat();
2949   tm, key_x, val_x, y: Integer;
2950   w1, w2, h: Word;
2951   s1, s2: String;
2953   procedure player_stat(n: Integer);
2954   var
2955     kpm: Real;
2957   begin
2958   // "Kills: # / #":
2959     s1 := Format(' %d ', [SingleStat.PlayerStat[n].Kills]);
2960     s2 := Format(' %d', [gTotalMonsters]);
2962     e_CharFont_Print(gMenuFont, key_x, y, _lc[I_MENU_INTER_KILLS]);
2963     e_CharFont_PrintEx(gMenuFont, val_x, y, s1, _RGB(255, 0, 0));
2964     e_CharFont_GetSize(gMenuFont, s1, w1, h);
2965     e_CharFont_Print(gMenuFont, val_x+w1, y, '/');
2966     s1 := s1 + '/';
2967     e_CharFont_GetSize(gMenuFont, s1, w1, h);
2968     e_CharFont_PrintEx(gMenuFont, val_x+w1, y, s2, _RGB(255, 0, 0));
2970   // "Kills-per-minute: ##.#":
2971     s1 := _lc[I_MENU_INTER_KPM];
2972     if tm > 0 then
2973       kpm := (SingleStat.PlayerStat[n].Kills / tm) * 60
2974     else
2975       kpm := SingleStat.PlayerStat[n].Kills;
2976     s2 := Format(' %.1f', [kpm]);
2978     e_CharFont_Print(gMenuFont, key_x, y+32, s1);
2979     e_CharFont_PrintEx(gMenuFont, val_x, y+32, s2, _RGB(255, 0, 0));
2981   // "Secrets found: # / #":
2982     s1 := Format(' %d ', [SingleStat.PlayerStat[n].Secrets]);
2983     s2 := Format(' %d', [SingleStat.TotalSecrets]);
2985     e_CharFont_Print(gMenuFont, key_x, y+64, _lc[I_MENU_INTER_SECRETS]);
2986     e_CharFont_PrintEx(gMenuFont, val_x, y+64, s1, _RGB(255, 0, 0));
2987     e_CharFont_GetSize(gMenuFont, s1, w1, h);
2988     e_CharFont_Print(gMenuFont, val_x+w1, y+64, '/');
2989     s1 := s1 + '/';
2990     e_CharFont_GetSize(gMenuFont, s1, w1, h);
2991     e_CharFont_PrintEx(gMenuFont, val_x+w1, y+64, s2, _RGB(255, 0, 0));
2992   end;
2994 begin
2995 // "Level Complete":
2996   e_CharFont_GetSize(gMenuFont, _lc[I_MENU_INTER_LEVEL_COMPLETE], w1, h);
2997   e_CharFont_Print(gMenuFont, (gScreenWidth-w1) div 2, 32, _lc[I_MENU_INTER_LEVEL_COMPLETE]);
2999 // Îïðåäåëÿåì êîîðäèíàòû âûðàâíèâàíèÿ ïî ñàìîé äëèííîé ñòðîêå:
3000   s1 := _lc[I_MENU_INTER_KPM];
3001   e_CharFont_GetSize(gMenuFont, s1, w1, h);
3002   Inc(w1, 16);
3003   s1 := ' 9999.9';
3004   e_CharFont_GetSize(gMenuFont, s1, w2, h);
3006   key_x := (gScreenWidth-w1-w2) div 2;
3007   val_x := key_x + w1;
3009 // "Time: #:##:##":
3010   tm := SingleStat.GameTime div 1000;
3011   s1 := _lc[I_MENU_INTER_TIME];
3012   s2 := Format(' %d:%.2d:%.2d', [tm div (60*60), (tm mod (60*60)) div 60, tm mod 60]);
3014   e_CharFont_Print(gMenuFont, key_x, 80, s1);
3015   e_CharFont_PrintEx(gMenuFont, val_x, 80, s2, _RGB(255, 0, 0));
3017   if SingleStat.TwoPlayers then
3018     begin
3019     // "Player 1":
3020       s1 := _lc[I_MENU_PLAYER_1];
3021       e_CharFont_GetSize(gMenuFont, s1, w1, h);
3022       e_CharFont_Print(gMenuFont, (gScreenWidth-w1) div 2, 128, s1);
3024     // Ñòàòèñòèêà ïåðâîãî èãðîêà:
3025       y := 176;
3026       player_stat(0);
3028     // "Player 2":
3029       s1 := _lc[I_MENU_PLAYER_2];
3030       e_CharFont_GetSize(gMenuFont, s1, w1, h);
3031       e_CharFont_Print(gMenuFont, (gScreenWidth-w1) div 2, 288, s1);
3033     // Ñòàòèñòèêà âòîðîãî èãðîêà:
3034       y := 336;
3035       player_stat(1);
3036     end
3037   else
3038     begin
3039     // Ñòàòèñòèêà ïåðâîãî èãðîêà:
3040       y := 128;
3041       player_stat(0);
3042     end;
3043 end;
3045 procedure DrawLoadingStat();
3046   procedure drawRect (x, y, w, h: Integer);
3047   begin
3048     if (w < 1) or (h < 1) then exit;
3049     glBegin(GL_QUADS);
3050       glVertex2f(x+0.375, y+0.375);
3051       glVertex2f(x+w+0.375, y+0.375);
3052       glVertex2f(x+w+0.375, y+h+0.375);
3053       glVertex2f(x+0.375, y+h+0.375);
3054     glEnd();
3055   end;
3057   function drawPBar (cur, total: Integer; washere: Boolean): Boolean;
3058   var
3059     rectW, rectH: Integer;
3060     x0, y0: Integer;
3061     wdt: Integer;
3062     wl, hl: Integer;
3063     wr, hr: Integer;
3064     wb, hb: Integer;
3065     wm, hm: Integer;
3066     idl, idr, idb, idm: LongWord;
3067     f, my: Integer;
3068   begin
3069     result := false;
3070     if (total < 1) then exit;
3071     if (cur < 1) then exit; // don't blink
3072     if (not washere) and (cur >= total) then exit; // don't blink
3073     //if (cur < 0) then cur := 0;
3074     //if (cur > total) then cur := total;
3075     result := true;
3077     if (hasPBarGfx) then
3078     begin
3079       g_Texture_Get('UI_GFX_PBAR_LEFT', idl);
3080       g_Texture_GetSize('UI_GFX_PBAR_LEFT', wl, hl);
3081       g_Texture_Get('UI_GFX_PBAR_RIGHT', idr);
3082       g_Texture_GetSize('UI_GFX_PBAR_RIGHT', wr, hr);
3083       g_Texture_Get('UI_GFX_PBAR_MIDDLE', idb);
3084       g_Texture_GetSize('UI_GFX_PBAR_MIDDLE', wb, hb);
3085       g_Texture_Get('UI_GFX_PBAR_MARKER', idm);
3086       g_Texture_GetSize('UI_GFX_PBAR_MARKER', wm, hm);
3088       //rectW := gScreenWidth-360;
3089       rectW := trunc(624.0*gScreenWidth/1024.0);
3090       rectH := hl;
3092       x0 := (gScreenWidth-rectW) div 2;
3093       y0 := gScreenHeight-rectH-64;
3094       if (y0 < 2) then y0 := 2;
3096       glEnable(GL_SCISSOR_TEST);
3098       // left and right
3099       glScissor(x0, gScreenHeight-y0-rectH, rectW, rectH);
3100       e_DrawSize(idl, x0, y0, 0, true, false, wl, hl);
3101       e_DrawSize(idr, x0+rectW-wr, y0, 0, true, false, wr, hr);
3103       // body
3104       glScissor(x0+wl, gScreenHeight-y0-rectH, rectW-wl-wr, rectH);
3105       f := x0+wl;
3106       while (f < x0+rectW) do
3107       begin
3108         e_DrawSize(idb, f, y0, 0, true, false, wb, hb);
3109         f += wb;
3110       end;
3112       // filled part
3113       wdt := (rectW-wl-wr)*cur div total;
3114       if (wdt > rectW-wl-wr) then wdt := rectW-wr-wr;
3115       if (wdt > 0) then
3116       begin
3117         my := y0; // don't be so smart, ketmar: +(rectH-wm) div 2;
3118         glScissor(x0+wl, gScreenHeight-my-rectH, wdt, hm);
3119         f := x0+wl;
3120         while (wdt > 0) do
3121         begin
3122           e_DrawSize(idm, f, y0, 0, true, false, wm, hm);
3123           f += wm;
3124           wdt -= wm;
3125         end;
3126       end;
3128       glScissor(0, 0, gScreenWidth, gScreenHeight);
3129     end
3130     else
3131     begin
3132       rectW := gScreenWidth-64;
3133       rectH := 16;
3135       x0 := (gScreenWidth-rectW) div 2;
3136       y0 := gScreenHeight-rectH-64;
3137       if (y0 < 2) then y0 := 2;
3139       glDisable(GL_BLEND);
3140       glDisable(GL_TEXTURE_2D);
3142       //glClearColor(0, 0, 0, 0);
3143       //glClear(GL_COLOR_BUFFER_BIT);
3145       glColor4ub(127, 127, 127, 255);
3146       drawRect(x0-2, y0-2, rectW+4, rectH+4);
3148       glColor4ub(0, 0, 0, 255);
3149       drawRect(x0-1, y0-1, rectW+2, rectH+2);
3151       glColor4ub(127, 127, 127, 255);
3152       wdt := rectW*cur div total;
3153       if (wdt > rectW) then wdt := rectW;
3154       drawRect(x0, y0, wdt, rectH);
3155     end;
3156   end;
3159   ww, hh: Word;
3160   xx, yy, i: Integer;
3161   s: String;
3162 begin
3163   if (Length(LoadingStat.Msgs) = 0) then exit;
3165   e_CharFont_GetSize(gMenuFont, _lc[I_MENU_LOADING], ww, hh);
3166   yy := (gScreenHeight div 3);
3167   e_CharFont_Print(gMenuFont, (gScreenWidth div 2)-(ww div 2), yy-2*hh, _lc[I_MENU_LOADING]);
3168   xx := (gScreenWidth div 3);
3170   with LoadingStat do
3171   begin
3172     for i := 0 to NextMsg-1 do
3173     begin
3174       if (i = (NextMsg-1)) and (MaxValue > 0) then
3175         s := Format('%s:  %d/%d', [Msgs[i], CurValue, MaxValue])
3176       else
3177         s := Msgs[i];
3179       e_CharFont_PrintEx(gMenuSmallFont, xx, yy, s, _RGB(255, 0, 0));
3180       yy := yy + LOADING_INTERLINE;
3181       PBarWasHere := drawPBar(CurValue, MaxValue, PBarWasHere);
3182     end;
3183   end;
3184 end;
3186 procedure DrawMenuBackground(tex: AnsiString);
3188   w, h: Word;
3189   ID: DWord;
3191 begin
3192   if g_Texture_Get(tex, ID) then
3193   begin
3194     e_Clear(GL_COLOR_BUFFER_BIT, 0, 0, 0);
3195     e_GetTextureSize(ID, @w, @h);
3196     if w = h then
3197       w := round(w * 1.333 * (gScreenHeight / h))
3198     else
3199       w := trunc(w * (gScreenHeight / h));
3200     e_DrawSize(ID, (gScreenWidth - w) div 2, 0, 0, False, False, w, gScreenHeight);
3201   end
3202   else e_Clear(GL_COLOR_BUFFER_BIT, 0, 0, 0);
3203 end;
3205 procedure DrawMinimap(p: TPlayer; RenderRect: e_graphics.TRect);
3207   a, aX, aY, aX2, aY2, Scale, ScaleSz: Integer;
3209   function monDraw (mon: TMonster): Boolean;
3210   begin
3211     result := false; // don't stop
3212     with mon do
3213     begin
3214       if alive then
3215       begin
3216         // Ëåâûé âåðõíèé óãîë
3217         aX := Obj.X div ScaleSz + 1;
3218         aY := Obj.Y div ScaleSz + 1;
3219         // Ðàçìåðû
3220         aX2 := max(Obj.Rect.Width div ScaleSz, 1);
3221         aY2 := max(Obj.Rect.Height div ScaleSz, 1);
3222         // Ïðàâûé íèæíèé óãîë
3223         aX2 := aX + aX2 - 1;
3224         aY2 := aY + aY2 - 1;
3225         e_DrawFillQuad(aX, aY, aX2, aY2, 255, 255, 0, 0);
3226       end;
3227     end;
3228   end;
3230 begin
3231   if (gMapInfo.Width > RenderRect.Right - RenderRect.Left) or
3232      (gMapInfo.Height > RenderRect.Bottom - RenderRect.Top) then
3233   begin
3234     Scale := 1;
3235   // Ñêîëüêî ïèêñåëîâ êàðòû â 1 ïèêñåëå ìèíè-êàðòû:
3236     ScaleSz := 16 div Scale;
3237   // Ðàçìåðû ìèíè-êàðòû:
3238     aX := max(gMapInfo.Width div ScaleSz, 1);
3239     aY := max(gMapInfo.Height div ScaleSz, 1);
3240   // Ðàìêà êàðòû:
3241     e_DrawFillQuad(0, 0, aX-1, aY-1, 0, 0, 0, 0);
3243     if gWalls <> nil then
3244     begin
3245     // Ðèñóåì ñòåíû:
3246       for a := 0 to High(gWalls) do
3247         with gWalls[a] do
3248           if PanelType <> 0 then
3249           begin
3250           // Ëåâûé âåðõíèé óãîë:
3251             aX := X div ScaleSz;
3252             aY := Y div ScaleSz;
3253           // Ðàçìåðû:
3254             aX2 := max(Width div ScaleSz, 1);
3255             aY2 := max(Height div ScaleSz, 1);
3256           // Ïðàâûé íèæíèé óãîë:
3257             aX2 := aX + aX2 - 1;
3258             aY2 := aY + aY2 - 1;
3260             case PanelType of
3261               PANEL_WALL:      e_DrawFillQuad(aX, aY, aX2, aY2, 208, 208, 208, 0);
3262               PANEL_OPENDOOR, PANEL_CLOSEDOOR:
3263                 if Enabled then e_DrawFillQuad(aX, aY, aX2, aY2, 160, 160, 160, 0);
3264             end;
3265           end;
3266     end;
3267     if gSteps <> nil then
3268     begin
3269     // Ðèñóåì ñòóïåíè:
3270       for a := 0 to High(gSteps) do
3271         with gSteps[a] do
3272           if PanelType <> 0 then
3273           begin
3274           // Ëåâûé âåðõíèé óãîë:
3275             aX := X div ScaleSz;
3276             aY := Y div ScaleSz;
3277           // Ðàçìåðû:
3278             aX2 := max(Width div ScaleSz, 1);
3279             aY2 := max(Height div ScaleSz, 1);
3280           // Ïðàâûé íèæíèé óãîë:
3281             aX2 := aX + aX2 - 1;
3282             aY2 := aY + aY2 - 1;
3284             e_DrawFillQuad(aX, aY, aX2, aY2, 128, 128, 128, 0);
3285           end;
3286     end;
3287     if gLifts <> nil then
3288     begin
3289     // Ðèñóåì ëèôòû:
3290       for a := 0 to High(gLifts) do
3291         with gLifts[a] do
3292           if PanelType <> 0 then
3293           begin
3294           // Ëåâûé âåðõíèé óãîë:
3295             aX := X div ScaleSz;
3296             aY := Y div ScaleSz;
3297           // Ðàçìåðû:
3298             aX2 := max(Width div ScaleSz, 1);
3299             aY2 := max(Height div ScaleSz, 1);
3300           // Ïðàâûé íèæíèé óãîë:
3301             aX2 := aX + aX2 - 1;
3302             aY2 := aY + aY2 - 1;
3304             case LiftType of
3305               LIFTTYPE_UP:    e_DrawFillQuad(aX, aY, aX2, aY2, 116,  72,  36, 0);
3306               LIFTTYPE_DOWN:  e_DrawFillQuad(aX, aY, aX2, aY2, 116, 124,  96, 0);
3307               LIFTTYPE_LEFT:  e_DrawFillQuad(aX, aY, aX2, aY2, 200,  80,   4, 0);
3308               LIFTTYPE_RIGHT: e_DrawFillQuad(aX, aY, aX2, aY2, 252, 140,  56, 0);
3309             end;
3310           end;
3311     end;
3312     if gWater <> nil then
3313     begin
3314     // Ðèñóåì âîäó:
3315       for a := 0 to High(gWater) do
3316         with gWater[a] do
3317           if PanelType <> 0 then
3318           begin
3319           // Ëåâûé âåðõíèé óãîë:
3320             aX := X div ScaleSz;
3321             aY := Y div ScaleSz;
3322           // Ðàçìåðû:
3323             aX2 := max(Width div ScaleSz, 1);
3324             aY2 := max(Height div ScaleSz, 1);
3325           // Ïðàâûé íèæíèé óãîë:
3326             aX2 := aX + aX2 - 1;
3327             aY2 := aY + aY2 - 1;
3329             e_DrawFillQuad(aX, aY, aX2, aY2, 0, 0, 192, 0);
3330           end;
3331     end;
3332     if gAcid1 <> nil then
3333     begin
3334     // Ðèñóåì êèñëîòó 1:
3335       for a := 0 to High(gAcid1) do
3336         with gAcid1[a] do
3337           if PanelType <> 0 then
3338           begin
3339           // Ëåâûé âåðõíèé óãîë:
3340             aX := X div ScaleSz;
3341             aY := Y div ScaleSz;
3342           // Ðàçìåðû:
3343             aX2 := max(Width div ScaleSz, 1);
3344             aY2 := max(Height div ScaleSz, 1);
3345           // Ïðàâûé íèæíèé óãîë:
3346             aX2 := aX + aX2 - 1;
3347             aY2 := aY + aY2 - 1;
3349             e_DrawFillQuad(aX, aY, aX2, aY2, 0, 176, 0, 0);
3350           end;
3351     end;
3352     if gAcid2 <> nil then
3353     begin
3354     // Ðèñóåì êèñëîòó 2:
3355       for a := 0 to High(gAcid2) do
3356         with gAcid2[a] do
3357           if PanelType <> 0 then
3358           begin
3359           // Ëåâûé âåðõíèé óãîë:
3360             aX := X div ScaleSz;
3361             aY := Y div ScaleSz;
3362           // Ðàçìåðû:
3363             aX2 := max(Width div ScaleSz, 1);
3364             aY2 := max(Height div ScaleSz, 1);
3365           // Ïðàâûé íèæíèé óãîë:
3366             aX2 := aX + aX2 - 1;
3367             aY2 := aY + aY2 - 1;
3369             e_DrawFillQuad(aX, aY, aX2, aY2, 176, 0, 0, 0);
3370           end;
3371     end;
3372     if gPlayers <> nil then
3373     begin
3374     // Ðèñóåì èãðîêîâ:
3375       for a := 0 to High(gPlayers) do
3376         if gPlayers[a] <> nil then with gPlayers[a] do
3377           if alive then begin
3378           // Ëåâûé âåðõíèé óãîë:
3379             aX := Obj.X div ScaleSz + 1;
3380             aY := Obj.Y div ScaleSz + 1;
3381           // Ðàçìåðû:
3382             aX2 := max(Obj.Rect.Width div ScaleSz, 1);
3383             aY2 := max(Obj.Rect.Height div ScaleSz, 1);
3384           // Ïðàâûé íèæíèé óãîë:
3385             aX2 := aX + aX2 - 1;
3386             aY2 := aY + aY2 - 1;
3388             if gPlayers[a] = p then
3389               e_DrawFillQuad(aX, aY, aX2, aY2, 0, 255, 0, 0)
3390             else
3391               case Team of
3392                 TEAM_RED:  e_DrawFillQuad(aX, aY, aX2, aY2, 255,   0,   0, 0);
3393                 TEAM_BLUE: e_DrawFillQuad(aX, aY, aX2, aY2, 0,     0, 255, 0);
3394                 else       e_DrawFillQuad(aX, aY, aX2, aY2, 255, 128,   0, 0);
3395               end;
3396           end;
3397     end;
3398     // Ðèñóåì ìîíñòðîâ
3399     g_Mons_ForEach(monDraw);
3400   end;
3401 end;
3404 procedure renderAmbientQuad (hasAmbient: Boolean; constref ambColor: TDFColor);
3405 begin
3406   if not hasAmbient then exit;
3407   e_AmbientQuad(sX, sY, sWidth, sHeight, ambColor.r, ambColor.g, ambColor.b, ambColor.a);
3408 end;
3411 // setup sX, sY, sWidth, sHeight, and transformation matrix before calling this!
3412 //FIXME: broken for splitscreen mode
3413 procedure renderDynLightsInternal ();
3415   //hasAmbient: Boolean;
3416   //ambColor: TDFColor;
3417   lln: Integer;
3418   lx, ly, lrad: Integer;
3419   scxywh: array[0..3] of GLint;
3420   wassc: Boolean;
3421 begin
3422   if e_NoGraphics then exit;
3424   //TODO: lights should be in separate grid, i think
3425   //      but on the other side: grid may be slower for dynlights, as their lifetime is short
3426   if (not gwin_k8_enable_light_experiments) or (not gwin_has_stencil) or (g_dynLightCount < 1) then exit;
3428   // rendering mode
3429   //ambColor := gCurrentMap['light_ambient'].rgba;
3430   //hasAmbient := (not ambColor.isOpaque) or (not ambColor.isBlack);
3432   { // this will multiply incoming color to alpha from framebuffer
3433     glEnable(GL_BLEND);
3434     glBlendFunc(GL_DST_ALPHA, GL_ONE);
3435   }
3437   (*
3438    * light rendering: (INVALID!)
3439    *   glStencilFunc(GL_EQUAL, 0, $ff);
3440    *   for each light:
3441    *     glClear(GL_STENCIL_BUFFER_BIT);
3442    *     glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
3443    *     draw shadow volume into stencil buffer
3444    *     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // modify color buffer
3445    *     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // don't modify stencil buffer
3446    *     turn off blending
3447    *     draw color-less quad with light alpha (WARNING! don't touch color!)
3448    *     glEnable(GL_BLEND);
3449    *     glBlendFunc(GL_DST_ALPHA, GL_ONE);
3450    *     draw all geometry up to and including walls (with alpha-testing, probably) -- this does lighting
3451    *)
3452   wassc := (glIsEnabled(GL_SCISSOR_TEST) <> 0);
3453   if wassc then glGetIntegerv(GL_SCISSOR_BOX, @scxywh[0]) else glGetIntegerv(GL_VIEWPORT, @scxywh[0]);
3455   // setup OpenGL parameters
3456   glStencilMask($FFFFFFFF);
3457   glStencilFunc(GL_ALWAYS, 0, $FFFFFFFF);
3458   glEnable(GL_STENCIL_TEST);
3459   glEnable(GL_SCISSOR_TEST);
3460   glClear(GL_STENCIL_BUFFER_BIT);
3461   glStencilFunc(GL_EQUAL, 0, $ff);
3463   for lln := 0 to g_dynLightCount-1 do
3464   begin
3465     lx := g_dynLights[lln].x;
3466     ly := g_dynLights[lln].y;
3467     lrad := g_dynLights[lln].radius;
3468     if (lrad < 3) then continue;
3470     if (lx-sX+lrad < 0) then continue;
3471     if (ly-sY+lrad < 0) then continue;
3472     if (lx-sX-lrad >= gPlayerScreenSize.X) then continue;
3473     if (ly-sY-lrad >= gPlayerScreenSize.Y) then continue;
3475     // set scissor to optimize drawing
3476     if (g_dbg_scale = 1.0) then
3477     begin
3478       glScissor((lx-sX)-lrad+2, gPlayerScreenSize.Y-(ly-sY)-lrad-1+2, lrad*2-4, lrad*2-4);
3479     end
3480     else
3481     begin
3482       glScissor(0, 0, gScreenWidth, gScreenHeight);
3483     end;
3484     // no need to clear stencil buffer, light blitting will do it for us... but only for normal scale
3485     if (g_dbg_scale <> 1.0) then glClear(GL_STENCIL_BUFFER_BIT);
3486     glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
3487     // draw extruded panels
3488     glDisable(GL_TEXTURE_2D);
3489     glDisable(GL_BLEND);
3490     glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); // no need to modify color buffer
3491     if (lrad > 4) then g_Map_DrawPanelShadowVolumes(lx, ly, lrad);
3492     // render light texture
3493     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // modify color buffer
3494     glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); // draw light, and clear stencil buffer
3495     // blend it
3496     glEnable(GL_BLEND);
3497     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
3498     glEnable(GL_TEXTURE_2D);
3499     // color and opacity
3500     glColor4f(g_dynLights[lln].r, g_dynLights[lln].g, g_dynLights[lln].b, g_dynLights[lln].a);
3501     glBindTexture(GL_TEXTURE_2D, g_Texture_Light());
3502     glBegin(GL_QUADS);
3503       glTexCoord2f(0.0, 0.0); glVertex2i(lx-lrad, ly-lrad); // top-left
3504       glTexCoord2f(1.0, 0.0); glVertex2i(lx+lrad, ly-lrad); // top-right
3505       glTexCoord2f(1.0, 1.0); glVertex2i(lx+lrad, ly+lrad); // bottom-right
3506       glTexCoord2f(0.0, 1.0); glVertex2i(lx-lrad, ly+lrad); // bottom-left
3507     glEnd();
3508   end;
3510   // done
3511   glDisable(GL_STENCIL_TEST);
3512   glDisable(GL_BLEND);
3513   glDisable(GL_SCISSOR_TEST);
3514   //glScissor(0, 0, sWidth, sHeight);
3516   glScissor(scxywh[0], scxywh[1], scxywh[2], scxywh[3]);
3517   if wassc then glEnable(GL_SCISSOR_TEST) else glDisable(GL_SCISSOR_TEST);
3518 end;
3521 function fixViewportForScale (): Boolean;
3523   nx0, ny0, nw, nh: Integer;
3524 begin
3525   result := false;
3526   if (g_dbg_scale <> 1.0) then
3527   begin
3528     result := true;
3529     nx0 := round(sX-(gPlayerScreenSize.X-(sWidth*g_dbg_scale))/2/g_dbg_scale);
3530     ny0 := round(sY-(gPlayerScreenSize.Y-(sHeight*g_dbg_scale))/2/g_dbg_scale);
3531     nw := round(sWidth/g_dbg_scale);
3532     nh := round(sHeight/g_dbg_scale);
3533     sX := nx0;
3534     sY := ny0;
3535     sWidth := nw;
3536     sHeight := nh;
3537   end;
3538 end;
3541 // setup sX, sY, sWidth, sHeight, and transformation matrix before calling this!
3542 // WARNING! this WILL CALL `glTranslatef()`, but won't restore matrices!
3543 procedure renderMapInternal (backXOfs, backYOfs: Integer; setTransMatrix: Boolean);
3544 type
3545   TDrawCB = procedure ();
3548   hasAmbient: Boolean;
3549   ambColor: TDFColor;
3550   doAmbient: Boolean = false;
3552   procedure drawPanelType (profname: AnsiString; panType: DWord; doDraw: Boolean);
3553   var
3554     tagmask: Integer;
3555     pan: TPanel;
3556   begin
3557     if (profileFrameDraw <> nil) then profileFrameDraw.sectionBegin(profname);
3558     if gdbg_map_use_accel_render then
3559     begin
3560       tagmask := panelTypeToTag(panType);
3561       while (gDrawPanelList.count > 0) do
3562       begin
3563         pan := TPanel(gDrawPanelList.front());
3564         if ((pan.tag and tagmask) = 0) then break;
3565         if doDraw then pan.Draw(doAmbient, ambColor);
3566         gDrawPanelList.popFront();
3567       end;
3568     end
3569     else
3570     begin
3571       if doDraw then g_Map_DrawPanels(panType, hasAmbient, ambColor);
3572     end;
3573     if (profileFrameDraw <> nil) then profileFrameDraw.sectionEnd();
3574   end;
3576   procedure drawOther (profname: AnsiString; cb: TDrawCB);
3577   begin
3578     if (profileFrameDraw <> nil) then profileFrameDraw.sectionBegin(profname);
3579     if assigned(cb) then cb();
3580     if (profileFrameDraw <> nil) then profileFrameDraw.sectionEnd();
3581   end;
3583 begin
3584   if (profileFrameDraw <> nil) then profileFrameDraw.sectionBegin('total');
3586   // our accelerated renderer will collect all panels to gDrawPanelList
3587   // we can use panel tag to render level parts (see GridTagXXX in g_map.pas)
3588   if (profileFrameDraw <> nil) then profileFrameDraw.sectionBegin('collect');
3589   if gdbg_map_use_accel_render then
3590   begin
3591     g_Map_CollectDrawPanels(sX, sY, sWidth, sHeight);
3592   end;
3593   if (profileFrameDraw <> nil) then profileFrameDraw.sectionEnd();
3595   if (profileFrameDraw <> nil) then profileFrameDraw.sectionBegin('skyback');
3596   g_Map_DrawBack(backXOfs, backYOfs);
3597   if (profileFrameDraw <> nil) then profileFrameDraw.sectionEnd();
3599   if setTransMatrix then
3600   begin
3601     //if (g_dbg_scale <> 1.0) then glTranslatef(0.0, -0.375/2, 0);
3602     glScalef(g_dbg_scale, g_dbg_scale, 1.0);
3603     glTranslatef(-sX, -sY, 0);
3604   end;
3606   // rendering mode
3607   ambColor := gCurrentMap['light_ambient'].rgba;
3608   hasAmbient := (not ambColor.isOpaque) or (not ambColor.isBlack);
3610   {
3611   if hasAmbient then
3612   begin
3613     //writeln('color: (', ambColor.r, ',', ambColor.g, ',', ambColor.b, ',', ambColor.a, ')');
3614     glColor4ub(ambColor.r, ambColor.g, ambColor.b, ambColor.a);
3615     glClear(GL_COLOR_BUFFER_BIT);
3616   end;
3617   }
3618   //writeln('color: (', ambColor.r, ',', ambColor.g, ',', ambColor.b, ',', ambColor.a, ')');
3621   drawPanelType('*back', PANEL_BACK, g_rlayer_back);
3622   drawPanelType('*step', PANEL_STEP, g_rlayer_step);
3623   drawOther('items', @g_Items_Draw);
3624   drawOther('weapons', @g_Weapon_Draw);
3625   drawOther('shells', @g_Player_DrawShells);
3626   drawOther('drawall', @g_Player_DrawAll);
3627   drawOther('corpses', @g_Player_DrawCorpses);
3628   drawPanelType('*wall', PANEL_WALL, g_rlayer_wall);
3629   drawOther('monsters', @g_Monsters_Draw);
3630   drawOther('itemdrop', @g_Items_DrawDrop);
3631   drawPanelType('*door', PANEL_CLOSEDOOR, g_rlayer_door);
3632   drawOther('gfx', @g_GFX_Draw);
3633   drawOther('flags', @g_Map_DrawFlags);
3634   drawPanelType('*acid1', PANEL_ACID1, g_rlayer_acid1);
3635   drawPanelType('*acid2', PANEL_ACID2, g_rlayer_acid2);
3636   drawPanelType('*water', PANEL_WATER, g_rlayer_water);
3637   drawOther('dynlights', @renderDynLightsInternal);
3639   if hasAmbient {and ((not g_playerLight) or (not gwin_has_stencil) or (g_dynLightCount < 1))} then
3640   begin
3641     renderAmbientQuad(hasAmbient, ambColor);
3642   end;
3644   doAmbient := true;
3645   drawPanelType('*fore', PANEL_FORE, g_rlayer_fore);
3648   if g_debug_HealthBar then
3649   begin
3650     g_Monsters_DrawHealth();
3651     g_Player_DrawHealth();
3652   end;
3654   if (profileFrameDraw <> nil) then profileFrameDraw.mainEnd(); // map rendering
3655 end;
3658 procedure DrawMapView(x, y, w, h: Integer);
3661   bx, by: Integer;
3662 begin
3663   glPushMatrix();
3665   bx := Round(x/(gMapInfo.Width - w)*(gBackSize.X - w));
3666   by := Round(y/(gMapInfo.Height - h)*(gBackSize.Y - h));
3668   sX := x;
3669   sY := y;
3670   sWidth := w;
3671   sHeight := h;
3673   fixViewportForScale();
3674   renderMapInternal(-bx, -by, true);
3676   glPopMatrix();
3677 end;
3680 procedure DrawPlayer(p: TPlayer);
3682   px, py, a, b, c, d, i, fX, fY: Integer;
3683   camObj: TObj;
3684   //R: TRect;
3685 begin
3686   if (p = nil) or (p.FDummy) then
3687   begin
3688     glPushMatrix();
3689     g_Map_DrawBack(0, 0);
3690     glPopMatrix();
3691     Exit;
3692   end;
3694   if (profileFrameDraw = nil) then profileFrameDraw := TProfiler.Create('RENDER', g_profile_history_size);
3695   if (profileFrameDraw <> nil) then profileFrameDraw.mainBegin(g_profile_frame_draw);
3697   gPlayerDrawn := p;
3699   glPushMatrix();
3701   camObj := p.getCameraObj();
3702   camObj.lerp(gLerpFactor, fX, fY);
3703   px := fX + PLAYER_RECT_CX;
3704   py := fY + PLAYER_RECT_CY+nlerp(p.SlopeOld, camObj.slopeUpLeft, gLerpFactor);
3706   if (g_dbg_scale = 1.0) and (not g_dbg_ignore_bounds) then
3707   begin
3708     if (px > (gPlayerScreenSize.X div 2)) then a := -px+(gPlayerScreenSize.X div 2) else a := 0;
3709     if (py > (gPlayerScreenSize.Y div 2)) then b := -py+(gPlayerScreenSize.Y div 2) else b := 0;
3711     if (px > gMapInfo.Width-(gPlayerScreenSize.X div 2)) then a := -gMapInfo.Width+gPlayerScreenSize.X;
3712     if (py > gMapInfo.Height-(gPlayerScreenSize.Y div 2)) then b := -gMapInfo.Height+gPlayerScreenSize.Y;
3714          if (gMapInfo.Width = gPlayerScreenSize.X) then a := 0
3715     else if (gMapInfo.Width < gPlayerScreenSize.X) then
3716     begin
3717       // hcenter
3718       a := (gPlayerScreenSize.X-gMapInfo.Width) div 2;
3719     end;
3721          if (gMapInfo.Height = gPlayerScreenSize.Y) then b := 0
3722     else if (gMapInfo.Height < gPlayerScreenSize.Y) then
3723     begin
3724       // vcenter
3725       b := (gPlayerScreenSize.Y-gMapInfo.Height) div 2;
3726     end;
3727   end
3728   else
3729   begin
3730     // scaled, ignore level bounds
3731     a := -px+(gPlayerScreenSize.X div 2);
3732     b := -py+(gPlayerScreenSize.Y div 2);
3733   end;
3735   sX := -a;
3736   sY := -b;
3737   sWidth := gPlayerScreenSize.X;
3738   sHeight := gPlayerScreenSize.Y;
3739   fixViewportForScale();
3741   i := py - (sY + sHeight div 2);
3742   if (p.IncCam > 0) then
3743   begin
3744     // clamp to level bounds
3745     if (sY - p.IncCam < 0) then
3746       p.IncCam := nclamp(sY, 0, 120);
3747     // clamp around player position
3748     if (i > 0) then
3749       p.IncCam := nclamp(p.IncCam, 0, max(0, 120 - i));
3750   end
3751   else if (p.IncCam < 0) then
3752   begin
3753     // clamp to level bounds
3754     if (sY + sHeight - p.IncCam > gMapInfo.Height) then
3755       p.IncCam := nclamp(sY + sHeight - gMapInfo.Height, -120, 0);
3756     // clamp around player position
3757     if (i < 0) then
3758       p.IncCam := nclamp(p.IncCam, min(0, -120 - i), 0);
3759   end;
3761   sY := sY - nlerp(p.IncCamOld, p.IncCam, gLerpFactor);
3763   if (not g_dbg_ignore_bounds) then
3764   begin
3765     if (sX+sWidth > gMapInfo.Width) then sX := gMapInfo.Width-sWidth;
3766     if (sY+sHeight > gMapInfo.Height) then sY := gMapInfo.Height-sHeight;
3767     if (sX < 0) then sX := 0;
3768     if (sY < 0) then sY := 0;
3769   end;
3771   if (gBackSize.X <= gPlayerScreenSize.X) or (gMapInfo.Width <= sWidth) then c := 0 else c := trunc((gBackSize.X-gPlayerScreenSize.X)*sX/(gMapInfo.Width-sWidth));
3772   if (gBackSize.Y <= gPlayerScreenSize.Y) or (gMapInfo.Height <= sHeight) then d := 0 else d := trunc((gBackSize.Y-gPlayerScreenSize.Y)*sY/(gMapInfo.Height-sHeight));
3774   //r_smallmap_h: 0: left; 1: center; 2: right
3775   //r_smallmap_v: 0: top; 1: center; 2: bottom
3776   // horiz small map?
3777   if (gMapInfo.Width = sWidth) then
3778   begin
3779     sX := 0;
3780   end
3781   else if (gMapInfo.Width < sWidth) then
3782   begin
3783     case r_smallmap_h of
3784       1: sX := -((sWidth-gMapInfo.Width) div 2); // center
3785       2: sX := -(sWidth-gMapInfo.Width); // right
3786       else sX := 0; // left
3787     end;
3788   end;
3789   // vert small map?
3790   if (gMapInfo.Height = sHeight) then
3791   begin
3792     sY := 0;
3793   end
3794   else if (gMapInfo.Height < sHeight) then
3795   begin
3796     case r_smallmap_v of
3797       1: sY := -((sHeight-gMapInfo.Height) div 2); // center
3798       2: sY := -(sHeight-gMapInfo.Height); // bottom
3799       else sY := 0; // top
3800     end;
3801   end;
3803   p.viewPortX := sX;
3804   p.viewPortY := sY;
3805   p.viewPortW := sWidth;
3806   p.viewPortH := sHeight;
3808 {$IFDEF ENABLE_HOLMES}
3809   if (p = gPlayer1) then
3810   begin
3811     g_Holmes_plrViewPos(sX, sY);
3812     g_Holmes_plrViewSize(sWidth, sHeight);
3813   end;
3814 {$ENDIF}
3816   renderMapInternal(-c, -d, true);
3818   if (gGameSettings.GameMode <> GM_SINGLE) and (gPlayerIndicator > 0) then
3819     case gPlayerIndicator of
3820       1:
3821         p.DrawIndicator(_RGB(255, 255, 255));
3823       2:
3824         for i := 0 to High(gPlayers) do
3825           if gPlayers[i] <> nil then
3826             if gPlayers[i] = p then p.DrawIndicator(_RGB(255, 255, 255))
3827             else if (gPlayers[i].Team = p.Team) and (gPlayers[i].Team <> TEAM_NONE) then
3828               if gPlayerIndicatorStyle = 1 then
3829                 gPlayers[i].DrawIndicator(_RGB(192, 192, 192))
3830               else gPlayers[i].DrawIndicator(gPlayers[i].GetColor);
3831     end;
3833   {
3834   for a := 0 to High(gCollideMap) do
3835     for b := 0 to High(gCollideMap[a]) do
3836     begin
3837       d := 0;
3838       if ByteBool(gCollideMap[a, b] and MARK_WALL) then
3839         d := d + 1;
3840       if ByteBool(gCollideMap[a, b] and MARK_DOOR) then
3841         d := d + 2;
3843       case d of
3844         1: e_DrawPoint(1, b, a, 200, 200, 200);
3845         2: e_DrawPoint(1, b, a, 64, 64, 255);
3846         3: e_DrawPoint(1, b, a, 255, 0, 255);
3847       end;
3848     end;
3849   }
3851   glPopMatrix();
3853   p.DrawPain();
3854   p.DrawPickup();
3855   p.DrawRulez();
3856   if gShowMap then DrawMinimap(p, _TRect(0, 0, 128, 128));
3857   if g_Debug_Player then
3858     g_Player_DrawDebug(p);
3859   p.DrawGUI();
3860 end;
3862 procedure drawProfilers ();
3864   px: Integer = -1;
3865   py: Integer = -1;
3866 begin
3867   if g_profile_frame_draw and (profileFrameDraw <> nil) then px := px-drawProfiles(px, py, profileFrameDraw);
3868   if g_profile_collision and (profMapCollision <> nil) then begin px := px-drawProfiles(px, py, profMapCollision); py -= calcProfilesHeight(profMonsLOS); end;
3869   if g_profile_los and (profMonsLOS <> nil) then begin px := px-drawProfiles(px, py, profMonsLOS); py -= calcProfilesHeight(profMonsLOS); end;
3870 end;
3872 procedure g_Game_Draw();
3874   ID: DWORD;
3875   w, h: Word;
3876   ww, hh: Byte;
3877   Time: Int64;
3878   back: string;
3879   plView1, plView2: TPlayer;
3880   Split: Boolean;
3881 begin
3882   if gExit = EXIT_QUIT then Exit;
3884   Time := sys_GetTicks() {div 1000};
3885   FPSCounter := FPSCounter+1;
3886   if Time - FPSTime >= 1000 then
3887   begin
3888     FPS := FPSCounter;
3889     FPSCounter := 0;
3890     FPSTime := Time;
3891   end;
3893   e_SetRendertarget(True);
3894   e_SetViewPort(0, 0, gScreenWidth, gScreenHeight);
3896   if gGameOn or (gState = STATE_FOLD) then
3897   begin
3898     if (gPlayer1 <> nil) and (gPlayer2 <> nil) then
3899     begin
3900       gSpectMode := SPECT_NONE;
3901       if not gRevertPlayers then
3902       begin
3903         plView1 := gPlayer1;
3904         plView2 := gPlayer2;
3905       end
3906       else
3907       begin
3908         plView1 := gPlayer2;
3909         plView2 := gPlayer1;
3910       end;
3911     end
3912     else
3913       if (gPlayer1 <> nil) or (gPlayer2 <> nil) then
3914       begin
3915         gSpectMode := SPECT_NONE;
3916         if gPlayer2 = nil then
3917           plView1 := gPlayer1
3918         else
3919           plView1 := gPlayer2;
3920         plView2 := nil;
3921       end
3922       else
3923       begin
3924         plView1 := nil;
3925         plView2 := nil;
3926       end;
3928     if (plView1 = nil) and (plView2 = nil) and (gSpectMode = SPECT_NONE) then
3929       gSpectMode := SPECT_STATS;
3931     if gSpectMode = SPECT_PLAYERS then
3932       if gPlayers <> nil then
3933       begin
3934         plView1 := GetActivePlayer_ByID(gSpectPID1);
3935         if plView1 = nil then
3936         begin
3937           gSpectPID1 := GetActivePlayerID_Next();
3938           plView1 := GetActivePlayer_ByID(gSpectPID1);
3939         end;
3940         if gSpectViewTwo then
3941         begin
3942           plView2 := GetActivePlayer_ByID(gSpectPID2);
3943           if plView2 = nil then
3944           begin
3945             gSpectPID2 := GetActivePlayerID_Next();
3946             plView2 := GetActivePlayer_ByID(gSpectPID2);
3947           end;
3948         end;
3949       end;
3951     if gSpectMode = SPECT_MAPVIEW then
3952     begin
3953     // Ðåæèì ïðîñìîòðà êàðòû
3954       Split := False;
3955       e_SetViewPort(0, 0, gScreenWidth, gScreenHeight);
3956       DrawMapView(gSpectX, gSpectY, gScreenWidth, gScreenHeight);
3957       gHearPoint1.Active := True;
3958       gHearPoint1.Coords.X := gScreenWidth div 2 + gSpectX;
3959       gHearPoint1.Coords.Y := gScreenHeight div 2 + gSpectY;
3960       gHearPoint2.Active := False;
3961     end
3962     else
3963     begin
3964       Split := (plView1 <> nil) and (plView2 <> nil);
3966     // Òî÷êè ñëóõà èãðîêîâ
3967       if plView1 <> nil then
3968       begin
3969         gHearPoint1.Active := True;
3970         gHearPoint1.Coords.X := plView1.GameX + PLAYER_RECT.Width;
3971         gHearPoint1.Coords.Y := plView1.GameY + PLAYER_RECT.Height DIV 2;
3972       end else
3973         gHearPoint1.Active := False;
3974       if plView2 <> nil then
3975       begin
3976         gHearPoint2.Active := True;
3977         gHearPoint2.Coords.X := plView2.GameX + PLAYER_RECT.Width;
3978         gHearPoint2.Coords.Y := plView2.GameY + PLAYER_RECT.Height DIV 2;
3979       end else
3980         gHearPoint2.Active := False;
3982     // Ðàçìåð ýêðàíîâ èãðîêîâ:
3983       gPlayerScreenSize.X := gScreenWidth-196;
3984       if Split then
3985       begin
3986         gPlayerScreenSize.Y := gScreenHeight div 2;
3987         if gScreenHeight mod 2 = 0 then
3988           Dec(gPlayerScreenSize.Y);
3989       end
3990       else
3991         gPlayerScreenSize.Y := gScreenHeight;
3993       if Split then
3994         if gScreenHeight mod 2 = 0 then
3995           e_SetViewPort(0, gPlayerScreenSize.Y+2, gPlayerScreenSize.X+196, gPlayerScreenSize.Y)
3996         else
3997           e_SetViewPort(0, gPlayerScreenSize.Y+1, gPlayerScreenSize.X+196, gPlayerScreenSize.Y);
3999       DrawPlayer(plView1);
4000       gPlayer1ScreenCoord.X := sX;
4001       gPlayer1ScreenCoord.Y := sY;
4003       if Split then
4004       begin
4005         e_SetViewPort(0, 0, gPlayerScreenSize.X+196, gPlayerScreenSize.Y);
4007         DrawPlayer(plView2);
4008         gPlayer2ScreenCoord.X := sX;
4009         gPlayer2ScreenCoord.Y := sY;
4010       end;
4012       e_SetViewPort(0, 0, gScreenWidth, gScreenHeight);
4014       if Split then
4015         e_DrawLine(2, 0, gScreenHeight div 2, gScreenWidth, gScreenHeight div 2, 0, 0, 0);
4016     end;
4018 {$IFDEF ENABLE_HOLMES}
4019     // draw inspector
4020     if (g_holmes_enabled) then g_Holmes_Draw();
4021 {$ENDIF}
4023     if MessageText <> '' then
4024     begin
4025       w := 0;
4026       h := 0;
4027       e_CharFont_GetSizeFmt(gMenuFont, MessageText, w, h);
4028       if Split then
4029         e_CharFont_PrintFmt(gMenuFont, (gScreenWidth div 2)-(w div 2),
4030                         (gScreenHeight div 2)-(h div 2), MessageText)
4031       else
4032         e_CharFont_PrintFmt(gMenuFont, (gScreenWidth div 2)-(w div 2),
4033                   Round(gScreenHeight / 2.75)-(h div 2), MessageText);
4034     end;
4036     if IsDrawStat or (gSpectMode = SPECT_STATS) then
4037       DrawStat();
4039     if gSpectHUD and (not gChatShow) and (gSpectMode <> SPECT_NONE) and (not gSpectAuto) then
4040     begin
4041     // Draw spectator GUI
4042       ww := 0;
4043       hh := 0;
4044       e_TextureFontGetSize(gStdFont, ww, hh);
4045       case gSpectMode of
4046         SPECT_STATS:
4047           e_TextureFontPrintEx(0, gScreenHeight - (hh+2)*2, 'MODE: Stats', gStdFont, 255, 255, 255, 1);
4048         SPECT_MAPVIEW:
4049           e_TextureFontPrintEx(0, gScreenHeight - (hh+2)*2, 'MODE: Observe Map', gStdFont, 255, 255, 255, 1);
4050         SPECT_PLAYERS:
4051           e_TextureFontPrintEx(0, gScreenHeight - (hh+2)*2, 'MODE: Watch Players', gStdFont, 255, 255, 255, 1);
4052       end;
4053       e_TextureFontPrintEx(2*ww, gScreenHeight - (hh+2), '< jump >', gStdFont, 255, 255, 255, 1);
4054       if gSpectMode = SPECT_STATS then
4055       begin
4056         e_TextureFontPrintEx(16*ww, gScreenHeight - (hh+2)*2, 'Autoview', gStdFont, 255, 255, 255, 1);
4057         e_TextureFontPrintEx(16*ww, gScreenHeight - (hh+2), '< fire >', gStdFont, 255, 255, 255, 1);
4058       end;
4059       if gSpectMode = SPECT_MAPVIEW then
4060       begin
4061         e_TextureFontPrintEx(22*ww, gScreenHeight - (hh+2)*2, '[-]', gStdFont, 255, 255, 255, 1);
4062         e_TextureFontPrintEx(26*ww, gScreenHeight - (hh+2)*2, 'Step ' + IntToStr(gSpectStep), gStdFont, 255, 255, 255, 1);
4063         e_TextureFontPrintEx(34*ww, gScreenHeight - (hh+2)*2, '[+]', gStdFont, 255, 255, 255, 1);
4064         e_TextureFontPrintEx(18*ww, gScreenHeight - (hh+2), '<prev weap>', gStdFont, 255, 255, 255, 1);
4065         e_TextureFontPrintEx(30*ww, gScreenHeight - (hh+2), '<next weap>', gStdFont, 255, 255, 255, 1);
4066       end;
4067       if gSpectMode = SPECT_PLAYERS then
4068       begin
4069         e_TextureFontPrintEx(22*ww, gScreenHeight - (hh+2)*2, 'Player 1', gStdFont, 255, 255, 255, 1);
4070         e_TextureFontPrintEx(20*ww, gScreenHeight - (hh+2), '<left/right>', gStdFont, 255, 255, 255, 1);
4071         if gSpectViewTwo then
4072         begin
4073           e_TextureFontPrintEx(37*ww, gScreenHeight - (hh+2)*2, 'Player 2', gStdFont, 255, 255, 255, 1);
4074           e_TextureFontPrintEx(34*ww, gScreenHeight - (hh+2), '<prev w/next w>', gStdFont, 255, 255, 255, 1);
4075           e_TextureFontPrintEx(52*ww, gScreenHeight - (hh+2)*2, '2x View', gStdFont, 255, 255, 255, 1);
4076           e_TextureFontPrintEx(51*ww, gScreenHeight - (hh+2), '<up/down>', gStdFont, 255, 255, 255, 1);
4077         end
4078         else
4079         begin
4080           e_TextureFontPrintEx(35*ww, gScreenHeight - (hh+2)*2, '2x View', gStdFont, 255, 255, 255, 1);
4081           e_TextureFontPrintEx(34*ww, gScreenHeight - (hh+2), '<up/down>', gStdFont, 255, 255, 255, 1);
4082         end;
4083       end;
4084     end;
4085   end;
4087   if gPauseMain and gGameOn and (g_ActiveWindow = nil) then
4088   begin
4089     //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
4090     e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
4092     e_CharFont_GetSize(gMenuFont, _lc[I_MENU_PAUSE], w, h);
4093     e_CharFont_Print(gMenuFont, (gScreenWidth div 2)-(w div 2),
4094                     (gScreenHeight div 2)-(h div 2), _lc[I_MENU_PAUSE]);
4095   end;
4097   if not gGameOn then
4098   begin
4099     if (gState = STATE_MENU) then
4100     begin
4101       if (g_ActiveWindow = nil) or (g_ActiveWindow.BackTexture = '') then DrawMenuBackground('MENU_BACKGROUND');
4102       // F3 at menu will show game loading dialog
4103       if e_KeyPressed(IK_F3) then g_Menu_Show_LoadMenu(true);
4104       if (g_ActiveWindow <> nil) then
4105       begin
4106         //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
4107         e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
4108       end
4109       else
4110       begin
4111         // F3 at titlepic will show game loading dialog
4112         if e_KeyPressed(IK_F3) then
4113         begin
4114           g_Menu_Show_LoadMenu(true);
4115           if (g_ActiveWindow <> nil) then e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
4116         end;
4117       end;
4118     end;
4120     if gState = STATE_FOLD then
4121     begin
4122       e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 0, 0, 0, EndingGameCounter);
4123     end;
4125     if gState = STATE_INTERCUSTOM then
4126     begin
4127       if gLastMap and (gGameSettings.GameMode = GM_COOP) then
4128       begin
4129         back := 'TEXTURE_endpic';
4130         if not g_Texture_Get(back, ID) then
4131           back := _lc[I_TEXTURE_ENDPIC];
4132       end
4133       else
4134         back := 'INTER';
4136       DrawMenuBackground(back);
4138       DrawCustomStat();
4140       if g_ActiveWindow <> nil then
4141       begin
4142         //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
4143         e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
4144       end;
4145     end;
4147     if gState = STATE_INTERSINGLE then
4148     begin
4149       if EndingGameCounter > 0 then
4150       begin
4151         e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 0, 0, 0, EndingGameCounter);
4152       end
4153       else
4154       begin
4155         back := 'INTER';
4157         DrawMenuBackground(back);
4159         DrawSingleStat();
4161         if g_ActiveWindow <> nil then
4162         begin
4163           //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
4164           e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
4165         end;
4166       end;
4167     end;
4169     if gState = STATE_ENDPIC then
4170     begin
4171       ID := DWORD(-1);
4172       if g_Texture_Get('TEXTURE_endpic', ID) then DrawMenuBackground('TEXTURE_endpic')
4173       else DrawMenuBackground(_lc[I_TEXTURE_ENDPIC]);
4175       if g_ActiveWindow <> nil then
4176       begin
4177         //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
4178         e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
4179       end;
4180     end;
4182     if gState = STATE_SLIST then
4183     begin
4184 //      if g_Texture_Get('MENU_BACKGROUND', ID) then
4185 //      begin
4186 //        e_DrawSize(ID, 0, 0, 0, False, False, gScreenWidth, gScreenHeight);
4187 //        //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
4188 //      end;
4189       DrawMenuBackground('MENU_BACKGROUND');
4190       e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
4191       g_Serverlist_Draw(slCurrent, slTable);
4192     end;
4193   end;
4195   if g_ActiveWindow <> nil then
4196   begin
4197     if gGameOn then
4198     begin
4199       //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
4200       e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
4201     end;
4202     g_ActiveWindow.Draw();
4203   end;
4205 {$IFNDEF HEADLESS}
4206   g_Console_Draw();
4207 {$ENDIF}
4209   if g_debug_Sounds and gGameOn then
4210   begin
4211     for w := 0 to High(e_SoundsArray) do
4212       for h := 0 to e_SoundsArray[w].nRefs do
4213         e_DrawPoint(1, w+100, h+100, 255, 0, 0);
4214   end;
4216   if gShowFPS then
4217   begin
4218     e_TextureFontPrint(0, 0, Format('FPS: %d', [FPS]), gStdFont);
4219     e_TextureFontPrint(0, 16, Format('UPS: %d', [UPS]), gStdFont);
4220   end;
4222   if gGameOn and gShowTime then
4223     drawTime(gScreenWidth-72, gScreenHeight-16);
4225   if gGameOn then drawProfilers();
4227   // TODO: draw this after the FBO and remap mouse click coordinates
4229 {$IFDEF ENABLE_HOLMES}
4230   g_Holmes_DrawUI();
4231 {$ENDIF}
4233   // blit framebuffer to screen
4235   e_SetRendertarget(False);
4236   e_SetViewPort(0, 0, gWinSizeX, gWinSizeY);
4237   e_BlitFramebuffer(gWinSizeX, gWinSizeY);
4239   // draw the overlay stuff on top of it
4241   g_Touch_Draw;
4242 end;
4244 procedure g_Game_Quit();
4245 begin
4246   g_Game_StopAllSounds(True);
4247   gMusic.Free();
4248   g_Game_FreeData();
4249   g_PlayerModel_FreeData();
4250   g_Texture_DeleteAll();
4251   g_Frames_DeleteAll();
4252 {$IFNDEF HEADLESS}
4253   //g_Menu_Free(); //k8: this segfaults after resolution change; who cares?
4254 {$ENDIF}
4256   if NetInitDone then g_Net_Free;
4258 // Íàäî óäàëèòü êàðòó ïîñëå òåñòà:
4259   if gMapToDelete <> '' then
4260     g_Game_DeleteTestMap();
4262   gExit := EXIT_QUIT;
4263   sys_RequestQuit;
4264 end;
4266 procedure g_FatalError(Text: String);
4267 begin
4268   g_Console_Add(Format(_lc[I_FATAL_ERROR], [Text]), True);
4269   e_WriteLog(Format(_lc[I_FATAL_ERROR], [Text]), TMsgType.Warning);
4271   gExit := EXIT_SIMPLE;
4272   if gGameOn then EndGame;
4273 end;
4275 procedure g_SimpleError(Text: String);
4276 begin
4277   g_Console_Add(Format(_lc[I_SIMPLE_ERROR], [Text]), True);
4278   e_WriteLog(Format(_lc[I_SIMPLE_ERROR], [Text]), TMsgType.Warning);
4279 end;
4281 procedure g_Game_SetupScreenSize();
4282 const
4283   RES_FACTOR = 4.0 / 3.0;
4285   s: Single;
4286   rf: Single;
4287   bw, bh: Word;
4288 begin
4289 // Ðàçìåð ýêðàíîâ èãðîêîâ:
4290   gPlayerScreenSize.X := gScreenWidth-196;
4291   if (gPlayer1 <> nil) and (gPlayer2 <> nil) then
4292     gPlayerScreenSize.Y := gScreenHeight div 2
4293   else
4294     gPlayerScreenSize.Y := gScreenHeight;
4296 // Ðàçìåð çàäíåãî ïëàíà:
4297   if BackID <> DWORD(-1) then
4298   begin
4299     s := SKY_STRETCH;
4300     if (gScreenWidth*s > gMapInfo.Width) or
4301        (gScreenHeight*s > gMapInfo.Height) then
4302     begin
4303       gBackSize.X := gScreenWidth;
4304       gBackSize.Y := gScreenHeight;
4305     end
4306     else
4307     begin
4308       e_GetTextureSize(BackID, @bw, @bh);
4309       rf := Single(bw) / Single(bh);
4310       if (rf > RES_FACTOR) then bw := Round(Single(bh) * RES_FACTOR)
4311       else if (rf < RES_FACTOR) then bh := Round(Single(bw) / RES_FACTOR);
4312       s := Max(gScreenWidth / bw, gScreenHeight / bh);
4313       if (s < 1.0) then s := 1.0;
4314       gBackSize.X := Round(bw*s);
4315       gBackSize.Y := Round(bh*s);
4316     end;
4317   end;
4318 end;
4320 procedure g_Game_ChangeResolution(newWidth, newHeight: Word; nowFull, nowMax: Boolean);
4321 begin
4322   sys_SetDisplayMode(newWidth, newHeight, gBPP, nowFull, nowMax);
4323 end;
4325 procedure g_Game_AddPlayer(Team: Byte = TEAM_NONE);
4326 begin
4327   if ((not gGameOn) and (gState <> STATE_INTERCUSTOM))
4328   or (not (gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT])) then
4329     Exit;
4331   if (gGameSettings.MaxLives > 0) and (gLMSRespawn = LMS_RESPAWN_NONE) then
4332     Exit;
4334   if gPlayer1 = nil then
4335   begin
4336     if g_Game_IsClient then
4337     begin
4338       if NetPlrUID1 > -1 then
4339         MC_SEND_CheatRequest(NET_CHEAT_SPECTATE);
4340       Exit;
4341     end;
4343     if not (Team in [TEAM_RED, TEAM_BLUE]) then
4344       Team := gPlayer1Settings.Team;
4346     // Ñîçäàíèå ïåðâîãî èãðîêà:
4347     gPlayer1 := g_Player_Get(g_Player_Create(gPlayer1Settings.Model,
4348                                              gPlayer1Settings.Color,
4349                                              Team, False));
4350     if gPlayer1 = nil then
4351       g_FatalError(Format(_lc[I_GAME_ERROR_PLAYER_CREATE], [1]))
4352     else
4353     begin
4354       gPlayer1.Name := gPlayer1Settings.Name;
4355       gPlayer1.WeapSwitchMode := gPlayer1Settings.WeaponSwitch;
4356       gPlayer1.setWeaponPrefs(gPlayer1Settings.WeaponPreferences);
4357       gPlayer1.SwitchToEmpty := gPlayer1Settings.SwitchToEmpty;
4358       gPlayer1.SkipFist := gPlayer1Settings.SkipFist;
4359       g_Console_Add(Format(_lc[I_PLAYER_JOIN], [gPlayer1.Name]), True);
4360       if g_Game_IsServer and g_Game_IsNet then
4361         MH_SEND_PlayerCreate(gPlayer1.UID);
4362       gPlayer1.Respawn(False, True);
4363       g_Net_Slist_ServerPlayerComes();
4364     end;
4366     Exit;
4367   end;
4368   if gPlayer2 = nil then
4369   begin
4370     if g_Game_IsClient then
4371     begin
4372       if NetPlrUID2 > -1 then
4373         gPlayer2 := g_Player_Get(NetPlrUID2);
4374       Exit;
4375     end;
4377     if not (Team in [TEAM_RED, TEAM_BLUE]) then
4378       Team := gPlayer2Settings.Team;
4380     // Ñîçäàíèå âòîðîãî èãðîêà:
4381     gPlayer2 := g_Player_Get(g_Player_Create(gPlayer2Settings.Model,
4382                                              gPlayer2Settings.Color,
4383                                              Team, False));
4384     if gPlayer2 = nil then
4385       g_FatalError(Format(_lc[I_GAME_ERROR_PLAYER_CREATE], [2]))
4386     else
4387     begin
4388       gPlayer2.Name := gPlayer2Settings.Name;
4389       gPlayer2.WeapSwitchMode := gPlayer2Settings.WeaponSwitch;
4390       gPlayer2.setWeaponPrefs(gPlayer2Settings.WeaponPreferences);
4391       gPlayer2.SwitchToEmpty := gPlayer2Settings.SwitchToEmpty;
4392       gPlayer2.SkipFist := gPlayer2Settings.SkipFist;
4393       g_Console_Add(Format(_lc[I_PLAYER_JOIN], [gPlayer2.Name]), True);
4394       if g_Game_IsServer and g_Game_IsNet then
4395         MH_SEND_PlayerCreate(gPlayer2.UID);
4396       gPlayer2.Respawn(False, True);
4397       g_Net_Slist_ServerPlayerComes();
4398     end;
4400     Exit;
4401   end;
4402 end;
4404 procedure g_Game_RemovePlayer();
4406   Pl: TPlayer;
4407 begin
4408   if ((not gGameOn) and (gState <> STATE_INTERCUSTOM))
4409   or (not (gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT])) then
4410     Exit;
4411   Pl := gPlayer2;
4412   if Pl <> nil then
4413   begin
4414     if g_Game_IsServer then
4415     begin
4416       Pl.Lives := 0;
4417       Pl.Kill(K_SIMPLEKILL, 0, HIT_DISCON);
4418       g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [Pl.Name]), True);
4419       g_Player_Remove(Pl.UID);
4420       g_Net_Slist_ServerPlayerLeaves();
4421     end
4422     else
4423     begin
4424       gSpectLatchPID2 := Pl.UID;
4425       gPlayer2 := nil;
4426     end;
4427     Exit;
4428   end;
4429   Pl := gPlayer1;
4430   if Pl <> nil then
4431   begin
4432     if g_Game_IsServer then
4433     begin
4434       Pl.Lives := 0;
4435       Pl.Kill(K_SIMPLEKILL, 0, HIT_DISCON);
4436       g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [Pl.Name]), True);
4437       g_Player_Remove(Pl.UID);
4438       g_Net_Slist_ServerPlayerLeaves();
4439     end else
4440     begin
4441       gSpectLatchPID1 := Pl.UID;
4442       gPlayer1 := nil;
4443       MC_SEND_CheatRequest(NET_CHEAT_SPECTATE);
4444     end;
4445     Exit;
4446   end;
4447   g_Net_Slist_ServerPlayerLeaves();
4448 end;
4450 procedure g_Game_Spectate();
4451 begin
4452   g_Game_RemovePlayer();
4453   if gPlayer1 <> nil then
4454     g_Game_RemovePlayer();
4455 end;
4457 procedure g_Game_SpectateCenterView();
4458 begin
4459   gSpectX := Max(gMapInfo.Width div 2 - gScreenWidth div 2, 0);
4460   gSpectY := Max(gMapInfo.Height div 2 - gScreenHeight div 2, 0);
4461 end;
4463 procedure g_Game_StartSingle(Map: String; TwoPlayers: Boolean; nPlayers: Byte);
4465   i, nPl: Integer;
4466   tmps: AnsiString;
4467 begin
4468   g_Game_Free();
4470   e_WriteLog('Starting singleplayer game...', TMsgType.Notify);
4472   g_Game_ClearLoading();
4474 // Íàñòðîéêè èãðû:
4475   FillByte(gGameSettings, SizeOf(TGameSettings), 0);
4476   gAimLine := False;
4477   gShowMap := False;
4478   gGameSettings.GameType := GT_SINGLE;
4479   gGameSettings.MaxLives := 0;
4480   gGameSettings.Options := gGameSettings.Options + GAME_OPTION_ALLOWEXIT;
4481   gGameSettings.Options := gGameSettings.Options + GAME_OPTION_MONSTERS;
4482   gGameSettings.Options := gGameSettings.Options + GAME_OPTION_BOTVSMONSTER;
4483   gGameSettings.Options := gGameSettings.Options + GAME_OPTION_TEAMHITPROJECTILE;
4484   gGameSettings.Options := gGameSettings.Options + GAME_OPTION_TEAMHITTRACE;
4485   gSwitchGameMode := GM_SINGLE;
4487   gLMSRespawn := LMS_RESPAWN_NONE;
4488   gLMSRespawnTime := 0;
4489   gSpectLatchPID1 := 0;
4490   gSpectLatchPID2 := 0;
4492   g_Game_ExecuteEvent('ongamestart');
4494 // Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ:
4495   g_Game_SetupScreenSize();
4497 // Ñîçäàíèå ïåðâîãî èãðîêà:
4498   gPlayer1 := g_Player_Get(g_Player_Create(gPlayer1Settings.Model,
4499                                            gPlayer1Settings.Color,
4500                                            gPlayer1Settings.Team, False));
4501   if gPlayer1 = nil then
4502   begin
4503     g_FatalError(Format(_lc[I_GAME_ERROR_PLAYER_CREATE], [1]));
4504     Exit;
4505   end;
4507   gPlayer1.Name := gPlayer1Settings.Name;
4508   gPlayer1.WeapSwitchMode := gPlayer1Settings.WeaponSwitch;
4509   gPlayer1.setWeaponPrefs(gPlayer1Settings.WeaponPreferences);
4510   gPlayer1.SwitchToEmpty := gPlayer1Settings.SwitchToEmpty;
4511   gPlayer1.SkipFist := gPlayer1Settings.SkipFist;
4512   nPl := 1;
4514 // Ñîçäàíèå âòîðîãî èãðîêà, åñëè åñòü:
4515   if TwoPlayers then
4516   begin
4517     gPlayer2 := g_Player_Get(g_Player_Create(gPlayer2Settings.Model,
4518                                              gPlayer2Settings.Color,
4519                                              gPlayer2Settings.Team, False));
4520     if gPlayer2 = nil then
4521     begin
4522       g_FatalError(Format(_lc[I_GAME_ERROR_PLAYER_CREATE], [2]));
4523       Exit;
4524     end;
4526     gPlayer2.Name := gPlayer2Settings.Name;
4527     gPlayer2.WeapSwitchMode := gPlayer2Settings.WeaponSwitch;
4528     gPlayer2.setWeaponPrefs(gPlayer2Settings.WeaponPreferences);
4529     gPlayer2.SwitchToEmpty := gPlayer2Settings.SwitchToEmpty;
4530     gPlayer2.SkipFist := gPlayer2Settings.SkipFist;
4531     Inc(nPl);
4532   end;
4534 // Çàãðóçêà è çàïóñê êàðòû:
4535   if not g_Game_StartMap(false{asMegawad}, MAP, True) then
4536   begin
4537     if (Pos(':\', Map) > 0) or (Pos(':/', Map) > 0) then tmps := Map else tmps := gGameSettings.WAD + ':\' + MAP;
4538     g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [tmps]));
4539     Exit;
4540   end;
4542 // Íàñòðîéêè èãðîêîâ è áîòîâ:
4543   g_Player_Init();
4545 // Ñîçäàåì áîòîâ:
4546   for i := nPl+1 to nPlayers do
4547     g_Player_Create(STD_PLAYER_MODEL, _RGB(0, 0, 0), 0, True);
4548 end;
4550 procedure g_Game_StartCustom(Map: String; GameMode: Byte;
4551                              TimeLimit, GoalLimit: Word;
4552                              MaxLives: Byte;
4553                              Options: LongWord; nPlayers: Byte);
4555   i, nPl: Integer;
4556 begin
4557   g_Game_Free();
4559   e_WriteLog('Starting custom game...', TMsgType.Notify);
4561   g_Game_ClearLoading();
4563 // Íàñòðîéêè èãðû:
4564   gGameSettings.GameType := GT_CUSTOM;
4565   gGameSettings.GameMode := GameMode;
4566   gSwitchGameMode := GameMode;
4567   gGameSettings.TimeLimit := TimeLimit;
4568   gGameSettings.GoalLimit := GoalLimit;
4569   gGameSettings.MaxLives := IfThen(GameMode = GM_CTF, 0, MaxLives);
4570   gGameSettings.Options := Options;
4572   gCoopTotalMonstersKilled := 0;
4573   gCoopTotalSecretsFound := 0;
4574   gCoopTotalMonsters := 0;
4575   gCoopTotalSecrets := 0;
4576   gAimLine := False;
4577   gShowMap := False;
4579   gLMSRespawn := LMS_RESPAWN_NONE;
4580   gLMSRespawnTime := 0;
4581   gSpectLatchPID1 := 0;
4582   gSpectLatchPID2 := 0;
4584   g_Game_ExecuteEvent('ongamestart');
4586 // Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ:
4587   g_Game_SetupScreenSize();
4589 // Ðåæèì íàáëþäàòåëÿ:
4590   if nPlayers = 0 then
4591   begin
4592     gPlayer1 := nil;
4593     gPlayer2 := nil;
4594   end;
4596   nPl := 0;
4597   if nPlayers >= 1 then
4598   begin
4599   // Ñîçäàíèå ïåðâîãî èãðîêà:
4600     gPlayer1 := g_Player_Get(g_Player_Create(gPlayer1Settings.Model,
4601                                              gPlayer1Settings.Color,
4602                                              gPlayer1Settings.Team, False));
4603     if gPlayer1 = nil then
4604     begin
4605       g_FatalError(Format(_lc[I_GAME_ERROR_PLAYER_CREATE], [1]));
4606       Exit;
4607     end;
4609     gPlayer1.Name := gPlayer1Settings.Name;
4610     gPlayer1.WeapSwitchMode := gPlayer1Settings.WeaponSwitch;
4611     gPlayer1.setWeaponPrefs(gPlayer1Settings.WeaponPreferences);
4612     gPlayer1.SwitchToEmpty := gPlayer1Settings.SwitchToEmpty;
4613     gPlayer1.SkipFist := gPlayer1Settings.SkipFist;
4614     Inc(nPl);
4615   end;
4617   if nPlayers >= 2 then
4618   begin
4619   // Ñîçäàíèå âòîðîãî èãðîêà:
4620     gPlayer2 := g_Player_Get(g_Player_Create(gPlayer2Settings.Model,
4621                                              gPlayer2Settings.Color,
4622                                              gPlayer2Settings.Team, False));
4623     if gPlayer2 = nil then
4624     begin
4625       g_FatalError(Format(_lc[I_GAME_ERROR_PLAYER_CREATE], [2]));
4626       Exit;
4627     end;
4629     gPlayer2.Name := gPlayer2Settings.Name;
4630     gPlayer2.WeapSwitchMode := gPlayer2Settings.WeaponSwitch;
4631     gPlayer2.setWeaponPrefs(gPlayer2Settings.WeaponPreferences);
4632     gPlayer2.SwitchToEmpty := gPlayer2Settings.SwitchToEmpty;
4633     gPlayer2.SkipFist := gPlayer2Settings.SkipFist;
4634     Inc(nPl);
4635   end;
4637 // Çàãðóçêà è çàïóñê êàðòû:
4638   if not g_Game_StartMap(true{asMegawad}, Map, True) then
4639   begin
4640     g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Map]));
4641     Exit;
4642   end;
4644 // Íåò òî÷åê ïîÿâëåíèÿ:
4645   if (g_Map_GetPointCount(RESPAWNPOINT_PLAYER1) +
4646       g_Map_GetPointCount(RESPAWNPOINT_PLAYER2) +
4647       g_Map_GetPointCount(RESPAWNPOINT_DM) +
4648       g_Map_GetPointCount(RESPAWNPOINT_RED)+
4649       g_Map_GetPointCount(RESPAWNPOINT_BLUE)) < 1 then
4650   begin
4651     g_FatalError(_lc[I_GAME_ERROR_GET_SPAWN]);
4652     Exit;
4653   end;
4655 // Íàñòðîéêè èãðîêîâ è áîòîâ:
4656   g_Player_Init();
4658 // Ñîçäàåì áîòîâ:
4659   for i := nPl+1 to nPlayers do
4660     g_Player_Create(STD_PLAYER_MODEL, _RGB(0, 0, 0), 0, True);
4661 end;
4663 procedure g_Game_StartServer(Map: String; GameMode: Byte;
4664                              TimeLimit, GoalLimit: Word; MaxLives: Byte;
4665                              Options: LongWord; nPlayers: Byte;
4666                              IPAddr: LongWord; Port: Word);
4667 begin
4668   g_Game_Free();
4669   g_Net_Slist_ServerClosed();
4671   e_WriteLog('Starting net game (server)...', TMsgType.Notify);
4673   g_Game_ClearLoading();
4675 // Íàñòðîéêè èãðû:
4676   gGameSettings.GameType := GT_SERVER;
4677   gGameSettings.GameMode := GameMode;
4678   gSwitchGameMode := GameMode;
4679   gGameSettings.TimeLimit := TimeLimit;
4680   gGameSettings.GoalLimit := GoalLimit;
4681   gGameSettings.MaxLives := IfThen(GameMode = GM_CTF, 0, MaxLives);
4682   gGameSettings.Options := Options;
4684   gCoopTotalMonstersKilled := 0;
4685   gCoopTotalSecretsFound := 0;
4686   gCoopTotalMonsters := 0;
4687   gCoopTotalSecrets := 0;
4688   gAimLine := False;
4689   gShowMap := False;
4691   gLMSRespawn := LMS_RESPAWN_NONE;
4692   gLMSRespawnTime := 0;
4693   gSpectLatchPID1 := 0;
4694   gSpectLatchPID2 := 0;
4696   g_Game_ExecuteEvent('ongamestart');
4698 // Óñòàíîâêà ðàçìåðîâ îêíà èãðîêà
4699   g_Game_SetupScreenSize();
4701 // Ðåæèì íàáëþäàòåëÿ:
4702   if nPlayers = 0 then
4703   begin
4704     gPlayer1 := nil;
4705     gPlayer2 := nil;
4706   end;
4708   if nPlayers >= 1 then
4709   begin
4710   // Ñîçäàíèå ïåðâîãî èãðîêà:
4711     gPlayer1 := g_Player_Get(g_Player_Create(gPlayer1Settings.Model,
4712                                              gPlayer1Settings.Color,
4713                                              gPlayer1Settings.Team, False));
4714     if gPlayer1 = nil then
4715     begin
4716       g_FatalError(Format(_lc[I_GAME_ERROR_PLAYER_CREATE], [1]));
4717       Exit;
4718     end;
4720     gPlayer1.Name := gPlayer1Settings.Name;
4721     gPlayer1.WeapSwitchMode := gPlayer1Settings.WeaponSwitch;
4722     gPlayer1.setWeaponPrefs(gPlayer1Settings.WeaponPreferences);
4723     gPlayer1.SwitchToEmpty := gPlayer1Settings.SwitchToEmpty;
4724     gPlayer1.SkipFist := gPlayer1Settings.SkipFist;
4725   end;
4727   if nPlayers >= 2 then
4728   begin
4729   // Ñîçäàíèå âòîðîãî èãðîêà:
4730     gPlayer2 := g_Player_Get(g_Player_Create(gPlayer2Settings.Model,
4731                                              gPlayer2Settings.Color,
4732                                              gPlayer2Settings.Team, False));
4733     if gPlayer2 = nil then
4734     begin
4735       g_FatalError(Format(_lc[I_GAME_ERROR_PLAYER_CREATE], [2]));
4736       Exit;
4737     end;
4739     gPlayer2.Name := gPlayer2Settings.Name;
4740     gPlayer2.WeapSwitchMode := gPlayer2Settings.WeaponSwitch;
4741     gPlayer2.setWeaponPrefs(gPlayer2Settings.WeaponPreferences);
4742     gPlayer2.SwitchToEmpty := gPlayer2Settings.SwitchToEmpty;
4743     gPlayer2.SkipFist := gPlayer2Settings.SkipFist;
4744   end;
4746   g_Game_SetLoadingText(_lc[I_LOAD_HOST], 0, False);
4747   if NetForwardPorts then
4748     g_Game_SetLoadingText(_lc[I_LOAD_PORTS], 0, False);
4750 // Ñòàðòóåì ñåðâåð
4751   if not g_Net_Host(IPAddr, Port, NetMaxClients) then
4752   begin
4753     g_FatalError(_lc[I_NET_MSG] + Format(_lc[I_NET_ERR_HOST], [Port]));
4754     Exit;
4755   end;
4757   g_Net_Slist_Set(NetMasterList);
4759   g_Net_Slist_ServerStarted();
4761 // Çàãðóçêà è çàïóñê êàðòû:
4762   if not g_Game_StartMap(false{asMegawad}, Map, True) then
4763   begin
4764     g_Net_Slist_ServerClosed();
4765     g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Map]));
4766     Exit;
4767   end;
4769 // Íåò òî÷åê ïîÿâëåíèÿ:
4770   if (g_Map_GetPointCount(RESPAWNPOINT_PLAYER1) +
4771       g_Map_GetPointCount(RESPAWNPOINT_PLAYER2) +
4772       g_Map_GetPointCount(RESPAWNPOINT_DM) +
4773       g_Map_GetPointCount(RESPAWNPOINT_RED)+
4774       g_Map_GetPointCount(RESPAWNPOINT_BLUE)) < 1 then
4775   begin
4776     g_Net_Slist_ServerClosed();
4777     g_FatalError(_lc[I_GAME_ERROR_GET_SPAWN]);
4778     Exit;
4779   end;
4781 // Íàñòðîéêè èãðîêîâ è áîòîâ:
4782   g_Player_Init();
4784   g_Net_Slist_ServerMapStarted();
4785   NetState := NET_STATE_GAME;
4786 end;
4788 procedure g_Game_StartClient(Addr: String; Port: Word; PW: String);
4790   Map: String;
4791   WadName: string;
4792   Ptr: Pointer;
4793   T: Cardinal;
4794   MID: Byte;
4795   State: Byte;
4796   OuterLoop: Boolean;
4797   newResPath: string;
4798   InMsg: TMsg;
4799 begin
4800   g_Game_Free();
4802   State := 0;
4803   e_WriteLog('Starting net game (client)...', TMsgType.Notify);
4804   e_WriteLog('NET: Trying to connect to ' + Addr + ':' + IntToStr(Port) + '...', TMsgType.Notify);
4806   g_Game_ClearLoading();
4808 // Íàñòðîéêè èãðû:
4809   gGameSettings.GameType := GT_CLIENT;
4811   gCoopTotalMonstersKilled := 0;
4812   gCoopTotalSecretsFound := 0;
4813   gCoopTotalMonsters := 0;
4814   gCoopTotalSecrets := 0;
4815   gAimLine := False;
4816   gShowMap := False;
4818   g_Game_ExecuteEvent('ongamestart');
4820 // Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ:
4821   g_Game_SetupScreenSize();
4823   NetState := NET_STATE_AUTH;
4825   g_Game_SetLoadingText(_lc[I_LOAD_CONNECT], 0, False);
4827   // create (or update) map/resource databases
4828   g_Res_CreateDatabases(true);
4830   gLMSRespawn := LMS_RESPAWN_NONE;
4831   gLMSRespawnTime := 0;
4832   gSpectLatchPID1 := 0;
4833   gSpectLatchPID2 := 0;
4835 // Ñòàðòóåì êëèåíò
4836   if not g_Net_Connect(Addr, Port) then
4837   begin
4838     g_FatalError(_lc[I_NET_MSG] + _lc[I_NET_ERR_CONN]);
4839     NetState := NET_STATE_NONE;
4840     Exit;
4841   end;
4843   g_Game_SetLoadingText(_lc[I_LOAD_SEND_INFO], 0, False);
4844   MC_SEND_Info(PW);
4845   g_Game_SetLoadingText(_lc[I_LOAD_WAIT_INFO], 0, False);
4847   OuterLoop := True;
4848   while OuterLoop do
4849   begin
4850     // fuck! https://www.mail-archive.com/enet-discuss@cubik.org/msg00852.html
4851     // tl;dr: on shitdows, we can get -1 sometimes, and it is *NOT* a failure.
4852     //        thank you, enet. let's ignore failures altogether then.
4853     while (enet_host_service(NetHost, @NetEvent, 50) > 0) do
4854     begin
4855       if (NetEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
4856       begin
4857         if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then
4858         begin
4859           // ignore all download packets, they're processed by separate code
4860           enet_packet_destroy(NetEvent.packet);
4861           continue;
4862         end;
4863         Ptr := NetEvent.packet^.data;
4864         if not InMsg.Init(Ptr, NetEvent.packet^.dataLength, True) then
4865         begin
4866           enet_packet_destroy(NetEvent.packet);
4867           continue;
4868         end;
4870         InMsg.ReadLongWord(); // skip size
4871         MID := InMsg.ReadByte();
4873         if (MID = NET_MSG_INFO) and (State = 0) then
4874         begin
4875           NetMyID := InMsg.ReadByte();
4876           NetPlrUID1 := InMsg.ReadWord();
4878           WadName := InMsg.ReadString();
4879           Map := InMsg.ReadString();
4881           gWADHash := InMsg.ReadMD5();
4883           gGameSettings.GameMode := InMsg.ReadByte();
4884           gSwitchGameMode := gGameSettings.GameMode;
4885           gGameSettings.GoalLimit := InMsg.ReadWord();
4886           gGameSettings.TimeLimit := InMsg.ReadWord();
4887           gGameSettings.MaxLives := InMsg.ReadByte();
4888           gGameSettings.Options := InMsg.ReadLongWord();
4889           T := InMsg.ReadLongWord();
4891           //newResPath := g_Res_SearchSameWAD(MapsDir, WadName, gWADHash);
4892           //if newResPath = '' then
4893           begin
4894             //g_Game_SetLoadingText(_lc[I_LOAD_DL_RES], 0, False);
4895             newResPath := g_Res_DownloadMapWAD(ExtractFileName(WadName), gWADHash);
4896             if newResPath = '' then
4897             begin
4898               g_FatalError(_lc[I_NET_ERR_HASH]);
4899               enet_packet_destroy(NetEvent.packet);
4900               NetState := NET_STATE_NONE;
4901               Exit;
4902             end;
4903             e_LogWritefln('using downloaded map wad [%s] for [%s]`', [newResPath, WadName], TMsgType.Notify);
4904           end;
4905           //newResPath := ExtractRelativePath(MapsDir, newResPath);
4908           gPlayer1 := g_Player_Get(g_Player_Create(gPlayer1Settings.Model,
4909                                                    gPlayer1Settings.Color,
4910                                                    gPlayer1Settings.Team, False));
4912           if gPlayer1 = nil then
4913           begin
4914             g_FatalError(Format(_lc[I_GAME_ERROR_PLAYER_CREATE], [1]));
4916             enet_packet_destroy(NetEvent.packet);
4917             NetState := NET_STATE_NONE;
4918             Exit;
4919           end;
4921           gPlayer1.Name := gPlayer1Settings.Name;
4922           gPlayer1.WeapSwitchMode := gPlayer1Settings.WeaponSwitch;
4923           gPlayer1.setWeaponPrefs(gPlayer1Settings.WeaponPreferences);
4924           gPlayer1.SwitchToEmpty := gPlayer1Settings.SwitchToEmpty;
4925           gPlayer1.SkipFist := gPlayer1Settings.SkipFist;
4926           gPlayer1.UID := NetPlrUID1;
4927           gPlayer1.Reset(True);
4929           if not g_Game_StartMap(false{asMegawad}, newResPath + ':\' + Map, True) then
4930           begin
4931             g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [WadName + ':\' + Map]));
4933             enet_packet_destroy(NetEvent.packet);
4934             NetState := NET_STATE_NONE;
4935             Exit;
4936           end;
4938           gTime := T;
4940           State := 1;
4941           OuterLoop := False;
4942           enet_packet_destroy(NetEvent.packet);
4943           break;
4944         end
4945         else
4946           enet_packet_destroy(NetEvent.packet);
4947       end
4948       else
4949       begin
4950         if (NetEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
4951         begin
4952           State := 0;
4953           if (NetEvent.data <= NET_DISC_MAX) then
4954             g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' +
4955             _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + NetEvent.data)], True);
4956           OuterLoop := False;
4957           Break;
4958         end;
4959       end;
4960     end;
4962     ProcessLoading(true);
4964     if g_Net_UserRequestExit() then
4965     begin
4966       State := 0;
4967       break;
4968     end;
4969   end;
4971   if State <> 1 then
4972   begin
4973     g_FatalError(_lc[I_NET_MSG] + _lc[I_NET_ERR_CONN]);
4974     NetState := NET_STATE_NONE;
4975     Exit;
4976   end;
4978   g_Player_Init();
4979   NetState := NET_STATE_GAME;
4980   MC_SEND_FullStateRequest;
4981   e_WriteLog('NET: Connection successful.', TMsgType.Notify);
4982 end;
4985   lastAsMegaWad: Boolean = false;
4987 procedure g_Game_ChangeMap(const MapPath: String);
4989   Force: Boolean;
4990 begin
4991   g_Game_ClearLoading();
4993   Force := gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF];
4994   // Åñëè óðîâåíü çàâåðøèëñÿ ïî òðèããåðó Âûõîä, íå î÷èùàòü èíâåíòàðü
4995   if gExitByTrigger then
4996   begin
4997     Force := False;
4998     gExitByTrigger := False;
4999   end;
5000   if not g_Game_StartMap(lastAsMegaWad, MapPath, Force) then
5001     g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [MapPath]));
5002 end;
5004 procedure g_Game_Restart();
5006   Map: string;
5007 begin
5008   if g_Game_IsClient then
5009     Exit;
5010   map := g_ExtractFileName(gMapInfo.Map);
5011   e_LogWritefln('g_Game_Restart: map = "%s" gCurrentMapFileName = "%s"', [map, gCurrentMapFileName]);
5013   MessageTime := 0;
5014   gGameOn := False;
5015   g_Game_ClearLoading();
5016   g_Game_StartMap(lastAsMegaWad, Map, True, gCurrentMapFileName);
5017 end;
5019 function g_Game_StartMap (asMegawad: Boolean; Map: String; Force: Boolean = False; const oldMapPath: AnsiString=''): Boolean;
5021   NewWAD, ResName: String;
5022   I: Integer;
5023   nws: AnsiString;
5024 begin
5025   g_Map_Free((Map <> gCurrentMapFileName) and (oldMapPath <> gCurrentMapFileName));
5026   g_Player_RemoveAllCorpses();
5028   if (not g_Game_IsClient) and
5029      (gSwitchGameMode <> gGameSettings.GameMode) and
5030      (gGameSettings.GameMode <> GM_SINGLE) then
5031   begin
5032     if gSwitchGameMode = GM_CTF then
5033       gGameSettings.MaxLives := 0;
5034     gGameSettings.GameMode := gSwitchGameMode;
5035     Force := True;
5036   end else
5037     gSwitchGameMode := gGameSettings.GameMode;
5039   g_Player_ResetTeams();
5041   lastAsMegaWad := asMegawad;
5042   if isWadPath(Map) then
5043   begin
5044     NewWAD := g_ExtractWadName(Map);
5045     ResName := g_ExtractFileName(Map);
5046     if g_Game_IsServer then
5047     begin
5048       nws := findDiskWad(NewWAD);
5049       //writeln('000: Map=[', Map, ']; nws=[', nws, ']; NewWAD=[', NewWAD, ']');
5050       if (asMegawad) then
5051       begin
5052         if (length(nws) = 0) then nws := e_FindWad(MegawadDirs, NewWAD);
5053         if (length(nws) = 0) then nws := e_FindWad(MapDirs, NewWAD);
5054       end
5055       else
5056       begin
5057         if (length(nws) = 0) then nws := e_FindWad(MapDirs, NewWAD);
5058         if (length(nws) = 0) then nws := e_FindWad(MegawadDirs, NewWAD);
5059       end;
5060       //if (length(nws) = 0) then nws := e_FindWad(MapDownloadDirs, NewWAD);
5061       //writeln('001: Map=[', Map, ']; nws=[', nws, ']; NewWAD=[', NewWAD, ']');
5062       //nws := NewWAD;
5063       if (length(nws) = 0) then
5064       begin
5065         ResName := ''; // failed
5066       end
5067       else
5068       begin
5069         NewWAD := nws;
5070         if (g_Game_IsNet) then gWADHash := MD5File(nws);
5071         //writeln('********: nws=', nws, ' : Map=', Map, ' : nw=', NewWAD, ' : resname=', ResName);
5072         g_Game_LoadWAD(NewWAD);
5073       end;
5074     end
5075     else
5076     begin
5077       // hash received in MC_RECV_GameEvent -> NET_EV_MAPSTART
5078       NewWAD := g_Game_ClientWAD(NewWAD, gWADHash);
5079     end;
5080   end
5081   else
5082   begin
5083     NewWAD := gGameSettings.WAD;
5084     ResName := Map;
5085   end;
5087   gTime := 0;
5089   //writeln('********: gsw=', gGameSettings.WAD, '; rn=', ResName);
5090   result := false;
5091   if (ResName <> '') and (NewWAD <> '') then
5092   begin
5093     //result := g_Map_Load(gGameSettings.WAD + ':\' + ResName);
5094     result := g_Map_Load(NewWAD+':\'+ResName);
5095   end;
5096   if Result then
5097     begin
5098       g_Player_ResetAll(Force or gLastMap, gGameSettings.GameType = GT_SINGLE);
5100       gState := STATE_NONE;
5101       g_ActiveWindow := nil;
5102       gGameOn := True;
5104       DisableCheats();
5105       ResetTimer();
5107       if gGameSettings.GameMode = GM_CTF then
5108       begin
5109         g_Map_ResetFlag(FLAG_RED);
5110         g_Map_ResetFlag(FLAG_BLUE);
5111         // CTF, à ôëàãîâ íåò:
5112         if not g_Map_HaveFlagPoints() then
5113           g_SimpleError(_lc[I_GAME_ERROR_CTF]);
5114       end;
5115     end
5116   else
5117     begin
5118       gState := STATE_MENU;
5119       gGameOn := False;
5120     end;
5122   gExit := 0;
5123   gPauseMain := false;
5124   gPauseHolmes := false;
5125   NetTimeToUpdate := 1;
5126   NetTimeToReliable := 0;
5127   NetTimeToMaster := NetMasterRate;
5128   gSpectLatchPID1 := 0;
5129   gSpectLatchPID2 := 0;
5130   gMissionFailed := False;
5131   gNextMap := '';
5133   gCoopMonstersKilled := 0;
5134   gCoopSecretsFound := 0;
5136   gVoteInProgress := False;
5137   gVotePassed := False;
5138   gVoteCount := 0;
5139   gVoted := False;
5141   gStatsOff := False;
5143   if not gGameOn then Exit;
5145   g_Game_SpectateCenterView();
5147   if g_Game_IsServer then
5148   begin
5149     if (gGameSettings.MaxLives > 0) and (gGameSettings.WarmupTime > 0) then
5150     begin
5151       gLMSRespawn := LMS_RESPAWN_WARMUP;
5152       gLMSRespawnTime := gTime + gGameSettings.WarmupTime*1000;
5153       gLMSSoftSpawn := True;
5154       if g_Game_IsNet then
5155         MH_SEND_GameEvent(NET_EV_LMS_WARMUP, gLMSRespawnTime - gTime);
5156     end
5157     else
5158     begin
5159       gLMSRespawn := LMS_RESPAWN_NONE;
5160       gLMSRespawnTime := 0;
5161     end;
5162   end;
5164   if NetMode = NET_SERVER then
5165   begin
5166     MH_SEND_GameEvent(NET_EV_MAPSTART, gGameSettings.GameMode, Map);
5168   // Ìàñòåðñåðâåð
5169     g_Net_Slist_ServerMapStarted();
5171     if NetClients <> nil then
5172       for I := 0 to High(NetClients) do
5173         if NetClients[I].Used then
5174         begin
5175           NetClients[I].Voted := False;
5176           if NetClients[I].RequestedFullUpdate then
5177           begin
5178             MH_SEND_Everything((NetClients[I].State = NET_STATE_AUTH), I);
5179             NetClients[I].RequestedFullUpdate := False;
5180           end;
5181         end;
5183     g_Net_UnbanNonPermHosts();
5184   end;
5186   if gLastMap then
5187   begin
5188     gCoopTotalMonstersKilled := 0;
5189     gCoopTotalSecretsFound := 0;
5190     gCoopTotalMonsters := 0;
5191     gCoopTotalSecrets := 0;
5192     gLastMap := False;
5193   end;
5195   g_Game_ExecuteEvent('onmapstart');
5196 end;
5198 procedure SetFirstLevel;
5199 begin
5200   gNextMap := '';
5202   MapList := g_Map_GetMapsList(gGameSettings.WAD);
5203   if MapList = nil then
5204     Exit;
5206   SortSArray(MapList);
5207   gNextMap := MapList[Low(MapList)];
5209   MapList := nil;
5210 end;
5212 procedure g_Game_ExitLevel(const Map: AnsiString);
5213 begin
5214   gNextMap := Map;
5216   gCoopTotalMonstersKilled := gCoopTotalMonstersKilled + gCoopMonstersKilled;
5217   gCoopTotalSecretsFound := gCoopTotalSecretsFound + gCoopSecretsFound;
5218   gCoopTotalMonsters := gCoopTotalMonsters + gTotalMonsters;
5219   gCoopTotalSecrets := gCoopTotalSecrets + gSecretsCount;
5221 // Âûøëè â âûõîä â Îäèíî÷íîé èãðå:
5222   if gGameSettings.GameType = GT_SINGLE then
5223     gExit := EXIT_ENDLEVELSINGLE
5224   else // Âûøëè â âûõîä â Ñâîåé èãðå
5225   begin
5226     gExit := EXIT_ENDLEVELCUSTOM;
5227     if gGameSettings.GameMode = GM_COOP then
5228       g_Player_RememberAll;
5230     if not g_Map_Exist(gGameSettings.WAD + ':\' + gNextMap) then
5231     begin
5232       gLastMap := True;
5233       if gGameSettings.GameMode = GM_COOP then
5234         gStatsOff := True;
5236       gStatsPressed := True;
5237       gNextMap := 'MAP01';
5239       if not g_Map_Exist(gGameSettings.WAD + ':\' + gNextMap) then
5240         g_Game_NextLevel;
5242       if g_Game_IsNet then
5243       begin
5244         MH_SEND_GameStats();
5245         MH_SEND_CoopStats();
5246       end;
5247     end;
5248   end;
5249 end;
5251 procedure g_Game_RestartLevel();
5253   Map: string;
5254 begin
5255   if gGameSettings.GameMode = GM_SINGLE then
5256   begin
5257     g_Game_Restart();
5258     Exit;
5259   end;
5260   gExit := EXIT_ENDLEVELCUSTOM;
5261   Map := g_ExtractFileName(gMapInfo.Map);
5262   gNextMap := Map;
5263 end;
5265 function g_Game_ClientWAD (NewWAD: String; const WHash: TMD5Digest): AnsiString;
5267   gWAD{, xwad}: String;
5268 begin
5269   result := NewWAD;
5270   if not g_Game_IsClient then Exit;
5271   //e_LogWritefln('*** g_Game_ClientWAD: `%s`', [NewWAD]);
5273   gWAD := g_Res_DownloadMapWAD(ExtractFileName(NewWAD), WHash);
5274   if gWAD = '' then
5275   begin
5276     result := '';
5277     g_Game_Free();
5278     g_FatalError(Format(_lc[I_GAME_ERROR_MAP_WAD], [ExtractFileName(NewWAD)]));
5279     Exit;
5280   end;
5282   e_LogWritefln('using downloaded client map wad [%s] for [%s]', [gWAD, NewWAD], TMsgType.Notify);
5283   NewWAD := gWAD;
5285   g_Game_LoadWAD(NewWAD);
5286   result := NewWAD;
5288   {
5289   if LowerCase(NewWAD) = LowerCase(gGameSettings.WAD) then Exit;
5290   gWAD := g_Res_SearchSameWAD(MapsDir, ExtractFileName(NewWAD), WHash);
5291   if gWAD = '' then
5292   begin
5293     g_Game_SetLoadingText(_lc[I_LOAD_DL_RES], 0, False);
5294     gWAD := g_Res_DownloadMapWAD(ExtractFileName(NewWAD), WHash);
5295     if gWAD = '' then
5296     begin
5297       g_Game_Free();
5298       g_FatalError(Format(_lc[I_GAME_ERROR_MAP_WAD], [ExtractFileName(NewWAD)]));
5299       Exit;
5300     end;
5301   end;
5302   NewWAD := ExtractRelativePath(MapsDir, gWAD);
5303   g_Game_LoadWAD(NewWAD);
5304   }
5305 end;
5307 procedure g_Game_RestartRound(NoMapRestart: Boolean = False);
5309   i, n, nb, nr: Integer;
5310 begin
5311   if not g_Game_IsServer then Exit;
5312   if gLMSRespawn = LMS_RESPAWN_NONE then Exit;
5313   gLMSRespawn := LMS_RESPAWN_NONE;
5314   gLMSRespawnTime := 0;
5315   MessageTime := 0;
5317   if (gGameSettings.GameMode = GM_COOP) and not NoMapRestart then
5318   begin
5319     gMissionFailed := True;
5320     g_Game_RestartLevel;
5321     Exit;
5322   end;
5324   n := 0; nb := 0; nr := 0;
5325   for i := Low(gPlayers) to High(gPlayers) do
5326     if (gPlayers[i] <> nil) and
5327        ((not gPlayers[i].FSpectator) or gPlayers[i].FWantsInGame or
5328         (gPlayers[i] is TBot)) then
5329       begin
5330         Inc(n);
5331         if gPlayers[i].Team = TEAM_RED then Inc(nr)
5332         else if gPlayers[i].Team = TEAM_BLUE then Inc(nb)
5333       end;
5335   if (n < 1) or ((gGameSettings.GameMode = GM_TDM) and ((nr = 0) or (nb = 0))) then
5336   begin
5337     // wait a second until the fuckers finally decide to join
5338     gLMSRespawn := LMS_RESPAWN_WARMUP;
5339     gLMSRespawnTime := gTime + gGameSettings.WarmupTime*1000;
5340     gLMSSoftSpawn := NoMapRestart;
5341     if g_Game_IsNet then
5342       MH_SEND_GameEvent(NET_EV_LMS_WARMUP, gLMSRespawnTime - gTime);
5343     Exit;
5344   end;
5346   g_Player_RemoveAllCorpses;
5347   g_Game_Message(_lc[I_MESSAGE_LMS_START], 144);
5348   if g_Game_IsNet then
5349     MH_SEND_GameEvent(NET_EV_LMS_START);
5351   for i := Low(gPlayers) to High(gPlayers) do
5352   begin
5353     if gPlayers[i] = nil then continue;
5354     if gPlayers[i] is TBot then gPlayers[i].FWantsInGame := True;
5355     // don't touch normal spectators
5356     if gPlayers[i].FSpectator and not gPlayers[i].FWantsInGame then
5357     begin
5358       gPlayers[i].FNoRespawn := True;
5359       gPlayers[i].Lives := 0;
5360       if g_Game_IsNet then
5361         MH_SEND_PlayerStats(gPlayers[I].UID);
5362       continue;
5363     end;
5364     gPlayers[i].FNoRespawn := False;
5365     gPlayers[i].Lives := gGameSettings.MaxLives;
5366     gPlayers[i].Respawn(False, True);
5367     if gGameSettings.GameMode = GM_COOP then
5368     begin
5369       gPlayers[i].Frags := 0;
5370       gPlayers[i].RecallState;
5371     end;
5372     if (gPlayer1 = nil) and (gSpectLatchPID1 > 0) then
5373       gPlayer1 := g_Player_Get(gSpectLatchPID1);
5374     if (gPlayer2 = nil) and (gSpectLatchPID2 > 0) then
5375       gPlayer2 := g_Player_Get(gSpectLatchPID2);
5376   end;
5378   g_Items_RestartRound();
5380   gLMSSoftSpawn := False;
5381 end;
5383 function g_Game_GetFirstMap(WAD: String): String;
5384 begin
5385   Result := '';
5387   MapList := g_Map_GetMapsList(WAD);
5388   if MapList = nil then
5389     Exit;
5391   SortSArray(MapList);
5392   Result := MapList[Low(MapList)];
5394   if not g_Map_Exist(WAD + ':\' + Result) then
5395     Result := '';
5397   MapList := nil;
5398 end;
5400 function g_Game_GetNextMap(): String;
5402   I: Integer;
5403   Map: string;
5404 begin
5405   Result := '';
5407   MapList := g_Map_GetMapsList(gGameSettings.WAD);
5408   if MapList = nil then
5409     Exit;
5411   Map := g_ExtractFileName(gMapInfo.Map);
5413   SortSArray(MapList);
5414   MapIndex := -255;
5415   for I := Low(MapList) to High(MapList) do
5416     if Map = MapList[I] then
5417     begin
5418       MapIndex := I;
5419       Break;
5420     end;
5422   if MapIndex <> -255 then
5423   begin
5424     if MapIndex = High(MapList) then
5425      Result := MapList[Low(MapList)]
5426     else
5427       Result := MapList[MapIndex + 1];
5429     if not g_Map_Exist(gGameSettings.WAD + ':\' + Result) then Result := Map;
5430   end;
5432   MapList := nil;
5433 end;
5435 procedure g_Game_NextLevel();
5436 begin
5437   if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF, GM_COOP] then
5438     gExit := EXIT_ENDLEVELCUSTOM
5439   else
5440   begin
5441     gExit := EXIT_ENDLEVELSINGLE;
5442     Exit;
5443   end;
5445   if gNextMap <> '' then Exit;
5446   gNextMap := g_Game_GetNextMap();
5447 end;
5449 function g_Game_IsTestMap(): Boolean;
5450 begin
5451   result := StrEquCI1251(TEST_MAP_NAME, g_ExtractFileName(gMapInfo.Map));
5452 end;
5454 procedure g_Game_DeleteTestMap();
5456   a: Integer;
5457   //MapName: AnsiString;
5458   WadName: string;
5460   WAD: TWADFile;
5461   MapList: SSArray;
5462   time: Integer;
5464 begin
5465   a := Pos('.wad:\', toLowerCase1251(gMapToDelete));
5466   if (a = 0) then a := Pos('.wad:/', toLowerCase1251(gMapToDelete));
5467   if (a = 0) then exit;
5469   // Âûäåëÿåì èìÿ wad-ôàéëà è èìÿ êàðòû
5470   WadName := Copy(gMapToDelete, 1, a+3);
5471   Delete(gMapToDelete, 1, a+5);
5472   gMapToDelete := UpperCase(gMapToDelete);
5473   //MapName := '';
5474   //CopyMemory(@MapName[0], @gMapToDelete[1], Min(16, Length(gMapToDelete)));
5477 // Èìÿ êàðòû íå ñòàíäàðòíîå òåñòîâîå:
5478   if MapName <> TEST_MAP_NAME then
5479     Exit;
5481   if not gTempDelete then
5482   begin
5483     time := g_GetFileTime(WadName);
5484     WAD := TWADFile.Create();
5486   // ×èòàåì Wad-ôàéë:
5487     if not WAD.ReadFile(WadName) then
5488     begin // Íåò òàêîãî WAD-ôàéëà
5489       WAD.Free();
5490       Exit;
5491     end;
5493   // Ñîñòàâëÿåì ñïèñîê êàðò è èùåì íóæíóþ:
5494     WAD.CreateImage();
5495     MapList := WAD.GetResourcesList('');
5497     if MapList <> nil then
5498       for a := 0 to High(MapList) do
5499         if MapList[a] = MapName then
5500         begin
5501         // Óäàëÿåì è ñîõðàíÿåì:
5502           WAD.RemoveResource('', MapName);
5503           WAD.SaveTo(WadName);
5504           Break;
5505         end;
5507     WAD.Free();
5508     g_SetFileTime(WadName, time);
5509   end else
5511   if gTempDelete then DeleteFile(WadName);
5512 end;
5514 procedure GameCVars(P: SSArray);
5516   a, b: Integer;
5517   stat: TPlayerStatArray;
5518   cmd: string;
5520   procedure ParseGameFlag(Flag: LongWord; OffMsg, OnMsg: TStrings_Locale; OnMapChange: Boolean = False);
5521   var
5522     x: Boolean;
5523   begin
5524     if Length(P) > 1 then
5525     begin
5526       x := P[1] = '1';
5528       if x then
5529         gsGameFlags := gsGameFlags or Flag
5530       else
5531         gsGameFlags := gsGameFlags and (not Flag);
5533       if g_Game_IsServer then
5534       begin
5535         if x then
5536           gGameSettings.Options := gGameSettings.Options or Flag
5537         else
5538           gGameSettings.Options := gGameSettings.Options and (not Flag);
5539         if g_Game_IsNet then MH_SEND_GameSettings;
5540       end;
5541     end;
5543     if LongBool(gsGameFlags and Flag) then
5544       g_Console_Add(_lc[OnMsg])
5545     else
5546       g_Console_Add(_lc[OffMsg]);
5548     if OnMapChange and g_Game_IsServer then
5549       g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
5550   end;
5552 begin
5553   stat := nil;
5554   cmd := LowerCase(P[0]);
5556   if cmd = 'g_gamemode' then
5557   begin
5558     if (Length(P) > 1) then
5559     begin
5560       a := g_Game_TextToMode(P[1]);
5561       if a = GM_SINGLE then a := GM_COOP;
5562       gsGameMode := g_Game_ModeToText(a);
5563       if g_Game_IsServer then
5564       begin
5565         gSwitchGameMode := a;
5566         if (gGameOn and (gGameSettings.GameMode = GM_SINGLE)) or
5567            (gState = STATE_INTERSINGLE) then
5568           gSwitchGameMode := GM_SINGLE;
5569         if not gGameOn then
5570           gGameSettings.GameMode := gSwitchGameMode;
5571       end;
5572     end;
5574     if gSwitchGameMode = gGameSettings.GameMode then
5575       g_Console_Add(Format(_lc[I_MSG_GAMEMODE_CURRENT],
5576                           [g_Game_ModeToText(gGameSettings.GameMode)]))
5577     else
5578       g_Console_Add(Format(_lc[I_MSG_GAMEMODE_CHANGE],
5579                           [g_Game_ModeToText(gGameSettings.GameMode),
5580                            g_Game_ModeToText(gSwitchGameMode)]));
5581   end
5582   else if cmd = 'g_friendlyfire' then
5583   begin
5584     ParseGameFlag(GAME_OPTION_TEAMDAMAGE, I_MSG_FRIENDLY_FIRE_OFF, I_MSG_FRIENDLY_FIRE_ON);
5585   end
5586   else if cmd = 'g_friendly_absorb_damage' then
5587   begin
5588     ParseGameFlag(GAME_OPTION_TEAMABSORBDAMAGE, I_MSG_FRIENDLY_ABSORB_DAMAGE_OFF, I_MSG_FRIENDLY_ABSORB_DAMAGE_ON);
5589   end
5590   else if cmd = 'g_friendly_hit_trace' then
5591   begin
5592     ParseGameFlag(GAME_OPTION_TEAMHITTRACE, I_MSG_FRIENDLY_HIT_TRACE_OFF, I_MSG_FRIENDLY_HIT_TRACE_ON);
5593   end
5594   else if cmd = 'g_friendly_hit_projectile' then
5595   begin
5596     ParseGameFlag(GAME_OPTION_TEAMHITPROJECTILE, I_MSG_FRIENDLY_PROJECT_TRACE_OFF, I_MSG_FRIENDLY_PROJECT_TRACE_ON);
5597   end
5598   else if cmd = 'g_weaponstay' then
5599   begin
5600     ParseGameFlag(GAME_OPTION_WEAPONSTAY, I_MSG_WEAPONSTAY_OFF, I_MSG_WEAPONSTAY_ON);
5601   end
5602   else if cmd = 'g_allow_exit' then
5603   begin
5604     ParseGameFlag(GAME_OPTION_ALLOWEXIT, I_MSG_ALLOWEXIT_OFF, I_MSG_ALLOWEXIT_ON, True);
5605   end
5606   else if cmd = 'g_allow_monsters' then
5607   begin
5608     ParseGameFlag(GAME_OPTION_MONSTERS, I_MSG_ALLOWMON_OFF, I_MSG_ALLOWMON_ON, True);
5609   end
5610   else if cmd = 'g_allow_dropflag' then
5611   begin
5612     ParseGameFlag(GAME_OPTION_ALLOWDROPFLAG, I_MSG_ALLOWDROPFLAG_OFF, I_MSG_ALLOWDROPFLAG_ON);
5613   end
5614   else if cmd = 'g_throw_flag' then
5615   begin
5616     ParseGameFlag(GAME_OPTION_THROWFLAG, I_MSG_THROWFLAG_OFF, I_MSG_THROWFLAG_ON);
5617   end
5618   else if cmd = 'g_bot_vsplayers' then
5619   begin
5620     ParseGameFlag(GAME_OPTION_BOTVSPLAYER, I_MSG_BOTSVSPLAYERS_OFF, I_MSG_BOTSVSPLAYERS_ON);
5621   end
5622   else if cmd = 'g_bot_vsmonsters' then
5623   begin
5624     ParseGameFlag(GAME_OPTION_BOTVSMONSTER, I_MSG_BOTSVSMONSTERS_OFF, I_MSG_BOTSVSMONSTERS_ON);
5625   end
5626   else if cmd = 'g_dm_keys' then
5627   begin
5628     ParseGameFlag(GAME_OPTION_DMKEYS, I_MSG_DMKEYS_OFF, I_MSG_DMKEYS_ON, True);
5629   end
5630   else if cmd = 'g_gameflags' then
5631   begin
5632     if Length(P) > 1 then
5633     begin
5634       gsGameFlags := StrToDWordDef(P[1], gsGameFlags);
5635       if g_Game_IsServer then
5636       begin
5637         gGameSettings.Options := gsGameFlags;
5638         if g_Game_IsNet then MH_SEND_GameSettings;
5639       end;
5640     end;
5642     g_Console_Add(Format('%s %u', [cmd, gsGameFlags]));
5643   end
5644   else if cmd = 'g_warmup_time' then
5645   begin
5646     if Length(P) > 1 then
5647     begin
5648       gsWarmupTime := nclamp(StrToIntDef(P[1], gsWarmupTime), 0, $FFFF);
5649       if g_Game_IsServer then
5650       begin
5651         gGameSettings.WarmupTime := gsWarmupTime;
5652         // extend warmup if it's already going
5653         if gLMSRespawn = LMS_RESPAWN_WARMUP then
5654         begin
5655           gLMSRespawnTime := gTime + gsWarmupTime * 1000;
5656           if g_Game_IsNet then MH_SEND_GameEvent(NET_EV_LMS_WARMUP, gLMSRespawnTime - gTime);
5657         end;
5658         if g_Game_IsNet then MH_SEND_GameSettings;
5659       end;
5660     end;
5662     g_Console_Add(Format(_lc[I_MSG_WARMUP], [Integer(gsWarmupTime)]));
5663     if g_Game_IsServer then g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
5664   end
5665   else if cmd = 'g_spawn_invul' then
5666   begin
5667     if Length(P) > 1 then
5668     begin
5669       gsSpawnInvul := nclamp(StrToIntDef(P[1], gsSpawnInvul), 0, $FFFF);
5670       if g_Game_IsServer then
5671       begin
5672         gGameSettings.SpawnInvul := gsSpawnInvul;
5673         if g_Game_IsNet then MH_SEND_GameSettings;
5674       end;
5675     end;
5677     g_Console_Add(Format('%s %d', [cmd, Integer(gsSpawnInvul)]));
5678   end
5679   else if cmd = 'g_item_respawn_time' then
5680   begin
5681     if Length(P) > 1 then
5682     begin
5683       gsItemRespawnTime := nclamp(StrToIntDef(P[1], gsItemRespawnTime), 0, $FFFF);
5684       if g_Game_IsServer then
5685       begin
5686         gGameSettings.ItemRespawnTime := gsItemRespawnTime;
5687         if g_Game_IsNet then MH_SEND_GameSettings;
5688       end;
5689     end;
5691     g_Console_Add(Format('%s %d', [cmd, Integer(gsItemRespawnTime)]));
5692     if g_Game_IsServer then g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
5693   end
5694   else if cmd = 'sv_intertime' then
5695   begin
5696     if (Length(P) > 1) then
5697       gDefInterTime := Min(Max(StrToIntDef(P[1], gDefInterTime), -1), 120);
5699     g_Console_Add(cmd + ' = ' + IntToStr(gDefInterTime));
5700   end
5701   else if cmd = 'g_max_particles' then
5702   begin
5703     if Length(p) = 2 then
5704     begin
5705       a := Max(0, StrToIntDef(p[1], 0));
5706       g_GFX_SetMax(a)
5707     end
5708     else if Length(p) = 1 then
5709     begin
5710       e_LogWritefln('%s', [g_GFX_GetMax()])
5711     end
5712     else
5713     begin
5714       e_LogWritefln('usage: %s <n>', [cmd])
5715     end
5716   end
5717   else if cmd = 'g_max_shells' then
5718   begin
5719     if Length(p) = 2 then
5720     begin
5721       a := Max(0, StrToIntDef(p[1], 0));
5722       g_Shells_SetMax(a)
5723     end
5724     else if Length(p) = 1 then
5725     begin
5726       e_LogWritefln('%s', [g_Shells_GetMax()])
5727     end
5728     else
5729     begin
5730       e_LogWritefln('usage: %s <n>', [cmd])
5731     end
5732   end
5733   else if cmd = 'g_max_gibs' then
5734   begin
5735     if Length(p) = 2 then
5736     begin