DEADSOFTWARE

gl: draw menu background
[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,
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 FPunchTime: LongWord;
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 PunchTime: LongWord read FPunchTime;
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 FPunchTime := 0;
1649 resetWeaponQueue();
1650 end;
1652 procedure TPlayer.positionChanged (); inline;
1653 begin
1654 end;
1656 procedure TPlayer.doDamage (v: Integer);
1657 begin
1658 if (v <= 0) then exit;
1659 if (v > 32767) then v := 32767;
1660 Damage(v, 0, 0, 0, mEDamageType);
1661 end;
1663 procedure TPlayer.Damage(value: Word; SpawnerUID: Word; vx, vy: Integer; t: Byte);
1664 var
1665 c: Word;
1666 begin
1667 if (not g_Game_IsClient) and (not FAlive) then
1668 Exit;
1670 FLastHit := t;
1672 // Неуязвимость не спасает от ловушек:
1673 if ((t = HIT_TRAP) or (t = HIT_SELF)) and (not FGodMode) then
1674 begin
1675 if not g_Game_IsClient then
1676 begin
1677 FArmor := 0;
1678 if t = HIT_TRAP then
1679 begin
1680 // Ловушка убивает сразу:
1681 FHealth := -100;
1682 Kill(K_EXTRAHARDKILL, SpawnerUID, t);
1683 end;
1684 if t = HIT_SELF then
1685 begin
1686 // Самоубийство:
1687 FHealth := 0;
1688 Kill(K_SIMPLEKILL, SpawnerUID, t);
1689 end;
1690 end;
1691 // Обнулить действия примочек, чтобы фон пропал
1692 FMegaRulez[MR_SUIT] := 0;
1693 FMegaRulez[MR_INVUL] := 0;
1694 FMegaRulez[MR_INVIS] := 0;
1695 FSpawnInvul := 0;
1696 FBerserk := 0;
1697 end;
1699 // Но от остального спасает:
1700 if FMegaRulez[MR_INVUL] >= gTime then
1701 Exit;
1703 // Чит-код "ГОРЕЦ":
1704 if FGodMode then
1705 Exit;
1707 // Если есть урон своим, или ранил сам себя, или тебя ранил противник:
1708 if LongBool(gGameSettings.Options and GAME_OPTION_TEAMDAMAGE) or
1709 (SpawnerUID = FUID) or
1710 (not SameTeam(FUID, SpawnerUID)) then
1711 begin
1712 FLastSpawnerUID := SpawnerUID;
1714 // Кровь (пузырьки, если в воде):
1715 if gBloodCount > 0 then
1716 begin
1717 c := Min(value, 200)*gBloodCount + Random(Min(value, 200) div 2);
1718 if value div 4 <= c then
1719 c := c - (value div 4)
1720 else
1721 c := 0;
1723 if (t = HIT_SOME) and (vx = 0) and (vy = 0) then
1724 MakeBloodSimple(c)
1725 else
1726 case t of
1727 HIT_TRAP, HIT_ACID, HIT_FLAME, HIT_SELF: MakeBloodSimple(c);
1728 HIT_BFG, HIT_ROCKET, HIT_SOME: MakeBloodVector(c, vx, vy);
1729 end;
1731 {$IFDEF ENABLE_GFX}
1732 if t = HIT_WATER then
1733 begin
1734 g_GFX_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
1735 FObj.Y+PLAYER_RECT.Y-4, value div 2, 8, 4);
1736 if Random(2) = 0
1737 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
1738 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
1739 end;
1740 {$ENDIF}
1741 end;
1743 // Буфер урона:
1744 if FAlive then
1745 Inc(FDamageBuffer, value);
1747 // Вспышка боли:
1748 if gFlash <> 0 then
1749 FPain := FPain + value;
1750 end;
1752 if g_Game_IsServer and g_Game_IsNet then
1753 begin
1754 MH_SEND_PlayerDamage(FUID, t, SpawnerUID, value, vx, vy);
1755 MH_SEND_PlayerStats(FUID);
1756 MH_SEND_PlayerPos(False, FUID);
1757 end;
1758 end;
1760 function TPlayer.Heal(value: Word; Soft: Boolean): Boolean;
1761 begin
1762 Result := False;
1763 if g_Game_IsClient then
1764 Exit;
1765 if not FAlive then
1766 Exit;
1768 if Soft and (FHealth < PLAYER_HP_SOFT) then
1769 begin
1770 IncMax(FHealth, value, PLAYER_HP_SOFT);
1771 Result := True;
1772 end;
1773 if (not Soft) and (FHealth < PLAYER_HP_LIMIT) then
1774 begin
1775 IncMax(FHealth, value, PLAYER_HP_LIMIT);
1776 Result := True;
1777 end;
1779 if Result and g_Game_IsServer and g_Game_IsNet then
1780 MH_SEND_PlayerStats(FUID);
1781 end;
1783 destructor TPlayer.Destroy();
1784 begin
1785 if (gPlayer1 <> nil) and (gPlayer1.FUID = FUID) then
1786 gPlayer1 := nil;
1787 if (gPlayer2 <> nil) and (gPlayer2.FUID = FUID) then
1788 gPlayer2 := nil;
1790 FSawSound.Free();
1791 FSawSoundIdle.Free();
1792 FSawSoundHit.Free();
1793 FSawSoundSelect.Free();
1794 FFlameSoundOn.Free();
1795 FFlameSoundOff.Free();
1796 FFlameSoundWork.Free();
1797 FJetSoundFly.Free();
1798 FJetSoundOn.Free();
1799 FJetSoundOff.Free();
1800 FModel.Free();
1801 FPunchTime := 0;
1803 inherited;
1804 end;
1806 procedure TPlayer.DoPunch();
1807 begin
1808 FPunchTime := gTime;
1809 end;
1811 procedure TPlayer.Fire();
1812 var
1813 f, DidFire: Boolean;
1814 wx, wy, xd, yd: Integer;
1815 locobj: TObj;
1816 begin
1817 if g_Game_IsClient then Exit;
1818 // FBFGFireCounter - время перед выстрелом (для BFG)
1819 // FReloading - время после выстрела (для всего)
1821 if FSpectator then
1822 begin
1823 Respawn(False);
1824 Exit;
1825 end;
1827 if FReloading[FCurrWeap] <> 0 then Exit;
1829 DidFire := False;
1831 f := False;
1832 wx := FObj.X+WEAPONPOINT[FDirection].X;
1833 wy := FObj.Y+WEAPONPOINT[FDirection].Y;
1834 xd := wx+IfThen(FDirection = TDirection.D_LEFT, -30, 30);
1835 yd := wy+firediry();
1837 case FCurrWeap of
1838 WEAPON_KASTET:
1839 begin
1840 DoPunch();
1841 if R_BERSERK in FRulez then
1842 begin
1843 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
1844 locobj.X := FObj.X+FObj.Rect.X;
1845 locobj.Y := FObj.Y+FObj.Rect.Y;
1846 locobj.rect.X := 0;
1847 locobj.rect.Y := 0;
1848 locobj.rect.Width := 39;
1849 locobj.rect.Height := 52;
1850 locobj.Vel.X := (xd-wx) div 2;
1851 locobj.Vel.Y := (yd-wy) div 2;
1852 locobj.Accel.X := xd-wx;
1853 locobj.Accel.y := yd-wy;
1855 if g_Weapon_Hit(@locobj, 50, FUID, HIT_SOME) <> 0 then
1856 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj.X, FObj.Y)
1857 else
1858 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj.X, FObj.Y);
1860 if (gFlash = 1) and (FPain < 50) then FPain := min(FPain + 25, 50);
1861 end
1862 else
1863 begin
1864 g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 3, FUID);
1865 end;
1867 DidFire := True;
1868 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
1869 end;
1871 WEAPON_SAW:
1872 begin
1873 if g_Weapon_chainsaw(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
1874 IfThen(gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF], 9, 3), FUID) <> 0 then
1875 begin
1876 FSawSoundSelect.Stop();
1877 FSawSound.Stop();
1878 FSawSoundHit.PlayAt(FObj.X, FObj.Y);
1879 end
1880 else if not FSawSoundHit.IsPlaying() then
1881 begin
1882 FSawSoundSelect.Stop();
1883 FSawSound.PlayAt(FObj.X, FObj.Y);
1884 end;
1886 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
1887 DidFire := True;
1888 f := True;
1889 end;
1891 WEAPON_PISTOL:
1892 if FAmmo[A_BULLETS] > 0 then
1893 begin
1894 g_Weapon_pistol(wx, wy, xd, yd, FUID);
1895 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
1896 Dec(FAmmo[A_BULLETS]);
1897 FFireAngle := FAngle;
1898 f := True;
1899 DidFire := True;
1900 {$IFDEF ENABLE_SHELLS}
1901 g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_BULLET);
1902 {$ENDIF}
1903 end;
1905 WEAPON_SHOTGUN1:
1906 if FAmmo[A_SHELLS] > 0 then
1907 begin
1908 g_Weapon_shotgun(wx, wy, xd, yd, FUID);
1909 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', wx, wy);
1910 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
1911 Dec(FAmmo[A_SHELLS]);
1912 FFireAngle := FAngle;
1913 f := True;
1914 DidFire := True;
1915 {$IFDEF ENABLE_SHELLS}
1916 FShellTimer := 10;
1917 FShellType := SHELL_SHELL;
1918 {$ENDIF}
1919 end;
1921 WEAPON_SHOTGUN2:
1922 if FAmmo[A_SHELLS] >= 2 then
1923 begin
1924 g_Weapon_dshotgun(wx, wy, xd, yd, FUID);
1925 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
1926 Dec(FAmmo[A_SHELLS], 2);
1927 FFireAngle := FAngle;
1928 f := True;
1929 DidFire := True;
1930 {$IFDEF ENABLE_SHELLS}
1931 FShellTimer := 13;
1932 FShellType := SHELL_DBLSHELL;
1933 {$ENDIF}
1934 end;
1936 WEAPON_CHAINGUN:
1937 if FAmmo[A_BULLETS] > 0 then
1938 begin
1939 g_Weapon_mgun(wx, wy, xd, yd, FUID);
1940 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', wx, wy);
1941 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
1942 Dec(FAmmo[A_BULLETS]);
1943 FFireAngle := FAngle;
1944 f := True;
1945 DidFire := True;
1946 {$IFDEF ENABLE_SHELLS}
1947 g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_BULLET);
1948 {$ENDIF}
1949 end;
1951 WEAPON_ROCKETLAUNCHER:
1952 if FAmmo[A_ROCKETS] > 0 then
1953 begin
1954 g_Weapon_rocket(wx, wy, xd, yd, FUID);
1955 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
1956 Dec(FAmmo[A_ROCKETS]);
1957 FFireAngle := FAngle;
1958 f := True;
1959 DidFire := True;
1960 end;
1962 WEAPON_PLASMA:
1963 if FAmmo[A_CELLS] > 0 then
1964 begin
1965 g_Weapon_plasma(wx, wy, xd, yd, FUID);
1966 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
1967 Dec(FAmmo[A_CELLS]);
1968 FFireAngle := FAngle;
1969 f := True;
1970 DidFire := True;
1971 end;
1973 WEAPON_BFG:
1974 if (FAmmo[A_CELLS] >= 40) and (FBFGFireCounter = -1) then
1975 begin
1976 FBFGFireCounter := 17;
1977 if not FNoReload then
1978 g_Sound_PlayExAt('SOUND_WEAPON_STARTFIREBFG', FObj.X, FObj.Y);
1979 Dec(FAmmo[A_CELLS], 40);
1980 DidFire := True;
1981 end;
1983 WEAPON_SUPERPULEMET:
1984 if FAmmo[A_SHELLS] > 0 then
1985 begin
1986 g_Weapon_shotgun(wx, wy, xd, yd, FUID);
1987 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx, wy);
1988 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
1989 Dec(FAmmo[A_SHELLS]);
1990 FFireAngle := FAngle;
1991 f := True;
1992 DidFire := True;
1993 {$IFDEF ENABLE_SHELLS}
1994 g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_SHELL);
1995 {$ENDIF}
1996 end;
1998 WEAPON_FLAMETHROWER:
1999 if FAmmo[A_FUEL] > 0 then
2000 begin
2001 g_Weapon_flame(wx, wy, xd, yd, FUID);
2002 FlamerOn;
2003 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2004 Dec(FAmmo[A_FUEL]);
2005 FFireAngle := FAngle;
2006 f := True;
2007 DidFire := True;
2008 end
2009 else
2010 begin
2011 FlamerOff;
2012 if g_Game_IsNet and g_Game_IsServer then MH_SEND_PlayerStats(FUID);
2013 end;
2014 end;
2016 if g_Game_IsNet then
2017 begin
2018 if DidFire then
2019 begin
2020 if FCurrWeap <> WEAPON_BFG then
2021 MH_SEND_PlayerFire(FUID, FCurrWeap, wx, wy, xd, yd, LastShotID)
2022 else
2023 if not FNoReload then
2024 MH_SEND_Sound(FObj.X, FObj.Y, 'SOUND_WEAPON_STARTFIREBFG');
2025 end;
2027 MH_SEND_PlayerStats(FUID);
2028 end;
2030 if not f then Exit;
2032 if (FAngle = 0) or (FAngle = 180) then SetAction(A_ATTACK)
2033 else if (FAngle = ANGLE_LEFTDOWN) or (FAngle = ANGLE_RIGHTDOWN) then SetAction(A_ATTACKDOWN)
2034 else if (FAngle = ANGLE_LEFTUP) or (FAngle = ANGLE_RIGHTUP) then SetAction(A_ATTACKUP);
2035 end;
2037 function TPlayer.GetAmmoByWeapon(Weapon: Byte): Word;
2038 begin
2039 case Weapon of
2040 WEAPON_PISTOL, WEAPON_CHAINGUN: Result := FAmmo[A_BULLETS];
2041 WEAPON_SHOTGUN1, WEAPON_SHOTGUN2, WEAPON_SUPERPULEMET: Result := FAmmo[A_SHELLS];
2042 WEAPON_ROCKETLAUNCHER: Result := FAmmo[A_ROCKETS];
2043 WEAPON_PLASMA, WEAPON_BFG: Result := FAmmo[A_CELLS];
2044 WEAPON_FLAMETHROWER: Result := FAmmo[A_FUEL];
2045 else Result := 0;
2046 end;
2047 end;
2049 function TPlayer.HeadInLiquid(XInc, YInc: Integer): Boolean;
2050 begin
2051 Result := g_Map_CollidePanel(FObj.X+PLAYER_HEADRECT.X+XInc, FObj.Y+PLAYER_HEADRECT.Y+YInc,
2052 PLAYER_HEADRECT.Width, PLAYER_HEADRECT.Height,
2053 PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, True);
2054 end;
2056 procedure TPlayer.FlamerOn;
2057 begin
2058 FFlameSoundOff.Stop();
2059 FFlameSoundOff.SetPosition(0);
2060 if FFlaming then
2061 begin
2062 if (not FFlameSoundOn.IsPlaying()) and (not FFlameSoundWork.IsPlaying()) then
2063 FFlameSoundWork.PlayAt(FObj.X, FObj.Y);
2064 end
2065 else
2066 begin
2067 FFlameSoundOn.PlayAt(FObj.X, FObj.Y);
2068 FFlaming := True;
2069 end;
2070 end;
2072 procedure TPlayer.FlamerOff;
2073 begin
2074 if FFlaming then
2075 begin
2076 FFlameSoundOn.Stop();
2077 FFlameSoundOn.SetPosition(0);
2078 FFlameSoundWork.Stop();
2079 FFlameSoundWork.SetPosition(0);
2080 FFlameSoundOff.PlayAt(FObj.X, FObj.Y);
2081 FFlaming := False;
2082 end;
2083 end;
2085 procedure TPlayer.JetpackOn;
2086 begin
2087 FJetSoundFly.Stop;
2088 FJetSoundOff.Stop;
2089 FJetSoundOn.SetPosition(0);
2090 FJetSoundOn.PlayAt(FObj.X, FObj.Y);
2091 FlySmoke(8);
2092 end;
2094 procedure TPlayer.JetpackOff;
2095 begin
2096 FJetSoundFly.Stop;
2097 FJetSoundOn.Stop;
2098 FJetSoundOff.SetPosition(0);
2099 FJetSoundOff.PlayAt(FObj.X, FObj.Y);
2100 end;
2102 procedure TPlayer.CatchFire(Attacker: Word; Timeout: Integer = PLAYER_BURN_TIME);
2103 begin
2104 if Timeout <= 0 then
2105 exit;
2106 if (FMegaRulez[MR_SUIT] > gTime) or (FMegaRulez[MR_INVUL] > gTime) then
2107 exit; // Не загораемся когда есть защита
2108 if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2) then
2109 exit; // Не подгораем в воде на всякий случай
2110 if FFireTime <= 0 then
2111 g_Sound_PlayExAt('SOUND_IGNITE', FObj.X, FObj.Y);
2112 FFireTime := Timeout;
2113 FFireAttacker := Attacker;
2114 if g_Game_IsNet and g_Game_IsServer then
2115 MH_SEND_PlayerStats(FUID);
2116 end;
2118 procedure TPlayer.Jump();
2119 begin
2120 if gFly or FJetpack then
2121 begin
2122 // Полет (чит-код или джетпак):
2123 if FObj.Vel.Y > -VEL_FLY then
2124 FObj.Vel.Y := FObj.Vel.Y - 3;
2125 if FJetpack then
2126 begin
2127 if FJetFuel > 0 then
2128 Dec(FJetFuel);
2129 if (FJetFuel < 1) and g_Game_IsServer then
2130 begin
2131 FJetpack := False;
2132 JetpackOff;
2133 if g_Game_IsNet then
2134 MH_SEND_PlayerStats(FUID);
2135 end;
2136 end;
2137 Exit;
2138 end;
2140 // Не включать джетпак в режиме прохождения сквозь стены
2141 if FGhost then
2142 FCanJetpack := False;
2144 // Прыгаем или всплываем:
2145 if (CollideLevel(0, 1) or
2146 g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y+36, PLAYER_RECT.Width,
2147 PLAYER_RECT.Height-33, PANEL_STEP, False)
2148 ) and (FObj.Accel.Y = 0) then // Не прыгать, если есть вертикальное ускорение
2149 begin
2150 FObj.Vel.Y := -VEL_JUMP;
2151 FCanJetpack := False;
2152 end
2153 else
2154 begin
2155 if BodyInLiquid(0, 0) then
2156 FObj.Vel.Y := -VEL_SW
2157 else if (FJetFuel > 0) and FCanJetpack and
2158 g_Game_IsServer and (not g_Obj_CollideLiquid(@FObj, 0, 0)) then
2159 begin
2160 FJetpack := True;
2161 JetpackOn;
2162 if g_Game_IsNet then
2163 MH_SEND_PlayerStats(FUID);
2164 end;
2165 end;
2166 end;
2168 procedure TPlayer.Kill(KillType: Byte; SpawnerUID: Word; t: Byte);
2169 var
2170 a, i, k, ab, ar: Byte;
2171 s: String;
2172 mon: TMonster;
2173 plr: TPlayer;
2174 srv, netsrv: Boolean;
2175 DoFrags: Boolean;
2176 OldLR: Byte;
2177 KP: TPlayer;
2178 it: PItem;
2180 procedure PushItem(t: Byte);
2181 var
2182 id: DWORD;
2183 begin
2184 id := g_Items_Create(FObj.X, FObj.Y, t, True, False);
2185 it := g_Items_ByIdx(id);
2186 if KillType = K_EXTRAHARDKILL then // -7..+7; -8..0
2187 begin
2188 g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-7+Random(15),
2189 (FObj.Vel.Y div 2)-Random(9));
2190 it.positionChanged(); // this updates spatial accelerators
2191 end
2192 else
2193 begin
2194 if KillType = K_HARDKILL then // -5..+5; -5..0
2195 begin
2196 g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-5+Random(11),
2197 (FObj.Vel.Y div 2)-Random(6));
2198 end
2199 else // -3..+3; -3..0
2200 begin
2201 g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-3+Random(7),
2202 (FObj.Vel.Y div 2)-Random(4));
2203 end;
2204 it.positionChanged(); // this updates spatial accelerators
2205 end;
2207 if g_Game_IsNet and g_Game_IsServer then
2208 MH_SEND_ItemSpawn(True, id);
2209 end;
2211 begin
2212 DoFrags := (gGameSettings.MaxLives = 0) or (gGameSettings.GameMode = GM_COOP);
2213 Srv := g_Game_IsServer;
2214 Netsrv := g_Game_IsServer and g_Game_IsNet;
2215 if Srv then FDeath := FDeath + 1;
2216 if FAlive then
2217 begin
2218 if FGhost then
2219 FGhost := False;
2220 if not FPhysics then
2221 FPhysics := True;
2222 FAlive := False;
2223 end;
2225 {$IFDEF ENABLE_SHELLS}
2226 FShellTimer := -1;
2227 {$ENDIF}
2229 if (gGameSettings.MaxLives > 0) and Srv and (gLMSRespawn = LMS_RESPAWN_NONE) then
2230 begin
2231 if FLives > 0 then FLives := FLives - 1;
2232 if FLives = 0 then FNoRespawn := True;
2233 end;
2235 // Номер типа смерти:
2236 a := 1;
2237 case KillType of
2238 K_SIMPLEKILL: a := 1;
2239 K_HARDKILL: a := 2;
2240 K_EXTRAHARDKILL: a := 3;
2241 K_FALLKILL: a := 4;
2242 end;
2244 // Звук смерти:
2245 if not FModel.PlaySound(MODELSOUND_DIE, a, FObj.X, FObj.Y) then
2246 for i := 1 to 3 do
2247 if FModel.PlaySound(MODELSOUND_DIE, i, FObj.X, FObj.Y) then
2248 Break;
2250 // Время респауна:
2251 if Srv then
2252 case KillType of
2253 K_SIMPLEKILL:
2254 FTime[T_RESPAWN] := gTime + TIME_RESPAWN1;
2255 K_HARDKILL:
2256 FTime[T_RESPAWN] := gTime + TIME_RESPAWN2;
2257 K_EXTRAHARDKILL, K_FALLKILL:
2258 FTime[T_RESPAWN] := gTime + TIME_RESPAWN3;
2259 end;
2261 // Переключаем состояние:
2262 case KillType of
2263 K_SIMPLEKILL:
2264 SetAction(A_DIE1);
2265 K_HARDKILL, K_EXTRAHARDKILL:
2266 SetAction(A_DIE2);
2267 end;
2269 // Реакция монстров на смерть игрока:
2270 if (KillType <> K_FALLKILL) and (Srv) then
2271 g_Monsters_killedp();
2273 if SpawnerUID = FUID then
2274 begin // Самоубился
2275 if Srv then
2276 begin
2277 if gGameSettings.GameMode = GM_TDM then
2278 Dec(gTeamStat[FTeam].Score);
2279 if DoFrags or (gGameSettings.GameMode = GM_TDM) then
2280 begin
2281 Dec(FFrags);
2282 FLastFrag := 0;
2283 end;
2284 end;
2285 g_Console_Add(Format(_lc[I_PLAYER_KILL_SELF], [FName]), True);
2286 end
2287 else
2288 if g_GetUIDType(SpawnerUID) = UID_PLAYER then
2289 begin // Убит другим игроком
2290 KP := g_Player_Get(SpawnerUID);
2291 if (KP <> nil) and Srv then
2292 begin
2293 if (DoFrags or (gGameSettings.GameMode = GM_TDM)) then
2294 if SameTeam(FUID, SpawnerUID) then
2295 begin
2296 Dec(KP.FFrags);
2297 KP.FLastFrag := 0;
2298 end else
2299 begin
2300 Inc(KP.FFrags);
2301 KP.FragCombo();
2302 end;
2304 if (gGameSettings.GameMode = GM_TDM) and DoFrags then
2305 Inc(gTeamStat[KP.Team].Score,
2306 IfThen(SameTeam(FUID, SpawnerUID), -1, 1));
2308 if netsrv then MH_SEND_PlayerStats(SpawnerUID);
2309 end;
2311 plr := g_Player_Get(SpawnerUID);
2312 if plr = nil then
2313 s := '?'
2314 else
2315 s := plr.FName;
2317 case KillType of
2318 K_HARDKILL:
2319 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_2],
2320 [FName, s]),
2321 gShowKillMsg);
2322 K_EXTRAHARDKILL:
2323 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_1],
2324 [FName, s]),
2325 gShowKillMsg);
2326 else
2327 g_Console_Add(Format(_lc[I_PLAYER_KILL],
2328 [FName, s]),
2329 gShowKillMsg);
2330 end;
2331 end
2332 else if g_GetUIDType(SpawnerUID) = UID_MONSTER then
2333 begin // Убит монстром
2334 mon := g_Monsters_ByUID(SpawnerUID);
2335 if mon = nil then
2336 s := '?'
2337 else
2338 s := g_Mons_GetKilledByTypeId(mon.MonsterType);
2340 case KillType of
2341 K_HARDKILL:
2342 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_2],
2343 [FName, s]),
2344 gShowKillMsg);
2345 K_EXTRAHARDKILL:
2346 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_1],
2347 [FName, s]),
2348 gShowKillMsg);
2349 else
2350 g_Console_Add(Format(_lc[I_PLAYER_KILL],
2351 [FName, s]),
2352 gShowKillMsg);
2353 end;
2354 end
2355 else // Особые типы смерти
2356 case t of
2357 HIT_DISCON: ;
2358 HIT_SELF: g_Console_Add(Format(_lc[I_PLAYER_KILL_SELF], [FName]), True);
2359 HIT_FALL: g_Console_Add(Format(_lc[I_PLAYER_KILL_FALL], [FName]), True);
2360 HIT_WATER: g_Console_Add(Format(_lc[I_PLAYER_KILL_WATER], [FName]), True);
2361 HIT_ACID: g_Console_Add(Format(_lc[I_PLAYER_KILL_ACID], [FName]), True);
2362 HIT_TRAP: g_Console_Add(Format(_lc[I_PLAYER_KILL_TRAP], [FName]), True);
2363 else g_Console_Add(Format(_lc[I_PLAYER_DIED], [FName]), True);
2364 end;
2366 if Srv then
2367 begin
2368 // Выброс оружия:
2369 for a := WP_FIRST to WP_LAST do
2370 if FWeapon[a] then
2371 begin
2372 case a of
2373 WEAPON_SAW: i := ITEM_WEAPON_SAW;
2374 WEAPON_SHOTGUN1: i := ITEM_WEAPON_SHOTGUN1;
2375 WEAPON_SHOTGUN2: i := ITEM_WEAPON_SHOTGUN2;
2376 WEAPON_CHAINGUN: i := ITEM_WEAPON_CHAINGUN;
2377 WEAPON_ROCKETLAUNCHER: i := ITEM_WEAPON_ROCKETLAUNCHER;
2378 WEAPON_PLASMA: i := ITEM_WEAPON_PLASMA;
2379 WEAPON_BFG: i := ITEM_WEAPON_BFG;
2380 WEAPON_SUPERPULEMET: i := ITEM_WEAPON_SUPERPULEMET;
2381 WEAPON_FLAMETHROWER: i := ITEM_WEAPON_FLAMETHROWER;
2382 else i := 0;
2383 end;
2385 if i <> 0 then
2386 PushItem(i);
2387 end;
2389 // Выброс рюкзака:
2390 if R_ITEM_BACKPACK in FRulez then
2391 PushItem(ITEM_AMMO_BACKPACK);
2393 // Выброс ракетного ранца:
2394 if FJetFuel > 0 then
2395 PushItem(ITEM_JETPACK);
2397 // Выброс ключей:
2398 if (not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) or
2399 (not LongBool(gGameSettings.Options and GAME_OPTION_DMKEYS)) then
2400 begin
2401 if R_KEY_RED in FRulez then
2402 PushItem(ITEM_KEY_RED);
2404 if R_KEY_GREEN in FRulez then
2405 PushItem(ITEM_KEY_GREEN);
2407 if R_KEY_BLUE in FRulez then
2408 PushItem(ITEM_KEY_BLUE);
2409 end;
2411 // Выброс флага:
2412 DropFlag(KillType = K_FALLKILL);
2413 end;
2415 {$IFDEF ENABLE_CORPSES}
2416 FCorpse := g_Corpses_Create(Self);
2417 {$ENDIF}
2419 if Srv and (gGameSettings.MaxLives > 0) and FNoRespawn and
2420 (gLMSRespawn = LMS_RESPAWN_NONE) then
2421 begin
2422 a := 0;
2423 k := 0;
2424 ar := 0;
2425 ab := 0;
2426 for i := Low(gPlayers) to High(gPlayers) do
2427 begin
2428 if gPlayers[i] = nil then continue;
2429 if (not gPlayers[i].FNoRespawn) and (not gPlayers[i].FSpectator) then
2430 begin
2431 Inc(a);
2432 if gPlayers[i].FTeam = TEAM_RED then Inc(ar)
2433 else if gPlayers[i].FTeam = TEAM_BLUE then Inc(ab);
2434 k := i;
2435 end;
2436 end;
2438 OldLR := gLMSRespawn;
2439 if (gGameSettings.GameMode = GM_COOP) then
2440 begin
2441 if (a = 0) then
2442 begin
2443 // everyone is dead, restart the map
2444 g_Game_Message(_lc[I_MESSAGE_LMS_LOSE], 144);
2445 if Netsrv then
2446 MH_SEND_GameEvent(NET_EV_LMS_LOSE);
2447 gLMSRespawn := LMS_RESPAWN_FINAL;
2448 gLMSRespawnTime := gTime + 5000;
2449 end
2450 else if (a = 1) then
2451 begin
2452 if (gPlayers[k] <> nil) and not (gPlayers[k] is TBot) then
2453 if (gPlayers[k] = gPlayer1) or
2454 (gPlayers[k] = gPlayer2) then
2455 g_Console_Add('*** ' + _lc[I_MESSAGE_LMS_SURVIVOR] + ' ***', True)
2456 else if Netsrv and (gPlayers[k].FClientID >= 0) then
2457 MH_SEND_GameEvent(NET_EV_LMS_SURVIVOR, 0, 'N', gPlayers[k].FClientID);
2458 end;
2459 end
2460 else if (gGameSettings.GameMode = GM_TDM) then
2461 begin
2462 if (ab = 0) and (ar <> 0) then
2463 begin
2464 // blu team ded
2465 g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 144);
2466 if Netsrv then
2467 MH_SEND_GameEvent(NET_EV_TLMS_WIN, TEAM_RED);
2468 Inc(gTeamStat[TEAM_RED].Score);
2469 gLMSRespawn := LMS_RESPAWN_FINAL;
2470 gLMSRespawnTime := gTime + 5000;
2471 end
2472 else if (ar = 0) and (ab <> 0) then
2473 begin
2474 // red team ded
2475 g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 144);
2476 if Netsrv then
2477 MH_SEND_GameEvent(NET_EV_TLMS_WIN, TEAM_BLUE);
2478 Inc(gTeamStat[TEAM_BLUE].Score);
2479 gLMSRespawn := LMS_RESPAWN_FINAL;
2480 gLMSRespawnTime := gTime + 5000;
2481 end
2482 else if (ar = 0) and (ab = 0) then
2483 begin
2484 // everyone ded
2485 g_Game_Message(_lc[I_GAME_WIN_DRAW], 144);
2486 if Netsrv then
2487 MH_SEND_GameEvent(NET_EV_LMS_DRAW, 0, FName);
2488 gLMSRespawn := LMS_RESPAWN_FINAL;
2489 gLMSRespawnTime := gTime + 5000;
2490 end;
2491 end
2492 else if (gGameSettings.GameMode = GM_DM) then
2493 begin
2494 if (a = 1) then
2495 begin
2496 if gPlayers[k] <> nil then
2497 with gPlayers[k] do
2498 begin
2499 // survivor is the winner
2500 g_Game_Message(Format(_lc[I_MESSAGE_LMS_WIN], [AnsiUpperCase(FName)]), 144);
2501 if Netsrv then
2502 MH_SEND_GameEvent(NET_EV_LMS_WIN, 0, FName);
2503 Inc(FFrags);
2504 end;
2505 gLMSRespawn := LMS_RESPAWN_FINAL;
2506 gLMSRespawnTime := gTime + 5000;
2507 end
2508 else if (a = 0) then
2509 begin
2510 // everyone is dead, restart the map
2511 g_Game_Message(_lc[I_GAME_WIN_DRAW], 144);
2512 if Netsrv then
2513 MH_SEND_GameEvent(NET_EV_LMS_DRAW, 0, FName);
2514 gLMSRespawn := LMS_RESPAWN_FINAL;
2515 gLMSRespawnTime := gTime + 5000;
2516 end;
2517 end;
2518 if srv and (OldLR = LMS_RESPAWN_NONE) and (gLMSRespawn > LMS_RESPAWN_NONE) then
2519 begin
2520 if NetMode = NET_SERVER then
2521 MH_SEND_GameEvent(NET_EV_LMS_WARMUP, gLMSRespawnTime - gTime)
2522 else
2523 g_Console_Add(Format(_lc[I_MSG_WARMUP_START], [(gLMSRespawnTime - gTime) div 1000]), True);
2524 end;
2525 end;
2527 if Netsrv then
2528 begin
2529 MH_SEND_PlayerStats(FUID);
2530 MH_SEND_PlayerDeath(FUID, KillType, t, SpawnerUID);
2531 if gGameSettings.GameMode = GM_TDM then MH_SEND_GameStats;
2532 end;
2534 if srv and FNoRespawn then Spectate(True);
2535 FWantsInGame := True;
2536 end;
2538 function TPlayer.BodyInLiquid(XInc, YInc: Integer): Boolean;
2539 begin
2540 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc, PLAYER_RECT.Width,
2541 PLAYER_RECT.Height-20, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, False);
2542 end;
2544 function TPlayer.BodyInAcid(XInc, YInc: Integer): Boolean;
2545 begin
2546 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc, PLAYER_RECT.Width,
2547 PLAYER_RECT.Height-20, PANEL_ACID1 or PANEL_ACID2, False);
2548 end;
2550 procedure TPlayer.MakeBloodSimple(Count: Word);
2551 {$IFDEF ENABLE_GFX}
2552 var Blood: TModelBlood;
2553 {$ENDIF}
2554 begin
2555 {$IFDEF ENABLE_GFX}
2556 Blood := SELF.FModel.GetBlood();
2557 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)+8,
2558 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
2559 Count div 2, 3, -1, 16, (PLAYER_RECT.Height*2 div 3),
2560 Blood.R, Blood.G, Blood.B, Blood.Kind);
2561 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-8,
2562 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
2563 Count div 2, -3, -1, 16, (PLAYER_RECT.Height*2) div 3,
2564 Blood.R, Blood.G, Blood.B, Blood.Kind);
2565 {$ENDIF}
2566 end;
2568 procedure TPlayer.MakeBloodVector(Count: Word; VelX, VelY: Integer);
2569 {$IFDEF ENABLE_GFX}
2570 var Blood: TModelBlood;
2571 {$ENDIF}
2572 begin
2573 {$IFDEF ENABLE_GFX}
2574 Blood := SELF.FModel.GetBlood();
2575 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
2576 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
2577 Count, VelX, VelY, 16, (PLAYER_RECT.Height*2) div 3,
2578 Blood.R, Blood.G, Blood.B, Blood.Kind);
2579 {$ENDIF}
2580 end;
2582 procedure TPlayer.ProcessWeaponAction(Action: Byte);
2583 begin
2584 if g_Game_IsClient then Exit;
2585 case Action of
2586 WP_PREV: PrevWeapon();
2587 WP_NEXT: NextWeapon();
2588 end;
2589 end;
2591 procedure TPlayer.QueueWeaponSwitch(Weapon: Byte);
2592 begin
2593 if g_Game_IsClient then Exit;
2594 if Weapon > High(FWeapon) then Exit;
2595 FNextWeap := FNextWeap or (1 shl Weapon);
2596 end;
2598 procedure TPlayer.resetWeaponQueue ();
2599 begin
2600 FNextWeap := 0;
2601 FNextWeapDelay := 0;
2602 end;
2604 function TPlayer.hasAmmoForWeapon (weapon: Byte): Boolean;
2605 begin
2606 result := false;
2607 case weapon of
2608 WEAPON_KASTET, WEAPON_SAW: result := true;
2609 WEAPON_SHOTGUN1, WEAPON_SHOTGUN2, WEAPON_SUPERPULEMET: result := (FAmmo[A_SHELLS] > 0);
2610 WEAPON_PISTOL, WEAPON_CHAINGUN: result := (FAmmo[A_BULLETS] > 0);
2611 WEAPON_ROCKETLAUNCHER: result := (FAmmo[A_ROCKETS] > 0);
2612 WEAPON_PLASMA, WEAPON_BFG: result := (FAmmo[A_CELLS] > 0);
2613 WEAPON_FLAMETHROWER: result := (FAmmo[A_FUEL] > 0);
2614 else result := (weapon < length(FWeapon));
2615 end;
2616 end;
2618 function TPlayer.hasAmmoForShooting (weapon: Byte): Boolean;
2619 begin
2620 result := false;
2621 case weapon of
2622 WEAPON_KASTET, WEAPON_SAW: result := true;
2623 WEAPON_SHOTGUN1, WEAPON_SUPERPULEMET: result := (FAmmo[A_SHELLS] > 0);
2624 WEAPON_SHOTGUN2: result := (FAmmo[A_SHELLS] > 1);
2625 WEAPON_PISTOL, WEAPON_CHAINGUN: result := (FAmmo[A_BULLETS] > 0);
2626 WEAPON_ROCKETLAUNCHER: result := (FAmmo[A_ROCKETS] > 0);
2627 WEAPON_PLASMA: result := (FAmmo[A_CELLS] > 0);
2628 WEAPON_BFG: result := (FAmmo[A_CELLS] >= 40);
2629 WEAPON_FLAMETHROWER: result := (FAmmo[A_FUEL] > 0);
2630 else result := (weapon < length(FWeapon));
2631 end;
2632 end;
2634 function TPlayer.shouldSwitch (weapon: Byte; hadWeapon: Boolean): Boolean;
2635 begin
2636 result := false;
2637 if (weapon > WP_LAST + 1) then
2638 begin
2639 result := false;
2640 exit;
2641 end;
2642 if (FWeapSwitchMode = 1) and not hadWeapon then
2643 result := true
2644 else if (FWeapSwitchMode = 2) then
2645 result := (FWeapPreferences[weapon] > FWeapPreferences[FCurrWeap]);
2646 end;
2648 // return 255 for "no switch"
2649 function TPlayer.getNextWeaponIndex (): Byte;
2650 var
2651 i: Word;
2652 wantThisWeapon: array[0..64] of Boolean;
2653 wwc: Integer = 0; //HACK!
2654 dir, cwi: Integer;
2655 begin
2656 result := 255; // default result: "no switch"
2657 //e_LogWriteFln('FSWITCHTOEMPTY: %s', [FSwitchToEmpty], TMsgType.Notify);
2658 // had weapon cycling on previous frame? remove that flag
2659 if (FNextWeap and $2000) <> 0 then
2660 begin
2661 FNextWeap := FNextWeap and $1FFF;
2662 FNextWeapDelay := 0;
2663 end;
2664 // cycling has priority
2665 if (FNextWeap and $C000) <> 0 then
2666 begin
2667 if (FNextWeap and $8000) <> 0 then
2668 dir := 1
2669 else
2670 dir := -1;
2671 FNextWeap := FNextWeap or $2000; // we need this
2672 if FNextWeapDelay > 0 then
2673 exit; // cooldown time
2674 cwi := FCurrWeap;
2675 for i := 0 to High(FWeapon) do
2676 begin
2677 cwi := (cwi+length(FWeapon)+dir) mod length(FWeapon);
2678 if FWeapon[cwi] and maySwitch(cwi) then
2679 begin
2680 //e_LogWriteFln(' SWITCH: cur=%d; new=%d %s %s', [FCurrWeap, cwi, FSwitchToEmpty, hasAmmoForWeapon(cwi)], TMsgType.Notify);
2681 result := Byte(cwi);
2682 FNextWeapDelay := WEAPON_DELAY;
2683 exit;
2684 end;
2685 end;
2686 resetWeaponQueue();
2687 exit;
2688 end;
2689 // no cycling
2690 for i := 0 to High(wantThisWeapon) do
2691 wantThisWeapon[i] := false;
2692 for i := 0 to High(FWeapon) do
2693 if (FNextWeap and (1 shl i)) <> 0 then
2694 begin
2695 wantThisWeapon[i] := true;
2696 Inc(wwc);
2697 end;
2699 // exclude currently selected weapon from the set
2700 wantThisWeapon[FCurrWeap] := false;
2701 // slow down alterations a little
2702 if wwc > 1 then
2703 begin
2704 //e_WriteLog(Format(' FNextWeap=%x; delay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
2705 // more than one weapon requested, assume "alteration" and check alteration delay
2706 if FNextWeapDelay > 0 then
2707 begin
2708 FNextWeap := 0;
2709 exit;
2710 end; // yeah
2711 end;
2712 // do not reset weapon queue, it will be done in `RealizeCurrentWeapon()`
2713 // but clear all counters if no weapon should be switched
2714 if wwc < 1 then
2715 begin
2716 resetWeaponQueue();
2717 exit;
2718 end;
2719 //e_WriteLog(Format('wwc=%d', [wwc]), MSG_WARNING);
2720 // try weapons in descending order
2721 for i := High(FWeapon) downto 0 do
2722 begin
2723 if wantThisWeapon[i] and FWeapon[i] and ((wwc = 1) or hasAmmoForWeapon(i)) then
2724 begin
2725 // i found her!
2726 result := Byte(i);
2727 resetWeaponQueue();
2728 FNextWeapDelay := WEAPON_DELAY * 2; // anyway, 'cause why not
2729 //e_LogWriteFln('FOUND %s %s %s', [result, FSwitchToEmpty, hasAmmoForWeapon(i)], TMsgType.Notify);
2730 exit;
2731 end;
2732 end;
2733 // no suitable weapon found, so reset the queue, to avoid accidental "queuing" of weapon w/o ammo
2734 resetWeaponQueue();
2735 end;
2737 procedure TPlayer.RealizeCurrentWeapon();
2738 function switchAllowed (): Boolean;
2739 var
2740 i: Byte;
2741 begin
2742 result := false;
2743 if FBFGFireCounter <> -1 then
2744 exit;
2745 if FTime[T_SWITCH] > gTime then
2746 exit;
2747 for i := WP_FIRST to WP_LAST do
2748 if FReloading[i] > 0 then
2749 exit;
2750 result := true;
2751 end;
2753 var
2754 nw: Byte;
2755 begin
2756 //e_WriteLog(Format('***RealizeCurrentWeapon: FNextWeap=%x; FNextWeapDelay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
2757 //FNextWeap := FNextWeap and $1FFF;
2758 if FNextWeapDelay > 0 then Dec(FNextWeapDelay); // "alteration delay"
2760 if not switchAllowed then
2761 begin
2762 //HACK for weapon cycling
2763 if (FNextWeap and $E000) <> 0 then FNextWeap := 0;
2764 exit;
2765 end;
2767 nw := getNextWeaponIndex();
2768 //
2769 if nw = 255 then exit; // don't reset anything here
2770 if nw > High(FWeapon) then
2771 begin
2772 // don't forget to reset queue here!
2773 //e_WriteLog(' RealizeCurrentWeapon: WUTAFUUUU', MSG_WARNING);
2774 resetWeaponQueue();
2775 exit;
2776 end;
2778 if FWeapon[nw] then
2779 begin
2780 FCurrWeap := nw;
2781 FTime[T_SWITCH] := gTime+156;
2782 if FCurrWeap = WEAPON_SAW then FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
2783 FModel.SetWeapon(FCurrWeap);
2784 if g_Game_IsNet then MH_SEND_PlayerStats(FUID);
2785 end;
2786 end;
2788 procedure TPlayer.NextWeapon();
2789 begin
2790 if g_Game_IsClient then Exit;
2791 FNextWeap := $8000;
2792 end;
2794 procedure TPlayer.PrevWeapon();
2795 begin
2796 if g_Game_IsClient then Exit;
2797 FNextWeap := $4000;
2798 end;
2800 procedure TPlayer.SetWeapon(W: Byte);
2801 begin
2802 if FCurrWeap <> W then
2803 if W = WEAPON_SAW then
2804 FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
2806 FCurrWeap := W;
2807 FModel.SetWeapon(CurrWeap);
2808 resetWeaponQueue();
2809 end;
2811 function TPlayer.PickItem(ItemType: Byte; arespawn: Boolean; var remove: Boolean): Boolean;
2812 var
2813 a: Boolean;
2814 switchWeapon: Byte = 255;
2815 hadWeapon: Boolean = False;
2816 begin
2817 Result := False;
2818 if g_Game_IsClient then Exit;
2820 // a = true - место спавна предмета:
2821 a := LongBool(gGameSettings.Options and GAME_OPTION_WEAPONSTAY) and arespawn;
2822 remove := not a;
2823 case ItemType of
2824 ITEM_MEDKIT_SMALL:
2825 if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
2826 begin
2827 if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 10, PLAYER_HP_SOFT);
2828 Result := True;
2829 remove := True;
2830 FFireTime := 0;
2831 if gFlash = 2 then Inc(FPickup, 5);
2832 end;
2834 ITEM_MEDKIT_LARGE:
2835 if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
2836 begin
2837 if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 25, PLAYER_HP_SOFT);
2838 Result := True;
2839 remove := True;
2840 FFireTime := 0;
2841 if gFlash = 2 then Inc(FPickup, 5);
2842 end;
2844 ITEM_ARMOR_GREEN:
2845 if FArmor < PLAYER_AP_SOFT then
2846 begin
2847 FArmor := PLAYER_AP_SOFT;
2848 Result := True;
2849 remove := True;
2850 if gFlash = 2 then Inc(FPickup, 5);
2851 end;
2853 ITEM_ARMOR_BLUE:
2854 if FArmor < PLAYER_AP_LIMIT then
2855 begin
2856 FArmor := PLAYER_AP_LIMIT;
2857 Result := True;
2858 remove := True;
2859 if gFlash = 2 then Inc(FPickup, 5);
2860 end;
2862 ITEM_SPHERE_BLUE:
2863 if (FHealth < PLAYER_HP_LIMIT) or (FFireTime > 0) then
2864 begin
2865 if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 100, PLAYER_HP_LIMIT);
2866 Result := True;
2867 remove := True;
2868 FFireTime := 0;
2869 if gFlash = 2 then Inc(FPickup, 5);
2870 end;
2872 ITEM_SPHERE_WHITE:
2873 if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) or (FFireTime > 0) then
2874 begin
2875 if FHealth < PLAYER_HP_LIMIT then
2876 FHealth := PLAYER_HP_LIMIT;
2877 if FArmor < PLAYER_AP_LIMIT then
2878 FArmor := PLAYER_AP_LIMIT;
2879 Result := True;
2880 remove := True;
2881 FFireTime := 0;
2882 if gFlash = 2 then Inc(FPickup, 5);
2883 end;
2885 ITEM_WEAPON_SAW:
2886 if (not FWeapon[WEAPON_SAW]) or ((not arespawn) and (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) then
2887 begin
2888 hadWeapon := FWeapon[WEAPON_SAW];
2889 switchWeapon := WEAPON_SAW;
2890 FWeapon[WEAPON_SAW] := True;
2891 Result := True;
2892 if gFlash = 2 then Inc(FPickup, 5);
2893 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
2894 end;
2896 ITEM_WEAPON_SHOTGUN1:
2897 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SHOTGUN1] then
2898 begin
2899 // Нужно, чтобы не взять все пули сразу:
2900 if a and FWeapon[WEAPON_SHOTGUN1] then Exit;
2901 hadWeapon := FWeapon[WEAPON_SHOTGUN1];
2902 switchWeapon := WEAPON_SHOTGUN1;
2903 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
2904 FWeapon[WEAPON_SHOTGUN1] := True;
2905 Result := True;
2906 if gFlash = 2 then Inc(FPickup, 5);
2907 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
2908 end;
2910 ITEM_WEAPON_SHOTGUN2:
2911 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SHOTGUN2] then
2912 begin
2913 if a and FWeapon[WEAPON_SHOTGUN2] then Exit;
2914 hadWeapon := FWeapon[WEAPON_SHOTGUN2];
2915 switchWeapon := WEAPON_SHOTGUN2;
2916 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
2917 FWeapon[WEAPON_SHOTGUN2] := True;
2918 Result := True;
2919 if gFlash = 2 then Inc(FPickup, 5);
2920 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
2921 end;
2923 ITEM_WEAPON_CHAINGUN:
2924 if (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or not FWeapon[WEAPON_CHAINGUN] then
2925 begin
2926 if a and FWeapon[WEAPON_CHAINGUN] then Exit;
2927 hadWeapon := FWeapon[WEAPON_CHAINGUN];
2928 switchWeapon := WEAPON_CHAINGUN;
2929 IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
2930 FWeapon[WEAPON_CHAINGUN] := True;
2931 Result := True;
2932 if gFlash = 2 then Inc(FPickup, 5);
2933 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
2934 end;
2936 ITEM_WEAPON_ROCKETLAUNCHER:
2937 if (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or not FWeapon[WEAPON_ROCKETLAUNCHER] then
2938 begin
2939 if a and FWeapon[WEAPON_ROCKETLAUNCHER] then Exit;
2940 switchWeapon := WEAPON_ROCKETLAUNCHER;
2941 hadWeapon := FWeapon[WEAPON_ROCKETLAUNCHER];
2942 IncMax(FAmmo[A_ROCKETS], 2, FMaxAmmo[A_ROCKETS]);
2943 FWeapon[WEAPON_ROCKETLAUNCHER] := True;
2944 Result := True;
2945 if gFlash = 2 then Inc(FPickup, 5);
2946 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
2947 end;
2949 ITEM_WEAPON_PLASMA:
2950 if (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or not FWeapon[WEAPON_PLASMA] then
2951 begin
2952 if a and FWeapon[WEAPON_PLASMA] then Exit;
2953 switchWeapon := WEAPON_PLASMA;
2954 hadWeapon := FWeapon[WEAPON_PLASMA];
2955 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
2956 FWeapon[WEAPON_PLASMA] := True;
2957 Result := True;
2958 if gFlash = 2 then Inc(FPickup, 5);
2959 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
2960 end;
2962 ITEM_WEAPON_BFG:
2963 if (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or not FWeapon[WEAPON_BFG] then
2964 begin
2965 if a and FWeapon[WEAPON_BFG] then Exit;
2966 switchWeapon := WEAPON_BFG;
2967 hadWeapon := FWeapon[WEAPON_BFG];
2968 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
2969 FWeapon[WEAPON_BFG] := True;
2970 Result := True;
2971 if gFlash = 2 then Inc(FPickup, 5);
2972 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
2973 end;
2975 ITEM_WEAPON_SUPERPULEMET:
2976 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SUPERPULEMET] then
2977 begin
2978 if a and FWeapon[WEAPON_SUPERPULEMET] then Exit;
2979 switchWeapon := WEAPON_SUPERPULEMET;
2980 hadWeapon := FWeapon[WEAPON_SUPERPULEMET];
2981 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
2982 FWeapon[WEAPON_SUPERPULEMET] := True;
2983 Result := True;
2984 if gFlash = 2 then Inc(FPickup, 5);
2985 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
2986 end;
2988 ITEM_WEAPON_FLAMETHROWER:
2989 if (FAmmo[A_FUEL] < FMaxAmmo[A_FUEL]) or not FWeapon[WEAPON_FLAMETHROWER] then
2990 begin
2991 if a and FWeapon[WEAPON_FLAMETHROWER] then Exit;
2992 switchWeapon := WEAPON_FLAMETHROWER;
2993 hadWeapon := FWeapon[WEAPON_FLAMETHROWER];
2994 IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
2995 FWeapon[WEAPON_FLAMETHROWER] := True;
2996 Result := True;
2997 if gFlash = 2 then Inc(FPickup, 5);
2998 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
2999 end;
3001 ITEM_AMMO_BULLETS:
3002 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
3003 begin
3004 IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
3005 Result := True;
3006 remove := True;
3007 if gFlash = 2 then Inc(FPickup, 5);
3008 end;
3010 ITEM_AMMO_BULLETS_BOX:
3011 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
3012 begin
3013 IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
3014 Result := True;
3015 remove := True;
3016 if gFlash = 2 then Inc(FPickup, 5);
3017 end;
3019 ITEM_AMMO_SHELLS:
3020 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
3021 begin
3022 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
3023 Result := True;
3024 remove := True;
3025 if gFlash = 2 then Inc(FPickup, 5);
3026 end;
3028 ITEM_AMMO_SHELLS_BOX:
3029 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
3030 begin
3031 IncMax(FAmmo[A_SHELLS], 25, FMaxAmmo[A_SHELLS]);
3032 Result := True;
3033 remove := True;
3034 if gFlash = 2 then Inc(FPickup, 5);
3035 end;
3037 ITEM_AMMO_ROCKET:
3038 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
3039 begin
3040 IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
3041 Result := True;
3042 remove := True;
3043 if gFlash = 2 then Inc(FPickup, 5);
3044 end;
3046 ITEM_AMMO_ROCKET_BOX:
3047 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
3048 begin
3049 IncMax(FAmmo[A_ROCKETS], 5, FMaxAmmo[A_ROCKETS]);
3050 Result := True;
3051 remove := True;
3052 if gFlash = 2 then Inc(FPickup, 5);
3053 end;
3055 ITEM_AMMO_CELL:
3056 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
3057 begin
3058 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
3059 Result := True;
3060 remove := True;
3061 if gFlash = 2 then Inc(FPickup, 5);
3062 end;
3064 ITEM_AMMO_CELL_BIG:
3065 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
3066 begin
3067 IncMax(FAmmo[A_CELLS], 100, FMaxAmmo[A_CELLS]);
3068 Result := True;
3069 remove := True;
3070 if gFlash = 2 then Inc(FPickup, 5);
3071 end;
3073 ITEM_AMMO_FUELCAN:
3074 if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then
3075 begin
3076 IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
3077 Result := True;
3078 remove := True;
3079 if gFlash = 2 then Inc(FPickup, 5);
3080 end;
3082 ITEM_AMMO_BACKPACK:
3083 if not(R_ITEM_BACKPACK in FRulez) or
3084 (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or
3085 (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or
3086 (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or
3087 (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or
3088 (FAmmo[A_FUEL] < FMaxAmmo[A_FUEL]) then
3089 begin
3090 FMaxAmmo[A_BULLETS] := AmmoLimits[1, A_BULLETS];
3091 FMaxAmmo[A_SHELLS] := AmmoLimits[1, A_SHELLS];
3092 FMaxAmmo[A_ROCKETS] := AmmoLimits[1, A_ROCKETS];
3093 FMaxAmmo[A_CELLS] := AmmoLimits[1, A_CELLS];
3094 FMaxAmmo[A_FUEL] := AmmoLimits[1, A_FUEL];
3096 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
3097 IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
3098 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
3099 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
3100 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
3101 IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
3102 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
3103 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
3104 if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then
3105 IncMax(FAmmo[A_FUEL], 50, FMaxAmmo[A_FUEL]);
3107 FRulez := FRulez + [R_ITEM_BACKPACK];
3108 Result := True;
3109 remove := True;
3110 if gFlash = 2 then Inc(FPickup, 5);
3111 end;
3113 ITEM_KEY_RED:
3114 if not(R_KEY_RED in FRulez) then
3115 begin
3116 Include(FRulez, R_KEY_RED);
3117 Result := True;
3118 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
3119 if gFlash = 2 then Inc(FPickup, 5);
3120 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
3121 end;
3123 ITEM_KEY_GREEN:
3124 if not(R_KEY_GREEN in FRulez) then
3125 begin
3126 Include(FRulez, R_KEY_GREEN);
3127 Result := True;
3128 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
3129 if gFlash = 2 then Inc(FPickup, 5);
3130 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
3131 end;
3133 ITEM_KEY_BLUE:
3134 if not(R_KEY_BLUE in FRulez) then
3135 begin
3136 Include(FRulez, R_KEY_BLUE);
3137 Result := True;
3138 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
3139 if gFlash = 2 then Inc(FPickup, 5);
3140 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
3141 end;
3143 ITEM_SUIT:
3144 if FMegaRulez[MR_SUIT] < gTime+PLAYER_SUIT_TIME then
3145 begin
3146 FMegaRulez[MR_SUIT] := gTime+PLAYER_SUIT_TIME;
3147 Result := True;
3148 remove := True;
3149 FFireTime := 0;
3150 if gFlash = 2 then Inc(FPickup, 5);
3151 end;
3153 ITEM_OXYGEN:
3154 if FAir < AIR_MAX then
3155 begin
3156 FAir := AIR_MAX;
3157 Result := True;
3158 remove := True;
3159 if gFlash = 2 then Inc(FPickup, 5);
3160 end;
3162 ITEM_MEDKIT_BLACK:
3163 begin
3164 if not (R_BERSERK in FRulez) then
3165 begin
3166 Include(FRulez, R_BERSERK);
3167 if (FBFGFireCounter = -1) then
3168 begin
3169 FCurrWeap := WEAPON_KASTET;
3170 resetWeaponQueue();
3171 FModel.SetWeapon(WEAPON_KASTET);
3172 end;
3173 if gFlash <> 0 then
3174 begin
3175 Inc(FPain, 100);
3176 if gFlash = 2 then Inc(FPickup, 5);
3177 end;
3178 FBerserk := gTime+30000;
3179 Result := True;
3180 remove := True;
3181 FFireTime := 0;
3182 end;
3183 if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
3184 begin
3185 if FHealth < PLAYER_HP_SOFT then FHealth := PLAYER_HP_SOFT;
3186 FBerserk := gTime+30000;
3187 Result := True;
3188 remove := True;
3189 FFireTime := 0;
3190 end;
3191 end;
3193 ITEM_INVUL:
3194 if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
3195 begin
3196 FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
3197 FSpawnInvul := 0;
3198 Result := True;
3199 remove := True;
3200 if gFlash = 2 then Inc(FPickup, 5);
3201 end;
3203 ITEM_BOTTLE:
3204 if (FHealth < PLAYER_HP_LIMIT) or (FFireTime > 0) then
3205 begin
3206 if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 4, PLAYER_HP_LIMIT);
3207 Result := True;
3208 remove := True;
3209 FFireTime := 0;
3210 if gFlash = 2 then Inc(FPickup, 5);
3211 end;
3213 ITEM_HELMET:
3214 if FArmor < PLAYER_AP_LIMIT then
3215 begin
3216 IncMax(FArmor, 5, PLAYER_AP_LIMIT);
3217 Result := True;
3218 remove := True;
3219 if gFlash = 2 then Inc(FPickup, 5);
3220 end;
3222 ITEM_JETPACK:
3223 if FJetFuel < JET_MAX then
3224 begin
3225 FJetFuel := JET_MAX;
3226 Result := True;
3227 remove := True;
3228 if gFlash = 2 then Inc(FPickup, 5);
3229 end;
3231 ITEM_INVIS:
3232 if FMegaRulez[MR_INVIS] < gTime+PLAYER_INVIS_TIME then
3233 begin
3234 FMegaRulez[MR_INVIS] := gTime+PLAYER_INVIS_TIME;
3235 Result := True;
3236 remove := True;
3237 if gFlash = 2 then Inc(FPickup, 5);
3238 end;
3239 end;
3241 if (shouldSwitch(switchWeapon, hadWeapon)) then
3242 QueueWeaponSwitch(switchWeapon);
3243 end;
3245 procedure TPlayer.Touch();
3246 begin
3247 if not FAlive then
3248 Exit;
3249 //FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
3250 if FIamBot then
3251 begin
3252 // Бросить флаг товарищу:
3253 if gGameSettings.GameMode = GM_CTF then
3254 DropFlag();
3255 end;
3256 end;
3258 procedure TPlayer.Push(vx, vy: Integer);
3259 begin
3260 if (not FPhysics) and FGhost then
3261 Exit;
3262 FObj.Accel.X := FObj.Accel.X + vx;
3263 FObj.Accel.Y := FObj.Accel.Y + vy;
3264 if g_Game_IsNet and g_Game_IsServer then
3265 MH_SEND_PlayerPos(True, FUID, NET_EVERYONE);
3266 end;
3268 procedure TPlayer.Reset(Force: Boolean);
3269 begin
3270 if Force then
3271 FAlive := False;
3273 FSpawned := False;
3274 FTime[T_RESPAWN] := 0;
3275 FTime[T_FLAGCAP] := 0;
3276 FGodMode := False;
3277 FNoTarget := False;
3278 FNoReload := False;
3279 FFrags := 0;
3280 FLastFrag := 0;
3281 FComboEvnt := -1;
3282 FKills := 0;
3283 FMonsterKills := 0;
3284 FDeath := 0;
3285 FSecrets := 0;
3286 FSpawnInvul := 0;
3287 {$IFDEF ENABLE_CORPSES}
3288 FCorpse := -1;
3289 {$ENDIF}
3290 FReady := False;
3291 if FNoRespawn then
3292 begin
3293 FSpectator := False;
3294 FGhost := False;
3295 FPhysics := True;
3296 FSpectatePlayer := -1;
3297 FNoRespawn := False;
3298 end;
3299 FLives := gGameSettings.MaxLives;
3301 SetFlag(FLAG_NONE);
3302 end;
3304 procedure TPlayer.SoftReset();
3305 begin
3306 ReleaseKeys();
3308 FDamageBuffer := 0;
3309 FSlopeOld := 0;
3310 FIncCamOld := 0;
3311 FIncCam := 0;
3312 FBFGFireCounter := -1;
3313 {$IFDEF ENABLE_SHELLS}
3314 FShellTimer := -1;
3315 {$ENDIF}
3316 FPain := 0;
3317 FLastHit := 0;
3318 FLastFrag := 0;
3319 FComboEvnt := -1;
3321 SetFlag(FLAG_NONE);
3322 SetAction(A_STAND, True);
3323 end;
3325 function TPlayer.GetRespawnPoint(): Byte;
3326 var
3327 c: Byte;
3328 begin
3329 Result := 255;
3330 // На будущее: FSpawn - игрок уже играл и перерождается
3332 // Одиночная игра/кооператив
3333 if gGameSettings.GameMode in [GM_COOP, GM_SINGLE] then
3334 begin
3335 if Self = gPlayer1 then
3336 begin
3337 // player 1 should try to spawn on the player 1 point
3338 if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1) > 0 then
3339 Exit(RESPAWNPOINT_PLAYER1)
3340 else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2) > 0 then
3341 Exit(RESPAWNPOINT_PLAYER2);
3342 end
3343 else if Self = gPlayer2 then
3344 begin
3345 // player 2 should try to spawn on the player 2 point
3346 if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2) > 0 then
3347 Exit(RESPAWNPOINT_PLAYER2)
3348 else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1) > 0 then
3349 Exit(RESPAWNPOINT_PLAYER1);
3350 end
3351 else
3352 begin
3353 // other players randomly pick either the first or the second point
3354 c := IfThen((Random(2) = 0), RESPAWNPOINT_PLAYER1, RESPAWNPOINT_PLAYER2);
3355 if g_Map_GetPointCount(c) > 0 then
3356 Exit(c);
3357 // try the other one
3358 c := IfThen((c = RESPAWNPOINT_PLAYER1), RESPAWNPOINT_PLAYER2, RESPAWNPOINT_PLAYER1);
3359 if g_Map_GetPointCount(c) > 0 then
3360 Exit(c);
3361 end;
3362 end;
3364 // Мясоповал
3365 if gGameSettings.GameMode = GM_DM then
3366 begin
3367 // try DM points first
3368 if g_Map_GetPointCount(RESPAWNPOINT_DM) > 0 then
3369 Exit(RESPAWNPOINT_DM);
3370 end;
3372 // Командные
3373 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
3374 begin
3375 // try team points first
3376 c := RESPAWNPOINT_DM;
3377 if FTeam = TEAM_RED then
3378 c := RESPAWNPOINT_RED
3379 else if FTeam = TEAM_BLUE then
3380 c := RESPAWNPOINT_BLUE;
3381 if g_Map_GetPointCount(c) > 0 then
3382 Exit(c);
3383 end;
3385 // still haven't found a spawnpoint, try random shit
3386 Result := g_Map_GetRandomPointType();
3387 end;
3389 procedure TPlayer.Respawn(Silent: Boolean; Force: Boolean = False);
3390 var
3391 RespawnPoint: TRespawnPoint;
3392 a, b, c: Byte;
3393 begin
3394 FSlopeOld := 0;
3395 FIncCamOld := 0;
3396 FIncCam := 0;
3397 FBFGFireCounter := -1;
3398 {$IFDEF ENABLE_SHELLS}
3399 FShellTimer := -1;
3400 {$ENDIF}
3401 FPain := 0;
3402 FLastHit := 0;
3403 FSpawnInvul := 0;
3405 {$IFDEF ENABLE_CORPSES}
3406 FCorpse := -1;
3407 {$ENDIF}
3409 if not g_Game_IsServer then
3410 Exit;
3411 if FDummy then
3412 Exit;
3413 FWantsInGame := True;
3414 FJustTeleported := True;
3415 if Force then
3416 begin
3417 FTime[T_RESPAWN] := 0;
3418 FAlive := False;
3419 end;
3420 FNetTime := 0;
3421 // if server changes MaxLives we gotta be ready
3422 if gGameSettings.MaxLives = 0 then FNoRespawn := False;
3424 // Еще нельзя возродиться:
3425 if FTime[T_RESPAWN] > gTime then
3426 Exit;
3428 // Просрал все жизни:
3429 if FNoRespawn then
3430 begin
3431 if not FSpectator then Spectate(True);
3432 FWantsInGame := True;
3433 Exit;
3434 end;
3436 if (gGameSettings.GameType <> GT_SINGLE) and (gGameSettings.GameMode <> GM_COOP) then
3437 begin // "Своя игра"
3438 // Берсерк не сохраняется между уровнями:
3439 FRulez := FRulez-[R_BERSERK];
3440 end
3441 else // "Одиночная игра"/"Кооп"
3442 begin
3443 // Берсерк и ключи не сохраняются между уровнями:
3444 FRulez := FRulez-[R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE, R_BERSERK];
3445 end;
3447 // Получаем точку спауна игрока:
3448 c := GetRespawnPoint();
3450 ReleaseKeys();
3451 SetFlag(FLAG_NONE);
3453 // Воскрешение без оружия:
3454 if not FAlive then
3455 begin
3456 FHealth := Round(PLAYER_HP_SOFT * (FHandicap / 100));
3457 FArmor := 0;
3458 FAlive := True;
3459 FAir := AIR_DEF;
3460 FJetFuel := 0;
3462 for a := WP_FIRST to WP_LAST do
3463 begin
3464 FWeapon[a] := False;
3465 FReloading[a] := 0;
3466 end;
3468 FWeapon[WEAPON_PISTOL] := True;
3469 FWeapon[WEAPON_KASTET] := True;
3470 FCurrWeap := WEAPON_PISTOL;
3471 resetWeaponQueue();
3473 FModel.SetWeapon(FCurrWeap);
3475 for b := A_BULLETS to A_HIGH do
3476 FAmmo[b] := 0;
3478 FAmmo[A_BULLETS] := 50;
3480 FMaxAmmo[A_BULLETS] := AmmoLimits[0, A_BULLETS];
3481 FMaxAmmo[A_SHELLS] := AmmoLimits[0, A_SHELLS];
3482 FMaxAmmo[A_ROCKETS] := AmmoLimits[0, A_SHELLS];
3483 FMaxAmmo[A_CELLS] := AmmoLimits[0, A_CELLS];
3484 FMaxAmmo[A_FUEL] := AmmoLimits[0, A_FUEL];
3486 if (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) and
3487 LongBool(gGameSettings.Options and GAME_OPTION_DMKEYS) then
3488 FRulez := [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE]
3489 else
3490 FRulez := [];
3491 end;
3493 // Получаем координаты точки возрождения:
3494 if not g_Map_GetPoint(c, RespawnPoint) then
3495 begin
3496 g_FatalError(_lc[I_GAME_ERROR_GET_SPAWN]);
3497 Exit;
3498 end;
3500 // Установка координат и сброс всех параметров:
3501 FObj.X := RespawnPoint.X-PLAYER_RECT.X;
3502 FObj.Y := RespawnPoint.Y-PLAYER_RECT.Y;
3503 FObj.oldX := FObj.X; // don't interpolate after respawn
3504 FObj.oldY := FObj.Y;
3505 FObj.Vel.X := 0;
3506 FObj.Vel.Y := 0;
3507 FObj.Accel.X := 0;
3508 FObj.Accel.Y := 0;
3510 FDirection := RespawnPoint.Direction;
3511 if FDirection = TDirection.D_LEFT then
3512 FAngle := 180
3513 else
3514 FAngle := 0;
3516 SetAction(A_STAND, True);
3517 FModel.Direction := FDirection;
3519 for a := Low(FTime) to High(FTime) do
3520 FTime[a] := 0;
3522 for a := Low(FMegaRulez) to High(FMegaRulez) do
3523 FMegaRulez[a] := 0;
3525 // Respawn invulnerability
3526 if (gGameSettings.GameType <> GT_SINGLE) and (gGameSettings.SpawnInvul > 0) then
3527 begin
3528 FMegaRulez[MR_INVUL] := gTime + gGameSettings.SpawnInvul * 1000;
3529 FSpawnInvul := FMegaRulez[MR_INVUL];
3530 end;
3532 FDamageBuffer := 0;
3533 FJetpack := False;
3534 FCanJetpack := False;
3535 FFlaming := False;
3536 FFireTime := 0;
3537 FFirePainTime := 0;
3538 FFireAttacker := 0;
3540 {$IFDEF ENABLE_GFX}
3541 // Анимация возрождения:
3542 if (not gLoadGameMode) and (not Silent) then
3543 begin
3544 g_GFX_QueueEffect(
3545 R_GFX_TELEPORT_FAST,
3546 FObj.X + PLAYER_RECT.X + (PLAYER_RECT.Width div 2) - 32,
3547 FObj.Y + PLAYER_RECT.Y + (PLAYER_RECT.Height div 2) - 32
3548 );
3549 end;
3550 {$ENDIF}
3552 FSpectator := False;
3553 FGhost := False;
3554 FPhysics := True;
3555 FSpectatePlayer := -1;
3556 FSpawned := True;
3558 if (gPlayer1 = nil) and (gSpectLatchPID1 = FUID) then
3559 gPlayer1 := self;
3560 if (gPlayer2 = nil) and (gSpectLatchPID2 = FUID) then
3561 gPlayer2 := self;
3563 if g_Game_IsNet then
3564 begin
3565 MH_SEND_PlayerPos(True, FUID, NET_EVERYONE);
3566 MH_SEND_PlayerStats(FUID, NET_EVERYONE);
3567 if not Silent then
3568 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
3569 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32,
3570 0, NET_GFX_TELE);
3571 end;
3572 end;
3574 procedure TPlayer.Spectate(NoMove: Boolean = False);
3575 begin
3576 if FAlive then
3577 Kill(K_EXTRAHARDKILL, FUID, HIT_SOME)
3578 else if (not NoMove) then
3579 begin
3580 GameX := gMapInfo.Width div 2;
3581 GameY := gMapInfo.Height div 2;
3582 end;
3583 FXTo := GameX;
3584 FYTo := GameY;
3586 FAlive := False;
3587 FSpectator := True;
3588 FGhost := True;
3589 FPhysics := False;
3590 FWantsInGame := False;
3591 FSpawned := False;
3593 {$IFDEF ENABLE_CORPSES}
3594 FCorpse := -1;
3595 {$ENDIF}
3597 if FNoRespawn then
3598 begin
3599 if Self = gPlayer1 then
3600 begin
3601 gSpectLatchPID1 := FUID;
3602 gPlayer1 := nil;
3603 end
3604 else if Self = gPlayer2 then
3605 begin
3606 gSpectLatchPID2 := FUID;
3607 gPlayer2 := nil;
3608 end;
3609 end;
3611 if g_Game_IsNet then
3612 MH_SEND_PlayerStats(FUID);
3613 end;
3615 procedure TPlayer.SwitchNoClip;
3616 begin
3617 if not FAlive then
3618 Exit;
3619 FGhost := not FGhost;
3620 FPhysics := not FGhost;
3621 if FGhost then
3622 begin
3623 FXTo := FObj.X;
3624 FYTo := FObj.Y;
3625 end else
3626 begin
3627 FObj.Accel.X := 0;
3628 FObj.Accel.Y := 0;
3629 end;
3630 end;
3632 procedure TPlayer.Run(Direction: TDirection);
3633 {$IFDEF ENABLE_GIBS}
3634 var a, b: Integer;
3635 {$ENDIF}
3636 begin
3637 if MAX_RUNVEL > 8 then
3638 FlySmoke();
3640 // Бежим:
3641 if Direction = TDirection.D_LEFT then
3642 begin
3643 if FObj.Vel.X > -MAX_RUNVEL then
3644 FObj.Vel.X := FObj.Vel.X - (MAX_RUNVEL shr 3);
3645 end
3646 else
3647 if FObj.Vel.X < MAX_RUNVEL then
3648 FObj.Vel.X := FObj.Vel.X + (MAX_RUNVEL shr 3);
3650 {$IFDEF ENABLE_GIBS}
3651 // Возможно, пинаем куски:
3652 if (FObj.Vel.X <> 0) and (gGibs <> nil) then
3653 begin
3654 b := Abs(FObj.Vel.X);
3655 if b > 1 then b := b * (Random(8 div b) + 1);
3656 for a := 0 to High(gGibs) do
3657 begin
3658 if gGibs[a].alive and
3659 g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
3660 FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then
3661 begin
3662 // Пинаем куски
3663 if FObj.Vel.X < 0 then
3664 begin
3665 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120) // налево
3666 end
3667 else
3668 begin
3669 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // направо
3670 end;
3671 gGibs[a].positionChanged(); // this updates spatial accelerators
3672 end;
3673 end;
3674 end;
3675 {$ENDIF}
3677 SetAction(A_WALK);
3678 end;
3680 procedure TPlayer.SeeDown();
3681 begin
3682 SetAction(A_SEEDOWN);
3684 if FDirection = TDirection.D_LEFT then FAngle := ANGLE_LEFTDOWN else FAngle := ANGLE_RIGHTDOWN;
3686 if FIncCam > -120 then DecMin(FIncCam, 5, -120);
3687 end;
3689 procedure TPlayer.SeeUp();
3690 begin
3691 SetAction(A_SEEUP);
3693 if FDirection = TDirection.D_LEFT then FAngle := ANGLE_LEFTUP else FAngle := ANGLE_RIGHTUP;
3695 if FIncCam < 120 then IncMax(FIncCam, 5, 120);
3696 end;
3698 procedure TPlayer.SetAction(Action: Byte; Force: Boolean = False);
3699 var
3700 Prior: Byte;
3701 begin
3702 case Action of
3703 A_WALK: Prior := 3;
3704 A_DIE1: Prior := 5;
3705 A_DIE2: Prior := 5;
3706 A_ATTACK: Prior := 2;
3707 A_SEEUP: Prior := 1;
3708 A_SEEDOWN: Prior := 1;
3709 A_ATTACKUP: Prior := 2;
3710 A_ATTACKDOWN: Prior := 2;
3711 A_PAIN: Prior := 4;
3712 else Prior := 0;
3713 end;
3715 if (Prior > FActionPrior) or Force then
3716 if not ((Prior = 2) and (FCurrWeap = WEAPON_SAW)) then
3717 begin
3718 FActionPrior := Prior;
3719 FActionAnim := Action;
3720 FActionForce := Force;
3721 FActionChanged := True;
3722 end;
3724 if Action in [A_ATTACK, A_ATTACKUP, A_ATTACKDOWN] then FModel.SetFire(True);
3725 end;
3727 function TPlayer.StayOnStep(XInc, YInc: Integer): Boolean;
3728 begin
3729 Result := not g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+YInc+PLAYER_RECT.Y+PLAYER_RECT.Height-1,
3730 PLAYER_RECT.Width, 1, PANEL_STEP, False)
3731 and g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+YInc+PLAYER_RECT.Y+PLAYER_RECT.Height,
3732 PLAYER_RECT.Width, 1, PANEL_STEP, False);
3733 end;
3735 function TPlayer.TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean;
3736 begin
3737 Result := False;
3739 if g_CollideLevel(X, Y, PLAYER_RECT.Width, PLAYER_RECT.Height) then
3740 begin
3741 g_Sound_PlayExAt('SOUND_GAME_NOTELEPORT', FObj.X, FObj.Y);
3742 if g_Game_IsServer and g_Game_IsNet then
3743 MH_SEND_Sound(FObj.X, FObj.Y, 'SOUND_GAME_NOTELEPORT');
3744 Exit;
3745 end;
3747 FJustTeleported := True;
3749 if not silent then
3750 begin
3751 g_Sound_PlayExAt('SOUND_GAME_TELEPORT', FObj.X, FObj.Y);
3752 {$IFDEF ENABLE_GFX}
3753 g_GFX_QueueEffect(
3754 R_GFX_TELEPORT_FAST,
3755 FObj.X + PLAYER_RECT.X + (PLAYER_RECT.Width div 2) - 32,
3756 FObj.Y + PLAYER_RECT.Y + (PLAYER_RECT.Height div 2) - 32
3757 );
3758 {$ENDIF}
3759 if g_Game_IsServer and g_Game_IsNet then
3760 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
3761 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 1,
3762 NET_GFX_TELE);
3763 end;
3765 FObj.X := X-PLAYER_RECT.X;
3766 FObj.Y := Y-PLAYER_RECT.Y;
3767 FObj.oldX := FObj.X; // don't interpolate after respawn
3768 FObj.oldY := FObj.Y;
3769 if FAlive and FGhost then
3770 begin
3771 FXTo := FObj.X;
3772 FYTo := FObj.Y;
3773 end;
3775 if not g_Game_IsNet then
3776 begin
3777 if dir = 1 then
3778 begin
3779 SetDirection(TDirection.D_LEFT);
3780 FAngle := 180;
3781 end
3782 else
3783 if dir = 2 then
3784 begin
3785 SetDirection(TDirection.D_RIGHT);
3786 FAngle := 0;
3787 end
3788 else
3789 if dir = 3 then
3790 begin // обратное
3791 if FDirection = TDirection.D_RIGHT then
3792 begin
3793 SetDirection(TDirection.D_LEFT);
3794 FAngle := 180;
3795 end
3796 else
3797 begin
3798 SetDirection(TDirection.D_RIGHT);
3799 FAngle := 0;
3800 end;
3801 end;
3802 end;
3804 if not silent then
3805 begin
3806 {$IFDEF ENABLE_GFX}
3807 g_GFX_QueueEffect(
3808 R_GFX_TELEPORT_FAST,
3809 FObj.X + PLAYER_RECT.X + (PLAYER_RECT.Width div 2) - 32,
3810 FObj.Y + PLAYER_RECT.Y + (PLAYER_RECT.Height div 2) - 32
3811 );
3812 {$ENDIF}
3813 if g_Game_IsServer and g_Game_IsNet then
3814 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
3815 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 0,
3816 NET_GFX_TELE);
3817 end;
3819 Result := True;
3820 end;
3822 function nonz(a: Single): Single;
3823 begin
3824 if a <> 0 then
3825 Result := a
3826 else
3827 Result := 1;
3828 end;
3830 procedure TPlayer.PreUpdate();
3831 begin
3832 FSlopeOld := FObj.slopeUpLeft;
3833 FIncCamOld := FIncCam;
3834 FObj.oldX := FObj.X;
3835 FObj.oldY := FObj.Y;
3836 end;
3838 procedure TPlayer.Update();
3839 var
3840 b: Byte;
3841 i, ii, wx, wy, xd, yd, k: Integer;
3842 blockmon, headwater, dospawn: Boolean;
3843 NetServer: Boolean;
3844 AnyServer: Boolean;
3845 SetSpect: Boolean;
3846 begin
3847 NetServer := g_Game_IsNet and g_Game_IsServer;
3848 AnyServer := g_Game_IsServer;
3850 if g_Game_IsClient and (NetInterpLevel > 0) then
3851 DoLerp(NetInterpLevel + 1)
3852 else
3853 if FGhost then
3854 DoLerp(4);
3856 if NetServer then
3857 if (FClientID >= 0) and (NetClients[FClientID].Peer <> nil) then
3858 begin
3859 FPing := NetClients[FClientID].Peer^.lastRoundTripTime;
3860 if NetClients[FClientID].Peer^.packetsSent > 0 then
3861 FLoss := Round(100*NetClients[FClientID].Peer^.packetsLost/NetClients[FClientID].Peer^.packetsSent)
3862 else
3863 FLoss := 0;
3864 end else
3865 begin
3866 FPing := 0;
3867 FLoss := 0;
3868 end;
3870 if FAlive and (gFly or FJetpack) then
3871 FlySmoke();
3873 if FDirection = TDirection.D_LEFT then
3874 FAngle := 180
3875 else
3876 FAngle := 0;
3878 if FAlive and (not FGhost) then
3879 begin
3880 if FKeys[KEY_UP].Pressed then
3881 SeeUp();
3882 if FKeys[KEY_DOWN].Pressed then
3883 SeeDown();
3884 end;
3886 if (not (FKeys[KEY_UP].Pressed or FKeys[KEY_DOWN].Pressed)) and
3887 (FIncCam <> 0) then
3888 begin
3889 i := g_basic.Sign(FIncCam);
3890 FIncCam := Abs(FIncCam);
3891 DecMin(FIncCam, 5, 0);
3892 FIncCam := FIncCam*i;
3893 end;
3895 if gTime mod (GAME_TICK*2) <> 0 then
3896 begin
3897 if (FObj.Vel.X = 0) and FAlive then
3898 begin
3899 if FKeys[KEY_LEFT].Pressed then
3900 Run(TDirection.D_LEFT);
3901 if FKeys[KEY_RIGHT].Pressed then
3902 Run(TDirection.D_RIGHT);
3903 end;
3905 if FPhysics then
3906 begin
3907 g_Obj_Move(@FObj, True, True, True);
3908 positionChanged(); // this updates spatial accelerators
3909 end;
3911 Exit;
3912 end;
3914 FActionChanged := False;
3916 if FAlive then
3917 begin
3918 // Let alive player do some actions
3919 if FKeys[KEY_LEFT].Pressed then Run(TDirection.D_LEFT);
3920 if FKeys[KEY_RIGHT].Pressed then Run(TDirection.D_RIGHT);
3921 if FKeys[KEY_FIRE].Pressed and AnyServer then Fire()
3922 else
3923 begin
3924 if AnyServer then
3925 begin
3926 FlamerOff;
3927 if NetServer then MH_SEND_PlayerStats(FUID);
3928 end;
3929 end;
3930 if FKeys[KEY_OPEN].Pressed and AnyServer then Use();
3931 if FKeys[KEY_JUMP].Pressed then Jump()
3932 else
3933 begin
3934 if AnyServer and FJetpack then
3935 begin
3936 FJetpack := False;
3937 JetpackOff;
3938 if NetServer then MH_SEND_PlayerStats(FUID);
3939 end;
3940 FCanJetpack := True;
3941 end;
3942 end
3943 else // Dead
3944 begin
3945 dospawn := False;
3946 if not FGhost then
3947 for k := Low(FKeys) to KEY_CHAT-1 do
3948 begin
3949 if FKeys[k].Pressed then
3950 begin
3951 dospawn := True;
3952 break;
3953 end;
3954 end;
3955 if dospawn then
3956 begin
3957 if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
3958 Respawn(False)
3959 else // Single
3960 if (FTime[T_RESPAWN] <= gTime) and
3961 gGameOn and (not FAlive) then
3962 begin
3963 if (g_Player_GetCount() > 1) then
3964 Respawn(False)
3965 else
3966 begin
3967 gExit := EXIT_RESTART;
3968 Exit;
3969 end;
3970 end;
3971 end;
3972 // Dead spectator actions
3973 if FGhost then
3974 begin
3975 if FKeys[KEY_OPEN].Pressed and AnyServer then Fire();
3976 if FKeys[KEY_FIRE].Pressed and AnyServer then
3977 begin
3978 if FSpectator then
3979 begin
3980 if (FSpectatePlayer >= High(gPlayers)) then
3981 FSpectatePlayer := -1
3982 else
3983 begin
3984 SetSpect := False;
3985 for I := FSpectatePlayer + 1 to High(gPlayers) do
3986 if gPlayers[I] <> nil then
3987 if gPlayers[I].alive then
3988 if gPlayers[I].UID <> FUID then
3989 begin
3990 FSpectatePlayer := I;
3991 SetSpect := True;
3992 break;
3993 end;
3995 if not SetSpect then FSpectatePlayer := -1;
3996 end;
3998 ReleaseKeys;
3999 end;
4000 end;
4001 end;
4002 end;
4003 // No clipping
4004 if FGhost then
4005 begin
4006 if FKeys[KEY_UP].Pressed or FKeys[KEY_JUMP].Pressed then
4007 begin
4008 FYTo := FObj.Y - 32;
4009 FSpectatePlayer := -1;
4010 end;
4011 if FKeys[KEY_DOWN].Pressed then
4012 begin
4013 FYTo := FObj.Y + 32;
4014 FSpectatePlayer := -1;
4015 end;
4016 if FKeys[KEY_LEFT].Pressed then
4017 begin
4018 FXTo := FObj.X - 32;
4019 FSpectatePlayer := -1;
4020 end;
4021 if FKeys[KEY_RIGHT].Pressed then
4022 begin
4023 FXTo := FObj.X + 32;
4024 FSpectatePlayer := -1;
4025 end;
4027 if (FXTo < -64) then
4028 FXTo := -64
4029 else if (FXTo > gMapInfo.Width + 32) then
4030 FXTo := gMapInfo.Width + 32;
4031 if (FYTo < -72) then
4032 FYTo := -72
4033 else if (FYTo > gMapInfo.Height + 32) then
4034 FYTo := gMapInfo.Height + 32;
4035 end;
4037 if FPhysics then
4038 begin
4039 g_Obj_Move(@FObj, True, True, True);
4040 positionChanged(); // this updates spatial accelerators
4041 end
4042 else
4043 begin
4044 FObj.Vel.X := 0;
4045 FObj.Vel.Y := 0;
4046 if FSpectator then
4047 if (FSpectatePlayer <= High(gPlayers)) and (FSpectatePlayer >= 0) then
4048 if gPlayers[FSpectatePlayer] <> nil then
4049 if gPlayers[FSpectatePlayer].alive then
4050 begin
4051 FXTo := gPlayers[FSpectatePlayer].GameX;
4052 FYTo := gPlayers[FSpectatePlayer].GameY;
4053 end;
4054 end;
4056 blockmon := g_Map_CollidePanel(FObj.X+PLAYER_HEADRECT.X, FObj.Y+PLAYER_HEADRECT.Y,
4057 PLAYER_HEADRECT.Width, PLAYER_HEADRECT.Height,
4058 PANEL_BLOCKMON, True);
4059 headwater := HeadInLiquid(0, 0);
4061 // Сопротивление воздуха:
4062 if (not FAlive) or not (FKeys[KEY_LEFT].Pressed or FKeys[KEY_RIGHT].Pressed) then
4063 if FObj.Vel.X <> 0 then
4064 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
4066 if (FLastHit = HIT_TRAP) and (FPain > 90) then FPain := 90;
4067 DecMin(FPain, 5, 0);
4068 DecMin(FPickup, 1, 0);
4070 if FAlive and (FObj.Y > Integer(gMapInfo.Height)+128) and AnyServer then
4071 begin
4072 // Обнулить действия примочек, чтобы фон пропал
4073 FMegaRulez[MR_SUIT] := 0;
4074 FMegaRulez[MR_INVUL] := 0;
4075 FMegaRulez[MR_INVIS] := 0;
4076 Kill(K_FALLKILL, 0, HIT_FALL);
4077 end;
4079 i := 9;
4081 if FAlive then
4082 begin
4083 if FCurrWeap = WEAPON_SAW then
4084 if not (FSawSound.IsPlaying() or FSawSoundHit.IsPlaying() or
4085 FSawSoundSelect.IsPlaying()) then
4086 FSawSoundIdle.PlayAt(FObj.X, FObj.Y);
4088 if FJetpack then
4089 if (not FJetSoundFly.IsPlaying()) and (not FJetSoundOn.IsPlaying()) and
4090 (not FJetSoundOff.IsPlaying()) then
4091 begin
4092 FJetSoundFly.SetPosition(0);
4093 FJetSoundFly.PlayAt(FObj.X, FObj.Y);
4094 end;
4096 for b := WP_FIRST to WP_LAST do
4097 if FReloading[b] > 0 then
4098 if FNoReload then
4099 FReloading[b] := 0
4100 else
4101 Dec(FReloading[b]);
4103 {$IFDEF ENABLE_SHELLS}
4104 if FShellTimer > -1 then
4105 if FShellTimer = 0 then
4106 begin
4107 if FShellType = SHELL_SHELL then
4108 g_Shells_Create(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
4109 GameVelX, GameVelY-2, SHELL_SHELL)
4110 else if FShellType = SHELL_DBLSHELL then
4111 begin
4112 g_Shells_Create(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
4113 GameVelX+1, GameVelY-2, SHELL_SHELL);
4114 g_Shells_Create(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
4115 GameVelX-1, GameVelY-2, SHELL_SHELL);
4116 end;
4117 FShellTimer := -1;
4118 end else Dec(FShellTimer);
4119 {$ENDIF}
4121 if (FBFGFireCounter > -1) then
4122 if FBFGFireCounter = 0 then
4123 begin
4124 if AnyServer then
4125 begin
4126 wx := FObj.X+WEAPONPOINT[FDirection].X;
4127 wy := FObj.Y+WEAPONPOINT[FDirection].Y;
4128 xd := wx+IfThen(FDirection = TDirection.D_LEFT, -30, 30);
4129 yd := wy+firediry();
4130 g_Weapon_bfgshot(wx, wy, xd, yd, FUID);
4131 if NetServer then MH_SEND_PlayerFire(FUID, WEAPON_BFG, wx, wy, xd, yd);
4132 if (FAngle = 0) or (FAngle = 180) then SetAction(A_ATTACK)
4133 else if (FAngle = ANGLE_LEFTDOWN) or (FAngle = ANGLE_RIGHTDOWN) then SetAction(A_ATTACKDOWN)
4134 else if (FAngle = ANGLE_LEFTUP) or (FAngle = ANGLE_RIGHTUP) then SetAction(A_ATTACKUP);
4135 end;
4137 FReloading[WEAPON_BFG] := WEAPON_RELOAD[WEAPON_BFG];
4138 FBFGFireCounter := -1;
4139 end else
4140 if FNoReload then
4141 FBFGFireCounter := 0
4142 else
4143 Dec(FBFGFireCounter);
4145 if (FMegaRulez[MR_SUIT] < gTime) and AnyServer then
4146 begin
4147 b := g_GetAcidHit(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width, PLAYER_RECT.Height);
4149 if (b > 0) and (gTime mod (15*GAME_TICK) = 0) then Damage(b, 0, 0, 0, HIT_ACID);
4150 end;
4152 if (headwater or blockmon) then
4153 begin
4154 Dec(FAir);
4156 if FAir < -9 then
4157 begin
4158 if AnyServer then Damage(10, 0, 0, 0, HIT_WATER);
4159 FAir := 0;
4160 end
4161 else if (FAir mod 31 = 0) and not blockmon then
4162 begin
4163 {$IFDEF ENABLE_GFX}
4164 g_GFX_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2), FObj.Y+PLAYER_RECT.Y-4, 5+Random(6), 8, 4);
4165 {$ENDIF}
4166 if Random(2) = 0
4167 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
4168 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
4169 end;
4170 end else if FAir < AIR_DEF then
4171 FAir := AIR_DEF;
4173 if FFireTime > 0 then
4174 begin
4175 if BodyInLiquid(0, 0) then
4176 begin
4177 FFireTime := 0;
4178 FFirePainTime := 0;
4179 end
4180 else if FMegaRulez[MR_SUIT] >= gTime then
4181 begin
4182 if FMegaRulez[MR_SUIT] = gTime then
4183 FFireTime := 1;
4184 FFirePainTime := 0;
4185 end
4186 else
4187 begin
4188 OnFireFlame(1);
4189 if FFirePainTime <= 0 then
4190 begin
4191 if g_Game_IsServer then
4192 Damage(2, FFireAttacker, 0, 0, HIT_FLAME);
4193 FFirePainTime := 12 - FFireTime div 12;
4194 end;
4195 FFirePainTime := FFirePainTime - 1;
4196 FFireTime := FFireTime - 1;
4197 if ((FFireTime mod 33) = 0) and (FMegaRulez[MR_INVUL] < gTime) then
4198 FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
4199 if (FFireTime = 0) and g_Game_IsNet and g_Game_IsServer then
4200 MH_SEND_PlayerStats(FUID);
4201 end;
4202 end;
4204 if FDamageBuffer > 0 then
4205 begin
4206 if FDamageBuffer >= 9 then
4207 begin
4208 SetAction(A_PAIN);
4210 if FDamageBuffer < 30 then i := 9
4211 else if FDamageBuffer < 100 then i := 18
4212 else i := 27;
4213 end;
4215 ii := Round(FDamageBuffer*FHealth / nonz(FArmor*(3/4)+FHealth));
4216 FArmor := FArmor-(FDamageBuffer-ii);
4217 FHealth := FHealth-ii;
4218 if FArmor < 0 then
4219 begin
4220 FHealth := FHealth+FArmor;
4221 FArmor := 0;
4222 end;
4224 if AnyServer then
4225 if FHealth <= 0 then
4226 if FHealth > -30 then Kill(K_SIMPLEKILL, FLastSpawnerUID, FLastHit)
4227 else if FHealth > -50 then Kill(K_HARDKILL, FLastSpawnerUID, FLastHit)
4228 else Kill(K_EXTRAHARDKILL, FLastSpawnerUID, FLastHit);
4230 if FAlive and ((FLastHit <> HIT_FLAME) or (FFireTime <= 0)) then
4231 begin
4232 if FDamageBuffer <= 20 then FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y)
4233 else if FDamageBuffer <= 55 then FModel.PlaySound(MODELSOUND_PAIN, 2, FObj.X, FObj.Y)
4234 else if FDamageBuffer <= 120 then FModel.PlaySound(MODELSOUND_PAIN, 3, FObj.X, FObj.Y)
4235 else FModel.PlaySound(MODELSOUND_PAIN, 4, FObj.X, FObj.Y);
4236 end;
4238 FDamageBuffer := 0;
4239 end;
4241 {CollideItem();}
4242 end; // if FAlive then ...
4244 if (FActionAnim = A_PAIN) and (FModel.Animation <> A_PAIN) then
4245 begin
4246 FModel.ChangeAnimation(FActionAnim, FActionForce);
4247 FModel.AnimState.MinLength := i;
4248 end else FModel.ChangeAnimation(FActionAnim, FActionForce and (FModel.Animation <> A_STAND));
4250 if (FModel.AnimState.Played or ((not FActionChanged) and (FModel.Animation = A_WALK)))
4251 then SetAction(A_STAND, True);
4253 if not ((FModel.Animation = A_WALK) and (Abs(FObj.Vel.X) < 4) and not FModel.GetFire()) then FModel.Update;
4255 for b := Low(FKeys) to High(FKeys) do
4256 if FKeys[b].Time = 0 then FKeys[b].Pressed := False else Dec(FKeys[b].Time);
4257 end;
4260 procedure TPlayer.getMapBox (out x, y, w, h: Integer); inline;
4261 begin
4262 x := FObj.X+PLAYER_RECT.X;
4263 y := FObj.Y+PLAYER_RECT.Y;
4264 w := PLAYER_RECT.Width;
4265 h := PLAYER_RECT.Height;
4266 end;
4269 procedure TPlayer.moveBy (dx, dy: Integer); inline;
4270 begin
4271 if (dx <> 0) or (dy <> 0) then
4272 begin
4273 FObj.X += dx;
4274 FObj.Y += dy;
4275 positionChanged();
4276 end;
4277 end;
4280 function TPlayer.Collide(X, Y: Integer; Width, Height: Word): Boolean;
4281 begin
4282 Result := g_Collide(FObj.X+PLAYER_RECT.X,
4283 FObj.Y+PLAYER_RECT.Y,
4284 PLAYER_RECT.Width,
4285 PLAYER_RECT.Height,
4286 X, Y,
4287 Width, Height);
4288 end;
4290 function TPlayer.Collide(Panel: TPanel): Boolean;
4291 begin
4292 Result := g_Collide(FObj.X+PLAYER_RECT.X,
4293 FObj.Y+PLAYER_RECT.Y,
4294 PLAYER_RECT.Width,
4295 PLAYER_RECT.Height,
4296 Panel.X, Panel.Y,
4297 Panel.Width, Panel.Height);
4298 end;
4300 function TPlayer.Collide(X, Y: Integer): Boolean;
4301 begin
4302 X := X-FObj.X-PLAYER_RECT.X;
4303 Y := Y-FObj.Y-PLAYER_RECT.Y;
4304 Result := (x >= 0) and (x <= PLAYER_RECT.Width) and
4305 (y >= 0) and (y <= PLAYER_RECT.Height);
4306 end;
4308 function g_Player_ValidName(Name: string): Boolean;
4309 var
4310 a: Integer;
4311 begin
4312 Result := True;
4314 if gPlayers = nil then Exit;
4316 for a := 0 to High(gPlayers) do
4317 if gPlayers[a] <> nil then
4318 if LowerCase(Name) = LowerCase(gPlayers[a].FName) then
4319 begin
4320 Result := False;
4321 Exit;
4322 end;
4323 end;
4325 procedure TPlayer.SetDirection(Direction: TDirection);
4326 var
4327 d: TDirection;
4328 begin
4329 d := FModel.Direction;
4331 FModel.Direction := Direction;
4332 if d <> Direction then FModel.ChangeAnimation(FModel.Animation, True);
4334 FDirection := Direction;
4335 end;
4337 function TPlayer.GetKeys(): Byte;
4338 begin
4339 Result := 0;
4341 if R_KEY_RED in FRulez then Result := KEY_RED;
4342 if R_KEY_GREEN in FRulez then Result := Result or KEY_GREEN;
4343 if R_KEY_BLUE in FRulez then Result := Result or KEY_BLUE;
4345 if FTeam = TEAM_RED then Result := Result or KEY_REDTEAM;
4346 if FTeam = TEAM_BLUE then Result := Result or KEY_BLUETEAM;
4347 end;
4349 procedure TPlayer.Use();
4350 var
4351 a: Integer;
4352 begin
4353 if FTime[T_USE] > gTime then Exit;
4355 g_Triggers_PressR(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width,
4356 PLAYER_RECT.Height, FUID, ACTIVATE_PLAYERPRESS);
4358 for a := 0 to High(gPlayers) do
4359 if (gPlayers[a] <> nil) and (gPlayers[a] <> Self) and
4360 gPlayers[a].alive and SameTeam(FUID, gPlayers[a].FUID) and
4361 g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
4362 FObj.Rect.Width, FObj.Rect.Height, @gPlayers[a].FObj) then
4363 begin
4364 gPlayers[a].Touch();
4365 if g_Game_IsNet and g_Game_IsServer then
4366 MH_SEND_GameEvent(NET_EV_PLAYER_TOUCH, gPlayers[a].FUID);
4367 end;
4369 FTime[T_USE] := gTime+120;
4370 end;
4372 procedure TPlayer.NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1);
4373 var
4374 locObj: TObj;
4375 visible: Boolean = True;
4376 WX, WY, XD, YD: Integer;
4377 begin
4378 WX := X;
4379 WY := Y;
4380 XD := AX;
4381 YD := AY;
4383 case FCurrWeap of
4384 WEAPON_KASTET:
4385 begin
4386 visible := False;
4387 DoPunch();
4388 if R_BERSERK in FRulez then
4389 begin
4390 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
4391 locobj.X := FObj.X+FObj.Rect.X;
4392 locobj.Y := FObj.Y+FObj.Rect.Y;
4393 locobj.rect.X := 0;
4394 locobj.rect.Y := 0;
4395 locobj.rect.Width := 39;
4396 locobj.rect.Height := 52;
4397 locobj.Vel.X := (xd-wx) div 2;
4398 locobj.Vel.Y := (yd-wy) div 2;
4399 locobj.Accel.X := xd-wx;
4400 locobj.Accel.y := yd-wy;
4402 if g_Weapon_Hit(@locobj, 50, FUID, HIT_SOME) <> 0 then
4403 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj.X, FObj.Y)
4404 else
4405 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj.X, FObj.Y);
4407 if gFlash = 1 then
4408 if FPain < 50 then
4409 FPain := min(FPain + 25, 50);
4410 end else
4411 g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 3, FUID);
4412 end;
4414 WEAPON_SAW:
4415 begin
4416 if g_Weapon_chainsaw(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
4417 IfThen(gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF], 9, 3), FUID) <> 0 then
4418 begin
4419 FSawSoundSelect.Stop();
4420 FSawSound.Stop();
4421 FSawSoundHit.PlayAt(FObj.X, FObj.Y);
4422 end
4423 else if not FSawSoundHit.IsPlaying() then
4424 begin
4425 FSawSoundSelect.Stop();
4426 FSawSound.PlayAt(FObj.X, FObj.Y);
4427 end;
4428 end;
4430 WEAPON_PISTOL:
4431 begin
4432 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', GameX, Gamey);
4433 FFireAngle := FAngle;
4434 {$IFDEF ENABLE_SHELLS}
4435 g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_BULLET);
4436 {$ENDIF}
4437 end;
4439 WEAPON_SHOTGUN1:
4440 begin
4441 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex, Gamey);
4442 FFireAngle := FAngle;
4443 {$IFDEF ENABLE_SHELLS}
4444 FShellTimer := 10;
4445 FShellType := SHELL_SHELL;
4446 {$ENDIF}
4447 end;
4449 WEAPON_SHOTGUN2:
4450 begin
4451 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', Gamex, Gamey);
4452 FFireAngle := FAngle;
4453 {$IFDEF ENABLE_SHELLS}
4454 FShellTimer := 13;
4455 FShellType := SHELL_DBLSHELL;
4456 {$ENDIF}
4457 end;
4459 WEAPON_CHAINGUN:
4460 begin
4461 g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', Gamex, Gamey);
4462 FFireAngle := FAngle;
4463 {$IFDEF ENABLE_SHELLS}
4464 g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_BULLET);
4465 {$ENDIF}
4466 end;
4468 WEAPON_ROCKETLAUNCHER:
4469 begin
4470 g_Weapon_Rocket(wx, wy, xd, yd, FUID, WID);
4471 FFireAngle := FAngle;
4472 end;
4474 WEAPON_PLASMA:
4475 begin
4476 g_Weapon_Plasma(wx, wy, xd, yd, FUID, WID);
4477 FFireAngle := FAngle;
4478 end;
4480 WEAPON_BFG:
4481 begin
4482 g_Weapon_BFGShot(wx, wy, xd, yd, FUID, WID);
4483 FFireAngle := FAngle;
4484 end;
4486 WEAPON_SUPERPULEMET:
4487 begin
4488 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex, Gamey);
4489 FFireAngle := FAngle;
4490 {$IFDEF ENABLE_SHELLS}
4491 g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_SHELL);
4492 {$ENDIF}
4493 end;
4495 WEAPON_FLAMETHROWER:
4496 begin
4497 g_Weapon_flame(wx, wy, xd, yd, FUID, WID);
4498 FlamerOn;
4499 FFireAngle := FAngle;
4500 end;
4501 end;
4503 if not visible then Exit;
4505 if (FAngle = 0) or (FAngle = 180) then SetAction(A_ATTACK)
4506 else if (FAngle = ANGLE_LEFTDOWN) or (FAngle = ANGLE_RIGHTDOWN) then SetAction(A_ATTACKDOWN)
4507 else if (FAngle = ANGLE_LEFTUP) or (FAngle = ANGLE_RIGHTUP) then SetAction(A_ATTACKUP);
4508 end;
4510 procedure TPlayer.DoLerp(Level: Integer = 2);
4511 begin
4512 if FObj.X <> FXTo then FObj.X := Lerp(FObj.X, FXTo, Level);
4513 if FObj.Y <> FYTo then FObj.Y := Lerp(FObj.Y, FYTo, Level);
4514 end;
4516 procedure TPlayer.SetLerp(XTo, YTo: Integer);
4517 var
4518 AX, AY: Integer;
4519 begin
4520 FXTo := XTo;
4521 FYTo := YTo;
4522 if FJustTeleported or (NetInterpLevel < 1) then
4523 begin
4524 FObj.X := XTo;
4525 FObj.Y := YTo;
4526 if FJustTeleported then
4527 begin
4528 FObj.oldX := FObj.X;
4529 FObj.oldY := FObj.Y;
4530 end;
4531 end
4532 else
4533 begin
4534 AX := Abs(FXTo - FObj.X);
4535 AY := Abs(FYTo - FObj.Y);
4536 if (AX > 32) or (AX <= NetInterpLevel) then
4537 FObj.X := FXTo;
4538 if (AY > 32) or (AY <= NetInterpLevel) then
4539 FObj.Y := FYTo;
4540 end;
4541 end;
4543 function TPlayer.FullInLift(XInc, YInc: Integer): Integer;
4544 begin
4545 if g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
4546 PLAYER_RECT.Width, PLAYER_RECT.Height-8,
4547 PANEL_LIFTUP, False) then Result := -1
4548 else
4549 if g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
4550 PLAYER_RECT.Width, PLAYER_RECT.Height-8,
4551 PANEL_LIFTDOWN, False) then Result := 1
4552 else Result := 0;
4553 end;
4555 function TPlayer.GetFlag(Flag: Byte): Boolean;
4556 var
4557 s, ts: String;
4558 evtype, a: Byte;
4559 begin
4560 Result := False;
4562 if Flag = FLAG_NONE then
4563 Exit;
4565 if not g_Game_IsServer then Exit;
4567 // Принес чужой флаг на свою базу:
4568 if (Flag = FTeam) and
4569 (gFlags[Flag].State = FLAG_STATE_NORMAL) and
4570 (FFlag <> FLAG_NONE) then
4571 begin
4572 if FFlag = FLAG_RED then
4573 s := _lc[I_PLAYER_FLAG_RED]
4574 else
4575 s := _lc[I_PLAYER_FLAG_BLUE];
4577 evtype := FLAG_STATE_SCORED;
4579 ts := Format('%.4d', [gFlags[FFlag].CaptureTime]);
4580 Insert('.', ts, Length(ts) + 1 - 3);
4581 g_Console_Add(Format(_lc[I_PLAYER_FLAG_CAPTURE], [FName, s, ts]), True);
4583 g_Map_ResetFlag(FFlag);
4584 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_CAPTURE], [AnsiUpperCase(s)]), 144);
4586 if ((Self = gPlayer1) or (Self = gPlayer2)
4587 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
4588 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
4589 a := 0
4590 else
4591 a := 1;
4593 if not sound_cap_flag[a].IsPlaying() then
4594 sound_cap_flag[a].Play();
4596 gTeamStat[FTeam].Score += 1;
4598 Result := True;
4599 if g_Game_IsNet then
4600 begin
4601 MH_SEND_FlagEvent(evtype, FFlag, FUID, False);
4602 MH_SEND_GameStats;
4603 end;
4605 gFlags[FFlag].CaptureTime := 0;
4606 SetFlag(FLAG_NONE);
4607 Exit;
4608 end;
4610 // Подобрал свой флаг - вернул его на базу:
4611 if (Flag = FTeam) and
4612 (gFlags[Flag].State = FLAG_STATE_DROPPED) then
4613 begin
4614 if Flag = FLAG_RED then
4615 s := _lc[I_PLAYER_FLAG_RED]
4616 else
4617 s := _lc[I_PLAYER_FLAG_BLUE];
4619 evtype := FLAG_STATE_RETURNED;
4620 gFlags[Flag].CaptureTime := 0;
4622 g_Console_Add(Format(_lc[I_PLAYER_FLAG_RETURN], [FName, s]), True);
4624 g_Map_ResetFlag(Flag);
4625 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
4627 if ((Self = gPlayer1) or (Self = gPlayer2)
4628 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
4629 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
4630 a := 0
4631 else
4632 a := 1;
4634 if not sound_ret_flag[a].IsPlaying() then
4635 sound_ret_flag[a].Play();
4637 Result := True;
4638 if g_Game_IsNet then
4639 begin
4640 MH_SEND_FlagEvent(evtype, Flag, FUID, False);
4641 MH_SEND_GameStats;
4642 end;
4643 Exit;
4644 end;
4646 // Подобрал чужой флаг:
4647 if (Flag <> FTeam) and (FTime[T_FLAGCAP] <= gTime) then
4648 begin
4649 SetFlag(Flag);
4651 if Flag = FLAG_RED then
4652 s := _lc[I_PLAYER_FLAG_RED]
4653 else
4654 s := _lc[I_PLAYER_FLAG_BLUE];
4656 evtype := FLAG_STATE_CAPTURED;
4658 g_Console_Add(Format(_lc[I_PLAYER_FLAG_GET], [FName, s]), True);
4660 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_GET], [AnsiUpperCase(s)]), 144);
4662 gFlags[Flag].State := FLAG_STATE_CAPTURED;
4664 if ((Self = gPlayer1) or (Self = gPlayer2)
4665 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
4666 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
4667 a := 0
4668 else
4669 a := 1;
4671 if not sound_get_flag[a].IsPlaying() then
4672 sound_get_flag[a].Play();
4674 Result := True;
4675 if g_Game_IsNet then
4676 begin
4677 MH_SEND_FlagEvent(evtype, Flag, FUID, False);
4678 MH_SEND_GameStats;
4679 end;
4680 end;
4681 end;
4683 procedure TPlayer.SetFlag(Flag: Byte);
4684 begin
4685 FFlag := Flag;
4686 if FModel <> nil then
4687 FModel.SetFlag(FFlag);
4688 end;
4690 function TPlayer.TryDropFlag(): Boolean;
4691 begin
4692 if LongBool(gGameSettings.Options and GAME_OPTION_ALLOWDROPFLAG) then
4693 Result := DropFlag(False, LongBool(gGameSettings.Options and GAME_OPTION_THROWFLAG))
4694 else
4695 Result := False;
4696 end;
4698 function TPlayer.DropFlag(Silent: Boolean = True; DoThrow: Boolean = False): Boolean;
4699 var
4700 s: String;
4701 a: Byte;
4702 xv, yv: Integer;
4703 begin
4704 Result := False;
4705 if (not g_Game_IsServer) or (FFlag = FLAG_NONE) then
4706 Exit;
4707 FTime[T_FLAGCAP] := gTime + 2000;
4708 with gFlags[FFlag] do
4709 begin
4710 Obj.X := FObj.X;
4711 Obj.Y := FObj.Y;
4712 Direction := FDirection;
4713 State := FLAG_STATE_DROPPED;
4714 Count := FLAG_TIME;
4715 if DoThrow then
4716 begin
4717 xv := FObj.Vel.X + IfThen(Direction = TDirection.D_RIGHT, 10, -10);
4718 yv := FObj.Vel.Y - 2;
4719 end
4720 else
4721 begin
4722 xv := (FObj.Vel.X div 2);
4723 yv := (FObj.Vel.Y div 2) - 2;
4724 end;
4725 g_Obj_Push(@Obj, xv, yv);
4727 positionChanged(); // this updates spatial accelerators
4729 if FFlag = FLAG_RED then
4730 s := _lc[I_PLAYER_FLAG_RED]
4731 else
4732 s := _lc[I_PLAYER_FLAG_BLUE];
4734 g_Console_Add(Format(_lc[I_PLAYER_FLAG_DROP], [FName, s]), True);
4735 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_DROP], [AnsiUpperCase(s)]), 144);
4737 if ((Self = gPlayer1) or (Self = gPlayer2)
4738 or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
4739 or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
4740 a := 0
4741 else
4742 a := 1;
4744 if (not Silent) and (not sound_lost_flag[a].IsPlaying()) then
4745 sound_lost_flag[a].Play();
4747 if g_Game_IsNet then
4748 MH_SEND_FlagEvent(FLAG_STATE_DROPPED, Flag, FUID, False);
4749 end;
4750 SetFlag(FLAG_NONE);
4751 Result := True;
4752 end;
4754 procedure TPlayer.GetSecret();
4755 begin
4756 if (self = gPlayer1) or (self = gPlayer2) then
4757 begin
4758 g_Console_Add(Format(_lc[I_PLAYER_SECRET], [FName]), True);
4759 g_Sound_PlayEx('SOUND_GAME_SECRET');
4760 end;
4761 Inc(FSecrets);
4762 end;
4764 procedure TPlayer.PressKey(Key: Byte; Time: Word = 1);
4765 begin
4766 Assert(Key <= High(FKeys));
4768 FKeys[Key].Pressed := True;
4769 FKeys[Key].Time := Time;
4770 end;
4772 function TPlayer.IsKeyPressed(K: Byte): Boolean;
4773 begin
4774 Result := FKeys[K].Pressed;
4775 end;
4777 procedure TPlayer.ReleaseKeys();
4778 var
4779 a: Integer;
4780 begin
4781 for a := Low(FKeys) to High(FKeys) do
4782 begin
4783 FKeys[a].Pressed := False;
4784 FKeys[a].Time := 0;
4785 end;
4786 end;
4788 procedure TPlayer.OnDamage(Angle: SmallInt);
4789 begin
4790 end;
4792 function TPlayer.firediry(): Integer;
4793 begin
4794 if FKeys[KEY_UP].Pressed then Result := -42
4795 else if FKeys[KEY_DOWN].Pressed then Result := 19
4796 else Result := 0;
4797 end;
4799 procedure TPlayer.RememberState();
4800 var
4801 i: Integer;
4802 SavedState: TPlayerSavedState;
4803 begin
4804 SavedState.Health := FHealth;
4805 SavedState.Armor := FArmor;
4806 SavedState.Air := FAir;
4807 SavedState.JetFuel := FJetFuel;
4808 SavedState.CurrWeap := FCurrWeap;
4809 SavedState.NextWeap := FNextWeap;
4810 SavedState.NextWeapDelay := FNextWeapDelay;
4811 for i := Low(FWeapon) to High(FWeapon) do
4812 SavedState.Weapon[i] := FWeapon[i];
4813 for i := Low(FAmmo) to High(FAmmo) do
4814 SavedState.Ammo[i] := FAmmo[i];
4815 for i := Low(FMaxAmmo) to High(FMaxAmmo) do
4816 SavedState.MaxAmmo[i] := FMaxAmmo[i];
4817 SavedState.Rulez := FRulez - [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE];
4819 FSavedStateNum := -1;
4820 for i := Low(SavedStates) to High(SavedStates) do
4821 if not SavedStates[i].Used then
4822 begin
4823 FSavedStateNum := i;
4824 break;
4825 end;
4826 if FSavedStateNum < 0 then
4827 begin
4828 SetLength(SavedStates, Length(SavedStates) + 1);
4829 FSavedStateNum := High(SavedStates);
4830 end;
4832 SavedState.Used := True;
4833 SavedStates[FSavedStateNum] := SavedState;
4834 end;
4836 procedure TPlayer.RecallState();
4837 var
4838 i: Integer;
4839 SavedState: TPlayerSavedState;
4840 begin
4841 if(FSavedStateNum < 0) or (FSavedStateNum > High(SavedStates)) then
4842 Exit;
4844 SavedState := SavedStates[FSavedStateNum];
4845 SavedStates[FSavedStateNum].Used := False;
4846 FSavedStateNum := -1;
4848 FHealth := SavedState.Health;
4849 FArmor := SavedState.Armor;
4850 FAir := SavedState.Air;
4851 FJetFuel := SavedState.JetFuel;
4852 FCurrWeap := SavedState.CurrWeap;
4853 FNextWeap := SavedState.NextWeap;
4854 FNextWeapDelay := SavedState.NextWeapDelay;
4855 for i := Low(FWeapon) to High(FWeapon) do
4856 FWeapon[i] := SavedState.Weapon[i];
4857 for i := Low(FAmmo) to High(FAmmo) do
4858 FAmmo[i] := SavedState.Ammo[i];
4859 for i := Low(FMaxAmmo) to High(FMaxAmmo) do
4860 FMaxAmmo[i] := SavedState.MaxAmmo[i];
4861 FRulez := SavedState.Rulez;
4863 if gGameSettings.GameType = GT_SERVER then
4864 MH_SEND_PlayerStats(FUID);
4865 end;
4867 procedure TPlayer.SaveState (st: TStream);
4868 var
4869 i: Integer;
4870 b: Byte;
4871 begin
4872 // Сигнатура игрока
4873 utils.writeSign(st, 'PLYR');
4874 utils.writeInt(st, Byte(PLR_SAVE_VERSION)); // version
4875 // Бот или человек
4876 utils.writeBool(st, FIamBot);
4877 // UID игрока
4878 utils.writeInt(st, Word(FUID));
4879 // Имя игрока
4880 utils.writeStr(st, FName);
4881 // Команда
4882 utils.writeInt(st, Byte(FTeam));
4883 // Жив ли
4884 utils.writeBool(st, FAlive);
4885 // Израсходовал ли все жизни
4886 utils.writeBool(st, FNoRespawn);
4887 // Направление
4888 if FDirection = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT
4889 utils.writeInt(st, Byte(b));
4890 // Здоровье
4891 utils.writeInt(st, LongInt(FHealth));
4892 // Коэффициент инвалидности
4893 utils.writeInt(st, LongInt(FHandicap));
4894 // Жизни
4895 utils.writeInt(st, Byte(FLives));
4896 // Броня
4897 utils.writeInt(st, LongInt(FArmor));
4898 // Запас воздуха
4899 utils.writeInt(st, LongInt(FAir));
4900 // Запас горючего
4901 utils.writeInt(st, LongInt(FJetFuel));
4902 // Боль
4903 utils.writeInt(st, LongInt(FPain));
4904 // Убил
4905 utils.writeInt(st, LongInt(FKills));
4906 // Убил монстров
4907 utils.writeInt(st, LongInt(FMonsterKills));
4908 // Фрагов
4909 utils.writeInt(st, LongInt(FFrags));
4910 // Фрагов подряд
4911 utils.writeInt(st, Byte(FFragCombo));
4912 // Время последнего фрага
4913 utils.writeInt(st, LongWord(FLastFrag));
4914 // Смертей
4915 utils.writeInt(st, LongInt(FDeath));
4916 // Какой флаг несет
4917 utils.writeInt(st, Byte(FFlag));
4918 // Нашел секретов
4919 utils.writeInt(st, LongInt(FSecrets));
4920 // Текущее оружие
4921 utils.writeInt(st, Byte(FCurrWeap));
4922 // Желаемое оружие
4923 utils.writeInt(st, Word(FNextWeap));
4924 // ...и пауза
4925 utils.writeInt(st, Byte(FNextWeapDelay));
4926 // Время зарядки BFG
4927 utils.writeInt(st, SmallInt(FBFGFireCounter));
4928 // Буфер урона
4929 utils.writeInt(st, LongInt(FDamageBuffer));
4930 // Последний ударивший
4931 utils.writeInt(st, Word(FLastSpawnerUID));
4932 // Тип последнего полученного урона
4933 utils.writeInt(st, Byte(FLastHit));
4934 // Объект игрока
4935 Obj_SaveState(st, @FObj);
4936 // Текущее количество патронов
4937 for i := A_BULLETS to A_HIGH do utils.writeInt(st, Word(FAmmo[i]));
4938 // Максимальное количество патронов
4939 for i := A_BULLETS to A_HIGH do utils.writeInt(st, Word(FMaxAmmo[i]));
4940 // Наличие оружия
4941 for i := WP_FIRST to WP_LAST do utils.writeBool(st, FWeapon[i]);
4942 // Время перезарядки оружия
4943 for i := WP_FIRST to WP_LAST do utils.writeInt(st, Word(FReloading[i]));
4944 // Наличие рюкзака
4945 utils.writeBool(st, (R_ITEM_BACKPACK in FRulez));
4946 // Наличие красного ключа
4947 utils.writeBool(st, (R_KEY_RED in FRulez));
4948 // Наличие зеленого ключа
4949 utils.writeBool(st, (R_KEY_GREEN in FRulez));
4950 // Наличие синего ключа
4951 utils.writeBool(st, (R_KEY_BLUE in FRulez));
4952 // Наличие берсерка
4953 utils.writeBool(st, (R_BERSERK in FRulez));
4954 // Время действия специальных предметов
4955 for i := MR_SUIT to MR_MAX do utils.writeInt(st, LongWord(FMegaRulez[i]));
4956 // Время до повторного респауна, смены оружия, исользования, захвата флага
4957 for i := T_RESPAWN to T_FLAGCAP do utils.writeInt(st, LongWord(FTime[i]));
4958 // Название модели
4959 utils.writeStr(st, FModel.GetName());
4960 // Цвет модели
4961 utils.writeInt(st, Byte(FColor.R));
4962 utils.writeInt(st, Byte(FColor.G));
4963 utils.writeInt(st, Byte(FColor.B));
4964 end;
4967 procedure TPlayer.LoadState (st: TStream);
4968 var
4969 i: Integer;
4970 str: String;
4971 b: Byte;
4972 begin
4973 assert(st <> nil);
4975 // Сигнатура игрока
4976 if not utils.checkSign(st, 'PLYR') then raise XStreamError.Create('invalid player signature');
4977 if (utils.readByte(st) <> PLR_SAVE_VERSION) then raise XStreamError.Create('invalid player version');
4978 // Бот или человек:
4979 FIamBot := utils.readBool(st);
4980 // UID игрока
4981 FUID := utils.readWord(st);
4982 // Имя игрока
4983 str := utils.readStr(st);
4984 if (self <> gPlayer1) and (self <> gPlayer2) then FName := str;
4985 // Команда
4986 FTeam := utils.readByte(st);
4987 // Жив ли
4988 FAlive := utils.readBool(st);
4989 // Израсходовал ли все жизни
4990 FNoRespawn := utils.readBool(st);
4991 // Направление
4992 b := utils.readByte(st);
4993 if b = 1 then FDirection := TDirection.D_LEFT else FDirection := TDirection.D_RIGHT; // b = 2
4994 // Здоровье
4995 FHealth := utils.readLongInt(st);
4996 // Коэффициент инвалидности
4997 FHandicap := utils.readLongInt(st);
4998 // Жизни
4999 FLives := utils.readByte(st);
5000 // Броня
5001 FArmor := utils.readLongInt(st);
5002 // Запас воздуха
5003 FAir := utils.readLongInt(st);
5004 // Запас горючего
5005 FJetFuel := utils.readLongInt(st);
5006 // Боль
5007 FPain := utils.readLongInt(st);
5008 // Убил
5009 FKills := utils.readLongInt(st);
5010 // Убил монстров
5011 FMonsterKills := utils.readLongInt(st);
5012 // Фрагов
5013 FFrags := utils.readLongInt(st);
5014 // Фрагов подряд
5015 FFragCombo := utils.readByte(st);
5016 // Время последнего фрага
5017 FLastFrag := utils.readLongWord(st);
5018 // Смертей
5019 FDeath := utils.readLongInt(st);
5020 // Какой флаг несет
5021 FFlag := utils.readByte(st);
5022 // Нашел секретов
5023 FSecrets := utils.readLongInt(st);
5024 // Текущее оружие
5025 FCurrWeap := utils.readByte(st);
5026 // Желаемое оружие
5027 FNextWeap := utils.readWord(st);
5028 // ...и пауза
5029 FNextWeapDelay := utils.readByte(st);
5030 // Время зарядки BFG
5031 FBFGFireCounter := utils.readSmallInt(st);
5032 // Буфер урона
5033 FDamageBuffer := utils.readLongInt(st);
5034 // Последний ударивший
5035 FLastSpawnerUID := utils.readWord(st);
5036 // Тип последнего полученного урона
5037 FLastHit := utils.readByte(st);
5038 // Объект игрока
5039 Obj_LoadState(@FObj, st);
5040 // Текущее количество патронов
5041 for i := A_BULLETS to A_HIGH do FAmmo[i] := utils.readWord(st);
5042 // Максимальное количество патронов
5043 for i := A_BULLETS to A_HIGH do FMaxAmmo[i] := utils.readWord(st);
5044 // Наличие оружия
5045 for i := WP_FIRST to WP_LAST do FWeapon[i] := utils.readBool(st);
5046 // Время перезарядки оружия
5047 for i := WP_FIRST to WP_LAST do FReloading[i] := utils.readWord(st);
5048 // Наличие рюкзака
5049 if utils.readBool(st) then Include(FRulez, R_ITEM_BACKPACK);
5050 // Наличие красного ключа
5051 if utils.readBool(st) then Include(FRulez, R_KEY_RED);
5052 // Наличие зеленого ключа
5053 if utils.readBool(st) then Include(FRulez, R_KEY_GREEN);
5054 // Наличие синего ключа
5055 if utils.readBool(st) then Include(FRulez, R_KEY_BLUE);
5056 // Наличие берсерка
5057 if utils.readBool(st) then Include(FRulez, R_BERSERK);
5058 // Время действия специальных предметов
5059 for i := MR_SUIT to MR_MAX do FMegaRulez[i] := utils.readLongWord(st);
5060 // Время до повторного респауна, смены оружия, исользования, захвата флага
5061 for i := T_RESPAWN to T_FLAGCAP do FTime[i] := utils.readLongWord(st);
5062 // Название модели
5063 str := utils.readStr(st);
5064 // Цвет модели
5065 FColor.R := utils.readByte(st);
5066 FColor.G := utils.readByte(st);
5067 FColor.B := utils.readByte(st);
5068 if (self = gPlayer1) then
5069 begin
5070 str := gPlayer1Settings.Model;
5071 FColor := gPlayer1Settings.Color;
5072 end
5073 else if (self = gPlayer2) then
5074 begin
5075 str := gPlayer2Settings.Model;
5076 FColor := gPlayer2Settings.Color;
5077 end;
5078 // Обновляем модель игрока
5079 SetModel(str);
5080 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
5081 FModel.Color := TEAMCOLOR[FTeam]
5082 else
5083 FModel.Color := FColor;
5084 end;
5087 procedure TPlayer.AllRulez(Health: Boolean);
5088 var
5089 a: Integer;
5090 begin
5091 if Health then
5092 begin
5093 FHealth := PLAYER_HP_LIMIT;
5094 FArmor := PLAYER_AP_LIMIT;
5095 Exit;
5096 end;
5098 for a := WP_FIRST to WP_LAST do FWeapon[a] := True;
5099 for a := A_BULLETS to A_HIGH do FAmmo[a] := 30000;
5100 FRulez := FRulez+[R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE];
5101 end;
5103 procedure TPlayer.RestoreHealthArmor();
5104 begin
5105 FHealth := PLAYER_HP_LIMIT;
5106 FArmor := PLAYER_AP_LIMIT;
5107 end;
5109 procedure TPlayer.FragCombo();
5110 var
5111 Param: Integer;
5112 begin
5113 if (gGameSettings.GameMode in [GM_COOP, GM_SINGLE]) or g_Game_IsClient then
5114 Exit;
5115 if gTime - FLastFrag < FRAG_COMBO_TIME then
5116 begin
5117 if FFragCombo < 5 then
5118 Inc(FFragCombo);
5119 Param := FUID or (FFragCombo shl 16);
5120 if (FComboEvnt >= Low(gDelayedEvents)) and
5121 (FComboEvnt <= High(gDelayedEvents)) and
5122 gDelayedEvents[FComboEvnt].Pending and
5123 (gDelayedEvents[FComboEvnt].DEType = DE_KILLCOMBO) and
5124 (gDelayedEvents[FComboEvnt].DENum and $FFFF = FUID) then
5125 begin
5126 gDelayedEvents[FComboEvnt].Time := gTime + 500;
5127 gDelayedEvents[FComboEvnt].DENum := Param;
5128 end
5129 else
5130 FComboEvnt := g_Game_DelayEvent(DE_KILLCOMBO, 500, Param);
5131 end
5132 else
5133 FFragCombo := 1;
5135 FLastFrag := gTime;
5136 end;
5138 procedure TPlayer.GiveItem(ItemType: Byte);
5139 begin
5140 case ItemType of
5141 ITEM_SUIT:
5142 if FMegaRulez[MR_SUIT] < gTime+PLAYER_SUIT_TIME then
5143 begin
5144 FMegaRulez[MR_SUIT] := gTime+PLAYER_SUIT_TIME;
5145 end;
5147 ITEM_OXYGEN:
5148 if FAir < AIR_MAX then
5149 begin
5150 FAir := AIR_MAX;
5151 end;
5153 ITEM_MEDKIT_BLACK:
5154 begin
5155 if not (R_BERSERK in FRulez) then
5156 begin
5157 Include(FRulez, R_BERSERK);
5158 if FBFGFireCounter < 1 then
5159 begin
5160 FCurrWeap := WEAPON_KASTET;
5161 resetWeaponQueue();
5162 FModel.SetWeapon(WEAPON_KASTET);
5163 end;
5164 if gFlash <> 0 then
5165 Inc(FPain, 100);
5166 FBerserk := gTime+30000;
5167 end;
5168 if FHealth < PLAYER_HP_SOFT then
5169 begin
5170 FHealth := PLAYER_HP_SOFT;
5171 FBerserk := gTime+30000;
5172 end;
5173 end;
5175 ITEM_INVUL:
5176 if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
5177 begin
5178 FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
5179 FSpawnInvul := 0;
5180 end;
5182 ITEM_INVIS:
5183 if FMegaRulez[MR_INVIS] < gTime+PLAYER_INVIS_TIME then
5184 begin
5185 FMegaRulez[MR_INVIS] := gTime+PLAYER_INVIS_TIME;
5186 end;
5188 ITEM_JETPACK:
5189 if FJetFuel < JET_MAX then
5190 begin
5191 FJetFuel := JET_MAX;
5192 end;
5194 ITEM_MEDKIT_SMALL: if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 10, PLAYER_HP_SOFT);
5195 ITEM_MEDKIT_LARGE: if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 25, PLAYER_HP_SOFT);
5197 ITEM_ARMOR_GREEN: if FArmor < PLAYER_AP_SOFT then FArmor := PLAYER_AP_SOFT;
5198 ITEM_ARMOR_BLUE: if FArmor < PLAYER_AP_LIMIT then FArmor := PLAYER_AP_LIMIT;
5200 ITEM_SPHERE_BLUE: if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 100, PLAYER_HP_LIMIT);
5201 ITEM_SPHERE_WHITE:
5202 if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) then
5203 begin
5204 if FHealth < PLAYER_HP_LIMIT then FHealth := PLAYER_HP_LIMIT;
5205 if FArmor < PLAYER_AP_LIMIT then FArmor := PLAYER_AP_LIMIT;
5206 end;
5208 ITEM_WEAPON_SAW: FWeapon[WEAPON_SAW] := True;
5209 ITEM_WEAPON_SHOTGUN1: FWeapon[WEAPON_SHOTGUN1] := True;
5210 ITEM_WEAPON_SHOTGUN2: FWeapon[WEAPON_SHOTGUN2] := True;
5211 ITEM_WEAPON_CHAINGUN: FWeapon[WEAPON_CHAINGUN] := True;
5212 ITEM_WEAPON_ROCKETLAUNCHER: FWeapon[WEAPON_ROCKETLAUNCHER] := True;
5213 ITEM_WEAPON_PLASMA: FWeapon[WEAPON_PLASMA] := True;
5214 ITEM_WEAPON_BFG: FWeapon[WEAPON_BFG] := True;
5215 ITEM_WEAPON_SUPERPULEMET: FWeapon[WEAPON_SUPERPULEMET] := True;
5216 ITEM_WEAPON_FLAMETHROWER: FWeapon[WEAPON_FLAMETHROWER] := True;
5218 ITEM_AMMO_BULLETS: if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
5219 ITEM_AMMO_BULLETS_BOX: if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
5220 ITEM_AMMO_SHELLS: if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
5221 ITEM_AMMO_SHELLS_BOX: if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 25, FMaxAmmo[A_SHELLS]);
5222 ITEM_AMMO_ROCKET: if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
5223 ITEM_AMMO_ROCKET_BOX: if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 5, FMaxAmmo[A_ROCKETS]);
5224 ITEM_AMMO_CELL: if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
5225 ITEM_AMMO_CELL_BIG: if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 100, FMaxAmmo[A_CELLS]);
5226 ITEM_AMMO_FUELCAN: if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
5228 ITEM_AMMO_BACKPACK:
5229 if (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or
5230 (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or
5231 (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or
5232 (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or
5233 (FMaxAmmo[A_FUEL] < AmmoLimits[1, A_FUEL]) then
5234 begin
5235 FMaxAmmo[A_BULLETS] := AmmoLimits[1, A_BULLETS];
5236 FMaxAmmo[A_SHELLS] := AmmoLimits[1, A_SHELLS];
5237 FMaxAmmo[A_ROCKETS] := AmmoLimits[1, A_ROCKETS];
5238 FMaxAmmo[A_CELLS] := AmmoLimits[1, A_CELLS];
5239 FMaxAmmo[A_FUEL] := AmmoLimits[1, A_FUEL];
5241 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
5242 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
5243 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
5244 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
5246 FRulez := FRulez + [R_ITEM_BACKPACK];
5247 end;
5249 ITEM_KEY_RED: if not (R_KEY_RED in FRulez) then Include(FRulez, R_KEY_RED);
5250 ITEM_KEY_GREEN: if not (R_KEY_GREEN in FRulez) then Include(FRulez, R_KEY_GREEN);
5251 ITEM_KEY_BLUE: if not (R_KEY_BLUE in FRulez) then Include(FRulez, R_KEY_BLUE);
5253 ITEM_BOTTLE: if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 4, PLAYER_HP_LIMIT);
5254 ITEM_HELMET: if FArmor < PLAYER_AP_LIMIT then IncMax(FArmor, 5, PLAYER_AP_LIMIT);
5256 else
5257 Exit;
5258 end;
5259 if g_Game_IsNet and g_Game_IsServer then
5260 MH_SEND_PlayerStats(FUID);
5261 end;
5263 procedure TPlayer.FlySmoke(Times: DWORD = 1);
5264 var i: DWORD;
5265 begin
5266 if (Random(5) = 1) and (Times = 1) then
5267 Exit;
5269 if BodyInLiquid(0, 0) then
5270 begin
5271 {$IFDEF ENABLE_GFX}
5272 g_GFX_Bubbles(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)+Random(3)-1,
5273 Obj.Y+Obj.Rect.Height+8, 1, 8, 4);
5274 {$ENDIF}
5275 if Random(2) = 0
5276 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
5277 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
5278 Exit;
5279 end;
5281 for i := 1 to Times do
5282 begin
5283 {$IFDEF ENABLE_GFX}
5284 g_GFX_QueueEffect(
5285 R_GFX_SMOKE_TRANS,
5286 Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(R_GFX_SMOKE_WIDTH div 2),
5287 Obj.Y+Obj.Rect.Height-4+Random(8+Times*2)
5288 );
5289 {$ENDIF}
5290 end;
5291 end;
5293 procedure TPlayer.OnFireFlame(Times: DWORD = 1);
5294 var i: DWORD;
5295 begin
5296 if (Random(10) = 1) and (Times = 1) then
5297 Exit;
5299 for i := 1 to Times do
5300 begin
5301 {$IFDEF ENABLE_GFX}
5302 g_GFX_QueueEffect(
5303 R_GFX_FLAME,
5304 Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(R_GFX_FLAME_WIDTH div 2),
5305 Obj.Y+8+Random(8+Times*2)
5306 );
5307 {$ENDIF}
5308 end;
5309 end;
5311 procedure TPlayer.PauseSounds(Enable: Boolean);
5312 begin
5313 FSawSound.Pause(Enable);
5314 FSawSoundIdle.Pause(Enable);
5315 FSawSoundHit.Pause(Enable);
5316 FSawSoundSelect.Pause(Enable);
5317 FFlameSoundOn.Pause(Enable);
5318 FFlameSoundOff.Pause(Enable);
5319 FFlameSoundWork.Pause(Enable);
5320 FJetSoundFly.Pause(Enable);
5321 FJetSoundOn.Pause(Enable);
5322 FJetSoundOff.Pause(Enable);
5323 end;
5325 { T B o t : }
5327 constructor TBot.Create();
5328 var
5329 a: Integer;
5330 begin
5331 inherited Create();
5333 FPhysics := True;
5334 FSpectator := False;
5335 FGhost := False;
5337 FIamBot := True;
5339 Inc(gNumBots);
5341 for a := WP_FIRST to WP_LAST do
5342 begin
5343 FDifficult.WeaponPrior[a] := WEAPON_PRIOR1[a];
5344 FDifficult.CloseWeaponPrior[a] := WEAPON_PRIOR2[a];
5345 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
5346 end;
5347 end;
5349 destructor TBot.Destroy();
5350 begin
5351 Dec(gNumBots);
5352 inherited Destroy();
5353 end;
5355 procedure TBot.Respawn(Silent: Boolean; Force: Boolean = False);
5356 begin
5357 inherited Respawn(Silent, Force);
5359 FAIFlags := nil;
5360 FSelectedWeapon := FCurrWeap;
5361 resetWeaponQueue();
5362 FTargetUID := 0;
5363 end;
5365 procedure TBot.UpdateCombat();
5366 type
5367 TTarget = record
5368 UID: Word;
5369 X, Y: Integer;
5370 Rect: TRectWH;
5371 cX, cY: Integer;
5372 Dist: Word;
5373 Line: Boolean;
5374 Visible: Boolean;
5375 IsPlayer: Boolean;
5376 end;
5378 TTargetRecord = array of TTarget;
5380 function Compare(a, b: TTarget): Integer;
5381 begin
5382 if a.Line and not b.Line then // A на линии огня
5383 Result := -1
5384 else
5385 if not a.Line and b.Line then // B на линии огня
5386 Result := 1
5387 else // И A, и B на линии или не на линии огня
5388 if (a.Line and b.Line) or ((not a.Line) and (not b.Line)) then
5389 begin
5390 if a.Dist > b.Dist then // B ближе
5391 Result := 1
5392 else // A ближе или равноудаленно с B
5393 Result := -1;
5394 end
5395 else // Странно -> A
5396 Result := -1;
5397 end;
5399 var
5400 a, x1, y1, x2, y2: Integer;
5401 targets: TTargetRecord;
5402 ammo: Word;
5403 Target, BestTarget: TTarget;
5404 firew, fireh: Integer;
5405 angle: SmallInt;
5406 mon: TMonster;
5407 pla, tpla: TPlayer;
5408 vsPlayer, vsMonster, ok: Boolean;
5411 function monsUpdate (mon: TMonster): Boolean;
5412 begin
5413 result := false; // don't stop
5414 if mon.alive and (mon.MonsterType <> MONSTER_BARREL) then
5415 begin
5416 if not TargetOnScreen(mon.Obj.X+mon.Obj.Rect.X, mon.Obj.Y+mon.Obj.Rect.Y) then exit;
5418 x2 := mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2);
5419 y2 := mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2);
5421 // Если монстр на экране и не прикрыт стеной
5422 if g_TraceVector(x1, y1, x2, y2) then
5423 begin
5424 // Добавляем к списку возможных целей
5425 SetLength(targets, Length(targets)+1);
5426 with targets[High(targets)] do
5427 begin
5428 UID := mon.UID;
5429 X := mon.Obj.X;
5430 Y := mon.Obj.Y;
5431 cX := x2;
5432 cY := y2;
5433 Rect := mon.Obj.Rect;
5434 Dist := g_PatchLength(x1, y1, x2, y2);
5435 Line := (y1+4 < Target.Y + mon.Obj.Rect.Y + mon.Obj.Rect.Height) and
5436 (y1-4 > Target.Y + mon.Obj.Rect.Y);
5437 Visible := True;
5438 IsPlayer := False;
5439 end;
5440 end;
5441 end;
5442 end;
5444 begin
5445 vsPlayer := LongBool(gGameSettings.Options and GAME_OPTION_BOTVSPLAYER);
5446 vsMonster := LongBool(gGameSettings.Options and GAME_OPTION_BOTVSMONSTER);
5448 // Если текущее оружие не то, что нужно, то меняем:
5449 if FCurrWeap <> FSelectedWeapon then
5450 NextWeapon();
5452 // Если нужно стрелять и нужное оружие, то нажать "Стрелять":
5453 if (GetAIFlag('NEEDFIRE') <> '') and (FCurrWeap = FSelectedWeapon) then
5454 begin
5455 RemoveAIFlag('NEEDFIRE');
5457 case FCurrWeap of
5458 WEAPON_PLASMA, WEAPON_SUPERPULEMET, WEAPON_CHAINGUN: PressKey(KEY_FIRE, 20);
5459 WEAPON_SAW, WEAPON_KASTET, WEAPON_FLAMETHROWER: PressKey(KEY_FIRE, 40);
5460 else PressKey(KEY_FIRE);
5461 end;
5462 end;
5464 // Координаты ствола:
5465 x1 := FObj.X + WEAPONPOINT[FDirection].X;
5466 y1 := FObj.Y + WEAPONPOINT[FDirection].Y;
5468 Target.UID := FTargetUID;
5470 ok := False;
5471 if Target.UID <> 0 then
5472 begin // Цель есть - настраиваем
5473 if (g_GetUIDType(Target.UID) = UID_PLAYER) and
5474 vsPlayer then
5475 begin // Игрок
5476 tpla := g_Player_Get(Target.UID);
5477 if tpla <> nil then
5478 with tpla do
5479 begin
5480 if (@FObj) <> nil then
5481 begin
5482 Target.X := FObj.X;
5483 Target.Y := FObj.Y;
5484 end;
5485 end;
5487 Target.cX := Target.X + PLAYER_RECT_CX;
5488 Target.cY := Target.Y + PLAYER_RECT_CY;
5489 Target.Rect := PLAYER_RECT;
5490 Target.Visible := g_TraceVector(x1, y1, Target.cX, Target.cY);
5491 Target.Line := (y1+4 < Target.Y+PLAYER_RECT.Y+PLAYER_RECT.Height) and
5492 (y1-4 > Target.Y+PLAYER_RECT.Y);
5493 Target.IsPlayer := True;
5494 ok := True;
5495 end
5496 else
5497 if (g_GetUIDType(Target.UID) = UID_MONSTER) and
5498 vsMonster then
5499 begin // Монстр
5500 mon := g_Monsters_ByUID(Target.UID);
5501 if mon <> nil then
5502 begin
5503 Target.X := mon.Obj.X;
5504 Target.Y := mon.Obj.Y;
5506 Target.cX := Target.X + mon.Obj.Rect.X + (mon.Obj.Rect.Width div 2);
5507 Target.cY := Target.Y + mon.Obj.Rect.Y + (mon.Obj.Rect.Height div 2);
5508 Target.Rect := mon.Obj.Rect;
5509 Target.Visible := g_TraceVector(x1, y1, Target.cX, Target.cY);
5510 Target.Line := (y1+4 < Target.Y + mon.Obj.Rect.Y + mon.Obj.Rect.Height) and
5511 (y1-4 > Target.Y + mon.Obj.Rect.Y);
5512 Target.IsPlayer := False;
5513 ok := True;
5514 end;
5515 end;
5516 end;
5518 if not ok then
5519 begin // Цели нет - обнуляем
5520 Target.X := 0;
5521 Target.Y := 0;
5522 Target.cX := 0;
5523 Target.cY := 0;
5524 Target.Visible := False;
5525 Target.Line := False;
5526 Target.IsPlayer := False;
5527 end;
5529 targets := nil;
5531 // Если цель не видима или не на линии огня, то ищем все возможные цели:
5532 if (not Target.Line) or (not Target.Visible) then
5533 begin
5534 // Игроки:
5535 if vsPlayer then
5536 for a := 0 to High(gPlayers) do
5537 if (gPlayers[a] <> nil) and (gPlayers[a].alive) and
5538 (gPlayers[a].FUID <> FUID) and
5539 (not SameTeam(FUID, gPlayers[a].FUID)) and
5540 (not gPlayers[a].NoTarget) and
5541 (gPlayers[a].FMegaRulez[MR_INVIS] < gTime) then
5542 begin
5543 if not TargetOnScreen(gPlayers[a].FObj.X + PLAYER_RECT.X,
5544 gPlayers[a].FObj.Y + PLAYER_RECT.Y) then
5545 Continue;
5547 x2 := gPlayers[a].FObj.X + PLAYER_RECT_CX;
5548 y2 := gPlayers[a].FObj.Y + PLAYER_RECT_CY;
5550 // Если игрок на экране и не прикрыт стеной:
5551 if g_TraceVector(x1, y1, x2, y2) then
5552 begin
5553 // Добавляем к списку возможных целей:
5554 SetLength(targets, Length(targets)+1);
5555 with targets[High(targets)] do
5556 begin
5557 UID := gPlayers[a].FUID;
5558 X := gPlayers[a].FObj.X;
5559 Y := gPlayers[a].FObj.Y;
5560 cX := x2;
5561 cY := y2;
5562 Rect := PLAYER_RECT;
5563 Dist := g_PatchLength(x1, y1, x2, y2);
5564 Line := (y1+4 < Target.Y+PLAYER_RECT.Y+PLAYER_RECT.Height) and
5565 (y1-4 > Target.Y+PLAYER_RECT.Y);
5566 Visible := True;
5567 IsPlayer := True;
5568 end;
5569 end;
5570 end;
5572 // Монстры:
5573 if vsMonster then g_Mons_ForEach(monsUpdate);
5574 end;
5576 // Если есть возможные цели:
5577 // (Выбираем лучшую, меняем оружие и бежим к ней/от нее)
5578 if targets <> nil then
5579 begin
5580 // Выбираем наилучшую цель:
5581 BestTarget := targets[0];
5582 if Length(targets) > 1 then
5583 for a := 1 to High(targets) do
5584 if Compare(BestTarget, targets[a]) = 1 then
5585 BestTarget := targets[a];
5587 // Если лучшая цель "виднее" текущей, то текущая := лучшая:
5588 if ((not Target.Visible) and BestTarget.Visible and (Target.UID <> BestTarget.UID)) or
5589 ((not Target.Line) and BestTarget.Line and BestTarget.Visible) then
5590 begin
5591 Target := BestTarget;
5593 if (Healthy() = 3) or ((Healthy() = 2)) then
5594 begin // Если здоровы - догоняем
5595 if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
5596 SetAIFlag('GORIGHT', '1');
5597 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
5598 SetAIFlag('GOLEFT', '1');
5599 end
5600 else
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;
5608 // Выбираем оружие на основе расстояния и приоритетов:
5609 SelectWeapon(Abs(x1-Target.cX));
5610 end;
5611 end;
5613 // Если есть цель:
5614 // (Догоняем/убегаем, стреляем по направлению к цели)
5615 // (Если цель далеко, то хватит следить за ней)
5616 if Target.UID <> 0 then
5617 begin
5618 if not TargetOnScreen(Target.X + Target.Rect.X,
5619 Target.Y + Target.Rect.Y) then
5620 begin // Цель сбежала с "экрана"
5621 if (Healthy() = 3) or ((Healthy() = 2)) then
5622 begin // Если здоровы - догоняем
5623 if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
5624 SetAIFlag('GORIGHT', '1');
5625 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
5626 SetAIFlag('GOLEFT', '1');
5627 end
5628 else
5629 begin // Если побиты - забываем о цели и убегаем
5630 Target.UID := 0;
5631 if ((RunDirection() = TDirection.D_LEFT) and (Target.X < FObj.X)) then
5632 SetAIFlag('GORIGHT', '1');
5633 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
5634 SetAIFlag('GOLEFT', '1');
5635 end;
5636 end
5637 else
5638 begin // Цель пока на "экране"
5639 // Если цель не загорожена стеной, то отмечаем, когда ее видели:
5640 if g_TraceVector(x1, y1, Target.cX, Target.cY) then
5641 FLastVisible := gTime;
5642 // Если разница высот не велика, то догоняем:
5643 if (Abs(FObj.Y-Target.Y) <= 128) then
5644 begin
5645 if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
5646 SetAIFlag('GORIGHT', '1');
5647 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
5648 SetAIFlag('GOLEFT', '1');
5649 end;
5650 end;
5652 // Выбираем угол вверх:
5653 if FDirection = TDirection.D_LEFT then
5654 angle := ANGLE_LEFTUP
5655 else
5656 angle := ANGLE_RIGHTUP;
5658 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
5659 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
5661 // Если при угле вверх можно попасть в приблизительное положение цели:
5662 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
5663 Target.X+Target.Rect.X+GetInterval(FDifficult.DiagPrecision, 128), //96
5664 Target.Y+Target.Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
5665 Target.Rect.Width, Target.Rect.Height) and
5666 g_TraceVector(x1, y1, Target.cX, Target.cY) then
5667 begin // то нужно стрелять вверх
5668 SetAIFlag('NEEDFIRE', '1');
5669 SetAIFlag('NEEDSEEUP', '1');
5670 end;
5672 // Выбираем угол вниз:
5673 if FDirection = TDirection.D_LEFT then
5674 angle := ANGLE_LEFTDOWN
5675 else
5676 angle := ANGLE_RIGHTDOWN;
5678 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
5679 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
5681 // Если при угле вниз можно попасть в приблизительное положение цели:
5682 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
5683 Target.X+Target.Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
5684 Target.Y+Target.Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
5685 Target.Rect.Width, Target.Rect.Height) and
5686 g_TraceVector(x1, y1, Target.cX, Target.cY) then
5687 begin // то нужно стрелять вниз
5688 SetAIFlag('NEEDFIRE', '1');
5689 SetAIFlag('NEEDSEEDOWN', '1');
5690 end;
5692 // Если цель видно и она на такой же высоте:
5693 if Target.Visible and
5694 (y1+4 < Target.Y+Target.Rect.Y+Target.Rect.Height) and
5695 (y1-4 > Target.Y+Target.Rect.Y) then
5696 begin
5697 // Если идем в сторону цели, то надо стрелять:
5698 if ((FDirection = TDirection.D_LEFT) and (Target.X < FObj.X)) or
5699 ((FDirection = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
5700 begin // то нужно стрелять вперед
5701 SetAIFlag('NEEDFIRE', '1');
5702 SetAIFlag('NEEDSEEDOWN', '');
5703 SetAIFlag('NEEDSEEUP', '');
5704 end;
5705 // Если цель в пределах "экрана" и сложность позволяет прыжки сближения:
5706 if Abs(FObj.X-Target.X) < Trunc(gPlayerScreenSize.X*0.75) then
5707 if GetRnd(FDifficult.CloseJump) then
5708 begin // то если повезет - прыгаем (особенно, если близко)
5709 if Abs(FObj.X-Target.X) < 128 then
5710 a := 4
5711 else
5712 a := 30;
5713 if Random(a) = 0 then
5714 SetAIFlag('NEEDJUMP', '1');
5715 end;
5716 end;
5718 // Если цель все еще есть:
5719 if Target.UID <> 0 then
5720 if gTime-FLastVisible > 2000 then // Если видели давно
5721 Target.UID := 0 // то забыть цель
5722 else // Если видели недавно
5723 begin // но цель убили
5724 if Target.IsPlayer then
5725 begin // Цель - игрок
5726 pla := g_Player_Get(Target.UID);
5727 if (pla = nil) or (not pla.alive) or pla.NoTarget or
5728 (pla.FMegaRulez[MR_INVIS] >= gTime) then
5729 Target.UID := 0; // то забыть цель
5730 end
5731 else
5732 begin // Цель - монстр
5733 mon := g_Monsters_ByUID(Target.UID);
5734 if (mon = nil) or (not mon.alive) then
5735 Target.UID := 0; // то забыть цель
5736 end;
5737 end;
5738 end; // if Target.UID <> 0
5740 FTargetUID := Target.UID;
5742 // Если возможных целей нет:
5743 // (Атака чего-нибудь слева или справа)
5744 if targets = nil then
5745 if GetAIFlag('ATTACKLEFT') <> '' then
5746 begin // Если нужно атаковать налево
5747 RemoveAIFlag('ATTACKLEFT');
5749 SetAIFlag('NEEDJUMP', '1');
5751 if RunDirection() = TDirection.D_RIGHT then
5752 begin // Идем не в ту сторону
5753 if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then
5754 begin // Если здоровы, то, возможно, стреляем бежим влево и стреляем
5755 SetAIFlag('NEEDFIRE', '1');
5756 SetAIFlag('GOLEFT', '1');
5757 end;
5758 end
5759 else
5760 begin // Идем в нужную сторону
5761 if GetRnd(FDifficult.InvisFire) then // Возможно, стреляем вслепую
5762 SetAIFlag('NEEDFIRE', '1');
5763 if Healthy() <= 1 then // Побиты - убегаем
5764 SetAIFlag('GORIGHT', '1');
5765 end;
5766 end
5767 else
5768 if GetAIFlag('ATTACKRIGHT') <> '' then
5769 begin // Если нужно атаковать направо
5770 RemoveAIFlag('ATTACKRIGHT');
5772 SetAIFlag('NEEDJUMP', '1');
5774 if RunDirection() = TDirection.D_LEFT then
5775 begin // Идем не в ту сторону
5776 if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then
5777 begin // Если здоровы, то, возможно, бежим вправо и стреляем
5778 SetAIFlag('NEEDFIRE', '1');
5779 SetAIFlag('GORIGHT', '1');
5780 end;
5781 end
5782 else
5783 begin
5784 if GetRnd(FDifficult.InvisFire) then // Возможно, стреляем вслепую
5785 SetAIFlag('NEEDFIRE', '1');
5786 if Healthy() <= 1 then // Побиты - убегаем
5787 SetAIFlag('GOLEFT', '1');
5788 end;
5789 end;
5791 //HACK! (does it belongs there?)
5792 RealizeCurrentWeapon();
5794 // Если есть возможные цели:
5795 // (Стреляем по направлению к целям)
5796 if (targets <> nil) and (GetAIFlag('NEEDFIRE') <> '') then
5797 for a := 0 to High(targets) do
5798 begin
5799 // Если можем стрелять по диагонали:
5800 if GetRnd(FDifficult.DiagFire) then
5801 begin
5802 // Ищем цель сверху и стреляем, если есть:
5803 if FDirection = TDirection.D_LEFT then
5804 angle := ANGLE_LEFTUP
5805 else
5806 angle := ANGLE_RIGHTUP;
5808 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
5809 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
5811 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
5812 targets[a].X+targets[a].Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
5813 targets[a].Y+targets[a].Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
5814 targets[a].Rect.Width, targets[a].Rect.Height) and
5815 g_TraceVector(x1, y1, targets[a].cX, targets[a].cY) then
5816 begin
5817 SetAIFlag('NEEDFIRE', '1');
5818 SetAIFlag('NEEDSEEUP', '1');
5819 end;
5821 // Ищем цель снизу и стреляем, если есть:
5822 if FDirection = TDirection.D_LEFT then
5823 angle := ANGLE_LEFTDOWN
5824 else
5825 angle := ANGLE_RIGHTDOWN;
5827 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
5828 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
5830 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
5831 targets[a].X+targets[a].Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
5832 targets[a].Y+targets[a].Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
5833 targets[a].Rect.Width, targets[a].Rect.Height) and
5834 g_TraceVector(x1, y1, targets[a].cX, targets[a].cY) then
5835 begin
5836 SetAIFlag('NEEDFIRE', '1');
5837 SetAIFlag('NEEDSEEDOWN', '1');
5838 end;
5839 end;
5841 // Если цель "перед носом", то стреляем:
5842 if targets[a].Line and targets[a].Visible and
5843 (((FDirection = TDirection.D_LEFT) and (targets[a].X < FObj.X)) or
5844 ((FDirection = TDirection.D_RIGHT) and (targets[a].X > FObj.X))) then
5845 begin
5846 SetAIFlag('NEEDFIRE', '1');
5847 Break;
5848 end;
5849 end;
5851 // Если летит пуля, то, возможно, подпрыгиваем:
5852 if g_Weapon_Danger(FUID, FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y,
5853 PLAYER_RECT.Width, PLAYER_RECT.Height,
5854 40+GetInterval(FDifficult.Cover, 40)) then
5855 SetAIFlag('NEEDJUMP', '1');
5857 // Если кончились паторны, то нужно сменить оружие:
5858 ammo := GetAmmoByWeapon(FCurrWeap);
5859 if ((FCurrWeap = WEAPON_SHOTGUN2) and (ammo < 2)) or
5860 ((FCurrWeap = WEAPON_BFG) and (ammo < 40)) or
5861 (ammo = 0) then
5862 SetAIFlag('SELECTWEAPON', '1');
5864 // Если нужно сменить оружие, то выбираем нужное:
5865 if GetAIFlag('SELECTWEAPON') = '1' then
5866 begin
5867 SelectWeapon(-1);
5868 RemoveAIFlag('SELECTWEAPON');
5869 end;
5870 end;
5872 procedure TBot.Update();
5873 var
5874 EnableAI: Boolean;
5875 begin
5876 if not FAlive then
5877 begin // Respawn
5878 ReleaseKeys();
5879 PressKey(KEY_UP);
5880 end
5881 else
5882 begin
5883 EnableAI := True;
5885 // Проверяем, отключён ли AI ботов
5886 if (g_debug_BotAIOff = 1) and (Team = TEAM_RED) then
5887 EnableAI := False;
5888 if (g_debug_BotAIOff = 2) and (Team = TEAM_BLUE) then
5889 EnableAI := False;
5890 if g_debug_BotAIOff = 3 then
5891 EnableAI := False;
5893 if EnableAI then
5894 begin
5895 UpdateMove();
5896 UpdateCombat();
5897 end
5898 else
5899 begin
5900 RealizeCurrentWeapon();
5901 end;
5902 end;
5904 inherited Update();
5905 end;
5907 procedure TBot.ReleaseKey(Key: Byte);
5908 begin
5909 with FKeys[Key] do
5910 begin
5911 Pressed := False;
5912 Time := 0;
5913 end;
5914 end;
5916 function TBot.KeyPressed(Key: Word): Boolean;
5917 begin
5918 Result := FKeys[Key].Pressed;
5919 end;
5921 function TBot.GetAIFlag(aName: String20): String20;
5922 var
5923 a: Integer;
5924 begin
5925 Result := '';
5927 aName := LowerCase(aName);
5929 if FAIFlags <> nil then
5930 for a := 0 to High(FAIFlags) do
5931 if LowerCase(FAIFlags[a].Name) = aName then
5932 begin
5933 Result := FAIFlags[a].Value;
5934 Break;
5935 end;
5936 end;
5938 procedure TBot.RemoveAIFlag(aName: String20);
5939 var
5940 a, b: Integer;
5941 begin
5942 if FAIFlags = nil then Exit;
5944 aName := LowerCase(aName);
5946 for a := 0 to High(FAIFlags) do
5947 if LowerCase(FAIFlags[a].Name) = aName then
5948 begin
5949 if a <> High(FAIFlags) then
5950 for b := a to High(FAIFlags)-1 do
5951 FAIFlags[b] := FAIFlags[b+1];
5953 SetLength(FAIFlags, Length(FAIFlags)-1);
5954 Break;
5955 end;
5956 end;
5958 procedure TBot.SetAIFlag(aName, fValue: String20);
5959 var
5960 a: Integer;
5961 ok: Boolean;
5962 begin
5963 a := 0;
5964 ok := False;
5966 aName := LowerCase(aName);
5968 if FAIFlags <> nil then
5969 for a := 0 to High(FAIFlags) do
5970 if LowerCase(FAIFlags[a].Name) = aName then
5971 begin
5972 ok := True;
5973 Break;
5974 end;
5976 if ok then FAIFlags[a].Value := fValue
5977 else
5978 begin
5979 SetLength(FAIFlags, Length(FAIFlags)+1);
5980 with FAIFlags[High(FAIFlags)] do
5981 begin
5982 Name := aName;
5983 Value := fValue;
5984 end;
5985 end;
5986 end;
5988 procedure TBot.UpdateMove;
5990 procedure GoLeft(Time: Word = 1);
5991 begin
5992 ReleaseKey(KEY_LEFT);
5993 ReleaseKey(KEY_RIGHT);
5994 PressKey(KEY_LEFT, Time);
5995 SetDirection(TDirection.D_LEFT);
5996 end;
5998 procedure GoRight(Time: Word = 1);
5999 begin
6000 ReleaseKey(KEY_LEFT);
6001 ReleaseKey(KEY_RIGHT);
6002 PressKey(KEY_RIGHT, Time);
6003 SetDirection(TDirection.D_RIGHT);
6004 end;
6006 function Rnd(a: Word): Boolean;
6007 begin
6008 Result := Random(a) = 0;
6009 end;
6011 procedure Turn(Time: Word = 1200);
6012 begin
6013 if RunDirection() = TDirection.D_LEFT then GoRight(Time) else GoLeft(Time);
6014 end;
6016 procedure Stop();
6017 begin
6018 ReleaseKey(KEY_LEFT);
6019 ReleaseKey(KEY_RIGHT);
6020 end;
6022 function CanRunLeft(): Boolean;
6023 begin
6024 Result := not CollideLevel(-1, 0);
6025 end;
6027 function CanRunRight(): Boolean;
6028 begin
6029 Result := not CollideLevel(1, 0);
6030 end;
6032 function CanRun(): Boolean;
6033 begin
6034 if RunDirection() = TDirection.D_LEFT then Result := CanRunLeft() else Result := CanRunRight();
6035 end;
6037 procedure Jump(Time: Word = 30);
6038 begin
6039 PressKey(KEY_JUMP, Time);
6040 end;
6042 function NearHole(): Boolean;
6043 var
6044 x, sx: Integer;
6045 begin
6046 { TODO 5 : Лестницы }
6047 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
6048 for x := 1 to PLAYER_RECT.Width do
6049 if (not StayOnStep(x*sx, 0)) and
6050 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
6051 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
6052 begin
6053 Result := True;
6054 Exit;
6055 end;
6057 Result := False;
6058 end;
6060 function BorderHole(): Boolean;
6061 var
6062 x, sx, xx: Integer;
6063 begin
6064 { TODO 5 : Лестницы }
6065 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
6066 for x := 1 to PLAYER_RECT.Width do
6067 if (not StayOnStep(x*sx, 0)) and
6068 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
6069 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
6070 begin
6071 for xx := x to x+32 do
6072 if CollideLevel(xx*sx, PLAYER_RECT.Height) then
6073 begin
6074 Result := True;
6075 Exit;
6076 end;
6077 end;
6079 Result := False;
6080 end;
6082 function NearDeepHole(): Boolean;
6083 var
6084 x, sx, y: Integer;
6085 begin
6086 Result := False;
6088 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
6089 y := 3;
6091 for x := 1 to PLAYER_RECT.Width do
6092 if (not StayOnStep(x*sx, 0)) and
6093 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
6094 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
6095 begin
6096 while FObj.Y+y*PLAYER_RECT.Height < gMapInfo.Height do
6097 begin
6098 if CollideLevel(x*sx, PLAYER_RECT.Height*y) then Exit;
6099 y := y+1;
6100 end;
6102 Result := True;
6103 end else Result := False;
6104 end;
6106 function OverDeepHole(): Boolean;
6107 var
6108 y: Integer;
6109 begin
6110 Result := False;
6112 y := 1;
6113 while FObj.Y+y*PLAYER_RECT.Height < gMapInfo.Height do
6114 begin
6115 if CollideLevel(0, PLAYER_RECT.Height*y) then Exit;
6116 y := y+1;
6117 end;
6119 Result := True;
6120 end;
6122 function OnGround(): Boolean;
6123 begin
6124 Result := StayOnStep(0, 0) or CollideLevel(0, 1);
6125 end;
6127 function OnLadder(): Boolean;
6128 begin
6129 Result := FullInStep(0, 0);
6130 end;
6132 function BelowLadder(): Boolean;
6133 begin
6134 Result := (FullInStep(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) and
6135 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or
6136 (FullInStep(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) and
6137 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP));
6138 end;
6140 function BelowLiftUp(): Boolean;
6141 begin
6142 Result := ((FullInLift(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) = -1) and
6143 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or
6144 ((FullInLift(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) = -1) and
6145 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP));
6146 end;
6148 function OnTopLift(): Boolean;
6149 begin
6150 Result := (FullInLift(0, 0) = -1) and (FullInLift(0, -32) = 0);
6151 end;
6153 function CanJumpOver(): Boolean;
6154 var
6155 sx, y: Integer;
6156 begin
6157 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
6159 Result := False;
6161 if not CollideLevel(sx, 0) then Exit;
6163 for y := 1 to BOT_MAXJUMP do
6164 if CollideLevel(0, -y) then Exit else
6165 if not CollideLevel(sx, -y) then
6166 begin
6167 Result := True;
6168 Exit;
6169 end;
6170 end;
6172 function CanJumpUp(Dist: ShortInt): Boolean;
6173 var
6174 y, yy: Integer;
6175 c: Boolean;
6176 begin
6177 Result := False;
6179 if CollideLevel(Dist, 0) then Exit;
6181 c := False;
6182 for y := 0 to BOT_MAXJUMP do
6183 if CollideLevel(Dist, -y) then
6184 begin
6185 c := True;
6186 Break;
6187 end;
6189 if not c then Exit;
6191 c := False;
6192 for yy := y+1 to BOT_MAXJUMP do
6193 if not CollideLevel(Dist, -yy) then
6194 begin
6195 c := True;
6196 Break;
6197 end;
6199 if not c then Exit;
6201 c := False;
6202 for y := 0 to BOT_MAXJUMP do
6203 if CollideLevel(0, -y) then
6204 begin
6205 c := True;
6206 Break;
6207 end;
6209 if c then Exit;
6211 if y < yy then Exit;
6213 Result := True;
6214 end;
6216 function IsSafeTrigger(): Boolean;
6217 var
6218 a: Integer;
6219 begin
6220 Result := True;
6221 if gTriggers = nil then
6222 Exit;
6223 for a := 0 to High(gTriggers) do
6224 if Collide(gTriggers[a].X,
6225 gTriggers[a].Y,
6226 gTriggers[a].Width,
6227 gTriggers[a].Height) and
6228 (gTriggers[a].TriggerType in [TRIGGER_EXIT, TRIGGER_CLOSEDOOR,
6229 TRIGGER_CLOSETRAP, TRIGGER_TRAP,
6230 TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF,
6231 TRIGGER_ONOFF, TRIGGER_SPAWNMONSTER,
6232 TRIGGER_DAMAGE, TRIGGER_SHOT]) then
6233 Result := False;
6234 end;
6236 begin
6237 // Возможно, нажимаем кнопку:
6238 if Rnd(16) and IsSafeTrigger() then
6239 PressKey(KEY_OPEN);
6241 // Если под лифтом или ступеньками, то, возможно, прыгаем:
6242 if OnLadder() or ((BelowLadder() or BelowLiftUp()) and Rnd(8)) then
6243 begin
6244 ReleaseKey(KEY_LEFT);
6245 ReleaseKey(KEY_RIGHT);
6246 Jump();
6247 end;
6249 // Идем влево, если надо было:
6250 if GetAIFlag('GOLEFT') <> '' then
6251 begin
6252 RemoveAIFlag('GOLEFT');
6253 if CanRunLeft() then
6254 GoLeft(360);
6255 end;
6257 // Идем вправо, если надо было:
6258 if GetAIFlag('GORIGHT') <> '' then
6259 begin
6260 RemoveAIFlag('GORIGHT');
6261 if CanRunRight() then
6262 GoRight(360);
6263 end;
6265 // Если вылетели за карту, то пробуем вернуться:
6266 if FObj.X < -32 then
6267 GoRight(360)
6268 else
6269 if FObj.X+32 > gMapInfo.Width then
6270 GoLeft(360);
6272 // Прыгаем, если надо было:
6273 if GetAIFlag('NEEDJUMP') <> '' then
6274 begin
6275 Jump(0);
6276 RemoveAIFlag('NEEDJUMP');
6277 end;
6279 // Смотрим вверх, если надо было:
6280 if GetAIFlag('NEEDSEEUP') <> '' then
6281 begin
6282 ReleaseKey(KEY_UP);
6283 ReleaseKey(KEY_DOWN);
6284 PressKey(KEY_UP, 20);
6285 RemoveAIFlag('NEEDSEEUP');
6286 end;
6288 // Смотрим вниз, если надо было:
6289 if GetAIFlag('NEEDSEEDOWN') <> '' then
6290 begin
6291 ReleaseKey(KEY_UP);
6292 ReleaseKey(KEY_DOWN);
6293 PressKey(KEY_DOWN, 20);
6294 RemoveAIFlag('NEEDSEEDOWN');
6295 end;
6297 // Если нужно было в дыру и мы не на земле, то покорно летим:
6298 if GetAIFlag('GOINHOLE') <> '' then
6299 if not OnGround() then
6300 begin
6301 ReleaseKey(KEY_LEFT);
6302 ReleaseKey(KEY_RIGHT);
6303 RemoveAIFlag('GOINHOLE');
6304 SetAIFlag('FALLINHOLE', '1');
6305 end;
6307 // Если падали и достигли земли, то хватит падать:
6308 if GetAIFlag('FALLINHOLE') <> '' then
6309 if OnGround() then
6310 RemoveAIFlag('FALLINHOLE');
6312 // Если летели прямо и сейчас не на лестнице или на вершине лифта, то отходим в сторону:
6313 if not (KeyPressed(KEY_LEFT) or KeyPressed(KEY_RIGHT)) then
6314 if GetAIFlag('FALLINHOLE') = '' then
6315 if (not OnLadder()) or (FObj.Vel.Y >= 0) or (OnTopLift()) then
6316 if Rnd(2) then
6317 GoLeft(360)
6318 else
6319 GoRight(360);
6321 // Если на земле и можно подпрыгнуть, то, возможно, прыгаем:
6322 if OnGround() and
6323 CanJumpUp(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*32) and
6324 Rnd(8) then
6325 Jump();
6327 // Если на земле и возле дыры (глубина > 2 ростов игрока):
6328 if OnGround() and NearHole() then
6329 if NearDeepHole() then // Если это бездна
6330 case Random(6) of
6331 0..3: Turn(); // Бежим обратно
6332 4: Jump(); // Прыгаем
6333 5: begin // Прыгаем обратно
6334 Turn();
6335 Jump();
6336 end;
6337 end
6338 else // Это не бездна и мы еще не летим туда
6339 if GetAIFlag('GOINHOLE') = '' then
6340 case Random(6) of
6341 0: Turn(); // Не нужно туда
6342 1: Jump(); // Вдруг повезет - прыгаем
6343 else // Если яма с границей, то при случае можно туда прыгнуть
6344 if BorderHole() then
6345 SetAIFlag('GOINHOLE', '1');
6346 end;
6348 // Если на земле, но некуда идти:
6349 if (not CanRun()) and OnGround() then
6350 begin
6351 // Если мы на лестнице или можно перепрыгнуть, то прыгаем:
6352 if CanJumpOver() or OnLadder() then
6353 Jump()
6354 else // иначе попытаемся в другую сторону
6355 if Random(2) = 0 then
6356 begin
6357 if IsSafeTrigger() then
6358 PressKey(KEY_OPEN);
6359 end else
6360 Turn();
6361 end;
6363 // Осталось мало воздуха:
6364 if FAir < 36 * 2 then
6365 Jump(20);
6367 // Выбираемся из кислоты, если нет костюма, обожглись, или мало здоровья:
6368 if (FMegaRulez[MR_SUIT] < gTime) and ((FLastHit = HIT_ACID) or (Healthy() <= 1)) then
6369 if BodyInAcid(0, 0) then
6370 Jump();
6371 end;
6373 function TBot.FullInStep(XInc, YInc: Integer): Boolean;
6374 begin
6375 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
6376 PLAYER_RECT.Width, PLAYER_RECT.Height, PANEL_STEP, False);
6377 end;
6379 {function TBot.NeedItem(Item: Byte): Byte;
6380 begin
6381 Result := 4;
6382 end;}
6384 procedure TBot.SelectWeapon(Dist: Integer);
6385 var
6386 a: Integer;
6388 function HaveAmmo(weapon: Byte): Boolean;
6389 begin
6390 case weapon of
6391 WEAPON_PISTOL: Result := FAmmo[A_BULLETS] >= 1;
6392 WEAPON_SHOTGUN1: Result := FAmmo[A_SHELLS] >= 1;
6393 WEAPON_SHOTGUN2: Result := FAmmo[A_SHELLS] >= 2;
6394 WEAPON_CHAINGUN: Result := FAmmo[A_BULLETS] >= 10;
6395 WEAPON_ROCKETLAUNCHER: Result := FAmmo[A_ROCKETS] >= 1;
6396 WEAPON_PLASMA: Result := FAmmo[A_CELLS] >= 10;
6397 WEAPON_BFG: Result := FAmmo[A_CELLS] >= 40;
6398 WEAPON_SUPERPULEMET: Result := FAmmo[A_SHELLS] >= 1;
6399 WEAPON_FLAMETHROWER: Result := FAmmo[A_FUEL] >= 1;
6400 else Result := True;
6401 end;
6402 end;
6404 begin
6405 if Dist = -1 then Dist := BOT_LONGDIST;
6407 if Dist > BOT_LONGDIST then
6408 begin // Дальний бой
6409 for a := 0 to 9 do
6410 if FWeapon[FDifficult.WeaponPrior[a]] and HaveAmmo(FDifficult.WeaponPrior[a]) then
6411 begin
6412 FSelectedWeapon := FDifficult.WeaponPrior[a];
6413 Break;
6414 end;
6415 end
6416 else //if Dist > BOT_UNSAFEDIST then
6417 begin // Ближний бой
6418 for a := 0 to 9 do
6419 if FWeapon[FDifficult.CloseWeaponPrior[a]] and HaveAmmo(FDifficult.CloseWeaponPrior[a]) then
6420 begin
6421 FSelectedWeapon := FDifficult.CloseWeaponPrior[a];
6422 Break;
6423 end;
6424 end;
6425 { else
6426 begin
6427 for a := 0 to 9 do
6428 if FWeapon[FDifficult.SafeWeaponPrior[a]] and HaveAmmo(FDifficult.SafeWeaponPrior[a]) then
6429 begin
6430 FSelectedWeapon := FDifficult.SafeWeaponPrior[a];
6431 Break;
6432 end;
6433 end;}
6434 end;
6436 function TBot.PickItem(ItemType: Byte; force: Boolean; var remove: Boolean): Boolean;
6437 begin
6438 Result := inherited PickItem(ItemType, force, remove);
6440 if Result then SetAIFlag('SELECTWEAPON', '1');
6441 end;
6443 function TBot.Heal(value: Word; Soft: Boolean): Boolean;
6444 begin
6445 Result := inherited Heal(value, Soft);
6446 end;
6448 function TBot.Healthy(): Byte;
6449 begin
6450 if FMegaRulez[MR_INVUL] >= gTime then Result := 3
6451 else if (FHealth > 80) or ((FHealth > 50) and (FArmor > 20)) then Result := 3
6452 else if (FHealth > 50) then Result := 2
6453 else if (FHealth > 20) then Result := 1
6454 else Result := 0;
6455 end;
6457 function TBot.TargetOnScreen(TX, TY: Integer): Boolean;
6458 begin
6459 Result := (Abs(FObj.X-TX) <= Trunc(gPlayerScreenSize.X*0.6)) and
6460 (Abs(FObj.Y-TY) <= Trunc(gPlayerScreenSize.Y*0.6));
6461 end;
6463 procedure TBot.OnDamage(Angle: SmallInt);
6464 var
6465 pla: TPlayer;
6466 mon: TMonster;
6467 ok: Boolean;
6468 begin
6469 inherited;
6471 if (Angle = 0) or (Angle = 180) then
6472 begin
6473 ok := False;
6474 if (g_GetUIDType(FLastSpawnerUID) = UID_PLAYER) and
6475 LongBool(gGameSettings.Options and GAME_OPTION_BOTVSPLAYER) then
6476 begin // Игрок
6477 pla := g_Player_Get(FLastSpawnerUID);
6478 ok := not TargetOnScreen(pla.FObj.X + PLAYER_RECT.X,
6479 pla.FObj.Y + PLAYER_RECT.Y);
6480 end
6481 else
6482 if (g_GetUIDType(FLastSpawnerUID) = UID_MONSTER) and
6483 LongBool(gGameSettings.Options and GAME_OPTION_BOTVSMONSTER) then
6484 begin // Монстр
6485 mon := g_Monsters_ByUID(FLastSpawnerUID);
6486 ok := not TargetOnScreen(mon.Obj.X + mon.Obj.Rect.X,
6487 mon.Obj.Y + mon.Obj.Rect.Y);
6488 end;
6490 if ok then
6491 if Angle = 0 then
6492 SetAIFlag('ATTACKLEFT', '1')
6493 else
6494 SetAIFlag('ATTACKRIGHT', '1');
6495 end;
6496 end;
6498 function TBot.RunDirection(): TDirection;
6499 begin
6500 if Abs(Vel.X) >= 1 then
6501 begin
6502 if Vel.X > 0 then Result := TDirection.D_RIGHT else Result := TDirection.D_LEFT;
6503 end else
6504 Result := FDirection;
6505 end;
6507 function TBot.GetRnd(a: Byte): Boolean;
6508 begin
6509 if a = 0 then Result := False
6510 else if a = 255 then Result := True
6511 else Result := Random(256) > 255-a;
6512 end;
6514 function TBot.GetInterval(a: Byte; radius: SmallInt): SmallInt;
6515 begin
6516 Result := Round((255-a)/255*radius*(Random(2)-1));
6517 end;
6520 procedure TDifficult.save (st: TStream);
6521 begin
6522 utils.writeInt(st, Byte(DiagFire));
6523 utils.writeInt(st, Byte(InvisFire));
6524 utils.writeInt(st, Byte(DiagPrecision));
6525 utils.writeInt(st, Byte(FlyPrecision));
6526 utils.writeInt(st, Byte(Cover));
6527 utils.writeInt(st, Byte(CloseJump));
6528 st.WriteBuffer(WeaponPrior[Low(WeaponPrior)], sizeof(WeaponPrior));
6529 st.WriteBuffer(CloseWeaponPrior[Low(CloseWeaponPrior)], sizeof(CloseWeaponPrior));
6530 end;
6532 procedure TDifficult.load (st: TStream);
6533 begin
6534 DiagFire := utils.readByte(st);
6535 InvisFire := utils.readByte(st);
6536 DiagPrecision := utils.readByte(st);
6537 FlyPrecision := utils.readByte(st);
6538 Cover := utils.readByte(st);
6539 CloseJump := utils.readByte(st);
6540 st.ReadBuffer(WeaponPrior[Low(WeaponPrior)], sizeof(WeaponPrior));
6541 st.ReadBuffer(CloseWeaponPrior[Low(CloseWeaponPrior)], sizeof(CloseWeaponPrior));
6542 end;
6545 procedure TBot.SaveState (st: TStream);
6546 var
6547 i: Integer;
6548 dw: Integer;
6549 begin
6550 inherited SaveState(st);
6551 utils.writeSign(st, 'BOT0');
6552 // Выбранное оружие
6553 utils.writeInt(st, Byte(FSelectedWeapon));
6554 // UID цели
6555 utils.writeInt(st, Word(FTargetUID));
6556 // Время потери цели
6557 utils.writeInt(st, LongWord(FLastVisible));
6558 // Количество флагов ИИ
6559 dw := Length(FAIFlags);
6560 utils.writeInt(st, LongInt(dw));
6561 // Флаги ИИ
6562 for i := 0 to dw-1 do
6563 begin
6564 utils.writeStr(st, FAIFlags[i].Name, 20);
6565 utils.writeStr(st, FAIFlags[i].Value, 20);
6566 end;
6567 // Настройки сложности
6568 FDifficult.save(st);
6569 end;
6572 procedure TBot.LoadState (st: TStream);
6573 var
6574 i: Integer;
6575 dw: Integer;
6576 begin
6577 inherited LoadState(st);
6578 if not utils.checkSign(st, 'BOT0') then raise XStreamError.Create('invalid bot signature');
6579 // Выбранное оружие
6580 FSelectedWeapon := utils.readByte(st);
6581 // UID цели
6582 FTargetUID := utils.readWord(st);
6583 // Время потери цели
6584 FLastVisible := utils.readLongWord(st);
6585 // Количество флагов ИИ
6586 dw := utils.readLongInt(st);
6587 if (dw < 0) or (dw > 16384) then raise XStreamError.Create('invalid number of bot AI flags');
6588 SetLength(FAIFlags, dw);
6589 // Флаги ИИ
6590 for i := 0 to dw-1 do
6591 begin
6592 FAIFlags[i].Name := utils.readStr(st, 20);
6593 FAIFlags[i].Value := utils.readStr(st, 20);
6594 end;
6595 // Настройки сложности
6596 FDifficult.load(st);
6597 end;
6600 begin
6601 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');
6602 conRegVar('player_indicator_style', @gPlayerIndicatorStyle, 'Visual appearance of indicator', 'Visual appearance of indicator');
6603 end.