Database.pas 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343
  1. {
  2. Copyright (C) 2005 Remco Mulder
  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; either version 2 of the License, or
  6. (at your option) any later version.
  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. You should have received a copy of the GNU General Public License
  12. along with this program; if not, write to the Free Software
  13. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  14. For source notes please refer to Notes.txt
  15. For license terms please refer to GPL.txt.
  16. These files should be stored in the root of the compression you
  17. received this source in.
  18. }
  19. // This unit controls all database access.
  20. unit
  21. DataBase;
  22. interface
  23. uses
  24. Core,
  25. Classes,
  26. SysUtils;
  27. const
  28. DATABASE_VERSION = 7;
  29. Day : array[1..7] of string = ('Sun', 'Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat');
  30. type
  31. // Enumerated types
  32. TFighterType = (ftToll, ftDefensive, ftOffensive);
  33. TSectorExploredType = (etNo, etCalc, etDensity, etHolo);
  34. TProductType = (ptFuelOre, ptOrganics, ptEquipment);
  35. // Exceptions
  36. EDatabaseError = class(Exception);
  37. // database records
  38. TDataHeader = record
  39. ProgramName : string[12];
  40. Version : Byte;
  41. Sectors,
  42. StarDock,
  43. Class0_1,
  44. Class0_2 : Word;
  45. Address,
  46. Description : string[40];
  47. Port : Word;
  48. LoginScript : string[255];
  49. Password,
  50. LoginName : string[40];
  51. Game : Char;
  52. UseLogin : Boolean;
  53. RobFactor,
  54. StealFactor : Byte;
  55. LastPortCIM : TDateTime;
  56. end;
  57. PDataHeader = ^TDataHeader;
  58. TSpaceObject = record
  59. Quantity : LongInt;
  60. Owner : string[40];
  61. FigType : TFighterType;
  62. end;
  63. PTrader = ^TTrader;
  64. TTrader = record
  65. Name,
  66. ShipType,
  67. ShipName : string[40];
  68. Figs : LongInt;
  69. NextTrader : LongInt;
  70. end;
  71. PShip = ^TShip;
  72. TShip = record
  73. Name,
  74. Owner,
  75. ShipType : string[40];
  76. Figs : LongInt;
  77. NextShip : LongInt;
  78. end;
  79. TPort = record
  80. Name : string[40];
  81. Dead : Boolean;
  82. BuildTime,
  83. ClassIndex : Byte;
  84. BuyProduct : array[TProductType] of Boolean;
  85. ProductPercent : array[TProductType] of Byte;
  86. ProductAmount : array[TProductType] of Word;
  87. UpDate : TDateTime;
  88. end;
  89. PSector = ^TSector;
  90. TSector = record
  91. // Index : Word;
  92. Warp : array[1..6] of Word;
  93. SPort : TPort;
  94. NavHaz : Byte;
  95. Figs,
  96. Mines_Armid,
  97. Mines_Limpet : TSpaceObject;
  98. Constellation,
  99. Beacon : string[40];
  100. UpDate : TDateTime;
  101. Anomoly : Boolean;
  102. Density : LongInt;
  103. Warps : Byte;
  104. Explored : TSectorExploredType;
  105. Ships,
  106. Traders,
  107. Planets,
  108. Vars : LongInt;
  109. end;
  110. PPlanet = ^TPlanet;
  111. TPlanet = record
  112. Name : string[40];
  113. NextPlanet : LongInt;
  114. end;
  115. PWarpIn = ^TWarpIn;
  116. TWarpIn = record
  117. Origin : Word;
  118. NextWarpIn : PWarpIn;
  119. end;
  120. TSectorVar = record
  121. VarName: string[10];
  122. Value: string[40];
  123. NextVar: LongInt;
  124. end;
  125. PSectorVar = ^TSectorVar;
  126. TSectorItem = (itPlanet, itTrader, itShip);
  127. TEmptyRecordGroup = record
  128. RecordSize : Word;
  129. EmptyRecords : array of Integer;
  130. end;
  131. TModDatabase = class(TTWXModule, IModDatabase, ITWXGlobals)
  132. private
  133. CacheAllocated,
  134. FRecording,
  135. FUseCache,
  136. FDataBaseOpen : Boolean;
  137. FDataFilename : string;
  138. DataFile : File;
  139. DataCache,
  140. SectorWarpCache : Pointer;
  141. DBSize,
  142. CacheSize : Integer;
  143. EmptyRecordGroups : array of TEmptyRecordGroup;
  144. FDBHeader : TDataHeader;
  145. FProgramDir : string;
  146. procedure AddSectorVar(SectorIndex: Integer; SectorVar: PSectorVar);
  147. procedure FindSectorVar(SectorIndex: Integer; const VarName: string; var SectorVar: PSectorVar; var Index: Integer);
  148. procedure DeleteSectorVar(SectorIndex: Integer; const VarName: string);
  149. procedure AddWarpIn(Sect, Origin : Word);
  150. function GetEmptyRecord(Size : Integer) : Integer;
  151. procedure CacheEmptyRecords;
  152. procedure CacheEmptyRecord(Index : Integer; Size : Word);
  153. procedure ReadRecordList(List : TList; FirstPos : Integer);
  154. function WriteRecordList(List : TList; RecordSize : Word) : Integer;
  155. procedure PurgeRecordList(FirstPos : Integer);
  156. procedure WriteRecord(Rec : Pointer; Pos, Next : Integer; Size : Word);
  157. procedure ReadData(Data : Pointer; Index, Size : Integer);
  158. procedure WriteData(Data : Pointer; Index, Size : Integer);
  159. procedure OpenCache;
  160. procedure CloseCache;
  161. function GetLastPortCIM: TDateTime;
  162. procedure SetLastPortCIM(const Value: TDateTime);
  163. protected
  164. { ITWXGlobals }
  165. function GetProgramDir: string;
  166. procedure SetProgramDir(const Value: string);
  167. { IModDatabase }
  168. function GetDatabaseName: string;
  169. procedure SetDatabaseName(const Value: string);
  170. function GetUseCache: Boolean;
  171. procedure SetUseCache(Value: Boolean);
  172. function GetRecording: Boolean;
  173. procedure SetRecording(Value: Boolean);
  174. public
  175. procedure AfterConstruction; override;
  176. procedure BeforeDestruction; override;
  177. // DB control methods
  178. procedure OpenDataBase(Filename : string);
  179. procedure CloseDataBase;
  180. procedure CreateDatabase(Filename : string; Head : TDataHeader);
  181. // main storage/retrieval methods
  182. procedure SaveSector(S: TSector; Index: Integer; ShipList: TList = nil; TraderList: TList = nil; PlanetList: TList = nil);
  183. function LoadSector(I : Integer) : TSector;
  184. procedure UpdateWarps(SectIndex : Integer);
  185. function GetSectorItems(ItemType : TSectorItem; Sector : TSector) : TList;
  186. function GetWarpsIn(Sect : Integer) : TList;
  187. function GetBackDoors(S: TSector; SectorIndex: Integer) : TList;
  188. function PlotWarpCourse(FromSect, ToSect : Word) : TList;
  189. procedure WriteHeader;
  190. procedure DumpData;
  191. procedure SetSectorVar(SectorIndex: Integer; const VarName, VarValue: string);
  192. function GetSectorVar(SectorIndex: Integer; const VarName: string): string;
  193. // null item retrieval
  194. procedure NULLSector(var Sector : TSector);
  195. procedure NULLPlanet(var Planet : TPlanet);
  196. procedure NULLTrader(var Trader : TTrader);
  197. procedure NULLShip(var Ship : TShip);
  198. property Sectors[Index : Integer] : TSector read LoadSector;
  199. property DataBaseOpen : Boolean read FDataBaseOpen;
  200. property DBHeader: TDataHeader read FDBHeader;
  201. property LastPortCIM: TDateTime read GetLastPortCIM write SetLastPortCIM;
  202. published
  203. property DatabaseName: string read GetDatabaseName write SetDatabaseName;
  204. property UseCache: Boolean read GetUseCache write SetUseCache;
  205. property Recording: Boolean read GetRecording write SetRecording;
  206. end;
  207. function GetBlankHeader : PDataHeader;
  208. implementation
  209. uses
  210. Global,
  211. Utility,
  212. Windows,
  213. Forms,
  214. Ansi,
  215. Dialogs;
  216. type
  217. TWarpItem = class(TObject)
  218. public
  219. Index : Word;
  220. Parent : TWarpItem;
  221. end;
  222. function WarpsTo(Source: TSector; DestIndex: Integer) : Boolean;
  223. begin
  224. WarpsTo := (Source.Warp[1] = DestIndex)
  225. or (Source.Warp[2] = DestIndex)
  226. or (Source.Warp[3] = DestIndex)
  227. or (Source.Warp[4] = DestIndex)
  228. or (Source.Warp[5] = DestIndex)
  229. or (Source.Warp[6] = DestIndex);
  230. end;
  231. function GetBlankHeader : PDataHeader;
  232. begin
  233. Result := AllocMem(SizeOf(TDataHeader));
  234. ZeroMemory(Result, SizeOf(TDataHeader));
  235. Result^.ProgramName := 'TWX DATABASE';
  236. Result^.Version := DATABASE_VERSION;
  237. Result^.Address := '<Server>';
  238. Result^.Port := 23;
  239. end;
  240. // ***********************************************************
  241. // Public Implementation
  242. procedure TModDatabase.AfterConstruction;
  243. begin
  244. inherited;
  245. // initialise variables
  246. FDatabaseOpen := FALSE;
  247. SectorWarpCache := nil;
  248. FUseCache := True;
  249. FRecording := True;
  250. end;
  251. procedure TModDatabase.BeforeDestruction;
  252. begin
  253. // ensure database is closed
  254. CloseDataBase;
  255. inherited;
  256. end;
  257. function TModDatabase.GetProgramDir: string;
  258. begin
  259. Result := FProgramDir;
  260. end;
  261. procedure TModDatabase.SetProgramDir(const Value: string);
  262. begin
  263. FProgramDir := Value;
  264. end;
  265. // ---------------------------
  266. // DB control methods
  267. procedure TModDatabase.OpenDataBase(Filename : string);
  268. var
  269. WarpCount,
  270. I,
  271. X : Integer;
  272. S : TSector;
  273. begin
  274. if (DataBaseOpen) then
  275. Exit;
  276. TWXServer.Broadcast(endl + ANSI_15 + 'Loading database: ' + ANSI_7 + Filename + endl);
  277. FDataFilename := Filename;
  278. SetCurrentDir(FProgramDir);
  279. // Open database
  280. AssignFile(DataFile, Filename);
  281. {$I-}
  282. Reset(DataFile, 1);
  283. if (IOResult <> 0) then
  284. begin
  285. TWXServer.Broadcast(endl + ANSI_12 + 'Warning: This database does not exist. No data will be saved/retrieved' + ANSI_7 + endl);
  286. CloseDatabase;
  287. Exit;
  288. end;
  289. {$I+}
  290. // Check database validity and version number
  291. Seek(DataFile, 0);
  292. BlockRead(DataFile, FDBHeader, SizeOf(TDataHeader));
  293. if (DBHeader.ProgramName <> 'TWX DATABASE') then
  294. begin
  295. TWXServer.Broadcast(endl + ANSI_12 + 'Warning: This database has been corrupted, no data will be saved/retrieved' + ANSI_7 + endl);
  296. CloseDatabase;
  297. Exit;
  298. end;
  299. if (DBHeader.Version <> DATABASE_VERSION) then
  300. begin
  301. TWXServer.Broadcast(endl + ANSI_12 + 'Warning: Database version ' + IntToStr(DBHeader.Version) + ', expected version ' + IntToStr(DATABASE_VERSION) + ', no data will be saved/retrieved' + ANSI_7 + endl);
  302. CloseDatabase;
  303. Exit;
  304. end;
  305. if (UseCache) then
  306. OpenCache
  307. else
  308. CloseCache;
  309. FDatabaseOpen := TRUE;
  310. DBSize := FileSize(DataFile);
  311. // cache empty record indexes
  312. CacheEmptyRecords;
  313. // construct sector warp cache
  314. SectorWarpCache := AllocMem(DBHeader.Sectors * 4);
  315. WarpCount := 0;
  316. for I := 1 to DBHeader.Sectors do
  317. begin
  318. S := LoadSector(I);
  319. for X := 1 to 6 do
  320. if (S.Warp[X] > 0) then
  321. begin
  322. Inc(WarpCount);
  323. AddWarpIn(S.Warp[X], I);
  324. end
  325. else
  326. Break;
  327. end;
  328. TWXLog.DatabaseChanged;
  329. TWXServer.Broadcast(endl + ANSI_15 + 'Database successfully loaded - ' + IntToStr(DBHeader.Sectors) + ' sectors, ' + IntToStr(WarpCount) + ' warps' + endl);
  330. TWXGUI.DatabaseName := StripFileExtension(ShortFilename(Filename));
  331. end;
  332. procedure TModDatabase.CloseDataBase;
  333. var
  334. W,
  335. Last : PWarpIn;
  336. P : Pointer;
  337. I,
  338. CacheEnd : Integer;
  339. begin
  340. if not (FDatabaseOpen) then
  341. Exit;
  342. {$I-}
  343. CloseFile(DataFile);
  344. {$I+}
  345. FDatabaseOpen := FALSE;
  346. // deconstruct sector warp cache
  347. P := SectorWarpCache;
  348. CacheEnd := Integer(P) + DBHeader.Sectors * 4;
  349. while (Integer(P) < CacheEnd) do
  350. begin
  351. W := PWarpIn(P^);
  352. while (W <> nil) do
  353. begin
  354. Last := W;
  355. W := W^.NextWarpIn;
  356. FreeMem(Last);
  357. end;
  358. P := Pointer(Integer(P) + SizeOf(Pointer));
  359. end;
  360. // purge old empty record caches
  361. if (Length(EmptyRecordGroups) > 0) then
  362. for I := 0 to Length(EmptyRecordGroups) - 1 do
  363. SetLength(EmptyRecordGroups[I].EmptyRecords, 0);
  364. SetLength(EmptyRecordGroups, 0);
  365. FreeMem(SectorWarpCache);
  366. CloseCache;
  367. FDataFilename := '';
  368. end;
  369. procedure TModDatabase.CreateDatabase(Filename : string; Head : TDataHeader);
  370. var
  371. I : Integer;
  372. Sect : TSector;
  373. F : File;
  374. FileOpen : Boolean;
  375. begin
  376. // Make a database - it doesn't exist
  377. TWXServer.Broadcast(endl + ANSI_15 + 'Creating database: ' + ANSI_7 + Filename + ANSI_15 + ' (' + IntToStr(Head.Sectors) + ')' + ANSI_7 + endl);
  378. SetCurrentDir(FProgramDir);
  379. FileOpen := FALSE;
  380. try
  381. AssignFile(F, Filename);
  382. ReWrite(F, 1);
  383. FileOpen := TRUE;
  384. BlockWrite(F, Head, SizeOf(TDataHeader));
  385. NULLSector(Sect);
  386. for I := 0 to Head.Sectors do
  387. BlockWrite(F, Sect, SizeOf(Sect));
  388. finally
  389. if (FileOpen) then
  390. CloseFile(F);
  391. end;
  392. end;
  393. // ---------------------------
  394. // main storage/retrieval methods
  395. procedure TModDatabase.SaveSector(S: TSector; Index: Integer; ShipList: TList = nil; TraderList: TList = nil; PlanetList: TList = nil);
  396. var
  397. Sect : TSector;
  398. I,
  399. X : Integer;
  400. Bad,
  401. Found : Boolean;
  402. WarpsIn : TList;
  403. Product : TProductType;
  404. begin
  405. // Save this sector to file if set to record data
  406. if (Recording) and (DatabaseOpen) then
  407. begin
  408. Bad := (Index < 1) or (Index > DBHeader.Sectors);
  409. I := 1;
  410. while not Bad and (I <= 6) do
  411. begin
  412. if (S.Warp[I] > DBHeader.Sectors) then
  413. Bad := True;
  414. Inc(I);
  415. end;
  416. if Bad then
  417. begin
  418. SetForegroundWindow(Application.Handle);
  419. TWXServer.ClientMessage('Unable to store sector ''' + IntToStr(Index) + ''', closing database.');
  420. CloseDatabase;
  421. Exit;
  422. end;
  423. ReadData(@Sect, Index * SizeOf(TSector) + SizeOf(TDataHeader), SizeOf(Sect));
  424. // If this sector has been probed, recall warps if seen before
  425. if (S.Warp[1] = 0) and (Sect.Warp[1] <> 0) then
  426. S.Warp := Sect.Warp;
  427. // Don't go over density or anomoly readings unless this is a density scan
  428. if (S.Density = -1) then
  429. begin
  430. S.Density := Sect.Density;
  431. S.Anomoly := Sect.Anomoly;
  432. end;
  433. // Don't go over port details unless they are specified
  434. if (S.SPort.UpDate = 0) then
  435. begin
  436. S.SPort.Update := Sect.SPort.UpDate;
  437. for Product := Low(TProductType) to High(TProductType) do
  438. begin
  439. S.SPort.ProductAmount[Product] := Sect.SPort.ProductAmount[Product];
  440. S.SPort.ProductPercent[Product] := Sect.SPort.ProductPercent[Product];
  441. end;
  442. end;
  443. // save stardock details if this sector has it
  444. if (S.SPort.ClassIndex = 9) and (Sect.SPort.ClassIndex <> 9) then
  445. begin
  446. FDBHeader.StarDock := Index;
  447. WriteHeader;
  448. end;
  449. // update sector warp count
  450. S.Warps := 6;
  451. for I := 1 to 6 do
  452. if (S.Warp[I] = 0) then
  453. begin
  454. S.Warps := I - 1;
  455. Break;
  456. end;
  457. // Purge old ship, trader and planet data
  458. PurgeRecordList(Sect.Ships);
  459. PurgeRecordList(Sect.Traders);
  460. PurgeRecordList(Sect.Planets);
  461. // Write the ships to file
  462. S.Ships := WriteRecordList(ShipList, SizeOf(TShip));
  463. // Write the traders to file
  464. S.Traders := WriteRecordList(TraderList, SizeOf(TTrader));
  465. // Write the planets to file
  466. S.Planets := WriteRecordList(PlanetList, SizeOf(TPlanet));
  467. // write sector to database
  468. WriteData(@S, Index * SizeOf(TSector) + SizeOf(TDataHeader), SizeOf(TSector));
  469. // Update sector warp cache with new specs (if need be)
  470. for I := 1 to 6 do
  471. if (S.Warp[I] > 0) then
  472. begin
  473. WarpsIn := GetWarpsIn(S.Warp[I]);
  474. // see if its in there already
  475. X := 0;
  476. Found := FALSE;
  477. while (X < WarpsIn.Count) do
  478. begin
  479. if (TWarpIn(WarpsIn.Items[X]^).Origin = Index) then
  480. begin
  481. Found := TRUE;
  482. Break;
  483. end;
  484. Inc(X);
  485. end;
  486. WarpsIn.Free;
  487. if not (Found) then
  488. AddWarpIn(S.Warp[I], Index);
  489. end
  490. else
  491. Break;
  492. end;
  493. end;
  494. function TModDatabase.LoadSector(I : Integer) : TSector;
  495. begin
  496. if (I <= 0) or (I > DBHeader.Sectors) then
  497. raise EDatabaseError.Create('Unable to load sector: ' + IntToStr(I));
  498. if not (DatabaseOpen) then
  499. // no database or database is corrupt - load a blank sector
  500. NULLSector(Result)
  501. else
  502. ReadData(@Result, I * SizeOf(TSector) + SizeOf(TDataHeader), SizeOf(TSector));
  503. end;
  504. procedure TModDatabase.UpdateWarps(SectIndex : Integer);
  505. var
  506. I : Integer;
  507. S : TSector;
  508. begin
  509. // find out how many warps there are going out of this sector
  510. S := LoadSector(SectIndex);
  511. I := 1;
  512. while (I <= 6) do
  513. begin
  514. if (S.Warp[I] = 0) then
  515. Break;
  516. Inc(I);
  517. end;
  518. S.Warps := I;
  519. SaveSector(S, SectIndex, nil, nil, nil);
  520. end;
  521. function TModDatabase.GetSectorItems(ItemType : TSectorItem; Sector : TSector) : TList;
  522. begin
  523. Result := TList.Create;
  524. if not (DatabaseOpen) then
  525. // no database or database is corrupt
  526. Exit;
  527. if (ItemType = itPlanet) then
  528. ReadRecordList(Result, Sector.Planets)
  529. else if (ItemType = itTrader) then
  530. ReadRecordList(Result, Sector.Traders)
  531. else if (ItemType = itShip) then
  532. ReadRecordList(Result, Sector.Ships);
  533. end;
  534. function TModDatabase.GetWarpsIn(Sect : Integer) : TList;
  535. var
  536. W : PWarpIn;
  537. begin
  538. Result := TList.Create;
  539. if not (DatabaseOpen) then
  540. Exit;
  541. W := PWarpIn(Pointer(Integer(SectorWarpCache) + (Sect - 1) * 4)^);
  542. while (W <> nil) do
  543. begin
  544. Result.Add(W);
  545. W := W^.NextWarpIn;
  546. end;
  547. end;
  548. function TModDatabase.GetBackDoors(S: TSector; SectorIndex: Integer) : TList;
  549. var
  550. I : Integer;
  551. W : PWarpIn;
  552. Sect : ^Word;
  553. WarpsIn : TList;
  554. begin
  555. WarpsIn := GetWarpsIn(SectorIndex);
  556. Result := TList.Create;
  557. if not (DatabaseOpen) then
  558. // no database or database is corrupt
  559. Exit;
  560. I := 0;
  561. while (I < WarpsIn.Count) do
  562. begin
  563. W := PWarpIn(WarpsIn.Items[I]);
  564. if not (WarpsTo(S, W^.Origin)) then
  565. begin
  566. Sect := AllocMem(SizeOf(Word));
  567. Sect^ := W^.Origin;
  568. Result.Add(Sect);
  569. end;
  570. Inc(I);
  571. end;
  572. WarpsIn.Free;
  573. end;
  574. function TModDatabase.PlotWarpCourse(FromSect, ToSect : Word) : TList;
  575. var
  576. S : TSector;
  577. Warp : TWarpItem;
  578. Map,
  579. ItemDone : Pointer;
  580. I,
  581. X,
  582. ListSize,
  583. Avoid : Integer;
  584. CurWarp : TWarpItem;
  585. W : ^Word;
  586. procedure AddItem(Index : Word; Parent : TWarpItem);
  587. var
  588. NewWarp : TWarpItem;
  589. begin
  590. NewWarp := TWarpItem.Create;
  591. NewWarp.Parent := Parent;
  592. NewWarp.Index := Index;
  593. TWarpItem(Pointer(Integer(Map) + ListSize * SizeOf(Pointer))^) := NewWarp;
  594. // record this sector as added
  595. Byte(Pointer(Integer(ItemDone) + Index - 1)^) := 1;
  596. Inc(ListSize);
  597. end;
  598. begin
  599. Result := TList.Create;
  600. if not (DatabaseOpen) then
  601. // no database or database is corrupt
  602. Exit;
  603. Map := AllocMem((DBHeader.Sectors + 1) * SizeOf(Pointer));
  604. ItemDone := AllocMem(DBHeader.Sectors + 1);
  605. ListSize := 1;
  606. I := 0;
  607. Warp := TWarpItem.Create;
  608. Warp.Parent := nil;
  609. Warp.Index := FromSect;
  610. TWarpItem(Map^) := Warp;
  611. repeat
  612. if (I >= ListSize) then
  613. Break;
  614. CurWarp := TWarpItem(Pointer(Integer(Map) + I * SizeOf(Pointer))^);
  615. if (CurWarp.Index = ToSect) then
  616. begin
  617. // get the path
  618. Warp := CurWarp;
  619. while (Warp <> nil) do
  620. begin
  621. W := AllocMem(SizeOf(Word));
  622. W^ := Warp.Index;
  623. Result.Add(W);
  624. Warp := Warp.Parent;
  625. end;
  626. Break;
  627. end;
  628. // get the warps out and add them to the map
  629. S := LoadSector(CurWarp.Index);
  630. if (CurWarp.Parent = nil) then
  631. Avoid := 0
  632. else
  633. Avoid := CurWarp.Parent.Index;
  634. X := 1;
  635. while (X <= 6) and (S.Warp[X] <> 0) do
  636. begin
  637. // Add warp to the list
  638. if (S.Warp[X] <> Avoid) and (Byte(Pointer(Integer(ItemDone) + S.Warp[X] - 1)^) = 0) then
  639. AddItem(S.Warp[X], CurWarp);
  640. Inc(X);
  641. end;
  642. Inc(I);
  643. until (I >= DBHeader.Sectors);
  644. for I := ListSize - 1 downto 0 do
  645. TWarpItem(Pointer(Integer(Map) + I * SizeOf(Pointer))^).Free;
  646. FreeMem(Map);
  647. FreeMem(ItemDone);
  648. end;
  649. procedure TModDatabase.WriteHeader;
  650. begin
  651. if not (DatabaseOpen) then
  652. // no database or database is corrupt
  653. Exit;
  654. // update the DB header in file and cache
  655. WriteData(@DBHeader, 0, SizeOf(TDataHeader));
  656. end;
  657. procedure TModDatabase.DumpData;
  658. var
  659. Next,
  660. Pos : Integer;
  661. Size : Word;
  662. InUse : Byte;
  663. InUseStr : string;
  664. begin
  665. // dump stored records
  666. Pos := SizeOf(TSector) * (DBHeader.Sectors + 1) + SizeOf(TDataHeader);
  667. while (Pos < DBSize) do
  668. begin
  669. ReadData(@Size, Pos, 2);
  670. ReadData(@InUse, Pos + 2, 1);
  671. ReadData(@Next, Pos + 3, 4);
  672. if (InUse <> 0) then
  673. InUseStr := 'YES'
  674. else
  675. InUseStr := 'NO';
  676. TWXServer.Broadcast(endl + ANSI_15 + 'In use: ' + ANSI_14 + InUseStr);
  677. TWXServer.Broadcast(endl + ANSI_15 + 'Size: ' + ANSI_14 + IntToStr(Size));
  678. TWXServer.Broadcast(endl + ANSI_15 + 'Next: ' + ANSI_14 + IntToStr(Next) + endl);
  679. Inc(Pos, Size + 7);
  680. end;
  681. end;
  682. procedure TModDatabase.SetSectorVar(SectorIndex: Integer; const VarName, VarValue: string);
  683. var
  684. SectorVar: PSectorVar;
  685. VarOffset: Integer;
  686. P: Pointer;
  687. begin
  688. if (VarValue = '') then
  689. DeleteSectorVar(SectorIndex, VarName)
  690. else
  691. begin
  692. FindSectorVar(SectorIndex, VarName, SectorVar, VarOffset);
  693. if Assigned(SectorVar) then
  694. begin
  695. // Record already exists - write over the Value part of it
  696. SectorVar.Value := VarValue;
  697. // 7 Bytes record overhead + 10 bytes var name in record
  698. P := Pointer(Integer(SectorVar) + 10);
  699. WriteData(P, VarOffset + 17, 40);
  700. end
  701. else
  702. begin
  703. // Record doesn't exist - create a new one
  704. SectorVar := AllocMem(SizeOf(TSectorVar));
  705. SectorVar.VarName := VarName;
  706. SectorVar.Value := VarValue;
  707. AddSectorVar(SectorIndex, SectorVar);
  708. Dispose(SectorVar);
  709. end;
  710. end;
  711. end;
  712. function TModDatabase.GetSectorVar(SectorIndex: Integer; const VarName: string): string;
  713. var
  714. SectorVar: PSectorVar;
  715. VarOffset: Integer;
  716. begin
  717. // return value of this sector variable
  718. FindSectorVar(SectorIndex, VarName, SectorVar, VarOffset);
  719. if Assigned(SectorVar) then
  720. Result := SectorVar.Value
  721. else
  722. Result := '';
  723. end;
  724. // ---------------------------
  725. // null item retrieval
  726. procedure TModDatabase.NULLSector(var Sector : TSector);
  727. begin
  728. ZeroMemory(@Sector, SizeOf(Sector));
  729. Sector.Density := -1;
  730. end;
  731. procedure TModDatabase.NULLPlanet(var Planet : TPlanet);
  732. begin
  733. ZeroMemory(@Planet, SizeOf(Planet));
  734. end;
  735. procedure TModDatabase.NULLTrader(var Trader : TTrader);
  736. begin
  737. ZeroMemory(@Trader, SizeOf(Trader));
  738. end;
  739. procedure TModDatabase.NULLShip(var Ship : TShip);
  740. begin
  741. ZeroMemory(@Ship, SizeOf(Ship));
  742. end;
  743. // ***********************************************************
  744. // Protected Implementation
  745. procedure TModDatabase.AddSectorVar(SectorIndex: Integer; SectorVar: PSectorVar);
  746. const
  747. RecordSize = SizeOf(TSectorVar);
  748. var
  749. RecPtr,
  750. RecPos,
  751. NextRecord: Integer;
  752. S: TSector;
  753. begin
  754. // seek the end of the sector variable list for this sector and add the variable
  755. NextRecord := Sectors[SectorIndex].Vars;
  756. RecPtr := NextRecord;
  757. while (NextRecord <> 0) do
  758. begin
  759. RecPtr := NextRecord + 3;
  760. ReadData(@NextRecord, RecPtr, 4);
  761. end;
  762. RecPos := GetEmptyRecord(RecordSize);
  763. if (RecPtr = 0) then
  764. begin
  765. // update sector directly (no variables yet)
  766. S := LoadSector(SectorIndex);
  767. S.Vars := RecPos;
  768. SaveSector(S, SectorIndex);
  769. end
  770. else
  771. WriteData(@RecPos, RecPtr, 4);
  772. WriteRecord(SectorVar, RecPos, 0, RecordSize);
  773. end;
  774. procedure TModDatabase.FindSectorVar(SectorIndex: Integer; const VarName: string; var SectorVar: PSectorVar; var Index: Integer);
  775. const
  776. RecordSize = SizeOf(TSectorVar);
  777. var
  778. NextRecord : Integer;
  779. begin
  780. // Locate a sector variable of the specific name within the database, returning
  781. // a pointer to a record representing it in memory, and the index of its position
  782. // within the database.
  783. NextRecord := Sectors[SectorIndex].Vars;
  784. Index := 0;
  785. while (NextRecord <> 0) do
  786. begin
  787. SectorVar := AllocMem(RecordSize);
  788. ReadData(SectorVar, NextRecord + 7, RecordSize);
  789. if (SectorVar.VarName = VarName) then
  790. begin
  791. Index := NextRecord;
  792. Dispose(SectorVar);
  793. Break;
  794. end;
  795. Dispose(SectorVar);
  796. ReadData(@NextRecord, NextRecord + 3, 4);
  797. end;
  798. if (Index = 0) then
  799. SectorVar := nil;
  800. end;
  801. procedure TModDatabase.DeleteSectorVar(SectorIndex: Integer; const VarName: string);
  802. const
  803. RecordSize = SizeOf(TSectorVar);
  804. var
  805. LastRecord,
  806. ThisRecord,
  807. NextRecord: Integer;
  808. S: TSector;
  809. SectorVar: PSectorVar;
  810. begin
  811. // Find the sector var with a matching name and unlink it from the list,
  812. // flagging it as inactive.
  813. ThisRecord := Sectors[SectorIndex].Vars;
  814. LastRecord := 0;
  815. NextRecord := 0;
  816. while (ThisRecord <> 0) do
  817. begin
  818. SectorVar := AllocMem(RecordSize);
  819. ReadData(SectorVar, ThisRecord + 7, RecordSize);
  820. ReadData(@NextRecord, ThisRecord + 3, 4);
  821. if (SectorVar.VarName = VarName) then
  822. begin
  823. // update previous record
  824. if (LastRecord = 0) then
  825. begin
  826. // previous record is root (sector), update it
  827. S := LoadSector(SectorIndex);
  828. S.Vars := NextRecord;
  829. SaveSector(S, SectorIndex);
  830. end
  831. else
  832. begin
  833. // previous record is another record
  834. WriteData(@NextRecord, LastRecord + 3, 4);
  835. end;
  836. NextRecord := 0; // exit loop
  837. end;
  838. Dispose(SectorVar);
  839. LastRecord := ThisRecord;
  840. ThisRecord := NextRecord;
  841. end;
  842. end;
  843. procedure TModDatabase.AddWarpIn(Sect, Origin : Word);
  844. var
  845. P : Pointer;
  846. W : PWarpIn;
  847. begin
  848. W := AllocMem(SizeOf(TWarpIn));
  849. W^.Origin := Origin;
  850. // hook the new warp into this sector's warpin list
  851. P := Pointer(Integer(SectorWarpCache) + (Sect - 1) * SizeOf(Pointer));
  852. W^.NextWarpIn := PWarpIn(P^);
  853. Pointer(P^) := W;
  854. end;
  855. function TModDatabase.GetEmptyRecord(Size : Integer) : Integer;
  856. var
  857. I : Integer;
  858. begin
  859. Result := DBSize;
  860. I := 0;
  861. while (I < Length(EmptyRecordGroups)) do
  862. begin
  863. if (EmptyRecordGroups[I].RecordSize = Size) then
  864. begin
  865. if (Length(EmptyRecordGroups[I].EmptyRecords) > 0) then
  866. begin
  867. Result := EmptyRecordGroups[I].EmptyRecords[Length(EmptyRecordGroups[I].EmptyRecords) - 1];
  868. SetLength(EmptyRecordGroups[I].EmptyRecords, Length(EmptyRecordGroups[I].EmptyRecords) - 1);
  869. end;
  870. Break;
  871. end;
  872. Inc(I);
  873. end;
  874. end;
  875. procedure TModDatabase.CacheEmptyRecords;
  876. var
  877. Pos : Integer;
  878. Size : Word;
  879. InUse : Byte;
  880. begin
  881. // go through and find all empty records, storing them.
  882. Pos := SizeOf(TSector) * (DBHeader.Sectors + 1) + SizeOf(TDataHeader);
  883. while (Pos < DBSize) do
  884. begin
  885. ReadData(@Size, Pos, 2);
  886. ReadData(@InUse, Pos + 2, 1);
  887. if (InUse = 0) then
  888. CacheEmptyRecord(Pos, Size);
  889. Inc(Pos, Size + 7);
  890. end;
  891. end;
  892. procedure TModDatabase.CacheEmptyRecord(Index : Integer; Size : Word);
  893. var
  894. I : Integer;
  895. Found : Boolean;
  896. begin
  897. // loop through record cache lists, add record to a list matching its size
  898. Found := FALSE;
  899. I := 0;
  900. while (I < Length(EmptyRecordGroups)) do
  901. begin
  902. if (EmptyRecordGroups[I].RecordSize = Size) then
  903. begin
  904. Found := TRUE;
  905. Break;
  906. end;
  907. Inc(I);
  908. end;
  909. if not (Found) then
  910. begin
  911. I := Length(EmptyRecordGroups);
  912. SetLength(EmptyRecordGroups, I + 1);
  913. EmptyRecordGroups[I].RecordSize := Size;
  914. end;
  915. SetLength(EmptyRecordGroups[I].EmptyRecords, Length(EmptyRecordGroups[I].EmptyRecords) + 1);
  916. EmptyRecordGroups[I].EmptyRecords[Length(EmptyRecordGroups[I].EmptyRecords) - 1] := Index;
  917. end;
  918. procedure TModDatabase.ReadRecordList(List : TList; FirstPos : Integer);
  919. var
  920. NextRecord : Integer;
  921. RecordSize : Word;
  922. Rec : Pointer;
  923. begin
  924. NextRecord := FirstPos;
  925. while (NextRecord <> 0) do
  926. begin
  927. ReadData(@RecordSize, NextRecord, 2);
  928. Rec := AllocMem(RecordSize);
  929. ReadData(Rec, NextRecord + 7, RecordSize);
  930. List.Add(Rec);
  931. ReadData(@NextRecord, NextRecord + 3, 4);
  932. end;
  933. end;
  934. function TModDatabase.WriteRecordList(List : TList; RecordSize : Word) : Integer;
  935. var
  936. I,
  937. Pos,
  938. LastPos : Integer;
  939. begin
  940. Result := 0;
  941. if (List <> nil) then
  942. if (List.Count > 0) then
  943. begin
  944. LastPos := 0;
  945. for I := List.Count - 1 downto 0 do
  946. begin
  947. // Find the nearest zeroed record (or EOF)
  948. Pos := GetEmptyRecord(RecordSize);
  949. // Write it in
  950. WriteRecord(List[I], Pos, LastPos, RecordSize);
  951. LastPos := Pos;
  952. end;
  953. Result := LastPos;
  954. end;
  955. end;
  956. procedure TModDatabase.PurgeRecordList(FirstPos : Integer);
  957. var
  958. NextRecord : Integer;
  959. InUse : Byte;
  960. Size : Word;
  961. begin
  962. NextRecord := FirstPos;
  963. InUse := 0;
  964. while (NextRecord <> 0) do
  965. begin
  966. ReadData(@Size, NextRecord, 2);
  967. CacheEmptyRecord(NextRecord, Size);
  968. WriteData(@InUse, NextRecord + 2, 1);
  969. ReadData(@NextRecord, NextRecord + 3, 4);
  970. end;
  971. end;
  972. procedure TModDatabase.WriteRecord(Rec : Pointer; Pos, Next : Integer; Size : Word);
  973. var
  974. InUse : Byte;
  975. begin
  976. WriteData(@Size, Pos, 2);
  977. InUse := 1;
  978. WriteData(@InUse, Pos + 2, 1);
  979. WriteData(@Next, Pos + 3, 4);
  980. WriteData(Rec, Pos + 7, Size);
  981. end;
  982. procedure TModDatabase.ReadData(Data : Pointer; Index, Size : Integer);
  983. begin
  984. if (UseCache) then
  985. // cache enabled - read data directly from data cache
  986. CopyMemory(Data, Pointer(Integer(DataCache) + Index), Size)
  987. else
  988. begin
  989. // cache disabled - read data from file
  990. Seek(DataFile, Index);
  991. BlockRead(DataFile, Data^, Size);
  992. end;
  993. end;
  994. procedure TModDatabase.WriteData(Data : Pointer; Index, Size : Integer);
  995. var
  996. DataIndex : Pointer;
  997. CurDatabase : string;
  998. begin
  999. if (UseCache) then
  1000. begin
  1001. if (Index + Size > CacheSize) then
  1002. begin
  1003. // increase cache size
  1004. try
  1005. Inc(CacheSize, 50000);
  1006. ReallocMem(DataCache, CacheSize);
  1007. except
  1008. UseCache := FALSE;
  1009. CurDatabase := FDataFilename;
  1010. TWXServer.ClientMessage('Not enough free memory available for cache extensions - database cache disabled');
  1011. CloseDataBase;
  1012. OpenDatabase(CurDatabase);
  1013. WriteData(Data, Index, Size);
  1014. Exit;
  1015. end;
  1016. end;
  1017. DataIndex := Pointer(Integer(DataCache) + Index);
  1018. // compare memory with cached data
  1019. if not (CompareMem(DataIndex, Data, Size)) then
  1020. begin
  1021. // data is different - update data cache and file
  1022. CopyMemory(DataIndex, Data, Size);
  1023. Seek(DataFile, Index);
  1024. BlockWrite(DataFile, Data^, Size);
  1025. DBSize := FileSize(DataFile);
  1026. end;
  1027. end
  1028. else
  1029. begin
  1030. // write data directly to file
  1031. Seek(DataFile, Index);
  1032. BlockWrite(DataFile, Data^, Size);
  1033. DBSize := FileSize(DataFile);
  1034. end;
  1035. end;
  1036. procedure TModDatabase.OpenCache;
  1037. begin
  1038. // load the database into cache
  1039. if (CacheAllocated) then
  1040. CloseCache;
  1041. try
  1042. CacheSize := FileSize(DataFile) + 50000;
  1043. DataCache := AllocMem(CacheSize);
  1044. CacheAllocated := TRUE;
  1045. Seek(DataFile, 0);
  1046. BlockRead(DataFile, DataCache^, FileSize(DataFile));
  1047. except
  1048. TWXServer.Broadcast(endl + ANSI_12 + 'Error while caching database, database cache has been disabled' + ANSI_7 + endl);
  1049. UseCache := False;
  1050. end;
  1051. end;
  1052. procedure TModDatabase.CloseCache;
  1053. begin
  1054. // release data cache
  1055. if not (CacheAllocated) then
  1056. Exit;
  1057. FreeMem(DataCache, CacheSize);
  1058. CacheSize := 0;
  1059. CacheAllocated := FALSE;
  1060. end;
  1061. function TModDatabase.GetDatabaseName: string;
  1062. begin
  1063. Result := FDataFilename;
  1064. end;
  1065. procedure TModDatabase.SetDatabaseName(const Value: string);
  1066. begin
  1067. if (Value <> FDataFilename) then
  1068. begin
  1069. CloseDatabase;
  1070. OpenDatabase(Value);
  1071. end;
  1072. end;
  1073. function TModDatabase.GetUseCache: Boolean;
  1074. begin
  1075. Result := FUseCache;
  1076. end;
  1077. procedure TModDatabase.SetUseCache(Value: Boolean);
  1078. begin
  1079. if (FUseCache <> Value) then
  1080. begin
  1081. FUseCache := Value;
  1082. if (FUseCache) then
  1083. OpenCache
  1084. else
  1085. CloseCache;
  1086. end;
  1087. end;
  1088. function TModDatabase.GetRecording: Boolean;
  1089. begin
  1090. Result := FRecording;
  1091. end;
  1092. procedure TModDatabase.SetRecording(Value: Boolean);
  1093. begin
  1094. FRecording := Value;
  1095. TWXGUI.Recording := Value;
  1096. end;
  1097. function TModDatabase.GetLastPortCIM: TDateTime;
  1098. begin
  1099. Result := DBHeader.LastPortCIM;
  1100. end;
  1101. procedure TModDatabase.SetLastPortCIM(const Value: TDateTime);
  1102. begin
  1103. FDBHeader.LastPortCIM := Value;
  1104. end;
  1105. end.