DEADSOFTWARE

map records and fields can have optional tooltips ("tip") and help ("help") in mapdef...
[d2df-sdl.git] / src / shared / xdynrec.pas
index 223dc51518c92d89b11e8ad9cb957c89c57ba1aa..e56266351a060fd6e0dad26351a58c1e6af99ed2 100644 (file)
@@ -71,6 +71,8 @@ type
   private
     mOwner: TDynRecord; // owner record
     mName: AnsiString; // field name
+    mTip: AnsiString; // short tip
+    mHelp: AnsiString; // long help
     mType: TType; // field type
     mIVal: Integer; // for all integer types
     mIVal2: Integer; // for point and size
@@ -167,7 +169,7 @@ type
 
     // binary parser and writer (DO NOT USE!)
     procedure parseBinValue (st: TStream);
-    procedure writeBinTo (st: TStream);
+    procedure writeBinTo (var hasLostData: Boolean; st: TStream);
 
   public
     // the following functions are here only for 'mapgen'! DO NOT USE!
@@ -197,6 +199,9 @@ type
     // field value as Variant
     property value: Variant read getVar write setVar;
 
+    property tip: AnsiString read mTip;
+    property help: AnsiString read mHelp;
+
   public
     // userdata (you can use these properties as you want to; they won't be written or read to files)
     property tagInt: Integer read mTagInt write mTagInt;
@@ -208,6 +213,7 @@ type
     property hasTPrefix: Boolean read mAsT;
     property separatePasFields: Boolean read mSepPosSize;
     property binOfs: Integer read mBinOfs;
+    property equToDefault: Boolean read isDefaultValue;
   end;
 
 
@@ -217,6 +223,8 @@ type
     mOwner: TDynMapDef;
     mId: AnsiString;
     mTypeName: AnsiString;
+    mTip: AnsiString; // short tip
+    mHelp: AnsiString; // long help
     mSize: Integer;
     mFields: TDynFieldList;
     {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
@@ -313,7 +321,7 @@ type
 
     // binary parser and writer (DO NOT USE!)
     procedure parseBinValue (st: TStream; forceData: Boolean=false);
-    procedure writeBinTo (st: TStream; trigbufsz: Integer=-1; onlyFields: Boolean=false);
+    procedure writeBinTo (var hasLostData: Boolean; st: TStream; trigbufsz: Integer=-1; onlyFields: Boolean=false);
 
   public
     property mapdef: TDynMapDef read mOwner;
@@ -330,6 +338,9 @@ type
     property headerRec: TDynRecord read mHeaderRec; // get header record for this one (header contains all other records, enums, bitsets, etc.)
     property isHeader: Boolean read mHeader; // is this a header record?
 
+    property tip: AnsiString read mTip;
+    property help: AnsiString read mHelp;
+
   public
     // user fields; user can add arbitrary custom fields
     // by default, any user field will be marked as "internal"
@@ -349,6 +360,8 @@ type
     mOwner: TDynMapDef;
     mIsEnum: Boolean;
     mTypeName: AnsiString;
+    mTip: AnsiString; // short tip
+    mHelp: AnsiString; // long help
     mIds: array of AnsiString;
     mVals: array of Integer;
     mMaxName: AnsiString; // MAX field
@@ -380,6 +393,9 @@ type
     property isEnum: Boolean read mIsEnum; // is this enum? `false` means "bitset"
     property has[const aname: AnsiString]: Boolean read hasByName;
     property field[const aname: AnsiString]: Integer read getFieldByName;
+
+    property tip: AnsiString read mTip;
+    property help: AnsiString read mHelp;
   end;
 
 
@@ -421,7 +437,7 @@ type
   public
     // parse text or binary map, return new header record
     // WARNING! stream must be seekable
-    function parseMap (st: TStream): TDynRecord;
+    function parseMap (st: TStream; wasBinary: PBoolean=nil): TDynRecord;
 
     // returns `true` if the given stream can be a map file
     // stream position is 0 on return
@@ -599,6 +615,8 @@ end;
 procedure TDynField.cleanup ();
 begin
   mName := '';
+  mTip := '';
+  mHelp := '';
   mType := TType.TInt;
   mIVal := 0;
   mIVal2 := 0;
@@ -642,6 +660,8 @@ begin
   result.mOwner := mOwner;
   if (newOwner <> nil) then result.mOwner := newOwner else result.mOwner := mOwner;
   result.mName := mName;
+  result.mTip := mTip;
+  result.mHelp := mHelp;
   result.mType := mType;
   result.mIVal := mIVal;
   result.mIVal2 := mIVal2;
@@ -1070,6 +1090,7 @@ var
   asmonid: Boolean;
   defech: AnsiChar;
   xalias: AnsiString;
+  atip, ahelp: AnsiString;
 begin
   fldname := '';
   fldtype := '';
@@ -1091,6 +1112,8 @@ begin
   lmaxdim := -1;
   lebs := TDynField.TEBS.TNone;
   xalias := '';
+  atip := '';
+  ahelp := '';
 
   // field name
   fldname := pr.expectStrOrId();
@@ -1120,6 +1143,20 @@ begin
       continue;
     end;
 
+    if pr.eatId('tip') then
+    begin
+      if (Length(atip) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate tip definition for field ''%s''', [fldname]);
+      atip := pr.expectStr(false);
+      continue;
+    end;
+
+    if pr.eatId('help') then
+    begin
+      if (Length(ahelp) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate tip definition for field ''%s''', [fldname]);
+      ahelp := pr.expectStr(false);
+      continue;
+    end;
+
     if pr.eatId('offset') then
     begin
       if (fldofs >= 0) then raise TDynParseException.CreateFmt(pr, 'duplicate field ''%s'' offset', [fldname]);
@@ -1282,6 +1319,8 @@ begin
   self.mWriteDef := writedef;
   self.mInternal := ainternal;
   self.mAlias := xalias;
+  self.mTip := atip;
+  self.mHelp := ahelp;
 end;
 
 
@@ -1292,7 +1331,7 @@ begin
 end;
 
 
-procedure TDynField.writeBinTo (st: TStream);
+procedure TDynField.writeBinTo (var hasLostData: Boolean; st: TStream);
 var
   s: AnsiString;
   f: Integer;
@@ -1319,7 +1358,7 @@ begin
             if (mRecRef <> nil) then
             begin
               ws := TSFSMemoryChunkStream.Create(buf, mMaxDim);
-              mRecRef.writeBinTo(ws, mMaxDim); // as trigdata
+              mRecRef.writeBinTo(hasLostData, ws, mMaxDim); // as trigdata
             end;
             st.WriteBuffer(buf^, mMaxDim);
           finally
@@ -1434,15 +1473,9 @@ begin
         exit;
       end;
     TType.TList:
-      begin
-        assert(false);
-        exit;
-      end;
+      raise TDynRecException.Create('cannot write lists to binary format');
     TType.TTrigData:
-      begin
-        assert(false);
-        exit;
-      end;
+      raise TDynRecException.Create('cannot write triggers to binary format (internal error)');
     else raise TDynRecException.Create('ketmar forgot to handle some field type');
   end;
 end;
@@ -1526,7 +1559,7 @@ begin
             begin
               if (es.mVals[f] = mask) then
               begin
-                if not first then wr.put('+') else first := false;
+                if not first then wr.put(' | ') else first := false;
                 wr.put(es.mIds[f]);
                 found := true;
                 break;
@@ -2205,6 +2238,8 @@ begin
   result.mOwner := mOwner;
   result.mId := mId;
   result.mTypeName := mTypeName;
+  result.mTip := mTip;
+  result.mHelp := mHelp;
   result.mSize := mSize;
   result.mHeader := mHeader;
   result.mBinBlock := mBinBlock;
@@ -2477,6 +2512,18 @@ begin
         if (mBinBlock < 1) then raise TDynParseException.CreateFmt(pr, 'invalid record ''%s'' binblock: %d', [mTypeName, mBinBlock]);
         continue;
       end;
+      if pr.eatId('tip') then
+      begin
+        if (Length(mTip) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate tip definition for record ''%s''', [mTypeName]);
+        mTip := pr.expectStr(false);
+        continue;
+      end;
+      if pr.eatId('help') then
+      begin
+        if (Length(mHelp) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate help definition for record ''%s''', [mTypeName]);
+        mHelp := pr.expectStr(false);
+        continue;
+      end;
     end;
   end;
 
@@ -2676,14 +2723,13 @@ begin
 end;
 
 
-procedure TDynRecord.writeBinTo (st: TStream; trigbufsz: Integer=-1; onlyFields: Boolean=false);
+procedure TDynRecord.writeBinTo (var hasLostData: Boolean; st: TStream; trigbufsz: Integer=-1; onlyFields: Boolean=false);
 var
   fld: TDynField;
   rec, rv: TDynRecord;
   buf: PByte = nil;
   ws: TStream = nil;
   blk, blkmax: Integer;
-  //f, c: Integer;
   bufsz: Integer = 0;
   blksz: Integer;
 begin
@@ -2708,11 +2754,15 @@ begin
       // record list?
       if (fld.mType = fld.TType.TList) then continue; // later
       if fld.mInternal then continue;
-      if (fld.mBinOfs < 0) then continue;
+      if (fld.mBinOfs < 0) then
+      begin
+        if not fld.equToDefault then hasLostData := true;
+        continue;
+      end;
       if (fld.mBinOfs >= bufsz) then raise TDynRecException.Create('binary value offset is outside of the buffer');
       TSFSMemoryChunkStream(ws).setup(buf+fld.mBinOfs, bufsz-fld.mBinOfs);
       //writeln('writing field <', fld.mName, '>');
-      fld.writeBinTo(ws);
+      fld.writeBinTo(hasLostData, ws);
     end;
 
     // write block with normal fields
@@ -2762,7 +2812,7 @@ begin
             if (rec = nil) then continue;
             if (rec.mBinBlock <> blk) then continue;
             if (ws = nil) then ws := TMemoryStream.Create();
-            for rv in fld.mRVal do rv.writeBinTo(ws);
+            for rv in fld.mRVal do rv.writeBinTo(hasLostData, ws);
           end;
         end;
         // flush block
@@ -2794,6 +2844,8 @@ procedure TDynRecord.writeTo (wr: TTextWriter; putHeader: Boolean=true);
 var
   fld: TDynField;
   rec: TDynRecord;
+  putTypeComment: Boolean;
+  f: Integer;
 begin
   if putHeader then
   begin
@@ -2810,11 +2862,31 @@ begin
       if (fld.mType = fld.TType.TList) then
       begin
         if not mHeader then raise TDynRecException.Create('record list in non-header record');
-        if (fld.mRVal <> nil) then
+        if (fld.mRVal <> nil) and (fld.mRVal.count > 0) then
         begin
+          putTypeComment := true;
           for rec in fld.mRVal do
           begin
-            if (Length(rec.mId) = 0) then continue;
+            if (rec = nil) or (Length(rec.mId) = 0) then continue;
+            if putTypeComment then
+            begin
+              wr.put(#10);
+              if (80-wr.curIndent*2 >= 2) then
+              begin
+                wr.putIndent();
+                for f := wr.curIndent to 80-wr.curIndent do wr.put('/');
+                wr.put(#10);
+              end;
+              putTypeComment := false;
+              wr.putIndent();
+              wr.put('// ');
+              wr.put(fld.name);
+              wr.put(#10);
+            end
+            else
+            begin
+              wr.put(#10);
+            end;
             wr.putIndent();
             rec.writeTo(wr, true);
           end;
@@ -3011,6 +3083,8 @@ procedure TDynEBS.cleanup ();
 begin
   mIsEnum := false;
   mTypeName := '';
+  mTip := '';
+  mHelp := '';
   mIds := nil;
   mVals := nil;
   mMaxName := '';
@@ -3115,6 +3189,22 @@ begin
   mTypeName := pr.expectId();
   mMaxVal := Integer($80000000);
   if mIsEnum then cv := 0 else cv := 1;
+  while (pr.tokType <> pr.TTBegin) do
+  begin
+    if pr.eatId('tip') then
+    begin
+      if (Length(mTip) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate tip definition for enum/bitset ''%s''', [mTypeName]);
+      mTip := pr.expectStr(false);
+      continue;
+    end;
+    if pr.eatId('help') then
+    begin
+      if (Length(mHelp) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate help definition for enum/bitset ''%s''', [mTypeName]);
+      mHelp := pr.expectStr(false);
+      continue;
+    end;
+    break;
+  end;
   pr.expectTT(pr.TTBegin);
   while (pr.tokType <> pr.TTEnd) do
   begin
@@ -3401,11 +3491,12 @@ end;
 
 
 // WARNING! stream must be seekable
-function TDynMapDef.parseMap (st: TStream): TDynRecord;
+function TDynMapDef.parseMap (st: TStream; wasBinary: PBoolean=nil): TDynRecord;
 var
   sign: packed array[0..3] of AnsiChar;
   pr: TTextParser;
 begin
+  if (wasBinary <> nil) then wasBinary^ := false;
   st.position := 0;
   st.ReadBuffer(sign[0], 4);
   st.position := 0;
@@ -3413,6 +3504,7 @@ begin
   begin
     if (sign[3] = #1) then
     begin
+      if (wasBinary <> nil) then wasBinary^ := true;
       result := parseBinMap(st);
       exit;
     end;