1 (* Copyright (C) Doom 2D: Forever Developers
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 {$INCLUDE ../shared/a_modes.inc}
23 {$IFDEF USE_MEMPOOL}mempool
,{$ENDIF}
24 g_base
, g_playermodel
, g_basic
, g_textures
,
25 g_weapons
, g_phys
, g_sound
, g_saveload
, MAPDEF
,
58 AmmoLimits
: Array [0..1] of Array [A_BULLETS
..A_HIGH
] of Word =
59 ((200, 50, 50, 300, 100),
60 (400, 100, 100, 600, 200));
81 ANGLE_NONE
= Low(SmallInt);
83 CORPSE_STATE_REMOVEME
= 0;
84 CORPSE_STATE_NORMAL
= 1;
85 CORPSE_STATE_MESS
= 2;
87 PLAYER_RECT
: TRectWH
= (X
:15; Y
:12; Width
:34; Height
:52);
88 PLAYER_RECT_CX
= 15+(34 div 2);
89 PLAYER_RECT_CY
= 12+(52 div 2);
90 PLAYER_CORPSERECT
: TRectWH
= (X
:15; Y
:48; Width
:34; Height
:16);
93 PLAYER_HP_LIMIT
= 200;
95 PLAYER_AP_LIMIT
= 200;
99 PLAYER_BURN_TIME
= 110;
101 PLAYER1_DEF_COLOR
: TRGB
= (R
:64; G
:175; B
:48);
102 PLAYER2_DEF_COLOR
: TRGB
= (R
:96; G
:96; B
:96);
106 JET_MAX
= 540; // ~30 sec
108 ANGLE_RIGHTDOWN
= -35;
110 ANGLE_LEFTDOWN
= -145;
111 WEAPONPOINT
: Array [TDirection
] of TDFPoint
= ((X
:16; Y
:32), (X
:47; Y
:32));
112 TEAMCOLOR
: Array [TEAM_RED
..TEAM_BLUE
] of TRGB
= ((R
:255; G
:0; B
:0),
131 TPlayerStatArray
= Array of TPlayerStat
;
133 TPlayerSavedState
= record
141 Ammo
: Array [A_BULLETS
..A_HIGH
] of Word;
142 MaxAmmo
: Array [A_BULLETS
..A_HIGH
] of Word;
143 Weapon
: Array [WP_FIRST
..WP_LAST
] of Boolean;
144 Rulez
: Set of R_ITEM_BACKPACK
..R_BERSERK
;
153 TPlayer
= class{$IFDEF USE_MEMPOOL}(TPoolObject
){$ENDIF}
161 FDirection
: TDirection
;
169 FMonsterKills
: Integer;
175 FCanJetpack
: Boolean;
181 FNextWeapDelay
: Byte; // frames
182 FBFGFireCounter
: SmallInt;
183 FLastSpawnerUID
: Word;
187 FSpectatePlayer
: Integer;
188 FFirePainTime
: Integer;
191 FSavedStateNum
: Integer;
193 FModel
: TPlayerModel
;
194 FPunchAnim
: TAnimationState
;
197 FActionForce
: Boolean;
198 FActionChanged
: Boolean;
200 FFireAngle
: SmallInt;
204 FShellTimer
: Integer;
206 FSawSound
: TPlayableSound
;
207 FSawSoundIdle
: TPlayableSound
;
208 FSawSoundHit
: TPlayableSound
;
209 FSawSoundSelect
: TPlayableSound
;
210 FFlameSoundOn
: TPlayableSound
;
211 FFlameSoundOff
: TPlayableSound
;
212 FFlameSoundWork
: TPlayableSound
;
213 FJetSoundOn
: TPlayableSound
;
214 FJetSoundOff
: TPlayableSound
;
215 FJetSoundFly
: TPlayableSound
;
219 FJustTeleported
: Boolean;
221 mEDamageType
: Integer;
224 function CollideLevel(XInc
, YInc
: Integer): Boolean;
225 function StayOnStep(XInc
, YInc
: Integer): Boolean;
226 function HeadInLiquid(XInc
, YInc
: Integer): Boolean;
227 function BodyInLiquid(XInc
, YInc
: Integer): Boolean;
228 function BodyInAcid(XInc
, YInc
: Integer): Boolean;
229 function FullInLift(XInc
, YInc
: Integer): Integer;
230 {procedure CollideItem();}
231 procedure FlySmoke(Times
: DWORD
= 1);
232 procedure OnFireFlame(Times
: DWORD
= 1);
233 procedure SetAction(Action
: Byte; Force
: Boolean = False);
234 procedure OnDamage(Angle
: SmallInt); virtual;
235 function firediry(): Integer;
238 procedure Run(Direction
: TDirection
);
239 procedure NextWeapon();
240 procedure PrevWeapon();
247 function getNextWeaponIndex (): Byte; // return 255 for "no switch"
248 procedure resetWeaponQueue ();
249 function hasAmmoForWeapon (weapon
: Byte): Boolean;
251 procedure doDamage (v
: Integer);
253 function refreshCorpse(): Boolean;
256 FDamageBuffer
: Integer;
258 FAmmo
: Array [A_BULLETS
..A_HIGH
] of Word;
259 FMaxAmmo
: Array [A_BULLETS
..A_HIGH
] of Word;
260 FWeapon
: Array [WP_FIRST
..WP_LAST
] of Boolean;
261 FRulez
: Set of R_ITEM_BACKPACK
..R_BERSERK
;
263 FMegaRulez
: Array [MR_SUIT
..MR_MAX
] of DWORD
;
264 FReloading
: Array [WP_FIRST
..WP_LAST
] of Word;
265 FTime
: Array [T_RESPAWN
..T_FLAGCAP
] of DWORD
;
266 FKeys
: Array [KEY_LEFT
..KEY_CHAT
] of TKeyState
;
268 FPreferredTeam
: Byte;
271 FWantsInGame
: Boolean;
276 FActualModelName
: string;
283 FSpawnInvul
: Integer;
285 FWaitForFirstSpawn
: Boolean; // set to `true` in server, used to spawn a player on first full state request
288 // debug: viewport offset
289 viewPortX
, viewPortY
, viewPortW
, viewPortH
: Integer;
291 function isValidViewPort (): Boolean; inline;
293 constructor Create(); virtual;
294 destructor Destroy(); override;
295 procedure Respawn(Silent
: Boolean; Force
: Boolean = False); virtual;
296 function GetRespawnPoint(): Byte;
297 procedure PressKey(Key
: Byte; Time
: Word = 1);
298 procedure ReleaseKeys();
299 procedure SetModel(ModelName
: String);
300 procedure SetColor(Color
: TRGB
);
301 function GetColor(): TRGB
;
302 procedure SetWeapon(W
: Byte);
303 function IsKeyPressed(K
: Byte): Boolean;
304 function GetKeys(): Byte;
305 function PickItem(ItemType
: Byte; arespawn
: Boolean; var remove
: Boolean): Boolean; virtual;
306 function Collide(X
, Y
: Integer; Width
, Height
: Word): Boolean; overload
;
307 function Collide(Panel
: TPanel
): Boolean; overload
;
308 function Collide(X
, Y
: Integer): Boolean; overload
;
309 procedure SetDirection(Direction
: TDirection
);
310 procedure GetSecret();
311 function TeleportTo(X
, Y
: Integer; silent
: Boolean; dir
: Byte): Boolean;
313 procedure Push(vx
, vy
: Integer);
314 procedure ChangeModel(ModelName
: String);
315 procedure SwitchTeam
;
316 procedure ChangeTeam(Team
: Byte);
318 function GetFlag(Flag
: Byte): Boolean;
319 procedure SetFlag(Flag
: Byte);
320 function DropFlag(Silent
: Boolean = True): Boolean;
321 procedure AllRulez(Health
: Boolean);
322 procedure RestoreHealthArmor();
323 procedure FragCombo();
324 procedure GiveItem(ItemType
: Byte);
325 procedure Damage(value
: Word; SpawnerUID
: Word; vx
, vy
: Integer; t
: Byte); virtual;
326 function Heal(value
: Word; Soft
: Boolean): Boolean; virtual;
327 procedure MakeBloodVector(Count
: Word; VelX
, VelY
: Integer);
328 procedure MakeBloodSimple(Count
: Word);
329 procedure Kill(KillType
: Byte; SpawnerUID
: Word; t
: Byte);
330 procedure Reset(Force
: Boolean);
331 procedure Spectate(NoMove
: Boolean = False);
332 procedure SwitchNoClip
;
333 procedure SoftReset();
334 procedure PreUpdate();
335 procedure Update(); virtual;
336 procedure RememberState();
337 procedure RecallState();
338 procedure SaveState (st
: TStream
); virtual;
339 procedure LoadState (st
: TStream
); virtual;
340 procedure PauseSounds(Enable
: Boolean);
341 procedure NetFire(Wpn
: Byte; X
, Y
, AX
, AY
: Integer; WID
: Integer = -1);
342 procedure DoLerp(Level
: Integer = 2);
343 procedure SetLerp(XTo
, YTo
: Integer);
344 procedure QueueWeaponSwitch(Weapon
: Byte);
345 procedure RealizeCurrentWeapon();
349 procedure JetpackOff
;
350 procedure CatchFire(Attacker
: Word; Timeout
: Integer = PLAYER_BURN_TIME
);
352 //WARNING! this does nothing for now, but still call it!
353 procedure positionChanged (); //WARNING! call this after entity position was changed, or coldet will not work right!
355 procedure getMapBox (out x
, y
, w
, h
: Integer); inline;
356 procedure moveBy (dx
, dy
: Integer); inline;
358 function getCameraObj(): TObj
;
360 function GetAmmoByWeapon(Weapon
: Byte): Word; // private state
363 property Vel
: TPoint2i read FObj
.Vel
;
364 property Obj
: TObj read FObj
;
366 property Name
: String read FName write FName
;
367 property Model
: TPlayerModel read FModel
;
368 property Health
: Integer read FHealth write FHealth
;
369 property Lives
: Byte read FLives write FLives
;
370 property Armor
: Integer read FArmor write FArmor
;
371 property Air
: Integer read FAir write FAir
;
372 property JetFuel
: Integer read FJetFuel write FJetFuel
;
373 property Frags
: Integer read FFrags write FFrags
;
374 property Death
: Integer read FDeath write FDeath
;
375 property Kills
: Integer read FKills write FKills
;
376 property CurrWeap
: Byte read FCurrWeap write FCurrWeap
;
377 property MonsterKills
: Integer read FMonsterKills write FMonsterKills
;
378 property Secrets
: Integer read FSecrets
;
379 property GodMode
: Boolean read FGodMode write FGodMode
;
380 property NoTarget
: Boolean read FNoTarget write FNoTarget
;
381 property NoReload
: Boolean read FNoReload write FNoReload
;
382 property alive
: Boolean read FAlive write FAlive
;
383 property Flag
: Byte read FFlag
;
384 property Team
: Byte read FTeam write FTeam
;
385 property Direction
: TDirection read FDirection
;
386 property GameX
: Integer read FObj
.X write FObj
.X
;
387 property GameY
: Integer read FObj
.Y write FObj
.Y
;
388 property GameVelX
: Integer read FObj
.Vel
.X write FObj
.Vel
.X
;
389 property GameVelY
: Integer read FObj
.Vel
.Y write FObj
.Vel
.Y
;
390 property GameAccelX
: Integer read FObj
.Accel
.X write FObj
.Accel
.X
;
391 property GameAccelY
: Integer read FObj
.Accel
.Y write FObj
.Accel
.Y
;
392 property IncCam
: Integer read FIncCam write FIncCam
;
393 property IncCamOld
: Integer read FIncCamOld write FIncCamOld
;
394 property SlopeOld
: Integer read FSlopeOld write FSlopeOld
;
395 property UID
: Word read FUID write FUID
;
396 property JustTeleported
: Boolean read FJustTeleported write FJustTeleported
;
397 property NetTime
: LongWord read FNetTime write FNetTime
;
400 property Angle_
: SmallInt read FAngle
;
401 property Spectator
: Boolean read FSpectator
;
402 property NoRespawn
: Boolean read FNoRespawn
;
403 property Berserk
: Integer read FBerserk
;
404 property Pain
: Integer read FPain
;
405 property Pickup
: Integer read FPickup
;
406 property PunchAnim
: TAnimationState read FPunchAnim write FPunchAnim
;
407 property SpawnInvul
: Integer read FSpawnInvul
;
408 property Ghost
: Boolean read FGhost
;
411 property eName
: String read FName write FName
;
412 property eHealth
: Integer read FHealth write FHealth
;
413 property eLives
: Byte read FLives write FLives
;
414 property eArmor
: Integer read FArmor write FArmor
;
415 property eAir
: Integer read FAir write FAir
;
416 property eJetFuel
: Integer read FJetFuel write FJetFuel
;
417 property eFrags
: Integer read FFrags write FFrags
;
418 property eDeath
: Integer read FDeath write FDeath
;
419 property eKills
: Integer read FKills write FKills
;
420 property eCurrWeap
: Byte read FCurrWeap write FCurrWeap
;
421 property eMonsterKills
: Integer read FMonsterKills write FMonsterKills
;
422 property eSecrets
: Integer read FSecrets write FSecrets
;
423 property eGodMode
: Boolean read FGodMode write FGodMode
;
424 property eNoTarget
: Boolean read FNoTarget write FNoTarget
;
425 property eNoReload
: Boolean read FNoReload write FNoReload
;
426 property eAlive
: Boolean read FAlive write FAlive
;
427 property eFlag
: Byte read FFlag
;
428 property eTeam
: Byte read FTeam write FTeam
;
429 property eDirection
: TDirection read FDirection
;
430 property eGameX
: Integer read FObj
.X write FObj
.X
;
431 property eGameY
: Integer read FObj
.Y write FObj
.Y
;
432 property eGameVelX
: Integer read FObj
.Vel
.X write FObj
.Vel
.X
;
433 property eGameVelY
: Integer read FObj
.Vel
.Y write FObj
.Vel
.Y
;
434 property eGameAccelX
: Integer read FObj
.Accel
.X write FObj
.Accel
.X
;
435 property eGameAccelY
: Integer read FObj
.Accel
.Y write FObj
.Accel
.Y
;
436 property eIncCam
: Integer read FIncCam write FIncCam
;
437 property eUID
: Word read FUID
;
438 property eJustTeleported
: Boolean read FJustTeleported
;
439 property eNetTime
: LongWord read FNetTime
;
441 // set this before assigning something to `eDamage`
442 property eDamageType
: Integer read mEDamageType write mEDamageType
;
443 property eDamage
: Integer write doDamage
;
454 WeaponPrior
: packed array [WP_FIRST
..WP_LAST
] of Byte;
455 CloseWeaponPrior
: packed array [WP_FIRST
..WP_LAST
] of Byte;
456 //SafeWeaponPrior: Array [WP_FIRST..WP_LAST] of Byte;
459 procedure save (st
: TStream
);
460 procedure load (st
: TStream
);
468 TBot
= class(TPlayer
)
470 FSelectedWeapon
: Byte;
473 FAIFlags
: Array of TAIFlag
;
474 FDifficult
: TDifficult
;
476 function GetRnd(a
: Byte): Boolean;
477 function GetInterval(a
: Byte; radius
: SmallInt): SmallInt;
478 function RunDirection(): TDirection
;
479 function FullInStep(XInc
, YInc
: Integer): Boolean;
480 //function NeedItem(Item: Byte): Byte;
481 procedure SelectWeapon(Dist
: Integer);
482 procedure SetAIFlag(aName
, fValue
: String20
);
483 function GetAIFlag(aName
: String20
): String20
;
484 procedure RemoveAIFlag(aName
: String20
);
485 function Healthy(): Byte;
486 procedure UpdateMove();
487 procedure UpdateCombat();
488 function KeyPressed(Key
: Word): Boolean;
489 procedure ReleaseKey(Key
: Byte);
490 function TargetOnScreen(TX
, TY
: Integer): Boolean;
491 procedure OnDamage(Angle
: SmallInt); override;
494 procedure Respawn(Silent
: Boolean; Force
: Boolean = False); override;
495 constructor Create(); override;
496 destructor Destroy(); override;
497 function PickItem(ItemType
: Byte; force
: Boolean; var remove
: Boolean): Boolean; override;
498 function Heal(value
: Word; Soft
: Boolean): Boolean; override;
499 procedure Update(); override;
500 procedure SaveState (st
: TStream
); override;
501 procedure LoadState (st
: TStream
); override;
514 procedure getMapBox (out x
, y
, w
, h
: Integer); inline;
515 procedure moveBy (dx
, dy
: Integer); inline;
517 procedure positionChanged (); inline; //WARNING! call this after entity position was changed, or coldet will not work right!
529 procedure getMapBox (out x
, y
, w
, h
: Integer); inline;
530 procedure moveBy (dx
, dy
: Integer); inline;
532 procedure positionChanged (); inline; //WARNING! call this after entity position was changed, or coldet will not work right!
535 TCorpse
= class{$IFDEF USE_MEMPOOL}(TPoolObject
){$ENDIF}
542 FModel
: TPlayerModel
;
545 constructor Create(X
, Y
: Integer; ModelName
: String; aMess
: Boolean);
546 destructor Destroy(); override;
547 procedure Damage(Value
: Word; SpawnerUID
: Word; vx
, vy
: Integer);
549 procedure SaveState (st
: TStream
);
550 procedure LoadState (st
: TStream
);
552 procedure getMapBox (out x
, y
, w
, h
: Integer); inline;
553 procedure moveBy (dx
, dy
: Integer); inline;
555 procedure positionChanged (); inline; //WARNING! call this after entity position was changed, or coldet will not work right!
557 function ObjPtr (): PObj
; inline;
559 property Obj
: TObj read FObj
; // copies object
560 property State
: Byte read FState
;
561 property Mess
: Boolean read FMess
;
562 property Model
: TPlayerModel read FModel
;
565 TTeamStat
= Array [TEAM_RED
..TEAM_BLUE
] of
571 gPlayers
: Array of TPlayer
;
572 gCorpses
: Array of TCorpse
;
573 gGibs
: Array of TGib
;
574 gShells
: Array of TShell
;
575 gTeamStat
: TTeamStat
;
576 gFly
: Boolean = False;
577 gAimLine
: Boolean = False;
578 gChatBubble
: Integer = 0;
579 gPlayerIndicator
: Integer = 1;
580 gPlayerIndicatorStyle
: Integer = 0;
582 gSpectLatchPID1
: Word = 0;
583 gSpectLatchPID2
: Word = 0;
584 MAX_RUNVEL
: Integer = 8;
585 VEL_JUMP
: Integer = 10;
586 SHELL_TIMEOUT
: Cardinal = 60000;
588 function Lerp(X
, Y
, Factor
: Integer): Integer;
590 procedure g_Gibs_SetMax(Count
: Word);
591 function g_Gibs_GetMax(): Word;
592 procedure g_Corpses_SetMax(Count
: Word);
593 function g_Corpses_GetMax(): Word;
594 procedure g_Shells_SetMax(Count
: Word);
595 function g_Shells_GetMax(): Word;
597 procedure g_Player_Init();
598 procedure g_Player_Free();
599 function g_Player_Create(ModelName
: String; Color
: TRGB
; Team
: Byte; Bot
: Boolean): Word;
600 function g_Player_CreateFromState (st
: TStream
): Word;
601 procedure g_Player_Remove(UID
: Word);
602 procedure g_Player_ResetTeams();
603 procedure g_Player_PreUpdate();
604 procedure g_Player_UpdateAll();
605 procedure g_Player_RememberAll();
606 procedure g_Player_ResetAll(Force
, Silent
: Boolean);
607 function g_Player_Get(UID
: Word): TPlayer
;
608 function g_Player_GetCount(): Byte;
609 function g_Player_GetStats(): TPlayerStatArray
;
610 function g_Player_ValidName(Name
: String): Boolean;
611 function g_Player_CreateCorpse(Player
: TPlayer
): Integer;
612 procedure g_Player_CreateGibs (fX
, fY
, mid
: Integer; fColor
: TRGB
);
613 procedure g_Player_CreateShell(fX
, fY
, dX
, dY
: Integer; T
: Byte);
614 procedure g_Player_UpdatePhysicalObjects();
615 procedure g_Player_RemoveAllCorpses();
616 procedure g_Player_Corpses_SaveState (st
: TStream
);
617 procedure g_Player_Corpses_LoadState (st
: TStream
);
618 procedure g_Player_ResetReady();
619 procedure g_Bot_Add(Team
, Difficult
: Byte; Handicap
: Integer = 100);
620 procedure g_Bot_AddList(Team
: Byte; lname
: ShortString; num
: Integer = -1; Handicap
: Integer = 100);
621 procedure g_Bot_MixNames();
622 procedure g_Bot_RemoveAll();
627 {$IFDEF ENABLE_HOLMES}
633 e_log
, g_map
, g_items
, g_console
, g_gfx
, Math
,
634 g_options
, g_triggers
, g_menu
, g_game
, g_grid
, e_res
,
635 wadreader
, g_monsters
, CONFIG
, g_language
,
639 const PLR_SAVE_VERSION
= 0;
649 diag_precision
: Byte;
653 w_prior1
: Array [WP_FIRST
..WP_LAST
] of Byte;
654 w_prior2
: Array [WP_FIRST
..WP_LAST
] of Byte;
655 w_prior3
: Array [WP_FIRST
..WP_LAST
] of Byte;
659 TIME_RESPAWN1
= 1500;
660 TIME_RESPAWN2
= 2000;
661 TIME_RESPAWN3
= 3000;
662 PLAYER_SUIT_TIME
= 30000;
663 PLAYER_INVUL_TIME
= 30000;
664 PLAYER_INVIS_TIME
= 35000;
665 FRAG_COMBO_TIME
= 3000;
668 PLAYER_HEADRECT
: TRectWH
= (X
:24; Y
:12; Width
:20; Height
:12);
671 BOT_UNSAFEDIST
= 128;
672 DIFFICULT_EASY
: TDifficult
= (DiagFire
: 32; InvisFire
: 32; DiagPrecision
: 32;
673 FlyPrecision
: 32; Cover
: 32; CloseJump
: 32;
674 WeaponPrior
:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior
:(0,0,0,0,0,0,0,0,0,0,0));
675 DIFFICULT_MEDIUM
: TDifficult
= (DiagFire
: 127; InvisFire
: 127; DiagPrecision
: 127;
676 FlyPrecision
: 127; Cover
: 127; CloseJump
: 127;
677 WeaponPrior
:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior
:(0,0,0,0,0,0,0,0,0,0,0));
678 DIFFICULT_HARD
: TDifficult
= (DiagFire
: 255; InvisFire
: 255; DiagPrecision
: 255;
679 FlyPrecision
: 255; Cover
: 255; CloseJump
: 255;
680 WeaponPrior
:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior
:(0,0,0,0,0,0,0,0,0,0,0));
681 WEAPON_PRIOR1
: Array [WP_FIRST
..WP_LAST
] of Byte =
682 (WEAPON_FLAMETHROWER
, WEAPON_SUPERPULEMET
,
683 WEAPON_SHOTGUN2
, WEAPON_SHOTGUN1
,
684 WEAPON_CHAINGUN
, WEAPON_PLASMA
, WEAPON_ROCKETLAUNCHER
,
685 WEAPON_BFG
, WEAPON_PISTOL
, WEAPON_SAW
, WEAPON_KASTET
);
686 WEAPON_PRIOR2
: Array [WP_FIRST
..WP_LAST
] of Byte =
687 (WEAPON_FLAMETHROWER
, WEAPON_SUPERPULEMET
,
688 WEAPON_BFG
, WEAPON_ROCKETLAUNCHER
,
689 WEAPON_SHOTGUN2
, WEAPON_PLASMA
, WEAPON_SHOTGUN1
,
690 WEAPON_CHAINGUN
, WEAPON_PISTOL
, WEAPON_SAW
, WEAPON_KASTET
);
691 //WEAPON_PRIOR3: Array [WP_FIRST..WP_LAST] of Byte =
692 // (WEAPON_FLAMETHROWER, WEAPON_SUPERPULEMET,
693 // WEAPON_BFG, WEAPON_PLASMA, WEAPON_SHOTGUN2,
694 // WEAPON_CHAINGUN, WEAPON_SHOTGUN1, WEAPON_SAW,
695 // WEAPON_ROCKETLAUNCHER, WEAPON_PISTOL, WEAPON_KASTET);
696 WEAPON_RELOAD
: Array [WP_FIRST
..WP_LAST
] of Byte =
697 (5, 2, 6, 18, 36, 2, 12, 2, 14, 2, 2);
699 PLAYER_SIGNATURE
= $52594C50; // 'PLYR'
700 CORPSE_SIGNATURE
= $50524F43; // 'CORP'
702 BOTNAMES_FILENAME
= 'botnames.txt';
703 BOTLIST_FILENAME
= 'botlist.txt';
707 MaxCorpses
: Word = 20;
708 MaxShells
: Word = 300;
709 CurrentGib
: Integer = 0;
710 CurrentShell
: Integer = 0;
711 BotNames
: Array of String;
712 BotList
: Array of TBotProfile
;
713 SavedStates
: Array of TPlayerSavedState
;
716 function Lerp(X
, Y
, Factor
: Integer): Integer;
718 Result
:= X
+ ((Y
- X
) div Factor
);
721 function SameTeam(UID1
, UID2
: Word): Boolean;
725 if (UID1
> UID_MAX_PLAYER
) or (UID1
<= UID_MAX_GAME
) or
726 (UID2
> UID_MAX_PLAYER
) or (UID2
<= UID_MAX_GAME
) then Exit
;
728 if (g_Player_Get(UID1
) = nil) or (g_Player_Get(UID2
) = nil) then Exit
;
730 if ((g_Player_Get(UID1
).Team
= TEAM_NONE
) or
731 (g_Player_Get(UID2
).Team
= TEAM_NONE
)) then Exit
;
733 Result
:= g_Player_Get(UID1
).FTeam
= g_Player_Get(UID2
).FTeam
;
736 procedure g_Gibs_SetMax(Count
: Word);
739 SetLength(gGibs
, Count
);
741 if CurrentGib
>= Count
then
745 function g_Gibs_GetMax(): Word;
750 procedure g_Shells_SetMax(Count
: Word);
753 SetLength(gShells
, Count
);
755 if CurrentShell
>= Count
then
759 function g_Shells_GetMax(): Word;
765 procedure g_Corpses_SetMax(Count
: Word);
768 SetLength(gCorpses
, Count
);
771 function g_Corpses_GetMax(): Word;
773 Result
:= MaxCorpses
;
776 function g_Player_Create(ModelName
: String; Color
: TRGB
; Team
: Byte; Bot
: Boolean): Word;
786 // Есть ли место в gPlayers:
787 if gPlayers
<> nil then
788 for a
:= 0 to High(gPlayers
) do
789 if gPlayers
[a
] = nil then
795 // Нет места - расширяем gPlayers:
798 SetLength(gPlayers
, Length(gPlayers
)+1);
802 // Создаем объект игрока:
804 gPlayers
[a
] := TBot
.Create()
806 gPlayers
[a
] := TPlayer
.Create();
809 gPlayers
[a
].FActualModelName
:= ModelName
;
810 gPlayers
[a
].SetModel(ModelName
);
812 // Нет модели - создание не возможно:
813 if gPlayers
[a
].FModel
= nil then
817 g_FatalError(Format(_lc
[I_GAME_ERROR_MODEL
], [ModelName
]));
821 if not (Team
in [TEAM_RED
, TEAM_BLUE
]) then
822 if Random(2) = 0 then
826 gPlayers
[a
].FPreferredTeam
:= Team
;
828 case gGameSettings
.GameMode
of
829 GM_DM
: gPlayers
[a
].FTeam
:= TEAM_NONE
;
831 GM_CTF
: gPlayers
[a
].FTeam
:= gPlayers
[a
].FPreferredTeam
;
833 GM_COOP
: gPlayers
[a
].FTeam
:= TEAM_COOP
;
836 // Если командная игра - красим модель в цвет команды:
837 gPlayers
[a
].FColor
:= Color
;
838 if gPlayers
[a
].FTeam
in [TEAM_RED
, TEAM_BLUE
] then
839 gPlayers
[a
].FModel
.Color
:= TEAMCOLOR
[gPlayers
[a
].FTeam
]
841 gPlayers
[a
].FModel
.Color
:= Color
;
843 gPlayers
[a
].FUID
:= g_CreateUID(UID_PLAYER
);
844 gPlayers
[a
].FAlive
:= False;
846 Result
:= gPlayers
[a
].FUID
;
849 function g_Player_CreateFromState (st
: TStream
): Word;
856 if (st
= nil) then exit
; //???
859 if not utils
.checkSign(st
, 'PLYR') then raise XStreamError
.Create('invalid player signature');
860 if (utils
.readByte(st
) <> PLR_SAVE_VERSION
) then raise XStreamError
.Create('invalid player version');
863 Bot
:= utils
.readBool(st
);
868 // Есть ли место в gPlayers:
869 for a
:= 0 to High(gPlayers
) do if (gPlayers
[a
] = nil) then begin ok
:= true; break
; end;
871 // Нет места - расширяем gPlayers
874 SetLength(gPlayers
, Length(gPlayers
)+1);
878 // Создаем объект игрока
880 gPlayers
[a
] := TBot
.Create()
882 gPlayers
[a
] := TPlayer
.Create();
883 gPlayers
[a
].FIamBot
:= Bot
;
884 gPlayers
[a
].FPhysics
:= True;
887 gPlayers
[a
].FUID
:= utils
.readWord(st
);
889 gPlayers
[a
].FName
:= utils
.readStr(st
);
891 gPlayers
[a
].FTeam
:= utils
.readByte(st
);
892 gPlayers
[a
].FPreferredTeam
:= gPlayers
[a
].FTeam
;
894 gPlayers
[a
].FAlive
:= utils
.readBool(st
);
895 // Израсходовал ли все жизни
896 gPlayers
[a
].FNoRespawn
:= utils
.readBool(st
);
898 b
:= utils
.readByte(st
);
899 if b
= 1 then gPlayers
[a
].FDirection
:= TDirection
.D_LEFT
else gPlayers
[a
].FDirection
:= TDirection
.D_RIGHT
; // b = 2
901 gPlayers
[a
].FHealth
:= utils
.readLongInt(st
);
903 gPlayers
[a
].FHandicap
:= utils
.readLongInt(st
);
905 gPlayers
[a
].FLives
:= utils
.readByte(st
);
907 gPlayers
[a
].FArmor
:= utils
.readLongInt(st
);
909 gPlayers
[a
].FAir
:= utils
.readLongInt(st
);
911 gPlayers
[a
].FJetFuel
:= utils
.readLongInt(st
);
913 gPlayers
[a
].FPain
:= utils
.readLongInt(st
);
915 gPlayers
[a
].FKills
:= utils
.readLongInt(st
);
917 gPlayers
[a
].FMonsterKills
:= utils
.readLongInt(st
);
919 gPlayers
[a
].FFrags
:= utils
.readLongInt(st
);
921 gPlayers
[a
].FFragCombo
:= utils
.readByte(st
);
922 // Время последнего фрага
923 gPlayers
[a
].FLastFrag
:= utils
.readLongWord(st
);
925 gPlayers
[a
].FDeath
:= utils
.readLongInt(st
);
927 gPlayers
[a
].FFlag
:= utils
.readByte(st
);
929 gPlayers
[a
].FSecrets
:= utils
.readLongInt(st
);
931 gPlayers
[a
].FCurrWeap
:= utils
.readByte(st
);
932 // Следующее желаемое оружие
933 gPlayers
[a
].FNextWeap
:= utils
.readWord(st
);
935 gPlayers
[a
].FNextWeapDelay
:= utils
.readByte(st
);
937 gPlayers
[a
].FBFGFireCounter
:= utils
.readSmallInt(st
);
939 gPlayers
[a
].FDamageBuffer
:= utils
.readLongInt(st
);
940 // Последний ударивший
941 gPlayers
[a
].FLastSpawnerUID
:= utils
.readWord(st
);
942 // Тип последнего полученного урона
943 gPlayers
[a
].FLastHit
:= utils
.readByte(st
);
945 Obj_LoadState(@gPlayers
[a
].FObj
, st
);
946 // Текущее количество патронов
947 for i
:= A_BULLETS
to A_HIGH
do gPlayers
[a
].FAmmo
[i
] := utils
.readWord(st
);
948 // Максимальное количество патронов
949 for i
:= A_BULLETS
to A_HIGH
do gPlayers
[a
].FMaxAmmo
[i
] := utils
.readWord(st
);
951 for i
:= WP_FIRST
to WP_LAST
do gPlayers
[a
].FWeapon
[i
] := utils
.readBool(st
);
952 // Время перезарядки оружия
953 for i
:= WP_FIRST
to WP_LAST
do gPlayers
[a
].FReloading
[i
] := utils
.readWord(st
);
955 if utils
.readBool(st
) then Include(gPlayers
[a
].FRulez
, R_ITEM_BACKPACK
);
956 // Наличие красного ключа
957 if utils
.readBool(st
) then Include(gPlayers
[a
].FRulez
, R_KEY_RED
);
958 // Наличие зеленого ключа
959 if utils
.readBool(st
) then Include(gPlayers
[a
].FRulez
, R_KEY_GREEN
);
960 // Наличие синего ключа
961 if utils
.readBool(st
) then Include(gPlayers
[a
].FRulez
, R_KEY_BLUE
);
963 if utils
.readBool(st
) then Include(gPlayers
[a
].FRulez
, R_BERSERK
);
964 // Время действия специальных предметов
965 for i
:= MR_SUIT
to MR_MAX
do gPlayers
[a
].FMegaRulez
[i
] := utils
.readLongWord(st
);
966 // Время до повторного респауна, смены оружия, исользования, захвата флага
967 for i
:= T_RESPAWN
to T_FLAGCAP
do gPlayers
[a
].FTime
[i
] := utils
.readLongWord(st
);
970 gPlayers
[a
].FActualModelName
:= utils
.readStr(st
);
972 gPlayers
[a
].FColor
.R
:= utils
.readByte(st
);
973 gPlayers
[a
].FColor
.G
:= utils
.readByte(st
);
974 gPlayers
[a
].FColor
.B
:= utils
.readByte(st
);
975 // Обновляем модель игрока
976 gPlayers
[a
].SetModel(gPlayers
[a
].FActualModelName
);
978 // Нет модели - создание невозможно
979 if (gPlayers
[a
].FModel
= nil) then
983 g_FatalError(Format(_lc
[I_GAME_ERROR_MODEL
], [gPlayers
[a
].FActualModelName
]));
987 // Если командная игра - красим модель в цвет команды
988 if gGameSettings
.GameMode
in [GM_TDM
, GM_CTF
] then
989 gPlayers
[a
].FModel
.Color
:= TEAMCOLOR
[gPlayers
[a
].FTeam
]
991 gPlayers
[a
].FModel
.Color
:= gPlayers
[a
].FColor
;
993 result
:= gPlayers
[a
].FUID
;
997 procedure g_Player_ResetTeams();
1001 if g_Game_IsClient
then
1003 if gPlayers
= nil then
1005 for a
:= Low(gPlayers
) to High(gPlayers
) do
1006 if gPlayers
[a
] <> nil then
1007 case gGameSettings
.GameMode
of
1009 gPlayers
[a
].ChangeTeam(TEAM_NONE
);
1011 if not (gPlayers
[a
].Team
in [TEAM_RED
, TEAM_BLUE
]) then
1012 if gPlayers
[a
].FPreferredTeam
in [TEAM_RED
, TEAM_BLUE
] then
1013 gPlayers
[a
].ChangeTeam(gPlayers
[a
].FPreferredTeam
)
1016 gPlayers
[a
].ChangeTeam(TEAM_RED
)
1018 gPlayers
[a
].ChangeTeam(TEAM_BLUE
);
1021 gPlayers
[a
].ChangeTeam(TEAM_COOP
);
1025 procedure g_Bot_Add(Team
, Difficult
: Byte; Handicap
: Integer = 100);
1028 _name
, _model
: String;
1031 if not g_Game_IsServer
then Exit
;
1033 // Список названий моделей:
1034 m
:= g_PlayerModel_GetNames();
1039 if (gGameSettings
.GameType
= GT_SINGLE
) or (gGameSettings
.GameMode
= GM_COOP
) then
1040 Team
:= TEAM_COOP
// COOP
1042 if gGameSettings
.GameMode
= GM_DM
then
1043 Team
:= TEAM_NONE
// DM
1045 if Team
= TEAM_NONE
then // CTF / TDM
1047 // Автобаланс команд:
1051 for a
:= 0 to High(gPlayers
) do
1052 if gPlayers
[a
] <> nil then
1054 if gPlayers
[a
].Team
= TEAM_RED
then
1057 if gPlayers
[a
].Team
= TEAM_BLUE
then
1067 if Random(2) = 0 then
1073 // Выбираем боту имя:
1075 if BotNames
<> nil then
1076 for a
:= 0 to High(BotNames
) do
1077 if g_Player_ValidName(BotNames
[a
]) then
1079 _name
:= BotNames
[a
];
1083 // Выбираем случайную модель:
1084 _model
:= m
[Random(Length(m
))];
1087 with g_Player_Get(g_Player_Create(_model
,
1088 _RGB(Min(Random(9)*32, 255),
1089 Min(Random(9)*32, 255),
1090 Min(Random(9)*32, 255)),
1091 Team
, True)) as TBot
do
1093 // Если имени нет, делаем его из UID бота
1095 Name
:= Format('DFBOT%.5d', [UID
])
1100 1: FDifficult
:= DIFFICULT_EASY
;
1101 2: FDifficult
:= DIFFICULT_MEDIUM
;
1102 else FDifficult
:= DIFFICULT_HARD
;
1105 for a
:= WP_FIRST
to WP_LAST
do
1107 FDifficult
.WeaponPrior
[a
] := WEAPON_PRIOR1
[a
];
1108 FDifficult
.CloseWeaponPrior
[a
] := WEAPON_PRIOR2
[a
];
1109 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
1112 FHandicap
:= Handicap
;
1114 g_Console_Add(Format(_lc
[I_PLAYER_JOIN
], [Name
]), True);
1116 if g_Game_IsNet
then MH_SEND_PlayerCreate(UID
);
1117 if g_Game_IsServer
and (gGameSettings
.MaxLives
> 0) then
1122 procedure g_Bot_AddList(Team
: Byte; lName
: ShortString; num
: Integer = -1; Handicap
: Integer = 100);
1125 _name
, _model
: String;
1128 if not g_Game_IsServer
then Exit
;
1130 // Список названий моделей:
1131 m
:= g_PlayerModel_GetNames();
1136 if (gGameSettings
.GameType
= GT_SINGLE
) or (gGameSettings
.GameMode
= GM_COOP
) then
1137 Team
:= TEAM_COOP
// COOP
1139 if gGameSettings
.GameMode
= GM_DM
then
1140 Team
:= TEAM_NONE
// DM
1142 if Team
= TEAM_NONE
then
1143 Team
:= BotList
[num
].team
; // CTF / TDM
1145 // Выбираем настройки бота из списка по номеру или имени:
1146 lName
:= AnsiLowerCase(lName
);
1147 if (num
< 0) or (num
> Length(BotList
)-1) then
1149 if (num
= -1) and (lName
<> '') and (BotList
<> nil) then
1150 for a
:= 0 to High(BotList
) do
1151 if AnsiLowerCase(BotList
[a
].name
) = lName
then
1160 _name
:= BotList
[num
].name
;
1161 // Занято - выбираем случайное:
1162 if not g_Player_ValidName(_name
) then
1164 _name
:= Format('DFBOT%.2d', [Random(100)]);
1165 until g_Player_ValidName(_name
);
1168 _model
:= BotList
[num
].model
;
1169 // Нет такой - выбираем случайную:
1170 if not InSArray(_model
, m
) then
1171 _model
:= m
[Random(Length(m
))];
1174 with g_Player_Get(g_Player_Create(_model
, BotList
[num
].color
, Team
, True)) as TBot
do
1178 FDifficult
.DiagFire
:= BotList
[num
].diag_fire
;
1179 FDifficult
.InvisFire
:= BotList
[num
].invis_fire
;
1180 FDifficult
.DiagPrecision
:= BotList
[num
].diag_precision
;
1181 FDifficult
.FlyPrecision
:= BotList
[num
].fly_precision
;
1182 FDifficult
.Cover
:= BotList
[num
].cover
;
1183 FDifficult
.CloseJump
:= BotList
[num
].close_jump
;
1185 FHandicap
:= Handicap
;
1187 for a
:= WP_FIRST
to WP_LAST
do
1189 FDifficult
.WeaponPrior
[a
] := BotList
[num
].w_prior1
[a
];
1190 FDifficult
.CloseWeaponPrior
[a
] := BotList
[num
].w_prior2
[a
];
1191 //FDifficult.SafeWeaponPrior[a] := BotList[num].w_prior3[a];
1194 g_Console_Add(Format(_lc
[I_PLAYER_JOIN
], [Name
]), True);
1196 if g_Game_IsNet
then MH_SEND_PlayerCreate(UID
);
1200 procedure g_Bot_RemoveAll();
1204 if not g_Game_IsServer
then Exit
;
1205 if gPlayers
= nil then Exit
;
1207 for a
:= 0 to High(gPlayers
) do
1208 if gPlayers
[a
] <> nil then
1209 if gPlayers
[a
] is TBot
then
1211 gPlayers
[a
].Lives
:= 0;
1212 gPlayers
[a
].Kill(K_SIMPLEKILL
, 0, HIT_DISCON
);
1213 g_Console_Add(Format(_lc
[I_PLAYER_LEAVE
], [gPlayers
[a
].Name
]), True);
1214 g_Player_Remove(gPlayers
[a
].FUID
);
1220 procedure g_Bot_MixNames();
1225 if BotNames
<> nil then
1226 for a
:= 0 to High(BotNames
) do
1228 b
:= Random(Length(BotNames
));
1230 Botnames
[a
] := BotNames
[b
];
1235 procedure g_Player_Remove(UID
: Word);
1239 if gPlayers
= nil then Exit
;
1241 if g_Game_IsServer
and g_Game_IsNet
then
1242 MH_SEND_PlayerDelete(UID
);
1244 for i
:= 0 to High(gPlayers
) do
1245 if gPlayers
[i
] <> nil then
1246 if gPlayers
[i
].FUID
= UID
then
1248 if gPlayers
[i
] is TPlayer
then
1249 TPlayer(gPlayers
[i
]).Free()
1251 TBot(gPlayers
[i
]).Free();
1257 procedure g_Player_Init();
1268 path
:= BOTNAMES_FILENAME
;
1269 if e_FindResource(DataDirs
, path
) = false then
1272 // Читаем возможные имена ботов из файла:
1273 AssignFile(F
, path
);
1284 SetLength(BotNames
, Length(BotNames
)+1);
1285 BotNames
[High(BotNames
)] := s
;
1293 // Читаем файл с параметрами ботов:
1294 config
:= TConfig
.CreateFile(path
);
1298 while config
.SectionExists(IntToStr(a
)) do
1300 SetLength(BotList
, Length(BotList
)+1);
1302 with BotList
[High(BotList
)] do
1305 name
:= config
.ReadStr(IntToStr(a
), 'name', '');
1307 model
:= config
.ReadStr(IntToStr(a
), 'model', '');
1309 if config
.ReadStr(IntToStr(a
), 'team', 'red') = 'red' then
1314 sa
:= parse(config
.ReadStr(IntToStr(a
), 'color', ''));
1315 color
.R
:= StrToIntDef(sa
[0], 0);
1316 color
.G
:= StrToIntDef(sa
[1], 0);
1317 color
.B
:= StrToIntDef(sa
[2], 0);
1318 // Вероятность стрельбы под углом:
1319 diag_fire
:= config
.ReadInt(IntToStr(a
), 'diag_fire', 0);
1320 // Вероятность ответного огня по невидимому сопернику:
1321 invis_fire
:= config
.ReadInt(IntToStr(a
), 'invis_fire', 0);
1322 // Точность стрельбы под углом:
1323 diag_precision
:= config
.ReadInt(IntToStr(a
), 'diag_precision', 0);
1324 // Точность стрельбы в полете:
1325 fly_precision
:= config
.ReadInt(IntToStr(a
), 'fly_precision', 0);
1326 // Точность уклонения от снарядов:
1327 cover
:= config
.ReadInt(IntToStr(a
), 'cover', 0);
1328 // Вероятность прыжка при приближении соперника:
1329 close_jump
:= config
.ReadInt(IntToStr(a
), 'close_jump', 0);
1330 // Приоритеты оружия для дальнего боя:
1331 sa
:= parse(config
.ReadStr(IntToStr(a
), 'w_prior1', ''));
1332 if Length(sa
) = 10 then
1334 w_prior1
[b
] := EnsureRange(StrToInt(sa
[b
]), 0, 9);
1335 // Приоритеты оружия для ближнего боя:
1336 sa
:= parse(config
.ReadStr(IntToStr(a
), 'w_prior2', ''));
1337 if Length(sa
) = 10 then
1339 w_prior2
[b
] := EnsureRange(StrToInt(sa
[b
]), 0, 9);
1341 {sa := parse(config.ReadStr(IntToStr(a), 'w_prior3', ''));
1342 if Length(sa) = 10 then
1344 w_prior3[b] := EnsureRange(StrToInt(sa[b]), 0, 9);}
1351 SetLength(SavedStates
, 0);
1354 procedure g_Player_Free();
1358 if gPlayers
<> nil then
1360 for i
:= 0 to High(gPlayers
) do
1361 if gPlayers
[i
] <> nil then
1363 if gPlayers
[i
] is TPlayer
then
1364 TPlayer(gPlayers
[i
]).Free()
1366 TBot(gPlayers
[i
]).Free();
1375 SetLength(SavedStates
, 0);
1378 procedure g_Player_PreUpdate();
1382 if gPlayers
= nil then Exit
;
1383 for i
:= 0 to High(gPlayers
) do
1384 if gPlayers
[i
] <> nil then
1385 gPlayers
[i
].PreUpdate();
1388 procedure g_Player_UpdateAll();
1392 if gPlayers
= nil then Exit
;
1394 //e_WriteLog('***g_Player_UpdateAll: ENTER', MSG_WARNING);
1395 for i
:= 0 to High(gPlayers
) do
1397 if gPlayers
[i
] <> nil then
1399 if gPlayers
[i
] is TPlayer
then
1401 gPlayers
[i
].Update();
1402 gPlayers
[i
].RealizeCurrentWeapon(); // WARNING! DO NOT MOVE THIS INTO `Update()`!
1406 // bot updates weapons in `UpdateCombat()`
1407 TBot(gPlayers
[i
]).Update();
1411 //e_WriteLog('***g_Player_UpdateAll: EXIT', MSG_WARNING);
1414 function g_Player_Get(UID
: Word): TPlayer
;
1420 if gPlayers
= nil then
1423 for a
:= 0 to High(gPlayers
) do
1424 if gPlayers
[a
] <> nil then
1425 if gPlayers
[a
].FUID
= UID
then
1427 Result
:= gPlayers
[a
];
1432 function g_Player_GetCount(): Byte;
1438 if gPlayers
= nil then
1441 for a
:= 0 to High(gPlayers
) do
1442 if gPlayers
[a
] <> nil then
1443 Result
:= Result
+ 1;
1446 function g_Player_GetStats(): TPlayerStatArray
;
1452 if gPlayers
= nil then Exit
;
1454 for a
:= 0 to High(gPlayers
) do
1455 if gPlayers
[a
] <> nil then
1457 SetLength(Result
, Length(Result
)+1);
1458 with Result
[High(Result
)] do
1461 Ping
:= gPlayers
[a
].FPing
;
1462 Loss
:= gPlayers
[a
].FLoss
;
1463 Name
:= gPlayers
[a
].FName
;
1464 Team
:= gPlayers
[a
].FTeam
;
1465 Frags
:= gPlayers
[a
].FFrags
;
1466 Deaths
:= gPlayers
[a
].FDeath
;
1467 Kills
:= gPlayers
[a
].FKills
;
1468 Color
:= gPlayers
[a
].FModel
.Color
;
1469 Lives
:= gPlayers
[a
].FLives
;
1470 Spectator
:= gPlayers
[a
].FSpectator
;
1471 UID
:= gPlayers
[a
].FUID
;
1476 procedure g_Player_ResetReady();
1480 if not g_Game_IsServer
then Exit
;
1481 if gPlayers
= nil then Exit
;
1483 for a
:= 0 to High(gPlayers
) do
1484 if gPlayers
[a
] <> nil then
1486 gPlayers
[a
].FReady
:= False;
1487 if g_Game_IsNet
then
1488 MH_SEND_GameEvent(NET_EV_INTER_READY
, gPlayers
[a
].UID
, 'N');
1492 procedure g_Player_RememberAll
;
1496 for i
:= Low(gPlayers
) to High(gPlayers
) do
1497 if (gPlayers
[i
] <> nil) and gPlayers
[i
].alive
then
1498 gPlayers
[i
].RememberState
;
1501 procedure g_Player_ResetAll(Force
, Silent
: Boolean);
1505 gTeamStat
[TEAM_RED
].Goals
:= 0;
1506 gTeamStat
[TEAM_BLUE
].Goals
:= 0;
1508 if gPlayers
<> nil then
1509 for i
:= 0 to High(gPlayers
) do
1510 if gPlayers
[i
] <> nil then
1512 gPlayers
[i
].Reset(Force
);
1514 if gPlayers
[i
] is TPlayer
then
1516 if (not gPlayers
[i
].FSpectator
) or gPlayers
[i
].FWantsInGame
then
1517 gPlayers
[i
].Respawn(Silent
)
1519 gPlayers
[i
].Spectate();
1522 TBot(gPlayers
[i
]).Respawn(Silent
);
1526 function g_Player_CreateCorpse(Player
: TPlayer
): Integer;
1534 if Player
.alive
then
1537 // Разрываем связь с прежним трупом:
1538 i
:= Player
.FCorpse
;
1539 if (i
>= 0) and (i
< Length(gCorpses
)) then
1541 if (gCorpses
[i
] <> nil) and (gCorpses
[i
].FPlayerUID
= Player
.FUID
) then
1542 gCorpses
[i
].FPlayerUID
:= 0;
1545 if Player
.FObj
.Y
>= gMapInfo
.Height
+128 then
1550 if (FHealth
>= -50) or (gGibsCount
= 0) then
1552 if (gCorpses
= nil) or (Length(gCorpses
) = 0) then
1556 for find_id
:= 0 to High(gCorpses
) do
1557 if gCorpses
[find_id
] = nil then
1564 find_id
:= Random(Length(gCorpses
));
1566 gCorpses
[find_id
] := TCorpse
.Create(FObj
.X
, FObj
.Y
, FModel
.GetName(), FHealth
< -20);
1567 gCorpses
[find_id
].FModel
.Color
:= FModel
.Color
;
1568 gCorpses
[find_id
].FObj
.Vel
:= FObj
.Vel
;
1569 gCorpses
[find_id
].FObj
.Accel
:= FObj
.Accel
;
1570 gCorpses
[find_id
].FPlayerUID
:= FUID
;
1575 g_Player_CreateGibs(FObj
.X
+ PLAYER_RECT_CX
, FObj
.Y
+ PLAYER_RECT_CY
, FModel
.id
, FModel
.Color
);
1579 procedure g_Player_CreateShell(fX
, fY
, dX
, dY
: Integer; T
: Byte);
1581 if (gShells
= nil) or (Length(gShells
) = 0) then
1584 with gShells
[CurrentShell
] do
1589 if T
= SHELL_BULLET
then
1591 Obj
.Rect
.Width
:= 4;
1592 Obj
.Rect
.Height
:= 2;
1596 Obj
.Rect
.Width
:= 7;
1597 Obj
.Rect
.Height
:= 3;
1603 g_Obj_Push(@Obj
, dX
+ Random(4)-Random(4), dY
-Random(4));
1604 positionChanged(); // this updates spatial accelerators
1605 RAngle
:= Random(360);
1606 Timeout
:= gTime
+ SHELL_TIMEOUT
;
1608 if CurrentShell
>= High(gShells
) then
1615 procedure g_Player_CreateGibs (fX
, fY
, mid
: Integer; fColor
: TRGB
);
1618 GibsArray
: TGibsArray
;
1623 if (gGibs
= nil) or (Length(gGibs
) = 0) then
1625 if not g_PlayerModel_GetGibs(mid
, GibsArray
) then
1627 Blood
:= PlayerModelsArray
[mid
].Blood
;
1629 for a
:= 0 to High(GibsArray
) do
1630 with gGibs
[CurrentGib
] do
1633 GibID
:= GibsArray
[a
];
1638 Obj
.Rect
:= r_Render_GetGibRect(ModelID
, GibID
);
1642 Obj
.Rect
.Width
:= 16;
1643 Obj
.Rect
.Height
:= 16;
1645 Obj
.X
:= fX
- Obj
.Rect
.X
- (Obj
.Rect
.Width
div 2);
1646 Obj
.Y
:= fY
- Obj
.Rect
.Y
- (Obj
.Rect
.Height
div 2);
1647 g_Obj_PushA(@Obj
, 25 + Random(10), Random(361));
1648 positionChanged(); // this updates spatial accelerators
1649 RAngle
:= Random(360);
1651 if gBloodCount
> 0 then
1652 g_GFX_Blood(fX
, fY
, 16*gBloodCount
+Random(5*gBloodCount
), -16+Random(33), -16+Random(33),
1653 Random(48), Random(48), Blood
.R
, Blood
.G
, Blood
.B
, Blood
.Kind
);
1655 if CurrentGib
>= High(gGibs
) then
1662 procedure g_Player_UpdatePhysicalObjects();
1668 procedure ShellSound_Bounce(X
, Y
: Integer; T
: Byte);
1673 if T
= SHELL_BULLET
then
1674 g_Sound_PlayExAt('SOUND_PLAYER_CASING' + IntToStr(k
), X
, Y
)
1676 g_Sound_PlayExAt('SOUND_PLAYER_SHELL' + IntToStr(k
), X
, Y
);
1681 if gGibs
<> nil then
1682 for i
:= 0 to High(gGibs
) do
1683 if gGibs
[i
].alive
then
1690 mr
:= g_Obj_Move(@Obj
, True, False, True);
1691 positionChanged(); // this updates spatial accelerators
1693 if WordBool(mr
and MOVE_FALLOUT
) then
1699 // Отлетает от удара о стену/потолок/пол:
1700 if WordBool(mr
and MOVE_HITWALL
) then
1701 Obj
.Vel
.X
:= -(vel
.X
div 2);
1702 if WordBool(mr
and (MOVE_HITCEIL
or MOVE_HITLAND
)) then
1703 Obj
.Vel
.Y
:= -(vel
.Y
div 2);
1705 if (Obj
.Vel
.X
>= 0) then
1707 RAngle
:= RAngle
+ Abs(Obj
.Vel
.X
)*6 + Abs(Obj
.Vel
.Y
);
1708 if RAngle
>= 360 then
1709 RAngle
:= RAngle
mod 360;
1710 end else begin // Counter-clockwise
1711 RAngle
:= RAngle
- Abs(Obj
.Vel
.X
)*6 - Abs(Obj
.Vel
.Y
);
1713 RAngle
:= (360 - (Abs(RAngle
) mod 360)) mod 360;
1716 // Сопротивление воздуха для куска трупа:
1717 if gTime
mod (GAME_TICK
*3) = 0 then
1718 Obj
.Vel
.X
:= z_dec(Obj
.Vel
.X
, 1);
1722 if gCorpses
<> nil then
1723 for i
:= 0 to High(gCorpses
) do
1724 if gCorpses
[i
] <> nil then
1725 if gCorpses
[i
].State
= CORPSE_STATE_REMOVEME
then
1731 gCorpses
[i
].Update();
1734 if gShells
<> nil then
1735 for i
:= 0 to High(gShells
) do
1736 if gShells
[i
].alive
then
1743 mr
:= g_Obj_Move(@Obj
, True, False, True);
1744 positionChanged(); // this updates spatial accelerators
1746 if WordBool(mr
and MOVE_FALLOUT
) or (gShells
[i
].Timeout
< gTime
) then
1752 // Отлетает от удара о стену/потолок/пол:
1753 if WordBool(mr
and MOVE_HITWALL
) then
1755 Obj
.Vel
.X
:= -(vel
.X
div 2);
1756 if not WordBool(mr
and MOVE_INWATER
) then
1757 ShellSound_Bounce(Obj
.X
, Obj
.Y
, SType
);
1759 if WordBool(mr
and (MOVE_HITCEIL
or MOVE_HITLAND
)) then
1761 Obj
.Vel
.Y
:= -(vel
.Y
div 2);
1762 if Obj
.Vel
.X
<> 0 then Obj
.Vel
.X
:= Obj
.Vel
.X
div 2;
1763 if (Obj
.Vel
.X
= 0) and (Obj
.Vel
.Y
= 0) then
1765 if RAngle
mod 90 <> 0 then
1766 RAngle
:= (RAngle
div 90) * 90;
1768 else if not WordBool(mr
and MOVE_INWATER
) then
1769 ShellSound_Bounce(Obj
.X
, Obj
.Y
, SType
);
1772 if (Obj
.Vel
.X
>= 0) then
1774 RAngle
:= RAngle
+ Abs(Obj
.Vel
.X
)*8 + Abs(Obj
.Vel
.Y
);
1775 if RAngle
>= 360 then
1776 RAngle
:= RAngle
mod 360;
1777 end else begin // Counter-clockwise
1778 RAngle
:= RAngle
- Abs(Obj
.Vel
.X
)*8 - Abs(Obj
.Vel
.Y
);
1780 RAngle
:= (360 - (Abs(RAngle
) mod 360)) mod 360;
1786 procedure TGib
.getMapBox (out x
, y
, w
, h
: Integer); inline;
1788 x
:= Obj
.X
+Obj
.Rect
.X
;
1789 y
:= Obj
.Y
+Obj
.Rect
.Y
;
1790 w
:= Obj
.Rect
.Width
;
1791 h
:= Obj
.Rect
.Height
;
1794 procedure TGib
.moveBy (dx
, dy
: Integer); inline;
1796 if (dx
<> 0) or (dy
<> 0) then
1805 procedure TShell
.getMapBox (out x
, y
, w
, h
: Integer); inline;
1809 w
:= Obj
.Rect
.Width
;
1810 h
:= Obj
.Rect
.Height
;
1813 procedure TShell
.moveBy (dx
, dy
: Integer); inline;
1815 if (dx
<> 0) or (dy
<> 0) then
1824 procedure TGib
.positionChanged (); inline; begin end;
1825 procedure TShell
.positionChanged (); inline; begin end;
1828 procedure g_Player_RemoveAllCorpses();
1834 SetLength(gGibs
, MaxGibs
);
1835 SetLength(gShells
, MaxGibs
);
1839 if gCorpses
<> nil then
1840 for i
:= 0 to High(gCorpses
) do
1844 SetLength(gCorpses
, MaxCorpses
);
1847 procedure g_Player_Corpses_SaveState (st
: TStream
);
1851 // Считаем количество существующих трупов
1853 for i
:= 0 to High(gCorpses
) do if (gCorpses
[i
] <> nil) then Inc(count
);
1855 // Количество трупов
1856 utils
.writeInt(st
, LongInt(count
));
1858 if (count
= 0) then exit
;
1861 for i
:= 0 to High(gCorpses
) do
1863 if gCorpses
[i
] <> nil then
1866 utils
.writeStr(st
, gCorpses
[i
].FModel
.GetName());
1868 utils
.writeBool(st
, gCorpses
[i
].Mess
);
1869 // Сохраняем данные трупа:
1870 gCorpses
[i
].SaveState(st
);
1876 procedure g_Player_Corpses_LoadState (st
: TStream
);
1884 g_Player_RemoveAllCorpses();
1886 // Количество трупов:
1887 count
:= utils
.readLongInt(st
);
1888 if (count
< 0) or (count
> Length(gCorpses
)) then raise XStreamError
.Create('invalid number of corpses');
1890 if (count
= 0) then exit
;
1893 for i
:= 0 to count
-1 do
1896 str
:= utils
.readStr(st
);
1898 b
:= utils
.readBool(st
);
1900 gCorpses
[i
] := TCorpse
.Create(0, 0, str
, b
);
1901 // Загружаем данные трупа
1902 gCorpses
[i
].LoadState(st
);
1909 function TPlayer
.isValidViewPort (): Boolean; inline; begin result
:= (viewPortW
> 0) and (viewPortH
> 0); end;
1911 procedure TPlayer
.BFGHit();
1913 g_Weapon_BFGHit(FObj
.X
+FObj
.Rect
.X
+(FObj
.Rect
.Width
div 2),
1914 FObj
.Y
+FObj
.Rect
.Y
+(FObj
.Rect
.Height
div 2));
1915 if g_Game_IsServer
and g_Game_IsNet
then
1916 MH_SEND_Effect(FObj
.X
+FObj
.Rect
.X
+(FObj
.Rect
.Width
div 2),
1917 FObj
.Y
+FObj
.Rect
.Y
+(FObj
.Rect
.Height
div 2),
1921 procedure TPlayer
.ChangeModel(ModelName
: string);
1923 locModel
: TPlayerModel
;
1925 locModel
:= g_PlayerModel_Get(ModelName
);
1926 if locModel
= nil then Exit
;
1932 procedure TPlayer
.SetModel(ModelName
: string);
1936 m
:= g_PlayerModel_Get(ModelName
);
1939 g_SimpleError(Format(_lc
[I_GAME_ERROR_MODEL_FALLBACK
], [ModelName
]));
1940 m
:= g_PlayerModel_Get('doomer');
1943 g_FatalError(Format(_lc
[I_GAME_ERROR_MODEL
], ['doomer']));
1948 if FModel
<> nil then
1953 if not (gGameSettings
.GameMode
in [GM_TDM
, GM_CTF
]) then
1954 FModel
.Color
:= FColor
1956 FModel
.Color
:= TEAMCOLOR
[FTeam
];
1957 FModel
.SetWeapon(FCurrWeap
);
1958 FModel
.SetFlag(FFlag
);
1959 SetDirection(FDirection
);
1962 procedure TPlayer
.SetColor(Color
: TRGB
);
1965 if not (gGameSettings
.GameMode
in [GM_TDM
, GM_CTF
]) then
1966 if FModel
<> nil then FModel
.Color
:= Color
;
1969 function TPlayer
.GetColor(): TRGB
;
1971 result
:= FModel
.Color
;
1974 procedure TPlayer
.SwitchTeam
;
1976 if g_Game_IsClient
then
1978 if not (gGameSettings
.GameMode
in [GM_TDM
, GM_CTF
]) then Exit
;
1980 if gGameOn
and FAlive
then
1981 Kill(K_SIMPLEKILL
, FUID
, HIT_SELF
);
1983 if FTeam
= TEAM_RED
then
1985 ChangeTeam(TEAM_BLUE
);
1986 g_Console_Add(Format(_lc
[I_PLAYER_CHTEAM_BLUE
], [FName
]), True);
1987 if g_Game_IsNet
then
1988 MH_SEND_GameEvent(NET_EV_CHANGE_TEAM
, TEAM_BLUE
, FName
);
1992 ChangeTeam(TEAM_RED
);
1993 g_Console_Add(Format(_lc
[I_PLAYER_CHTEAM_RED
], [FName
]), True);
1994 if g_Game_IsNet
then
1995 MH_SEND_GameEvent(NET_EV_CHANGE_TEAM
, TEAM_RED
, FName
);
1997 FPreferredTeam
:= FTeam
;
2000 procedure TPlayer
.ChangeTeam(Team
: Byte);
2007 TEAM_RED
, TEAM_BLUE
:
2008 FModel
.Color
:= TEAMCOLOR
[Team
];
2010 FModel
.Color
:= FColor
;
2012 if (FTeam
<> OldTeam
) and g_Game_IsNet
and g_Game_IsServer
then
2013 MH_SEND_PlayerStats(FUID
);
2017 procedure TPlayer.CollideItem();
2022 if gItems = nil then Exit;
2023 if not FAlive then Exit;
2025 for i := 0 to High(gItems) do
2028 if (ItemType <> ITEM_NONE) and alive then
2029 if g_Obj_Collide(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width,
2030 PLAYER_RECT.Height, @Obj) then
2032 if not PickItem(ItemType, gItems[i].Respawnable, r) then Continue;
2034 if ItemType in [ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_INVUL] then
2035 g_Sound_PlayExAt('SOUND_ITEM_GETRULEZ', FObj.X, FObj.Y)
2036 else if ItemType in [ITEM_MEDKIT_SMALL, ITEM_MEDKIT_LARGE, ITEM_MEDKIT_BLACK] then
2037 g_Sound_PlayExAt('SOUND_ITEM_GETMED', FObj.X, FObj.Y)
2038 else g_Sound_PlayExAt('SOUND_ITEM_GETITEM', FObj.X, FObj.Y);
2040 // Надо убрать с карты, если это не ключ, которым нужно поделится с другим игроком:
2041 if r and not ((ItemType in [ITEM_KEY_RED, ITEM_KEY_GREEN, ITEM_KEY_BLUE]) and
2042 (gGameSettings.GameType = GT_SINGLE) and
2043 (g_Player_GetCount() > 1)) then
2044 if not Respawnable then g_Items_Remove(i) else g_Items_Pick(i);
2050 function TPlayer
.CollideLevel(XInc
, YInc
: Integer): Boolean;
2052 Result
:= g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
,
2053 PLAYER_RECT
.Width
, PLAYER_RECT
.Height
, PANEL_WALL
,
2057 constructor TPlayer
.Create();
2063 mEDamageType
:= HIT_SOME
;
2069 FSawSound
:= TPlayableSound
.Create();
2070 FSawSoundIdle
:= TPlayableSound
.Create();
2071 FSawSoundHit
:= TPlayableSound
.Create();
2072 FSawSoundSelect
:= TPlayableSound
.Create();
2073 FFlameSoundOn
:= TPlayableSound
.Create();
2074 FFlameSoundOff
:= TPlayableSound
.Create();
2075 FFlameSoundWork
:= TPlayableSound
.Create();
2076 FJetSoundFly
:= TPlayableSound
.Create();
2077 FJetSoundOn
:= TPlayableSound
.Create();
2078 FJetSoundOff
:= TPlayableSound
.Create();
2080 FSawSound
.SetByName('SOUND_WEAPON_FIRESAW');
2081 FSawSoundIdle
.SetByName('SOUND_WEAPON_IDLESAW');
2082 FSawSoundHit
.SetByName('SOUND_WEAPON_HITSAW');
2083 FSawSoundSelect
.SetByName('SOUND_WEAPON_SELECTSAW');
2084 FFlameSoundOn
.SetByName('SOUND_WEAPON_FLAMEON');
2085 FFlameSoundOff
.SetByName('SOUND_WEAPON_FLAMEOFF');
2086 FFlameSoundWork
.SetByName('SOUND_WEAPON_FLAMEWORK');
2087 FJetSoundFly
.SetByName('SOUND_PLAYER_JETFLY');
2088 FJetSoundOn
.SetByName('SOUND_PLAYER_JETON');
2089 FJetSoundOff
.SetByName('SOUND_PLAYER_JETOFF');
2091 FSpectatePlayer
:= -1;
2095 FSavedStateNum
:= -1;
2103 FActualModelName
:= 'doomer';
2106 FObj
.Rect
:= PLAYER_RECT
;
2108 FBFGFireCounter
:= -1;
2109 FJustTeleported
:= False;
2112 FWaitForFirstSpawn
:= false;
2113 FPunchAnim
:= TAnimationState
.Create(False, 1, 4);
2119 procedure TPlayer
.positionChanged (); inline;
2123 procedure TPlayer
.doDamage (v
: Integer);
2125 if (v
<= 0) then exit
;
2126 if (v
> 32767) then v
:= 32767;
2127 Damage(v
, 0, 0, 0, mEDamageType
);
2130 procedure TPlayer
.Damage(value
: Word; SpawnerUID
: Word; vx
, vy
: Integer; t
: Byte);
2134 if (not g_Game_IsClient
) and (not FAlive
) then
2139 // Неуязвимость не спасает от ловушек:
2140 if ((t
= HIT_TRAP
) or (t
= HIT_SELF
)) and (not FGodMode
) then
2142 if not g_Game_IsClient
then
2145 if t
= HIT_TRAP
then
2147 // Ловушка убивает сразу:
2149 Kill(K_EXTRAHARDKILL
, SpawnerUID
, t
);
2151 if t
= HIT_SELF
then
2155 Kill(K_SIMPLEKILL
, SpawnerUID
, t
);
2158 // Обнулить действия примочек, чтобы фон пропал
2159 FMegaRulez
[MR_SUIT
] := 0;
2160 FMegaRulez
[MR_INVUL
] := 0;
2161 FMegaRulez
[MR_INVIS
] := 0;
2166 // Но от остального спасает:
2167 if FMegaRulez
[MR_INVUL
] >= gTime
then
2174 // Если есть урон своим, или ранил сам себя, или тебя ранил противник:
2175 if LongBool(gGameSettings
.Options
and GAME_OPTION_TEAMDAMAGE
) or
2176 (SpawnerUID
= FUID
) or
2177 (not SameTeam(FUID
, SpawnerUID
)) then
2179 FLastSpawnerUID
:= SpawnerUID
;
2181 // Кровь (пузырьки, если в воде):
2182 if gBloodCount
> 0 then
2184 c
:= Min(value
, 200)*gBloodCount
+ Random(Min(value
, 200) div 2);
2185 if value
div 4 <= c
then
2186 c
:= c
- (value
div 4)
2190 if (t
= HIT_SOME
) and (vx
= 0) and (vy
= 0) then
2194 HIT_TRAP
, HIT_ACID
, HIT_FLAME
, HIT_SELF
: MakeBloodSimple(c
);
2195 HIT_BFG
, HIT_ROCKET
, HIT_SOME
: MakeBloodVector(c
, vx
, vy
);
2198 if t
= HIT_WATER
then
2199 g_GFX_Bubbles(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2),
2200 FObj
.Y
+PLAYER_RECT
.Y
-4, value
div 2, 8, 4);
2205 Inc(FDamageBuffer
, value
);
2209 FPain
:= FPain
+ value
;
2212 if g_Game_IsServer
and g_Game_IsNet
then
2214 MH_SEND_PlayerDamage(FUID
, t
, SpawnerUID
, value
, vx
, vy
);
2215 MH_SEND_PlayerStats(FUID
);
2216 MH_SEND_PlayerPos(False, FUID
);
2220 function TPlayer
.Heal(value
: Word; Soft
: Boolean): Boolean;
2223 if g_Game_IsClient
then
2228 if Soft
and (FHealth
< PLAYER_HP_SOFT
) then
2230 IncMax(FHealth
, value
, PLAYER_HP_SOFT
);
2233 if (not Soft
) and (FHealth
< PLAYER_HP_LIMIT
) then
2235 IncMax(FHealth
, value
, PLAYER_HP_LIMIT
);
2239 if Result
and g_Game_IsServer
and g_Game_IsNet
then
2240 MH_SEND_PlayerStats(FUID
);
2243 destructor TPlayer
.Destroy();
2245 if (gPlayer1
<> nil) and (gPlayer1
.FUID
= FUID
) then
2247 if (gPlayer2
<> nil) and (gPlayer2
.FUID
= FUID
) then
2251 FSawSoundIdle
.Free();
2252 FSawSoundHit
.Free();
2253 FSawSoundSelect
.Free();
2254 FFlameSoundOn
.Free();
2255 FFlameSoundOff
.Free();
2256 FFlameSoundWork
.Free();
2257 FJetSoundFly
.Free();
2259 FJetSoundOff
.Free();
2266 procedure TPlayer
.DoPunch();
2272 procedure TPlayer
.Fire();
2274 f
, DidFire
: Boolean;
2275 wx
, wy
, xd
, yd
: Integer;
2278 if g_Game_IsClient
then Exit
;
2279 // FBFGFireCounter - время перед выстрелом (для BFG)
2280 // FReloading - время после выстрела (для всего)
2288 if FReloading
[FCurrWeap
] <> 0 then Exit
;
2293 wx
:= FObj
.X
+WEAPONPOINT
[FDirection
].X
;
2294 wy
:= FObj
.Y
+WEAPONPOINT
[FDirection
].Y
;
2295 xd
:= wx
+IfThen(FDirection
= TDirection
.D_LEFT
, -30, 30);
2296 yd
:= wy
+firediry();
2302 if R_BERSERK
in FRulez
then
2304 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
2305 locobj
.X
:= FObj
.X
+FObj
.Rect
.X
;
2306 locobj
.Y
:= FObj
.Y
+FObj
.Rect
.Y
;
2309 locobj
.rect
.Width
:= 39;
2310 locobj
.rect
.Height
:= 52;
2311 locobj
.Vel
.X
:= (xd
-wx
) div 2;
2312 locobj
.Vel
.Y
:= (yd
-wy
) div 2;
2313 locobj
.Accel
.X
:= xd
-wx
;
2314 locobj
.Accel
.y
:= yd
-wy
;
2316 if g_Weapon_Hit(@locobj
, 50, FUID
, HIT_SOME
) <> 0 then
2317 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj
.X
, FObj
.Y
)
2319 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj
.X
, FObj
.Y
);
2321 if (gFlash
= 1) and (FPain
< 50) then FPain
:= min(FPain
+ 25, 50);
2325 g_Weapon_punch(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
, 3, FUID
);
2329 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2334 if g_Weapon_chainsaw(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
,
2335 IfThen(gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
], 9, 3), FUID
) <> 0 then
2337 FSawSoundSelect
.Stop();
2339 FSawSoundHit
.PlayAt(FObj
.X
, FObj
.Y
);
2341 else if not FSawSoundHit
.IsPlaying() then
2343 FSawSoundSelect
.Stop();
2344 FSawSound
.PlayAt(FObj
.X
, FObj
.Y
);
2347 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2353 if FAmmo
[A_BULLETS
] > 0 then
2355 g_Weapon_pistol(wx
, wy
, xd
, yd
, FUID
);
2356 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2357 Dec(FAmmo
[A_BULLETS
]);
2358 FFireAngle
:= FAngle
;
2361 g_Player_CreateShell(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
2362 GameVelX
, GameVelY
-2, SHELL_BULLET
);
2366 if FAmmo
[A_SHELLS
] > 0 then
2368 g_Weapon_shotgun(wx
, wy
, xd
, yd
, FUID
);
2369 if not gSoundEffectsDF
then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', wx
, wy
);
2370 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2371 Dec(FAmmo
[A_SHELLS
]);
2372 FFireAngle
:= FAngle
;
2376 FShellType
:= SHELL_SHELL
;
2380 if FAmmo
[A_SHELLS
] >= 2 then
2382 g_Weapon_dshotgun(wx
, wy
, xd
, yd
, FUID
);
2383 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2384 Dec(FAmmo
[A_SHELLS
], 2);
2385 FFireAngle
:= FAngle
;
2389 FShellType
:= SHELL_DBLSHELL
;
2393 if FAmmo
[A_BULLETS
] > 0 then
2395 g_Weapon_mgun(wx
, wy
, xd
, yd
, FUID
);
2396 if not gSoundEffectsDF
then g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', wx
, wy
);
2397 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2398 Dec(FAmmo
[A_BULLETS
]);
2399 FFireAngle
:= FAngle
;
2402 g_Player_CreateShell(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
2403 GameVelX
, GameVelY
-2, SHELL_BULLET
);
2406 WEAPON_ROCKETLAUNCHER
:
2407 if FAmmo
[A_ROCKETS
] > 0 then
2409 g_Weapon_rocket(wx
, wy
, xd
, yd
, FUID
);
2410 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2411 Dec(FAmmo
[A_ROCKETS
]);
2412 FFireAngle
:= FAngle
;
2418 if FAmmo
[A_CELLS
] > 0 then
2420 g_Weapon_plasma(wx
, wy
, xd
, yd
, FUID
);
2421 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2422 Dec(FAmmo
[A_CELLS
]);
2423 FFireAngle
:= FAngle
;
2429 if (FAmmo
[A_CELLS
] >= 40) and (FBFGFireCounter
= -1) then
2431 FBFGFireCounter
:= 17;
2432 if not FNoReload
then
2433 g_Sound_PlayExAt('SOUND_WEAPON_STARTFIREBFG', FObj
.X
, FObj
.Y
);
2434 Dec(FAmmo
[A_CELLS
], 40);
2438 WEAPON_SUPERPULEMET
:
2439 if FAmmo
[A_SHELLS
] > 0 then
2441 g_Weapon_shotgun(wx
, wy
, xd
, yd
, FUID
);
2442 if not gSoundEffectsDF
then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx
, wy
);
2443 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2444 Dec(FAmmo
[A_SHELLS
]);
2445 FFireAngle
:= FAngle
;
2448 g_Player_CreateShell(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
2449 GameVelX
, GameVelY
-2, SHELL_SHELL
);
2452 WEAPON_FLAMETHROWER
:
2453 if FAmmo
[A_FUEL
] > 0 then
2455 g_Weapon_flame(wx
, wy
, xd
, yd
, FUID
);
2457 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2459 FFireAngle
:= FAngle
;
2466 if g_Game_IsNet
and g_Game_IsServer
then MH_SEND_PlayerStats(FUID
);
2470 if g_Game_IsNet
then
2474 if FCurrWeap
<> WEAPON_BFG
then
2475 MH_SEND_PlayerFire(FUID
, FCurrWeap
, wx
, wy
, xd
, yd
, LastShotID
)
2477 if not FNoReload
then
2478 MH_SEND_Sound(FObj
.X
, FObj
.Y
, 'SOUND_WEAPON_STARTFIREBFG');
2481 MH_SEND_PlayerStats(FUID
);
2486 if (FAngle
= 0) or (FAngle
= 180) then SetAction(A_ATTACK
)
2487 else if (FAngle
= ANGLE_LEFTDOWN
) or (FAngle
= ANGLE_RIGHTDOWN
) then SetAction(A_ATTACKDOWN
)
2488 else if (FAngle
= ANGLE_LEFTUP
) or (FAngle
= ANGLE_RIGHTUP
) then SetAction(A_ATTACKUP
);
2491 function TPlayer
.GetAmmoByWeapon(Weapon
: Byte): Word;
2494 WEAPON_PISTOL
, WEAPON_CHAINGUN
: Result
:= FAmmo
[A_BULLETS
];
2495 WEAPON_SHOTGUN1
, WEAPON_SHOTGUN2
, WEAPON_SUPERPULEMET
: Result
:= FAmmo
[A_SHELLS
];
2496 WEAPON_ROCKETLAUNCHER
: Result
:= FAmmo
[A_ROCKETS
];
2497 WEAPON_PLASMA
, WEAPON_BFG
: Result
:= FAmmo
[A_CELLS
];
2498 WEAPON_FLAMETHROWER
: Result
:= FAmmo
[A_FUEL
];
2503 function TPlayer
.HeadInLiquid(XInc
, YInc
: Integer): Boolean;
2505 Result
:= g_Map_CollidePanel(FObj
.X
+PLAYER_HEADRECT
.X
+XInc
, FObj
.Y
+PLAYER_HEADRECT
.Y
+YInc
,
2506 PLAYER_HEADRECT
.Width
, PLAYER_HEADRECT
.Height
,
2507 PANEL_WATER
or PANEL_ACID1
or PANEL_ACID2
, True);
2510 procedure TPlayer
.FlamerOn
;
2512 FFlameSoundOff
.Stop();
2513 FFlameSoundOff
.SetPosition(0);
2516 if (not FFlameSoundOn
.IsPlaying()) and (not FFlameSoundWork
.IsPlaying()) then
2517 FFlameSoundWork
.PlayAt(FObj
.X
, FObj
.Y
);
2521 FFlameSoundOn
.PlayAt(FObj
.X
, FObj
.Y
);
2526 procedure TPlayer
.FlamerOff
;
2530 FFlameSoundOn
.Stop();
2531 FFlameSoundOn
.SetPosition(0);
2532 FFlameSoundWork
.Stop();
2533 FFlameSoundWork
.SetPosition(0);
2534 FFlameSoundOff
.PlayAt(FObj
.X
, FObj
.Y
);
2539 procedure TPlayer
.JetpackOn
;
2543 FJetSoundOn
.SetPosition(0);
2544 FJetSoundOn
.PlayAt(FObj
.X
, FObj
.Y
);
2548 procedure TPlayer
.JetpackOff
;
2552 FJetSoundOff
.SetPosition(0);
2553 FJetSoundOff
.PlayAt(FObj
.X
, FObj
.Y
);
2556 procedure TPlayer
.CatchFire(Attacker
: Word; Timeout
: Integer = PLAYER_BURN_TIME
);
2558 if Timeout
<= 0 then
2560 if (FMegaRulez
[MR_SUIT
] > gTime
) or (FMegaRulez
[MR_INVUL
] > gTime
) then
2561 exit
; // Не загораемся когда есть защита
2562 if g_Obj_CollidePanel(@FObj
, 0, 0, PANEL_WATER
or PANEL_ACID1
or PANEL_ACID2
) then
2563 exit
; // Не подгораем в воде на всякий случай
2564 if FFireTime
<= 0 then
2565 g_Sound_PlayExAt('SOUND_IGNITE', FObj
.X
, FObj
.Y
);
2566 FFireTime
:= Timeout
;
2567 FFireAttacker
:= Attacker
;
2568 if g_Game_IsNet
and g_Game_IsServer
then
2569 MH_SEND_PlayerStats(FUID
);
2572 procedure TPlayer
.Jump();
2574 if gFly
or FJetpack
then
2576 // Полет (чит-код или джетпак):
2577 if FObj
.Vel
.Y
> -VEL_FLY
then
2578 FObj
.Vel
.Y
:= FObj
.Vel
.Y
- 3;
2581 if FJetFuel
> 0 then
2583 if (FJetFuel
< 1) and g_Game_IsServer
then
2587 if g_Game_IsNet
then
2588 MH_SEND_PlayerStats(FUID
);
2594 // Не включать джетпак в режиме прохождения сквозь стены
2596 FCanJetpack
:= False;
2598 // Прыгаем или всплываем:
2599 if (CollideLevel(0, 1) or
2600 g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+PLAYER_RECT
.Y
+36, PLAYER_RECT
.Width
,
2601 PLAYER_RECT
.Height
-33, PANEL_STEP
, False)
2602 ) and (FObj
.Accel
.Y
= 0) then // Не прыгать, если есть вертикальное ускорение
2604 FObj
.Vel
.Y
:= -VEL_JUMP
;
2605 FCanJetpack
:= False;
2609 if BodyInLiquid(0, 0) then
2610 FObj
.Vel
.Y
:= -VEL_SW
2611 else if (FJetFuel
> 0) and FCanJetpack
and
2612 g_Game_IsServer
and (not g_Obj_CollideLiquid(@FObj
, 0, 0)) then
2616 if g_Game_IsNet
then
2617 MH_SEND_PlayerStats(FUID
);
2622 procedure TPlayer
.Kill(KillType
: Byte; SpawnerUID
: Word; t
: Byte);
2624 a
, i
, k
, ab
, ar
: Byte;
2628 srv
, netsrv
: Boolean;
2634 procedure PushItem(t
: Byte);
2638 id
:= g_Items_Create(FObj
.X
, FObj
.Y
, t
, True, False);
2639 it
:= g_Items_ByIdx(id
);
2640 if KillType
= K_EXTRAHARDKILL
then // -7..+7; -8..0
2642 g_Obj_Push(@it
.Obj
, (FObj
.Vel
.X
div 2)-7+Random(15),
2643 (FObj
.Vel
.Y
div 2)-Random(9));
2644 it
.positionChanged(); // this updates spatial accelerators
2648 if KillType
= K_HARDKILL
then // -5..+5; -5..0
2650 g_Obj_Push(@it
.Obj
, (FObj
.Vel
.X
div 2)-5+Random(11),
2651 (FObj
.Vel
.Y
div 2)-Random(6));
2653 else // -3..+3; -3..0
2655 g_Obj_Push(@it
.Obj
, (FObj
.Vel
.X
div 2)-3+Random(7),
2656 (FObj
.Vel
.Y
div 2)-Random(4));
2658 it
.positionChanged(); // this updates spatial accelerators
2661 if g_Game_IsNet
and g_Game_IsServer
then
2662 MH_SEND_ItemSpawn(True, id
);
2666 DoFrags
:= (gGameSettings
.MaxLives
= 0) or (gGameSettings
.GameMode
= GM_COOP
);
2667 Srv
:= g_Game_IsServer
;
2668 Netsrv
:= g_Game_IsServer
and g_Game_IsNet
;
2669 if Srv
then FDeath
:= FDeath
+ 1;
2674 if not FPhysics
then
2680 if (gGameSettings
.MaxLives
> 0) and Srv
and (gLMSRespawn
= LMS_RESPAWN_NONE
) then
2682 if FLives
> 0 then FLives
:= FLives
- 1;
2683 if FLives
= 0 then FNoRespawn
:= True;
2686 // Номер типа смерти:
2689 K_SIMPLEKILL
: a
:= 1;
2691 K_EXTRAHARDKILL
: a
:= 3;
2696 if not FModel
.PlaySound(MODELSOUND_DIE
, a
, FObj
.X
, FObj
.Y
) then
2698 if FModel
.PlaySound(MODELSOUND_DIE
, i
, FObj
.X
, FObj
.Y
) then
2705 FTime
[T_RESPAWN
] := gTime
+ TIME_RESPAWN1
;
2707 FTime
[T_RESPAWN
] := gTime
+ TIME_RESPAWN2
;
2708 K_EXTRAHARDKILL
, K_FALLKILL
:
2709 FTime
[T_RESPAWN
] := gTime
+ TIME_RESPAWN3
;
2712 // Переключаем состояние:
2716 K_HARDKILL
, K_EXTRAHARDKILL
:
2720 // Реакция монстров на смерть игрока:
2721 if (KillType
<> K_FALLKILL
) and (Srv
) then
2722 g_Monsters_killedp();
2724 if SpawnerUID
= FUID
then
2726 if Srv
and (DoFrags
or (gGameSettings
.GameMode
= GM_TDM
)) then
2731 g_Console_Add(Format(_lc
[I_PLAYER_KILL_SELF
], [FName
]), True);
2734 if g_GetUIDType(SpawnerUID
) = UID_PLAYER
then
2735 begin // Убит другим игроком
2736 KP
:= g_Player_Get(SpawnerUID
);
2737 if (KP
<> nil) and Srv
then
2739 if (DoFrags
or (gGameSettings
.GameMode
= GM_TDM
)) then
2740 if SameTeam(FUID
, SpawnerUID
) then
2750 if (gGameSettings
.GameMode
= GM_TDM
) and DoFrags
then
2751 Inc(gTeamStat
[KP
.Team
].Goals
,
2752 IfThen(SameTeam(FUID
, SpawnerUID
), -1, 1));
2754 if netsrv
then MH_SEND_PlayerStats(SpawnerUID
);
2757 plr
:= g_Player_Get(SpawnerUID
);
2765 g_Console_Add(Format(_lc
[I_PLAYER_KILL_EXTRAHARD_2
],
2769 g_Console_Add(Format(_lc
[I_PLAYER_KILL_EXTRAHARD_1
],
2773 g_Console_Add(Format(_lc
[I_PLAYER_KILL
],
2778 else if g_GetUIDType(SpawnerUID
) = UID_MONSTER
then
2779 begin // Убит монстром
2780 mon
:= g_Monsters_ByUID(SpawnerUID
);
2784 s
:= g_Mons_GetKilledByTypeId(mon
.MonsterType
);
2788 g_Console_Add(Format(_lc
[I_PLAYER_KILL_EXTRAHARD_2
],
2792 g_Console_Add(Format(_lc
[I_PLAYER_KILL_EXTRAHARD_1
],
2796 g_Console_Add(Format(_lc
[I_PLAYER_KILL
],
2801 else // Особые типы смерти
2804 HIT_SELF
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_SELF
], [FName
]), True);
2805 HIT_FALL
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_FALL
], [FName
]), True);
2806 HIT_WATER
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_WATER
], [FName
]), True);
2807 HIT_ACID
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_ACID
], [FName
]), True);
2808 HIT_TRAP
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_TRAP
], [FName
]), True);
2809 else g_Console_Add(Format(_lc
[I_PLAYER_DIED
], [FName
]), True);
2815 for a
:= WP_FIRST
to WP_LAST
do
2819 WEAPON_SAW
: i
:= ITEM_WEAPON_SAW
;
2820 WEAPON_SHOTGUN1
: i
:= ITEM_WEAPON_SHOTGUN1
;
2821 WEAPON_SHOTGUN2
: i
:= ITEM_WEAPON_SHOTGUN2
;
2822 WEAPON_CHAINGUN
: i
:= ITEM_WEAPON_CHAINGUN
;
2823 WEAPON_ROCKETLAUNCHER
: i
:= ITEM_WEAPON_ROCKETLAUNCHER
;
2824 WEAPON_PLASMA
: i
:= ITEM_WEAPON_PLASMA
;
2825 WEAPON_BFG
: i
:= ITEM_WEAPON_BFG
;
2826 WEAPON_SUPERPULEMET
: i
:= ITEM_WEAPON_SUPERPULEMET
;
2827 WEAPON_FLAMETHROWER
: i
:= ITEM_WEAPON_FLAMETHROWER
;
2836 if R_ITEM_BACKPACK
in FRulez
then
2837 PushItem(ITEM_AMMO_BACKPACK
);
2839 // Выброс ракетного ранца:
2840 if FJetFuel
> 0 then
2841 PushItem(ITEM_JETPACK
);
2844 if (not (gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
])) or
2845 (not LongBool(gGameSettings
.Options
and GAME_OPTION_DMKEYS
)) then
2847 if R_KEY_RED
in FRulez
then
2848 PushItem(ITEM_KEY_RED
);
2850 if R_KEY_GREEN
in FRulez
then
2851 PushItem(ITEM_KEY_GREEN
);
2853 if R_KEY_BLUE
in FRulez
then
2854 PushItem(ITEM_KEY_BLUE
);
2858 DropFlag(KillType
= K_FALLKILL
);
2861 FCorpse
:= g_Player_CreateCorpse(Self
);
2863 if Srv
and (gGameSettings
.MaxLives
> 0) and FNoRespawn
and
2864 (gLMSRespawn
= LMS_RESPAWN_NONE
) then
2870 for i
:= Low(gPlayers
) to High(gPlayers
) do
2872 if gPlayers
[i
] = nil then continue
;
2873 if (not gPlayers
[i
].FNoRespawn
) and (not gPlayers
[i
].FSpectator
) then
2876 if gPlayers
[i
].FTeam
= TEAM_RED
then Inc(ar
)
2877 else if gPlayers
[i
].FTeam
= TEAM_BLUE
then Inc(ab
);
2882 OldLR
:= gLMSRespawn
;
2883 if (gGameSettings
.GameMode
= GM_COOP
) then
2887 // everyone is dead, restart the map
2888 g_Game_Message(_lc
[I_MESSAGE_LMS_LOSE
], 144);
2890 MH_SEND_GameEvent(NET_EV_LMS_LOSE
);
2891 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2892 gLMSRespawnTime
:= gTime
+ 5000;
2894 else if (a
= 1) then
2896 if (gPlayers
[k
] <> nil) and not (gPlayers
[k
] is TBot
) then
2897 if (gPlayers
[k
] = gPlayer1
) or
2898 (gPlayers
[k
] = gPlayer2
) then
2899 g_Console_Add('*** ' + _lc
[I_MESSAGE_LMS_SURVIVOR
] + ' ***', True)
2900 else if Netsrv
and (gPlayers
[k
].FClientID
>= 0) then
2901 MH_SEND_GameEvent(NET_EV_LMS_SURVIVOR
, 0, 'N', gPlayers
[k
].FClientID
);
2904 else if (gGameSettings
.GameMode
= GM_TDM
) then
2906 if (ab
= 0) and (ar
<> 0) then
2909 g_Game_Message(Format(_lc
[I_MESSAGE_TLMS_WIN
], [AnsiUpperCase(_lc
[I_GAME_TEAM_RED
])]), 144);
2911 MH_SEND_GameEvent(NET_EV_TLMS_WIN
, TEAM_RED
);
2912 Inc(gTeamStat
[TEAM_RED
].Goals
);
2913 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2914 gLMSRespawnTime
:= gTime
+ 5000;
2916 else if (ar
= 0) and (ab
<> 0) then
2919 g_Game_Message(Format(_lc
[I_MESSAGE_TLMS_WIN
], [AnsiUpperCase(_lc
[I_GAME_TEAM_BLUE
])]), 144);
2921 MH_SEND_GameEvent(NET_EV_TLMS_WIN
, TEAM_BLUE
);
2922 Inc(gTeamStat
[TEAM_BLUE
].Goals
);
2923 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2924 gLMSRespawnTime
:= gTime
+ 5000;
2926 else if (ar
= 0) and (ab
= 0) then
2929 g_Game_Message(_lc
[I_GAME_WIN_DRAW
], 144);
2931 MH_SEND_GameEvent(NET_EV_LMS_DRAW
, 0, FName
);
2932 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2933 gLMSRespawnTime
:= gTime
+ 5000;
2936 else if (gGameSettings
.GameMode
= GM_DM
) then
2940 if gPlayers
[k
] <> nil then
2943 // survivor is the winner
2944 g_Game_Message(Format(_lc
[I_MESSAGE_LMS_WIN
], [AnsiUpperCase(FName
)]), 144);
2946 MH_SEND_GameEvent(NET_EV_LMS_WIN
, 0, FName
);
2949 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2950 gLMSRespawnTime
:= gTime
+ 5000;
2952 else if (a
= 0) then
2954 // everyone is dead, restart the map
2955 g_Game_Message(_lc
[I_GAME_WIN_DRAW
], 144);
2957 MH_SEND_GameEvent(NET_EV_LMS_DRAW
, 0, FName
);
2958 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2959 gLMSRespawnTime
:= gTime
+ 5000;
2962 if srv
and (OldLR
= LMS_RESPAWN_NONE
) and (gLMSRespawn
> LMS_RESPAWN_NONE
) then
2964 if NetMode
= NET_SERVER
then
2965 MH_SEND_GameEvent(NET_EV_LMS_WARMUP
, gLMSRespawnTime
- gTime
)
2967 g_Console_Add(Format(_lc
[I_MSG_WARMUP_START
], [(gLMSRespawnTime
- gTime
) div 1000]), True);
2973 MH_SEND_PlayerStats(FUID
);
2974 MH_SEND_PlayerDeath(FUID
, KillType
, t
, SpawnerUID
);
2975 if gGameSettings
.GameMode
= GM_TDM
then MH_SEND_GameStats
;
2978 if srv
and FNoRespawn
then Spectate(True);
2979 FWantsInGame
:= True;
2982 function TPlayer
.BodyInLiquid(XInc
, YInc
: Integer): Boolean;
2984 Result
:= g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
, PLAYER_RECT
.Width
,
2985 PLAYER_RECT
.Height
-20, PANEL_WATER
or PANEL_ACID1
or PANEL_ACID2
, False);
2988 function TPlayer
.BodyInAcid(XInc
, YInc
: Integer): Boolean;
2990 Result
:= g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
, PLAYER_RECT
.Width
,
2991 PLAYER_RECT
.Height
-20, PANEL_ACID1
or PANEL_ACID2
, False);
2994 procedure TPlayer
.MakeBloodSimple(Count
: Word);
2995 var Blood
: TModelBlood
;
2997 Blood
:= SELF
.FModel
.GetBlood();
2998 g_GFX_Blood(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)+8,
2999 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2),
3000 Count
div 2, 3, -1, 16, (PLAYER_RECT
.Height
*2 div 3),
3001 Blood
.R
, Blood
.G
, Blood
.B
, Blood
.Kind
);
3002 g_GFX_Blood(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)-8,
3003 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2),
3004 Count
div 2, -3, -1, 16, (PLAYER_RECT
.Height
*2) div 3,
3005 Blood
.R
, Blood
.G
, Blood
.B
, Blood
.Kind
);
3008 procedure TPlayer
.MakeBloodVector(Count
: Word; VelX
, VelY
: Integer);
3009 var Blood
: TModelBlood
;
3011 Blood
:= SELF
.FModel
.GetBlood();
3012 g_GFX_Blood(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2),
3013 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2),
3014 Count
, VelX
, VelY
, 16, (PLAYER_RECT
.Height
*2) div 3,
3015 Blood
.R
, Blood
.G
, Blood
.B
, Blood
.Kind
);
3018 procedure TPlayer
.QueueWeaponSwitch(Weapon
: Byte);
3020 if g_Game_IsClient
then Exit
;
3021 if Weapon
> High(FWeapon
) then Exit
;
3022 FNextWeap
:= FNextWeap
or (1 shl Weapon
);
3025 procedure TPlayer
.resetWeaponQueue ();
3028 FNextWeapDelay
:= 0;
3031 function TPlayer
.hasAmmoForWeapon (weapon
: Byte): Boolean;
3035 WEAPON_KASTET
, WEAPON_SAW
: result
:= true;
3036 WEAPON_SHOTGUN1
, WEAPON_SHOTGUN2
, WEAPON_SUPERPULEMET
: result
:= (FAmmo
[A_SHELLS
] > 0);
3037 WEAPON_PISTOL
, WEAPON_CHAINGUN
: result
:= (FAmmo
[A_BULLETS
] > 0);
3038 WEAPON_ROCKETLAUNCHER
: result
:= (FAmmo
[A_ROCKETS
] > 0);
3039 WEAPON_PLASMA
, WEAPON_BFG
: result
:= (FAmmo
[A_CELLS
] > 0);
3040 WEAPON_FLAMETHROWER
: result
:= (FAmmo
[A_FUEL
] > 0);
3041 else result
:= (weapon
< length(FWeapon
));
3045 // return 255 for "no switch"
3046 function TPlayer
.getNextWeaponIndex (): Byte;
3049 wantThisWeapon
: array[0..64] of Boolean;
3050 wwc
: Integer = 0; //HACK!
3053 result
:= 255; // default result: "no switch"
3054 // had weapon cycling on previous frame? remove that flag
3055 if (FNextWeap
and $2000) <> 0 then
3057 FNextWeap
:= FNextWeap
and $1FFF;
3058 FNextWeapDelay
:= 0;
3060 // cycling has priority
3061 if (FNextWeap
and $C000) <> 0 then
3063 if (FNextWeap
and $8000) <> 0 then
3067 FNextWeap
:= FNextWeap
or $2000; // we need this
3068 if FNextWeapDelay
> 0 then
3069 exit
; // cooldown time
3071 for i
:= 0 to High(FWeapon
) do
3073 cwi
:= (cwi
+length(FWeapon
)+dir
) mod length(FWeapon
);
3074 if FWeapon
[cwi
] then
3076 //e_WriteLog(Format(' SWITCH: cur=%d; new=%d', [FCurrWeap, cwi]), MSG_WARNING);
3077 result
:= Byte(cwi
);
3078 FNextWeapDelay
:= WEAPON_DELAY
;
3086 for i
:= 0 to High(wantThisWeapon
) do
3087 wantThisWeapon
[i
] := false;
3088 for i
:= 0 to High(FWeapon
) do
3089 if (FNextWeap
and (1 shl i
)) <> 0 then
3091 wantThisWeapon
[i
] := true;
3094 // exclude currently selected weapon from the set
3095 wantThisWeapon
[FCurrWeap
] := false;
3096 // slow down alterations a little
3099 //e_WriteLog(Format(' FNextWeap=%x; delay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
3100 // more than one weapon requested, assume "alteration" and check alteration delay
3101 if FNextWeapDelay
> 0 then
3107 // do not reset weapon queue, it will be done in `RealizeCurrentWeapon()`
3108 // but clear all counters if no weapon should be switched
3114 //e_WriteLog(Format('wwc=%d', [wwc]), MSG_WARNING);
3115 // try weapons in descending order
3116 for i
:= High(FWeapon
) downto 0 do
3118 if wantThisWeapon
[i
] and FWeapon
[i
] and ((wwc
= 1) or hasAmmoForWeapon(i
)) then
3123 FNextWeapDelay
:= WEAPON_DELAY
* 2; // anyway, 'cause why not
3127 // no suitable weapon found, so reset the queue, to avoid accidental "queuing" of weapon w/o ammo
3131 procedure TPlayer
.RealizeCurrentWeapon();
3132 function switchAllowed (): Boolean;
3137 if FBFGFireCounter
<> -1 then
3139 if FTime
[T_SWITCH
] > gTime
then
3141 for i
:= WP_FIRST
to WP_LAST
do
3142 if FReloading
[i
] > 0 then
3150 //e_WriteLog(Format('***RealizeCurrentWeapon: FNextWeap=%x; FNextWeapDelay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
3151 //FNextWeap := FNextWeap and $1FFF;
3152 if FNextWeapDelay
> 0 then Dec(FNextWeapDelay
); // "alteration delay"
3154 if not switchAllowed
then
3156 //HACK for weapon cycling
3157 if (FNextWeap
and $E000) <> 0 then FNextWeap
:= 0;
3161 nw
:= getNextWeaponIndex();
3162 if nw
= 255 then exit
; // don't reset anything here
3163 if nw
> High(FWeapon
) then
3165 // don't forget to reset queue here!
3166 //e_WriteLog(' RealizeCurrentWeapon: WUTAFUUUU', MSG_WARNING);
3174 FTime
[T_SWITCH
] := gTime
+156;
3175 if FCurrWeap
= WEAPON_SAW
then FSawSoundSelect
.PlayAt(FObj
.X
, FObj
.Y
);
3176 FModel
.SetWeapon(FCurrWeap
);
3177 if g_Game_IsNet
then MH_SEND_PlayerStats(FUID
);
3181 procedure TPlayer
.NextWeapon();
3183 if g_Game_IsClient
then Exit
;
3187 procedure TPlayer
.PrevWeapon();
3189 if g_Game_IsClient
then Exit
;
3193 procedure TPlayer
.SetWeapon(W
: Byte);
3195 if FCurrWeap
<> W
then
3196 if W
= WEAPON_SAW
then
3197 FSawSoundSelect
.PlayAt(FObj
.X
, FObj
.Y
);
3200 FModel
.SetWeapon(CurrWeap
);
3204 function TPlayer
.PickItem(ItemType
: Byte; arespawn
: Boolean; var remove
: Boolean): Boolean;
3206 function allowBerserkSwitching (): Boolean;
3208 if (FBFGFireCounter
<> -1) then begin result
:= false; exit
; end;
3210 if gBerserkAutoswitch
then exit
;
3211 if not conIsCheatsEnabled
then exit
;
3219 if g_Game_IsClient
then Exit
;
3221 // a = true - место спавна предмета:
3222 a
:= LongBool(gGameSettings
.Options
and GAME_OPTION_WEAPONSTAY
) and arespawn
;
3227 if (FHealth
< PLAYER_HP_SOFT
) or (FFireTime
> 0) then
3229 if FHealth
< PLAYER_HP_SOFT
then IncMax(FHealth
, 10, PLAYER_HP_SOFT
);
3233 if gFlash
= 2 then Inc(FPickup
, 5);
3237 if (FHealth
< PLAYER_HP_SOFT
) or (FFireTime
> 0) then
3239 if FHealth
< PLAYER_HP_SOFT
then IncMax(FHealth
, 25, PLAYER_HP_SOFT
);
3243 if gFlash
= 2 then Inc(FPickup
, 5);
3247 if FArmor
< PLAYER_AP_SOFT
then
3249 FArmor
:= PLAYER_AP_SOFT
;
3252 if gFlash
= 2 then Inc(FPickup
, 5);
3256 if FArmor
< PLAYER_AP_LIMIT
then
3258 FArmor
:= PLAYER_AP_LIMIT
;
3261 if gFlash
= 2 then Inc(FPickup
, 5);
3265 if (FHealth
< PLAYER_HP_LIMIT
) or (FFireTime
> 0) then
3267 if FHealth
< PLAYER_HP_LIMIT
then IncMax(FHealth
, 100, PLAYER_HP_LIMIT
);
3271 if gFlash
= 2 then Inc(FPickup
, 5);
3275 if (FHealth
< PLAYER_HP_LIMIT
) or (FArmor
< PLAYER_AP_LIMIT
) or (FFireTime
> 0) then
3277 if FHealth
< PLAYER_HP_LIMIT
then
3278 FHealth
:= PLAYER_HP_LIMIT
;
3279 if FArmor
< PLAYER_AP_LIMIT
then
3280 FArmor
:= PLAYER_AP_LIMIT
;
3284 if gFlash
= 2 then Inc(FPickup
, 5);
3288 if (not FWeapon
[WEAPON_SAW
]) or ((not arespawn
) and (gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
])) then
3290 FWeapon
[WEAPON_SAW
] := True;
3292 if gFlash
= 2 then Inc(FPickup
, 5);
3293 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3296 ITEM_WEAPON_SHOTGUN1
:
3297 if (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or not FWeapon
[WEAPON_SHOTGUN1
] then
3299 // Нужно, чтобы не взять все пули сразу:
3300 if a
and FWeapon
[WEAPON_SHOTGUN1
] then Exit
;
3302 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
3303 FWeapon
[WEAPON_SHOTGUN1
] := True;
3305 if gFlash
= 2 then Inc(FPickup
, 5);
3306 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3309 ITEM_WEAPON_SHOTGUN2
:
3310 if (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or not FWeapon
[WEAPON_SHOTGUN2
] then
3312 if a
and FWeapon
[WEAPON_SHOTGUN2
] then Exit
;
3314 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
3315 FWeapon
[WEAPON_SHOTGUN2
] := True;
3317 if gFlash
= 2 then Inc(FPickup
, 5);
3318 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3321 ITEM_WEAPON_CHAINGUN
:
3322 if (FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
]) or not FWeapon
[WEAPON_CHAINGUN
] then
3324 if a
and FWeapon
[WEAPON_CHAINGUN
] then Exit
;
3326 IncMax(FAmmo
[A_BULLETS
], 50, FMaxAmmo
[A_BULLETS
]);
3327 FWeapon
[WEAPON_CHAINGUN
] := True;
3329 if gFlash
= 2 then Inc(FPickup
, 5);
3330 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3333 ITEM_WEAPON_ROCKETLAUNCHER
:
3334 if (FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
]) or not FWeapon
[WEAPON_ROCKETLAUNCHER
] then
3336 if a
and FWeapon
[WEAPON_ROCKETLAUNCHER
] then Exit
;
3338 IncMax(FAmmo
[A_ROCKETS
], 2, FMaxAmmo
[A_ROCKETS
]);
3339 FWeapon
[WEAPON_ROCKETLAUNCHER
] := True;
3341 if gFlash
= 2 then Inc(FPickup
, 5);
3342 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3346 if (FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
]) or not FWeapon
[WEAPON_PLASMA
] then
3348 if a
and FWeapon
[WEAPON_PLASMA
] then Exit
;
3350 IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
3351 FWeapon
[WEAPON_PLASMA
] := True;
3353 if gFlash
= 2 then Inc(FPickup
, 5);
3354 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3358 if (FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
]) or not FWeapon
[WEAPON_BFG
] then
3360 if a
and FWeapon
[WEAPON_BFG
] then Exit
;
3362 IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
3363 FWeapon
[WEAPON_BFG
] := True;
3365 if gFlash
= 2 then Inc(FPickup
, 5);
3366 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3369 ITEM_WEAPON_SUPERPULEMET
:
3370 if (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or not FWeapon
[WEAPON_SUPERPULEMET
] then
3372 if a
and FWeapon
[WEAPON_SUPERPULEMET
] then Exit
;
3374 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
3375 FWeapon
[WEAPON_SUPERPULEMET
] := True;
3377 if gFlash
= 2 then Inc(FPickup
, 5);
3378 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3381 ITEM_WEAPON_FLAMETHROWER
:
3382 if (FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
]) or not FWeapon
[WEAPON_FLAMETHROWER
] then
3384 if a
and FWeapon
[WEAPON_FLAMETHROWER
] then Exit
;
3386 IncMax(FAmmo
[A_FUEL
], 100, FMaxAmmo
[A_FUEL
]);
3387 FWeapon
[WEAPON_FLAMETHROWER
] := True;
3389 if gFlash
= 2 then Inc(FPickup
, 5);
3390 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3394 if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then
3396 IncMax(FAmmo
[A_BULLETS
], 10, FMaxAmmo
[A_BULLETS
]);
3399 if gFlash
= 2 then Inc(FPickup
, 5);
3402 ITEM_AMMO_BULLETS_BOX
:
3403 if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then
3405 IncMax(FAmmo
[A_BULLETS
], 50, FMaxAmmo
[A_BULLETS
]);
3408 if gFlash
= 2 then Inc(FPickup
, 5);
3412 if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then
3414 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
3417 if gFlash
= 2 then Inc(FPickup
, 5);
3420 ITEM_AMMO_SHELLS_BOX
:
3421 if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then
3423 IncMax(FAmmo
[A_SHELLS
], 25, FMaxAmmo
[A_SHELLS
]);
3426 if gFlash
= 2 then Inc(FPickup
, 5);
3430 if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then
3432 IncMax(FAmmo
[A_ROCKETS
], 1, FMaxAmmo
[A_ROCKETS
]);
3435 if gFlash
= 2 then Inc(FPickup
, 5);
3438 ITEM_AMMO_ROCKET_BOX
:
3439 if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then
3441 IncMax(FAmmo
[A_ROCKETS
], 5, FMaxAmmo
[A_ROCKETS
]);
3444 if gFlash
= 2 then Inc(FPickup
, 5);
3448 if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then
3450 IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
3453 if gFlash
= 2 then Inc(FPickup
, 5);
3457 if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then
3459 IncMax(FAmmo
[A_CELLS
], 100, FMaxAmmo
[A_CELLS
]);
3462 if gFlash
= 2 then Inc(FPickup
, 5);
3466 if FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
] then
3468 IncMax(FAmmo
[A_FUEL
], 100, FMaxAmmo
[A_FUEL
]);
3471 if gFlash
= 2 then Inc(FPickup
, 5);
3475 if not(R_ITEM_BACKPACK
in FRulez
) or
3476 (FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
]) or
3477 (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or
3478 (FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
]) or
3479 (FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
]) or
3480 (FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
]) then
3482 FMaxAmmo
[A_BULLETS
] := AmmoLimits
[1, A_BULLETS
];
3483 FMaxAmmo
[A_SHELLS
] := AmmoLimits
[1, A_SHELLS
];
3484 FMaxAmmo
[A_ROCKETS
] := AmmoLimits
[1, A_ROCKETS
];
3485 FMaxAmmo
[A_CELLS
] := AmmoLimits
[1, A_CELLS
];
3486 FMaxAmmo
[A_FUEL
] := AmmoLimits
[1, A_FUEL
];
3488 if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then
3489 IncMax(FAmmo
[A_BULLETS
], 10, FMaxAmmo
[A_BULLETS
]);
3490 if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then
3491 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
3492 if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then
3493 IncMax(FAmmo
[A_ROCKETS
], 1, FMaxAmmo
[A_ROCKETS
]);
3494 if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then
3495 IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
3496 if FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
] then
3497 IncMax(FAmmo
[A_FUEL
], 50, FMaxAmmo
[A_FUEL
]);
3499 FRulez
:= FRulez
+ [R_ITEM_BACKPACK
];
3502 if gFlash
= 2 then Inc(FPickup
, 5);
3506 if not(R_KEY_RED
in FRulez
) then
3508 Include(FRulez
, R_KEY_RED
);
3510 remove
:= (gGameSettings
.GameMode
<> GM_COOP
) and (g_Player_GetCount() < 2);
3511 if gFlash
= 2 then Inc(FPickup
, 5);
3512 if (not remove
) and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETITEM');
3516 if not(R_KEY_GREEN
in FRulez
) then
3518 Include(FRulez
, R_KEY_GREEN
);
3520 remove
:= (gGameSettings
.GameMode
<> GM_COOP
) and (g_Player_GetCount() < 2);
3521 if gFlash
= 2 then Inc(FPickup
, 5);
3522 if (not remove
) and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETITEM');
3526 if not(R_KEY_BLUE
in FRulez
) then
3528 Include(FRulez
, R_KEY_BLUE
);
3530 remove
:= (gGameSettings
.GameMode
<> GM_COOP
) and (g_Player_GetCount() < 2);
3531 if gFlash
= 2 then Inc(FPickup
, 5);
3532 if (not remove
) and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETITEM');
3536 if FMegaRulez
[MR_SUIT
] < gTime
+PLAYER_SUIT_TIME
then
3538 FMegaRulez
[MR_SUIT
] := gTime
+PLAYER_SUIT_TIME
;
3542 if gFlash
= 2 then Inc(FPickup
, 5);
3546 if FAir
< AIR_MAX
then
3551 if gFlash
= 2 then Inc(FPickup
, 5);
3556 if not (R_BERSERK
in FRulez
) then
3558 Include(FRulez
, R_BERSERK
);
3559 if allowBerserkSwitching
then
3561 FCurrWeap
:= WEAPON_KASTET
;
3563 FModel
.SetWeapon(WEAPON_KASTET
);
3568 if gFlash
= 2 then Inc(FPickup
, 5);
3570 FBerserk
:= gTime
+30000;
3575 if (FHealth
< PLAYER_HP_SOFT
) or (FFireTime
> 0) then
3577 if FHealth
< PLAYER_HP_SOFT
then FHealth
:= PLAYER_HP_SOFT
;
3578 FBerserk
:= gTime
+30000;
3586 if FMegaRulez
[MR_INVUL
] < gTime
+PLAYER_INVUL_TIME
then
3588 FMegaRulez
[MR_INVUL
] := gTime
+PLAYER_INVUL_TIME
;
3592 if gFlash
= 2 then Inc(FPickup
, 5);
3596 if (FHealth
< PLAYER_HP_LIMIT
) or (FFireTime
> 0) then
3598 if FHealth
< PLAYER_HP_LIMIT
then IncMax(FHealth
, 4, PLAYER_HP_LIMIT
);
3602 if gFlash
= 2 then Inc(FPickup
, 5);
3606 if FArmor
< PLAYER_AP_LIMIT
then
3608 IncMax(FArmor
, 5, PLAYER_AP_LIMIT
);
3611 if gFlash
= 2 then Inc(FPickup
, 5);
3615 if FJetFuel
< JET_MAX
then
3617 FJetFuel
:= JET_MAX
;
3620 if gFlash
= 2 then Inc(FPickup
, 5);
3624 if FMegaRulez
[MR_INVIS
] < gTime
+PLAYER_INVIS_TIME
then
3626 FMegaRulez
[MR_INVIS
] := gTime
+PLAYER_INVIS_TIME
;
3629 if gFlash
= 2 then Inc(FPickup
, 5);
3634 procedure TPlayer
.Touch();
3638 //FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
3641 // Бросить флаг товарищу:
3642 if gGameSettings
.GameMode
= GM_CTF
then
3647 procedure TPlayer
.Push(vx
, vy
: Integer);
3649 if (not FPhysics
) and FGhost
then
3651 FObj
.Accel
.X
:= FObj
.Accel
.X
+ vx
;
3652 FObj
.Accel
.Y
:= FObj
.Accel
.Y
+ vy
;
3653 if g_Game_IsNet
and g_Game_IsServer
then
3654 MH_SEND_PlayerPos(True, FUID
, NET_EVERYONE
);
3657 procedure TPlayer
.Reset(Force
: Boolean);
3663 FTime
[T_RESPAWN
] := 0;
3664 FTime
[T_FLAGCAP
] := 0;
3680 FSpectator
:= False;
3683 FSpectatePlayer
:= -1;
3684 FNoRespawn
:= False;
3686 FLives
:= gGameSettings
.MaxLives
;
3691 procedure TPlayer
.SoftReset();
3699 FBFGFireCounter
:= -1;
3707 SetAction(A_STAND
, True);
3710 function TPlayer
.GetRespawnPoint(): Byte;
3715 // На будущее: FSpawn - игрок уже играл и перерождается
3717 // Одиночная игра/кооператив
3718 if gGameSettings
.GameMode
in [GM_COOP
, GM_SINGLE
] then
3720 if Self
= gPlayer1
then
3722 // player 1 should try to spawn on the player 1 point
3723 if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1
) > 0 then
3724 Exit(RESPAWNPOINT_PLAYER1
)
3725 else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2
) > 0 then
3726 Exit(RESPAWNPOINT_PLAYER2
);
3728 else if Self
= gPlayer2
then
3730 // player 2 should try to spawn on the player 2 point
3731 if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2
) > 0 then
3732 Exit(RESPAWNPOINT_PLAYER2
)
3733 else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1
) > 0 then
3734 Exit(RESPAWNPOINT_PLAYER1
);
3738 // other players randomly pick either the first or the second point
3739 c
:= IfThen((Random(2) = 0), RESPAWNPOINT_PLAYER1
, RESPAWNPOINT_PLAYER2
);
3740 if g_Map_GetPointCount(c
) > 0 then
3742 // try the other one
3743 c
:= IfThen((c
= RESPAWNPOINT_PLAYER1
), RESPAWNPOINT_PLAYER2
, RESPAWNPOINT_PLAYER1
);
3744 if g_Map_GetPointCount(c
) > 0 then
3750 if gGameSettings
.GameMode
= GM_DM
then
3752 // try DM points first
3753 if g_Map_GetPointCount(RESPAWNPOINT_DM
) > 0 then
3754 Exit(RESPAWNPOINT_DM
);
3758 if gGameSettings
.GameMode
in [GM_TDM
, GM_CTF
] then
3760 // try team points first
3761 c
:= RESPAWNPOINT_DM
;
3762 if FTeam
= TEAM_RED
then
3763 c
:= RESPAWNPOINT_RED
3764 else if FTeam
= TEAM_BLUE
then
3765 c
:= RESPAWNPOINT_BLUE
;
3766 if g_Map_GetPointCount(c
) > 0 then
3770 // still haven't found a spawnpoint, try random shit
3771 Result
:= g_Map_GetRandomPointType();
3774 procedure TPlayer
.Respawn(Silent
: Boolean; Force
: Boolean = False);
3776 RespawnPoint
: TRespawnPoint
;
3782 FBFGFireCounter
:= -1;
3789 if not g_Game_IsServer
then
3793 FWantsInGame
:= True;
3794 FJustTeleported
:= True;
3797 FTime
[T_RESPAWN
] := 0;
3801 // if server changes MaxLives we gotta be ready
3802 if gGameSettings
.MaxLives
= 0 then FNoRespawn
:= False;
3804 // Еще нельзя возродиться:
3805 if FTime
[T_RESPAWN
] > gTime
then
3808 // Просрал все жизни:
3811 if not FSpectator
then Spectate(True);
3812 FWantsInGame
:= True;
3816 if (gGameSettings
.GameType
<> GT_SINGLE
) and (gGameSettings
.GameMode
<> GM_COOP
) then
3817 begin // "Своя игра"
3818 // Берсерк не сохраняется между уровнями:
3819 FRulez
:= FRulez
-[R_BERSERK
];
3821 else // "Одиночная игра"/"Кооп"
3823 // Берсерк и ключи не сохраняются между уровнями:
3824 FRulez
:= FRulez
-[R_KEY_RED
, R_KEY_GREEN
, R_KEY_BLUE
, R_BERSERK
];
3827 // Получаем точку спауна игрока:
3828 c
:= GetRespawnPoint();
3833 // Воскрешение без оружия:
3836 FHealth
:= Round(PLAYER_HP_SOFT
* (FHandicap
/ 100));
3842 for a
:= WP_FIRST
to WP_LAST
do
3844 FWeapon
[a
] := False;
3848 FWeapon
[WEAPON_PISTOL
] := True;
3849 FWeapon
[WEAPON_KASTET
] := True;
3850 FCurrWeap
:= WEAPON_PISTOL
;
3853 FModel
.SetWeapon(FCurrWeap
);
3855 for b
:= A_BULLETS
to A_HIGH
do
3858 FAmmo
[A_BULLETS
] := 50;
3860 FMaxAmmo
[A_BULLETS
] := AmmoLimits
[0, A_BULLETS
];
3861 FMaxAmmo
[A_SHELLS
] := AmmoLimits
[0, A_SHELLS
];
3862 FMaxAmmo
[A_ROCKETS
] := AmmoLimits
[0, A_SHELLS
];
3863 FMaxAmmo
[A_CELLS
] := AmmoLimits
[0, A_CELLS
];
3864 FMaxAmmo
[A_FUEL
] := AmmoLimits
[0, A_FUEL
];
3866 if (gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
]) and
3867 LongBool(gGameSettings
.Options
and GAME_OPTION_DMKEYS
) then
3868 FRulez
:= [R_KEY_RED
, R_KEY_GREEN
, R_KEY_BLUE
]
3873 // Получаем координаты точки возрождения:
3874 if not g_Map_GetPoint(c
, RespawnPoint
) then
3876 g_FatalError(_lc
[I_GAME_ERROR_GET_SPAWN
]);
3880 // Установка координат и сброс всех параметров:
3881 FObj
.X
:= RespawnPoint
.X
-PLAYER_RECT
.X
;
3882 FObj
.Y
:= RespawnPoint
.Y
-PLAYER_RECT
.Y
;
3883 FObj
.oldX
:= FObj
.X
; // don't interpolate after respawn
3884 FObj
.oldY
:= FObj
.Y
;
3890 FDirection
:= RespawnPoint
.Direction
;
3891 if FDirection
= TDirection
.D_LEFT
then
3896 SetAction(A_STAND
, True);
3897 FModel
.Direction
:= FDirection
;
3899 for a
:= Low(FTime
) to High(FTime
) do
3902 for a
:= Low(FMegaRulez
) to High(FMegaRulez
) do
3905 // Respawn invulnerability
3906 if (gGameSettings
.GameType
<> GT_SINGLE
) and (gGameSettings
.SpawnInvul
> 0) then
3908 FMegaRulez
[MR_INVUL
] := gTime
+ gGameSettings
.SpawnInvul
* 1000;
3909 FSpawnInvul
:= FMegaRulez
[MR_INVUL
];
3914 FCanJetpack
:= False;
3920 // Анимация возрождения:
3921 if (not gLoadGameMode
) and (not Silent
) then
3923 R_GFX_TELEPORT_FAST
,
3924 FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)-32,
3925 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2)-32
3928 FSpectator
:= False;
3931 FSpectatePlayer
:= -1;
3934 if (gPlayer1
= nil) and (gSpectLatchPID1
= FUID
) then
3936 if (gPlayer2
= nil) and (gSpectLatchPID2
= FUID
) then
3939 if g_Game_IsNet
then
3941 MH_SEND_PlayerPos(True, FUID
, NET_EVERYONE
);
3942 MH_SEND_PlayerStats(FUID
, NET_EVERYONE
);
3944 MH_SEND_Effect(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)-32,
3945 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2)-32,
3950 procedure TPlayer
.Spectate(NoMove
: Boolean = False);
3953 Kill(K_EXTRAHARDKILL
, FUID
, HIT_SOME
)
3954 else if (not NoMove
) then
3956 GameX
:= gMapInfo
.Width
div 2;
3957 GameY
:= gMapInfo
.Height
div 2;
3966 FWantsInGame
:= False;
3972 if Self
= gPlayer1
then
3974 gSpectLatchPID1
:= FUID
;
3977 else if Self
= gPlayer2
then
3979 gSpectLatchPID2
:= FUID
;
3984 if g_Game_IsNet
then
3985 MH_SEND_PlayerStats(FUID
);
3988 procedure TPlayer
.SwitchNoClip
;
3992 FGhost
:= not FGhost
;
3993 FPhysics
:= not FGhost
;
4005 procedure TPlayer
.Run(Direction
: TDirection
);
4009 if MAX_RUNVEL
> 8 then
4013 if Direction
= TDirection
.D_LEFT
then
4015 if FObj
.Vel
.X
> -MAX_RUNVEL
then
4016 FObj
.Vel
.X
:= FObj
.Vel
.X
- (MAX_RUNVEL
shr 3);
4019 if FObj
.Vel
.X
< MAX_RUNVEL
then
4020 FObj
.Vel
.X
:= FObj
.Vel
.X
+ (MAX_RUNVEL
shr 3);
4022 // Возможно, пинаем куски:
4023 if (FObj
.Vel
.X
<> 0) and (gGibs
<> nil) then
4025 b
:= Abs(FObj
.Vel
.X
);
4026 if b
> 1 then b
:= b
* (Random(8 div b
) + 1);
4027 for a
:= 0 to High(gGibs
) do
4029 if gGibs
[a
].alive
and
4030 g_Obj_Collide(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
+FObj
.Rect
.Height
-4,
4031 FObj
.Rect
.Width
, 8, @gGibs
[a
].Obj
) and (Random(3) = 0) then
4034 if FObj
.Vel
.X
< 0 then
4036 g_Obj_PushA(@gGibs
[a
].Obj
, b
, Random(61)+120) // налево
4040 g_Obj_PushA(@gGibs
[a
].Obj
, b
, Random(61)); // направо
4042 gGibs
[a
].positionChanged(); // this updates spatial accelerators
4050 procedure TPlayer
.SeeDown();
4052 SetAction(A_SEEDOWN
);
4054 if FDirection
= TDirection
.D_LEFT
then FAngle
:= ANGLE_LEFTDOWN
else FAngle
:= ANGLE_RIGHTDOWN
;
4056 if FIncCam
> -120 then DecMin(FIncCam
, 5, -120);
4059 procedure TPlayer
.SeeUp();
4063 if FDirection
= TDirection
.D_LEFT
then FAngle
:= ANGLE_LEFTUP
else FAngle
:= ANGLE_RIGHTUP
;
4065 if FIncCam
< 120 then IncMax(FIncCam
, 5, 120);
4068 procedure TPlayer
.SetAction(Action
: Byte; Force
: Boolean = False);
4076 A_ATTACK
: Prior
:= 2;
4077 A_SEEUP
: Prior
:= 1;
4078 A_SEEDOWN
: Prior
:= 1;
4079 A_ATTACKUP
: Prior
:= 2;
4080 A_ATTACKDOWN
: Prior
:= 2;
4085 if (Prior
> FActionPrior
) or Force
then
4086 if not ((Prior
= 2) and (FCurrWeap
= WEAPON_SAW
)) then
4088 FActionPrior
:= Prior
;
4089 FActionAnim
:= Action
;
4090 FActionForce
:= Force
;
4091 FActionChanged
:= True;
4094 if Action
in [A_ATTACK
, A_ATTACKUP
, A_ATTACKDOWN
] then FModel
.SetFire(True);
4097 function TPlayer
.StayOnStep(XInc
, YInc
: Integer): Boolean;
4099 Result
:= not g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+YInc
+PLAYER_RECT
.Y
+PLAYER_RECT
.Height
-1,
4100 PLAYER_RECT
.Width
, 1, PANEL_STEP
, False)
4101 and g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+YInc
+PLAYER_RECT
.Y
+PLAYER_RECT
.Height
,
4102 PLAYER_RECT
.Width
, 1, PANEL_STEP
, False);
4105 function TPlayer
.TeleportTo(X
, Y
: Integer; silent
: Boolean; dir
: Byte): Boolean;
4109 if g_CollideLevel(X
, Y
, PLAYER_RECT
.Width
, PLAYER_RECT
.Height
) then
4111 g_Sound_PlayExAt('SOUND_GAME_NOTELEPORT', FObj
.X
, FObj
.Y
);
4112 if g_Game_IsServer
and g_Game_IsNet
then
4113 MH_SEND_Sound(FObj
.X
, FObj
.Y
, 'SOUND_GAME_NOTELEPORT');
4117 FJustTeleported
:= True;
4121 g_Sound_PlayExAt('SOUND_GAME_TELEPORT', FObj
.X
, FObj
.Y
);
4123 R_GFX_TELEPORT_FAST
,
4124 FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)-32,
4125 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2)-32
4127 if g_Game_IsServer
and g_Game_IsNet
then
4128 MH_SEND_Effect(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)-32,
4129 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2)-32, 1,
4133 FObj
.X
:= X
-PLAYER_RECT
.X
;
4134 FObj
.Y
:= Y
-PLAYER_RECT
.Y
;
4135 FObj
.oldX
:= FObj
.X
; // don't interpolate after respawn
4136 FObj
.oldY
:= FObj
.Y
;
4137 if FAlive
and FGhost
then
4143 if not g_Game_IsNet
then
4147 SetDirection(TDirection
.D_LEFT
);
4153 SetDirection(TDirection
.D_RIGHT
);
4159 if FDirection
= TDirection
.D_RIGHT
then
4161 SetDirection(TDirection
.D_LEFT
);
4166 SetDirection(TDirection
.D_RIGHT
);
4175 R_GFX_TELEPORT_FAST
,
4176 FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)-32,
4177 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2)-32
4179 if g_Game_IsServer
and g_Game_IsNet
then
4180 MH_SEND_Effect(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)-32,
4181 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2)-32, 0,
4188 function nonz(a
: Single): Single;
4196 function TPlayer
.refreshCorpse(): Boolean;
4202 if FAlive
or FSpectator
then
4204 if (gCorpses
= nil) or (Length(gCorpses
) = 0) then
4206 for i
:= 0 to High(gCorpses
) do
4207 if gCorpses
[i
] <> nil then
4208 if gCorpses
[i
].FPlayerUID
= FUID
then
4216 function TPlayer
.getCameraObj(): TObj
;
4218 if (not FAlive
) and (not FSpectator
) and
4219 (FCorpse
>= 0) and (FCorpse
< Length(gCorpses
)) and
4220 (gCorpses
[FCorpse
] <> nil) and (gCorpses
[FCorpse
].FPlayerUID
= FUID
) then
4222 gCorpses
[FCorpse
].FObj
.slopeUpLeft
:= FObj
.slopeUpLeft
;
4223 Result
:= gCorpses
[FCorpse
].FObj
;
4231 procedure TPlayer
.PreUpdate();
4233 FSlopeOld
:= FObj
.slopeUpLeft
;
4234 FIncCamOld
:= FIncCam
;
4235 FObj
.oldX
:= FObj
.X
;
4236 FObj
.oldY
:= FObj
.Y
;
4239 procedure TPlayer
.Update();
4242 i
, ii
, wx
, wy
, xd
, yd
, k
: Integer;
4243 blockmon
, headwater
, dospawn
: Boolean;
4248 NetServer
:= g_Game_IsNet
and g_Game_IsServer
;
4249 AnyServer
:= g_Game_IsServer
;
4251 if g_Game_IsClient
and (NetInterpLevel
> 0) then
4252 DoLerp(NetInterpLevel
+ 1)
4258 if FClientID
>= 0 then
4260 FPing
:= NetClients
[FClientID
].Peer
^.lastRoundTripTime
;
4261 if NetClients
[FClientID
].Peer
^.packetsSent
> 0 then
4262 FLoss
:= Round(100*NetClients
[FClientID
].Peer
^.packetsLost
/NetClients
[FClientID
].Peer
^.packetsSent
)
4273 if FPunchAnim
.played
then
4276 if FAlive
and (gFly
or FJetpack
) then
4279 if FDirection
= TDirection
.D_LEFT
then
4284 if FAlive
and (not FGhost
) then
4286 if FKeys
[KEY_UP
].Pressed
then
4288 if FKeys
[KEY_DOWN
].Pressed
then
4292 if (not (FKeys
[KEY_UP
].Pressed
or FKeys
[KEY_DOWN
].Pressed
)) and
4295 i
:= g_basic
.Sign(FIncCam
);
4296 FIncCam
:= Abs(FIncCam
);
4297 DecMin(FIncCam
, 5, 0);
4298 FIncCam
:= FIncCam
*i
;
4301 // no need to do that each second frame, weapon queue will take care of it
4302 if FAlive
and FKeys
[KEY_NEXTWEAPON
].Pressed
and AnyServer
then NextWeapon();
4303 if FAlive
and FKeys
[KEY_PREVWEAPON
].Pressed
and AnyServer
then PrevWeapon();
4305 if gTime
mod (GAME_TICK
*2) <> 0 then
4307 if (FObj
.Vel
.X
= 0) and FAlive
then
4309 if FKeys
[KEY_LEFT
].Pressed
then
4310 Run(TDirection
.D_LEFT
);
4311 if FKeys
[KEY_RIGHT
].Pressed
then
4312 Run(TDirection
.D_RIGHT
);
4317 g_Obj_Move(@FObj
, True, True, True);
4318 positionChanged(); // this updates spatial accelerators
4324 FActionChanged
:= False;
4328 // Let alive player do some actions
4329 if FKeys
[KEY_LEFT
].Pressed
then Run(TDirection
.D_LEFT
);
4330 if FKeys
[KEY_RIGHT
].Pressed
then Run(TDirection
.D_RIGHT
);
4331 //if FKeys[KEY_NEXTWEAPON].Pressed and AnyServer then NextWeapon();
4332 //if FKeys[KEY_PREVWEAPON].Pressed and AnyServer then PrevWeapon();
4333 if FKeys
[KEY_FIRE
].Pressed
and AnyServer
then Fire()
4339 if NetServer
then MH_SEND_PlayerStats(FUID
);
4342 if FKeys
[KEY_OPEN
].Pressed
and AnyServer
then Use();
4343 if FKeys
[KEY_JUMP
].Pressed
then Jump()
4346 if AnyServer
and FJetpack
then
4350 if NetServer
then MH_SEND_PlayerStats(FUID
);
4352 FCanJetpack
:= True;
4359 for k
:= Low(FKeys
) to KEY_CHAT
-1 do
4361 if FKeys
[k
].Pressed
then
4369 if gGameSettings
.GameType
in [GT_CUSTOM
, GT_SERVER
, GT_CLIENT
] then
4372 if (FTime
[T_RESPAWN
] <= gTime
) and
4373 gGameOn
and (not FAlive
) then
4375 if (g_Player_GetCount() > 1) then
4379 gExit
:= EXIT_RESTART
;
4384 // Dead spectator actions
4387 if FKeys
[KEY_OPEN
].Pressed
and AnyServer
then Fire();
4388 if FKeys
[KEY_FIRE
].Pressed
and AnyServer
then
4392 if (FSpectatePlayer
>= High(gPlayers
)) then
4393 FSpectatePlayer
:= -1
4397 for I
:= FSpectatePlayer
+ 1 to High(gPlayers
) do
4398 if gPlayers
[I
] <> nil then
4399 if gPlayers
[I
].alive
then
4400 if gPlayers
[I
].UID
<> FUID
then
4402 FSpectatePlayer
:= I
;
4407 if not SetSpect
then FSpectatePlayer
:= -1;
4418 if FKeys
[KEY_UP
].Pressed
or FKeys
[KEY_JUMP
].Pressed
then
4420 FYTo
:= FObj
.Y
- 32;
4421 FSpectatePlayer
:= -1;
4423 if FKeys
[KEY_DOWN
].Pressed
then
4425 FYTo
:= FObj
.Y
+ 32;
4426 FSpectatePlayer
:= -1;
4428 if FKeys
[KEY_LEFT
].Pressed
then
4430 FXTo
:= FObj
.X
- 32;
4431 FSpectatePlayer
:= -1;
4433 if FKeys
[KEY_RIGHT
].Pressed
then
4435 FXTo
:= FObj
.X
+ 32;
4436 FSpectatePlayer
:= -1;
4439 if (FXTo
< -64) then
4441 else if (FXTo
> gMapInfo
.Width
+ 32) then
4442 FXTo
:= gMapInfo
.Width
+ 32;
4443 if (FYTo
< -72) then
4445 else if (FYTo
> gMapInfo
.Height
+ 32) then
4446 FYTo
:= gMapInfo
.Height
+ 32;
4451 g_Obj_Move(@FObj
, True, True, True);
4452 positionChanged(); // this updates spatial accelerators
4459 if (FSpectatePlayer
<= High(gPlayers
)) and (FSpectatePlayer
>= 0) then
4460 if gPlayers
[FSpectatePlayer
] <> nil then
4461 if gPlayers
[FSpectatePlayer
].alive
then
4463 FXTo
:= gPlayers
[FSpectatePlayer
].GameX
;
4464 FYTo
:= gPlayers
[FSpectatePlayer
].GameY
;
4468 blockmon
:= g_Map_CollidePanel(FObj
.X
+PLAYER_HEADRECT
.X
, FObj
.Y
+PLAYER_HEADRECT
.Y
,
4469 PLAYER_HEADRECT
.Width
, PLAYER_HEADRECT
.Height
,
4470 PANEL_BLOCKMON
, True);
4471 headwater
:= HeadInLiquid(0, 0);
4473 // Сопротивление воздуха:
4474 if (not FAlive
) or not (FKeys
[KEY_LEFT
].Pressed
or FKeys
[KEY_RIGHT
].Pressed
) then
4475 if FObj
.Vel
.X
<> 0 then
4476 FObj
.Vel
.X
:= z_dec(FObj
.Vel
.X
, 1);
4478 if (FLastHit
= HIT_TRAP
) and (FPain
> 90) then FPain
:= 90;
4479 DecMin(FPain
, 5, 0);
4480 DecMin(FPickup
, 1, 0);
4482 if FAlive
and (FObj
.Y
> Integer(gMapInfo
.Height
)+128) and AnyServer
then
4484 // Обнулить действия примочек, чтобы фон пропал
4485 FMegaRulez
[MR_SUIT
] := 0;
4486 FMegaRulez
[MR_INVUL
] := 0;
4487 FMegaRulez
[MR_INVIS
] := 0;
4488 Kill(K_FALLKILL
, 0, HIT_FALL
);
4495 if FCurrWeap
= WEAPON_SAW
then
4496 if not (FSawSound
.IsPlaying() or FSawSoundHit
.IsPlaying() or
4497 FSawSoundSelect
.IsPlaying()) then
4498 FSawSoundIdle
.PlayAt(FObj
.X
, FObj
.Y
);
4501 if (not FJetSoundFly
.IsPlaying()) and (not FJetSoundOn
.IsPlaying()) and
4502 (not FJetSoundOff
.IsPlaying()) then
4504 FJetSoundFly
.SetPosition(0);
4505 FJetSoundFly
.PlayAt(FObj
.X
, FObj
.Y
);
4508 for b
:= WP_FIRST
to WP_LAST
do
4509 if FReloading
[b
] > 0 then
4515 if FShellTimer
> -1 then
4516 if FShellTimer
= 0 then
4518 if FShellType
= SHELL_SHELL
then
4519 g_Player_CreateShell(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
4520 GameVelX
, GameVelY
-2, SHELL_SHELL
)
4521 else if FShellType
= SHELL_DBLSHELL
then
4523 g_Player_CreateShell(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
4524 GameVelX
+1, GameVelY
-2, SHELL_SHELL
);
4525 g_Player_CreateShell(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
4526 GameVelX
-1, GameVelY
-2, SHELL_SHELL
);
4529 end else Dec(FShellTimer
);
4531 if (FBFGFireCounter
> -1) then
4532 if FBFGFireCounter
= 0 then
4536 wx
:= FObj
.X
+WEAPONPOINT
[FDirection
].X
;
4537 wy
:= FObj
.Y
+WEAPONPOINT
[FDirection
].Y
;
4538 xd
:= wx
+IfThen(FDirection
= TDirection
.D_LEFT
, -30, 30);
4539 yd
:= wy
+firediry();
4540 g_Weapon_bfgshot(wx
, wy
, xd
, yd
, FUID
);
4541 if NetServer
then MH_SEND_PlayerFire(FUID
, WEAPON_BFG
, wx
, wy
, xd
, yd
);
4542 if (FAngle
= 0) or (FAngle
= 180) then SetAction(A_ATTACK
)
4543 else if (FAngle
= ANGLE_LEFTDOWN
) or (FAngle
= ANGLE_RIGHTDOWN
) then SetAction(A_ATTACKDOWN
)
4544 else if (FAngle
= ANGLE_LEFTUP
) or (FAngle
= ANGLE_RIGHTUP
) then SetAction(A_ATTACKUP
);
4547 FReloading
[WEAPON_BFG
] := WEAPON_RELOAD
[WEAPON_BFG
];
4548 FBFGFireCounter
:= -1;
4551 FBFGFireCounter
:= 0
4553 Dec(FBFGFireCounter
);
4555 if (FMegaRulez
[MR_SUIT
] < gTime
) and AnyServer
then
4557 b
:= g_GetAcidHit(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+PLAYER_RECT
.Y
, PLAYER_RECT
.Width
, PLAYER_RECT
.Height
);
4559 if (b
> 0) and (gTime
mod (15*GAME_TICK
) = 0) then Damage(b
, 0, 0, 0, HIT_ACID
);
4562 if (headwater
or blockmon
) then
4568 if AnyServer
then Damage(10, 0, 0, 0, HIT_WATER
);
4571 else if (FAir
mod 31 = 0) and not blockmon
then
4573 g_GFX_Bubbles(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2), FObj
.Y
+PLAYER_RECT
.Y
-4, 5+Random(6), 8, 4);
4574 if Random(2) = 0 then
4575 g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj
.X
, FObj
.Y
)
4577 g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj
.X
, FObj
.Y
);
4579 end else if FAir
< AIR_DEF
then
4582 if FFireTime
> 0 then
4584 if BodyInLiquid(0, 0) then
4589 else if FMegaRulez
[MR_SUIT
] >= gTime
then
4591 if FMegaRulez
[MR_SUIT
] = gTime
then
4598 if FFirePainTime
<= 0 then
4600 if g_Game_IsServer
then
4601 Damage(2, FFireAttacker
, 0, 0, HIT_FLAME
);
4602 FFirePainTime
:= 12 - FFireTime
div 12;
4604 FFirePainTime
:= FFirePainTime
- 1;
4605 FFireTime
:= FFireTime
- 1;
4606 if ((FFireTime
mod 33) = 0) and (FMegaRulez
[MR_INVUL
] < gTime
) then
4607 FModel
.PlaySound(MODELSOUND_PAIN
, 1, FObj
.X
, FObj
.Y
);
4608 if (FFireTime
= 0) and g_Game_IsNet
and g_Game_IsServer
then
4609 MH_SEND_PlayerStats(FUID
);
4613 if FDamageBuffer
> 0 then
4615 if FDamageBuffer
>= 9 then
4619 if FDamageBuffer
< 30 then i
:= 9
4620 else if FDamageBuffer
< 100 then i
:= 18
4624 ii
:= Round(FDamageBuffer
*FHealth
/ nonz(FArmor
*(3/4)+FHealth
));
4625 FArmor
:= FArmor
-(FDamageBuffer
-ii
);
4626 FHealth
:= FHealth
-ii
;
4629 FHealth
:= FHealth
+FArmor
;
4634 if FHealth
<= 0 then
4635 if FHealth
> -30 then Kill(K_SIMPLEKILL
, FLastSpawnerUID
, FLastHit
)
4636 else if FHealth
> -50 then Kill(K_HARDKILL
, FLastSpawnerUID
, FLastHit
)
4637 else Kill(K_EXTRAHARDKILL
, FLastSpawnerUID
, FLastHit
);
4639 if FAlive
and ((FLastHit
<> HIT_FLAME
) or (FFireTime
<= 0)) then
4641 if FDamageBuffer
<= 20 then FModel
.PlaySound(MODELSOUND_PAIN
, 1, FObj
.X
, FObj
.Y
)
4642 else if FDamageBuffer
<= 55 then FModel
.PlaySound(MODELSOUND_PAIN
, 2, FObj
.X
, FObj
.Y
)
4643 else if FDamageBuffer
<= 120 then FModel
.PlaySound(MODELSOUND_PAIN
, 3, FObj
.X
, FObj
.Y
)
4644 else FModel
.PlaySound(MODELSOUND_PAIN
, 4, FObj
.X
, FObj
.Y
);
4651 end; // if FAlive then ...
4653 if (FActionAnim
= A_PAIN
) and (FModel
.Animation
<> A_PAIN
) then
4655 FModel
.ChangeAnimation(FActionAnim
, FActionForce
);
4656 FModel
.AnimState
.MinLength
:= i
;
4657 end else FModel
.ChangeAnimation(FActionAnim
, FActionForce
and (FModel
.Animation
<> A_STAND
));
4659 if (FModel
.AnimState
.Played
or ((not FActionChanged
) and (FModel
.Animation
= A_WALK
)))
4660 then SetAction(A_STAND
, True);
4662 if not ((FModel
.Animation
= A_WALK
) and (Abs(FObj
.Vel
.X
) < 4) and not FModel
.GetFire()) then FModel
.Update
;
4664 for b
:= Low(FKeys
) to High(FKeys
) do
4665 if FKeys
[b
].Time
= 0 then FKeys
[b
].Pressed
:= False else Dec(FKeys
[b
].Time
);
4669 procedure TPlayer
.getMapBox (out x
, y
, w
, h
: Integer); inline;
4671 x
:= FObj
.X
+PLAYER_RECT
.X
;
4672 y
:= FObj
.Y
+PLAYER_RECT
.Y
;
4673 w
:= PLAYER_RECT
.Width
;
4674 h
:= PLAYER_RECT
.Height
;
4678 procedure TPlayer
.moveBy (dx
, dy
: Integer); inline;
4680 if (dx
<> 0) or (dy
<> 0) then
4689 function TPlayer
.Collide(X
, Y
: Integer; Width
, Height
: Word): Boolean;
4691 Result
:= g_Collide(FObj
.X
+PLAYER_RECT
.X
,
4692 FObj
.Y
+PLAYER_RECT
.Y
,
4699 function TPlayer
.Collide(Panel
: TPanel
): Boolean;
4701 Result
:= g_Collide(FObj
.X
+PLAYER_RECT
.X
,
4702 FObj
.Y
+PLAYER_RECT
.Y
,
4706 Panel
.Width
, Panel
.Height
);
4709 function TPlayer
.Collide(X
, Y
: Integer): Boolean;
4711 X
:= X
-FObj
.X
-PLAYER_RECT
.X
;
4712 Y
:= Y
-FObj
.Y
-PLAYER_RECT
.Y
;
4713 Result
:= (x
>= 0) and (x
<= PLAYER_RECT
.Width
) and
4714 (y
>= 0) and (y
<= PLAYER_RECT
.Height
);
4717 function g_Player_ValidName(Name
: string): Boolean;
4723 if gPlayers
= nil then Exit
;
4725 for a
:= 0 to High(gPlayers
) do
4726 if gPlayers
[a
] <> nil then
4727 if LowerCase(Name
) = LowerCase(gPlayers
[a
].FName
) then
4734 procedure TPlayer
.SetDirection(Direction
: TDirection
);
4738 d
:= FModel
.Direction
;
4740 FModel
.Direction
:= Direction
;
4741 if d
<> Direction
then FModel
.ChangeAnimation(FModel
.Animation
, True);
4743 FDirection
:= Direction
;
4746 function TPlayer
.GetKeys(): Byte;
4750 if R_KEY_RED
in FRulez
then Result
:= KEY_RED
;
4751 if R_KEY_GREEN
in FRulez
then Result
:= Result
or KEY_GREEN
;
4752 if R_KEY_BLUE
in FRulez
then Result
:= Result
or KEY_BLUE
;
4754 if FTeam
= TEAM_RED
then Result
:= Result
or KEY_REDTEAM
;
4755 if FTeam
= TEAM_BLUE
then Result
:= Result
or KEY_BLUETEAM
;
4758 procedure TPlayer
.Use();
4762 if FTime
[T_USE
] > gTime
then Exit
;
4764 g_Triggers_PressR(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+PLAYER_RECT
.Y
, PLAYER_RECT
.Width
,
4765 PLAYER_RECT
.Height
, FUID
, ACTIVATE_PLAYERPRESS
);
4767 for a
:= 0 to High(gPlayers
) do
4768 if (gPlayers
[a
] <> nil) and (gPlayers
[a
] <> Self
) and
4769 gPlayers
[a
].alive
and SameTeam(FUID
, gPlayers
[a
].FUID
) and
4770 g_Obj_Collide(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
,
4771 FObj
.Rect
.Width
, FObj
.Rect
.Height
, @gPlayers
[a
].FObj
) then
4773 gPlayers
[a
].Touch();
4774 if g_Game_IsNet
and g_Game_IsServer
then
4775 MH_SEND_GameEvent(NET_EV_PLAYER_TOUCH
, gPlayers
[a
].FUID
);
4778 FTime
[T_USE
] := gTime
+120;
4781 procedure TPlayer
.NetFire(Wpn
: Byte; X
, Y
, AX
, AY
: Integer; WID
: Integer = -1);
4785 WX
, WY
, XD
, YD
: Integer;
4797 if R_BERSERK
in FRulez
then
4799 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
4800 locobj
.X
:= FObj
.X
+FObj
.Rect
.X
;
4801 locobj
.Y
:= FObj
.Y
+FObj
.Rect
.Y
;
4804 locobj
.rect
.Width
:= 39;
4805 locobj
.rect
.Height
:= 52;
4806 locobj
.Vel
.X
:= (xd
-wx
) div 2;
4807 locobj
.Vel
.Y
:= (yd
-wy
) div 2;
4808 locobj
.Accel
.X
:= xd
-wx
;
4809 locobj
.Accel
.y
:= yd
-wy
;
4811 if g_Weapon_Hit(@locobj
, 50, FUID
, HIT_SOME
) <> 0 then
4812 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj
.X
, FObj
.Y
)
4814 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj
.X
, FObj
.Y
);
4818 FPain
:= min(FPain
+ 25, 50);
4820 g_Weapon_punch(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
, 3, FUID
);
4825 if g_Weapon_chainsaw(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
,
4826 IfThen(gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
], 9, 3), FUID
) <> 0 then
4828 FSawSoundSelect
.Stop();
4830 FSawSoundHit
.PlayAt(FObj
.X
, FObj
.Y
);
4832 else if not FSawSoundHit
.IsPlaying() then
4834 FSawSoundSelect
.Stop();
4835 FSawSound
.PlayAt(FObj
.X
, FObj
.Y
);
4842 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', GameX
, Gamey
);
4843 FFireAngle
:= FAngle
;
4845 g_Player_CreateShell(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
4846 GameVelX
, GameVelY
-2, SHELL_BULLET
);
4851 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex
, Gamey
);
4852 FFireAngle
:= FAngle
;
4855 FShellType
:= SHELL_SHELL
;
4860 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', Gamex
, Gamey
);
4861 FFireAngle
:= FAngle
;
4864 FShellType
:= SHELL_DBLSHELL
;
4869 g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', Gamex
, Gamey
);
4870 FFireAngle
:= FAngle
;
4872 g_Player_CreateShell(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
4873 GameVelX
, GameVelY
-2, SHELL_BULLET
);
4876 WEAPON_ROCKETLAUNCHER
:
4878 g_Weapon_Rocket(wx
, wy
, xd
, yd
, FUID
, WID
);
4879 FFireAngle
:= FAngle
;
4885 g_Weapon_Plasma(wx
, wy
, xd
, yd
, FUID
, WID
);
4886 FFireAngle
:= FAngle
;
4892 g_Weapon_BFGShot(wx
, wy
, xd
, yd
, FUID
, WID
);
4893 FFireAngle
:= FAngle
;
4897 WEAPON_SUPERPULEMET
:
4899 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex
, Gamey
);
4900 FFireAngle
:= FAngle
;
4902 g_Player_CreateShell(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
4903 GameVelX
, GameVelY
-2, SHELL_SHELL
);
4906 WEAPON_FLAMETHROWER
:
4908 g_Weapon_flame(wx
, wy
, xd
, yd
, FUID
, WID
);
4910 FFireAngle
:= FAngle
;
4917 if (FAngle
= 0) or (FAngle
= 180) then SetAction(A_ATTACK
)
4918 else if (FAngle
= ANGLE_LEFTDOWN
) or (FAngle
= ANGLE_RIGHTDOWN
) then SetAction(A_ATTACKDOWN
)
4919 else if (FAngle
= ANGLE_LEFTUP
) or (FAngle
= ANGLE_RIGHTUP
) then SetAction(A_ATTACKUP
);
4922 procedure TPlayer
.DoLerp(Level
: Integer = 2);
4924 if FObj
.X
<> FXTo
then FObj
.X
:= Lerp(FObj
.X
, FXTo
, Level
);
4925 if FObj
.Y
<> FYTo
then FObj
.Y
:= Lerp(FObj
.Y
, FYTo
, Level
);
4928 procedure TPlayer
.SetLerp(XTo
, YTo
: Integer);
4934 if FJustTeleported
or (NetInterpLevel
< 1) then
4938 if FJustTeleported
then
4940 FObj
.oldX
:= FObj
.X
;
4941 FObj
.oldY
:= FObj
.Y
;
4946 AX
:= Abs(FXTo
- FObj
.X
);
4947 AY
:= Abs(FYTo
- FObj
.Y
);
4948 if (AX
> 32) or (AX
<= NetInterpLevel
) then
4950 if (AY
> 32) or (AY
<= NetInterpLevel
) then
4955 function TPlayer
.FullInLift(XInc
, YInc
: Integer): Integer;
4957 if g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
,
4958 PLAYER_RECT
.Width
, PLAYER_RECT
.Height
-8,
4959 PANEL_LIFTUP
, False) then Result
:= -1
4961 if g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
,
4962 PLAYER_RECT
.Width
, PLAYER_RECT
.Height
-8,
4963 PANEL_LIFTDOWN
, False) then Result
:= 1
4967 function TPlayer
.GetFlag(Flag
: Byte): Boolean;
4974 if Flag
= FLAG_NONE
then
4977 if not g_Game_IsServer
then Exit
;
4979 // Принес чужой флаг на свою базу:
4980 if (Flag
= FTeam
) and
4981 (gFlags
[Flag
].State
= FLAG_STATE_NORMAL
) and
4982 (FFlag
<> FLAG_NONE
) then
4984 if FFlag
= FLAG_RED
then
4985 s
:= _lc
[I_PLAYER_FLAG_RED
]
4987 s
:= _lc
[I_PLAYER_FLAG_BLUE
];
4989 evtype
:= FLAG_STATE_SCORED
;
4991 ts
:= Format('%.4d', [gFlags
[FFlag
].CaptureTime
]);
4992 Insert('.', ts
, Length(ts
) + 1 - 3);
4993 g_Console_Add(Format(_lc
[I_PLAYER_FLAG_CAPTURE
], [FName
, s
, ts
]), True);
4995 g_Map_ResetFlag(FFlag
);
4996 g_Game_Message(Format(_lc
[I_MESSAGE_FLAG_CAPTURE
], [AnsiUpperCase(s
)]), 144);
4998 if ((Self
= gPlayer1
) or (Self
= gPlayer2
)
4999 or ((gPlayer1
<> nil) and (gPlayer1
.Team
= FTeam
))
5000 or ((gPlayer2
<> nil) and (gPlayer2
.Team
= FTeam
))) then
5005 if not sound_cap_flag
[a
].IsPlaying() then
5006 sound_cap_flag
[a
].Play();
5008 gTeamStat
[FTeam
].Goals
:= gTeamStat
[FTeam
].Goals
+ 1;
5011 if g_Game_IsNet
then
5013 MH_SEND_FlagEvent(evtype
, FFlag
, FUID
, False);
5017 gFlags
[FFlag
].CaptureTime
:= 0;
5022 // Подобрал свой флаг - вернул его на базу:
5023 if (Flag
= FTeam
) and
5024 (gFlags
[Flag
].State
= FLAG_STATE_DROPPED
) then
5026 if Flag
= FLAG_RED
then
5027 s
:= _lc
[I_PLAYER_FLAG_RED
]
5029 s
:= _lc
[I_PLAYER_FLAG_BLUE
];
5031 evtype
:= FLAG_STATE_RETURNED
;
5032 gFlags
[Flag
].CaptureTime
:= 0;
5034 g_Console_Add(Format(_lc
[I_PLAYER_FLAG_RETURN
], [FName
, s
]), True);
5036 g_Map_ResetFlag(Flag
);
5037 g_Game_Message(Format(_lc
[I_MESSAGE_FLAG_RETURN
], [AnsiUpperCase(s
)]), 144);
5039 if ((Self
= gPlayer1
) or (Self
= gPlayer2
)
5040 or ((gPlayer1
<> nil) and (gPlayer1
.Team
= FTeam
))
5041 or ((gPlayer2
<> nil) and (gPlayer2
.Team
= FTeam
))) then
5046 if not sound_ret_flag
[a
].IsPlaying() then
5047 sound_ret_flag
[a
].Play();
5050 if g_Game_IsNet
then
5052 MH_SEND_FlagEvent(evtype
, Flag
, FUID
, False);
5058 // Подобрал чужой флаг:
5059 if (Flag
<> FTeam
) and (FTime
[T_FLAGCAP
] <= gTime
) then
5063 if Flag
= FLAG_RED
then
5064 s
:= _lc
[I_PLAYER_FLAG_RED
]
5066 s
:= _lc
[I_PLAYER_FLAG_BLUE
];
5068 evtype
:= FLAG_STATE_CAPTURED
;
5070 g_Console_Add(Format(_lc
[I_PLAYER_FLAG_GET
], [FName
, s
]), True);
5072 g_Game_Message(Format(_lc
[I_MESSAGE_FLAG_GET
], [AnsiUpperCase(s
)]), 144);
5074 gFlags
[Flag
].State
:= FLAG_STATE_CAPTURED
;
5076 if ((Self
= gPlayer1
) or (Self
= gPlayer2
)
5077 or ((gPlayer1
<> nil) and (gPlayer1
.Team
= FTeam
))
5078 or ((gPlayer2
<> nil) and (gPlayer2
.Team
= FTeam
))) then
5083 if not sound_get_flag
[a
].IsPlaying() then
5084 sound_get_flag
[a
].Play();
5087 if g_Game_IsNet
then
5089 MH_SEND_FlagEvent(evtype
, Flag
, FUID
, False);
5095 procedure TPlayer
.SetFlag(Flag
: Byte);
5098 if FModel
<> nil then
5099 FModel
.SetFlag(FFlag
);
5102 function TPlayer
.DropFlag(Silent
: Boolean = True): Boolean;
5108 if (not g_Game_IsServer
) or (FFlag
= FLAG_NONE
) then
5110 FTime
[T_FLAGCAP
] := gTime
+ 2000;
5111 with gFlags
[FFlag
] do
5115 Direction
:= FDirection
;
5116 State
:= FLAG_STATE_DROPPED
;
5118 g_Obj_Push(@Obj
, (FObj
.Vel
.X
div 2)-2+Random(5),
5119 (FObj
.Vel
.Y
div 2)-2+Random(5));
5120 positionChanged(); // this updates spatial accelerators
5122 if FFlag
= FLAG_RED
then
5123 s
:= _lc
[I_PLAYER_FLAG_RED
]
5125 s
:= _lc
[I_PLAYER_FLAG_BLUE
];
5127 g_Console_Add(Format(_lc
[I_PLAYER_FLAG_DROP
], [FName
, s
]), True);
5128 g_Game_Message(Format(_lc
[I_MESSAGE_FLAG_DROP
], [AnsiUpperCase(s
)]), 144);
5130 if ((Self
= gPlayer1
) or (Self
= gPlayer2
)
5131 or ((gPlayer1
<> nil) and (gPlayer1
.Team
= FTeam
))
5132 or ((gPlayer2
<> nil) and (gPlayer2
.Team
= FTeam
))) then
5137 if (not Silent
) and (not sound_lost_flag
[a
].IsPlaying()) then
5138 sound_lost_flag
[a
].Play();
5140 if g_Game_IsNet
then
5141 MH_SEND_FlagEvent(FLAG_STATE_DROPPED
, Flag
, FUID
, False);
5147 procedure TPlayer
.GetSecret();
5149 if (self
= gPlayer1
) or (self
= gPlayer2
) then
5151 g_Console_Add(Format(_lc
[I_PLAYER_SECRET
], [FName
]), True);
5152 g_Sound_PlayEx('SOUND_GAME_SECRET');
5157 procedure TPlayer
.PressKey(Key
: Byte; Time
: Word = 1);
5159 Assert(Key
<= High(FKeys
));
5161 FKeys
[Key
].Pressed
:= True;
5162 FKeys
[Key
].Time
:= Time
;
5165 function TPlayer
.IsKeyPressed(K
: Byte): Boolean;
5167 Result
:= FKeys
[K
].Pressed
;
5170 procedure TPlayer
.ReleaseKeys();
5174 for a
:= Low(FKeys
) to High(FKeys
) do
5176 FKeys
[a
].Pressed
:= False;
5181 procedure TPlayer
.OnDamage(Angle
: SmallInt);
5185 function TPlayer
.firediry(): Integer;
5187 if FKeys
[KEY_UP
].Pressed
then Result
:= -42
5188 else if FKeys
[KEY_DOWN
].Pressed
then Result
:= 19
5192 procedure TPlayer
.RememberState();
5195 SavedState
: TPlayerSavedState
;
5197 SavedState
.Health
:= FHealth
;
5198 SavedState
.Armor
:= FArmor
;
5199 SavedState
.Air
:= FAir
;
5200 SavedState
.JetFuel
:= FJetFuel
;
5201 SavedState
.CurrWeap
:= FCurrWeap
;
5202 SavedState
.NextWeap
:= FNextWeap
;
5203 SavedState
.NextWeapDelay
:= FNextWeapDelay
;
5204 for i
:= Low(FWeapon
) to High(FWeapon
) do
5205 SavedState
.Weapon
[i
] := FWeapon
[i
];
5206 for i
:= Low(FAmmo
) to High(FAmmo
) do
5207 SavedState
.Ammo
[i
] := FAmmo
[i
];
5208 for i
:= Low(FMaxAmmo
) to High(FMaxAmmo
) do
5209 SavedState
.MaxAmmo
[i
] := FMaxAmmo
[i
];
5210 SavedState
.Rulez
:= FRulez
- [R_KEY_RED
, R_KEY_GREEN
, R_KEY_BLUE
];
5212 FSavedStateNum
:= -1;
5213 for i
:= Low(SavedStates
) to High(SavedStates
) do
5214 if not SavedStates
[i
].Used
then
5216 FSavedStateNum
:= i
;
5219 if FSavedStateNum
< 0 then
5221 SetLength(SavedStates
, Length(SavedStates
) + 1);
5222 FSavedStateNum
:= High(SavedStates
);
5225 SavedState
.Used
:= True;
5226 SavedStates
[FSavedStateNum
] := SavedState
;
5229 procedure TPlayer
.RecallState();
5232 SavedState
: TPlayerSavedState
;
5234 if(FSavedStateNum
< 0) or (FSavedStateNum
> High(SavedStates
)) then
5237 SavedState
:= SavedStates
[FSavedStateNum
];
5238 SavedStates
[FSavedStateNum
].Used
:= False;
5239 FSavedStateNum
:= -1;
5241 FHealth
:= SavedState
.Health
;
5242 FArmor
:= SavedState
.Armor
;
5243 FAir
:= SavedState
.Air
;
5244 FJetFuel
:= SavedState
.JetFuel
;
5245 FCurrWeap
:= SavedState
.CurrWeap
;
5246 FNextWeap
:= SavedState
.NextWeap
;
5247 FNextWeapDelay
:= SavedState
.NextWeapDelay
;
5248 for i
:= Low(FWeapon
) to High(FWeapon
) do
5249 FWeapon
[i
] := SavedState
.Weapon
[i
];
5250 for i
:= Low(FAmmo
) to High(FAmmo
) do
5251 FAmmo
[i
] := SavedState
.Ammo
[i
];
5252 for i
:= Low(FMaxAmmo
) to High(FMaxAmmo
) do
5253 FMaxAmmo
[i
] := SavedState
.MaxAmmo
[i
];
5254 FRulez
:= SavedState
.Rulez
;
5256 if gGameSettings
.GameType
= GT_SERVER
then
5257 MH_SEND_PlayerStats(FUID
);
5260 procedure TPlayer
.SaveState (st
: TStream
);
5266 utils
.writeSign(st
, 'PLYR');
5267 utils
.writeInt(st
, Byte(PLR_SAVE_VERSION
)); // version
5269 utils
.writeBool(st
, FIamBot
);
5271 utils
.writeInt(st
, Word(FUID
));
5273 utils
.writeStr(st
, FName
);
5275 utils
.writeInt(st
, Byte(FTeam
));
5277 utils
.writeBool(st
, FAlive
);
5278 // Израсходовал ли все жизни
5279 utils
.writeBool(st
, FNoRespawn
);
5281 if FDirection
= TDirection
.D_LEFT
then b
:= 1 else b
:= 2; // D_RIGHT
5282 utils
.writeInt(st
, Byte(b
));
5284 utils
.writeInt(st
, LongInt(FHealth
));
5285 // Коэффициент инвалидности
5286 utils
.writeInt(st
, LongInt(FHandicap
));
5288 utils
.writeInt(st
, Byte(FLives
));
5290 utils
.writeInt(st
, LongInt(FArmor
));
5292 utils
.writeInt(st
, LongInt(FAir
));
5294 utils
.writeInt(st
, LongInt(FJetFuel
));
5296 utils
.writeInt(st
, LongInt(FPain
));
5298 utils
.writeInt(st
, LongInt(FKills
));
5300 utils
.writeInt(st
, LongInt(FMonsterKills
));
5302 utils
.writeInt(st
, LongInt(FFrags
));
5304 utils
.writeInt(st
, Byte(FFragCombo
));
5305 // Время последнего фрага
5306 utils
.writeInt(st
, LongWord(FLastFrag
));
5308 utils
.writeInt(st
, LongInt(FDeath
));
5310 utils
.writeInt(st
, Byte(FFlag
));
5312 utils
.writeInt(st
, LongInt(FSecrets
));
5314 utils
.writeInt(st
, Byte(FCurrWeap
));
5316 utils
.writeInt(st
, Word(FNextWeap
));
5318 utils
.writeInt(st
, Byte(FNextWeapDelay
));
5319 // Время зарядки BFG
5320 utils
.writeInt(st
, SmallInt(FBFGFireCounter
));
5322 utils
.writeInt(st
, LongInt(FDamageBuffer
));
5323 // Последний ударивший
5324 utils
.writeInt(st
, Word(FLastSpawnerUID
));
5325 // Тип последнего полученного урона
5326 utils
.writeInt(st
, Byte(FLastHit
));
5328 Obj_SaveState(st
, @FObj
);
5329 // Текущее количество патронов
5330 for i
:= A_BULLETS
to A_HIGH
do utils
.writeInt(st
, Word(FAmmo
[i
]));
5331 // Максимальное количество патронов
5332 for i
:= A_BULLETS
to A_HIGH
do utils
.writeInt(st
, Word(FMaxAmmo
[i
]));
5334 for i
:= WP_FIRST
to WP_LAST
do utils
.writeBool(st
, FWeapon
[i
]);
5335 // Время перезарядки оружия
5336 for i
:= WP_FIRST
to WP_LAST
do utils
.writeInt(st
, Word(FReloading
[i
]));
5338 utils
.writeBool(st
, (R_ITEM_BACKPACK
in FRulez
));
5339 // Наличие красного ключа
5340 utils
.writeBool(st
, (R_KEY_RED
in FRulez
));
5341 // Наличие зеленого ключа
5342 utils
.writeBool(st
, (R_KEY_GREEN
in FRulez
));
5343 // Наличие синего ключа
5344 utils
.writeBool(st
, (R_KEY_BLUE
in FRulez
));
5346 utils
.writeBool(st
, (R_BERSERK
in FRulez
));
5347 // Время действия специальных предметов
5348 for i
:= MR_SUIT
to MR_MAX
do utils
.writeInt(st
, LongWord(FMegaRulez
[i
]));
5349 // Время до повторного респауна, смены оружия, исользования, захвата флага
5350 for i
:= T_RESPAWN
to T_FLAGCAP
do utils
.writeInt(st
, LongWord(FTime
[i
]));
5352 utils
.writeStr(st
, FModel
.GetName());
5354 utils
.writeInt(st
, Byte(FColor
.R
));
5355 utils
.writeInt(st
, Byte(FColor
.G
));
5356 utils
.writeInt(st
, Byte(FColor
.B
));
5360 procedure TPlayer
.LoadState (st
: TStream
);
5369 if not utils
.checkSign(st
, 'PLYR') then raise XStreamError
.Create('invalid player signature');
5370 if (utils
.readByte(st
) <> PLR_SAVE_VERSION
) then raise XStreamError
.Create('invalid player version');
5372 FIamBot
:= utils
.readBool(st
);
5374 FUID
:= utils
.readWord(st
);
5376 str
:= utils
.readStr(st
);
5377 if (self
<> gPlayer1
) and (self
<> gPlayer2
) then FName
:= str
;
5379 FTeam
:= utils
.readByte(st
);
5381 FAlive
:= utils
.readBool(st
);
5382 // Израсходовал ли все жизни
5383 FNoRespawn
:= utils
.readBool(st
);
5385 b
:= utils
.readByte(st
);
5386 if b
= 1 then FDirection
:= TDirection
.D_LEFT
else FDirection
:= TDirection
.D_RIGHT
; // b = 2
5388 FHealth
:= utils
.readLongInt(st
);
5389 // Коэффициент инвалидности
5390 FHandicap
:= utils
.readLongInt(st
);
5392 FLives
:= utils
.readByte(st
);
5394 FArmor
:= utils
.readLongInt(st
);
5396 FAir
:= utils
.readLongInt(st
);
5398 FJetFuel
:= utils
.readLongInt(st
);
5400 FPain
:= utils
.readLongInt(st
);
5402 FKills
:= utils
.readLongInt(st
);
5404 FMonsterKills
:= utils
.readLongInt(st
);
5406 FFrags
:= utils
.readLongInt(st
);
5408 FFragCombo
:= utils
.readByte(st
);
5409 // Время последнего фрага
5410 FLastFrag
:= utils
.readLongWord(st
);
5412 FDeath
:= utils
.readLongInt(st
);
5414 FFlag
:= utils
.readByte(st
);
5416 FSecrets
:= utils
.readLongInt(st
);
5418 FCurrWeap
:= utils
.readByte(st
);
5420 FNextWeap
:= utils
.readWord(st
);
5422 FNextWeapDelay
:= utils
.readByte(st
);
5423 // Время зарядки BFG
5424 FBFGFireCounter
:= utils
.readSmallInt(st
);
5426 FDamageBuffer
:= utils
.readLongInt(st
);
5427 // Последний ударивший
5428 FLastSpawnerUID
:= utils
.readWord(st
);
5429 // Тип последнего полученного урона
5430 FLastHit
:= utils
.readByte(st
);
5432 Obj_LoadState(@FObj
, st
);
5433 // Текущее количество патронов
5434 for i
:= A_BULLETS
to A_HIGH
do FAmmo
[i
] := utils
.readWord(st
);
5435 // Максимальное количество патронов
5436 for i
:= A_BULLETS
to A_HIGH
do FMaxAmmo
[i
] := utils
.readWord(st
);
5438 for i
:= WP_FIRST
to WP_LAST
do FWeapon
[i
] := utils
.readBool(st
);
5439 // Время перезарядки оружия
5440 for i
:= WP_FIRST
to WP_LAST
do FReloading
[i
] := utils
.readWord(st
);
5442 if utils
.readBool(st
) then Include(FRulez
, R_ITEM_BACKPACK
);
5443 // Наличие красного ключа
5444 if utils
.readBool(st
) then Include(FRulez
, R_KEY_RED
);
5445 // Наличие зеленого ключа
5446 if utils
.readBool(st
) then Include(FRulez
, R_KEY_GREEN
);
5447 // Наличие синего ключа
5448 if utils
.readBool(st
) then Include(FRulez
, R_KEY_BLUE
);
5450 if utils
.readBool(st
) then Include(FRulez
, R_BERSERK
);
5451 // Время действия специальных предметов
5452 for i
:= MR_SUIT
to MR_MAX
do FMegaRulez
[i
] := utils
.readLongWord(st
);
5453 // Время до повторного респауна, смены оружия, исользования, захвата флага
5454 for i
:= T_RESPAWN
to T_FLAGCAP
do FTime
[i
] := utils
.readLongWord(st
);
5456 str
:= utils
.readStr(st
);
5458 FColor
.R
:= utils
.readByte(st
);
5459 FColor
.G
:= utils
.readByte(st
);
5460 FColor
.B
:= utils
.readByte(st
);
5461 if (self
= gPlayer1
) then
5463 str
:= gPlayer1Settings
.Model
;
5464 FColor
:= gPlayer1Settings
.Color
;
5466 else if (self
= gPlayer2
) then
5468 str
:= gPlayer2Settings
.Model
;
5469 FColor
:= gPlayer2Settings
.Color
;
5471 // Обновляем модель игрока
5473 if gGameSettings
.GameMode
in [GM_TDM
, GM_CTF
] then
5474 FModel
.Color
:= TEAMCOLOR
[FTeam
]
5476 FModel
.Color
:= FColor
;
5480 procedure TPlayer
.AllRulez(Health
: Boolean);
5486 FHealth
:= PLAYER_HP_LIMIT
;
5487 FArmor
:= PLAYER_AP_LIMIT
;
5491 for a
:= WP_FIRST
to WP_LAST
do FWeapon
[a
] := True;
5492 for a
:= A_BULLETS
to A_HIGH
do FAmmo
[a
] := 30000;
5493 FRulez
:= FRulez
+[R_KEY_RED
, R_KEY_GREEN
, R_KEY_BLUE
];
5496 procedure TPlayer
.RestoreHealthArmor();
5498 FHealth
:= PLAYER_HP_LIMIT
;
5499 FArmor
:= PLAYER_AP_LIMIT
;
5502 procedure TPlayer
.FragCombo();
5506 if (gGameSettings
.GameMode
in [GM_COOP
, GM_SINGLE
]) or g_Game_IsClient
then
5508 if gTime
- FLastFrag
< FRAG_COMBO_TIME
then
5510 if FFragCombo
< 5 then
5512 Param
:= FUID
or (FFragCombo
shl 16);
5513 if (FComboEvnt
>= Low(gDelayedEvents
)) and
5514 (FComboEvnt
<= High(gDelayedEvents
)) and
5515 gDelayedEvents
[FComboEvnt
].Pending
and
5516 (gDelayedEvents
[FComboEvnt
].DEType
= DE_KILLCOMBO
) and
5517 (gDelayedEvents
[FComboEvnt
].DENum
and $FFFF = FUID
) then
5519 gDelayedEvents
[FComboEvnt
].Time
:= gTime
+ 500;
5520 gDelayedEvents
[FComboEvnt
].DENum
:= Param
;
5523 FComboEvnt
:= g_Game_DelayEvent(DE_KILLCOMBO
, 500, Param
);
5531 procedure TPlayer
.GiveItem(ItemType
: Byte);
5535 if FMegaRulez
[MR_SUIT
] < gTime
+PLAYER_SUIT_TIME
then
5537 FMegaRulez
[MR_SUIT
] := gTime
+PLAYER_SUIT_TIME
;
5541 if FAir
< AIR_MAX
then
5548 if not (R_BERSERK
in FRulez
) then
5550 Include(FRulez
, R_BERSERK
);
5551 if FBFGFireCounter
< 1 then
5553 FCurrWeap
:= WEAPON_KASTET
;
5555 FModel
.SetWeapon(WEAPON_KASTET
);
5559 FBerserk
:= gTime
+30000;
5561 if FHealth
< PLAYER_HP_SOFT
then
5563 FHealth
:= PLAYER_HP_SOFT
;
5564 FBerserk
:= gTime
+30000;
5569 if FMegaRulez
[MR_INVUL
] < gTime
+PLAYER_INVUL_TIME
then
5571 FMegaRulez
[MR_INVUL
] := gTime
+PLAYER_INVUL_TIME
;
5576 if FMegaRulez
[MR_INVIS
] < gTime
+PLAYER_INVIS_TIME
then
5578 FMegaRulez
[MR_INVIS
] := gTime
+PLAYER_INVIS_TIME
;
5582 if FJetFuel
< JET_MAX
then
5584 FJetFuel
:= JET_MAX
;
5587 ITEM_MEDKIT_SMALL
: if FHealth
< PLAYER_HP_SOFT
then IncMax(FHealth
, 10, PLAYER_HP_SOFT
);
5588 ITEM_MEDKIT_LARGE
: if FHealth
< PLAYER_HP_SOFT
then IncMax(FHealth
, 25, PLAYER_HP_SOFT
);
5590 ITEM_ARMOR_GREEN
: if FArmor
< PLAYER_AP_SOFT
then FArmor
:= PLAYER_AP_SOFT
;
5591 ITEM_ARMOR_BLUE
: if FArmor
< PLAYER_AP_LIMIT
then FArmor
:= PLAYER_AP_LIMIT
;
5593 ITEM_SPHERE_BLUE
: if FHealth
< PLAYER_HP_LIMIT
then IncMax(FHealth
, 100, PLAYER_HP_LIMIT
);
5595 if (FHealth
< PLAYER_HP_LIMIT
) or (FArmor
< PLAYER_AP_LIMIT
) then
5597 if FHealth
< PLAYER_HP_LIMIT
then FHealth
:= PLAYER_HP_LIMIT
;
5598 if FArmor
< PLAYER_AP_LIMIT
then FArmor
:= PLAYER_AP_LIMIT
;
5601 ITEM_WEAPON_SAW
: FWeapon
[WEAPON_SAW
] := True;
5602 ITEM_WEAPON_SHOTGUN1
: FWeapon
[WEAPON_SHOTGUN1
] := True;
5603 ITEM_WEAPON_SHOTGUN2
: FWeapon
[WEAPON_SHOTGUN2
] := True;
5604 ITEM_WEAPON_CHAINGUN
: FWeapon
[WEAPON_CHAINGUN
] := True;
5605 ITEM_WEAPON_ROCKETLAUNCHER
: FWeapon
[WEAPON_ROCKETLAUNCHER
] := True;
5606 ITEM_WEAPON_PLASMA
: FWeapon
[WEAPON_PLASMA
] := True;
5607 ITEM_WEAPON_BFG
: FWeapon
[WEAPON_BFG
] := True;
5608 ITEM_WEAPON_SUPERPULEMET
: FWeapon
[WEAPON_SUPERPULEMET
] := True;
5609 ITEM_WEAPON_FLAMETHROWER
: FWeapon
[WEAPON_FLAMETHROWER
] := True;
5611 ITEM_AMMO_BULLETS
: if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then IncMax(FAmmo
[A_BULLETS
], 10, FMaxAmmo
[A_BULLETS
]);
5612 ITEM_AMMO_BULLETS_BOX
: if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then IncMax(FAmmo
[A_BULLETS
], 50, FMaxAmmo
[A_BULLETS
]);
5613 ITEM_AMMO_SHELLS
: if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
5614 ITEM_AMMO_SHELLS_BOX
: if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then IncMax(FAmmo
[A_SHELLS
], 25, FMaxAmmo
[A_SHELLS
]);
5615 ITEM_AMMO_ROCKET
: if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then IncMax(FAmmo
[A_ROCKETS
], 1, FMaxAmmo
[A_ROCKETS
]);
5616 ITEM_AMMO_ROCKET_BOX
: if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then IncMax(FAmmo
[A_ROCKETS
], 5, FMaxAmmo
[A_ROCKETS
]);
5617 ITEM_AMMO_CELL
: if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
5618 ITEM_AMMO_CELL_BIG
: if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then IncMax(FAmmo
[A_CELLS
], 100, FMaxAmmo
[A_CELLS
]);
5619 ITEM_AMMO_FUELCAN
: if FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
] then IncMax(FAmmo
[A_FUEL
], 100, FMaxAmmo
[A_FUEL
]);
5622 if (FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
]) or
5623 (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or
5624 (FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
]) or
5625 (FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
]) or
5626 (FMaxAmmo
[A_FUEL
] < AmmoLimits
[1, A_FUEL
]) then
5628 FMaxAmmo
[A_BULLETS
] := AmmoLimits
[1, A_BULLETS
];
5629 FMaxAmmo
[A_SHELLS
] := AmmoLimits
[1, A_SHELLS
];
5630 FMaxAmmo
[A_ROCKETS
] := AmmoLimits
[1, A_ROCKETS
];
5631 FMaxAmmo
[A_CELLS
] := AmmoLimits
[1, A_CELLS
];
5632 FMaxAmmo
[A_FUEL
] := AmmoLimits
[1, A_FUEL
];
5634 if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then IncMax(FAmmo
[A_BULLETS
], 10, FMaxAmmo
[A_BULLETS
]);
5635 if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
5636 if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then IncMax(FAmmo
[A_ROCKETS
], 1, FMaxAmmo
[A_ROCKETS
]);
5637 if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
5639 FRulez
:= FRulez
+ [R_ITEM_BACKPACK
];
5642 ITEM_KEY_RED
: if not (R_KEY_RED
in FRulez
) then Include(FRulez
, R_KEY_RED
);
5643 ITEM_KEY_GREEN
: if not (R_KEY_GREEN
in FRulez
) then Include(FRulez
, R_KEY_GREEN
);
5644 ITEM_KEY_BLUE
: if not (R_KEY_BLUE
in FRulez
) then Include(FRulez
, R_KEY_BLUE
);
5646 ITEM_BOTTLE
: if FHealth
< PLAYER_HP_LIMIT
then IncMax(FHealth
, 4, PLAYER_HP_LIMIT
);
5647 ITEM_HELMET
: if FArmor
< PLAYER_AP_LIMIT
then IncMax(FArmor
, 5, PLAYER_AP_LIMIT
);
5652 if g_Game_IsNet
and g_Game_IsServer
then
5653 MH_SEND_PlayerStats(FUID
);
5656 procedure TPlayer
.FlySmoke(Times
: DWORD
= 1);
5659 if (Random(5) = 1) and (Times
= 1) then
5662 if BodyInLiquid(0, 0) then
5664 g_GFX_Bubbles(Obj
.X
+Obj
.Rect
.X
+(Obj
.Rect
.Width
div 2)+Random(3)-1,
5665 Obj
.Y
+Obj
.Rect
.Height
+8, 1, 8, 4);
5666 if Random(2) = 0 then
5667 g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj
.X
, FObj
.Y
)
5669 g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj
.X
, FObj
.Y
);
5673 for i
:= 1 to Times
do
5677 Obj
.X
+Obj
.Rect
.X
+Random(Obj
.Rect
.Width
+Times
*2)-(R_GFX_SMOKE_WIDTH
div 2),
5678 Obj
.Y
+Obj
.Rect
.Height
-4+Random(8+Times
*2)
5683 procedure TPlayer
.OnFireFlame(Times
: DWORD
= 1);
5686 if (Random(10) = 1) and (Times
= 1) then
5689 for i
:= 1 to Times
do
5693 Obj
.X
+Obj
.Rect
.X
+Random(Obj
.Rect
.Width
+Times
*2)-(R_GFX_FLAME_WIDTH
div 2),
5694 Obj
.Y
+8+Random(8+Times
*2)
5699 procedure TPlayer
.PauseSounds(Enable
: Boolean);
5701 FSawSound
.Pause(Enable
);
5702 FSawSoundIdle
.Pause(Enable
);
5703 FSawSoundHit
.Pause(Enable
);
5704 FSawSoundSelect
.Pause(Enable
);
5705 FFlameSoundOn
.Pause(Enable
);
5706 FFlameSoundOff
.Pause(Enable
);
5707 FFlameSoundWork
.Pause(Enable
);
5708 FJetSoundFly
.Pause(Enable
);
5709 FJetSoundOn
.Pause(Enable
);
5710 FJetSoundOff
.Pause(Enable
);
5715 constructor TCorpse
.Create(X
, Y
: Integer; ModelName
: String; aMess
: Boolean);
5720 FObj
.Rect
:= PLAYER_CORPSERECT
;
5722 FModel
:= g_PlayerModel_Get(ModelName
);
5726 FState
:= CORPSE_STATE_MESS
;
5727 FModel
.ChangeAnimation(A_DIE2
);
5731 FState
:= CORPSE_STATE_NORMAL
;
5732 FModel
.ChangeAnimation(A_DIE1
);
5736 destructor TCorpse
.Destroy();
5742 function TCorpse
.ObjPtr (): PObj
; inline; begin result
:= @FObj
; end;
5744 procedure TCorpse
.positionChanged (); inline; begin end;
5746 procedure TCorpse
.moveBy (dx
, dy
: Integer); inline;
5748 if (dx
<> 0) or (dy
<> 0) then
5757 procedure TCorpse
.getMapBox (out x
, y
, w
, h
: Integer); inline;
5759 x
:= FObj
.X
+PLAYER_CORPSERECT
.X
;
5760 y
:= FObj
.Y
+PLAYER_CORPSERECT
.Y
;
5761 w
:= PLAYER_CORPSERECT
.Width
;
5762 h
:= PLAYER_CORPSERECT
.Height
;
5766 procedure TCorpse
.Damage(Value
: Word; SpawnerUID
: Word; vx
, vy
: Integer);
5767 var Blood
: TModelBlood
;
5769 if FState
= CORPSE_STATE_REMOVEME
then
5772 FDamage
:= FDamage
+ Value
;
5774 if FDamage
> 150 then
5776 if FModel
<> nil then
5778 FState
:= CORPSE_STATE_REMOVEME
;
5780 g_Player_CreateGibs(
5781 FObj
.X
+ FObj
.Rect
.X
+ (FObj
.Rect
.Width
div 2),
5782 FObj
.Y
+ FObj
.Rect
.Y
+ (FObj
.Rect
.Height
div 2),
5787 // Звук мяса от трупа:
5788 FModel
.PlaySound(MODELSOUND_DIE
, 5, FObj
.X
, FObj
.Y
);
5791 if (gBodyKillEvent
<> -1) and gDelayedEvents
[gBodyKillEvent
].Pending
then
5792 gDelayedEvents
[gBodyKillEvent
].Pending
:= False;
5793 gBodyKillEvent
:= g_Game_DelayEvent(DE_BODYKILL
, 1050, SpawnerUID
);
5801 Blood
:= FModel
.GetBlood();
5802 FObj
.Vel
.X
:= FObj
.Vel
.X
+ vx
;
5803 FObj
.Vel
.Y
:= FObj
.Vel
.Y
+ vy
;
5804 g_GFX_Blood(FObj
.X
+PLAYER_CORPSERECT
.X
+(PLAYER_CORPSERECT
.Width
div 2),
5805 FObj
.Y
+PLAYER_CORPSERECT
.Y
+(PLAYER_CORPSERECT
.Height
div 2),
5806 Value
, vx
, vy
, 16, (PLAYER_CORPSERECT
.Height
*2) div 3,
5807 Blood
.R
, Blood
.G
, Blood
.B
, Blood
.Kind
);
5811 procedure TCorpse
.Update();
5815 if FState
= CORPSE_STATE_REMOVEME
then
5818 FObj
.oldX
:= FObj
.X
;
5819 FObj
.oldY
:= FObj
.Y
;
5821 if gTime
mod (GAME_TICK
*2) <> 0 then
5823 g_Obj_Move(@FObj
, True, True, True);
5824 positionChanged(); // this updates spatial accelerators
5828 // Сопротивление воздуха для трупа:
5829 FObj
.Vel
.X
:= z_dec(FObj
.Vel
.X
, 1);
5831 st
:= g_Obj_Move(@FObj
, True, True, True);
5832 positionChanged(); // this updates spatial accelerators
5834 if WordBool(st
and MOVE_FALLOUT
) then
5836 FState
:= CORPSE_STATE_REMOVEME
;
5840 if FModel
<> nil then
5845 procedure TCorpse
.SaveState (st
: TStream
);
5851 utils
.writeSign(st
, 'CORP');
5852 utils
.writeInt(st
, Byte(0));
5854 utils
.writeInt(st
, Byte(FState
));
5856 utils
.writeInt(st
, Byte(FDamage
));
5858 utils
.writeInt(st
, Byte(FModel
.Color
.R
));
5859 utils
.writeInt(st
, Byte(FModel
.Color
.G
));
5860 utils
.writeInt(st
, Byte(FModel
.Color
.B
));
5862 Obj_SaveState(st
, @FObj
);
5863 utils
.writeInt(st
, Word(FPlayerUID
));
5865 anim
:= (FModel
<> nil);
5866 utils
.writeBool(st
, anim
);
5867 if anim
then FModel
.AnimState
.SaveState(st
);
5868 // animation for mask (same as animation, compat with older saves)
5869 anim
:= (FModel
<> nil);
5870 utils
.writeBool(st
, anim
);
5871 if anim
then FModel
.AnimState
.SaveState(st
);
5875 procedure TCorpse
.LoadState (st
: TStream
);
5876 var anim
: Boolean; r
, g
, b
: Byte; stub
: TAnimationState
;
5881 if not utils
.checkSign(st
, 'CORP') then raise XStreamError
.Create('invalid corpse signature');
5882 if (utils
.readByte(st
) <> 0) then raise XStreamError
.Create('invalid corpse version');
5884 FState
:= utils
.readByte(st
);
5886 FDamage
:= utils
.readByte(st
);
5888 r
:= utils
.readByte(st
);
5889 g
:= utils
.readByte(st
);
5890 b
:= utils
.readByte(st
);
5891 FModel
.SetColor(r
, g
, b
);
5893 Obj_LoadState(@FObj
, st
);
5894 FPlayerUID
:= utils
.readWord(st
);
5896 stub
:= TAnimationState
.Create(False, 0, 0);
5897 anim
:= utils
.readBool(st
);
5901 FModel
.AnimState
.CurrentFrame
:= Min(stub
.CurrentFrame
, FModel
.AnimState
.Length
);
5908 // animation for mask (same as animation, compat with older saves)
5909 anim
:= utils
.readBool(st
);
5910 if anim
then stub
.LoadState(st
);
5916 constructor TBot
.Create();
5923 FSpectator
:= False;
5930 for a
:= WP_FIRST
to WP_LAST
do
5932 FDifficult
.WeaponPrior
[a
] := WEAPON_PRIOR1
[a
];
5933 FDifficult
.CloseWeaponPrior
[a
] := WEAPON_PRIOR2
[a
];
5934 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
5938 destructor TBot
.Destroy();
5941 inherited Destroy();
5944 procedure TBot
.Respawn(Silent
: Boolean; Force
: Boolean = False);
5946 inherited Respawn(Silent
, Force
);
5949 FSelectedWeapon
:= FCurrWeap
;
5954 procedure TBot
.UpdateCombat();
5967 TTargetRecord
= array of TTarget
;
5969 function Compare(a
, b
: TTarget
): Integer;
5971 if a
.Line
and not b
.Line
then // A на линии огня
5974 if not a
.Line
and b
.Line
then // B на линии огня
5976 else // И A, и B на линии или не на линии огня
5977 if (a
.Line
and b
.Line
) or ((not a
.Line
) and (not b
.Line
)) then
5979 if a
.Dist
> b
.Dist
then // B ближе
5981 else // A ближе или равноудаленно с B
5984 else // Странно -> A
5989 a
, x1
, y1
, x2
, y2
: Integer;
5990 targets
: TTargetRecord
;
5992 Target
, BestTarget
: TTarget
;
5993 firew
, fireh
: Integer;
5997 vsPlayer
, vsMonster
, ok
: Boolean;
6000 function monsUpdate (mon
: TMonster
): Boolean;
6002 result
:= false; // don't stop
6003 if mon
.alive
and (mon
.MonsterType
<> MONSTER_BARREL
) then
6005 if not TargetOnScreen(mon
.Obj
.X
+mon
.Obj
.Rect
.X
, mon
.Obj
.Y
+mon
.Obj
.Rect
.Y
) then exit
;
6007 x2
:= mon
.Obj
.X
+mon
.Obj
.Rect
.X
+(mon
.Obj
.Rect
.Width
div 2);
6008 y2
:= mon
.Obj
.Y
+mon
.Obj
.Rect
.Y
+(mon
.Obj
.Rect
.Height
div 2);
6010 // Если монстр на экране и не прикрыт стеной
6011 if g_TraceVector(x1
, y1
, x2
, y2
) then
6013 // Добавляем к списку возможных целей
6014 SetLength(targets
, Length(targets
)+1);
6015 with targets
[High(targets
)] do
6022 Rect
:= mon
.Obj
.Rect
;
6023 Dist
:= g_PatchLength(x1
, y1
, x2
, y2
);
6024 Line
:= (y1
+4 < Target
.Y
+ mon
.Obj
.Rect
.Y
+ mon
.Obj
.Rect
.Height
) and
6025 (y1
-4 > Target
.Y
+ mon
.Obj
.Rect
.Y
);
6034 vsPlayer
:= LongBool(gGameSettings
.Options
and GAME_OPTION_BOTVSPLAYER
);
6035 vsMonster
:= LongBool(gGameSettings
.Options
and GAME_OPTION_BOTVSMONSTER
);
6037 // Если текущее оружие не то, что нужно, то меняем:
6038 if FCurrWeap
<> FSelectedWeapon
then
6041 // Если нужно стрелять и нужное оружие, то нажать "Стрелять":
6042 if (GetAIFlag('NEEDFIRE') <> '') and (FCurrWeap
= FSelectedWeapon
) then
6044 RemoveAIFlag('NEEDFIRE');
6047 WEAPON_PLASMA
, WEAPON_SUPERPULEMET
, WEAPON_CHAINGUN
: PressKey(KEY_FIRE
, 20);
6048 WEAPON_SAW
, WEAPON_KASTET
, WEAPON_FLAMETHROWER
: PressKey(KEY_FIRE
, 40);
6049 else PressKey(KEY_FIRE
);
6053 // Координаты ствола:
6054 x1
:= FObj
.X
+ WEAPONPOINT
[FDirection
].X
;
6055 y1
:= FObj
.Y
+ WEAPONPOINT
[FDirection
].Y
;
6057 Target
.UID
:= FTargetUID
;
6060 if Target
.UID
<> 0 then
6061 begin // Цель есть - настраиваем
6062 if (g_GetUIDType(Target
.UID
) = UID_PLAYER
) and
6065 tpla
:= g_Player_Get(Target
.UID
);
6069 if (@FObj
) <> nil then
6076 Target
.cX
:= Target
.X
+ PLAYER_RECT_CX
;
6077 Target
.cY
:= Target
.Y
+ PLAYER_RECT_CY
;
6078 Target
.Rect
:= PLAYER_RECT
;
6079 Target
.Visible
:= g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
);
6080 Target
.Line
:= (y1
+4 < Target
.Y
+PLAYER_RECT
.Y
+PLAYER_RECT
.Height
) and
6081 (y1
-4 > Target
.Y
+PLAYER_RECT
.Y
);
6082 Target
.IsPlayer
:= True;
6086 if (g_GetUIDType(Target
.UID
) = UID_MONSTER
) and
6089 mon
:= g_Monsters_ByUID(Target
.UID
);
6092 Target
.X
:= mon
.Obj
.X
;
6093 Target
.Y
:= mon
.Obj
.Y
;
6095 Target
.cX
:= Target
.X
+ mon
.Obj
.Rect
.X
+ (mon
.Obj
.Rect
.Width
div 2);
6096 Target
.cY
:= Target
.Y
+ mon
.Obj
.Rect
.Y
+ (mon
.Obj
.Rect
.Height
div 2);
6097 Target
.Rect
:= mon
.Obj
.Rect
;
6098 Target
.Visible
:= g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
);
6099 Target
.Line
:= (y1
+4 < Target
.Y
+ mon
.Obj
.Rect
.Y
+ mon
.Obj
.Rect
.Height
) and
6100 (y1
-4 > Target
.Y
+ mon
.Obj
.Rect
.Y
);
6101 Target
.IsPlayer
:= False;
6108 begin // Цели нет - обнуляем
6113 Target
.Visible
:= False;
6114 Target
.Line
:= False;
6115 Target
.IsPlayer
:= False;
6120 // Если цель не видима или не на линии огня, то ищем все возможные цели:
6121 if (not Target
.Line
) or (not Target
.Visible
) then
6125 for a
:= 0 to High(gPlayers
) do
6126 if (gPlayers
[a
] <> nil) and (gPlayers
[a
].alive
) and
6127 (gPlayers
[a
].FUID
<> FUID
) and
6128 (not SameTeam(FUID
, gPlayers
[a
].FUID
)) and
6129 (not gPlayers
[a
].NoTarget
) and
6130 (gPlayers
[a
].FMegaRulez
[MR_INVIS
] < gTime
) then
6132 if not TargetOnScreen(gPlayers
[a
].FObj
.X
+ PLAYER_RECT
.X
,
6133 gPlayers
[a
].FObj
.Y
+ PLAYER_RECT
.Y
) then
6136 x2
:= gPlayers
[a
].FObj
.X
+ PLAYER_RECT_CX
;
6137 y2
:= gPlayers
[a
].FObj
.Y
+ PLAYER_RECT_CY
;
6139 // Если игрок на экране и не прикрыт стеной:
6140 if g_TraceVector(x1
, y1
, x2
, y2
) then
6142 // Добавляем к списку возможных целей:
6143 SetLength(targets
, Length(targets
)+1);
6144 with targets
[High(targets
)] do
6146 UID
:= gPlayers
[a
].FUID
;
6147 X
:= gPlayers
[a
].FObj
.X
;
6148 Y
:= gPlayers
[a
].FObj
.Y
;
6151 Rect
:= PLAYER_RECT
;
6152 Dist
:= g_PatchLength(x1
, y1
, x2
, y2
);
6153 Line
:= (y1
+4 < Target
.Y
+PLAYER_RECT
.Y
+PLAYER_RECT
.Height
) and
6154 (y1
-4 > Target
.Y
+PLAYER_RECT
.Y
);
6162 if vsMonster
then g_Mons_ForEach(monsUpdate
);
6165 // Если есть возможные цели:
6166 // (Выбираем лучшую, меняем оружие и бежим к ней/от нее)
6167 if targets
<> nil then
6169 // Выбираем наилучшую цель:
6170 BestTarget
:= targets
[0];
6171 if Length(targets
) > 1 then
6172 for a
:= 1 to High(targets
) do
6173 if Compare(BestTarget
, targets
[a
]) = 1 then
6174 BestTarget
:= targets
[a
];
6176 // Если лучшая цель "виднее" текущей, то текущая := лучшая:
6177 if ((not Target
.Visible
) and BestTarget
.Visible
and (Target
.UID
<> BestTarget
.UID
)) or
6178 ((not Target
.Line
) and BestTarget
.Line
and BestTarget
.Visible
) then
6180 Target
:= BestTarget
;
6182 if (Healthy() = 3) or ((Healthy() = 2)) then
6183 begin // Если здоровы - догоняем
6184 if ((RunDirection() = TDirection
.D_LEFT
) and (Target
.X
> FObj
.X
)) then
6185 SetAIFlag('GORIGHT', '1');
6186 if ((RunDirection() = TDirection
.D_RIGHT
) and (Target
.X
< FObj
.X
)) then
6187 SetAIFlag('GOLEFT', '1');
6190 begin // Если побиты - убегаем
6191 if ((RunDirection() = TDirection
.D_LEFT
) and (Target
.X
< FObj
.X
)) then
6192 SetAIFlag('GORIGHT', '1');
6193 if ((RunDirection() = TDirection
.D_RIGHT
) and (Target
.X
> FObj
.X
)) then
6194 SetAIFlag('GOLEFT', '1');
6197 // Выбираем оружие на основе расстояния и приоритетов:
6198 SelectWeapon(Abs(x1
-Target
.cX
));
6203 // (Догоняем/убегаем, стреляем по направлению к цели)
6204 // (Если цель далеко, то хватит следить за ней)
6205 if Target
.UID
<> 0 then
6207 if not TargetOnScreen(Target
.X
+ Target
.Rect
.X
,
6208 Target
.Y
+ Target
.Rect
.Y
) then
6209 begin // Цель сбежала с "экрана"
6210 if (Healthy() = 3) or ((Healthy() = 2)) then
6211 begin // Если здоровы - догоняем
6212 if ((RunDirection() = TDirection
.D_LEFT
) and (Target
.X
> FObj
.X
)) then
6213 SetAIFlag('GORIGHT', '1');
6214 if ((RunDirection() = TDirection
.D_RIGHT
) and (Target
.X
< FObj
.X
)) then
6215 SetAIFlag('GOLEFT', '1');
6218 begin // Если побиты - забываем о цели и убегаем
6220 if ((RunDirection() = TDirection
.D_LEFT
) and (Target
.X
< FObj
.X
)) then
6221 SetAIFlag('GORIGHT', '1');
6222 if ((RunDirection() = TDirection
.D_RIGHT
) and (Target
.X
> FObj
.X
)) then
6223 SetAIFlag('GOLEFT', '1');
6227 begin // Цель пока на "экране"
6228 // Если цель не загорожена стеной, то отмечаем, когда ее видели:
6229 if g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
) then
6230 FLastVisible
:= gTime
;
6231 // Если разница высот не велика, то догоняем:
6232 if (Abs(FObj
.Y
-Target
.Y
) <= 128) then
6234 if ((RunDirection() = TDirection
.D_LEFT
) and (Target
.X
> FObj
.X
)) then
6235 SetAIFlag('GORIGHT', '1');
6236 if ((RunDirection() = TDirection
.D_RIGHT
) and (Target
.X
< FObj
.X
)) then
6237 SetAIFlag('GOLEFT', '1');
6241 // Выбираем угол вверх:
6242 if FDirection
= TDirection
.D_LEFT
then
6243 angle
:= ANGLE_LEFTUP
6245 angle
:= ANGLE_RIGHTUP
;
6247 firew
:= Trunc(Cos(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
6248 fireh
:= Trunc(Sin(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
6250 // Если при угле вверх можно попасть в приблизительное положение цели:
6251 if g_CollideLine(x1
, y1
, x1
+firew
, y1
+fireh
,
6252 Target
.X
+Target
.Rect
.X
+GetInterval(FDifficult
.DiagPrecision
, 128), //96
6253 Target
.Y
+Target
.Rect
.Y
+GetInterval(FDifficult
.DiagPrecision
, 128),
6254 Target
.Rect
.Width
, Target
.Rect
.Height
) and
6255 g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
) then
6256 begin // то нужно стрелять вверх
6257 SetAIFlag('NEEDFIRE', '1');
6258 SetAIFlag('NEEDSEEUP', '1');
6261 // Выбираем угол вниз:
6262 if FDirection
= TDirection
.D_LEFT
then
6263 angle
:= ANGLE_LEFTDOWN
6265 angle
:= ANGLE_RIGHTDOWN
;
6267 firew
:= Trunc(Cos(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
6268 fireh
:= Trunc(Sin(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
6270 // Если при угле вниз можно попасть в приблизительное положение цели:
6271 if g_CollideLine(x1
, y1
, x1
+firew
, y1
+fireh
,
6272 Target
.X
+Target
.Rect
.X
+GetInterval(FDifficult
.DiagPrecision
, 128),
6273 Target
.Y
+Target
.Rect
.Y
+GetInterval(FDifficult
.DiagPrecision
, 128),
6274 Target
.Rect
.Width
, Target
.Rect
.Height
) and
6275 g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
) then
6276 begin // то нужно стрелять вниз
6277 SetAIFlag('NEEDFIRE', '1');
6278 SetAIFlag('NEEDSEEDOWN', '1');
6281 // Если цель видно и она на такой же высоте:
6282 if Target
.Visible
and
6283 (y1
+4 < Target
.Y
+Target
.Rect
.Y
+Target
.Rect
.Height
) and
6284 (y1
-4 > Target
.Y
+Target
.Rect
.Y
) then
6286 // Если идем в сторону цели, то надо стрелять:
6287 if ((FDirection
= TDirection
.D_LEFT
) and (Target
.X
< FObj
.X
)) or
6288 ((FDirection
= TDirection
.D_RIGHT
) and (Target
.X
> FObj
.X
)) then
6289 begin // то нужно стрелять вперед
6290 SetAIFlag('NEEDFIRE', '1');
6291 SetAIFlag('NEEDSEEDOWN', '');
6292 SetAIFlag('NEEDSEEUP', '');
6294 // Если цель в пределах "экрана" и сложность позволяет прыжки сближения:
6295 if Abs(FObj
.X
-Target
.X
) < Trunc(gPlayerScreenSize
.X
*0.75) then
6296 if GetRnd(FDifficult
.CloseJump
) then
6297 begin // то если повезет - прыгаем (особенно, если близко)
6298 if Abs(FObj
.X
-Target
.X
) < 128 then
6302 if Random(a
) = 0 then
6303 SetAIFlag('NEEDJUMP', '1');
6307 // Если цель все еще есть:
6308 if Target
.UID
<> 0 then
6309 if gTime
-FLastVisible
> 2000 then // Если видели давно
6310 Target
.UID
:= 0 // то забыть цель
6311 else // Если видели недавно
6312 begin // но цель убили
6313 if Target
.IsPlayer
then
6314 begin // Цель - игрок
6315 pla
:= g_Player_Get(Target
.UID
);
6316 if (pla
= nil) or (not pla
.alive
) or pla
.NoTarget
or
6317 (pla
.FMegaRulez
[MR_INVIS
] >= gTime
) then
6318 Target
.UID
:= 0; // то забыть цель
6321 begin // Цель - монстр
6322 mon
:= g_Monsters_ByUID(Target
.UID
);
6323 if (mon
= nil) or (not mon
.alive
) then
6324 Target
.UID
:= 0; // то забыть цель
6327 end; // if Target.UID <> 0
6329 FTargetUID
:= Target
.UID
;
6331 // Если возможных целей нет:
6332 // (Атака чего-нибудь слева или справа)
6333 if targets
= nil then
6334 if GetAIFlag('ATTACKLEFT') <> '' then
6335 begin // Если нужно атаковать налево
6336 RemoveAIFlag('ATTACKLEFT');
6338 SetAIFlag('NEEDJUMP', '1');
6340 if RunDirection() = TDirection
.D_RIGHT
then
6341 begin // Идем не в ту сторону
6342 if (Healthy() > 1) and GetRnd(FDifficult
.InvisFire
) then
6343 begin // Если здоровы, то, возможно, стреляем бежим влево и стреляем
6344 SetAIFlag('NEEDFIRE', '1');
6345 SetAIFlag('GOLEFT', '1');
6349 begin // Идем в нужную сторону
6350 if GetRnd(FDifficult
.InvisFire
) then // Возможно, стреляем вслепую
6351 SetAIFlag('NEEDFIRE', '1');
6352 if Healthy() <= 1 then // Побиты - убегаем
6353 SetAIFlag('GORIGHT', '1');
6357 if GetAIFlag('ATTACKRIGHT') <> '' then
6358 begin // Если нужно атаковать направо
6359 RemoveAIFlag('ATTACKRIGHT');
6361 SetAIFlag('NEEDJUMP', '1');
6363 if RunDirection() = TDirection
.D_LEFT
then
6364 begin // Идем не в ту сторону
6365 if (Healthy() > 1) and GetRnd(FDifficult
.InvisFire
) then
6366 begin // Если здоровы, то, возможно, бежим вправо и стреляем
6367 SetAIFlag('NEEDFIRE', '1');
6368 SetAIFlag('GORIGHT', '1');
6373 if GetRnd(FDifficult
.InvisFire
) then // Возможно, стреляем вслепую
6374 SetAIFlag('NEEDFIRE', '1');
6375 if Healthy() <= 1 then // Побиты - убегаем
6376 SetAIFlag('GOLEFT', '1');
6380 //HACK! (does it belongs there?)
6381 RealizeCurrentWeapon();
6383 // Если есть возможные цели:
6384 // (Стреляем по направлению к целям)
6385 if (targets
<> nil) and (GetAIFlag('NEEDFIRE') <> '') then
6386 for a
:= 0 to High(targets
) do
6388 // Если можем стрелять по диагонали:
6389 if GetRnd(FDifficult
.DiagFire
) then
6391 // Ищем цель сверху и стреляем, если есть:
6392 if FDirection
= TDirection
.D_LEFT
then
6393 angle
:= ANGLE_LEFTUP
6395 angle
:= ANGLE_RIGHTUP
;
6397 firew
:= Trunc(Cos(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
6398 fireh
:= Trunc(Sin(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
6400 if g_CollideLine(x1
, y1
, x1
+firew
, y1
+fireh
,
6401 targets
[a
].X
+targets
[a
].Rect
.X
+GetInterval(FDifficult
.DiagPrecision
, 128),
6402 targets
[a
].Y
+targets
[a
].Rect
.Y
+GetInterval(FDifficult
.DiagPrecision
, 128),
6403 targets
[a
].Rect
.Width
, targets
[a
].Rect
.Height
) and
6404 g_TraceVector(x1
, y1
, targets
[a
].cX
, targets
[a
].cY
) then
6406 SetAIFlag('NEEDFIRE', '1');
6407 SetAIFlag('NEEDSEEUP', '1');
6410 // Ищем цель снизу и стреляем, если есть:
6411 if FDirection
= TDirection
.D_LEFT
then
6412 angle
:= ANGLE_LEFTDOWN
6414 angle
:= ANGLE_RIGHTDOWN
;
6416 firew
:= Trunc(Cos(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
6417 fireh
:= Trunc(Sin(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
6419 if g_CollideLine(x1
, y1
, x1
+firew
, y1
+fireh
,
6420 targets
[a
].X
+targets
[a
].Rect
.X
+GetInterval(FDifficult
.DiagPrecision
, 128),
6421 targets
[a
].Y
+targets
[a
].Rect
.Y
+GetInterval(FDifficult
.DiagPrecision
, 128),
6422 targets
[a
].Rect
.Width
, targets
[a
].Rect
.Height
) and
6423 g_TraceVector(x1
, y1
, targets
[a
].cX
, targets
[a
].cY
) then
6425 SetAIFlag('NEEDFIRE', '1');
6426 SetAIFlag('NEEDSEEDOWN', '1');
6430 // Если цель "перед носом", то стреляем:
6431 if targets
[a
].Line
and targets
[a
].Visible
and
6432 (((FDirection
= TDirection
.D_LEFT
) and (targets
[a
].X
< FObj
.X
)) or
6433 ((FDirection
= TDirection
.D_RIGHT
) and (targets
[a
].X
> FObj
.X
))) then
6435 SetAIFlag('NEEDFIRE', '1');
6440 // Если летит пуля, то, возможно, подпрыгиваем:
6441 if g_Weapon_Danger(FUID
, FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+PLAYER_RECT
.Y
,
6442 PLAYER_RECT
.Width
, PLAYER_RECT
.Height
,
6443 40+GetInterval(FDifficult
.Cover
, 40)) then
6444 SetAIFlag('NEEDJUMP', '1');
6446 // Если кончились паторны, то нужно сменить оружие:
6447 ammo
:= GetAmmoByWeapon(FCurrWeap
);
6448 if ((FCurrWeap
= WEAPON_SHOTGUN2
) and (ammo
< 2)) or
6449 ((FCurrWeap
= WEAPON_BFG
) and (ammo
< 40)) or
6451 SetAIFlag('SELECTWEAPON', '1');
6453 // Если нужно сменить оружие, то выбираем нужное:
6454 if GetAIFlag('SELECTWEAPON') = '1' then
6457 RemoveAIFlag('SELECTWEAPON');
6461 procedure TBot
.Update();
6474 // Проверяем, отключён ли AI ботов
6475 if (g_debug_BotAIOff
= 1) and (Team
= TEAM_RED
) then
6477 if (g_debug_BotAIOff
= 2) and (Team
= TEAM_BLUE
) then
6479 if g_debug_BotAIOff
= 3 then
6489 RealizeCurrentWeapon();
6496 procedure TBot
.ReleaseKey(Key
: Byte);
6505 function TBot
.KeyPressed(Key
: Word): Boolean;
6507 Result
:= FKeys
[Key
].Pressed
;
6510 function TBot
.GetAIFlag(aName
: String20
): String20
;
6516 aName
:= LowerCase(aName
);
6518 if FAIFlags
<> nil then
6519 for a
:= 0 to High(FAIFlags
) do
6520 if LowerCase(FAIFlags
[a
].Name
) = aName
then
6522 Result
:= FAIFlags
[a
].Value
;
6527 procedure TBot
.RemoveAIFlag(aName
: String20
);
6531 if FAIFlags
= nil then Exit
;
6533 aName
:= LowerCase(aName
);
6535 for a
:= 0 to High(FAIFlags
) do
6536 if LowerCase(FAIFlags
[a
].Name
) = aName
then
6538 if a
<> High(FAIFlags
) then
6539 for b
:= a
to High(FAIFlags
)-1 do
6540 FAIFlags
[b
] := FAIFlags
[b
+1];
6542 SetLength(FAIFlags
, Length(FAIFlags
)-1);
6547 procedure TBot
.SetAIFlag(aName
, fValue
: String20
);
6555 aName
:= LowerCase(aName
);
6557 if FAIFlags
<> nil then
6558 for a
:= 0 to High(FAIFlags
) do
6559 if LowerCase(FAIFlags
[a
].Name
) = aName
then
6565 if ok
then FAIFlags
[a
].Value
:= fValue
6568 SetLength(FAIFlags
, Length(FAIFlags
)+1);
6569 with FAIFlags
[High(FAIFlags
)] do
6577 procedure TBot
.UpdateMove
;
6579 procedure GoLeft(Time
: Word = 1);
6581 ReleaseKey(KEY_LEFT
);
6582 ReleaseKey(KEY_RIGHT
);
6583 PressKey(KEY_LEFT
, Time
);
6584 SetDirection(TDirection
.D_LEFT
);
6587 procedure GoRight(Time
: Word = 1);
6589 ReleaseKey(KEY_LEFT
);
6590 ReleaseKey(KEY_RIGHT
);
6591 PressKey(KEY_RIGHT
, Time
);
6592 SetDirection(TDirection
.D_RIGHT
);
6595 function Rnd(a
: Word): Boolean;
6597 Result
:= Random(a
) = 0;
6600 procedure Turn(Time
: Word = 1200);
6602 if RunDirection() = TDirection
.D_LEFT
then GoRight(Time
) else GoLeft(Time
);
6607 ReleaseKey(KEY_LEFT
);
6608 ReleaseKey(KEY_RIGHT
);
6611 function CanRunLeft(): Boolean;
6613 Result
:= not CollideLevel(-1, 0);
6616 function CanRunRight(): Boolean;
6618 Result
:= not CollideLevel(1, 0);
6621 function CanRun(): Boolean;
6623 if RunDirection() = TDirection
.D_LEFT
then Result
:= CanRunLeft() else Result
:= CanRunRight();
6626 procedure Jump(Time
: Word = 30);
6628 PressKey(KEY_JUMP
, Time
);
6631 function NearHole(): Boolean;
6635 { TODO 5 : Лестницы }
6636 sx
:= IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1);
6637 for x
:= 1 to PLAYER_RECT
.Width
do
6638 if (not StayOnStep(x
*sx
, 0)) and
6639 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
)) and
6640 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
*2)) then
6649 function BorderHole(): Boolean;
6653 { TODO 5 : Лестницы }
6654 sx
:= IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1);
6655 for x
:= 1 to PLAYER_RECT
.Width
do
6656 if (not StayOnStep(x
*sx
, 0)) and
6657 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
)) and
6658 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
*2)) then
6660 for xx
:= x
to x
+32 do
6661 if CollideLevel(xx
*sx
, PLAYER_RECT
.Height
) then
6671 function NearDeepHole(): Boolean;
6677 sx
:= IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1);
6680 for x
:= 1 to PLAYER_RECT
.Width
do
6681 if (not StayOnStep(x
*sx
, 0)) and
6682 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
)) and
6683 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
*2)) then
6685 while FObj
.Y
+y
*PLAYER_RECT
.Height
< gMapInfo
.Height
do
6687 if CollideLevel(x
*sx
, PLAYER_RECT
.Height
*y
) then Exit
;
6692 end else Result
:= False;
6695 function OverDeepHole(): Boolean;
6702 while FObj
.Y
+y
*PLAYER_RECT
.Height
< gMapInfo
.Height
do
6704 if CollideLevel(0, PLAYER_RECT
.Height
*y
) then Exit
;
6711 function OnGround(): Boolean;
6713 Result
:= StayOnStep(0, 0) or CollideLevel(0, 1);
6716 function OnLadder(): Boolean;
6718 Result
:= FullInStep(0, 0);
6721 function BelowLadder(): Boolean;
6723 Result
:= (FullInStep(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -PLAYER_RECT
.Height
) and
6724 not CollideLevel(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -PLAYER_RECT
.Height
)) or
6725 (FullInStep(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -BOT_MAXJUMP
) and
6726 not CollideLevel(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -BOT_MAXJUMP
));
6729 function BelowLiftUp(): Boolean;
6731 Result
:= ((FullInLift(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -PLAYER_RECT
.Height
) = -1) and
6732 not CollideLevel(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -PLAYER_RECT
.Height
)) or
6733 ((FullInLift(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -BOT_MAXJUMP
) = -1) and
6734 not CollideLevel(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -BOT_MAXJUMP
));
6737 function OnTopLift(): Boolean;
6739 Result
:= (FullInLift(0, 0) = -1) and (FullInLift(0, -32) = 0);
6742 function CanJumpOver(): Boolean;
6746 sx
:= IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1);
6750 if not CollideLevel(sx
, 0) then Exit
;
6752 for y
:= 1 to BOT_MAXJUMP
do
6753 if CollideLevel(0, -y
) then Exit
else
6754 if not CollideLevel(sx
, -y
) then
6761 function CanJumpUp(Dist
: ShortInt): Boolean;
6768 if CollideLevel(Dist
, 0) then Exit
;
6771 for y
:= 0 to BOT_MAXJUMP
do
6772 if CollideLevel(Dist
, -y
) then
6781 for yy
:= y
+1 to BOT_MAXJUMP
do
6782 if not CollideLevel(Dist
, -yy
) then
6791 for y
:= 0 to BOT_MAXJUMP
do
6792 if CollideLevel(0, -y
) then
6800 if y
< yy
then Exit
;
6805 function IsSafeTrigger(): Boolean;
6810 if gTriggers
= nil then
6812 for a
:= 0 to High(gTriggers
) do
6813 if Collide(gTriggers
[a
].X
,
6816 gTriggers
[a
].Height
) and
6817 (gTriggers
[a
].TriggerType
in [TRIGGER_EXIT
, TRIGGER_CLOSEDOOR
,
6818 TRIGGER_CLOSETRAP
, TRIGGER_TRAP
,
6819 TRIGGER_PRESS
, TRIGGER_ON
, TRIGGER_OFF
,
6820 TRIGGER_ONOFF
, TRIGGER_SPAWNMONSTER
,
6821 TRIGGER_DAMAGE
, TRIGGER_SHOT
]) then
6826 // Возможно, нажимаем кнопку:
6827 if Rnd(16) and IsSafeTrigger() then
6830 // Если под лифтом или ступеньками, то, возможно, прыгаем:
6831 if OnLadder() or ((BelowLadder() or BelowLiftUp()) and Rnd(8)) then
6833 ReleaseKey(KEY_LEFT
);
6834 ReleaseKey(KEY_RIGHT
);
6838 // Идем влево, если надо было:
6839 if GetAIFlag('GOLEFT') <> '' then
6841 RemoveAIFlag('GOLEFT');
6842 if CanRunLeft() then
6846 // Идем вправо, если надо было:
6847 if GetAIFlag('GORIGHT') <> '' then
6849 RemoveAIFlag('GORIGHT');
6850 if CanRunRight() then
6854 // Если вылетели за карту, то пробуем вернуться:
6855 if FObj
.X
< -32 then
6858 if FObj
.X
+32 > gMapInfo
.Width
then
6861 // Прыгаем, если надо было:
6862 if GetAIFlag('NEEDJUMP') <> '' then
6865 RemoveAIFlag('NEEDJUMP');
6868 // Смотрим вверх, если надо было:
6869 if GetAIFlag('NEEDSEEUP') <> '' then
6872 ReleaseKey(KEY_DOWN
);
6873 PressKey(KEY_UP
, 20);
6874 RemoveAIFlag('NEEDSEEUP');
6877 // Смотрим вниз, если надо было:
6878 if GetAIFlag('NEEDSEEDOWN') <> '' then
6881 ReleaseKey(KEY_DOWN
);
6882 PressKey(KEY_DOWN
, 20);
6883 RemoveAIFlag('NEEDSEEDOWN');
6886 // Если нужно было в дыру и мы не на земле, то покорно летим:
6887 if GetAIFlag('GOINHOLE') <> '' then
6888 if not OnGround() then
6890 ReleaseKey(KEY_LEFT
);
6891 ReleaseKey(KEY_RIGHT
);
6892 RemoveAIFlag('GOINHOLE');
6893 SetAIFlag('FALLINHOLE', '1');
6896 // Если падали и достигли земли, то хватит падать:
6897 if GetAIFlag('FALLINHOLE') <> '' then
6899 RemoveAIFlag('FALLINHOLE');
6901 // Если летели прямо и сейчас не на лестнице или на вершине лифта, то отходим в сторону:
6902 if not (KeyPressed(KEY_LEFT
) or KeyPressed(KEY_RIGHT
)) then
6903 if GetAIFlag('FALLINHOLE') = '' then
6904 if (not OnLadder()) or (FObj
.Vel
.Y
>= 0) or (OnTopLift()) then
6910 // Если на земле и можно подпрыгнуть, то, возможно, прыгаем:
6912 CanJumpUp(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*32) and
6916 // Если на земле и возле дыры (глубина > 2 ростов игрока):
6917 if OnGround() and NearHole() then
6918 if NearDeepHole() then // Если это бездна
6920 0..3: Turn(); // Бежим обратно
6921 4: Jump(); // Прыгаем
6922 5: begin // Прыгаем обратно
6927 else // Это не бездна и мы еще не летим туда
6928 if GetAIFlag('GOINHOLE') = '' then
6930 0: Turn(); // Не нужно туда
6931 1: Jump(); // Вдруг повезет - прыгаем
6932 else // Если яма с границей, то при случае можно туда прыгнуть
6933 if BorderHole() then
6934 SetAIFlag('GOINHOLE', '1');
6937 // Если на земле, но некуда идти:
6938 if (not CanRun()) and OnGround() then
6940 // Если мы на лестнице или можно перепрыгнуть, то прыгаем:
6941 if CanJumpOver() or OnLadder() then
6943 else // иначе попытаемся в другую сторону
6944 if Random(2) = 0 then
6946 if IsSafeTrigger() then
6952 // Осталось мало воздуха:
6953 if FAir
< 36 * 2 then
6956 // Выбираемся из кислоты, если нет костюма, обожглись, или мало здоровья:
6957 if (FMegaRulez
[MR_SUIT
] < gTime
) and ((FLastHit
= HIT_ACID
) or (Healthy() <= 1)) then
6958 if BodyInAcid(0, 0) then
6962 function TBot
.FullInStep(XInc
, YInc
: Integer): Boolean;
6964 Result
:= g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
,
6965 PLAYER_RECT
.Width
, PLAYER_RECT
.Height
, PANEL_STEP
, False);
6968 {function TBot.NeedItem(Item: Byte): Byte;
6973 procedure TBot
.SelectWeapon(Dist
: Integer);
6977 function HaveAmmo(weapon
: Byte): Boolean;
6980 WEAPON_PISTOL
: Result
:= FAmmo
[A_BULLETS
] >= 1;
6981 WEAPON_SHOTGUN1
: Result
:= FAmmo
[A_SHELLS
] >= 1;
6982 WEAPON_SHOTGUN2
: Result
:= FAmmo
[A_SHELLS
] >= 2;
6983 WEAPON_CHAINGUN
: Result
:= FAmmo
[A_BULLETS
] >= 10;
6984 WEAPON_ROCKETLAUNCHER
: Result
:= FAmmo
[A_ROCKETS
] >= 1;
6985 WEAPON_PLASMA
: Result
:= FAmmo
[A_CELLS
] >= 10;
6986 WEAPON_BFG
: Result
:= FAmmo
[A_CELLS
] >= 40;
6987 WEAPON_SUPERPULEMET
: Result
:= FAmmo
[A_SHELLS
] >= 1;
6988 WEAPON_FLAMETHROWER
: Result
:= FAmmo
[A_FUEL
] >= 1;
6989 else Result
:= True;
6994 if Dist
= -1 then Dist
:= BOT_LONGDIST
;
6996 if Dist
> BOT_LONGDIST
then
6997 begin // Дальний бой
6999 if FWeapon
[FDifficult
.WeaponPrior
[a
]] and HaveAmmo(FDifficult
.WeaponPrior
[a
]) then
7001 FSelectedWeapon
:= FDifficult
.WeaponPrior
[a
];
7005 else //if Dist > BOT_UNSAFEDIST then
7006 begin // Ближний бой
7008 if FWeapon
[FDifficult
.CloseWeaponPrior
[a
]] and HaveAmmo(FDifficult
.CloseWeaponPrior
[a
]) then
7010 FSelectedWeapon
:= FDifficult
.CloseWeaponPrior
[a
];
7017 if FWeapon[FDifficult.SafeWeaponPrior[a]] and HaveAmmo(FDifficult.SafeWeaponPrior[a]) then
7019 FSelectedWeapon := FDifficult.SafeWeaponPrior[a];
7025 function TBot
.PickItem(ItemType
: Byte; force
: Boolean; var remove
: Boolean): Boolean;
7027 Result
:= inherited PickItem(ItemType
, force
, remove
);
7029 if Result
then SetAIFlag('SELECTWEAPON', '1');
7032 function TBot
.Heal(value
: Word; Soft
: Boolean): Boolean;
7034 Result
:= inherited Heal(value
, Soft
);
7037 function TBot
.Healthy(): Byte;
7039 if FMegaRulez
[MR_INVUL
] >= gTime
then Result
:= 3
7040 else if (FHealth
> 80) or ((FHealth
> 50) and (FArmor
> 20)) then Result
:= 3
7041 else if (FHealth
> 50) then Result
:= 2
7042 else if (FHealth
> 20) then Result
:= 1
7046 function TBot
.TargetOnScreen(TX
, TY
: Integer): Boolean;
7048 Result
:= (Abs(FObj
.X
-TX
) <= Trunc(gPlayerScreenSize
.X
*0.6)) and
7049 (Abs(FObj
.Y
-TY
) <= Trunc(gPlayerScreenSize
.Y
*0.6));
7052 procedure TBot
.OnDamage(Angle
: SmallInt);
7060 if (Angle
= 0) or (Angle
= 180) then
7063 if (g_GetUIDType(FLastSpawnerUID
) = UID_PLAYER
) and
7064 LongBool(gGameSettings
.Options
and GAME_OPTION_BOTVSPLAYER
) then
7066 pla
:= g_Player_Get(FLastSpawnerUID
);
7067 ok
:= not TargetOnScreen(pla
.FObj
.X
+ PLAYER_RECT
.X
,
7068 pla
.FObj
.Y
+ PLAYER_RECT
.Y
);
7071 if (g_GetUIDType(FLastSpawnerUID
) = UID_MONSTER
) and
7072 LongBool(gGameSettings
.Options
and GAME_OPTION_BOTVSMONSTER
) then
7074 mon
:= g_Monsters_ByUID(FLastSpawnerUID
);
7075 ok
:= not TargetOnScreen(mon
.Obj
.X
+ mon
.Obj
.Rect
.X
,
7076 mon
.Obj
.Y
+ mon
.Obj
.Rect
.Y
);
7081 SetAIFlag('ATTACKLEFT', '1')
7083 SetAIFlag('ATTACKRIGHT', '1');
7087 function TBot
.RunDirection(): TDirection
;
7089 if Abs(Vel
.X
) >= 1 then
7091 if Vel
.X
> 0 then Result
:= TDirection
.D_RIGHT
else Result
:= TDirection
.D_LEFT
;
7093 Result
:= FDirection
;
7096 function TBot
.GetRnd(a
: Byte): Boolean;
7098 if a
= 0 then Result
:= False
7099 else if a
= 255 then Result
:= True
7100 else Result
:= Random(256) > 255-a
;
7103 function TBot
.GetInterval(a
: Byte; radius
: SmallInt): SmallInt;
7105 Result
:= Round((255-a
)/255*radius
*(Random(2)-1));
7109 procedure TDifficult
.save (st
: TStream
);
7111 utils
.writeInt(st
, Byte(DiagFire
));
7112 utils
.writeInt(st
, Byte(InvisFire
));
7113 utils
.writeInt(st
, Byte(DiagPrecision
));
7114 utils
.writeInt(st
, Byte(FlyPrecision
));
7115 utils
.writeInt(st
, Byte(Cover
));
7116 utils
.writeInt(st
, Byte(CloseJump
));
7117 st
.WriteBuffer(WeaponPrior
[Low(WeaponPrior
)], sizeof(WeaponPrior
));
7118 st
.WriteBuffer(CloseWeaponPrior
[Low(CloseWeaponPrior
)], sizeof(CloseWeaponPrior
));
7121 procedure TDifficult
.load (st
: TStream
);
7123 DiagFire
:= utils
.readByte(st
);
7124 InvisFire
:= utils
.readByte(st
);
7125 DiagPrecision
:= utils
.readByte(st
);
7126 FlyPrecision
:= utils
.readByte(st
);
7127 Cover
:= utils
.readByte(st
);
7128 CloseJump
:= utils
.readByte(st
);
7129 st
.ReadBuffer(WeaponPrior
[Low(WeaponPrior
)], sizeof(WeaponPrior
));
7130 st
.ReadBuffer(CloseWeaponPrior
[Low(CloseWeaponPrior
)], sizeof(CloseWeaponPrior
));
7134 procedure TBot
.SaveState (st
: TStream
);
7139 inherited SaveState(st
);
7140 utils
.writeSign(st
, 'BOT0');
7142 utils
.writeInt(st
, Byte(FSelectedWeapon
));
7144 utils
.writeInt(st
, Word(FTargetUID
));
7145 // Время потери цели
7146 utils
.writeInt(st
, LongWord(FLastVisible
));
7147 // Количество флагов ИИ
7148 dw
:= Length(FAIFlags
);
7149 utils
.writeInt(st
, LongInt(dw
));
7151 for i
:= 0 to dw
-1 do
7153 utils
.writeStr(st
, FAIFlags
[i
].Name
, 20);
7154 utils
.writeStr(st
, FAIFlags
[i
].Value
, 20);
7156 // Настройки сложности
7157 FDifficult
.save(st
);
7161 procedure TBot
.LoadState (st
: TStream
);
7166 inherited LoadState(st
);
7167 if not utils
.checkSign(st
, 'BOT0') then raise XStreamError
.Create('invalid bot signature');
7169 FSelectedWeapon
:= utils
.readByte(st
);
7171 FTargetUID
:= utils
.readWord(st
);
7172 // Время потери цели
7173 FLastVisible
:= utils
.readLongWord(st
);
7174 // Количество флагов ИИ
7175 dw
:= utils
.readLongInt(st
);
7176 if (dw
< 0) or (dw
> 16384) then raise XStreamError
.Create('invalid number of bot AI flags');
7177 SetLength(FAIFlags
, dw
);
7179 for i
:= 0 to dw
-1 do
7181 FAIFlags
[i
].Name
:= utils
.readStr(st
, 20);
7182 FAIFlags
[i
].Value
:= utils
.readStr(st
, 20);
7184 // Настройки сложности
7185 FDifficult
.load(st
);
7190 conRegVar('cheat_berserk_autoswitch', @gBerserkAutoswitch
, 'autoswitch to fist when berserk pack taken', '', true, true);
7191 conRegVar('player_indicator', @gPlayerIndicator
, 'Draw indicator only for current player, also for teammates, or not at all', 'Draw indicator only for current player, also for teammates, or not at all');
7192 conRegVar('player_indicator_style', @gPlayerIndicatorStyle
, 'Visual appearance of indicator', 'Visual appearance of indicator');