DEADSOFTWARE

reset weapon switch timer in various places; just in case
[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, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *)
16 {$INCLUDE ../shared/a_modes.inc}
17 {$M+}
18 unit g_player;
20 interface
22 uses
23 SysUtils, Classes,
24 {$IFDEF USE_MEMPOOL}mempool,{$ENDIF}
25 e_graphics, g_playermodel, g_basic, g_textures,
26 g_weapons, g_phys, g_sound, g_saveload, MAPDEF,
27 g_panel;
29 const
30 KEY_LEFT = 1;
31 KEY_RIGHT = 2;
32 KEY_UP = 3;
33 KEY_DOWN = 4;
34 KEY_FIRE = 5;
35 KEY_NEXTWEAPON = 6;
36 KEY_PREVWEAPON = 7;
37 KEY_OPEN = 8;
38 KEY_JUMP = 9;
39 KEY_CHAT = 10;
41 R_ITEM_BACKPACK = 0;
42 R_KEY_RED = 1;
43 R_KEY_GREEN = 2;
44 R_KEY_BLUE = 3;
45 R_BERSERK = 4;
47 MR_SUIT = 0;
48 MR_INVUL = 1;
49 MR_INVIS = 2;
50 MR_MAX = 2;
52 A_BULLETS = 0;
53 A_SHELLS = 1;
54 A_ROCKETS = 2;
55 A_CELLS = 3;
56 A_FUEL = 4;
57 A_HIGH = 4;
59 AmmoLimits: Array [0..1] of Array [A_BULLETS..A_HIGH] of Word =
60 ((200, 50, 50, 300, 100),
61 (400, 100, 100, 600, 200));
63 K_SIMPLEKILL = 0;
64 K_HARDKILL = 1;
65 K_EXTRAHARDKILL = 2;
66 K_FALLKILL = 3;
68 T_RESPAWN = 0;
69 T_SWITCH = 1;
70 T_USE = 2;
71 T_FLAGCAP = 3;
73 TEAM_NONE = 0;
74 TEAM_RED = 1;
75 TEAM_BLUE = 2;
76 TEAM_COOP = 3;
78 SHELL_BULLET = 0;
79 SHELL_SHELL = 1;
80 SHELL_DBLSHELL = 2;
82 ANGLE_NONE = Low(SmallInt);
84 CORPSE_STATE_REMOVEME = 0;
85 CORPSE_STATE_NORMAL = 1;
86 CORPSE_STATE_MESS = 2;
88 PLAYER_RECT: TRectWH = (X:15; Y:12; Width:34; Height:52);
89 PLAYER_RECT_CX = 15+(34 div 2);
90 PLAYER_RECT_CY = 12+(52 div 2);
91 PLAYER_CORPSERECT: TRectWH = (X:15; Y:48; Width:34; Height:16);
93 PLAYER_HP_SOFT = 100;
94 PLAYER_HP_LIMIT = 200;
95 PLAYER_AP_SOFT = 100;
96 PLAYER_AP_LIMIT = 200;
97 SUICIDE_DAMAGE = 112;
99 PLAYER1_DEF_COLOR: TRGB = (R:64; G:175; B:48);
100 PLAYER2_DEF_COLOR: TRGB = (R:96; G:96; B:96);
102 type
103 TPlayerStat = record
104 Ping: Word;
105 Loss: Byte;
106 Name: String;
107 Team: Byte;
108 Frags: SmallInt;
109 Deaths: SmallInt;
110 Lives: Byte;
111 Kills: Word;
112 Color: TRGB;
113 Spectator: Boolean;
114 end;
116 TPlayerStatArray = Array of TPlayerStat;
118 TPlayerSavedState = record
119 Health: Integer;
120 Armor: Integer;
121 Air: Integer;
122 JetFuel: Integer;
123 CurrWeap: Byte;
124 NextWeap: WORD;
125 NextWeapDelay: Byte;
126 Ammo: Array [A_BULLETS..A_HIGH] of Word;
127 MaxAmmo: Array [A_BULLETS..A_HIGH] of Word;
128 Weapon: Array [WP_FIRST..WP_LAST] of Boolean;
129 Rulez: Set of R_ITEM_BACKPACK..R_BERSERK;
130 WaitRecall: Boolean;
131 end;
133 TKeyState = record
134 Pressed: Boolean;
135 Time: Word;
136 end;
138 TPlayer = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
139 private
140 FIamBot: Boolean;
141 FUID: Word;
142 FName: String;
143 FTeam: Byte;
144 FAlive: Boolean;
145 FSpawned: Boolean;
146 FDirection: TDirection;
147 FHealth: Integer;
148 FLives: Byte;
149 FArmor: Integer;
150 FAir: Integer;
151 FPain: Integer;
152 FPickup: Integer;
153 FKills: Integer;
154 FMonsterKills: Integer;
155 FFrags: Integer;
156 FFragCombo: Byte;
157 FLastFrag: LongWord;
158 FComboEvnt: Integer;
159 FDeath: Integer;
160 FCanJetpack: Boolean;
161 FJetFuel: Integer;
162 FFlag: Byte;
163 FSecrets: Integer;
164 FCurrWeap: Byte;
165 //FNetForceWeap: Byte; // spam server with this -- this is new weapon we want to use
166 FNetForceWeapFIdx: LongWord; // frame index; ignore weapon change if it is lesser than this
167 //FCurrFrameIdx: LongWord; // increased in each `Update()`
168 FNextWeap: WORD;
169 FNextWeapDelay: Byte; // frames (unused)
170 FBFGFireCounter: SmallInt;
171 FLastSpawnerUID: Word;
172 FLastHit: Byte;
173 FObj: TObj;
174 FXTo, FYTo: Integer;
175 FSpectatePlayer: Integer;
176 FFirePainTime: Integer;
177 FFireAttacker: Word;
179 FSavedState: TPlayerSavedState;
181 FModel: TPlayerModel;
182 FPunchAnim: TAnimation;
183 FActionPrior: Byte;
184 FActionAnim: Byte;
185 FActionForce: Boolean;
186 FActionChanged: Boolean;
187 FAngle: SmallInt;
188 FFireAngle: SmallInt;
189 FIncCam: Integer;
190 FShellTimer: Integer;
191 FShellType: Byte;
192 FSawSound: TPlayableSound;
193 FSawSoundIdle: TPlayableSound;
194 FSawSoundHit: TPlayableSound;
195 FSawSoundSelect: TPlayableSound;
196 FJetSoundOn: TPlayableSound;
197 FJetSoundOff: TPlayableSound;
198 FJetSoundFly: TPlayableSound;
199 FGodMode: Boolean;
200 FNoTarget: Boolean;
201 FNoReload: Boolean;
202 FJustTeleported: Boolean;
203 FNetTime: LongWord;
204 mEDamageType: Integer;
206 // client-side only
207 weaponSwitchKeyReleased: array[0..16] of Byte; // bit 0: was released on prev frame; bit 1: new status
210 function CollideLevel(XInc, YInc: Integer): Boolean;
211 function StayOnStep(XInc, YInc: Integer): Boolean;
212 function HeadInLiquid(XInc, YInc: Integer): Boolean;
213 function BodyInLiquid(XInc, YInc: Integer): Boolean;
214 function BodyInAcid(XInc, YInc: Integer): Boolean;
215 function FullInLift(XInc, YInc: Integer): Integer;
216 {procedure CollideItem();}
217 procedure FlySmoke(Times: DWORD = 1);
218 procedure OnFireFlame(Times: DWORD = 1);
219 function GetAmmoByWeapon(Weapon: Byte): Word;
220 procedure SetAction(Action: Byte; Force: Boolean = False);
221 procedure OnDamage(Angle: SmallInt); virtual;
222 function firediry(): Integer;
223 procedure DoPunch();
225 procedure Run(Direction: TDirection);
226 procedure NextWeapon();
227 procedure PrevWeapon();
228 procedure SeeUp();
229 procedure SeeDown();
230 procedure Fire();
231 procedure Jump();
232 procedure Use();
234 function getNextWeaponIndex (): Byte; // return 255 for "no switch"
235 procedure resetWeaponQueue ();
236 function hasAmmoForWeapon (weapon: Byte): Boolean;
238 procedure doDamage (v: Integer);
240 function followCorpse(): Boolean;
242 public
243 FDamageBuffer: Integer;
245 FAmmo: Array [A_BULLETS..A_HIGH] of Word;
246 FMaxAmmo: Array [A_BULLETS..A_HIGH] of Word;
247 FWeapon: Array [WP_FIRST..WP_LAST] of Boolean;
248 FRulez: Set of R_ITEM_BACKPACK..R_BERSERK;
249 FBerserk: Integer;
250 FMegaRulez: Array [MR_SUIT..MR_MAX] of DWORD;
251 FReloading: Array [WP_FIRST..WP_LAST] of Word;
252 FTime: Array [T_RESPAWN..T_FLAGCAP] of DWORD;
253 FKeys: Array [KEY_LEFT..KEY_CHAT] of TKeyState;
254 FColor: TRGB;
255 FPreferredTeam: Byte;
256 FSpectator: Boolean;
257 FNoRespawn: Boolean;
258 FWantsInGame: Boolean;
259 FGhost: Boolean;
260 FPhysics: Boolean;
261 FJetpack: Boolean;
262 FActualModelName: string;
263 FClientID: SmallInt;
264 FPing: Word;
265 FLoss: Byte;
266 FDummy: Boolean;
267 FFireTime: Integer;
269 // debug: viewport offset
270 viewPortX, viewPortY, viewPortW, viewPortH: Integer;
272 function isValidViewPort (): Boolean; inline;
274 constructor Create(); virtual;
275 destructor Destroy(); override;
276 procedure Respawn(Silent: Boolean; Force: Boolean = False); virtual;
277 function GetRespawnPoint(): Byte;
278 procedure PressKey(Key: Byte; Time: Word = 1);
279 procedure ReleaseKeys();
280 procedure ReleaseKeysNoWeapon();
281 procedure SetModel(ModelName: String);
282 procedure SetColor(Color: TRGB);
283 procedure SetWeapon(W: Byte);
284 function IsKeyPressed(K: Byte): Boolean;
285 function GetKeys(): Byte;
286 function PickItem(ItemType: Byte; arespawn: Boolean; var remove: Boolean): Boolean; virtual;
287 function Collide(X, Y: Integer; Width, Height: Word): Boolean; overload;
288 function Collide(Panel: TPanel): Boolean; overload;
289 function Collide(X, Y: Integer): Boolean; overload;
290 procedure SetDirection(Direction: TDirection);
291 procedure GetSecret();
292 function TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean;
293 procedure Touch();
294 procedure Push(vx, vy: Integer);
295 procedure ChangeModel(ModelName: String);
296 procedure SwitchTeam;
297 procedure ChangeTeam(Team: Byte);
298 procedure BFGHit();
299 function GetFlag(Flag: Byte): Boolean;
300 procedure SetFlag(Flag: Byte);
301 function DropFlag(): Boolean;
302 procedure AllRulez(Health: Boolean);
303 procedure RestoreHealthArmor();
304 procedure FragCombo();
305 procedure GiveItem(ItemType: Byte);
306 procedure Damage(value: Word; SpawnerUID: Word; vx, vy: Integer; t: Byte); virtual;
307 function Heal(value: Word; Soft: Boolean): Boolean; virtual;
308 procedure MakeBloodVector(Count: Word; VelX, VelY: Integer);
309 procedure MakeBloodSimple(Count: Word);
310 procedure Kill(KillType: Byte; SpawnerUID: Word; t: Byte);
311 procedure Reset(Force: Boolean);
312 procedure Spectate(NoMove: Boolean = False);
313 procedure SwitchNoClip;
314 procedure SoftReset();
315 procedure Draw(); virtual;
316 procedure DrawPain();
317 procedure DrawPickup();
318 procedure DrawRulez();
319 procedure DrawAim();
320 procedure DrawBubble();
321 procedure DrawGUI();
322 procedure Update(); virtual;
323 procedure RememberState();
324 procedure RecallState();
325 procedure SaveState (st: TStream); virtual;
326 procedure LoadState (st: TStream); virtual;
327 procedure PauseSounds(Enable: Boolean);
328 procedure NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1);
329 procedure DoLerp(Level: Integer = 2);
330 procedure SetLerp(XTo, YTo: Integer);
331 procedure QueueWeaponSwitch(Weapon: Byte);
332 procedure RealizeCurrentWeapon();
333 procedure JetpackOn;
334 procedure JetpackOff;
335 procedure CatchFire(Attacker: Word);
337 //WARNING! this does nothing for now, but still call it!
338 procedure positionChanged (); //WARNING! call this after entity position was changed, or coldet will not work right!
340 procedure getMapBox (out x, y, w, h: Integer); inline;
341 procedure moveBy (dx, dy: Integer); inline;
343 procedure releaseAllWeaponSwitchKeys ();
344 procedure weaponSwitchKeysStateChange (index: Integer; pressed: Boolean);
345 function isWeaponSwitchKeyReleased (index: Integer): Boolean;
346 procedure weaponSwitchKeysShiftNewStates ();
348 public
349 property Vel: TPoint2i read FObj.Vel;
350 property Obj: TObj read FObj;
352 property Name: String read FName write FName;
353 property Model: TPlayerModel read FModel;
354 property Health: Integer read FHealth write FHealth;
355 property Lives: Byte read FLives write FLives;
356 property Armor: Integer read FArmor write FArmor;
357 property Air: Integer read FAir write FAir;
358 property JetFuel: Integer read FJetFuel write FJetFuel;
359 property Frags: Integer read FFrags write FFrags;
360 property Death: Integer read FDeath write FDeath;
361 property Kills: Integer read FKills write FKills;
362 property CurrWeap: Byte read FCurrWeap write FCurrWeap;
363 //property NetForceWeap: Byte read FNetForceWeap write FNetForceWeap;
364 property NetForceWeapFIdx: LongWord read FNetForceWeapFIdx write FNetForceWeapFIdx;
365 //property CurrFrameIdx: LongWord read FCurrFrameIdx write FCurrFrameIdx;
366 property MonsterKills: Integer read FMonsterKills write FMonsterKills;
367 property Secrets: Integer read FSecrets;
368 property GodMode: Boolean read FGodMode write FGodMode;
369 property NoTarget: Boolean read FNoTarget write FNoTarget;
370 property NoReload: Boolean read FNoReload write FNoReload;
371 property alive: Boolean read FAlive write FAlive;
372 property Flag: Byte read FFlag;
373 property Team: Byte read FTeam write FTeam;
374 property Direction: TDirection read FDirection;
375 property GameX: Integer read FObj.X write FObj.X;
376 property GameY: Integer read FObj.Y write FObj.Y;
377 property GameVelX: Integer read FObj.Vel.X write FObj.Vel.X;
378 property GameVelY: Integer read FObj.Vel.Y write FObj.Vel.Y;
379 property GameAccelX: Integer read FObj.Accel.X write FObj.Accel.X;
380 property GameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y;
381 property IncCam: Integer read FIncCam write FIncCam;
382 property UID: Word read FUID write FUID;
383 property JustTeleported: Boolean read FJustTeleported write FJustTeleported;
384 property NetTime: LongWord read FNetTime write FNetTime;
386 published
387 property eName: String read FName write FName;
388 property eHealth: Integer read FHealth write FHealth;
389 property eLives: Byte read FLives write FLives;
390 property eArmor: Integer read FArmor write FArmor;
391 property eAir: Integer read FAir write FAir;
392 property eJetFuel: Integer read FJetFuel write FJetFuel;
393 property eFrags: Integer read FFrags write FFrags;
394 property eDeath: Integer read FDeath write FDeath;
395 property eKills: Integer read FKills write FKills;
396 property eCurrWeap: Byte read FCurrWeap write FCurrWeap;
397 property eMonsterKills: Integer read FMonsterKills write FMonsterKills;
398 property eSecrets: Integer read FSecrets write FSecrets;
399 property eGodMode: Boolean read FGodMode write FGodMode;
400 property eNoTarget: Boolean read FNoTarget write FNoTarget;
401 property eNoReload: Boolean read FNoReload write FNoReload;
402 property eAlive: Boolean read FAlive write FAlive;
403 property eFlag: Byte read FFlag;
404 property eTeam: Byte read FTeam write FTeam;
405 property eDirection: TDirection read FDirection;
406 property eGameX: Integer read FObj.X write FObj.X;
407 property eGameY: Integer read FObj.Y write FObj.Y;
408 property eGameVelX: Integer read FObj.Vel.X write FObj.Vel.X;
409 property eGameVelY: Integer read FObj.Vel.Y write FObj.Vel.Y;
410 property eGameAccelX: Integer read FObj.Accel.X write FObj.Accel.X;
411 property eGameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y;
412 property eIncCam: Integer read FIncCam write FIncCam;
413 property eUID: Word read FUID;
414 property eJustTeleported: Boolean read FJustTeleported;
415 property eNetTime: LongWord read FNetTime;
417 // set this before assigning something to `eDamage`
418 property eDamageType: Integer read mEDamageType write mEDamageType;
419 property eDamage: Integer write doDamage;
420 end;
422 TDifficult = record
423 public
424 DiagFire: Byte;
425 InvisFire: Byte;
426 DiagPrecision: Byte;
427 FlyPrecision: Byte;
428 Cover: Byte;
429 CloseJump: Byte;
430 WeaponPrior: packed array [WP_FIRST..WP_LAST] of Byte;
431 CloseWeaponPrior: packed array [WP_FIRST..WP_LAST] of Byte;
432 //SafeWeaponPrior: Array [WP_FIRST..WP_LAST] of Byte;
434 public
435 procedure save (st: TStream);
436 procedure load (st: TStream);
437 end;
439 TAIFlag = record
440 Name: String;
441 Value: String;
442 end;
444 TBot = class(TPlayer)
445 private
446 FSelectedWeapon: Byte;
447 FTargetUID: Word;
448 FLastVisible: DWORD;
449 FAIFlags: Array of TAIFlag;
450 FDifficult: TDifficult;
452 function GetRnd(a: Byte): Boolean;
453 function GetInterval(a: Byte; radius: SmallInt): SmallInt;
454 function RunDirection(): TDirection;
455 function FullInStep(XInc, YInc: Integer): Boolean;
456 //function NeedItem(Item: Byte): Byte;
457 procedure SelectWeapon(Dist: Integer);
458 procedure SetAIFlag(aName, fValue: String20);
459 function GetAIFlag(aName: String20): String20;
460 procedure RemoveAIFlag(aName: String20);
461 function Healthy(): Byte;
462 procedure UpdateMove();
463 procedure UpdateCombat();
464 function KeyPressed(Key: Word): Boolean;
465 procedure ReleaseKey(Key: Byte);
466 function TargetOnScreen(TX, TY: Integer): Boolean;
467 procedure OnDamage(Angle: SmallInt); override;
469 public
470 procedure Respawn(Silent: Boolean; Force: Boolean = False); override;
471 constructor Create(); override;
472 destructor Destroy(); override;
473 procedure Draw(); override;
474 function PickItem(ItemType: Byte; force: Boolean; var remove: Boolean): Boolean; override;
475 function Heal(value: Word; Soft: Boolean): Boolean; override;
476 procedure Update(); override;
477 procedure SaveState (st: TStream); override;
478 procedure LoadState (st: TStream); override;
479 end;
481 PGib = ^TGib;
482 TGib = record
483 alive: Boolean;
484 ID: DWORD;
485 MaskID: DWORD;
486 RAngle: Integer;
487 Color: TRGB;
488 Obj: TObj;
490 procedure getMapBox (out x, y, w, h: Integer); inline;
491 procedure moveBy (dx, dy: Integer); inline;
493 procedure positionChanged (); inline; //WARNING! call this after entity position was changed, or coldet will not work right!
494 end;
497 PShell = ^TShell;
498 TShell = record
499 SpriteID: DWORD;
500 alive: Boolean;
501 SType: Byte;
502 RAngle: Integer;
503 Timeout: Cardinal;
504 CX, CY: Integer;
505 Obj: TObj;
507 procedure getMapBox (out x, y, w, h: Integer); inline;
508 procedure moveBy (dx, dy: Integer); inline;
510 procedure positionChanged (); inline; //WARNING! call this after entity position was changed, or coldet will not work right!
511 end;
513 TCorpse = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
514 private
515 FModelName: String;
516 FMess: Boolean;
517 FState: Byte;
518 FDamage: Byte;
519 FColor: TRGB;
520 FObj: TObj;
521 FPlayerUID: Word;
522 FAnimation: TAnimation;
523 FAnimationMask: TAnimation;
525 public
526 constructor Create(X, Y: Integer; ModelName: String; aMess: Boolean);
527 destructor Destroy(); override;
528 procedure Damage(Value: Word; vx, vy: Integer);
529 procedure Update();
530 procedure Draw();
531 procedure SaveState (st: TStream);
532 procedure LoadState (st: TStream);
534 procedure getMapBox (out x, y, w, h: Integer); inline;
535 procedure moveBy (dx, dy: Integer); inline;
537 procedure positionChanged (); inline; //WARNING! call this after entity position was changed, or coldet will not work right!
539 function ObjPtr (): PObj; inline;
541 property Obj: TObj read FObj; // copies object
542 property State: Byte read FState;
543 property Mess: Boolean read FMess;
544 end;
546 TTeamStat = Array [TEAM_RED..TEAM_BLUE] of
547 record
548 Goals: SmallInt;
549 end;
551 var
552 gPlayers: Array of TPlayer;
553 gCorpses: Array of TCorpse;
554 gGibs: Array of TGib;
555 gShells: Array of TShell;
556 gTeamStat: TTeamStat;
557 gFly: Boolean = False;
558 gAimLine: Boolean = False;
559 gChatBubble: Byte = 0;
560 gNumBots: Word = 0;
561 gLMSPID1: Word = 0;
562 gLMSPID2: Word = 0;
563 MAX_RUNVEL: Integer = 8;
564 VEL_JUMP: Integer = 10;
565 SHELL_TIMEOUT: Cardinal = 60000;
567 function Lerp(X, Y, Factor: Integer): Integer;
569 procedure g_Gibs_SetMax(Count: Word);
570 function g_Gibs_GetMax(): Word;
571 procedure g_Corpses_SetMax(Count: Word);
572 function g_Corpses_GetMax(): Word;
573 procedure g_Shells_SetMax(Count: Word);
574 function g_Shells_GetMax(): Word;
576 procedure g_Player_Init();
577 procedure g_Player_Free();
578 function g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boolean): Word;
579 function g_Player_CreateFromState (st: TStream): Word;
580 procedure g_Player_Remove(UID: Word);
581 procedure g_Player_ResetTeams();
582 procedure g_Player_UpdateAll();
583 procedure g_Player_DrawAll();
584 procedure g_Player_DrawDebug(p: TPlayer);
585 procedure g_Player_DrawHealth();
586 procedure g_Player_RememberAll();
587 procedure g_Player_ResetAll(Force, Silent: Boolean);
588 function g_Player_Get(UID: Word): TPlayer;
589 function g_Player_GetCount(): Byte;
590 function g_Player_GetStats(): TPlayerStatArray;
591 function g_Player_ValidName(Name: String): Boolean;
592 procedure g_Player_CreateCorpse(Player: TPlayer);
593 procedure g_Player_CreateGibs(fX, fY: Integer; ModelName: String; fColor: TRGB);
594 procedure g_Player_CreateShell(fX, fY, dX, dY: Integer; T: Byte);
595 procedure g_Player_UpdatePhysicalObjects();
596 procedure g_Player_DrawCorpses();
597 procedure g_Player_DrawShells();
598 procedure g_Player_RemoveAllCorpses();
599 procedure g_Player_Corpses_SaveState (st: TStream);
600 procedure g_Player_Corpses_LoadState (st: TStream);
601 procedure g_Bot_Add(Team, Difficult: Byte);
602 procedure g_Bot_AddList(Team: Byte; lname: ShortString; num: Integer = -1);
603 procedure g_Bot_MixNames();
604 procedure g_Bot_RemoveAll();
606 implementation
608 uses
609 {$INCLUDE ../nogl/noGLuses.inc}
610 {$IFDEF ENABLE_HOLMES}
611 g_holmes,
612 {$ENDIF}
613 e_log, g_map, g_items, g_console, g_gfx, Math,
614 g_options, g_triggers, g_menu, g_game, g_grid,
615 wadreader, g_main, g_monsters, CONFIG, g_language,
616 g_net, g_netmsg, g_window,
617 utils, xstreams;
619 const PLR_SAVE_VERSION = 0;
621 type
622 TBotProfile = record
623 name: ShortString;
624 model: ShortString;
625 team: Byte;
626 color: TRGB;
627 diag_fire: Byte;
628 invis_fire: Byte;
629 diag_precision: Byte;
630 fly_precision: Byte;
631 cover: Byte;
632 close_jump: Byte;
633 w_prior1: Array [WP_FIRST..WP_LAST] of Byte;
634 w_prior2: Array [WP_FIRST..WP_LAST] of Byte;
635 w_prior3: Array [WP_FIRST..WP_LAST] of Byte;
636 end;
638 const
639 TIME_RESPAWN1 = 1500;
640 TIME_RESPAWN2 = 2000;
641 TIME_RESPAWN3 = 3000;
642 AIR_DEF = 360;
643 AIR_MAX = 1091;
644 JET_MAX = 540; // ~30 sec
645 PLAYER_SUIT_TIME = 30000;
646 PLAYER_INVUL_TIME = 30000;
647 PLAYER_INVIS_TIME = 35000;
648 FRAG_COMBO_TIME = 3000;
649 VEL_SW = 4;
650 VEL_FLY = 6;
651 ANGLE_RIGHTUP = 55;
652 ANGLE_RIGHTDOWN = -35;
653 ANGLE_LEFTUP = 125;
654 ANGLE_LEFTDOWN = -145;
655 PLAYER_HEADRECT: TRectWH = (X:24; Y:12; Width:20; Height:12);
656 WEAPONPOINT: Array [TDirection] of TDFPoint = ((X:16; Y:32), (X:47; Y:32));
657 BOT_MAXJUMP = 84;
658 BOT_LONGDIST = 300;
659 BOT_UNSAFEDIST = 128;
660 TEAMCOLOR: Array [TEAM_RED..TEAM_BLUE] of TRGB = ((R:255; G:0; B:0),
661 (R:0; G:0; B:255));
662 DIFFICULT_EASY: TDifficult = (DiagFire: 32; InvisFire: 32; DiagPrecision: 32;
663 FlyPrecision: 32; Cover: 32; CloseJump: 32;
664 WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0));
665 DIFFICULT_MEDIUM: TDifficult = (DiagFire: 127; InvisFire: 127; DiagPrecision: 127;
666 FlyPrecision: 127; Cover: 127; CloseJump: 127;
667 WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0));
668 DIFFICULT_HARD: TDifficult = (DiagFire: 255; InvisFire: 255; DiagPrecision: 255;
669 FlyPrecision: 255; Cover: 255; CloseJump: 255;
670 WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0));
671 WEAPON_PRIOR1: Array [WP_FIRST..WP_LAST] of Byte =
672 (WEAPON_FLAMETHROWER, WEAPON_SUPERPULEMET,
673 WEAPON_SHOTGUN2, WEAPON_SHOTGUN1,
674 WEAPON_CHAINGUN, WEAPON_PLASMA, WEAPON_ROCKETLAUNCHER,
675 WEAPON_BFG, WEAPON_PISTOL, WEAPON_SAW, WEAPON_KASTET);
676 WEAPON_PRIOR2: Array [WP_FIRST..WP_LAST] of Byte =
677 (WEAPON_FLAMETHROWER, WEAPON_SUPERPULEMET,
678 WEAPON_BFG, WEAPON_ROCKETLAUNCHER,
679 WEAPON_SHOTGUN2, WEAPON_PLASMA, WEAPON_SHOTGUN1,
680 WEAPON_CHAINGUN, WEAPON_PISTOL, WEAPON_SAW, WEAPON_KASTET);
681 //WEAPON_PRIOR3: Array [WP_FIRST..WP_LAST] of Byte =
682 // (WEAPON_FLAMETHROWER, WEAPON_SUPERPULEMET,
683 // WEAPON_BFG, WEAPON_PLASMA, WEAPON_SHOTGUN2,
684 // WEAPON_CHAINGUN, WEAPON_SHOTGUN1, WEAPON_SAW,
685 // WEAPON_ROCKETLAUNCHER, WEAPON_PISTOL, WEAPON_KASTET);
686 WEAPON_RELOAD: Array [WP_FIRST..WP_LAST] of Byte =
687 (5, 2, 6, 18, 36, 2, 12, 2, 14, 2, 2);
689 PLAYER_SIGNATURE = $52594C50; // 'PLYR'
690 CORPSE_SIGNATURE = $50524F43; // 'CORP'
692 BOTNAMES_FILENAME = 'botnames.txt';
693 BOTLIST_FILENAME = 'botlist.txt';
695 var
696 MaxGibs: Word = 150;
697 MaxCorpses: Word = 20;
698 MaxShells: Word = 300;
699 CurrentGib: Integer = 0;
700 CurrentShell: Integer = 0;
701 BotNames: Array of String;
702 BotList: Array of TBotProfile;
705 function Lerp(X, Y, Factor: Integer): Integer;
706 begin
707 Result := X + ((Y - X) div Factor);
708 end;
710 function SameTeam(UID1, UID2: Word): Boolean;
711 begin
712 Result := False;
714 if (UID1 > UID_MAX_PLAYER) or (UID1 <= UID_MAX_GAME) or
715 (UID2 > UID_MAX_PLAYER) or (UID2 <= UID_MAX_GAME) then Exit;
717 if (g_Player_Get(UID1) = nil) or (g_Player_Get(UID2) = nil) then Exit;
719 if ((g_Player_Get(UID1).Team = TEAM_NONE) or
720 (g_Player_Get(UID2).Team = TEAM_NONE)) then Exit;
722 Result := g_Player_Get(UID1).FTeam = g_Player_Get(UID2).FTeam;
723 end;
725 procedure g_Gibs_SetMax(Count: Word);
726 begin
727 MaxGibs := Count;
728 SetLength(gGibs, Count);
730 if CurrentGib >= Count then
731 CurrentGib := 0;
732 end;
734 function g_Gibs_GetMax(): Word;
735 begin
736 Result := MaxGibs;
737 end;
739 procedure g_Shells_SetMax(Count: Word);
740 begin
741 MaxShells := Count;
742 SetLength(gShells, Count);
744 if CurrentShell >= Count then
745 CurrentShell := 0;
746 end;
748 function g_Shells_GetMax(): Word;
749 begin
750 Result := MaxShells;
751 end;
754 procedure g_Corpses_SetMax(Count: Word);
755 begin
756 MaxCorpses := Count;
757 SetLength(gCorpses, Count);
758 end;
760 function g_Corpses_GetMax(): Word;
761 begin
762 Result := MaxCorpses;
763 end;
765 function g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boolean): Word;
766 var
767 a: Integer;
768 ok: Boolean;
769 begin
770 Result := 0;
772 ok := False;
773 a := 0;
775 // Åñòü ëè ìåñòî â gPlayers:
776 if gPlayers <> nil then
777 for a := 0 to High(gPlayers) do
778 if gPlayers[a] = nil then
779 begin
780 ok := True;
781 Break;
782 end;
784 // Íåò ìåñòà - ðàñøèðÿåì gPlayers:
785 if not ok then
786 begin
787 SetLength(gPlayers, Length(gPlayers)+1);
788 a := High(gPlayers);
789 end;
791 // Ñîçäàåì îáúåêò èãðîêà:
792 if Bot then
793 gPlayers[a] := TBot.Create()
794 else
795 gPlayers[a] := TPlayer.Create();
798 gPlayers[a].FActualModelName := ModelName;
799 gPlayers[a].SetModel(ModelName);
801 // Íåò ìîäåëè - ñîçäàíèå íå âîçìîæíî:
802 if gPlayers[a].FModel = nil then
803 begin
804 gPlayers[a].Free();
805 gPlayers[a] := nil;
806 g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], [ModelName]));
807 Exit;
808 end;
810 if not (Team in [TEAM_RED, TEAM_BLUE]) then
811 if Random(2) = 0 then
812 Team := TEAM_RED
813 else
814 Team := TEAM_BLUE;
815 gPlayers[a].FPreferredTeam := Team;
817 case gGameSettings.GameMode of
818 GM_DM: gPlayers[a].FTeam := TEAM_NONE;
819 GM_TDM,
820 GM_CTF: gPlayers[a].FTeam := gPlayers[a].FPreferredTeam;
821 GM_SINGLE,
822 GM_COOP: gPlayers[a].FTeam := TEAM_COOP;
823 end;
825 // Åñëè êîìàíäíàÿ èãðà - êðàñèì ìîäåëü â öâåò êîìàíäû:
826 gPlayers[a].FColor := Color;
827 if gPlayers[a].FTeam in [TEAM_RED, TEAM_BLUE] then
828 gPlayers[a].FModel.Color := TEAMCOLOR[gPlayers[a].FTeam]
829 else
830 gPlayers[a].FModel.Color := Color;
832 gPlayers[a].FUID := g_CreateUID(UID_PLAYER);
833 gPlayers[a].FAlive := False;
835 Result := gPlayers[a].FUID;
836 end;
838 function g_Player_CreateFromState (st: TStream): Word;
839 var
840 a, i: Integer;
841 ok, Bot: Boolean;
842 b: Byte;
843 begin
844 result := 0;
845 if (st = nil) then exit; //???
847 // Ñèãíàòóðà èãðîêà
848 if not utils.checkSign(st, 'PLYR') then raise XStreamError.Create('invalid player signature');
849 if (utils.readByte(st) <> PLR_SAVE_VERSION) then raise XStreamError.Create('invalid player version');
851 // Áîò èëè ÷åëîâåê:
852 Bot := utils.readBool(st);
854 ok := false;
855 a := 0;
857 // Åñòü ëè ìåñòî â gPlayers:
858 for a := 0 to High(gPlayers) do if (gPlayers[a] = nil) then begin ok := true; break; end;
860 // Íåò ìåñòà - ðàñøèðÿåì gPlayers
861 if not ok then
862 begin
863 SetLength(gPlayers, Length(gPlayers)+1);
864 a := High(gPlayers);
865 end;
867 // Ñîçäàåì îáúåêò èãðîêà
868 if Bot then
869 gPlayers[a] := TBot.Create()
870 else
871 gPlayers[a] := TPlayer.Create();
872 gPlayers[a].FIamBot := Bot;
873 gPlayers[a].FPhysics := True;
875 // UID èãðîêà
876 gPlayers[a].FUID := utils.readWord(st);
877 // Èìÿ èãðîêà
878 gPlayers[a].FName := utils.readStr(st);
879 // Êîìàíäà
880 gPlayers[a].FTeam := utils.readByte(st);
881 gPlayers[a].FPreferredTeam := gPlayers[a].FTeam;
882 // Æèâ ëè
883 gPlayers[a].FAlive := utils.readBool(st);
884 // Èçðàñõîäîâàë ëè âñå æèçíè
885 gPlayers[a].FNoRespawn := utils.readBool(st);
886 // Íàïðàâëåíèå
887 b := utils.readByte(st);
888 if b = 1 then gPlayers[a].FDirection := TDirection.D_LEFT else gPlayers[a].FDirection := TDirection.D_RIGHT; // b = 2
889 // Çäîðîâüå
890 gPlayers[a].FHealth := utils.readLongInt(st);
891 // Æèçíè
892 gPlayers[a].FLives := utils.readByte(st);
893 // Áðîíÿ
894 gPlayers[a].FArmor := utils.readLongInt(st);
895 // Çàïàñ âîçäóõà
896 gPlayers[a].FAir := utils.readLongInt(st);
897 // Çàïàñ ãîðþ÷åãî
898 gPlayers[a].FJetFuel := utils.readLongInt(st);
899 // Áîëü
900 gPlayers[a].FPain := utils.readLongInt(st);
901 // Óáèë
902 gPlayers[a].FKills := utils.readLongInt(st);
903 // Óáèë ìîíñòðîâ
904 gPlayers[a].FMonsterKills := utils.readLongInt(st);
905 // Ôðàãîâ
906 gPlayers[a].FFrags := utils.readLongInt(st);
907 // Ôðàãîâ ïîäðÿä
908 gPlayers[a].FFragCombo := utils.readByte(st);
909 // Âðåìÿ ïîñëåäíåãî ôðàãà
910 gPlayers[a].FLastFrag := utils.readLongWord(st);
911 // Ñìåðòåé
912 gPlayers[a].FDeath := utils.readLongInt(st);
913 // Êàêîé ôëàã íåñåò
914 gPlayers[a].FFlag := utils.readByte(st);
915 // Íàøåë ñåêðåòîâ
916 gPlayers[a].FSecrets := utils.readLongInt(st);
917 // Òåêóùåå îðóæèå
918 gPlayers[a].FCurrWeap := utils.readByte(st);
919 //gPlayers[a].FNetForceWeap := gPlayers[a].FCurrWeap;
920 // Ñëåäóþùåå æåëàåìîå îðóæèå
921 gPlayers[a].FNextWeap := utils.readWord(st);
922 // ...è ïàóçà
923 gPlayers[a].FNextWeapDelay := utils.readByte(st);
924 // Âðåìÿ çàðÿäêè BFG
925 gPlayers[a].FBFGFireCounter := utils.readSmallInt(st);
926 // Áóôåð óðîíà
927 gPlayers[a].FDamageBuffer := utils.readLongInt(st);
928 // Ïîñëåäíèé óäàðèâøèé
929 gPlayers[a].FLastSpawnerUID := utils.readWord(st);
930 // Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà
931 gPlayers[a].FLastHit := utils.readByte(st);
932 // Îáúåêò èãðîêà:
933 Obj_LoadState(@gPlayers[a].FObj, st);
934 // Òåêóùåå êîëè÷åñòâî ïàòðîíîâ
935 for i := A_BULLETS to A_HIGH do gPlayers[a].FAmmo[i] := utils.readWord(st);
936 // Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ
937 for i := A_BULLETS to A_HIGH do gPlayers[a].FMaxAmmo[i] := utils.readWord(st);
938 // Íàëè÷èå îðóæèÿ
939 for i := WP_FIRST to WP_LAST do gPlayers[a].FWeapon[i] := utils.readBool(st);
940 // Âðåìÿ ïåðåçàðÿäêè îðóæèÿ
941 for i := WP_FIRST to WP_LAST do gPlayers[a].FReloading[i] := utils.readWord(st);
942 // Íàëè÷èå ðþêçàêà
943 if utils.readBool(st) then Include(gPlayers[a].FRulez, R_ITEM_BACKPACK);
944 // Íàëè÷èå êðàñíîãî êëþ÷à
945 if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_RED);
946 // Íàëè÷èå çåëåíîãî êëþ÷à
947 if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_GREEN);
948 // Íàëè÷èå ñèíåãî êëþ÷à
949 if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_BLUE);
950 // Íàëè÷èå áåðñåðêà
951 if utils.readBool(st) then Include(gPlayers[a].FRulez, R_BERSERK);
952 // Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ
953 for i := MR_SUIT to MR_MAX do gPlayers[a].FMegaRulez[i] := utils.readLongWord(st);
954 // Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà
955 for i := T_RESPAWN to T_FLAGCAP do gPlayers[a].FTime[i] := utils.readLongWord(st);
957 // Íàçâàíèå ìîäåëè:
958 gPlayers[a].FActualModelName := utils.readStr(st);
959 // Öâåò ìîäåëè
960 gPlayers[a].FColor.R := utils.readByte(st);
961 gPlayers[a].FColor.G := utils.readByte(st);
962 gPlayers[a].FColor.B := utils.readByte(st);
963 // Îáíîâëÿåì ìîäåëü èãðîêà
964 gPlayers[a].SetModel(gPlayers[a].FActualModelName);
966 // Íåò ìîäåëè - ñîçäàíèå íåâîçìîæíî
967 if (gPlayers[a].FModel = nil) then
968 begin
969 gPlayers[a].Free();
970 gPlayers[a] := nil;
971 g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], [gPlayers[a].FActualModelName]));
972 exit;
973 end;
975 // Åñëè êîìàíäíàÿ èãðà - êðàñèì ìîäåëü â öâåò êîìàíäû
976 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
977 gPlayers[a].FModel.Color := TEAMCOLOR[gPlayers[a].FTeam]
978 else
979 gPlayers[a].FModel.Color := gPlayers[a].FColor;
981 result := gPlayers[a].FUID;
982 end;
985 procedure g_Player_ResetTeams();
986 var
987 a: Integer;
988 begin
989 if g_Game_IsClient then
990 Exit;
991 if gPlayers = nil then
992 Exit;
993 for a := Low(gPlayers) to High(gPlayers) do
994 if gPlayers[a] <> nil then
995 case gGameSettings.GameMode of
996 GM_DM:
997 gPlayers[a].ChangeTeam(TEAM_NONE);
998 GM_TDM, GM_CTF:
999 if not (gPlayers[a].Team in [TEAM_RED, TEAM_BLUE]) then
1000 if gPlayers[a].FPreferredTeam in [TEAM_RED, TEAM_BLUE] then
1001 gPlayers[a].ChangeTeam(gPlayers[a].FPreferredTeam)
1002 else
1003 if a mod 2 = 0 then
1004 gPlayers[a].ChangeTeam(TEAM_RED)
1005 else
1006 gPlayers[a].ChangeTeam(TEAM_BLUE);
1007 GM_SINGLE,
1008 GM_COOP:
1009 gPlayers[a].ChangeTeam(TEAM_COOP);
1010 end;
1011 end;
1013 procedure g_Bot_Add(Team, Difficult: Byte);
1014 var
1015 m: SSArray;
1016 _name, _model: String;
1017 a, tr, tb: Integer;
1018 begin
1019 if not g_Game_IsServer then Exit;
1021 // Ñïèñîê íàçâàíèé ìîäåëåé:
1022 m := g_PlayerModel_GetNames();
1023 if m = nil then
1024 Exit;
1026 // Êîìàíäà:
1027 if (gGameSettings.GameType = GT_SINGLE) or (gGameSettings.GameMode = GM_COOP) then
1028 Team := TEAM_COOP // COOP
1029 else
1030 if gGameSettings.GameMode = GM_DM then
1031 Team := TEAM_NONE // DM
1032 else
1033 if Team = TEAM_NONE then // CTF / TDM
1034 begin
1035 // Àâòîáàëàíñ êîìàíä:
1036 tr := 0;
1037 tb := 0;
1039 for a := 0 to High(gPlayers) do
1040 if gPlayers[a] <> nil then
1041 begin
1042 if gPlayers[a].Team = TEAM_RED then
1043 Inc(tr)
1044 else
1045 if gPlayers[a].Team = TEAM_BLUE then
1046 Inc(tb);
1047 end;
1049 if tr > tb then
1050 Team := TEAM_BLUE
1051 else
1052 if tb > tr then
1053 Team := TEAM_RED
1054 else // tr = tb
1055 if Random(2) = 0 then
1056 Team := TEAM_RED
1057 else
1058 Team := TEAM_BLUE;
1059 end;
1061 // Âûáèðàåì áîòó èìÿ:
1062 _name := '';
1063 if BotNames <> nil then
1064 for a := 0 to High(BotNames) do
1065 if g_Player_ValidName(BotNames[a]) then
1066 begin
1067 _name := BotNames[a];
1068 Break;
1069 end;
1071 // Èìåíè íåò, çàäàåì ñëó÷àéíîå:
1072 if _name = '' then
1073 repeat
1074 _name := Format('DFBOT%.2d', [Random(100)]);
1075 until g_Player_ValidName(_name);
1077 // Âûáèðàåì ñëó÷àéíóþ ìîäåëü:
1078 _model := m[Random(Length(m))];
1080 // Ñîçäàåì áîòà:
1081 with g_Player_Get(g_Player_Create(_model,
1082 _RGB(Min(Random(9)*32, 255),
1083 Min(Random(9)*32, 255),
1084 Min(Random(9)*32, 255)),
1085 Team, True)) as TBot do
1086 begin
1087 Name := _name;
1089 case Difficult of
1090 1: FDifficult := DIFFICULT_EASY;
1091 2: FDifficult := DIFFICULT_MEDIUM;
1092 else FDifficult := DIFFICULT_HARD;
1093 end;
1095 for a := WP_FIRST to WP_LAST do
1096 begin
1097 FDifficult.WeaponPrior[a] := WEAPON_PRIOR1[a];
1098 FDifficult.CloseWeaponPrior[a] := WEAPON_PRIOR2[a];
1099 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
1100 end;
1102 g_Console_Add(Format(_lc[I_PLAYER_JOIN], [Name]), True);
1104 if g_Game_IsNet then MH_SEND_PlayerCreate(UID);
1105 if g_Game_IsServer and (gGameSettings.MaxLives > 0) then
1106 Spectate();
1107 end;
1108 end;
1110 procedure g_Bot_AddList(Team: Byte; lName: ShortString; num: Integer = -1);
1111 var
1112 m: SSArray;
1113 _name, _model: String;
1114 a: Integer;
1115 begin
1116 if not g_Game_IsServer then Exit;
1118 // Ñïèñîê íàçâàíèé ìîäåëåé:
1119 m := g_PlayerModel_GetNames();
1120 if m = nil then
1121 Exit;
1123 // Êîìàíäà:
1124 if (gGameSettings.GameType = GT_SINGLE) or (gGameSettings.GameMode = GM_COOP) then
1125 Team := TEAM_COOP // COOP
1126 else
1127 if gGameSettings.GameMode = GM_DM then
1128 Team := TEAM_NONE // DM
1129 else
1130 if Team = TEAM_NONE then
1131 Team := BotList[num].team; // CTF / TDM
1133 // Âûáèðàåì íàñòðîéêè áîòà èç ñïèñêà ïî íîìåðó èëè èìåíè:
1134 lName := AnsiLowerCase(lName);
1135 if (num < 0) or (num > Length(BotList)-1) then
1136 num := -1;
1137 if (num = -1) and (lName <> '') and (BotList <> nil) then
1138 for a := 0 to High(BotList) do
1139 if AnsiLowerCase(BotList[a].name) = lName then
1140 begin
1141 num := a;
1142 Break;
1143 end;
1144 if num = -1 then
1145 Exit;
1147 // Èìÿ áîòà:
1148 _name := BotList[num].name;
1149 // Çàíÿòî - âûáèðàåì ñëó÷àéíîå:
1150 if not g_Player_ValidName(_name) then
1151 repeat
1152 _name := Format('DFBOT%.2d', [Random(100)]);
1153 until g_Player_ValidName(_name);
1155 // Ìîäåëü:
1156 _model := BotList[num].model;
1157 // Íåò òàêîé - âûáèðàåì ñëó÷àéíóþ:
1158 if not InSArray(_model, m) then
1159 _model := m[Random(Length(m))];
1161 // Ñîçäàåì áîòà:
1162 with g_Player_Get(g_Player_Create(_model, BotList[num].color, Team, True)) as TBot do
1163 begin
1164 Name := _name;
1166 FDifficult.DiagFire := BotList[num].diag_fire;
1167 FDifficult.InvisFire := BotList[num].invis_fire;
1168 FDifficult.DiagPrecision := BotList[num].diag_precision;
1169 FDifficult.FlyPrecision := BotList[num].fly_precision;
1170 FDifficult.Cover := BotList[num].cover;
1171 FDifficult.CloseJump := BotList[num].close_jump;
1173 for a := WP_FIRST to WP_LAST do
1174 begin
1175 FDifficult.WeaponPrior[a] := BotList[num].w_prior1[a];
1176 FDifficult.CloseWeaponPrior[a] := BotList[num].w_prior2[a];
1177 //FDifficult.SafeWeaponPrior[a] := BotList[num].w_prior3[a];
1178 end;
1180 g_Console_Add(Format(_lc[I_PLAYER_JOIN], [Name]), True);
1182 if g_Game_IsNet then MH_SEND_PlayerCreate(UID);
1183 end;
1184 end;
1186 procedure g_Bot_RemoveAll();
1187 var
1188 a: Integer;
1189 begin
1190 if not g_Game_IsServer then Exit;
1191 if gPlayers = nil then Exit;
1193 for a := 0 to High(gPlayers) do
1194 if gPlayers[a] <> nil then
1195 if gPlayers[a] is TBot then
1196 begin
1197 gPlayers[a].Lives := 0;
1198 gPlayers[a].Kill(K_SIMPLEKILL, 0, HIT_DISCON);
1199 g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [gPlayers[a].Name]), True);
1200 g_Player_Remove(gPlayers[a].FUID);
1201 end;
1203 g_Bot_MixNames();
1204 end;
1206 procedure g_Bot_MixNames();
1207 var
1208 s: String;
1209 a, b: Integer;
1210 begin
1211 if BotNames <> nil then
1212 for a := 0 to High(BotNames) do
1213 begin
1214 b := Random(Length(BotNames));
1215 s := BotNames[a];
1216 Botnames[a] := BotNames[b];
1217 BotNames[b] := s;
1218 end;
1219 end;
1221 procedure g_Player_Remove(UID: Word);
1222 var
1223 i: Integer;
1224 begin
1225 if gPlayers = nil then Exit;
1227 if g_Game_IsServer and g_Game_IsNet then
1228 MH_SEND_PlayerDelete(UID);
1230 for i := 0 to High(gPlayers) do
1231 if gPlayers[i] <> nil then
1232 if gPlayers[i].FUID = UID then
1233 begin
1234 if gPlayers[i] is TPlayer then
1235 TPlayer(gPlayers[i]).Free()
1236 else
1237 TBot(gPlayers[i]).Free();
1238 gPlayers[i] := nil;
1239 Exit;
1240 end;
1241 end;
1243 procedure g_Player_Init();
1244 var
1245 F: TextFile;
1246 s: String;
1247 a, b: Integer;
1248 config: TConfig;
1249 sa: SSArray;
1250 begin
1251 BotNames := nil;
1253 if not FileExists(DataDir + BOTNAMES_FILENAME) then
1254 Exit;
1256 // ×èòàåì âîçìîæíûå èìåíà áîòîâ èç ôàéëà:
1257 AssignFile(F, DataDir + BOTNAMES_FILENAME);
1258 Reset(F);
1260 while not EOF(F) do
1261 begin
1262 ReadLn(F, s);
1264 s := Trim(s);
1265 if s = '' then
1266 Continue;
1268 SetLength(BotNames, Length(BotNames)+1);
1269 BotNames[High(BotNames)] := s;
1270 end;
1272 CloseFile(F);
1274 // Ïåðåìåøèâàåì èõ:
1275 g_Bot_MixNames();
1277 // ×èòàåì ôàéë ñ ïàðàìåòðàìè áîòîâ:
1278 config := TConfig.CreateFile(DataDir + BOTLIST_FILENAME);
1279 BotList := nil;
1280 a := 0;
1282 while config.SectionExists(IntToStr(a)) do
1283 begin
1284 SetLength(BotList, Length(BotList)+1);
1286 with BotList[High(BotList)] do
1287 begin
1288 // Èìÿ áîòà:
1289 name := config.ReadStr(IntToStr(a), 'name', '');
1290 // Ìîäåëü:
1291 model := config.ReadStr(IntToStr(a), 'model', '');
1292 // Êîìàíäà:
1293 if config.ReadStr(IntToStr(a), 'team', 'red') = 'red' then
1294 team := TEAM_RED
1295 else
1296 team := TEAM_BLUE;
1297 // Öâåò ìîäåëè:
1298 sa := parse(config.ReadStr(IntToStr(a), 'color', ''));
1299 color.R := StrToIntDef(sa[0], 0);
1300 color.G := StrToIntDef(sa[1], 0);
1301 color.B := StrToIntDef(sa[2], 0);
1302 // Âåðîÿòíîñòü ñòðåëüáû ïîä óãëîì:
1303 diag_fire := config.ReadInt(IntToStr(a), 'diag_fire', 0);
1304 // Âåðîÿòíîñòü îòâåòíîãî îãíÿ ïî íåâèäèìîìó ñîïåðíèêó:
1305 invis_fire := config.ReadInt(IntToStr(a), 'invis_fire', 0);
1306 // Òî÷íîñòü ñòðåëüáû ïîä óãëîì:
1307 diag_precision := config.ReadInt(IntToStr(a), 'diag_precision', 0);
1308 // Òî÷íîñòü ñòðåëüáû â ïîëåòå:
1309 fly_precision := config.ReadInt(IntToStr(a), 'fly_precision', 0);
1310 // Òî÷íîñòü óêëîíåíèÿ îò ñíàðÿäîâ:
1311 cover := config.ReadInt(IntToStr(a), 'cover', 0);
1312 // Âåðîÿòíîñòü ïðûæêà ïðè ïðèáëèæåíèè ñîïåðíèêà:
1313 close_jump := config.ReadInt(IntToStr(a), 'close_jump', 0);
1314 // Ïðèîðèòåòû îðóæèÿ äëÿ äàëüíåãî áîÿ:
1315 sa := parse(config.ReadStr(IntToStr(a), 'w_prior1', ''));
1316 if Length(sa) = 10 then
1317 for b := 0 to 9 do
1318 w_prior1[b] := EnsureRange(StrToInt(sa[b]), 0, 9);
1319 // Ïðèîðèòåòû îðóæèÿ äëÿ áëèæíåãî áîÿ:
1320 sa := parse(config.ReadStr(IntToStr(a), 'w_prior2', ''));
1321 if Length(sa) = 10 then
1322 for b := 0 to 9 do
1323 w_prior2[b] := EnsureRange(StrToInt(sa[b]), 0, 9);
1325 {sa := parse(config.ReadStr(IntToStr(a), 'w_prior3', ''));
1326 if Length(sa) = 10 then
1327 for b := 0 to 9 do
1328 w_prior3[b] := EnsureRange(StrToInt(sa[b]), 0, 9);}
1329 end;
1331 a := a + 1;
1332 end;
1334 config.Free();
1335 end;
1337 procedure g_Player_Free();
1338 var
1339 i: Integer;
1340 begin
1341 if gPlayers <> nil then
1342 begin
1343 for i := 0 to High(gPlayers) do
1344 if gPlayers[i] <> nil then
1345 begin
1346 if gPlayers[i] is TPlayer then
1347 TPlayer(gPlayers[i]).Free()
1348 else
1349 TBot(gPlayers[i]).Free();
1350 gPlayers[i] := nil;
1351 end;
1353 gPlayers := nil;
1354 end;
1356 gPlayer1 := nil;
1357 gPlayer2 := nil;
1358 end;
1360 procedure g_Player_UpdateAll();
1361 var
1362 i: Integer;
1363 begin
1364 if gPlayers = nil then Exit;
1366 //e_WriteLog('***g_Player_UpdateAll: ENTER', MSG_WARNING);
1367 for i := 0 to High(gPlayers) do
1368 begin
1369 if gPlayers[i] <> nil then
1370 begin
1371 if gPlayers[i] is TPlayer then
1372 begin
1373 gPlayers[i].Update();
1374 if (not gPlayers[i].alive) then gPlayers[i].NetForceWeapFIdx := 0; // just in case
1375 //if g_Game_IsClient or not g_Game_IsNet then
1376 begin
1377 gPlayers[i].RealizeCurrentWeapon(); // WARNING! DO NOT MOVE THIS INTO `Update()`!
1378 end;
1379 end
1380 else
1381 begin
1382 // bot updates weapons in `UpdateCombat()`
1383 TBot(gPlayers[i]).Update();
1384 end;
1385 end;
1386 end;
1387 //e_WriteLog('***g_Player_UpdateAll: EXIT', MSG_WARNING);
1388 end;
1390 procedure g_Player_DrawAll();
1391 var
1392 i: Integer;
1393 begin
1394 if gPlayers = nil then Exit;
1396 for i := 0 to High(gPlayers) do
1397 if gPlayers[i] <> nil then
1398 if gPlayers[i] is TPlayer then gPlayers[i].Draw()
1399 else TBot(gPlayers[i]).Draw();
1400 end;
1402 procedure g_Player_DrawDebug(p: TPlayer);
1403 var
1404 fW, fH: Byte;
1405 begin
1406 if p = nil then Exit;
1407 if (@p.FObj) = nil then Exit;
1409 e_TextureFontGetSize(gStdFont, fW, fH);
1411 e_TextureFontPrint(0, 0 , 'Pos X: ' + IntToStr(p.FObj.X), gStdFont);
1412 e_TextureFontPrint(0, fH , 'Pos Y: ' + IntToStr(p.FObj.Y), gStdFont);
1413 e_TextureFontPrint(0, fH * 2, 'Vel X: ' + IntToStr(p.FObj.Vel.X), gStdFont);
1414 e_TextureFontPrint(0, fH * 3, 'Vel Y: ' + IntToStr(p.FObj.Vel.Y), gStdFont);
1415 e_TextureFontPrint(0, fH * 4, 'Acc X: ' + IntToStr(p.FObj.Accel.X), gStdFont);
1416 e_TextureFontPrint(0, fH * 5, 'Acc Y: ' + IntToStr(p.FObj.Accel.Y), gStdFont);
1417 end;
1419 procedure g_Player_DrawHealth();
1420 var
1421 i: Integer;
1422 fW, fH: Byte;
1423 begin
1424 if gPlayers = nil then Exit;
1425 e_TextureFontGetSize(gStdFont, fW, fH);
1427 for i := 0 to High(gPlayers) do
1428 if gPlayers[i] <> nil then
1429 begin
1430 e_TextureFontPrint(gPlayers[i].FObj.X + gPlayers[i].FObj.Rect.X,
1431 gPlayers[i].FObj.Y + gPlayers[i].FObj.Rect.Y + gPlayers[i].FObj.Rect.Height - fH * 2,
1432 IntToStr(gPlayers[i].FHealth), gStdFont);
1433 e_TextureFontPrint(gPlayers[i].FObj.X + gPlayers[i].FObj.Rect.X,
1434 gPlayers[i].FObj.Y + gPlayers[i].FObj.Rect.Y + gPlayers[i].FObj.Rect.Height - fH,
1435 IntToStr(gPlayers[i].FArmor), gStdFont);
1436 end;
1437 end;
1439 function g_Player_Get(UID: Word): TPlayer;
1440 var
1441 a: Integer;
1442 begin
1443 Result := nil;
1445 if gPlayers = nil then
1446 Exit;
1448 for a := 0 to High(gPlayers) do
1449 if gPlayers[a] <> nil then
1450 if gPlayers[a].FUID = UID then
1451 begin
1452 Result := gPlayers[a];
1453 Exit;
1454 end;
1455 end;
1457 function g_Player_GetCount(): Byte;
1458 var
1459 a: Integer;
1460 begin
1461 Result := 0;
1463 if gPlayers = nil then
1464 Exit;
1466 for a := 0 to High(gPlayers) do
1467 if gPlayers[a] <> nil then
1468 Result := Result + 1;
1469 end;
1471 function g_Player_GetStats(): TPlayerStatArray;
1472 var
1473 a: Integer;
1474 begin
1475 Result := nil;
1477 if gPlayers = nil then Exit;
1479 for a := 0 to High(gPlayers) do
1480 if gPlayers[a] <> nil then
1481 begin
1482 SetLength(Result, Length(Result)+1);
1483 with Result[High(Result)] do
1484 begin
1485 Ping := gPlayers[a].FPing;
1486 Loss := gPlayers[a].FLoss;
1487 Name := gPlayers[a].FName;
1488 Team := gPlayers[a].FTeam;
1489 Frags := gPlayers[a].FFrags;
1490 Deaths := gPlayers[a].FDeath;
1491 Kills := gPlayers[a].FKills;
1492 Color := gPlayers[a].FModel.Color;
1493 Lives := gPlayers[a].FLives;
1494 Spectator := gPlayers[a].FSpectator;
1495 end;
1496 end;
1497 end;
1499 procedure g_Player_RememberAll;
1500 var
1501 i: Integer;
1502 begin
1503 for i := Low(gPlayers) to High(gPlayers) do
1504 if (gPlayers[i] <> nil) and gPlayers[i].alive then
1505 gPlayers[i].RememberState;
1506 end;
1508 procedure g_Player_ResetAll(Force, Silent: Boolean);
1509 var
1510 i: Integer;
1511 begin
1512 gTeamStat[TEAM_RED].Goals := 0;
1513 gTeamStat[TEAM_BLUE].Goals := 0;
1515 if gPlayers <> nil then
1516 for i := 0 to High(gPlayers) do
1517 if gPlayers[i] <> nil then
1518 begin
1519 gPlayers[i].Reset(Force);
1521 if gPlayers[i] is TPlayer then
1522 begin
1523 if (not gPlayers[i].FSpectator) or gPlayers[i].FWantsInGame then
1524 gPlayers[i].Respawn(Silent)
1525 else
1526 gPlayers[i].Spectate();
1527 end
1528 else
1529 TBot(gPlayers[i]).Respawn(Silent);
1530 end;
1531 end;
1533 procedure g_Player_CreateCorpse(Player: TPlayer);
1534 var
1535 i: Integer;
1536 find_id: DWORD;
1537 ok: Boolean;
1538 begin
1539 if Player.alive then
1540 Exit;
1542 // Ðàçðûâàåì ñâÿçü ñ ïðåæíèì òðóïîì:
1543 if gCorpses <> nil then
1544 for i := 0 to High(gCorpses) do
1545 if gCorpses[i] <> nil then
1546 if gCorpses[i].FPlayerUID = Player.FUID then
1547 gCorpses[i].FPlayerUID := 0;
1549 if Player.FObj.Y >= gMapInfo.Height+128 then
1550 Exit;
1552 with Player do
1553 begin
1554 if (FHealth >= -50) or (gGibsCount = 0) then
1555 begin
1556 if (gCorpses = nil) or (Length(gCorpses) = 0) then
1557 Exit;
1559 ok := False;
1560 for find_id := 0 to High(gCorpses) do
1561 if gCorpses[find_id] = nil then
1562 begin
1563 ok := True;
1564 Break;
1565 end;
1567 if not ok then
1568 find_id := Random(Length(gCorpses));
1570 gCorpses[find_id] := TCorpse.Create(FObj.X, FObj.Y, FModel.Name, FHealth < -20);
1571 gCorpses[find_id].FColor := FModel.Color;
1572 gCorpses[find_id].FObj.Vel := FObj.Vel;
1573 gCorpses[find_id].FObj.Accel := FObj.Accel;
1574 gCorpses[find_id].FPlayerUID := FUID;
1575 end
1576 else
1577 g_Player_CreateGibs(FObj.X + PLAYER_RECT_CX,
1578 FObj.Y + PLAYER_RECT_CY,
1579 FModel.Name, FModel.Color);
1580 end;
1581 end;
1583 procedure g_Player_CreateShell(fX, fY, dX, dY: Integer; T: Byte);
1584 var
1585 SID: DWORD;
1586 begin
1587 if (gShells = nil) or (Length(gShells) = 0) then
1588 Exit;
1590 with gShells[CurrentShell] do
1591 begin
1592 SpriteID := 0;
1593 g_Obj_Init(@Obj);
1594 Obj.Rect.X := 0;
1595 Obj.Rect.Y := 0;
1596 if T = SHELL_BULLET then
1597 begin
1598 if g_Texture_Get('TEXTURE_SHELL_BULLET', SID) then
1599 SpriteID := SID;
1600 CX := 2;
1601 CY := 1;
1602 Obj.Rect.Width := 4;
1603 Obj.Rect.Height := 2;
1604 end
1605 else
1606 begin
1607 if g_Texture_Get('TEXTURE_SHELL_SHELL', SID) then
1608 SpriteID := SID;
1609 CX := 4;
1610 CY := 2;
1611 Obj.Rect.Width := 7;
1612 Obj.Rect.Height := 3;
1613 end;
1614 SType := T;
1615 alive := True;
1616 Obj.X := fX;
1617 Obj.Y := fY;
1618 g_Obj_Push(@Obj, dX + Random(4)-Random(4), dY-Random(4));
1619 positionChanged(); // this updates spatial accelerators
1620 RAngle := Random(360);
1621 Timeout := gTime + SHELL_TIMEOUT;
1623 if CurrentShell >= High(gShells) then
1624 CurrentShell := 0
1625 else
1626 Inc(CurrentShell);
1627 end;
1628 end;
1630 procedure g_Player_CreateGibs(fX, fY: Integer; ModelName: string; fColor: TRGB);
1631 var
1632 a: Integer;
1633 GibsArray: TGibsArray;
1634 Blood: TModelBlood;
1635 begin
1636 if (gGibs = nil) or (Length(gGibs) = 0) then
1637 Exit;
1638 if not g_PlayerModel_GetGibs(ModelName, GibsArray) then
1639 Exit;
1640 Blood := g_PlayerModel_GetBlood(ModelName);
1642 for a := 0 to High(GibsArray) do
1643 with gGibs[CurrentGib] do
1644 begin
1645 Color := fColor;
1646 ID := GibsArray[a].ID;
1647 MaskID := GibsArray[a].MaskID;
1648 alive := True;
1649 g_Obj_Init(@Obj);
1650 Obj.Rect := GibsArray[a].Rect;
1651 Obj.X := fX-GibsArray[a].Rect.X-(GibsArray[a].Rect.Width div 2);
1652 Obj.Y := fY-GibsArray[a].Rect.Y-(GibsArray[a].Rect.Height div 2);
1653 g_Obj_PushA(@Obj, 25 + Random(10), Random(361));
1654 positionChanged(); // this updates spatial accelerators
1655 RAngle := Random(360);
1657 if gBloodCount > 0 then
1658 g_GFX_Blood(fX, fY, 16*gBloodCount+Random(5*gBloodCount), -16+Random(33), -16+Random(33),
1659 Random(48), Random(48), Blood.R, Blood.G, Blood.B, Blood.Kind);
1661 if CurrentGib >= High(gGibs) then
1662 CurrentGib := 0
1663 else
1664 Inc(CurrentGib);
1665 end;
1666 end;
1668 procedure g_Player_UpdatePhysicalObjects();
1669 var
1670 i: Integer;
1671 vel: TPoint2i;
1672 mr: Word;
1674 procedure ShellSound_Bounce(X, Y: Integer; T: Byte);
1675 var
1676 k: Integer;
1677 begin
1678 k := 1 + Random(2);
1679 if T = SHELL_BULLET then
1680 g_Sound_PlayExAt('SOUND_PLAYER_CASING' + IntToStr(k), X, Y)
1681 else
1682 g_Sound_PlayExAt('SOUND_PLAYER_SHELL' + IntToStr(k), X, Y);
1683 end;
1685 begin
1686 // Êóñêè ìÿñà:
1687 if gGibs <> nil then
1688 for i := 0 to High(gGibs) do
1689 if gGibs[i].alive then
1690 with gGibs[i] do
1691 begin
1692 vel := Obj.Vel;
1693 mr := g_Obj_Move(@Obj, True, False, True);
1694 positionChanged(); // this updates spatial accelerators
1696 if WordBool(mr and MOVE_FALLOUT) then
1697 begin
1698 alive := False;
1699 Continue;
1700 end;
1702 // Îòëåòàåò îò óäàðà î ñòåíó/ïîòîëîê/ïîë:
1703 if WordBool(mr and MOVE_HITWALL) then
1704 Obj.Vel.X := -(vel.X div 2);
1705 if WordBool(mr and (MOVE_HITCEIL or MOVE_HITLAND)) then
1706 Obj.Vel.Y := -(vel.Y div 2);
1708 if (Obj.Vel.X >= 0) then
1709 begin // Clockwise
1710 RAngle := RAngle + Abs(Obj.Vel.X)*6 + Abs(Obj.Vel.Y);
1711 if RAngle >= 360 then
1712 RAngle := RAngle mod 360;
1713 end else begin // Counter-clockwise
1714 RAngle := RAngle - Abs(Obj.Vel.X)*6 - Abs(Obj.Vel.Y);
1715 if RAngle < 0 then
1716 RAngle := (360 - (Abs(RAngle) mod 360)) mod 360;
1717 end;
1719 // Ñîïðîòèâëåíèå âîçäóõà äëÿ êóñêà òðóïà:
1720 if gTime mod (GAME_TICK*3) = 0 then
1721 Obj.Vel.X := z_dec(Obj.Vel.X, 1);
1722 end;
1724 // Òðóïû:
1725 if gCorpses <> nil then
1726 for i := 0 to High(gCorpses) do
1727 if gCorpses[i] <> nil then
1728 if gCorpses[i].State = CORPSE_STATE_REMOVEME then
1729 begin
1730 gCorpses[i].Free();
1731 gCorpses[i] := nil;
1732 end
1733 else
1734 gCorpses[i].Update();
1736 // Ãèëüçû:
1737 if gShells <> nil then
1738 for i := 0 to High(gShells) do
1739 if gShells[i].alive then
1740 with gShells[i] do
1741 begin
1742 vel := Obj.Vel;
1743 mr := g_Obj_Move(@Obj, True, False, True);
1744 positionChanged(); // this updates spatial accelerators
1746 if WordBool(mr and MOVE_FALLOUT) or (gShells[i].Timeout < gTime) then
1747 begin
1748 alive := False;
1749 Continue;
1750 end;
1752 // Îòëåòàåò îò óäàðà î ñòåíó/ïîòîëîê/ïîë:
1753 if WordBool(mr and MOVE_HITWALL) then
1754 begin
1755 Obj.Vel.X := -(vel.X div 2);
1756 if not WordBool(mr and MOVE_INWATER) then
1757 ShellSound_Bounce(Obj.X, Obj.Y, SType);
1758 end;
1759 if WordBool(mr and (MOVE_HITCEIL or MOVE_HITLAND)) then
1760 begin
1761 Obj.Vel.Y := -(vel.Y div 2);
1762 if Obj.Vel.X <> 0 then Obj.Vel.X := Obj.Vel.X div 2;
1763 if (Obj.Vel.X = 0) and (Obj.Vel.Y = 0) then
1764 begin
1765 if RAngle mod 90 <> 0 then
1766 RAngle := (RAngle div 90) * 90;
1767 end
1768 else if not WordBool(mr and MOVE_INWATER) then
1769 ShellSound_Bounce(Obj.X, Obj.Y, SType);
1770 end;
1772 if (Obj.Vel.X >= 0) then
1773 begin // Clockwise
1774 RAngle := RAngle + Abs(Obj.Vel.X)*8 + Abs(Obj.Vel.Y);
1775 if RAngle >= 360 then
1776 RAngle := RAngle mod 360;
1777 end else begin // Counter-clockwise
1778 RAngle := RAngle - Abs(Obj.Vel.X)*8 - Abs(Obj.Vel.Y);
1779 if RAngle < 0 then
1780 RAngle := (360 - (Abs(RAngle) mod 360)) mod 360;
1781 end;
1782 end;
1783 end;
1786 procedure TGib.getMapBox (out x, y, w, h: Integer); inline;
1787 begin
1788 x := Obj.X+Obj.Rect.X;
1789 y := Obj.Y+Obj.Rect.Y;
1790 w := Obj.Rect.Width;
1791 h := Obj.Rect.Height;
1792 end;
1794 procedure TGib.moveBy (dx, dy: Integer); inline;
1795 begin
1796 if (dx <> 0) or (dy <> 0) then
1797 begin
1798 Obj.X += dx;
1799 Obj.Y += dy;
1800 positionChanged();
1801 end;
1802 end;
1805 procedure TShell.getMapBox (out x, y, w, h: Integer); inline;
1806 begin
1807 x := Obj.X;
1808 y := Obj.Y;
1809 w := Obj.Rect.Width;
1810 h := Obj.Rect.Height;
1811 end;
1813 procedure TShell.moveBy (dx, dy: Integer); inline;
1814 begin
1815 if (dx <> 0) or (dy <> 0) then
1816 begin
1817 Obj.X += dx;
1818 Obj.Y += dy;
1819 positionChanged();
1820 end;
1821 end;
1824 procedure TGib.positionChanged (); inline; begin end;
1825 procedure TShell.positionChanged (); inline; begin end;
1828 procedure g_Player_DrawCorpses();
1829 var
1830 i: Integer;
1831 a: TDFPoint;
1832 begin
1833 if gGibs <> nil then
1834 for i := 0 to High(gGibs) do
1835 if gGibs[i].alive then
1836 with gGibs[i] do
1837 begin
1838 if not g_Obj_Collide(sX, sY, sWidth, sHeight, @Obj) then
1839 Continue;
1841 a.X := Obj.Rect.X+(Obj.Rect.Width div 2);
1842 a.y := Obj.Rect.Y+(Obj.Rect.Height div 2);
1844 e_DrawAdv(ID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, TMirrorType.None);
1846 e_Colors := Color;
1847 e_DrawAdv(MaskID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, TMirrorType.None);
1848 e_Colors.R := 255;
1849 e_Colors.G := 255;
1850 e_Colors.B := 255;
1851 end;
1853 if gCorpses <> nil then
1854 for i := 0 to High(gCorpses) do
1855 if gCorpses[i] <> nil then
1856 gCorpses[i].Draw();
1857 end;
1859 procedure g_Player_DrawShells();
1860 var
1861 i: Integer;
1862 a: TDFPoint;
1863 begin
1864 if gShells <> nil then
1865 for i := 0 to High(gShells) do
1866 if gShells[i].alive then
1867 with gShells[i] do
1868 begin
1869 if not g_Obj_Collide(sX, sY, sWidth, sHeight, @Obj) then
1870 Continue;
1872 a.X := CX;
1873 a.Y := CY;
1875 e_DrawAdv(SpriteID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, TMirrorType.None);
1876 end;
1877 end;
1879 procedure g_Player_RemoveAllCorpses();
1880 var
1881 i: Integer;
1882 begin
1883 gGibs := nil;
1884 gShells := nil;
1885 SetLength(gGibs, MaxGibs);
1886 SetLength(gShells, MaxGibs);
1887 CurrentGib := 0;
1888 CurrentShell := 0;
1890 if gCorpses <> nil then
1891 for i := 0 to High(gCorpses) do
1892 gCorpses[i].Free();
1894 gCorpses := nil;
1895 SetLength(gCorpses, MaxCorpses);
1896 end;
1898 procedure g_Player_Corpses_SaveState (st: TStream);
1899 var
1900 count, i: Integer;
1901 begin
1902 // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ òðóïîâ
1903 count := 0;
1904 for i := 0 to High(gCorpses) do if (gCorpses[i] <> nil) then Inc(count);
1906 // Êîëè÷åñòâî òðóïîâ
1907 utils.writeInt(st, LongInt(count));
1909 if (count = 0) then exit;
1911 // Ñîõðàíÿåì òðóïû
1912 for i := 0 to High(gCorpses) do
1913 begin
1914 if gCorpses[i] <> nil then
1915 begin
1916 // Íàçâàíèå ìîäåëè
1917 utils.writeStr(st, gCorpses[i].FModelName);
1918 // Òèï ñìåðòè
1919 utils.writeBool(st, gCorpses[i].Mess);
1920 // Ñîõðàíÿåì äàííûå òðóïà:
1921 gCorpses[i].SaveState(st);
1922 end;
1923 end;
1924 end;
1927 procedure g_Player_Corpses_LoadState (st: TStream);
1928 var
1929 count, i: Integer;
1930 str: String;
1931 b: Boolean;
1932 begin
1933 assert(st <> nil);
1935 g_Player_RemoveAllCorpses();
1937 // Êîëè÷åñòâî òðóïîâ:
1938 count := utils.readLongInt(st);
1939 if (count < 0) or (count > Length(gCorpses)) then raise XStreamError.Create('invalid number of corpses');
1941 if (count = 0) then exit;
1943 // Çàãðóæàåì òðóïû
1944 for i := 0 to count-1 do
1945 begin
1946 // Íàçâàíèå ìîäåëè:
1947 str := utils.readStr(st);
1948 // Òèï ñìåðòè
1949 b := utils.readBool(st);
1950 // Ñîçäàåì òðóï
1951 gCorpses[i] := TCorpse.Create(0, 0, str, b);
1952 // Çàãðóæàåì äàííûå òðóïà
1953 gCorpses[i].LoadState(st);
1954 end;
1955 end;
1958 { T P l a y e r : }
1960 function TPlayer.isValidViewPort (): Boolean; inline; begin result := (viewPortW > 0) and (viewPortH > 0); end;
1962 procedure TPlayer.BFGHit();
1963 begin
1964 g_Weapon_BFGHit(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
1965 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2));
1966 if g_Game_IsServer and g_Game_IsNet then
1967 MH_SEND_Effect(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
1968 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
1969 0, NET_GFX_BFGHIT);
1970 end;
1972 procedure TPlayer.ChangeModel(ModelName: string);
1973 var
1974 locModel: TPlayerModel;
1975 begin
1976 locModel := g_PlayerModel_Get(ModelName);
1977 if locModel = nil then Exit;
1979 FModel.Free();
1980 FModel := locModel;
1981 end;
1983 procedure TPlayer.SetModel(ModelName: string);
1984 var
1985 m: TPlayerModel;
1986 begin
1987 m := g_PlayerModel_Get(ModelName);
1988 if m = nil then
1989 begin
1990 g_SimpleError(Format(_lc[I_GAME_ERROR_MODEL_FALLBACK], [ModelName]));
1991 m := g_PlayerModel_Get('doomer');
1992 if m = nil then
1993 begin
1994 g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], ['doomer']));
1995 Exit;
1996 end;
1997 end;
1999 if FModel <> nil then
2000 FModel.Free();
2002 FModel := m;
2004 if not (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
2005 FModel.Color := FColor
2006 else
2007 FModel.Color := TEAMCOLOR[FTeam];
2008 FModel.SetWeapon(FCurrWeap);
2009 FModel.SetFlag(FFlag);
2010 SetDirection(FDirection);
2011 end;
2013 procedure TPlayer.SetColor(Color: TRGB);
2014 begin
2015 FColor := Color;
2016 if not (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
2017 if FModel <> nil then FModel.Color := Color;
2018 end;
2020 procedure TPlayer.SwitchTeam;
2021 begin
2022 if g_Game_IsClient then
2023 Exit;
2024 if not (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then Exit;
2026 if gGameOn and FAlive then
2027 Kill(K_SIMPLEKILL, FUID, HIT_SELF);
2029 if FTeam = TEAM_RED then
2030 begin
2031 ChangeTeam(TEAM_BLUE);
2032 g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_BLUE], [FName]), True);
2033 if g_Game_IsNet then
2034 MH_SEND_GameEvent(NET_EV_CHANGE_TEAM, TEAM_BLUE, FName);
2035 end
2036 else
2037 begin
2038 ChangeTeam(TEAM_RED);
2039 g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_RED], [FName]), True);
2040 if g_Game_IsNet then
2041 MH_SEND_GameEvent(NET_EV_CHANGE_TEAM, TEAM_RED, FName);
2042 end;
2043 FPreferredTeam := FTeam;
2044 end;
2046 procedure TPlayer.ChangeTeam(Team: Byte);
2047 var
2048 OldTeam: Byte;
2049 begin
2050 OldTeam := FTeam;
2051 FTeam := Team;
2052 case Team of
2053 TEAM_RED, TEAM_BLUE:
2054 FModel.Color := TEAMCOLOR[Team];
2055 else
2056 FModel.Color := FColor;
2057 end;
2058 if (FTeam <> OldTeam) and g_Game_IsNet and g_Game_IsServer then
2059 MH_SEND_PlayerStats(FUID);
2060 end;
2063 procedure TPlayer.CollideItem();
2064 var
2065 i: Integer;
2066 r: Boolean;
2067 begin
2068 if gItems = nil then Exit;
2069 if not FAlive then Exit;
2071 for i := 0 to High(gItems) do
2072 with gItems[i] do
2073 begin
2074 if (ItemType <> ITEM_NONE) and alive then
2075 if g_Obj_Collide(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width,
2076 PLAYER_RECT.Height, @Obj) then
2077 begin
2078 if not PickItem(ItemType, gItems[i].Respawnable, r) then Continue;
2080 if ItemType in [ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_INVUL] then
2081 g_Sound_PlayExAt('SOUND_ITEM_GETRULEZ', FObj.X, FObj.Y)
2082 else if ItemType in [ITEM_MEDKIT_SMALL, ITEM_MEDKIT_LARGE, ITEM_MEDKIT_BLACK] then
2083 g_Sound_PlayExAt('SOUND_ITEM_GETMED', FObj.X, FObj.Y)
2084 else g_Sound_PlayExAt('SOUND_ITEM_GETITEM', FObj.X, FObj.Y);
2086 // Íàäî óáðàòü ñ êàðòû, åñëè ýòî íå êëþ÷, êîòîðûì íóæíî ïîäåëèòñÿ ñ äðóãèì èãðîêîì:
2087 if r and not ((ItemType in [ITEM_KEY_RED, ITEM_KEY_GREEN, ITEM_KEY_BLUE]) and
2088 (gGameSettings.GameType = GT_SINGLE) and
2089 (g_Player_GetCount() > 1)) then
2090 if not Respawnable then g_Items_Remove(i) else g_Items_Pick(i);
2091 end;
2092 end;
2093 end;
2096 function TPlayer.CollideLevel(XInc, YInc: Integer): Boolean;
2097 begin
2098 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
2099 PLAYER_RECT.Width, PLAYER_RECT.Height, PANEL_WALL,
2100 False);
2101 end;
2103 constructor TPlayer.Create();
2104 begin
2105 viewPortX := 0;
2106 viewPortY := 0;
2107 viewPortW := 0;
2108 viewPortH := 0;
2109 mEDamageType := HIT_SOME;
2111 FIamBot := False;
2112 FDummy := False;
2113 FSpawned := False;
2115 FSawSound := TPlayableSound.Create();
2116 FSawSoundIdle := TPlayableSound.Create();
2117 FSawSoundHit := TPlayableSound.Create();
2118 FSawSoundSelect := TPlayableSound.Create();
2119 FJetSoundFly := TPlayableSound.Create();
2120 FJetSoundOn := TPlayableSound.Create();
2121 FJetSoundOff := TPlayableSound.Create();
2123 FSawSound.SetByName('SOUND_WEAPON_FIRESAW');
2124 FSawSoundIdle.SetByName('SOUND_WEAPON_IDLESAW');
2125 FSawSoundHit.SetByName('SOUND_WEAPON_HITSAW');
2126 FSawSoundSelect.SetByName('SOUND_WEAPON_SELECTSAW');
2127 FJetSoundFly.SetByName('SOUND_PLAYER_JETFLY');
2128 FJetSoundOn.SetByName('SOUND_PLAYER_JETON');
2129 FJetSoundOff.SetByName('SOUND_PLAYER_JETOFF');
2131 FSpectatePlayer := -1;
2132 FClientID := -1;
2133 FPing := 0;
2134 FLoss := 0;
2135 FSavedState.WaitRecall := False;
2136 FShellTimer := -1;
2137 FFireTime := 0;
2138 FFirePainTime := 0;
2139 FFireAttacker := 0;
2141 FActualModelName := 'doomer';
2143 g_Obj_Init(@FObj);
2144 FObj.Rect := PLAYER_RECT;
2146 FBFGFireCounter := -1;
2147 FJustTeleported := False;
2148 FNetTime := 0;
2150 //FNetForceWeap := FCurrWeap;
2151 FNetForceWeapFIdx := 0;
2152 //FCurrFrameIdx := 0;
2154 resetWeaponQueue();
2155 releaseAllWeaponSwitchKeys();
2156 end;
2159 procedure TPlayer.releaseAllWeaponSwitchKeys ();
2160 var
2161 f: Integer;
2162 begin
2163 for f := 0 to High(weaponSwitchKeyReleased) do weaponSwitchKeyReleased[f] := $03;
2164 end;
2166 procedure TPlayer.weaponSwitchKeysStateChange (index: Integer; pressed: Boolean);
2167 begin
2168 Inc(index, 2); // -2: prev; -1: next
2169 if (index < 0) or (index > High(weaponSwitchKeyReleased)) then exit;
2170 weaponSwitchKeyReleased[index] := weaponSwitchKeyReleased[index] or $02;
2171 if (pressed) then weaponSwitchKeyReleased[index] := weaponSwitchKeyReleased[index] xor $02;
2172 end;
2174 function TPlayer.isWeaponSwitchKeyReleased (index: Integer): Boolean;
2175 begin
2176 Inc(index, 2); // -2: prev; -1: next
2177 if (index < 0) or (index > High(weaponSwitchKeyReleased)) then
2178 begin
2179 result := true;
2180 end
2181 else
2182 begin
2183 result := (weaponSwitchKeyReleased[index] and $01) <> 0;
2184 end;
2185 end;
2187 procedure TPlayer.weaponSwitchKeysShiftNewStates ();
2188 var
2189 f: Integer;
2190 begin
2191 // copy bit 1 to bit 0
2192 for f := 0 to High(weaponSwitchKeyReleased) do
2193 begin
2194 weaponSwitchKeyReleased[f] :=
2195 (weaponSwitchKeyReleased[f] and $02) or
2196 ((weaponSwitchKeyReleased[f] shr 1) and $01);
2197 end;
2198 end;
2201 procedure TPlayer.positionChanged (); inline;
2202 begin
2203 end;
2205 procedure TPlayer.doDamage (v: Integer);
2206 begin
2207 if (v <= 0) then exit;
2208 if (v > 32767) then v := 32767;
2209 Damage(v, 0, 0, 0, mEDamageType);
2210 end;
2212 procedure TPlayer.Damage(value: Word; SpawnerUID: Word; vx, vy: Integer; t: Byte);
2213 var
2214 c: Word;
2215 begin
2216 if (not g_Game_IsClient) and (not FAlive) then
2217 Exit;
2219 FLastHit := t;
2221 // Íåóÿçâèìîñòü íå ñïàñàåò îò ëîâóøåê:
2222 if ((t = HIT_TRAP) or (t = HIT_SELF)) and (not FGodMode) then
2223 begin
2224 if not g_Game_IsClient then
2225 begin
2226 FArmor := 0;
2227 if t = HIT_TRAP then
2228 begin
2229 // Ëîâóøêà óáèâàåò ñðàçó:
2230 FHealth := -100;
2231 Kill(K_EXTRAHARDKILL, SpawnerUID, t);
2232 end;
2233 if t = HIT_SELF then
2234 begin
2235 // Ñàìîóáèéñòâî:
2236 FHealth := 0;
2237 Kill(K_SIMPLEKILL, SpawnerUID, t);
2238 end;
2239 end;
2240 // Îáíóëèòü äåéñòâèÿ ïðèìî÷åê, ÷òîáû ôîí ïðîïàë
2241 FMegaRulez[MR_SUIT] := 0;
2242 FMegaRulez[MR_INVUL] := 0;
2243 FMegaRulez[MR_INVIS] := 0;
2244 FBerserk := 0;
2245 end;
2247 // Íî îò îñòàëüíîãî ñïàñàåò:
2248 if FMegaRulez[MR_INVUL] >= gTime then
2249 Exit;
2251 // ×èò-êîä "ÃÎÐÅÖ":
2252 if FGodMode then
2253 Exit;
2255 // Åñëè åñòü óðîí ñâîèì, èëè ðàíèë ñàì ñåáÿ, èëè òåáÿ ðàíèë ïðîòèâíèê:
2256 if LongBool(gGameSettings.Options and GAME_OPTION_TEAMDAMAGE) or
2257 (SpawnerUID = FUID) or
2258 (not SameTeam(FUID, SpawnerUID)) then
2259 begin
2260 FLastSpawnerUID := SpawnerUID;
2262 // Êðîâü (ïóçûðüêè, åñëè â âîäå):
2263 if gBloodCount > 0 then
2264 begin
2265 c := Min(value, 200)*gBloodCount + Random(Min(value, 200) div 2);
2266 if value div 4 <= c then
2267 c := c - (value div 4)
2268 else
2269 c := 0;
2271 if (t = HIT_SOME) and (vx = 0) and (vy = 0) then
2272 MakeBloodSimple(c)
2273 else
2274 case t of
2275 HIT_TRAP, HIT_ACID, HIT_FLAME, HIT_SELF: MakeBloodSimple(c);
2276 HIT_BFG, HIT_ROCKET, HIT_SOME: MakeBloodVector(c, vx, vy);
2277 end;
2279 if t = HIT_WATER then
2280 g_GFX_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
2281 FObj.Y+PLAYER_RECT.Y-4, value div 2, 8, 4);
2282 end;
2284 // Áóôåð óðîíà:
2285 if FAlive then
2286 Inc(FDamageBuffer, value);
2288 // Âñïûøêà áîëè:
2289 if gFlash <> 0 then
2290 FPain := FPain + value;
2291 end;
2293 if g_Game_IsServer and g_Game_IsNet then
2294 begin
2295 MH_SEND_PlayerDamage(FUID, t, SpawnerUID, value, vx, vy);
2296 MH_SEND_PlayerStats(FUID);
2297 MH_SEND_PlayerPos(False, FUID);
2298 end;
2299 end;
2301 function TPlayer.Heal(value: Word; Soft: Boolean): Boolean;
2302 begin
2303 Result := False;
2304 if g_Game_IsClient then
2305 Exit;
2306 if not FAlive then
2307 Exit;
2309 if Soft and (FHealth < PLAYER_HP_SOFT) then
2310 begin
2311 IncMax(FHealth, value, PLAYER_HP_SOFT);
2312 Result := True;
2313 end;
2314 if (not Soft) and (FHealth < PLAYER_HP_LIMIT) then
2315 begin
2316 IncMax(FHealth, value, PLAYER_HP_LIMIT);
2317 Result := True;
2318 end;
2320 if Result and g_Game_IsServer and g_Game_IsNet then
2321 MH_SEND_PlayerStats(FUID);
2322 end;
2324 destructor TPlayer.Destroy();
2325 begin
2326 if (gPlayer1 <> nil) and (gPlayer1.FUID = FUID) then
2327 gPlayer1 := nil;
2328 if (gPlayer2 <> nil) and (gPlayer2.FUID = FUID) then
2329 gPlayer2 := nil;
2331 FSawSound.Free();
2332 FSawSoundIdle.Free();
2333 FSawSoundHit.Free();
2334 FJetSoundFly.Free();
2335 FJetSoundOn.Free();
2336 FJetSoundOff.Free();
2337 FModel.Free();
2338 if FPunchAnim <> nil then
2339 FPunchAnim.Free();
2341 inherited;
2342 end;
2344 procedure TPlayer.DrawBubble();
2345 var
2346 bubX, bubY: Integer;
2347 ID: LongWord;
2348 Rb, Gb, Bb,
2349 Rw, Gw, Bw: SmallInt;
2350 Dot: Byte;
2351 begin
2352 bubX := FObj.X+FObj.Rect.X + IfThen(FDirection = TDirection.D_LEFT, -4, 18);
2353 bubY := FObj.Y+FObj.Rect.Y - 18;
2354 Rb := 64;
2355 Gb := 64;
2356 Bb := 64;
2357 Rw := 240;
2358 Gw := 240;
2359 Bw := 240;
2360 case gChatBubble of
2361 1: // simple textual non-bubble
2362 begin
2363 bubX := FObj.X+FObj.Rect.X - 11;
2364 bubY := FObj.Y+FObj.Rect.Y - 17;
2365 e_TextureFontPrint(bubX, bubY, '[...]', gStdFont);
2366 Exit;
2367 end;
2368 2: // advanced pixel-perfect bubble
2369 begin
2370 if FTeam = TEAM_RED then
2371 Rb := 255
2372 else
2373 if FTeam = TEAM_BLUE then
2374 Bb := 255;
2375 end;
2376 3: // colored bubble
2377 begin
2378 Rb := FModel.Color.R;
2379 Gb := FModel.Color.G;
2380 Bb := FModel.Color.B;
2381 Rw := Min(Rb * 2 + 64, 255);
2382 Gw := Min(Gb * 2 + 64, 255);
2383 Bw := Min(Bb * 2 + 64, 255);
2384 if (Abs(Rw - Rb) < 32)
2385 or (Abs(Gw - Gb) < 32)
2386 or (Abs(Bw - Bb) < 32) then
2387 begin
2388 Rb := Max(Rw div 2 - 16, 0);
2389 Gb := Max(Gw div 2 - 16, 0);
2390 Bb := Max(Bw div 2 - 16, 0);
2391 end;
2392 end;
2393 4: // custom textured bubble
2394 begin
2395 if g_Texture_Get('TEXTURE_PLAYER_TALKBUBBLE', ID) then
2396 if FDirection = TDirection.D_RIGHT then
2397 e_Draw(ID, bubX - 6, bubY - 7, 0, True, False)
2398 else
2399 e_Draw(ID, bubX - 6, bubY - 7, 0, True, False, TMirrorType.Horizontal);
2400 Exit;
2401 end;
2402 end;
2404 // Outer borders
2405 e_DrawQuad(bubX + 1, bubY , bubX + 18, bubY + 13, Rb, Gb, Bb);
2406 e_DrawQuad(bubX , bubY + 1, bubX + 19, bubY + 12, Rb, Gb, Bb);
2407 // Inner box
2408 e_DrawFillQuad(bubX + 1, bubY + 1, bubX + 18, bubY + 12, Rw, Gw, Bw, 0);
2410 // Tail
2411 Dot := IfThen(FDirection = TDirection.D_LEFT, 14, 5);
2412 e_DrawLine(1, bubX + Dot, bubY + 14, bubX + Dot, bubY + 16, Rb, Gb, Bb);
2413 e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 1, Dot + 1), bubY + 13, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 1, Dot + 1), bubY + 15, Rw, Gw, Bw);
2414 e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 2, Dot + 2), bubY + 13, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 2, Dot + 2), bubY + 14, Rw, Gw, Bw);
2415 e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 3, Dot + 3), bubY + 13, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 3, Dot + 3), bubY + 13, Rw, Gw, Bw);
2416 e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 3, Dot + 3), bubY + 14, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 1, Dot + 1), bubY + 16, Rb, Gb, Bb);
2418 // Dots
2419 Dot := 6;
2420 e_DrawFillQuad(bubX + Dot, bubY + 8, bubX + Dot + 1, bubY + 9, Rb, Gb, Bb, 0);
2421 e_DrawFillQuad(bubX + Dot + 3, bubY + 8, bubX + Dot + 4, bubY + 9, Rb, Gb, Bb, 0);
2422 e_DrawFillQuad(bubX + Dot + 6, bubY + 8, bubX + Dot + 7, bubY + 9, Rb, Gb, Bb, 0);
2423 end;
2425 procedure TPlayer.Draw();
2426 var
2427 ID: DWORD;
2428 w, h: Word;
2429 dr: Boolean;
2430 Mirror: TMirrorType;
2431 begin
2432 if FAlive then
2433 begin
2434 if Direction = TDirection.D_RIGHT then
2435 Mirror := TMirrorType.None
2436 else
2437 Mirror := TMirrorType.Horizontal;
2439 if FPunchAnim <> nil then
2440 begin
2441 FPunchAnim.Draw(FObj.X+IfThen(Direction = TDirection.D_LEFT, 15-FObj.Rect.X, FObj.Rect.X-15),
2442 FObj.Y+FObj.Rect.Y-11, Mirror);
2443 if FPunchAnim.played then
2444 begin
2445 FPunchAnim.Free;
2446 FPunchAnim := nil;
2447 end;
2448 end;
2450 if (FMegaRulez[MR_INVUL] > gTime) and (gPlayerDrawn <> Self) then
2451 if g_Texture_Get('TEXTURE_PLAYER_INVULPENTA', ID) then
2452 begin
2453 e_GetTextureSize(ID, @w, @h);
2454 if FDirection = TDirection.D_LEFT then
2455 e_Draw(ID, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)+4,
2456 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7+FObj.slopeUpLeft, 0, True, False)
2457 else
2458 e_Draw(ID, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)-2,
2459 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7+FObj.slopeUpLeft, 0, True, False);
2460 end;
2462 if FMegaRulez[MR_INVIS] > gTime then
2463 begin
2464 if (gPlayerDrawn <> nil) and ((Self = gPlayerDrawn) or
2465 ((FTeam = gPlayerDrawn.Team) and (gGameSettings.GameMode <> GM_DM))) then
2466 begin
2467 if (FMegaRulez[MR_INVIS] - gTime) <= 2100 then
2468 dr := not Odd((FMegaRulez[MR_INVIS] - gTime) div 300)
2469 else
2470 dr := True;
2471 if dr then
2472 FModel.Draw(FObj.X, FObj.Y+FObj.slopeUpLeft, 200)
2473 else
2474 FModel.Draw(FObj.X, FObj.Y+FObj.slopeUpLeft);
2475 end
2476 else
2477 FModel.Draw(FObj.X, FObj.Y+FObj.slopeUpLeft, 254);
2478 end
2479 else
2480 FModel.Draw(FObj.X, FObj.Y+FObj.slopeUpLeft);
2481 end;
2483 if g_debug_Frames then
2484 begin
2485 e_DrawQuad(FObj.X+FObj.Rect.X,
2486 FObj.Y+FObj.Rect.Y,
2487 FObj.X+FObj.Rect.X+FObj.Rect.Width-1,
2488 FObj.Y+FObj.Rect.Y+FObj.Rect.Height-1,
2489 0, 255, 0);
2490 end;
2492 if (gChatBubble > 0) and (FKeys[KEY_CHAT].Pressed) and not FGhost then
2493 DrawBubble();
2494 // e_DrawPoint(5, 335, 288, 255, 0, 0); // DL, UR, DL, UR
2495 if gAimLine and alive and
2496 ((Self = gPlayer1) or (Self = gPlayer2)) then
2497 DrawAim();
2498 end;
2501 procedure TPlayer.DrawAim();
2502 procedure drawCast (sz: Integer; ax0, ay0, ax1, ay1: Integer);
2503 var
2504 ex, ey: Integer;
2505 begin
2507 {$IFDEF ENABLE_HOLMES}
2508 if isValidViewPort and (self = gPlayer1) then
2509 begin
2510 g_Holmes_plrLaser(ax0, ay0, ax1, ay1);
2511 end;
2512 {$ENDIF}
2514 e_DrawLine(sz, ax0, ay0, ax1, ay1, 255, 0, 0, 96);
2515 if (g_Map_traceToNearestWall(ax0, ay0, ax1, ay1, @ex, @ey) <> nil) then
2516 begin
2517 e_DrawLine(sz, ax0, ay0, ex, ey, 0, 255, 0, 96);
2518 end
2519 else
2520 begin
2521 e_DrawLine(sz, ax0, ay0, ex, ey, 0, 0, 255, 96);
2522 end;
2523 end;
2525 var
2526 wx, wy, xx, yy: Integer;
2527 angle: SmallInt;
2528 sz, len: Word;
2529 begin
2530 wx := FObj.X + WEAPONPOINT[FDirection].X + IfThen(FDirection = TDirection.D_LEFT, 7, -7);
2531 wy := FObj.Y + WEAPONPOINT[FDirection].Y;
2532 angle := FAngle;
2533 len := 1024;
2534 sz := 2;
2535 case FCurrWeap of
2536 0: begin // Punch
2537 len := 12;
2538 sz := 4;
2539 end;
2540 1: begin // Chainsaw
2541 len := 24;
2542 sz := 6;
2543 end;
2544 2: begin // Pistol
2545 len := 1024;
2546 sz := 2;
2547 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2548 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2549 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2550 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2551 end;
2552 3: begin // Shotgun
2553 len := 1024;
2554 sz := 3;
2555 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2556 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2557 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2558 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2559 end;
2560 4: begin // Double Shotgun
2561 len := 1024;
2562 sz := 4;
2563 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2564 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2565 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2566 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2567 end;
2568 5: begin // Chaingun
2569 len := 1024;
2570 sz := 3;
2571 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2572 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2573 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2574 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2575 end;
2576 6: begin // Rocket Launcher
2577 len := 1024;
2578 sz := 7;
2579 if angle = ANGLE_RIGHTUP then Inc(angle, 2);
2580 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2581 if angle = ANGLE_LEFTUP then Dec(angle, 2);
2582 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2583 end;
2584 7: begin // Plasmagun
2585 len := 1024;
2586 sz := 5;
2587 if angle = ANGLE_RIGHTUP then Inc(angle);
2588 if angle = ANGLE_RIGHTDOWN then Inc(angle, 3);
2589 if angle = ANGLE_LEFTUP then Dec(angle);
2590 if angle = ANGLE_LEFTDOWN then Dec(angle, 3);
2591 end;
2592 8: begin // BFG
2593 len := 1024;
2594 sz := 12;
2595 if angle = ANGLE_RIGHTUP then Inc(angle, 1);
2596 if angle = ANGLE_RIGHTDOWN then Inc(angle, 2);
2597 if angle = ANGLE_LEFTUP then Dec(angle, 1);
2598 if angle = ANGLE_LEFTDOWN then Dec(angle, 2);
2599 end;
2600 9: begin // Super Chaingun
2601 len := 1024;
2602 sz := 4;
2603 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2604 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2605 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2606 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2607 end;
2608 end;
2609 xx := Trunc(Cos(-DegToRad(angle)) * len) + wx;
2610 yy := Trunc(Sin(-DegToRad(angle)) * len) + wy;
2611 {$IF DEFINED(D2F_DEBUG)}
2612 drawCast(sz, wx, wy, xx, yy);
2613 {$ELSE}
2614 e_DrawLine(sz, wx, wy, xx, yy, 255, 0, 0, 96);
2615 {$ENDIF}
2616 end;
2618 procedure TPlayer.DrawGUI();
2619 var
2620 ID: DWORD;
2621 X, Y, SY, a, p, m: Integer;
2622 tw, th: Word;
2623 cw, ch: Byte;
2624 s: string;
2625 stat: TPlayerStatArray;
2626 begin
2627 X := gPlayerScreenSize.X;
2628 SY := gPlayerScreenSize.Y;
2629 Y := 0;
2631 if gShowGoals and (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
2632 begin
2633 if gGameSettings.GameMode = GM_CTF then
2634 a := 32 + 8
2635 else
2636 a := 0;
2637 if gGameSettings.GameMode = GM_CTF then
2638 begin
2639 s := 'TEXTURE_PLAYER_REDFLAG';
2640 if gFlags[FLAG_RED].State = FLAG_STATE_CAPTURED then
2641 s := 'TEXTURE_PLAYER_REDFLAG_S';
2642 if gFlags[FLAG_RED].State = FLAG_STATE_DROPPED then
2643 s := 'TEXTURE_PLAYER_REDFLAG_D';
2644 if g_Texture_Get(s, ID) then
2645 e_Draw(ID, X-16-32, 240-72-4, 0, True, False);
2646 end;
2648 s := IntToStr(gTeamStat[TEAM_RED].Goals);
2649 e_CharFont_GetSize(gMenuFont, s, tw, th);
2650 e_CharFont_PrintEx(gMenuFont, X-16-a-tw, 240-72-4, s, TEAMCOLOR[TEAM_RED]);
2652 if gGameSettings.GameMode = GM_CTF then
2653 begin
2654 s := 'TEXTURE_PLAYER_BLUEFLAG';
2655 if gFlags[FLAG_BLUE].State = FLAG_STATE_CAPTURED then
2656 s := 'TEXTURE_PLAYER_BLUEFLAG_S';
2657 if gFlags[FLAG_BLUE].State = FLAG_STATE_DROPPED then
2658 s := 'TEXTURE_PLAYER_BLUEFLAG_D';
2659 if g_Texture_Get(s, ID) then
2660 e_Draw(ID, X-16-32, 240-32-4, 0, True, False);
2661 end;
2663 s := IntToStr(gTeamStat[TEAM_BLUE].Goals);
2664 e_CharFont_GetSize(gMenuFont, s, tw, th);
2665 e_CharFont_PrintEx(gMenuFont, X-16-a-tw, 240-32-4, s, TEAMCOLOR[TEAM_BLUE]);
2666 end;
2668 if g_Texture_Get('TEXTURE_PLAYER_HUDBG', ID) then
2669 e_DrawFill(ID, X, 0, 1, (gPlayerScreenSize.Y div 256)+IfThen(gPlayerScreenSize.Y mod 256 > 0, 1, 0),
2670 0, False, False);
2672 if g_Texture_Get('TEXTURE_PLAYER_HUD', ID) then
2673 e_Draw(ID, X+2, Y, 0, True, False);
2675 if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
2676 begin
2677 if gShowStat then
2678 begin
2679 s := IntToStr(Frags);
2680 e_CharFont_GetSize(gMenuFont, s, tw, th);
2681 e_CharFont_PrintEx(gMenuFont, X-16-tw, Y, s, _RGB(255, 0, 0));
2683 s := '';
2684 p := 1;
2685 m := 0;
2686 stat := g_Player_GetStats();
2687 if stat <> nil then
2688 begin
2689 p := 1;
2691 for a := 0 to High(stat) do
2692 if stat[a].Name <> Name then
2693 begin
2694 if stat[a].Frags > m then m := stat[a].Frags;
2695 if stat[a].Frags > Frags then p := p+1;
2696 end;
2697 end;
2699 s := IntToStr(p)+' / '+IntToStr(Length(stat))+' ';
2700 if Frags >= m then s := s+'+' else s := s+'-';
2701 s := s+IntToStr(Abs(Frags-m));
2703 e_CharFont_GetSize(gMenuSmallFont, s, tw, th);
2704 e_CharFont_PrintEx(gMenuSmallFont, X-16-tw, Y+32, s, _RGB(255, 0, 0));
2705 end;
2707 if gShowLives and (gGameSettings.MaxLives > 0) then
2708 begin
2709 s := IntToStr(Lives);
2710 e_CharFont_GetSize(gMenuFont, s, tw, th);
2711 e_CharFont_PrintEx(gMenuFont, X-16-tw, SY-32, s, _RGB(0, 255, 0));
2712 end;
2713 end;
2715 e_CharFont_GetSize(gMenuSmallFont, FName, tw, th);
2716 e_CharFont_PrintEx(gMenuSmallFont, X+98-(tw div 2), Y+8, FName, _RGB(255, 0, 0));
2718 if R_BERSERK in FRulez then
2719 e_Draw(gItemsTexturesID[ITEM_MEDKIT_BLACK], X+37, Y+45, 0, True, False)
2720 else
2721 e_Draw(gItemsTexturesID[ITEM_MEDKIT_LARGE], X+37, Y+45, 0, True, False);
2723 if g_Texture_Get('TEXTURE_PLAYER_ARMORHUD', ID) then
2724 e_Draw(ID, X+36, Y+77, 0, True, False);
2726 s := IntToStr(IfThen(FHealth > 0, FHealth, 0));
2727 e_CharFont_GetSize(gMenuFont, s, tw, th);
2728 e_CharFont_PrintEx(gMenuFont, X+178-tw, Y+40, s, _RGB(255, 0, 0));
2730 s := IntToStr(FArmor);
2731 e_CharFont_GetSize(gMenuFont, s, tw, th);
2732 e_CharFont_PrintEx(gMenuFont, X+178-tw, Y+68, s, _RGB(255, 0, 0));
2734 s := IntToStr(GetAmmoByWeapon(FCurrWeap));
2736 case FCurrWeap of
2737 WEAPON_KASTET:
2738 begin
2739 s := '--';
2740 ID := gItemsTexturesID[ITEM_WEAPON_KASTET];
2741 end;
2742 WEAPON_SAW:
2743 begin
2744 s := '--';
2745 ID := gItemsTexturesID[ITEM_WEAPON_SAW];
2746 end;
2747 WEAPON_PISTOL: ID := gItemsTexturesID[ITEM_WEAPON_PISTOL];
2748 WEAPON_CHAINGUN: ID := gItemsTexturesID[ITEM_WEAPON_CHAINGUN];
2749 WEAPON_SHOTGUN1: ID := gItemsTexturesID[ITEM_WEAPON_SHOTGUN1];
2750 WEAPON_SHOTGUN2: ID := gItemsTexturesID[ITEM_WEAPON_SHOTGUN2];
2751 WEAPON_SUPERPULEMET: ID := gItemsTexturesID[ITEM_WEAPON_SUPERPULEMET];
2752 WEAPON_ROCKETLAUNCHER: ID := gItemsTexturesID[ITEM_WEAPON_ROCKETLAUNCHER];
2753 WEAPON_PLASMA: ID := gItemsTexturesID[ITEM_WEAPON_PLASMA];
2754 WEAPON_BFG: ID := gItemsTexturesID[ITEM_WEAPON_BFG];
2755 WEAPON_FLAMETHROWER: ID := gItemsTexturesID[ITEM_WEAPON_FLAMETHROWER];
2756 end;
2758 e_CharFont_GetSize(gMenuFont, s, tw, th);
2759 e_CharFont_PrintEx(gMenuFont, X+178-tw, Y+158, s, _RGB(255, 0, 0));
2760 e_Draw(ID, X+20, Y+160, 0, True, False);
2762 if R_KEY_RED in FRulez then
2763 e_Draw(gItemsTexturesID[ITEM_KEY_RED], X+78, Y+214, 0, True, False);
2765 if R_KEY_GREEN in FRulez then
2766 e_Draw(gItemsTexturesID[ITEM_KEY_GREEN], X+95, Y+214, 0, True, False);
2768 if R_KEY_BLUE in FRulez then
2769 e_Draw(gItemsTexturesID[ITEM_KEY_BLUE], X+112, Y+214, 0, True, False);
2771 if FJetFuel > 0 then
2772 begin
2773 if g_Texture_Get('TEXTURE_PLAYER_HUDAIR', ID) then
2774 e_Draw(ID, X+2, Y+116, 0, True, False);
2775 if g_Texture_Get('TEXTURE_PLAYER_HUDJET', ID) then
2776 e_Draw(ID, X+2, Y+126, 0, True, False);
2777 e_DrawLine(4, X+16, Y+122, X+16+Trunc(168*IfThen(FAir > 0, FAir, 0)/AIR_MAX), Y+122, 0, 0, 196);
2778 e_DrawLine(4, X+16, Y+132, X+16+Trunc(168*FJetFuel/JET_MAX), Y+132, 208, 0, 0);
2779 end
2780 else
2781 begin
2782 if g_Texture_Get('TEXTURE_PLAYER_HUDAIR', ID) then
2783 e_Draw(ID, X+2, Y+124, 0, True, False);
2784 e_DrawLine(4, X+16, Y+130, X+16+Trunc(168*IfThen(FAir > 0, FAir, 0)/AIR_MAX), Y+130, 0, 0, 196);
2785 end;
2787 if gShowPing and g_Game_IsClient then
2788 begin
2789 s := _lc[I_GAME_PING_HUD] + IntToStr(NetPeer.lastRoundTripTime) + _lc[I_NET_SLIST_PING_MS];
2790 e_TextureFontPrint(X + 4, Y + 242, s, gStdFont);
2791 Y := Y + 16;
2792 end;
2794 if FSpectator then
2795 begin
2796 e_TextureFontPrint(X + 4, Y + 242, _lc[I_PLAYER_SPECT], gStdFont);
2797 e_TextureFontPrint(X + 4, Y + 258, _lc[I_PLAYER_SPECT2], gStdFont);
2798 e_TextureFontPrint(X + 4, Y + 274, _lc[I_PLAYER_SPECT1], gStdFont);
2799 if FNoRespawn then
2800 begin
2801 e_TextureFontGetSize(gStdFont, cw, ch);
2802 s := _lc[I_PLAYER_SPECT4];
2803 e_TextureFontPrintEx(gScreenWidth div 2 - cw*(Length(s) div 2),
2804 gScreenHeight-4-ch, s, gStdFont, 255, 255, 255, 1, True);
2805 e_TextureFontPrint(X + 4, Y + 290, _lc[I_PLAYER_SPECT1S], gStdFont);
2806 end;
2808 end;
2809 end;
2811 procedure TPlayer.DrawRulez();
2812 var
2813 dr: Boolean;
2814 begin
2815 // Ïðè âçÿòèè íåóÿçâèìîñòè ðèñóåòñÿ èíâåðñèîííûé áåëûé ôîí
2816 if FMegaRulez[MR_INVUL] >= gTime then
2817 begin
2818 if (FMegaRulez[MR_INVUL]-gTime) <= 2100 then
2819 dr := not Odd((FMegaRulez[MR_INVUL]-gTime) div 300)
2820 else
2821 dr := True;
2823 if dr then
2824 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1,
2825 191, 191, 191, 0, TBlending.Invert);
2826 end;
2828 // Ïðè âçÿòèè çàùèòíîãî êîñòþìà ðèñóåòñÿ çåëåíîâàòûé ôîí
2829 if FMegaRulez[MR_SUIT] >= gTime then
2830 begin
2831 if (FMegaRulez[MR_SUIT]-gTime) <= 2100 then
2832 dr := not Odd((FMegaRulez[MR_SUIT]-gTime) div 300)
2833 else
2834 dr := True;
2836 if dr then
2837 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1,
2838 0, 96, 0, 200, TBlending.None);
2839 end;
2841 // Ïðè âçÿòèè áåðñåðêà ðèñóåòñÿ êðàñíîâàòûé ôîí
2842 if (FBerserk >= 0) and (LongWord(FBerserk) >= gTime) and (gFlash = 2) then
2843 begin
2844 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1,
2845 255, 0, 0, 200, TBlending.None);
2846 end;
2847 end;
2849 procedure TPlayer.DrawPain();
2850 var
2851 a, h: Integer;
2852 begin
2853 if FPain = 0 then Exit;
2855 a := FPain;
2857 if a < 15 then h := 0
2858 else if a < 35 then h := 1
2859 else if a < 55 then h := 2
2860 else if a < 75 then h := 3
2861 else if a < 95 then h := 4
2862 else h := 5;
2864 //if a > 255 then a := 255;
2866 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, 255, 0, 0, 255-h*50);
2867 //e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, 255-min(128, a), 255-a, 255-a, 0, B_FILTER);
2868 end;
2870 procedure TPlayer.DrawPickup();
2871 var
2872 a, h: Integer;
2873 begin
2874 if FPickup = 0 then Exit;
2876 a := FPickup;
2878 if a < 15 then h := 1
2879 else if a < 35 then h := 2
2880 else if a < 55 then h := 3
2881 else if a < 75 then h := 4
2882 else h := 5;
2884 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, 150, 200, 150, 255-h*50);
2885 end;
2887 procedure TPlayer.DoPunch();
2888 var
2889 id: DWORD;
2890 st: String;
2891 begin
2892 if FPunchAnim <> nil then begin
2893 FPunchAnim.reset();
2894 FPunchAnim.Free;
2895 FPunchAnim := nil;
2896 end;
2897 st := 'FRAMES_PUNCH';
2898 if R_BERSERK in FRulez then
2899 st := st + '_BERSERK';
2900 if FKeys[KEY_UP].Pressed then
2901 st := st + '_UP'
2902 else if FKeys[KEY_DOWN].Pressed then
2903 st := st + '_DN';
2904 g_Frames_Get(id, st);
2905 FPunchAnim := TAnimation.Create(id, False, 1);
2906 end;
2908 procedure TPlayer.Fire();
2909 var
2910 f, DidFire: Boolean;
2911 wx, wy, xd, yd: Integer;
2912 locobj: TObj;
2913 begin
2914 if g_Game_IsClient then Exit;
2915 // FBFGFireCounter - âðåìÿ ïåðåä âûñòðåëîì (äëÿ BFG)
2916 // FReloading - âðåìÿ ïîñëå âûñòðåëà (äëÿ âñåãî)
2918 if FSpectator then
2919 begin
2920 Respawn(False);
2921 Exit;
2922 end;
2924 if FReloading[FCurrWeap] <> 0 then Exit;
2926 DidFire := False;
2928 f := False;
2929 wx := FObj.X+WEAPONPOINT[FDirection].X;
2930 wy := FObj.Y+WEAPONPOINT[FDirection].Y;
2931 xd := wx+IfThen(FDirection = TDirection.D_LEFT, -30, 30);
2932 yd := wy+firediry();
2934 case FCurrWeap of
2935 WEAPON_KASTET:
2936 begin
2937 DoPunch();
2938 if R_BERSERK in FRulez then
2939 begin
2940 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
2941 locobj.X := FObj.X+FObj.Rect.X;
2942 locobj.Y := FObj.Y+FObj.Rect.Y;
2943 locobj.rect.X := 0;
2944 locobj.rect.Y := 0;
2945 locobj.rect.Width := 39;
2946 locobj.rect.Height := 52;
2947 locobj.Vel.X := (xd-wx) div 2;
2948 locobj.Vel.Y := (yd-wy) div 2;
2949 locobj.Accel.X := xd-wx;
2950 locobj.Accel.y := yd-wy;
2952 if g_Weapon_Hit(@locobj, 50, FUID, HIT_SOME) <> 0 then
2953 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj.X, FObj.Y)
2954 else
2955 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj.X, FObj.Y);
2957 if (gFlash = 1) and (FPain < 50) then FPain := min(FPain + 25, 50);
2958 end
2959 else
2960 begin
2961 g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 3, FUID);
2962 end;
2964 DidFire := True;
2965 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2966 end;
2968 WEAPON_SAW:
2969 begin
2970 if g_Weapon_chainsaw(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
2971 IfThen(gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF], 9, 3), FUID) <> 0 then
2972 begin
2973 FSawSoundSelect.Stop();
2974 FSawSound.Stop();
2975 FSawSoundHit.PlayAt(FObj.X, FObj.Y);
2976 end
2977 else if not FSawSoundHit.IsPlaying() then
2978 begin
2979 FSawSoundSelect.Stop();
2980 FSawSound.PlayAt(FObj.X, FObj.Y);
2981 end;
2983 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2984 DidFire := True;
2985 f := True;
2986 end;
2988 WEAPON_PISTOL:
2989 if FAmmo[A_BULLETS] > 0 then
2990 begin
2991 g_Weapon_pistol(wx, wy, xd, yd, FUID);
2992 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2993 Dec(FAmmo[A_BULLETS]);
2994 FFireAngle := FAngle;
2995 f := True;
2996 DidFire := True;
2997 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
2998 GameVelX, GameVelY-2, SHELL_BULLET);
2999 end;
3001 WEAPON_SHOTGUN1:
3002 if FAmmo[A_SHELLS] > 0 then
3003 begin
3004 g_Weapon_shotgun(wx, wy, xd, yd, FUID);
3005 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', wx, wy);
3006 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3007 Dec(FAmmo[A_SHELLS]);
3008 FFireAngle := FAngle;
3009 f := True;
3010 DidFire := True;
3011 FShellTimer := 10;
3012 FShellType := SHELL_SHELL;
3013 end;
3015 WEAPON_SHOTGUN2:
3016 if FAmmo[A_SHELLS] >= 2 then
3017 begin
3018 g_Weapon_dshotgun(wx, wy, xd, yd, FUID);
3019 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3020 Dec(FAmmo[A_SHELLS], 2);
3021 FFireAngle := FAngle;
3022 f := True;
3023 DidFire := True;
3024 FShellTimer := 13;
3025 FShellType := SHELL_DBLSHELL;
3026 end;
3028 WEAPON_CHAINGUN:
3029 if FAmmo[A_BULLETS] > 0 then
3030 begin
3031 g_Weapon_mgun(wx, wy, xd, yd, FUID);
3032 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', wx, wy);
3033 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3034 Dec(FAmmo[A_BULLETS]);
3035 FFireAngle := FAngle;
3036 f := True;
3037 DidFire := True;
3038 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
3039 GameVelX, GameVelY-2, SHELL_BULLET);
3040 end;
3042 WEAPON_ROCKETLAUNCHER:
3043 if FAmmo[A_ROCKETS] > 0 then
3044 begin
3045 g_Weapon_rocket(wx, wy, xd, yd, FUID);
3046 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3047 Dec(FAmmo[A_ROCKETS]);
3048 FFireAngle := FAngle;
3049 f := True;
3050 DidFire := True;
3051 end;
3053 WEAPON_PLASMA:
3054 if FAmmo[A_CELLS] > 0 then
3055 begin
3056 g_Weapon_plasma(wx, wy, xd, yd, FUID);
3057 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3058 Dec(FAmmo[A_CELLS]);
3059 FFireAngle := FAngle;
3060 f := True;
3061 DidFire := True;
3062 end;
3064 WEAPON_BFG:
3065 if (FAmmo[A_CELLS] >= 40) and (FBFGFireCounter = -1) then
3066 begin
3067 FBFGFireCounter := 17;
3068 if not FNoReload then
3069 g_Sound_PlayExAt('SOUND_WEAPON_STARTFIREBFG', FObj.X, FObj.Y);
3070 Dec(FAmmo[A_CELLS], 40);
3071 DidFire := True;
3072 end;
3074 WEAPON_SUPERPULEMET:
3075 if FAmmo[A_SHELLS] > 0 then
3076 begin
3077 g_Weapon_shotgun(wx, wy, xd, yd, FUID);
3078 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx, wy);
3079 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3080 Dec(FAmmo[A_SHELLS]);
3081 FFireAngle := FAngle;
3082 f := True;
3083 DidFire := True;
3084 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
3085 GameVelX, GameVelY-2, SHELL_SHELL);
3086 end;
3088 WEAPON_FLAMETHROWER:
3089 if FAmmo[A_FUEL] > 0 then
3090 begin
3091 g_Weapon_flame(wx, wy, xd, yd, FUID);
3092 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
3093 Dec(FAmmo[A_FUEL]);
3094 FFireAngle := FAngle;
3095 f := True;
3096 DidFire := True;
3097 end;
3098 end;
3100 if g_Game_IsNet then
3101 begin
3102 if DidFire then
3103 begin
3104 if FCurrWeap <> WEAPON_BFG then
3105 MH_SEND_PlayerFire(FUID, FCurrWeap, wx, wy, xd, yd, LastShotID)
3106 else
3107 if not FNoReload then
3108 MH_SEND_Sound(FObj.X, FObj.Y, 'SOUND_WEAPON_STARTFIREBFG');
3109 end;
3111 MH_SEND_PlayerStats(FUID);
3112 end;
3114 if not f then Exit;
3116 if (FAngle = 0) or (FAngle = 180) then SetAction(A_ATTACK)
3117 else if (FAngle = ANGLE_LEFTDOWN) or (FAngle = ANGLE_RIGHTDOWN) then SetAction(A_ATTACKDOWN)
3118 else if (FAngle = ANGLE_LEFTUP) or (FAngle = ANGLE_RIGHTUP) then SetAction(A_ATTACKUP);
3119 end;
3121 function TPlayer.GetAmmoByWeapon(Weapon: Byte): Word;
3122 begin
3123 case Weapon of
3124 WEAPON_PISTOL, WEAPON_CHAINGUN: Result := FAmmo[A_BULLETS];
3125 WEAPON_SHOTGUN1, WEAPON_SHOTGUN2, WEAPON_SUPERPULEMET: Result := FAmmo[A_SHELLS];
3126 WEAPON_ROCKETLAUNCHER: Result := FAmmo[A_ROCKETS];
3127 WEAPON_PLASMA, WEAPON_BFG: Result := FAmmo[A_CELLS];
3128 WEAPON_FLAMETHROWER: Result := FAmmo[A_FUEL];
3129 else Result := 0;
3130 end;
3131 end;
3133 function TPlayer.HeadInLiquid(XInc, YInc: Integer): Boolean;
3134 begin
3135 Result := g_Map_CollidePanel(FObj.X+PLAYER_HEADRECT.X+XInc, FObj.Y+PLAYER_HEADRECT.Y+YInc,
3136 PLAYER_HEADRECT.Width, PLAYER_HEADRECT.Height,
3137 PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, True);
3138 end;
3140 procedure TPlayer.JetpackOn;
3141 begin
3142 FJetSoundFly.Stop;
3143 FJetSoundOff.Stop;
3144 FJetSoundOn.SetPosition(0);
3145 FJetSoundOn.PlayAt(FObj.X, FObj.Y);
3146 FlySmoke(8);
3147 end;
3149 procedure TPlayer.JetpackOff;
3150 begin
3151 FJetSoundFly.Stop;
3152 FJetSoundOn.Stop;
3153 FJetSoundOff.SetPosition(0);
3154 FJetSoundOff.PlayAt(FObj.X, FObj.Y);
3155 end;
3157 procedure TPlayer.CatchFire(Attacker: Word);
3158 begin
3159 FFireTime := 100;
3160 FFireAttacker := Attacker;
3161 if g_Game_IsNet and g_Game_IsServer then
3162 MH_SEND_PlayerStats(FUID);
3163 end;
3165 procedure TPlayer.Jump();
3166 begin
3167 if gFly or FJetpack then
3168 begin
3169 // Ïîëåò (÷èò-êîä èëè äæåòïàê):
3170 if FObj.Vel.Y > -VEL_FLY then
3171 FObj.Vel.Y := FObj.Vel.Y - 3;
3172 if FJetpack then
3173 begin
3174 if FJetFuel > 0 then
3175 Dec(FJetFuel);
3176 if (FJetFuel < 1) and g_Game_IsServer then
3177 begin
3178 FJetpack := False;
3179 JetpackOff;
3180 if g_Game_IsNet then
3181 MH_SEND_PlayerStats(FUID);
3182 end;
3183 end;
3184 Exit;
3185 end;
3187 // Íå âêëþ÷àòü äæåòïàê â ðåæèìå ïðîõîæäåíèÿ ñêâîçü ñòåíû
3188 if FGhost then
3189 FCanJetpack := False;
3191 // Ïðûãàåì èëè âñïëûâàåì:
3192 if (CollideLevel(0, 1) or
3193 g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y+36, PLAYER_RECT.Width,
3194 PLAYER_RECT.Height-33, PANEL_STEP, False)
3195 ) and (FObj.Accel.Y = 0) then // Íå ïðûãàòü, åñëè åñòü âåðòèêàëüíîå óñêîðåíèå
3196 begin
3197 FObj.Vel.Y := -VEL_JUMP;
3198 FCanJetpack := False;
3199 end
3200 else
3201 begin
3202 if BodyInLiquid(0, 0) then
3203 FObj.Vel.Y := -VEL_SW
3204 else if (FJetFuel > 0) and FCanJetpack and
3205 g_Game_IsServer and (not g_Obj_CollideLiquid(@FObj, 0, 0)) then
3206 begin
3207 FJetpack := True;
3208 JetpackOn;
3209 if g_Game_IsNet then
3210 MH_SEND_PlayerStats(FUID);
3211 end;
3212 end;
3213 end;
3215 procedure TPlayer.Kill(KillType: Byte; SpawnerUID: Word; t: Byte);
3216 var
3217 a, i, k, ab, ar: Byte;
3218 s: String;
3219 mon: TMonster;
3220 plr: TPlayer;
3221 srv, netsrv: Boolean;
3222 DoFrags: Boolean;
3223 OldLR: Byte;
3224 KP: TPlayer;
3225 it: PItem;
3227 procedure PushItem(t: Byte);
3228 var
3229 id: DWORD;
3230 begin
3231 id := g_Items_Create(FObj.X, FObj.Y, t, True, False);
3232 it := g_Items_ByIdx(id);
3233 if KillType = K_EXTRAHARDKILL then // -7..+7; -8..0
3234 begin
3235 g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-7+Random(15),
3236 (FObj.Vel.Y div 2)-Random(9));
3237 it.positionChanged(); // this updates spatial accelerators
3238 end
3239 else
3240 begin
3241 if KillType = K_HARDKILL then // -5..+5; -5..0
3242 begin
3243 g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-5+Random(11),
3244 (FObj.Vel.Y div 2)-Random(6));
3245 end
3246 else // -3..+3; -3..0
3247 begin
3248 g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-3+Random(7),
3249 (FObj.Vel.Y div 2)-Random(4));
3250 end;
3251 it.positionChanged(); // this updates spatial accelerators
3252 end;
3254 if g_Game_IsNet and g_Game_IsServer then
3255 MH_SEND_ItemSpawn(True, id);
3256 end;
3258 begin
3259 DoFrags := (gGameSettings.MaxLives = 0) or (gGameSettings.GameMode = GM_COOP);
3260 Srv := g_Game_IsServer;
3261 Netsrv := g_Game_IsServer and g_Game_IsNet;
3262 if Srv then FDeath := FDeath + 1;
3263 if FAlive then
3264 begin
3265 if FGhost then
3266 FGhost := False;
3267 if not FPhysics then
3268 FPhysics := True;
3269 FAlive := False;
3270 end;
3271 FShellTimer := -1;
3273 if (gGameSettings.MaxLives > 0) and Srv and (gLMSRespawn = LMS_RESPAWN_NONE) then
3274 begin
3275 if FLives > 0 then FLives := FLives - 1;
3276 if FLives = 0 then FNoRespawn := True;
3277 end;
3279 // Íîìåð òèïà ñìåðòè:
3280 a := 1;
3281 case KillType of
3282 K_SIMPLEKILL: a := 1;
3283 K_HARDKILL: a := 2;
3284 K_EXTRAHARDKILL: a := 3;
3285 K_FALLKILL: a := 4;
3286 end;
3288 // Çâóê ñìåðòè:
3289 if not FModel.PlaySound(MODELSOUND_DIE, a, FObj.X, FObj.Y) then
3290 for i := 1 to 3 do
3291 if FModel.PlaySound(MODELSOUND_DIE, i, FObj.X, FObj.Y) then
3292 Break;
3294 // Âðåìÿ ðåñïàóíà:
3295 if Srv then
3296 case KillType of
3297 K_SIMPLEKILL:
3298 FTime[T_RESPAWN] := gTime + TIME_RESPAWN1;
3299 K_HARDKILL:
3300 FTime[T_RESPAWN] := gTime + TIME_RESPAWN2;
3301 K_EXTRAHARDKILL, K_FALLKILL:
3302 FTime[T_RESPAWN] := gTime + TIME_RESPAWN3;
3303 end;
3305 // Ïåðåêëþ÷àåì ñîñòîÿíèå:
3306 case KillType of
3307 K_SIMPLEKILL:
3308 SetAction(A_DIE1);
3309 K_HARDKILL, K_EXTRAHARDKILL:
3310 SetAction(A_DIE2);
3311 end;
3313 // Ðåàêöèÿ ìîíñòðîâ íà ñìåðòü èãðîêà:
3314 if (KillType <> K_FALLKILL) and (Srv) then
3315 g_Monsters_killedp();
3317 if SpawnerUID = FUID then
3318 begin // Ñàìîóáèëñÿ
3319 if Srv and (DoFrags or (gGameSettings.GameMode = GM_TDM)) then
3320 begin
3321 Dec(FFrags);
3322 FLastFrag := 0;
3323 end;
3324 g_Console_Add(Format(_lc[I_PLAYER_KILL_SELF], [FName]), True);
3325 end
3326 else
3327 if g_GetUIDType(SpawnerUID) = UID_PLAYER then
3328 begin // Óáèò äðóãèì èãðîêîì
3329 KP := g_Player_Get(SpawnerUID);
3330 if (KP <> nil) and Srv then
3331 begin
3332 if (DoFrags or (gGameSettings.GameMode = GM_TDM)) then
3333 if SameTeam(FUID, SpawnerUID) then
3334 begin
3335 Dec(KP.FFrags);
3336 KP.FLastFrag := 0;
3337 end else
3338 begin
3339 Inc(KP.FFrags);
3340 KP.FragCombo();
3341 end;
3343 if (gGameSettings.GameMode = GM_TDM) and DoFrags then
3344 Inc(gTeamStat[KP.Team].Goals,
3345 IfThen(SameTeam(FUID, SpawnerUID), -1, 1));
3347 if netsrv then MH_SEND_PlayerStats(SpawnerUID);
3348 end;
3350 plr := g_Player_Get(SpawnerUID);
3351 if plr = nil then
3352 s := '?'
3353 else
3354 s := plr.FName;
3356 case KillType of
3357 K_HARDKILL:
3358 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_2],
3359 [FName, s]),
3360 gShowKillMsg);
3361 K_EXTRAHARDKILL:
3362 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_1],
3363 [FName, s]),
3364 gShowKillMsg);
3365 else
3366 g_Console_Add(Format(_lc[I_PLAYER_KILL],
3367 [FName, s]),
3368 gShowKillMsg);
3369 end;
3370 end
3371 else if g_GetUIDType(SpawnerUID) = UID_MONSTER then
3372 begin // Óáèò ìîíñòðîì
3373 mon := g_Monsters_ByUID(SpawnerUID);
3374 if mon = nil then
3375 s := '?'
3376 else
3377 s := g_Mons_GetKilledByTypeId(mon.MonsterType);
3379 case KillType of
3380 K_HARDKILL:
3381 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_2],
3382 [FName, s]),
3383 gShowKillMsg);
3384 K_EXTRAHARDKILL:
3385 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_1],
3386 [FName, s]),
3387 gShowKillMsg);
3388 else
3389 g_Console_Add(Format(_lc[I_PLAYER_KILL],
3390 [FName, s]),
3391 gShowKillMsg);
3392 end;
3393 end
3394 else // Îñîáûå òèïû ñìåðòè
3395 case t of
3396 HIT_DISCON: ;
3397 HIT_SELF: g_Console_Add(Format(_lc[I_PLAYER_KILL_SELF], [FName]), True);
3398 HIT_FALL: g_Console_Add(Format(_lc[I_PLAYER_KILL_FALL], [FName]), True);
3399 HIT_WATER: g_Console_Add(Format(_lc[I_PLAYER_KILL_WATER], [FName]), True);
3400 HIT_ACID: g_Console_Add(Format(_lc[I_PLAYER_KILL_ACID], [FName]), True);
3401 HIT_TRAP: g_Console_Add(Format(_lc[I_PLAYER_KILL_TRAP], [FName]), True);
3402 else g_Console_Add(Format(_lc[I_PLAYER_DIED], [FName]), True);
3403 end;
3405 if Srv then
3406 begin
3407 // Âûáðîñ îðóæèÿ:
3408 for a := WP_FIRST to WP_LAST do
3409 if FWeapon[a] then
3410 begin
3411 case a of
3412 WEAPON_SAW: i := ITEM_WEAPON_SAW;
3413 WEAPON_SHOTGUN1: i := ITEM_WEAPON_SHOTGUN1;
3414 WEAPON_SHOTGUN2: i := ITEM_WEAPON_SHOTGUN2;
3415 WEAPON_CHAINGUN: i := ITEM_WEAPON_CHAINGUN;
3416 WEAPON_ROCKETLAUNCHER: i := ITEM_WEAPON_ROCKETLAUNCHER;
3417 WEAPON_PLASMA: i := ITEM_WEAPON_PLASMA;
3418 WEAPON_BFG: i := ITEM_WEAPON_BFG;
3419 WEAPON_SUPERPULEMET: i := ITEM_WEAPON_SUPERPULEMET;
3420 WEAPON_FLAMETHROWER: i := ITEM_WEAPON_FLAMETHROWER;
3421 else i := 0;
3422 end;
3424 if i <> 0 then
3425 PushItem(i);
3426 end;
3428 // Âûáðîñ ðþêçàêà:
3429 if R_ITEM_BACKPACK in FRulez then
3430 PushItem(ITEM_AMMO_BACKPACK);
3432 // Âûáðîñ ðàêåòíîãî ðàíöà:
3433 if FJetFuel > 0 then
3434 PushItem(ITEM_JETPACK);
3436 // Âûáðîñ êëþ÷åé:
3437 if not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) then
3438 begin
3439 if R_KEY_RED in FRulez then
3440 PushItem(ITEM_KEY_RED);
3442 if R_KEY_GREEN in FRulez then
3443 PushItem(ITEM_KEY_GREEN);
3445 if R_KEY_BLUE in FRulez then
3446 PushItem(ITEM_KEY_BLUE);
3447 end;
3449 // Âûáðîñ ôëàãà:
3450 DropFlag();
3451 end;
3453 g_Player_CreateCorpse(Self);
3455 if Srv and (gGameSettings.MaxLives > 0) and FNoRespawn and
3456 (gLMSRespawn = LMS_RESPAWN_NONE) then
3457 begin
3458 a := 0;
3459 k := 0;
3460 ar := 0;
3461 ab := 0;
3462 for i := Low(gPlayers) to High(gPlayers) do
3463 begin
3464 if gPlayers[i] = nil then continue;
3465 if (not gPlayers[i].FNoRespawn) and (not gPlayers[i].FSpectator) then
3466 begin
3467 Inc(a);
3468 if gPlayers[i].FTeam = TEAM_RED then Inc(ar)
3469 else if gPlayers[i].FTeam = TEAM_BLUE then Inc(ab);
3470 k := i;
3471 end;
3472 end;
3474 OldLR := gLMSRespawn;
3475 if (gGameSettings.GameMode = GM_COOP) then
3476 begin
3477 if (a = 0) then
3478 begin
3479 // everyone is dead, restart the map
3480 g_Game_Message(_lc[I_MESSAGE_LMS_LOSE], 144);
3481 if Netsrv then
3482 MH_SEND_GameEvent(NET_EV_LMS_LOSE);
3483 gLMSRespawn := LMS_RESPAWN_FINAL;
3484 gLMSRespawnTime := gTime + 5000;
3485 end
3486 else if (a = 1) then
3487 begin
3488 if (gPlayers[k] <> nil) and not (gPlayers[k] is TBot) then
3489 if (gPlayers[k] = gPlayer1) or
3490 (gPlayers[k] = gPlayer2) then
3491 g_Console_Add('*** ' + _lc[I_MESSAGE_LMS_SURVIVOR] + ' ***', True)
3492 else if Netsrv and (gPlayers[k].FClientID >= 0) then
3493 MH_SEND_GameEvent(NET_EV_LMS_SURVIVOR, 0, 'N', gPlayers[k].FClientID);
3494 end;
3495 end
3496 else if (gGameSettings.GameMode = GM_TDM) then
3497 begin
3498 if (ab = 0) and (ar <> 0) then
3499 begin
3500 // blu team ded
3501 g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 144);
3502 if Netsrv then
3503 MH_SEND_GameEvent(NET_EV_TLMS_WIN, TEAM_RED);
3504 Inc(gTeamStat[TEAM_RED].Goals);
3505 gLMSRespawn := LMS_RESPAWN_FINAL;
3506 gLMSRespawnTime := gTime + 5000;
3507 end
3508 else if (ar = 0) and (ab <> 0) then
3509 begin
3510 // red team ded
3511 g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 144);
3512 if Netsrv then
3513 MH_SEND_GameEvent(NET_EV_TLMS_WIN, TEAM_BLUE);
3514 Inc(gTeamStat[TEAM_BLUE].Goals);
3515 gLMSRespawn := LMS_RESPAWN_FINAL;
3516 gLMSRespawnTime := gTime + 5000;
3517 end
3518 else if (ar = 0) and (ab = 0) then
3519 begin
3520 // everyone ded
3521 g_Game_Message(_lc[I_GAME_WIN_DRAW], 144);
3522 if Netsrv then
3523 MH_SEND_GameEvent(NET_EV_LMS_DRAW, 0, FName);
3524 gLMSRespawn := LMS_RESPAWN_FINAL;
3525 gLMSRespawnTime := gTime + 5000;
3526 end;
3527 end
3528 else if (gGameSettings.GameMode = GM_DM) then
3529 begin
3530 if (a = 1) then
3531 begin
3532 if gPlayers[k] <> nil then
3533 with gPlayers[k] do
3534 begin
3535 // survivor is the winner
3536 g_Game_Message(Format(_lc[I_MESSAGE_LMS_WIN], [AnsiUpperCase(FName)]), 144);
3537 if Netsrv then
3538 MH_SEND_GameEvent(NET_EV_LMS_WIN, 0, FName);
3539 Inc(FFrags);
3540 end;
3541 gLMSRespawn := LMS_RESPAWN_FINAL;
3542 gLMSRespawnTime := gTime + 5000;
3543 end
3544 else if (a = 0) then
3545 begin
3546 // everyone is dead, restart the map
3547 g_Game_Message(_lc[I_GAME_WIN_DRAW], 144);
3548 if Netsrv then
3549 MH_SEND_GameEvent(NET_EV_LMS_DRAW, 0, FName);
3550 gLMSRespawn := LMS_RESPAWN_FINAL;
3551 gLMSRespawnTime := gTime + 5000;
3552 end;
3553 end;
3554 if srv and (OldLR = LMS_RESPAWN_NONE) and (gLMSRespawn > LMS_RESPAWN_NONE) then
3555 begin
3556 if NetMode = NET_SERVER then
3557 MH_SEND_GameEvent(NET_EV_LMS_WARMUP, (gLMSRespawnTime - gTime) div 1000)
3558 else
3559 g_Console_Add(Format(_lc[I_MSG_WARMUP_START], [(gLMSRespawnTime - gTime) div 1000]), True);
3560 end;
3561 end;
3563 if Netsrv then
3564 begin
3565 MH_SEND_PlayerStats(FUID);
3566 MH_SEND_PlayerDeath(FUID, KillType, t, SpawnerUID);
3567 if gGameSettings.GameMode = GM_TDM then MH_SEND_GameStats;
3568 end;
3570 if srv and FNoRespawn then Spectate(True);
3571 FWantsInGame := True;
3572 end;
3574 function TPlayer.BodyInLiquid(XInc, YInc: Integer): Boolean;
3575 begin
3576 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc, PLAYER_RECT.Width,
3577 PLAYER_RECT.Height-20, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, False);
3578 end;
3580 function TPlayer.BodyInAcid(XInc, YInc: Integer): Boolean;
3581 begin
3582 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc, PLAYER_RECT.Width,
3583 PLAYER_RECT.Height-20, PANEL_ACID1 or PANEL_ACID2, False);
3584 end;
3586 procedure TPlayer.MakeBloodSimple(Count: Word);
3587 begin
3588 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)+8,
3589 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
3590 Count div 2, 3, -1, 16, (PLAYER_RECT.Height*2 div 3),
3591 FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind);
3592 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-8,
3593 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
3594 Count div 2, -3, -1, 16, (PLAYER_RECT.Height*2) div 3,
3595 FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind);
3596 end;
3598 procedure TPlayer.MakeBloodVector(Count: Word; VelX, VelY: Integer);
3599 begin
3600 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
3601 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
3602 Count, VelX, VelY, 16, (PLAYER_RECT.Height*2) div 3,
3603 FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind);
3604 end;
3606 procedure TPlayer.QueueWeaponSwitch(Weapon: Byte);
3607 begin
3608 //if g_Game_IsClient then Exit;
3609 if Weapon > High(FWeapon) then Exit;
3610 FNextWeap := FNextWeap or (1 shl Weapon);
3611 end;
3613 procedure TPlayer.resetWeaponQueue ();
3614 begin
3615 FNextWeap := 0;
3616 FNextWeapDelay := 0;
3617 end;
3619 function TPlayer.hasAmmoForWeapon (weapon: Byte): Boolean;
3620 begin
3621 result := false;
3622 case weapon of
3623 WEAPON_KASTET, WEAPON_SAW: result := true;
3624 WEAPON_SHOTGUN1, WEAPON_SHOTGUN2, WEAPON_SUPERPULEMET: result := (FAmmo[A_SHELLS] > 0);
3625 WEAPON_PISTOL, WEAPON_CHAINGUN: result := (FAmmo[A_BULLETS] > 0);
3626 WEAPON_ROCKETLAUNCHER: result := (FAmmo[A_ROCKETS] > 0);
3627 WEAPON_PLASMA, WEAPON_BFG: result := (FAmmo[A_CELLS] > 0);
3628 WEAPON_FLAMETHROWER: result := (FAmmo[A_FUEL] > 0);
3629 else result := (weapon < length(FWeapon));
3630 end;
3631 end;
3633 // return 255 for "no switch"
3634 function TPlayer.getNextWeaponIndex (): Byte;
3635 var
3636 i: Word;
3637 wantThisWeapon: array[0..64] of Boolean;
3638 weaponOrder: array[0..16] of Integer; // value: index in `FWeapon`
3639 wwc: Integer;
3640 f, dir, cwi, rwidx, curlidx: Integer;
3642 function real2log (ridx: Integer): Integer;
3643 var
3644 f: Integer;
3645 begin
3646 if (ridx >= 0) then
3647 begin
3648 for f := 0 to High(weaponOrder) do if (weaponOrder[f] = ridx) then begin result := f; exit; end;
3649 end;
3650 result := -1;
3651 end;
3653 begin
3654 result := 255; // default result: "no switch"
3656 // had weapon cycling on previous frame? remove that flag
3657 if (FNextWeap and $2000) <> 0 then
3658 begin
3659 FNextWeap := FNextWeap and $1FFF;
3660 FNextWeapDelay := 0;
3661 end;
3663 for f := 0 to High(weaponOrder) do weaponOrder[f] := -1;
3665 // build weapon order (k8: i know, i know, learn how to do constants and such... gtfo, please!)
3666 // two knuckles are for "normal" and "berserk" (see `if` below -- it removes the one that is not needed)
3667 // priorities:
3668 // bfg, launcher, plasma, flamethrower, ssg, minigun, sg, pistol, berserk, chainsaw, fist
3669 weaponOrder[0] := WEAPON_SUPERPULEMET;
3670 weaponOrder[1] := WEAPON_BFG;
3671 weaponOrder[2] := WEAPON_ROCKETLAUNCHER;
3672 weaponOrder[3] := WEAPON_PLASMA;
3673 weaponOrder[4] := WEAPON_FLAMETHROWER;
3674 weaponOrder[5] := WEAPON_SHOTGUN2;
3675 weaponOrder[6] := WEAPON_CHAINGUN;
3676 weaponOrder[7] := WEAPON_SHOTGUN1;
3677 weaponOrder[8] := WEAPON_PISTOL;
3678 weaponOrder[9] := WEAPON_KASTET+666; // berserk fist
3679 weaponOrder[10] := WEAPON_SAW;
3680 weaponOrder[11] := WEAPON_KASTET; // normal fist
3682 for f := 0 to High(weaponOrder) do
3683 begin
3684 if (weaponOrder[f] = WEAPON_KASTET) then
3685 begin
3686 // normal fist: remove if we have a berserk pack
3687 if (R_BERSERK in FRulez) then weaponOrder[f] := -1;
3688 end
3689 else
3690 if (weaponOrder[f] = WEAPON_KASTET+666) then
3691 begin
3692 // berserk fist: remove if we don't have a berserk pack
3693 if (R_BERSERK in FRulez) then weaponOrder[f] := WEAPON_KASTET else weaponOrder[f] := -1;
3694 end;
3695 end;
3697 (*
3698 WEAPON_KASTET = 0;
3699 WEAPON_SAW = 1;
3700 WEAPON_PISTOL = 2;
3701 WEAPON_SHOTGUN1 = 3;
3702 WEAPON_SHOTGUN2 = 4;
3703 WEAPON_CHAINGUN = 5;
3704 WEAPON_ROCKETLAUNCHER = 6;
3705 WEAPON_PLASMA = 7;
3706 WEAPON_BFG = 8;
3707 WEAPON_SUPERPULEMET = 9;
3708 WEAPON_FLAMETHROWER = 10;
3709 *)
3711 // cycling has priority
3712 if (FNextWeap and $C000) <> 0 then
3713 begin
3714 if (FNextWeap and $8000) <> 0 then dir := 1 else dir := -1; // should be reversed if we want "priority-driven cycling"
3715 FNextWeap := FNextWeap or $2000; // we need this
3716 if FNextWeapDelay > 0 then exit; // cooldown time
3717 //cwi := real2log(FCurrWeap);
3718 //if (cwi < 0) then cwi := 0;
3719 cwi := FCurrWeap;
3720 for i := 0 to High(FWeapon) do
3721 begin
3722 cwi := (cwi+length(FWeapon)+dir) mod length(FWeapon);
3723 //rwidx := weaponOrder[cwi];
3724 rwidx := cwi; // sorry
3725 if (rwidx < 0) then continue;
3726 if FWeapon[rwidx] then
3727 begin
3728 //e_WriteLog(Format(' SWITCH: cur=%d; new=%d (dir=%d; log=%d)', [FCurrWeap, rwidx, dir, cwi]), TMsgType.Warning);
3729 result := Byte(rwidx);
3730 //FNextWeapDelay := 10; //k8: not needed anymore
3731 exit;
3732 end;
3733 end;
3734 resetWeaponQueue();
3735 exit;
3736 end;
3738 // no cycling
3739 for i := 0 to High(wantThisWeapon) do wantThisWeapon[i] := false;
3740 wwc := 0;
3742 curlidx := -1; // start from a weapon with a highest priority (-1, 'cause loop will immediately increment this index)
3744 for i := 0 to High(FWeapon) do
3745 begin
3746 if (FNextWeap and (1 shl i)) <> 0 then
3747 begin
3748 cwi := real2log(i);
3749 if (cwi >= 0) then
3750 begin
3751 wantThisWeapon[cwi] := true;
3752 Inc(wwc);
3753 // if we hit currently selected weapon, start seachring from it, so we'll get "next weaker" weapon
3754 if (i = FCurrWeap) then curlidx := cwi; // compare real, start from logical
3755 end;
3756 end;
3757 end;
3759 // slow down alterations a little
3760 if (wwc > 1) then
3761 begin
3762 // more than one weapon requested, assume "alteration", and check alteration delay
3763 if FNextWeapDelay > 0 then
3764 begin
3765 //e_WriteLog(Format(' FNextWeap=%x; delay=%d', [FNextWeap, FNextWeapDelay]), TMsgType.Warning);
3766 FNextWeap := 0;
3767 exit;
3768 end; // yeah
3769 end;
3771 // do not reset weapon queue, it will be done in `RealizeCurrentWeapon()`
3772 // but clear all counters if no weapon should be switched
3773 if (wwc < 1) then
3774 begin
3775 resetWeaponQueue();
3776 exit;
3777 end;
3779 //e_WriteLog(Format('*** wwc=%d; currweap=%d; logweap=%d', [wwc, FCurrWeap, curlidx]), TMsgType.Warning);
3781 // find next weapon to switch onto
3782 cwi := curlidx;
3783 for i := 0 to High(weaponOrder) do
3784 begin
3785 cwi := (cwi+length(weaponOrder)+1) mod length(weaponOrder);
3786 if (cwi = curlidx) then continue; // skip current weapon
3787 if not wantThisWeapon[cwi] then continue;
3788 rwidx := weaponOrder[cwi];
3789 if (rwidx < 0) then continue;
3790 //e_WriteLog(Format(' trying logical %d (real %d); has=%d, hasammo=%d', [cwi, rwidx, Integer(FWeapon[rwidx]), Integer(hasAmmoForWeapon(rwidx))]), TMsgType.Warning);
3791 if FWeapon[rwidx] and ((wwc = 1) or hasAmmoForWeapon(rwidx)) then
3792 begin
3793 //e_WriteLog(' I FOUND HER!', TMsgType.Warning);
3794 // i found her!
3795 result := Byte(rwidx);
3796 resetWeaponQueue();
3797 //FNextWeapDelay := 10; // anyway, 'cause why not; k8: not needed anymore
3798 exit;
3799 end;
3800 end;
3802 // no suitable weapon found, so reset the queue, to avoid accidental "queuing" of weapon w/o ammo
3803 resetWeaponQueue();
3804 end;
3806 procedure TPlayer.RealizeCurrentWeapon();
3807 function switchAllowed (): Boolean;
3808 var
3809 i: Byte;
3810 begin
3811 result := false;
3812 if FBFGFireCounter <> -1 then
3813 exit;
3814 if FTime[T_SWITCH] > gTime then
3815 exit;
3816 for i := WP_FIRST to WP_LAST do
3817 if FReloading[i] > 0 then
3818 exit;
3819 result := true;
3820 end;
3822 var
3823 nw: Byte;
3824 begin
3825 //e_WriteLog(Format('***RealizeCurrentWeapon: FNextWeap=%x; FNextWeapDelay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
3826 //FNextWeap := FNextWeap and $1FFF;
3827 //HACK: alteration delay will be reset when player released any weapon switch key
3828 FNextWeapDelay := 0; //k8: just in case
3829 //if FNextWeapDelay > 0 then Dec(FNextWeapDelay); // "alteration delay"
3831 if not switchAllowed then
3832 begin
3833 //HACK for weapon cycling
3834 if (FNextWeap and $E000) <> 0 then FNextWeap := 0;
3835 exit;
3836 end;
3838 nw := getNextWeaponIndex();
3839 if nw = 255 then exit; // don't reset anything here
3840 if nw > High(FWeapon) then
3841 begin
3842 // don't forget to reset queue here!
3843 //e_WriteLog(Format(' RealizeCurrentWeapon: WUTAFUUUU?! (%d)', [nw]), TMsgType.Warning);
3844 resetWeaponQueue();
3845 exit;
3846 end;
3848 if FWeapon[nw] then
3849 begin
3850 //k8: emulate this on client immediately, or wait for server confirmation?
3852 if g_Game_IsClient then
3853 begin
3854 FNetForceWeap := nw;
3855 //FNetForceWeapFIdx := FCurrFrameIdx+5; // force for ~5 frames
3856 FNetForceWeapFIdx := gTime+5; // force for ~5 frames
3857 writeln('NETWEAPONSWITCH: fw=', NetForceWeap, '; fwfidx=', NetForceWeapFIdx, '; cfrm=', gTime);
3858 end;
3860 FNetForceWeapFIdx := gTime+5; // force for ~5 frames
3861 FCurrWeap := nw;
3862 if nw = WEAPON_SAW then FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
3863 FModel.SetWeapon(nw);
3864 FTime[T_SWITCH] := gTime+156;
3865 if g_Game_IsNet then MH_SEND_PlayerStats(FUID);
3866 //if g_Game_IsNet and g_Game_IsClient then
3867 end;
3868 end;
3870 procedure TPlayer.NextWeapon();
3871 begin
3872 //if g_Game_IsClient then Exit;
3873 FNextWeap := $8000;
3874 end;
3876 procedure TPlayer.PrevWeapon();
3877 begin
3878 //if g_Game_IsClient then Exit;
3879 FNextWeap := $4000;
3880 end;
3882 // used by network layer
3883 procedure TPlayer.SetWeapon(W: Byte);
3884 begin
3885 if FCurrWeap <> W then
3886 if (W = WEAPON_SAW) then
3887 FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
3889 FCurrWeap := W;
3890 FModel.SetWeapon(CurrWeap);
3891 //if g_Game_IsClient then resetWeaponQueue();
3892 end;
3894 function TPlayer.PickItem(ItemType: Byte; arespawn: Boolean; var remove: Boolean): Boolean;
3896 function allowBerserkSwitching (): Boolean;
3897 begin
3898 if (FBFGFireCounter <> -1) then begin result := false; exit; end;
3899 result := true;
3900 if gBerserkAutoswitch then exit;
3901 if not conIsCheatsEnabled then exit;
3902 result := false;
3903 end;
3905 var
3906 a: Boolean;
3907 begin
3908 Result := False;
3909 if g_Game_IsClient then Exit;
3911 // a = true - ìåñòî ñïàâíà ïðåäìåòà:
3912 a := LongBool(gGameSettings.Options and GAME_OPTION_WEAPONSTAY) and arespawn;
3913 remove := not a;
3915 case ItemType of
3916 ITEM_MEDKIT_SMALL:
3917 if FHealth < PLAYER_HP_SOFT then
3918 begin
3919 IncMax(FHealth, 10, PLAYER_HP_SOFT);
3920 Result := True;
3921 remove := True;
3922 FFireTime := 0;
3923 if gFlash = 2 then Inc(FPickup, 5);
3924 end;
3926 ITEM_MEDKIT_LARGE:
3927 if FHealth < PLAYER_HP_SOFT then
3928 begin
3929 IncMax(FHealth, 25, PLAYER_HP_SOFT);
3930 Result := True;
3931 remove := True;
3932 FFireTime := 0;
3933 if gFlash = 2 then Inc(FPickup, 5);
3934 end;
3936 ITEM_ARMOR_GREEN:
3937 if FArmor < PLAYER_AP_SOFT then
3938 begin
3939 FArmor := PLAYER_AP_SOFT;
3940 Result := True;
3941 remove := True;
3942 if gFlash = 2 then Inc(FPickup, 5);
3943 end;
3945 ITEM_ARMOR_BLUE:
3946 if FArmor < PLAYER_AP_LIMIT then
3947 begin
3948 FArmor := PLAYER_AP_LIMIT;
3949 Result := True;
3950 remove := True;
3951 if gFlash = 2 then Inc(FPickup, 5);
3952 end;
3954 ITEM_SPHERE_BLUE:
3955 if FHealth < PLAYER_HP_LIMIT then
3956 begin
3957 IncMax(FHealth, 100, PLAYER_HP_LIMIT);
3958 Result := True;
3959 remove := True;
3960 FFireTime := 0;
3961 if gFlash = 2 then Inc(FPickup, 5);
3962 end;
3964 ITEM_SPHERE_WHITE:
3965 if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) then
3966 begin
3967 if FHealth < PLAYER_HP_LIMIT then
3968 FHealth := PLAYER_HP_LIMIT;
3969 if FArmor < PLAYER_AP_LIMIT then
3970 FArmor := PLAYER_AP_LIMIT;
3971 Result := True;
3972 remove := True;
3973 FFireTime := 0;
3974 if gFlash = 2 then Inc(FPickup, 5);
3975 end;
3977 ITEM_WEAPON_SAW:
3978 if (not FWeapon[WEAPON_SAW]) or ((not arespawn) and (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) then
3979 begin
3980 FWeapon[WEAPON_SAW] := True;
3981 Result := True;
3982 if gFlash = 2 then Inc(FPickup, 5);
3983 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3984 end;
3986 ITEM_WEAPON_SHOTGUN1:
3987 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SHOTGUN1] then
3988 begin
3989 // Íóæíî, ÷òîáû íå âçÿòü âñå ïóëè ñðàçó:
3990 if a and FWeapon[WEAPON_SHOTGUN1] then Exit;
3992 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
3993 FWeapon[WEAPON_SHOTGUN1] := True;
3994 Result := True;
3995 if gFlash = 2 then Inc(FPickup, 5);
3996 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3997 end;
3999 ITEM_WEAPON_SHOTGUN2:
4000 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SHOTGUN2] then
4001 begin
4002 if a and FWeapon[WEAPON_SHOTGUN2] then Exit;
4004 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
4005 FWeapon[WEAPON_SHOTGUN2] := True;
4006 Result := True;
4007 if gFlash = 2 then Inc(FPickup, 5);
4008 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4009 end;
4011 ITEM_WEAPON_CHAINGUN:
4012 if (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or not FWeapon[WEAPON_CHAINGUN] then
4013 begin
4014 if a and FWeapon[WEAPON_CHAINGUN] then Exit;
4016 IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
4017 FWeapon[WEAPON_CHAINGUN] := True;
4018 Result := True;
4019 if gFlash = 2 then Inc(FPickup, 5);
4020 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4021 end;
4023 ITEM_WEAPON_ROCKETLAUNCHER:
4024 if (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or not FWeapon[WEAPON_ROCKETLAUNCHER] then
4025 begin
4026 if a and FWeapon[WEAPON_ROCKETLAUNCHER] then Exit;
4028 IncMax(FAmmo[A_ROCKETS], 2, FMaxAmmo[A_ROCKETS]);
4029 FWeapon[WEAPON_ROCKETLAUNCHER] := True;
4030 Result := True;
4031 if gFlash = 2 then Inc(FPickup, 5);
4032 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4033 end;
4035 ITEM_WEAPON_PLASMA:
4036 if (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or not FWeapon[WEAPON_PLASMA] then
4037 begin
4038 if a and FWeapon[WEAPON_PLASMA] then Exit;
4040 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
4041 FWeapon[WEAPON_PLASMA] := True;
4042 Result := True;
4043 if gFlash = 2 then Inc(FPickup, 5);
4044 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4045 end;
4047 ITEM_WEAPON_BFG:
4048 if (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or not FWeapon[WEAPON_BFG] then
4049 begin
4050 if a and FWeapon[WEAPON_BFG] then Exit;
4052 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
4053 FWeapon[WEAPON_BFG] := True;
4054 Result := True;
4055 if gFlash = 2 then Inc(FPickup, 5);
4056 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4057 end;
4059 ITEM_WEAPON_SUPERPULEMET:
4060 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SUPERPULEMET] then
4061 begin
4062 if a and FWeapon[WEAPON_SUPERPULEMET] then Exit;
4064 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
4065 FWeapon[WEAPON_SUPERPULEMET] := True;
4066 Result := True;
4067 if gFlash = 2 then Inc(FPickup, 5);
4068 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4069 end;
4071 ITEM_WEAPON_FLAMETHROWER:
4072 if (FAmmo[A_FUEL] < FMaxAmmo[A_FUEL]) or not FWeapon[WEAPON_FLAMETHROWER] then
4073 begin
4074 if a and FWeapon[WEAPON_FLAMETHROWER] then Exit;
4076 IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
4077 FWeapon[WEAPON_FLAMETHROWER] := True;
4078 Result := True;
4079 if gFlash = 2 then Inc(FPickup, 5);
4080 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
4081 end;
4083 ITEM_AMMO_BULLETS:
4084 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
4085 begin
4086 IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
4087 Result := True;
4088 remove := True;
4089 if gFlash = 2 then Inc(FPickup, 5);
4090 end;
4092 ITEM_AMMO_BULLETS_BOX:
4093 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
4094 begin
4095 IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
4096 Result := True;
4097 remove := True;
4098 if gFlash = 2 then Inc(FPickup, 5);
4099 end;
4101 ITEM_AMMO_SHELLS:
4102 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
4103 begin
4104 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
4105 Result := True;
4106 remove := True;
4107 if gFlash = 2 then Inc(FPickup, 5);
4108 end;
4110 ITEM_AMMO_SHELLS_BOX:
4111 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
4112 begin
4113 IncMax(FAmmo[A_SHELLS], 25, FMaxAmmo[A_SHELLS]);
4114 Result := True;
4115 remove := True;
4116 if gFlash = 2 then Inc(FPickup, 5);
4117 end;
4119 ITEM_AMMO_ROCKET:
4120 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
4121 begin
4122 IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
4123 Result := True;
4124 remove := True;
4125 if gFlash = 2 then Inc(FPickup, 5);
4126 end;
4128 ITEM_AMMO_ROCKET_BOX:
4129 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
4130 begin
4131 IncMax(FAmmo[A_ROCKETS], 5, FMaxAmmo[A_ROCKETS]);
4132 Result := True;
4133 remove := True;
4134 if gFlash = 2 then Inc(FPickup, 5);
4135 end;
4137 ITEM_AMMO_CELL:
4138 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
4139 begin
4140 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
4141 Result := True;
4142 remove := True;
4143 if gFlash = 2 then Inc(FPickup, 5);
4144 end;
4146 ITEM_AMMO_CELL_BIG:
4147 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
4148 begin
4149 IncMax(FAmmo[A_CELLS], 100, FMaxAmmo[A_CELLS]);
4150 Result := True;
4151 remove := True;
4152 if gFlash = 2 then Inc(FPickup, 5);
4153 end;
4155 ITEM_AMMO_FUELCAN:
4156 if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then
4157 begin
4158 IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
4159 Result := True;
4160 remove := True;
4161 if gFlash = 2 then Inc(FPickup, 5);
4162 end;
4164 ITEM_AMMO_BACKPACK:
4165 if not(R_ITEM_BACKPACK in FRulez) or
4166 (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or
4167 (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or
4168 (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or
4169 (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or
4170 (FMaxAmmo[A_FUEL] < AmmoLimits[1, A_FUEL]) then
4171 begin
4172 FMaxAmmo[A_BULLETS] := AmmoLimits[1, A_BULLETS];
4173 FMaxAmmo[A_SHELLS] := AmmoLimits[1, A_SHELLS];
4174 FMaxAmmo[A_ROCKETS] := AmmoLimits[1, A_ROCKETS];
4175 FMaxAmmo[A_CELLS] := AmmoLimits[1, A_CELLS];
4176 FMaxAmmo[A_FUEL] := AmmoLimits[1, A_FUEL];
4178 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
4179 IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
4180 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
4181 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
4182 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
4183 IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
4184 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
4185 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
4187 FRulez := FRulez + [R_ITEM_BACKPACK];
4188 Result := True;
4189 remove := True;
4190 if gFlash = 2 then Inc(FPickup, 5);
4191 end;
4193 ITEM_KEY_RED:
4194 if not(R_KEY_RED in FRulez) then
4195 begin
4196 Include(FRulez, R_KEY_RED);
4197 Result := True;
4198 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
4199 if gFlash = 2 then Inc(FPickup, 5);
4200 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
4201 end;
4203 ITEM_KEY_GREEN:
4204 if not(R_KEY_GREEN in FRulez) then
4205 begin
4206 Include(FRulez, R_KEY_GREEN);
4207 Result := True;
4208 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
4209 if gFlash = 2 then Inc(FPickup, 5);
4210 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
4211 end;
4213 ITEM_KEY_BLUE:
4214 if not(R_KEY_BLUE in FRulez) then
4215 begin
4216 Include(FRulez, R_KEY_BLUE);
4217 Result := True;
4218 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
4219 if gFlash = 2 then Inc(FPickup, 5);
4220 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
4221 end;
4223 ITEM_SUIT:
4224 if FMegaRulez[MR_SUIT] < gTime+PLAYER_SUIT_TIME then
4225 begin
4226 FMegaRulez[MR_SUIT] := gTime+PLAYER_SUIT_TIME;
4227 Result := True;
4228 remove := True;
4229 FFireTime := 0;
4230 if gFlash = 2 then Inc(FPickup, 5);
4231 end;
4233 ITEM_OXYGEN:
4234 if FAir < AIR_MAX then
4235 begin
4236 FAir := AIR_MAX;
4237 Result := True;
4238 remove := True;
4239 if gFlash = 2 then Inc(FPickup, 5);
4240 end;
4242 ITEM_MEDKIT_BLACK:
4243 begin
4244 if not (R_BERSERK in FRulez) then
4245 begin
4246 Include(FRulez, R_BERSERK);
4247 if allowBerserkSwitching then
4248 begin
4249 FCurrWeap := WEAPON_KASTET;
4250 //FNetForceWeap := FCurrWeap;
4251 resetWeaponQueue();
4252 FModel.SetWeapon(WEAPON_KASTET);
4253 end;
4254 if gFlash <> 0 then
4255 begin
4256 Inc(FPain, 100);
4257 if gFlash = 2 then Inc(FPickup, 5);
4258 end;
4259 FBerserk := gTime+30000;
4260 Result := True;
4261 remove := True;
4262 FFireTime := 0;
4263 //k8:do we need it? if g_Game_IsNet and g_Game_IsServer then MH_SEND_PlayerStats(FUID);
4264 end;
4265 if FHealth < PLAYER_HP_SOFT then
4266 begin
4267 FHealth := PLAYER_HP_SOFT;
4268 FBerserk := gTime+30000;
4269 Result := True;
4270 remove := True;
4271 FFireTime := 0;
4272 end;
4273 end;
4275 ITEM_INVUL:
4276 if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
4277 begin
4278 FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
4279 Result := True;
4280 remove := True;
4281 if gFlash = 2 then Inc(FPickup, 5);
4282 end;
4284 ITEM_BOTTLE:
4285 if FHealth < PLAYER_HP_LIMIT then
4286 begin
4287 IncMax(FHealth, 4, PLAYER_HP_LIMIT);
4288 Result := True;
4289 remove := True;
4290 FFireTime := 0;
4291 if gFlash = 2 then Inc(FPickup, 5);
4292 end;
4294 ITEM_HELMET:
4295 if FArmor < PLAYER_AP_LIMIT then
4296 begin
4297 IncMax(FArmor, 5, PLAYER_AP_LIMIT);
4298 Result := True;
4299 remove := True;
4300 if gFlash = 2 then Inc(FPickup, 5);
4301 end;
4303 ITEM_JETPACK:
4304 if FJetFuel < JET_MAX then
4305 begin
4306 FJetFuel := JET_MAX;
4307 Result := True;
4308 remove := True;
4309 if gFlash = 2 then Inc(FPickup, 5);
4310 end;
4312 ITEM_INVIS:
4313 if FMegaRulez[MR_INVIS] < gTime+PLAYER_INVIS_TIME then
4314 begin
4315 FMegaRulez[MR_INVIS] := gTime+PLAYER_INVIS_TIME;
4316 Result := True;
4317 remove := True;
4318 if gFlash = 2 then Inc(FPickup, 5);
4319 end;
4320 end;
4321 end;
4323 procedure TPlayer.Touch();
4324 begin
4325 if not FAlive then
4326 Exit;
4327 //FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
4328 if FIamBot then
4329 begin
4330 // Áðîñèòü ôëàã òîâàðèùó:
4331 if gGameSettings.GameMode = GM_CTF then
4332 DropFlag();
4333 end;
4334 end;
4336 procedure TPlayer.Push(vx, vy: Integer);
4337 begin
4338 if (not FPhysics) and FGhost then
4339 Exit;
4340 FObj.Accel.X := FObj.Accel.X + vx;
4341 FObj.Accel.Y := FObj.Accel.Y + vy;
4342 if g_Game_IsNet and g_Game_IsServer then
4343 MH_SEND_PlayerPos(True, FUID, NET_EVERYONE);
4344 end;
4346 procedure TPlayer.Reset(Force: Boolean);
4347 begin
4348 if Force then
4349 FAlive := False;
4351 FSpawned := False;
4352 FTime[T_RESPAWN] := 0;
4353 FTime[T_FLAGCAP] := 0;
4354 FGodMode := False;
4355 FNoTarget := False;
4356 FNoReload := False;
4357 FFrags := 0;
4358 FLastFrag := 0;
4359 FComboEvnt := -1;
4360 FKills := 0;
4361 FMonsterKills := 0;
4362 FDeath := 0;
4363 FSecrets := 0;
4364 //FCurrFrameIdx := 0;
4365 //FNetForceWeap := FCurrWeap;
4366 FNetForceWeapFIdx := 0;
4367 if FNoRespawn then
4368 begin
4369 FSpectator := False;
4370 FGhost := False;
4371 FPhysics := True;
4372 FSpectatePlayer := -1;
4373 FNoRespawn := False;
4374 end;
4375 FLives := gGameSettings.MaxLives;
4377 SetFlag(FLAG_NONE);
4378 end;
4380 procedure TPlayer.SoftReset();
4381 begin
4382 ReleaseKeys();
4384 FDamageBuffer := 0;
4385 FIncCam := 0;
4386 FBFGFireCounter := -1;
4387 FShellTimer := -1;
4388 FPain := 0;
4389 FLastHit := 0;
4390 FLastFrag := 0;
4391 FComboEvnt := -1;
4392 FNetForceWeapFIdx := 0;
4394 SetFlag(FLAG_NONE);
4395 SetAction(A_STAND, True);
4396 end;
4398 function TPlayer.GetRespawnPoint(): Byte;
4399 var
4400 c: Byte;
4401 begin
4402 Result := 255;
4403 // Íà áóäóùåå: FSpawn - èãðîê óæå èãðàë è ïåðåðîæäàåòñÿ
4405 // Îäèíî÷íàÿ èãðà/êîîïåðàòèâ
4406 if gGameSettings.GameMode in [GM_COOP, GM_SINGLE] then
4407 begin
4408 if (Self = gPlayer1) or (Self = gPlayer2) then
4409 begin
4410 // Òî÷êà ïîÿâëåíèÿ ñâîåãî èãðîêà
4411 if Self = gPlayer1 then
4412 c := RESPAWNPOINT_PLAYER1
4413 else
4414 c := RESPAWNPOINT_PLAYER2;
4415 if g_Map_GetPointCount(c) > 0 then
4416 begin
4417 Result := c;
4418 Exit;
4419 end;
4421 // Òî÷êà ïîÿâëåíèÿ äðóãîãî èãðîêà
4422 if Self = gPlayer1 then
4423 c := RESPAWNPOINT_PLAYER2
4424 else
4425 c := RESPAWNPOINT_PLAYER1;
4426 if g_Map_GetPointCount(c) > 0 then
4427 begin
4428 Result := c;
4429 Exit;
4430 end;
4431 end else
4432 begin
4433 // Òî÷êà ïîÿâëåíèÿ ëþáîãî èãðîêà (áîòà)
4434 if Random(2) = 0 then
4435 c := RESPAWNPOINT_PLAYER1
4436 else
4437 c := RESPAWNPOINT_PLAYER2;
4438 if g_Map_GetPointCount(c) > 0 then
4439 begin
4440 Result := c;
4441 Exit;
4442 end;
4443 end;
4445 // Òî÷êà ëþáîé èç êîìàíä
4446 if Random(2) = 0 then
4447 c := RESPAWNPOINT_RED
4448 else
4449 c := RESPAWNPOINT_BLUE;
4450 if g_Map_GetPointCount(c) > 0 then
4451 begin
4452 Result := c;
4453 Exit;
4454 end;
4456 // Òî÷êà DM
4457 c := RESPAWNPOINT_DM;
4458 if g_Map_GetPointCount(c) > 0 then
4459 begin
4460 Result := c;
4461 Exit;
4462 end;
4463 end;
4465 // Ìÿñîïîâàë
4466 if gGameSettings.GameMode = GM_DM then
4467 begin
4468 // Òî÷êà DM
4469 c := RESPAWNPOINT_DM;
4470 if g_Map_GetPointCount(c) > 0 then
4471 begin
4472 Result := c;
4473 Exit;
4474 end;
4476 // Òî÷êà ïîÿâëåíèÿ ëþáîãî èãðîêà
4477 if Random(2) = 0 then
4478 c := RESPAWNPOINT_PLAYER1
4479 else
4480 c := RESPAWNPOINT_PLAYER2;
4481 if g_Map_GetPointCount(c) > 0 then
4482 begin
4483 Result := c;
4484 Exit;
4485 end;
4487 // Òî÷êà ëþáîé èç êîìàíä
4488 if Random(2) = 0 then
4489 c := RESPAWNPOINT_RED
4490 else
4491 c := RESPAWNPOINT_BLUE;
4492 if g_Map_GetPointCount(c) > 0 then
4493 begin
4494 Result := c;
4495 Exit;
4496 end;
4497 end;
4499 // Êîìàíäíûå
4500 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
4501 begin
4502 // Òî÷êà ñâîåé êîìàíäû
4503 c := RESPAWNPOINT_DM;
4504 if FTeam = TEAM_RED then
4505 c := RESPAWNPOINT_RED;
4506 if FTeam = TEAM_BLUE then
4507 c := RESPAWNPOINT_BLUE;
4508 if g_Map_GetPointCount(c) > 0 then
4509 begin
4510 Result := c;
4511 Exit;
4512 end;
4514 // Òî÷êà DM
4515 c := RESPAWNPOINT_DM;
4516 if g_Map_GetPointCount(c) > 0 then
4517 begin
4518 Result := c;
4519 Exit;
4520 end;
4522 // Òî÷êà ïîÿâëåíèÿ ëþáîãî èãðîêà
4523 if Random(2) = 0 then
4524 c := RESPAWNPOINT_PLAYER1
4525 else
4526 c := RESPAWNPOINT_PLAYER2;
4527 if g_Map_GetPointCount(c) > 0 then
4528 begin
4529 Result := c;
4530 Exit;
4531 end;
4533 // Òî÷êà äðóãîé êîìàíäû
4534 c := RESPAWNPOINT_DM;
4535 if FTeam = TEAM_RED then
4536 c := RESPAWNPOINT_BLUE;
4537 if FTeam = TEAM_BLUE then
4538 c := RESPAWNPOINT_RED;
4539 if g_Map_GetPointCount(c) > 0 then
4540 begin
4541 Result := c;
4542 Exit;
4543 end;
4544 end;
4545 end;
4547 procedure TPlayer.Respawn(Silent: Boolean; Force: Boolean = False);
4548 var
4549 RespawnPoint: TRespawnPoint;
4550 a, b, c: Byte;
4551 Anim: TAnimation;
4552 ID: DWORD;
4553 begin
4554 FIncCam := 0;
4555 FBFGFireCounter := -1;
4556 FShellTimer := -1;
4557 FPain := 0;
4558 FLastHit := 0;
4559 //FNetForceWeap := FCurrWeap;
4560 FNetForceWeapFIdx := 0;
4562 if not g_Game_IsServer then
4563 Exit;
4564 if FDummy then
4565 Exit;
4566 FWantsInGame := True;
4567 FJustTeleported := True;
4568 if Force then
4569 begin
4570 FTime[T_RESPAWN] := 0;
4571 FAlive := False;
4572 end;
4573 FNetTime := 0;
4574 // if server changes MaxLives we gotta be ready
4575 if gGameSettings.MaxLives = 0 then FNoRespawn := False;
4577 // Åùå íåëüçÿ âîçðîäèòüñÿ:
4578 if FTime[T_RESPAWN] > gTime then
4579 Exit;
4581 // Ïðîñðàë âñå æèçíè:
4582 if FNoRespawn then
4583 begin
4584 if not FSpectator then Spectate(True);
4585 FWantsInGame := True;
4586 Exit;
4587 end;
4589 if (gGameSettings.GameType <> GT_SINGLE) and (gGameSettings.GameMode <> GM_COOP) then
4590 begin // "Ñâîÿ èãðà"
4591 // Áåðñåðê íå ñîõðàíÿåòñÿ ìåæäó óðîâíÿìè:
4592 FRulez := FRulez-[R_BERSERK];
4593 end
4594 else // "Îäèíî÷íàÿ èãðà"/"Êîîï"
4595 begin
4596 // Áåðñåðê è êëþ÷è íå ñîõðàíÿþòñÿ ìåæäó óðîâíÿìè:
4597 FRulez := FRulez-[R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE, R_BERSERK];
4598 end;
4600 // Ïîëó÷àåì òî÷êó ñïàóíà èãðîêà:
4601 c := GetRespawnPoint();
4603 ReleaseKeys();
4604 SetFlag(FLAG_NONE);
4606 // Âîñêðåøåíèå áåç îðóæèÿ:
4607 if not FAlive then
4608 begin
4609 FHealth := PLAYER_HP_SOFT;
4610 FArmor := 0;
4611 FAlive := True;
4612 FAir := AIR_DEF;
4613 FJetFuel := 0;
4615 for a := WP_FIRST to WP_LAST do
4616 begin
4617 FWeapon[a] := False;
4618 FReloading[a] := 0;
4619 end;
4621 FWeapon[WEAPON_PISTOL] := True;
4622 FWeapon[WEAPON_KASTET] := True;
4623 FCurrWeap := WEAPON_PISTOL;
4624 //FNetForceWeap := FCurrWeap;
4625 resetWeaponQueue();
4627 FModel.SetWeapon(FCurrWeap);
4629 for b := A_BULLETS to A_HIGH do
4630 FAmmo[b] := 0;
4632 FAmmo[A_BULLETS] := 50;
4634 FMaxAmmo[A_BULLETS] := AmmoLimits[0, A_BULLETS];
4635 FMaxAmmo[A_SHELLS] := AmmoLimits[0, A_SHELLS];
4636 FMaxAmmo[A_ROCKETS] := AmmoLimits[0, A_SHELLS];
4637 FMaxAmmo[A_CELLS] := AmmoLimits[0, A_CELLS];
4638 FMaxAmmo[A_FUEL] := AmmoLimits[0, A_FUEL];
4640 if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then
4641 FRulez := [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE]
4642 else
4643 FRulez := [];
4644 end;
4646 // Ïîëó÷àåì êîîðäèíàòû òî÷êè âîçðîæäåíèÿ:
4647 if not g_Map_GetPoint(c, RespawnPoint) then
4648 begin
4649 g_FatalError(_lc[I_GAME_ERROR_GET_SPAWN]);
4650 Exit;
4651 end;
4653 // Óñòàíîâêà êîîðäèíàò è ñáðîñ âñåõ ïàðàìåòðîâ:
4654 FObj.X := RespawnPoint.X-PLAYER_RECT.X;
4655 FObj.Y := RespawnPoint.Y-PLAYER_RECT.Y;
4656 FObj.Vel.X := 0;
4657 FObj.Vel.Y := 0;
4658 FObj.Accel.X := 0;
4659 FObj.Accel.Y := 0;
4661 FDirection := RespawnPoint.Direction;
4662 if FDirection = TDirection.D_LEFT then
4663 FAngle := 180
4664 else
4665 FAngle := 0;
4667 SetAction(A_STAND, True);
4668 FModel.Direction := FDirection;
4670 for a := Low(FTime) to High(FTime) do
4671 FTime[a] := 0;
4673 for a := Low(FMegaRulez) to High(FMegaRulez) do
4674 FMegaRulez[a] := 0;
4676 FDamageBuffer := 0;
4677 FJetpack := False;
4678 FCanJetpack := False;
4679 FFireTime := 0;
4680 FFirePainTime := 0;
4681 FFireAttacker := 0;
4683 // Àíèìàöèÿ âîçðîæäåíèÿ:
4684 if (not gLoadGameMode) and (not Silent) then
4685 if g_Frames_Get(ID, 'FRAMES_TELEPORT') then
4686 begin
4687 Anim := TAnimation.Create(ID, False, 3);
4688 g_GFX_OnceAnim(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
4689 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, Anim);
4690 Anim.Free();
4691 end;
4693 FSpectator := False;
4694 FGhost := False;
4695 FPhysics := True;
4696 FSpectatePlayer := -1;
4697 FSpawned := True;
4699 if (gPlayer1 = nil) and (gLMSPID1 = FUID) then
4700 gPlayer1 := self;
4701 if (gPlayer2 = nil) and (gLMSPID2 = FUID) then
4702 gPlayer2 := self;
4704 //FNetForceWeap := FCurrWeap;
4706 if g_Game_IsNet then
4707 begin
4708 MH_SEND_PlayerPos(True, FUID, NET_EVERYONE);
4709 MH_SEND_PlayerStats(FUID, NET_EVERYONE);
4710 if not Silent then
4711 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
4712 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32,
4713 0, NET_GFX_TELE);
4714 end;
4715 end;
4717 procedure TPlayer.Spectate(NoMove: Boolean = False);
4718 begin
4719 if FAlive then
4720 Kill(K_EXTRAHARDKILL, FUID, HIT_SOME)
4721 else if (not NoMove) then
4722 begin
4723 GameX := gMapInfo.Width div 2;
4724 GameY := gMapInfo.Height div 2;
4725 end;
4726 FXTo := GameX;
4727 FYTo := GameY;
4729 FAlive := False;
4730 FSpectator := True;
4731 FGhost := True;
4732 FPhysics := False;
4733 FWantsInGame := False;
4734 FSpawned := False;
4736 if FNoRespawn then
4737 begin
4738 if Self = gPlayer1 then
4739 begin
4740 gLMSPID1 := FUID;
4741 gPlayer1 := nil;
4742 end;
4743 if Self = gPlayer2 then
4744 begin
4745 gLMSPID2 := FUID;
4746 gPlayer2 := nil;
4747 end;
4748 end;
4750 if g_Game_IsNet then
4751 MH_SEND_PlayerStats(FUID);
4752 end;
4754 procedure TPlayer.SwitchNoClip;
4755 begin
4756 if not FAlive then
4757 Exit;
4758 FGhost := not FGhost;
4759 FPhysics := not FGhost;
4760 if FGhost then
4761 begin
4762 FXTo := FObj.X;
4763 FYTo := FObj.Y;
4764 end else
4765 begin
4766 FObj.Accel.X := 0;
4767 FObj.Accel.Y := 0;
4768 end;
4769 end;
4771 procedure TPlayer.Run(Direction: TDirection);
4772 var
4773 a, b: Integer;
4774 begin
4775 if MAX_RUNVEL > 8 then
4776 FlySmoke();
4778 // Áåæèì:
4779 if Direction = TDirection.D_LEFT then
4780 begin
4781 if FObj.Vel.X > -MAX_RUNVEL then
4782 FObj.Vel.X := FObj.Vel.X - (MAX_RUNVEL shr 3);
4783 end
4784 else
4785 if FObj.Vel.X < MAX_RUNVEL then
4786 FObj.Vel.X := FObj.Vel.X + (MAX_RUNVEL shr 3);
4788 // Âîçìîæíî, ïèíàåì êóñêè:
4789 if (FObj.Vel.X <> 0) and (gGibs <> nil) then
4790 begin
4791 b := Abs(FObj.Vel.X);
4792 if b > 1 then b := b * (Random(8 div b) + 1);
4793 for a := 0 to High(gGibs) do
4794 begin
4795 if gGibs[a].alive and
4796 g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
4797 FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then
4798 begin
4799 // Ïèíàåì êóñêè
4800 if FObj.Vel.X < 0 then
4801 begin
4802 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120) // íàëåâî
4803 end
4804 else
4805 begin
4806 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // íàïðàâî
4807 end;
4808 gGibs[a].positionChanged(); // this updates spatial accelerators
4809 end;
4810 end;
4811 end;
4813 SetAction(A_WALK);
4814 end;
4816 procedure TPlayer.SeeDown();
4817 begin
4818 SetAction(A_SEEDOWN);
4820 if FDirection = TDirection.D_LEFT then FAngle := ANGLE_LEFTDOWN else FAngle := ANGLE_RIGHTDOWN;
4822 if FIncCam > -120 then DecMin(FIncCam, 5, -120);
4823 end;
4825 procedure TPlayer.SeeUp();
4826 begin
4827 SetAction(A_SEEUP);
4829 if FDirection = TDirection.D_LEFT then FAngle := ANGLE_LEFTUP else FAngle := ANGLE_RIGHTUP;
4831 if FIncCam < 120 then IncMax(FIncCam, 5, 120);
4832 end;
4834 procedure TPlayer.SetAction(Action: Byte; Force: Boolean = False);
4835 var
4836 Prior: Byte;
4837 begin
4838 case Action of
4839 A_WALK: Prior := 3;
4840 A_DIE1: Prior := 5;
4841 A_DIE2: Prior := 5;
4842 A_ATTACK: Prior := 2;
4843 A_SEEUP: Prior := 1;
4844 A_SEEDOWN: Prior := 1;
4845 A_ATTACKUP: Prior := 2;
4846 A_ATTACKDOWN: Prior := 2;
4847 A_PAIN: Prior := 4;
4848 else Prior := 0;
4849 end;
4851 if (Prior > FActionPrior) or Force then
4852 if not ((Prior = 2) and (FCurrWeap = WEAPON_SAW)) then
4853 begin
4854 FActionPrior := Prior;
4855 FActionAnim := Action;
4856 FActionForce := Force;
4857 FActionChanged := True;
4858 end;
4860 if Action in [A_ATTACK, A_ATTACKUP, A_ATTACKDOWN] then FModel.SetFire(True);
4861 end;
4863 function TPlayer.StayOnStep(XInc, YInc: Integer): Boolean;
4864 begin
4865 Result := not g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+YInc+PLAYER_RECT.Y+PLAYER_RECT.Height-1,
4866 PLAYER_RECT.Width, 1, PANEL_STEP, False)
4867 and g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+YInc+PLAYER_RECT.Y+PLAYER_RECT.Height,
4868 PLAYER_RECT.Width, 1, PANEL_STEP, False);
4869 end;
4871 function TPlayer.TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean;
4872 var
4873 Anim: TAnimation;
4874 ID: DWORD;
4875 begin
4876 Result := False;
4878 if g_CollideLevel(X, Y, PLAYER_RECT.Width, PLAYER_RECT.Height) then
4879 begin
4880 g_Sound_PlayExAt('SOUND_GAME_NOTELEPORT', FObj.X, FObj.Y);
4881 if g_Game_IsServer and g_Game_IsNet then
4882 MH_SEND_Sound(FObj.X, FObj.Y, 'SOUND_GAME_NOTELEPORT');
4883 Exit;
4884 end;
4886 FJustTeleported := True;
4888 Anim := nil;
4889 if not silent then
4890 begin
4891 if g_Frames_Get(ID, 'FRAMES_TELEPORT') then
4892 begin
4893 Anim := TAnimation.Create(ID, False, 3);
4894 end;
4896 g_Sound_PlayExAt('SOUND_GAME_TELEPORT', FObj.X, FObj.Y);
4897 g_GFX_OnceAnim(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
4898 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, Anim);
4899 if g_Game_IsServer and g_Game_IsNet then
4900 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
4901 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 1,
4902 NET_GFX_TELE);
4903 end;
4905 FObj.X := X-PLAYER_RECT.X;
4906 FObj.Y := Y-PLAYER_RECT.Y;
4907 if FAlive and FGhost then
4908 begin
4909 FXTo := FObj.X;
4910 FYTo := FObj.Y;
4911 end;
4913 if not g_Game_IsNet then
4914 begin
4915 if dir = 1 then
4916 begin
4917 SetDirection(TDirection.D_LEFT);
4918 FAngle := 180;
4919 end
4920 else
4921 if dir = 2 then
4922 begin
4923 SetDirection(TDirection.D_RIGHT);
4924 FAngle := 0;
4925 end
4926 else
4927 if dir = 3 then
4928 begin // îáðàòíîå
4929 if FDirection = TDirection.D_RIGHT then
4930 begin
4931 SetDirection(TDirection.D_LEFT);
4932 FAngle := 180;
4933 end
4934 else
4935 begin
4936 SetDirection(TDirection.D_RIGHT);
4937 FAngle := 0;
4938 end;
4939 end;
4940 end;
4942 if not silent and (Anim <> nil) then
4943 begin
4944 g_GFX_OnceAnim(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
4945 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, Anim);
4946 Anim.Free();
4948 if g_Game_IsServer and g_Game_IsNet then
4949 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
4950 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 0,
4951 NET_GFX_TELE);
4952 end;
4954 Result := True;
4955 end;
4957 function nonz(a: Single): Single;
4958 begin
4959 if a <> 0 then
4960 Result := a
4961 else
4962 Result := 1;
4963 end;
4965 function TPlayer.followCorpse(): Boolean;
4966 var
4967 i: Integer;
4968 begin
4969 Result := False;
4970 if FAlive or FSpectator then
4971 Exit;
4972 if (gCorpses = nil) or (Length(gCorpses) = 0) then
4973 Exit;
4974 for i := 0 to High(gCorpses) do
4975 if gCorpses[i] <> nil then
4976 if gCorpses[i].FPlayerUID = FUID then
4977 begin
4978 Result := True;
4979 FObj.X := gCorpses[i].FObj.X;
4980 FObj.Y := gCorpses[i].FObj.Y;
4981 FObj.Vel.X := gCorpses[i].FObj.Vel.X;
4982 FObj.Vel.Y := gCorpses[i].FObj.Vel.Y;
4983 FObj.Accel.X := gCorpses[i].FObj.Accel.X;
4984 FObj.Accel.Y := gCorpses[i].FObj.Accel.Y;
4985 break;
4986 end;
4987 end;
4989 procedure TPlayer.Update();
4990 var
4991 b: Byte;
4992 i, ii, wx, wy, xd, yd, k: Integer;
4993 blockmon, headwater, dospawn: Boolean;
4994 NetServer: Boolean;
4995 AnyServer: Boolean;
4996 SetSpect: Boolean;
4997 begin
4998 //Inc(FCurrFrameIdx);
5000 NetServer := g_Game_IsNet and g_Game_IsServer;
5001 AnyServer := g_Game_IsServer;
5003 if g_Game_IsClient and (NetInterpLevel > 0) then
5004 DoLerp(NetInterpLevel + 1)
5005 else
5006 if FGhost then
5007 DoLerp(4);
5009 if NetServer then
5010 begin
5011 if FClientID >= 0 then
5012 begin
5013 FPing := NetClients[FClientID].Peer^.lastRoundTripTime;
5014 if NetClients[FClientID].Peer^.packetsSent > 0 then
5015 FLoss := Round(100*NetClients[FClientID].Peer^.packetsLost/NetClients[FClientID].Peer^.packetsSent)
5016 else
5017 FLoss := 0;
5018 end else
5019 begin
5020 FPing := 0;
5021 FLoss := 0;
5022 end;
5023 end;
5025 if FAlive and (FPunchAnim <> nil) then
5026 FPunchAnim.Update();
5028 if FAlive and (gFly or FJetpack) then
5029 FlySmoke();
5031 if FDirection = TDirection.D_LEFT then
5032 FAngle := 180
5033 else
5034 FAngle := 0;
5036 if FAlive and (not FGhost) then
5037 begin
5038 if FKeys[KEY_UP].Pressed then
5039 SeeUp();
5040 if FKeys[KEY_DOWN].Pressed then
5041 SeeDown();
5042 end;
5044 if (not (FKeys[KEY_UP].Pressed or FKeys[KEY_DOWN].Pressed)) and
5045 (FIncCam <> 0) then
5046 begin
5047 i := g_basic.Sign(FIncCam);
5048 FIncCam := Abs(FIncCam);
5049 DecMin(FIncCam, 5, 0);
5050 FIncCam := FIncCam*i;
5051 end;
5053 // no need to do that each second frame, weapon queue will take care of it
5054 if FAlive and FKeys[KEY_NEXTWEAPON].Pressed {and AnyServer} then NextWeapon();
5055 if FAlive and FKeys[KEY_PREVWEAPON].Pressed {and AnyServer} then PrevWeapon();
5057 if gTime mod (GAME_TICK*2) <> 0 then
5058 begin
5059 if (FObj.Vel.X = 0) and FAlive then
5060 begin
5061 if FKeys[KEY_LEFT].Pressed then
5062 Run(TDirection.D_LEFT);
5063 if FKeys[KEY_RIGHT].Pressed then
5064 Run(TDirection.D_RIGHT);
5065 end;
5067 if FPhysics then
5068 begin
5069 if not followCorpse() then
5070 g_Obj_Move(@FObj, True, True, True);
5071 positionChanged(); // this updates spatial accelerators
5072 end;
5074 Exit;
5075 end;
5077 FActionChanged := False;
5079 if FAlive then
5080 begin
5081 // Let alive player do some actions
5082 if FKeys[KEY_LEFT].Pressed then Run(TDirection.D_LEFT);
5083 if FKeys[KEY_RIGHT].Pressed then Run(TDirection.D_RIGHT);
5084 //if FKeys[KEY_NEXTWEAPON].Pressed and AnyServer then NextWeapon();
5085 //if FKeys[KEY_PREVWEAPON].Pressed and AnyServer then PrevWeapon();
5086 if FKeys[KEY_FIRE].Pressed and AnyServer then Fire();
5087 if FKeys[KEY_OPEN].Pressed and AnyServer then Use();
5088 if FKeys[KEY_JUMP].Pressed then Jump()
5089 else
5090 begin
5091 if AnyServer and FJetpack then
5092 begin
5093 FJetpack := False;
5094 JetpackOff;
5095 if NetServer then MH_SEND_PlayerStats(FUID);
5096 end;
5097 FCanJetpack := True;
5098 end;
5099 end
5100 else // Dead
5101 begin
5102 dospawn := False;
5103 if not FGhost then
5104 for k := Low(FKeys) to KEY_CHAT-1 do
5105 begin
5106 if FKeys[k].Pressed then
5107 begin
5108 dospawn := True;
5109 break;
5110 end;
5111 end;
5112 if dospawn then
5113 begin
5114 if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
5115 Respawn(False)
5116 else // Single
5117 if (FTime[T_RESPAWN] <= gTime) and
5118 gGameOn and (not FAlive) then
5119 begin
5120 if (g_Player_GetCount() > 1) then
5121 Respawn(False)
5122 else
5123 begin
5124 gExit := EXIT_RESTART;
5125 Exit;
5126 end;
5127 end;
5128 end;
5129 // Dead spectator actions
5130 if FGhost then
5131 begin
5132 if FKeys[KEY_OPEN].Pressed and AnyServer then Fire();
5133 if FKeys[KEY_FIRE].Pressed and AnyServer then
5134 begin
5135 if FSpectator then
5136 begin
5137 if (FSpectatePlayer >= High(gPlayers)) then
5138 FSpectatePlayer := -1
5139 else
5140 begin
5141 SetSpect := False;
5142 for I := FSpectatePlayer + 1 to High(gPlayers) do
5143 if gPlayers[I] <> nil then
5144 if gPlayers[I].alive then
5145 if gPlayers[I].UID <> FUID then
5146 begin
5147 FSpectatePlayer := I;
5148 SetSpect := True;
5149 break;
5150 end;
5152 if not SetSpect then FSpectatePlayer := -1;
5153 end;
5155 ReleaseKeys;
5156 end;
5157 end;
5158 end;
5159 end;
5160 // No clipping
5161 if FGhost then
5162 begin
5163 if FKeys[KEY_UP].Pressed or FKeys[KEY_JUMP].Pressed then
5164 begin
5165 FYTo := FObj.Y - 32;
5166 FSpectatePlayer := -1;
5167 end;
5168 if FKeys[KEY_DOWN].Pressed then
5169 begin
5170 FYTo := FObj.Y + 32;
5171 FSpectatePlayer := -1;
5172 end;
5173 if FKeys[KEY_LEFT].Pressed then
5174 begin
5175 FXTo := FObj.X - 32;
5176 FSpectatePlayer := -1;
5177 end;
5178 if FKeys[KEY_RIGHT].Pressed then
5179 begin
5180 FXTo := FObj.X + 32;
5181 FSpectatePlayer := -1;
5182 end;
5184 if (FXTo < -64) then
5185 FXTo := -64
5186 else if (FXTo > gMapInfo.Width + 32) then
5187 FXTo := gMapInfo.Width + 32;
5188 if (FYTo < -72) then
5189 FYTo := -72
5190 else if (FYTo > gMapInfo.Height + 32) then
5191 FYTo := gMapInfo.Height + 32;
5192 end;
5194 if FPhysics then
5195 begin
5196 if not followCorpse() then
5197 g_Obj_Move(@FObj, True, True, True);
5198 positionChanged(); // this updates spatial accelerators
5199 end
5200 else
5201 begin
5202 FObj.Vel.X := 0;
5203 FObj.Vel.Y := 0;
5204 if FSpectator then
5205 if (FSpectatePlayer <= High(gPlayers)) and (FSpectatePlayer >= 0) then
5206 if gPlayers[FSpectatePlayer] <> nil then
5207 if gPlayers[FSpectatePlayer].alive then
5208 begin
5209 FXTo := gPlayers[FSpectatePlayer].GameX;
5210 FYTo := gPlayers[FSpectatePlayer].GameY;
5211 end;
5212 end;
5214 blockmon := g_Map_CollidePanel(FObj.X+PLAYER_HEADRECT.X, FObj.Y+PLAYER_HEADRECT.Y,
5215 PLAYER_HEADRECT.Width, PLAYER_HEADRECT.Height,
5216 PANEL_BLOCKMON, True);
5217 headwater := HeadInLiquid(0, 0);
5219 // Ñîïðîòèâëåíèå âîçäóõà:
5220 if (not FAlive) or not (FKeys[KEY_LEFT].Pressed or FKeys[KEY_RIGHT].Pressed) then
5221 if FObj.Vel.X <> 0 then
5222 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
5224 if (FLastHit = HIT_TRAP) and (FPain > 90) then FPain := 90;
5225 DecMin(FPain, 5, 0);
5226 DecMin(FPickup, 1, 0);
5228 if FAlive and (FObj.Y > Integer(gMapInfo.Height)+128) and AnyServer then
5229 begin
5230 // Îáíóëèòü äåéñòâèÿ ïðèìî÷åê, ÷òîáû ôîí ïðîïàë
5231 FMegaRulez[MR_SUIT] := 0;
5232 FMegaRulez[MR_INVUL] := 0;
5233 FMegaRulez[MR_INVIS] := 0;
5234 Kill(K_FALLKILL, 0, HIT_FALL);
5235 end;
5237 i := 9;
5239 if FAlive then
5240 begin
5241 if FCurrWeap = WEAPON_SAW then
5242 if not (FSawSound.IsPlaying() or FSawSoundHit.IsPlaying() or
5243 FSawSoundSelect.IsPlaying()) then
5244 FSawSoundIdle.PlayAt(FObj.X, FObj.Y);
5246 if FJetpack then
5247 if (not FJetSoundFly.IsPlaying()) and (not FJetSoundOn.IsPlaying()) and
5248 (not FJetSoundOff.IsPlaying()) then
5249 begin
5250 FJetSoundFly.SetPosition(0);
5251 FJetSoundFly.PlayAt(FObj.X, FObj.Y);
5252 end;
5254 for b := WP_FIRST to WP_LAST do
5255 if FReloading[b] > 0 then
5256 if FNoReload then
5257 FReloading[b] := 0
5258 else
5259 Dec(FReloading[b]);
5261 if FShellTimer > -1 then
5262 if FShellTimer = 0 then
5263 begin
5264 if FShellType = SHELL_SHELL then
5265 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
5266 GameVelX, GameVelY-2, SHELL_SHELL)
5267 else if FShellType = SHELL_DBLSHELL then
5268 begin
5269 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
5270 GameVelX+1, GameVelY-2, SHELL_SHELL);
5271 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
5272 GameVelX-1, GameVelY-2, SHELL_SHELL);
5273 end;
5274 FShellTimer := -1;
5275 end else Dec(FShellTimer);
5277 if (FBFGFireCounter > -1) then
5278 if FBFGFireCounter = 0 then
5279 begin
5280 if AnyServer then
5281 begin
5282 wx := FObj.X+WEAPONPOINT[FDirection].X;
5283 wy := FObj.Y+WEAPONPOINT[FDirection].Y;
5284 xd := wx+IfThen(FDirection = TDirection.D_LEFT, -30, 30);
5285 yd := wy+firediry();
5286 g_Weapon_bfgshot(wx, wy, xd, yd, FUID);
5287 if NetServer then MH_SEND_PlayerFire(FUID, WEAPON_BFG, wx, wy, xd, yd);
5288 if (FAngle = 0) or (FAngle = 180) then SetAction(A_ATTACK)
5289 else if (FAngle = ANGLE_LEFTDOWN) or (FAngle = ANGLE_RIGHTDOWN) then SetAction(A_ATTACKDOWN)
5290 else if (FAngle = ANGLE_LEFTUP) or (FAngle = ANGLE_RIGHTUP) then SetAction(A_ATTACKUP);
5291 end;
5293 FReloading[WEAPON_BFG] := WEAPON_RELOAD[WEAPON_BFG];
5294 FBFGFireCounter := -1;
5295 end else
5296 if FNoReload then
5297 FBFGFireCounter := 0
5298 else
5299 Dec(FBFGFireCounter);
5301 if (FMegaRulez[MR_SUIT] < gTime) and AnyServer then
5302 begin
5303 b := g_GetAcidHit(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width, PLAYER_RECT.Height);
5305 if (b > 0) and (gTime mod (15*GAME_TICK) = 0) then Damage(b, 0, 0, 0, HIT_ACID);
5306 end;
5308 if (headwater or blockmon) then
5309 begin
5310 Dec(FAir);
5312 if FAir < -9 then
5313 begin
5314 if AnyServer then Damage(10, 0, 0, 0, HIT_WATER);
5315 FAir := 0;
5316 end
5317 else if (FAir mod 31 = 0) and not blockmon then
5318 begin
5319 g_GFX_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2), FObj.Y+PLAYER_RECT.Y-4, 5+Random(6), 8, 4);
5320 if Random(2) = 0 then
5321 g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
5322 else
5323 g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
5324 end;
5325 end else if FAir < AIR_DEF then
5326 FAir := AIR_DEF;
5328 if FFireTime > 0 then
5329 begin
5330 if BodyInLiquid(0, 0) then
5331 begin
5332 FFireTime := 0;
5333 FFirePainTime := 0;
5334 end
5335 else if FMegaRulez[MR_SUIT] >= gTime then
5336 begin
5337 if FMegaRulez[MR_SUIT] = gTime then
5338 FFireTime := 1;
5339 FFirePainTime := 0;
5340 end
5341 else
5342 begin
5343 OnFireFlame(1);
5344 if FFirePainTime <= 0 then
5345 begin
5346 if g_Game_IsServer then
5347 Damage(5, FFireAttacker, 0, 0, HIT_FLAME);
5348 FFirePainTime := 18;
5349 end;
5350 FFirePainTime := FFirePainTime - 1;
5351 FFireTime := FFireTime - 1;
5352 if (FFireTime = 0) and g_Game_IsNet and g_Game_IsServer then
5353 MH_SEND_PlayerStats(FUID);
5354 end;
5355 end;
5357 if FDamageBuffer > 0 then
5358 begin
5359 if FDamageBuffer >= 9 then
5360 begin
5361 SetAction(A_PAIN);
5363 if FDamageBuffer < 30 then i := 9
5364 else if FDamageBuffer < 100 then i := 18
5365 else i := 27;
5366 end;
5368 ii := Round(FDamageBuffer*FHealth / nonz(FArmor*(3/4)+FHealth));
5369 FArmor := FArmor-(FDamageBuffer-ii);
5370 FHealth := FHealth-ii;
5371 if FArmor < 0 then
5372 begin
5373 FHealth := FHealth+FArmor;
5374 FArmor := 0;
5375 end;
5377 if AnyServer then
5378 if FHealth <= 0 then
5379 if FHealth > -30 then Kill(K_SIMPLEKILL, FLastSpawnerUID, FLastHit)
5380 else if FHealth > -50 then Kill(K_HARDKILL, FLastSpawnerUID, FLastHit)
5381 else Kill(K_EXTRAHARDKILL, FLastSpawnerUID, FLastHit);
5383 if FAlive then
5384 begin
5385 if FDamageBuffer <= 20 then FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y)
5386 else if FDamageBuffer <= 55 then FModel.PlaySound(MODELSOUND_PAIN, 2, FObj.X, FObj.Y)
5387 else if FDamageBuffer <= 120 then FModel.PlaySound(MODELSOUND_PAIN, 3, FObj.X, FObj.Y)
5388 else FModel.PlaySound(MODELSOUND_PAIN, 4, FObj.X, FObj.Y);
5389 end;
5391 FDamageBuffer := 0;
5392 end;
5394 {CollideItem();}
5395 end; // if FAlive then ...
5397 if (FActionAnim = A_PAIN) and (FModel.Animation <> A_PAIN) then
5398 begin
5399 FModel.ChangeAnimation(FActionAnim, FActionForce);
5400 FModel.GetCurrentAnimation.MinLength := i;
5401 FModel.GetCurrentAnimationMask.MinLength := i;
5402 end else FModel.ChangeAnimation(FActionAnim, FActionForce and (FModel.Animation <> A_STAND));
5404 if (FModel.GetCurrentAnimation.Played or ((not FActionChanged) and (FModel.Animation = A_WALK)))
5405 then SetAction(A_STAND, True);
5407 if not ((FModel.Animation = A_WALK) and (Abs(FObj.Vel.X) < 4) and not FModel.Fire) then FModel.Update;
5409 for b := Low(FKeys) to High(FKeys) do
5410 if FKeys[b].Time = 0 then FKeys[b].Pressed := False else Dec(FKeys[b].Time);
5411 end;
5414 procedure TPlayer.getMapBox (out x, y, w, h: Integer); inline;
5415 begin
5416 x := FObj.X+PLAYER_RECT.X;
5417 y := FObj.Y+PLAYER_RECT.Y;
5418 w := PLAYER_RECT.Width;
5419 h := PLAYER_RECT.Height;
5420 end;
5423 procedure TPlayer.moveBy (dx, dy: Integer); inline;
5424 begin
5425 if (dx <> 0) or (dy <> 0) then
5426 begin
5427 FObj.X += dx;
5428 FObj.Y += dy;
5429 positionChanged();
5430 end;
5431 end;
5434 function TPlayer.Collide(X, Y: Integer; Width, Height: Word): Boolean;
5435 begin
5436 Result := g_Collide(FObj.X+PLAYER_RECT.X,
5437 FObj.Y+PLAYER_RECT.Y,
5438 PLAYER_RECT.Width,
5439 PLAYER_RECT.Height,
5440 X, Y,
5441 Width, Height);
5442 end;
5444 function TPlayer.Collide(Panel: TPanel): Boolean;
5445 begin
5446 Result := g_Collide(FObj.X+PLAYER_RECT.X,
5447 FObj.Y+PLAYER_RECT.Y,
5448 PLAYER_RECT.Width,
5449 PLAYER_RECT.Height,
5450 Panel.X, Panel.Y,
5451 Panel.Width, Panel.Height);
5452 end;
5454 function TPlayer.Collide(X, Y: Integer): Boolean;
5455 begin
5456 X := X-FObj.X-PLAYER_RECT.X;
5457 Y := Y-FObj.Y-PLAYER_RECT.Y;
5458 Result := (x >= 0) and (x <= PLAYER_RECT.Width) and
5459 (y >= 0) and (y <= PLAYER_RECT.Height);
5460 end;
5462 function g_Player_ValidName(Name: string): Boolean;
5463 var
5464 a: Integer;
5465 begin
5466 Result := True;
5468 if gPlayers = nil then Exit;
5470 for a := 0 to High(gPlayers) do
5471 if gPlayers[a] <> nil then
5472 if LowerCase(Name) = LowerCase(gPlayers[a].FName) then
5473 begin
5474 Result := False;
5475 Exit;
5476 end;
5477 end;
5479 procedure TPlayer.SetDirection(Direction: TDirection);
5480 var
5481 d: TDirection;
5482 begin
5483 d := FModel.Direction;
5485 FModel.Direction := Direction;
5486 if d <> Direction then FModel.ChangeAnimation(FModel.Animation, True);
5488 FDirection := Direction;
5489 end;
5491 function TPlayer.GetKeys(): Byte;
5492 begin
5493 Result := 0;
5495 if R_KEY_RED in FRulez then Result := KEY_RED;
5496 if R_KEY_GREEN in FRulez then Result := Result or KEY_GREEN;
5497 if R_KEY_BLUE in FRulez then Result := Result or KEY_BLUE;
5499 if FTeam = TEAM_RED then Result := Result or KEY_REDTEAM;
5500 if FTeam = TEAM_BLUE then Result := Result or KEY_BLUETEAM;
5501 end;
5503 procedure TPlayer.Use();
5504 var
5505 a: Integer;
5506 begin
5507 if FTime[T_USE] > gTime then Exit;
5509 g_Triggers_PressR(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width,
5510 PLAYER_RECT.Height, FUID, ACTIVATE_PLAYERPRESS);
5512 for a := 0 to High(gPlayers) do
5513 if (gPlayers[a] <> nil) and (gPlayers[a] <> Self) and
5514 gPlayers[a].alive and SameTeam(FUID, gPlayers[a].FUID) and
5515 g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
5516 FObj.Rect.Width, FObj.Rect.Height, @gPlayers[a].FObj) then
5517 begin
5518 gPlayers[a].Touch();
5519 if g_Game_IsNet and g_Game_IsServer then
5520 MH_SEND_GameEvent(NET_EV_PLAYER_TOUCH, gPlayers[a].FUID);
5521 end;
5523 FTime[T_USE] := gTime+120;
5524 end;
5526 procedure TPlayer.NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1);
5527 var
5528 locObj: TObj;
5529 F: Boolean;
5530 WX, WY, XD, YD: Integer;
5531 begin
5532 F := False;
5533 WX := X;
5534 WY := Y;
5535 XD := AX;
5536 YD := AY;
5538 case FCurrWeap of
5539 WEAPON_KASTET:
5540 begin
5541 DoPunch();
5542 if R_BERSERK in FRulez then
5543 begin
5544 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
5545 locobj.X := FObj.X+FObj.Rect.X;
5546 locobj.Y := FObj.Y+FObj.Rect.Y;
5547 locobj.rect.X := 0;
5548 locobj.rect.Y := 0;
5549 locobj.rect.Width := 39;
5550 locobj.rect.Height := 52;
5551 locobj.Vel.X := (xd-wx) div 2;
5552 locobj.Vel.Y := (yd-wy) div 2;
5553 locobj.Accel.X := xd-wx;
5554 locobj.Accel.y := yd-wy;
5556 if g_Weapon_Hit(@locobj, 50, FUID, HIT_SOME) <> 0 then
5557 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj.X, FObj.Y)
5558 else
5559 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj.X, FObj.Y);
5561 if gFlash = 1 then
5562 if FPain < 50 then
5563 FPain := min(FPain + 25, 50);
5564 end else
5565 g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 3, FUID);
5566 end;
5568 WEAPON_SAW:
5569 begin
5570 if g_Weapon_chainsaw(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
5571 IfThen(gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF], 9, 3), FUID) <> 0 then
5572 begin
5573 FSawSoundSelect.Stop();
5574 FSawSound.Stop();
5575 FSawSoundHit.PlayAt(FObj.X, FObj.Y);
5576 end
5577 else if not FSawSoundHit.IsPlaying() then
5578 begin
5579 FSawSoundSelect.Stop();
5580 FSawSound.PlayAt(FObj.X, FObj.Y);
5581 end;
5582 f := True;
5583 end;
5585 WEAPON_PISTOL:
5586 begin
5587 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', GameX, Gamey);
5588 FFireAngle := FAngle;
5589 f := True;
5590 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
5591 GameVelX, GameVelY-2, SHELL_BULLET);
5592 end;
5594 WEAPON_SHOTGUN1:
5595 begin
5596 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex, Gamey);
5597 FFireAngle := FAngle;
5598 f := True;
5599 FShellTimer := 10;
5600 FShellType := SHELL_SHELL;
5601 end;
5603 WEAPON_SHOTGUN2:
5604 begin
5605 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', Gamex, Gamey);
5606 FFireAngle := FAngle;
5607 f := True;
5608 FShellTimer := 13;
5609 FShellType := SHELL_DBLSHELL;
5610 end;
5612 WEAPON_CHAINGUN:
5613 begin
5614 g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', Gamex, Gamey);
5615 FFireAngle := FAngle;
5616 f := True;
5617 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
5618 GameVelX, GameVelY-2, SHELL_BULLET);
5619 end;
5621 WEAPON_ROCKETLAUNCHER:
5622 begin
5623 g_Weapon_Rocket(wx, wy, xd, yd, FUID, WID);
5624 FFireAngle := FAngle;
5625 f := True;
5626 end;
5628 WEAPON_PLASMA:
5629 begin
5630 g_Weapon_Plasma(wx, wy, xd, yd, FUID, WID);
5631 FFireAngle := FAngle;
5632 f := True;
5633 end;
5635 WEAPON_BFG:
5636 begin
5637 g_Weapon_BFGShot(wx, wy, xd, yd, FUID, WID);
5638 FFireAngle := FAngle;
5639 f := True;
5640 end;
5642 WEAPON_SUPERPULEMET:
5643 begin
5644 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex, Gamey);
5645 FFireAngle := FAngle;
5646 f := True;
5647 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
5648 GameVelX, GameVelY-2, SHELL_SHELL);
5649 end;
5651 WEAPON_FLAMETHROWER:
5652 begin
5653 g_Weapon_flame(wx, wy, xd, yd, FUID, WID);
5654 FFireAngle := FAngle;
5655 f := True;
5656 end;
5657 end;
5659 if not f then Exit;
5661 if (FAngle = 0) or (FAngle = 180) then SetAction(A_ATTACK)
5662 else if (FAngle = ANGLE_LEFTDOWN) or (FAngle = ANGLE_RIGHTDOWN) then SetAction(A_ATTACKDOWN)
5663 else if (FAngle = ANGLE_LEFTUP) or (FAngle = ANGLE_RIGHTUP) then SetAction(A_ATTACKUP);
5664 end;
5666 procedure TPlayer.DoLerp(Level: Integer = 2);
5667 begin
5668 if FObj.X <> FXTo then FObj.X := Lerp(FObj.X, FXTo, Level);
5669 if FObj.Y <> FYTo then FObj.Y := Lerp(FObj.Y, FYTo, Level);
5670 end;
5672 procedure TPlayer.SetLerp(XTo, YTo: Integer);
5673 var
5674 AX, AY: Integer;
5675 begin
5676 if NetInterpLevel < 1 then
5677 begin
5678 FObj.X := XTo;
5679 FObj.Y := YTo;
5680 end
5681 else
5682 begin
5683 FXTo := XTo;
5684 FYTo := YTo;
5686 AX := Abs(FXTo - FObj.X);
5687 AY := Abs(FYTo - FObj.Y);
5688 if (AX > 32) or (AX <= NetInterpLevel) then
5689 FObj.X := FXTo;
5690 if (AY > 32) or (AY <= NetInterpLevel) then
5691 FObj.Y := FYTo;
5692 end;
5693 end;
5695 function TPlayer.FullInLift(XInc, YInc: Integer): Integer;
5696 begin
5697 if g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
5698 PLAYER_RECT.Width, PLAYER_RECT.Height-8,
5699 PANEL_LIFTUP, False) then Result := -1
5700 else
5701 if g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
5702 PLAYER_RECT.Width, PLAYER_RECT.Height-8,
5703 PANEL_LIFTDOWN, False) then Result := 1
5704 else Result := 0;
5705 end;
5707 function TPlayer.GetFlag(Flag: Byte): Boolean;
5708 var
5709 s, ts: String;
5710 evtype: Byte;
5711 begin
5712 Result := False;
5714 if Flag = FLAG_NONE then
5715 Exit;
5717 if not g_Game_IsServer then Exit;
5719 // Ïðèíåñ ÷óæîé ôëàã íà ñâîþ áàçó:
5720 if (Flag = FTeam) and
5721 (gFlags[Flag].State = FLAG_STATE_NORMAL) and
5722 (FFlag <> FLAG_NONE) then
5723 begin
5724 if FFlag = FLAG_RED then
5725 s := _lc[I_PLAYER_FLAG_RED]
5726 else
5727 s := _lc[I_PLAYER_FLAG_BLUE];
5729 evtype := FLAG_STATE_SCORED;
5731 ts := Format('%.4d', [gFlags[FFlag].CaptureTime]);
5732 Insert('.', ts, Length(ts) + 1 - 3);
5733 g_Console_Add(Format(_lc[I_PLAYER_FLAG_CAPTURE], [FName, s, ts]), True);
5735 g_Map_ResetFlag(FFlag);
5736 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_CAPTURE], [AnsiUpperCase(s)]), 144);
5738 gTeamStat[FTeam].Goals := gTeamStat[FTeam].Goals + 1;
5740 Result := True;
5741 if g_Game_IsNet then
5742 begin
5743 MH_SEND_FlagEvent(evtype, FFlag, FUID, False);
5744 MH_SEND_GameStats;
5745 end;
5747 gFlags[FFlag].CaptureTime := 0;
5748 SetFlag(FLAG_NONE);
5749 Exit;
5750 end;
5752 // Ïîäîáðàë ñâîé ôëàã - âåðíóë åãî íà áàçó:
5753 if (Flag = FTeam) and
5754 (gFlags[Flag].State = FLAG_STATE_DROPPED) then
5755 begin
5756 if Flag = FLAG_RED then
5757 s := _lc[I_PLAYER_FLAG_RED]
5758 else
5759 s := _lc[I_PLAYER_FLAG_BLUE];
5761 evtype := FLAG_STATE_RETURNED;
5762 gFlags[Flag].CaptureTime := 0;
5764 g_Console_Add(Format(_lc[I_PLAYER_FLAG_RETURN], [FName, s]), True);
5766 g_Map_ResetFlag(Flag);
5767 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
5769 Result := True;
5770 if g_Game_IsNet then
5771 begin
5772 MH_SEND_FlagEvent(evtype, Flag, FUID, False);
5773 MH_SEND_GameStats;
5774 end;
5775 Exit;
5776 end;
5778 // Ïîäîáðàë ÷óæîé ôëàã:
5779 if (Flag <> FTeam) and (FTime[T_FLAGCAP] <= gTime) then
5780 begin
5781 SetFlag(Flag);
5783 if Flag = FLAG_RED then
5784 s := _lc[I_PLAYER_FLAG_RED]
5785 else
5786 s := _lc[I_PLAYER_FLAG_BLUE];
5788 evtype := FLAG_STATE_CAPTURED;
5790 g_Console_Add(Format(_lc[I_PLAYER_FLAG_GET], [FName, s]), True);
5792 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_GET], [AnsiUpperCase(s)]), 144);
5794 gFlags[Flag].State := FLAG_STATE_CAPTURED;
5796 Result := True;
5797 if g_Game_IsNet then
5798 begin
5799 MH_SEND_FlagEvent(evtype, Flag, FUID, False);
5800 MH_SEND_GameStats;
5801 end;
5802 end;
5803 end;
5805 procedure TPlayer.SetFlag(Flag: Byte);
5806 begin
5807 FFlag := Flag;
5808 if FModel <> nil then
5809 FModel.SetFlag(FFlag);
5810 end;
5812 function TPlayer.DropFlag(): Boolean;
5813 var
5814 s: String;
5815 begin
5816 Result := False;
5817 if (not g_Game_IsServer) or (FFlag = FLAG_NONE) then
5818 Exit;
5819 FTime[T_FLAGCAP] := gTime + 2000;
5820 with gFlags[FFlag] do
5821 begin
5822 Obj.X := FObj.X;
5823 Obj.Y := FObj.Y;
5824 Direction := FDirection;
5825 State := FLAG_STATE_DROPPED;
5826 Count := FLAG_TIME;
5827 g_Obj_Push(@Obj, (FObj.Vel.X div 2)-2+Random(5),
5828 (FObj.Vel.Y div 2)-2+Random(5));
5829 positionChanged(); // this updates spatial accelerators
5831 if FFlag = FLAG_RED then
5832 s := _lc[I_PLAYER_FLAG_RED]
5833 else
5834 s := _lc[I_PLAYER_FLAG_BLUE];
5836 g_Console_Add(Format(_lc[I_PLAYER_FLAG_DROP], [FName, s]), True);
5837 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_DROP], [AnsiUpperCase(s)]), 144);
5839 if g_Game_IsNet then
5840 MH_SEND_FlagEvent(FLAG_STATE_DROPPED, Flag, FUID, False);
5841 end;
5842 SetFlag(FLAG_NONE);
5843 Result := True;
5844 end;
5846 procedure TPlayer.GetSecret();
5847 begin
5848 Inc(FSecrets);
5849 end;
5851 procedure TPlayer.PressKey(Key: Byte; Time: Word = 1);
5852 begin
5853 Assert(Key <= High(FKeys));
5855 FKeys[Key].Pressed := True;
5856 FKeys[Key].Time := Time;
5857 end;
5859 function TPlayer.IsKeyPressed(K: Byte): Boolean;
5860 begin
5861 Result := FKeys[K].Pressed;
5862 end;
5864 procedure TPlayer.ReleaseKeys();
5865 var
5866 a: Integer;
5867 begin
5868 for a := Low(FKeys) to High(FKeys) do
5869 begin
5870 FKeys[a].Pressed := False;
5871 FKeys[a].Time := 0;
5872 end;
5873 end;
5875 procedure TPlayer.ReleaseKeysNoWeapon();
5876 var
5877 a: Integer;
5878 begin
5879 for a := Low(FKeys) to High(FKeys) do
5880 begin
5881 if (a = KEY_PREVWEAPON) or (a = KEY_NEXTWEAPON) then continue;
5882 FKeys[a].Pressed := False;
5883 FKeys[a].Time := 0;
5884 end;
5885 end;
5887 procedure TPlayer.OnDamage(Angle: SmallInt);
5888 begin
5889 end;
5891 function TPlayer.firediry(): Integer;
5892 begin
5893 if FKeys[KEY_UP].Pressed then Result := -42
5894 else if FKeys[KEY_DOWN].Pressed then Result := 19
5895 else Result := 0;
5896 end;
5898 procedure TPlayer.RememberState();
5899 var
5900 i: Integer;
5901 begin
5902 FSavedState.Health := FHealth;
5903 FSavedState.Armor := FArmor;
5904 FSavedState.Air := FAir;
5905 FSavedState.JetFuel := FJetFuel;
5906 FSavedState.CurrWeap := FCurrWeap;
5907 FSavedState.NextWeap := FNextWeap;
5908 FSavedState.NextWeapDelay := FNextWeapDelay;
5910 for i := 0 to 3 do
5911 FSavedState.Ammo[i] := FAmmo[i];
5912 for i := 0 to 3 do
5913 FSavedState.MaxAmmo[i] := FMaxAmmo[i];
5915 FSavedState.Rulez := FRulez;
5916 FSavedState.WaitRecall := True;
5917 end;
5919 procedure TPlayer.RecallState();
5920 var
5921 i: Integer;
5922 begin
5923 if not FSavedState.WaitRecall then Exit;
5925 FHealth := FSavedState.Health;
5926 FArmor := FSavedState.Armor;
5927 FAir := FSavedState.Air;
5928 FJetFuel := FSavedState.JetFuel;
5929 FCurrWeap := FSavedState.CurrWeap;
5930 //FNetForceWeap := FCurrWeap;
5931 FNextWeap := FSavedState.NextWeap;
5932 FNextWeapDelay := FSavedState.NextWeapDelay;
5934 for i := 0 to 3 do
5935 FAmmo[i] := FSavedState.Ammo[i];
5936 for i := 0 to 3 do
5937 FMaxAmmo[i] := FSavedState.MaxAmmo[i];
5939 FRulez := FSavedState.Rulez;
5940 FSavedState.WaitRecall := False;
5942 if gGameSettings.GameType = GT_SERVER then
5943 MH_SEND_PlayerStats(FUID);
5944 end;
5946 procedure TPlayer.SaveState (st: TStream);
5947 var
5948 i: Integer;
5949 b: Byte;
5950 begin
5951 // Ñèãíàòóðà èãðîêà
5952 utils.writeSign(st, 'PLYR');
5953 utils.writeInt(st, Byte(PLR_SAVE_VERSION)); // version
5954 // Áîò èëè ÷åëîâåê
5955 utils.writeBool(st, FIamBot);
5956 // UID èãðîêà
5957 utils.writeInt(st, Word(FUID));
5958 // Èìÿ èãðîêà
5959 utils.writeStr(st, FName);
5960 // Êîìàíäà
5961 utils.writeInt(st, Byte(FTeam));
5962 // Æèâ ëè
5963 utils.writeBool(st, FAlive);
5964 // Èçðàñõîäîâàë ëè âñå æèçíè
5965 utils.writeBool(st, FNoRespawn);
5966 // Íàïðàâëåíèå
5967 if FDirection = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT
5968 utils.writeInt(st, Byte(b));
5969 // Çäîðîâüå
5970 utils.writeInt(st, LongInt(FHealth));
5971 // Æèçíè
5972 utils.writeInt(st, Byte(FLives));
5973 // Áðîíÿ
5974 utils.writeInt(st, LongInt(FArmor));
5975 // Çàïàñ âîçäóõà
5976 utils.writeInt(st, LongInt(FAir));
5977 // Çàïàñ ãîðþ÷åãî
5978 utils.writeInt(st, LongInt(FJetFuel));
5979 // Áîëü
5980 utils.writeInt(st, LongInt(FPain));
5981 // Óáèë
5982 utils.writeInt(st, LongInt(FKills));
5983 // Óáèë ìîíñòðîâ
5984 utils.writeInt(st, LongInt(FMonsterKills));
5985 // Ôðàãîâ
5986 utils.writeInt(st, LongInt(FFrags));
5987 // Ôðàãîâ ïîäðÿä
5988 utils.writeInt(st, Byte(FFragCombo));
5989 // Âðåìÿ ïîñëåäíåãî ôðàãà
5990 utils.writeInt(st, LongWord(FLastFrag));
5991 // Ñìåðòåé
5992 utils.writeInt(st, LongInt(FDeath));
5993 // Êàêîé ôëàã íåñåò
5994 utils.writeInt(st, Byte(FFlag));
5995 // Íàøåë ñåêðåòîâ
5996 utils.writeInt(st, LongInt(FSecrets));
5997 // Òåêóùåå îðóæèå
5998 utils.writeInt(st, Byte(FCurrWeap));
5999 // Æåëàåìîå îðóæèå
6000 utils.writeInt(st, Word(FNextWeap));
6001 // ...è ïàóçà
6002 utils.writeInt(st, Byte(FNextWeapDelay));
6003 // Âðåìÿ çàðÿäêè BFG
6004 utils.writeInt(st, SmallInt(FBFGFireCounter));
6005 // Áóôåð óðîíà
6006 utils.writeInt(st, LongInt(FDamageBuffer));
6007 // Ïîñëåäíèé óäàðèâøèé
6008 utils.writeInt(st, Word(FLastSpawnerUID));
6009 // Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà
6010 utils.writeInt(st, Byte(FLastHit));
6011 // Îáúåêò èãðîêà
6012 Obj_SaveState(st, @FObj);
6013 // Òåêóùåå êîëè÷åñòâî ïàòðîíîâ
6014 for i := A_BULLETS to A_HIGH do utils.writeInt(st, Word(FAmmo[i]));
6015 // Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ
6016 for i := A_BULLETS to A_HIGH do utils.writeInt(st, Word(FMaxAmmo[i]));
6017 // Íàëè÷èå îðóæèÿ
6018 for i := WP_FIRST to WP_LAST do utils.writeBool(st, FWeapon[i]);
6019 // Âðåìÿ ïåðåçàðÿäêè îðóæèÿ
6020 for i := WP_FIRST to WP_LAST do utils.writeInt(st, Word(FReloading[i]));
6021 // Íàëè÷èå ðþêçàêà
6022 utils.writeBool(st, (R_ITEM_BACKPACK in FRulez));
6023 // Íàëè÷èå êðàñíîãî êëþ÷à
6024 utils.writeBool(st, (R_KEY_RED in FRulez));
6025 // Íàëè÷èå çåëåíîãî êëþ÷à
6026 utils.writeBool(st, (R_KEY_GREEN in FRulez));
6027 // Íàëè÷èå ñèíåãî êëþ÷à
6028 utils.writeBool(st, (R_KEY_BLUE in FRulez));
6029 // Íàëè÷èå áåðñåðêà
6030 utils.writeBool(st, (R_BERSERK in FRulez));
6031 // Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ
6032 for i := MR_SUIT to MR_MAX do utils.writeInt(st, LongWord(FMegaRulez[i]));
6033 // Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà
6034 for i := T_RESPAWN to T_FLAGCAP do utils.writeInt(st, LongWord(FTime[i]));
6035 // Íàçâàíèå ìîäåëè
6036 utils.writeStr(st, FModel.Name);
6037 // Öâåò ìîäåëè
6038 utils.writeInt(st, Byte(FColor.R));
6039 utils.writeInt(st, Byte(FColor.G));
6040 utils.writeInt(st, Byte(FColor.B));
6041 end;
6044 procedure TPlayer.LoadState (st: TStream);
6045 var
6046 i: Integer;
6047 str: String;
6048 b: Byte;
6049 begin
6050 assert(st <> nil);
6052 // Ñèãíàòóðà èãðîêà
6053 if not utils.checkSign(st, 'PLYR') then raise XStreamError.Create('invalid player signature');
6054 if (utils.readByte(st) <> PLR_SAVE_VERSION) then raise XStreamError.Create('invalid player version');
6055 // Áîò èëè ÷åëîâåê:
6056 FIamBot := utils.readBool(st);
6057 // UID èãðîêà
6058 FUID := utils.readWord(st);
6059 // Èìÿ èãðîêà
6060 str := utils.readStr(st);
6061 if (self <> gPlayer1) and (self <> gPlayer2) then FName := str;
6062 // Êîìàíäà
6063 FTeam := utils.readByte(st);
6064 // Æèâ ëè
6065 FAlive := utils.readBool(st);
6066 // Èçðàñõîäîâàë ëè âñå æèçíè
6067 FNoRespawn := utils.readBool(st);
6068 // Íàïðàâëåíèå
6069 b := utils.readByte(st);
6070 if b = 1 then FDirection := TDirection.D_LEFT else FDirection := TDirection.D_RIGHT; // b = 2
6071 // Çäîðîâüå
6072 FHealth := utils.readLongInt(st);
6073 // Æèçíè
6074 FLives := utils.readByte(st);
6075 // Áðîíÿ
6076 FArmor := utils.readLongInt(st);
6077 // Çàïàñ âîçäóõà
6078 FAir := utils.readLongInt(st);
6079 // Çàïàñ ãîðþ÷åãî
6080 FJetFuel := utils.readLongInt(st);
6081 // Áîëü
6082 FPain := utils.readLongInt(st);
6083 // Óáèë
6084 FKills := utils.readLongInt(st);
6085 // Óáèë ìîíñòðîâ
6086 FMonsterKills := utils.readLongInt(st);
6087 // Ôðàãîâ
6088 FFrags := utils.readLongInt(st);
6089 // Ôðàãîâ ïîäðÿä
6090 FFragCombo := utils.readByte(st);
6091 // Âðåìÿ ïîñëåäíåãî ôðàãà
6092 FLastFrag := utils.readLongWord(st);
6093 // Ñìåðòåé
6094 FDeath := utils.readLongInt(st);
6095 // Êàêîé ôëàã íåñåò
6096 FFlag := utils.readByte(st);
6097 // Íàøåë ñåêðåòîâ
6098 FSecrets := utils.readLongInt(st);
6099 // Òåêóùåå îðóæèå
6100 FCurrWeap := utils.readByte(st);
6101 //FNetForceWeap := FCurrWeap;
6102 // Æåëàåìîå îðóæèå
6103 FNextWeap := utils.readWord(st);
6104 // ...è ïàóçà
6105 FNextWeapDelay := utils.readByte(st);
6106 // Âðåìÿ çàðÿäêè BFG
6107 FBFGFireCounter := utils.readSmallInt(st);
6108 // Áóôåð óðîíà
6109 FDamageBuffer := utils.readLongInt(st);
6110 // Ïîñëåäíèé óäàðèâøèé
6111 FLastSpawnerUID := utils.readWord(st);
6112 // Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà
6113 FLastHit := utils.readByte(st);
6114 // Îáúåêò èãðîêà
6115 Obj_LoadState(@FObj, st);
6116 // Òåêóùåå êîëè÷åñòâî ïàòðîíîâ
6117 for i := A_BULLETS to A_HIGH do FAmmo[i] := utils.readWord(st);
6118 // Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ
6119 for i := A_BULLETS to A_HIGH do FMaxAmmo[i] := utils.readWord(st);
6120 // Íàëè÷èå îðóæèÿ
6121 for i := WP_FIRST to WP_LAST do FWeapon[i] := utils.readBool(st);
6122 // Âðåìÿ ïåðåçàðÿäêè îðóæèÿ
6123 for i := WP_FIRST to WP_LAST do FReloading[i] := utils.readWord(st);
6124 // Íàëè÷èå ðþêçàêà
6125 if utils.readBool(st) then Include(FRulez, R_ITEM_BACKPACK);
6126 // Íàëè÷èå êðàñíîãî êëþ÷à
6127 if utils.readBool(st) then Include(FRulez, R_KEY_RED);
6128 // Íàëè÷èå çåëåíîãî êëþ÷à
6129 if utils.readBool(st) then Include(FRulez, R_KEY_GREEN);
6130 // Íàëè÷èå ñèíåãî êëþ÷à
6131 if utils.readBool(st) then Include(FRulez, R_KEY_BLUE);
6132 // Íàëè÷èå áåðñåðêà
6133 if utils.readBool(st) then Include(FRulez, R_BERSERK);
6134 // Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ
6135 for i := MR_SUIT to MR_MAX do FMegaRulez[i] := utils.readLongWord(st);
6136 // Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà
6137 for i := T_RESPAWN to T_FLAGCAP do FTime[i] := utils.readLongWord(st);
6138 // Íàçâàíèå ìîäåëè
6139 str := utils.readStr(st);
6140 // Öâåò ìîäåëè
6141 FColor.R := utils.readByte(st);
6142 FColor.G := utils.readByte(st);
6143 FColor.B := utils.readByte(st);
6144 if (self = gPlayer1) then
6145 begin
6146 str := gPlayer1Settings.Model;
6147 FColor := gPlayer1Settings.Color;
6148 end
6149 else if (self = gPlayer2) then
6150 begin
6151 str := gPlayer2Settings.Model;
6152 FColor := gPlayer2Settings.Color;
6153 end;
6154 // Îáíîâëÿåì ìîäåëü èãðîêà
6155 SetModel(str);
6156 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
6157 FModel.Color := TEAMCOLOR[FTeam]
6158 else
6159 FModel.Color := FColor;
6160 end;
6163 procedure TPlayer.AllRulez(Health: Boolean);
6164 var
6165 a: Integer;
6166 begin
6167 if Health then
6168 begin
6169 FHealth := PLAYER_HP_LIMIT;
6170 FArmor := PLAYER_AP_LIMIT;
6171 Exit;
6172 end;
6174 for a := WP_FIRST to WP_LAST do FWeapon[a] := True;
6175 for a := A_BULLETS to A_HIGH do FAmmo[a] := 30000;
6176 FRulez := FRulez+[R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE];
6177 end;
6179 procedure TPlayer.RestoreHealthArmor();
6180 begin
6181 FHealth := PLAYER_HP_LIMIT;
6182 FArmor := PLAYER_AP_LIMIT;
6183 end;
6185 procedure TPlayer.FragCombo();
6186 var
6187 Param: Integer;
6188 begin
6189 if (gGameSettings.GameMode in [GM_COOP, GM_SINGLE]) or g_Game_IsClient then
6190 Exit;
6191 if gTime - FLastFrag < FRAG_COMBO_TIME then
6192 begin
6193 if FFragCombo < 5 then
6194 Inc(FFragCombo);
6195 Param := FUID or (FFragCombo shl 16);
6196 if (FComboEvnt >= Low(gDelayedEvents)) and
6197 (FComboEvnt <= High(gDelayedEvents)) and
6198 gDelayedEvents[FComboEvnt].Pending and
6199 (gDelayedEvents[FComboEvnt].DEType = DE_KILLCOMBO) and
6200 (gDelayedEvents[FComboEvnt].DENum and $FFFF = FUID) then
6201 begin
6202 gDelayedEvents[FComboEvnt].Time := gTime + 500;
6203 gDelayedEvents[FComboEvnt].DENum := Param;
6204 end
6205 else
6206 FComboEvnt := g_Game_DelayEvent(DE_KILLCOMBO, 500, Param);
6207 end
6208 else
6209 FFragCombo := 1;
6211 FLastFrag := gTime;
6212 end;
6214 procedure TPlayer.GiveItem(ItemType: Byte);
6215 begin
6216 case ItemType of
6217 ITEM_SUIT:
6218 if FMegaRulez[MR_SUIT] < gTime+PLAYER_SUIT_TIME then
6219 begin
6220 FMegaRulez[MR_SUIT] := gTime+PLAYER_SUIT_TIME;
6221 end;
6223 ITEM_OXYGEN:
6224 if FAir < AIR_MAX then
6225 begin
6226 FAir := AIR_MAX;
6227 end;
6229 ITEM_MEDKIT_BLACK:
6230 begin
6231 if not (R_BERSERK in FRulez) then
6232 begin
6233 Include(FRulez, R_BERSERK);
6234 if FBFGFireCounter < 1 then
6235 begin
6236 FCurrWeap := WEAPON_KASTET;
6237 //FNetForceWeap := FCurrWeap;
6238 resetWeaponQueue();
6239 FModel.SetWeapon(WEAPON_KASTET);
6240 end;
6241 if gFlash <> 0 then
6242 Inc(FPain, 100);
6243 FBerserk := gTime+30000;
6244 end;
6245 if FHealth < PLAYER_HP_SOFT then
6246 begin
6247 FHealth := PLAYER_HP_SOFT;
6248 FBerserk := gTime+30000;
6249 end;
6250 end;
6252 ITEM_INVUL:
6253 if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
6254 begin
6255 FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
6256 end;
6258 ITEM_INVIS:
6259 if FMegaRulez[MR_INVIS] < gTime+PLAYER_INVIS_TIME then
6260 begin
6261 FMegaRulez[MR_INVIS] := gTime+PLAYER_INVIS_TIME;
6262 end;
6264 ITEM_JETPACK:
6265 if FJetFuel < JET_MAX then
6266 begin
6267 FJetFuel := JET_MAX;
6268 end;
6270 ITEM_MEDKIT_SMALL: if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 10, PLAYER_HP_SOFT);
6271 ITEM_MEDKIT_LARGE: if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 25, PLAYER_HP_SOFT);
6273 ITEM_ARMOR_GREEN: if FArmor < PLAYER_AP_SOFT then FArmor := PLAYER_AP_SOFT;
6274 ITEM_ARMOR_BLUE: if FArmor < PLAYER_AP_LIMIT then FArmor := PLAYER_AP_LIMIT;
6276 ITEM_SPHERE_BLUE: if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 100, PLAYER_HP_LIMIT);
6277 ITEM_SPHERE_WHITE:
6278 if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) then
6279 begin
6280 if FHealth < PLAYER_HP_LIMIT then FHealth := PLAYER_HP_LIMIT;
6281 if FArmor < PLAYER_AP_LIMIT then FArmor := PLAYER_AP_LIMIT;
6282 end;
6284 ITEM_WEAPON_SAW: FWeapon[WEAPON_SAW] := True;
6285 ITEM_WEAPON_SHOTGUN1: FWeapon[WEAPON_SHOTGUN1] := True;
6286 ITEM_WEAPON_SHOTGUN2: FWeapon[WEAPON_SHOTGUN2] := True;
6287 ITEM_WEAPON_CHAINGUN: FWeapon[WEAPON_CHAINGUN] := True;
6288 ITEM_WEAPON_ROCKETLAUNCHER: FWeapon[WEAPON_ROCKETLAUNCHER] := True;
6289 ITEM_WEAPON_PLASMA: FWeapon[WEAPON_PLASMA] := True;
6290 ITEM_WEAPON_BFG: FWeapon[WEAPON_BFG] := True;
6291 ITEM_WEAPON_SUPERPULEMET: FWeapon[WEAPON_SUPERPULEMET] := True;
6292 ITEM_WEAPON_FLAMETHROWER: FWeapon[WEAPON_FLAMETHROWER] := True;
6294 ITEM_AMMO_BULLETS: if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
6295 ITEM_AMMO_BULLETS_BOX: if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
6296 ITEM_AMMO_SHELLS: if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
6297 ITEM_AMMO_SHELLS_BOX: if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 25, FMaxAmmo[A_SHELLS]);
6298 ITEM_AMMO_ROCKET: if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
6299 ITEM_AMMO_ROCKET_BOX: if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 5, FMaxAmmo[A_ROCKETS]);
6300 ITEM_AMMO_CELL: if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
6301 ITEM_AMMO_CELL_BIG: if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 100, FMaxAmmo[A_CELLS]);
6302 ITEM_AMMO_FUELCAN: if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
6304 ITEM_AMMO_BACKPACK:
6305 if (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or
6306 (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or
6307 (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or
6308 (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or
6309 (FMaxAmmo[A_FUEL] < AmmoLimits[1, A_FUEL]) then
6310 begin
6311 FMaxAmmo[A_BULLETS] := AmmoLimits[1, A_BULLETS];
6312 FMaxAmmo[A_SHELLS] := AmmoLimits[1, A_SHELLS];
6313 FMaxAmmo[A_ROCKETS] := AmmoLimits[1, A_ROCKETS];
6314 FMaxAmmo[A_CELLS] := AmmoLimits[1, A_CELLS];
6315 FMaxAmmo[A_FUEL] := AmmoLimits[1, A_FUEL];
6317 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
6318 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
6319 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
6320 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
6322 FRulez := FRulez + [R_ITEM_BACKPACK];
6323 end;
6325 ITEM_KEY_RED: if not (R_KEY_RED in FRulez) then Include(FRulez, R_KEY_RED);
6326 ITEM_KEY_GREEN: if not (R_KEY_GREEN in FRulez) then Include(FRulez, R_KEY_GREEN);
6327 ITEM_KEY_BLUE: if not (R_KEY_BLUE in FRulez) then Include(FRulez, R_KEY_BLUE);
6329 ITEM_BOTTLE: if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 4, PLAYER_HP_LIMIT);
6330 ITEM_HELMET: if FArmor < PLAYER_AP_LIMIT then IncMax(FArmor, 5, PLAYER_AP_LIMIT);
6332 else
6333 Exit;
6334 end;
6335 if g_Game_IsNet and g_Game_IsServer then
6336 MH_SEND_PlayerStats(FUID);
6337 end;
6339 procedure TPlayer.FlySmoke(Times: DWORD = 1);
6340 var
6341 id, i: DWORD;
6342 Anim: TAnimation;
6343 begin
6344 if (Random(5) = 1) and (Times = 1) then
6345 Exit;
6347 if BodyInLiquid(0, 0) then
6348 begin
6349 g_GFX_Bubbles(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)+Random(3)-1,
6350 Obj.Y+Obj.Rect.Height+8, 1, 8, 4);
6351 if Random(2) = 0 then
6352 g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
6353 else
6354 g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
6355 Exit;
6356 end;
6358 if g_Frames_Get(id, 'FRAMES_SMOKE') then
6359 begin
6360 for i := 1 to Times do
6361 begin
6362 Anim := TAnimation.Create(id, False, 3);
6363 Anim.Alpha := 150;
6364 g_GFX_OnceAnim(Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(Anim.Width div 2),
6365 Obj.Y+Obj.Rect.Height-4+Random(8+Times*2), Anim, ONCEANIM_SMOKE);
6366 Anim.Free();
6367 end;
6368 end;
6369 end;
6371 procedure TPlayer.OnFireFlame(Times: DWORD = 1);
6372 var
6373 id, i: DWORD;
6374 Anim: TAnimation;
6375 begin
6376 if (Random(10) = 1) and (Times = 1) then
6377 Exit;
6379 if g_Frames_Get(id, 'FRAMES_FLAME') then
6380 begin
6381 for i := 1 to Times do
6382 begin
6383 Anim := TAnimation.Create(id, False, 3);
6384 Anim.Alpha := 0;
6385 g_GFX_OnceAnim(Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(Anim.Width div 2),
6386 Obj.Y+8+Random(8+Times*2), Anim, ONCEANIM_SMOKE);
6387 Anim.Free();
6388 end;
6389 end;
6390 end;
6392 procedure TPlayer.PauseSounds(Enable: Boolean);
6393 begin
6394 FSawSound.Pause(Enable);
6395 FSawSoundIdle.Pause(Enable);
6396 FSawSoundHit.Pause(Enable);
6397 FSawSoundSelect.Pause(Enable);
6398 end;
6400 { T C o r p s e : }
6402 constructor TCorpse.Create(X, Y: Integer; ModelName: String; aMess: Boolean);
6403 begin
6404 g_Obj_Init(@FObj);
6405 FObj.X := X;
6406 FObj.Y := Y;
6407 FObj.Rect := PLAYER_CORPSERECT;
6408 FModelName := ModelName;
6409 FMess := aMess;
6411 if FMess then
6412 begin
6413 FState := CORPSE_STATE_MESS;
6414 g_PlayerModel_GetAnim(ModelName, A_DIE2, FAnimation, FAnimationMask);
6415 end
6416 else
6417 begin
6418 FState := CORPSE_STATE_NORMAL;
6419 g_PlayerModel_GetAnim(ModelName, A_DIE1, FAnimation, FAnimationMask);
6420 end;
6421 end;
6423 destructor TCorpse.Destroy();
6424 begin
6425 FAnimation.Free();
6427 inherited;
6428 end;
6430 function TCorpse.ObjPtr (): PObj; inline; begin result := @FObj; end;
6432 procedure TCorpse.positionChanged (); inline; begin end;
6434 procedure TCorpse.moveBy (dx, dy: Integer); inline;
6435 begin
6436 if (dx <> 0) or (dy <> 0) then
6437 begin
6438 FObj.X += dx;
6439 FObj.Y += dy;
6440 positionChanged();
6441 end;
6442 end;
6445 procedure TCorpse.getMapBox (out x, y, w, h: Integer); inline;
6446 begin
6447 x := FObj.X+PLAYER_CORPSERECT.X;
6448 y := FObj.Y+PLAYER_CORPSERECT.Y;
6449 w := PLAYER_CORPSERECT.Width;
6450 h := PLAYER_CORPSERECT.Height;
6451 end;
6454 procedure TCorpse.Damage(Value: Word; vx, vy: Integer);
6455 var
6456 pm: TPlayerModel;
6457 Blood: TModelBlood;
6458 begin
6459 if FState = CORPSE_STATE_REMOVEME then
6460 Exit;
6462 FDamage := FDamage + Value;
6464 if FDamage > 150 then
6465 begin
6466 if FAnimation <> nil then
6467 begin
6468 FAnimation.Free();
6469 FAnimation := nil;
6471 FState := CORPSE_STATE_REMOVEME;
6473 g_Player_CreateGibs(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
6474 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
6475 FModelName, FColor);
6476 // Çâóê ìÿñà îò òðóïà:
6477 pm := g_PlayerModel_Get(FModelName);
6478 pm.PlaySound(MODELSOUND_DIE, 5, FObj.X, FObj.Y);
6479 pm.Free;
6481 // Çëîâåùèé ñìåõ:
6482 if (gBodyKillEvent <> -1)
6483 and gDelayedEvents[gBodyKillEvent].Pending then
6484 gDelayedEvents[gBodyKillEvent].Pending := False;
6485 gBodyKillEvent := g_Game_DelayEvent(DE_BODYKILL, 1050, 0);
6486 end;
6487 end
6488 else
6489 begin
6490 Blood := g_PlayerModel_GetBlood(FModelName);
6491 FObj.Vel.X := FObj.Vel.X + vx;
6492 FObj.Vel.Y := FObj.Vel.Y + vy;
6493 g_GFX_Blood(FObj.X+PLAYER_CORPSERECT.X+(PLAYER_CORPSERECT.Width div 2),
6494 FObj.Y+PLAYER_CORPSERECT.Y+(PLAYER_CORPSERECT.Height div 2),
6495 Value, vx, vy, 16, (PLAYER_CORPSERECT.Height*2) div 3,
6496 Blood.R, Blood.G, Blood.B, Blood.Kind);
6497 end;
6498 end;
6500 procedure TCorpse.Draw();
6501 begin
6502 if FState = CORPSE_STATE_REMOVEME then
6503 Exit;
6505 if FAnimation <> nil then
6506 FAnimation.Draw(FObj.X, FObj.Y, TMirrorType.None);
6508 if FAnimationMask <> nil then
6509 begin
6510 e_Colors := FColor;
6511 FAnimationMask.Draw(FObj.X, FObj.Y, TMirrorType.None);
6512 e_Colors.R := 255;
6513 e_Colors.G := 255;
6514 e_Colors.B := 255;
6515 end;
6516 end;
6518 procedure TCorpse.Update();
6519 var
6520 st: Word;
6521 begin
6522 if FState = CORPSE_STATE_REMOVEME then
6523 Exit;
6525 if gTime mod (GAME_TICK*2) <> 0 then
6526 begin
6527 g_Obj_Move(@FObj, True, True, True);
6528 positionChanged(); // this updates spatial accelerators
6529 Exit;
6530 end;
6532 // Ñîïðîòèâëåíèå âîçäóõà äëÿ òðóïà:
6533 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
6535 st := g_Obj_Move(@FObj, True, True, True);
6536 positionChanged(); // this updates spatial accelerators
6538 if WordBool(st and MOVE_FALLOUT) then
6539 begin
6540 FState := CORPSE_STATE_REMOVEME;
6541 Exit;
6542 end;
6544 if FAnimation <> nil then
6545 FAnimation.Update();
6546 if FAnimationMask <> nil then
6547 FAnimationMask.Update();
6548 end;
6551 procedure TCorpse.SaveState (st: TStream);
6552 var
6553 anim: Boolean;
6554 begin
6555 assert(st <> nil);
6557 // Ñèãíàòóðà òðóïà
6558 utils.writeSign(st, 'CORP');
6559 utils.writeInt(st, Byte(0));
6560 // Ñîñòîÿíèå
6561 utils.writeInt(st, Byte(FState));
6562 // Íàêîïëåííûé óðîí
6563 utils.writeInt(st, Byte(FDamage));
6564 // Öâåò
6565 utils.writeInt(st, Byte(FColor.R));
6566 utils.writeInt(st, Byte(FColor.G));
6567 utils.writeInt(st, Byte(FColor.B));
6568 // Îáúåêò òðóïà
6569 Obj_SaveState(st, @FObj);
6570 utils.writeInt(st, Word(FPlayerUID));
6571 // Åñòü ëè àíèìàöèÿ
6572 anim := (FAnimation <> nil);
6573 utils.writeBool(st, anim);
6574 // Åñëè åñòü - ñîõðàíÿåì
6575 if anim then FAnimation.SaveState(st);
6576 // Åñòü ëè ìàñêà àíèìàöèè
6577 anim := (FAnimationMask <> nil);
6578 utils.writeBool(st, anim);
6579 // Åñëè åñòü - ñîõðàíÿåì
6580 if anim then FAnimationMask.SaveState(st);
6581 end;
6584 procedure TCorpse.LoadState (st: TStream);
6585 var
6586 anim: Boolean;
6587 begin
6588 assert(st <> nil);
6590 // Ñèãíàòóðà òðóïà
6591 if not utils.checkSign(st, 'CORP') then raise XStreamError.Create('invalid corpse signature');
6592 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid corpse version');
6593 // Ñîñòîÿíèå
6594 FState := utils.readByte(st);
6595 // Íàêîïëåííûé óðîí
6596 FDamage := utils.readByte(st);
6597 // Öâåò
6598 FColor.R := utils.readByte(st);
6599 FColor.G := utils.readByte(st);
6600 FColor.B := utils.readByte(st);
6601 // Îáúåêò òðóïà
6602 Obj_LoadState(@FObj, st);
6603 FPlayerUID := utils.readWord(st);
6604 // Åñòü ëè àíèìàöèÿ
6605 anim := utils.readBool(st);
6606 // Åñëè åñòü - çàãðóæàåì
6607 if anim then
6608 begin
6609 Assert(FAnimation <> nil, 'TCorpse.LoadState: no FAnimation');
6610 FAnimation.LoadState(st);
6611 end;
6612 // Åñòü ëè ìàñêà àíèìàöèè
6613 anim := utils.readBool(st);
6614 // Åñëè åñòü - çàãðóæàåì
6615 if anim then
6616 begin
6617 Assert(FAnimationMask <> nil, 'TCorpse.LoadState: no FAnimationMask');
6618 FAnimationMask.LoadState(st);
6619 end;
6620 end;
6622 { T B o t : }
6624 constructor TBot.Create();
6625 var
6626 a: Integer;
6627 begin
6628 inherited Create();
6630 FPhysics := True;
6631 FSpectator := False;
6632 FGhost := False;
6634 FIamBot := True;
6636 Inc(gNumBots);
6638 for a := WP_FIRST to WP_LAST do
6639 begin
6640 FDifficult.WeaponPrior[a] := WEAPON_PRIOR1[a];
6641 FDifficult.CloseWeaponPrior[a] := WEAPON_PRIOR2[a];
6642 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
6643 end;
6644 end;
6646 destructor TBot.Destroy();
6647 begin
6648 Dec(gNumBots);
6649 inherited Destroy();
6650 end;
6652 procedure TBot.Draw();
6653 begin
6654 inherited Draw();
6656 //if FTargetUID <> 0 then e_DrawLine(1, FObj.X, FObj.Y, g_Player_Get(FTargetUID).FObj.X,
6657 // g_Player_Get(FTargetUID).FObj.Y, 255, 0, 0);
6658 end;
6660 procedure TBot.Respawn(Silent: Boolean; Force: Boolean = False);
6661 begin
6662 inherited Respawn(Silent, Force);
6664 FAIFlags := nil;
6665 FSelectedWeapon := FCurrWeap;
6666 resetWeaponQueue();
6667 FTargetUID := 0;
6668 end;
6670 procedure TBot.UpdateCombat();
6671 type
6672 TTarget = record
6673 UID: Word;
6674 X, Y: Integer;
6675 Rect: TRectWH;
6676 cX, cY: Integer;
6677 Dist: Word;
6678 Line: Boolean;
6679 Visible: Boolean;
6680 IsPlayer: Boolean;
6681 end;
6683 TTargetRecord = array of TTarget;
6685 function Compare(a, b: TTarget): Integer;
6686 begin
6687 if a.Line and not b.Line then // A íà ëèíèè îãíÿ
6688 Result := -1
6689 else
6690 if not a.Line and b.Line then // B íà ëèíèè îãíÿ
6691 Result := 1
6692 else // È A, è B íà ëèíèè èëè íå íà ëèíèè îãíÿ
6693 if (a.Line and b.Line) or ((not a.Line) and (not b.Line)) then
6694 begin
6695 if a.Dist > b.Dist then // B áëèæå
6696 Result := 1
6697 else // A áëèæå èëè ðàâíîóäàëåííî ñ B
6698 Result := -1;
6699 end
6700 else // Ñòðàííî -> A
6701 Result := -1;
6702 end;
6704 var
6705 a, x1, y1, x2, y2: Integer;
6706 targets: TTargetRecord;
6707 ammo: Word;
6708 Target, BestTarget: TTarget;
6709 firew, fireh: Integer;
6710 angle: SmallInt;
6711 mon: TMonster;
6712 pla, tpla: TPlayer;
6713 vsPlayer, vsMonster, ok: Boolean;
6716 function monsUpdate (mon: TMonster): Boolean;
6717 begin
6718 result := false; // don't stop
6719 if mon.alive and (mon.MonsterType <> MONSTER_BARREL) then
6720 begin
6721 if not TargetOnScreen(mon.Obj.X+mon.Obj.Rect.X, mon.Obj.Y+mon.Obj.Rect.Y) then exit;
6723 x2 := mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2);
6724 y2 := mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2);
6726 // Åñëè ìîíñòð íà ýêðàíå è íå ïðèêðûò ñòåíîé
6727 if g_TraceVector(x1, y1, x2, y2) then
6728 begin
6729 // Äîáàâëÿåì ê ñïèñêó âîçìîæíûõ öåëåé
6730 SetLength(targets, Length(targets)+1);
6731 with targets[High(targets)] do
6732 begin
6733 UID := mon.UID;
6734 X := mon.Obj.X;
6735 Y := mon.Obj.Y;
6736 cX := x2;
6737 cY := y2;
6738 Rect := mon.Obj.Rect;
6739 Dist := g_PatchLength(x1, y1, x2, y2);
6740 Line := (y1+4 < Target.Y + mon.Obj.Rect.Y + mon.Obj.Rect.Height) and
6741 (y1-4 > Target.Y + mon.Obj.Rect.Y);
6742 Visible := True;
6743 IsPlayer := False;
6744 end;
6745 end;
6746 end;
6747 end;
6749 begin
6750 vsPlayer := LongBool(gGameSettings.Options and GAME_OPTION_BOTVSPLAYER);
6751 vsMonster := LongBool(gGameSettings.Options and GAME_OPTION_BOTVSMONSTER);
6753 // Åñëè òåêóùåå îðóæèå íå òî, ÷òî íóæíî, òî ìåíÿåì:
6754 if FCurrWeap <> FSelectedWeapon then
6755 NextWeapon();
6757 // Åñëè íóæíî ñòðåëÿòü è íóæíîå îðóæèå, òî íàæàòü "Ñòðåëÿòü":
6758 if (GetAIFlag('NEEDFIRE') <> '') and (FCurrWeap = FSelectedWeapon) then
6759 begin
6760 RemoveAIFlag('NEEDFIRE');
6762 case FCurrWeap of
6763 WEAPON_PLASMA, WEAPON_SUPERPULEMET, WEAPON_CHAINGUN: PressKey(KEY_FIRE, 20);
6764 WEAPON_SAW, WEAPON_KASTET, WEAPON_FLAMETHROWER: PressKey(KEY_FIRE, 40);
6765 else PressKey(KEY_FIRE);
6766 end;
6767 end;
6769 // Êîîðäèíàòû ñòâîëà:
6770 x1 := FObj.X + WEAPONPOINT[FDirection].X;
6771 y1 := FObj.Y + WEAPONPOINT[FDirection].Y;
6773 Target.UID := FTargetUID;
6775 ok := False;
6776 if Target.UID <> 0 then
6777 begin // Öåëü åñòü - íàñòðàèâàåì
6778 if (g_GetUIDType(Target.UID) = UID_PLAYER) and
6779 vsPlayer then
6780 begin // Èãðîê
6781 tpla := g_Player_Get(Target.UID);
6782 if tpla <> nil then
6783 with tpla do
6784 begin
6785 if (@FObj) <> nil then
6786 begin
6787 Target.X := FObj.X;
6788 Target.Y := FObj.Y;
6789 end;
6790 end;
6792 Target.cX := Target.X + PLAYER_RECT_CX;
6793 Target.cY := Target.Y + PLAYER_RECT_CY;
6794 Target.Rect := PLAYER_RECT;
6795 Target.Visible := g_TraceVector(x1, y1, Target.cX, Target.cY);
6796 Target.Line := (y1+4 < Target.Y+PLAYER_RECT.Y+PLAYER_RECT.Height) and
6797 (y1-4 > Target.Y+PLAYER_RECT.Y);
6798 Target.IsPlayer := True;
6799 ok := True;
6800 end
6801 else
6802 if (g_GetUIDType(Target.UID) = UID_MONSTER) and
6803 vsMonster then
6804 begin // Ìîíñòð
6805 mon := g_Monsters_ByUID(Target.UID);
6806 if mon <> nil then
6807 begin
6808 Target.X := mon.Obj.X;
6809 Target.Y := mon.Obj.Y;
6811 Target.cX := Target.X + mon.Obj.Rect.X + (mon.Obj.Rect.Width div 2);
6812 Target.cY := Target.Y + mon.Obj.Rect.Y + (mon.Obj.Rect.Height div 2);
6813 Target.Rect := mon.Obj.Rect;
6814 Target.Visible := g_TraceVector(x1, y1, Target.cX, Target.cY);
6815 Target.Line := (y1+4 < Target.Y + mon.Obj.Rect.Y + mon.Obj.Rect.Height) and
6816 (y1-4 > Target.Y + mon.Obj.Rect.Y);
6817 Target.IsPlayer := False;
6818 ok := True;
6819 end;
6820 end;
6821 end;
6823 if not ok then
6824 begin // Öåëè íåò - îáíóëÿåì
6825 Target.X := 0;
6826 Target.Y := 0;
6827 Target.cX := 0;
6828 Target.cY := 0;
6829 Target.Visible := False;
6830 Target.Line := False;
6831 Target.IsPlayer := False;
6832 end;
6834 targets := nil;
6836 // Åñëè öåëü íå âèäèìà èëè íå íà ëèíèè îãíÿ, òî èùåì âñå âîçìîæíûå öåëè:
6837 if (not Target.Line) or (not Target.Visible) then
6838 begin
6839 // Èãðîêè:
6840 if vsPlayer then
6841 for a := 0 to High(gPlayers) do
6842 if (gPlayers[a] <> nil) and (gPlayers[a].alive) and
6843 (gPlayers[a].FUID <> FUID) and
6844 (not SameTeam(FUID, gPlayers[a].FUID)) and
6845 (not gPlayers[a].NoTarget) and
6846 (gPlayers[a].FMegaRulez[MR_INVIS] < gTime) then
6847 begin
6848 if not TargetOnScreen(gPlayers[a].FObj.X + PLAYER_RECT.X,
6849 gPlayers[a].FObj.Y + PLAYER_RECT.Y) then
6850 Continue;
6852 x2 := gPlayers[a].FObj.X + PLAYER_RECT_CX;
6853 y2 := gPlayers[a].FObj.Y + PLAYER_RECT_CY;
6855 // Åñëè èãðîê íà ýêðàíå è íå ïðèêðûò ñòåíîé:
6856 if g_TraceVector(x1, y1, x2, y2) then
6857 begin
6858 // Äîáàâëÿåì ê ñïèñêó âîçìîæíûõ öåëåé:
6859 SetLength(targets, Length(targets)+1);
6860 with targets[High(targets)] do
6861 begin
6862 UID := gPlayers[a].FUID;
6863 X := gPlayers[a].FObj.X;
6864 Y := gPlayers[a].FObj.Y;
6865 cX := x2;
6866 cY := y2;
6867 Rect := PLAYER_RECT;
6868 Dist := g_PatchLength(x1, y1, x2, y2);
6869 Line := (y1+4 < Target.Y+PLAYER_RECT.Y+PLAYER_RECT.Height) and
6870 (y1-4 > Target.Y+PLAYER_RECT.Y);
6871 Visible := True;
6872 IsPlayer := True;
6873 end;
6874 end;
6875 end;
6877 // Ìîíñòðû:
6878 if vsMonster then g_Mons_ForEach(monsUpdate);
6879 end;
6881 // Åñëè åñòü âîçìîæíûå öåëè:
6882 // (Âûáèðàåì ëó÷øóþ, ìåíÿåì îðóæèå è áåæèì ê íåé/îò íåå)
6883 if targets <> nil then
6884 begin
6885 // Âûáèðàåì íàèëó÷øóþ öåëü:
6886 BestTarget := targets[0];
6887 if Length(targets) > 1 then
6888 for a := 1 to High(targets) do
6889 if Compare(BestTarget, targets[a]) = 1 then
6890 BestTarget := targets[a];
6892 // Åñëè ëó÷øàÿ öåëü "âèäíåå" òåêóùåé, òî òåêóùàÿ := ëó÷øàÿ:
6893 if ((not Target.Visible) and BestTarget.Visible and (Target.UID <> BestTarget.UID)) or
6894 ((not Target.Line) and BestTarget.Line and BestTarget.Visible) then
6895 begin
6896 Target := BestTarget;
6898 if (Healthy() = 3) or ((Healthy() = 2)) then
6899 begin // Åñëè çäîðîâû - äîãîíÿåì
6900 if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
6901 SetAIFlag('GORIGHT', '1');
6902 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
6903 SetAIFlag('GOLEFT', '1');
6904 end
6905 else
6906 begin // Åñëè ïîáèòû - óáåãàåì
6907 if ((RunDirection() = TDirection.D_LEFT) and (Target.X < FObj.X)) then
6908 SetAIFlag('GORIGHT', '1');
6909 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
6910 SetAIFlag('GOLEFT', '1');
6911 end;
6913 // Âûáèðàåì îðóæèå íà îñíîâå ðàññòîÿíèÿ è ïðèîðèòåòîâ:
6914 SelectWeapon(Abs(x1-Target.cX));
6915 end;
6916 end;
6918 // Åñëè åñòü öåëü:
6919 // (Äîãîíÿåì/óáåãàåì, ñòðåëÿåì ïî íàïðàâëåíèþ ê öåëè)
6920 // (Åñëè öåëü äàëåêî, òî õâàòèò ñëåäèòü çà íåé)
6921 if Target.UID <> 0 then
6922 begin
6923 if not TargetOnScreen(Target.X + Target.Rect.X,
6924 Target.Y + Target.Rect.Y) then
6925 begin // Öåëü ñáåæàëà ñ "ýêðàíà"
6926 if (Healthy() = 3) or ((Healthy() = 2)) then
6927 begin // Åñëè çäîðîâû - äîãîíÿåì
6928 if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
6929 SetAIFlag('GORIGHT', '1');
6930 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
6931 SetAIFlag('GOLEFT', '1');
6932 end
6933 else
6934 begin // Åñëè ïîáèòû - çàáûâàåì î öåëè è óáåãàåì
6935 Target.UID := 0;
6936 if ((RunDirection() = TDirection.D_LEFT) and (Target.X < FObj.X)) then
6937 SetAIFlag('GORIGHT', '1');
6938 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
6939 SetAIFlag('GOLEFT', '1');
6940 end;
6941 end
6942 else
6943 begin // Öåëü ïîêà íà "ýêðàíå"
6944 // Åñëè öåëü íå çàãîðîæåíà ñòåíîé, òî îòìå÷àåì, êîãäà åå âèäåëè:
6945 if g_TraceVector(x1, y1, Target.cX, Target.cY) then
6946 FLastVisible := gTime;
6947 // Åñëè ðàçíèöà âûñîò íå âåëèêà, òî äîãîíÿåì:
6948 if (Abs(FObj.Y-Target.Y) <= 128) then
6949 begin
6950 if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
6951 SetAIFlag('GORIGHT', '1');
6952 if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
6953 SetAIFlag('GOLEFT', '1');
6954 end;
6955 end;
6957 // Âûáèðàåì óãîë ââåðõ:
6958 if FDirection = TDirection.D_LEFT then
6959 angle := ANGLE_LEFTUP
6960 else
6961 angle := ANGLE_RIGHTUP;
6963 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6964 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6966 // Åñëè ïðè óãëå ââåðõ ìîæíî ïîïàñòü â ïðèáëèçèòåëüíîå ïîëîæåíèå öåëè:
6967 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
6968 Target.X+Target.Rect.X+GetInterval(FDifficult.DiagPrecision, 128), //96
6969 Target.Y+Target.Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
6970 Target.Rect.Width, Target.Rect.Height) and
6971 g_TraceVector(x1, y1, Target.cX, Target.cY) then
6972 begin // òî íóæíî ñòðåëÿòü ââåðõ
6973 SetAIFlag('NEEDFIRE', '1');
6974 SetAIFlag('NEEDSEEUP', '1');
6975 end;
6977 // Âûáèðàåì óãîë âíèç:
6978 if FDirection = TDirection.D_LEFT then
6979 angle := ANGLE_LEFTDOWN
6980 else
6981 angle := ANGLE_RIGHTDOWN;
6983 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6984 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6986 // Åñëè ïðè óãëå âíèç ìîæíî ïîïàñòü â ïðèáëèçèòåëüíîå ïîëîæåíèå öåëè:
6987 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
6988 Target.X+Target.Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
6989 Target.Y+Target.Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
6990 Target.Rect.Width, Target.Rect.Height) and
6991 g_TraceVector(x1, y1, Target.cX, Target.cY) then
6992 begin // òî íóæíî ñòðåëÿòü âíèç
6993 SetAIFlag('NEEDFIRE', '1');
6994 SetAIFlag('NEEDSEEDOWN', '1');
6995 end;
6997 // Åñëè öåëü âèäíî è îíà íà òàêîé æå âûñîòå:
6998 if Target.Visible and
6999 (y1+4 < Target.Y+Target.Rect.Y+Target.Rect.Height) and
7000 (y1-4 > Target.Y+Target.Rect.Y) then
7001 begin
7002 // Åñëè èäåì â ñòîðîíó öåëè, òî íàäî ñòðåëÿòü:
7003 if ((FDirection = TDirection.D_LEFT) and (Target.X < FObj.X)) or
7004 ((FDirection = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
7005 begin // òî íóæíî ñòðåëÿòü âïåðåä
7006 SetAIFlag('NEEDFIRE', '1');
7007 SetAIFlag('NEEDSEEDOWN', '');
7008 SetAIFlag('NEEDSEEUP', '');
7009 end;
7010 // Åñëè öåëü â ïðåäåëàõ "ýêðàíà" è ñëîæíîñòü ïîçâîëÿåò ïðûæêè ñáëèæåíèÿ:
7011 if Abs(FObj.X-Target.X) < Trunc(gPlayerScreenSize.X*0.75) then
7012 if GetRnd(FDifficult.CloseJump) then
7013 begin // òî åñëè ïîâåçåò - ïðûãàåì (îñîáåííî, åñëè áëèçêî)
7014 if Abs(FObj.X-Target.X) < 128 then
7015 a := 4
7016 else
7017 a := 30;
7018 if Random(a) = 0 then
7019 SetAIFlag('NEEDJUMP', '1');
7020 end;
7021 end;
7023 // Åñëè öåëü âñå åùå åñòü:
7024 if Target.UID <> 0 then
7025 if gTime-FLastVisible > 2000 then // Åñëè âèäåëè äàâíî
7026 Target.UID := 0 // òî çàáûòü öåëü
7027 else // Åñëè âèäåëè íåäàâíî
7028 begin // íî öåëü óáèëè
7029 if Target.IsPlayer then
7030 begin // Öåëü - èãðîê
7031 pla := g_Player_Get(Target.UID);
7032 if (pla = nil) or (not pla.alive) or pla.NoTarget or
7033 (pla.FMegaRulez[MR_INVIS] >= gTime) then
7034 Target.UID := 0; // òî çàáûòü öåëü
7035 end
7036 else
7037 begin // Öåëü - ìîíñòð
7038 mon := g_Monsters_ByUID(Target.UID);
7039 if (mon = nil) or (not mon.alive) then
7040 Target.UID := 0; // òî çàáûòü öåëü
7041 end;
7042 end;
7043 end; // if Target.UID <> 0
7045 FTargetUID := Target.UID;
7047 // Åñëè âîçìîæíûõ öåëåé íåò:
7048 // (Àòàêà ÷åãî-íèáóäü ñëåâà èëè ñïðàâà)
7049 if targets = nil then
7050 if GetAIFlag('ATTACKLEFT') <> '' then
7051 begin // Åñëè íóæíî àòàêîâàòü íàëåâî
7052 RemoveAIFlag('ATTACKLEFT');
7054 SetAIFlag('NEEDJUMP', '1');
7056 if RunDirection() = TDirection.D_RIGHT then
7057 begin // Èäåì íå â òó ñòîðîíó
7058 if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then
7059 begin // Åñëè çäîðîâû, òî, âîçìîæíî, ñòðåëÿåì áåæèì âëåâî è ñòðåëÿåì
7060 SetAIFlag('NEEDFIRE', '1');
7061 SetAIFlag('GOLEFT', '1');
7062 end;
7063 end
7064 else
7065 begin // Èäåì â íóæíóþ ñòîðîíó
7066 if GetRnd(FDifficult.InvisFire) then // Âîçìîæíî, ñòðåëÿåì âñëåïóþ
7067 SetAIFlag('NEEDFIRE', '1');
7068 if Healthy() <= 1 then // Ïîáèòû - óáåãàåì
7069 SetAIFlag('GORIGHT', '1');
7070 end;
7071 end
7072 else
7073 if GetAIFlag('ATTACKRIGHT') <> '' then
7074 begin // Åñëè íóæíî àòàêîâàòü íàïðàâî
7075 RemoveAIFlag('ATTACKRIGHT');
7077 SetAIFlag('NEEDJUMP', '1');
7079 if RunDirection() = TDirection.D_LEFT then
7080 begin // Èäåì íå â òó ñòîðîíó
7081 if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then
7082 begin // Åñëè çäîðîâû, òî, âîçìîæíî, áåæèì âïðàâî è ñòðåëÿåì
7083 SetAIFlag('NEEDFIRE', '1');
7084 SetAIFlag('GORIGHT', '1');
7085 end;
7086 end
7087 else
7088 begin
7089 if GetRnd(FDifficult.InvisFire) then // Âîçìîæíî, ñòðåëÿåì âñëåïóþ
7090 SetAIFlag('NEEDFIRE', '1');
7091 if Healthy() <= 1 then // Ïîáèòû - óáåãàåì
7092 SetAIFlag('GOLEFT', '1');
7093 end;
7094 end;
7096 //HACK! (does it belong there?)
7097 RealizeCurrentWeapon();
7099 // Åñëè åñòü âîçìîæíûå öåëè:
7100 // (Ñòðåëÿåì ïî íàïðàâëåíèþ ê öåëÿì)
7101 if (targets <> nil) and (GetAIFlag('NEEDFIRE') <> '') then
7102 for a := 0 to High(targets) do
7103 begin
7104 // Åñëè ìîæåì ñòðåëÿòü ïî äèàãîíàëè:
7105 if GetRnd(FDifficult.DiagFire) then
7106 begin
7107 // Èùåì öåëü ñâåðõó è ñòðåëÿåì, åñëè åñòü:
7108 if FDirection = TDirection.D_LEFT then
7109 angle := ANGLE_LEFTUP
7110 else
7111 angle := ANGLE_RIGHTUP;
7113 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7114 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7116 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
7117 targets[a].X+targets[a].Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
7118 targets[a].Y+targets[a].Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
7119 targets[a].Rect.Width, targets[a].Rect.Height) and
7120 g_TraceVector(x1, y1, targets[a].cX, targets[a].cY) then
7121 begin
7122 SetAIFlag('NEEDFIRE', '1');
7123 SetAIFlag('NEEDSEEUP', '1');
7124 end;
7126 // Èùåì öåëü ñíèçó è ñòðåëÿåì, åñëè åñòü:
7127 if FDirection = TDirection.D_LEFT then
7128 angle := ANGLE_LEFTDOWN
7129 else
7130 angle := ANGLE_RIGHTDOWN;
7132 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7133 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
7135 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
7136 targets[a].X+targets[a].Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
7137 targets[a].Y+targets[a].Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
7138 targets[a].Rect.Width, targets[a].Rect.Height) and
7139 g_TraceVector(x1, y1, targets[a].cX, targets[a].cY) then
7140 begin
7141 SetAIFlag('NEEDFIRE', '1');
7142 SetAIFlag('NEEDSEEDOWN', '1');
7143 end;
7144 end;
7146 // Åñëè öåëü "ïåðåä íîñîì", òî ñòðåëÿåì:
7147 if targets[a].Line and targets[a].Visible and
7148 (((FDirection = TDirection.D_LEFT) and (targets[a].X < FObj.X)) or
7149 ((FDirection = TDirection.D_RIGHT) and (targets[a].X > FObj.X))) then
7150 begin
7151 SetAIFlag('NEEDFIRE', '1');
7152 Break;
7153 end;
7154 end;
7156 // Åñëè ëåòèò ïóëÿ, òî, âîçìîæíî, ïîäïðûãèâàåì:
7157 if g_Weapon_Danger(FUID, FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y,
7158 PLAYER_RECT.Width, PLAYER_RECT.Height,
7159 40+GetInterval(FDifficult.Cover, 40)) then
7160 SetAIFlag('NEEDJUMP', '1');
7162 // Åñëè êîí÷èëèñü ïàòîðíû, òî íóæíî ñìåíèòü îðóæèå:
7163 ammo := GetAmmoByWeapon(FCurrWeap);
7164 if ((FCurrWeap = WEAPON_SHOTGUN2) and (ammo < 2)) or
7165 ((FCurrWeap = WEAPON_BFG) and (ammo < 40)) or
7166 (ammo = 0) then
7167 SetAIFlag('SELECTWEAPON', '1');
7169 // Åñëè íóæíî ñìåíèòü îðóæèå, òî âûáèðàåì íóæíîå:
7170 if GetAIFlag('SELECTWEAPON') = '1' then
7171 begin
7172 SelectWeapon(-1);
7173 RemoveAIFlag('SELECTWEAPON');
7174 end;
7175 end;
7177 procedure TBot.Update();
7178 var
7179 EnableAI: Boolean;
7180 begin
7181 if not FAlive then
7182 begin // Respawn
7183 ReleaseKeys();
7184 PressKey(KEY_UP);
7185 end
7186 else
7187 begin
7188 EnableAI := True;
7190 // Ïðîâåðÿåì, îòêëþ÷¸í ëè AI áîòîâ
7191 if (g_debug_BotAIOff = 1) and (Team = TEAM_RED) then
7192 EnableAI := False;
7193 if (g_debug_BotAIOff = 2) and (Team = TEAM_BLUE) then
7194 EnableAI := False;
7195 if g_debug_BotAIOff = 3 then
7196 EnableAI := False;
7198 if EnableAI then
7199 begin
7200 UpdateMove();
7201 UpdateCombat();
7202 end
7203 else
7204 begin
7205 RealizeCurrentWeapon();
7206 end;
7207 end;
7209 inherited Update();
7210 end;
7212 procedure TBot.ReleaseKey(Key: Byte);
7213 begin
7214 with FKeys[Key] do
7215 begin
7216 Pressed := False;
7217 Time := 0;
7218 end;
7219 end;
7221 function TBot.KeyPressed(Key: Word): Boolean;
7222 begin
7223 Result := FKeys[Key].Pressed;
7224 end;
7226 function TBot.GetAIFlag(aName: String20): String20;
7227 var
7228 a: Integer;
7229 begin
7230 Result := '';
7232 aName := LowerCase(aName);
7234 if FAIFlags <> nil then
7235 for a := 0 to High(FAIFlags) do
7236 if LowerCase(FAIFlags[a].Name) = aName then
7237 begin
7238 Result := FAIFlags[a].Value;
7239 Break;
7240 end;
7241 end;
7243 procedure TBot.RemoveAIFlag(aName: String20);
7244 var
7245 a, b: Integer;
7246 begin
7247 if FAIFlags = nil then Exit;
7249 aName := LowerCase(aName);
7251 for a := 0 to High(FAIFlags) do
7252 if LowerCase(FAIFlags[a].Name) = aName then
7253 begin
7254 if a <> High(FAIFlags) then
7255 for b := a to High(FAIFlags)-1 do
7256 FAIFlags[b] := FAIFlags[b+1];
7258 SetLength(FAIFlags, Length(FAIFlags)-1);
7259 Break;
7260 end;
7261 end;
7263 procedure TBot.SetAIFlag(aName, fValue: String20);
7264 var
7265 a: Integer;
7266 ok: Boolean;
7267 begin
7268 a := 0;
7269 ok := False;
7271 aName := LowerCase(aName);
7273 if FAIFlags <> nil then
7274 for a := 0 to High(FAIFlags) do
7275 if LowerCase(FAIFlags[a].Name) = aName then
7276 begin
7277 ok := True;
7278 Break;
7279 end;
7281 if ok then FAIFlags[a].Value := fValue
7282 else
7283 begin
7284 SetLength(FAIFlags, Length(FAIFlags)+1);
7285 with FAIFlags[High(FAIFlags)] do
7286 begin
7287 Name := aName;
7288 Value := fValue;
7289 end;
7290 end;
7291 end;
7293 procedure TBot.UpdateMove;
7295 procedure GoLeft(Time: Word = 1);
7296 begin
7297 ReleaseKey(KEY_LEFT);
7298 ReleaseKey(KEY_RIGHT);
7299 PressKey(KEY_LEFT, Time);
7300 SetDirection(TDirection.D_LEFT);
7301 end;
7303 procedure GoRight(Time: Word = 1);
7304 begin
7305 ReleaseKey(KEY_LEFT);
7306 ReleaseKey(KEY_RIGHT);
7307 PressKey(KEY_RIGHT, Time);
7308 SetDirection(TDirection.D_RIGHT);
7309 end;
7311 function Rnd(a: Word): Boolean;
7312 begin
7313 Result := Random(a) = 0;
7314 end;
7316 procedure Turn(Time: Word = 1200);
7317 begin
7318 if RunDirection() = TDirection.D_LEFT then GoRight(Time) else GoLeft(Time);
7319 end;
7321 procedure Stop();
7322 begin
7323 ReleaseKey(KEY_LEFT);
7324 ReleaseKey(KEY_RIGHT);
7325 end;
7327 function CanRunLeft(): Boolean;
7328 begin
7329 Result := not CollideLevel(-1, 0);
7330 end;
7332 function CanRunRight(): Boolean;
7333 begin
7334 Result := not CollideLevel(1, 0);
7335 end;
7337 function CanRun(): Boolean;
7338 begin
7339 if RunDirection() = TDirection.D_LEFT then Result := CanRunLeft() else Result := CanRunRight();
7340 end;
7342 procedure Jump(Time: Word = 30);
7343 begin
7344 PressKey(KEY_JUMP, Time);
7345 end;
7347 function NearHole(): Boolean;
7348 var
7349 x, sx: Integer;
7350 begin
7351 { TODO 5 : Ëåñòíèöû }
7352 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
7353 for x := 1 to PLAYER_RECT.Width do
7354 if (not StayOnStep(x*sx, 0)) and
7355 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
7356 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
7357 begin
7358 Result := True;
7359 Exit;
7360 end;
7362 Result := False;
7363 end;
7365 function BorderHole(): Boolean;
7366 var
7367 x, sx, xx: Integer;
7368 begin
7369 { TODO 5 : Ëåñòíèöû }
7370 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
7371 for x := 1 to PLAYER_RECT.Width do
7372 if (not StayOnStep(x*sx, 0)) and
7373 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
7374 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
7375 begin
7376 for xx := x to x+32 do
7377 if CollideLevel(xx*sx, PLAYER_RECT.Height) then
7378 begin
7379 Result := True;
7380 Exit;
7381 end;
7382 end;
7384 Result := False;
7385 end;
7387 function NearDeepHole(): Boolean;
7388 var
7389 x, sx, y: Integer;
7390 begin
7391 Result := False;
7393 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
7394 y := 3;
7396 for x := 1 to PLAYER_RECT.Width do
7397 if (not StayOnStep(x*sx, 0)) and
7398 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
7399 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
7400 begin
7401 while FObj.Y+y*PLAYER_RECT.Height < gMapInfo.Height do
7402 begin
7403 if CollideLevel(x*sx, PLAYER_RECT.Height*y) then Exit;
7404 y := y+1;
7405 end;
7407 Result := True;
7408 end else Result := False;
7409 end;
7411 function OverDeepHole(): Boolean;
7412 var
7413 y: Integer;
7414 begin
7415 Result := False;
7417 y := 1;
7418 while FObj.Y+y*PLAYER_RECT.Height < gMapInfo.Height do
7419 begin
7420 if CollideLevel(0, PLAYER_RECT.Height*y) then Exit;
7421 y := y+1;
7422 end;
7424 Result := True;
7425 end;
7427 function OnGround(): Boolean;
7428 begin
7429 Result := StayOnStep(0, 0) or CollideLevel(0, 1);
7430 end;
7432 function OnLadder(): Boolean;
7433 begin
7434 Result := FullInStep(0, 0);
7435 end;
7437 function BelowLadder(): Boolean;
7438 begin
7439 Result := (FullInStep(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) and
7440 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or
7441 (FullInStep(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) and
7442 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP));
7443 end;
7445 function BelowLiftUp(): Boolean;
7446 begin
7447 Result := ((FullInLift(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) = -1) and
7448 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or
7449 ((FullInLift(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) = -1) and
7450 not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP));
7451 end;
7453 function OnTopLift(): Boolean;
7454 begin
7455 Result := (FullInLift(0, 0) = -1) and (FullInLift(0, -32) = 0);
7456 end;
7458 function CanJumpOver(): Boolean;
7459 var
7460 sx, y: Integer;
7461 begin
7462 sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
7464 Result := False;
7466 if not CollideLevel(sx, 0) then Exit;
7468 for y := 1 to BOT_MAXJUMP do
7469 if CollideLevel(0, -y) then Exit else
7470 if not CollideLevel(sx, -y) then
7471 begin
7472 Result := True;
7473 Exit;
7474 end;
7475 end;
7477 function CanJumpUp(Dist: ShortInt): Boolean;
7478 var
7479 y, yy: Integer;
7480 c: Boolean;
7481 begin
7482 Result := False;
7484 if CollideLevel(Dist, 0) then Exit;
7486 c := False;
7487 for y := 0 to BOT_MAXJUMP do
7488 if CollideLevel(Dist, -y) then
7489 begin
7490 c := True;
7491 Break;
7492 end;
7494 if not c then Exit;
7496 c := False;
7497 for yy := y+1 to BOT_MAXJUMP do
7498 if not CollideLevel(Dist, -yy) then
7499 begin
7500 c := True;
7501 Break;
7502 end;
7504 if not c then Exit;
7506 c := False;
7507 for y := 0 to BOT_MAXJUMP do
7508 if CollideLevel(0, -y) then
7509 begin
7510 c := True;
7511 Break;
7512 end;
7514 if c then Exit;
7516 if y < yy then Exit;
7518 Result := True;
7519 end;
7521 function IsSafeTrigger(): Boolean;
7522 var
7523 a: Integer;
7524 begin
7525 Result := True;
7526 if gTriggers = nil then
7527 Exit;
7528 for a := 0 to High(gTriggers) do
7529 if Collide(gTriggers[a].X,
7530 gTriggers[a].Y,
7531 gTriggers[a].Width,
7532 gTriggers[a].Height) and
7533 (gTriggers[a].TriggerType in [TRIGGER_EXIT, TRIGGER_CLOSEDOOR,
7534 TRIGGER_CLOSETRAP, TRIGGER_TRAP,
7535 TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF,
7536 TRIGGER_ONOFF, TRIGGER_SPAWNMONSTER,
7537 TRIGGER_DAMAGE, TRIGGER_SHOT]) then
7538 Result := False;
7539 end;
7541 begin
7542 // Âîçìîæíî, íàæèìàåì êíîïêó:
7543 if Rnd(16) and IsSafeTrigger() then
7544 PressKey(KEY_OPEN);
7546 // Åñëè ïîä ëèôòîì èëè ñòóïåíüêàìè, òî, âîçìîæíî, ïðûãàåì:
7547 if OnLadder() or ((BelowLadder() or BelowLiftUp()) and Rnd(8)) then
7548 begin
7549 ReleaseKey(KEY_LEFT);
7550 ReleaseKey(KEY_RIGHT);
7551 Jump();
7552 end;
7554 // Èäåì âëåâî, åñëè íàäî áûëî:
7555 if GetAIFlag('GOLEFT') <> '' then
7556 begin
7557 RemoveAIFlag('GOLEFT');
7558 if CanRunLeft() then
7559 GoLeft(360);
7560 end;
7562 // Èäåì âïðàâî, åñëè íàäî áûëî:
7563 if GetAIFlag('GORIGHT') <> '' then
7564 begin
7565 RemoveAIFlag('GORIGHT');
7566 if CanRunRight() then
7567 GoRight(360);
7568 end;
7570 // Åñëè âûëåòåëè çà êàðòó, òî ïðîáóåì âåðíóòüñÿ:
7571 if FObj.X < -32 then
7572 GoRight(360)
7573 else
7574 if FObj.X+32 > gMapInfo.Width then
7575 GoLeft(360);
7577 // Ïðûãàåì, åñëè íàäî áûëî:
7578 if GetAIFlag('NEEDJUMP') <> '' then
7579 begin
7580 Jump(0);
7581 RemoveAIFlag('NEEDJUMP');
7582 end;
7584 // Ñìîòðèì ââåðõ, åñëè íàäî áûëî:
7585 if GetAIFlag('NEEDSEEUP') <> '' then
7586 begin
7587 ReleaseKey(KEY_UP);
7588 ReleaseKey(KEY_DOWN);
7589 PressKey(KEY_UP, 20);
7590 RemoveAIFlag('NEEDSEEUP');
7591 end;
7593 // Ñìîòðèì âíèç, åñëè íàäî áûëî:
7594 if GetAIFlag('NEEDSEEDOWN') <> '' then
7595 begin
7596 ReleaseKey(KEY_UP);
7597 ReleaseKey(KEY_DOWN);
7598 PressKey(KEY_DOWN, 20);
7599 RemoveAIFlag('NEEDSEEDOWN');
7600 end;
7602 // Åñëè íóæíî áûëî â äûðó è ìû íå íà çåìëå, òî ïîêîðíî ëåòèì:
7603 if GetAIFlag('GOINHOLE') <> '' then
7604 if not OnGround() then
7605 begin
7606 ReleaseKey(KEY_LEFT);
7607 ReleaseKey(KEY_RIGHT);
7608 RemoveAIFlag('GOINHOLE');
7609 SetAIFlag('FALLINHOLE', '1');
7610 end;
7612 // Åñëè ïàäàëè è äîñòèãëè çåìëè, òî õâàòèò ïàäàòü:
7613 if GetAIFlag('FALLINHOLE') <> '' then
7614 if OnGround() then
7615 RemoveAIFlag('FALLINHOLE');
7617 // Åñëè ëåòåëè ïðÿìî è ñåé÷àñ íå íà ëåñòíèöå èëè íà âåðøèíå ëèôòà, òî îòõîäèì â ñòîðîíó:
7618 if not (KeyPressed(KEY_LEFT) or KeyPressed(KEY_RIGHT)) then
7619 if GetAIFlag('FALLINHOLE') = '' then
7620 if (not OnLadder()) or (FObj.Vel.Y >= 0) or (OnTopLift()) then
7621 if Rnd(2) then
7622 GoLeft(360)
7623 else
7624 GoRight(360);
7626 // Åñëè íà çåìëå è ìîæíî ïîäïðûãíóòü, òî, âîçìîæíî, ïðûãàåì:
7627 if OnGround() and
7628 CanJumpUp(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*32) and
7629 Rnd(8) then
7630 Jump();
7632 // Åñëè íà çåìëå è âîçëå äûðû (ãëóáèíà > 2 ðîñòîâ èãðîêà):
7633 if OnGround() and NearHole() then
7634 if NearDeepHole() then // Åñëè ýòî áåçäíà
7635 case Random(6) of
7636 0..3: Turn(); // Áåæèì îáðàòíî
7637 4: Jump(); // Ïðûãàåì
7638 5: begin // Ïðûãàåì îáðàòíî
7639 Turn();
7640 Jump();
7641 end;
7642 end
7643 else // Ýòî íå áåçäíà è ìû åùå íå ëåòèì òóäà
7644 if GetAIFlag('GOINHOLE') = '' then
7645 case Random(6) of
7646 0: Turn(); // Íå íóæíî òóäà
7647 1: Jump(); // Âäðóã ïîâåçåò - ïðûãàåì
7648 else // Åñëè ÿìà ñ ãðàíèöåé, òî ïðè ñëó÷àå ìîæíî òóäà ïðûãíóòü
7649 if BorderHole() then
7650 SetAIFlag('GOINHOLE', '1');
7651 end;
7653 // Åñëè íà çåìëå, íî íåêóäà èäòè:
7654 if (not CanRun()) and OnGround() then
7655 begin
7656 // Åñëè ìû íà ëåñòíèöå èëè ìîæíî ïåðåïðûãíóòü, òî ïðûãàåì:
7657 if CanJumpOver() or OnLadder() then
7658 Jump()
7659 else // èíà÷å ïîïûòàåìñÿ â äðóãóþ ñòîðîíó
7660 if Random(2) = 0 then
7661 begin
7662 if IsSafeTrigger() then
7663 PressKey(KEY_OPEN);
7664 end else
7665 Turn();
7666 end;
7668 // Îñòàëîñü ìàëî âîçäóõà:
7669 if FAir < 36 * 2 then
7670 Jump(20);
7672 // Âûáèðàåìñÿ èç êèñëîòû, åñëè íåò êîñòþìà, îáîæãëèñü, èëè ìàëî çäîðîâüÿ:
7673 if (FMegaRulez[MR_SUIT] < gTime) and ((FLastHit = HIT_ACID) or (Healthy() <= 1)) then
7674 if BodyInAcid(0, 0) then
7675 Jump();
7676 end;
7678 function TBot.FullInStep(XInc, YInc: Integer): Boolean;
7679 begin
7680 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
7681 PLAYER_RECT.Width, PLAYER_RECT.Height, PANEL_STEP, False);
7682 end;
7684 {function TBot.NeedItem(Item: Byte): Byte;
7685 begin
7686 Result := 4;
7687 end;}
7689 procedure TBot.SelectWeapon(Dist: Integer);
7690 var
7691 a: Integer;
7693 function HaveAmmo(weapon: Byte): Boolean;
7694 begin
7695 case weapon of
7696 WEAPON_PISTOL: Result := FAmmo[A_BULLETS] >= 1;
7697 WEAPON_SHOTGUN1: Result := FAmmo[A_SHELLS] >= 1;
7698 WEAPON_SHOTGUN2: Result := FAmmo[A_SHELLS] >= 2;
7699 WEAPON_CHAINGUN: Result := FAmmo[A_BULLETS] >= 10;
7700 WEAPON_ROCKETLAUNCHER: Result := FAmmo[A_ROCKETS] >= 1;
7701 WEAPON_PLASMA: Result := FAmmo[A_CELLS] >= 10;
7702 WEAPON_BFG: Result := FAmmo[A_CELLS] >= 40;
7703 WEAPON_SUPERPULEMET: Result := FAmmo[A_SHELLS] >= 1;
7704 WEAPON_FLAMETHROWER: Result := FAmmo[A_FUEL] >= 1;
7705 else Result := True;
7706 end;
7707 end;
7709 begin
7710 if Dist = -1 then Dist := BOT_LONGDIST;
7712 if Dist > BOT_LONGDIST then
7713 begin // Äàëüíèé áîé
7714 for a := 0 to 9 do
7715 if FWeapon[FDifficult.WeaponPrior[a]] and HaveAmmo(FDifficult.WeaponPrior[a]) then
7716 begin
7717 FSelectedWeapon := FDifficult.WeaponPrior[a];
7718 Break;
7719 end;
7720 end
7721 else //if Dist > BOT_UNSAFEDIST then
7722 begin // Áëèæíèé áîé
7723 for a := 0 to 9 do
7724 if FWeapon[FDifficult.CloseWeaponPrior[a]] and HaveAmmo(FDifficult.CloseWeaponPrior[a]) then
7725 begin
7726 FSelectedWeapon := FDifficult.CloseWeaponPrior[a];
7727 Break;
7728 end;
7729 end;
7730 { else
7731 begin
7732 for a := 0 to 9 do
7733 if FWeapon[FDifficult.SafeWeaponPrior[a]] and HaveAmmo(FDifficult.SafeWeaponPrior[a]) then
7734 begin
7735 FSelectedWeapon := FDifficult.SafeWeaponPrior[a];
7736 Break;
7737 end;
7738 end;}
7739 end;
7741 function TBot.PickItem(ItemType: Byte; force: Boolean; var remove: Boolean): Boolean;
7742 begin
7743 Result := inherited PickItem(ItemType, force, remove);
7745 if Result then SetAIFlag('SELECTWEAPON', '1');
7746 end;
7748 function TBot.Heal(value: Word; Soft: Boolean): Boolean;
7749 begin
7750 Result := inherited Heal(value, Soft);
7751 end;
7753 function TBot.Healthy(): Byte;
7754 begin
7755 if FMegaRulez[MR_INVUL] >= gTime then Result := 3
7756 else if (FHealth > 80) or ((FHealth > 50) and (FArmor > 20)) then Result := 3
7757 else if (FHealth > 50) then Result := 2
7758 else if (FHealth > 20) then Result := 1
7759 else Result := 0;
7760 end;
7762 function TBot.TargetOnScreen(TX, TY: Integer): Boolean;
7763 begin
7764 Result := (Abs(FObj.X-TX) <= Trunc(gPlayerScreenSize.X*0.6)) and
7765 (Abs(FObj.Y-TY) <= Trunc(gPlayerScreenSize.Y*0.6));
7766 end;
7768 procedure TBot.OnDamage(Angle: SmallInt);
7769 var
7770 pla: TPlayer;
7771 mon: TMonster;
7772 ok: Boolean;
7773 begin
7774 inherited;
7776 if (Angle = 0) or (Angle = 180) then
7777 begin
7778 ok := False;
7779 if (g_GetUIDType(FLastSpawnerUID) = UID_PLAYER) and
7780 LongBool(gGameSettings.Options and GAME_OPTION_BOTVSPLAYER) then
7781 begin // Èãðîê
7782 pla := g_Player_Get(FLastSpawnerUID);
7783 ok := not TargetOnScreen(pla.FObj.X + PLAYER_RECT.X,
7784 pla.FObj.Y + PLAYER_RECT.Y);
7785 end
7786 else
7787 if (g_GetUIDType(FLastSpawnerUID) = UID_MONSTER) and
7788 LongBool(gGameSettings.Options and GAME_OPTION_BOTVSMONSTER) then
7789 begin // Ìîíñòð
7790 mon := g_Monsters_ByUID(FLastSpawnerUID);
7791 ok := not TargetOnScreen(mon.Obj.X + mon.Obj.Rect.X,
7792 mon.Obj.Y + mon.Obj.Rect.Y);
7793 end;
7795 if ok then
7796 if Angle = 0 then
7797 SetAIFlag('ATTACKLEFT', '1')
7798 else
7799 SetAIFlag('ATTACKRIGHT', '1');
7800 end;
7801 end;
7803 function TBot.RunDirection(): TDirection;
7804 begin
7805 if Abs(Vel.X) >= 1 then
7806 begin
7807 if Vel.X > 0 then Result := TDirection.D_RIGHT else Result := TDirection.D_LEFT;
7808 end else
7809 Result := FDirection;
7810 end;
7812 function TBot.GetRnd(a: Byte): Boolean;
7813 begin
7814 if a = 0 then Result := False
7815 else if a = 255 then Result := True
7816 else Result := Random(256) > 255-a;
7817 end;
7819 function TBot.GetInterval(a: Byte; radius: SmallInt): SmallInt;
7820 begin
7821 Result := Round((255-a)/255*radius*(Random(2)-1));
7822 end;
7825 procedure TDifficult.save (st: TStream);
7826 begin
7827 utils.writeInt(st, Byte(DiagFire));
7828 utils.writeInt(st, Byte(InvisFire));
7829 utils.writeInt(st, Byte(DiagPrecision));
7830 utils.writeInt(st, Byte(FlyPrecision));
7831 utils.writeInt(st, Byte(Cover));
7832 utils.writeInt(st, Byte(CloseJump));
7833 st.WriteBuffer(WeaponPrior[Low(WeaponPrior)], sizeof(WeaponPrior));
7834 st.WriteBuffer(CloseWeaponPrior[Low(CloseWeaponPrior)], sizeof(CloseWeaponPrior));
7835 end;
7837 procedure TDifficult.load (st: TStream);
7838 begin
7839 DiagFire := utils.readByte(st);
7840 InvisFire := utils.readByte(st);
7841 DiagPrecision := utils.readByte(st);
7842 FlyPrecision := utils.readByte(st);
7843 Cover := utils.readByte(st);
7844 CloseJump := utils.readByte(st);
7845 st.ReadBuffer(WeaponPrior[Low(WeaponPrior)], sizeof(WeaponPrior));
7846 st.ReadBuffer(CloseWeaponPrior[Low(CloseWeaponPrior)], sizeof(CloseWeaponPrior));
7847 end;
7850 procedure TBot.SaveState (st: TStream);
7851 var
7852 i: Integer;
7853 dw: Integer;
7854 begin
7855 inherited SaveState(st);
7856 utils.writeSign(st, 'BOT0');
7857 // Âûáðàííîå îðóæèå
7858 utils.writeInt(st, Byte(FSelectedWeapon));
7859 // UID öåëè
7860 utils.writeInt(st, Word(FTargetUID));
7861 // Âðåìÿ ïîòåðè öåëè
7862 utils.writeInt(st, LongWord(FLastVisible));
7863 // Êîëè÷åñòâî ôëàãîâ ÈÈ
7864 dw := Length(FAIFlags);
7865 utils.writeInt(st, LongInt(dw));
7866 // Ôëàãè ÈÈ
7867 for i := 0 to dw-1 do
7868 begin
7869 utils.writeStr(st, FAIFlags[i].Name, 20);
7870 utils.writeStr(st, FAIFlags[i].Value, 20);
7871 end;
7872 // Íàñòðîéêè ñëîæíîñòè
7873 FDifficult.save(st);
7874 end;
7877 procedure TBot.LoadState (st: TStream);
7878 var
7879 i: Integer;
7880 dw: Integer;
7881 begin
7882 inherited LoadState(st);
7883 if not utils.checkSign(st, 'BOT0') then raise XStreamError.Create('invalid bot signature');
7884 // Âûáðàííîå îðóæèå
7885 FSelectedWeapon := utils.readByte(st);
7886 // UID öåëè
7887 FTargetUID := utils.readWord(st);
7888 // Âðåìÿ ïîòåðè öåëè
7889 FLastVisible := utils.readLongWord(st);
7890 // Êîëè÷åñòâî ôëàãîâ ÈÈ
7891 dw := utils.readLongInt(st);
7892 if (dw < 0) or (dw > 16384) then raise XStreamError.Create('invalid number of bot AI flags');
7893 SetLength(FAIFlags, dw);
7894 // Ôëàãè ÈÈ
7895 for i := 0 to dw-1 do
7896 begin
7897 FAIFlags[i].Name := utils.readStr(st, 20);
7898 FAIFlags[i].Value := utils.readStr(st, 20);
7899 end;
7900 // Íàñòðîéêè ñëîæíîñòè
7901 FDifficult.load(st);
7902 end;
7905 begin
7906 conRegVar('cheat_berserk_autoswitch', @gBerserkAutoswitch, 'autoswitch to fist when berserk pack taken', '', true, true);
7907 end.