DEADSOFTWARE

the game is able to read text maps now (WARNING! the feature is still experimental!)
authorKetmar Dark <ketmar@ketmar.no-ip.org>
Tue, 29 Aug 2017 21:53:09 +0000 (00:53 +0300)
committerKetmar Dark <ketmar@ketmar.no-ip.org>
Tue, 29 Aug 2017 21:53:42 +0000 (00:53 +0300)
src/engine/e_log.pas
src/game/Doom2DF.dpr
src/game/g_map.pas
src/shared/a_modes.inc
src/shared/mapdef.txt
src/shared/utils.pas
src/shared/wadreader.pas
src/shared/xdynrec.pas
src/shared/xparser.pas

index aba6e0d99d3f794b3de7f85dbf69d99de4c32a57..afc638cac7be82a9d7af6a0f1eb764811adf4eb8 100644 (file)
@@ -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);
index e5ecf92df9006496a1f5577aa5657d1826efdbbb..bfd07a5b8f15c1a6668a003710606823d283b118 100644 (file)
@@ -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',
index 4f86e9b0d3f14ddc3f21e097756080f4996e2e12..4bb340d1026a97f86893f6843e0f0f3dfb93a61a 100644 (file)
@@ -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
index a0ea22057a20fde14b3d03f2a2de5dcf87937d7b..2834ce096081b14bcbdaba59c75d61a91587a233 100644 (file)
@@ -63,6 +63,8 @@
 {$WARNINGS ON}
 {$NOTES ON}
 
+// include support for text maps
+{$DEFINE D2D_NEW_MAP_READER}
 
 {$IFDEF MSWINDOWS}
   {$IFNDEF WINDOWS}
index 4e172871d6a9368ea2db43eef0a4b411e9030dc1..d93b48d58bf87449d3b2b8356176eb8f26dee0a3 100644 (file)
@@ -134,6 +134,7 @@ enum TextureSpecial {
 enum DirType {
   DIR_LEFT, // 0
   DIR_RIGHT, // 1
+  DIR_SOMETHING2, // 2
 }
 
 // triggers
index d73d25766e0734e972f0cf5a8a099d099d2a586e..9e136495bc8792dc661c3e4fa254ee58d24974a6 100644 (file)
@@ -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;
index df0de0fc591a48ffe3d16ce7f68ba412fd781b85..3830af240f6f924519c5704f81f1dc9683ab0dc5 100644 (file)
@@ -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);
index c6897578d7d6334e3f9f71f964e64753013b6338..3a31a9663c9c96ef3e0bd3d3ba2d5c8603dc679f 100644 (file)
@@ -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;
index d8b600dbc1d6340a0a16c9b8017dfde7bd9f401a..d0f0d3af202bef33b10715f495e13db098ce7dd3 100644 (file)
@@ -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;