DEADSOFTWARE

game: disable shells for server
[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 g_base, 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 ANGLE_NONE = Low(SmallInt);
79 CORPSE_STATE_REMOVEME = 0;
80 CORPSE_STATE_NORMAL = 1;
81 CORPSE_STATE_MESS = 2;
83 PLAYER_RECT: TRectWH = (X:15; Y:12; Width:34; Height:52);
84 PLAYER_RECT_CX = 15+(34 div 2);
85 PLAYER_RECT_CY = 12+(52 div 2);
86 PLAYER_CORPSERECT: TRectWH = (X:15; Y:48; Width:34; Height:16);
88 PLAYER_HP_SOFT = 100;
89 PLAYER_HP_LIMIT = 200;
90 PLAYER_AP_SOFT = 100;
91 PLAYER_AP_LIMIT = 200;
92 SUICIDE_DAMAGE = 112;
93 WEAPON_DELAY = 5;
95 PLAYER_BURN_TIME = 110;
97 PLAYER1_DEF_COLOR: TRGB = (R:64; G:175; B:48);
98 PLAYER2_DEF_COLOR: TRGB = (R:96; G:96; B:96);
100 AIR_DEF = 360;
101 AIR_MAX = 1091;
102 JET_MAX = 540; // ~30 sec
103 ANGLE_RIGHTUP = 55;
104 ANGLE_RIGHTDOWN = -35;
105 ANGLE_LEFTUP = 125;
106 ANGLE_LEFTDOWN = -145;
107 WEAPONPOINT: Array [TDirection] of TDFPoint = ((X:16; Y:32), (X:47; Y:32));
108 TEAMCOLOR: Array [TEAM_RED..TEAM_BLUE] of TRGB = ((R:255; G:0; B:0),
109 (R:0; G:0; B:255));
111 type
112 TPlayerStat = record
113 Num: Integer;
114 Ping: Word;
115 Loss: Byte;
116 Name: String;
117 Team: Byte;
118 Frags: SmallInt;
119 Deaths: SmallInt;
120 Lives: Byte;
121 Kills: Word;
122 Color: TRGB;
123 Spectator: Boolean;
124 UID: Word;
125 end;
127 TPlayerStatArray = Array of TPlayerStat;
129 TPlayerSavedState = record
130 Health: Integer;
131 Armor: Integer;
132 Air: Integer;
133 JetFuel: Integer;
134 CurrWeap: Byte;
135 NextWeap: WORD;
136 NextWeapDelay: Byte;
137 Ammo: Array [A_BULLETS..A_HIGH] of Word;
138 MaxAmmo: Array [A_BULLETS..A_HIGH] of Word;
139 Weapon: Array [WP_FIRST..WP_LAST] of Boolean;
140 Rulez: Set of R_ITEM_BACKPACK..R_BERSERK;
141 Used: Boolean;
142 end;
144 TKeyState = record
145 Pressed: Boolean;
146 Time: Word;
147 end;
149 TPlayer = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
150 private
151 FIamBot: Boolean;
152 FUID: Word;
153 FName: String;
154 FTeam: Byte;
155 FAlive: Boolean;
156 FSpawned: Boolean;
157 FDirection: TDirection;
158 FHealth: Integer;
159 FLives: Byte;
160 FArmor: Integer;
161 FAir: Integer;
162 FPain: Integer;
163 FPickup: Integer;
164 FKills: Integer;
165 FMonsterKills: Integer;
166 FFrags: Integer;
167 FFragCombo: Byte;
168 FLastFrag: LongWord;
169 FComboEvnt: Integer;
170 FDeath: Integer;
171 FCanJetpack: Boolean;
172 FJetFuel: Integer;
173 FFlag: Byte;
174 FSecrets: Integer;
175 FCurrWeap: Byte;
176 FNextWeap: WORD;
177 FNextWeapDelay: Byte; // frames
178 FBFGFireCounter: SmallInt;
179 FLastSpawnerUID: Word;
180 FLastHit: Byte;
181 FObj: TObj;
182 FXTo, FYTo: Integer;
183 FSpectatePlayer: Integer;
184 FFirePainTime: Integer;
185 FFireAttacker: Word;
187 FSavedStateNum: Integer;
189 FModel: TPlayerModel;
190 FPunchAnim: TAnimationState;
191 FActionPrior: Byte;
192 FActionAnim: Byte;
193 FActionForce: Boolean;
194 FActionChanged: Boolean;
195 FAngle: SmallInt;
196 FFireAngle: SmallInt;
197 FIncCamOld: Integer;
198 FIncCam: Integer;
199 FSlopeOld: Integer;
200 {$IFDEF ENABLE_SHELLS}
201 FShellTimer: Integer;
202 FShellType: Byte;
203 {$ENDIF}
204 FSawSound: TPlayableSound;
205 FSawSoundIdle: TPlayableSound;
206 FSawSoundHit: TPlayableSound;
207 FSawSoundSelect: TPlayableSound;
208 FFlameSoundOn: TPlayableSound;
209 FFlameSoundOff: TPlayableSound;
210 FFlameSoundWork: TPlayableSound;
211 FJetSoundOn: TPlayableSound;
212 FJetSoundOff: TPlayableSound;
213 FJetSoundFly: TPlayableSound;
214 FGodMode: Boolean;
215 FNoTarget: Boolean;
216 FNoReload: Boolean;
217 FJustTeleported: Boolean;
218 FNetTime: LongWord;
219 mEDamageType: Integer;
222 function CollideLevel(XInc, YInc: Integer): Boolean;
223 function StayOnStep(XInc, YInc: Integer): Boolean;
224 function HeadInLiquid(XInc, YInc: Integer): Boolean;
225 function BodyInLiquid(XInc, YInc: Integer): Boolean;
226 function BodyInAcid(XInc, YInc: Integer): Boolean;
227 function FullInLift(XInc, YInc: Integer): Integer;
228 {procedure CollideItem();}
229 procedure FlySmoke(Times: DWORD = 1);
230 procedure OnFireFlame(Times: DWORD = 1);
231 procedure SetAction(Action: Byte; Force: Boolean = False);
232 procedure OnDamage(Angle: SmallInt); virtual;
233 function firediry(): Integer;
234 procedure DoPunch();
236 procedure Run(Direction: TDirection);
237 procedure NextWeapon();
238 procedure PrevWeapon();
239 procedure SeeUp();
240 procedure SeeDown();
241 procedure Fire();
242 procedure Jump();
243 procedure Use();
245 function getNextWeaponIndex (): Byte; // return 255 for "no switch"
246 procedure resetWeaponQueue ();
247 function hasAmmoForWeapon (weapon: Byte): Boolean;
249 procedure doDamage (v: Integer);
251 function refreshCorpse(): Boolean;
253 public
254 FDamageBuffer: Integer;
256 FAmmo: Array [A_BULLETS..A_HIGH] of Word;
257 FMaxAmmo: Array [A_BULLETS..A_HIGH] of Word;
258 FWeapon: Array [WP_FIRST..WP_LAST] of Boolean;
259 FRulez: Set of R_ITEM_BACKPACK..R_BERSERK;
260 FBerserk: Integer;
261 FMegaRulez: Array [MR_SUIT..MR_MAX] of DWORD;
262 FReloading: Array [WP_FIRST..WP_LAST] of Word;
263 FTime: Array [T_RESPAWN..T_FLAGCAP] of DWORD;
264 FKeys: Array [KEY_LEFT..KEY_CHAT] of TKeyState;
265 FColor: TRGB;
266 FPreferredTeam: Byte;
267 FSpectator: Boolean;
268 FNoRespawn: Boolean;
269 FWantsInGame: Boolean;
270 FGhost: Boolean;
271 FPhysics: Boolean;
272 FFlaming: Boolean;
273 FJetpack: Boolean;
274 FActualModelName: string;
275 FClientID: SmallInt;
276 FPing: Word;
277 FLoss: Byte;
278 FReady: Boolean;
279 FDummy: Boolean;
280 FFireTime: Integer;
281 FSpawnInvul: Integer;
282 FHandicap: Integer;
283 FWaitForFirstSpawn: Boolean; // set to `true` in server, used to spawn a player on first full state request
284 FCorpse: Integer;
286 // debug: viewport offset
287 viewPortX, viewPortY, viewPortW, viewPortH: Integer;
289 function isValidViewPort (): Boolean; inline;
291 constructor Create(); virtual;
292 destructor Destroy(); override;
293 procedure Respawn(Silent: Boolean; Force: Boolean = False); virtual;
294 function GetRespawnPoint(): Byte;
295 procedure PressKey(Key: Byte; Time: Word = 1);
296 procedure ReleaseKeys();
297 procedure SetModel(ModelName: String);
298 procedure SetColor(Color: TRGB);
299 function GetColor(): TRGB;
300 procedure SetWeapon(W: Byte);
301 function IsKeyPressed(K: Byte): Boolean;
302 function GetKeys(): Byte;
303 function PickItem(ItemType: Byte; arespawn: Boolean; var remove: Boolean): Boolean; virtual;
304 function Collide(X, Y: Integer; Width, Height: Word): Boolean; overload;
305 function Collide(Panel: TPanel): Boolean; overload;
306 function Collide(X, Y: Integer): Boolean; overload;
307 procedure SetDirection(Direction: TDirection);
308 procedure GetSecret();
309 function TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean;
310 procedure Touch();
311 procedure Push(vx, vy: Integer);
312 procedure ChangeModel(ModelName: String);
313 procedure SwitchTeam;
314 procedure ChangeTeam(Team: Byte);
315 procedure BFGHit();
316 function GetFlag(Flag: Byte): Boolean;
317 procedure SetFlag(Flag: Byte);
318 function DropFlag(Silent: Boolean = True): Boolean;
319 procedure AllRulez(Health: Boolean);
320 procedure RestoreHealthArmor();
321 procedure FragCombo();
322 procedure GiveItem(ItemType: Byte);
323 procedure Damage(value: Word; SpawnerUID: Word; vx, vy: Integer; t: Byte); virtual;
324 function Heal(value: Word; Soft: Boolean): Boolean; virtual;
325 procedure MakeBloodVector(Count: Word; VelX, VelY: Integer);
326 procedure MakeBloodSimple(Count: Word);
327 procedure Kill(KillType: Byte; SpawnerUID: Word; t: Byte);
328 procedure Reset(Force: Boolean);
329 procedure Spectate(NoMove: Boolean = False);
330 procedure SwitchNoClip;
331 procedure SoftReset();
332 procedure PreUpdate();
333 procedure Update(); virtual;
334 procedure RememberState();
335 procedure RecallState();
336 procedure SaveState (st: TStream); virtual;
337 procedure LoadState (st: TStream); virtual;
338 procedure PauseSounds(Enable: Boolean);
339 procedure NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1);
340 procedure DoLerp(Level: Integer = 2);
341 procedure SetLerp(XTo, YTo: Integer);
342 procedure QueueWeaponSwitch(Weapon: Byte);
343 procedure RealizeCurrentWeapon();
344 procedure FlamerOn;
345 procedure FlamerOff;
346 procedure JetpackOn;
347 procedure JetpackOff;
348 procedure CatchFire(Attacker: Word; Timeout: Integer = PLAYER_BURN_TIME);
350 //WARNING! this does nothing for now, but still call it!
351 procedure positionChanged (); //WARNING! call this after entity position was changed, or coldet will not work right!
353 procedure getMapBox (out x, y, w, h: Integer); inline;
354 procedure moveBy (dx, dy: Integer); inline;
356 function getCameraObj(): TObj;
358 function GetAmmoByWeapon(Weapon: Byte): Word; // private state
360 public
361 property Vel: TPoint2i read FObj.Vel;
362 property Obj: TObj read FObj;
364 property Name: String read FName write FName;
365 property Model: TPlayerModel read FModel;
366 property Health: Integer read FHealth write FHealth;
367 property Lives: Byte read FLives write FLives;
368 property Armor: Integer read FArmor write FArmor;
369 property Air: Integer read FAir write FAir;
370 property JetFuel: Integer read FJetFuel write FJetFuel;
371 property Frags: Integer read FFrags write FFrags;
372 property Death: Integer read FDeath write FDeath;
373 property Kills: Integer read FKills write FKills;
374 property CurrWeap: Byte read FCurrWeap write FCurrWeap;
375 property MonsterKills: Integer read FMonsterKills write FMonsterKills;
376 property Secrets: Integer read FSecrets;
377 property GodMode: Boolean read FGodMode write FGodMode;
378 property NoTarget: Boolean read FNoTarget write FNoTarget;
379 property NoReload: Boolean read FNoReload write FNoReload;
380 property alive: Boolean read FAlive write FAlive;
381 property Flag: Byte read FFlag;
382 property Team: Byte read FTeam write FTeam;
383 property Direction: TDirection read FDirection;
384 property GameX: Integer read FObj.X write FObj.X;
385 property GameY: Integer read FObj.Y write FObj.Y;
386 property GameVelX: Integer read FObj.Vel.X write FObj.Vel.X;
387 property GameVelY: Integer read FObj.Vel.Y write FObj.Vel.Y;
388 property GameAccelX: Integer read FObj.Accel.X write FObj.Accel.X;
389 property GameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y;
390 property IncCam: Integer read FIncCam write FIncCam;
391 property IncCamOld: Integer read FIncCamOld write FIncCamOld;
392 property SlopeOld: Integer read FSlopeOld write FSlopeOld;
393 property UID: Word read FUID write FUID;
394 property JustTeleported: Boolean read FJustTeleported write FJustTeleported;
395 property NetTime: LongWord read FNetTime write FNetTime;
397 (* internal state *)
398 property Angle_: SmallInt read FAngle;
399 property Spectator: Boolean read FSpectator;
400 property NoRespawn: Boolean read FNoRespawn;
401 property Berserk: Integer read FBerserk;
402 property Pain: Integer read FPain;
403 property Pickup: Integer read FPickup;
404 property PunchAnim: TAnimationState read FPunchAnim write FPunchAnim;
405 property SpawnInvul: Integer read FSpawnInvul;
406 property Ghost: Boolean read FGhost;
408 published
409 property eName: String read FName write FName;
410 property eHealth: Integer read FHealth write FHealth;
411 property eLives: Byte read FLives write FLives;
412 property eArmor: Integer read FArmor write FArmor;
413 property eAir: Integer read FAir write FAir;
414 property eJetFuel: Integer read FJetFuel write FJetFuel;
415 property eFrags: Integer read FFrags write FFrags;
416 property eDeath: Integer read FDeath write FDeath;
417 property eKills: Integer read FKills write FKills;
418 property eCurrWeap: Byte read FCurrWeap write FCurrWeap;
419 property eMonsterKills: Integer read FMonsterKills write FMonsterKills;
420 property eSecrets: Integer read FSecrets write FSecrets;
421 property eGodMode: Boolean read FGodMode write FGodMode;
422 property eNoTarget: Boolean read FNoTarget write FNoTarget;
423 property eNoReload: Boolean read FNoReload write FNoReload;
424 property eAlive: Boolean read FAlive write FAlive;
425 property eFlag: Byte read FFlag;
426 property eTeam: Byte read FTeam write FTeam;
427 property eDirection: TDirection read FDirection;
428 property eGameX: Integer read FObj.X write FObj.X;
429 property eGameY: Integer read FObj.Y write FObj.Y;
430 property eGameVelX: Integer read FObj.Vel.X write FObj.Vel.X;
431 property eGameVelY: Integer read FObj.Vel.Y write FObj.Vel.Y;
432 property eGameAccelX: Integer read FObj.Accel.X write FObj.Accel.X;
433 property eGameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y;
434 property eIncCam: Integer read FIncCam write FIncCam;
435 property eUID: Word read FUID;
436 property eJustTeleported: Boolean read FJustTeleported;
437 property eNetTime: LongWord read FNetTime;
439 // set this before assigning something to `eDamage`
440 property eDamageType: Integer read mEDamageType write mEDamageType;
441 property eDamage: Integer write doDamage;
442 end;
444 TDifficult = record
445 public
446 DiagFire: Byte;
447 InvisFire: Byte;
448 DiagPrecision: Byte;
449 FlyPrecision: Byte;
450 Cover: Byte;
451 CloseJump: Byte;
452 WeaponPrior: packed array [WP_FIRST..WP_LAST] of Byte;
453 CloseWeaponPrior: packed array [WP_FIRST..WP_LAST] of Byte;
454 //SafeWeaponPrior: Array [WP_FIRST..WP_LAST] of Byte;
456 public
457 procedure save (st: TStream);
458 procedure load (st: TStream);
459 end;
461 TAIFlag = record
462 Name: String;
463 Value: String;
464 end;
466 TBot = class(TPlayer)
467 private
468 FSelectedWeapon: Byte;
469 FTargetUID: Word;
470 FLastVisible: DWORD;
471 FAIFlags: Array of TAIFlag;
472 FDifficult: TDifficult;
474 function GetRnd(a: Byte): Boolean;
475 function GetInterval(a: Byte; radius: SmallInt): SmallInt;
476 function RunDirection(): TDirection;
477 function FullInStep(XInc, YInc: Integer): Boolean;
478 //function NeedItem(Item: Byte): Byte;
479 procedure SelectWeapon(Dist: Integer);
480 procedure SetAIFlag(aName, fValue: String20);
481 function GetAIFlag(aName: String20): String20;
482 procedure RemoveAIFlag(aName: String20);
483 function Healthy(): Byte;
484 procedure UpdateMove();
485 procedure UpdateCombat();
486 function KeyPressed(Key: Word): Boolean;
487 procedure ReleaseKey(Key: Byte);
488 function TargetOnScreen(TX, TY: Integer): Boolean;
489 procedure OnDamage(Angle: SmallInt); override;
491 public
492 procedure Respawn(Silent: Boolean; Force: Boolean = False); override;
493 constructor Create(); override;
494 destructor Destroy(); override;
495 function PickItem(ItemType: Byte; force: Boolean; var remove: Boolean): Boolean; override;
496 function Heal(value: Word; Soft: Boolean): Boolean; override;
497 procedure Update(); override;
498 procedure SaveState (st: TStream); override;
499 procedure LoadState (st: TStream); override;
500 end;
502 TCorpse = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
503 private
504 FMess: Boolean;
505 FState: Byte;
506 FDamage: Byte;
507 FObj: TObj;
508 FPlayerUID: Word;
509 FModel: TPlayerModel;
511 public
512 constructor Create(X, Y: Integer; ModelName: String; aMess: Boolean);
513 destructor Destroy(); override;
514 procedure Damage(Value: Word; SpawnerUID: Word; vx, vy: Integer);
515 procedure Update();
516 procedure SaveState (st: TStream);
517 procedure LoadState (st: TStream);
519 procedure getMapBox (out x, y, w, h: Integer); inline;
520 procedure moveBy (dx, dy: Integer); inline;
522 procedure positionChanged (); inline; //WARNING! call this after entity position was changed, or coldet will not work right!
524 function ObjPtr (): PObj; inline;
526 property Obj: TObj read FObj; // copies object
527 property State: Byte read FState;
528 property Mess: Boolean read FMess;
529 property Model: TPlayerModel read FModel;
530 end;
532 TTeamStat = Array [TEAM_RED..TEAM_BLUE] of
533 record
534 Goals: SmallInt;
535 end;
537 var
538 gPlayers: Array of TPlayer;
539 gCorpses: Array of TCorpse;
540 gTeamStat: TTeamStat;
541 gFly: Boolean = False;
542 gAimLine: Boolean = False;
543 gChatBubble: Integer = 0;
544 gPlayerIndicator: Integer = 1;
545 gPlayerIndicatorStyle: Integer = 0;
546 gNumBots: Word = 0;
547 gSpectLatchPID1: Word = 0;
548 gSpectLatchPID2: Word = 0;
549 MAX_RUNVEL: Integer = 8;
550 VEL_JUMP: Integer = 10;
552 function Lerp(X, Y, Factor: Integer): Integer;
554 procedure g_Corpses_SetMax(Count: Word);
555 function g_Corpses_GetMax(): Word;
557 procedure g_Player_Init();
558 procedure g_Player_Free();
559 function g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boolean): Word;
560 function g_Player_CreateFromState (st: TStream): Word;
561 procedure g_Player_Remove(UID: Word);
562 procedure g_Player_ResetTeams();
563 procedure g_Player_PreUpdate();
564 procedure g_Player_UpdateAll();
565 procedure g_Player_RememberAll();
566 procedure g_Player_ResetAll(Force, Silent: Boolean);
567 function g_Player_Get(UID: Word): TPlayer;
568 function g_Player_GetCount(): Byte;
569 function g_Player_GetStats(): TPlayerStatArray;
570 function g_Player_ValidName(Name: String): Boolean;
571 function g_Player_CreateCorpse(Player: TPlayer): Integer;
572 procedure g_Player_UpdatePhysicalObjects();
573 procedure g_Player_RemoveAllCorpses();
574 procedure g_Player_Corpses_SaveState (st: TStream);
575 procedure g_Player_Corpses_LoadState (st: TStream);
576 procedure g_Player_ResetReady();
577 procedure g_Bot_Add(Team, Difficult: Byte; Handicap: Integer = 100);
578 procedure g_Bot_AddList(Team: Byte; lname: ShortString; num: Integer = -1; Handicap: Integer = 100);
579 procedure g_Bot_MixNames();
580 procedure g_Bot_RemoveAll();
582 implementation
584 uses
585 {$IFDEF ENABLE_HOLMES}
586 g_holmes,
587 {$ENDIF}
588 {$IFDEF ENABLE_MENU}
589 g_menu,
590 {$ENDIF}
591 {$IFNDEF HEADLESS}
592 r_render,
593 {$ENDIF}
594 {$IFDEF ENABLE_GFX}
595 g_gfx,
596 {$ENDIF}
597 {$IFDEF ENABLE_GIBS}
598 g_gibs,
599 {$ENDIF}
600 {$IFDEF ENABLE_SHELLS}
601 g_shells,
602 {$ENDIF}
603 e_log, g_map, g_items, g_console, Math,
604 g_options, g_triggers, g_game, g_grid, e_res,
605 wadreader, g_monsters, CONFIG, g_language,
606 g_net, g_netmsg,
607 utils, xstreams;
609 const PLR_SAVE_VERSION = 0;
611 type
612 TBotProfile = record
613 name: ShortString;
614 model: ShortString;
615 team: Byte;
616 color: TRGB;
617 diag_fire: Byte;
618 invis_fire: Byte;
619 diag_precision: Byte;
620 fly_precision: Byte;
621 cover: Byte;
622 close_jump: Byte;
623 w_prior1: Array [WP_FIRST..WP_LAST] of Byte;
624 w_prior2: Array [WP_FIRST..WP_LAST] of Byte;
625 w_prior3: Array [WP_FIRST..WP_LAST] of Byte;
626 end;
628 const
629 TIME_RESPAWN1 = 1500;
630 TIME_RESPAWN2 = 2000;
631 TIME_RESPAWN3 = 3000;
632 PLAYER_SUIT_TIME = 30000;
633 PLAYER_INVUL_TIME = 30000;
634 PLAYER_INVIS_TIME = 35000;
635 FRAG_COMBO_TIME = 3000;
636 VEL_SW = 4;
637 VEL_FLY = 6;
638 PLAYER_HEADRECT: TRectWH = (X:24; Y:12; Width:20; Height:12);
639 BOT_MAXJUMP = 84;
640 BOT_LONGDIST = 300;
641 BOT_UNSAFEDIST = 128;
642 DIFFICULT_EASY: TDifficult = (DiagFire: 32; InvisFire: 32; DiagPrecision: 32;
643 FlyPrecision: 32; Cover: 32; CloseJump: 32;
644 WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0));
645 DIFFICULT_MEDIUM: TDifficult = (DiagFire: 127; InvisFire: 127; DiagPrecision: 127;
646 FlyPrecision: 127; Cover: 127; CloseJump: 127;
647 WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0));
648 DIFFICULT_HARD: TDifficult = (DiagFire: 255; InvisFire: 255; DiagPrecision: 255;
649 FlyPrecision: 255; Cover: 255; CloseJump: 255;
650 WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0));
651 WEAPON_PRIOR1: Array [WP_FIRST..WP_LAST] of Byte =
652 (WEAPON_FLAMETHROWER, WEAPON_SUPERPULEMET,
653 WEAPON_SHOTGUN2, WEAPON_SHOTGUN1,
654 WEAPON_CHAINGUN, WEAPON_PLASMA, WEAPON_ROCKETLAUNCHER,
655 WEAPON_BFG, WEAPON_PISTOL, WEAPON_SAW, WEAPON_KASTET);
656 WEAPON_PRIOR2: Array [WP_FIRST..WP_LAST] of Byte =
657 (WEAPON_FLAMETHROWER, WEAPON_SUPERPULEMET,
658 WEAPON_BFG, WEAPON_ROCKETLAUNCHER,
659 WEAPON_SHOTGUN2, WEAPON_PLASMA, WEAPON_SHOTGUN1,
660 WEAPON_CHAINGUN, WEAPON_PISTOL, WEAPON_SAW, WEAPON_KASTET);
661 //WEAPON_PRIOR3: Array [WP_FIRST..WP_LAST] of Byte =
662 // (WEAPON_FLAMETHROWER, WEAPON_SUPERPULEMET,
663 // WEAPON_BFG, WEAPON_PLASMA, WEAPON_SHOTGUN2,
664 // WEAPON_CHAINGUN, WEAPON_SHOTGUN1, WEAPON_SAW,
665 // WEAPON_ROCKETLAUNCHER, WEAPON_PISTOL, WEAPON_KASTET);
666 WEAPON_RELOAD: Array [WP_FIRST..WP_LAST] of Byte =
667 (5, 2, 6, 18, 36, 2, 12, 2, 14, 2, 2);
669 PLAYER_SIGNATURE = $52594C50; // 'PLYR'
670 CORPSE_SIGNATURE = $50524F43; // 'CORP'
672 BOTNAMES_FILENAME = 'botnames.txt';
673 BOTLIST_FILENAME = 'botlist.txt';
675 var
676 MaxCorpses: Word = 20;
677 BotNames: Array of String;
678 BotList: Array of TBotProfile;
679 SavedStates: Array of TPlayerSavedState;
682 function Lerp(X, Y, Factor: Integer): Integer;
683 begin
684 Result := X + ((Y - X) div Factor);
685 end;
687 function SameTeam(UID1, UID2: Word): Boolean;
688 begin
689 Result := False;
691 if (UID1 > UID_MAX_PLAYER) or (UID1 <= UID_MAX_GAME) or
692 (UID2 > UID_MAX_PLAYER) or (UID2 <= UID_MAX_GAME) then Exit;
694 if (g_Player_Get(UID1) = nil) or (g_Player_Get(UID2) = nil) then Exit;
696 if ((g_Player_Get(UID1).Team = TEAM_NONE) or
697 (g_Player_Get(UID2).Team = TEAM_NONE)) then Exit;
699 Result := g_Player_Get(UID1).FTeam = g_Player_Get(UID2).FTeam;
700 end;
702 procedure g_Corpses_SetMax(Count: Word);
703 begin
704 MaxCorpses := Count;
705 SetLength(gCorpses, Count);
706 end;
708 function g_Corpses_GetMax(): Word;
709 begin
710 Result := MaxCorpses;
711 end;
713 function g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boolean): Word;
714 var
715 a: Integer;
716 ok: Boolean;
717 begin
718 Result := 0;
720 ok := False;
721 a := 0;
723 // Есть ли место в gPlayers:
724 if gPlayers <> nil then
725 for a := 0 to High(gPlayers) do
726 if gPlayers[a] = nil then
727 begin
728 ok := True;
729 Break;
730 end;
732 // Нет места - расширяем gPlayers:
733 if not ok then
734 begin
735 SetLength(gPlayers, Length(gPlayers)+1);
736 a := High(gPlayers);
737 end;
739 // Создаем объект игрока:
740 if Bot then
741 gPlayers[a] := TBot.Create()
742 else
743 gPlayers[a] := TPlayer.Create();
746 gPlayers[a].FActualModelName := ModelName;
747 gPlayers[a].SetModel(ModelName);
749 // Нет модели - создание не возможно:
750 if gPlayers[a].FModel = nil then
751 begin
752 gPlayers[a].Free();
753 gPlayers[a] := nil;
754 g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], [ModelName]));
755 Exit;
756 end;
758 if not (Team in [TEAM_RED, TEAM_BLUE]) then
759 if Random(2) = 0 then
760 Team := TEAM_RED
761 else
762 Team := TEAM_BLUE;
763 gPlayers[a].FPreferredTeam := Team;
765 case gGameSettings.GameMode of
766 GM_DM: gPlayers[a].FTeam := TEAM_NONE;
767 GM_TDM,
768 GM_CTF: gPlayers[a].FTeam := gPlayers[a].FPreferredTeam;
769 GM_SINGLE,
770 GM_COOP: gPlayers[a].FTeam := TEAM_COOP;
771 end;
773 // Если командная игра - красим модель в цвет команды:
774 gPlayers[a].FColor := Color;
775 if gPlayers[a].FTeam in [TEAM_RED, TEAM_BLUE] then
776 gPlayers[a].FModel.Color := TEAMCOLOR[gPlayers[a].FTeam]
777 else
778 gPlayers[a].FModel.Color := Color;
780 gPlayers[a].FUID := g_CreateUID(UID_PLAYER);
781 gPlayers[a].FAlive := False;
783 Result := gPlayers[a].FUID;
784 end;
786 function g_Player_CreateFromState (st: TStream): Word;
787 var
788 a, i: Integer;
789 ok, Bot: Boolean;
790 b: Byte;
791 begin
792 result := 0;
793 if (st = nil) then exit; //???
795 // Сигнатура игрока
796 if not utils.checkSign(st, 'PLYR') then raise XStreamError.Create('invalid player signature');
797 if (utils.readByte(st) <> PLR_SAVE_VERSION) then raise XStreamError.Create('invalid player version');
799 // Бот или человек:
800 Bot := utils.readBool(st);
802 ok := false;
803 a := 0;
805 // Есть ли место в gPlayers:
806 for a := 0 to High(gPlayers) do if (gPlayers[a] = nil) then begin ok := true; break; end;
808 // Нет места - расширяем gPlayers
809 if not ok then
810 begin
811 SetLength(gPlayers, Length(gPlayers)+1);
812 a := High(gPlayers);
813 end;
815 // Создаем объект игрока
816 if Bot then
817 gPlayers[a] := TBot.Create()
818 else
819 gPlayers[a] := TPlayer.Create();
820 gPlayers[a].FIamBot := Bot;
821 gPlayers[a].FPhysics := True;
823 // UID игрока
824 gPlayers[a].FUID := utils.readWord(st);
825 // Имя игрока
826 gPlayers[a].FName := utils.readStr(st);
827 // Команда
828 gPlayers[a].FTeam := utils.readByte(st);
829 gPlayers[a].FPreferredTeam := gPlayers[a].FTeam;
830 // Жив ли
831 gPlayers[a].FAlive := utils.readBool(st);
832 // Израсходовал ли все жизни
833 gPlayers[a].FNoRespawn := utils.readBool(st);
834 // Направление
835 b := utils.readByte(st);
836 if b = 1 then gPlayers[a].FDirection := TDirection.D_LEFT else gPlayers[a].FDirection := TDirection.D_RIGHT; // b = 2
837 // Здоровье
838 gPlayers[a].FHealth := utils.readLongInt(st);
839 // Фора
840 gPlayers[a].FHandicap := utils.readLongInt(st);
841 // Жизни
842 gPlayers[a].FLives := utils.readByte(st);
843 // Броня
844 gPlayers[a].FArmor := utils.readLongInt(st);
845 // Запас воздуха
846 gPlayers[a].FAir := utils.readLongInt(st);
847 // Запас горючего
848 gPlayers[a].FJetFuel := utils.readLongInt(st);
849 // Боль
850 gPlayers[a].FPain := utils.readLongInt(st);
851 // Убил
852 gPlayers[a].FKills := utils.readLongInt(st);
853 // Убил монстров
854 gPlayers[a].FMonsterKills := utils.readLongInt(st);
855 // Фрагов
856 gPlayers[a].FFrags := utils.readLongInt(st);
857 // Фрагов подряд
858 gPlayers[a].FFragCombo := utils.readByte(st);
859 // Время последнего фрага
860 gPlayers[a].FLastFrag := utils.readLongWord(st);
861 // Смертей
862 gPlayers[a].FDeath := utils.readLongInt(st);
863 // Какой флаг несет
864 gPlayers[a].FFlag := utils.readByte(st);
865 // Нашел секретов
866 gPlayers[a].FSecrets := utils.readLongInt(st);
867 // Текущее оружие
868 gPlayers[a].FCurrWeap := utils.readByte(st);
869 // Следующее желаемое оружие
870 gPlayers[a].FNextWeap := utils.readWord(st);
871 // ...и пауза
872 gPlayers[a].FNextWeapDelay := utils.readByte(st);
873 // Время зарядки BFG
874 gPlayers[a].FBFGFireCounter := utils.readSmallInt(st);
875 // Буфер урона
876 gPlayers[a].FDamageBuffer := utils.readLongInt(st);
877 // Последний ударивший
878 gPlayers[a].FLastSpawnerUID := utils.readWord(st);
879 // Тип последнего полученного урона
880 gPlayers[a].FLastHit := utils.readByte(st);
881 // Объект игрока:
882 Obj_LoadState(@gPlayers[a].FObj, st);
883 // Текущее количество патронов
884 for i := A_BULLETS to A_HIGH do gPlayers[a].FAmmo[i] := utils.readWord(st);
885 // Максимальное количество патронов
886 for i := A_BULLETS to A_HIGH do gPlayers[a].FMaxAmmo[i] := utils.readWord(st);
887 // Наличие оружия
888 for i := WP_FIRST to WP_LAST do gPlayers[a].FWeapon[i] := utils.readBool(st);
889 // Время перезарядки оружия
890 for i := WP_FIRST to WP_LAST do gPlayers[a].FReloading[i] := utils.readWord(st);
891 // Наличие рюкзака
892 if utils.readBool(st) then Include(gPlayers[a].FRulez, R_ITEM_BACKPACK);
893 // Наличие красного ключа
894 if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_RED);
895 // Наличие зеленого ключа
896 if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_GREEN);
897 // Наличие синего ключа
898 if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_BLUE);
899 // Наличие берсерка
900 if utils.readBool(st) then Include(gPlayers[a].FRulez, R_BERSERK);
901 // Время действия специальных предметов
902 for i := MR_SUIT to MR_MAX do gPlayers[a].FMegaRulez[i] := utils.readLongWord(st);
903 // Время до повторного респауна, смены оружия, исользования, захвата флага
904 for i := T_RESPAWN to T_FLAGCAP do gPlayers[a].FTime[i] := utils.readLongWord(st);
906 // Название модели:
907 gPlayers[a].FActualModelName := utils.readStr(st);
908 // Цвет модели
909 gPlayers[a].FColor.R := utils.readByte(st);
910 gPlayers[a].FColor.G := utils.readByte(st);
911 gPlayers[a].FColor.B := utils.readByte(st);
912 // Обновляем модель игрока
913 gPlayers[a].SetModel(gPlayers[a].FActualModelName);
915 // Нет модели - создание невозможно
916 if (gPlayers[a].FModel = nil) then
917 begin
918 gPlayers[a].Free();
919 gPlayers[a] := nil;
920 g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], [gPlayers[a].FActualModelName]));
921 exit;
922 end;
924 // Если командная игра - красим модель в цвет команды
925 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
926 gPlayers[a].FModel.Color := TEAMCOLOR[gPlayers[a].FTeam]
927 else
928 gPlayers[a].FModel.Color := gPlayers[a].FColor;
930 result := gPlayers[a].FUID;
931 end;
934 procedure g_Player_ResetTeams();
935 var
936 a: Integer;
937 begin
938 if g_Game_IsClient then
939 Exit;
940 if gPlayers = nil then
941 Exit;
942 for a := Low(gPlayers) to High(gPlayers) do
943 if gPlayers[a] <> nil then
944 case gGameSettings.GameMode of
945 GM_DM:
946 gPlayers[a].ChangeTeam(TEAM_NONE);
947 GM_TDM, GM_CTF:
948 if not (gPlayers[a].Team in [TEAM_RED, TEAM_BLUE]) then
949 if gPlayers[a].FPreferredTeam in [TEAM_RED, TEAM_BLUE] then
950 gPlayers[a].ChangeTeam(gPlayers[a].FPreferredTeam)
951 else
952 if a mod 2 = 0 then
953 gPlayers[a].ChangeTeam(TEAM_RED)
954 else
955 gPlayers[a].ChangeTeam(TEAM_BLUE);
956 GM_SINGLE,
957 GM_COOP:
958 gPlayers[a].ChangeTeam(TEAM_COOP);
959 end;
960 end;
962 procedure g_Bot_Add(Team, Difficult: Byte; Handicap: Integer = 100);
963 var
964 m: SSArray;
965 _name, _model: String;
966 a, tr, tb: Integer;
967 begin
968 if not g_Game_IsServer then Exit;
970 // Список названий моделей:
971 m := g_PlayerModel_GetNames();
972 if m = nil then
973 Exit;
975 // Команда:
976 if (gGameSettings.GameType = GT_SINGLE) or (gGameSettings.GameMode = GM_COOP) then
977 Team := TEAM_COOP // COOP
978 else
979 if gGameSettings.GameMode = GM_DM then
980 Team := TEAM_NONE // DM
981 else
982 if Team = TEAM_NONE then // CTF / TDM
983 begin
984 // Автобаланс команд:
985 tr := 0;
986 tb := 0;
988 for a := 0 to High(gPlayers) do
989 if gPlayers[a] <> nil then
990 begin
991 if gPlayers[a].Team = TEAM_RED then
992 Inc(tr)
993 else
994 if gPlayers[a].Team = TEAM_BLUE then
995 Inc(tb);
996 end;
998 if tr > tb then
999 Team := TEAM_BLUE
1000 else
1001 if tb > tr then
1002 Team := TEAM_RED
1003 else // tr = tb
1004 if Random(2) = 0 then
1005 Team := TEAM_RED
1006 else
1007 Team := TEAM_BLUE;
1008 end;
1010 // Выбираем боту имя:
1011 _name := '';
1012 if BotNames <> nil then
1013 for a := 0 to High(BotNames) do
1014 if g_Player_ValidName(BotNames[a]) then
1015 begin
1016 _name := BotNames[a];
1017 Break;
1018 end;
1020 // Выбираем случайную модель:
1021 _model := m[Random(Length(m))];
1023 // Создаем бота:
1024 with g_Player_Get(g_Player_Create(_model,
1025 _RGB(Min(Random(9)*32, 255),
1026 Min(Random(9)*32, 255),
1027 Min(Random(9)*32, 255)),
1028 Team, True)) as TBot do
1029 begin
1030 // Если имени нет, делаем его из UID бота
1031 if _name = '' then
1032 Name := Format('DFBOT%.5d', [UID])
1033 else
1034 Name := _name;
1036 case Difficult of
1037 1: FDifficult := DIFFICULT_EASY;
1038 2: FDifficult := DIFFICULT_MEDIUM;
1039 else FDifficult := DIFFICULT_HARD;
1040 end;
1042 for a := WP_FIRST to WP_LAST do
1043 begin
1044 FDifficult.WeaponPrior[a] := WEAPON_PRIOR1[a];
1045 FDifficult.CloseWeaponPrior[a] := WEAPON_PRIOR2[a];
1046 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
1047 end;
1049 FHandicap := Handicap;
1051 g_Console_Add(Format(_lc[I_PLAYER_JOIN], [Name]), True);
1053 if g_Game_IsNet then MH_SEND_PlayerCreate(UID);
1054 if g_Game_IsServer and (gGameSettings.MaxLives > 0) then
1055 Spectate();
1056 end;
1057 end;
1059 procedure g_Bot_AddList(Team: Byte; lName: ShortString; num: Integer = -1; Handicap: Integer = 100);
1060 var
1061 m: SSArray;
1062 _name, _model: String;
1063 a: Integer;
1064 begin
1065 if not g_Game_IsServer then Exit;
1067 // Список названий моделей:
1068 m := g_PlayerModel_GetNames();
1069 if m = nil then
1070 Exit;
1072 // Команда:
1073 if (gGameSettings.GameType = GT_SINGLE) or (gGameSettings.GameMode = GM_COOP) then
1074 Team := TEAM_COOP // COOP
1075 else
1076 if gGameSettings.GameMode = GM_DM then
1077 Team := TEAM_NONE // DM
1078 else
1079 if Team = TEAM_NONE then
1080 Team := BotList[num].team; // CTF / TDM
1082 // Выбираем настройки бота из списка по номеру или имени:
1083 lName := AnsiLowerCase(lName);
1084 if (num < 0) or (num > Length(BotList)-1) then
1085 num := -1;
1086 if (num = -1) and (lName <> '') and (BotList <> nil) then
1087 for a := 0 to High(BotList) do
1088 if AnsiLowerCase(BotList[a].name) = lName then
1089 begin
1090 num := a;
1091 Break;
1092 end;
1093 if num = -1 then
1094 Exit;
1096 // Имя бота:
1097 _name := BotList[num].name;
1098 // Занято - выбираем случайное:
1099 if not g_Player_ValidName(_name) then
1100 repeat
1101 _name := Format('DFBOT%.2d', [Random(100)]);
1102 until g_Player_ValidName(_name);
1104 // Модель:
1105 _model := BotList[num].model;
1106 // Нет такой - выбираем случайную:
1107 if not InSArray(_model, m) then
1108 _model := m[Random(Length(m))];
1110 // Создаем бота:
1111 with g_Player_Get(g_Player_Create(_model, BotList[num].color, Team, True)) as TBot do
1112 begin
1113 Name := _name;
1115 FDifficult.DiagFire := BotList[num].diag_fire;
1116 FDifficult.InvisFire := BotList[num].invis_fire;
1117 FDifficult.DiagPrecision := BotList[num].diag_precision;
1118 FDifficult.FlyPrecision := BotList[num].fly_precision;
1119 FDifficult.Cover := BotList[num].cover;
1120 FDifficult.CloseJump := BotList[num].close_jump;
1122 FHandicap := Handicap;
1124 for a := WP_FIRST to WP_LAST do
1125 begin
1126 FDifficult.WeaponPrior[a] := BotList[num].w_prior1[a];
1127 FDifficult.CloseWeaponPrior[a] := BotList[num].w_prior2[a];
1128 //FDifficult.SafeWeaponPrior[a] := BotList[num].w_prior3[a];
1129 end;
1131 g_Console_Add(Format(_lc[I_PLAYER_JOIN], [Name]), True);
1133 if g_Game_IsNet then MH_SEND_PlayerCreate(UID);
1134 end;
1135 end;
1137 procedure g_Bot_RemoveAll();
1138 var
1139 a: Integer;
1140 begin
1141 if not g_Game_IsServer then Exit;
1142 if gPlayers = nil then Exit;
1144 for a := 0 to High(gPlayers) do
1145 if gPlayers[a] <> nil then
1146 if gPlayers[a] is TBot then
1147 begin
1148 gPlayers[a].Lives := 0;
1149 gPlayers[a].Kill(K_SIMPLEKILL, 0, HIT_DISCON);
1150 g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [gPlayers[a].Name]), True);
1151 g_Player_Remove(gPlayers[a].FUID);
1152 end;
1154 g_Bot_MixNames();
1155 end;
1157 procedure g_Bot_MixNames();
1158 var
1159 s: String;
1160 a, b: Integer;
1161 begin
1162 if BotNames <> nil then
1163 for a := 0 to High(BotNames) do
1164 begin
1165 b := Random(Length(BotNames));
1166 s := BotNames[a];
1167 Botnames[a] := BotNames[b];
1168 BotNames[b] := s;
1169 end;
1170 end;
1172 procedure g_Player_Remove(UID: Word);
1173 var
1174 i: Integer;
1175 begin
1176 if gPlayers = nil then Exit;
1178 if g_Game_IsServer and g_Game_IsNet then
1179 MH_SEND_PlayerDelete(UID);
1181 for i := 0 to High(gPlayers) do
1182 if gPlayers[i] <> nil then
1183 if gPlayers[i].FUID = UID then
1184 begin
1185 if gPlayers[i] is TPlayer then
1186 TPlayer(gPlayers[i]).Free()
1187 else
1188 TBot(gPlayers[i]).Free();
1189 gPlayers[i] := nil;
1190 Exit;
1191 end;
1192 end;
1194 procedure g_Player_Init();
1195 var
1196 F: TextFile;
1197 s: String;
1198 a, b: Integer;
1199 config: TConfig;
1200 sa: SSArray;
1201 path: AnsiString;
1202 begin
1203 BotNames := nil;
1205 path := BOTNAMES_FILENAME;
1206 if e_FindResource(DataDirs, path) = false then
1207 Exit;
1209 // Читаем возможные имена ботов из файла:
1210 AssignFile(F, path);
1211 Reset(F);
1213 while not EOF(F) do
1214 begin
1215 ReadLn(F, s);
1217 s := Trim(s);
1218 if s = '' then
1219 Continue;
1221 SetLength(BotNames, Length(BotNames)+1);
1222 BotNames[High(BotNames)] := s;
1223 end;
1225 CloseFile(F);
1227 // Перемешиваем их:
1228 g_Bot_MixNames();
1230 // Читаем файл с параметрами ботов:
1231 config := TConfig.CreateFile(path);
1232 BotList := nil;
1233 a := 0;
1235 while config.SectionExists(IntToStr(a)) do
1236 begin
1237 SetLength(BotList, Length(BotList)+1);
1239 with BotList[High(BotList)] do
1240 begin
1241 // Имя бота:
1242 name := config.ReadStr(IntToStr(a), 'name', '');
1243 // Модель:
1244 model := config.ReadStr(IntToStr(a), 'model', '');
1245 // Команда:
1246 if config.ReadStr(IntToStr(a), 'team', 'red') = 'red' then
1247 team := TEAM_RED
1248 else
1249 team := TEAM_BLUE;
1250 // Цвет модели:
1251 sa := parse(config.ReadStr(IntToStr(a), 'color', ''));
1252 color.R := StrToIntDef(sa[0], 0);
1253 color.G := StrToIntDef(sa[1], 0);
1254 color.B := StrToIntDef(sa[2], 0);
1255 // Вероятность стрельбы под углом:
1256 diag_fire := config.ReadInt(IntToStr(a), 'diag_fire', 0);
1257 // Вероятность ответного огня по невидимому сопернику:
1258 invis_fire := config.ReadInt(IntToStr(a), 'invis_fire', 0);
1259 // Точность стрельбы под углом:
1260 diag_precision := config.ReadInt(IntToStr(a), 'diag_precision', 0);
1261 // Точность стрельбы в полете:
1262 fly_precision := config.ReadInt(IntToStr(a), 'fly_precision', 0);
1263 // Точность уклонения от снарядов:
1264 cover := config.ReadInt(IntToStr(a), 'cover', 0);
1265 // Вероятность прыжка при приближении соперника:
1266 close_jump := config.ReadInt(IntToStr(a), 'close_jump', 0);
1267 // Приоритеты оружия для дальнего боя:
1268 sa := parse(config.ReadStr(IntToStr(a), 'w_prior1', ''));
1269 if Length(sa) = 10 then
1270 for b := 0 to 9 do
1271 w_prior1[b] := EnsureRange(StrToInt(sa[b]), 0, 9);
1272 // Приоритеты оружия для ближнего боя:
1273 sa := parse(config.ReadStr(IntToStr(a), 'w_prior2', ''));
1274 if Length(sa) = 10 then
1275 for b := 0 to 9 do
1276 w_prior2[b] := EnsureRange(StrToInt(sa[b]), 0, 9);
1278 {sa := parse(config.ReadStr(IntToStr(a), 'w_prior3', ''));
1279 if Length(sa) = 10 then
1280 for b := 0 to 9 do
1281 w_prior3[b] := EnsureRange(StrToInt(sa[b]), 0, 9);}
1282 end;
1284 a := a + 1;
1285 end;
1287 config.Free();
1288 SetLength(SavedStates, 0);
1289 end;
1291 procedure g_Player_Free();
1292 var
1293 i: Integer;
1294 begin
1295 if gPlayers <> nil then
1296 begin
1297 for i := 0 to High(gPlayers) do
1298 if gPlayers[i] <> nil then
1299 begin
1300 if gPlayers[i] is TPlayer then
1301 TPlayer(gPlayers[i]).Free()
1302 else
1303 TBot(gPlayers[i]).Free();
1304 gPlayers[i] := nil;
1305 end;
1307 gPlayers := nil;
1308 end;
1310 gPlayer1 := nil;
1311 gPlayer2 := nil;
1312 SetLength(SavedStates, 0);
1313 end;
1315 procedure g_Player_PreUpdate();
1316 var
1317 i: Integer;
1318 begin
1319 if gPlayers = nil then Exit;
1320 for i := 0 to High(gPlayers) do
1321 if gPlayers[i] <> nil then
1322 gPlayers[i].PreUpdate();
1323 end;
1325 procedure g_Player_UpdateAll();
1326 var
1327 i: Integer;
1328 begin
1329 if gPlayers = nil then Exit;
1331 //e_WriteLog('***g_Player_UpdateAll: ENTER', MSG_WARNING);
1332 for i := 0 to High(gPlayers) do
1333 begin
1334 if gPlayers[i] <> nil then
1335 begin
1336 if gPlayers[i] is TPlayer then
1337 begin
1338 gPlayers[i].Update();
1339 gPlayers[i].RealizeCurrentWeapon(); // WARNING! DO NOT MOVE THIS INTO `Update()`!
1340 end
1341 else
1342 begin
1343 // bot updates weapons in `UpdateCombat()`
1344 TBot(gPlayers[i]).Update();
1345 end;
1346 end;
1347 end;
1348 //e_WriteLog('***g_Player_UpdateAll: EXIT', MSG_WARNING);
1349 end;
1351 function g_Player_Get(UID: Word): TPlayer;
1352 var
1353 a: Integer;
1354 begin
1355 Result := nil;
1357 if gPlayers = nil then
1358 Exit;
1360 for a := 0 to High(gPlayers) do
1361 if gPlayers[a] <> nil then
1362 if gPlayers[a].FUID = UID then
1363 begin
1364 Result := gPlayers[a];
1365 Exit;
1366 end;
1367 end;
1369 function g_Player_GetCount(): Byte;
1370 var
1371 a: Integer;
1372 begin
1373 Result := 0;
1375 if gPlayers = nil then
1376 Exit;
1378 for a := 0 to High(gPlayers) do
1379 if gPlayers[a] <> nil then
1380 Result := Result + 1;
1381 end;
1383 function g_Player_GetStats(): TPlayerStatArray;
1384 var
1385 a: Integer;
1386 begin
1387 Result := nil;
1389 if gPlayers = nil then Exit;
1391 for a := 0 to High(gPlayers) do
1392 if gPlayers[a] <> nil then
1393 begin
1394 SetLength(Result, Length(Result)+1);
1395 with Result[High(Result)] do
1396 begin
1397 Num := a;
1398 Ping := gPlayers[a].FPing;
1399 Loss := gPlayers[a].FLoss;
1400 Name := gPlayers[a].FName;
1401 Team := gPlayers[a].FTeam;
1402 Frags := gPlayers[a].FFrags;
1403 Deaths := gPlayers[a].FDeath;
1404 Kills := gPlayers[a].FKills;
1405 Color := gPlayers[a].FModel.Color;
1406 Lives := gPlayers[a].FLives;
1407 Spectator := gPlayers[a].FSpectator;
1408 UID := gPlayers[a].FUID;
1409 end;
1410 end;
1411 end;
1413 procedure g_Player_ResetReady();
1414 var
1415 a: Integer;
1416 begin
1417 if not g_Game_IsServer then Exit;
1418 if gPlayers = nil then Exit;
1420 for a := 0 to High(gPlayers) do
1421 if gPlayers[a] <> nil then
1422 begin
1423 gPlayers[a].FReady := False;
1424 if g_Game_IsNet then
1425 MH_SEND_GameEvent(NET_EV_INTER_READY, gPlayers[a].UID, 'N');
1426 end;
1427 end;
1429 procedure g_Player_RememberAll;
1430 var
1431 i: Integer;
1432 begin
1433 for i := Low(gPlayers) to High(gPlayers) do
1434 if (gPlayers[i] <> nil) and gPlayers[i].alive then
1435 gPlayers[i].RememberState;
1436 end;
1438 procedure g_Player_ResetAll(Force, Silent: Boolean);
1439 var
1440 i: Integer;
1441 begin
1442 gTeamStat[TEAM_RED].Goals := 0;
1443 gTeamStat[TEAM_BLUE].Goals := 0;
1445 if gPlayers <> nil then
1446 for i := 0 to High(gPlayers) do
1447 if gPlayers[i] <> nil then
1448 begin
1449 gPlayers[i].Reset(Force);
1451 if gPlayers[i] is TPlayer then
1452 begin
1453 if (not gPlayers[i].FSpectator) or gPlayers[i].FWantsInGame then
1454 gPlayers[i].Respawn(Silent)
1455 else
1456 gPlayers[i].Spectate();
1457 end
1458 else
1459 TBot(gPlayers[i]).Respawn(Silent);
1460 end;
1461 end;
1463 function g_Player_CreateCorpse(Player: TPlayer): Integer;
1464 var
1465 i: Integer;
1466 find_id: DWORD;
1467 ok: Boolean;
1468 begin
1469 Result := -1;
1471 if Player.alive then
1472 Exit;
1474 // Разрываем связь с прежним трупом:
1475 i := Player.FCorpse;
1476 if (i >= 0) and (i < Length(gCorpses)) then
1477 begin
1478 if (gCorpses[i] <> nil) and (gCorpses[i].FPlayerUID = Player.FUID) then
1479 gCorpses[i].FPlayerUID := 0;
1480 end;
1482 if Player.FObj.Y >= gMapInfo.Height+128 then
1483 Exit;
1485 with Player do
1486 begin
1487 {$IFDEF ENABLE_GIBS}
1488 if (FHealth < -50) and (gGibsCount > 0) then
1489 begin
1490 g_Gibs_Create(FObj.X + PLAYER_RECT_CX, FObj.Y + PLAYER_RECT_CY, FModel.id, FModel.Color);
1491 end
1492 else
1493 {$ENDIF}
1494 begin
1495 if (gCorpses = nil) or (Length(gCorpses) = 0) then
1496 Exit;
1498 ok := False;
1499 for find_id := 0 to High(gCorpses) do
1500 if gCorpses[find_id] = nil then
1501 begin
1502 ok := True;
1503 Break;
1504 end;
1506 if not ok then
1507 find_id := Random(Length(gCorpses));
1509 gCorpses[find_id] := TCorpse.Create(FObj.X, FObj.Y, FModel.GetName(), FHealth < -20);
1510 gCorpses[find_id].FModel.Color := FModel.Color;
1511 gCorpses[find_id].FObj.Vel := FObj.Vel;
1512 gCorpses[find_id].FObj.Accel := FObj.Accel;
1513 gCorpses[find_id].FPlayerUID := FUID;
1515 Result := find_id;
1516 end
1517 end;
1518 end;
1520 procedure g_Player_UpdatePhysicalObjects();
1521 var i: Integer;
1522 begin
1523 if gCorpses <> nil then
1524 for i := 0 to High(gCorpses) do
1525 if gCorpses[i] <> nil then
1526 if gCorpses[i].State = CORPSE_STATE_REMOVEME then
1527 begin
1528 gCorpses[i].Free();
1529 gCorpses[i] := nil;
1530 end
1531 else
1532 gCorpses[i].Update();
1533 end;
1535 procedure g_Player_RemoveAllCorpses();
1536 var i: Integer;
1537 begin
1538 {$IFDEF ENABLE_GIBS}
1539 i := g_Gibs_GetMax();
1540 g_Gibs_SetMax(0);
1541 g_Gibs_SetMax(i);
1542 {$ENDIF}
1543 {$IFDEF ENABLE_SHELLS}
1544 i := g_Shells_GetMax();
1545 g_Shells_SetMax(0);
1546 g_Shells_SetMax(i);
1547 {$ENDIF}
1549 if gCorpses <> nil then
1550 for i := 0 to High(gCorpses) do
1551 gCorpses[i].Free();
1553 gCorpses := nil;
1554 SetLength(gCorpses, MaxCorpses);
1555 end;
1557 procedure g_Player_Corpses_SaveState (st: TStream);
1558 var
1559 count, i: Integer;
1560 begin
1561 // Считаем количество существующих трупов
1562 count := 0;
1563 for i := 0 to High(gCorpses) do if (gCorpses[i] <> nil) then Inc(count);
1565 // Количество трупов
1566 utils.writeInt(st, LongInt(count));
1568 if (count = 0) then exit;
1570 // Сохраняем трупы
1571 for i := 0 to High(gCorpses) do
1572 begin
1573 if gCorpses[i] <> nil then
1574 begin
1575 // Название модели
1576 utils.writeStr(st, gCorpses[i].FModel.GetName());
1577 // Тип смерти
1578 utils.writeBool(st, gCorpses[i].Mess);
1579 // Сохраняем данные трупа:
1580 gCorpses[i].SaveState(st);
1581 end;
1582 end;
1583 end;
1586 procedure g_Player_Corpses_LoadState (st: TStream);
1587 var
1588 count, i: Integer;
1589 str: String;
1590 b: Boolean;
1591 begin
1592 assert(st <> nil);
1594 g_Player_RemoveAllCorpses();
1596 // Количество трупов:
1597 count := utils.readLongInt(st);
1598 if (count < 0) or (count > Length(gCorpses)) then raise XStreamError.Create('invalid number of corpses');
1600 if (count = 0) then exit;
1602 // Загружаем трупы
1603 for i := 0 to count-1 do
1604 begin
1605 // Название модели:
1606 str := utils.readStr(st);
1607 // Тип смерти
1608 b := utils.readBool(st);
1609 // Создаем труп
1610 gCorpses[i] := TCorpse.Create(0, 0, str, b);
1611 // Загружаем данные трупа
1612 gCorpses[i].LoadState(st);
1613 end;
1614 end;
1617 { T P l a y e r : }
1619 function TPlayer.isValidViewPort (): Boolean; inline; begin result := (viewPortW > 0) and (viewPortH > 0); end;
1621 procedure TPlayer.BFGHit();
1622 begin
1623 g_Weapon_BFGHit(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
1624 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2));
1625 if g_Game_IsServer and g_Game_IsNet then
1626 MH_SEND_Effect(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
1627 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
1628 0, NET_GFX_BFGHIT);
1629 end;
1631 procedure TPlayer.ChangeModel(ModelName: string);
1632 var
1633 locModel: TPlayerModel;
1634 begin
1635 locModel := g_PlayerModel_Get(ModelName);
1636 if locModel = nil then Exit;
1638 FModel.Free();
1639 FModel := locModel;
1640 end;
1642 procedure TPlayer.SetModel(ModelName: string);
1643 var
1644 m: TPlayerModel;
1645 begin
1646 m := g_PlayerModel_Get(ModelName);
1647 if m = nil then
1648 begin
1649 g_SimpleError(Format(_lc[I_GAME_ERROR_MODEL_FALLBACK], [ModelName]));
1650 m := g_PlayerModel_Get('doomer');
1651 if m = nil then
1652 begin
1653 g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], ['doomer']));
1654 Exit;
1655 end;
1656 end;
1658 if FModel <> nil then
1659 FModel.Free();
1661 FModel := m;
1663 if not (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
1664 FModel.Color := FColor
1665 else
1666 FModel.Color := TEAMCOLOR[FTeam];
1667 FModel.SetWeapon(FCurrWeap);
1668 FModel.SetFlag(FFlag);
1669 SetDirection(FDirection);
1670 end;
1672 procedure TPlayer.SetColor(Color: TRGB);
1673 begin
1674 FColor := Color;
1675 if not (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
1676 if FModel <> nil then FModel.Color := Color;
1677 end;
1679 function TPlayer.GetColor(): TRGB;
1680 begin
1681 result := FModel.Color;
1682 end;
1684 procedure TPlayer.SwitchTeam;
1685 begin
1686 if g_Game_IsClient then
1687 Exit;
1688 if not (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then Exit;
1690 if gGameOn and FAlive then
1691 Kill(K_SIMPLEKILL, FUID, HIT_SELF);
1693 if FTeam = TEAM_RED then
1694 begin
1695 ChangeTeam(TEAM_BLUE);
1696 g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_BLUE], [FName]), True);
1697 if g_Game_IsNet then
1698 MH_SEND_GameEvent(NET_EV_CHANGE_TEAM, TEAM_BLUE, FName);
1699 end
1700 else
1701 begin
1702 ChangeTeam(TEAM_RED);
1703 g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_RED], [FName]), True);
1704 if g_Game_IsNet then
1705 MH_SEND_GameEvent(NET_EV_CHANGE_TEAM, TEAM_RED, FName);
1706 end;
1707 FPreferredTeam := FTeam;
1708 end;
1710 procedure TPlayer.ChangeTeam(Team: Byte);
1711 var
1712 OldTeam: Byte;
1713 begin
1714 OldTeam := FTeam;
1715 FTeam := Team;
1716 case Team of
1717 TEAM_RED, TEAM_BLUE:
1718 FModel.Color := TEAMCOLOR[Team];
1719 else
1720 FModel.Color := FColor;
1721 end;
1722 if (FTeam <> OldTeam) and g_Game_IsNet and g_Game_IsServer then
1723 MH_SEND_PlayerStats(FUID);
1724 end;
1727 procedure TPlayer.CollideItem();
1728 var
1729 i: Integer;
1730 r: Boolean;
1731 begin
1732 if gItems = nil then Exit;
1733 if not FAlive then Exit;
1735 for i := 0 to High(gItems) do
1736 with gItems[i] do
1737 begin
1738 if (ItemType <> ITEM_NONE) and alive then
1739 if g_Obj_Collide(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width,
1740 PLAYER_RECT.Height, @Obj) then
1741 begin
1742 if not PickItem(ItemType, gItems[i].Respawnable, r) then Continue;
1744 if ItemType in [ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_INVUL] then
1745 g_Sound_PlayExAt('SOUND_ITEM_GETRULEZ', FObj.X, FObj.Y)
1746 else if ItemType in [ITEM_MEDKIT_SMALL, ITEM_MEDKIT_LARGE, ITEM_MEDKIT_BLACK] then
1747 g_Sound_PlayExAt('SOUND_ITEM_GETMED', FObj.X, FObj.Y)
1748 else g_Sound_PlayExAt('SOUND_ITEM_GETITEM', FObj.X, FObj.Y);
1750 // Надо убрать с карты, если это не ключ, которым нужно поделится с другим игроком:
1751 if r and not ((ItemType in [ITEM_KEY_RED, ITEM_KEY_GREEN, ITEM_KEY_BLUE]) and
1752 (gGameSettings.GameType = GT_SINGLE) and
1753 (g_Player_GetCount() > 1)) then
1754 if not Respawnable then g_Items_Remove(i) else g_Items_Pick(i);
1755 end;
1756 end;
1757 end;
1760 function TPlayer.CollideLevel(XInc, YInc: Integer): Boolean;
1761 begin
1762 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
1763 PLAYER_RECT.Width, PLAYER_RECT.Height, PANEL_WALL,
1764 False);
1765 end;
1767 constructor TPlayer.Create();
1768 begin
1769 viewPortX := 0;
1770 viewPortY := 0;
1771 viewPortW := 0;
1772 viewPortH := 0;
1773 mEDamageType := HIT_SOME;
1775 FIamBot := False;
1776 FDummy := False;
1777 FSpawned := False;
1779 FSawSound := TPlayableSound.Create();
1780 FSawSoundIdle := TPlayableSound.Create();
1781 FSawSoundHit := TPlayableSound.Create();
1782 FSawSoundSelect := TPlayableSound.Create();
1783 FFlameSoundOn := TPlayableSound.Create();
1784 FFlameSoundOff := TPlayableSound.Create();
1785 FFlameSoundWork := TPlayableSound.Create();
1786 FJetSoundFly := TPlayableSound.Create();
1787 FJetSoundOn := TPlayableSound.Create();
1788 FJetSoundOff := TPlayableSound.Create();
1790 FSawSound.SetByName('SOUND_WEAPON_FIRESAW');
1791 FSawSoundIdle.SetByName('SOUND_WEAPON_IDLESAW');
1792 FSawSoundHit.SetByName('SOUND_WEAPON_HITSAW');
1793 FSawSoundSelect.SetByName('SOUND_WEAPON_SELECTSAW');
1794 FFlameSoundOn.SetByName('SOUND_WEAPON_FLAMEON');
1795 FFlameSoundOff.SetByName('SOUND_WEAPON_FLAMEOFF');
1796 FFlameSoundWork.SetByName('SOUND_WEAPON_FLAMEWORK');
1797 FJetSoundFly.SetByName('SOUND_PLAYER_JETFLY');
1798 FJetSoundOn.SetByName('SOUND_PLAYER_JETON');
1799 FJetSoundOff.SetByName('SOUND_PLAYER_JETOFF');
1801 FSpectatePlayer := -1;
1802 FClientID := -1;
1803 FPing := 0;
1804 FLoss := 0;
1805 FSavedStateNum := -1;
1806 {$IFDEF ENABLE_SHELLS}
1807 FShellTimer := -1;
1808 {$ENDIF}
1809 FFireTime := 0;
1810 FFirePainTime := 0;
1811 FFireAttacker := 0;
1812 FHandicap := 100;
1813 FCorpse := -1;
1815 FActualModelName := 'doomer';
1817 g_Obj_Init(@FObj);
1818 FObj.Rect := PLAYER_RECT;
1820 FBFGFireCounter := -1;
1821 FJustTeleported := False;
1822 FNetTime := 0;
1824 FWaitForFirstSpawn := false;
1825 FPunchAnim := TAnimationState.Create(False, 1, 4);
1826 FPunchAnim.Disable;
1828 resetWeaponQueue();
1829 end;
1831 procedure TPlayer.positionChanged (); inline;
1832 begin
1833 end;
1835 procedure TPlayer.doDamage (v: Integer);
1836 begin
1837 if (v <= 0) then exit;
1838 if (v > 32767) then v := 32767;
1839 Damage(v, 0, 0, 0, mEDamageType);
1840 end;
1842 procedure TPlayer.Damage(value: Word; SpawnerUID: Word; vx, vy: Integer; t: Byte);
1843 var
1844 c: Word;
1845 begin
1846 if (not g_Game_IsClient) and (not FAlive) then
1847 Exit;
1849 FLastHit := t;
1851 // Неуязвимость не спасает от ловушек:
1852 if ((t = HIT_TRAP) or (t = HIT_SELF)) and (not FGodMode) then
1853 begin
1854 if not g_Game_IsClient then
1855 begin
1856 FArmor := 0;
1857 if t = HIT_TRAP then
1858 begin
1859 // Ловушка убивает сразу:
1860 FHealth := -100;
1861 Kill(K_EXTRAHARDKILL, SpawnerUID, t);
1862 end;
1863 if t = HIT_SELF then
1864 begin
1865 // Самоубийство:
1866 FHealth := 0;
1867 Kill(K_SIMPLEKILL, SpawnerUID, t);
1868 end;
1869 end;
1870 // Обнулить действия примочек, чтобы фон пропал
1871 FMegaRulez[MR_SUIT] := 0;
1872 FMegaRulez[MR_INVUL] := 0;
1873 FMegaRulez[MR_INVIS] := 0;
1874 FSpawnInvul := 0;
1875 FBerserk := 0;
1876 end;
1878 // Но от остального спасает:
1879 if FMegaRulez[MR_INVUL] >= gTime then
1880 Exit;
1882 // Чит-код "ГОРЕЦ":
1883 if FGodMode then
1884 Exit;
1886 // Если есть урон своим, или ранил сам себя, или тебя ранил противник:
1887 if LongBool(gGameSettings.Options and GAME_OPTION_TEAMDAMAGE) or
1888 (SpawnerUID = FUID) or
1889 (not SameTeam(FUID, SpawnerUID)) then
1890 begin
1891 FLastSpawnerUID := SpawnerUID;
1893 // Кровь (пузырьки, если в воде):
1894 if gBloodCount > 0 then
1895 begin
1896 c := Min(value, 200)*gBloodCount + Random(Min(value, 200) div 2);
1897 if value div 4 <= c then
1898 c := c - (value div 4)
1899 else
1900 c := 0;
1902 if (t = HIT_SOME) and (vx = 0) and (vy = 0) then
1903 MakeBloodSimple(c)
1904 else
1905 case t of
1906 HIT_TRAP, HIT_ACID, HIT_FLAME, HIT_SELF: MakeBloodSimple(c);
1907 HIT_BFG, HIT_ROCKET, HIT_SOME: MakeBloodVector(c, vx, vy);
1908 end;
1910 {$IFDEF ENABLE_GFX}
1911 if t = HIT_WATER then
1912 begin
1913 g_GFX_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
1914 FObj.Y+PLAYER_RECT.Y-4, value div 2, 8, 4);
1915 end;
1916 {$ENDIF}
1917 end;
1919 // Буфер урона:
1920 if FAlive then
1921 Inc(FDamageBuffer, value);
1923 // Вспышка боли:
1924 if gFlash <> 0 then
1925 FPain := FPain + value;
1926 end;
1928 if g_Game_IsServer and g_Game_IsNet then
1929 begin
1930 MH_SEND_PlayerDamage(FUID, t, SpawnerUID, value, vx, vy);
1931 MH_SEND_PlayerStats(FUID);
1932 MH_SEND_PlayerPos(False, FUID);
1933 end;
1934 end;
1936 function TPlayer.Heal(value: Word; Soft: Boolean): Boolean;
1937 begin
1938 Result := False;
1939 if g_Game_IsClient then
1940 Exit;
1941 if not FAlive then
1942 Exit;
1944 if Soft and (FHealth < PLAYER_HP_SOFT) then
1945 begin
1946 IncMax(FHealth, value, PLAYER_HP_SOFT);
1947 Result := True;
1948 end;
1949 if (not Soft) and (FHealth < PLAYER_HP_LIMIT) then
1950 begin
1951 IncMax(FHealth, value, PLAYER_HP_LIMIT);
1952 Result := True;
1953 end;
1955 if Result and g_Game_IsServer and g_Game_IsNet then
1956 MH_SEND_PlayerStats(FUID);
1957 end;
1959 destructor TPlayer.Destroy();
1960 begin
1961 if (gPlayer1 <> nil) and (gPlayer1.FUID = FUID) then
1962 gPlayer1 := nil;
1963 if (gPlayer2 <> nil) and (gPlayer2.FUID = FUID) then
1964 gPlayer2 := nil;
1966 FSawSound.Free();
1967 FSawSoundIdle.Free();
1968 FSawSoundHit.Free();
1969 FSawSoundSelect.Free();
1970 FFlameSoundOn.Free();
1971 FFlameSoundOff.Free();
1972 FFlameSoundWork.Free();
1973 FJetSoundFly.Free();
1974 FJetSoundOn.Free();
1975 FJetSoundOff.Free();
1976 FModel.Free();
1977 FPunchAnim.Free();
1979 inherited;
1980 end;
1982 procedure TPlayer.DoPunch();
1983 begin
1984 FPunchAnim.Reset;
1985 FPunchAnim.Enable;
1986 end;
1988 procedure TPlayer.Fire();
1989 var
1990 f, DidFire: Boolean;
1991 wx, wy, xd, yd: Integer;
1992 locobj: TObj;
1993 begin
1994 if g_Game_IsClient then Exit;
1995 // FBFGFireCounter - время перед выстрелом (для BFG)
1996 // FReloading - время после выстрела (для всего)
1998 if FSpectator then
1999 begin
2000 Respawn(False);
2001 Exit;
2002 end;
2004 if FReloading[FCurrWeap] <> 0 then Exit;
2006 DidFire := False;
2008 f := False;
2009 wx := FObj.X+WEAPONPOINT[FDirection].X;
2010 wy := FObj.Y+WEAPONPOINT[FDirection].Y;
2011 xd := wx+IfThen(FDirection = TDirection.D_LEFT, -30, 30);
2012 yd := wy+firediry();
2014 case FCurrWeap of
2015 WEAPON_KASTET:
2016 begin
2017 DoPunch();
2018 if R_BERSERK in FRulez then
2019 begin
2020 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
2021 locobj.X := FObj.X+FObj.Rect.X;
2022 locobj.Y := FObj.Y+FObj.Rect.Y;
2023 locobj.rect.X := 0;
2024 locobj.rect.Y := 0;
2025 locobj.rect.Width := 39;
2026 locobj.rect.Height := 52;
2027 locobj.Vel.X := (xd-wx) div 2;
2028 locobj.Vel.Y := (yd-wy) div 2;
2029 locobj.Accel.X := xd-wx;
2030 locobj.Accel.y := yd-wy;
2032 if g_Weapon_Hit(@locobj, 50, FUID, HIT_SOME) <> 0 then
2033 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj.X, FObj.Y)
2034 else
2035 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj.X, FObj.Y);
2037 if (gFlash = 1) and (FPain < 50) then FPain := min(FPain + 25, 50);
2038 end
2039 else
2040 begin
2041 g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 3, FUID);
2042 end;
2044 DidFire := True;
2045 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2046 end;
2048 WEAPON_SAW:
2049 begin
2050 if g_Weapon_chainsaw(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
2051 IfThen(gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF], 9, 3), FUID) <> 0 then
2052 begin
2053 FSawSoundSelect.Stop();
2054 FSawSound.Stop();
2055 FSawSoundHit.PlayAt(FObj.X, FObj.Y);
2056 end
2057 else if not FSawSoundHit.IsPlaying() then
2058 begin
2059 FSawSoundSelect.Stop();
2060 FSawSound.PlayAt(FObj.X, FObj.Y);
2061 end;
2063 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2064 DidFire := True;
2065 f := True;
2066 end;
2068 WEAPON_PISTOL:
2069 if FAmmo[A_BULLETS] > 0 then
2070 begin
2071 g_Weapon_pistol(wx, wy, xd, yd, FUID);
2072 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2073 Dec(FAmmo[A_BULLETS]);
2074 FFireAngle := FAngle;
2075 f := True;
2076 DidFire := True;
2077 {$IFDEF ENABLE_SHELLS}
2078 g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_BULLET);
2079 {$ENDIF}
2080 end;
2082 WEAPON_SHOTGUN1:
2083 if FAmmo[A_SHELLS] > 0 then
2084 begin
2085 g_Weapon_shotgun(wx, wy, xd, yd, FUID);
2086 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', wx, wy);
2087 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2088 Dec(FAmmo[A_SHELLS]);
2089 FFireAngle := FAngle;
2090 f := True;
2091 DidFire := True;
2092 {$IFDEF ENABLE_SHELLS}
2093 FShellTimer := 10;
2094 FShellType := SHELL_SHELL;
2095 {$ENDIF}
2096 end;
2098 WEAPON_SHOTGUN2:
2099 if FAmmo[A_SHELLS] >= 2 then
2100 begin
2101 g_Weapon_dshotgun(wx, wy, xd, yd, FUID);
2102 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2103 Dec(FAmmo[A_SHELLS], 2);
2104 FFireAngle := FAngle;
2105 f := True;
2106 DidFire := True;
2107 {$IFDEF ENABLE_SHELLS}
2108 FShellTimer := 13;
2109 FShellType := SHELL_DBLSHELL;
2110 {$ENDIF}
2111 end;
2113 WEAPON_CHAINGUN:
2114 if FAmmo[A_BULLETS] > 0 then
2115 begin
2116 g_Weapon_mgun(wx, wy, xd, yd, FUID);
2117 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', wx, wy);
2118 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2119 Dec(FAmmo[A_BULLETS]);
2120 FFireAngle := FAngle;
2121 f := True;
2122 DidFire := True;
2123 {$IFDEF ENABLE_SHELLS}
2124 g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_BULLET);
2125 {$ENDIF}
2126 end;
2128 WEAPON_ROCKETLAUNCHER:
2129 if FAmmo[A_ROCKETS] > 0 then
2130 begin
2131 g_Weapon_rocket(wx, wy, xd, yd, FUID);
2132 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2133 Dec(FAmmo[A_ROCKETS]);
2134 FFireAngle := FAngle;
2135 f := True;
2136 DidFire := True;
2137 end;
2139 WEAPON_PLASMA:
2140 if FAmmo[A_CELLS] > 0 then
2141 begin
2142 g_Weapon_plasma(wx, wy, xd, yd, FUID);
2143 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2144 Dec(FAmmo[A_CELLS]);
2145 FFireAngle := FAngle;
2146 f := True;
2147 DidFire := True;
2148 end;
2150 WEAPON_BFG:
2151 if (FAmmo[A_CELLS] >= 40) and (FBFGFireCounter = -1) then
2152 begin
2153 FBFGFireCounter := 17;
2154 if not FNoReload then
2155 g_Sound_PlayExAt('SOUND_WEAPON_STARTFIREBFG', FObj.X, FObj.Y);
2156 Dec(FAmmo[A_CELLS], 40);
2157 DidFire := True;
2158 end;
2160 WEAPON_SUPERPULEMET:
2161 if FAmmo[A_SHELLS] > 0 then
2162 begin
2163 g_Weapon_shotgun(wx, wy, xd, yd, FUID);
2164 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx, wy);
2165 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2166 Dec(FAmmo[A_SHELLS]);
2167 FFireAngle := FAngle;
2168 f := True;
2169 DidFire := True;
2170 {$IFDEF ENABLE_SHELLS}
2171 g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_SHELL);
2172 {$ENDIF}
2173 end;
2175 WEAPON_FLAMETHROWER:
2176 if FAmmo[A_FUEL] > 0 then
2177 begin
2178 g_Weapon_flame(wx, wy, xd, yd, FUID);
2179 FlamerOn;
2180 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2181 Dec(FAmmo[A_FUEL]);
2182 FFireAngle := FAngle;
2183 f := True;
2184 DidFire := True;
2185 end
2186 else
2187 begin
2188 FlamerOff;
2189 if g_Game_IsNet and g_Game_IsServer then MH_SEND_PlayerStats(FUID);
2190 end;
2191 end;
2193 if g_Game_IsNet then
2194 begin
2195 if DidFire then
2196 begin
2197 if FCurrWeap <> WEAPON_BFG then
2198 MH_SEND_PlayerFire(FUID, FCurrWeap, wx, wy, xd, yd, LastShotID)
2199 else
2200 if not FNoReload then
2201 MH_SEND_Sound(FObj.X, FObj.Y, 'SOUND_WEAPON_STARTFIREBFG');
2202 end;
2204 MH_SEND_PlayerStats(FUID);
2205 end;
2207 if not f then Exit;
2209 if (FAngle = 0) or (FAngle = 180) then SetAction(A_ATTACK)
2210 else if (FAngle = ANGLE_LEFTDOWN) or (FAngle = ANGLE_RIGHTDOWN) then SetAction(A_ATTACKDOWN)
2211 else if (FAngle = ANGLE_LEFTUP) or (FAngle = ANGLE_RIGHTUP) then SetAction(A_ATTACKUP);
2212 end;
2214 function TPlayer.GetAmmoByWeapon(Weapon: Byte): Word;
2215 begin
2216 case Weapon of
2217 WEAPON_PISTOL, WEAPON_CHAINGUN: Result := FAmmo[A_BULLETS];
2218 WEAPON_SHOTGUN1, WEAPON_SHOTGUN2, WEAPON_SUPERPULEMET: Result := FAmmo[A_SHELLS];
2219 WEAPON_ROCKETLAUNCHER: Result := FAmmo[A_ROCKETS];
2220 WEAPON_PLASMA, WEAPON_BFG: Result := FAmmo[A_CELLS];
2221 WEAPON_FLAMETHROWER: Result := FAmmo[A_FUEL];
2222 else Result := 0;
2223 end;
2224 end;
2226 function TPlayer.HeadInLiquid(XInc, YInc: Integer): Boolean;
2227 begin
2228 Result := g_Map_CollidePanel(FObj.X+PLAYER_HEADRECT.X+XInc, FObj.Y+PLAYER_HEADRECT.Y+YInc,
2229 PLAYER_HEADRECT.Width, PLAYER_HEADRECT.Height,
2230 PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, True);
2231 end;
2233 procedure TPlayer.FlamerOn;
2234 begin
2235 FFlameSoundOff.Stop();
2236 FFlameSoundOff.SetPosition(0);
2237 if FFlaming then
2238 begin
2239 if (not FFlameSoundOn.IsPlaying()) and (not FFlameSoundWork.IsPlaying()) then
2240 FFlameSoundWork.PlayAt(FObj.X, FObj.Y);
2241 end
2242 else
2243 begin
2244 FFlameSoundOn.PlayAt(FObj.X, FObj.Y);
2245 FFlaming := True;
2246 end;
2247 end;
2249 procedure TPlayer.FlamerOff;
2250 begin
2251 if FFlaming then
2252 begin
2253 FFlameSoundOn.Stop();
2254 FFlameSoundOn.SetPosition(0);
2255 FFlameSoundWork.Stop();
2256 FFlameSoundWork.SetPosition(0);
2257 FFlameSoundOff.PlayAt(FObj.X, FObj.Y);
2258 FFlaming := False;
2259 end;
2260 end;
2262 procedure TPlayer.JetpackOn;
2263 begin
2264 FJetSoundFly.Stop;
2265 FJetSoundOff.Stop;
2266 FJetSoundOn.SetPosition(0);
2267 FJetSoundOn.PlayAt(FObj.X, FObj.Y);
2268 FlySmoke(8);
2269 end;
2271 procedure TPlayer.JetpackOff;
2272 begin
2273 FJetSoundFly.Stop;
2274 FJetSoundOn.Stop;
2275 FJetSoundOff.SetPosition(0);
2276 FJetSoundOff.PlayAt(FObj.X, FObj.Y);
2277 end;
2279 procedure TPlayer.CatchFire(Attacker: Word; Timeout: Integer = PLAYER_BURN_TIME);
2280 begin
2281 if Timeout <= 0 then
2282 exit;
2283 if (FMegaRulez[MR_SUIT] > gTime) or (FMegaRulez[MR_INVUL] > gTime) then
2284 exit; // Не загораемся когда есть защита
2285 if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2) then
2286 exit; // Не подгораем в воде на всякий случай
2287 if FFireTime <= 0 then
2288 g_Sound_PlayExAt('SOUND_IGNITE', FObj.X, FObj.Y);
2289 FFireTime := Timeout;
2290 FFireAttacker := Attacker;
2291 if g_Game_IsNet and g_Game_IsServer then
2292 MH_SEND_PlayerStats(FUID);
2293 end;
2295 procedure TPlayer.Jump();
2296 begin
2297 if gFly or FJetpack then
2298 begin
2299 // Полет (чит-код или джетпак):
2300 if FObj.Vel.Y > -VEL_FLY then
2301 FObj.Vel.Y := FObj.Vel.Y - 3;
2302 if FJetpack then
2303 begin
2304 if FJetFuel > 0 then
2305 Dec(FJetFuel);
2306 if (FJetFuel < 1) and g_Game_IsServer then
2307 begin
2308 FJetpack := False;
2309 JetpackOff;
2310 if g_Game_IsNet then
2311 MH_SEND_PlayerStats(FUID);
2312 end;
2313 end;
2314 Exit;
2315 end;
2317 // Не включать джетпак в режиме прохождения сквозь стены
2318 if FGhost then
2319 FCanJetpack := False;
2321 // Прыгаем или всплываем:
2322 if (CollideLevel(0, 1) or
2323 g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y+36, PLAYER_RECT.Width,
2324 PLAYER_RECT.Height-33, PANEL_STEP, False)
2325 ) and (FObj.Accel.Y = 0) then // Не прыгать, если есть вертикальное ускорение
2326 begin
2327 FObj.Vel.Y := -VEL_JUMP;
2328 FCanJetpack := False;
2329 end
2330 else
2331 begin
2332 if BodyInLiquid(0, 0) then
2333 FObj.Vel.Y := -VEL_SW
2334 else if (FJetFuel > 0) and FCanJetpack and
2335 g_Game_IsServer and (not g_Obj_CollideLiquid(@FObj, 0, 0)) then
2336 begin
2337 FJetpack := True;
2338 JetpackOn;
2339 if g_Game_IsNet then
2340 MH_SEND_PlayerStats(FUID);
2341 end;
2342 end;
2343 end;
2345 procedure TPlayer.Kill(KillType: Byte; SpawnerUID: Word; t: Byte);
2346 var
2347 a, i, k, ab, ar: Byte;
2348 s: String;
2349 mon: TMonster;
2350 plr: TPlayer;
2351 srv, netsrv: Boolean;
2352 DoFrags: Boolean;
2353 OldLR: Byte;
2354 KP: TPlayer;
2355 it: PItem;
2357 procedure PushItem(t: Byte);
2358 var
2359 id: DWORD;
2360 begin
2361 id := g_Items_Create(FObj.X, FObj.Y, t, True, False);
2362 it := g_Items_ByIdx(id);
2363 if KillType = K_EXTRAHARDKILL then // -7..+7; -8..0
2364 begin
2365 g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-7+Random(15),
2366 (FObj.Vel.Y div 2)-Random(9));
2367 it.positionChanged(); // this updates spatial accelerators
2368 end
2369 else
2370 begin
2371 if KillType = K_HARDKILL then // -5..+5; -5..0
2372 begin
2373 g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-5+Random(11),
2374 (FObj.Vel.Y div 2)-Random(6));
2375 end
2376 else // -3..+3; -3..0
2377 begin
2378 g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-3+Random(7),
2379 (FObj.Vel.Y div 2)-Random(4));
2380 end;
2381 it.positionChanged(); // this updates spatial accelerators
2382 end;
2384 if g_Game_IsNet and g_Game_IsServer then
2385 MH_SEND_ItemSpawn(True, id);
2386 end;
2388 begin
2389 DoFrags := (gGameSettings.MaxLives = 0) or (gGameSettings.GameMode = GM_COOP);
2390 Srv := g_Game_IsServer;
2391 Netsrv := g_Game_IsServer and g_Game_IsNet;
2392 if Srv then FDeath := FDeath + 1;
2393 if FAlive then
2394 begin
2395 if FGhost then
2396 FGhost := False;
2397 if not FPhysics then
2398 FPhysics := True;
2399 FAlive := False;
2400 end;
2402 {$IFDEF ENABLE_SHELLS}
2403 FShellTimer := -1;
2404 {$ENDIF}
2406 if (gGameSettings.MaxLives > 0) and Srv and (gLMSRespawn = LMS_RESPAWN_NONE) then
2407 begin
2408 if FLives > 0 then FLives := FLives - 1;
2409 if FLives = 0 then FNoRespawn := True;
2410 end;
2412 // Номер типа смерти:
2413 a := 1;
2414 case KillType of
2415 K_SIMPLEKILL: a := 1;
2416 K_HARDKILL: a := 2;
2417 K_EXTRAHARDKILL: a := 3;
2418 K_FALLKILL: a := 4;
2419 end;
2421 // Звук смерти:
2422 if not FModel.PlaySound(MODELSOUND_DIE, a, FObj.X, FObj.Y) then
2423 for i := 1 to 3 do
2424 if FModel.PlaySound(MODELSOUND_DIE, i, FObj.X, FObj.Y) then
2425 Break;
2427 // Время респауна:
2428 if Srv then
2429 case KillType of
2430 K_SIMPLEKILL:
2431 FTime[T_RESPAWN] := gTime + TIME_RESPAWN1;
2432 K_HARDKILL:
2433 FTime[T_RESPAWN] := gTime + TIME_RESPAWN2;
2434 K_EXTRAHARDKILL, K_FALLKILL:
2435 FTime[T_RESPAWN] := gTime + TIME_RESPAWN3;
2436 end;
2438 // Переключаем состояние:
2439 case KillType of
2440 K_SIMPLEKILL:
2441 SetAction(A_DIE1);
2442 K_HARDKILL, K_EXTRAHARDKILL:
2443 SetAction(A_DIE2);
2444 end;
2446 // Реакция монстров на смерть игрока:
2447 if (KillType <> K_FALLKILL) and (Srv) then
2448 g_Monsters_killedp();
2450 if SpawnerUID = FUID then
2451 begin // Самоубился
2452 if Srv and (DoFrags or (gGameSettings.GameMode = GM_TDM)) then
2453 begin
2454 Dec(FFrags);
2455 FLastFrag := 0;
2456 end;
2457 g_Console_Add(Format(_lc[I_PLAYER_KILL_SELF], [FName]), True);
2458 end
2459 else
2460 if g_GetUIDType(SpawnerUID) = UID_PLAYER then
2461 begin // Убит другим игроком
2462 KP := g_Player_Get(SpawnerUID);
2463 if (KP <> nil) and Srv then
2464 begin
2465 if (DoFrags or (gGameSettings.GameMode = GM_TDM)) then
2466 if SameTeam(FUID, SpawnerUID) then
2467 begin
2468 Dec(KP.FFrags);
2469 KP.FLastFrag := 0;
2470 end else
2471 begin
2472 Inc(KP.FFrags);
2473 KP.FragCombo();
2474 end;
2476 if (gGameSettings.GameMode = GM_TDM) and DoFrags then
2477 Inc(gTeamStat[KP.Team].Goals,
2478 IfThen(SameTeam(FUID, SpawnerUID), -1, 1));
2480 if netsrv then MH_SEND_PlayerStats(SpawnerUID);
2481 end;
2483 plr := g_Player_Get(SpawnerUID);
2484 if plr = nil then
2485 s := '?'
2486 else
2487 s := plr.FName;
2489 case KillType of
2490 K_HARDKILL:
2491 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_2],
2492 [FName, s]),
2493 gShowKillMsg);
2494 K_EXTRAHARDKILL:
2495 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_1],
2496 [FName, s]),
2497 gShowKillMsg);
2498 else
2499 g_Console_Add(Format(_lc[I_PLAYER_KILL],
2500 [FName, s]),
2501 gShowKillMsg);
2502 end;
2503 end
2504 else if g_GetUIDType(SpawnerUID) = UID_MONSTER then
2505 begin // Убит монстром
2506 mon := g_Monsters_ByUID(SpawnerUID);
2507 if mon = nil then
2508 s := '?'
2509 else
2510 s := g_Mons_GetKilledByTypeId(mon.MonsterType);
2512 case KillType of
2513 K_HARDKILL:
2514 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_2],
2515 [FName, s]),
2516 gShowKillMsg);
2517 K_EXTRAHARDKILL:
2518 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_1],
2519 [FName, s]),
2520 gShowKillMsg);
2521 else
2522 g_Console_Add(Format(_lc[I_PLAYER_KILL],
2523 [FName, s]),
2524 gShowKillMsg);
2525 end;
2526 end
2527 else // Особые типы смерти
2528 case t of
2529 HIT_DISCON: ;
2530 HIT_SELF: g_Console_Add(Format(_lc[I_PLAYER_KILL_SELF], [FName]), True);
2531 HIT_FALL: g_Console_Add(Format(_lc[I_PLAYER_KILL_FALL], [FName]), True);
2532 HIT_WATER: g_Console_Add(Format(_lc[I_PLAYER_KILL_WATER], [FName]), True);
2533 HIT_ACID: g_Console_Add(Format(_lc[I_PLAYER_KILL_ACID], [FName]), True);
2534 HIT_TRAP: g_Console_Add(Format(_lc[I_PLAYER_KILL_TRAP], [FName]), True);
2535 else g_Console_Add(Format(_lc[I_PLAYER_DIED], [FName]), True);
2536 end;
2538 if Srv then
2539 begin
2540 // Выброс оружия:
2541 for a := WP_FIRST to WP_LAST do
2542 if FWeapon[a] then
2543 begin
2544 case a of
2545 WEAPON_SAW: i := ITEM_WEAPON_SAW;
2546 WEAPON_SHOTGUN1: i := ITEM_WEAPON_SHOTGUN1;
2547 WEAPON_SHOTGUN2: i := ITEM_WEAPON_SHOTGUN2;
2548 WEAPON_CHAINGUN: i := ITEM_WEAPON_CHAINGUN;
2549 WEAPON_ROCKETLAUNCHER: i := ITEM_WEAPON_ROCKETLAUNCHER;
2550 WEAPON_PLASMA: i := ITEM_WEAPON_PLASMA;
2551 WEAPON_BFG: i := ITEM_WEAPON_BFG;
2552 WEAPON_SUPERPULEMET: i := ITEM_WEAPON_SUPERPULEMET;
2553 WEAPON_FLAMETHROWER: i := ITEM_WEAPON_FLAMETHROWER;
2554 else i := 0;
2555 end;
2557 if i <> 0 then
2558 PushItem(i);
2559 end;
2561 // Выброс рюкзака:
2562 if R_ITEM_BACKPACK in FRulez then
2563 PushItem(ITEM_AMMO_BACKPACK);
2565 // Выброс ракетного ранца:
2566 if FJetFuel > 0 then
2567 PushItem(ITEM_JETPACK);
2569 // Выброс ключей:
2570 if (not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) or
2571 (not LongBool(gGameSettings.Options and GAME_OPTION_DMKEYS)) then
2572 begin
2573 if R_KEY_RED in FRulez then
2574 PushItem(ITEM_KEY_RED);
2576 if R_KEY_GREEN in FRulez then
2577 PushItem(ITEM_KEY_GREEN);
2579 if R_KEY_BLUE in FRulez then
2580 PushItem(ITEM_KEY_BLUE);
2581 end;
2583 // Выброс флага:
2584 DropFlag(KillType = K_FALLKILL);
2585 end;
2587 FCorpse := g_Player_CreateCorpse(Self);
2589 if Srv and (gGameSettings.MaxLives > 0) and FNoRespawn and
2590 (gLMSRespawn = LMS_RESPAWN_NONE) then
2591 begin
2592 a := 0;
2593 k := 0;
2594 ar := 0;
2595 ab := 0;
2596 for i := Low(gPlayers) to High(gPlayers) do
2597 begin
2598 if gPlayers[i] = nil then continue;
2599 if (not gPlayers[i].FNoRespawn) and (not gPlayers[i].FSpectator) then
2600 begin
2601 Inc(a);
2602 if gPlayers[i].FTeam = TEAM_RED then Inc(ar)
2603 else if gPlayers[i].FTeam = TEAM_BLUE then Inc(ab);
2604 k := i;
2605 end;
2606 end;
2608 OldLR := gLMSRespawn;
2609 if (gGameSettings.GameMode = GM_COOP) then
2610 begin
2611 if (a = 0) then
2612 begin
2613 // everyone is dead, restart the map
2614 g_Game_Message(_lc[I_MESSAGE_LMS_LOSE], 144);
2615 if Netsrv then
2616 MH_SEND_GameEvent(NET_EV_LMS_LOSE);
2617 gLMSRespawn := LMS_RESPAWN_FINAL;
2618 gLMSRespawnTime := gTime + 5000;
2619 end
2620 else if (a = 1) then
2621 begin
2622 if (gPlayers[k] <> nil) and not (gPlayers[k] is TBot) then
2623 if (gPlayers[k] = gPlayer1) or
2624 (gPlayers[k] = gPlayer2) then
2625 g_Console_Add('*** ' + _lc[I_MESSAGE_LMS_SURVIVOR] + ' ***', True)
2626 else if Netsrv and (gPlayers[k].FClientID >= 0) then
2627 MH_SEND_GameEvent(NET_EV_LMS_SURVIVOR, 0, 'N', gPlayers[k].FClientID);
2628 end;
2629 end
2630 else if (gGameSettings.GameMode = GM_TDM) then
2631 begin
2632 if (ab = 0) and (ar <> 0) then
2633 begin
2634 // blu team ded
2635 g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 144);
2636 if Netsrv then
2637 MH_SEND_GameEvent(NET_EV_TLMS_WIN, TEAM_RED);
2638 Inc(gTeamStat[TEAM_RED].Goals);
2639 gLMSRespawn := LMS_RESPAWN_FINAL;
2640 gLMSRespawnTime := gTime + 5000;
2641 end
2642 else if (ar = 0) and (ab <> 0) then
2643 begin
2644 // red team ded
2645 g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 144);
2646 if Netsrv then
2647 MH_SEND_GameEvent(NET_EV_TLMS_WIN, TEAM_BLUE);
2648 Inc(gTeamStat[TEAM_BLUE].Goals);
2649 gLMSRespawn := LMS_RESPAWN_FINAL;
2650 gLMSRespawnTime := gTime + 5000;
2651 end
2652 else if (ar = 0) and (ab = 0) then
2653 begin
2654 // everyone ded
2655 g_Game_Message(_lc[I_GAME_WIN_DRAW], 144);
2656 if Netsrv then
2657 MH_SEND_GameEvent(NET_EV_LMS_DRAW, 0, FName);
2658 gLMSRespawn := LMS_RESPAWN_FINAL;
2659 gLMSRespawnTime := gTime + 5000;
2660 end;
2661 end
2662 else if (gGameSettings.GameMode = GM_DM) then
2663 begin
2664 if (a = 1) then
2665 begin
2666 if gPlayers[k] <> nil then
2667 with gPlayers[k] do
2668 begin
2669 // survivor is the winner
2670 g_Game_Message(Format(_lc[I_MESSAGE_LMS_WIN], [AnsiUpperCase(FName)]), 144);
2671 if Netsrv then
2672 MH_SEND_GameEvent(NET_EV_LMS_WIN, 0, FName);
2673 Inc(FFrags);
2674 end;
2675 gLMSRespawn := LMS_RESPAWN_FINAL;
2676 gLMSRespawnTime := gTime + 5000;
2677 end
2678 else if (a = 0) then
2679 begin
2680 // everyone is dead, restart the map
2681 g_Game_Message(_lc[I_GAME_WIN_DRAW], 144);
2682 if Netsrv then
2683 MH_SEND_GameEvent(NET_EV_LMS_DRAW, 0, FName);
2684 gLMSRespawn := LMS_RESPAWN_FINAL;
2685 gLMSRespawnTime := gTime + 5000;
2686 end;
2687 end;
2688 if srv and (OldLR = LMS_RESPAWN_NONE) and (gLMSRespawn > LMS_RESPAWN_NONE) then
2689 begin
2690 if NetMode = NET_SERVER then
2691 MH_SEND_GameEvent(NET_EV_LMS_WARMUP, gLMSRespawnTime - gTime)
2692 else
2693 g_Console_Add(Format(_lc[I_MSG_WARMUP_START], [(gLMSRespawnTime - gTime) div 1000]), True);
2694 end;
2695 end;
2697 if Netsrv then
2698 begin
2699 MH_SEND_PlayerStats(FUID);
2700 MH_SEND_PlayerDeath(FUID, KillType, t, SpawnerUID);
2701 if gGameSettings.GameMode = GM_TDM then MH_SEND_GameStats;
2702 end;
2704 if srv and FNoRespawn then Spectate(True);
2705 FWantsInGame := True;
2706 end;
2708 function TPlayer.BodyInLiquid(XInc, YInc: Integer): Boolean;
2709 begin
2710 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc, PLAYER_RECT.Width,
2711 PLAYER_RECT.Height-20, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, False);
2712 end;
2714 function TPlayer.BodyInAcid(XInc, YInc: Integer): Boolean;
2715 begin
2716 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc, PLAYER_RECT.Width,
2717 PLAYER_RECT.Height-20, PANEL_ACID1 or PANEL_ACID2, False);
2718 end;
2720 procedure TPlayer.MakeBloodSimple(Count: Word);
2721 {$IFDEF ENABLE_GFX}
2722 var Blood: TModelBlood;
2723 {$ENDIF}
2724 begin
2725 {$IFDEF ENABLE_GFX}
2726 Blood := SELF.FModel.GetBlood();
2727 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)+8,
2728 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
2729 Count div 2, 3, -1, 16, (PLAYER_RECT.Height*2 div 3),
2730 Blood.R, Blood.G, Blood.B, Blood.Kind);
2731 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-8,
2732 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
2733 Count div 2, -3, -1, 16, (PLAYER_RECT.Height*2) div 3,
2734 Blood.R, Blood.G, Blood.B, Blood.Kind);
2735 {$ENDIF}
2736 end;
2738 procedure TPlayer.MakeBloodVector(Count: Word; VelX, VelY: Integer);
2739 {$IFDEF ENABLE_GFX}
2740 var Blood: TModelBlood;
2741 {$ENDIF}
2742 begin
2743 {$IFDEF ENABLE_GFX}
2744 Blood := SELF.FModel.GetBlood();
2745 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
2746 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
2747 Count, VelX, VelY, 16, (PLAYER_RECT.Height*2) div 3,
2748 Blood.R, Blood.G, Blood.B, Blood.Kind);
2749 {$ENDIF}
2750 end;
2752 procedure TPlayer.QueueWeaponSwitch(Weapon: Byte);
2753 begin
2754 if g_Game_IsClient then Exit;
2755 if Weapon > High(FWeapon) then Exit;
2756 FNextWeap := FNextWeap or (1 shl Weapon);
2757 end;
2759 procedure TPlayer.resetWeaponQueue ();
2760 begin
2761 FNextWeap := 0;
2762 FNextWeapDelay := 0;
2763 end;
2765 function TPlayer.hasAmmoForWeapon (weapon: Byte): Boolean;
2766 begin
2767 result := false;
2768 case weapon of
2769 WEAPON_KASTET, WEAPON_SAW: result := true;
2770 WEAPON_SHOTGUN1, WEAPON_SHOTGUN2, WEAPON_SUPERPULEMET: result := (FAmmo[A_SHELLS] > 0);
2771 WEAPON_PISTOL, WEAPON_CHAINGUN: result := (FAmmo[A_BULLETS] > 0);
2772 WEAPON_ROCKETLAUNCHER: result := (FAmmo[A_ROCKETS] > 0);
2773 WEAPON_PLASMA, WEAPON_BFG: result := (FAmmo[A_CELLS] > 0);
2774 WEAPON_FLAMETHROWER: result := (FAmmo[A_FUEL] > 0);
2775 else result := (weapon < length(FWeapon));
2776 end;
2777 end;
2779 // return 255 for "no switch"
2780 function TPlayer.getNextWeaponIndex (): Byte;
2781 var
2782 i: Word;
2783 wantThisWeapon: array[0..64] of Boolean;
2784 wwc: Integer = 0; //HACK!
2785 dir, cwi: Integer;
2786 begin
2787 result := 255; // default result: "no switch"
2788 // had weapon cycling on previous frame? remove that flag
2789 if (FNextWeap and $2000) <> 0 then
2790 begin
2791 FNextWeap := FNextWeap and $1FFF;
2792 FNextWeapDelay := 0;
2793 end;
2794 // cycling has priority
2795 if (FNextWeap and $C000) <> 0 then
2796 begin
2797 if (FNextWeap and $8000) <> 0 then
2798 dir := 1
2799 else
2800 dir := -1;
2801 FNextWeap := FNextWeap or $2000; // we need this
2802 if FNextWeapDelay > 0 then
2803 exit; // cooldown time
2804 cwi := FCurrWeap;
2805 for i := 0 to High(FWeapon) do
2806 begin
2807 cwi := (cwi+length(FWeapon)+dir) mod length(FWeapon);
2808 if FWeapon[cwi] then
2809 begin
2810 //e_WriteLog(Format(' SWITCH: cur=%d; new=%d', [FCurrWeap, cwi]), MSG_WARNING);
2811 result := Byte(cwi);
2812 FNextWeapDelay := WEAPON_DELAY;
2813 exit;
2814 end;
2815 end;
2816 resetWeaponQueue();
2817 exit;
2818 end;
2819 // no cycling
2820 for i := 0 to High(wantThisWeapon) do
2821 wantThisWeapon[i] := false;
2822 for i := 0 to High(FWeapon) do
2823 if (FNextWeap and (1 shl i)) <> 0 then
2824 begin
2825 wantThisWeapon[i] := true;
2826 Inc(wwc);
2827 end;
2828 // exclude currently selected weapon from the set
2829 wantThisWeapon[FCurrWeap] := false;
2830 // slow down alterations a little
2831 if wwc > 1 then
2832 begin
2833 //e_WriteLog(Format(' FNextWeap=%x; delay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
2834 // more than one weapon requested, assume "alteration" and check alteration delay
2835 if FNextWeapDelay > 0 then
2836 begin
2837 FNextWeap := 0;
2838 exit;
2839 end; // yeah
2840 end;
2841 // do not reset weapon queue, it will be done in `RealizeCurrentWeapon()`
2842 // but clear all counters if no weapon should be switched
2843 if wwc < 1 then
2844 begin
2845 resetWeaponQueue();
2846 exit;
2847 end;
2848 //e_WriteLog(Format('wwc=%d', [wwc]), MSG_WARNING);
2849 // try weapons in descending order
2850 for i := High(FWeapon) downto 0 do
2851 begin
2852 if wantThisWeapon[i] and FWeapon[i] and ((wwc = 1) or hasAmmoForWeapon(i)) then
2853 begin
2854 // i found her!
2855 result := Byte(i);
2856 resetWeaponQueue();
2857 FNextWeapDelay := WEAPON_DELAY * 2; // anyway, 'cause why not
2858 exit;
2859 end;
2860 end;
2861 // no suitable weapon found, so reset the queue, to avoid accidental "queuing" of weapon w/o ammo
2862 resetWeaponQueue();
2863 end;
2865 procedure TPlayer.RealizeCurrentWeapon();
2866 function switchAllowed (): Boolean;
2867 var
2868 i: Byte;
2869 begin
2870 result := false;
2871 if FBFGFireCounter <> -1 then
2872 exit;
2873 if FTime[T_SWITCH] > gTime then
2874 exit;
2875 for i := WP_FIRST to WP_LAST do
2876 if FReloading[i] > 0 then
2877 exit;
2878 result := true;
2879 end;
2881 var
2882 nw: Byte;
2883 begin
2884 //e_WriteLog(Format('***RealizeCurrentWeapon: FNextWeap=%x; FNextWeapDelay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
2885 //FNextWeap := FNextWeap and $1FFF;
2886 if FNextWeapDelay > 0 then Dec(FNextWeapDelay); // "alteration delay"
2888 if not switchAllowed then
2889 begin
2890 //HACK for weapon cycling
2891 if (FNextWeap and $E000) <> 0 then FNextWeap := 0;
2892 exit;
2893 end;
2895 nw := getNextWeaponIndex();
2896 if nw = 255 then exit; // don't reset anything here
2897 if nw > High(FWeapon) then
2898 begin
2899 // don't forget to reset queue here!
2900 //e_WriteLog(' RealizeCurrentWeapon: WUTAFUUUU', MSG_WARNING);
2901 resetWeaponQueue();
2902 exit;
2903 end;
2905 if FWeapon[nw] then
2906 begin
2907 FCurrWeap := nw;
2908 FTime[T_SWITCH] := gTime+156;
2909 if FCurrWeap = WEAPON_SAW then FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
2910 FModel.SetWeapon(FCurrWeap);
2911 if g_Game_IsNet then MH_SEND_PlayerStats(FUID);
2912 end;
2913 end;
2915 procedure TPlayer.NextWeapon();
2916 begin
2917 if g_Game_IsClient then Exit;
2918 FNextWeap := $8000;
2919 end;
2921 procedure TPlayer.PrevWeapon();
2922 begin
2923 if g_Game_IsClient then Exit;
2924 FNextWeap := $4000;
2925 end;
2927 procedure TPlayer.SetWeapon(W: Byte);
2928 begin
2929 if FCurrWeap <> W then
2930 if W = WEAPON_SAW then
2931 FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
2933 FCurrWeap := W;
2934 FModel.SetWeapon(CurrWeap);
2935 resetWeaponQueue();
2936 end;
2938 function TPlayer.PickItem(ItemType: Byte; arespawn: Boolean; var remove: Boolean): Boolean;
2940 function allowBerserkSwitching (): Boolean;
2941 begin
2942 if (FBFGFireCounter <> -1) then begin result := false; exit; end;
2943 result := true;
2944 if gBerserkAutoswitch then exit;
2945 if not conIsCheatsEnabled then exit;
2946 result := false;
2947 end;
2949 var
2950 a: Boolean;
2951 begin
2952 Result := False;
2953 if g_Game_IsClient then Exit;
2955 // a = true - место спавна предмета:
2956 a := LongBool(gGameSettings.Options and GAME_OPTION_WEAPONSTAY) and arespawn;
2957 remove := not a;
2959 case ItemType of
2960 ITEM_MEDKIT_SMALL:
2961 if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
2962 begin
2963 if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 10, PLAYER_HP_SOFT);
2964 Result := True;
2965 remove := True;
2966 FFireTime := 0;
2967 if gFlash = 2 then Inc(FPickup, 5);
2968 end;
2970 ITEM_MEDKIT_LARGE:
2971 if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
2972 begin
2973 if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 25, PLAYER_HP_SOFT);
2974 Result := True;
2975 remove := True;
2976 FFireTime := 0;
2977 if gFlash = 2 then Inc(FPickup, 5);
2978 end;
2980 ITEM_ARMOR_GREEN:
2981 if FArmor < PLAYER_AP_SOFT then
2982 begin
2983 FArmor := PLAYER_AP_SOFT;
2984 Result := True;
2985 remove := True;
2986 if gFlash = 2 then Inc(FPickup, 5);
2987 end;
2989 ITEM_ARMOR_BLUE:
2990 if FArmor < PLAYER_AP_LIMIT then
2991 begin
2992 FArmor := PLAYER_AP_LIMIT;
2993 Result := True;
2994 remove := True;
2995 if gFlash = 2 then Inc(FPickup, 5);
2996 end;
2998 ITEM_SPHERE_BLUE:
2999 if (FHealth < PLAYER_HP_LIMIT) or (FFireTime > 0) then
3000 begin
3001 if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 100, PLAYER_HP_LIMIT);
3002 Result := True;
3003 remove := True;
3004 FFireTime := 0;
3005 if gFlash = 2 then Inc(FPickup, 5);
3006 end;
3008 ITEM_SPHERE_WHITE:
3009 if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) or (FFireTime > 0) then
3010 begin
3011 if FHealth < PLAYER_HP_LIMIT then
3012 FHealth := PLAYER_HP_LIMIT;
3013 if FArmor < PLAYER_AP_LIMIT then
3014 FArmor := PLAYER_AP_LIMIT;
3015 Result := True;
3016 remove := True;
3017 FFireTime := 0;
3018 if gFlash = 2 then Inc(FPickup, 5);
3019 end;
3021 ITEM_WEAPON_SAW:
3022 if (not FWeapon[WEAPON_SAW]) or ((not arespawn) and (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) then
3023 begin
3024 FWeapon[WEAPON_SAW] := True;
3025 Result := True;
3026 if gFlash = 2 then Inc(FPickup, 5);
3027 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3028 end;
3030 ITEM_WEAPON_SHOTGUN1:
3031 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SHOTGUN1] then
3032 begin
3033 // Нужно, чтобы не взять все пули сразу:
3034 if a and FWeapon[WEAPON_SHOTGUN1] then Exit;
3036 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
3037 FWeapon[WEAPON_SHOTGUN1] := True;
3038 Result := True;
3039 if gFlash = 2 then Inc(FPickup, 5);
3040 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3041 end;
3043 ITEM_WEAPON_SHOTGUN2:
3044 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SHOTGUN2] then
3045 begin
3046 if a and FWeapon[WEAPON_SHOTGUN2] then Exit;
3048 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
3049 FWeapon[WEAPON_SHOTGUN2] := True;
3050 Result := True;
3051 if gFlash = 2 then Inc(FPickup, 5);
3052 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3053 end;
3055 ITEM_WEAPON_CHAINGUN:
3056 if (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or not FWeapon[WEAPON_CHAINGUN] then
3057 begin
3058 if a and FWeapon[WEAPON_CHAINGUN] then Exit;
3060 IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
3061 FWeapon[WEAPON_CHAINGUN] := True;
3062 Result := True;
3063 if gFlash = 2 then Inc(FPickup, 5);
3064 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3065 end;
3067 ITEM_WEAPON_ROCKETLAUNCHER:
3068 if (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or not FWeapon[WEAPON_ROCKETLAUNCHER] then
3069 begin
3070 if a and FWeapon[WEAPON_ROCKETLAUNCHER] then Exit;
3072 IncMax(FAmmo[A_ROCKETS], 2, FMaxAmmo[A_ROCKETS]);
3073 FWeapon[WEAPON_ROCKETLAUNCHER] := True;
3074 Result := True;
3075 if gFlash = 2 then Inc(FPickup, 5);
3076 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3077 end;
3079 ITEM_WEAPON_PLASMA:
3080 if (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or not FWeapon[WEAPON_PLASMA] then
3081 begin
3082 if a and FWeapon[WEAPON_PLASMA] then Exit;
3084 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
3085 FWeapon[WEAPON_PLASMA] := True;
3086 Result := True;
3087 if gFlash = 2 then Inc(FPickup, 5);
3088 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3089 end;
3091 ITEM_WEAPON_BFG:
3092 if (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or not FWeapon[WEAPON_BFG] then
3093 begin
3094 if a and FWeapon[WEAPON_BFG] then Exit;
3096 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
3097 FWeapon[WEAPON_BFG] := True;
3098 Result := True;
3099 if gFlash = 2 then Inc(FPickup, 5);
3100 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3101 end;
3103 ITEM_WEAPON_SUPERPULEMET:
3104 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SUPERPULEMET] then
3105 begin
3106 if a and FWeapon[WEAPON_SUPERPULEMET] then Exit;
3108 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
3109 FWeapon[WEAPON_SUPERPULEMET] := True;
3110 Result := True;
3111 if gFlash = 2 then Inc(FPickup, 5);
3112 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3113 end;
3115 ITEM_WEAPON_FLAMETHROWER:
3116 if (FAmmo[A_FUEL] < FMaxAmmo[A_FUEL]) or not FWeapon[WEAPON_FLAMETHROWER] then
3117 begin
3118 if a and FWeapon[WEAPON_FLAMETHROWER] then Exit;
3120 IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
3121 FWeapon[WEAPON_FLAMETHROWER] := True;
3122 Result := True;
3123 if gFlash = 2 then Inc(FPickup, 5);
3124 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3125 end;
3127 ITEM_AMMO_BULLETS:
3128 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
3129 begin
3130 IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
3131 Result := True;
3132 remove := True;
3133 if gFlash = 2 then Inc(FPickup, 5);
3134 end;
3136 ITEM_AMMO_BULLETS_BOX:
3137 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
3138 begin
3139 IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
3140 Result := True;
3141 remove := True;
3142 if gFlash = 2 then Inc(FPickup, 5);
3143 end;
3145 ITEM_AMMO_SHELLS:
3146 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
3147 begin
3148 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
3149 Result := True;
3150 remove := True;
3151 if gFlash = 2 then Inc(FPickup, 5);
3152 end;
3154 ITEM_AMMO_SHELLS_BOX:
3155 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
3156 begin
3157 IncMax(FAmmo[A_SHELLS], 25, FMaxAmmo[A_SHELLS]);
3158 Result := True;
3159 remove := True;
3160 if gFlash = 2 then Inc(FPickup, 5);
3161 end;
3163 ITEM_AMMO_ROCKET:
3164 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
3165 begin
3166 IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
3167 Result := True;
3168 remove := True;
3169 if gFlash = 2 then Inc(FPickup, 5);
3170 end;
3172 ITEM_AMMO_ROCKET_BOX:
3173 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
3174 begin
3175 IncMax(FAmmo[A_ROCKETS], 5, FMaxAmmo[A_ROCKETS]);
3176 Result := True;
3177 remove := True;
3178 if gFlash = 2 then Inc(FPickup, 5);
3179 end;
3181 ITEM_AMMO_CELL:
3182 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
3183 begin
3184 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
3185 Result := True;
3186 remove := True;
3187 if gFlash = 2 then Inc(FPickup, 5);
3188 end;
3190 ITEM_AMMO_CELL_BIG:
3191 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
3192 begin
3193 IncMax(FAmmo[A_CELLS], 100, FMaxAmmo[A_CELLS]);
3194 Result := True;
3195 remove := True;
3196 if gFlash = 2 then Inc(FPickup, 5);
3197 end;
3199 ITEM_AMMO_FUELCAN:
3200 if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then
3201 begin
3202 IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
3203 Result := True;
3204 remove := True;
3205 if gFlash = 2 then Inc(FPickup, 5);
3206 end;
3208 ITEM_AMMO_BACKPACK:
3209 if not(R_ITEM_BACKPACK in FRulez) or
3210 (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or
3211 (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or
3212 (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or
3213 (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or
3214 (FAmmo[A_FUEL] < FMaxAmmo[A_FUEL]) then
3215 begin
3216 FMaxAmmo[A_BULLETS] := AmmoLimits[1, A_BULLETS];
3217 FMaxAmmo[A_SHELLS] := AmmoLimits[1, A_SHELLS];
3218 FMaxAmmo[A_ROCKETS] := AmmoLimits[1, A_ROCKETS];
3219 FMaxAmmo[A_CELLS] := AmmoLimits[1, A_CELLS];
3220 FMaxAmmo[A_FUEL] := AmmoLimits[1, A_FUEL];
3222 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
3223 IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
3224 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
3225 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
3226 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
3227 IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
3228 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
3229 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
3230 if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then
3231 IncMax(FAmmo[A_FUEL], 50, FMaxAmmo[A_FUEL]);
3233 FRulez := FRulez + [R_ITEM_BACKPACK];
3234 Result := True;
3235 remove := True;
3236 if gFlash = 2 then Inc(FPickup, 5);
3237 end;
3239 ITEM_KEY_RED:
3240 if not(R_KEY_RED in FRulez) then
3241 begin
3242 Include(FRulez, R_KEY_RED);
3243 Result := True;
3244 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
3245 if gFlash = 2 then Inc(FPickup, 5);
3246 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
3247 end;
3249 ITEM_KEY_GREEN:
3250 if not(R_KEY_GREEN in FRulez) then
3251 begin
3252 Include(FRulez, R_KEY_GREEN);
3253 Result := True;
3254 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
3255 if gFlash = 2 then Inc(FPickup, 5);
3256 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
3257 end;
3259 ITEM_KEY_BLUE:
3260 if not(R_KEY_BLUE in FRulez) then
3261 begin
3262 Include(FRulez, R_KEY_BLUE);
3263 Result := True;
3264 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
3265 if gFlash = 2 then Inc(FPickup, 5);
3266 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
3267 end;
3269 ITEM_SUIT:
3270 if FMegaRulez[MR_SUIT] < gTime+PLAYER_SUIT_TIME then
3271 begin
3272 FMegaRulez[MR_SUIT] := gTime+PLAYER_SUIT_TIME;
3273 Result := True;
3274 remove := True;
3275 FFireTime := 0;
3276 if gFlash = 2 then Inc(FPickup, 5);
3277 end;
3279 ITEM_OXYGEN:
3280 if FAir < AIR_MAX then
3281 begin
3282 FAir := AIR_MAX;
3283 Result := True;
3284 remove := True;
3285 if gFlash = 2 then Inc(FPickup, 5);
3286 end;
3288 ITEM_MEDKIT_BLACK:
3289 begin
3290 if not (R_BERSERK in FRulez) then
3291 begin
3292 Include(FRulez, R_BERSERK);
3293 if allowBerserkSwitching then
3294 begin
3295 FCurrWeap := WEAPON_KASTET;
3296 resetWeaponQueue();
3297 FModel.SetWeapon(WEAPON_KASTET);
3298 end;
3299 if gFlash <> 0 then
3300 begin
3301 Inc(FPain, 100);
3302 if gFlash = 2 then Inc(FPickup, 5);
3303 end;
3304 FBerserk := gTime+30000;
3305 Result := True;
3306 remove := True;
3307 FFireTime := 0;
3308 end;
3309 if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
3310 begin
3311 if FHealth < PLAYER_HP_SOFT then FHealth := PLAYER_HP_SOFT;
3312 FBerserk := gTime+30000;
3313 Result := True;
3314 remove := True;
3315 FFireTime := 0;
3316 end;
3317 end;
3319 ITEM_INVUL:
3320 if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
3321 begin
3322 FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
3323 FSpawnInvul := 0;
3324 Result := True;
3325 remove := True;
3326 if gFlash = 2 then Inc(FPickup, 5);
3327 end;
3329 ITEM_BOTTLE:
3330 if (FHealth < PLAYER_HP_LIMIT) or (FFireTime > 0) then
3331 begin
3332 if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 4, PLAYER_HP_LIMIT);
3333 Result := True;
3334 remove := True;
3335 FFireTime := 0;
3336 if gFlash = 2 then Inc(FPickup, 5);
3337 end;
3339 ITEM_HELMET:
3340 if FArmor < PLAYER_AP_LIMIT then
3341 begin
3342 IncMax(FArmor, 5, PLAYER_AP_LIMIT);
3343 Result := True;
3344 remove := True;
3345 if gFlash = 2 then Inc(FPickup, 5);
3346 end;
3348 ITEM_JETPACK:
3349 if FJetFuel < JET_MAX then
3350 begin
3351 FJetFuel := JET_MAX;
3352 Result := True;
3353 remove := True;
3354 if gFlash = 2 then Inc(FPickup, 5);
3355 end;
3357 ITEM_INVIS:
3358 if FMegaRulez[MR_INVIS] < gTime+PLAYER_INVIS_TIME then
3359 begin
3360 FMegaRulez[MR_INVIS] := gTime+PLAYER_INVIS_TIME;
3361 Result := True;
3362 remove := True;
3363 if gFlash = 2 then Inc(FPickup, 5);
3364 end;
3365 end;
3366 end;
3368 procedure TPlayer.Touch();
3369 begin
3370 if not FAlive then
3371 Exit;
3372 //FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
3373 if FIamBot then
3374 begin
3375 // Бросить флаг товарищу:
3376 if gGameSettings.GameMode = GM_CTF then
3377 DropFlag();
3378 end;
3379 end;
3381 procedure TPlayer.Push(vx, vy: Integer);
3382 begin
3383 if (not FPhysics) and FGhost then
3384 Exit;
3385 FObj.Accel.X := FObj.Accel.X + vx;
3386 FObj.Accel.Y := FObj.Accel.Y + vy;
3387 if g_Game_IsNet and g_Game_IsServer then
3388 MH_SEND_PlayerPos(True, FUID, NET_EVERYONE);
3389 end;
3391 procedure TPlayer.Reset(Force: Boolean);
3392 begin
3393 if Force then
3394 FAlive := False;
3396 FSpawned := False;
3397 FTime[T_RESPAWN] := 0;
3398 FTime[T_FLAGCAP] := 0;
3399 FGodMode := False;
3400 FNoTarget := False;
3401 FNoReload := False;
3402 FFrags := 0;
3403 FLastFrag := 0;
3404 FComboEvnt := -1;
3405 FKills := 0;
3406 FMonsterKills := 0;
3407 FDeath := 0;
3408 FSecrets := 0;
3409 FSpawnInvul := 0;
3410 FCorpse := -1;
3411 FReady := False;
3412 if FNoRespawn then
3413 begin
3414 FSpectator := False;
3415 FGhost := False;
3416 FPhysics := True;
3417 FSpectatePlayer := -1;
3418 FNoRespawn := False;
3419 end;
3420 FLives := gGameSettings.MaxLives;
3422 SetFlag(FLAG_NONE);
3423 end;
3425 procedure TPlayer.SoftReset();
3426 begin
3427 ReleaseKeys();
3429 FDamageBuffer := 0;
3430 FSlopeOld := 0;
3431 FIncCamOld := 0;
3432 FIncCam := 0;
3433 FBFGFireCounter := -1;
3434 {$IFDEF ENABLE_SHELLS}
3435 FShellTimer := -1;
3436 {$ENDIF}
3437 FPain := 0;
3438 FLastHit := 0;
3439 FLastFrag := 0;
3440 FComboEvnt := -1;
3442 SetFlag(FLAG_NONE);
3443 SetAction(A_STAND, True);
3444 end;
3446 function TPlayer.GetRespawnPoint(): Byte;
3447 var
3448 c: Byte;
3449 begin
3450 Result := 255;
3451 // На будущее: FSpawn - игрок уже играл и перерождается
3453 // Одиночная игра/кооператив
3454 if gGameSettings.GameMode in [GM_COOP, GM_SINGLE] then
3455 begin
3456 if Self = gPlayer1 then
3457 begin
3458 // player 1 should try to spawn on the player 1 point
3459 if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1) > 0 then
3460 Exit(RESPAWNPOINT_PLAYER1)
3461 else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2) > 0 then
3462 Exit(RESPAWNPOINT_PLAYER2);
3463 end
3464 else if Self = gPlayer2 then
3465 begin
3466 // player 2 should try to spawn on the player 2 point
3467 if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2) > 0 then
3468 Exit(RESPAWNPOINT_PLAYER2)
3469 else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1) > 0 then
3470 Exit(RESPAWNPOINT_PLAYER1);
3471 end
3472 else
3473 begin
3474 // other players randomly pick either the first or the second point
3475 c := IfThen((Random(2) = 0), RESPAWNPOINT_PLAYER1, RESPAWNPOINT_PLAYER2);
3476 if g_Map_GetPointCount(c) > 0 then
3477 Exit(c);
3478 // try the other one
3479 c := IfThen((c = RESPAWNPOINT_PLAYER1), RESPAWNPOINT_PLAYER2, RESPAWNPOINT_PLAYER1);
3480 if g_Map_GetPointCount(c) > 0 then
3481 Exit(c);
3482 end;
3483 end;
3485 // Мясоповал
3486 if gGameSettings.GameMode = GM_DM then
3487 begin
3488 // try DM points first
3489 if g_Map_GetPointCount(RESPAWNPOINT_DM) > 0 then
3490 Exit(RESPAWNPOINT_DM);
3491 end;
3493 // Командные
3494 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
3495 begin
3496 // try team points first
3497 c := RESPAWNPOINT_DM;
3498 if FTeam = TEAM_RED then
3499 c := RESPAWNPOINT_RED
3500 else if FTeam = TEAM_BLUE then
3501 c := RESPAWNPOINT_BLUE;
3502 if g_Map_GetPointCount(c) > 0 then
3503 Exit(c);
3504 end;
3506 // still haven't found a spawnpoint, try random shit
3507 Result := g_Map_GetRandomPointType();
3508 end;
3510 procedure TPlayer.Respawn(Silent: Boolean; Force: Boolean = False);
3511 var
3512 RespawnPoint: TRespawnPoint;
3513 a, b, c: Byte;
3514 begin
3515 FSlopeOld := 0;
3516 FIncCamOld := 0;
3517 FIncCam := 0;
3518 FBFGFireCounter := -1;
3519 {$IFDEF ENABLE_SHELLS}
3520 FShellTimer := -1;
3521 {$ENDIF}
3522 FPain := 0;
3523 FLastHit := 0;
3524 FSpawnInvul := 0;
3525 FCorpse := -1;
3527 if not g_Game_IsServer then
3528 Exit;
3529 if FDummy then
3530 Exit;
3531 FWantsInGame := True;
3532 FJustTeleported := True;
3533 if Force then
3534 begin
3535 FTime[T_RESPAWN] := 0;
3536 FAlive := False;
3537 end;
3538 FNetTime := 0;
3539 // if server changes MaxLives we gotta be ready
3540 if gGameSettings.MaxLives = 0 then FNoRespawn := False;
3542 // Еще нельзя возродиться:
3543 if FTime[T_RESPAWN] > gTime then
3544 Exit;
3546 // Просрал все жизни:
3547 if FNoRespawn then
3548 begin
3549 if not FSpectator then Spectate(True);
3550 FWantsInGame := True;
3551 Exit;
3552 end;
3554 if (gGameSettings.GameType <> GT_SINGLE) and (gGameSettings.GameMode <> GM_COOP) then
3555 begin // "Своя игра"
3556 // Берсерк не сохраняется между уровнями:
3557 FRulez := FRulez-[R_BERSERK];
3558 end
3559 else // "Одиночная игра"/"Кооп"
3560 begin
3561 // Берсерк и ключи не сохраняются между уровнями:
3562 FRulez := FRulez-[R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE, R_BERSERK];
3563 end;
3565 // Получаем точку спауна игрока:
3566 c := GetRespawnPoint();
3568 ReleaseKeys();
3569 SetFlag(FLAG_NONE);
3571 // Воскрешение без оружия:
3572 if not FAlive then
3573 begin
3574 FHealth := Round(PLAYER_HP_SOFT * (FHandicap / 100));
3575 FArmor := 0;
3576 FAlive := True;
3577 FAir := AIR_DEF;
3578 FJetFuel := 0;
3580 for a := WP_FIRST to WP_LAST do
3581 begin
3582 FWeapon[a] := False;
3583 FReloading[a] := 0;
3584 end;
3586 FWeapon[WEAPON_PISTOL] := True;
3587 FWeapon[WEAPON_KASTET] := True;
3588 FCurrWeap := WEAPON_PISTOL;
3589 resetWeaponQueue();
3591 FModel.SetWeapon(FCurrWeap);
3593 for b := A_BULLETS to A_HIGH do
3594 FAmmo[b] := 0;
3596 FAmmo[A_BULLETS] := 50;
3598 FMaxAmmo[A_BULLETS] := AmmoLimits[0, A_BULLETS];
3599 FMaxAmmo[A_SHELLS] := AmmoLimits[0, A_SHELLS];
3600 FMaxAmmo[A_ROCKETS] := AmmoLimits[0, A_SHELLS];
3601 FMaxAmmo[A_CELLS] := AmmoLimits[0, A_CELLS];
3602 FMaxAmmo[A_FUEL] := AmmoLimits[0, A_FUEL];
3604 if (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) and
3605 LongBool(gGameSettings.Options and GAME_OPTION_DMKEYS) then
3606 FRulez := [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE]
3607 else
3608 FRulez := [];
3609 end;
3611 // Получаем координаты точки возрождения:
3612 if not g_Map_GetPoint(c, RespawnPoint) then
3613 begin
3614 g_FatalError(_lc[I_GAME_ERROR_GET_SPAWN]);
3615 Exit;
3616 end;
3618 // Установка координат и сброс всех параметров:
3619 FObj.X := RespawnPoint.X-PLAYER_RECT.X;
3620 FObj.Y := RespawnPoint.Y-PLAYER_RECT.Y;
3621 FObj.oldX := FObj.X; // don't interpolate after respawn
3622 FObj.oldY := FObj.Y;
3623 FObj.Vel.X := 0;
3624 FObj.Vel.Y := 0;
3625 FObj.Accel.X := 0;
3626 FObj.Accel.Y := 0;
3628 FDirection := RespawnPoint.Direction;
3629 if FDirection = TDirection.D_LEFT then
3630 FAngle := 180
3631 else
3632 FAngle := 0;
3634 SetAction(A_STAND, True);
3635 FModel.Direction := FDirection;
3637 for a := Low(FTime) to High(FTime) do
3638 FTime[a] := 0;
3640 for a := Low(FMegaRulez) to High(FMegaRulez) do
3641 FMegaRulez[a] := 0;
3643 // Respawn invulnerability
3644 if (gGameSettings.GameType <> GT_SINGLE) and (gGameSettings.SpawnInvul > 0) then
3645 begin
3646 FMegaRulez[MR_INVUL] := gTime + gGameSettings.SpawnInvul * 1000;
3647 FSpawnInvul := FMegaRulez[MR_INVUL];
3648 end;
3650 FDamageBuffer := 0;
3651 FJetpack := False;
3652 FCanJetpack := False;
3653 FFlaming := False;
3654 FFireTime := 0;
3655 FFirePainTime := 0;
3656 FFireAttacker := 0;
3658 {$IFDEF ENABLE_GFX}
3659 // Анимация возрождения:
3660 if (not gLoadGameMode) and (not Silent) then
3661 begin
3662 g_GFX_QueueEffect(
3663 R_GFX_TELEPORT_FAST,
3664 FObj.X + PLAYER_RECT.X + (PLAYER_RECT.Width div 2) - 32,
3665 FObj.Y + PLAYER_RECT.Y + (PLAYER_RECT.Height div 2) - 32
3666 );
3667 end;
3668 {$ENDIF}
3670 FSpectator := False;
3671 FGhost := False;
3672 FPhysics := True;
3673 FSpectatePlayer := -1;
3674 FSpawned := True;
3676 if (gPlayer1 = nil) and (gSpectLatchPID1 = FUID) then
3677 gPlayer1 := self;
3678 if (gPlayer2 = nil) and (gSpectLatchPID2 = FUID) then
3679 gPlayer2 := self;
3681 if g_Game_IsNet then
3682 begin
3683 MH_SEND_PlayerPos(True, FUID, NET_EVERYONE);
3684 MH_SEND_PlayerStats(FUID, NET_EVERYONE);
3685 if not Silent then
3686 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
3687 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32,
3688 0, NET_GFX_TELE);
3689 end;
3690 end;
3692 procedure TPlayer.Spectate(NoMove: Boolean = False);
3693 begin
3694 if FAlive then
3695 Kill(K_EXTRAHARDKILL, FUID, HIT_SOME)
3696 else if (not NoMove) then
3697 begin
3698 GameX := gMapInfo.Width div 2;
3699 GameY := gMapInfo.Height div 2;
3700 end;
3701 FXTo := GameX;
3702 FYTo := GameY;
3704 FAlive := False;
3705 FSpectator := True;
3706 FGhost := True;
3707 FPhysics := False;
3708 FWantsInGame := False;
3709 FSpawned := False;
3710 FCorpse := -1;
3712 if FNoRespawn then
3713 begin
3714 if Self = gPlayer1 then
3715 begin
3716 gSpectLatchPID1 := FUID;
3717 gPlayer1 := nil;
3718 end
3719 else if Self = gPlayer2 then
3720 begin
3721 gSpectLatchPID2 := FUID;
3722 gPlayer2 := nil;
3723 end;
3724 end;
3726 if g_Game_IsNet then
3727 MH_SEND_PlayerStats(FUID);
3728 end;
3730 procedure TPlayer.SwitchNoClip;
3731 begin
3732 if not FAlive then
3733 Exit;
3734 FGhost := not FGhost;
3735 FPhysics := not FGhost;
3736 if FGhost then
3737 begin
3738 FXTo := FObj.X;
3739 FYTo := FObj.Y;
3740 end else
3741 begin
3742 FObj.Accel.X := 0;
3743 FObj.Accel.Y := 0;
3744 end;
3745 end;
3747 procedure TPlayer.Run(Direction: TDirection);
3748 {$IFDEF ENABLE_GIBS}
3749 var a, b: Integer;
3750 {$ENDIF}
3751 begin
3752 if MAX_RUNVEL > 8 then
3753 FlySmoke();
3755 // Бежим:
3756 if Direction = TDirection.D_LEFT then
3757 begin
3758 if FObj.Vel.X > -MAX_RUNVEL then
3759 FObj.Vel.X := FObj.Vel.X - (MAX_RUNVEL shr 3);
3760 end
3761 else
3762 if FObj.Vel.X < MAX_RUNVEL then
3763 FObj.Vel.X := FObj.Vel.X + (MAX_RUNVEL shr 3);
3765 {$IFDEF ENABLE_GIBS}
3766 // Возможно, пинаем куски:
3767 if (FObj.Vel.X <> 0) and (gGibs <> nil) then
3768 begin
3769 b := Abs(FObj.Vel.X);
3770 if b > 1 then b := b * (Random(8 div b) + 1);
3771 for a := 0 to High(gGibs) do
3772 begin
3773 if gGibs[a].alive and
3774 g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
3775 FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then
3776 begin
3777 // Пинаем куски
3778 if FObj.Vel.X < 0 then
3779 begin
3780 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120) // налево
3781 end
3782 else
3783 begin
3784 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // направо
3785 end;
3786 gGibs[a].positionChanged(); // this updates spatial accelerators
3787 end;
3788 end;
3789 end;
3790 {$ENDIF}
3792 SetAction(A_WALK);
3793 end;
3795 procedure TPlayer.SeeDown();
3796 begin
3797 SetAction(A_SEEDOWN);
3799 if FDirection = TDirection.D_LEFT then FAngle := ANGLE_LEFTDOWN else FAngle := ANGLE_RIGHTDOWN;
3801 if FIncCam > -120 then DecMin(FIncCam, 5, -120);
3802 end;
3804 procedure TPlayer.SeeUp();
3805 begin
3806 SetAction(A_SEEUP);
3808 if FDirection = TDirection.D_LEFT then FAngle := ANGLE_LEFTUP else FAngle := ANGLE_RIGHTUP;
3810 if FIncCam < 120 then IncMax(FIncCam, 5, 120);
3811 end;
3813 procedure TPlayer.SetAction(Action: Byte; Force: Boolean = False);
3814 var
3815 Prior: Byte;
3816 begin
3817 case Action of
3818 A_WALK: Prior := 3;
3819 A_DIE1: Prior := 5;
3820 A_DIE2: Prior := 5;
3821 A_ATTACK: Prior := 2;
3822 A_SEEUP: Prior := 1;
3823 A_SEEDOWN: Prior := 1;
3824 A_ATTACKUP: Prior := 2;
3825 A_ATTACKDOWN: Prior := 2;
3826 A_PAIN: Prior := 4;
3827 else Prior := 0;
3828 end;
3830 if (Prior > FActionPrior) or Force then
3831 if not ((Prior = 2) and (FCurrWeap = WEAPON_SAW)) then
3832 begin
3833 FActionPrior := Prior;
3834 FActionAnim := Action;
3835 FActionForce := Force;
3836 FActionChanged := True;
3837 end;
3839 if Action in [A_ATTACK, A_ATTACKUP, A_ATTACKDOWN] then FModel.SetFire(True);
3840 end;
3842 function TPlayer.StayOnStep(XInc, YInc: Integer): Boolean;
3843 begin
3844 Result := not g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+YInc+PLAYER_RECT.Y+PLAYER_RECT.Height-1,
3845 PLAYER_RECT.Width, 1, PANEL_STEP, False)
3846 and g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+YInc+PLAYER_RECT.Y+PLAYER_RECT.Height,
3847 PLAYER_RECT.Width, 1, PANEL_STEP, False);
3848 end;
3850 function TPlayer.TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean;
3851 begin
3852 Result := False;
3854 if g_CollideLevel(X, Y, PLAYER_RECT.Width, PLAYER_RECT.Height) then
3855 begin
3856 g_Sound_PlayExAt('SOUND_GAME_NOTELEPORT', FObj.X, FObj.Y);
3857 if g_Game_IsServer and g_Game_IsNet then
3858 MH_SEND_Sound(FObj.X, FObj.Y, 'SOUND_GAME_NOTELEPORT');
3859 Exit;
3860 end;
3862 FJustTeleported := True;
3864 if not silent then
3865 begin
3866 g_Sound_PlayExAt('SOUND_GAME_TELEPORT', FObj.X, FObj.Y);
3867 {$IFDEF ENABLE_GFX}
3868 g_GFX_QueueEffect(
3869 R_GFX_TELEPORT_FAST,
3870 FObj.X + PLAYER_RECT.X + (PLAYER_RECT.Width div 2) - 32,
3871 FObj.Y + PLAYER_RECT.Y + (PLAYER_RECT.Height div 2) - 32
3872 );
3873 {$ENDIF}
3874 if g_Game_IsServer and g_Game_IsNet then
3875 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
3876 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 1,
3877 NET_GFX_TELE);
3878 end;
3880 FObj.X := X-PLAYER_RECT.X;
3881 FObj.Y := Y-PLAYER_RECT.Y;
3882 FObj.oldX := FObj.X; // don't interpolate after respawn
3883 FObj.oldY := FObj.Y;
3884 if FAlive and FGhost then
3885 begin
3886 FXTo := FObj.X;
3887 FYTo := FObj.Y;
3888 end;
3890 if not g_Game_IsNet then
3891 begin
3892 if dir = 1 then
3893 begin
3894 SetDirection(TDirection.D_LEFT);
3895 FAngle := 180;
3896 end
3897 else
3898 if dir = 2 then
3899 begin
3900 SetDirection(TDirection.D_RIGHT);
3901 FAngle := 0;
3902 end
3903 else
3904 if dir = 3 then
3905 begin // обратное
3906 if FDirection = TDirection.D_RIGHT then
3907 begin
3908 SetDirection(TDirection.D_LEFT);
3909 FAngle := 180;
3910 end
3911 else
3912 begin
3913 SetDirection(TDirection.D_RIGHT);
3914 FAngle := 0;
3915 end;
3916 end;
3917 end;
3919 if not silent then
3920 begin
3921 {$IFDEF ENABLE_GFX}
3922 g_GFX_QueueEffect(
3923 R_GFX_TELEPORT_FAST,
3924 FObj.X + PLAYER_RECT.X + (PLAYER_RECT.Width div 2) - 32,
3925 FObj.Y + PLAYER_RECT.Y + (PLAYER_RECT.Height div 2) - 32
3926 );
3927 {$ENDIF}
3928 if g_Game_IsServer and g_Game_IsNet then
3929 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
3930 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 0,
3931 NET_GFX_TELE);
3932 end;
3934 Result := True;
3935 end;
3937 function nonz(a: Single): Single;
3938 begin
3939 if a <> 0 then
3940 Result := a
3941 else
3942 Result := 1;
3943 end;
3945 function TPlayer.refreshCorpse(): Boolean;
3946 var
3947 i: Integer;
3948 begin
3949 Result := False;
3950 FCorpse := -1;
3951 if FAlive or FSpectator then
3952 Exit;
3953 if (gCorpses = nil) or (Length(gCorpses) = 0) then
3954 Exit;
3955 for i := 0 to High(gCorpses) do
3956 if gCorpses[i] <> nil then
3957 if gCorpses[i].FPlayerUID = FUID then
3958 begin
3959 Result := True;
3960 FCorpse := i;
3961 break;
3962 end;
3963 end;
3965 function TPlayer.getCameraObj(): TObj;
3966 begin
3967 if (not FAlive) and (not FSpectator) and
3968 (FCorpse >= 0) and (FCorpse < Length(gCorpses)) and
3969 (gCorpses[FCorpse] <> nil) and (gCorpses[FCorpse].FPlayerUID = FUID) then
3970 begin
3971 gCorpses[FCorpse].FObj.slopeUpLeft := FObj.slopeUpLeft;
3972 Result := gCorpses[FCorpse].FObj;
3973 end
3974 else
3975 begin
3976 Result := FObj;
3977 end;
3978 end;
3980 procedure TPlayer.PreUpdate();
3981 begin
3982 FSlopeOld := FObj.slopeUpLeft;
3983 FIncCamOld := FIncCam;
3984 FObj.oldX := FObj.X;
3985 FObj.oldY := FObj.Y;
3986 end;
3988 procedure TPlayer.Update();
3989 var
3990 b: Byte;
3991 i, ii, wx, wy, xd, yd, k: Integer;
3992 blockmon, headwater, dospawn: Boolean;
3993 NetServer: Boolean;
3994 AnyServer: Boolean;
3995 SetSpect: Boolean;
3996 begin
3997 NetServer := g_Game_IsNet and g_Game_IsServer;
3998 AnyServer := g_Game_IsServer;
4000 if g_Game_IsClient and (NetInterpLevel > 0) then
4001 DoLerp(NetInterpLevel + 1)
4002 else
4003 if FGhost then
4004 DoLerp(4);
4006 if NetServer then
4007 if FClientID >= 0 then
4008 begin
4009 FPing := NetClients[FClientID].Peer^.lastRoundTripTime;
4010 if NetClients[FClientID].Peer^.packetsSent > 0 then
4011 FLoss := Round(100*NetClients[FClientID].Peer^.packetsLost/NetClients[FClientID].Peer^.packetsSent)
4012 else
4013 FLoss := 0;
4014 end else
4015 begin
4016 FPing := 0;
4017 FLoss := 0;
4018 end;
4020 if FAlive then
4021 FPunchAnim.Update;
4022 if FPunchAnim.played then
4023 FPunchAnim.Disable;
4025 if FAlive and (gFly or FJetpack) then
4026 FlySmoke();
4028 if FDirection = TDirection.D_LEFT then
4029 FAngle := 180
4030 else
4031 FAngle := 0;
4033 if FAlive and (not FGhost) then
4034 begin
4035 if FKeys[KEY_UP].Pressed then
4036 SeeUp();
4037 if FKeys[KEY_DOWN].Pressed then
4038 SeeDown();
4039 end;
4041 if (not (FKeys[KEY_UP].Pressed or FKeys[KEY_DOWN].Pressed)) and
4042 (FIncCam <> 0) then
4043 begin
4044 i := g_basic.Sign(FIncCam);
4045 FIncCam := Abs(FIncCam);
4046 DecMin(FIncCam, 5, 0);
4047 FIncCam := FIncCam*i;
4048 end;
4050 // no need to do that each second frame, weapon queue will take care of it
4051 if FAlive and FKeys[KEY_NEXTWEAPON].Pressed and AnyServer then NextWeapon();
4052 if FAlive and FKeys[KEY_PREVWEAPON].Pressed and AnyServer then PrevWeapon();
4054 if gTime mod (GAME_TICK*2) <> 0 then
4055 begin
4056 if (FObj.Vel.X = 0) and FAlive then
4057 begin
4058 if FKeys[KEY_LEFT].Pressed then
4059 Run(TDirection.D_LEFT);
4060 if FKeys[KEY_RIGHT].Pressed then
4061 Run(TDirection.D_RIGHT);
4062 end;
4064 if FPhysics then
4065 begin
4066 g_Obj_Move(@FObj, True, True, True);
4067 positionChanged(); // this updates spatial accelerators
4068 end;
4070 Exit;
4071 end;
4073 FActionChanged := False;
4075 if FAlive then
4076 begin
4077 // Let alive player do some actions
4078 if FKeys[KEY_LEFT].Pressed then Run(TDirection.D_LEFT);
4079 if FKeys[KEY_RIGHT].Pressed then Run(TDirection.D_RIGHT);
4080 //if FKeys[KEY_NEXTWEAPON].Pressed and AnyServer then NextWeapon();
4081 //if FKeys[KEY_PREVWEAPON].Pressed and AnyServer then PrevWeapon();
4082 if FKeys[KEY_FIRE].Pressed and AnyServer then Fire()
4083 else
4084 begin
4085 if AnyServer then
4086 begin
4087 FlamerOff;
4088 if NetServer then MH_SEND_PlayerStats(FUID);
4089 end;
4090 end;
4091 if FKeys[KEY_OPEN].Pressed and AnyServer then Use();
4092 if FKeys[KEY_JUMP].Pressed then Jump()
4093 else
4094 begin
4095 if AnyServer and FJetpack then
4096 begin
4097 FJetpack := False;
4098 JetpackOff;
4099 if NetServer then MH_SEND_PlayerStats(FUID);
4100 end;
4101 FCanJetpack := True;
4102 end;
4103 end
4104 else // Dead
4105 begin
4106 dospawn := False;
4107 if not FGhost then
4108 for k := Low(FKeys) to KEY_CHAT-1 do
4109 begin
4110 if FKeys[k].Pressed then
4111 begin
4112 dospawn := True;
4113 break;
4114 end;
4115 end;
4116 if dospawn then
4117 begin
4118 if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
4119 Respawn(False)
4120 else // Single
4121 if (FTime[T_RESPAWN] <= gTime) and
4122 gGameOn and (not FAlive) then
4123 begin
4124 if (g_Player_GetCount() > 1) then
4125 Respawn(False)
4126 else
4127 begin
4128 gExit := EXIT_RESTART;
4129 Exit;
4130 end;
4131 end;
4132 end;
4133 // Dead spectator actions
4134 if FGhost then
4135 begin
4136 if FKeys[KEY_OPEN].Pressed and AnyServer then Fire();
4137 if FKeys[KEY_FIRE].Pressed and AnyServer then
4138 begin
4139 if FSpectator then
4140 begin
4141 if (FSpectatePlayer >= High(gPlayers)) then
4142 FSpectatePlayer := -1
4143 else
4144 begin
4145 SetSpect := False;
4146 for I := FSpectatePlayer + 1 to High(gPlayers) do
4147 if gPlayers[I] <> nil then
4148 if gPlayers[I].alive then
4149 if gPlayers[I].UID <> FUID then
4150 begin
4151 FSpectatePlayer := I;
4152 SetSpect := True;
4153 break;
4154 end;
4156 if not SetSpect then FSpectatePlayer := -1;
4157 end;
4159 ReleaseKeys;
4160 end;
4161 end;
4162 end;
4163 end;
4164 // No clipping
4165 if FGhost then
4166 begin
4167 if FKeys[KEY_UP].Pressed or FKeys[KEY_JUMP].Pressed then
4168 begin
4169 FYTo := FObj.Y - 32;
4170 FSpectatePlayer := -1;
4171 end;
4172 if FKeys[KEY_DOWN].Pressed then
4173 begin
4174 FYTo := FObj.Y + 32;
4175 FSpectatePlayer := -1;
4176 end;
4177 if FKeys[KEY_LEFT].Pressed then
4178 begin
4179 FXTo := FObj.X - 32;
4180 FSpectatePlayer := -1;
4181 end;
4182 if FKeys[KEY_RIGHT].Pressed then
4183 begin
4184 FXTo := FObj.X + 32;
4185 FSpectatePlayer := -1;
4186 end;
4188 if (FXTo < -64) then
4189 FXTo := -64
4190 else if (FXTo > gMapInfo.Width + 32) then
4191 FXTo := gMapInfo.Width + 32;
4192 if (FYTo < -72) then
4193 FYTo := -72
4194 else if (FYTo > gMapInfo.Height + 32) then
4195 FYTo := gMapInfo.Height + 32;
4196 end;
4198 if FPhysics then
4199 begin
4200 g_Obj_Move(@FObj, True, True, True);
4201 positionChanged(); // this updates spatial accelerators
4202 end
4203 else
4204 begin
4205 FObj.Vel.X := 0;
4206 FObj.Vel.Y := 0;
4207 if FSpectator then
4208 if (FSpectatePlayer <= High(gPlayers)) and (FSpectatePlayer >= 0) then
4209 if gPlayers[FSpectatePlayer] <> nil then
4210 if gPlayers[FSpectatePlayer].alive then
4211 begin
4212 FXTo := gPlayers[FSpectatePlayer].GameX;
4213 FYTo := gPlayers[FSpectatePlayer].GameY;
4214 end;
4215 end;
4217 blockmon := g_Map_CollidePanel(FObj.X+PLAYER_HEADRECT.X, FObj.Y+PLAYER_HEADRECT.Y,
4218 PLAYER_HEADRECT.Width, PLAYER_HEADRECT.Height,
4219 PANEL_BLOCKMON, True);
4220 headwater := HeadInLiquid(0, 0);
4222 // Сопротивление воздуха:
4223 if (not FAlive) or not (FKeys[KEY_LEFT].Pressed or FKeys[KEY_RIGHT].Pressed) then
4224 if FObj.Vel.X <> 0 then
4225 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
4227 if (FLastHit = HIT_TRAP) and (FPain > 90) then FPain := 90;
4228 DecMin(FPain, 5, 0);
4229 DecMin(FPickup, 1, 0);
4231 if FAlive and (FObj.Y > Integer(gMapInfo.Height)+128) and AnyServer then
4232 begin
4233 // Обнулить действия примочек, чтобы фон пропал
4234 FMegaRulez[MR_SUIT] := 0;
4235 FMegaRulez[MR_INVUL] := 0;
4236 FMegaRulez[MR_INVIS] := 0;
4237 Kill(K_FALLKILL, 0, HIT_FALL);
4238 end;
4240 i := 9;
4242 if FAlive then
4243 begin
4244 if FCurrWeap = WEAPON_SAW then
4245 if not (FSawSound.IsPlaying() or FSawSoundHit.IsPlaying() or
4246 FSawSoundSelect.IsPlaying()) then
4247 FSawSoundIdle.PlayAt(FObj.X, FObj.Y);
4249 if FJetpack then
4250 if (not FJetSoundFly.IsPlaying()) and (not FJetSoundOn.IsPlaying()) and
4251 (not FJetSoundOff.IsPlaying()) then
4252 begin
4253 FJetSoundFly.SetPosition(0);
4254 FJetSoundFly.PlayAt(FObj.X, FObj.Y);
4255 end;
4257 for b := WP_FIRST to WP_LAST do
4258 if FReloading[b] > 0 then
4259 if FNoReload then
4260 FReloading[b] := 0
4261 else
4262 Dec(FReloading[b]);
4264 {$IFDEF ENABLE_SHELLS}
4265 if FShellTimer > -1 then
4266 if FShellTimer = 0 then
4267 begin
4268 if FShellType = SHELL_SHELL then
4269 g_Shells_Create(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
4270 GameVelX, GameVelY-2, SHELL_SHELL)
4271 else if FShellType = SHELL_DBLSHELL then
4272 begin
4273 g_Shells_Create(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
4274 GameVelX+1, GameVelY-2, SHELL_SHELL);
4275 g_Shells_Create(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
4276 GameVelX-1, GameVelY-2, SHELL_SHELL);
4277 end;
4278 FShellTimer := -1;
4279 end else Dec(FShellTimer);
4280 {$ENDIF}
4282 if (FBFGFireCounter > -1) then
4283 if FBFGFireCounter = 0 then
4284 begin
4285 if AnyServer then
4286 begin
4287 wx := FObj.X+WEAPONPOINT[FDirection].X;
4288 wy := FObj.Y+WEAPONPOINT[FDirection].Y;
4289 xd := wx+IfThen(FDirection = TDirection.D_LEFT, -30, 30);
4290 yd := wy+firediry();
4291 g_Weapon_bfgshot(wx, wy, xd, yd, FUID);
4292 if NetServer then MH_SEND_PlayerFire(FUID, WEAPON_BFG, wx, wy, xd, yd);
4293 if (FAngle = 0) or (FAngle = 180) then SetAction(A_ATTACK)
4294 else if (FAngle = ANGLE_LEFTDOWN) or (FAngle = ANGLE_RIGHTDOWN) then SetAction(A_ATTACKDOWN)
4295 else if (FAngle = ANGLE_LEFTUP) or (FAngle = ANGLE_RIGHTUP) then SetAction(A_ATTACKUP);
4296 end;
4298 FReloading[WEAPON_BFG] := WEAPON_RELOAD[WEAPON_BFG];
4299 FBFGFireCounter := -1;
4300 end else
4301 if FNoReload then
4302 FBFGFireCounter := 0
4303 else
4304 Dec(FBFGFireCounter);
4306 if (FMegaRulez[MR_SUIT] < gTime) and AnyServer then
4307 begin
4308 b := g_GetAcidHit(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width, PLAYER_RECT.Height);
4310 if (b > 0) and (gTime mod (15*GAME_TICK) = 0) then Damage(b, 0, 0, 0, HIT_ACID);
4311 end;
4313 if (headwater or blockmon) then
4314 begin
4315 Dec(FAir);
4317 if FAir < -9 then
4318 begin
4319 if AnyServer then Damage(10, 0, 0, 0, HIT_WATER);
4320 FAir := 0;
4321 end
4322 else if (FAir mod 31 = 0) and not blockmon then
4323 begin
4324 {$IFDEF ENABLE_GFX}
4325 g_GFX_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2), FObj.Y+PLAYER_RECT.Y-4, 5+Random(6), 8, 4);
4326 {$ENDIF}
4327 if Random(2) = 0 then
4328 g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
4329 else
4330 g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
4331 end;
4332 end else if FAir < AIR_DEF then
4333 FAir := AIR_DEF;
4335 if FFireTime > 0 then
4336 begin
4337 if BodyInLiquid(0, 0) then
4338 begin
4339 FFireTime := 0;
4340 FFirePainTime := 0;
4341 end
4342 else if FMegaRulez[MR_SUIT] >= gTime then
4343 begin
4344 if FMegaRulez[MR_SUIT] = gTime then
4345 FFireTime := 1;
4346 FFirePainTime := 0;
4347 end
4348 else
4349 begin
4350 OnFireFlame(1);
4351 if FFirePainTime <= 0 then
4352 begin
4353 if g_Game_IsServer then
4354 Damage(2, FFireAttacker, 0, 0, HIT_FLAME);
4355 FFirePainTime := 12 - FFireTime div 12;
4356 end;
4357 FFirePainTime := FFirePainTime - 1;
4358 FFireTime := FFireTime - 1;
4359 if ((FFireTime mod 33) = 0) and (FMegaRulez[MR_INVUL] < gTime) then
4360 FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
4361 if (FFireTime = 0) and g_Game_IsNet and g_Game_IsServer then
4362 MH_SEND_PlayerStats(FUID);
4363 end;
4364 end;
4366 if FDamageBuffer > 0 then
4367 begin
4368 if FDamageBuffer >= 9 then
4369 begin
4370 SetAction(A_PAIN);
4372 if FDamageBuffer < 30 then i := 9
4373 else if FDamageBuffer < 100 then i := 18
4374 else i := 27;
4375 end;
4377 ii := Round(FDamageBuffer*FHealth / nonz(FArmor*(3/4)+FHealth));
4378 FArmor := FArmor-(FDamageBuffer-ii);
4379 FHealth := FHealth-ii;
4380 if FArmor < 0 then
4381 begin
4382 FHealth := FHealth+FArmor;
4383 FArmor := 0;
4384 end;
4386 if AnyServer then
4387 if FHealth <= 0 then
4388 if FHealth > -30 then Kill(K_SIMPLEKILL, FLastSpawnerUID, FLastHit)
4389 else if FHealth > -50 then Kill(K_HARDKILL, FLastSpawnerUID, FLastHit)
4390 else Kill(K_EXTRAHARDKILL, FLastSpawnerUID, FLastHit);
4392 if FAlive and ((FLastHit <> HIT_FLAME) or (FFireTime <= 0)) then
4393 begin
4394 if FDamageBuffer <= 20 then FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y)
4395 else if FDamageBuffer <= 55 then FModel.PlaySound(MODELSOUND_PAIN, 2, FObj.X, FObj.Y)
4396 else if FDamageBuffer <= 120 then FModel.PlaySound(MODELSOUND_PAIN, 3, FObj.X, FObj.Y)
4397 else FModel.PlaySound(MODELSOUND_PAIN, 4, FObj.X, FObj.Y);
4398 end;
4400 FDamageBuffer := 0;
4401 end;
4403 {CollideItem();}
4404 end; // if FAlive then ...
4406 if (FActionAnim = A_PAIN) and (FModel.Animation <> A_PAIN) then
4407 begin
4408 FModel.ChangeAnimation(FActionAnim, FActionForce);
4409 FModel.AnimState.MinLength := i;
4410 end else FModel.ChangeAnimation(FActionAnim, FActionForce and (FModel.Animation <> A_STAND));
4412 if (FModel.AnimState.Played or ((not FActionChanged) and (FModel.Animation = A_WALK)))
4413 then SetAction(A_STAND, True);
4415 if not ((FModel.Animation = A_WALK) and (Abs(FObj.Vel.X) < 4) and not FModel.GetFire()) then FModel.Update;
4417 for b := Low(FKeys) to High(FKeys) do
4418 if FKeys[b].Time = 0 then FKeys[b].Pressed := False else Dec(FKeys[b].Time);
4419 end;
4422 procedure TPlayer.getMapBox (out x, y, w, h: Integer); inline;
4423 begin
4424 x := FObj.X+PLAYER_RECT.X;
4425 y := FObj.Y+PLAYER_RECT.Y;
4426 w := PLAYER_RECT.Width;
4427 h := PLAYER_RECT.Height;
4428 end;
4431 procedure TPlayer.moveBy (dx, dy: Integer); inline;
4432 begin
4433 if (dx <> 0) or (dy <> 0) then
4434 begin
4435 FObj.X += dx;
4436 FObj.Y += dy;
4437 positionChanged();
4438 end;
4439 end;
4442 function TPlayer.Collide(X, Y: Integer; Width, Height: Word): Boolean;
4443 begin
4444 Result := g_Collide(FObj.X+PLAYER_RECT.X,
4445 FObj.Y+PLAYER_RECT.Y,
4446 PLAYER_RECT.Width,
4447 PLAYER_RECT.Height,
4448 X, Y,
4449 Width, Height);
4450 end;
4452 function TPlayer.Collide(Panel: TPanel): Boolean;
4453 begin
4454 Result := g_Collide(FObj.X+PLAYER_RECT.X,
4455 FObj.Y+PLAYER_RECT.Y,
4456 PLAYER_RECT.Width,
4457 PLAYER_RECT.Height,
4458 Panel.X, Panel.Y,
4459 Panel.Width, Panel.Height);
4460 end;
4462 function TPlayer.Collide(X, Y: Integer): Boolean;
4463 begin
4464 X := X-FObj.X-PLAYER_RECT.X;
4465 Y := Y-FObj.Y-PLAYER_RECT.Y;
4466 Result := (x >= 0) and (x <= PLAYER_RECT.Width) and
4467 (y >= 0) and (y <= PLAYER_RECT.Height);
4468 end;
4470 function g_Player_ValidName(Name: string): Boolean;
4471 var
4472 a: Integer;
4473 begin
4474 Result := True;
4476 if gPlayers = nil then Exit;
4478 for a := 0 to High(gPlayers) do
4479 if gPlayers[a] <> nil then
4480 if LowerCase(Name) = LowerCase(gPlayers[a].FName) then
4481 begin
4482 Result := False;
4483 Exit;
4484 end;
4485 end;
4487 procedure TPlayer.SetDirection(Direction: TDirection);
4488 var
4489 d: TDirection;
4490 begin
4491 d := FModel.Direction;
4493 FModel.Direction := Direction;
4494 if d <> Direction then FModel.ChangeAnimation(FModel.Animation, True);
4496 FDirection := Direction;
4497 end;
4499 function TPlayer.GetKeys(): Byte;
4500 begin
4501 Result := 0;
4503 if R_KEY_RED in FRulez then Result := KEY_RED;
4504 if R_KEY_GREEN in FRulez then Result := Result or KEY_GREEN;
4505 if R_KEY_BLUE in FRulez then Result := Result or KEY_BLUE;
4507 if FTeam = TEAM_RED then Result := Result or KEY_REDTEAM;
4508 if FTeam = TEAM_BLUE then Result := Result or KEY_BLUETEAM;
4509 end;
4511 procedure TPlayer.Use();
4512 var
4513 a: Integer;
4514 begin
4515 if FTime[T_USE] > gTime then Exit;
4517 g_Triggers_PressR(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width,
4518 PLAYER_RECT.Height, FUID, ACTIVATE_PLAYERPRESS);
4520 for a := 0 to High(gPlayers) do
4521 if (gPlayers[a] <> nil) and (gPlayers[a] <> Self) and
4522 gPlayers[a].alive and SameTeam(FUID, gPlayers[a].FUID) and
4523 g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
4524 FObj.Rect.Width, FObj.Rect.Height, @gPlayers[a].FObj) then
4525 begin
4526 gPlayers[a].Touch();
4527 if g_Game_IsNet and g_Game_IsServer then
4528 MH_SEND_GameEvent(NET_EV_PLAYER_TOUCH, gPlayers[a].FUID);
4529 end;
4531 FTime[T_USE] := gTime+120;
4532 end;
4534 procedure TPlayer.NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1);
4535 var
4536 locObj: TObj;
4537 F: Boolean;
4538 WX, WY, XD, YD: Integer;
4539 begin
4540 F := False;
4541 WX := X;
4542 WY := Y;
4543 XD := AX;
4544 YD := AY;
4546 case FCurrWeap of
4547 WEAPON_KASTET:
4548 begin
4549 DoPunch();
4550 if R_BERSERK in FRulez then
4551 begin
4552 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
4553 locobj.X := FObj.X+FObj.Rect.X;
4554 locobj.Y := FObj.Y+FObj.Rect.Y;
4555 locobj.rect.X := 0;
4556 locobj.rect.Y := 0;
4557 locobj.rect.Width := 39;
4558 locobj.rect.Height := 52;
4559 locobj.Vel.X := (xd-wx) div 2;
4560 locobj.Vel.Y := (yd-wy) div 2;
4561 locobj.Accel.X := xd-wx;
4562 locobj.Accel.y := yd-wy;
4564 if g_Weapon_Hit(@locobj, 50, FUID, HIT_SOME) <> 0 then
4565 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj.X, FObj.Y)
4566 else
4567 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj.X, FObj.Y);
4569 if gFlash = 1 then
4570 if FPain < 50 then
4571 FPain := min(FPain + 25, 50);
4572 end else
4573 g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 3, FUID);
4574 end;
4576 WEAPON_SAW:
4577 begin
4578 if g_Weapon_chainsaw(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
4579 IfThen(gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF], 9, 3), FUID) <> 0 then
4580 begin
4581 FSawSoundSelect.Stop();
4582 FSawSound.Stop();
4583 FSawSoundHit.PlayAt(FObj.X, FObj.Y);
4584 end
4585 else if not FSawSoundHit.IsPlaying() then
4586 begin
4587 FSawSoundSelect.Stop();
4588 FSawSound.PlayAt(FObj.X, FObj.Y);
4589 end;
4590 f := True;
4591 end;
4593 WEAPON_PISTOL:
4594 begin
4595 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', GameX, Gamey);
4596 FFireAngle := FAngle;
4597 f := True;
4598 {$IFDEF ENABLE_SHELLS}
4599 g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_BULLET);
4600 {$ENDIF}
4601 end;
4603 WEAPON_SHOTGUN1:
4604 begin
4605 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex, Gamey);
4606 FFireAngle := FAngle;
4607 f := True;
4608 {$IFDEF ENABLE_SHELLS}
4609 FShellTimer := 10;
4610 FShellType := SHELL_SHELL;
4611 {$ENDIF}
4612 end;
4614 WEAPON_SHOTGUN2:
4615 begin
4616 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', Gamex, Gamey);
4617 FFireAngle := FAngle;
4618 f := True;
4619 {$IFDEF ENABLE_SHELLS}
4620 FShellTimer := 13;
4621 FShellType := SHELL_DBLSHELL;
4622 {$ENDIF}
4623 end;
4625 WEAPON_CHAINGUN:
4626 begin
4627 g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', Gamex, Gamey);
4628 FFireAngle := FAngle;
4629 f := True;
4630 {$IFDEF ENABLE_SHELLS}
4631 g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_BULLET);
4632 {$ENDIF}
4633 end;
4635 WEAPON_ROCKETLAUNCHER:
4636 begin
4637 g_Weapon_Rocket(wx, wy, xd, yd, FUID, WID);
4638 FFireAngle := FAngle;
4639 f := True;
4640 end;
4642 WEAPON_PLASMA:
4643 begin
4644 g_Weapon_Plasma(wx, wy, xd, yd, FUID, WID);
4645 FFireAngle := FAngle;
4646 f := True;
4647 end;
4649 WEAPON_BFG:
4650 begin
4651 g_Weapon_BFGShot(wx, wy, xd, yd, FUID, WID);
4652 FFireAngle := FAngle;
4653 f := True;
4654 end;
4656 WEAPON_SUPERPULEMET:
4657 begin
4658 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex, Gamey);
4659 FFireAngle := FAngle;
4660 f := True;
4661 {$IFDEF ENABLE_SHELLS}
4662 g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_SHELL);
4663 {$ENDIF}
4664 end;
4666 WEAPON_FLAMETHROWER:
4667 begin
4668 g_Weapon_flame(wx, wy, xd, yd, FUID, WID);
4669 FlamerOn;
4670 FFireAngle := FAngle;
4671 f := True;
4672 end;
4673 end;
4675 if not f then Exit;
4677 if (FAngle = 0) or (FAngle = 180) then SetAction(A_ATTACK)
4678 else if (FAngle = ANGLE_LEFTDOWN) or (FAngle = ANGLE_RIGHTDOWN) then SetAction(A_ATTACKDOWN)
4679 else if (FAngle = ANGLE_LEFTUP) or (FAngle = ANGLE_RIGHTUP) then SetAction(A_ATTACKUP);
4680 end;
4682 procedure TPlayer.DoLerp(Level: Integer = 2);
4683 begin
4684 if FObj.X <> FXTo then FObj.X := Lerp(FObj.X, FXTo, Level);
4685 if FObj.Y <> FYTo then FObj.Y := Lerp(FObj.Y, FYTo, Level);
4686 end;
4688 procedure TPlayer.SetLerp(XTo, YTo: Integer);
4689 var
4690 AX, AY: Integer;
4691 begin
4692 FXTo := XTo;
4693 FYTo := YTo;
4694 if FJustTeleported or (NetInterpLevel < 1) then
4695 begin
4696 FObj.X := XTo;
4697 FObj.Y := YTo;
4698 if FJustTeleported then
4699 begin
4700 FObj.oldX := FObj.X;
4701 FObj.oldY := FObj.Y;
4702 end;
4703 end
4704 else
4705 begin
4706 AX := Abs(FXTo - FObj.X);
4707 AY := Abs(FYTo - FObj.Y);
4708 if (AX > 32) or (AX <= NetInterpLevel) then
4709 FObj.X := FXTo;
4710 if (AY > 32) or (AY <= NetInterpLevel) then
4711 FObj.Y := FYTo;
4712 end;
4713 end;
4715 function TPlayer.FullInLift(XInc, YInc: Integer): Integer;
4716 begin
4717 if g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
4718 PLAYER_RECT.Width, PLAYER_RECT.Height-8,
4719 PANEL_LIFTUP, False) then Result := -1
4720 else
4721 if g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
4722 PLAYER_RECT.Width, PLAYER_RECT.Height-8,
4723 PANEL_LIFTDOWN, False) then Result := 1
4724 else Result := 0;
4725 end;
4727 function TPlayer.GetFlag(Flag: Byte): Boolean;
4728 var
4729 s, ts: String;
4730 evtype, a: Byte;
4731 begin
4732 Result := False;
4734 if Flag = FLAG_NONE then
4735 Exit;
4737 if not g_Game_IsServer then Exit;
4739 // Принес чужой флаг на свою базу:
4740 if (Flag = FTeam) and
4741 (gFlags[Flag].State = FLAG_STATE_NORMAL) and
4742 (FFlag <> FLAG_NONE) then
4743 begin
4744 if FFlag = FLAG_RED then
4745 s := _lc[I_PLAYER_FLAG_RED]
4746 else
4747 s := _lc[I_PLAYER_FLAG_BLUE];
4749 evtype := FLAG_STATE_SCORED;
4751 ts := Format('%.4d', [gFlags[FFlag].CaptureTime]);
4752 Insert('.', ts, Length(ts) + 1 - 3);
4753 g_Console_Add(Format(_lc[I_PLAYER_FLAG_CAPTURE], [FName, s, ts]), True);
4755 g_Map_ResetFlag(FFlag);
4756 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_CAPTURE], [AnsiUpperCase(s)]), 144);
4758 if ((Self = gPlayer1) or (Self = gPlayer2)
4759 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
4760 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
4761 a := 0
4762 else
4763 a := 1;
4765 if not sound_cap_flag[a].IsPlaying() then
4766 sound_cap_flag[a].Play();
4768 gTeamStat[FTeam].Goals := gTeamStat[FTeam].Goals + 1;
4770 Result := True;
4771 if g_Game_IsNet then
4772 begin
4773 MH_SEND_FlagEvent(evtype, FFlag, FUID, False);
4774 MH_SEND_GameStats;
4775 end;
4777 gFlags[FFlag].CaptureTime := 0;
4778 SetFlag(FLAG_NONE);
4779 Exit;
4780 end;
4782 // Подобрал свой флаг - вернул его на базу:
4783 if (Flag = FTeam) and
4784 (gFlags[Flag].State = FLAG_STATE_DROPPED) then
4785 begin
4786 if Flag = FLAG_RED then
4787 s := _lc[I_PLAYER_FLAG_RED]
4788 else
4789 s := _lc[I_PLAYER_FLAG_BLUE];
4791 evtype := FLAG_STATE_RETURNED;
4792 gFlags[Flag].CaptureTime := 0;
4794 g_Console_Add(Format(_lc[I_PLAYER_FLAG_RETURN], [FName, s]), True);
4796 g_Map_ResetFlag(Flag);
4797 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
4799 if ((Self = gPlayer1) or (Self = gPlayer2)
4800 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
4801 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
4802 a := 0
4803 else
4804 a := 1;
4806 if not sound_ret_flag[a].IsPlaying() then
4807 sound_ret_flag[a].Play();
4809 Result := True;
4810 if g_Game_IsNet then
4811 begin
4812 MH_SEND_FlagEvent(evtype, Flag, FUID, False);
4813 MH_SEND_GameStats;
4814 end;
4815 Exit;
4816 end;
4818 // Подобрал чужой флаг:
4819 if (Flag <> FTeam) and (FTime[T_FLAGCAP] <= gTime) then
4820 begin
4821 SetFlag(Flag);
4823 if Flag = FLAG_RED then
4824 s := _lc[I_PLAYER_FLAG_RED]
4825 else
4826 s := _lc[I_PLAYER_FLAG_BLUE];
4828 evtype := FLAG_STATE_CAPTURED;
4830 g_Console_Add(Format(_lc[I_PLAYER_FLAG_GET], [FName, s]), True);
4832 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_GET], [AnsiUpperCase(s)]), 144);
4834 gFlags[Flag].State := FLAG_STATE_CAPTURED;
4836 if ((Self = gPlayer1) or (Self = gPlayer2)
4837 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
4838 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
4839 a := 0
4840 else
4841 a := 1;
4843 if not sound_get_flag[a].IsPlaying() then
4844 sound_get_flag[a].Play();
4846 Result := True;
4847 if g_Game_IsNet then
4848 begin
4849 MH_SEND_FlagEvent(evtype, Flag, FUID, False);
4850 MH_SEND_GameStats;
4851 end;
4852 end;
4853 end;
4855 procedure TPlayer.SetFlag(Flag: Byte);
4856 begin
4857 FFlag := Flag;
4858 if FModel <> nil then
4859 FModel.SetFlag(FFlag);
4860 end;
4862 function TPlayer.DropFlag(Silent: Boolean = True): Boolean;
4863 var
4864 s: String;
4865 a: Byte;
4866 begin
4867 Result := False;
4868 if (not g_Game_IsServer) or (FFlag = FLAG_NONE) then
4869 Exit;
4870 FTime[T_FLAGCAP] := gTime + 2000;
4871 with gFlags[FFlag] do
4872 begin
4873 Obj.X := FObj.X;
4874 Obj.Y := FObj.Y;
4875 Direction := FDirection;
4876 State := FLAG_STATE_DROPPED;
4877 Count := FLAG_TIME;
4878 g_Obj_Push(@Obj, (FObj.Vel.X div 2)-2+Random(5),
4879 (FObj.Vel.Y div 2)-2+Random(5));
4880 positionChanged(); // this updates spatial accelerators
4882 if FFlag = FLAG_RED then
4883 s := _lc[I_PLAYER_FLAG_RED]
4884 else
4885 s := _lc[I_PLAYER_FLAG_BLUE];
4887 g_Console_Add(Format(_lc[I_PLAYER_FLAG_DROP], [FName, s]), True);
4888 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_DROP], [AnsiUpperCase(s)]), 144);
4890 if ((Self = gPlayer1) or (Self = gPlayer2)
4891 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
4892 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
4893 a := 0
4894 else
4895 a := 1;
4897 if (not Silent) and (not sound_lost_flag[a].IsPlaying()) then
4898 sound_lost_flag[a].Play();
4900 if g_Game_IsNet then
4901 MH_SEND_FlagEvent(FLAG_STATE_DROPPED, Flag, FUID, False);
4902 end;
4903 SetFlag(FLAG_NONE);
4904 Result := True;
4905 end;
4907 procedure TPlayer.GetSecret();
4908 begin
4909 if (self = gPlayer1) or (self = gPlayer2) then
4910 begin
4911 g_Console_Add(Format(_lc[I_PLAYER_SECRET], [FName]), True);
4912 g_Sound_PlayEx('SOUND_GAME_SECRET');
4913 end;
4914 Inc(FSecrets);
4915 end;
4917 procedure TPlayer.PressKey(Key: Byte; Time: Word = 1);
4918 begin
4919 Assert(Key <= High(FKeys));
4921 FKeys[Key].Pressed := True;
4922 FKeys[Key].Time := Time;
4923 end;
4925 function TPlayer.IsKeyPressed(K: Byte): Boolean;
4926 begin
4927 Result := FKeys[K].Pressed;
4928 end;
4930 procedure TPlayer.ReleaseKeys();
4931 var
4932 a: Integer;
4933 begin
4934 for a := Low(FKeys) to High(FKeys) do
4935 begin
4936 FKeys[a].Pressed := False;
4937 FKeys[a].Time := 0;
4938 end;
4939 end;
4941 procedure TPlayer.OnDamage(Angle: SmallInt);
4942 begin
4943 end;
4945 function TPlayer.firediry(): Integer;
4946 begin
4947 if FKeys[KEY_UP].Pressed then Result := -42
4948 else if FKeys[KEY_DOWN].Pressed then Result := 19
4949 else Result := 0;
4950 end;
4952 procedure TPlayer.RememberState();
4953 var
4954 i: Integer;
4955 SavedState: TPlayerSavedState;
4956 begin
4957 SavedState.Health := FHealth;
4958 SavedState.Armor := FArmor;
4959 SavedState.Air := FAir;
4960 SavedState.JetFuel := FJetFuel;
4961 SavedState.CurrWeap := FCurrWeap;
4962 SavedState.NextWeap := FNextWeap;
4963 SavedState.NextWeapDelay := FNextWeapDelay;
4964 for i := Low(FWeapon) to High(FWeapon) do
4965 SavedState.Weapon[i] := FWeapon[i];
4966 for i := Low(FAmmo) to High(FAmmo) do
4967 SavedState.Ammo[i] := FAmmo[i];
4968 for i := Low(FMaxAmmo) to High(FMaxAmmo) do
4969 SavedState.MaxAmmo[i] := FMaxAmmo[i];
4970 SavedState.Rulez := FRulez - [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE];
4972 FSavedStateNum := -1;
4973 for i := Low(SavedStates) to High(SavedStates) do
4974 if not SavedStates[i].Used then
4975 begin
4976 FSavedStateNum := i;
4977 break;
4978 end;
4979 if FSavedStateNum < 0 then
4980 begin
4981 SetLength(SavedStates, Length(SavedStates) + 1);
4982 FSavedStateNum := High(SavedStates);
4983 end;
4985 SavedState.Used := True;
4986 SavedStates[FSavedStateNum] := SavedState;
4987 end;
4989 procedure TPlayer.RecallState();
4990 var
4991 i: Integer;
4992 SavedState: TPlayerSavedState;
4993 begin
4994 if(FSavedStateNum < 0) or (FSavedStateNum > High(SavedStates)) then
4995 Exit;
4997 SavedState := SavedStates[FSavedStateNum];
4998 SavedStates[FSavedStateNum].Used := False;
4999 FSavedStateNum := -1;
5001 FHealth := SavedState.Health;
5002 FArmor := SavedState.Armor;
5003 FAir := SavedState.Air;
5004 FJetFuel := SavedState.JetFuel;
5005 FCurrWeap := SavedState.CurrWeap;
5006 FNextWeap := SavedState.NextWeap;
5007 FNextWeapDelay := SavedState.NextWeapDelay;
5008 for i := Low(FWeapon) to High(FWeapon) do
5009 FWeapon[i] := SavedState.Weapon[i];
5010 for i := Low(FAmmo) to High(FAmmo) do
5011 FAmmo[i] := SavedState.Ammo[i];
5012 for i := Low(FMaxAmmo) to High(FMaxAmmo) do
5013 FMaxAmmo[i] := SavedState.MaxAmmo[i];
5014 FRulez := SavedState.Rulez;
5016 if gGameSettings.GameType = GT_SERVER then
5017 MH_SEND_PlayerStats(FUID);
5018 end;
5020 procedure TPlayer.SaveState (st: TStream);
5021 var
5022 i: Integer;
5023 b: Byte;
5024 begin
5025 // Сигнатура игрока
5026 utils.writeSign(st, 'PLYR');
5027 utils.writeInt(st, Byte(PLR_SAVE_VERSION)); // version
5028 // Бот или человек
5029 utils.writeBool(st, FIamBot);
5030 // UID игрока
5031 utils.writeInt(st, Word(FUID));
5032 // Имя игрока
5033 utils.writeStr(st, FName);
5034 // Команда
5035 utils.writeInt(st, Byte(FTeam));
5036 // Жив ли
5037 utils.writeBool(st, FAlive);
5038 // Израсходовал ли все жизни
5039 utils.writeBool(st, FNoRespawn);
5040 // Направление
5041 if FDirection = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT
5042 utils.writeInt(st, Byte(b));
5043 // Здоровье
5044 utils.writeInt(st, LongInt(FHealth));
5045 // Коэффициент инвалидности
5046 utils.writeInt(st, LongInt(FHandicap));
5047 // Жизни
5048 utils.writeInt(st, Byte(FLives));
5049 // Броня
5050 utils.writeInt(st, LongInt(FArmor));
5051 // Запас воздуха
5052 utils.writeInt(st, LongInt(FAir));
5053 // Запас горючего
5054 utils.writeInt(st, LongInt(FJetFuel));
5055 // Боль
5056 utils.writeInt(st, LongInt(FPain));
5057 // Убил
5058 utils.writeInt(st, LongInt(FKills));
5059 // Убил монстров
5060 utils.writeInt(st, LongInt(FMonsterKills));
5061 // Фрагов
5062 utils.writeInt(st, LongInt(FFrags));
5063 // Фрагов подряд
5064 utils.writeInt(st, Byte(FFragCombo));
5065 // Время последнего фрага
5066 utils.writeInt(st, LongWord(FLastFrag));
5067 // Смертей
5068 utils.writeInt(st, LongInt(FDeath));
5069 // Какой флаг несет
5070 utils.writeInt(st, Byte(FFlag));
5071 // Нашел секретов
5072 utils.writeInt(st, LongInt(FSecrets));
5073 // Текущее оружие
5074 utils.writeInt(st, Byte(FCurrWeap));
5075 // Желаемое оружие
5076 utils.writeInt(st, Word(FNextWeap));
5077 // ...и пауза
5078 utils.writeInt(st, Byte(FNextWeapDelay));
5079 // Время зарядки BFG
5080 utils.writeInt(st, SmallInt(FBFGFireCounter));
5081 // Буфер урона
5082 utils.writeInt(st, LongInt(FDamageBuffer));
5083 // Последний ударивший
5084 utils.writeInt(st, Word(FLastSpawnerUID));
5085 // Тип последнего полученного урона
5086 utils.writeInt(st, Byte(FLastHit));
5087 // Объект игрока
5088 Obj_SaveState(st, @FObj);
5089 // Текущее количество патронов
5090 for i := A_BULLETS to A_HIGH do utils.writeInt(st, Word(FAmmo[i]));
5091 // Максимальное количество патронов
5092 for i := A_BULLETS to A_HIGH do utils.writeInt(st, Word(FMaxAmmo[i]));
5093 // Наличие оружия
5094 for i := WP_FIRST to WP_LAST do utils.writeBool(st, FWeapon[i]);
5095 // Время перезарядки оружия
5096 for i := WP_FIRST to WP_LAST do utils.writeInt(st, Word(FReloading[i]));
5097 // Наличие рюкзака
5098 utils.writeBool(st, (R_ITEM_BACKPACK in FRulez));
5099 // Наличие красного ключа
5100 utils.writeBool(st, (R_KEY_RED in FRulez));
5101 // Наличие зеленого ключа
5102 utils.writeBool(st, (R_KEY_GREEN in FRulez));
5103 // Наличие синего ключа
5104 utils.writeBool(st, (R_KEY_BLUE in FRulez));
5105 // Наличие берсерка
5106 utils.writeBool(st, (R_BERSERK in FRulez));
5107 // Время действия специальных предметов
5108 for i := MR_SUIT to MR_MAX do utils.writeInt(st, LongWord(FMegaRulez[i]));
5109 // Время до повторного респауна, смены оружия, исользования, захвата флага
5110 for i := T_RESPAWN to T_FLAGCAP do utils.writeInt(st, LongWord(FTime[i]));
5111 // Название модели
5112 utils.writeStr(st, FModel.GetName());
5113 // Цвет модели
5114 utils.writeInt(st, Byte(FColor.R));
5115 utils.writeInt(st, Byte(FColor.G));
5116 utils.writeInt(st, Byte(FColor.B));
5117 end;
5120 procedure TPlayer.LoadState (st: TStream);
5121 var
5122 i: Integer;
5123 str: String;
5124 b: Byte;
5125 begin
5126 assert(st <> nil);
5128 // Сигнатура игрока
5129 if not utils.checkSign(st, 'PLYR') then raise XStreamError.Create('invalid player signature');
5130 if (utils.readByte(st) <> PLR_SAVE_VERSION) then raise XStreamError.Create('invalid player version');
5131 // Бот или человек:
5132 FIamBot := utils.readBool(st);
5133 // UID игрока
5134 FUID := utils.readWord(st);
5135 // Имя игрока
5136 str := utils.readStr(st);
5137 if (self <> gPlayer1) and (self <> gPlayer2) then FName := str;
5138 // Команда
5139 FTeam := utils.readByte(st);
5140 // Жив ли
5141 FAlive := utils.readBool(st);
5142 // Израсходовал ли все жизни
5143 FNoRespawn := utils.readBool(st);
5144 // Направление
5145 b := utils.readByte(st);
5146 if b = 1 then FDirection := TDirection.D_LEFT else FDirection := TDirection.D_RIGHT; // b = 2
5147 // Здоровье
5148 FHealth := utils.readLongInt(st);
5149 // Коэффициент инвалидности
5150 FHandicap := utils.readLongInt(st);
5151 // Жизни
5152 FLives := utils.readByte(st);
5153 // Броня
5154 FArmor := utils.readLongInt(st);
5155 // Запас воздуха
5156 FAir := utils.readLongInt(st);
5157 // Запас горючего
5158 FJetFuel := utils.readLongInt(st);
5159 // Боль
5160 FPain := utils.readLongInt(st);
5161 // Убил
5162 FKills := utils.readLongInt(st);
5163 // Убил монстров
5164 FMonsterKills := utils.readLongInt(st);
5165 // Фрагов
5166 FFrags := utils.readLongInt(st);
5167 // Фрагов подряд
5168 FFragCombo := utils.readByte(st);
5169 // Время последнего фрага
5170 FLastFrag := utils.readLongWord(st);
5171 // Смертей
5172 FDeath := utils.readLongInt(st);
5173 // Какой флаг несет
5174 FFlag := utils.readByte(st);
5175 // Нашел секретов
5176 FSecrets := utils.readLongInt(st);
5177 // Текущее оружие
5178 FCurrWeap := utils.readByte(st);
5179 // Желаемое оружие
5180 FNextWeap := utils.readWord(st);
5181 // ...и пауза
5182 FNextWeapDelay := utils.readByte(st);
5183 // Время зарядки BFG
5184 FBFGFireCounter := utils.readSmallInt(st);
5185 // Буфер урона
5186 FDamageBuffer := utils.readLongInt(st);
5187 // Последний ударивший
5188 FLastSpawnerUID := utils.readWord(st);
5189 // Тип последнего полученного урона
5190 FLastHit := utils.readByte(st);
5191 // Объект игрока
5192 Obj_LoadState(@FObj, st);
5193 // Текущее количество патронов
5194 for i := A_BULLETS to A_HIGH do FAmmo[i] := utils.readWord(st);
5195 // Максимальное количество патронов
5196 for i := A_BULLETS to A_HIGH do FMaxAmmo[i] := utils.readWord(st);
5197 // Наличие оружия
5198 for i := WP_FIRST to WP_LAST do FWeapon[i] := utils.readBool(st);
5199 // Время перезарядки оружия
5200 for i := WP_FIRST to WP_LAST do FReloading[i] := utils.readWord(st);
5201 // Наличие рюкзака
5202 if utils.readBool(st) then Include(FRulez, R_ITEM_BACKPACK);
5203 // Наличие красного ключа
5204 if utils.readBool(st) then Include(FRulez, R_KEY_RED);
5205 // Наличие зеленого ключа
5206 if utils.readBool(st) then Include(FRulez, R_KEY_GREEN);
5207 // Наличие синего ключа
5208 if utils.readBool(st) then Include(FRulez, R_KEY_BLUE);
5209 // Наличие берсерка
5210 if utils.readBool(st) then Include(FRulez, R_BERSERK);
5211 // Время действия специальных предметов
5212 for i := MR_SUIT to MR_MAX do FMegaRulez[i] := utils.readLongWord(st);
5213 // Время до повторного респауна, смены оружия, исользования, захвата флага
5214 for i := T_RESPAWN to T_FLAGCAP do FTime[i] := utils.readLongWord(st);
5215 // Название модели
5216 str := utils.readStr(st);
5217 // Цвет модели
5218 FColor.R := utils.readByte(st);
5219 FColor.G := utils.readByte(st);
5220 FColor.B := utils.readByte(st);
5221 if (self = gPlayer1) then
5222 begin
5223 str := gPlayer1Settings.Model;
5224 FColor := gPlayer1Settings.Color;
5225 end
5226 else if (self = gPlayer2) then
5227 begin
5228 str := gPlayer2Settings.Model;
5229 FColor := gPlayer2Settings.Color;
5230 end;
5231 // Обновляем модель игрока
5232 SetModel(str);
5233 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
5234 FModel.Color := TEAMCOLOR[FTeam]
5235 else
5236 FModel.Color := FColor;
5237 end;
5240 procedure TPlayer.AllRulez(Health: Boolean);
5241 var
5242 a: Integer;
5243 begin
5244 if Health then
5245 begin
5246 FHealth := PLAYER_HP_LIMIT;
5247 FArmor := PLAYER_AP_LIMIT;
5248 Exit;
5249 end;
5251 for a := WP_FIRST to WP_LAST do FWeapon[a] := True;
5252 for a := A_BULLETS to A_HIGH do FAmmo[a] := 30000;
5253 FRulez := FRulez+[R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE];
5254 end;
5256 procedure TPlayer.RestoreHealthArmor();
5257 begin
5258 FHealth := PLAYER_HP_LIMIT;
5259 FArmor := PLAYER_AP_LIMIT;
5260 end;
5262 procedure TPlayer.FragCombo();
5263 var
5264 Param: Integer;
5265 begin
5266 if (gGameSettings.GameMode in [GM_COOP, GM_SINGLE]) or g_Game_IsClient then
5267 Exit;
5268 if gTime - FLastFrag < FRAG_COMBO_TIME then
5269 begin
5270 if FFragCombo < 5 then
5271 Inc(FFragCombo);
5272 Param := FUID or (FFragCombo shl 16);
5273 if (FComboEvnt >= Low(gDelayedEvents)) and
5274 (FComboEvnt <= High(gDelayedEvents)) and
5275 gDelayedEvents[FComboEvnt].Pending and
5276 (gDelayedEvents[FComboEvnt].DEType = DE_KILLCOMBO) and
5277 (gDelayedEvents[FComboEvnt].DENum and $FFFF = FUID) then
5278 begin
5279 gDelayedEvents[FComboEvnt].Time := gTime + 500;
5280 gDelayedEvents[FComboEvnt].DENum := Param;
5281 end
5282 else
5283 FComboEvnt := g_Game_DelayEvent(DE_KILLCOMBO, 500, Param);
5284 end
5285 else
5286 FFragCombo := 1;
5288 FLastFrag := gTime;
5289 end;
5291 procedure TPlayer.GiveItem(ItemType: Byte);
5292 begin
5293 case ItemType of
5294 ITEM_SUIT:
5295 if FMegaRulez[MR_SUIT] < gTime+PLAYER_SUIT_TIME then
5296 begin
5297 FMegaRulez[MR_SUIT] := gTime+PLAYER_SUIT_TIME;
5298 end;
5300 ITEM_OXYGEN:
5301 if FAir < AIR_MAX then
5302 begin
5303 FAir := AIR_MAX;
5304 end;
5306 ITEM_MEDKIT_BLACK:
5307 begin
5308 if not (R_BERSERK in FRulez) then
5309 begin
5310 Include(FRulez, R_BERSERK);
5311 if FBFGFireCounter < 1 then
5312 begin
5313 FCurrWeap := WEAPON_KASTET;
5314 resetWeaponQueue();
5315 FModel.SetWeapon(WEAPON_KASTET);
5316 end;
5317 if gFlash <> 0 then
5318 Inc(FPain, 100);
5319 FBerserk := gTime+30000;
5320 end;
5321 if FHealth < PLAYER_HP_SOFT then
5322 begin
5323 FHealth := PLAYER_HP_SOFT;
5324 FBerserk := gTime+30000;
5325 end;
5326 end;
5328 ITEM_INVUL:
5329 if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
5330 begin
5331 FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
5332 FSpawnInvul := 0;
5333 end;
5335 ITEM_INVIS:
5336 if FMegaRulez[MR_INVIS] < gTime+PLAYER_INVIS_TIME then
5337 begin
5338 FMegaRulez[MR_INVIS] := gTime+PLAYER_INVIS_TIME;
5339 end;
5341 ITEM_JETPACK:
5342 if FJetFuel < JET_MAX then
5343 begin
5344 FJetFuel := JET_MAX;
5345 end;
5347 ITEM_MEDKIT_SMALL: if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 10, PLAYER_HP_SOFT);
5348 ITEM_MEDKIT_LARGE: if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 25, PLAYER_HP_SOFT);
5350 ITEM_ARMOR_GREEN: if FArmor < PLAYER_AP_SOFT then FArmor := PLAYER_AP_SOFT;
5351 ITEM_ARMOR_BLUE: if FArmor < PLAYER_AP_LIMIT then FArmor := PLAYER_AP_LIMIT;
5353 ITEM_SPHERE_BLUE: if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 100, PLAYER_HP_LIMIT);
5354 ITEM_SPHERE_WHITE:
5355 if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) then
5356 begin
5357 if FHealth < PLAYER_HP_LIMIT then FHealth := PLAYER_HP_LIMIT;
5358 if FArmor < PLAYER_AP_LIMIT then FArmor := PLAYER_AP_LIMIT;
5359 end;
5361 ITEM_WEAPON_SAW: FWeapon[WEAPON_SAW] := True;
5362 ITEM_WEAPON_SHOTGUN1: FWeapon[WEAPON_SHOTGUN1] := True;
5363 ITEM_WEAPON_SHOTGUN2: FWeapon[WEAPON_SHOTGUN2] := True;
5364 ITEM_WEAPON_CHAINGUN: FWeapon[WEAPON_CHAINGUN] := True;
5365 ITEM_WEAPON_ROCKETLAUNCHER: FWeapon[WEAPON_ROCKETLAUNCHER] := True;
5366 ITEM_WEAPON_PLASMA: FWeapon[WEAPON_PLASMA] := True;
5367 ITEM_WEAPON_BFG: FWeapon[WEAPON_BFG] := True;
5368 ITEM_WEAPON_SUPERPULEMET: FWeapon[WEAPON_SUPERPULEMET] := True;
5369 ITEM_WEAPON_FLAMETHROWER: FWeapon[WEAPON_FLAMETHROWER] := True;
5371 ITEM_AMMO_BULLETS: if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
5372 ITEM_AMMO_BULLETS_BOX: if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
5373 ITEM_AMMO_SHELLS: if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
5374 ITEM_AMMO_SHELLS_BOX: if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 25, FMaxAmmo[A_SHELLS]);
5375 ITEM_AMMO_ROCKET: if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
5376 ITEM_AMMO_ROCKET_BOX: if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 5, FMaxAmmo[A_ROCKETS]);
5377 ITEM_AMMO_CELL: if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
5378 ITEM_AMMO_CELL_BIG: if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 100, FMaxAmmo[A_CELLS]);
5379 ITEM_AMMO_FUELCAN: if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
5381 ITEM_AMMO_BACKPACK:
5382 if (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or
5383 (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or
5384 (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or
5385 (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or
5386 (FMaxAmmo[A_FUEL] < AmmoLimits[1, A_FUEL]) then
5387 begin
5388 FMaxAmmo[A_BULLETS] := AmmoLimits[1, A_BULLETS];
5389 FMaxAmmo[A_SHELLS] := AmmoLimits[1, A_SHELLS];
5390 FMaxAmmo[A_ROCKETS] := AmmoLimits[1, A_ROCKETS];
5391 FMaxAmmo[A_CELLS] := AmmoLimits[1, A_CELLS];
5392 FMaxAmmo[A_FUEL] := AmmoLimits[1, A_FUEL];
5394 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
5395 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
5396 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
5397 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
5399 FRulez := FRulez + [R_ITEM_BACKPACK];
5400 end;
5402 ITEM_KEY_RED: if not (R_KEY_RED in FRulez) then Include(FRulez, R_KEY_RED);
5403 ITEM_KEY_GREEN: if not (R_KEY_GREEN in FRulez) then Include(FRulez, R_KEY_GREEN);
5404 ITEM_KEY_BLUE: if not (R_KEY_BLUE in FRulez) then Include(FRulez, R_KEY_BLUE);
5406 ITEM_BOTTLE: if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 4, PLAYER_HP_LIMIT);
5407 ITEM_HELMET: if FArmor < PLAYER_AP_LIMIT then IncMax(FArmor, 5, PLAYER_AP_LIMIT);
5409 else
5410 Exit;
5411 end;
5412 if g_Game_IsNet and g_Game_IsServer then
5413 MH_SEND_PlayerStats(FUID);
5414 end;
5416 procedure TPlayer.FlySmoke(Times: DWORD = 1);
5417 var i: DWORD;
5418 begin
5419 if (Random(5) = 1) and (Times = 1) then
5420 Exit;
5422 if BodyInLiquid(0, 0) then
5423 begin
5424 {$IFDEF ENABLE_GFX}
5425 g_GFX_Bubbles(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)+Random(3)-1,
5426 Obj.Y+Obj.Rect.Height+8, 1, 8, 4);
5427 {$ENDIF}
5428 if Random(2) = 0 then
5429 g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
5430 else
5431 g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
5432 Exit;
5433 end;
5435 for i := 1 to Times do
5436 begin
5437 {$IFDEF ENABLE_GFX}
5438 g_GFX_QueueEffect(
5439 R_GFX_SMOKE_TRANS,
5440 Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(R_GFX_SMOKE_WIDTH div 2),
5441 Obj.Y+Obj.Rect.Height-4+Random(8+Times*2)
5442 );
5443 {$ENDIF}
5444 end;
5445 end;
5447 procedure TPlayer.OnFireFlame(Times: DWORD = 1);
5448 var i: DWORD;
5449 begin
5450 if (Random(10) = 1) and (Times = 1) then
5451 Exit;
5453 for i := 1 to Times do
5454 begin
5455 {$IFDEF ENABLE_GFX}
5456 g_GFX_QueueEffect(
5457 R_GFX_FLAME,
5458 Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(R_GFX_FLAME_WIDTH div 2),
5459 Obj.Y+8+Random(8+Times*2)
5460 );
5461 {$ENDIF}
5462 end;
5463 end;
5465 procedure TPlayer.PauseSounds(Enable: Boolean);
5466 begin
5467 FSawSound.Pause(Enable);
5468 FSawSoundIdle.Pause(Enable);
5469 FSawSoundHit.Pause(Enable);
5470 FSawSoundSelect.Pause(Enable);
5471 FFlameSoundOn.Pause(Enable);
5472 FFlameSoundOff.Pause(Enable);
5473 FFlameSoundWork.Pause(Enable);
5474 FJetSoundFly.Pause(Enable);
5475 FJetSoundOn.Pause(Enable);
5476 FJetSoundOff.Pause(Enable);
5477 end;
5479 { T C o r p s e : }
5481 constructor TCorpse.Create(X, Y: Integer; ModelName: String; aMess: Boolean);
5482 begin
5483 g_Obj_Init(@FObj);
5484 FObj.X := X;
5485 FObj.Y := Y;
5486 FObj.Rect := PLAYER_CORPSERECT;
5487 FMess := aMess;
5488 FModel := g_PlayerModel_Get(ModelName);
5490 if FMess then
5491 begin
5492 FState := CORPSE_STATE_MESS;
5493 FModel.ChangeAnimation(A_DIE2);
5494 end
5495 else
5496 begin
5497 FState := CORPSE_STATE_NORMAL;
5498 FModel.ChangeAnimation(A_DIE1);
5499 end;
5500 end;
5502 destructor TCorpse.Destroy();
5503 begin
5504 FModel.Free;
5505 inherited;
5506 end;
5508 function TCorpse.ObjPtr (): PObj; inline; begin result := @FObj; end;
5510 procedure TCorpse.positionChanged (); inline; begin end;
5512 procedure TCorpse.moveBy (dx, dy: Integer); inline;
5513 begin
5514 if (dx <> 0) or (dy <> 0) then
5515 begin
5516 FObj.X += dx;
5517 FObj.Y += dy;
5518 positionChanged();
5519 end;
5520 end;
5523 procedure TCorpse.getMapBox (out x, y, w, h: Integer); inline;
5524 begin
5525 x := FObj.X+PLAYER_CORPSERECT.X;
5526 y := FObj.Y+PLAYER_CORPSERECT.Y;
5527 w := PLAYER_CORPSERECT.Width;
5528 h := PLAYER_CORPSERECT.Height;
5529 end;
5532 procedure TCorpse.Damage(Value: Word; SpawnerUID: Word; vx, vy: Integer);
5533 {$IFDEF ENABLE_GFX}
5534 var Blood: TModelBlood;
5535 {$ENDIF}
5536 begin
5537 if FState = CORPSE_STATE_REMOVEME then
5538 Exit;
5540 FDamage := FDamage + Value;
5542 {$IFDEF ENABLE_GIBS}
5543 if FDamage > 150 then
5544 begin
5545 if FModel <> nil then
5546 begin
5547 FState := CORPSE_STATE_REMOVEME;
5549 g_Gibs_Create(
5550 FObj.X + FObj.Rect.X + (FObj.Rect.Width div 2),
5551 FObj.Y + FObj.Rect.Y + (FObj.Rect.Height div 2),
5552 FModel.id,
5553 FModel.Color
5554 );
5556 // Звук мяса от трупа:
5557 FModel.PlaySound(MODELSOUND_DIE, 5, FObj.X, FObj.Y);
5559 // Зловещий смех:
5560 if (gBodyKillEvent <> -1) and gDelayedEvents[gBodyKillEvent].Pending then
5561 gDelayedEvents[gBodyKillEvent].Pending := False;
5562 gBodyKillEvent := g_Game_DelayEvent(DE_BODYKILL, 1050, SpawnerUID);
5564 FModel.Free;
5565 FModel := nil;
5566 end
5567 end
5568 else
5569 {$ENDIF}
5570 begin
5571 FObj.Vel.X := FObj.Vel.X + vx;
5572 FObj.Vel.Y := FObj.Vel.Y + vy;
5573 {$IFDEF ENABLE_GFX}
5574 Blood := FModel.GetBlood();
5575 g_GFX_Blood(FObj.X+PLAYER_CORPSERECT.X+(PLAYER_CORPSERECT.Width div 2),
5576 FObj.Y+PLAYER_CORPSERECT.Y+(PLAYER_CORPSERECT.Height div 2),
5577 Value, vx, vy, 16, (PLAYER_CORPSERECT.Height*2) div 3,
5578 Blood.R, Blood.G, Blood.B, Blood.Kind);
5579 {$ENDIF}
5580 end;
5581 end;
5583 procedure TCorpse.Update();
5584 var
5585 st: Word;
5586 begin
5587 if FState = CORPSE_STATE_REMOVEME then
5588 Exit;
5590 FObj.oldX := FObj.X;
5591 FObj.oldY := FObj.Y;
5593 if gTime mod (GAME_TICK*2) <> 0 then
5594 begin
5595 g_Obj_Move(@FObj, True, True, True);
5596 positionChanged(); // this updates spatial accelerators
5597 Exit;
5598 end;
5600 // Сопротивление воздуха для трупа:
5601 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
5603 st := g_Obj_Move(@FObj, True, True, True);
5604 positionChanged(); // this updates spatial accelerators
5606 if WordBool(st and MOVE_FALLOUT) then
5607 begin
5608 FState := CORPSE_STATE_REMOVEME;
5609 Exit;
5610 end;
5612 if FModel <> nil then
5613 FModel.Update;
5614 end;
5617 procedure TCorpse.SaveState (st: TStream);
5618 var anim: Boolean;
5619 begin
5620 assert(st <> nil);
5622 // Сигнатура трупа
5623 utils.writeSign(st, 'CORP');
5624 utils.writeInt(st, Byte(0));
5625 // Состояние
5626 utils.writeInt(st, Byte(FState));
5627 // Накопленный урон
5628 utils.writeInt(st, Byte(FDamage));
5629 // Цвет
5630 utils.writeInt(st, Byte(FModel.Color.R));
5631 utils.writeInt(st, Byte(FModel.Color.G));
5632 utils.writeInt(st, Byte(FModel.Color.B));
5633 // Объект трупа
5634 Obj_SaveState(st, @FObj);
5635 utils.writeInt(st, Word(FPlayerUID));
5636 // animation
5637 anim := (FModel <> nil);
5638 utils.writeBool(st, anim);
5639 if anim then FModel.AnimState.SaveState(st, 0, False);
5640 // animation for mask (same as animation, compat with older saves)
5641 anim := (FModel <> nil);
5642 utils.writeBool(st, anim);
5643 if anim then FModel.AnimState.SaveState(st, 0, False);
5644 end;
5647 procedure TCorpse.LoadState (st: TStream);
5648 var anim, blending: Boolean; r, g, b, alpha: Byte; stub: TAnimationState;
5649 begin
5650 assert(st <> nil);
5652 // Сигнатура трупа
5653 if not utils.checkSign(st, 'CORP') then raise XStreamError.Create('invalid corpse signature');
5654 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid corpse version');
5655 // Состояние
5656 FState := utils.readByte(st);
5657 // Накопленный урон
5658 FDamage := utils.readByte(st);
5659 // Цвет
5660 r := utils.readByte(st);
5661 g := utils.readByte(st);
5662 b := utils.readByte(st);
5663 FModel.SetColor(r, g, b);
5664 // Объект трупа
5665 Obj_LoadState(@FObj, st);
5666 FPlayerUID := utils.readWord(st);
5667 // animation
5668 stub := TAnimationState.Create(False, 0, 0);
5669 anim := utils.readBool(st);
5670 if anim then
5671 begin
5672 stub.LoadState(st, alpha, blending);
5673 FModel.AnimState.CurrentFrame := Min(stub.CurrentFrame, FModel.AnimState.Length);
5674 end
5675 else
5676 begin
5677 FModel.Free;
5678 FModel := nil
5679 end;
5680 // animation for mask (same as animation, compat with older saves)
5681 anim := utils.readBool(st);
5682 if anim then stub.LoadState(st, alpha, blending);
5683 stub.Free;
5684 end;
5686 { T B o t : }
5688 constructor TBot.Create();
5689 var
5690 a: Integer;
5691 begin
5692 inherited Create();
5694 FPhysics := True;
5695 FSpectator := False;
5696 FGhost := False;
5698 FIamBot := True;
5700 Inc(gNumBots);
5702 for a := WP_FIRST to WP_LAST do
5703 begin
5704 FDifficult.WeaponPrior[a] := WEAPON_PRIOR1[a];
5705 FDifficult.CloseWeaponPrior[a] := WEAPON_PRIOR2[a];
5706 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
5707 end;
5708 end;
5710 destructor TBot.Destroy();
5711 begin
5712 Dec(gNumBots);
5713 inherited Destroy();
5714 end;
5716 procedure TBot.Respawn(Silent: Boolean; Force: Boolean = False);
5717 begin
5718 inherited Respawn(Silent, Force);
5720 FAIFlags := nil;
5721 FSelectedWeapon := FCurrWeap;
5722 resetWeaponQueue();
5723 FTargetUID := 0;
5724 end;
5726 procedure TBot.UpdateCombat();
5727 type
5728 TTarget = record
5729 UID: Word;
5730 X, Y: Integer;
5731 Rect: TRectWH;
5732 cX, cY: Integer;
5733 Dist: Word;
5734 Line: Boolean;
5735 Visible: Boolean;
5736 IsPlayer: Boolean;
5737 end;
5739 TTargetRecord = array of TTarget;
5741 function Compare(a, b: TTarget): Integer;
5742 begin
5743 if a.Line and not b.Line then // A на линии огня
5744 Result := -1
5745 else
5746 if not a.Line and b.Line then // B на линии огня
5747 Result := 1
5748 else // И A, и B на линии или не на линии огня
5749 if (a.Line and b.Line) or ((not a.Line) and (not b.Line)) then
5750 begin
5751 if a.Dist > b.Dist then // B ближе
5752 Result := 1
5753 else // A ближе или равноудаленно с B
5754 Result := -1;
5755 end
5756 else // Странно -> A
5757 Result := -1;
5758 end;
5760 var
5761 a, x1, y1, x2, y2: Integer;
5762 targets: TTargetRecord;
5763 ammo: Word;
5764 Target, BestTarget: TTarget;
5765 firew, fireh: Integer;
5766 angle: SmallInt;
5767 mon: TMonster;
5768 pla, tpla: TPlayer;
5769 vsPlayer, vsMonster, ok: Boolean;
5772 function monsUpdate (mon: TMonster): Boolean;
5773 begin
5774 result := false; // don't stop
5775 if mon.alive and (mon.MonsterType <> MONSTER_BARREL) then
5776 begin
5777 if not TargetOnScreen(mon.Obj.X+mon.Obj.Rect.X, mon.Obj.Y+mon.Obj.Rect.Y) then exit;
5779 x2 := mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2);
5780 y2 := mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2);
5782 // Если монстр на экране и не прикрыт стеной
5783 if g_TraceVector(x1, y1, x2, y2) then
5784 begin
5785 // Добавляем к списку возможных целей
5786 SetLength(targets, Length(targets)+1);
5787 with targets[High(targets)] do
5788 begin
5789 UID := mon.UID;
5790 X := mon.Obj.X;
5791 Y := mon.Obj.Y;
5792 cX := x2;
5793 cY := y2;
5794 Rect := mon.Obj.Rect;
5795 Dist := g_PatchLength(x1, y1, x2, y2);
5796 Line := (y1+4 < Target.Y + mon.Obj.Rect.Y + mon.Obj.Rect.Height) and
5797 (y1-4 > Target.Y + mon.Obj.Rect.Y);
5798 Visible := True;
5799 IsPlayer := False;
5800 end;
5801 end;
5802 end;
5803 end;
5805 begin
5806 vsPlayer := LongBool(gGameSettings.Options and GAME_OPTION_BOTVSPLAYER);
5807 vsMonster := LongBool(gGameSettings.Options and GAME_OPTION_BOTVSMONSTER);
5809 // Если текущее оружие не то, что нужно, то меняем:
5810 if FCurrWeap <> FSelectedWeapon then
5811 NextWeapon();
5813 // Если нужно стрелять и нужное оружие, то нажать "Стрелять":
5814 if (GetAIFlag('NEEDFIRE') <> '') and (FCurrWeap = FSelectedWeapon) then
5815 begin
5816 RemoveAIFlag('NEEDFIRE');
5818 case FCurrWeap of
5819 WEAPON_PLASMA, WEAPON_SUPERPULEMET, WEAPON_CHAINGUN: PressKey(KEY_FIRE, 20);
5820 WEAPON_SAW, WEAPON_KASTET, WEAPON_FLAMETHROWER: PressKey(KEY_FIRE, 40);
5821 else PressKey(KEY_FIRE);
5822 end;
5823 end;
5825 // Координаты ствола:
5826 x1 := FObj.X + WEAPONPOINT[FDirection].X;
5827 y1 := FObj.Y + WEAPONPOINT[FDirection].Y;
5829 Target.UID := FTargetUID;
5831 ok := False;
5832 if Target.UID <> 0 then
5833 begin // Цель есть - настраиваем
5834 if (g_GetUIDType(Target.UID) = UID_PLAYER) and
5835 vsPlayer then
5836 begin // Игрок
5837 tpla := g_Player_Get(Target.UID);
5838 if tpla <> nil then
5839 with tpla do
5840 begin
5841 if (@FObj) <> nil then
5842 begin
5843 Target.X := FObj.X;
5844 Target.Y := FObj.Y;
5845 end;
5846 end;
5848 Target.cX := Target.X + PLAYER_RECT_CX;
5849 Target.cY := Target.Y + PLAYER_RECT_CY;
5850 Target.Rect := PLAYER_RECT;
5851 Target.Visible := g_TraceVector(x1, y1, Target.cX, Target.cY);
5852 Target.Line := (y1+4 < Target.Y+PLAYER_RECT.Y+PLAYER_RECT.Height) and
5853 (y1-4 > Target.Y+PLAYER_RECT.Y);
5854 Target.IsPlayer := True;
5855 ok := True;
5856 end
5857 else
5858 if (g_GetUIDType(Target.UID) = UID_MONSTER) and
5859 vsMonster then
5860 begin // Монстр
5861 mon := g_Monsters_ByUID(Target.UID);
5862 if mon <> nil then
5863 begin
5864 Target.X := mon.Obj.X;
5865 Target.Y := mon.Obj.Y;
5867 Target.cX := Target.X + mon.Obj.Rect.X + (mon.Obj.Rect.Width div 2);
5868 Target.cY := Target.Y + mon.Obj.Rect.Y + (mon.Obj.Rect.Height div 2);
5869 Target.Rect := mon.Obj.Rect;
5870 Target.Visible := g_TraceVector(x1, y1, Target.cX, Target.cY);
5871 Target.Line := (y1+4 < Target.Y + mon.Obj.Rect.Y + mon.Obj.Rect.Height) and
5872 (y1-4 > Target.Y + mon.Obj.Rect.Y);
5873 Target.IsPlayer := False;
5874 ok := True;
5875 end;
5876 end;
5877 end;
5879 if not ok then
5880 begin // Цели нет - обнуляем
5881 Target.X := 0;
5882 Target.Y := 0;
5883 Target.cX := 0;
5884 Target.cY := 0;
5885 Target.Visible := False;
5886 Target.Line := False;
5887 Target.IsPlayer := False;
5888 end;
5890 targets := nil;
5892 // Если цель не видима или не на линии огня, то ищем все возможные цели:
5893 if (not Target.Line) or (not Target.Visible) then
5894 begin
5895 // Игроки:
5896 if vsPlayer then
5897 for a := 0 to High(gPlayers) do
5898 if (gPlayers[a] <> nil) and (gPlayers[a].alive) and
5899 (gPlayers[a].FUID <> FUID) and
5900 (not SameTeam(FUID, gPlayers[a].FUID)) and
5901 (not gPlayers[a].NoTarget) and
5902 (gPlayers[a].FMegaRulez[MR_INVIS] < gTime) then
5903 begin
5904 if not TargetOnScreen(gPlayers[a].FObj.X + PLAYER_RECT.X,
5905 gPlayers[a].FObj.Y + PLAYER_RECT.Y) then
5906 Continue;
5908 x2 := gPlayers[a].FObj.X + PLAYER_RECT_CX;
5909 y2 := gPlayers[a].FObj.Y + PLAYER_RECT_CY;
5911 // Если игрок на экране и не прикрыт стеной:
5912 if g_TraceVector(x1, y1, x2, y2) then
5913 begin
5914 // Добавляем к списку возможных целей:
5915 SetLength(targets, Length(targets)+1);
5916 with targets[High(targets)] do
5917 begin
5918 UID := gPlayers[a].FUID;
5919 X := gPlayers[a].FObj.X;
5920 Y := gPlayers[a].FObj.Y;
5921 cX := x2;
5922 cY := y2;
5923 Rect := PLAYER_RECT;
5924 Dist := g_PatchLength(x1, y1, x2, y2);
5925 Line := (y1+4 < Target.Y+PLAYER_RECT.Y+PLAYER_RECT.Height) and
5926 (y1-4 > Target.Y+PLAYER_RECT.Y);
5927 Visible := True;
5928 IsPlayer := True;
5929 end;
5930 end;
5931 end;
5933 // Монстры:
5934 if vsMonster then g_Mons_ForEach(monsUpdate);
5935 end;
5937 // Если есть возможные цели:
5938 // (Выбираем лучшую, меняем оружие и бежим к ней/от нее)
5939 if targets <> nil then
5940 begin
5941 // Выбираем наилучшую цель:
5942 BestTarget := targets[0];
5943 if Length(targets) > 1 then
5944 for a := 1 to High(targets) do
5945 if Compare(BestTarget, targets[a]) = 1 then
5946 BestTarget := targets[a];
5948 // Если лучшая цель "виднее" текущей, то текущая := лучшая:
5949 if ((not Target.Visible) and BestTarget.Visible and (Target.UID <> BestTarget.UID)) or
5950 ((not Target.Line) and BestTarget.Line and BestTarget.Visible) then
5951 begin
5952 Target := BestTarget;
5954 if (Healthy() = 3) or ((Healthy() = 2)) then
5955 begin // Если здоровы - догоняем
5956 if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
5957 SetAIFlag('GORIGHT', '1');
5958 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
5959 SetAIFlag('GOLEFT', '1');
5960 end
5961 else
5962 begin // Если побиты - убегаем
5963 if ((RunDirection() = TDirection.D_LEFT) and (Target.X < FObj.X)) then
5964 SetAIFlag('GORIGHT', '1');
5965 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
5966 SetAIFlag('GOLEFT', '1');
5967 end;
5969 // Выбираем оружие на основе расстояния и приоритетов:
5970 SelectWeapon(Abs(x1-Target.cX));
5971 end;
5972 end;
5974 // Если есть цель:
5975 // (Догоняем/убегаем, стреляем по направлению к цели)
5976 // (Если цель далеко, то хватит следить за ней)
5977 if Target.UID <> 0 then
5978 begin
5979 if not TargetOnScreen(Target.X + Target.Rect.X,
5980 Target.Y + Target.Rect.Y) then
5981 begin // Цель сбежала с "экрана"
5982 if (Healthy() = 3) or ((Healthy() = 2)) then
5983 begin // Если здоровы - догоняем
5984 if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
5985 SetAIFlag('GORIGHT', '1');
5986 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
5987 SetAIFlag('GOLEFT', '1');
5988 end
5989 else
5990 begin // Если побиты - забываем о цели и убегаем
5991 Target.UID := 0;
5992 if ((RunDirection() = TDirection.D_LEFT) and (Target.X < FObj.X)) then
5993 SetAIFlag('GORIGHT', '1');
5994 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
5995 SetAIFlag('GOLEFT', '1');
5996 end;
5997 end
5998 else
5999 begin // Цель пока на "экране"
6000 // Если цель не загорожена стеной, то отмечаем, когда ее видели:
6001 if g_TraceVector(x1, y1, Target.cX, Target.cY) then
6002 FLastVisible := gTime;
6003 // Если разница высот не велика, то догоняем:
6004 if (Abs(FObj.Y-Target.Y) <= 128) then
6005 begin
6006 if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
6007 SetAIFlag('GORIGHT', '1');
6008 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
6009 SetAIFlag('GOLEFT', '1');
6010 end;
6011 end;
6013 // Выбираем угол вверх:
6014 if FDirection = TDirection.D_LEFT then
6015 angle := ANGLE_LEFTUP
6016 else
6017 angle := ANGLE_RIGHTUP;
6019 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6020 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6022 // Если при угле вверх можно попасть в приблизительное положение цели:
6023 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
6024 Target.X+Target.Rect.X+GetInterval(FDifficult.DiagPrecision, 128), //96
6025 Target.Y+Target.Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
6026 Target.Rect.Width, Target.Rect.Height) and
6027 g_TraceVector(x1, y1, Target.cX, Target.cY) then
6028 begin // то нужно стрелять вверх
6029 SetAIFlag('NEEDFIRE', '1');
6030 SetAIFlag('NEEDSEEUP', '1');
6031 end;
6033 // Выбираем угол вниз:
6034 if FDirection = TDirection.D_LEFT then
6035 angle := ANGLE_LEFTDOWN
6036 else
6037 angle := ANGLE_RIGHTDOWN;
6039 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6040 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6042 // Если при угле вниз можно попасть в приблизительное положение цели:
6043 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
6044 Target.X+Target.Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
6045 Target.Y+Target.Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
6046 Target.Rect.Width, Target.Rect.Height) and
6047 g_TraceVector(x1, y1, Target.cX, Target.cY) then
6048 begin // то нужно стрелять вниз
6049 SetAIFlag('NEEDFIRE', '1');
6050 SetAIFlag('NEEDSEEDOWN', '1');
6051 end;
6053 // Если цель видно и она на такой же высоте:
6054 if Target.Visible and
6055 (y1+4 < Target.Y+Target.Rect.Y+Target.Rect.Height) and
6056 (y1-4 > Target.Y+Target.Rect.Y) then
6057 begin
6058 // Если идем в сторону цели, то надо стрелять:
6059 if ((FDirection = TDirection.D_LEFT) and (Target.X < FObj.X)) or
6060 ((FDirection = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
6061 begin // то нужно стрелять вперед
6062 SetAIFlag('NEEDFIRE', '1');
6063 SetAIFlag('NEEDSEEDOWN', '');
6064 SetAIFlag('NEEDSEEUP', '');
6065 end;
6066 // Если цель в пределах "экрана" и сложность позволяет прыжки сближения:
6067 if Abs(FObj.X-Target.X) < Trunc(gPlayerScreenSize.X*0.75) then
6068 if GetRnd(FDifficult.CloseJump) then
6069 begin // то если повезет - прыгаем (особенно, если близко)
6070 if Abs(FObj.X-Target.X) < 128 then
6071 a := 4
6072 else
6073 a := 30;
6074 if Random(a) = 0 then
6075 SetAIFlag('NEEDJUMP', '1');
6076 end;
6077 end;
6079 // Если цель все еще есть:
6080 if Target.UID <> 0 then
6081 if gTime-FLastVisible > 2000 then // Если видели давно
6082 Target.UID := 0 // то забыть цель
6083 else // Если видели недавно
6084 begin // но цель убили
6085 if Target.IsPlayer then
6086 begin // Цель - игрок
6087 pla := g_Player_Get(Target.UID);
6088 if (pla = nil) or (not pla.alive) or pla.NoTarget or
6089 (pla.FMegaRulez[MR_INVIS] >= gTime) then
6090 Target.UID := 0; // то забыть цель
6091 end
6092 else
6093 begin // Цель - монстр
6094 mon := g_Monsters_ByUID(Target.UID);
6095 if (mon = nil) or (not mon.alive) then
6096 Target.UID := 0; // то забыть цель
6097 end;
6098 end;
6099 end; // if Target.UID <> 0
6101 FTargetUID := Target.UID;
6103 // Если возможных целей нет:
6104 // (Атака чего-нибудь слева или справа)
6105 if targets = nil then
6106 if GetAIFlag('ATTACKLEFT') <> '' then
6107 begin // Если нужно атаковать налево
6108 RemoveAIFlag('ATTACKLEFT');
6110 SetAIFlag('NEEDJUMP', '1');
6112 if RunDirection() = TDirection.D_RIGHT then
6113 begin // Идем не в ту сторону
6114 if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then
6115 begin // Если здоровы, то, возможно, стреляем бежим влево и стреляем
6116 SetAIFlag('NEEDFIRE', '1');
6117 SetAIFlag('GOLEFT', '1');
6118 end;
6119 end
6120 else
6121 begin // Идем в нужную сторону
6122 if GetRnd(FDifficult.InvisFire) then // Возможно, стреляем вслепую
6123 SetAIFlag('NEEDFIRE', '1');
6124 if Healthy() <= 1 then // Побиты - убегаем
6125 SetAIFlag('GORIGHT', '1');
6126 end;
6127 end
6128 else
6129 if GetAIFlag('ATTACKRIGHT') <> '' then
6130 begin // Если нужно атаковать направо
6131 RemoveAIFlag('ATTACKRIGHT');
6133 SetAIFlag('NEEDJUMP', '1');
6135 if RunDirection() = TDirection.D_LEFT then
6136 begin // Идем не в ту сторону
6137 if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then
6138 begin // Если здоровы, то, возможно, бежим вправо и стреляем
6139 SetAIFlag('NEEDFIRE', '1');
6140 SetAIFlag('GORIGHT', '1');
6141 end;
6142 end
6143 else
6144 begin
6145 if GetRnd(FDifficult.InvisFire) then // Возможно, стреляем вслепую
6146 SetAIFlag('NEEDFIRE', '1');
6147 if Healthy() <= 1 then // Побиты - убегаем
6148 SetAIFlag('GOLEFT', '1');
6149 end;
6150 end;
6152 //HACK! (does it belongs there?)
6153 RealizeCurrentWeapon();
6155 // Если есть возможные цели:
6156 // (Стреляем по направлению к целям)
6157 if (targets <> nil) and (GetAIFlag('NEEDFIRE') <> '') then
6158 for a := 0 to High(targets) do
6159 begin
6160 // Если можем стрелять по диагонали:
6161 if GetRnd(FDifficult.DiagFire) then
6162 begin
6163 // Ищем цель сверху и стреляем, если есть:
6164 if FDirection = TDirection.D_LEFT then
6165 angle := ANGLE_LEFTUP
6166 else
6167 angle := ANGLE_RIGHTUP;
6169 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6170 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6172 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
6173 targets[a].X+targets[a].Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
6174 targets[a].Y+targets[a].Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
6175 targets[a].Rect.Width, targets[a].Rect.Height) and
6176 g_TraceVector(x1, y1, targets[a].cX, targets[a].cY) then
6177 begin
6178 SetAIFlag('NEEDFIRE', '1');
6179 SetAIFlag('NEEDSEEUP', '1');
6180 end;
6182 // Ищем цель снизу и стреляем, если есть:
6183 if FDirection = TDirection.D_LEFT then
6184 angle := ANGLE_LEFTDOWN
6185 else
6186 angle := ANGLE_RIGHTDOWN;
6188 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6189 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6191 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
6192 targets[a].X+targets[a].Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
6193 targets[a].Y+targets[a].Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
6194 targets[a].Rect.Width, targets[a].Rect.Height) and
6195 g_TraceVector(x1, y1, targets[a].cX, targets[a].cY) then
6196 begin
6197 SetAIFlag('NEEDFIRE', '1');
6198 SetAIFlag('NEEDSEEDOWN', '1');
6199 end;
6200 end;
6202 // Если цель "перед носом", то стреляем:
6203 if targets[a].Line and targets[a].Visible and
6204 (((FDirection = TDirection.D_LEFT) and (targets[a].X < FObj.X)) or
6205 ((FDirection = TDirection.D_RIGHT) and (targets[a].X > FObj.X))) then
6206 begin
6207 SetAIFlag('NEEDFIRE', '1');
6208 Break;
6209 end;
6210 end;
6212 // Если летит пуля, то, возможно, подпрыгиваем:
6213 if g_Weapon_Danger(FUID, FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y,
6214 PLAYER_RECT.Width, PLAYER_RECT.Height,
6215 40+GetInterval(FDifficult.Cover, 40)) then
6216 SetAIFlag('NEEDJUMP', '1');
6218 // Если кончились паторны, то нужно сменить оружие:
6219 ammo := GetAmmoByWeapon(FCurrWeap);
6220 if ((FCurrWeap = WEAPON_SHOTGUN2) and (ammo < 2)) or
6221 ((FCurrWeap = WEAPON_BFG) and (ammo < 40)) or
6222 (ammo = 0) then
6223 SetAIFlag('SELECTWEAPON', '1');
6225 // Если нужно сменить оружие, то выбираем нужное:
6226 if GetAIFlag('SELECTWEAPON') = '1' then
6227 begin
6228 SelectWeapon(-1);
6229 RemoveAIFlag('SELECTWEAPON');
6230 end;
6231 end;
6233 procedure TBot.Update();
6234 var
6235 EnableAI: Boolean;
6236 begin
6237 if not FAlive then
6238 begin // Respawn
6239 ReleaseKeys();
6240 PressKey(KEY_UP);
6241 end
6242 else
6243 begin
6244 EnableAI := True;
6246 // Проверяем, отключён ли AI ботов
6247 if (g_debug_BotAIOff = 1) and (Team = TEAM_RED) then
6248 EnableAI := False;
6249 if (g_debug_BotAIOff = 2) and (Team = TEAM_BLUE) then
6250 EnableAI := False;
6251 if g_debug_BotAIOff = 3 then
6252 EnableAI := False;
6254 if EnableAI then
6255 begin
6256 UpdateMove();
6257 UpdateCombat();
6258 end
6259 else
6260 begin
6261 RealizeCurrentWeapon();
6262 end;
6263 end;
6265 inherited Update();
6266 end;
6268 procedure TBot.ReleaseKey(Key: Byte);
6269 begin
6270 with FKeys[Key] do
6271 begin
6272 Pressed := False;
6273 Time := 0;
6274 end;
6275 end;
6277 function TBot.KeyPressed(Key: Word): Boolean;
6278 begin
6279 Result := FKeys[Key].Pressed;
6280 end;
6282 function TBot.GetAIFlag(aName: String20): String20;
6283 var
6284 a: Integer;
6285 begin
6286 Result := '';
6288 aName := LowerCase(aName);
6290 if FAIFlags <> nil then
6291 for a := 0 to High(FAIFlags) do
6292 if LowerCase(FAIFlags[a].Name) = aName then
6293 begin
6294 Result := FAIFlags[a].Value;
6295 Break;
6296 end;
6297 end;
6299 procedure TBot.RemoveAIFlag(aName: String20);
6300 var
6301 a, b: Integer;
6302 begin
6303 if FAIFlags = nil then Exit;
6305 aName := LowerCase(aName);
6307 for a := 0 to High(FAIFlags) do
6308 if LowerCase(FAIFlags[a].Name) = aName then
6309 begin
6310 if a <> High(FAIFlags) then
6311 for b := a to High(FAIFlags)-1 do
6312 FAIFlags[b] := FAIFlags[b+1];
6314 SetLength(FAIFlags, Length(FAIFlags)-1);
6315 Break;
6316 end;
6317 end;
6319 procedure TBot.SetAIFlag(aName, fValue: String20);
6320 var
6321 a: Integer;
6322 ok: Boolean;
6323 begin
6324 a := 0;
6325 ok := False;
6327 aName := LowerCase(aName);
6329 if FAIFlags <> nil then
6330 for a := 0 to High(FAIFlags) do
6331 if LowerCase(FAIFlags[a].Name) = aName then
6332 begin
6333 ok := True;
6334 Break;
6335 end;
6337 if ok then FAIFlags[a].Value := fValue
6338 else
6339 begin
6340 SetLength(FAIFlags, Length(FAIFlags)+1);
6341 with FAIFlags[High(FAIFlags)] do
6342 begin
6343 Name := aName;
6344 Value := fValue;
6345 end;
6346 end;
6347 end;
6349 procedure TBot.UpdateMove;
6351 procedure GoLeft(Time: Word = 1);
6352 begin
6353 ReleaseKey(KEY_LEFT);
6354 ReleaseKey(KEY_RIGHT);
6355 PressKey(KEY_LEFT, Time);
6356 SetDirection(TDirection.D_LEFT);
6357 end;
6359 procedure GoRight(Time: Word = 1);
6360 begin
6361 ReleaseKey(KEY_LEFT);
6362 ReleaseKey(KEY_RIGHT);
6363 PressKey(KEY_RIGHT, Time);
6364 SetDirection(TDirection.D_RIGHT);
6365 end;
6367 function Rnd(a: Word): Boolean;
6368 begin
6369 Result := Random(a) = 0;
6370 end;
6372 procedure Turn(Time: Word = 1200);
6373 begin
6374 if RunDirection() = TDirection.D_LEFT then GoRight(Time) else GoLeft(Time);
6375 end;
6377 procedure Stop();
6378 begin
6379 ReleaseKey(KEY_LEFT);
6380 ReleaseKey(KEY_RIGHT);
6381 end;
6383 function CanRunLeft(): Boolean;
6384 begin
6385 Result := not CollideLevel(-1, 0);
6386 end;
6388 function CanRunRight(): Boolean;
6389 begin
6390 Result := not CollideLevel(1, 0);
6391 end;
6393 function CanRun(): Boolean;
6394 begin
6395 if RunDirection() = TDirection.D_LEFT then Result := CanRunLeft() else Result := CanRunRight();
6396 end;
6398 procedure Jump(Time: Word = 30);
6399 begin
6400 PressKey(KEY_JUMP, Time);
6401 end;
6403 function NearHole(): Boolean;
6404 var
6405 x, sx: Integer;
6406 begin
6407 { TODO 5 : Лестницы }
6408 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
6409 for x := 1 to PLAYER_RECT.Width do
6410 if (not StayOnStep(x*sx, 0)) and
6411 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
6412 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
6413 begin
6414 Result := True;
6415 Exit;
6416 end;
6418 Result := False;
6419 end;
6421 function BorderHole(): Boolean;
6422 var
6423 x, sx, xx: Integer;
6424 begin
6425 { TODO 5 : Лестницы }
6426 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
6427 for x := 1 to PLAYER_RECT.Width do
6428 if (not StayOnStep(x*sx, 0)) and
6429 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
6430 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
6431 begin
6432 for xx := x to x+32 do
6433 if CollideLevel(xx*sx, PLAYER_RECT.Height) then
6434 begin
6435 Result := True;
6436 Exit;
6437 end;
6438 end;
6440 Result := False;
6441 end;
6443 function NearDeepHole(): Boolean;
6444 var
6445 x, sx, y: Integer;
6446 begin
6447 Result := False;
6449 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
6450 y := 3;
6452 for x := 1 to PLAYER_RECT.Width do
6453 if (not StayOnStep(x*sx, 0)) and
6454 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
6455 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
6456 begin
6457 while FObj.Y+y*PLAYER_RECT.Height < gMapInfo.Height do
6458 begin
6459 if CollideLevel(x*sx, PLAYER_RECT.Height*y) then Exit;
6460 y := y+1;
6461 end;
6463 Result := True;
6464 end else Result := False;
6465 end;
6467 function OverDeepHole(): Boolean;
6468 var
6469 y: Integer;
6470 begin
6471 Result := False;
6473 y := 1;
6474 while FObj.Y+y*PLAYER_RECT.Height < gMapInfo.Height do
6475 begin
6476 if CollideLevel(0, PLAYER_RECT.Height*y) then Exit;
6477 y := y+1;
6478 end;
6480 Result := True;
6481 end;
6483 function OnGround(): Boolean;
6484 begin
6485 Result := StayOnStep(0, 0) or CollideLevel(0, 1);
6486 end;
6488 function OnLadder(): Boolean;
6489 begin
6490 Result := FullInStep(0, 0);
6491 end;
6493 function BelowLadder(): Boolean;
6494 begin
6495 Result := (FullInStep(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) and
6496 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or
6497 (FullInStep(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) and
6498 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP));
6499 end;
6501 function BelowLiftUp(): Boolean;
6502 begin
6503 Result := ((FullInLift(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) = -1) and
6504 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or
6505 ((FullInLift(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) = -1) and
6506 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP));
6507 end;
6509 function OnTopLift(): Boolean;
6510 begin
6511 Result := (FullInLift(0, 0) = -1) and (FullInLift(0, -32) = 0);
6512 end;
6514 function CanJumpOver(): Boolean;
6515 var
6516 sx, y: Integer;
6517 begin
6518 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
6520 Result := False;
6522 if not CollideLevel(sx, 0) then Exit;
6524 for y := 1 to BOT_MAXJUMP do
6525 if CollideLevel(0, -y) then Exit else
6526 if not CollideLevel(sx, -y) then
6527 begin
6528 Result := True;
6529 Exit;
6530 end;
6531 end;
6533 function CanJumpUp(Dist: ShortInt): Boolean;
6534 var
6535 y, yy: Integer;
6536 c: Boolean;
6537 begin
6538 Result := False;
6540 if CollideLevel(Dist, 0) then Exit;
6542 c := False;
6543 for y := 0 to BOT_MAXJUMP do
6544 if CollideLevel(Dist, -y) then
6545 begin
6546 c := True;
6547 Break;
6548 end;
6550 if not c then Exit;
6552 c := False;
6553 for yy := y+1 to BOT_MAXJUMP do
6554 if not CollideLevel(Dist, -yy) then
6555 begin
6556 c := True;
6557 Break;
6558 end;
6560 if not c then Exit;
6562 c := False;
6563 for y := 0 to BOT_MAXJUMP do
6564 if CollideLevel(0, -y) then
6565 begin
6566 c := True;
6567 Break;
6568 end;
6570 if c then Exit;
6572 if y < yy then Exit;
6574 Result := True;
6575 end;
6577 function IsSafeTrigger(): Boolean;
6578 var
6579 a: Integer;
6580 begin
6581 Result := True;
6582 if gTriggers = nil then
6583 Exit;
6584 for a := 0 to High(gTriggers) do
6585 if Collide(gTriggers[a].X,
6586 gTriggers[a].Y,
6587 gTriggers[a].Width,
6588 gTriggers[a].Height) and
6589 (gTriggers[a].TriggerType in [TRIGGER_EXIT, TRIGGER_CLOSEDOOR,
6590 TRIGGER_CLOSETRAP, TRIGGER_TRAP,
6591 TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF,
6592 TRIGGER_ONOFF, TRIGGER_SPAWNMONSTER,
6593 TRIGGER_DAMAGE, TRIGGER_SHOT]) then
6594 Result := False;
6595 end;
6597 begin
6598 // Возможно, нажимаем кнопку:
6599 if Rnd(16) and IsSafeTrigger() then
6600 PressKey(KEY_OPEN);
6602 // Если под лифтом или ступеньками, то, возможно, прыгаем:
6603 if OnLadder() or ((BelowLadder() or BelowLiftUp()) and Rnd(8)) then
6604 begin
6605 ReleaseKey(KEY_LEFT);
6606 ReleaseKey(KEY_RIGHT);
6607 Jump();
6608 end;
6610 // Идем влево, если надо было:
6611 if GetAIFlag('GOLEFT') <> '' then
6612 begin
6613 RemoveAIFlag('GOLEFT');
6614 if CanRunLeft() then
6615 GoLeft(360);
6616 end;
6618 // Идем вправо, если надо было:
6619 if GetAIFlag('GORIGHT') <> '' then
6620 begin
6621 RemoveAIFlag('GORIGHT');
6622 if CanRunRight() then
6623 GoRight(360);
6624 end;
6626 // Если вылетели за карту, то пробуем вернуться:
6627 if FObj.X < -32 then
6628 GoRight(360)
6629 else
6630 if FObj.X+32 > gMapInfo.Width then
6631 GoLeft(360);
6633 // Прыгаем, если надо было:
6634 if GetAIFlag('NEEDJUMP') <> '' then
6635 begin
6636 Jump(0);
6637 RemoveAIFlag('NEEDJUMP');
6638 end;
6640 // Смотрим вверх, если надо было:
6641 if GetAIFlag('NEEDSEEUP') <> '' then
6642 begin
6643 ReleaseKey(KEY_UP);
6644 ReleaseKey(KEY_DOWN);
6645 PressKey(KEY_UP, 20);
6646 RemoveAIFlag('NEEDSEEUP');
6647 end;
6649 // Смотрим вниз, если надо было:
6650 if GetAIFlag('NEEDSEEDOWN') <> '' then
6651 begin
6652 ReleaseKey(KEY_UP);
6653 ReleaseKey(KEY_DOWN);
6654 PressKey(KEY_DOWN, 20);
6655 RemoveAIFlag('NEEDSEEDOWN');
6656 end;
6658 // Если нужно было в дыру и мы не на земле, то покорно летим:
6659 if GetAIFlag('GOINHOLE') <> '' then
6660 if not OnGround() then
6661 begin
6662 ReleaseKey(KEY_LEFT);
6663 ReleaseKey(KEY_RIGHT);
6664 RemoveAIFlag('GOINHOLE');
6665 SetAIFlag('FALLINHOLE', '1');
6666 end;
6668 // Если падали и достигли земли, то хватит падать:
6669 if GetAIFlag('FALLINHOLE') <> '' then
6670 if OnGround() then
6671 RemoveAIFlag('FALLINHOLE');
6673 // Если летели прямо и сейчас не на лестнице или на вершине лифта, то отходим в сторону:
6674 if not (KeyPressed(KEY_LEFT) or KeyPressed(KEY_RIGHT)) then
6675 if GetAIFlag('FALLINHOLE') = '' then
6676 if (not OnLadder()) or (FObj.Vel.Y >= 0) or (OnTopLift()) then
6677 if Rnd(2) then
6678 GoLeft(360)
6679 else
6680 GoRight(360);
6682 // Если на земле и можно подпрыгнуть, то, возможно, прыгаем:
6683 if OnGround() and
6684 CanJumpUp(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*32) and
6685 Rnd(8) then
6686 Jump();
6688 // Если на земле и возле дыры (глубина > 2 ростов игрока):
6689 if OnGround() and NearHole() then
6690 if NearDeepHole() then // Если это бездна
6691 case Random(6) of
6692 0..3: Turn(); // Бежим обратно
6693 4: Jump(); // Прыгаем
6694 5: begin // Прыгаем обратно
6695 Turn();
6696 Jump();
6697 end;
6698 end
6699 else // Это не бездна и мы еще не летим туда
6700 if GetAIFlag('GOINHOLE') = '' then
6701 case Random(6) of
6702 0: Turn(); // Не нужно туда
6703 1: Jump(); // Вдруг повезет - прыгаем
6704 else // Если яма с границей, то при случае можно туда прыгнуть
6705 if BorderHole() then
6706 SetAIFlag('GOINHOLE', '1');
6707 end;
6709 // Если на земле, но некуда идти:
6710 if (not CanRun()) and OnGround() then
6711 begin
6712 // Если мы на лестнице или можно перепрыгнуть, то прыгаем:
6713 if CanJumpOver() or OnLadder() then
6714 Jump()
6715 else // иначе попытаемся в другую сторону
6716 if Random(2) = 0 then
6717 begin
6718 if IsSafeTrigger() then
6719 PressKey(KEY_OPEN);
6720 end else
6721 Turn();
6722 end;
6724 // Осталось мало воздуха:
6725 if FAir < 36 * 2 then
6726 Jump(20);
6728 // Выбираемся из кислоты, если нет костюма, обожглись, или мало здоровья:
6729 if (FMegaRulez[MR_SUIT] < gTime) and ((FLastHit = HIT_ACID) or (Healthy() <= 1)) then
6730 if BodyInAcid(0, 0) then
6731 Jump();
6732 end;
6734 function TBot.FullInStep(XInc, YInc: Integer): Boolean;
6735 begin
6736 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
6737 PLAYER_RECT.Width, PLAYER_RECT.Height, PANEL_STEP, False);
6738 end;
6740 {function TBot.NeedItem(Item: Byte): Byte;
6741 begin
6742 Result := 4;
6743 end;}
6745 procedure TBot.SelectWeapon(Dist: Integer);
6746 var
6747 a: Integer;
6749 function HaveAmmo(weapon: Byte): Boolean;
6750 begin
6751 case weapon of
6752 WEAPON_PISTOL: Result := FAmmo[A_BULLETS] >= 1;
6753 WEAPON_SHOTGUN1: Result := FAmmo[A_SHELLS] >= 1;
6754 WEAPON_SHOTGUN2: Result := FAmmo[A_SHELLS] >= 2;
6755 WEAPON_CHAINGUN: Result := FAmmo[A_BULLETS] >= 10;
6756 WEAPON_ROCKETLAUNCHER: Result := FAmmo[A_ROCKETS] >= 1;
6757 WEAPON_PLASMA: Result := FAmmo[A_CELLS] >= 10;
6758 WEAPON_BFG: Result := FAmmo[A_CELLS] >= 40;
6759 WEAPON_SUPERPULEMET: Result := FAmmo[A_SHELLS] >= 1;
6760 WEAPON_FLAMETHROWER: Result := FAmmo[A_FUEL] >= 1;
6761 else Result := True;
6762 end;
6763 end;
6765 begin
6766 if Dist = -1 then Dist := BOT_LONGDIST;
6768 if Dist > BOT_LONGDIST then
6769 begin // Дальний бой
6770 for a := 0 to 9 do
6771 if FWeapon[FDifficult.WeaponPrior[a]] and HaveAmmo(FDifficult.WeaponPrior[a]) then
6772 begin
6773 FSelectedWeapon := FDifficult.WeaponPrior[a];
6774 Break;
6775 end;
6776 end
6777 else //if Dist > BOT_UNSAFEDIST then
6778 begin // Ближний бой
6779 for a := 0 to 9 do
6780 if FWeapon[FDifficult.CloseWeaponPrior[a]] and HaveAmmo(FDifficult.CloseWeaponPrior[a]) then
6781 begin
6782 FSelectedWeapon := FDifficult.CloseWeaponPrior[a];
6783 Break;
6784 end;
6785 end;
6786 { else
6787 begin
6788 for a := 0 to 9 do
6789 if FWeapon[FDifficult.SafeWeaponPrior[a]] and HaveAmmo(FDifficult.SafeWeaponPrior[a]) then
6790 begin
6791 FSelectedWeapon := FDifficult.SafeWeaponPrior[a];
6792 Break;
6793 end;
6794 end;}
6795 end;
6797 function TBot.PickItem(ItemType: Byte; force: Boolean; var remove: Boolean): Boolean;
6798 begin
6799 Result := inherited PickItem(ItemType, force, remove);
6801 if Result then SetAIFlag('SELECTWEAPON', '1');
6802 end;
6804 function TBot.Heal(value: Word; Soft: Boolean): Boolean;
6805 begin
6806 Result := inherited Heal(value, Soft);
6807 end;
6809 function TBot.Healthy(): Byte;
6810 begin
6811 if FMegaRulez[MR_INVUL] >= gTime then Result := 3
6812 else if (FHealth > 80) or ((FHealth > 50) and (FArmor > 20)) then Result := 3
6813 else if (FHealth > 50) then Result := 2
6814 else if (FHealth > 20) then Result := 1
6815 else Result := 0;
6816 end;
6818 function TBot.TargetOnScreen(TX, TY: Integer): Boolean;
6819 begin
6820 Result := (Abs(FObj.X-TX) <= Trunc(gPlayerScreenSize.X*0.6)) and
6821 (Abs(FObj.Y-TY) <= Trunc(gPlayerScreenSize.Y*0.6));
6822 end;
6824 procedure TBot.OnDamage(Angle: SmallInt);
6825 var
6826 pla: TPlayer;
6827 mon: TMonster;
6828 ok: Boolean;
6829 begin
6830 inherited;
6832 if (Angle = 0) or (Angle = 180) then
6833 begin
6834 ok := False;
6835 if (g_GetUIDType(FLastSpawnerUID) = UID_PLAYER) and
6836 LongBool(gGameSettings.Options and GAME_OPTION_BOTVSPLAYER) then
6837 begin // Игрок
6838 pla := g_Player_Get(FLastSpawnerUID);
6839 ok := not TargetOnScreen(pla.FObj.X + PLAYER_RECT.X,
6840 pla.FObj.Y + PLAYER_RECT.Y);
6841 end
6842 else
6843 if (g_GetUIDType(FLastSpawnerUID) = UID_MONSTER) and
6844 LongBool(gGameSettings.Options and GAME_OPTION_BOTVSMONSTER) then
6845 begin // Монстр
6846 mon := g_Monsters_ByUID(FLastSpawnerUID);
6847 ok := not TargetOnScreen(mon.Obj.X + mon.Obj.Rect.X,
6848 mon.Obj.Y + mon.Obj.Rect.Y);
6849 end;
6851 if ok then
6852 if Angle = 0 then
6853 SetAIFlag('ATTACKLEFT', '1')
6854 else
6855 SetAIFlag('ATTACKRIGHT', '1');
6856 end;
6857 end;
6859 function TBot.RunDirection(): TDirection;
6860 begin
6861 if Abs(Vel.X) >= 1 then
6862 begin
6863 if Vel.X > 0 then Result := TDirection.D_RIGHT else Result := TDirection.D_LEFT;
6864 end else
6865 Result := FDirection;
6866 end;
6868 function TBot.GetRnd(a: Byte): Boolean;
6869 begin
6870 if a = 0 then Result := False
6871 else if a = 255 then Result := True
6872 else Result := Random(256) > 255-a;
6873 end;
6875 function TBot.GetInterval(a: Byte; radius: SmallInt): SmallInt;
6876 begin
6877 Result := Round((255-a)/255*radius*(Random(2)-1));
6878 end;
6881 procedure TDifficult.save (st: TStream);
6882 begin
6883 utils.writeInt(st, Byte(DiagFire));
6884 utils.writeInt(st, Byte(InvisFire));
6885 utils.writeInt(st, Byte(DiagPrecision));
6886 utils.writeInt(st, Byte(FlyPrecision));
6887 utils.writeInt(st, Byte(Cover));
6888 utils.writeInt(st, Byte(CloseJump));
6889 st.WriteBuffer(WeaponPrior[Low(WeaponPrior)], sizeof(WeaponPrior));
6890 st.WriteBuffer(CloseWeaponPrior[Low(CloseWeaponPrior)], sizeof(CloseWeaponPrior));
6891 end;
6893 procedure TDifficult.load (st: TStream);
6894 begin
6895 DiagFire := utils.readByte(st);
6896 InvisFire := utils.readByte(st);
6897 DiagPrecision := utils.readByte(st);
6898 FlyPrecision := utils.readByte(st);
6899 Cover := utils.readByte(st);
6900 CloseJump := utils.readByte(st);
6901 st.ReadBuffer(WeaponPrior[Low(WeaponPrior)], sizeof(WeaponPrior));
6902 st.ReadBuffer(CloseWeaponPrior[Low(CloseWeaponPrior)], sizeof(CloseWeaponPrior));
6903 end;
6906 procedure TBot.SaveState (st: TStream);
6907 var
6908 i: Integer;
6909 dw: Integer;
6910 begin
6911 inherited SaveState(st);
6912 utils.writeSign(st, 'BOT0');
6913 // Выбранное оружие
6914 utils.writeInt(st, Byte(FSelectedWeapon));
6915 // UID цели
6916 utils.writeInt(st, Word(FTargetUID));
6917 // Время потери цели
6918 utils.writeInt(st, LongWord(FLastVisible));
6919 // Количество флагов ИИ
6920 dw := Length(FAIFlags);
6921 utils.writeInt(st, LongInt(dw));
6922 // Флаги ИИ
6923 for i := 0 to dw-1 do
6924 begin
6925 utils.writeStr(st, FAIFlags[i].Name, 20);
6926 utils.writeStr(st, FAIFlags[i].Value, 20);
6927 end;
6928 // Настройки сложности
6929 FDifficult.save(st);
6930 end;
6933 procedure TBot.LoadState (st: TStream);
6934 var
6935 i: Integer;
6936 dw: Integer;
6937 begin
6938 inherited LoadState(st);
6939 if not utils.checkSign(st, 'BOT0') then raise XStreamError.Create('invalid bot signature');
6940 // Выбранное оружие
6941 FSelectedWeapon := utils.readByte(st);
6942 // UID цели
6943 FTargetUID := utils.readWord(st);
6944 // Время потери цели
6945 FLastVisible := utils.readLongWord(st);
6946 // Количество флагов ИИ
6947 dw := utils.readLongInt(st);
6948 if (dw < 0) or (dw > 16384) then raise XStreamError.Create('invalid number of bot AI flags');
6949 SetLength(FAIFlags, dw);
6950 // Флаги ИИ
6951 for i := 0 to dw-1 do
6952 begin
6953 FAIFlags[i].Name := utils.readStr(st, 20);
6954 FAIFlags[i].Value := utils.readStr(st, 20);
6955 end;
6956 // Настройки сложности
6957 FDifficult.load(st);
6958 end;
6961 begin
6962 conRegVar('cheat_berserk_autoswitch', @gBerserkAutoswitch, 'autoswitch to fist when berserk pack taken', '', true, true);
6963 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');
6964 conRegVar('player_indicator_style', @gPlayerIndicatorStyle, 'Visual appearance of indicator', 'Visual appearance of indicator');
6965 end.