DEADSOFTWARE

slow down weapon switching a little if more than one weapon requested
[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 {$MODE DELPHI}
17 unit g_player;
19 interface
21 uses
22 e_graphics, g_playermodel, g_basic, g_textures,
23 g_weapons, g_phys, g_sound, g_saveload, MAPSTRUCT,
24 BinEditor, g_panel;
26 const
27 KEY_LEFT = 1;
28 KEY_RIGHT = 2;
29 KEY_UP = 3;
30 KEY_DOWN = 4;
31 KEY_FIRE = 5;
32 KEY_NEXTWEAPON = 6;
33 KEY_PREVWEAPON = 7;
34 KEY_OPEN = 8;
35 KEY_JUMP = 9;
36 KEY_CHAT = 10;
38 R_ITEM_BACKPACK = 0;
39 R_KEY_RED = 1;
40 R_KEY_GREEN = 2;
41 R_KEY_BLUE = 3;
42 R_BERSERK = 4;
44 MR_SUIT = 0;
45 MR_INVUL = 1;
46 MR_INVIS = 2;
47 MR_MAX = 2;
49 A_BULLETS = 0;
50 A_SHELLS = 1;
51 A_ROCKETS = 2;
52 A_CELLS = 3;
54 K_SIMPLEKILL = 0;
55 K_HARDKILL = 1;
56 K_EXTRAHARDKILL = 2;
57 K_FALLKILL = 3;
59 T_RESPAWN = 0;
60 T_SWITCH = 1;
61 T_USE = 2;
62 T_FLAGCAP = 3;
64 TEAM_NONE = 0;
65 TEAM_RED = 1;
66 TEAM_BLUE = 2;
67 TEAM_COOP = 3;
69 SHELL_BULLET = 0;
70 SHELL_SHELL = 1;
71 SHELL_DBLSHELL = 2;
73 ANGLE_NONE = Low(SmallInt);
75 CORPSE_STATE_REMOVEME = 0;
76 CORPSE_STATE_NORMAL = 1;
77 CORPSE_STATE_MESS = 2;
79 PLAYER_RECT: TRectWH = (X:15; Y:12; Width:34; Height:52);
80 PLAYER_RECT_CX = 15+(34 div 2);
81 PLAYER_RECT_CY = 12+(52 div 2);
82 PLAYER_CORPSERECT: TRectWH = (X:15; Y:48; Width:34; Height:16);
84 PLAYER_HP_SOFT = 100;
85 PLAYER_HP_LIMIT = 200;
86 PLAYER_AP_SOFT = 100;
87 PLAYER_AP_LIMIT = 200;
88 SUICIDE_DAMAGE = 112;
90 PLAYER1_DEF_COLOR: TRGB = (R:64; G:175; B:48);
91 PLAYER2_DEF_COLOR: TRGB = (R:96; G:96; B:96);
93 type
94 TPlayerStat = record
95 Ping: Word;
96 Loss: Byte;
97 Name: String;
98 Team: Byte;
99 Frags: SmallInt;
100 Deaths: SmallInt;
101 Lives: Byte;
102 Kills: Word;
103 Color: TRGB;
104 Spectator: Boolean;
105 end;
107 TPlayerStatArray = Array of TPlayerStat;
109 TPlayerSavedState = record
110 Health: Integer;
111 Armor: Integer;
112 Air: Integer;
113 JetFuel: Integer;
114 CurrWeap: Byte;
115 NextWeap: WORD;
116 NextWeapDelay: Byte;
117 Ammo: Array [A_BULLETS..A_CELLS] of Word;
118 MaxAmmo: Array [A_BULLETS..A_CELLS] of Word;
119 Weapon: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Boolean;
120 Rulez: Set of R_ITEM_BACKPACK..R_BERSERK;
121 WaitRecall: Boolean;
122 end;
124 TKeyState = record
125 Pressed: Boolean;
126 Time: Word;
127 end;
129 TPlayer = class (TObject)
130 private
131 FIamBot: Boolean;
132 FUID: Word;
133 FName: String;
134 FTeam: Byte;
135 FLive: Boolean;
136 FSpawned: Boolean;
137 FDirection: TDirection;
138 FHealth: Integer;
139 FLives: Byte;
140 FArmor: Integer;
141 FAir: Integer;
142 FPain: Integer;
143 FPickup: Integer;
144 FKills: Integer;
145 FMonsterKills: Integer;
146 FFrags: Integer;
147 FFragCombo: Byte;
148 FLastFrag: LongWord;
149 FComboEvnt: Integer;
150 FDeath: Integer;
151 FCanJetpack: Boolean;
152 FJetFuel: Integer;
153 FFlag: Byte;
154 FSecrets: Integer;
155 FCurrWeap: Byte;
156 FNextWeap: WORD;
157 FNextWeapDelay: Byte; // frames
158 FBFGFireCounter: SmallInt;
159 FLastSpawnerUID: Word;
160 FLastHit: Byte;
161 FObj: TObj;
162 FXTo, FYTo: Integer;
163 FSpectatePlayer: Integer;
165 FSavedState: TPlayerSavedState;
167 FModel: TPlayerModel;
168 FActionPrior: Byte;
169 FActionAnim: Byte;
170 FActionForce: Boolean;
171 FActionChanged: Boolean;
172 FAngle: SmallInt;
173 FFireAngle: SmallInt;
174 FIncCam: Integer;
175 FShellTimer: Integer;
176 FShellType: Byte;
177 FSawSound: TPlayableSound;
178 FSawSoundIdle: TPlayableSound;
179 FSawSoundHit: TPlayableSound;
180 FSawSoundSelect: TPlayableSound;
181 FJetSoundOn: TPlayableSound;
182 FJetSoundOff: TPlayableSound;
183 FJetSoundFly: TPlayableSound;
184 FGodMode: Boolean;
185 FNoTarget: Boolean;
186 FNoReload: Boolean;
187 FJustTeleported: Boolean;
188 FNetTime: LongWord;
190 function CollideLevel(XInc, YInc: Integer): Boolean;
191 function StayOnStep(XInc, YInc: Integer): Boolean;
192 function HeadInLiquid(XInc, YInc: Integer): Boolean;
193 function BodyInLiquid(XInc, YInc: Integer): Boolean;
194 function BodyInAcid(XInc, YInc: Integer): Boolean;
195 function FullInLift(XInc, YInc: Integer): Integer;
196 {procedure CollideItem();}
197 procedure FlySmoke(Times: DWORD = 1);
198 function GetAmmoByWeapon(Weapon: Byte): Word;
199 procedure SetAction(Action: Byte; Force: Boolean = False);
200 procedure OnDamage(Angle: SmallInt); virtual;
201 function firediry(): Integer;
203 procedure Run(Direction: TDirection);
204 procedure NextWeapon();
205 procedure PrevWeapon();
206 procedure SeeUp();
207 procedure SeeDown();
208 procedure Fire();
209 procedure Jump();
210 procedure Use();
212 procedure cycleWeapon (dir: Integer);
213 function getNextWeaponIndex (): Byte; // return 255 for "no switch"
214 procedure resetWeaponQueue ();
216 public
217 FDamageBuffer: Integer;
219 FAmmo: Array [A_BULLETS..A_CELLS] of Word;
220 FMaxAmmo: Array [A_BULLETS..A_CELLS] of Word;
221 FWeapon: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Boolean;
222 FRulez: Set of R_ITEM_BACKPACK..R_BERSERK;
223 FBerserk: Integer;
224 FMegaRulez: Array [MR_SUIT..MR_MAX] of DWORD;
225 FReloading: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Word;
226 FTime: Array [T_RESPAWN..T_FLAGCAP] of DWORD;
227 FKeys: Array [KEY_LEFT..KEY_CHAT] of TKeyState;
228 FColor: TRGB;
229 FPreferredTeam: Byte;
230 FSpectator: Boolean;
231 FNoRespawn: Boolean;
232 FWantsInGame: Boolean;
233 FGhost: Boolean;
234 FPhysics: Boolean;
235 FJetpack: Boolean;
236 FActualModelName: string;
237 FClientID: SmallInt;
238 FPing: Word;
239 FLoss: Byte;
240 FDummy: Boolean;
242 constructor Create(); virtual;
243 destructor Destroy(); override;
244 procedure Respawn(Silent: Boolean; Force: Boolean = False); virtual;
245 function GetRespawnPoint(): Byte;
246 procedure PressKey(Key: Byte; Time: Word = 1);
247 procedure ReleaseKeys();
248 procedure SetModel(ModelName: String);
249 procedure SetColor(Color: TRGB);
250 procedure SetWeapon(W: Byte);
251 function IsKeyPressed(K: Byte): Boolean;
252 function GetKeys(): Byte;
253 function PickItem(ItemType: Byte; respawn: Boolean; var remove: Boolean): Boolean; virtual;
254 function Collide(X, Y: Integer; Width, Height: Word): Boolean; overload;
255 function Collide(Panel: TPanel): Boolean; overload;
256 function Collide(X, Y: Integer): Boolean; overload;
257 procedure SetDirection(Direction: TDirection);
258 procedure GetSecret();
259 function TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean;
260 procedure Touch();
261 procedure Push(vx, vy: Integer);
262 procedure ChangeModel(ModelName: String);
263 procedure SwitchTeam;
264 procedure ChangeTeam(Team: Byte);
265 procedure BFGHit();
266 function GetFlag(Flag: Byte): Boolean;
267 procedure SetFlag(Flag: Byte);
268 function DropFlag(): Boolean;
269 procedure AllRulez(Health: Boolean);
270 procedure RestoreHealthArmor();
271 procedure FragCombo();
272 procedure GiveItem(ItemType: Byte);
273 procedure Damage(value: Word; SpawnerUID: Word; vx, vy: Integer; t: Byte); virtual;
274 function Heal(value: Word; Soft: Boolean): Boolean; virtual;
275 procedure MakeBloodVector(Count: Word; VelX, VelY: Integer);
276 procedure MakeBloodSimple(Count: Word);
277 procedure Kill(KillType: Byte; SpawnerUID: Word; t: Byte);
278 procedure Reset(Force: Boolean);
279 procedure Spectate(NoMove: Boolean = False);
280 procedure SwitchNoClip;
281 procedure SoftReset();
282 procedure Draw(); virtual;
283 procedure DrawPain();
284 procedure DrawPickup();
285 procedure DrawRulez();
286 procedure DrawAim();
287 procedure DrawBubble();
288 procedure DrawGUI();
289 procedure Update(); virtual;
290 procedure RememberState();
291 procedure RecallState();
292 procedure SaveState(var Mem: TBinMemoryWriter); virtual;
293 procedure LoadState(var Mem: TBinMemoryReader); virtual;
294 procedure PauseSounds(Enable: Boolean);
295 procedure NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1);
296 procedure DoLerp(Level: Integer = 2);
297 procedure SetLerp(XTo, YTo: Integer);
298 procedure QueueWeaponSwitch(Weapon: Byte);
299 procedure RealizeCurrentWeapon();
300 procedure JetpackOn;
301 procedure JetpackOff;
303 property Name: String read FName write FName;
304 property Model: TPlayerModel read FModel;
305 property Health: Integer read FHealth write FHealth;
306 property Lives: Byte read FLives write FLives;
307 property Armor: Integer read FArmor write FArmor;
308 property Air: Integer read FAir write FAir;
309 property JetFuel: Integer read FJetFuel write FJetFuel;
310 property Frags: Integer read FFrags write FFrags;
311 property Death: Integer read FDeath write FDeath;
312 property Kills: Integer read FKills write FKills;
313 property CurrWeap: Byte read FCurrWeap write FCurrWeap;
314 property MonsterKills: Integer read FMonsterKills write FMonsterKills;
315 property Secrets: Integer read FSecrets;
316 property GodMode: Boolean read FGodMode write FGodMode;
317 property NoTarget: Boolean read FNoTarget write FNoTarget;
318 property NoReload: Boolean read FNoReload write FNoReload;
319 property Live: Boolean read FLive write FLive;
320 property Flag: Byte read FFlag;
321 property Team: Byte read FTeam write FTeam;
322 property Direction: TDirection read FDirection;
323 property GameX: Integer read FObj.X write FObj.X;
324 property GameY: Integer read FObj.Y write FObj.Y;
325 property GameVelX: Integer read FObj.Vel.X write FObj.Vel.X;
326 property GameVelY: Integer read FObj.Vel.Y write FObj.Vel.Y;
327 property GameAccelX: Integer read FObj.Accel.X write FObj.Accel.X;
328 property GameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y;
329 property Vel: TPoint2i read FObj.Vel;
330 property Obj: TObj read FObj;
331 property IncCam: Integer read FIncCam write FIncCam;
332 property UID: Word read FUID write FUID;
333 property JustTeleported: Boolean read FJustTeleported write FJustTeleported;
334 property NetTime: LongWord read FNetTime write FNetTime;
335 end;
337 TDifficult = record
338 DiagFire: Byte;
339 InvisFire: Byte;
340 DiagPrecision: Byte;
341 FlyPrecision: Byte;
342 Cover: Byte;
343 CloseJump: Byte;
344 WeaponPrior: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte;
345 CloseWeaponPrior: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte;
346 //SafeWeaponPrior: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte;
347 end;
349 TAIFlag = record
350 Name: String;
351 Value: String;
352 end;
354 TBot = class (TPlayer)
355 private
356 FSelectedWeapon: Byte;
357 FTargetUID: Word;
358 FLastVisible: DWORD;
359 FAIFlags: Array of TAIFlag;
360 FDifficult: TDifficult;
362 function GetRnd(a: Byte): Boolean;
363 function GetInterval(a: Byte; radius: SmallInt): SmallInt;
364 function RunDirection(): TDirection;
365 function FullInStep(XInc, YInc: Integer): Boolean;
366 //function NeedItem(Item: Byte): Byte;
367 procedure SelectWeapon(Dist: Integer);
368 procedure SetAIFlag(fName, fValue: String20);
369 function GetAIFlag(fName: String20): String20;
370 procedure RemoveAIFlag(fName: String20);
371 function Healthy(): Byte;
372 procedure UpdateMove();
373 procedure UpdateCombat();
374 function KeyPressed(Key: Word): Boolean;
375 procedure ReleaseKey(Key: Byte);
376 function TargetOnScreen(TX, TY: Integer): Boolean;
377 procedure OnDamage(Angle: SmallInt); override;
379 public
380 procedure Respawn(Silent: Boolean; Force: Boolean = False); override;
381 constructor Create(); override;
382 destructor Destroy(); override;
383 procedure Draw(); override;
384 function PickItem(ItemType: Byte; force: Boolean; var remove: Boolean): Boolean; override;
385 function Heal(value: Word; Soft: Boolean): Boolean; override;
386 procedure Update(); override;
387 procedure SaveState(var Mem: TBinMemoryWriter); override;
388 procedure LoadState(var Mem: TBinMemoryReader); override;
389 end;
391 TGib = record
392 Live: Boolean;
393 ID: DWORD;
394 MaskID: DWORD;
395 RAngle: Integer;
396 Color: TRGB;
397 Obj: TObj;
398 end;
400 TShell = record
401 SpriteID: DWORD;
402 Live: Boolean;
403 SType: Byte;
404 RAngle: Integer;
405 Timeout: Cardinal;
406 CX, CY: Integer;
407 Obj: TObj;
408 end;
410 TCorpse = class (TObject)
411 private
412 FModelName: String;
413 FMess: Boolean;
414 FState: Byte;
415 FDamage: Byte;
416 FColor: TRGB;
417 FObj: TObj;
418 FAnimation: TAnimation;
419 FAnimationMask: TAnimation;
421 public
422 constructor Create(X, Y: Integer; ModelName: String; aMess: Boolean);
423 destructor Destroy(); override;
424 procedure Damage(Value: Word; vx, vy: Integer);
425 procedure Update();
426 procedure Draw();
427 procedure SaveState(var Mem: TBinMemoryWriter);
428 procedure LoadState(var Mem: TBinMemoryReader);
430 property Obj: TObj read FObj;
431 property State: Byte read FState;
432 property Mess: Boolean read FMess;
433 end;
435 TTeamStat = Array [TEAM_RED..TEAM_BLUE] of
436 record
437 Goals: SmallInt;
438 end;
440 var
441 gPlayers: Array of TPlayer;
442 gCorpses: Array of TCorpse;
443 gGibs: Array of TGib;
444 gShells: Array of TShell;
445 gTeamStat: TTeamStat;
446 gFly: Boolean = False;
447 gAimLine: Boolean = False;
448 gChatBubble: Byte = 0;
449 gNumBots: Word = 0;
450 gLMSPID1: Word = 0;
451 gLMSPID2: Word = 0;
452 MAX_RUNVEL: Integer = 8;
453 VEL_JUMP: Integer = 10;
454 SHELL_TIMEOUT: Cardinal = 60000;
456 function Lerp(X, Y, Factor: Integer): Integer;
458 procedure g_Gibs_SetMax(Count: Word);
459 function g_Gibs_GetMax(): Word;
460 procedure g_Corpses_SetMax(Count: Word);
461 function g_Corpses_GetMax(): Word;
462 procedure g_Shells_SetMax(Count: Word);
463 function g_Shells_GetMax(): Word;
465 procedure g_Player_Init();
466 procedure g_Player_Free();
467 function g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boolean): Word;
468 function g_Player_CreateFromState(var Mem: TBinMemoryReader): Word;
469 procedure g_Player_Remove(UID: Word);
470 procedure g_Player_ResetTeams();
471 procedure g_Player_UpdateAll();
472 procedure g_Player_DrawAll();
473 procedure g_Player_DrawDebug(p: TPlayer);
474 procedure g_Player_DrawHealth();
475 procedure g_Player_RememberAll();
476 procedure g_Player_ResetAll(Force, Silent: Boolean);
477 function g_Player_Get(UID: Word): TPlayer;
478 function g_Player_GetCount(): Byte;
479 function g_Player_GetStats(): TPlayerStatArray;
480 function g_Player_ValidName(Name: String): Boolean;
481 procedure g_Player_CreateCorpse(Player: TPlayer);
482 procedure g_Player_CreateGibs(fX, fY: Integer; ModelName: String; fColor: TRGB);
483 procedure g_Player_CreateShell(fX, fY, dX, dY: Integer; T: Byte);
484 procedure g_Player_UpdatePhysicalObjects();
485 procedure g_Player_DrawCorpses();
486 procedure g_Player_DrawShells();
487 procedure g_Player_RemoveAllCorpses();
488 procedure g_Player_Corpses_SaveState(var Mem: TBinMemoryWriter);
489 procedure g_Player_Corpses_LoadState(var Mem: TBinMemoryReader);
490 procedure g_Bot_Add(Team, Difficult: Byte);
491 procedure g_Bot_AddList(Team: Byte; lname: ShortString; num: Integer = -1);
492 procedure g_Bot_MixNames();
493 procedure g_Bot_RemoveAll();
495 implementation
497 uses
498 e_log, g_map, g_items, g_console, SysUtils, g_gfx, Math,
499 g_options, g_triggers, g_menu, MAPDEF, g_game,
500 wadreader, g_main, g_monsters, CONFIG, g_language, g_net, g_netmsg;
502 type
503 TBotProfile = record
504 name: ShortString;
505 model: ShortString;
506 team: Byte;
507 color: TRGB;
508 diag_fire: Byte;
509 invis_fire: Byte;
510 diag_precision: Byte;
511 fly_precision: Byte;
512 cover: Byte;
513 close_jump: Byte;
514 w_prior1: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte;
515 w_prior2: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte;
516 w_prior3: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte;
517 end;
519 const
520 TIME_RESPAWN1 = 1500;
521 TIME_RESPAWN2 = 2000;
522 TIME_RESPAWN3 = 3000;
523 AIR_DEF = 360;
524 AIR_MAX = 1091;
525 JET_MAX = 540; // ~30 sec
526 PLAYER_SUIT_TIME = 30000;
527 PLAYER_INVUL_TIME = 30000;
528 PLAYER_INVIS_TIME = 35000;
529 FRAG_COMBO_TIME = 3000;
530 VEL_SW = 4;
531 VEL_FLY = 6;
532 ANGLE_RIGHTUP = 55;
533 ANGLE_RIGHTDOWN = -35;
534 ANGLE_LEFTUP = 125;
535 ANGLE_LEFTDOWN = -145;
536 PLAYER_HEADRECT: TRectWH = (X:24; Y:12; Width:20; Height:12);
537 WEAPONPOINT: Array [TDirection] of TPoint = ((X:16; Y:32), (X:47; Y:32));
538 BOT_MAXJUMP = 84;
539 BOT_LONGDIST = 300;
540 BOT_UNSAFEDIST = 128;
541 TEAMCOLOR: Array [TEAM_RED..TEAM_BLUE] of TRGB = ((R:255; G:0; B:0),
542 (R:0; G:0; B:255));
543 DIFFICULT_EASY: TDifficult = (DiagFire: 32; InvisFire: 32; DiagPrecision: 32;
544 FlyPrecision: 32; Cover: 32; CloseJump: 32;
545 WeaponPrior:(0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0));
546 DIFFICULT_MEDIUM: TDifficult = (DiagFire: 127; InvisFire: 127; DiagPrecision: 127;
547 FlyPrecision: 127; Cover: 127; CloseJump: 127;
548 WeaponPrior:(0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0));
549 DIFFICULT_HARD: TDifficult = (DiagFire: 255; InvisFire: 255; DiagPrecision: 255;
550 FlyPrecision: 255; Cover: 255; CloseJump: 255;
551 WeaponPrior:(0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0));
552 WEAPON_PRIOR1: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte =
553 (WEAPON_SUPERPULEMET, WEAPON_SHOTGUN2, WEAPON_SHOTGUN1,
554 WEAPON_CHAINGUN, WEAPON_PLASMA, WEAPON_ROCKETLAUNCHER,
555 WEAPON_BFG, WEAPON_PISTOL, WEAPON_SAW, WEAPON_KASTET);
556 WEAPON_PRIOR2: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte =
557 (WEAPON_SUPERPULEMET, WEAPON_BFG, WEAPON_ROCKETLAUNCHER,
558 WEAPON_SHOTGUN2, WEAPON_PLASMA, WEAPON_SHOTGUN1,
559 WEAPON_CHAINGUN, WEAPON_PISTOL, WEAPON_SAW, WEAPON_KASTET);
560 //WEAPON_PRIOR3: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte =
561 // (WEAPON_SUPERPULEMET, WEAPON_BFG, WEAPON_PLASMA,
562 // WEAPON_SHOTGUN2, WEAPON_CHAINGUN, WEAPON_SHOTGUN1,
563 // WEAPON_SAW, WEAPON_ROCKETLAUNCHER, WEAPON_PISTOL, WEAPON_KASTET);
564 WEAPON_RELOAD: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte =
565 (5, 2, 6, 18, 36, 2, 12, 2, 14, 2);
567 PLAYER_SIGNATURE = $52594C50; // 'PLYR'
568 CORPSE_SIGNATURE = $50524F43; // 'CORP'
570 BOTNAMES_FILENAME = 'botnames.txt';
571 BOTLIST_FILENAME = 'botlist.txt';
573 var
574 MaxGibs: Word = 150;
575 MaxCorpses: Word = 20;
576 MaxShells: Word = 300;
577 CurrentGib: Integer = 0;
578 CurrentShell: Integer = 0;
579 BotNames: Array of String;
580 BotList: Array of TBotProfile;
582 function Lerp(X, Y, Factor: Integer): Integer;
583 begin
584 Result := X + ((Y - X) div Factor);
585 end;
587 function SameTeam(UID1, UID2: Word): Boolean;
588 begin
589 Result := False;
591 if (UID1 > UID_MAX_PLAYER) or (UID1 <= UID_MAX_GAME) or
592 (UID2 > UID_MAX_PLAYER) or (UID2 <= UID_MAX_GAME) then Exit;
594 if (g_Player_Get(UID1) = nil) or (g_Player_Get(UID2) = nil) then Exit;
596 if ((g_Player_Get(UID1).Team = TEAM_NONE) or
597 (g_Player_Get(UID2).Team = TEAM_NONE)) then Exit;
599 Result := g_Player_Get(UID1).FTeam = g_Player_Get(UID2).FTeam;
600 end;
602 procedure g_Gibs_SetMax(Count: Word);
603 begin
604 MaxGibs := Count;
605 SetLength(gGibs, Count);
607 if CurrentGib >= Count then
608 CurrentGib := 0;
609 end;
611 function g_Gibs_GetMax(): Word;
612 begin
613 Result := MaxGibs;
614 end;
616 procedure g_Shells_SetMax(Count: Word);
617 begin
618 MaxShells := Count;
619 SetLength(gShells, Count);
621 if CurrentShell >= Count then
622 CurrentShell := 0;
623 end;
625 function g_Shells_GetMax(): Word;
626 begin
627 Result := MaxShells;
628 end;
631 procedure g_Corpses_SetMax(Count: Word);
632 begin
633 MaxCorpses := Count;
634 SetLength(gCorpses, Count);
635 end;
637 function g_Corpses_GetMax(): Word;
638 begin
639 Result := MaxCorpses;
640 end;
642 function g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boolean): Word;
643 var
644 a: Integer;
645 ok: Boolean;
646 begin
647 Result := 0;
649 ok := False;
650 a := 0;
652 // Åñòü ëè ìåñòî â gPlayers:
653 if gPlayers <> nil then
654 for a := 0 to High(gPlayers) do
655 if gPlayers[a] = nil then
656 begin
657 ok := True;
658 Break;
659 end;
661 // Íåò ìåñòà - ðàñøèðÿåì gPlayers:
662 if not ok then
663 begin
664 SetLength(gPlayers, Length(gPlayers)+1);
665 a := High(gPlayers);
666 end;
668 // Ñîçäàåì îáúåêò èãðîêà:
669 if Bot then
670 gPlayers[a] := TBot.Create()
671 else
672 gPlayers[a] := TPlayer.Create();
675 gPlayers[a].FActualModelName := ModelName;
676 gPlayers[a].SetModel(ModelName);
678 // Íåò ìîäåëè - ñîçäàíèå íå âîçìîæíî:
679 if gPlayers[a].FModel = nil then
680 begin
681 gPlayers[a].Free();
682 gPlayers[a] := nil;
683 g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], [ModelName]));
684 Exit;
685 end;
687 if not (Team in [TEAM_RED, TEAM_BLUE]) then
688 if Random(2) = 0 then
689 Team := TEAM_RED
690 else
691 Team := TEAM_BLUE;
692 gPlayers[a].FPreferredTeam := Team;
694 case gGameSettings.GameMode of
695 GM_DM: gPlayers[a].FTeam := TEAM_NONE;
696 GM_TDM,
697 GM_CTF: gPlayers[a].FTeam := gPlayers[a].FPreferredTeam;
698 GM_SINGLE,
699 GM_COOP: gPlayers[a].FTeam := TEAM_COOP;
700 end;
702 // Åñëè êîìàíäíàÿ èãðà - êðàñèì ìîäåëü â öâåò êîìàíäû:
703 gPlayers[a].FColor := Color;
704 if gPlayers[a].FTeam in [TEAM_RED, TEAM_BLUE] then
705 gPlayers[a].FModel.Color := TEAMCOLOR[gPlayers[a].FTeam]
706 else
707 gPlayers[a].FModel.Color := Color;
709 gPlayers[a].FUID := g_CreateUID(UID_PLAYER);
710 gPlayers[a].FLive := False;
712 Result := gPlayers[a].FUID;
713 end;
715 function g_Player_CreateFromState(var Mem: TBinMemoryReader): Word;
716 var
717 a, i: Integer;
718 ok, Bot: Boolean;
719 sig: DWORD;
720 b: Byte;
721 begin
722 Result := 0;
723 if Mem = nil then
724 Exit;
726 // Ñèãíàòóðà èãðîêà:
727 Mem.ReadDWORD(sig);
728 if sig <> PLAYER_SIGNATURE then // 'PLYR'
729 begin
730 raise EBinSizeError.Create('g_Player_CreateFromState: Wrong Player Signature');
731 end;
733 // Áîò èëè ÷åëîâåê:
734 Mem.ReadBoolean(Bot);
736 ok := False;
737 a := 0;
739 // Åñòü ëè ìåñòî â gPlayers:
740 if gPlayers <> nil then
741 for a := 0 to High(gPlayers) do
742 if gPlayers[a] = nil then
743 begin
744 ok := True;
745 Break;
746 end;
748 // Íåò ìåñòà - ðàñøèðÿåì gPlayers:
749 if not ok then
750 begin
751 SetLength(gPlayers, Length(gPlayers)+1);
752 a := High(gPlayers);
753 end;
755 // Ñîçäàåì îáúåêò èãðîêà:
756 if Bot then
757 gPlayers[a] := TBot.Create()
758 else
759 gPlayers[a] := TPlayer.Create();
760 gPlayers[a].FIamBot := Bot;
761 gPlayers[a].FPhysics := True;
763 // UID èãðîêà:
764 Mem.ReadWord(gPlayers[a].FUID);
765 // Èìÿ èãðîêà:
766 Mem.ReadString(gPlayers[a].FName);
767 // Êîìàíäà:
768 Mem.ReadByte(gPlayers[a].FTeam);
769 gPlayers[a].FPreferredTeam := gPlayers[a].FTeam;
770 // Æèâ ëè:
771 Mem.ReadBoolean(gPlayers[a].FLive);
772 // Èçðàñõîäîâàë ëè âñå æèçíè:
773 Mem.ReadBoolean(gPlayers[a].FNoRespawn);
774 // Íàïðàâëåíèå:
775 Mem.ReadByte(b);
776 if b = 1 then
777 gPlayers[a].FDirection := D_LEFT
778 else // b = 2
779 gPlayers[a].FDirection := D_RIGHT;
780 // Çäîðîâüå:
781 Mem.ReadInt(gPlayers[a].FHealth);
782 // Æèçíè:
783 Mem.ReadByte(gPlayers[a].FLives);
784 // Áðîíÿ:
785 Mem.ReadInt(gPlayers[a].FArmor);
786 // Çàïàñ âîçäóõà:
787 Mem.ReadInt(gPlayers[a].FAir);
788 // Çàïàñ ãîðþ÷åãî:
789 Mem.ReadInt(gPlayers[a].FJetFuel);
790 // Áîëü:
791 Mem.ReadInt(gPlayers[a].FPain);
792 // Óáèë:
793 Mem.ReadInt(gPlayers[a].FKills);
794 // Óáèë ìîíñòðîâ:
795 Mem.ReadInt(gPlayers[a].FMonsterKills);
796 // Ôðàãîâ:
797 Mem.ReadInt(gPlayers[a].FFrags);
798 // Ôðàãîâ ïîäðÿä:
799 Mem.ReadByte(gPlayers[a].FFragCombo);
800 // Âðåìÿ ïîñëåäíåãî ôðàãà:
801 Mem.ReadDWORD(gPlayers[a].FLastFrag);
802 // Ñìåðòåé:
803 Mem.ReadInt(gPlayers[a].FDeath);
804 // Êàêîé ôëàã íåñåò:
805 Mem.ReadByte(gPlayers[a].FFlag);
806 // Íàøåë ñåêðåòîâ:
807 Mem.ReadInt(gPlayers[a].FSecrets);
808 // Òåêóùåå îðóæèå:
809 Mem.ReadByte(gPlayers[a].FCurrWeap);
810 // Ñëåäóþùåå æåëàåìîå îðóæèå:
811 Mem.ReadWord(gPlayers[a].FNextWeap);
812 // ...è ïàóçà:
813 Mem.ReadByte(gPlayers[a].FNextWeapDelay);
814 // Âðåìÿ çàðÿäêè BFG:
815 Mem.ReadSmallInt(gPlayers[a].FBFGFireCounter);
816 // Áóôåð óðîíà:
817 Mem.ReadInt(gPlayers[a].FDamageBuffer);
818 // Ïîñëåäíèé óäàðèâøèé:
819 Mem.ReadWord(gPlayers[a].FLastSpawnerUID);
820 // Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà:
821 Mem.ReadByte(gPlayers[a].FLastHit);
822 // Îáúåêò èãðîêà:
823 Obj_LoadState(@gPlayers[a].FObj, Mem);
824 // Òåêóùåå êîëè÷åñòâî ïàòðîíîâ:
825 for i := A_BULLETS to A_CELLS do
826 Mem.ReadWord(gPlayers[a].FAmmo[i]);
827 // Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ:
828 for i := A_BULLETS to A_CELLS do
829 Mem.ReadWord(gPlayers[a].FMaxAmmo[i]);
830 // Íàëè÷èå îðóæèÿ:
831 for i := WEAPON_KASTET to WEAPON_SUPERPULEMET do
832 Mem.ReadBoolean(gPlayers[a].FWeapon[i]);
833 // Âðåìÿ ïåðåçàðÿäêè îðóæèÿ:
834 for i := WEAPON_KASTET to WEAPON_SUPERPULEMET do
835 Mem.ReadWord(gPlayers[a].FReloading[i]);
836 // Íàëè÷èå ðþêçàêà:
837 Mem.ReadByte(b);
838 if b = 1 then
839 Include(gPlayers[a].FRulez, R_ITEM_BACKPACK);
840 // Íàëè÷èå êðàñíîãî êëþ÷à:
841 Mem.ReadByte(b);
842 if b = 1 then
843 Include(gPlayers[a].FRulez, R_KEY_RED);
844 // Íàëè÷èå çåëåíîãî êëþ÷à:
845 Mem.ReadByte(b);
846 if b = 1 then
847 Include(gPlayers[a].FRulez, R_KEY_GREEN);
848 // Íàëè÷èå ñèíåãî êëþ÷à:
849 Mem.ReadByte(b);
850 if b = 1 then
851 Include(gPlayers[a].FRulez, R_KEY_BLUE);
852 // Íàëè÷èå áåðñåðêà:
853 Mem.ReadByte(b);
854 if b = 1 then
855 Include(gPlayers[a].FRulez, R_BERSERK);
856 // Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ:
857 for i := MR_SUIT to MR_MAX do
858 Mem.ReadDWORD(gPlayers[a].FMegaRulez[i]);
859 // Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà:
860 for i := T_RESPAWN to T_FLAGCAP do
861 Mem.ReadDWORD(gPlayers[a].FTime[i]);
863 // Íàçâàíèå ìîäåëè:
864 Mem.ReadString(gPlayers[a].FActualModelName);
865 // Öâåò ìîäåëè:
866 Mem.ReadByte(gPlayers[a].FColor.R);
867 Mem.ReadByte(gPlayers[a].FColor.G);
868 Mem.ReadByte(gPlayers[a].FColor.B);
869 // Îáíîâëÿåì ìîäåëü èãðîêà:
870 gPlayers[a].SetModel(gPlayers[a].FActualModelName);
872 // Íåò ìîäåëè - ñîçäàíèå íå âîçìîæíî:
873 if gPlayers[a].FModel = nil then
874 begin
875 gPlayers[a].Free();
876 gPlayers[a] := nil;
877 g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], [gPlayers[a].FActualModelName]));
878 Exit;
879 end;
881 // Åñëè êîìàíäíàÿ èãðà - êðàñèì ìîäåëü â öâåò êîìàíäû:
882 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
883 gPlayers[a].FModel.Color := TEAMCOLOR[gPlayers[a].FTeam]
884 else
885 gPlayers[a].FModel.Color := gPlayers[a].FColor;
887 Result := gPlayers[a].FUID;
888 end;
890 procedure g_Player_ResetTeams();
891 var
892 a: Integer;
893 begin
894 if g_Game_IsClient then
895 Exit;
896 if gPlayers = nil then
897 Exit;
898 for a := Low(gPlayers) to High(gPlayers) do
899 if gPlayers[a] <> nil then
900 case gGameSettings.GameMode of
901 GM_DM:
902 gPlayers[a].ChangeTeam(TEAM_NONE);
903 GM_TDM, GM_CTF:
904 if not (gPlayers[a].Team in [TEAM_RED, TEAM_BLUE]) then
905 if gPlayers[a].FPreferredTeam in [TEAM_RED, TEAM_BLUE] then
906 gPlayers[a].ChangeTeam(gPlayers[a].FPreferredTeam)
907 else
908 if a mod 2 = 0 then
909 gPlayers[a].ChangeTeam(TEAM_RED)
910 else
911 gPlayers[a].ChangeTeam(TEAM_BLUE);
912 GM_SINGLE,
913 GM_COOP:
914 gPlayers[a].ChangeTeam(TEAM_COOP);
915 end;
916 end;
918 procedure g_Bot_Add(Team, Difficult: Byte);
919 var
920 m: SArray;
921 _name, _model: String;
922 a, tr, tb: Integer;
923 begin
924 if not g_Game_IsServer then Exit;
926 // Ñïèñîê íàçâàíèé ìîäåëåé:
927 m := g_PlayerModel_GetNames();
928 if m = nil then
929 Exit;
931 // Êîìàíäà:
932 if (gGameSettings.GameType = GT_SINGLE) or (gGameSettings.GameMode = GM_COOP) then
933 Team := TEAM_COOP // COOP
934 else
935 if gGameSettings.GameMode = GM_DM then
936 Team := TEAM_NONE // DM
937 else
938 if Team = TEAM_NONE then // CTF / TDM
939 begin
940 // Àâòîáàëàíñ êîìàíä:
941 tr := 0;
942 tb := 0;
944 for a := 0 to High(gPlayers) do
945 if gPlayers[a] <> nil then
946 begin
947 if gPlayers[a].Team = TEAM_RED then
948 Inc(tr)
949 else
950 if gPlayers[a].Team = TEAM_BLUE then
951 Inc(tb);
952 end;
954 if tr > tb then
955 Team := TEAM_BLUE
956 else
957 if tb > tr then
958 Team := TEAM_RED
959 else // tr = tb
960 if Random(2) = 0 then
961 Team := TEAM_RED
962 else
963 Team := TEAM_BLUE;
964 end;
966 // Âûáèðàåì áîòó èìÿ:
967 _name := '';
968 if BotNames <> nil then
969 for a := 0 to High(BotNames) do
970 if g_Player_ValidName(BotNames[a]) then
971 begin
972 _name := BotNames[a];
973 Break;
974 end;
976 // Èìåíè íåò, çàäàåì ñëó÷àéíîå:
977 if _name = '' then
978 repeat
979 _name := Format('DFBOT%.2d', [Random(100)]);
980 until g_Player_ValidName(_name);
982 // Âûáèðàåì ñëó÷àéíóþ ìîäåëü:
983 _model := m[Random(Length(m))];
985 // Ñîçäàåì áîòà:
986 with g_Player_Get(g_Player_Create(_model,
987 _RGB(Min(Random(9)*32, 255),
988 Min(Random(9)*32, 255),
989 Min(Random(9)*32, 255)),
990 Team, True)) as TBot do
991 begin
992 Name := _name;
994 case Difficult of
995 1: FDifficult := DIFFICULT_EASY;
996 2: FDifficult := DIFFICULT_MEDIUM;
997 else FDifficult := DIFFICULT_HARD;
998 end;
1000 for a := WEAPON_KASTET to WEAPON_SUPERPULEMET do
1001 begin
1002 FDifficult.WeaponPrior[a] := WEAPON_PRIOR1[a];
1003 FDifficult.CloseWeaponPrior[a] := WEAPON_PRIOR2[a];
1004 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
1005 end;
1007 g_Console_Add(Format(_lc[I_PLAYER_JOIN], [Name]), True);
1009 if g_Game_IsNet then MH_SEND_PlayerCreate(UID);
1010 if g_Game_IsServer and (gGameSettings.MaxLives > 0) then
1011 Spectate();
1012 end;
1013 end;
1015 procedure g_Bot_AddList(Team: Byte; lName: ShortString; num: Integer = -1);
1016 var
1017 m: SArray;
1018 _name, _model: String;
1019 a: Integer;
1020 begin
1021 if not g_Game_IsServer then Exit;
1023 // Ñïèñîê íàçâàíèé ìîäåëåé:
1024 m := g_PlayerModel_GetNames();
1025 if m = nil then
1026 Exit;
1028 // Êîìàíäà:
1029 if (gGameSettings.GameType = GT_SINGLE) or (gGameSettings.GameMode = GM_COOP) then
1030 Team := TEAM_COOP // COOP
1031 else
1032 if gGameSettings.GameMode = GM_DM then
1033 Team := TEAM_NONE // DM
1034 else
1035 if Team = TEAM_NONE then
1036 Team := BotList[num].team; // CTF / TDM
1038 // Âûáèðàåì íàñòðîéêè áîòà èç ñïèñêà ïî íîìåðó èëè èìåíè:
1039 lName := AnsiLowerCase(lName);
1040 if (num < 0) or (num > Length(BotList)-1) then
1041 num := -1;
1042 if (num = -1) and (lName <> '') and (BotList <> nil) then
1043 for a := 0 to High(BotList) do
1044 if AnsiLowerCase(BotList[a].name) = lName then
1045 begin
1046 num := a;
1047 Break;
1048 end;
1049 if num = -1 then
1050 Exit;
1052 // Èìÿ áîòà:
1053 _name := BotList[num].name;
1054 // Çàíÿòî - âûáèðàåì ñëó÷àéíîå:
1055 if not g_Player_ValidName(_name) then
1056 repeat
1057 _name := Format('DFBOT%.2d', [Random(100)]);
1058 until g_Player_ValidName(_name);
1060 // Ìîäåëü:
1061 _model := BotList[num].model;
1062 // Íåò òàêîé - âûáèðàåì ñëó÷àéíóþ:
1063 if not InSArray(_model, m) then
1064 _model := m[Random(Length(m))];
1066 // Ñîçäàåì áîòà:
1067 with g_Player_Get(g_Player_Create(_model, BotList[num].color, Team, True)) as TBot do
1068 begin
1069 Name := _name;
1071 FDifficult.DiagFire := BotList[num].diag_fire;
1072 FDifficult.InvisFire := BotList[num].invis_fire;
1073 FDifficult.DiagPrecision := BotList[num].diag_precision;
1074 FDifficult.FlyPrecision := BotList[num].fly_precision;
1075 FDifficult.Cover := BotList[num].cover;
1076 FDifficult.CloseJump := BotList[num].close_jump;
1078 for a := WEAPON_KASTET to WEAPON_SUPERPULEMET do
1079 begin
1080 FDifficult.WeaponPrior[a] := BotList[num].w_prior1[a];
1081 FDifficult.CloseWeaponPrior[a] := BotList[num].w_prior2[a];
1082 //FDifficult.SafeWeaponPrior[a] := BotList[num].w_prior3[a];
1083 end;
1085 g_Console_Add(Format(_lc[I_PLAYER_JOIN], [Name]), True);
1087 if g_Game_IsNet then MH_SEND_PlayerCreate(UID);
1088 end;
1089 end;
1091 procedure g_Bot_RemoveAll();
1092 var
1093 a: Integer;
1094 begin
1095 if not g_Game_IsServer then Exit;
1096 if gPlayers = nil then Exit;
1098 for a := 0 to High(gPlayers) do
1099 if gPlayers[a] <> nil then
1100 if gPlayers[a] is TBot then
1101 begin
1102 gPlayers[a].Lives := 0;
1103 gPlayers[a].Kill(K_SIMPLEKILL, 0, HIT_DISCON);
1104 g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [gPlayers[a].Name]), True);
1105 g_Player_Remove(gPlayers[a].FUID);
1106 end;
1108 g_Bot_MixNames();
1109 end;
1111 procedure g_Bot_MixNames();
1112 var
1113 s: String;
1114 a, b: Integer;
1115 begin
1116 if BotNames <> nil then
1117 for a := 0 to High(BotNames) do
1118 begin
1119 b := Random(Length(BotNames));
1120 s := BotNames[a];
1121 Botnames[a] := BotNames[b];
1122 BotNames[b] := s;
1123 end;
1124 end;
1126 procedure g_Player_Remove(UID: Word);
1127 var
1128 i: Integer;
1129 begin
1130 if gPlayers = nil then Exit;
1132 if g_Game_IsServer and g_Game_IsNet then
1133 MH_SEND_PlayerDelete(UID);
1135 for i := 0 to High(gPlayers) do
1136 if gPlayers[i] <> nil then
1137 if gPlayers[i].FUID = UID then
1138 begin
1139 if gPlayers[i] is TPlayer then
1140 TPlayer(gPlayers[i]).Free()
1141 else
1142 TBot(gPlayers[i]).Free();
1143 gPlayers[i] := nil;
1144 Exit;
1145 end;
1146 end;
1148 procedure g_Player_Init();
1149 var
1150 F: TextFile;
1151 s: String;
1152 a, b: Integer;
1153 config: TConfig;
1154 sa: SArray;
1155 begin
1156 BotNames := nil;
1158 if not FileExists(DataDir + BOTNAMES_FILENAME) then
1159 Exit;
1161 // ×èòàåì âîçìîæíûå èìåíà áîòîâ èç ôàéëà:
1162 AssignFile(F, DataDir + BOTNAMES_FILENAME);
1163 Reset(F);
1165 while not EOF(F) do
1166 begin
1167 ReadLn(F, s);
1169 s := Trim(s);
1170 if s = '' then
1171 Continue;
1173 SetLength(BotNames, Length(BotNames)+1);
1174 BotNames[High(BotNames)] := s;
1175 end;
1177 CloseFile(F);
1179 // Ïåðåìåøèâàåì èõ:
1180 g_Bot_MixNames();
1182 // ×èòàåì ôàéë ñ ïàðàìåòðàìè áîòîâ:
1183 config := TConfig.CreateFile(DataDir + BOTLIST_FILENAME);
1184 BotList := nil;
1185 a := 0;
1187 while config.SectionExists(IntToStr(a)) do
1188 begin
1189 SetLength(BotList, Length(BotList)+1);
1191 with BotList[High(BotList)] do
1192 begin
1193 // Èìÿ áîòà:
1194 name := config.ReadStr(IntToStr(a), 'name', '');
1195 // Ìîäåëü:
1196 model := config.ReadStr(IntToStr(a), 'model', '');
1197 // Êîìàíäà:
1198 if config.ReadStr(IntToStr(a), 'team', 'red') = 'red' then
1199 team := TEAM_RED
1200 else
1201 team := TEAM_BLUE;
1202 // Öâåò ìîäåëè:
1203 sa := parse(config.ReadStr(IntToStr(a), 'color', ''));
1204 color.R := StrToIntDef(sa[0], 0);
1205 color.G := StrToIntDef(sa[1], 0);
1206 color.B := StrToIntDef(sa[2], 0);
1207 // Âåðîÿòíîñòü ñòðåëüáû ïîä óãëîì:
1208 diag_fire := config.ReadInt(IntToStr(a), 'diag_fire', 0);
1209 // Âåðîÿòíîñòü îòâåòíîãî îãíÿ ïî íåâèäèìîìó ñîïåðíèêó:
1210 invis_fire := config.ReadInt(IntToStr(a), 'invis_fire', 0);
1211 // Òî÷íîñòü ñòðåëüáû ïîä óãëîì:
1212 diag_precision := config.ReadInt(IntToStr(a), 'diag_precision', 0);
1213 // Òî÷íîñòü ñòðåëüáû â ïîëåòå:
1214 fly_precision := config.ReadInt(IntToStr(a), 'fly_precision', 0);
1215 // Òî÷íîñòü óêëîíåíèÿ îò ñíàðÿäîâ:
1216 cover := config.ReadInt(IntToStr(a), 'cover', 0);
1217 // Âåðîÿòíîñòü ïðûæêà ïðè ïðèáëèæåíèè ñîïåðíèêà:
1218 close_jump := config.ReadInt(IntToStr(a), 'close_jump', 0);
1219 // Ïðèîðèòåòû îðóæèÿ äëÿ äàëüíåãî áîÿ:
1220 sa := parse(config.ReadStr(IntToStr(a), 'w_prior1', ''));
1221 if Length(sa) = 10 then
1222 for b := 0 to 9 do
1223 w_prior1[b] := EnsureRange(StrToInt(sa[b]), 0, 9);
1224 // Ïðèîðèòåòû îðóæèÿ äëÿ áëèæíåãî áîÿ:
1225 sa := parse(config.ReadStr(IntToStr(a), 'w_prior2', ''));
1226 if Length(sa) = 10 then
1227 for b := 0 to 9 do
1228 w_prior2[b] := EnsureRange(StrToInt(sa[b]), 0, 9);
1230 {sa := parse(config.ReadStr(IntToStr(a), 'w_prior3', ''));
1231 if Length(sa) = 10 then
1232 for b := 0 to 9 do
1233 w_prior3[b] := EnsureRange(StrToInt(sa[b]), 0, 9);}
1234 end;
1236 a := a + 1;
1237 end;
1239 config.Free();
1240 end;
1242 procedure g_Player_Free();
1243 var
1244 i: Integer;
1245 begin
1246 if gPlayers <> nil then
1247 begin
1248 for i := 0 to High(gPlayers) do
1249 if gPlayers[i] <> nil then
1250 begin
1251 if gPlayers[i] is TPlayer then
1252 TPlayer(gPlayers[i]).Free()
1253 else
1254 TBot(gPlayers[i]).Free();
1255 gPlayers[i] := nil;
1256 end;
1258 gPlayers := nil;
1259 end;
1261 gPlayer1 := nil;
1262 gPlayer2 := nil;
1263 end;
1265 procedure g_Player_UpdateAll();
1266 var
1267 i: Integer;
1268 begin
1269 if gPlayers = nil then Exit;
1271 for i := 0 to High(gPlayers) do
1272 if gPlayers[i] <> nil then
1273 if gPlayers[i] is TPlayer then gPlayers[i].Update()
1274 else TBot(gPlayers[i]).Update();
1275 end;
1277 procedure g_Player_DrawAll();
1278 var
1279 i: Integer;
1280 begin
1281 if gPlayers = nil then Exit;
1283 for i := 0 to High(gPlayers) do
1284 if gPlayers[i] <> nil then
1285 if gPlayers[i] is TPlayer then gPlayers[i].Draw()
1286 else TBot(gPlayers[i]).Draw();
1287 end;
1289 procedure g_Player_DrawDebug(p: TPlayer);
1290 var
1291 fW, fH: Byte;
1292 begin
1293 if p = nil then Exit;
1294 if (@p.FObj) = nil then Exit;
1296 e_TextureFontGetSize(gStdFont, fW, fH);
1298 e_TextureFontPrint(0, 0 , 'Pos X: ' + IntToStr(p.FObj.X), gStdFont);
1299 e_TextureFontPrint(0, fH , 'Pos Y: ' + IntToStr(p.FObj.Y), gStdFont);
1300 e_TextureFontPrint(0, fH * 2, 'Vel X: ' + IntToStr(p.FObj.Vel.X), gStdFont);
1301 e_TextureFontPrint(0, fH * 3, 'Vel Y: ' + IntToStr(p.FObj.Vel.Y), gStdFont);
1302 e_TextureFontPrint(0, fH * 4, 'Acc X: ' + IntToStr(p.FObj.Accel.X), gStdFont);
1303 e_TextureFontPrint(0, fH * 5, 'Acc Y: ' + IntToStr(p.FObj.Accel.Y), gStdFont);
1304 end;
1306 procedure g_Player_DrawHealth();
1307 var
1308 i: Integer;
1309 fW, fH: Byte;
1310 begin
1311 if gPlayers = nil then Exit;
1312 e_TextureFontGetSize(gStdFont, fW, fH);
1314 for i := 0 to High(gPlayers) do
1315 if gPlayers[i] <> nil then
1316 begin
1317 e_TextureFontPrint(gPlayers[i].FObj.X + gPlayers[i].FObj.Rect.X,
1318 gPlayers[i].FObj.Y + gPlayers[i].FObj.Rect.Y + gPlayers[i].FObj.Rect.Height - fH * 2,
1319 IntToStr(gPlayers[i].FHealth), gStdFont);
1320 e_TextureFontPrint(gPlayers[i].FObj.X + gPlayers[i].FObj.Rect.X,
1321 gPlayers[i].FObj.Y + gPlayers[i].FObj.Rect.Y + gPlayers[i].FObj.Rect.Height - fH,
1322 IntToStr(gPlayers[i].FArmor), gStdFont);
1323 end;
1324 end;
1326 function g_Player_Get(UID: Word): TPlayer;
1327 var
1328 a: Integer;
1329 begin
1330 Result := nil;
1332 if gPlayers = nil then
1333 Exit;
1335 for a := 0 to High(gPlayers) do
1336 if gPlayers[a] <> nil then
1337 if gPlayers[a].FUID = UID then
1338 begin
1339 Result := gPlayers[a];
1340 Exit;
1341 end;
1342 end;
1344 function g_Player_GetCount(): Byte;
1345 var
1346 a: Integer;
1347 begin
1348 Result := 0;
1350 if gPlayers = nil then
1351 Exit;
1353 for a := 0 to High(gPlayers) do
1354 if gPlayers[a] <> nil then
1355 Result := Result + 1;
1356 end;
1358 function g_Player_GetStats(): TPlayerStatArray;
1359 var
1360 a: Integer;
1361 begin
1362 Result := nil;
1364 if gPlayers = nil then Exit;
1366 for a := 0 to High(gPlayers) do
1367 if gPlayers[a] <> nil then
1368 begin
1369 SetLength(Result, Length(Result)+1);
1370 with Result[High(Result)] do
1371 begin
1372 Ping := gPlayers[a].FPing;
1373 Loss := gPlayers[a].FLoss;
1374 Name := gPlayers[a].FName;
1375 Team := gPlayers[a].FTeam;
1376 Frags := gPlayers[a].FFrags;
1377 Deaths := gPlayers[a].FDeath;
1378 Kills := gPlayers[a].FKills;
1379 Color := gPlayers[a].FModel.Color;
1380 Lives := gPlayers[a].FLives;
1381 Spectator := gPlayers[a].FSpectator;
1382 end;
1383 end;
1384 end;
1386 procedure g_Player_RememberAll;
1387 var
1388 i: Integer;
1389 begin
1390 for i := Low(gPlayers) to High(gPlayers) do
1391 if (gPlayers[i] <> nil) and gPlayers[i].Live then
1392 gPlayers[i].RememberState;
1393 end;
1395 procedure g_Player_ResetAll(Force, Silent: Boolean);
1396 var
1397 i: Integer;
1398 begin
1399 gTeamStat[TEAM_RED].Goals := 0;
1400 gTeamStat[TEAM_BLUE].Goals := 0;
1402 if gPlayers <> nil then
1403 for i := 0 to High(gPlayers) do
1404 if gPlayers[i] <> nil then
1405 begin
1406 gPlayers[i].Reset(Force);
1408 if gPlayers[i] is TPlayer then
1409 begin
1410 if (not gPlayers[i].FSpectator) or gPlayers[i].FWantsInGame then
1411 gPlayers[i].Respawn(Silent)
1412 else
1413 gPlayers[i].Spectate();
1414 end
1415 else
1416 TBot(gPlayers[i]).Respawn(Silent);
1417 end;
1418 end;
1420 procedure g_Player_CreateCorpse(Player: TPlayer);
1421 var
1422 find_id: DWORD;
1423 ok: Boolean;
1424 begin
1425 if Player.Live then
1426 Exit;
1427 if Player.FObj.Y >= gMapInfo.Height+128 then
1428 Exit;
1430 with Player do
1431 begin
1432 if (FHealth >= -50) or (gGibsCount = 0) then
1433 begin
1434 if (gCorpses = nil) or (Length(gCorpses) = 0) then
1435 Exit;
1437 ok := False;
1438 for find_id := 0 to High(gCorpses) do
1439 if gCorpses[find_id] = nil then
1440 begin
1441 ok := True;
1442 Break;
1443 end;
1445 if not ok then
1446 find_id := Random(Length(gCorpses));
1448 gCorpses[find_id] := TCorpse.Create(FObj.X, FObj.Y, FModel.Name, FHealth < -20);
1449 gCorpses[find_id].FColor := FModel.Color;
1450 gCorpses[find_id].FObj.Vel := FObj.Vel;
1451 gCorpses[find_id].FObj.Accel := FObj.Accel;
1452 end
1453 else
1454 g_Player_CreateGibs(FObj.X + PLAYER_RECT_CX,
1455 FObj.Y + PLAYER_RECT_CY,
1456 FModel.Name, FModel.Color);
1457 end;
1458 end;
1460 procedure g_Player_CreateShell(fX, fY, dX, dY: Integer; T: Byte);
1461 var
1462 SID: DWORD;
1463 begin
1464 if (gShells = nil) or (Length(gShells) = 0) then
1465 Exit;
1467 with gShells[CurrentShell] do
1468 begin
1469 SpriteID := 0;
1470 g_Obj_Init(@Obj);
1471 Obj.Rect.X := 0;
1472 Obj.Rect.Y := 0;
1473 if T = SHELL_BULLET then
1474 begin
1475 if g_Texture_Get('TEXTURE_SHELL_BULLET', SID) then
1476 SpriteID := SID;
1477 CX := 2;
1478 CY := 1;
1479 Obj.Rect.Width := 4;
1480 Obj.Rect.Height := 2;
1481 end
1482 else
1483 begin
1484 if g_Texture_Get('TEXTURE_SHELL_SHELL', SID) then
1485 SpriteID := SID;
1486 CX := 4;
1487 CY := 2;
1488 Obj.Rect.Width := 7;
1489 Obj.Rect.Height := 3;
1490 end;
1491 SType := T;
1492 Live := True;
1493 Obj.X := fX;
1494 Obj.Y := fY;
1495 g_Obj_Push(@Obj, dX + Random(4)-Random(4), dY-Random(4));
1496 RAngle := Random(360);
1497 Timeout := gTime + SHELL_TIMEOUT;
1499 if CurrentShell >= High(gShells) then
1500 CurrentShell := 0
1501 else
1502 Inc(CurrentShell);
1503 end;
1504 end;
1506 procedure g_Player_CreateGibs(fX, fY: Integer; ModelName: string; fColor: TRGB);
1507 var
1508 a: Integer;
1509 GibsArray: TGibsArray;
1510 begin
1511 if (gGibs = nil) or (Length(gGibs) = 0) then
1512 Exit;
1513 if not g_PlayerModel_GetGibs(ModelName, GibsArray) then
1514 Exit;
1516 for a := 0 to High(GibsArray) do
1517 with gGibs[CurrentGib] do
1518 begin
1519 Color := fColor;
1520 ID := GibsArray[a].ID;
1521 MaskID := GibsArray[a].MaskID;
1522 Live := True;
1523 g_Obj_Init(@Obj);
1524 Obj.Rect := GibsArray[a].Rect;
1525 Obj.X := fX-GibsArray[a].Rect.X-(GibsArray[a].Rect.Width div 2);
1526 Obj.Y := fY-GibsArray[a].Rect.Y-(GibsArray[a].Rect.Height div 2);
1527 g_Obj_PushA(@Obj, 25 + Random(10), Random(361));
1528 RAngle := Random(360);
1530 if gBloodCount > 0 then
1531 g_GFX_Blood(fX, fY, 16*gBloodCount+Random(5*gBloodCount), -16+Random(33), -16+Random(33),
1532 Random(48), Random(48), 150, 0, 0);
1534 if CurrentGib >= High(gGibs) then
1535 CurrentGib := 0
1536 else
1537 Inc(CurrentGib);
1538 end;
1539 end;
1541 procedure g_Player_UpdatePhysicalObjects();
1542 var
1543 i: Integer;
1544 vel: TPoint2i;
1545 mr: Word;
1547 procedure ShellSound_Bounce(X, Y: Integer; T: Byte);
1548 var
1549 k: Integer;
1550 begin
1551 k := 1 + Random(2);
1552 if T = SHELL_BULLET then
1553 g_Sound_PlayExAt('SOUND_PLAYER_CASING' + IntToStr(k), X, Y)
1554 else
1555 g_Sound_PlayExAt('SOUND_PLAYER_SHELL' + IntToStr(k), X, Y);
1556 end;
1558 begin
1559 // Êóñêè ìÿñà:
1560 if gGibs <> nil then
1561 for i := 0 to High(gGibs) do
1562 if gGibs[i].Live then
1563 with gGibs[i] do
1564 begin
1565 vel := Obj.Vel;
1566 mr := g_Obj_Move(@Obj, True, False, True);
1568 if WordBool(mr and MOVE_FALLOUT) then
1569 begin
1570 Live := False;
1571 Continue;
1572 end;
1574 // Îòëåòàåò îò óäàðà î ñòåíó/ïîòîëîê/ïîë:
1575 if WordBool(mr and MOVE_HITWALL) then
1576 Obj.Vel.X := -(vel.X div 2);
1577 if WordBool(mr and (MOVE_HITCEIL or MOVE_HITLAND)) then
1578 Obj.Vel.Y := -(vel.Y div 2);
1580 if (Obj.Vel.X >= 0) then
1581 begin // Clockwise
1582 RAngle := RAngle + Abs(Obj.Vel.X)*6 + Abs(Obj.Vel.Y);
1583 if RAngle >= 360 then
1584 RAngle := RAngle mod 360;
1585 end else begin // Counter-clockwise
1586 RAngle := RAngle - Abs(Obj.Vel.X)*6 - Abs(Obj.Vel.Y);
1587 if RAngle < 0 then
1588 RAngle := (360 - (Abs(RAngle) mod 360)) mod 360;
1589 end;
1591 // Ñîïðîòèâëåíèå âîçäóõà äëÿ êóñêà òðóïà:
1592 if gTime mod (GAME_TICK*3) = 0 then
1593 Obj.Vel.X := z_dec(Obj.Vel.X, 1);
1594 end;
1596 // Òðóïû:
1597 if gCorpses <> nil then
1598 for i := 0 to High(gCorpses) do
1599 if gCorpses[i] <> nil then
1600 if gCorpses[i].State = CORPSE_STATE_REMOVEME then
1601 begin
1602 gCorpses[i].Free();
1603 gCorpses[i] := nil;
1604 end
1605 else
1606 gCorpses[i].Update();
1608 // Ãèëüçû:
1609 if gShells <> nil then
1610 for i := 0 to High(gShells) do
1611 if gShells[i].Live then
1612 with gShells[i] do
1613 begin
1614 vel := Obj.Vel;
1615 mr := g_Obj_Move(@Obj, True, False, True);
1617 if WordBool(mr and MOVE_FALLOUT) or (gShells[i].Timeout < gTime) then
1618 begin
1619 Live := False;
1620 Continue;
1621 end;
1623 // Îòëåòàåò îò óäàðà î ñòåíó/ïîòîëîê/ïîë:
1624 if WordBool(mr and MOVE_HITWALL) then
1625 begin
1626 Obj.Vel.X := -(vel.X div 2);
1627 if not WordBool(mr and MOVE_INWATER) then
1628 ShellSound_Bounce(Obj.X, Obj.Y, SType);
1629 end;
1630 if WordBool(mr and (MOVE_HITCEIL or MOVE_HITLAND)) then
1631 begin
1632 Obj.Vel.Y := -(vel.Y div 2);
1633 if Obj.Vel.X <> 0 then Obj.Vel.X := Obj.Vel.X div 2;
1634 if (Obj.Vel.X = 0) and (Obj.Vel.Y = 0) then
1635 begin
1636 if RAngle mod 90 <> 0 then
1637 RAngle := (RAngle div 90) * 90;
1638 end
1639 else if not WordBool(mr and MOVE_INWATER) then
1640 ShellSound_Bounce(Obj.X, Obj.Y, SType);
1641 end;
1643 if (Obj.Vel.X >= 0) then
1644 begin // Clockwise
1645 RAngle := RAngle + Abs(Obj.Vel.X)*8 + Abs(Obj.Vel.Y);
1646 if RAngle >= 360 then
1647 RAngle := RAngle mod 360;
1648 end else begin // Counter-clockwise
1649 RAngle := RAngle - Abs(Obj.Vel.X)*8 - Abs(Obj.Vel.Y);
1650 if RAngle < 0 then
1651 RAngle := (360 - (Abs(RAngle) mod 360)) mod 360;
1652 end;
1653 end;
1654 end;
1656 procedure g_Player_DrawCorpses();
1657 var
1658 i: Integer;
1659 a: TPoint;
1660 begin
1661 if gGibs <> nil then
1662 for i := 0 to High(gGibs) do
1663 if gGibs[i].Live then
1664 with gGibs[i] do
1665 begin
1666 if not g_Obj_Collide(sX, sY, sWidth, sHeight, @Obj) then
1667 Continue;
1669 a.X := Obj.Rect.X+(Obj.Rect.Width div 2);
1670 a.y := Obj.Rect.Y+(Obj.Rect.Height div 2);
1672 e_DrawAdv(ID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, M_NONE);
1674 e_Colors := Color;
1675 e_DrawAdv(MaskID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, M_NONE);
1676 e_Colors.R := 255;
1677 e_Colors.G := 255;
1678 e_Colors.B := 255;
1679 end;
1681 if gCorpses <> nil then
1682 for i := 0 to High(gCorpses) do
1683 if gCorpses[i] <> nil then
1684 gCorpses[i].Draw();
1685 end;
1687 procedure g_Player_DrawShells();
1688 var
1689 i: Integer;
1690 a: TPoint;
1691 begin
1692 if gShells <> nil then
1693 for i := 0 to High(gShells) do
1694 if gShells[i].Live then
1695 with gShells[i] do
1696 begin
1697 if not g_Obj_Collide(sX, sY, sWidth, sHeight, @Obj) then
1698 Continue;
1700 a.X := CX;
1701 a.Y := CY;
1703 e_DrawAdv(SpriteID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, M_NONE);
1704 end;
1705 end;
1707 procedure g_Player_RemoveAllCorpses();
1708 var
1709 i: Integer;
1710 begin
1711 gGibs := nil;
1712 gShells := nil;
1713 SetLength(gGibs, MaxGibs);
1714 SetLength(gShells, MaxGibs);
1715 CurrentGib := 0;
1716 CurrentShell := 0;
1718 if gCorpses <> nil then
1719 for i := 0 to High(gCorpses) do
1720 gCorpses[i].Free();
1722 gCorpses := nil;
1723 SetLength(gCorpses, MaxCorpses);
1724 end;
1726 procedure g_Player_Corpses_SaveState(var Mem: TBinMemoryWriter);
1727 var
1728 count, i: Integer;
1729 b: Boolean;
1730 begin
1731 // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ òðóïîâ:
1732 count := 0;
1733 if gCorpses <> nil then
1734 for i := 0 to High(gCorpses) do
1735 if gCorpses[i] <> nil then
1736 count := count + 1;
1738 Mem := TBinMemoryWriter.Create((count+1) * 128);
1740 // Êîëè÷åñòâî òðóïîâ:
1741 Mem.WriteInt(count);
1743 if count = 0 then
1744 Exit;
1746 // Ñîõðàíÿåì òðóïû:
1747 for i := 0 to High(gCorpses) do
1748 if gCorpses[i] <> nil then
1749 begin
1750 // Íàçâàíèå ìîäåëè:
1751 Mem.WriteString(gCorpses[i].FModelName);
1752 // Òèï ñìåðòè:
1753 b := gCorpses[i].Mess;
1754 Mem.WriteBoolean(b);
1755 // Ñîõðàíÿåì äàííûå òðóïà:
1756 gCorpses[i].SaveState(Mem);
1757 end;
1758 end;
1760 procedure g_Player_Corpses_LoadState(var Mem: TBinMemoryReader);
1761 var
1762 count, i: Integer;
1763 str: String;
1764 b: Boolean;
1765 begin
1766 if Mem = nil then
1767 Exit;
1769 g_Player_RemoveAllCorpses();
1771 // Êîëè÷åñòâî òðóïîâ:
1772 Mem.ReadInt(count);
1774 if count > Length(gCorpses) then
1775 begin
1776 raise EBinSizeError.Create('g_Player_Corpses_LoadState: Too Many Corpses');
1777 end;
1779 if count = 0 then
1780 Exit;
1782 // Çàãðóæàåì òðóïû:
1783 for i := 0 to count-1 do
1784 begin
1785 // Íàçâàíèå ìîäåëè:
1786 Mem.ReadString(str);
1787 // Òèï ñìåðòè:
1788 Mem.ReadBoolean(b);
1789 // Ñîçäàåì òðóï:
1790 gCorpses[i] := TCorpse.Create(0, 0, str, b);
1791 // Çàãðóæàåì äàííûå òðóïà:
1792 gCorpses[i].LoadState(Mem);
1793 end;
1794 end;
1796 { T P l a y e r : }
1798 procedure TPlayer.BFGHit();
1799 begin
1800 g_Weapon_BFGHit(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
1801 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2));
1802 if g_Game_IsServer and g_Game_IsNet then
1803 MH_SEND_Effect(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
1804 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
1805 0, NET_GFX_BFGHIT);
1806 end;
1808 procedure TPlayer.ChangeModel(ModelName: string);
1809 var
1810 Model: TPlayerModel;
1811 begin
1812 Model := g_PlayerModel_Get(ModelName);
1813 if Model = nil then Exit;
1815 FModel.Free();
1816 FModel := Model;
1817 end;
1819 procedure TPlayer.SetModel(ModelName: string);
1820 var
1821 m: TPlayerModel;
1822 begin
1823 m := g_PlayerModel_Get(ModelName);
1824 if m = nil then
1825 begin
1826 g_SimpleError(Format(_lc[I_GAME_ERROR_MODEL_FALLBACK], [ModelName]));
1827 m := g_PlayerModel_Get('doomer');
1828 if m = nil then
1829 begin
1830 g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], ['doomer']));
1831 Exit;
1832 end;
1833 end;
1835 if FModel <> nil then
1836 FModel.Free();
1838 FModel := m;
1840 if not (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
1841 FModel.Color := FColor
1842 else
1843 FModel.Color := TEAMCOLOR[FTeam];
1844 FModel.SetWeapon(FCurrWeap);
1845 FModel.SetFlag(FFlag);
1846 SetDirection(FDirection);
1847 end;
1849 procedure TPlayer.SetColor(Color: TRGB);
1850 begin
1851 FColor := Color;
1852 if not (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
1853 if FModel <> nil then FModel.Color := Color;
1854 end;
1856 procedure TPlayer.SwitchTeam;
1857 begin
1858 if g_Game_IsClient then
1859 Exit;
1860 if not (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then Exit;
1862 if gGameOn and FLive then
1863 Kill(K_SIMPLEKILL, FUID, HIT_SELF);
1865 if FTeam = TEAM_RED then
1866 begin
1867 ChangeTeam(TEAM_BLUE);
1868 g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_BLUE], [FName]), True);
1869 if g_Game_IsNet then
1870 MH_SEND_GameEvent(NET_EV_CHANGE_TEAM, TEAM_BLUE, FName);
1871 end
1872 else
1873 begin
1874 ChangeTeam(TEAM_RED);
1875 g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_RED], [FName]), True);
1876 if g_Game_IsNet then
1877 MH_SEND_GameEvent(NET_EV_CHANGE_TEAM, TEAM_RED, FName);
1878 end;
1879 FPreferredTeam := FTeam;
1880 end;
1882 procedure TPlayer.ChangeTeam(Team: Byte);
1883 var
1884 OldTeam: Byte;
1885 begin
1886 OldTeam := FTeam;
1887 FTeam := Team;
1888 case Team of
1889 TEAM_RED, TEAM_BLUE:
1890 FModel.Color := TEAMCOLOR[Team];
1891 else
1892 FModel.Color := FColor;
1893 end;
1894 if (FTeam <> OldTeam) and g_Game_IsNet and g_Game_IsServer then
1895 MH_SEND_PlayerStats(FUID);
1896 end;
1899 procedure TPlayer.CollideItem();
1900 var
1901 i: Integer;
1902 r: Boolean;
1903 begin
1904 if gItems = nil then Exit;
1905 if not FLive then Exit;
1907 for i := 0 to High(gItems) do
1908 with gItems[i] do
1909 begin
1910 if (ItemType <> ITEM_NONE) and Live then
1911 if g_Obj_Collide(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width,
1912 PLAYER_RECT.Height, @Obj) then
1913 begin
1914 if not PickItem(ItemType, gItems[i].Respawnable, r) then Continue;
1916 if ItemType in [ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_INVUL] then
1917 g_Sound_PlayExAt('SOUND_ITEM_GETRULEZ', FObj.X, FObj.Y)
1918 else if ItemType in [ITEM_MEDKIT_SMALL, ITEM_MEDKIT_LARGE, ITEM_MEDKIT_BLACK] then
1919 g_Sound_PlayExAt('SOUND_ITEM_GETMED', FObj.X, FObj.Y)
1920 else g_Sound_PlayExAt('SOUND_ITEM_GETITEM', FObj.X, FObj.Y);
1922 // Íàäî óáðàòü ñ êàðòû, åñëè ýòî íå êëþ÷, êîòîðûì íóæíî ïîäåëèòñÿ ñ äðóãèì èãðîêîì:
1923 if r and not ((ItemType in [ITEM_KEY_RED, ITEM_KEY_GREEN, ITEM_KEY_BLUE]) and
1924 (gGameSettings.GameType = GT_SINGLE) and
1925 (g_Player_GetCount() > 1)) then
1926 if not Respawnable then g_Items_Remove(i) else g_Items_Pick(i);
1927 end;
1928 end;
1929 end;
1932 function TPlayer.CollideLevel(XInc, YInc: Integer): Boolean;
1933 begin
1934 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
1935 PLAYER_RECT.Width, PLAYER_RECT.Height, PANEL_WALL,
1936 False);
1937 end;
1939 constructor TPlayer.Create();
1940 begin
1941 FIamBot := False;
1942 FDummy := False;
1943 FSpawned := False;
1945 FSawSound := TPlayableSound.Create();
1946 FSawSoundIdle := TPlayableSound.Create();
1947 FSawSoundHit := TPlayableSound.Create();
1948 FSawSoundSelect := TPlayableSound.Create();
1949 FJetSoundFly := TPlayableSound.Create();
1950 FJetSoundOn := TPlayableSound.Create();
1951 FJetSoundOff := TPlayableSound.Create();
1953 FSawSound.SetByName('SOUND_WEAPON_FIRESAW');
1954 FSawSoundIdle.SetByName('SOUND_WEAPON_IDLESAW');
1955 FSawSoundHit.SetByName('SOUND_WEAPON_HITSAW');
1956 FSawSoundSelect.SetByName('SOUND_WEAPON_SELECTSAW');
1957 FJetSoundFly.SetByName('SOUND_PLAYER_JETFLY');
1958 FJetSoundOn.SetByName('SOUND_PLAYER_JETON');
1959 FJetSoundOff.SetByName('SOUND_PLAYER_JETOFF');
1961 FSpectatePlayer := -1;
1962 FClientID := -1;
1963 FPing := 0;
1964 FLoss := 0;
1965 FSavedState.WaitRecall := False;
1966 FShellTimer := -1;
1968 FActualModelName := 'doomer';
1970 g_Obj_Init(@FObj);
1971 FObj.Rect := PLAYER_RECT;
1973 FBFGFireCounter := -1;
1974 FJustTeleported := False;
1975 FNetTime := 0;
1977 resetWeaponQueue();
1978 end;
1980 procedure TPlayer.Damage(value: Word; SpawnerUID: Word; vx, vy: Integer; t: Byte);
1981 var
1982 c: Word;
1983 begin
1984 if (not g_Game_IsClient) and (not FLive) then
1985 Exit;
1987 FLastHit := t;
1989 // Íåóÿçâèìîñòü íå ñïàñàåò îò ëîâóøåê:
1990 if ((t = HIT_TRAP) or (t = HIT_SELF)) and (not FGodMode) then
1991 begin
1992 if not g_Game_IsClient then
1993 begin
1994 FArmor := 0;
1995 if t = HIT_TRAP then
1996 begin
1997 // Ëîâóøêà óáèâàåò ñðàçó:
1998 FHealth := -100;
1999 Kill(K_EXTRAHARDKILL, SpawnerUID, t);
2000 end;
2001 if t = HIT_SELF then
2002 begin
2003 // Ñàìîóáèéñòâî:
2004 FHealth := 0;
2005 Kill(K_SIMPLEKILL, SpawnerUID, t);
2006 end;
2007 end;
2008 // Îáíóëèòü äåéñòâèÿ ïðèìî÷åê, ÷òîáû ôîí ïðîïàë
2009 FMegaRulez[MR_SUIT] := 0;
2010 FMegaRulez[MR_INVUL] := 0;
2011 FMegaRulez[MR_INVIS] := 0;
2012 FBerserk := 0;
2013 end;
2015 // Íî îò îñòàëüíîãî ñïàñàåò:
2016 if FMegaRulez[MR_INVUL] >= gTime then
2017 Exit;
2019 // ×èò-êîä "ÃÎÐÅÖ":
2020 if FGodMode then
2021 Exit;
2023 // Åñëè åñòü óðîí ñâîèì, èëè ðàíèë ñàì ñåáÿ, èëè òåáÿ ðàíèë ïðîòèâíèê:
2024 if LongBool(gGameSettings.Options and GAME_OPTION_TEAMDAMAGE) or
2025 (SpawnerUID = FUID) or
2026 (not SameTeam(FUID, SpawnerUID)) then
2027 begin
2028 FLastSpawnerUID := SpawnerUID;
2030 // Êðîâü (ïóçûðüêè, åñëè â âîäå):
2031 if gBloodCount > 0 then
2032 begin
2033 c := Min(value, 200)*gBloodCount + Random(Min(value, 200) div 2);
2034 if value div 4 <= c then
2035 c := c - (value div 4)
2036 else
2037 c := 0;
2039 if (t = HIT_SOME) and (vx = 0) and (vy = 0) then
2040 MakeBloodSimple(c)
2041 else
2042 case t of
2043 HIT_TRAP, HIT_ACID, HIT_FLAME, HIT_SELF: MakeBloodSimple(c);
2044 HIT_BFG, HIT_ROCKET, HIT_SOME: MakeBloodVector(c, vx, vy);
2045 end;
2047 if t = HIT_WATER then
2048 g_GFX_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
2049 FObj.Y+PLAYER_RECT.Y-4, value div 2, 8, 4);
2050 end;
2052 // Áóôåð óðîíà:
2053 if FLive then
2054 Inc(FDamageBuffer, value);
2056 // Âñïûøêà áîëè:
2057 if gFlash <> 0 then
2058 FPain := FPain + value;
2059 end;
2061 if g_Game_IsServer and g_Game_IsNet then
2062 begin
2063 MH_SEND_PlayerDamage(FUID, t, SpawnerUID, value, vx, vy);
2064 MH_SEND_PlayerStats(FUID);
2065 MH_SEND_PlayerPos(False, FUID);
2066 end;
2067 end;
2069 function TPlayer.Heal(value: Word; Soft: Boolean): Boolean;
2070 begin
2071 Result := False;
2072 if g_Game_IsClient then
2073 Exit;
2074 if not FLive then
2075 Exit;
2077 if Soft and (FHealth < PLAYER_HP_SOFT) then
2078 begin
2079 IncMax(FHealth, value, PLAYER_HP_SOFT);
2080 Result := True;
2081 end;
2082 if (not Soft) and (FHealth < PLAYER_HP_LIMIT) then
2083 begin
2084 IncMax(FHealth, value, PLAYER_HP_LIMIT);
2085 Result := True;
2086 end;
2088 if Result and g_Game_IsServer and g_Game_IsNet then
2089 MH_SEND_PlayerStats(FUID);
2090 end;
2092 destructor TPlayer.Destroy();
2093 begin
2094 if (gPlayer1 <> nil) and (gPlayer1.FUID = FUID) then
2095 gPlayer1 := nil;
2096 if (gPlayer2 <> nil) and (gPlayer2.FUID = FUID) then
2097 gPlayer2 := nil;
2099 FSawSound.Free();
2100 FSawSoundIdle.Free();
2101 FSawSoundHit.Free();
2102 FJetSoundFly.Free();
2103 FJetSoundOn.Free();
2104 FJetSoundOff.Free();
2105 FModel.Free();
2107 inherited;
2108 end;
2110 procedure TPlayer.DrawBubble();
2111 var
2112 bubX, bubY: Integer;
2113 ID: LongWord;
2114 Rb, Gb, Bb,
2115 Rw, Gw, Bw: SmallInt;
2116 Dot: Byte;
2117 begin
2118 bubX := FObj.X+FObj.Rect.X + IfThen(FDirection = D_LEFT, -4, 18);
2119 bubY := FObj.Y+FObj.Rect.Y - 18;
2120 Rb := 64;
2121 Gb := 64;
2122 Bb := 64;
2123 Rw := 240;
2124 Gw := 240;
2125 Bw := 240;
2126 case gChatBubble of
2127 1: // simple textual non-bubble
2128 begin
2129 bubX := FObj.X+FObj.Rect.X - 11;
2130 bubY := FObj.Y+FObj.Rect.Y - 17;
2131 e_TextureFontPrint(bubX, bubY, '[...]', gStdFont);
2132 Exit;
2133 end;
2134 2: // advanced pixel-perfect bubble
2135 begin
2136 if FTeam = TEAM_RED then
2137 Rb := 255
2138 else
2139 if FTeam = TEAM_BLUE then
2140 Bb := 255;
2141 end;
2142 3: // colored bubble
2143 begin
2144 Rb := FModel.Color.R;
2145 Gb := FModel.Color.G;
2146 Bb := FModel.Color.B;
2147 Rw := Min(Rb * 2 + 64, 255);
2148 Gw := Min(Gb * 2 + 64, 255);
2149 Bw := Min(Bb * 2 + 64, 255);
2150 if (Abs(Rw - Rb) < 32)
2151 or (Abs(Gw - Gb) < 32)
2152 or (Abs(Bw - Bb) < 32) then
2153 begin
2154 Rb := Max(Rw div 2 - 16, 0);
2155 Gb := Max(Gw div 2 - 16, 0);
2156 Bb := Max(Bw div 2 - 16, 0);
2157 end;
2158 end;
2159 4: // custom textured bubble
2160 begin
2161 if g_Texture_Get('TEXTURE_PLAYER_TALKBUBBLE', ID) then
2162 if FDirection = D_RIGHT then
2163 e_Draw(ID, bubX - 6, bubY - 7, 0, True, False)
2164 else
2165 e_Draw(ID, bubX - 6, bubY - 7, 0, True, False, M_HORIZONTAL);
2166 Exit;
2167 end;
2168 end;
2170 // Outer borders
2171 e_DrawQuad(bubX + 1, bubY , bubX + 18, bubY + 13, Rb, Gb, Bb);
2172 e_DrawQuad(bubX , bubY + 1, bubX + 19, bubY + 12, Rb, Gb, Bb);
2173 // Inner box
2174 e_DrawFillQuad(bubX + 1, bubY + 1, bubX + 18, bubY + 12, Rw, Gw, Bw, 0);
2176 // Tail
2177 Dot := IfThen(FDirection = D_LEFT, 14, 5);
2178 e_DrawLine(1, bubX + Dot, bubY + 14, bubX + Dot, bubY + 16, Rb, Gb, Bb);
2179 e_DrawLine(1, bubX + IfThen(FDirection = D_LEFT, Dot - 1, Dot + 1), bubY + 13, bubX + IfThen(FDirection = D_LEFT, Dot - 1, Dot + 1), bubY + 15, Rw, Gw, Bw);
2180 e_DrawLine(1, bubX + IfThen(FDirection = D_LEFT, Dot - 2, Dot + 2), bubY + 13, bubX + IfThen(FDirection = D_LEFT, Dot - 2, Dot + 2), bubY + 14, Rw, Gw, Bw);
2181 e_DrawLine(1, bubX + IfThen(FDirection = D_LEFT, Dot - 3, Dot + 3), bubY + 13, bubX + IfThen(FDirection = D_LEFT, Dot - 3, Dot + 3), bubY + 13, Rw, Gw, Bw);
2182 e_DrawLine(1, bubX + IfThen(FDirection = D_LEFT, Dot - 3, Dot + 3), bubY + 14, bubX + IfThen(FDirection = D_LEFT, Dot - 1, Dot + 1), bubY + 16, Rb, Gb, Bb);
2184 // Dots
2185 Dot := 6;
2186 e_DrawFillQuad(bubX + Dot, bubY + 8, bubX + Dot + 1, bubY + 9, Rb, Gb, Bb, 0);
2187 e_DrawFillQuad(bubX + Dot + 3, bubY + 8, bubX + Dot + 4, bubY + 9, Rb, Gb, Bb, 0);
2188 e_DrawFillQuad(bubX + Dot + 6, bubY + 8, bubX + Dot + 7, bubY + 9, Rb, Gb, Bb, 0);
2189 end;
2191 procedure TPlayer.Draw();
2192 var
2193 ID: DWORD;
2194 w, h: Word;
2195 dr: Boolean;
2196 begin
2197 if FLive then
2198 begin
2199 if (FMegaRulez[MR_INVUL] > gTime) and (gPlayerDrawn <> Self) then
2200 if g_Texture_Get('TEXTURE_PLAYER_INVULPENTA', ID) then
2201 begin
2202 e_GetTextureSize(ID, @w, @h);
2203 if FDirection = D_LEFT then
2204 e_Draw(ID, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)+4,
2205 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7, 0, True, False)
2206 else
2207 e_Draw(ID, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)-2,
2208 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7, 0, True, False);
2209 end;
2211 if FMegaRulez[MR_INVIS] > gTime then
2212 begin
2213 if (gPlayerDrawn <> nil) and ((Self = gPlayerDrawn) or
2214 ((FTeam = gPlayerDrawn.Team) and (gGameSettings.GameMode <> GM_DM))) then
2215 begin
2216 if (FMegaRulez[MR_INVIS] - gTime) <= 2100 then
2217 dr := not Odd((FMegaRulez[MR_INVIS] - gTime) div 300)
2218 else
2219 dr := True;
2220 if dr then
2221 FModel.Draw(FObj.X, FObj.Y, 200)
2222 else
2223 FModel.Draw(FObj.X, FObj.Y);
2224 end
2225 else
2226 FModel.Draw(FObj.X, FObj.Y, 254);
2227 end
2228 else
2229 FModel.Draw(FObj.X, FObj.Y);
2230 end;
2232 if g_debug_Frames then
2233 begin
2234 e_DrawQuad(FObj.X+FObj.Rect.X,
2235 FObj.Y+FObj.Rect.Y,
2236 FObj.X+FObj.Rect.X+FObj.Rect.Width-1,
2237 FObj.Y+FObj.Rect.Y+FObj.Rect.Height-1,
2238 0, 255, 0);
2239 end;
2241 if (gChatBubble > 0) and (FKeys[KEY_CHAT].Pressed) and not FGhost then
2242 DrawBubble();
2243 // e_DrawPoint(5, 335, 288, 255, 0, 0); // DL, UR, DL, UR
2244 if gAimLine and Live and
2245 ((Self = gPlayer1) or (Self = gPlayer2)) then
2246 DrawAim();
2247 end;
2249 procedure TPlayer.DrawAim();
2250 var
2251 wx, wy, xx, yy: Integer;
2252 angle: SmallInt;
2253 sz, len: Word;
2254 begin
2255 wx := FObj.X + WEAPONPOINT[FDirection].X + IfThen(FDirection = D_LEFT, 7, -7);
2256 wy := FObj.Y + WEAPONPOINT[FDirection].Y;
2257 angle := FAngle;
2258 len := 1024;
2259 sz := 2;
2260 case FCurrWeap of
2261 0: begin // Punch
2262 len := 12;
2263 sz := 4;
2264 end;
2265 1: begin // Chainsaw
2266 len := 24;
2267 sz := 6;
2268 end;
2269 2: begin // Pistol
2270 len := 1024;
2271 sz := 2;
2272 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2273 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2274 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2275 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2276 end;
2277 3: begin // Shotgun
2278 len := 1024;
2279 sz := 3;
2280 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2281 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2282 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2283 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2284 end;
2285 4: begin // Double Shotgun
2286 len := 1024;
2287 sz := 4;
2288 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2289 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2290 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2291 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2292 end;
2293 5: begin // Chaingun
2294 len := 1024;
2295 sz := 3;
2296 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2297 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2298 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2299 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2300 end;
2301 6: begin // Rocket Launcher
2302 len := 1024;
2303 sz := 7;
2304 if angle = ANGLE_RIGHTUP then Inc(angle, 2);
2305 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2306 if angle = ANGLE_LEFTUP then Dec(angle, 2);
2307 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2308 end;
2309 7: begin // Plasmagun
2310 len := 1024;
2311 sz := 5;
2312 if angle = ANGLE_RIGHTUP then Inc(angle);
2313 if angle = ANGLE_RIGHTDOWN then Inc(angle, 3);
2314 if angle = ANGLE_LEFTUP then Dec(angle);
2315 if angle = ANGLE_LEFTDOWN then Dec(angle, 3);
2316 end;
2317 8: begin // BFG
2318 len := 1024;
2319 sz := 12;
2320 if angle = ANGLE_RIGHTUP then Inc(angle, 1);
2321 if angle = ANGLE_RIGHTDOWN then Inc(angle, 2);
2322 if angle = ANGLE_LEFTUP then Dec(angle, 1);
2323 if angle = ANGLE_LEFTDOWN then Dec(angle, 2);
2324 end;
2325 9: begin // Super Chaingun
2326 len := 1024;
2327 sz := 4;
2328 if angle = ANGLE_RIGHTUP then Dec(angle, 2);
2329 if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
2330 if angle = ANGLE_LEFTUP then Inc(angle, 2);
2331 if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
2332 end;
2333 end;
2334 xx := Trunc(Cos(-DegToRad(angle)) * len) + wx;
2335 yy := Trunc(Sin(-DegToRad(angle)) * len) + wy;
2336 e_DrawLine(sz, wx, wy, xx, yy, 255, 0, 0, 96);
2337 end;
2339 procedure TPlayer.DrawGUI();
2340 var
2341 ID: DWORD;
2342 X, Y, SY, a, p, m: Integer;
2343 tw, th: Word;
2344 cw, ch: Byte;
2345 s: string;
2346 stat: TPlayerStatArray;
2347 begin
2348 X := gPlayerScreenSize.X;
2349 SY := gPlayerScreenSize.Y;
2350 Y := 0;
2352 if gShowGoals and (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
2353 begin
2354 if gGameSettings.GameMode = GM_CTF then
2355 a := 32 + 8
2356 else
2357 a := 0;
2358 if gGameSettings.GameMode = GM_CTF then
2359 begin
2360 s := 'TEXTURE_PLAYER_REDFLAG';
2361 if gFlags[FLAG_RED].State = FLAG_STATE_CAPTURED then
2362 s := 'TEXTURE_PLAYER_REDFLAG_S';
2363 if gFlags[FLAG_RED].State = FLAG_STATE_DROPPED then
2364 s := 'TEXTURE_PLAYER_REDFLAG_D';
2365 if g_Texture_Get(s, ID) then
2366 e_Draw(ID, X-16-32, 240-72-4, 0, True, False);
2367 end;
2369 s := IntToStr(gTeamStat[TEAM_RED].Goals);
2370 e_CharFont_GetSize(gMenuFont, s, tw, th);
2371 e_CharFont_PrintEx(gMenuFont, X-16-a-tw, 240-72-4, s, TEAMCOLOR[TEAM_RED]);
2373 if gGameSettings.GameMode = GM_CTF then
2374 begin
2375 s := 'TEXTURE_PLAYER_BLUEFLAG';
2376 if gFlags[FLAG_BLUE].State = FLAG_STATE_CAPTURED then
2377 s := 'TEXTURE_PLAYER_BLUEFLAG_S';
2378 if gFlags[FLAG_BLUE].State = FLAG_STATE_DROPPED then
2379 s := 'TEXTURE_PLAYER_BLUEFLAG_D';
2380 if g_Texture_Get(s, ID) then
2381 e_Draw(ID, X-16-32, 240-32-4, 0, True, False);
2382 end;
2384 s := IntToStr(gTeamStat[TEAM_BLUE].Goals);
2385 e_CharFont_GetSize(gMenuFont, s, tw, th);
2386 e_CharFont_PrintEx(gMenuFont, X-16-a-tw, 240-32-4, s, TEAMCOLOR[TEAM_BLUE]);
2387 end;
2389 if g_Texture_Get('TEXTURE_PLAYER_HUDBG', ID) then
2390 e_DrawFill(ID, X, 0, 1, (gPlayerScreenSize.Y div 256)+IfThen(gPlayerScreenSize.Y mod 256 > 0, 1, 0),
2391 0, False, False);
2393 if g_Texture_Get('TEXTURE_PLAYER_HUD', ID) then
2394 e_Draw(ID, X+2, Y, 0, True, False);
2396 if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
2397 begin
2398 if gShowStat then
2399 begin
2400 s := IntToStr(Frags);
2401 e_CharFont_GetSize(gMenuFont, s, tw, th);
2402 e_CharFont_PrintEx(gMenuFont, X-16-tw, Y, s, _RGB(255, 0, 0));
2404 s := '';
2405 p := 1;
2406 m := 0;
2407 stat := g_Player_GetStats();
2408 if stat <> nil then
2409 begin
2410 p := 1;
2412 for a := 0 to High(stat) do
2413 if stat[a].Name <> Name then
2414 begin
2415 if stat[a].Frags > m then m := stat[a].Frags;
2416 if stat[a].Frags > Frags then p := p+1;
2417 end;
2418 end;
2420 s := IntToStr(p)+' / '+IntToStr(Length(stat))+' ';
2421 if Frags >= m then s := s+'+' else s := s+'-';
2422 s := s+IntToStr(Abs(Frags-m));
2424 e_CharFont_GetSize(gMenuSmallFont, s, tw, th);
2425 e_CharFont_PrintEx(gMenuSmallFont, X-16-tw, Y+32, s, _RGB(255, 0, 0));
2426 end;
2428 if gShowLives and (gGameSettings.MaxLives > 0) then
2429 begin
2430 s := IntToStr(Lives);
2431 e_CharFont_GetSize(gMenuFont, s, tw, th);
2432 e_CharFont_PrintEx(gMenuFont, X-16-tw, SY-32, s, _RGB(0, 255, 0));
2433 end;
2434 end;
2436 e_CharFont_GetSize(gMenuSmallFont, FName, tw, th);
2437 e_CharFont_PrintEx(gMenuSmallFont, X+98-(tw div 2), Y+8, FName, _RGB(255, 0, 0));
2439 if R_BERSERK in FRulez then
2440 e_Draw(gItemsTexturesID[ITEM_MEDKIT_BLACK], X+37, Y+45, 0, True, False)
2441 else
2442 e_Draw(gItemsTexturesID[ITEM_MEDKIT_LARGE], X+37, Y+45, 0, True, False);
2444 if g_Texture_Get('TEXTURE_PLAYER_ARMORHUD', ID) then
2445 e_Draw(ID, X+36, Y+77, 0, True, False);
2447 s := IntToStr(IfThen(FHealth > 0, FHealth, 0));
2448 e_CharFont_GetSize(gMenuFont, s, tw, th);
2449 e_CharFont_PrintEx(gMenuFont, X+178-tw, Y+40, s, _RGB(255, 0, 0));
2451 s := IntToStr(FArmor);
2452 e_CharFont_GetSize(gMenuFont, s, tw, th);
2453 e_CharFont_PrintEx(gMenuFont, X+178-tw, Y+68, s, _RGB(255, 0, 0));
2455 s := IntToStr(GetAmmoByWeapon(FCurrWeap));
2457 case FCurrWeap of
2458 WEAPON_KASTET:
2459 begin
2460 s := '--';
2461 ID := gItemsTexturesID[ITEM_WEAPON_KASTET];
2462 end;
2463 WEAPON_SAW:
2464 begin
2465 s := '--';
2466 ID := gItemsTexturesID[ITEM_WEAPON_SAW];
2467 end;
2468 WEAPON_PISTOL: ID := gItemsTexturesID[ITEM_WEAPON_PISTOL];
2469 WEAPON_CHAINGUN: ID := gItemsTexturesID[ITEM_WEAPON_CHAINGUN];
2470 WEAPON_SHOTGUN1: ID := gItemsTexturesID[ITEM_WEAPON_SHOTGUN1];
2471 WEAPON_SHOTGUN2: ID := gItemsTexturesID[ITEM_WEAPON_SHOTGUN2];
2472 WEAPON_SUPERPULEMET: ID := gItemsTexturesID[ITEM_WEAPON_SUPERPULEMET];
2473 WEAPON_ROCKETLAUNCHER: ID := gItemsTexturesID[ITEM_WEAPON_ROCKETLAUNCHER];
2474 WEAPON_PLASMA: ID := gItemsTexturesID[ITEM_WEAPON_PLASMA];
2475 WEAPON_BFG: ID := gItemsTexturesID[ITEM_WEAPON_BFG];
2476 end;
2478 e_CharFont_GetSize(gMenuFont, s, tw, th);
2479 e_CharFont_PrintEx(gMenuFont, X+178-tw, Y+158, s, _RGB(255, 0, 0));
2480 e_Draw(ID, X+20, Y+160, 0, True, False);
2482 if R_KEY_RED in FRulez then
2483 e_Draw(gItemsTexturesID[ITEM_KEY_RED], X+78, Y+214, 0, True, False);
2485 if R_KEY_GREEN in FRulez then
2486 e_Draw(gItemsTexturesID[ITEM_KEY_GREEN], X+95, Y+214, 0, True, False);
2488 if R_KEY_BLUE in FRulez then
2489 e_Draw(gItemsTexturesID[ITEM_KEY_BLUE], X+112, Y+214, 0, True, False);
2491 if FJetFuel > 0 then
2492 begin
2493 if g_Texture_Get('TEXTURE_PLAYER_HUDAIR', ID) then
2494 e_Draw(ID, X+2, Y+116, 0, True, False);
2495 if g_Texture_Get('TEXTURE_PLAYER_HUDJET', ID) then
2496 e_Draw(ID, X+2, Y+126, 0, True, False);
2497 e_DrawLine(4, X+16, Y+122, X+16+Trunc(168*IfThen(FAir > 0, FAir, 0)/AIR_MAX), Y+122, 0, 0, 196);
2498 e_DrawLine(4, X+16, Y+132, X+16+Trunc(168*FJetFuel/JET_MAX), Y+132, 208, 0, 0);
2499 end
2500 else
2501 begin
2502 if g_Texture_Get('TEXTURE_PLAYER_HUDAIR', ID) then
2503 e_Draw(ID, X+2, Y+124, 0, True, False);
2504 e_DrawLine(4, X+16, Y+130, X+16+Trunc(168*IfThen(FAir > 0, FAir, 0)/AIR_MAX), Y+130, 0, 0, 196);
2505 end;
2507 if gShowPing and g_Game_IsClient then
2508 begin
2509 s := _lc[I_GAME_PING_HUD] + IntToStr(NetPeer.lastRoundTripTime) + _lc[I_NET_SLIST_PING_MS];
2510 e_TextureFontPrint(X + 4, Y + 242, s, gStdFont);
2511 Y := Y + 16;
2512 end;
2514 if FSpectator then
2515 begin
2516 e_TextureFontPrint(X + 4, Y + 242, _lc[I_PLAYER_SPECT], gStdFont);
2517 e_TextureFontPrint(X + 4, Y + 258, _lc[I_PLAYER_SPECT2], gStdFont);
2518 e_TextureFontPrint(X + 4, Y + 274, _lc[I_PLAYER_SPECT1], gStdFont);
2519 if FNoRespawn then
2520 begin
2521 e_TextureFontGetSize(gStdFont, cw, ch);
2522 s := _lc[I_PLAYER_SPECT4];
2523 e_TextureFontPrintEx(gScreenWidth div 2 - cw*(Length(s) div 2),
2524 gScreenHeight-4-ch, s, gStdFont, 255, 255, 255, 1, True);
2525 e_TextureFontPrint(X + 4, Y + 290, _lc[I_PLAYER_SPECT1S], gStdFont);
2526 end;
2528 end;
2529 end;
2531 procedure TPlayer.DrawRulez();
2532 var
2533 dr: Boolean;
2534 begin
2535 // Ïðè âçÿòèè íåóÿçâèìîñòè ðèñóåòñÿ èíâåðñèîííûé áåëûé ôîí
2536 if FMegaRulez[MR_INVUL] >= gTime then
2537 begin
2538 if (FMegaRulez[MR_INVUL]-gTime) <= 2100 then
2539 dr := not Odd((FMegaRulez[MR_INVUL]-gTime) div 300)
2540 else
2541 dr := True;
2543 if dr then
2544 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1,
2545 191, 191, 191, 0, B_INVERT);
2546 end;
2548 // Ïðè âçÿòèè çàùèòíîãî êîñòþìà ðèñóåòñÿ çåëåíîâàòûé ôîí
2549 if FMegaRulez[MR_SUIT] >= gTime then
2550 begin
2551 if (FMegaRulez[MR_SUIT]-gTime) <= 2100 then
2552 dr := not Odd((FMegaRulez[MR_SUIT]-gTime) div 300)
2553 else
2554 dr := True;
2556 if dr then
2557 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1,
2558 0, 96, 0, 200, B_NONE);
2559 end;
2561 // Ïðè âçÿòèè áåðñåðêà ðèñóåòñÿ êðàñíîâàòûé ôîí
2562 if (FBerserk >= 0) and (LongWord(FBerserk) >= gTime) and (gFlash = 2) then
2563 begin
2564 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1,
2565 255, 0, 0, 200, B_NONE);
2566 end;
2567 end;
2569 procedure TPlayer.DrawPain();
2570 var
2571 a, h: Integer;
2572 begin
2573 if FPain = 0 then Exit;
2575 a := FPain;
2577 if a < 15 then h := 0
2578 else if a < 35 then h := 1
2579 else if a < 55 then h := 2
2580 else if a < 75 then h := 3
2581 else if a < 95 then h := 4
2582 else h := 5;
2584 //if a > 255 then a := 255;
2586 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, 255, 0, 0, 255-h*50);
2587 //e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, 255-min(128, a), 255-a, 255-a, 0, B_FILTER);
2588 end;
2590 procedure TPlayer.DrawPickup();
2591 var
2592 a, h: Integer;
2593 begin
2594 if FPickup = 0 then Exit;
2596 a := FPickup;
2598 if a < 15 then h := 1
2599 else if a < 35 then h := 2
2600 else if a < 55 then h := 3
2601 else if a < 75 then h := 4
2602 else h := 5;
2604 e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, 150, 200, 150, 255-h*50);
2605 end;
2607 procedure TPlayer.Fire();
2608 var
2609 f, DidFire: Boolean;
2610 wx, wy, xd, yd: Integer;
2611 obj: TObj;
2612 begin
2613 if g_Game_IsClient then Exit;
2614 // FBFGFireCounter - âðåìÿ ïåðåä âûñòðåëîì (äëÿ BFG)
2615 // FReloading - âðåìÿ ïîñëå âûñòðåëà (äëÿ âñåãî)
2617 if FSpectator then
2618 begin
2619 Respawn(False);
2620 Exit;
2621 end;
2623 if FReloading[FCurrWeap] <> 0 then Exit;
2625 DidFire := False;
2627 f := False;
2628 wx := FObj.X+WEAPONPOINT[FDirection].X;
2629 wy := FObj.Y+WEAPONPOINT[FDirection].Y;
2630 xd := wx+IfThen(FDirection = D_LEFT, -30, 30);
2631 yd := wy+firediry();
2633 case FCurrWeap of
2634 WEAPON_KASTET:
2635 begin
2636 if R_BERSERK in FRulez then
2637 begin
2638 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
2639 obj.X := FObj.X+FObj.Rect.X;
2640 obj.Y := FObj.Y+FObj.Rect.Y;
2641 obj.rect.X := 0;
2642 obj.rect.Y := 0;
2643 obj.rect.Width := 39;
2644 obj.rect.Height := 52;
2645 obj.Vel.X := (xd-wx) div 2;
2646 obj.Vel.Y := (yd-wy) div 2;
2647 obj.Accel.X := xd-wx;
2648 obj.Accel.y := yd-wy;
2650 if g_Weapon_Hit(@obj, 50, FUID, HIT_SOME) <> 0 then
2651 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj.X, FObj.Y)
2652 else
2653 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj.X, FObj.Y);
2655 if gFlash = 1 then
2656 if FPain < 50 then
2657 FPain := min(FPain + 25, 50);
2658 end else g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 3, FUID);
2660 DidFire := True;
2661 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2662 end;
2664 WEAPON_SAW:
2665 begin
2666 if g_Weapon_chainsaw(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
2667 IfThen(gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF], 9, 3), FUID) <> 0 then
2668 begin
2669 FSawSoundSelect.Stop();
2670 FSawSound.Stop();
2671 FSawSoundHit.PlayAt(FObj.X, FObj.Y);
2672 end
2673 else if not FSawSoundHit.IsPlaying() then
2674 begin
2675 FSawSoundSelect.Stop();
2676 FSawSound.PlayAt(FObj.X, FObj.Y);
2677 end;
2679 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2680 DidFire := True;
2681 f := True;
2682 end;
2684 WEAPON_PISTOL:
2685 if FAmmo[A_BULLETS] > 0 then
2686 begin
2687 g_Weapon_pistol(wx, wy, xd, yd, FUID);
2688 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2689 Dec(FAmmo[A_BULLETS]);
2690 FFireAngle := FAngle;
2691 f := True;
2692 DidFire := True;
2693 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
2694 GameVelX, GameVelY-2, SHELL_BULLET);
2695 end;
2697 WEAPON_SHOTGUN1:
2698 if FAmmo[A_SHELLS] > 0 then
2699 begin
2700 g_Weapon_shotgun(wx, wy, xd, yd, FUID);
2701 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', wx, wy);
2702 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2703 Dec(FAmmo[A_SHELLS]);
2704 FFireAngle := FAngle;
2705 f := True;
2706 DidFire := True;
2707 FShellTimer := 10;
2708 FShellType := SHELL_SHELL;
2709 end;
2711 WEAPON_SHOTGUN2:
2712 if FAmmo[A_SHELLS] >= 2 then
2713 begin
2714 g_Weapon_dshotgun(wx, wy, xd, yd, FUID);
2715 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2716 Dec(FAmmo[A_SHELLS], 2);
2717 FFireAngle := FAngle;
2718 f := True;
2719 DidFire := True;
2720 FShellTimer := 13;
2721 FShellType := SHELL_DBLSHELL;
2722 end;
2724 WEAPON_CHAINGUN:
2725 if FAmmo[A_BULLETS] > 0 then
2726 begin
2727 g_Weapon_mgun(wx, wy, xd, yd, FUID);
2728 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', wx, wy);
2729 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2730 Dec(FAmmo[A_BULLETS]);
2731 FFireAngle := FAngle;
2732 f := True;
2733 DidFire := True;
2734 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
2735 GameVelX, GameVelY-2, SHELL_BULLET);
2736 end;
2738 WEAPON_ROCKETLAUNCHER:
2739 if FAmmo[A_ROCKETS] > 0 then
2740 begin
2741 g_Weapon_rocket(wx, wy, xd, yd, FUID);
2742 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2743 Dec(FAmmo[A_ROCKETS]);
2744 FFireAngle := FAngle;
2745 f := True;
2746 DidFire := True;
2747 end;
2749 WEAPON_PLASMA:
2750 if FAmmo[A_CELLS] > 0 then
2751 begin
2752 g_Weapon_plasma(wx, wy, xd, yd, FUID);
2753 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2754 Dec(FAmmo[A_CELLS]);
2755 FFireAngle := FAngle;
2756 f := True;
2757 DidFire := True;
2758 end;
2760 WEAPON_BFG:
2761 if (FAmmo[A_CELLS] >= 40) and (FBFGFireCounter = -1) then
2762 begin
2763 FBFGFireCounter := 17;
2764 if not FNoReload then
2765 g_Sound_PlayExAt('SOUND_WEAPON_STARTFIREBFG', FObj.X, FObj.Y);
2766 Dec(FAmmo[A_CELLS], 40);
2767 DidFire := True;
2768 end;
2770 WEAPON_SUPERPULEMET:
2771 if FAmmo[A_SHELLS] > 0 then
2772 begin
2773 g_Weapon_shotgun(wx, wy, xd, yd, FUID);
2774 if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx, wy);
2775 FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
2776 Dec(FAmmo[A_SHELLS]);
2777 FFireAngle := FAngle;
2778 f := True;
2779 DidFire := True;
2780 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
2781 GameVelX, GameVelY-2, SHELL_SHELL);
2782 end;
2783 end;
2785 if g_Game_IsNet then
2786 begin
2787 if DidFire then
2788 begin
2789 if FCurrWeap <> WEAPON_BFG then
2790 MH_SEND_PlayerFire(FUID, FCurrWeap, wx, wy, xd, yd, LastShotID)
2791 else
2792 if not FNoReload then
2793 MH_SEND_Sound(FObj.X, FObj.Y, 'SOUND_WEAPON_STARTFIREBFG');
2794 end;
2796 MH_SEND_PlayerStats(FUID);
2797 end;
2799 if not f then Exit;
2801 if (FAngle = 0) or (FAngle = 180) then SetAction(A_ATTACK)
2802 else if (FAngle = ANGLE_LEFTDOWN) or (FAngle = ANGLE_RIGHTDOWN) then SetAction(A_ATTACKDOWN)
2803 else if (FAngle = ANGLE_LEFTUP) or (FAngle = ANGLE_RIGHTUP) then SetAction(A_ATTACKUP);
2804 end;
2806 function TPlayer.GetAmmoByWeapon(Weapon: Byte): Word;
2807 begin
2808 case Weapon of
2809 WEAPON_PISTOL, WEAPON_CHAINGUN: Result := FAmmo[A_BULLETS];
2810 WEAPON_SHOTGUN1, WEAPON_SHOTGUN2, WEAPON_SUPERPULEMET: Result := FAmmo[A_SHELLS];
2811 WEAPON_ROCKETLAUNCHER: Result := FAmmo[A_ROCKETS];
2812 WEAPON_PLASMA, WEAPON_BFG: Result := FAmmo[A_CELLS];
2813 else Result := 0;
2814 end;
2815 end;
2817 function TPlayer.HeadInLiquid(XInc, YInc: Integer): Boolean;
2818 begin
2819 Result := g_Map_CollidePanel(FObj.X+PLAYER_HEADRECT.X+XInc, FObj.Y+PLAYER_HEADRECT.Y+YInc,
2820 PLAYER_HEADRECT.Width, PLAYER_HEADRECT.Height,
2821 PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, True);
2822 end;
2824 procedure TPlayer.JetpackOn;
2825 begin
2826 FJetSoundFly.Stop;
2827 FJetSoundOff.Stop;
2828 FJetSoundOn.SetPosition(0);
2829 FJetSoundOn.PlayAt(FObj.X, FObj.Y);
2830 FlySmoke(8);
2831 end;
2833 procedure TPlayer.JetpackOff;
2834 begin
2835 FJetSoundFly.Stop;
2836 FJetSoundOn.Stop;
2837 FJetSoundOff.SetPosition(0);
2838 FJetSoundOff.PlayAt(FObj.X, FObj.Y);
2839 end;
2841 procedure TPlayer.Jump();
2842 begin
2843 if gFly or FJetpack then
2844 begin
2845 // Ïîëåò (÷èò-êîä èëè äæåòïàê):
2846 if FObj.Vel.Y > -VEL_FLY then
2847 FObj.Vel.Y := FObj.Vel.Y - 3;
2848 if FJetpack then
2849 begin
2850 if FJetFuel > 0 then
2851 Dec(FJetFuel);
2852 if (FJetFuel < 1) and g_Game_IsServer then
2853 begin
2854 FJetpack := False;
2855 JetpackOff;
2856 if g_Game_IsNet then
2857 MH_SEND_PlayerStats(FUID);
2858 end;
2859 end;
2860 Exit;
2861 end;
2863 // Íå âêëþ÷àòü äæåòïàê â ðåæèìå ïðîõîæäåíèÿ ñêâîçü ñòåíû
2864 if FGhost then
2865 FCanJetpack := False;
2867 // Ïðûãàåì èëè âñïëûâàåì:
2868 if (CollideLevel(0, 1) or
2869 g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y+36, PLAYER_RECT.Width,
2870 PLAYER_RECT.Height-33, PANEL_STEP, False)
2871 ) and (FObj.Accel.Y = 0) then // Íå ïðûãàòü, åñëè åñòü âåðòèêàëüíîå óñêîðåíèå
2872 begin
2873 FObj.Vel.Y := -VEL_JUMP;
2874 FCanJetpack := False;
2875 end
2876 else
2877 begin
2878 if BodyInLiquid(0, 0) then
2879 FObj.Vel.Y := -VEL_SW
2880 else if (FJetFuel > 0) and FCanJetpack and
2881 g_Game_IsServer and (not g_Obj_CollideLiquid(@FObj, 0, 0)) then
2882 begin
2883 FJetpack := True;
2884 JetpackOn;
2885 if g_Game_IsNet then
2886 MH_SEND_PlayerStats(FUID);
2887 end;
2888 end;
2889 end;
2891 procedure TPlayer.Kill(KillType: Byte; SpawnerUID: Word; t: Byte);
2892 var
2893 a, i, k, ab, ar: Byte;
2894 s: String;
2895 mon: TMonster;
2896 plr: TPlayer;
2897 srv, netsrv: Boolean;
2898 DoFrags: Boolean;
2899 OldLR: Byte;
2900 KP: TPlayer;
2902 procedure PushItem(t: Byte);
2903 var
2904 id: DWORD;
2905 begin
2906 id := g_Items_Create(FObj.X, FObj.Y, t, True, False);
2907 if KillType = K_EXTRAHARDKILL then // -7..+7; -8..0
2908 g_Obj_Push(@gItems[id].Obj, (FObj.Vel.X div 2)-7+Random(15),
2909 (FObj.Vel.Y div 2)-Random(9))
2910 else
2911 if KillType = K_HARDKILL then // -5..+5; -5..0
2912 g_Obj_Push(@gItems[id].Obj, (FObj.Vel.X div 2)-5+Random(11),
2913 (FObj.Vel.Y div 2)-Random(6))
2914 else // -3..+3; -3..0
2915 g_Obj_Push(@gItems[id].Obj, (FObj.Vel.X div 2)-3+Random(7),
2916 (FObj.Vel.Y div 2)-Random(4));
2918 if g_Game_IsNet and g_Game_IsServer then
2919 MH_SEND_ItemSpawn(True, id);
2920 end;
2922 begin
2923 DoFrags := (gGameSettings.MaxLives = 0) or (gGameSettings.GameMode = GM_COOP);
2924 Srv := g_Game_IsServer;
2925 Netsrv := g_Game_IsServer and g_Game_IsNet;
2926 if Srv then FDeath := FDeath + 1;
2927 if FLive then
2928 begin
2929 if FGhost then
2930 FGhost := False;
2931 if not FPhysics then
2932 FPhysics := True;
2933 FLive := False;
2934 end;
2935 FShellTimer := -1;
2937 if (gGameSettings.MaxLives > 0) and Srv and (gLMSRespawn = LMS_RESPAWN_NONE) then
2938 begin
2939 if FLives > 0 then FLives := FLives - 1;
2940 if FLives = 0 then FNoRespawn := True;
2941 end;
2943 // Íîìåð òèïà ñìåðòè:
2944 a := 1;
2945 case KillType of
2946 K_SIMPLEKILL: a := 1;
2947 K_HARDKILL: a := 2;
2948 K_EXTRAHARDKILL: a := 3;
2949 K_FALLKILL: a := 4;
2950 end;
2952 // Çâóê ñìåðòè:
2953 if not FModel.PlaySound(MODELSOUND_DIE, a, FObj.X, FObj.Y) then
2954 for i := 1 to 3 do
2955 if FModel.PlaySound(MODELSOUND_DIE, i, FObj.X, FObj.Y) then
2956 Break;
2958 // Âðåìÿ ðåñïàóíà:
2959 if Srv then
2960 case KillType of
2961 K_SIMPLEKILL:
2962 FTime[T_RESPAWN] := gTime + TIME_RESPAWN1;
2963 K_HARDKILL:
2964 FTime[T_RESPAWN] := gTime + TIME_RESPAWN2;
2965 K_EXTRAHARDKILL, K_FALLKILL:
2966 FTime[T_RESPAWN] := gTime + TIME_RESPAWN3;
2967 end;
2969 // Ïåðåêëþ÷àåì ñîñòîÿíèå:
2970 case KillType of
2971 K_SIMPLEKILL:
2972 SetAction(A_DIE1);
2973 K_HARDKILL, K_EXTRAHARDKILL:
2974 SetAction(A_DIE2);
2975 end;
2977 // Ðåàêöèÿ ìîíñòðîâ íà ñìåðòü èãðîêà:
2978 if (KillType <> K_FALLKILL) and (Srv) then
2979 g_Monsters_killedp();
2981 if SpawnerUID = FUID then
2982 begin // Ñàìîóáèëñÿ
2983 if Srv and (DoFrags or (gGameSettings.GameMode = GM_TDM)) then
2984 begin
2985 Dec(FFrags);
2986 FLastFrag := 0;
2987 end;
2988 g_Console_Add(Format(_lc[I_PLAYER_KILL_SELF], [FName]), True);
2989 end
2990 else
2991 if g_GetUIDType(SpawnerUID) = UID_PLAYER then
2992 begin // Óáèò äðóãèì èãðîêîì
2993 KP := g_Player_Get(SpawnerUID);
2994 if (KP <> nil) and Srv then
2995 begin
2996 if (DoFrags or (gGameSettings.GameMode = GM_TDM)) then
2997 if SameTeam(FUID, SpawnerUID) then
2998 begin
2999 Dec(KP.FFrags);
3000 KP.FLastFrag := 0;
3001 end else
3002 begin
3003 Inc(KP.FFrags);
3004 KP.FragCombo();
3005 end;
3007 if (gGameSettings.GameMode = GM_TDM) and DoFrags then
3008 Inc(gTeamStat[KP.Team].Goals,
3009 IfThen(SameTeam(FUID, SpawnerUID), -1, 1));
3011 if netsrv then MH_SEND_PlayerStats(SpawnerUID);
3012 end;
3014 plr := g_Player_Get(SpawnerUID);
3015 if plr = nil then
3016 s := '?'
3017 else
3018 s := plr.FName;
3020 case KillType of
3021 K_HARDKILL:
3022 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_2],
3023 [FName, s]),
3024 gShowKillMsg);
3025 K_EXTRAHARDKILL:
3026 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_1],
3027 [FName, s]),
3028 gShowKillMsg);
3029 else
3030 g_Console_Add(Format(_lc[I_PLAYER_KILL],
3031 [FName, s]),
3032 gShowKillMsg);
3033 end;
3034 end
3035 else if g_GetUIDType(SpawnerUID) = UID_MONSTER then
3036 begin // Óáèò ìîíñòðîì
3037 mon := g_Monsters_Get(SpawnerUID);
3038 if mon = nil then
3039 s := '?'
3040 else
3041 s := g_Monsters_GetKilledBy(mon.MonsterType);
3043 case KillType of
3044 K_HARDKILL:
3045 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_2],
3046 [FName, s]),
3047 gShowKillMsg);
3048 K_EXTRAHARDKILL:
3049 g_Console_Add(Format(_lc[I_PLAYER_KILL_EXTRAHARD_1],
3050 [FName, s]),
3051 gShowKillMsg);
3052 else
3053 g_Console_Add(Format(_lc[I_PLAYER_KILL],
3054 [FName, s]),
3055 gShowKillMsg);
3056 end;
3057 end
3058 else // Îñîáûå òèïû ñìåðòè
3059 case t of
3060 HIT_DISCON: ;
3061 HIT_SELF: g_Console_Add(Format(_lc[I_PLAYER_KILL_SELF], [FName]), True);
3062 HIT_FALL: g_Console_Add(Format(_lc[I_PLAYER_KILL_FALL], [FName]), True);
3063 HIT_WATER: g_Console_Add(Format(_lc[I_PLAYER_KILL_WATER], [FName]), True);
3064 HIT_ACID: g_Console_Add(Format(_lc[I_PLAYER_KILL_ACID], [FName]), True);
3065 HIT_TRAP: g_Console_Add(Format(_lc[I_PLAYER_KILL_TRAP], [FName]), True);
3066 else g_Console_Add(Format(_lc[I_PLAYER_DIED], [FName]), True);
3067 end;
3069 if Srv then
3070 begin
3071 // Âûáðîñ îðóæèÿ:
3072 for a := WEAPON_KASTET to WEAPON_SUPERPULEMET do
3073 if FWeapon[a] then
3074 begin
3075 case a of
3076 WEAPON_SAW: i := ITEM_WEAPON_SAW;
3077 WEAPON_SHOTGUN1: i := ITEM_WEAPON_SHOTGUN1;
3078 WEAPON_SHOTGUN2: i := ITEM_WEAPON_SHOTGUN2;
3079 WEAPON_CHAINGUN: i := ITEM_WEAPON_CHAINGUN;
3080 WEAPON_ROCKETLAUNCHER: i := ITEM_WEAPON_ROCKETLAUNCHER;
3081 WEAPON_PLASMA: i := ITEM_WEAPON_PLASMA;
3082 WEAPON_BFG: i := ITEM_WEAPON_BFG;
3083 WEAPON_SUPERPULEMET: i := ITEM_WEAPON_SUPERPULEMET;
3084 else i := 0;
3085 end;
3087 if i <> 0 then
3088 PushItem(i);
3089 end;
3091 // Âûáðîñ ðþêçàêà:
3092 if R_ITEM_BACKPACK in FRulez then
3093 PushItem(ITEM_AMMO_BACKPACK);
3095 // Âûáðîñ ðàêåòíîãî ðàíöà:
3096 if FJetFuel > 0 then
3097 PushItem(ITEM_JETPACK);
3099 // Âûáðîñ êëþ÷åé:
3100 if not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) then
3101 begin
3102 if R_KEY_RED in FRulez then
3103 PushItem(ITEM_KEY_RED);
3105 if R_KEY_GREEN in FRulez then
3106 PushItem(ITEM_KEY_GREEN);
3108 if R_KEY_BLUE in FRulez then
3109 PushItem(ITEM_KEY_BLUE);
3110 end;
3112 // Âûáðîñ ôëàãà:
3113 DropFlag();
3114 end;
3116 g_Player_CreateCorpse(Self);
3118 if Srv and (gGameSettings.MaxLives > 0) and FNoRespawn and
3119 (gLMSRespawn = LMS_RESPAWN_NONE) then
3120 begin
3121 a := 0;
3122 k := 0;
3123 ar := 0;
3124 ab := 0;
3125 for i := Low(gPlayers) to High(gPlayers) do
3126 begin
3127 if gPlayers[i] = nil then continue;
3128 if (not gPlayers[i].FNoRespawn) and (not gPlayers[i].FSpectator) then
3129 begin
3130 Inc(a);
3131 if gPlayers[i].FTeam = TEAM_RED then Inc(ar)
3132 else if gPlayers[i].FTeam = TEAM_BLUE then Inc(ab);
3133 k := i;
3134 end;
3135 end;
3137 OldLR := gLMSRespawn;
3138 if (gGameSettings.GameMode = GM_COOP) then
3139 begin
3140 if (a = 0) then
3141 begin
3142 // everyone is dead, restart the map
3143 g_Game_Message(_lc[I_MESSAGE_LMS_LOSE], 144);
3144 if Netsrv then
3145 MH_SEND_GameEvent(NET_EV_LMS_LOSE);
3146 gLMSRespawn := LMS_RESPAWN_FINAL;
3147 gLMSRespawnTime := gTime + 5000;
3148 end
3149 else if (a = 1) then
3150 begin
3151 if (gPlayers[k] <> nil) and not (gPlayers[k] is TBot) then
3152 if (gPlayers[k] = gPlayer1) or
3153 (gPlayers[k] = gPlayer2) then
3154 g_Console_Add('*** ' + _lc[I_MESSAGE_LMS_SURVIVOR] + ' ***', True)
3155 else if Netsrv and (gPlayers[k].FClientID >= 0) then
3156 MH_SEND_GameEvent(NET_EV_LMS_SURVIVOR, 0, 'N', gPlayers[k].FClientID);
3157 end;
3158 end
3159 else if (gGameSettings.GameMode = GM_TDM) then
3160 begin
3161 if (ab = 0) and (ar <> 0) then
3162 begin
3163 // blu team ded
3164 g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 144);
3165 if Netsrv then
3166 MH_SEND_GameEvent(NET_EV_TLMS_WIN, TEAM_RED);
3167 Inc(gTeamStat[TEAM_RED].Goals);
3168 gLMSRespawn := LMS_RESPAWN_FINAL;
3169 gLMSRespawnTime := gTime + 5000;
3170 end
3171 else if (ar = 0) and (ab <> 0) then
3172 begin
3173 // red team ded
3174 g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 144);
3175 if Netsrv then
3176 MH_SEND_GameEvent(NET_EV_TLMS_WIN, TEAM_BLUE);
3177 Inc(gTeamStat[TEAM_BLUE].Goals);
3178 gLMSRespawn := LMS_RESPAWN_FINAL;
3179 gLMSRespawnTime := gTime + 5000;
3180 end
3181 else if (ar = 0) and (ab = 0) then
3182 begin
3183 // everyone ded
3184 g_Game_Message(_lc[I_GAME_WIN_DRAW], 144);
3185 if Netsrv then
3186 MH_SEND_GameEvent(NET_EV_LMS_DRAW, 0, FName);
3187 gLMSRespawn := LMS_RESPAWN_FINAL;
3188 gLMSRespawnTime := gTime + 5000;
3189 end;
3190 end
3191 else if (gGameSettings.GameMode = GM_DM) then
3192 begin
3193 if (a = 1) then
3194 begin
3195 if gPlayers[k] <> nil then
3196 with gPlayers[k] do
3197 begin
3198 // survivor is the winner
3199 g_Game_Message(Format(_lc[I_MESSAGE_LMS_WIN], [AnsiUpperCase(FName)]), 144);
3200 if Netsrv then
3201 MH_SEND_GameEvent(NET_EV_LMS_WIN, 0, FName);
3202 Inc(FFrags);
3203 end;
3204 gLMSRespawn := LMS_RESPAWN_FINAL;
3205 gLMSRespawnTime := gTime + 5000;
3206 end
3207 else if (a = 0) then
3208 begin
3209 // everyone is dead, restart the map
3210 g_Game_Message(_lc[I_GAME_WIN_DRAW], 144);
3211 if Netsrv then
3212 MH_SEND_GameEvent(NET_EV_LMS_DRAW, 0, FName);
3213 gLMSRespawn := LMS_RESPAWN_FINAL;
3214 gLMSRespawnTime := gTime + 5000;
3215 end;
3216 end;
3217 if srv and (OldLR = LMS_RESPAWN_NONE) and (gLMSRespawn > LMS_RESPAWN_NONE) then
3218 begin
3219 if NetMode = NET_SERVER then
3220 MH_SEND_GameEvent(NET_EV_LMS_WARMUP, (gLMSRespawnTime - gTime) div 1000)
3221 else
3222 g_Console_Add(Format(_lc[I_MSG_WARMUP_START], [(gLMSRespawnTime - gTime) div 1000]), True);
3223 end;
3224 end;
3226 if Netsrv then
3227 begin
3228 MH_SEND_PlayerStats(FUID);
3229 MH_SEND_PlayerDeath(FUID, KillType, t, SpawnerUID);
3230 if gGameSettings.GameMode = GM_TDM then MH_SEND_GameStats;
3231 end;
3233 if srv and FNoRespawn then Spectate(True);
3234 FWantsInGame := True;
3235 end;
3237 function TPlayer.BodyInLiquid(XInc, YInc: Integer): Boolean;
3238 begin
3239 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc, PLAYER_RECT.Width,
3240 PLAYER_RECT.Height-20, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, False);
3241 end;
3243 function TPlayer.BodyInAcid(XInc, YInc: Integer): Boolean;
3244 begin
3245 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc, PLAYER_RECT.Width,
3246 PLAYER_RECT.Height-20, PANEL_ACID1 or PANEL_ACID2, False);
3247 end;
3249 procedure TPlayer.MakeBloodSimple(Count: Word);
3250 begin
3251 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)+8,
3252 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
3253 Count div 2, 3, -1, 16, (PLAYER_RECT.Height*2 div 3),
3254 150, 0, 0);
3255 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-8,
3256 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
3257 Count div 2, -3, -1, 16, (PLAYER_RECT.Height*2) div 3,
3258 150, 0, 0);
3259 end;
3261 procedure TPlayer.MakeBloodVector(Count: Word; VelX, VelY: Integer);
3262 begin
3263 g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
3264 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
3265 Count, VelX, VelY, 16, (PLAYER_RECT.Height*2) div 3,
3266 150, 0, 0);
3267 end;
3269 procedure TPlayer.QueueWeaponSwitch(Weapon: Byte);
3270 begin
3271 if g_Game_IsClient then Exit;
3272 if Weapon > High(FWeapon) then Exit;
3273 FNextWeap := FNextWeap or (1 shl Weapon);
3274 end;
3276 procedure TPlayer.resetWeaponQueue ();
3277 begin
3278 FNextWeap := 0;
3279 FNextWeapDelay := 0;
3280 end;
3282 // return 255 for "no switch"
3283 function TPlayer.getNextWeaponIndex (): Byte;
3284 var
3285 i: Word;
3286 wantThisWeapon: array[0..64] of Boolean;
3287 wwc: Integer = 0; //HACK!
3288 begin
3289 result := 255; // default result: "no switch"
3290 for i := 0 to High(wantThisWeapon) do wantThisWeapon[i] := false;
3291 for i := 0 to High(FWeapon) do if (FNextWeap and (1 shl i)) <> 0 then begin wantThisWeapon[i] := true; Inc(wwc); end;
3292 if wantThisWeapon[FCurrWeap] then
3293 begin
3294 // these hacks implements alternating between SG and SSG; sorry
3295 if FCurrWeap = WEAPON_SHOTGUN1 then begin wantThisWeapon[WEAPON_SHOTGUN2] := true; Inc(wwc); end;
3296 if FCurrWeap = WEAPON_SHOTGUN2 then begin wantThisWeapon[WEAPON_SHOTGUN1] := true; Inc(wwc); end;
3297 // these hacks implements alternating between knuckles and chainsaw; sorry
3298 if FCurrWeap = WEAPON_KASTET then begin wantThisWeapon[WEAPON_SAW] := true; Inc(wwc); end;
3299 if FCurrWeap = WEAPON_SAW then begin wantThisWeapon[WEAPON_KASTET] := true; Inc(wwc); end;
3300 end;
3301 // exclude currently selected weapon from the set
3302 wantThisWeapon[FCurrWeap] := false;
3303 // slow down alterations a little
3304 if wwc > 1 then
3305 begin
3306 // more than one weapon requested, assume "alteration" and check alteration delay
3307 if FNextWeapDelay > 0 then begin FNextWeap := 0; exit; end; // yeah
3308 end;
3309 // no more hacks (yet)
3310 // do not reset weapon queue, it will be done in `RealizeCurrentWeapon()`
3311 // try weapons in descending order
3312 for i := High(FWeapon) downto 0 do
3313 begin
3314 if wantThisWeapon[i] and FWeapon[i] then
3315 begin
3316 // i found her!
3317 result := Byte(i);
3318 exit;
3319 end;
3320 end;
3321 end;
3323 procedure TPlayer.RealizeCurrentWeapon();
3324 var
3325 i, nw: Byte;
3326 begin
3327 nw := getNextWeaponIndex();
3328 if FNextWeapDelay > 0 then Dec(FNextWeapDelay); // "alteration delay"
3329 if nw = 255 then exit; // don't reset anything here
3330 if nw > High(FWeapon) then begin resetWeaponQueue(); exit; end; // don't forget to reset queue here!
3332 if FBFGFireCounter <> -1 then exit;
3333 if FTime[T_SWITCH] > gTime then exit;
3335 for i := WEAPON_KASTET to WEAPON_SUPERPULEMET do if FReloading[i] > 0 then exit;
3337 if FWeapon[nw] then
3338 begin
3339 FCurrWeap := nw;
3340 FTime[T_SWITCH] := gTime+156;
3341 if FCurrWeap = WEAPON_SAW then FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
3342 FModel.SetWeapon(FCurrWeap);
3343 if g_Game_IsNet then MH_SEND_PlayerStats(FUID);
3344 end;
3345 // reset weapon queue; `getNextWeaponIndex()` guarantees to not select a weapon player don't have
3346 resetWeaponQueue();
3347 FNextWeapDelay := 10; // anyway, 'cause why not
3348 end;
3350 procedure TPlayer.cycleWeapon (dir: Integer);
3351 var
3352 i, cwi: Integer;
3353 begin
3354 if dir < 0 then dir := 1 else if dir > 0 then dir := 1 else exit;
3355 cwi := FCurrWeap;
3356 for i := 0 to High(FWeapon) do
3357 begin
3358 cwi := cwi+dir;
3359 if cwi < 0 then cwi += length(FWeapon)
3360 else if cwi > High(FWeapon) then cwi := cwi-length(FWeapon);
3361 if FWeapon[cwi] then
3362 begin
3363 QueueWeaponSwitch(Byte(cwi));
3364 exit;
3365 end;
3366 end;
3367 end;
3369 procedure TPlayer.NextWeapon();
3370 begin
3371 if g_Game_IsClient then Exit;
3372 cycleWeapon(1);
3373 end;
3375 procedure TPlayer.PrevWeapon();
3376 begin
3377 if g_Game_IsClient then Exit;
3378 cycleWeapon(-1);
3379 end;
3381 procedure TPlayer.SetWeapon(W: Byte);
3382 begin
3383 if FCurrWeap <> W then
3384 if W = WEAPON_SAW then
3385 FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
3387 FCurrWeap := W;
3388 FModel.SetWeapon(CurrWeap);
3389 resetWeaponQueue();
3390 end;
3392 function TPlayer.PickItem(ItemType: Byte; respawn: Boolean; var remove: Boolean): Boolean;
3393 var
3394 a: Boolean;
3395 begin
3396 Result := False;
3397 if g_Game_IsClient then Exit;
3399 // a = true - ìåñòî ñïàâíà ïðåäìåòà:
3400 a := LongBool(gGameSettings.Options and GAME_OPTION_WEAPONSTAY) and respawn;
3401 remove := not a;
3403 case ItemType of
3404 ITEM_MEDKIT_SMALL:
3405 if FHealth < PLAYER_HP_SOFT then
3406 begin
3407 IncMax(FHealth, 10, PLAYER_HP_SOFT);
3408 Result := True;
3409 remove := True;
3410 if gFlash = 2 then Inc(FPickup, 5);
3411 end;
3413 ITEM_MEDKIT_LARGE:
3414 if FHealth < PLAYER_HP_SOFT then
3415 begin
3416 IncMax(FHealth, 25, PLAYER_HP_SOFT);
3417 Result := True;
3418 remove := True;
3419 if gFlash = 2 then Inc(FPickup, 5);
3420 end;
3422 ITEM_ARMOR_GREEN:
3423 if FArmor < PLAYER_AP_SOFT then
3424 begin
3425 FArmor := PLAYER_AP_SOFT;
3426 Result := True;
3427 remove := True;
3428 if gFlash = 2 then Inc(FPickup, 5);
3429 end;
3431 ITEM_ARMOR_BLUE:
3432 if FArmor < PLAYER_AP_LIMIT then
3433 begin
3434 FArmor := PLAYER_AP_LIMIT;
3435 Result := True;
3436 remove := True;
3437 if gFlash = 2 then Inc(FPickup, 5);
3438 end;
3440 ITEM_SPHERE_BLUE:
3441 if FHealth < PLAYER_HP_LIMIT then
3442 begin
3443 IncMax(FHealth, 100, PLAYER_HP_LIMIT);
3444 Result := True;
3445 remove := True;
3446 if gFlash = 2 then Inc(FPickup, 5);
3447 end;
3449 ITEM_SPHERE_WHITE:
3450 if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) then
3451 begin
3452 if FHealth < PLAYER_HP_LIMIT then
3453 FHealth := PLAYER_HP_LIMIT;
3454 if FArmor < PLAYER_AP_LIMIT then
3455 FArmor := PLAYER_AP_LIMIT;
3456 Result := True;
3457 remove := True;
3458 if gFlash = 2 then Inc(FPickup, 5);
3459 end;
3461 ITEM_WEAPON_SAW:
3462 if (not FWeapon[WEAPON_SAW]) or ((not respawn) and (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) then
3463 begin
3464 FWeapon[WEAPON_SAW] := True;
3465 Result := True;
3466 if gFlash = 2 then Inc(FPickup, 5);
3467 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3468 end;
3470 ITEM_WEAPON_SHOTGUN1:
3471 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SHOTGUN1] then
3472 begin
3473 // Íóæíî, ÷òîáû íå âçÿòü âñå ïóëè ñðàçó:
3474 if a and FWeapon[WEAPON_SHOTGUN1] then Exit;
3476 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
3477 FWeapon[WEAPON_SHOTGUN1] := True;
3478 Result := True;
3479 if gFlash = 2 then Inc(FPickup, 5);
3480 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3481 end;
3483 ITEM_WEAPON_SHOTGUN2:
3484 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SHOTGUN2] then
3485 begin
3486 if a and FWeapon[WEAPON_SHOTGUN2] then Exit;
3488 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
3489 FWeapon[WEAPON_SHOTGUN2] := True;
3490 Result := True;
3491 if gFlash = 2 then Inc(FPickup, 5);
3492 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3493 end;
3495 ITEM_WEAPON_CHAINGUN:
3496 if (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or not FWeapon[WEAPON_CHAINGUN] then
3497 begin
3498 if a and FWeapon[WEAPON_CHAINGUN] then Exit;
3500 IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
3501 FWeapon[WEAPON_CHAINGUN] := True;
3502 Result := True;
3503 if gFlash = 2 then Inc(FPickup, 5);
3504 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3505 end;
3507 ITEM_WEAPON_ROCKETLAUNCHER:
3508 if (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or not FWeapon[WEAPON_ROCKETLAUNCHER] then
3509 begin
3510 if a and FWeapon[WEAPON_ROCKETLAUNCHER] then Exit;
3512 IncMax(FAmmo[A_ROCKETS], 2, FMaxAmmo[A_ROCKETS]);
3513 FWeapon[WEAPON_ROCKETLAUNCHER] := True;
3514 Result := True;
3515 if gFlash = 2 then Inc(FPickup, 5);
3516 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3517 end;
3519 ITEM_WEAPON_PLASMA:
3520 if (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or not FWeapon[WEAPON_PLASMA] then
3521 begin
3522 if a and FWeapon[WEAPON_PLASMA] then Exit;
3524 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
3525 FWeapon[WEAPON_PLASMA] := True;
3526 Result := True;
3527 if gFlash = 2 then Inc(FPickup, 5);
3528 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3529 end;
3531 ITEM_WEAPON_BFG:
3532 if (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or not FWeapon[WEAPON_BFG] then
3533 begin
3534 if a and FWeapon[WEAPON_BFG] then Exit;
3536 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
3537 FWeapon[WEAPON_BFG] := True;
3538 Result := True;
3539 if gFlash = 2 then Inc(FPickup, 5);
3540 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3541 end;
3543 ITEM_WEAPON_SUPERPULEMET:
3544 if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SUPERPULEMET] then
3545 begin
3546 if a and FWeapon[WEAPON_SUPERPULEMET] then Exit;
3548 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
3549 FWeapon[WEAPON_SUPERPULEMET] := True;
3550 Result := True;
3551 if gFlash = 2 then Inc(FPickup, 5);
3552 if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
3553 end;
3555 ITEM_AMMO_BULLETS:
3556 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
3557 begin
3558 IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
3559 Result := True;
3560 remove := True;
3561 if gFlash = 2 then Inc(FPickup, 5);
3562 end;
3564 ITEM_AMMO_BULLETS_BOX:
3565 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
3566 begin
3567 IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
3568 Result := True;
3569 remove := True;
3570 if gFlash = 2 then Inc(FPickup, 5);
3571 end;
3573 ITEM_AMMO_SHELLS:
3574 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
3575 begin
3576 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
3577 Result := True;
3578 remove := True;
3579 if gFlash = 2 then Inc(FPickup, 5);
3580 end;
3582 ITEM_AMMO_SHELLS_BOX:
3583 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
3584 begin
3585 IncMax(FAmmo[A_SHELLS], 25, FMaxAmmo[A_SHELLS]);
3586 Result := True;
3587 remove := True;
3588 if gFlash = 2 then Inc(FPickup, 5);
3589 end;
3591 ITEM_AMMO_ROCKET:
3592 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
3593 begin
3594 IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
3595 Result := True;
3596 remove := True;
3597 if gFlash = 2 then Inc(FPickup, 5);
3598 end;
3600 ITEM_AMMO_ROCKET_BOX:
3601 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
3602 begin
3603 IncMax(FAmmo[A_ROCKETS], 5, FMaxAmmo[A_ROCKETS]);
3604 Result := True;
3605 remove := True;
3606 if gFlash = 2 then Inc(FPickup, 5);
3607 end;
3609 ITEM_AMMO_CELL:
3610 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
3611 begin
3612 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
3613 Result := True;
3614 remove := True;
3615 if gFlash = 2 then Inc(FPickup, 5);
3616 end;
3618 ITEM_AMMO_CELL_BIG:
3619 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
3620 begin
3621 IncMax(FAmmo[A_CELLS], 100, FMaxAmmo[A_CELLS]);
3622 Result := True;
3623 remove := True;
3624 if gFlash = 2 then Inc(FPickup, 5);
3625 end;
3627 ITEM_AMMO_BACKPACK:
3628 if not(R_ITEM_BACKPACK in FRulez) or
3629 (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or
3630 (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or
3631 (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or
3632 (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) then
3633 begin
3634 FMaxAmmo[A_BULLETS] := 400;
3635 FMaxAmmo[A_SHELLS] := 100;
3636 FMaxAmmo[A_ROCKETS] := 100;
3637 FMaxAmmo[A_CELLS] := 600;
3639 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
3640 IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
3641 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then
3642 IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
3643 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then
3644 IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
3645 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
3646 IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
3648 FRulez := FRulez + [R_ITEM_BACKPACK];
3649 Result := True;
3650 remove := True;
3651 if gFlash = 2 then Inc(FPickup, 5);
3652 end;
3654 ITEM_KEY_RED:
3655 if not(R_KEY_RED in FRulez) then
3656 begin
3657 Include(FRulez, R_KEY_RED);
3658 Result := True;
3659 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
3660 if gFlash = 2 then Inc(FPickup, 5);
3661 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
3662 end;
3664 ITEM_KEY_GREEN:
3665 if not(R_KEY_GREEN in FRulez) then
3666 begin
3667 Include(FRulez, R_KEY_GREEN);
3668 Result := True;
3669 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
3670 if gFlash = 2 then Inc(FPickup, 5);
3671 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
3672 end;
3674 ITEM_KEY_BLUE:
3675 if not(R_KEY_BLUE in FRulez) then
3676 begin
3677 Include(FRulez, R_KEY_BLUE);
3678 Result := True;
3679 remove := (gGameSettings.GameMode <> GM_COOP) and (g_Player_GetCount() < 2);
3680 if gFlash = 2 then Inc(FPickup, 5);
3681 if (not remove) and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETITEM');
3682 end;
3684 ITEM_SUIT:
3685 if FMegaRulez[MR_SUIT] < gTime+PLAYER_SUIT_TIME then
3686 begin
3687 FMegaRulez[MR_SUIT] := gTime+PLAYER_SUIT_TIME;
3688 Result := True;
3689 remove := True;
3690 if gFlash = 2 then Inc(FPickup, 5);
3691 end;
3693 ITEM_OXYGEN:
3694 if FAir < AIR_MAX then
3695 begin
3696 FAir := AIR_MAX;
3697 Result := True;
3698 remove := True;
3699 if gFlash = 2 then Inc(FPickup, 5);
3700 end;
3702 ITEM_MEDKIT_BLACK:
3703 begin
3704 if not (R_BERSERK in FRulez) then
3705 begin
3706 Include(FRulez, R_BERSERK);
3707 if FBFGFireCounter = -1 then
3708 begin
3709 FCurrWeap := WEAPON_KASTET;
3710 resetWeaponQueue();
3711 FModel.SetWeapon(WEAPON_KASTET);
3712 end;
3713 if gFlash <> 0 then
3714 Inc(FPain, 100);
3715 if gFlash = 2 then Inc(FPickup, 5);
3716 FBerserk := gTime+30000;
3717 Result := True;
3718 remove := True;
3719 end;
3720 if FHealth < PLAYER_HP_SOFT then
3721 begin
3722 FHealth := PLAYER_HP_SOFT;
3723 FBerserk := gTime+30000;
3724 Result := True;
3725 remove := True;
3726 end;
3727 end;
3729 ITEM_INVUL:
3730 if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
3731 begin
3732 FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
3733 Result := True;
3734 remove := True;
3735 if gFlash = 2 then Inc(FPickup, 5);
3736 end;
3738 ITEM_BOTTLE:
3739 if FHealth < PLAYER_HP_LIMIT then
3740 begin
3741 IncMax(FHealth, 4, PLAYER_HP_LIMIT);
3742 Result := True;
3743 remove := True;
3744 if gFlash = 2 then Inc(FPickup, 5);
3745 end;
3747 ITEM_HELMET:
3748 if FArmor < PLAYER_AP_LIMIT then
3749 begin
3750 IncMax(FArmor, 5, PLAYER_AP_LIMIT);
3751 Result := True;
3752 remove := True;
3753 if gFlash = 2 then Inc(FPickup, 5);
3754 end;
3756 ITEM_JETPACK:
3757 if FJetFuel < JET_MAX then
3758 begin
3759 FJetFuel := JET_MAX;
3760 Result := True;
3761 remove := True;
3762 if gFlash = 2 then Inc(FPickup, 5);
3763 end;
3765 ITEM_INVIS:
3766 if FMegaRulez[MR_INVIS] < gTime+PLAYER_INVIS_TIME then
3767 begin
3768 FMegaRulez[MR_INVIS] := gTime+PLAYER_INVIS_TIME;
3769 Result := True;
3770 remove := True;
3771 if gFlash = 2 then Inc(FPickup, 5);
3772 end;
3773 end;
3774 end;
3776 procedure TPlayer.Touch();
3777 begin
3778 if not FLive then
3779 Exit;
3780 //FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
3781 if FIamBot then
3782 begin
3783 // Áðîñèòü ôëàã òîâàðèùó:
3784 if gGameSettings.GameMode = GM_CTF then
3785 DropFlag();
3786 end;
3787 end;
3789 procedure TPlayer.Push(vx, vy: Integer);
3790 begin
3791 if (not FPhysics) and FGhost then
3792 Exit;
3793 FObj.Accel.X := FObj.Accel.X + vx;
3794 FObj.Accel.Y := FObj.Accel.Y + vy;
3795 if g_Game_IsNet and g_Game_IsServer then
3796 MH_SEND_PlayerPos(True, FUID, NET_EVERYONE);
3797 end;
3799 procedure TPlayer.Reset(Force: Boolean);
3800 begin
3801 if Force then
3802 FLive := False;
3804 FSpawned := False;
3805 FTime[T_RESPAWN] := 0;
3806 FTime[T_FLAGCAP] := 0;
3807 FGodMode := False;
3808 FNoTarget := False;
3809 FNoReload := False;
3810 FFrags := 0;
3811 FLastFrag := 0;
3812 FComboEvnt := -1;
3813 FKills := 0;
3814 FMonsterKills := 0;
3815 FDeath := 0;
3816 FSecrets := 0;
3817 if FNoRespawn then
3818 begin
3819 FSpectator := False;
3820 FGhost := False;
3821 FPhysics := True;
3822 FSpectatePlayer := -1;
3823 FNoRespawn := False;
3824 end;
3825 FLives := gGameSettings.MaxLives;
3827 SetFlag(FLAG_NONE);
3828 end;
3830 procedure TPlayer.SoftReset();
3831 begin
3832 ReleaseKeys();
3834 FDamageBuffer := 0;
3835 FIncCam := 0;
3836 FBFGFireCounter := -1;
3837 FShellTimer := -1;
3838 FPain := 0;
3839 FLastHit := 0;
3840 FLastFrag := 0;
3841 FComboEvnt := -1;
3843 SetFlag(FLAG_NONE);
3844 SetAction(A_STAND, True);
3845 end;
3847 function TPlayer.GetRespawnPoint(): Byte;
3848 var
3849 c: Byte;
3850 begin
3851 Result := 255;
3852 // Íà áóäóùåå: FSpawn - èãðîê óæå èãðàë è ïåðåðîæäàåòñÿ
3854 // Îäèíî÷íàÿ èãðà/êîîïåðàòèâ
3855 if gGameSettings.GameMode in [GM_COOP, GM_SINGLE] then
3856 begin
3857 if (Self = gPlayer1) or (Self = gPlayer2) then
3858 begin
3859 // Òî÷êà ïîÿâëåíèÿ ñâîåãî èãðîêà
3860 if Self = gPlayer1 then
3861 c := RESPAWNPOINT_PLAYER1
3862 else
3863 c := RESPAWNPOINT_PLAYER2;
3864 if g_Map_GetPointCount(c) > 0 then
3865 begin
3866 Result := c;
3867 Exit;
3868 end;
3870 // Òî÷êà ïîÿâëåíèÿ äðóãîãî èãðîêà
3871 if Self = gPlayer1 then
3872 c := RESPAWNPOINT_PLAYER2
3873 else
3874 c := RESPAWNPOINT_PLAYER1;
3875 if g_Map_GetPointCount(c) > 0 then
3876 begin
3877 Result := c;
3878 Exit;
3879 end;
3880 end else
3881 begin
3882 // Òî÷êà ïîÿâëåíèÿ ëþáîãî èãðîêà (áîòà)
3883 if Random(2) = 0 then
3884 c := RESPAWNPOINT_PLAYER1
3885 else
3886 c := RESPAWNPOINT_PLAYER2;
3887 if g_Map_GetPointCount(c) > 0 then
3888 begin
3889 Result := c;
3890 Exit;
3891 end;
3892 end;
3894 // Òî÷êà ëþáîé èç êîìàíä
3895 if Random(2) = 0 then
3896 c := RESPAWNPOINT_RED
3897 else
3898 c := RESPAWNPOINT_BLUE;
3899 if g_Map_GetPointCount(c) > 0 then
3900 begin
3901 Result := c;
3902 Exit;
3903 end;
3905 // Òî÷êà DM
3906 c := RESPAWNPOINT_DM;
3907 if g_Map_GetPointCount(c) > 0 then
3908 begin
3909 Result := c;
3910 Exit;
3911 end;
3912 end;
3914 // Ìÿñîïîâàë
3915 if gGameSettings.GameMode = GM_DM then
3916 begin
3917 // Òî÷êà DM
3918 c := RESPAWNPOINT_DM;
3919 if g_Map_GetPointCount(c) > 0 then
3920 begin
3921 Result := c;
3922 Exit;
3923 end;
3925 // Òî÷êà ïîÿâëåíèÿ ëþáîãî èãðîêà
3926 if Random(2) = 0 then
3927 c := RESPAWNPOINT_PLAYER1
3928 else
3929 c := RESPAWNPOINT_PLAYER2;
3930 if g_Map_GetPointCount(c) > 0 then
3931 begin
3932 Result := c;
3933 Exit;
3934 end;
3936 // Òî÷êà ëþáîé èç êîìàíä
3937 if Random(2) = 0 then
3938 c := RESPAWNPOINT_RED
3939 else
3940 c := RESPAWNPOINT_BLUE;
3941 if g_Map_GetPointCount(c) > 0 then
3942 begin
3943 Result := c;
3944 Exit;
3945 end;
3946 end;
3948 // Êîìàíäíûå
3949 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
3950 begin
3951 // Òî÷êà ñâîåé êîìàíäû
3952 c := RESPAWNPOINT_DM;
3953 if FTeam = TEAM_RED then
3954 c := RESPAWNPOINT_RED;
3955 if FTeam = TEAM_BLUE then
3956 c := RESPAWNPOINT_BLUE;
3957 if g_Map_GetPointCount(c) > 0 then
3958 begin
3959 Result := c;
3960 Exit;
3961 end;
3963 // Òî÷êà DM
3964 c := RESPAWNPOINT_DM;
3965 if g_Map_GetPointCount(c) > 0 then
3966 begin
3967 Result := c;
3968 Exit;
3969 end;
3971 // Òî÷êà ïîÿâëåíèÿ ëþáîãî èãðîêà
3972 if Random(2) = 0 then
3973 c := RESPAWNPOINT_PLAYER1
3974 else
3975 c := RESPAWNPOINT_PLAYER2;
3976 if g_Map_GetPointCount(c) > 0 then
3977 begin
3978 Result := c;
3979 Exit;
3980 end;
3982 // Òî÷êà äðóãîé êîìàíäû
3983 c := RESPAWNPOINT_DM;
3984 if FTeam = TEAM_RED then
3985 c := RESPAWNPOINT_BLUE;
3986 if FTeam = TEAM_BLUE then
3987 c := RESPAWNPOINT_RED;
3988 if g_Map_GetPointCount(c) > 0 then
3989 begin
3990 Result := c;
3991 Exit;
3992 end;
3993 end;
3994 end;
3996 procedure TPlayer.Respawn(Silent: Boolean; Force: Boolean = False);
3997 var
3998 RespawnPoint: TRespawnPoint;
3999 a, b, c: Byte;
4000 Anim: TAnimation;
4001 ID: DWORD;
4002 begin
4003 if not g_Game_IsServer then
4004 Exit;
4005 if FDummy then
4006 Exit;
4007 FWantsInGame := True;
4008 FJustTeleported := True;
4009 if Force then
4010 begin
4011 FTime[T_RESPAWN] := 0;
4012 FLive := False;
4013 end;
4014 FNetTime := 0;
4015 // if server changes MaxLives we gotta be ready
4016 if gGameSettings.MaxLives = 0 then FNoRespawn := False;
4018 // Åùå íåëüçÿ âîçðîäèòüñÿ:
4019 if FTime[T_RESPAWN] > gTime then
4020 Exit;
4022 // Ïðîñðàë âñå æèçíè:
4023 if FNoRespawn then
4024 begin
4025 if not FSpectator then Spectate(True);
4026 FWantsInGame := True;
4027 Exit;
4028 end;
4030 if (gGameSettings.GameType <> GT_SINGLE) and (gGameSettings.GameMode <> GM_COOP) then
4031 begin // "Ñâîÿ èãðà"
4032 // Áåðñåðê íå ñîõðàíÿåòñÿ ìåæäó óðîâíÿìè:
4033 FRulez := FRulez-[R_BERSERK];
4034 end
4035 else // "Îäèíî÷íàÿ èãðà"/"Êîîï"
4036 begin
4037 // Áåðñåðê è êëþ÷è íå ñîõðàíÿþòñÿ ìåæäó óðîâíÿìè:
4038 FRulez := FRulez-[R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE, R_BERSERK];
4039 end;
4041 // Ïîëó÷àåì òî÷êó ñïàóíà èãðîêà:
4042 c := GetRespawnPoint();
4044 ReleaseKeys();
4045 SetFlag(FLAG_NONE);
4047 // Âîñêðåøåíèå áåç îðóæèÿ:
4048 if not FLive then
4049 begin
4050 FHealth := PLAYER_HP_SOFT;
4051 FArmor := 0;
4052 FLive := True;
4053 FAir := AIR_DEF;
4054 FJetFuel := 0;
4056 for a := WEAPON_KASTET to WEAPON_SUPERPULEMET do
4057 begin
4058 FWeapon[a] := False;
4059 FReloading[a] := 0;
4060 end;
4062 FWeapon[WEAPON_PISTOL] := True;
4063 FWeapon[WEAPON_KASTET] := True;
4064 FCurrWeap := WEAPON_PISTOL;
4065 resetWeaponQueue();
4067 FModel.SetWeapon(FCurrWeap);
4069 for b := A_BULLETS to A_CELLS do
4070 FAmmo[b] := 0;
4072 FAmmo[A_BULLETS] := 50;
4074 FMaxAmmo[A_BULLETS] := 200;
4075 FMaxAmmo[A_SHELLS] := 50;
4076 FMaxAmmo[A_ROCKETS] := 50;
4077 FMaxAmmo[A_CELLS] := 300;
4079 if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then
4080 FRulez := [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE]
4081 else
4082 FRulez := [];
4083 end;
4085 // Ïîëó÷àåì êîîðäèíàòû òî÷êè âîçðîæäåíèÿ:
4086 if not g_Map_GetPoint(c, RespawnPoint) then
4087 begin
4088 g_FatalError(_lc[I_GAME_ERROR_GET_SPAWN]);
4089 Exit;
4090 end;
4092 // Óñòàíîâêà êîîðäèíàò è ñáðîñ âñåõ ïàðàìåòðîâ:
4093 FObj.X := RespawnPoint.X-PLAYER_RECT.X;
4094 FObj.Y := RespawnPoint.Y-PLAYER_RECT.Y;
4095 FObj.Vel.X := 0;
4096 FObj.Vel.Y := 0;
4097 FObj.Accel.X := 0;
4098 FObj.Accel.Y := 0;
4100 FDirection := RespawnPoint.Direction;
4101 if FDirection = D_LEFT then
4102 FAngle := 180
4103 else
4104 FAngle := 0;
4106 FIncCam := 0;
4107 FBFGFireCounter := -1;
4108 FShellTimer := -1;
4109 FPain := 0;
4110 FLastHit := 0;
4112 SetAction(A_STAND, True);
4113 FModel.Direction := FDirection;
4115 for a := Low(FTime) to High(FTime) do
4116 FTime[a] := 0;
4118 for a := Low(FMegaRulez) to High(FMegaRulez) do
4119 FMegaRulez[a] := 0;
4121 FDamageBuffer := 0;
4122 FJetpack := False;
4123 FCanJetpack := False;
4125 // Àíèìàöèÿ âîçðîæäåíèÿ:
4126 if (not gLoadGameMode) and (not Silent) then
4127 if g_Frames_Get(ID, 'FRAMES_TELEPORT') then
4128 begin
4129 Anim := TAnimation.Create(ID, False, 3);
4130 g_GFX_OnceAnim(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
4131 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, Anim);
4132 Anim.Free();
4133 end;
4135 FSpectator := False;
4136 FGhost := False;
4137 FPhysics := True;
4138 FSpectatePlayer := -1;
4139 FSpawned := True;
4141 if g_Game_IsNet then
4142 begin
4143 MH_SEND_PlayerPos(True, FUID, NET_EVERYONE);
4144 MH_SEND_PlayerStats(FUID, NET_EVERYONE);
4145 if not Silent then
4146 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
4147 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32,
4148 0, NET_GFX_TELE);
4149 end;
4150 end;
4152 procedure TPlayer.Spectate(NoMove: Boolean = False);
4153 begin
4154 if FLive then
4155 Kill(K_EXTRAHARDKILL, FUID, HIT_SOME)
4156 else if (not NoMove) then
4157 begin
4158 GameX := gMapInfo.Width div 2;
4159 GameY := gMapInfo.Height div 2;
4160 end;
4161 FXTo := GameX;
4162 FYTo := GameY;
4164 FLive := False;
4165 FSpectator := True;
4166 FGhost := True;
4167 FPhysics := False;
4168 FWantsInGame := False;
4169 FSpawned := False;
4171 if FNoRespawn then
4172 begin
4173 if Self = gPlayer1 then
4174 begin
4175 gLMSPID1 := FUID;
4176 gPlayer1 := nil;
4177 end;
4178 if Self = gPlayer2 then
4179 begin
4180 gLMSPID2 := FUID;
4181 gPlayer2 := nil;
4182 end;
4183 end;
4185 if g_Game_IsNet then
4186 MH_SEND_PlayerStats(FUID);
4187 end;
4189 procedure TPlayer.SwitchNoClip;
4190 begin
4191 if not FLive then
4192 Exit;
4193 FGhost := not FGhost;
4194 FPhysics := not FGhost;
4195 if FGhost then
4196 begin
4197 FXTo := FObj.X;
4198 FYTo := FObj.Y;
4199 end else
4200 begin
4201 FObj.Accel.X := 0;
4202 FObj.Accel.Y := 0;
4203 end;
4204 end;
4206 procedure TPlayer.Run(Direction: TDirection);
4207 var
4208 a, b: Integer;
4209 begin
4210 if MAX_RUNVEL > 8 then
4211 FlySmoke();
4213 // Áåæèì:
4214 if Direction = D_LEFT then
4215 begin
4216 if FObj.Vel.X > -MAX_RUNVEL then
4217 FObj.Vel.X := FObj.Vel.X - (MAX_RUNVEL shr 3);
4218 end
4219 else
4220 if FObj.Vel.X < MAX_RUNVEL then
4221 FObj.Vel.X := FObj.Vel.X + (MAX_RUNVEL shr 3);
4223 // Âîçìîæíî, ïèíàåì êóñêè:
4224 if (FObj.Vel.X <> 0) and (gGibs <> nil) then
4225 begin
4226 b := Abs(FObj.Vel.X);
4227 if b > 1 then b := b * (Random(8 div b) + 1);
4228 for a := 0 to High(gGibs) do
4229 if gGibs[a].Live and
4230 g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
4231 FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then
4232 // Ïèíàåì êóñêè
4233 if FObj.Vel.X < 0 then
4234 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120) // íàëåâî
4235 else
4236 g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // íàïðàâî
4237 end;
4239 SetAction(A_WALK);
4240 end;
4242 procedure TPlayer.SeeDown();
4243 begin
4244 SetAction(A_SEEDOWN);
4246 if FDirection = D_LEFT then FAngle := ANGLE_LEFTDOWN else FAngle := ANGLE_RIGHTDOWN;
4248 if FIncCam > -120 then DecMin(FIncCam, 5, -120);
4249 end;
4251 procedure TPlayer.SeeUp();
4252 begin
4253 SetAction(A_SEEUP);
4255 if FDirection = D_LEFT then FAngle := ANGLE_LEFTUP else FAngle := ANGLE_RIGHTUP;
4257 if FIncCam < 120 then IncMax(FIncCam, 5, 120);
4258 end;
4260 procedure TPlayer.SetAction(Action: Byte; Force: Boolean = False);
4261 var
4262 Prior: Byte;
4263 begin
4264 case Action of
4265 A_WALK: Prior := 3;
4266 A_DIE1: Prior := 5;
4267 A_DIE2: Prior := 5;
4268 A_ATTACK: Prior := 2;
4269 A_SEEUP: Prior := 1;
4270 A_SEEDOWN: Prior := 1;
4271 A_ATTACKUP: Prior := 2;
4272 A_ATTACKDOWN: Prior := 2;
4273 A_PAIN: Prior := 4;
4274 else Prior := 0;
4275 end;
4277 if (Prior > FActionPrior) or Force then
4278 if not ((Prior = 2) and (FCurrWeap = WEAPON_SAW)) then
4279 begin
4280 FActionPrior := Prior;
4281 FActionAnim := Action;
4282 FActionForce := Force;
4283 FActionChanged := True;
4284 end;
4286 if Action in [A_ATTACK, A_ATTACKUP, A_ATTACKDOWN] then FModel.SetFire(True);
4287 end;
4289 function TPlayer.StayOnStep(XInc, YInc: Integer): Boolean;
4290 begin
4291 Result := not g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+YInc+PLAYER_RECT.Y+PLAYER_RECT.Height-1,
4292 PLAYER_RECT.Width, 1, PANEL_STEP, False)
4293 and g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+YInc+PLAYER_RECT.Y+PLAYER_RECT.Height,
4294 PLAYER_RECT.Width, 1, PANEL_STEP, False);
4295 end;
4297 function TPlayer.TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean;
4298 var
4299 Anim: TAnimation;
4300 ID: DWORD;
4301 begin
4302 Result := False;
4304 if g_CollideLevel(X, Y, PLAYER_RECT.Width, PLAYER_RECT.Height) then
4305 begin
4306 g_Sound_PlayExAt('SOUND_GAME_NOTELEPORT', FObj.X, FObj.Y);
4307 if g_Game_IsServer and g_Game_IsNet then
4308 MH_SEND_Sound(FObj.X, FObj.Y, 'SOUND_GAME_NOTELEPORT');
4309 Exit;
4310 end;
4312 FJustTeleported := True;
4314 Anim := nil;
4315 if not silent then
4316 begin
4317 if g_Frames_Get(ID, 'FRAMES_TELEPORT') then
4318 begin
4319 Anim := TAnimation.Create(ID, False, 3);
4320 end;
4322 g_Sound_PlayExAt('SOUND_GAME_TELEPORT', FObj.X, FObj.Y);
4323 g_GFX_OnceAnim(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
4324 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, Anim);
4325 if g_Game_IsServer and g_Game_IsNet then
4326 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
4327 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 1,
4328 NET_GFX_TELE);
4329 end;
4331 FObj.X := X-PLAYER_RECT.X;
4332 FObj.Y := Y-PLAYER_RECT.Y;
4333 if FLive and FGhost then
4334 begin
4335 FXTo := FObj.X;
4336 FYTo := FObj.Y;
4337 end;
4339 if not g_Game_IsNet then
4340 begin
4341 if dir = 1 then
4342 begin
4343 SetDirection(D_LEFT);
4344 FAngle := 180;
4345 end
4346 else
4347 if dir = 2 then
4348 begin
4349 SetDirection(D_RIGHT);
4350 FAngle := 0;
4351 end
4352 else
4353 if dir = 3 then
4354 begin // îáðàòíîå
4355 if FDirection = D_RIGHT then
4356 begin
4357 SetDirection(D_LEFT);
4358 FAngle := 180;
4359 end
4360 else
4361 begin
4362 SetDirection(D_RIGHT);
4363 FAngle := 0;
4364 end;
4365 end;
4366 end;
4368 if not silent and (Anim <> nil) then
4369 begin
4370 g_GFX_OnceAnim(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
4371 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, Anim);
4372 Anim.Free();
4374 if g_Game_IsServer and g_Game_IsNet then
4375 MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
4376 FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 0,
4377 NET_GFX_TELE);
4378 end;
4380 Result := True;
4381 end;
4383 function nonz(a: Single): Single;
4384 begin
4385 if a <> 0 then
4386 Result := a
4387 else
4388 Result := 1;
4389 end;
4391 procedure TPlayer.Update();
4392 var
4393 b: Byte;
4394 i, ii, wx, wy, xd, yd, k: Integer;
4395 blockmon, headwater, dospawn: Boolean;
4396 NetServer: Boolean;
4397 AnyServer: Boolean;
4398 SetSpect: Boolean;
4399 begin
4400 NetServer := g_Game_IsNet and g_Game_IsServer;
4401 AnyServer := g_Game_IsServer;
4403 if g_Game_IsClient and (NetInterpLevel > 0) then
4404 DoLerp(NetInterpLevel + 1)
4405 else
4406 if FGhost then
4407 DoLerp(4);
4409 if NetServer then
4410 if FClientID >= 0 then
4411 begin
4412 FPing := NetClients[FClientID].Peer^.lastRoundTripTime;
4413 if NetClients[FClientID].Peer^.packetsSent > 0 then
4414 FLoss := Round(100*NetClients[FClientID].Peer^.packetsLost/NetClients[FClientID].Peer^.packetsSent)
4415 else
4416 FLoss := 0;
4417 end else
4418 begin
4419 FPing := 0;
4420 FLoss := 0;
4421 end;
4423 if FLive and (gFly or FJetpack) then
4424 FlySmoke();
4426 if FDirection = D_LEFT then
4427 FAngle := 180
4428 else
4429 FAngle := 0;
4431 if FLive and (not FGhost) then
4432 begin
4433 if FKeys[KEY_UP].Pressed then
4434 SeeUp();
4435 if FKeys[KEY_DOWN].Pressed then
4436 SeeDown();
4437 end;
4439 if (not (FKeys[KEY_UP].Pressed or FKeys[KEY_DOWN].Pressed)) and
4440 (FIncCam <> 0) then
4441 begin
4442 i := g_basic.Sign(FIncCam);
4443 FIncCam := Abs(FIncCam);
4444 DecMin(FIncCam, 5, 0);
4445 FIncCam := FIncCam*i;
4446 end;
4448 if gTime mod (GAME_TICK*2) <> 0 then
4449 begin
4450 if (FObj.Vel.X = 0) and FLive then
4451 begin
4452 if FKeys[KEY_LEFT].Pressed then
4453 Run(D_LEFT);
4454 if FKeys[KEY_RIGHT].Pressed then
4455 Run(D_RIGHT);
4456 end;
4458 if FPhysics then
4459 g_Obj_Move(@FObj, True, True, True);
4461 Exit;
4462 end;
4464 FActionChanged := False;
4466 if FLive then
4467 begin
4468 // Let alive player do some actions
4469 if FKeys[KEY_LEFT].Pressed then Run(D_LEFT);
4470 if FKeys[KEY_RIGHT].Pressed then Run(D_RIGHT);
4471 if FKeys[KEY_NEXTWEAPON].Pressed and AnyServer then NextWeapon();
4472 if FKeys[KEY_PREVWEAPON].Pressed and AnyServer then PrevWeapon();
4473 if FKeys[KEY_FIRE].Pressed and AnyServer then Fire();
4474 if FKeys[KEY_OPEN].Pressed and AnyServer then Use();
4475 if FKeys[KEY_JUMP].Pressed then Jump()
4476 else
4477 begin
4478 if AnyServer and FJetpack then
4479 begin
4480 FJetpack := False;
4481 JetpackOff;
4482 if NetServer then MH_SEND_PlayerStats(FUID);
4483 end;
4484 FCanJetpack := True;
4485 end;
4486 end
4487 else // Dead
4488 begin
4489 dospawn := False;
4490 if not FGhost then
4491 for k := Low(FKeys) to KEY_CHAT-1 do
4492 begin
4493 if FKeys[k].Pressed then
4494 begin
4495 dospawn := True;
4496 break;
4497 end;
4498 end;
4499 if dospawn then
4500 begin
4501 if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
4502 Respawn(False)
4503 else // Single
4504 if (FTime[T_RESPAWN] <= gTime) and
4505 gGameOn and (not FLive) then
4506 begin
4507 if (g_Player_GetCount() > 1) then
4508 Respawn(False)
4509 else
4510 begin
4511 gExit := EXIT_RESTART;
4512 Exit;
4513 end;
4514 end;
4515 end;
4516 // Dead spectator actions
4517 if FGhost then
4518 begin
4519 if FKeys[KEY_OPEN].Pressed and AnyServer then Fire();
4520 if FKeys[KEY_FIRE].Pressed and AnyServer then
4521 begin
4522 if FSpectator then
4523 begin
4524 if (FSpectatePlayer >= High(gPlayers)) then
4525 FSpectatePlayer := -1
4526 else
4527 begin
4528 SetSpect := False;
4529 for I := FSpectatePlayer + 1 to High(gPlayers) do
4530 if gPlayers[I] <> nil then
4531 if gPlayers[I].Live then
4532 if gPlayers[I].UID <> FUID then
4533 begin
4534 FSpectatePlayer := I;
4535 SetSpect := True;
4536 break;
4537 end;
4539 if not SetSpect then FSpectatePlayer := -1;
4540 end;
4542 ReleaseKeys;
4543 end;
4544 end;
4545 end;
4546 end;
4547 // No clipping
4548 if FGhost then
4549 begin
4550 if FKeys[KEY_UP].Pressed or FKeys[KEY_JUMP].Pressed then
4551 begin
4552 FYTo := FObj.Y - 32;
4553 FSpectatePlayer := -1;
4554 end;
4555 if FKeys[KEY_DOWN].Pressed then
4556 begin
4557 FYTo := FObj.Y + 32;
4558 FSpectatePlayer := -1;
4559 end;
4560 if FKeys[KEY_LEFT].Pressed then
4561 begin
4562 FXTo := FObj.X - 32;
4563 FSpectatePlayer := -1;
4564 end;
4565 if FKeys[KEY_RIGHT].Pressed then
4566 begin
4567 FXTo := FObj.X + 32;
4568 FSpectatePlayer := -1;
4569 end;
4571 if (FXTo < -64) then
4572 FXTo := -64
4573 else if (FXTo > gMapInfo.Width + 32) then
4574 FXTo := gMapInfo.Width + 32;
4575 if (FYTo < -72) then
4576 FYTo := -72
4577 else if (FYTo > gMapInfo.Height + 32) then
4578 FYTo := gMapInfo.Height + 32;
4579 end;
4581 if FPhysics then
4582 g_Obj_Move(@FObj, True, True, True)
4583 else
4584 begin
4585 FObj.Vel.X := 0;
4586 FObj.Vel.Y := 0;
4587 if FSpectator then
4588 if (FSpectatePlayer <= High(gPlayers)) and (FSpectatePlayer >= 0) then
4589 if gPlayers[FSpectatePlayer] <> nil then
4590 if gPlayers[FSpectatePlayer].Live then
4591 begin
4592 FXTo := gPlayers[FSpectatePlayer].GameX;
4593 FYTo := gPlayers[FSpectatePlayer].GameY;
4594 end;
4595 end;
4597 blockmon := g_Map_CollidePanel(FObj.X+PLAYER_HEADRECT.X, FObj.Y+PLAYER_HEADRECT.Y,
4598 PLAYER_HEADRECT.Width, PLAYER_HEADRECT.Height,
4599 PANEL_BLOCKMON, True);
4600 headwater := HeadInLiquid(0, 0);
4602 // Ñîïðîòèâëåíèå âîçäóõà:
4603 if (not FLive) or not (FKeys[KEY_LEFT].Pressed or FKeys[KEY_RIGHT].Pressed) then
4604 if FObj.Vel.X <> 0 then
4605 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
4607 if (FLastHit = HIT_TRAP) and (FPain > 90) then FPain := 90;
4608 DecMin(FPain, 5, 0);
4609 DecMin(FPickup, 1, 0);
4611 if FLive and (FObj.Y > gMapInfo.Height+128) and AnyServer then
4612 begin
4613 // Îáíóëèòü äåéñòâèÿ ïðèìî÷åê, ÷òîáû ôîí ïðîïàë
4614 FMegaRulez[MR_SUIT] := 0;
4615 FMegaRulez[MR_INVUL] := 0;
4616 FMegaRulez[MR_INVIS] := 0;
4617 Kill(K_FALLKILL, 0, HIT_FALL);
4618 end;
4620 i := 9;
4622 if FLive then
4623 begin
4624 if FCurrWeap = WEAPON_SAW then
4625 if not (FSawSound.IsPlaying() or FSawSoundHit.IsPlaying() or
4626 FSawSoundSelect.IsPlaying()) then
4627 FSawSoundIdle.PlayAt(FObj.X, FObj.Y);
4629 if FJetpack then
4630 if (not FJetSoundFly.IsPlaying()) and (not FJetSoundOn.IsPlaying()) and
4631 (not FJetSoundOff.IsPlaying()) then
4632 begin
4633 FJetSoundFly.SetPosition(0);
4634 FJetSoundFly.PlayAt(FObj.X, FObj.Y);
4635 end;
4637 for b := WEAPON_KASTET to WEAPON_SUPERPULEMET do
4638 if FReloading[b] > 0 then
4639 if FNoReload then
4640 FReloading[b] := 0
4641 else
4642 Dec(FReloading[b]);
4644 if FShellTimer > -1 then
4645 if FShellTimer = 0 then
4646 begin
4647 if FShellType = SHELL_SHELL then
4648 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
4649 GameVelX, GameVelY-2, SHELL_SHELL)
4650 else if FShellType = SHELL_DBLSHELL then
4651 begin
4652 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
4653 GameVelX+1, GameVelY-2, SHELL_SHELL);
4654 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
4655 GameVelX-1, GameVelY-2, SHELL_SHELL);
4656 end;
4657 FShellTimer := -1;
4658 end else Dec(FShellTimer);
4660 if (FBFGFireCounter > -1) then
4661 if FBFGFireCounter = 0 then
4662 begin
4663 if AnyServer then
4664 begin
4665 wx := FObj.X+WEAPONPOINT[FDirection].X;
4666 wy := FObj.Y+WEAPONPOINT[FDirection].Y;
4667 xd := wx+IfThen(FDirection = D_LEFT, -30, 30);
4668 yd := wy+firediry();
4669 g_Weapon_bfgshot(wx, wy, xd, yd, FUID);
4670 if NetServer then MH_SEND_PlayerFire(FUID, WEAPON_BFG, wx, wy, xd, yd);
4671 if (FAngle = 0) or (FAngle = 180) then SetAction(A_ATTACK)
4672 else if (FAngle = ANGLE_LEFTDOWN) or (FAngle = ANGLE_RIGHTDOWN) then SetAction(A_ATTACKDOWN)
4673 else if (FAngle = ANGLE_LEFTUP) or (FAngle = ANGLE_RIGHTUP) then SetAction(A_ATTACKUP);
4674 end;
4676 FReloading[WEAPON_BFG] := WEAPON_RELOAD[WEAPON_BFG];
4677 FBFGFireCounter := -1;
4678 end else
4679 if FNoReload then
4680 FBFGFireCounter := 0
4681 else
4682 Dec(FBFGFireCounter);
4684 if (FMegaRulez[MR_SUIT] < gTime) and AnyServer then
4685 begin
4686 b := g_GetAcidHit(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width, PLAYER_RECT.Height);
4688 if (b > 0) and (gTime mod (15*GAME_TICK) = 0) then Damage(b, 0, 0, 0, HIT_ACID);
4689 end;
4691 if (headwater or blockmon) then
4692 begin
4693 Dec(FAir);
4695 if FAir < -9 then
4696 begin
4697 if AnyServer then Damage(10, 0, 0, 0, HIT_WATER);
4698 FAir := 0;
4699 end
4700 else if (FAir mod 31 = 0) and not blockmon then
4701 begin
4702 g_GFX_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2), FObj.Y+PLAYER_RECT.Y-4, 5+Random(6), 8, 4);
4703 if Random(2) = 0 then
4704 g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
4705 else
4706 g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
4707 end;
4708 end else if FAir < AIR_DEF then
4709 FAir := AIR_DEF;
4711 if FDamageBuffer > 0 then
4712 begin
4713 if FDamageBuffer >= 9 then
4714 begin
4715 SetAction(A_PAIN);
4717 if FDamageBuffer < 30 then i := 9
4718 else if FDamageBuffer < 100 then i := 18
4719 else i := 27;
4720 end;
4722 ii := Round(FDamageBuffer*FHealth / nonz(FArmor*(3/4)+FHealth));
4723 FArmor := FArmor-(FDamageBuffer-ii);
4724 FHealth := FHealth-ii;
4725 if FArmor < 0 then
4726 begin
4727 FHealth := FHealth+FArmor;
4728 FArmor := 0;
4729 end;
4731 if AnyServer then
4732 if FHealth <= 0 then
4733 if FHealth > -30 then Kill(K_SIMPLEKILL, FLastSpawnerUID, FLastHit)
4734 else if FHealth > -50 then Kill(K_HARDKILL, FLastSpawnerUID, FLastHit)
4735 else Kill(K_EXTRAHARDKILL, FLastSpawnerUID, FLastHit);
4737 if FLive then
4738 begin
4739 if FDamageBuffer <= 20 then FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y)
4740 else if FDamageBuffer <= 55 then FModel.PlaySound(MODELSOUND_PAIN, 2, FObj.X, FObj.Y)
4741 else if FDamageBuffer <= 120 then FModel.PlaySound(MODELSOUND_PAIN, 3, FObj.X, FObj.Y)
4742 else FModel.PlaySound(MODELSOUND_PAIN, 4, FObj.X, FObj.Y);
4743 end;
4745 FDamageBuffer := 0;
4746 end;
4748 {CollideItem();}
4749 end; // if FLive then ...
4751 if (FActionAnim = A_PAIN) and (FModel.Animation <> A_PAIN) then
4752 begin
4753 FModel.ChangeAnimation(FActionAnim, FActionForce);
4754 FModel.GetCurrentAnimation.MinLength := i;
4755 FModel.GetCurrentAnimationMask.MinLength := i;
4756 end else FModel.ChangeAnimation(FActionAnim, FActionForce and (FModel.Animation <> A_STAND));
4758 if (FModel.GetCurrentAnimation.Played or ((not FActionChanged) and (FModel.Animation = A_WALK)))
4759 then SetAction(A_STAND, True);
4761 if not ((FModel.Animation = A_WALK) and (Abs(FObj.Vel.X) < 4) and not FModel.Fire) then FModel.Update;
4763 for b := Low(FKeys) to High(FKeys) do
4764 if FKeys[b].Time = 0 then FKeys[b].Pressed := False else Dec(FKeys[b].Time);
4765 end;
4767 function TPlayer.Collide(X, Y: Integer; Width, Height: Word): Boolean;
4768 begin
4769 Result := g_Collide(FObj.X+PLAYER_RECT.X,
4770 FObj.Y+PLAYER_RECT.Y,
4771 PLAYER_RECT.Width,
4772 PLAYER_RECT.Height,
4773 X, Y,
4774 Width, Height);
4775 end;
4777 function TPlayer.Collide(Panel: TPanel): Boolean;
4778 begin
4779 Result := g_Collide(FObj.X+PLAYER_RECT.X,
4780 FObj.Y+PLAYER_RECT.Y,
4781 PLAYER_RECT.Width,
4782 PLAYER_RECT.Height,
4783 Panel.X, Panel.Y,
4784 Panel.Width, Panel.Height);
4785 end;
4787 function TPlayer.Collide(X, Y: Integer): Boolean;
4788 begin
4789 X := X-FObj.X-PLAYER_RECT.X;
4790 Y := Y-FObj.Y-PLAYER_RECT.Y;
4791 Result := (x >= 0) and (x <= PLAYER_RECT.Width) and
4792 (y >= 0) and (y <= PLAYER_RECT.Height);
4793 end;
4795 function g_Player_ValidName(Name: string): Boolean;
4796 var
4797 a: Integer;
4798 begin
4799 Result := True;
4801 if gPlayers = nil then Exit;
4803 for a := 0 to High(gPlayers) do
4804 if gPlayers[a] <> nil then
4805 if LowerCase(Name) = LowerCase(gPlayers[a].FName) then
4806 begin
4807 Result := False;
4808 Exit;
4809 end;
4810 end;
4812 procedure TPlayer.SetDirection(Direction: TDirection);
4813 var
4814 d: TDirection;
4815 begin
4816 d := FModel.Direction;
4818 FModel.Direction := Direction;
4819 if d <> Direction then FModel.ChangeAnimation(FModel.Animation, True);
4821 FDirection := Direction;
4822 end;
4824 function TPlayer.GetKeys(): Byte;
4825 begin
4826 Result := 0;
4828 if R_KEY_RED in FRulez then Result := KEY_RED;
4829 if R_KEY_GREEN in FRulez then Result := Result or KEY_GREEN;
4830 if R_KEY_BLUE in FRulez then Result := Result or KEY_BLUE;
4832 if FTeam = TEAM_RED then Result := Result or KEY_REDTEAM;
4833 if FTeam = TEAM_BLUE then Result := Result or KEY_BLUETEAM;
4834 end;
4836 procedure TPlayer.Use();
4837 var
4838 a: Integer;
4839 begin
4840 if FTime[T_USE] > gTime then Exit;
4842 g_Triggers_PressR(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width,
4843 PLAYER_RECT.Height, FUID, ACTIVATE_PLAYERPRESS);
4845 for a := 0 to High(gPlayers) do
4846 if (gPlayers[a] <> nil) and (gPlayers[a] <> Self) and
4847 gPlayers[a].Live and SameTeam(FUID, gPlayers[a].FUID) and
4848 g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
4849 FObj.Rect.Width, FObj.Rect.Height, @gPlayers[a].FObj) then
4850 begin
4851 gPlayers[a].Touch();
4852 if g_Game_IsNet and g_Game_IsServer then
4853 MH_SEND_GameEvent(NET_EV_PLAYER_TOUCH, gPlayers[a].FUID);
4854 end;
4856 FTime[T_USE] := gTime+120;
4857 end;
4859 procedure TPlayer.NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1);
4860 var
4861 Obj: TObj;
4862 F: Boolean;
4863 WX, WY, XD, YD: Integer;
4864 begin
4865 F := False;
4866 WX := X;
4867 WY := Y;
4868 XD := AX;
4869 YD := AY;
4871 case FCurrWeap of
4872 WEAPON_KASTET:
4873 begin
4874 if R_BERSERK in FRulez then
4875 begin
4876 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
4877 obj.X := FObj.X+FObj.Rect.X;
4878 obj.Y := FObj.Y+FObj.Rect.Y;
4879 obj.rect.X := 0;
4880 obj.rect.Y := 0;
4881 obj.rect.Width := 39;
4882 obj.rect.Height := 52;
4883 obj.Vel.X := (xd-wx) div 2;
4884 obj.Vel.Y := (yd-wy) div 2;
4885 obj.Accel.X := xd-wx;
4886 obj.Accel.y := yd-wy;
4888 if g_Weapon_Hit(@obj, 50, FUID, HIT_SOME) <> 0 then
4889 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj.X, FObj.Y)
4890 else
4891 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj.X, FObj.Y);
4893 if gFlash = 1 then
4894 if FPain < 50 then
4895 FPain := min(FPain + 25, 50);
4896 end else
4897 g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 3, FUID);
4898 end;
4900 WEAPON_SAW:
4901 begin
4902 if g_Weapon_chainsaw(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
4903 IfThen(gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF], 9, 3), FUID) <> 0 then
4904 begin
4905 FSawSoundSelect.Stop();
4906 FSawSound.Stop();
4907 FSawSoundHit.PlayAt(FObj.X, FObj.Y);
4908 end
4909 else if not FSawSoundHit.IsPlaying() then
4910 begin
4911 FSawSoundSelect.Stop();
4912 FSawSound.PlayAt(FObj.X, FObj.Y);
4913 end;
4914 f := True;
4915 end;
4917 WEAPON_PISTOL:
4918 begin
4919 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', GameX, Gamey);
4920 FFireAngle := FAngle;
4921 f := True;
4922 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
4923 GameVelX, GameVelY-2, SHELL_BULLET);
4924 end;
4926 WEAPON_SHOTGUN1:
4927 begin
4928 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex, Gamey);
4929 FFireAngle := FAngle;
4930 f := True;
4931 FShellTimer := 10;
4932 FShellType := SHELL_SHELL;
4933 end;
4935 WEAPON_SHOTGUN2:
4936 begin
4937 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', Gamex, Gamey);
4938 FFireAngle := FAngle;
4939 f := True;
4940 FShellTimer := 13;
4941 FShellType := SHELL_DBLSHELL;
4942 end;
4944 WEAPON_CHAINGUN:
4945 begin
4946 g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', Gamex, Gamey);
4947 FFireAngle := FAngle;
4948 f := True;
4949 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
4950 GameVelX, GameVelY-2, SHELL_BULLET);
4951 end;
4953 WEAPON_ROCKETLAUNCHER:
4954 begin
4955 g_Weapon_Rocket(wx, wy, xd, yd, FUID, WID);
4956 FFireAngle := FAngle;
4957 f := True;
4958 end;
4960 WEAPON_PLASMA:
4961 begin
4962 g_Weapon_Plasma(wx, wy, xd, yd, FUID, WID);
4963 FFireAngle := FAngle;
4964 f := True;
4965 end;
4967 WEAPON_BFG:
4968 begin
4969 g_Weapon_BFGShot(wx, wy, xd, yd, FUID, WID);
4970 FFireAngle := FAngle;
4971 f := True;
4972 end;
4974 WEAPON_SUPERPULEMET:
4975 begin
4976 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex, Gamey);
4977 FFireAngle := FAngle;
4978 f := True;
4979 g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
4980 GameVelX, GameVelY-2, SHELL_SHELL);
4981 end;
4982 end;
4984 if not f then Exit;
4986 if (FAngle = 0) or (FAngle = 180) then SetAction(A_ATTACK)
4987 else if (FAngle = ANGLE_LEFTDOWN) or (FAngle = ANGLE_RIGHTDOWN) then SetAction(A_ATTACKDOWN)
4988 else if (FAngle = ANGLE_LEFTUP) or (FAngle = ANGLE_RIGHTUP) then SetAction(A_ATTACKUP);
4989 end;
4991 procedure TPlayer.DoLerp(Level: Integer = 2);
4992 begin
4993 if FObj.X <> FXTo then FObj.X := Lerp(FObj.X, FXTo, Level);
4994 if FObj.Y <> FYTo then FObj.Y := Lerp(FObj.Y, FYTo, Level);
4995 end;
4997 procedure TPlayer.SetLerp(XTo, YTo: Integer);
4998 var
4999 AX, AY: Integer;
5000 begin
5001 if NetInterpLevel < 1 then
5002 begin
5003 FObj.X := XTo;
5004 FObj.Y := YTo;
5005 end
5006 else
5007 begin
5008 FXTo := XTo;
5009 FYTo := YTo;
5011 AX := Abs(FXTo - FObj.X);
5012 AY := Abs(FYTo - FObj.Y);
5013 if (AX > 32) or (AX <= NetInterpLevel) then
5014 FObj.X := FXTo;
5015 if (AY > 32) or (AY <= NetInterpLevel) then
5016 FObj.Y := FYTo;
5017 end;
5018 end;
5020 function TPlayer.FullInLift(XInc, YInc: Integer): Integer;
5021 begin
5022 if g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
5023 PLAYER_RECT.Width, PLAYER_RECT.Height-8,
5024 PANEL_LIFTUP, False) then Result := -1
5025 else
5026 if g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
5027 PLAYER_RECT.Width, PLAYER_RECT.Height-8,
5028 PANEL_LIFTDOWN, False) then Result := 1
5029 else Result := 0;
5030 end;
5032 function TPlayer.GetFlag(Flag: Byte): Boolean;
5033 var
5034 s, ts: String;
5035 evtype: Byte;
5036 begin
5037 Result := False;
5039 if Flag = FLAG_NONE then
5040 Exit;
5042 if not g_Game_IsServer then Exit;
5044 // Ïðèíåñ ÷óæîé ôëàã íà ñâîþ áàçó:
5045 if (Flag = FTeam) and
5046 (gFlags[Flag].State = FLAG_STATE_NORMAL) and
5047 (FFlag <> FLAG_NONE) then
5048 begin
5049 if FFlag = FLAG_RED then
5050 s := _lc[I_PLAYER_FLAG_RED]
5051 else
5052 s := _lc[I_PLAYER_FLAG_BLUE];
5054 evtype := FLAG_STATE_SCORED;
5056 ts := Format('%.4d', [gFlags[FFlag].CaptureTime]);
5057 Insert('.', ts, Length(ts) + 1 - 3);
5058 g_Console_Add(Format(_lc[I_PLAYER_FLAG_CAPTURE], [FName, s, ts]), True);
5060 g_Map_ResetFlag(FFlag);
5061 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_CAPTURE], [AnsiUpperCase(s)]), 144);
5063 gTeamStat[FTeam].Goals := gTeamStat[FTeam].Goals + 1;
5065 Result := True;
5066 if g_Game_IsNet then
5067 begin
5068 MH_SEND_FlagEvent(evtype, FFlag, FUID, False);
5069 MH_SEND_GameStats;
5070 end;
5072 gFlags[FFlag].CaptureTime := 0;
5073 SetFlag(FLAG_NONE);
5074 Exit;
5075 end;
5077 // Ïîäîáðàë ñâîé ôëàã - âåðíóë åãî íà áàçó:
5078 if (Flag = FTeam) and
5079 (gFlags[Flag].State = FLAG_STATE_DROPPED) then
5080 begin
5081 if Flag = FLAG_RED then
5082 s := _lc[I_PLAYER_FLAG_RED]
5083 else
5084 s := _lc[I_PLAYER_FLAG_BLUE];
5086 evtype := FLAG_STATE_RETURNED;
5087 gFlags[Flag].CaptureTime := 0;
5089 g_Console_Add(Format(_lc[I_PLAYER_FLAG_RETURN], [FName, s]), True);
5091 g_Map_ResetFlag(Flag);
5092 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
5094 Result := True;
5095 if g_Game_IsNet then
5096 begin
5097 MH_SEND_FlagEvent(evtype, Flag, FUID, False);
5098 MH_SEND_GameStats;
5099 end;
5100 Exit;
5101 end;
5103 // Ïîäîáðàë ÷óæîé ôëàã:
5104 if (Flag <> FTeam) and (FTime[T_FLAGCAP] <= gTime) then
5105 begin
5106 SetFlag(Flag);
5108 if Flag = FLAG_RED then
5109 s := _lc[I_PLAYER_FLAG_RED]
5110 else
5111 s := _lc[I_PLAYER_FLAG_BLUE];
5113 evtype := FLAG_STATE_CAPTURED;
5115 g_Console_Add(Format(_lc[I_PLAYER_FLAG_GET], [FName, s]), True);
5117 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_GET], [AnsiUpperCase(s)]), 144);
5119 gFlags[Flag].State := FLAG_STATE_CAPTURED;
5121 Result := True;
5122 if g_Game_IsNet then
5123 begin
5124 MH_SEND_FlagEvent(evtype, Flag, FUID, False);
5125 MH_SEND_GameStats;
5126 end;
5127 end;
5128 end;
5130 procedure TPlayer.SetFlag(Flag: Byte);
5131 begin
5132 FFlag := Flag;
5133 if FModel <> nil then
5134 FModel.SetFlag(FFlag);
5135 end;
5137 function TPlayer.DropFlag(): Boolean;
5138 var
5139 s: String;
5140 begin
5141 Result := False;
5142 if (not g_Game_IsServer) or (FFlag = FLAG_NONE) then
5143 Exit;
5144 FTime[T_FLAGCAP] := gTime + 2000;
5145 with gFlags[FFlag] do
5146 begin
5147 Obj.X := FObj.X;
5148 Obj.Y := FObj.Y;
5149 Direction := FDirection;
5150 State := FLAG_STATE_DROPPED;
5151 Count := FLAG_TIME;
5152 g_Obj_Push(@Obj, (FObj.Vel.X div 2)-2+Random(5),
5153 (FObj.Vel.Y div 2)-2+Random(5));
5155 if FFlag = FLAG_RED then
5156 s := _lc[I_PLAYER_FLAG_RED]
5157 else
5158 s := _lc[I_PLAYER_FLAG_BLUE];
5160 g_Console_Add(Format(_lc[I_PLAYER_FLAG_DROP], [FName, s]), True);
5161 g_Game_Message(Format(_lc[I_MESSAGE_FLAG_DROP], [AnsiUpperCase(s)]), 144);
5163 if g_Game_IsNet then
5164 MH_SEND_FlagEvent(FLAG_STATE_DROPPED, Flag, FUID, False);
5165 end;
5166 SetFlag(FLAG_NONE);
5167 Result := True;
5168 end;
5170 procedure TPlayer.GetSecret();
5171 begin
5172 Inc(FSecrets);
5173 end;
5175 procedure TPlayer.PressKey(Key: Byte; Time: Word = 1);
5176 begin
5177 Assert(Key <= High(FKeys));
5179 FKeys[Key].Pressed := True;
5180 FKeys[Key].Time := Time;
5181 end;
5183 function TPlayer.IsKeyPressed(K: Byte): Boolean;
5184 begin
5185 Result := FKeys[K].Pressed;
5186 end;
5188 procedure TPlayer.ReleaseKeys();
5189 var
5190 a: Integer;
5191 begin
5192 for a := Low(FKeys) to High(FKeys) do
5193 begin
5194 FKeys[a].Pressed := False;
5195 FKeys[a].Time := 0;
5196 end;
5197 end;
5199 procedure TPlayer.OnDamage(Angle: SmallInt);
5200 begin
5201 end;
5203 function TPlayer.firediry(): Integer;
5204 begin
5205 if FKeys[KEY_UP].Pressed then Result := -42
5206 else if FKeys[KEY_DOWN].Pressed then Result := 19
5207 else Result := 0;
5208 end;
5210 procedure TPlayer.RememberState();
5211 var
5212 i: Integer;
5213 begin
5214 FSavedState.Health := FHealth;
5215 FSavedState.Armor := FArmor;
5216 FSavedState.Air := FAir;
5217 FSavedState.JetFuel := FJetFuel;
5218 FSavedState.CurrWeap := FCurrWeap;
5219 FSavedState.NextWeap := FNextWeap;
5220 FSavedState.NextWeapDelay := FNextWeapDelay;
5222 for i := 0 to 3 do
5223 FSavedState.Ammo[i] := FAmmo[i];
5224 for i := 0 to 3 do
5225 FSavedState.MaxAmmo[i] := FMaxAmmo[i];
5227 FSavedState.Rulez := FRulez;
5228 FSavedState.WaitRecall := True;
5229 end;
5231 procedure TPlayer.RecallState();
5232 var
5233 i: Integer;
5234 begin
5235 if not FSavedState.WaitRecall then Exit;
5237 FHealth := FSavedState.Health;
5238 FArmor := FSavedState.Armor;
5239 FAir := FSavedState.Air;
5240 FJetFuel := FSavedState.JetFuel;
5241 FCurrWeap := FSavedState.CurrWeap;
5242 FNextWeap := FSavedState.NextWeap;
5243 FNextWeapDelay := FSavedState.NextWeapDelay;
5245 for i := 0 to 3 do
5246 FAmmo[i] := FSavedState.Ammo[i];
5247 for i := 0 to 3 do
5248 FMaxAmmo[i] := FSavedState.MaxAmmo[i];
5250 FRulez := FSavedState.Rulez;
5251 FSavedState.WaitRecall := False;
5253 if gGameSettings.GameType = GT_SERVER then
5254 MH_SEND_PlayerStats(FUID);
5255 end;
5257 procedure TPlayer.SaveState(var Mem: TBinMemoryWriter);
5258 var
5259 i: Integer;
5260 sig: DWORD;
5261 str: String;
5262 b: Byte;
5263 begin
5264 if FIamBot then
5265 i := 512
5266 else
5267 i := 256;
5269 Mem := TBinMemoryWriter.Create(i);
5271 // Ñèãíàòóðà èãðîêà:
5272 sig := PLAYER_SIGNATURE; // 'PLYR'
5273 Mem.WriteDWORD(sig);
5274 // Áîò èëè ÷åëîâåê:
5275 Mem.WriteBoolean(FIamBot);
5276 // UID èãðîêà:
5277 Mem.WriteWord(FUID);
5278 // Èìÿ èãðîêà:
5279 Mem.WriteString(FName, 32);
5280 // Êîìàíäà:
5281 Mem.WriteByte(FTeam);
5282 // Æèâ ëè:
5283 Mem.WriteBoolean(FLive);
5284 // Èçðàñõîäîâàë ëè âñå æèçíè:
5285 Mem.WriteBoolean(FNoRespawn);
5286 // Íàïðàâëåíèå:
5287 if FDirection = D_LEFT then
5288 b := 1
5289 else // D_RIGHT
5290 b := 2;
5291 Mem.WriteByte(b);
5292 // Çäîðîâüå:
5293 Mem.WriteInt(FHealth);
5294 // Æèçíè:
5295 Mem.WriteByte(FLives);
5296 // Áðîíÿ:
5297 Mem.WriteInt(FArmor);
5298 // Çàïàñ âîçäóõà:
5299 Mem.WriteInt(FAir);
5300 // Çàïàñ ãîðþ÷åãî:
5301 Mem.WriteInt(FJetFuel);
5302 // Áîëü:
5303 Mem.WriteInt(FPain);
5304 // Óáèë:
5305 Mem.WriteInt(FKills);
5306 // Óáèë ìîíñòðîâ:
5307 Mem.WriteInt(FMonsterKills);
5308 // Ôðàãîâ:
5309 Mem.WriteInt(FFrags);
5310 // Ôðàãîâ ïîäðÿä:
5311 Mem.WriteByte(FFragCombo);
5312 // Âðåìÿ ïîñëåäíåãî ôðàãà:
5313 Mem.WriteDWORD(FLastFrag);
5314 // Ñìåðòåé:
5315 Mem.WriteInt(FDeath);
5316 // Êàêîé ôëàã íåñåò:
5317 Mem.WriteByte(FFlag);
5318 // Íàøåë ñåêðåòîâ:
5319 Mem.WriteInt(FSecrets);
5320 // Òåêóùåå îðóæèå:
5321 Mem.WriteByte(FCurrWeap);
5322 // Æåëàåìîå îðóæèå:
5323 Mem.WriteWord(FNextWeap);
5324 // ...è ïàóçà
5325 Mem.WriteByte(FNextWeapDelay);
5326 // Âðåìÿ çàðÿäêè BFG:
5327 Mem.WriteSmallInt(FBFGFireCounter);
5328 // Áóôåð óðîíà:
5329 Mem.WriteInt(FDamageBuffer);
5330 // Ïîñëåäíèé óäàðèâøèé:
5331 Mem.WriteWord(FLastSpawnerUID);
5332 // Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà:
5333 Mem.WriteByte(FLastHit);
5334 // Îáúåêò èãðîêà:
5335 Obj_SaveState(@FObj, Mem);
5336 // Òåêóùåå êîëè÷åñòâî ïàòðîíîâ:
5337 for i := A_BULLETS to A_CELLS do
5338 Mem.WriteWord(FAmmo[i]);
5339 // Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ:
5340 for i := A_BULLETS to A_CELLS do
5341 Mem.WriteWord(FMaxAmmo[i]);
5342 // Íàëè÷èå îðóæèÿ:
5343 for i := WEAPON_KASTET to WEAPON_SUPERPULEMET do
5344 Mem.WriteBoolean(FWeapon[i]);
5345 // Âðåìÿ ïåðåçàðÿäêè îðóæèÿ:
5346 for i := WEAPON_KASTET to WEAPON_SUPERPULEMET do
5347 Mem.WriteWord(FReloading[i]);
5348 // Íàëè÷èå ðþêçàêà:
5349 if R_ITEM_BACKPACK in FRulez then
5350 b := 1
5351 else
5352 b := 0;
5353 Mem.WriteByte(b);
5354 // Íàëè÷èå êðàñíîãî êëþ÷à:
5355 if R_KEY_RED in FRulez then
5356 b := 1
5357 else
5358 b := 0;
5359 Mem.WriteByte(b);
5360 // Íàëè÷èå çåëåíîãî êëþ÷à:
5361 if R_KEY_GREEN in FRulez then
5362 b := 1
5363 else
5364 b := 0;
5365 Mem.WriteByte(b);
5366 // Íàëè÷èå ñèíåãî êëþ÷à:
5367 if R_KEY_BLUE in FRulez then
5368 b := 1
5369 else
5370 b := 0;
5371 Mem.WriteByte(b);
5372 // Íàëè÷èå áåðñåðêà:
5373 if R_BERSERK in FRulez then
5374 b := 1
5375 else
5376 b := 0;
5377 Mem.WriteByte(b);
5378 // Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ:
5379 for i := MR_SUIT to MR_MAX do
5380 Mem.WriteDWORD(FMegaRulez[i]);
5381 // Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà:
5382 for i := T_RESPAWN to T_FLAGCAP do
5383 Mem.WriteDWORD(FTime[i]);
5384 // Íàçâàíèå ìîäåëè:
5385 str := FModel.Name;
5386 Mem.WriteString(str);
5387 // Öâåò ìîäåëè:
5388 b := FColor.R;
5389 Mem.WriteByte(b);
5390 b := FColor.G;
5391 Mem.WriteByte(b);
5392 b := FColor.B;
5393 Mem.WriteByte(b);
5394 end;
5396 procedure TPlayer.LoadState(var Mem: TBinMemoryReader);
5397 var
5398 i: Integer;
5399 sig: DWORD;
5400 str: String;
5401 b: Byte;
5402 begin
5403 if Mem = nil then
5404 Exit;
5406 // Ñèãíàòóðà èãðîêà:
5407 Mem.ReadDWORD(sig);
5408 if sig <> PLAYER_SIGNATURE then // 'PLYR'
5409 begin
5410 raise EBinSizeError.Create('TPlayer.LoadState: Wrong Player Signature');
5411 end;
5412 // Áîò èëè ÷åëîâåê:
5413 Mem.ReadBoolean(FIamBot);
5414 // UID èãðîêà:
5415 Mem.ReadWord(FUID);
5416 // Èìÿ èãðîêà:
5417 Mem.ReadString(str);
5418 if (Self <> gPlayer1) and (Self <> gPlayer2) then
5419 FName := str;
5420 // Êîìàíäà:
5421 Mem.ReadByte(FTeam);
5422 // Æèâ ëè:
5423 Mem.ReadBoolean(FLive);
5424 // Èçðàñõîäîâàë ëè âñå æèçíè:
5425 Mem.ReadBoolean(FNoRespawn);
5426 // Íàïðàâëåíèå:
5427 Mem.ReadByte(b);
5428 if b = 1 then
5429 FDirection := D_LEFT
5430 else // b = 2
5431 FDirection := D_RIGHT;
5432 // Çäîðîâüå:
5433 Mem.ReadInt(FHealth);
5434 // Æèçíè:
5435 Mem.ReadByte(FLives);
5436 // Áðîíÿ:
5437 Mem.ReadInt(FArmor);
5438 // Çàïàñ âîçäóõà:
5439 Mem.ReadInt(FAir);
5440 // Çàïàñ ãîðþ÷åãî:
5441 Mem.ReadInt(FJetFuel);
5442 // Áîëü:
5443 Mem.ReadInt(FPain);
5444 // Óáèë:
5445 Mem.ReadInt(FKills);
5446 // Óáèë ìîíñòðîâ:
5447 Mem.ReadInt(FMonsterKills);
5448 // Ôðàãîâ:
5449 Mem.ReadInt(FFrags);
5450 // Ôðàãîâ ïîäðÿä:
5451 Mem.ReadByte(FFragCombo);
5452 // Âðåìÿ ïîñëåäíåãî ôðàãà:
5453 Mem.ReadDWORD(FLastFrag);
5454 // Ñìåðòåé:
5455 Mem.ReadInt(FDeath);
5456 // Êàêîé ôëàã íåñåò:
5457 Mem.ReadByte(FFlag);
5458 // Íàøåë ñåêðåòîâ:
5459 Mem.ReadInt(FSecrets);
5460 // Òåêóùåå îðóæèå:
5461 Mem.ReadByte(FCurrWeap);
5462 // Æåëàåìîå îðóæèå:
5463 Mem.ReadWord(FNextWeap);
5464 // ...è ïàóçà
5465 Mem.ReadByte(FNextWeapDelay);
5466 // Âðåìÿ çàðÿäêè BFG:
5467 Mem.ReadSmallInt(FBFGFireCounter);
5468 // Áóôåð óðîíà:
5469 Mem.ReadInt(FDamageBuffer);
5470 // Ïîñëåäíèé óäàðèâøèé:
5471 Mem.ReadWord(FLastSpawnerUID);
5472 // Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà:
5473 Mem.ReadByte(FLastHit);
5474 // Îáúåêò èãðîêà:
5475 Obj_LoadState(@FObj, Mem);
5476 // Òåêóùåå êîëè÷åñòâî ïàòðîíîâ:
5477 for i := A_BULLETS to A_CELLS do
5478 Mem.ReadWord(FAmmo[i]);
5479 // Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ:
5480 for i := A_BULLETS to A_CELLS do
5481 Mem.ReadWord(FMaxAmmo[i]);
5482 // Íàëè÷èå îðóæèÿ:
5483 for i := WEAPON_KASTET to WEAPON_SUPERPULEMET do
5484 Mem.ReadBoolean(FWeapon[i]);
5485 // Âðåìÿ ïåðåçàðÿäêè îðóæèÿ:
5486 for i := WEAPON_KASTET to WEAPON_SUPERPULEMET do
5487 Mem.ReadWord(FReloading[i]);
5488 // Íàëè÷èå ðþêçàêà:
5489 Mem.ReadByte(b);
5490 if b = 1 then
5491 Include(FRulez, R_ITEM_BACKPACK);
5492 // Íàëè÷èå êðàñíîãî êëþ÷à:
5493 Mem.ReadByte(b);
5494 if b = 1 then
5495 Include(FRulez, R_KEY_RED);
5496 // Íàëè÷èå çåëåíîãî êëþ÷à:
5497 Mem.ReadByte(b);
5498 if b = 1 then
5499 Include(FRulez, R_KEY_GREEN);
5500 // Íàëè÷èå ñèíåãî êëþ÷à:
5501 Mem.ReadByte(b);
5502 if b = 1 then
5503 Include(FRulez, R_KEY_BLUE);
5504 // Íàëè÷èå áåðñåðêà:
5505 Mem.ReadByte(b);
5506 if b = 1 then
5507 Include(FRulez, R_BERSERK);
5508 // Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ:
5509 for i := MR_SUIT to MR_MAX do
5510 Mem.ReadDWORD(FMegaRulez[i]);
5511 // Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà:
5512 for i := T_RESPAWN to T_FLAGCAP do
5513 Mem.ReadDWORD(FTime[i]);
5514 // Íàçâàíèå ìîäåëè:
5515 Mem.ReadString(str);
5516 // Öâåò ìîäåëè:
5517 Mem.ReadByte(FColor.R);
5518 Mem.ReadByte(FColor.G);
5519 Mem.ReadByte(FColor.B);
5520 if Self = gPlayer1 then
5521 begin
5522 str := gPlayer1Settings.Model;
5523 FColor := gPlayer1Settings.Color;
5524 end;
5525 if Self = gPlayer2 then
5526 begin
5527 str := gPlayer2Settings.Model;
5528 FColor := gPlayer2Settings.Color;
5529 end;
5530 // Îáíîâëÿåì ìîäåëü èãðîêà:
5531 SetModel(str);
5532 if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
5533 FModel.Color := TEAMCOLOR[FTeam]
5534 else
5535 FModel.Color := FColor;
5536 end;
5538 procedure TPlayer.AllRulez(Health: Boolean);
5539 var
5540 a: Integer;
5541 begin
5542 if Health then
5543 begin
5544 FHealth := PLAYER_HP_LIMIT;
5545 FArmor := PLAYER_AP_LIMIT;
5546 Exit;
5547 end;
5549 for a := WEAPON_KASTET to WEAPON_SUPERPULEMET do FWeapon[a] := True;
5550 for a := A_BULLETS to A_CELLS do FAmmo[a] := 30000;
5551 FRulez := FRulez+[R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE];
5552 end;
5554 procedure TPlayer.RestoreHealthArmor();
5555 begin
5556 FHealth := PLAYER_HP_LIMIT;
5557 FArmor := PLAYER_AP_LIMIT;
5558 end;
5560 procedure TPlayer.FragCombo();
5561 var
5562 Param: Integer;
5563 begin
5564 if (gGameSettings.GameMode in [GM_COOP, GM_SINGLE]) or g_Game_IsClient then
5565 Exit;
5566 if gTime - FLastFrag < FRAG_COMBO_TIME then
5567 begin
5568 if FFragCombo < 5 then
5569 Inc(FFragCombo);
5570 Param := FUID or (FFragCombo shl 16);
5571 if (FComboEvnt >= Low(gDelayedEvents)) and
5572 (FComboEvnt <= High(gDelayedEvents)) and
5573 gDelayedEvents[FComboEvnt].Pending and
5574 (gDelayedEvents[FComboEvnt].DEType = DE_KILLCOMBO) and
5575 (gDelayedEvents[FComboEvnt].DENum and $FFFF = FUID) then
5576 begin
5577 gDelayedEvents[FComboEvnt].Time := gTime + 500;
5578 gDelayedEvents[FComboEvnt].DENum := Param;
5579 end
5580 else
5581 FComboEvnt := g_Game_DelayEvent(DE_KILLCOMBO, 500, Param);
5582 end
5583 else
5584 FFragCombo := 1;
5586 FLastFrag := gTime;
5587 end;
5589 procedure TPlayer.GiveItem(ItemType: Byte);
5590 begin
5591 case ItemType of
5592 ITEM_SUIT:
5593 if FMegaRulez[MR_SUIT] < gTime+PLAYER_SUIT_TIME then
5594 begin
5595 FMegaRulez[MR_SUIT] := gTime+PLAYER_SUIT_TIME;
5596 end;
5598 ITEM_OXYGEN:
5599 if FAir < AIR_MAX then
5600 begin
5601 FAir := AIR_MAX;
5602 end;
5604 ITEM_MEDKIT_BLACK:
5605 begin
5606 if not (R_BERSERK in FRulez) then
5607 begin
5608 Include(FRulez, R_BERSERK);
5609 if FBFGFireCounter < 1 then
5610 begin
5611 FCurrWeap := WEAPON_KASTET;
5612 resetWeaponQueue();
5613 FModel.SetWeapon(WEAPON_KASTET);
5614 end;
5615 if gFlash <> 0 then
5616 Inc(FPain, 100);
5617 FBerserk := gTime+30000;
5618 end;
5619 if FHealth < PLAYER_HP_SOFT then
5620 begin
5621 FHealth := PLAYER_HP_SOFT;
5622 FBerserk := gTime+30000;
5623 end;
5624 end;
5626 ITEM_INVUL:
5627 if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
5628 begin
5629 FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
5630 end;
5632 ITEM_INVIS:
5633 if FMegaRulez[MR_INVIS] < gTime+PLAYER_INVIS_TIME then
5634 begin
5635 FMegaRulez[MR_INVIS] := gTime+PLAYER_INVIS_TIME;
5636 end;
5638 ITEM_JETPACK:
5639 if FJetFuel < JET_MAX then
5640 begin
5641 FJetFuel := JET_MAX;
5642 end;
5644 ITEM_WEAPON_SAW: FWeapon[WEAPON_SAW] := True;
5645 ITEM_WEAPON_SHOTGUN1: FWeapon[WEAPON_SHOTGUN1] := True;
5646 ITEM_WEAPON_SHOTGUN2: FWeapon[WEAPON_SHOTGUN2] := True;
5647 ITEM_WEAPON_CHAINGUN: FWeapon[WEAPON_CHAINGUN] := True;
5648 ITEM_WEAPON_ROCKETLAUNCHER: FWeapon[WEAPON_ROCKETLAUNCHER] := True;
5649 ITEM_WEAPON_PLASMA: FWeapon[WEAPON_PLASMA] := True;
5650 ITEM_WEAPON_BFG: FWeapon[WEAPON_BFG] := True;
5651 ITEM_WEAPON_SUPERPULEMET: FWeapon[WEAPON_SUPERPULEMET] := True;
5653 ITEM_AMMO_BULLETS: if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
5654 ITEM_AMMO_BULLETS_BOX: if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
5655 ITEM_AMMO_SHELLS: if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
5656 ITEM_AMMO_SHELLS_BOX: if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 25, FMaxAmmo[A_SHELLS]);
5657 ITEM_AMMO_ROCKET: if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
5658 ITEM_AMMO_ROCKET_BOX: if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 5, FMaxAmmo[A_ROCKETS]);
5659 ITEM_AMMO_CELL: if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
5660 ITEM_AMMO_CELL_BIG: if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 100, FMaxAmmo[A_CELLS]);
5662 ITEM_AMMO_BACKPACK:
5663 if (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or
5664 (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or
5665 (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or
5666 (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) then
5667 begin
5668 FMaxAmmo[A_BULLETS] := 400;
5669 FMaxAmmo[A_SHELLS] := 100;
5670 FMaxAmmo[A_ROCKETS] := 100;
5671 FMaxAmmo[A_CELLS] := 600;
5673 if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
5674 if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
5675 if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
5676 if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
5678 FRulez := FRulez + [R_ITEM_BACKPACK];
5679 end;
5681 ITEM_KEY_RED: if not (R_KEY_RED in FRulez) then Include(FRulez, R_KEY_RED);
5682 ITEM_KEY_GREEN: if not (R_KEY_GREEN in FRulez) then Include(FRulez, R_KEY_GREEN);
5683 ITEM_KEY_BLUE: if not (R_KEY_BLUE in FRulez) then Include(FRulez, R_KEY_BLUE);
5685 ITEM_BOTTLE: if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 4, PLAYER_HP_LIMIT);
5686 ITEM_HELMET: if FArmor < PLAYER_AP_LIMIT then IncMax(FArmor, 5, PLAYER_AP_LIMIT);
5688 else
5689 Exit;
5690 end;
5691 if g_Game_IsNet and g_Game_IsServer then
5692 MH_SEND_PlayerStats(FUID);
5693 end;
5695 procedure TPlayer.FlySmoke(Times: DWORD = 1);
5696 var
5697 id, i: DWORD;
5698 Anim: TAnimation;
5699 begin
5700 if (Random(5) = 1) and (Times = 1) then
5701 Exit;
5703 if BodyInLiquid(0, 0) then
5704 begin
5705 g_GFX_Bubbles(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)+Random(3)-1,
5706 Obj.Y+Obj.Rect.Height+8, 1, 8, 4);
5707 if Random(2) = 0 then
5708 g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
5709 else
5710 g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
5711 Exit;
5712 end;
5714 if g_Frames_Get(id, 'FRAMES_SMOKE') then
5715 begin
5716 for i := 1 to Times do
5717 begin
5718 Anim := TAnimation.Create(id, False, 3);
5719 Anim.Alpha := 150;
5720 g_GFX_OnceAnim(Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(Anim.Width div 2),
5721 Obj.Y+Obj.Rect.Height-4+Random(8+Times*2), Anim, ONCEANIM_SMOKE);
5722 Anim.Free();
5723 end;
5724 end;
5725 end;
5727 procedure TPlayer.PauseSounds(Enable: Boolean);
5728 begin
5729 FSawSound.Pause(Enable);
5730 FSawSoundIdle.Pause(Enable);
5731 FSawSoundHit.Pause(Enable);
5732 FSawSoundSelect.Pause(Enable);
5733 end;
5735 { T C o r p s e : }
5737 constructor TCorpse.Create(X, Y: Integer; ModelName: String; aMess: Boolean);
5738 begin
5739 g_Obj_Init(@FObj);
5740 FObj.X := X;
5741 FObj.Y := Y;
5742 FObj.Rect := PLAYER_CORPSERECT;
5743 FModelName := ModelName;
5744 FMess := aMess;
5746 if FMess then
5747 begin
5748 FState := CORPSE_STATE_MESS;
5749 g_PlayerModel_GetAnim(ModelName, A_DIE2, FAnimation, FAnimationMask);
5750 end
5751 else
5752 begin
5753 FState := CORPSE_STATE_NORMAL;
5754 g_PlayerModel_GetAnim(ModelName, A_DIE1, FAnimation, FAnimationMask);
5755 end;
5756 end;
5758 destructor TCorpse.Destroy();
5759 begin
5760 FAnimation.Free();
5762 inherited;
5763 end;
5765 procedure TCorpse.Damage(Value: Word; vx, vy: Integer);
5766 var
5767 pm: TPlayerModel;
5768 begin
5769 if FState = CORPSE_STATE_REMOVEME then
5770 Exit;
5772 FDamage := FDamage + Value;
5774 if FDamage > 150 then
5775 begin
5776 if FAnimation <> nil then
5777 begin
5778 FAnimation.Free();
5779 FAnimation := nil;
5781 FState := CORPSE_STATE_REMOVEME;
5783 g_Player_CreateGibs(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
5784 FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
5785 FModelName, FColor);
5786 // Çâóê ìÿñà îò òðóïà:
5787 pm := g_PlayerModel_Get(FModelName);
5788 pm.PlaySound(MODELSOUND_DIE, 5, FObj.X, FObj.Y);
5789 pm.Free;
5790 end;
5791 end
5792 else
5793 begin
5794 FObj.Vel.X := FObj.Vel.X + vx;
5795 FObj.Vel.Y := FObj.Vel.Y + vy;
5796 g_GFX_Blood(FObj.X+PLAYER_CORPSERECT.X+(PLAYER_CORPSERECT.Width div 2),
5797 FObj.Y+PLAYER_CORPSERECT.Y+(PLAYER_CORPSERECT.Height div 2),
5798 Value, vx, vy, 16, (PLAYER_CORPSERECT.Height*2) div 3,
5799 150, 0, 0);
5800 end;
5801 end;
5803 procedure TCorpse.Draw();
5804 begin
5805 if FState = CORPSE_STATE_REMOVEME then
5806 Exit;
5808 if FAnimation <> nil then
5809 FAnimation.Draw(FObj.X, FObj.Y, M_NONE);
5811 if FAnimationMask <> nil then
5812 begin
5813 e_Colors := FColor;
5814 FAnimationMask.Draw(FObj.X, FObj.Y, M_NONE);
5815 e_Colors.R := 255;
5816 e_Colors.G := 255;
5817 e_Colors.B := 255;
5818 end;
5819 end;
5821 procedure TCorpse.Update();
5822 var
5823 st: Word;
5824 begin
5825 if FState = CORPSE_STATE_REMOVEME then
5826 Exit;
5828 if gTime mod (GAME_TICK*2) <> 0 then
5829 begin
5830 g_Obj_Move(@FObj, True, True, True);
5832 Exit;
5833 end;
5835 // Ñîïðîòèâëåíèå âîçäóõà äëÿ òðóïà:
5836 FObj.Vel.X := z_dec(FObj.Vel.X, 1);
5838 st := g_Obj_Move(@FObj, True, True, True);
5840 if WordBool(st and MOVE_FALLOUT) then
5841 begin
5842 FState := CORPSE_STATE_REMOVEME;
5843 Exit;
5844 end;
5846 if FAnimation <> nil then
5847 FAnimation.Update();
5848 if FAnimationMask <> nil then
5849 FAnimationMask.Update();
5850 end;
5852 procedure TCorpse.SaveState(var Mem: TBinMemoryWriter);
5853 var
5854 sig: DWORD;
5855 anim: Boolean;
5856 begin
5857 if Mem = nil then
5858 Exit;
5860 // Ñèãíàòóðà òðóïà:
5861 sig := CORPSE_SIGNATURE; // 'CORP'
5862 Mem.WriteDWORD(sig);
5863 // Ñîñòîÿíèå:
5864 Mem.WriteByte(FState);
5865 // Íàêîïëåííûé óðîí:
5866 Mem.WriteByte(FDamage);
5867 // Öâåò:
5868 Mem.WriteByte(FColor.R);
5869 Mem.WriteByte(FColor.G);
5870 Mem.WriteByte(FColor.B);
5871 // Îáúåêò òðóïà:
5872 Obj_SaveState(@FObj, Mem);
5873 // Åñòü ëè àíèìàöèÿ:
5874 anim := FAnimation <> nil;
5875 Mem.WriteBoolean(anim);
5876 // Åñëè åñòü - ñîõðàíÿåì:
5877 if anim then
5878 FAnimation.SaveState(Mem);
5879 // Åñòü ëè ìàñêà àíèìàöèè:
5880 anim := FAnimationMask <> nil;
5881 Mem.WriteBoolean(anim);
5882 // Åñëè åñòü - ñîõðàíÿåì:
5883 if anim then
5884 FAnimationMask.SaveState(Mem);
5885 end;
5887 procedure TCorpse.LoadState(var Mem: TBinMemoryReader);
5888 var
5889 sig: DWORD;
5890 anim: Boolean;
5891 begin
5892 if Mem = nil then
5893 Exit;
5895 // Ñèãíàòóðà òðóïà:
5896 Mem.ReadDWORD(sig);
5897 if sig <> CORPSE_SIGNATURE then // 'CORP'
5898 begin
5899 raise EBinSizeError.Create('TCorpse.LoadState: Wrong Corpse Signature');
5900 end;
5901 // Ñîñòîÿíèå:
5902 Mem.ReadByte(FState);
5903 // Íàêîïëåííûé óðîí:
5904 Mem.ReadByte(FDamage);
5905 // Öâåò:
5906 Mem.ReadByte(FColor.R);
5907 Mem.ReadByte(FColor.G);
5908 Mem.ReadByte(FColor.B);
5909 // Îáúåêò òðóïà:
5910 Obj_LoadState(@FObj, Mem);
5911 // Åñòü ëè àíèìàöèÿ:
5912 Mem.ReadBoolean(anim);
5913 // Åñëè åñòü - çàãðóæàåì:
5914 if anim then
5915 begin
5916 Assert(FAnimation <> nil, 'TCorpse.LoadState: no FAnimation');
5917 FAnimation.LoadState(Mem);
5918 end;
5919 // Åñòü ëè ìàñêà àíèìàöèè:
5920 Mem.ReadBoolean(anim);
5921 // Åñëè åñòü - çàãðóæàåì:
5922 if anim then
5923 begin
5924 Assert(FAnimationMask <> nil, 'TCorpse.LoadState: no FAnimationMask');
5925 FAnimationMask.LoadState(Mem);
5926 end;
5927 end;
5929 { T B o t : }
5931 constructor TBot.Create();
5932 var
5933 a: Integer;
5934 begin
5935 inherited Create();
5937 FPhysics := True;
5938 FSpectator := False;
5939 FGhost := False;
5941 FIamBot := True;
5943 Inc(gNumBots);
5945 for a := WEAPON_KASTET to WEAPON_SUPERPULEMET do
5946 begin
5947 FDifficult.WeaponPrior[a] := WEAPON_PRIOR1[a];
5948 FDifficult.CloseWeaponPrior[a] := WEAPON_PRIOR2[a];
5949 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
5950 end;
5951 end;
5953 destructor TBot.Destroy();
5954 begin
5955 Dec(gNumBots);
5956 inherited Destroy();
5957 end;
5959 procedure TBot.Draw();
5960 begin
5961 inherited Draw();
5963 //if FTargetUID <> 0 then e_DrawLine(1, FObj.X, FObj.Y, g_Player_Get(FTargetUID).FObj.X,
5964 // g_Player_Get(FTargetUID).FObj.Y, 255, 0, 0);
5965 end;
5967 procedure TBot.Respawn(Silent: Boolean; Force: Boolean = False);
5968 begin
5969 inherited Respawn(Silent, Force);
5971 FAIFlags := nil;
5972 FSelectedWeapon := FCurrWeap;
5973 resetWeaponQueue();
5974 FTargetUID := 0;
5975 end;
5977 procedure TBot.UpdateCombat();
5978 type
5979 TTarget = record
5980 UID: Word;
5981 X, Y: Integer;
5982 Rect: TRectWH;
5983 cX, cY: Integer;
5984 Dist: Word;
5985 Line: Boolean;
5986 Visible: Boolean;
5987 IsPlayer: Boolean;
5988 end;
5990 TTargetRecord = array of TTarget;
5992 function Compare(a, b: TTarget): Integer;
5993 begin
5994 if a.Line and not b.Line then // A íà ëèíèè îãíÿ
5995 Result := -1
5996 else
5997 if not a.Line and b.Line then // B íà ëèíèè îãíÿ
5998 Result := 1
5999 else // È A, è B íà ëèíèè èëè íå íà ëèíèè îãíÿ
6000 if (a.Line and b.Line) or ((not a.Line) and (not b.Line)) then
6001 begin
6002 if a.Dist > b.Dist then // B áëèæå
6003 Result := 1
6004 else // A áëèæå èëè ðàâíîóäàëåííî ñ B
6005 Result := -1;
6006 end
6007 else // Ñòðàííî -> A
6008 Result := -1;
6009 end;
6011 var
6012 a, x1, y1, x2, y2: Integer;
6013 targets: TTargetRecord;
6014 ammo: Word;
6015 Target, BestTarget: TTarget;
6016 firew, fireh: Integer;
6017 angle: SmallInt;
6018 mon: TMonster;
6019 pla: TPlayer;
6020 vsPlayer, vsMonster, ok: Boolean;
6021 begin
6022 vsPlayer := LongBool(gGameSettings.Options and GAME_OPTION_BOTVSPLAYER);
6023 vsMonster := LongBool(gGameSettings.Options and GAME_OPTION_BOTVSMONSTER);
6025 // Åñëè òåêóùåå îðóæèå íå òî, ÷òî íóæíî, òî ìåíÿåì:
6026 if FCurrWeap <> FSelectedWeapon then
6027 NextWeapon();
6029 // Åñëè íóæíî ñòðåëÿòü è íóæíîå îðóæèå, òî íàæàòü "Ñòðåëÿòü":
6030 if (GetAIFlag('NEEDFIRE') <> '') and (FCurrWeap = FSelectedWeapon) then
6031 begin
6032 RemoveAIFlag('NEEDFIRE');
6034 case FCurrWeap of
6035 WEAPON_PLASMA, WEAPON_SUPERPULEMET, WEAPON_CHAINGUN: PressKey(KEY_FIRE, 20);
6036 WEAPON_SAW, WEAPON_KASTET, WEAPON_MEGAKASTET: PressKey(KEY_FIRE, 40);
6037 else PressKey(KEY_FIRE);
6038 end;
6039 end;
6041 // Êîîðäèíàòû ñòâîëà:
6042 x1 := FObj.X + WEAPONPOINT[FDirection].X;
6043 y1 := FObj.Y + WEAPONPOINT[FDirection].Y;
6045 Target.UID := FTargetUID;
6047 ok := False;
6048 if Target.UID <> 0 then
6049 begin // Öåëü åñòü - íàñòðàèâàåì
6050 if (g_GetUIDType(Target.UID) = UID_PLAYER) and
6051 vsPlayer then
6052 begin // Èãðîê
6053 with g_Player_Get(Target.UID) do
6054 begin
6055 if (@FObj) <> nil then
6056 begin
6057 Target.X := FObj.X;
6058 Target.Y := FObj.Y;
6059 end;
6060 end;
6062 Target.cX := Target.X + PLAYER_RECT_CX;
6063 Target.cY := Target.Y + PLAYER_RECT_CY;
6064 Target.Rect := PLAYER_RECT;
6065 Target.Visible := g_TraceVector(x1, y1, Target.cX, Target.cY);
6066 Target.Line := (y1+4 < Target.Y+PLAYER_RECT.Y+PLAYER_RECT.Height) and
6067 (y1-4 > Target.Y+PLAYER_RECT.Y);
6068 Target.IsPlayer := True;
6069 ok := True;
6070 end
6071 else
6072 if (g_GetUIDType(Target.UID) = UID_MONSTER) and
6073 vsMonster then
6074 begin // Ìîíñòð
6075 mon := g_Monsters_Get(Target.UID);
6076 if mon <> nil then
6077 begin
6078 Target.X := mon.Obj.X;
6079 Target.Y := mon.Obj.Y;
6081 Target.cX := Target.X + mon.Obj.Rect.X + (mon.Obj.Rect.Width div 2);
6082 Target.cY := Target.Y + mon.Obj.Rect.Y + (mon.Obj.Rect.Height div 2);
6083 Target.Rect := mon.Obj.Rect;
6084 Target.Visible := g_TraceVector(x1, y1, Target.cX, Target.cY);
6085 Target.Line := (y1+4 < Target.Y + mon.Obj.Rect.Y + mon.Obj.Rect.Height) and
6086 (y1-4 > Target.Y + mon.Obj.Rect.Y);
6087 Target.IsPlayer := False;
6088 ok := True;
6089 end;
6090 end;
6091 end;
6093 if not ok then
6094 begin // Öåëè íåò - îáíóëÿåì
6095 Target.X := 0;
6096 Target.Y := 0;
6097 Target.cX := 0;
6098 Target.cY := 0;
6099 Target.Visible := False;
6100 Target.Line := False;
6101 Target.IsPlayer := False;
6102 end;
6104 targets := nil;
6106 // Åñëè öåëü íå âèäèìà èëè íå íà ëèíèè îãíÿ, òî èùåì âñå âîçìîæíûå öåëè:
6107 if (not Target.Line) or (not Target.Visible) then
6108 begin
6109 // Èãðîêè:
6110 if vsPlayer then
6111 for a := 0 to High(gPlayers) do
6112 if (gPlayers[a] <> nil) and (gPlayers[a].Live) and
6113 (gPlayers[a].FUID <> FUID) and
6114 (not SameTeam(FUID, gPlayers[a].FUID)) and
6115 (not gPlayers[a].NoTarget) and
6116 (gPlayers[a].FMegaRulez[MR_INVIS] < gTime) then
6117 begin
6118 if not TargetOnScreen(gPlayers[a].FObj.X + PLAYER_RECT.X,
6119 gPlayers[a].FObj.Y + PLAYER_RECT.Y) then
6120 Continue;
6122 x2 := gPlayers[a].FObj.X + PLAYER_RECT_CX;
6123 y2 := gPlayers[a].FObj.Y + PLAYER_RECT_CY;
6125 // Åñëè èãðîê íà ýêðàíå è íå ïðèêðûò ñòåíîé:
6126 if g_TraceVector(x1, y1, x2, y2) then
6127 begin
6128 // Äîáàâëÿåì ê ñïèñêó âîçìîæíûõ öåëåé:
6129 SetLength(targets, Length(targets)+1);
6130 with targets[High(targets)] do
6131 begin
6132 UID := gPlayers[a].FUID;
6133 X := gPlayers[a].FObj.X;
6134 Y := gPlayers[a].FObj.Y;
6135 cX := x2;
6136 cY := y2;
6137 Rect := PLAYER_RECT;
6138 Dist := g_PatchLength(x1, y1, x2, y2);
6139 Line := (y1+4 < Target.Y+PLAYER_RECT.Y+PLAYER_RECT.Height) and
6140 (y1-4 > Target.Y+PLAYER_RECT.Y);
6141 Visible := True;
6142 IsPlayer := True;
6143 end;
6144 end;
6145 end;
6147 // Ìîíñòðû:
6148 if vsMonster and (gMonsters <> nil) then
6149 for a := 0 to High(gMonsters) do
6150 if (gMonsters[a] <> nil) and (gMonsters[a].Live) and
6151 (gMonsters[a].MonsterType <> MONSTER_BARREL) then
6152 begin
6153 mon := gMonsters[a];
6155 if not TargetOnScreen(mon.Obj.X + mon.Obj.Rect.X,
6156 mon.Obj.Y + mon.Obj.Rect.Y) then
6157 Continue;
6159 x2 := mon.Obj.X + mon.Obj.Rect.X + (mon.Obj.Rect.Width div 2);
6160 y2 := mon.Obj.Y + mon.Obj.Rect.Y + (mon.Obj.Rect.Height div 2);
6162 // Åñëè ìîíñòð íà ýêðàíå è íå ïðèêðûò ñòåíîé:
6163 if g_TraceVector(x1, y1, x2, y2) then
6164 begin
6165 // Äîáàâëÿåì ê ñïèñêó âîçìîæíûõ öåëåé:
6166 SetLength(targets, Length(targets)+1);
6167 with targets[High(targets)] do
6168 begin
6169 UID := mon.UID;
6170 X := mon.Obj.X;
6171 Y := mon.Obj.Y;
6172 cX := x2;
6173 cY := y2;
6174 Rect := mon.Obj.Rect;
6175 Dist := g_PatchLength(x1, y1, x2, y2);
6176 Line := (y1+4 < Target.Y + mon.Obj.Rect.Y + mon.Obj.Rect.Height) and
6177 (y1-4 > Target.Y + mon.Obj.Rect.Y);
6178 Visible := True;
6179 IsPlayer := False;
6180 end;
6181 end;
6182 end;
6183 end;
6185 // Åñëè åñòü âîçìîæíûå öåëè:
6186 // (Âûáèðàåì ëó÷øóþ, ìåíÿåì îðóæèå è áåæèì ê íåé/îò íåå)
6187 if targets <> nil then
6188 begin
6189 // Âûáèðàåì íàèëó÷øóþ öåëü:
6190 BestTarget := targets[0];
6191 if Length(targets) > 1 then
6192 for a := 1 to High(targets) do
6193 if Compare(BestTarget, targets[a]) = 1 then
6194 BestTarget := targets[a];
6196 // Åñëè ëó÷øàÿ öåëü "âèäíåå" òåêóùåé, òî òåêóùàÿ := ëó÷øàÿ:
6197 if ((not Target.Visible) and BestTarget.Visible and (Target.UID <> BestTarget.UID)) or
6198 ((not Target.Line) and BestTarget.Line and BestTarget.Visible) then
6199 begin
6200 Target := BestTarget;
6202 if (Healthy() = 3) or ((Healthy() = 2)) then
6203 begin // Åñëè çäîðîâû - äîãîíÿåì
6204 if ((RunDirection() = D_LEFT) and (Target.X > FObj.X)) then
6205 SetAIFlag('GORIGHT', '1');
6206 if ((RunDirection() = D_RIGHT) and (Target.X < FObj.X)) then
6207 SetAIFlag('GOLEFT', '1');
6208 end
6209 else
6210 begin // Åñëè ïîáèòû - óáåãàåì
6211 if ((RunDirection() = D_LEFT) and (Target.X < FObj.X)) then
6212 SetAIFlag('GORIGHT', '1');
6213 if ((RunDirection() = D_RIGHT) and (Target.X > FObj.X)) then
6214 SetAIFlag('GOLEFT', '1');
6215 end;
6217 // Âûáèðàåì îðóæèå íà îñíîâå ðàññòîÿíèÿ è ïðèîðèòåòîâ:
6218 SelectWeapon(Abs(x1-Target.cX));
6219 end;
6220 end;
6222 // Åñëè åñòü öåëü:
6223 // (Äîãîíÿåì/óáåãàåì, ñòðåëÿåì ïî íàïðàâëåíèþ ê öåëè)
6224 // (Åñëè öåëü äàëåêî, òî õâàòèò ñëåäèòü çà íåé)
6225 if Target.UID <> 0 then
6226 begin
6227 if not TargetOnScreen(Target.X + Target.Rect.X,
6228 Target.Y + Target.Rect.Y) then
6229 begin // Öåëü ñáåæàëà ñ "ýêðàíà"
6230 if (Healthy() = 3) or ((Healthy() = 2)) then
6231 begin // Åñëè çäîðîâû - äîãîíÿåì
6232 if ((RunDirection() = D_LEFT) and (Target.X > FObj.X)) then
6233 SetAIFlag('GORIGHT', '1');
6234 if ((RunDirection() = D_RIGHT) and (Target.X < FObj.X)) then
6235 SetAIFlag('GOLEFT', '1');
6236 end
6237 else
6238 begin // Åñëè ïîáèòû - çàáûâàåì î öåëè è óáåãàåì
6239 Target.UID := 0;
6240 if ((RunDirection() = D_LEFT) and (Target.X < FObj.X)) then
6241 SetAIFlag('GORIGHT', '1');
6242 if ((RunDirection() = D_RIGHT) and (Target.X > FObj.X)) then
6243 SetAIFlag('GOLEFT', '1');
6244 end;
6245 end
6246 else
6247 begin // Öåëü ïîêà íà "ýêðàíå"
6248 // Åñëè öåëü íå çàãîðîæåíà ñòåíîé, òî îòìå÷àåì, êîãäà åå âèäåëè:
6249 if g_TraceVector(x1, y1, Target.cX, Target.cY) then
6250 FLastVisible := gTime;
6251 // Åñëè ðàçíèöà âûñîò íå âåëèêà, òî äîãîíÿåì:
6252 if (Abs(FObj.Y-Target.Y) <= 128) then
6253 begin
6254 if ((RunDirection() = D_LEFT) and (Target.X > FObj.X)) then
6255 SetAIFlag('GORIGHT', '1');
6256 if ((RunDirection() = D_RIGHT) and (Target.X < FObj.X)) then
6257 SetAIFlag('GOLEFT', '1');
6258 end;
6259 end;
6261 // Âûáèðàåì óãîë ââåðõ:
6262 if FDirection = D_LEFT then
6263 angle := ANGLE_LEFTUP
6264 else
6265 angle := ANGLE_RIGHTUP;
6267 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6268 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6270 // Åñëè ïðè óãëå ââåðõ ìîæíî ïîïàñòü â ïðèáëèçèòåëüíîå ïîëîæåíèå öåëè:
6271 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
6272 Target.X+Target.Rect.X+GetInterval(FDifficult.DiagPrecision, 128), //96
6273 Target.Y+Target.Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
6274 Target.Rect.Width, Target.Rect.Height) and
6275 g_TraceVector(x1, y1, Target.cX, Target.cY) then
6276 begin // òî íóæíî ñòðåëÿòü ââåðõ
6277 SetAIFlag('NEEDFIRE', '1');
6278 SetAIFlag('NEEDSEEUP', '1');
6279 end;
6281 // Âûáèðàåì óãîë âíèç:
6282 if FDirection = D_LEFT then
6283 angle := ANGLE_LEFTDOWN
6284 else
6285 angle := ANGLE_RIGHTDOWN;
6287 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6288 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6290 // Åñëè ïðè óãëå âíèç ìîæíî ïîïàñòü â ïðèáëèçèòåëüíîå ïîëîæåíèå öåëè:
6291 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
6292 Target.X+Target.Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
6293 Target.Y+Target.Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
6294 Target.Rect.Width, Target.Rect.Height) and
6295 g_TraceVector(x1, y1, Target.cX, Target.cY) then
6296 begin // òî íóæíî ñòðåëÿòü âíèç
6297 SetAIFlag('NEEDFIRE', '1');
6298 SetAIFlag('NEEDSEEDOWN', '1');
6299 end;
6301 // Åñëè öåëü âèäíî è îíà íà òàêîé æå âûñîòå:
6302 if Target.Visible and
6303 (y1+4 < Target.Y+Target.Rect.Y+Target.Rect.Height) and
6304 (y1-4 > Target.Y+Target.Rect.Y) then
6305 begin
6306 // Åñëè èäåì â ñòîðîíó öåëè, òî íàäî ñòðåëÿòü:
6307 if ((FDirection = D_LEFT) and (Target.X < FObj.X)) or
6308 ((FDirection = D_RIGHT) and (Target.X > FObj.X)) then
6309 begin // òî íóæíî ñòðåëÿòü âïåðåä
6310 SetAIFlag('NEEDFIRE', '1');
6311 SetAIFlag('NEEDSEEDOWN', '');
6312 SetAIFlag('NEEDSEEUP', '');
6313 end;
6314 // Åñëè öåëü â ïðåäåëàõ "ýêðàíà" è ñëîæíîñòü ïîçâîëÿåò ïðûæêè ñáëèæåíèÿ:
6315 if Abs(FObj.X-Target.X) < Trunc(gPlayerScreenSize.X*0.75) then
6316 if GetRnd(FDifficult.CloseJump) then
6317 begin // òî åñëè ïîâåçåò - ïðûãàåì (îñîáåííî, åñëè áëèçêî)
6318 if Abs(FObj.X-Target.X) < 128 then
6319 a := 4
6320 else
6321 a := 30;
6322 if Random(a) = 0 then
6323 SetAIFlag('NEEDJUMP', '1');
6324 end;
6325 end;
6327 // Åñëè öåëü âñå åùå åñòü:
6328 if Target.UID <> 0 then
6329 if gTime-FLastVisible > 2000 then // Åñëè âèäåëè äàâíî
6330 Target.UID := 0 // òî çàáûòü öåëü
6331 else // Åñëè âèäåëè íåäàâíî
6332 begin // íî öåëü óáèëè
6333 if Target.IsPlayer then
6334 begin // Öåëü - èãðîê
6335 pla := g_Player_Get(Target.UID);
6336 if (pla = nil) or (not pla.Live) or pla.NoTarget or
6337 (pla.FMegaRulez[MR_INVIS] >= gTime) then
6338 Target.UID := 0; // òî çàáûòü öåëü
6339 end
6340 else
6341 begin // Öåëü - ìîíñòð
6342 mon := g_Monsters_Get(Target.UID);
6343 if (mon = nil) or (not mon.Live) then
6344 Target.UID := 0; // òî çàáûòü öåëü
6345 end;
6346 end;
6347 end; // if Target.UID <> 0
6349 FTargetUID := Target.UID;
6351 // Åñëè âîçìîæíûõ öåëåé íåò:
6352 // (Àòàêà ÷åãî-íèáóäü ñëåâà èëè ñïðàâà)
6353 if targets = nil then
6354 if GetAIFlag('ATTACKLEFT') <> '' then
6355 begin // Åñëè íóæíî àòàêîâàòü íàëåâî
6356 RemoveAIFlag('ATTACKLEFT');
6358 SetAIFlag('NEEDJUMP', '1');
6360 if RunDirection() = D_RIGHT then
6361 begin // Èäåì íå â òó ñòîðîíó
6362 if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then
6363 begin // Åñëè çäîðîâû, òî, âîçìîæíî, ñòðåëÿåì áåæèì âëåâî è ñòðåëÿåì
6364 SetAIFlag('NEEDFIRE', '1');
6365 SetAIFlag('GOLEFT', '1');
6366 end;
6367 end
6368 else
6369 begin // Èäåì â íóæíóþ ñòîðîíó
6370 if GetRnd(FDifficult.InvisFire) then // Âîçìîæíî, ñòðåëÿåì âñëåïóþ
6371 SetAIFlag('NEEDFIRE', '1');
6372 if Healthy() <= 1 then // Ïîáèòû - óáåãàåì
6373 SetAIFlag('GORIGHT', '1');
6374 end;
6375 end
6376 else
6377 if GetAIFlag('ATTACKRIGHT') <> '' then
6378 begin // Åñëè íóæíî àòàêîâàòü íàïðàâî
6379 RemoveAIFlag('ATTACKRIGHT');
6381 SetAIFlag('NEEDJUMP', '1');
6383 if RunDirection() = D_LEFT then
6384 begin // Èäåì íå â òó ñòîðîíó
6385 if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then
6386 begin // Åñëè çäîðîâû, òî, âîçìîæíî, áåæèì âïðàâî è ñòðåëÿåì
6387 SetAIFlag('NEEDFIRE', '1');
6388 SetAIFlag('GORIGHT', '1');
6389 end;
6390 end
6391 else
6392 begin
6393 if GetRnd(FDifficult.InvisFire) then // Âîçìîæíî, ñòðåëÿåì âñëåïóþ
6394 SetAIFlag('NEEDFIRE', '1');
6395 if Healthy() <= 1 then // Ïîáèòû - óáåãàåì
6396 SetAIFlag('GOLEFT', '1');
6397 end;
6398 end;
6400 //HACK! (does it belongs there?)
6401 RealizeCurrentWeapon();
6403 // Åñëè åñòü âîçìîæíûå öåëè:
6404 // (Ñòðåëÿåì ïî íàïðàâëåíèþ ê öåëÿì)
6405 if (targets <> nil) and (GetAIFlag('NEEDFIRE') <> '') then
6406 for a := 0 to High(targets) do
6407 begin
6408 // Åñëè ìîæåì ñòðåëÿòü ïî äèàãîíàëè:
6409 if GetRnd(FDifficult.DiagFire) then
6410 begin
6411 // Èùåì öåëü ñâåðõó è ñòðåëÿåì, åñëè åñòü:
6412 if FDirection = D_LEFT then
6413 angle := ANGLE_LEFTUP
6414 else
6415 angle := ANGLE_RIGHTUP;
6417 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6418 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6420 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
6421 targets[a].X+targets[a].Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
6422 targets[a].Y+targets[a].Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
6423 targets[a].Rect.Width, targets[a].Rect.Height) and
6424 g_TraceVector(x1, y1, targets[a].cX, targets[a].cY) then
6425 begin
6426 SetAIFlag('NEEDFIRE', '1');
6427 SetAIFlag('NEEDSEEUP', '1');
6428 end;
6430 // Èùåì öåëü ñíèçó è ñòðåëÿåì, åñëè åñòü:
6431 if FDirection = D_LEFT then
6432 angle := ANGLE_LEFTDOWN
6433 else
6434 angle := ANGLE_RIGHTDOWN;
6436 firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6437 fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
6439 if g_CollideLine(x1, y1, x1+firew, y1+fireh,
6440 targets[a].X+targets[a].Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
6441 targets[a].Y+targets[a].Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
6442 targets[a].Rect.Width, targets[a].Rect.Height) and
6443 g_TraceVector(x1, y1, targets[a].cX, targets[a].cY) then
6444 begin
6445 SetAIFlag('NEEDFIRE', '1');
6446 SetAIFlag('NEEDSEEDOWN', '1');
6447 end;
6448 end;
6450 // Åñëè öåëü "ïåðåä íîñîì", òî ñòðåëÿåì:
6451 if targets[a].Line and targets[a].Visible and
6452 (((FDirection = D_LEFT) and (targets[a].X < FObj.X)) or
6453 ((FDirection = D_RIGHT) and (targets[a].X > FObj.X))) then
6454 begin
6455 SetAIFlag('NEEDFIRE', '1');
6456 Break;
6457 end;
6458 end;
6460 // Åñëè ëåòèò ïóëÿ, òî, âîçìîæíî, ïîäïðûãèâàåì:
6461 if g_Weapon_Danger(FUID, FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y,
6462 PLAYER_RECT.Width, PLAYER_RECT.Height,
6463 40+GetInterval(FDifficult.Cover, 40)) then
6464 SetAIFlag('NEEDJUMP', '1');
6466 // Åñëè êîí÷èëèñü ïàòîðíû, òî íóæíî ñìåíèòü îðóæèå:
6467 ammo := GetAmmoByWeapon(FCurrWeap);
6468 if ((FCurrWeap = WEAPON_SHOTGUN2) and (ammo < 2)) or
6469 ((FCurrWeap = WEAPON_BFG) and (ammo < 40)) or
6470 (ammo = 0) then
6471 SetAIFlag('SELECTWEAPON', '1');
6473 // Åñëè íóæíî ñìåíèòü îðóæèå, òî âûáèðàåì íóæíîå:
6474 if GetAIFlag('SELECTWEAPON') = '1' then
6475 begin
6476 SelectWeapon(-1);
6477 RemoveAIFlag('SELECTWEAPON');
6478 end;
6479 end;
6481 procedure TBot.Update();
6482 var
6483 EnableAI: Boolean;
6484 begin
6485 if not FLive then
6486 begin // Respawn
6487 ReleaseKeys();
6488 PressKey(KEY_UP);
6489 end
6490 else
6491 begin
6492 EnableAI := True;
6494 // Ïðîâåðÿåì, îòêëþ÷¸í ëè AI áîòîâ
6495 if (g_debug_BotAIOff = 1) and (Team = TEAM_RED) then
6496 EnableAI := False;
6497 if (g_debug_BotAIOff = 2) and (Team = TEAM_BLUE) then
6498 EnableAI := False;
6499 if g_debug_BotAIOff = 3 then
6500 EnableAI := False;
6502 if EnableAI then
6503 begin
6504 UpdateMove();
6505 UpdateCombat();
6506 end;
6507 end;
6509 inherited Update();
6510 end;
6512 procedure TBot.ReleaseKey(Key: Byte);
6513 begin
6514 with FKeys[Key] do
6515 begin
6516 Pressed := False;
6517 Time := 0;
6518 end;
6519 end;
6521 function TBot.KeyPressed(Key: Word): Boolean;
6522 begin
6523 Result := FKeys[Key].Pressed;
6524 end;
6526 function TBot.GetAIFlag(fName: String20): String20;
6527 var
6528 a: Integer;
6529 begin
6530 Result := '';
6532 fName := LowerCase(fName);
6534 if FAIFlags <> nil then
6535 for a := 0 to High(FAIFlags) do
6536 if LowerCase(FAIFlags[a].Name) = fName then
6537 begin
6538 Result := FAIFlags[a].Value;
6539 Break;
6540 end;
6541 end;
6543 procedure TBot.RemoveAIFlag(fName: String20);
6544 var
6545 a, b: Integer;
6546 begin
6547 if FAIFlags = nil then Exit;
6549 fName := LowerCase(fName);
6551 for a := 0 to High(FAIFlags) do
6552 if LowerCase(FAIFlags[a].Name) = fName then
6553 begin
6554 if a <> High(FAIFlags) then
6555 for b := a to High(FAIFlags)-1 do
6556 FAIFlags[b] := FAIFlags[b+1];
6558 SetLength(FAIFlags, Length(FAIFlags)-1);
6559 Break;
6560 end;
6561 end;
6563 procedure TBot.SetAIFlag(fName, fValue: String20);
6564 var
6565 a: Integer;
6566 ok: Boolean;
6567 begin
6568 a := 0;
6569 ok := False;
6571 fName := LowerCase(fName);
6573 if FAIFlags <> nil then
6574 for a := 0 to High(FAIFlags) do
6575 if LowerCase(FAIFlags[a].Name) = fName then
6576 begin
6577 ok := True;
6578 Break;
6579 end;
6581 if ok then FAIFlags[a].Value := fValue
6582 else
6583 begin
6584 SetLength(FAIFlags, Length(FAIFlags)+1);
6585 with FAIFlags[High(FAIFlags)] do
6586 begin
6587 Name := fName;
6588 Value := fValue;
6589 end;
6590 end;
6591 end;
6593 procedure TBot.UpdateMove;
6595 procedure GoLeft(Time: Word = 1);
6596 begin
6597 ReleaseKey(KEY_LEFT);
6598 ReleaseKey(KEY_RIGHT);
6599 PressKey(KEY_LEFT, Time);
6600 SetDirection(D_LEFT);
6601 end;
6603 procedure GoRight(Time: Word = 1);
6604 begin
6605 ReleaseKey(KEY_LEFT);
6606 ReleaseKey(KEY_RIGHT);
6607 PressKey(KEY_RIGHT, Time);
6608 SetDirection(D_RIGHT);
6609 end;
6611 function Rnd(a: Word): Boolean;
6612 begin
6613 Result := Random(a) = 0;
6614 end;
6616 procedure Turn(Time: Word = 1200);
6617 begin
6618 if RunDirection() = D_LEFT then GoRight(Time) else GoLeft(Time);
6619 end;
6621 procedure Stop();
6622 begin
6623 ReleaseKey(KEY_LEFT);
6624 ReleaseKey(KEY_RIGHT);
6625 end;
6627 function CanRunLeft(): Boolean;
6628 begin
6629 Result := not CollideLevel(-1, 0);
6630 end;
6632 function CanRunRight(): Boolean;
6633 begin
6634 Result := not CollideLevel(1, 0);
6635 end;
6637 function CanRun(): Boolean;
6638 begin
6639 if RunDirection() = D_LEFT then Result := CanRunLeft() else Result := CanRunRight();
6640 end;
6642 procedure Jump(Time: Word = 30);
6643 begin
6644 PressKey(KEY_JUMP, Time);
6645 end;
6647 function NearHole(): Boolean;
6648 var
6649 x, sx: Integer;
6650 begin
6651 { TODO 5 : Ëåñòíèöû }
6652 sx := IfThen(RunDirection() = D_LEFT, -1, 1);
6653 for x := 1 to PLAYER_RECT.Width do
6654 if (not StayOnStep(x*sx, 0)) and
6655 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
6656 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
6657 begin
6658 Result := True;
6659 Exit;
6660 end;
6662 Result := False;
6663 end;
6665 function BorderHole(): Boolean;
6666 var
6667 x, sx, xx: Integer;
6668 begin
6669 { TODO 5 : Ëåñòíèöû }
6670 sx := IfThen(RunDirection() = D_LEFT, -1, 1);
6671 for x := 1 to PLAYER_RECT.Width do
6672 if (not StayOnStep(x*sx, 0)) and
6673 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
6674 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
6675 begin
6676 for xx := x to x+32 do
6677 if CollideLevel(xx*sx, PLAYER_RECT.Height) then
6678 begin
6679 Result := True;
6680 Exit;
6681 end;
6682 end;
6684 Result := False;
6685 end;
6687 function NearDeepHole(): Boolean;
6688 var
6689 x, sx, y: Integer;
6690 begin
6691 Result := False;
6693 sx := IfThen(RunDirection() = D_LEFT, -1, 1);
6694 y := 3;
6696 for x := 1 to PLAYER_RECT.Width do
6697 if (not StayOnStep(x*sx, 0)) and
6698 (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
6699 (not CollideLevel(x*sx, PLAYER_RECT.Height*2)) then
6700 begin
6701 while FObj.Y+y*PLAYER_RECT.Height < gMapInfo.Height do
6702 begin
6703 if CollideLevel(x*sx, PLAYER_RECT.Height*y) then Exit;
6704 y := y+1;
6705 end;
6707 Result := True;
6708 end else Result := False;
6709 end;
6711 function OverDeepHole(): Boolean;
6712 var
6713 y: Integer;
6714 begin
6715 Result := False;
6717 y := 1;
6718 while FObj.Y+y*PLAYER_RECT.Height < gMapInfo.Height do
6719 begin
6720 if CollideLevel(0, PLAYER_RECT.Height*y) then Exit;
6721 y := y+1;
6722 end;
6724 Result := True;
6725 end;
6727 function OnGround(): Boolean;
6728 begin
6729 Result := StayOnStep(0, 0) or CollideLevel(0, 1);
6730 end;
6732 function OnLadder(): Boolean;
6733 begin
6734 Result := FullInStep(0, 0);
6735 end;
6737 function BelowLadder(): Boolean;
6738 begin
6739 Result := (FullInStep(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) and
6740 not CollideLevel(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or
6741 (FullInStep(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) and
6742 not CollideLevel(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP));
6743 end;
6745 function BelowLiftUp(): Boolean;
6746 begin
6747 Result := ((FullInLift(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) = -1) and
6748 not CollideLevel(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or
6749 ((FullInLift(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) = -1) and
6750 not CollideLevel(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP));
6751 end;
6753 function OnTopLift(): Boolean;
6754 begin
6755 Result := (FullInLift(0, 0) = -1) and (FullInLift(0, -32) = 0);
6756 end;
6758 function CanJumpOver(): Boolean;
6759 var
6760 sx, y: Integer;
6761 begin
6762 sx := IfThen(RunDirection() = D_LEFT, -1, 1);
6764 Result := False;
6766 if not CollideLevel(sx, 0) then Exit;
6768 for y := 1 to BOT_MAXJUMP do
6769 if CollideLevel(0, -y) then Exit else
6770 if not CollideLevel(sx, -y) then
6771 begin
6772 Result := True;
6773 Exit;
6774 end;
6775 end;
6777 function CanJumpUp(Dist: ShortInt): Boolean;
6778 var
6779 y, yy: Integer;
6780 c: Boolean;
6781 begin
6782 Result := False;
6784 if CollideLevel(Dist, 0) then Exit;
6786 c := False;
6787 for y := 0 to BOT_MAXJUMP do
6788 if CollideLevel(Dist, -y) then
6789 begin
6790 c := True;
6791 Break;
6792 end;
6794 if not c then Exit;
6796 c := False;
6797 for yy := y+1 to BOT_MAXJUMP do
6798 if not CollideLevel(Dist, -yy) then
6799 begin
6800 c := True;
6801 Break;
6802 end;
6804 if not c then Exit;
6806 c := False;
6807 for y := 0 to BOT_MAXJUMP do
6808 if CollideLevel(0, -y) then
6809 begin
6810 c := True;
6811 Break;
6812 end;
6814 if c then Exit;
6816 if y < yy then Exit;
6818 Result := True;
6819 end;
6821 function IsSafeTrigger(): Boolean;
6822 var
6823 a: Integer;
6824 begin
6825 Result := True;
6826 if gTriggers = nil then
6827 Exit;
6828 for a := 0 to High(gTriggers) do
6829 if Collide(gTriggers[a].X,
6830 gTriggers[a].Y,
6831 gTriggers[a].Width,
6832 gTriggers[a].Height) and
6833 (gTriggers[a].TriggerType in [TRIGGER_EXIT, TRIGGER_CLOSEDOOR,
6834 TRIGGER_CLOSETRAP, TRIGGER_TRAP,
6835 TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF,
6836 TRIGGER_ONOFF, TRIGGER_SPAWNMONSTER,
6837 TRIGGER_DAMAGE, TRIGGER_SHOT]) then
6838 Result := False;
6839 end;
6841 begin
6842 // Âîçìîæíî, íàæèìàåì êíîïêó:
6843 if Rnd(16) and IsSafeTrigger() then
6844 PressKey(KEY_OPEN);
6846 // Åñëè ïîä ëèôòîì èëè ñòóïåíüêàìè, òî, âîçìîæíî, ïðûãàåì:
6847 if OnLadder() or ((BelowLadder() or BelowLiftUp()) and Rnd(8)) then
6848 begin
6849 ReleaseKey(KEY_LEFT);
6850 ReleaseKey(KEY_RIGHT);
6851 Jump();
6852 end;
6854 // Èäåì âëåâî, åñëè íàäî áûëî:
6855 if GetAIFlag('GOLEFT') <> '' then
6856 begin
6857 RemoveAIFlag('GOLEFT');
6858 if CanRunLeft() then
6859 GoLeft(360);
6860 end;
6862 // Èäåì âïðàâî, åñëè íàäî áûëî:
6863 if GetAIFlag('GORIGHT') <> '' then
6864 begin
6865 RemoveAIFlag('GORIGHT');
6866 if CanRunRight() then
6867 GoRight(360);
6868 end;
6870 // Åñëè âûëåòåëè çà êàðòó, òî ïðîáóåì âåðíóòüñÿ:
6871 if FObj.X < -32 then
6872 GoRight(360)
6873 else
6874 if FObj.X+32 > gMapInfo.Width then
6875 GoLeft(360);
6877 // Ïðûãàåì, åñëè íàäî áûëî:
6878 if GetAIFlag('NEEDJUMP') <> '' then
6879 begin
6880 Jump(0);
6881 RemoveAIFlag('NEEDJUMP');
6882 end;
6884 // Ñìîòðèì ââåðõ, åñëè íàäî áûëî:
6885 if GetAIFlag('NEEDSEEUP') <> '' then
6886 begin
6887 ReleaseKey(KEY_UP);
6888 ReleaseKey(KEY_DOWN);
6889 PressKey(KEY_UP, 20);
6890 RemoveAIFlag('NEEDSEEUP');
6891 end;
6893 // Ñìîòðèì âíèç, åñëè íàäî áûëî:
6894 if GetAIFlag('NEEDSEEDOWN') <> '' then
6895 begin
6896 ReleaseKey(KEY_UP);
6897 ReleaseKey(KEY_DOWN);
6898 PressKey(KEY_DOWN, 20);
6899 RemoveAIFlag('NEEDSEEDOWN');
6900 end;
6902 // Åñëè íóæíî áûëî â äûðó è ìû íå íà çåìëå, òî ïîêîðíî ëåòèì:
6903 if GetAIFlag('GOINHOLE') <> '' then
6904 if not OnGround() then
6905 begin
6906 ReleaseKey(KEY_LEFT);
6907 ReleaseKey(KEY_RIGHT);
6908 RemoveAIFlag('GOINHOLE');
6909 SetAIFlag('FALLINHOLE', '1');
6910 end;
6912 // Åñëè ïàäàëè è äîñòèãëè çåìëè, òî õâàòèò ïàäàòü:
6913 if GetAIFlag('FALLINHOLE') <> '' then
6914 if OnGround() then
6915 RemoveAIFlag('FALLINHOLE');
6917 // Åñëè ëåòåëè ïðÿìî è ñåé÷àñ íå íà ëåñòíèöå èëè íà âåðøèíå ëèôòà, òî îòõîäèì â ñòîðîíó:
6918 if not (KeyPressed(KEY_LEFT) or KeyPressed(KEY_RIGHT)) then
6919 if GetAIFlag('FALLINHOLE') = '' then
6920 if (not OnLadder()) or (FObj.Vel.Y >= 0) or (OnTopLift()) then
6921 if Rnd(2) then
6922 GoLeft(360)
6923 else
6924 GoRight(360);
6926 // Åñëè íà çåìëå è ìîæíî ïîäïðûãíóòü, òî, âîçìîæíî, ïðûãàåì:
6927 if OnGround() and
6928 CanJumpUp(IfThen(RunDirection() = D_LEFT, -1, 1)*32) and
6929 Rnd(8) then
6930 Jump();
6932 // Åñëè íà çåìëå è âîçëå äûðû (ãëóáèíà > 2 ðîñòîâ èãðîêà):
6933 if OnGround() and NearHole() then
6934 if NearDeepHole() then // Åñëè ýòî áåçäíà
6935 case Random(6) of
6936 0..3: Turn(); // Áåæèì îáðàòíî
6937 4: Jump(); // Ïðûãàåì
6938 5: begin // Ïðûãàåì îáðàòíî
6939 Turn();
6940 Jump();
6941 end;
6942 end
6943 else // Ýòî íå áåçäíà è ìû åùå íå ëåòèì òóäà
6944 if GetAIFlag('GOINHOLE') = '' then
6945 case Random(6) of
6946 0: Turn(); // Íå íóæíî òóäà
6947 1: Jump(); // Âäðóã ïîâåçåò - ïðûãàåì
6948 else // Åñëè ÿìà ñ ãðàíèöåé, òî ïðè ñëó÷àå ìîæíî òóäà ïðûãíóòü
6949 if BorderHole() then
6950 SetAIFlag('GOINHOLE', '1');
6951 end;
6953 // Åñëè íà çåìëå, íî íåêóäà èäòè:
6954 if (not CanRun()) and OnGround() then
6955 begin
6956 // Åñëè ìû íà ëåñòíèöå èëè ìîæíî ïåðåïðûãíóòü, òî ïðûãàåì:
6957 if CanJumpOver() or OnLadder() then
6958 Jump()
6959 else // èíà÷å ïîïûòàåìñÿ â äðóãóþ ñòîðîíó
6960 if Random(2) = 0 then
6961 begin
6962 if IsSafeTrigger() then
6963 PressKey(KEY_OPEN);
6964 end else
6965 Turn();
6966 end;
6968 // Îñòàëîñü ìàëî âîçäóõà:
6969 if FAir < 36 * 2 then
6970 Jump(20);
6972 // Âûáèðàåìñÿ èç êèñëîòû, åñëè íåò êîñòþìà, îáîæãëèñü, èëè ìàëî çäîðîâüÿ:
6973 if (FMegaRulez[MR_SUIT] < gTime) and ((FLastHit = HIT_ACID) or (Healthy() <= 1)) then
6974 if BodyInAcid(0, 0) then
6975 Jump();
6976 end;
6978 function TBot.FullInStep(XInc, YInc: Integer): Boolean;
6979 begin
6980 Result := g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
6981 PLAYER_RECT.Width, PLAYER_RECT.Height, PANEL_STEP, False);
6982 end;
6984 {function TBot.NeedItem(Item: Byte): Byte;
6985 begin
6986 Result := 4;
6987 end;}
6989 procedure TBot.SelectWeapon(Dist: Integer);
6990 var
6991 a: Integer;
6993 function HaveAmmo(weapon: Byte): Boolean;
6994 begin
6995 case weapon of
6996 WEAPON_PISTOL: Result := FAmmo[A_BULLETS] >= 1;
6997 WEAPON_SHOTGUN1: Result := FAmmo[A_SHELLS] >= 1;
6998 WEAPON_SHOTGUN2: Result := FAmmo[A_SHELLS] >= 2;
6999 WEAPON_CHAINGUN: Result := FAmmo[A_BULLETS] >= 10;
7000 WEAPON_ROCKETLAUNCHER: Result := FAmmo[A_ROCKETS] >= 1;
7001 WEAPON_PLASMA: Result := FAmmo[A_CELLS] >= 10;
7002 WEAPON_BFG: Result := FAmmo[A_CELLS] >= 40;
7003 WEAPON_SUPERPULEMET: Result := FAmmo[A_SHELLS] >= 1;
7004 else Result := True;
7005 end;
7006 end;
7008 begin
7009 if Dist = -1 then Dist := BOT_LONGDIST;
7011 if Dist > BOT_LONGDIST then
7012 begin // Äàëüíèé áîé
7013 for a := 0 to 9 do
7014 if FWeapon[FDifficult.WeaponPrior[a]] and HaveAmmo(FDifficult.WeaponPrior[a]) then
7015 begin
7016 FSelectedWeapon := FDifficult.WeaponPrior[a];
7017 Break;
7018 end;
7019 end
7020 else //if Dist > BOT_UNSAFEDIST then
7021 begin // Áëèæíèé áîé
7022 for a := 0 to 9 do
7023 if FWeapon[FDifficult.CloseWeaponPrior[a]] and HaveAmmo(FDifficult.CloseWeaponPrior[a]) then
7024 begin
7025 FSelectedWeapon := FDifficult.CloseWeaponPrior[a];
7026 Break;
7027 end;
7028 end;
7029 { else
7030 begin
7031 for a := 0 to 9 do
7032 if FWeapon[FDifficult.SafeWeaponPrior[a]] and HaveAmmo(FDifficult.SafeWeaponPrior[a]) then
7033 begin
7034 FSelectedWeapon := FDifficult.SafeWeaponPrior[a];
7035 Break;
7036 end;
7037 end;}
7038 end;
7040 function TBot.PickItem(ItemType: Byte; force: Boolean; var remove: Boolean): Boolean;
7041 begin
7042 Result := inherited PickItem(ItemType, force, remove);
7044 if Result then SetAIFlag('SELECTWEAPON', '1');
7045 end;
7047 function TBot.Heal(value: Word; Soft: Boolean): Boolean;
7048 begin
7049 Result := inherited Heal(value, Soft);
7050 end;
7052 function TBot.Healthy(): Byte;
7053 begin
7054 if FMegaRulez[MR_INVUL] >= gTime then Result := 3
7055 else if (FHealth > 80) or ((FHealth > 50) and (FArmor > 20)) then Result := 3
7056 else if (FHealth > 50) then Result := 2
7057 else if (FHealth > 20) then Result := 1
7058 else Result := 0;
7059 end;
7061 function TBot.TargetOnScreen(TX, TY: Integer): Boolean;
7062 begin
7063 Result := (Abs(FObj.X-TX) <= Trunc(gPlayerScreenSize.X*0.6)) and
7064 (Abs(FObj.Y-TY) <= Trunc(gPlayerScreenSize.Y*0.6));
7065 end;
7067 procedure TBot.OnDamage(Angle: SmallInt);
7068 var
7069 pla: TPlayer;
7070 mon: TMonster;
7071 ok: Boolean;
7072 begin
7073 inherited;
7075 if (Angle = 0) or (Angle = 180) then
7076 begin
7077 ok := False;
7078 if (g_GetUIDType(FLastSpawnerUID) = UID_PLAYER) and
7079 LongBool(gGameSettings.Options and GAME_OPTION_BOTVSPLAYER) then
7080 begin // Èãðîê
7081 pla := g_Player_Get(FLastSpawnerUID);
7082 ok := not TargetOnScreen(pla.FObj.X + PLAYER_RECT.X,
7083 pla.FObj.Y + PLAYER_RECT.Y);
7084 end
7085 else
7086 if (g_GetUIDType(FLastSpawnerUID) = UID_MONSTER) and
7087 LongBool(gGameSettings.Options and GAME_OPTION_BOTVSMONSTER) then
7088 begin // Ìîíñòð
7089 mon := g_Monsters_Get(FLastSpawnerUID);
7090 ok := not TargetOnScreen(mon.Obj.X + mon.Obj.Rect.X,
7091 mon.Obj.Y + mon.Obj.Rect.Y);
7092 end;
7094 if ok then
7095 if Angle = 0 then
7096 SetAIFlag('ATTACKLEFT', '1')
7097 else
7098 SetAIFlag('ATTACKRIGHT', '1');
7099 end;
7100 end;
7102 function TBot.RunDirection(): TDirection;
7103 begin
7104 if Abs(Vel.X) >= 1 then
7105 begin
7106 if Vel.X > 0 then Result := D_RIGHT else Result := D_LEFT;
7107 end else
7108 Result := FDirection;
7109 end;
7111 function TBot.GetRnd(a: Byte): Boolean;
7112 begin
7113 if a = 0 then Result := False
7114 else if a = 255 then Result := True
7115 else Result := Random(256) > 255-a;
7116 end;
7118 function TBot.GetInterval(a: Byte; radius: SmallInt): SmallInt;
7119 begin
7120 Result := Round((255-a)/255*radius*(Random(2)-1));
7121 end;
7123 procedure TBot.SaveState(var Mem: TBinMemoryWriter);
7124 var
7125 i: Integer;
7126 dw: DWORD;
7127 p: Pointer;
7128 begin
7129 inherited SaveState(Mem);
7131 // Âûáðàííîå îðóæèå:
7132 Mem.WriteByte(FSelectedWeapon);
7133 // UID öåëè:
7134 Mem.WriteWord(FTargetUID);
7135 // Âðåìÿ ïîòåðè öåëè:
7136 Mem.WriteDWORD(FLastVisible);
7137 // Êîëè÷åñòâî ôëàãîâ ÈÈ:
7138 dw := Length(FAIFlags);
7139 Mem.WriteDWORD(dw);
7140 // Ôëàãè ÈÈ:
7141 for i := 0 to Integer(dw)-1 do
7142 begin
7143 Mem.WriteString(FAIFlags[i].Name, 20);
7144 Mem.WriteString(FAIFlags[i].Value, 20);
7145 end;
7146 // Íàñòðîéêè ñëîæíîñòè:
7147 p := @FDifficult;
7148 Mem.WriteMemory(p, SizeOf(TDifficult));
7149 end;
7151 procedure TBot.LoadState(var Mem: TBinMemoryReader);
7152 var
7153 i: Integer;
7154 dw: DWORD;
7155 p: Pointer;
7156 begin
7157 inherited LoadState(Mem);
7159 // Âûáðàííîå îðóæèå:
7160 Mem.ReadByte(FSelectedWeapon);
7161 // UID öåëè:
7162 Mem.ReadWord(FTargetUID);
7163 // Âðåìÿ ïîòåðè öåëè:
7164 Mem.ReadDWORD(FLastVisible);
7165 // Êîëè÷åñòâî ôëàãîâ ÈÈ:
7166 Mem.ReadDWORD(dw);
7167 SetLength(FAIFlags, dw);
7168 // Ôëàãè ÈÈ:
7169 for i := 0 to Integer(dw)-1 do
7170 begin
7171 Mem.ReadString(FAIFlags[i].Name);
7172 Mem.ReadString(FAIFlags[i].Value);
7173 end;
7174 // Íàñòðîéêè ñëîæíîñòè:
7175 Mem.ReadMemory(p, dw);
7176 if dw <> SizeOf(TDifficult) then
7177 begin
7178 raise EBinSizeError.Create('TBot.LoadState: Wrong FDifficult Size');
7179 end;
7180 FDifficult := TDifficult(p^);
7181 end;
7183 end.