DEADSOFTWARE

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