DEADSOFTWARE

85b8e1537ad9024686d09be58f5ad61914968249
[d2df-sdl.git] / src / game / g_player.pas
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
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.
6 *
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.
11 *
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/>.
14 *)
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 e_graphics, g_playermodel, g_basic, g_textures,
25 g_weapons, g_phys, g_sound, g_saveload, MAPDEF,
26 g_panel;
28 const
29 KEY_LEFT = 1;
30 KEY_RIGHT = 2;
31 KEY_UP = 3;
32 KEY_DOWN = 4;
33 KEY_FIRE = 5;
34 KEY_NEXTWEAPON = 6;
35 KEY_PREVWEAPON = 7;
36 KEY_OPEN = 8;
37 KEY_JUMP = 9;
38 KEY_CHAT = 10;
40 R_ITEM_BACKPACK = 0;
41 R_KEY_RED = 1;
42 R_KEY_GREEN = 2;
43 R_KEY_BLUE = 3;
44 R_BERSERK = 4;
46 MR_SUIT = 0;
47 MR_INVUL = 1;
48 MR_INVIS = 2;
49 MR_MAX = 2;
51 A_BULLETS = 0;
52 A_SHELLS = 1;
53 A_ROCKETS = 2;
54 A_CELLS = 3;
55 A_FUEL = 4;
56 A_HIGH = 4;
58 AmmoLimits: Array [0..1] of Array [A_BULLETS..A_HIGH] of Word =
59 ((200, 50, 50, 300, 100),
60 (400, 100, 100, 600, 200));
62 K_SIMPLEKILL = 0;
63 K_HARDKILL = 1;
64 K_EXTRAHARDKILL = 2;
65 K_FALLKILL = 3;
67 T_RESPAWN = 0;
68 T_SWITCH = 1;
69 T_USE = 2;
70 T_FLAGCAP = 3;
72 TEAM_NONE = 0;
73 TEAM_RED = 1;
74 TEAM_BLUE = 2;
75 TEAM_COOP = 3;
77 SHELL_BULLET = 0;
78 SHELL_SHELL = 1;
79 SHELL_DBLSHELL = 2;
81 ANGLE_NONE = Low(SmallInt);
83 CORPSE_STATE_REMOVEME = 0;
84 CORPSE_STATE_NORMAL = 1;
85 CORPSE_STATE_MESS = 2;
87 PLAYER_RECT: TRectWH = (X:15; Y:12; Width:34; Height:52);
88 PLAYER_RECT_CX = 15+(34 div 2);
89 PLAYER_RECT_CY = 12+(52 div 2);
90 PLAYER_CORPSERECT: TRectWH = (X:15; Y:48; Width:34; Height:16);
92 PLAYER_HP_SOFT = 100;
93 PLAYER_HP_LIMIT = 200;
94 PLAYER_AP_SOFT = 100;
95 PLAYER_AP_LIMIT = 200;
96 SUICIDE_DAMAGE = 112;
97 WEAPON_DELAY = 5;
99 PLAYER_BURN_TIME = 110;
101 PLAYER1_DEF_COLOR: TRGB = (R:64; G:175; B:48);
102 PLAYER2_DEF_COLOR: TRGB = (R:96; G:96; B:96);
104 type
105 TPlayerStat = record
106 Num: Integer;
107 Ping: Word;
108 Loss: Byte;
109 Name: String;
110 Team: Byte;
111 Frags: SmallInt;
112 Deaths: SmallInt;
113 Lives: Byte;
114 Kills: Word;
115 Color: TRGB;
116 Spectator: Boolean;
117 end;
119 TPlayerStatArray = Array of TPlayerStat;
121 TPlayerSavedState = record
122 Health: Integer;
123 Armor: Integer;
124 Air: Integer;
125 JetFuel: Integer;
126 CurrWeap: Byte;
127 NextWeap: WORD;
128 NextWeapDelay: Byte;
129 Ammo: Array [A_BULLETS..A_HIGH] of Word;
130 MaxAmmo: Array [A_BULLETS..A_HIGH] of Word;
131 Weapon: Array [WP_FIRST..WP_LAST] of Boolean;
132 Rulez: Set of R_ITEM_BACKPACK..R_BERSERK;
133 Used: Boolean;
134 end;
136 TKeyState = record
137 Pressed: Boolean;
138 Time: Word;
139 end;
141 TPlayer = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
142 private
143 FIamBot: Boolean;
144 FUID: Word;
145 FName: String;
146 FTeam: Byte;
147 FAlive: Boolean;
148 FSpawned: Boolean;
149 FDirection: TDirection;
150 FHealth: Integer;
151 FLives: Byte;
152 FArmor: Integer;
153 FAir: Integer;
154 FPain: Integer;
155 FPickup: Integer;
156 FKills: Integer;
157 FMonsterKills: Integer;
158 FFrags: Integer;
159 FFragCombo: Byte;
160 FLastFrag: LongWord;
161 FComboEvnt: Integer;
162 FDeath: Integer;
163 FCanJetpack: Boolean;
164 FJetFuel: Integer;
165 FFlag: Byte;
166 FSecrets: Integer;
167 FCurrWeap: Byte;
168 FNextWeap: WORD;
169 FNextWeapDelay: Byte; // frames
170 FBFGFireCounter: SmallInt;
171 FLastSpawnerUID: Word;
172 FLastHit: Byte;
173 FObj: TObj;
174 FXTo, FYTo: Integer;
175 FSpectatePlayer: Integer;
176 FFirePainTime: Integer;
177 FFireAttacker: Word;
179 FSavedStateNum: Integer;
181 FModel: TPlayerModel;
182 FPunchAnim: TAnimation;
183 FActionPrior: Byte;
184 FActionAnim: Byte;
185 FActionForce: Boolean;
186 FActionChanged: Boolean;
187 FAngle: SmallInt;
188 FFireAngle: SmallInt;
189 FIncCamOld: Integer;
190 FIncCam: Integer;
191 FSlopeOld: Integer;
192 FShellTimer: Integer;
193 FShellType: Byte;
194 FSawSound: TPlayableSound;
195 FSawSoundIdle: TPlayableSound;
196 FSawSoundHit: TPlayableSound;
197 FSawSoundSelect: TPlayableSound;
198 FFlameSoundOn: TPlayableSound;
199 FFlameSoundOff: TPlayableSound;
200 FFlameSoundWork: TPlayableSound;
201 FJetSoundOn: TPlayableSound;
202 FJetSoundOff: TPlayableSound;
203 FJetSoundFly: TPlayableSound;
204 FGodMode: Boolean;
205 FNoTarget: Boolean;
206 FNoReload: Boolean;
207 FJustTeleported: Boolean;
208 FNetTime: LongWord;
209 mEDamageType: Integer;
212 function CollideLevel(XInc, YInc: Integer): Boolean;
213 function StayOnStep(XInc, YInc: Integer): Boolean;
214 function HeadInLiquid(XInc, YInc: Integer): Boolean;
215 function BodyInLiquid(XInc, YInc: Integer): Boolean;
216 function BodyInAcid(XInc, YInc: Integer): Boolean;
217 function FullInLift(XInc, YInc: Integer): Integer;
218 {procedure CollideItem();}
219 procedure FlySmoke(Times: DWORD = 1);
220 procedure OnFireFlame(Times: DWORD = 1);
221 function GetAmmoByWeapon(Weapon: Byte): Word;
222 procedure SetAction(Action: Byte; Force: Boolean = False);
223 procedure OnDamage(Angle: SmallInt); virtual;
224 function firediry(): Integer;
225 procedure DoPunch();
227 procedure Run(Direction: TDirection);
228 procedure NextWeapon();
229 procedure PrevWeapon();
230 procedure SeeUp();
231 procedure SeeDown();
232 procedure Fire();
233 procedure Jump();
234 procedure Use();
236 function getNextWeaponIndex (): Byte; // return 255 for "no switch"
237 procedure resetWeaponQueue ();
238 function hasAmmoForWeapon (weapon: Byte): Boolean;
240 procedure doDamage (v: Integer);
242 function refreshCorpse(): Boolean;
244 public
245 FDamageBuffer: Integer;
247 FAmmo: Array [A_BULLETS..A_HIGH] of Word;
248 FMaxAmmo: Array [A_BULLETS..A_HIGH] of Word;
249 FWeapon: Array [WP_FIRST..WP_LAST] of Boolean;
250 FRulez: Set of R_ITEM_BACKPACK..R_BERSERK;
251 FBerserk: Integer;
252 FMegaRulez: Array [MR_SUIT..MR_MAX] of DWORD;
253 FReloading: Array [WP_FIRST..WP_LAST] of Word;
254 FTime: Array [T_RESPAWN..T_FLAGCAP] of DWORD;
255 FKeys: Array [KEY_LEFT..KEY_CHAT] of TKeyState;
256 FColor: TRGB;
257 FPreferredTeam: Byte;
258 FSpectator: Boolean;
259 FNoRespawn: Boolean;
260 FWantsInGame: Boolean;
261 FGhost: Boolean;
262 FPhysics: Boolean;
263 FFlaming: Boolean;
264 FJetpack: Boolean;
265 FActualModelName: string;
266 FClientID: SmallInt;
267 FPing: Word;
268 FLoss: Byte;
269 FReady: Boolean;
270 FDummy: Boolean;
271 FFireTime: Integer;
272 FSpawnInvul: Integer;
273 FHandicap: Integer;
274 FWaitForFirstSpawn: Boolean; // set to `true` in server, used to spawn a player on first full state request
275 FCorpse: Integer;
277 // debug: viewport offset
278 viewPortX, viewPortY, viewPortW, viewPortH: Integer;
280 function isValidViewPort (): Boolean; inline;
282 constructor Create(); virtual;
283 destructor Destroy(); override;
284 procedure Respawn(Silent: Boolean; Force: Boolean = False); virtual;
285 function GetRespawnPoint(): Byte;
286 procedure PressKey(Key: Byte; Time: Word = 1);
287 procedure ReleaseKeys();
288 procedure SetModel(ModelName: String);
289 procedure SetColor(Color: TRGB);
290 function GetColor(): TRGB;
291 procedure SetWeapon(W: Byte);
292 function IsKeyPressed(K: Byte): Boolean;
293 function GetKeys(): Byte;
294 function PickItem(ItemType: Byte; arespawn: Boolean; var remove: Boolean): Boolean; virtual;
295 function Collide(X, Y: Integer; Width, Height: Word): Boolean; overload;
296 function Collide(Panel: TPanel): Boolean; overload;
297 function Collide(X, Y: Integer): Boolean; overload;
298 procedure SetDirection(Direction: TDirection);
299 procedure GetSecret();
300 function TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean;
301 procedure Touch();
302 procedure Push(vx, vy: Integer);
303 procedure ChangeModel(ModelName: String);
304 procedure SwitchTeam;
305 procedure ChangeTeam(Team: Byte);
306 procedure BFGHit();
307 function GetFlag(Flag: Byte): Boolean;
308 procedure SetFlag(Flag: Byte);
309 function DropFlag(Silent: Boolean = True): Boolean;
310 procedure AllRulez(Health: Boolean);
311 procedure RestoreHealthArmor();
312 procedure FragCombo();
313 procedure GiveItem(ItemType: Byte);
314 procedure Damage(value: Word; SpawnerUID: Word; vx, vy: Integer; t: Byte); virtual;
315 function Heal(value: Word; Soft: Boolean): Boolean; virtual;
316 procedure MakeBloodVector(Count: Word; VelX, VelY: Integer);
317 procedure MakeBloodSimple(Count: Word);
318 procedure Kill(KillType: Byte; SpawnerUID: Word; t: Byte);
319 procedure Reset(Force: Boolean);
320 procedure Spectate(NoMove: Boolean = False);
321 procedure SwitchNoClip;
322 procedure SoftReset();
323 procedure Draw(); virtual;
324 procedure DrawPain();
325 procedure DrawPickup();
326 procedure DrawRulez();
327 procedure DrawAim();
328 procedure DrawIndicator(Color: TRGB);
329 procedure DrawBubble();
330 procedure DrawGUI();
331 procedure PreUpdate();
332 procedure Update(); virtual;
333 procedure RememberState();
334 procedure RecallState();
335 procedure SaveState (st: TStream); virtual;
336 procedure LoadState (st: TStream); virtual;
337 procedure PauseSounds(Enable: Boolean);
338 procedure NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1);
339 procedure DoLerp(Level: Integer = 2);
340 procedure SetLerp(XTo, YTo: Integer);
341 procedure QueueWeaponSwitch(Weapon: Byte);
342 procedure RealizeCurrentWeapon();
343 procedure FlamerOn;
344 procedure FlamerOff;
345 procedure JetpackOn;
346 procedure JetpackOff;
347 procedure CatchFire(Attacker: Word; Timeout: Integer = PLAYER_BURN_TIME);
349 //WARNING! this does nothing for now, but still call it!
350 procedure positionChanged (); //WARNING! call this after entity position was changed, or coldet will not work right!
352 procedure getMapBox (out x, y, w, h: Integer); inline;
353 procedure moveBy (dx, dy: Integer); inline;
355 function getCameraObj(): TObj;
357 public
358 property Vel: TPoint2i read FObj.Vel;
359 property Obj: TObj read FObj;
361 property Name: String read FName write FName;
362 property Model: TPlayerModel read FModel;
363 property Health: Integer read FHealth write FHealth;
364 property Lives: Byte read FLives write FLives;
365 property Armor: Integer read FArmor write FArmor;
366 property Air: Integer read FAir write FAir;
367 property JetFuel: Integer read FJetFuel write FJetFuel;
368 property Frags: Integer read FFrags write FFrags;
369 property Death: Integer read FDeath write FDeath;
370 property Kills: Integer read FKills write FKills;
371 property CurrWeap: Byte read FCurrWeap write FCurrWeap;
372 property MonsterKills: Integer read FMonsterKills write FMonsterKills;
373 property Secrets: Integer read FSecrets;
374 property GodMode: Boolean read FGodMode write FGodMode;
375 property NoTarget: Boolean read FNoTarget write FNoTarget;
376 property NoReload: Boolean read FNoReload write FNoReload;
377 property alive: Boolean read FAlive write FAlive;
378 property Flag: Byte read FFlag;
379 property Team: Byte read FTeam write FTeam;
380 property Direction: TDirection read FDirection;
381 property GameX: Integer read FObj.X write FObj.X;
382 property GameY: Integer read FObj.Y write FObj.Y;
383 property GameVelX: Integer read FObj.Vel.X write FObj.Vel.X;
384 property GameVelY: Integer read FObj.Vel.Y write FObj.Vel.Y;
385 property GameAccelX: Integer read FObj.Accel.X write FObj.Accel.X;
386 property GameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y;
387 property IncCam: Integer read FIncCam write FIncCam;
388 property IncCamOld: Integer read FIncCamOld write FIncCamOld;
389 property SlopeOld: Integer read FSlopeOld write FSlopeOld;
390 property UID: Word read FUID write FUID;
391 property JustTeleported: Boolean read FJustTeleported write FJustTeleported;
392 property NetTime: LongWord read FNetTime write FNetTime;
394 published
395 property eName: String read FName write FName;
396 property eHealth: Integer read FHealth write FHealth;
397 property eLives: Byte read FLives write FLives;
398 property eArmor: Integer read FArmor write FArmor;
399 property eAir: Integer read FAir write FAir;
400 property eJetFuel: Integer read FJetFuel write FJetFuel;
401 property eFrags: Integer read FFrags write FFrags;
402 property eDeath: Integer read FDeath write FDeath;
403 property eKills: Integer read FKills write FKills;
404 property eCurrWeap: Byte read FCurrWeap write FCurrWeap;
405 property eMonsterKills: Integer read FMonsterKills write FMonsterKills;
406 property eSecrets: Integer read FSecrets write FSecrets;
407 property eGodMode: Boolean read FGodMode write FGodMode;
408 property eNoTarget: Boolean read FNoTarget write FNoTarget;
409 property eNoReload: Boolean read FNoReload write FNoReload;
410 property eAlive: Boolean read FAlive write FAlive;
411 property eFlag: Byte read FFlag;
412 property eTeam: Byte read FTeam write FTeam;
413 property eDirection: TDirection read FDirection;
414 property eGameX: Integer read FObj.X write FObj.X;
415 property eGameY: Integer read FObj.Y write FObj.Y;
416 property eGameVelX: Integer read FObj.Vel.X write FObj.Vel.X;
417 property eGameVelY: Integer read FObj.Vel.Y write FObj.Vel.Y;
418 property eGameAccelX: Integer read FObj.Accel.X write FObj.Accel.X;
419 property eGameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y;
420 property eIncCam: Integer read FIncCam write FIncCam;
421 property eUID: Word read FUID;
422 property eJustTeleported: Boolean read FJustTeleported;
423 property eNetTime: LongWord read FNetTime;
425 // set this before assigning something to `eDamage`
426 property eDamageType: Integer read mEDamageType write mEDamageType;
427 property eDamage: Integer write doDamage;
428 end;
430 TDifficult = record
431 public
432 DiagFire: Byte;
433 InvisFire: Byte;
434 DiagPrecision: Byte;
435 FlyPrecision: Byte;
436 Cover: Byte;
437 CloseJump: Byte;
438 WeaponPrior: packed array [WP_FIRST..WP_LAST] of Byte;
439 CloseWeaponPrior: packed array [WP_FIRST..WP_LAST] of Byte;
440 //SafeWeaponPrior: Array [WP_FIRST..WP_LAST] of Byte;
442 public
443 procedure save (st: TStream);
444 procedure load (st: TStream);
445 end;
447 TAIFlag = record
448 Name: String;
449 Value: String;
450 end;
452 TBot = class(TPlayer)
453 private
454 FSelectedWeapon: Byte;
455 FTargetUID: Word;
456 FLastVisible: DWORD;
457 FAIFlags: Array of TAIFlag;
458 FDifficult: TDifficult;
460 function GetRnd(a: Byte): Boolean;
461 function GetInterval(a: Byte; radius: SmallInt): SmallInt;
462 function RunDirection(): TDirection;
463 function FullInStep(XInc, YInc: Integer): Boolean;
464 //function NeedItem(Item: Byte): Byte;
465 procedure SelectWeapon(Dist: Integer);
466 procedure SetAIFlag(aName, fValue: String20);
467 function GetAIFlag(aName: String20): String20;
468 procedure RemoveAIFlag(aName: String20);
469 function Healthy(): Byte;
470 procedure UpdateMove();
471 procedure UpdateCombat();
472 function KeyPressed(Key: Word): Boolean;
473 procedure ReleaseKey(Key: Byte);
474 function TargetOnScreen(TX, TY: Integer): Boolean;
475 procedure OnDamage(Angle: SmallInt); override;
477 public
478 procedure Respawn(Silent: Boolean; Force: Boolean = False); override;
479 constructor Create(); override;
480 destructor Destroy(); override;
481 procedure Draw(); override;
482 function PickItem(ItemType: Byte; force: Boolean; var remove: Boolean): Boolean; override;
483 function Heal(value: Word; Soft: Boolean): Boolean; override;
484 procedure Update(); override;
485 procedure SaveState (st: TStream); override;
486 procedure LoadState (st: TStream); override;
487 end;
489 PGib = ^TGib;
490 TGib = record
491 alive: Boolean;
492 ID: DWORD;
493 MaskID: DWORD;
494 RAngle: Integer;
495 Color: TRGB;
496 Obj: TObj;
498 procedure getMapBox (out x, y, w, h: Integer); inline;
499 procedure moveBy (dx, dy: Integer); inline;
501 procedure positionChanged (); inline; //WARNING! call this after entity position was changed, or coldet will not work right!
502 end;
505 PShell = ^TShell;
506 TShell = record
507 SpriteID: DWORD;
508 alive: Boolean;
509 SType: Byte;
510 RAngle: Integer;
511 Timeout: Cardinal;
512 CX, CY: Integer;
513 Obj: TObj;
515 procedure getMapBox (out x, y, w, h: Integer); inline;
516 procedure moveBy (dx, dy: Integer); inline;
518 procedure positionChanged (); inline; //WARNING! call this after entity position was changed, or coldet will not work right!
519 end;
521 TCorpse = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
522 private
523 FModelName: String;
524 FMess: Boolean;
525 FState: Byte;
526 FDamage: Byte;
527 FColor: TRGB;
528 FObj: TObj;
529 FPlayerUID: Word;
530 FAnimation: TAnimation;
531 FAnimationMask: TAnimation;
533 public
534 constructor Create(X, Y: Integer; ModelName: String; aMess: Boolean);
535 destructor Destroy(); override;
536 procedure Damage(Value: Word; vx, vy: Integer);
537 procedure Update();
538 procedure Draw();
539 procedure SaveState (st: TStream);
540 procedure LoadState (st: TStream);
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!
547 function ObjPtr (): PObj; inline;
549 property Obj: TObj read FObj; // copies object
550 property State: Byte read FState;
551 property Mess: Boolean read FMess;
552 end;
554 TTeamStat = Array [TEAM_RED..TEAM_BLUE] of
555 record
556 Goals: SmallInt;
557 end;
559 var
560 gPlayers: Array of TPlayer;
561 gCorpses: Array of TCorpse;
562 gGibs: Array of TGib;
563 gShells: Array of TShell;
564 gTeamStat: TTeamStat;
565 gFly: Boolean = False;
566 gAimLine: Boolean = False;
567 gChatBubble: Integer = 0;
568 gPlayerIndicator: Integer = 1;
569 gPlayerIndicatorStyle: Integer = 0;
570 gNumBots: Word = 0;
571 gSpectLatchPID1: Word = 0;
572 gSpectLatchPID2: Word = 0;
573 MAX_RUNVEL: Integer = 8;
574 VEL_JUMP: Integer = 10;
575 SHELL_TIMEOUT: Cardinal = 60000;
577 function Lerp(X, Y, Factor: Integer): Integer;
579 procedure g_Gibs_SetMax(Count: Word);
580 function g_Gibs_GetMax(): Word;
581 procedure g_Corpses_SetMax(Count: Word);
582 function g_Corpses_GetMax(): Word;
583 procedure g_Shells_SetMax(Count: Word);
584 function g_Shells_GetMax(): Word;
586 procedure g_Player_Init();
587 procedure g_Player_Free();
588 function g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boolean): Word;
589 function g_Player_CreateFromState (st: TStream): Word;
590 procedure g_Player_Remove(UID: Word);
591 procedure g_Player_ResetTeams();
592 procedure g_Player_PreUpdate();
593 procedure g_Player_UpdateAll();
594 procedure g_Player_DrawAll();
595 procedure g_Player_DrawDebug(p: TPlayer);
596 procedure g_Player_DrawHealth();
597 procedure g_Player_RememberAll();
598 procedure g_Player_ResetAll(Force, Silent: Boolean);
599 function g_Player_Get(UID: Word): TPlayer;
600 function g_Player_GetCount(): Byte;
601 function g_Player_GetStats(): TPlayerStatArray;
602 function g_Player_ValidName(Name: String): Boolean;
603 function g_Player_CreateCorpse(Player: TPlayer): Integer;
604 procedure g_Player_CreateGibs(fX, fY: Integer; ModelName: String; fColor: TRGB);
605 procedure g_Player_CreateShell(fX, fY, dX, dY: Integer; T: Byte);
606 procedure g_Player_UpdatePhysicalObjects();
607 procedure g_Player_DrawCorpses();
608 procedure g_Player_DrawShells();
609 procedure g_Player_RemoveAllCorpses();
610 procedure g_Player_Corpses_SaveState (st: TStream);
611 procedure g_Player_Corpses_LoadState (st: TStream);
612 procedure g_Player_ResetReady();
613 procedure g_Bot_Add(Team, Difficult: Byte; Handicap: Integer = 100);
614 procedure g_Bot_AddList(Team: Byte; lname: ShortString; num: Integer = -1; Handicap: Integer = 100);
615 procedure g_Bot_MixNames();
616 procedure g_Bot_RemoveAll();
618 implementation
620 uses
621 {$INCLUDE ../nogl/noGLuses.inc}
622 {$IFDEF ENABLE_HOLMES}
623 g_holmes,
624 {$ENDIF}
625 e_log, g_map, g_items, g_console, g_gfx, Math,
626 g_options, g_triggers, g_menu, g_game, g_grid, e_res,
627 wadreader, g_main, g_monsters, CONFIG, g_language,
628 g_net, g_netmsg, g_window,
629 utils, xstreams;
631 const PLR_SAVE_VERSION = 0;
633 type
634 TBotProfile = record
635 name: ShortString;
636 model: ShortString;
637 team: Byte;
638 color: TRGB;
639 diag_fire: Byte;
640 invis_fire: Byte;
641 diag_precision: Byte;
642 fly_precision: Byte;
643 cover: Byte;
644 close_jump: Byte;
645 w_prior1: Array [WP_FIRST..WP_LAST] of Byte;
646 w_prior2: Array [WP_FIRST..WP_LAST] of Byte;
647 w_prior3: Array [WP_FIRST..WP_LAST] of Byte;
648 end;
650 const
651 TIME_RESPAWN1 = 1500;
652 TIME_RESPAWN2 = 2000;
653 TIME_RESPAWN3 = 3000;
654 AIR_DEF = 360;
655 AIR_MAX = 1091;
656 JET_MAX = 540; // ~30 sec
657 PLAYER_SUIT_TIME = 30000;
658 PLAYER_INVUL_TIME = 30000;
659 PLAYER_INVIS_TIME = 35000;
660 FRAG_COMBO_TIME = 3000;
661 VEL_SW = 4;
662 VEL_FLY = 6;
663 ANGLE_RIGHTUP = 55;
664 ANGLE_RIGHTDOWN = -35;
665 ANGLE_LEFTUP = 125;
666 ANGLE_LEFTDOWN = -145;
667 PLAYER_HEADRECT: TRectWH = (X:24; Y:12; Width:20; Height:12);
668 WEAPONPOINT: Array [TDirection] of TDFPoint = ((X:16; Y:32), (X:47; Y:32));
669 BOT_MAXJUMP = 84;
670 BOT_LONGDIST = 300;
671 BOT_UNSAFEDIST = 128;
672 TEAMCOLOR: Array [TEAM_RED..TEAM_BLUE] of TRGB = ((R:255; G:0; B:0),
673 (R:0; G:0; B:255));
674 DIFFICULT_EASY: TDifficult = (DiagFire: 32; InvisFire: 32; DiagPrecision: 32;
675 FlyPrecision: 32; Cover: 32; CloseJump: 32;
676 WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0));
677 DIFFICULT_MEDIUM: TDifficult = (DiagFire: 127; InvisFire: 127; DiagPrecision: 127;
678 FlyPrecision: 127; Cover: 127; CloseJump: 127;
679 WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0));
680 DIFFICULT_HARD: TDifficult = (DiagFire: 255; InvisFire: 255; DiagPrecision: 255;
681 FlyPrecision: 255; Cover: 255; CloseJump: 255;
682 WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0));
683 WEAPON_PRIOR1: Array [WP_FIRST..WP_LAST] of Byte =
684 (WEAPON_FLAMETHROWER, WEAPON_SUPERPULEMET,
685 WEAPON_SHOTGUN2, WEAPON_SHOTGUN1,
686 WEAPON_CHAINGUN, WEAPON_PLASMA, WEAPON_ROCKETLAUNCHER,
687 WEAPON_BFG, WEAPON_PISTOL, WEAPON_SAW, WEAPON_KASTET);
688 WEAPON_PRIOR2: Array [WP_FIRST..WP_LAST] of Byte =
689 (WEAPON_FLAMETHROWER, WEAPON_SUPERPULEMET,
690 WEAPON_BFG, WEAPON_ROCKETLAUNCHER,
691 WEAPON_SHOTGUN2, WEAPON_PLASMA, WEAPON_SHOTGUN1,
692 WEAPON_CHAINGUN, WEAPON_PISTOL, WEAPON_SAW, WEAPON_KASTET);
693 //WEAPON_PRIOR3: Array [WP_FIRST..WP_LAST] of Byte =
694 // (WEAPON_FLAMETHROWER, WEAPON_SUPERPULEMET,
695 // WEAPON_BFG, WEAPON_PLASMA, WEAPON_SHOTGUN2,
696 // WEAPON_CHAINGUN, WEAPON_SHOTGUN1, WEAPON_SAW,
697 // WEAPON_ROCKETLAUNCHER, WEAPON_PISTOL, WEAPON_KASTET);
698 WEAPON_RELOAD: Array [WP_FIRST..WP_LAST] of Byte =
699 (5, 2, 6, 18, 36, 2, 12, 2, 14, 2, 2);
701 PLAYER_SIGNATURE = $52594C50; // 'PLYR'
702 CORPSE_SIGNATURE = $50524F43; // 'CORP'
704 BOTNAMES_FILENAME = 'botnames.txt';
705 BOTLIST_FILENAME = 'botlist.txt';
707 var
708 MaxGibs: Word = 150;
709 MaxCorpses: Word = 20;
710 MaxShells: Word = 300;
711 CurrentGib: Integer = 0;
712 CurrentShell: Integer = 0;
713 BotNames: Array of String;
714 BotList: Array of TBotProfile;
715 SavedStates: Array of TPlayerSavedState;
718 function Lerp(X, Y, Factor: Integer): Integer;
719 begin
720 Result := X + ((Y - X) div Factor);
721 end;
723 function SameTeam(UID1, UID2: Word): Boolean;
724 begin
725 Result := False;
727 if (UID1 > UID_MAX_PLAYER) or (UID1 <= UID_MAX_GAME) or
728 (UID2 > UID_MAX_PLAYER) or (UID2 <= UID_MAX_GAME) then Exit;
730 if (g_Player_Get(UID1) = nil) or (g_Player_Get(UID2) = nil) then Exit;
732 if ((g_Player_Get(UID1).Team = TEAM_NONE) or
733 (g_Player_Get(UID2).Team = TEAM_NONE)) then Exit;
735 Result := g_Player_Get(UID1).FTeam = g_Player_Get(UID2).FTeam;
736 end;
738 procedure g_Gibs_SetMax(Count: Word);
739 begin
740 MaxGibs := Count;
741 SetLength(gGibs, Count);
743 if CurrentGib >= Count then
744 CurrentGib := 0;
745 end;
747 function g_Gibs_GetMax(): Word;
748 begin
749 Result := MaxGibs;
750 end;
752 procedure g_Shells_SetMax(Count: Word);
753 begin
754 MaxShells := Count;
755 SetLength(gShells, Count);
757 if CurrentShell >= Count then
758 CurrentShell := 0;
759 end;
761 function g_Shells_GetMax(): Word;
762 begin
763 Result := MaxShells;
764 end;
767 procedure g_Corpses_SetMax(Count: Word);
768 begin
769 MaxCorpses := Count;
770 SetLength(gCorpses, Count);
771 end;
773 function g_Corpses_GetMax(): Word;
774 begin
775 Result := MaxCorpses;
776 end;
778 function g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boolean): Word;
779 var
780 a: Integer;
781 ok: Boolean;
782 begin
783 Result := 0;
785 ok := False;
786 a := 0;
788 // Åñòü ëè ìåñòî â gPlayers:
789 if gPlayers <> nil then
790 for a := 0 to High(gPlayers) do
791 if gPlayers[a] = nil then
792 begin
793 ok := True;
794 Break;
795 end;
797 // Íåò ìåñòà - ðàñøèðÿåì gPlayers:
798 if not ok then
799 begin
800 SetLength(gPlayers, Length(gPlayers)+1);
801 a := High(gPlayers);
802 end;
804 // Ñîçäàåì îáúåêò èãðîêà:
805 if Bot then
806 gPlayers[a] := TBot.Create()
807 else
808 gPlayers[a] := TPlayer.Create();
811 gPlayers[a].FActualModelName := ModelName;
812 gPlayers[a].SetModel(ModelName);
814 // Íåò ìîäåëè - ñîçäàíèå íå âîçìîæíî:
815 if gPlayers[a].FModel = nil then
816 begin
817 gPlayers[a].Free();
818 gPlayers[a] := nil;
819 g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], [ModelName]));
820 Exit;
821 end;
823 if not (Team in [TEAM_RED, TEAM_BLUE]) then
824 if Random(2) = 0 then
825 Team := TEAM_RED
826 else
827 Team := TEAM_BLUE;
828 gPlayers[a].FPreferredTeam := Team;
830 case gGameSettings.GameMode of
831 GM_DM: gPlayers[a].FTeam := TEAM_NONE;
832 GM_TDM,
833 GM_CTF: gPlayers[a].FTeam := gPlayers[a].FPreferredTeam;
834 GM_SINGLE,
835 GM_COOP: gPlayers[a].FTeam := TEAM_COOP;
836 end;
838 // Åñëè êîìàíäíàÿ èãðà - êðàñèì ìîäåëü â öâåò êîìàíäû:
839 gPlayers[a].FColor := Color;
840 if gPlayers[a].FTeam in [TEAM_RED, TEAM_BLUE] then
841 gPlayers[a].FModel.Color := TEAMCOLOR[gPlayers[a].FTeam]
842 else
843 gPlayers[a].FModel.Color := Color;
845 gPlayers[a].FUID := g_CreateUID(UID_PLAYER);
846 gPlayers[a].FAlive := False;
848 Result := gPlayers[a].FUID;
849 end;
851 function g_Player_CreateFromState (st: TStream): Word;
852 var
853 a, i: Integer;
854 ok, Bot: Boolean;
855 b: Byte;
856 begin
857 result := 0;
858 if (st = nil) then exit; //???
860 // Ñèãíàòóðà èãðîêà
861 if not utils.checkSign(st, 'PLYR') then raise XStreamError.Create('invalid player signature');
862 if (utils.readByte(st) <> PLR_SAVE_VERSION) then raise XStreamError.Create('invalid player version');
864 // Áîò èëè ÷åëîâåê:
865 Bot := utils.readBool(st);
867 ok := false;
868 a := 0;
870 // Åñòü ëè ìåñòî â gPlayers:
871 for a := 0 to High(gPlayers) do if (gPlayers[a] = nil) then begin ok := true; break; end;
873 // Íåò ìåñòà - ðàñøèðÿåì gPlayers
874 if not ok then
875 begin
876 SetLength(gPlayers, Length(gPlayers)+1);
877 a := High(gPlayers);
878 end;
880 // Ñîçäàåì îáúåêò èãðîêà
881 if Bot then
882 gPlayers[a] := TBot.Create()
883 else
884 gPlayers[a] := TPlayer.Create();
885 gPlayers[a].FIamBot := Bot;
886 gPlayers[a].FPhysics := True;
888 // UID èãðîêà
889 gPlayers[a].FUID := utils.readWord(st);
890 // Èìÿ èãðîêà
891 gPlayers[a].FName := utils.readStr(st);
892 // Êîìàíäà
893 gPlayers[a].FTeam := utils.readByte(st);
894 gPlayers[a].FPreferredTeam := gPlayers[a].FTeam;
895 // Æèâ ëè
896 gPlayers[a].FAlive := utils.readBool(st);
897 // Èçðàñõîäîâàë ëè âñå æèçíè
898 gPlayers[a].FNoRespawn := utils.readBool(st);
899 // Íàïðàâëåíèå
900 b := utils.readByte(st);
901 if b = 1 then gPlayers[a].FDirection := TDirection.D_LEFT else gPlayers[a].FDirection := TDirection.D_RIGHT; // b = 2
902 // Çäîðîâüå
903 gPlayers[a].FHealth := utils.readLongInt(st);
904 // Ôîðà
905 gPlayers[a].FHandicap := utils.readLongInt(st);
906 // Æèçíè
907 gPlayers[a].FLives := utils.readByte(st);
908 // Áðîíÿ
909 gPlayers[a].FArmor := utils.readLongInt(st);
910 // Çàïàñ âîçäóõà
911 gPlayers[a].FAir := utils.readLongInt(st);
912 // Çàïàñ ãîðþ÷åãî
913 gPlayers[a].FJetFuel := utils.readLongInt(st);
914 // Áîëü
915 gPlayers[a].FPain := utils.readLongInt(st);
916 // Óáèë
917 gPlayers[a].FKills := utils.readLongInt(st);
918 // Óáèë ìîíñòðîâ
919 gPlayers[a].FMonsterKills := utils.readLongInt(st);
920 // Ôðàãîâ
921 gPlayers[a].FFrags := utils.readLongInt(st);
922 // Ôðàãîâ ïîäðÿä
923 gPlayers[a].FFragCombo := utils.readByte(st);
924 // Âðåìÿ ïîñëåäíåãî ôðàãà
925 gPlayers[a].FLastFrag := utils.readLongWord(st);
926 // Ñìåðòåé
927 gPlayers[a].FDeath := utils.readLongInt(st);
928 // Êàêîé ôëàã íåñåò
929 gPlayers[a].FFlag := utils.readByte(st);
930 // Íàøåë ñåêðåòîâ
931 gPlayers[a].FSecrets := utils.readLongInt(st);
932 // Òåêóùåå îðóæèå
933 gPlayers[a].FCurrWeap := utils.readByte(st);
934 // Ñëåäóþùåå æåëàåìîå îðóæèå
935 gPlayers[a].FNextWeap := utils.readWord(st);
936 // ...è ïàóçà
937 gPlayers[a].FNextWeapDelay := utils.readByte(st);
938 // Âðåìÿ çàðÿäêè BFG
939 gPlayers[a].FBFGFireCounter := utils.readSmallInt(st);
940 // Áóôåð óðîíà
941 gPlayers[a].FDamageBuffer := utils.readLongInt(st);
942 // Ïîñëåäíèé óäàðèâøèé
943 gPlayers[a].FLastSpawnerUID := utils.readWord(st);
944 // Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà
945 gPlayers[a].FLastHit := utils.readByte(st);
946 // Îáúåêò èãðîêà:
947 Obj_LoadState(@gPlayers[a].FObj, st);
948 // Òåêóùåå êîëè÷åñòâî ïàòðîíîâ
949 for i := A_BULLETS to A_HIGH do gPlayers[a].FAmmo[i] := utils.readWord(st);
950 // Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ
951 for i := A_BULLETS to A_HIGH do gPlayers[a].FMaxAmmo[i] := utils.readWord(st);
952 // Íàëè÷èå îðóæèÿ
953 for i := WP_FIRST to WP_LAST do gPlayers[a].FWeapon[i] := utils.readBool(st);
954 // Âðåìÿ ïåðåçàðÿäêè îðóæèÿ
955 for i := WP_FIRST to WP_LAST do gPlayers[a].FReloading[i] := utils.readWord(st);
956 // Íàëè÷èå ðþêçàêà
957 if utils.readBool(st) then Include(gPlayers[a].FRulez, R_ITEM_BACKPACK);
958 // Íàëè÷èå êðàñíîãî êëþ÷à
959 if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_RED);
960 // Íàëè÷èå çåëåíîãî êëþ÷à
961 if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_GREEN);
962 // Íàëè÷èå ñèíåãî êëþ÷à
963 if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_BLUE);
964 // Íàëè÷èå áåðñåðêà
965 if utils.readBool(st) then Include(gPlayers[a].FRulez, R_BERSERK);
966 // Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ
967 for i := MR_SUIT to MR_MAX do gPlayers[a].FMegaRulez[i] := utils.readLongWord(st);
968 // Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà
969 for i := T_RESPAWN to T_FLAGCAP do gPlayers[a].FTime[i] := utils.readLongWord(st);
971 // Íàçâàíèå ìîäåëè:
972 gPlayers[a].FActualModelName := utils.readStr(st);
973 // Öâåò ìîäåëè
974 gPlayers[a].FColor.R := utils.readByte(st);
975 gPlayers[a].FColor.G := utils.readByte(st);
976 gPlayers[a].FColor.B := utils.readByte(st);
977 // Îáíîâëÿåì ìîäåëü èãðîêà
978 gPlayers[a].SetModel(gPlayers[a].FActualModelName);
980 // Íåò ìîäåëè - ñîçäàíèå íåâîçìîæíî
981 if (gPlayers[a].FModel = nil) then
982 begin
983 gPlayers[a].Free();
984 gPlayers[a] := nil;
985 g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], [gPlayers[a].FActualModelName]));
986 exit;
987 end;
989 // Åñëè êîìàíäíàÿ èãðà - êðàñèì ìîäåëü â öâåò êîìàíäû
990 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
991 gPlayers[a].FModel.Color := TEAMCOLOR[gPlayers[a].FTeam]
992 else
993 gPlayers[a].FModel.Color := gPlayers[a].FColor;
995 result := gPlayers[a].FUID;
996 end;
999 procedure g_Player_ResetTeams();
1000 var
1001 a: Integer;
1002 begin
1003 if g_Game_IsClient then
1004 Exit;
1005 if gPlayers = nil then
1006 Exit;
1007 for a := Low(gPlayers) to High(gPlayers) do
1008 if gPlayers[a] <> nil then
1009 case gGameSettings.GameMode of
1010 GM_DM:
1011 gPlayers[a].ChangeTeam(TEAM_NONE);
1012 GM_TDM, GM_CTF:
1013 if not (gPlayers[a].Team in [TEAM_RED, TEAM_BLUE]) then
1014 if gPlayers[a].FPreferredTeam in [TEAM_RED, TEAM_BLUE] then
1015 gPlayers[a].ChangeTeam(gPlayers[a].FPreferredTeam)
1016 else
1017 if a mod 2 = 0 then
1018 gPlayers[a].ChangeTeam(TEAM_RED)
1019 else
1020 gPlayers[a].ChangeTeam(TEAM_BLUE);
1021 GM_SINGLE,
1022 GM_COOP:
1023 gPlayers[a].ChangeTeam(TEAM_COOP);
1024 end;
1025 end;
1027 procedure g_Bot_Add(Team, Difficult: Byte; Handicap: Integer = 100);
1028 var
1029 m: SSArray;
1030 _name, _model: String;
1031 a, tr, tb: Integer;
1032 begin
1033 if not g_Game_IsServer then Exit;
1035 // Ñïèñîê íàçâàíèé ìîäåëåé:
1036 m := g_PlayerModel_GetNames();
1037 if m = nil then
1038 Exit;
1040 // Êîìàíäà:
1041 if (gGameSettings.GameType = GT_SINGLE) or (gGameSettings.GameMode = GM_COOP) then
1042 Team := TEAM_COOP // COOP
1043 else
1044 if gGameSettings.GameMode = GM_DM then
1045 Team := TEAM_NONE // DM
1046 else
1047 if Team = TEAM_NONE then // CTF / TDM
1048 begin
1049 // Àâòîáàëàíñ êîìàíä:
1050 tr := 0;
1051 tb := 0;
1053 for a := 0 to High(gPlayers) do
1054 if gPlayers[a] <> nil then
1055 begin
1056 if gPlayers[a].Team = TEAM_RED then
1057 Inc(tr)
1058 else
1059 if gPlayers[a].Team = TEAM_BLUE then
1060 Inc(tb);
1061 end;
1063 if tr > tb then
1064 Team := TEAM_BLUE
1065 else
1066 if tb > tr then
1067 Team := TEAM_RED
1068 else // tr = tb
1069 if Random(2) = 0 then
1070 Team := TEAM_RED
1071 else
1072 Team := TEAM_BLUE;
1073 end;
1075 // Âûáèðàåì áîòó èìÿ:
1076 _name := '';
1077 if BotNames <> nil then
1078 for a := 0 to High(BotNames) do
1079 if g_Player_ValidName(BotNames[a]) then
1080 begin
1081 _name := BotNames[a];
1082 Break;
1083 end;
1085 // Âûáèðàåì ñëó÷àéíóþ ìîäåëü:
1086 _model := m[Random(Length(m))];
1088 // Ñîçäàåì áîòà:
1089 with g_Player_Get(g_Player_Create(_model,
1090 _RGB(Min(Random(9)*32, 255),
1091 Min(Random(9)*32, 255),
1092 Min(Random(9)*32, 255)),
1093 Team, True)) as TBot do
1094 begin
1095 // Åñëè èìåíè íåò, äåëàåì åãî èç UID áîòà
1096 if _name = '' then
1097 Name := Format('DFBOT%.5d', [UID])
1098 else
1099 Name := _name;
1101 case Difficult of
1102 1: FDifficult := DIFFICULT_EASY;
1103 2: FDifficult := DIFFICULT_MEDIUM;
1104 else FDifficult := DIFFICULT_HARD;
1105 end;
1107 for a := WP_FIRST to WP_LAST do
1108 begin
1109 FDifficult.WeaponPrior[a] := WEAPON_PRIOR1[a];
1110 FDifficult.CloseWeaponPrior[a] := WEAPON_PRIOR2[a];
1111 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
1112 end;
1114 FHandicap := Handicap;
1116 g_Console_Add(Format(_lc[I_PLAYER_JOIN], [Name]), True);
1118 if g_Game_IsNet then MH_SEND_PlayerCreate(UID);
1119 if g_Game_IsServer and (gGameSettings.MaxLives > 0) then
1120 Spectate();
1121 end;
1122 end;
1124 procedure g_Bot_AddList(Team: Byte; lName: ShortString; num: Integer = -1; Handicap: Integer = 100);
1125 var
1126 m: SSArray;
1127 _name, _model: String;
1128 a: Integer;
1129 begin
1130 if not g_Game_IsServer then Exit;
1132 // Ñïèñîê íàçâàíèé ìîäåëåé:
1133 m := g_PlayerModel_GetNames();
1134 if m = nil then
1135 Exit;
1137 // Êîìàíäà:
1138 if (gGameSettings.GameType = GT_SINGLE) or (gGameSettings.GameMode = GM_COOP) then
1139 Team := TEAM_COOP // COOP
1140 else
1141 if gGameSettings.GameMode = GM_DM then
1142 Team := TEAM_NONE // DM
1143 else
1144 if Team = TEAM_NONE then
1145 Team := BotList[num].team; // CTF / TDM
1147 // Âûáèðàåì íàñòðîéêè áîòà èç ñïèñêà ïî íîìåðó èëè èìåíè:
1148 lName := AnsiLowerCase(lName);
1149 if (num < 0) or (num > Length(BotList)-1) then
1150 num := -1;
1151 if (num = -1) and (lName <> '') and (BotList <> nil) then
1152 for a := 0 to High(BotList) do
1153 if AnsiLowerCase(BotList[a].name) = lName then
1154 begin
1155 num := a;
1156 Break;
1157 end;
1158 if num = -1 then
1159 Exit;
1161 // Èìÿ áîòà:
1162 _name := BotList[num].name;
1163 // Çàíÿòî - âûáèðàåì ñëó÷àéíîå:
1164 if not g_Player_ValidName(_name) then
1165 repeat
1166 _name := Format('DFBOT%.2d', [Random(100)]);
1167 until g_Player_ValidName(_name);
1169 // Ìîäåëü:
1170 _model := BotList[num].model;
1171 // Íåò òàêîé - âûáèðàåì ñëó÷àéíóþ:
1172 if not InSArray(_model, m) then
1173 _model := m[Random(Length(m))];
1175 // Ñîçäàåì áîòà:
1176 with g_Player_Get(g_Player_Create(_model, BotList[num].color, Team, True)) as TBot do
1177 begin
1178 Name := _name;
1180 FDifficult.DiagFire := BotList[num].diag_fire;
1181 FDifficult.InvisFire := BotList[num].invis_fire;
1182 FDifficult.DiagPrecision := BotList[num].diag_precision;
1183 FDifficult.FlyPrecision := BotList[num].fly_precision;
1184 FDifficult.Cover := BotList[num].cover;
1185 FDifficult.CloseJump := BotList[num].close_jump;
1187 FHandicap := Handicap;
1189 for a := WP_FIRST to WP_LAST do
1190 begin
1191 FDifficult.WeaponPrior[a] := BotList[num].w_prior1[a];
1192 FDifficult.CloseWeaponPrior[a] := BotList[num].w_prior2[a];
1193 //FDifficult.SafeWeaponPrior[a] := BotList[num].w_prior3[a];
1194 end;
1196 g_Console_Add(Format(_lc[I_PLAYER_JOIN], [Name]), True);
1198 if g_Game_IsNet then MH_SEND_PlayerCreate(UID);
1199 end;
1200 end;
1202 procedure g_Bot_RemoveAll();
1203 var
1204 a: Integer;
1205 begin
1206 if not g_Game_IsServer then Exit;
1207 if gPlayers = nil then Exit;
1209 for a := 0 to High(gPlayers) do
1210 if gPlayers[a] <> nil then
1211 if gPlayers[a] is TBot then
1212 begin
1213 gPlayers[a].Lives := 0;
1214 gPlayers[a].Kill(K_SIMPLEKILL, 0, HIT_DISCON);
1215 g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [gPlayers[a].Name]), True);
1216 g_Player_Remove(gPlayers[a].FUID);
1217 end;
1219 g_Bot_MixNames();
1220 end;
1222 procedure g_Bot_MixNames();
1223 var
1224 s: String;
1225 a, b: Integer;
1226 begin
1227 if BotNames <> nil then
1228 for a := 0 to High(BotNames) do
1229 begin
1230 b := Random(Length(BotNames));
1231 s := BotNames[a];
1232 Botnames[a] := BotNames[b];
1233 BotNames[b] := s;
1234 end;
1235 end;
1237 procedure g_Player_Remove(UID: Word);
1238 var
1239 i: Integer;
1240 begin
1241 if gPlayers = nil then Exit;
1243 if g_Game_IsServer and g_Game_IsNet then
1244 MH_SEND_PlayerDelete(UID);
1246 for i := 0 to High(gPlayers) do
1247 if gPlayers[i] <> nil then
1248 if gPlayers[i].FUID = UID then
1249 begin
1250 if gPlayers[i] is TPlayer then
1251 TPlayer(gPlayers[i]).Free()
1252 else
1253 TBot(gPlayers[i]).Free();
1254 gPlayers[i] := nil;
1255 Exit;
1256 end;
1257 end;
1259 procedure g_Player_Init();
1260 var
1261 F: TextFile;
1262 s: String;
1263 a, b: Integer;
1264 config: TConfig;
1265 sa: SSArray;
1266 path: AnsiString;
1267 begin
1268 BotNames := nil;
1270 path := BOTNAMES_FILENAME;
1271 if e_FindResource(DataDirs, path) = false then
1272 Exit;
1274 // ×èòàåì âîçìîæíûå èìåíà áîòîâ èç ôàéëà:
1275 AssignFile(F, path);
1276 Reset(F);
1278 while not EOF(F) do
1279 begin
1280 ReadLn(F, s);
1282 s := Trim(s);
1283 if s = '' then
1284 Continue;
1286 SetLength(BotNames, Length(BotNames)+1);
1287 BotNames[High(BotNames)] := s;
1288 end;
1290 CloseFile(F);
1292 // Ïåðåìåøèâàåì èõ:
1293 g_Bot_MixNames();
1295 // ×èòàåì ôàéë ñ ïàðàìåòðàìè áîòîâ:
1296 config := TConfig.CreateFile(path);
1297 BotList := nil;
1298 a := 0;
1300 while config.SectionExists(IntToStr(a)) do
1301 begin
1302 SetLength(BotList, Length(BotList)+1);
1304 with BotList[High(BotList)] do
1305 begin
1306 // Èìÿ áîòà:
1307 name := config.ReadStr(IntToStr(a), 'name', '');
1308 // Ìîäåëü:
1309 model := config.ReadStr(IntToStr(a), 'model', '');
1310 // Êîìàíäà:
1311 if config.ReadStr(IntToStr(a), 'team', 'red') = 'red' then
1312 team := TEAM_RED
1313 else
1314 team := TEAM_BLUE;
1315 // Öâåò ìîäåëè:
1316 sa := parse(config.ReadStr(IntToStr(a), 'color', ''));
1317 color.R := StrToIntDef(sa[0], 0);
1318 color.G := StrToIntDef(sa[1], 0);
1319 color.B := StrToIntDef(sa[2], 0);
1320 // Âåðîÿòíîñòü ñòðåëüáû ïîä óãëîì:
1321 diag_fire := config.ReadInt(IntToStr(a), 'diag_fire', 0);
1322 // Âåðîÿòíîñòü îòâåòíîãî îãíÿ ïî íåâèäèìîìó ñîïåðíèêó:
1323 invis_fire := config.ReadInt(IntToStr(a), 'invis_fire', 0);
1324 // Òî÷íîñòü ñòðåëüáû ïîä óãëîì:
1325 diag_precision := config.ReadInt(IntToStr(a), 'diag_precision', 0);
1326 // Òî÷íîñòü ñòðåëüáû â ïîëåòå:
1327 fly_precision := config.ReadInt(IntToStr(a), 'fly_precision', 0);
1328 // Òî÷íîñòü óêëîíåíèÿ îò ñíàðÿäîâ:
1329 cover := config.ReadInt(IntToStr(a), 'cover', 0);
1330 // Âåðîÿòíîñòü ïðûæêà ïðè ïðèáëèæåíèè ñîïåðíèêà:
1331 close_jump := config.ReadInt(IntToStr(a), 'close_jump', 0);
1332 // Ïðèîðèòåòû îðóæèÿ äëÿ äàëüíåãî áîÿ:
1333 sa := parse(config.ReadStr(IntToStr(a), 'w_prior1', ''));
1334 if Length(sa) = 10 then
1335 for b := 0 to 9 do
1336 w_prior1[b] := EnsureRange(StrToInt(sa[b]), 0, 9);
1337 // Ïðèîðèòåòû îðóæèÿ äëÿ áëèæíåãî áîÿ:
1338 sa := parse(config.ReadStr(IntToStr(a), 'w_prior2', ''));
1339 if Length(sa) = 10 then
1340 for b := 0 to 9 do
1341 w_prior2[b] := EnsureRange(StrToInt(sa[b]), 0, 9);
1343 {sa := parse(config.ReadStr(IntToStr(a), 'w_prior3', ''));
1344 if Length(sa) = 10 then
1345 for b := 0 to 9 do
1346 w_prior3[b] := EnsureRange(StrToInt(sa[b]), 0, 9);}
1347 end;
1349 a := a + 1;
1350 end;
1352 config.Free();
1353 SetLength(SavedStates, 0);
1354 end;
1356 procedure g_Player_Free();
1357 var
1358 i: Integer;
1359 begin
1360 if gPlayers <> nil then
1361 begin
1362 for i := 0 to High(gPlayers) do
1363 if gPlayers[i] <> nil then
1364 begin
1365 if gPlayers[i] is TPlayer then
1366 TPlayer(gPlayers[i]).Free()
1367 else
1368 TBot(gPlayers[i]).Free();
1369 gPlayers[i] := nil;
1370 end;
1372 gPlayers := nil;
1373 end;
1375 gPlayer1 := nil;
1376 gPlayer2 := nil;
1377 SetLength(SavedStates, 0);
1378 end;
1380 procedure g_Player_PreUpdate();
1381 var
1382 i: Integer;
1383 begin
1384 if gPlayers = nil then Exit;
1385 for i := 0 to High(gPlayers) do
1386 if gPlayers[i] <> nil then
1387 gPlayers[i].PreUpdate();
1388 end;
1390 procedure g_Player_UpdateAll();
1391 var
1392 i: Integer;
1393 begin
1394 if gPlayers = nil then Exit;
1396 //e_WriteLog('***g_Player_UpdateAll: ENTER', MSG_WARNING);
1397 for i := 0 to High(gPlayers) do
1398 begin
1399 if gPlayers[i] <> nil then
1400 begin
1401 if gPlayers[i] is TPlayer then
1402 begin
1403 gPlayers[i].Update();
1404 gPlayers[i].RealizeCurrentWeapon(); // WARNING! DO NOT MOVE THIS INTO `Update()`!
1405 end
1406 else
1407 begin
1408 // bot updates weapons in `UpdateCombat()`
1409 TBot(gPlayers[i]).Update();
1410 end;
1411 end;
1412 end;
1413 //e_WriteLog('***g_Player_UpdateAll: EXIT', MSG_WARNING);
1414 end;
1416 procedure g_Player_DrawAll();
1417 var
1418 i: Integer;
1419 begin
1420 if gPlayers = nil then Exit;
1422 for i := 0 to High(gPlayers) do
1423 if gPlayers[i] <> nil then
1424 if gPlayers[i] is TPlayer then gPlayers[i].Draw()
1425 else TBot(gPlayers[i]).Draw();
1426 end;
1428 procedure g_Player_DrawDebug(p: TPlayer);
1429 var
1430 fW, fH: Byte;
1431 begin
1432 if p = nil then Exit;
1433 if (@p.FObj) = nil then Exit;
1435 e_TextureFontGetSize(gStdFont, fW, fH);
1437 e_TextureFontPrint(0, 0 , 'Pos X: ' + IntToStr(p.FObj.X), gStdFont);
1438 e_TextureFontPrint(0, fH , 'Pos Y: ' + IntToStr(p.FObj.Y), gStdFont);
1439 e_TextureFontPrint(0, fH * 2, 'Vel X: ' + IntToStr(p.FObj.Vel.X), gStdFont);
1440 e_TextureFontPrint(0, fH * 3, 'Vel Y: ' + IntToStr(p.FObj.Vel.Y), gStdFont);
1441 e_TextureFontPrint(0, fH * 4, 'Acc X: ' + IntToStr(p.FObj.Accel.X), gStdFont);
1442 e_TextureFontPrint(0, fH * 5, 'Acc Y: ' + IntToStr(p.FObj.Accel.Y), gStdFont);
1443 e_TextureFontPrint(0, fH * 6, 'Old X: ' + IntToStr(p.FObj.oldX), gStdFont);
1444 e_TextureFontPrint(0, fH * 7, 'Old Y: ' + IntToStr(p.FObj.oldY), gStdFont);
1445 end;
1447 procedure g_Player_DrawHealth();
1448 var
1449 i: Integer;
1450 fW, fH: Byte;
1451 begin
1452 if gPlayers = nil then Exit;
1453 e_TextureFontGetSize(gStdFont, fW, fH);
1455 for i := 0 to High(gPlayers) do
1456 if gPlayers[i] <> nil then
1457 begin
1458 e_TextureFontPrint(gPlayers[i].FObj.X + gPlayers[i].FObj.Rect.X,
1459 gPlayers[i].FObj.Y + gPlayers[i].FObj.Rect.Y + gPlayers[i].FObj.Rect.Height - fH * 2,
1460 IntToStr(gPlayers[i].FHealth), gStdFont);
1461 e_TextureFontPrint(gPlayers[i].FObj.X + gPlayers[i].FObj.Rect.X,
1462 gPlayers[i].FObj.Y + gPlayers[i].FObj.Rect.Y + gPlayers[i].FObj.Rect.Height - fH,
1463 IntToStr(gPlayers[i].FArmor), gStdFont);
1464 end;
1465 end;
1467 function g_Player_Get(UID: Word): TPlayer;
1468 var
1469 a: Integer;
1470 begin
1471 Result := nil;
1473 if gPlayers = nil then
1474 Exit;
1476 for a := 0 to High(gPlayers) do
1477 if gPlayers[a] <> nil then
1478 if gPlayers[a].FUID = UID then
1479 begin
1480 Result := gPlayers[a];
1481 Exit;
1482 end;
1483 end;
1485 function g_Player_GetCount(): Byte;
1486 var
1487 a: Integer;
1488 begin
1489 Result := 0;
1491 if gPlayers = nil then
1492 Exit;
1494 for a := 0 to High(gPlayers) do
1495 if gPlayers[a] <> nil then
1496 Result := Result + 1;
1497 end;
1499 function g_Player_GetStats(): TPlayerStatArray;
1500 var
1501 a: Integer;
1502 begin
1503 Result := nil;
1505 if gPlayers = nil then Exit;
1507 for a := 0 to High(gPlayers) do
1508 if gPlayers[a] <> nil then
1509 begin
1510 SetLength(Result, Length(Result)+1);
1511 with Result[High(Result)] do
1512 begin
1513 Num := a;
1514 Ping := gPlayers[a].FPing;
1515 Loss := gPlayers[a].FLoss;
1516 Name := gPlayers[a].FName;
1517 Team := gPlayers[a].FTeam;
1518 Frags := gPlayers[a].FFrags;
1519 Deaths := gPlayers[a].FDeath;
1520 Kills := gPlayers[a].FKills;
1521 Color := gPlayers[a].FModel.Color;
1522 Lives := gPlayers[a].FLives;
1523 Spectator := gPlayers[a].FSpectator;
1524 end;
1525 end;
1526 end;
1528 procedure g_Player_ResetReady();
1529 var
1530 a: Integer;
1531 begin
1532 if not g_Game_IsServer then Exit;
1533 if gPlayers = nil then Exit;
1535 for a := 0 to High(gPlayers) do
1536 if gPlayers[a] <> nil then
1537 begin
1538 gPlayers[a].FReady := False;
1539 if g_Game_IsNet then
1540 MH_SEND_GameEvent(NET_EV_INTER_READY, gPlayers[a].UID, 'N');
1541 end;
1542 end;
1544 procedure g_Player_RememberAll;
1545 var
1546 i: Integer;
1547 begin
1548 for i := Low(gPlayers) to High(gPlayers) do
1549 if (gPlayers[i] <> nil) and gPlayers[i].alive then
1550 gPlayers[i].RememberState;
1551 end;
1553 procedure g_Player_ResetAll(Force, Silent: Boolean);
1554 var
1555 i: Integer;
1556 begin
1557 gTeamStat[TEAM_RED].Goals := 0;
1558 gTeamStat[TEAM_BLUE].Goals := 0;
1560 if gPlayers <> nil then
1561 for i := 0 to High(gPlayers) do
1562 if gPlayers[i] <> nil then
1563 begin
1564 gPlayers[i].Reset(Force);
1566 if gPlayers[i] is TPlayer then
1567 begin
1568 if (not gPlayers[i].FSpectator) or gPlayers[i].FWantsInGame then
1569 gPlayers[i].Respawn(Silent)
1570 else
1571 gPlayers[i].Spectate();
1572 end
1573 else
1574 TBot(gPlayers[i]).Respawn(Silent);
1575 end;
1576 end;
1578 function g_Player_CreateCorpse(Player: TPlayer): Integer;
1579 var
1580 i: Integer;
1581 find_id: DWORD;
1582 ok: Boolean;
1583 begin
1584 Result := -1;
1586 if Player.alive then
1587 Exit;
1589 // Ðàçðûâàåì ñâÿçü ñ ïðåæíèì òðóïîì:
1590 i := Player.FCorpse;
1591 if (i >= 0) and (i < Length(gCorpses)) then
1592 begin
1593 if (gCorpses[i] <> nil) and (gCorpses[i].FPlayerUID = Player.FUID) then
1594 gCorpses[i].FPlayerUID := 0;
1595 end;
1597 if Player.FObj.Y >= gMapInfo.Height+128 then
1598 Exit;
1600 with Player do
1601 begin
1602 if (FHealth >= -50) or (gGibsCount = 0) then
1603 begin
1604 if (gCorpses = nil) or (Length(gCorpses) = 0) then
1605 Exit;
1607 ok := False;
1608 for find_id := 0 to High(gCorpses) do
1609 if gCorpses[find_id] = nil then
1610 begin
1611 ok := True;
1612 Break;
1613 end;
1615 if not ok then
1616 find_id := Random(Length(gCorpses));
1618 gCorpses[find_id] := TCorpse.Create(FObj.X, FObj.Y, FModel.Name, FHealth < -20);
1619 gCorpses[find_id].FColor := FModel.Color;
1620 gCorpses[find_id].FObj.Vel := FObj.Vel;
1621 gCorpses[find_id].FObj.Accel := FObj.Accel;
1622 gCorpses[find_id].FPlayerUID := FUID;
1624 Result := find_id;
1625 end
1626 else
1627 g_Player_CreateGibs(FObj.X + PLAYER_RECT_CX,
1628 FObj.Y + PLAYER_RECT_CY,
1629 FModel.Name, FModel.Color);
1630 end;
1631 end;
1633 procedure g_Player_CreateShell(fX, fY, dX, dY: Integer; T: Byte);
1634 var
1635 SID: DWORD;
1636 begin
1637 if (gShells = nil) or (Length(gShells) = 0) then
1638 Exit;
1640 with gShells[CurrentShell] do
1641 begin
1642 SpriteID := 0;
1643 g_Obj_Init(@Obj);
1644 Obj.Rect.X := 0;
1645 Obj.Rect.Y := 0;
1646 if T = SHELL_BULLET then
1647 begin
1648 if g_Texture_Get('TEXTURE_SHELL_BULLET', SID) then
1649 SpriteID := SID;
1650 CX := 2;
1651 CY := 1;
1652 Obj.Rect.Width := 4;
1653 Obj.Rect.Height := 2;
1654 end
1655 else
1656 begin
1657 if g_Texture_Get('TEXTURE_SHELL_SHELL', SID) then
1658 SpriteID := SID;
1659 CX := 4;
1660 CY := 2;
1661 Obj.Rect.Width := 7;
1662 Obj.Rect.Height := 3;
1663 end;
1664 SType := T;
1665 alive := True;
1666 Obj.X := fX;
1667 Obj.Y := fY;
1668 g_Obj_Push(@Obj, dX + Random(4)-Random(4), dY-Random(4));
1669 positionChanged(); // this updates spatial accelerators
1670 RAngle := Random(360);
1671 Timeout := gTime + SHELL_TIMEOUT;
1673 if CurrentShell >= High(gShells) then
1674 CurrentShell := 0
1675 else
1676 Inc(CurrentShell);
1677 end;
1678 end;
1680 procedure g_Player_CreateGibs(fX, fY: Integer; ModelName: string; fColor: TRGB);
1681 var
1682 a: Integer;
1683 GibsArray: TGibsArray;
1684 Blood: TModelBlood;
1685 begin
1686 if (gGibs = nil) or (Length(gGibs) = 0) then
1687 Exit;
1688 if not g_PlayerModel_GetGibs(ModelName, GibsArray) then
1689 Exit;
1690 Blood := g_PlayerModel_GetBlood(ModelName);
1692 for a := 0 to High(GibsArray) do
1693 with gGibs[CurrentGib] do
1694 begin
1695 Color := fColor;
1696 ID := GibsArray[a].ID;
1697 MaskID := GibsArray[a].MaskID;
1698 alive := True;
1699 g_Obj_Init(@Obj);
1700 Obj.Rect := GibsArray[a].Rect;
1701 Obj.X := fX-GibsArray[a].Rect.X-(GibsArray[a].Rect.Width div 2);
1702 Obj.Y := fY-GibsArray[a].Rect.Y-(GibsArray[a].Rect.Height div 2);
1703 g_Obj_PushA(@Obj, 25 + Random(10), Random(361));
1704 positionChanged(); // this updates spatial accelerators
1705 RAngle := Random(360);
1707 if gBloodCount > 0 then
1708 g_GFX_Blood(fX, fY, 16*gBloodCount+Random(5*gBloodCount), -16+Random(33), -16+Random(33),
1709 Random(48), Random(48), Blood.R, Blood.G, Blood.B, Blood.Kind);
1711 if CurrentGib >= High(gGibs) then
1712 CurrentGib := 0
1713 else
1714 Inc(CurrentGib);
1715 end;
1716 end;
1718 procedure g_Player_UpdatePhysicalObjects();
1719 var
1720 i: Integer;
1721 vel: TPoint2i;
1722 mr: Word;
1724 procedure ShellSound_Bounce(X, Y: Integer; T: Byte);
1725 var
1726 k: Integer;
1727 begin
1728 k := 1 + Random(2);
1729 if T = SHELL_BULLET then
1730 g_Sound_PlayExAt('SOUND_PLAYER_CASING' + IntToStr(k), X, Y)
1731 else
1732 g_Sound_PlayExAt('SOUND_PLAYER_SHELL' + IntToStr(k), X, Y);
1733 end;
1735 begin
1736 // Êóñêè ìÿñà:
1737 if gGibs <> nil then
1738 for i := 0 to High(gGibs) do
1739 if gGibs[i].alive then
1740 with gGibs[i] do
1741 begin
1742 Obj.oldX := Obj.X;
1743 Obj.oldY := Obj.Y;
1745 vel := Obj.Vel;
1746 mr := g_Obj_Move(@Obj, True, False, True);
1747 positionChanged(); // this updates spatial accelerators
1749 if WordBool(mr and MOVE_FALLOUT) then
1750 begin
1751 alive := False;
1752 Continue;
1753 end;
1755 // Îòëåòàåò îò óäàðà î ñòåíó/ïîòîëîê/ïîë:
1756 if WordBool(mr and MOVE_HITWALL) then
1757 Obj.Vel.X := -(vel.X div 2);
1758 if WordBool(mr and (MOVE_HITCEIL or MOVE_HITLAND)) then
1759 Obj.Vel.Y := -(vel.Y div 2);
1761 if (Obj.Vel.X >= 0) then
1762 begin // Clockwise
1763 RAngle := RAngle + Abs(Obj.Vel.X)*6 + Abs(Obj.Vel.Y);
1764 if RAngle >= 360 then
1765 RAngle := RAngle mod 360;
1766 end else begin // Counter-clockwise
1767 RAngle := RAngle - Abs(Obj.Vel.X)*6 - Abs(Obj.Vel.Y);
1768 if RAngle < 0 then
1769 RAngle := (360 - (Abs(RAngle) mod 360)) mod 360;
1770 end;
1772 // Ñîïðîòèâëåíèå âîçäóõà äëÿ êóñêà òðóïà:
1773 if gTime mod (GAME_TICK*3) = 0 then
1774 Obj.Vel.X := z_dec(Obj.Vel.X, 1);
1775 end;
1777 // Òðóïû:
1778 if gCorpses <> nil then
1779 for i := 0 to High(gCorpses) do
1780 if gCorpses[i] <> nil then
1781 if gCorpses[i].State = CORPSE_STATE_REMOVEME then
1782 begin
1783 gCorpses[i].Free();
1784 gCorpses[i] := nil;
1785 end
1786 else
1787 gCorpses[i].Update();
1789 // Ãèëüçû:
1790 if gShells <> nil then
1791 for i := 0 to High(gShells) do
1792 if gShells[i].alive then
1793 with gShells[i] do
1794 begin
1795 Obj.oldX := Obj.X;
1796 Obj.oldY := Obj.Y;
1798 vel := Obj.Vel;
1799 mr := g_Obj_Move(@Obj, True, False, True);
1800 positionChanged(); // this updates spatial accelerators
1802 if WordBool(mr and MOVE_FALLOUT) or (gShells[i].Timeout < gTime) then
1803 begin
1804 alive := False;
1805 Continue;
1806 end;
1808 // Îòëåòàåò îò óäàðà î ñòåíó/ïîòîëîê/ïîë:
1809 if WordBool(mr and MOVE_HITWALL) then
1810 begin
1811 Obj.Vel.X := -(vel.X div 2);
1812 if not WordBool(mr and MOVE_INWATER) then
1813 ShellSound_Bounce(Obj.X, Obj.Y, SType);
1814 end;
1815 if WordBool(mr and (MOVE_HITCEIL or MOVE_HITLAND)) then
1816 begin
1817 Obj.Vel.Y := -(vel.Y div 2);
1818 if Obj.Vel.X <> 0 then Obj.Vel.X := Obj.Vel.X div 2;
1819 if (Obj.Vel.X = 0) and (Obj.Vel.Y = 0) then
1820 begin
1821 if RAngle mod 90 <> 0 then
1822 RAngle := (RAngle div 90) * 90;
1823 end
1824 else if not WordBool(mr and MOVE_INWATER) then
1825 ShellSound_Bounce(Obj.X, Obj.Y, SType);
1826 end;
1828 if (Obj.Vel.X >= 0) then
1829 begin // Clockwise
1830 RAngle := RAngle + Abs(Obj.Vel.X)*8 + Abs(Obj.Vel.Y);
1831 if RAngle >= 360 then
1832 RAngle := RAngle mod 360;
1833 end else begin // Counter-clockwise
1834 RAngle := RAngle - Abs(Obj.Vel.X)*8 - Abs(Obj.Vel.Y);
1835 if RAngle < 0 then
1836 RAngle := (360 - (Abs(RAngle) mod 360)) mod 360;
1837 end;
1838 end;
1839 end;
1842 procedure TGib.getMapBox (out x, y, w, h: Integer); inline;
1843 begin
1844 x := Obj.X+Obj.Rect.X;
1845 y := Obj.Y+Obj.Rect.Y;
1846 w := Obj.Rect.Width;
1847 h := Obj.Rect.Height;
1848 end;
1850 procedure TGib.moveBy (dx, dy: Integer); inline;
1851 begin
1852 if (dx <> 0) or (dy <> 0) then
1853 begin
1854 Obj.X += dx;
1855 Obj.Y += dy;
1856 positionChanged();
1857 end;
1858 end;
1861 procedure TShell.getMapBox (out x, y, w, h: Integer); inline;
1862 begin
1863 x := Obj.X;
1864 y := Obj.Y;
1865 w := Obj.Rect.Width;
1866 h := Obj.Rect.Height;
1867 end;
1869 procedure TShell.moveBy (dx, dy: Integer); inline;
1870 begin
1871 if (dx <> 0) or (dy <> 0) then
1872 begin
1873 Obj.X += dx;
1874 Obj.Y += dy;
1875 positionChanged();
1876 end;
1877 end;
1880 procedure TGib.positionChanged (); inline; begin end;
1881 procedure TShell.positionChanged (); inline; begin end;
1884 procedure g_Player_DrawCorpses();
1885 var
1886 i, fX, fY: Integer;
1887 a: TDFPoint;
1888 begin
1889 if gGibs <> nil then
1890 for i := 0 to High(gGibs) do
1891 if gGibs[i].alive then
1892 with gGibs[i] do
1893 begin
1894 if not g_Obj_Collide(sX, sY, sWidth, sHeight, @Obj) then
1895 Continue;
1897 Obj.lerp(gLerpFactor, fX, fY);
1899 a.X := Obj.Rect.X+(Obj.Rect.Width div 2);
1900 a.y := Obj.Rect.Y+(Obj.Rect.Height div 2);
1902 e_DrawAdv(ID, fX, fY, 0, True, False, RAngle, @a, TMirrorType.None);
1904 e_Colors := Color;
1905 e_DrawAdv(MaskID, fX, fY, 0, True, False, RAngle, @a, TMirrorType.None);
1906 e_Colors.R := 255;
1907 e_Colors.G := 255;
1908 e_Colors.B := 255;
1909 end;
1911 if gCorpses <> nil then
1912 for i := 0 to High(gCorpses) do
1913 if gCorpses[i] <> nil then
1914 gCorpses[i].Draw();
1915 end;
1917 procedure g_Player_DrawShells();
1918 var
1919 i, fX, fY: Integer;
1920 a: TDFPoint;
1921 begin
1922 if gShells <> nil then
1923 for i := 0 to High(gShells) do
1924 if gShells[i].alive then
1925 with gShells[i] do
1926 begin
1927 if not g_Obj_Collide(sX, sY, sWidth, sHeight, @Obj) then
1928 Continue;
1930 Obj.lerp(gLerpFactor, fX, fY);
1932 a.X := CX;
1933 a.Y := CY;
1935 e_DrawAdv(SpriteID, fX, fY, 0, True, False, RAngle, @a, TMirrorType.None);
1936 end;
1937 end;
1939 procedure g_Player_RemoveAllCorpses();
1940 var
1941 i: Integer;
1942 begin
1943 gGibs := nil;
1944 gShells := nil;
1945 SetLength(gGibs, MaxGibs);
1946 SetLength(gShells, MaxGibs);
1947 CurrentGib := 0;
1948 CurrentShell := 0;
1950 if gCorpses <> nil then
1951 for i := 0 to High(gCorpses) do
1952 gCorpses[i].Free();
1954 gCorpses := nil;
1955 SetLength(gCorpses, MaxCorpses);
1956 end;
1958 procedure g_Player_Corpses_SaveState (st: TStream);
1959 var
1960 count, i: Integer;
1961 begin
1962 // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ òðóïîâ
1963 count := 0;
1964 for i := 0 to High(gCorpses) do if (gCorpses[i] <> nil) then Inc(count);
1966 // Êîëè÷åñòâî òðóïîâ
1967 utils.writeInt(st, LongInt(count));
1969 if (count = 0) then exit;
1971 // Ñîõðàíÿåì òðóïû
1972 for i := 0 to High(gCorpses) do
1973 begin
1974 if gCorpses[i] <> nil then
1975 begin
1976 // Íàçâàíèå ìîäåëè
1977 utils.writeStr(st, gCorpses[i].FModelName);
1978 // Òèï ñìåðòè
1979 utils.writeBool(st, gCorpses[i].Mess);
1980 // Ñîõðàíÿåì äàííûå òðóïà:
1981 gCorpses[i].SaveState(st);
1982 end;
1983 end;
1984 end;
1987 procedure g_Player_Corpses_LoadState (st: TStream);
1988 var
1989 count, i: Integer;
1990 str: String;
1991 b: Boolean;
1992 begin
1993 assert(st <> nil);
1995 g_Player_RemoveAllCorpses();
1997 // Êîëè÷åñòâî òðóïîâ:
1998 count := utils.readLongInt(st);
1999 if (count < 0) or (count > Length(gCorpses)) then raise XStreamError.Create('invalid number of corpses');
2001 if (count = 0) then exit;
2003 // Çàãðóæàåì òðóïû
2004 for i := 0 to count-1 do
2005 begin
2006 // Íàçâàíèå ìîäåëè:
2007 str := utils.readStr(st);
2008 // Òèï ñìåðòè
2009 b := utils.readBool(st);
2010 // Ñîçäàåì òðóï
2011 gCorpses[i] := TCorpse.Create(0, 0, str, b);
2012 // Çàãðóæàåì äàííûå òðóïà
2013 gCorpses[i].LoadState(st);
2014 end;
2015 end;
2018 { T P l a y e r : }
2020 function TPlayer.isValidViewPort (): Boolean; inline; begin result := (viewPortW > 0) and (viewPortH > 0); end;
2022 procedure TPlayer.BFGHit();
2023 begin
2024 g_Weapon_BFGHit(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
2025 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2));
2026 if g_Game_IsServer and g_Game_IsNet then
2027 MH_SEND_Effect(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
2028 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
2029 0, NET_GFX_BFGHIT);
2030 end;
2032 procedure TPlayer.ChangeModel(ModelName: string);
2033 var
2034 locModel: TPlayerModel;
2035 begin
2036 locModel := g_PlayerModel_Get(ModelName);
2037 if locModel = nil then Exit;
2039 FModel.Free();
2040 FModel := locModel;
2041 end;
2043 procedure TPlayer.SetModel(ModelName: string);
2044 var
2045 m: TPlayerModel;
2046 begin
2047 m := g_PlayerModel_Get(ModelName);
2048 if m = nil then
2049 begin
2050 g_SimpleError(Format(_lc[I_GAME_ERROR_MODEL_FALLBACK], [ModelName]));
2051 m := g_PlayerModel_Get('doomer');
2052 if m = nil then
2053 begin
2054 g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], ['doomer']));
2055 Exit;
2056 end;
2057 end;
2059 if FModel <> nil then
2060 FModel.Free();
2062 FModel := m;
2064 if not (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
2065 FModel.Color := FColor
2066 else
2067 FModel.Color := TEAMCOLOR[FTeam];
2068 FModel.SetWeapon(FCurrWeap);
2069 FModel.SetFlag(FFlag);
2070 SetDirection(FDirection);
2071 end;
2073 procedure TPlayer.SetColor(Color: TRGB);
2074 begin
2075 FColor := Color;
2076 if not (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
2077 if FModel <> nil then FModel.Color := Color;
2078 end;
2080 function TPlayer.GetColor(): TRGB;
2081 begin
2082 result := FModel.Color;
2083 end;
2085 procedure TPlayer.SwitchTeam;
2086 begin
2087 if g_Game_IsClient then
2088 Exit;
2089 if not (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then Exit;
2091 if gGameOn and FAlive then
2092 Kill(K_SIMPLEKILL, FUID, HIT_SELF);
2094 if FTeam = TEAM_RED then
2095 begin
2096 ChangeTeam(TEAM_BLUE);
2097 g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_BLUE], [FName]), True);
2098 if g_Game_IsNet then
2099 MH_SEND_GameEvent(NET_EV_CHANGE_TEAM, TEAM_BLUE, FName);
2100 end
2101 else
2102 begin
2103 ChangeTeam(TEAM_RED);
2104 g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_RED], [FName]), True);
2105 if g_Game_IsNet then
2106 MH_SEND_GameEvent(NET_EV_CHANGE_TEAM, TEAM_RED, FName);
2107 end;
2108 FPreferredTeam := FTeam;
2109 end;
2111 procedure TPlayer.ChangeTeam(Team: Byte);
2112 var
2113 OldTeam: Byte;
2114 begin
2115 OldTeam := FTeam;
2116 FTeam := Team;
2117 case Team of
2118 TEAM_RED, TEAM_BLUE:
2119 FModel.Color := TEAMCOLOR[Team];
2120 else
2121 FModel.Color := FColor;
2122 end;
2123 if (FTeam <> OldTeam) and g_Game_IsNet and g_Game_IsServer then
2124 MH_SEND_PlayerStats(FUID);
2125 end;
2128 procedure TPlayer.CollideItem();
2129 var
2130 i: Integer;
2131 r: Boolean;
2132 begin
2133 if gItems = nil then Exit;
2134 if not FAlive then Exit;
2136 for i := 0 to High(gItems) do
2137 with gItems[i] do
2138 begin
2139 if (ItemType <> ITEM_NONE) and alive then
2140 if g_Obj_Collide(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width,
2141 PLAYER_RECT.Height, @Obj) then
2142 begin
2143 if not PickItem(ItemType, gItems[i].Respawnable, r) then Continue;
2145 if ItemType in [ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_INVUL] then
2146 g_Sound_PlayExAt('SOUND_ITEM_GETRULEZ', FObj.X, FObj.Y)
2147 else if ItemType in [ITEM_MEDKIT_SMALL, ITEM_MEDKIT_LARGE, ITEM_MEDKIT_BLACK] then
2148 g_Sound_PlayExAt('SOUND_ITEM_GETMED', FObj.X, FObj.Y)
2149 else g_Sound_PlayExAt('SOUND_ITEM_GETITEM', FObj.X, FObj.Y);
2151 // Íàäî óáðàòü ñ êàðòû, åñëè ýòî íå êëþ÷, êîòîðûì íóæíî ïîäåëèòñÿ ñ äðóãèì èãðîêîì:
2152 if r and not ((ItemType in [ITEM_KEY_RED, ITEM_KEY_GREEN, ITEM_KEY_BLUE]) and
2153 (gGameSettings.GameType = GT_SINGLE) and
2154 (g_Player_GetCount() > 1)) then
2155 if not Respawnable then g_Items_Remove(i) else g_Items_Pick(i);
2156 end;
2157 end;
2158 end;
2161 function TPlayer.CollideLevel(XInc, YInc: Integer): Boolean;
2162 begin
2163 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
2164 PLAYER_RECT.Width, PLAYER_RECT.Height, PANEL_WALL,
2165 False);
2166 end;
2168 constructor TPlayer.Create();
2169 begin
2170 viewPortX := 0;
2171 viewPortY := 0;
2172 viewPortW := 0;
2173 viewPortH := 0;
2174 mEDamageType := HIT_SOME;
2176 FIamBot := False;
2177 FDummy := False;
2178 FSpawned := False;
2180 FSawSound := TPlayableSound.Create();
2181 FSawSoundIdle := TPlayableSound.Create();
2182 FSawSoundHit := TPlayableSound.Create();
2183 FSawSoundSelect := TPlayableSound.Create();
2184 FFlameSoundOn := TPlayableSound.Create();
2185 FFlameSoundOff := TPlayableSound.Create();
2186 FFlameSoundWork := TPlayableSound.Create();
2187 FJetSoundFly := TPlayableSound.Create();
2188 FJetSoundOn := TPlayableSound.Create();
2189 FJetSoundOff := TPlayableSound.Create();
2191 FSawSound.SetByName('SOUND_WEAPON_FIRESAW');
2192 FSawSoundIdle.SetByName('SOUND_WEAPON_IDLESAW');
2193 FSawSoundHit.SetByName('SOUND_WEAPON_HITSAW');
2194 FSawSoundSelect.SetByName('SOUND_WEAPON_SELECTSAW');
2195 FFlameSoundOn.SetByName('SOUND_WEAPON_FLAMEON');
2196 FFlameSoundOff.SetByName('SOUND_WEAPON_FLAMEOFF');
2197 FFlameSoundWork.SetByName('SOUND_WEAPON_FLAMEWORK');
2198 FJetSoundFly.SetByName('SOUND_PLAYER_JETFLY');
2199 FJetSoundOn.SetByName('SOUND_PLAYER_JETON');
2200 FJetSoundOff.SetByName('SOUND_PLAYER_JETOFF');
2202 FSpectatePlayer := -1;
2203 FClientID := -1;
2204 FPing := 0;
2205 FLoss := 0;
2206 FSavedStateNum := -1;
2207 FShellTimer := -1;
2208 FFireTime := 0;
2209 FFirePainTime := 0;
2210 FFireAttacker := 0;
2211 FHandicap := 100;
2212 FCorpse := -1;
2214 FActualModelName := 'doomer';
2216 g_Obj_Init(@FObj);
2217 FObj.Rect := PLAYER_RECT;
2219 FBFGFireCounter := -1;
2220 FJustTeleported := False;
2221 FNetTime := 0;
2223 FWaitForFirstSpawn := false;
2225 resetWeaponQueue();
2226 end;
2228 procedure TPlayer.positionChanged (); inline;
2229 begin
2230 end;
2232 procedure TPlayer.doDamage (v: Integer);
2233 begin
2234 if (v <= 0) then exit;
2235 if (v > 32767) then v := 32767;
2236 Damage(v, 0, 0, 0, mEDamageType);
2237 end;
2239 procedure TPlayer.Damage(value: Word; SpawnerUID: Word; vx, vy: Integer; t: Byte);
2240 var
2241 c: Word;
2242 begin
2243 if (not g_Game_IsClient) and (not FAlive) then
2244 Exit;
2246 FLastHit := t;
2248 // Íåóÿçâèìîñòü íå ñïàñàåò îò ëîâóøåê:
2249 if ((t = HIT_TRAP) or (t = HIT_SELF)) and (not FGodMode) then
2250 begin
2251 if not g_Game_IsClient then
2252 begin
2253 FArmor := 0;
2254 if t = HIT_TRAP then
2255 begin
2256 // Ëîâóøêà óáèâàåò ñðàçó:
2257 FHealth := -100;
2258 Kill(K_EXTRAHARDKILL, SpawnerUID, t);
2259 end;
2260 if t = HIT_SELF then
2261 begin
2262 // Ñàìîóáèéñòâî:
2263 FHealth := 0;
2264 Kill(K_SIMPLEKILL, SpawnerUID, t);
2265 end;
2266 end;
2267 // Îáíóëèòü äåéñòâèÿ ïðèìî÷åê, ÷òîáû ôîí ïðîïàë
2268 FMegaRulez[MR_SUIT] := 0;
2269 FMegaRulez[MR_INVUL] := 0;
2270 FMegaRulez[MR_INVIS] := 0;
2271 FSpawnInvul := 0;
2272 FBerserk := 0;
2273 end;
2275 // Íî îò îñòàëüíîãî ñïàñàåò:
2276 if FMegaRulez[MR_INVUL] >= gTime then
2277 Exit;
2279 // ×èò-êîä "ÃÎÐÅÖ":
2280 if FGodMode then
2281 Exit;
2283 // Åñëè åñòü óðîí ñâîèì, èëè ðàíèë ñàì ñåáÿ, èëè òåáÿ ðàíèë ïðîòèâíèê:
2284 if LongBool(gGameSettings.Options and GAME_OPTION_TEAMDAMAGE) or
2285 (SpawnerUID = FUID) or
2286 (not SameTeam(FUID, SpawnerUID)) then
2287 begin
2288 FLastSpawnerUID := SpawnerUID;
2290 // Êðîâü (ïóçûðüêè, åñëè â âîäå):
2291 if gBloodCount > 0 then
2292 begin
2293 c := Min(value, 200)*gBloodCount + Random(Min(value, 200) div 2);
2294 if value div 4 <= c then
2295 c := c - (value div 4)
2296 else
2297 c := 0;
2299 if (t = HIT_SOME) and (vx = 0) and (vy = 0) then
2300 MakeBloodSimple(c)
2301 else
2302 case t of
2303 HIT_TRAP, HIT_ACID, HIT_FLAME, HIT_SELF: MakeBloodSimple(c);
2304 HIT_BFG, HIT_ROCKET, HIT_SOME: MakeBloodVector(c, vx, vy);
2305 end;
2307 if t = HIT_WATER then
2308 g_GFX_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
2309 FObj.Y+PLAYER_RECT.Y-4, value div 2, 8, 4);
2310 end;
2312 // Áóôåð óðîíà:
2313 if FAlive then
2314 Inc(FDamageBuffer, value);
2316 // Âñïûøêà áîëè:
2317 if gFlash <> 0 then
2318 FPain := FPain + value;
2319 end;
2321 if g_Game_IsServer and g_Game_IsNet then
2322 begin
2323 MH_SEND_PlayerDamage(FUID, t, SpawnerUID, value, vx, vy);
2324 MH_SEND_PlayerStats(FUID);
2325 MH_SEND_PlayerPos(False, FUID);
2326 end;
2327 end;
2329 function TPlayer.Heal(value: Word; Soft: Boolean): Boolean;
2330 begin
2331 Result := False;
2332 if g_Game_IsClient then
2333 Exit;
2334 if not FAlive then
2335 Exit;
2337 if Soft and (FHealth < PLAYER_HP_SOFT) then
2338 begin
2339 IncMax(FHealth, value, PLAYER_HP_SOFT);
2340 Result := True;
2341 end;
2342 if (not Soft) and (FHealth < PLAYER_HP_LIMIT) then
2343 begin
2344 IncMax(FHealth, value, PLAYER_HP_LIMIT);
2345 Result := True;
2346 end;
2348 if Result and g_Game_IsServer and g_Game_IsNet then
2349 MH_SEND_PlayerStats(FUID);
2350 end;
2352 destructor TPlayer.Destroy();
2353 begin
2354 if (gPlayer1 <> nil) and (gPlayer1.FUID = FUID) then
2355 gPlayer1 := nil;
2356 if (gPlayer2 <> nil) and (gPlayer2.FUID = FUID) then
2357 gPlayer2 := nil;
2359 FSawSound.Free();
2360 FSawSoundIdle.Free();
2361 FSawSoundHit.Free();
2362 FSawSoundSelect.Free();
2363 FFlameSoundOn.Free();
2364 FFlameSoundOff.Free();
2365 FFlameSoundWork.Free();
2366 FJetSoundFly.Free();
2367 FJetSoundOn.Free();
2368 FJetSoundOff.Free();
2369 FModel.Free();
2370 if FPunchAnim <> nil then
2371 FPunchAnim.Free();
2373 inherited;
2374 end;
2376 procedure TPlayer.DrawIndicator(Color: TRGB);
2377 var
2378 indX, indY, fX, fY, fSlope: Integer;
2379 indW, indH: Word;
2380 indA: Single;
2381 a: TDFPoint;
2382 nW, nH: Byte;
2383 ID: DWORD;
2384 c: TRGB;
2385 begin
2386 if FAlive then
2387 begin
2388 FObj.lerp(gLerpFactor, fX, fY);
2389 fSlope := nlerp(FSlopeOld, FObj.slopeUpLeft, gLerpFactor);
2391 case gPlayerIndicatorStyle of
2392 0:
2393 begin
2394 if g_Texture_Get('TEXTURE_PLAYER_INDICATOR', ID) then
2395 begin
2396 e_GetTextureSize(ID, @indW, @indH);
2397 a.X := indW div 2;
2398 a.Y := indH div 2;
2400 if (FObj.X + FObj.Rect.X) < 0 then
2401 begin
2402 indA := 90;
2403 indX := fX + FObj.Rect.X + FObj.Rect.Width;
2404 indY := fY + FObj.Rect.Y + (FObj.Rect.Height - indW) div 2;
2405 end
2407 else if (FObj.X + FObj.Rect.X + FObj.Rect.Width) > Max(gMapInfo.Width, gPlayerScreenSize.X) then
2408 begin
2409 indA := 270;
2410 indX := fX + FObj.Rect.X - indH;
2411 indY := fY + FObj.Rect.Y + (FObj.Rect.Height - indW) div 2;
2412 end
2414 else if (FObj.Y - indH) < 0 then
2415 begin
2416 indA := 180;
2417 indX := fX + FObj.Rect.X + (FObj.Rect.Width - indW) div 2;
2418 indY := fY + FObj.Rect.Y + FObj.Rect.Height;
2419 end
2421 else
2422 begin
2423 indA := 0;
2424 indX := fX + FObj.Rect.X + (FObj.Rect.Width - indW) div 2;
2425 indY := fY - indH;
2426 end;
2428 indY := indY + fSlope;
2429 indX := EnsureRange(indX, 0, Max(gMapInfo.Width, gPlayerScreenSize.X) - indW);
2430 indY := EnsureRange(indY, 0, Max(gMapInfo.Height, gPlayerScreenSize.Y) - indH);
2432 c := e_Colors;
2433 e_Colors := Color;
2434 e_DrawAdv(ID, indX, indY, 0, True, False, indA, @a);
2435 e_Colors := c;
2436 end;
2437 end;
2439 1:
2440 begin
2441 e_TextureFontGetSize(gStdFont, nW, nH);
2442 indX := fX + FObj.Rect.X + (FObj.Rect.Width - Length(FName) * nW) div 2;
2443 indY := fY - nH + fSlope;
2444 e_TextureFontPrintEx(indX, indY, FName, gStdFont, Color.R, Color.G, Color.B, 1.0, True);
2445 end;
2446 end;
2447 end;
2448 end;
2450 procedure TPlayer.DrawBubble();
2451 var
2452 bubX, bubY, fX, fY: Integer;
2453 ID: LongWord;
2454 Rb, Gb, Bb,
2455 Rw, Gw, Bw: SmallInt;
2456 Dot: Byte;
2457 begin
2458 FObj.lerp(gLerpFactor, fX, fY);
2459 bubX := fX+FObj.Rect.X + IfThen(FDirection = TDirection.D_LEFT, -4, 18);
2460 bubY := fY+FObj.Rect.Y - 18;
2461 Rb := 64;
2462 Gb := 64;
2463 Bb := 64;
2464 Rw := 240;
2465 Gw := 240;
2466 Bw := 240;
2467 case gChatBubble of
2468 1: // simple textual non-bubble
2469 begin
2470 bubX := fX+FObj.Rect.X - 11;
2471 bubY := fY+FObj.Rect.Y - 17;
2472 e_TextureFontPrint(bubX, bubY, '[...]', gStdFont);
2473 Exit;
2474 end;
2475 2: // advanced pixel-perfect bubble
2476 begin
2477 if FTeam = TEAM_RED then
2478 Rb := 255
2479 else
2480 if FTeam = TEAM_BLUE then
2481 Bb := 255;
2482 end;
2483 3: // colored bubble
2484 begin
2485 Rb := FModel.Color.R;
2486 Gb := FModel.Color.G;
2487 Bb := FModel.Color.B;
2488 Rw := Min(Rb * 2 + 64, 255);
2489 Gw := Min(Gb * 2 + 64, 255);
2490 Bw := Min(Bb * 2 + 64, 255);
2491 if (Abs(Rw - Rb) < 32)
2492 or (Abs(Gw - Gb) < 32)
2493 or (Abs(Bw - Bb) < 32) then
2494 begin
2495 Rb := Max(Rw div 2 - 16, 0);
2496 Gb := Max(Gw div 2 - 16, 0);
2497 Bb := Max(Bw div 2 - 16, 0);
2498 end;
2499 end;
2500 4: // custom textured bubble
2501 begin
2502 if g_Texture_Get('TEXTURE_PLAYER_TALKBUBBLE', ID) then
2503 if FDirection = TDirection.D_RIGHT then
2504 e_Draw(ID, bubX - 6, bubY - 7, 0, True, False)
2505 else
2506 e_Draw(ID, bubX - 6, bubY - 7, 0, True, False, TMirrorType.Horizontal);
2507 Exit;
2508 end;
2509 end;
2511 // Outer borders
2512 e_DrawQuad(bubX + 1, bubY , bubX + 18, bubY + 13, Rb, Gb, Bb);
2513 e_DrawQuad(bubX , bubY + 1, bubX + 19, bubY + 12, Rb, Gb, Bb);
2514 // Inner box
2515 e_DrawFillQuad(bubX + 1, bubY + 1, bubX + 18, bubY + 12, Rw, Gw, Bw, 0);
2517 // Tail
2518 Dot := IfThen(FDirection = TDirection.D_LEFT, 14, 5);
2519 e_DrawLine(1, bubX + Dot, bubY + 14, bubX + Dot, bubY + 16, Rb, Gb, Bb);
2520 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);
2521 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);
2522 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);
2523 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);
2525 // Dots
2526 Dot := 6;
2527 e_DrawFillQuad(bubX + Dot, bubY + 8, bubX + Dot + 1, bubY + 9, Rb, Gb, Bb, 0);
2528 e_DrawFillQuad(bubX + Dot + 3, bubY + 8, bubX + Dot + 4, bubY + 9, Rb, Gb, Bb, 0);
2529 e_DrawFillQuad(bubX + Dot + 6, bubY + 8, bubX + Dot + 7, bubY + 9, Rb, Gb, Bb, 0);
2530 end;
2532 procedure TPlayer.Draw();
2533 var
2534 ID: DWORD;
2535 w, h: Word;
2536 dr: Boolean;
2537 Mirror: TMirrorType;
2538 fX, fY, fSlope: Integer;
2539 begin
2540 FObj.lerp(gLerpFactor, fX, fY);
2541 fSlope := nlerp(FSlopeOld, FObj.slopeUpLeft, gLerpFactor);
2543 if FAlive then
2544 begin
2545 if Direction = TDirection.D_RIGHT then
2546 Mirror := TMirrorType.None
2547 else
2548 Mirror := TMirrorType.Horizontal;
2550 if FPunchAnim <> nil then
2551 begin
2552 FPunchAnim.Draw(fX+IfThen(Direction = TDirection.D_LEFT, 15-FObj.Rect.X, FObj.Rect.X-15),
2553 fY+fSlope+FObj.Rect.Y-11, Mirror);
2554 if FPunchAnim.played then
2555 begin
2556 FPunchAnim.Free;
2557 FPunchAnim := nil;
2558 end;
2559 end;
2561 if (FMegaRulez[MR_INVUL] > gTime) and ((gPlayerDrawn <> Self) or (FSpawnInvul >= gTime)) then
2562 if g_Texture_Get('TEXTURE_PLAYER_INVULPENTA', ID) then
2563 begin
2564 e_GetTextureSize(ID, @w, @h);
2565 if FDirection = TDirection.D_LEFT then
2566 e_Draw(ID, fX+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)+4,
2567 fY+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7+fSlope, 0, True, False)
2568 else
2569 e_Draw(ID, fX+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)-2,
2570 fY+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7+fSlope, 0, True, False);
2571 end;
2573 if FMegaRulez[MR_INVIS] > gTime then
2574 begin
2575 if (gPlayerDrawn <> nil) and ((Self = gPlayerDrawn) or
2576 ((FTeam = gPlayerDrawn.Team) and (gGameSettings.GameMode <> GM_DM))) then
2577 begin
2578 if (FMegaRulez[MR_INVIS] - gTime) <= 2100 then
2579 dr := not Odd((FMegaRulez[MR_INVIS] - gTime) div 300)
2580 else
2581 dr := True;
2582 if dr then
2583 FModel.Draw(fX, fY+fSlope, 200)
2584 else
2585 FModel.Draw(fX, fY+fSlope);
2586 end
2587 else
2588 FModel.Draw(fX, fY+fSlope, 254);
2589 end
2590 else
2591 FModel.Draw(fX, fY+fSlope);
2592 end;
2594 if g_debug_Frames then
2595 begin
2596 e_DrawQuad(FObj.X+FObj.Rect.X,
2597 FObj.Y+FObj.Rect.Y,
2598 FObj.X+FObj.Rect.X+FObj.Rect.Width-1,
2599 FObj.Y+FObj.Rect.Y+FObj.Rect.Height-1,
2600 0, 255, 0);
2601 end;
2603 if (gChatBubble > 0) and (FKeys[KEY_CHAT].Pressed) and not FGhost then
2604 if (FMegaRulez[MR_INVIS] <= gTime) or ((gPlayerDrawn <> nil) and ((Self = gPlayerDrawn) or
2605 ((FTeam = gPlayerDrawn.Team) and (gGameSettings.GameMode <> GM_DM)))) then
2606 DrawBubble();
2607 // e_DrawPoint(5, 335, 288, 255, 0, 0); // DL, UR, DL, UR
2608 if gAimLine and alive and
2609 ((Self = gPlayer1) or (Self = gPlayer2)) then
2610 DrawAim();
2611 end;
2614 procedure TPlayer.DrawAim();
2615 procedure drawCast (sz: Integer; ax0, ay0, ax1, ay1: Integer);
2616 var
2617 ex, ey: Integer;
2618 begin
2620 {$IFDEF ENABLE_HOLMES}
2621 if isValidViewPort and (self = gPlayer1) then
2622 begin
2623 g_Holmes_plrLaser(ax0, ay0, ax1, ay1);
2624 end;
2625 {$ENDIF}
2627 e_DrawLine(sz, ax0, ay0, ax1, ay1, 255, 0, 0, 96);
2628 if (g_Map_traceToNearestWall(ax0, ay0, ax1, ay1, @ex, @ey) <> nil) then
2629 begin
2630 e_DrawLine(sz, ax0, ay0, ex, ey, 0, 255, 0, 96);
2631 end
2632 else
2633 begin
2634 e_DrawLine(sz, ax0, ay0, ex, ey, 0, 0, 255, 96);
2635 end;
2636 end;
2638 var
2639 wx, wy, xx, yy: Integer;
2640 angle: SmallInt;
2641 sz, len: Word;
2642 begin
2643 wx := FObj.X + WEAPONPOINT[FDirection].X + IfThen(FDirection = TDirection.D_LEFT, 7, -7);
2644 wy := FObj.Y + WEAPONPOINT[FDirection].Y;
2645 angle := FAngle;
2646 len := 1024;
2647 sz := 2;
2648 case FCurrWeap of
2649 0: begin // Punch
2650 len := 12;
2651 sz := 4;
2652 end;
2653 1: begin // Chainsaw
2654 len := 24;
2655 sz := 6;
2656 end;
2657 2: begin // Pistol
2658 len := 1024;
2659 sz := 2;
2660 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2661 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2662 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2663 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2664 end;
2665 3: begin // Shotgun
2666 len := 1024;
2667 sz := 3;
2668 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2669 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2670 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2671 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2672 end;
2673 4: begin // Double Shotgun
2674 len := 1024;
2675 sz := 4;
2676 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2677 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2678 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2679 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2680 end;
2681 5: begin // Chaingun
2682 len := 1024;
2683 sz := 3;
2684 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2685 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2686 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2687 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2688 end;
2689 6: begin // Rocket Launcher
2690 len := 1024;
2691 sz := 7;
2692 if angle = ANGLE_RIGHTUP then Inc(angle, 2);
2693 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2694 if angle = ANGLE_LEFTUP then Dec(angle, 2);
2695 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2696 end;
2697 7: begin // Plasmagun
2698 len := 1024;
2699 sz := 5;
2700 if angle = ANGLE_RIGHTUP then Inc(angle);
2701 if angle = ANGLE_RIGHTDOWN then Inc(angle, 3);
2702 if angle = ANGLE_LEFTUP then Dec(angle);
2703 if angle = ANGLE_LEFTDOWN then Dec(angle, 3);
2704 end;
2705 8: begin // BFG
2706 len := 1024;
2707 sz := 12;
2708 if angle = ANGLE_RIGHTUP then Inc(angle, 1);
2709 if angle = ANGLE_RIGHTDOWN then Inc(angle, 2);
2710 if angle = ANGLE_LEFTUP then Dec(angle, 1);
2711 if angle = ANGLE_LEFTDOWN then Dec(angle, 2);
2712 end;
2713 9: begin // Super Chaingun
2714 len := 1024;
2715 sz := 4;
2716 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2717 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2718 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2719 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2720 end;
2721 end;
2722 xx := Trunc(Cos(-DegToRad(angle)) * len) + wx;
2723 yy := Trunc(Sin(-DegToRad(angle)) * len) + wy;
2724 {$IF DEFINED(D2F_DEBUG)}
2725 drawCast(sz, wx, wy, xx, yy);
2726 {$ELSE}
2727 e_DrawLine(sz, wx, wy, xx, yy, 255, 0, 0, 96);
2728 {$ENDIF}
2729 end;
2731 procedure TPlayer.DrawGUI();
2732 var
2733 ID: DWORD;
2734 X, Y, SY, a, p, m: Integer;
2735 tw, th: Word;
2736 cw, ch: Byte;
2737 s: string;
2738 stat: TPlayerStatArray;
2739 begin
2740 X := gPlayerScreenSize.X;
2741 SY := gPlayerScreenSize.Y;
2742 Y := 0;
2744 if gShowGoals and (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
2745 begin
2746 if gGameSettings.GameMode = GM_CTF then
2747 a := 32 + 8
2748 else
2749 a := 0;
2750 if gGameSettings.GameMode = GM_CTF then
2751 begin
2752 s := 'TEXTURE_PLAYER_REDFLAG';
2753 if gFlags[FLAG_RED].State = FLAG_STATE_CAPTURED then
2754 s := 'TEXTURE_PLAYER_REDFLAG_S';
2755 if gFlags[FLAG_RED].State = FLAG_STATE_DROPPED then
2756 s := 'TEXTURE_PLAYER_REDFLAG_D';
2757 if g_Texture_Get(s, ID) then
2758 e_Draw(ID, X-16-32, 240-72-4, 0, True, False);
2759 end;
2761 s := IntToStr(gTeamStat[TEAM_RED].Goals);
2762 e_CharFont_GetSize(gMenuFont, s, tw, th);
2763 e_CharFont_PrintEx(gMenuFont, X-16-a-tw, 240-72-4, s, TEAMCOLOR[TEAM_RED]);
2765 if gGameSettings.GameMode = GM_CTF then
2766 begin
2767 s := 'TEXTURE_PLAYER_BLUEFLAG';
2768 if gFlags[FLAG_BLUE].State = FLAG_STATE_CAPTURED then
2769 s := 'TEXTURE_PLAYER_BLUEFLAG_S';
2770 if gFlags[FLAG_BLUE].State = FLAG_STATE_DROPPED then
2771 s := 'TEXTURE_PLAYER_BLUEFLAG_D';
2772 if g_Texture_Get(s, ID) then
2773 e_Draw(ID, X-16-32, 240-32-4, 0, True, False);
2774 end;
2776 s := IntToStr(gTeamStat[TEAM_BLUE].Goals);
2777 e_CharFont_GetSize(gMenuFont, s, tw, th);
2778 e_CharFont_PrintEx(gMenuFont, X-16-a-tw, 240-32-4, s, TEAMCOLOR[TEAM_BLUE]);
2779 end;
2781 if g_Texture_Get('TEXTURE_PLAYER_HUDBG', ID) then
2782 e_DrawFill(ID, X, 0, 1, (gPlayerScreenSize.Y div 256)+IfThen(gPlayerScreenSize.Y mod 256 > 0, 1, 0),
2783 0, False, False);
2785 if g_Texture_Get('TEXTURE_PLAYER_HUD', ID) then
2786 e_Draw(ID, X+2, Y, 0, True, False);
2788 if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
2789 begin
2790 if gShowStat then
2791 begin
2792 s := IntToStr(Frags);
2793 e_CharFont_GetSize(gMenuFont, s, tw, th);
2794 e_CharFont_PrintEx(gMenuFont, X-16-tw, Y, s, _RGB(255, 0, 0));
2796 s := '';
2797 p := 1;
2798 m := 0;
2799 stat := g_Player_GetStats();
2800 if stat <> nil then
2801 begin
2802 p := 1;
2804 for a := 0 to High(stat) do
2805 if stat[a].Name <> Name then
2806 begin
2807 if stat[a].Frags > m then m := stat[a].Frags;
2808 if stat[a].Frags > Frags then p := p+1;
2809 end;
2810 end;
2812 s := IntToStr(p)+' / '+IntToStr(Length(stat))+' ';
2813 if Frags >= m then s := s+'+' else s := s+'-';
2814 s := s+IntToStr(Abs(Frags-m));
2816 e_CharFont_GetSize(gMenuSmallFont, s, tw, th);
2817 e_CharFont_PrintEx(gMenuSmallFont, X-16-tw, Y+32, s, _RGB(255, 0, 0));
2818 end;
2820 if gLMSRespawn > LMS_RESPAWN_NONE then
2821 begin
2822 s := _lc[I_GAME_WARMUP];
2823 e_CharFont_GetSize(gMenuFont, s, tw, th);
2824 s := s + ': ' + IntToStr((gLMSRespawnTime - gTime) div 1000);
2825 e_CharFont_PrintEx(gMenuFont, X-64-tw, SY-32, s, _RGB(0, 255, 0));
2826 end
2827 else if gShowLives and (gGameSettings.MaxLives > 0) then
2828 begin
2829 s := IntToStr(Lives);
2830 e_CharFont_GetSize(gMenuFont, s, tw, th);
2831 e_CharFont_PrintEx(gMenuFont, X-16-tw, SY-32, s, _RGB(0, 255, 0));
2832 end;
2833 end;
2835 e_CharFont_GetSize(gMenuSmallFont, FName, tw, th);
2836 e_CharFont_PrintEx(gMenuSmallFont, X+98-(tw div 2), Y+8, FName, _RGB(255, 0, 0));
2838 if R_BERSERK in FRulez then
2839 e_Draw(gItemsTexturesID[ITEM_MEDKIT_BLACK], X+37, Y+45, 0, True, False)
2840 else
2841 e_Draw(gItemsTexturesID[ITEM_MEDKIT_LARGE], X+37, Y+45, 0, True, False);
2843 if g_Texture_Get('TEXTURE_PLAYER_ARMORHUD', ID) then
2844 e_Draw(ID, X+36, Y+77, 0, True, False);
2846 s := IntToStr(IfThen(FHealth > 0, FHealth, 0));
2847 e_CharFont_GetSize(gMenuFont, s, tw, th);
2848 e_CharFont_PrintEx(gMenuFont, X+178-tw, Y+40, s, _RGB(255, 0, 0));
2850 s := IntToStr(FArmor);
2851 e_CharFont_GetSize(gMenuFont, s, tw, th);
2852 e_CharFont_PrintEx(gMenuFont, X+178-tw, Y+68, s, _RGB(255, 0, 0));
2854 s := IntToStr(GetAmmoByWeapon(FCurrWeap));
2856 case FCurrWeap of
2857 WEAPON_KASTET:
2858 begin
2859 s := '--';
2860 ID := gItemsTexturesID[ITEM_WEAPON_KASTET];
2861 end;
2862 WEAPON_SAW:
2863 begin
2864 s := '--';
2865 ID := gItemsTexturesID[ITEM_WEAPON_SAW];
2866 end;
2867 WEAPON_PISTOL: ID := gItemsTexturesID[ITEM_WEAPON_PISTOL];
2868 WEAPON_CHAINGUN: ID := gItemsTexturesID[ITEM_WEAPON_CHAINGUN];
2869 WEAPON_SHOTGUN1: ID := gItemsTexturesID[ITEM_WEAPON_SHOTGUN1];
2870 WEAPON_SHOTGUN2: ID := gItemsTexturesID[ITEM_WEAPON_SHOTGUN2];
2871 WEAPON_SUPERPULEMET: ID := gItemsTexturesID[ITEM_WEAPON_SUPERPULEMET];
2872 WEAPON_ROCKETLAUNCHER: ID := gItemsTexturesID[ITEM_WEAPON_ROCKETLAUNCHER];
2873 WEAPON_PLASMA: ID := gItemsTexturesID[ITEM_WEAPON_PLASMA];
2874 WEAPON_BFG: ID := gItemsTexturesID[ITEM_WEAPON_BFG];
2875 WEAPON_FLAMETHROWER: ID := gItemsTexturesID[ITEM_WEAPON_FLAMETHROWER];
2876 end;
2878 e_CharFont_GetSize(gMenuFont, s, tw, th);
2879 e_CharFont_PrintEx(gMenuFont, X+178-tw, Y+158, s, _RGB(255, 0, 0));
2880 e_Draw(ID, X+20, Y+160, 0, True, False);
2882 if R_KEY_RED in FRulez then
2883 e_Draw(gItemsTexturesID[ITEM_KEY_RED], X+78, Y+214, 0, True, False);
2885 if R_KEY_GREEN in FRulez then
2886 e_Draw(gItemsTexturesID[ITEM_KEY_GREEN], X+95, Y+214, 0, True, False);
2888 if R_KEY_BLUE in FRulez then
2889 e_Draw(gItemsTexturesID[ITEM_KEY_BLUE], X+112, Y+214, 0, True, False);
2891 if FJetFuel > 0 then
2892 begin
2893 if g_Texture_Get('TEXTURE_PLAYER_HUDAIR', ID) then
2894 e_Draw(ID, X+2, Y+116, 0, True, False);
2895 if g_Texture_Get('TEXTURE_PLAYER_HUDJET', ID) then
2896 e_Draw(ID, X+2, Y+126, 0, True, False);
2897 e_DrawLine(4, X+16, Y+122, X+16+Trunc(168*IfThen(FAir > 0, FAir, 0)/AIR_MAX), Y+122, 0, 0, 196);
2898 e_DrawLine(4, X+16, Y+132, X+16+Trunc(168*FJetFuel/JET_MAX), Y+132, 208, 0, 0);
2899 end
2900 else
2901 begin
2902 if g_Texture_Get('TEXTURE_PLAYER_HUDAIR', ID) then
2903 e_Draw(ID, X+2, Y+124, 0, True, False);
2904 e_DrawLine(4, X+16, Y+130, X+16+Trunc(168*IfThen(FAir > 0, FAir, 0)/AIR_MAX), Y+130, 0, 0, 196);
2905 end;
2907 if gShowPing and g_Game_IsClient then
2908 begin
2909 s := _lc[I_GAME_PING_HUD] + IntToStr(NetPeer.lastRoundTripTime) + _lc[I_NET_SLIST_PING_MS];
2910 e_TextureFontPrint(X + 4, Y + 242, s, gStdFont);
2911 Y := Y + 16;
2912 end;
2914 if FSpectator then
2915 begin
2916 e_TextureFontPrint(X + 4, Y + 242, _lc[I_PLAYER_SPECT], gStdFont);
2917 e_TextureFontPrint(X + 4, Y + 258, _lc[I_PLAYER_SPECT2], gStdFont);
2918 e_TextureFontPrint(X + 4, Y + 274, _lc[I_PLAYER_SPECT1], gStdFont);
2919 if FNoRespawn then
2920 begin
2921 e_TextureFontGetSize(gStdFont, cw, ch);
2922 s := _lc[I_PLAYER_SPECT4];
2923 e_TextureFontPrintEx(gScreenWidth div 2 - cw*(Length(s) div 2),
2924 gScreenHeight-4-ch, s, gStdFont, 255, 255, 255, 1, True);
2925 e_TextureFontPrint(X + 4, Y + 290, _lc[I_PLAYER_SPECT1S], gStdFont);
2926 end;
2928 end;
2929 end;
2931 procedure TPlayer.DrawRulez();
2932 var
2933 dr: Boolean;
2934 begin
2935 // Ïðè âçÿòèè íåóÿçâèìîñòè ðèñóåòñÿ èíâåðñèîííûé áåëûé ôîí
2936 if (FMegaRulez[MR_INVUL] >= gTime) and (FSpawnInvul < gTime) then
2937 begin
2938 if (FMegaRulez[MR_INVUL]-gTime) <= 2100 then
2939 dr := not Odd((FMegaRulez[MR_INVUL]-gTime) div 300)
2940 else
2941 dr := True;
2943 if dr then
2944 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1,
2945 191, 191, 191, 0, TBlending.Invert);
2946 end;
2948 // Ïðè âçÿòèè çàùèòíîãî êîñòþìà ðèñóåòñÿ çåëåíîâàòûé ôîí
2949 if FMegaRulez[MR_SUIT] >= gTime then
2950 begin
2951 if (FMegaRulez[MR_SUIT]-gTime) <= 2100 then
2952 dr := not Odd((FMegaRulez[MR_SUIT]-gTime) div 300)
2953 else
2954 dr := True;
2956 if dr then
2957 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1,
2958 0, 96, 0, 200, TBlending.None);
2959 end;
2961 // Ïðè âçÿòèè áåðñåðêà ðèñóåòñÿ êðàñíîâàòûé ôîí
2962 if (FBerserk >= 0) and (LongWord(FBerserk) >= gTime) and (gFlash = 2) then
2963 begin
2964 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1,
2965 255, 0, 0, 200, TBlending.None);
2966 end;
2967 end;
2969 procedure TPlayer.DrawPain();
2970 var
2971 a, h: Integer;
2972 begin
2973 if FPain = 0 then Exit;
2975 a := FPain;
2977 if a < 15 then h := 0
2978 else if a < 35 then h := 1
2979 else if a < 55 then h := 2
2980 else if a < 75 then h := 3
2981 else if a < 95 then h := 4
2982 else h := 5;
2984 //if a > 255 then a := 255;
2986 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, 255, 0, 0, 255-h*50);
2987 //e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, 255-min(128, a), 255-a, 255-a, 0, B_FILTER);
2988 end;
2990 procedure TPlayer.DrawPickup();
2991 var
2992 a, h: Integer;
2993 begin
2994 if FPickup = 0 then Exit;
2996 a := FPickup;
2998 if a < 15 then h := 1
2999 else if a < 35 then h := 2
3000 else if a < 55 then h := 3
3001 else if a < 75 then h := 4
3002 else h := 5;
3004 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, 150, 200, 150, 255-h*50);
3005 end;
3007 procedure TPlayer.DoPunch();
3008 var
3009 id: DWORD;
3010 st: String;
3011 begin
3012 if FPunchAnim <> nil then begin
3013 FPunchAnim.reset();
3014 FPunchAnim.Free;
3015 FPunchAnim := nil;
3016 end;
3017 st := 'FRAMES_PUNCH';
3018 if R_BERSERK in FRulez then
3019 st := st + '_BERSERK';
3020 if FKeys[KEY_UP].Pressed then
3021 st := st + '_UP'
3022 else if FKeys[KEY_DOWN].Pressed then
3023 st := st + '_DN';
3024 g_Frames_Get(id, st);
3025 FPunchAnim := TAnimation.Create(id, False, 1);
3026 end;
3028 procedure TPlayer.Fire();
3029 var
3030 f, DidFire: Boolean;
3031 wx, wy, xd, yd: Integer;
3032 locobj: TObj;
3033 begin
3034 if g_Game_IsClient then Exit;
3035 // FBFGFireCounter - âðåìÿ ïåðåä âûñòðåëîì (äëÿ BFG)
3036 // FReloading - âðåìÿ ïîñëå âûñòðåëà (äëÿ âñåãî)
3038 if FSpectator then
3039 begin
3040 Respawn(False);
3041 Exit;
3042 end;
3044 if FReloading[FCurrWeap] <> 0 then Exit;
3046 DidFire := False;
3048 f := False;
3049 wx := FObj.X+WEAPONPOINT[FDirection].X;
3050 wy := FObj.Y+WEAPONPOINT[FDirection].Y;
3051 xd := wx+IfThen(FDirection = TDirection.D_LEFT, -30, 30);
3052 yd := wy+firediry();
3054 case FCurrWeap of
3055 WEAPON_KASTET:
3056 begin
3057 DoPunch();
3058 if R_BERSERK in FRulez then
3059 begin
3060 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
3061 locobj.X := FObj.X+FObj.Rect.X;
3062 locobj.Y := FObj.Y+FObj.Rect.Y;
3063 locobj.rect.X := 0;
3064 locobj.rect.Y := 0;
3065 locobj.rect.Width := 39;
3066 locobj.rect.Height := 52;
3067 locobj.Vel.X := (xd-wx) div 2;
3068 locobj.Vel.Y := (yd-wy) div 2;
3069 locobj.Accel.X := xd-wx;
3070 locobj.Accel.y := yd-wy;
3072 if g_Weapon_Hit(@locobj, 50, FUID, HIT_SOME) <> 0 then
3073 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj.X, FObj.Y)
3074 else
3075 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj.X, FObj.Y);
3077 if (gFlash = 1) and (FPain < 50) then FPain := min(FPain + 25, 50);
3078 end
3079 else
3080 begin
3081 g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 3, FUID);
3082 end;
3084 DidFire := True;
3085 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3086 end;
3088 WEAPON_SAW:
3089 begin
3090 if g_Weapon_chainsaw(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
3091 IfThen(gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF], 9, 3), FUID) <> 0 then
3092 begin
3093 FSawSoundSelect.Stop();
3094 FSawSound.Stop();
3095 FSawSoundHit.PlayAt(FObj.X, FObj.Y);
3096 end
3097 else if not FSawSoundHit.IsPlaying() then
3098 begin
3099 FSawSoundSelect.Stop();
3100 FSawSound.PlayAt(FObj.X, FObj.Y);
3101 end;
3103 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3104 DidFire := True;
3105 f := True;
3106 end;
3108 WEAPON_PISTOL:
3109 if FAmmo[A_BULLETS] > 0 then
3110 begin
3111 g_Weapon_pistol(wx, wy, xd, yd, FUID);
3112 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3113 Dec(FAmmo[A_BULLETS]);
3114 FFireAngle := FAngle;
3115 f := True;
3116 DidFire := True;
3117 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
3118 GameVelX, GameVelY-2, SHELL_BULLET);
3119 end;
3121 WEAPON_SHOTGUN1:
3122 if FAmmo[A_SHELLS] > 0 then
3123 begin
3124 g_Weapon_shotgun(wx, wy, xd, yd, FUID);
3125 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', wx, wy);
3126 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3127 Dec(FAmmo[A_SHELLS]);
3128 FFireAngle := FAngle;
3129 f := True;
3130 DidFire := True;
3131 FShellTimer := 10;
3132 FShellType := SHELL_SHELL;
3133 end;
3135 WEAPON_SHOTGUN2:
3136 if FAmmo[A_SHELLS] >= 2 then
3137 begin
3138 g_Weapon_dshotgun(wx, wy, xd, yd, FUID);
3139 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3140 Dec(FAmmo[A_SHELLS], 2);
3141 FFireAngle := FAngle;
3142 f := True;
3143 DidFire := True;
3144 FShellTimer := 13;
3145 FShellType := SHELL_DBLSHELL;
3146 end;
3148 WEAPON_CHAINGUN:
3149 if FAmmo[A_BULLETS] > 0 then
3150 begin
3151 g_Weapon_mgun(wx, wy, xd, yd, FUID);
3152 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', wx, wy);
3153 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3154 Dec(FAmmo[A_BULLETS]);
3155 FFireAngle := FAngle;
3156 f := True;
3157 DidFire := True;
3158 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
3159 GameVelX, GameVelY-2, SHELL_BULLET);
3160 end;
3162 WEAPON_ROCKETLAUNCHER:
3163 if FAmmo[A_ROCKETS] > 0 then
3164 begin
3165 g_Weapon_rocket(wx, wy, xd, yd, FUID);
3166 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3167 Dec(FAmmo[A_ROCKETS]);
3168 FFireAngle := FAngle;
3169 f := True;
3170 DidFire := True;
3171 end;
3173 WEAPON_PLASMA:
3174 if FAmmo[A_CELLS] > 0 then
3175 begin
3176 g_Weapon_plasma(wx, wy, xd, yd, FUID);
3177 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3178 Dec(FAmmo[A_CELLS]);
3179 FFireAngle := FAngle;
3180 f := True;
3181 DidFire := True;
3182 end;
3184 WEAPON_BFG:
3185 if (FAmmo[A_CELLS] >= 40) and (FBFGFireCounter = -1) then
3186 begin
3187 FBFGFireCounter := 17;
3188 if not FNoReload then
3189 g_Sound_PlayExAt('SOUND_WEAPON_STARTFIREBFG', FObj.X, FObj.Y);
3190 Dec(FAmmo[A_CELLS], 40);
3191 DidFire := True;
3192 end;
3194 WEAPON_SUPERPULEMET:
3195 if FAmmo[A_SHELLS] > 0 then
3196 begin
3197 g_Weapon_shotgun(wx, wy, xd, yd, FUID);
3198 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx, wy);
3199 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3200 Dec(FAmmo[A_SHELLS]);
3201 FFireAngle := FAngle;
3202 f := True;
3203 DidFire := True;
3204 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
3205 GameVelX, GameVelY-2, SHELL_SHELL);
3206 end;
3208 WEAPON_FLAMETHROWER:
3209 if FAmmo[A_FUEL] > 0 then
3210 begin
3211 g_Weapon_flame(wx, wy, xd, yd, FUID);
3212 FlamerOn;
3213 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3214 Dec(FAmmo[A_FUEL]);
3215 FFireAngle := FAngle;
3216 f := True;
3217 DidFire := True;
3218 end
3219 else
3220 begin
3221 FlamerOff;
3222 if g_Game_IsNet and g_Game_IsServer then MH_SEND_PlayerStats(FUID);
3223 end;
3224 end;
3226 if g_Game_IsNet then
3227 begin
3228 if DidFire then
3229 begin
3230 if FCurrWeap <> WEAPON_BFG then
3231 MH_SEND_PlayerFire(FUID, FCurrWeap, wx, wy, xd, yd, LastShotID)
3232 else
3233 if not FNoReload then
3234 MH_SEND_Sound(FObj.X, FObj.Y, 'SOUND_WEAPON_STARTFIREBFG');
3235 end;
3237 MH_SEND_PlayerStats(FUID);
3238 end;
3240 if not f then Exit;
3242 if (FAngle = 0) or (FAngle = 180) then SetAction(A_ATTACK)
3243 else if (FAngle = ANGLE_LEFTDOWN) or (FAngle = ANGLE_RIGHTDOWN) then SetAction(A_ATTACKDOWN)
3244 else if (FAngle = ANGLE_LEFTUP) or (FAngle = ANGLE_RIGHTUP) then SetAction(A_ATTACKUP);
3245 end;
3247 function TPlayer.GetAmmoByWeapon(Weapon: Byte): Word;
3248 begin
3249 case Weapon of
3250 WEAPON_PISTOL, WEAPON_CHAINGUN: Result := FAmmo[A_BULLETS];
3251 WEAPON_SHOTGUN1, WEAPON_SHOTGUN2, WEAPON_SUPERPULEMET: Result := FAmmo[A_SHELLS];
3252 WEAPON_ROCKETLAUNCHER: Result := FAmmo[A_ROCKETS];
3253 WEAPON_PLASMA, WEAPON_BFG: Result := FAmmo[A_CELLS];
3254 WEAPON_FLAMETHROWER: Result := FAmmo[A_FUEL];
3255 else Result := 0;
3256 end;
3257 end;
3259 function TPlayer.HeadInLiquid(XInc, YInc: Integer): Boolean;
3260 begin
3261 Result := g_Map_CollidePanel(FObj.X+PLAYER_HEADRECT.X+XInc, FObj.Y+PLAYER_HEADRECT.Y+YInc,
3262 PLAYER_HEADRECT.Width, PLAYER_HEADRECT.Height,
3263 PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, True);
3264 end;
3266 procedure TPlayer.FlamerOn;
3267 begin
3268 FFlameSoundOff.Stop();
3269 FFlameSoundOff.SetPosition(0);
3270 if FFlaming then
3271 begin
3272 if (not FFlameSoundOn.IsPlaying()) and (not FFlameSoundWork.IsPlaying()) then
3273 FFlameSoundWork.PlayAt(FObj.X, FObj.Y);
3274 end
3275 else
3276 begin
3277 FFlameSoundOn.PlayAt(FObj.X, FObj.Y);
3278 FFlaming := True;
3279 end;
3280 end;
3282 procedure TPlayer.FlamerOff;
3283 begin
3284 if FFlaming then
3285 begin
3286 FFlameSoundOn.Stop();
3287 FFlameSoundOn.SetPosition(0);
3288 FFlameSoundWork.Stop();
3289 FFlameSoundWork.SetPosition(0);
3290 FFlameSoundOff.PlayAt(FObj.X, FObj.Y);
3291 FFlaming := False;
3292 end;
3293 end;
3295 procedure TPlayer.JetpackOn;
3296 begin
3297 FJetSoundFly.Stop;
3298 FJetSoundOff.Stop;
3299 FJetSoundOn.SetPosition(0);
3300 FJetSoundOn.PlayAt(FObj.X, FObj.Y);
3301 FlySmoke(8);
3302 end;
3304 procedure TPlayer.JetpackOff;
3305 begin
3306 FJetSoundFly.Stop;
3307 FJetSoundOn.Stop;
3308 FJetSoundOff.SetPosition(0);
3309 FJetSoundOff.PlayAt(FObj.X, FObj.Y);
3310 end;
3312 procedure TPlayer.CatchFire(Attacker: Word; Timeout: Integer = PLAYER_BURN_TIME);
3313 begin
3314 if Timeout <= 0 then
3315 exit;
3316 if (FMegaRulez[MR_SUIT] > gTime) or (FMegaRulez[MR_INVUL] > gTime) then
3317 exit; // Íå çàãîðàåìñÿ êîãäà åñòü çàùèòà
3318 if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2) then
3319 exit; // Íå ïîäãîðàåì â âîäå íà âñÿêèé ñëó÷àé
3320 if FFireTime <= 0 then
3321 g_Sound_PlayExAt('SOUND_IGNITE', FObj.X, FObj.Y);
3322 FFireTime := Timeout;
3323 FFireAttacker := Attacker;
3324 if g_Game_IsNet and g_Game_IsServer then
3325 MH_SEND_PlayerStats(FUID);
3326 end;
3328 procedure TPlayer.Jump();
3329 begin
3330 if gFly or FJetpack then
3331 begin
3332 // Ïîëåò (÷èò-êîä èëè äæåòïàê):
3333 if FObj.Vel.Y > -VEL_FLY then
3334 FObj.Vel.Y := FObj.Vel.Y - 3;
3335 if FJetpack then
3336 begin
3337 if FJetFuel > 0 then
3338 Dec(FJetFuel);
3339 if (FJetFuel < 1) and g_Game_IsServer then
3340 begin
3341 FJetpack := False;
3342 JetpackOff;
3343 if g_Game_IsNet then
3344 MH_SEND_PlayerStats(FUID);
3345 end;
3346 end;
3347 Exit;
3348 end;
3350 // Íå âêëþ÷àòü äæåòïàê â ðåæèìå ïðîõîæäåíèÿ ñêâîçü ñòåíû
3351 if FGhost then
3352 FCanJetpack := False;
3354 // Ïðûãàåì èëè âñïëûâàåì:
3355 if (CollideLevel(0, 1) or
3356 g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y+36, PLAYER_RECT.Width,
3357 PLAYER_RECT.Height-33, PANEL_STEP, False)
3358 ) and (FObj.Accel.Y = 0) then // Íå ïðûãàòü, åñëè åñòü âåðòèêàëüíîå óñêîðåíèå
3359 begin
3360 FObj.Vel.Y := -VEL_JUMP;
3361 FCanJetpack := False;
3362 end
3363 else
3364 begin
3365 if BodyInLiquid(0, 0) then
3366 FObj.Vel.Y := -VEL_SW
3367 else if (FJetFuel > 0) and FCanJetpack and
3368 g_Game_IsServer and (not g_Obj_CollideLiquid(@FObj, 0, 0)) then
3369 begin
3370 FJetpack := True;
3371 JetpackOn;
3372 if g_Game_IsNet then
3373 MH_SEND_PlayerStats(FUID);
3374 end;
3375 end;
3376 end;
3378 procedure TPlayer.Kill(KillType: Byte; SpawnerUID: Word; t: Byte);
3379 var
3380 a, i, k, ab, ar: Byte;
3381 s: String;
3382 mon: TMonster;
3383 plr: TPlayer;
3384 srv, netsrv: Boolean;
3385 DoFrags: Boolean;
3386 OldLR: Byte;
3387 KP: TPlayer;
3388 it: PItem;
3390 procedure PushItem(t: Byte);
3391 var
3392 id: DWORD;
3393 begin
3394 id := g_Items_Create(FObj.X, FObj.Y, t, True, False);
3395 it := g_Items_ByIdx(id);
3396 if KillType = K_EXTRAHARDKILL then // -7..+7; -8..0
3397 begin
3398 g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-7+Random(15),
3399 (FObj.Vel.Y div 2)-Random(9));
3400 it.positionChanged(); // this updates spatial accelerators
3401 end
3402 else
3403 begin
3404 if KillType = K_HARDKILL then // -5..+5; -5..0
3405 begin
3406 g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-5+Random(11),
3407 (FObj.Vel.Y div 2)-Random(6));
3408 end
3409 else // -3..+3; -3..0
3410 begin
3411 g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-3+Random(7),
3412 (FObj.Vel.Y div 2)-Random(4));
3413 end;
3414 it.positionChanged(); // this updates spatial accelerators
3415 end;
3417 if g_Game_IsNet and g_Game_IsServer then
3418 MH_SEND_ItemSpawn(True, id);
3419 end;
3421 begin
3422 DoFrags := (gGameSettings.MaxLives = 0) or (gGameSettings.GameMode = GM_COOP);
3423 Srv := g_Game_IsServer;
3424 Netsrv := g_Game_IsServer and g_Game_IsNet;
3425 if Srv then FDeath := FDeath + 1;
3426 if FAlive then
3427 begin
3428 if FGhost then
3429 FGhost := False;
3430 if not FPhysics then
3431 FPhysics := True;
3432 FAlive := False;
3433 end;
3434 FShellTimer := -1;
3436 if (gGameSettings.MaxLives > 0) and Srv and (gLMSRespawn = LMS_RESPAWN_NONE) then
3437 begin
3438 if FLives > 0 then FLives := FLives - 1;
3439 if FLives = 0 then FNoRespawn := True;
3440 end;
3442 // Íîìåð òèïà ñìåðòè:
3443 a := 1;
3444 case KillType of
3445 K_SIMPLEKILL: a := 1;
3446 K_HARDKILL: a := 2;
3447 K_EXTRAHARDKILL: a := 3;
3448 K_FALLKILL: a := 4;
3449 end;
3451 // Çâóê ñìåðòè:
3452 if not FModel.PlaySound(MODELSOUND_DIE, a, FObj.X, FObj.Y) then
3453 for i := 1 to 3 do
3454 if FModel.PlaySound(MODELSOUND_DIE, i, FObj.X, FObj.Y) then
3455 Break;
3457 // Âðåìÿ ðåñïàóíà:
3458 if Srv then
3459 case KillType of
3460 K_SIMPLEKILL:
3461 FTime[T_RESPAWN] := gTime + TIME_RESPAWN1;
3462 K_HARDKILL:
3463 FTime[T_RESPAWN] := gTime + TIME_RESPAWN2;
3464 K_EXTRAHARDKILL, K_FALLKILL:
3465 FTime[T_RESPAWN] := gTime + TIME_RESPAWN3;
3466 end;
3468 // Ïåðåêëþ÷àåì ñîñòîÿíèå:
3469 case KillType of
3470 K_SIMPLEKILL:
3471 SetAction(A_DIE1);
3472 K_HARDKILL, K_EXTRAHARDKILL:
3473 SetAction(A_DIE2);
3474 end;
3476 // Ðåàêöèÿ ìîíñòðîâ íà ñìåðòü èãðîêà:
3477 if (KillType <> K_FALLKILL) and (Srv) then
3478 g_Monsters_killedp();
3480 if SpawnerUID = FUID then
3481 begin // Ñàìîóáèëñÿ
3482 if Srv and (DoFrags or (gGameSettings.GameMode = GM_TDM)) then
3483 begin
3484 Dec(FFrags);
3485 FLastFrag := 0;
3486 end;
3487 g_Console_Add(Format(_lc[I_PLAYER_KILL_SELF], [FName]), True);
3488 end
3489 else
3490 if g_GetUIDType(SpawnerUID) = UID_PLAYER then
3491 begin // Óáèò äðóãèì èãðîêîì
3492 KP := g_Player_Get(SpawnerUID);
3493 if (KP <> nil) and Srv then
3494 begin
3495 if (DoFrags or (gGameSettings.GameMode = GM_TDM)) then
3496 if SameTeam(FUID, SpawnerUID) then
3497 begin
3498 Dec(KP.FFrags);
3499 KP.FLastFrag := 0;
3500 end else
3501 begin
3502 Inc(KP.FFrags);
3503 KP.FragCombo();
3504 end;
3506 if (gGameSettings.GameMode = GM_TDM) and DoFrags then
3507 Inc(gTeamStat[KP.Team].Goals,
3508 IfThen(SameTeam(FUID, SpawnerUID), -1, 1));
3510 if netsrv then MH_SEND_PlayerStats(SpawnerUID);
3511 end;
3513 plr := g_Player_Get(SpawnerUID);
3514 if plr = nil then
3515 s := '?'
3516 else
3517 s := plr.FName;
3519 case KillType of
3520 K_HARDKILL:
3521 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_2],
3522 [FName, s]),
3523 gShowKillMsg);
3524 K_EXTRAHARDKILL:
3525 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_1],
3526 [FName, s]),
3527 gShowKillMsg);
3528 else
3529 g_Console_Add(Format(_lc[I_PLAYER_KILL],
3530 [FName, s]),
3531 gShowKillMsg);
3532 end;
3533 end
3534 else if g_GetUIDType(SpawnerUID) = UID_MONSTER then
3535 begin // Óáèò ìîíñòðîì
3536 mon := g_Monsters_ByUID(SpawnerUID);
3537 if mon = nil then
3538 s := '?'
3539 else
3540 s := g_Mons_GetKilledByTypeId(mon.MonsterType);
3542 case KillType of
3543 K_HARDKILL:
3544 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_2],
3545 [FName, s]),
3546 gShowKillMsg);
3547 K_EXTRAHARDKILL:
3548 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_1],
3549 [FName, s]),
3550 gShowKillMsg);
3551 else
3552 g_Console_Add(Format(_lc[I_PLAYER_KILL],
3553 [FName, s]),
3554 gShowKillMsg);
3555 end;
3556 end
3557 else // Îñîáûå òèïû ñìåðòè
3558 case t of
3559 HIT_DISCON: ;
3560 HIT_SELF: g_Console_Add(Format(_lc[I_PLAYER_KILL_SELF], [FName]), True);
3561 HIT_FALL: g_Console_Add(Format(_lc[I_PLAYER_KILL_FALL], [FName]), True);
3562 HIT_WATER: g_Console_Add(Format(_lc[I_PLAYER_KILL_WATER], [FName]), True);
3563 HIT_ACID: g_Console_Add(Format(_lc[I_PLAYER_KILL_ACID], [FName]), True);
3564 HIT_TRAP: g_Console_Add(Format(_lc[I_PLAYER_KILL_TRAP], [FName]), True);
3565 else g_Console_Add(Format(_lc[I_PLAYER_DIED], [FName]), True);
3566 end;
3568 if Srv then
3569 begin
3570 // Âûáðîñ îðóæèÿ:
3571 for a := WP_FIRST to WP_LAST do
3572 if FWeapon[a] then
3573 begin
3574 case a of
3575 WEAPON_SAW: i := ITEM_WEAPON_SAW;
3576 WEAPON_SHOTGUN1: i := ITEM_WEAPON_SHOTGUN1;
3577 WEAPON_SHOTGUN2: i := ITEM_WEAPON_SHOTGUN2;
3578 WEAPON_CHAINGUN: i := ITEM_WEAPON_CHAINGUN;
3579 WEAPON_ROCKETLAUNCHER: i := ITEM_WEAPON_ROCKETLAUNCHER;
3580 WEAPON_PLASMA: i := ITEM_WEAPON_PLASMA;
3581 WEAPON_BFG: i := ITEM_WEAPON_BFG;
3582 WEAPON_SUPERPULEMET: i := ITEM_WEAPON_SUPERPULEMET;
3583 WEAPON_FLAMETHROWER: i := ITEM_WEAPON_FLAMETHROWER;
3584 else i := 0;
3585 end;
3587 if i <> 0 then
3588 PushItem(i);
3589 end;
3591 // Âûáðîñ ðþêçàêà:
3592 if R_ITEM_BACKPACK in FRulez then
3593 PushItem(ITEM_AMMO_BACKPACK);
3595 // Âûáðîñ ðàêåòíîãî ðàíöà:
3596 if FJetFuel > 0 then
3597 PushItem(ITEM_JETPACK);
3599 // Âûáðîñ êëþ÷åé:
3600 if (not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) or
3601 (not LongBool(gGameSettings.Options and GAME_OPTION_DMKEYS)) then
3602 begin
3603 if R_KEY_RED in FRulez then
3604 PushItem(ITEM_KEY_RED);
3606 if R_KEY_GREEN in FRulez then
3607 PushItem(ITEM_KEY_GREEN);
3609 if R_KEY_BLUE in FRulez then
3610 PushItem(ITEM_KEY_BLUE);
3611 end;
3613 // Âûáðîñ ôëàãà:
3614 DropFlag(KillType = K_FALLKILL);
3615 end;
3617 FCorpse := g_Player_CreateCorpse(Self);
3619 if Srv and (gGameSettings.MaxLives > 0) and FNoRespawn and
3620 (gLMSRespawn = LMS_RESPAWN_NONE) then
3621 begin
3622 a := 0;
3623 k := 0;
3624 ar := 0;
3625 ab := 0;
3626 for i := Low(gPlayers) to High(gPlayers) do
3627 begin
3628 if gPlayers[i] = nil then continue;
3629 if (not gPlayers[i].FNoRespawn) and (not gPlayers[i].FSpectator) then
3630 begin
3631 Inc(a);
3632 if gPlayers[i].FTeam = TEAM_RED then Inc(ar)
3633 else if gPlayers[i].FTeam = TEAM_BLUE then Inc(ab);
3634 k := i;
3635 end;
3636 end;
3638 OldLR := gLMSRespawn;
3639 if (gGameSettings.GameMode = GM_COOP) then
3640 begin
3641 if (a = 0) then
3642 begin
3643 // everyone is dead, restart the map
3644 g_Game_Message(_lc[I_MESSAGE_LMS_LOSE], 144);
3645 if Netsrv then
3646 MH_SEND_GameEvent(NET_EV_LMS_LOSE);
3647 gLMSRespawn := LMS_RESPAWN_FINAL;
3648 gLMSRespawnTime := gTime + 5000;
3649 end
3650 else if (a = 1) then
3651 begin
3652 if (gPlayers[k] <> nil) and not (gPlayers[k] is TBot) then
3653 if (gPlayers[k] = gPlayer1) or
3654 (gPlayers[k] = gPlayer2) then
3655 g_Console_Add('*** ' + _lc[I_MESSAGE_LMS_SURVIVOR] + ' ***', True)
3656 else if Netsrv and (gPlayers[k].FClientID >= 0) then
3657 MH_SEND_GameEvent(NET_EV_LMS_SURVIVOR, 0, 'N', gPlayers[k].FClientID);
3658 end;
3659 end
3660 else if (gGameSettings.GameMode = GM_TDM) then
3661 begin
3662 if (ab = 0) and (ar <> 0) then
3663 begin
3664 // blu team ded
3665 g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 144);
3666 if Netsrv then
3667 MH_SEND_GameEvent(NET_EV_TLMS_WIN, TEAM_RED);
3668 Inc(gTeamStat[TEAM_RED].Goals);
3669 gLMSRespawn := LMS_RESPAWN_FINAL;
3670 gLMSRespawnTime := gTime + 5000;
3671 end
3672 else if (ar = 0) and (ab <> 0) then
3673 begin
3674 // red team ded
3675 g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 144);
3676 if Netsrv then
3677 MH_SEND_GameEvent(NET_EV_TLMS_WIN, TEAM_BLUE);
3678 Inc(gTeamStat[TEAM_BLUE].Goals);
3679 gLMSRespawn := LMS_RESPAWN_FINAL;
3680 gLMSRespawnTime := gTime + 5000;
3681 end
3682 else if (ar = 0) and (ab = 0) then
3683 begin
3684 // everyone ded
3685 g_Game_Message(_lc[I_GAME_WIN_DRAW], 144);
3686 if Netsrv then
3687 MH_SEND_GameEvent(NET_EV_LMS_DRAW, 0, FName);
3688 gLMSRespawn := LMS_RESPAWN_FINAL;
3689 gLMSRespawnTime := gTime + 5000;
3690 end;
3691 end
3692 else if (gGameSettings.GameMode = GM_DM) then
3693 begin
3694 if (a = 1) then
3695 begin
3696 if gPlayers[k] <> nil then
3697 with gPlayers[k] do
3698 begin
3699 // survivor is the winner
3700 g_Game_Message(Format(_lc[I_MESSAGE_LMS_WIN], [AnsiUpperCase(FName)]), 144);
3701 if Netsrv then
3702 MH_SEND_GameEvent(NET_EV_LMS_WIN, 0, FName);
3703 Inc(FFrags);
3704 end;
3705 gLMSRespawn := LMS_RESPAWN_FINAL;
3706 gLMSRespawnTime := gTime + 5000;
3707 end
3708 else if (a = 0) then
3709 begin
3710 // everyone is dead, restart the map
3711 g_Game_Message(_lc[I_GAME_WIN_DRAW], 144);
3712 if Netsrv then
3713 MH_SEND_GameEvent(NET_EV_LMS_DRAW, 0, FName);
3714 gLMSRespawn := LMS_RESPAWN_FINAL;
3715 gLMSRespawnTime := gTime + 5000;
3716 end;
3717 end;
3718 if srv and (OldLR = LMS_RESPAWN_NONE) and (gLMSRespawn > LMS_RESPAWN_NONE) then
3719 begin
3720 if NetMode = NET_SERVER then
3721 MH_SEND_GameEvent(NET_EV_LMS_WARMUP, gLMSRespawnTime - gTime)
3722 else
3723 g_Console_Add(Format(_lc[I_MSG_WARMUP_START], [(gLMSRespawnTime - gTime) div 1000]), True);
3724 end;
3725 end;
3727 if Netsrv then
3728 begin
3729 MH_SEND_PlayerStats(FUID);
3730 MH_SEND_PlayerDeath(FUID, KillType, t, SpawnerUID);
3731 if gGameSettings.GameMode = GM_TDM then MH_SEND_GameStats;
3732 end;
3734 if srv and FNoRespawn then Spectate(True);
3735 FWantsInGame := True;
3736 end;
3738 function TPlayer.BodyInLiquid(XInc, YInc: Integer): Boolean;
3739 begin
3740 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc, PLAYER_RECT.Width,
3741 PLAYER_RECT.Height-20, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, False);
3742 end;
3744 function TPlayer.BodyInAcid(XInc, YInc: Integer): Boolean;
3745 begin
3746 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc, PLAYER_RECT.Width,
3747 PLAYER_RECT.Height-20, PANEL_ACID1 or PANEL_ACID2, False);
3748 end;
3750 procedure TPlayer.MakeBloodSimple(Count: Word);
3751 begin
3752 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)+8,
3753 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
3754 Count div 2, 3, -1, 16, (PLAYER_RECT.Height*2 div 3),
3755 FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind);
3756 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-8,
3757 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
3758 Count div 2, -3, -1, 16, (PLAYER_RECT.Height*2) div 3,
3759 FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind);
3760 end;
3762 procedure TPlayer.MakeBloodVector(Count: Word; VelX, VelY: Integer);
3763 begin
3764 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
3765 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
3766 Count, VelX, VelY, 16, (PLAYER_RECT.Height*2) div 3,
3767 FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind);
3768 end;
3770 procedure TPlayer.QueueWeaponSwitch(Weapon: Byte);
3771 begin
3772 if g_Game_IsClient then Exit;
3773 if Weapon > High(FWeapon) then Exit;
3774 FNextWeap := FNextWeap or (1 shl Weapon);
3775 end;
3777 procedure TPlayer.resetWeaponQueue ();
3778 begin
3779 FNextWeap := 0;
3780 FNextWeapDelay := 0;
3781 end;
3783 function TPlayer.hasAmmoForWeapon (weapon: Byte): Boolean;
3784 begin
3785 result := false;
3786 case weapon of
3787 WEAPON_KASTET, WEAPON_SAW: result := true;
3788 WEAPON_SHOTGUN1, WEAPON_SHOTGUN2, WEAPON_SUPERPULEMET: result := (FAmmo[A_SHELLS] > 0);
3789 WEAPON_PISTOL, WEAPON_CHAINGUN: result := (FAmmo[A_BULLETS] > 0);
3790 WEAPON_ROCKETLAUNCHER: result := (FAmmo[A_ROCKETS] > 0);
3791 WEAPON_PLASMA, WEAPON_BFG: result := (FAmmo[A_CELLS] > 0);
3792 WEAPON_FLAMETHROWER: result := (FAmmo[A_FUEL] > 0);
3793 else result := (weapon < length(FWeapon));
3794 end;
3795 end;
3797 // return 255 for "no switch"
3798 function TPlayer.getNextWeaponIndex (): Byte;
3799 var
3800 i: Word;
3801 wantThisWeapon: array[0..64] of Boolean;
3802 wwc: Integer = 0; //HACK!
3803 dir, cwi: Integer;
3804 begin
3805 result := 255; // default result: "no switch"
3806 // had weapon cycling on previous frame? remove that flag
3807 if (FNextWeap and $2000) <> 0 then
3808 begin
3809 FNextWeap := FNextWeap and $1FFF;
3810 FNextWeapDelay := 0;
3811 end;
3812 // cycling has priority
3813 if (FNextWeap and $C000) <> 0 then
3814 begin
3815 if (FNextWeap and $8000) <> 0 then
3816 dir := 1
3817 else
3818 dir := -1;
3819 FNextWeap := FNextWeap or $2000; // we need this
3820 if FNextWeapDelay > 0 then
3821 exit; // cooldown time
3822 cwi := FCurrWeap;
3823 for i := 0 to High(FWeapon) do
3824 begin
3825 cwi := (cwi+length(FWeapon)+dir) mod length(FWeapon);
3826 if FWeapon[cwi] then
3827 begin
3828 //e_WriteLog(Format(' SWITCH: cur=%d; new=%d', [FCurrWeap, cwi]), MSG_WARNING);
3829 result := Byte(cwi);
3830 FNextWeapDelay := WEAPON_DELAY;
3831 exit;
3832 end;
3833 end;
3834 resetWeaponQueue();
3835 exit;
3836 end;
3837 // no cycling
3838 for i := 0 to High(wantThisWeapon) do
3839 wantThisWeapon[i] := false;
3840 for i := 0 to High(FWeapon) do
3841 if (FNextWeap and (1 shl i)) <> 0 then
3842 begin
3843 wantThisWeapon[i] := true;
3844 Inc(wwc);
3845 end;
3846 // exclude currently selected weapon from the set
3847 wantThisWeapon[FCurrWeap] := false;
3848 // slow down alterations a little
3849 if wwc > 1 then
3850 begin
3851 //e_WriteLog(Format(' FNextWeap=%x; delay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
3852 // more than one weapon requested, assume "alteration" and check alteration delay
3853 if FNextWeapDelay > 0 then
3854 begin
3855 FNextWeap := 0;
3856 exit;
3857 end; // yeah
3858 end;
3859 // do not reset weapon queue, it will be done in `RealizeCurrentWeapon()`
3860 // but clear all counters if no weapon should be switched
3861 if wwc < 1 then
3862 begin
3863 resetWeaponQueue();
3864 exit;
3865 end;
3866 //e_WriteLog(Format('wwc=%d', [wwc]), MSG_WARNING);
3867 // try weapons in descending order
3868 for i := High(FWeapon) downto 0 do
3869 begin
3870 if wantThisWeapon[i] and FWeapon[i] and ((wwc = 1) or hasAmmoForWeapon(i)) then
3871 begin
3872 // i found her!
3873 result := Byte(i);
3874 resetWeaponQueue();
3875 FNextWeapDelay := WEAPON_DELAY * 2; // anyway, 'cause why not
3876 exit;
3877 end;
3878 end;
3879 // no suitable weapon found, so reset the queue, to avoid accidental "queuing" of weapon w/o ammo
3880 resetWeaponQueue();
3881 end;
3883 procedure TPlayer.RealizeCurrentWeapon();
3884 function switchAllowed (): Boolean;
3885 var
3886 i: Byte;
3887 begin
3888 result := false;
3889 if FBFGFireCounter <> -1 then
3890 exit;
3891 if FTime[T_SWITCH] > gTime then
3892 exit;
3893 for i := WP_FIRST to WP_LAST do
3894 if FReloading[i] > 0 then
3895 exit;
3896 result := true;
3897 end;
3899 var
3900 nw: Byte;
3901 begin
3902 //e_WriteLog(Format('***RealizeCurrentWeapon: FNextWeap=%x; FNextWeapDelay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
3903 //FNextWeap := FNextWeap and $1FFF;
3904 if FNextWeapDelay > 0 then Dec(FNextWeapDelay); // "alteration delay"
3906 if not switchAllowed then
3907 begin
3908 //HACK for weapon cycling
3909 if (FNextWeap and $E000) <> 0 then FNextWeap := 0;
3910 exit;
3911 end;
3913 nw := getNextWeaponIndex();
3914 if nw = 255 then exit; // don't reset anything here
3915 if nw > High(FWeapon) then
3916 begin
3917 // don't forget to reset queue here!
3918 //e_WriteLog(' RealizeCurrentWeapon: WUTAFUUUU', MSG_WARNING);
3919 resetWeaponQueue();
3920 exit;
3921 end;
3923 if FWeapon[nw] then
3924 begin
3925 FCurrWeap := nw;
3926 FTime[T_SWITCH] := gTime+156;
3927 if FCurrWeap = WEAPON_SAW then FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
3928 FModel.SetWeapon(FCurrWeap);
3929 if g_Game_IsNet then MH_SEND_PlayerStats(FUID);
3930 end;
3931 end;
3933 procedure TPlayer.NextWeapon();
3934 begin
3935 if g_Game_IsClient then Exit;
3936 FNextWeap := $8000;
3937 end;
3939 procedure TPlayer.PrevWeapon();
3940 begin
3941 if g_Game_IsClient then Exit;
3942 FNextWeap := $4000;
3943 end;
3945 procedure TPlayer.SetWeapon(W: Byte);
3946 begin
3947 if FCurrWeap <> W then
3948 if W = WEAPON_SAW then
3949 FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
3951 FCurrWeap := W;
3952 FModel.SetWeapon(CurrWeap);
3953 resetWeaponQueue();
3954 end;
3956 function TPlayer.PickItem(ItemType: Byte; arespawn: Boolean; var remove: Boolean): Boolean;
3958 function allowBerserkSwitching (): Boolean;
3959 begin
3960 if (FBFGFireCounter <> -1) then begin result := false; exit; end;
3961 result := true;
3962 if gBerserkAutoswitch then exit;
3963 if not conIsCheatsEnabled then exit;
3964 result := false;
3965 end;
3967 var
3968 a: Boolean;
3969 begin
3970 Result := False;
3971 if g_Game_IsClient then Exit;
3973 // a = true - ìåñòî ñïàâíà ïðåäìåòà:
3974 a := LongBool(gGameSettings.Options and GAME_OPTION_WEAPONSTAY) and arespawn;
3975 remove := not a;
3977 case ItemType of
3978 ITEM_MEDKIT_SMALL:
3979 if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
3980 begin
3981 if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 10, PLAYER_HP_SOFT);
3982 Result := True;
3983 remove := True;
3984 FFireTime := 0;
3985 if gFlash = 2 then Inc(FPickup, 5);
3986 end;
3988 ITEM_MEDKIT_LARGE:
3989 if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
3990 begin
3991 if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 25, PLAYER_HP_SOFT);
3992 Result := True;
3993 remove := True;
3994 FFireTime := 0;
3995 if gFlash = 2 then Inc(FPickup, 5);
3996 end;
3998 ITEM_ARMOR_GREEN:
3999 if FArmor < PLAYER_AP_SOFT then
4000 begin
4001 FArmor := PLAYER_AP_SOFT;
4002 Result := True;
4003 remove := True;
4004 if gFlash = 2 then Inc(FPickup, 5);
4005 end;
4007 ITEM_ARMOR_BLUE:
4008 if FArmor < PLAYER_AP_LIMIT then
4009 begin
4010 FArmor := PLAYER_AP_LIMIT;
4011 Result := True;
4012 remove := True;
4013 if gFlash = 2 then Inc(FPickup, 5);
4014 end;
4016 ITEM_SPHERE_BLUE:
4017 if (FHealth < PLAYER_HP_LIMIT) or (FFireTime > 0) then
4018 begin
4019 if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 100, PLAYER_HP_LIMIT);
4020 Result := True;
4021 remove := True;
4022 FFireTime := 0;
4023 if gFlash = 2 then Inc(FPickup, 5);
4024 end;
4026 ITEM_SPHERE_WHITE:
4027 if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) or (FFireTime > 0) then
4028 begin
4029 if FHealth < PLAYER_HP_LIMIT then
4030 FHealth := PLAYER_HP_LIMIT;
4031 if FArmor < PLAYER_AP_LIMIT then
4032 FArmor := PLAYER_AP_LIMIT;
4033 Result := True;
4034 remove := True;
4035 FFireTime := 0;
4036 if gFlash = 2 then Inc(FPickup, 5);
4037 end;
4039 ITEM_WEAPON_SAW:
4040 if (not FWeapon[WEAPON_SAW]) or ((not arespawn) and (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) then
4041 begin
4042 FWeapon[WEAPON_SAW] := True;
4043 Result := True;
4044 if gFlash = 2 then Inc(FPickup, 5);
4045 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4046 end;
4048 ITEM_WEAPON_SHOTGUN1:
4049 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SHOTGUN1] then
4050 begin
4051 // Íóæíî, ÷òîáû íå âçÿòü âñå ïóëè ñðàçó:
4052 if a and FWeapon[WEAPON_SHOTGUN1] then Exit;
4054 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
4055 FWeapon[WEAPON_SHOTGUN1] := True;
4056 Result := True;
4057 if gFlash = 2 then Inc(FPickup, 5);
4058 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4059 end;
4061 ITEM_WEAPON_SHOTGUN2:
4062 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SHOTGUN2] then
4063 begin
4064 if a and FWeapon[WEAPON_SHOTGUN2] then Exit;
4066 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
4067 FWeapon[WEAPON_SHOTGUN2] := True;
4068 Result := True;
4069 if gFlash = 2 then Inc(FPickup, 5);
4070 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4071 end;
4073 ITEM_WEAPON_CHAINGUN:
4074 if (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or not FWeapon[WEAPON_CHAINGUN] then
4075 begin
4076 if a and FWeapon[WEAPON_CHAINGUN] then Exit;
4078 IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
4079 FWeapon[WEAPON_CHAINGUN] := True;
4080 Result := True;
4081 if gFlash = 2 then Inc(FPickup, 5);
4082 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4083 end;
4085 ITEM_WEAPON_ROCKETLAUNCHER:
4086 if (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or not FWeapon[WEAPON_ROCKETLAUNCHER] then
4087 begin
4088 if a and FWeapon[WEAPON_ROCKETLAUNCHER] then Exit;
4090 IncMax(FAmmo[A_ROCKETS], 2, FMaxAmmo[A_ROCKETS]);
4091 FWeapon[WEAPON_ROCKETLAUNCHER] := True;
4092 Result := True;
4093 if gFlash = 2 then Inc(FPickup, 5);
4094 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4095 end;
4097 ITEM_WEAPON_PLASMA:
4098 if (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or not FWeapon[WEAPON_PLASMA] then
4099 begin
4100 if a and FWeapon[WEAPON_PLASMA] then Exit;
4102 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
4103 FWeapon[WEAPON_PLASMA] := True;
4104 Result := True;
4105 if gFlash = 2 then Inc(FPickup, 5);
4106 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4107 end;
4109 ITEM_WEAPON_BFG:
4110 if (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or not FWeapon[WEAPON_BFG] then
4111 begin
4112 if a and FWeapon[WEAPON_BFG] then Exit;
4114 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
4115 FWeapon[WEAPON_BFG] := True;
4116 Result := True;
4117 if gFlash = 2 then Inc(FPickup, 5);
4118 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4119 end;
4121 ITEM_WEAPON_SUPERPULEMET:
4122 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SUPERPULEMET] then
4123 begin
4124 if a and FWeapon[WEAPON_SUPERPULEMET] then Exit;
4126 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
4127 FWeapon[WEAPON_SUPERPULEMET] := True;
4128 Result := True;
4129 if gFlash = 2 then Inc(FPickup, 5);
4130 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4131 end;
4133 ITEM_WEAPON_FLAMETHROWER:
4134 if (FAmmo[A_FUEL] < FMaxAmmo[A_FUEL]) or not FWeapon[WEAPON_FLAMETHROWER] then
4135 begin
4136 if a and FWeapon[WEAPON_FLAMETHROWER] then Exit;
4138 IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
4139 FWeapon[WEAPON_FLAMETHROWER] := True;
4140 Result := True;
4141 if gFlash = 2 then Inc(FPickup, 5);
4142 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4143 end;
4145 ITEM_AMMO_BULLETS:
4146 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
4147 begin
4148 IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
4149 Result := True;
4150 remove := True;
4151 if gFlash = 2 then Inc(FPickup, 5);
4152 end;
4154 ITEM_AMMO_BULLETS_BOX:
4155 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
4156 begin
4157 IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
4158 Result := True;
4159 remove := True;
4160 if gFlash = 2 then Inc(FPickup, 5);
4161 end;
4163 ITEM_AMMO_SHELLS:
4164 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
4165 begin
4166 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
4167 Result := True;
4168 remove := True;
4169 if gFlash = 2 then Inc(FPickup, 5);
4170 end;
4172 ITEM_AMMO_SHELLS_BOX:
4173 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
4174 begin
4175 IncMax(FAmmo[A_SHELLS], 25, FMaxAmmo[A_SHELLS]);
4176 Result := True;
4177 remove := True;
4178 if gFlash = 2 then Inc(FPickup, 5);
4179 end;
4181 ITEM_AMMO_ROCKET:
4182 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
4183 begin
4184 IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
4185 Result := True;
4186 remove := True;
4187 if gFlash = 2 then Inc(FPickup, 5);
4188 end;
4190 ITEM_AMMO_ROCKET_BOX:
4191 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
4192 begin
4193 IncMax(FAmmo[A_ROCKETS], 5, FMaxAmmo[A_ROCKETS]);
4194 Result := True;
4195 remove := True;
4196 if gFlash = 2 then Inc(FPickup, 5);
4197 end;
4199 ITEM_AMMO_CELL:
4200 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
4201 begin
4202 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
4203 Result := True;
4204 remove := True;
4205 if gFlash = 2 then Inc(FPickup, 5);
4206 end;
4208 ITEM_AMMO_CELL_BIG:
4209 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
4210 begin
4211 IncMax(FAmmo[A_CELLS], 100, FMaxAmmo[A_CELLS]);
4212 Result := True;
4213 remove := True;
4214 if gFlash = 2 then Inc(FPickup, 5);
4215 end;
4217 ITEM_AMMO_FUELCAN:
4218 if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then
4219 begin
4220 IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
4221 Result := True;
4222 remove := True;
4223 if gFlash = 2 then Inc(FPickup, 5);
4224 end;
4226 ITEM_AMMO_BACKPACK:
4227 if not(R_ITEM_BACKPACK in FRulez) or
4228 (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or
4229 (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or
4230 (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or
4231 (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or
4232 (FAmmo[A_FUEL] < FMaxAmmo[A_FUEL]) then
4233 begin
4234 FMaxAmmo[A_BULLETS] := AmmoLimits[1, A_BULLETS];
4235 FMaxAmmo[A_SHELLS] := AmmoLimits[1, A_SHELLS];
4236 FMaxAmmo[A_ROCKETS] := AmmoLimits[1, A_ROCKETS];
4237 FMaxAmmo[A_CELLS] := AmmoLimits[1, A_CELLS];
4238 FMaxAmmo[A_FUEL] := AmmoLimits[1, A_FUEL];
4240 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
4241 IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
4242 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
4243 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
4244 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
4245 IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
4246 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
4247 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
4248 if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then
4249 IncMax(FAmmo[A_FUEL], 50, FMaxAmmo[A_FUEL]);
4251 FRulez := FRulez + [R_ITEM_BACKPACK];
4252 Result := True;
4253 remove := True;
4254 if gFlash = 2 then Inc(FPickup, 5);
4255 end;
4257 ITEM_KEY_RED:
4258 if not(R_KEY_RED in FRulez) then
4259 begin
4260 Include(FRulez, R_KEY_RED);
4261 Result := True;
4262 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
4263 if gFlash = 2 then Inc(FPickup, 5);
4264 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
4265 end;
4267 ITEM_KEY_GREEN:
4268 if not(R_KEY_GREEN in FRulez) then
4269 begin
4270 Include(FRulez, R_KEY_GREEN);
4271 Result := True;
4272 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
4273 if gFlash = 2 then Inc(FPickup, 5);
4274 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
4275 end;
4277 ITEM_KEY_BLUE:
4278 if not(R_KEY_BLUE in FRulez) then
4279 begin
4280 Include(FRulez, R_KEY_BLUE);
4281 Result := True;
4282 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
4283 if gFlash = 2 then Inc(FPickup, 5);
4284 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
4285 end;
4287 ITEM_SUIT:
4288 if FMegaRulez[MR_SUIT] < gTime+PLAYER_SUIT_TIME then
4289 begin
4290 FMegaRulez[MR_SUIT] := gTime+PLAYER_SUIT_TIME;
4291 Result := True;
4292 remove := True;
4293 FFireTime := 0;
4294 if gFlash = 2 then Inc(FPickup, 5);
4295 end;
4297 ITEM_OXYGEN:
4298 if FAir < AIR_MAX then
4299 begin
4300 FAir := AIR_MAX;
4301 Result := True;
4302 remove := True;
4303 if gFlash = 2 then Inc(FPickup, 5);
4304 end;
4306 ITEM_MEDKIT_BLACK:
4307 begin
4308 if not (R_BERSERK in FRulez) then
4309 begin
4310 Include(FRulez, R_BERSERK);
4311 if allowBerserkSwitching then
4312 begin
4313 FCurrWeap := WEAPON_KASTET;
4314 resetWeaponQueue();
4315 FModel.SetWeapon(WEAPON_KASTET);
4316 end;
4317 if gFlash <> 0 then
4318 begin
4319 Inc(FPain, 100);
4320 if gFlash = 2 then Inc(FPickup, 5);
4321 end;
4322 FBerserk := gTime+30000;
4323 Result := True;
4324 remove := True;
4325 FFireTime := 0;
4326 end;
4327 if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
4328 begin
4329 if FHealth < PLAYER_HP_SOFT then FHealth := PLAYER_HP_SOFT;
4330 FBerserk := gTime+30000;
4331 Result := True;
4332 remove := True;
4333 FFireTime := 0;
4334 end;
4335 end;
4337 ITEM_INVUL:
4338 if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
4339 begin
4340 FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
4341 FSpawnInvul := 0;
4342 Result := True;
4343 remove := True;
4344 if gFlash = 2 then Inc(FPickup, 5);
4345 end;
4347 ITEM_BOTTLE:
4348 if (FHealth < PLAYER_HP_LIMIT) or (FFireTime > 0) then
4349 begin
4350 if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 4, PLAYER_HP_LIMIT);
4351 Result := True;
4352 remove := True;
4353 FFireTime := 0;
4354 if gFlash = 2 then Inc(FPickup, 5);
4355 end;
4357 ITEM_HELMET:
4358 if FArmor < PLAYER_AP_LIMIT then
4359 begin
4360 IncMax(FArmor, 5, PLAYER_AP_LIMIT);
4361 Result := True;
4362 remove := True;
4363 if gFlash = 2 then Inc(FPickup, 5);
4364 end;
4366 ITEM_JETPACK:
4367 if FJetFuel < JET_MAX then
4368 begin
4369 FJetFuel := JET_MAX;
4370 Result := True;
4371 remove := True;
4372 if gFlash = 2 then Inc(FPickup, 5);
4373 end;
4375 ITEM_INVIS:
4376 if FMegaRulez[MR_INVIS] < gTime+PLAYER_INVIS_TIME then
4377 begin
4378 FMegaRulez[MR_INVIS] := gTime+PLAYER_INVIS_TIME;
4379 Result := True;
4380 remove := True;
4381 if gFlash = 2 then Inc(FPickup, 5);
4382 end;
4383 end;
4384 end;
4386 procedure TPlayer.Touch();
4387 begin
4388 if not FAlive then
4389 Exit;
4390 //FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
4391 if FIamBot then
4392 begin
4393 // Áðîñèòü ôëàã òîâàðèùó:
4394 if gGameSettings.GameMode = GM_CTF then
4395 DropFlag();
4396 end;
4397 end;
4399 procedure TPlayer.Push(vx, vy: Integer);
4400 begin
4401 if (not FPhysics) and FGhost then
4402 Exit;
4403 FObj.Accel.X := FObj.Accel.X + vx;
4404 FObj.Accel.Y := FObj.Accel.Y + vy;
4405 if g_Game_IsNet and g_Game_IsServer then
4406 MH_SEND_PlayerPos(True, FUID, NET_EVERYONE);
4407 end;
4409 procedure TPlayer.Reset(Force: Boolean);
4410 begin
4411 if Force then
4412 FAlive := False;
4414 FSpawned := False;
4415 FTime[T_RESPAWN] := 0;
4416 FTime[T_FLAGCAP] := 0;
4417 FGodMode := False;
4418 FNoTarget := False;
4419 FNoReload := False;
4420 FFrags := 0;
4421 FLastFrag := 0;
4422 FComboEvnt := -1;
4423 FKills := 0;
4424 FMonsterKills := 0;
4425 FDeath := 0;
4426 FSecrets := 0;
4427 FSpawnInvul := 0;
4428 FCorpse := -1;
4429 FReady := False;
4430 if FNoRespawn then
4431 begin
4432 FSpectator := False;
4433 FGhost := False;
4434 FPhysics := True;
4435 FSpectatePlayer := -1;
4436 FNoRespawn := False;
4437 end;
4438 FLives := gGameSettings.MaxLives;
4440 SetFlag(FLAG_NONE);
4441 end;
4443 procedure TPlayer.SoftReset();
4444 begin
4445 ReleaseKeys();
4447 FDamageBuffer := 0;
4448 FSlopeOld := 0;
4449 FIncCamOld := 0;
4450 FIncCam := 0;
4451 FBFGFireCounter := -1;
4452 FShellTimer := -1;
4453 FPain := 0;
4454 FLastHit := 0;
4455 FLastFrag := 0;
4456 FComboEvnt := -1;
4458 SetFlag(FLAG_NONE);
4459 SetAction(A_STAND, True);
4460 end;
4462 function TPlayer.GetRespawnPoint(): Byte;
4463 var
4464 c: Byte;
4465 begin
4466 Result := 255;
4467 // Íà áóäóùåå: FSpawn - èãðîê óæå èãðàë è ïåðåðîæäàåòñÿ
4469 // Îäèíî÷íàÿ èãðà/êîîïåðàòèâ
4470 if gGameSettings.GameMode in [GM_COOP, GM_SINGLE] then
4471 begin
4472 if (Self = gPlayer1) or (Self = gPlayer2) then
4473 begin
4474 // Òî÷êà ïîÿâëåíèÿ ñâîåãî èãðîêà
4475 if Self = gPlayer1 then
4476 c := RESPAWNPOINT_PLAYER1
4477 else
4478 c := RESPAWNPOINT_PLAYER2;
4479 if g_Map_GetPointCount(c) > 0 then
4480 begin
4481 Result := c;
4482 Exit;
4483 end;
4485 // Òî÷êà ïîÿâëåíèÿ äðóãîãî èãðîêà
4486 if Self = gPlayer1 then
4487 c := RESPAWNPOINT_PLAYER2
4488 else
4489 c := RESPAWNPOINT_PLAYER1;
4490 if g_Map_GetPointCount(c) > 0 then
4491 begin
4492 Result := c;
4493 Exit;
4494 end;
4495 end else
4496 begin
4497 // Òî÷êà ïîÿâëåíèÿ ëþáîãî èãðîêà (áîòà)
4498 if Random(2) = 0 then
4499 c := RESPAWNPOINT_PLAYER1
4500 else
4501 c := RESPAWNPOINT_PLAYER2;
4502 if g_Map_GetPointCount(c) > 0 then
4503 begin
4504 Result := c;
4505 Exit;
4506 end;
4507 end;
4509 // Òî÷êà ëþáîé èç êîìàíä
4510 if Random(2) = 0 then
4511 c := RESPAWNPOINT_RED
4512 else
4513 c := RESPAWNPOINT_BLUE;
4514 if g_Map_GetPointCount(c) > 0 then
4515 begin
4516 Result := c;
4517 Exit;
4518 end;
4520 // Òî÷êà DM
4521 c := RESPAWNPOINT_DM;
4522 if g_Map_GetPointCount(c) > 0 then
4523 begin
4524 Result := c;
4525 Exit;
4526 end;
4527 end;
4529 // Ìÿñîïîâàë
4530 if gGameSettings.GameMode = GM_DM then
4531 begin
4532 // Òî÷êà DM
4533 c := RESPAWNPOINT_DM;
4534 if g_Map_GetPointCount(c) > 0 then
4535 begin
4536 Result := c;
4537 Exit;
4538 end;
4540 // Òî÷êà ïîÿâëåíèÿ ëþáîãî èãðîêà
4541 if Random(2) = 0 then
4542 c := RESPAWNPOINT_PLAYER1
4543 else
4544 c := RESPAWNPOINT_PLAYER2;
4545 if g_Map_GetPointCount(c) > 0 then
4546 begin
4547 Result := c;
4548 Exit;
4549 end;
4551 // Òî÷êà ëþáîé èç êîìàíä
4552 if Random(2) = 0 then
4553 c := RESPAWNPOINT_RED
4554 else
4555 c := RESPAWNPOINT_BLUE;
4556 if g_Map_GetPointCount(c) > 0 then
4557 begin
4558 Result := c;
4559 Exit;
4560 end;
4561 end;
4563 // Êîìàíäíûå
4564 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
4565 begin
4566 // Òî÷êà ñâîåé êîìàíäû
4567 c := RESPAWNPOINT_DM;
4568 if FTeam = TEAM_RED then
4569 c := RESPAWNPOINT_RED;
4570 if FTeam = TEAM_BLUE then
4571 c := RESPAWNPOINT_BLUE;
4572 if g_Map_GetPointCount(c) > 0 then
4573 begin
4574 Result := c;
4575 Exit;
4576 end;
4578 // Òî÷êà DM
4579 c := RESPAWNPOINT_DM;
4580 if g_Map_GetPointCount(c) > 0 then
4581 begin
4582 Result := c;
4583 Exit;
4584 end;
4586 // Òî÷êà ïîÿâëåíèÿ ëþáîãî èãðîêà
4587 if Random(2) = 0 then
4588 c := RESPAWNPOINT_PLAYER1
4589 else
4590 c := RESPAWNPOINT_PLAYER2;
4591 if g_Map_GetPointCount(c) > 0 then
4592 begin
4593 Result := c;
4594 Exit;
4595 end;
4597 // Òî÷êà äðóãîé êîìàíäû
4598 c := RESPAWNPOINT_DM;
4599 if FTeam = TEAM_RED then
4600 c := RESPAWNPOINT_BLUE;
4601 if FTeam = TEAM_BLUE then
4602 c := RESPAWNPOINT_RED;
4603 if g_Map_GetPointCount(c) > 0 then
4604 begin
4605 Result := c;
4606 Exit;
4607 end;
4608 end;
4609 end;
4611 procedure TPlayer.Respawn(Silent: Boolean; Force: Boolean = False);
4612 var
4613 RespawnPoint: TRespawnPoint;
4614 a, b, c: Byte;
4615 Anim: TAnimation;
4616 ID: DWORD;
4617 begin
4618 FSlopeOld := 0;
4619 FIncCamOld := 0;
4620 FIncCam := 0;
4621 FBFGFireCounter := -1;
4622 FShellTimer := -1;
4623 FPain := 0;
4624 FLastHit := 0;
4625 FSpawnInvul := 0;
4626 FCorpse := -1;
4628 if not g_Game_IsServer then
4629 Exit;
4630 if FDummy then
4631 Exit;
4632 FWantsInGame := True;
4633 FJustTeleported := True;
4634 if Force then
4635 begin
4636 FTime[T_RESPAWN] := 0;
4637 FAlive := False;
4638 end;
4639 FNetTime := 0;
4640 // if server changes MaxLives we gotta be ready
4641 if gGameSettings.MaxLives = 0 then FNoRespawn := False;
4643 // Åùå íåëüçÿ âîçðîäèòüñÿ:
4644 if FTime[T_RESPAWN] > gTime then
4645 Exit;
4647 // Ïðîñðàë âñå æèçíè:
4648 if FNoRespawn then
4649 begin
4650 if not FSpectator then Spectate(True);
4651 FWantsInGame := True;
4652 Exit;
4653 end;
4655 if (gGameSettings.GameType <> GT_SINGLE) and (gGameSettings.GameMode <> GM_COOP) then
4656 begin // "Ñâîÿ èãðà"
4657 // Áåðñåðê íå ñîõðàíÿåòñÿ ìåæäó óðîâíÿìè:
4658 FRulez := FRulez-[R_BERSERK];
4659 end
4660 else // "Îäèíî÷íàÿ èãðà"/"Êîîï"
4661 begin
4662 // Áåðñåðê è êëþ÷è íå ñîõðàíÿþòñÿ ìåæäó óðîâíÿìè:
4663 FRulez := FRulez-[R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE, R_BERSERK];
4664 end;
4666 // Ïîëó÷àåì òî÷êó ñïàóíà èãðîêà:
4667 c := GetRespawnPoint();
4669 ReleaseKeys();
4670 SetFlag(FLAG_NONE);
4672 // Âîñêðåøåíèå áåç îðóæèÿ:
4673 if not FAlive then
4674 begin
4675 FHealth := Round(PLAYER_HP_SOFT * (FHandicap / 100));
4676 FArmor := 0;
4677 FAlive := True;
4678 FAir := AIR_DEF;
4679 FJetFuel := 0;
4681 for a := WP_FIRST to WP_LAST do
4682 begin
4683 FWeapon[a] := False;
4684 FReloading[a] := 0;
4685 end;
4687 FWeapon[WEAPON_PISTOL] := True;
4688 FWeapon[WEAPON_KASTET] := True;
4689 FCurrWeap := WEAPON_PISTOL;
4690 resetWeaponQueue();
4692 FModel.SetWeapon(FCurrWeap);
4694 for b := A_BULLETS to A_HIGH do
4695 FAmmo[b] := 0;
4697 FAmmo[A_BULLETS] := 50;
4699 FMaxAmmo[A_BULLETS] := AmmoLimits[0, A_BULLETS];
4700 FMaxAmmo[A_SHELLS] := AmmoLimits[0, A_SHELLS];
4701 FMaxAmmo[A_ROCKETS] := AmmoLimits[0, A_SHELLS];
4702 FMaxAmmo[A_CELLS] := AmmoLimits[0, A_CELLS];
4703 FMaxAmmo[A_FUEL] := AmmoLimits[0, A_FUEL];
4705 if (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) and
4706 LongBool(gGameSettings.Options and GAME_OPTION_DMKEYS) then
4707 FRulez := [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE]
4708 else
4709 FRulez := [];
4710 end;
4712 // Ïîëó÷àåì êîîðäèíàòû òî÷êè âîçðîæäåíèÿ:
4713 if not g_Map_GetPoint(c, RespawnPoint) then
4714 begin
4715 g_FatalError(_lc[I_GAME_ERROR_GET_SPAWN]);
4716 Exit;
4717 end;
4719 // Óñòàíîâêà êîîðäèíàò è ñáðîñ âñåõ ïàðàìåòðîâ:
4720 FObj.X := RespawnPoint.X-PLAYER_RECT.X;
4721 FObj.Y := RespawnPoint.Y-PLAYER_RECT.Y;
4722 FObj.oldX := FObj.X; // don't interpolate after respawn
4723 FObj.oldY := FObj.Y;
4724 FObj.Vel.X := 0;
4725 FObj.Vel.Y := 0;
4726 FObj.Accel.X := 0;
4727 FObj.Accel.Y := 0;
4729 FDirection := RespawnPoint.Direction;
4730 if FDirection = TDirection.D_LEFT then
4731 FAngle := 180
4732 else
4733 FAngle := 0;
4735 SetAction(A_STAND, True);
4736 FModel.Direction := FDirection;
4738 for a := Low(FTime) to High(FTime) do
4739 FTime[a] := 0;
4741 for a := Low(FMegaRulez) to High(FMegaRulez) do
4742 FMegaRulez[a] := 0;
4744 // Respawn invulnerability
4745 if (gGameSettings.GameType <> GT_SINGLE) and (gGameSettings.SpawnInvul > 0) then
4746 begin
4747 FMegaRulez[MR_INVUL] := gTime + gGameSettings.SpawnInvul * 1000;
4748 FSpawnInvul := FMegaRulez[MR_INVUL];
4749 end;
4751 FDamageBuffer := 0;
4752 FJetpack := False;
4753 FCanJetpack := False;
4754 FFlaming := False;
4755 FFireTime := 0;
4756 FFirePainTime := 0;
4757 FFireAttacker := 0;
4759 // Àíèìàöèÿ âîçðîæäåíèÿ:
4760 if (not gLoadGameMode) and (not Silent) then
4761 if g_Frames_Get(ID, 'FRAMES_TELEPORT') then
4762 begin
4763 Anim := TAnimation.Create(ID, False, 3);
4764 g_GFX_OnceAnim(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
4765 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, Anim);
4766 Anim.Free();
4767 end;
4769 FSpectator := False;
4770 FGhost := False;
4771 FPhysics := True;
4772 FSpectatePlayer := -1;
4773 FSpawned := True;
4775 if (gPlayer1 = nil) and (gSpectLatchPID1 = FUID) then
4776 gPlayer1 := self;
4777 if (gPlayer2 = nil) and (gSpectLatchPID2 = FUID) then
4778 gPlayer2 := self;
4780 if g_Game_IsNet then
4781 begin
4782 MH_SEND_PlayerPos(True, FUID, NET_EVERYONE);
4783 MH_SEND_PlayerStats(FUID, NET_EVERYONE);
4784 if not Silent then
4785 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
4786 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32,
4787 0, NET_GFX_TELE);
4788 end;
4789 end;
4791 procedure TPlayer.Spectate(NoMove: Boolean = False);
4792 begin
4793 if FAlive then
4794 Kill(K_EXTRAHARDKILL, FUID, HIT_SOME)
4795 else if (not NoMove) then
4796 begin
4797 GameX := gMapInfo.Width div 2;
4798 GameY := gMapInfo.Height div 2;
4799 end;
4800 FXTo := GameX;
4801 FYTo := GameY;
4803 FAlive := False;
4804 FSpectator := True;
4805 FGhost := True;
4806 FPhysics := False;
4807 FWantsInGame := False;
4808 FSpawned := False;
4809 FCorpse := -1;
4811 if FNoRespawn then
4812 begin
4813 if Self = gPlayer1 then
4814 begin
4815 gSpectLatchPID1 := FUID;
4816 gPlayer1 := nil;
4817 end
4818 else if Self = gPlayer2 then
4819 begin
4820 gSpectLatchPID2 := FUID;
4821 gPlayer2 := nil;
4822 end;
4823 end;
4825 if g_Game_IsNet then
4826 MH_SEND_PlayerStats(FUID);
4827 end;
4829 procedure TPlayer.SwitchNoClip;
4830 begin
4831 if not FAlive then
4832 Exit;
4833 FGhost := not FGhost;
4834 FPhysics := not FGhost;
4835 if FGhost then
4836 begin
4837 FXTo := FObj.X;
4838 FYTo := FObj.Y;
4839 end else
4840 begin
4841 FObj.Accel.X := 0;
4842 FObj.Accel.Y := 0;
4843 end;
4844 end;
4846 procedure TPlayer.Run(Direction: TDirection);
4847 var
4848 a, b: Integer;
4849 begin
4850 if MAX_RUNVEL > 8 then
4851 FlySmoke();
4853 // Áåæèì:
4854 if Direction = TDirection.D_LEFT then
4855 begin
4856 if FObj.Vel.X > -MAX_RUNVEL then
4857 FObj.Vel.X := FObj.Vel.X - (MAX_RUNVEL shr 3);
4858 end
4859 else
4860 if FObj.Vel.X < MAX_RUNVEL then
4861 FObj.Vel.X := FObj.Vel.X + (MAX_RUNVEL shr 3);
4863 // Âîçìîæíî, ïèíàåì êóñêè:
4864 if (FObj.Vel.X <> 0) and (gGibs <> nil) then
4865 begin
4866 b := Abs(FObj.Vel.X);
4867 if b > 1 then b := b * (Random(8 div b) + 1);
4868 for a := 0 to High(gGibs) do
4869 begin
4870 if gGibs[a].alive and
4871 g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
4872 FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then
4873 begin
4874 // Ïèíàåì êóñêè
4875 if FObj.Vel.X < 0 then
4876 begin
4877 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120) // íàëåâî
4878 end
4879 else
4880 begin
4881 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // íàïðàâî
4882 end;
4883 gGibs[a].positionChanged(); // this updates spatial accelerators
4884 end;
4885 end;
4886 end;
4888 SetAction(A_WALK);
4889 end;
4891 procedure TPlayer.SeeDown();
4892 begin
4893 SetAction(A_SEEDOWN);
4895 if FDirection = TDirection.D_LEFT then FAngle := ANGLE_LEFTDOWN else FAngle := ANGLE_RIGHTDOWN;
4897 if FIncCam > -120 then DecMin(FIncCam, 5, -120);
4898 end;
4900 procedure TPlayer.SeeUp();
4901 begin
4902 SetAction(A_SEEUP);
4904 if FDirection = TDirection.D_LEFT then FAngle := ANGLE_LEFTUP else FAngle := ANGLE_RIGHTUP;
4906 if FIncCam < 120 then IncMax(FIncCam, 5, 120);
4907 end;
4909 procedure TPlayer.SetAction(Action: Byte; Force: Boolean = False);
4910 var
4911 Prior: Byte;
4912 begin
4913 case Action of
4914 A_WALK: Prior := 3;
4915 A_DIE1: Prior := 5;
4916 A_DIE2: Prior := 5;
4917 A_ATTACK: Prior := 2;
4918 A_SEEUP: Prior := 1;
4919 A_SEEDOWN: Prior := 1;
4920 A_ATTACKUP: Prior := 2;
4921 A_ATTACKDOWN: Prior := 2;
4922 A_PAIN: Prior := 4;
4923 else Prior := 0;
4924 end;
4926 if (Prior > FActionPrior) or Force then
4927 if not ((Prior = 2) and (FCurrWeap = WEAPON_SAW)) then
4928 begin
4929 FActionPrior := Prior;
4930 FActionAnim := Action;
4931 FActionForce := Force;
4932 FActionChanged := True;
4933 end;
4935 if Action in [A_ATTACK, A_ATTACKUP, A_ATTACKDOWN] then FModel.SetFire(True);
4936 end;
4938 function TPlayer.StayOnStep(XInc, YInc: Integer): Boolean;
4939 begin
4940 Result := not g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+YInc+PLAYER_RECT.Y+PLAYER_RECT.Height-1,
4941 PLAYER_RECT.Width, 1, PANEL_STEP, False)
4942 and g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+YInc+PLAYER_RECT.Y+PLAYER_RECT.Height,
4943 PLAYER_RECT.Width, 1, PANEL_STEP, False);
4944 end;
4946 function TPlayer.TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean;
4947 var
4948 Anim: TAnimation;
4949 ID: DWORD;
4950 begin
4951 Result := False;
4953 if g_CollideLevel(X, Y, PLAYER_RECT.Width, PLAYER_RECT.Height) then
4954 begin
4955 g_Sound_PlayExAt('SOUND_GAME_NOTELEPORT', FObj.X, FObj.Y);
4956 if g_Game_IsServer and g_Game_IsNet then
4957 MH_SEND_Sound(FObj.X, FObj.Y, 'SOUND_GAME_NOTELEPORT');
4958 Exit;
4959 end;
4961 FJustTeleported := True;
4963 Anim := nil;
4964 if not silent then
4965 begin
4966 if g_Frames_Get(ID, 'FRAMES_TELEPORT') then
4967 begin
4968 Anim := TAnimation.Create(ID, False, 3);
4969 end;
4971 g_Sound_PlayExAt('SOUND_GAME_TELEPORT', FObj.X, FObj.Y);
4972 g_GFX_OnceAnim(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
4973 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, Anim);
4974 if g_Game_IsServer and g_Game_IsNet then
4975 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
4976 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 1,
4977 NET_GFX_TELE);
4978 end;
4980 FObj.X := X-PLAYER_RECT.X;
4981 FObj.Y := Y-PLAYER_RECT.Y;
4982 FObj.oldX := FObj.X; // don't interpolate after respawn
4983 FObj.oldY := FObj.Y;
4984 if FAlive and FGhost then
4985 begin
4986 FXTo := FObj.X;
4987 FYTo := FObj.Y;
4988 end;
4990 if not g_Game_IsNet then
4991 begin
4992 if dir = 1 then
4993 begin
4994 SetDirection(TDirection.D_LEFT);
4995 FAngle := 180;
4996 end
4997 else
4998 if dir = 2 then
4999 begin
5000 SetDirection(TDirection.D_RIGHT);
5001 FAngle := 0;
5002 end
5003 else
5004 if dir = 3 then
5005 begin // îáðàòíîå
5006 if FDirection = TDirection.D_RIGHT then
5007 begin
5008 SetDirection(TDirection.D_LEFT);
5009 FAngle := 180;
5010 end
5011 else
5012 begin
5013 SetDirection(TDirection.D_RIGHT);
5014 FAngle := 0;
5015 end;
5016 end;
5017 end;
5019 if not silent and (Anim <> nil) then
5020 begin
5021 g_GFX_OnceAnim(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
5022 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, Anim);
5023 Anim.Free();
5025 if g_Game_IsServer and g_Game_IsNet then
5026 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
5027 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 0,
5028 NET_GFX_TELE);
5029 end;
5031 Result := True;
5032 end;
5034 function nonz(a: Single): Single;
5035 begin
5036 if a <> 0 then
5037 Result := a
5038 else
5039 Result := 1;
5040 end;
5042 function TPlayer.refreshCorpse(): Boolean;
5043 var
5044 i: Integer;
5045 begin
5046 Result := False;
5047 FCorpse := -1;
5048 if FAlive or FSpectator then
5049 Exit;
5050 if (gCorpses = nil) or (Length(gCorpses) = 0) then
5051 Exit;
5052 for i := 0 to High(gCorpses) do
5053 if gCorpses[i] <> nil then
5054 if gCorpses[i].FPlayerUID = FUID then
5055 begin
5056 Result := True;
5057 FCorpse := i;
5058 break;
5059 end;
5060 end;
5062 function TPlayer.getCameraObj(): TObj;
5063 begin
5064 if (not FAlive) and (not FSpectator) and
5065 (FCorpse >= 0) and (FCorpse < Length(gCorpses)) and
5066 (gCorpses[FCorpse] <> nil) and (gCorpses[FCorpse].FPlayerUID = FUID) then
5067 begin
5068 gCorpses[FCorpse].FObj.slopeUpLeft := FObj.slopeUpLeft;
5069 Result := gCorpses[FCorpse].FObj;
5070 end
5071 else
5072 begin
5073 Result := FObj;
5074 end;
5075 end;
5077 procedure TPlayer.PreUpdate();
5078 begin
5079 FSlopeOld := FObj.slopeUpLeft;
5080 FIncCamOld := FIncCam;
5081 FObj.oldX := FObj.X;
5082 FObj.oldY := FObj.Y;
5083 end;
5085 procedure TPlayer.Update();
5086 var
5087 b: Byte;
5088 i, ii, wx, wy, xd, yd, k: Integer;
5089 blockmon, headwater, dospawn: Boolean;
5090 NetServer: Boolean;
5091 AnyServer: Boolean;
5092 SetSpect: Boolean;
5093 begin
5094 NetServer := g_Game_IsNet and g_Game_IsServer;
5095 AnyServer := g_Game_IsServer;
5097 if g_Game_IsClient and (NetInterpLevel > 0) then
5098 DoLerp(NetInterpLevel + 1)
5099 else
5100 if FGhost then
5101 DoLerp(4);
5103 if NetServer then
5104 if FClientID >= 0 then
5105 begin
5106 FPing := NetClients[FClientID].Peer^.lastRoundTripTime;
5107 if NetClients[FClientID].Peer^.packetsSent > 0 then
5108 FLoss := Round(100*NetClients[FClientID].Peer^.packetsLost/NetClients[FClientID].Peer^.packetsSent)
5109 else
5110 FLoss := 0;
5111 end else
5112 begin
5113 FPing := 0;
5114 FLoss := 0;
5115 end;
5117 if FAlive and (FPunchAnim <> nil) then
5118 FPunchAnim.Update();
5120 if FAlive and (gFly or FJetpack) then
5121 FlySmoke();
5123 if FDirection = TDirection.D_LEFT then
5124 FAngle := 180
5125 else
5126 FAngle := 0;
5128 if FAlive and (not FGhost) then
5129 begin
5130 if FKeys[KEY_UP].Pressed then
5131 SeeUp();
5132 if FKeys[KEY_DOWN].Pressed then
5133 SeeDown();
5134 end;
5136 if (not (FKeys[KEY_UP].Pressed or FKeys[KEY_DOWN].Pressed)) and
5137 (FIncCam <> 0) then
5138 begin
5139 i := g_basic.Sign(FIncCam);
5140 FIncCam := Abs(FIncCam);
5141 DecMin(FIncCam, 5, 0);
5142 FIncCam := FIncCam*i;
5143 end;
5145 // no need to do that each second frame, weapon queue will take care of it
5146 if FAlive and FKeys[KEY_NEXTWEAPON].Pressed and AnyServer then NextWeapon();
5147 if FAlive and FKeys[KEY_PREVWEAPON].Pressed and AnyServer then PrevWeapon();
5149 if gTime mod (GAME_TICK*2) <> 0 then
5150 begin
5151 if (FObj.Vel.X = 0) and FAlive then
5152 begin
5153 if FKeys[KEY_LEFT].Pressed then
5154 Run(TDirection.D_LEFT);
5155 if FKeys[KEY_RIGHT].Pressed then
5156 Run(TDirection.D_RIGHT);
5157 end;
5159 if FPhysics then
5160 begin
5161 g_Obj_Move(@FObj, True, True, True);
5162 positionChanged(); // this updates spatial accelerators
5163 end;
5165 Exit;
5166 end;
5168 FActionChanged := False;
5170 if FAlive then
5171 begin
5172 // Let alive player do some actions
5173 if FKeys[KEY_LEFT].Pressed then Run(TDirection.D_LEFT);
5174 if FKeys[KEY_RIGHT].Pressed then Run(TDirection.D_RIGHT);
5175 //if FKeys[KEY_NEXTWEAPON].Pressed and AnyServer then NextWeapon();
5176 //if FKeys[KEY_PREVWEAPON].Pressed and AnyServer then PrevWeapon();
5177 if FKeys[KEY_FIRE].Pressed and AnyServer then Fire()
5178 else
5179 begin
5180 if AnyServer then
5181 begin
5182 FlamerOff;
5183 if NetServer then MH_SEND_PlayerStats(FUID);
5184 end;
5185 end;
5186 if FKeys[KEY_OPEN].Pressed and AnyServer then Use();
5187 if FKeys[KEY_JUMP].Pressed then Jump()
5188 else
5189 begin
5190 if AnyServer and FJetpack then
5191 begin
5192 FJetpack := False;
5193 JetpackOff;
5194 if NetServer then MH_SEND_PlayerStats(FUID);
5195 end;
5196 FCanJetpack := True;
5197 end;
5198 end
5199 else // Dead
5200 begin
5201 dospawn := False;
5202 if not FGhost then
5203 for k := Low(FKeys) to KEY_CHAT-1 do
5204 begin
5205 if FKeys[k].Pressed then
5206 begin
5207 dospawn := True;
5208 break;
5209 end;
5210 end;
5211 if dospawn then
5212 begin
5213 if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
5214 Respawn(False)
5215 else // Single
5216 if (FTime[T_RESPAWN] <= gTime) and
5217 gGameOn and (not FAlive) then
5218 begin
5219 if (g_Player_GetCount() > 1) then
5220 Respawn(False)
5221 else
5222 begin
5223 gExit := EXIT_RESTART;
5224 Exit;
5225 end;
5226 end;
5227 end;
5228 // Dead spectator actions
5229 if FGhost then
5230 begin
5231 if FKeys[KEY_OPEN].Pressed and AnyServer then Fire();
5232 if FKeys[KEY_FIRE].Pressed and AnyServer then
5233 begin
5234 if FSpectator then
5235 begin
5236 if (FSpectatePlayer >= High(gPlayers)) then
5237 FSpectatePlayer := -1
5238 else
5239 begin
5240 SetSpect := False;
5241 for I := FSpectatePlayer + 1 to High(gPlayers) do
5242 if gPlayers[I] <> nil then
5243 if gPlayers[I].alive then
5244 if gPlayers[I].UID <> FUID then
5245 begin
5246 FSpectatePlayer := I;
5247 SetSpect := True;
5248 break;
5249 end;
5251 if not SetSpect then FSpectatePlayer := -1;
5252 end;
5254 ReleaseKeys;
5255 end;
5256 end;
5257 end;
5258 end;
5259 // No clipping
5260 if FGhost then
5261 begin
5262 if FKeys[KEY_UP].Pressed or FKeys[KEY_JUMP].Pressed then
5263 begin
5264 FYTo := FObj.Y - 32;
5265 FSpectatePlayer := -1;
5266 end;
5267 if FKeys[KEY_DOWN].Pressed then
5268 begin
5269 FYTo := FObj.Y + 32;
5270 FSpectatePlayer := -1;
5271 end;
5272 if FKeys[KEY_LEFT].Pressed then
5273 begin
5274 FXTo := FObj.X - 32;
5275 FSpectatePlayer := -1;
5276 end;
5277 if FKeys[KEY_RIGHT].Pressed then
5278 begin
5279 FXTo := FObj.X + 32;
5280 FSpectatePlayer := -1;
5281 end;
5283 if (FXTo < -64) then
5284 FXTo := -64
5285 else if (FXTo > gMapInfo.Width + 32) then
5286 FXTo := gMapInfo.Width + 32;
5287 if (FYTo < -72) then
5288 FYTo := -72
5289 else if (FYTo > gMapInfo.Height + 32) then
5290 FYTo := gMapInfo.Height + 32;
5291 end;
5293 if FPhysics then
5294 begin
5295 g_Obj_Move(@FObj, True, True, True);
5296 positionChanged(); // this updates spatial accelerators
5297 end
5298 else
5299 begin
5300 FObj.Vel.X := 0;
5301 FObj.Vel.Y := 0;
5302 if FSpectator then
5303 if (FSpectatePlayer <= High(gPlayers)) and (FSpectatePlayer >= 0) then
5304 if gPlayers[FSpectatePlayer] <> nil then
5305 if gPlayers[FSpectatePlayer].alive then
5306 begin
5307 FXTo := gPlayers[FSpectatePlayer].GameX;
5308 FYTo := gPlayers[FSpectatePlayer].GameY;
5309 end;
5310 end;
5312 blockmon := g_Map_CollidePanel(FObj.X+PLAYER_HEADRECT.X, FObj.Y+PLAYER_HEADRECT.Y,
5313 PLAYER_HEADRECT.Width, PLAYER_HEADRECT.Height,
5314 PANEL_BLOCKMON, True);
5315 headwater := HeadInLiquid(0, 0);
5317 // Ñîïðîòèâëåíèå âîçäóõà:
5318 if (not FAlive) or not (FKeys[KEY_LEFT].Pressed or FKeys[KEY_RIGHT].Pressed) then
5319 if FObj.Vel.X <> 0 then
5320 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
5322 if (FLastHit = HIT_TRAP) and (FPain > 90) then FPain := 90;
5323 DecMin(FPain, 5, 0);
5324 DecMin(FPickup, 1, 0);
5326 if FAlive and (FObj.Y > Integer(gMapInfo.Height)+128) and AnyServer then
5327 begin
5328 // Îáíóëèòü äåéñòâèÿ ïðèìî÷åê, ÷òîáû ôîí ïðîïàë
5329 FMegaRulez[MR_SUIT] := 0;
5330 FMegaRulez[MR_INVUL] := 0;
5331 FMegaRulez[MR_INVIS] := 0;
5332 Kill(K_FALLKILL, 0, HIT_FALL);
5333 end;
5335 i := 9;
5337 if FAlive then
5338 begin
5339 if FCurrWeap = WEAPON_SAW then
5340 if not (FSawSound.IsPlaying() or FSawSoundHit.IsPlaying() or
5341 FSawSoundSelect.IsPlaying()) then
5342 FSawSoundIdle.PlayAt(FObj.X, FObj.Y);
5344 if FJetpack then
5345 if (not FJetSoundFly.IsPlaying()) and (not FJetSoundOn.IsPlaying()) and
5346 (not FJetSoundOff.IsPlaying()) then
5347 begin
5348 FJetSoundFly.SetPosition(0);
5349 FJetSoundFly.PlayAt(FObj.X, FObj.Y);
5350 end;
5352 for b := WP_FIRST to WP_LAST do
5353 if FReloading[b] > 0 then
5354 if FNoReload then
5355 FReloading[b] := 0
5356 else
5357 Dec(FReloading[b]);
5359 if FShellTimer > -1 then
5360 if FShellTimer = 0 then
5361 begin
5362 if FShellType = SHELL_SHELL then
5363 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
5364 GameVelX, GameVelY-2, SHELL_SHELL)
5365 else if FShellType = SHELL_DBLSHELL then
5366 begin
5367 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
5368 GameVelX+1, GameVelY-2, SHELL_SHELL);
5369 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
5370 GameVelX-1, GameVelY-2, SHELL_SHELL);
5371 end;
5372 FShellTimer := -1;
5373 end else Dec(FShellTimer);
5375 if (FBFGFireCounter > -1) then
5376 if FBFGFireCounter = 0 then
5377 begin
5378 if AnyServer then
5379 begin
5380 wx := FObj.X+WEAPONPOINT[FDirection].X;
5381 wy := FObj.Y+WEAPONPOINT[FDirection].Y;
5382 xd := wx+IfThen(FDirection = TDirection.D_LEFT, -30, 30);
5383 yd := wy+firediry();
5384 g_Weapon_bfgshot(wx, wy, xd, yd, FUID);
5385 if NetServer then MH_SEND_PlayerFire(FUID, WEAPON_BFG, wx, wy, xd, yd);
5386 if (FAngle = 0) or (FAngle = 180) then SetAction(A_ATTACK)
5387 else if (FAngle = ANGLE_LEFTDOWN) or (FAngle = ANGLE_RIGHTDOWN) then SetAction(A_ATTACKDOWN)
5388 else if (FAngle = ANGLE_LEFTUP) or (FAngle = ANGLE_RIGHTUP) then SetAction(A_ATTACKUP);
5389 end;
5391 FReloading[WEAPON_BFG] := WEAPON_RELOAD[WEAPON_BFG];
5392 FBFGFireCounter := -1;
5393 end else
5394 if FNoReload then
5395 FBFGFireCounter := 0
5396 else
5397 Dec(FBFGFireCounter);
5399 if (FMegaRulez[MR_SUIT] < gTime) and AnyServer then
5400 begin
5401 b := g_GetAcidHit(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width, PLAYER_RECT.Height);
5403 if (b > 0) and (gTime mod (15*GAME_TICK) = 0) then Damage(b, 0, 0, 0, HIT_ACID);
5404 end;
5406 if (headwater or blockmon) then
5407 begin
5408 Dec(FAir);
5410 if FAir < -9 then
5411 begin
5412 if AnyServer then Damage(10, 0, 0, 0, HIT_WATER);
5413 FAir := 0;
5414 end
5415 else if (FAir mod 31 = 0) and not blockmon then
5416 begin
5417 g_GFX_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2), FObj.Y+PLAYER_RECT.Y-4, 5+Random(6), 8, 4);
5418 if Random(2) = 0 then
5419 g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
5420 else
5421 g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
5422 end;
5423 end else if FAir < AIR_DEF then
5424 FAir := AIR_DEF;
5426 if FFireTime > 0 then
5427 begin
5428 if BodyInLiquid(0, 0) then
5429 begin
5430 FFireTime := 0;
5431 FFirePainTime := 0;
5432 end
5433 else if FMegaRulez[MR_SUIT] >= gTime then
5434 begin
5435 if FMegaRulez[MR_SUIT] = gTime then
5436 FFireTime := 1;
5437 FFirePainTime := 0;
5438 end
5439 else
5440 begin
5441 OnFireFlame(1);
5442 if FFirePainTime <= 0 then
5443 begin
5444 if g_Game_IsServer then
5445 Damage(2, FFireAttacker, 0, 0, HIT_FLAME);
5446 FFirePainTime := 12 - FFireTime div 12;
5447 end;
5448 FFirePainTime := FFirePainTime - 1;
5449 FFireTime := FFireTime - 1;
5450 if ((FFireTime mod 33) = 0) and (FMegaRulez[MR_INVUL] < gTime) then
5451 FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
5452 if (FFireTime = 0) and g_Game_IsNet and g_Game_IsServer then
5453 MH_SEND_PlayerStats(FUID);
5454 end;
5455 end;
5457 if FDamageBuffer > 0 then
5458 begin
5459 if FDamageBuffer >= 9 then
5460 begin
5461 SetAction(A_PAIN);
5463 if FDamageBuffer < 30 then i := 9
5464 else if FDamageBuffer < 100 then i := 18
5465 else i := 27;
5466 end;
5468 ii := Round(FDamageBuffer*FHealth / nonz(FArmor*(3/4)+FHealth));
5469 FArmor := FArmor-(FDamageBuffer-ii);
5470 FHealth := FHealth-ii;
5471 if FArmor < 0 then
5472 begin
5473 FHealth := FHealth+FArmor;
5474 FArmor := 0;
5475 end;
5477 if AnyServer then
5478 if FHealth <= 0 then
5479 if FHealth > -30 then Kill(K_SIMPLEKILL, FLastSpawnerUID, FLastHit)
5480 else if FHealth > -50 then Kill(K_HARDKILL, FLastSpawnerUID, FLastHit)
5481 else Kill(K_EXTRAHARDKILL, FLastSpawnerUID, FLastHit);
5483 if FAlive and ((FLastHit <> HIT_FLAME) or (FFireTime <= 0)) then
5484 begin
5485 if FDamageBuffer <= 20 then FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y)
5486 else if FDamageBuffer <= 55 then FModel.PlaySound(MODELSOUND_PAIN, 2, FObj.X, FObj.Y)
5487 else if FDamageBuffer <= 120 then FModel.PlaySound(MODELSOUND_PAIN, 3, FObj.X, FObj.Y)
5488 else FModel.PlaySound(MODELSOUND_PAIN, 4, FObj.X, FObj.Y);
5489 end;
5491 FDamageBuffer := 0;
5492 end;
5494 {CollideItem();}
5495 end; // if FAlive then ...
5497 if (FActionAnim = A_PAIN) and (FModel.Animation <> A_PAIN) then
5498 begin
5499 FModel.ChangeAnimation(FActionAnim, FActionForce);
5500 FModel.GetCurrentAnimation.MinLength := i;
5501 FModel.GetCurrentAnimationMask.MinLength := i;
5502 end else FModel.ChangeAnimation(FActionAnim, FActionForce and (FModel.Animation <> A_STAND));
5504 if (FModel.GetCurrentAnimation.Played or ((not FActionChanged) and (FModel.Animation = A_WALK)))
5505 then SetAction(A_STAND, True);
5507 if not ((FModel.Animation = A_WALK) and (Abs(FObj.Vel.X) < 4) and not FModel.Fire) then FModel.Update;
5509 for b := Low(FKeys) to High(FKeys) do
5510 if FKeys[b].Time = 0 then FKeys[b].Pressed := False else Dec(FKeys[b].Time);
5511 end;
5514 procedure TPlayer.getMapBox (out x, y, w, h: Integer); inline;
5515 begin
5516 x := FObj.X+PLAYER_RECT.X;
5517 y := FObj.Y+PLAYER_RECT.Y;
5518 w := PLAYER_RECT.Width;
5519 h := PLAYER_RECT.Height;
5520 end;
5523 procedure TPlayer.moveBy (dx, dy: Integer); inline;
5524 begin
5525 if (dx <> 0) or (dy <> 0) then
5526 begin
5527 FObj.X += dx;
5528 FObj.Y += dy;
5529 positionChanged();
5530 end;
5531 end;
5534 function TPlayer.Collide(X, Y: Integer; Width, Height: Word): Boolean;
5535 begin
5536 Result := g_Collide(FObj.X+PLAYER_RECT.X,
5537 FObj.Y+PLAYER_RECT.Y,
5538 PLAYER_RECT.Width,
5539 PLAYER_RECT.Height,
5540 X, Y,
5541 Width, Height);
5542 end;
5544 function TPlayer.Collide(Panel: TPanel): Boolean;
5545 begin
5546 Result := g_Collide(FObj.X+PLAYER_RECT.X,
5547 FObj.Y+PLAYER_RECT.Y,
5548 PLAYER_RECT.Width,
5549 PLAYER_RECT.Height,
5550 Panel.X, Panel.Y,
5551 Panel.Width, Panel.Height);
5552 end;
5554 function TPlayer.Collide(X, Y: Integer): Boolean;
5555 begin
5556 X := X-FObj.X-PLAYER_RECT.X;
5557 Y := Y-FObj.Y-PLAYER_RECT.Y;
5558 Result := (x >= 0) and (x <= PLAYER_RECT.Width) and
5559 (y >= 0) and (y <= PLAYER_RECT.Height);
5560 end;
5562 function g_Player_ValidName(Name: string): Boolean;
5563 var
5564 a: Integer;
5565 begin
5566 Result := True;
5568 if gPlayers = nil then Exit;
5570 for a := 0 to High(gPlayers) do
5571 if gPlayers[a] <> nil then
5572 if LowerCase(Name) = LowerCase(gPlayers[a].FName) then
5573 begin
5574 Result := False;
5575 Exit;
5576 end;
5577 end;
5579 procedure TPlayer.SetDirection(Direction: TDirection);
5580 var
5581 d: TDirection;
5582 begin
5583 d := FModel.Direction;
5585 FModel.Direction := Direction;
5586 if d <> Direction then FModel.ChangeAnimation(FModel.Animation, True);
5588 FDirection := Direction;
5589 end;
5591 function TPlayer.GetKeys(): Byte;
5592 begin
5593 Result := 0;
5595 if R_KEY_RED in FRulez then Result := KEY_RED;
5596 if R_KEY_GREEN in FRulez then Result := Result or KEY_GREEN;
5597 if R_KEY_BLUE in FRulez then Result := Result or KEY_BLUE;
5599 if FTeam = TEAM_RED then Result := Result or KEY_REDTEAM;
5600 if FTeam = TEAM_BLUE then Result := Result or KEY_BLUETEAM;
5601 end;
5603 procedure TPlayer.Use();
5604 var
5605 a: Integer;
5606 begin
5607 if FTime[T_USE] > gTime then Exit;
5609 g_Triggers_PressR(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width,
5610 PLAYER_RECT.Height, FUID, ACTIVATE_PLAYERPRESS);
5612 for a := 0 to High(gPlayers) do
5613 if (gPlayers[a] <> nil) and (gPlayers[a] <> Self) and
5614 gPlayers[a].alive and SameTeam(FUID, gPlayers[a].FUID) and
5615 g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
5616 FObj.Rect.Width, FObj.Rect.Height, @gPlayers[a].FObj) then
5617 begin
5618 gPlayers[a].Touch();
5619 if g_Game_IsNet and g_Game_IsServer then
5620 MH_SEND_GameEvent(NET_EV_PLAYER_TOUCH, gPlayers[a].FUID);
5621 end;
5623 FTime[T_USE] := gTime+120;
5624 end;
5626 procedure TPlayer.NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1);
5627 var
5628 locObj: TObj;
5629 F: Boolean;
5630 WX, WY, XD, YD: Integer;
5631 begin
5632 F := False;
5633 WX := X;
5634 WY := Y;
5635 XD := AX;
5636 YD := AY;
5638 case FCurrWeap of
5639 WEAPON_KASTET:
5640 begin
5641 DoPunch();
5642 if R_BERSERK in FRulez then
5643 begin
5644 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
5645 locobj.X := FObj.X+FObj.Rect.X;
5646 locobj.Y := FObj.Y+FObj.Rect.Y;
5647 locobj.rect.X := 0;
5648 locobj.rect.Y := 0;
5649 locobj.rect.Width := 39;
5650 locobj.rect.Height := 52;
5651 locobj.Vel.X := (xd-wx) div 2;
5652 locobj.Vel.Y := (yd-wy) div 2;
5653 locobj.Accel.X := xd-wx;
5654 locobj.Accel.y := yd-wy;
5656 if g_Weapon_Hit(@locobj, 50, FUID, HIT_SOME) <> 0 then
5657 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj.X, FObj.Y)
5658 else
5659 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj.X, FObj.Y);
5661 if gFlash = 1 then
5662 if FPain < 50 then
5663 FPain := min(FPain + 25, 50);
5664 end else
5665 g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 3, FUID);
5666 end;
5668 WEAPON_SAW:
5669 begin
5670 if g_Weapon_chainsaw(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
5671 IfThen(gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF], 9, 3), FUID) <> 0 then
5672 begin
5673 FSawSoundSelect.Stop();
5674 FSawSound.Stop();
5675 FSawSoundHit.PlayAt(FObj.X, FObj.Y);
5676 end
5677 else if not FSawSoundHit.IsPlaying() then
5678 begin
5679 FSawSoundSelect.Stop();
5680 FSawSound.PlayAt(FObj.X, FObj.Y);
5681 end;
5682 f := True;
5683 end;
5685 WEAPON_PISTOL:
5686 begin
5687 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', GameX, Gamey);
5688 FFireAngle := FAngle;
5689 f := True;
5690 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
5691 GameVelX, GameVelY-2, SHELL_BULLET);
5692 end;
5694 WEAPON_SHOTGUN1:
5695 begin
5696 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex, Gamey);
5697 FFireAngle := FAngle;
5698 f := True;
5699 FShellTimer := 10;
5700 FShellType := SHELL_SHELL;
5701 end;
5703 WEAPON_SHOTGUN2:
5704 begin
5705 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', Gamex, Gamey);
5706 FFireAngle := FAngle;
5707 f := True;
5708 FShellTimer := 13;
5709 FShellType := SHELL_DBLSHELL;
5710 end;
5712 WEAPON_CHAINGUN:
5713 begin
5714 g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', Gamex, Gamey);
5715 FFireAngle := FAngle;
5716 f := True;
5717 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
5718 GameVelX, GameVelY-2, SHELL_BULLET);
5719 end;
5721 WEAPON_ROCKETLAUNCHER:
5722 begin
5723 g_Weapon_Rocket(wx, wy, xd, yd, FUID, WID);
5724 FFireAngle := FAngle;
5725 f := True;
5726 end;
5728 WEAPON_PLASMA:
5729 begin
5730 g_Weapon_Plasma(wx, wy, xd, yd, FUID, WID);
5731 FFireAngle := FAngle;
5732 f := True;
5733 end;
5735 WEAPON_BFG:
5736 begin
5737 g_Weapon_BFGShot(wx, wy, xd, yd, FUID, WID);
5738 FFireAngle := FAngle;
5739 f := True;
5740 end;
5742 WEAPON_SUPERPULEMET:
5743 begin
5744 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex, Gamey);
5745 FFireAngle := FAngle;
5746 f := True;
5747 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
5748 GameVelX, GameVelY-2, SHELL_SHELL);
5749 end;
5751 WEAPON_FLAMETHROWER:
5752 begin
5753 g_Weapon_flame(wx, wy, xd, yd, FUID, WID);
5754 FlamerOn;
5755 FFireAngle := FAngle;
5756 f := True;
5757 end;
5758 end;
5760 if not f then Exit;
5762 if (FAngle = 0) or (FAngle = 180) then SetAction(A_ATTACK)
5763 else if (FAngle = ANGLE_LEFTDOWN) or (FAngle = ANGLE_RIGHTDOWN) then SetAction(A_ATTACKDOWN)
5764 else if (FAngle = ANGLE_LEFTUP) or (FAngle = ANGLE_RIGHTUP) then SetAction(A_ATTACKUP);
5765 end;
5767 procedure TPlayer.DoLerp(Level: Integer = 2);
5768 begin
5769 if FObj.X <> FXTo then FObj.X := Lerp(FObj.X, FXTo, Level);
5770 if FObj.Y <> FYTo then FObj.Y := Lerp(FObj.Y, FYTo, Level);
5771 end;
5773 procedure TPlayer.SetLerp(XTo, YTo: Integer);
5774 var
5775 AX, AY: Integer;
5776 begin
5777 FXTo := XTo;
5778 FYTo := YTo;
5779 if NetInterpLevel < 1 then
5780 begin
5781 FObj.X := XTo;
5782 FObj.Y := YTo;
5783 end
5784 else
5785 begin
5786 AX := Abs(FXTo - FObj.X);
5787 AY := Abs(FYTo - FObj.Y);
5788 if (AX > 32) or (AX <= NetInterpLevel) then
5789 FObj.X := FXTo;
5790 if (AY > 32) or (AY <= NetInterpLevel) then
5791 FObj.Y := FYTo;
5792 end;
5793 end;
5795 function TPlayer.FullInLift(XInc, YInc: Integer): Integer;
5796 begin
5797 if g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
5798 PLAYER_RECT.Width, PLAYER_RECT.Height-8,
5799 PANEL_LIFTUP, False) then Result := -1
5800 else
5801 if g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
5802 PLAYER_RECT.Width, PLAYER_RECT.Height-8,
5803 PANEL_LIFTDOWN, False) then Result := 1
5804 else Result := 0;
5805 end;
5807 function TPlayer.GetFlag(Flag: Byte): Boolean;
5808 var
5809 s, ts: String;
5810 evtype, a: Byte;
5811 begin
5812 Result := False;
5814 if Flag = FLAG_NONE then
5815 Exit;
5817 if not g_Game_IsServer then Exit;
5819 // Ïðèíåñ ÷óæîé ôëàã íà ñâîþ áàçó:
5820 if (Flag = FTeam) and
5821 (gFlags[Flag].State = FLAG_STATE_NORMAL) and
5822 (FFlag <> FLAG_NONE) then
5823 begin
5824 if FFlag = FLAG_RED then
5825 s := _lc[I_PLAYER_FLAG_RED]
5826 else
5827 s := _lc[I_PLAYER_FLAG_BLUE];
5829 evtype := FLAG_STATE_SCORED;
5831 ts := Format('%.4d', [gFlags[FFlag].CaptureTime]);
5832 Insert('.', ts, Length(ts) + 1 - 3);
5833 g_Console_Add(Format(_lc[I_PLAYER_FLAG_CAPTURE], [FName, s, ts]), True);
5835 g_Map_ResetFlag(FFlag);
5836 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_CAPTURE], [AnsiUpperCase(s)]), 144);
5838 if ((Self = gPlayer1) or (Self = gPlayer2)
5839 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
5840 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
5841 a := 0
5842 else
5843 a := 1;
5845 if not sound_cap_flag[a].IsPlaying() then
5846 sound_cap_flag[a].Play();
5848 gTeamStat[FTeam].Goals := gTeamStat[FTeam].Goals + 1;
5850 Result := True;
5851 if g_Game_IsNet then
5852 begin
5853 MH_SEND_FlagEvent(evtype, FFlag, FUID, False);
5854 MH_SEND_GameStats;
5855 end;
5857 gFlags[FFlag].CaptureTime := 0;
5858 SetFlag(FLAG_NONE);
5859 Exit;
5860 end;
5862 // Ïîäîáðàë ñâîé ôëàã - âåðíóë åãî íà áàçó:
5863 if (Flag = FTeam) and
5864 (gFlags[Flag].State = FLAG_STATE_DROPPED) then
5865 begin
5866 if Flag = FLAG_RED then
5867 s := _lc[I_PLAYER_FLAG_RED]
5868 else
5869 s := _lc[I_PLAYER_FLAG_BLUE];
5871 evtype := FLAG_STATE_RETURNED;
5872 gFlags[Flag].CaptureTime := 0;
5874 g_Console_Add(Format(_lc[I_PLAYER_FLAG_RETURN], [FName, s]), True);
5876 g_Map_ResetFlag(Flag);
5877 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
5879 if ((Self = gPlayer1) or (Self = gPlayer2)
5880 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
5881 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
5882 a := 0
5883 else
5884 a := 1;
5886 if not sound_ret_flag[a].IsPlaying() then
5887 sound_ret_flag[a].Play();
5889 Result := True;
5890 if g_Game_IsNet then
5891 begin
5892 MH_SEND_FlagEvent(evtype, Flag, FUID, False);
5893 MH_SEND_GameStats;
5894 end;
5895 Exit;
5896 end;
5898 // Ïîäîáðàë ÷óæîé ôëàã:
5899 if (Flag <> FTeam) and (FTime[T_FLAGCAP] <= gTime) then
5900 begin
5901 SetFlag(Flag);
5903 if Flag = FLAG_RED then
5904 s := _lc[I_PLAYER_FLAG_RED]
5905 else
5906 s := _lc[I_PLAYER_FLAG_BLUE];
5908 evtype := FLAG_STATE_CAPTURED;
5910 g_Console_Add(Format(_lc[I_PLAYER_FLAG_GET], [FName, s]), True);
5912 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_GET], [AnsiUpperCase(s)]), 144);
5914 gFlags[Flag].State := FLAG_STATE_CAPTURED;
5916 if ((Self = gPlayer1) or (Self = gPlayer2)
5917 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
5918 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
5919 a := 0
5920 else
5921 a := 1;
5923 if not sound_get_flag[a].IsPlaying() then
5924 sound_get_flag[a].Play();
5926 Result := True;
5927 if g_Game_IsNet then
5928 begin
5929 MH_SEND_FlagEvent(evtype, Flag, FUID, False);
5930 MH_SEND_GameStats;
5931 end;
5932 end;
5933 end;
5935 procedure TPlayer.SetFlag(Flag: Byte);
5936 begin
5937 FFlag := Flag;
5938 if FModel <> nil then
5939 FModel.SetFlag(FFlag);
5940 end;
5942 function TPlayer.DropFlag(Silent: Boolean = True): Boolean;
5943 var
5944 s: String;
5945 a: Byte;
5946 begin
5947 Result := False;
5948 if (not g_Game_IsServer) or (FFlag = FLAG_NONE) then
5949 Exit;
5950 FTime[T_FLAGCAP] := gTime + 2000;
5951 with gFlags[FFlag] do
5952 begin
5953 Obj.X := FObj.X;
5954 Obj.Y := FObj.Y;
5955 Direction := FDirection;
5956 State := FLAG_STATE_DROPPED;
5957 Count := FLAG_TIME;
5958 g_Obj_Push(@Obj, (FObj.Vel.X div 2)-2+Random(5),
5959 (FObj.Vel.Y div 2)-2+Random(5));
5960 positionChanged(); // this updates spatial accelerators
5962 if FFlag = FLAG_RED then
5963 s := _lc[I_PLAYER_FLAG_RED]
5964 else
5965 s := _lc[I_PLAYER_FLAG_BLUE];
5967 g_Console_Add(Format(_lc[I_PLAYER_FLAG_DROP], [FName, s]), True);
5968 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_DROP], [AnsiUpperCase(s)]), 144);
5970 if ((Self = gPlayer1) or (Self = gPlayer2)
5971 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
5972 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
5973 a := 0
5974 else
5975 a := 1;
5977 if (not Silent) and (not sound_lost_flag[a].IsPlaying()) then
5978 sound_lost_flag[a].Play();
5980 if g_Game_IsNet then
5981 MH_SEND_FlagEvent(FLAG_STATE_DROPPED, Flag, FUID, False);
5982 end;
5983 SetFlag(FLAG_NONE);
5984 Result := True;
5985 end;
5987 procedure TPlayer.GetSecret();
5988 begin
5989 if (self = gPlayer1) or (self = gPlayer2) then
5990 begin
5991 g_Console_Add(Format(_lc[I_PLAYER_SECRET], [FName]), True);
5992 g_Sound_PlayEx('SOUND_GAME_SECRET');
5993 end;
5994 Inc(FSecrets);
5995 end;
5997 procedure TPlayer.PressKey(Key: Byte; Time: Word = 1);
5998 begin
5999 Assert(Key <= High(FKeys));
6001 FKeys[Key].Pressed := True;
6002 FKeys[Key].Time := Time;
6003 end;
6005 function TPlayer.IsKeyPressed(K: Byte): Boolean;
6006 begin
6007 Result := FKeys[K].Pressed;
6008 end;
6010 procedure TPlayer.ReleaseKeys();
6011 var
6012 a: Integer;
6013 begin
6014 for a := Low(FKeys) to High(FKeys) do
6015 begin
6016 FKeys[a].Pressed := False;
6017 FKeys[a].Time := 0;
6018 end;
6019 end;
6021 procedure TPlayer.OnDamage(Angle: SmallInt);
6022 begin
6023 end;
6025 function TPlayer.firediry(): Integer;
6026 begin
6027 if FKeys[KEY_UP].Pressed then Result := -42
6028 else if FKeys[KEY_DOWN].Pressed then Result := 19
6029 else Result := 0;
6030 end;
6032 procedure TPlayer.RememberState();
6033 var
6034 i: Integer;
6035 SavedState: TPlayerSavedState;
6036 begin
6037 SavedState.Health := FHealth;
6038 SavedState.Armor := FArmor;
6039 SavedState.Air := FAir;
6040 SavedState.JetFuel := FJetFuel;
6041 SavedState.CurrWeap := FCurrWeap;
6042 SavedState.NextWeap := FNextWeap;
6043 SavedState.NextWeapDelay := FNextWeapDelay;
6044 for i := Low(FWeapon) to High(FWeapon) do
6045 SavedState.Weapon[i] := FWeapon[i];
6046 for i := Low(FAmmo) to High(FAmmo) do
6047 SavedState.Ammo[i] := FAmmo[i];
6048 for i := Low(FMaxAmmo) to High(FMaxAmmo) do
6049 SavedState.MaxAmmo[i] := FMaxAmmo[i];
6050 SavedState.Rulez := FRulez - [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE];
6052 FSavedStateNum := -1;
6053 for i := Low(SavedStates) to High(SavedStates) do
6054 if not SavedStates[i].Used then
6055 begin
6056 FSavedStateNum := i;
6057 break;
6058 end;
6059 if FSavedStateNum < 0 then
6060 begin
6061 SetLength(SavedStates, Length(SavedStates) + 1);
6062 FSavedStateNum := High(SavedStates);
6063 end;
6065 SavedState.Used := True;
6066 SavedStates[FSavedStateNum] := SavedState;
6067 end;
6069 procedure TPlayer.RecallState();
6070 var
6071 i: Integer;
6072 SavedState: TPlayerSavedState;
6073 begin
6074 if(FSavedStateNum < 0) or (FSavedStateNum > High(SavedStates)) then
6075 Exit;
6077 SavedState := SavedStates[FSavedStateNum];
6078 SavedStates[FSavedStateNum].Used := False;
6079 FSavedStateNum := -1;
6081 FHealth := SavedState.Health;
6082 FArmor := SavedState.Armor;
6083 FAir := SavedState.Air;
6084 FJetFuel := SavedState.JetFuel;
6085 FCurrWeap := SavedState.CurrWeap;
6086 FNextWeap := SavedState.NextWeap;
6087 FNextWeapDelay := SavedState.NextWeapDelay;
6088 for i := Low(FWeapon) to High(FWeapon) do
6089 FWeapon[i] := SavedState.Weapon[i];
6090 for i := Low(FAmmo) to High(FAmmo) do
6091 FAmmo[i] := SavedState.Ammo[i];
6092 for i := Low(FMaxAmmo) to High(FMaxAmmo) do
6093 FMaxAmmo[i] := SavedState.MaxAmmo[i];
6094 FRulez := SavedState.Rulez;
6096 if gGameSettings.GameType = GT_SERVER then
6097 MH_SEND_PlayerStats(FUID);
6098 end;
6100 procedure TPlayer.SaveState (st: TStream);
6101 var
6102 i: Integer;
6103 b: Byte;
6104 begin
6105 // Ñèãíàòóðà èãðîêà
6106 utils.writeSign(st, 'PLYR');
6107 utils.writeInt(st, Byte(PLR_SAVE_VERSION)); // version
6108 // Áîò èëè ÷åëîâåê
6109 utils.writeBool(st, FIamBot);
6110 // UID èãðîêà
6111 utils.writeInt(st, Word(FUID));
6112 // Èìÿ èãðîêà
6113 utils.writeStr(st, FName);
6114 // Êîìàíäà
6115 utils.writeInt(st, Byte(FTeam));
6116 // Æèâ ëè
6117 utils.writeBool(st, FAlive);
6118 // Èçðàñõîäîâàë ëè âñå æèçíè
6119 utils.writeBool(st, FNoRespawn);
6120 // Íàïðàâëåíèå
6121 if FDirection = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT
6122 utils.writeInt(st, Byte(b));
6123 // Çäîðîâüå
6124 utils.writeInt(st, LongInt(FHealth));
6125 // Êîýôôèöèåíò èíâàëèäíîñòè
6126 utils.writeInt(st, LongInt(FHandicap));
6127 // Æèçíè
6128 utils.writeInt(st, Byte(FLives));
6129 // Áðîíÿ
6130 utils.writeInt(st, LongInt(FArmor));
6131 // Çàïàñ âîçäóõà
6132 utils.writeInt(st, LongInt(FAir));
6133 // Çàïàñ ãîðþ÷åãî
6134 utils.writeInt(st, LongInt(FJetFuel));
6135 // Áîëü
6136 utils.writeInt(st, LongInt(FPain));
6137 // Óáèë
6138 utils.writeInt(st, LongInt(FKills));
6139 // Óáèë ìîíñòðîâ
6140 utils.writeInt(st, LongInt(FMonsterKills));
6141 // Ôðàãîâ
6142 utils.writeInt(st, LongInt(FFrags));
6143 // Ôðàãîâ ïîäðÿä
6144 utils.writeInt(st, Byte(FFragCombo));
6145 // Âðåìÿ ïîñëåäíåãî ôðàãà
6146 utils.writeInt(st, LongWord(FLastFrag));
6147 // Ñìåðòåé
6148 utils.writeInt(st, LongInt(FDeath));
6149 // Êàêîé ôëàã íåñåò
6150 utils.writeInt(st, Byte(FFlag));
6151 // Íàøåë ñåêðåòîâ
6152 utils.writeInt(st, LongInt(FSecrets));
6153 // Òåêóùåå îðóæèå
6154 utils.writeInt(st, Byte(FCurrWeap));
6155 // Æåëàåìîå îðóæèå
6156 utils.writeInt(st, Word(FNextWeap));
6157 // ...è ïàóçà
6158 utils.writeInt(st, Byte(FNextWeapDelay));
6159 // Âðåìÿ çàðÿäêè BFG
6160 utils.writeInt(st, SmallInt(FBFGFireCounter));
6161 // Áóôåð óðîíà
6162 utils.writeInt(st, LongInt(FDamageBuffer));
6163 // Ïîñëåäíèé óäàðèâøèé
6164 utils.writeInt(st, Word(FLastSpawnerUID));
6165 // Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà
6166 utils.writeInt(st, Byte(FLastHit));
6167 // Îáúåêò èãðîêà
6168 Obj_SaveState(st, @FObj);
6169 // Òåêóùåå êîëè÷åñòâî ïàòðîíîâ
6170 for i := A_BULLETS to A_HIGH do utils.writeInt(st, Word(FAmmo[i]));
6171 // Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ
6172 for i := A_BULLETS to A_HIGH do utils.writeInt(st, Word(FMaxAmmo[i]));
6173 // Íàëè÷èå îðóæèÿ
6174 for i := WP_FIRST to WP_LAST do utils.writeBool(st, FWeapon[i]);
6175 // Âðåìÿ ïåðåçàðÿäêè îðóæèÿ
6176 for i := WP_FIRST to WP_LAST do utils.writeInt(st, Word(FReloading[i]));
6177 // Íàëè÷èå ðþêçàêà
6178 utils.writeBool(st, (R_ITEM_BACKPACK in FRulez));
6179 // Íàëè÷èå êðàñíîãî êëþ÷à
6180 utils.writeBool(st, (R_KEY_RED in FRulez));
6181 // Íàëè÷èå çåëåíîãî êëþ÷à
6182 utils.writeBool(st, (R_KEY_GREEN in FRulez));
6183 // Íàëè÷èå ñèíåãî êëþ÷à
6184 utils.writeBool(st, (R_KEY_BLUE in FRulez));
6185 // Íàëè÷èå áåðñåðêà
6186 utils.writeBool(st, (R_BERSERK in FRulez));
6187 // Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ
6188 for i := MR_SUIT to MR_MAX do utils.writeInt(st, LongWord(FMegaRulez[i]));
6189 // Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà
6190 for i := T_RESPAWN to T_FLAGCAP do utils.writeInt(st, LongWord(FTime[i]));
6191 // Íàçâàíèå ìîäåëè
6192 utils.writeStr(st, FModel.Name);
6193 // Öâåò ìîäåëè
6194 utils.writeInt(st, Byte(FColor.R));
6195 utils.writeInt(st, Byte(FColor.G));
6196 utils.writeInt(st, Byte(FColor.B));
6197 end;
6200 procedure TPlayer.LoadState (st: TStream);
6201 var
6202 i: Integer;
6203 str: String;
6204 b: Byte;
6205 begin
6206 assert(st <> nil);
6208 // Ñèãíàòóðà èãðîêà
6209 if not utils.checkSign(st, 'PLYR') then raise XStreamError.Create('invalid player signature');
6210 if (utils.readByte(st) <> PLR_SAVE_VERSION) then raise XStreamError.Create('invalid player version');
6211 // Áîò èëè ÷åëîâåê:
6212 FIamBot := utils.readBool(st);
6213 // UID èãðîêà
6214 FUID := utils.readWord(st);
6215 // Èìÿ èãðîêà
6216 str := utils.readStr(st);
6217 if (self <> gPlayer1) and (self <> gPlayer2) then FName := str;
6218 // Êîìàíäà
6219 FTeam := utils.readByte(st);
6220 // Æèâ ëè
6221 FAlive := utils.readBool(st);
6222 // Èçðàñõîäîâàë ëè âñå æèçíè
6223 FNoRespawn := utils.readBool(st);
6224 // Íàïðàâëåíèå
6225 b := utils.readByte(st);
6226 if b = 1 then FDirection := TDirection.D_LEFT else FDirection := TDirection.D_RIGHT; // b = 2
6227 // Çäîðîâüå
6228 FHealth := utils.readLongInt(st);
6229 // Êîýôôèöèåíò èíâàëèäíîñòè
6230 FHandicap := utils.readLongInt(st);
6231 // Æèçíè
6232 FLives := utils.readByte(st);
6233 // Áðîíÿ
6234 FArmor := utils.readLongInt(st);
6235 // Çàïàñ âîçäóõà
6236 FAir := utils.readLongInt(st);
6237 // Çàïàñ ãîðþ÷åãî
6238 FJetFuel := utils.readLongInt(st);
6239 // Áîëü
6240 FPain := utils.readLongInt(st);
6241 // Óáèë
6242 FKills := utils.readLongInt(st);
6243 // Óáèë ìîíñòðîâ
6244 FMonsterKills := utils.readLongInt(st);
6245 // Ôðàãîâ
6246 FFrags := utils.readLongInt(st);
6247 // Ôðàãîâ ïîäðÿä
6248 FFragCombo := utils.readByte(st);
6249 // Âðåìÿ ïîñëåäíåãî ôðàãà
6250 FLastFrag := utils.readLongWord(st);
6251 // Ñìåðòåé
6252 FDeath := utils.readLongInt(st);
6253 // Êàêîé ôëàã íåñåò
6254 FFlag := utils.readByte(st);
6255 // Íàøåë ñåêðåòîâ
6256 FSecrets := utils.readLongInt(st);
6257 // Òåêóùåå îðóæèå
6258 FCurrWeap := utils.readByte(st);
6259 // Æåëàåìîå îðóæèå
6260 FNextWeap := utils.readWord(st);
6261 // ...è ïàóçà
6262 FNextWeapDelay := utils.readByte(st);
6263 // Âðåìÿ çàðÿäêè BFG
6264 FBFGFireCounter := utils.readSmallInt(st);
6265 // Áóôåð óðîíà
6266 FDamageBuffer := utils.readLongInt(st);
6267 // Ïîñëåäíèé óäàðèâøèé
6268 FLastSpawnerUID := utils.readWord(st);
6269 // Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà
6270 FLastHit := utils.readByte(st);
6271 // Îáúåêò èãðîêà
6272 Obj_LoadState(@FObj, st);
6273 // Òåêóùåå êîëè÷åñòâî ïàòðîíîâ
6274 for i := A_BULLETS to A_HIGH do FAmmo[i] := utils.readWord(st);
6275 // Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ
6276 for i := A_BULLETS to A_HIGH do FMaxAmmo[i] := utils.readWord(st);
6277 // Íàëè÷èå îðóæèÿ
6278 for i := WP_FIRST to WP_LAST do FWeapon[i] := utils.readBool(st);
6279 // Âðåìÿ ïåðåçàðÿäêè îðóæèÿ
6280 for i := WP_FIRST to WP_LAST do FReloading[i] := utils.readWord(st);
6281 // Íàëè÷èå ðþêçàêà
6282 if utils.readBool(st) then Include(FRulez, R_ITEM_BACKPACK);
6283 // Íàëè÷èå êðàñíîãî êëþ÷à
6284 if utils.readBool(st) then Include(FRulez, R_KEY_RED);
6285 // Íàëè÷èå çåëåíîãî êëþ÷à
6286 if utils.readBool(st) then Include(FRulez, R_KEY_GREEN);
6287 // Íàëè÷èå ñèíåãî êëþ÷à
6288 if utils.readBool(st) then Include(FRulez, R_KEY_BLUE);
6289 // Íàëè÷èå áåðñåðêà
6290 if utils.readBool(st) then Include(FRulez, R_BERSERK);
6291 // Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ
6292 for i := MR_SUIT to MR_MAX do FMegaRulez[i] := utils.readLongWord(st);
6293 // Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà
6294 for i := T_RESPAWN to T_FLAGCAP do FTime[i] := utils.readLongWord(st);
6295 // Íàçâàíèå ìîäåëè
6296 str := utils.readStr(st);
6297 // Öâåò ìîäåëè
6298 FColor.R := utils.readByte(st);
6299 FColor.G := utils.readByte(st);
6300 FColor.B := utils.readByte(st);
6301 if (self = gPlayer1) then
6302 begin
6303 str := gPlayer1Settings.Model;
6304 FColor := gPlayer1Settings.Color;
6305 end
6306 else if (self = gPlayer2) then
6307 begin
6308 str := gPlayer2Settings.Model;
6309 FColor := gPlayer2Settings.Color;
6310 end;
6311 // Îáíîâëÿåì ìîäåëü èãðîêà
6312 SetModel(str);
6313 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
6314 FModel.Color := TEAMCOLOR[FTeam]
6315 else
6316 FModel.Color := FColor;
6317 end;
6320 procedure TPlayer.AllRulez(Health: Boolean);
6321 var
6322 a: Integer;
6323 begin
6324 if Health then
6325 begin
6326 FHealth := PLAYER_HP_LIMIT;
6327 FArmor := PLAYER_AP_LIMIT;
6328 Exit;
6329 end;
6331 for a := WP_FIRST to WP_LAST do FWeapon[a] := True;
6332 for a := A_BULLETS to A_HIGH do FAmmo[a] := 30000;
6333 FRulez := FRulez+[R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE];
6334 end;
6336 procedure TPlayer.RestoreHealthArmor();
6337 begin
6338 FHealth := PLAYER_HP_LIMIT;
6339 FArmor := PLAYER_AP_LIMIT;
6340 end;
6342 procedure TPlayer.FragCombo();
6343 var
6344 Param: Integer;
6345 begin
6346 if (gGameSettings.GameMode in [GM_COOP, GM_SINGLE]) or g_Game_IsClient then
6347 Exit;
6348 if gTime - FLastFrag < FRAG_COMBO_TIME then
6349 begin
6350 if FFragCombo < 5 then
6351 Inc(FFragCombo);
6352 Param := FUID or (FFragCombo shl 16);
6353 if (FComboEvnt >= Low(gDelayedEvents)) and
6354 (FComboEvnt <= High(gDelayedEvents)) and
6355 gDelayedEvents[FComboEvnt].Pending and
6356 (gDelayedEvents[FComboEvnt].DEType = DE_KILLCOMBO) and
6357 (gDelayedEvents[FComboEvnt].DENum and $FFFF = FUID) then
6358 begin
6359 gDelayedEvents[FComboEvnt].Time := gTime + 500;
6360 gDelayedEvents[FComboEvnt].DENum := Param;
6361 end
6362 else
6363 FComboEvnt := g_Game_DelayEvent(DE_KILLCOMBO, 500, Param);
6364 end
6365 else
6366 FFragCombo := 1;
6368 FLastFrag := gTime;
6369 end;
6371 procedure TPlayer.GiveItem(ItemType: Byte);
6372 begin
6373 case ItemType of
6374 ITEM_SUIT:
6375 if FMegaRulez[MR_SUIT] < gTime+PLAYER_SUIT_TIME then
6376 begin
6377 FMegaRulez[MR_SUIT] := gTime+PLAYER_SUIT_TIME;
6378 end;
6380 ITEM_OXYGEN:
6381 if FAir < AIR_MAX then
6382 begin
6383 FAir := AIR_MAX;
6384 end;
6386 ITEM_MEDKIT_BLACK:
6387 begin
6388 if not (R_BERSERK in FRulez) then
6389 begin
6390 Include(FRulez, R_BERSERK);
6391 if FBFGFireCounter < 1 then
6392 begin
6393 FCurrWeap := WEAPON_KASTET;
6394 resetWeaponQueue();
6395 FModel.SetWeapon(WEAPON_KASTET);
6396 end;
6397 if gFlash <> 0 then
6398 Inc(FPain, 100);
6399 FBerserk := gTime+30000;
6400 end;
6401 if FHealth < PLAYER_HP_SOFT then
6402 begin
6403 FHealth := PLAYER_HP_SOFT;
6404 FBerserk := gTime+30000;
6405 end;
6406 end;
6408 ITEM_INVUL:
6409 if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
6410 begin
6411 FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
6412 FSpawnInvul := 0;
6413 end;
6415 ITEM_INVIS:
6416 if FMegaRulez[MR_INVIS] < gTime+PLAYER_INVIS_TIME then
6417 begin
6418 FMegaRulez[MR_INVIS] := gTime+PLAYER_INVIS_TIME;
6419 end;
6421 ITEM_JETPACK:
6422 if FJetFuel < JET_MAX then
6423 begin
6424 FJetFuel := JET_MAX;
6425 end;
6427 ITEM_MEDKIT_SMALL: if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 10, PLAYER_HP_SOFT);
6428 ITEM_MEDKIT_LARGE: if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 25, PLAYER_HP_SOFT);
6430 ITEM_ARMOR_GREEN: if FArmor < PLAYER_AP_SOFT then FArmor := PLAYER_AP_SOFT;
6431 ITEM_ARMOR_BLUE: if FArmor < PLAYER_AP_LIMIT then FArmor := PLAYER_AP_LIMIT;
6433 ITEM_SPHERE_BLUE: if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 100, PLAYER_HP_LIMIT);
6434 ITEM_SPHERE_WHITE:
6435 if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) then
6436 begin
6437 if FHealth < PLAYER_HP_LIMIT then FHealth := PLAYER_HP_LIMIT;
6438 if FArmor < PLAYER_AP_LIMIT then FArmor := PLAYER_AP_LIMIT;
6439 end;
6441 ITEM_WEAPON_SAW: FWeapon[WEAPON_SAW] := True;
6442 ITEM_WEAPON_SHOTGUN1: FWeapon[WEAPON_SHOTGUN1] := True;
6443 ITEM_WEAPON_SHOTGUN2: FWeapon[WEAPON_SHOTGUN2] := True;
6444 ITEM_WEAPON_CHAINGUN: FWeapon[WEAPON_CHAINGUN] := True;
6445 ITEM_WEAPON_ROCKETLAUNCHER: FWeapon[WEAPON_ROCKETLAUNCHER] := True;
6446 ITEM_WEAPON_PLASMA: FWeapon[WEAPON_PLASMA] := True;
6447 ITEM_WEAPON_BFG: FWeapon[WEAPON_BFG] := True;
6448 ITEM_WEAPON_SUPERPULEMET: FWeapon[WEAPON_SUPERPULEMET] := True;
6449 ITEM_WEAPON_FLAMETHROWER: FWeapon[WEAPON_FLAMETHROWER] := True;
6451 ITEM_AMMO_BULLETS: if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
6452 ITEM_AMMO_BULLETS_BOX: if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
6453 ITEM_AMMO_SHELLS: if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
6454 ITEM_AMMO_SHELLS_BOX: if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 25, FMaxAmmo[A_SHELLS]);
6455 ITEM_AMMO_ROCKET: if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
6456 ITEM_AMMO_ROCKET_BOX: if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 5, FMaxAmmo[A_ROCKETS]);
6457 ITEM_AMMO_CELL: if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
6458 ITEM_AMMO_CELL_BIG: if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 100, FMaxAmmo[A_CELLS]);
6459 ITEM_AMMO_FUELCAN: if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
6461 ITEM_AMMO_BACKPACK:
6462 if (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or
6463 (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or
6464 (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or
6465 (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or
6466 (FMaxAmmo[A_FUEL] < AmmoLimits[1, A_FUEL]) then
6467 begin
6468 FMaxAmmo[A_BULLETS] := AmmoLimits[1, A_BULLETS];
6469 FMaxAmmo[A_SHELLS] := AmmoLimits[1, A_SHELLS];
6470 FMaxAmmo[A_ROCKETS] := AmmoLimits[1, A_ROCKETS];
6471 FMaxAmmo[A_CELLS] := AmmoLimits[1, A_CELLS];
6472 FMaxAmmo[A_FUEL] := AmmoLimits[1, A_FUEL];
6474 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
6475 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
6476 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
6477 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
6479 FRulez := FRulez + [R_ITEM_BACKPACK];
6480 end;
6482 ITEM_KEY_RED: if not (R_KEY_RED in FRulez) then Include(FRulez, R_KEY_RED);
6483 ITEM_KEY_GREEN: if not (R_KEY_GREEN in FRulez) then Include(FRulez, R_KEY_GREEN);
6484 ITEM_KEY_BLUE: if not (R_KEY_BLUE in FRulez) then Include(FRulez, R_KEY_BLUE);
6486 ITEM_BOTTLE: if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 4, PLAYER_HP_LIMIT);
6487 ITEM_HELMET: if FArmor < PLAYER_AP_LIMIT then IncMax(FArmor, 5, PLAYER_AP_LIMIT);
6489 else
6490 Exit;
6491 end;
6492 if g_Game_IsNet and g_Game_IsServer then
6493 MH_SEND_PlayerStats(FUID);
6494 end;
6496 procedure TPlayer.FlySmoke(Times: DWORD = 1);
6497 var
6498 id, i: DWORD;
6499 Anim: TAnimation;
6500 begin
6501 if (Random(5) = 1) and (Times = 1) then
6502 Exit;
6504 if BodyInLiquid(0, 0) then
6505 begin
6506 g_GFX_Bubbles(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)+Random(3)-1,
6507 Obj.Y+Obj.Rect.Height+8, 1, 8, 4);
6508 if Random(2) = 0 then
6509 g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
6510 else
6511 g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
6512 Exit;
6513 end;
6515 if g_Frames_Get(id, 'FRAMES_SMOKE') then
6516 begin
6517 for i := 1 to Times do
6518 begin
6519 Anim := TAnimation.Create(id, False, 3);
6520 Anim.Alpha := 150;
6521 g_GFX_OnceAnim(Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(Anim.Width div 2),
6522 Obj.Y+Obj.Rect.Height-4+Random(8+Times*2), Anim, ONCEANIM_SMOKE);
6523 Anim.Free();
6524 end;
6525 end;
6526 end;
6528 procedure TPlayer.OnFireFlame(Times: DWORD = 1);
6529 var
6530 id, i: DWORD;
6531 Anim: TAnimation;
6532 begin
6533 if (Random(10) = 1) and (Times = 1) then
6534 Exit;
6536 if g_Frames_Get(id, 'FRAMES_FLAME') then
6537 begin
6538 for i := 1 to Times do
6539 begin
6540 Anim := TAnimation.Create(id, False, 3);
6541 Anim.Alpha := 0;
6542 g_GFX_OnceAnim(Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(Anim.Width div 2),
6543 Obj.Y+8+Random(8+Times*2), Anim, ONCEANIM_SMOKE);
6544 Anim.Free();
6545 end;
6546 end;
6547 end;
6549 procedure TPlayer.PauseSounds(Enable: Boolean);
6550 begin
6551 FSawSound.Pause(Enable);
6552 FSawSoundIdle.Pause(Enable);
6553 FSawSoundHit.Pause(Enable);
6554 FSawSoundSelect.Pause(Enable);
6555 FFlameSoundOn.Pause(Enable);
6556 FFlameSoundOff.Pause(Enable);
6557 FFlameSoundWork.Pause(Enable);
6558 FJetSoundFly.Pause(Enable);
6559 FJetSoundOn.Pause(Enable);
6560 FJetSoundOff.Pause(Enable);
6561 end;
6563 { T C o r p s e : }
6565 constructor TCorpse.Create(X, Y: Integer; ModelName: String; aMess: Boolean);
6566 begin
6567 g_Obj_Init(@FObj);
6568 FObj.X := X;
6569 FObj.Y := Y;
6570 FObj.Rect := PLAYER_CORPSERECT;
6571 FModelName := ModelName;
6572 FMess := aMess;
6574 if FMess then
6575 begin
6576 FState := CORPSE_STATE_MESS;
6577 g_PlayerModel_GetAnim(ModelName, A_DIE2, FAnimation, FAnimationMask);
6578 end
6579 else
6580 begin
6581 FState := CORPSE_STATE_NORMAL;
6582 g_PlayerModel_GetAnim(ModelName, A_DIE1, FAnimation, FAnimationMask);
6583 end;
6584 end;
6586 destructor TCorpse.Destroy();
6587 begin
6588 FAnimation.Free();
6590 inherited;
6591 end;
6593 function TCorpse.ObjPtr (): PObj; inline; begin result := @FObj; end;
6595 procedure TCorpse.positionChanged (); inline; begin end;
6597 procedure TCorpse.moveBy (dx, dy: Integer); inline;
6598 begin
6599 if (dx <> 0) or (dy <> 0) then
6600 begin
6601 FObj.X += dx;
6602 FObj.Y += dy;
6603 positionChanged();
6604 end;
6605 end;
6608 procedure TCorpse.getMapBox (out x, y, w, h: Integer); inline;
6609 begin
6610 x := FObj.X+PLAYER_CORPSERECT.X;
6611 y := FObj.Y+PLAYER_CORPSERECT.Y;
6612 w := PLAYER_CORPSERECT.Width;
6613 h := PLAYER_CORPSERECT.Height;
6614 end;
6617 procedure TCorpse.Damage(Value: Word; vx, vy: Integer);
6618 var
6619 pm: TPlayerModel;
6620 Blood: TModelBlood;
6621 begin
6622 if FState = CORPSE_STATE_REMOVEME then
6623 Exit;
6625 FDamage := FDamage + Value;
6627 if FDamage > 150 then
6628 begin
6629 if FAnimation <> nil then
6630 begin
6631 FAnimation.Free();
6632 FAnimation := nil;
6634 FState := CORPSE_STATE_REMOVEME;
6636 g_Player_CreateGibs(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
6637 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
6638 FModelName, FColor);
6639 // Çâóê ìÿñà îò òðóïà:
6640 pm := g_PlayerModel_Get(FModelName);
6641 pm.PlaySound(MODELSOUND_DIE, 5, FObj.X, FObj.Y);
6642 pm.Free;
6644 // Çëîâåùèé ñìåõ:
6645 if (gBodyKillEvent <> -1)
6646 and gDelayedEvents[gBodyKillEvent].Pending then
6647 gDelayedEvents[gBodyKillEvent].Pending := False;
6648 gBodyKillEvent := g_Game_DelayEvent(DE_BODYKILL, 1050, 0);
6649 end;
6650 end
6651 else
6652 begin
6653 Blood := g_PlayerModel_GetBlood(FModelName);
6654 FObj.Vel.X := FObj.Vel.X + vx;
6655 FObj.Vel.Y := FObj.Vel.Y + vy;
6656 g_GFX_Blood(FObj.X+PLAYER_CORPSERECT.X+(PLAYER_CORPSERECT.Width div 2),
6657 FObj.Y+PLAYER_CORPSERECT.Y+(PLAYER_CORPSERECT.Height div 2),
6658 Value, vx, vy, 16, (PLAYER_CORPSERECT.Height*2) div 3,
6659 Blood.R, Blood.G, Blood.B, Blood.Kind);
6660 end;
6661 end;
6663 procedure TCorpse.Draw();
6664 var
6665 fX, fY: Integer;
6666 begin
6667 if FState = CORPSE_STATE_REMOVEME then
6668 Exit;
6670 FObj.lerp(gLerpFactor, fX, fY);
6672 if FAnimation <> nil then
6673 FAnimation.Draw(fX, fY, TMirrorType.None);
6675 if FAnimationMask <> nil then
6676 begin
6677 e_Colors := FColor;
6678 FAnimationMask.Draw(fX, fY, TMirrorType.None);
6679 e_Colors.R := 255;
6680 e_Colors.G := 255;
6681 e_Colors.B := 255;
6682 end;
6683 end;
6685 procedure TCorpse.Update();
6686 var
6687 st: Word;
6688 begin
6689 if FState = CORPSE_STATE_REMOVEME then
6690 Exit;
6692 FObj.oldX := FObj.X;
6693 FObj.oldY := FObj.Y;
6695 if gTime mod (GAME_TICK*2) <> 0 then
6696 begin
6697 g_Obj_Move(@FObj, True, True, True);
6698 positionChanged(); // this updates spatial accelerators
6699 Exit;
6700 end;
6702 // Ñîïðîòèâëåíèå âîçäóõà äëÿ òðóïà:
6703 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
6705 st := g_Obj_Move(@FObj, True, True, True);
6706 positionChanged(); // this updates spatial accelerators
6708 if WordBool(st and MOVE_FALLOUT) then
6709 begin
6710 FState := CORPSE_STATE_REMOVEME;
6711 Exit;
6712 end;
6714 if FAnimation <> nil then
6715 FAnimation.Update();
6716 if FAnimationMask <> nil then
6717 FAnimationMask.Update();
6718 end;
6721 procedure TCorpse.SaveState (st: TStream);
6722 var
6723 anim: Boolean;
6724 begin
6725 assert(st <> nil);
6727 // Ñèãíàòóðà òðóïà
6728 utils.writeSign(st, 'CORP');
6729 utils.writeInt(st, Byte(0));
6730 // Ñîñòîÿíèå
6731 utils.writeInt(st, Byte(FState));
6732 // Íàêîïëåííûé óðîí
6733 utils.writeInt(st, Byte(FDamage));
6734 // Öâåò
6735 utils.writeInt(st, Byte(FColor.R));
6736 utils.writeInt(st, Byte(FColor.G));
6737 utils.writeInt(st, Byte(FColor.B));
6738 // Îáúåêò òðóïà
6739 Obj_SaveState(st, @FObj);
6740 utils.writeInt(st, Word(FPlayerUID));
6741 // Åñòü ëè àíèìàöèÿ
6742 anim := (FAnimation <> nil);
6743 utils.writeBool(st, anim);
6744 // Åñëè åñòü - ñîõðàíÿåì
6745 if anim then FAnimation.SaveState(st);
6746 // Åñòü ëè ìàñêà àíèìàöèè
6747 anim := (FAnimationMask <> nil);
6748 utils.writeBool(st, anim);
6749 // Åñëè åñòü - ñîõðàíÿåì
6750 if anim then FAnimationMask.SaveState(st);
6751 end;
6754 procedure TCorpse.LoadState (st: TStream);
6755 var
6756 anim: Boolean;
6757 begin
6758 assert(st <> nil);
6760 // Ñèãíàòóðà òðóïà
6761 if not utils.checkSign(st, 'CORP') then raise XStreamError.Create('invalid corpse signature');
6762 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid corpse version');
6763 // Ñîñòîÿíèå
6764 FState := utils.readByte(st);
6765 // Íàêîïëåííûé óðîí
6766 FDamage := utils.readByte(st);
6767 // Öâåò
6768 FColor.R := utils.readByte(st);
6769 FColor.G := utils.readByte(st);
6770 FColor.B := utils.readByte(st);
6771 // Îáúåêò òðóïà
6772 Obj_LoadState(@FObj, st);
6773 FPlayerUID := utils.readWord(st);
6774 // Åñòü ëè àíèìàöèÿ
6775 anim := utils.readBool(st);
6776 // Åñëè åñòü - çàãðóæàåì
6777 if anim then
6778 begin
6779 Assert(FAnimation <> nil, 'TCorpse.LoadState: no FAnimation');
6780 FAnimation.LoadState(st);
6781 end;
6782 // Åñòü ëè ìàñêà àíèìàöèè
6783 anim := utils.readBool(st);
6784 // Åñëè åñòü - çàãðóæàåì
6785 if anim then
6786 begin
6787 Assert(FAnimationMask <> nil, 'TCorpse.LoadState: no FAnimationMask');
6788 FAnimationMask.LoadState(st);
6789 end;
6790 end;
6792 { T B o t : }
6794 constructor TBot.Create();
6795 var
6796 a: Integer;
6797 begin
6798 inherited Create();
6800 FPhysics := True;
6801 FSpectator := False;
6802 FGhost := False;
6804 FIamBot := True;
6806 Inc(gNumBots);
6808 for a := WP_FIRST to WP_LAST do
6809 begin
6810 FDifficult.WeaponPrior[a] := WEAPON_PRIOR1[a];
6811 FDifficult.CloseWeaponPrior[a] := WEAPON_PRIOR2[a];
6812 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
6813 end;
6814 end;
6816 destructor TBot.Destroy();
6817 begin
6818 Dec(gNumBots);
6819 inherited Destroy();
6820 end;
6822 procedure TBot.Draw();
6823 begin
6824 inherited Draw();
6826 //if FTargetUID <> 0 then e_DrawLine(1, FObj.X, FObj.Y, g_Player_Get(FTargetUID).FObj.X,
6827 // g_Player_Get(FTargetUID).FObj.Y, 255, 0, 0);
6828 end;
6830 procedure TBot.Respawn(Silent: Boolean; Force: Boolean = False);
6831 begin
6832 inherited Respawn(Silent, Force);
6834 FAIFlags := nil;
6835 FSelectedWeapon := FCurrWeap;
6836 resetWeaponQueue();
6837 FTargetUID := 0;
6838 end;
6840 procedure TBot.UpdateCombat();
6841 type
6842 TTarget = record
6843 UID: Word;
6844 X, Y: Integer;
6845 Rect: TRectWH;
6846 cX, cY: Integer;
6847 Dist: Word;
6848 Line: Boolean;
6849 Visible: Boolean;
6850 IsPlayer: Boolean;
6851 end;
6853 TTargetRecord = array of TTarget;
6855 function Compare(a, b: TTarget): Integer;
6856 begin
6857 if a.Line and not b.Line then // A íà ëèíèè îãíÿ
6858 Result := -1
6859 else
6860 if not a.Line and b.Line then // B íà ëèíèè îãíÿ
6861 Result := 1
6862 else // È A, è B íà ëèíèè èëè íå íà ëèíèè îãíÿ
6863 if (a.Line and b.Line) or ((not a.Line) and (not b.Line)) then
6864 begin
6865 if a.Dist > b.Dist then // B áëèæå
6866 Result := 1
6867 else // A áëèæå èëè ðàâíîóäàëåííî ñ B
6868 Result := -1;
6869 end
6870 else // Ñòðàííî -> A
6871 Result := -1;
6872 end;
6874 var
6875 a, x1, y1, x2, y2: Integer;
6876 targets: TTargetRecord;
6877 ammo: Word;
6878 Target, BestTarget: TTarget;
6879 firew, fireh: Integer;
6880 angle: SmallInt;
6881 mon: TMonster;
6882 pla, tpla: TPlayer;
6883 vsPlayer, vsMonster, ok: Boolean;
6886 function monsUpdate (mon: TMonster): Boolean;
6887 begin
6888 result := false; // don't stop
6889 if mon.alive and (mon.MonsterType <> MONSTER_BARREL) then
6890 begin
6891 if not TargetOnScreen(mon.Obj.X+mon.Obj.Rect.X, mon.Obj.Y+mon.Obj.Rect.Y) then exit;
6893 x2 := mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2);
6894 y2 := mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2);
6896 // Åñëè ìîíñòð íà ýêðàíå è íå ïðèêðûò ñòåíîé
6897 if g_TraceVector(x1, y1, x2, y2) then
6898 begin
6899 // Äîáàâëÿåì ê ñïèñêó âîçìîæíûõ öåëåé
6900 SetLength(targets, Length(targets)+1);
6901 with targets[High(targets)] do
6902 begin
6903 UID := mon.UID;
6904 X := mon.Obj.X;
6905 Y := mon.Obj.Y;
6906 cX := x2;
6907 cY := y2;
6908 Rect := mon.Obj.Rect;
6909 Dist := g_PatchLength(x1, y1, x2, y2);
6910 Line := (y1+4 < Target.Y + mon.Obj.Rect.Y + mon.Obj.Rect.Height) and
6911 (y1-4 > Target.Y + mon.Obj.Rect.Y);
6912 Visible := True;
6913 IsPlayer := False;
6914 end;
6915 end;
6916 end;
6917 end;
6919 begin
6920 vsPlayer := LongBool(gGameSettings.Options and GAME_OPTION_BOTVSPLAYER);
6921 vsMonster := LongBool(gGameSettings.Options and GAME_OPTION_BOTVSMONSTER);
6923 // Åñëè òåêóùåå îðóæèå íå òî, ÷òî íóæíî, òî ìåíÿåì:
6924 if FCurrWeap <> FSelectedWeapon then
6925 NextWeapon();
6927 // Åñëè íóæíî ñòðåëÿòü è íóæíîå îðóæèå, òî íàæàòü "Ñòðåëÿòü":
6928 if (GetAIFlag('NEEDFIRE') <> '') and (FCurrWeap = FSelectedWeapon) then
6929 begin
6930 RemoveAIFlag('NEEDFIRE');
6932 case FCurrWeap of
6933 WEAPON_PLASMA, WEAPON_SUPERPULEMET, WEAPON_CHAINGUN: PressKey(KEY_FIRE, 20);
6934 WEAPON_SAW, WEAPON_KASTET, WEAPON_FLAMETHROWER: PressKey(KEY_FIRE, 40);
6935 else PressKey(KEY_FIRE);
6936 end;
6937 end;
6939 // Êîîðäèíàòû ñòâîëà:
6940 x1 := FObj.X + WEAPONPOINT[FDirection].X;
6941 y1 := FObj.Y + WEAPONPOINT[FDirection].Y;
6943 Target.UID := FTargetUID;
6945 ok := False;
6946 if Target.UID <> 0 then
6947 begin // Öåëü åñòü - íàñòðàèâàåì
6948 if (g_GetUIDType(Target.UID) = UID_PLAYER) and
6949 vsPlayer then
6950 begin // Èãðîê
6951 tpla := g_Player_Get(Target.UID);
6952 if tpla <> nil then
6953 with tpla do
6954 begin
6955 if (@FObj) <> nil then
6956 begin
6957 Target.X := FObj.X;
6958 Target.Y := FObj.Y;
6959 end;
6960 end;
6962 Target.cX := Target.X + PLAYER_RECT_CX;
6963 Target.cY := Target.Y + PLAYER_RECT_CY;
6964 Target.Rect := PLAYER_RECT;
6965 Target.Visible := g_TraceVector(x1, y1, Target.cX, Target.cY);
6966 Target.Line := (y1+4 < Target.Y+PLAYER_RECT.Y+PLAYER_RECT.Height) and
6967 (y1-4 > Target.Y+PLAYER_RECT.Y);
6968 Target.IsPlayer := True;
6969 ok := True;
6970 end
6971 else
6972 if (g_GetUIDType(Target.UID) = UID_MONSTER) and
6973 vsMonster then
6974 begin // Ìîíñòð
6975 mon := g_Monsters_ByUID(Target.UID);
6976 if mon <> nil then
6977 begin
6978 Target.X := mon.Obj.X;
6979 Target.Y := mon.Obj.Y;
6981 Target.cX := Target.X + mon.Obj.Rect.X + (mon.Obj.Rect.Width div 2);
6982 Target.cY := Target.Y + mon.Obj.Rect.Y + (mon.Obj.Rect.Height div 2);
6983 Target.Rect := mon.Obj.Rect;
6984 Target.Visible := g_TraceVector(x1, y1, Target.cX, Target.cY);
6985 Target.Line := (y1+4 < Target.Y + mon.Obj.Rect.Y + mon.Obj.Rect.Height) and
6986 (y1-4 > Target.Y + mon.Obj.Rect.Y);
6987 Target.IsPlayer := False;
6988 ok := True;
6989 end;
6990 end;
6991 end;
6993 if not ok then
6994 begin // Öåëè íåò - îáíóëÿåì
6995 Target.X := 0;
6996 Target.Y := 0;
6997 Target.cX := 0;
6998 Target.cY := 0;
6999 Target.Visible := False;
7000 Target.Line := False;
7001 Target.IsPlayer := False;
7002 end;
7004 targets := nil;
7006 // Åñëè öåëü íå âèäèìà èëè íå íà ëèíèè îãíÿ, òî èùåì âñå âîçìîæíûå öåëè:
7007 if (not Target.Line) or (not Target.Visible) then
7008 begin
7009 // Èãðîêè:
7010 if vsPlayer then
7011 for a := 0 to High(gPlayers) do
7012 if (gPlayers[a] <> nil) and (gPlayers[a].alive) and
7013 (gPlayers[a].FUID <> FUID) and
7014 (not SameTeam(FUID, gPlayers[a].FUID)) and
7015 (not gPlayers[a].NoTarget) and
7016 (gPlayers[a].FMegaRulez[MR_INVIS] < gTime) then
7017 begin
7018 if not TargetOnScreen(gPlayers[a].FObj.X + PLAYER_RECT.X,
7019 gPlayers[a].FObj.Y + PLAYER_RECT.Y) then
7020 Continue;
7022 x2 := gPlayers[a].FObj.X + PLAYER_RECT_CX;
7023 y2 := gPlayers[a].FObj.Y + PLAYER_RECT_CY;
7025 // Åñëè èãðîê íà ýêðàíå è íå ïðèêðûò ñòåíîé:
7026 if g_TraceVector(x1, y1, x2, y2) then
7027 begin
7028 // Äîáàâëÿåì ê ñïèñêó âîçìîæíûõ öåëåé:
7029 SetLength(targets, Length(targets)+1);
7030 with targets[High(targets)] do
7031 begin
7032 UID := gPlayers[a].FUID;
7033 X := gPlayers[a].FObj.X;
7034 Y := gPlayers[a].FObj.Y;
7035 cX := x2;
7036 cY := y2;
7037 Rect := PLAYER_RECT;
7038 Dist := g_PatchLength(x1, y1, x2, y2);
7039 Line := (y1+4 < Target.Y+PLAYER_RECT.Y+PLAYER_RECT.Height) and
7040 (y1-4 > Target.Y+PLAYER_RECT.Y);
7041 Visible := True;
7042 IsPlayer := True;
7043 end;
7044 end;
7045 end;
7047 // Ìîíñòðû:
7048 if vsMonster then g_Mons_ForEach(monsUpdate);
7049 end;
7051 // Åñëè åñòü âîçìîæíûå öåëè:
7052 // (Âûáèðàåì ëó÷øóþ, ìåíÿåì îðóæèå è áåæèì ê íåé/îò íåå)
7053 if targets <> nil then
7054 begin
7055 // Âûáèðàåì íàèëó÷øóþ öåëü:
7056 BestTarget := targets[0];
7057 if Length(targets) > 1 then
7058 for a := 1 to High(targets) do
7059 if Compare(BestTarget, targets[a]) = 1 then
7060 BestTarget := targets[a];
7062 // Åñëè ëó÷øàÿ öåëü "âèäíåå" òåêóùåé, òî òåêóùàÿ := ëó÷øàÿ:
7063 if ((not Target.Visible) and BestTarget.Visible and (Target.UID <> BestTarget.UID)) or
7064 ((not Target.Line) and BestTarget.Line and BestTarget.Visible) then
7065 begin
7066 Target := BestTarget;
7068 if (Healthy() = 3) or ((Healthy() = 2)) then
7069 begin // Åñëè çäîðîâû - äîãîíÿåì
7070 if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
7071 SetAIFlag('GORIGHT', '1');
7072 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
7073 SetAIFlag('GOLEFT', '1');
7074 end
7075 else
7076 begin // Åñëè ïîáèòû - óáåãàåì
7077 if ((RunDirection() = TDirection.D_LEFT) and (Target.X < FObj.X)) then
7078 SetAIFlag('GORIGHT', '1');
7079 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
7080 SetAIFlag('GOLEFT', '1');
7081 end;
7083 // Âûáèðàåì îðóæèå íà îñíîâå ðàññòîÿíèÿ è ïðèîðèòåòîâ:
7084 SelectWeapon(Abs(x1-Target.cX));
7085 end;
7086 end;
7088 // Åñëè åñòü öåëü:
7089 // (Äîãîíÿåì/óáåãàåì, ñòðåëÿåì ïî íàïðàâëåíèþ ê öåëè)
7090 // (Åñëè öåëü äàëåêî, òî õâàòèò ñëåäèòü çà íåé)
7091 if Target.UID <> 0 then
7092 begin
7093 if not TargetOnScreen(Target.X + Target.Rect.X,
7094 Target.Y + Target.Rect.Y) then
7095 begin // Öåëü ñáåæàëà ñ "ýêðàíà"
7096 if (Healthy() = 3) or ((Healthy() = 2)) then
7097 begin // Åñëè çäîðîâû - äîãîíÿåì
7098 if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
7099 SetAIFlag('GORIGHT', '1');
7100 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
7101 SetAIFlag('GOLEFT', '1');
7102 end
7103 else
7104 begin // Åñëè ïîáèòû - çàáûâàåì î öåëè è óáåãàåì
7105 Target.UID := 0;
7106 if ((RunDirection() = TDirection.D_LEFT) and (Target.X < FObj.X)) then
7107 SetAIFlag('GORIGHT', '1');
7108 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
7109 SetAIFlag('GOLEFT', '1');
7110 end;
7111 end
7112 else
7113 begin // Öåëü ïîêà íà "ýêðàíå"
7114 // Åñëè öåëü íå çàãîðîæåíà ñòåíîé, òî îòìå÷àåì, êîãäà åå âèäåëè:
7115 if g_TraceVector(x1, y1, Target.cX, Target.cY) then
7116 FLastVisible := gTime;
7117 // Åñëè ðàçíèöà âûñîò íå âåëèêà, òî äîãîíÿåì:
7118 if (Abs(FObj.Y-Target.Y) <= 128) then
7119 begin
7120 if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
7121 SetAIFlag('GORIGHT', '1');
7122 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
7123 SetAIFlag('GOLEFT', '1');
7124 end;
7125 end;
7127 // Âûáèðàåì óãîë ââåðõ:
7128 if FDirection = TDirection.D_LEFT then
7129 angle := ANGLE_LEFTUP
7130 else
7131 angle := ANGLE_RIGHTUP;
7133 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7134 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7136 // Åñëè ïðè óãëå ââåðõ ìîæíî ïîïàñòü â ïðèáëèçèòåëüíîå ïîëîæåíèå öåëè:
7137 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
7138 Target.X+Target.Rect.X+GetInterval(FDifficult.DiagPrecision, 128), //96
7139 Target.Y+Target.Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
7140 Target.Rect.Width, Target.Rect.Height) and
7141 g_TraceVector(x1, y1, Target.cX, Target.cY) then
7142 begin // òî íóæíî ñòðåëÿòü ââåðõ
7143 SetAIFlag('NEEDFIRE', '1');
7144 SetAIFlag('NEEDSEEUP', '1');
7145 end;
7147 // Âûáèðàåì óãîë âíèç:
7148 if FDirection = TDirection.D_LEFT then
7149 angle := ANGLE_LEFTDOWN
7150 else
7151 angle := ANGLE_RIGHTDOWN;
7153 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7154 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7156 // Åñëè ïðè óãëå âíèç ìîæíî ïîïàñòü â ïðèáëèçèòåëüíîå ïîëîæåíèå öåëè:
7157 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
7158 Target.X+Target.Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
7159 Target.Y+Target.Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
7160 Target.Rect.Width, Target.Rect.Height) and
7161 g_TraceVector(x1, y1, Target.cX, Target.cY) then
7162 begin // òî íóæíî ñòðåëÿòü âíèç
7163 SetAIFlag('NEEDFIRE', '1');
7164 SetAIFlag('NEEDSEEDOWN', '1');
7165 end;
7167 // Åñëè öåëü âèäíî è îíà íà òàêîé æå âûñîòå:
7168 if Target.Visible and
7169 (y1+4 < Target.Y+Target.Rect.Y+Target.Rect.Height) and
7170 (y1-4 > Target.Y+Target.Rect.Y) then
7171 begin
7172 // Åñëè èäåì â ñòîðîíó öåëè, òî íàäî ñòðåëÿòü:
7173 if ((FDirection = TDirection.D_LEFT) and (Target.X < FObj.X)) or
7174 ((FDirection = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
7175 begin // òî íóæíî ñòðåëÿòü âïåðåä
7176 SetAIFlag('NEEDFIRE', '1');
7177 SetAIFlag('NEEDSEEDOWN', '');
7178 SetAIFlag('NEEDSEEUP', '');
7179 end;
7180 // Åñëè öåëü â ïðåäåëàõ "ýêðàíà" è ñëîæíîñòü ïîçâîëÿåò ïðûæêè ñáëèæåíèÿ:
7181 if Abs(FObj.X-Target.X) < Trunc(gPlayerScreenSize.X*0.75) then
7182 if GetRnd(FDifficult.CloseJump) then
7183 begin // òî åñëè ïîâåçåò - ïðûãàåì (îñîáåííî, åñëè áëèçêî)
7184 if Abs(FObj.X-Target.X) < 128 then
7185 a := 4
7186 else
7187 a := 30;
7188 if Random(a) = 0 then
7189 SetAIFlag('NEEDJUMP', '1');
7190 end;
7191 end;
7193 // Åñëè öåëü âñå åùå åñòü:
7194 if Target.UID <> 0 then
7195 if gTime-FLastVisible > 2000 then // Åñëè âèäåëè äàâíî
7196 Target.UID := 0 // òî çàáûòü öåëü
7197 else // Åñëè âèäåëè íåäàâíî
7198 begin // íî öåëü óáèëè
7199 if Target.IsPlayer then
7200 begin // Öåëü - èãðîê
7201 pla := g_Player_Get(Target.UID);
7202 if (pla = nil) or (not pla.alive) or pla.NoTarget or
7203 (pla.FMegaRulez[MR_INVIS] >= gTime) then
7204 Target.UID := 0; // òî çàáûòü öåëü
7205 end
7206 else
7207 begin // Öåëü - ìîíñòð
7208 mon := g_Monsters_ByUID(Target.UID);
7209 if (mon = nil) or (not mon.alive) then
7210 Target.UID := 0; // òî çàáûòü öåëü
7211 end;
7212 end;
7213 end; // if Target.UID <> 0
7215 FTargetUID := Target.UID;
7217 // Åñëè âîçìîæíûõ öåëåé íåò:
7218 // (Àòàêà ÷åãî-íèáóäü ñëåâà èëè ñïðàâà)
7219 if targets = nil then
7220 if GetAIFlag('ATTACKLEFT') <> '' then
7221 begin // Åñëè íóæíî àòàêîâàòü íàëåâî
7222 RemoveAIFlag('ATTACKLEFT');
7224 SetAIFlag('NEEDJUMP', '1');
7226 if RunDirection() = TDirection.D_RIGHT then
7227 begin // Èäåì íå â òó ñòîðîíó
7228 if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then
7229 begin // Åñëè çäîðîâû, òî, âîçìîæíî, ñòðåëÿåì áåæèì âëåâî è ñòðåëÿåì
7230 SetAIFlag('NEEDFIRE', '1');
7231 SetAIFlag('GOLEFT', '1');
7232 end;
7233 end
7234 else
7235 begin // Èäåì â íóæíóþ ñòîðîíó
7236 if GetRnd(FDifficult.InvisFire) then // Âîçìîæíî, ñòðåëÿåì âñëåïóþ
7237 SetAIFlag('NEEDFIRE', '1');
7238 if Healthy() <= 1 then // Ïîáèòû - óáåãàåì
7239 SetAIFlag('GORIGHT', '1');
7240 end;
7241 end
7242 else
7243 if GetAIFlag('ATTACKRIGHT') <> '' then
7244 begin // Åñëè íóæíî àòàêîâàòü íàïðàâî
7245 RemoveAIFlag('ATTACKRIGHT');
7247 SetAIFlag('NEEDJUMP', '1');
7249 if RunDirection() = TDirection.D_LEFT then
7250 begin // Èäåì íå â òó ñòîðîíó
7251 if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then
7252 begin // Åñëè çäîðîâû, òî, âîçìîæíî, áåæèì âïðàâî è ñòðåëÿåì
7253 SetAIFlag('NEEDFIRE', '1');
7254 SetAIFlag('GORIGHT', '1');
7255 end;
7256 end
7257 else
7258 begin
7259 if GetRnd(FDifficult.InvisFire) then // Âîçìîæíî, ñòðåëÿåì âñëåïóþ
7260 SetAIFlag('NEEDFIRE', '1');
7261 if Healthy() <= 1 then // Ïîáèòû - óáåãàåì
7262 SetAIFlag('GOLEFT', '1');
7263 end;
7264 end;
7266 //HACK! (does it belongs there?)
7267 RealizeCurrentWeapon();
7269 // Åñëè åñòü âîçìîæíûå öåëè:
7270 // (Ñòðåëÿåì ïî íàïðàâëåíèþ ê öåëÿì)
7271 if (targets <> nil) and (GetAIFlag('NEEDFIRE') <> '') then
7272 for a := 0 to High(targets) do
7273 begin
7274 // Åñëè ìîæåì ñòðåëÿòü ïî äèàãîíàëè:
7275 if GetRnd(FDifficult.DiagFire) then
7276 begin
7277 // Èùåì öåëü ñâåðõó è ñòðåëÿåì, åñëè åñòü:
7278 if FDirection = TDirection.D_LEFT then
7279 angle := ANGLE_LEFTUP
7280 else
7281 angle := ANGLE_RIGHTUP;
7283 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7284 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7286 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
7287 targets[a].X+targets[a].Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
7288 targets[a].Y+targets[a].Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
7289 targets[a].Rect.Width, targets[a].Rect.Height) and
7290 g_TraceVector(x1, y1, targets[a].cX, targets[a].cY) then
7291 begin
7292 SetAIFlag('NEEDFIRE', '1');
7293 SetAIFlag('NEEDSEEUP', '1');
7294 end;
7296 // Èùåì öåëü ñíèçó è ñòðåëÿåì, åñëè åñòü:
7297 if FDirection = TDirection.D_LEFT then
7298 angle := ANGLE_LEFTDOWN
7299 else
7300 angle := ANGLE_RIGHTDOWN;
7302 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7303 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7305 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
7306 targets[a].X+targets[a].Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
7307 targets[a].Y+targets[a].Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
7308 targets[a].Rect.Width, targets[a].Rect.Height) and
7309 g_TraceVector(x1, y1, targets[a].cX, targets[a].cY) then
7310 begin
7311 SetAIFlag('NEEDFIRE', '1');
7312 SetAIFlag('NEEDSEEDOWN', '1');
7313 end;
7314 end;
7316 // Åñëè öåëü "ïåðåä íîñîì", òî ñòðåëÿåì:
7317 if targets[a].Line and targets[a].Visible and
7318 (((FDirection = TDirection.D_LEFT) and (targets[a].X < FObj.X)) or
7319 ((FDirection = TDirection.D_RIGHT) and (targets[a].X > FObj.X))) then
7320 begin
7321 SetAIFlag('NEEDFIRE', '1');
7322 Break;
7323 end;
7324 end;
7326 // Åñëè ëåòèò ïóëÿ, òî, âîçìîæíî, ïîäïðûãèâàåì:
7327 if g_Weapon_Danger(FUID, FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y,
7328 PLAYER_RECT.Width, PLAYER_RECT.Height,
7329 40+GetInterval(FDifficult.Cover, 40)) then
7330 SetAIFlag('NEEDJUMP', '1');
7332 // Åñëè êîí÷èëèñü ïàòîðíû, òî íóæíî ñìåíèòü îðóæèå:
7333 ammo := GetAmmoByWeapon(FCurrWeap);
7334 if ((FCurrWeap = WEAPON_SHOTGUN2) and (ammo < 2)) or
7335 ((FCurrWeap = WEAPON_BFG) and (ammo < 40)) or
7336 (ammo = 0) then
7337 SetAIFlag('SELECTWEAPON', '1');
7339 // Åñëè íóæíî ñìåíèòü îðóæèå, òî âûáèðàåì íóæíîå:
7340 if GetAIFlag('SELECTWEAPON') = '1' then
7341 begin
7342 SelectWeapon(-1);
7343 RemoveAIFlag('SELECTWEAPON');
7344 end;
7345 end;
7347 procedure TBot.Update();
7348 var
7349 EnableAI: Boolean;
7350 begin
7351 if not FAlive then
7352 begin // Respawn
7353 ReleaseKeys();
7354 PressKey(KEY_UP);
7355 end
7356 else
7357 begin
7358 EnableAI := True;
7360 // Ïðîâåðÿåì, îòêëþ÷¸í ëè AI áîòîâ
7361 if (g_debug_BotAIOff = 1) and (Team = TEAM_RED) then
7362 EnableAI := False;
7363 if (g_debug_BotAIOff = 2) and (Team = TEAM_BLUE) then
7364 EnableAI := False;
7365 if g_debug_BotAIOff = 3 then
7366 EnableAI := False;
7368 if EnableAI then
7369 begin
7370 UpdateMove();
7371 UpdateCombat();
7372 end
7373 else
7374 begin
7375 RealizeCurrentWeapon();
7376 end;
7377 end;
7379 inherited Update();
7380 end;
7382 procedure TBot.ReleaseKey(Key: Byte);
7383 begin
7384 with FKeys[Key] do
7385 begin
7386 Pressed := False;
7387 Time := 0;
7388 end;
7389 end;
7391 function TBot.KeyPressed(Key: Word): Boolean;
7392 begin
7393 Result := FKeys[Key].Pressed;
7394 end;
7396 function TBot.GetAIFlag(aName: String20): String20;
7397 var
7398 a: Integer;
7399 begin
7400 Result := '';
7402 aName := LowerCase(aName);
7404 if FAIFlags <> nil then
7405 for a := 0 to High(FAIFlags) do
7406 if LowerCase(FAIFlags[a].Name) = aName then
7407 begin
7408 Result := FAIFlags[a].Value;
7409 Break;
7410 end;
7411 end;
7413 procedure TBot.RemoveAIFlag(aName: String20);
7414 var
7415 a, b: Integer;
7416 begin
7417 if FAIFlags = nil then Exit;
7419 aName := LowerCase(aName);
7421 for a := 0 to High(FAIFlags) do
7422 if LowerCase(FAIFlags[a].Name) = aName then
7423 begin
7424 if a <> High(FAIFlags) then
7425 for b := a to High(FAIFlags)-1 do
7426 FAIFlags[b] := FAIFlags[b+1];
7428 SetLength(FAIFlags, Length(FAIFlags)-1);
7429 Break;
7430 end;
7431 end;
7433 procedure TBot.SetAIFlag(aName, fValue: String20);
7434 var
7435 a: Integer;
7436 ok: Boolean;
7437 begin
7438 a := 0;
7439 ok := False;
7441 aName := LowerCase(aName);
7443 if FAIFlags <> nil then
7444 for a := 0 to High(FAIFlags) do
7445 if LowerCase(FAIFlags[a].Name) = aName then
7446 begin
7447 ok := True;
7448 Break;
7449 end;
7451 if ok then FAIFlags[a].Value := fValue
7452 else
7453 begin
7454 SetLength(FAIFlags, Length(FAIFlags)+1);
7455 with FAIFlags[High(FAIFlags)] do
7456 begin
7457 Name := aName;
7458 Value := fValue;
7459 end;
7460 end;
7461 end;
7463 procedure TBot.UpdateMove;
7465 procedure GoLeft(Time: Word = 1);
7466 begin
7467 ReleaseKey(KEY_LEFT);
7468 ReleaseKey(KEY_RIGHT);
7469 PressKey(KEY_LEFT, Time);
7470 SetDirection(TDirection.D_LEFT);
7471 end;
7473 procedure GoRight(Time: Word = 1);
7474 begin
7475 ReleaseKey(KEY_LEFT);
7476 ReleaseKey(KEY_RIGHT);
7477 PressKey(KEY_RIGHT, Time);
7478 SetDirection(TDirection.D_RIGHT);
7479 end;
7481 function Rnd(a: Word): Boolean;
7482 begin
7483 Result := Random(a) = 0;
7484 end;
7486 procedure Turn(Time: Word = 1200);
7487 begin
7488 if RunDirection() = TDirection.D_LEFT then GoRight(Time) else GoLeft(Time);
7489 end;
7491 procedure Stop();
7492 begin
7493 ReleaseKey(KEY_LEFT);
7494 ReleaseKey(KEY_RIGHT);
7495 end;
7497 function CanRunLeft(): Boolean;
7498 begin
7499 Result := not CollideLevel(-1, 0);
7500 end;
7502 function CanRunRight(): Boolean;
7503 begin
7504 Result := not CollideLevel(1, 0);
7505 end;
7507 function CanRun(): Boolean;
7508 begin
7509 if RunDirection() = TDirection.D_LEFT then Result := CanRunLeft() else Result := CanRunRight();
7510 end;
7512 procedure Jump(Time: Word = 30);
7513 begin
7514 PressKey(KEY_JUMP, Time);
7515 end;
7517 function NearHole(): Boolean;
7518 var
7519 x, sx: Integer;
7520 begin
7521 { TODO 5 : Ëåñòíèöû }
7522 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
7523 for x := 1 to PLAYER_RECT.Width do
7524 if (not StayOnStep(x*sx, 0)) and
7525 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
7526 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
7527 begin
7528 Result := True;
7529 Exit;
7530 end;
7532 Result := False;
7533 end;
7535 function BorderHole(): Boolean;
7536 var
7537 x, sx, xx: Integer;
7538 begin
7539 { TODO 5 : Ëåñòíèöû }
7540 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
7541 for x := 1 to PLAYER_RECT.Width do
7542 if (not StayOnStep(x*sx, 0)) and
7543 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
7544 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
7545 begin
7546 for xx := x to x+32 do
7547 if CollideLevel(xx*sx, PLAYER_RECT.Height) then
7548 begin
7549 Result := True;
7550 Exit;
7551 end;
7552 end;
7554 Result := False;
7555 end;
7557 function NearDeepHole(): Boolean;
7558 var
7559 x, sx, y: Integer;
7560 begin
7561 Result := False;
7563 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
7564 y := 3;
7566 for x := 1 to PLAYER_RECT.Width do
7567 if (not StayOnStep(x*sx, 0)) and
7568 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
7569 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
7570 begin
7571 while FObj.Y+y*PLAYER_RECT.Height < gMapInfo.Height do
7572 begin
7573 if CollideLevel(x*sx, PLAYER_RECT.Height*y) then Exit;
7574 y := y+1;
7575 end;
7577 Result := True;
7578 end else Result := False;
7579 end;
7581 function OverDeepHole(): Boolean;
7582 var
7583 y: Integer;
7584 begin
7585 Result := False;
7587 y := 1;
7588 while FObj.Y+y*PLAYER_RECT.Height < gMapInfo.Height do
7589 begin
7590 if CollideLevel(0, PLAYER_RECT.Height*y) then Exit;
7591 y := y+1;
7592 end;
7594 Result := True;
7595 end;
7597 function OnGround(): Boolean;
7598 begin
7599 Result := StayOnStep(0, 0) or CollideLevel(0, 1);
7600 end;
7602 function OnLadder(): Boolean;
7603 begin
7604 Result := FullInStep(0, 0);
7605 end;
7607 function BelowLadder(): Boolean;
7608 begin
7609 Result := (FullInStep(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) and
7610 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or
7611 (FullInStep(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) and
7612 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP));
7613 end;
7615 function BelowLiftUp(): Boolean;
7616 begin
7617 Result := ((FullInLift(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) = -1) and
7618 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or
7619 ((FullInLift(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) = -1) and
7620 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP));
7621 end;
7623 function OnTopLift(): Boolean;
7624 begin
7625 Result := (FullInLift(0, 0) = -1) and (FullInLift(0, -32) = 0);
7626 end;
7628 function CanJumpOver(): Boolean;
7629 var
7630 sx, y: Integer;
7631 begin
7632 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
7634 Result := False;
7636 if not CollideLevel(sx, 0) then Exit;
7638 for y := 1 to BOT_MAXJUMP do
7639 if CollideLevel(0, -y) then Exit else
7640 if not CollideLevel(sx, -y) then
7641 begin
7642 Result := True;
7643 Exit;
7644 end;
7645 end;
7647 function CanJumpUp(Dist: ShortInt): Boolean;
7648 var
7649 y, yy: Integer;
7650 c: Boolean;
7651 begin
7652 Result := False;
7654 if CollideLevel(Dist, 0) then Exit;
7656 c := False;
7657 for y := 0 to BOT_MAXJUMP do
7658 if CollideLevel(Dist, -y) then
7659 begin
7660 c := True;
7661 Break;
7662 end;
7664 if not c then Exit;
7666 c := False;
7667 for yy := y+1 to BOT_MAXJUMP do
7668 if not CollideLevel(Dist, -yy) then
7669 begin
7670 c := True;
7671 Break;
7672 end;
7674 if not c then Exit;
7676 c := False;
7677 for y := 0 to BOT_MAXJUMP do
7678 if CollideLevel(0, -y) then
7679 begin
7680 c := True;
7681 Break;
7682 end;
7684 if c then Exit;
7686 if y < yy then Exit;
7688 Result := True;
7689 end;
7691 function IsSafeTrigger(): Boolean;
7692 var
7693 a: Integer;
7694 begin
7695 Result := True;
7696 if gTriggers = nil then
7697 Exit;
7698 for a := 0 to High(gTriggers) do
7699 if Collide(gTriggers[a].X,
7700 gTriggers[a].Y,
7701 gTriggers[a].Width,
7702 gTriggers[a].Height) and
7703 (gTriggers[a].TriggerType in [TRIGGER_EXIT, TRIGGER_CLOSEDOOR,
7704 TRIGGER_CLOSETRAP, TRIGGER_TRAP,
7705 TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF,
7706 TRIGGER_ONOFF, TRIGGER_SPAWNMONSTER,
7707 TRIGGER_DAMAGE, TRIGGER_SHOT]) then
7708 Result := False;
7709 end;
7711 begin
7712 // Âîçìîæíî, íàæèìàåì êíîïêó:
7713 if Rnd(16) and IsSafeTrigger() then
7714 PressKey(KEY_OPEN);
7716 // Åñëè ïîä ëèôòîì èëè ñòóïåíüêàìè, òî, âîçìîæíî, ïðûãàåì:
7717 if OnLadder() or ((BelowLadder() or BelowLiftUp()) and Rnd(8)) then
7718 begin
7719 ReleaseKey(KEY_LEFT);
7720 ReleaseKey(KEY_RIGHT);
7721 Jump();
7722 end;
7724 // Èäåì âëåâî, åñëè íàäî áûëî:
7725 if GetAIFlag('GOLEFT') <> '' then
7726 begin
7727 RemoveAIFlag('GOLEFT');
7728 if CanRunLeft() then
7729 GoLeft(360);
7730 end;
7732 // Èäåì âïðàâî, åñëè íàäî áûëî:
7733 if GetAIFlag('GORIGHT') <> '' then
7734 begin
7735 RemoveAIFlag('GORIGHT');
7736 if CanRunRight() then
7737 GoRight(360);
7738 end;
7740 // Åñëè âûëåòåëè çà êàðòó, òî ïðîáóåì âåðíóòüñÿ:
7741 if FObj.X < -32 then
7742 GoRight(360)
7743 else
7744 if FObj.X+32 > gMapInfo.Width then
7745 GoLeft(360);
7747 // Ïðûãàåì, åñëè íàäî áûëî:
7748 if GetAIFlag('NEEDJUMP') <> '' then
7749 begin
7750 Jump(0);
7751 RemoveAIFlag('NEEDJUMP');
7752 end;
7754 // Ñìîòðèì ââåðõ, åñëè íàäî áûëî:
7755 if GetAIFlag('NEEDSEEUP') <> '' then
7756 begin
7757 ReleaseKey(KEY_UP);
7758 ReleaseKey(KEY_DOWN);
7759 PressKey(KEY_UP, 20);
7760 RemoveAIFlag('NEEDSEEUP');
7761 end;
7763 // Ñìîòðèì âíèç, åñëè íàäî áûëî:
7764 if GetAIFlag('NEEDSEEDOWN') <> '' then
7765 begin
7766 ReleaseKey(KEY_UP);
7767 ReleaseKey(KEY_DOWN);
7768 PressKey(KEY_DOWN, 20);
7769 RemoveAIFlag('NEEDSEEDOWN');
7770 end;
7772 // Åñëè íóæíî áûëî â äûðó è ìû íå íà çåìëå, òî ïîêîðíî ëåòèì:
7773 if GetAIFlag('GOINHOLE') <> '' then
7774 if not OnGround() then
7775 begin
7776 ReleaseKey(KEY_LEFT);
7777 ReleaseKey(KEY_RIGHT);
7778 RemoveAIFlag('GOINHOLE');
7779 SetAIFlag('FALLINHOLE', '1');
7780 end;
7782 // Åñëè ïàäàëè è äîñòèãëè çåìëè, òî õâàòèò ïàäàòü:
7783 if GetAIFlag('FALLINHOLE') <> '' then
7784 if OnGround() then
7785 RemoveAIFlag('FALLINHOLE');
7787 // Åñëè ëåòåëè ïðÿìî è ñåé÷àñ íå íà ëåñòíèöå èëè íà âåðøèíå ëèôòà, òî îòõîäèì â ñòîðîíó:
7788 if not (KeyPressed(KEY_LEFT) or KeyPressed(KEY_RIGHT)) then
7789 if GetAIFlag('FALLINHOLE') = '' then
7790 if (not OnLadder()) or (FObj.Vel.Y >= 0) or (OnTopLift()) then
7791 if Rnd(2) then
7792 GoLeft(360)
7793 else
7794 GoRight(360);
7796 // Åñëè íà çåìëå è ìîæíî ïîäïðûãíóòü, òî, âîçìîæíî, ïðûãàåì:
7797 if OnGround() and
7798 CanJumpUp(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*32) and
7799 Rnd(8) then
7800 Jump();
7802 // Åñëè íà çåìëå è âîçëå äûðû (ãëóáèíà > 2 ðîñòîâ èãðîêà):
7803 if OnGround() and NearHole() then
7804 if NearDeepHole() then // Åñëè ýòî áåçäíà
7805 case Random(6) of
7806 0..3: Turn(); // Áåæèì îáðàòíî
7807 4: Jump(); // Ïðûãàåì
7808 5: begin // Ïðûãàåì îáðàòíî
7809 Turn();
7810 Jump();
7811 end;
7812 end
7813 else // Ýòî íå áåçäíà è ìû åùå íå ëåòèì òóäà
7814 if GetAIFlag('GOINHOLE') = '' then
7815 case Random(6) of
7816 0: Turn(); // Íå íóæíî òóäà
7817 1: Jump(); // Âäðóã ïîâåçåò - ïðûãàåì
7818 else // Åñëè ÿìà ñ ãðàíèöåé, òî ïðè ñëó÷àå ìîæíî òóäà ïðûãíóòü
7819 if BorderHole() then
7820 SetAIFlag('GOINHOLE', '1');
7821 end;
7823 // Åñëè íà çåìëå, íî íåêóäà èäòè:
7824 if (not CanRun()) and OnGround() then
7825 begin
7826 // Åñëè ìû íà ëåñòíèöå èëè ìîæíî ïåðåïðûãíóòü, òî ïðûãàåì:
7827 if CanJumpOver() or OnLadder() then
7828 Jump()
7829 else // èíà÷å ïîïûòàåìñÿ â äðóãóþ ñòîðîíó
7830 if Random(2) = 0 then
7831 begin
7832 if IsSafeTrigger() then
7833 PressKey(KEY_OPEN);
7834 end else
7835 Turn();
7836 end;
7838 // Îñòàëîñü ìàëî âîçäóõà:
7839 if FAir < 36 * 2 then
7840 Jump(20);
7842 // Âûáèðàåìñÿ èç êèñëîòû, åñëè íåò êîñòþìà, îáîæãëèñü, èëè ìàëî çäîðîâüÿ:
7843 if (FMegaRulez[MR_SUIT] < gTime) and ((FLastHit = HIT_ACID) or (Healthy() <= 1)) then
7844 if BodyInAcid(0, 0) then
7845 Jump();
7846 end;
7848 function TBot.FullInStep(XInc, YInc: Integer): Boolean;
7849 begin
7850 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
7851 PLAYER_RECT.Width, PLAYER_RECT.Height, PANEL_STEP, False);
7852 end;
7854 {function TBot.NeedItem(Item: Byte): Byte;
7855 begin
7856 Result := 4;
7857 end;}
7859 procedure TBot.SelectWeapon(Dist: Integer);
7860 var
7861 a: Integer;
7863 function HaveAmmo(weapon: Byte): Boolean;
7864 begin
7865 case weapon of
7866 WEAPON_PISTOL: Result := FAmmo[A_BULLETS] >= 1;
7867 WEAPON_SHOTGUN1: Result := FAmmo[A_SHELLS] >= 1;
7868 WEAPON_SHOTGUN2: Result := FAmmo[A_SHELLS] >= 2;
7869 WEAPON_CHAINGUN: Result := FAmmo[A_BULLETS] >= 10;
7870 WEAPON_ROCKETLAUNCHER: Result := FAmmo[A_ROCKETS] >= 1;
7871 WEAPON_PLASMA: Result := FAmmo[A_CELLS] >= 10;
7872 WEAPON_BFG: Result := FAmmo[A_CELLS] >= 40;
7873 WEAPON_SUPERPULEMET: Result := FAmmo[A_SHELLS] >= 1;
7874 WEAPON_FLAMETHROWER: Result := FAmmo[A_FUEL] >= 1;
7875 else Result := True;
7876 end;
7877 end;
7879 begin
7880 if Dist = -1 then Dist := BOT_LONGDIST;
7882 if Dist > BOT_LONGDIST then
7883 begin // Äàëüíèé áîé
7884 for a := 0 to 9 do
7885 if FWeapon[FDifficult.WeaponPrior[a]] and HaveAmmo(FDifficult.WeaponPrior[a]) then
7886 begin
7887 FSelectedWeapon := FDifficult.WeaponPrior[a];
7888 Break;
7889 end;
7890 end
7891 else //if Dist > BOT_UNSAFEDIST then
7892 begin // Áëèæíèé áîé
7893 for a := 0 to 9 do
7894 if FWeapon[FDifficult.CloseWeaponPrior[a]] and HaveAmmo(FDifficult.CloseWeaponPrior[a]) then
7895 begin
7896 FSelectedWeapon := FDifficult.CloseWeaponPrior[a];
7897 Break;
7898 end;
7899 end;
7900 { else
7901 begin
7902 for a := 0 to 9 do
7903 if FWeapon[FDifficult.SafeWeaponPrior[a]] and HaveAmmo(FDifficult.SafeWeaponPrior[a]) then
7904 begin
7905 FSelectedWeapon := FDifficult.SafeWeaponPrior[a];
7906 Break;
7907 end;
7908 end;}
7909 end;
7911 function TBot.PickItem(ItemType: Byte; force: Boolean; var remove: Boolean): Boolean;
7912 begin
7913 Result := inherited PickItem(ItemType, force, remove);
7915 if Result then SetAIFlag('SELECTWEAPON', '1');
7916 end;
7918 function TBot.Heal(value: Word; Soft: Boolean): Boolean;
7919 begin
7920 Result := inherited Heal(value, Soft);
7921 end;
7923 function TBot.Healthy(): Byte;
7924 begin
7925 if FMegaRulez[MR_INVUL] >= gTime then Result := 3
7926 else if (FHealth > 80) or ((FHealth > 50) and (FArmor > 20)) then Result := 3
7927 else if (FHealth > 50) then Result := 2
7928 else if (FHealth > 20) then Result := 1
7929 else Result := 0;
7930 end;
7932 function TBot.TargetOnScreen(TX, TY: Integer): Boolean;
7933 begin
7934 Result := (Abs(FObj.X-TX) <= Trunc(gPlayerScreenSize.X*0.6)) and
7935 (Abs(FObj.Y-TY) <= Trunc(gPlayerScreenSize.Y*0.6));
7936 end;
7938 procedure TBot.OnDamage(Angle: SmallInt);
7939 var
7940 pla: TPlayer;
7941 mon: TMonster;
7942 ok: Boolean;
7943 begin
7944 inherited;
7946 if (Angle = 0) or (Angle = 180) then
7947 begin
7948 ok := False;
7949 if (g_GetUIDType(FLastSpawnerUID) = UID_PLAYER) and
7950 LongBool(gGameSettings.Options and GAME_OPTION_BOTVSPLAYER) then
7951 begin // Èãðîê
7952 pla := g_Player_Get(FLastSpawnerUID);
7953 ok := not TargetOnScreen(pla.FObj.X + PLAYER_RECT.X,
7954 pla.FObj.Y + PLAYER_RECT.Y);
7955 end
7956 else
7957 if (g_GetUIDType(FLastSpawnerUID) = UID_MONSTER) and
7958 LongBool(gGameSettings.Options and GAME_OPTION_BOTVSMONSTER) then
7959 begin // Ìîíñòð
7960 mon := g_Monsters_ByUID(FLastSpawnerUID);
7961 ok := not TargetOnScreen(mon.Obj.X + mon.Obj.Rect.X,
7962 mon.Obj.Y + mon.Obj.Rect.Y);
7963 end;
7965 if ok then
7966 if Angle = 0 then
7967 SetAIFlag('ATTACKLEFT', '1')
7968 else
7969 SetAIFlag('ATTACKRIGHT', '1');
7970 end;
7971 end;
7973 function TBot.RunDirection(): TDirection;
7974 begin
7975 if Abs(Vel.X) >= 1 then
7976 begin
7977 if Vel.X > 0 then Result := TDirection.D_RIGHT else Result := TDirection.D_LEFT;
7978 end else
7979 Result := FDirection;
7980 end;
7982 function TBot.GetRnd(a: Byte): Boolean;
7983 begin
7984 if a = 0 then Result := False
7985 else if a = 255 then Result := True
7986 else Result := Random(256) > 255-a;
7987 end;
7989 function TBot.GetInterval(a: Byte; radius: SmallInt): SmallInt;
7990 begin
7991 Result := Round((255-a)/255*radius*(Random(2)-1));
7992 end;
7995 procedure TDifficult.save (st: TStream);
7996 begin
7997 utils.writeInt(st, Byte(DiagFire));
7998 utils.writeInt(st, Byte(InvisFire));
7999 utils.writeInt(st, Byte(DiagPrecision));
8000 utils.writeInt(st, Byte(FlyPrecision));
8001 utils.writeInt(st, Byte(Cover));
8002 utils.writeInt(st, Byte(CloseJump));
8003 st.WriteBuffer(WeaponPrior[Low(WeaponPrior)], sizeof(WeaponPrior));
8004 st.WriteBuffer(CloseWeaponPrior[Low(CloseWeaponPrior)], sizeof(CloseWeaponPrior));
8005 end;
8007 procedure TDifficult.load (st: TStream);
8008 begin
8009 DiagFire := utils.readByte(st);
8010 InvisFire := utils.readByte(st);
8011 DiagPrecision := utils.readByte(st);
8012 FlyPrecision := utils.readByte(st);
8013 Cover := utils.readByte(st);
8014 CloseJump := utils.readByte(st);
8015 st.ReadBuffer(WeaponPrior[Low(WeaponPrior)], sizeof(WeaponPrior));
8016 st.ReadBuffer(CloseWeaponPrior[Low(CloseWeaponPrior)], sizeof(CloseWeaponPrior));
8017 end;
8020 procedure TBot.SaveState (st: TStream);
8021 var
8022 i: Integer;
8023 dw: Integer;
8024 begin
8025 inherited SaveState(st);
8026 utils.writeSign(st, 'BOT0');
8027 // Âûáðàííîå îðóæèå
8028 utils.writeInt(st, Byte(FSelectedWeapon));
8029 // UID öåëè
8030 utils.writeInt(st, Word(FTargetUID));
8031 // Âðåìÿ ïîòåðè öåëè
8032 utils.writeInt(st, LongWord(FLastVisible));
8033 // Êîëè÷åñòâî ôëàãîâ ÈÈ
8034 dw := Length(FAIFlags);
8035 utils.writeInt(st, LongInt(dw));
8036 // Ôëàãè ÈÈ
8037 for i := 0 to dw-1 do
8038 begin
8039 utils.writeStr(st, FAIFlags[i].Name, 20);
8040 utils.writeStr(st, FAIFlags[i].Value, 20);
8041 end;
8042 // Íàñòðîéêè ñëîæíîñòè
8043 FDifficult.save(st);
8044 end;
8047 procedure TBot.LoadState (st: TStream);
8048 var
8049 i: Integer;
8050 dw: Integer;
8051 begin
8052 inherited LoadState(st);
8053 if not utils.checkSign(st, 'BOT0') then raise XStreamError.Create('invalid bot signature');
8054 // Âûáðàííîå îðóæèå
8055 FSelectedWeapon := utils.readByte(st);
8056 // UID öåëè
8057 FTargetUID := utils.readWord(st);
8058 // Âðåìÿ ïîòåðè öåëè
8059 FLastVisible := utils.readLongWord(st);
8060 // Êîëè÷åñòâî ôëàãîâ ÈÈ
8061 dw := utils.readLongInt(st);
8062 if (dw < 0) or (dw > 16384) then raise XStreamError.Create('invalid number of bot AI flags');
8063 SetLength(FAIFlags, dw);
8064 // Ôëàãè ÈÈ
8065 for i := 0 to dw-1 do
8066 begin
8067 FAIFlags[i].Name := utils.readStr(st, 20);
8068 FAIFlags[i].Value := utils.readStr(st, 20);
8069 end;
8070 // Íàñòðîéêè ñëîæíîñòè
8071 FDifficult.load(st);
8072 end;
8075 begin
8076 conRegVar('cheat_berserk_autoswitch', @gBerserkAutoswitch, 'autoswitch to fist when berserk pack taken', '', true, true);
8077 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');
8078 conRegVar('player_indicator_style', @gPlayerIndicatorStyle, 'Visual appearance of indicator', 'Visual appearance of indicator');
8079 end.