source: trunk/Engine.pas

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