saveload: fix read/write unexisting value
[d2df-sdl.git] / src / game / g_player.pas
blob31ed0740f4b14b3e8fd23e3457947f9faf6914e2
1 (* Copyright (C) Doom 2D: Forever Developers
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 {$INCLUDE ../shared/a_modes.inc}
16 {$M+}
17 unit g_player;
19 interface
21 uses
22 SysUtils, Classes,
23 {$IFDEF USE_MEMPOOL}mempool,{$ENDIF}
24 {$IFDEF ENABLE_SOUND}
25 g_sound,
26 {$ENDIF}
27 e_graphics, g_playermodel, g_basic, g_textures,
28 g_weapons, g_phys, g_saveload, MAPDEF,
29 g_panel;
31 const
32 KEY_LEFT = 1;
33 KEY_RIGHT = 2;
34 KEY_UP = 3;
35 KEY_DOWN = 4;
36 KEY_FIRE = 5;
37 KEY_OPEN = 6;
38 KEY_JUMP = 7;
39 KEY_CHAT = 8;
41 WP_PREV = 0;
42 WP_NEXT = 1;
43 WP_FACT = WP_PREV;
44 WP_LACT = WP_NEXT;
46 R_ITEM_BACKPACK = 0;
47 R_KEY_RED = 1;
48 R_KEY_GREEN = 2;
49 R_KEY_BLUE = 3;
50 R_BERSERK = 4;
52 MR_SUIT = 0;
53 MR_INVUL = 1;
54 MR_INVIS = 2;
55 MR_MAX = 2;
57 A_BULLETS = 0;
58 A_SHELLS = 1;
59 A_ROCKETS = 2;
60 A_CELLS = 3;
61 A_FUEL = 4;
62 A_HIGH = 4;
64 AmmoLimits: Array [0..1] of Array [A_BULLETS..A_HIGH] of Word =
65 ((200, 50, 50, 300, 100),
66 (400, 100, 100, 600, 200));
68 K_SIMPLEKILL = 0;
69 K_HARDKILL = 1;
70 K_EXTRAHARDKILL = 2;
71 K_FALLKILL = 3;
73 T_RESPAWN = 0;
74 T_SWITCH = 1;
75 T_USE = 2;
76 T_FLAGCAP = 3;
78 TEAM_NONE = 0;
79 TEAM_RED = 1;
80 TEAM_BLUE = 2;
81 TEAM_COOP = 3;
83 SHELL_BULLET = 0;
84 SHELL_SHELL = 1;
85 SHELL_DBLSHELL = 2;
87 ANGLE_NONE = Low(SmallInt);
89 CORPSE_STATE_REMOVEME = 0;
90 CORPSE_STATE_NORMAL = 1;
91 CORPSE_STATE_MESS = 2;
93 PLAYER_RECT: TRectWH = (X:15; Y:12; Width:34; Height:52);
94 PLAYER_RECT_CX = 15+(34 div 2);
95 PLAYER_RECT_CY = 12+(52 div 2);
96 PLAYER_CORPSERECT: TRectWH = (X:15; Y:48; Width:34; Height:16);
98 PLAYER_HP_SOFT = 100;
99 PLAYER_HP_LIMIT = 200;
100 PLAYER_AP_SOFT = 100;
101 PLAYER_AP_LIMIT = 200;
102 SUICIDE_DAMAGE = 112;
103 WEAPON_DELAY = 5;
105 PLAYER_BURN_TIME = 110;
107 PLAYER1_DEF_COLOR: TRGB = (R:64; G:175; B:48);
108 PLAYER2_DEF_COLOR: TRGB = (R:96; G:96; B:96);
110 type
111 TPlayerStat = record
112 Num: Integer;
113 Ping: Word;
114 Loss: Byte;
115 Name: String;
116 Team: Byte;
117 Frags: SmallInt;
118 Deaths: SmallInt;
119 Lives: Byte;
120 Kills: Word;
121 Color: TRGB;
122 Spectator: Boolean;
123 UID: Word;
124 end;
126 TPlayerStatArray = Array of TPlayerStat;
128 TPlayerSavedState = record
129 Health: Integer;
130 Armor: Integer;
131 Air: Integer;
132 JetFuel: Integer;
133 CurrWeap: Byte;
134 NextWeap: WORD;
135 NextWeapDelay: Byte;
136 Ammo: Array [A_BULLETS..A_HIGH] of Word;
137 MaxAmmo: Array [A_BULLETS..A_HIGH] of Word;
138 Weapon: Array [WP_FIRST..WP_LAST] of Boolean;
139 Inventory: Set of R_ITEM_BACKPACK..R_BERSERK;
140 Used: Boolean;
141 end;
143 TKeyState = record
144 Pressed: Boolean;
145 Time: Word;
146 end;
148 TPlayer = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
149 private
150 FIamBot: Boolean;
151 FUID: Word;
152 FName: String;
153 FTeam: Byte;
154 FAlive: Boolean;
155 FSpawned: Boolean;
156 FDirection: TDirection;
157 FHealth: Integer;
158 FLives: Byte;
159 FArmor: Integer;
160 FAir: Integer;
161 FPain: Integer;
162 FPickup: Integer;
163 FKills: Integer;
164 FMonsterKills: Integer;
165 FFrags: Integer;
166 FFragCombo: Byte;
167 FLastFrag: LongWord;
168 FComboEvnt: Integer;
169 FDeath: Integer;
170 FCanJetpack: Boolean;
171 FJetFuel: Integer;
172 FFlag: Byte;
173 FSecrets: Integer;
174 FCurrWeap: Byte;
175 FNextWeap: WORD;
176 FNextWeapDelay: Byte; // frames
177 FBFGFireCounter: SmallInt;
178 FLastSpawnerUID: Word;
179 FLastHit: Byte;
180 FObj: TObj;
181 FXTo, FYTo: Integer;
182 FSpectatePlayer: Integer;
183 FFirePainTime: Integer;
184 FFireAttacker: Word;
186 FSavedStateNum: Integer;
188 FModel: TPlayerModel;
189 FPunchAnim: TAnimation;
190 FActionPrior: Byte;
191 FActionAnim: Byte;
192 FActionForce: Boolean;
193 FActionChanged: Boolean;
194 FAngle: SmallInt;
195 FFireAngle: SmallInt;
196 FIncCamOld: Integer;
197 FIncCam: Integer;
198 FSlopeOld: Integer;
199 FShellTimer: Integer;
200 FShellType: Byte;
201 {$IFDEF ENABLE_SOUND}
202 FSawSound: TPlayableSound;
203 FSawSoundIdle: TPlayableSound;
204 FSawSoundHit: TPlayableSound;
205 FSawSoundSelect: TPlayableSound;
206 FFlameSoundOn: TPlayableSound;
207 FFlameSoundOff: TPlayableSound;
208 FFlameSoundWork: TPlayableSound;
209 FJetSoundOn: TPlayableSound;
210 FJetSoundOff: TPlayableSound;
211 FJetSoundFly: TPlayableSound;
212 {$ENDIF}
213 FGodMode: Boolean;
214 FNoTarget: Boolean;
215 FNoReload: Boolean;
216 FJustTeleported: Boolean;
217 FNetTime: LongWord;
218 mEDamageType: Integer;
221 function CollideLevel(XInc, YInc: Integer): Boolean;
222 function StayOnStep(XInc, YInc: Integer): Boolean;
223 function HeadInLiquid(XInc, YInc: Integer): Boolean;
224 function BodyInLiquid(XInc, YInc: Integer): Boolean;
225 function BodyInAcid(XInc, YInc: Integer): Boolean;
226 function FullInLift(XInc, YInc: Integer): Integer;
227 {procedure CollideItem();}
228 procedure FlySmoke(Times: DWORD = 1);
229 procedure OnFireFlame(Times: DWORD = 1);
230 function GetAmmoByWeapon(Weapon: Byte): Word;
231 procedure SetAction(Action: Byte; Force: Boolean = False);
232 procedure OnDamage(Angle: SmallInt); virtual;
233 function firediry(): Integer;
234 procedure DoPunch();
236 procedure Run(Direction: TDirection);
237 procedure NextWeapon();
238 procedure PrevWeapon();
239 procedure SeeUp();
240 procedure SeeDown();
241 procedure Fire();
242 procedure Jump();
243 procedure Use();
245 function getNextWeaponIndex (): Byte; // returns 255 for "no switch"
246 procedure resetWeaponQueue ();
247 function hasAmmoForWeapon (weapon: Byte): Boolean;
248 function hasAmmoForShooting (weapon: Byte): Boolean;
249 function shouldSwitch (weapon: Byte; hadWeapon: Boolean) : Boolean;
251 procedure doDamage (v: Integer);
253 function refreshCorpse(): Boolean;
255 public
256 FDamageBuffer: Integer;
258 FAmmo: Array [A_BULLETS..A_HIGH] of Word;
259 FMaxAmmo: Array [A_BULLETS..A_HIGH] of Word;
260 FWeapon: Array [WP_FIRST..WP_LAST] of Boolean;
261 FInventory: Set of R_ITEM_BACKPACK..R_BERSERK;
262 FBerserk: Integer;
263 FPowerups: Array [MR_SUIT..MR_MAX] of DWORD;
264 FReloading: Array [WP_FIRST..WP_LAST] of Word;
265 FTime: Array [T_RESPAWN..T_FLAGCAP] of DWORD;
266 FKeys: Array [KEY_LEFT..KEY_CHAT] of TKeyState;
267 FWeapSwitchMode: Byte;
268 FWeapPreferences: Array [WP_FIRST .. WP_LAST+1] of Byte;
269 FSwitchToEmpty: Byte;
270 FSkipIronFist: Byte;
271 FColor: TRGB;
272 FPreferredTeam: Byte;
273 FSpectator: Boolean;
274 FNoRespawn: Boolean;
275 FWantsInGame: Boolean;
276 FGhost: Boolean;
277 FPhysics: Boolean;
278 FFlaming: Boolean;
279 FJetpack: Boolean;
280 FActualModelName: string;
281 FClientID: SmallInt;
282 FPing: Word;
283 FLoss: Byte;
284 FReady: Boolean;
285 FDummy: Boolean;
286 FFireTime: Integer;
287 FSpawnInvul: Integer;
288 FHandicap: Integer;
289 FWaitForFirstSpawn: Boolean; // set to `true` in server, used to spawn a player on first full state request
290 FCorpse: Integer;
292 // debug: viewport offset
293 viewPortX, viewPortY, viewPortW, viewPortH: Integer;
295 function isValidViewPort (): Boolean; inline;
297 constructor Create(); virtual;
298 destructor Destroy(); override;
299 procedure Respawn(Silent: Boolean; Force: Boolean = False); virtual;
300 function GetRespawnPoint(): Byte;
301 procedure PressKey(Key: Byte; Time: Word = 1);
302 procedure ReleaseKeys();
303 procedure SetModel(ModelName: String);
304 procedure SetColor(Color: TRGB);
305 function GetColor(): TRGB;
306 procedure SetWeapon(W: Byte);
307 function IsKeyPressed(K: Byte): Boolean;
308 function GetKeys(): Byte;
309 function PickItem(ItemType: Byte; arespawn: Boolean; var remove: Boolean): Boolean; virtual;
310 procedure SetWeaponPrefs(Prefs: Array of Byte);
311 procedure SetWeaponPref(Weapon, Pref: Byte);
312 function GetWeaponPref(Weapon: Byte) : Byte;
313 function GetMorePrefered() : Byte;
314 function MaySwitch(Weapon: Byte) : Boolean;
315 function Collide(X, Y: Integer; Width, Height: Word): Boolean; overload;
316 function Collide(Panel: TPanel): Boolean; overload;
317 function Collide(X, Y: Integer): Boolean; overload;
318 procedure SetDirection(Direction: TDirection);
319 procedure GetSecret();
320 function TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean;
321 procedure Touch();
322 procedure Push(vx, vy: Integer);
323 procedure ChangeModel(ModelName: String);
324 procedure SwitchTeam;
325 procedure ChangeTeam(Team: Byte);
326 procedure BFGHit();
327 function GetFlag(Flag: Byte): Boolean;
328 procedure SetFlag(Flag: Byte);
329 function DropFlag(Silent: Boolean = True; DoThrow: Boolean = False): Boolean;
330 function TryDropFlag(): Boolean;
331 procedure TankRamboCheats(Health: Boolean);
332 procedure RestoreHealthArmor();
333 procedure FragCombo();
334 procedure GiveItem(ItemType: Byte);
335 procedure Damage(value: Word; SpawnerUID: Word; vx, vy: Integer; t: Byte); virtual;
336 function Heal(value: Word; Soft: Boolean): Boolean; virtual;
337 procedure MakeBloodVector(Count: Word; VelX, VelY: Integer);
338 procedure MakeBloodSimple(Count: Word);
339 procedure Kill(KillType: Byte; SpawnerUID: Word; t: Byte);
340 procedure Reset(Force: Boolean);
341 procedure Spectate(NoMove: Boolean = False);
342 procedure SwitchNoClip;
343 procedure SoftReset();
344 procedure Draw(); virtual;
345 procedure DrawPain();
346 procedure DrawPickup();
347 procedure DrawOverlay();
348 procedure DrawAim();
349 procedure DrawIndicator(Color: TRGB);
350 procedure DrawBubble();
351 procedure DrawGUI();
352 procedure PreUpdate();
353 procedure Update(); virtual;
354 procedure PreserveState();
355 procedure RestoreState();
356 procedure SaveState (st: TStream); virtual;
357 procedure LoadState (st: TStream); virtual;
358 {$IFDEF ENABLE_SOUND}
359 procedure PauseSounds(Enable: Boolean);
360 {$ENDIF}
361 procedure NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1);
362 procedure DoLerp(Level: Integer = 2);
363 procedure SetLerp(XTo, YTo: Integer);
364 procedure ProcessWeaponAction(Action: Byte);
365 procedure QueueWeaponSwitch(Weapon: Byte);
366 procedure RealizeCurrentWeapon();
367 procedure FlamerOn;
368 procedure FlamerOff;
369 procedure JetpackOn;
370 procedure JetpackOff;
371 procedure CatchFire(Attacker: Word; Timeout: Integer = PLAYER_BURN_TIME);
373 //WARNING! this does nothing for now, but still call it!
374 procedure positionChanged (); //WARNING! call this after entity position was changed, or coldet will not work right!
376 procedure getMapBox (out x, y, w, h: Integer); inline;
377 procedure moveBy (dx, dy: Integer); inline;
379 function getCameraObj(): TObj;
381 public
382 property Vel: TPoint2i read FObj.Vel;
383 property Obj: TObj read FObj;
385 property Name: String read FName write FName;
386 property Model: TPlayerModel read FModel;
387 property Health: Integer read FHealth write FHealth;
388 property Lives: Byte read FLives write FLives;
389 property Armor: Integer read FArmor write FArmor;
390 property Air: Integer read FAir write FAir;
391 property JetFuel: Integer read FJetFuel write FJetFuel;
392 property Frags: Integer read FFrags write FFrags;
393 property Death: Integer read FDeath write FDeath;
394 property Kills: Integer read FKills write FKills;
395 property CurrWeap: Byte read FCurrWeap write FCurrWeap;
396 property WeapSwitchMode: Byte read FWeapSwitchMode write FWeapSwitchMode;
397 property SwitchToEmpty: Byte read FSwitchToEmpty write FSwitchToEmpty;
398 property SkipIronFist: Byte read FSkipIronFist write FSkipIronFist;
399 property MonsterKills: Integer read FMonsterKills write FMonsterKills;
400 property Secrets: Integer read FSecrets;
401 property GodMode: Boolean read FGodMode write FGodMode;
402 property NoTarget: Boolean read FNoTarget write FNoTarget;
403 property NoReload: Boolean read FNoReload write FNoReload;
404 property alive: Boolean read FAlive write FAlive;
405 property Flag: Byte read FFlag;
406 property Team: Byte read FTeam write FTeam;
407 property Direction: TDirection read FDirection;
408 property GameX: Integer read FObj.X write FObj.X;
409 property GameY: Integer read FObj.Y write FObj.Y;
410 property GameVelX: Integer read FObj.Vel.X write FObj.Vel.X;
411 property GameVelY: Integer read FObj.Vel.Y write FObj.Vel.Y;
412 property GameAccelX: Integer read FObj.Accel.X write FObj.Accel.X;
413 property GameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y;
414 property IncCam: Integer read FIncCam write FIncCam;
415 property IncCamOld: Integer read FIncCamOld write FIncCamOld;
416 property SlopeOld: Integer read FSlopeOld write FSlopeOld;
417 property UID: Word read FUID write FUID;
418 property JustTeleported: Boolean read FJustTeleported write FJustTeleported;
419 property NetTime: LongWord read FNetTime write FNetTime;
421 published
422 property eName: String read FName write FName;
423 property eHealth: Integer read FHealth write FHealth;
424 property eLives: Byte read FLives write FLives;
425 property eArmor: Integer read FArmor write FArmor;
426 property eAir: Integer read FAir write FAir;
427 property eJetFuel: Integer read FJetFuel write FJetFuel;
428 property eFrags: Integer read FFrags write FFrags;
429 property eDeath: Integer read FDeath write FDeath;
430 property eKills: Integer read FKills write FKills;
431 property eCurrWeap: Byte read FCurrWeap write FCurrWeap;
432 property eMonsterKills: Integer read FMonsterKills write FMonsterKills;
433 property eSecrets: Integer read FSecrets write FSecrets;
434 property eGodMode: Boolean read FGodMode write FGodMode;
435 property eNoTarget: Boolean read FNoTarget write FNoTarget;
436 property eNoReload: Boolean read FNoReload write FNoReload;
437 property eAlive: Boolean read FAlive write FAlive;
438 property eFlag: Byte read FFlag;
439 property eTeam: Byte read FTeam write FTeam;
440 property eDirection: TDirection read FDirection;
441 property eGameX: Integer read FObj.X write FObj.X;
442 property eGameY: Integer read FObj.Y write FObj.Y;
443 property eGameVelX: Integer read FObj.Vel.X write FObj.Vel.X;
444 property eGameVelY: Integer read FObj.Vel.Y write FObj.Vel.Y;
445 property eGameAccelX: Integer read FObj.Accel.X write FObj.Accel.X;
446 property eGameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y;
447 property eIncCam: Integer read FIncCam write FIncCam;
448 property eUID: Word read FUID;
449 property eJustTeleported: Boolean read FJustTeleported;
450 property eNetTime: LongWord read FNetTime;
452 // set this before assigning something to `eDamage`
453 property eDamageType: Integer read mEDamageType write mEDamageType;
454 property eDamage: Integer write doDamage;
455 end;
457 TDifficult = record
458 public
459 DiagFire: Byte;
460 InvisFire: Byte;
461 DiagPrecision: Byte;
462 FlyPrecision: Byte;
463 Cover: Byte;
464 CloseJump: Byte;
465 WeaponPrior: packed array [WP_FIRST..WP_LAST] of Byte;
466 CloseWeaponPrior: packed array [WP_FIRST..WP_LAST] of Byte;
467 //SafeWeaponPrior: Array [WP_FIRST..WP_LAST] of Byte;
469 public
470 procedure save (st: TStream);
471 procedure load (st: TStream);
472 end;
474 TAIFlag = record
475 Name: String;
476 Value: String;
477 end;
479 TBot = class(TPlayer)
480 private
481 FSelectedWeapon: Byte;
482 FTargetUID: Word;
483 FLastVisible: DWORD;
484 FAIFlags: Array of TAIFlag;
485 FDifficult: TDifficult;
487 function GetRnd(a: Byte): Boolean;
488 function GetInterval(a: Byte; radius: SmallInt): SmallInt;
489 function RunDirection(): TDirection;
490 function FullInStep(XInc, YInc: Integer): Boolean;
491 //function NeedItem(Item: Byte): Byte;
492 procedure SelectWeapon(Dist: Integer);
493 procedure SetAIFlag(const aName, fValue: String);
494 function GetAIFlag(const aName: String): String;
495 procedure RemoveAIFlag(const aName: String);
496 function Healthy(): Byte;
497 procedure UpdateMove();
498 procedure UpdateCombat();
499 function KeyPressed(Key: Word): Boolean;
500 procedure ReleaseKey(Key: Byte);
501 function TargetOnScreen(TX, TY: Integer): Boolean;
502 procedure OnDamage(Angle: SmallInt); override;
504 public
505 procedure Respawn(Silent: Boolean; Force: Boolean = False); override;
506 constructor Create(); override;
507 destructor Destroy(); override;
508 procedure Draw(); override;
509 function PickItem(ItemType: Byte; force: Boolean; var remove: Boolean): Boolean; override;
510 function Heal(value: Word; Soft: Boolean): Boolean; override;
511 procedure Update(); override;
512 procedure SaveState (st: TStream); override;
513 procedure LoadState (st: TStream); override;
514 end;
516 PGib = ^TGib;
517 TGib = record
518 alive: Boolean;
519 ID: DWORD;
520 MaskID: DWORD;
521 RAngle: Integer;
522 Color: TRGB;
523 Obj: TObj;
525 procedure getMapBox (out x, y, w, h: Integer); inline;
526 procedure moveBy (dx, dy: Integer); inline;
528 procedure positionChanged (); inline; //WARNING! call this after entity position was changed, or coldet will not work right!
529 end;
532 PShell = ^TShell;
533 TShell = record
534 SpriteID: DWORD;
535 alive: Boolean;
536 SType: Byte;
537 RAngle: Integer;
538 Timeout: Cardinal;
539 CX, CY: Integer;
540 Obj: TObj;
542 procedure getMapBox (out x, y, w, h: Integer); inline;
543 procedure moveBy (dx, dy: Integer); inline;
545 procedure positionChanged (); inline; //WARNING! call this after entity position was changed, or coldet will not work right!
546 end;
548 TCorpse = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
549 private
550 FModelName: String;
551 FMess: Boolean;
552 FState: Byte;
553 FDamage: Byte;
554 FColor: TRGB;
555 FObj: TObj;
556 FPlayerUID: Word;
557 FAnimation: TAnimation;
558 FAnimationMask: TAnimation;
560 public
561 constructor Create(X, Y: Integer; ModelName: String; aMess: Boolean);
562 destructor Destroy(); override;
563 procedure Damage(Value: Word; SpawnerUID: Word; vx, vy: Integer);
564 procedure Update();
565 procedure Draw();
566 procedure SaveState (st: TStream);
567 procedure LoadState (st: TStream);
569 procedure getMapBox (out x, y, w, h: Integer); inline;
570 procedure moveBy (dx, dy: Integer); inline;
572 procedure positionChanged (); inline; //WARNING! call this after entity position was changed, or coldet will not work right!
574 function ObjPtr (): PObj; inline;
576 property Obj: TObj read FObj; // copies object
577 property State: Byte read FState;
578 property Mess: Boolean read FMess;
579 end;
581 TTeamStat = Array [TEAM_RED..TEAM_BLUE] of
582 record
583 Score: SmallInt;
584 end;
587 gPlayers: Array of TPlayer;
588 gCorpses: Array of TCorpse;
589 gGibs: Array of TGib;
590 gShells: Array of TShell;
591 gTeamStat: TTeamStat;
592 gFly: Boolean;
593 gAimLine: Boolean;
594 gChatBubble: Integer;
595 gPlayerIndicator: Integer = 1;
596 gPlayerIndicatorStyle: Integer;
597 gNumBots: Word;
598 gSpectLatchPID1: Word;
599 gSpectLatchPID2: Word;
600 MAX_RUNVEL: Integer = 8;
601 VEL_JUMP: Integer = 10;
602 SHELL_TIMEOUT: Cardinal = 60000;
604 function Lerp(X, Y, Factor: Integer): Integer;
606 procedure g_Gibs_SetMax(Count: Word);
607 function g_Gibs_GetMax(): Word;
608 procedure g_Corpses_SetMax(Count: Word);
609 function g_Corpses_GetMax(): Word;
610 procedure g_Force_Model_Set(Mode: Word);
611 function g_Force_Model_Get(): Word;
612 procedure g_Forced_Model_SetName(Model: String);
613 function g_Forced_Model_GetName(): String;
614 procedure g_Shells_SetMax(Count: Word);
615 function g_Shells_GetMax(): Word;
617 procedure g_Player_Init();
618 procedure g_Player_Free();
619 function g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boolean): Word;
620 function g_Player_CreateFromState (st: TStream): Word;
621 procedure g_Player_Remove(UID: Word);
622 procedure g_Player_ResetTeams();
623 procedure g_Player_PreUpdate();
624 procedure g_Player_UpdateAll();
625 procedure g_Player_DrawAll();
626 procedure g_Player_DrawDebug(p: TPlayer);
627 procedure g_Player_DrawHealth();
628 procedure g_Player_RememberAll();
629 procedure g_Player_ResetAll(Force, Silent: Boolean);
630 function g_Player_Get(UID: Word): TPlayer;
631 function g_Player_GetCount(): Byte;
632 function g_Player_GetStats(): TPlayerStatArray;
633 function g_Player_ExistingName(Name: String): Boolean;
634 function g_Player_CreateCorpse(Player: TPlayer): Integer;
635 procedure g_Player_CreateGibs(fX, fY: Integer; ModelName: String; fColor: TRGB);
636 procedure g_Player_CreateShell(fX, fY, dX, dY: Integer; T: Byte);
637 procedure g_Player_UpdatePhysicalObjects();
638 procedure g_Player_DrawCorpses();
639 procedure g_Player_DrawShells();
640 procedure g_Player_RemoveAllCorpses();
641 procedure g_Player_Corpses_SaveState (st: TStream);
642 procedure g_Player_Corpses_LoadState (st: TStream);
643 procedure g_Player_ResetReady();
644 procedure g_Bot_Add(Team, Difficult: Byte; Handicap: Integer = 100);
645 procedure g_Bot_AddList(Team: Byte; lname: ShortString; num: Integer = -1; Handicap: Integer = 100);
646 procedure g_Bot_MixNames();
647 procedure g_Bot_RemoveAll();
648 function g_Bot_GetCount(): Integer;
650 implementation
652 uses
653 {$INCLUDE ../nogl/noGLuses.inc}
654 {$IFDEF ENABLE_HOLMES}
655 g_holmes,
656 {$ENDIF}
657 e_log, g_map, g_items, g_console, g_gfx, Math,
658 g_options, g_triggers, g_menu, g_game, g_grid, e_res,
659 wadreader, g_main, g_monsters, CONFIG, g_language,
660 g_net, g_netmsg, g_window,
661 utils, xstreams;
663 const PLR_SAVE_VERSION = 0;
665 type
666 TBotProfile = record
667 name: ShortString;
668 model: ShortString;
669 team: Byte;
670 color: TRGB;
671 diag_fire: Byte;
672 invis_fire: Byte;
673 diag_precision: Byte;
674 fly_precision: Byte;
675 cover: Byte;
676 close_jump: Byte;
677 w_prior1: Array [WP_FIRST..WP_LAST] of Byte;
678 w_prior2: Array [WP_FIRST..WP_LAST] of Byte;
679 w_prior3: Array [WP_FIRST..WP_LAST] of Byte;
680 end;
682 const
683 TIME_RESPAWN1 = 1500;
684 TIME_RESPAWN2 = 2000;
685 TIME_RESPAWN3 = 3000;
686 AIR_DEF = 360;
687 AIR_MAX = 1091;
688 JET_MAX = 540; // ~30 sec
689 PLAYER_SUIT_TIME = 30000;
690 PLAYER_INVUL_TIME = 30000;
691 PLAYER_INVIS_TIME = 35000;
692 FRAG_COMBO_TIME = 3000;
693 VEL_SW = 4;
694 VEL_FLY = 6;
695 ANGLE_RIGHTUP = 55;
696 ANGLE_RIGHTDOWN = -35;
697 ANGLE_LEFTUP = 125;
698 ANGLE_LEFTDOWN = -145;
699 PLAYER_HEADRECT: TRectWH = (X:24; Y:12; Width:20; Height:12);
700 WEAPONPOINT: Array [TDirection] of TDFPoint = ((X:16; Y:32), (X:47; Y:32));
701 BOT_MAXJUMP = 84;
702 BOT_LONGDIST = 300;
703 BOT_UNSAFEDIST = 128;
704 TEAMCOLOR: Array [TEAM_RED..TEAM_BLUE] of TRGB = ((R:255; G:0; B:0),
705 (R:0; G:0; B:255));
706 DIFFICULT_EASY: TDifficult = (DiagFire: 32; InvisFire: 32; DiagPrecision: 32;
707 FlyPrecision: 32; Cover: 32; CloseJump: 32;
708 WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0));
709 DIFFICULT_MEDIUM: TDifficult = (DiagFire: 127; InvisFire: 127; DiagPrecision: 127;
710 FlyPrecision: 127; Cover: 127; CloseJump: 127;
711 WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0));
712 DIFFICULT_HARD: TDifficult = (DiagFire: 255; InvisFire: 255; DiagPrecision: 255;
713 FlyPrecision: 255; Cover: 255; CloseJump: 255;
714 WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0));
715 WEAPON_PRIOR1: Array [WP_FIRST..WP_LAST] of Byte =
716 (WEAPON_FLAMETHROWER, WEAPON_SUPERCHAINGUN,
717 WEAPON_SHOTGUN2, WEAPON_SHOTGUN1,
718 WEAPON_CHAINGUN, WEAPON_PLASMA, WEAPON_ROCKETLAUNCHER,
719 WEAPON_BFG, WEAPON_PISTOL, WEAPON_SAW, WEAPON_IRONFIST);
720 WEAPON_PRIOR2: Array [WP_FIRST..WP_LAST] of Byte =
721 (WEAPON_FLAMETHROWER, WEAPON_SUPERCHAINGUN,
722 WEAPON_BFG, WEAPON_ROCKETLAUNCHER,
723 WEAPON_SHOTGUN2, WEAPON_PLASMA, WEAPON_SHOTGUN1,
724 WEAPON_CHAINGUN, WEAPON_PISTOL, WEAPON_SAW, WEAPON_IRONFIST);
725 //WEAPON_PRIOR3: Array [WP_FIRST..WP_LAST] of Byte =
726 // (WEAPON_FLAMETHROWER, WEAPON_SUPERCHAINGUN,
727 // WEAPON_BFG, WEAPON_PLASMA, WEAPON_SHOTGUN2,
728 // WEAPON_CHAINGUN, WEAPON_SHOTGUN1, WEAPON_SAW,
729 // WEAPON_ROCKETLAUNCHER, WEAPON_PISTOL, WEAPON_IRONFIST);
730 WEAPON_RELOAD: Array [WP_FIRST..WP_LAST] of Byte =
731 (5, 2, 6, 18, 36, 2, 12, 2, 14, 2, 2);
733 PLAYER_SIGNATURE = $52594C50; // 'PLYR'
734 CORPSE_SIGNATURE = $50524F43; // 'CORP'
736 BOTNAMES_FILENAME = 'botnames.txt';
737 BOTLIST_FILENAME = 'botlist.txt';
740 MaxGibs: Word = 150;
741 MaxCorpses: Word = 20;
742 MaxShells: Word = 300;
743 ForceModel: Word = 0;
744 ForcedModelName: String = STD_PLAYER_MODEL;
745 CurrentGib: Integer = 0;
746 CurrentShell: Integer = 0;
747 BotNames: Array of String;
748 BotList: Array of TBotProfile;
749 SavedStates: Array of TPlayerSavedState;
752 function Lerp(X, Y, Factor: Integer): Integer;
753 begin
754 Result := X + ((Y - X) div Factor);
755 end;
757 function SameTeam(UID1, UID2: Word): Boolean;
758 begin
759 Result := False;
761 if (UID1 > UID_MAX_PLAYER) or (UID1 <= UID_MAX_GAME) or
762 (UID2 > UID_MAX_PLAYER) or (UID2 <= UID_MAX_GAME) then Exit;
764 if (g_Player_Get(UID1) = nil) or (g_Player_Get(UID2) = nil) then Exit;
766 if ((g_Player_Get(UID1).Team = TEAM_NONE) or
767 (g_Player_Get(UID2).Team = TEAM_NONE)) then Exit;
769 Result := g_Player_Get(UID1).FTeam = g_Player_Get(UID2).FTeam;
770 end;
772 procedure g_Gibs_SetMax(Count: Word);
773 begin
774 MaxGibs := Count;
775 SetLength(gGibs, Count);
777 if CurrentGib >= Count then
778 CurrentGib := 0;
779 end;
781 function g_Gibs_GetMax(): Word;
782 begin
783 Result := MaxGibs;
784 end;
786 procedure g_Shells_SetMax(Count: Word);
787 begin
788 MaxShells := Count;
789 SetLength(gShells, Count);
791 if CurrentShell >= Count then
792 CurrentShell := 0;
793 end;
795 function g_Shells_GetMax(): Word;
796 begin
797 Result := MaxShells;
798 end;
801 procedure g_Corpses_SetMax(Count: Word);
802 begin
803 MaxCorpses := Count;
804 SetLength(gCorpses, Count);
805 end;
807 function g_Corpses_GetMax(): Word;
808 begin
809 Result := MaxCorpses;
810 end;
812 procedure g_Force_Model_Set(Mode: Word);
813 begin
814 ForceModel := Mode;
815 end;
817 function g_Force_Model_Get(): Word;
818 begin
819 Result := ForceModel;
820 end;
822 procedure g_Forced_Model_SetName(Model: String);
823 begin
824 ForcedModelName := Model;
825 end;
827 function g_Forced_Model_GetName(): String;
828 begin
829 Result := ForcedModelName;
830 end;
832 function g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boolean): Word;
834 a: Integer;
835 ok: Boolean = False;
836 begin
837 Result := 0;
839 // Åñòü ëè ìåñòî â gPlayers:
840 for a := 0 to High(gPlayers) do
841 if gPlayers[a] = nil then
842 begin
843 ok := True;
844 Break;
845 end;
847 // Íåò ìåñòà - ðàñøèðÿåì gPlayers:
848 if not ok then
849 begin
850 SetLength(gPlayers, Length(gPlayers)+1);
851 a := High(gPlayers);
852 end;
854 // Ñîçäàåì îáúåêò èãðîêà:
855 if Bot
856 then gPlayers[a] := TBot.Create()
857 else gPlayers[a] := TPlayer.Create();
859 gPlayers[a].FActualModelName := ModelName;
860 gPlayers[a].SetModel(ModelName);
861 if Bot and (g_Force_Model_Get() <> 0) then
862 gPlayers[a].SetModel(g_Forced_Model_GetName());
864 // Íåò ìîäåëè - ñîçäàíèå íåâîçìîæíî:
865 if gPlayers[a].FModel = nil then
866 begin
867 FreeAndNil(gPlayers[a]);
868 g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], [ModelName]));
869 Exit;
870 end;
872 if not (Team in [TEAM_RED, TEAM_BLUE]) then
873 if Random(2) = 0
874 then Team := TEAM_RED
875 else Team := TEAM_BLUE;
876 gPlayers[a].FPreferredTeam := Team;
878 case gGameSettings.GameMode of
879 GM_DM: gPlayers[a].FTeam := TEAM_NONE;
880 GM_TDM, GM_CTF: gPlayers[a].FTeam := gPlayers[a].FPreferredTeam;
881 GM_SINGLE, GM_COOP: gPlayers[a].FTeam := TEAM_COOP;
882 end;
884 // Åñëè êîìàíäíàÿ èãðà - êðàñèì ìîäåëü â öâåò êîìàíäû:
885 gPlayers[a].FColor := Color;
886 if gPlayers[a].FTeam in [TEAM_RED, TEAM_BLUE]
887 then gPlayers[a].FModel.Color := TEAMCOLOR[gPlayers[a].FTeam]
888 else gPlayers[a].FModel.Color := Color;
890 gPlayers[a].FUID := g_CreateUID(UID_PLAYER);
891 gPlayers[a].FAlive := False;
893 Result := gPlayers[a].FUID;
894 end;
896 function g_Player_CreateFromState (st: TStream): Word;
898 i: SizeInt;
899 ok, Bot: Boolean;
900 pos: Int64;
901 begin
902 Assert(st <> nil);
904 // check signature and entity type
905 pos := st.Position;
906 if not utils.checkSign(st, 'PLYR') then
907 Raise XStreamError.Create('invalid player signature');
908 if st.ReadByte() <> PLR_SAVE_VERSION then
909 Raise XStreamError.Create('invalid player version');
911 Bot := st.ReadBool();
912 st.Position := pos;
914 // find free player slot
915 ok := False;
916 for i := 0 to High(gPlayers) do
917 if gPlayers[i] = nil then
918 begin
919 ok := True;
920 break;
921 end;
923 // allocate player slot
924 if not ok then
925 begin
926 SetLength(gPlayers, Length(gPlayers)+1);
927 i := High(gPlayers);
928 end;
930 // create entity and load state
931 if Bot then
932 begin
933 gPlayers[i] := TBot.Create();
934 if g_Force_Model_Get() <> 0 then
935 gPlayers[i].SetModel(g_Forced_Model_GetName());
937 else
938 gPlayers[i] := TPlayer.Create();
940 gPlayers[i].FPhysics := True; // ???
941 gPlayers[i].LoadState(st);
943 Result := gPlayers[i].FUID;
944 end;
947 procedure g_Player_ResetTeams();
949 a: Integer;
950 begin
951 if g_Game_IsClient then
952 Exit;
953 if gPlayers = nil then
954 Exit;
955 for a := Low(gPlayers) to High(gPlayers) do
956 if gPlayers[a] <> nil then
957 case gGameSettings.GameMode of
958 GM_DM:
959 gPlayers[a].ChangeTeam(TEAM_NONE);
960 GM_TDM, GM_CTF:
961 if not (gPlayers[a].Team in [TEAM_RED, TEAM_BLUE]) then
962 if gPlayers[a].FPreferredTeam in [TEAM_RED, TEAM_BLUE] then
963 gPlayers[a].ChangeTeam(gPlayers[a].FPreferredTeam)
964 else
965 if a mod 2 = 0
966 then gPlayers[a].ChangeTeam(TEAM_RED)
967 else gPlayers[a].ChangeTeam(TEAM_BLUE);
968 GM_SINGLE,
969 GM_COOP:
970 gPlayers[a].ChangeTeam(TEAM_COOP);
971 end;
972 end;
974 procedure g_Bot_Add(Team, Difficult: Byte; Handicap: Integer = 100);
976 m: SSArray;
977 _name, _model: String;
978 a, tr, tb: Integer;
979 begin
980 if not g_Game_IsServer then Exit;
982 // Íå äîáàâëÿåì áîòîâ åñëè ëèìèò óæå äîñòèãíóò
983 if (g_Bot_GetCount() >= gMaxBots) then Exit;
985 // Ñïèñîê íàçâàíèé ìîäåëåé:
986 m := g_PlayerModel_GetNames();
987 if m = nil then
988 Exit;
990 // Êîìàíäà:
991 if (gGameSettings.GameType = GT_SINGLE) or (gGameSettings.GameMode = GM_COOP) then
992 Team := TEAM_COOP // COOP
993 else
994 if gGameSettings.GameMode = GM_DM then
995 Team := TEAM_NONE // DM
996 else
997 if Team = TEAM_NONE then // CTF / TDM
998 begin
999 // Àâòîáàëàíñ êîìàíä:
1000 tr := 0;
1001 tb := 0;
1003 for a := 0 to High(gPlayers) do
1004 if gPlayers[a] <> nil then
1005 begin
1006 if gPlayers[a].Team = TEAM_RED then
1007 Inc(tr)
1008 else
1009 if gPlayers[a].Team = TEAM_BLUE then
1010 Inc(tb);
1011 end;
1013 if tr > tb then
1014 Team := TEAM_BLUE
1015 else
1016 if tb > tr then
1017 Team := TEAM_RED
1018 else // tr = tb
1019 if Random(2) = 0 then
1020 Team := TEAM_RED
1021 else
1022 Team := TEAM_BLUE;
1023 end;
1025 // Âûáèðàåì áîòó èìÿ:
1026 _name := '';
1027 if BotNames <> nil then
1028 for a := 0 to High(BotNames) do
1029 if g_Player_ExistingName(BotNames[a]) then
1030 begin
1031 _name := BotNames[a];
1032 Break;
1033 end;
1035 // Âûáèðàåì ñëó÷àéíóþ ìîäåëü:
1036 _model := m[Random(Length(m))];
1038 // Ñîçäàåì áîòà:
1039 with g_Player_Get(g_Player_Create(_model,
1040 _RGB(Min(Random(9)*32, 255),
1041 Min(Random(9)*32, 255),
1042 Min(Random(9)*32, 255)),
1043 Team, True)) as TBot do
1044 begin
1045 // Åñëè èìåíè íåò, äåëàåì åãî èç UID áîòà
1046 if _name = '' then
1047 Name := Format('DFBOT%.5d', [UID])
1048 else
1049 Name := _name;
1051 case Difficult of
1052 1: FDifficult := DIFFICULT_EASY;
1053 2: FDifficult := DIFFICULT_MEDIUM;
1054 else FDifficult := DIFFICULT_HARD;
1055 end;
1057 for a := WP_FIRST to WP_LAST do
1058 begin
1059 FDifficult.WeaponPrior[a] := WEAPON_PRIOR1[a];
1060 FDifficult.CloseWeaponPrior[a] := WEAPON_PRIOR2[a];
1061 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
1062 end;
1064 FHandicap := Handicap;
1066 g_Console_Add(Format(_lc[I_PLAYER_JOIN], [Name]), True);
1068 if g_Game_IsNet then MH_SEND_PlayerCreate(UID);
1069 if g_Game_IsServer and (gGameSettings.MaxLives > 0) then
1070 Spectate();
1071 end;
1072 end;
1074 procedure g_Bot_AddList(Team: Byte; lName: ShortString; num: Integer = -1; Handicap: Integer = 100);
1076 m: SSArray;
1077 _name, _model: String;
1078 a: Integer;
1079 begin
1080 if not g_Game_IsServer then Exit;
1082 // Íå äîáàâëÿåì áîòîâ åñëè ëèìèò óæå äîñòèãíóò
1083 if (g_Bot_GetCount() >= gMaxBots) then Exit;
1085 // Ñïèñîê íàçâàíèé ìîäåëåé:
1086 m := g_PlayerModel_GetNames();
1087 if m = nil then
1088 Exit;
1090 // Êîìàíäà:
1091 if (gGameSettings.GameType = GT_SINGLE) or (gGameSettings.GameMode = GM_COOP) then
1092 Team := TEAM_COOP // COOP
1093 else
1094 if gGameSettings.GameMode = GM_DM then
1095 Team := TEAM_NONE // DM
1096 else
1097 if Team = TEAM_NONE then
1098 Team := BotList[num].team; // CTF / TDM
1100 // Âûáèðàåì íàñòðîéêè áîòà èç ñïèñêà ïî íîìåðó èëè èìåíè:
1101 if lName = '' then
1102 num := Random(Length(BotList))
1103 else
1104 begin
1105 if (num < 0) or (num > Length(BotList)-1) then
1106 num := -1;
1107 if (num = -1) and (BotList <> nil) then
1108 lName := AnsiLowerCase(lName);
1109 for a := 0 to High(BotList) do
1110 if AnsiLowerCase(BotList[a].name) = lName then
1111 begin
1112 num := a;
1113 Break;
1114 end;
1115 if num = -1 then
1116 Exit;
1117 end;
1119 // Èìÿ áîòà:
1120 _name := BotList[num].name;
1121 if (_name = '') and (BotNames <> nil) then
1122 for a := 0 to High(BotNames) do
1123 if g_Player_ExistingName(BotNames[a]) then
1124 begin
1125 _name := BotNames[a];
1126 Break;
1127 end;
1129 // Ìîäåëü:
1130 _model := BotList[num].model;
1131 // Íåò òàêîé - âûáèðàåì ñëó÷àéíóþ:
1132 if not InSArray(_model, m) then
1133 _model := m[Random(Length(m))];
1135 // Ñîçäàåì áîòà:
1136 with g_Player_Get(g_Player_Create(_model, BotList[num].color, Team, True)) as TBot do
1137 begin
1138 // Åñëè èìåíè íåò, äåëàåì åãî èç UID áîòà
1139 if _name = ''
1140 then Name := Format('DFBOT%.5d', [UID])
1141 else Name := _name;
1143 FDifficult.DiagFire := BotList[num].diag_fire;
1144 FDifficult.InvisFire := BotList[num].invis_fire;
1145 FDifficult.DiagPrecision := BotList[num].diag_precision;
1146 FDifficult.FlyPrecision := BotList[num].fly_precision;
1147 FDifficult.Cover := BotList[num].cover;
1148 FDifficult.CloseJump := BotList[num].close_jump;
1150 FHandicap := Handicap;
1152 for a := WP_FIRST to WP_LAST do
1153 begin
1154 FDifficult.WeaponPrior[a] := BotList[num].w_prior1[a];
1155 FDifficult.CloseWeaponPrior[a] := BotList[num].w_prior2[a];
1156 //FDifficult.SafeWeaponPrior[a] := BotList[num].w_prior3[a];
1157 end;
1159 g_Console_Add(Format(_lc[I_PLAYER_JOIN], [Name]), True);
1161 if g_Game_IsNet then MH_SEND_PlayerCreate(UID);
1162 end;
1163 end;
1165 procedure g_Bot_RemoveAll();
1167 a: Integer;
1168 begin
1169 if not g_Game_IsServer then Exit;
1170 if gPlayers = nil then Exit;
1172 for a := 0 to High(gPlayers) do
1173 if gPlayers[a] <> nil then
1174 if gPlayers[a] is TBot then
1175 begin
1176 gPlayers[a].Lives := 0;
1177 gPlayers[a].Kill(K_SIMPLEKILL, 0, HIT_DISCON);
1178 g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [gPlayers[a].Name]), True);
1179 g_Player_Remove(gPlayers[a].FUID);
1180 end;
1182 g_Bot_MixNames();
1183 end;
1185 procedure g_Bot_MixNames();
1187 s: String;
1188 a, b: Integer;
1189 begin
1190 if BotNames <> nil then
1191 for a := 0 to High(BotNames) do
1192 begin
1193 b := Random(Length(BotNames));
1194 s := BotNames[a];
1195 Botnames[a] := BotNames[b];
1196 BotNames[b] := s;
1197 end;
1198 end;
1200 procedure g_Player_Remove(UID: Word);
1202 i: Integer;
1203 begin
1204 if gPlayers = nil then Exit;
1206 if g_Game_IsServer and g_Game_IsNet then
1207 MH_SEND_PlayerDelete(UID);
1209 for i := 0 to High(gPlayers) do
1210 if gPlayers[i] <> nil then
1211 if gPlayers[i].FUID = UID then
1212 begin
1213 if gPlayers[i] is TPlayer then
1214 TPlayer(gPlayers[i]).Free()
1215 else
1216 TBot(gPlayers[i]).Free();
1217 gPlayers[i] := nil;
1218 Exit;
1219 end;
1220 end;
1222 procedure g_Player_Init();
1224 F: TextFile;
1225 s: String;
1226 a, b: Integer;
1227 config: TConfig;
1228 sa: SSArray;
1229 path: AnsiString;
1230 begin
1231 BotNames := nil;
1232 BotList := nil;
1234 path := BOTNAMES_FILENAME;
1235 if e_FindResource(DataDirs, path) then
1236 begin
1237 // ×èòàåì âîçìîæíûå èìåíà áîòîâ èç ôàéëà:
1238 AssignFile(F, path);
1239 Reset(F);
1241 while not EOF(F) do
1242 begin
1243 ReadLn(F, s);
1245 s := Trim(s);
1246 if s = '' then
1247 Continue;
1249 SetLength(BotNames, Length(BotNames)+1);
1250 BotNames[High(BotNames)] := s;
1251 end;
1253 CloseFile(F);
1255 // Ïåðåìåøèâàåì èõ:
1256 g_Bot_MixNames();
1257 end;
1259 path := BOTLIST_FILENAME;
1260 if e_FindResource(DataDirs, path) then
1261 begin
1262 // ×èòàåì ôàéë ñ ïàðàìåòðàìè áîòîâ:
1263 config := TConfig.CreateFile(path);
1264 a := 0;
1266 while config.SectionExists(IntToStr(a)) do
1267 begin
1268 SetLength(BotList, Length(BotList)+1);
1270 with BotList[High(BotList)] do
1271 begin
1272 name := config.ReadStr(IntToStr(a), 'name', ''); // Èìÿ áîòà
1273 model := config.ReadStr(IntToStr(a), 'model', ''); // Ìîäåëü
1275 // Êîìàíäà
1276 s := config.ReadStr(IntToStr(a), 'team', '');
1277 if s = 'red' then
1278 team := TEAM_RED
1279 else if s = 'blue' then
1280 team := TEAM_BLUE
1281 else
1282 team := TEAM_NONE;
1284 // Öâåò ìîäåëè
1285 sa := parse(config.ReadStr(IntToStr(a), 'color', ''));
1286 SetLength(sa, 3);
1287 color.R := StrToIntDef(sa[0], 0);
1288 color.G := StrToIntDef(sa[1], 0);
1289 color.B := StrToIntDef(sa[2], 0);
1291 diag_fire := config.ReadInt(IntToStr(a), 'diag_fire', 0); // Âåðîÿòíîñòü ñòðåëüáû ïîä óãëîì
1292 invis_fire := config.ReadInt(IntToStr(a), 'invis_fire', 0); // Âåðîÿòíîñòü îòâåòíîãî îãíÿ ïî íåâèäèìîìó ñîïåðíèêó
1293 diag_precision := config.ReadInt(IntToStr(a), 'diag_precision', 0); // Òî÷íîñòü ñòðåëüáû ïîä óãëîì
1294 fly_precision := config.ReadInt(IntToStr(a), 'fly_precision', 0); // Òî÷íîñòü ñòðåëüáû â ïîëåòå
1295 cover := config.ReadInt(IntToStr(a), 'cover', 0); // Òî÷íîñòü óêëîíåíèÿ îò ñíàðÿäîâ
1296 close_jump := config.ReadInt(IntToStr(a), 'close_jump', 0); // Âåðîÿòíîñòü ïðûæêà ïðè ïðèáëèæåíèè ñîïåðíèêà
1298 // Ïðèîðèòåòû îðóæèÿ äëÿ äàëüíåãî áîÿ
1299 sa := parse(config.ReadStr(IntToStr(a), 'w_prior1', ''));
1300 if Length(sa) = 10 then
1301 for b := 0 to 9 do
1302 w_prior1[b] := EnsureRange(StrToInt(sa[b]), 0, 9);
1304 // Ïðèîðèòåòû îðóæèÿ äëÿ áëèæíåãî áîÿ
1305 sa := parse(config.ReadStr(IntToStr(a), 'w_prior2', ''));
1306 if Length(sa) = 10 then
1307 for b := 0 to 9 do
1308 w_prior2[b] := EnsureRange(StrToInt(sa[b]), 0, 9);
1310 {sa := parse(config.ReadStr(IntToStr(a), 'w_prior3', ''));
1311 if Length(sa) = 10 then
1312 for b := 0 to 9 do
1313 w_prior3[b] := EnsureRange(StrToInt(sa[b]), 0, 9);}
1314 end;
1316 a += 1;
1317 end;
1319 config.Free();
1320 end;
1322 SetLength(SavedStates, 0);
1323 end;
1325 procedure g_Player_Free();
1327 i: Integer;
1328 begin
1329 if gPlayers <> nil then
1330 begin
1331 for i := 0 to High(gPlayers) do
1332 if gPlayers[i] <> nil then
1333 begin
1334 if gPlayers[i] is TPlayer then
1335 TPlayer(gPlayers[i]).Free()
1336 else
1337 TBot(gPlayers[i]).Free();
1338 gPlayers[i] := nil;
1339 end;
1341 gPlayers := nil;
1342 end;
1344 gPlayer1 := nil;
1345 gPlayer2 := nil;
1346 SetLength(SavedStates, 0);
1347 end;
1349 procedure g_Player_PreUpdate();
1351 i: Integer;
1352 begin
1353 if gPlayers = nil then Exit;
1354 for i := 0 to High(gPlayers) do
1355 if gPlayers[i] <> nil then
1356 gPlayers[i].PreUpdate();
1357 end;
1359 procedure g_Player_UpdateAll();
1361 i: Integer;
1362 begin
1363 if gPlayers = nil then Exit;
1365 //e_WriteLog('***g_Player_UpdateAll: ENTER', MSG_WARNING);
1366 for i := 0 to High(gPlayers) do
1367 begin
1368 if gPlayers[i] <> nil then
1369 begin
1370 if gPlayers[i] is TPlayer then
1371 begin
1372 gPlayers[i].Update();
1373 gPlayers[i].RealizeCurrentWeapon(); // WARNING! DO NOT MOVE THIS INTO `Update()`!
1375 else
1376 begin
1377 // bot updates weapons in `UpdateCombat()`
1378 TBot(gPlayers[i]).Update();
1379 end;
1380 end;
1381 end;
1382 //e_WriteLog('***g_Player_UpdateAll: EXIT', MSG_WARNING);
1383 end;
1385 procedure g_Player_DrawAll();
1387 i: Integer;
1388 begin
1389 if gPlayers = nil then Exit;
1391 for i := 0 to High(gPlayers) do
1392 if gPlayers[i] <> nil then
1393 if gPlayers[i] is TPlayer then gPlayers[i].Draw()
1394 else TBot(gPlayers[i]).Draw();
1395 end;
1397 procedure g_Player_DrawDebug(p: TPlayer);
1399 fW, fH: Byte;
1400 begin
1401 if p = nil then Exit;
1402 if (@p.FObj) = nil then Exit;
1404 e_TextureFontGetSize(gStdFont, fW, fH);
1406 e_TextureFontPrint(0, 0 , 'Pos X: ' + IntToStr(p.FObj.X), gStdFont);
1407 e_TextureFontPrint(0, fH , 'Pos Y: ' + IntToStr(p.FObj.Y), gStdFont);
1408 e_TextureFontPrint(0, fH * 2, 'Vel X: ' + IntToStr(p.FObj.Vel.X), gStdFont);
1409 e_TextureFontPrint(0, fH * 3, 'Vel Y: ' + IntToStr(p.FObj.Vel.Y), gStdFont);
1410 e_TextureFontPrint(0, fH * 4, 'Acc X: ' + IntToStr(p.FObj.Accel.X), gStdFont);
1411 e_TextureFontPrint(0, fH * 5, 'Acc Y: ' + IntToStr(p.FObj.Accel.Y), gStdFont);
1412 e_TextureFontPrint(0, fH * 6, 'Old X: ' + IntToStr(p.FObj.oldX), gStdFont);
1413 e_TextureFontPrint(0, fH * 7, 'Old Y: ' + IntToStr(p.FObj.oldY), gStdFont);
1414 end;
1416 procedure g_Player_DrawHealth();
1418 i: Integer;
1419 fW, fH: Byte;
1420 begin
1421 if gPlayers = nil then Exit;
1422 e_TextureFontGetSize(gStdFont, fW, fH);
1424 for i := 0 to High(gPlayers) do
1425 if gPlayers[i] <> nil then
1426 begin
1427 e_TextureFontPrint(gPlayers[i].FObj.X + gPlayers[i].FObj.Rect.X,
1428 gPlayers[i].FObj.Y + gPlayers[i].FObj.Rect.Y + gPlayers[i].FObj.Rect.Height - fH * 2,
1429 IntToStr(gPlayers[i].FHealth), gStdFont);
1430 e_TextureFontPrint(gPlayers[i].FObj.X + gPlayers[i].FObj.Rect.X,
1431 gPlayers[i].FObj.Y + gPlayers[i].FObj.Rect.Y + gPlayers[i].FObj.Rect.Height - fH,
1432 IntToStr(gPlayers[i].FArmor), gStdFont);
1433 end;
1434 end;
1436 function g_Player_Get(UID: Word): TPlayer;
1438 a: Integer;
1439 begin
1440 Result := nil;
1442 if gPlayers = nil then
1443 Exit;
1445 for a := 0 to High(gPlayers) do
1446 if gPlayers[a] <> nil then
1447 if gPlayers[a].FUID = UID then
1448 begin
1449 Result := gPlayers[a];
1450 Exit;
1451 end;
1452 end;
1454 function g_Player_GetCount(): Byte;
1456 a: Integer;
1457 begin
1458 Result := 0;
1460 if gPlayers = nil then
1461 Exit;
1463 for a := 0 to High(gPlayers) do
1464 if gPlayers[a] <> nil then
1465 Result := Result + 1;
1466 end;
1468 function g_Bot_GetCount(): Integer;
1470 a: Integer;
1471 begin
1472 Result := 0;
1474 if gPlayers = nil then
1475 Exit;
1477 for a := 0 to High(gPlayers) do
1478 if (gPlayers[a] <> nil) and (gPlayers[a] is TBot) then
1479 Result := Result + 1;
1480 end;
1482 function g_Player_GetStats(): TPlayerStatArray;
1484 a: Integer;
1485 begin
1486 Result := nil;
1488 if gPlayers = nil then Exit;
1490 for a := 0 to High(gPlayers) do
1491 if gPlayers[a] <> nil then
1492 begin
1493 SetLength(Result, Length(Result)+1);
1494 with Result[High(Result)] do
1495 begin
1496 Num := a;
1497 Ping := gPlayers[a].FPing;
1498 Loss := gPlayers[a].FLoss;
1499 Name := gPlayers[a].FName;
1500 Team := gPlayers[a].FTeam;
1501 Frags := gPlayers[a].FFrags;
1502 Deaths := gPlayers[a].FDeath;
1503 Kills := gPlayers[a].FKills;
1504 Color := gPlayers[a].FModel.Color;
1505 Lives := gPlayers[a].FLives;
1506 Spectator := gPlayers[a].FSpectator;
1507 UID := gPlayers[a].FUID;
1508 end;
1509 end;
1510 end;
1512 procedure g_Player_ResetReady();
1514 a: Integer;
1515 begin
1516 if not g_Game_IsServer then Exit;
1517 if gPlayers = nil then Exit;
1519 for a := 0 to High(gPlayers) do
1520 if gPlayers[a] <> nil then
1521 begin
1522 gPlayers[a].FReady := False;
1523 if g_Game_IsNet then
1524 MH_SEND_GameEvent(NET_EV_INTER_READY, gPlayers[a].UID, 'N');
1525 end;
1526 end;
1528 procedure g_Player_RememberAll;
1530 i: Integer;
1531 begin
1532 for i := Low(gPlayers) to High(gPlayers) do
1533 if (gPlayers[i] <> nil) and gPlayers[i].alive then
1534 gPlayers[i].PreserveState;
1535 end;
1537 procedure g_Player_ResetAll(Force, Silent: Boolean);
1539 i: Integer;
1540 begin
1541 gTeamStat[TEAM_RED].Score := 0;
1542 gTeamStat[TEAM_BLUE].Score := 0;
1544 if gPlayers <> nil then
1545 for i := 0 to High(gPlayers) do
1546 if gPlayers[i] <> nil then
1547 begin
1548 gPlayers[i].Reset(Force);
1550 if gPlayers[i] is TPlayer then
1551 begin
1552 if (not gPlayers[i].FSpectator) or gPlayers[i].FWantsInGame then
1553 gPlayers[i].Respawn(Silent)
1554 else
1555 gPlayers[i].Spectate();
1557 else
1558 TBot(gPlayers[i]).Respawn(Silent);
1559 end;
1560 end;
1562 function g_Player_CreateCorpse(Player: TPlayer): Integer;
1564 i: Integer;
1565 find_id: DWORD;
1566 ok: Boolean;
1567 begin
1568 Result := -1;
1570 if Player.alive then
1571 Exit;
1573 // Ðàçðûâàåì ñâÿçü ñ ïðåæíèì òðóïîì:
1574 i := Player.FCorpse;
1575 if (Low(gCorpses) <= i) and (High(gCorpses) >= i) then
1576 begin
1577 if (gCorpses[i] <> nil) and (gCorpses[i].FPlayerUID = Player.FUID) then
1578 gCorpses[i].FPlayerUID := 0;
1579 end;
1581 if Player.FObj.Y >= gMapInfo.Height+128 then
1582 Exit;
1584 with Player do
1585 begin
1586 if (FHealth >= -50) or (gGibsCount = 0) then
1587 begin
1588 if Length(gCorpses) = 0 then
1589 Exit;
1591 ok := False;
1592 for find_id := 0 to High(gCorpses) do
1593 if gCorpses[find_id] = nil then
1594 begin
1595 ok := True;
1596 Break;
1597 end;
1599 if not ok then
1600 begin
1601 find_id := Random(Length(gCorpses));
1602 gCorpses[find_id].Destroy();
1603 end;
1605 gCorpses[find_id] := TCorpse.Create(FObj.X, FObj.Y, FModel.Name, FHealth < -20);
1606 gCorpses[find_id].FColor := FModel.Color;
1607 gCorpses[find_id].FObj.Vel := FObj.Vel;
1608 gCorpses[find_id].FObj.Accel := FObj.Accel;
1609 gCorpses[find_id].FPlayerUID := FUID;
1611 Result := find_id;
1613 else
1614 g_Player_CreateGibs(FObj.X + PLAYER_RECT_CX,
1615 FObj.Y + PLAYER_RECT_CY,
1616 FModel.Name, FModel.Color);
1617 end;
1618 end;
1620 procedure g_Player_CreateShell(fX, fY, dX, dY: Integer; T: Byte);
1622 SID: DWORD;
1623 begin
1624 if (gShells = nil) or (Length(gShells) = 0) then
1625 Exit;
1627 with gShells[CurrentShell] do
1628 begin
1629 SpriteID := 0;
1630 g_Obj_Init(@Obj);
1631 Obj.Rect.X := 0;
1632 Obj.Rect.Y := 0;
1633 if T = SHELL_BULLET then
1634 begin
1635 if g_Texture_Get('TEXTURE_SHELL_BULLET', SID) then
1636 SpriteID := SID;
1637 CX := 2;
1638 CY := 1;
1639 Obj.Rect.Width := 4;
1640 Obj.Rect.Height := 2;
1642 else
1643 begin
1644 if g_Texture_Get('TEXTURE_SHELL_SHELL', SID) then
1645 SpriteID := SID;
1646 CX := 4;
1647 CY := 2;
1648 Obj.Rect.Width := 7;
1649 Obj.Rect.Height := 3;
1650 end;
1651 SType := T;
1652 alive := True;
1653 Obj.X := fX;
1654 Obj.Y := fY;
1655 g_Obj_Push(@Obj, dX + Random(4)-Random(4), dY-Random(4));
1656 positionChanged(); // this updates spatial accelerators
1657 RAngle := Random(360);
1658 Timeout := gTime + SHELL_TIMEOUT;
1660 if CurrentShell >= High(gShells) then
1661 CurrentShell := 0
1662 else
1663 Inc(CurrentShell);
1664 end;
1665 end;
1667 procedure g_Player_CreateGibs(fX, fY: Integer; ModelName: string; fColor: TRGB);
1669 a: Integer;
1670 GibsArray: TGibsArray;
1671 Blood: TModelBlood;
1672 begin
1673 if (gGibs = nil) or (Length(gGibs) = 0) then
1674 Exit;
1675 if not g_PlayerModel_GetGibs(ModelName, GibsArray) then
1676 Exit;
1677 Blood := g_PlayerModel_GetBlood(ModelName);
1679 for a := 0 to High(GibsArray) do
1680 with gGibs[CurrentGib] do
1681 begin
1682 Color := fColor;
1683 ID := GibsArray[a].ID;
1684 MaskID := GibsArray[a].MaskID;
1685 alive := True;
1686 g_Obj_Init(@Obj);
1687 Obj.Rect := GibsArray[a].Rect;
1688 Obj.X := fX-GibsArray[a].Rect.X-(GibsArray[a].Rect.Width div 2);
1689 Obj.Y := fY-GibsArray[a].Rect.Y-(GibsArray[a].Rect.Height div 2);
1690 g_Obj_PushA(@Obj, 25 + Random(10), Random(361));
1691 positionChanged(); // this updates spatial accelerators
1692 RAngle := Random(360);
1694 if gBloodCount > 0 then
1695 g_GFX_Blood(fX, fY, 16*gBloodCount+Random(5*gBloodCount), -16+Random(33), -16+Random(33),
1696 Random(48), Random(48), Blood.R, Blood.G, Blood.B, Blood.Kind);
1698 if CurrentGib >= High(gGibs) then
1699 CurrentGib := 0
1700 else
1701 Inc(CurrentGib);
1702 end;
1703 end;
1705 procedure g_Player_UpdatePhysicalObjects();
1707 i: Integer;
1708 vel: TPoint2i;
1709 mr: Word;
1711 procedure ShellSound_Bounce(X, Y: Integer; T: Byte);
1713 k: Integer;
1714 begin
1715 k := 1 + Random(2);
1716 {$IFDEF ENABLE_SOUND}
1717 if T = SHELL_BULLET
1718 then g_Sound_PlayExAt('SOUND_PLAYER_CASING' + IntToStr(k), X, Y)
1719 else g_Sound_PlayExAt('SOUND_PLAYER_SHELL' + IntToStr(k), X, Y);
1720 {$ENDIF}
1721 end;
1723 begin
1724 // Êóñêè ìÿñà:
1725 for i := 0 to High(gGibs) do
1726 if gGibs[i].alive then
1727 with gGibs[i] do
1728 begin
1729 Obj.oldX := Obj.X;
1730 Obj.oldY := Obj.Y;
1732 vel := Obj.Vel;
1733 mr := g_Obj_Move(@Obj, True, False, True);
1734 positionChanged(); // this updates spatial accelerators
1736 if WordBool(mr and MOVE_FALLOUT) then
1737 begin
1738 alive := False;
1739 Continue;
1740 end;
1742 // Îòëåòàåò îò óäàðà î ñòåíó/ïîòîëîê/ïîë:
1743 if WordBool(mr and MOVE_HITWALL) then
1744 Obj.Vel.X := -(vel.X div 2);
1745 if WordBool(mr and (MOVE_HITCEIL or MOVE_HITLAND)) then
1746 Obj.Vel.Y := -(vel.Y div 2);
1748 if (Obj.Vel.X >= 0) then
1749 begin // Clockwise
1750 RAngle := RAngle + Abs(Obj.Vel.X)*6 + Abs(Obj.Vel.Y);
1751 if RAngle >= 360 then
1752 RAngle := RAngle mod 360;
1754 else
1755 begin // Counter-clockwise
1756 RAngle := RAngle - Abs(Obj.Vel.X)*6 - Abs(Obj.Vel.Y);
1757 if RAngle < 0 then
1758 RAngle := (360 - (Abs(RAngle) mod 360)) mod 360;
1759 end;
1761 // Ñîïðîòèâëåíèå âîçäóõà äëÿ êóñêà òðóïà:
1762 if gTime mod (GAME_TICK*3) = 0 then
1763 Obj.Vel.X := z_dec(Obj.Vel.X, 1);
1764 end;
1766 // Òðóïû:
1767 for i := 0 to High(gCorpses) do
1768 if gCorpses[i] <> nil then
1769 if gCorpses[i].State = CORPSE_STATE_REMOVEME
1770 then FreeAndNil(gCorpses[i])
1771 else gCorpses[i].Update();
1773 // Ãèëüçû:
1774 for i := 0 to High(gShells) do
1775 if gShells[i].alive then
1776 with gShells[i] do
1777 begin
1778 Obj.oldX := Obj.X;
1779 Obj.oldY := Obj.Y;
1781 vel := Obj.Vel;
1782 mr := g_Obj_Move(@Obj, True, False, True);
1783 positionChanged(); // this updates spatial accelerators
1785 if WordBool(mr and MOVE_FALLOUT) or (gShells[i].Timeout < gTime) then
1786 begin
1787 alive := False;
1788 Continue;
1789 end;
1791 // Îòëåòàåò îò óäàðà î ñòåíó/ïîòîëîê/ïîë:
1792 if WordBool(mr and MOVE_HITWALL) then
1793 begin
1794 Obj.Vel.X := -(vel.X div 2);
1795 if not WordBool(mr and MOVE_INWATER) then
1796 ShellSound_Bounce(Obj.X, Obj.Y, SType);
1797 end;
1798 if WordBool(mr and (MOVE_HITCEIL or MOVE_HITLAND)) then
1799 begin
1800 Obj.Vel.Y := -(vel.Y div 2);
1801 if Obj.Vel.X <> 0 then Obj.Vel.X := Obj.Vel.X div 2;
1802 if (Obj.Vel.X = 0) and (Obj.Vel.Y = 0) then
1803 begin
1804 if RAngle mod 90 <> 0 then
1805 RAngle := (RAngle div 90) * 90;
1807 else if not WordBool(mr and MOVE_INWATER) then
1808 ShellSound_Bounce(Obj.X, Obj.Y, SType);
1809 end;
1811 if (Obj.Vel.X >= 0) then
1812 begin // Clockwise
1813 RAngle := RAngle + Abs(Obj.Vel.X)*8 + Abs(Obj.Vel.Y);
1814 if RAngle >= 360 then
1815 RAngle := RAngle mod 360;
1817 else
1818 begin // Counter-clockwise
1819 RAngle := RAngle - Abs(Obj.Vel.X)*8 - Abs(Obj.Vel.Y);
1820 if RAngle < 0 then
1821 RAngle := (360 - (Abs(RAngle) mod 360)) mod 360;
1822 end;
1823 end;
1824 end;
1827 procedure TGib.getMapBox (out x, y, w, h: Integer); inline;
1828 begin
1829 x := Obj.X+Obj.Rect.X;
1830 y := Obj.Y+Obj.Rect.Y;
1831 w := Obj.Rect.Width;
1832 h := Obj.Rect.Height;
1833 end;
1835 procedure TGib.moveBy (dx, dy: Integer); inline;
1836 begin
1837 if (dx <> 0) or (dy <> 0) then
1838 begin
1839 Obj.X += dx;
1840 Obj.Y += dy;
1841 positionChanged();
1842 end;
1843 end;
1846 procedure TShell.getMapBox (out x, y, w, h: Integer); inline;
1847 begin
1848 x := Obj.X;
1849 y := Obj.Y;
1850 w := Obj.Rect.Width;
1851 h := Obj.Rect.Height;
1852 end;
1854 procedure TShell.moveBy (dx, dy: Integer); inline;
1855 begin
1856 if (dx <> 0) or (dy <> 0) then
1857 begin
1858 Obj.X += dx;
1859 Obj.Y += dy;
1860 positionChanged();
1861 end;
1862 end;
1865 procedure TGib.positionChanged (); inline; begin end;
1866 procedure TShell.positionChanged (); inline; begin end;
1869 procedure g_Player_DrawCorpses();
1871 i, fX, fY: Integer;
1872 a: TDFPoint;
1873 begin
1874 for i := 0 to High(gGibs) do
1875 if gGibs[i].alive then
1876 with gGibs[i] do
1877 begin
1878 if not g_Obj_Collide(sX, sY, sWidth, sHeight, @Obj) then
1879 Continue;
1881 Obj.lerp(gLerpFactor, fX, fY);
1883 a.X := Obj.Rect.X+(Obj.Rect.Width div 2);
1884 a.y := Obj.Rect.Y+(Obj.Rect.Height div 2);
1886 e_DrawAdv(ID, fX, fY, 0, True, False, RAngle, @a, TMirrorType.None);
1888 e_Colors := Color;
1889 e_DrawAdv(MaskID, fX, fY, 0, True, False, RAngle, @a, TMirrorType.None);
1890 e_Colors.R := 255;
1891 e_Colors.G := 255;
1892 e_Colors.B := 255;
1893 end;
1895 for i := 0 to High(gCorpses) do
1896 if gCorpses[i] <> nil then
1897 gCorpses[i].Draw();
1898 end;
1900 procedure g_Player_DrawShells();
1902 i, fX, fY: Integer;
1903 a: TDFPoint;
1904 begin
1905 for i := 0 to High(gShells) do
1906 if gShells[i].alive then
1907 with gShells[i] do
1908 begin
1909 if not g_Obj_Collide(sX, sY, sWidth, sHeight, @Obj) then
1910 Continue;
1912 Obj.lerp(gLerpFactor, fX, fY);
1914 a.X := CX;
1915 a.Y := CY;
1917 e_DrawAdv(SpriteID, fX, fY, 0, True, False, RAngle, @a, TMirrorType.None);
1918 end;
1919 end;
1921 procedure g_Player_RemoveAllCorpses();
1923 i: Integer;
1924 begin
1925 gGibs := nil;
1926 SetLength(gGibs, MaxGibs);
1928 gShells := nil;
1929 SetLength(gShells, MaxGibs);
1931 CurrentGib := 0;
1932 CurrentShell := 0;
1934 for i := 0 to High(gCorpses) do
1935 FreeAndNil(gCorpses[i]);
1936 SetLength(gCorpses, MaxCorpses);
1937 end;
1939 procedure g_Player_Corpses_SaveState (st: TStream);
1941 count: SizeUInt;
1942 i: SizeInt;
1943 begin
1944 // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ òðóïîâ
1945 count := 0;
1946 for i := 0 to High(gCorpses) do
1947 if gCorpses[i] <> nil then
1948 count += 1;
1950 // Êîëè÷åñòâî òðóïîâ
1951 st.WriteDWordLE(count);
1952 if count = 0 then Exit;
1954 // Ñîõðàíÿåì òðóïû
1955 for i := 0 to High(gCorpses) do
1956 begin
1957 if gCorpses[i] <> nil then
1958 begin
1959 utils.writeStr(st, gCorpses[i].FModelName); // Íàçâàíèå ìîäåëè
1960 st.WriteBool(gCorpses[i].Mess); // Òèï ñìåðòè
1961 gCorpses[i].SaveState(st); // Ñîõðàíÿåì äàííûå òðóïà:
1962 end;
1963 end;
1964 end;
1967 procedure g_Player_Corpses_LoadState (st: TStream);
1969 count, i: SizeUInt;
1970 str: String;
1971 b: Boolean;
1972 begin
1973 Assert(st <> nil);
1974 g_Player_RemoveAllCorpses();
1976 // Êîëè÷åñòâî òðóïîâ:
1977 count := st.ReadDWordLE();
1978 if count = 0 then Exit;
1979 if Length(gCorpses) < count then
1980 raise XStreamError.Create('invalid number of corpses');
1982 // Çàãðóæàåì òðóïû
1983 for i := 0 to count-1 do
1984 begin
1985 str := utils.readStr(st); // Íàçâàíèå ìîäåëè:
1986 b := st.ReadBool(); // Òèï ñìåðòè
1988 // Ñîçäàåì òðóï
1989 gCorpses[i] := TCorpse.Create(0, 0, str, b);
1990 gCorpses[i].LoadState(st); // Çàãðóæàåì äàííûå òðóïà
1991 end;
1992 end;
1995 { TPlayer: }
1997 function TPlayer.isValidViewPort (): Boolean; inline;
1998 begin
1999 Result := (viewPortW > 0) and (viewPortH > 0);
2000 end;
2002 procedure TPlayer.BFGHit();
2003 begin
2004 g_Weapon_BFGHit(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
2005 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2));
2006 if g_Game_IsServer and g_Game_IsNet then
2007 MH_SEND_Effect(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
2008 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
2009 0, NET_GFX_BFGHIT);
2010 end;
2012 procedure TPlayer.ChangeModel(ModelName: string);
2014 locModel: TPlayerModel;
2015 begin
2016 locModel := g_PlayerModel_Get(ModelName);
2017 if locModel = nil then Exit;
2019 FModel.Free();
2020 FModel := locModel;
2021 end;
2023 procedure TPlayer.SetModel(ModelName: string);
2025 m: TPlayerModel;
2026 begin
2027 m := g_PlayerModel_Get(ModelName);
2028 if m = nil then
2029 begin
2030 g_SimpleError(Format(_lc[I_GAME_ERROR_MODEL_FALLBACK], [ModelName]));
2031 m := g_PlayerModel_Get('doomer');
2032 if m = nil then
2033 begin
2034 g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], ['doomer']));
2035 Exit;
2036 end;
2037 end;
2039 if FModel <> nil then
2040 FModel.Free();
2042 FModel := m;
2044 if not (gGameSettings.GameMode in [GM_TDM, GM_CTF])
2045 then FModel.Color := FColor
2046 else FModel.Color := TEAMCOLOR[FTeam];
2048 FModel.SetWeapon(FCurrWeap);
2049 FModel.SetFlag(FFlag);
2050 SetDirection(FDirection);
2051 end;
2053 procedure TPlayer.SetColor(Color: TRGB);
2054 begin
2055 FColor := Color;
2056 if not (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
2057 if FModel <> nil then FModel.Color := Color;
2058 end;
2062 function TPlayer.GetColor(): TRGB;
2063 begin
2064 result := FModel.Color;
2065 end;
2067 procedure TPlayer.SetWeaponPrefs(Prefs: Array of Byte);
2069 i: Integer;
2070 begin
2071 for i := WP_FIRST to WP_LAST + 1 do
2072 begin
2073 if (Prefs[i] > WP_LAST + 1) then
2074 FWeapPreferences[i] := 0
2075 else
2076 FWeapPreferences[i] := Prefs[i];
2077 end;
2078 end;
2080 procedure TPlayer.SetWeaponPref(Weapon, Pref: Byte);
2081 begin
2082 if (Weapon > WP_LAST + 1) then
2083 exit
2084 else if (Pref <= WP_LAST + 1) and (Weapon <= WP_LAST + 1) then
2085 FWeapPreferences[Weapon] := Pref
2086 else if (Weapon <= WP_LAST + 1) and (Pref > WP_LAST + 1) then
2087 FWeapPreferences[Weapon] := 0;
2088 end;
2090 function TPlayer.GetWeaponPref(Weapon: Byte) : Byte;
2091 begin
2092 if (Weapon > WP_LAST + 1) then
2093 result := 0
2094 else if (FWeapPreferences[Weapon] > WP_LAST + 1) then
2095 result := 0
2096 else
2097 result := FWeapPreferences[Weapon];
2098 end;
2100 function TPlayer.GetMorePrefered() : Byte;
2102 testedWeap, i: Byte;
2103 begin
2104 testedWeap := FCurrWeap;
2105 for i := WP_FIRST to WP_LAST do
2106 if FWeapon[i] and maySwitch(i) and (FWeapPreferences[i] > FWeapPreferences[testedWeap]) then
2107 testedWeap := i;
2108 if (R_BERSERK in FInventory) and (FWeapPreferences[WP_LAST + 1] > FWeapPreferences[testedWeap]) then
2109 testedWeap := WEAPON_IRONFIST;
2110 result := testedWeap;
2111 end;
2113 function TPlayer.maySwitch(Weapon: Byte) : Boolean;
2114 begin
2115 result := true;
2116 if (Weapon = WEAPON_IRONFIST) and (FSkipIronFist <> 0) then
2117 begin
2118 if (FSkipIronFist = 1) and (not (R_BERSERK in FInventory)) then
2119 result := false;
2121 else if (FSwitchToEmpty = 0) and (not hasAmmoForShooting(Weapon)) then
2122 result := false;
2123 end;
2125 procedure TPlayer.SwitchTeam;
2126 begin
2127 if g_Game_IsClient then
2128 Exit;
2129 if not (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then Exit;
2131 if gGameOn and FAlive then
2132 Kill(K_SIMPLEKILL, FUID, HIT_SELF);
2134 if FTeam = TEAM_RED then
2135 begin
2136 ChangeTeam(TEAM_BLUE);
2137 g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_BLUE], [FName]), True);
2138 if g_Game_IsNet then
2139 MH_SEND_GameEvent(NET_EV_CHANGE_TEAM, TEAM_BLUE, FName);
2141 else
2142 begin
2143 ChangeTeam(TEAM_RED);
2144 g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_RED], [FName]), True);
2145 if g_Game_IsNet then
2146 MH_SEND_GameEvent(NET_EV_CHANGE_TEAM, TEAM_RED, FName);
2147 end;
2148 FPreferredTeam := FTeam;
2149 end;
2151 procedure TPlayer.ChangeTeam(Team: Byte);
2153 OldTeam: Byte;
2154 begin
2155 OldTeam := FTeam;
2156 FTeam := Team;
2157 case Team of
2158 TEAM_RED, TEAM_BLUE:
2159 FModel.Color := TEAMCOLOR[Team];
2160 else
2161 FModel.Color := FColor;
2162 end;
2163 if (FTeam <> OldTeam) and g_Game_IsNet and g_Game_IsServer then
2164 MH_SEND_PlayerStats(FUID);
2165 end;
2168 procedure TPlayer.CollideItem();
2170 i: Integer;
2171 r: Boolean;
2172 begin
2173 if gItems = nil then Exit;
2174 if not FAlive then Exit;
2176 for i := 0 to High(gItems) do
2177 with gItems[i] do
2178 begin
2179 if (ItemType <> ITEM_NONE) and alive then
2180 if g_Obj_Collide(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width,
2181 PLAYER_RECT.Height, @Obj) then
2182 begin
2183 if not PickItem(ItemType, gItems[i].Respawnable, r) then Continue;
2185 if ItemType in [ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_INVUL] then
2186 g_Sound_PlayExAt('SOUND_ITEM_GETPOWERUP', FObj.X, FObj.Y)
2187 else if ItemType in [ITEM_MEDKIT_SMALL, ITEM_MEDKIT_LARGE, ITEM_MEDKIT_BLACK] then
2188 g_Sound_PlayExAt('SOUND_ITEM_GETMED', FObj.X, FObj.Y)
2189 else g_Sound_PlayExAt('SOUND_ITEM_GETITEM', FObj.X, FObj.Y);
2191 // Íàäî óáðàòü ñ êàðòû, åñëè ýòî íå êëþ÷, êîòîðûì íóæíî ïîäåëèòüñÿ ñ äðóãèì èãðîêîì:
2192 if r and not ((ItemType in [ITEM_KEY_RED, ITEM_KEY_GREEN, ITEM_KEY_BLUE]) and
2193 (gGameSettings.GameType = GT_SINGLE) and
2194 (g_Player_GetCount() > 1)) then
2195 if not Respawnable then g_Items_Remove(i) else g_Items_Pick(i);
2196 end;
2197 end;
2198 end;
2201 function TPlayer.CollideLevel(XInc, YInc: Integer): Boolean;
2202 begin
2203 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
2204 PLAYER_RECT.Width, PLAYER_RECT.Height, PANEL_WALL,
2205 False);
2206 end;
2208 constructor TPlayer.Create();
2209 begin
2210 viewPortX := 0;
2211 viewPortY := 0;
2212 viewPortW := 0;
2213 viewPortH := 0;
2214 mEDamageType := HIT_SOME;
2216 FIamBot := False;
2217 FDummy := False;
2218 FSpawned := False;
2220 {$IFDEF ENABLE_SOUND}
2221 FSawSound := TPlayableSound.Create();
2222 FSawSoundIdle := TPlayableSound.Create();
2223 FSawSoundHit := TPlayableSound.Create();
2224 FSawSoundSelect := TPlayableSound.Create();
2225 FFlameSoundOn := TPlayableSound.Create();
2226 FFlameSoundOff := TPlayableSound.Create();
2227 FFlameSoundWork := TPlayableSound.Create();
2228 FJetSoundFly := TPlayableSound.Create();
2229 FJetSoundOn := TPlayableSound.Create();
2230 FJetSoundOff := TPlayableSound.Create();
2232 FSawSound.SetByName('SOUND_WEAPON_FIRESAW');
2233 FSawSoundIdle.SetByName('SOUND_WEAPON_IDLESAW');
2234 FSawSoundHit.SetByName('SOUND_WEAPON_HITSAW');
2235 FSawSoundSelect.SetByName('SOUND_WEAPON_SELECTSAW');
2236 FFlameSoundOn.SetByName('SOUND_WEAPON_FLAMEON');
2237 FFlameSoundOff.SetByName('SOUND_WEAPON_FLAMEOFF');
2238 FFlameSoundWork.SetByName('SOUND_WEAPON_FLAMEWORK');
2239 FJetSoundFly.SetByName('SOUND_PLAYER_JETFLY');
2240 FJetSoundOn.SetByName('SOUND_PLAYER_JETON');
2241 FJetSoundOff.SetByName('SOUND_PLAYER_JETOFF');
2242 {$ENDIF}
2244 FSpectatePlayer := -1;
2245 FClientID := -1;
2246 FPing := 0;
2247 FLoss := 0;
2248 FSavedStateNum := -1;
2249 FShellTimer := -1;
2250 FFireTime := 0;
2251 FFirePainTime := 0;
2252 FFireAttacker := 0;
2253 FHandicap := 100;
2254 FCorpse := -1;
2256 FActualModelName := 'doomer';
2258 g_Obj_Init(@FObj);
2259 FObj.Rect := PLAYER_RECT;
2261 FBFGFireCounter := -1;
2262 FJustTeleported := False;
2263 FNetTime := 0;
2265 FWaitForFirstSpawn := false;
2267 resetWeaponQueue();
2268 end;
2270 procedure TPlayer.positionChanged (); inline;
2271 begin
2272 end;
2274 procedure TPlayer.doDamage (v: Integer);
2275 begin
2276 if (v <= 0) then exit;
2277 if (v > 32767) then v := 32767;
2278 Damage(v, 0, 0, 0, mEDamageType);
2279 end;
2281 procedure TPlayer.Damage(value: Word; SpawnerUID: Word; vx, vy: Integer; t: Byte);
2283 c: Word;
2284 begin
2285 if (not g_Game_IsClient) and (not FAlive) then
2286 Exit;
2288 FLastHit := t;
2290 // Íåóÿçâèìîñòü íå ñïàñàåò îò ëîâóøåê:
2291 if ((t = HIT_TRAP) or (t = HIT_SELF)) and (not FGodMode) then
2292 begin
2293 if not g_Game_IsClient then
2294 begin
2295 FArmor := 0;
2296 if t = HIT_TRAP then
2297 begin
2298 // Ëîâóøêà óáèâàåò ñðàçó:
2299 FHealth := -100;
2300 Kill(K_EXTRAHARDKILL, SpawnerUID, t);
2301 end;
2302 if t = HIT_SELF then
2303 begin
2304 // Ñàìîóáèéñòâî:
2305 FHealth := 0;
2306 Kill(K_SIMPLEKILL, SpawnerUID, t);
2307 end;
2308 end;
2309 // Îáíóëèòü äåéñòâèÿ ïðèìî÷åê, ÷òîáû ôîí ïðîïàë
2310 FPowerups[MR_SUIT] := 0;
2311 FPowerups[MR_INVUL] := 0;
2312 FPowerups[MR_INVIS] := 0;
2313 FSpawnInvul := 0;
2314 FBerserk := 0;
2315 end;
2317 // Íî îò îñòàëüíîãî ñïàñàåò:
2318 if FPowerups[MR_INVUL] >= gTime then
2319 Exit;
2321 // ×èò-êîä "ÃÎÐÅÖ":
2322 if FGodMode then
2323 Exit;
2325 // Åñëè åñòü óðîí ñâîèì, èëè ðàíèë ñàì ñåáÿ, èëè òåáÿ ðàíèë ïðîòèâíèê:
2326 if (TGameOption.TEAM_DAMAGE in gGameSettings.Options) or
2327 (SpawnerUID = FUID) or
2328 (not SameTeam(FUID, SpawnerUID)) then
2329 begin
2330 FLastSpawnerUID := SpawnerUID;
2332 // Êðîâü (ïóçûðüêè, åñëè â âîäå):
2333 if gBloodCount > 0 then
2334 begin
2335 c := Min(value, 200)*gBloodCount + Random(Min(value, 200) div 2);
2336 if value div 4 <= c then
2337 c := c - (value div 4)
2338 else
2339 c := 0;
2341 if (t = HIT_SOME) and (vx = 0) and (vy = 0) then
2342 MakeBloodSimple(c)
2343 else
2344 case t of
2345 HIT_TRAP, HIT_ACID, HIT_FLAME, HIT_SELF: MakeBloodSimple(c);
2346 HIT_BFG, HIT_ROCKET, HIT_SOME: MakeBloodVector(c, vx, vy);
2347 end;
2349 if t = HIT_WATER then
2350 g_Game_Effect_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
2351 FObj.Y+PLAYER_RECT.Y-4, value div 2, 8, 4);
2352 end;
2354 // Áóôåð óðîíà:
2355 if FAlive then
2356 Inc(FDamageBuffer, value);
2358 // Âñïûøêà áîëè:
2359 if gFlash <> 0 then
2360 FPain := FPain + value;
2361 end;
2363 if g_Game_IsServer and g_Game_IsNet then
2364 begin
2365 MH_SEND_PlayerDamage(FUID, t, SpawnerUID, value, vx, vy);
2366 MH_SEND_PlayerStats(FUID);
2367 MH_SEND_PlayerPos(False, FUID);
2368 end;
2369 end;
2371 function TPlayer.Heal(value: Word; Soft: Boolean): Boolean;
2372 begin
2373 Result := False;
2374 if g_Game_IsClient then
2375 Exit;
2376 if not FAlive then
2377 Exit;
2379 if Soft and (FHealth < PLAYER_HP_SOFT) then
2380 begin
2381 IncMax(FHealth, value, PLAYER_HP_SOFT);
2382 Result := True;
2383 end;
2384 if (not Soft) and (FHealth < PLAYER_HP_LIMIT) then
2385 begin
2386 IncMax(FHealth, value, PLAYER_HP_LIMIT);
2387 Result := True;
2388 end;
2390 if Result and g_Game_IsServer and g_Game_IsNet then
2391 MH_SEND_PlayerStats(FUID);
2392 end;
2394 destructor TPlayer.Destroy();
2395 begin
2396 if (gPlayer1 <> nil) and (gPlayer1.FUID = FUID) then
2397 gPlayer1 := nil;
2398 if (gPlayer2 <> nil) and (gPlayer2.FUID = FUID) then
2399 gPlayer2 := nil;
2401 {$IFDEF ENABLE_SOUND}
2402 FSawSound.Free();
2403 FSawSoundIdle.Free();
2404 FSawSoundHit.Free();
2405 FSawSoundSelect.Free();
2406 FFlameSoundOn.Free();
2407 FFlameSoundOff.Free();
2408 FFlameSoundWork.Free();
2409 FJetSoundFly.Free();
2410 FJetSoundOn.Free();
2411 FJetSoundOff.Free();
2412 {$ENDIF}
2414 FModel.Free();
2415 FPunchAnim.Free();
2417 inherited;
2418 end;
2420 procedure TPlayer.DrawIndicator(Color: TRGB);
2422 indX, indY, fX, fY, fSlope: Integer;
2423 indW, indH: Word;
2424 indA: Single;
2425 a: TDFPoint;
2426 nW, nH: Byte;
2427 ID: DWORD;
2428 c: TRGB;
2429 begin
2430 if FAlive then
2431 begin
2432 FObj.lerp(gLerpFactor, fX, fY);
2433 fSlope := nlerp(FSlopeOld, FObj.slopeUpLeft, gLerpFactor);
2435 case gPlayerIndicatorStyle of
2437 begin
2438 if g_Texture_Get('TEXTURE_PLAYER_INDICATOR', ID) then
2439 begin
2440 e_GetTextureSize(ID, @indW, @indH);
2441 a.X := indW div 2;
2442 a.Y := indH div 2;
2444 if (FObj.X + FObj.Rect.X) < 0 then
2445 begin
2446 indA := 90;
2447 indX := fX + FObj.Rect.X + FObj.Rect.Width;
2448 indY := fY + FObj.Rect.Y + (FObj.Rect.Height - indW) div 2;
2451 else if (FObj.X + FObj.Rect.X + FObj.Rect.Width) > Max(gMapInfo.Width, gPlayerScreenSize.X) then
2452 begin
2453 indA := 270;
2454 indX := fX + FObj.Rect.X - indH;
2455 indY := fY + FObj.Rect.Y + (FObj.Rect.Height - indW) div 2;
2458 else if (FObj.Y - indH) < 0 then
2459 begin
2460 indA := 180;
2461 indX := fX + FObj.Rect.X + (FObj.Rect.Width - indW) div 2;
2462 indY := fY + FObj.Rect.Y + FObj.Rect.Height;
2465 else
2466 begin
2467 indA := 0;
2468 indX := fX + FObj.Rect.X + (FObj.Rect.Width - indW) div 2;
2469 indY := fY - indH;
2470 end;
2472 indY := indY + fSlope;
2473 indX := EnsureRange(indX, 0, Max(gMapInfo.Width, gPlayerScreenSize.X) - indW);
2474 indY := EnsureRange(indY, 0, Max(gMapInfo.Height, gPlayerScreenSize.Y) - indH);
2476 c := e_Colors;
2477 e_Colors := Color;
2478 e_DrawAdv(ID, indX, indY, 0, True, False, indA, @a);
2479 e_Colors := c;
2480 end;
2481 end;
2484 begin
2485 e_TextureFontGetSize(gStdFont, nW, nH);
2486 indX := fX + FObj.Rect.X + (FObj.Rect.Width - Length(FName) * nW) div 2;
2487 indY := fY - nH + fSlope;
2488 e_TextureFontPrintEx(indX, indY, FName, gStdFont, Color.R, Color.G, Color.B, 1.0, True);
2489 end;
2490 end;
2491 end;
2492 end;
2494 procedure TPlayer.DrawBubble();
2496 bubX, bubY, fX, fY: Integer;
2497 ID: LongWord;
2498 Rb, Gb, Bb,
2499 Rw, Gw, Bw: SmallInt;
2500 Dot: Byte;
2501 CObj: TObj;
2502 begin
2503 CObj := getCameraObj();
2504 CObj.lerp(gLerpFactor, fX, fY);
2505 // NB: _F_Obj.Rect is used to keep the bubble higher; this is not a mistake
2506 bubX := fX+FObj.Rect.X + IfThen(FDirection = TDirection.D_LEFT, -4, 18);
2507 bubY := fY+FObj.Rect.Y - 18;
2508 Rb := 64;
2509 Gb := 64;
2510 Bb := 64;
2511 Rw := 240;
2512 Gw := 240;
2513 Bw := 240;
2514 case gChatBubble of
2515 1: // simple textual non-bubble
2516 begin
2517 bubX := fX+FObj.Rect.X - 11;
2518 bubY := fY+FObj.Rect.Y - 17;
2519 e_TextureFontPrint(bubX, bubY, '[...]', gStdFont);
2520 Exit;
2521 end;
2522 2: // advanced pixel-perfect bubble
2523 begin
2524 if FTeam = TEAM_RED then
2525 Rb := 255
2526 else
2527 if FTeam = TEAM_BLUE then
2528 Bb := 255;
2529 end;
2530 3: // colored bubble
2531 begin
2532 Rb := FModel.Color.R;
2533 Gb := FModel.Color.G;
2534 Bb := FModel.Color.B;
2535 Rw := Min(Rb * 2 + 64, 255);
2536 Gw := Min(Gb * 2 + 64, 255);
2537 Bw := Min(Bb * 2 + 64, 255);
2538 if (Abs(Rw - Rb) < 32)
2539 or (Abs(Gw - Gb) < 32)
2540 or (Abs(Bw - Bb) < 32) then
2541 begin
2542 Rb := Max(Rw div 2 - 16, 0);
2543 Gb := Max(Gw div 2 - 16, 0);
2544 Bb := Max(Bw div 2 - 16, 0);
2545 end;
2546 end;
2547 4: // custom textured bubble
2548 begin
2549 if g_Texture_Get('TEXTURE_PLAYER_TALKBUBBLE', ID) then
2550 if FDirection = TDirection.D_RIGHT then
2551 e_Draw(ID, bubX - 6, bubY - 7, 0, True, False)
2552 else
2553 e_Draw(ID, bubX - 6, bubY - 7, 0, True, False, TMirrorType.Horizontal);
2554 Exit;
2555 end;
2556 end;
2558 // Outer borders
2559 e_DrawQuad(bubX + 1, bubY , bubX + 18, bubY + 13, Rb, Gb, Bb);
2560 e_DrawQuad(bubX , bubY + 1, bubX + 19, bubY + 12, Rb, Gb, Bb);
2561 // Inner box
2562 e_DrawFillQuad(bubX + 1, bubY + 1, bubX + 18, bubY + 12, Rw, Gw, Bw, 0);
2564 // Tail
2565 Dot := IfThen(FDirection = TDirection.D_LEFT, 14, 5);
2566 e_DrawLine(1, bubX + Dot, bubY + 14, bubX + Dot, bubY + 16, Rb, Gb, Bb);
2567 e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 1, Dot + 1), bubY + 13, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 1, Dot + 1), bubY + 15, Rw, Gw, Bw);
2568 e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 2, Dot + 2), bubY + 13, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 2, Dot + 2), bubY + 14, Rw, Gw, Bw);
2569 e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 3, Dot + 3), bubY + 13, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 3, Dot + 3), bubY + 13, Rw, Gw, Bw);
2570 e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 3, Dot + 3), bubY + 14, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 1, Dot + 1), bubY + 16, Rb, Gb, Bb);
2572 // Dots
2573 Dot := 6;
2574 e_DrawFillQuad(bubX + Dot, bubY + 8, bubX + Dot + 1, bubY + 9, Rb, Gb, Bb, 0);
2575 e_DrawFillQuad(bubX + Dot + 3, bubY + 8, bubX + Dot + 4, bubY + 9, Rb, Gb, Bb, 0);
2576 e_DrawFillQuad(bubX + Dot + 6, bubY + 8, bubX + Dot + 7, bubY + 9, Rb, Gb, Bb, 0);
2577 end;
2579 procedure TPlayer.Draw();
2581 ID: DWORD;
2582 w, h: Word;
2583 dr: Boolean;
2584 Mirror: TMirrorType;
2585 fX, fY, fSlope: Integer;
2586 begin
2587 FObj.lerp(gLerpFactor, fX, fY);
2588 fSlope := nlerp(FSlopeOld, FObj.slopeUpLeft, gLerpFactor);
2590 if FAlive then
2591 begin
2592 if Direction = TDirection.D_RIGHT then
2593 Mirror := TMirrorType.None
2594 else
2595 Mirror := TMirrorType.Horizontal;
2597 if FPunchAnim <> nil then
2598 begin
2599 FPunchAnim.Draw(fX+IfThen(Direction = TDirection.D_LEFT, 15-FObj.Rect.X, FObj.Rect.X-15),
2600 fY+fSlope+FObj.Rect.Y-11, Mirror);
2601 if FPunchAnim.played then
2602 FreeAndNil(FPunchAnim);
2603 end;
2605 if (FPowerups[MR_INVUL] > gTime) and ((gPlayerDrawn <> Self) or (FSpawnInvul >= gTime)) then
2606 if g_Texture_Get('TEXTURE_PLAYER_INVULPENTA', ID) then
2607 begin
2608 e_GetTextureSize(ID, @w, @h);
2609 if FDirection = TDirection.D_LEFT then
2610 e_Draw(ID, fX+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)+4,
2611 fY+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7+fSlope, 0, True, False)
2612 else
2613 e_Draw(ID, fX+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)-2,
2614 fY+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7+fSlope, 0, True, False);
2615 end;
2617 if FPowerups[MR_INVIS] > gTime then
2618 begin
2619 if (gPlayerDrawn <> nil) and ((Self = gPlayerDrawn) or
2620 ((FTeam = gPlayerDrawn.Team) and (gGameSettings.GameMode <> GM_DM))) then
2621 begin
2622 if (FPowerups[MR_INVIS] - gTime) <= 2100
2623 then dr := not Odd((FPowerups[MR_INVIS] - gTime) div 300)
2624 else dr := True;
2625 if dr
2626 then FModel.Draw(fX, fY+fSlope, 200)
2627 else FModel.Draw(fX, fY+fSlope);
2629 else
2630 FModel.Draw(fX, fY+fSlope, 255);
2632 else
2633 FModel.Draw(fX, fY+fSlope);
2634 end;
2636 if g_debug_Frames then
2637 begin
2638 e_DrawQuad(FObj.X+FObj.Rect.X,
2639 FObj.Y+FObj.Rect.Y,
2640 FObj.X+FObj.Rect.X+FObj.Rect.Width-1,
2641 FObj.Y+FObj.Rect.Y+FObj.Rect.Height-1,
2642 0, 255, 0);
2643 end;
2645 if (gChatBubble > 0) and (FKeys[KEY_CHAT].Pressed) and not FGhost then
2646 if (FPowerups[MR_INVIS] <= gTime) or ((gPlayerDrawn <> nil) and ((Self = gPlayerDrawn) or
2647 ((FTeam = gPlayerDrawn.Team) and (gGameSettings.GameMode <> GM_DM)))) then
2648 DrawBubble();
2649 // e_DrawPoint(5, 335, 288, 255, 0, 0); // DL, UR, DL, UR
2650 if gAimLine and alive and
2651 ((Self = gPlayer1) or (Self = gPlayer2)) then
2652 DrawAim();
2653 end;
2656 procedure TPlayer.DrawAim();
2657 procedure drawCast (sz: Integer; ax0, ay0, ax1, ay1: Integer);
2659 ex, ey: Integer;
2660 begin
2662 {$IFDEF ENABLE_HOLMES}
2663 if isValidViewPort and (self = gPlayer1) then
2664 begin
2665 g_Holmes_plrLaser(ax0, ay0, ax1, ay1);
2666 end;
2667 {$ENDIF}
2669 e_DrawLine(sz, ax0, ay0, ax1, ay1, 255, 0, 0, 96);
2670 if (g_Map_traceToNearestWall(ax0, ay0, ax1, ay1, @ex, @ey) <> nil) then
2671 begin
2672 e_DrawLine(sz, ax0, ay0, ex, ey, 0, 255, 0, 96);
2674 else
2675 begin
2676 e_DrawLine(sz, ax0, ay0, ex, ey, 0, 0, 255, 96);
2677 end;
2678 end;
2681 wx, wy, xx, yy: Integer;
2682 angle: SmallInt;
2683 sz, len: Word;
2684 begin
2685 wx := FObj.X + WEAPONPOINT[FDirection].X + IfThen(FDirection = TDirection.D_LEFT, 7, -7);
2686 wy := FObj.Y + WEAPONPOINT[FDirection].Y;
2687 angle := FAngle;
2688 len := 1024;
2689 sz := 2;
2690 case FCurrWeap of
2691 0: begin // Punch
2692 len := 12;
2693 sz := 4;
2694 end;
2695 1: begin // Chainsaw
2696 len := 24;
2697 sz := 6;
2698 end;
2699 2: begin // Pistol
2700 len := 1024;
2701 sz := 2;
2702 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2703 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2704 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2705 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2706 end;
2707 3: begin // Shotgun
2708 len := 1024;
2709 sz := 3;
2710 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2711 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2712 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2713 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2714 end;
2715 4: begin // Double Shotgun
2716 len := 1024;
2717 sz := 4;
2718 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2719 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2720 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2721 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2722 end;
2723 5: begin // Chaingun
2724 len := 1024;
2725 sz := 3;
2726 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2727 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2728 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2729 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2730 end;
2731 6: begin // Rocket Launcher
2732 len := 1024;
2733 sz := 7;
2734 if angle = ANGLE_RIGHTUP then Inc(angle, 2);
2735 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2736 if angle = ANGLE_LEFTUP then Dec(angle, 2);
2737 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2738 end;
2739 7: begin // Plasmagun
2740 len := 1024;
2741 sz := 5;
2742 if angle = ANGLE_RIGHTUP then Inc(angle);
2743 if angle = ANGLE_RIGHTDOWN then Inc(angle, 3);
2744 if angle = ANGLE_LEFTUP then Dec(angle);
2745 if angle = ANGLE_LEFTDOWN then Dec(angle, 3);
2746 end;
2747 8: begin // BFG
2748 len := 1024;
2749 sz := 12;
2750 if angle = ANGLE_RIGHTUP then Inc(angle, 1);
2751 if angle = ANGLE_RIGHTDOWN then Inc(angle, 2);
2752 if angle = ANGLE_LEFTUP then Dec(angle, 1);
2753 if angle = ANGLE_LEFTDOWN then Dec(angle, 2);
2754 end;
2755 9: begin // Super Chaingun
2756 len := 1024;
2757 sz := 4;
2758 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2759 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2760 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2761 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2762 end;
2763 end;
2764 xx := Trunc(Cos(-DegToRad(angle)) * len) + wx;
2765 yy := Trunc(Sin(-DegToRad(angle)) * len) + wy;
2766 {$IF DEFINED(D2F_DEBUG)}
2767 drawCast(sz, wx, wy, xx, yy);
2768 {$ELSE}
2769 e_DrawLine(sz, wx, wy, xx, yy, 255, 0, 0, 96);
2770 {$ENDIF}
2771 end;
2773 procedure TPlayer.DrawGUI();
2775 ID: DWORD;
2776 X, Y, SY, a, p, m: Integer;
2777 tw, th: Word;
2778 cw, ch: Byte;
2779 s: string;
2780 stat: TPlayerStatArray;
2781 begin
2782 X := gPlayerScreenSize.X;
2783 SY := gPlayerScreenSize.Y;
2784 Y := 0;
2786 if gShowScore and (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
2787 begin
2788 if gGameSettings.GameMode = GM_CTF
2789 then a := 32 + 8
2790 else a := 0;
2792 if gGameSettings.GameMode = GM_CTF then
2793 begin
2794 s := 'TEXTURE_PLAYER_REDFLAG';
2795 if gFlags[FLAG_RED].State = FLAG_STATE_CAPTURED then
2796 s := 'TEXTURE_PLAYER_REDFLAG_S';
2797 if gFlags[FLAG_RED].State = FLAG_STATE_DROPPED then
2798 s := 'TEXTURE_PLAYER_REDFLAG_D';
2799 if g_Texture_Get(s, ID) then
2800 e_Draw(ID, X-16-32, 240-72-4, 0, True, False);
2801 end;
2803 s := IntToStr(gTeamStat[TEAM_RED].Score);
2804 e_CharFont_GetSize(gMenuFont, s, tw, th);
2805 e_CharFont_PrintEx(gMenuFont, X-16-a-tw, 240-72-4, s, TEAMCOLOR[TEAM_RED]);
2807 if gGameSettings.GameMode = GM_CTF then
2808 begin
2809 s := 'TEXTURE_PLAYER_BLUEFLAG';
2810 if gFlags[FLAG_BLUE].State = FLAG_STATE_CAPTURED then
2811 s := 'TEXTURE_PLAYER_BLUEFLAG_S';
2812 if gFlags[FLAG_BLUE].State = FLAG_STATE_DROPPED then
2813 s := 'TEXTURE_PLAYER_BLUEFLAG_D';
2814 if g_Texture_Get(s, ID) then
2815 e_Draw(ID, X-16-32, 240-32-4, 0, True, False);
2816 end;
2818 s := IntToStr(gTeamStat[TEAM_BLUE].Score);
2819 e_CharFont_GetSize(gMenuFont, s, tw, th);
2820 e_CharFont_PrintEx(gMenuFont, X-16-a-tw, 240-32-4, s, TEAMCOLOR[TEAM_BLUE]);
2821 end;
2823 if g_Texture_Get('TEXTURE_PLAYER_HUDBG', ID) then
2824 e_DrawFill(ID, X, 0, 1, (gPlayerScreenSize.Y div 256)+IfThen(gPlayerScreenSize.Y mod 256 > 0, 1, 0),
2825 0, False, False);
2827 if g_Texture_Get('TEXTURE_PLAYER_HUD', ID) then
2828 e_Draw(ID, X+2, Y, 0, True, False);
2830 if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
2831 begin
2832 if gShowStat then
2833 begin
2834 s := IntToStr(Frags);
2835 e_CharFont_GetSize(gMenuFont, s, tw, th);
2836 e_CharFont_PrintEx(gMenuFont, X-16-tw, Y, s, _RGB(255, 0, 0));
2838 s := '';
2839 p := 1;
2840 m := 0;
2841 stat := g_Player_GetStats();
2842 if stat <> nil then
2843 begin
2844 p := 1;
2846 for a := 0 to High(stat) do
2847 if stat[a].Name <> Name then
2848 begin
2849 if stat[a].Frags > m then m := stat[a].Frags;
2850 if stat[a].Frags > Frags then p := p+1;
2851 end;
2852 end;
2854 s := IntToStr(p)+' / '+IntToStr(Length(stat))+' ';
2855 if Frags >= m then s := s+'+' else s := s+'-';
2856 s := s+IntToStr(Abs(Frags-m));
2858 e_CharFont_GetSize(gMenuSmallFont, s, tw, th);
2859 e_CharFont_PrintEx(gMenuSmallFont, X-16-tw, Y+32, s, _RGB(255, 0, 0));
2860 end;
2862 if gLMSRespawn > LMS_RESPAWN_NONE then
2863 begin
2864 s := _lc[I_GAME_WARMUP];
2865 e_CharFont_GetSize(gMenuFont, s, tw, th);
2866 s := s + ': ' + IntToStr((gLMSRespawnTime - gTime) div 1000);
2867 e_CharFont_PrintEx(gMenuFont, X-64-tw, SY-32, s, _RGB(0, 255, 0));
2869 else if gShowLives and (gGameSettings.MaxLives > 0) then
2870 begin
2871 s := IntToStr(Lives);
2872 e_CharFont_GetSize(gMenuFont, s, tw, th);
2873 e_CharFont_PrintEx(gMenuFont, X-16-tw, SY-32, s, _RGB(0, 255, 0));
2874 end;
2875 end;
2877 e_CharFont_GetSize(gMenuSmallFont, FName, tw, th);
2878 e_CharFont_PrintEx(gMenuSmallFont, X+98-(tw div 2), Y+8, FName, _RGB(255, 0, 0));
2880 if R_BERSERK in FInventory
2881 then e_Draw(gItemsTexturesID[ITEM_MEDKIT_BLACK], X+37, Y+45, 0, True, False)
2882 else e_Draw(gItemsTexturesID[ITEM_MEDKIT_LARGE], X+37, Y+45, 0, True, False);
2884 if g_Texture_Get('TEXTURE_PLAYER_ARMORHUD', ID) then
2885 e_Draw(ID, X+36, Y+77, 0, True, False);
2887 s := IntToStr(IfThen(FHealth > 0, FHealth, 0));
2888 e_CharFont_GetSize(gMenuFont, s, tw, th);
2889 e_CharFont_PrintEx(gMenuFont, X+178-tw, Y+40, s, _RGB(255, 0, 0));
2891 s := IntToStr(FArmor);
2892 e_CharFont_GetSize(gMenuFont, s, tw, th);
2893 e_CharFont_PrintEx(gMenuFont, X+178-tw, Y+68, s, _RGB(255, 0, 0));
2895 s := IntToStr(GetAmmoByWeapon(FCurrWeap));
2897 case FCurrWeap of
2898 WEAPON_IRONFIST:
2899 begin
2900 s := '--';
2901 ID := gItemsTexturesID[ITEM_WEAPON_IRONFIST];
2902 end;
2903 WEAPON_SAW:
2904 begin
2905 s := '--';
2906 ID := gItemsTexturesID[ITEM_WEAPON_SAW];
2907 end;
2908 WEAPON_PISTOL: ID := gItemsTexturesID[ITEM_WEAPON_PISTOL];
2909 WEAPON_CHAINGUN: ID := gItemsTexturesID[ITEM_WEAPON_CHAINGUN];
2910 WEAPON_SHOTGUN1: ID := gItemsTexturesID[ITEM_WEAPON_SHOTGUN1];
2911 WEAPON_SHOTGUN2: ID := gItemsTexturesID[ITEM_WEAPON_SHOTGUN2];
2912 WEAPON_SUPERCHAINGUN: ID := gItemsTexturesID[ITEM_WEAPON_SUPERCHAINGUN];
2913 WEAPON_ROCKETLAUNCHER: ID := gItemsTexturesID[ITEM_WEAPON_ROCKETLAUNCHER];
2914 WEAPON_PLASMA: ID := gItemsTexturesID[ITEM_WEAPON_PLASMA];
2915 WEAPON_BFG: ID := gItemsTexturesID[ITEM_WEAPON_BFG];
2916 WEAPON_FLAMETHROWER: ID := gItemsTexturesID[ITEM_WEAPON_FLAMETHROWER];
2917 end;
2919 e_CharFont_GetSize(gMenuFont, s, tw, th);
2920 e_CharFont_PrintEx(gMenuFont, X+178-tw, Y+158, s, _RGB(255, 0, 0));
2921 e_Draw(ID, X+20, Y+160, 0, True, False);
2923 if R_KEY_RED in FInventory then
2924 e_Draw(gItemsTexturesID[ITEM_KEY_RED], X+78, Y+214, 0, True, False);
2926 if R_KEY_GREEN in FInventory then
2927 e_Draw(gItemsTexturesID[ITEM_KEY_GREEN], X+95, Y+214, 0, True, False);
2929 if R_KEY_BLUE in FInventory then
2930 e_Draw(gItemsTexturesID[ITEM_KEY_BLUE], X+112, Y+214, 0, True, False);
2932 if FJetFuel > 0 then
2933 begin
2934 if g_Texture_Get('TEXTURE_PLAYER_HUDAIR', ID) then
2935 e_Draw(ID, X+2, Y+116, 0, True, False);
2936 if g_Texture_Get('TEXTURE_PLAYER_HUDJET', ID) then
2937 e_Draw(ID, X+2, Y+126, 0, True, False);
2938 e_DrawLine(4, X+16, Y+122, X+16+Trunc(168*IfThen(FAir > 0, FAir, 0)/AIR_MAX), Y+122, 0, 0, 196);
2939 e_DrawLine(4, X+16, Y+132, X+16+Trunc(168*FJetFuel/JET_MAX), Y+132, 208, 0, 0);
2941 else
2942 begin
2943 if g_Texture_Get('TEXTURE_PLAYER_HUDAIR', ID) then
2944 e_Draw(ID, X+2, Y+124, 0, True, False);
2945 e_DrawLine(4, X+16, Y+130, X+16+Trunc(168*IfThen(FAir > 0, FAir, 0)/AIR_MAX), Y+130, 0, 0, 196);
2946 end;
2948 if gShowPing and g_Game_IsClient then
2949 begin
2950 s := _lc[I_GAME_PING_HUD] + IntToStr(NetPeer.lastRoundTripTime) + _lc[I_NET_SLIST_PING_MS];
2951 e_TextureFontPrint(X + 4, Y + 242, s, gStdFont);
2952 Y := Y + 16;
2953 end;
2955 if FSpectator then
2956 begin
2957 e_TextureFontPrint(X + 4, Y + 242, _lc[I_PLAYER_SPECT], gStdFont);
2958 e_TextureFontPrint(X + 4, Y + 258, _lc[I_PLAYER_SPECT2], gStdFont);
2959 e_TextureFontPrint(X + 4, Y + 274, _lc[I_PLAYER_SPECT1], gStdFont);
2960 if FNoRespawn then
2961 begin
2962 e_TextureFontGetSize(gStdFont, cw, ch);
2963 s := _lc[I_PLAYER_SPECT4];
2964 e_TextureFontPrintEx(gScreenWidth div 2 - cw*(Length(s) div 2),
2965 gScreenHeight-4-ch, s, gStdFont, 255, 255, 255, 1, True);
2966 e_TextureFontPrint(X + 4, Y + 290, _lc[I_PLAYER_SPECT1S], gStdFont);
2967 end;
2969 end;
2970 end;
2972 procedure TPlayer.DrawOverlay();
2974 dr: Boolean;
2975 begin
2976 // Ïðè âçÿòèè íåóÿçâèìîñòè ðèñóåòñÿ èíâåðñèîííûé áåëûé ôîí
2977 if (FPowerups[MR_INVUL] >= gTime) and (FSpawnInvul < gTime) then
2978 begin
2979 if (FPowerups[MR_INVUL]-gTime) <= 2100 then
2980 dr := not Odd((FPowerups[MR_INVUL]-gTime) div 300)
2981 else
2982 dr := True;
2984 if dr then
2985 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1,
2986 191, 191, 191, 0, TBlending.Invert);
2987 end;
2989 // Ïðè âçÿòèè çàùèòíîãî êîñòþìà ðèñóåòñÿ çåëåíîâàòûé ôîí
2990 if FPowerups[MR_SUIT] >= gTime then
2991 begin
2992 if (FPowerups[MR_SUIT]-gTime) <= 2100 then
2993 dr := not Odd((FPowerups[MR_SUIT]-gTime) div 300)
2994 else
2995 dr := True;
2997 if dr then
2998 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1,
2999 0, 96, 0, 200, TBlending.None);
3000 end;
3002 // Ïðè âçÿòèè áåðñåðêà ðèñóåòñÿ êðàñíîâàòûé ôîí
3003 if (FBerserk >= 0) and (LongWord(FBerserk) >= gTime) and (gFlash = 2) then
3004 begin
3005 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1,
3006 255, 0, 0, 200, TBlending.None);
3007 end;
3008 end;
3010 procedure TPlayer.DrawPain();
3012 a, h: Integer;
3013 begin
3014 if FPain = 0 then Exit;
3016 a := FPain;
3018 if a < 15 then h := 0
3019 else if a < 35 then h := 1
3020 else if a < 55 then h := 2
3021 else if a < 75 then h := 3
3022 else if a < 95 then h := 4
3023 else h := 5;
3025 //if a > 255 then a := 255;
3027 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, 255, 0, 0, 255-h*50);
3028 //e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, 255-min(128, a), 255-a, 255-a, 0, B_FILTER);
3029 end;
3031 procedure TPlayer.DrawPickup();
3033 a, h: Integer;
3034 begin
3035 if FPickup = 0 then Exit;
3037 a := FPickup;
3039 if a < 15 then h := 1
3040 else if a < 35 then h := 2
3041 else if a < 55 then h := 3
3042 else if a < 75 then h := 4
3043 else h := 5;
3045 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, 150, 200, 150, 255-h*50);
3046 end;
3048 procedure TPlayer.DoPunch();
3050 id: DWORD;
3051 st: String;
3052 begin
3053 if FPunchAnim <> nil then
3054 begin
3055 FPunchAnim.reset();
3056 FPunchAnim.Destroy();
3057 end;
3058 st := 'FRAMES_PUNCH';
3059 if R_BERSERK in FInventory then
3060 st := st + '_BERSERK';
3061 if FKeys[KEY_UP].Pressed then
3062 st := st + '_UP'
3063 else if FKeys[KEY_DOWN].Pressed then
3064 st := st + '_DN';
3065 g_Frames_Get(id, st);
3066 FPunchAnim := TAnimation.Create(id, False, 1);
3067 end;
3069 procedure TPlayer.Fire();
3071 f, DidFire: Boolean;
3072 wx, wy, xd, yd: Integer;
3073 locobj: TObj;
3074 ProjID: SizeInt = -1;
3075 begin
3076 if g_Game_IsClient then Exit;
3077 // FBFGFireCounter - âðåìÿ ïåðåä âûñòðåëîì (äëÿ BFG)
3078 // FReloading - âðåìÿ ïîñëå âûñòðåëà (äëÿ âñåãî)
3080 if FSpectator then
3081 begin
3082 Respawn(False);
3083 Exit;
3084 end;
3086 if FReloading[FCurrWeap] <> 0 then Exit;
3088 DidFire := False;
3090 f := False;
3091 wx := FObj.X+WEAPONPOINT[FDirection].X;
3092 wy := FObj.Y+WEAPONPOINT[FDirection].Y;
3093 xd := wx+IfThen(FDirection = TDirection.D_LEFT, -30, 30);
3094 yd := wy+firediry();
3096 case FCurrWeap of
3097 WEAPON_IRONFIST:
3098 begin
3099 DoPunch();
3100 if R_BERSERK in FInventory then
3101 begin
3102 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
3103 locobj.X := FObj.X+FObj.Rect.X;
3104 locobj.Y := FObj.Y+FObj.Rect.Y;
3105 locobj.rect.X := 0;
3106 locobj.rect.Y := 0;
3107 locobj.rect.Width := 39;
3108 locobj.rect.Height := 52;
3109 locobj.Vel.X := (xd-wx) div 2;
3110 locobj.Vel.Y := (yd-wy) div 2;
3111 locobj.Accel.X := xd-wx;
3112 locobj.Accel.y := yd-wy;
3114 if g_Weapon_Hit(@locobj, 50, FUID, HIT_SOME) <> 0 then
3115 begin
3116 {$IFDEF ENABLE_SOUND}
3117 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj.X, FObj.Y)
3118 {$ENDIF}
3120 else
3121 begin
3122 {$IFDEF ENABLE_SOUND}
3123 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj.X, FObj.Y);
3124 {$ENDIF}
3125 end;
3127 if (gFlash = 1) and (FPain < 50) then FPain := min(FPain + 25, 50);
3129 else
3130 g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 3, FUID);
3132 DidFire := True;
3133 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3134 end;
3136 WEAPON_SAW:
3137 begin
3138 {$IFDEF ENABLE_SOUND}
3139 if g_Weapon_chainsaw(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
3140 IfThen(gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF], 9, 3), FUID) <> 0 then
3141 begin
3142 FSawSoundSelect.Stop();
3143 FSawSound.Stop();
3144 FSawSoundHit.PlayAt(FObj.X, FObj.Y);
3146 else if not FSawSoundHit.IsPlaying() then
3147 begin
3148 FSawSoundSelect.Stop();
3149 FSawSound.PlayAt(FObj.X, FObj.Y);
3150 end;
3151 {$ELSE}
3152 g_Weapon_chainsaw(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
3153 IfThen(gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF], 9, 3), FUID);
3154 {$ENDIF}
3156 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3157 DidFire := True;
3158 f := True;
3159 end;
3161 WEAPON_PISTOL:
3162 if FAmmo[A_BULLETS] > 0 then
3163 begin
3164 g_Weapon_pistol(wx, wy, xd, yd, FUID);
3165 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3166 Dec(FAmmo[A_BULLETS]);
3167 FFireAngle := FAngle;
3168 f := True;
3169 DidFire := True;
3170 g_Player_CreateShell(
3171 GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
3172 GameVelX, GameVelY-2,
3173 SHELL_BULLET
3175 end;
3177 WEAPON_SHOTGUN1:
3178 if FAmmo[A_SHELLS] > 0 then
3179 begin
3180 g_Weapon_shotgun(wx, wy, xd, yd, FUID);
3181 {$IFDEF ENABLE_SOUND}
3182 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', wx, wy);
3183 {$ENDIF}
3184 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3185 Dec(FAmmo[A_SHELLS]);
3186 FFireAngle := FAngle;
3187 f := True;
3188 DidFire := True;
3189 FShellTimer := 10;
3190 FShellType := SHELL_SHELL;
3191 end;
3193 WEAPON_SHOTGUN2:
3194 if FAmmo[A_SHELLS] >= 2 then
3195 begin
3196 g_Weapon_dshotgun(wx, wy, xd, yd, FUID);
3197 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3198 Dec(FAmmo[A_SHELLS], 2);
3199 FFireAngle := FAngle;
3200 f := True;
3201 DidFire := True;
3202 FShellTimer := 13;
3203 FShellType := SHELL_DBLSHELL;
3204 end;
3206 WEAPON_CHAINGUN:
3207 if FAmmo[A_BULLETS] > 0 then
3208 begin
3209 g_Weapon_mgun(wx, wy, xd, yd, FUID);
3210 {$IFDEF ENABLE_SOUND}
3211 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', wx, wy);
3212 {$ENDIF}
3213 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3214 Dec(FAmmo[A_BULLETS]);
3215 FFireAngle := FAngle;
3216 f := True;
3217 DidFire := True;
3218 g_Player_CreateShell(
3219 GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
3220 GameVelX, GameVelY-2,
3221 SHELL_BULLET
3223 end;
3225 WEAPON_ROCKETLAUNCHER:
3226 if FAmmo[A_ROCKETS] > 0 then
3227 begin
3228 ProjID := g_Weapon_rocket(wx, wy, xd, yd, FUID);
3229 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3230 Dec(FAmmo[A_ROCKETS]);
3231 FFireAngle := FAngle;
3232 f := True;
3233 DidFire := True;
3234 end;
3236 WEAPON_PLASMA:
3237 if FAmmo[A_CELLS] > 0 then
3238 begin
3239 ProjID := g_Weapon_plasma(wx, wy, xd, yd, FUID);
3240 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3241 Dec(FAmmo[A_CELLS]);
3242 FFireAngle := FAngle;
3243 f := True;
3244 DidFire := True;
3245 end;
3247 WEAPON_BFG:
3248 if (FAmmo[A_CELLS] >= 40) and (FBFGFireCounter = -1) then
3249 begin
3250 FBFGFireCounter := 17;
3251 {$IFDEF ENABLE_SOUND}
3252 if not FNoReload then
3253 g_Sound_PlayExAt('SOUND_WEAPON_STARTFIREBFG', FObj.X, FObj.Y);
3254 {$ENDIF}
3255 Dec(FAmmo[A_CELLS], 40);
3256 DidFire := True;
3257 end;
3259 WEAPON_SUPERCHAINGUN:
3260 if FAmmo[A_SHELLS] > 0 then
3261 begin
3262 g_Weapon_shotgun(wx, wy, xd, yd, FUID);
3263 {$IFDEF ENABLE_SOUND}
3264 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx, wy);
3265 {$ENDIF}
3266 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3267 Dec(FAmmo[A_SHELLS]);
3268 FFireAngle := FAngle;
3269 f := True;
3270 DidFire := True;
3271 g_Player_CreateShell(
3272 GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
3273 GameVelX, GameVelY-2,
3274 SHELL_SHELL
3276 end;
3278 WEAPON_FLAMETHROWER:
3279 if FAmmo[A_FUEL] > 0 then
3280 begin
3281 ProjID := g_Weapon_flame(wx, wy, xd, yd, FUID);
3282 FlamerOn();
3283 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3284 Dec(FAmmo[A_FUEL]);
3285 FFireAngle := FAngle;
3286 f := True;
3287 DidFire := True;
3289 else
3290 begin
3291 FlamerOff();
3292 if g_Game_IsNet and g_Game_IsServer then MH_SEND_PlayerStats(FUID);
3293 end;
3294 end;
3296 if g_Game_IsNet then
3297 begin
3298 if DidFire then
3299 begin
3300 if FCurrWeap <> WEAPON_BFG then
3301 MH_SEND_PlayerFire(FUID, FCurrWeap, wx, wy, xd, yd, ProjID)
3302 else
3303 if not FNoReload then
3304 MH_SEND_Sound(FObj.X, FObj.Y, 'SOUND_WEAPON_STARTFIREBFG');
3305 end;
3307 MH_SEND_PlayerStats(FUID);
3308 end;
3310 if not f then Exit;
3312 case FAngle of
3313 0, 180: SetAction(A_ATTACK);
3314 ANGLE_LEFTDOWN, ANGLE_RIGHTDOWN: SetAction(A_ATTACKDOWN);
3315 ANGLE_LEFTUP, ANGLE_RIGHTUP: SetAction(A_ATTACKUP);
3316 end;
3317 end;
3319 function TPlayer.GetAmmoByWeapon(Weapon: Byte): Word;
3320 begin
3321 case Weapon of
3322 WEAPON_PISTOL, WEAPON_CHAINGUN: Result := FAmmo[A_BULLETS];
3323 WEAPON_SHOTGUN1, WEAPON_SHOTGUN2, WEAPON_SUPERCHAINGUN: Result := FAmmo[A_SHELLS];
3324 WEAPON_ROCKETLAUNCHER: Result := FAmmo[A_ROCKETS];
3325 WEAPON_PLASMA, WEAPON_BFG: Result := FAmmo[A_CELLS];
3326 WEAPON_FLAMETHROWER: Result := FAmmo[A_FUEL];
3327 else Result := 0;
3328 end;
3329 end;
3331 function TPlayer.HeadInLiquid(XInc, YInc: Integer): Boolean;
3332 begin
3333 Result := g_Map_CollidePanel(FObj.X+PLAYER_HEADRECT.X+XInc, FObj.Y+PLAYER_HEADRECT.Y+YInc,
3334 PLAYER_HEADRECT.Width, PLAYER_HEADRECT.Height,
3335 PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, True);
3336 end;
3338 procedure TPlayer.FlamerOn;
3339 begin
3340 {$IFDEF ENABLE_SOUND}
3341 FFlameSoundOff.Stop();
3342 FFlameSoundOff.SetPosition(0);
3343 if FFlaming then
3344 begin
3345 if (not FFlameSoundOn.IsPlaying()) and (not FFlameSoundWork.IsPlaying()) then
3346 FFlameSoundWork.PlayAt(FObj.X, FObj.Y);
3348 else
3349 begin
3350 FFlameSoundOn.PlayAt(FObj.X, FObj.Y);
3351 end;
3352 {$ENDIF}
3353 FFlaming := True;
3354 end;
3356 procedure TPlayer.FlamerOff;
3357 begin
3358 {$IFDEF ENABLE_SOUND}
3359 if FFlaming then
3360 begin
3361 FFlameSoundOn.Stop();
3362 FFlameSoundOn.SetPosition(0);
3363 FFlameSoundWork.Stop();
3364 FFlameSoundWork.SetPosition(0);
3365 FFlameSoundOff.PlayAt(FObj.X, FObj.Y);
3366 end;
3367 {$ENDIF}
3368 FFlaming := False;
3369 end;
3371 procedure TPlayer.JetpackOn;
3372 begin
3373 {$IFDEF ENABLE_SOUND}
3374 FJetSoundFly.Stop;
3375 FJetSoundOff.Stop;
3376 FJetSoundOn.SetPosition(0);
3377 FJetSoundOn.PlayAt(FObj.X, FObj.Y);
3378 {$ENDIF}
3379 FlySmoke(8);
3380 end;
3382 procedure TPlayer.JetpackOff;
3383 begin
3384 {$IFDEF ENABLE_SOUND}
3385 FJetSoundFly.Stop;
3386 FJetSoundOn.Stop;
3387 FJetSoundOff.SetPosition(0);
3388 FJetSoundOff.PlayAt(FObj.X, FObj.Y);
3389 {$ENDIF}
3390 end;
3392 procedure TPlayer.CatchFire(Attacker: Word; Timeout: Integer = PLAYER_BURN_TIME);
3393 begin
3394 if Timeout <= 0 then
3395 exit;
3396 if (FPowerups[MR_SUIT] > gTime) or (FPowerups[MR_INVUL] > gTime) then
3397 exit; // Íå çàãîðàåìñÿ êîãäà åñòü çàùèòà
3398 if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2) then
3399 exit; // Íå ïîäãîðàåì â âîäå íà âñÿêèé ñëó÷àé
3400 {$IFDEF ENABLE_SOUND}
3401 if FFireTime <= 0 then
3402 g_Sound_PlayExAt('SOUND_IGNITE', FObj.X, FObj.Y);
3403 {$ENDIF}
3404 FFireTime := Timeout;
3405 FFireAttacker := Attacker;
3406 if g_Game_IsNet and g_Game_IsServer then
3407 MH_SEND_PlayerStats(FUID);
3408 end;
3410 procedure TPlayer.Jump();
3411 begin
3412 if gFly or FJetpack then
3413 begin
3414 // Ïîëåò (÷èò-êîä èëè äæåòïàê):
3415 if FObj.Vel.Y > -VEL_FLY then
3416 FObj.Vel.Y := FObj.Vel.Y - 3;
3417 if FJetpack then
3418 begin
3419 if FJetFuel > 0 then
3420 Dec(FJetFuel);
3421 if (FJetFuel < 1) and g_Game_IsServer then
3422 begin
3423 FJetpack := False;
3424 JetpackOff;
3425 if g_Game_IsNet then
3426 MH_SEND_PlayerStats(FUID);
3427 end;
3428 end;
3429 Exit;
3430 end;
3432 // Íå âêëþ÷àòü äæåòïàê â ðåæèìå ïðîõîæäåíèÿ ñêâîçü ñòåíû
3433 if FGhost then
3434 FCanJetpack := False;
3436 // Ïðûãàåì èëè âñïëûâàåì:
3437 if (CollideLevel(0, 1) or
3438 g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y+36, PLAYER_RECT.Width,
3439 PLAYER_RECT.Height-33, PANEL_STEP, False)
3440 ) and (FObj.Accel.Y = 0) then // Íå ïðûãàòü, åñëè åñòü âåðòèêàëüíîå óñêîðåíèå
3441 begin
3442 FObj.Vel.Y := -VEL_JUMP;
3443 FCanJetpack := False;
3445 else
3446 begin
3447 if BodyInLiquid(0, 0) then
3448 FObj.Vel.Y := -VEL_SW
3449 else if (FJetFuel > 0) and FCanJetpack and
3450 g_Game_IsServer and (not g_Obj_CollideLiquid(@FObj, 0, 0)) then
3451 begin
3452 FJetpack := True;
3453 JetpackOn;
3454 if g_Game_IsNet then
3455 MH_SEND_PlayerStats(FUID);
3456 end;
3457 end;
3458 end;
3460 procedure TPlayer.Kill(KillType: Byte; SpawnerUID: Word; t: Byte);
3462 a, i, k, ab, ar: Byte;
3463 s: String;
3464 mon: TMonster;
3465 plr: TPlayer;
3466 srv, netsrv: Boolean;
3467 DoFrags: Boolean;
3468 OldLR: Byte;
3469 KP: TPlayer;
3470 it: PItem;
3472 procedure PushItem(t: Byte);
3474 id: DWORD;
3475 begin
3476 id := g_Items_Create(FObj.X, FObj.Y, t, True, False);
3477 it := g_Items_ByIdx(id);
3478 if KillType = K_EXTRAHARDKILL then // -7..+7; -8..0
3479 begin
3480 g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-7+Random(15),
3481 (FObj.Vel.Y div 2)-Random(9));
3482 it.positionChanged(); // this updates spatial accelerators
3484 else
3485 begin
3486 if KillType = K_HARDKILL then // -5..+5; -5..0
3487 begin
3488 g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-5+Random(11),
3489 (FObj.Vel.Y div 2)-Random(6));
3491 else // -3..+3; -3..0
3492 begin
3493 g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-3+Random(7),
3494 (FObj.Vel.Y div 2)-Random(4));
3495 end;
3496 it.positionChanged(); // this updates spatial accelerators
3497 end;
3499 if g_Game_IsNet and g_Game_IsServer then
3500 MH_SEND_ItemSpawn(True, id);
3501 end;
3503 begin
3504 DoFrags := (gGameSettings.MaxLives = 0) or (gGameSettings.GameMode = GM_COOP);
3505 Srv := g_Game_IsServer;
3506 Netsrv := g_Game_IsServer and g_Game_IsNet;
3507 if Srv then FDeath := FDeath + 1;
3508 if FAlive then
3509 begin
3510 if FGhost then
3511 FGhost := False;
3512 if not FPhysics then
3513 FPhysics := True;
3514 FAlive := False;
3515 end;
3516 FShellTimer := -1;
3518 if (gGameSettings.MaxLives > 0) and Srv and (gLMSRespawn = LMS_RESPAWN_NONE) then
3519 begin
3520 if FLives > 0 then FLives := FLives - 1;
3521 if FLives = 0 then FNoRespawn := True;
3522 end;
3524 // Íîìåð òèïà ñìåðòè:
3525 a := 1;
3526 case KillType of
3527 K_SIMPLEKILL: a := 1;
3528 K_HARDKILL: a := 2;
3529 K_EXTRAHARDKILL: a := 3;
3530 K_FALLKILL: a := 4;
3531 end;
3533 // Çâóê ñìåðòè:
3534 {$IFDEF ENABLE_SOUND}
3535 if not FModel.PlaySound(MODELSOUND_DIE, a, FObj.X, FObj.Y) then
3536 for i := 1 to 3 do
3537 if FModel.PlaySound(MODELSOUND_DIE, i, FObj.X, FObj.Y) then
3538 Break;
3539 {$ENDIF}
3541 // Âðåìÿ ðåñïàóíà:
3542 if Srv then
3543 case KillType of
3544 K_SIMPLEKILL:
3545 FTime[T_RESPAWN] := gTime + TIME_RESPAWN1;
3546 K_HARDKILL:
3547 FTime[T_RESPAWN] := gTime + TIME_RESPAWN2;
3548 K_EXTRAHARDKILL, K_FALLKILL:
3549 FTime[T_RESPAWN] := gTime + TIME_RESPAWN3;
3550 end;
3552 // Ïåðåêëþ÷àåì ñîñòîÿíèå:
3553 case KillType of
3554 K_SIMPLEKILL:
3555 SetAction(A_DIE1);
3556 K_HARDKILL, K_EXTRAHARDKILL:
3557 SetAction(A_DIE2);
3558 end;
3560 // Ðåàêöèÿ ìîíñòðîâ íà ñìåðòü èãðîêà:
3561 if (KillType <> K_FALLKILL) and (Srv) then
3562 g_Monsters_killedp();
3564 if SpawnerUID = FUID then
3565 begin // Ñàìîóáèëñÿ
3566 if Srv then
3567 begin
3568 if gGameSettings.GameMode = GM_TDM then
3569 Dec(gTeamStat[FTeam].Score);
3570 if DoFrags or (gGameSettings.GameMode = GM_TDM) then
3571 begin
3572 Dec(FFrags);
3573 FLastFrag := 0;
3574 end;
3575 end;
3576 g_Console_Add(Format(_lc[I_PLAYER_KILL_SELF], [FName]), True);
3578 else
3579 if g_GetUIDType(SpawnerUID) = UID_PLAYER then
3580 begin // Óáèò äðóãèì èãðîêîì
3581 KP := g_Player_Get(SpawnerUID);
3582 if (KP <> nil) and Srv then
3583 begin
3584 if (DoFrags or (gGameSettings.GameMode = GM_TDM)) then
3585 if SameTeam(FUID, SpawnerUID) then
3586 begin
3587 Dec(KP.FFrags);
3588 KP.FLastFrag := 0;
3589 end else
3590 begin
3591 Inc(KP.FFrags);
3592 KP.FragCombo();
3593 end;
3595 if (gGameSettings.GameMode = GM_TDM) and DoFrags then
3596 Inc(gTeamStat[KP.Team].Score,
3597 IfThen(SameTeam(FUID, SpawnerUID), -1, 1));
3599 if netsrv then MH_SEND_PlayerStats(SpawnerUID);
3600 end;
3602 plr := g_Player_Get(SpawnerUID);
3603 if plr = nil then
3604 s := '?'
3605 else
3606 s := plr.FName;
3608 case KillType of
3609 K_HARDKILL:
3610 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_2],
3611 [FName, s]),
3612 gShowKillMsg);
3613 K_EXTRAHARDKILL:
3614 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_1],
3615 [FName, s]),
3616 gShowKillMsg);
3617 else
3618 g_Console_Add(Format(_lc[I_PLAYER_KILL],
3619 [FName, s]),
3620 gShowKillMsg);
3621 end;
3623 else if g_GetUIDType(SpawnerUID) = UID_MONSTER then
3624 begin // Óáèò ìîíñòðîì
3625 mon := g_Monsters_ByUID(SpawnerUID);
3626 if mon = nil then
3627 s := '?'
3628 else
3629 s := g_Mons_GetKilledByTypeId(mon.MonsterType);
3631 case KillType of
3632 K_HARDKILL:
3633 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_2],
3634 [FName, s]),
3635 gShowKillMsg);
3636 K_EXTRAHARDKILL:
3637 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_1],
3638 [FName, s]),
3639 gShowKillMsg);
3640 else
3641 g_Console_Add(Format(_lc[I_PLAYER_KILL],
3642 [FName, s]),
3643 gShowKillMsg);
3644 end;
3646 else // Îñîáûå òèïû ñìåðòè
3647 case t of
3648 HIT_DISCON: ;
3649 HIT_SELF: g_Console_Add(Format(_lc[I_PLAYER_KILL_SELF], [FName]), True);
3650 HIT_FALL: g_Console_Add(Format(_lc[I_PLAYER_KILL_FALL], [FName]), True);
3651 HIT_WATER: g_Console_Add(Format(_lc[I_PLAYER_KILL_WATER], [FName]), True);
3652 HIT_ACID: g_Console_Add(Format(_lc[I_PLAYER_KILL_ACID], [FName]), True);
3653 HIT_TRAP: g_Console_Add(Format(_lc[I_PLAYER_KILL_TRAP], [FName]), True);
3654 else g_Console_Add(Format(_lc[I_PLAYER_DIED], [FName]), True);
3655 end;
3657 if Srv then
3658 begin
3659 // Âûáðîñ îðóæèÿ:
3660 for a := WP_FIRST to WP_LAST do
3661 if FWeapon[a] then
3662 begin
3663 case a of
3664 WEAPON_SAW: i := ITEM_WEAPON_SAW;
3665 WEAPON_SHOTGUN1: i := ITEM_WEAPON_SHOTGUN1;
3666 WEAPON_SHOTGUN2: i := ITEM_WEAPON_SHOTGUN2;
3667 WEAPON_CHAINGUN: i := ITEM_WEAPON_CHAINGUN;
3668 WEAPON_ROCKETLAUNCHER: i := ITEM_WEAPON_ROCKETLAUNCHER;
3669 WEAPON_PLASMA: i := ITEM_WEAPON_PLASMA;
3670 WEAPON_BFG: i := ITEM_WEAPON_BFG;
3671 WEAPON_SUPERCHAINGUN: i := ITEM_WEAPON_SUPERCHAINGUN;
3672 WEAPON_FLAMETHROWER: i := ITEM_WEAPON_FLAMETHROWER;
3673 else i := 0;
3674 end;
3676 if i <> 0 then
3677 PushItem(i);
3678 end;
3680 // Âûáðîñ ðþêçàêà:
3681 if R_ITEM_BACKPACK in FInventory then
3682 PushItem(ITEM_AMMO_BACKPACK);
3684 // Âûáðîñ ðàêåòíîãî ðàíöà:
3685 if FJetFuel > 0 then
3686 PushItem(ITEM_JETPACK);
3688 // Âûáðîñ êëþ÷åé:
3689 if (not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) or
3690 (not (TGameOption.DM_KEYS in gGameSettings.Options)) then
3691 begin
3692 if R_KEY_RED in FInventory then
3693 PushItem(ITEM_KEY_RED);
3695 if R_KEY_GREEN in FInventory then
3696 PushItem(ITEM_KEY_GREEN);
3698 if R_KEY_BLUE in FInventory then
3699 PushItem(ITEM_KEY_BLUE);
3700 end;
3702 // Âûáðîñ ôëàãà:
3703 DropFlag(KillType = K_FALLKILL);
3704 end;
3706 FCorpse := g_Player_CreateCorpse(Self);
3708 if Srv and (gGameSettings.MaxLives > 0) and FNoRespawn and
3709 (gLMSRespawn = LMS_RESPAWN_NONE) then
3710 begin
3711 a := 0;
3712 k := 0;
3713 ar := 0;
3714 ab := 0;
3715 for i := Low(gPlayers) to High(gPlayers) do
3716 begin
3717 if gPlayers[i] = nil then continue;
3718 if (not gPlayers[i].FNoRespawn) and (not gPlayers[i].FSpectator) then
3719 begin
3720 Inc(a);
3721 if gPlayers[i].FTeam = TEAM_RED then Inc(ar)
3722 else if gPlayers[i].FTeam = TEAM_BLUE then Inc(ab);
3723 k := i;
3724 end;
3725 end;
3727 OldLR := gLMSRespawn;
3728 if (gGameSettings.GameMode = GM_COOP) then
3729 begin
3730 if (a = 0) then
3731 begin
3732 // everyone is dead, restart the map
3733 g_Game_Message(_lc[I_MESSAGE_LMS_LOSE], 144);
3734 if Netsrv then
3735 MH_SEND_GameEvent(NET_EV_LMS_LOSE);
3736 gLMSRespawn := LMS_RESPAWN_FINAL;
3737 gLMSRespawnTime := gTime + 5000;
3739 else if (a = 1) then
3740 begin
3741 if (gPlayers[k] <> nil) and not (gPlayers[k] is TBot) then
3742 if (gPlayers[k] = gPlayer1) or
3743 (gPlayers[k] = gPlayer2) then
3744 g_Console_Add('*** ' + _lc[I_MESSAGE_LMS_SURVIVOR] + ' ***', True)
3745 else if Netsrv and (gPlayers[k].FClientID >= 0) then
3746 MH_SEND_GameEvent(NET_EV_LMS_SURVIVOR, 0, 'N', gPlayers[k].FClientID);
3747 end;
3749 else if (gGameSettings.GameMode = GM_TDM) then
3750 begin
3751 if (ab = 0) and (ar <> 0) then
3752 begin
3753 // blu team ded
3754 g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 144);
3755 if Netsrv then
3756 MH_SEND_GameEvent(NET_EV_TLMS_WIN, TEAM_RED);
3757 Inc(gTeamStat[TEAM_RED].Score);
3758 gLMSRespawn := LMS_RESPAWN_FINAL;
3759 gLMSRespawnTime := gTime + 5000;
3761 else if (ar = 0) and (ab <> 0) then
3762 begin
3763 // red team ded
3764 g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 144);
3765 if Netsrv then
3766 MH_SEND_GameEvent(NET_EV_TLMS_WIN, TEAM_BLUE);
3767 Inc(gTeamStat[TEAM_BLUE].Score);
3768 gLMSRespawn := LMS_RESPAWN_FINAL;
3769 gLMSRespawnTime := gTime + 5000;
3771 else if (ar = 0) and (ab = 0) then
3772 begin
3773 // everyone ded
3774 g_Game_Message(_lc[I_GAME_WIN_DRAW], 144);
3775 if Netsrv then
3776 MH_SEND_GameEvent(NET_EV_LMS_DRAW, 0, FName);
3777 gLMSRespawn := LMS_RESPAWN_FINAL;
3778 gLMSRespawnTime := gTime + 5000;
3779 end;
3781 else if (gGameSettings.GameMode = GM_DM) then
3782 begin
3783 if (a = 1) then
3784 begin
3785 if gPlayers[k] <> nil then
3786 with gPlayers[k] do
3787 begin
3788 // survivor is the winner
3789 g_Game_Message(Format(_lc[I_MESSAGE_LMS_WIN], [AnsiUpperCase(FName)]), 144);
3790 if Netsrv then
3791 MH_SEND_GameEvent(NET_EV_LMS_WIN, 0, FName);
3792 Inc(FFrags);
3793 end;
3794 gLMSRespawn := LMS_RESPAWN_FINAL;
3795 gLMSRespawnTime := gTime + 5000;
3797 else if (a = 0) then
3798 begin
3799 // everyone is dead, restart the map
3800 g_Game_Message(_lc[I_GAME_WIN_DRAW], 144);
3801 if Netsrv then
3802 MH_SEND_GameEvent(NET_EV_LMS_DRAW, 0, FName);
3803 gLMSRespawn := LMS_RESPAWN_FINAL;
3804 gLMSRespawnTime := gTime + 5000;
3805 end;
3806 end;
3807 if srv and (OldLR = LMS_RESPAWN_NONE) and (gLMSRespawn > LMS_RESPAWN_NONE) then
3808 begin
3809 if NetMode = NET_SERVER then
3810 MH_SEND_GameEvent(NET_EV_LMS_WARMUP, gLMSRespawnTime - gTime)
3811 else
3812 g_Console_Add(Format(_lc[I_MSG_WARMUP_START], [(gLMSRespawnTime - gTime) div 1000]), True);
3813 end;
3814 end;
3816 if Netsrv then
3817 begin
3818 MH_SEND_PlayerStats(FUID);
3819 MH_SEND_PlayerDeath(FUID, KillType, t, SpawnerUID);
3820 if gGameSettings.GameMode = GM_TDM then MH_SEND_GameStats;
3821 end;
3823 if srv and FNoRespawn then Spectate(True);
3824 FWantsInGame := True;
3825 end;
3827 function TPlayer.BodyInLiquid(XInc, YInc: Integer): Boolean;
3828 begin
3829 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc, PLAYER_RECT.Width,
3830 PLAYER_RECT.Height-20, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, False);
3831 end;
3833 function TPlayer.BodyInAcid(XInc, YInc: Integer): Boolean;
3834 begin
3835 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc, PLAYER_RECT.Width,
3836 PLAYER_RECT.Height-20, PANEL_ACID1 or PANEL_ACID2, False);
3837 end;
3839 procedure TPlayer.MakeBloodSimple(Count: Word);
3840 begin
3841 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)+8,
3842 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
3843 Count div 2, 3, -1, 16, (PLAYER_RECT.Height*2 div 3),
3844 FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind);
3845 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-8,
3846 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
3847 Count div 2, -3, -1, 16, (PLAYER_RECT.Height*2) div 3,
3848 FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind);
3849 end;
3851 procedure TPlayer.MakeBloodVector(Count: Word; VelX, VelY: Integer);
3852 begin
3853 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
3854 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
3855 Count, VelX, VelY, 16, (PLAYER_RECT.Height*2) div 3,
3856 FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind);
3857 end;
3859 procedure TPlayer.ProcessWeaponAction(Action: Byte);
3860 begin
3861 if g_Game_IsClient then Exit;
3862 case Action of
3863 WP_PREV: PrevWeapon();
3864 WP_NEXT: NextWeapon();
3865 end;
3866 end;
3868 procedure TPlayer.QueueWeaponSwitch(Weapon: Byte);
3869 begin
3870 if g_Game_IsClient then Exit;
3871 if Weapon > High(FWeapon) then Exit;
3872 FNextWeap := FNextWeap or (1 shl Weapon);
3873 end;
3875 procedure TPlayer.resetWeaponQueue ();
3876 begin
3877 FNextWeap := 0;
3878 FNextWeapDelay := 0;
3879 end;
3881 function TPlayer.hasAmmoForWeapon (weapon: Byte): Boolean;
3882 begin
3883 result := false;
3884 case weapon of
3885 WEAPON_IRONFIST, WEAPON_SAW: result := true;
3886 WEAPON_SHOTGUN1, WEAPON_SHOTGUN2, WEAPON_SUPERCHAINGUN: result := (FAmmo[A_SHELLS] > 0);
3887 WEAPON_PISTOL, WEAPON_CHAINGUN: result := (FAmmo[A_BULLETS] > 0);
3888 WEAPON_ROCKETLAUNCHER: result := (FAmmo[A_ROCKETS] > 0);
3889 WEAPON_PLASMA, WEAPON_BFG: result := (FAmmo[A_CELLS] > 0);
3890 WEAPON_FLAMETHROWER: result := (FAmmo[A_FUEL] > 0);
3891 else result := (weapon < length(FWeapon));
3892 end;
3893 end;
3895 function TPlayer.hasAmmoForShooting (weapon: Byte): Boolean;
3896 begin
3897 result := false;
3898 case weapon of
3899 WEAPON_IRONFIST, WEAPON_SAW: result := true;
3900 WEAPON_SHOTGUN1, WEAPON_SUPERCHAINGUN: result := (FAmmo[A_SHELLS] > 0);
3901 WEAPON_SHOTGUN2: result := (FAmmo[A_SHELLS] > 1);
3902 WEAPON_PISTOL, WEAPON_CHAINGUN: result := (FAmmo[A_BULLETS] > 0);
3903 WEAPON_ROCKETLAUNCHER: result := (FAmmo[A_ROCKETS] > 0);
3904 WEAPON_PLASMA: result := (FAmmo[A_CELLS] > 0);
3905 WEAPON_BFG: result := (FAmmo[A_CELLS] >= 40);
3906 WEAPON_FLAMETHROWER: result := (FAmmo[A_FUEL] > 0);
3907 else result := (weapon < length(FWeapon));
3908 end;
3909 end;
3911 function TPlayer.shouldSwitch (weapon: Byte; hadWeapon: Boolean): Boolean;
3912 begin
3913 result := false;
3914 if (weapon > WP_LAST + 1) then
3915 begin
3916 result := false;
3917 exit;
3918 end;
3919 if (FWeapSwitchMode = 1) and not hadWeapon then
3920 result := true
3921 else if (FWeapSwitchMode = 2) then
3922 result := (FWeapPreferences[weapon] > FWeapPreferences[FCurrWeap]);
3923 end;
3925 // return 255 for "no switch"
3926 function TPlayer.getNextWeaponIndex (): Byte;
3928 i: Word;
3929 wantThisWeapon: array[0..64] of Boolean;
3930 wwc: Integer = 0; //HACK!
3931 dir, cwi: Integer;
3932 begin
3933 result := 255; // default result: "no switch"
3934 //e_LogWriteFln('FSWITCHTOEMPTY: %s', [FSwitchToEmpty], TMsgType.Notify);
3935 // had weapon cycling on previous frame? remove that flag
3936 if (FNextWeap and $2000) <> 0 then
3937 begin
3938 FNextWeap := FNextWeap and $1FFF;
3939 FNextWeapDelay := 0;
3940 end;
3941 // cycling has priority
3942 if (FNextWeap and $C000) <> 0 then
3943 begin
3944 if (FNextWeap and $8000) <> 0 then
3945 dir := 1
3946 else
3947 dir := -1;
3948 FNextWeap := FNextWeap or $2000; // we need this
3949 if FNextWeapDelay > 0 then
3950 exit; // cooldown time
3951 cwi := FCurrWeap;
3952 for i := 0 to High(FWeapon) do
3953 begin
3954 cwi := (cwi+length(FWeapon)+dir) mod length(FWeapon);
3955 if FWeapon[cwi] and maySwitch(cwi) then
3956 begin
3957 //e_LogWriteFln(' SWITCH: cur=%d; new=%d %s %s', [FCurrWeap, cwi, FSwitchToEmpty, hasAmmoForWeapon(cwi)], TMsgType.Notify);
3958 result := Byte(cwi);
3959 FNextWeapDelay := WEAPON_DELAY;
3960 exit;
3961 end;
3962 end;
3963 resetWeaponQueue();
3964 exit;
3965 end;
3966 // no cycling
3967 for i := 0 to High(wantThisWeapon) do
3968 wantThisWeapon[i] := false;
3969 for i := 0 to High(FWeapon) do
3970 if (FNextWeap and (1 shl i)) <> 0 then
3971 begin
3972 wantThisWeapon[i] := true;
3973 Inc(wwc);
3974 end;
3976 // exclude currently selected weapon from the set
3977 wantThisWeapon[FCurrWeap] := false;
3978 // slow down alterations a little
3979 if wwc > 1 then
3980 begin
3981 //e_WriteLog(Format(' FNextWeap=%x; delay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
3982 // more than one weapon requested, assume "alteration" and check alteration delay
3983 if FNextWeapDelay > 0 then
3984 begin
3985 FNextWeap := 0;
3986 exit;
3987 end; // yeah
3988 end;
3989 // do not reset weapon queue, it will be done in `RealizeCurrentWeapon()`
3990 // but clear all counters if no weapon should be switched
3991 if wwc < 1 then
3992 begin
3993 resetWeaponQueue();
3994 exit;
3995 end;
3996 //e_WriteLog(Format('wwc=%d', [wwc]), MSG_WARNING);
3997 // try weapons in descending order
3998 for i := High(FWeapon) downto 0 do
3999 begin
4000 if wantThisWeapon[i] and FWeapon[i] and ((wwc = 1) or hasAmmoForWeapon(i)) then
4001 begin
4002 // i found her!
4003 result := Byte(i);
4004 resetWeaponQueue();
4005 FNextWeapDelay := WEAPON_DELAY * 2; // anyway, 'cause why not
4006 //e_LogWriteFln('FOUND %s %s %s', [result, FSwitchToEmpty, hasAmmoForWeapon(i)], TMsgType.Notify);
4007 exit;
4008 end;
4009 end;
4010 // no suitable weapon found, so reset the queue, to avoid accidental "queuing" of weapon w/o ammo
4011 resetWeaponQueue();
4012 end;
4014 procedure TPlayer.RealizeCurrentWeapon();
4015 function switchAllowed (): Boolean;
4017 i: Byte;
4018 begin
4019 result := false;
4020 if FBFGFireCounter <> -1 then
4021 exit;
4022 if FTime[T_SWITCH] > gTime then
4023 exit;
4024 for i := WP_FIRST to WP_LAST do
4025 if FReloading[i] > 0 then
4026 exit;
4027 result := true;
4028 end;
4031 nw: Byte;
4032 begin
4033 //e_WriteLog(Format('***RealizeCurrentWeapon: FNextWeap=%x; FNextWeapDelay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
4034 //FNextWeap := FNextWeap and $1FFF;
4035 if FNextWeapDelay > 0 then Dec(FNextWeapDelay); // "alteration delay"
4037 if not switchAllowed then
4038 begin
4039 //HACK for weapon cycling
4040 if (FNextWeap and $E000) <> 0 then FNextWeap := 0;
4041 exit;
4042 end;
4044 nw := getNextWeaponIndex();
4046 if nw = 255 then exit; // don't reset anything here
4047 if nw > High(FWeapon) then
4048 begin
4049 // don't forget to reset queue here!
4050 //e_WriteLog(' RealizeCurrentWeapon: WUTAFUUUU', MSG_WARNING);
4051 resetWeaponQueue();
4052 exit;
4053 end;
4055 if FWeapon[nw] then
4056 begin
4057 FCurrWeap := nw;
4058 FTime[T_SWITCH] := gTime+156;
4059 {$IFDEF ENABLE_SOUND}
4060 if FCurrWeap = WEAPON_SAW then FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
4061 {$ENDIF}
4062 FModel.SetWeapon(FCurrWeap);
4063 if g_Game_IsNet then MH_SEND_PlayerStats(FUID);
4064 end;
4065 end;
4067 procedure TPlayer.NextWeapon();
4068 begin
4069 if g_Game_IsClient then Exit;
4070 FNextWeap := $8000;
4071 end;
4073 procedure TPlayer.PrevWeapon();
4074 begin
4075 if g_Game_IsClient then Exit;
4076 FNextWeap := $4000;
4077 end;
4079 procedure TPlayer.SetWeapon(W: Byte);
4080 begin
4081 {$IFDEF ENABLE_SOUND}
4082 if FCurrWeap <> W then
4083 if W = WEAPON_SAW then
4084 FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
4085 {$ENDIF}
4087 FCurrWeap := W;
4088 FModel.SetWeapon(CurrWeap);
4089 resetWeaponQueue();
4090 end;
4092 function TPlayer.PickItem(ItemType: Byte; arespawn: Boolean; var remove: Boolean): Boolean;
4094 a: Boolean;
4095 switchWeapon: Byte = 255;
4096 hadWeapon: Boolean = False;
4097 begin
4098 Result := False;
4099 if g_Game_IsClient then Exit;
4101 // a = true - ìåñòî ñïàâíà ïðåäìåòà:
4102 a := (TGameOption.WEAPONS_STAY in gGameSettings.Options) and arespawn;
4103 remove := not a;
4104 case ItemType of
4105 ITEM_MEDKIT_SMALL:
4106 if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
4107 begin
4108 if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 10, PLAYER_HP_SOFT);
4109 Result := True;
4110 remove := True;
4111 FFireTime := 0;
4112 if gFlash = 2 then Inc(FPickup, 5);
4113 end;
4115 ITEM_MEDKIT_LARGE:
4116 if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
4117 begin
4118 if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 25, PLAYER_HP_SOFT);
4119 Result := True;
4120 remove := True;
4121 FFireTime := 0;
4122 if gFlash = 2 then Inc(FPickup, 5);
4123 end;
4125 ITEM_ARMOR_GREEN:
4126 if FArmor < PLAYER_AP_SOFT then
4127 begin
4128 FArmor := PLAYER_AP_SOFT;
4129 Result := True;
4130 remove := True;
4131 if gFlash = 2 then Inc(FPickup, 5);
4132 end;
4134 ITEM_ARMOR_BLUE:
4135 if FArmor < PLAYER_AP_LIMIT then
4136 begin
4137 FArmor := PLAYER_AP_LIMIT;
4138 Result := True;
4139 remove := True;
4140 if gFlash = 2 then Inc(FPickup, 5);
4141 end;
4143 ITEM_SPHERE_BLUE:
4144 if (FHealth < PLAYER_HP_LIMIT) or (FFireTime > 0) then
4145 begin
4146 if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 100, PLAYER_HP_LIMIT);
4147 Result := True;
4148 remove := True;
4149 FFireTime := 0;
4150 if gFlash = 2 then Inc(FPickup, 5);
4151 end;
4153 ITEM_SPHERE_WHITE:
4154 if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) or (FFireTime > 0) then
4155 begin
4156 if FHealth < PLAYER_HP_LIMIT then
4157 FHealth := PLAYER_HP_LIMIT;
4158 if FArmor < PLAYER_AP_LIMIT then
4159 FArmor := PLAYER_AP_LIMIT;
4160 Result := True;
4161 remove := True;
4162 FFireTime := 0;
4163 if gFlash = 2 then Inc(FPickup, 5);
4164 end;
4166 ITEM_WEAPON_SAW:
4167 if (not FWeapon[WEAPON_SAW]) or ((not arespawn) and (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) then
4168 begin
4169 hadWeapon := FWeapon[WEAPON_SAW];
4170 switchWeapon := WEAPON_SAW;
4171 FWeapon[WEAPON_SAW] := True;
4172 Result := True;
4173 if gFlash = 2 then Inc(FPickup, 5);
4174 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4175 end;
4177 ITEM_WEAPON_SHOTGUN1:
4178 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SHOTGUN1] then
4179 begin
4180 // Íóæíî, ÷òîáû íå âçÿòü âñå ïóëè ñðàçó:
4181 if a and FWeapon[WEAPON_SHOTGUN1] then Exit;
4182 hadWeapon := FWeapon[WEAPON_SHOTGUN1];
4183 switchWeapon := WEAPON_SHOTGUN1;
4184 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
4185 FWeapon[WEAPON_SHOTGUN1] := True;
4186 Result := True;
4187 if gFlash = 2 then Inc(FPickup, 5);
4188 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4189 end;
4191 ITEM_WEAPON_SHOTGUN2:
4192 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SHOTGUN2] then
4193 begin
4194 if a and FWeapon[WEAPON_SHOTGUN2] then Exit;
4195 hadWeapon := FWeapon[WEAPON_SHOTGUN2];
4196 switchWeapon := WEAPON_SHOTGUN2;
4197 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
4198 FWeapon[WEAPON_SHOTGUN2] := True;
4199 Result := True;
4200 if gFlash = 2 then Inc(FPickup, 5);
4201 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4202 end;
4204 ITEM_WEAPON_CHAINGUN:
4205 if (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or not FWeapon[WEAPON_CHAINGUN] then
4206 begin
4207 if a and FWeapon[WEAPON_CHAINGUN] then Exit;
4208 hadWeapon := FWeapon[WEAPON_CHAINGUN];
4209 switchWeapon := WEAPON_CHAINGUN;
4210 IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
4211 FWeapon[WEAPON_CHAINGUN] := True;
4212 Result := True;
4213 if gFlash = 2 then Inc(FPickup, 5);
4214 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4215 end;
4217 ITEM_WEAPON_ROCKETLAUNCHER:
4218 if (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or not FWeapon[WEAPON_ROCKETLAUNCHER] then
4219 begin
4220 if a and FWeapon[WEAPON_ROCKETLAUNCHER] then Exit;
4221 switchWeapon := WEAPON_ROCKETLAUNCHER;
4222 hadWeapon := FWeapon[WEAPON_ROCKETLAUNCHER];
4223 IncMax(FAmmo[A_ROCKETS], 2, FMaxAmmo[A_ROCKETS]);
4224 FWeapon[WEAPON_ROCKETLAUNCHER] := True;
4225 Result := True;
4226 if gFlash = 2 then Inc(FPickup, 5);
4227 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4228 end;
4230 ITEM_WEAPON_PLASMA:
4231 if (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or not FWeapon[WEAPON_PLASMA] then
4232 begin
4233 if a and FWeapon[WEAPON_PLASMA] then Exit;
4234 switchWeapon := WEAPON_PLASMA;
4235 hadWeapon := FWeapon[WEAPON_PLASMA];
4236 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
4237 FWeapon[WEAPON_PLASMA] := True;
4238 Result := True;
4239 if gFlash = 2 then Inc(FPickup, 5);
4240 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4241 end;
4243 ITEM_WEAPON_BFG:
4244 if (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or not FWeapon[WEAPON_BFG] then
4245 begin
4246 if a and FWeapon[WEAPON_BFG] then Exit;
4247 switchWeapon := WEAPON_BFG;
4248 hadWeapon := FWeapon[WEAPON_BFG];
4249 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
4250 FWeapon[WEAPON_BFG] := True;
4251 Result := True;
4252 if gFlash = 2 then Inc(FPickup, 5);
4253 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4254 end;
4256 ITEM_WEAPON_SUPERCHAINGUN:
4257 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SUPERCHAINGUN] then
4258 begin
4259 if a and FWeapon[WEAPON_SUPERCHAINGUN] then Exit;
4260 switchWeapon := WEAPON_SUPERCHAINGUN;
4261 hadWeapon := FWeapon[WEAPON_SUPERCHAINGUN];
4262 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
4263 FWeapon[WEAPON_SUPERCHAINGUN] := True;
4264 Result := True;
4265 if gFlash = 2 then Inc(FPickup, 5);
4266 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4267 end;
4269 ITEM_WEAPON_FLAMETHROWER:
4270 if (FAmmo[A_FUEL] < FMaxAmmo[A_FUEL]) or not FWeapon[WEAPON_FLAMETHROWER] then
4271 begin
4272 if a and FWeapon[WEAPON_FLAMETHROWER] then Exit;
4273 switchWeapon := WEAPON_FLAMETHROWER;
4274 hadWeapon := FWeapon[WEAPON_FLAMETHROWER];
4275 IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
4276 FWeapon[WEAPON_FLAMETHROWER] := True;
4277 Result := True;
4278 if gFlash = 2 then Inc(FPickup, 5);
4279 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4280 end;
4282 ITEM_AMMO_BULLETS:
4283 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
4284 begin
4285 IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
4286 Result := True;
4287 remove := True;
4288 if gFlash = 2 then Inc(FPickup, 5);
4289 end;
4291 ITEM_AMMO_BULLETS_BOX:
4292 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
4293 begin
4294 IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
4295 Result := True;
4296 remove := True;
4297 if gFlash = 2 then Inc(FPickup, 5);
4298 end;
4300 ITEM_AMMO_SHELLS:
4301 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
4302 begin
4303 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
4304 Result := True;
4305 remove := True;
4306 if gFlash = 2 then Inc(FPickup, 5);
4307 end;
4309 ITEM_AMMO_SHELLS_BOX:
4310 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
4311 begin
4312 IncMax(FAmmo[A_SHELLS], 25, FMaxAmmo[A_SHELLS]);
4313 Result := True;
4314 remove := True;
4315 if gFlash = 2 then Inc(FPickup, 5);
4316 end;
4318 ITEM_AMMO_ROCKET:
4319 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
4320 begin
4321 IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
4322 Result := True;
4323 remove := True;
4324 if gFlash = 2 then Inc(FPickup, 5);
4325 end;
4327 ITEM_AMMO_ROCKET_BOX:
4328 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
4329 begin
4330 IncMax(FAmmo[A_ROCKETS], 5, FMaxAmmo[A_ROCKETS]);
4331 Result := True;
4332 remove := True;
4333 if gFlash = 2 then Inc(FPickup, 5);
4334 end;
4336 ITEM_AMMO_CELL:
4337 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
4338 begin
4339 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
4340 Result := True;
4341 remove := True;
4342 if gFlash = 2 then Inc(FPickup, 5);
4343 end;
4345 ITEM_AMMO_CELL_BIG:
4346 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
4347 begin
4348 IncMax(FAmmo[A_CELLS], 100, FMaxAmmo[A_CELLS]);
4349 Result := True;
4350 remove := True;
4351 if gFlash = 2 then Inc(FPickup, 5);
4352 end;
4354 ITEM_AMMO_FUELCAN:
4355 if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then
4356 begin
4357 IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
4358 Result := True;
4359 remove := True;
4360 if gFlash = 2 then Inc(FPickup, 5);
4361 end;
4363 ITEM_AMMO_BACKPACK:
4364 if not(R_ITEM_BACKPACK in FInventory) or
4365 (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or
4366 (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or
4367 (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or
4368 (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or
4369 (FAmmo[A_FUEL] < FMaxAmmo[A_FUEL]) then
4370 begin
4371 FMaxAmmo[A_BULLETS] := AmmoLimits[1, A_BULLETS];
4372 FMaxAmmo[A_SHELLS] := AmmoLimits[1, A_SHELLS];
4373 FMaxAmmo[A_ROCKETS] := AmmoLimits[1, A_ROCKETS];
4374 FMaxAmmo[A_CELLS] := AmmoLimits[1, A_CELLS];
4375 FMaxAmmo[A_FUEL] := AmmoLimits[1, A_FUEL];
4377 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
4378 IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
4379 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
4380 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
4381 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
4382 IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
4383 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
4384 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
4385 if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then
4386 IncMax(FAmmo[A_FUEL], 50, FMaxAmmo[A_FUEL]);
4388 FInventory += [R_ITEM_BACKPACK];
4389 Result := True;
4390 remove := True;
4391 if gFlash = 2 then FPickup += 5;
4392 end;
4394 ITEM_KEY_RED:
4395 if not(R_KEY_RED in FInventory) then
4396 begin
4397 FInventory += [R_KEY_RED];
4398 Result := True;
4399 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
4400 if gFlash = 2 then Inc(FPickup, 5);
4401 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
4402 end;
4404 ITEM_KEY_GREEN:
4405 if not(R_KEY_GREEN in FInventory) then
4406 begin
4407 FInventory += [R_KEY_GREEN];
4408 Result := True;
4409 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
4410 if gFlash = 2 then Inc(FPickup, 5);
4411 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
4412 end;
4414 ITEM_KEY_BLUE:
4415 if not(R_KEY_BLUE in FInventory) then
4416 begin
4417 FInventory += [R_KEY_BLUE];
4418 Result := True;
4419 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
4420 if gFlash = 2 then Inc(FPickup, 5);
4421 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
4422 end;
4424 ITEM_SUIT:
4425 if FPowerups[MR_SUIT] < gTime+PLAYER_SUIT_TIME then
4426 begin
4427 FPowerups[MR_SUIT] := gTime+PLAYER_SUIT_TIME;
4428 Result := True;
4429 remove := True;
4430 FFireTime := 0;
4431 if gFlash = 2 then Inc(FPickup, 5);
4432 end;
4434 ITEM_OXYGEN:
4435 if FAir < AIR_MAX then
4436 begin
4437 FAir := AIR_MAX;
4438 Result := True;
4439 remove := True;
4440 if gFlash = 2 then Inc(FPickup, 5);
4441 end;
4443 ITEM_MEDKIT_BLACK:
4444 begin
4445 if not (R_BERSERK in FInventory) then
4446 begin
4447 FInventory += [R_BERSERK];
4448 if (FBFGFireCounter = -1) then
4449 begin
4450 FCurrWeap := WEAPON_IRONFIST;
4451 resetWeaponQueue();
4452 FModel.SetWeapon(WEAPON_IRONFIST);
4453 end;
4454 if gFlash <> 0 then
4455 begin
4456 FPain += 100;
4457 if gFlash = 2 then Inc(FPickup, 5);
4458 end;
4459 FBerserk := gTime+30000;
4460 Result := True;
4461 remove := True;
4462 FFireTime := 0;
4463 end;
4464 if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
4465 begin
4466 if FHealth < PLAYER_HP_SOFT then FHealth := PLAYER_HP_SOFT;
4467 FBerserk := gTime+30000;
4468 Result := True;
4469 remove := True;
4470 FFireTime := 0;
4471 end;
4472 end;
4474 ITEM_INVUL:
4475 if FPowerups[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
4476 begin
4477 FPowerups[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
4478 FSpawnInvul := 0;
4479 Result := True;
4480 remove := True;
4481 if gFlash = 2 then Inc(FPickup, 5);
4482 end;
4484 ITEM_BOTTLE:
4485 if (FHealth < PLAYER_HP_LIMIT) or (FFireTime > 0) then
4486 begin
4487 if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 4, PLAYER_HP_LIMIT);
4488 Result := True;
4489 remove := True;
4490 FFireTime := 0;
4491 if gFlash = 2 then Inc(FPickup, 5);
4492 end;
4494 ITEM_HELMET:
4495 if FArmor < PLAYER_AP_LIMIT then
4496 begin
4497 IncMax(FArmor, 5, PLAYER_AP_LIMIT);
4498 Result := True;
4499 remove := True;
4500 if gFlash = 2 then Inc(FPickup, 5);
4501 end;
4503 ITEM_JETPACK:
4504 if FJetFuel < JET_MAX then
4505 begin
4506 FJetFuel := JET_MAX;
4507 Result := True;
4508 remove := True;
4509 if gFlash = 2 then Inc(FPickup, 5);
4510 end;
4512 ITEM_INVIS:
4513 if FPowerups[MR_INVIS] < gTime+PLAYER_INVIS_TIME then
4514 begin
4515 FPowerups[MR_INVIS] := gTime+PLAYER_INVIS_TIME;
4516 Result := True;
4517 remove := True;
4518 if gFlash = 2 then Inc(FPickup, 5);
4519 end;
4520 end;
4522 if (shouldSwitch(switchWeapon, hadWeapon)) then
4523 QueueWeaponSwitch(switchWeapon);
4524 end;
4526 procedure TPlayer.Touch();
4527 begin
4528 if not FAlive then
4529 Exit;
4530 //FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
4531 if FIamBot then
4532 begin
4533 // Áðîñèòü ôëàã òîâàðèùó:
4534 if gGameSettings.GameMode = GM_CTF then
4535 DropFlag();
4536 end;
4537 end;
4539 procedure TPlayer.Push(vx, vy: Integer);
4540 begin
4541 if (not FPhysics) and FGhost then
4542 Exit;
4543 FObj.Accel.X := FObj.Accel.X + vx;
4544 FObj.Accel.Y := FObj.Accel.Y + vy;
4545 if g_Game_IsNet and g_Game_IsServer then
4546 MH_SEND_PlayerPos(True, FUID, NET_EVERYONE);
4547 end;
4549 procedure TPlayer.Reset(Force: Boolean);
4550 begin
4551 if Force then
4552 FAlive := False;
4554 FSpawned := False;
4555 FTime[T_RESPAWN] := 0;
4556 FTime[T_FLAGCAP] := 0;
4557 FGodMode := False;
4558 FNoTarget := False;
4559 FNoReload := False;
4560 FFrags := 0;
4561 FLastFrag := 0;
4562 FComboEvnt := -1;
4563 FKills := 0;
4564 FMonsterKills := 0;
4565 FDeath := 0;
4566 FSecrets := 0;
4567 FSpawnInvul := 0;
4568 FCorpse := -1;
4569 FReady := False;
4570 if FNoRespawn then
4571 begin
4572 FSpectator := False;
4573 FGhost := False;
4574 FPhysics := True;
4575 FSpectatePlayer := -1;
4576 FNoRespawn := False;
4577 end;
4578 FLives := gGameSettings.MaxLives;
4580 SetFlag(FLAG_NONE);
4581 end;
4583 procedure TPlayer.SoftReset();
4584 begin
4585 ReleaseKeys();
4587 FDamageBuffer := 0;
4588 FSlopeOld := 0;
4589 FIncCamOld := 0;
4590 FIncCam := 0;
4591 FBFGFireCounter := -1;
4592 FShellTimer := -1;
4593 FPain := 0;
4594 FLastHit := 0;
4595 FLastFrag := 0;
4596 FComboEvnt := -1;
4598 SetFlag(FLAG_NONE);
4599 SetAction(A_STAND, True);
4600 end;
4602 function TPlayer.GetRespawnPoint(): Byte;
4604 c: Byte;
4605 begin
4606 Result := 255;
4607 // Íà áóäóùåå: FSpawn - èãðîê óæå èãðàë è ïåðåðîæäàåòñÿ
4609 // Îäèíî÷íàÿ èãðà/êîîïåðàòèâ
4610 if gGameSettings.GameMode in [GM_COOP, GM_SINGLE] then
4611 begin
4612 if Self = gPlayer1 then
4613 begin
4614 // player 1 should try to spawn on the player 1 point
4615 if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1) > 0 then
4616 Exit(RESPAWNPOINT_PLAYER1)
4617 else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2) > 0 then
4618 Exit(RESPAWNPOINT_PLAYER2);
4620 else if Self = gPlayer2 then
4621 begin
4622 // player 2 should try to spawn on the player 2 point
4623 if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2) > 0 then
4624 Exit(RESPAWNPOINT_PLAYER2)
4625 else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1) > 0 then
4626 Exit(RESPAWNPOINT_PLAYER1);
4628 else
4629 begin
4630 // other players randomly pick either the first or the second point
4631 c := IfThen((Random(2) = 0), RESPAWNPOINT_PLAYER1, RESPAWNPOINT_PLAYER2);
4632 if g_Map_GetPointCount(c) > 0 then
4633 Exit(c);
4634 // try the other one
4635 c := IfThen((c = RESPAWNPOINT_PLAYER1), RESPAWNPOINT_PLAYER2, RESPAWNPOINT_PLAYER1);
4636 if g_Map_GetPointCount(c) > 0 then
4637 Exit(c);
4638 end;
4639 end;
4641 // Ìÿñîïîâàë
4642 if gGameSettings.GameMode = GM_DM then
4643 begin
4644 // try DM points first
4645 if g_Map_GetPointCount(RESPAWNPOINT_DM) > 0 then
4646 Exit(RESPAWNPOINT_DM);
4647 end;
4649 // Êîìàíäíûå
4650 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
4651 begin
4652 // try team points first
4653 c := RESPAWNPOINT_DM;
4654 if FTeam = TEAM_RED then
4655 c := RESPAWNPOINT_RED
4656 else if FTeam = TEAM_BLUE then
4657 c := RESPAWNPOINT_BLUE;
4658 if g_Map_GetPointCount(c) > 0 then
4659 Exit(c);
4660 end;
4662 // still haven't found a spawnpoint, try random shit
4663 Result := g_Map_GetRandomPointType();
4664 end;
4666 procedure TPlayer.Respawn(Silent: Boolean; Force: Boolean = False);
4668 RespawnPoint: TRespawnPoint;
4669 a, b, c: Byte;
4670 Anim: TAnimation;
4671 ID: DWORD;
4672 begin
4673 FSlopeOld := 0;
4674 FIncCamOld := 0;
4675 FIncCam := 0;
4676 FBFGFireCounter := -1;
4677 FShellTimer := -1;
4678 FPain := 0;
4679 FLastHit := 0;
4680 FSpawnInvul := 0;
4681 FCorpse := -1;
4683 if not g_Game_IsServer then
4684 Exit;
4685 if FDummy then
4686 Exit;
4687 FWantsInGame := True;
4688 FJustTeleported := True;
4689 if Force then
4690 begin
4691 FTime[T_RESPAWN] := 0;
4692 FAlive := False;
4693 end;
4694 FNetTime := 0;
4695 // if server changes MaxLives we gotta be ready
4696 if gGameSettings.MaxLives = 0 then FNoRespawn := False;
4698 // Åùå íåëüçÿ âîçðîäèòüñÿ:
4699 if FTime[T_RESPAWN] > gTime then
4700 Exit;
4702 // Ïðîñðàë âñå æèçíè:
4703 if FNoRespawn then
4704 begin
4705 if not FSpectator then Spectate(True);
4706 FWantsInGame := True;
4707 Exit;
4708 end;
4710 if (gGameSettings.GameType <> GT_SINGLE) and (gGameSettings.GameMode <> GM_COOP) then
4711 begin // "Ñâîÿ èãðà"
4712 // Áåðñåðê íå ñîõðàíÿåòñÿ ìåæäó óðîâíÿìè:
4713 FInventory -= [R_BERSERK];
4715 else // "Îäèíî÷íàÿ èãðà"/"Êîîï"
4716 begin
4717 // Áåðñåðê è êëþ÷è íå ñîõðàíÿþòñÿ ìåæäó óðîâíÿìè:
4718 FInventory -= [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE, R_BERSERK];
4719 end;
4721 // Ïîëó÷àåì òî÷êó ñïàóíà èãðîêà:
4722 c := GetRespawnPoint();
4724 ReleaseKeys();
4725 SetFlag(FLAG_NONE);
4727 // Âîñêðåøåíèå áåç îðóæèÿ:
4728 if not FAlive then
4729 begin
4730 FHealth := Round(PLAYER_HP_SOFT * (FHandicap / 100));
4731 FArmor := 0;
4732 FAlive := True;
4733 FAir := AIR_DEF;
4734 FJetFuel := 0;
4736 for a := WP_FIRST to WP_LAST do
4737 begin
4738 FWeapon[a] := False;
4739 FReloading[a] := 0;
4740 end;
4742 FWeapon[WEAPON_PISTOL] := True;
4743 FWeapon[WEAPON_IRONFIST] := True;
4744 FCurrWeap := WEAPON_PISTOL;
4745 resetWeaponQueue();
4747 FModel.SetWeapon(FCurrWeap);
4749 for b := A_BULLETS to A_HIGH do
4750 FAmmo[b] := 0;
4752 FAmmo[A_BULLETS] := 50;
4754 FMaxAmmo[A_BULLETS] := AmmoLimits[0, A_BULLETS];
4755 FMaxAmmo[A_SHELLS] := AmmoLimits[0, A_SHELLS];
4756 FMaxAmmo[A_ROCKETS] := AmmoLimits[0, A_SHELLS];
4757 FMaxAmmo[A_CELLS] := AmmoLimits[0, A_CELLS];
4758 FMaxAmmo[A_FUEL] := AmmoLimits[0, A_FUEL];
4760 if (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) and
4761 (TGameOption.DM_KEYS in gGameSettings.Options)
4762 then FInventory := [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE]
4763 else FInventory := [];
4764 end;
4766 // Ïîëó÷àåì êîîðäèíàòû òî÷êè âîçðîæäåíèÿ:
4767 if not g_Map_GetPoint(c, RespawnPoint) then
4768 begin
4769 g_FatalError(_lc[I_GAME_ERROR_GET_SPAWN]);
4770 Exit;
4771 end;
4773 // Óñòàíîâêà êîîðäèíàò è ñáðîñ âñåõ ïàðàìåòðîâ:
4774 FObj.X := RespawnPoint.X-PLAYER_RECT.X;
4775 FObj.Y := RespawnPoint.Y-PLAYER_RECT.Y;
4776 FObj.oldX := FObj.X; // don't interpolate after respawn
4777 FObj.oldY := FObj.Y;
4778 FObj.Vel.X := 0;
4779 FObj.Vel.Y := 0;
4780 FObj.Accel.X := 0;
4781 FObj.Accel.Y := 0;
4783 FDirection := RespawnPoint.Direction;
4784 if FDirection = TDirection.D_LEFT then
4785 FAngle := 180
4786 else
4787 FAngle := 0;
4789 SetAction(A_STAND, True);
4790 FModel.Direction := FDirection;
4792 for a := Low(FTime) to High(FTime) do
4793 FTime[a] := 0;
4795 for a := Low(FPowerups) to High(FPowerups) do
4796 FPowerups[a] := 0;
4798 // Respawn invulnerability
4799 if (gGameSettings.GameType <> GT_SINGLE) and (gGameSettings.SpawnInvul > 0) then
4800 begin
4801 FPowerups[MR_INVUL] := gTime + gGameSettings.SpawnInvul * 1000;
4802 FSpawnInvul := FPowerups[MR_INVUL];
4803 end;
4805 FDamageBuffer := 0;
4806 FJetpack := False;
4807 FCanJetpack := False;
4808 FFlaming := False;
4809 FFireTime := 0;
4810 FFirePainTime := 0;
4811 FFireAttacker := 0;
4813 // Àíèìàöèÿ âîçðîæäåíèÿ:
4814 if (not gLoadGameMode) and (not Silent) then
4815 if g_Frames_Get(ID, 'FRAMES_TELEPORT') then
4816 begin
4817 Anim := TAnimation.Create(ID, False, 3);
4818 g_GFX_OnceAnim(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
4819 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, Anim);
4820 Anim.Destroy();
4821 end;
4823 FSpectator := False;
4824 FGhost := False;
4825 FPhysics := True;
4826 FSpectatePlayer := -1;
4827 FSpawned := True;
4829 if (gPlayer1 = nil) and (gSpectLatchPID1 = FUID) then
4830 gPlayer1 := self;
4831 if (gPlayer2 = nil) and (gSpectLatchPID2 = FUID) then
4832 gPlayer2 := self;
4834 if g_Game_IsNet then
4835 begin
4836 MH_SEND_PlayerPos(True, FUID, NET_EVERYONE);
4837 MH_SEND_PlayerStats(FUID, NET_EVERYONE);
4838 if not Silent then
4839 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
4840 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32,
4841 0, NET_GFX_TELE);
4842 end;
4843 end;
4845 procedure TPlayer.Spectate(NoMove: Boolean = False);
4846 begin
4847 if FAlive then
4848 Kill(K_EXTRAHARDKILL, FUID, HIT_SOME)
4849 else if (not NoMove) then
4850 begin
4851 GameX := gMapInfo.Width div 2;
4852 GameY := gMapInfo.Height div 2;
4853 end;
4854 FXTo := GameX;
4855 FYTo := GameY;
4857 FAlive := False;
4858 FSpectator := True;
4859 FGhost := True;
4860 FPhysics := False;
4861 FWantsInGame := False;
4862 FSpawned := False;
4863 FCorpse := -1;
4865 if FNoRespawn then
4866 begin
4867 if Self = gPlayer1 then
4868 begin
4869 gSpectLatchPID1 := FUID;
4870 gPlayer1 := nil;
4872 else if Self = gPlayer2 then
4873 begin
4874 gSpectLatchPID2 := FUID;
4875 gPlayer2 := nil;
4876 end;
4877 end;
4879 if g_Game_IsNet then
4880 MH_SEND_PlayerStats(FUID);
4881 end;
4883 procedure TPlayer.SwitchNoClip;
4884 begin
4885 if not FAlive then
4886 Exit;
4887 FGhost := not FGhost;
4888 FPhysics := not FGhost;
4889 if FGhost then
4890 begin
4891 FXTo := FObj.X;
4892 FYTo := FObj.Y;
4893 end else
4894 begin
4895 FObj.Accel.X := 0;
4896 FObj.Accel.Y := 0;
4897 end;
4898 end;
4900 procedure TPlayer.Run(Direction: TDirection);
4902 a, b: Integer;
4903 begin
4904 if MAX_RUNVEL > 8 then
4905 FlySmoke();
4907 // Áåæèì:
4908 if Direction = TDirection.D_LEFT then
4909 begin
4910 if FObj.Vel.X > -MAX_RUNVEL then
4911 FObj.Vel.X := FObj.Vel.X - (MAX_RUNVEL shr 3);
4913 else
4914 if FObj.Vel.X < MAX_RUNVEL then
4915 FObj.Vel.X := FObj.Vel.X + (MAX_RUNVEL shr 3);
4917 // Âîçìîæíî, ïèíàåì êóñêè:
4918 if (FObj.Vel.X <> 0) and (gGibs <> nil) then
4919 begin
4920 b := Abs(FObj.Vel.X);
4921 if b > 1 then b := b * (Random(8 div b) + 1);
4922 for a := 0 to High(gGibs) do
4923 begin
4924 if gGibs[a].alive and
4925 g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
4926 FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then
4927 begin
4928 // Ïèíàåì êóñêè
4929 if FObj.Vel.X < 0 then
4930 begin
4931 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120) // íàëåâî
4933 else
4934 begin
4935 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // íàïðàâî
4936 end;
4937 gGibs[a].positionChanged(); // this updates spatial accelerators
4938 end;
4939 end;
4940 end;
4942 SetAction(A_WALK);
4943 end;
4945 procedure TPlayer.SeeDown();
4946 begin
4947 SetAction(A_SEEDOWN);
4949 if FDirection = TDirection.D_LEFT then FAngle := ANGLE_LEFTDOWN else FAngle := ANGLE_RIGHTDOWN;
4951 if FIncCam > -120 then DecMin(FIncCam, 5, -120);
4952 end;
4954 procedure TPlayer.SeeUp();
4955 begin
4956 SetAction(A_SEEUP);
4958 if FDirection = TDirection.D_LEFT then FAngle := ANGLE_LEFTUP else FAngle := ANGLE_RIGHTUP;
4960 if FIncCam < 120 then IncMax(FIncCam, 5, 120);
4961 end;
4963 procedure TPlayer.SetAction(Action: Byte; Force: Boolean = False);
4965 Prior: Byte;
4966 begin
4967 case Action of
4968 A_WALK: Prior := 3;
4969 A_DIE1: Prior := 5;
4970 A_DIE2: Prior := 5;
4971 A_ATTACK: Prior := 2;
4972 A_SEEUP: Prior := 1;
4973 A_SEEDOWN: Prior := 1;
4974 A_ATTACKUP: Prior := 2;
4975 A_ATTACKDOWN: Prior := 2;
4976 A_PAIN: Prior := 4;
4977 else Prior := 0;
4978 end;
4980 if (Prior > FActionPrior) or Force then
4981 if not ((Prior = 2) and (FCurrWeap = WEAPON_SAW)) then
4982 begin
4983 FActionPrior := Prior;
4984 FActionAnim := Action;
4985 FActionForce := Force;
4986 FActionChanged := True;
4987 end;
4989 if Action in [A_ATTACK, A_ATTACKUP, A_ATTACKDOWN] then FModel.SetFire(True);
4990 end;
4992 function TPlayer.StayOnStep(XInc, YInc: Integer): Boolean;
4993 begin
4994 Result := not g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+YInc+PLAYER_RECT.Y+PLAYER_RECT.Height-1,
4995 PLAYER_RECT.Width, 1, PANEL_STEP, False)
4996 and g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+YInc+PLAYER_RECT.Y+PLAYER_RECT.Height,
4997 PLAYER_RECT.Width, 1, PANEL_STEP, False);
4998 end;
5000 function TPlayer.TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean;
5002 Anim: TAnimation;
5003 ID: DWORD;
5004 begin
5005 Result := False;
5007 if g_CollideLevel(X, Y, PLAYER_RECT.Width, PLAYER_RECT.Height) then
5008 begin
5009 {$IFDEF ENABLE_SOUND}
5010 g_Sound_PlayExAt('SOUND_GAME_NOTELEPORT', FObj.X, FObj.Y);
5011 {$ENDIF}
5012 if g_Game_IsServer and g_Game_IsNet then
5013 MH_SEND_Sound(FObj.X, FObj.Y, 'SOUND_GAME_NOTELEPORT');
5014 Exit;
5015 end;
5017 FJustTeleported := True;
5019 Anim := nil;
5020 if not silent then
5021 begin
5022 if g_Frames_Get(ID, 'FRAMES_TELEPORT') then
5023 begin
5024 Anim := TAnimation.Create(ID, False, 3);
5025 end;
5027 {$IFDEF ENABLE_SOUND}
5028 g_Sound_PlayExAt('SOUND_GAME_TELEPORT', FObj.X, FObj.Y);
5029 {$ENDIF}
5030 g_GFX_OnceAnim(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
5031 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, Anim);
5032 if g_Game_IsServer and g_Game_IsNet then
5033 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
5034 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 1,
5035 NET_GFX_TELE);
5036 end;
5038 FObj.X := X-PLAYER_RECT.X;
5039 FObj.Y := Y-PLAYER_RECT.Y;
5040 FObj.oldX := FObj.X; // don't interpolate after respawn
5041 FObj.oldY := FObj.Y;
5042 if FAlive and FGhost then
5043 begin
5044 FXTo := FObj.X;
5045 FYTo := FObj.Y;
5046 end;
5048 if not g_Game_IsNet then
5049 begin
5050 if dir = 1 then
5051 begin
5052 SetDirection(TDirection.D_LEFT);
5053 FAngle := 180;
5055 else
5056 if dir = 2 then
5057 begin
5058 SetDirection(TDirection.D_RIGHT);
5059 FAngle := 0;
5061 else
5062 if dir = 3 then
5063 begin // îáðàòíîå
5064 if FDirection = TDirection.D_RIGHT then
5065 begin
5066 SetDirection(TDirection.D_LEFT);
5067 FAngle := 180;
5069 else
5070 begin
5071 SetDirection(TDirection.D_RIGHT);
5072 FAngle := 0;
5073 end;
5074 end;
5075 end;
5077 if not silent and (Anim <> nil) then
5078 begin
5079 g_GFX_OnceAnim(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
5080 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, Anim);
5081 Anim.Destroy();
5083 if g_Game_IsServer and g_Game_IsNet then
5084 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
5085 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 0,
5086 NET_GFX_TELE);
5087 end;
5089 Result := True;
5090 end;
5092 function nonz(a: Single): Single;
5093 begin
5094 if a <> 0
5095 then Result := a
5096 else Result := 1;
5097 end;
5099 function TPlayer.refreshCorpse(): Boolean;
5101 i: Integer;
5102 begin
5103 Result := False;
5104 FCorpse := -1;
5105 if FAlive or FSpectator then
5106 Exit;
5108 for i := 0 to High(gCorpses) do
5109 if gCorpses[i] <> nil then
5110 if gCorpses[i].FPlayerUID = FUID then
5111 begin
5112 Result := True;
5113 FCorpse := i;
5114 break;
5115 end;
5116 end;
5118 function TPlayer.getCameraObj(): TObj;
5119 begin
5120 if (not FAlive) and (not FSpectator) and
5121 (Low(gCorpses) <= FCorpse) and (High(gCorpses) >= FCorpse) and
5122 (gCorpses[FCorpse] <> nil) and (gCorpses[FCorpse].FPlayerUID = FUID) then
5123 begin
5124 gCorpses[FCorpse].FObj.slopeUpLeft := FObj.slopeUpLeft;
5125 Result := gCorpses[FCorpse].FObj;
5127 else
5128 begin
5129 Result := FObj;
5130 end;
5131 end;
5133 procedure TPlayer.PreUpdate();
5134 begin
5135 FSlopeOld := FObj.slopeUpLeft;
5136 FIncCamOld := FIncCam;
5137 FObj.oldX := FObj.X;
5138 FObj.oldY := FObj.Y;
5139 end;
5141 procedure TPlayer.Update();
5143 b: Byte;
5144 i, ii, wx, wy, xd, yd, k: Integer;
5145 blockmon, headwater, dospawn: Boolean;
5146 NetServer: Boolean;
5147 AnyServer: Boolean;
5148 SetSpect: Boolean;
5149 ProjID: SizeInt;
5150 begin
5151 NetServer := g_Game_IsNet and g_Game_IsServer;
5152 AnyServer := g_Game_IsServer;
5154 if g_Game_IsClient and (NetInterpLevel > 0) then
5155 DoLerp(NetInterpLevel + 1)
5156 else
5157 if FGhost then
5158 DoLerp(4);
5160 if NetServer then
5161 if (FClientID >= 0) and (NetClients[FClientID].Peer <> nil) then
5162 begin
5163 FPing := NetClients[FClientID].Peer^.lastRoundTripTime;
5164 if NetClients[FClientID].Peer^.packetsSent > 0 then
5165 FLoss := Round(100*NetClients[FClientID].Peer^.packetsLost/NetClients[FClientID].Peer^.packetsSent)
5166 else
5167 FLoss := 0;
5168 end else
5169 begin
5170 FPing := 0;
5171 FLoss := 0;
5172 end;
5174 if FAlive and (FPunchAnim <> nil) then
5175 FPunchAnim.Update();
5177 if FAlive and (gFly or FJetpack) then
5178 FlySmoke();
5180 if FDirection = TDirection.D_LEFT then
5181 FAngle := 180
5182 else
5183 FAngle := 0;
5185 if FAlive and (not FGhost) then
5186 begin
5187 if FKeys[KEY_UP].Pressed then
5188 SeeUp();
5189 if FKeys[KEY_DOWN].Pressed then
5190 SeeDown();
5191 end;
5193 if (not (FKeys[KEY_UP].Pressed or FKeys[KEY_DOWN].Pressed)) and
5194 (FIncCam <> 0) then
5195 begin
5196 i := g_basic.Sign(FIncCam);
5197 FIncCam := Abs(FIncCam);
5198 DecMin(FIncCam, 5, 0);
5199 FIncCam := FIncCam*i;
5200 end;
5202 if gTime mod (GAME_TICK*2) <> 0 then
5203 begin
5204 if (FObj.Vel.X = 0) and FAlive then
5205 begin
5206 if FKeys[KEY_LEFT].Pressed then
5207 Run(TDirection.D_LEFT);
5208 if FKeys[KEY_RIGHT].Pressed then
5209 Run(TDirection.D_RIGHT);
5210 end;
5212 if FPhysics then
5213 begin
5214 g_Obj_Move(@FObj, True, True, True);
5215 positionChanged(); // this updates spatial accelerators
5216 end;
5218 Exit;
5219 end;
5221 FActionChanged := False;
5223 if FAlive then
5224 begin
5225 // Let alive player do some actions
5226 if FKeys[KEY_LEFT].Pressed then Run(TDirection.D_LEFT);
5227 if FKeys[KEY_RIGHT].Pressed then Run(TDirection.D_RIGHT);
5228 if FKeys[KEY_FIRE].Pressed and AnyServer then Fire()
5229 else
5230 begin
5231 if AnyServer then
5232 begin
5233 FlamerOff;
5234 if NetServer then MH_SEND_PlayerStats(FUID);
5235 end;
5236 end;
5237 if FKeys[KEY_OPEN].Pressed and AnyServer then Use();
5238 if FKeys[KEY_JUMP].Pressed then Jump()
5239 else
5240 begin
5241 if AnyServer and FJetpack then
5242 begin
5243 FJetpack := False;
5244 JetpackOff;
5245 if NetServer then MH_SEND_PlayerStats(FUID);
5246 end;
5247 FCanJetpack := True;
5248 end;
5250 else // Dead
5251 begin
5252 dospawn := False;
5253 if not FGhost then
5254 for k := Low(FKeys) to KEY_CHAT-1 do
5255 begin
5256 if FKeys[k].Pressed then
5257 begin
5258 dospawn := True;
5259 break;
5260 end;
5261 end;
5262 if dospawn then
5263 begin
5264 if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
5265 Respawn(False)
5266 else // Single
5267 if (FTime[T_RESPAWN] <= gTime) and
5268 gGameOn and (not FAlive) then
5269 begin
5270 if (g_Player_GetCount() > 1) then
5271 Respawn(False)
5272 else
5273 begin
5274 gExit := EXIT_RESTART;
5275 Exit;
5276 end;
5277 end;
5278 end;
5279 // Dead spectator actions
5280 if FGhost then
5281 begin
5282 if FKeys[KEY_OPEN].Pressed and AnyServer then Fire();
5283 if FKeys[KEY_FIRE].Pressed and AnyServer then
5284 begin
5285 if FSpectator then
5286 begin
5287 if (FSpectatePlayer >= High(gPlayers)) then
5288 FSpectatePlayer := -1
5289 else
5290 begin
5291 SetSpect := False;
5292 for I := FSpectatePlayer + 1 to High(gPlayers) do
5293 if gPlayers[I] <> nil then
5294 if gPlayers[I].alive then
5295 if gPlayers[I].UID <> FUID then
5296 begin
5297 FSpectatePlayer := I;
5298 SetSpect := True;
5299 break;
5300 end;
5302 if not SetSpect then FSpectatePlayer := -1;
5303 end;
5305 ReleaseKeys;
5306 end;
5307 end;
5308 end;
5309 end;
5310 // No clipping
5311 if FGhost then
5312 begin
5313 if FKeys[KEY_UP].Pressed or FKeys[KEY_JUMP].Pressed then
5314 begin
5315 FYTo := FObj.Y - 32;
5316 FSpectatePlayer := -1;
5317 end;
5318 if FKeys[KEY_DOWN].Pressed then
5319 begin
5320 FYTo := FObj.Y + 32;
5321 FSpectatePlayer := -1;
5322 end;
5323 if FKeys[KEY_LEFT].Pressed then
5324 begin
5325 FXTo := FObj.X - 32;
5326 FSpectatePlayer := -1;
5327 end;
5328 if FKeys[KEY_RIGHT].Pressed then
5329 begin
5330 FXTo := FObj.X + 32;
5331 FSpectatePlayer := -1;
5332 end;
5334 if (FXTo < -64) then
5335 FXTo := -64
5336 else if (FXTo > gMapInfo.Width + 32) then
5337 FXTo := gMapInfo.Width + 32;
5338 if (FYTo < -72) then
5339 FYTo := -72
5340 else if (FYTo > gMapInfo.Height + 32) then
5341 FYTo := gMapInfo.Height + 32;
5342 end;
5344 if FPhysics then
5345 begin
5346 g_Obj_Move(@FObj, True, True, True);
5347 positionChanged(); // this updates spatial accelerators
5349 else
5350 begin
5351 FObj.Vel.X := 0;
5352 FObj.Vel.Y := 0;
5353 if FSpectator then
5354 if (FSpectatePlayer <= High(gPlayers)) and (FSpectatePlayer >= 0) then
5355 if gPlayers[FSpectatePlayer] <> nil then
5356 if gPlayers[FSpectatePlayer].alive then
5357 begin
5358 FXTo := gPlayers[FSpectatePlayer].GameX;
5359 FYTo := gPlayers[FSpectatePlayer].GameY;
5360 end;
5361 end;
5363 blockmon := g_Map_CollidePanel(FObj.X+PLAYER_HEADRECT.X, FObj.Y+PLAYER_HEADRECT.Y,
5364 PLAYER_HEADRECT.Width, PLAYER_HEADRECT.Height,
5365 PANEL_BLOCKMON, True);
5366 headwater := HeadInLiquid(0, 0);
5368 // Ñîïðîòèâëåíèå âîçäóõà:
5369 if (not FAlive) or not (FKeys[KEY_LEFT].Pressed or FKeys[KEY_RIGHT].Pressed) then
5370 if FObj.Vel.X <> 0 then
5371 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
5373 if (FLastHit = HIT_TRAP) and (FPain > 90) then FPain := 90;
5374 DecMin(FPain, 5, 0);
5375 DecMin(FPickup, 1, 0);
5377 if FAlive and (FObj.Y > Integer(gMapInfo.Height)+128) and AnyServer then
5378 begin
5379 // Îáíóëèòü äåéñòâèÿ ïðèìî÷åê, ÷òîáû ôîí ïðîïàë
5380 FPowerups[MR_SUIT] := 0;
5381 FPowerups[MR_INVUL] := 0;
5382 FPowerups[MR_INVIS] := 0;
5383 Kill(K_FALLKILL, 0, HIT_FALL);
5384 end;
5386 i := 9;
5388 if FAlive then
5389 begin
5390 {$IFDEF ENABLE_SOUND}
5391 if FCurrWeap = WEAPON_SAW then
5392 if not (FSawSound.IsPlaying() or FSawSoundHit.IsPlaying() or
5393 FSawSoundSelect.IsPlaying()) then
5394 FSawSoundIdle.PlayAt(FObj.X, FObj.Y);
5396 if FJetpack then
5397 if (not FJetSoundFly.IsPlaying()) and (not FJetSoundOn.IsPlaying()) and
5398 (not FJetSoundOff.IsPlaying()) then
5399 begin
5400 FJetSoundFly.SetPosition(0);
5401 FJetSoundFly.PlayAt(FObj.X, FObj.Y);
5402 end;
5403 {$ENDIF}
5405 for b := WP_FIRST to WP_LAST do
5406 if FReloading[b] > 0 then
5407 if FNoReload
5408 then FReloading[b] := 0
5409 else FReloading[b] -= 1;
5411 if FShellTimer > -1 then
5412 if FShellTimer = 0 then
5413 begin
5414 if FShellType = SHELL_SHELL then
5415 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
5416 GameVelX, GameVelY-2, SHELL_SHELL)
5417 else if FShellType = SHELL_DBLSHELL then
5418 begin
5419 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
5420 GameVelX+1, GameVelY-2, SHELL_SHELL);
5421 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
5422 GameVelX-1, GameVelY-2, SHELL_SHELL);
5423 end;
5424 FShellTimer := -1;
5425 end else Dec(FShellTimer);
5427 if (FBFGFireCounter > -1) then
5428 if FBFGFireCounter = 0 then
5429 begin
5430 if AnyServer then
5431 begin
5432 wx := FObj.X+WEAPONPOINT[FDirection].X;
5433 wy := FObj.Y+WEAPONPOINT[FDirection].Y;
5434 xd := wx+IfThen(FDirection = TDirection.D_LEFT, -30, 30);
5435 yd := wy+firediry();
5436 ProjID := g_Weapon_bfgshot(wx, wy, xd, yd, FUID);
5437 if NetServer then MH_SEND_PlayerFire(FUID, WEAPON_BFG, wx, wy, xd, yd, ProjID);
5438 case FAngle of
5439 0, 180: SetAction(A_ATTACK);
5440 ANGLE_LEFTDOWN, ANGLE_RIGHTDOWN: SetAction(A_ATTACKDOWN);
5441 ANGLE_LEFTUP, ANGLE_RIGHTUP: SetAction(A_ATTACKUP);
5442 end;
5443 end;
5445 FReloading[WEAPON_BFG] := WEAPON_RELOAD[WEAPON_BFG];
5446 FBFGFireCounter := -1;
5447 end else
5448 if FNoReload
5449 then FBFGFireCounter := 0
5450 else FBFGFireCounter -= 1;
5452 if (FPowerups[MR_SUIT] < gTime) and AnyServer then
5453 begin
5454 b := g_GetAcidHit(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width, PLAYER_RECT.Height);
5456 if (b > 0) and (gTime mod (15*GAME_TICK) = 0) then Damage(b, 0, 0, 0, HIT_ACID);
5457 end;
5459 if (headwater or blockmon) then
5460 begin
5461 Dec(FAir);
5463 if FAir < -9 then
5464 begin
5465 if AnyServer then Damage(10, 0, 0, 0, HIT_WATER);
5466 FAir := 0;
5468 else if (FAir mod 31 = 0) and not blockmon then
5469 g_Game_Effect_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
5470 FObj.Y+PLAYER_RECT.Y-4, 5+Random(6), 8, 4);
5471 end else if FAir < AIR_DEF then
5472 FAir := AIR_DEF;
5474 if FFireTime > 0 then
5475 begin
5476 if BodyInLiquid(0, 0) then
5477 begin
5478 FFireTime := 0;
5479 FFirePainTime := 0;
5481 else if FPowerups[MR_SUIT] >= gTime then
5482 begin
5483 if FPowerups[MR_SUIT] = gTime then
5484 FFireTime := 1;
5485 FFirePainTime := 0;
5487 else
5488 begin
5489 OnFireFlame(1);
5490 if FFirePainTime <= 0 then
5491 begin
5492 if g_Game_IsServer then
5493 Damage(2, FFireAttacker, 0, 0, HIT_FLAME);
5494 FFirePainTime := 12 - FFireTime div 12;
5495 end;
5496 FFirePainTime := FFirePainTime - 1;
5497 FFireTime := FFireTime - 1;
5498 {$IFDEF ENABLE_SOUND}
5499 if ((FFireTime mod 33) = 0) and (FPowerups[MR_INVUL] < gTime) then
5500 FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
5501 {$ENDIF}
5502 if (FFireTime = 0) and g_Game_IsNet and g_Game_IsServer then
5503 MH_SEND_PlayerStats(FUID);
5504 end;
5505 end;
5507 if FDamageBuffer > 0 then
5508 begin
5509 if FDamageBuffer >= 9 then
5510 begin
5511 SetAction(A_PAIN);
5513 if FDamageBuffer < 30 then i := 9
5514 else if FDamageBuffer < 100 then i := 18
5515 else i := 27;
5516 end;
5518 ii := Round(FDamageBuffer*FHealth / nonz(FArmor*(3/4)+FHealth));
5519 FArmor := FArmor-(FDamageBuffer-ii);
5520 FHealth := FHealth-ii;
5521 if FArmor < 0 then
5522 begin
5523 FHealth := FHealth+FArmor;
5524 FArmor := 0;
5525 end;
5527 if AnyServer then
5528 if FHealth <= 0 then
5529 if FHealth > -30 then Kill(K_SIMPLEKILL, FLastSpawnerUID, FLastHit)
5530 else if FHealth > -50 then Kill(K_HARDKILL, FLastSpawnerUID, FLastHit)
5531 else Kill(K_EXTRAHARDKILL, FLastSpawnerUID, FLastHit);
5533 {$IFDEF ENABLE_SOUND}
5534 if FAlive and ((FLastHit <> HIT_FLAME) or (FFireTime <= 0)) then
5535 begin
5536 if FDamageBuffer <= 20 then FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y)
5537 else if FDamageBuffer <= 55 then FModel.PlaySound(MODELSOUND_PAIN, 2, FObj.X, FObj.Y)
5538 else if FDamageBuffer <= 120 then FModel.PlaySound(MODELSOUND_PAIN, 3, FObj.X, FObj.Y)
5539 else FModel.PlaySound(MODELSOUND_PAIN, 4, FObj.X, FObj.Y);
5540 end;
5541 {$ENDIF}
5543 FDamageBuffer := 0;
5544 end;
5546 {CollideItem();}
5547 end; // if FAlive then ...
5549 if (FActionAnim = A_PAIN) and (FModel.Animation <> A_PAIN) then
5550 begin
5551 FModel.ChangeAnimation(FActionAnim, FActionForce);
5552 FModel.GetCurrentAnimation.MinLength := i;
5553 FModel.GetCurrentAnimationMask.MinLength := i;
5554 end else FModel.ChangeAnimation(FActionAnim, FActionForce and (FModel.Animation <> A_STAND));
5556 if (FModel.GetCurrentAnimation.Played or ((not FActionChanged) and (FModel.Animation = A_WALK)))
5557 then SetAction(A_STAND, True);
5559 if not ((FModel.Animation = A_WALK) and (Abs(FObj.Vel.X) < 4) and not FModel.Fire) then FModel.Update;
5561 for b := Low(FKeys) to High(FKeys) do
5562 if FKeys[b].Time = 0 then FKeys[b].Pressed := False else Dec(FKeys[b].Time);
5563 end;
5566 procedure TPlayer.getMapBox (out x, y, w, h: Integer); inline;
5567 begin
5568 x := FObj.X+PLAYER_RECT.X;
5569 y := FObj.Y+PLAYER_RECT.Y;
5570 w := PLAYER_RECT.Width;
5571 h := PLAYER_RECT.Height;
5572 end;
5575 procedure TPlayer.moveBy (dx, dy: Integer); inline;
5576 begin
5577 if (dx <> 0) or (dy <> 0) then
5578 begin
5579 FObj.X += dx;
5580 FObj.Y += dy;
5581 positionChanged();
5582 end;
5583 end;
5586 function TPlayer.Collide(X, Y: Integer; Width, Height: Word): Boolean;
5587 begin
5588 Result := g_Collide(FObj.X+PLAYER_RECT.X,
5589 FObj.Y+PLAYER_RECT.Y,
5590 PLAYER_RECT.Width,
5591 PLAYER_RECT.Height,
5592 X, Y,
5593 Width, Height);
5594 end;
5596 function TPlayer.Collide(Panel: TPanel): Boolean;
5597 begin
5598 Result := g_Collide(FObj.X+PLAYER_RECT.X,
5599 FObj.Y+PLAYER_RECT.Y,
5600 PLAYER_RECT.Width,
5601 PLAYER_RECT.Height,
5602 Panel.X, Panel.Y,
5603 Panel.Width, Panel.Height);
5604 end;
5606 function TPlayer.Collide(X, Y: Integer): Boolean;
5607 begin
5608 X := X-FObj.X-PLAYER_RECT.X;
5609 Y := Y-FObj.Y-PLAYER_RECT.Y;
5610 Result := (x >= 0) and (x <= PLAYER_RECT.Width) and
5611 (y >= 0) and (y <= PLAYER_RECT.Height);
5612 end;
5614 function g_Player_ExistingName(Name: string): Boolean;
5616 a: Integer;
5617 begin
5618 Result := True;
5620 if gPlayers = nil then Exit;
5622 for a := 0 to High(gPlayers) do
5623 if gPlayers[a] <> nil then
5624 if LowerCase(Name) = LowerCase(gPlayers[a].FName) then
5625 begin
5626 Result := False;
5627 Exit;
5628 end;
5629 end;
5631 procedure TPlayer.SetDirection(Direction: TDirection);
5633 d: TDirection;
5634 begin
5635 d := FModel.Direction;
5637 FModel.Direction := Direction;
5638 if d <> Direction then FModel.ChangeAnimation(FModel.Animation, True);
5640 FDirection := Direction;
5641 end;
5643 function TPlayer.GetKeys(): Byte;
5644 begin
5645 Result := 0;
5647 if R_KEY_RED in FInventory then Result := Result or KEY_RED;
5648 if R_KEY_GREEN in FInventory then Result := Result or KEY_GREEN;
5649 if R_KEY_BLUE in FInventory then Result := Result or KEY_BLUE;
5651 if FTeam = TEAM_RED then Result := Result or KEY_REDTEAM;
5652 if FTeam = TEAM_BLUE then Result := Result or KEY_BLUETEAM;
5653 end;
5655 procedure TPlayer.Use();
5657 a: Integer;
5658 begin
5659 if FTime[T_USE] > gTime then Exit;
5661 g_Triggers_PressR(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width,
5662 PLAYER_RECT.Height, FUID, ACTIVATE_PLAYERPRESS);
5664 for a := 0 to High(gPlayers) do
5665 if (gPlayers[a] <> nil) and (gPlayers[a] <> Self) and
5666 gPlayers[a].alive and SameTeam(FUID, gPlayers[a].FUID) and
5667 g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
5668 FObj.Rect.Width, FObj.Rect.Height, @gPlayers[a].FObj) then
5669 begin
5670 gPlayers[a].Touch();
5671 if g_Game_IsNet and g_Game_IsServer then
5672 MH_SEND_GameEvent(NET_EV_PLAYER_TOUCH, gPlayers[a].FUID);
5673 end;
5675 FTime[T_USE] := gTime+120;
5676 end;
5678 procedure TPlayer.NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer);
5680 locObj: TObj;
5681 visible: Boolean = True;
5682 WX, WY, XD, YD: Integer;
5683 begin
5684 WX := X;
5685 WY := Y;
5686 XD := AX;
5687 YD := AY;
5689 case FCurrWeap of
5690 WEAPON_IRONFIST:
5691 begin
5692 visible := False;
5693 DoPunch();
5694 if R_BERSERK in FInventory then
5695 begin
5696 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
5697 locobj.X := FObj.X+FObj.Rect.X;
5698 locobj.Y := FObj.Y+FObj.Rect.Y;
5699 locobj.rect.X := 0;
5700 locobj.rect.Y := 0;
5701 locobj.rect.Width := 39;
5702 locobj.rect.Height := 52;
5703 locobj.Vel.X := (xd-wx) div 2;
5704 locobj.Vel.Y := (yd-wy) div 2;
5705 locobj.Accel.X := xd-wx;
5706 locobj.Accel.y := yd-wy;
5708 if g_Weapon_Hit(@locobj, 50, FUID, HIT_SOME) <> 0 then
5709 begin
5710 {$IFDEF ENABLE_SOUND}
5711 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj.X, FObj.Y)
5712 {$ENDIF}
5714 else
5715 begin
5716 {$IFDEF ENABLE_SOUND}
5717 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj.X, FObj.Y);
5718 {$ENDIF}
5719 end;
5721 if (gFlash = 1) and (FPain < 50) then
5722 FPain := min(FPain + 25, 50);
5723 end else
5724 g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 3, FUID);
5725 end;
5727 WEAPON_SAW:
5728 begin
5729 {$IFDEF ENABLE_SOUND}
5730 if g_Weapon_chainsaw(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
5731 IfThen(gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF], 9, 3), FUID) <> 0 then
5732 begin
5733 FSawSoundSelect.Stop();
5734 FSawSound.Stop();
5735 FSawSoundHit.PlayAt(FObj.X, FObj.Y);
5737 else if not FSawSoundHit.IsPlaying() then
5738 begin
5739 FSawSoundSelect.Stop();
5740 FSawSound.PlayAt(FObj.X, FObj.Y);
5741 end;
5742 {$ELSE}
5743 g_Weapon_chainsaw(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
5744 IfThen(gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF], 9, 3), FUID);
5745 {$ENDIF}
5746 end;
5748 WEAPON_PISTOL:
5749 begin
5750 {$IFDEF ENABLE_SOUND}
5751 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', GameX, Gamey);
5752 {$ENDIF}
5753 FFireAngle := FAngle;
5754 g_Player_CreateShell(
5755 GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
5756 GameVelX, GameVelY-2,
5757 SHELL_BULLET
5759 end;
5761 WEAPON_SHOTGUN1:
5762 begin
5763 {$IFDEF ENABLE_SOUND}
5764 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex, Gamey);
5765 {$ENDIF}
5766 FFireAngle := FAngle;
5767 FShellTimer := 10;
5768 FShellType := SHELL_SHELL;
5769 end;
5771 WEAPON_SHOTGUN2:
5772 begin
5773 {$IFDEF ENABLE_SOUND}
5774 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', Gamex, Gamey);
5775 {$ENDIF}
5776 FFireAngle := FAngle;
5777 FShellTimer := 13;
5778 FShellType := SHELL_DBLSHELL;
5779 end;
5781 WEAPON_CHAINGUN:
5782 begin
5783 {$IFDEF ENABLE_SOUND}
5784 g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', Gamex, Gamey);
5785 {$ENDIF}
5786 FFireAngle := FAngle;
5787 g_Player_CreateShell(
5788 GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
5789 GameVelX, GameVelY-2,
5790 SHELL_BULLET
5792 end;
5794 WEAPON_ROCKETLAUNCHER:
5795 begin
5796 g_Weapon_Rocket(wx, wy, xd, yd, FUID, WID);
5797 FFireAngle := FAngle;
5798 end;
5800 WEAPON_PLASMA:
5801 begin
5802 g_Weapon_Plasma(wx, wy, xd, yd, FUID, WID);
5803 FFireAngle := FAngle;
5804 end;
5806 WEAPON_BFG:
5807 begin
5808 g_Weapon_BFGShot(wx, wy, xd, yd, FUID, WID);
5809 FFireAngle := FAngle;
5810 end;
5812 WEAPON_SUPERCHAINGUN:
5813 begin
5814 {$IFDEF ENABLE_SOUND}
5815 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex, Gamey);
5816 {$ENDIF}
5817 FFireAngle := FAngle;
5818 g_Player_CreateShell(
5819 GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
5820 GameVelX, GameVelY-2,
5821 SHELL_SHELL
5823 end;
5825 WEAPON_FLAMETHROWER:
5826 begin
5827 g_Weapon_flame(wx, wy, xd, yd, FUID, WID);
5828 FlamerOn;
5829 FFireAngle := FAngle;
5830 end;
5831 end;
5833 if not visible then Exit;
5835 case FAngle of
5836 0, 180: SetAction(A_ATTACK);
5837 ANGLE_LEFTDOWN, ANGLE_RIGHTDOWN: SetAction(A_ATTACKDOWN);
5838 ANGLE_LEFTUP, ANGLE_RIGHTUP: SetAction(A_ATTACKUP);
5839 end;
5840 end;
5842 procedure TPlayer.DoLerp(Level: Integer = 2);
5843 begin
5844 if FObj.X <> FXTo then FObj.X := Lerp(FObj.X, FXTo, Level);
5845 if FObj.Y <> FYTo then FObj.Y := Lerp(FObj.Y, FYTo, Level);
5846 end;
5848 procedure TPlayer.SetLerp(XTo, YTo: Integer);
5850 AX, AY: Integer;
5851 begin
5852 FXTo := XTo;
5853 FYTo := YTo;
5854 if FJustTeleported or (NetInterpLevel < 1) then
5855 begin
5856 FObj.X := XTo;
5857 FObj.Y := YTo;
5858 if FJustTeleported then
5859 begin
5860 FObj.oldX := FObj.X;
5861 FObj.oldY := FObj.Y;
5862 end;
5864 else
5865 begin
5866 AX := Abs(FXTo - FObj.X);
5867 AY := Abs(FYTo - FObj.Y);
5868 if (AX > 32) or (AX <= NetInterpLevel) then
5869 FObj.X := FXTo;
5870 if (AY > 32) or (AY <= NetInterpLevel) then
5871 FObj.Y := FYTo;
5872 end;
5873 end;
5875 function TPlayer.FullInLift(XInc, YInc: Integer): Integer;
5876 begin
5877 if g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
5878 PLAYER_RECT.Width, PLAYER_RECT.Height-8,
5879 PANEL_LIFTUP, False) then Result := -1
5880 else
5881 if g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
5882 PLAYER_RECT.Width, PLAYER_RECT.Height-8,
5883 PANEL_LIFTDOWN, False) then Result := 1
5884 else Result := 0;
5885 end;
5887 function TPlayer.GetFlag(Flag: Byte): Boolean;
5889 s, ts: String;
5890 evtype: Byte;
5891 {$IFDEF ENABLE_SOUND}
5892 a: Byte;
5893 {$ENDIF}
5894 begin
5895 Result := False;
5897 if Flag = FLAG_NONE then
5898 Exit;
5900 if not g_Game_IsServer then Exit;
5902 // Ïðèíåñ ÷óæîé ôëàã íà ñâîþ áàçó:
5903 if (Flag = FTeam) and (gFlags[Flag].State = FLAG_STATE_NORMAL) and (FFlag <> FLAG_NONE) then
5904 begin
5905 if FFlag = FLAG_RED
5906 then s := _lc[I_PLAYER_FLAG_RED]
5907 else s := _lc[I_PLAYER_FLAG_BLUE];
5909 evtype := FLAG_STATE_SCORED;
5911 ts := Format('%.4d', [gFlags[FFlag].CaptureTime]);
5912 Insert('.', ts, Length(ts) + 1 - 3);
5913 g_Console_Add(Format(_lc[I_PLAYER_FLAG_CAPTURE], [FName, s, ts]), True);
5915 g_Map_ResetFlag(FFlag);
5916 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_CAPTURE], [AnsiUpperCase(s)]), 144);
5918 {$IFDEF ENABLE_SOUND}
5919 if ((Self = gPlayer1) or (Self = gPlayer2)
5920 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
5921 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam)))
5922 then a := 0
5923 else a := 1;
5925 if not sound_cap_flag[a].IsPlaying() then
5926 sound_cap_flag[a].Play();
5927 {$ENDIF}
5929 gTeamStat[FTeam].Score += 1;
5931 Result := True;
5932 if g_Game_IsNet then
5933 begin
5934 MH_SEND_FlagEvent(evtype, FFlag, FUID, False);
5935 MH_SEND_GameStats;
5936 end;
5938 gFlags[FFlag].CaptureTime := 0;
5939 SetFlag(FLAG_NONE);
5940 Exit;
5941 end;
5943 // Ïîäîáðàë ñâîé ôëàã - âåðíóë åãî íà áàçó:
5944 if (Flag = FTeam) and (gFlags[Flag].State = FLAG_STATE_DROPPED) then
5945 begin
5946 if Flag = FLAG_RED
5947 then s := _lc[I_PLAYER_FLAG_RED]
5948 else s := _lc[I_PLAYER_FLAG_BLUE];
5950 evtype := FLAG_STATE_RETURNED;
5951 gFlags[Flag].CaptureTime := 0;
5953 g_Console_Add(Format(_lc[I_PLAYER_FLAG_RETURN], [FName, s]), True);
5955 g_Map_ResetFlag(Flag);
5956 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
5958 {$IFDEF ENABLE_SOUND}
5959 if ((Self = gPlayer1) or (Self = gPlayer2)
5960 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
5961 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam)))
5962 then a := 0
5963 else a := 1;
5965 if not sound_ret_flag[a].IsPlaying() then
5966 sound_ret_flag[a].Play();
5967 {$ENDIF}
5969 Result := True;
5970 if g_Game_IsNet then
5971 begin
5972 MH_SEND_FlagEvent(evtype, Flag, FUID, False);
5973 MH_SEND_GameStats;
5974 end;
5975 Exit;
5976 end;
5978 // Ïîäîáðàë ÷óæîé ôëàã:
5979 if (Flag <> FTeam) and (FTime[T_FLAGCAP] <= gTime) then
5980 begin
5981 SetFlag(Flag);
5983 if Flag = FLAG_RED
5984 then s := _lc[I_PLAYER_FLAG_RED]
5985 else s := _lc[I_PLAYER_FLAG_BLUE];
5987 evtype := FLAG_STATE_CAPTURED;
5989 g_Console_Add(Format(_lc[I_PLAYER_FLAG_GET], [FName, s]), True);
5991 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_GET], [AnsiUpperCase(s)]), 144);
5993 gFlags[Flag].State := FLAG_STATE_CAPTURED;
5995 {$IFDEF ENABLE_SOUND}
5996 if ((Self = gPlayer1) or (Self = gPlayer2)
5997 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
5998 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam)))
5999 then a := 0
6000 else a := 1;
6002 if not sound_get_flag[a].IsPlaying() then
6003 sound_get_flag[a].Play();
6004 {$ENDIF}
6006 Result := True;
6007 if g_Game_IsNet then
6008 begin
6009 MH_SEND_FlagEvent(evtype, Flag, FUID, False);
6010 MH_SEND_GameStats;
6011 end;
6012 end;
6013 end;
6015 procedure TPlayer.SetFlag(Flag: Byte);
6016 begin
6017 FFlag := Flag;
6018 if FModel <> nil then
6019 FModel.SetFlag(FFlag);
6020 end;
6022 function TPlayer.TryDropFlag(): Boolean;
6023 begin
6024 if (TGameOption.ALLOW_DROP_FLAG in gGameSettings.Options)
6025 then Result := DropFlag(False, TGameOption.THROW_FLAG in gGameSettings.Options)
6026 else Result := False;
6027 end;
6029 function TPlayer.DropFlag(Silent: Boolean; DoThrow: Boolean): Boolean;
6031 s: String;
6032 a: Byte;
6033 xv, yv: Integer;
6034 begin
6035 Result := False;
6036 if (not g_Game_IsServer) or (FFlag = FLAG_NONE) then
6037 Exit;
6038 FTime[T_FLAGCAP] := gTime + 2000;
6039 with gFlags[FFlag] do
6040 begin
6041 Obj.X := FObj.X;
6042 Obj.Y := FObj.Y;
6043 Direction := FDirection;
6044 State := FLAG_STATE_DROPPED;
6045 Count := FLAG_TIME;
6046 if DoThrow then
6047 begin
6048 xv := FObj.Vel.X + IfThen(Direction = TDirection.D_RIGHT, 10, -10);
6049 yv := FObj.Vel.Y - 2;
6051 else
6052 begin
6053 xv := (FObj.Vel.X div 2);
6054 yv := (FObj.Vel.Y div 2) - 2;
6055 end;
6056 g_Obj_Push(@Obj, xv, yv);
6058 positionChanged(); // this updates spatial accelerators
6060 if FFlag = FLAG_RED
6061 then s := _lc[I_PLAYER_FLAG_RED]
6062 else s := _lc[I_PLAYER_FLAG_BLUE];
6064 g_Console_Add(Format(_lc[I_PLAYER_FLAG_DROP], [FName, s]), True);
6065 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_DROP], [AnsiUpperCase(s)]), 144);
6067 {$IFDEF ENABLE_SOUND}
6068 if ((Self = gPlayer1) or (Self = gPlayer2)
6069 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
6070 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam)))
6071 then a := 0
6072 else a := 1;
6074 if (not Silent) and (not sound_lost_flag[a].IsPlaying()) then
6075 sound_lost_flag[a].Play();
6076 {$ENDIF}
6078 if g_Game_IsNet then
6079 MH_SEND_FlagEvent(FLAG_STATE_DROPPED, Flag, FUID, False);
6080 end;
6081 SetFlag(FLAG_NONE);
6082 Result := True;
6083 end;
6085 procedure TPlayer.GetSecret();
6086 begin
6087 if (self = gPlayer1) or (self = gPlayer2) then
6088 begin
6089 g_Console_Add(Format(_lc[I_PLAYER_SECRET], [FName]), True);
6090 {$IFDEF ENABLE_SOUND}
6091 g_Sound_PlayEx('SOUND_GAME_SECRET');
6092 {$ENDIF}
6093 end;
6094 FSecrets += 1;
6095 end;
6097 procedure TPlayer.PressKey(Key: Byte; Time: Word);
6098 begin
6099 Assert(Key <= High(FKeys));
6101 FKeys[Key].Pressed := True;
6102 FKeys[Key].Time := Time;
6103 end;
6105 function TPlayer.IsKeyPressed(K: Byte): Boolean;
6106 begin
6107 Result := FKeys[K].Pressed;
6108 end;
6110 procedure TPlayer.ReleaseKeys();
6112 a: Integer;
6113 begin
6114 for a := Low(FKeys) to High(FKeys) do
6115 begin
6116 FKeys[a].Pressed := False;
6117 FKeys[a].Time := 0;
6118 end;
6119 end;
6121 procedure TPlayer.OnDamage(Angle: SmallInt);
6122 begin
6123 end;
6125 function TPlayer.firediry(): Integer;
6126 begin
6127 if FKeys[KEY_UP].Pressed then Result := -42
6128 else if FKeys[KEY_DOWN].Pressed then Result := 19
6129 else Result := 0;
6130 end;
6132 procedure TPlayer.PreserveState();
6134 i: Integer;
6135 SavedState: TPlayerSavedState;
6136 begin
6137 SavedState.Health := FHealth;
6138 SavedState.Armor := FArmor;
6139 SavedState.Air := FAir;
6140 SavedState.JetFuel := FJetFuel;
6141 SavedState.CurrWeap := FCurrWeap;
6142 SavedState.NextWeap := FNextWeap;
6143 SavedState.NextWeapDelay := FNextWeapDelay;
6144 for i := Low(FWeapon) to High(FWeapon) do
6145 SavedState.Weapon[i] := FWeapon[i];
6146 for i := Low(FAmmo) to High(FAmmo) do
6147 SavedState.Ammo[i] := FAmmo[i];
6148 for i := Low(FMaxAmmo) to High(FMaxAmmo) do
6149 SavedState.MaxAmmo[i] := FMaxAmmo[i];
6150 SavedState.Inventory := FInventory - [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE];
6152 FSavedStateNum := -1;
6153 for i := Low(SavedStates) to High(SavedStates) do
6154 if not SavedStates[i].Used then
6155 begin
6156 FSavedStateNum := i;
6157 break;
6158 end;
6159 if FSavedStateNum < 0 then
6160 begin
6161 SetLength(SavedStates, Length(SavedStates) + 1);
6162 FSavedStateNum := High(SavedStates);
6163 end;
6165 SavedState.Used := True;
6166 SavedStates[FSavedStateNum] := SavedState;
6167 end;
6169 procedure TPlayer.RestoreState();
6171 i: Integer;
6172 SavedState: TPlayerSavedState;
6173 begin
6174 if(FSavedStateNum < 0) or (FSavedStateNum > High(SavedStates)) then
6175 Exit;
6177 SavedState := SavedStates[FSavedStateNum];
6178 SavedStates[FSavedStateNum].Used := False;
6179 FSavedStateNum := -1;
6181 FHealth := SavedState.Health;
6182 FArmor := SavedState.Armor;
6183 FAir := SavedState.Air;
6184 FJetFuel := SavedState.JetFuel;
6185 FCurrWeap := SavedState.CurrWeap;
6186 FNextWeap := SavedState.NextWeap;
6187 FNextWeapDelay := SavedState.NextWeapDelay;
6188 for i := Low(FWeapon) to High(FWeapon) do
6189 FWeapon[i] := SavedState.Weapon[i];
6190 for i := Low(FAmmo) to High(FAmmo) do
6191 FAmmo[i] := SavedState.Ammo[i];
6192 for i := Low(FMaxAmmo) to High(FMaxAmmo) do
6193 FMaxAmmo[i] := SavedState.MaxAmmo[i];
6194 FInventory := SavedState.Inventory;
6196 if gGameSettings.GameType = GT_SERVER then
6197 MH_SEND_PlayerStats(FUID);
6198 end;
6200 procedure TPlayer.SaveState (st: TStream);
6202 i: Integer;
6203 begin
6204 // Ñèãíàòóðà èãðîêà
6205 utils.writeSign(st, 'PLYR');
6206 st.WriteByte(PLR_SAVE_VERSION); // version
6208 st.WriteBool(FIamBot); // Áîò èëè ÷åëîâåê
6209 st.WriteWordLE(FUID); // UID èãðîêà
6210 utils.writeStr(st, FName); // Èìÿ èãðîêà
6211 st.WriteByte(FTeam); // Êîìàíäà
6212 st.WriteBool(FAlive); // Æèâ ëè
6213 st.WriteBool(FNoRespawn); // Èçðàñõîäîâàë ëè âñå æèçíè
6215 // Íàïðàâëåíèå
6216 if FDirection = TDirection.D_LEFT
6217 then st.WriteByte(1)
6218 else st.WriteByte(2); // D_RIGHT
6220 st.WriteInt32LE(FHealth); // Çäîðîâüå
6221 st.WriteInt32LE(FHandicap); // Êîýôôèöèåíò èíâàëèäíîñòè
6222 st.WriteByte(FLives); // Æèçíè
6223 st.WriteInt32LE(FArmor); // Áðîíÿ
6224 st.WriteInt32LE(FAir); // Çàïàñ âîçäóõà
6225 st.WriteInt32LE(FJetFuel); // Çàïàñ ãîðþ÷åãî
6226 st.WriteInt32LE(FPain); // Áîëü
6227 st.WriteInt32LE(FKills); // Óáèë
6228 st.WriteInt32LE(FMonsterKills); // Óáèë ìîíñòðîâ
6229 st.WriteInt32LE(FFrags); // Ôðàãîâ
6230 st.WriteByte(FFragCombo); // Ôðàãîâ ïîäðÿä
6231 st.WriteDWordLE(FLastFrag); // Âðåìÿ ïîñëåäíåãî ôðàãà
6232 st.WriteInt32LE(FDeath); // Ñìåðòåé
6233 st.WriteByte(FFlag); // Êàêîé ôëàã íåñåò
6234 st.WriteInt32LE(FSecrets); // Íàøåë ñåêðåòîâ
6235 st.WriteByte(FCurrWeap); // Òåêóùåå îðóæèå
6236 st.WriteWordLE(FNextWeap); // Æåëàåìîå îðóæèå
6237 st.WriteByte(FNextWeapDelay); // ...è ïàóçà
6238 st.WriteInt16LE(FBFGFireCounter); // Âðåìÿ çàðÿäêè BFG
6239 st.WriteInt32LE(FDamageBuffer); // Áóôåð óðîíà
6240 st.WriteWordLE(FLastSpawnerUID); // Ïîñëåäíèé óäàðèâøèé
6241 st.WriteByte(FLastHit); // Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà
6242 Obj_SaveState(st, @FObj); // Îáúåêò èãðîêà
6244 // Òåêóùåå êîëè÷åñòâî ïàòðîíîâ
6245 for i := A_BULLETS to A_HIGH do
6246 st.WriteWordLE(FAmmo[i]);
6248 // Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ
6249 for i := A_BULLETS to A_HIGH do
6250 st.WriteWordLE(FMaxAmmo[i]);
6252 // Íàëè÷èå îðóæèÿ
6253 for i := WP_FIRST to WP_LAST do
6254 st.WriteBool(FWeapon[i]);
6256 // Âðåìÿ ïåðåçàðÿäêè îðóæèÿ
6257 for i := WP_FIRST to WP_LAST do
6258 st.WriteWordLE(FReloading[i]);
6260 st.WriteBool(R_ITEM_BACKPACK in FInventory); // Íàëè÷èå ðþêçàêà
6261 st.WriteBool(R_KEY_RED in FInventory); // Íàëè÷èå êðàñíîãî êëþ÷à
6262 st.WriteBool(R_KEY_GREEN in FInventory); // Íàëè÷èå çåëåíîãî êëþ÷à
6263 st.WriteBool(R_KEY_BLUE in FInventory); // Íàëè÷èå ñèíåãî êëþ÷à
6264 st.WriteBool(R_BERSERK in FInventory); // Íàëè÷èå áåðñåðêà
6266 // Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ
6267 for i := MR_SUIT to MR_MAX do
6268 st.WriteDWordLE(FPowerups[i]);
6270 // Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà
6271 for i := T_RESPAWN to T_FLAGCAP do
6272 st.WriteDWordLE(FTime[i]);
6274 // Íàçâàíèå ìîäåëè
6275 utils.writeStr(st, FModel.Name);
6276 // Öâåò ìîäåëè
6277 st.WriteByte(FColor.R);
6278 st.WriteByte(FColor.G);
6279 st.WriteByte(FColor.B);
6280 end;
6283 procedure TPlayer.LoadState (st: TStream);
6285 i: Integer;
6286 str: String;
6287 begin
6288 Assert(st <> nil);
6290 // Ñèãíàòóðà èãðîêà
6291 if not utils.checkSign(st, 'PLYR') then
6292 Raise XStreamError.Create('invalid player signature');
6293 if st.ReadByte() <> PLR_SAVE_VERSION then
6294 Raise XStreamError.Create('invalid player version');
6296 FIamBot := st.ReadBool(); // Áîò èëè ÷åëîâåê:
6297 FUID := st.ReadWordLE(); // UID èãðîêà
6299 // Èìÿ èãðîêà
6300 str := utils.readStr(st);
6301 if (self <> gPlayer1) and (self <> gPlayer2) then
6302 FName := str;
6304 FTeam := st.ReadByte(); // Êîìàíäà
6305 FAlive := st.ReadBool(); // Æèâ ëè
6306 FNoRespawn := st.ReadBool(); // Èçðàñõîäîâàë ëè âñå æèçíè
6308 // Íàïðàâëåíèå
6309 if st.ReadByte() = 1
6310 then FDirection := TDirection.D_LEFT
6311 else FDirection := TDirection.D_RIGHT; // b = 2
6313 FHealth := st.ReadInt32LE(); // Çäîðîâüå
6314 FHandicap := st.ReadInt32LE(); // Êîýôôèöèåíò èíâàëèäíîñòè
6315 FLives := st.ReadByte(); // Æèçíè
6316 FArmor := st.ReadInt32LE(); // Áðîíÿ
6317 FAir := st.ReadInt32LE(); // Çàïàñ âîçäóõà
6318 FJetFuel := st.ReadInt32LE(); // Çàïàñ ãîðþ÷åãî
6319 FPain := st.ReadInt32LE(); // Áîëü
6320 FKills := st.ReadInt32LE(); // Óáèë
6321 FMonsterKills := st.ReadInt32LE(); // Óáèë ìîíñòðîâ
6322 FFrags := st.ReadInt32LE(); // Ôðàãîâ
6323 FFragCombo := st.ReadByte(); // Ôðàãîâ ïîäðÿä
6324 FLastFrag := st.ReadDWordLE(); // Âðåìÿ ïîñëåäíåãî ôðàãà
6325 FDeath := st.ReadInt32LE(); // Ñìåðòåé
6326 FFlag := st.ReadByte(); // Êàêîé ôëàã íåñåò
6327 FSecrets := st.ReadInt32LE(); // Íàøåë ñåêðåòîâ
6328 FCurrWeap := st.ReadByte(); // Òåêóùåå îðóæèå
6329 FNextWeap := st.ReadWordLE(); // Æåëàåìîå îðóæèå
6330 FNextWeapDelay := st.ReadByte(); // ...è ïàóçà
6331 FBFGFireCounter := st.ReadInt16LE(); // Âðåìÿ çàðÿäêè BFG
6332 FDamageBuffer := st.ReadInt32LE(); // Áóôåð óðîíà
6333 FLastSpawnerUID := st.ReadWordLE(); // Ïîñëåäíèé óäàðèâøèé
6334 FLastHit := st.ReadByte(); // Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà
6335 Obj_LoadState(@FObj, st); // Îáúåêò èãðîêà
6337 // Òåêóùåå êîëè÷åñòâî ïàòðîíîâ
6338 for i := A_BULLETS to A_HIGH do
6339 FAmmo[i] := st.ReadWordLE();
6341 // Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ
6342 for i := A_BULLETS to A_HIGH do
6343 FMaxAmmo[i] := st.ReadWordLE();
6345 // Íàëè÷èå îðóæèÿ
6346 for i := WP_FIRST to WP_LAST do
6347 FWeapon[i] := st.ReadBool();
6349 // Âðåìÿ ïåðåçàðÿäêè îðóæèÿ
6350 for i := WP_FIRST to WP_LAST do
6351 FReloading[i] := st.ReadWordLE();
6353 if st.ReadBool() then FInventory += [R_ITEM_BACKPACK]; // Íàëè÷èå ðþêçàêà
6354 if st.ReadBool() then FInventory += [R_KEY_RED]; // Íàëè÷èå êðàñíîãî êëþ÷à
6355 if st.ReadBool() then FInventory += [R_KEY_GREEN]; // Íàëè÷èå çåëåíîãî êëþ÷à
6356 if st.ReadBool() then FInventory += [R_KEY_BLUE]; // Íàëè÷èå ñèíåãî êëþ÷à
6357 if st.ReadBool() then FInventory += [R_BERSERK]; // Íàëè÷èå áåðñåðêà
6359 // Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ
6360 for i := MR_SUIT to MR_MAX do
6361 FPowerups[i] := st.ReadDWordLE();
6363 // Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà
6364 for i := T_RESPAWN to T_FLAGCAP do
6365 FTime[i] := st.ReadDWordLE();
6367 // Íàçâàíèå ìîäåëè
6368 str := utils.readStr(st);
6369 // Öâåò ìîäåëè
6370 FColor.R := st.ReadByte();
6371 FColor.G := st.ReadByte();
6372 FColor.B := st.ReadByte();
6374 // Îáíîâëÿåì ìîäåëü èãðîêà
6375 if self = gPlayer1 then
6376 begin
6377 str := gPlayer1Settings.Model;
6378 FColor := gPlayer1Settings.Color;
6380 else if self = gPlayer2 then
6381 begin
6382 str := gPlayer2Settings.Model;
6383 FColor := gPlayer2Settings.Color;
6384 end;
6386 SetModel(str);
6387 if gGameSettings.GameMode in [GM_TDM, GM_CTF]
6388 then FModel.Color := TEAMCOLOR[FTeam]
6389 else FModel.Color := FColor;
6390 end;
6393 procedure TPlayer.TankRamboCheats(Health: Boolean);
6395 a: Integer;
6396 begin
6397 if Health then
6398 begin
6399 FHealth := PLAYER_HP_LIMIT;
6400 FArmor := PLAYER_AP_LIMIT;
6401 Exit;
6402 end;
6404 for a := WP_FIRST to WP_LAST do FWeapon[a] := True;
6405 for a := A_BULLETS to A_HIGH do FAmmo[a] := 30000;
6406 FInventory += [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE];
6407 end;
6409 procedure TPlayer.RestoreHealthArmor();
6410 begin
6411 FHealth := PLAYER_HP_LIMIT;
6412 FArmor := PLAYER_AP_LIMIT;
6413 end;
6415 procedure TPlayer.FragCombo();
6417 Param: Integer;
6418 begin
6419 if (gGameSettings.GameMode in [GM_COOP, GM_SINGLE]) or g_Game_IsClient then
6420 Exit;
6421 if gTime - FLastFrag < FRAG_COMBO_TIME then
6422 begin
6423 if FFragCombo < 5 then
6424 Inc(FFragCombo);
6425 Param := FUID or (FFragCombo shl 16);
6426 if (FComboEvnt >= Low(gDelayedEvents)) and
6427 (FComboEvnt <= High(gDelayedEvents)) and
6428 gDelayedEvents[FComboEvnt].Pending and
6429 (gDelayedEvents[FComboEvnt].DEType = DE_KILLCOMBO) and
6430 (gDelayedEvents[FComboEvnt].DENum and $FFFF = FUID) then
6431 begin
6432 gDelayedEvents[FComboEvnt].Time := gTime + 500;
6433 gDelayedEvents[FComboEvnt].DENum := Param;
6435 else
6436 FComboEvnt := g_Game_DelayEvent(DE_KILLCOMBO, 500, Param);
6438 else
6439 FFragCombo := 1;
6441 FLastFrag := gTime;
6442 end;
6444 procedure TPlayer.GiveItem(ItemType: Byte);
6445 begin
6446 case ItemType of
6447 ITEM_SUIT:
6448 if FPowerups[MR_SUIT] < gTime+PLAYER_SUIT_TIME then
6449 FPowerups[MR_SUIT] := gTime+PLAYER_SUIT_TIME;
6451 ITEM_OXYGEN:
6452 if FAir < AIR_MAX then FAir := AIR_MAX;
6454 ITEM_MEDKIT_BLACK:
6455 begin
6456 if not (R_BERSERK in FInventory) then
6457 begin
6458 FInventory += [R_BERSERK];
6459 if FBFGFireCounter < 1 then
6460 begin
6461 FCurrWeap := WEAPON_IRONFIST;
6462 resetWeaponQueue();
6463 FModel.SetWeapon(WEAPON_IRONFIST);
6464 end;
6465 if gFlash <> 0 then FPain += 100;
6466 FBerserk := gTime+30000;
6467 end;
6468 if FHealth < PLAYER_HP_SOFT then
6469 begin
6470 FHealth := PLAYER_HP_SOFT;
6471 FBerserk := gTime+30000;
6472 end;
6473 end;
6475 ITEM_INVUL:
6476 if FPowerups[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
6477 begin
6478 FPowerups[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
6479 FSpawnInvul := 0;
6480 end;
6482 ITEM_INVIS:
6483 if FPowerups[MR_INVIS] < gTime+PLAYER_INVIS_TIME then
6484 FPowerups[MR_INVIS] := gTime+PLAYER_INVIS_TIME;
6486 ITEM_JETPACK:
6487 if FJetFuel < JET_MAX then FJetFuel := JET_MAX;
6489 ITEM_MEDKIT_SMALL: if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 10, PLAYER_HP_SOFT);
6490 ITEM_MEDKIT_LARGE: if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 25, PLAYER_HP_SOFT);
6492 ITEM_ARMOR_GREEN: if FArmor < PLAYER_AP_SOFT then FArmor := PLAYER_AP_SOFT;
6493 ITEM_ARMOR_BLUE: if FArmor < PLAYER_AP_LIMIT then FArmor := PLAYER_AP_LIMIT;
6495 ITEM_SPHERE_BLUE: if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 100, PLAYER_HP_LIMIT);
6496 ITEM_SPHERE_WHITE:
6497 if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) then
6498 begin
6499 if FHealth < PLAYER_HP_LIMIT then FHealth := PLAYER_HP_LIMIT;
6500 if FArmor < PLAYER_AP_LIMIT then FArmor := PLAYER_AP_LIMIT;
6501 end;
6503 ITEM_WEAPON_SAW: FWeapon[WEAPON_SAW] := True;
6504 ITEM_WEAPON_SHOTGUN1: FWeapon[WEAPON_SHOTGUN1] := True;
6505 ITEM_WEAPON_SHOTGUN2: FWeapon[WEAPON_SHOTGUN2] := True;
6506 ITEM_WEAPON_CHAINGUN: FWeapon[WEAPON_CHAINGUN] := True;
6507 ITEM_WEAPON_ROCKETLAUNCHER: FWeapon[WEAPON_ROCKETLAUNCHER] := True;
6508 ITEM_WEAPON_PLASMA: FWeapon[WEAPON_PLASMA] := True;
6509 ITEM_WEAPON_BFG: FWeapon[WEAPON_BFG] := True;
6510 ITEM_WEAPON_SUPERCHAINGUN: FWeapon[WEAPON_SUPERCHAINGUN] := True;
6511 ITEM_WEAPON_FLAMETHROWER: FWeapon[WEAPON_FLAMETHROWER] := True;
6513 ITEM_AMMO_BULLETS: if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
6514 ITEM_AMMO_BULLETS_BOX: if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
6515 ITEM_AMMO_SHELLS: if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
6516 ITEM_AMMO_SHELLS_BOX: if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 25, FMaxAmmo[A_SHELLS]);
6517 ITEM_AMMO_ROCKET: if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
6518 ITEM_AMMO_ROCKET_BOX: if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 5, FMaxAmmo[A_ROCKETS]);
6519 ITEM_AMMO_CELL: if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
6520 ITEM_AMMO_CELL_BIG: if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 100, FMaxAmmo[A_CELLS]);
6521 ITEM_AMMO_FUELCAN: if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
6523 ITEM_AMMO_BACKPACK:
6524 if (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or
6525 (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or
6526 (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or
6527 (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or
6528 (FMaxAmmo[A_FUEL] < AmmoLimits[1, A_FUEL]) then
6529 begin
6530 FMaxAmmo[A_BULLETS] := AmmoLimits[1, A_BULLETS];
6531 FMaxAmmo[A_SHELLS] := AmmoLimits[1, A_SHELLS];
6532 FMaxAmmo[A_ROCKETS] := AmmoLimits[1, A_ROCKETS];
6533 FMaxAmmo[A_CELLS] := AmmoLimits[1, A_CELLS];
6534 FMaxAmmo[A_FUEL] := AmmoLimits[1, A_FUEL];
6536 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
6537 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
6538 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
6539 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
6541 FInventory += [R_ITEM_BACKPACK];
6542 end;
6544 ITEM_KEY_RED: if not (R_KEY_RED in FInventory) then FInventory += [R_KEY_RED];
6545 ITEM_KEY_GREEN: if not (R_KEY_GREEN in FInventory) then FInventory += [R_KEY_GREEN];
6546 ITEM_KEY_BLUE: if not (R_KEY_BLUE in FInventory) then FInventory += [R_KEY_BLUE];
6548 ITEM_BOTTLE: if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 4, PLAYER_HP_LIMIT);
6549 ITEM_HELMET: if FArmor < PLAYER_AP_LIMIT then IncMax(FArmor, 5, PLAYER_AP_LIMIT);
6551 else
6552 Exit;
6553 end;
6554 if g_Game_IsNet and g_Game_IsServer then
6555 MH_SEND_PlayerStats(FUID);
6556 end;
6558 procedure TPlayer.FlySmoke(Times: DWORD = 1);
6560 id, i: DWORD;
6561 Anim: TAnimation;
6562 begin
6563 if (Random(5) = 1) and (Times = 1) then
6564 Exit;
6566 if BodyInLiquid(0, 0) then
6567 begin
6568 g_Game_Effect_Bubbles(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)+Random(3)-1,
6569 Obj.Y+Obj.Rect.Height+8, 1, 8, 4);
6570 Exit;
6571 end;
6573 if g_Frames_Get(id, 'FRAMES_SMOKE') then
6574 begin
6575 for i := 1 to Times do
6576 begin
6577 Anim := TAnimation.Create(id, False, 3);
6578 Anim.Alpha := 150;
6579 g_GFX_OnceAnim(Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(Anim.Width div 2),
6580 Obj.Y+Obj.Rect.Height-4+Random(8+Times*2), Anim, ONCEANIM_SMOKE);
6581 Anim.Destroy();
6582 end;
6583 end;
6584 end;
6586 procedure TPlayer.OnFireFlame(Times: DWORD = 1);
6588 id, i: DWORD;
6589 Anim: TAnimation;
6590 begin
6591 if (Random(10) = 1) and (Times = 1) then
6592 Exit;
6594 if g_Frames_Get(id, 'FRAMES_FLAME') then
6595 begin
6596 for i := 1 to Times do
6597 begin
6598 Anim := TAnimation.Create(id, False, 3);
6599 Anim.Alpha := 0;
6600 g_GFX_OnceAnim(Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(Anim.Width div 2),
6601 Obj.Y+8+Random(8+Times*2), Anim, ONCEANIM_SMOKE);
6602 Anim.Destroy();
6603 end;
6604 end;
6605 end;
6607 {$IFDEF ENABLE_SOUND}
6608 procedure TPlayer.PauseSounds(Enable: Boolean);
6609 begin
6610 FSawSound.Pause(Enable);
6611 FSawSoundIdle.Pause(Enable);
6612 FSawSoundHit.Pause(Enable);
6613 FSawSoundSelect.Pause(Enable);
6614 FFlameSoundOn.Pause(Enable);
6615 FFlameSoundOff.Pause(Enable);
6616 FFlameSoundWork.Pause(Enable);
6617 FJetSoundFly.Pause(Enable);
6618 FJetSoundOn.Pause(Enable);
6619 FJetSoundOff.Pause(Enable);
6620 end;
6621 {$ENDIF}
6623 { TCorpse: }
6625 constructor TCorpse.Create(X, Y: Integer; ModelName: String; aMess: Boolean);
6626 begin
6627 g_Obj_Init(@FObj);
6628 FObj.X := X;
6629 FObj.Y := Y;
6630 FObj.Rect := PLAYER_CORPSERECT;
6631 FModelName := ModelName;
6632 FMess := aMess;
6634 if FMess then
6635 begin
6636 FState := CORPSE_STATE_MESS;
6637 g_PlayerModel_GetAnim(ModelName, A_DIE2, FAnimation, FAnimationMask);
6639 else
6640 begin
6641 FState := CORPSE_STATE_NORMAL;
6642 g_PlayerModel_GetAnim(ModelName, A_DIE1, FAnimation, FAnimationMask);
6643 end;
6644 end;
6646 destructor TCorpse.Destroy();
6647 begin
6648 FAnimation.Free();
6649 FAnimationMask.Free();
6651 inherited;
6652 end;
6654 function TCorpse.ObjPtr (): PObj; inline;
6655 begin
6656 Result := @FObj;
6657 end;
6659 procedure TCorpse.positionChanged (); inline;
6660 begin
6661 end;
6663 procedure TCorpse.moveBy (dx, dy: Integer); inline;
6664 begin
6665 if (dx <> 0) or (dy <> 0) then
6666 begin
6667 FObj.X += dx;
6668 FObj.Y += dy;
6669 positionChanged();
6670 end;
6671 end;
6674 procedure TCorpse.getMapBox (out x, y, w, h: Integer); inline;
6675 begin
6676 x := FObj.X+PLAYER_CORPSERECT.X;
6677 y := FObj.Y+PLAYER_CORPSERECT.Y;
6678 w := PLAYER_CORPSERECT.Width;
6679 h := PLAYER_CORPSERECT.Height;
6680 end;
6683 procedure TCorpse.Damage(Value: Word; SpawnerUID: Word; vx, vy: Integer);
6685 pm: TPlayerModel;
6686 Blood: TModelBlood;
6687 begin
6688 if FState = CORPSE_STATE_REMOVEME then
6689 Exit;
6691 FDamage := FDamage + Value;
6693 if FDamage > 150 then
6694 begin
6695 if FAnimation <> nil then
6696 begin
6697 FAnimation.Free();
6698 FAnimation := nil;
6700 FState := CORPSE_STATE_REMOVEME;
6702 g_Player_CreateGibs(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
6703 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
6704 FModelName, FColor);
6706 {$IFDEF ENABLE_SOUND}
6707 // Çâóê ìÿñà îò òðóïà:
6708 pm := g_PlayerModel_Get(FModelName);
6709 pm.PlaySound(MODELSOUND_DIE, 5, FObj.X, FObj.Y);
6710 pm.Free;
6711 {$ENDIF}
6713 // Çëîâåùèé ñìåõ:
6714 if (gBodyKillEvent <> -1)
6715 and gDelayedEvents[gBodyKillEvent].Pending then
6716 gDelayedEvents[gBodyKillEvent].Pending := False;
6717 gBodyKillEvent := g_Game_DelayEvent(DE_BODYKILL, 1050, SpawnerUID);
6718 end;
6720 else
6721 begin
6722 Blood := g_PlayerModel_GetBlood(FModelName);
6723 FObj.Vel.X := FObj.Vel.X + vx;
6724 FObj.Vel.Y := FObj.Vel.Y + vy;
6725 g_GFX_Blood(FObj.X+PLAYER_CORPSERECT.X+(PLAYER_CORPSERECT.Width div 2),
6726 FObj.Y+PLAYER_CORPSERECT.Y+(PLAYER_CORPSERECT.Height div 2),
6727 Value, vx, vy, 16, (PLAYER_CORPSERECT.Height*2) div 3,
6728 Blood.R, Blood.G, Blood.B, Blood.Kind);
6729 end;
6730 end;
6732 procedure TCorpse.Draw();
6734 fX, fY: Integer;
6735 begin
6736 if FState = CORPSE_STATE_REMOVEME then
6737 Exit;
6739 FObj.lerp(gLerpFactor, fX, fY);
6741 if FAnimation <> nil then
6742 FAnimation.Draw(fX, fY, TMirrorType.None);
6744 if FAnimationMask <> nil then
6745 begin
6746 e_Colors := FColor;
6747 FAnimationMask.Draw(fX, fY, TMirrorType.None);
6748 e_Colors.R := 255;
6749 e_Colors.G := 255;
6750 e_Colors.B := 255;
6751 end;
6752 end;
6754 procedure TCorpse.Update();
6756 st: Word;
6757 begin
6758 if FState = CORPSE_STATE_REMOVEME then
6759 Exit;
6761 FObj.oldX := FObj.X;
6762 FObj.oldY := FObj.Y;
6764 if gTime mod (GAME_TICK*2) <> 0 then
6765 begin
6766 g_Obj_Move(@FObj, True, True, True);
6767 positionChanged(); // this updates spatial accelerators
6768 Exit;
6769 end;
6771 // Ñîïðîòèâëåíèå âîçäóõà äëÿ òðóïà:
6772 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
6774 st := g_Obj_Move(@FObj, True, True, True);
6775 positionChanged(); // this updates spatial accelerators
6777 if WordBool(st and MOVE_FALLOUT) then
6778 begin
6779 FState := CORPSE_STATE_REMOVEME;
6780 Exit;
6781 end;
6783 if FAnimation <> nil then
6784 FAnimation.Update();
6785 if FAnimationMask <> nil then
6786 FAnimationMask.Update();
6787 end;
6790 procedure TCorpse.SaveState (st: TStream);
6792 anim: Boolean;
6793 begin
6794 Assert(st <> nil);
6796 // Ñèãíàòóðà òðóïà
6797 utils.writeSign(st, 'CORP');
6798 st.WriteByte(0);
6800 st.WriteByte(FState); // Ñîñòîÿíèå
6801 st.WriteByte(FDamage); // Íàêîïëåííûé óðîí
6803 // Öâåò
6804 st.WriteByte(FColor.R);
6805 st.WriteByte(FColor.G);
6806 st.WriteByte(FColor.B);
6808 // Îáúåêò òðóïà
6809 Obj_SaveState(st, @FObj);
6810 st.WriteWordLE(FPlayerUID);
6812 // Åñòü ëè àíèìàöèÿ
6813 anim := FAnimation <> nil;
6814 st.WriteBool(anim);
6815 // Åñëè åñòü - ñîõðàíÿåì
6816 if anim then FAnimation.SaveState(st);
6818 // Åñòü ëè ìàñêà àíèìàöèè
6819 anim := FAnimationMask <> nil;
6820 st.WriteBool(anim);
6821 // Åñëè åñòü - ñîõðàíÿåì
6822 if anim then FAnimationMask.SaveState(st);
6823 end;
6826 procedure TCorpse.LoadState (st: TStream);
6828 anim: Boolean;
6829 begin
6830 Assert(st <> nil);
6832 // Ñèãíàòóðà òðóïà
6833 if not utils.checkSign(st, 'CORP') then
6834 Raise XStreamError.Create('invalid corpse signature');
6835 if st.ReadByte() <> 0 then
6836 Raise XStreamError.Create('invalid corpse version');
6838 FState := st.ReadByte(); // Ñîñòîÿíèå
6839 FDamage := st.ReadByte(); // Íàêîïëåííûé óðîí
6841 // Öâåò
6842 FColor.R := st.ReadByte();
6843 FColor.G := st.ReadByte();
6844 FColor.B := st.ReadByte();
6846 // Îáúåêò òðóïà
6847 Obj_LoadState(@FObj, st);
6848 FPlayerUID := st.ReadWordLE();
6850 // Åñòü ëè àíèìàöèÿ
6851 anim := st.ReadBool();
6852 // Åñëè åñòü - çàãðóæàåì
6853 if anim then
6854 begin
6855 Assert(FAnimation <> nil, 'TCorpse.LoadState: no FAnimation');
6856 FAnimation.LoadState(st);
6857 end;
6859 // Åñòü ëè ìàñêà àíèìàöèè
6860 anim := st.ReadBool();
6861 // Åñëè åñòü - çàãðóæàåì
6862 if anim then
6863 begin
6864 Assert(FAnimationMask <> nil, 'TCorpse.LoadState: no FAnimationMask');
6865 FAnimationMask.LoadState(st);
6866 end;
6867 end;
6869 { T B o t : }
6871 constructor TBot.Create();
6873 a: Integer;
6874 begin
6875 inherited Create();
6877 FPhysics := True;
6878 FSpectator := False;
6879 FGhost := False;
6881 FIamBot := True;
6883 Inc(gNumBots);
6885 for a := WP_FIRST to WP_LAST do
6886 begin
6887 FDifficult.WeaponPrior[a] := WEAPON_PRIOR1[a];
6888 FDifficult.CloseWeaponPrior[a] := WEAPON_PRIOR2[a];
6889 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
6890 end;
6891 end;
6893 destructor TBot.Destroy();
6894 begin
6895 Dec(gNumBots);
6896 inherited Destroy();
6897 end;
6899 procedure TBot.Draw();
6900 begin
6901 inherited Draw();
6903 //if FTargetUID <> 0 then e_DrawLine(1, FObj.X, FObj.Y, g_Player_Get(FTargetUID).FObj.X,
6904 // g_Player_Get(FTargetUID).FObj.Y, 255, 0, 0);
6905 end;
6907 procedure TBot.Respawn(Silent: Boolean; Force: Boolean = False);
6908 begin
6909 inherited Respawn(Silent, Force);
6911 FAIFlags := nil;
6912 FSelectedWeapon := FCurrWeap;
6913 resetWeaponQueue();
6914 FTargetUID := 0;
6915 end;
6917 procedure TBot.UpdateCombat();
6918 type
6919 TTarget = record
6920 UID: Word;
6921 X, Y: Integer;
6922 Rect: TRectWH;
6923 cX, cY: Integer;
6924 Dist: Word;
6925 Line: Boolean;
6926 Visible: Boolean;
6927 IsPlayer: Boolean;
6928 end;
6930 TTargetRecord = array of TTarget;
6932 function Compare(a, b: TTarget): Integer;
6933 begin
6934 if a.Line and not b.Line then // A íà ëèíèè îãíÿ
6935 Result := -1
6936 else
6937 if not a.Line and b.Line then // B íà ëèíèè îãíÿ
6938 Result := 1
6939 else // È A, è B íà ëèíèè èëè íå íà ëèíèè îãíÿ
6940 if (a.Line and b.Line) or ((not a.Line) and (not b.Line)) then
6941 begin
6942 if a.Dist > b.Dist then // B áëèæå
6943 Result := 1
6944 else // A áëèæå èëè ðàâíîóäàëåííî ñ B
6945 Result := -1;
6947 else // Ñòðàííî -> A
6948 Result := -1;
6949 end;
6952 a, x1, y1, x2, y2: Integer;
6953 targets: TTargetRecord;
6954 ammo: Word;
6955 Target, BestTarget: TTarget;
6956 firew, fireh: Integer;
6957 angle: SmallInt;
6958 mon: TMonster;
6959 pla, tpla: TPlayer;
6960 vsPlayer, vsMonster, ok: Boolean;
6963 function monsUpdate (mon: TMonster): Boolean;
6964 begin
6965 result := false; // don't stop
6966 if mon.alive and (mon.MonsterType <> MONSTER_BARREL) then
6967 begin
6968 if not TargetOnScreen(mon.Obj.X+mon.Obj.Rect.X, mon.Obj.Y+mon.Obj.Rect.Y) then exit;
6970 x2 := mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2);
6971 y2 := mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2);
6973 // Åñëè ìîíñòð íà ýêðàíå è íå ïðèêðûò ñòåíîé
6974 if g_TraceVector(x1, y1, x2, y2) then
6975 begin
6976 // Äîáàâëÿåì ê ñïèñêó âîçìîæíûõ öåëåé
6977 SetLength(targets, Length(targets)+1);
6978 with targets[High(targets)] do
6979 begin
6980 UID := mon.UID;
6981 X := mon.Obj.X;
6982 Y := mon.Obj.Y;
6983 cX := x2;
6984 cY := y2;
6985 Rect := mon.Obj.Rect;
6986 Dist := g_PatchLength(x1, y1, x2, y2);
6987 Line := (y1+4 < Target.Y + mon.Obj.Rect.Y + mon.Obj.Rect.Height) and
6988 (y1-4 > Target.Y + mon.Obj.Rect.Y);
6989 Visible := True;
6990 IsPlayer := False;
6991 end;
6992 end;
6993 end;
6994 end;
6996 begin
6997 vsPlayer := TGameOption.BOTS_VS_PLAYERS in gGameSettings.Options;
6998 vsMonster := TGameOption.BOTS_VS_MONSTERS in gGameSettings.Options;
7000 // Åñëè òåêóùåå îðóæèå íå òî, ÷òî íóæíî, òî ìåíÿåì:
7001 if FCurrWeap <> FSelectedWeapon then
7002 NextWeapon();
7004 // Åñëè íóæíî ñòðåëÿòü è íóæíîå îðóæèå, òî íàæàòü "Ñòðåëÿòü":
7005 if (GetAIFlag('NEEDFIRE') <> '') and (FCurrWeap = FSelectedWeapon) then
7006 begin
7007 RemoveAIFlag('NEEDFIRE');
7009 case FCurrWeap of
7010 WEAPON_PLASMA, WEAPON_SUPERCHAINGUN, WEAPON_CHAINGUN: PressKey(KEY_FIRE, 20);
7011 WEAPON_SAW, WEAPON_IRONFIST, WEAPON_FLAMETHROWER: PressKey(KEY_FIRE, 40);
7012 else PressKey(KEY_FIRE);
7013 end;
7014 end;
7016 // Êîîðäèíàòû ñòâîëà:
7017 x1 := FObj.X + WEAPONPOINT[FDirection].X;
7018 y1 := FObj.Y + WEAPONPOINT[FDirection].Y;
7020 Target.UID := FTargetUID;
7022 ok := False;
7023 if Target.UID <> 0 then
7024 begin // Öåëü åñòü - íàñòðàèâàåì
7025 if (g_GetUIDType(Target.UID) = UID_PLAYER) and
7026 vsPlayer then
7027 begin // Èãðîê
7028 tpla := g_Player_Get(Target.UID);
7029 if tpla <> nil then
7030 with tpla do
7031 begin
7032 if (@FObj) <> nil then
7033 begin
7034 Target.X := FObj.X;
7035 Target.Y := FObj.Y;
7036 end;
7037 end;
7039 Target.cX := Target.X + PLAYER_RECT_CX;
7040 Target.cY := Target.Y + PLAYER_RECT_CY;
7041 Target.Rect := PLAYER_RECT;
7042 Target.Visible := g_TraceVector(x1, y1, Target.cX, Target.cY);
7043 Target.Line := (y1+4 < Target.Y+PLAYER_RECT.Y+PLAYER_RECT.Height) and
7044 (y1-4 > Target.Y+PLAYER_RECT.Y);
7045 Target.IsPlayer := True;
7046 ok := True;
7048 else
7049 if (g_GetUIDType(Target.UID) = UID_MONSTER) and
7050 vsMonster then
7051 begin // Ìîíñòð
7052 mon := g_Monsters_ByUID(Target.UID);
7053 if mon <> nil then
7054 begin
7055 Target.X := mon.Obj.X;
7056 Target.Y := mon.Obj.Y;
7058 Target.cX := Target.X + mon.Obj.Rect.X + (mon.Obj.Rect.Width div 2);
7059 Target.cY := Target.Y + mon.Obj.Rect.Y + (mon.Obj.Rect.Height div 2);
7060 Target.Rect := mon.Obj.Rect;
7061 Target.Visible := g_TraceVector(x1, y1, Target.cX, Target.cY);
7062 Target.Line := (y1+4 < Target.Y + mon.Obj.Rect.Y + mon.Obj.Rect.Height) and
7063 (y1-4 > Target.Y + mon.Obj.Rect.Y);
7064 Target.IsPlayer := False;
7065 ok := True;
7066 end;
7067 end;
7068 end;
7070 if not ok then
7071 begin // Öåëè íåò - îáíóëÿåì
7072 Target.X := 0;
7073 Target.Y := 0;
7074 Target.cX := 0;
7075 Target.cY := 0;
7076 Target.Visible := False;
7077 Target.Line := False;
7078 Target.IsPlayer := False;
7079 end;
7081 targets := nil;
7083 // Åñëè öåëü íå âèäèìà èëè íå íà ëèíèè îãíÿ, òî èùåì âñå âîçìîæíûå öåëè:
7084 if (not Target.Line) or (not Target.Visible) then
7085 begin
7086 // Èãðîêè:
7087 if vsPlayer then
7088 for a := 0 to High(gPlayers) do
7089 if (gPlayers[a] <> nil) and (gPlayers[a].alive) and
7090 (gPlayers[a].FUID <> FUID) and
7091 (not SameTeam(FUID, gPlayers[a].FUID)) and
7092 (not gPlayers[a].NoTarget) and
7093 (gPlayers[a].FPowerups[MR_INVIS] < gTime) then
7094 begin
7095 if not TargetOnScreen(gPlayers[a].FObj.X + PLAYER_RECT.X,
7096 gPlayers[a].FObj.Y + PLAYER_RECT.Y) then
7097 Continue;
7099 x2 := gPlayers[a].FObj.X + PLAYER_RECT_CX;
7100 y2 := gPlayers[a].FObj.Y + PLAYER_RECT_CY;
7102 // Åñëè èãðîê íà ýêðàíå è íå ïðèêðûò ñòåíîé:
7103 if g_TraceVector(x1, y1, x2, y2) then
7104 begin
7105 // Äîáàâëÿåì ê ñïèñêó âîçìîæíûõ öåëåé:
7106 SetLength(targets, Length(targets)+1);
7107 with targets[High(targets)] do
7108 begin
7109 UID := gPlayers[a].FUID;
7110 X := gPlayers[a].FObj.X;
7111 Y := gPlayers[a].FObj.Y;
7112 cX := x2;
7113 cY := y2;
7114 Rect := PLAYER_RECT;
7115 Dist := g_PatchLength(x1, y1, x2, y2);
7116 Line := (y1+4 < Target.Y+PLAYER_RECT.Y+PLAYER_RECT.Height) and
7117 (y1-4 > Target.Y+PLAYER_RECT.Y);
7118 Visible := True;
7119 IsPlayer := True;
7120 end;
7121 end;
7122 end;
7124 // Ìîíñòðû:
7125 if vsMonster then g_Mons_ForEach(monsUpdate);
7126 end;
7128 // Åñëè åñòü âîçìîæíûå öåëè:
7129 // (Âûáèðàåì ëó÷øóþ, ìåíÿåì îðóæèå è áåæèì ê íåé/îò íåå)
7130 if targets <> nil then
7131 begin
7132 // Âûáèðàåì íàèëó÷øóþ öåëü:
7133 BestTarget := targets[0];
7134 if Length(targets) > 1 then
7135 for a := 1 to High(targets) do
7136 if Compare(BestTarget, targets[a]) = 1 then
7137 BestTarget := targets[a];
7139 // Åñëè ëó÷øàÿ öåëü "âèäíåå" òåêóùåé, òî òåêóùàÿ := ëó÷øàÿ:
7140 if ((not Target.Visible) and BestTarget.Visible and (Target.UID <> BestTarget.UID)) or
7141 ((not Target.Line) and BestTarget.Line and BestTarget.Visible) then
7142 begin
7143 Target := BestTarget;
7145 if (Healthy() = 3) or ((Healthy() = 2)) then
7146 begin // Åñëè çäîðîâû - äîãîíÿåì
7147 if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
7148 SetAIFlag('GORIGHT', '1');
7149 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
7150 SetAIFlag('GOLEFT', '1');
7152 else
7153 begin // Åñëè ïîáèòû - óáåãàåì
7154 if ((RunDirection() = TDirection.D_LEFT) and (Target.X < FObj.X)) then
7155 SetAIFlag('GORIGHT', '1');
7156 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
7157 SetAIFlag('GOLEFT', '1');
7158 end;
7160 // Âûáèðàåì îðóæèå íà îñíîâå ðàññòîÿíèÿ è ïðèîðèòåòîâ:
7161 SelectWeapon(Abs(x1-Target.cX));
7162 end;
7163 end;
7165 // Åñëè åñòü öåëü:
7166 // (Äîãîíÿåì/óáåãàåì, ñòðåëÿåì ïî íàïðàâëåíèþ ê öåëè)
7167 // (Åñëè öåëü äàëåêî, òî õâàòèò ñëåäèòü çà íåé)
7168 if Target.UID <> 0 then
7169 begin
7170 if not TargetOnScreen(Target.X + Target.Rect.X,
7171 Target.Y + Target.Rect.Y) then
7172 begin // Öåëü ñáåæàëà ñ "ýêðàíà"
7173 if (Healthy() = 3) or ((Healthy() = 2)) then
7174 begin // Åñëè çäîðîâû - äîãîíÿåì
7175 if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
7176 SetAIFlag('GORIGHT', '1');
7177 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
7178 SetAIFlag('GOLEFT', '1');
7180 else
7181 begin // Åñëè ïîáèòû - çàáûâàåì î öåëè è óáåãàåì
7182 Target.UID := 0;
7183 if ((RunDirection() = TDirection.D_LEFT) and (Target.X < FObj.X)) then
7184 SetAIFlag('GORIGHT', '1');
7185 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
7186 SetAIFlag('GOLEFT', '1');
7187 end;
7189 else
7190 begin // Öåëü ïîêà íà "ýêðàíå"
7191 // Åñëè öåëü íå çàãîðîæåíà ñòåíîé, òî îòìå÷àåì, êîãäà åå âèäåëè:
7192 if g_TraceVector(x1, y1, Target.cX, Target.cY) then
7193 FLastVisible := gTime;
7194 // Åñëè ðàçíèöà âûñîò íå âåëèêà, òî äîãîíÿåì:
7195 if (Abs(FObj.Y-Target.Y) <= 128) then
7196 begin
7197 if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
7198 SetAIFlag('GORIGHT', '1');
7199 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
7200 SetAIFlag('GOLEFT', '1');
7201 end;
7202 end;
7204 // Âûáèðàåì óãîë ââåðõ:
7205 if FDirection = TDirection.D_LEFT then
7206 angle := ANGLE_LEFTUP
7207 else
7208 angle := ANGLE_RIGHTUP;
7210 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7211 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7213 // Åñëè ïðè óãëå ââåðõ ìîæíî ïîïàñòü â ïðèáëèçèòåëüíîå ïîëîæåíèå öåëè:
7214 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
7215 Target.X+Target.Rect.X+GetInterval(FDifficult.DiagPrecision, 128), //96
7216 Target.Y+Target.Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
7217 Target.Rect.Width, Target.Rect.Height) and
7218 g_TraceVector(x1, y1, Target.cX, Target.cY) then
7219 begin // òî íóæíî ñòðåëÿòü ââåðõ
7220 SetAIFlag('NEEDFIRE', '1');
7221 SetAIFlag('NEEDSEEUP', '1');
7222 end;
7224 // Âûáèðàåì óãîë âíèç:
7225 if FDirection = TDirection.D_LEFT then
7226 angle := ANGLE_LEFTDOWN
7227 else
7228 angle := ANGLE_RIGHTDOWN;
7230 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7231 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7233 // Åñëè ïðè óãëå âíèç ìîæíî ïîïàñòü â ïðèáëèçèòåëüíîå ïîëîæåíèå öåëè:
7234 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
7235 Target.X+Target.Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
7236 Target.Y+Target.Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
7237 Target.Rect.Width, Target.Rect.Height) and
7238 g_TraceVector(x1, y1, Target.cX, Target.cY) then
7239 begin // òî íóæíî ñòðåëÿòü âíèç
7240 SetAIFlag('NEEDFIRE', '1');
7241 SetAIFlag('NEEDSEEDOWN', '1');
7242 end;
7244 // Åñëè öåëü âèäíî è îíà íà òàêîé æå âûñîòå:
7245 if Target.Visible and
7246 (y1+4 < Target.Y+Target.Rect.Y+Target.Rect.Height) and
7247 (y1-4 > Target.Y+Target.Rect.Y) then
7248 begin
7249 // Åñëè èäåì â ñòîðîíó öåëè, òî íàäî ñòðåëÿòü:
7250 if ((FDirection = TDirection.D_LEFT) and (Target.X < FObj.X)) or
7251 ((FDirection = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
7252 begin // òî íóæíî ñòðåëÿòü âïåðåä
7253 SetAIFlag('NEEDFIRE', '1');
7254 SetAIFlag('NEEDSEEDOWN', '');
7255 SetAIFlag('NEEDSEEUP', '');
7256 end;
7257 // Åñëè öåëü â ïðåäåëàõ "ýêðàíà" è ñëîæíîñòü ïîçâîëÿåò ïðûæêè ñáëèæåíèÿ:
7258 if Abs(FObj.X-Target.X) < Trunc(gPlayerScreenSize.X*0.75) then
7259 if GetRnd(FDifficult.CloseJump) then
7260 begin // òî åñëè ïîâåçåò - ïðûãàåì (îñîáåííî, åñëè áëèçêî)
7261 if Abs(FObj.X-Target.X) < 128 then
7262 a := 4
7263 else
7264 a := 30;
7265 if Random(a) = 0 then
7266 SetAIFlag('NEEDJUMP', '1');
7267 end;
7268 end;
7270 // Åñëè öåëü âñå åùå åñòü:
7271 if Target.UID <> 0 then
7272 if gTime-FLastVisible > 2000 then // Åñëè âèäåëè äàâíî
7273 Target.UID := 0 // òî çàáûòü öåëü
7274 else // Åñëè âèäåëè íåäàâíî
7275 begin // íî öåëü óáèëè
7276 if Target.IsPlayer then
7277 begin // Öåëü - èãðîê
7278 pla := g_Player_Get(Target.UID);
7279 if (pla = nil) or (not pla.alive) or pla.NoTarget or
7280 (pla.FPowerups[MR_INVIS] >= gTime) then
7281 Target.UID := 0; // òî çàáûòü öåëü
7283 else
7284 begin // Öåëü - ìîíñòð
7285 mon := g_Monsters_ByUID(Target.UID);
7286 if (mon = nil) or (not mon.alive) then
7287 Target.UID := 0; // òî çàáûòü öåëü
7288 end;
7289 end;
7290 end; // if Target.UID <> 0
7292 FTargetUID := Target.UID;
7294 // Åñëè âîçìîæíûõ öåëåé íåò:
7295 // (Àòàêà ÷åãî-íèáóäü ñëåâà èëè ñïðàâà)
7296 if targets = nil then
7297 if GetAIFlag('ATTACKLEFT') <> '' then
7298 begin // Åñëè íóæíî àòàêîâàòü íàëåâî
7299 RemoveAIFlag('ATTACKLEFT');
7301 SetAIFlag('NEEDJUMP', '1');
7303 if RunDirection() = TDirection.D_RIGHT then
7304 begin // Èäåì íå â òó ñòîðîíó
7305 if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then
7306 begin // Åñëè çäîðîâû, òî, âîçìîæíî, ñòðåëÿåì áåæèì âëåâî è ñòðåëÿåì
7307 SetAIFlag('NEEDFIRE', '1');
7308 SetAIFlag('GOLEFT', '1');
7309 end;
7311 else
7312 begin // Èäåì â íóæíóþ ñòîðîíó
7313 if GetRnd(FDifficult.InvisFire) then // Âîçìîæíî, ñòðåëÿåì âñëåïóþ
7314 SetAIFlag('NEEDFIRE', '1');
7315 if Healthy() <= 1 then // Ïîáèòû - óáåãàåì
7316 SetAIFlag('GORIGHT', '1');
7317 end;
7319 else
7320 if GetAIFlag('ATTACKRIGHT') <> '' then
7321 begin // Åñëè íóæíî àòàêîâàòü íàïðàâî
7322 RemoveAIFlag('ATTACKRIGHT');
7324 SetAIFlag('NEEDJUMP', '1');
7326 if RunDirection() = TDirection.D_LEFT then
7327 begin // Èäåì íå â òó ñòîðîíó
7328 if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then
7329 begin // Åñëè çäîðîâû, òî, âîçìîæíî, áåæèì âïðàâî è ñòðåëÿåì
7330 SetAIFlag('NEEDFIRE', '1');
7331 SetAIFlag('GORIGHT', '1');
7332 end;
7334 else
7335 begin
7336 if GetRnd(FDifficult.InvisFire) then // Âîçìîæíî, ñòðåëÿåì âñëåïóþ
7337 SetAIFlag('NEEDFIRE', '1');
7338 if Healthy() <= 1 then // Ïîáèòû - óáåãàåì
7339 SetAIFlag('GOLEFT', '1');
7340 end;
7341 end;
7343 //HACK! (does it belongs there?)
7344 RealizeCurrentWeapon();
7346 // Åñëè åñòü âîçìîæíûå öåëè:
7347 // (Ñòðåëÿåì ïî íàïðàâëåíèþ ê öåëÿì)
7348 if (targets <> nil) and (GetAIFlag('NEEDFIRE') <> '') then
7349 for a := 0 to High(targets) do
7350 begin
7351 // Åñëè ìîæåì ñòðåëÿòü ïî äèàãîíàëè:
7352 if GetRnd(FDifficult.DiagFire) then
7353 begin
7354 // Èùåì öåëü ñâåðõó è ñòðåëÿåì, åñëè åñòü:
7355 if FDirection = TDirection.D_LEFT then
7356 angle := ANGLE_LEFTUP
7357 else
7358 angle := ANGLE_RIGHTUP;
7360 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7361 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7363 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
7364 targets[a].X+targets[a].Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
7365 targets[a].Y+targets[a].Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
7366 targets[a].Rect.Width, targets[a].Rect.Height) and
7367 g_TraceVector(x1, y1, targets[a].cX, targets[a].cY) then
7368 begin
7369 SetAIFlag('NEEDFIRE', '1');
7370 SetAIFlag('NEEDSEEUP', '1');
7371 end;
7373 // Èùåì öåëü ñíèçó è ñòðåëÿåì, åñëè åñòü:
7374 if FDirection = TDirection.D_LEFT then
7375 angle := ANGLE_LEFTDOWN
7376 else
7377 angle := ANGLE_RIGHTDOWN;
7379 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7380 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7382 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
7383 targets[a].X+targets[a].Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
7384 targets[a].Y+targets[a].Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
7385 targets[a].Rect.Width, targets[a].Rect.Height) and
7386 g_TraceVector(x1, y1, targets[a].cX, targets[a].cY) then
7387 begin
7388 SetAIFlag('NEEDFIRE', '1');
7389 SetAIFlag('NEEDSEEDOWN', '1');
7390 end;
7391 end;
7393 // Åñëè öåëü "ïåðåä íîñîì", òî ñòðåëÿåì:
7394 if targets[a].Line and targets[a].Visible and
7395 (((FDirection = TDirection.D_LEFT) and (targets[a].X < FObj.X)) or
7396 ((FDirection = TDirection.D_RIGHT) and (targets[a].X > FObj.X))) then
7397 begin
7398 SetAIFlag('NEEDFIRE', '1');
7399 Break;
7400 end;
7401 end;
7403 // Åñëè ëåòèò ïóëÿ, òî, âîçìîæíî, ïîäïðûãèâàåì:
7404 if g_Weapon_Danger(FUID, FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y,
7405 PLAYER_RECT.Width, PLAYER_RECT.Height,
7406 40+GetInterval(FDifficult.Cover, 40)) then
7407 SetAIFlag('NEEDJUMP', '1');
7409 // Åñëè êîí÷èëèñü ïàòîðíû, òî íóæíî ñìåíèòü îðóæèå:
7410 ammo := GetAmmoByWeapon(FCurrWeap);
7411 if ((FCurrWeap = WEAPON_SHOTGUN2) and (ammo < 2)) or
7412 ((FCurrWeap = WEAPON_BFG) and (ammo < 40)) or
7413 (ammo = 0) then
7414 SetAIFlag('SELECTWEAPON', '1');
7416 // Åñëè íóæíî ñìåíèòü îðóæèå, òî âûáèðàåì íóæíîå:
7417 if GetAIFlag('SELECTWEAPON') = '1' then
7418 begin
7419 SelectWeapon(-1);
7420 RemoveAIFlag('SELECTWEAPON');
7421 end;
7422 end;
7424 procedure TBot.Update();
7426 EnableAI: Boolean;
7427 begin
7428 if not FAlive then
7429 begin // Respawn
7430 ReleaseKeys();
7431 PressKey(KEY_UP);
7433 else
7434 begin
7435 EnableAI := True;
7437 // Ïðîâåðÿåì, îòêëþ÷¸í ëè AI áîòîâ
7438 if (g_debug_BotAIOff = 1) and (Team = TEAM_RED) then
7439 EnableAI := False;
7440 if (g_debug_BotAIOff = 2) and (Team = TEAM_BLUE) then
7441 EnableAI := False;
7442 if g_debug_BotAIOff = 3 then
7443 EnableAI := False;
7445 if EnableAI then
7446 begin
7447 UpdateMove();
7448 UpdateCombat();
7450 else
7451 begin
7452 RealizeCurrentWeapon();
7453 end;
7454 end;
7456 inherited Update();
7457 end;
7459 procedure TBot.ReleaseKey(Key: Byte);
7460 begin
7461 with FKeys[Key] do
7462 begin
7463 Pressed := False;
7464 Time := 0;
7465 end;
7466 end;
7468 function TBot.KeyPressed(Key: Word): Boolean;
7469 begin
7470 Result := FKeys[Key].Pressed;
7471 end;
7473 function TBot.GetAIFlag(const aName: String): String;
7475 i: Integer;
7476 begin
7477 Result := '';
7478 if FAIFlags = nil then Exit;
7480 for i := 0 to High(FAIFlags) do
7481 if CompareText(aName, FAIFlags[i].Name) = 0 then
7482 Exit(FAIFlags[i].Value);
7483 end;
7485 procedure TBot.RemoveAIFlag(const aName: String);
7487 a, b: Integer;
7488 begin
7489 if FAIFlags = nil then Exit;
7491 for a := 0 to High(FAIFlags) do
7492 if CompareText(aName, FAIFlags[a].Name) = 0 then
7493 begin
7494 if a <> High(FAIFlags) then
7495 for b := a to High(FAIFlags)-1 do
7496 FAIFlags[b] := FAIFlags[b+1];
7498 SetLength(FAIFlags, Length(FAIFlags)-1);
7499 Break;
7500 end;
7501 end;
7503 procedure TBot.SetAIFlag(const aName, fValue: String);
7505 a: Integer;
7506 ok: Boolean;
7507 begin
7508 a := 0;
7509 ok := False;
7511 if FAIFlags <> nil then
7512 for a := 0 to High(FAIFlags) do
7513 if CompareText(aName, FAIFlags[a].Name) = 0 then
7514 begin
7515 ok := True;
7516 Break;
7517 end;
7519 if ok then FAIFlags[a].Value := fValue
7520 else
7521 begin
7522 SetLength(FAIFlags, Length(FAIFlags)+1);
7523 with FAIFlags[High(FAIFlags)] do
7524 begin
7525 Name := aName;
7526 Value := fValue;
7527 end;
7528 end;
7529 end;
7531 procedure TBot.UpdateMove;
7533 procedure GoLeft(Time: Word = 1);
7534 begin
7535 ReleaseKey(KEY_LEFT);
7536 ReleaseKey(KEY_RIGHT);
7537 PressKey(KEY_LEFT, Time);
7538 SetDirection(TDirection.D_LEFT);
7539 end;
7541 procedure GoRight(Time: Word = 1);
7542 begin
7543 ReleaseKey(KEY_LEFT);
7544 ReleaseKey(KEY_RIGHT);
7545 PressKey(KEY_RIGHT, Time);
7546 SetDirection(TDirection.D_RIGHT);
7547 end;
7549 function Rnd(a: Word): Boolean; inline;
7550 begin
7551 Result := Random(a) = 0;
7552 end;
7554 procedure Turn(Time: Word = 1200);
7555 begin
7556 if RunDirection() = TDirection.D_LEFT
7557 then GoRight(Time)
7558 else GoLeft(Time);
7559 end;
7561 procedure Stop();
7562 begin
7563 ReleaseKey(KEY_LEFT);
7564 ReleaseKey(KEY_RIGHT);
7565 end;
7567 function CanRunLeft(): Boolean;
7568 begin
7569 Result := not CollideLevel(-1, 0);
7570 end;
7572 function CanRunRight(): Boolean;
7573 begin
7574 Result := not CollideLevel(1, 0);
7575 end;
7577 function CanRun(): Boolean;
7578 begin
7579 if RunDirection() = TDirection.D_LEFT then Result := CanRunLeft() else Result := CanRunRight();
7580 end;
7582 procedure Jump(Time: Word = 30);
7583 begin
7584 PressKey(KEY_JUMP, Time);
7585 end;
7587 function NearHole(): Boolean;
7589 x, sx: Integer;
7590 begin
7591 { TODO 5 : Ëåñòíèöû }
7592 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
7593 for x := 1 to PLAYER_RECT.Width do
7594 if (not StayOnStep(x*sx, 0)) and
7595 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
7596 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
7597 begin
7598 Result := True;
7599 Exit;
7600 end;
7602 Result := False;
7603 end;
7605 function BorderHole(): Boolean;
7607 x, sx, xx: Integer;
7608 begin
7609 { TODO 5 : Ëåñòíèöû }
7610 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
7611 for x := 1 to PLAYER_RECT.Width do
7612 if (not StayOnStep(x*sx, 0)) and
7613 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
7614 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
7615 begin
7616 for xx := x to x+32 do
7617 if CollideLevel(xx*sx, PLAYER_RECT.Height) then
7618 begin
7619 Result := True;
7620 Exit;
7621 end;
7622 end;
7624 Result := False;
7625 end;
7627 function NearDeepHole(): Boolean;
7629 x, sx, y: Integer;
7630 begin
7631 Result := False;
7633 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
7634 y := 3;
7636 for x := 1 to PLAYER_RECT.Width do
7637 if (not StayOnStep(x*sx, 0)) and
7638 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
7639 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
7640 begin
7641 while FObj.Y+y*PLAYER_RECT.Height < gMapInfo.Height do
7642 begin
7643 if CollideLevel(x*sx, PLAYER_RECT.Height*y) then Exit;
7644 y := y+1;
7645 end;
7647 Result := True;
7648 end else Result := False;
7649 end;
7651 function OverDeepHole(): Boolean;
7653 y: Integer;
7654 begin
7655 Result := False;
7657 y := 1;
7658 while FObj.Y+y*PLAYER_RECT.Height < gMapInfo.Height do
7659 begin
7660 if CollideLevel(0, PLAYER_RECT.Height*y) then Exit;
7661 y := y+1;
7662 end;
7664 Result := True;
7665 end;
7667 function OnGround(): Boolean;
7668 begin
7669 Result := StayOnStep(0, 0) or CollideLevel(0, 1);
7670 end;
7672 function OnLadder(): Boolean;
7673 begin
7674 Result := FullInStep(0, 0);
7675 end;
7677 function BelowLadder(): Boolean;
7678 begin
7679 Result := (FullInStep(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) and
7680 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or
7681 (FullInStep(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) and
7682 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP));
7683 end;
7685 function BelowLiftUp(): Boolean;
7686 begin
7687 Result := ((FullInLift(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) = -1) and
7688 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or
7689 ((FullInLift(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) = -1) and
7690 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP));
7691 end;
7693 function OnTopLift(): Boolean;
7694 begin
7695 Result := (FullInLift(0, 0) = -1) and (FullInLift(0, -32) = 0);
7696 end;
7698 function CanJumpOver(): Boolean;
7700 sx, y: Integer;
7701 begin
7702 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
7704 Result := False;
7706 if not CollideLevel(sx, 0) then Exit;
7708 for y := 1 to BOT_MAXJUMP do
7709 if CollideLevel(0, -y) then Exit else
7710 if not CollideLevel(sx, -y) then
7711 begin
7712 Result := True;
7713 Exit;
7714 end;
7715 end;
7717 function CanJumpUp(Dist: ShortInt): Boolean;
7719 y, yy: Integer;
7720 c: Boolean;
7721 begin
7722 Result := False;
7724 if CollideLevel(Dist, 0) then Exit;
7726 c := False;
7727 for y := 0 to BOT_MAXJUMP do
7728 if CollideLevel(Dist, -y) then
7729 begin
7730 c := True;
7731 Break;
7732 end;
7734 if not c then Exit;
7736 c := False;
7737 for yy := y+1 to BOT_MAXJUMP do
7738 if not CollideLevel(Dist, -yy) then
7739 begin
7740 c := True;
7741 Break;
7742 end;
7744 if not c then Exit;
7746 c := False;
7747 for y := 0 to BOT_MAXJUMP do
7748 if CollideLevel(0, -y) then
7749 begin
7750 c := True;
7751 Break;
7752 end;
7754 if c then Exit;
7756 if y < yy then Exit;
7758 Result := True;
7759 end;
7761 function IsSafeTrigger(): Boolean;
7763 a: Integer;
7764 begin
7765 Result := True;
7766 for a := 0 to High(gTriggers) do
7767 if Collide(gTriggers[a].X, gTriggers[a].Y, gTriggers[a].Width, gTriggers[a].Height)
7768 and (gTriggers[a].TriggerType in [TRIGGER_EXIT, TRIGGER_CLOSEDOOR, TRIGGER_CLOSETRAP,
7769 TRIGGER_TRAP, TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF,
7770 TRIGGER_SPAWNMONSTER, TRIGGER_DAMAGE, TRIGGER_SHOT]) then Exit(False);
7771 end;
7773 begin
7774 // Âîçìîæíî, íàæèìàåì êíîïêó:
7775 if Rnd(16) and IsSafeTrigger() then
7776 PressKey(KEY_OPEN);
7778 // Åñëè ïîä ëèôòîì èëè ñòóïåíüêàìè, òî, âîçìîæíî, ïðûãàåì:
7779 if OnLadder() or ((BelowLadder() or BelowLiftUp()) and Rnd(8)) then
7780 begin
7781 ReleaseKey(KEY_LEFT);
7782 ReleaseKey(KEY_RIGHT);
7783 Jump();
7784 end;
7786 // Èäåì âëåâî, åñëè íàäî áûëî:
7787 if GetAIFlag('GOLEFT') <> '' then
7788 begin
7789 RemoveAIFlag('GOLEFT');
7790 if CanRunLeft() then
7791 GoLeft(360);
7792 end;
7794 // Èäåì âïðàâî, åñëè íàäî áûëî:
7795 if GetAIFlag('GORIGHT') <> '' then
7796 begin
7797 RemoveAIFlag('GORIGHT');
7798 if CanRunRight() then
7799 GoRight(360);
7800 end;
7802 // Åñëè âûëåòåëè çà êàðòó, òî ïðîáóåì âåðíóòüñÿ:
7803 if FObj.X < -32 then
7804 GoRight(360)
7805 else
7806 if FObj.X+32 > gMapInfo.Width then
7807 GoLeft(360);
7809 // Ïðûãàåì, åñëè íàäî áûëî:
7810 if GetAIFlag('NEEDJUMP') <> '' then
7811 begin
7812 Jump(0);
7813 RemoveAIFlag('NEEDJUMP');
7814 end;
7816 // Ñìîòðèì ââåðõ, åñëè íàäî áûëî:
7817 if GetAIFlag('NEEDSEEUP') <> '' then
7818 begin
7819 ReleaseKey(KEY_UP);
7820 ReleaseKey(KEY_DOWN);
7821 PressKey(KEY_UP, 20);
7822 RemoveAIFlag('NEEDSEEUP');
7823 end;
7825 // Ñìîòðèì âíèç, åñëè íàäî áûëî:
7826 if GetAIFlag('NEEDSEEDOWN') <> '' then
7827 begin
7828 ReleaseKey(KEY_UP);
7829 ReleaseKey(KEY_DOWN);
7830 PressKey(KEY_DOWN, 20);
7831 RemoveAIFlag('NEEDSEEDOWN');
7832 end;
7834 // Åñëè íóæíî áûëî â äûðó è ìû íå íà çåìëå, òî ïîêîðíî ëåòèì:
7835 if GetAIFlag('GOINHOLE') <> '' then
7836 if not OnGround() then
7837 begin
7838 ReleaseKey(KEY_LEFT);
7839 ReleaseKey(KEY_RIGHT);
7840 RemoveAIFlag('GOINHOLE');
7841 SetAIFlag('FALLINHOLE', '1');
7842 end;
7844 // Åñëè ïàäàëè è äîñòèãëè çåìëè, òî õâàòèò ïàäàòü:
7845 if GetAIFlag('FALLINHOLE') <> '' then
7846 if OnGround() then
7847 RemoveAIFlag('FALLINHOLE');
7849 // Åñëè ëåòåëè ïðÿìî è ñåé÷àñ íå íà ëåñòíèöå èëè íà âåðøèíå ëèôòà, òî îòõîäèì â ñòîðîíó:
7850 if not (KeyPressed(KEY_LEFT) or KeyPressed(KEY_RIGHT)) then
7851 if GetAIFlag('FALLINHOLE') = '' then
7852 if (not OnLadder()) or (FObj.Vel.Y >= 0) or (OnTopLift()) then
7853 if Rnd(2) then
7854 GoLeft(360)
7855 else
7856 GoRight(360);
7858 // Åñëè íà çåìëå è ìîæíî ïîäïðûãíóòü, òî, âîçìîæíî, ïðûãàåì:
7859 if OnGround() and
7860 CanJumpUp(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*32) and
7861 Rnd(8) then
7862 Jump();
7864 // Åñëè íà çåìëå è âîçëå äûðû (ãëóáèíà > 2 ðîñòîâ èãðîêà):
7865 if OnGround() and NearHole() then
7866 if NearDeepHole() then // Åñëè ýòî áåçäíà
7867 case Random(6) of
7868 0..3: Turn(); // Áåæèì îáðàòíî
7869 4: Jump(); // Ïðûãàåì
7870 5: begin // Ïðûãàåì îáðàòíî
7871 Turn();
7872 Jump();
7873 end;
7875 else // Ýòî íå áåçäíà è ìû åùå íå ëåòèì òóäà
7876 if GetAIFlag('GOINHOLE') = '' then
7877 case Random(6) of
7878 0: Turn(); // Íå íóæíî òóäà
7879 1: Jump(); // Âäðóã ïîâåçåò - ïðûãàåì
7880 else // Åñëè ÿìà ñ ãðàíèöåé, òî ïðè ñëó÷àå ìîæíî òóäà ïðûãíóòü
7881 if BorderHole() then
7882 SetAIFlag('GOINHOLE', '1');
7883 end;
7885 // Åñëè íà çåìëå, íî íåêóäà èäòè:
7886 if (not CanRun()) and OnGround() then
7887 begin
7888 // Åñëè ìû íà ëåñòíèöå èëè ìîæíî ïåðåïðûãíóòü, òî ïðûãàåì:
7889 if CanJumpOver() or OnLadder() then
7890 Jump()
7891 else // èíà÷å ïîïûòàåìñÿ â äðóãóþ ñòîðîíó
7892 if Random(2) = 0 then
7893 begin
7894 if IsSafeTrigger() then
7895 PressKey(KEY_OPEN);
7896 end else
7897 Turn();
7898 end;
7900 // Îñòàëîñü ìàëî âîçäóõà:
7901 if FAir < 36 * 2 then
7902 Jump(20);
7904 // Âûáèðàåìñÿ èç êèñëîòû, åñëè íåò êîñòþìà, îáîæãëèñü, èëè ìàëî çäîðîâüÿ:
7905 if (FPowerups[MR_SUIT] < gTime) and ((FLastHit = HIT_ACID) or (Healthy() <= 1)) then
7906 if BodyInAcid(0, 0) then
7907 Jump();
7908 end;
7910 function TBot.FullInStep(XInc, YInc: Integer): Boolean;
7911 begin
7912 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
7913 PLAYER_RECT.Width, PLAYER_RECT.Height, PANEL_STEP, False);
7914 end;
7916 {function TBot.NeedItem(Item: Byte): Byte;
7917 begin
7918 Result := 4;
7919 end;}
7921 procedure TBot.SelectWeapon(Dist: Integer);
7923 a: Integer;
7925 function HaveAmmo(weapon: Byte): Boolean;
7926 begin
7927 case weapon of
7928 WEAPON_PISTOL: Result := FAmmo[A_BULLETS] >= 1;
7929 WEAPON_SHOTGUN1: Result := FAmmo[A_SHELLS] >= 1;
7930 WEAPON_SHOTGUN2: Result := FAmmo[A_SHELLS] >= 2;
7931 WEAPON_CHAINGUN: Result := FAmmo[A_BULLETS] >= 10;
7932 WEAPON_ROCKETLAUNCHER: Result := FAmmo[A_ROCKETS] >= 1;
7933 WEAPON_PLASMA: Result := FAmmo[A_CELLS] >= 10;
7934 WEAPON_BFG: Result := FAmmo[A_CELLS] >= 40;
7935 WEAPON_SUPERCHAINGUN: Result := FAmmo[A_SHELLS] >= 1;
7936 WEAPON_FLAMETHROWER: Result := FAmmo[A_FUEL] >= 1;
7937 else Result := True;
7938 end;
7939 end;
7941 begin
7942 if Dist = -1 then Dist := BOT_LONGDIST;
7944 if Dist > BOT_LONGDIST then
7945 begin // Äàëüíèé áîé
7946 for a := 0 to 9 do
7947 if FWeapon[FDifficult.WeaponPrior[a]] and HaveAmmo(FDifficult.WeaponPrior[a]) then
7948 begin
7949 FSelectedWeapon := FDifficult.WeaponPrior[a];
7950 Break;
7951 end;
7953 else //if Dist > BOT_UNSAFEDIST then
7954 begin // Áëèæíèé áîé
7955 for a := 0 to 9 do
7956 if FWeapon[FDifficult.CloseWeaponPrior[a]] and HaveAmmo(FDifficult.CloseWeaponPrior[a]) then
7957 begin
7958 FSelectedWeapon := FDifficult.CloseWeaponPrior[a];
7959 Break;
7960 end;
7961 end;
7962 { else
7963 begin
7964 for a := 0 to 9 do
7965 if FWeapon[FDifficult.SafeWeaponPrior[a]] and HaveAmmo(FDifficult.SafeWeaponPrior[a]) then
7966 begin
7967 FSelectedWeapon := FDifficult.SafeWeaponPrior[a];
7968 Break;
7969 end;
7970 end;}
7971 end;
7973 function TBot.PickItem(ItemType: Byte; force: Boolean; var remove: Boolean): Boolean;
7974 begin
7975 Result := inherited PickItem(ItemType, force, remove);
7977 if Result then SetAIFlag('SELECTWEAPON', '1');
7978 end;
7980 function TBot.Heal(value: Word; Soft: Boolean): Boolean;
7981 begin
7982 Result := inherited Heal(value, Soft);
7983 end;
7985 function TBot.Healthy(): Byte;
7986 begin
7987 if FPowerups[MR_INVUL] >= gTime then Result := 3
7988 else if (FHealth > 80) or ((FHealth > 50) and (FArmor > 20)) then Result := 3
7989 else if (FHealth > 50) then Result := 2
7990 else if (FHealth > 20) then Result := 1
7991 else Result := 0;
7992 end;
7994 function TBot.TargetOnScreen(TX, TY: Integer): Boolean;
7995 begin
7996 Result := (Abs(FObj.X-TX) <= Trunc(gPlayerScreenSize.X*0.6)) and
7997 (Abs(FObj.Y-TY) <= Trunc(gPlayerScreenSize.Y*0.6));
7998 end;
8000 procedure TBot.OnDamage(Angle: SmallInt);
8002 pla: TPlayer;
8003 mon: TMonster;
8004 ok: Boolean;
8005 begin
8006 inherited;
8008 if (Angle = 0) or (Angle = 180) then
8009 begin
8010 ok := False;
8011 if (g_GetUIDType(FLastSpawnerUID) = UID_PLAYER) and
8012 (TGameOption.BOTS_VS_PLAYERS in gGameSettings.Options) then
8013 begin // Èãðîê
8014 pla := g_Player_Get(FLastSpawnerUID);
8015 ok := not TargetOnScreen(pla.FObj.X + PLAYER_RECT.X,
8016 pla.FObj.Y + PLAYER_RECT.Y);
8018 else
8019 if (g_GetUIDType(FLastSpawnerUID) = UID_MONSTER) and
8020 (TGameOption.BOTS_VS_MONSTERS in gGameSettings.Options) then
8021 begin // Ìîíñòð
8022 mon := g_Monsters_ByUID(FLastSpawnerUID);
8023 ok := not TargetOnScreen(mon.Obj.X + mon.Obj.Rect.X,
8024 mon.Obj.Y + mon.Obj.Rect.Y);
8025 end;
8027 if ok then
8028 if Angle = 0 then
8029 SetAIFlag('ATTACKLEFT', '1')
8030 else
8031 SetAIFlag('ATTACKRIGHT', '1');
8032 end;
8033 end;
8035 function TBot.RunDirection(): TDirection;
8036 begin
8037 if Abs(Vel.X) >= 1 then
8038 begin
8039 if Vel.X > 0 then Result := TDirection.D_RIGHT else Result := TDirection.D_LEFT;
8040 end else
8041 Result := FDirection;
8042 end;
8044 function TBot.GetRnd(a: Byte): Boolean;
8045 begin
8046 if a = 0 then Result := False
8047 else if a = 255 then Result := True
8048 else Result := Random(256) > 255-a;
8049 end;
8051 function TBot.GetInterval(a: Byte; radius: SmallInt): SmallInt;
8052 begin
8053 Result := Round((255-a)/255*radius*(Random(2)-1));
8054 end;
8057 procedure TDifficult.save (st: TStream);
8058 begin
8059 st.WriteByte(DiagFire);
8060 st.WriteByte(InvisFire);
8061 st.WriteByte(DiagPrecision);
8062 st.WriteByte(FlyPrecision);
8063 st.WriteByte(Cover);
8064 st.WriteByte(CloseJump);
8065 st.WriteBuffer(WeaponPrior[Low(WeaponPrior)], SizeOf(WeaponPrior));
8066 st.WriteBuffer(CloseWeaponPrior[Low(CloseWeaponPrior)], SizeOf(CloseWeaponPrior));
8067 end;
8069 procedure TDifficult.load (st: TStream);
8070 begin
8071 DiagFire := st.ReadByte();
8072 InvisFire := st.ReadByte();
8073 DiagPrecision := st.ReadByte();
8074 FlyPrecision := st.ReadByte();
8075 Cover := st.ReadByte();
8076 CloseJump := st.ReadByte();
8077 st.ReadBuffer(WeaponPrior[Low(WeaponPrior)], SizeOf(WeaponPrior));
8078 st.ReadBuffer(CloseWeaponPrior[Low(CloseWeaponPrior)], SizeOf(CloseWeaponPrior));
8079 end;
8082 procedure TBot.SaveState (st: TStream);
8084 dw: SizeUInt;
8085 i: SizeInt;
8086 begin
8087 inherited SaveState(st);
8088 utils.writeSign(st, 'BOT0');
8090 st.WriteByte(FSelectedWeapon); // Âûáðàííîå îðóæèå
8091 st.WriteWordLE(FTargetUID); // UID öåëè
8092 st.WriteDWordLE(FLastVisible); // Âðåìÿ ïîòåðè öåëè
8094 // Êîëè÷åñòâî ôëàãîâ ÈÈ
8095 dw := Length(FAIFlags);
8096 st.WriteDWordLE(dw);
8098 // Ôëàãè ÈÈ
8099 for i := 0 to dw-1 do
8100 begin
8101 utils.writeStr(st, FAIFlags[i].Name, 20);
8102 utils.writeStr(st, FAIFlags[i].Value, 20);
8103 end;
8105 // Íàñòðîéêè ñëîæíîñòè
8106 FDifficult.save(st);
8107 end;
8110 procedure TBot.LoadState (st: TStream);
8112 dw: SizeUInt;
8113 i: SizeInt;
8114 begin
8115 inherited LoadState(st);
8116 if not utils.checkSign(st, 'BOT0') then
8117 Raise XStreamError.Create('invalid bot signature');
8119 FSelectedWeapon := st.ReadByte(); // Âûáðàííîå îðóæèå
8120 FTargetUID := st.ReadWordLE(); // UID öåëè
8121 FLastVisible := st.ReadDWordLE(); // Âðåìÿ ïîòåðè öåëè
8123 // Êîëè÷åñòâî ôëàãîâ ÈÈ
8124 dw := st.ReadDWordLE();
8125 if dw > 16384 then
8126 Raise XStreamError.Create('invalid number of bot AI flags');
8127 SetLength(FAIFlags, dw);
8129 // Ôëàãè ÈÈ
8130 for i := 0 to dw-1 do
8131 begin
8132 FAIFlags[i].Name := utils.readStr(st, 20);
8133 FAIFlags[i].Value := utils.readStr(st, 20);
8134 end;
8136 // Íàñòðîéêè ñëîæíîñòè
8137 FDifficult.load(st);
8138 end;
8141 begin
8142 conRegVar('player_indicator', @gPlayerIndicator, 'Draw indicator only for current player, also for teammates, or not at all', 'Draw indicator only for current player, also for teammates, or not at all');
8143 conRegVar('player_indicator_style', @gPlayerIndicatorStyle, 'Visual appearance of indicator', 'Visual appearance of indicator');
8144 end.