source: trunk/Engine.pas

Last change on this file was 103, checked in by chronos, 6 months ago
  • Fixed: Destroying other player didn't end the round.
  • Modified: End the game after three wins of the best player.
File size: 63.4 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 UnixDataDir = '/usr/share/Tunneler';
1650 UnixAudioDir = UnixDataDir + '/Audio';
1651{$ENDIF}
1652begin
1653 Result := GetCurrentDir + DirectorySeparator + 'Audio';
1654 {$IFDEF UNIX}
1655 if not DirectoryExists(Result) and DirectoryExists(UnixAudioDir) then begin
1656 Result := UnixAudioDir;
1657 Exit;
1658 end;
1659 {$ENDIF}
1660end;
1661
1662function TEngine.Scale(Value: Integer): Integer;
1663begin
1664 Result := ScaleX(Value, 96);
1665end;
1666
1667procedure TEngine.InitDigMasks;
1668var
1669 NewMask: TMatrixByte;
1670 I: Integer;
1671begin
1672 DigMasks.Clear;
1673
1674 // 001111100
1675 // 0111A1110
1676 // 00z1A1z00
1677 // 00zxAxz00
1678 // 00zxAxz00
1679 // 00zxxxz00
1680 // 00z000z00
1681 // 000000000
1682 // 000000000
1683
1684 NewMask := TMatrixByte.Create;
1685 with NewMask do begin
1686 Count := CreateIndex(9, 9);
1687 for I := 0 to 4 do ItemsXY[2 + I, 0] := 1;
1688 for I := 0 to 2 do begin
1689 ItemsXY[1 + I, 1] := 1;
1690 ItemsXY[5 + I, 1] := 1;
1691 end;
1692 ItemsXY[3, 2] := 1;
1693 ItemsXY[5, 2] := 1;
1694 end;
1695 DigMasks.Add(NewMask);
1696
1697 // 000011110
1698 // 0000z1111
1699 // 000zx1A11
1700 // 00zxxA111
1701 // 0zxxAxxz1
1702 // 000xxxz00
1703 // 0000xz000
1704 // 0000z0000
1705 // 000000000
1706
1707 NewMask := TMatrixByte.Create;
1708 with NewMask do begin
1709 Count := CreateIndex(9, 9);
1710 for I := 0 to 3 do begin
1711 ItemsXY[4 + I, 0] := 1;
1712 ItemsXY[5 + I, 1] := 1;
1713 end;
1714 ItemsXY[5, 2] := 1;
1715 ItemsXY[7, 2] := 1;
1716 ItemsXY[8, 2] := 1;
1717 for I := 0 to 2 do
1718 ItemsXY[6 + I, 3] := 1;
1719 ItemsXY[8, 4] := 1;
1720 end;
1721 DigMasks.Add(NewMask);
1722
1723 NewMask := TMatrixByte.Create;
1724 NewMask.Assign(DigMasks[0]);
1725 NewMask.Reverse;
1726 NewMask.ReverseHorizontal;
1727 DigMasks.Add(NewMask);
1728
1729 NewMask := TMatrixByte.Create;
1730 NewMask.Assign(DigMasks[1]);
1731 NewMask.ReverseVertical;
1732 DigMasks.Add(NewMask);
1733
1734 NewMask := TMatrixByte.Create;
1735 NewMask.Assign(DigMasks[0]);
1736 NewMask.ReverseVertical;
1737 DigMasks.Add(NewMask);
1738
1739 NewMask := TMatrixByte.Create;
1740 NewMask.Assign(DigMasks[1]);
1741 NewMask.ReverseVertical;
1742 NewMask.ReverseHorizontal;
1743 DigMasks.Add(NewMask);
1744
1745 NewMask := TMatrixByte.Create;
1746 NewMask.Assign(DigMasks[0]);
1747 NewMask.Reverse;
1748 DigMasks.Add(NewMask);
1749
1750 NewMask := TMatrixByte.Create;
1751 NewMask.Assign(DigMasks[1]);
1752 NewMask.ReverseHorizontal;
1753 DigMasks.Add(NewMask);
1754end;
1755
1756procedure TEngine.InitPlayerPool;
1757var
1758 I: Integer;
1759begin
1760 PlayerPool.Clear;
1761 with PlayerPool.AddNew do begin
1762 Name := SGreen;
1763 Keys.Left := vkA;
1764 Keys.Down := vkX;
1765 Keys.Right := vkD;
1766 Keys.Up := vkW;
1767 Keys.Shoot := vkControl;
1768 Color1 := $00ff00;
1769 Color2 := $00a000;
1770 Enabled := True;
1771 end;
1772 with PlayerPool.AddNew do begin
1773 Name := SBlue;
1774 Keys.Left := vkLeft;
1775 Keys.Down := vkDown;
1776 Keys.Right := vkRight;
1777 Keys.Up := vkUp;
1778 Keys.Shoot := vkReturn;
1779 Color1 := $ff2c2c;
1780 Color2 := $b60000;
1781 Enabled := True;
1782 end;
1783 with PlayerPool.AddNew do begin
1784 Name := SRed;
1785 Keys.Left := vkL;
1786 Keys.Down := 147;
1787 Keys.Right := vkQuote;
1788 Keys.Up := vkP;
1789 Keys.Shoot := vkSlash;
1790 Color1 := $0000ff;
1791 Color2 := $0000a0;
1792 end;
1793 with PlayerPool.AddNew do begin
1794 Name := SPink;
1795 Keys.Left := vkNumpad4;
1796 Keys.Down := vkNumpad2;
1797 Keys.Right := vkNumpad6;
1798 Keys.Up := vkNumpad8;
1799 Keys.Shoot := vkNumpad9;
1800 Color1 := $ff2cff;
1801 Color2 := $b600b6;
1802 end;
1803 with PlayerPool.AddNew do begin
1804 Name := SCyan;
1805 Keys.Left := 0;
1806 Keys.Down := 0;
1807 Keys.Right := 0;
1808 Keys.Up := 0;
1809 Keys.Shoot := 0;
1810 Color1 := $ffff2c;
1811 Color2 := $b6b600;
1812 end;
1813 with PlayerPool.AddNew do begin
1814 Name := SYellow;
1815 Keys.Left := 0;
1816 Keys.Down := 0;
1817 Keys.Right := 0;
1818 Keys.Up := 0;
1819 Keys.Shoot := 0;
1820 Color1 := $2cffff;
1821 Color2 := $00b6b6;
1822 end;
1823 with PlayerPool.AddNew do begin
1824 Name := SOrange;
1825 Keys.Left := 0;
1826 Keys.Down := 0;
1827 Keys.Right := 0;
1828 Keys.Up := 0;
1829 Keys.Shoot := 0;
1830 Color1 := $008cff;
1831 Color2 := $002da0;
1832 end;
1833 with PlayerPool.AddNew do begin
1834 Name := SGray;
1835 Keys.Left := 0;
1836 Keys.Down := 0;
1837 Keys.Right := 0;
1838 Keys.Up := 0;
1839 Keys.Shoot := 0;
1840 Color1 := $d0d0d0;
1841 Color2 := $707070;
1842 end;
1843 for I := 0 to PlayerPool.Count - 1 do
1844 with PlayerPool[I] do begin
1845 Engine := Self;
1846 Id := I;
1847 InitTanks;
1848 if I < 2 then Enabled := True;
1849 end;
1850end;
1851
1852procedure TEngine.Translate;
1853begin
1854 PlayerPool[0].Name := SGreen;
1855 PlayerPool[1].Name := SBlue;
1856 PlayerPool[2].Name := SRed;
1857 PlayerPool[3].Name := SPink;
1858 PlayerPool[4].Name := SCyan;
1859 PlayerPool[5].Name := SYellow;
1860 PlayerPool[6].Name := SOrange;
1861 PlayerPool[7].Name := SGray;
1862end;
1863
1864procedure TEngine.InitPlayers;
1865var
1866 I: Integer;
1867 NewPlayer: TPlayer;
1868begin
1869 Players.Clear;
1870 for I := 0 to PlayerPool.Count - 1 do
1871 with PlayerPool[I] do
1872 if Enabled then begin
1873 NewPlayer := TPlayer.Create;
1874 NewPlayer.Assign(PlayerPool[I]);
1875 Players.Add(NewPlayer);
1876 Score := 0;
1877 World.Matters[Integer(miPlayer1Cannon) + I * 4].Color := clYellow;
1878 World.Matters[Integer(miPlayer1Home) + I * 4].Color := Color1;
1879 World.Matters[Integer(miPlayer1TankBody) + I * 4].Color := Color1;
1880 World.Matters[Integer(miPlayer1TankBody2) + I * 4].Color := Color2;
1881 end;
1882end;
1883
1884procedure TEngine.CheckGameEnd;
1885var
1886 I: Integer;
1887 HighestScore: Integer;
1888begin
1889 if Players.GetAliveCount <= 1 then begin
1890 HighestScore := 0;
1891 for I := 0 to Players.Count - 1 do
1892 with Players[I] do begin
1893 if not Exploded then Inc(Score);
1894 HighestScore := Max(Score, HighestScore);
1895 end;
1896 if HighestScore < MaxScore then begin
1897 Inc(CurrentRound);
1898 NewRound;
1899 State := gsNewRound;
1900 end else begin
1901 State := gsMap;
1902 if Assigned(FOnGameEnd) then
1903 FOnGameEnd(Self);
1904 end;
1905 end;
1906end;
1907
1908procedure TEngine.ClearBackground;
1909begin
1910 with Bitmap.Canvas do begin
1911 Brush.Style := bsSolid;
1912 Brush.Color := clBlack;
1913 FillRect(0, 0, Bitmap.Width, Bitmap.Height);
1914 end;
1915end;
1916
1917procedure TEngine.ShowMenuItem(Key, Text: string; X, Y: Integer; Canvas: TCanvas);
1918begin
1919 Canvas.TextOut(X, Y, '<' + Key + '>');
1920 Canvas.TextOut(X + Scale(140), Y, Text);
1921end;
1922
1923procedure TEngine.DrawMenu;
1924var
1925 Text: string;
1926 MenuWidth: Integer;
1927 X: Integer;
1928begin
1929 with Bitmap.Canvas do begin
1930 ClearBackground;
1931
1932 MenuWidth := GetMenuWidth;
1933
1934 Brush.Style := bsClear;
1935 Pen.Style := psSolid;
1936 Pen.Color := clWhite;
1937 Font.Color := clTuna;
1938 Font.Size := Scale(20);
1939 Text := 'TUNNELER';
1940 TextOut((MenuWidth - TextWidth(Text)) div 2, Bitmap.Height div 10, Text);
1941
1942 Font.Color := clDarkOrange;
1943 Font.Size := Scale(14);
1944 Text := 'by Chronos';
1945 TextOut((MenuWidth - TextWidth(Text)) div 2, Bitmap.Height div 10 + Scale(60), Text);
1946
1947 Pen.Color := clPurple;
1948 Pen.Width := 6;
1949 Frame((MenuWidth - Scale(400)) div 2, Bitmap.Height div 10 * 4 - Scale(40),
1950 (MenuWidth + Scale(400)) div 2, Bitmap.Height div 10 * 4 + Scale(240));
1951
1952 Font.Color := clPurple;
1953 Font.Size := Scale(14);
1954
1955 X := MenuWidth div 2 - Scale(180);
1956 ShowMenuItem('F1', SStartGame, X, Bitmap.Height div 10 * 4, Bitmap.Canvas);
1957 ShowMenuItem('F2', SInstructions, X, Bitmap.Height div 10 * 4 + Scale(40), Bitmap.Canvas);
1958 ShowMenuItem('F3', SInformation, X, Bitmap.Height div 10 * 4 + Scale(80), Bitmap.Canvas);
1959 ShowMenuItem('F4', SSettings, X, Bitmap.Height div 10 * 4 + Scale(120), Bitmap.Canvas);
1960 ShowMenuItem('F10', SExit, X, Bitmap.Height div 10 * 4 + Scale(160), Bitmap.Canvas);
1961
1962 Font.Color := clDarkGreen;
1963 Font.Size := Scale(14);
1964 Text := '(' + SWorldReady + ')';
1965 TextOut((MenuWidth - TextWidth(Text)) div 2, Bitmap.Height div 10 * 9, Text);
1966 end;
1967end;
1968
1969procedure TEngine.DrawStatsPartial(var Y: Integer; PlayerIndex: Integer);
1970var
1971 X: Integer;
1972 I: Integer;
1973 Text: string;
1974 ShotsPercent: Integer;
1975 LineHeight: Integer;
1976 TempY: Integer;
1977begin
1978 TempY := Y;
1979
1980 LineHeight := Scale(40);
1981 with Bitmap.Canvas do begin
1982 Font.Color := clOrange;
1983 Font.Size := Scale(14);
1984
1985 X := Bitmap.Width div 2 + Scale(50);
1986 Inc(Y, LineHeight);
1987 Text := SShotsFired;
1988 TextOut(X, Y, Text);
1989 Inc(Y, LineHeight);
1990 Text := SShotsHit;
1991 TextOut(X, Y, Text);
1992 Inc(Y, LineHeight);
1993 Text := SPercentHit;
1994 TextOut(X, Y, Text);
1995 Inc(Y, LineHeight);
1996 Inc(Y, LineHeight);
1997 Text := SMetersDug;
1998 TextOut(X, Y, Text);
1999 Inc(Y, LineHeight);
2000 Text := SMetersTravelled;
2001 TextOut(X, Y, Text);
2002 Inc(Y, LineHeight);
2003
2004 for I := PlayerIndex to Min(Players.Count - 1, PlayerIndex + 3) do
2005 if I < Players.Count then
2006 with Players[I] do begin
2007 Y := TempY;
2008 X := Bitmap.Width div 2 + Scale(50 + 300 + 150 * (I - PlayerIndex));
2009 Font.Color := Color1;
2010 Text := Name;
2011 TextOut(X - TextWidth(Text), Y, Text);
2012 Inc(Y, LineHeight);
2013 Text := IntToStr(ShotsCount);
2014 TextOut(X - TextWidth(Text), Y, Text);
2015 Inc(Y, LineHeight);
2016 Text := IntToStr(ShotsHit);
2017 TextOut(X - TextWidth(Text), Y, Text);
2018 Inc(Y, LineHeight);
2019 if ShotsCount > 0 then
2020 ShotsPercent := Round(ShotsHit / ShotsCount * 100)
2021 else ShotsPercent := 0;
2022 Text := IntToStr(ShotsPercent) + '%';
2023 TextOut(X - TextWidth(Text), Y, Text);
2024 Inc(Y, LineHeight);
2025 Inc(Y, LineHeight);
2026 Text := IntToStr(MetersDug);
2027 TextOut(X - TextWidth(Text), Y, Text);
2028 Inc(Y, LineHeight);
2029 Text := IntToStr(MetersTravelled);
2030 TextOut(X - TextWidth(Text), Y, Text);
2031 Inc(Y, LineHeight);
2032 end;
2033
2034 Inc(Y, 2 * LineHeight);
2035 end;
2036end;
2037
2038procedure TEngine.DrawStats;
2039var
2040 X: Integer;
2041 Y: Integer;
2042 Text: string;
2043 Winner: TPlayer;
2044 LineHeight: Integer;
2045begin
2046 LineHeight := Scale(40);
2047 with Bitmap.Canvas do begin
2048 X := Bitmap.Width div 4 * 3;
2049
2050 Brush.Style := bsClear;
2051 Pen.Style := psSolid;
2052
2053 Pen.Color := clWhite;
2054 MoveTo(Bitmap.Width div 2, 0);
2055 LineTo(Bitmap.Width div 2, Bitmap.Height);
2056
2057 Font.Color := clCyan;
2058 Font.Size := Scale(14);
2059 Text := SStatistics;
2060 TextOut(X - TextWidth(Text) div 2, Bitmap.Height div 10, Text);
2061
2062 Y := Bitmap.Height div 10 + 3 * LineHeight;
2063
2064 DrawStatsPartial(Y, 0);
2065 if Players.Count > 4 then DrawStatsPartial(Y, 4);
2066
2067 X := Bitmap.Width div 2 + Scale(50);
2068 Font.Color := clOrange;
2069 Winner := Players.GetWinner;
2070 if Assigned(Winner) then begin
2071 Text := SWinnerIs;
2072 TextOut(X, Y, Text);
2073 X := X + TextWidth(Text) + Scale(20);
2074 Font.Color := Winner.Color1;
2075 Text := Winner.Name;
2076 TextOut(X, Y, Text);
2077 end;
2078 end;
2079end;
2080
2081procedure TEngine.DrawGamePrepare(Thread: TVirtualThread);
2082var
2083 I: Integer;
2084 OldCount: TBitmapTColorIndex;
2085begin
2086 Lock.Acquire;
2087 try
2088 // TODO: Without this (re)initialization we get range error
2089 OldCount := FBitmapLower.Count;
2090 FBitmapLower.Count := FBitmapLower.CreateIndex(0, 0);
2091 FBitmapLower.Count := OldCount;
2092
2093 if FClearBackground then FBitmapLower.FillAll(World.Matters[Integer(miBorder)].Color);
2094 for I := 0 to Players.Count - 1 do
2095 if Players[I].Enabled then begin
2096 Players[I].Paint;
2097 end;
2098 finally
2099 Lock.Release;
2100 end;
2101end;
2102
2103procedure TEngine.ResizePlayerFrames;
2104var
2105 HorizFrameCount: Integer;
2106 VertFrameCount: Integer;
2107 I: Integer;
2108begin
2109 if Assigned(FBitmapLower) then begin
2110 if Players.Count > 1 then begin
2111 if Players.Count > 2 then VertFrameCount := 2
2112 else VertFrameCount := 1;
2113 HorizFrameCount := Ceil(Players.Count / VertFrameCount);
2114 end else begin
2115 VertFrameCount := 1;
2116 HorizFrameCount := 1;
2117 end;
2118 FBitmapLower.Count := FBitmapLower.CreateIndex(PlayerFrameWidth * HorizFrameCount,
2119 PlayerFrameHeight * VertFrameCount);
2120 for I := 0 to Players.Count - 1 do begin
2121 Players[I].ScreenFrame.AsTRect := Rect(
2122 (I mod HorizFrameCount) * (FBitmapLower.Count.X div HorizFrameCount) + 1,
2123 (I div HorizFrameCount) * (FBitmapLower.Count.Y div VertFrameCount) + 1,
2124 ((I mod HorizFrameCount) + 1) * (FBitmapLower.Width div HorizFrameCount),
2125 ((I div HorizFrameCount) + 1) * (FBitmapLower.Height div VertFrameCount));
2126 end;
2127 end;
2128 FClearBackground := True;
2129 Redraw;
2130end;
2131
2132constructor TEngine.Create(AOwner: TComponent);
2133begin
2134 inherited;
2135 Lock := TCriticalSection.Create;
2136 FBitmapLower := TBitmapTColor.Create;
2137 FBitmapLock := TCriticalSection.Create;
2138 PlayerPool := TPlayers.Create;
2139 PlayerPool.Engine := Self;
2140 Players := TPlayers.Create;
2141 Players.Engine := Self;
2142 Keyboard := TKeyboard.Create;
2143 World := TWorld.Create;
2144 //DefaultAudioSystem := TAudioSystemMPlayer.Create(nil);
2145 AudioShot := TSound.Create(nil);
2146 AudioShot.FileName := GetAudioDir + DirectorySeparator + 'GE_KF7_Soviet.wav';
2147 AudioExplode := TSound.Create(nil);
2148 AudioExplode.FileName := GetAudioDir + DirectorySeparator + 'bomb.wav';
2149 InitPlayerPool;
2150 DigMasks := TObjectList<TMatrixByte>.Create;
2151 InitDigMasks;
2152 Redraw;
2153 MaxScore := 3;
2154end;
2155
2156destructor TEngine.Destroy;
2157begin
2158 Active := False;
2159 FreeAndNil(DigMasks);
2160 FreeAndNil(FBitmapLower);
2161 FreeAndNil(FBitmapLock);
2162 FreeAndNil(PlayerPool);
2163 FreeAndNil(Players);
2164 FreeAndNil(Keyboard);
2165 FreeAndNil(World);
2166 FreeAndNil(Lock);
2167 FreeAndNil(AudioShot);
2168 FreeAndNil(AudioExplode);
2169 inherited;
2170end;
2171
2172procedure TEngine.Tick;
2173var
2174 I: Integer;
2175begin
2176 if State = gsGame then begin
2177 Lock.Acquire;
2178 try
2179 for I := 0 to Players.Count - 1 do begin
2180 Players[I].Control;
2181 Players[I].Tick;
2182 end;
2183 finally
2184 Lock.Release;
2185 end;
2186 end else
2187 if State = gsNewRound then begin
2188 if SecondOf(Now - FStateTime) > NewRoundDelay then begin
2189 State := gsGame;
2190 end;
2191 end;
2192end;
2193
2194procedure TEngine.Draw;
2195var
2196 DrawStart: TDateTime;
2197begin
2198 if FRedrawPending then begin
2199 DrawStart := NowPrecise;
2200 FRedrawPending := False;
2201 case State of
2202 gsGame: DrawGame;
2203 gsMenu: DrawMenu;
2204 gsInformation: DrawInformation;
2205 gsInstructions: DrawInstructions;
2206 gsSettings: DrawSettings;
2207 gsMap: DrawMap;
2208 gsNewRound: DrawNewRound;
2209 gsPlayerKeys: DrawPlayerKeys;
2210 gsPlayerKeysRedefine: DrawPlayerKeysRedefine;
2211 end;
2212
2213 DrawDuration := NowPrecise - DrawStart;
2214 end;
2215end;
2216
2217procedure TEngine.DrawThread(Thread: TVirtualThread);
2218begin
2219 if State = gsGame then DrawGamePrepare(Thread);
2220end;
2221
2222procedure TEngine.NewGame;
2223var
2224 I: Integer;
2225begin
2226 Active := False;
2227 InitPlayers;
2228 ResizePlayerFrames;
2229 CurrentRound := 1;
2230 World.Generate;
2231 for I := 0 to Players.Count - 1 do
2232 Players[I].Init;
2233 NewRound;
2234
2235 Active := True;
2236end;
2237
2238procedure TEngine.NewRound;
2239var
2240 I: Integer;
2241begin
2242 for I := 0 to Players.Count - 1 do
2243 Players[I].ResetTank;
2244 FClearBackground := True;
2245 Redraw;
2246end;
2247
2248procedure TEngine.LoadFromRegistry(Context: TRegistryContext);
2249begin
2250 with TRegistryEx.Create do
2251 try
2252 CurrentContext := Context;
2253 PlayerPool.LoadFromRegistry(TRegistryContext.Create(Context.RootKey, Context.Key + '\Players'));
2254 finally
2255 Free;
2256 end;
2257end;
2258
2259procedure TEngine.SaveToRegistry(Context: TRegistryContext);
2260begin
2261 with TRegistryEx.Create do
2262 try
2263 CurrentContext := Context;
2264
2265 PlayerPool.SaveToRegistry(TRegistryContext.Create(Context.RootKey, Context.Key + '\Players'));
2266 finally
2267 Free;
2268 end;
2269end;
2270
2271procedure TEngine.KeyUp(Key: Word);
2272var
2273 I: Integer;
2274begin
2275 KeyBoard.KeyState[Key] := False;
2276
2277 if State = gsMenu then begin
2278 if Key = vkF1 then begin
2279 State := gsNewRound;
2280 NewGame;
2281 end else
2282 if Key = vkF2 then begin
2283 State := gsInstructions;
2284 end else
2285 if Key = vkF3 then begin
2286 State := gsInformation;
2287 end else
2288 if Key = vkF4 then begin
2289 State := gsSettings;
2290 end else
2291 if Key = vkF10 then begin
2292 if Assigned(FOnClose) then FOnClose(Self);
2293 end;
2294 end else
2295 if State = gsMap then begin
2296 if Key = vkEscape then begin
2297 State := gsMenu;
2298 FShowMenuStats := True;
2299 end;
2300 end else
2301 if State in [gsInformation, gsInstructions] then begin
2302 if Key = vkEscape then begin
2303 State := gsMenu;
2304 end;
2305 end else
2306 if State = gsSettings then begin
2307 if Key = vkF1 then begin
2308 PlayerPool.EnableMore;
2309 Redraw;
2310 end else
2311 if Key = vkF2 then begin
2312 PlayerPool.EnableLess;
2313 Redraw;
2314 end else
2315 if Key = vkF3 then begin
2316 State := gsPlayerKeys;
2317 end else
2318 if Key = vkEscape then begin
2319 State := gsMenu;
2320 end;
2321 end else
2322 if State = gsPlayerKeys then begin
2323 if Key = vkF1 then begin
2324 FSelectedPlayer := PlayerPool[0];
2325 FSelectedPlayer.ClearKeys;
2326 State := gsPlayerKeysRedefine;
2327 end else
2328 if Key = vkF2 then begin
2329 FSelectedPlayer := PlayerPool[1];
2330 FSelectedPlayer.ClearKeys;
2331 State := gsPlayerKeysRedefine;
2332 end else
2333 if Key = vkF3 then begin
2334 FSelectedPlayer := PlayerPool[2];
2335 FSelectedPlayer.ClearKeys;
2336 State := gsPlayerKeysRedefine;
2337 end else
2338 if Key = vkF4 then begin
2339 FSelectedPlayer := PlayerPool[3];
2340 FSelectedPlayer.ClearKeys;
2341 State := gsPlayerKeysRedefine;
2342 end else
2343 if Key = vkF5 then begin
2344 FSelectedPlayer := PlayerPool[4];
2345 FSelectedPlayer.ClearKeys;
2346 State := gsPlayerKeysRedefine;
2347 end else
2348 if Key = vkF6 then begin
2349 FSelectedPlayer := PlayerPool[5];
2350 FSelectedPlayer.ClearKeys;
2351 State := gsPlayerKeysRedefine;
2352 end else
2353 if Key = vkF7 then begin
2354 FSelectedPlayer := PlayerPool[6];
2355 FSelectedPlayer.ClearKeys;
2356 State := gsPlayerKeysRedefine;
2357 end else
2358 if Key = vkF8 then begin
2359 FSelectedPlayer := PlayerPool[7];
2360 FSelectedPlayer.ClearKeys;
2361 State := gsPlayerKeysRedefine;
2362 end else
2363 if Key = vkEscape then begin
2364 State := gsSettings;
2365 end;
2366 end else
2367 if State = gsPlayerKeysRedefine then begin
2368 if Key = vkEscape then begin
2369 State := gsPlayerKeys;
2370 end else begin
2371 if FSelectedPlayer.Keys.Left = 0 then begin
2372 PlayerPool.ClearKey(Key);
2373 FSelectedPlayer.Keys.Left := Key;
2374 end else
2375 if FSelectedPlayer.Keys.Up = 0 then begin
2376 PlayerPool.ClearKey(Key);
2377 FSelectedPlayer.Keys.Up := Key;
2378 end else
2379 if FSelectedPlayer.Keys.Right = 0 then begin
2380 PlayerPool.ClearKey(Key);
2381 FSelectedPlayer.Keys.Right := Key;
2382 end else
2383 if FSelectedPlayer.Keys.Down = 0 then begin
2384 PlayerPool.ClearKey(Key);
2385 FSelectedPlayer.Keys.Down := Key;
2386 end else
2387 if FSelectedPlayer.Keys.Shoot = 0 then begin
2388 PlayerPool.ClearKey(Key);
2389 FSelectedPlayer.Keys.Shoot := Key;
2390 end;
2391
2392 Redraw;
2393 end;
2394 end else
2395 if State = gsGame then begin
2396 if Key = vkEscape then begin
2397 State := gsMap;
2398 end;
2399 end;
2400
2401 {$IFDEF DEBUG}
2402 if (State = gsGame) and (Key = vkF5) then begin
2403 // Destroy first alive player
2404 for I := 0 to Players.Count - 1 do
2405 with Players[I] do begin
2406 if not Exploded then begin
2407 Energy := -100;
2408 Break;
2409 end;
2410 end;
2411 end;
2412 {$ENDIF}
2413end;
2414
2415procedure TEngine.KeyDown(Key: Word);
2416begin
2417 KeyBoard.KeyState[Key] := True;
2418end;
2419
2420end.
2421
Note: See TracBrowser for help on using the repository browser.