DEADSOFTWARE

escape strings in stat files
[d2df-sdl.git] / src / game / g_game.pas
index 80f53ccc05e25ea145ac322c15bdc4898ff3d935..ef256d18c597441246b1fd81e115d67b259a1c77 100644 (file)
@@ -101,9 +101,8 @@ procedure g_Game_StartClient(Addr: String; Port: Word; PW: String);
 procedure g_Game_Restart();
 procedure g_Game_RestartLevel();
 procedure g_Game_RestartRound(NoMapRestart: Boolean = False);
-procedure g_Game_ClientWAD(NewWAD: String; const WHash: TMD5Digest);
-procedure g_Game_SaveOptions();
-function  g_Game_StartMap(Map: String; Force: Boolean = False; const oldMapPath: AnsiString=''): Boolean;
+function  g_Game_ClientWAD (NewWAD: String; const WHash: TMD5Digest): AnsiString;
+function  g_Game_StartMap(asMegawad: Boolean; Map: String; Force: Boolean = False; const oldMapPath: AnsiString=''): Boolean;
 procedure g_Game_ChangeMap(const MapPath: String);
 procedure g_Game_ExitLevel(const Map: AnsiString);
 function  g_Game_GetFirstMap(WAD: String): String;
@@ -126,7 +125,7 @@ procedure g_Game_Announce_KillCombo(Param: Integer);
 procedure g_Game_Announce_BodyKill(SpawnerUID: Word);
 procedure g_Game_StartVote(Command, Initiator: string);
 procedure g_Game_CheckVote;
-procedure g_TakeScreenShot();
+procedure g_TakeScreenShot(Filename: string = '');
 procedure g_FatalError(Text: String);
 procedure g_SimpleError(Text: String);
 function  g_Game_IsTestMap(): Boolean;
@@ -221,6 +220,8 @@ const
   DEFAULT_PLAYERS = 1;
 {$ENDIF}
 
+  STATFILE_VERSION = $03;
+
 var
   gStdFont: DWORD;
   gGameSettings: TGameSettings;
@@ -238,7 +239,7 @@ var
   gHearPoint1, gHearPoint2: THearPoint;
   gSoundEffectsDF: Boolean = False;
   gSoundTriggerTime: Word = 0;
-  gAnnouncer: Byte = ANNOUNCE_NONE;
+  gAnnouncer: Integer = ANNOUNCE_NONE;
   goodsnd: array[0..3] of TPlayableSound;
   killsnd: array[0..3] of TPlayableSound;
   hahasnd: array[0..2] of TPlayableSound;
@@ -287,12 +288,9 @@ var
   gMapToDelete: String;
   gTempDelete: Boolean = False;
   gLastMap: Boolean = False;
-  gWinPosX, gWinPosY: Integer;
   gWinSizeX, gWinSizeY: Integer;
-  gWinFrameX, gWinFrameY, gWinCaption: Integer;
-  gWinActive: Boolean = True; // by default window is active, lol
   gResolutionChange: Boolean = False;
-  gRC_Width, gRC_Height: Word;
+  gRC_Width, gRC_Height: Integer;
   gRC_FullScreen, gRC_Maximized: Boolean;
   gLanguageChange: Boolean = False;
   gDebugMode: Boolean = False;
@@ -377,11 +375,11 @@ uses
 {$IFDEF ENABLE_HOLMES}
   g_holmes,
 {$ENDIF}
-  e_texture, e_res, g_textures, g_main, g_window, g_menu,
+  e_texture, e_res, g_textures, g_window, g_menu,
   e_input, e_log, g_console, g_items, g_map, g_panel,
   g_playermodel, g_gfx, g_options, Math,
   g_triggers, g_monsters, e_sound, CONFIG,
-  g_language, g_net,
+  g_language, g_net, g_main,
   ENet, e_msg, g_netmsg, g_netmaster,
   sfs, wadreader, g_system;
 
@@ -585,6 +583,9 @@ var
   MapList: SSArray = nil;
   MapIndex: Integer = -1;
   InterReadyTime: Integer = -1;
+  StatShotDone: Boolean = False;
+  StatFilename: string = ''; // used by stat screenshot to save with the same name as the csv
+  StatDate: string = '';
   MegaWAD: record
     info: TMegaWADInfo;
     endpic: String;
@@ -643,6 +644,68 @@ begin
       end;
 end;
 
+// saves a shitty CSV containing the game stats passed to it
+procedure SaveGameStat(Stat: TEndCustomGameStat; Path: string);
+var 
+  s: TextFile;
+  dir, fname, map, mode, etime: String;
+  I: Integer;
+begin
+  try
+    dir := e_GetWriteableDir(StatsDirs);
+    // stats are placed in stats/yy/mm/dd/*.csv
+    fname := e_CatPath(dir, Path);
+    ForceDirectories(fname); // ensure yy/mm/dd exists within the stats dir
+    fname := e_CatPath(fname, StatFilename + '.csv');
+    AssignFile(s, fname);
+    try
+      Rewrite(s);
+      // line 1: stats ver, datetime, server name, map name, game mode, time limit, score limit, dmflags, game time, num players
+      if g_Game_IsNet then fname := NetServerName else fname := '';
+      map := g_ExtractWadNameNoPath(gMapInfo.Map) + ':/' + g_ExtractFileName(gMapInfo.Map);
+      mode := g_Game_ModeToText(Stat.GameMode);
+      etime := Format('%d:%.2d:%.2d', [
+        Stat.GameTime div 1000 div 3600,
+        (Stat.GameTime div 1000 div 60) mod 60,
+        Stat.GameTime div 1000 mod 60
+      ]);
+      WriteLn(s, 'stats_ver,datetime,server,map,mode,timelimit,scorelimit,dmflags,time,num_players');
+      WriteLn(s, Format('%d,%s,%s,%s,%s,%u,%u,%u,%s,%d', [
+        STATFILE_VERSION,
+        StatDate,
+        dquoteStr(fname),
+        dquoteStr(map),
+        mode,
+        gGameSettings.TimeLimit,
+        gGameSettings.GoalLimit,
+        gGameSettings.Options,
+        etime,
+        Length(Stat.PlayerStat)
+      ]));
+      // line 2: game specific shit
+      //   if it's a team game: red score, blue score
+      //   if it's a coop game: monsters killed, monsters total, secrets found, secrets total
+      //   otherwise nothing
+      if Stat.GameMode in [GM_TDM, GM_CTF] then
+        WriteLn(s, 
+          Format('red_score,blue_score' + LineEnding + '%d,%d', [Stat.TeamStat[TEAM_RED].Goals, Stat.TeamStat[TEAM_BLUE].Goals]))
+      else if Stat.GameMode in [GM_COOP, GM_SINGLE] then
+        WriteLn(s,
+          Format('mon_killed,mon_total,secrets_found,secrets_total' + LineEnding + '%d,%d,%d,%d',[gCoopMonstersKilled, gTotalMonsters, gCoopSecretsFound, gSecretsCount]));
+      // lines 3-...: team, player name, frags, deaths
+      WriteLn(s, 'team,name,frags,deaths');
+      for I := Low(Stat.PlayerStat) to High(Stat.PlayerStat) do
+        with Stat.PlayerStat[I] do
+          WriteLn(s, Format('%d,%s,%d,%d', [Team, dquoteStr(Name), Frags, Deaths]));
+    except
+      g_Console_Add(Format(_lc[I_CONSOLE_ERROR_WRITE], [fname]));
+    end;
+  except
+    g_Console_Add('could not create gamestats file "' + fname + '"');
+  end;
+  CloseFile(s);
+end;
+
 function g_Game_ModeToText(Mode: Byte): string;
 begin
   Result := '';
@@ -902,6 +965,7 @@ procedure EndGame();
 var
   a: Integer;
   FileName: string;
+  t: TDateTime;
 begin
   if g_Game_IsNet and g_Game_IsServer then
     MH_SEND_GameEvent(NET_EV_MAPEND, Byte(gMissionFailed));
@@ -997,6 +1061,18 @@ begin
             end;
 
           SortGameStat(CustomStat.PlayerStat);
+
+          if gSaveStats or gScreenshotStats then
+          begin
+            t := Now;
+            if g_Game_IsNet then StatFilename := NetServerName else StatFilename := 'local';
+            StatDate := FormatDateTime('yymmdd_hhnnss', t);
+            StatFilename := StatFilename + '_' + CustomStat.Map + '_' + g_Game_ModeToText(CustomStat.GameMode);
+            StatFilename := sanitizeFilename(StatFilename) + '_' + StatDate;
+            if gSaveStats then SaveGameStat(CustomStat, FormatDateTime('yyyy"/"mm"/"dd', t));
+          end;
+
+          StatShotDone := False;
         end;
 
         g_Game_ExecuteEvent('onmapend');
@@ -1727,7 +1803,7 @@ begin
             and (not gJustChatted) and (not gConsoleShow) and (not gChatShow)
             and (g_ActiveWindow = nil)
           )
-          or (g_Game_IsNet and ((gInterTime > gInterEndTime) or (gInterReadyCount >= NetClientCount)))
+          or (g_Game_IsNet and ((gInterTime > gInterEndTime) or ((gInterReadyCount >= NetClientCount) and (NetClientCount > 0))))
         )
         then
         begin // Íàæàëè <Enter>/<Ïðîáåë> èëè ïðîøëî äîñòàòî÷íî âðåìåíè:
@@ -2794,6 +2870,13 @@ begin
         _y := _y+24;
       end;
   end;
+
+  // HACK: take stats screenshot immediately after the first frame of the stats showing
+  if gScreenshotStats and not StatShotDone then
+  begin
+    g_TakeScreenShot('stats/' + StatFilename);
+    StatShotDone := True;
+  end;
 end;
 
 procedure DrawSingleStat();
@@ -4106,7 +4189,6 @@ procedure g_Game_Quit();
 begin
   g_Game_StopAllSounds(True);
   gMusic.Free();
-  g_Game_SaveOptions();
   g_Game_FreeData();
   g_PlayerModel_FreeData();
   g_Texture_DeleteAll();
@@ -4180,7 +4262,7 @@ end;
 
 procedure g_Game_ChangeResolution(newWidth, newHeight: Word; nowFull, nowMax: Boolean);
 begin
-  sys_SetDisplayMode(newWidth, newHeight, gBPP, nowFull);
+  sys_SetDisplayMode(newWidth, newHeight, gBPP, nowFull, nowMax);
 end;
 
 procedure g_Game_AddPlayer(Team: Byte = TEAM_NONE);
@@ -4364,7 +4446,7 @@ begin
   end;
 
 // Çàãðóçêà è çàïóñê êàðòû:
-  if not g_Game_StartMap(MAP, True) then
+  if not g_Game_StartMap(false{asMegawad}, MAP, True) then
   begin
     if (Pos(':\', Map) > 0) or (Pos(':/', Map) > 0) then tmps := Map else tmps := gGameSettings.WAD + ':\' + MAP;
     g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [tmps]));
@@ -4454,7 +4536,7 @@ begin
   end;
 
 // Çàãðóçêà è çàïóñê êàðòû:
-  if not g_Game_StartMap(Map, True) then
+  if not g_Game_StartMap(true{asMegawad}, Map, True) then
   begin
     g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Map]));
     Exit;
@@ -4565,7 +4647,7 @@ begin
   g_Net_Slist_ServerStarted();
 
 // Çàãðóçêà è çàïóñê êàðòû:
-  if not g_Game_StartMap(Map, True) then
+  if not g_Game_StartMap(false{asMegawad}, Map, True) then
   begin
     g_Net_Slist_ServerClosed();
     g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Map]));
@@ -4693,7 +4775,7 @@ begin
           //if newResPath = '' then
           begin
             //g_Game_SetLoadingText(_lc[I_LOAD_DL_RES], 0, False);
-            newResPath := g_Res_DownloadMapWAD(WadName, gWADHash);
+            newResPath := g_Res_DownloadMapWAD(ExtractFileName(WadName), gWADHash);
             if newResPath = '' then
             begin
               g_FatalError(_lc[I_NET_ERR_HASH]);
@@ -4704,7 +4786,7 @@ begin
             e_LogWritefln('using downloaded map wad [%s] for [%s]`', [newResPath, WadName], TMsgType.Notify);
           end;
           //newResPath := ExtractRelativePath(MapsDir, newResPath);
-          
+
 
           gPlayer1 := g_Player_Get(g_Player_Create(gPlayer1Settings.Model,
                                                    gPlayer1Settings.Color,
@@ -4723,7 +4805,7 @@ begin
           gPlayer1.UID := NetPlrUID1;
           gPlayer1.Reset(True);
 
-          if not g_Game_StartMap(newResPath + ':\' + Map, True) then
+          if not g_Game_StartMap(false{asMegawad}, newResPath + ':\' + Map, True) then
           begin
             g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [WadName + ':\' + Map]));
 
@@ -4781,15 +4863,8 @@ begin
   e_WriteLog('NET: Connection successful.', TMsgType.Notify);
 end;
 
-procedure g_Game_SaveOptions;
-  var s: AnsiString;
-begin
-  s := e_GetWriteableDir(ConfigDirs);
-  if s <> '' then
-    g_Options_Write_Video(s + '/' + CONFIG_FILENAME)
-  else
-    e_LogWritefln('unable to find or create directory for configs', []);
-end;
+var
+  lastAsMegaWad: Boolean = false;
 
 procedure g_Game_ChangeMap(const MapPath: String);
 var
@@ -4804,7 +4879,7 @@ begin
     Force := False;
     gExitByTrigger := False;
   end;
-  if not g_Game_StartMap(MapPath, Force) then
+  if not g_Game_StartMap(lastAsMegaWad, MapPath, Force) then
     g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [MapPath]));
 end;
 
@@ -4820,10 +4895,10 @@ begin
   MessageTime := 0;
   gGameOn := False;
   g_Game_ClearLoading();
-  g_Game_StartMap(Map, True, gCurrentMapFileName);
+  g_Game_StartMap(lastAsMegaWad, Map, True, gCurrentMapFileName);
 end;
 
-function g_Game_StartMap(Map: String; Force: Boolean = False; const oldMapPath: AnsiString=''): Boolean;
+function g_Game_StartMap (asMegawad: Boolean; Map: String; Force: Boolean = False; const oldMapPath: AnsiString=''): Boolean;
 var
   NewWAD, ResName: String;
   I: Integer;
@@ -4845,34 +4920,59 @@ begin
 
   g_Player_ResetTeams();
 
+  lastAsMegaWad := asMegawad;
   if isWadPath(Map) then
   begin
     NewWAD := g_ExtractWadName(Map);
     ResName := g_ExtractFileName(Map);
     if g_Game_IsServer then
     begin
-//     nws := e_FindWad(MapDirs, NewWAD);
-      nws := NewWAD;
+      nws := findDiskWad(NewWAD);
+      //writeln('000: Map=[', Map, ']; nws=[', nws, ']; NewWAD=[', NewWAD, ']');
+      if (asMegawad) then
+      begin
+        if (length(nws) = 0) then nws := e_FindWad(MegawadDirs, NewWAD);
+        if (length(nws) = 0) then nws := e_FindWad(MapDirs, NewWAD);
+      end
+      else
+      begin
+        if (length(nws) = 0) then nws := e_FindWad(MapDirs, NewWAD);
+        if (length(nws) = 0) then nws := e_FindWad(MegawadDirs, NewWAD);
+      end;
+      //if (length(nws) = 0) then nws := e_FindWad(MapDownloadDirs, NewWAD);
+      //writeln('001: Map=[', Map, ']; nws=[', nws, ']; NewWAD=[', NewWAD, ']');
+      //nws := NewWAD;
       if (length(nws) = 0) then
       begin
-        ResName := '';
+        ResName := ''; // failed
       end
       else
       begin
+        NewWAD := nws;
         if (g_Game_IsNet) then gWADHash := MD5File(nws);
         //writeln('********: nws=', nws, ' : Map=', Map, ' : nw=', NewWAD, ' : resname=', ResName);
         g_Game_LoadWAD(NewWAD);
       end;
-    end else
+    end
+    else
+    begin
       // hash received in MC_RECV_GameEvent -> NET_EV_MAPSTART
-      g_Game_ClientWAD(NewWAD, gWADHash);
-  end else
+      NewWAD := g_Game_ClientWAD(NewWAD, gWADHash);
+    end;
+  end
+  else
+  begin
+    NewWAD := gGameSettings.WAD;
     ResName := Map;
+  end;
 
   //writeln('********: gsw=', gGameSettings.WAD, '; rn=', ResName);
   result := false;
-  if ResName <> '' then
-    result := g_Map_Load(gGameSettings.WAD + ':\' + ResName);
+  if (ResName <> '') and (NewWAD <> '') then
+  begin
+    //result := g_Map_Load(gGameSettings.WAD + ':\' + ResName);
+    result := g_Map_Load(NewWAD+':\'+ResName);
+  end;
   if Result then
     begin
       g_Player_ResetAll(Force or gLastMap, gGameSettings.GameType = GT_SINGLE);
@@ -5037,30 +5137,28 @@ begin
   gNextMap := Map;
 end;
 
-procedure g_Game_ClientWAD(NewWAD: String; const WHash: TMD5Digest);
+function g_Game_ClientWAD (NewWAD: String; const WHash: TMD5Digest): AnsiString;
 var
   gWAD{, xwad}: String;
 begin
+  result := NewWAD;
   if not g_Game_IsClient then Exit;
   //e_LogWritefln('*** g_Game_ClientWAD: `%s`', [NewWAD]);
 
   gWAD := g_Res_DownloadMapWAD(ExtractFileName(NewWAD), WHash);
   if gWAD = '' then
   begin
+    result := '';
     g_Game_Free();
     g_FatalError(Format(_lc[I_GAME_ERROR_MAP_WAD], [ExtractFileName(NewWAD)]));
     Exit;
   end;
 
-(*
-  xwad := ExtractRelativePath(MapsDir, gWAD);
-  e_LogWritefln('using downloaded client map wad [%s] for [%s]`', [xwad, NewWAD], TMsgType.Notify);
-  NewWAD := xwad;
-  g_Game_LoadWAD(NewWAD);
-*)
+  e_LogWritefln('using downloaded client map wad [%s] for [%s]', [gWAD, NewWAD], TMsgType.Notify);
+  NewWAD := gWAD;
 
-  e_LogWritefln('using downloaded client map wad [%s]`', [NewWAD], TMsgType.Notify);
   g_Game_LoadWAD(NewWAD);
+  result := NewWAD;
 
   {
   if LowerCase(NewWAD) = LowerCase(gGameSettings.WAD) then Exit;
@@ -5835,11 +5933,8 @@ begin
     cmd := LowerCase(P[0]);
     if cmd = 'd_window' then
     begin
-      g_Console_Add(Format('gWinPosX = %d, gWinPosY %d', [gWinPosX, gWinPosY]));
-      g_Console_Add(Format('gWinRealPosX = %d, gWinRealPosY %d', [gWinRealPosX, gWinRealPosY]));
       g_Console_Add(Format('gScreenWidth = %d, gScreenHeight = %d', [gScreenWidth, gScreenHeight]));
       g_Console_Add(Format('gWinSizeX = %d, gWinSizeY = %d', [gWinSizeX, gWinSizeY]));
-      g_Console_Add(Format('Frame X = %d, Y = %d, Caption Y = %d', [gWinFrameX, gWinFrameY, gWinCaption]));
     end
     else if cmd = 'd_sounds' then
     begin
@@ -6733,6 +6828,7 @@ begin
             if found then
             begin
               // no such map, found wad
+              pw := P[1];
               SetLength(P, 3);
               P[1] := ExpandFileName(pw);
               P[2] := g_Game_GetFirstMap(P[1]);
@@ -7216,17 +7312,31 @@ begin
   end;
 end;
 
-procedure g_TakeScreenShot;
-  var s: TStream; t: TDateTime; date, name: String;
+procedure g_TakeScreenShot(Filename: string = '');
+  var s: TStream; t: TDateTime; dir, date, name: String;
 begin
   if e_NoGraphics then Exit;
-  t := Now;
-  DateTimeToString(date, 'yyyy-mm-dd-hh-nn-ss', t);
-  name := 'screenshot-' + date + '.png';
   try
-    s := e_CreateResource(ScreenshotDirs, name);
-    e_MakeScreenshot(s, gScreenWidth, gScreenHeight);
-    g_Console_Add(Format(_lc[I_CONSOLE_SCREENSHOT], [name]))
+    dir := e_GetWriteableDir(ScreenshotDirs);
+
+    if Filename = '' then
+    begin
+      t := Now;
+      DateTimeToString(date, 'yyyy-mm-dd-hh-nn-ss', t);
+      Filename := 'screenshot-' + date;
+    end;
+    
+    name := e_CatPath(dir, Filename + '.png');
+    s := createDiskFile(name);
+    try
+      e_MakeScreenshot(s, gScreenWidth, gScreenHeight);
+      s.Free;
+      g_Console_Add(Format(_lc[I_CONSOLE_SCREENSHOT], [name]))
+    except
+      g_Console_Add(Format(_lc[I_CONSOLE_ERROR_WRITE], [name]));
+      s.Free;
+      DeleteFile(name)
+    end
   except
     g_Console_Add('oh shit, i can''t create screenshot!')
   end