DEADSOFTWARE

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