database_troops.pas 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943
  1. unit database_troops;
  2. {$mode objfpc}{$H+}
  3. {$modeswitch nestedprocvars}
  4. interface
  5. uses
  6. Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls,
  7. Buttons, database_base, itemselectorframeunit, battleeventframeunit, fpjson,
  8. BGRABitmap, BGRABitmapTypes, fgl;
  9. type
  10. TSelectedTroopMemberData = record
  11. IsActive: Boolean;
  12. MemberId: Integer;
  13. ShiftX: Integer;
  14. ShiftY: Integer;
  15. end;
  16. TBitBtnCallback = procedure (Btn: TBitBtn) is nested;
  17. TMemberList = specialize TFPGList<TJSONObject>;
  18. { TDatabaseTroopsFrame }
  19. TDatabaseTroopsFrame = class(TDatabaseBaseFrame)
  20. AddEnemyBitBtn: TBitBtn;
  21. TroopBattleEventFrame: TBattleEventFrame;
  22. EnemyHiddenCheckBox: TCheckBox;
  23. EnemiesListBoxPanel: TPanel;
  24. EnemiesListBox: TListBox;
  25. BattleEventGroupBox: TGroupBox;
  26. RemoveEnemyBitBtn: TBitBtn;
  27. AutoNameButton: TButton;
  28. EnemiesFlowPanel: TFlowPanel;
  29. EnemiesPaintBox: TPaintBox;
  30. EnemiesPaintBoxPanel: TPanel;
  31. EnemyButtons: TPanel;
  32. ClearEnemiesBitBtn: TBitBtn;
  33. AlignEnemiesBitBtn: TBitBtn;
  34. TroopImageList: TImageList;
  35. SetBackgroundButton: TButton;
  36. TestBattleButton: TButton;
  37. SetBackgroundPanel: TPanel;
  38. TestBattlePanel: TPanel;
  39. ContentScrollBox: TScrollBox;
  40. NameEdit: TEdit;
  41. NameLabel: TLabel;
  42. MainGenralSettingsFlowPanel: TFlowPanel;
  43. GeneralSettingsGroupBox: TGroupBox;
  44. NamePanel: TPanel;
  45. AutoNamePanel: TPanel;
  46. SelectorContentSplitter: TSplitter;
  47. TroopSelectorFrame: TItemSelectorFrame;
  48. procedure AddEnemyBitBtnClick(Sender: TObject);
  49. procedure AlignEnemiesBitBtnClick(Sender: TObject);
  50. procedure AutoNameButtonClick(Sender: TObject);
  51. procedure ClearEnemiesBitBtnClick(Sender: TObject);
  52. procedure EnemiesListBoxDblClick(Sender: TObject);
  53. procedure EnemiesPaintBoxMouseDown(Sender: TObject; Button: TMouseButton;
  54. Shift: TShiftState; X, Y: Integer);
  55. procedure EnemiesPaintBoxMouseMove(Sender: TObject; Shift: TShiftState; X,
  56. Y: Integer);
  57. procedure EnemiesPaintBoxMouseUp(Sender: TObject; Button: TMouseButton;
  58. Shift: TShiftState; X, Y: Integer);
  59. procedure EnemiesPaintBoxPaint(Sender: TObject);
  60. procedure EnemyHiddenCheckBoxChange(Sender: TObject);
  61. procedure Init; override;
  62. procedure NameEditChange(Sender: TObject);
  63. procedure PagesTabControlChange(Sender: TObject);
  64. procedure RemoveEnemyBitBtnClick(Sender: TObject);
  65. procedure SetBackgroundButtonClick(Sender: TObject);
  66. function GetSortedMembers(Troop: TJSONObject): TMemberList;
  67. private
  68. IsLoading: Boolean;
  69. IsUpdatingHiddenCheckbox: Boolean;
  70. SelectedId: Integer;
  71. SelectedMember: TSelectedTroopMemberData;
  72. EnemyIsMoved: Boolean;
  73. EnemyImages: array of TBGRACustomBitmap;
  74. Background: TBGRACustomBitmap;
  75. EnemyImageBuffer: TBGRABitmap;
  76. procedure CacheEnemyImage(EnemyId: Integer);
  77. procedure CacheEnemyImages(Troop: TJSONObject);
  78. procedure UpdateBackgroud;
  79. function GetSelectedTroop: TJSONObject;
  80. function GetSelectedMember: TJSONObject;
  81. procedure LoadGeneralSettings(Troop: TJSONObject);
  82. procedure LoadBattleEvent(Troop: TJSONObject);
  83. function GetEnemyIdByCoordinates(X, Y: Integer): TSelectedTroopMemberData;
  84. procedure MoveSelectedEnemyByMouse(X, Y: Integer);
  85. procedure FillEnemiesListBox;
  86. procedure UpdateBtnsEnabled;
  87. procedure UpdateHiddenCheckbox;
  88. function IsAlignedByDefaultAlgorithm(Troop: TJSONObject): Boolean;
  89. procedure Align(Troop: TJSONObject);
  90. function GetTotalMembersWidth(Members: TJSONArray): Integer;
  91. function GetMemberWidth(Member: TJSONObject): Integer;
  92. procedure SetSameWidthForAllButtons;
  93. function GetScaleFactor: Double; inline;
  94. public
  95. procedure LoadTroopData(Id: Integer);
  96. destructor Destroy; override;
  97. end;
  98. const
  99. DEFAULT_Y_POSITION = 436;
  100. resourcestring
  101. TroopsHeader = 'Troops';
  102. implementation
  103. uses
  104. globals, gameproject, constants, doublebackgroundselection;
  105. {$R *.lfm}
  106. { TDatabaseTroopsFrame }
  107. procedure TDatabaseTroopsFrame.Init;
  108. begin
  109. IsLoading := True;
  110. TroopSelectorFrame.SetData(Db.Troops, @Db.ResizeTroops, @LoadTroopData);
  111. TroopSelectorFrame.Title := TroopsHeader;
  112. TroopSelectorFrame.JsonDataType := 'application/rpgmv-troops';
  113. TroopSelectorFrame.CreateElementCallback := @Db.CreateEmptyTroop;
  114. SetSameWidthForAllButtons;
  115. UpdateBackgroud;
  116. EnemyImageBuffer := TBGRABitmap.Create(EnemiesPaintBox.Width, EnemiesPaintBox.Height);
  117. EnemyIsMoved := False;
  118. SelectedMember.IsActive := False;
  119. TroopBattleEventFrame.SetDatabase(Db);
  120. SetLength(EnemyImages, Db.Enemies.Count);
  121. FillEnemiesListBox;
  122. if EnemiesListBox.Count > 1 then
  123. EnemiesListBox.ItemIndex := 0;
  124. LoadTroopData(1);
  125. end;
  126. procedure TDatabaseTroopsFrame.NameEditChange(Sender: TObject);
  127. var
  128. Troop: TJSONObject = nil;
  129. begin
  130. if not IsLoading then
  131. Troop := GetSelectedTroop;
  132. if Troop <> nil then
  133. Troop.Strings['name'] := NameEdit.Text;
  134. TroopSelectorFrame.RefreshSelectedName;
  135. end;
  136. procedure TDatabaseTroopsFrame.PagesTabControlChange(Sender: TObject);
  137. begin
  138. end;
  139. procedure TDatabaseTroopsFrame.RemoveEnemyBitBtnClick(Sender: TObject);
  140. var
  141. Troop: TJSONObject;
  142. MemberId: Integer;
  143. begin
  144. Troop := GetSelectedTroop;
  145. if SelectedMember.IsActive then begin
  146. MemberId := SelectedMember.MemberId;
  147. SelectedMember.IsActive := False;
  148. SelectedMember.MemberId := -1;
  149. Troop.Arrays['members'].Delete(MemberId);
  150. UpdateBtnsEnabled;
  151. UpdateHiddenCheckbox;
  152. EnemiesPaintBox.Refresh;
  153. end;
  154. end;
  155. procedure TDatabaseTroopsFrame.SetBackgroundButtonClick(Sender: TObject);
  156. var
  157. Bg1: String = '';
  158. Bg2: String = '';
  159. Bg1Json, Bg2Json: TJSONString;
  160. begin
  161. if (Db.System <> nil) and Db.System.Find('battleback1Name', Bg1Json) then
  162. Bg1 := Bg1Json.AsString;
  163. if (Db.System <> nil) and Db.System.Find('battleback2Name', Bg2Json) then
  164. Bg2 := Bg2Json.AsString;
  165. with DoubleBackgroundSelectionForm do
  166. if ShowBattleBgSelection(Bg1, Bg2) then begin
  167. Db.System.Strings['battleback1Name'] := SelectedBgs[1];
  168. Db.System.Strings['battleback2Name'] := SelectedBgs[2];
  169. UpdateBackgroud;
  170. EnemiesPaintBox.Refresh;
  171. end;
  172. end;
  173. function CompareMembersByY(const A, B: TJSONObject): Integer;
  174. var
  175. AX: Integer = 0;
  176. AY: Integer = 0;
  177. AXJson, AYJson: TJSONNumber;
  178. BX: Integer = 0;
  179. BY: Integer = 0;
  180. BXJson, BYJson: TJSONNumber;
  181. begin
  182. if A.Find('y', AYJson) then
  183. AY := AYJson.AsInteger;
  184. if B.Find('y', BYJson) then
  185. BY := BYJson.AsInteger;
  186. if AY = BY then begin
  187. if A.Find('x', AXJson) then
  188. AX := AXJson.AsInteger;
  189. if B.Find('x', BXJson) then
  190. BX := BXJson.AsInteger;
  191. if AX = BX then
  192. CompareMembersByY := 0
  193. else if AX < BX then
  194. CompareMembersByY := -1
  195. else
  196. CompareMembersByY := 1
  197. end else if AY < BY then
  198. CompareMembersByY := -1
  199. else
  200. CompareMembersByY := 1;
  201. end;
  202. function TDatabaseTroopsFrame.GetSortedMembers(Troop: TJSONObject): TMemberList;
  203. var
  204. Members: TJSONArray;
  205. List: TMemberList;
  206. I: Integer;
  207. begin
  208. List := TMemberList.Create;
  209. if Troop.Find('members', Members) then begin
  210. List := TMemberList.Create;
  211. List.Capacity := Members.Count;
  212. for I := 0 to Members.Count -1 do begin
  213. List.Add(Members.Objects[I]);
  214. end;
  215. List.Sort(@CompareMembersByY);
  216. end;
  217. GetSortedMembers := List;
  218. end;
  219. procedure TDatabaseTroopsFrame.EnemiesPaintBoxPaint(Sender: TObject);
  220. var
  221. Troop: TJSONObject;
  222. Members: TJSONArray;
  223. Member: TJSONObject;
  224. EnemyId, CenterX, BottomY, LeftX, TopY: Integer;
  225. EnemyIdJson, CenterXJson, BottomYJson: TJSONNumber;
  226. IsHidden: Boolean;
  227. IsHiddenJson: TJSONBoolean;
  228. EnemyImage: TBGRACustomBitmap;
  229. MaskedImage, MaskImage: TBGRABitmap;
  230. SortedMembers: TMemberList;
  231. I: Integer;
  232. procedure FillMemberData;
  233. begin
  234. EnemyId := 0;
  235. if (Member <> nil) and Member.Find('enemyId', EnemyIdJson) then
  236. EnemyId := EnemyIdJson.AsInteger;
  237. CenterX := 0;
  238. if (Member <> nil) and Member.Find('x', CenterXJson) then
  239. CenterX := CenterXJson.AsInteger;
  240. BottomY := 0;
  241. if (Member <> nil) and Member.Find('y', BottomYJson) then
  242. BottomY := BottomYJson.AsInteger;
  243. IsHidden := False;
  244. if (Member <> nil) and Member.Find('hidden', IsHiddenJson) then
  245. IsHidden := IsHiddenJson.AsBoolean;
  246. EnemyImage := nil;
  247. if (EnemyId > 0) and (EnemyId <= High(EnemyImages)) then
  248. EnemyImage := EnemyImages[EnemyId];
  249. if EnemyImage <> nil then begin
  250. LeftX := Round(CenterX * GetScaleFactor) - EnemyImage.Width div 2;
  251. TopY := Round(BottomY * GetScaleFactor) - EnemyImage.Height;
  252. end;
  253. end;
  254. begin
  255. if Background <> nil then
  256. { The shift accounts for position reserved for screen shake (the image width
  257. is larger than screen so that shaking wouldn't add black borders). }
  258. EnemyImageBuffer.CanvasBGRA.Draw(-30, 0, Background)
  259. else
  260. with EnemyImageBuffer.CanvasBGRA do begin
  261. Brush.Color := ColorToBGRA(clWhite);
  262. FillRect(0, 0, EnemiesPaintBox.Width, EnemiesPaintBox.Height);
  263. end;
  264. Troop := GetSelectedTroop;
  265. if (Troop <> nil) and Troop.Find('members', Members) then begin
  266. SortedMembers := GetSortedMembers(Troop);
  267. try
  268. for I := 0 to SortedMembers.Count -1 do begin
  269. Member := SortedMembers[I];
  270. FillMemberData;
  271. if EnemyImage <> nil then begin
  272. if IsHidden then begin
  273. MaskedImage := TBGRABitmap.Create(EnemyImage);
  274. MaskImage := TBGRABitmap.Create(EnemyImage.Width, EnemyImage.Height, BGRA(160, 160, 160));
  275. MaskedImage.ApplyMask(MaskImage);
  276. EnemyImageBuffer.CanvasBGRA.Draw(LeftX, TopY, MaskedImage);
  277. MaskImage.Free;
  278. MaskedImage.Free;
  279. end else
  280. EnemyImageBuffer.CanvasBGRA.Draw(LeftX, TopY, EnemyImage);
  281. end;
  282. end;
  283. finally
  284. SortedMembers.Free;
  285. end;
  286. for I := 0 to Members.Count -1 do begin
  287. Member := Members.Objects[I];
  288. FillMemberData;
  289. if SelectedMember.IsActive and (SelectedMember.MemberId = I) then
  290. with EnemyImageBuffer.CanvasBGRA do begin
  291. Pen.Color := clWhite;
  292. Brush.Style := bsClear;
  293. Rectangle(LeftX, TopY, LeftX + EnemyImage.Width, TopY + EnemyImage.Height);
  294. Pen.Color := clBlack;
  295. Rectangle(LeftX - 1, TopY - 1, LeftX + EnemyImage.Width + 1, TopY + EnemyImage.Height + 1);
  296. Pen.Color := clWhite;
  297. Rectangle(LeftX - 2, TopY - 2, LeftX + EnemyImage.Width + 2, TopY + EnemyImage.Height + 2);
  298. end;
  299. end;
  300. end;
  301. EnemyImageBuffer.Draw(EnemiesPaintBox.Canvas, 0, 0);
  302. end;
  303. procedure TDatabaseTroopsFrame.EnemyHiddenCheckBoxChange(Sender: TObject);
  304. var
  305. Member: TJSONObject;
  306. begin
  307. if IsUpdatingHiddenCheckbox then
  308. Exit;
  309. Member := GetSelectedMember;
  310. if Member <> nil then
  311. Member.Booleans['hidden'] := EnemyHiddenCheckBox.Checked;
  312. EnemiesPaintBox.Refresh
  313. end;
  314. procedure TDatabaseTroopsFrame.EnemiesPaintBoxMouseUp(Sender: TObject;
  315. Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  316. begin
  317. if SelectedMember.IsActive and EnemyIsMoved then begin
  318. MoveSelectedEnemyByMouse(X, Y);
  319. EnemiesPaintBox.Refresh;
  320. end;
  321. EnemyIsMoved := False;
  322. end;
  323. procedure TDatabaseTroopsFrame.EnemiesPaintBoxMouseDown(Sender: TObject;
  324. Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  325. begin
  326. SelectedMember := GetEnemyIdByCoordinates(X, Y);
  327. if SelectedMember.IsActive then
  328. EnemyIsMoved := True
  329. else
  330. EnemyIsMoved := False;
  331. UpdateBtnsEnabled;
  332. UpdateHiddenCheckbox;
  333. EnemiesPaintBoxPanel.SetFocus;
  334. EnemiesPaintBox.Refresh;
  335. end;
  336. procedure TDatabaseTroopsFrame.ClearEnemiesBitBtnClick(Sender: TObject);
  337. var
  338. Troop: TJSONObject;
  339. begin
  340. Troop := GetSelectedTroop;
  341. SelectedMember.IsActive := False;
  342. SelectedMember.MemberId := -1;
  343. Troop.Delete('members');
  344. Troop.Arrays['members'] := TJSONArray.Create;
  345. UpdateBtnsEnabled;
  346. UpdateHiddenCheckbox;
  347. EnemiesPaintBox.Refresh;
  348. end;
  349. procedure TDatabaseTroopsFrame.EnemiesListBoxDblClick(Sender: TObject);
  350. begin
  351. if AddEnemyBitBtn.Enabled then
  352. AddEnemyBitBtn.Click;
  353. end;
  354. procedure TDatabaseTroopsFrame.AddEnemyBitBtnClick(Sender: TObject);
  355. var
  356. Troop: TJSONObject;
  357. NewEnemyId: Integer;
  358. NewX, NewY: Integer;
  359. Member: TJSONObject;
  360. Img: TBGRACustomBitmap;
  361. DefaultAlignment: Boolean;
  362. begin
  363. Troop := GetSelectedTroop;
  364. if (Troop <> nil) then begin
  365. DefaultAlignment := IsAlignedByDefaultAlgorithm(Troop);
  366. NewX := SCREEN_WIDTH div 2;
  367. NewY := DEFAULT_Y_POSITION;
  368. NewEnemyId := EnemiesListBox.ItemIndex + 1;
  369. CacheEnemyImage(NewEnemyId);
  370. Img := EnemyImages[NewEnemyId];
  371. if Img <> nil then
  372. Dec(NewX, Round(Img.Width / GetScaleFactor / 2));
  373. Member := TJSONObject.Create([
  374. 'enemyId', NewEnemyId,
  375. 'x', NewX,
  376. 'y', NewY,
  377. 'hidden', False
  378. ]);
  379. Troop.Arrays['members'].Add(Member);
  380. if DefaultAlignment then
  381. Align(Troop);
  382. UpdateBtnsEnabled;
  383. EnemiesPaintBox.Refresh;
  384. end;
  385. end;
  386. procedure TDatabaseTroopsFrame.AlignEnemiesBitBtnClick(Sender: TObject);
  387. begin
  388. Align(GetSelectedTroop);
  389. EnemiesPaintBox.Refresh;
  390. end;
  391. procedure TDatabaseTroopsFrame.AutoNameButtonClick(Sender: TObject);
  392. var
  393. Troop: TJSONObject = nil;
  394. Members: TJSONArray = nil;
  395. Enemy: TJSONObject = nil;
  396. NameJson: TJSONString = nil;
  397. Name1: String = '';
  398. Name2: String = '';
  399. Count1: Integer = 0;
  400. Count2: Integer = 0;
  401. HasMore: Boolean = False;
  402. I: Integer;
  403. function NameWithCount(N: String; C: Integer): String;
  404. begin
  405. if C = 1 then
  406. NameWithCount := N
  407. else
  408. NameWithCount := '%s*%d'.Format([N, C]);
  409. end;
  410. var
  411. Res: String;
  412. begin
  413. Troop := GetSelectedTroop;
  414. if (Troop <> nil) and Troop.Find('members', Members) then begin
  415. for I := 0 to Members.Count -1 do begin
  416. Enemy := Db.Enemies.Objects[Members.Objects[I].Integers['enemyId']];
  417. if Enemy.Find('name', NameJson) then begin
  418. if (Name1 = NameJson.AsString) and (Count1 > 0) then
  419. Inc(Count1)
  420. else if (Name2 = NameJson.AsString) and (Count2 > 0) then
  421. Inc(Count2)
  422. else if Name1 = '' then begin
  423. Name1 := NameJson.AsString;
  424. Count1 := 1;
  425. end else if Name2 = '' then begin
  426. Name2 := NameJson.AsString;
  427. Count2 := 1;
  428. end else
  429. HasMore := True;
  430. end;
  431. end;
  432. end;
  433. Res := NameWithCount(Name1, Count1);
  434. if Count2 > 0 then
  435. Res := Res + ', ' + NameWithCount(Name2, Count2);
  436. if HasMore then
  437. Res := Res + '...';
  438. NameEdit.Text := Res;
  439. end;
  440. procedure TDatabaseTroopsFrame.EnemiesPaintBoxMouseMove(Sender: TObject;
  441. Shift: TShiftState; X, Y: Integer);
  442. begin
  443. if SelectedMember.IsActive and EnemyIsMoved then begin
  444. MoveSelectedEnemyByMouse(X, Y);
  445. EnemiesPaintBox.Refresh
  446. end;
  447. end;
  448. procedure TDatabaseTroopsFrame.CacheEnemyImage(EnemyId: Integer);
  449. var
  450. Enemy: TJSONObject;
  451. EnemyFilename: String = '';
  452. EnemyFilenameJson: TJSONString;
  453. Hue: Integer = 0;
  454. HueJson: TJSONNumber;
  455. BattleStyle: TBattleStyle;
  456. begin
  457. if (EnemyId >= 1) and (EnemyId < Db.Enemies.Count) then begin
  458. Enemy := Db.Enemies.Objects[EnemyId];
  459. if Enemy.Find('battlerName', EnemyFilenameJson) then
  460. EnemyFilename := EnemyFilenameJson.AsString;
  461. if Enemy.Find('battlerHue', HueJson) then
  462. Hue := HueJson.AsInteger;
  463. BattleStyle := bsFrontView;
  464. if Db.IsSvBattle then
  465. BattleStyle := bsSideView;
  466. EnemyImages[EnemyId] := Game.GetEnemyGraphicsBGRA(EnemyFilename, Hue,
  467. EnemiesPaintBox.Width / SCREEN_WIDTH, BattleStyle);
  468. end;
  469. end;
  470. procedure TDatabaseTroopsFrame.CacheEnemyImages(Troop: TJSONObject);
  471. var
  472. Members: TJSONArray;
  473. Member: TJSONObject;
  474. EnemyIdJson: TJSONNumber;
  475. I: Integer;
  476. begin
  477. if Troop.Find('members', Members) then
  478. for I := 0 to Members.Count - 1 do begin
  479. Member := Members.Objects[I];
  480. if Member.Find('enemyId', EnemyIdJson) then
  481. CacheEnemyImage(EnemyIdJson.AsInteger);
  482. end;
  483. end;
  484. procedure TDatabaseTroopsFrame.UpdateBackgroud;
  485. var
  486. Bg1: String = '';
  487. Bg2: String = '';
  488. Bg1Json, Bg2Json: TJSONString;
  489. begin
  490. if (Db.System <> nil) and Db.System.Find('battleback1Name', Bg1Json) then
  491. Bg1 := Bg1Json.AsString;
  492. if (Db.System <> nil) and Db.System.Find('battleback2Name', Bg2Json) then
  493. Bg2 := Bg2Json.AsString;
  494. Background := Game.GetBackgroundBGRA(btBattle, Bg1, Bg2, EnemiesPaintBox.Width / SCREEN_WIDTH);
  495. end;
  496. function TDatabaseTroopsFrame.GetSelectedTroop: TJSONObject;
  497. begin
  498. GetSelectedTroop := nil;
  499. if Db.Troops <> nil then
  500. if (SelectedId >= 1) and (SelectedId <= Db.Troops.Count -1) then
  501. GetSelectedTroop := Db.Troops.Objects[SelectedId];
  502. end;
  503. function TDatabaseTroopsFrame.GetSelectedMember: TJSONObject;
  504. var
  505. Troop: TJSONObject;
  506. Members: TJSONArray;
  507. begin
  508. GetSelectedMember := nil;
  509. if not SelectedMember.IsActive then
  510. Exit;
  511. if SelectedMember.MemberId < 0 then
  512. Exit;
  513. Troop := GetSelectedTroop;
  514. if (Troop <> nil) and Troop.Find('members', Members) then begin
  515. if SelectedMember.MemberId < Members.Count then
  516. GetSelectedMember := Members.Objects[SelectedMember.MemberId];
  517. end;
  518. end;
  519. procedure TDatabaseTroopsFrame.LoadGeneralSettings(Troop: TJSONObject);
  520. var
  521. TroopName: String;
  522. NameJson: TJSONString;
  523. begin
  524. if (Troop <> nil) and Troop.Find('name', NameJson) then
  525. TroopName := NameJson.AsString;
  526. NameEdit.Text := TroopName;
  527. end;
  528. procedure TDatabaseTroopsFrame.LoadBattleEvent(Troop: TJSONObject);
  529. var
  530. Pages: TJSONArray = nil;
  531. begin
  532. if (Troop <> nil) and Troop.Find('pages', Pages) then
  533. TroopBattleEventFrame.SetEditedPages(Pages, Troop);
  534. end;
  535. function TDatabaseTroopsFrame.GetEnemyIdByCoordinates(X, Y: Integer
  536. ): TSelectedTroopMemberData;
  537. var
  538. Troop: TJSONObject = nil;
  539. Members: TJSONArray = nil;
  540. Member: TJSONObject = nil;
  541. CenterX, BottomY, LeftX, TopY, EnemyId: Integer;
  542. EnemyImage: TBGRACustomBitmap;
  543. CenterXJson, BottomYJson, EnemyIdJson: TJSONNumber;
  544. I: Integer;
  545. XMatches, YMatches: Boolean;
  546. begin
  547. Troop := GetSelectedTroop;
  548. if (Troop <> nil) and Troop.Find('members', Members) then begin
  549. for I := 0 to Members.Count -1 do begin
  550. Member := Members.Objects[I];
  551. EnemyId := 0;
  552. if (Member <> nil) and Member.Find('enemyId', EnemyIdJson) then
  553. EnemyId := EnemyIdJson.AsInteger;
  554. CenterX := 0;
  555. if (Member <> nil) and Member.Find('x', CenterXJson) then
  556. CenterX := CenterXJson.AsInteger;
  557. BottomY := 0;
  558. if (Member <> nil) and Member.Find('y', BottomYJson) then
  559. BottomY := BottomYJson.AsInteger;
  560. EnemyImage := nil;
  561. if (EnemyId > 0) and (EnemyId <= High(EnemyImages)) then
  562. EnemyImage := EnemyImages[EnemyId];
  563. if EnemyImage <> nil then begin
  564. LeftX := Round(CenterX * GetScaleFactor) - EnemyImage.Width div 2;
  565. TopY := Round(BottomY * GetScaleFactor) - EnemyImage.Height;
  566. XMatches := (X >= LeftX) and (X <= LeftX + EnemyImage.Width);
  567. YMatches := (Y >= TopY) and (Y <= TopY + EnemyImage.Height);
  568. if XMatches and YMatches then begin
  569. GetEnemyIdByCoordinates.MemberId := I;
  570. GetEnemyIdByCoordinates.ShiftX := X - Round(CenterX * GetScaleFactor);
  571. GetEnemyIdByCoordinates.ShiftY := Y - Round(BottomY * GetScaleFactor);
  572. GetEnemyIdByCoordinates.IsActive := True;
  573. Exit
  574. end
  575. end
  576. end
  577. end;
  578. GetEnemyIdByCoordinates.MemberId := -1;
  579. GetEnemyIdByCoordinates.IsActive := False;
  580. end;
  581. procedure TDatabaseTroopsFrame.MoveSelectedEnemyByMouse(X, Y: Integer);
  582. var
  583. NewX, NewY: Integer;
  584. Troop, Member: TJSONObject;
  585. begin
  586. //TODO
  587. NewX := Round((X - SelectedMember.ShiftX) / GetScaleFactor);
  588. NewY := Round((Y - SelectedMember.ShiftY) / GetScaleFactor);
  589. Troop := GetSelectedTroop;
  590. Member := Troop.Arrays['members'].Objects[SelectedMember.MemberId];
  591. Member.Integers['x'] := NewX;
  592. Member.Integers['y'] := NewY;
  593. end;
  594. procedure TDatabaseTroopsFrame.FillEnemiesListBox;
  595. var
  596. I: Integer;
  597. begin
  598. with EnemiesListBox.Items do begin
  599. BeginUpdate;
  600. Clear;
  601. for I := 1 to Db.Enemies.Count -1 do begin
  602. Add(Db.FormatObjName(Db.Enemies.Objects[I], True));
  603. end;
  604. EndUpdate;
  605. end;
  606. end;
  607. procedure TDatabaseTroopsFrame.UpdateBtnsEnabled;
  608. var
  609. Troop: TJSONObject;
  610. Members: TJSONArray;
  611. CanAdd, CanDelete, CanClear, CanAlign: Boolean;
  612. begin
  613. CanAdd := False;
  614. CanDelete := SelectedMember.IsActive and (SelectedMember.MemberId >= 0);
  615. CanClear := False;
  616. CanAlign := False;
  617. Troop := GetSelectedTroop;
  618. if (Troop <> nil) and Troop.Find('members', Members) then begin
  619. CanAdd := Members.Count < 8;
  620. CanClear := Members.Count > 0;
  621. CanAlign := Members.Count > 0;
  622. end;
  623. AddEnemyBitBtn.Enabled := CanAdd;
  624. RemoveEnemyBitBtn.Enabled := CanDelete;
  625. ClearEnemiesBitBtn.Enabled := CanClear;
  626. AlignEnemiesBitBtn.Enabled := CanAlign;
  627. end;
  628. procedure TDatabaseTroopsFrame.UpdateHiddenCheckbox;
  629. var
  630. Member: TJSONObject;
  631. IsHidden: Boolean;
  632. IsHiddenJson: TJSONBoolean;
  633. begin
  634. IsUpdatingHiddenCheckbox := True;
  635. Member := GetSelectedMember;
  636. if (Member <> nil) and Member.Find('hidden', IsHiddenJson) then
  637. IsHidden := IsHiddenJson.AsBoolean;
  638. EnemyHiddenCheckBox.Checked := IsHidden;
  639. EnemyHiddenCheckBox.Enabled := Member <> nil;
  640. IsUpdatingHiddenCheckbox := False;
  641. end;
  642. function TDatabaseTroopsFrame.IsAlignedByDefaultAlgorithm(Troop: TJSONObject
  643. ): Boolean;
  644. var
  645. Members: TJSONArray;
  646. TotalWidth: Integer;
  647. I: Integer;
  648. CurrectXOffset: Integer = 0;
  649. EveryItemOffset: Integer = 0;
  650. XIsAligned, YIsAligned: Boolean;
  651. function RelativelyEquals(A, B: Integer): Boolean;
  652. var
  653. AllowedDifference: Integer;
  654. begin
  655. { The exact difference can be different because we use downscaled images
  656. for comparisons, and scale factor is different depending on the system
  657. DPI. So, we allow things to be a few pixels off.
  658. Also, RPG Maker MV uses a slightly different algorithm, but this lazy
  659. comparison at least recognises RMMV's default placement when all the
  660. enemies are of the same type. }
  661. AllowedDifference := Round(5 / GetScaleFactor);
  662. RelativelyEquals := Abs(A - B) <= AllowedDifference;
  663. end;
  664. begin
  665. Members := Troop.Arrays['members'];
  666. if Members.Count < 1 then begin
  667. IsAlignedByDefaultAlgorithm := True;
  668. Exit;
  669. end;
  670. YIsAligned := True;
  671. for I := 0 to Members.Count -1 do
  672. if Members.Objects[I].Integers['y'] <> DEFAULT_Y_POSITION then
  673. YIsAligned := False;
  674. TotalWidth := GetTotalMembersWidth(Members);
  675. if TotalWidth <= SCREEN_WIDTH then
  676. CurrectXOffset := (SCREEN_WIDTH - TotalWidth) div 2 + GetMemberWidth(Members.Objects[0]) div 2
  677. else begin
  678. EveryItemOffset := (TotalWidth - SCREEN_WIDTH + GetMemberWidth(Members.Objects[0]) div 2) div Members.Count;
  679. CurrectXOffset := GetMemberWidth(Members.Objects[0]) div 2;
  680. end;
  681. XIsAligned := True;
  682. for I := 0 to Members.Count -1 do begin
  683. if not RelativelyEquals(Members.Objects[I].Integers['x'], CurrectXOffset) then
  684. XIsAligned := False;
  685. Inc(CurrectXOffset, GetMemberWidth(Members.Objects[I]) - EveryItemOffset);
  686. end;
  687. IsAlignedByDefaultAlgorithm := XIsAligned and YIsAligned;
  688. end;
  689. function TDatabaseTroopsFrame.GetTotalMembersWidth(Members: TJSONArray): Integer;
  690. var
  691. I: Integer;
  692. ImgForWidth: TBGRACustomBitmap;
  693. Sum: Integer;
  694. begin
  695. Sum := 0;
  696. for I := 0 to Members.Count -1 do begin
  697. ImgForWidth := EnemyImages[Members.Objects[I].Integers['enemyId']];
  698. if ImgForWidth <> nil then
  699. Inc(Sum, ImgForWidth.Width);
  700. end;
  701. GetTotalMembersWidth := Round(Sum / GetScaleFactor);
  702. end;
  703. function TDatabaseTroopsFrame.GetMemberWidth(Member: TJSONObject): Integer;
  704. var
  705. Img: TBGRACustomBitmap;
  706. begin
  707. Img := EnemyImages[Member.Integers['enemyId']];
  708. if Img <> nil then
  709. GetMemberWidth := Round(Img.Width / GetScaleFactor)
  710. else
  711. GetMemberWidth := 0;
  712. end;
  713. procedure TDatabaseTroopsFrame.SetSameWidthForAllButtons;
  714. procedure DoForAllButtons(Callback: TBitBtnCallback);
  715. begin
  716. Callback(AddEnemyBitBtn);
  717. Callback(RemoveEnemyBitBtn);
  718. Callback(ClearEnemiesBitBtn);
  719. Callback(AlignEnemiesBitBtn);
  720. end;
  721. var
  722. MaxWidth: Integer = 0;
  723. procedure FindMaxWidth(Btn: TBitBtn);
  724. begin
  725. if Btn.Width > MaxWidth then
  726. MaxWidth := Btn.Width;
  727. end;
  728. procedure SetMaxWidth(Btn: TBitBtn);
  729. begin
  730. Btn.AutoSize := False;
  731. Btn.Width := MaxWidth;
  732. end;
  733. begin
  734. DoForAllButtons(@FindMaxWidth);
  735. DoForAllButtons(@SetMaxWidth);
  736. end;
  737. function TDatabaseTroopsFrame.GetScaleFactor: Double; inline;
  738. begin
  739. GetScaleFactor := EnemiesPaintBox.Width / SCREEN_WIDTH;
  740. end;
  741. procedure TDatabaseTroopsFrame.Align(Troop: TJSONObject);
  742. var
  743. Members: TJSONArray;
  744. TotalWidth: Integer;
  745. I: Integer;
  746. CurrectXOffset: Integer = 0;
  747. EveryItemOffset: Integer = 0;
  748. begin
  749. { TODO: fix alignment so that it works well with troops of different enemy
  750. types. (Maybe add a second row, too?) }
  751. Members := Troop.Arrays['members'];
  752. for I := 0 to Members.Count -1 do
  753. Members.Objects[I].Integers['y'] := DEFAULT_Y_POSITION;
  754. TotalWidth := GetTotalMembersWidth(Members);
  755. if TotalWidth <= SCREEN_WIDTH then
  756. CurrectXOffset := (SCREEN_WIDTH - TotalWidth) div 2 + GetMemberWidth(Members.Objects[0]) div 2
  757. else begin
  758. EveryItemOffset := (TotalWidth - SCREEN_WIDTH + GetMemberWidth(Members.Objects[0]) div 2) div Members.Count;
  759. CurrectXOffset := GetMemberWidth(Members.Objects[0]) div 2;
  760. end;
  761. for I := 0 to Members.Count -1 do begin
  762. Members.Objects[I].Integers['x'] := CurrectXOffset;
  763. Inc(CurrectXOffset, GetMemberWidth(Members.Objects[I]) - EveryItemOffset);
  764. end;
  765. end;
  766. procedure TDatabaseTroopsFrame.LoadTroopData(Id: Integer);
  767. var
  768. Troop: TJSONObject = nil;
  769. begin
  770. IsLoading := True;
  771. SelectedId := Id;
  772. Troop := GetSelectedTroop;
  773. CacheEnemyImages(Troop);
  774. LoadGeneralSettings(Troop);
  775. LoadBattleEvent(Troop);
  776. SelectedMember.IsActive := False;
  777. SelectedMember.MemberId := -1;
  778. EnemyIsMoved := False;
  779. UpdateBtnsEnabled;
  780. UpdateHiddenCheckbox;
  781. EnemiesPaintBox.Refresh;
  782. IsLoading := False;
  783. end;
  784. destructor TDatabaseTroopsFrame.Destroy;
  785. var
  786. I: Integer;
  787. begin
  788. for I := Low(EnemyImages) to High(EnemyImages) - 1 do
  789. if EnemyImages[I] <> nil then begin
  790. EnemyImages[I].Free;
  791. EnemyImages[I] := nil;
  792. end;
  793. if Background <> nil then
  794. Background.Free;
  795. if EnemyImageBuffer <> nil then
  796. EnemyImageBuffer.Free;
  797. inherited Destroy;
  798. end;
  799. end.