source: tags/1.1.0/Engine.pas

Last change on this file was 87, checked in by chronos, 6 months ago
  • Fixed: Show stats for more than 4 player in two parts second bellow first part.
  • Fixed: Translate player names.
  • Fixed: Player view size calculation for 5 players.
  • Modified: Use same default keys as in the original game.
File size: 61.6 KB
Line 
1unit Engine;
2
3interface
4
5uses
6 Dialogs, Classes, SysUtils, Graphics, SpecializedMatrix, RegistryEx, Common,
7 IntfGraphics, FPImage, LCLType, SpecializedBitmap, GraphType, Math, Rectangle,
8 Syncobjs, Threading, Forms, DateUtils, AudioSystem, Generics.Collections,
9 World, Matter, Sound;
10
11const
12 MaxBulletCount = 10;
13 EnergyDecreaseDig = 0;
14 EnergyDecreaseMove = 0.00025; // ~2.5 minutes of movement
15 EnergyDecreaseShoot = 0.005; // ~20 seconds of shooting
16 EnergyDecreaseOutside = 0.00003; // ~5 minutes of live
17 EnergyIncreaseHome = 0.001;
18 ShieldIncreaseHome = 0.001;
19 ShieldDecreaseHit = 0.15;
20 ExplosionBulletCount = 100;
21 ExplosionRange = 20;
22 ExplosionBulletMaxSpeed = 0.5;
23 ExplosionBulletMinSpeed = 0.2;
24 BulletExplosionRange = 4;
25 ShootDelay = 0.1; // seconds
26 DigDelay = 0.2; // seconds
27 ShootDigDelay = 0.05; // seconds
28 MoveDelay = 0.05; // seconds
29 PlayerFrameWidth = 80;
30 PlayerFrameHeight = 80;
31 PlayerHouseSize = 29;
32 PlayerHouseDoorSize = 7;
33 ExplosionDelay = 2;
34 NewRoundDelay = 2;
35 clTuna = $5555ff;
36 clPurple = $aa00aa;
37 clOrange = $0055aa;
38 clDarkOrange = $0000aa;
39 clDarkGreen = $00aa00;
40 clCyan = $ffff00;
41
42type
43 TEngine = class;
44 TPlayer = class;
45
46 TRealPoint = record
47 X, Y: Real;
48 end;
49
50 TPlayerKeys = record
51 Left: Word;
52 Right: Word;
53 Up: Word;
54 Down: Word;
55 Shoot: Word;
56 end;
57
58 { TBullet }
59
60 TBullet = class
61 Player: TPlayer;
62 Position: TRealPoint;
63 PositionTail: TRealPoint;
64 Direction: TRealPoint;
65 MaxDistance: Integer;
66 Distance: Real;
67 StopByDirt: Boolean;
68 CanKill: Boolean;
69 constructor Create;
70 end;
71
72 { TBullets }
73
74 TBullets = class(TObjectList<TBullet>)
75 procedure HideAll;
76 end;
77
78 { TTank }
79
80 TTank = class
81 Image: TMatrixByte;
82 Mask: TMatrixByte;
83 procedure Assign(Source: TTank);
84 constructor Create;
85 destructor Destroy; override;
86 end;
87
88 { TTanks }
89
90 TTanks = class(TObjectList<TTank>)
91 procedure Assign(Source: TTanks);
92 end;
93
94 TColisionState = record
95 Blocking: Boolean;
96 Diggable: Boolean;
97 end;
98
99 { TPlayer }
100
101 TPlayer = class
102 private
103 FExploded: Boolean;
104 NewDirection: Integer;
105 NewPosition: TPoint;
106 Dig: Boolean;
107 LastPos: TPoint;
108 ExplosionPending: Boolean;
109 ExplosionTime: TDateTime;
110 procedure SetExploded(const AValue: Boolean);
111 function ShowTankProc(Item1, Item2: Byte): Byte;
112 function HideTankProc(Item1, Item2: Byte): Byte;
113 function DigProc(Item1, Item2: Byte): Byte;
114 public
115 Name: string;
116 Color1: TColor;
117 Color2: TColor;
118 Id: Integer;
119 Enabled: Boolean;
120 Engine: TEngine;
121 Position: TPoint;
122 StartPosition: TPoint;
123 Score: Integer;
124 Direction: Integer;
125 ScreenFrame: TRectangle;
126 Keys: TPlayerKeys;
127 Tanks: TTanks;
128 Bullets: TBullets;
129 LastShootTime: TDateTime;
130 LastDigTime: TDateTime;
131 LastMoveTime: TDateTime;
132 Energy: Real;
133 LastEnergy: Real;
134 Shield: Real;
135 LastShield: Real;
136 House: TRectangle;
137 ShotsCount: Integer;
138 ShotsHit: Integer;
139 MetersDug: Integer;
140 MetersTravelled: Integer;
141 procedure ResetTank;
142 procedure Init;
143 procedure Explosion(Position: TPoint; Distance: Integer);
144 procedure Control;
145 function CheckNewDestination: Boolean;
146 procedure Tick;
147 procedure Paint;
148 procedure PlaceHouse;
149 function CheckColision: TColisionState;
150 procedure ShowTank;
151 procedure HideTank;
152 procedure InitTanks;
153 procedure ClearKeys;
154 constructor Create;
155 destructor Destroy; override;
156 procedure Assign(Source: TPlayer);
157 procedure LoadFromRegistry(Context: TRegistryContext);
158 procedure SaveToRegistry(Context: TRegistryContext);
159 property Exploded: Boolean read FExploded write SetExploded;
160 end;
161
162 { TPlayers }
163
164 TPlayers = class(TObjectList<TPlayer>)
165 Engine: TEngine;
166 function AddNew: TPlayer;
167 procedure Assign(Players: TPlayers);
168 procedure LoadFromRegistry(Context: TRegistryContext);
169 procedure SaveToRegistry(Context: TRegistryContext);
170 function GetAliveCount: Integer;
171 function GetWinner: TPlayer;
172 function GetEnabledCount: Integer;
173 procedure EnableMore;
174 procedure EnableLess;
175 procedure ClearKey(Key: Integer);
176 end;
177
178 { TKeyboard }
179
180 TKeyboard = class
181 KeyState: array[0..High(Word)] of Boolean;
182 procedure ClearAll;
183 end;
184
185 { TSystemThread }
186
187 TSystemThread = class(TListedThread)
188 Engine: TEngine;
189 procedure Execute; override;
190 end;
191
192 { TDrawThread }
193
194 TDrawThread = class(TListedThread)
195 private
196 procedure DrawSync;
197 public
198 Engine: TEngine;
199 procedure Execute; override;
200 end;
201
202 TCanvasMethod = procedure (Canvas: TCanvas) of object;
203
204 TGameState = (gsMenu, gsGame, gsNewRound, gsMap, gsInformation,
205 gsInstructions, gsSettings, gsPlayerKeys, gsPlayerKeysRedefine);
206
207 TPlayerColor = (pcGreen);
208
209 { TEngine }
210
211 TEngine = class(TComponent)
212 private
213 FActive: Boolean;
214 FBitmap: TBitmap;
215 FBitmapLock: TCriticalSection;
216 FOnClose: TNotifyEvent;
217 FOnGameEnd: TNotifyEvent;
218 FRedrawPending: Boolean;
219 FBitmapLower: TBitmapTColor;
220 FDrawThread: TDrawThread;
221 FState: TGameState;
222 FSystemThread: TSystemThread;
223 FClearBackground: Boolean;
224 FStateTime: TDateTime;
225 FShowMenuStats: Boolean;
226 FSelectedPlayer: TPlayer;
227 procedure InitDigMasks;
228 procedure SetActive(const AValue: Boolean);
229 procedure SetBitmap(const AValue: TBitmap);
230 function IsInsideHouses(Pos: TPoint): Boolean;
231 procedure InitPlayers;
232 procedure CheckGameEnd;
233 procedure ClearBackground;
234 procedure DrawMenu;
235 procedure DrawStatsPartial(PlayerIndex, YDiff: Integer);
236 procedure DrawStats;
237 procedure DrawGamePrepare(Thread: TVirtualThread);
238 procedure DrawGame;
239 procedure DrawInformation;
240 procedure DrawInstructions;
241 procedure DrawSettings;
242 procedure DrawPlayerKeys;
243 procedure DrawPlayerKeysRedefine;
244 procedure DrawNewRound;
245 procedure DrawMap;
246 procedure SetState(AValue: TGameState);
247 procedure ShowMenuItem(Key, Text: string; X, Y: Integer; Canvas: TCanvas);
248 function TextOutWordWrap(Canvas: TCanvas; X, Y: Integer; Text: string; Width: Integer): Integer;
249 function GetMenuWidth: Integer;
250 public
251 ThreadErrorMessage: string;
252 Keyboard: TKeyboard;
253 World: TWorld;
254 PlayerPool: TPlayers;
255 Players: TPlayers;
256 DigMasks: TObjectList<TMatrixByte>;
257 Lock: TCriticalSection;
258 CurrentRound: Integer;
259 MaxRound: Integer;
260 AudioShot: TSound;
261 AudioExplode: TSound;
262 DrawDuration: TDatetime;
263 HomePage: string;
264 constructor Create(AOwner: TComponent); override;
265 destructor Destroy; override;
266 procedure ResizePlayerFrames;
267 procedure Tick;
268 procedure Redraw;
269 procedure Draw;
270 procedure DrawThread(Thread: TVirtualThread);
271 procedure NewGame;
272 procedure NewRound;
273 procedure KeyUp(Key: Word);
274 procedure KeyDown(Key: Word);
275 procedure LoadFromRegistry(Context: TRegistryContext);
276 procedure SaveToRegistry(Context: TRegistryContext);
277 procedure InitPlayerPool;
278 procedure Translate;
279 property Bitmap: TBitmap read FBitmap write SetBitmap;
280 property Active: Boolean read FActive write SetActive;
281 property State: TGameState read FState write SetState;
282 property OnGameEnd: TNotifyEvent read FOnGameEnd write FOnGameEnd;
283 property OnClose: TNotifyEvent read FOnClose write FOnClose;
284 end;
285
286const
287 DirectionToDelta: array[0..7] of TPoint =
288 ((X: 0; Y: -1), (X: 1; Y: -1), (X: 1; Y: 0), (X: 1; Y: 1),
289 (X: 0; Y: 1), (X: -1; Y: 1), (X: -1; Y: 0), (X: -1; Y: -1));
290
291
292implementation
293
294uses
295 Platform, FastPixel, UITypes2;
296
297resourcestring
298 SRound = 'Round';
299 SPressEsc = 'Press ESC to continue';
300 SStartGame = 'Start game';
301 SInstructions = 'Instructions';
302 SInstructionsDetails = 'Tunneler is a two player game. The objective of the game is to be the first to win three rounds. ' +
303 'A round continues until one tank blows up (from being shot or simply running out of energy).';
304 SInstructionsDetails2 = 'The display: Each player has a view screen and an instrument panel with two meters. ' +
305 'The view screen shows your surroundings from above (about 1% of the entire "worlds"). ' +
306 'The two meters indicate the player''s energy and shield condition.';
307 SInstructionsDetails3 = 'The Game: Players can move in one of 8 directions. ' +
308 'Movement in tunnels is three times as fast as normal digging. ' +
309 'Fast digging can be accomplished by firing the tank''s cannon while moving. ' +
310 'Various actions use up different amounts of energy : moving costs some energy, digging costs more, and shotting costs the most. ' +
311 'Shields are damaged when hit by the other player''s cannon. ' +
312 'Players can refuel at either base but can repair their shields only at their own bases.';
313 SInstructionsDetails4 = 'Controls: \n ' +
314 'Blue: W - up, A - left, X - down, D - right, Control - shoot \n ' +
315 'Green: arrows key, Enter - shoot';
316 SInformation = 'Information';
317 SInformationDetails = 'Tunneler is written from scratch in Lazarus/FPC. ' +
318 'It is designed to be as similar as the original MS-DOS game (https://tunneler.org/) but also with some modern extensions. ' +
319 'The program uses standard canvas drawing with combination of low resolution fast pixel matrix. ' +
320 'It supports High DPI scaling. It supports up to eight players.';
321 SInformationDetails2 = 'This is a public domain open source program: feel free to copy it for friends and study the source code. \n ' +
322 'Homepage: %s';
323 SSettings = 'Settings';
324 SWorldReady = 'World ready';
325 SExit = 'Exit';
326 SStatistics = 'Statistics';
327 SShotsFired = 'Shots fired';
328 SShotsHit = 'Shots hit';
329 SPercentHit = 'Percent hit';
330 SMetersDug = 'Meters dug';
331 SMetersTravelled = 'Meters travelled';
332 SWinnerIs = 'The winner is';
333 SGreen = 'Green';
334 SBlue = 'Blue';
335 SRed = 'Red';
336 SPink = 'Pink';
337 SCyan = 'Cyan';
338 SYellow = 'Yellow';
339 SOrange = 'Orange';
340 SGray = 'Gray';
341 SMorePlayers = 'More players';
342 SLessPlayers = 'Less players';
343 SPlayersKeys = 'Players keys';
344 SDefinePlayerKeys = 'Define player keys';
345 SBack = 'Back';
346 SPlayersCount = 'Players count';
347 SLeft = 'Left';
348 SUp = 'Up';
349 SRight = 'Right';
350 SDown = 'Down';
351 SShoot = 'Shoot';
352 SSet = 'Set';
353 SDone = 'Done';
354
355{ TBullets }
356
357procedure TBullets.HideAll;
358var
359 I: Integer;
360begin
361 for I := 0 to Count - 1 do
362 with Items[I] do begin
363 Player.Engine.World.Surface.ItemsXY[Trunc(Position.X), Trunc(Position.Y)] := Byte(miSpace);
364 Player.Engine.World.Surface.ItemsXY[Trunc(PositionTail.X), Trunc(PositionTail.Y)] := Byte(miSpace);
365 end;
366end;
367
368{ TTanks }
369
370procedure TTanks.Assign(Source: TTanks);
371var
372 I: Integer;
373begin
374 Clear;
375 Count := Source.Count;
376 for I := 0 to Count - 1 do begin
377 Items[I] := TTank.Create;
378 Items[I].Assign(Source.Items[I]);
379 end;
380end;
381
382{ TPlayers }
383
384function TPlayers.AddNew: TPlayer;
385begin
386 Result := TPlayer.Create;
387 Add(Result);
388end;
389
390procedure TPlayers.Assign(Players: TPlayers);
391var
392 I: Integer;
393begin
394 while Count < Players.Count do Add(TPlayer.Create);
395 while Count > Players.Count do Delete(Count - 1);
396 for I := 0 to Count - 1 do
397 Items[I].Assign(Players[I]);
398end;
399
400procedure TPlayers.LoadFromRegistry(Context: TRegistryContext);
401var
402 Player: TPlayer;
403begin
404 for Player in Self do begin
405 Player.LoadFromRegistry(TRegistryContext.Create(Context.RootKey, Context.Key + '\' + IntToStr(Player.Id)));
406 end;
407end;
408
409procedure TPlayers.SaveToRegistry(Context: TRegistryContext);
410var
411 Player: TPlayer;
412begin
413 for Player in Self do begin
414 Player.SaveToRegistry(TRegistryContext.Create(Context.RootKey, Context.Key + '\' + IntToStr(Player.Id)));
415 end;
416end;
417
418function TPlayers.GetAliveCount: Integer;
419var
420 I: Integer;
421begin
422 Result := 0;
423 for I := 0 to Count - 1 do
424 with Items[I] do
425 if not Exploded then Inc(Result);
426end;
427
428function TPlayers.GetWinner: TPlayer;
429var
430 I: Integer;
431 TopScore: Integer;
432begin
433 Result := nil;
434 TopScore := 0;
435 for I := 0 to Count - 1 do
436 with Items[I] do
437 if Enabled and (Score > TopScore) then begin
438 TopScore := Score;
439 Result := Items[I];
440 end;
441end;
442
443function TPlayers.GetEnabledCount: Integer;
444var
445 I: Integer;
446begin
447 Result := 0;
448 for I := 0 to Count - 1 do begin
449 if Items[I].Enabled then Inc(Result);
450 end;
451end;
452
453procedure TPlayers.EnableMore;
454var
455 I: Integer;
456begin
457 for I := 0 to Count - 1 do begin
458 if not Items[I].Enabled then begin
459 Items[I].Enabled := True;
460 Break;
461 end;
462 end;
463end;
464
465procedure TPlayers.EnableLess;
466var
467 I: Integer;
468begin
469 if GetEnabledCount > 2 then
470 for I := Count - 1 downto 0 do begin
471 if Items[I].Enabled then begin
472 Items[I].Enabled := False;
473 Break;
474 end;
475 end;
476end;
477
478procedure TPlayers.ClearKey(Key: Integer);
479var
480 I: Integer;
481begin
482 for I := 0 to Count - 1 do
483 with Items[I].Keys do begin
484 if Left = Key then Left := 0;
485 if Up = Key then Up := 0;
486 if Right = Key then Right := 0;
487 if Down = Key then Down := 0;
488 if Shoot = Key then Shoot := 0;
489 end;
490end;
491
492{ TSystemThread }
493
494procedure TSystemThread.Execute;
495begin
496 try
497 repeat
498 Engine.Tick;
499 Sleep(10);
500 until Terminated;
501 except
502 on E: Exception do
503 Engine.ThreadErrorMessage := E.Message;
504 end;
505end;
506
507{ TDrawThread }
508
509procedure TDrawThread.DrawSync;
510begin
511 with Engine do
512 if Assigned(Bitmap) then begin
513 Lock.Acquire;
514 Bitmap.BeginUpdate(True);
515 try
516 Draw;
517 finally
518 Bitmap.EndUpdate;
519 Lock.Release;
520 end;
521 end;
522end;
523
524procedure TDrawThread.Execute;
525begin
526 try
527 repeat
528 Engine.DrawThread(Self);
529 if not Terminated then Synchronize(DrawSync);
530 Sleep(50);
531 until Terminated;
532 except
533 on E: Exception do
534 Engine.ThreadErrorMessage := E.Message;
535 end;
536end;
537
538{ TKeyboard }
539
540procedure TKeyboard.ClearAll;
541var
542 I: Integer;
543begin
544 for I := 0 to High(KeyState) do
545 KeyState[I] := False;
546end;
547
548{ TBullet }
549
550constructor TBullet.Create;
551begin
552 MaxDistance := -1;
553 Distance := 0;
554 CanKill := True;
555end;
556
557{ TTank }
558
559procedure TTank.Assign(Source: TTank);
560begin
561 Image.Assign(Source.Image);
562 Mask.Assign(Source.Mask);
563end;
564
565constructor TTank.Create;
566begin
567 Mask := TMatrixByte.Create;
568 Image := TMatrixByte.Create;
569end;
570
571destructor TTank.Destroy;
572begin
573 FreeAndNil(Mask);
574 FreeAndNil(Image);
575 inherited;
576end;
577
578{ TPlayer }
579
580procedure TPlayer.Control;
581var
582 Delta: TPoint;
583 NewBullet: TBullet;
584begin
585 if Exploded then Exit;
586
587 Delta.X := 0;
588 Delta.Y := 0;
589 if Engine.KeyBoard.KeyState[Ord(Keys.Down)] then Delta.Y := Delta.Y + 1;
590 if Engine.KeyBoard.KeyState[Ord(Keys.Up)] then Delta.Y := Delta.Y - 1;
591 if Engine.KeyBoard.KeyState[Ord(Keys.Right)] then Delta.X := Delta.X + 1;
592 if Engine.KeyBoard.KeyState[Ord(Keys.Left)] then Delta.X := Delta.X - 1;
593
594 NewDirection := Direction;
595 if (Delta.X <> 0) or (Delta.Y <> 0) then begin
596 if (Delta.X = 0) and (Delta.Y = -1) then NewDirection := 0
597 else if (Delta.X = 1) and (Delta.Y = -1) then NewDirection := 1
598 else if (Delta.X = 1) and (Delta.Y = 0) then NewDirection := 2
599 else if (Delta.X = 1) and (Delta.Y = 1) then NewDirection := 3
600 else if (Delta.X = 0) and (Delta.Y = 1) then NewDirection := 4
601 else if (Delta.X = -1) and (Delta.Y = 1) then NewDirection := 5
602 else if (Delta.X = -1) and (Delta.Y = 0) then NewDirection := 6
603 else if (Delta.X = -1) and (Delta.Y = -1) then NewDirection := 7;
604
605 if NewDirection = Direction then begin
606 NewPosition := Point(Position.X + Delta.X, Position.Y + Delta.Y);
607 CheckNewDestination;
608 end else begin
609 NewPosition := Position;
610 if not CheckNewDestination then begin
611 // if direction is changed then try to check movement plus one step
612 // to prevent tank block
613 NewPosition := Point(Position.X + Delta.X, Position.Y + Delta.Y);
614 CheckNewDestination;
615 end;
616 end;
617 end;
618
619 if Engine.KeyBoard.KeyState[Ord(Keys.Shoot)] then
620 if (Bullets.Count < MaxBulletCount) and
621 ((Now - LastShootTime) > ShootDelay * OneSecond) then begin
622 Inc(ShotsCount);
623 NewBullet := TBullet.Create;
624 NewBullet.Player := Self;
625 NewBullet.Position.X := Position.X + DirectionToDelta[Direction].X * 3;
626 NewBullet.Position.Y := Position.Y + DirectionToDelta[Direction].Y * 3;
627 NewBullet.Direction.X := DirectionToDelta[Direction].X;
628 NewBullet.Direction.Y := DirectionToDelta[Direction].Y;
629 NewBullet.StopByDirt := True;
630 Bullets.Add(NewBullet);
631 LastShootTime := Now;
632
633 Energy := Energy - EnergyDecreaseShoot;
634 if Energy < 0 then Energy := 0;
635
636 // Do not imedietelly clear dirt by bullet in front of tank
637 //with Engine, World do
638 //if TMatter(Matter[Surface.Items[Position]]).Diggable ;
639 Engine.AudioShot.Play;
640 end;
641end;
642
643function TPlayer.CheckNewDestination: Boolean;
644var
645 ColisionState: TColisionState;
646begin
647 Result := False;
648 HideTank;
649 ColisionState := CheckColision;
650 if not ColisionState.Blocking then begin
651 if ColisionState.Diggable then begin
652 if (Engine.KeyBoard.KeyState[Ord(Keys.Shoot)] and
653 ((Now - LastDigTime) > ShootDigDelay * OneSecond)) or
654 (not Engine.KeyBoard.KeyState[Ord(Keys.Shoot)] and
655 ((Now - LastDigTime) > DigDelay * OneSecond)) then begin
656 Dig := not Dig;
657 with Engine, World do
658 Surface.Merge(Surface.CreateIndex(
659 Position.X - TMatrixByte(DigMasks[Direction]).Count.X div 2,
660 Position.Y - TMatrixByte(DigMasks[Direction]).Count.Y div 2),
661 TMatrixByte(DigMasks[Direction]), DigProc);
662 Energy := Energy - EnergyDecreaseDig;
663 if Energy < 0 then Energy := 0;
664 Engine.Redraw;
665 LastDigTime := Now;
666 Direction := NewDirection;
667 Result := True;
668 Inc(MetersDug);
669 end;
670 end else begin
671 if ((Now - LastMoveTime) > MoveDelay * OneSecond) then begin
672 Position := NewPosition;
673 Direction := NewDirection;
674 Result := True;
675 Engine.Redraw;
676 LastMoveTime := Now;
677 Energy := Energy - EnergyDecreaseMove;
678 Inc(MetersTravelled);
679 end;
680 end;
681 end;
682 ShowTank;
683end;
684
685procedure TPlayer.Tick;
686var
687 I: Integer;
688 P: Integer;
689 Pos: TPoint;
690begin
691 // Check energy
692 if not ExplosionPending and not Exploded then begin
693 if not Engine.IsInsideHouses(Position) then begin
694 Energy := Energy - EnergyDecreaseOutside;
695 end else begin
696 if not Exploded then
697 Energy := Energy + EnergyIncreaseHome;
698 if Energy > 1 then Energy := 1;
699 end;
700 if Energy <= 0 then begin
701 Energy := 0;
702 Explosion(Position, ExplosionRange);
703 Exploded := True;
704 ExplosionPending := True;
705 ExplosionTime := Now;
706 Engine.AudioExplode.Play;
707 end;
708 end;
709 if ExplosionPending and (SecondOf(Now - ExplosionTime) > ExplosionDelay) then begin
710 ExplosionPending := False;
711 Engine.CheckGameEnd;
712 end;
713 if LastEnergy <> Energy then begin
714 LastEnergy := Energy;
715 Engine.Redraw;
716 end;
717
718 // Check shield
719 if House.IsInside(Position) then begin
720 if not Exploded then
721 Shield := Shield + ShieldIncreaseHome;
722 if Shield > 1 then Shield := 1;
723 end;
724 if LastShield <> Shield then begin
725 LastShield := Shield;
726 Engine.Redraw;
727 end;
728 if (Shield <= 0) and not Exploded then begin
729 Shield := 0;
730 Explosion(Position, ExplosionRange);
731 Exploded := True;
732 Engine.AudioExplode.Play;
733 end;
734
735 // Bullet movement
736 for I := Bullets.Count - 1 downto 0 do
737 with Bullets[I], Engine.World.Surface do begin
738 Pos := Point(Trunc(Position.X), Trunc(Position.Y));
739 if (ItemsXY[LastPos.X, LastPos.Y] = Byte(miBullet1)) or
740 (ItemsXY[LastPos.X, LastPos.Y] = Byte(miBullet2)) then
741 ItemsXY[LastPos.X, LastPos.Y] := Byte(miSpace);
742 LastPos := Pos;
743 P := Trunc(Direction.Y);
744
745 PositionTail := Position;
746 Position.X := Position.X + Direction.X;
747 Position.Y := Position.Y + Direction.Y;
748 Distance := Distance + Sqrt(Sqr(Direction.X) + Sqr(Direction.Y));
749 //ShowMessage(FloatToStr(Distance));
750 if (Distance > MaxDistance) and (MaxDistance >= 0) then begin
751 Bullets.Delete(I);
752 Engine.Redraw;
753 Continue;
754 end;
755
756 Pos := Point(Trunc(Position.X), Trunc(Position.Y));
757
758 if (ItemsXY[Pos.X, Pos.Y] <> Byte(miSpace)) and
759 (ItemsXY[Pos.X, Pos.Y] <> Byte(miBullet1)) and
760 (ItemsXY[Pos.X, Pos.Y] <> Byte(miBullet2)) then begin
761 if (ItemsXY[Pos.X, Pos.Y] = Byte(miDirt1)) or
762 (ItemsXY[Pos.X, Pos.Y] = Byte(miDirt2)) then begin
763 //ItemsXY[Pos.X, Pos.Y] := Byte(miSpace);
764 if StopByDirt then begin
765 Explosion(LastPos, BulletExplosionRange);
766 Bullets.Delete(I);
767 Engine.Redraw;
768 Continue;
769 end;
770 end else begin
771 for P := 0 to Engine.Players.Count - 1 do
772 with Engine.Players[P] do
773 if (Self.Id <> P) and
774 (Engine.World.Matters[ItemsXY[Pos.X, Pos.Y]].Kind = mkTankBody) and
775 (Engine.World.Matters[ItemsXY[Pos.X, Pos.Y]].Player = P) and CanKill then begin
776 Shield := Shield - ShieldDecreaseHit;
777 Inc(Self.ShotsHit);
778 end;
779 if StopByDirt then Explosion(LastPos, BulletExplosionRange);
780 Bullets.Delete(I);
781 Engine.Redraw;
782 Continue;
783 end;
784 end;
785
786 // Max position limit checking
787 with Engine.World.Surface do
788 if (Pos.X >= Count.X) or (Pos.X < 0) or
789 (Pos.Y >= Count.Y) or (Pos.Y < 0) then begin
790 Bullets.Delete(I);
791 Engine.Redraw;
792 Continue;
793 end;
794 ItemsXY[Pos.X, Pos.Y] := Byte(miBullet1);
795 ItemsXY[LastPos.X, LastPos.Y] := Byte(miBullet2);
796 Engine.Redraw;
797 end;
798
799 if (Engine.State = gsGame) and not Exploded then ShowTank;
800end;
801
802procedure TPlayer.Paint;
803var
804 X, Y: Integer;
805 XX, YY: Integer;
806 I: Integer;
807 B: TColor;
808begin
809 with Engine.FBitmapLower do begin
810 Fill(CreateIndex(ScreenFrame.Left, ScreenFrame.Top),
811 CreateIndex(ScreenFrame.Width, ScreenFrame.Height),
812 Engine.World.Matters[Integer(miRock)].Color);
813
814 with Engine.World do
815 for Y := ScreenFrame.Top to ScreenFrame.Bottom - 1 do
816 for X := ScreenFrame.Left to ScreenFrame.Right - 1 do begin
817 XX := X - ScreenFrame.Left - ((ScreenFrame.Right - ScreenFrame.Left) div 2) + Position.X;
818 YY := Y - ScreenFrame.Top - ((ScreenFrame.Bottom - ScreenFrame.Top) div 2) + Position.Y;
819 if (YY >= 0) and (YY < Surface.Count.Y) and
820 (XX >= 0) and (XX < Surface.Count.X) then begin
821 B := Engine.World.Matters[Surface.ItemsXY[XX, YY]].Color;
822 ItemsXY[X, Y] := B;
823 end;
824 end;
825
826 // Energy bar
827 for I := 1 to ScreenFrame.Width - 2 do
828 if Energy < I / (ScreenFrame.Width - 2) then
829 ItemsXY[ScreenFrame.Left + I, ScreenFrame.Bottom - 2] := clBlack
830 else ItemsXY[ScreenFrame.Left + I, ScreenFrame.Bottom - 2] := clYellow;
831
832 // Shield bar
833 for I := 1 to ScreenFrame.Width - 2 do
834 if Shield < I / (ScreenFrame.Width - 2) then
835 ItemsXY[ScreenFrame.Left + I, ScreenFrame.Bottom - 1] := clBlack
836 else ItemsXY[ScreenFrame.Left + I, ScreenFrame.Bottom - 1] := clAqua;
837 end;
838end;
839
840procedure TPlayer.PlaceHouse;
841var
842 X, Y: Integer;
843 Matter: Byte;
844begin
845 House.AsTRect := Rect(Position.X - PlayerHouseSize div 2, Position.Y - PlayerHouseSize div 2,
846 Position.X + PlayerHouseSize div 2, Position.Y + PlayerHouseSize div 2);
847 for Y := 0 to PlayerHouseSize - 1 do
848 for X := 0 to PlayerHouseSize - 1 do begin
849 if ((Y = 0) or (Y = (PlayerHouseSize - 1)) or (X = 0) or (X = (PlayerHouseSize - 1))) and
850 not (((Y = 0) or (Y = (PlayerHouseSize - 1))) and
851 (X >= ((PlayerHouseSize - PlayerHouseDoorSize) div 2)) and
852 (X <= ((PlayerHouseSize - 1 + PlayerHouseDoorSize) div 2)))
853 then Matter := Byte(miPlayer1Home) + Id * 4
854 else Matter := Byte(miSpace);
855 Engine.World.Surface.ItemsXY[House.Left + X,
856 House.Top + Y] := Matter;
857 end;
858end;
859
860function TPlayer.CheckColision: TColisionState;
861var
862 X, Y: Integer;
863 XX, YY: Integer;
864begin
865 Result.Diggable := False;
866 Result.Blocking := False;
867 with Engine, World, TTank(Tanks[NewDirection]) do
868 for Y := 0 to Image.Count.Y - 1 do
869 for X := 0 to Image.Count.X - 1 do begin
870 XX := X + NewPosition.X - Image.Count.X div 2;
871 YY := Y + NewPosition.Y - Image.Count.Y div 2;
872 if Image.ItemsXY[X, Y] > 0 then begin
873 if TMatter(Matters[Surface.ItemsXY[XX, YY]]).Blocking then
874 Result.Blocking := True;
875 if TMatter(Matters[Surface.ItemsXY[XX, YY]]).Diggable then
876 Result.Diggable := True;
877 end;
878 end;
879end;
880
881function TPlayer.ShowTankProc(Item1, Item2: Byte): Byte;
882begin
883 if Item2 > 0 then Result := Item2
884 else Result := Item1;
885end;
886
887procedure TPlayer.SetExploded(const AValue: Boolean);
888begin
889 if FExploded = AValue then Exit;
890 FExploded := AValue;
891 if FExploded then begin
892 HideTank;
893 Energy := 0;
894 Shield := 0;
895 end else ShowTank;
896end;
897
898procedure TPlayer.ShowTank;
899begin
900 with Engine.World do begin
901 Surface.Merge(Surface.CreateIndex(
902 Position.X - Tanks[Direction].Image.Count.X div 2,
903 Position.Y - Tanks[Direction].Image.Count.Y div 2),
904 Tanks[Direction].Image, ShowTankProc);
905 end;
906end;
907
908function TPlayer.HideTankProc(Item1, Item2: Byte): Byte;
909begin
910 if Item2 > 0 then Result := 0 else Result := Item1;
911end;
912
913function TPlayer.DigProc(Item1, Item2: Byte): Byte;
914begin
915 if ((Item1 = Integer(miDirt1)) or (Item1 = Integer(miDirt2))) and (Item2 = 1) then
916 Result := Integer(miSpace) else Result := Item1;
917end;
918
919procedure TPlayer.ResetTank;
920begin
921 HideTank;
922 Position := StartPosition;
923 ExplosionPending := False;
924 Bullets.HideAll;
925 Bullets.Clear;
926 Energy := 1;
927 Shield := 1;
928 Direction := 0;
929 ShowTank;
930 Exploded := False;
931end;
932
933procedure TPlayer.Init;
934begin
935 with Engine do
936 StartPosition := Point(Round(World.Surface.Count.X * 0.2) + Random(Round(World.Surface.Count.X * 0.6)),
937 Round(World.Surface.Count.Y * 0.2) + Random(Round(World.Surface.Count.Y * 0.6)));
938 Position := StartPosition;
939 PlaceHouse;
940 ShotsCount := 0;
941 ShotsHit := 0;
942 MetersDug := 0;
943 MetersTravelled := 0;
944end;
945
946procedure TPlayer.Explosion(Position: TPoint; Distance: Integer);
947var
948 NewBullet: TBullet;
949 I: Integer;
950 Speed: Real;
951 Angle: Real;
952begin
953 if not Exploded then begin
954 for I := 0 to Distance * 2 - 1 do begin
955 NewBullet := TBullet.Create;
956 NewBullet.Player := Self;
957 Speed := ExplosionBulletMinSpeed + (ExplosionBulletMaxSpeed - ExplosionBulletMinSpeed) * Random;
958 Angle := Random * 2 * Pi;
959 NewBullet.Direction.X := Sin(Angle) * Speed;
960 NewBullet.Direction.Y := Cos(Angle) * Speed;
961 NewBullet.Position.X := Position.X; // + NewBullet.Direction.X * 3;
962 NewBullet.Position.Y := Position.Y; // + NewBullet.Direction.Y * 3;
963 NewBullet.MaxDistance := Random(Distance);
964 NewBullet.CanKill := False;
965 Bullets.Add(NewBullet);
966 end;
967 end;
968end;
969
970procedure TPlayer.HideTank;
971begin
972 with Engine.World do begin
973 Surface.Merge(Surface.CreateIndex(
974 Position.X - Tanks[Direction].Image.Count.X div 2,
975 Position.Y - Tanks[Direction].Image.Count.Y div 2),
976 Tanks[Direction].Image, HideTankProc);
977 end;
978end;
979
980procedure TPlayer.InitTanks;
981var
982 NewTank: TTank;
983 I: Integer;
984 X, Y: Integer;
985begin
986 Tanks.Clear;
987
988 NewTank := TTank.Create;
989 with NewTank do begin
990 Image.Count := Image.CreateIndex(7, 7);
991 for I := 0 to 3 do
992 Image[3, I] := Byte(miPlayer1Cannon) + Id * 4;
993 for I := 1 to 6 do begin
994 Image[1, I] := Byte(miPlayer1TankBody) + Id * 4;
995 Image[5, I] := Byte(miPlayer1TankBody) + Id * 4;
996 end;
997 for I := 2 to 5 do begin
998 Image[2, I] := Byte(miPlayer1TankBody2) + Id * 4;
999 Image[4, I] := Byte(miPlayer1TankBody2) + Id * 4;
1000 end;
1001 Image[3, 4] := Byte(miPlayer1TankBody2) + Id * 4;
1002 Image[3, 5] := Byte(miPlayer1TankBody2) + Id * 4;
1003 end;
1004 Tanks.Add(NewTank);
1005
1006 NewTank := TTank.Create;
1007 with NewTank do begin
1008 Image.Count := Image.CreateIndex(7, 7);
1009 for I := 0 to 2 do
1010 Image[3 + I, 3 - I] := Byte(miPlayer1Cannon) + Id * 4;
1011 for I := 0 to 3 do begin
1012 Image[I, 3 - I] := Byte(miPlayer1TankBody) + Id * 4;
1013 Image[3 + I, 6 - I] := Byte(miPlayer1TankBody) + Id * 4;
1014 end;
1015 for I := 0 to 2 do begin
1016 Image[1 + I, 3 - I] := Byte(miPlayer1TankBody2) + Id * 4;
1017 Image[3 + I, 5 - I] := Byte(miPlayer1TankBody2) + Id * 4;
1018 end;
1019 Image[3, 2] := Byte(miPlayer1TankBody2) + Id * 4;
1020 Image[2, 3] := Byte(miPlayer1TankBody2) + Id * 4;
1021 Image[2, 4] := Byte(miPlayer1TankBody2) + Id * 4;
1022 Image[3, 4] := Byte(miPlayer1TankBody2) + Id * 4;
1023 Image[4, 3] := Byte(miPlayer1TankBody2) + Id * 4;
1024 end;
1025 Tanks.Add(NewTank);
1026
1027 NewTank := TTank.Create;
1028 NewTank.Image.Assign(Tanks[0].Image);
1029 NewTank.Image.Reverse;
1030 NewTank.Image.ReverseHorizontal;
1031 Tanks.Add(NewTank);
1032
1033 NewTank := TTank.Create;
1034 NewTank.Image.Assign(Tanks[1].Image);
1035 NewTank.Image.ReverseVertical;
1036 Tanks.Add(NewTank);
1037
1038 NewTank := TTank.Create;
1039 NewTank.Image.Assign(Tanks[0].Image);
1040 NewTank.Image.ReverseVertical;
1041 Tanks.Add(NewTank);
1042
1043 NewTank := TTank.Create;
1044 NewTank.Image.Assign(Tanks[1].Image);
1045 NewTank.Image.ReverseVertical;
1046 NewTank.Image.ReverseHorizontal;
1047 Tanks.Add(NewTank);
1048
1049 NewTank := TTank.Create;
1050 NewTank.Image.Assign(Tanks[0].Image);
1051 NewTank.Image.Reverse;
1052 Tanks.Add(NewTank);
1053
1054 NewTank := TTank.Create;
1055 NewTank.Image.Assign(Tanks[1].Image);
1056 NewTank.Image.ReverseHorizontal;
1057 Tanks.Add(NewTank);
1058
1059 for I := 0 to Tanks.Count - 1 do
1060 with Tanks[I] do begin
1061 Mask.Assign(Image);
1062 for Y := 0 to Mask.Count.Y - 1 do
1063 for X := 0 to Mask.Count.X - 1 do
1064 if Mask.ItemsXY[X, Y] > 0 then Mask.ItemsXY[X, Y] := 1;
1065 end;
1066end;
1067
1068procedure TPlayer.ClearKeys;
1069begin
1070 Keys.Left := 0;
1071 Keys.Up := 0;
1072 Keys.Right := 0;
1073 Keys.Down := 0;
1074 Keys.Shoot := 0;
1075end;
1076
1077constructor TPlayer.Create;
1078begin
1079 Tanks := TTanks.Create;
1080 Bullets := TBullets.Create;
1081 House := TRectangle.Create;
1082 ScreenFrame := TRectangle.Create;
1083end;
1084
1085destructor TPlayer.Destroy;
1086begin
1087 FreeAndNil(ScreenFrame);
1088 FreeAndNil(House);
1089 FreeAndNil(Bullets);
1090 FreeAndNil(Tanks);
1091 inherited;
1092end;
1093
1094procedure TPlayer.Assign(Source: TPlayer);
1095begin
1096 Engine := Source.Engine;
1097 Id := Source.Id;
1098 Keys := Source.Keys;
1099 Color1 := Source.Color1;
1100 Color2 := Source.Color2;
1101 Energy := Source.Energy;
1102 Shield := Source.Shield;
1103 Name := Source.Name;
1104 Enabled := Source.Enabled;
1105 Position := Source.Position;
1106 StartPosition := Source.StartPosition;
1107 Score := Source.Score;
1108 ShotsCount := Source.ShotsCount;
1109 ShotsHit := Source.ShotsHit;
1110 MetersDug := Source.MetersDug;
1111 MetersTravelled := Source.MetersTravelled;
1112 FExploded := Source.FExploded;
1113 Tanks.Assign(Source.Tanks);
1114end;
1115
1116procedure TPlayer.LoadFromRegistry(Context: TRegistryContext);
1117begin
1118 with TRegistryEx.Create do
1119 try
1120 CurrentContext := Context;
1121 Name := ReadStringWithDefault('Name', Name);
1122 Color1 := ReadIntegerWithDefault('Color1', Color1);
1123 Color2 := ReadIntegerWithDefault('Color2', Color2);
1124 Enabled := ReadBoolWithDefault('Enabled', Enabled);
1125 Keys.Left := ReadIntegerWithDefault('KeysLeft', Keys.Left);
1126 Keys.Right := ReadIntegerWithDefault('KeyRight', Keys.Right);
1127 Keys.Down := ReadIntegerWithDefault('KeyDown', Keys.Down);
1128 Keys.Up := ReadIntegerWithDefault('KeyUp', Keys.Up);
1129 Keys.Shoot := ReadIntegerWithDefault('KeyShoot', Keys.Shoot);
1130 finally
1131 Free;
1132 end;
1133end;
1134
1135procedure TPlayer.SaveToRegistry(Context: TRegistryContext);
1136begin
1137 with TRegistryEx.Create do
1138 try
1139 CurrentContext := Context;
1140 WriteString('Name', Name);
1141 WriteInteger('Color1', Color1);
1142 WriteInteger('Color2', Color2);
1143 WriteBool('Enabled', Enabled);
1144 WriteInteger('KeysLeft', Keys.Left);
1145 WriteInteger('KeyRight', Keys.Right);
1146 WriteInteger('KeyDown', Keys.Down);
1147 WriteInteger('KeyUp', Keys.Up);
1148 WriteInteger('KeyShoot', Keys.Shoot);
1149 finally
1150 Free;
1151 end;
1152end;
1153
1154{ TEngine }
1155
1156procedure TEngine.SetActive(const AValue: Boolean);
1157begin
1158 if FActive = AValue then Exit;
1159 FActive := AValue;
1160 if AValue then begin
1161 FDrawThread := TDrawThread.Create(True);
1162 FDrawThread.Engine := Self;
1163 FDrawThread.FreeOnTerminate := False;
1164 FDrawThread.Name := 'Draw';
1165 FDrawThread.Start;
1166 FSystemThread := TSystemThread.Create(True);
1167 FSystemThread.Engine := Self;
1168 FSystemThread.FreeOnTerminate := False;
1169 FSystemThread.Name := 'Engine';
1170 FSystemThread.Start;
1171 end else begin
1172 FDrawThread.Terminate;
1173 Application.ProcessMessages;
1174 FreeAndNil(FDrawThread);
1175 FreeAndNil(FSystemThread);
1176 end;
1177end;
1178
1179procedure TEngine.SetBitmap(const AValue: TBitmap);
1180begin
1181 FBitmap := AValue;
1182 ResizePlayerFrames;
1183end;
1184
1185procedure TEngine.Redraw;
1186begin
1187 FRedrawPending := True;
1188end;
1189
1190function TEngine.IsInsideHouses(Pos: TPoint): Boolean;
1191var
1192 I: Integer;
1193begin
1194 Result := False;
1195 for I := 0 to Players.Count - 1 do
1196 if Players[I].House.IsInside(Pos) then begin
1197 Result := True;
1198 end;
1199end;
1200
1201procedure TEngine.DrawGame;
1202var
1203 X, Y: Integer;
1204 PixelX, PixelY: Integer;
1205 SubPixelPtr: PInteger;
1206 SubPixelRowPtr: PInteger;
1207 SubPixelSizeX: Integer;
1208 SubPixelSizeY: Integer;
1209 PixelPtr: PInteger;
1210 PixelRowPtr: PInteger;
1211 BytePerPixel: Integer;
1212 BytePerRow: Integer;
1213 RawImage: TRawImage;
1214 Color: Int64;
1215 Shift: TPoint;
1216 XDiv, XMod, XAcc: Integer;
1217 YDiv, YMod, YAcc: Integer;
1218 Ratio: Real;
1219 TargetHeight: Integer;
1220 TargetWidth: Integer;
1221 BgColor: Cardinal;
1222begin
1223 // TODO: To be able to draw into Bitmap not just through Canvas
1224 Bitmap.EndUpdate;
1225 Bitmap.BeginUpdate;
1226
1227 {$IFDEF WINDOWS}
1228 Bitmap.PixelFormat := pf32bit;
1229 {$ENDIF}
1230 RawImage := Bitmap.RawImage;
1231 BytePerPixel := RawImage.Description.BitsPerPixel div 8;
1232 BytePerRow := RawImage.Description.BytesPerLine;
1233 if FClearBackground then begin
1234 BgColor := World.Matters[Integer(miBorder)].Color;
1235 BgColor := SwapBRComponent(BgColor);
1236 FillDWord(RawImage.Data^, Bitmap.Height * BytePerRow div 4, BgColor);
1237 FClearBackground := False;
1238 end;
1239
1240 if (FBitmap.Width / FBitmapLower.Width) < (FBitmap.Height / FBitmapLower.Height) then
1241 Ratio := FBitmap.Width / FBitmapLower.Width
1242 else Ratio := FBitmap.Height / FBitmapLower.Height;
1243
1244 // Preserve aspect ratio
1245 TargetWidth := Trunc(FBitmapLower.Width * Ratio);
1246 TargetHeight := Trunc(FBitmapLower.Height * Ratio);
1247
1248 Shift.X := Trunc((Bitmap.Width - TargetWidth) / 2);
1249 Shift.Y := Trunc((Bitmap.Height - TargetHeight) / 2);
1250
1251 XDiv := TargetWidth div FBitmapLower.Width;
1252 XMod := TargetWidth mod FBitmapLower.Width;
1253 YDiv := TargetHeight div FBitmapLower.Height;
1254 YMod := TargetHeight mod FBitmapLower.Height;
1255
1256 PixelRowPtr := PInteger(RawImage.Data + BytePerRow * Shift.Y);
1257 YAcc := FBitmapLower.Height div 2;
1258 for Y := 0 to FBitmapLower.Height - 1 do begin
1259 SubPixelSizeY := YDiv;
1260 Inc(YAcc, YMod);
1261 if YAcc >= FBitmapLower.Height then begin
1262 Dec(YAcc, FBitmapLower.Height);
1263 Inc(SubPixelSizeY);
1264 end;
1265
1266 PixelPtr := PixelRowPtr + Shift.X;
1267 XAcc := FBitmapLower.Width div 2;
1268 for X := 0 to FBitmapLower.Width - 1 do begin
1269 SubPixelSizeX := XDiv;
1270 Inc(XAcc, XMod);
1271 if XAcc >= FBitmapLower.Width then begin
1272 Dec(XAcc, FBitmapLower.Width);
1273 Inc(SubPixelSizeX);
1274 end;
1275 Color := FBitmapLower.Pixels[X, Y] and $ffffff;
1276
1277 Color := SwapBRComponent(Color);
1278
1279 // Draw large pixel
1280 SubPixelRowPtr := PixelPtr;
1281 for PixelY := 0 to SubPixelSizeY - 1 do begin
1282 SubPixelPtr := SubPixelRowPtr;
1283 for PixelX := 0 to SubPixelSizeX - 1 do begin
1284 SubPixelPtr^ := Color;
1285 Inc(PByte(SubPixelPtr), BytePerPixel);
1286 end;
1287 Inc(PByte(SubPixelRowPtr), BytePerRow);
1288 end;
1289 Inc(PByte(PixelPtr), BytePerPixel * SubPixelSizeX);
1290 end;
1291 Inc(PByte(PixelRowPtr), BytePerRow * SubPixelSizeY);
1292 end;
1293end;
1294
1295procedure TEngine.DrawInformation;
1296var
1297 Text: string;
1298 X: Integer;
1299 Y: Integer;
1300const
1301 LineHeight = 60;
1302begin
1303 with Bitmap.Canvas do begin
1304 ClearBackground;
1305
1306 X := Bitmap.Width div 2;
1307 Y := Bitmap.Height div 20;
1308
1309 Brush.Style := bsClear;
1310 Pen.Style := psSolid;
1311 Pen.Color := clWhite;
1312 Font.Color := clGreen;
1313 Font.Size := 30;
1314 Text := SInformation;
1315 TextOut(X - TextWidth(Text) div 2, Y, Text);
1316 Inc(Y, 2 * LineHeight);
1317
1318 X := 30;
1319
1320 Font.Color := clYellow;
1321 Font.Size := 20;
1322 Text := SInformationDetails;
1323 Inc(Y, LineHeight * TextOutWordWrap(Bitmap.Canvas, X, Y, Text, Bitmap.Width - 60));
1324 Inc(Y, LineHeight);
1325
1326 Text := Format(SInformationDetails2, [HomePage]);
1327 Inc(Y, LineHeight * TextOutWordWrap(Bitmap.Canvas, X, Y, Text, Bitmap.Width - 60));
1328 Inc(Y, LineHeight);
1329
1330 X := Bitmap.Width div 2;
1331
1332 Font.Color := clGreen;
1333 Font.Size := 30;
1334 Text := SPressEsc;
1335 TextOut(X - TextWidth(Text) div 2, Bitmap.Height div 10 * 9, Text);
1336 end;
1337end;
1338
1339procedure TEngine.DrawInstructions;
1340var
1341 Text: string;
1342 X: Integer;
1343 Y: Integer;
1344const
1345 LineHeight = 60;
1346begin
1347 with Bitmap.Canvas do begin
1348 ClearBackground;
1349
1350 X := Bitmap.Width div 2;
1351 Y := Bitmap.Height div 20;
1352
1353 Brush.Style := bsClear;
1354 Pen.Style := psSolid;
1355 Pen.Color := clWhite;
1356 Font.Color := clTuna;
1357 Font.Size := 30;
1358 Text := SInstructions;
1359 TextOut(X - TextWidth(Text) div 2, Y, Text);
1360 Inc(Y, 2 * LineHeight);
1361
1362 X := 30;
1363
1364 Font.Color := clTeal;
1365 Font.Size := 20;
1366 Text := SInstructionsDetails;
1367 Inc(Y, LineHeight * TextOutWordWrap(Bitmap.Canvas, X, Y, Text, Bitmap.Width - 60));
1368 Inc(Y, LineHeight);
1369
1370 Text := SInstructionsDetails2;
1371 Inc(Y, LineHeight * TextOutWordWrap(Bitmap.Canvas, X, Y, Text, Bitmap.Width - 60));
1372 Inc(Y, LineHeight);
1373
1374 Text := SInstructionsDetails3;
1375 Inc(Y, LineHeight * TextOutWordWrap(Bitmap.Canvas, X, Y, Text, Bitmap.Width - 60));
1376 Inc(Y, LineHeight);
1377
1378 Text := SInstructionsDetails4;
1379 Inc(Y, LineHeight * TextOutWordWrap(Bitmap.Canvas, X, Y, Text, Bitmap.Width - 60));
1380 Inc(Y, LineHeight);
1381
1382 X := Bitmap.Width div 2;
1383 Font.Color := clGreen;
1384 Font.Size := 30;
1385 Text := SPressEsc;
1386 TextOut(X - TextWidth(Text) div 2, Bitmap.Height div 10 * 9, Text);
1387 end;
1388end;
1389
1390procedure TEngine.DrawSettings;
1391var
1392 Text: string;
1393 MenuWidth: Integer;
1394begin
1395 with Bitmap.Canvas do begin
1396 ClearBackground;
1397
1398 MenuWidth := GetMenuWidth;
1399
1400 Brush.Style := bsClear;
1401 Pen.Style := psSolid;
1402 Pen.Color := clWhite;
1403 Font.Color := clTuna;
1404 Font.Size := 30;
1405 Text := SSettings;
1406 TextOut((MenuWidth - TextWidth(Text)) div 2, Bitmap.Height div 20, Text);
1407
1408 Pen.Color := clPurple;
1409 Pen.Width := 6;
1410 Frame((MenuWidth - 400) div 2, Bitmap.Height div 10 * 4 - 40,
1411 (MenuWidth + 500) div 2, Bitmap.Height div 10 * 4 + 200);
1412
1413 Font.Color := clPurple;
1414 Font.Size := 20;
1415
1416 ShowMenuItem('F1', SMorePlayers, MenuWidth div 2 - 180, Bitmap.Height div 10 * 4, Bitmap.Canvas);
1417 ShowMenuItem('F2', SLessPlayers, MenuWidth div 2 - 180, Bitmap.Height div 10 * 4 + 40, Bitmap.Canvas);
1418 ShowMenuItem('F3', SPlayersKeys, MenuWidth div 2 - 180, Bitmap.Height div 10 * 4 + 80, Bitmap.Canvas);
1419 ShowMenuItem('ESC', SBack, MenuWidth div 2 - 180, Bitmap.Height div 10 * 4 + 120, Bitmap.Canvas);
1420
1421 Font.Color := clDarkGreen;
1422 Font.Size := 20;
1423 Text := SPlayersCount + ': ' + IntToStr(PlayerPool.GetEnabledCount);
1424 TextOut((MenuWidth - TextWidth(Text)) div 2, Bitmap.Height div 10 * 8, Text);
1425 end;
1426end;
1427
1428procedure TEngine.DrawPlayerKeys;
1429var
1430 Text: string;
1431 MenuWidth: Integer;
1432 Y: Integer;
1433 I: Integer;
1434begin
1435 with Bitmap.Canvas do begin
1436 ClearBackground;
1437
1438 MenuWidth := GetMenuWidth;
1439
1440 Brush.Style := bsClear;
1441 Pen.Style := psSolid;
1442 Pen.Color := clWhite;
1443 Font.Color := clTuna;
1444 Font.Size := 30;
1445 Text := SPlayersKeys;
1446 TextOut((MenuWidth - TextWidth(Text)) div 2, Bitmap.Height div 20, Text);
1447
1448 Pen.Color := clPurple;
1449 Pen.Width := 6;
1450 Frame((MenuWidth - 400) div 2, Bitmap.Height div 10 * 4 - 40,
1451 (MenuWidth + 400) div 2, Bitmap.Height div 10 * 4 + 440);
1452
1453 Font.Color := clPurple;
1454 Font.Size := 20;
1455
1456 Y := Bitmap.Height div 10 * 4;
1457 for I := 0 to PlayerPool.GetEnabledCount - 1 do begin
1458 Font.Color := PlayerPool[I].Color1;
1459 ShowMenuItem('F' + IntToStr(I + 1), PlayerPool[I].Name, MenuWidth div 2 - 180, Y, Bitmap.Canvas);
1460 Y := Y + 40;
1461 end;
1462 Font.Color := clPurple;
1463 ShowMenuItem('ESC', SBack, MenuWidth div 2 - 180, Y, Bitmap.Canvas);
1464 end;
1465end;
1466
1467procedure TEngine.DrawPlayerKeysRedefine;
1468var
1469 Text: string;
1470 MenuWidth: Integer;
1471 Y: Integer;
1472 I: Integer;
1473
1474function IsKeySet(Key: Integer): string;
1475begin
1476 if Key <> 0 then Result := SSet
1477 else Result := '';
1478end;
1479
1480begin
1481 with Bitmap.Canvas do begin
1482 ClearBackground;
1483
1484 MenuWidth := GetMenuWidth;
1485
1486 Brush.Style := bsClear;
1487 Pen.Style := psSolid;
1488 Pen.Color := clWhite;
1489 Font.Color := clTuna;
1490 Font.Size := 30;
1491 Text := SDefinePlayerKeys;
1492 TextOut((MenuWidth - TextWidth(Text)) div 2, Bitmap.Height div 20, Text);
1493
1494 Pen.Color := clPurple;
1495 Pen.Width := 6;
1496 Frame((MenuWidth - 400) div 2, Bitmap.Height div 10 * 4 - 40,
1497 (MenuWidth + 400) div 2, Bitmap.Height div 10 * 4 + 280);
1498
1499 Font.Color := clPurple;
1500 Font.Size := 20;
1501
1502 ShowMenuItem(IsKeySet(FSelectedPlayer.Keys.Left), SLeft, MenuWidth div 2 - 180, Bitmap.Height div 10 * 4, Bitmap.Canvas);
1503 ShowMenuItem(IsKeySet(FSelectedPlayer.Keys.Up), SUp, MenuWidth div 2 - 180, Bitmap.Height div 10 * 4 + 40, Bitmap.Canvas);
1504 ShowMenuItem(IsKeySet(FSelectedPlayer.Keys.Right), SRight, MenuWidth div 2 - 180, Bitmap.Height div 10 * 4 + 80, Bitmap.Canvas);
1505 ShowMenuItem(IsKeySet(FSelectedPlayer.Keys.Down), SDown, MenuWidth div 2 - 180, Bitmap.Height div 10 * 4 + 120, Bitmap.Canvas);
1506 ShowMenuItem(IsKeySet(FSelectedPlayer.Keys.Shoot), SShoot, MenuWidth div 2 - 180, Bitmap.Height div 10 * 4 + 160, Bitmap.Canvas);
1507 ShowMenuItem('ESC', SBack, MenuWidth div 2 - 180, Bitmap.Height div 10 * 4 + 200, Bitmap.Canvas);
1508
1509 if (FSelectedPlayer.Keys.Left <> 0) and
1510 (FSelectedPlayer.Keys.Up <> 0) and
1511 (FSelectedPlayer.Keys.Right <> 0) and
1512 (FSelectedPlayer.Keys.Down <> 0) and
1513 (FSelectedPlayer.Keys.Shoot <> 0) then begin
1514 Font.Color := clDarkGreen;
1515 Font.Size := 20;
1516 Text := SDone;
1517 TextOut((MenuWidth - TextWidth(Text)) div 2, Bitmap.Height div 10 * 9, Text);
1518 end;
1519 end;
1520end;
1521
1522procedure TEngine.DrawNewRound;
1523var
1524 Text: string;
1525 I: Integer;
1526 Y: Integer;
1527begin
1528 with Bitmap.Canvas do begin
1529 ClearBackground;
1530
1531 Brush.Style := bsClear;
1532 Pen.Style := psSolid;
1533 Pen.Color := clWhite;
1534 Font.Color := clTuna;
1535 Font.Size := 30;
1536 Text := SRound + ' ' + IntToStr(CurrentRound);
1537 TextOut((Bitmap.Width - TextWidth(Text)) div 2, Bitmap.Height div 5, Text);
1538
1539
1540 Pen.Color := clPurple;
1541 Pen.Width := 6;
1542 Frame((Bitmap.Width - 400) div 2, Bitmap.Height div 5 - 10,
1543 (Bitmap.Width + 400) div 2, Bitmap.Height div 5 + 100);
1544
1545 Y := 0;
1546 for I := 0 to Players.Count - 1 do
1547 with TPlayer(Players[I]) do begin
1548 if Enabled then begin
1549 Font.Color := Color1;
1550 Text := Name + ': ' + IntToStr(Score);
1551 TextOut((Bitmap.Width - TextWidth(Text)) div 2, Bitmap.Height div 5 * 2 + Y, Text);
1552 Inc(Y, 60);
1553 end;
1554 end;
1555 end;
1556end;
1557
1558procedure TEngine.DrawMap;
1559var
1560 Text: string;
1561begin
1562 Bitmap.EndUpdate;
1563 Bitmap.BeginUpdate;
1564
1565 World.DrawToBitmap(Bitmap);
1566
1567 Bitmap.EndUpdate;
1568 Bitmap.BeginUpdate(True);
1569 with Bitmap.Canvas do begin
1570 Brush.Style := bsClear;
1571 Pen.Style := psSolid;
1572 Font.Color := clGreen;
1573 Font.Size := 30;
1574 Text := SPressEsc;
1575 TextOut((Bitmap.Width - TextWidth(Text)) div 2, Bitmap.Height div 10 * 9, Text);
1576 end;
1577end;
1578
1579procedure TEngine.SetState(AValue: TGameState);
1580begin
1581 if FState = AValue then Exit;
1582 FState := AValue;
1583 FRedrawPending := True;
1584 FStateTime := Now;
1585end;
1586
1587function TEngine.TextOutWordWrap(Canvas: TCanvas; X, Y: Integer; Text: string; Width: Integer): Integer;
1588var
1589 Parts: TStringArray;
1590 I: Integer;
1591 XX: Integer;
1592begin
1593 Result := 1;
1594 XX := 0;
1595 Parts := Explode(' ', Text);
1596 for I := 0 to Length(Parts) - 1 do begin
1597 if (X + XX + Canvas.TextWidth(Parts[I]) > Width) or (Parts[I] = '\n') then begin
1598 Y := Y + Canvas.TextHeight(Parts[I]);
1599 XX := 0;
1600 Inc(Result);
1601 end;
1602 if Parts[I] = '\n' then Continue;
1603 Canvas.TextOut(X + XX, Y, Parts[I]);
1604 XX := XX + Canvas.TextWidth(Parts[I]) + Canvas.TextWidth(' ');
1605 end;
1606end;
1607
1608function TEngine.GetMenuWidth: Integer;
1609begin
1610 if FShowMenuStats then begin
1611 Result := Bitmap.Width div 2;
1612 DrawStats;
1613 end else Result := Bitmap.Width;
1614end;
1615
1616procedure TEngine.InitDigMasks;
1617var
1618 NewMask: TMatrixByte;
1619 I: Integer;
1620begin
1621 DigMasks.Clear;
1622
1623 // 001111100
1624 // 0111A1110
1625 // 00z1A1z00
1626 // 00zxAxz00
1627 // 00zxAxz00
1628 // 00zxxxz00
1629 // 00z000z00
1630 // 000000000
1631 // 000000000
1632
1633 NewMask := TMatrixByte.Create;
1634 with NewMask do begin
1635 Count := CreateIndex(9, 9);
1636 for I := 0 to 4 do ItemsXY[2 + I, 0] := 1;
1637 for I := 0 to 2 do begin
1638 ItemsXY[1 + I, 1] := 1;
1639 ItemsXY[5 + I, 1] := 1;
1640 end;
1641 ItemsXY[3, 2] := 1;
1642 ItemsXY[5, 2] := 1;
1643 end;
1644 DigMasks.Add(NewMask);
1645
1646 // 000011110
1647 // 0000z1111
1648 // 000zx1A11
1649 // 00zxxA111
1650 // 0zxxAxxz1
1651 // 000xxxz00
1652 // 0000xz000
1653 // 0000z0000
1654 // 000000000
1655
1656 NewMask := TMatrixByte.Create;
1657 with NewMask do begin
1658 Count := CreateIndex(9, 9);
1659 for I := 0 to 3 do begin
1660 ItemsXY[4 + I, 0] := 1;
1661 ItemsXY[5 + I, 1] := 1;
1662 end;
1663 ItemsXY[5, 2] := 1;
1664 ItemsXY[7, 2] := 1;
1665 ItemsXY[8, 2] := 1;
1666 for I := 0 to 2 do
1667 ItemsXY[6 + I, 3] := 1;
1668 ItemsXY[8, 4] := 1;
1669 end;
1670 DigMasks.Add(NewMask);
1671
1672 NewMask := TMatrixByte.Create;
1673 NewMask.Assign(DigMasks[0]);
1674 NewMask.Reverse;
1675 NewMask.ReverseHorizontal;
1676 DigMasks.Add(NewMask);
1677
1678 NewMask := TMatrixByte.Create;
1679 NewMask.Assign(DigMasks[1]);
1680 NewMask.ReverseVertical;
1681 DigMasks.Add(NewMask);
1682
1683 NewMask := TMatrixByte.Create;
1684 NewMask.Assign(DigMasks[0]);
1685 NewMask.ReverseVertical;
1686 DigMasks.Add(NewMask);
1687
1688 NewMask := TMatrixByte.Create;
1689 NewMask.Assign(DigMasks[1]);
1690 NewMask.ReverseVertical;
1691 NewMask.ReverseHorizontal;
1692 DigMasks.Add(NewMask);
1693
1694 NewMask := TMatrixByte.Create;
1695 NewMask.Assign(DigMasks[0]);
1696 NewMask.Reverse;
1697 DigMasks.Add(NewMask);
1698
1699 NewMask := TMatrixByte.Create;
1700 NewMask.Assign(DigMasks[1]);
1701 NewMask.ReverseHorizontal;
1702 DigMasks.Add(NewMask);
1703end;
1704
1705procedure TEngine.InitPlayerPool;
1706var
1707 I: Integer;
1708begin
1709 PlayerPool.Clear;
1710 with PlayerPool.AddNew do begin
1711 Name := SGreen;
1712 Keys.Left := vkA;
1713 Keys.Down := vkX;
1714 Keys.Right := vkD;
1715 Keys.Up := vkW;
1716 Keys.Shoot := vkControl;
1717 Color1 := $00ff00;
1718 Color2 := $00a000;
1719 Enabled := True;
1720 end;
1721 with PlayerPool.AddNew do begin
1722 Name := SBlue;
1723 Keys.Left := vkLeft;
1724 Keys.Down := vkDown;
1725 Keys.Right := vkRight;
1726 Keys.Up := vkUp;
1727 Keys.Shoot := vkReturn;
1728 Color1 := $ff2c2c;
1729 Color2 := $b60000;
1730 Enabled := True;
1731 end;
1732 with PlayerPool.AddNew do begin
1733 Name := SRed;
1734 Keys.Left := vkL;
1735 Keys.Down := 147;
1736 Keys.Right := vkQuote;
1737 Keys.Up := vkP;
1738 Keys.Shoot := vkSlash;
1739 Color1 := $0000ff;
1740 Color2 := $0000a0;
1741 end;
1742 with PlayerPool.AddNew do begin
1743 Name := SPink;
1744 Keys.Left := vkNumpad4;
1745 Keys.Down := vkNumpad2;
1746 Keys.Right := vkNumpad6;
1747 Keys.Up := vkNumpad8;
1748 Keys.Shoot := vkNumpad9;
1749 Color1 := $ff2cff;
1750 Color2 := $b600b6;
1751 end;
1752 with PlayerPool.AddNew do begin
1753 Name := SCyan;
1754 Keys.Left := 0;
1755 Keys.Down := 0;
1756 Keys.Right := 0;
1757 Keys.Up := 0;
1758 Keys.Shoot := 0;
1759 Color1 := $ffff2c;
1760 Color2 := $b6b600;
1761 end;
1762 with PlayerPool.AddNew do begin
1763 Name := SYellow;
1764 Keys.Left := 0;
1765 Keys.Down := 0;
1766 Keys.Right := 0;
1767 Keys.Up := 0;
1768 Keys.Shoot := 0;
1769 Color1 := $2cffff;
1770 Color2 := $00b6b6;
1771 end;
1772 with PlayerPool.AddNew do begin
1773 Name := SOrange;
1774 Keys.Left := 0;
1775 Keys.Down := 0;
1776 Keys.Right := 0;
1777 Keys.Up := 0;
1778 Keys.Shoot := 0;
1779 Color1 := $008cff;
1780 Color2 := $002da0;
1781 end;
1782 with PlayerPool.AddNew do begin
1783 Name := SGray;
1784 Keys.Left := 0;
1785 Keys.Down := 0;
1786 Keys.Right := 0;
1787 Keys.Up := 0;
1788 Keys.Shoot := 0;
1789 Color1 := $d0d0d0;
1790 Color2 := $707070;
1791 end;
1792 for I := 0 to PlayerPool.Count - 1 do
1793 with PlayerPool[I] do begin
1794 Engine := Self;
1795 Id := I;
1796 InitTanks;
1797 if I < 2 then Enabled := True;
1798 end;
1799end;
1800
1801procedure TEngine.Translate;
1802begin
1803 PlayerPool[0].Name := SGreen;
1804 PlayerPool[1].Name := SBlue;
1805 PlayerPool[2].Name := SRed;
1806 PlayerPool[3].Name := SPink;
1807 PlayerPool[4].Name := SCyan;
1808 PlayerPool[5].Name := SYellow;
1809 PlayerPool[6].Name := SOrange;
1810 PlayerPool[7].Name := SGray;
1811end;
1812
1813procedure TEngine.InitPlayers;
1814var
1815 I: Integer;
1816 NewPlayer: TPlayer;
1817begin
1818 Players.Clear;
1819 for I := 0 to PlayerPool.Count - 1 do
1820 with PlayerPool[I] do
1821 if Enabled then begin
1822 NewPlayer := TPlayer.Create;
1823 NewPlayer.Assign(PlayerPool[I]);
1824 Players.Add(NewPlayer);
1825 Score := 0;
1826 World.Matters[Integer(miPlayer1Cannon) + I * 4].Color := clYellow;
1827 World.Matters[Integer(miPlayer1Home) + I * 4].Color := Color1;
1828 World.Matters[Integer(miPlayer1TankBody) + I * 4].Color := Color1;
1829 World.Matters[Integer(miPlayer1TankBody2) + I * 4].Color := Color2;
1830 end;
1831end;
1832
1833procedure TEngine.CheckGameEnd;
1834var
1835 I: Integer;
1836begin
1837 if Players.GetAliveCount <= 1 then begin
1838 for I := 0 to Players.Count - 1 do
1839 with Players[I] do
1840 if not Exploded then Inc(Score);
1841 if CurrentRound < MaxRound then begin
1842 Inc(CurrentRound);
1843 NewRound;
1844 State := gsNewRound;
1845 end else
1846 State := gsMap;
1847 if Assigned(FOnGameEnd) then
1848 FOnGameEnd(Self);
1849 end;
1850end;
1851
1852procedure TEngine.ClearBackground;
1853begin
1854 with Bitmap.Canvas do begin
1855 Brush.Style := bsSolid;
1856 Brush.Color := clBlack;
1857 FillRect(0, 0, Bitmap.Width, Bitmap.Height);
1858 end;
1859end;
1860
1861procedure TEngine.ShowMenuItem(Key, Text: string; X, Y: Integer; Canvas: TCanvas);
1862begin
1863 Canvas.TextOut(X, Y, '<' + Key + '>');
1864 Canvas.TextOut(X + 140, Y, Text);
1865end;
1866
1867procedure TEngine.DrawMenu;
1868var
1869 Text: string;
1870 MenuWidth: Integer;
1871begin
1872 with Bitmap.Canvas do begin
1873 ClearBackground;
1874
1875 MenuWidth := GetMenuWidth;
1876
1877 Brush.Style := bsClear;
1878 Pen.Style := psSolid;
1879 Pen.Color := clWhite;
1880 Font.Color := clTuna;
1881 Font.Size := 30;
1882 Text := 'TUNNELER';
1883 TextOut((MenuWidth - TextWidth(Text)) div 2, Bitmap.Height div 10, Text);
1884
1885 Font.Color := clDarkOrange;
1886 Font.Size := 20;
1887 Text := 'by Chronos';
1888 TextOut((MenuWidth - TextWidth(Text)) div 2, Bitmap.Height div 10 + 70, Text);
1889
1890 Pen.Color := clPurple;
1891 Pen.Width := 6;
1892 Frame((MenuWidth - 400) div 2, Bitmap.Height div 10 * 4 - 40,
1893 (MenuWidth + 400) div 2, Bitmap.Height div 10 * 4 + 240);
1894
1895 Font.Color := clPurple;
1896 Font.Size := 20;
1897
1898 ShowMenuItem('F1', SStartGame, MenuWidth div 2 - 180, Bitmap.Height div 10 * 4, Bitmap.Canvas);
1899 ShowMenuItem('F2', SInstructions, MenuWidth div 2 - 180, Bitmap.Height div 10 * 4 + 40, Bitmap.Canvas);
1900 ShowMenuItem('F3', SInformation, MenuWidth div 2 - 180, Bitmap.Height div 10 * 4 + 80, Bitmap.Canvas);
1901 ShowMenuItem('F4', SSettings, MenuWidth div 2 - 180, Bitmap.Height div 10 * 4 + 120, Bitmap.Canvas);
1902 ShowMenuItem('F10', SExit, MenuWidth div 2 - 180, Bitmap.Height div 10 * 4 + 160, Bitmap.Canvas);
1903
1904 Font.Color := clDarkGreen;
1905 Font.Size := 20;
1906 Text := '(' + SWorldReady + ')';
1907 TextOut((MenuWidth - TextWidth(Text)) div 2, Bitmap.Height div 10 * 9, Text);
1908 end;
1909end;
1910
1911procedure TEngine.DrawStatsPartial(PlayerIndex, YDiff: Integer);
1912var
1913 X: Integer;
1914 Y: Integer;
1915 I: Integer;
1916 Text: string;
1917 ShotsPercent: Integer;
1918const
1919 LineHeight = 40;
1920begin
1921 with Bitmap.Canvas do begin
1922 Font.Color := clOrange;
1923 Font.Size := 20;
1924
1925 Y := Bitmap.Height div 10 + 3 * LineHeight + YDiff;
1926 X := Bitmap.Width div 2 + 50;
1927 Inc(Y, LineHeight);
1928 Text := SShotsFired;
1929 TextOut(X, Y, Text);
1930 Inc(Y, LineHeight);
1931 Text := SShotsHit;
1932 TextOut(X, Y, Text);
1933 Inc(Y, LineHeight);
1934 Text := SPercentHit;
1935 TextOut(X, Y, Text);
1936 Inc(Y, LineHeight);
1937 Inc(Y, LineHeight);
1938 Text := SMetersDug;
1939 TextOut(X, Y, Text);
1940 Inc(Y, LineHeight);
1941 Text := SMetersTravelled;
1942 TextOut(X, Y, Text);
1943 Inc(Y, LineHeight);
1944
1945 for I := PlayerIndex to Min(Players.Count - 1, PlayerIndex + 3) do
1946 if I < Players.Count then
1947 with Players[I] do begin
1948 Y := Bitmap.Height div 10 + 3 * LineHeight + YDiff;
1949 X := Bitmap.Width div 2 + 50 + 500 + 200 * (I - PlayerIndex);
1950 Font.Color := Color1;
1951 Text := Name;
1952 TextOut(X - TextWidth(Text) , Y, Text);
1953 Inc(Y, LineHeight);
1954 Text := IntToStr(ShotsCount);
1955 TextOut(X - TextWidth(Text), Y, Text);
1956 Inc(Y, LineHeight);
1957 Text := IntToStr(ShotsHit);
1958 TextOut(X - TextWidth(Text), Y, Text);
1959 Inc(Y, LineHeight);
1960 if ShotsCount > 0 then
1961 ShotsPercent := Round(ShotsHit / ShotsCount * 100)
1962 else ShotsPercent := 0;
1963 Text := IntToStr(ShotsPercent) + '%';
1964 TextOut(X - TextWidth(Text), Y, Text);
1965 Inc(Y, LineHeight);
1966 Inc(Y, LineHeight);
1967 Text := IntToStr(MetersDug);
1968 TextOut(X - TextWidth(Text), Y, Text);
1969 Inc(Y, LineHeight);
1970 Text := IntToStr(MetersTravelled);
1971 TextOut(X - TextWidth(Text), Y, Text);
1972 Inc(Y, LineHeight);
1973 end;
1974
1975 Inc(Y, 3 * LineHeight);
1976 end;
1977end;
1978
1979procedure TEngine.DrawStats;
1980var
1981 X: Integer;
1982 Y: Integer;
1983 Text: string;
1984 Winner: TPlayer;
1985begin
1986 with Bitmap.Canvas do begin
1987 X := Bitmap.Width div 4 * 3;
1988
1989 Brush.Style := bsClear;
1990 Pen.Style := psSolid;
1991
1992 Pen.Color := clWhite;
1993 MoveTo(Bitmap.Width div 2, 0);
1994 LineTo(Bitmap.Width div 2, Bitmap.Height);
1995
1996 Font.Color := clCyan;
1997 Font.Size := 20;
1998 Text := SStatistics;
1999 TextOut(X - TextWidth(Text) div 2, Bitmap.Height div 10, Text);
2000
2001 DrawStatsPartial(0, 0);
2002 if Players.Count > 4 then
2003 DrawStatsPartial(4, 400);
2004
2005 X := Bitmap.Width div 2 + 50;
2006 Font.Color := clOrange;
2007 Winner := Players.GetWinner;
2008 if Assigned(Winner) then begin
2009 Text := SWinnerIs;
2010 TextOut(X, Y, Text);
2011 X := X + TextWidth(Text) + 20;
2012 Font.Color := Winner.Color1;
2013 Text := Winner.Name;
2014 TextOut(X, Y, Text);
2015 end;
2016 end;
2017end;
2018
2019procedure TEngine.DrawGamePrepare(Thread: TVirtualThread);
2020var
2021 I: Integer;
2022 OldCount: TBitmapTColorIndex;
2023begin
2024 Lock.Acquire;
2025 try
2026 // TODO: Without this (re)initialization we get range error
2027 OldCount := FBitmapLower.Count;
2028 FBitmapLower.Count := FBitmapLower.CreateIndex(0, 0);
2029 FBitmapLower.Count := OldCount;
2030
2031 if FClearBackground then FBitmapLower.FillAll(World.Matters[Integer(miBorder)].Color);
2032 for I := 0 to Players.Count - 1 do
2033 if Players[I].Enabled then begin
2034 Players[I].Paint;
2035 end;
2036 finally
2037 Lock.Release;
2038 end;
2039end;
2040
2041procedure TEngine.ResizePlayerFrames;
2042var
2043 HorizFrameCount: Integer;
2044 VertFrameCount: Integer;
2045 I: Integer;
2046begin
2047 if Assigned(FBitmapLower) then begin
2048 if Players.Count > 1 then begin
2049 if Players.Count > 2 then VertFrameCount := 2
2050 else VertFrameCount := 1;
2051 HorizFrameCount := Ceil(Players.Count / VertFrameCount);
2052 end else begin
2053 VertFrameCount := 1;
2054 HorizFrameCount := 1;
2055 end;
2056 FBitmapLower.Count := FBitmapLower.CreateIndex(PlayerFrameWidth * HorizFrameCount,
2057 PlayerFrameHeight * VertFrameCount);
2058 for I := 0 to Players.Count - 1 do begin
2059 Players[I].ScreenFrame.AsTRect := Rect(
2060 (I mod HorizFrameCount) * (FBitmapLower.Count.X div HorizFrameCount) + 1,
2061 (I div HorizFrameCount) * (FBitmapLower.Count.Y div VertFrameCount) + 1,
2062 ((I mod HorizFrameCount) + 1) * (FBitmapLower.Width div HorizFrameCount),
2063 ((I div HorizFrameCount) + 1) * (FBitmapLower.Height div VertFrameCount));
2064 end;
2065 end;
2066 FClearBackground := True;
2067 Redraw;
2068end;
2069
2070constructor TEngine.Create(AOwner: TComponent);
2071begin
2072 inherited;
2073 Lock := TCriticalSection.Create;
2074 FBitmapLower := TBitmapTColor.Create;
2075 FBitmapLock := TCriticalSection.Create;
2076 PlayerPool := TPlayers.Create;
2077 PlayerPool.Engine := Self;
2078 Players := TPlayers.Create;
2079 Players.Engine := Self;
2080 Keyboard := TKeyboard.Create;
2081 World := TWorld.Create;
2082 //DefaultAudioSystem := TAudioSystemMPlayer.Create(nil);
2083 AudioShot := TSound.Create(nil);
2084 AudioShot.FileName := GetCurrentDir + '/Audio/GE_KF7_Soviet.wav';
2085 AudioExplode := TSound.Create(nil);
2086 AudioExplode.FileName := GetCurrentDir + '/Audio/bomb.wav';
2087 InitPlayerPool;
2088 DigMasks := TObjectList<TMatrixByte>.Create;
2089 InitDigMasks;
2090 Redraw;
2091 MaxRound := 5;
2092end;
2093
2094destructor TEngine.Destroy;
2095begin
2096 Active := False;
2097 FreeAndNil(DigMasks);
2098 FreeAndNil(FBitmapLower);
2099 FreeAndNil(FBitmapLock);
2100 FreeAndNil(PlayerPool);
2101 FreeAndNil(Players);
2102 FreeAndNil(Keyboard);
2103 FreeAndNil(World);
2104 FreeAndNil(Lock);
2105 FreeAndNil(AudioShot);
2106 FreeAndNil(AudioExplode);
2107 inherited;
2108end;
2109
2110procedure TEngine.Tick;
2111var
2112 I: Integer;
2113begin
2114 if State = gsGame then begin
2115 Lock.Acquire;
2116 try
2117 for I := 0 to Players.Count - 1 do begin
2118 Players[I].Control;
2119 Players[I].Tick;
2120 end;
2121 finally
2122 Lock.Release;
2123 end;
2124 end else
2125 if State = gsNewRound then begin
2126 if SecondOf(Now - FStateTime) > NewRoundDelay then begin
2127 State := gsGame;
2128 end;
2129 end;
2130end;
2131
2132procedure TEngine.Draw;
2133var
2134 DrawStart: TDateTime;
2135begin
2136 if FRedrawPending then begin
2137 DrawStart := NowPrecise;
2138 FRedrawPending := False;
2139 case State of
2140 gsGame: DrawGame;
2141 gsMenu: DrawMenu;
2142 gsInformation: DrawInformation;
2143 gsInstructions: DrawInstructions;
2144 gsSettings: DrawSettings;
2145 gsMap: DrawMap;
2146 gsNewRound: DrawNewRound;
2147 gsPlayerKeys: DrawPlayerKeys;
2148 gsPlayerKeysRedefine: DrawPlayerKeysRedefine;
2149 end;
2150
2151 DrawDuration := NowPrecise - DrawStart;
2152 end;
2153end;
2154
2155procedure TEngine.DrawThread(Thread: TVirtualThread);
2156begin
2157 if State = gsGame then DrawGamePrepare(Thread);
2158end;
2159
2160procedure TEngine.NewGame;
2161var
2162 I: Integer;
2163begin
2164 Active := False;
2165 InitPlayers;
2166 ResizePlayerFrames;
2167 CurrentRound := 1;
2168 World.Generate;
2169 for I := 0 to Players.Count - 1 do
2170 Players[I].Init;
2171 NewRound;
2172
2173 Active := True;
2174end;
2175
2176procedure TEngine.NewRound;
2177var
2178 I: Integer;
2179begin
2180 for I := 0 to Players.Count - 1 do
2181 Players[I].ResetTank;
2182 FClearBackground := True;
2183 Redraw;
2184end;
2185
2186procedure TEngine.LoadFromRegistry(Context: TRegistryContext);
2187begin
2188 with TRegistryEx.Create do
2189 try
2190 CurrentContext := Context;
2191 PlayerPool.LoadFromRegistry(TRegistryContext.Create(Context.RootKey, Context.Key + '\Players'));
2192 finally
2193 Free;
2194 end;
2195end;
2196
2197procedure TEngine.SaveToRegistry(Context: TRegistryContext);
2198begin
2199 with TRegistryEx.Create do
2200 try
2201 CurrentContext := Context;
2202
2203 PlayerPool.SaveToRegistry(TRegistryContext.Create(Context.RootKey, Context.Key + '\Players'));
2204 finally
2205 Free;
2206 end;
2207end;
2208
2209procedure TEngine.KeyUp(Key: Word);
2210var
2211 I: Integer;
2212begin
2213 KeyBoard.KeyState[Key] := False;
2214
2215 if State = gsMenu then begin
2216 if Key = vkF1 then begin
2217 State := gsNewRound;
2218 NewGame;
2219 end else
2220 if Key = vkF2 then begin
2221 State := gsInstructions;
2222 end else
2223 if Key = vkF3 then begin
2224 State := gsInformation;
2225 end else
2226 if Key = vkF4 then begin
2227 State := gsSettings;
2228 end else
2229 if Key = vkF10 then begin
2230 if Assigned(FOnClose) then FOnClose(Self);
2231 end;
2232 end else
2233 if State = gsMap then begin
2234 if Key = vkEscape then begin
2235 State := gsMenu;
2236 FShowMenuStats := True;
2237 end;
2238 end else
2239 if State in [gsInformation, gsInstructions] then begin
2240 if Key = vkEscape then begin
2241 State := gsMenu;
2242 end;
2243 end else
2244 if State = gsSettings then begin
2245 if Key = vkF1 then begin
2246 PlayerPool.EnableMore;
2247 Redraw;
2248 end else
2249 if Key = vkF2 then begin
2250 PlayerPool.EnableLess;
2251 Redraw;
2252 end else
2253 if Key = vkF3 then begin
2254 State := gsPlayerKeys;
2255 end else
2256 if Key = vkEscape then begin
2257 State := gsMenu;
2258 end;
2259 end else
2260 if State = gsPlayerKeys then begin
2261 if Key = vkF1 then begin
2262 FSelectedPlayer := PlayerPool[0];
2263 FSelectedPlayer.ClearKeys;
2264 State := gsPlayerKeysRedefine;
2265 end else
2266 if Key = vkF2 then begin
2267 FSelectedPlayer := PlayerPool[1];
2268 FSelectedPlayer.ClearKeys;
2269 State := gsPlayerKeysRedefine;
2270 end else
2271 if Key = vkF3 then begin
2272 FSelectedPlayer := PlayerPool[2];
2273 FSelectedPlayer.ClearKeys;
2274 State := gsPlayerKeysRedefine;
2275 end else
2276 if Key = vkF4 then begin
2277 FSelectedPlayer := PlayerPool[3];
2278 FSelectedPlayer.ClearKeys;
2279 State := gsPlayerKeysRedefine;
2280 end else
2281 if Key = vkF5 then begin
2282 FSelectedPlayer := PlayerPool[4];
2283 FSelectedPlayer.ClearKeys;
2284 State := gsPlayerKeysRedefine;
2285 end else
2286 if Key = vkF6 then begin
2287 FSelectedPlayer := PlayerPool[5];
2288 FSelectedPlayer.ClearKeys;
2289 State := gsPlayerKeysRedefine;
2290 end else
2291 if Key = vkF7 then begin
2292 FSelectedPlayer := PlayerPool[6];
2293 FSelectedPlayer.ClearKeys;
2294 State := gsPlayerKeysRedefine;
2295 end else
2296 if Key = vkF8 then begin
2297 FSelectedPlayer := PlayerPool[7];
2298 FSelectedPlayer.ClearKeys;
2299 State := gsPlayerKeysRedefine;
2300 end else
2301 if Key = vkEscape then begin
2302 State := gsSettings;
2303 end;
2304 end else
2305 if State = gsPlayerKeysRedefine then begin
2306 if Key = vkEscape then begin
2307 State := gsPlayerKeys;
2308 end else begin
2309 if FSelectedPlayer.Keys.Left = 0 then begin
2310 PlayerPool.ClearKey(Key);
2311 FSelectedPlayer.Keys.Left := Key;
2312 end else
2313 if FSelectedPlayer.Keys.Up = 0 then begin
2314 PlayerPool.ClearKey(Key);
2315 FSelectedPlayer.Keys.Up := Key;
2316 end else
2317 if FSelectedPlayer.Keys.Right = 0 then begin
2318 PlayerPool.ClearKey(Key);
2319 FSelectedPlayer.Keys.Right := Key;
2320 end else
2321 if FSelectedPlayer.Keys.Down = 0 then begin
2322 PlayerPool.ClearKey(Key);
2323 FSelectedPlayer.Keys.Down := Key;
2324 end else
2325 if FSelectedPlayer.Keys.Shoot = 0 then begin
2326 PlayerPool.ClearKey(Key);
2327 FSelectedPlayer.Keys.Shoot := Key;
2328 end;
2329
2330 Redraw;
2331 end;
2332 end else
2333 if State = gsGame then begin
2334 if Key = vkEscape then begin
2335 State := gsMap;
2336 end;
2337 end;
2338
2339 {$IFDEF DEBUG}
2340 if (State = gsGame) and (Key = vkF5) then begin
2341 // Destroy first alive player
2342 for I := 0 to Players.Count - 1 do
2343 with Players[I] do begin
2344 if not Exploded then begin
2345 Energy := -100;
2346 Break;
2347 end;
2348 end;
2349 end;
2350 {$ENDIF}
2351end;
2352
2353procedure TEngine.KeyDown(Key: Word);
2354begin
2355 KeyBoard.KeyState[Key] := True;
2356end;
2357
2358end.
2359
Note: See TracBrowser for help on using the repository browser.