DEADSOFTWARE

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