From 2fa77a7c9667395ef6d4141cde69ff6349bf301e Mon Sep 17 00:00:00 2001 From: Ketmar Dark Date: Fri, 1 Sep 2017 03:33:01 +0300 Subject: [PATCH] restarting the same map will not reload textures (yay, quickload!); don't spam log with invalid "no file" warnings --- src/engine/e_log.pas | 7 + src/game/g_game.pas | 14 +- src/game/g_map.pas | 276 ++++++++++++++++++++++++++------------- src/game/g_saveload.pas | 73 +++-------- src/shared/BinEditor.pas | 81 ++++++------ src/shared/MAPDEF.pas | 33 ++++- src/shared/utils.pas | 22 ++++ src/shared/wadreader.pas | 20 +-- src/shared/xdynrec.pas | 26 ++++ 9 files changed, 346 insertions(+), 206 deletions(-) diff --git a/src/engine/e_log.pas b/src/engine/e_log.pas index 3a8d0ad..937730d 100644 --- a/src/engine/e_log.pas +++ b/src/engine/e_log.pas @@ -41,6 +41,7 @@ function DecodeIPV4 (ip: LongWord): string; procedure e_InitWritelnDriver (); procedure e_LogWritefln (const fmt: AnsiString; args: array of const; category: TRecordCategory=MSG_NOTIFY; writeTime: Boolean=true); +procedure e_LogWriteln (const s: AnsiString; category: TRecordCategory=MSG_NOTIFY; writeTime: Boolean=true); var @@ -70,6 +71,12 @@ begin end; +procedure e_LogWriteln (const s: AnsiString; category: TRecordCategory=MSG_NOTIFY; writeTime: Boolean=true); +begin + e_LogWritefln('%s', [s], category, writeTime); +end; + + // returns formatted string if `writerCB` is `nil`, empty string otherwise //function formatstrf (const fmt: AnsiString; args: array of const; writerCB: TFormatStrFCallback=nil): AnsiString; //TFormatStrFCallback = procedure (constref buf; len: SizeUInt); diff --git a/src/game/g_game.pas b/src/game/g_game.pas index cc71ea1..a7f5a48 100644 --- a/src/game/g_game.pas +++ b/src/game/g_game.pas @@ -70,7 +70,7 @@ function g_Game_IsNet(): Boolean; function g_Game_IsServer(): Boolean; function g_Game_IsClient(): Boolean; procedure g_Game_Init(); -procedure g_Game_Free(); +procedure g_Game_Free (freeTextures: Boolean=true); procedure g_Game_LoadData(); procedure g_Game_FreeData(); procedure g_Game_Update(); @@ -95,7 +95,7 @@ procedure g_Game_RestartLevel(); procedure g_Game_RestartRound(NoMapRestart: Boolean = False); procedure g_Game_ClientWAD(NewWAD: String; WHash: TMD5Digest); procedure g_Game_SaveOptions(); -function g_Game_StartMap(Map: String; Force: Boolean = False): Boolean; +function g_Game_StartMap(Map: String; Force: Boolean = False; const oldMapPath: AnsiString=''): Boolean; procedure g_Game_ChangeMap(MapPath: String); procedure g_Game_ExitLevel(Map: Char16); function g_Game_GetFirstMap(WAD: String): String; @@ -1299,12 +1299,12 @@ begin end; end; -procedure g_Game_Free(); +procedure g_Game_Free(freeTextures: Boolean=true); begin if NetMode = NET_CLIENT then g_Net_Disconnect(); if NetMode = NET_SERVER then g_Net_Host_Die(); - g_Map_Free(); + g_Map_Free(freeTextures); g_Player_Free(); g_Player_RemoveAllCorpses(); @@ -4050,15 +4050,15 @@ begin MessageTime := 0; gGameOn := False; g_Game_ClearLoading(); - g_Game_StartMap(Map, True); + g_Game_StartMap(Map, True, gCurrentMapFileName); end; -function g_Game_StartMap(Map: String; Force: Boolean = False): Boolean; +function g_Game_StartMap(Map: String; Force: Boolean = False; const oldMapPath: AnsiString=''): Boolean; var NewWAD, ResName: String; I: Integer; begin - g_Map_Free(); + g_Map_Free((Map <> gCurrentMapFileName) and (oldMapPath <> gCurrentMapFileName)); g_Player_RemoveAllCorpses(); if (not g_Game_IsClient) and diff --git a/src/game/g_map.pas b/src/game/g_map.pas index bd2b450..22a9aee 100644 --- a/src/game/g_map.pas +++ b/src/game/g_map.pas @@ -60,7 +60,7 @@ function g_Map_Load(Res: String): Boolean; function g_Map_GetMapInfo(Res: String): TMapInfo; function g_Map_GetMapsList(WADName: String): SArray; function g_Map_Exist(Res: String): Boolean; -procedure g_Map_Free(); +procedure g_Map_Free(freeTextures: Boolean=true); procedure g_Map_Update(); procedure g_Map_DrawPanels (PanelType: Word); // unaccelerated @@ -202,6 +202,7 @@ var gDrawPanelList: TBinaryHeapObj = nil; // binary heap of all walls we have to render, populated by `g_Map_CollectDrawPanels()` gCurrentMap: TDynRecord = nil; + gCurrentMapFileName: AnsiString = ''; // so we can skip texture reloading function panelTypeToTag (panelType: Word): Integer; // returns GridTagXXX @@ -438,7 +439,9 @@ type var PanelById: array of TPanelID; - Textures: TLevelTextureArray; + Textures: TLevelTextureArray = nil; + TextNameHash: THashStrInt = nil; // key: texture name; value: index in `Textures` + BadTextNameHash: THashStrInt = nil; // set; so we won't spam with non-existing texture messages RespawnPoints: Array of TRespawnPoint; FlagPoints: Array [FLAG_RED..FLAG_BLUE] of PFlagPoint; //DOMFlagPoints: Array of TFlagPoint; @@ -784,8 +787,13 @@ begin PanelByID[len].PArrID := Result; end; + function CreateNullTexture(RecName: String): Integer; begin + RecName := toLowerCase1251(RecName); + if (TextNameHash = nil) then TextNameHash := hashNewStrInt(); + if TextNameHash.get(RecName, result) then exit; // i found her! + SetLength(Textures, Length(Textures)+1); result := High(Textures); @@ -797,26 +805,47 @@ begin Anim := False; TextureID := LongWord(TEXTURE_NONE); end; + + TextNameHash.put(RecName, result); end; -function CreateTexture(RecName: String; Map: string; log: Boolean): Integer; + +function CreateTexture(RecName: AnsiString; Map: string; log: Boolean): Integer; var WAD: TWADFile; TextureData: Pointer; - WADName, txname: String; + WADName: String; a, ResLength: Integer; begin + RecName := toLowerCase1251(RecName); + if (TextNameHash = nil) then TextNameHash := hashNewStrInt(); + if TextNameHash.get(RecName, result) then + begin + // i found her! + //e_LogWritefln('texture ''%s'' already loaded', [RecName]); + exit; + end; + Result := -1; + if (BadTextNameHash <> nil) and BadTextNameHash.has(RecName) then exit; // don't do it again and again + + { if Textures <> nil then + begin for a := 0 to High(Textures) do - if Textures[a].TextureName = RecName then + begin + if (Textures[a].TextureName = RecName) then begin // Òåêñòóðà ñ òàêèì èìåíåì óæå åñòü + e_LogWritefln('texture ''%s'' already loaded', [RecName]); Result := a; Exit; end; + end; + end; + } -// Òåêñòóðû ñî ñïåöèàëüíûìè èìåíàìè (âîäà, ëàâà, êèñëîòà): + // Òåêñòóðû ñî ñïåöèàëüíûìè èìåíàìè (âîäà, ëàâà, êèñëîòà): if (RecName = TEXTURE_NAME_WATER) or (RecName = TEXTURE_NAME_ACID1) or (RecName = TEXTURE_NAME_ACID2) then @@ -826,36 +855,28 @@ begin with Textures[High(Textures)] do begin TextureName := RecName; - - if TextureName = TEXTURE_NAME_WATER then - TextureID := LongWord(TEXTURE_SPECIAL_WATER) - else - if TextureName = TEXTURE_NAME_ACID1 then - TextureID := LongWord(TEXTURE_SPECIAL_ACID1) - else - if TextureName = TEXTURE_NAME_ACID2 then - TextureID := LongWord(TEXTURE_SPECIAL_ACID2); + if (TextureName = TEXTURE_NAME_WATER) then TextureID := LongWord(TEXTURE_SPECIAL_WATER) + else if (TextureName = TEXTURE_NAME_ACID1) then TextureID := LongWord(TEXTURE_SPECIAL_ACID1) + else if (TextureName = TEXTURE_NAME_ACID2) then TextureID := LongWord(TEXTURE_SPECIAL_ACID2); Anim := False; end; result := High(Textures); + TextNameHash.put(RecName, result); Exit; end; -// Çàãðóæàåì ðåñóðñ òåêñòóðû â ïàìÿòü èç WAD'à: + // Çàãðóæàåì ðåñóðñ òåêñòóðû â ïàìÿòü èç WAD'à: WADName := g_ExtractWadName(RecName); WAD := TWADFile.Create(); - if WADName <> '' then - WADName := GameDir+'/wads/'+WADName - else - WADName := Map; + if WADName <> '' then WADName := GameDir+'/wads/'+WADName else WADName := Map; WAD.ReadFile(WADName); - txname := RecName; + //txname := RecName; { if (WADName = Map) and WAD.GetResource(g_ExtractFilePathName(RecName), TextureData, ResLength) then begin @@ -864,33 +885,38 @@ begin end; } - if WAD.GetResource(g_ExtractFilePathName(RecName), TextureData, ResLength) then + if WAD.GetResource(g_ExtractFilePathName(RecName), TextureData, ResLength, log) then + begin + SetLength(Textures, Length(Textures)+1); + if not e_CreateTextureMem(TextureData, ResLength, Textures[High(Textures)].TextureID) then begin - SetLength(Textures, Length(Textures)+1); - if not e_CreateTextureMem(TextureData, ResLength, Textures[High(Textures)].TextureID) then - Exit; - e_GetTextureSize(Textures[High(Textures)].TextureID, - @Textures[High(Textures)].Width, - @Textures[High(Textures)].Height); - FreeMem(TextureData); - Textures[High(Textures)].TextureName := {RecName}txname; - Textures[High(Textures)].Anim := False; - - result := High(Textures); - end + SetLength(Textures, Length(Textures)-1); + Exit; + end; + e_GetTextureSize(Textures[High(Textures)].TextureID, @Textures[High(Textures)].Width, @Textures[High(Textures)].Height); + FreeMem(TextureData); + Textures[High(Textures)].TextureName := RecName; + Textures[High(Textures)].Anim := False; + + result := High(Textures); + TextNameHash.put(RecName, result); + end else // Íåò òàêîãî ðåóñðñà â WAD'å begin - //e_WriteLog(Format('SHIT! Error loading texture %s : %s : %s', [RecName, txname, g_ExtractFilePathName(RecName)]), MSG_WARNING); - if log then - begin - e_WriteLog(Format('Error loading texture %s', [RecName]), MSG_WARNING); - //e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING); - end; + //e_WriteLog(Format('SHIT! Error loading texture %s : %s', [RecName, g_ExtractFilePathName(RecName)]), MSG_WARNING); + if (BadTextNameHash = nil) then BadTextNameHash := hashNewStrInt(); + if log and (not BadTextNameHash.get(RecName, a)) then + begin + e_WriteLog(Format('Error loading texture %s', [RecName]), MSG_WARNING); + //e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING); + end; + BadTextNameHash.put(RecName, -1); end; WAD.Free(); end; + function CreateAnimTexture(RecName: String; Map: string; log: Boolean): Integer; var WAD: TWADFile; @@ -907,29 +933,44 @@ var ia: TDynImageDataArray = nil; f, c, frdelay, frloop: Integer; begin + RecName := toLowerCase1251(RecName); + if (TextNameHash = nil) then TextNameHash := hashNewStrInt(); + if TextNameHash.get(RecName, result) then + begin + // i found her! + //e_LogWritefln('animated texture ''%s'' already loaded', [RecName]); + exit; + end; + result := -1; - //e_WriteLog(Format('*** Loading animated texture "%s"', [RecName]), MSG_NOTIFY); + //e_LogWritefln('*** Loading animated texture "%s"', [RecName]); + + if (BadTextNameHash = nil) then BadTextNameHash := hashNewStrInt(); + if BadTextNameHash.get(RecName, f) then + begin + //e_WriteLog(Format('no animation texture %s (don''t worry)', [RecName]), MSG_NOTIFY); + exit; + end; // ×èòàåì WAD-ðåñóðñ àíèì.òåêñòóðû èç WAD'à â ïàìÿòü: WADName := g_ExtractWadName(RecName); WAD := TWADFile.Create(); try - if WADName <> '' then - WADName := GameDir+'/wads/'+WADName - else - WADName := Map; + if WADName <> '' then WADName := GameDir+'/wads/'+WADName else WADName := Map; WAD.ReadFile(WADName); - if not WAD.GetResource(g_ExtractFilePathName(RecName), TextureWAD, ResLength) then + if not WAD.GetResource(g_ExtractFilePathName(RecName), TextureWAD, ResLength, log) then begin - if log then + if (BadTextNameHash = nil) then BadTextNameHash := hashNewStrInt(); + if log and (not BadTextNameHash.get(RecName, f)) then begin e_WriteLog(Format('Error loading animation texture %s', [RecName]), MSG_WARNING); //e_WriteLog(Format('WAD Reader error: %s', [WAD.GetLastErrorStr]), MSG_WARNING); end; + BadTextNameHash.put(RecName, -1); exit; end; @@ -946,6 +987,7 @@ begin if ResLength < 6 then begin e_WriteLog(Format('Animated texture file "%s" too short', [RecName]), MSG_WARNING); + BadTextNameHash.put(RecName, -1); exit; end; @@ -957,6 +999,7 @@ begin if not WAD.ReadMemory(TextureWAD, ResLength) then begin e_WriteLog(Format('Animated texture WAD file "%s" is invalid', [RecName]), MSG_WARNING); + BadTextNameHash.put(RecName, -1); exit; end; @@ -964,6 +1007,7 @@ begin if not WAD.GetResource('TEXT/ANIM', TextData, ResLength) then begin e_WriteLog(Format('Animated texture file "%s" has invalid INI', [RecName]), MSG_WARNING); + BadTextNameHash.put(RecName, -1); exit; end; @@ -973,6 +1017,7 @@ begin if TextureResource = '' then begin e_WriteLog(Format('Animated texture WAD file "%s" has no "resource"', [RecName]), MSG_WARNING); + BadTextNameHash.put(RecName, -1); exit; end; @@ -989,6 +1034,7 @@ begin if not WAD.GetResource('TEXTURES/'+TextureResource, TextureData, ResLength) then begin e_WriteLog(Format('Animated texture WAD file "%s" has no texture "%s"', [RecName, 'TEXTURES/'+TextureResource]), MSG_WARNING); + BadTextNameHash.put(RecName, -1); exit; end; @@ -1008,10 +1054,16 @@ begin FramesCount := _framecount; Speed := _speed; result := High(Textures); + TextNameHash.put(RecName, result); end else begin - if log then e_WriteLog(Format('Error loading animation texture %s', [RecName]), MSG_WARNING); + if (BadTextNameHash = nil) then BadTextNameHash := hashNewStrInt(); + if log and (not BadTextNameHash.get(RecName, f)) then + begin + e_WriteLog(Format('Error loading animation texture %s', [RecName]), MSG_WARNING); + end; + BadTextNameHash.put(RecName, -1); end; end; end @@ -1031,11 +1083,13 @@ begin if not LoadMultiImageFromMemory(TextureWAD, ResLength, ia) then begin e_WriteLog(Format('Animated texture file "%s" cannot be loaded', [RecName]), MSG_WARNING); + BadTextNameHash.put(RecName, -1); exit; end; if length(ia) = 0 then begin e_WriteLog(Format('Animated texture file "%s" has no frames', [RecName]), MSG_WARNING); + BadTextNameHash.put(RecName, -1); exit; end; @@ -1091,20 +1145,26 @@ begin Textures[High(Textures)].FramesCount := length(ia); Textures[High(Textures)].Speed := _speed; result := High(Textures); + TextNameHash.put(RecName, result); //writeln(' CREATED!'); end else begin - if log then e_WriteLog(Format('Error loading animation texture "%s" images', [RecName]), MSG_WARNING); + if (BadTextNameHash = nil) then BadTextNameHash := hashNewStrInt(); + if log and (not BadTextNameHash.get(RecName, f)) then + begin + e_WriteLog(Format('Error loading animation texture "%s" images', [RecName]), MSG_WARNING); + end; + BadTextNameHash.put(RecName, -1); end; end; finally for f := 0 to High(ia) do FreeImage(ia[f]); WAD.Free(); cfg.Free(); - if TextureWAD <> nil then FreeMem(TextureWAD); - if TextData <> nil then FreeMem(TextData); - if TextureData <> nil then FreeMem(TextureData); + if (TextureWAD <> nil) then FreeMem(TextureWAD); + if (TextData <> nil) then FreeMem(TextData); + if (TextureData <> nil) then FreeMem(TextureData); end; end; @@ -1698,6 +1758,11 @@ begin pttit.shotPan := mapReader.panel[rec.trigRec.tgShotPanelID]; end; + if (pttit.texPan <> nil) then pttit.texPan.userPanelTrigRef := true; + if (pttit.liftPan <> nil) then pttit.liftPan.userPanelTrigRef := true; + if (pttit.doorPan <> nil) then pttit.doorPan.userPanelTrigRef := true; + if (pttit.shotPan <> nil) then pttit.shotPan.userPanelTrigRef := true; + g_Game_StepLoading(); end; end; @@ -1712,6 +1777,7 @@ begin for rec in panels do begin Inc(pannum); + //e_LogWritefln('PANSTART: pannum=%s', [pannum]); texrec := nil; SetLength(AddTextures, 0); trigRef := False; @@ -1731,6 +1797,7 @@ begin ok := false; if (TriggersTable <> nil) and (mapTextureList <> nil) then begin + { for b := 0 to High(TriggersTable) do begin if (TriggersTable[b].texPan = rec) or (TriggersTable[b].shotPan = rec) then @@ -1740,6 +1807,13 @@ begin break; end; end; + } + if rec.userPanelTrigRef then + begin + // e_LogWritefln('trigref for panel %s', [pannum]); + trigRef := True; + ok := True; + end; end; end; @@ -1776,30 +1850,39 @@ begin begin // Íà÷àëüíàÿ - àíèìèðîâàííàÿ, èùåì àíèìèðîâàííóþ isAnim := True; + //e_LogWritefln('000: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]); ok := CreateAnimTexture(TexName, FileName, False) >= 0; + //e_LogWritefln('001: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]); if not ok then begin // Íåò àíèìèðîâàííîé, èùåì îáû÷íóþ isAnim := False; + //e_LogWritefln('002: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]); ok := CreateTexture(TexName, FileName, False) >= 0; + //e_LogWritefln('003: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]); end; end else begin // Íà÷àëüíàÿ - îáû÷íàÿ, èùåì îáû÷íóþ isAnim := False; + //e_LogWritefln('004: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]); ok := CreateTexture(TexName, FileName, False) >= 0; + //e_LogWritefln('005: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]); if not ok then begin // Íåò îáû÷íîé, èùåì àíèìèðîâàííóþ isAnim := True; + //e_LogWritefln('006: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]); ok := CreateAnimTexture(TexName, FileName, False) >= 0; + //e_LogWritefln('007: pannum=%s; TexName=[%s]; FileName=[%s]', [pannum, TexName, FileName]); end; end; // Îíà ñóùåñòâóåò. Çàíîñèì åå ID â ñïèñîê ïàíåëè if ok then begin + { for c := 0 to High(Textures) do begin if (Textures[c].TextureName = TexName) then @@ -1810,6 +1893,13 @@ begin break; end; end; + } + if (TextNameHash <> nil) and TextNameHash.get(toLowerCase1251(TexName), c) then + begin + SetLength(AddTextures, Length(AddTextures)+1); + AddTextures[High(AddTextures)].Texture := c; + AddTextures[High(AddTextures)].Anim := isAnim; + end; end; end else @@ -1846,11 +1936,15 @@ begin //e_WriteLog(Format('panel #%d: TextureNum=%d; ht=%d; ht1=%d; atl=%d', [a, panels[a].TextureNum, High(mapTextureList), High(Textures), High(AddTextures)]), MSG_NOTIFY); + //e_LogWritefln('PANADD: pannum=%s', [pannum]); + // Ñîçäàåì ïàíåëü è çàïîìèíàåì åå íîìåð PanelID := CreatePanel(rec, AddTextures, CurTex, trigRef); //e_LogWritefln('panel #%s of type %s got id #%s', [pannum, rec.PanelType, PanelID]); // set 'gamePanelId' field to panel id - rec.gamePanelId := PanelID; // remember game panel id, we'll fix triggers later + rec.userPanelId := PanelID; // remember game panel id, we'll fix triggers later + + //e_LogWritefln('PANEND: pannum=%s', [pannum]); g_Game_StepLoading(); end; @@ -1859,10 +1953,10 @@ begin // ×èíèì ID'û ïàíåëåé, êîòîðûå èñïîëüçóþòñÿ â òðèããåðàõ for b := 0 to High(TriggersTable) do begin - if (TriggersTable[b].texPan <> nil) then TriggersTable[b].texPanIdx := TriggersTable[b].texPan.gamePanelId; - if (TriggersTable[b].liftPan <> nil) then TriggersTable[b].LiftPanelIdx := TriggersTable[b].liftPan.gamePanelId; - if (TriggersTable[b].doorPan <> nil) then TriggersTable[b].DoorPanelIdx := TriggersTable[b].doorPan.gamePanelId; - if (TriggersTable[b].shotPan <> nil) then TriggersTable[b].ShotPanelIdx := TriggersTable[b].shotPan.gamePanelId; + if (TriggersTable[b].texPan <> nil) then TriggersTable[b].texPanIdx := TriggersTable[b].texPan.userPanelId; + if (TriggersTable[b].liftPan <> nil) then TriggersTable[b].LiftPanelIdx := TriggersTable[b].liftPan.userPanelId; + if (TriggersTable[b].doorPan <> nil) then TriggersTable[b].DoorPanelIdx := TriggersTable[b].doorPan.userPanelId; + if (TriggersTable[b].shotPan <> nil) then TriggersTable[b].ShotPanelIdx := TriggersTable[b].shotPan.userPanelId; end; // create map grid, init other grids (for monsters, for example) @@ -1930,6 +2024,7 @@ begin end; gCurrentMap := mapReader; // this will be our current map now + gCurrentMapFileName := Res; mapReader := nil; // Çàãðóçêà íåáà @@ -2048,22 +2143,6 @@ begin WAD.Free(); - { - MapReader := TMapReader_1.Create(); - if not MapReader.LoadMap(Data) then - begin - g_Console_Add(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Res]), True); - ZeroMemory(@Header, SizeOf(Header)); - Result.Name := _lc[I_GAME_ERROR_MAP_SELECT]; - Result.Description := _lc[I_GAME_ERROR_MAP_SELECT]; - end - else - begin - Header := MapReader.GetMapHeader(); - Result.Name := Header.MapName; - Result.Description := Header.MapDescription; - end; - } try mapReader := g_Map_ParseMap(Data, Len); except @@ -2071,10 +2150,6 @@ begin end; FreeMem(Data); - //MapReader.Free(); - - //if (mapReader <> nil) then Header := GetMapHeader(mapReader) else FillChar(Header, sizeof(Header), 0); - //MapReader.Free(); if (mapReader.Width > 0) and (mapReader.Height > 0) then begin @@ -2155,7 +2230,7 @@ begin end; end; -procedure g_Map_Free(); +procedure g_Map_Free(freeTextures: Boolean=true); var a: Integer; @@ -2194,17 +2269,42 @@ begin //gDOMFlags := nil; - if Textures <> nil then + if (Length(gCurrentMapFileName) <> 0) then begin - for a := 0 to High(Textures) do - if not g_Map_IsSpecialTexture(Textures[a].TextureName) then - if Textures[a].Anim then - g_Frames_DeleteByID(Textures[a].FramesID) - else - if Textures[a].TextureID <> LongWord(TEXTURE_NONE) then - e_DeleteTexture(Textures[a].TextureID); - - Textures := nil; + e_LogWritefln('g_Map_Free: previous map was ''%s''...', [gCurrentMapFileName]); + end + else + begin + e_LogWritefln('g_Map_Free: no previous map.', []); + end; + if freeTextures then + begin + e_LogWritefln('g_Map_Free: clearing textures...', []); + if (Textures <> nil) then + begin + for a := 0 to High(Textures) do + begin + if not g_Map_IsSpecialTexture(Textures[a].TextureName) then + begin + if Textures[a].Anim then + begin + g_Frames_DeleteByID(Textures[a].FramesID) + end + else + begin + if (Textures[a].TextureID <> LongWord(TEXTURE_NONE)) then + begin + e_DeleteTexture(Textures[a].TextureID); + end; + end; + end; + end; + Textures := nil; + end; + TextNameHash.Free(); + TextNameHash := nil; + BadTextNameHash.Free(); + BadTextNameHash := nil; end; FreePanelArray(gWalls); diff --git a/src/game/g_saveload.pas b/src/game/g_saveload.pas index 5a13098..a54e26b 100644 --- a/src/game/g_saveload.pas +++ b/src/game/g_saveload.pas @@ -21,21 +21,13 @@ interface uses e_graphics, g_phys, g_textures, BinEditor; + function g_GetSaveName(n: Integer): String; function g_SaveGame(n: Integer; Name: String): Boolean; function g_LoadGame(n: Integer): Boolean; procedure Obj_SaveState(o: PObj; var Mem: TBinMemoryWriter); procedure Obj_LoadState(o: PObj; var Mem: TBinMemoryReader); -type - TLoadSaveHook = procedure (); - -procedure g_SetPreLoadHook (ahook: TLoadSaveHook); -procedure g_SetPostLoadHook (ahook: TLoadSaveHook); - -procedure g_CallPreLoadHooks (); -procedure g_CallPostLoadHooks (); - implementation @@ -47,49 +39,12 @@ uses const SAVE_SIGNATURE = $56534644; // 'DFSV' - SAVE_VERSION = $03; + SAVE_VERSION = $04; END_MARKER_STRING = 'END'; PLAYER_VIEW_SIGNATURE = $57564C50; // 'PLVW' OBJ_SIGNATURE = $4A424F5F; // '_OBJ' -var - preloadHooks: array of TLoadSaveHook = nil; - postloadHooks: array of TLoadSaveHook = nil; - - -procedure g_SetPreLoadHook (ahook: TLoadSaveHook); -begin - if not assigned(ahook) then exit; - SetLength(preloadHooks, Length(preloadHooks)+1); - preloadHooks[High(preloadHooks)] := ahook; -end; - - -procedure g_SetPostLoadHook (ahook: TLoadSaveHook); -begin - if not assigned(ahook) then exit; - SetLength(postloadHooks, Length(postloadHooks)+1); - postloadHooks[High(postloadHooks)] := ahook; -end; - - -procedure g_CallPreLoadHooks (); -var - f: Integer; -begin - for f := 0 to High(preloadHooks) do preloadHooks[f](); -end; - - -procedure g_CallPostLoadHooks (); -var - f: Integer; -begin - for f := 0 to High(postloadHooks) do postloadHooks[f](); -end; - - procedure Obj_SaveState(o: PObj; var Mem: TBinMemoryWriter); var sig: DWORD; @@ -211,6 +166,9 @@ begin bMem := TBinMemoryWriter.Create(256); // Èìÿ èãðû: bMem.WriteString(Name, 32); + // Ïîëíûé ïóòü ê âàäó è êàðòà + if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('SAVE: current map is ''%s''...', [gCurrentMapFileName]); + bMem.WriteString(gCurrentMapFileName); // Ïóòü ê êàðòå: str := gGameSettings.WAD; bMem.WriteString(str, 128); @@ -385,6 +343,8 @@ var Game_CoopTotalSecrets, PID1, PID2: Word; i: Integer; + gameCleared: Boolean = false; + curmapfile: AnsiString = ''; begin Result := False; bMem := nil; @@ -401,19 +361,27 @@ begin end; e_WriteLog('Loading saved game...', MSG_NOTIFY); - g_Game_Free(); + //g_Game_Free(false); // don't free textures for the same map g_Game_ClearLoading(); g_Game_SetLoadingText(_lc[I_LOAD_SAVE_FILE], 0, False); gLoadGameMode := True; - g_CallPreLoadHooks(); - ///// Çàãðóæàåì ñîñòîÿíèå èãðû: ///// bMem := TBinMemoryReader.Create(); bFile.ReadMemory(bMem); // Èìÿ èãðû: bMem.ReadString(str); + + // Ïîëíûé ïóòü ê âàäó è êàðòà + bMem.ReadString(curmapfile); + + if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('LOAD: previous map was ''%s''...', [gCurrentMapFileName]); + if (Length(curmapfile) <> 0) then e_LogWritefln('LOAD: new map is ''%s''...', [curmapfile]); + // À âîò òóò, íàêîíåö, ÷èñòèì ðåñóðñû + g_Game_Free(curmapfile <> gCurrentMapFileName); // don't free textures for the same map + gameCleared := true; + // Ïóòü ê êàðòå: bMem.ReadString(WAD_Path); // Èìÿ êàðòû: @@ -491,7 +459,7 @@ begin g_Game_SetupScreenSize(); // Çàãðóçêà è çàïóñê êàðòû: - if not g_Game_StartMap(WAD_Path + ':\' + Map_Name, True) then + if not g_Game_StartMap(WAD_Path + ':\' + Map_Name, True, curmapfile) then begin g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [WAD_Path + ':\' + Map_Name])); Exit; @@ -621,7 +589,6 @@ begin // Çàêðûâàåì ôàéë çàãðóçêè: bFile.Close(); gLoadGameMode := False; - g_CallPostLoadHooks(); Result := True; except @@ -629,11 +596,13 @@ begin begin g_Console_Add(_lc[I_GAME_ERROR_LOAD]); e_WriteLog('LoadState I/O Error: '+E1.Message, MSG_WARNING); + if not gameCleared then g_Game_Free(); end; on E2: EBinSizeError do begin g_Console_Add(_lc[I_GAME_ERROR_LOAD]); e_WriteLog('LoadState Size Error: '+E2.Message, MSG_WARNING); + if not gameCleared then g_Game_Free(); end; end; diff --git a/src/shared/BinEditor.pas b/src/shared/BinEditor.pas index 1f53456..25526e1 100644 --- a/src/shared/BinEditor.pas +++ b/src/shared/BinEditor.pas @@ -44,7 +44,7 @@ Type Procedure WriteInt(Var x: Integer); Procedure WriteSingle(Var x: Single); Procedure WriteBoolean(Var x: Boolean); - Procedure WriteString(Var x: String; aMaxLen: Byte = 255); + Procedure WriteString(const x: AnsiString; aMaxLen: Word=65535); Procedure WriteMemory(Var x: Pointer; memSize: Cardinal); Procedure Fill(aLen: Cardinal; aFillSym: Byte); Procedure SaveToFile(Var aFile: File); @@ -70,7 +70,7 @@ Type Procedure ReadInt(Var x: Integer); Procedure ReadSingle(Var x: Single); Procedure ReadBoolean(Var x: Boolean); - Procedure ReadString(Var x: String); + Procedure ReadString(Var x: AnsiString); Procedure ReadMemory(Var x: Pointer; Var memSize: Cardinal); Procedure Skip(aLen: Cardinal); Procedure LoadFromFile(Var aFile: File); @@ -245,26 +245,22 @@ begin WriteVar(y, SizeOf(Byte)); end; -Procedure TBinMemoryWriter.WriteString(Var x: String; aMaxLen: Byte = 255); +Procedure TBinMemoryWriter.WriteString (const x: AnsiString; aMaxLen: Word=65535); var - len: Byte; - + len: Word; begin - len := Min(Length(x), aMaxLen); + if (Length(x) > aMaxLen) then len := aMaxLen else len := Word(Length(x)); - if (FPosition + SizeOf(Byte) + len) > FSize then - ExtendMemory(SizeOf(Byte) + len); + if (FPosition+SizeOf(Byte)+len) > FSize then ExtendMemory(SizeOf(Byte)+len); -// Äëèíà ñòðîêè: - CopyMemory(Pointer(NativeUInt(FData) + FPosition), - @len, SizeOf(Byte)); - FPosition := FPosition + SizeOf(Byte); -// Ñòðîêà: - if len > 0 then + // Äëèíà ñòðîêè: + CopyMemory(Pointer(NativeUInt(FData)+FPosition), @len, SizeOf(len)); + FPosition := FPosition+SizeOf(len); + // Ñòðîêà: + if (len > 0) then begin - CopyMemory(Pointer(NativeUInt(FData) + FPosition), - @x[1], len); - FPosition := FPosition + len; + CopyMemory(Pointer(NativeUInt(FData) + FPosition), @x[1], len); + FPosition := FPosition+len; end; end; @@ -410,38 +406,39 @@ begin x := False; end; -Procedure TBinMemoryReader.ReadString(Var x: String); +Procedure TBinMemoryReader.ReadString (Var x: AnsiString); var - len: Byte; - + len: Word; begin - if (FPosition + SizeOf(Byte)) <= FSize then - begin + if (FPosition+SizeOf(len)) <= FSize then + begin // Äëèíà ñòðîêè: - CopyMemory(@len, - Pointer(NativeUInt(FData) + FPosition), - SizeOf(Byte)); - - if (FPosition + SizeOf(Byte) + len) <= FSize then - begin - FPosition := FPosition + SizeOf(Byte); - // Ñòðîêà: - SetLength(x, len); - if len > 0 then - begin - CopyMemory(@x[1], - Pointer(NativeUInt(FData) + FPosition), - len); - FPosition := FPosition + len; - end - else - x := ''; - end + CopyMemory(@len, Pointer(NativeUInt(FData)+FPosition), SizeOf(len)); + if (FPosition+SizeOf(len)+len <= FSize) then + begin + FPosition := FPosition+SizeOf(len); + // Ñòðîêà: + UniqueString(x); + SetLength(x, len); + if (len > 0) then + begin + CopyMemory(@x[1], Pointer(NativeUInt(FData) + FPosition), len); + FPosition := FPosition+len; + end else - raise EBinSizeError.Create('TBinMemoryReader.ReadString: Too Long String'); + begin + x := ''; + end; end + else + begin + raise EBinSizeError.Create('TBinMemoryReader.ReadString: Too Long String'); + end; + end else + begin raise EBinSizeError.Create('TBinMemoryReader.ReadString: End of Memory'); + end; end; Procedure TBinMemoryReader.ReadMemory(Var x: Pointer; Var memSize: Cardinal); diff --git a/src/shared/MAPDEF.pas b/src/shared/MAPDEF.pas index ededbdd..d22e73f 100644 --- a/src/shared/MAPDEF.pas +++ b/src/shared/MAPDEF.pas @@ -119,8 +119,11 @@ type private // user fields - function getGamePanelId (): Integer; inline; - procedure setGamePanelId (v: Integer); inline; + function getUserPanelId (): Integer; inline; + procedure setUserPanelId (v: Integer); inline; + + function getUserTrigRef (): Boolean; inline; + procedure setUserTrigRef (v: Boolean); inline; public property panel[idx: Integer]: TDynRecord read getPanelByIdx; @@ -130,7 +133,8 @@ type property tgShotPanelID: Integer read getPanelId {write setPanelId}; property TexturePanel: Integer read getTexturePanel {write setTexturePanel}; // texturepanel, int // user fields - property gamePanelId: Integer read getGamePanelId write setGamePanelId; + property userPanelId: Integer read getUserPanelId write setUserPanelId; + property userPanelTrigRef: Boolean read getUserTrigRef write setUserTrigRef; end; implementation @@ -144,18 +148,33 @@ constructor TDFPoint.Create (ax, ay: LongInt); begin X := ax; Y := ay; end; // ////////////////////////////////////////////////////////////////////////// // -function TDynRecordHelper.getGamePanelId (): Integer; inline; +function TDynRecordHelper.getUserPanelId (): Integer; inline; var fld: TDynField; begin - fld := field['gamePanelId']; + fld := field['userPanelId']; if (fld = nil) or (fld.baseType <> TDynField.TType.TInt) then result := -1 else result := fld.ival; end; -procedure TDynRecordHelper.setGamePanelId (v: Integer); inline; +procedure TDynRecordHelper.setUserPanelId (v: Integer); inline; +begin + setUserField('userPanelId', Integer(v)); +end; + + +function TDynRecordHelper.getUserTrigRef (): Boolean; inline; +var + fld: TDynField; +begin + fld := field['userPanelTrigRef']; + if (fld = nil) or (fld.baseType <> TDynField.TType.TBool) then result := false else result := (fld.ival <> 0); +end; + + +procedure TDynRecordHelper.setUserTrigRef (v: Boolean); inline; begin - setUserField('gamePanelId', Integer(v)); + setUserField('userPanelTrigRef', v); end; diff --git a/src/shared/utils.pas b/src/shared/utils.pas index 1caa1f3..c01287f 100644 --- a/src/shared/utils.pas +++ b/src/shared/utils.pas @@ -81,6 +81,8 @@ function Int64ToStrComma (i: Int64): AnsiString; function UpCase1251 (ch: Char): Char; function LoCase1251 (ch: Char): Char; +function toLowerCase1251 (const s: AnsiString): AnsiString; + // `true` if strings are equal; ignoring case for cp1251 function StrEquCI1251 (const s0, s1: AnsiString): Boolean; @@ -843,6 +845,26 @@ begin end; +function toLowerCase1251 (const s: AnsiString): AnsiString; +var + f: Integer; + ch: AnsiChar; +begin + for ch in s do + begin + if (ch <> LoCase1251(ch)) then + begin + result := ''; + SetLength(result, Length(s)); + for f := 1 to Length(s) do result[f] := LoCase1251(s[f]); + exit; + end; + end; + // nothing to do + result := s; +end; + + // ////////////////////////////////////////////////////////////////////////// // // utils // `ch`: utf8 start diff --git a/src/shared/wadreader.pas b/src/shared/wadreader.pas index a916ff6..69d0439 100644 --- a/src/shared/wadreader.pas +++ b/src/shared/wadreader.pas @@ -36,7 +36,7 @@ type function getIsOpen (): Boolean; function isMapResource (idx: Integer): Boolean; - function GetResourceEx (name: AnsiString; wantMap: Boolean; var pData: Pointer; var Len: Integer): Boolean; + function GetResourceEx (name: AnsiString; wantMap: Boolean; var pData: Pointer; var Len: Integer; logError: Boolean=true): Boolean; public constructor Create (); @@ -47,8 +47,8 @@ type function ReadFile (FileName: AnsiString): Boolean; function ReadMemory (Data: Pointer; Len: LongWord): Boolean; - function GetResource (name: AnsiString; var pData: Pointer; var Len: Integer): Boolean; - function GetMapResource (name: AnsiString; var pData: Pointer; var Len: Integer): Boolean; + function GetResource (name: AnsiString; var pData: Pointer; var Len: Integer; logError: Boolean=true): Boolean; + function GetMapResource (name: AnsiString; var pData: Pointer; var Len: Integer; logError: Boolean=true): Boolean; function GetMapResources (): SArray; // returns `nil` if file wasn't found @@ -297,7 +297,7 @@ begin end; -function TWADFile.GetResourceEx (name: AnsiString; wantMap: Boolean; var pData: Pointer; var Len: Integer): Boolean; +function TWADFile.GetResourceEx (name: AnsiString; wantMap: Boolean; var pData: Pointer; var Len: Integer; logError: Boolean=true): Boolean; var f, lastSlash: Integer; fi: TSFSFileInfo; @@ -355,7 +355,7 @@ begin if fs = nil then begin if wantMap then continue; - e_WriteLog(Format('DFWAD: can''t open file [%s] in [%s]', [name, fFileName]), MSG_WARNING); + if logError then e_WriteLog(Format('DFWAD: can''t open file [%s] in [%s]', [name, fFileName]), MSG_WARNING); break; end; // if we want only maps, check if this is map @@ -431,17 +431,17 @@ begin exit; end; end; - e_WriteLog(Format('DFWAD: file [%s] not found in [%s]', [name, fFileName]), MSG_WARNING); + if logError then e_WriteLog(Format('DFWAD: file [%s] not found in [%s]', [name, fFileName]), MSG_WARNING); end; -function TWADFile.GetResource (name: AnsiString; var pData: Pointer; var Len: Integer): Boolean; +function TWADFile.GetResource (name: AnsiString; var pData: Pointer; var Len: Integer; logError: Boolean=true): Boolean; begin - result := GetResourceEx(name, false, pData, Len); + result := GetResourceEx(name, false, pData, Len, logError); end; -function TWADFile.GetMapResource (name: AnsiString; var pData: Pointer; var Len: Integer): Boolean; +function TWADFile.GetMapResource (name: AnsiString; var pData: Pointer; var Len: Integer; logError: Boolean=true): Boolean; begin - result := GetResourceEx(name, true, pData, Len); + result := GetResourceEx(name, true, pData, Len, logError); end; function TWADFile.GetMapResources (): SArray; diff --git a/src/shared/xdynrec.pas b/src/shared/xdynrec.pas index ac55cf5..12d7256 100644 --- a/src/shared/xdynrec.pas +++ b/src/shared/xdynrec.pas @@ -249,6 +249,7 @@ type procedure setUserField (const fldname: AnsiString; v: LongInt); procedure setUserField (const fldname: AnsiString; v: AnsiString); + procedure setUserField (const fldname: AnsiString; v: Boolean); public property id: AnsiString read mId; // for map parser @@ -1985,6 +1986,31 @@ begin end; +procedure TDynRecord.setUserField (const fldname: AnsiString; v: Boolean); +var + fld: TDynField; +begin + if (Length(fldname) = 0) then exit; + fld := field[fldname]; + if (fld <> nil) then + begin + if (fld.mType <> fld.TType.TBool) or (fld.mEBS <> fld.TEBS.TNone) then + begin + raise Exception.Create(Format('invalid user field ''%s'' type', [fld.name])); + end; + end + else + begin + fld := TDynField.Create(fldname, fld.TType.TBool); + fld.mOwner := self; + fld.mIVal := Integer(v); + fld.mInternal := true; + fld.mDefined := true; + addField(fld); + end; +end; + + procedure TDynRecord.parseDef (pr: TTextParser); var fld: TDynField; -- 2.29.2