1 (* Copyright (C) Doom 2D: Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *)
15 {$INCLUDE a_modes.inc}
19 interface
21 uses
27 // ////////////////////////////////////////////////////////////////////////// //
28 type
30 public
36 public
39 public
45 // ////////////////////////////////////////////////////////////////////////// //
46 type
56 // this is base type for all scalars (and arrays)
58 public
59 type
60 TType = (TBool, TChar, TByte, TUByte, TShort, TUShort, TInt, TUInt, TString, TPoint, TSize, TColor, TList, TTrigData);
61 // TPoint: pair of Integers
62 // TSize: pair of UShorts
63 // TList: actually, array of records
64 // TTrigData: array of mMaxDim bytes, but internally a record (mRecRef)
65 // in binary: arrays of chars are pascal shortstrings (with counter in the first byte)
67 private
68 type
71 private
95 mAsMonsterId: Boolean; // special hack for triggers: monster record number+1 in binary (so 0 means "none")
96 // default value
103 mEBSType: TObject; // either TDynRecord or TDynEBS; nil means "simple type"; nil for `TTrigData` too
105 // for binary parser
108 // for userdata
112 // for pasgen
115 private
118 procedure parseDefaultValue (); // parse `mDefUnparsed` to `mDefSVal`, `mDefIVal`, `mDefIVal2`, `mDefRecRef`
136 protected
137 // returns `true` for duplicate record id
141 public
142 // get string name for the given type
145 public
151 // clone this field; register all list records in `registerIn`
152 // "registration" is required to manage record lifetime; use header record if in doubt
153 // owner will be set to `newOwner`, if it is not `nil`, or to `owner`
154 // for lists, cloning will clone all list members
157 // compare field values (including trigdata)
158 // WARNING: won't work for lists
161 // parse string value to appropriate type and set new field value
164 // supports `for rec in field do` (for lists)
179 public
180 // text parser and writer
184 // binary parser and writer (DO NOT USE!)
188 public
189 // the following functions are here only for 'mapgen'! DO NOT USE!
190 // build "alias name" for pascal code
193 public
199 property ival: Integer read mIVal; // integer value for int field (for speed), first field (x/w) for `TPoint` and `TSize`
208 property hasDefault: Boolean read mHasDefault; // `true` if this field has default value in mapdef
211 property ebstype: TObject read mEBSType; // reference type (nil, TDynRecord, TDynEBS); WARNING: don't modify type!
213 property recref: TDynRecord read mRecRef write setRecRef; // referenced record (actual one, you can modify it)
214 property recrefIndex: Integer read getRecRefIndex; // index of referenced record in header; -1: not found
215 // for record lists
218 property item[const aname: AnsiString]: TDynRecord read getListItem; default; // alas, FPC 3+ lost property overloading feature
219 // field value as Variant
225 public
226 // userdata (you can use these properties as you want to; they won't be written or read to files)
230 public
231 // the following properties are here only for 'mapgen'! DO NOT USE!
240 // record, either with actual values, or with type definitions
242 private
250 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
252 {$ENDIF}
256 mHeaderRec: TDynRecord; // for "value" records this is header record with data, for "type" records this is header type record
258 // for userdata
264 private
287 protected
290 function addRecordByType (const atypename: AnsiString; rc: TDynRecord): Boolean; // `true`: duplicate record id
295 public
300 // clone this record; register all list records in `registerIn`
301 // "registration" is required to manage record lifetime; use header record if in doubt
302 // all fields are cloned too
305 // compare records (values of all fields, including trigdata)
306 // WARNING: won't work for records with list fields
309 // find field with `TriggerType` type
312 // number of records of the given instance
315 // only for headers: create new record with the given type
316 // will return cloned record ready for use, or `nil` on unknown type name
317 // `aid` must not be empty, and must be unique
320 // remove record with the given type and id
321 // return `true` if record was successfully found and removed
322 // this will do all necessary recref cleanup too
323 // WARNING: not tested yet
326 //TODO:
327 // [.] API to create triggers
328 // [.] API to properly remove triggers (remove trigdata)
329 // [.] check if `removeTypedRecord()` does the right thing with inline records
330 // [.] for fields: assigning `recref` should remove previously assigned inline record (record without id)
331 // [.] other API i forgot
333 public
334 // text parser
335 // `beginEaten`: `true` if "{" was eaten
338 // text writer
339 // `putHeader`: `true` to write complete header, otherwise only "{...}"
342 // binary parser and writer (DO NOT USE!)
344 procedure writeBinTo (var hasLostData: Boolean; st: TStream; trigbufsz: Integer=-1; onlyFields: Boolean=false);
346 public
350 property has[const aname: AnsiString]: Boolean read hasByName; // do we have field with the given name?
352 property field[const aname: AnsiString]: TDynField read getFieldByName; default; // get field by name
355 property isForTrig[const aname: AnsiString]: Boolean read getIsForTrig; // can this "TriggerData" be used for the trigger with the given type?
356 property forTrigCount: Integer read getForTrigCount; // number of trigger type names for "TriggerData"
357 property forTrigAt[idx: Integer]: AnsiString read getForTrigAt; // trigger type name at the given index for "TriggerData"
358 property headerRec: TDynRecord read mHeaderRec; // get header record for this one (header contains all other records, enums, bitsets, etc.)
364 public
365 // user fields; user can add arbitrary custom fields
366 // by default, any user field will be marked as "internal"
367 // note: you can use this to manipulate non-user fields too
370 public
371 // userdata (you can use these properties as you want to; they won't be written or read to files)
377 // bitset/enum definition
379 private
390 private
402 public
406 // find name for the given value
407 // return empty string if not found
410 public
422 // parsed "mapdef.txt"
424 public
429 private
443 // creates new header record
446 // creates new header record
449 public
457 public
458 // parse text or binary map, return new header record
459 // WARNING! stream must be seekable
462 // returns `true` if the given stream can be a map file
463 // stream position is 0 on return
464 // WARNING! stream must be seekable
467 public
468 // the following functions are here only for 'mapgen'! DO NOT USE!
471 public
473 // for record types
477 // for enum/bitset types
481 // for trigtypes
488 {$IF DEFINED(D2D_DYNREC_PROFILER)}
490 {$ENDIF}
492 var
495 implementation
497 {$IF DEFINED(D2D_DYNREC_PROFILER)}
498 uses
499 xprofiler;
500 {$ENDIF}
503 // ////////////////////////////////////////////////////////////////////////// //
507 // ////////////////////////////////////////////////////////////////////////// //
509 begin
514 begin
519 // ////////////////////////////////////////////////////////////////////////// //
521 begin
522 if (pr <> nil) then begin tokLine := pr.tokLine; tokCol := pr.tokCol; end else begin tokLine := 0; tokCol := 0; end;
526 constructor TDynParseException.CreateFmt (pr: TTextParser; const afmt: AnsiString; const args: array of const);
527 begin
528 if (pr <> nil) then begin tokLine := pr.tokLine; tokCol := pr.tokCol; end else begin tokLine := 0; tokCol := 0; end;
533 // ////////////////////////////////////////////////////////////////////////// //
535 begin
536 //result := TListEnumerator.Create(mRVal);
537 if (mRVal <> nil) then result := mRVal.GetEnumerator else result := TDynRecList.TEnumerator.Create(nil, 0);
541 // ////////////////////////////////////////////////////////////////////////// //
543 begin
551 begin
559 begin
567 begin
591 else
595 begin
632 begin
639 begin
683 var
685 begin
699 begin
734 var
737 begin
739 begin
740 if firstUp then result := UpCase1251(mAlias[1])+Copy(mAlias, 2, Length(mAlias)-1) else result := mAlias;
741 end
742 else
743 begin
747 begin
757 var
759 begin
761 TEBS.TNone: raise TDynRecException.CreateFmt('cannot set refrec for non-reference field ''%s''', [mName]);
763 begin
765 begin
767 if (trc = nil) then raise TDynRecException.CreateFmt('cannot set refrec for field ''%s'' (type conflict: improperly initialized field)', [mName]);
768 if (trc.typeName <> arec.typeName) then raise TDynRecException.CreateFmt('cannot set refrec for field ''%s'' (type conflict: expected ''%s'' got ''%s'')', [mName, trc.typeName, arec.typeName]);
772 exit;
774 TEBS.TEnum: raise TDynRecException.CreateFmt('cannot set refrec for enum field ''%s''', [mName]);
775 TEBS.TBitSet: raise TDynRecException.CreateFmt('cannot set refrec for bitset field ''%s''', [mName]);
782 begin
799 else result := Unassigned; raise TDynRecException.Create('ketmar forgot to handle some field type');
806 begin
830 else
834 begin
845 varString:
847 begin
849 end
850 else
851 begin
855 varBoolean:
867 else
872 varByte,
873 varWord,
874 varShortInt,
875 varSmallint,
876 varInteger:
878 varInt64:
881 else
883 varLongWord:
884 if (val > LongWord($7FFFFFFF)) then raise TDynRecException.Create('cannot convert longword variant to field value')
894 // won't work for lists
896 begin
913 result := ((mIVal = fld.mIVal) and (mIVal2 = fld.mIVal2) and (mIVal3 = fld.mIVal3) and (mIVal4 = fld.mIVal4));
916 begin
926 var
928 begin
930 try
932 finally
938 function TDynField.getRed (): Integer; inline; begin result := mIVal; if (result < 0) then result := 0 else if (result > 255) then result := 255; end;
939 procedure TDynField.setRed (v: Integer); inline; begin if (v < 0) then v := 0 else if (v > 255) then v := 255; mIVal := v; end;
941 function TDynField.getGreen (): Integer; inline; begin result := mIVal2; if (result < 0) then result := 0 else if (result > 255) then result := 255; end;
942 procedure TDynField.setGreen (v: Integer); inline; begin if (v < 0) then v := 0 else if (v > 255) then v := 255; mIVal2 := v; end;
944 function TDynField.getBlue (): Integer; inline; begin result := mIVal3; if (result < 0) then result := 0 else if (result > 255) then result := 255; end;
945 procedure TDynField.setBlue (v: Integer); inline; begin if (v < 0) then v := 0 else if (v > 255) then v := 255; mIVal3 := v; end;
947 function TDynField.getAlpha (): Integer; inline; begin result := mIVal4; if (result < 0) then result := 0 else if (result > 255) then result := 255; end;
948 procedure TDynField.setAlpha (v: Integer); inline; begin if (v < 0) then v := 0 else if (v > 255) then v := 255; mIVal4 := v; end;
952 var
958 begin
960 begin
967 end
968 else
969 begin
977 try
980 //if (mType = TType.TColor) then writeln('4=[', mIVal4, ']');
987 finally
1001 // default value should be parsed
1003 begin
1006 begin
1008 raise TDynRecException.CreateFmt('field ''%s'' in record ''%s'' of record type ''%s'' is not set', [mName, mOwner.mId, mOwner.mTypeName]);
1016 //if (mType = TType.TColor) then writeln('4=[', mDefIVal4, ']');
1021 // default value should be parsed
1023 begin
1029 TType.TColor: result := (mIVal = mDefIVal2) and (mIVal2 = mDefIVal2) and (mIVal3 = mDefIVal3) and (mIVal4 = mDefIVal4);
1037 begin
1043 begin
1044 if (mRVal <> nil) and (idx >= 0) and (idx < mRVal.count) then result := mRVal[idx] else result := nil;
1049 var
1051 begin
1057 begin
1060 begin
1068 var
1070 begin
1073 begin
1076 // fix hash and list
1078 begin
1088 begin
1110 begin
1120 TEBS.TBitSet: begin result += ' bitset '; if mBitSetUnique then result += 'unique '; result += mEBSTypeName; end;
1125 begin
1126 if (mType = TType.TPoint) then begin if (mAsT) then result += ' as txy' else result += ' as xy'; end
1127 else if (mType = TType.TSize) then begin if (mAsT) then result += ' as twh' else result += ' as wh'; end;
1135 var
1155 begin
1185 begin
1187 begin
1188 if (Length(fldtype) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate type definition for field ''%s''', [fldname]);
1193 begin
1195 // arbitrary limits
1196 if (lmaxdim < 1) or (lmaxdim > 32768) then raise TDynParseException.CreateFmt(pr, 'invalid field ''%s'' array size', [fldname]);
1198 end;
1199 continue;
1203 begin
1204 if (Length(xalias) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate alias definition for field ''%s''', [fldname]);
1206 continue;
1210 begin
1211 if (Length(atip) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate tip definition for field ''%s''', [fldname]);
1213 continue;
1217 begin
1218 if (Length(ahelp) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate tip definition for field ''%s''', [fldname]);
1220 continue;
1224 begin
1225 if (fldofs >= 0) then raise TDynParseException.CreateFmt(pr, 'duplicate field ''%s'' offset', [fldname]);
1227 if (fldofs < 0) then raise TDynParseException.CreateFmt(pr, 'invalid field ''%s'' offset', [fldname]);
1228 continue;
1232 begin
1239 continue;
1240 end;
1243 begin
1245 if (Length(fldrecname) <> 0) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' already typed as ''%s''', [fldname, fldrecname]);
1247 continue;
1251 begin
1253 if (Length(fldrecname) <> 0) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' already typed as ''%s''', [fldname, fldrecname]);
1256 continue;
1260 begin
1261 if hasdefStr or hasdefInt or hasdefId then raise TDynParseException.CreateFmt(pr, 'field ''%s'' has duplicate default', [fldname]);
1264 begin
1269 begin
1274 begin
1279 begin
1285 begin
1291 else
1294 continue;
1298 begin
1300 continue;
1304 begin
1306 continue;
1309 // record type, no special modifiers
1310 if (pr.tokType <> pr.TTId) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' has something unexpected in definition', [fldname]);
1312 if (Length(fldrecname) <> 0) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' already typed as ''%s''', [fldname, fldrecname]);
1319 // create field
1335 else
1336 begin
1337 // record types defaults to int
1339 begin
1341 end
1342 else
1343 begin
1344 if (Length(fldtype) = 0) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' has no type', [fldname])
1345 else raise TDynParseException.CreateFmt(pr, 'field ''%s'' has invalid type ''%s''', [fldname, fldtype]);
1349 // check for valid arrays
1350 if (lmaxdim > 0) and (mType <> TType.TChar) and (mType <> TType.TTrigData) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' of type ''%s'' cannot be array', [fldname, fldtype]);
1352 // check for valid trigdata or record type
1354 begin
1355 // trigdata
1356 if (lmaxdim < 1) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' of type ''%s'' cannot be non-array', [fldname, 'trigdata']);
1357 if (Length(fldrecname) > 0) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' of type ''%s'' cannot have another type', [fldname, 'trigdata']);
1359 end
1361 begin
1362 // record
1363 if not (mType in [TType.TByte, TType.TUByte, TType.TShort, TType.TUShort, TType.TInt, TType.TUInt]) then
1364 begin
1365 raise TDynParseException.CreateFmt(pr, 'field ''%s'' of record type ''%s'' cannot have type ''%s''', [fldname, fldrecname, fldtype]);
1369 // setup default value
1373 begin
1376 else if (mType = TType.TColor) then self.mDefUnparsed := Format('(%d %d %d %d)', [defint, defint2, defint3, defint4])
1394 end;
1398 begin
1405 var
1411 begin
1415 begin
1417 begin
1418 // this must be triggerdata
1420 begin
1421 raise TDynRecException.CreateFmt('record reference type ''%s'' in field ''%s'' cannot be written', [mEBSTypeName, mName]);
1423 // write triggerdata
1425 if (buf = nil) then raise TDynRecException.CreateFmt('record reference type ''%s'' in field ''%s'' cannot be written', [mEBSTypeName, mName]);
1426 try
1429 begin
1434 finally
1438 exit;
1440 // record reference
1448 else raise TDynRecException.CreateFmt('record reference type ''%s'' in field ''%s'' cannot be written', [mEBSTypeName, mName]);
1450 // find record number
1452 begin
1454 if (f < 0) then raise TDynRecException.CreateFmt('record reference type ''%s'' in field ''%s'' not found in record list', [mEBSTypeName, mName]);
1456 if (f > maxv) then raise TDynRecException.CreateFmt('record reference type ''%s'' in field ''%s'' has too big index', [mEBSTypeName, mName]);
1457 end
1458 else
1459 begin
1466 else raise TDynRecException.CreateFmt('record reference type ''%s'' in field ''%s'' cannot be written', [mEBSTypeName, mName]);
1468 exit;
1477 begin
1479 begin
1481 end
1482 else
1483 begin
1486 exit;
1489 begin
1490 if (mMaxDim = 0) then raise TDynRecException.CreateFmt('invalid string size definition for field ''%s''', [mName]);
1492 begin
1493 if (Length(mSVal) <> 1) then raise TDynRecException.CreateFmt('invalid string size definition for field ''%s''', [mName]);
1495 end
1496 else
1497 begin
1498 if (Length(mSVal) > mMaxDim) then raise TDynRecException.CreateFmt('invalid string size definition for field ''%s''', [mName]);
1503 exit;
1507 begin
1508 // triggerdata array was processed earlier
1509 if (mMaxDim >= 0) then TDynRecException.CreateFmt('byte array in field ''%s'' cannot be written', [mName]);
1511 exit;
1515 begin
1516 if (mMaxDim >= 0) then raise TDynRecException.CreateFmt('short array in field ''%s'' cannot be written', [mName]);
1518 exit;
1522 begin
1523 if (mMaxDim >= 0) then raise TDynRecException.CreateFmt('int array in field ''%s'' cannot be written', [mName]);
1525 exit;
1528 begin
1532 begin
1533 if (mMaxDim >= 0) then raise TDynRecException.CreateFmt('pos/size array in field ''%s'' cannot be written', [mName]);
1536 exit;
1539 begin
1540 if (mMaxDim >= 0) then raise TDynRecException.CreateFmt('pos/size array in field ''%s'' cannot be written', [mName]);
1543 exit;
1546 begin
1547 if (mMaxDim >= 0) then raise TDynRecException.CreateFmt('color array in field ''%s'' cannot be written', [mName]);
1551 //writeInt(st, Byte(mIVal4)); // the only place we have RGB in binary map is effect trigger, and it has no alpha
1553 exit;
1565 var
1569 begin
1575 begin
1577 begin
1579 end
1581 begin
1583 end
1584 else
1585 begin
1589 exit;
1592 begin
1593 //def := mOwner.mOwner;
1594 //es := def.ebsType[mEBSTypeName];
1597 if (es = nil) or (not es.mIsEnum) then raise TDynRecException.CreateFmt('record enum type ''%s'' for field ''%s'' not found', [mEBSTypeName, mName]);
1599 begin
1601 begin
1604 exit;
1607 raise TDynRecException.CreateFmt('value %d in record enum type ''%s'' for field ''%s'' not found', [mIVal, mEBSTypeName, mName]);
1610 begin
1611 //def := mOwner.mOwner;
1612 //es := def.ebsType[mEBSTypeName];
1615 if (es = nil) or es.mIsEnum then raise TDynRecException.CreateFmt('record bitset type ''%s'' for field ''%s'' not found', [mEBSTypeName, mName]);
1616 // none?
1618 begin
1620 begin
1622 begin
1625 exit;
1628 raise TDynRecException.CreateFmt('value %d in record bitset type ''%s'' for field ''%s'' not found', [0, mEBSTypeName, mName]);
1630 // not none
1634 begin
1636 begin
1639 begin
1641 begin
1645 break;
1648 if not found then raise TDynRecException.CreateFmt('value %d in record bitset type ''%s'' for field ''%s'' not found', [mask, mEBSTypeName, mName]);
1653 exit;
1660 begin
1662 exit;
1665 begin
1666 if (mMaxDim = 0) then raise TDynRecException.CreateFmt('invalid string size definition for field ''%s''', [mName]);
1669 exit;
1677 begin
1679 exit;
1682 begin
1685 exit;
1689 begin
1691 exit;
1694 begin
1697 exit;
1700 begin
1702 exit;
1705 begin
1707 exit;
1716 var
1723 begin
1727 begin
1728 // this must be triggerdata
1730 begin
1733 // find trigger definition
1735 if (tfld = nil) then raise TDynRecException.CreateFmt('triggerdata value for field ''%s'' in record ''%s'' without TriggerType field', [mName, rec.mTypeName]);
1737 if (rc = nil) then raise TDynRecException.CreateFmt('triggerdata definition for field ''%s'' in record ''%s'' with type ''%s'' not found', [mName, rec.mTypeName, tfld.mSVal]);
1740 // on error, it will be freed by memowner
1744 exit;
1745 end
1746 else
1747 begin
1748 // not a trigger data
1756 else raise TDynRecException.CreateFmt('invalid non-numeric type ''%s'' for field ''%s'' of record ''%s''', [getTypeName(mType), mName, mEBSTypeName]);
1762 exit;
1766 begin
1775 else raise TDynRecException.CreateFmt('invalid non-numeric type ''%s'' for field ''%s'' of record ''%s''', [getTypeName(mType), mName, mEBSTypeName]);
1779 if (es = nil) or (es.mIsEnum <> (mEBS = TEBS.TEnum)) then raise TDynRecException.CreateFmt('record enum type ''%s'' for field ''%s'' not found', [mEBSTypeName, mName]);
1781 // build enum/bitfield values
1783 begin
1785 if (Length(mSVal) = 0) then raise TDynRecException.CreateFmt('record enum type ''%s'' for field ''%s'' has invalid value %d', [mEBSTypeName, mName, mIVal]);
1786 end
1787 else
1788 begin
1789 // special for 'none'
1791 begin
1793 if (Length(mSVal) = 0) then raise TDynRecException.CreateFmt('record bitset type ''%s'' for field ''%s'' has invalid value %d', [mEBSTypeName, mName, mIVal]);
1794 end
1795 else
1796 begin
1800 begin
1802 begin
1804 if (Length(s) = 0) then raise TDynRecException.CreateFmt('record bitset type ''%s'' for field ''%s'' has invalid value %d', [mEBSTypeName, mName, mask]);
1812 //writeln('ebs <', es.mName, '>: ', mSVal);
1814 exit;
1821 begin
1827 exit;
1830 begin
1832 begin
1834 end
1835 else
1836 begin
1839 try
1844 begin
1849 finally
1854 exit;
1863 begin
1865 exit;
1868 begin
1872 exit;
1875 begin
1879 exit;
1882 begin
1886 //mIVal4 := readByte(st); // the only place we have RGB in binary map is effect trigger, and it has no alpha
1889 exit;
1892 begin
1894 exit;
1897 begin
1899 exit;
1910 begin
1912 if (mIVal < min) or (mIVal > max) then raise TDynParseException.CreateFmt(pr, 'invalid %s value for field ''%s''', [getTypeName(mType), mName]);
1916 var
1922 begin
1925 // if this field should contain struct, convert type and parse struct
1929 begin
1930 // ugly hack. sorry.
1932 begin
1935 begin
1936 // '{}'
1938 end
1939 else
1940 begin
1942 // find trigger definition
1944 if (tfld = nil) then raise TDynParseException.CreateFmt(pr, 'triggerdata value for field ''%s'' in record ''%s'' without ''type'' field', [mName, rec.mTypeName]);
1946 if (rc = nil) then raise TDynParseException.CreateFmt(pr, 'triggerdata definition for field ''%s'' in record ''%s'' with type ''%s'' not found', [mName, rec.mTypeName, tfld.mSVal]);
1949 //writeln(rc.definition);
1950 // on error, it will be freed by memowner
1956 exit;
1958 // other record types
1960 begin
1962 begin
1964 end
1965 else
1966 begin
1969 begin
1971 end
1972 else
1973 begin
1981 exit;
1982 end
1984 begin
1985 //rec := mOwner.mOwner.recType[mEBSTypeName]; // find in mapdef
1988 if (rec = nil) then raise TDynParseException.CreateFmt(pr, 'record type ''%s'' for field ''%s'' not found', [mEBSTypeName, mName]);
1995 begin
1996 raise TDynParseException.CreateFmt(pr, 'duplicate record with id ''%s'' for field ''%s'' in record ''%s''', [rc.mId, mName, mOwner.mTypeName]);
1999 exit;
2004 begin
2005 //es := mOwner.mOwner.ebsType[mEBSTypeName]; // find in mapdef
2008 if (es = nil) or (not es.mIsEnum) then raise TDynParseException.CreateFmt(pr, 'record enum type ''%s'' for field ''%s'' not found', [mEBSTypeName, mName]);
2010 if not es.has[tk] then raise TDynParseException.CreateFmt(pr, 'record enum value ''%s'' of type ''%s'' for field ''%s'' not found', [tk, mEBSTypeName, mName]);
2013 //writeln('ENUM ', mEBSName, '; element <', mSVal, '> with value ', mIVal);
2016 exit;
2019 begin
2020 //es := mOwner.mOwner.ebsType[mEBSTypeName]; // find in mapdef
2023 if (es = nil) or es.mIsEnum then raise TDynParseException.CreateFmt(pr, 'record bitset type ''%s'' for field ''%s'' not found', [mEBSTypeName, mName]);
2026 begin
2028 if not es.has[tk] then raise TDynParseException.CreateFmt(pr, 'record bitset value ''%s'' of type ''%s'' for field ''%s'' not found', [tk, mEBSTypeName, mName]);
2032 if mBitSetUnique then raise TDynParseException.CreateFmt(pr, 'record bitset of type ''%s'' for field ''%s'' expects only one value', [tk, mEBSTypeName, mName]);
2037 exit;
2044 begin
2050 exit;
2053 begin
2054 if (mMaxDim = 0) then raise TDynParseException.CreateFmt(pr, 'invalid string size definition for field ''%s''', [mName]);
2057 begin
2058 // single char
2059 if (Length(mSVal) <> 1) then raise TDynParseException.CreateFmt(pr, 'invalid string size for field ''%s''', [mName]);
2062 end
2063 else
2064 begin
2065 // string
2066 if (Length(mSVal) > mMaxDim) then raise TDynParseException.CreateFmt(pr, 'invalid string size for field ''%s''', [mName]);
2070 exit;
2073 begin
2076 exit;
2079 begin
2082 exit;
2085 begin
2088 exit;
2091 begin
2094 exit;
2097 begin
2100 exit;
2103 begin
2106 exit;
2109 begin
2113 exit;
2117 begin
2121 begin
2122 if (mIVal < 0) or (mIVal > 65535) then raise TDynParseException.CreateFmt(pr, 'invalid %s value for field ''%s''', [getTypeName(mType), mName]);
2126 begin
2127 if (mIVal2 < 0) or (mIVal2 > 65535) then raise TDynParseException.CreateFmt(pr, 'invalid %s value for field ''%s''', [getTypeName(mType), mName]);
2132 exit;
2135 begin
2138 if (mIVal < 0) or (mIVal > 255) then raise TDynParseException.CreateFmt(pr, 'invalid %s value for field ''%s''', [getTypeName(mType), mName]);
2140 if (mIVal2 < 0) or (mIVal2 > 255) then raise TDynParseException.CreateFmt(pr, 'invalid %s value for field ''%s''', [getTypeName(mType), mName]);
2142 if (mIVal3 < 0) or (mIVal3 > 255) then raise TDynParseException.CreateFmt(pr, 'invalid %s value for field ''%s''', [getTypeName(mType), mName]);
2144 begin
2146 if (mIVal4 < 0) or (mIVal4 > 255) then raise TDynParseException.CreateFmt(pr, 'invalid %s value for field ''%s''', [getTypeName(mType), mName]);
2147 end
2148 else
2149 begin
2155 exit;
2158 begin
2160 exit;
2163 begin
2165 exit;
2173 // ////////////////////////////////////////////////////////////////////////// //
2175 begin
2176 if (pr = nil) then raise TDynParseException.Create(pr, 'cannot create record type without type definition');
2181 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
2183 {$ENDIF}
2195 begin
2199 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
2201 {$ENDIF}
2212 var
2215 begin
2217 begin
2219 begin
2221 begin
2222 //writeln(formatstrf('freeing: 0x%08x; name=%s; id=%s', [Pointer(rec), rec.mName, rec.mId]));
2233 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
2236 {$ENDIF}
2246 begin
2248 begin
2256 begin
2259 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
2261 {$ENDIF}
2265 function TDynRecord.addFieldChecked (fld: TDynField): Boolean; inline; // `true`: duplicate name
2266 begin
2269 {$IF not DEFINED(XDYNREC_USE_FIELDHASH)}
2271 {$ENDIF}
2273 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
2275 {$ENDIF}
2280 begin
2281 {$IF DEFINED(XDYNREC_USE_FIELDHASH)}
2283 {$ELSE}
2286 begin
2291 {$ENDIF}
2296 begin
2302 var
2304 begin
2311 begin
2317 begin
2323 begin
2329 var
2331 begin
2339 begin
2345 begin
2351 var
2354 begin
2368 begin
2379 var
2382 begin
2385 // find record data
2388 if (fld.mType <> fld.TType.TList) then raise TDynRecException.CreateFmt('cannot get record of type ''%s'' due to name conflict with ordinary field', [atypename]);
2389 // find by id
2391 begin
2394 // alas
2398 function TDynRecord.findRecordNumByType (const atypename: AnsiString; rc: TDynRecord): Integer;
2399 var
2402 begin
2404 // find record data
2407 if (fld.mType <> fld.TType.TList) then raise TDynRecException.CreateFmt('cannot get record of type ''%s'' due to name conflict with ordinary field', [atypename]);
2408 // find by ref
2410 begin
2412 begin
2416 // alas
2421 var
2423 begin
2424 // find record data
2427 begin
2428 // first record
2433 if (fld.mType <> fld.TType.TList) then raise TDynRecException.CreateFmt('cannot append record of type ''%s'' due to name conflict with ordinary field', [atypename]);
2434 // append
2436 begin
2445 var
2447 begin
2453 begin
2461 var
2464 begin
2466 begin
2477 // number of records of the given instance
2479 var
2481 begin
2489 var
2492 begin
2493 if not mHeader then raise TDynRecException.Create('cannot create new records with non-header');
2494 if (Length(aid) = 0) then raise TDynRecException.CreateFmt('cannot create new record of type ''%s'' without id', [atypename]);
2497 // check if aid is unique
2499 if (fld <> nil) and (fld.getListItem(aid) <> nil) then raise TDynRecException.CreateFmt('cannot create record of type ''%s'' with duplicate id ''%s''', [atypename, aid]);
2508 var
2510 begin
2515 var
2517 begin
2523 // remove record with the given type and id
2524 // return `true` if record was successfully found and removed
2525 // this will do all necessary recref cleanup too
2527 var
2532 begin
2544 begin
2546 begin
2556 var
2558 begin
2565 var
2567 begin
2570 begin
2571 if (Length(aname) = 0) then raise TDynRecException.Create('cannot create nameless user field');
2576 end
2577 else
2578 begin
2585 var
2588 begin
2590 begin
2593 begin
2595 begin
2599 if isForTrig[tdn] then raise TDynParseException.CreateFmt(pr, 'duplicate trigdata ''%s'' trigtype ''%s''', [mTypeName, tdn]);
2603 end
2604 else
2605 begin
2611 end
2612 else
2613 begin
2616 begin
2619 begin
2620 if (mSize > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate `size` in record ''%s''', [mTypeName]);
2622 if (mSize < 1) then raise TDynParseException.CreateFmt(pr, 'invalid record ''%s'' size: %d', [mTypeName, mSize]);
2624 continue;
2627 begin
2628 if (mBinBlock >= 0) then raise TDynParseException.CreateFmt(pr, 'duplicate `binblock` in record ''%s''', [mTypeName]);
2630 if (mBinBlock < 1) then raise TDynParseException.CreateFmt(pr, 'invalid record ''%s'' binblock: %d', [mTypeName, mBinBlock]);
2631 continue;
2634 begin
2635 if (Length(mTip) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate tip definition for record ''%s''', [mTypeName]);
2637 continue;
2640 begin
2641 if (Length(mHelp) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate help definition for record ''%s''', [mTypeName]);
2643 continue;
2649 // load fields
2651 begin
2653 // append
2656 begin
2660 // done with field
2667 var
2669 begin
2671 begin
2672 // trigger data
2675 begin
2678 begin
2683 end
2684 else
2685 begin
2688 end
2689 else
2690 begin
2691 // record
2698 begin
2708 var
2720 var
2723 begin
2724 //writeln('*** rec: ', rec.mName, '.', rec.mId, ' (', rec.mFields.count, ')');
2726 begin
2728 begin
2730 continue;
2736 begin
2738 begin
2739 DynWarningCB(formatstrf('record of type ''%s'' with id ''%s'' links to inexistant record of type ''%s'' with id ''%s''', [rec.mTypeName, rec.mId, fld.mEBSTypeName, fld.mRecRefId]), -1, -1);
2741 //raise TDynRecException.CreateFmt('record of type ''%s'' with id ''%s'' links to inexistant record of type ''%s'' with id ''%s''', [rec.mName, rec.mId, fld.mEBSTypeName, fld.mRecRefId]);
2743 //writeln(' ', rec.mName, '.', rec.mId, ':', fld.mName, ' -> ', rt.mName, '.', rt.mId, ' (', fld.mEBSTypeName, '.', fld.mRecRefId, ')');
2749 begin
2750 //if (fld.mName = 'ambient_color') then writeln('****', fld.mName);
2755 begin
2758 try
2760 begin
2761 // parse map file as sequence of blocks
2765 // parse blocks
2767 begin
2773 if (bsize < 0) or (bsize > $1fffffff) then raise TDynRecException.CreateFmt('block of type %d has invalid size %d', [btype, bsize]);
2774 if loaded[btype] then raise TDynRecException.CreateFmt('block of type %d already loaded', [btype]);
2776 // find record type for this block
2779 if (rect = nil) then raise TDynRecException.CreateFmt('block of type %d has no corresponding record', [btype]);
2780 //writeln('found type ''', rec.mName, ''' for block type ', btype);
2781 if (rec.mSize = 0) or ((bsize mod rec.mSize) <> 0) then raise TDynRecException.CreateFmt('block of type %d has invalid number of records', [btype]);
2782 // header?
2784 begin
2785 if (bsize <> mSize) then raise TDynRecException.CreateFmt('header block of type %d has invalid number of records', [btype]);
2790 end
2791 else
2792 begin
2793 // create list for this type
2798 begin
2802 begin
2809 //writeln('parsed ''', rec.mId, '''...');
2815 //st.position := st.position+bsize;
2817 // link fields
2819 begin
2823 exit;
2826 // read fields
2828 if (mSize < 1) then raise TDynRecException.CreateFmt('cannot read record of type ''%s'' with unknown size', [mTypeName]);
2832 begin
2835 if (fld.mBinOfs >= st.size) then raise TDynRecException.CreateFmt('record of type ''%s'' has invalid field ''%s''', [fld.mName]);
2837 //writeln('parsing ''', mName, '.', fld.mName, '''...');
2840 // fix default values
2842 begin
2846 finally
2853 procedure TDynRecord.writeBinTo (var hasLostData: Boolean; st: TStream; trigbufsz: Integer=-1; onlyFields: Boolean=false);
2854 var
2862 begin
2864 begin
2865 if (mBinBlock < 1) then raise TDynRecException.Create('cannot write binary record without block number');
2868 end
2869 else
2870 begin
2873 try
2878 // write normal fields
2880 begin
2881 // record list?
2885 begin
2887 continue;
2889 if (fld.mBinOfs >= bufsz) then raise TDynRecException.Create('binary value offset is outside of the buffer');
2891 //writeln('writing field <', fld.mName, '>');
2895 // write block with normal fields
2897 begin
2898 //writeln('writing header...');
2899 // signature and version
2910 // write other blocks, if any
2912 begin
2913 // calculate blkmax
2916 begin
2917 // record list?
2919 begin
2927 // write blocks
2929 begin
2933 begin
2934 // record list?
2936 begin
2945 // flush block
2947 begin
2958 // write end marker
2963 finally
2971 var
2976 begin
2978 begin
2985 try
2987 begin
2988 // record list?
2990 begin
2993 begin
2996 begin
2999 begin
3002 begin
3012 end
3013 else
3014 begin
3021 continue;
3028 finally
3036 {$IF DEFINED(D2D_DYNREC_PROFILER)}
3037 var
3048 begin
3050 writeln('record cloning: ', profCloneRec div 1000, '.', profCloneRec mod 1000, ' milliseconds');
3051 writeln('findRecType : ', profFindRecType div 1000, '.', profFindRecType mod 1000, ' milliseconds');
3052 writeln('field[] : ', profFieldSearching div 1000, '.', profFieldSearching mod 1000, ' milliseconds');
3053 writeln('list dup check: ', profListDupChecking div 1000, '.', profListDupChecking mod 1000, ' milliseconds');
3054 writeln('addRecByType : ', profAddRecByType div 1000, '.', profAddRecByType mod 1000, ' milliseconds');
3055 writeln('field valparse: ', profFieldValParsing div 1000, '.', profFieldValParsing mod 1000, ' milliseconds');
3056 writeln('fix defaults : ', profFixDefaults div 1000, '.', profFixDefaults mod 1000, ' milliseconds');
3057 writeln('recvalparse : ', profRecValParse div 1000, '.', profRecValParse mod 1000, ' milliseconds');
3059 {$ENDIF}
3063 var
3067 {$IF DEFINED(D2D_DYNREC_PROFILER)}
3069 {$ENDIF}
3072 var
3075 begin
3077 //writeln('*** rec: ', rec.mName, '.', rec.mId, ' (', rec.mFields.count, ')');
3079 begin
3081 begin
3085 begin
3086 //if (fld.mRecRef <> nil) then linkNames(fld.mRecRef);
3087 continue;
3093 begin
3094 //e_LogWritefln('record of type ''%s'' with id ''%s'' links to inexistant record of type ''%s'' with id ''%s''', [rec.mName, rec.mId, fld.mEBSTypeName, fld.mRecRefId], MSG_WARNING);
3095 raise TDynParseException.CreateFmt(pr, 'record of type ''%s'' with id ''%s'' links to inexistant record of type ''%s'' with id ''%s''', [rec.mTypeName, rec.mId, fld.mEBSTypeName, fld.mRecRefId]);
3097 //writeln(' ', rec.mName, '.', rec.mId, ':', fld.mName, ' -> ', rt.mName, '.', rt.mId, ' (', fld.mEBSTypeName, '.', fld.mRecRefId, ')');
3103 begin
3104 //writeln(' ', fld.mName);
3109 begin
3110 if (mOwner = nil) then raise TDynParseException.CreateFmt(pr, 'can''t parse record ''%s'' value without owner', [mTypeName]);
3114 // not a header?
3116 begin
3117 // id?
3119 end
3120 else
3121 begin
3125 //writeln('parsing record <', mName, '>');
3128 begin
3130 //writeln('<', mName, '.', pr.tokStr, '>');
3132 // records
3134 begin
3135 // add records with this type (if any)
3140 begin
3145 // on error, it will be freed by memowner
3151 continue;
3155 // fields
3157 //writeln('0: <', mName, '.', pr.tokStr, '>');
3159 //writeln('1: <', mName, '.', pr.tokStr, '>');
3162 begin
3163 //writeln('2: <', mName, '.', pr.tokStr, '>');
3164 if fld.defined then raise TDynParseException.CreateFmt(pr, 'duplicate field ''%s'' in record ''%s''', [fld.mName, mTypeName]);
3165 if fld.internal then raise TDynParseException.CreateFmt(pr, 'internal field ''%s'' in record ''%s''', [fld.mName, mTypeName]);
3167 //writeln('3: <', mName, '.', pr.tokStr, '>:', pr.tokType);
3171 continue;
3174 // something is wrong
3175 raise TDynParseException.CreateFmt(pr, 'unknown field ''%s'' in record ''%s''', [pr.tokStr, mTypeName]);
3180 begin
3181 // link fields
3185 //writeln('done parsing record <', mName, '>');
3186 //{$IF DEFINED(D2D_DYNREC_PROFILER)}writeln('stall: ', getTimeMicro()-stall);{$ENDIF}
3191 // ////////////////////////////////////////////////////////////////////////// //
3193 begin
3200 begin
3207 begin
3220 begin
3223 begin
3232 begin
3238 var
3240 begin
3247 var
3249 begin
3253 // fields
3256 begin
3260 begin
3264 end
3265 else
3266 begin
3271 // max field
3278 var
3280 begin
3282 // fields
3284 begin
3291 var
3293 begin
3295 begin
3303 var
3309 begin
3317 begin
3319 begin
3320 if (Length(mTip) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate tip definition for enum/bitset ''%s''', [mTypeName]);
3322 continue;
3325 begin
3326 if (Length(mHelp) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate help definition for enum/bitset ''%s''', [mTypeName]);
3328 continue;
3330 break;
3334 begin
3337 begin
3338 if StrEqu(mIds[f], idname) then raise TDynParseException.CreateFmt(pr, 'duplicate field ''%s'' in enum/bitset ''%s''', [idname, mTypeName]);
3340 if StrEqu(mMaxName, idname) then raise TDynParseException.CreateFmt(pr, 'duplicate field ''%s'' in enum/bitset ''%s''', [idname, mTypeName]);
3344 // has value?
3346 begin
3348 begin
3349 if (Length(mMaxName) > 0) then raise TDynParseException.CreateFmt(pr, 'duplicate max field ''%s'' in enum/bitset ''%s''', [idname, mTypeName]);
3352 end
3353 else
3354 begin
3360 // append it?
3362 begin
3363 // fix maxvalue
3365 begin
3372 // next cv
3374 begin
3383 // add max field
3385 begin
3394 // ////////////////////////////////////////////////////////////////////////// //
3396 begin
3405 var
3408 begin
3409 //!!!FIXME!!! check who owns trigs and recs!
3424 begin
3431 var
3433 begin
3435 begin
3443 var
3445 begin
3447 begin
3455 var
3457 begin
3459 begin
3467 var
3472 // setup header links and type links
3474 var
3476 begin
3479 begin
3484 begin
3486 if (fld.mEBSType = nil) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' of type ''%s'' has no correcponding record definition', [fld.mName, fld.mEBSTypeName]);
3490 begin
3492 if (fld.mEBSType = nil) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' of type ''%s'' has no correcponding enum/bitset', [fld.mName, fld.mEBSTypeName]);
3493 if ((fld.mEBS = TDynField.TEBS.TEnum) <> (fld.mEBSType as TDynEBS).mIsEnum) then raise TDynParseException.CreateFmt(pr, 'field ''%s'' of type ''%s'' enum/bitset type conflict', [fld.mName, fld.mEBSTypeName]);
3499 // setup default values
3501 var
3503 begin
3507 begin
3510 begin
3514 begin
3515 // enum or bitset
3517 begin
3520 begin
3526 //writeln(eb.definition); writeln;
3527 continue;
3530 // triggerdata
3532 begin
3535 begin
3537 begin
3544 //writeln(dr.definition); writeln;
3545 continue;
3550 //writeln(dr.definition); writeln;
3551 if (findRecType(rec.typeName) <> nil) then begin rec.Free(); raise TDynParseException.CreateFmt(pr, 'duplicate record ''%s''', [rec.typeName]); end;
3552 if (hdr <> nil) and StrEqu(rec.typeName, hdr.typeName) then begin rec.Free(); raise TDynParseException.CreateFmt(pr, 'duplicate record ''%s''', [rec.typeName]); end;
3555 begin
3556 if (hdr <> nil) then begin rec.Free(); raise TDynParseException.CreateFmt(pr, 'duplicate header record ''%s'' (previous is ''%s'')', [rec.typeName, hdr.typeName]); end;
3558 end
3559 else
3560 begin
3565 // put header record to top
3566 if (hdr = nil) then raise TDynParseException.Create(pr, 'header definition not found in mapdef');
3571 // setup header links and type links
3575 // setup default values
3581 // ////////////////////////////////////////////////////////////////////////// //
3583 var
3585 begin
3587 try
3594 finally
3601 var
3603 begin
3605 try
3611 finally
3617 // WARNING! stream must be seekable
3619 var
3622 begin
3628 begin
3630 begin
3633 exit;
3636 end
3637 else
3638 begin
3640 try
3641 try
3646 finally
3653 // returns `true` if the given stream can be a map file
3654 // stream position is 0 on return
3655 // WARNING! stream must be seekable
3657 var
3660 begin
3665 begin
3667 end
3668 else
3669 begin
3680 var
3682 begin
3684 result += '// ////////////////////////////////////////////////////////////////////////// //'#10;
3691 function TDynMapDef.getRecTypeAt (idx: Integer): TDynRecord; inline; begin if (idx >= 0) and (idx < recTypes.count) then result := recTypes[idx] else result := nil; end;
3694 function TDynMapDef.getEBSTypeAt (idx: Integer): TDynEBS; inline; begin if (idx >= 0) and (idx < ebsTypes.count) then result := ebsTypes[idx] else result := nil; end;
3696 function TDynMapDef.getTrigTypeCount (): Integer; inline; begin result := trigTypes.count; end;
3697 function TDynMapDef.getTrigTypeAt (idx: Integer): TDynRecord; inline; begin if (idx >= 0) and (idx < trigTypes.count) then result := trigTypes[idx] else result := nil; end;