From 223356cbae3197afc861efa6241c4ae91bd92885 Mon Sep 17 00:00:00 2001 From: Ketmar Dark Date: Wed, 30 Aug 2017 00:53:09 +0300 Subject: [PATCH] the game is able to read text maps now (WARNING! the feature is still experimental!) --- src/engine/e_log.pas | 19 ++-- src/game/Doom2DF.dpr | 1 + src/game/g_map.pas | 192 ++++++++++++++++++++++++++------------- src/shared/a_modes.inc | 2 + src/shared/mapdef.txt | 1 + src/shared/utils.pas | 2 +- src/shared/wadreader.pas | 63 ++++++++++--- src/shared/xdynrec.pas | 9 +- src/shared/xparser.pas | 31 ++++++- 9 files changed, 222 insertions(+), 98 deletions(-) diff --git a/src/engine/e_log.pas b/src/engine/e_log.pas index aba6e0d..afc638c 100644 --- a/src/engine/e_log.pas +++ b/src/engine/e_log.pas @@ -133,7 +133,7 @@ begin while (len > 0) do begin if (len > 255) then slen := 255 else slen := Integer(len); - Move(b^, ss[1], len); + Move(b^, ss[1], slen); ss[0] := AnsiChar(slen); write(ss); b += slen; @@ -179,12 +179,13 @@ begin while (len > 0) do begin slen := 0; - while (slen < len) and (slen < 255) and (b[slen] <> 13) and (b[slen] <> 10) do Inc(slen); + while (slen < len) and (b[slen] <> 13) and (b[slen] <> 10) do Inc(slen); + if (slen > 255) then slen := 255; // print string if (slen > 0) then begin if xlogWantSpace then begin write(xlogFile, ' '); xlogWantSpace := false; end; - Move(b^, ss[1], len); + Move(b^, ss[1], slen); ss[0] := AnsiChar(slen); write(xlogFile, ss); b += slen; @@ -194,16 +195,8 @@ begin // process newline if (len > 0) and ((b[0] = 13) or (b[0] = 10)) then begin - if (len > 1) and (b[0] = 13) and (b[1] = 10) then - begin - len -= 2; - b += 2; - end - else - begin - len -= 1; - b += 1; - end; + if (b[0] = 13) then begin len -= 1; b += 1; end; + if (len > 0) and (b[0] = 10) then begin len -= 1; b += 1; end; xlogLastWasEOL := false; writeln(xlogFile, ''); write(xlogFile, xlogPrefix); diff --git a/src/game/Doom2DF.dpr b/src/game/Doom2DF.dpr index e5ecf92..bfd07a5 100644 --- a/src/game/Doom2DF.dpr +++ b/src/game/Doom2DF.dpr @@ -102,6 +102,7 @@ uses hashtable in '../shared/hashtable.pas', idpool in '../shared/idpool.pas', xparser in '../shared/xparser.pas', + xdynrec in '../shared/xdynrec.pas', BinEditor in '../shared/BinEditor.pas', envvars in '../shared/envvars.pas', g_panel in 'g_panel.pas', diff --git a/src/game/g_map.pas b/src/game/g_map.pas index 4f86e9b..4bb340d 100644 --- a/src/game/g_map.pas +++ b/src/game/g_map.pas @@ -208,7 +208,7 @@ uses GL, GLExt, g_weapons, g_game, g_sound, e_sound, CONFIG, g_options, MAPREADER, g_triggers, g_player, MAPDEF, Math, g_monsters, g_saveload, g_language, g_netmsg, - utils, sfs, + utils, sfs, xparser, xdynrec, xstreams, ImagingTypes, Imaging, ImagingUtility, ImagingGif, ImagingNetworkGraphics; @@ -218,6 +218,47 @@ const FLAG_SIGNATURE = $47414C46; // 'FLAG' +{$IF DEFINED(D2D_NEW_MAP_READER)} +var + dfmapdef: TDynMapDef = nil; + + +procedure loadMapDefinition (); +var + pr: TTextParser = nil; + st: TStream = nil; + WAD: TWADFile = nil; +begin + if (dfmapdef <> nil) then exit; + try + e_LogWritefln('parsing "mapdef.txt"...', []); + st := openDiskFileRO(DataDir+'mapdef.txt'); + except + st := nil; + e_LogWritefln('local "%smapdef.txt" not found', [DataDir]); + end; + if (st = nil) then + begin + WAD := TWADFile.Create(); + if not WAD.ReadFile(GameWAD) then raise Exception.Create('cannot load "game.wad"'); + st := WAD.openFileStream('mapdef.txt'); + end; + + if (st = nil) then raise Exception.Create('cannot open "mapdef.txt"'); + pr := TFileTextParser.Create(st); + + try + dfmapdef := TDynMapDef.Create(pr); + except on e: Exception do + raise Exception.Create(Format('ERROR in "mapdef.txt" at (%s,%s): %s', [pr.line, pr.col, e.message])); + end; + + st.Free(); + WAD.Free(); +end; +{$ENDIF} + + function panelTypeToTag (panelType: Word): Integer; begin case panelType of @@ -1274,12 +1315,15 @@ var Len: Integer; ok, isAnim, trigRef: Boolean; CurTex, ntn: Integer; - + {$IF DEFINED(D2D_NEW_MAP_READER)} + pr: TTextParser = nil; + st: TStream = nil; + wst: TSFSMemoryChunkStream = nil; + rec: TDynRecord; + {$ENDIF} begin mapGrid.Free(); mapGrid := nil; - //mapTree.Free(); - //mapTree := nil; Result := False; gMapInfo.Map := Res; @@ -1300,6 +1344,7 @@ begin WAD.Free(); Exit; end; + //k8: why loader ignores path here? mapResName := g_ExtractFileName(Res); if not WAD.GetMapResource(mapResName, Data, Len) then @@ -1311,9 +1356,67 @@ begin WAD.Free(); - // Çàãðóçêà êàðòû: - e_WriteLog('Loading map: '+mapResName, MSG_NOTIFY); + if (Len < 4) then + begin + e_LogWritefln('invalid map file: ''%s''', [mapResName]); + FreeMem(Data); + exit; + end; + + // Çàãðóçêà êàðòû: + e_LogWritefln('Loading map: %s', [mapResName], MSG_NOTIFY); g_Game_SetLoadingText(_lc[I_LOAD_MAP], 0, False); + + {$IF DEFINED(D2D_NEW_MAP_READER)} + if (PChar(Data)[0] = 'M') and (PChar(Data)[1] = 'A') and (PChar(Data)[2] = 'P') and (PByte(Data)[3] = 1) then + begin + // nothing + end + else + begin + e_LogWritefln('Loading text map: %s', [mapResName]); + loadMapDefinition(); + if (dfmapdef = nil) then raise Exception.Create('internal map loader error'); + //e_LogWritefln('***'#10'%s'#10'***', [dfmapdef.headerType.definition]); + wst := TSFSMemoryChunkStream.Create(Data, Len); + try + pr := TFileTextParser.Create(wst); + e_LogWritefln('parsing text map: %s', [mapResName]); + rec := dfmapdef.parseMap(pr); + except on e: Exception do + begin + if (pr <> nil) then e_LogWritefln('ERROR at (%s,%s): %s', [pr.line, pr.col, e.message]) + else e_LogWritefln('ERROR: %s', [e.message]); + pr.Free(); + wst.Free(); + FreeMem(Data); + exit; + end; + end; + pr.Free(); + //wst.Free(); // pr will do it + e_LogWritefln('writing text map to temporary bin storage...', []); + st := TMemoryStream.Create(); + try + rec.writeBinTo(st); + Len := Integer(st.position); + st.position := 0; + FreeMem(Data); + GetMem(Data, Len); + st.ReadBuffer(Data^, Len); + except on e: Exception do + begin + rec.Free(); + st.Free(); + e_LogWritefln('ERROR: %s', [e.message]); + FreeMem(Data); + exit; + end; + end; + st.Free(); + end; + {$ENDIF} + MapReader := TMapReader_1.Create(); if not MapReader.LoadMap(Data) then @@ -2083,15 +2186,7 @@ procedure g_Map_CollectDrawPanels (x0, y0, wdt, hgt: Integer); begin dplClear(); //tagmask := panelTypeToTag(PanelType); - - {if gdbg_map_use_tree_draw then - begin - mapTree.aabbQuery(x0, y0, wdt, hgt, checker, (GridTagBack or GridTagStep or GridTagWall or GridTagDoor or GridTagAcid1 or GridTagAcid2 or GridTagWater or GridTagFore)); - end - else} - begin - mapGrid.forEachInAABB(x0, y0, wdt, hgt, checker, GridDrawableMask); - end; + mapGrid.forEachInAABB(x0, y0, wdt, hgt, checker, GridDrawableMask); // list will be rendered in `g_game.DrawPlayer()` end; @@ -2105,14 +2200,7 @@ procedure g_Map_DrawPanelShadowVolumes(lightX: Integer; lightY: Integer; radius: end; begin - {if gdbg_map_use_tree_draw then - begin - mapTree.aabbQuery(lightX-radius, lightY-radius, radius*2, radius*2, checker, (GridTagWall or GridTagDoor)); - end - else} - begin - mapGrid.forEachInAABB(lightX-radius, lightY-radius, radius*2, radius*2, checker, (GridTagWall or GridTagDoor)); - end; + mapGrid.forEachInAABB(lightX-radius, lightY-radius, radius*2, radius*2, checker, (GridTagWall or GridTagDoor)); end; @@ -2326,43 +2414,30 @@ begin if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('*solids'); if gdbg_map_use_accel_coldet then begin - {if gdbg_map_use_tree_coldet then + if (Width = 1) and (Height = 1) then begin - //e_WriteLog(Format('coldet query: x=%d; y=%d; w=%d; h=%d', [X, Y, Width, Height]), MSG_NOTIFY); - result := (mapTree.aabbQuery(X, Y, Width, Height, checker, tagmask) <> nil); - if (gdbg_map_dump_coldet_tree_queries) and (mapTree.nodesVisited <> 0) then + if ((tagmask and SlowMask) <> 0) then + begin + // slow + result := (mapGrid.forEachAtPoint(X, Y, checker, tagmask) <> nil); + end + else begin - //e_WriteLog(Format('map collision: %d nodes visited (%d deep)', [mapTree.nodesVisited, mapTree.nodesDeepVisited]), MSG_NOTIFY); - g_Console_Add(Format('map collision: %d nodes visited (%d deep)', [mapTree.nodesVisited, mapTree.nodesDeepVisited])); + // fast + result := (mapGrid.forEachAtPoint(X, Y, nil, tagmask) <> nil); end; end - else} + else begin - if (Width = 1) and (Height = 1) then + if ((tagmask and SlowMask) <> 0) then begin - if ((tagmask and SlowMask) <> 0) then - begin - // slow - result := (mapGrid.forEachAtPoint(X, Y, checker, tagmask) <> nil); - end - else - begin - // fast - result := (mapGrid.forEachAtPoint(X, Y, nil, tagmask) <> nil); - end; + // slow + result := (mapGrid.forEachInAABB(X, Y, Width, Height, checker, tagmask) <> nil); end else begin - if ((tagmask and SlowMask) <> 0) then - begin - // slow - result := (mapGrid.forEachInAABB(X, Y, Width, Height, checker, tagmask) <> nil); - end - else - begin - // fast - result := (mapGrid.forEachInAABB(X, Y, Width, Height, nil, tagmask) <> nil); - end; + // fast + result := (mapGrid.forEachInAABB(X, Y, Width, Height, nil, tagmask) <> nil); end; end; end @@ -2407,20 +2482,13 @@ begin if gdbg_map_use_accel_coldet then begin texid := TEXTURE_NONE; - {if gdbg_map_use_tree_coldet then + if (Width = 1) and (Height = 1) then begin - mapTree.aabbQuery(X, Y, Width, Height, checker, (GridTagWater or GridTagAcid1 or GridTagAcid2)); + mapGrid.forEachAtPoint(X, Y, checker, (GridTagWater or GridTagAcid1 or GridTagAcid2)); end - else} + else begin - if (Width = 1) and (Height = 1) then - begin - mapGrid.forEachAtPoint(X, Y, checker, (GridTagWater or GridTagAcid1 or GridTagAcid2)); - end - else - begin - mapGrid.forEachInAABB(X, Y, Width, Height, checker, (GridTagWater or GridTagAcid1 or GridTagAcid2)); - end; + mapGrid.forEachInAABB(X, Y, Width, Height, checker, (GridTagWater or GridTagAcid1 or GridTagAcid2)); end; result := texid; end diff --git a/src/shared/a_modes.inc b/src/shared/a_modes.inc index a0ea220..2834ce0 100644 --- a/src/shared/a_modes.inc +++ b/src/shared/a_modes.inc @@ -63,6 +63,8 @@ {$WARNINGS ON} {$NOTES ON} +// include support for text maps +{$DEFINE D2D_NEW_MAP_READER} {$IFDEF MSWINDOWS} {$IFNDEF WINDOWS} diff --git a/src/shared/mapdef.txt b/src/shared/mapdef.txt index 4e17287..d93b48d 100644 --- a/src/shared/mapdef.txt +++ b/src/shared/mapdef.txt @@ -134,6 +134,7 @@ enum TextureSpecial { enum DirType { DIR_LEFT, // 0 DIR_RIGHT, // 1 + DIR_SOMETHING2, // 2 } // triggers diff --git a/src/shared/utils.pas b/src/shared/utils.pas index d73d257..9e13649 100644 --- a/src/shared/utils.pas +++ b/src/shared/utils.pas @@ -1039,7 +1039,7 @@ var while (len > 0) do begin if (len > 255) then slen := 255 else slen := Integer(len); - Move(b^, ss[1], len); + Move(b^, ss[1], slen); ss[0] := AnsiChar(slen); result += ss; b += slen; diff --git a/src/shared/wadreader.pas b/src/shared/wadreader.pas index df0de0f..3830af2 100644 --- a/src/shared/wadreader.pas +++ b/src/shared/wadreader.pas @@ -22,7 +22,7 @@ unit wadreader; interface uses - sfs, xstreams; + sfs, xstreams, Classes; type @@ -51,6 +51,9 @@ type function GetMapResource (name: AnsiString; var pData: Pointer; var Len: Integer): Boolean; function GetMapResources (): SArray; + // returns `nil` if file wasn't found + function openFileStream (name: AnsiString): TStream; + property isOpen: Boolean read getIsOpen; end; @@ -73,7 +76,7 @@ var implementation uses - SysUtils, Classes{, BinEditor}, e_log{, g_options}, utils, MAPSTRUCT; + SysUtils, e_log, utils, MAPSTRUCT; function findDiskWad (fname: AnsiString): AnsiString; @@ -231,19 +234,21 @@ begin fFileName := ''; end; + +//FIXME: detect text maps properly here function TWADFile.isMapResource (idx: Integer): Boolean; var sign: packed array [0..2] of Char; - fs: TStream; + fs: TStream = nil; begin result := false; if not isOpen or (fIter = nil) then exit; if (idx < 0) or (idx >= fIter.Count) then exit; - fs := nil; try fs := fIter.volume.OpenFileByIndex(idx); fs.readBuffer(sign, 3); result := (sign = MAP_SIGNATURE); + if not result then result := (sign[0] = 'm') and (sign[1] = 'a') and (sign[2] = 'p'); except if fs <> nil then fs.Free(); exit; @@ -251,6 +256,32 @@ begin fs.Free(); end; + +// returns `nil` if file wasn't found +function TWADFile.openFileStream (name: AnsiString): TStream; +var + f: Integer; + fi: TSFSFileInfo; +begin + result := nil; + // backwards, due to possible similar names and such + for f := fIter.Count-1 downto 0 do + begin + fi := fIter.Files[f]; + if fi = nil then continue; + if StrEquCI1251(fi.name, name) then + begin + try + result := fIter.volume.OpenFileByIndex(f); + except + result := nil; + end; + if (result <> nil) then exit; + end; + end; +end; + + function removeExt (s: AnsiString): AnsiString; var i: Integer; @@ -265,6 +296,7 @@ begin result := s; end; + function TWADFile.GetResourceEx (name: AnsiString; wantMap: Boolean; var pData: Pointer; var Len: Integer): Boolean; var f, lastSlash: Integer; @@ -272,7 +304,7 @@ var fs: TStream; fpp: Pointer; rpath, rname: AnsiString; - sign: array [0..2] of Char; + sign: packed array [0..2] of Char; goodMap: Boolean; begin Result := False; @@ -331,21 +363,26 @@ begin if wantMap then begin goodMap := false; - //e_WriteLog(Format('DFWAD: checking for good map in wad [%s], file [%s] (#%d)', [fFileName, fi.fname, f]), MSG_NOTIFY); + {$IF DEFINED(D2D_NEW_MAP_READER_DBG)} + e_LogWritefln('DFWAD: checking for good map in wad [%s], file [%s] (#%d)', [fFileName, fi.fname, f]); + {$ENDIF} try fs.readBuffer(sign, 3); goodMap := (sign = MAP_SIGNATURE); - { + if not goodMap then goodMap := (sign[0] = 'm') and (sign[1] = 'a') and (sign[2] = 'p'); + {$IF DEFINED(D2D_NEW_MAP_READER_DBG)} if goodMap then - e_WriteLog(Format(' GOOD map in wad [%s], file [%s] (#%d)', [fFileName, fi.fname, f]), MSG_NOTIFY) + e_LogWritefln(' GOOD map in wad [%s], file [%s] (#%d)', [fFileName, fi.fname, f]) else - e_WriteLog(Format(' BAD map in wad [%s], file [%s] (#%d)', [fFileName, fi.fname, f]), MSG_NOTIFY); - } + e_LogWritefln(' BAD map in wad [%s], file [%s] (#%d)', [fFileName, fi.fname, f]); + {$ENDIF} except end; if not goodMap then begin - //e_WriteLog(Format(' not a map in wad [%s], file [%s] (#%d)', [fFileName, fi.fname, f]), MSG_NOTIFY); + {$IF DEFINED(D2D_NEW_MAP_READER_DBG)} + e_LogWritefln(' not a map in wad [%s], file [%s] (#%d)', [fFileName, fi.fname, f]); + {$ENDIF} fs.Free(); continue; end; @@ -420,7 +457,9 @@ begin fi := fIter.Files[f]; if fi = nil then continue; if length(fi.name) = 0 then continue; - //e_WriteLog(Format('DFWAD: checking for map in wad [%s], file [%s] (#%d)', [fFileName, fi.fname, f]), MSG_NOTIFY); + {$IF DEFINED(D2D_NEW_MAP_READER)} + //e_LogWritefln('DFWAD: checking for map in wad [%s], file [%s] (#%d)', [fFileName, fi.fname, f]); + {$ENDIF} if isMapResource(f) then begin s := removeExt(fi.name); diff --git a/src/shared/xdynrec.pas b/src/shared/xdynrec.pas index c689757..3a31a96 100644 --- a/src/shared/xdynrec.pas +++ b/src/shared/xdynrec.pas @@ -124,10 +124,6 @@ type property internal: Boolean read mInternal write mInternal; property ival: Integer read mIVal; property sval: AnsiString read mSVal; - //property list: TDynRecordArray read mRVal write mRVal; - property maxdim: Integer read mMaxDim; // for fixed-size arrays - property binOfs: Integer read mBinOfs; // offset in binary; <0 - none - property recOfs: Integer read mRecOfs; // offset in record; <0 - none property hasDefault: Boolean read mHasDefault; property defsval: AnsiString read mDefSVal; property ebs: TEBS read mEBS; @@ -1222,6 +1218,7 @@ var es: TDynEBS = nil; tfld: TDynField; tk: AnsiString; + edim: AnsiChar; begin // if this field should contain struct, convert type and parse struct case mEBS of @@ -1412,7 +1409,7 @@ begin TType.TPoint, TType.TSize: begin - pr.expectDelim('('); + if pr.eatDelim('[') then edim := ']' else begin pr.expectDelim('('); edim := ')'; end; mIVal := pr.expectInt(); if (mType = TType.TSize) then begin @@ -1424,7 +1421,7 @@ begin if (mIVal2 < 0) or (mIVal2 > 32767) then raise Exception.Create(Format('invalid %s value for field ''%s''', [getTypeName(mType), mName])); end; mDefined := true; - pr.expectDelim(')'); + pr.expectDelim(edim); pr.expectTT(pr.TTSemi); exit; end; diff --git a/src/shared/xparser.pas b/src/shared/xparser.pas index d8b600d..d0f0d3a 100644 --- a/src/shared/xparser.pas +++ b/src/shared/xparser.pas @@ -102,8 +102,14 @@ type // ////////////////////////////////////////////////////////////////////////// // type TFileTextParser = class(TTextParser) + private + const BufSize = 65536; + private mFile: TStream; + mBuffer: PChar; + mBufLen: Integer; + mBufPos: Integer; protected procedure loadNextChar (); override; // loads next char into mNextChar; #0 means 'eof' @@ -587,7 +593,12 @@ end; // ////////////////////////////////////////////////////////////////////////// // constructor TFileTextParser.Create (const fname: AnsiString; loadToken: Boolean=true); begin + mBuffer := nil; mFile := openDiskFileRO(fname); + GetMem(mBuffer, BufSize); + mBufPos := 0; + mBufLen := mFile.Read(mBuffer^, BufSize); + if (mBufLen < 0) then raise Exception.Create('TFileTextParser: read error'); inherited Create(loadToken); end; @@ -596,23 +607,35 @@ constructor TFileTextParser.Create (st: TStream; loadToken: Boolean=true); begin if (st = nil) then raise Exception.Create('cannot create parser for nil stream'); mFile := st; + GetMem(mBuffer, BufSize); + mBufPos := 0; + mBufLen := mFile.Read(mBuffer^, BufSize); + if (mBufLen < 0) then raise Exception.Create('TFileTextParser: read error'); inherited Create(loadToken); end; destructor TFileTextParser.Destroy (); begin + if (mBuffer <> nil) then FreeMem(mBuffer); mFile.Free(); inherited; end; procedure TFileTextParser.loadNextChar (); -var - rd: Integer; begin - rd := mFile.Read(mNextChar, 1); - if (rd = 0) then begin mNextChar := #0; exit; end; + if (mBufLen = 0) then begin mNextChar := #0; exit; end; + if (mBufPos >= mBufLen) then + begin + mBufLen := mFile.Read(mBuffer^, BufSize); + if (mBufLen < 0) then raise Exception.Create('TFileTextParser: read error'); + if (mBufLen = 0) then begin mNextChar := #0; exit; end; + mBufPos := 0; + end; + assert(mBufPos < mBufLen); + mNextChar := mBuffer[mBufPos]; + Inc(mBufPos); if (mNextChar = #0) then mNextChar := ' '; end; -- 2.29.2