DEADSOFTWARE

ea2aea5d3dba7f8568d18fa7f17e894a745dc984
[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 PLAYER_RECT: TRectWH = (X:15; Y:12; Width:34; Height:52);
83 PLAYER_RECT_CX = 15+(34 div 2);
84 PLAYER_RECT_CY = 12+(52 div 2);
86 PLAYER_HP_SOFT = 100;
87 PLAYER_HP_LIMIT = 200;
88 PLAYER_AP_SOFT = 100;
89 PLAYER_AP_LIMIT = 200;
90 SUICIDE_DAMAGE = 112;
91 WEAPON_DELAY = 5;
93 PLAYER_BURN_TIME = 110;
95 PLAYER1_DEF_COLOR: TRGB = (R:64; G:175; B:48);
96 PLAYER2_DEF_COLOR: TRGB = (R:96; G:96; B:96);
98 AIR_DEF = 360;
99 AIR_MAX = 1091;
100 JET_MAX = 540; // ~30 sec
101 ANGLE_RIGHTUP = 55;
102 ANGLE_RIGHTDOWN = -35;
103 ANGLE_LEFTUP = 125;
104 ANGLE_LEFTDOWN = -145;
105 WEAPONPOINT: Array [TDirection] of TDFPoint = ((X:16; Y:32), (X:47; Y:32));
106 TEAMCOLOR: Array [TEAM_RED..TEAM_BLUE] of TRGB = ((R:255; G:0; B:0),
107 (R:0; G:0; B:255));
109 type
110 TPlayerStat = record
111 Num: Integer;
112 Ping: Word;
113 Loss: Byte;
114 Name: String;
115 Team: Byte;
116 Frags: SmallInt;
117 Deaths: SmallInt;
118 Lives: Byte;
119 Kills: Word;
120 Color: TRGB;
121 Spectator: Boolean;
122 UID: Word;
123 end;
125 TPlayerStatArray = Array of TPlayerStat;
127 TPlayerSavedState = record
128 Health: Integer;
129 Armor: Integer;
130 Air: Integer;
131 JetFuel: Integer;
132 CurrWeap: Byte;
133 NextWeap: WORD;
134 NextWeapDelay: Byte;
135 Ammo: Array [A_BULLETS..A_HIGH] of Word;
136 MaxAmmo: Array [A_BULLETS..A_HIGH] of Word;
137 Weapon: Array [WP_FIRST..WP_LAST] of Boolean;
138 Rulez: Set of R_ITEM_BACKPACK..R_BERSERK;
139 Used: Boolean;
140 end;
142 TKeyState = record
143 Pressed: Boolean;
144 Time: Word;
145 end;
147 TPlayer = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
148 private
149 FIamBot: Boolean;
150 FUID: Word;
151 FName: String;
152 FTeam: Byte;
153 FAlive: Boolean;
154 FSpawned: Boolean;
155 FDirection: TDirection;
156 FHealth: Integer;
157 FLives: Byte;
158 FArmor: Integer;
159 FAir: Integer;
160 FPain: Integer;
161 FPickup: Integer;
162 FKills: Integer;
163 FMonsterKills: Integer;
164 FFrags: Integer;
165 FFragCombo: Byte;
166 FLastFrag: LongWord;
167 FComboEvnt: Integer;
168 FDeath: Integer;
169 FCanJetpack: Boolean;
170 FJetFuel: Integer;
171 FFlag: Byte;
172 FSecrets: Integer;
173 FCurrWeap: Byte;
174 FNextWeap: WORD;
175 FNextWeapDelay: Byte; // frames
176 FBFGFireCounter: SmallInt;
177 FLastSpawnerUID: Word;
178 FLastHit: Byte;
179 FObj: TObj;
180 FXTo, FYTo: Integer;
181 FSpectatePlayer: Integer;
182 FFirePainTime: Integer;
183 FFireAttacker: Word;
185 FSavedStateNum: Integer;
187 FModel: TPlayerModel;
188 FPunchAnim: TAnimationState;
189 FActionPrior: Byte;
190 FActionAnim: Byte;
191 FActionForce: Boolean;
192 FActionChanged: Boolean;
193 FAngle: SmallInt;
194 FFireAngle: SmallInt;
195 FIncCamOld: Integer;
196 FIncCam: Integer;
197 FSlopeOld: Integer;
198 {$IFDEF ENABLE_SHELLS}
199 FShellTimer: Integer;
200 FShellType: Byte;
201 {$ENDIF}
202 FSawSound: TPlayableSound;
203 FSawSoundIdle: TPlayableSound;
204 FSawSoundHit: TPlayableSound;
205 FSawSoundSelect: TPlayableSound;
206 FFlameSoundOn: TPlayableSound;
207 FFlameSoundOff: TPlayableSound;
208 FFlameSoundWork: TPlayableSound;
209 FJetSoundOn: TPlayableSound;
210 FJetSoundOff: TPlayableSound;
211 FJetSoundFly: TPlayableSound;
212 FGodMode: Boolean;
213 FNoTarget: Boolean;
214 FNoReload: Boolean;
215 FJustTeleported: Boolean;
216 FNetTime: LongWord;
217 mEDamageType: Integer;
220 function CollideLevel(XInc, YInc: Integer): Boolean;
221 function StayOnStep(XInc, YInc: Integer): Boolean;
222 function HeadInLiquid(XInc, YInc: Integer): Boolean;
223 function BodyInLiquid(XInc, YInc: Integer): Boolean;
224 function BodyInAcid(XInc, YInc: Integer): Boolean;
225 function FullInLift(XInc, YInc: Integer): Integer;
226 {procedure CollideItem();}
227 procedure FlySmoke(Times: DWORD = 1);
228 procedure OnFireFlame(Times: DWORD = 1);
229 procedure SetAction(Action: Byte; Force: Boolean = False);
230 procedure OnDamage(Angle: SmallInt); virtual;
231 function firediry(): Integer;
232 procedure DoPunch();
234 procedure Run(Direction: TDirection);
235 procedure NextWeapon();
236 procedure PrevWeapon();
237 procedure SeeUp();
238 procedure SeeDown();
239 procedure Fire();
240 procedure Jump();
241 procedure Use();
243 function getNextWeaponIndex (): Byte; // return 255 for "no switch"
244 procedure resetWeaponQueue ();
245 function hasAmmoForWeapon (weapon: Byte): Boolean;
246 function hasAmmoForShooting (weapon: Byte): Boolean;
247 function shouldSwitch (weapon: Byte; hadWeapon: Boolean) : Boolean;
249 procedure doDamage (v: Integer);
251 public
252 FDamageBuffer: Integer;
254 FAmmo: Array [A_BULLETS..A_HIGH] of Word;
255 FMaxAmmo: Array [A_BULLETS..A_HIGH] of Word;
256 FWeapon: Array [WP_FIRST..WP_LAST] of Boolean;
257 FRulez: Set of R_ITEM_BACKPACK..R_BERSERK;
258 FBerserk: Integer;
259 FMegaRulez: Array [MR_SUIT..MR_MAX] of DWORD;
260 FReloading: Array [WP_FIRST..WP_LAST] of Word;
261 FTime: Array [T_RESPAWN..T_FLAGCAP] of DWORD;
262 FKeys: Array [KEY_LEFT..KEY_CHAT] of TKeyState;
263 FWeapSwitchMode: Byte;
264 FWeapPreferences: Array [WP_FIRST .. WP_LAST+1] of Byte;
265 FSwitchToEmpty: Byte;
266 FSkipFist: Byte;
267 FColor: TRGB;
268 FPreferredTeam: Byte;
269 FSpectator: Boolean;
270 FNoRespawn: Boolean;
271 FWantsInGame: Boolean;
272 FGhost: Boolean;
273 FPhysics: Boolean;
274 FFlaming: Boolean;
275 FJetpack: Boolean;
276 FActualModelName: string;
277 FClientID: SmallInt;
278 FPing: Word;
279 FLoss: Byte;
280 FReady: Boolean;
281 FDummy: Boolean;
282 FFireTime: Integer;
283 FSpawnInvul: Integer;
284 FHandicap: Integer;
285 FWaitForFirstSpawn: Boolean; // set to `true` in server, used to spawn a player on first full state request
287 {$IFDEF ENABLE_CORPSES}
288 FCorpse: Integer;
289 {$ENDIF}
291 // debug: viewport offset
292 viewPortX, viewPortY, viewPortW, viewPortH: Integer;
294 function isValidViewPort (): Boolean; inline;
296 constructor Create(); virtual;
297 destructor Destroy(); override;
298 procedure Respawn(Silent: Boolean; Force: Boolean = False); virtual;
299 function GetRespawnPoint(): Byte;
300 procedure PressKey(Key: Byte; Time: Word = 1);
301 procedure ReleaseKeys();
302 procedure SetModel(ModelName: String);
303 procedure SetColor(Color: TRGB);
304 function GetColor(): TRGB;
305 procedure SetWeapon(W: Byte);
306 function IsKeyPressed(K: Byte): Boolean;
307 function GetKeys(): Byte;
308 function PickItem(ItemType: Byte; arespawn: Boolean; var remove: Boolean): Boolean; virtual;
309 procedure SetWeaponPrefs(Prefs: Array of Byte);
310 procedure SetWeaponPref(Weapon, Pref: Byte);
311 function GetWeaponPref(Weapon: Byte) : Byte;
312 function GetMorePrefered() : Byte;
313 function MaySwitch(Weapon: Byte) : Boolean;
314 function Collide(X, Y: Integer; Width, Height: Word): Boolean; overload;
315 function Collide(Panel: TPanel): Boolean; overload;
316 function Collide(X, Y: Integer): Boolean; overload;
317 procedure SetDirection(Direction: TDirection);
318 procedure GetSecret();
319 function TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean;
320 procedure Touch();
321 procedure Push(vx, vy: Integer);
322 procedure ChangeModel(ModelName: String);
323 procedure SwitchTeam;
324 procedure ChangeTeam(Team: Byte);
325 procedure BFGHit();
326 function GetFlag(Flag: Byte): Boolean;
327 procedure SetFlag(Flag: Byte);
328 function DropFlag(Silent: Boolean = True; DoThrow: Boolean = False): Boolean;
329 function TryDropFlag(): Boolean;
330 procedure AllRulez(Health: Boolean);
331 procedure RestoreHealthArmor();
332 procedure FragCombo();
333 procedure GiveItem(ItemType: Byte);
334 procedure Damage(value: Word; SpawnerUID: Word; vx, vy: Integer; t: Byte); virtual;
335 function Heal(value: Word; Soft: Boolean): Boolean; virtual;
336 procedure MakeBloodVector(Count: Word; VelX, VelY: Integer);
337 procedure MakeBloodSimple(Count: Word);
338 procedure Kill(KillType: Byte; SpawnerUID: Word; t: Byte);
339 procedure Reset(Force: Boolean);
340 procedure Spectate(NoMove: Boolean = False);
341 procedure SwitchNoClip;
342 procedure SoftReset();
343 procedure PreUpdate();
344 procedure Update(); virtual;
345 procedure RememberState();
346 procedure RecallState();
347 procedure SaveState (st: TStream); virtual;
348 procedure LoadState (st: TStream); virtual;
349 procedure PauseSounds(Enable: Boolean);
350 procedure NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1);
351 procedure DoLerp(Level: Integer = 2);
352 procedure SetLerp(XTo, YTo: Integer);
353 procedure ProcessWeaponAction(Action: Byte);
354 procedure QueueWeaponSwitch(Weapon: Byte);
355 procedure RealizeCurrentWeapon();
356 procedure FlamerOn;
357 procedure FlamerOff;
358 procedure JetpackOn;
359 procedure JetpackOff;
360 procedure CatchFire(Attacker: Word; Timeout: Integer = PLAYER_BURN_TIME);
362 //WARNING! this does nothing for now, but still call it!
363 procedure positionChanged (); //WARNING! call this after entity position was changed, or coldet will not work right!
365 procedure getMapBox (out x, y, w, h: Integer); inline;
366 procedure moveBy (dx, dy: Integer); inline;
368 function GetAmmoByWeapon(Weapon: Byte): Word; // private state
370 public
371 property Vel: TPoint2i read FObj.Vel;
372 property Obj: TObj read FObj;
374 property Name: String read FName write FName;
375 property Model: TPlayerModel read FModel;
376 property Health: Integer read FHealth write FHealth;
377 property Lives: Byte read FLives write FLives;
378 property Armor: Integer read FArmor write FArmor;
379 property Air: Integer read FAir write FAir;
380 property JetFuel: Integer read FJetFuel write FJetFuel;
381 property Frags: Integer read FFrags write FFrags;
382 property Death: Integer read FDeath write FDeath;
383 property Kills: Integer read FKills write FKills;
384 property CurrWeap: Byte read FCurrWeap write FCurrWeap;
385 property WeapSwitchMode: Byte read FWeapSwitchMode write FWeapSwitchMode;
386 property SwitchToEmpty: Byte read FSwitchToEmpty write FSwitchToEmpty;
387 property SkipFist: Byte read FSkipFist write FSkipFist;
388 property MonsterKills: Integer read FMonsterKills write FMonsterKills;
389 property Secrets: Integer read FSecrets;
390 property GodMode: Boolean read FGodMode write FGodMode;
391 property NoTarget: Boolean read FNoTarget write FNoTarget;
392 property NoReload: Boolean read FNoReload write FNoReload;
393 property alive: Boolean read FAlive write FAlive;
394 property Flag: Byte read FFlag;
395 property Team: Byte read FTeam write FTeam;
396 property Direction: TDirection read FDirection;
397 property GameX: Integer read FObj.X write FObj.X;
398 property GameY: Integer read FObj.Y write FObj.Y;
399 property GameVelX: Integer read FObj.Vel.X write FObj.Vel.X;
400 property GameVelY: Integer read FObj.Vel.Y write FObj.Vel.Y;
401 property GameAccelX: Integer read FObj.Accel.X write FObj.Accel.X;
402 property GameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y;
403 property IncCam: Integer read FIncCam write FIncCam;
404 property IncCamOld: Integer read FIncCamOld write FIncCamOld;
405 property SlopeOld: Integer read FSlopeOld write FSlopeOld;
406 property UID: Word read FUID write FUID;
407 property JustTeleported: Boolean read FJustTeleported write FJustTeleported;
408 property NetTime: LongWord read FNetTime write FNetTime;
410 (* internal state *)
411 property Angle_: SmallInt read FAngle;
412 property Spectator: Boolean read FSpectator;
413 property NoRespawn: Boolean read FNoRespawn;
414 property Berserk: Integer read FBerserk;
415 property Pain: Integer read FPain;
416 property Pickup: Integer read FPickup;
417 property PunchAnim: TAnimationState read FPunchAnim write FPunchAnim;
418 property SpawnInvul: Integer read FSpawnInvul;
419 property Ghost: Boolean read FGhost;
421 published
422 property eName: String read FName write FName;
423 property eHealth: Integer read FHealth write FHealth;
424 property eLives: Byte read FLives write FLives;
425 property eArmor: Integer read FArmor write FArmor;
426 property eAir: Integer read FAir write FAir;
427 property eJetFuel: Integer read FJetFuel write FJetFuel;
428 property eFrags: Integer read FFrags write FFrags;
429 property eDeath: Integer read FDeath write FDeath;
430 property eKills: Integer read FKills write FKills;
431 property eCurrWeap: Byte read FCurrWeap write FCurrWeap;
432 property eMonsterKills: Integer read FMonsterKills write FMonsterKills;
433 property eSecrets: Integer read FSecrets write FSecrets;
434 property eGodMode: Boolean read FGodMode write FGodMode;
435 property eNoTarget: Boolean read FNoTarget write FNoTarget;
436 property eNoReload: Boolean read FNoReload write FNoReload;
437 property eAlive: Boolean read FAlive write FAlive;
438 property eFlag: Byte read FFlag;
439 property eTeam: Byte read FTeam write FTeam;
440 property eDirection: TDirection read FDirection;
441 property eGameX: Integer read FObj.X write FObj.X;
442 property eGameY: Integer read FObj.Y write FObj.Y;
443 property eGameVelX: Integer read FObj.Vel.X write FObj.Vel.X;
444 property eGameVelY: Integer read FObj.Vel.Y write FObj.Vel.Y;
445 property eGameAccelX: Integer read FObj.Accel.X write FObj.Accel.X;
446 property eGameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y;
447 property eIncCam: Integer read FIncCam write FIncCam;
448 property eUID: Word read FUID;
449 property eJustTeleported: Boolean read FJustTeleported;
450 property eNetTime: LongWord read FNetTime;
452 // set this before assigning something to `eDamage`
453 property eDamageType: Integer read mEDamageType write mEDamageType;
454 property eDamage: Integer write doDamage;
456 {$IFDEF ENABLE_CORPSES}
457 property Corpse: Integer read FCorpse;
458 {$ENDIF}
459 end;
461 TDifficult = record
462 public
463 DiagFire: Byte;
464 InvisFire: Byte;
465 DiagPrecision: Byte;
466 FlyPrecision: Byte;
467 Cover: Byte;
468 CloseJump: Byte;
469 WeaponPrior: packed array [WP_FIRST..WP_LAST] of Byte;
470 CloseWeaponPrior: packed array [WP_FIRST..WP_LAST] of Byte;
471 //SafeWeaponPrior: Array [WP_FIRST..WP_LAST] of Byte;
473 public
474 procedure save (st: TStream);
475 procedure load (st: TStream);
476 end;
478 TAIFlag = record
479 Name: String;
480 Value: String;
481 end;
483 TBot = class(TPlayer)
484 private
485 FSelectedWeapon: Byte;
486 FTargetUID: Word;
487 FLastVisible: DWORD;
488 FAIFlags: Array of TAIFlag;
489 FDifficult: TDifficult;
491 function GetRnd(a: Byte): Boolean;
492 function GetInterval(a: Byte; radius: SmallInt): SmallInt;
493 function RunDirection(): TDirection;
494 function FullInStep(XInc, YInc: Integer): Boolean;
495 //function NeedItem(Item: Byte): Byte;
496 procedure SelectWeapon(Dist: Integer);
497 procedure SetAIFlag(aName, fValue: String20);
498 function GetAIFlag(aName: String20): String20;
499 procedure RemoveAIFlag(aName: String20);
500 function Healthy(): Byte;
501 procedure UpdateMove();
502 procedure UpdateCombat();
503 function KeyPressed(Key: Word): Boolean;
504 procedure ReleaseKey(Key: Byte);
505 function TargetOnScreen(TX, TY: Integer): Boolean;
506 procedure OnDamage(Angle: SmallInt); override;
508 public
509 procedure Respawn(Silent: Boolean; Force: Boolean = False); override;
510 constructor Create(); override;
511 destructor Destroy(); override;
512 function PickItem(ItemType: Byte; force: Boolean; var remove: Boolean): Boolean; override;
513 function Heal(value: Word; Soft: Boolean): Boolean; override;
514 procedure Update(); override;
515 procedure SaveState (st: TStream); override;
516 procedure LoadState (st: TStream); override;
517 end;
519 TTeamStat = Array [TEAM_RED..TEAM_BLUE] of
520 record
521 Score: SmallInt;
522 end;
524 var
525 gPlayers: Array of TPlayer;
526 gTeamStat: TTeamStat;
527 gFly: Boolean = False;
528 gAimLine: Boolean = False;
529 gChatBubble: Integer = 0;
530 gPlayerIndicator: Integer = 1;
531 gPlayerIndicatorStyle: Integer = 0;
532 gNumBots: Word = 0;
533 gSpectLatchPID1: Word = 0;
534 gSpectLatchPID2: Word = 0;
535 MAX_RUNVEL: Integer = 8;
536 VEL_JUMP: Integer = 10;
538 function Lerp(X, Y, Factor: Integer): Integer;
540 procedure g_Force_Model_Set(Mode: Word);
541 function g_Force_Model_Get(): Word;
542 procedure g_Forced_Model_SetName(Model: String);
543 function g_Forced_Model_GetName(): String;
545 procedure g_Player_Init();
546 procedure g_Player_Free();
547 function g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boolean): Word;
548 function g_Player_CreateFromState (st: TStream): Word;
549 procedure g_Player_Remove(UID: Word);
550 procedure g_Player_ResetTeams();
551 procedure g_Player_PreUpdate();
552 procedure g_Player_UpdateAll();
553 procedure g_Player_RememberAll();
554 procedure g_Player_ResetAll(Force, Silent: Boolean);
555 function g_Player_Get(UID: Word): TPlayer;
556 function g_Player_GetCount(): Byte;
557 function g_Player_GetStats(): TPlayerStatArray;
558 function g_Player_ValidName(Name: String): Boolean;
559 procedure g_Player_ResetReady();
560 procedure g_Bot_Add(Team, Difficult: Byte; Handicap: Integer = 100);
561 procedure g_Bot_AddList(Team: Byte; lname: ShortString; num: Integer = -1; Handicap: Integer = 100);
562 procedure g_Bot_MixNames();
563 procedure g_Bot_RemoveAll();
564 function g_Bot_GetCount(): Integer;
566 implementation
568 uses
569 {$IFDEF ENABLE_HOLMES}
570 g_holmes,
571 {$ENDIF}
572 {$IFDEF ENABLE_MENU}
573 g_menu,
574 {$ENDIF}
575 {$IFDEF ENABLE_RENDER}
576 r_render,
577 {$ENDIF}
578 {$IFDEF ENABLE_GFX}
579 g_gfx,
580 {$ENDIF}
581 {$IFDEF ENABLE_GIBS}
582 g_gibs,
583 {$ENDIF}
584 {$IFDEF ENABLE_SHELLS}
585 g_shells,
586 {$ENDIF}
587 {$IFDEF ENABLE_CORPSES}
588 g_corpses,
589 {$ENDIF}
590 e_log, g_map, g_items, g_console, Math,
591 g_options, g_triggers, g_game, g_grid, e_res,
592 wadreader, g_monsters, CONFIG, g_language,
593 g_net, g_netmsg,
594 utils, xstreams;
596 const PLR_SAVE_VERSION = 0;
598 type
599 TBotProfile = record
600 name: ShortString;
601 model: ShortString;
602 team: Byte;
603 color: TRGB;
604 diag_fire: Byte;
605 invis_fire: Byte;
606 diag_precision: Byte;
607 fly_precision: Byte;
608 cover: Byte;
609 close_jump: Byte;
610 w_prior1: Array [WP_FIRST..WP_LAST] of Byte;
611 w_prior2: Array [WP_FIRST..WP_LAST] of Byte;
612 w_prior3: Array [WP_FIRST..WP_LAST] of Byte;
613 end;
615 const
616 TIME_RESPAWN1 = 1500;
617 TIME_RESPAWN2 = 2000;
618 TIME_RESPAWN3 = 3000;
619 PLAYER_SUIT_TIME = 30000;
620 PLAYER_INVUL_TIME = 30000;
621 PLAYER_INVIS_TIME = 35000;
622 FRAG_COMBO_TIME = 3000;
623 VEL_SW = 4;
624 VEL_FLY = 6;
625 PLAYER_HEADRECT: TRectWH = (X:24; Y:12; Width:20; Height:12);
626 BOT_MAXJUMP = 84;
627 BOT_LONGDIST = 300;
628 BOT_UNSAFEDIST = 128;
629 DIFFICULT_EASY: TDifficult = (DiagFire: 32; InvisFire: 32; DiagPrecision: 32;
630 FlyPrecision: 32; Cover: 32; CloseJump: 32;
631 WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0));
632 DIFFICULT_MEDIUM: TDifficult = (DiagFire: 127; InvisFire: 127; DiagPrecision: 127;
633 FlyPrecision: 127; Cover: 127; CloseJump: 127;
634 WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0));
635 DIFFICULT_HARD: TDifficult = (DiagFire: 255; InvisFire: 255; DiagPrecision: 255;
636 FlyPrecision: 255; Cover: 255; CloseJump: 255;
637 WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0));
638 WEAPON_PRIOR1: Array [WP_FIRST..WP_LAST] of Byte =
639 (WEAPON_FLAMETHROWER, WEAPON_SUPERPULEMET,
640 WEAPON_SHOTGUN2, WEAPON_SHOTGUN1,
641 WEAPON_CHAINGUN, WEAPON_PLASMA, WEAPON_ROCKETLAUNCHER,
642 WEAPON_BFG, WEAPON_PISTOL, WEAPON_SAW, WEAPON_KASTET);
643 WEAPON_PRIOR2: Array [WP_FIRST..WP_LAST] of Byte =
644 (WEAPON_FLAMETHROWER, WEAPON_SUPERPULEMET,
645 WEAPON_BFG, WEAPON_ROCKETLAUNCHER,
646 WEAPON_SHOTGUN2, WEAPON_PLASMA, WEAPON_SHOTGUN1,
647 WEAPON_CHAINGUN, WEAPON_PISTOL, WEAPON_SAW, WEAPON_KASTET);
648 //WEAPON_PRIOR3: Array [WP_FIRST..WP_LAST] of Byte =
649 // (WEAPON_FLAMETHROWER, WEAPON_SUPERPULEMET,
650 // WEAPON_BFG, WEAPON_PLASMA, WEAPON_SHOTGUN2,
651 // WEAPON_CHAINGUN, WEAPON_SHOTGUN1, WEAPON_SAW,
652 // WEAPON_ROCKETLAUNCHER, WEAPON_PISTOL, WEAPON_KASTET);
653 WEAPON_RELOAD: Array [WP_FIRST..WP_LAST] of Byte =
654 (5, 2, 6, 18, 36, 2, 12, 2, 14, 2, 2);
656 PLAYER_SIGNATURE = $52594C50; // 'PLYR'
657 CORPSE_SIGNATURE = $50524F43; // 'CORP'
659 BOTNAMES_FILENAME = 'botnames.txt';
660 BOTLIST_FILENAME = 'botlist.txt';
662 var
663 ForceModel: Word = 0;
664 ForcedModelName: String = STD_PLAYER_MODEL;
665 BotNames: Array of String;
666 BotList: Array of TBotProfile;
667 SavedStates: Array of TPlayerSavedState;
670 function Lerp(X, Y, Factor: Integer): Integer;
671 begin
672 Result := X + ((Y - X) div Factor);
673 end;
675 function SameTeam(UID1, UID2: Word): Boolean;
676 begin
677 Result := False;
679 if (UID1 > UID_MAX_PLAYER) or (UID1 <= UID_MAX_GAME) or
680 (UID2 > UID_MAX_PLAYER) or (UID2 <= UID_MAX_GAME) then Exit;
682 if (g_Player_Get(UID1) = nil) or (g_Player_Get(UID2) = nil) then Exit;
684 if ((g_Player_Get(UID1).Team = TEAM_NONE) or
685 (g_Player_Get(UID2).Team = TEAM_NONE)) then Exit;
687 Result := g_Player_Get(UID1).FTeam = g_Player_Get(UID2).FTeam;
688 end;
690 procedure g_Force_Model_Set(Mode: Word);
691 begin
692 ForceModel := Mode;
693 end;
695 function g_Force_Model_Get(): Word;
696 begin
697 Result := ForceModel;
698 end;
700 procedure g_Forced_Model_SetName(Model: String);
701 begin
702 ForcedModelName := Model;
703 end;
705 function g_Forced_Model_GetName(): String;
706 begin
707 Result := ForcedModelName;
708 end;
710 function g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boolean): Word;
711 var
712 a: Integer;
713 ok: Boolean;
714 begin
715 Result := 0;
717 ok := False;
718 a := 0;
720 // Есть ли место в gPlayers:
721 if gPlayers <> nil then
722 for a := 0 to High(gPlayers) do
723 if gPlayers[a] = nil then
724 begin
725 ok := True;
726 Break;
727 end;
729 // Нет места - расширяем gPlayers:
730 if not ok then
731 begin
732 SetLength(gPlayers, Length(gPlayers)+1);
733 a := High(gPlayers);
734 end;
736 // Создаем объект игрока:
737 if Bot then
738 gPlayers[a] := TBot.Create()
739 else
740 gPlayers[a] := TPlayer.Create();
743 gPlayers[a].FActualModelName := ModelName;
744 gPlayers[a].SetModel(ModelName);
745 if Bot and (g_Force_Model_Get() <> 0) then
746 gPlayers[a].SetModel(g_Forced_Model_GetName());
748 // Нет модели - создание не возможно:
749 if gPlayers[a].FModel = nil then
750 begin
751 gPlayers[a].Free();
752 gPlayers[a] := nil;
753 g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], [ModelName]));
754 Exit;
755 end;
757 if not (Team in [TEAM_RED, TEAM_BLUE]) then
758 if Random(2) = 0 then
759 Team := TEAM_RED
760 else
761 Team := TEAM_BLUE;
762 gPlayers[a].FPreferredTeam := Team;
764 case gGameSettings.GameMode of
765 GM_DM: gPlayers[a].FTeam := TEAM_NONE;
766 GM_TDM,
767 GM_CTF: gPlayers[a].FTeam := gPlayers[a].FPreferredTeam;
768 GM_SINGLE,
769 GM_COOP: gPlayers[a].FTeam := TEAM_COOP;
770 end;
772 // Если командная игра - красим модель в цвет команды:
773 gPlayers[a].FColor := Color;
774 if gPlayers[a].FTeam in [TEAM_RED, TEAM_BLUE] then
775 gPlayers[a].FModel.Color := TEAMCOLOR[gPlayers[a].FTeam]
776 else
777 gPlayers[a].FModel.Color := Color;
779 gPlayers[a].FUID := g_CreateUID(UID_PLAYER);
780 gPlayers[a].FAlive := False;
782 Result := gPlayers[a].FUID;
783 end;
785 function g_Player_CreateFromState (st: TStream): Word;
786 var a: Integer; ok, Bot: Boolean; pos: Int64;
787 begin
788 assert(st <> nil);
790 // check signature and entity type
791 pos := st.Position;
792 if not utils.checkSign(st, 'PLYR') then raise XStreamError.Create('invalid player signature');
793 if (utils.readByte(st) <> PLR_SAVE_VERSION) then raise XStreamError.Create('invalid player version');
794 Bot := utils.readBool(st);
795 st.Position := pos;
797 // find free player slot
798 ok := false;
799 for a := 0 to High(gPlayers) do
800 if gPlayers[a] = nil then
801 begin
802 ok := true;
803 break;
804 end;
806 // allocate player slot
807 if not ok then
808 begin
809 SetLength(gPlayers, Length(gPlayers)+1);
810 a := High(gPlayers);
811 end;
813 // create entity and load state
814 if Bot then
815 begin
816 gPlayers[a] := TBot.Create();
817 if (g_Force_Model_Get() <> 0) then
818 gPlayers[a].SetModel(g_Forced_Model_GetName());
819 end
820 else
821 gPlayers[a] := TPlayer.Create();
822 gPlayers[a].FPhysics := True; // ???
823 gPlayers[a].LoadState(st);
825 result := gPlayers[a].FUID;
826 end;
829 procedure g_Player_ResetTeams();
830 var
831 a: Integer;
832 begin
833 if g_Game_IsClient then
834 Exit;
835 if gPlayers = nil then
836 Exit;
837 for a := Low(gPlayers) to High(gPlayers) do
838 if gPlayers[a] <> nil then
839 case gGameSettings.GameMode of
840 GM_DM:
841 gPlayers[a].ChangeTeam(TEAM_NONE);
842 GM_TDM, GM_CTF:
843 if not (gPlayers[a].Team in [TEAM_RED, TEAM_BLUE]) then
844 if gPlayers[a].FPreferredTeam in [TEAM_RED, TEAM_BLUE] then
845 gPlayers[a].ChangeTeam(gPlayers[a].FPreferredTeam)
846 else
847 if a mod 2 = 0 then
848 gPlayers[a].ChangeTeam(TEAM_RED)
849 else
850 gPlayers[a].ChangeTeam(TEAM_BLUE);
851 GM_SINGLE,
852 GM_COOP:
853 gPlayers[a].ChangeTeam(TEAM_COOP);
854 end;
855 end;
857 procedure g_Bot_Add(Team, Difficult: Byte; Handicap: Integer = 100);
858 var
859 m: SSArray;
860 _name, _model: String;
861 a, tr, tb: Integer;
862 begin
863 if not g_Game_IsServer then Exit;
865 if (g_Bot_GetCount() >= gMaxBots) then Exit;
867 // Список названий моделей:
868 m := g_PlayerModel_GetNames();
869 if m = nil then
870 Exit;
872 // Команда:
873 if (gGameSettings.GameType = GT_SINGLE) or (gGameSettings.GameMode = GM_COOP) then
874 Team := TEAM_COOP // COOP
875 else
876 if gGameSettings.GameMode = GM_DM then
877 Team := TEAM_NONE // DM
878 else
879 if Team = TEAM_NONE then // CTF / TDM
880 begin
881 // Автобаланс команд:
882 tr := 0;
883 tb := 0;
885 for a := 0 to High(gPlayers) do
886 if gPlayers[a] <> nil then
887 begin
888 if gPlayers[a].Team = TEAM_RED then
889 Inc(tr)
890 else
891 if gPlayers[a].Team = TEAM_BLUE then
892 Inc(tb);
893 end;
895 if tr > tb then
896 Team := TEAM_BLUE
897 else
898 if tb > tr then
899 Team := TEAM_RED
900 else // tr = tb
901 if Random(2) = 0 then
902 Team := TEAM_RED
903 else
904 Team := TEAM_BLUE;
905 end;
907 // Выбираем боту имя:
908 _name := '';
909 if BotNames <> nil then
910 for a := 0 to High(BotNames) do
911 if g_Player_ValidName(BotNames[a]) then
912 begin
913 _name := BotNames[a];
914 Break;
915 end;
917 // Выбираем случайную модель:
918 _model := m[Random(Length(m))];
920 // Создаем бота:
921 with g_Player_Get(g_Player_Create(_model,
922 _RGB(Min(Random(9)*32, 255),
923 Min(Random(9)*32, 255),
924 Min(Random(9)*32, 255)),
925 Team, True)) as TBot do
926 begin
927 // Если имени нет, делаем его из UID бота
928 if _name = '' then
929 Name := Format('DFBOT%.5d', [UID])
930 else
931 Name := _name;
933 case Difficult of
934 1: FDifficult := DIFFICULT_EASY;
935 2: FDifficult := DIFFICULT_MEDIUM;
936 else FDifficult := DIFFICULT_HARD;
937 end;
939 for a := WP_FIRST to WP_LAST do
940 begin
941 FDifficult.WeaponPrior[a] := WEAPON_PRIOR1[a];
942 FDifficult.CloseWeaponPrior[a] := WEAPON_PRIOR2[a];
943 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
944 end;
946 FHandicap := Handicap;
948 g_Console_Add(Format(_lc[I_PLAYER_JOIN], [Name]), True);
950 if g_Game_IsNet then MH_SEND_PlayerCreate(UID);
951 if g_Game_IsServer and (gGameSettings.MaxLives > 0) then
952 Spectate();
953 end;
954 end;
956 procedure g_Bot_AddList(Team: Byte; lName: ShortString; num: Integer = -1; Handicap: Integer = 100);
957 var
958 m: SSArray;
959 _name, _model: String;
960 a: Integer;
961 begin
962 if not g_Game_IsServer then Exit;
964 if (g_Bot_GetCount() >= gMaxBots) then Exit;
966 // Список названий моделей:
967 m := g_PlayerModel_GetNames();
968 if m = nil then
969 Exit;
971 // Команда:
972 if (gGameSettings.GameType = GT_SINGLE) or (gGameSettings.GameMode = GM_COOP) then
973 Team := TEAM_COOP // COOP
974 else
975 if gGameSettings.GameMode = GM_DM then
976 Team := TEAM_NONE // DM
977 else
978 if Team = TEAM_NONE then
979 Team := BotList[num].team; // CTF / TDM
981 // Выбираем настройки бота из списка по номеру или имени:
982 lName := AnsiLowerCase(lName);
983 if (num < 0) or (num > Length(BotList)-1) then
984 num := -1;
985 if (num = -1) and (lName <> '') and (BotList <> nil) then
986 for a := 0 to High(BotList) do
987 if AnsiLowerCase(BotList[a].name) = lName then
988 begin
989 num := a;
990 Break;
991 end;
992 if num = -1 then
993 Exit;
995 // Имя бота:
996 _name := BotList[num].name;
997 // Занято - выбираем случайное:
998 if not g_Player_ValidName(_name) then
999 repeat
1000 _name := Format('DFBOT%.2d', [Random(100)]);
1001 until g_Player_ValidName(_name);
1003 // Модель:
1004 _model := BotList[num].model;
1005 // Нет такой - выбираем случайную:
1006 if not InSArray(_model, m) then
1007 _model := m[Random(Length(m))];
1009 // Создаем бота:
1010 with g_Player_Get(g_Player_Create(_model, BotList[num].color, Team, True)) as TBot do
1011 begin
1012 Name := _name;
1014 FDifficult.DiagFire := BotList[num].diag_fire;
1015 FDifficult.InvisFire := BotList[num].invis_fire;
1016 FDifficult.DiagPrecision := BotList[num].diag_precision;
1017 FDifficult.FlyPrecision := BotList[num].fly_precision;
1018 FDifficult.Cover := BotList[num].cover;
1019 FDifficult.CloseJump := BotList[num].close_jump;
1021 FHandicap := Handicap;
1023 for a := WP_FIRST to WP_LAST do
1024 begin
1025 FDifficult.WeaponPrior[a] := BotList[num].w_prior1[a];
1026 FDifficult.CloseWeaponPrior[a] := BotList[num].w_prior2[a];
1027 //FDifficult.SafeWeaponPrior[a] := BotList[num].w_prior3[a];
1028 end;
1030 g_Console_Add(Format(_lc[I_PLAYER_JOIN], [Name]), True);
1032 if g_Game_IsNet then MH_SEND_PlayerCreate(UID);
1033 end;
1034 end;
1036 procedure g_Bot_RemoveAll();
1037 var
1038 a: Integer;
1039 begin
1040 if not g_Game_IsServer then Exit;
1041 if gPlayers = nil then Exit;
1043 for a := 0 to High(gPlayers) do
1044 if gPlayers[a] <> nil then
1045 if gPlayers[a] is TBot then
1046 begin
1047 gPlayers[a].Lives := 0;
1048 gPlayers[a].Kill(K_SIMPLEKILL, 0, HIT_DISCON);
1049 g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [gPlayers[a].Name]), True);
1050 g_Player_Remove(gPlayers[a].FUID);
1051 end;
1053 g_Bot_MixNames();
1054 end;
1056 procedure g_Bot_MixNames();
1057 var
1058 s: String;
1059 a, b: Integer;
1060 begin
1061 if BotNames <> nil then
1062 for a := 0 to High(BotNames) do
1063 begin
1064 b := Random(Length(BotNames));
1065 s := BotNames[a];
1066 Botnames[a] := BotNames[b];
1067 BotNames[b] := s;
1068 end;
1069 end;
1071 procedure g_Player_Remove(UID: Word);
1072 var
1073 i: Integer;
1074 begin
1075 if gPlayers = nil then Exit;
1077 if g_Game_IsServer and g_Game_IsNet then
1078 MH_SEND_PlayerDelete(UID);
1080 for i := 0 to High(gPlayers) do
1081 if gPlayers[i] <> nil then
1082 if gPlayers[i].FUID = UID then
1083 begin
1084 if gPlayers[i] is TPlayer then
1085 TPlayer(gPlayers[i]).Free()
1086 else
1087 TBot(gPlayers[i]).Free();
1088 gPlayers[i] := nil;
1089 Exit;
1090 end;
1091 end;
1093 procedure g_Player_Init();
1094 var
1095 F: TextFile;
1096 s: String;
1097 a, b: Integer;
1098 config: TConfig;
1099 sa: SSArray;
1100 path: AnsiString;
1101 begin
1102 BotNames := nil;
1104 path := BOTNAMES_FILENAME;
1105 if e_FindResource(DataDirs, path) = false then
1106 Exit;
1108 // Читаем возможные имена ботов из файла:
1109 AssignFile(F, path);
1110 Reset(F);
1112 while not EOF(F) do
1113 begin
1114 ReadLn(F, s);
1116 s := Trim(s);
1117 if s = '' then
1118 Continue;
1120 SetLength(BotNames, Length(BotNames)+1);
1121 BotNames[High(BotNames)] := s;
1122 end;
1124 CloseFile(F);
1126 // Перемешиваем их:
1127 g_Bot_MixNames();
1129 // Читаем файл с параметрами ботов:
1130 config := TConfig.CreateFile(path);
1131 BotList := nil;
1132 a := 0;
1134 while config.SectionExists(IntToStr(a)) do
1135 begin
1136 SetLength(BotList, Length(BotList)+1);
1138 with BotList[High(BotList)] do
1139 begin
1140 // Имя бота:
1141 name := config.ReadStr(IntToStr(a), 'name', '');
1142 // Модель:
1143 model := config.ReadStr(IntToStr(a), 'model', '');
1144 // Команда:
1145 if config.ReadStr(IntToStr(a), 'team', 'red') = 'red' then
1146 team := TEAM_RED
1147 else
1148 team := TEAM_BLUE;
1149 // Цвет модели:
1150 sa := parse(config.ReadStr(IntToStr(a), 'color', ''));
1151 color.R := StrToIntDef(sa[0], 0);
1152 color.G := StrToIntDef(sa[1], 0);
1153 color.B := StrToIntDef(sa[2], 0);
1154 // Вероятность стрельбы под углом:
1155 diag_fire := config.ReadInt(IntToStr(a), 'diag_fire', 0);
1156 // Вероятность ответного огня по невидимому сопернику:
1157 invis_fire := config.ReadInt(IntToStr(a), 'invis_fire', 0);
1158 // Точность стрельбы под углом:
1159 diag_precision := config.ReadInt(IntToStr(a), 'diag_precision', 0);
1160 // Точность стрельбы в полете:
1161 fly_precision := config.ReadInt(IntToStr(a), 'fly_precision', 0);
1162 // Точность уклонения от снарядов:
1163 cover := config.ReadInt(IntToStr(a), 'cover', 0);
1164 // Вероятность прыжка при приближении соперника:
1165 close_jump := config.ReadInt(IntToStr(a), 'close_jump', 0);
1166 // Приоритеты оружия для дальнего боя:
1167 sa := parse(config.ReadStr(IntToStr(a), 'w_prior1', ''));
1168 if Length(sa) = 10 then
1169 for b := 0 to 9 do
1170 w_prior1[b] := EnsureRange(StrToInt(sa[b]), 0, 9);
1171 // Приоритеты оружия для ближнего боя:
1172 sa := parse(config.ReadStr(IntToStr(a), 'w_prior2', ''));
1173 if Length(sa) = 10 then
1174 for b := 0 to 9 do
1175 w_prior2[b] := EnsureRange(StrToInt(sa[b]), 0, 9);
1177 {sa := parse(config.ReadStr(IntToStr(a), 'w_prior3', ''));
1178 if Length(sa) = 10 then
1179 for b := 0 to 9 do
1180 w_prior3[b] := EnsureRange(StrToInt(sa[b]), 0, 9);}
1181 end;
1183 a := a + 1;
1184 end;
1186 config.Free();
1187 SetLength(SavedStates, 0);
1188 end;
1190 procedure g_Player_Free();
1191 var
1192 i: Integer;
1193 begin
1194 if gPlayers <> nil then
1195 begin
1196 for i := 0 to High(gPlayers) do
1197 if gPlayers[i] <> nil then
1198 begin
1199 if gPlayers[i] is TPlayer then
1200 TPlayer(gPlayers[i]).Free()
1201 else
1202 TBot(gPlayers[i]).Free();
1203 gPlayers[i] := nil;
1204 end;
1206 gPlayers := nil;
1207 end;
1209 gPlayer1 := nil;
1210 gPlayer2 := nil;
1211 SetLength(SavedStates, 0);
1212 end;
1214 procedure g_Player_PreUpdate();
1215 var
1216 i: Integer;
1217 begin
1218 if gPlayers = nil then Exit;
1219 for i := 0 to High(gPlayers) do
1220 if gPlayers[i] <> nil then
1221 gPlayers[i].PreUpdate();
1222 end;
1224 procedure g_Player_UpdateAll();
1225 var
1226 i: Integer;
1227 begin
1228 if gPlayers = nil then Exit;
1230 //e_WriteLog('***g_Player_UpdateAll: ENTER', MSG_WARNING);
1231 for i := 0 to High(gPlayers) do
1232 begin
1233 if gPlayers[i] <> nil then
1234 begin
1235 if gPlayers[i] is TPlayer then
1236 begin
1237 gPlayers[i].Update();
1238 gPlayers[i].RealizeCurrentWeapon(); // WARNING! DO NOT MOVE THIS INTO `Update()`!
1239 end
1240 else
1241 begin
1242 // bot updates weapons in `UpdateCombat()`
1243 TBot(gPlayers[i]).Update();
1244 end;
1245 end;
1246 end;
1247 //e_WriteLog('***g_Player_UpdateAll: EXIT', MSG_WARNING);
1248 end;
1250 function g_Player_Get(UID: Word): TPlayer;
1251 var
1252 a: Integer;
1253 begin
1254 Result := nil;
1256 if gPlayers = nil then
1257 Exit;
1259 for a := 0 to High(gPlayers) do
1260 if gPlayers[a] <> nil then
1261 if gPlayers[a].FUID = UID then
1262 begin
1263 Result := gPlayers[a];
1264 Exit;
1265 end;
1266 end;
1268 function g_Player_GetCount(): Byte;
1269 var
1270 a: Integer;
1271 begin
1272 Result := 0;
1274 if gPlayers = nil then
1275 Exit;
1277 for a := 0 to High(gPlayers) do
1278 if gPlayers[a] <> nil then
1279 Result := Result + 1;
1280 end;
1282 function g_Bot_GetCount(): Integer;
1283 var
1284 a: Integer;
1285 begin
1286 Result := 0;
1288 if gPlayers = nil then
1289 Exit;
1291 for a := 0 to High(gPlayers) do
1292 if (gPlayers[a] <> nil) and (gPlayers[a] is TBot) then
1293 Result := Result + 1;
1294 end;
1296 function g_Player_GetStats(): TPlayerStatArray;
1297 var
1298 a: Integer;
1299 begin
1300 Result := nil;
1302 if gPlayers = nil then Exit;
1304 for a := 0 to High(gPlayers) do
1305 if gPlayers[a] <> nil then
1306 begin
1307 SetLength(Result, Length(Result)+1);
1308 with Result[High(Result)] do
1309 begin
1310 Num := a;
1311 Ping := gPlayers[a].FPing;
1312 Loss := gPlayers[a].FLoss;
1313 Name := gPlayers[a].FName;
1314 Team := gPlayers[a].FTeam;
1315 Frags := gPlayers[a].FFrags;
1316 Deaths := gPlayers[a].FDeath;
1317 Kills := gPlayers[a].FKills;
1318 Color := gPlayers[a].FModel.Color;
1319 Lives := gPlayers[a].FLives;
1320 Spectator := gPlayers[a].FSpectator;
1321 UID := gPlayers[a].FUID;
1322 end;
1323 end;
1324 end;
1326 procedure g_Player_ResetReady();
1327 var
1328 a: Integer;
1329 begin
1330 if not g_Game_IsServer then Exit;
1331 if gPlayers = nil then Exit;
1333 for a := 0 to High(gPlayers) do
1334 if gPlayers[a] <> nil then
1335 begin
1336 gPlayers[a].FReady := False;
1337 if g_Game_IsNet then
1338 MH_SEND_GameEvent(NET_EV_INTER_READY, gPlayers[a].UID, 'N');
1339 end;
1340 end;
1342 procedure g_Player_RememberAll;
1343 var
1344 i: Integer;
1345 begin
1346 for i := Low(gPlayers) to High(gPlayers) do
1347 if (gPlayers[i] <> nil) and gPlayers[i].alive then
1348 gPlayers[i].RememberState;
1349 end;
1351 procedure g_Player_ResetAll(Force, Silent: Boolean);
1352 var
1353 i: Integer;
1354 begin
1355 gTeamStat[TEAM_RED].Score := 0;
1356 gTeamStat[TEAM_BLUE].Score := 0;
1358 if gPlayers <> nil then
1359 for i := 0 to High(gPlayers) do
1360 if gPlayers[i] <> nil then
1361 begin
1362 gPlayers[i].Reset(Force);
1364 if gPlayers[i] is TPlayer then
1365 begin
1366 if (not gPlayers[i].FSpectator) or gPlayers[i].FWantsInGame then
1367 gPlayers[i].Respawn(Silent)
1368 else
1369 gPlayers[i].Spectate();
1370 end
1371 else
1372 TBot(gPlayers[i]).Respawn(Silent);
1373 end;
1374 end;
1376 { T P l a y e r : }
1378 function TPlayer.isValidViewPort (): Boolean; inline; begin result := (viewPortW > 0) and (viewPortH > 0); end;
1380 procedure TPlayer.BFGHit();
1381 begin
1382 g_Weapon_BFGHit(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
1383 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2));
1384 if g_Game_IsServer and g_Game_IsNet then
1385 MH_SEND_Effect(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
1386 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
1387 0, NET_GFX_BFGHIT);
1388 end;
1390 procedure TPlayer.ChangeModel(ModelName: string);
1391 var
1392 locModel: TPlayerModel;
1393 begin
1394 locModel := g_PlayerModel_Get(ModelName);
1395 if locModel = nil then Exit;
1397 FModel.Free();
1398 FModel := locModel;
1399 end;
1401 procedure TPlayer.SetModel(ModelName: string);
1402 var
1403 m: TPlayerModel;
1404 begin
1405 m := g_PlayerModel_Get(ModelName);
1406 if m = nil then
1407 begin
1408 g_SimpleError(Format(_lc[I_GAME_ERROR_MODEL_FALLBACK], [ModelName]));
1409 m := g_PlayerModel_Get('doomer');
1410 if m = nil then
1411 begin
1412 g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], ['doomer']));
1413 Exit;
1414 end;
1415 end;
1417 if FModel <> nil then
1418 FModel.Free();
1420 FModel := m;
1422 if not (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
1423 FModel.Color := FColor
1424 else
1425 FModel.Color := TEAMCOLOR[FTeam];
1426 FModel.SetWeapon(FCurrWeap);
1427 FModel.SetFlag(FFlag);
1428 SetDirection(FDirection);
1429 end;
1431 procedure TPlayer.SetColor(Color: TRGB);
1432 begin
1433 FColor := Color;
1434 if not (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
1435 if FModel <> nil then FModel.Color := Color;
1436 end;
1440 function TPlayer.GetColor(): TRGB;
1441 begin
1442 result := FModel.Color;
1443 end;
1445 procedure TPlayer.SetWeaponPrefs(Prefs: Array of Byte);
1446 var
1447 i: Integer;
1448 begin
1449 for i := WP_FIRST to WP_LAST + 1 do
1450 begin
1451 if (Prefs[i] > WP_LAST + 1) then
1452 FWeapPreferences[i] := 0
1453 else
1454 FWeapPreferences[i] := Prefs[i];
1455 end;
1456 end;
1458 procedure TPlayer.SetWeaponPref(Weapon, Pref: Byte);
1459 begin
1460 if (Weapon > WP_LAST + 1) then
1461 exit
1462 else if (Pref <= WP_LAST + 1) and (Weapon <= WP_LAST + 1) then
1463 FWeapPreferences[Weapon] := Pref
1464 else if (Weapon <= WP_LAST + 1) and (Pref > WP_LAST + 1) then
1465 FWeapPreferences[Weapon] := 0;
1466 end;
1468 function TPlayer.GetWeaponPref(Weapon: Byte) : Byte;
1469 begin
1470 if (Weapon > WP_LAST + 1) then
1471 result := 0
1472 else if (FWeapPreferences[Weapon] > WP_LAST + 1) then
1473 result := 0
1474 else
1475 result := FWeapPreferences[Weapon];
1476 end;
1478 function TPlayer.GetMorePrefered() : Byte;
1479 var
1480 testedWeap, i: Byte;
1481 begin
1482 testedWeap := FCurrWeap;
1483 for i := WP_FIRST to WP_LAST do
1484 if FWeapon[i] and maySwitch(i) and (FWeapPreferences[i] > FWeapPreferences[testedWeap]) then
1485 testedWeap := i;
1486 if (R_BERSERK in FRulez) and (FWeapPreferences[WP_LAST + 1] > FWeapPreferences[testedWeap]) then
1487 testedWeap := WEAPON_KASTET;
1488 result := testedWeap;
1489 end;
1491 function TPlayer.maySwitch(Weapon: Byte) : Boolean;
1492 begin
1493 result := true;
1494 if (Weapon = WEAPON_KASTET) and (FSkipFist <> 0) then
1495 begin
1496 if (FSkipFist = 1) and (not (R_BERSERK in FRulez)) then
1497 result := false;
1498 end
1499 else if (FSwitchToEmpty = 0) and (not hasAmmoForShooting(Weapon)) then
1500 result := false;
1501 end;
1503 procedure TPlayer.SwitchTeam;
1504 begin
1505 if g_Game_IsClient then
1506 Exit;
1507 if not (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then Exit;
1509 if gGameOn and FAlive then
1510 Kill(K_SIMPLEKILL, FUID, HIT_SELF);
1512 if FTeam = TEAM_RED then
1513 begin
1514 ChangeTeam(TEAM_BLUE);
1515 g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_BLUE], [FName]), True);
1516 if g_Game_IsNet then
1517 MH_SEND_GameEvent(NET_EV_CHANGE_TEAM, TEAM_BLUE, FName);
1518 end
1519 else
1520 begin
1521 ChangeTeam(TEAM_RED);
1522 g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_RED], [FName]), True);
1523 if g_Game_IsNet then
1524 MH_SEND_GameEvent(NET_EV_CHANGE_TEAM, TEAM_RED, FName);
1525 end;
1526 FPreferredTeam := FTeam;
1527 end;
1529 procedure TPlayer.ChangeTeam(Team: Byte);
1530 var
1531 OldTeam: Byte;
1532 begin
1533 OldTeam := FTeam;
1534 FTeam := Team;
1535 case Team of
1536 TEAM_RED, TEAM_BLUE:
1537 FModel.Color := TEAMCOLOR[Team];
1538 else
1539 FModel.Color := FColor;
1540 end;
1541 if (FTeam <> OldTeam) and g_Game_IsNet and g_Game_IsServer then
1542 MH_SEND_PlayerStats(FUID);
1543 end;
1546 procedure TPlayer.CollideItem();
1547 var
1548 i: Integer;
1549 r: Boolean;
1550 begin
1551 if gItems = nil then Exit;
1552 if not FAlive then Exit;
1554 for i := 0 to High(gItems) do
1555 with gItems[i] do
1556 begin
1557 if (ItemType <> ITEM_NONE) and alive then
1558 if g_Obj_Collide(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width,
1559 PLAYER_RECT.Height, @Obj) then
1560 begin
1561 if not PickItem(ItemType, gItems[i].Respawnable, r) then Continue;
1563 if ItemType in [ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_INVUL] then
1564 g_Sound_PlayExAt('SOUND_ITEM_GETRULEZ', FObj.X, FObj.Y)
1565 else if ItemType in [ITEM_MEDKIT_SMALL, ITEM_MEDKIT_LARGE, ITEM_MEDKIT_BLACK] then
1566 g_Sound_PlayExAt('SOUND_ITEM_GETMED', FObj.X, FObj.Y)
1567 else g_Sound_PlayExAt('SOUND_ITEM_GETITEM', FObj.X, FObj.Y);
1569 // Надо убрать с карты, если это не ключ, которым нужно поделится с другим игроком:
1570 if r and not ((ItemType in [ITEM_KEY_RED, ITEM_KEY_GREEN, ITEM_KEY_BLUE]) and
1571 (gGameSettings.GameType = GT_SINGLE) and
1572 (g_Player_GetCount() > 1)) then
1573 if not Respawnable then g_Items_Remove(i) else g_Items_Pick(i);
1574 end;
1575 end;
1576 end;
1579 function TPlayer.CollideLevel(XInc, YInc: Integer): Boolean;
1580 begin
1581 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
1582 PLAYER_RECT.Width, PLAYER_RECT.Height, PANEL_WALL,
1583 False);
1584 end;
1586 constructor TPlayer.Create();
1587 begin
1588 viewPortX := 0;
1589 viewPortY := 0;
1590 viewPortW := 0;
1591 viewPortH := 0;
1592 mEDamageType := HIT_SOME;
1594 FIamBot := False;
1595 FDummy := False;
1596 FSpawned := False;
1598 FSawSound := TPlayableSound.Create();
1599 FSawSoundIdle := TPlayableSound.Create();
1600 FSawSoundHit := TPlayableSound.Create();
1601 FSawSoundSelect := TPlayableSound.Create();
1602 FFlameSoundOn := TPlayableSound.Create();
1603 FFlameSoundOff := TPlayableSound.Create();
1604 FFlameSoundWork := TPlayableSound.Create();
1605 FJetSoundFly := TPlayableSound.Create();
1606 FJetSoundOn := TPlayableSound.Create();
1607 FJetSoundOff := TPlayableSound.Create();
1609 FSawSound.SetByName('SOUND_WEAPON_FIRESAW');
1610 FSawSoundIdle.SetByName('SOUND_WEAPON_IDLESAW');
1611 FSawSoundHit.SetByName('SOUND_WEAPON_HITSAW');
1612 FSawSoundSelect.SetByName('SOUND_WEAPON_SELECTSAW');
1613 FFlameSoundOn.SetByName('SOUND_WEAPON_FLAMEON');
1614 FFlameSoundOff.SetByName('SOUND_WEAPON_FLAMEOFF');
1615 FFlameSoundWork.SetByName('SOUND_WEAPON_FLAMEWORK');
1616 FJetSoundFly.SetByName('SOUND_PLAYER_JETFLY');
1617 FJetSoundOn.SetByName('SOUND_PLAYER_JETON');
1618 FJetSoundOff.SetByName('SOUND_PLAYER_JETOFF');
1620 FSpectatePlayer := -1;
1621 FClientID := -1;
1622 FPing := 0;
1623 FLoss := 0;
1624 FSavedStateNum := -1;
1625 {$IFDEF ENABLE_SHELLS}
1626 FShellTimer := -1;
1627 {$ENDIF}
1628 FFireTime := 0;
1629 FFirePainTime := 0;
1630 FFireAttacker := 0;
1631 FHandicap := 100;
1633 {$IFDEF ENABLE_CORPSES}
1634 FCorpse := -1;
1635 {$ENDIF}
1637 FActualModelName := 'doomer';
1639 g_Obj_Init(@FObj);
1640 FObj.Rect := PLAYER_RECT;
1642 FBFGFireCounter := -1;
1643 FJustTeleported := False;
1644 FNetTime := 0;
1646 FWaitForFirstSpawn := false;
1647 FPunchAnim := TAnimationState.Create(False, 1, 4);
1648 FPunchAnim.Disable;
1650 resetWeaponQueue();
1651 end;
1653 procedure TPlayer.positionChanged (); inline;
1654 begin
1655 end;
1657 procedure TPlayer.doDamage (v: Integer);
1658 begin
1659 if (v <= 0) then exit;
1660 if (v > 32767) then v := 32767;
1661 Damage(v, 0, 0, 0, mEDamageType);
1662 end;
1664 procedure TPlayer.Damage(value: Word; SpawnerUID: Word; vx, vy: Integer; t: Byte);
1665 var
1666 c: Word;
1667 begin
1668 if (not g_Game_IsClient) and (not FAlive) then
1669 Exit;
1671 FLastHit := t;
1673 // Неуязвимость не спасает от ловушек:
1674 if ((t = HIT_TRAP) or (t = HIT_SELF)) and (not FGodMode) then
1675 begin
1676 if not g_Game_IsClient then
1677 begin
1678 FArmor := 0;
1679 if t = HIT_TRAP then
1680 begin
1681 // Ловушка убивает сразу:
1682 FHealth := -100;
1683 Kill(K_EXTRAHARDKILL, SpawnerUID, t);
1684 end;
1685 if t = HIT_SELF then
1686 begin
1687 // Самоубийство:
1688 FHealth := 0;
1689 Kill(K_SIMPLEKILL, SpawnerUID, t);
1690 end;
1691 end;
1692 // Обнулить действия примочек, чтобы фон пропал
1693 FMegaRulez[MR_SUIT] := 0;
1694 FMegaRulez[MR_INVUL] := 0;
1695 FMegaRulez[MR_INVIS] := 0;
1696 FSpawnInvul := 0;
1697 FBerserk := 0;
1698 end;
1700 // Но от остального спасает:
1701 if FMegaRulez[MR_INVUL] >= gTime then
1702 Exit;
1704 // Чит-код "ГОРЕЦ":
1705 if FGodMode then
1706 Exit;
1708 // Если есть урон своим, или ранил сам себя, или тебя ранил противник:
1709 if LongBool(gGameSettings.Options and GAME_OPTION_TEAMDAMAGE) or
1710 (SpawnerUID = FUID) or
1711 (not SameTeam(FUID, SpawnerUID)) then
1712 begin
1713 FLastSpawnerUID := SpawnerUID;
1715 // Кровь (пузырьки, если в воде):
1716 if gBloodCount > 0 then
1717 begin
1718 c := Min(value, 200)*gBloodCount + Random(Min(value, 200) div 2);
1719 if value div 4 <= c then
1720 c := c - (value div 4)
1721 else
1722 c := 0;
1724 if (t = HIT_SOME) and (vx = 0) and (vy = 0) then
1725 MakeBloodSimple(c)
1726 else
1727 case t of
1728 HIT_TRAP, HIT_ACID, HIT_FLAME, HIT_SELF: MakeBloodSimple(c);
1729 HIT_BFG, HIT_ROCKET, HIT_SOME: MakeBloodVector(c, vx, vy);
1730 end;
1732 {$IFDEF ENABLE_GFX}
1733 if t = HIT_WATER then
1734 begin
1735 g_GFX_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
1736 FObj.Y+PLAYER_RECT.Y-4, value div 2, 8, 4);
1737 if Random(2) = 0
1738 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
1739 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
1740 end;
1741 {$ENDIF}
1742 end;
1744 // Буфер урона:
1745 if FAlive then
1746 Inc(FDamageBuffer, value);
1748 // Вспышка боли:
1749 if gFlash <> 0 then
1750 FPain := FPain + value;
1751 end;
1753 if g_Game_IsServer and g_Game_IsNet then
1754 begin
1755 MH_SEND_PlayerDamage(FUID, t, SpawnerUID, value, vx, vy);
1756 MH_SEND_PlayerStats(FUID);
1757 MH_SEND_PlayerPos(False, FUID);
1758 end;
1759 end;
1761 function TPlayer.Heal(value: Word; Soft: Boolean): Boolean;
1762 begin
1763 Result := False;
1764 if g_Game_IsClient then
1765 Exit;
1766 if not FAlive then
1767 Exit;
1769 if Soft and (FHealth < PLAYER_HP_SOFT) then
1770 begin
1771 IncMax(FHealth, value, PLAYER_HP_SOFT);
1772 Result := True;
1773 end;
1774 if (not Soft) and (FHealth < PLAYER_HP_LIMIT) then
1775 begin
1776 IncMax(FHealth, value, PLAYER_HP_LIMIT);
1777 Result := True;
1778 end;
1780 if Result and g_Game_IsServer and g_Game_IsNet then
1781 MH_SEND_PlayerStats(FUID);
1782 end;
1784 destructor TPlayer.Destroy();
1785 begin
1786 if (gPlayer1 <> nil) and (gPlayer1.FUID = FUID) then
1787 gPlayer1 := nil;
1788 if (gPlayer2 <> nil) and (gPlayer2.FUID = FUID) then
1789 gPlayer2 := nil;
1791 FSawSound.Free();
1792 FSawSoundIdle.Free();
1793 FSawSoundHit.Free();
1794 FSawSoundSelect.Free();
1795 FFlameSoundOn.Free();
1796 FFlameSoundOff.Free();
1797 FFlameSoundWork.Free();
1798 FJetSoundFly.Free();
1799 FJetSoundOn.Free();
1800 FJetSoundOff.Free();
1801 FModel.Free();
1802 FPunchAnim.Free();
1804 inherited;
1805 end;
1807 procedure TPlayer.DoPunch();
1808 begin
1809 FPunchAnim.Reset;
1810 FPunchAnim.Enable;
1811 end;
1813 procedure TPlayer.Fire();
1814 var
1815 f, DidFire: Boolean;
1816 wx, wy, xd, yd: Integer;
1817 locobj: TObj;
1818 begin
1819 if g_Game_IsClient then Exit;
1820 // FBFGFireCounter - время перед выстрелом (для BFG)
1821 // FReloading - время после выстрела (для всего)
1823 if FSpectator then
1824 begin
1825 Respawn(False);
1826 Exit;
1827 end;
1829 if FReloading[FCurrWeap] <> 0 then Exit;
1831 DidFire := False;
1833 f := False;
1834 wx := FObj.X+WEAPONPOINT[FDirection].X;
1835 wy := FObj.Y+WEAPONPOINT[FDirection].Y;
1836 xd := wx+IfThen(FDirection = TDirection.D_LEFT, -30, 30);
1837 yd := wy+firediry();
1839 case FCurrWeap of
1840 WEAPON_KASTET:
1841 begin
1842 DoPunch();
1843 if R_BERSERK in FRulez then
1844 begin
1845 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
1846 locobj.X := FObj.X+FObj.Rect.X;
1847 locobj.Y := FObj.Y+FObj.Rect.Y;
1848 locobj.rect.X := 0;
1849 locobj.rect.Y := 0;
1850 locobj.rect.Width := 39;
1851 locobj.rect.Height := 52;
1852 locobj.Vel.X := (xd-wx) div 2;
1853 locobj.Vel.Y := (yd-wy) div 2;
1854 locobj.Accel.X := xd-wx;
1855 locobj.Accel.y := yd-wy;
1857 if g_Weapon_Hit(@locobj, 50, FUID, HIT_SOME) <> 0 then
1858 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj.X, FObj.Y)
1859 else
1860 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj.X, FObj.Y);
1862 if (gFlash = 1) and (FPain < 50) then FPain := min(FPain + 25, 50);
1863 end
1864 else
1865 begin
1866 g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 3, FUID);
1867 end;
1869 DidFire := True;
1870 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
1871 end;
1873 WEAPON_SAW:
1874 begin
1875 if g_Weapon_chainsaw(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
1876 IfThen(gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF], 9, 3), FUID) <> 0 then
1877 begin
1878 FSawSoundSelect.Stop();
1879 FSawSound.Stop();
1880 FSawSoundHit.PlayAt(FObj.X, FObj.Y);
1881 end
1882 else if not FSawSoundHit.IsPlaying() then
1883 begin
1884 FSawSoundSelect.Stop();
1885 FSawSound.PlayAt(FObj.X, FObj.Y);
1886 end;
1888 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
1889 DidFire := True;
1890 f := True;
1891 end;
1893 WEAPON_PISTOL:
1894 if FAmmo[A_BULLETS] > 0 then
1895 begin
1896 g_Weapon_pistol(wx, wy, xd, yd, FUID);
1897 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
1898 Dec(FAmmo[A_BULLETS]);
1899 FFireAngle := FAngle;
1900 f := True;
1901 DidFire := True;
1902 {$IFDEF ENABLE_SHELLS}
1903 g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_BULLET);
1904 {$ENDIF}
1905 end;
1907 WEAPON_SHOTGUN1:
1908 if FAmmo[A_SHELLS] > 0 then
1909 begin
1910 g_Weapon_shotgun(wx, wy, xd, yd, FUID);
1911 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', wx, wy);
1912 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
1913 Dec(FAmmo[A_SHELLS]);
1914 FFireAngle := FAngle;
1915 f := True;
1916 DidFire := True;
1917 {$IFDEF ENABLE_SHELLS}
1918 FShellTimer := 10;
1919 FShellType := SHELL_SHELL;
1920 {$ENDIF}
1921 end;
1923 WEAPON_SHOTGUN2:
1924 if FAmmo[A_SHELLS] >= 2 then
1925 begin
1926 g_Weapon_dshotgun(wx, wy, xd, yd, FUID);
1927 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
1928 Dec(FAmmo[A_SHELLS], 2);
1929 FFireAngle := FAngle;
1930 f := True;
1931 DidFire := True;
1932 {$IFDEF ENABLE_SHELLS}
1933 FShellTimer := 13;
1934 FShellType := SHELL_DBLSHELL;
1935 {$ENDIF}
1936 end;
1938 WEAPON_CHAINGUN:
1939 if FAmmo[A_BULLETS] > 0 then
1940 begin
1941 g_Weapon_mgun(wx, wy, xd, yd, FUID);
1942 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', wx, wy);
1943 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
1944 Dec(FAmmo[A_BULLETS]);
1945 FFireAngle := FAngle;
1946 f := True;
1947 DidFire := True;
1948 {$IFDEF ENABLE_SHELLS}
1949 g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_BULLET);
1950 {$ENDIF}
1951 end;
1953 WEAPON_ROCKETLAUNCHER:
1954 if FAmmo[A_ROCKETS] > 0 then
1955 begin
1956 g_Weapon_rocket(wx, wy, xd, yd, FUID);
1957 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
1958 Dec(FAmmo[A_ROCKETS]);
1959 FFireAngle := FAngle;
1960 f := True;
1961 DidFire := True;
1962 end;
1964 WEAPON_PLASMA:
1965 if FAmmo[A_CELLS] > 0 then
1966 begin
1967 g_Weapon_plasma(wx, wy, xd, yd, FUID);
1968 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
1969 Dec(FAmmo[A_CELLS]);
1970 FFireAngle := FAngle;
1971 f := True;
1972 DidFire := True;
1973 end;
1975 WEAPON_BFG:
1976 if (FAmmo[A_CELLS] >= 40) and (FBFGFireCounter = -1) then
1977 begin
1978 FBFGFireCounter := 17;
1979 if not FNoReload then
1980 g_Sound_PlayExAt('SOUND_WEAPON_STARTFIREBFG', FObj.X, FObj.Y);
1981 Dec(FAmmo[A_CELLS], 40);
1982 DidFire := True;
1983 end;
1985 WEAPON_SUPERPULEMET:
1986 if FAmmo[A_SHELLS] > 0 then
1987 begin
1988 g_Weapon_shotgun(wx, wy, xd, yd, FUID);
1989 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx, wy);
1990 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
1991 Dec(FAmmo[A_SHELLS]);
1992 FFireAngle := FAngle;
1993 f := True;
1994 DidFire := True;
1995 {$IFDEF ENABLE_SHELLS}
1996 g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_SHELL);
1997 {$ENDIF}
1998 end;
2000 WEAPON_FLAMETHROWER:
2001 if FAmmo[A_FUEL] > 0 then
2002 begin
2003 g_Weapon_flame(wx, wy, xd, yd, FUID);
2004 FlamerOn;
2005 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2006 Dec(FAmmo[A_FUEL]);
2007 FFireAngle := FAngle;
2008 f := True;
2009 DidFire := True;
2010 end
2011 else
2012 begin
2013 FlamerOff;
2014 if g_Game_IsNet and g_Game_IsServer then MH_SEND_PlayerStats(FUID);
2015 end;
2016 end;
2018 if g_Game_IsNet then
2019 begin
2020 if DidFire then
2021 begin
2022 if FCurrWeap <> WEAPON_BFG then
2023 MH_SEND_PlayerFire(FUID, FCurrWeap, wx, wy, xd, yd, LastShotID)
2024 else
2025 if not FNoReload then
2026 MH_SEND_Sound(FObj.X, FObj.Y, 'SOUND_WEAPON_STARTFIREBFG');
2027 end;
2029 MH_SEND_PlayerStats(FUID);
2030 end;
2032 if not f then Exit;
2034 if (FAngle = 0) or (FAngle = 180) then SetAction(A_ATTACK)
2035 else if (FAngle = ANGLE_LEFTDOWN) or (FAngle = ANGLE_RIGHTDOWN) then SetAction(A_ATTACKDOWN)
2036 else if (FAngle = ANGLE_LEFTUP) or (FAngle = ANGLE_RIGHTUP) then SetAction(A_ATTACKUP);
2037 end;
2039 function TPlayer.GetAmmoByWeapon(Weapon: Byte): Word;
2040 begin
2041 case Weapon of
2042 WEAPON_PISTOL, WEAPON_CHAINGUN: Result := FAmmo[A_BULLETS];
2043 WEAPON_SHOTGUN1, WEAPON_SHOTGUN2, WEAPON_SUPERPULEMET: Result := FAmmo[A_SHELLS];
2044 WEAPON_ROCKETLAUNCHER: Result := FAmmo[A_ROCKETS];
2045 WEAPON_PLASMA, WEAPON_BFG: Result := FAmmo[A_CELLS];
2046 WEAPON_FLAMETHROWER: Result := FAmmo[A_FUEL];
2047 else Result := 0;
2048 end;
2049 end;
2051 function TPlayer.HeadInLiquid(XInc, YInc: Integer): Boolean;
2052 begin
2053 Result := g_Map_CollidePanel(FObj.X+PLAYER_HEADRECT.X+XInc, FObj.Y+PLAYER_HEADRECT.Y+YInc,
2054 PLAYER_HEADRECT.Width, PLAYER_HEADRECT.Height,
2055 PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, True);
2056 end;
2058 procedure TPlayer.FlamerOn;
2059 begin
2060 FFlameSoundOff.Stop();
2061 FFlameSoundOff.SetPosition(0);
2062 if FFlaming then
2063 begin
2064 if (not FFlameSoundOn.IsPlaying()) and (not FFlameSoundWork.IsPlaying()) then
2065 FFlameSoundWork.PlayAt(FObj.X, FObj.Y);
2066 end
2067 else
2068 begin
2069 FFlameSoundOn.PlayAt(FObj.X, FObj.Y);
2070 FFlaming := True;
2071 end;
2072 end;
2074 procedure TPlayer.FlamerOff;
2075 begin
2076 if FFlaming then
2077 begin
2078 FFlameSoundOn.Stop();
2079 FFlameSoundOn.SetPosition(0);
2080 FFlameSoundWork.Stop();
2081 FFlameSoundWork.SetPosition(0);
2082 FFlameSoundOff.PlayAt(FObj.X, FObj.Y);
2083 FFlaming := False;
2084 end;
2085 end;
2087 procedure TPlayer.JetpackOn;
2088 begin
2089 FJetSoundFly.Stop;
2090 FJetSoundOff.Stop;
2091 FJetSoundOn.SetPosition(0);
2092 FJetSoundOn.PlayAt(FObj.X, FObj.Y);
2093 FlySmoke(8);
2094 end;
2096 procedure TPlayer.JetpackOff;
2097 begin
2098 FJetSoundFly.Stop;
2099 FJetSoundOn.Stop;
2100 FJetSoundOff.SetPosition(0);
2101 FJetSoundOff.PlayAt(FObj.X, FObj.Y);
2102 end;
2104 procedure TPlayer.CatchFire(Attacker: Word; Timeout: Integer = PLAYER_BURN_TIME);
2105 begin
2106 if Timeout <= 0 then
2107 exit;
2108 if (FMegaRulez[MR_SUIT] > gTime) or (FMegaRulez[MR_INVUL] > gTime) then
2109 exit; // Не загораемся когда есть защита
2110 if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2) then
2111 exit; // Не подгораем в воде на всякий случай
2112 if FFireTime <= 0 then
2113 g_Sound_PlayExAt('SOUND_IGNITE', FObj.X, FObj.Y);
2114 FFireTime := Timeout;
2115 FFireAttacker := Attacker;
2116 if g_Game_IsNet and g_Game_IsServer then
2117 MH_SEND_PlayerStats(FUID);
2118 end;
2120 procedure TPlayer.Jump();
2121 begin
2122 if gFly or FJetpack then
2123 begin
2124 // Полет (чит-код или джетпак):
2125 if FObj.Vel.Y > -VEL_FLY then
2126 FObj.Vel.Y := FObj.Vel.Y - 3;
2127 if FJetpack then
2128 begin
2129 if FJetFuel > 0 then
2130 Dec(FJetFuel);
2131 if (FJetFuel < 1) and g_Game_IsServer then
2132 begin
2133 FJetpack := False;
2134 JetpackOff;
2135 if g_Game_IsNet then
2136 MH_SEND_PlayerStats(FUID);
2137 end;
2138 end;
2139 Exit;
2140 end;
2142 // Не включать джетпак в режиме прохождения сквозь стены
2143 if FGhost then
2144 FCanJetpack := False;
2146 // Прыгаем или всплываем:
2147 if (CollideLevel(0, 1) or
2148 g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y+36, PLAYER_RECT.Width,
2149 PLAYER_RECT.Height-33, PANEL_STEP, False)
2150 ) and (FObj.Accel.Y = 0) then // Не прыгать, если есть вертикальное ускорение
2151 begin
2152 FObj.Vel.Y := -VEL_JUMP;
2153 FCanJetpack := False;
2154 end
2155 else
2156 begin
2157 if BodyInLiquid(0, 0) then
2158 FObj.Vel.Y := -VEL_SW
2159 else if (FJetFuel > 0) and FCanJetpack and
2160 g_Game_IsServer and (not g_Obj_CollideLiquid(@FObj, 0, 0)) then
2161 begin
2162 FJetpack := True;
2163 JetpackOn;
2164 if g_Game_IsNet then
2165 MH_SEND_PlayerStats(FUID);
2166 end;
2167 end;
2168 end;
2170 procedure TPlayer.Kill(KillType: Byte; SpawnerUID: Word; t: Byte);
2171 var
2172 a, i, k, ab, ar: Byte;
2173 s: String;
2174 mon: TMonster;
2175 plr: TPlayer;
2176 srv, netsrv: Boolean;
2177 DoFrags: Boolean;
2178 OldLR: Byte;
2179 KP: TPlayer;
2180 it: PItem;
2182 procedure PushItem(t: Byte);
2183 var
2184 id: DWORD;
2185 begin
2186 id := g_Items_Create(FObj.X, FObj.Y, t, True, False);
2187 it := g_Items_ByIdx(id);
2188 if KillType = K_EXTRAHARDKILL then // -7..+7; -8..0
2189 begin
2190 g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-7+Random(15),
2191 (FObj.Vel.Y div 2)-Random(9));
2192 it.positionChanged(); // this updates spatial accelerators
2193 end
2194 else
2195 begin
2196 if KillType = K_HARDKILL then // -5..+5; -5..0
2197 begin
2198 g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-5+Random(11),
2199 (FObj.Vel.Y div 2)-Random(6));
2200 end
2201 else // -3..+3; -3..0
2202 begin
2203 g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-3+Random(7),
2204 (FObj.Vel.Y div 2)-Random(4));
2205 end;
2206 it.positionChanged(); // this updates spatial accelerators
2207 end;
2209 if g_Game_IsNet and g_Game_IsServer then
2210 MH_SEND_ItemSpawn(True, id);
2211 end;
2213 begin
2214 DoFrags := (gGameSettings.MaxLives = 0) or (gGameSettings.GameMode = GM_COOP);
2215 Srv := g_Game_IsServer;
2216 Netsrv := g_Game_IsServer and g_Game_IsNet;
2217 if Srv then FDeath := FDeath + 1;
2218 if FAlive then
2219 begin
2220 if FGhost then
2221 FGhost := False;
2222 if not FPhysics then
2223 FPhysics := True;
2224 FAlive := False;
2225 end;
2227 {$IFDEF ENABLE_SHELLS}
2228 FShellTimer := -1;
2229 {$ENDIF}
2231 if (gGameSettings.MaxLives > 0) and Srv and (gLMSRespawn = LMS_RESPAWN_NONE) then
2232 begin
2233 if FLives > 0 then FLives := FLives - 1;
2234 if FLives = 0 then FNoRespawn := True;
2235 end;
2237 // Номер типа смерти:
2238 a := 1;
2239 case KillType of
2240 K_SIMPLEKILL: a := 1;
2241 K_HARDKILL: a := 2;
2242 K_EXTRAHARDKILL: a := 3;
2243 K_FALLKILL: a := 4;
2244 end;
2246 // Звук смерти:
2247 if not FModel.PlaySound(MODELSOUND_DIE, a, FObj.X, FObj.Y) then
2248 for i := 1 to 3 do
2249 if FModel.PlaySound(MODELSOUND_DIE, i, FObj.X, FObj.Y) then
2250 Break;
2252 // Время респауна:
2253 if Srv then
2254 case KillType of
2255 K_SIMPLEKILL:
2256 FTime[T_RESPAWN] := gTime + TIME_RESPAWN1;
2257 K_HARDKILL:
2258 FTime[T_RESPAWN] := gTime + TIME_RESPAWN2;
2259 K_EXTRAHARDKILL, K_FALLKILL:
2260 FTime[T_RESPAWN] := gTime + TIME_RESPAWN3;
2261 end;
2263 // Переключаем состояние:
2264 case KillType of
2265 K_SIMPLEKILL:
2266 SetAction(A_DIE1);
2267 K_HARDKILL, K_EXTRAHARDKILL:
2268 SetAction(A_DIE2);
2269 end;
2271 // Реакция монстров на смерть игрока:
2272 if (KillType <> K_FALLKILL) and (Srv) then
2273 g_Monsters_killedp();
2275 if SpawnerUID = FUID then
2276 begin // Самоубился
2277 if Srv then
2278 begin
2279 if gGameSettings.GameMode = GM_TDM then
2280 Dec(gTeamStat[FTeam].Score);
2281 if DoFrags or (gGameSettings.GameMode = GM_TDM) then
2282 begin
2283 Dec(FFrags);
2284 FLastFrag := 0;
2285 end;
2286 end;
2287 g_Console_Add(Format(_lc[I_PLAYER_KILL_SELF], [FName]), True);
2288 end
2289 else
2290 if g_GetUIDType(SpawnerUID) = UID_PLAYER then
2291 begin // Убит другим игроком
2292 KP := g_Player_Get(SpawnerUID);
2293 if (KP <> nil) and Srv then
2294 begin
2295 if (DoFrags or (gGameSettings.GameMode = GM_TDM)) then
2296 if SameTeam(FUID, SpawnerUID) then
2297 begin
2298 Dec(KP.FFrags);
2299 KP.FLastFrag := 0;
2300 end else
2301 begin
2302 Inc(KP.FFrags);
2303 KP.FragCombo();
2304 end;
2306 if (gGameSettings.GameMode = GM_TDM) and DoFrags then
2307 Inc(gTeamStat[KP.Team].Score,
2308 IfThen(SameTeam(FUID, SpawnerUID), -1, 1));
2310 if netsrv then MH_SEND_PlayerStats(SpawnerUID);
2311 end;
2313 plr := g_Player_Get(SpawnerUID);
2314 if plr = nil then
2315 s := '?'
2316 else
2317 s := plr.FName;
2319 case KillType of
2320 K_HARDKILL:
2321 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_2],
2322 [FName, s]),
2323 gShowKillMsg);
2324 K_EXTRAHARDKILL:
2325 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_1],
2326 [FName, s]),
2327 gShowKillMsg);
2328 else
2329 g_Console_Add(Format(_lc[I_PLAYER_KILL],
2330 [FName, s]),
2331 gShowKillMsg);
2332 end;
2333 end
2334 else if g_GetUIDType(SpawnerUID) = UID_MONSTER then
2335 begin // Убит монстром
2336 mon := g_Monsters_ByUID(SpawnerUID);
2337 if mon = nil then
2338 s := '?'
2339 else
2340 s := g_Mons_GetKilledByTypeId(mon.MonsterType);
2342 case KillType of
2343 K_HARDKILL:
2344 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_2],
2345 [FName, s]),
2346 gShowKillMsg);
2347 K_EXTRAHARDKILL:
2348 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_1],
2349 [FName, s]),
2350 gShowKillMsg);
2351 else
2352 g_Console_Add(Format(_lc[I_PLAYER_KILL],
2353 [FName, s]),
2354 gShowKillMsg);
2355 end;
2356 end
2357 else // Особые типы смерти
2358 case t of
2359 HIT_DISCON: ;
2360 HIT_SELF: g_Console_Add(Format(_lc[I_PLAYER_KILL_SELF], [FName]), True);
2361 HIT_FALL: g_Console_Add(Format(_lc[I_PLAYER_KILL_FALL], [FName]), True);
2362 HIT_WATER: g_Console_Add(Format(_lc[I_PLAYER_KILL_WATER], [FName]), True);
2363 HIT_ACID: g_Console_Add(Format(_lc[I_PLAYER_KILL_ACID], [FName]), True);
2364 HIT_TRAP: g_Console_Add(Format(_lc[I_PLAYER_KILL_TRAP], [FName]), True);
2365 else g_Console_Add(Format(_lc[I_PLAYER_DIED], [FName]), True);
2366 end;
2368 if Srv then
2369 begin
2370 // Выброс оружия:
2371 for a := WP_FIRST to WP_LAST do
2372 if FWeapon[a] then
2373 begin
2374 case a of
2375 WEAPON_SAW: i := ITEM_WEAPON_SAW;
2376 WEAPON_SHOTGUN1: i := ITEM_WEAPON_SHOTGUN1;
2377 WEAPON_SHOTGUN2: i := ITEM_WEAPON_SHOTGUN2;
2378 WEAPON_CHAINGUN: i := ITEM_WEAPON_CHAINGUN;
2379 WEAPON_ROCKETLAUNCHER: i := ITEM_WEAPON_ROCKETLAUNCHER;
2380 WEAPON_PLASMA: i := ITEM_WEAPON_PLASMA;
2381 WEAPON_BFG: i := ITEM_WEAPON_BFG;
2382 WEAPON_SUPERPULEMET: i := ITEM_WEAPON_SUPERPULEMET;
2383 WEAPON_FLAMETHROWER: i := ITEM_WEAPON_FLAMETHROWER;
2384 else i := 0;
2385 end;
2387 if i <> 0 then
2388 PushItem(i);
2389 end;
2391 // Выброс рюкзака:
2392 if R_ITEM_BACKPACK in FRulez then
2393 PushItem(ITEM_AMMO_BACKPACK);
2395 // Выброс ракетного ранца:
2396 if FJetFuel > 0 then
2397 PushItem(ITEM_JETPACK);
2399 // Выброс ключей:
2400 if (not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) or
2401 (not LongBool(gGameSettings.Options and GAME_OPTION_DMKEYS)) then
2402 begin
2403 if R_KEY_RED in FRulez then
2404 PushItem(ITEM_KEY_RED);
2406 if R_KEY_GREEN in FRulez then
2407 PushItem(ITEM_KEY_GREEN);
2409 if R_KEY_BLUE in FRulez then
2410 PushItem(ITEM_KEY_BLUE);
2411 end;
2413 // Выброс флага:
2414 DropFlag(KillType = K_FALLKILL);
2415 end;
2417 {$IFDEF ENABLE_CORPSES}
2418 FCorpse := g_Corpses_Create(Self);
2419 {$ENDIF}
2421 if Srv and (gGameSettings.MaxLives > 0) and FNoRespawn and
2422 (gLMSRespawn = LMS_RESPAWN_NONE) then
2423 begin
2424 a := 0;
2425 k := 0;
2426 ar := 0;
2427 ab := 0;
2428 for i := Low(gPlayers) to High(gPlayers) do
2429 begin
2430 if gPlayers[i] = nil then continue;
2431 if (not gPlayers[i].FNoRespawn) and (not gPlayers[i].FSpectator) then
2432 begin
2433 Inc(a);
2434 if gPlayers[i].FTeam = TEAM_RED then Inc(ar)
2435 else if gPlayers[i].FTeam = TEAM_BLUE then Inc(ab);
2436 k := i;
2437 end;
2438 end;
2440 OldLR := gLMSRespawn;
2441 if (gGameSettings.GameMode = GM_COOP) then
2442 begin
2443 if (a = 0) then
2444 begin
2445 // everyone is dead, restart the map
2446 g_Game_Message(_lc[I_MESSAGE_LMS_LOSE], 144);
2447 if Netsrv then
2448 MH_SEND_GameEvent(NET_EV_LMS_LOSE);
2449 gLMSRespawn := LMS_RESPAWN_FINAL;
2450 gLMSRespawnTime := gTime + 5000;
2451 end
2452 else if (a = 1) then
2453 begin
2454 if (gPlayers[k] <> nil) and not (gPlayers[k] is TBot) then
2455 if (gPlayers[k] = gPlayer1) or
2456 (gPlayers[k] = gPlayer2) then
2457 g_Console_Add('*** ' + _lc[I_MESSAGE_LMS_SURVIVOR] + ' ***', True)
2458 else if Netsrv and (gPlayers[k].FClientID >= 0) then
2459 MH_SEND_GameEvent(NET_EV_LMS_SURVIVOR, 0, 'N', gPlayers[k].FClientID);
2460 end;
2461 end
2462 else if (gGameSettings.GameMode = GM_TDM) then
2463 begin
2464 if (ab = 0) and (ar <> 0) then
2465 begin
2466 // blu team ded
2467 g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 144);
2468 if Netsrv then
2469 MH_SEND_GameEvent(NET_EV_TLMS_WIN, TEAM_RED);
2470 Inc(gTeamStat[TEAM_RED].Score);
2471 gLMSRespawn := LMS_RESPAWN_FINAL;
2472 gLMSRespawnTime := gTime + 5000;
2473 end
2474 else if (ar = 0) and (ab <> 0) then
2475 begin
2476 // red team ded
2477 g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 144);
2478 if Netsrv then
2479 MH_SEND_GameEvent(NET_EV_TLMS_WIN, TEAM_BLUE);
2480 Inc(gTeamStat[TEAM_BLUE].Score);
2481 gLMSRespawn := LMS_RESPAWN_FINAL;
2482 gLMSRespawnTime := gTime + 5000;
2483 end
2484 else if (ar = 0) and (ab = 0) then
2485 begin
2486 // everyone ded
2487 g_Game_Message(_lc[I_GAME_WIN_DRAW], 144);
2488 if Netsrv then
2489 MH_SEND_GameEvent(NET_EV_LMS_DRAW, 0, FName);
2490 gLMSRespawn := LMS_RESPAWN_FINAL;
2491 gLMSRespawnTime := gTime + 5000;
2492 end;
2493 end
2494 else if (gGameSettings.GameMode = GM_DM) then
2495 begin
2496 if (a = 1) then
2497 begin
2498 if gPlayers[k] <> nil then
2499 with gPlayers[k] do
2500 begin
2501 // survivor is the winner
2502 g_Game_Message(Format(_lc[I_MESSAGE_LMS_WIN], [AnsiUpperCase(FName)]), 144);
2503 if Netsrv then
2504 MH_SEND_GameEvent(NET_EV_LMS_WIN, 0, FName);
2505 Inc(FFrags);
2506 end;
2507 gLMSRespawn := LMS_RESPAWN_FINAL;
2508 gLMSRespawnTime := gTime + 5000;
2509 end
2510 else if (a = 0) then
2511 begin
2512 // everyone is dead, restart the map
2513 g_Game_Message(_lc[I_GAME_WIN_DRAW], 144);
2514 if Netsrv then
2515 MH_SEND_GameEvent(NET_EV_LMS_DRAW, 0, FName);
2516 gLMSRespawn := LMS_RESPAWN_FINAL;
2517 gLMSRespawnTime := gTime + 5000;
2518 end;
2519 end;
2520 if srv and (OldLR = LMS_RESPAWN_NONE) and (gLMSRespawn > LMS_RESPAWN_NONE) then
2521 begin
2522 if NetMode = NET_SERVER then
2523 MH_SEND_GameEvent(NET_EV_LMS_WARMUP, gLMSRespawnTime - gTime)
2524 else
2525 g_Console_Add(Format(_lc[I_MSG_WARMUP_START], [(gLMSRespawnTime - gTime) div 1000]), True);
2526 end;
2527 end;
2529 if Netsrv then
2530 begin
2531 MH_SEND_PlayerStats(FUID);
2532 MH_SEND_PlayerDeath(FUID, KillType, t, SpawnerUID);
2533 if gGameSettings.GameMode = GM_TDM then MH_SEND_GameStats;
2534 end;
2536 if srv and FNoRespawn then Spectate(True);
2537 FWantsInGame := True;
2538 end;
2540 function TPlayer.BodyInLiquid(XInc, YInc: Integer): Boolean;
2541 begin
2542 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc, PLAYER_RECT.Width,
2543 PLAYER_RECT.Height-20, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, False);
2544 end;
2546 function TPlayer.BodyInAcid(XInc, YInc: Integer): Boolean;
2547 begin
2548 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc, PLAYER_RECT.Width,
2549 PLAYER_RECT.Height-20, PANEL_ACID1 or PANEL_ACID2, False);
2550 end;
2552 procedure TPlayer.MakeBloodSimple(Count: Word);
2553 {$IFDEF ENABLE_GFX}
2554 var Blood: TModelBlood;
2555 {$ENDIF}
2556 begin
2557 {$IFDEF ENABLE_GFX}
2558 Blood := SELF.FModel.GetBlood();
2559 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)+8,
2560 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
2561 Count div 2, 3, -1, 16, (PLAYER_RECT.Height*2 div 3),
2562 Blood.R, Blood.G, Blood.B, Blood.Kind);
2563 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-8,
2564 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
2565 Count div 2, -3, -1, 16, (PLAYER_RECT.Height*2) div 3,
2566 Blood.R, Blood.G, Blood.B, Blood.Kind);
2567 {$ENDIF}
2568 end;
2570 procedure TPlayer.MakeBloodVector(Count: Word; VelX, VelY: Integer);
2571 {$IFDEF ENABLE_GFX}
2572 var Blood: TModelBlood;
2573 {$ENDIF}
2574 begin
2575 {$IFDEF ENABLE_GFX}
2576 Blood := SELF.FModel.GetBlood();
2577 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
2578 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
2579 Count, VelX, VelY, 16, (PLAYER_RECT.Height*2) div 3,
2580 Blood.R, Blood.G, Blood.B, Blood.Kind);
2581 {$ENDIF}
2582 end;
2584 procedure TPlayer.ProcessWeaponAction(Action: Byte);
2585 begin
2586 if g_Game_IsClient then Exit;
2587 case Action of
2588 WP_PREV: PrevWeapon();
2589 WP_NEXT: NextWeapon();
2590 end;
2591 end;
2593 procedure TPlayer.QueueWeaponSwitch(Weapon: Byte);
2594 begin
2595 if g_Game_IsClient then Exit;
2596 if Weapon > High(FWeapon) then Exit;
2597 FNextWeap := FNextWeap or (1 shl Weapon);
2598 end;
2600 procedure TPlayer.resetWeaponQueue ();
2601 begin
2602 FNextWeap := 0;
2603 FNextWeapDelay := 0;
2604 end;
2606 function TPlayer.hasAmmoForWeapon (weapon: Byte): Boolean;
2607 begin
2608 result := false;
2609 case weapon of
2610 WEAPON_KASTET, WEAPON_SAW: result := true;
2611 WEAPON_SHOTGUN1, WEAPON_SHOTGUN2, WEAPON_SUPERPULEMET: result := (FAmmo[A_SHELLS] > 0);
2612 WEAPON_PISTOL, WEAPON_CHAINGUN: result := (FAmmo[A_BULLETS] > 0);
2613 WEAPON_ROCKETLAUNCHER: result := (FAmmo[A_ROCKETS] > 0);
2614 WEAPON_PLASMA, WEAPON_BFG: result := (FAmmo[A_CELLS] > 0);
2615 WEAPON_FLAMETHROWER: result := (FAmmo[A_FUEL] > 0);
2616 else result := (weapon < length(FWeapon));
2617 end;
2618 end;
2620 function TPlayer.hasAmmoForShooting (weapon: Byte): Boolean;
2621 begin
2622 result := false;
2623 case weapon of
2624 WEAPON_KASTET, WEAPON_SAW: result := true;
2625 WEAPON_SHOTGUN1, WEAPON_SUPERPULEMET: result := (FAmmo[A_SHELLS] > 0);
2626 WEAPON_SHOTGUN2: result := (FAmmo[A_SHELLS] > 1);
2627 WEAPON_PISTOL, WEAPON_CHAINGUN: result := (FAmmo[A_BULLETS] > 0);
2628 WEAPON_ROCKETLAUNCHER: result := (FAmmo[A_ROCKETS] > 0);
2629 WEAPON_PLASMA: result := (FAmmo[A_CELLS] > 0);
2630 WEAPON_BFG: result := (FAmmo[A_CELLS] >= 40);
2631 WEAPON_FLAMETHROWER: result := (FAmmo[A_FUEL] > 0);
2632 else result := (weapon < length(FWeapon));
2633 end;
2634 end;
2636 function TPlayer.shouldSwitch (weapon: Byte; hadWeapon: Boolean): Boolean;
2637 begin
2638 result := false;
2639 if (weapon > WP_LAST + 1) then
2640 begin
2641 result := false;
2642 exit;
2643 end;
2644 if (FWeapSwitchMode = 1) and not hadWeapon then
2645 result := true
2646 else if (FWeapSwitchMode = 2) then
2647 result := (FWeapPreferences[weapon] > FWeapPreferences[FCurrWeap]);
2648 end;
2650 // return 255 for "no switch"
2651 function TPlayer.getNextWeaponIndex (): Byte;
2652 var
2653 i: Word;
2654 wantThisWeapon: array[0..64] of Boolean;
2655 wwc: Integer = 0; //HACK!
2656 dir, cwi: Integer;
2657 begin
2658 result := 255; // default result: "no switch"
2659 //e_LogWriteFln('FSWITCHTOEMPTY: %s', [FSwitchToEmpty], TMsgType.Notify);
2660 // had weapon cycling on previous frame? remove that flag
2661 if (FNextWeap and $2000) <> 0 then
2662 begin
2663 FNextWeap := FNextWeap and $1FFF;
2664 FNextWeapDelay := 0;
2665 end;
2666 // cycling has priority
2667 if (FNextWeap and $C000) <> 0 then
2668 begin
2669 if (FNextWeap and $8000) <> 0 then
2670 dir := 1
2671 else
2672 dir := -1;
2673 FNextWeap := FNextWeap or $2000; // we need this
2674 if FNextWeapDelay > 0 then
2675 exit; // cooldown time
2676 cwi := FCurrWeap;
2677 for i := 0 to High(FWeapon) do
2678 begin
2679 cwi := (cwi+length(FWeapon)+dir) mod length(FWeapon);
2680 if FWeapon[cwi] and maySwitch(cwi) then
2681 begin
2682 //e_LogWriteFln(' SWITCH: cur=%d; new=%d %s %s', [FCurrWeap, cwi, FSwitchToEmpty, hasAmmoForWeapon(cwi)], TMsgType.Notify);
2683 result := Byte(cwi);
2684 FNextWeapDelay := WEAPON_DELAY;
2685 exit;
2686 end;
2687 end;
2688 resetWeaponQueue();
2689 exit;
2690 end;
2691 // no cycling
2692 for i := 0 to High(wantThisWeapon) do
2693 wantThisWeapon[i] := false;
2694 for i := 0 to High(FWeapon) do
2695 if (FNextWeap and (1 shl i)) <> 0 then
2696 begin
2697 wantThisWeapon[i] := true;
2698 Inc(wwc);
2699 end;
2701 // exclude currently selected weapon from the set
2702 wantThisWeapon[FCurrWeap] := false;
2703 // slow down alterations a little
2704 if wwc > 1 then
2705 begin
2706 //e_WriteLog(Format(' FNextWeap=%x; delay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
2707 // more than one weapon requested, assume "alteration" and check alteration delay
2708 if FNextWeapDelay > 0 then
2709 begin
2710 FNextWeap := 0;
2711 exit;
2712 end; // yeah
2713 end;
2714 // do not reset weapon queue, it will be done in `RealizeCurrentWeapon()`
2715 // but clear all counters if no weapon should be switched
2716 if wwc < 1 then
2717 begin
2718 resetWeaponQueue();
2719 exit;
2720 end;
2721 //e_WriteLog(Format('wwc=%d', [wwc]), MSG_WARNING);
2722 // try weapons in descending order
2723 for i := High(FWeapon) downto 0 do
2724 begin
2725 if wantThisWeapon[i] and FWeapon[i] and ((wwc = 1) or hasAmmoForWeapon(i)) then
2726 begin
2727 // i found her!
2728 result := Byte(i);
2729 resetWeaponQueue();
2730 FNextWeapDelay := WEAPON_DELAY * 2; // anyway, 'cause why not
2731 //e_LogWriteFln('FOUND %s %s %s', [result, FSwitchToEmpty, hasAmmoForWeapon(i)], TMsgType.Notify);
2732 exit;
2733 end;
2734 end;
2735 // no suitable weapon found, so reset the queue, to avoid accidental "queuing" of weapon w/o ammo
2736 resetWeaponQueue();
2737 end;
2739 procedure TPlayer.RealizeCurrentWeapon();
2740 function switchAllowed (): Boolean;
2741 var
2742 i: Byte;
2743 begin
2744 result := false;
2745 if FBFGFireCounter <> -1 then
2746 exit;
2747 if FTime[T_SWITCH] > gTime then
2748 exit;
2749 for i := WP_FIRST to WP_LAST do
2750 if FReloading[i] > 0 then
2751 exit;
2752 result := true;
2753 end;
2755 var
2756 nw: Byte;
2757 begin
2758 //e_WriteLog(Format('***RealizeCurrentWeapon: FNextWeap=%x; FNextWeapDelay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
2759 //FNextWeap := FNextWeap and $1FFF;
2760 if FNextWeapDelay > 0 then Dec(FNextWeapDelay); // "alteration delay"
2762 if not switchAllowed then
2763 begin
2764 //HACK for weapon cycling
2765 if (FNextWeap and $E000) <> 0 then FNextWeap := 0;
2766 exit;
2767 end;
2769 nw := getNextWeaponIndex();
2770 //
2771 if nw = 255 then exit; // don't reset anything here
2772 if nw > High(FWeapon) then
2773 begin
2774 // don't forget to reset queue here!
2775 //e_WriteLog(' RealizeCurrentWeapon: WUTAFUUUU', MSG_WARNING);
2776 resetWeaponQueue();
2777 exit;
2778 end;
2780 if FWeapon[nw] then
2781 begin
2782 FCurrWeap := nw;
2783 FTime[T_SWITCH] := gTime+156;
2784 if FCurrWeap = WEAPON_SAW then FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
2785 FModel.SetWeapon(FCurrWeap);
2786 if g_Game_IsNet then MH_SEND_PlayerStats(FUID);
2787 end;
2788 end;
2790 procedure TPlayer.NextWeapon();
2791 begin
2792 if g_Game_IsClient then Exit;
2793 FNextWeap := $8000;
2794 end;
2796 procedure TPlayer.PrevWeapon();
2797 begin
2798 if g_Game_IsClient then Exit;
2799 FNextWeap := $4000;
2800 end;
2802 procedure TPlayer.SetWeapon(W: Byte);
2803 begin
2804 if FCurrWeap <> W then
2805 if W = WEAPON_SAW then
2806 FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
2808 FCurrWeap := W;
2809 FModel.SetWeapon(CurrWeap);
2810 resetWeaponQueue();
2811 end;
2813 function TPlayer.PickItem(ItemType: Byte; arespawn: Boolean; var remove: Boolean): Boolean;
2814 var
2815 a: Boolean;
2816 switchWeapon: Byte = 255;
2817 hadWeapon: Boolean = False;
2818 begin
2819 Result := False;
2820 if g_Game_IsClient then Exit;
2822 // a = true - место спавна предмета:
2823 a := LongBool(gGameSettings.Options and GAME_OPTION_WEAPONSTAY) and arespawn;
2824 remove := not a;
2825 case ItemType of
2826 ITEM_MEDKIT_SMALL:
2827 if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
2828 begin
2829 if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 10, PLAYER_HP_SOFT);
2830 Result := True;
2831 remove := True;
2832 FFireTime := 0;
2833 if gFlash = 2 then Inc(FPickup, 5);
2834 end;
2836 ITEM_MEDKIT_LARGE:
2837 if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
2838 begin
2839 if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 25, PLAYER_HP_SOFT);
2840 Result := True;
2841 remove := True;
2842 FFireTime := 0;
2843 if gFlash = 2 then Inc(FPickup, 5);
2844 end;
2846 ITEM_ARMOR_GREEN:
2847 if FArmor < PLAYER_AP_SOFT then
2848 begin
2849 FArmor := PLAYER_AP_SOFT;
2850 Result := True;
2851 remove := True;
2852 if gFlash = 2 then Inc(FPickup, 5);
2853 end;
2855 ITEM_ARMOR_BLUE:
2856 if FArmor < PLAYER_AP_LIMIT then
2857 begin
2858 FArmor := PLAYER_AP_LIMIT;
2859 Result := True;
2860 remove := True;
2861 if gFlash = 2 then Inc(FPickup, 5);
2862 end;
2864 ITEM_SPHERE_BLUE:
2865 if (FHealth < PLAYER_HP_LIMIT) or (FFireTime > 0) then
2866 begin
2867 if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 100, PLAYER_HP_LIMIT);
2868 Result := True;
2869 remove := True;
2870 FFireTime := 0;
2871 if gFlash = 2 then Inc(FPickup, 5);
2872 end;
2874 ITEM_SPHERE_WHITE:
2875 if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) or (FFireTime > 0) then
2876 begin
2877 if FHealth < PLAYER_HP_LIMIT then
2878 FHealth := PLAYER_HP_LIMIT;
2879 if FArmor < PLAYER_AP_LIMIT then
2880 FArmor := PLAYER_AP_LIMIT;
2881 Result := True;
2882 remove := True;
2883 FFireTime := 0;
2884 if gFlash = 2 then Inc(FPickup, 5);
2885 end;
2887 ITEM_WEAPON_SAW:
2888 if (not FWeapon[WEAPON_SAW]) or ((not arespawn) and (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) then
2889 begin
2890 hadWeapon := FWeapon[WEAPON_SAW];
2891 switchWeapon := WEAPON_SAW;
2892 FWeapon[WEAPON_SAW] := True;
2893 Result := True;
2894 if gFlash = 2 then Inc(FPickup, 5);
2895 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
2896 end;
2898 ITEM_WEAPON_SHOTGUN1:
2899 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SHOTGUN1] then
2900 begin
2901 // Нужно, чтобы не взять все пули сразу:
2902 if a and FWeapon[WEAPON_SHOTGUN1] then Exit;
2903 hadWeapon := FWeapon[WEAPON_SHOTGUN1];
2904 switchWeapon := WEAPON_SHOTGUN1;
2905 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
2906 FWeapon[WEAPON_SHOTGUN1] := True;
2907 Result := True;
2908 if gFlash = 2 then Inc(FPickup, 5);
2909 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
2910 end;
2912 ITEM_WEAPON_SHOTGUN2:
2913 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SHOTGUN2] then
2914 begin
2915 if a and FWeapon[WEAPON_SHOTGUN2] then Exit;
2916 hadWeapon := FWeapon[WEAPON_SHOTGUN2];
2917 switchWeapon := WEAPON_SHOTGUN2;
2918 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
2919 FWeapon[WEAPON_SHOTGUN2] := True;
2920 Result := True;
2921 if gFlash = 2 then Inc(FPickup, 5);
2922 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
2923 end;
2925 ITEM_WEAPON_CHAINGUN:
2926 if (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or not FWeapon[WEAPON_CHAINGUN] then
2927 begin
2928 if a and FWeapon[WEAPON_CHAINGUN] then Exit;
2929 hadWeapon := FWeapon[WEAPON_CHAINGUN];
2930 switchWeapon := WEAPON_CHAINGUN;
2931 IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
2932 FWeapon[WEAPON_CHAINGUN] := True;
2933 Result := True;
2934 if gFlash = 2 then Inc(FPickup, 5);
2935 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
2936 end;
2938 ITEM_WEAPON_ROCKETLAUNCHER:
2939 if (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or not FWeapon[WEAPON_ROCKETLAUNCHER] then
2940 begin
2941 if a and FWeapon[WEAPON_ROCKETLAUNCHER] then Exit;
2942 switchWeapon := WEAPON_ROCKETLAUNCHER;
2943 hadWeapon := FWeapon[WEAPON_ROCKETLAUNCHER];
2944 IncMax(FAmmo[A_ROCKETS], 2, FMaxAmmo[A_ROCKETS]);
2945 FWeapon[WEAPON_ROCKETLAUNCHER] := True;
2946 Result := True;
2947 if gFlash = 2 then Inc(FPickup, 5);
2948 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
2949 end;
2951 ITEM_WEAPON_PLASMA:
2952 if (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or not FWeapon[WEAPON_PLASMA] then
2953 begin
2954 if a and FWeapon[WEAPON_PLASMA] then Exit;
2955 switchWeapon := WEAPON_PLASMA;
2956 hadWeapon := FWeapon[WEAPON_PLASMA];
2957 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
2958 FWeapon[WEAPON_PLASMA] := True;
2959 Result := True;
2960 if gFlash = 2 then Inc(FPickup, 5);
2961 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
2962 end;
2964 ITEM_WEAPON_BFG:
2965 if (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or not FWeapon[WEAPON_BFG] then
2966 begin
2967 if a and FWeapon[WEAPON_BFG] then Exit;
2968 switchWeapon := WEAPON_BFG;
2969 hadWeapon := FWeapon[WEAPON_BFG];
2970 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
2971 FWeapon[WEAPON_BFG] := True;
2972 Result := True;
2973 if gFlash = 2 then Inc(FPickup, 5);
2974 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
2975 end;
2977 ITEM_WEAPON_SUPERPULEMET:
2978 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SUPERPULEMET] then
2979 begin
2980 if a and FWeapon[WEAPON_SUPERPULEMET] then Exit;
2981 switchWeapon := WEAPON_SUPERPULEMET;
2982 hadWeapon := FWeapon[WEAPON_SUPERPULEMET];
2983 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
2984 FWeapon[WEAPON_SUPERPULEMET] := True;
2985 Result := True;
2986 if gFlash = 2 then Inc(FPickup, 5);
2987 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
2988 end;
2990 ITEM_WEAPON_FLAMETHROWER:
2991 if (FAmmo[A_FUEL] < FMaxAmmo[A_FUEL]) or not FWeapon[WEAPON_FLAMETHROWER] then
2992 begin
2993 if a and FWeapon[WEAPON_FLAMETHROWER] then Exit;
2994 switchWeapon := WEAPON_FLAMETHROWER;
2995 hadWeapon := FWeapon[WEAPON_FLAMETHROWER];
2996 IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
2997 FWeapon[WEAPON_FLAMETHROWER] := True;
2998 Result := True;
2999 if gFlash = 2 then Inc(FPickup, 5);
3000 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3001 end;
3003 ITEM_AMMO_BULLETS:
3004 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
3005 begin
3006 IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
3007 Result := True;
3008 remove := True;
3009 if gFlash = 2 then Inc(FPickup, 5);
3010 end;
3012 ITEM_AMMO_BULLETS_BOX:
3013 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
3014 begin
3015 IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
3016 Result := True;
3017 remove := True;
3018 if gFlash = 2 then Inc(FPickup, 5);
3019 end;
3021 ITEM_AMMO_SHELLS:
3022 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
3023 begin
3024 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
3025 Result := True;
3026 remove := True;
3027 if gFlash = 2 then Inc(FPickup, 5);
3028 end;
3030 ITEM_AMMO_SHELLS_BOX:
3031 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
3032 begin
3033 IncMax(FAmmo[A_SHELLS], 25, FMaxAmmo[A_SHELLS]);
3034 Result := True;
3035 remove := True;
3036 if gFlash = 2 then Inc(FPickup, 5);
3037 end;
3039 ITEM_AMMO_ROCKET:
3040 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
3041 begin
3042 IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
3043 Result := True;
3044 remove := True;
3045 if gFlash = 2 then Inc(FPickup, 5);
3046 end;
3048 ITEM_AMMO_ROCKET_BOX:
3049 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
3050 begin
3051 IncMax(FAmmo[A_ROCKETS], 5, FMaxAmmo[A_ROCKETS]);
3052 Result := True;
3053 remove := True;
3054 if gFlash = 2 then Inc(FPickup, 5);
3055 end;
3057 ITEM_AMMO_CELL:
3058 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
3059 begin
3060 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
3061 Result := True;
3062 remove := True;
3063 if gFlash = 2 then Inc(FPickup, 5);
3064 end;
3066 ITEM_AMMO_CELL_BIG:
3067 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
3068 begin
3069 IncMax(FAmmo[A_CELLS], 100, FMaxAmmo[A_CELLS]);
3070 Result := True;
3071 remove := True;
3072 if gFlash = 2 then Inc(FPickup, 5);
3073 end;
3075 ITEM_AMMO_FUELCAN:
3076 if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then
3077 begin
3078 IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
3079 Result := True;
3080 remove := True;
3081 if gFlash = 2 then Inc(FPickup, 5);
3082 end;
3084 ITEM_AMMO_BACKPACK:
3085 if not(R_ITEM_BACKPACK in FRulez) or
3086 (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or
3087 (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or
3088 (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or
3089 (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or
3090 (FAmmo[A_FUEL] < FMaxAmmo[A_FUEL]) then
3091 begin
3092 FMaxAmmo[A_BULLETS] := AmmoLimits[1, A_BULLETS];
3093 FMaxAmmo[A_SHELLS] := AmmoLimits[1, A_SHELLS];
3094 FMaxAmmo[A_ROCKETS] := AmmoLimits[1, A_ROCKETS];
3095 FMaxAmmo[A_CELLS] := AmmoLimits[1, A_CELLS];
3096 FMaxAmmo[A_FUEL] := AmmoLimits[1, A_FUEL];
3098 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
3099 IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
3100 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
3101 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
3102 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
3103 IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
3104 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
3105 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
3106 if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then
3107 IncMax(FAmmo[A_FUEL], 50, FMaxAmmo[A_FUEL]);
3109 FRulez := FRulez + [R_ITEM_BACKPACK];
3110 Result := True;
3111 remove := True;
3112 if gFlash = 2 then Inc(FPickup, 5);
3113 end;
3115 ITEM_KEY_RED:
3116 if not(R_KEY_RED in FRulez) then
3117 begin
3118 Include(FRulez, R_KEY_RED);
3119 Result := True;
3120 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
3121 if gFlash = 2 then Inc(FPickup, 5);
3122 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
3123 end;
3125 ITEM_KEY_GREEN:
3126 if not(R_KEY_GREEN in FRulez) then
3127 begin
3128 Include(FRulez, R_KEY_GREEN);
3129 Result := True;
3130 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
3131 if gFlash = 2 then Inc(FPickup, 5);
3132 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
3133 end;
3135 ITEM_KEY_BLUE:
3136 if not(R_KEY_BLUE in FRulez) then
3137 begin
3138 Include(FRulez, R_KEY_BLUE);
3139 Result := True;
3140 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
3141 if gFlash = 2 then Inc(FPickup, 5);
3142 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
3143 end;
3145 ITEM_SUIT:
3146 if FMegaRulez[MR_SUIT] < gTime+PLAYER_SUIT_TIME then
3147 begin
3148 FMegaRulez[MR_SUIT] := gTime+PLAYER_SUIT_TIME;
3149 Result := True;
3150 remove := True;
3151 FFireTime := 0;
3152 if gFlash = 2 then Inc(FPickup, 5);
3153 end;
3155 ITEM_OXYGEN:
3156 if FAir < AIR_MAX then
3157 begin
3158 FAir := AIR_MAX;
3159 Result := True;
3160 remove := True;
3161 if gFlash = 2 then Inc(FPickup, 5);
3162 end;
3164 ITEM_MEDKIT_BLACK:
3165 begin
3166 if not (R_BERSERK in FRulez) then
3167 begin
3168 Include(FRulez, R_BERSERK);
3169 if (FBFGFireCounter = -1) then
3170 begin
3171 FCurrWeap := WEAPON_KASTET;
3172 resetWeaponQueue();
3173 FModel.SetWeapon(WEAPON_KASTET);
3174 end;
3175 if gFlash <> 0 then
3176 begin
3177 Inc(FPain, 100);
3178 if gFlash = 2 then Inc(FPickup, 5);
3179 end;
3180 FBerserk := gTime+30000;
3181 Result := True;
3182 remove := True;
3183 FFireTime := 0;
3184 end;
3185 if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
3186 begin
3187 if FHealth < PLAYER_HP_SOFT then FHealth := PLAYER_HP_SOFT;
3188 FBerserk := gTime+30000;
3189 Result := True;
3190 remove := True;
3191 FFireTime := 0;
3192 end;
3193 end;
3195 ITEM_INVUL:
3196 if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
3197 begin
3198 FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
3199 FSpawnInvul := 0;
3200 Result := True;
3201 remove := True;
3202 if gFlash = 2 then Inc(FPickup, 5);
3203 end;
3205 ITEM_BOTTLE:
3206 if (FHealth < PLAYER_HP_LIMIT) or (FFireTime > 0) then
3207 begin
3208 if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 4, PLAYER_HP_LIMIT);
3209 Result := True;
3210 remove := True;
3211 FFireTime := 0;
3212 if gFlash = 2 then Inc(FPickup, 5);
3213 end;
3215 ITEM_HELMET:
3216 if FArmor < PLAYER_AP_LIMIT then
3217 begin
3218 IncMax(FArmor, 5, PLAYER_AP_LIMIT);
3219 Result := True;
3220 remove := True;
3221 if gFlash = 2 then Inc(FPickup, 5);
3222 end;
3224 ITEM_JETPACK:
3225 if FJetFuel < JET_MAX then
3226 begin
3227 FJetFuel := JET_MAX;
3228 Result := True;
3229 remove := True;
3230 if gFlash = 2 then Inc(FPickup, 5);
3231 end;
3233 ITEM_INVIS:
3234 if FMegaRulez[MR_INVIS] < gTime+PLAYER_INVIS_TIME then
3235 begin
3236 FMegaRulez[MR_INVIS] := gTime+PLAYER_INVIS_TIME;
3237 Result := True;
3238 remove := True;
3239 if gFlash = 2 then Inc(FPickup, 5);
3240 end;
3241 end;
3243 if (shouldSwitch(switchWeapon, hadWeapon)) then
3244 QueueWeaponSwitch(switchWeapon);
3245 end;
3247 procedure TPlayer.Touch();
3248 begin
3249 if not FAlive then
3250 Exit;
3251 //FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
3252 if FIamBot then
3253 begin
3254 // Бросить флаг товарищу:
3255 if gGameSettings.GameMode = GM_CTF then
3256 DropFlag();
3257 end;
3258 end;
3260 procedure TPlayer.Push(vx, vy: Integer);
3261 begin
3262 if (not FPhysics) and FGhost then
3263 Exit;
3264 FObj.Accel.X := FObj.Accel.X + vx;
3265 FObj.Accel.Y := FObj.Accel.Y + vy;
3266 if g_Game_IsNet and g_Game_IsServer then
3267 MH_SEND_PlayerPos(True, FUID, NET_EVERYONE);
3268 end;
3270 procedure TPlayer.Reset(Force: Boolean);
3271 begin
3272 if Force then
3273 FAlive := False;
3275 FSpawned := False;
3276 FTime[T_RESPAWN] := 0;
3277 FTime[T_FLAGCAP] := 0;
3278 FGodMode := False;
3279 FNoTarget := False;
3280 FNoReload := False;
3281 FFrags := 0;
3282 FLastFrag := 0;
3283 FComboEvnt := -1;
3284 FKills := 0;
3285 FMonsterKills := 0;
3286 FDeath := 0;
3287 FSecrets := 0;
3288 FSpawnInvul := 0;
3289 {$IFDEF ENABLE_CORPSES}
3290 FCorpse := -1;
3291 {$ENDIF}
3292 FReady := False;
3293 if FNoRespawn then
3294 begin
3295 FSpectator := False;
3296 FGhost := False;
3297 FPhysics := True;
3298 FSpectatePlayer := -1;
3299 FNoRespawn := False;
3300 end;
3301 FLives := gGameSettings.MaxLives;
3303 SetFlag(FLAG_NONE);
3304 end;
3306 procedure TPlayer.SoftReset();
3307 begin
3308 ReleaseKeys();
3310 FDamageBuffer := 0;
3311 FSlopeOld := 0;
3312 FIncCamOld := 0;
3313 FIncCam := 0;
3314 FBFGFireCounter := -1;
3315 {$IFDEF ENABLE_SHELLS}
3316 FShellTimer := -1;
3317 {$ENDIF}
3318 FPain := 0;
3319 FLastHit := 0;
3320 FLastFrag := 0;
3321 FComboEvnt := -1;
3323 SetFlag(FLAG_NONE);
3324 SetAction(A_STAND, True);
3325 end;
3327 function TPlayer.GetRespawnPoint(): Byte;
3328 var
3329 c: Byte;
3330 begin
3331 Result := 255;
3332 // На будущее: FSpawn - игрок уже играл и перерождается
3334 // Одиночная игра/кооператив
3335 if gGameSettings.GameMode in [GM_COOP, GM_SINGLE] then
3336 begin
3337 if Self = gPlayer1 then
3338 begin
3339 // player 1 should try to spawn on the player 1 point
3340 if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1) > 0 then
3341 Exit(RESPAWNPOINT_PLAYER1)
3342 else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2) > 0 then
3343 Exit(RESPAWNPOINT_PLAYER2);
3344 end
3345 else if Self = gPlayer2 then
3346 begin
3347 // player 2 should try to spawn on the player 2 point
3348 if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2) > 0 then
3349 Exit(RESPAWNPOINT_PLAYER2)
3350 else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1) > 0 then
3351 Exit(RESPAWNPOINT_PLAYER1);
3352 end
3353 else
3354 begin
3355 // other players randomly pick either the first or the second point
3356 c := IfThen((Random(2) = 0), RESPAWNPOINT_PLAYER1, RESPAWNPOINT_PLAYER2);
3357 if g_Map_GetPointCount(c) > 0 then
3358 Exit(c);
3359 // try the other one
3360 c := IfThen((c = RESPAWNPOINT_PLAYER1), RESPAWNPOINT_PLAYER2, RESPAWNPOINT_PLAYER1);
3361 if g_Map_GetPointCount(c) > 0 then
3362 Exit(c);
3363 end;
3364 end;
3366 // Мясоповал
3367 if gGameSettings.GameMode = GM_DM then
3368 begin
3369 // try DM points first
3370 if g_Map_GetPointCount(RESPAWNPOINT_DM) > 0 then
3371 Exit(RESPAWNPOINT_DM);
3372 end;
3374 // Командные
3375 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
3376 begin
3377 // try team points first
3378 c := RESPAWNPOINT_DM;
3379 if FTeam = TEAM_RED then
3380 c := RESPAWNPOINT_RED
3381 else if FTeam = TEAM_BLUE then
3382 c := RESPAWNPOINT_BLUE;
3383 if g_Map_GetPointCount(c) > 0 then
3384 Exit(c);
3385 end;
3387 // still haven't found a spawnpoint, try random shit
3388 Result := g_Map_GetRandomPointType();
3389 end;
3391 procedure TPlayer.Respawn(Silent: Boolean; Force: Boolean = False);
3392 var
3393 RespawnPoint: TRespawnPoint;
3394 a, b, c: Byte;
3395 begin
3396 FSlopeOld := 0;
3397 FIncCamOld := 0;
3398 FIncCam := 0;
3399 FBFGFireCounter := -1;
3400 {$IFDEF ENABLE_SHELLS}
3401 FShellTimer := -1;
3402 {$ENDIF}
3403 FPain := 0;
3404 FLastHit := 0;
3405 FSpawnInvul := 0;
3407 {$IFDEF ENABLE_CORPSES}
3408 FCorpse := -1;
3409 {$ENDIF}
3411 if not g_Game_IsServer then
3412 Exit;
3413 if FDummy then
3414 Exit;
3415 FWantsInGame := True;
3416 FJustTeleported := True;
3417 if Force then
3418 begin
3419 FTime[T_RESPAWN] := 0;
3420 FAlive := False;
3421 end;
3422 FNetTime := 0;
3423 // if server changes MaxLives we gotta be ready
3424 if gGameSettings.MaxLives = 0 then FNoRespawn := False;
3426 // Еще нельзя возродиться:
3427 if FTime[T_RESPAWN] > gTime then
3428 Exit;
3430 // Просрал все жизни:
3431 if FNoRespawn then
3432 begin
3433 if not FSpectator then Spectate(True);
3434 FWantsInGame := True;
3435 Exit;
3436 end;
3438 if (gGameSettings.GameType <> GT_SINGLE) and (gGameSettings.GameMode <> GM_COOP) then
3439 begin // "Своя игра"
3440 // Берсерк не сохраняется между уровнями:
3441 FRulez := FRulez-[R_BERSERK];
3442 end
3443 else // "Одиночная игра"/"Кооп"
3444 begin
3445 // Берсерк и ключи не сохраняются между уровнями:
3446 FRulez := FRulez-[R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE, R_BERSERK];
3447 end;
3449 // Получаем точку спауна игрока:
3450 c := GetRespawnPoint();
3452 ReleaseKeys();
3453 SetFlag(FLAG_NONE);
3455 // Воскрешение без оружия:
3456 if not FAlive then
3457 begin
3458 FHealth := Round(PLAYER_HP_SOFT * (FHandicap / 100));
3459 FArmor := 0;
3460 FAlive := True;
3461 FAir := AIR_DEF;
3462 FJetFuel := 0;
3464 for a := WP_FIRST to WP_LAST do
3465 begin
3466 FWeapon[a] := False;
3467 FReloading[a] := 0;
3468 end;
3470 FWeapon[WEAPON_PISTOL] := True;
3471 FWeapon[WEAPON_KASTET] := True;
3472 FCurrWeap := WEAPON_PISTOL;
3473 resetWeaponQueue();
3475 FModel.SetWeapon(FCurrWeap);
3477 for b := A_BULLETS to A_HIGH do
3478 FAmmo[b] := 0;
3480 FAmmo[A_BULLETS] := 50;
3482 FMaxAmmo[A_BULLETS] := AmmoLimits[0, A_BULLETS];
3483 FMaxAmmo[A_SHELLS] := AmmoLimits[0, A_SHELLS];
3484 FMaxAmmo[A_ROCKETS] := AmmoLimits[0, A_SHELLS];
3485 FMaxAmmo[A_CELLS] := AmmoLimits[0, A_CELLS];
3486 FMaxAmmo[A_FUEL] := AmmoLimits[0, A_FUEL];
3488 if (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) and
3489 LongBool(gGameSettings.Options and GAME_OPTION_DMKEYS) then
3490 FRulez := [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE]
3491 else
3492 FRulez := [];
3493 end;
3495 // Получаем координаты точки возрождения:
3496 if not g_Map_GetPoint(c, RespawnPoint) then
3497 begin
3498 g_FatalError(_lc[I_GAME_ERROR_GET_SPAWN]);
3499 Exit;
3500 end;
3502 // Установка координат и сброс всех параметров:
3503 FObj.X := RespawnPoint.X-PLAYER_RECT.X;
3504 FObj.Y := RespawnPoint.Y-PLAYER_RECT.Y;
3505 FObj.oldX := FObj.X; // don't interpolate after respawn
3506 FObj.oldY := FObj.Y;
3507 FObj.Vel.X := 0;
3508 FObj.Vel.Y := 0;
3509 FObj.Accel.X := 0;
3510 FObj.Accel.Y := 0;
3512 FDirection := RespawnPoint.Direction;
3513 if FDirection = TDirection.D_LEFT then
3514 FAngle := 180
3515 else
3516 FAngle := 0;
3518 SetAction(A_STAND, True);
3519 FModel.Direction := FDirection;
3521 for a := Low(FTime) to High(FTime) do
3522 FTime[a] := 0;
3524 for a := Low(FMegaRulez) to High(FMegaRulez) do
3525 FMegaRulez[a] := 0;
3527 // Respawn invulnerability
3528 if (gGameSettings.GameType <> GT_SINGLE) and (gGameSettings.SpawnInvul > 0) then
3529 begin
3530 FMegaRulez[MR_INVUL] := gTime + gGameSettings.SpawnInvul * 1000;
3531 FSpawnInvul := FMegaRulez[MR_INVUL];
3532 end;
3534 FDamageBuffer := 0;
3535 FJetpack := False;
3536 FCanJetpack := False;
3537 FFlaming := False;
3538 FFireTime := 0;
3539 FFirePainTime := 0;
3540 FFireAttacker := 0;
3542 {$IFDEF ENABLE_GFX}
3543 // Анимация возрождения:
3544 if (not gLoadGameMode) and (not Silent) then
3545 begin
3546 g_GFX_QueueEffect(
3547 R_GFX_TELEPORT_FAST,
3548 FObj.X + PLAYER_RECT.X + (PLAYER_RECT.Width div 2) - 32,
3549 FObj.Y + PLAYER_RECT.Y + (PLAYER_RECT.Height div 2) - 32
3550 );
3551 end;
3552 {$ENDIF}
3554 FSpectator := False;
3555 FGhost := False;
3556 FPhysics := True;
3557 FSpectatePlayer := -1;
3558 FSpawned := True;
3560 if (gPlayer1 = nil) and (gSpectLatchPID1 = FUID) then
3561 gPlayer1 := self;
3562 if (gPlayer2 = nil) and (gSpectLatchPID2 = FUID) then
3563 gPlayer2 := self;
3565 if g_Game_IsNet then
3566 begin
3567 MH_SEND_PlayerPos(True, FUID, NET_EVERYONE);
3568 MH_SEND_PlayerStats(FUID, NET_EVERYONE);
3569 if not Silent then
3570 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
3571 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32,
3572 0, NET_GFX_TELE);
3573 end;
3574 end;
3576 procedure TPlayer.Spectate(NoMove: Boolean = False);
3577 begin
3578 if FAlive then
3579 Kill(K_EXTRAHARDKILL, FUID, HIT_SOME)
3580 else if (not NoMove) then
3581 begin
3582 GameX := gMapInfo.Width div 2;
3583 GameY := gMapInfo.Height div 2;
3584 end;
3585 FXTo := GameX;
3586 FYTo := GameY;
3588 FAlive := False;
3589 FSpectator := True;
3590 FGhost := True;
3591 FPhysics := False;
3592 FWantsInGame := False;
3593 FSpawned := False;
3595 {$IFDEF ENABLE_CORPSES}
3596 FCorpse := -1;
3597 {$ENDIF}
3599 if FNoRespawn then
3600 begin
3601 if Self = gPlayer1 then
3602 begin
3603 gSpectLatchPID1 := FUID;
3604 gPlayer1 := nil;
3605 end
3606 else if Self = gPlayer2 then
3607 begin
3608 gSpectLatchPID2 := FUID;
3609 gPlayer2 := nil;
3610 end;
3611 end;
3613 if g_Game_IsNet then
3614 MH_SEND_PlayerStats(FUID);
3615 end;
3617 procedure TPlayer.SwitchNoClip;
3618 begin
3619 if not FAlive then
3620 Exit;
3621 FGhost := not FGhost;
3622 FPhysics := not FGhost;
3623 if FGhost then
3624 begin
3625 FXTo := FObj.X;
3626 FYTo := FObj.Y;
3627 end else
3628 begin
3629 FObj.Accel.X := 0;
3630 FObj.Accel.Y := 0;
3631 end;
3632 end;
3634 procedure TPlayer.Run(Direction: TDirection);
3635 {$IFDEF ENABLE_GIBS}
3636 var a, b: Integer;
3637 {$ENDIF}
3638 begin
3639 if MAX_RUNVEL > 8 then
3640 FlySmoke();
3642 // Бежим:
3643 if Direction = TDirection.D_LEFT then
3644 begin
3645 if FObj.Vel.X > -MAX_RUNVEL then
3646 FObj.Vel.X := FObj.Vel.X - (MAX_RUNVEL shr 3);
3647 end
3648 else
3649 if FObj.Vel.X < MAX_RUNVEL then
3650 FObj.Vel.X := FObj.Vel.X + (MAX_RUNVEL shr 3);
3652 {$IFDEF ENABLE_GIBS}
3653 // Возможно, пинаем куски:
3654 if (FObj.Vel.X <> 0) and (gGibs <> nil) then
3655 begin
3656 b := Abs(FObj.Vel.X);
3657 if b > 1 then b := b * (Random(8 div b) + 1);
3658 for a := 0 to High(gGibs) do
3659 begin
3660 if gGibs[a].alive and
3661 g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
3662 FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then
3663 begin
3664 // Пинаем куски
3665 if FObj.Vel.X < 0 then
3666 begin
3667 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120) // налево
3668 end
3669 else
3670 begin
3671 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // направо
3672 end;
3673 gGibs[a].positionChanged(); // this updates spatial accelerators
3674 end;
3675 end;
3676 end;
3677 {$ENDIF}
3679 SetAction(A_WALK);
3680 end;
3682 procedure TPlayer.SeeDown();
3683 begin
3684 SetAction(A_SEEDOWN);
3686 if FDirection = TDirection.D_LEFT then FAngle := ANGLE_LEFTDOWN else FAngle := ANGLE_RIGHTDOWN;
3688 if FIncCam > -120 then DecMin(FIncCam, 5, -120);
3689 end;
3691 procedure TPlayer.SeeUp();
3692 begin
3693 SetAction(A_SEEUP);
3695 if FDirection = TDirection.D_LEFT then FAngle := ANGLE_LEFTUP else FAngle := ANGLE_RIGHTUP;
3697 if FIncCam < 120 then IncMax(FIncCam, 5, 120);
3698 end;
3700 procedure TPlayer.SetAction(Action: Byte; Force: Boolean = False);
3701 var
3702 Prior: Byte;
3703 begin
3704 case Action of
3705 A_WALK: Prior := 3;
3706 A_DIE1: Prior := 5;
3707 A_DIE2: Prior := 5;
3708 A_ATTACK: Prior := 2;
3709 A_SEEUP: Prior := 1;
3710 A_SEEDOWN: Prior := 1;
3711 A_ATTACKUP: Prior := 2;
3712 A_ATTACKDOWN: Prior := 2;
3713 A_PAIN: Prior := 4;
3714 else Prior := 0;
3715 end;
3717 if (Prior > FActionPrior) or Force then
3718 if not ((Prior = 2) and (FCurrWeap = WEAPON_SAW)) then
3719 begin
3720 FActionPrior := Prior;
3721 FActionAnim := Action;
3722 FActionForce := Force;
3723 FActionChanged := True;
3724 end;
3726 if Action in [A_ATTACK, A_ATTACKUP, A_ATTACKDOWN] then FModel.SetFire(True);
3727 end;
3729 function TPlayer.StayOnStep(XInc, YInc: Integer): Boolean;
3730 begin
3731 Result := not g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+YInc+PLAYER_RECT.Y+PLAYER_RECT.Height-1,
3732 PLAYER_RECT.Width, 1, PANEL_STEP, False)
3733 and g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+YInc+PLAYER_RECT.Y+PLAYER_RECT.Height,
3734 PLAYER_RECT.Width, 1, PANEL_STEP, False);
3735 end;
3737 function TPlayer.TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean;
3738 begin
3739 Result := False;
3741 if g_CollideLevel(X, Y, PLAYER_RECT.Width, PLAYER_RECT.Height) then
3742 begin
3743 g_Sound_PlayExAt('SOUND_GAME_NOTELEPORT', FObj.X, FObj.Y);
3744 if g_Game_IsServer and g_Game_IsNet then
3745 MH_SEND_Sound(FObj.X, FObj.Y, 'SOUND_GAME_NOTELEPORT');
3746 Exit;
3747 end;
3749 FJustTeleported := True;
3751 if not silent then
3752 begin
3753 g_Sound_PlayExAt('SOUND_GAME_TELEPORT', FObj.X, FObj.Y);
3754 {$IFDEF ENABLE_GFX}
3755 g_GFX_QueueEffect(
3756 R_GFX_TELEPORT_FAST,
3757 FObj.X + PLAYER_RECT.X + (PLAYER_RECT.Width div 2) - 32,
3758 FObj.Y + PLAYER_RECT.Y + (PLAYER_RECT.Height div 2) - 32
3759 );
3760 {$ENDIF}
3761 if g_Game_IsServer and g_Game_IsNet then
3762 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
3763 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 1,
3764 NET_GFX_TELE);
3765 end;
3767 FObj.X := X-PLAYER_RECT.X;
3768 FObj.Y := Y-PLAYER_RECT.Y;
3769 FObj.oldX := FObj.X; // don't interpolate after respawn
3770 FObj.oldY := FObj.Y;
3771 if FAlive and FGhost then
3772 begin
3773 FXTo := FObj.X;
3774 FYTo := FObj.Y;
3775 end;
3777 if not g_Game_IsNet then
3778 begin
3779 if dir = 1 then
3780 begin
3781 SetDirection(TDirection.D_LEFT);
3782 FAngle := 180;
3783 end
3784 else
3785 if dir = 2 then
3786 begin
3787 SetDirection(TDirection.D_RIGHT);
3788 FAngle := 0;
3789 end
3790 else
3791 if dir = 3 then
3792 begin // обратное
3793 if FDirection = TDirection.D_RIGHT then
3794 begin
3795 SetDirection(TDirection.D_LEFT);
3796 FAngle := 180;
3797 end
3798 else
3799 begin
3800 SetDirection(TDirection.D_RIGHT);
3801 FAngle := 0;
3802 end;
3803 end;
3804 end;
3806 if not silent then
3807 begin
3808 {$IFDEF ENABLE_GFX}
3809 g_GFX_QueueEffect(
3810 R_GFX_TELEPORT_FAST,
3811 FObj.X + PLAYER_RECT.X + (PLAYER_RECT.Width div 2) - 32,
3812 FObj.Y + PLAYER_RECT.Y + (PLAYER_RECT.Height div 2) - 32
3813 );
3814 {$ENDIF}
3815 if g_Game_IsServer and g_Game_IsNet then
3816 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
3817 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 0,
3818 NET_GFX_TELE);
3819 end;
3821 Result := True;
3822 end;
3824 function nonz(a: Single): Single;
3825 begin
3826 if a <> 0 then
3827 Result := a
3828 else
3829 Result := 1;
3830 end;
3832 procedure TPlayer.PreUpdate();
3833 begin
3834 FSlopeOld := FObj.slopeUpLeft;
3835 FIncCamOld := FIncCam;
3836 FObj.oldX := FObj.X;
3837 FObj.oldY := FObj.Y;
3838 end;
3840 procedure TPlayer.Update();
3841 var
3842 b: Byte;
3843 i, ii, wx, wy, xd, yd, k: Integer;
3844 blockmon, headwater, dospawn: Boolean;
3845 NetServer: Boolean;
3846 AnyServer: Boolean;
3847 SetSpect: Boolean;
3848 begin
3849 NetServer := g_Game_IsNet and g_Game_IsServer;
3850 AnyServer := g_Game_IsServer;
3852 if g_Game_IsClient and (NetInterpLevel > 0) then
3853 DoLerp(NetInterpLevel + 1)
3854 else
3855 if FGhost then
3856 DoLerp(4);
3858 if NetServer then
3859 if (FClientID >= 0) and (NetClients[FClientID].Peer <> nil) then
3860 begin
3861 FPing := NetClients[FClientID].Peer^.lastRoundTripTime;
3862 if NetClients[FClientID].Peer^.packetsSent > 0 then
3863 FLoss := Round(100*NetClients[FClientID].Peer^.packetsLost/NetClients[FClientID].Peer^.packetsSent)
3864 else
3865 FLoss := 0;
3866 end else
3867 begin
3868 FPing := 0;
3869 FLoss := 0;
3870 end;
3872 if FAlive then
3873 FPunchAnim.Update;
3874 if FPunchAnim.played then
3875 FPunchAnim.Disable;
3877 if FAlive and (gFly or FJetpack) then
3878 FlySmoke();
3880 if FDirection = TDirection.D_LEFT then
3881 FAngle := 180
3882 else
3883 FAngle := 0;
3885 if FAlive and (not FGhost) then
3886 begin
3887 if FKeys[KEY_UP].Pressed then
3888 SeeUp();
3889 if FKeys[KEY_DOWN].Pressed then
3890 SeeDown();
3891 end;
3893 if (not (FKeys[KEY_UP].Pressed or FKeys[KEY_DOWN].Pressed)) and
3894 (FIncCam <> 0) then
3895 begin
3896 i := g_basic.Sign(FIncCam);
3897 FIncCam := Abs(FIncCam);
3898 DecMin(FIncCam, 5, 0);
3899 FIncCam := FIncCam*i;
3900 end;
3902 if gTime mod (GAME_TICK*2) <> 0 then
3903 begin
3904 if (FObj.Vel.X = 0) and FAlive then
3905 begin
3906 if FKeys[KEY_LEFT].Pressed then
3907 Run(TDirection.D_LEFT);
3908 if FKeys[KEY_RIGHT].Pressed then
3909 Run(TDirection.D_RIGHT);
3910 end;
3912 if FPhysics then
3913 begin
3914 g_Obj_Move(@FObj, True, True, True);
3915 positionChanged(); // this updates spatial accelerators
3916 end;
3918 Exit;
3919 end;
3921 FActionChanged := False;
3923 if FAlive then
3924 begin
3925 // Let alive player do some actions
3926 if FKeys[KEY_LEFT].Pressed then Run(TDirection.D_LEFT);
3927 if FKeys[KEY_RIGHT].Pressed then Run(TDirection.D_RIGHT);
3928 if FKeys[KEY_FIRE].Pressed and AnyServer then Fire()
3929 else
3930 begin
3931 if AnyServer then
3932 begin
3933 FlamerOff;
3934 if NetServer then MH_SEND_PlayerStats(FUID);
3935 end;
3936 end;
3937 if FKeys[KEY_OPEN].Pressed and AnyServer then Use();
3938 if FKeys[KEY_JUMP].Pressed then Jump()
3939 else
3940 begin
3941 if AnyServer and FJetpack then
3942 begin
3943 FJetpack := False;
3944 JetpackOff;
3945 if NetServer then MH_SEND_PlayerStats(FUID);
3946 end;
3947 FCanJetpack := True;
3948 end;
3949 end
3950 else // Dead
3951 begin
3952 dospawn := False;
3953 if not FGhost then
3954 for k := Low(FKeys) to KEY_CHAT-1 do
3955 begin
3956 if FKeys[k].Pressed then
3957 begin
3958 dospawn := True;
3959 break;
3960 end;
3961 end;
3962 if dospawn then
3963 begin
3964 if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
3965 Respawn(False)
3966 else // Single
3967 if (FTime[T_RESPAWN] <= gTime) and
3968 gGameOn and (not FAlive) then
3969 begin
3970 if (g_Player_GetCount() > 1) then
3971 Respawn(False)
3972 else
3973 begin
3974 gExit := EXIT_RESTART;
3975 Exit;
3976 end;
3977 end;
3978 end;
3979 // Dead spectator actions
3980 if FGhost then
3981 begin
3982 if FKeys[KEY_OPEN].Pressed and AnyServer then Fire();
3983 if FKeys[KEY_FIRE].Pressed and AnyServer then
3984 begin
3985 if FSpectator then
3986 begin
3987 if (FSpectatePlayer >= High(gPlayers)) then
3988 FSpectatePlayer := -1
3989 else
3990 begin
3991 SetSpect := False;
3992 for I := FSpectatePlayer + 1 to High(gPlayers) do
3993 if gPlayers[I] <> nil then
3994 if gPlayers[I].alive then
3995 if gPlayers[I].UID <> FUID then
3996 begin
3997 FSpectatePlayer := I;
3998 SetSpect := True;
3999 break;
4000 end;
4002 if not SetSpect then FSpectatePlayer := -1;
4003 end;
4005 ReleaseKeys;
4006 end;
4007 end;
4008 end;
4009 end;
4010 // No clipping
4011 if FGhost then
4012 begin
4013 if FKeys[KEY_UP].Pressed or FKeys[KEY_JUMP].Pressed then
4014 begin
4015 FYTo := FObj.Y - 32;
4016 FSpectatePlayer := -1;
4017 end;
4018 if FKeys[KEY_DOWN].Pressed then
4019 begin
4020 FYTo := FObj.Y + 32;
4021 FSpectatePlayer := -1;
4022 end;
4023 if FKeys[KEY_LEFT].Pressed then
4024 begin
4025 FXTo := FObj.X - 32;
4026 FSpectatePlayer := -1;
4027 end;
4028 if FKeys[KEY_RIGHT].Pressed then
4029 begin
4030 FXTo := FObj.X + 32;
4031 FSpectatePlayer := -1;
4032 end;
4034 if (FXTo < -64) then
4035 FXTo := -64
4036 else if (FXTo > gMapInfo.Width + 32) then
4037 FXTo := gMapInfo.Width + 32;
4038 if (FYTo < -72) then
4039 FYTo := -72
4040 else if (FYTo > gMapInfo.Height + 32) then
4041 FYTo := gMapInfo.Height + 32;
4042 end;
4044 if FPhysics then
4045 begin
4046 g_Obj_Move(@FObj, True, True, True);
4047 positionChanged(); // this updates spatial accelerators
4048 end
4049 else
4050 begin
4051 FObj.Vel.X := 0;
4052 FObj.Vel.Y := 0;
4053 if FSpectator then
4054 if (FSpectatePlayer <= High(gPlayers)) and (FSpectatePlayer >= 0) then
4055 if gPlayers[FSpectatePlayer] <> nil then
4056 if gPlayers[FSpectatePlayer].alive then
4057 begin
4058 FXTo := gPlayers[FSpectatePlayer].GameX;
4059 FYTo := gPlayers[FSpectatePlayer].GameY;
4060 end;
4061 end;
4063 blockmon := g_Map_CollidePanel(FObj.X+PLAYER_HEADRECT.X, FObj.Y+PLAYER_HEADRECT.Y,
4064 PLAYER_HEADRECT.Width, PLAYER_HEADRECT.Height,
4065 PANEL_BLOCKMON, True);
4066 headwater := HeadInLiquid(0, 0);
4068 // Сопротивление воздуха:
4069 if (not FAlive) or not (FKeys[KEY_LEFT].Pressed or FKeys[KEY_RIGHT].Pressed) then
4070 if FObj.Vel.X <> 0 then
4071 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
4073 if (FLastHit = HIT_TRAP) and (FPain > 90) then FPain := 90;
4074 DecMin(FPain, 5, 0);
4075 DecMin(FPickup, 1, 0);
4077 if FAlive and (FObj.Y > Integer(gMapInfo.Height)+128) and AnyServer then
4078 begin
4079 // Обнулить действия примочек, чтобы фон пропал
4080 FMegaRulez[MR_SUIT] := 0;
4081 FMegaRulez[MR_INVUL] := 0;
4082 FMegaRulez[MR_INVIS] := 0;
4083 Kill(K_FALLKILL, 0, HIT_FALL);
4084 end;
4086 i := 9;
4088 if FAlive then
4089 begin
4090 if FCurrWeap = WEAPON_SAW then
4091 if not (FSawSound.IsPlaying() or FSawSoundHit.IsPlaying() or
4092 FSawSoundSelect.IsPlaying()) then
4093 FSawSoundIdle.PlayAt(FObj.X, FObj.Y);
4095 if FJetpack then
4096 if (not FJetSoundFly.IsPlaying()) and (not FJetSoundOn.IsPlaying()) and
4097 (not FJetSoundOff.IsPlaying()) then
4098 begin
4099 FJetSoundFly.SetPosition(0);
4100 FJetSoundFly.PlayAt(FObj.X, FObj.Y);
4101 end;
4103 for b := WP_FIRST to WP_LAST do
4104 if FReloading[b] > 0 then
4105 if FNoReload then
4106 FReloading[b] := 0
4107 else
4108 Dec(FReloading[b]);
4110 {$IFDEF ENABLE_SHELLS}
4111 if FShellTimer > -1 then
4112 if FShellTimer = 0 then
4113 begin
4114 if FShellType = SHELL_SHELL then
4115 g_Shells_Create(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
4116 GameVelX, GameVelY-2, SHELL_SHELL)
4117 else if FShellType = SHELL_DBLSHELL then
4118 begin
4119 g_Shells_Create(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
4120 GameVelX+1, GameVelY-2, SHELL_SHELL);
4121 g_Shells_Create(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
4122 GameVelX-1, GameVelY-2, SHELL_SHELL);
4123 end;
4124 FShellTimer := -1;
4125 end else Dec(FShellTimer);
4126 {$ENDIF}
4128 if (FBFGFireCounter > -1) then
4129 if FBFGFireCounter = 0 then
4130 begin
4131 if AnyServer then
4132 begin
4133 wx := FObj.X+WEAPONPOINT[FDirection].X;
4134 wy := FObj.Y+WEAPONPOINT[FDirection].Y;
4135 xd := wx+IfThen(FDirection = TDirection.D_LEFT, -30, 30);
4136 yd := wy+firediry();
4137 g_Weapon_bfgshot(wx, wy, xd, yd, FUID);
4138 if NetServer then MH_SEND_PlayerFire(FUID, WEAPON_BFG, wx, wy, xd, yd);
4139 if (FAngle = 0) or (FAngle = 180) then SetAction(A_ATTACK)
4140 else if (FAngle = ANGLE_LEFTDOWN) or (FAngle = ANGLE_RIGHTDOWN) then SetAction(A_ATTACKDOWN)
4141 else if (FAngle = ANGLE_LEFTUP) or (FAngle = ANGLE_RIGHTUP) then SetAction(A_ATTACKUP);
4142 end;
4144 FReloading[WEAPON_BFG] := WEAPON_RELOAD[WEAPON_BFG];
4145 FBFGFireCounter := -1;
4146 end else
4147 if FNoReload then
4148 FBFGFireCounter := 0
4149 else
4150 Dec(FBFGFireCounter);
4152 if (FMegaRulez[MR_SUIT] < gTime) and AnyServer then
4153 begin
4154 b := g_GetAcidHit(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width, PLAYER_RECT.Height);
4156 if (b > 0) and (gTime mod (15*GAME_TICK) = 0) then Damage(b, 0, 0, 0, HIT_ACID);
4157 end;
4159 if (headwater or blockmon) then
4160 begin
4161 Dec(FAir);
4163 if FAir < -9 then
4164 begin
4165 if AnyServer then Damage(10, 0, 0, 0, HIT_WATER);
4166 FAir := 0;
4167 end
4168 else if (FAir mod 31 = 0) and not blockmon then
4169 begin
4170 {$IFDEF ENABLE_GFX}
4171 g_GFX_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2), FObj.Y+PLAYER_RECT.Y-4, 5+Random(6), 8, 4);
4172 {$ENDIF}
4173 if Random(2) = 0
4174 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
4175 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
4176 end;
4177 end else if FAir < AIR_DEF then
4178 FAir := AIR_DEF;
4180 if FFireTime > 0 then
4181 begin
4182 if BodyInLiquid(0, 0) then
4183 begin
4184 FFireTime := 0;
4185 FFirePainTime := 0;
4186 end
4187 else if FMegaRulez[MR_SUIT] >= gTime then
4188 begin
4189 if FMegaRulez[MR_SUIT] = gTime then
4190 FFireTime := 1;
4191 FFirePainTime := 0;
4192 end
4193 else
4194 begin
4195 OnFireFlame(1);
4196 if FFirePainTime <= 0 then
4197 begin
4198 if g_Game_IsServer then
4199 Damage(2, FFireAttacker, 0, 0, HIT_FLAME);
4200 FFirePainTime := 12 - FFireTime div 12;
4201 end;
4202 FFirePainTime := FFirePainTime - 1;
4203 FFireTime := FFireTime - 1;
4204 if ((FFireTime mod 33) = 0) and (FMegaRulez[MR_INVUL] < gTime) then
4205 FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
4206 if (FFireTime = 0) and g_Game_IsNet and g_Game_IsServer then
4207 MH_SEND_PlayerStats(FUID);
4208 end;
4209 end;
4211 if FDamageBuffer > 0 then
4212 begin
4213 if FDamageBuffer >= 9 then
4214 begin
4215 SetAction(A_PAIN);
4217 if FDamageBuffer < 30 then i := 9
4218 else if FDamageBuffer < 100 then i := 18
4219 else i := 27;
4220 end;
4222 ii := Round(FDamageBuffer*FHealth / nonz(FArmor*(3/4)+FHealth));
4223 FArmor := FArmor-(FDamageBuffer-ii);
4224 FHealth := FHealth-ii;
4225 if FArmor < 0 then
4226 begin
4227 FHealth := FHealth+FArmor;
4228 FArmor := 0;
4229 end;
4231 if AnyServer then
4232 if FHealth <= 0 then
4233 if FHealth > -30 then Kill(K_SIMPLEKILL, FLastSpawnerUID, FLastHit)
4234 else if FHealth > -50 then Kill(K_HARDKILL, FLastSpawnerUID, FLastHit)
4235 else Kill(K_EXTRAHARDKILL, FLastSpawnerUID, FLastHit);
4237 if FAlive and ((FLastHit <> HIT_FLAME) or (FFireTime <= 0)) then
4238 begin
4239 if FDamageBuffer <= 20 then FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y)
4240 else if FDamageBuffer <= 55 then FModel.PlaySound(MODELSOUND_PAIN, 2, FObj.X, FObj.Y)
4241 else if FDamageBuffer <= 120 then FModel.PlaySound(MODELSOUND_PAIN, 3, FObj.X, FObj.Y)
4242 else FModel.PlaySound(MODELSOUND_PAIN, 4, FObj.X, FObj.Y);
4243 end;
4245 FDamageBuffer := 0;
4246 end;
4248 {CollideItem();}
4249 end; // if FAlive then ...
4251 if (FActionAnim = A_PAIN) and (FModel.Animation <> A_PAIN) then
4252 begin
4253 FModel.ChangeAnimation(FActionAnim, FActionForce);
4254 FModel.AnimState.MinLength := i;
4255 end else FModel.ChangeAnimation(FActionAnim, FActionForce and (FModel.Animation <> A_STAND));
4257 if (FModel.AnimState.Played or ((not FActionChanged) and (FModel.Animation = A_WALK)))
4258 then SetAction(A_STAND, True);
4260 if not ((FModel.Animation = A_WALK) and (Abs(FObj.Vel.X) < 4) and not FModel.GetFire()) then FModel.Update;
4262 for b := Low(FKeys) to High(FKeys) do
4263 if FKeys[b].Time = 0 then FKeys[b].Pressed := False else Dec(FKeys[b].Time);
4264 end;
4267 procedure TPlayer.getMapBox (out x, y, w, h: Integer); inline;
4268 begin
4269 x := FObj.X+PLAYER_RECT.X;
4270 y := FObj.Y+PLAYER_RECT.Y;
4271 w := PLAYER_RECT.Width;
4272 h := PLAYER_RECT.Height;
4273 end;
4276 procedure TPlayer.moveBy (dx, dy: Integer); inline;
4277 begin
4278 if (dx <> 0) or (dy <> 0) then
4279 begin
4280 FObj.X += dx;
4281 FObj.Y += dy;
4282 positionChanged();
4283 end;
4284 end;
4287 function TPlayer.Collide(X, Y: Integer; Width, Height: Word): Boolean;
4288 begin
4289 Result := g_Collide(FObj.X+PLAYER_RECT.X,
4290 FObj.Y+PLAYER_RECT.Y,
4291 PLAYER_RECT.Width,
4292 PLAYER_RECT.Height,
4293 X, Y,
4294 Width, Height);
4295 end;
4297 function TPlayer.Collide(Panel: TPanel): Boolean;
4298 begin
4299 Result := g_Collide(FObj.X+PLAYER_RECT.X,
4300 FObj.Y+PLAYER_RECT.Y,
4301 PLAYER_RECT.Width,
4302 PLAYER_RECT.Height,
4303 Panel.X, Panel.Y,
4304 Panel.Width, Panel.Height);
4305 end;
4307 function TPlayer.Collide(X, Y: Integer): Boolean;
4308 begin
4309 X := X-FObj.X-PLAYER_RECT.X;
4310 Y := Y-FObj.Y-PLAYER_RECT.Y;
4311 Result := (x >= 0) and (x <= PLAYER_RECT.Width) and
4312 (y >= 0) and (y <= PLAYER_RECT.Height);
4313 end;
4315 function g_Player_ValidName(Name: string): Boolean;
4316 var
4317 a: Integer;
4318 begin
4319 Result := True;
4321 if gPlayers = nil then Exit;
4323 for a := 0 to High(gPlayers) do
4324 if gPlayers[a] <> nil then
4325 if LowerCase(Name) = LowerCase(gPlayers[a].FName) then
4326 begin
4327 Result := False;
4328 Exit;
4329 end;
4330 end;
4332 procedure TPlayer.SetDirection(Direction: TDirection);
4333 var
4334 d: TDirection;
4335 begin
4336 d := FModel.Direction;
4338 FModel.Direction := Direction;
4339 if d <> Direction then FModel.ChangeAnimation(FModel.Animation, True);
4341 FDirection := Direction;
4342 end;
4344 function TPlayer.GetKeys(): Byte;
4345 begin
4346 Result := 0;
4348 if R_KEY_RED in FRulez then Result := KEY_RED;
4349 if R_KEY_GREEN in FRulez then Result := Result or KEY_GREEN;
4350 if R_KEY_BLUE in FRulez then Result := Result or KEY_BLUE;
4352 if FTeam = TEAM_RED then Result := Result or KEY_REDTEAM;
4353 if FTeam = TEAM_BLUE then Result := Result or KEY_BLUETEAM;
4354 end;
4356 procedure TPlayer.Use();
4357 var
4358 a: Integer;
4359 begin
4360 if FTime[T_USE] > gTime then Exit;
4362 g_Triggers_PressR(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width,
4363 PLAYER_RECT.Height, FUID, ACTIVATE_PLAYERPRESS);
4365 for a := 0 to High(gPlayers) do
4366 if (gPlayers[a] <> nil) and (gPlayers[a] <> Self) and
4367 gPlayers[a].alive and SameTeam(FUID, gPlayers[a].FUID) and
4368 g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
4369 FObj.Rect.Width, FObj.Rect.Height, @gPlayers[a].FObj) then
4370 begin
4371 gPlayers[a].Touch();
4372 if g_Game_IsNet and g_Game_IsServer then
4373 MH_SEND_GameEvent(NET_EV_PLAYER_TOUCH, gPlayers[a].FUID);
4374 end;
4376 FTime[T_USE] := gTime+120;
4377 end;
4379 procedure TPlayer.NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1);
4380 var
4381 locObj: TObj;
4382 visible: Boolean = True;
4383 WX, WY, XD, YD: Integer;
4384 begin
4385 WX := X;
4386 WY := Y;
4387 XD := AX;
4388 YD := AY;
4390 case FCurrWeap of
4391 WEAPON_KASTET:
4392 begin
4393 visible := False;
4394 DoPunch();
4395 if R_BERSERK in FRulez then
4396 begin
4397 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
4398 locobj.X := FObj.X+FObj.Rect.X;
4399 locobj.Y := FObj.Y+FObj.Rect.Y;
4400 locobj.rect.X := 0;
4401 locobj.rect.Y := 0;
4402 locobj.rect.Width := 39;
4403 locobj.rect.Height := 52;
4404 locobj.Vel.X := (xd-wx) div 2;
4405 locobj.Vel.Y := (yd-wy) div 2;
4406 locobj.Accel.X := xd-wx;
4407 locobj.Accel.y := yd-wy;
4409 if g_Weapon_Hit(@locobj, 50, FUID, HIT_SOME) <> 0 then
4410 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj.X, FObj.Y)
4411 else
4412 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj.X, FObj.Y);
4414 if gFlash = 1 then
4415 if FPain < 50 then
4416 FPain := min(FPain + 25, 50);
4417 end else
4418 g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 3, FUID);
4419 end;
4421 WEAPON_SAW:
4422 begin
4423 if g_Weapon_chainsaw(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
4424 IfThen(gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF], 9, 3), FUID) <> 0 then
4425 begin
4426 FSawSoundSelect.Stop();
4427 FSawSound.Stop();
4428 FSawSoundHit.PlayAt(FObj.X, FObj.Y);
4429 end
4430 else if not FSawSoundHit.IsPlaying() then
4431 begin
4432 FSawSoundSelect.Stop();
4433 FSawSound.PlayAt(FObj.X, FObj.Y);
4434 end;
4435 end;
4437 WEAPON_PISTOL:
4438 begin
4439 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', GameX, Gamey);
4440 FFireAngle := FAngle;
4441 {$IFDEF ENABLE_SHELLS}
4442 g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_BULLET);
4443 {$ENDIF}
4444 end;
4446 WEAPON_SHOTGUN1:
4447 begin
4448 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex, Gamey);
4449 FFireAngle := FAngle;
4450 {$IFDEF ENABLE_SHELLS}
4451 FShellTimer := 10;
4452 FShellType := SHELL_SHELL;
4453 {$ENDIF}
4454 end;
4456 WEAPON_SHOTGUN2:
4457 begin
4458 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', Gamex, Gamey);
4459 FFireAngle := FAngle;
4460 {$IFDEF ENABLE_SHELLS}
4461 FShellTimer := 13;
4462 FShellType := SHELL_DBLSHELL;
4463 {$ENDIF}
4464 end;
4466 WEAPON_CHAINGUN:
4467 begin
4468 g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', Gamex, Gamey);
4469 FFireAngle := FAngle;
4470 {$IFDEF ENABLE_SHELLS}
4471 g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_BULLET);
4472 {$ENDIF}
4473 end;
4475 WEAPON_ROCKETLAUNCHER:
4476 begin
4477 g_Weapon_Rocket(wx, wy, xd, yd, FUID, WID);
4478 FFireAngle := FAngle;
4479 end;
4481 WEAPON_PLASMA:
4482 begin
4483 g_Weapon_Plasma(wx, wy, xd, yd, FUID, WID);
4484 FFireAngle := FAngle;
4485 end;
4487 WEAPON_BFG:
4488 begin
4489 g_Weapon_BFGShot(wx, wy, xd, yd, FUID, WID);
4490 FFireAngle := FAngle;
4491 end;
4493 WEAPON_SUPERPULEMET:
4494 begin
4495 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex, Gamey);
4496 FFireAngle := FAngle;
4497 {$IFDEF ENABLE_SHELLS}
4498 g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_SHELL);
4499 {$ENDIF}
4500 end;
4502 WEAPON_FLAMETHROWER:
4503 begin
4504 g_Weapon_flame(wx, wy, xd, yd, FUID, WID);
4505 FlamerOn;
4506 FFireAngle := FAngle;
4507 end;
4508 end;
4510 if not visible then Exit;
4512 if (FAngle = 0) or (FAngle = 180) then SetAction(A_ATTACK)
4513 else if (FAngle = ANGLE_LEFTDOWN) or (FAngle = ANGLE_RIGHTDOWN) then SetAction(A_ATTACKDOWN)
4514 else if (FAngle = ANGLE_LEFTUP) or (FAngle = ANGLE_RIGHTUP) then SetAction(A_ATTACKUP);
4515 end;
4517 procedure TPlayer.DoLerp(Level: Integer = 2);
4518 begin
4519 if FObj.X <> FXTo then FObj.X := Lerp(FObj.X, FXTo, Level);
4520 if FObj.Y <> FYTo then FObj.Y := Lerp(FObj.Y, FYTo, Level);
4521 end;
4523 procedure TPlayer.SetLerp(XTo, YTo: Integer);
4524 var
4525 AX, AY: Integer;
4526 begin
4527 FXTo := XTo;
4528 FYTo := YTo;
4529 if FJustTeleported or (NetInterpLevel < 1) then
4530 begin
4531 FObj.X := XTo;
4532 FObj.Y := YTo;
4533 if FJustTeleported then
4534 begin
4535 FObj.oldX := FObj.X;
4536 FObj.oldY := FObj.Y;
4537 end;
4538 end
4539 else
4540 begin
4541 AX := Abs(FXTo - FObj.X);
4542 AY := Abs(FYTo - FObj.Y);
4543 if (AX > 32) or (AX <= NetInterpLevel) then
4544 FObj.X := FXTo;
4545 if (AY > 32) or (AY <= NetInterpLevel) then
4546 FObj.Y := FYTo;
4547 end;
4548 end;
4550 function TPlayer.FullInLift(XInc, YInc: Integer): Integer;
4551 begin
4552 if g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
4553 PLAYER_RECT.Width, PLAYER_RECT.Height-8,
4554 PANEL_LIFTUP, False) then Result := -1
4555 else
4556 if g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
4557 PLAYER_RECT.Width, PLAYER_RECT.Height-8,
4558 PANEL_LIFTDOWN, False) then Result := 1
4559 else Result := 0;
4560 end;
4562 function TPlayer.GetFlag(Flag: Byte): Boolean;
4563 var
4564 s, ts: String;
4565 evtype, a: Byte;
4566 begin
4567 Result := False;
4569 if Flag = FLAG_NONE then
4570 Exit;
4572 if not g_Game_IsServer then Exit;
4574 // Принес чужой флаг на свою базу:
4575 if (Flag = FTeam) and
4576 (gFlags[Flag].State = FLAG_STATE_NORMAL) and
4577 (FFlag <> FLAG_NONE) then
4578 begin
4579 if FFlag = FLAG_RED then
4580 s := _lc[I_PLAYER_FLAG_RED]
4581 else
4582 s := _lc[I_PLAYER_FLAG_BLUE];
4584 evtype := FLAG_STATE_SCORED;
4586 ts := Format('%.4d', [gFlags[FFlag].CaptureTime]);
4587 Insert('.', ts, Length(ts) + 1 - 3);
4588 g_Console_Add(Format(_lc[I_PLAYER_FLAG_CAPTURE], [FName, s, ts]), True);
4590 g_Map_ResetFlag(FFlag);
4591 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_CAPTURE], [AnsiUpperCase(s)]), 144);
4593 if ((Self = gPlayer1) or (Self = gPlayer2)
4594 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
4595 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
4596 a := 0
4597 else
4598 a := 1;
4600 if not sound_cap_flag[a].IsPlaying() then
4601 sound_cap_flag[a].Play();
4603 gTeamStat[FTeam].Score += 1;
4605 Result := True;
4606 if g_Game_IsNet then
4607 begin
4608 MH_SEND_FlagEvent(evtype, FFlag, FUID, False);
4609 MH_SEND_GameStats;
4610 end;
4612 gFlags[FFlag].CaptureTime := 0;
4613 SetFlag(FLAG_NONE);
4614 Exit;
4615 end;
4617 // Подобрал свой флаг - вернул его на базу:
4618 if (Flag = FTeam) and
4619 (gFlags[Flag].State = FLAG_STATE_DROPPED) then
4620 begin
4621 if Flag = FLAG_RED then
4622 s := _lc[I_PLAYER_FLAG_RED]
4623 else
4624 s := _lc[I_PLAYER_FLAG_BLUE];
4626 evtype := FLAG_STATE_RETURNED;
4627 gFlags[Flag].CaptureTime := 0;
4629 g_Console_Add(Format(_lc[I_PLAYER_FLAG_RETURN], [FName, s]), True);
4631 g_Map_ResetFlag(Flag);
4632 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
4634 if ((Self = gPlayer1) or (Self = gPlayer2)
4635 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
4636 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
4637 a := 0
4638 else
4639 a := 1;
4641 if not sound_ret_flag[a].IsPlaying() then
4642 sound_ret_flag[a].Play();
4644 Result := True;
4645 if g_Game_IsNet then
4646 begin
4647 MH_SEND_FlagEvent(evtype, Flag, FUID, False);
4648 MH_SEND_GameStats;
4649 end;
4650 Exit;
4651 end;
4653 // Подобрал чужой флаг:
4654 if (Flag <> FTeam) and (FTime[T_FLAGCAP] <= gTime) then
4655 begin
4656 SetFlag(Flag);
4658 if Flag = FLAG_RED then
4659 s := _lc[I_PLAYER_FLAG_RED]
4660 else
4661 s := _lc[I_PLAYER_FLAG_BLUE];
4663 evtype := FLAG_STATE_CAPTURED;
4665 g_Console_Add(Format(_lc[I_PLAYER_FLAG_GET], [FName, s]), True);
4667 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_GET], [AnsiUpperCase(s)]), 144);
4669 gFlags[Flag].State := FLAG_STATE_CAPTURED;
4671 if ((Self = gPlayer1) or (Self = gPlayer2)
4672 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
4673 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
4674 a := 0
4675 else
4676 a := 1;
4678 if not sound_get_flag[a].IsPlaying() then
4679 sound_get_flag[a].Play();
4681 Result := True;
4682 if g_Game_IsNet then
4683 begin
4684 MH_SEND_FlagEvent(evtype, Flag, FUID, False);
4685 MH_SEND_GameStats;
4686 end;
4687 end;
4688 end;
4690 procedure TPlayer.SetFlag(Flag: Byte);
4691 begin
4692 FFlag := Flag;
4693 if FModel <> nil then
4694 FModel.SetFlag(FFlag);
4695 end;
4697 function TPlayer.TryDropFlag(): Boolean;
4698 begin
4699 if LongBool(gGameSettings.Options and GAME_OPTION_ALLOWDROPFLAG) then
4700 Result := DropFlag(False, LongBool(gGameSettings.Options and GAME_OPTION_THROWFLAG))
4701 else
4702 Result := False;
4703 end;
4705 function TPlayer.DropFlag(Silent: Boolean = True; DoThrow: Boolean = False): Boolean;
4706 var
4707 s: String;
4708 a: Byte;
4709 xv, yv: Integer;
4710 begin
4711 Result := False;
4712 if (not g_Game_IsServer) or (FFlag = FLAG_NONE) then
4713 Exit;
4714 FTime[T_FLAGCAP] := gTime + 2000;
4715 with gFlags[FFlag] do
4716 begin
4717 Obj.X := FObj.X;
4718 Obj.Y := FObj.Y;
4719 Direction := FDirection;
4720 State := FLAG_STATE_DROPPED;
4721 Count := FLAG_TIME;
4722 if DoThrow then
4723 begin
4724 xv := FObj.Vel.X + IfThen(Direction = TDirection.D_RIGHT, 10, -10);
4725 yv := FObj.Vel.Y - 2;
4726 end
4727 else
4728 begin
4729 xv := (FObj.Vel.X div 2);
4730 yv := (FObj.Vel.Y div 2) - 2;
4731 end;
4732 g_Obj_Push(@Obj, xv, yv);
4734 positionChanged(); // this updates spatial accelerators
4736 if FFlag = FLAG_RED then
4737 s := _lc[I_PLAYER_FLAG_RED]
4738 else
4739 s := _lc[I_PLAYER_FLAG_BLUE];
4741 g_Console_Add(Format(_lc[I_PLAYER_FLAG_DROP], [FName, s]), True);
4742 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_DROP], [AnsiUpperCase(s)]), 144);
4744 if ((Self = gPlayer1) or (Self = gPlayer2)
4745 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
4746 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
4747 a := 0
4748 else
4749 a := 1;
4751 if (not Silent) and (not sound_lost_flag[a].IsPlaying()) then
4752 sound_lost_flag[a].Play();
4754 if g_Game_IsNet then
4755 MH_SEND_FlagEvent(FLAG_STATE_DROPPED, Flag, FUID, False);
4756 end;
4757 SetFlag(FLAG_NONE);
4758 Result := True;
4759 end;
4761 procedure TPlayer.GetSecret();
4762 begin
4763 if (self = gPlayer1) or (self = gPlayer2) then
4764 begin
4765 g_Console_Add(Format(_lc[I_PLAYER_SECRET], [FName]), True);
4766 g_Sound_PlayEx('SOUND_GAME_SECRET');
4767 end;
4768 Inc(FSecrets);
4769 end;
4771 procedure TPlayer.PressKey(Key: Byte; Time: Word = 1);
4772 begin
4773 Assert(Key <= High(FKeys));
4775 FKeys[Key].Pressed := True;
4776 FKeys[Key].Time := Time;
4777 end;
4779 function TPlayer.IsKeyPressed(K: Byte): Boolean;
4780 begin
4781 Result := FKeys[K].Pressed;
4782 end;
4784 procedure TPlayer.ReleaseKeys();
4785 var
4786 a: Integer;
4787 begin
4788 for a := Low(FKeys) to High(FKeys) do
4789 begin
4790 FKeys[a].Pressed := False;
4791 FKeys[a].Time := 0;
4792 end;
4793 end;
4795 procedure TPlayer.OnDamage(Angle: SmallInt);
4796 begin
4797 end;
4799 function TPlayer.firediry(): Integer;
4800 begin
4801 if FKeys[KEY_UP].Pressed then Result := -42
4802 else if FKeys[KEY_DOWN].Pressed then Result := 19
4803 else Result := 0;
4804 end;
4806 procedure TPlayer.RememberState();
4807 var
4808 i: Integer;
4809 SavedState: TPlayerSavedState;
4810 begin
4811 SavedState.Health := FHealth;
4812 SavedState.Armor := FArmor;
4813 SavedState.Air := FAir;
4814 SavedState.JetFuel := FJetFuel;
4815 SavedState.CurrWeap := FCurrWeap;
4816 SavedState.NextWeap := FNextWeap;
4817 SavedState.NextWeapDelay := FNextWeapDelay;
4818 for i := Low(FWeapon) to High(FWeapon) do
4819 SavedState.Weapon[i] := FWeapon[i];
4820 for i := Low(FAmmo) to High(FAmmo) do
4821 SavedState.Ammo[i] := FAmmo[i];
4822 for i := Low(FMaxAmmo) to High(FMaxAmmo) do
4823 SavedState.MaxAmmo[i] := FMaxAmmo[i];
4824 SavedState.Rulez := FRulez - [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE];
4826 FSavedStateNum := -1;
4827 for i := Low(SavedStates) to High(SavedStates) do
4828 if not SavedStates[i].Used then
4829 begin
4830 FSavedStateNum := i;
4831 break;
4832 end;
4833 if FSavedStateNum < 0 then
4834 begin
4835 SetLength(SavedStates, Length(SavedStates) + 1);
4836 FSavedStateNum := High(SavedStates);
4837 end;
4839 SavedState.Used := True;
4840 SavedStates[FSavedStateNum] := SavedState;
4841 end;
4843 procedure TPlayer.RecallState();
4844 var
4845 i: Integer;
4846 SavedState: TPlayerSavedState;
4847 begin
4848 if(FSavedStateNum < 0) or (FSavedStateNum > High(SavedStates)) then
4849 Exit;
4851 SavedState := SavedStates[FSavedStateNum];
4852 SavedStates[FSavedStateNum].Used := False;
4853 FSavedStateNum := -1;
4855 FHealth := SavedState.Health;
4856 FArmor := SavedState.Armor;
4857 FAir := SavedState.Air;
4858 FJetFuel := SavedState.JetFuel;
4859 FCurrWeap := SavedState.CurrWeap;
4860 FNextWeap := SavedState.NextWeap;
4861 FNextWeapDelay := SavedState.NextWeapDelay;
4862 for i := Low(FWeapon) to High(FWeapon) do
4863 FWeapon[i] := SavedState.Weapon[i];
4864 for i := Low(FAmmo) to High(FAmmo) do
4865 FAmmo[i] := SavedState.Ammo[i];
4866 for i := Low(FMaxAmmo) to High(FMaxAmmo) do
4867 FMaxAmmo[i] := SavedState.MaxAmmo[i];
4868 FRulez := SavedState.Rulez;
4870 if gGameSettings.GameType = GT_SERVER then
4871 MH_SEND_PlayerStats(FUID);
4872 end;
4874 procedure TPlayer.SaveState (st: TStream);
4875 var
4876 i: Integer;
4877 b: Byte;
4878 begin
4879 // Сигнатура игрока
4880 utils.writeSign(st, 'PLYR');
4881 utils.writeInt(st, Byte(PLR_SAVE_VERSION)); // version
4882 // Бот или человек
4883 utils.writeBool(st, FIamBot);
4884 // UID игрока
4885 utils.writeInt(st, Word(FUID));
4886 // Имя игрока
4887 utils.writeStr(st, FName);
4888 // Команда
4889 utils.writeInt(st, Byte(FTeam));
4890 // Жив ли
4891 utils.writeBool(st, FAlive);
4892 // Израсходовал ли все жизни
4893 utils.writeBool(st, FNoRespawn);
4894 // Направление
4895 if FDirection = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT
4896 utils.writeInt(st, Byte(b));
4897 // Здоровье
4898 utils.writeInt(st, LongInt(FHealth));
4899 // Коэффициент инвалидности
4900 utils.writeInt(st, LongInt(FHandicap));
4901 // Жизни
4902 utils.writeInt(st, Byte(FLives));
4903 // Броня
4904 utils.writeInt(st, LongInt(FArmor));
4905 // Запас воздуха
4906 utils.writeInt(st, LongInt(FAir));
4907 // Запас горючего
4908 utils.writeInt(st, LongInt(FJetFuel));
4909 // Боль
4910 utils.writeInt(st, LongInt(FPain));
4911 // Убил
4912 utils.writeInt(st, LongInt(FKills));
4913 // Убил монстров
4914 utils.writeInt(st, LongInt(FMonsterKills));
4915 // Фрагов
4916 utils.writeInt(st, LongInt(FFrags));
4917 // Фрагов подряд
4918 utils.writeInt(st, Byte(FFragCombo));
4919 // Время последнего фрага
4920 utils.writeInt(st, LongWord(FLastFrag));
4921 // Смертей
4922 utils.writeInt(st, LongInt(FDeath));
4923 // Какой флаг несет
4924 utils.writeInt(st, Byte(FFlag));
4925 // Нашел секретов
4926 utils.writeInt(st, LongInt(FSecrets));
4927 // Текущее оружие
4928 utils.writeInt(st, Byte(FCurrWeap));
4929 // Желаемое оружие
4930 utils.writeInt(st, Word(FNextWeap));
4931 // ...и пауза
4932 utils.writeInt(st, Byte(FNextWeapDelay));
4933 // Время зарядки BFG
4934 utils.writeInt(st, SmallInt(FBFGFireCounter));
4935 // Буфер урона
4936 utils.writeInt(st, LongInt(FDamageBuffer));
4937 // Последний ударивший
4938 utils.writeInt(st, Word(FLastSpawnerUID));
4939 // Тип последнего полученного урона
4940 utils.writeInt(st, Byte(FLastHit));
4941 // Объект игрока
4942 Obj_SaveState(st, @FObj);
4943 // Текущее количество патронов
4944 for i := A_BULLETS to A_HIGH do utils.writeInt(st, Word(FAmmo[i]));
4945 // Максимальное количество патронов
4946 for i := A_BULLETS to A_HIGH do utils.writeInt(st, Word(FMaxAmmo[i]));
4947 // Наличие оружия
4948 for i := WP_FIRST to WP_LAST do utils.writeBool(st, FWeapon[i]);
4949 // Время перезарядки оружия
4950 for i := WP_FIRST to WP_LAST do utils.writeInt(st, Word(FReloading[i]));
4951 // Наличие рюкзака
4952 utils.writeBool(st, (R_ITEM_BACKPACK in FRulez));
4953 // Наличие красного ключа
4954 utils.writeBool(st, (R_KEY_RED in FRulez));
4955 // Наличие зеленого ключа
4956 utils.writeBool(st, (R_KEY_GREEN in FRulez));
4957 // Наличие синего ключа
4958 utils.writeBool(st, (R_KEY_BLUE in FRulez));
4959 // Наличие берсерка
4960 utils.writeBool(st, (R_BERSERK in FRulez));
4961 // Время действия специальных предметов
4962 for i := MR_SUIT to MR_MAX do utils.writeInt(st, LongWord(FMegaRulez[i]));
4963 // Время до повторного респауна, смены оружия, исользования, захвата флага
4964 for i := T_RESPAWN to T_FLAGCAP do utils.writeInt(st, LongWord(FTime[i]));
4965 // Название модели
4966 utils.writeStr(st, FModel.GetName());
4967 // Цвет модели
4968 utils.writeInt(st, Byte(FColor.R));
4969 utils.writeInt(st, Byte(FColor.G));
4970 utils.writeInt(st, Byte(FColor.B));
4971 end;
4974 procedure TPlayer.LoadState (st: TStream);
4975 var
4976 i: Integer;
4977 str: String;
4978 b: Byte;
4979 begin
4980 assert(st <> nil);
4982 // Сигнатура игрока
4983 if not utils.checkSign(st, 'PLYR') then raise XStreamError.Create('invalid player signature');
4984 if (utils.readByte(st) <> PLR_SAVE_VERSION) then raise XStreamError.Create('invalid player version');
4985 // Бот или человек:
4986 FIamBot := utils.readBool(st);
4987 // UID игрока
4988 FUID := utils.readWord(st);
4989 // Имя игрока
4990 str := utils.readStr(st);
4991 if (self <> gPlayer1) and (self <> gPlayer2) then FName := str;
4992 // Команда
4993 FTeam := utils.readByte(st);
4994 // Жив ли
4995 FAlive := utils.readBool(st);
4996 // Израсходовал ли все жизни
4997 FNoRespawn := utils.readBool(st);
4998 // Направление
4999 b := utils.readByte(st);
5000 if b = 1 then FDirection := TDirection.D_LEFT else FDirection := TDirection.D_RIGHT; // b = 2
5001 // Здоровье
5002 FHealth := utils.readLongInt(st);
5003 // Коэффициент инвалидности
5004 FHandicap := utils.readLongInt(st);
5005 // Жизни
5006 FLives := utils.readByte(st);
5007 // Броня
5008 FArmor := utils.readLongInt(st);
5009 // Запас воздуха
5010 FAir := utils.readLongInt(st);
5011 // Запас горючего
5012 FJetFuel := utils.readLongInt(st);
5013 // Боль
5014 FPain := utils.readLongInt(st);
5015 // Убил
5016 FKills := utils.readLongInt(st);
5017 // Убил монстров
5018 FMonsterKills := utils.readLongInt(st);
5019 // Фрагов
5020 FFrags := utils.readLongInt(st);
5021 // Фрагов подряд
5022 FFragCombo := utils.readByte(st);
5023 // Время последнего фрага
5024 FLastFrag := utils.readLongWord(st);
5025 // Смертей
5026 FDeath := utils.readLongInt(st);
5027 // Какой флаг несет
5028 FFlag := utils.readByte(st);
5029 // Нашел секретов
5030 FSecrets := utils.readLongInt(st);
5031 // Текущее оружие
5032 FCurrWeap := utils.readByte(st);
5033 // Желаемое оружие
5034 FNextWeap := utils.readWord(st);
5035 // ...и пауза
5036 FNextWeapDelay := utils.readByte(st);
5037 // Время зарядки BFG
5038 FBFGFireCounter := utils.readSmallInt(st);
5039 // Буфер урона
5040 FDamageBuffer := utils.readLongInt(st);
5041 // Последний ударивший
5042 FLastSpawnerUID := utils.readWord(st);
5043 // Тип последнего полученного урона
5044 FLastHit := utils.readByte(st);
5045 // Объект игрока
5046 Obj_LoadState(@FObj, st);
5047 // Текущее количество патронов
5048 for i := A_BULLETS to A_HIGH do FAmmo[i] := utils.readWord(st);
5049 // Максимальное количество патронов
5050 for i := A_BULLETS to A_HIGH do FMaxAmmo[i] := utils.readWord(st);
5051 // Наличие оружия
5052 for i := WP_FIRST to WP_LAST do FWeapon[i] := utils.readBool(st);
5053 // Время перезарядки оружия
5054 for i := WP_FIRST to WP_LAST do FReloading[i] := utils.readWord(st);
5055 // Наличие рюкзака
5056 if utils.readBool(st) then Include(FRulez, R_ITEM_BACKPACK);
5057 // Наличие красного ключа
5058 if utils.readBool(st) then Include(FRulez, R_KEY_RED);
5059 // Наличие зеленого ключа
5060 if utils.readBool(st) then Include(FRulez, R_KEY_GREEN);
5061 // Наличие синего ключа
5062 if utils.readBool(st) then Include(FRulez, R_KEY_BLUE);
5063 // Наличие берсерка
5064 if utils.readBool(st) then Include(FRulez, R_BERSERK);
5065 // Время действия специальных предметов
5066 for i := MR_SUIT to MR_MAX do FMegaRulez[i] := utils.readLongWord(st);
5067 // Время до повторного респауна, смены оружия, исользования, захвата флага
5068 for i := T_RESPAWN to T_FLAGCAP do FTime[i] := utils.readLongWord(st);
5069 // Название модели
5070 str := utils.readStr(st);
5071 // Цвет модели
5072 FColor.R := utils.readByte(st);
5073 FColor.G := utils.readByte(st);
5074 FColor.B := utils.readByte(st);
5075 if (self = gPlayer1) then
5076 begin
5077 str := gPlayer1Settings.Model;
5078 FColor := gPlayer1Settings.Color;
5079 end
5080 else if (self = gPlayer2) then
5081 begin
5082 str := gPlayer2Settings.Model;
5083 FColor := gPlayer2Settings.Color;
5084 end;
5085 // Обновляем модель игрока
5086 SetModel(str);
5087 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
5088 FModel.Color := TEAMCOLOR[FTeam]
5089 else
5090 FModel.Color := FColor;
5091 end;
5094 procedure TPlayer.AllRulez(Health: Boolean);
5095 var
5096 a: Integer;
5097 begin
5098 if Health then
5099 begin
5100 FHealth := PLAYER_HP_LIMIT;
5101 FArmor := PLAYER_AP_LIMIT;
5102 Exit;
5103 end;
5105 for a := WP_FIRST to WP_LAST do FWeapon[a] := True;
5106 for a := A_BULLETS to A_HIGH do FAmmo[a] := 30000;
5107 FRulez := FRulez+[R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE];
5108 end;
5110 procedure TPlayer.RestoreHealthArmor();
5111 begin
5112 FHealth := PLAYER_HP_LIMIT;
5113 FArmor := PLAYER_AP_LIMIT;
5114 end;
5116 procedure TPlayer.FragCombo();
5117 var
5118 Param: Integer;
5119 begin
5120 if (gGameSettings.GameMode in [GM_COOP, GM_SINGLE]) or g_Game_IsClient then
5121 Exit;
5122 if gTime - FLastFrag < FRAG_COMBO_TIME then
5123 begin
5124 if FFragCombo < 5 then
5125 Inc(FFragCombo);
5126 Param := FUID or (FFragCombo shl 16);
5127 if (FComboEvnt >= Low(gDelayedEvents)) and
5128 (FComboEvnt <= High(gDelayedEvents)) and
5129 gDelayedEvents[FComboEvnt].Pending and
5130 (gDelayedEvents[FComboEvnt].DEType = DE_KILLCOMBO) and
5131 (gDelayedEvents[FComboEvnt].DENum and $FFFF = FUID) then
5132 begin
5133 gDelayedEvents[FComboEvnt].Time := gTime + 500;
5134 gDelayedEvents[FComboEvnt].DENum := Param;
5135 end
5136 else
5137 FComboEvnt := g_Game_DelayEvent(DE_KILLCOMBO, 500, Param);
5138 end
5139 else
5140 FFragCombo := 1;
5142 FLastFrag := gTime;
5143 end;
5145 procedure TPlayer.GiveItem(ItemType: Byte);
5146 begin
5147 case ItemType of
5148 ITEM_SUIT:
5149 if FMegaRulez[MR_SUIT] < gTime+PLAYER_SUIT_TIME then
5150 begin
5151 FMegaRulez[MR_SUIT] := gTime+PLAYER_SUIT_TIME;
5152 end;
5154 ITEM_OXYGEN:
5155 if FAir < AIR_MAX then
5156 begin
5157 FAir := AIR_MAX;
5158 end;
5160 ITEM_MEDKIT_BLACK:
5161 begin
5162 if not (R_BERSERK in FRulez) then
5163 begin
5164 Include(FRulez, R_BERSERK);
5165 if FBFGFireCounter < 1 then
5166 begin
5167 FCurrWeap := WEAPON_KASTET;
5168 resetWeaponQueue();
5169 FModel.SetWeapon(WEAPON_KASTET);
5170 end;
5171 if gFlash <> 0 then
5172 Inc(FPain, 100);
5173 FBerserk := gTime+30000;
5174 end;
5175 if FHealth < PLAYER_HP_SOFT then
5176 begin
5177 FHealth := PLAYER_HP_SOFT;
5178 FBerserk := gTime+30000;
5179 end;
5180 end;
5182 ITEM_INVUL:
5183 if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
5184 begin
5185 FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
5186 FSpawnInvul := 0;
5187 end;
5189 ITEM_INVIS:
5190 if FMegaRulez[MR_INVIS] < gTime+PLAYER_INVIS_TIME then
5191 begin
5192 FMegaRulez[MR_INVIS] := gTime+PLAYER_INVIS_TIME;
5193 end;
5195 ITEM_JETPACK:
5196 if FJetFuel < JET_MAX then
5197 begin
5198 FJetFuel := JET_MAX;
5199 end;
5201 ITEM_MEDKIT_SMALL: if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 10, PLAYER_HP_SOFT);
5202 ITEM_MEDKIT_LARGE: if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 25, PLAYER_HP_SOFT);
5204 ITEM_ARMOR_GREEN: if FArmor < PLAYER_AP_SOFT then FArmor := PLAYER_AP_SOFT;
5205 ITEM_ARMOR_BLUE: if FArmor < PLAYER_AP_LIMIT then FArmor := PLAYER_AP_LIMIT;
5207 ITEM_SPHERE_BLUE: if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 100, PLAYER_HP_LIMIT);
5208 ITEM_SPHERE_WHITE:
5209 if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) then
5210 begin
5211 if FHealth < PLAYER_HP_LIMIT then FHealth := PLAYER_HP_LIMIT;
5212 if FArmor < PLAYER_AP_LIMIT then FArmor := PLAYER_AP_LIMIT;
5213 end;
5215 ITEM_WEAPON_SAW: FWeapon[WEAPON_SAW] := True;
5216 ITEM_WEAPON_SHOTGUN1: FWeapon[WEAPON_SHOTGUN1] := True;
5217 ITEM_WEAPON_SHOTGUN2: FWeapon[WEAPON_SHOTGUN2] := True;
5218 ITEM_WEAPON_CHAINGUN: FWeapon[WEAPON_CHAINGUN] := True;
5219 ITEM_WEAPON_ROCKETLAUNCHER: FWeapon[WEAPON_ROCKETLAUNCHER] := True;
5220 ITEM_WEAPON_PLASMA: FWeapon[WEAPON_PLASMA] := True;
5221 ITEM_WEAPON_BFG: FWeapon[WEAPON_BFG] := True;
5222 ITEM_WEAPON_SUPERPULEMET: FWeapon[WEAPON_SUPERPULEMET] := True;
5223 ITEM_WEAPON_FLAMETHROWER: FWeapon[WEAPON_FLAMETHROWER] := True;
5225 ITEM_AMMO_BULLETS: if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
5226 ITEM_AMMO_BULLETS_BOX: if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
5227 ITEM_AMMO_SHELLS: if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
5228 ITEM_AMMO_SHELLS_BOX: if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 25, FMaxAmmo[A_SHELLS]);
5229 ITEM_AMMO_ROCKET: if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
5230 ITEM_AMMO_ROCKET_BOX: if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 5, FMaxAmmo[A_ROCKETS]);
5231 ITEM_AMMO_CELL: if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
5232 ITEM_AMMO_CELL_BIG: if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 100, FMaxAmmo[A_CELLS]);
5233 ITEM_AMMO_FUELCAN: if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
5235 ITEM_AMMO_BACKPACK:
5236 if (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or
5237 (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or
5238 (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or
5239 (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or
5240 (FMaxAmmo[A_FUEL] < AmmoLimits[1, A_FUEL]) then
5241 begin
5242 FMaxAmmo[A_BULLETS] := AmmoLimits[1, A_BULLETS];
5243 FMaxAmmo[A_SHELLS] := AmmoLimits[1, A_SHELLS];
5244 FMaxAmmo[A_ROCKETS] := AmmoLimits[1, A_ROCKETS];
5245 FMaxAmmo[A_CELLS] := AmmoLimits[1, A_CELLS];
5246 FMaxAmmo[A_FUEL] := AmmoLimits[1, A_FUEL];
5248 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
5249 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
5250 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
5251 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
5253 FRulez := FRulez + [R_ITEM_BACKPACK];
5254 end;
5256 ITEM_KEY_RED: if not (R_KEY_RED in FRulez) then Include(FRulez, R_KEY_RED);
5257 ITEM_KEY_GREEN: if not (R_KEY_GREEN in FRulez) then Include(FRulez, R_KEY_GREEN);
5258 ITEM_KEY_BLUE: if not (R_KEY_BLUE in FRulez) then Include(FRulez, R_KEY_BLUE);
5260 ITEM_BOTTLE: if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 4, PLAYER_HP_LIMIT);
5261 ITEM_HELMET: if FArmor < PLAYER_AP_LIMIT then IncMax(FArmor, 5, PLAYER_AP_LIMIT);
5263 else
5264 Exit;
5265 end;
5266 if g_Game_IsNet and g_Game_IsServer then
5267 MH_SEND_PlayerStats(FUID);
5268 end;
5270 procedure TPlayer.FlySmoke(Times: DWORD = 1);
5271 var i: DWORD;
5272 begin
5273 if (Random(5) = 1) and (Times = 1) then
5274 Exit;
5276 if BodyInLiquid(0, 0) then
5277 begin
5278 {$IFDEF ENABLE_GFX}
5279 g_GFX_Bubbles(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)+Random(3)-1,
5280 Obj.Y+Obj.Rect.Height+8, 1, 8, 4);
5281 {$ENDIF}
5282 if Random(2) = 0
5283 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
5284 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
5285 Exit;
5286 end;
5288 for i := 1 to Times do
5289 begin
5290 {$IFDEF ENABLE_GFX}
5291 g_GFX_QueueEffect(
5292 R_GFX_SMOKE_TRANS,
5293 Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(R_GFX_SMOKE_WIDTH div 2),
5294 Obj.Y+Obj.Rect.Height-4+Random(8+Times*2)
5295 );
5296 {$ENDIF}
5297 end;
5298 end;
5300 procedure TPlayer.OnFireFlame(Times: DWORD = 1);
5301 var i: DWORD;
5302 begin
5303 if (Random(10) = 1) and (Times = 1) then
5304 Exit;
5306 for i := 1 to Times do
5307 begin
5308 {$IFDEF ENABLE_GFX}
5309 g_GFX_QueueEffect(
5310 R_GFX_FLAME,
5311 Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(R_GFX_FLAME_WIDTH div 2),
5312 Obj.Y+8+Random(8+Times*2)
5313 );
5314 {$ENDIF}
5315 end;
5316 end;
5318 procedure TPlayer.PauseSounds(Enable: Boolean);
5319 begin
5320 FSawSound.Pause(Enable);
5321 FSawSoundIdle.Pause(Enable);
5322 FSawSoundHit.Pause(Enable);
5323 FSawSoundSelect.Pause(Enable);
5324 FFlameSoundOn.Pause(Enable);
5325 FFlameSoundOff.Pause(Enable);
5326 FFlameSoundWork.Pause(Enable);
5327 FJetSoundFly.Pause(Enable);
5328 FJetSoundOn.Pause(Enable);
5329 FJetSoundOff.Pause(Enable);
5330 end;
5332 { T B o t : }
5334 constructor TBot.Create();
5335 var
5336 a: Integer;
5337 begin
5338 inherited Create();
5340 FPhysics := True;
5341 FSpectator := False;
5342 FGhost := False;
5344 FIamBot := True;
5346 Inc(gNumBots);
5348 for a := WP_FIRST to WP_LAST do
5349 begin
5350 FDifficult.WeaponPrior[a] := WEAPON_PRIOR1[a];
5351 FDifficult.CloseWeaponPrior[a] := WEAPON_PRIOR2[a];
5352 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
5353 end;
5354 end;
5356 destructor TBot.Destroy();
5357 begin
5358 Dec(gNumBots);
5359 inherited Destroy();
5360 end;
5362 procedure TBot.Respawn(Silent: Boolean; Force: Boolean = False);
5363 begin
5364 inherited Respawn(Silent, Force);
5366 FAIFlags := nil;
5367 FSelectedWeapon := FCurrWeap;
5368 resetWeaponQueue();
5369 FTargetUID := 0;
5370 end;
5372 procedure TBot.UpdateCombat();
5373 type
5374 TTarget = record
5375 UID: Word;
5376 X, Y: Integer;
5377 Rect: TRectWH;
5378 cX, cY: Integer;
5379 Dist: Word;
5380 Line: Boolean;
5381 Visible: Boolean;
5382 IsPlayer: Boolean;
5383 end;
5385 TTargetRecord = array of TTarget;
5387 function Compare(a, b: TTarget): Integer;
5388 begin
5389 if a.Line and not b.Line then // A на линии огня
5390 Result := -1
5391 else
5392 if not a.Line and b.Line then // B на линии огня
5393 Result := 1
5394 else // И A, и B на линии или не на линии огня
5395 if (a.Line and b.Line) or ((not a.Line) and (not b.Line)) then
5396 begin
5397 if a.Dist > b.Dist then // B ближе
5398 Result := 1
5399 else // A ближе или равноудаленно с B
5400 Result := -1;
5401 end
5402 else // Странно -> A
5403 Result := -1;
5404 end;
5406 var
5407 a, x1, y1, x2, y2: Integer;
5408 targets: TTargetRecord;
5409 ammo: Word;
5410 Target, BestTarget: TTarget;
5411 firew, fireh: Integer;
5412 angle: SmallInt;
5413 mon: TMonster;
5414 pla, tpla: TPlayer;
5415 vsPlayer, vsMonster, ok: Boolean;
5418 function monsUpdate (mon: TMonster): Boolean;
5419 begin
5420 result := false; // don't stop
5421 if mon.alive and (mon.MonsterType <> MONSTER_BARREL) then
5422 begin
5423 if not TargetOnScreen(mon.Obj.X+mon.Obj.Rect.X, mon.Obj.Y+mon.Obj.Rect.Y) then exit;
5425 x2 := mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2);
5426 y2 := mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2);
5428 // Если монстр на экране и не прикрыт стеной
5429 if g_TraceVector(x1, y1, x2, y2) then
5430 begin
5431 // Добавляем к списку возможных целей
5432 SetLength(targets, Length(targets)+1);
5433 with targets[High(targets)] do
5434 begin
5435 UID := mon.UID;
5436 X := mon.Obj.X;
5437 Y := mon.Obj.Y;
5438 cX := x2;
5439 cY := y2;
5440 Rect := mon.Obj.Rect;
5441 Dist := g_PatchLength(x1, y1, x2, y2);
5442 Line := (y1+4 < Target.Y + mon.Obj.Rect.Y + mon.Obj.Rect.Height) and
5443 (y1-4 > Target.Y + mon.Obj.Rect.Y);
5444 Visible := True;
5445 IsPlayer := False;
5446 end;
5447 end;
5448 end;
5449 end;
5451 begin
5452 vsPlayer := LongBool(gGameSettings.Options and GAME_OPTION_BOTVSPLAYER);
5453 vsMonster := LongBool(gGameSettings.Options and GAME_OPTION_BOTVSMONSTER);
5455 // Если текущее оружие не то, что нужно, то меняем:
5456 if FCurrWeap <> FSelectedWeapon then
5457 NextWeapon();
5459 // Если нужно стрелять и нужное оружие, то нажать "Стрелять":
5460 if (GetAIFlag('NEEDFIRE') <> '') and (FCurrWeap = FSelectedWeapon) then
5461 begin
5462 RemoveAIFlag('NEEDFIRE');
5464 case FCurrWeap of
5465 WEAPON_PLASMA, WEAPON_SUPERPULEMET, WEAPON_CHAINGUN: PressKey(KEY_FIRE, 20);
5466 WEAPON_SAW, WEAPON_KASTET, WEAPON_FLAMETHROWER: PressKey(KEY_FIRE, 40);
5467 else PressKey(KEY_FIRE);
5468 end;
5469 end;
5471 // Координаты ствола:
5472 x1 := FObj.X + WEAPONPOINT[FDirection].X;
5473 y1 := FObj.Y + WEAPONPOINT[FDirection].Y;
5475 Target.UID := FTargetUID;
5477 ok := False;
5478 if Target.UID <> 0 then
5479 begin // Цель есть - настраиваем
5480 if (g_GetUIDType(Target.UID) = UID_PLAYER) and
5481 vsPlayer then
5482 begin // Игрок
5483 tpla := g_Player_Get(Target.UID);
5484 if tpla <> nil then
5485 with tpla do
5486 begin
5487 if (@FObj) <> nil then
5488 begin
5489 Target.X := FObj.X;
5490 Target.Y := FObj.Y;
5491 end;
5492 end;
5494 Target.cX := Target.X + PLAYER_RECT_CX;
5495 Target.cY := Target.Y + PLAYER_RECT_CY;
5496 Target.Rect := PLAYER_RECT;
5497 Target.Visible := g_TraceVector(x1, y1, Target.cX, Target.cY);
5498 Target.Line := (y1+4 < Target.Y+PLAYER_RECT.Y+PLAYER_RECT.Height) and
5499 (y1-4 > Target.Y+PLAYER_RECT.Y);
5500 Target.IsPlayer := True;
5501 ok := True;
5502 end
5503 else
5504 if (g_GetUIDType(Target.UID) = UID_MONSTER) and
5505 vsMonster then
5506 begin // Монстр
5507 mon := g_Monsters_ByUID(Target.UID);
5508 if mon <> nil then
5509 begin
5510 Target.X := mon.Obj.X;
5511 Target.Y := mon.Obj.Y;
5513 Target.cX := Target.X + mon.Obj.Rect.X + (mon.Obj.Rect.Width div 2);
5514 Target.cY := Target.Y + mon.Obj.Rect.Y + (mon.Obj.Rect.Height div 2);
5515 Target.Rect := mon.Obj.Rect;
5516 Target.Visible := g_TraceVector(x1, y1, Target.cX, Target.cY);
5517 Target.Line := (y1+4 < Target.Y + mon.Obj.Rect.Y + mon.Obj.Rect.Height) and
5518 (y1-4 > Target.Y + mon.Obj.Rect.Y);
5519 Target.IsPlayer := False;
5520 ok := True;
5521 end;
5522 end;
5523 end;
5525 if not ok then
5526 begin // Цели нет - обнуляем
5527 Target.X := 0;
5528 Target.Y := 0;
5529 Target.cX := 0;
5530 Target.cY := 0;
5531 Target.Visible := False;
5532 Target.Line := False;
5533 Target.IsPlayer := False;
5534 end;
5536 targets := nil;
5538 // Если цель не видима или не на линии огня, то ищем все возможные цели:
5539 if (not Target.Line) or (not Target.Visible) then
5540 begin
5541 // Игроки:
5542 if vsPlayer then
5543 for a := 0 to High(gPlayers) do
5544 if (gPlayers[a] <> nil) and (gPlayers[a].alive) and
5545 (gPlayers[a].FUID <> FUID) and
5546 (not SameTeam(FUID, gPlayers[a].FUID)) and
5547 (not gPlayers[a].NoTarget) and
5548 (gPlayers[a].FMegaRulez[MR_INVIS] < gTime) then
5549 begin
5550 if not TargetOnScreen(gPlayers[a].FObj.X + PLAYER_RECT.X,
5551 gPlayers[a].FObj.Y + PLAYER_RECT.Y) then
5552 Continue;
5554 x2 := gPlayers[a].FObj.X + PLAYER_RECT_CX;
5555 y2 := gPlayers[a].FObj.Y + PLAYER_RECT_CY;
5557 // Если игрок на экране и не прикрыт стеной:
5558 if g_TraceVector(x1, y1, x2, y2) then
5559 begin
5560 // Добавляем к списку возможных целей:
5561 SetLength(targets, Length(targets)+1);
5562 with targets[High(targets)] do
5563 begin
5564 UID := gPlayers[a].FUID;
5565 X := gPlayers[a].FObj.X;
5566 Y := gPlayers[a].FObj.Y;
5567 cX := x2;
5568 cY := y2;
5569 Rect := PLAYER_RECT;
5570 Dist := g_PatchLength(x1, y1, x2, y2);
5571 Line := (y1+4 < Target.Y+PLAYER_RECT.Y+PLAYER_RECT.Height) and
5572 (y1-4 > Target.Y+PLAYER_RECT.Y);
5573 Visible := True;
5574 IsPlayer := True;
5575 end;
5576 end;
5577 end;
5579 // Монстры:
5580 if vsMonster then g_Mons_ForEach(monsUpdate);
5581 end;
5583 // Если есть возможные цели:
5584 // (Выбираем лучшую, меняем оружие и бежим к ней/от нее)
5585 if targets <> nil then
5586 begin
5587 // Выбираем наилучшую цель:
5588 BestTarget := targets[0];
5589 if Length(targets) > 1 then
5590 for a := 1 to High(targets) do
5591 if Compare(BestTarget, targets[a]) = 1 then
5592 BestTarget := targets[a];
5594 // Если лучшая цель "виднее" текущей, то текущая := лучшая:
5595 if ((not Target.Visible) and BestTarget.Visible and (Target.UID <> BestTarget.UID)) or
5596 ((not Target.Line) and BestTarget.Line and BestTarget.Visible) then
5597 begin
5598 Target := BestTarget;
5600 if (Healthy() = 3) or ((Healthy() = 2)) then
5601 begin // Если здоровы - догоняем
5602 if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
5603 SetAIFlag('GORIGHT', '1');
5604 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
5605 SetAIFlag('GOLEFT', '1');
5606 end
5607 else
5608 begin // Если побиты - убегаем
5609 if ((RunDirection() = TDirection.D_LEFT) and (Target.X < FObj.X)) then
5610 SetAIFlag('GORIGHT', '1');
5611 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
5612 SetAIFlag('GOLEFT', '1');
5613 end;
5615 // Выбираем оружие на основе расстояния и приоритетов:
5616 SelectWeapon(Abs(x1-Target.cX));
5617 end;
5618 end;
5620 // Если есть цель:
5621 // (Догоняем/убегаем, стреляем по направлению к цели)
5622 // (Если цель далеко, то хватит следить за ней)
5623 if Target.UID <> 0 then
5624 begin
5625 if not TargetOnScreen(Target.X + Target.Rect.X,
5626 Target.Y + Target.Rect.Y) then
5627 begin // Цель сбежала с "экрана"
5628 if (Healthy() = 3) or ((Healthy() = 2)) then
5629 begin // Если здоровы - догоняем
5630 if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
5631 SetAIFlag('GORIGHT', '1');
5632 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
5633 SetAIFlag('GOLEFT', '1');
5634 end
5635 else
5636 begin // Если побиты - забываем о цели и убегаем
5637 Target.UID := 0;
5638 if ((RunDirection() = TDirection.D_LEFT) and (Target.X < FObj.X)) then
5639 SetAIFlag('GORIGHT', '1');
5640 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
5641 SetAIFlag('GOLEFT', '1');
5642 end;
5643 end
5644 else
5645 begin // Цель пока на "экране"
5646 // Если цель не загорожена стеной, то отмечаем, когда ее видели:
5647 if g_TraceVector(x1, y1, Target.cX, Target.cY) then
5648 FLastVisible := gTime;
5649 // Если разница высот не велика, то догоняем:
5650 if (Abs(FObj.Y-Target.Y) <= 128) then
5651 begin
5652 if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
5653 SetAIFlag('GORIGHT', '1');
5654 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
5655 SetAIFlag('GOLEFT', '1');
5656 end;
5657 end;
5659 // Выбираем угол вверх:
5660 if FDirection = TDirection.D_LEFT then
5661 angle := ANGLE_LEFTUP
5662 else
5663 angle := ANGLE_RIGHTUP;
5665 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
5666 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
5668 // Если при угле вверх можно попасть в приблизительное положение цели:
5669 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
5670 Target.X+Target.Rect.X+GetInterval(FDifficult.DiagPrecision, 128), //96
5671 Target.Y+Target.Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
5672 Target.Rect.Width, Target.Rect.Height) and
5673 g_TraceVector(x1, y1, Target.cX, Target.cY) then
5674 begin // то нужно стрелять вверх
5675 SetAIFlag('NEEDFIRE', '1');
5676 SetAIFlag('NEEDSEEUP', '1');
5677 end;
5679 // Выбираем угол вниз:
5680 if FDirection = TDirection.D_LEFT then
5681 angle := ANGLE_LEFTDOWN
5682 else
5683 angle := ANGLE_RIGHTDOWN;
5685 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
5686 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
5688 // Если при угле вниз можно попасть в приблизительное положение цели:
5689 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
5690 Target.X+Target.Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
5691 Target.Y+Target.Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
5692 Target.Rect.Width, Target.Rect.Height) and
5693 g_TraceVector(x1, y1, Target.cX, Target.cY) then
5694 begin // то нужно стрелять вниз
5695 SetAIFlag('NEEDFIRE', '1');
5696 SetAIFlag('NEEDSEEDOWN', '1');
5697 end;
5699 // Если цель видно и она на такой же высоте:
5700 if Target.Visible and
5701 (y1+4 < Target.Y+Target.Rect.Y+Target.Rect.Height) and
5702 (y1-4 > Target.Y+Target.Rect.Y) then
5703 begin
5704 // Если идем в сторону цели, то надо стрелять:
5705 if ((FDirection = TDirection.D_LEFT) and (Target.X < FObj.X)) or
5706 ((FDirection = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
5707 begin // то нужно стрелять вперед
5708 SetAIFlag('NEEDFIRE', '1');
5709 SetAIFlag('NEEDSEEDOWN', '');
5710 SetAIFlag('NEEDSEEUP', '');
5711 end;
5712 // Если цель в пределах "экрана" и сложность позволяет прыжки сближения:
5713 if Abs(FObj.X-Target.X) < Trunc(gPlayerScreenSize.X*0.75) then
5714 if GetRnd(FDifficult.CloseJump) then
5715 begin // то если повезет - прыгаем (особенно, если близко)
5716 if Abs(FObj.X-Target.X) < 128 then
5717 a := 4
5718 else
5719 a := 30;
5720 if Random(a) = 0 then
5721 SetAIFlag('NEEDJUMP', '1');
5722 end;
5723 end;
5725 // Если цель все еще есть:
5726 if Target.UID <> 0 then
5727 if gTime-FLastVisible > 2000 then // Если видели давно
5728 Target.UID := 0 // то забыть цель
5729 else // Если видели недавно
5730 begin // но цель убили
5731 if Target.IsPlayer then
5732 begin // Цель - игрок
5733 pla := g_Player_Get(Target.UID);
5734 if (pla = nil) or (not pla.alive) or pla.NoTarget or
5735 (pla.FMegaRulez[MR_INVIS] >= gTime) then
5736 Target.UID := 0; // то забыть цель
5737 end
5738 else
5739 begin // Цель - монстр
5740 mon := g_Monsters_ByUID(Target.UID);
5741 if (mon = nil) or (not mon.alive) then
5742 Target.UID := 0; // то забыть цель
5743 end;
5744 end;
5745 end; // if Target.UID <> 0
5747 FTargetUID := Target.UID;
5749 // Если возможных целей нет:
5750 // (Атака чего-нибудь слева или справа)
5751 if targets = nil then
5752 if GetAIFlag('ATTACKLEFT') <> '' then
5753 begin // Если нужно атаковать налево
5754 RemoveAIFlag('ATTACKLEFT');
5756 SetAIFlag('NEEDJUMP', '1');
5758 if RunDirection() = TDirection.D_RIGHT then
5759 begin // Идем не в ту сторону
5760 if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then
5761 begin // Если здоровы, то, возможно, стреляем бежим влево и стреляем
5762 SetAIFlag('NEEDFIRE', '1');
5763 SetAIFlag('GOLEFT', '1');
5764 end;
5765 end
5766 else
5767 begin // Идем в нужную сторону
5768 if GetRnd(FDifficult.InvisFire) then // Возможно, стреляем вслепую
5769 SetAIFlag('NEEDFIRE', '1');
5770 if Healthy() <= 1 then // Побиты - убегаем
5771 SetAIFlag('GORIGHT', '1');
5772 end;
5773 end
5774 else
5775 if GetAIFlag('ATTACKRIGHT') <> '' then
5776 begin // Если нужно атаковать направо
5777 RemoveAIFlag('ATTACKRIGHT');
5779 SetAIFlag('NEEDJUMP', '1');
5781 if RunDirection() = TDirection.D_LEFT then
5782 begin // Идем не в ту сторону
5783 if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then
5784 begin // Если здоровы, то, возможно, бежим вправо и стреляем
5785 SetAIFlag('NEEDFIRE', '1');
5786 SetAIFlag('GORIGHT', '1');
5787 end;
5788 end
5789 else
5790 begin
5791 if GetRnd(FDifficult.InvisFire) then // Возможно, стреляем вслепую
5792 SetAIFlag('NEEDFIRE', '1');
5793 if Healthy() <= 1 then // Побиты - убегаем
5794 SetAIFlag('GOLEFT', '1');
5795 end;
5796 end;
5798 //HACK! (does it belongs there?)
5799 RealizeCurrentWeapon();
5801 // Если есть возможные цели:
5802 // (Стреляем по направлению к целям)
5803 if (targets <> nil) and (GetAIFlag('NEEDFIRE') <> '') then
5804 for a := 0 to High(targets) do
5805 begin
5806 // Если можем стрелять по диагонали:
5807 if GetRnd(FDifficult.DiagFire) then
5808 begin
5809 // Ищем цель сверху и стреляем, если есть:
5810 if FDirection = TDirection.D_LEFT then
5811 angle := ANGLE_LEFTUP
5812 else
5813 angle := ANGLE_RIGHTUP;
5815 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
5816 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
5818 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
5819 targets[a].X+targets[a].Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
5820 targets[a].Y+targets[a].Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
5821 targets[a].Rect.Width, targets[a].Rect.Height) and
5822 g_TraceVector(x1, y1, targets[a].cX, targets[a].cY) then
5823 begin
5824 SetAIFlag('NEEDFIRE', '1');
5825 SetAIFlag('NEEDSEEUP', '1');
5826 end;
5828 // Ищем цель снизу и стреляем, если есть:
5829 if FDirection = TDirection.D_LEFT then
5830 angle := ANGLE_LEFTDOWN
5831 else
5832 angle := ANGLE_RIGHTDOWN;
5834 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
5835 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
5837 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
5838 targets[a].X+targets[a].Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
5839 targets[a].Y+targets[a].Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
5840 targets[a].Rect.Width, targets[a].Rect.Height) and
5841 g_TraceVector(x1, y1, targets[a].cX, targets[a].cY) then
5842 begin
5843 SetAIFlag('NEEDFIRE', '1');
5844 SetAIFlag('NEEDSEEDOWN', '1');
5845 end;
5846 end;
5848 // Если цель "перед носом", то стреляем:
5849 if targets[a].Line and targets[a].Visible and
5850 (((FDirection = TDirection.D_LEFT) and (targets[a].X < FObj.X)) or
5851 ((FDirection = TDirection.D_RIGHT) and (targets[a].X > FObj.X))) then
5852 begin
5853 SetAIFlag('NEEDFIRE', '1');
5854 Break;
5855 end;
5856 end;
5858 // Если летит пуля, то, возможно, подпрыгиваем:
5859 if g_Weapon_Danger(FUID, FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y,
5860 PLAYER_RECT.Width, PLAYER_RECT.Height,
5861 40+GetInterval(FDifficult.Cover, 40)) then
5862 SetAIFlag('NEEDJUMP', '1');
5864 // Если кончились паторны, то нужно сменить оружие:
5865 ammo := GetAmmoByWeapon(FCurrWeap);
5866 if ((FCurrWeap = WEAPON_SHOTGUN2) and (ammo < 2)) or
5867 ((FCurrWeap = WEAPON_BFG) and (ammo < 40)) or
5868 (ammo = 0) then
5869 SetAIFlag('SELECTWEAPON', '1');
5871 // Если нужно сменить оружие, то выбираем нужное:
5872 if GetAIFlag('SELECTWEAPON') = '1' then
5873 begin
5874 SelectWeapon(-1);
5875 RemoveAIFlag('SELECTWEAPON');
5876 end;
5877 end;
5879 procedure TBot.Update();
5880 var
5881 EnableAI: Boolean;
5882 begin
5883 if not FAlive then
5884 begin // Respawn
5885 ReleaseKeys();
5886 PressKey(KEY_UP);
5887 end
5888 else
5889 begin
5890 EnableAI := True;
5892 // Проверяем, отключён ли AI ботов
5893 if (g_debug_BotAIOff = 1) and (Team = TEAM_RED) then
5894 EnableAI := False;
5895 if (g_debug_BotAIOff = 2) and (Team = TEAM_BLUE) then
5896 EnableAI := False;
5897 if g_debug_BotAIOff = 3 then
5898 EnableAI := False;
5900 if EnableAI then
5901 begin
5902 UpdateMove();
5903 UpdateCombat();
5904 end
5905 else
5906 begin
5907 RealizeCurrentWeapon();
5908 end;
5909 end;
5911 inherited Update();
5912 end;
5914 procedure TBot.ReleaseKey(Key: Byte);
5915 begin
5916 with FKeys[Key] do
5917 begin
5918 Pressed := False;
5919 Time := 0;
5920 end;
5921 end;
5923 function TBot.KeyPressed(Key: Word): Boolean;
5924 begin
5925 Result := FKeys[Key].Pressed;
5926 end;
5928 function TBot.GetAIFlag(aName: String20): String20;
5929 var
5930 a: Integer;
5931 begin
5932 Result := '';
5934 aName := LowerCase(aName);
5936 if FAIFlags <> nil then
5937 for a := 0 to High(FAIFlags) do
5938 if LowerCase(FAIFlags[a].Name) = aName then
5939 begin
5940 Result := FAIFlags[a].Value;
5941 Break;
5942 end;
5943 end;
5945 procedure TBot.RemoveAIFlag(aName: String20);
5946 var
5947 a, b: Integer;
5948 begin
5949 if FAIFlags = nil then Exit;
5951 aName := LowerCase(aName);
5953 for a := 0 to High(FAIFlags) do
5954 if LowerCase(FAIFlags[a].Name) = aName then
5955 begin
5956 if a <> High(FAIFlags) then
5957 for b := a to High(FAIFlags)-1 do
5958 FAIFlags[b] := FAIFlags[b+1];
5960 SetLength(FAIFlags, Length(FAIFlags)-1);
5961 Break;
5962 end;
5963 end;
5965 procedure TBot.SetAIFlag(aName, fValue: String20);
5966 var
5967 a: Integer;
5968 ok: Boolean;
5969 begin
5970 a := 0;
5971 ok := False;
5973 aName := LowerCase(aName);
5975 if FAIFlags <> nil then
5976 for a := 0 to High(FAIFlags) do
5977 if LowerCase(FAIFlags[a].Name) = aName then
5978 begin
5979 ok := True;
5980 Break;
5981 end;
5983 if ok then FAIFlags[a].Value := fValue
5984 else
5985 begin
5986 SetLength(FAIFlags, Length(FAIFlags)+1);
5987 with FAIFlags[High(FAIFlags)] do
5988 begin
5989 Name := aName;
5990 Value := fValue;
5991 end;
5992 end;
5993 end;
5995 procedure TBot.UpdateMove;
5997 procedure GoLeft(Time: Word = 1);
5998 begin
5999 ReleaseKey(KEY_LEFT);
6000 ReleaseKey(KEY_RIGHT);
6001 PressKey(KEY_LEFT, Time);
6002 SetDirection(TDirection.D_LEFT);
6003 end;
6005 procedure GoRight(Time: Word = 1);
6006 begin
6007 ReleaseKey(KEY_LEFT);
6008 ReleaseKey(KEY_RIGHT);
6009 PressKey(KEY_RIGHT, Time);
6010 SetDirection(TDirection.D_RIGHT);
6011 end;
6013 function Rnd(a: Word): Boolean;
6014 begin
6015 Result := Random(a) = 0;
6016 end;
6018 procedure Turn(Time: Word = 1200);
6019 begin
6020 if RunDirection() = TDirection.D_LEFT then GoRight(Time) else GoLeft(Time);
6021 end;
6023 procedure Stop();
6024 begin
6025 ReleaseKey(KEY_LEFT);
6026 ReleaseKey(KEY_RIGHT);
6027 end;
6029 function CanRunLeft(): Boolean;
6030 begin
6031 Result := not CollideLevel(-1, 0);
6032 end;
6034 function CanRunRight(): Boolean;
6035 begin
6036 Result := not CollideLevel(1, 0);
6037 end;
6039 function CanRun(): Boolean;
6040 begin
6041 if RunDirection() = TDirection.D_LEFT then Result := CanRunLeft() else Result := CanRunRight();
6042 end;
6044 procedure Jump(Time: Word = 30);
6045 begin
6046 PressKey(KEY_JUMP, Time);
6047 end;
6049 function NearHole(): Boolean;
6050 var
6051 x, sx: Integer;
6052 begin
6053 { TODO 5 : Лестницы }
6054 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
6055 for x := 1 to PLAYER_RECT.Width do
6056 if (not StayOnStep(x*sx, 0)) and
6057 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
6058 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
6059 begin
6060 Result := True;
6061 Exit;
6062 end;
6064 Result := False;
6065 end;
6067 function BorderHole(): Boolean;
6068 var
6069 x, sx, xx: Integer;
6070 begin
6071 { TODO 5 : Лестницы }
6072 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
6073 for x := 1 to PLAYER_RECT.Width do
6074 if (not StayOnStep(x*sx, 0)) and
6075 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
6076 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
6077 begin
6078 for xx := x to x+32 do
6079 if CollideLevel(xx*sx, PLAYER_RECT.Height) then
6080 begin
6081 Result := True;
6082 Exit;
6083 end;
6084 end;
6086 Result := False;
6087 end;
6089 function NearDeepHole(): Boolean;
6090 var
6091 x, sx, y: Integer;
6092 begin
6093 Result := False;
6095 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
6096 y := 3;
6098 for x := 1 to PLAYER_RECT.Width do
6099 if (not StayOnStep(x*sx, 0)) and
6100 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
6101 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
6102 begin
6103 while FObj.Y+y*PLAYER_RECT.Height < gMapInfo.Height do
6104 begin
6105 if CollideLevel(x*sx, PLAYER_RECT.Height*y) then Exit;
6106 y := y+1;
6107 end;
6109 Result := True;
6110 end else Result := False;
6111 end;
6113 function OverDeepHole(): Boolean;
6114 var
6115 y: Integer;
6116 begin
6117 Result := False;
6119 y := 1;
6120 while FObj.Y+y*PLAYER_RECT.Height < gMapInfo.Height do
6121 begin
6122 if CollideLevel(0, PLAYER_RECT.Height*y) then Exit;
6123 y := y+1;
6124 end;
6126 Result := True;
6127 end;
6129 function OnGround(): Boolean;
6130 begin
6131 Result := StayOnStep(0, 0) or CollideLevel(0, 1);
6132 end;
6134 function OnLadder(): Boolean;
6135 begin
6136 Result := FullInStep(0, 0);
6137 end;
6139 function BelowLadder(): Boolean;
6140 begin
6141 Result := (FullInStep(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) and
6142 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or
6143 (FullInStep(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) and
6144 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP));
6145 end;
6147 function BelowLiftUp(): Boolean;
6148 begin
6149 Result := ((FullInLift(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) = -1) and
6150 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or
6151 ((FullInLift(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) = -1) and
6152 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP));
6153 end;
6155 function OnTopLift(): Boolean;
6156 begin
6157 Result := (FullInLift(0, 0) = -1) and (FullInLift(0, -32) = 0);
6158 end;
6160 function CanJumpOver(): Boolean;
6161 var
6162 sx, y: Integer;
6163 begin
6164 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
6166 Result := False;
6168 if not CollideLevel(sx, 0) then Exit;
6170 for y := 1 to BOT_MAXJUMP do
6171 if CollideLevel(0, -y) then Exit else
6172 if not CollideLevel(sx, -y) then
6173 begin
6174 Result := True;
6175 Exit;
6176 end;
6177 end;
6179 function CanJumpUp(Dist: ShortInt): Boolean;
6180 var
6181 y, yy: Integer;
6182 c: Boolean;
6183 begin
6184 Result := False;
6186 if CollideLevel(Dist, 0) then Exit;
6188 c := False;
6189 for y := 0 to BOT_MAXJUMP do
6190 if CollideLevel(Dist, -y) then
6191 begin
6192 c := True;
6193 Break;
6194 end;
6196 if not c then Exit;
6198 c := False;
6199 for yy := y+1 to BOT_MAXJUMP do
6200 if not CollideLevel(Dist, -yy) then
6201 begin
6202 c := True;
6203 Break;
6204 end;
6206 if not c then Exit;
6208 c := False;
6209 for y := 0 to BOT_MAXJUMP do
6210 if CollideLevel(0, -y) then
6211 begin
6212 c := True;
6213 Break;
6214 end;
6216 if c then Exit;
6218 if y < yy then Exit;
6220 Result := True;
6221 end;
6223 function IsSafeTrigger(): Boolean;
6224 var
6225 a: Integer;
6226 begin
6227 Result := True;
6228 if gTriggers = nil then
6229 Exit;
6230 for a := 0 to High(gTriggers) do
6231 if Collide(gTriggers[a].X,
6232 gTriggers[a].Y,
6233 gTriggers[a].Width,
6234 gTriggers[a].Height) and
6235 (gTriggers[a].TriggerType in [TRIGGER_EXIT, TRIGGER_CLOSEDOOR,
6236 TRIGGER_CLOSETRAP, TRIGGER_TRAP,
6237 TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF,
6238 TRIGGER_ONOFF, TRIGGER_SPAWNMONSTER,
6239 TRIGGER_DAMAGE, TRIGGER_SHOT]) then
6240 Result := False;
6241 end;
6243 begin
6244 // Возможно, нажимаем кнопку:
6245 if Rnd(16) and IsSafeTrigger() then
6246 PressKey(KEY_OPEN);
6248 // Если под лифтом или ступеньками, то, возможно, прыгаем:
6249 if OnLadder() or ((BelowLadder() or BelowLiftUp()) and Rnd(8)) then
6250 begin
6251 ReleaseKey(KEY_LEFT);
6252 ReleaseKey(KEY_RIGHT);
6253 Jump();
6254 end;
6256 // Идем влево, если надо было:
6257 if GetAIFlag('GOLEFT') <> '' then
6258 begin
6259 RemoveAIFlag('GOLEFT');
6260 if CanRunLeft() then
6261 GoLeft(360);
6262 end;
6264 // Идем вправо, если надо было:
6265 if GetAIFlag('GORIGHT') <> '' then
6266 begin
6267 RemoveAIFlag('GORIGHT');
6268 if CanRunRight() then
6269 GoRight(360);
6270 end;
6272 // Если вылетели за карту, то пробуем вернуться:
6273 if FObj.X < -32 then
6274 GoRight(360)
6275 else
6276 if FObj.X+32 > gMapInfo.Width then
6277 GoLeft(360);
6279 // Прыгаем, если надо было:
6280 if GetAIFlag('NEEDJUMP') <> '' then
6281 begin
6282 Jump(0);
6283 RemoveAIFlag('NEEDJUMP');
6284 end;
6286 // Смотрим вверх, если надо было:
6287 if GetAIFlag('NEEDSEEUP') <> '' then
6288 begin
6289 ReleaseKey(KEY_UP);
6290 ReleaseKey(KEY_DOWN);
6291 PressKey(KEY_UP, 20);
6292 RemoveAIFlag('NEEDSEEUP');
6293 end;
6295 // Смотрим вниз, если надо было:
6296 if GetAIFlag('NEEDSEEDOWN') <> '' then
6297 begin
6298 ReleaseKey(KEY_UP);
6299 ReleaseKey(KEY_DOWN);
6300 PressKey(KEY_DOWN, 20);
6301 RemoveAIFlag('NEEDSEEDOWN');
6302 end;
6304 // Если нужно было в дыру и мы не на земле, то покорно летим:
6305 if GetAIFlag('GOINHOLE') <> '' then
6306 if not OnGround() then
6307 begin
6308 ReleaseKey(KEY_LEFT);
6309 ReleaseKey(KEY_RIGHT);
6310 RemoveAIFlag('GOINHOLE');
6311 SetAIFlag('FALLINHOLE', '1');
6312 end;
6314 // Если падали и достигли земли, то хватит падать:
6315 if GetAIFlag('FALLINHOLE') <> '' then
6316 if OnGround() then
6317 RemoveAIFlag('FALLINHOLE');
6319 // Если летели прямо и сейчас не на лестнице или на вершине лифта, то отходим в сторону:
6320 if not (KeyPressed(KEY_LEFT) or KeyPressed(KEY_RIGHT)) then
6321 if GetAIFlag('FALLINHOLE') = '' then
6322 if (not OnLadder()) or (FObj.Vel.Y >= 0) or (OnTopLift()) then
6323 if Rnd(2) then
6324 GoLeft(360)
6325 else
6326 GoRight(360);
6328 // Если на земле и можно подпрыгнуть, то, возможно, прыгаем:
6329 if OnGround() and
6330 CanJumpUp(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*32) and
6331 Rnd(8) then
6332 Jump();
6334 // Если на земле и возле дыры (глубина > 2 ростов игрока):
6335 if OnGround() and NearHole() then
6336 if NearDeepHole() then // Если это бездна
6337 case Random(6) of
6338 0..3: Turn(); // Бежим обратно
6339 4: Jump(); // Прыгаем
6340 5: begin // Прыгаем обратно
6341 Turn();
6342 Jump();
6343 end;
6344 end
6345 else // Это не бездна и мы еще не летим туда
6346 if GetAIFlag('GOINHOLE') = '' then
6347 case Random(6) of
6348 0: Turn(); // Не нужно туда
6349 1: Jump(); // Вдруг повезет - прыгаем
6350 else // Если яма с границей, то при случае можно туда прыгнуть
6351 if BorderHole() then
6352 SetAIFlag('GOINHOLE', '1');
6353 end;
6355 // Если на земле, но некуда идти:
6356 if (not CanRun()) and OnGround() then
6357 begin
6358 // Если мы на лестнице или можно перепрыгнуть, то прыгаем:
6359 if CanJumpOver() or OnLadder() then
6360 Jump()
6361 else // иначе попытаемся в другую сторону
6362 if Random(2) = 0 then
6363 begin
6364 if IsSafeTrigger() then
6365 PressKey(KEY_OPEN);
6366 end else
6367 Turn();
6368 end;
6370 // Осталось мало воздуха:
6371 if FAir < 36 * 2 then
6372 Jump(20);
6374 // Выбираемся из кислоты, если нет костюма, обожглись, или мало здоровья:
6375 if (FMegaRulez[MR_SUIT] < gTime) and ((FLastHit = HIT_ACID) or (Healthy() <= 1)) then
6376 if BodyInAcid(0, 0) then
6377 Jump();
6378 end;
6380 function TBot.FullInStep(XInc, YInc: Integer): Boolean;
6381 begin
6382 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
6383 PLAYER_RECT.Width, PLAYER_RECT.Height, PANEL_STEP, False);
6384 end;
6386 {function TBot.NeedItem(Item: Byte): Byte;
6387 begin
6388 Result := 4;
6389 end;}
6391 procedure TBot.SelectWeapon(Dist: Integer);
6392 var
6393 a: Integer;
6395 function HaveAmmo(weapon: Byte): Boolean;
6396 begin
6397 case weapon of
6398 WEAPON_PISTOL: Result := FAmmo[A_BULLETS] >= 1;
6399 WEAPON_SHOTGUN1: Result := FAmmo[A_SHELLS] >= 1;
6400 WEAPON_SHOTGUN2: Result := FAmmo[A_SHELLS] >= 2;
6401 WEAPON_CHAINGUN: Result := FAmmo[A_BULLETS] >= 10;
6402 WEAPON_ROCKETLAUNCHER: Result := FAmmo[A_ROCKETS] >= 1;
6403 WEAPON_PLASMA: Result := FAmmo[A_CELLS] >= 10;
6404 WEAPON_BFG: Result := FAmmo[A_CELLS] >= 40;
6405 WEAPON_SUPERPULEMET: Result := FAmmo[A_SHELLS] >= 1;
6406 WEAPON_FLAMETHROWER: Result := FAmmo[A_FUEL] >= 1;
6407 else Result := True;
6408 end;
6409 end;
6411 begin
6412 if Dist = -1 then Dist := BOT_LONGDIST;
6414 if Dist > BOT_LONGDIST then
6415 begin // Дальний бой
6416 for a := 0 to 9 do
6417 if FWeapon[FDifficult.WeaponPrior[a]] and HaveAmmo(FDifficult.WeaponPrior[a]) then
6418 begin
6419 FSelectedWeapon := FDifficult.WeaponPrior[a];
6420 Break;
6421 end;
6422 end
6423 else //if Dist > BOT_UNSAFEDIST then
6424 begin // Ближний бой
6425 for a := 0 to 9 do
6426 if FWeapon[FDifficult.CloseWeaponPrior[a]] and HaveAmmo(FDifficult.CloseWeaponPrior[a]) then
6427 begin
6428 FSelectedWeapon := FDifficult.CloseWeaponPrior[a];
6429 Break;
6430 end;
6431 end;
6432 { else
6433 begin
6434 for a := 0 to 9 do
6435 if FWeapon[FDifficult.SafeWeaponPrior[a]] and HaveAmmo(FDifficult.SafeWeaponPrior[a]) then
6436 begin
6437 FSelectedWeapon := FDifficult.SafeWeaponPrior[a];
6438 Break;
6439 end;
6440 end;}
6441 end;
6443 function TBot.PickItem(ItemType: Byte; force: Boolean; var remove: Boolean): Boolean;
6444 begin
6445 Result := inherited PickItem(ItemType, force, remove);
6447 if Result then SetAIFlag('SELECTWEAPON', '1');
6448 end;
6450 function TBot.Heal(value: Word; Soft: Boolean): Boolean;
6451 begin
6452 Result := inherited Heal(value, Soft);
6453 end;
6455 function TBot.Healthy(): Byte;
6456 begin
6457 if FMegaRulez[MR_INVUL] >= gTime then Result := 3
6458 else if (FHealth > 80) or ((FHealth > 50) and (FArmor > 20)) then Result := 3
6459 else if (FHealth > 50) then Result := 2
6460 else if (FHealth > 20) then Result := 1
6461 else Result := 0;
6462 end;
6464 function TBot.TargetOnScreen(TX, TY: Integer): Boolean;
6465 begin
6466 Result := (Abs(FObj.X-TX) <= Trunc(gPlayerScreenSize.X*0.6)) and
6467 (Abs(FObj.Y-TY) <= Trunc(gPlayerScreenSize.Y*0.6));
6468 end;
6470 procedure TBot.OnDamage(Angle: SmallInt);
6471 var
6472 pla: TPlayer;
6473 mon: TMonster;
6474 ok: Boolean;
6475 begin
6476 inherited;
6478 if (Angle = 0) or (Angle = 180) then
6479 begin
6480 ok := False;
6481 if (g_GetUIDType(FLastSpawnerUID) = UID_PLAYER) and
6482 LongBool(gGameSettings.Options and GAME_OPTION_BOTVSPLAYER) then
6483 begin // Игрок
6484 pla := g_Player_Get(FLastSpawnerUID);
6485 ok := not TargetOnScreen(pla.FObj.X + PLAYER_RECT.X,
6486 pla.FObj.Y + PLAYER_RECT.Y);
6487 end
6488 else
6489 if (g_GetUIDType(FLastSpawnerUID) = UID_MONSTER) and
6490 LongBool(gGameSettings.Options and GAME_OPTION_BOTVSMONSTER) then
6491 begin // Монстр
6492 mon := g_Monsters_ByUID(FLastSpawnerUID);
6493 ok := not TargetOnScreen(mon.Obj.X + mon.Obj.Rect.X,
6494 mon.Obj.Y + mon.Obj.Rect.Y);
6495 end;
6497 if ok then
6498 if Angle = 0 then
6499 SetAIFlag('ATTACKLEFT', '1')
6500 else
6501 SetAIFlag('ATTACKRIGHT', '1');
6502 end;
6503 end;
6505 function TBot.RunDirection(): TDirection;
6506 begin
6507 if Abs(Vel.X) >= 1 then
6508 begin
6509 if Vel.X > 0 then Result := TDirection.D_RIGHT else Result := TDirection.D_LEFT;
6510 end else
6511 Result := FDirection;
6512 end;
6514 function TBot.GetRnd(a: Byte): Boolean;
6515 begin
6516 if a = 0 then Result := False
6517 else if a = 255 then Result := True
6518 else Result := Random(256) > 255-a;
6519 end;
6521 function TBot.GetInterval(a: Byte; radius: SmallInt): SmallInt;
6522 begin
6523 Result := Round((255-a)/255*radius*(Random(2)-1));
6524 end;
6527 procedure TDifficult.save (st: TStream);
6528 begin
6529 utils.writeInt(st, Byte(DiagFire));
6530 utils.writeInt(st, Byte(InvisFire));
6531 utils.writeInt(st, Byte(DiagPrecision));
6532 utils.writeInt(st, Byte(FlyPrecision));
6533 utils.writeInt(st, Byte(Cover));
6534 utils.writeInt(st, Byte(CloseJump));
6535 st.WriteBuffer(WeaponPrior[Low(WeaponPrior)], sizeof(WeaponPrior));
6536 st.WriteBuffer(CloseWeaponPrior[Low(CloseWeaponPrior)], sizeof(CloseWeaponPrior));
6537 end;
6539 procedure TDifficult.load (st: TStream);
6540 begin
6541 DiagFire := utils.readByte(st);
6542 InvisFire := utils.readByte(st);
6543 DiagPrecision := utils.readByte(st);
6544 FlyPrecision := utils.readByte(st);
6545 Cover := utils.readByte(st);
6546 CloseJump := utils.readByte(st);
6547 st.ReadBuffer(WeaponPrior[Low(WeaponPrior)], sizeof(WeaponPrior));
6548 st.ReadBuffer(CloseWeaponPrior[Low(CloseWeaponPrior)], sizeof(CloseWeaponPrior));
6549 end;
6552 procedure TBot.SaveState (st: TStream);
6553 var
6554 i: Integer;
6555 dw: Integer;
6556 begin
6557 inherited SaveState(st);
6558 utils.writeSign(st, 'BOT0');
6559 // Выбранное оружие
6560 utils.writeInt(st, Byte(FSelectedWeapon));
6561 // UID цели
6562 utils.writeInt(st, Word(FTargetUID));
6563 // Время потери цели
6564 utils.writeInt(st, LongWord(FLastVisible));
6565 // Количество флагов ИИ
6566 dw := Length(FAIFlags);
6567 utils.writeInt(st, LongInt(dw));
6568 // Флаги ИИ
6569 for i := 0 to dw-1 do
6570 begin
6571 utils.writeStr(st, FAIFlags[i].Name, 20);
6572 utils.writeStr(st, FAIFlags[i].Value, 20);
6573 end;
6574 // Настройки сложности
6575 FDifficult.save(st);
6576 end;
6579 procedure TBot.LoadState (st: TStream);
6580 var
6581 i: Integer;
6582 dw: Integer;
6583 begin
6584 inherited LoadState(st);
6585 if not utils.checkSign(st, 'BOT0') then raise XStreamError.Create('invalid bot signature');
6586 // Выбранное оружие
6587 FSelectedWeapon := utils.readByte(st);
6588 // UID цели
6589 FTargetUID := utils.readWord(st);
6590 // Время потери цели
6591 FLastVisible := utils.readLongWord(st);
6592 // Количество флагов ИИ
6593 dw := utils.readLongInt(st);
6594 if (dw < 0) or (dw > 16384) then raise XStreamError.Create('invalid number of bot AI flags');
6595 SetLength(FAIFlags, dw);
6596 // Флаги ИИ
6597 for i := 0 to dw-1 do
6598 begin
6599 FAIFlags[i].Name := utils.readStr(st, 20);
6600 FAIFlags[i].Value := utils.readStr(st, 20);
6601 end;
6602 // Настройки сложности
6603 FDifficult.load(st);
6604 end;
6607 begin
6608 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');
6609 conRegVar('player_indicator_style', @gPlayerIndicatorStyle, 'Visual appearance of indicator', 'Visual appearance of indicator');
6610 end.