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}
639 e_log
, g_map
, g_items
, g_console
, Math
,
640 g_options
, g_triggers
, g_game
, g_grid
, e_res
,
641 wadreader
, g_monsters
, CONFIG
, g_language
,
645 const PLR_SAVE_VERSION
= 0;
655 diag_precision
: Byte;
659 w_prior1
: Array [WP_FIRST
..WP_LAST
] of Byte;
660 w_prior2
: Array [WP_FIRST
..WP_LAST
] of Byte;
661 w_prior3
: Array [WP_FIRST
..WP_LAST
] of Byte;
665 TIME_RESPAWN1
= 1500;
666 TIME_RESPAWN2
= 2000;
667 TIME_RESPAWN3
= 3000;
668 PLAYER_SUIT_TIME
= 30000;
669 PLAYER_INVUL_TIME
= 30000;
670 PLAYER_INVIS_TIME
= 35000;
671 FRAG_COMBO_TIME
= 3000;
674 PLAYER_HEADRECT
: TRectWH
= (X
:24; Y
:12; Width
:20; Height
:12);
677 BOT_UNSAFEDIST
= 128;
678 DIFFICULT_EASY
: TDifficult
= (DiagFire
: 32; InvisFire
: 32; DiagPrecision
: 32;
679 FlyPrecision
: 32; Cover
: 32; CloseJump
: 32;
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 DIFFICULT_MEDIUM
: TDifficult
= (DiagFire
: 127; InvisFire
: 127; DiagPrecision
: 127;
682 FlyPrecision
: 127; Cover
: 127; CloseJump
: 127;
683 WeaponPrior
:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior
:(0,0,0,0,0,0,0,0,0,0,0));
684 DIFFICULT_HARD
: TDifficult
= (DiagFire
: 255; InvisFire
: 255; DiagPrecision
: 255;
685 FlyPrecision
: 255; Cover
: 255; CloseJump
: 255;
686 WeaponPrior
:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior
:(0,0,0,0,0,0,0,0,0,0,0));
687 WEAPON_PRIOR1
: Array [WP_FIRST
..WP_LAST
] of Byte =
688 (WEAPON_FLAMETHROWER
, WEAPON_SUPERPULEMET
,
689 WEAPON_SHOTGUN2
, WEAPON_SHOTGUN1
,
690 WEAPON_CHAINGUN
, WEAPON_PLASMA
, WEAPON_ROCKETLAUNCHER
,
691 WEAPON_BFG
, WEAPON_PISTOL
, WEAPON_SAW
, WEAPON_KASTET
);
692 WEAPON_PRIOR2
: Array [WP_FIRST
..WP_LAST
] of Byte =
693 (WEAPON_FLAMETHROWER
, WEAPON_SUPERPULEMET
,
694 WEAPON_BFG
, WEAPON_ROCKETLAUNCHER
,
695 WEAPON_SHOTGUN2
, WEAPON_PLASMA
, WEAPON_SHOTGUN1
,
696 WEAPON_CHAINGUN
, WEAPON_PISTOL
, WEAPON_SAW
, WEAPON_KASTET
);
697 //WEAPON_PRIOR3: Array [WP_FIRST..WP_LAST] of Byte =
698 // (WEAPON_FLAMETHROWER, WEAPON_SUPERPULEMET,
699 // WEAPON_BFG, WEAPON_PLASMA, WEAPON_SHOTGUN2,
700 // WEAPON_CHAINGUN, WEAPON_SHOTGUN1, WEAPON_SAW,
701 // WEAPON_ROCKETLAUNCHER, WEAPON_PISTOL, WEAPON_KASTET);
702 WEAPON_RELOAD
: Array [WP_FIRST
..WP_LAST
] of Byte =
703 (5, 2, 6, 18, 36, 2, 12, 2, 14, 2, 2);
705 PLAYER_SIGNATURE
= $52594C50; // 'PLYR'
706 CORPSE_SIGNATURE
= $50524F43; // 'CORP'
708 BOTNAMES_FILENAME
= 'botnames.txt';
709 BOTLIST_FILENAME
= 'botlist.txt';
713 MaxCorpses
: Word = 20;
714 MaxShells
: Word = 300;
715 CurrentGib
: Integer = 0;
716 CurrentShell
: Integer = 0;
717 BotNames
: Array of String;
718 BotList
: Array of TBotProfile
;
719 SavedStates
: Array of TPlayerSavedState
;
722 function Lerp(X
, Y
, Factor
: Integer): Integer;
724 Result
:= X
+ ((Y
- X
) div Factor
);
727 function SameTeam(UID1
, UID2
: Word): Boolean;
731 if (UID1
> UID_MAX_PLAYER
) or (UID1
<= UID_MAX_GAME
) or
732 (UID2
> UID_MAX_PLAYER
) or (UID2
<= UID_MAX_GAME
) then Exit
;
734 if (g_Player_Get(UID1
) = nil) or (g_Player_Get(UID2
) = nil) then Exit
;
736 if ((g_Player_Get(UID1
).Team
= TEAM_NONE
) or
737 (g_Player_Get(UID2
).Team
= TEAM_NONE
)) then Exit
;
739 Result
:= g_Player_Get(UID1
).FTeam
= g_Player_Get(UID2
).FTeam
;
742 procedure g_Gibs_SetMax(Count
: Word);
745 SetLength(gGibs
, Count
);
747 if CurrentGib
>= Count
then
751 function g_Gibs_GetMax(): Word;
756 procedure g_Shells_SetMax(Count
: Word);
759 SetLength(gShells
, Count
);
761 if CurrentShell
>= Count
then
765 function g_Shells_GetMax(): Word;
771 procedure g_Corpses_SetMax(Count
: Word);
774 SetLength(gCorpses
, Count
);
777 function g_Corpses_GetMax(): Word;
779 Result
:= MaxCorpses
;
782 function g_Player_Create(ModelName
: String; Color
: TRGB
; Team
: Byte; Bot
: Boolean): Word;
792 // Есть ли место в gPlayers:
793 if gPlayers
<> nil then
794 for a
:= 0 to High(gPlayers
) do
795 if gPlayers
[a
] = nil then
801 // Нет места - расширяем gPlayers:
804 SetLength(gPlayers
, Length(gPlayers
)+1);
808 // Создаем объект игрока:
810 gPlayers
[a
] := TBot
.Create()
812 gPlayers
[a
] := TPlayer
.Create();
815 gPlayers
[a
].FActualModelName
:= ModelName
;
816 gPlayers
[a
].SetModel(ModelName
);
818 // Нет модели - создание не возможно:
819 if gPlayers
[a
].FModel
= nil then
823 g_FatalError(Format(_lc
[I_GAME_ERROR_MODEL
], [ModelName
]));
827 if not (Team
in [TEAM_RED
, TEAM_BLUE
]) then
828 if Random(2) = 0 then
832 gPlayers
[a
].FPreferredTeam
:= Team
;
834 case gGameSettings
.GameMode
of
835 GM_DM
: gPlayers
[a
].FTeam
:= TEAM_NONE
;
837 GM_CTF
: gPlayers
[a
].FTeam
:= gPlayers
[a
].FPreferredTeam
;
839 GM_COOP
: gPlayers
[a
].FTeam
:= TEAM_COOP
;
842 // Если командная игра - красим модель в цвет команды:
843 gPlayers
[a
].FColor
:= Color
;
844 if gPlayers
[a
].FTeam
in [TEAM_RED
, TEAM_BLUE
] then
845 gPlayers
[a
].FModel
.Color
:= TEAMCOLOR
[gPlayers
[a
].FTeam
]
847 gPlayers
[a
].FModel
.Color
:= Color
;
849 gPlayers
[a
].FUID
:= g_CreateUID(UID_PLAYER
);
850 gPlayers
[a
].FAlive
:= False;
852 Result
:= gPlayers
[a
].FUID
;
855 function g_Player_CreateFromState (st
: TStream
): Word;
862 if (st
= nil) then exit
; //???
865 if not utils
.checkSign(st
, 'PLYR') then raise XStreamError
.Create('invalid player signature');
866 if (utils
.readByte(st
) <> PLR_SAVE_VERSION
) then raise XStreamError
.Create('invalid player version');
869 Bot
:= utils
.readBool(st
);
874 // Есть ли место в gPlayers:
875 for a
:= 0 to High(gPlayers
) do if (gPlayers
[a
] = nil) then begin ok
:= true; break
; end;
877 // Нет места - расширяем gPlayers
880 SetLength(gPlayers
, Length(gPlayers
)+1);
884 // Создаем объект игрока
886 gPlayers
[a
] := TBot
.Create()
888 gPlayers
[a
] := TPlayer
.Create();
889 gPlayers
[a
].FIamBot
:= Bot
;
890 gPlayers
[a
].FPhysics
:= True;
893 gPlayers
[a
].FUID
:= utils
.readWord(st
);
895 gPlayers
[a
].FName
:= utils
.readStr(st
);
897 gPlayers
[a
].FTeam
:= utils
.readByte(st
);
898 gPlayers
[a
].FPreferredTeam
:= gPlayers
[a
].FTeam
;
900 gPlayers
[a
].FAlive
:= utils
.readBool(st
);
901 // Израсходовал ли все жизни
902 gPlayers
[a
].FNoRespawn
:= utils
.readBool(st
);
904 b
:= utils
.readByte(st
);
905 if b
= 1 then gPlayers
[a
].FDirection
:= TDirection
.D_LEFT
else gPlayers
[a
].FDirection
:= TDirection
.D_RIGHT
; // b = 2
907 gPlayers
[a
].FHealth
:= utils
.readLongInt(st
);
909 gPlayers
[a
].FHandicap
:= utils
.readLongInt(st
);
911 gPlayers
[a
].FLives
:= utils
.readByte(st
);
913 gPlayers
[a
].FArmor
:= utils
.readLongInt(st
);
915 gPlayers
[a
].FAir
:= utils
.readLongInt(st
);
917 gPlayers
[a
].FJetFuel
:= utils
.readLongInt(st
);
919 gPlayers
[a
].FPain
:= utils
.readLongInt(st
);
921 gPlayers
[a
].FKills
:= utils
.readLongInt(st
);
923 gPlayers
[a
].FMonsterKills
:= utils
.readLongInt(st
);
925 gPlayers
[a
].FFrags
:= utils
.readLongInt(st
);
927 gPlayers
[a
].FFragCombo
:= utils
.readByte(st
);
928 // Время последнего фрага
929 gPlayers
[a
].FLastFrag
:= utils
.readLongWord(st
);
931 gPlayers
[a
].FDeath
:= utils
.readLongInt(st
);
933 gPlayers
[a
].FFlag
:= utils
.readByte(st
);
935 gPlayers
[a
].FSecrets
:= utils
.readLongInt(st
);
937 gPlayers
[a
].FCurrWeap
:= utils
.readByte(st
);
938 // Следующее желаемое оружие
939 gPlayers
[a
].FNextWeap
:= utils
.readWord(st
);
941 gPlayers
[a
].FNextWeapDelay
:= utils
.readByte(st
);
943 gPlayers
[a
].FBFGFireCounter
:= utils
.readSmallInt(st
);
945 gPlayers
[a
].FDamageBuffer
:= utils
.readLongInt(st
);
946 // Последний ударивший
947 gPlayers
[a
].FLastSpawnerUID
:= utils
.readWord(st
);
948 // Тип последнего полученного урона
949 gPlayers
[a
].FLastHit
:= utils
.readByte(st
);
951 Obj_LoadState(@gPlayers
[a
].FObj
, st
);
952 // Текущее количество патронов
953 for i
:= A_BULLETS
to A_HIGH
do gPlayers
[a
].FAmmo
[i
] := utils
.readWord(st
);
954 // Максимальное количество патронов
955 for i
:= A_BULLETS
to A_HIGH
do gPlayers
[a
].FMaxAmmo
[i
] := utils
.readWord(st
);
957 for i
:= WP_FIRST
to WP_LAST
do gPlayers
[a
].FWeapon
[i
] := utils
.readBool(st
);
958 // Время перезарядки оружия
959 for i
:= WP_FIRST
to WP_LAST
do gPlayers
[a
].FReloading
[i
] := utils
.readWord(st
);
961 if utils
.readBool(st
) then Include(gPlayers
[a
].FRulez
, R_ITEM_BACKPACK
);
962 // Наличие красного ключа
963 if utils
.readBool(st
) then Include(gPlayers
[a
].FRulez
, R_KEY_RED
);
964 // Наличие зеленого ключа
965 if utils
.readBool(st
) then Include(gPlayers
[a
].FRulez
, R_KEY_GREEN
);
966 // Наличие синего ключа
967 if utils
.readBool(st
) then Include(gPlayers
[a
].FRulez
, R_KEY_BLUE
);
969 if utils
.readBool(st
) then Include(gPlayers
[a
].FRulez
, R_BERSERK
);
970 // Время действия специальных предметов
971 for i
:= MR_SUIT
to MR_MAX
do gPlayers
[a
].FMegaRulez
[i
] := utils
.readLongWord(st
);
972 // Время до повторного респауна, смены оружия, исользования, захвата флага
973 for i
:= T_RESPAWN
to T_FLAGCAP
do gPlayers
[a
].FTime
[i
] := utils
.readLongWord(st
);
976 gPlayers
[a
].FActualModelName
:= utils
.readStr(st
);
978 gPlayers
[a
].FColor
.R
:= utils
.readByte(st
);
979 gPlayers
[a
].FColor
.G
:= utils
.readByte(st
);
980 gPlayers
[a
].FColor
.B
:= utils
.readByte(st
);
981 // Обновляем модель игрока
982 gPlayers
[a
].SetModel(gPlayers
[a
].FActualModelName
);
984 // Нет модели - создание невозможно
985 if (gPlayers
[a
].FModel
= nil) then
989 g_FatalError(Format(_lc
[I_GAME_ERROR_MODEL
], [gPlayers
[a
].FActualModelName
]));
993 // Если командная игра - красим модель в цвет команды
994 if gGameSettings
.GameMode
in [GM_TDM
, GM_CTF
] then
995 gPlayers
[a
].FModel
.Color
:= TEAMCOLOR
[gPlayers
[a
].FTeam
]
997 gPlayers
[a
].FModel
.Color
:= gPlayers
[a
].FColor
;
999 result
:= gPlayers
[a
].FUID
;
1003 procedure g_Player_ResetTeams();
1007 if g_Game_IsClient
then
1009 if gPlayers
= nil then
1011 for a
:= Low(gPlayers
) to High(gPlayers
) do
1012 if gPlayers
[a
] <> nil then
1013 case gGameSettings
.GameMode
of
1015 gPlayers
[a
].ChangeTeam(TEAM_NONE
);
1017 if not (gPlayers
[a
].Team
in [TEAM_RED
, TEAM_BLUE
]) then
1018 if gPlayers
[a
].FPreferredTeam
in [TEAM_RED
, TEAM_BLUE
] then
1019 gPlayers
[a
].ChangeTeam(gPlayers
[a
].FPreferredTeam
)
1022 gPlayers
[a
].ChangeTeam(TEAM_RED
)
1024 gPlayers
[a
].ChangeTeam(TEAM_BLUE
);
1027 gPlayers
[a
].ChangeTeam(TEAM_COOP
);
1031 procedure g_Bot_Add(Team
, Difficult
: Byte; Handicap
: Integer = 100);
1034 _name
, _model
: String;
1037 if not g_Game_IsServer
then Exit
;
1039 // Список названий моделей:
1040 m
:= g_PlayerModel_GetNames();
1045 if (gGameSettings
.GameType
= GT_SINGLE
) or (gGameSettings
.GameMode
= GM_COOP
) then
1046 Team
:= TEAM_COOP
// COOP
1048 if gGameSettings
.GameMode
= GM_DM
then
1049 Team
:= TEAM_NONE
// DM
1051 if Team
= TEAM_NONE
then // CTF / TDM
1053 // Автобаланс команд:
1057 for a
:= 0 to High(gPlayers
) do
1058 if gPlayers
[a
] <> nil then
1060 if gPlayers
[a
].Team
= TEAM_RED
then
1063 if gPlayers
[a
].Team
= TEAM_BLUE
then
1073 if Random(2) = 0 then
1079 // Выбираем боту имя:
1081 if BotNames
<> nil then
1082 for a
:= 0 to High(BotNames
) do
1083 if g_Player_ValidName(BotNames
[a
]) then
1085 _name
:= BotNames
[a
];
1089 // Выбираем случайную модель:
1090 _model
:= m
[Random(Length(m
))];
1093 with g_Player_Get(g_Player_Create(_model
,
1094 _RGB(Min(Random(9)*32, 255),
1095 Min(Random(9)*32, 255),
1096 Min(Random(9)*32, 255)),
1097 Team
, True)) as TBot
do
1099 // Если имени нет, делаем его из UID бота
1101 Name
:= Format('DFBOT%.5d', [UID
])
1106 1: FDifficult
:= DIFFICULT_EASY
;
1107 2: FDifficult
:= DIFFICULT_MEDIUM
;
1108 else FDifficult
:= DIFFICULT_HARD
;
1111 for a
:= WP_FIRST
to WP_LAST
do
1113 FDifficult
.WeaponPrior
[a
] := WEAPON_PRIOR1
[a
];
1114 FDifficult
.CloseWeaponPrior
[a
] := WEAPON_PRIOR2
[a
];
1115 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
1118 FHandicap
:= Handicap
;
1120 g_Console_Add(Format(_lc
[I_PLAYER_JOIN
], [Name
]), True);
1122 if g_Game_IsNet
then MH_SEND_PlayerCreate(UID
);
1123 if g_Game_IsServer
and (gGameSettings
.MaxLives
> 0) then
1128 procedure g_Bot_AddList(Team
: Byte; lName
: ShortString; num
: Integer = -1; Handicap
: Integer = 100);
1131 _name
, _model
: String;
1134 if not g_Game_IsServer
then Exit
;
1136 // Список названий моделей:
1137 m
:= g_PlayerModel_GetNames();
1142 if (gGameSettings
.GameType
= GT_SINGLE
) or (gGameSettings
.GameMode
= GM_COOP
) then
1143 Team
:= TEAM_COOP
// COOP
1145 if gGameSettings
.GameMode
= GM_DM
then
1146 Team
:= TEAM_NONE
// DM
1148 if Team
= TEAM_NONE
then
1149 Team
:= BotList
[num
].team
; // CTF / TDM
1151 // Выбираем настройки бота из списка по номеру или имени:
1152 lName
:= AnsiLowerCase(lName
);
1153 if (num
< 0) or (num
> Length(BotList
)-1) then
1155 if (num
= -1) and (lName
<> '') and (BotList
<> nil) then
1156 for a
:= 0 to High(BotList
) do
1157 if AnsiLowerCase(BotList
[a
].name
) = lName
then
1166 _name
:= BotList
[num
].name
;
1167 // Занято - выбираем случайное:
1168 if not g_Player_ValidName(_name
) then
1170 _name
:= Format('DFBOT%.2d', [Random(100)]);
1171 until g_Player_ValidName(_name
);
1174 _model
:= BotList
[num
].model
;
1175 // Нет такой - выбираем случайную:
1176 if not InSArray(_model
, m
) then
1177 _model
:= m
[Random(Length(m
))];
1180 with g_Player_Get(g_Player_Create(_model
, BotList
[num
].color
, Team
, True)) as TBot
do
1184 FDifficult
.DiagFire
:= BotList
[num
].diag_fire
;
1185 FDifficult
.InvisFire
:= BotList
[num
].invis_fire
;
1186 FDifficult
.DiagPrecision
:= BotList
[num
].diag_precision
;
1187 FDifficult
.FlyPrecision
:= BotList
[num
].fly_precision
;
1188 FDifficult
.Cover
:= BotList
[num
].cover
;
1189 FDifficult
.CloseJump
:= BotList
[num
].close_jump
;
1191 FHandicap
:= Handicap
;
1193 for a
:= WP_FIRST
to WP_LAST
do
1195 FDifficult
.WeaponPrior
[a
] := BotList
[num
].w_prior1
[a
];
1196 FDifficult
.CloseWeaponPrior
[a
] := BotList
[num
].w_prior2
[a
];
1197 //FDifficult.SafeWeaponPrior[a] := BotList[num].w_prior3[a];
1200 g_Console_Add(Format(_lc
[I_PLAYER_JOIN
], [Name
]), True);
1202 if g_Game_IsNet
then MH_SEND_PlayerCreate(UID
);
1206 procedure g_Bot_RemoveAll();
1210 if not g_Game_IsServer
then Exit
;
1211 if gPlayers
= nil then Exit
;
1213 for a
:= 0 to High(gPlayers
) do
1214 if gPlayers
[a
] <> nil then
1215 if gPlayers
[a
] is TBot
then
1217 gPlayers
[a
].Lives
:= 0;
1218 gPlayers
[a
].Kill(K_SIMPLEKILL
, 0, HIT_DISCON
);
1219 g_Console_Add(Format(_lc
[I_PLAYER_LEAVE
], [gPlayers
[a
].Name
]), True);
1220 g_Player_Remove(gPlayers
[a
].FUID
);
1226 procedure g_Bot_MixNames();
1231 if BotNames
<> nil then
1232 for a
:= 0 to High(BotNames
) do
1234 b
:= Random(Length(BotNames
));
1236 Botnames
[a
] := BotNames
[b
];
1241 procedure g_Player_Remove(UID
: Word);
1245 if gPlayers
= nil then Exit
;
1247 if g_Game_IsServer
and g_Game_IsNet
then
1248 MH_SEND_PlayerDelete(UID
);
1250 for i
:= 0 to High(gPlayers
) do
1251 if gPlayers
[i
] <> nil then
1252 if gPlayers
[i
].FUID
= UID
then
1254 if gPlayers
[i
] is TPlayer
then
1255 TPlayer(gPlayers
[i
]).Free()
1257 TBot(gPlayers
[i
]).Free();
1263 procedure g_Player_Init();
1274 path
:= BOTNAMES_FILENAME
;
1275 if e_FindResource(DataDirs
, path
) = false then
1278 // Читаем возможные имена ботов из файла:
1279 AssignFile(F
, path
);
1290 SetLength(BotNames
, Length(BotNames
)+1);
1291 BotNames
[High(BotNames
)] := s
;
1299 // Читаем файл с параметрами ботов:
1300 config
:= TConfig
.CreateFile(path
);
1304 while config
.SectionExists(IntToStr(a
)) do
1306 SetLength(BotList
, Length(BotList
)+1);
1308 with BotList
[High(BotList
)] do
1311 name
:= config
.ReadStr(IntToStr(a
), 'name', '');
1313 model
:= config
.ReadStr(IntToStr(a
), 'model', '');
1315 if config
.ReadStr(IntToStr(a
), 'team', 'red') = 'red' then
1320 sa
:= parse(config
.ReadStr(IntToStr(a
), 'color', ''));
1321 color
.R
:= StrToIntDef(sa
[0], 0);
1322 color
.G
:= StrToIntDef(sa
[1], 0);
1323 color
.B
:= StrToIntDef(sa
[2], 0);
1324 // Вероятность стрельбы под углом:
1325 diag_fire
:= config
.ReadInt(IntToStr(a
), 'diag_fire', 0);
1326 // Вероятность ответного огня по невидимому сопернику:
1327 invis_fire
:= config
.ReadInt(IntToStr(a
), 'invis_fire', 0);
1328 // Точность стрельбы под углом:
1329 diag_precision
:= config
.ReadInt(IntToStr(a
), 'diag_precision', 0);
1330 // Точность стрельбы в полете:
1331 fly_precision
:= config
.ReadInt(IntToStr(a
), 'fly_precision', 0);
1332 // Точность уклонения от снарядов:
1333 cover
:= config
.ReadInt(IntToStr(a
), 'cover', 0);
1334 // Вероятность прыжка при приближении соперника:
1335 close_jump
:= config
.ReadInt(IntToStr(a
), 'close_jump', 0);
1336 // Приоритеты оружия для дальнего боя:
1337 sa
:= parse(config
.ReadStr(IntToStr(a
), 'w_prior1', ''));
1338 if Length(sa
) = 10 then
1340 w_prior1
[b
] := EnsureRange(StrToInt(sa
[b
]), 0, 9);
1341 // Приоритеты оружия для ближнего боя:
1342 sa
:= parse(config
.ReadStr(IntToStr(a
), 'w_prior2', ''));
1343 if Length(sa
) = 10 then
1345 w_prior2
[b
] := EnsureRange(StrToInt(sa
[b
]), 0, 9);
1347 {sa := parse(config.ReadStr(IntToStr(a), 'w_prior3', ''));
1348 if Length(sa) = 10 then
1350 w_prior3[b] := EnsureRange(StrToInt(sa[b]), 0, 9);}
1357 SetLength(SavedStates
, 0);
1360 procedure g_Player_Free();
1364 if gPlayers
<> nil then
1366 for i
:= 0 to High(gPlayers
) do
1367 if gPlayers
[i
] <> nil then
1369 if gPlayers
[i
] is TPlayer
then
1370 TPlayer(gPlayers
[i
]).Free()
1372 TBot(gPlayers
[i
]).Free();
1381 SetLength(SavedStates
, 0);
1384 procedure g_Player_PreUpdate();
1388 if gPlayers
= nil then Exit
;
1389 for i
:= 0 to High(gPlayers
) do
1390 if gPlayers
[i
] <> nil then
1391 gPlayers
[i
].PreUpdate();
1394 procedure g_Player_UpdateAll();
1398 if gPlayers
= nil then Exit
;
1400 //e_WriteLog('***g_Player_UpdateAll: ENTER', MSG_WARNING);
1401 for i
:= 0 to High(gPlayers
) do
1403 if gPlayers
[i
] <> nil then
1405 if gPlayers
[i
] is TPlayer
then
1407 gPlayers
[i
].Update();
1408 gPlayers
[i
].RealizeCurrentWeapon(); // WARNING! DO NOT MOVE THIS INTO `Update()`!
1412 // bot updates weapons in `UpdateCombat()`
1413 TBot(gPlayers
[i
]).Update();
1417 //e_WriteLog('***g_Player_UpdateAll: EXIT', MSG_WARNING);
1420 function g_Player_Get(UID
: Word): TPlayer
;
1426 if gPlayers
= nil then
1429 for a
:= 0 to High(gPlayers
) do
1430 if gPlayers
[a
] <> nil then
1431 if gPlayers
[a
].FUID
= UID
then
1433 Result
:= gPlayers
[a
];
1438 function g_Player_GetCount(): Byte;
1444 if gPlayers
= nil then
1447 for a
:= 0 to High(gPlayers
) do
1448 if gPlayers
[a
] <> nil then
1449 Result
:= Result
+ 1;
1452 function g_Player_GetStats(): TPlayerStatArray
;
1458 if gPlayers
= nil then Exit
;
1460 for a
:= 0 to High(gPlayers
) do
1461 if gPlayers
[a
] <> nil then
1463 SetLength(Result
, Length(Result
)+1);
1464 with Result
[High(Result
)] do
1467 Ping
:= gPlayers
[a
].FPing
;
1468 Loss
:= gPlayers
[a
].FLoss
;
1469 Name
:= gPlayers
[a
].FName
;
1470 Team
:= gPlayers
[a
].FTeam
;
1471 Frags
:= gPlayers
[a
].FFrags
;
1472 Deaths
:= gPlayers
[a
].FDeath
;
1473 Kills
:= gPlayers
[a
].FKills
;
1474 Color
:= gPlayers
[a
].FModel
.Color
;
1475 Lives
:= gPlayers
[a
].FLives
;
1476 Spectator
:= gPlayers
[a
].FSpectator
;
1477 UID
:= gPlayers
[a
].FUID
;
1482 procedure g_Player_ResetReady();
1486 if not g_Game_IsServer
then Exit
;
1487 if gPlayers
= nil then Exit
;
1489 for a
:= 0 to High(gPlayers
) do
1490 if gPlayers
[a
] <> nil then
1492 gPlayers
[a
].FReady
:= False;
1493 if g_Game_IsNet
then
1494 MH_SEND_GameEvent(NET_EV_INTER_READY
, gPlayers
[a
].UID
, 'N');
1498 procedure g_Player_RememberAll
;
1502 for i
:= Low(gPlayers
) to High(gPlayers
) do
1503 if (gPlayers
[i
] <> nil) and gPlayers
[i
].alive
then
1504 gPlayers
[i
].RememberState
;
1507 procedure g_Player_ResetAll(Force
, Silent
: Boolean);
1511 gTeamStat
[TEAM_RED
].Goals
:= 0;
1512 gTeamStat
[TEAM_BLUE
].Goals
:= 0;
1514 if gPlayers
<> nil then
1515 for i
:= 0 to High(gPlayers
) do
1516 if gPlayers
[i
] <> nil then
1518 gPlayers
[i
].Reset(Force
);
1520 if gPlayers
[i
] is TPlayer
then
1522 if (not gPlayers
[i
].FSpectator
) or gPlayers
[i
].FWantsInGame
then
1523 gPlayers
[i
].Respawn(Silent
)
1525 gPlayers
[i
].Spectate();
1528 TBot(gPlayers
[i
]).Respawn(Silent
);
1532 function g_Player_CreateCorpse(Player
: TPlayer
): Integer;
1540 if Player
.alive
then
1543 // Разрываем связь с прежним трупом:
1544 i
:= Player
.FCorpse
;
1545 if (i
>= 0) and (i
< Length(gCorpses
)) then
1547 if (gCorpses
[i
] <> nil) and (gCorpses
[i
].FPlayerUID
= Player
.FUID
) then
1548 gCorpses
[i
].FPlayerUID
:= 0;
1551 if Player
.FObj
.Y
>= gMapInfo
.Height
+128 then
1556 if (FHealth
>= -50) or (gGibsCount
= 0) then
1558 if (gCorpses
= nil) or (Length(gCorpses
) = 0) then
1562 for find_id
:= 0 to High(gCorpses
) do
1563 if gCorpses
[find_id
] = nil then
1570 find_id
:= Random(Length(gCorpses
));
1572 gCorpses
[find_id
] := TCorpse
.Create(FObj
.X
, FObj
.Y
, FModel
.GetName(), FHealth
< -20);
1573 gCorpses
[find_id
].FModel
.Color
:= FModel
.Color
;
1574 gCorpses
[find_id
].FObj
.Vel
:= FObj
.Vel
;
1575 gCorpses
[find_id
].FObj
.Accel
:= FObj
.Accel
;
1576 gCorpses
[find_id
].FPlayerUID
:= FUID
;
1581 g_Player_CreateGibs(FObj
.X
+ PLAYER_RECT_CX
, FObj
.Y
+ PLAYER_RECT_CY
, FModel
.id
, FModel
.Color
);
1585 procedure g_Player_CreateShell(fX
, fY
, dX
, dY
: Integer; T
: Byte);
1587 if (gShells
= nil) or (Length(gShells
) = 0) then
1590 with gShells
[CurrentShell
] do
1595 if T
= SHELL_BULLET
then
1597 Obj
.Rect
.Width
:= 4;
1598 Obj
.Rect
.Height
:= 2;
1602 Obj
.Rect
.Width
:= 7;
1603 Obj
.Rect
.Height
:= 3;
1609 g_Obj_Push(@Obj
, dX
+ Random(4)-Random(4), dY
-Random(4));
1610 positionChanged(); // this updates spatial accelerators
1611 RAngle
:= Random(360);
1612 Timeout
:= gTime
+ SHELL_TIMEOUT
;
1614 if CurrentShell
>= High(gShells
) then
1621 procedure g_Player_CreateGibs (fX
, fY
, mid
: Integer; fColor
: TRGB
);
1624 GibsArray
: TGibsArray
;
1631 if (gGibs
= nil) or (Length(gGibs
) = 0) then
1633 if not g_PlayerModel_GetGibs(mid
, GibsArray
) then
1637 Blood
:= PlayerModelsArray
[mid
].Blood
;
1640 for a
:= 0 to High(GibsArray
) do
1641 with gGibs
[CurrentGib
] do
1644 GibID
:= GibsArray
[a
];
1649 Obj
.Rect
:= r_Render_GetGibRect(ModelID
, GibID
);
1653 Obj
.Rect
.Width
:= 16;
1654 Obj
.Rect
.Height
:= 16;
1656 Obj
.X
:= fX
- Obj
.Rect
.X
- (Obj
.Rect
.Width
div 2);
1657 Obj
.Y
:= fY
- Obj
.Rect
.Y
- (Obj
.Rect
.Height
div 2);
1658 g_Obj_PushA(@Obj
, 25 + Random(10), Random(361));
1659 positionChanged(); // this updates spatial accelerators
1660 RAngle
:= Random(360);
1663 if gBloodCount
> 0 then
1668 16 * gBloodCount
+ Random(5 * gBloodCount
),
1681 if CurrentGib
>= High(gGibs
) then
1688 procedure g_Player_UpdatePhysicalObjects();
1694 procedure ShellSound_Bounce(X
, Y
: Integer; T
: Byte);
1699 if T
= SHELL_BULLET
then
1700 g_Sound_PlayExAt('SOUND_PLAYER_CASING' + IntToStr(k
), X
, Y
)
1702 g_Sound_PlayExAt('SOUND_PLAYER_SHELL' + IntToStr(k
), X
, Y
);
1707 if gGibs
<> nil then
1708 for i
:= 0 to High(gGibs
) do
1709 if gGibs
[i
].alive
then
1716 mr
:= g_Obj_Move(@Obj
, True, False, True);
1717 positionChanged(); // this updates spatial accelerators
1719 if WordBool(mr
and MOVE_FALLOUT
) then
1725 // Отлетает от удара о стену/потолок/пол:
1726 if WordBool(mr
and MOVE_HITWALL
) then
1727 Obj
.Vel
.X
:= -(vel
.X
div 2);
1728 if WordBool(mr
and (MOVE_HITCEIL
or MOVE_HITLAND
)) then
1729 Obj
.Vel
.Y
:= -(vel
.Y
div 2);
1731 if (Obj
.Vel
.X
>= 0) then
1733 RAngle
:= RAngle
+ Abs(Obj
.Vel
.X
)*6 + Abs(Obj
.Vel
.Y
);
1734 if RAngle
>= 360 then
1735 RAngle
:= RAngle
mod 360;
1736 end else begin // Counter-clockwise
1737 RAngle
:= RAngle
- Abs(Obj
.Vel
.X
)*6 - Abs(Obj
.Vel
.Y
);
1739 RAngle
:= (360 - (Abs(RAngle
) mod 360)) mod 360;
1742 // Сопротивление воздуха для куска трупа:
1743 if gTime
mod (GAME_TICK
*3) = 0 then
1744 Obj
.Vel
.X
:= z_dec(Obj
.Vel
.X
, 1);
1748 if gCorpses
<> nil then
1749 for i
:= 0 to High(gCorpses
) do
1750 if gCorpses
[i
] <> nil then
1751 if gCorpses
[i
].State
= CORPSE_STATE_REMOVEME
then
1757 gCorpses
[i
].Update();
1760 if gShells
<> nil then
1761 for i
:= 0 to High(gShells
) do
1762 if gShells
[i
].alive
then
1769 mr
:= g_Obj_Move(@Obj
, True, False, True);
1770 positionChanged(); // this updates spatial accelerators
1772 if WordBool(mr
and MOVE_FALLOUT
) or (gShells
[i
].Timeout
< gTime
) then
1778 // Отлетает от удара о стену/потолок/пол:
1779 if WordBool(mr
and MOVE_HITWALL
) then
1781 Obj
.Vel
.X
:= -(vel
.X
div 2);
1782 if not WordBool(mr
and MOVE_INWATER
) then
1783 ShellSound_Bounce(Obj
.X
, Obj
.Y
, SType
);
1785 if WordBool(mr
and (MOVE_HITCEIL
or MOVE_HITLAND
)) then
1787 Obj
.Vel
.Y
:= -(vel
.Y
div 2);
1788 if Obj
.Vel
.X
<> 0 then Obj
.Vel
.X
:= Obj
.Vel
.X
div 2;
1789 if (Obj
.Vel
.X
= 0) and (Obj
.Vel
.Y
= 0) then
1791 if RAngle
mod 90 <> 0 then
1792 RAngle
:= (RAngle
div 90) * 90;
1794 else if not WordBool(mr
and MOVE_INWATER
) then
1795 ShellSound_Bounce(Obj
.X
, Obj
.Y
, SType
);
1798 if (Obj
.Vel
.X
>= 0) then
1800 RAngle
:= RAngle
+ Abs(Obj
.Vel
.X
)*8 + Abs(Obj
.Vel
.Y
);
1801 if RAngle
>= 360 then
1802 RAngle
:= RAngle
mod 360;
1803 end else begin // Counter-clockwise
1804 RAngle
:= RAngle
- Abs(Obj
.Vel
.X
)*8 - Abs(Obj
.Vel
.Y
);
1806 RAngle
:= (360 - (Abs(RAngle
) mod 360)) mod 360;
1812 procedure TGib
.getMapBox (out x
, y
, w
, h
: Integer); inline;
1814 x
:= Obj
.X
+Obj
.Rect
.X
;
1815 y
:= Obj
.Y
+Obj
.Rect
.Y
;
1816 w
:= Obj
.Rect
.Width
;
1817 h
:= Obj
.Rect
.Height
;
1820 procedure TGib
.moveBy (dx
, dy
: Integer); inline;
1822 if (dx
<> 0) or (dy
<> 0) then
1831 procedure TShell
.getMapBox (out x
, y
, w
, h
: Integer); inline;
1835 w
:= Obj
.Rect
.Width
;
1836 h
:= Obj
.Rect
.Height
;
1839 procedure TShell
.moveBy (dx
, dy
: Integer); inline;
1841 if (dx
<> 0) or (dy
<> 0) then
1850 procedure TGib
.positionChanged (); inline; begin end;
1851 procedure TShell
.positionChanged (); inline; begin end;
1854 procedure g_Player_RemoveAllCorpses();
1860 SetLength(gGibs
, MaxGibs
);
1861 SetLength(gShells
, MaxGibs
);
1865 if gCorpses
<> nil then
1866 for i
:= 0 to High(gCorpses
) do
1870 SetLength(gCorpses
, MaxCorpses
);
1873 procedure g_Player_Corpses_SaveState (st
: TStream
);
1877 // Считаем количество существующих трупов
1879 for i
:= 0 to High(gCorpses
) do if (gCorpses
[i
] <> nil) then Inc(count
);
1881 // Количество трупов
1882 utils
.writeInt(st
, LongInt(count
));
1884 if (count
= 0) then exit
;
1887 for i
:= 0 to High(gCorpses
) do
1889 if gCorpses
[i
] <> nil then
1892 utils
.writeStr(st
, gCorpses
[i
].FModel
.GetName());
1894 utils
.writeBool(st
, gCorpses
[i
].Mess
);
1895 // Сохраняем данные трупа:
1896 gCorpses
[i
].SaveState(st
);
1902 procedure g_Player_Corpses_LoadState (st
: TStream
);
1910 g_Player_RemoveAllCorpses();
1912 // Количество трупов:
1913 count
:= utils
.readLongInt(st
);
1914 if (count
< 0) or (count
> Length(gCorpses
)) then raise XStreamError
.Create('invalid number of corpses');
1916 if (count
= 0) then exit
;
1919 for i
:= 0 to count
-1 do
1922 str
:= utils
.readStr(st
);
1924 b
:= utils
.readBool(st
);
1926 gCorpses
[i
] := TCorpse
.Create(0, 0, str
, b
);
1927 // Загружаем данные трупа
1928 gCorpses
[i
].LoadState(st
);
1935 function TPlayer
.isValidViewPort (): Boolean; inline; begin result
:= (viewPortW
> 0) and (viewPortH
> 0); end;
1937 procedure TPlayer
.BFGHit();
1939 g_Weapon_BFGHit(FObj
.X
+FObj
.Rect
.X
+(FObj
.Rect
.Width
div 2),
1940 FObj
.Y
+FObj
.Rect
.Y
+(FObj
.Rect
.Height
div 2));
1941 if g_Game_IsServer
and g_Game_IsNet
then
1942 MH_SEND_Effect(FObj
.X
+FObj
.Rect
.X
+(FObj
.Rect
.Width
div 2),
1943 FObj
.Y
+FObj
.Rect
.Y
+(FObj
.Rect
.Height
div 2),
1947 procedure TPlayer
.ChangeModel(ModelName
: string);
1949 locModel
: TPlayerModel
;
1951 locModel
:= g_PlayerModel_Get(ModelName
);
1952 if locModel
= nil then Exit
;
1958 procedure TPlayer
.SetModel(ModelName
: string);
1962 m
:= g_PlayerModel_Get(ModelName
);
1965 g_SimpleError(Format(_lc
[I_GAME_ERROR_MODEL_FALLBACK
], [ModelName
]));
1966 m
:= g_PlayerModel_Get('doomer');
1969 g_FatalError(Format(_lc
[I_GAME_ERROR_MODEL
], ['doomer']));
1974 if FModel
<> nil then
1979 if not (gGameSettings
.GameMode
in [GM_TDM
, GM_CTF
]) then
1980 FModel
.Color
:= FColor
1982 FModel
.Color
:= TEAMCOLOR
[FTeam
];
1983 FModel
.SetWeapon(FCurrWeap
);
1984 FModel
.SetFlag(FFlag
);
1985 SetDirection(FDirection
);
1988 procedure TPlayer
.SetColor(Color
: TRGB
);
1991 if not (gGameSettings
.GameMode
in [GM_TDM
, GM_CTF
]) then
1992 if FModel
<> nil then FModel
.Color
:= Color
;
1995 function TPlayer
.GetColor(): TRGB
;
1997 result
:= FModel
.Color
;
2000 procedure TPlayer
.SwitchTeam
;
2002 if g_Game_IsClient
then
2004 if not (gGameSettings
.GameMode
in [GM_TDM
, GM_CTF
]) then Exit
;
2006 if gGameOn
and FAlive
then
2007 Kill(K_SIMPLEKILL
, FUID
, HIT_SELF
);
2009 if FTeam
= TEAM_RED
then
2011 ChangeTeam(TEAM_BLUE
);
2012 g_Console_Add(Format(_lc
[I_PLAYER_CHTEAM_BLUE
], [FName
]), True);
2013 if g_Game_IsNet
then
2014 MH_SEND_GameEvent(NET_EV_CHANGE_TEAM
, TEAM_BLUE
, FName
);
2018 ChangeTeam(TEAM_RED
);
2019 g_Console_Add(Format(_lc
[I_PLAYER_CHTEAM_RED
], [FName
]), True);
2020 if g_Game_IsNet
then
2021 MH_SEND_GameEvent(NET_EV_CHANGE_TEAM
, TEAM_RED
, FName
);
2023 FPreferredTeam
:= FTeam
;
2026 procedure TPlayer
.ChangeTeam(Team
: Byte);
2033 TEAM_RED
, TEAM_BLUE
:
2034 FModel
.Color
:= TEAMCOLOR
[Team
];
2036 FModel
.Color
:= FColor
;
2038 if (FTeam
<> OldTeam
) and g_Game_IsNet
and g_Game_IsServer
then
2039 MH_SEND_PlayerStats(FUID
);
2043 procedure TPlayer.CollideItem();
2048 if gItems = nil then Exit;
2049 if not FAlive then Exit;
2051 for i := 0 to High(gItems) do
2054 if (ItemType <> ITEM_NONE) and alive then
2055 if g_Obj_Collide(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width,
2056 PLAYER_RECT.Height, @Obj) then
2058 if not PickItem(ItemType, gItems[i].Respawnable, r) then Continue;
2060 if ItemType in [ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_INVUL] then
2061 g_Sound_PlayExAt('SOUND_ITEM_GETRULEZ', FObj.X, FObj.Y)
2062 else if ItemType in [ITEM_MEDKIT_SMALL, ITEM_MEDKIT_LARGE, ITEM_MEDKIT_BLACK] then
2063 g_Sound_PlayExAt('SOUND_ITEM_GETMED', FObj.X, FObj.Y)
2064 else g_Sound_PlayExAt('SOUND_ITEM_GETITEM', FObj.X, FObj.Y);
2066 // Надо убрать с карты, если это не ключ, которым нужно поделится с другим игроком:
2067 if r and not ((ItemType in [ITEM_KEY_RED, ITEM_KEY_GREEN, ITEM_KEY_BLUE]) and
2068 (gGameSettings.GameType = GT_SINGLE) and
2069 (g_Player_GetCount() > 1)) then
2070 if not Respawnable then g_Items_Remove(i) else g_Items_Pick(i);
2076 function TPlayer
.CollideLevel(XInc
, YInc
: Integer): Boolean;
2078 Result
:= g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
,
2079 PLAYER_RECT
.Width
, PLAYER_RECT
.Height
, PANEL_WALL
,
2083 constructor TPlayer
.Create();
2089 mEDamageType
:= HIT_SOME
;
2095 FSawSound
:= TPlayableSound
.Create();
2096 FSawSoundIdle
:= TPlayableSound
.Create();
2097 FSawSoundHit
:= TPlayableSound
.Create();
2098 FSawSoundSelect
:= TPlayableSound
.Create();
2099 FFlameSoundOn
:= TPlayableSound
.Create();
2100 FFlameSoundOff
:= TPlayableSound
.Create();
2101 FFlameSoundWork
:= TPlayableSound
.Create();
2102 FJetSoundFly
:= TPlayableSound
.Create();
2103 FJetSoundOn
:= TPlayableSound
.Create();
2104 FJetSoundOff
:= TPlayableSound
.Create();
2106 FSawSound
.SetByName('SOUND_WEAPON_FIRESAW');
2107 FSawSoundIdle
.SetByName('SOUND_WEAPON_IDLESAW');
2108 FSawSoundHit
.SetByName('SOUND_WEAPON_HITSAW');
2109 FSawSoundSelect
.SetByName('SOUND_WEAPON_SELECTSAW');
2110 FFlameSoundOn
.SetByName('SOUND_WEAPON_FLAMEON');
2111 FFlameSoundOff
.SetByName('SOUND_WEAPON_FLAMEOFF');
2112 FFlameSoundWork
.SetByName('SOUND_WEAPON_FLAMEWORK');
2113 FJetSoundFly
.SetByName('SOUND_PLAYER_JETFLY');
2114 FJetSoundOn
.SetByName('SOUND_PLAYER_JETON');
2115 FJetSoundOff
.SetByName('SOUND_PLAYER_JETOFF');
2117 FSpectatePlayer
:= -1;
2121 FSavedStateNum
:= -1;
2129 FActualModelName
:= 'doomer';
2132 FObj
.Rect
:= PLAYER_RECT
;
2134 FBFGFireCounter
:= -1;
2135 FJustTeleported
:= False;
2138 FWaitForFirstSpawn
:= false;
2139 FPunchAnim
:= TAnimationState
.Create(False, 1, 4);
2145 procedure TPlayer
.positionChanged (); inline;
2149 procedure TPlayer
.doDamage (v
: Integer);
2151 if (v
<= 0) then exit
;
2152 if (v
> 32767) then v
:= 32767;
2153 Damage(v
, 0, 0, 0, mEDamageType
);
2156 procedure TPlayer
.Damage(value
: Word; SpawnerUID
: Word; vx
, vy
: Integer; t
: Byte);
2160 if (not g_Game_IsClient
) and (not FAlive
) then
2165 // Неуязвимость не спасает от ловушек:
2166 if ((t
= HIT_TRAP
) or (t
= HIT_SELF
)) and (not FGodMode
) then
2168 if not g_Game_IsClient
then
2171 if t
= HIT_TRAP
then
2173 // Ловушка убивает сразу:
2175 Kill(K_EXTRAHARDKILL
, SpawnerUID
, t
);
2177 if t
= HIT_SELF
then
2181 Kill(K_SIMPLEKILL
, SpawnerUID
, t
);
2184 // Обнулить действия примочек, чтобы фон пропал
2185 FMegaRulez
[MR_SUIT
] := 0;
2186 FMegaRulez
[MR_INVUL
] := 0;
2187 FMegaRulez
[MR_INVIS
] := 0;
2192 // Но от остального спасает:
2193 if FMegaRulez
[MR_INVUL
] >= gTime
then
2200 // Если есть урон своим, или ранил сам себя, или тебя ранил противник:
2201 if LongBool(gGameSettings
.Options
and GAME_OPTION_TEAMDAMAGE
) or
2202 (SpawnerUID
= FUID
) or
2203 (not SameTeam(FUID
, SpawnerUID
)) then
2205 FLastSpawnerUID
:= SpawnerUID
;
2207 // Кровь (пузырьки, если в воде):
2208 if gBloodCount
> 0 then
2210 c
:= Min(value
, 200)*gBloodCount
+ Random(Min(value
, 200) div 2);
2211 if value
div 4 <= c
then
2212 c
:= c
- (value
div 4)
2216 if (t
= HIT_SOME
) and (vx
= 0) and (vy
= 0) then
2220 HIT_TRAP
, HIT_ACID
, HIT_FLAME
, HIT_SELF
: MakeBloodSimple(c
);
2221 HIT_BFG
, HIT_ROCKET
, HIT_SOME
: MakeBloodVector(c
, vx
, vy
);
2225 if t
= HIT_WATER
then
2227 g_GFX_Bubbles(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2),
2228 FObj
.Y
+PLAYER_RECT
.Y
-4, value
div 2, 8, 4);
2235 Inc(FDamageBuffer
, value
);
2239 FPain
:= FPain
+ value
;
2242 if g_Game_IsServer
and g_Game_IsNet
then
2244 MH_SEND_PlayerDamage(FUID
, t
, SpawnerUID
, value
, vx
, vy
);
2245 MH_SEND_PlayerStats(FUID
);
2246 MH_SEND_PlayerPos(False, FUID
);
2250 function TPlayer
.Heal(value
: Word; Soft
: Boolean): Boolean;
2253 if g_Game_IsClient
then
2258 if Soft
and (FHealth
< PLAYER_HP_SOFT
) then
2260 IncMax(FHealth
, value
, PLAYER_HP_SOFT
);
2263 if (not Soft
) and (FHealth
< PLAYER_HP_LIMIT
) then
2265 IncMax(FHealth
, value
, PLAYER_HP_LIMIT
);
2269 if Result
and g_Game_IsServer
and g_Game_IsNet
then
2270 MH_SEND_PlayerStats(FUID
);
2273 destructor TPlayer
.Destroy();
2275 if (gPlayer1
<> nil) and (gPlayer1
.FUID
= FUID
) then
2277 if (gPlayer2
<> nil) and (gPlayer2
.FUID
= FUID
) then
2281 FSawSoundIdle
.Free();
2282 FSawSoundHit
.Free();
2283 FSawSoundSelect
.Free();
2284 FFlameSoundOn
.Free();
2285 FFlameSoundOff
.Free();
2286 FFlameSoundWork
.Free();
2287 FJetSoundFly
.Free();
2289 FJetSoundOff
.Free();
2296 procedure TPlayer
.DoPunch();
2302 procedure TPlayer
.Fire();
2304 f
, DidFire
: Boolean;
2305 wx
, wy
, xd
, yd
: Integer;
2308 if g_Game_IsClient
then Exit
;
2309 // FBFGFireCounter - время перед выстрелом (для BFG)
2310 // FReloading - время после выстрела (для всего)
2318 if FReloading
[FCurrWeap
] <> 0 then Exit
;
2323 wx
:= FObj
.X
+WEAPONPOINT
[FDirection
].X
;
2324 wy
:= FObj
.Y
+WEAPONPOINT
[FDirection
].Y
;
2325 xd
:= wx
+IfThen(FDirection
= TDirection
.D_LEFT
, -30, 30);
2326 yd
:= wy
+firediry();
2332 if R_BERSERK
in FRulez
then
2334 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
2335 locobj
.X
:= FObj
.X
+FObj
.Rect
.X
;
2336 locobj
.Y
:= FObj
.Y
+FObj
.Rect
.Y
;
2339 locobj
.rect
.Width
:= 39;
2340 locobj
.rect
.Height
:= 52;
2341 locobj
.Vel
.X
:= (xd
-wx
) div 2;
2342 locobj
.Vel
.Y
:= (yd
-wy
) div 2;
2343 locobj
.Accel
.X
:= xd
-wx
;
2344 locobj
.Accel
.y
:= yd
-wy
;
2346 if g_Weapon_Hit(@locobj
, 50, FUID
, HIT_SOME
) <> 0 then
2347 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj
.X
, FObj
.Y
)
2349 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj
.X
, FObj
.Y
);
2351 if (gFlash
= 1) and (FPain
< 50) then FPain
:= min(FPain
+ 25, 50);
2355 g_Weapon_punch(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
, 3, FUID
);
2359 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2364 if g_Weapon_chainsaw(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
,
2365 IfThen(gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
], 9, 3), FUID
) <> 0 then
2367 FSawSoundSelect
.Stop();
2369 FSawSoundHit
.PlayAt(FObj
.X
, FObj
.Y
);
2371 else if not FSawSoundHit
.IsPlaying() then
2373 FSawSoundSelect
.Stop();
2374 FSawSound
.PlayAt(FObj
.X
, FObj
.Y
);
2377 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2383 if FAmmo
[A_BULLETS
] > 0 then
2385 g_Weapon_pistol(wx
, wy
, xd
, yd
, FUID
);
2386 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2387 Dec(FAmmo
[A_BULLETS
]);
2388 FFireAngle
:= FAngle
;
2391 g_Player_CreateShell(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
2392 GameVelX
, GameVelY
-2, SHELL_BULLET
);
2396 if FAmmo
[A_SHELLS
] > 0 then
2398 g_Weapon_shotgun(wx
, wy
, xd
, yd
, FUID
);
2399 if not gSoundEffectsDF
then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', wx
, wy
);
2400 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2401 Dec(FAmmo
[A_SHELLS
]);
2402 FFireAngle
:= FAngle
;
2406 FShellType
:= SHELL_SHELL
;
2410 if FAmmo
[A_SHELLS
] >= 2 then
2412 g_Weapon_dshotgun(wx
, wy
, xd
, yd
, FUID
);
2413 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2414 Dec(FAmmo
[A_SHELLS
], 2);
2415 FFireAngle
:= FAngle
;
2419 FShellType
:= SHELL_DBLSHELL
;
2423 if FAmmo
[A_BULLETS
] > 0 then
2425 g_Weapon_mgun(wx
, wy
, xd
, yd
, FUID
);
2426 if not gSoundEffectsDF
then g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', wx
, wy
);
2427 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2428 Dec(FAmmo
[A_BULLETS
]);
2429 FFireAngle
:= FAngle
;
2432 g_Player_CreateShell(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
2433 GameVelX
, GameVelY
-2, SHELL_BULLET
);
2436 WEAPON_ROCKETLAUNCHER
:
2437 if FAmmo
[A_ROCKETS
] > 0 then
2439 g_Weapon_rocket(wx
, wy
, xd
, yd
, FUID
);
2440 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2441 Dec(FAmmo
[A_ROCKETS
]);
2442 FFireAngle
:= FAngle
;
2448 if FAmmo
[A_CELLS
] > 0 then
2450 g_Weapon_plasma(wx
, wy
, xd
, yd
, FUID
);
2451 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2452 Dec(FAmmo
[A_CELLS
]);
2453 FFireAngle
:= FAngle
;
2459 if (FAmmo
[A_CELLS
] >= 40) and (FBFGFireCounter
= -1) then
2461 FBFGFireCounter
:= 17;
2462 if not FNoReload
then
2463 g_Sound_PlayExAt('SOUND_WEAPON_STARTFIREBFG', FObj
.X
, FObj
.Y
);
2464 Dec(FAmmo
[A_CELLS
], 40);
2468 WEAPON_SUPERPULEMET
:
2469 if FAmmo
[A_SHELLS
] > 0 then
2471 g_Weapon_shotgun(wx
, wy
, xd
, yd
, FUID
);
2472 if not gSoundEffectsDF
then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx
, wy
);
2473 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2474 Dec(FAmmo
[A_SHELLS
]);
2475 FFireAngle
:= FAngle
;
2478 g_Player_CreateShell(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
2479 GameVelX
, GameVelY
-2, SHELL_SHELL
);
2482 WEAPON_FLAMETHROWER
:
2483 if FAmmo
[A_FUEL
] > 0 then
2485 g_Weapon_flame(wx
, wy
, xd
, yd
, FUID
);
2487 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2489 FFireAngle
:= FAngle
;
2496 if g_Game_IsNet
and g_Game_IsServer
then MH_SEND_PlayerStats(FUID
);
2500 if g_Game_IsNet
then
2504 if FCurrWeap
<> WEAPON_BFG
then
2505 MH_SEND_PlayerFire(FUID
, FCurrWeap
, wx
, wy
, xd
, yd
, LastShotID
)
2507 if not FNoReload
then
2508 MH_SEND_Sound(FObj
.X
, FObj
.Y
, 'SOUND_WEAPON_STARTFIREBFG');
2511 MH_SEND_PlayerStats(FUID
);
2516 if (FAngle
= 0) or (FAngle
= 180) then SetAction(A_ATTACK
)
2517 else if (FAngle
= ANGLE_LEFTDOWN
) or (FAngle
= ANGLE_RIGHTDOWN
) then SetAction(A_ATTACKDOWN
)
2518 else if (FAngle
= ANGLE_LEFTUP
) or (FAngle
= ANGLE_RIGHTUP
) then SetAction(A_ATTACKUP
);
2521 function TPlayer
.GetAmmoByWeapon(Weapon
: Byte): Word;
2524 WEAPON_PISTOL
, WEAPON_CHAINGUN
: Result
:= FAmmo
[A_BULLETS
];
2525 WEAPON_SHOTGUN1
, WEAPON_SHOTGUN2
, WEAPON_SUPERPULEMET
: Result
:= FAmmo
[A_SHELLS
];
2526 WEAPON_ROCKETLAUNCHER
: Result
:= FAmmo
[A_ROCKETS
];
2527 WEAPON_PLASMA
, WEAPON_BFG
: Result
:= FAmmo
[A_CELLS
];
2528 WEAPON_FLAMETHROWER
: Result
:= FAmmo
[A_FUEL
];
2533 function TPlayer
.HeadInLiquid(XInc
, YInc
: Integer): Boolean;
2535 Result
:= g_Map_CollidePanel(FObj
.X
+PLAYER_HEADRECT
.X
+XInc
, FObj
.Y
+PLAYER_HEADRECT
.Y
+YInc
,
2536 PLAYER_HEADRECT
.Width
, PLAYER_HEADRECT
.Height
,
2537 PANEL_WATER
or PANEL_ACID1
or PANEL_ACID2
, True);
2540 procedure TPlayer
.FlamerOn
;
2542 FFlameSoundOff
.Stop();
2543 FFlameSoundOff
.SetPosition(0);
2546 if (not FFlameSoundOn
.IsPlaying()) and (not FFlameSoundWork
.IsPlaying()) then
2547 FFlameSoundWork
.PlayAt(FObj
.X
, FObj
.Y
);
2551 FFlameSoundOn
.PlayAt(FObj
.X
, FObj
.Y
);
2556 procedure TPlayer
.FlamerOff
;
2560 FFlameSoundOn
.Stop();
2561 FFlameSoundOn
.SetPosition(0);
2562 FFlameSoundWork
.Stop();
2563 FFlameSoundWork
.SetPosition(0);
2564 FFlameSoundOff
.PlayAt(FObj
.X
, FObj
.Y
);
2569 procedure TPlayer
.JetpackOn
;
2573 FJetSoundOn
.SetPosition(0);
2574 FJetSoundOn
.PlayAt(FObj
.X
, FObj
.Y
);
2578 procedure TPlayer
.JetpackOff
;
2582 FJetSoundOff
.SetPosition(0);
2583 FJetSoundOff
.PlayAt(FObj
.X
, FObj
.Y
);
2586 procedure TPlayer
.CatchFire(Attacker
: Word; Timeout
: Integer = PLAYER_BURN_TIME
);
2588 if Timeout
<= 0 then
2590 if (FMegaRulez
[MR_SUIT
] > gTime
) or (FMegaRulez
[MR_INVUL
] > gTime
) then
2591 exit
; // Не загораемся когда есть защита
2592 if g_Obj_CollidePanel(@FObj
, 0, 0, PANEL_WATER
or PANEL_ACID1
or PANEL_ACID2
) then
2593 exit
; // Не подгораем в воде на всякий случай
2594 if FFireTime
<= 0 then
2595 g_Sound_PlayExAt('SOUND_IGNITE', FObj
.X
, FObj
.Y
);
2596 FFireTime
:= Timeout
;
2597 FFireAttacker
:= Attacker
;
2598 if g_Game_IsNet
and g_Game_IsServer
then
2599 MH_SEND_PlayerStats(FUID
);
2602 procedure TPlayer
.Jump();
2604 if gFly
or FJetpack
then
2606 // Полет (чит-код или джетпак):
2607 if FObj
.Vel
.Y
> -VEL_FLY
then
2608 FObj
.Vel
.Y
:= FObj
.Vel
.Y
- 3;
2611 if FJetFuel
> 0 then
2613 if (FJetFuel
< 1) and g_Game_IsServer
then
2617 if g_Game_IsNet
then
2618 MH_SEND_PlayerStats(FUID
);
2624 // Не включать джетпак в режиме прохождения сквозь стены
2626 FCanJetpack
:= False;
2628 // Прыгаем или всплываем:
2629 if (CollideLevel(0, 1) or
2630 g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+PLAYER_RECT
.Y
+36, PLAYER_RECT
.Width
,
2631 PLAYER_RECT
.Height
-33, PANEL_STEP
, False)
2632 ) and (FObj
.Accel
.Y
= 0) then // Не прыгать, если есть вертикальное ускорение
2634 FObj
.Vel
.Y
:= -VEL_JUMP
;
2635 FCanJetpack
:= False;
2639 if BodyInLiquid(0, 0) then
2640 FObj
.Vel
.Y
:= -VEL_SW
2641 else if (FJetFuel
> 0) and FCanJetpack
and
2642 g_Game_IsServer
and (not g_Obj_CollideLiquid(@FObj
, 0, 0)) then
2646 if g_Game_IsNet
then
2647 MH_SEND_PlayerStats(FUID
);
2652 procedure TPlayer
.Kill(KillType
: Byte; SpawnerUID
: Word; t
: Byte);
2654 a
, i
, k
, ab
, ar
: Byte;
2658 srv
, netsrv
: Boolean;
2664 procedure PushItem(t
: Byte);
2668 id
:= g_Items_Create(FObj
.X
, FObj
.Y
, t
, True, False);
2669 it
:= g_Items_ByIdx(id
);
2670 if KillType
= K_EXTRAHARDKILL
then // -7..+7; -8..0
2672 g_Obj_Push(@it
.Obj
, (FObj
.Vel
.X
div 2)-7+Random(15),
2673 (FObj
.Vel
.Y
div 2)-Random(9));
2674 it
.positionChanged(); // this updates spatial accelerators
2678 if KillType
= K_HARDKILL
then // -5..+5; -5..0
2680 g_Obj_Push(@it
.Obj
, (FObj
.Vel
.X
div 2)-5+Random(11),
2681 (FObj
.Vel
.Y
div 2)-Random(6));
2683 else // -3..+3; -3..0
2685 g_Obj_Push(@it
.Obj
, (FObj
.Vel
.X
div 2)-3+Random(7),
2686 (FObj
.Vel
.Y
div 2)-Random(4));
2688 it
.positionChanged(); // this updates spatial accelerators
2691 if g_Game_IsNet
and g_Game_IsServer
then
2692 MH_SEND_ItemSpawn(True, id
);
2696 DoFrags
:= (gGameSettings
.MaxLives
= 0) or (gGameSettings
.GameMode
= GM_COOP
);
2697 Srv
:= g_Game_IsServer
;
2698 Netsrv
:= g_Game_IsServer
and g_Game_IsNet
;
2699 if Srv
then FDeath
:= FDeath
+ 1;
2704 if not FPhysics
then
2710 if (gGameSettings
.MaxLives
> 0) and Srv
and (gLMSRespawn
= LMS_RESPAWN_NONE
) then
2712 if FLives
> 0 then FLives
:= FLives
- 1;
2713 if FLives
= 0 then FNoRespawn
:= True;
2716 // Номер типа смерти:
2719 K_SIMPLEKILL
: a
:= 1;
2721 K_EXTRAHARDKILL
: a
:= 3;
2726 if not FModel
.PlaySound(MODELSOUND_DIE
, a
, FObj
.X
, FObj
.Y
) then
2728 if FModel
.PlaySound(MODELSOUND_DIE
, i
, FObj
.X
, FObj
.Y
) then
2735 FTime
[T_RESPAWN
] := gTime
+ TIME_RESPAWN1
;
2737 FTime
[T_RESPAWN
] := gTime
+ TIME_RESPAWN2
;
2738 K_EXTRAHARDKILL
, K_FALLKILL
:
2739 FTime
[T_RESPAWN
] := gTime
+ TIME_RESPAWN3
;
2742 // Переключаем состояние:
2746 K_HARDKILL
, K_EXTRAHARDKILL
:
2750 // Реакция монстров на смерть игрока:
2751 if (KillType
<> K_FALLKILL
) and (Srv
) then
2752 g_Monsters_killedp();
2754 if SpawnerUID
= FUID
then
2756 if Srv
and (DoFrags
or (gGameSettings
.GameMode
= GM_TDM
)) then
2761 g_Console_Add(Format(_lc
[I_PLAYER_KILL_SELF
], [FName
]), True);
2764 if g_GetUIDType(SpawnerUID
) = UID_PLAYER
then
2765 begin // Убит другим игроком
2766 KP
:= g_Player_Get(SpawnerUID
);
2767 if (KP
<> nil) and Srv
then
2769 if (DoFrags
or (gGameSettings
.GameMode
= GM_TDM
)) then
2770 if SameTeam(FUID
, SpawnerUID
) then
2780 if (gGameSettings
.GameMode
= GM_TDM
) and DoFrags
then
2781 Inc(gTeamStat
[KP
.Team
].Goals
,
2782 IfThen(SameTeam(FUID
, SpawnerUID
), -1, 1));
2784 if netsrv
then MH_SEND_PlayerStats(SpawnerUID
);
2787 plr
:= g_Player_Get(SpawnerUID
);
2795 g_Console_Add(Format(_lc
[I_PLAYER_KILL_EXTRAHARD_2
],
2799 g_Console_Add(Format(_lc
[I_PLAYER_KILL_EXTRAHARD_1
],
2803 g_Console_Add(Format(_lc
[I_PLAYER_KILL
],
2808 else if g_GetUIDType(SpawnerUID
) = UID_MONSTER
then
2809 begin // Убит монстром
2810 mon
:= g_Monsters_ByUID(SpawnerUID
);
2814 s
:= g_Mons_GetKilledByTypeId(mon
.MonsterType
);
2818 g_Console_Add(Format(_lc
[I_PLAYER_KILL_EXTRAHARD_2
],
2822 g_Console_Add(Format(_lc
[I_PLAYER_KILL_EXTRAHARD_1
],
2826 g_Console_Add(Format(_lc
[I_PLAYER_KILL
],
2831 else // Особые типы смерти
2834 HIT_SELF
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_SELF
], [FName
]), True);
2835 HIT_FALL
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_FALL
], [FName
]), True);
2836 HIT_WATER
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_WATER
], [FName
]), True);
2837 HIT_ACID
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_ACID
], [FName
]), True);
2838 HIT_TRAP
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_TRAP
], [FName
]), True);
2839 else g_Console_Add(Format(_lc
[I_PLAYER_DIED
], [FName
]), True);
2845 for a
:= WP_FIRST
to WP_LAST
do
2849 WEAPON_SAW
: i
:= ITEM_WEAPON_SAW
;
2850 WEAPON_SHOTGUN1
: i
:= ITEM_WEAPON_SHOTGUN1
;
2851 WEAPON_SHOTGUN2
: i
:= ITEM_WEAPON_SHOTGUN2
;
2852 WEAPON_CHAINGUN
: i
:= ITEM_WEAPON_CHAINGUN
;
2853 WEAPON_ROCKETLAUNCHER
: i
:= ITEM_WEAPON_ROCKETLAUNCHER
;
2854 WEAPON_PLASMA
: i
:= ITEM_WEAPON_PLASMA
;
2855 WEAPON_BFG
: i
:= ITEM_WEAPON_BFG
;
2856 WEAPON_SUPERPULEMET
: i
:= ITEM_WEAPON_SUPERPULEMET
;
2857 WEAPON_FLAMETHROWER
: i
:= ITEM_WEAPON_FLAMETHROWER
;
2866 if R_ITEM_BACKPACK
in FRulez
then
2867 PushItem(ITEM_AMMO_BACKPACK
);
2869 // Выброс ракетного ранца:
2870 if FJetFuel
> 0 then
2871 PushItem(ITEM_JETPACK
);
2874 if (not (gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
])) or
2875 (not LongBool(gGameSettings
.Options
and GAME_OPTION_DMKEYS
)) then
2877 if R_KEY_RED
in FRulez
then
2878 PushItem(ITEM_KEY_RED
);
2880 if R_KEY_GREEN
in FRulez
then
2881 PushItem(ITEM_KEY_GREEN
);
2883 if R_KEY_BLUE
in FRulez
then
2884 PushItem(ITEM_KEY_BLUE
);
2888 DropFlag(KillType
= K_FALLKILL
);
2891 FCorpse
:= g_Player_CreateCorpse(Self
);
2893 if Srv
and (gGameSettings
.MaxLives
> 0) and FNoRespawn
and
2894 (gLMSRespawn
= LMS_RESPAWN_NONE
) then
2900 for i
:= Low(gPlayers
) to High(gPlayers
) do
2902 if gPlayers
[i
] = nil then continue
;
2903 if (not gPlayers
[i
].FNoRespawn
) and (not gPlayers
[i
].FSpectator
) then
2906 if gPlayers
[i
].FTeam
= TEAM_RED
then Inc(ar
)
2907 else if gPlayers
[i
].FTeam
= TEAM_BLUE
then Inc(ab
);
2912 OldLR
:= gLMSRespawn
;
2913 if (gGameSettings
.GameMode
= GM_COOP
) then
2917 // everyone is dead, restart the map
2918 g_Game_Message(_lc
[I_MESSAGE_LMS_LOSE
], 144);
2920 MH_SEND_GameEvent(NET_EV_LMS_LOSE
);
2921 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2922 gLMSRespawnTime
:= gTime
+ 5000;
2924 else if (a
= 1) then
2926 if (gPlayers
[k
] <> nil) and not (gPlayers
[k
] is TBot
) then
2927 if (gPlayers
[k
] = gPlayer1
) or
2928 (gPlayers
[k
] = gPlayer2
) then
2929 g_Console_Add('*** ' + _lc
[I_MESSAGE_LMS_SURVIVOR
] + ' ***', True)
2930 else if Netsrv
and (gPlayers
[k
].FClientID
>= 0) then
2931 MH_SEND_GameEvent(NET_EV_LMS_SURVIVOR
, 0, 'N', gPlayers
[k
].FClientID
);
2934 else if (gGameSettings
.GameMode
= GM_TDM
) then
2936 if (ab
= 0) and (ar
<> 0) then
2939 g_Game_Message(Format(_lc
[I_MESSAGE_TLMS_WIN
], [AnsiUpperCase(_lc
[I_GAME_TEAM_RED
])]), 144);
2941 MH_SEND_GameEvent(NET_EV_TLMS_WIN
, TEAM_RED
);
2942 Inc(gTeamStat
[TEAM_RED
].Goals
);
2943 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2944 gLMSRespawnTime
:= gTime
+ 5000;
2946 else if (ar
= 0) and (ab
<> 0) then
2949 g_Game_Message(Format(_lc
[I_MESSAGE_TLMS_WIN
], [AnsiUpperCase(_lc
[I_GAME_TEAM_BLUE
])]), 144);
2951 MH_SEND_GameEvent(NET_EV_TLMS_WIN
, TEAM_BLUE
);
2952 Inc(gTeamStat
[TEAM_BLUE
].Goals
);
2953 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2954 gLMSRespawnTime
:= gTime
+ 5000;
2956 else if (ar
= 0) and (ab
= 0) then
2959 g_Game_Message(_lc
[I_GAME_WIN_DRAW
], 144);
2961 MH_SEND_GameEvent(NET_EV_LMS_DRAW
, 0, FName
);
2962 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2963 gLMSRespawnTime
:= gTime
+ 5000;
2966 else if (gGameSettings
.GameMode
= GM_DM
) then
2970 if gPlayers
[k
] <> nil then
2973 // survivor is the winner
2974 g_Game_Message(Format(_lc
[I_MESSAGE_LMS_WIN
], [AnsiUpperCase(FName
)]), 144);
2976 MH_SEND_GameEvent(NET_EV_LMS_WIN
, 0, FName
);
2979 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2980 gLMSRespawnTime
:= gTime
+ 5000;
2982 else if (a
= 0) then
2984 // everyone is dead, restart the map
2985 g_Game_Message(_lc
[I_GAME_WIN_DRAW
], 144);
2987 MH_SEND_GameEvent(NET_EV_LMS_DRAW
, 0, FName
);
2988 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2989 gLMSRespawnTime
:= gTime
+ 5000;
2992 if srv
and (OldLR
= LMS_RESPAWN_NONE
) and (gLMSRespawn
> LMS_RESPAWN_NONE
) then
2994 if NetMode
= NET_SERVER
then
2995 MH_SEND_GameEvent(NET_EV_LMS_WARMUP
, gLMSRespawnTime
- gTime
)
2997 g_Console_Add(Format(_lc
[I_MSG_WARMUP_START
], [(gLMSRespawnTime
- gTime
) div 1000]), True);
3003 MH_SEND_PlayerStats(FUID
);
3004 MH_SEND_PlayerDeath(FUID
, KillType
, t
, SpawnerUID
);
3005 if gGameSettings
.GameMode
= GM_TDM
then MH_SEND_GameStats
;
3008 if srv
and FNoRespawn
then Spectate(True);
3009 FWantsInGame
:= True;
3012 function TPlayer
.BodyInLiquid(XInc
, YInc
: Integer): Boolean;
3014 Result
:= g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
, PLAYER_RECT
.Width
,
3015 PLAYER_RECT
.Height
-20, PANEL_WATER
or PANEL_ACID1
or PANEL_ACID2
, False);
3018 function TPlayer
.BodyInAcid(XInc
, YInc
: Integer): Boolean;
3020 Result
:= g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
, PLAYER_RECT
.Width
,
3021 PLAYER_RECT
.Height
-20, PANEL_ACID1
or PANEL_ACID2
, False);
3024 procedure TPlayer
.MakeBloodSimple(Count
: Word);
3026 var Blood
: TModelBlood
;
3030 Blood
:= SELF
.FModel
.GetBlood();
3031 g_GFX_Blood(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)+8,
3032 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2),
3033 Count
div 2, 3, -1, 16, (PLAYER_RECT
.Height
*2 div 3),
3034 Blood
.R
, Blood
.G
, Blood
.B
, Blood
.Kind
);
3035 g_GFX_Blood(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)-8,
3036 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2),
3037 Count
div 2, -3, -1, 16, (PLAYER_RECT
.Height
*2) div 3,
3038 Blood
.R
, Blood
.G
, Blood
.B
, Blood
.Kind
);
3042 procedure TPlayer
.MakeBloodVector(Count
: Word; VelX
, VelY
: Integer);
3044 var Blood
: TModelBlood
;
3048 Blood
:= SELF
.FModel
.GetBlood();
3049 g_GFX_Blood(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2),
3050 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2),
3051 Count
, VelX
, VelY
, 16, (PLAYER_RECT
.Height
*2) div 3,
3052 Blood
.R
, Blood
.G
, Blood
.B
, Blood
.Kind
);
3056 procedure TPlayer
.QueueWeaponSwitch(Weapon
: Byte);
3058 if g_Game_IsClient
then Exit
;
3059 if Weapon
> High(FWeapon
) then Exit
;
3060 FNextWeap
:= FNextWeap
or (1 shl Weapon
);
3063 procedure TPlayer
.resetWeaponQueue ();
3066 FNextWeapDelay
:= 0;
3069 function TPlayer
.hasAmmoForWeapon (weapon
: Byte): Boolean;
3073 WEAPON_KASTET
, WEAPON_SAW
: result
:= true;
3074 WEAPON_SHOTGUN1
, WEAPON_SHOTGUN2
, WEAPON_SUPERPULEMET
: result
:= (FAmmo
[A_SHELLS
] > 0);
3075 WEAPON_PISTOL
, WEAPON_CHAINGUN
: result
:= (FAmmo
[A_BULLETS
] > 0);
3076 WEAPON_ROCKETLAUNCHER
: result
:= (FAmmo
[A_ROCKETS
] > 0);
3077 WEAPON_PLASMA
, WEAPON_BFG
: result
:= (FAmmo
[A_CELLS
] > 0);
3078 WEAPON_FLAMETHROWER
: result
:= (FAmmo
[A_FUEL
] > 0);
3079 else result
:= (weapon
< length(FWeapon
));
3083 // return 255 for "no switch"
3084 function TPlayer
.getNextWeaponIndex (): Byte;
3087 wantThisWeapon
: array[0..64] of Boolean;
3088 wwc
: Integer = 0; //HACK!
3091 result
:= 255; // default result: "no switch"
3092 // had weapon cycling on previous frame? remove that flag
3093 if (FNextWeap
and $2000) <> 0 then
3095 FNextWeap
:= FNextWeap
and $1FFF;
3096 FNextWeapDelay
:= 0;
3098 // cycling has priority
3099 if (FNextWeap
and $C000) <> 0 then
3101 if (FNextWeap
and $8000) <> 0 then
3105 FNextWeap
:= FNextWeap
or $2000; // we need this
3106 if FNextWeapDelay
> 0 then
3107 exit
; // cooldown time
3109 for i
:= 0 to High(FWeapon
) do
3111 cwi
:= (cwi
+length(FWeapon
)+dir
) mod length(FWeapon
);
3112 if FWeapon
[cwi
] then
3114 //e_WriteLog(Format(' SWITCH: cur=%d; new=%d', [FCurrWeap, cwi]), MSG_WARNING);
3115 result
:= Byte(cwi
);
3116 FNextWeapDelay
:= WEAPON_DELAY
;
3124 for i
:= 0 to High(wantThisWeapon
) do
3125 wantThisWeapon
[i
] := false;
3126 for i
:= 0 to High(FWeapon
) do
3127 if (FNextWeap
and (1 shl i
)) <> 0 then
3129 wantThisWeapon
[i
] := true;
3132 // exclude currently selected weapon from the set
3133 wantThisWeapon
[FCurrWeap
] := false;
3134 // slow down alterations a little
3137 //e_WriteLog(Format(' FNextWeap=%x; delay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
3138 // more than one weapon requested, assume "alteration" and check alteration delay
3139 if FNextWeapDelay
> 0 then
3145 // do not reset weapon queue, it will be done in `RealizeCurrentWeapon()`
3146 // but clear all counters if no weapon should be switched
3152 //e_WriteLog(Format('wwc=%d', [wwc]), MSG_WARNING);
3153 // try weapons in descending order
3154 for i
:= High(FWeapon
) downto 0 do
3156 if wantThisWeapon
[i
] and FWeapon
[i
] and ((wwc
= 1) or hasAmmoForWeapon(i
)) then
3161 FNextWeapDelay
:= WEAPON_DELAY
* 2; // anyway, 'cause why not
3165 // no suitable weapon found, so reset the queue, to avoid accidental "queuing" of weapon w/o ammo
3169 procedure TPlayer
.RealizeCurrentWeapon();
3170 function switchAllowed (): Boolean;
3175 if FBFGFireCounter
<> -1 then
3177 if FTime
[T_SWITCH
] > gTime
then
3179 for i
:= WP_FIRST
to WP_LAST
do
3180 if FReloading
[i
] > 0 then
3188 //e_WriteLog(Format('***RealizeCurrentWeapon: FNextWeap=%x; FNextWeapDelay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
3189 //FNextWeap := FNextWeap and $1FFF;
3190 if FNextWeapDelay
> 0 then Dec(FNextWeapDelay
); // "alteration delay"
3192 if not switchAllowed
then
3194 //HACK for weapon cycling
3195 if (FNextWeap
and $E000) <> 0 then FNextWeap
:= 0;
3199 nw
:= getNextWeaponIndex();
3200 if nw
= 255 then exit
; // don't reset anything here
3201 if nw
> High(FWeapon
) then
3203 // don't forget to reset queue here!
3204 //e_WriteLog(' RealizeCurrentWeapon: WUTAFUUUU', MSG_WARNING);
3212 FTime
[T_SWITCH
] := gTime
+156;
3213 if FCurrWeap
= WEAPON_SAW
then FSawSoundSelect
.PlayAt(FObj
.X
, FObj
.Y
);
3214 FModel
.SetWeapon(FCurrWeap
);
3215 if g_Game_IsNet
then MH_SEND_PlayerStats(FUID
);
3219 procedure TPlayer
.NextWeapon();
3221 if g_Game_IsClient
then Exit
;
3225 procedure TPlayer
.PrevWeapon();
3227 if g_Game_IsClient
then Exit
;
3231 procedure TPlayer
.SetWeapon(W
: Byte);
3233 if FCurrWeap
<> W
then
3234 if W
= WEAPON_SAW
then
3235 FSawSoundSelect
.PlayAt(FObj
.X
, FObj
.Y
);
3238 FModel
.SetWeapon(CurrWeap
);
3242 function TPlayer
.PickItem(ItemType
: Byte; arespawn
: Boolean; var remove
: Boolean): Boolean;
3244 function allowBerserkSwitching (): Boolean;
3246 if (FBFGFireCounter
<> -1) then begin result
:= false; exit
; end;
3248 if gBerserkAutoswitch
then exit
;
3249 if not conIsCheatsEnabled
then exit
;
3257 if g_Game_IsClient
then Exit
;
3259 // a = true - место спавна предмета:
3260 a
:= LongBool(gGameSettings
.Options
and GAME_OPTION_WEAPONSTAY
) and arespawn
;
3265 if (FHealth
< PLAYER_HP_SOFT
) or (FFireTime
> 0) then
3267 if FHealth
< PLAYER_HP_SOFT
then IncMax(FHealth
, 10, PLAYER_HP_SOFT
);
3271 if gFlash
= 2 then Inc(FPickup
, 5);
3275 if (FHealth
< PLAYER_HP_SOFT
) or (FFireTime
> 0) then
3277 if FHealth
< PLAYER_HP_SOFT
then IncMax(FHealth
, 25, PLAYER_HP_SOFT
);
3281 if gFlash
= 2 then Inc(FPickup
, 5);
3285 if FArmor
< PLAYER_AP_SOFT
then
3287 FArmor
:= PLAYER_AP_SOFT
;
3290 if gFlash
= 2 then Inc(FPickup
, 5);
3294 if FArmor
< PLAYER_AP_LIMIT
then
3296 FArmor
:= PLAYER_AP_LIMIT
;
3299 if gFlash
= 2 then Inc(FPickup
, 5);
3303 if (FHealth
< PLAYER_HP_LIMIT
) or (FFireTime
> 0) then
3305 if FHealth
< PLAYER_HP_LIMIT
then IncMax(FHealth
, 100, PLAYER_HP_LIMIT
);
3309 if gFlash
= 2 then Inc(FPickup
, 5);
3313 if (FHealth
< PLAYER_HP_LIMIT
) or (FArmor
< PLAYER_AP_LIMIT
) or (FFireTime
> 0) then
3315 if FHealth
< PLAYER_HP_LIMIT
then
3316 FHealth
:= PLAYER_HP_LIMIT
;
3317 if FArmor
< PLAYER_AP_LIMIT
then
3318 FArmor
:= PLAYER_AP_LIMIT
;
3322 if gFlash
= 2 then Inc(FPickup
, 5);
3326 if (not FWeapon
[WEAPON_SAW
]) or ((not arespawn
) and (gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
])) then
3328 FWeapon
[WEAPON_SAW
] := True;
3330 if gFlash
= 2 then Inc(FPickup
, 5);
3331 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3334 ITEM_WEAPON_SHOTGUN1
:
3335 if (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or not FWeapon
[WEAPON_SHOTGUN1
] then
3337 // Нужно, чтобы не взять все пули сразу:
3338 if a
and FWeapon
[WEAPON_SHOTGUN1
] then Exit
;
3340 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
3341 FWeapon
[WEAPON_SHOTGUN1
] := True;
3343 if gFlash
= 2 then Inc(FPickup
, 5);
3344 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3347 ITEM_WEAPON_SHOTGUN2
:
3348 if (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or not FWeapon
[WEAPON_SHOTGUN2
] then
3350 if a
and FWeapon
[WEAPON_SHOTGUN2
] then Exit
;
3352 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
3353 FWeapon
[WEAPON_SHOTGUN2
] := True;
3355 if gFlash
= 2 then Inc(FPickup
, 5);
3356 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3359 ITEM_WEAPON_CHAINGUN
:
3360 if (FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
]) or not FWeapon
[WEAPON_CHAINGUN
] then
3362 if a
and FWeapon
[WEAPON_CHAINGUN
] then Exit
;
3364 IncMax(FAmmo
[A_BULLETS
], 50, FMaxAmmo
[A_BULLETS
]);
3365 FWeapon
[WEAPON_CHAINGUN
] := True;
3367 if gFlash
= 2 then Inc(FPickup
, 5);
3368 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3371 ITEM_WEAPON_ROCKETLAUNCHER
:
3372 if (FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
]) or not FWeapon
[WEAPON_ROCKETLAUNCHER
] then
3374 if a
and FWeapon
[WEAPON_ROCKETLAUNCHER
] then Exit
;
3376 IncMax(FAmmo
[A_ROCKETS
], 2, FMaxAmmo
[A_ROCKETS
]);
3377 FWeapon
[WEAPON_ROCKETLAUNCHER
] := True;
3379 if gFlash
= 2 then Inc(FPickup
, 5);
3380 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3384 if (FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
]) or not FWeapon
[WEAPON_PLASMA
] then
3386 if a
and FWeapon
[WEAPON_PLASMA
] then Exit
;
3388 IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
3389 FWeapon
[WEAPON_PLASMA
] := True;
3391 if gFlash
= 2 then Inc(FPickup
, 5);
3392 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3396 if (FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
]) or not FWeapon
[WEAPON_BFG
] then
3398 if a
and FWeapon
[WEAPON_BFG
] then Exit
;
3400 IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
3401 FWeapon
[WEAPON_BFG
] := True;
3403 if gFlash
= 2 then Inc(FPickup
, 5);
3404 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3407 ITEM_WEAPON_SUPERPULEMET
:
3408 if (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or not FWeapon
[WEAPON_SUPERPULEMET
] then
3410 if a
and FWeapon
[WEAPON_SUPERPULEMET
] then Exit
;
3412 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
3413 FWeapon
[WEAPON_SUPERPULEMET
] := True;
3415 if gFlash
= 2 then Inc(FPickup
, 5);
3416 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3419 ITEM_WEAPON_FLAMETHROWER
:
3420 if (FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
]) or not FWeapon
[WEAPON_FLAMETHROWER
] then
3422 if a
and FWeapon
[WEAPON_FLAMETHROWER
] then Exit
;
3424 IncMax(FAmmo
[A_FUEL
], 100, FMaxAmmo
[A_FUEL
]);
3425 FWeapon
[WEAPON_FLAMETHROWER
] := True;
3427 if gFlash
= 2 then Inc(FPickup
, 5);
3428 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3432 if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then
3434 IncMax(FAmmo
[A_BULLETS
], 10, FMaxAmmo
[A_BULLETS
]);
3437 if gFlash
= 2 then Inc(FPickup
, 5);
3440 ITEM_AMMO_BULLETS_BOX
:
3441 if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then
3443 IncMax(FAmmo
[A_BULLETS
], 50, FMaxAmmo
[A_BULLETS
]);
3446 if gFlash
= 2 then Inc(FPickup
, 5);
3450 if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then
3452 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
3455 if gFlash
= 2 then Inc(FPickup
, 5);
3458 ITEM_AMMO_SHELLS_BOX
:
3459 if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then
3461 IncMax(FAmmo
[A_SHELLS
], 25, FMaxAmmo
[A_SHELLS
]);
3464 if gFlash
= 2 then Inc(FPickup
, 5);
3468 if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then
3470 IncMax(FAmmo
[A_ROCKETS
], 1, FMaxAmmo
[A_ROCKETS
]);
3473 if gFlash
= 2 then Inc(FPickup
, 5);
3476 ITEM_AMMO_ROCKET_BOX
:
3477 if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then
3479 IncMax(FAmmo
[A_ROCKETS
], 5, FMaxAmmo
[A_ROCKETS
]);
3482 if gFlash
= 2 then Inc(FPickup
, 5);
3486 if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then
3488 IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
3491 if gFlash
= 2 then Inc(FPickup
, 5);
3495 if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then
3497 IncMax(FAmmo
[A_CELLS
], 100, FMaxAmmo
[A_CELLS
]);
3500 if gFlash
= 2 then Inc(FPickup
, 5);
3504 if FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
] then
3506 IncMax(FAmmo
[A_FUEL
], 100, FMaxAmmo
[A_FUEL
]);
3509 if gFlash
= 2 then Inc(FPickup
, 5);
3513 if not(R_ITEM_BACKPACK
in FRulez
) or
3514 (FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
]) or
3515 (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or
3516 (FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
]) or
3517 (FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
]) or
3518 (FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
]) then
3520 FMaxAmmo
[A_BULLETS
] := AmmoLimits
[1, A_BULLETS
];
3521 FMaxAmmo
[A_SHELLS
] := AmmoLimits
[1, A_SHELLS
];
3522 FMaxAmmo
[A_ROCKETS
] := AmmoLimits
[1, A_ROCKETS
];
3523 FMaxAmmo
[A_CELLS
] := AmmoLimits
[1, A_CELLS
];
3524 FMaxAmmo
[A_FUEL
] := AmmoLimits
[1, A_FUEL
];
3526 if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then
3527 IncMax(FAmmo
[A_BULLETS
], 10, FMaxAmmo
[A_BULLETS
]);
3528 if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then
3529 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
3530 if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then
3531 IncMax(FAmmo
[A_ROCKETS
], 1, FMaxAmmo
[A_ROCKETS
]);
3532 if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then
3533 IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
3534 if FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
] then
3535 IncMax(FAmmo
[A_FUEL
], 50, FMaxAmmo
[A_FUEL
]);
3537 FRulez
:= FRulez
+ [R_ITEM_BACKPACK
];
3540 if gFlash
= 2 then Inc(FPickup
, 5);
3544 if not(R_KEY_RED
in FRulez
) then
3546 Include(FRulez
, R_KEY_RED
);
3548 remove
:= (gGameSettings
.GameMode
<> GM_COOP
) and (g_Player_GetCount() < 2);
3549 if gFlash
= 2 then Inc(FPickup
, 5);
3550 if (not remove
) and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETITEM');
3554 if not(R_KEY_GREEN
in FRulez
) then
3556 Include(FRulez
, R_KEY_GREEN
);
3558 remove
:= (gGameSettings
.GameMode
<> GM_COOP
) and (g_Player_GetCount() < 2);
3559 if gFlash
= 2 then Inc(FPickup
, 5);
3560 if (not remove
) and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETITEM');
3564 if not(R_KEY_BLUE
in FRulez
) then
3566 Include(FRulez
, R_KEY_BLUE
);
3568 remove
:= (gGameSettings
.GameMode
<> GM_COOP
) and (g_Player_GetCount() < 2);
3569 if gFlash
= 2 then Inc(FPickup
, 5);
3570 if (not remove
) and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETITEM');
3574 if FMegaRulez
[MR_SUIT
] < gTime
+PLAYER_SUIT_TIME
then
3576 FMegaRulez
[MR_SUIT
] := gTime
+PLAYER_SUIT_TIME
;
3580 if gFlash
= 2 then Inc(FPickup
, 5);
3584 if FAir
< AIR_MAX
then
3589 if gFlash
= 2 then Inc(FPickup
, 5);
3594 if not (R_BERSERK
in FRulez
) then
3596 Include(FRulez
, R_BERSERK
);
3597 if allowBerserkSwitching
then
3599 FCurrWeap
:= WEAPON_KASTET
;
3601 FModel
.SetWeapon(WEAPON_KASTET
);
3606 if gFlash
= 2 then Inc(FPickup
, 5);
3608 FBerserk
:= gTime
+30000;
3613 if (FHealth
< PLAYER_HP_SOFT
) or (FFireTime
> 0) then
3615 if FHealth
< PLAYER_HP_SOFT
then FHealth
:= PLAYER_HP_SOFT
;
3616 FBerserk
:= gTime
+30000;
3624 if FMegaRulez
[MR_INVUL
] < gTime
+PLAYER_INVUL_TIME
then
3626 FMegaRulez
[MR_INVUL
] := gTime
+PLAYER_INVUL_TIME
;
3630 if gFlash
= 2 then Inc(FPickup
, 5);
3634 if (FHealth
< PLAYER_HP_LIMIT
) or (FFireTime
> 0) then
3636 if FHealth
< PLAYER_HP_LIMIT
then IncMax(FHealth
, 4, PLAYER_HP_LIMIT
);
3640 if gFlash
= 2 then Inc(FPickup
, 5);
3644 if FArmor
< PLAYER_AP_LIMIT
then
3646 IncMax(FArmor
, 5, PLAYER_AP_LIMIT
);
3649 if gFlash
= 2 then Inc(FPickup
, 5);
3653 if FJetFuel
< JET_MAX
then
3655 FJetFuel
:= JET_MAX
;
3658 if gFlash
= 2 then Inc(FPickup
, 5);
3662 if FMegaRulez
[MR_INVIS
] < gTime
+PLAYER_INVIS_TIME
then
3664 FMegaRulez
[MR_INVIS
] := gTime
+PLAYER_INVIS_TIME
;
3667 if gFlash
= 2 then Inc(FPickup
, 5);
3672 procedure TPlayer
.Touch();
3676 //FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
3679 // Бросить флаг товарищу:
3680 if gGameSettings
.GameMode
= GM_CTF
then
3685 procedure TPlayer
.Push(vx
, vy
: Integer);
3687 if (not FPhysics
) and FGhost
then
3689 FObj
.Accel
.X
:= FObj
.Accel
.X
+ vx
;
3690 FObj
.Accel
.Y
:= FObj
.Accel
.Y
+ vy
;
3691 if g_Game_IsNet
and g_Game_IsServer
then
3692 MH_SEND_PlayerPos(True, FUID
, NET_EVERYONE
);
3695 procedure TPlayer
.Reset(Force
: Boolean);
3701 FTime
[T_RESPAWN
] := 0;
3702 FTime
[T_FLAGCAP
] := 0;
3718 FSpectator
:= False;
3721 FSpectatePlayer
:= -1;
3722 FNoRespawn
:= False;
3724 FLives
:= gGameSettings
.MaxLives
;
3729 procedure TPlayer
.SoftReset();
3737 FBFGFireCounter
:= -1;
3745 SetAction(A_STAND
, True);
3748 function TPlayer
.GetRespawnPoint(): Byte;
3753 // На будущее: FSpawn - игрок уже играл и перерождается
3755 // Одиночная игра/кооператив
3756 if gGameSettings
.GameMode
in [GM_COOP
, GM_SINGLE
] then
3758 if Self
= gPlayer1
then
3760 // player 1 should try to spawn on the player 1 point
3761 if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1
) > 0 then
3762 Exit(RESPAWNPOINT_PLAYER1
)
3763 else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2
) > 0 then
3764 Exit(RESPAWNPOINT_PLAYER2
);
3766 else if Self
= gPlayer2
then
3768 // player 2 should try to spawn on the player 2 point
3769 if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2
) > 0 then
3770 Exit(RESPAWNPOINT_PLAYER2
)
3771 else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1
) > 0 then
3772 Exit(RESPAWNPOINT_PLAYER1
);
3776 // other players randomly pick either the first or the second point
3777 c
:= IfThen((Random(2) = 0), RESPAWNPOINT_PLAYER1
, RESPAWNPOINT_PLAYER2
);
3778 if g_Map_GetPointCount(c
) > 0 then
3780 // try the other one
3781 c
:= IfThen((c
= RESPAWNPOINT_PLAYER1
), RESPAWNPOINT_PLAYER2
, RESPAWNPOINT_PLAYER1
);
3782 if g_Map_GetPointCount(c
) > 0 then
3788 if gGameSettings
.GameMode
= GM_DM
then
3790 // try DM points first
3791 if g_Map_GetPointCount(RESPAWNPOINT_DM
) > 0 then
3792 Exit(RESPAWNPOINT_DM
);
3796 if gGameSettings
.GameMode
in [GM_TDM
, GM_CTF
] then
3798 // try team points first
3799 c
:= RESPAWNPOINT_DM
;
3800 if FTeam
= TEAM_RED
then
3801 c
:= RESPAWNPOINT_RED
3802 else if FTeam
= TEAM_BLUE
then
3803 c
:= RESPAWNPOINT_BLUE
;
3804 if g_Map_GetPointCount(c
) > 0 then
3808 // still haven't found a spawnpoint, try random shit
3809 Result
:= g_Map_GetRandomPointType();
3812 procedure TPlayer
.Respawn(Silent
: Boolean; Force
: Boolean = False);
3814 RespawnPoint
: TRespawnPoint
;
3820 FBFGFireCounter
:= -1;
3827 if not g_Game_IsServer
then
3831 FWantsInGame
:= True;
3832 FJustTeleported
:= True;
3835 FTime
[T_RESPAWN
] := 0;
3839 // if server changes MaxLives we gotta be ready
3840 if gGameSettings
.MaxLives
= 0 then FNoRespawn
:= False;
3842 // Еще нельзя возродиться:
3843 if FTime
[T_RESPAWN
] > gTime
then
3846 // Просрал все жизни:
3849 if not FSpectator
then Spectate(True);
3850 FWantsInGame
:= True;
3854 if (gGameSettings
.GameType
<> GT_SINGLE
) and (gGameSettings
.GameMode
<> GM_COOP
) then
3855 begin // "Своя игра"
3856 // Берсерк не сохраняется между уровнями:
3857 FRulez
:= FRulez
-[R_BERSERK
];
3859 else // "Одиночная игра"/"Кооп"
3861 // Берсерк и ключи не сохраняются между уровнями:
3862 FRulez
:= FRulez
-[R_KEY_RED
, R_KEY_GREEN
, R_KEY_BLUE
, R_BERSERK
];
3865 // Получаем точку спауна игрока:
3866 c
:= GetRespawnPoint();
3871 // Воскрешение без оружия:
3874 FHealth
:= Round(PLAYER_HP_SOFT
* (FHandicap
/ 100));
3880 for a
:= WP_FIRST
to WP_LAST
do
3882 FWeapon
[a
] := False;
3886 FWeapon
[WEAPON_PISTOL
] := True;
3887 FWeapon
[WEAPON_KASTET
] := True;
3888 FCurrWeap
:= WEAPON_PISTOL
;
3891 FModel
.SetWeapon(FCurrWeap
);
3893 for b
:= A_BULLETS
to A_HIGH
do
3896 FAmmo
[A_BULLETS
] := 50;
3898 FMaxAmmo
[A_BULLETS
] := AmmoLimits
[0, A_BULLETS
];
3899 FMaxAmmo
[A_SHELLS
] := AmmoLimits
[0, A_SHELLS
];
3900 FMaxAmmo
[A_ROCKETS
] := AmmoLimits
[0, A_SHELLS
];
3901 FMaxAmmo
[A_CELLS
] := AmmoLimits
[0, A_CELLS
];
3902 FMaxAmmo
[A_FUEL
] := AmmoLimits
[0, A_FUEL
];
3904 if (gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
]) and
3905 LongBool(gGameSettings
.Options
and GAME_OPTION_DMKEYS
) then
3906 FRulez
:= [R_KEY_RED
, R_KEY_GREEN
, R_KEY_BLUE
]
3911 // Получаем координаты точки возрождения:
3912 if not g_Map_GetPoint(c
, RespawnPoint
) then
3914 g_FatalError(_lc
[I_GAME_ERROR_GET_SPAWN
]);
3918 // Установка координат и сброс всех параметров:
3919 FObj
.X
:= RespawnPoint
.X
-PLAYER_RECT
.X
;
3920 FObj
.Y
:= RespawnPoint
.Y
-PLAYER_RECT
.Y
;
3921 FObj
.oldX
:= FObj
.X
; // don't interpolate after respawn
3922 FObj
.oldY
:= FObj
.Y
;
3928 FDirection
:= RespawnPoint
.Direction
;
3929 if FDirection
= TDirection
.D_LEFT
then
3934 SetAction(A_STAND
, True);
3935 FModel
.Direction
:= FDirection
;
3937 for a
:= Low(FTime
) to High(FTime
) do
3940 for a
:= Low(FMegaRulez
) to High(FMegaRulez
) do
3943 // Respawn invulnerability
3944 if (gGameSettings
.GameType
<> GT_SINGLE
) and (gGameSettings
.SpawnInvul
> 0) then
3946 FMegaRulez
[MR_INVUL
] := gTime
+ gGameSettings
.SpawnInvul
* 1000;
3947 FSpawnInvul
:= FMegaRulez
[MR_INVUL
];
3952 FCanJetpack
:= False;
3959 // Анимация возрождения:
3960 if (not gLoadGameMode
) and (not Silent
) then
3963 R_GFX_TELEPORT_FAST
,
3964 FObj
.X
+ PLAYER_RECT
.X
+ (PLAYER_RECT
.Width
div 2) - 32,
3965 FObj
.Y
+ PLAYER_RECT
.Y
+ (PLAYER_RECT
.Height
div 2) - 32
3970 FSpectator
:= False;
3973 FSpectatePlayer
:= -1;
3976 if (gPlayer1
= nil) and (gSpectLatchPID1
= FUID
) then
3978 if (gPlayer2
= nil) and (gSpectLatchPID2
= FUID
) then
3981 if g_Game_IsNet
then
3983 MH_SEND_PlayerPos(True, FUID
, NET_EVERYONE
);
3984 MH_SEND_PlayerStats(FUID
, NET_EVERYONE
);
3986 MH_SEND_Effect(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)-32,
3987 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2)-32,
3992 procedure TPlayer
.Spectate(NoMove
: Boolean = False);
3995 Kill(K_EXTRAHARDKILL
, FUID
, HIT_SOME
)
3996 else if (not NoMove
) then
3998 GameX
:= gMapInfo
.Width
div 2;
3999 GameY
:= gMapInfo
.Height
div 2;
4008 FWantsInGame
:= False;
4014 if Self
= gPlayer1
then
4016 gSpectLatchPID1
:= FUID
;
4019 else if Self
= gPlayer2
then
4021 gSpectLatchPID2
:= FUID
;
4026 if g_Game_IsNet
then
4027 MH_SEND_PlayerStats(FUID
);
4030 procedure TPlayer
.SwitchNoClip
;
4034 FGhost
:= not FGhost
;
4035 FPhysics
:= not FGhost
;
4047 procedure TPlayer
.Run(Direction
: TDirection
);
4051 if MAX_RUNVEL
> 8 then
4055 if Direction
= TDirection
.D_LEFT
then
4057 if FObj
.Vel
.X
> -MAX_RUNVEL
then
4058 FObj
.Vel
.X
:= FObj
.Vel
.X
- (MAX_RUNVEL
shr 3);
4061 if FObj
.Vel
.X
< MAX_RUNVEL
then
4062 FObj
.Vel
.X
:= FObj
.Vel
.X
+ (MAX_RUNVEL
shr 3);
4064 // Возможно, пинаем куски:
4065 if (FObj
.Vel
.X
<> 0) and (gGibs
<> nil) then
4067 b
:= Abs(FObj
.Vel
.X
);
4068 if b
> 1 then b
:= b
* (Random(8 div b
) + 1);
4069 for a
:= 0 to High(gGibs
) do
4071 if gGibs
[a
].alive
and
4072 g_Obj_Collide(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
+FObj
.Rect
.Height
-4,
4073 FObj
.Rect
.Width
, 8, @gGibs
[a
].Obj
) and (Random(3) = 0) then
4076 if FObj
.Vel
.X
< 0 then
4078 g_Obj_PushA(@gGibs
[a
].Obj
, b
, Random(61)+120) // налево
4082 g_Obj_PushA(@gGibs
[a
].Obj
, b
, Random(61)); // направо
4084 gGibs
[a
].positionChanged(); // this updates spatial accelerators
4092 procedure TPlayer
.SeeDown();
4094 SetAction(A_SEEDOWN
);
4096 if FDirection
= TDirection
.D_LEFT
then FAngle
:= ANGLE_LEFTDOWN
else FAngle
:= ANGLE_RIGHTDOWN
;
4098 if FIncCam
> -120 then DecMin(FIncCam
, 5, -120);
4101 procedure TPlayer
.SeeUp();
4105 if FDirection
= TDirection
.D_LEFT
then FAngle
:= ANGLE_LEFTUP
else FAngle
:= ANGLE_RIGHTUP
;
4107 if FIncCam
< 120 then IncMax(FIncCam
, 5, 120);
4110 procedure TPlayer
.SetAction(Action
: Byte; Force
: Boolean = False);
4118 A_ATTACK
: Prior
:= 2;
4119 A_SEEUP
: Prior
:= 1;
4120 A_SEEDOWN
: Prior
:= 1;
4121 A_ATTACKUP
: Prior
:= 2;
4122 A_ATTACKDOWN
: Prior
:= 2;
4127 if (Prior
> FActionPrior
) or Force
then
4128 if not ((Prior
= 2) and (FCurrWeap
= WEAPON_SAW
)) then
4130 FActionPrior
:= Prior
;
4131 FActionAnim
:= Action
;
4132 FActionForce
:= Force
;
4133 FActionChanged
:= True;
4136 if Action
in [A_ATTACK
, A_ATTACKUP
, A_ATTACKDOWN
] then FModel
.SetFire(True);
4139 function TPlayer
.StayOnStep(XInc
, YInc
: Integer): Boolean;
4141 Result
:= not g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+YInc
+PLAYER_RECT
.Y
+PLAYER_RECT
.Height
-1,
4142 PLAYER_RECT
.Width
, 1, PANEL_STEP
, False)
4143 and g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+YInc
+PLAYER_RECT
.Y
+PLAYER_RECT
.Height
,
4144 PLAYER_RECT
.Width
, 1, PANEL_STEP
, False);
4147 function TPlayer
.TeleportTo(X
, Y
: Integer; silent
: Boolean; dir
: Byte): Boolean;
4151 if g_CollideLevel(X
, Y
, PLAYER_RECT
.Width
, PLAYER_RECT
.Height
) then
4153 g_Sound_PlayExAt('SOUND_GAME_NOTELEPORT', FObj
.X
, FObj
.Y
);
4154 if g_Game_IsServer
and g_Game_IsNet
then
4155 MH_SEND_Sound(FObj
.X
, FObj
.Y
, 'SOUND_GAME_NOTELEPORT');
4159 FJustTeleported
:= True;
4163 g_Sound_PlayExAt('SOUND_GAME_TELEPORT', FObj
.X
, FObj
.Y
);
4166 R_GFX_TELEPORT_FAST
,
4167 FObj
.X
+ PLAYER_RECT
.X
+ (PLAYER_RECT
.Width
div 2) - 32,
4168 FObj
.Y
+ PLAYER_RECT
.Y
+ (PLAYER_RECT
.Height
div 2) - 32
4171 if g_Game_IsServer
and g_Game_IsNet
then
4172 MH_SEND_Effect(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)-32,
4173 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2)-32, 1,
4177 FObj
.X
:= X
-PLAYER_RECT
.X
;
4178 FObj
.Y
:= Y
-PLAYER_RECT
.Y
;
4179 FObj
.oldX
:= FObj
.X
; // don't interpolate after respawn
4180 FObj
.oldY
:= FObj
.Y
;
4181 if FAlive
and FGhost
then
4187 if not g_Game_IsNet
then
4191 SetDirection(TDirection
.D_LEFT
);
4197 SetDirection(TDirection
.D_RIGHT
);
4203 if FDirection
= TDirection
.D_RIGHT
then
4205 SetDirection(TDirection
.D_LEFT
);
4210 SetDirection(TDirection
.D_RIGHT
);
4220 R_GFX_TELEPORT_FAST
,
4221 FObj
.X
+ PLAYER_RECT
.X
+ (PLAYER_RECT
.Width
div 2) - 32,
4222 FObj
.Y
+ PLAYER_RECT
.Y
+ (PLAYER_RECT
.Height
div 2) - 32
4225 if g_Game_IsServer
and g_Game_IsNet
then
4226 MH_SEND_Effect(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)-32,
4227 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2)-32, 0,
4234 function nonz(a
: Single): Single;
4242 function TPlayer
.refreshCorpse(): Boolean;
4248 if FAlive
or FSpectator
then
4250 if (gCorpses
= nil) or (Length(gCorpses
) = 0) then
4252 for i
:= 0 to High(gCorpses
) do
4253 if gCorpses
[i
] <> nil then
4254 if gCorpses
[i
].FPlayerUID
= FUID
then
4262 function TPlayer
.getCameraObj(): TObj
;
4264 if (not FAlive
) and (not FSpectator
) and
4265 (FCorpse
>= 0) and (FCorpse
< Length(gCorpses
)) and
4266 (gCorpses
[FCorpse
] <> nil) and (gCorpses
[FCorpse
].FPlayerUID
= FUID
) then
4268 gCorpses
[FCorpse
].FObj
.slopeUpLeft
:= FObj
.slopeUpLeft
;
4269 Result
:= gCorpses
[FCorpse
].FObj
;
4277 procedure TPlayer
.PreUpdate();
4279 FSlopeOld
:= FObj
.slopeUpLeft
;
4280 FIncCamOld
:= FIncCam
;
4281 FObj
.oldX
:= FObj
.X
;
4282 FObj
.oldY
:= FObj
.Y
;
4285 procedure TPlayer
.Update();
4288 i
, ii
, wx
, wy
, xd
, yd
, k
: Integer;
4289 blockmon
, headwater
, dospawn
: Boolean;
4294 NetServer
:= g_Game_IsNet
and g_Game_IsServer
;
4295 AnyServer
:= g_Game_IsServer
;
4297 if g_Game_IsClient
and (NetInterpLevel
> 0) then
4298 DoLerp(NetInterpLevel
+ 1)
4304 if FClientID
>= 0 then
4306 FPing
:= NetClients
[FClientID
].Peer
^.lastRoundTripTime
;
4307 if NetClients
[FClientID
].Peer
^.packetsSent
> 0 then
4308 FLoss
:= Round(100*NetClients
[FClientID
].Peer
^.packetsLost
/NetClients
[FClientID
].Peer
^.packetsSent
)
4319 if FPunchAnim
.played
then
4322 if FAlive
and (gFly
or FJetpack
) then
4325 if FDirection
= TDirection
.D_LEFT
then
4330 if FAlive
and (not FGhost
) then
4332 if FKeys
[KEY_UP
].Pressed
then
4334 if FKeys
[KEY_DOWN
].Pressed
then
4338 if (not (FKeys
[KEY_UP
].Pressed
or FKeys
[KEY_DOWN
].Pressed
)) and
4341 i
:= g_basic
.Sign(FIncCam
);
4342 FIncCam
:= Abs(FIncCam
);
4343 DecMin(FIncCam
, 5, 0);
4344 FIncCam
:= FIncCam
*i
;
4347 // no need to do that each second frame, weapon queue will take care of it
4348 if FAlive
and FKeys
[KEY_NEXTWEAPON
].Pressed
and AnyServer
then NextWeapon();
4349 if FAlive
and FKeys
[KEY_PREVWEAPON
].Pressed
and AnyServer
then PrevWeapon();
4351 if gTime
mod (GAME_TICK
*2) <> 0 then
4353 if (FObj
.Vel
.X
= 0) and FAlive
then
4355 if FKeys
[KEY_LEFT
].Pressed
then
4356 Run(TDirection
.D_LEFT
);
4357 if FKeys
[KEY_RIGHT
].Pressed
then
4358 Run(TDirection
.D_RIGHT
);
4363 g_Obj_Move(@FObj
, True, True, True);
4364 positionChanged(); // this updates spatial accelerators
4370 FActionChanged
:= False;
4374 // Let alive player do some actions
4375 if FKeys
[KEY_LEFT
].Pressed
then Run(TDirection
.D_LEFT
);
4376 if FKeys
[KEY_RIGHT
].Pressed
then Run(TDirection
.D_RIGHT
);
4377 //if FKeys[KEY_NEXTWEAPON].Pressed and AnyServer then NextWeapon();
4378 //if FKeys[KEY_PREVWEAPON].Pressed and AnyServer then PrevWeapon();
4379 if FKeys
[KEY_FIRE
].Pressed
and AnyServer
then Fire()
4385 if NetServer
then MH_SEND_PlayerStats(FUID
);
4388 if FKeys
[KEY_OPEN
].Pressed
and AnyServer
then Use();
4389 if FKeys
[KEY_JUMP
].Pressed
then Jump()
4392 if AnyServer
and FJetpack
then
4396 if NetServer
then MH_SEND_PlayerStats(FUID
);
4398 FCanJetpack
:= True;
4405 for k
:= Low(FKeys
) to KEY_CHAT
-1 do
4407 if FKeys
[k
].Pressed
then
4415 if gGameSettings
.GameType
in [GT_CUSTOM
, GT_SERVER
, GT_CLIENT
] then
4418 if (FTime
[T_RESPAWN
] <= gTime
) and
4419 gGameOn
and (not FAlive
) then
4421 if (g_Player_GetCount() > 1) then
4425 gExit
:= EXIT_RESTART
;
4430 // Dead spectator actions
4433 if FKeys
[KEY_OPEN
].Pressed
and AnyServer
then Fire();
4434 if FKeys
[KEY_FIRE
].Pressed
and AnyServer
then
4438 if (FSpectatePlayer
>= High(gPlayers
)) then
4439 FSpectatePlayer
:= -1
4443 for I
:= FSpectatePlayer
+ 1 to High(gPlayers
) do
4444 if gPlayers
[I
] <> nil then
4445 if gPlayers
[I
].alive
then
4446 if gPlayers
[I
].UID
<> FUID
then
4448 FSpectatePlayer
:= I
;
4453 if not SetSpect
then FSpectatePlayer
:= -1;
4464 if FKeys
[KEY_UP
].Pressed
or FKeys
[KEY_JUMP
].Pressed
then
4466 FYTo
:= FObj
.Y
- 32;
4467 FSpectatePlayer
:= -1;
4469 if FKeys
[KEY_DOWN
].Pressed
then
4471 FYTo
:= FObj
.Y
+ 32;
4472 FSpectatePlayer
:= -1;
4474 if FKeys
[KEY_LEFT
].Pressed
then
4476 FXTo
:= FObj
.X
- 32;
4477 FSpectatePlayer
:= -1;
4479 if FKeys
[KEY_RIGHT
].Pressed
then
4481 FXTo
:= FObj
.X
+ 32;
4482 FSpectatePlayer
:= -1;
4485 if (FXTo
< -64) then
4487 else if (FXTo
> gMapInfo
.Width
+ 32) then
4488 FXTo
:= gMapInfo
.Width
+ 32;
4489 if (FYTo
< -72) then
4491 else if (FYTo
> gMapInfo
.Height
+ 32) then
4492 FYTo
:= gMapInfo
.Height
+ 32;
4497 g_Obj_Move(@FObj
, True, True, True);
4498 positionChanged(); // this updates spatial accelerators
4505 if (FSpectatePlayer
<= High(gPlayers
)) and (FSpectatePlayer
>= 0) then
4506 if gPlayers
[FSpectatePlayer
] <> nil then
4507 if gPlayers
[FSpectatePlayer
].alive
then
4509 FXTo
:= gPlayers
[FSpectatePlayer
].GameX
;
4510 FYTo
:= gPlayers
[FSpectatePlayer
].GameY
;
4514 blockmon
:= g_Map_CollidePanel(FObj
.X
+PLAYER_HEADRECT
.X
, FObj
.Y
+PLAYER_HEADRECT
.Y
,
4515 PLAYER_HEADRECT
.Width
, PLAYER_HEADRECT
.Height
,
4516 PANEL_BLOCKMON
, True);
4517 headwater
:= HeadInLiquid(0, 0);
4519 // Сопротивление воздуха:
4520 if (not FAlive
) or not (FKeys
[KEY_LEFT
].Pressed
or FKeys
[KEY_RIGHT
].Pressed
) then
4521 if FObj
.Vel
.X
<> 0 then
4522 FObj
.Vel
.X
:= z_dec(FObj
.Vel
.X
, 1);
4524 if (FLastHit
= HIT_TRAP
) and (FPain
> 90) then FPain
:= 90;
4525 DecMin(FPain
, 5, 0);
4526 DecMin(FPickup
, 1, 0);
4528 if FAlive
and (FObj
.Y
> Integer(gMapInfo
.Height
)+128) and AnyServer
then
4530 // Обнулить действия примочек, чтобы фон пропал
4531 FMegaRulez
[MR_SUIT
] := 0;
4532 FMegaRulez
[MR_INVUL
] := 0;
4533 FMegaRulez
[MR_INVIS
] := 0;
4534 Kill(K_FALLKILL
, 0, HIT_FALL
);
4541 if FCurrWeap
= WEAPON_SAW
then
4542 if not (FSawSound
.IsPlaying() or FSawSoundHit
.IsPlaying() or
4543 FSawSoundSelect
.IsPlaying()) then
4544 FSawSoundIdle
.PlayAt(FObj
.X
, FObj
.Y
);
4547 if (not FJetSoundFly
.IsPlaying()) and (not FJetSoundOn
.IsPlaying()) and
4548 (not FJetSoundOff
.IsPlaying()) then
4550 FJetSoundFly
.SetPosition(0);
4551 FJetSoundFly
.PlayAt(FObj
.X
, FObj
.Y
);
4554 for b
:= WP_FIRST
to WP_LAST
do
4555 if FReloading
[b
] > 0 then
4561 if FShellTimer
> -1 then
4562 if FShellTimer
= 0 then
4564 if FShellType
= SHELL_SHELL
then
4565 g_Player_CreateShell(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
4566 GameVelX
, GameVelY
-2, SHELL_SHELL
)
4567 else if FShellType
= SHELL_DBLSHELL
then
4569 g_Player_CreateShell(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
4570 GameVelX
+1, GameVelY
-2, SHELL_SHELL
);
4571 g_Player_CreateShell(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
4572 GameVelX
-1, GameVelY
-2, SHELL_SHELL
);
4575 end else Dec(FShellTimer
);
4577 if (FBFGFireCounter
> -1) then
4578 if FBFGFireCounter
= 0 then
4582 wx
:= FObj
.X
+WEAPONPOINT
[FDirection
].X
;
4583 wy
:= FObj
.Y
+WEAPONPOINT
[FDirection
].Y
;
4584 xd
:= wx
+IfThen(FDirection
= TDirection
.D_LEFT
, -30, 30);
4585 yd
:= wy
+firediry();
4586 g_Weapon_bfgshot(wx
, wy
, xd
, yd
, FUID
);
4587 if NetServer
then MH_SEND_PlayerFire(FUID
, WEAPON_BFG
, wx
, wy
, xd
, yd
);
4588 if (FAngle
= 0) or (FAngle
= 180) then SetAction(A_ATTACK
)
4589 else if (FAngle
= ANGLE_LEFTDOWN
) or (FAngle
= ANGLE_RIGHTDOWN
) then SetAction(A_ATTACKDOWN
)
4590 else if (FAngle
= ANGLE_LEFTUP
) or (FAngle
= ANGLE_RIGHTUP
) then SetAction(A_ATTACKUP
);
4593 FReloading
[WEAPON_BFG
] := WEAPON_RELOAD
[WEAPON_BFG
];
4594 FBFGFireCounter
:= -1;
4597 FBFGFireCounter
:= 0
4599 Dec(FBFGFireCounter
);
4601 if (FMegaRulez
[MR_SUIT
] < gTime
) and AnyServer
then
4603 b
:= g_GetAcidHit(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+PLAYER_RECT
.Y
, PLAYER_RECT
.Width
, PLAYER_RECT
.Height
);
4605 if (b
> 0) and (gTime
mod (15*GAME_TICK
) = 0) then Damage(b
, 0, 0, 0, HIT_ACID
);
4608 if (headwater
or blockmon
) then
4614 if AnyServer
then Damage(10, 0, 0, 0, HIT_WATER
);
4617 else if (FAir
mod 31 = 0) and not blockmon
then
4620 g_GFX_Bubbles(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2), FObj
.Y
+PLAYER_RECT
.Y
-4, 5+Random(6), 8, 4);
4622 if Random(2) = 0 then
4623 g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj
.X
, FObj
.Y
)
4625 g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj
.X
, FObj
.Y
);
4627 end else if FAir
< AIR_DEF
then
4630 if FFireTime
> 0 then
4632 if BodyInLiquid(0, 0) then
4637 else if FMegaRulez
[MR_SUIT
] >= gTime
then
4639 if FMegaRulez
[MR_SUIT
] = gTime
then
4646 if FFirePainTime
<= 0 then
4648 if g_Game_IsServer
then
4649 Damage(2, FFireAttacker
, 0, 0, HIT_FLAME
);
4650 FFirePainTime
:= 12 - FFireTime
div 12;
4652 FFirePainTime
:= FFirePainTime
- 1;
4653 FFireTime
:= FFireTime
- 1;
4654 if ((FFireTime
mod 33) = 0) and (FMegaRulez
[MR_INVUL
] < gTime
) then
4655 FModel
.PlaySound(MODELSOUND_PAIN
, 1, FObj
.X
, FObj
.Y
);
4656 if (FFireTime
= 0) and g_Game_IsNet
and g_Game_IsServer
then
4657 MH_SEND_PlayerStats(FUID
);
4661 if FDamageBuffer
> 0 then
4663 if FDamageBuffer
>= 9 then
4667 if FDamageBuffer
< 30 then i
:= 9
4668 else if FDamageBuffer
< 100 then i
:= 18
4672 ii
:= Round(FDamageBuffer
*FHealth
/ nonz(FArmor
*(3/4)+FHealth
));
4673 FArmor
:= FArmor
-(FDamageBuffer
-ii
);
4674 FHealth
:= FHealth
-ii
;
4677 FHealth
:= FHealth
+FArmor
;
4682 if FHealth
<= 0 then
4683 if FHealth
> -30 then Kill(K_SIMPLEKILL
, FLastSpawnerUID
, FLastHit
)
4684 else if FHealth
> -50 then Kill(K_HARDKILL
, FLastSpawnerUID
, FLastHit
)
4685 else Kill(K_EXTRAHARDKILL
, FLastSpawnerUID
, FLastHit
);
4687 if FAlive
and ((FLastHit
<> HIT_FLAME
) or (FFireTime
<= 0)) then
4689 if FDamageBuffer
<= 20 then FModel
.PlaySound(MODELSOUND_PAIN
, 1, FObj
.X
, FObj
.Y
)
4690 else if FDamageBuffer
<= 55 then FModel
.PlaySound(MODELSOUND_PAIN
, 2, FObj
.X
, FObj
.Y
)
4691 else if FDamageBuffer
<= 120 then FModel
.PlaySound(MODELSOUND_PAIN
, 3, FObj
.X
, FObj
.Y
)
4692 else FModel
.PlaySound(MODELSOUND_PAIN
, 4, FObj
.X
, FObj
.Y
);
4699 end; // if FAlive then ...
4701 if (FActionAnim
= A_PAIN
) and (FModel
.Animation
<> A_PAIN
) then
4703 FModel
.ChangeAnimation(FActionAnim
, FActionForce
);
4704 FModel
.AnimState
.MinLength
:= i
;
4705 end else FModel
.ChangeAnimation(FActionAnim
, FActionForce
and (FModel
.Animation
<> A_STAND
));
4707 if (FModel
.AnimState
.Played
or ((not FActionChanged
) and (FModel
.Animation
= A_WALK
)))
4708 then SetAction(A_STAND
, True);
4710 if not ((FModel
.Animation
= A_WALK
) and (Abs(FObj
.Vel
.X
) < 4) and not FModel
.GetFire()) then FModel
.Update
;
4712 for b
:= Low(FKeys
) to High(FKeys
) do
4713 if FKeys
[b
].Time
= 0 then FKeys
[b
].Pressed
:= False else Dec(FKeys
[b
].Time
);
4717 procedure TPlayer
.getMapBox (out x
, y
, w
, h
: Integer); inline;
4719 x
:= FObj
.X
+PLAYER_RECT
.X
;
4720 y
:= FObj
.Y
+PLAYER_RECT
.Y
;
4721 w
:= PLAYER_RECT
.Width
;
4722 h
:= PLAYER_RECT
.Height
;
4726 procedure TPlayer
.moveBy (dx
, dy
: Integer); inline;
4728 if (dx
<> 0) or (dy
<> 0) then
4737 function TPlayer
.Collide(X
, Y
: Integer; Width
, Height
: Word): Boolean;
4739 Result
:= g_Collide(FObj
.X
+PLAYER_RECT
.X
,
4740 FObj
.Y
+PLAYER_RECT
.Y
,
4747 function TPlayer
.Collide(Panel
: TPanel
): Boolean;
4749 Result
:= g_Collide(FObj
.X
+PLAYER_RECT
.X
,
4750 FObj
.Y
+PLAYER_RECT
.Y
,
4754 Panel
.Width
, Panel
.Height
);
4757 function TPlayer
.Collide(X
, Y
: Integer): Boolean;
4759 X
:= X
-FObj
.X
-PLAYER_RECT
.X
;
4760 Y
:= Y
-FObj
.Y
-PLAYER_RECT
.Y
;
4761 Result
:= (x
>= 0) and (x
<= PLAYER_RECT
.Width
) and
4762 (y
>= 0) and (y
<= PLAYER_RECT
.Height
);
4765 function g_Player_ValidName(Name
: string): Boolean;
4771 if gPlayers
= nil then Exit
;
4773 for a
:= 0 to High(gPlayers
) do
4774 if gPlayers
[a
] <> nil then
4775 if LowerCase(Name
) = LowerCase(gPlayers
[a
].FName
) then
4782 procedure TPlayer
.SetDirection(Direction
: TDirection
);
4786 d
:= FModel
.Direction
;
4788 FModel
.Direction
:= Direction
;
4789 if d
<> Direction
then FModel
.ChangeAnimation(FModel
.Animation
, True);
4791 FDirection
:= Direction
;
4794 function TPlayer
.GetKeys(): Byte;
4798 if R_KEY_RED
in FRulez
then Result
:= KEY_RED
;
4799 if R_KEY_GREEN
in FRulez
then Result
:= Result
or KEY_GREEN
;
4800 if R_KEY_BLUE
in FRulez
then Result
:= Result
or KEY_BLUE
;
4802 if FTeam
= TEAM_RED
then Result
:= Result
or KEY_REDTEAM
;
4803 if FTeam
= TEAM_BLUE
then Result
:= Result
or KEY_BLUETEAM
;
4806 procedure TPlayer
.Use();
4810 if FTime
[T_USE
] > gTime
then Exit
;
4812 g_Triggers_PressR(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+PLAYER_RECT
.Y
, PLAYER_RECT
.Width
,
4813 PLAYER_RECT
.Height
, FUID
, ACTIVATE_PLAYERPRESS
);
4815 for a
:= 0 to High(gPlayers
) do
4816 if (gPlayers
[a
] <> nil) and (gPlayers
[a
] <> Self
) and
4817 gPlayers
[a
].alive
and SameTeam(FUID
, gPlayers
[a
].FUID
) and
4818 g_Obj_Collide(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
,
4819 FObj
.Rect
.Width
, FObj
.Rect
.Height
, @gPlayers
[a
].FObj
) then
4821 gPlayers
[a
].Touch();
4822 if g_Game_IsNet
and g_Game_IsServer
then
4823 MH_SEND_GameEvent(NET_EV_PLAYER_TOUCH
, gPlayers
[a
].FUID
);
4826 FTime
[T_USE
] := gTime
+120;
4829 procedure TPlayer
.NetFire(Wpn
: Byte; X
, Y
, AX
, AY
: Integer; WID
: Integer = -1);
4833 WX
, WY
, XD
, YD
: Integer;
4845 if R_BERSERK
in FRulez
then
4847 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
4848 locobj
.X
:= FObj
.X
+FObj
.Rect
.X
;
4849 locobj
.Y
:= FObj
.Y
+FObj
.Rect
.Y
;
4852 locobj
.rect
.Width
:= 39;
4853 locobj
.rect
.Height
:= 52;
4854 locobj
.Vel
.X
:= (xd
-wx
) div 2;
4855 locobj
.Vel
.Y
:= (yd
-wy
) div 2;
4856 locobj
.Accel
.X
:= xd
-wx
;
4857 locobj
.Accel
.y
:= yd
-wy
;
4859 if g_Weapon_Hit(@locobj
, 50, FUID
, HIT_SOME
) <> 0 then
4860 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj
.X
, FObj
.Y
)
4862 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj
.X
, FObj
.Y
);
4866 FPain
:= min(FPain
+ 25, 50);
4868 g_Weapon_punch(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
, 3, FUID
);
4873 if g_Weapon_chainsaw(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
,
4874 IfThen(gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
], 9, 3), FUID
) <> 0 then
4876 FSawSoundSelect
.Stop();
4878 FSawSoundHit
.PlayAt(FObj
.X
, FObj
.Y
);
4880 else if not FSawSoundHit
.IsPlaying() then
4882 FSawSoundSelect
.Stop();
4883 FSawSound
.PlayAt(FObj
.X
, FObj
.Y
);
4890 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', GameX
, Gamey
);
4891 FFireAngle
:= FAngle
;
4893 g_Player_CreateShell(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
4894 GameVelX
, GameVelY
-2, SHELL_BULLET
);
4899 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex
, Gamey
);
4900 FFireAngle
:= FAngle
;
4903 FShellType
:= SHELL_SHELL
;
4908 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', Gamex
, Gamey
);
4909 FFireAngle
:= FAngle
;
4912 FShellType
:= SHELL_DBLSHELL
;
4917 g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', Gamex
, Gamey
);
4918 FFireAngle
:= FAngle
;
4920 g_Player_CreateShell(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
4921 GameVelX
, GameVelY
-2, SHELL_BULLET
);
4924 WEAPON_ROCKETLAUNCHER
:
4926 g_Weapon_Rocket(wx
, wy
, xd
, yd
, FUID
, WID
);
4927 FFireAngle
:= FAngle
;
4933 g_Weapon_Plasma(wx
, wy
, xd
, yd
, FUID
, WID
);
4934 FFireAngle
:= FAngle
;
4940 g_Weapon_BFGShot(wx
, wy
, xd
, yd
, FUID
, WID
);
4941 FFireAngle
:= FAngle
;
4945 WEAPON_SUPERPULEMET
:
4947 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex
, Gamey
);
4948 FFireAngle
:= FAngle
;
4950 g_Player_CreateShell(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
4951 GameVelX
, GameVelY
-2, SHELL_SHELL
);
4954 WEAPON_FLAMETHROWER
:
4956 g_Weapon_flame(wx
, wy
, xd
, yd
, FUID
, WID
);
4958 FFireAngle
:= FAngle
;
4965 if (FAngle
= 0) or (FAngle
= 180) then SetAction(A_ATTACK
)
4966 else if (FAngle
= ANGLE_LEFTDOWN
) or (FAngle
= ANGLE_RIGHTDOWN
) then SetAction(A_ATTACKDOWN
)
4967 else if (FAngle
= ANGLE_LEFTUP
) or (FAngle
= ANGLE_RIGHTUP
) then SetAction(A_ATTACKUP
);
4970 procedure TPlayer
.DoLerp(Level
: Integer = 2);
4972 if FObj
.X
<> FXTo
then FObj
.X
:= Lerp(FObj
.X
, FXTo
, Level
);
4973 if FObj
.Y
<> FYTo
then FObj
.Y
:= Lerp(FObj
.Y
, FYTo
, Level
);
4976 procedure TPlayer
.SetLerp(XTo
, YTo
: Integer);
4982 if FJustTeleported
or (NetInterpLevel
< 1) then
4986 if FJustTeleported
then
4988 FObj
.oldX
:= FObj
.X
;
4989 FObj
.oldY
:= FObj
.Y
;
4994 AX
:= Abs(FXTo
- FObj
.X
);
4995 AY
:= Abs(FYTo
- FObj
.Y
);
4996 if (AX
> 32) or (AX
<= NetInterpLevel
) then
4998 if (AY
> 32) or (AY
<= NetInterpLevel
) then
5003 function TPlayer
.FullInLift(XInc
, YInc
: Integer): Integer;
5005 if g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
,
5006 PLAYER_RECT
.Width
, PLAYER_RECT
.Height
-8,
5007 PANEL_LIFTUP
, False) then Result
:= -1
5009 if g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
,
5010 PLAYER_RECT
.Width
, PLAYER_RECT
.Height
-8,
5011 PANEL_LIFTDOWN
, False) then Result
:= 1
5015 function TPlayer
.GetFlag(Flag
: Byte): Boolean;
5022 if Flag
= FLAG_NONE
then
5025 if not g_Game_IsServer
then Exit
;
5027 // Принес чужой флаг на свою базу:
5028 if (Flag
= FTeam
) and
5029 (gFlags
[Flag
].State
= FLAG_STATE_NORMAL
) and
5030 (FFlag
<> FLAG_NONE
) then
5032 if FFlag
= FLAG_RED
then
5033 s
:= _lc
[I_PLAYER_FLAG_RED
]
5035 s
:= _lc
[I_PLAYER_FLAG_BLUE
];
5037 evtype
:= FLAG_STATE_SCORED
;
5039 ts
:= Format('%.4d', [gFlags
[FFlag
].CaptureTime
]);
5040 Insert('.', ts
, Length(ts
) + 1 - 3);
5041 g_Console_Add(Format(_lc
[I_PLAYER_FLAG_CAPTURE
], [FName
, s
, ts
]), True);
5043 g_Map_ResetFlag(FFlag
);
5044 g_Game_Message(Format(_lc
[I_MESSAGE_FLAG_CAPTURE
], [AnsiUpperCase(s
)]), 144);
5046 if ((Self
= gPlayer1
) or (Self
= gPlayer2
)
5047 or ((gPlayer1
<> nil) and (gPlayer1
.Team
= FTeam
))
5048 or ((gPlayer2
<> nil) and (gPlayer2
.Team
= FTeam
))) then
5053 if not sound_cap_flag
[a
].IsPlaying() then
5054 sound_cap_flag
[a
].Play();
5056 gTeamStat
[FTeam
].Goals
:= gTeamStat
[FTeam
].Goals
+ 1;
5059 if g_Game_IsNet
then
5061 MH_SEND_FlagEvent(evtype
, FFlag
, FUID
, False);
5065 gFlags
[FFlag
].CaptureTime
:= 0;
5070 // Подобрал свой флаг - вернул его на базу:
5071 if (Flag
= FTeam
) and
5072 (gFlags
[Flag
].State
= FLAG_STATE_DROPPED
) then
5074 if Flag
= FLAG_RED
then
5075 s
:= _lc
[I_PLAYER_FLAG_RED
]
5077 s
:= _lc
[I_PLAYER_FLAG_BLUE
];
5079 evtype
:= FLAG_STATE_RETURNED
;
5080 gFlags
[Flag
].CaptureTime
:= 0;
5082 g_Console_Add(Format(_lc
[I_PLAYER_FLAG_RETURN
], [FName
, s
]), True);
5084 g_Map_ResetFlag(Flag
);
5085 g_Game_Message(Format(_lc
[I_MESSAGE_FLAG_RETURN
], [AnsiUpperCase(s
)]), 144);
5087 if ((Self
= gPlayer1
) or (Self
= gPlayer2
)
5088 or ((gPlayer1
<> nil) and (gPlayer1
.Team
= FTeam
))
5089 or ((gPlayer2
<> nil) and (gPlayer2
.Team
= FTeam
))) then
5094 if not sound_ret_flag
[a
].IsPlaying() then
5095 sound_ret_flag
[a
].Play();
5098 if g_Game_IsNet
then
5100 MH_SEND_FlagEvent(evtype
, Flag
, FUID
, False);
5106 // Подобрал чужой флаг:
5107 if (Flag
<> FTeam
) and (FTime
[T_FLAGCAP
] <= gTime
) then
5111 if Flag
= FLAG_RED
then
5112 s
:= _lc
[I_PLAYER_FLAG_RED
]
5114 s
:= _lc
[I_PLAYER_FLAG_BLUE
];
5116 evtype
:= FLAG_STATE_CAPTURED
;
5118 g_Console_Add(Format(_lc
[I_PLAYER_FLAG_GET
], [FName
, s
]), True);
5120 g_Game_Message(Format(_lc
[I_MESSAGE_FLAG_GET
], [AnsiUpperCase(s
)]), 144);
5122 gFlags
[Flag
].State
:= FLAG_STATE_CAPTURED
;
5124 if ((Self
= gPlayer1
) or (Self
= gPlayer2
)
5125 or ((gPlayer1
<> nil) and (gPlayer1
.Team
= FTeam
))
5126 or ((gPlayer2
<> nil) and (gPlayer2
.Team
= FTeam
))) then
5131 if not sound_get_flag
[a
].IsPlaying() then
5132 sound_get_flag
[a
].Play();
5135 if g_Game_IsNet
then
5137 MH_SEND_FlagEvent(evtype
, Flag
, FUID
, False);
5143 procedure TPlayer
.SetFlag(Flag
: Byte);
5146 if FModel
<> nil then
5147 FModel
.SetFlag(FFlag
);
5150 function TPlayer
.DropFlag(Silent
: Boolean = True): Boolean;
5156 if (not g_Game_IsServer
) or (FFlag
= FLAG_NONE
) then
5158 FTime
[T_FLAGCAP
] := gTime
+ 2000;
5159 with gFlags
[FFlag
] do
5163 Direction
:= FDirection
;
5164 State
:= FLAG_STATE_DROPPED
;
5166 g_Obj_Push(@Obj
, (FObj
.Vel
.X
div 2)-2+Random(5),
5167 (FObj
.Vel
.Y
div 2)-2+Random(5));
5168 positionChanged(); // this updates spatial accelerators
5170 if FFlag
= FLAG_RED
then
5171 s
:= _lc
[I_PLAYER_FLAG_RED
]
5173 s
:= _lc
[I_PLAYER_FLAG_BLUE
];
5175 g_Console_Add(Format(_lc
[I_PLAYER_FLAG_DROP
], [FName
, s
]), True);
5176 g_Game_Message(Format(_lc
[I_MESSAGE_FLAG_DROP
], [AnsiUpperCase(s
)]), 144);
5178 if ((Self
= gPlayer1
) or (Self
= gPlayer2
)
5179 or ((gPlayer1
<> nil) and (gPlayer1
.Team
= FTeam
))
5180 or ((gPlayer2
<> nil) and (gPlayer2
.Team
= FTeam
))) then
5185 if (not Silent
) and (not sound_lost_flag
[a
].IsPlaying()) then
5186 sound_lost_flag
[a
].Play();
5188 if g_Game_IsNet
then
5189 MH_SEND_FlagEvent(FLAG_STATE_DROPPED
, Flag
, FUID
, False);
5195 procedure TPlayer
.GetSecret();
5197 if (self
= gPlayer1
) or (self
= gPlayer2
) then
5199 g_Console_Add(Format(_lc
[I_PLAYER_SECRET
], [FName
]), True);
5200 g_Sound_PlayEx('SOUND_GAME_SECRET');
5205 procedure TPlayer
.PressKey(Key
: Byte; Time
: Word = 1);
5207 Assert(Key
<= High(FKeys
));
5209 FKeys
[Key
].Pressed
:= True;
5210 FKeys
[Key
].Time
:= Time
;
5213 function TPlayer
.IsKeyPressed(K
: Byte): Boolean;
5215 Result
:= FKeys
[K
].Pressed
;
5218 procedure TPlayer
.ReleaseKeys();
5222 for a
:= Low(FKeys
) to High(FKeys
) do
5224 FKeys
[a
].Pressed
:= False;
5229 procedure TPlayer
.OnDamage(Angle
: SmallInt);
5233 function TPlayer
.firediry(): Integer;
5235 if FKeys
[KEY_UP
].Pressed
then Result
:= -42
5236 else if FKeys
[KEY_DOWN
].Pressed
then Result
:= 19
5240 procedure TPlayer
.RememberState();
5243 SavedState
: TPlayerSavedState
;
5245 SavedState
.Health
:= FHealth
;
5246 SavedState
.Armor
:= FArmor
;
5247 SavedState
.Air
:= FAir
;
5248 SavedState
.JetFuel
:= FJetFuel
;
5249 SavedState
.CurrWeap
:= FCurrWeap
;
5250 SavedState
.NextWeap
:= FNextWeap
;
5251 SavedState
.NextWeapDelay
:= FNextWeapDelay
;
5252 for i
:= Low(FWeapon
) to High(FWeapon
) do
5253 SavedState
.Weapon
[i
] := FWeapon
[i
];
5254 for i
:= Low(FAmmo
) to High(FAmmo
) do
5255 SavedState
.Ammo
[i
] := FAmmo
[i
];
5256 for i
:= Low(FMaxAmmo
) to High(FMaxAmmo
) do
5257 SavedState
.MaxAmmo
[i
] := FMaxAmmo
[i
];
5258 SavedState
.Rulez
:= FRulez
- [R_KEY_RED
, R_KEY_GREEN
, R_KEY_BLUE
];
5260 FSavedStateNum
:= -1;
5261 for i
:= Low(SavedStates
) to High(SavedStates
) do
5262 if not SavedStates
[i
].Used
then
5264 FSavedStateNum
:= i
;
5267 if FSavedStateNum
< 0 then
5269 SetLength(SavedStates
, Length(SavedStates
) + 1);
5270 FSavedStateNum
:= High(SavedStates
);
5273 SavedState
.Used
:= True;
5274 SavedStates
[FSavedStateNum
] := SavedState
;
5277 procedure TPlayer
.RecallState();
5280 SavedState
: TPlayerSavedState
;
5282 if(FSavedStateNum
< 0) or (FSavedStateNum
> High(SavedStates
)) then
5285 SavedState
:= SavedStates
[FSavedStateNum
];
5286 SavedStates
[FSavedStateNum
].Used
:= False;
5287 FSavedStateNum
:= -1;
5289 FHealth
:= SavedState
.Health
;
5290 FArmor
:= SavedState
.Armor
;
5291 FAir
:= SavedState
.Air
;
5292 FJetFuel
:= SavedState
.JetFuel
;
5293 FCurrWeap
:= SavedState
.CurrWeap
;
5294 FNextWeap
:= SavedState
.NextWeap
;
5295 FNextWeapDelay
:= SavedState
.NextWeapDelay
;
5296 for i
:= Low(FWeapon
) to High(FWeapon
) do
5297 FWeapon
[i
] := SavedState
.Weapon
[i
];
5298 for i
:= Low(FAmmo
) to High(FAmmo
) do
5299 FAmmo
[i
] := SavedState
.Ammo
[i
];
5300 for i
:= Low(FMaxAmmo
) to High(FMaxAmmo
) do
5301 FMaxAmmo
[i
] := SavedState
.MaxAmmo
[i
];
5302 FRulez
:= SavedState
.Rulez
;
5304 if gGameSettings
.GameType
= GT_SERVER
then
5305 MH_SEND_PlayerStats(FUID
);
5308 procedure TPlayer
.SaveState (st
: TStream
);
5314 utils
.writeSign(st
, 'PLYR');
5315 utils
.writeInt(st
, Byte(PLR_SAVE_VERSION
)); // version
5317 utils
.writeBool(st
, FIamBot
);
5319 utils
.writeInt(st
, Word(FUID
));
5321 utils
.writeStr(st
, FName
);
5323 utils
.writeInt(st
, Byte(FTeam
));
5325 utils
.writeBool(st
, FAlive
);
5326 // Израсходовал ли все жизни
5327 utils
.writeBool(st
, FNoRespawn
);
5329 if FDirection
= TDirection
.D_LEFT
then b
:= 1 else b
:= 2; // D_RIGHT
5330 utils
.writeInt(st
, Byte(b
));
5332 utils
.writeInt(st
, LongInt(FHealth
));
5333 // Коэффициент инвалидности
5334 utils
.writeInt(st
, LongInt(FHandicap
));
5336 utils
.writeInt(st
, Byte(FLives
));
5338 utils
.writeInt(st
, LongInt(FArmor
));
5340 utils
.writeInt(st
, LongInt(FAir
));
5342 utils
.writeInt(st
, LongInt(FJetFuel
));
5344 utils
.writeInt(st
, LongInt(FPain
));
5346 utils
.writeInt(st
, LongInt(FKills
));
5348 utils
.writeInt(st
, LongInt(FMonsterKills
));
5350 utils
.writeInt(st
, LongInt(FFrags
));
5352 utils
.writeInt(st
, Byte(FFragCombo
));
5353 // Время последнего фрага
5354 utils
.writeInt(st
, LongWord(FLastFrag
));
5356 utils
.writeInt(st
, LongInt(FDeath
));
5358 utils
.writeInt(st
, Byte(FFlag
));
5360 utils
.writeInt(st
, LongInt(FSecrets
));
5362 utils
.writeInt(st
, Byte(FCurrWeap
));
5364 utils
.writeInt(st
, Word(FNextWeap
));
5366 utils
.writeInt(st
, Byte(FNextWeapDelay
));
5367 // Время зарядки BFG
5368 utils
.writeInt(st
, SmallInt(FBFGFireCounter
));
5370 utils
.writeInt(st
, LongInt(FDamageBuffer
));
5371 // Последний ударивший
5372 utils
.writeInt(st
, Word(FLastSpawnerUID
));
5373 // Тип последнего полученного урона
5374 utils
.writeInt(st
, Byte(FLastHit
));
5376 Obj_SaveState(st
, @FObj
);
5377 // Текущее количество патронов
5378 for i
:= A_BULLETS
to A_HIGH
do utils
.writeInt(st
, Word(FAmmo
[i
]));
5379 // Максимальное количество патронов
5380 for i
:= A_BULLETS
to A_HIGH
do utils
.writeInt(st
, Word(FMaxAmmo
[i
]));
5382 for i
:= WP_FIRST
to WP_LAST
do utils
.writeBool(st
, FWeapon
[i
]);
5383 // Время перезарядки оружия
5384 for i
:= WP_FIRST
to WP_LAST
do utils
.writeInt(st
, Word(FReloading
[i
]));
5386 utils
.writeBool(st
, (R_ITEM_BACKPACK
in FRulez
));
5387 // Наличие красного ключа
5388 utils
.writeBool(st
, (R_KEY_RED
in FRulez
));
5389 // Наличие зеленого ключа
5390 utils
.writeBool(st
, (R_KEY_GREEN
in FRulez
));
5391 // Наличие синего ключа
5392 utils
.writeBool(st
, (R_KEY_BLUE
in FRulez
));
5394 utils
.writeBool(st
, (R_BERSERK
in FRulez
));
5395 // Время действия специальных предметов
5396 for i
:= MR_SUIT
to MR_MAX
do utils
.writeInt(st
, LongWord(FMegaRulez
[i
]));
5397 // Время до повторного респауна, смены оружия, исользования, захвата флага
5398 for i
:= T_RESPAWN
to T_FLAGCAP
do utils
.writeInt(st
, LongWord(FTime
[i
]));
5400 utils
.writeStr(st
, FModel
.GetName());
5402 utils
.writeInt(st
, Byte(FColor
.R
));
5403 utils
.writeInt(st
, Byte(FColor
.G
));
5404 utils
.writeInt(st
, Byte(FColor
.B
));
5408 procedure TPlayer
.LoadState (st
: TStream
);
5417 if not utils
.checkSign(st
, 'PLYR') then raise XStreamError
.Create('invalid player signature');
5418 if (utils
.readByte(st
) <> PLR_SAVE_VERSION
) then raise XStreamError
.Create('invalid player version');
5420 FIamBot
:= utils
.readBool(st
);
5422 FUID
:= utils
.readWord(st
);
5424 str
:= utils
.readStr(st
);
5425 if (self
<> gPlayer1
) and (self
<> gPlayer2
) then FName
:= str
;
5427 FTeam
:= utils
.readByte(st
);
5429 FAlive
:= utils
.readBool(st
);
5430 // Израсходовал ли все жизни
5431 FNoRespawn
:= utils
.readBool(st
);
5433 b
:= utils
.readByte(st
);
5434 if b
= 1 then FDirection
:= TDirection
.D_LEFT
else FDirection
:= TDirection
.D_RIGHT
; // b = 2
5436 FHealth
:= utils
.readLongInt(st
);
5437 // Коэффициент инвалидности
5438 FHandicap
:= utils
.readLongInt(st
);
5440 FLives
:= utils
.readByte(st
);
5442 FArmor
:= utils
.readLongInt(st
);
5444 FAir
:= utils
.readLongInt(st
);
5446 FJetFuel
:= utils
.readLongInt(st
);
5448 FPain
:= utils
.readLongInt(st
);
5450 FKills
:= utils
.readLongInt(st
);
5452 FMonsterKills
:= utils
.readLongInt(st
);
5454 FFrags
:= utils
.readLongInt(st
);
5456 FFragCombo
:= utils
.readByte(st
);
5457 // Время последнего фрага
5458 FLastFrag
:= utils
.readLongWord(st
);
5460 FDeath
:= utils
.readLongInt(st
);
5462 FFlag
:= utils
.readByte(st
);
5464 FSecrets
:= utils
.readLongInt(st
);
5466 FCurrWeap
:= utils
.readByte(st
);
5468 FNextWeap
:= utils
.readWord(st
);
5470 FNextWeapDelay
:= utils
.readByte(st
);
5471 // Время зарядки BFG
5472 FBFGFireCounter
:= utils
.readSmallInt(st
);
5474 FDamageBuffer
:= utils
.readLongInt(st
);
5475 // Последний ударивший
5476 FLastSpawnerUID
:= utils
.readWord(st
);
5477 // Тип последнего полученного урона
5478 FLastHit
:= utils
.readByte(st
);
5480 Obj_LoadState(@FObj
, st
);
5481 // Текущее количество патронов
5482 for i
:= A_BULLETS
to A_HIGH
do FAmmo
[i
] := utils
.readWord(st
);
5483 // Максимальное количество патронов
5484 for i
:= A_BULLETS
to A_HIGH
do FMaxAmmo
[i
] := utils
.readWord(st
);
5486 for i
:= WP_FIRST
to WP_LAST
do FWeapon
[i
] := utils
.readBool(st
);
5487 // Время перезарядки оружия
5488 for i
:= WP_FIRST
to WP_LAST
do FReloading
[i
] := utils
.readWord(st
);
5490 if utils
.readBool(st
) then Include(FRulez
, R_ITEM_BACKPACK
);
5491 // Наличие красного ключа
5492 if utils
.readBool(st
) then Include(FRulez
, R_KEY_RED
);
5493 // Наличие зеленого ключа
5494 if utils
.readBool(st
) then Include(FRulez
, R_KEY_GREEN
);
5495 // Наличие синего ключа
5496 if utils
.readBool(st
) then Include(FRulez
, R_KEY_BLUE
);
5498 if utils
.readBool(st
) then Include(FRulez
, R_BERSERK
);
5499 // Время действия специальных предметов
5500 for i
:= MR_SUIT
to MR_MAX
do FMegaRulez
[i
] := utils
.readLongWord(st
);
5501 // Время до повторного респауна, смены оружия, исользования, захвата флага
5502 for i
:= T_RESPAWN
to T_FLAGCAP
do FTime
[i
] := utils
.readLongWord(st
);
5504 str
:= utils
.readStr(st
);
5506 FColor
.R
:= utils
.readByte(st
);
5507 FColor
.G
:= utils
.readByte(st
);
5508 FColor
.B
:= utils
.readByte(st
);
5509 if (self
= gPlayer1
) then
5511 str
:= gPlayer1Settings
.Model
;
5512 FColor
:= gPlayer1Settings
.Color
;
5514 else if (self
= gPlayer2
) then
5516 str
:= gPlayer2Settings
.Model
;
5517 FColor
:= gPlayer2Settings
.Color
;
5519 // Обновляем модель игрока
5521 if gGameSettings
.GameMode
in [GM_TDM
, GM_CTF
] then
5522 FModel
.Color
:= TEAMCOLOR
[FTeam
]
5524 FModel
.Color
:= FColor
;
5528 procedure TPlayer
.AllRulez(Health
: Boolean);
5534 FHealth
:= PLAYER_HP_LIMIT
;
5535 FArmor
:= PLAYER_AP_LIMIT
;
5539 for a
:= WP_FIRST
to WP_LAST
do FWeapon
[a
] := True;
5540 for a
:= A_BULLETS
to A_HIGH
do FAmmo
[a
] := 30000;
5541 FRulez
:= FRulez
+[R_KEY_RED
, R_KEY_GREEN
, R_KEY_BLUE
];
5544 procedure TPlayer
.RestoreHealthArmor();
5546 FHealth
:= PLAYER_HP_LIMIT
;
5547 FArmor
:= PLAYER_AP_LIMIT
;
5550 procedure TPlayer
.FragCombo();
5554 if (gGameSettings
.GameMode
in [GM_COOP
, GM_SINGLE
]) or g_Game_IsClient
then
5556 if gTime
- FLastFrag
< FRAG_COMBO_TIME
then
5558 if FFragCombo
< 5 then
5560 Param
:= FUID
or (FFragCombo
shl 16);
5561 if (FComboEvnt
>= Low(gDelayedEvents
)) and
5562 (FComboEvnt
<= High(gDelayedEvents
)) and
5563 gDelayedEvents
[FComboEvnt
].Pending
and
5564 (gDelayedEvents
[FComboEvnt
].DEType
= DE_KILLCOMBO
) and
5565 (gDelayedEvents
[FComboEvnt
].DENum
and $FFFF = FUID
) then
5567 gDelayedEvents
[FComboEvnt
].Time
:= gTime
+ 500;
5568 gDelayedEvents
[FComboEvnt
].DENum
:= Param
;
5571 FComboEvnt
:= g_Game_DelayEvent(DE_KILLCOMBO
, 500, Param
);
5579 procedure TPlayer
.GiveItem(ItemType
: Byte);
5583 if FMegaRulez
[MR_SUIT
] < gTime
+PLAYER_SUIT_TIME
then
5585 FMegaRulez
[MR_SUIT
] := gTime
+PLAYER_SUIT_TIME
;
5589 if FAir
< AIR_MAX
then
5596 if not (R_BERSERK
in FRulez
) then
5598 Include(FRulez
, R_BERSERK
);
5599 if FBFGFireCounter
< 1 then
5601 FCurrWeap
:= WEAPON_KASTET
;
5603 FModel
.SetWeapon(WEAPON_KASTET
);
5607 FBerserk
:= gTime
+30000;
5609 if FHealth
< PLAYER_HP_SOFT
then
5611 FHealth
:= PLAYER_HP_SOFT
;
5612 FBerserk
:= gTime
+30000;
5617 if FMegaRulez
[MR_INVUL
] < gTime
+PLAYER_INVUL_TIME
then
5619 FMegaRulez
[MR_INVUL
] := gTime
+PLAYER_INVUL_TIME
;
5624 if FMegaRulez
[MR_INVIS
] < gTime
+PLAYER_INVIS_TIME
then
5626 FMegaRulez
[MR_INVIS
] := gTime
+PLAYER_INVIS_TIME
;
5630 if FJetFuel
< JET_MAX
then
5632 FJetFuel
:= JET_MAX
;
5635 ITEM_MEDKIT_SMALL
: if FHealth
< PLAYER_HP_SOFT
then IncMax(FHealth
, 10, PLAYER_HP_SOFT
);
5636 ITEM_MEDKIT_LARGE
: if FHealth
< PLAYER_HP_SOFT
then IncMax(FHealth
, 25, PLAYER_HP_SOFT
);
5638 ITEM_ARMOR_GREEN
: if FArmor
< PLAYER_AP_SOFT
then FArmor
:= PLAYER_AP_SOFT
;
5639 ITEM_ARMOR_BLUE
: if FArmor
< PLAYER_AP_LIMIT
then FArmor
:= PLAYER_AP_LIMIT
;
5641 ITEM_SPHERE_BLUE
: if FHealth
< PLAYER_HP_LIMIT
then IncMax(FHealth
, 100, PLAYER_HP_LIMIT
);
5643 if (FHealth
< PLAYER_HP_LIMIT
) or (FArmor
< PLAYER_AP_LIMIT
) then
5645 if FHealth
< PLAYER_HP_LIMIT
then FHealth
:= PLAYER_HP_LIMIT
;
5646 if FArmor
< PLAYER_AP_LIMIT
then FArmor
:= PLAYER_AP_LIMIT
;
5649 ITEM_WEAPON_SAW
: FWeapon
[WEAPON_SAW
] := True;
5650 ITEM_WEAPON_SHOTGUN1
: FWeapon
[WEAPON_SHOTGUN1
] := True;
5651 ITEM_WEAPON_SHOTGUN2
: FWeapon
[WEAPON_SHOTGUN2
] := True;
5652 ITEM_WEAPON_CHAINGUN
: FWeapon
[WEAPON_CHAINGUN
] := True;
5653 ITEM_WEAPON_ROCKETLAUNCHER
: FWeapon
[WEAPON_ROCKETLAUNCHER
] := True;
5654 ITEM_WEAPON_PLASMA
: FWeapon
[WEAPON_PLASMA
] := True;
5655 ITEM_WEAPON_BFG
: FWeapon
[WEAPON_BFG
] := True;
5656 ITEM_WEAPON_SUPERPULEMET
: FWeapon
[WEAPON_SUPERPULEMET
] := True;
5657 ITEM_WEAPON_FLAMETHROWER
: FWeapon
[WEAPON_FLAMETHROWER
] := True;
5659 ITEM_AMMO_BULLETS
: if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then IncMax(FAmmo
[A_BULLETS
], 10, FMaxAmmo
[A_BULLETS
]);
5660 ITEM_AMMO_BULLETS_BOX
: if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then IncMax(FAmmo
[A_BULLETS
], 50, FMaxAmmo
[A_BULLETS
]);
5661 ITEM_AMMO_SHELLS
: if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
5662 ITEM_AMMO_SHELLS_BOX
: if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then IncMax(FAmmo
[A_SHELLS
], 25, FMaxAmmo
[A_SHELLS
]);
5663 ITEM_AMMO_ROCKET
: if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then IncMax(FAmmo
[A_ROCKETS
], 1, FMaxAmmo
[A_ROCKETS
]);
5664 ITEM_AMMO_ROCKET_BOX
: if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then IncMax(FAmmo
[A_ROCKETS
], 5, FMaxAmmo
[A_ROCKETS
]);
5665 ITEM_AMMO_CELL
: if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
5666 ITEM_AMMO_CELL_BIG
: if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then IncMax(FAmmo
[A_CELLS
], 100, FMaxAmmo
[A_CELLS
]);
5667 ITEM_AMMO_FUELCAN
: if FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
] then IncMax(FAmmo
[A_FUEL
], 100, FMaxAmmo
[A_FUEL
]);
5670 if (FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
]) or
5671 (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or
5672 (FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
]) or
5673 (FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
]) or
5674 (FMaxAmmo
[A_FUEL
] < AmmoLimits
[1, A_FUEL
]) then
5676 FMaxAmmo
[A_BULLETS
] := AmmoLimits
[1, A_BULLETS
];
5677 FMaxAmmo
[A_SHELLS
] := AmmoLimits
[1, A_SHELLS
];
5678 FMaxAmmo
[A_ROCKETS
] := AmmoLimits
[1, A_ROCKETS
];
5679 FMaxAmmo
[A_CELLS
] := AmmoLimits
[1, A_CELLS
];
5680 FMaxAmmo
[A_FUEL
] := AmmoLimits
[1, A_FUEL
];
5682 if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then IncMax(FAmmo
[A_BULLETS
], 10, FMaxAmmo
[A_BULLETS
]);
5683 if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
5684 if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then IncMax(FAmmo
[A_ROCKETS
], 1, FMaxAmmo
[A_ROCKETS
]);
5685 if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
5687 FRulez
:= FRulez
+ [R_ITEM_BACKPACK
];
5690 ITEM_KEY_RED
: if not (R_KEY_RED
in FRulez
) then Include(FRulez
, R_KEY_RED
);
5691 ITEM_KEY_GREEN
: if not (R_KEY_GREEN
in FRulez
) then Include(FRulez
, R_KEY_GREEN
);
5692 ITEM_KEY_BLUE
: if not (R_KEY_BLUE
in FRulez
) then Include(FRulez
, R_KEY_BLUE
);
5694 ITEM_BOTTLE
: if FHealth
< PLAYER_HP_LIMIT
then IncMax(FHealth
, 4, PLAYER_HP_LIMIT
);
5695 ITEM_HELMET
: if FArmor
< PLAYER_AP_LIMIT
then IncMax(FArmor
, 5, PLAYER_AP_LIMIT
);
5700 if g_Game_IsNet
and g_Game_IsServer
then
5701 MH_SEND_PlayerStats(FUID
);
5704 procedure TPlayer
.FlySmoke(Times
: DWORD
= 1);
5707 if (Random(5) = 1) and (Times
= 1) then
5710 if BodyInLiquid(0, 0) then
5713 g_GFX_Bubbles(Obj
.X
+Obj
.Rect
.X
+(Obj
.Rect
.Width
div 2)+Random(3)-1,
5714 Obj
.Y
+Obj
.Rect
.Height
+8, 1, 8, 4);
5716 if Random(2) = 0 then
5717 g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj
.X
, FObj
.Y
)
5719 g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj
.X
, FObj
.Y
);
5723 for i
:= 1 to Times
do
5728 Obj
.X
+Obj
.Rect
.X
+Random(Obj
.Rect
.Width
+Times
*2)-(R_GFX_SMOKE_WIDTH
div 2),
5729 Obj
.Y
+Obj
.Rect
.Height
-4+Random(8+Times
*2)
5735 procedure TPlayer
.OnFireFlame(Times
: DWORD
= 1);
5738 if (Random(10) = 1) and (Times
= 1) then
5741 for i
:= 1 to Times
do
5746 Obj
.X
+Obj
.Rect
.X
+Random(Obj
.Rect
.Width
+Times
*2)-(R_GFX_FLAME_WIDTH
div 2),
5747 Obj
.Y
+8+Random(8+Times
*2)
5753 procedure TPlayer
.PauseSounds(Enable
: Boolean);
5755 FSawSound
.Pause(Enable
);
5756 FSawSoundIdle
.Pause(Enable
);
5757 FSawSoundHit
.Pause(Enable
);
5758 FSawSoundSelect
.Pause(Enable
);
5759 FFlameSoundOn
.Pause(Enable
);
5760 FFlameSoundOff
.Pause(Enable
);
5761 FFlameSoundWork
.Pause(Enable
);
5762 FJetSoundFly
.Pause(Enable
);
5763 FJetSoundOn
.Pause(Enable
);
5764 FJetSoundOff
.Pause(Enable
);
5769 constructor TCorpse
.Create(X
, Y
: Integer; ModelName
: String; aMess
: Boolean);
5774 FObj
.Rect
:= PLAYER_CORPSERECT
;
5776 FModel
:= g_PlayerModel_Get(ModelName
);
5780 FState
:= CORPSE_STATE_MESS
;
5781 FModel
.ChangeAnimation(A_DIE2
);
5785 FState
:= CORPSE_STATE_NORMAL
;
5786 FModel
.ChangeAnimation(A_DIE1
);
5790 destructor TCorpse
.Destroy();
5796 function TCorpse
.ObjPtr (): PObj
; inline; begin result
:= @FObj
; end;
5798 procedure TCorpse
.positionChanged (); inline; begin end;
5800 procedure TCorpse
.moveBy (dx
, dy
: Integer); inline;
5802 if (dx
<> 0) or (dy
<> 0) then
5811 procedure TCorpse
.getMapBox (out x
, y
, w
, h
: Integer); inline;
5813 x
:= FObj
.X
+PLAYER_CORPSERECT
.X
;
5814 y
:= FObj
.Y
+PLAYER_CORPSERECT
.Y
;
5815 w
:= PLAYER_CORPSERECT
.Width
;
5816 h
:= PLAYER_CORPSERECT
.Height
;
5820 procedure TCorpse
.Damage(Value
: Word; SpawnerUID
: Word; vx
, vy
: Integer);
5822 var Blood
: TModelBlood
;
5825 if FState
= CORPSE_STATE_REMOVEME
then
5828 FDamage
:= FDamage
+ Value
;
5830 if FDamage
> 150 then
5832 if FModel
<> nil then
5834 FState
:= CORPSE_STATE_REMOVEME
;
5836 g_Player_CreateGibs(
5837 FObj
.X
+ FObj
.Rect
.X
+ (FObj
.Rect
.Width
div 2),
5838 FObj
.Y
+ FObj
.Rect
.Y
+ (FObj
.Rect
.Height
div 2),
5843 // Звук мяса от трупа:
5844 FModel
.PlaySound(MODELSOUND_DIE
, 5, FObj
.X
, FObj
.Y
);
5847 if (gBodyKillEvent
<> -1) and gDelayedEvents
[gBodyKillEvent
].Pending
then
5848 gDelayedEvents
[gBodyKillEvent
].Pending
:= False;
5849 gBodyKillEvent
:= g_Game_DelayEvent(DE_BODYKILL
, 1050, SpawnerUID
);
5857 FObj
.Vel
.X
:= FObj
.Vel
.X
+ vx
;
5858 FObj
.Vel
.Y
:= FObj
.Vel
.Y
+ vy
;
5860 Blood
:= FModel
.GetBlood();
5861 g_GFX_Blood(FObj
.X
+PLAYER_CORPSERECT
.X
+(PLAYER_CORPSERECT
.Width
div 2),
5862 FObj
.Y
+PLAYER_CORPSERECT
.Y
+(PLAYER_CORPSERECT
.Height
div 2),
5863 Value
, vx
, vy
, 16, (PLAYER_CORPSERECT
.Height
*2) div 3,
5864 Blood
.R
, Blood
.G
, Blood
.B
, Blood
.Kind
);
5869 procedure TCorpse
.Update();
5873 if FState
= CORPSE_STATE_REMOVEME
then
5876 FObj
.oldX
:= FObj
.X
;
5877 FObj
.oldY
:= FObj
.Y
;
5879 if gTime
mod (GAME_TICK
*2) <> 0 then
5881 g_Obj_Move(@FObj
, True, True, True);
5882 positionChanged(); // this updates spatial accelerators
5886 // Сопротивление воздуха для трупа:
5887 FObj
.Vel
.X
:= z_dec(FObj
.Vel
.X
, 1);
5889 st
:= g_Obj_Move(@FObj
, True, True, True);
5890 positionChanged(); // this updates spatial accelerators
5892 if WordBool(st
and MOVE_FALLOUT
) then
5894 FState
:= CORPSE_STATE_REMOVEME
;
5898 if FModel
<> nil then
5903 procedure TCorpse
.SaveState (st
: TStream
);
5909 utils
.writeSign(st
, 'CORP');
5910 utils
.writeInt(st
, Byte(0));
5912 utils
.writeInt(st
, Byte(FState
));
5914 utils
.writeInt(st
, Byte(FDamage
));
5916 utils
.writeInt(st
, Byte(FModel
.Color
.R
));
5917 utils
.writeInt(st
, Byte(FModel
.Color
.G
));
5918 utils
.writeInt(st
, Byte(FModel
.Color
.B
));
5920 Obj_SaveState(st
, @FObj
);
5921 utils
.writeInt(st
, Word(FPlayerUID
));
5923 anim
:= (FModel
<> nil);
5924 utils
.writeBool(st
, anim
);
5925 if anim
then FModel
.AnimState
.SaveState(st
, 0, False);
5926 // animation for mask (same as animation, compat with older saves)
5927 anim
:= (FModel
<> nil);
5928 utils
.writeBool(st
, anim
);
5929 if anim
then FModel
.AnimState
.SaveState(st
, 0, False);
5933 procedure TCorpse
.LoadState (st
: TStream
);
5934 var anim
, blending
: Boolean; r
, g
, b
, alpha
: Byte; stub
: TAnimationState
;
5939 if not utils
.checkSign(st
, 'CORP') then raise XStreamError
.Create('invalid corpse signature');
5940 if (utils
.readByte(st
) <> 0) then raise XStreamError
.Create('invalid corpse version');
5942 FState
:= utils
.readByte(st
);
5944 FDamage
:= utils
.readByte(st
);
5946 r
:= utils
.readByte(st
);
5947 g
:= utils
.readByte(st
);
5948 b
:= utils
.readByte(st
);
5949 FModel
.SetColor(r
, g
, b
);
5951 Obj_LoadState(@FObj
, st
);
5952 FPlayerUID
:= utils
.readWord(st
);
5954 stub
:= TAnimationState
.Create(False, 0, 0);
5955 anim
:= utils
.readBool(st
);
5958 stub
.LoadState(st
, alpha
, blending
);
5959 FModel
.AnimState
.CurrentFrame
:= Min(stub
.CurrentFrame
, FModel
.AnimState
.Length
);
5966 // animation for mask (same as animation, compat with older saves)
5967 anim
:= utils
.readBool(st
);
5968 if anim
then stub
.LoadState(st
, alpha
, blending
);
5974 constructor TBot
.Create();
5981 FSpectator
:= False;
5988 for a
:= WP_FIRST
to WP_LAST
do
5990 FDifficult
.WeaponPrior
[a
] := WEAPON_PRIOR1
[a
];
5991 FDifficult
.CloseWeaponPrior
[a
] := WEAPON_PRIOR2
[a
];
5992 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
5996 destructor TBot
.Destroy();
5999 inherited Destroy();
6002 procedure TBot
.Respawn(Silent
: Boolean; Force
: Boolean = False);
6004 inherited Respawn(Silent
, Force
);
6007 FSelectedWeapon
:= FCurrWeap
;
6012 procedure TBot
.UpdateCombat();
6025 TTargetRecord
= array of TTarget
;
6027 function Compare(a
, b
: TTarget
): Integer;
6029 if a
.Line
and not b
.Line
then // A на линии огня
6032 if not a
.Line
and b
.Line
then // B на линии огня
6034 else // И A, и B на линии или не на линии огня
6035 if (a
.Line
and b
.Line
) or ((not a
.Line
) and (not b
.Line
)) then
6037 if a
.Dist
> b
.Dist
then // B ближе
6039 else // A ближе или равноудаленно с B
6042 else // Странно -> A
6047 a
, x1
, y1
, x2
, y2
: Integer;
6048 targets
: TTargetRecord
;
6050 Target
, BestTarget
: TTarget
;
6051 firew
, fireh
: Integer;
6055 vsPlayer
, vsMonster
, ok
: Boolean;
6058 function monsUpdate (mon
: TMonster
): Boolean;
6060 result
:= false; // don't stop
6061 if mon
.alive
and (mon
.MonsterType
<> MONSTER_BARREL
) then
6063 if not TargetOnScreen(mon
.Obj
.X
+mon
.Obj
.Rect
.X
, mon
.Obj
.Y
+mon
.Obj
.Rect
.Y
) then exit
;
6065 x2
:= mon
.Obj
.X
+mon
.Obj
.Rect
.X
+(mon
.Obj
.Rect
.Width
div 2);
6066 y2
:= mon
.Obj
.Y
+mon
.Obj
.Rect
.Y
+(mon
.Obj
.Rect
.Height
div 2);
6068 // Если монстр на экране и не прикрыт стеной
6069 if g_TraceVector(x1
, y1
, x2
, y2
) then
6071 // Добавляем к списку возможных целей
6072 SetLength(targets
, Length(targets
)+1);
6073 with targets
[High(targets
)] do
6080 Rect
:= mon
.Obj
.Rect
;
6081 Dist
:= g_PatchLength(x1
, y1
, x2
, y2
);
6082 Line
:= (y1
+4 < Target
.Y
+ mon
.Obj
.Rect
.Y
+ mon
.Obj
.Rect
.Height
) and
6083 (y1
-4 > Target
.Y
+ mon
.Obj
.Rect
.Y
);
6092 vsPlayer
:= LongBool(gGameSettings
.Options
and GAME_OPTION_BOTVSPLAYER
);
6093 vsMonster
:= LongBool(gGameSettings
.Options
and GAME_OPTION_BOTVSMONSTER
);
6095 // Если текущее оружие не то, что нужно, то меняем:
6096 if FCurrWeap
<> FSelectedWeapon
then
6099 // Если нужно стрелять и нужное оружие, то нажать "Стрелять":
6100 if (GetAIFlag('NEEDFIRE') <> '') and (FCurrWeap
= FSelectedWeapon
) then
6102 RemoveAIFlag('NEEDFIRE');
6105 WEAPON_PLASMA
, WEAPON_SUPERPULEMET
, WEAPON_CHAINGUN
: PressKey(KEY_FIRE
, 20);
6106 WEAPON_SAW
, WEAPON_KASTET
, WEAPON_FLAMETHROWER
: PressKey(KEY_FIRE
, 40);
6107 else PressKey(KEY_FIRE
);
6111 // Координаты ствола:
6112 x1
:= FObj
.X
+ WEAPONPOINT
[FDirection
].X
;
6113 y1
:= FObj
.Y
+ WEAPONPOINT
[FDirection
].Y
;
6115 Target
.UID
:= FTargetUID
;
6118 if Target
.UID
<> 0 then
6119 begin // Цель есть - настраиваем
6120 if (g_GetUIDType(Target
.UID
) = UID_PLAYER
) and
6123 tpla
:= g_Player_Get(Target
.UID
);
6127 if (@FObj
) <> nil then
6134 Target
.cX
:= Target
.X
+ PLAYER_RECT_CX
;
6135 Target
.cY
:= Target
.Y
+ PLAYER_RECT_CY
;
6136 Target
.Rect
:= PLAYER_RECT
;
6137 Target
.Visible
:= g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
);
6138 Target
.Line
:= (y1
+4 < Target
.Y
+PLAYER_RECT
.Y
+PLAYER_RECT
.Height
) and
6139 (y1
-4 > Target
.Y
+PLAYER_RECT
.Y
);
6140 Target
.IsPlayer
:= True;
6144 if (g_GetUIDType(Target
.UID
) = UID_MONSTER
) and
6147 mon
:= g_Monsters_ByUID(Target
.UID
);
6150 Target
.X
:= mon
.Obj
.X
;
6151 Target
.Y
:= mon
.Obj
.Y
;
6153 Target
.cX
:= Target
.X
+ mon
.Obj
.Rect
.X
+ (mon
.Obj
.Rect
.Width
div 2);
6154 Target
.cY
:= Target
.Y
+ mon
.Obj
.Rect
.Y
+ (mon
.Obj
.Rect
.Height
div 2);
6155 Target
.Rect
:= mon
.Obj
.Rect
;
6156 Target
.Visible
:= g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
);
6157 Target
.Line
:= (y1
+4 < Target
.Y
+ mon
.Obj
.Rect
.Y
+ mon
.Obj
.Rect
.Height
) and
6158 (y1
-4 > Target
.Y
+ mon
.Obj
.Rect
.Y
);
6159 Target
.IsPlayer
:= False;
6166 begin // Цели нет - обнуляем
6171 Target
.Visible
:= False;
6172 Target
.Line
:= False;
6173 Target
.IsPlayer
:= False;
6178 // Если цель не видима или не на линии огня, то ищем все возможные цели:
6179 if (not Target
.Line
) or (not Target
.Visible
) then
6183 for a
:= 0 to High(gPlayers
) do
6184 if (gPlayers
[a
] <> nil) and (gPlayers
[a
].alive
) and
6185 (gPlayers
[a
].FUID
<> FUID
) and
6186 (not SameTeam(FUID
, gPlayers
[a
].FUID
)) and
6187 (not gPlayers
[a
].NoTarget
) and
6188 (gPlayers
[a
].FMegaRulez
[MR_INVIS
] < gTime
) then
6190 if not TargetOnScreen(gPlayers
[a
].FObj
.X
+ PLAYER_RECT
.X
,
6191 gPlayers
[a
].FObj
.Y
+ PLAYER_RECT
.Y
) then
6194 x2
:= gPlayers
[a
].FObj
.X
+ PLAYER_RECT_CX
;
6195 y2
:= gPlayers
[a
].FObj
.Y
+ PLAYER_RECT_CY
;
6197 // Если игрок на экране и не прикрыт стеной:
6198 if g_TraceVector(x1
, y1
, x2
, y2
) then
6200 // Добавляем к списку возможных целей:
6201 SetLength(targets
, Length(targets
)+1);
6202 with targets
[High(targets
)] do
6204 UID
:= gPlayers
[a
].FUID
;
6205 X
:= gPlayers
[a
].FObj
.X
;
6206 Y
:= gPlayers
[a
].FObj
.Y
;
6209 Rect
:= PLAYER_RECT
;
6210 Dist
:= g_PatchLength(x1
, y1
, x2
, y2
);
6211 Line
:= (y1
+4 < Target
.Y
+PLAYER_RECT
.Y
+PLAYER_RECT
.Height
) and
6212 (y1
-4 > Target
.Y
+PLAYER_RECT
.Y
);
6220 if vsMonster
then g_Mons_ForEach(monsUpdate
);
6223 // Если есть возможные цели:
6224 // (Выбираем лучшую, меняем оружие и бежим к ней/от нее)
6225 if targets
<> nil then
6227 // Выбираем наилучшую цель:
6228 BestTarget
:= targets
[0];
6229 if Length(targets
) > 1 then
6230 for a
:= 1 to High(targets
) do
6231 if Compare(BestTarget
, targets
[a
]) = 1 then
6232 BestTarget
:= targets
[a
];
6234 // Если лучшая цель "виднее" текущей, то текущая := лучшая:
6235 if ((not Target
.Visible
) and BestTarget
.Visible
and (Target
.UID
<> BestTarget
.UID
)) or
6236 ((not Target
.Line
) and BestTarget
.Line
and BestTarget
.Visible
) then
6238 Target
:= BestTarget
;
6240 if (Healthy() = 3) or ((Healthy() = 2)) then
6241 begin // Если здоровы - догоняем
6242 if ((RunDirection() = TDirection
.D_LEFT
) and (Target
.X
> FObj
.X
)) then
6243 SetAIFlag('GORIGHT', '1');
6244 if ((RunDirection() = TDirection
.D_RIGHT
) and (Target
.X
< FObj
.X
)) then
6245 SetAIFlag('GOLEFT', '1');
6248 begin // Если побиты - убегаем
6249 if ((RunDirection() = TDirection
.D_LEFT
) and (Target
.X
< FObj
.X
)) then
6250 SetAIFlag('GORIGHT', '1');
6251 if ((RunDirection() = TDirection
.D_RIGHT
) and (Target
.X
> FObj
.X
)) then
6252 SetAIFlag('GOLEFT', '1');
6255 // Выбираем оружие на основе расстояния и приоритетов:
6256 SelectWeapon(Abs(x1
-Target
.cX
));
6261 // (Догоняем/убегаем, стреляем по направлению к цели)
6262 // (Если цель далеко, то хватит следить за ней)
6263 if Target
.UID
<> 0 then
6265 if not TargetOnScreen(Target
.X
+ Target
.Rect
.X
,
6266 Target
.Y
+ Target
.Rect
.Y
) then
6267 begin // Цель сбежала с "экрана"
6268 if (Healthy() = 3) or ((Healthy() = 2)) then
6269 begin // Если здоровы - догоняем
6270 if ((RunDirection() = TDirection
.D_LEFT
) and (Target
.X
> FObj
.X
)) then
6271 SetAIFlag('GORIGHT', '1');
6272 if ((RunDirection() = TDirection
.D_RIGHT
) and (Target
.X
< FObj
.X
)) then
6273 SetAIFlag('GOLEFT', '1');
6276 begin // Если побиты - забываем о цели и убегаем
6278 if ((RunDirection() = TDirection
.D_LEFT
) and (Target
.X
< FObj
.X
)) then
6279 SetAIFlag('GORIGHT', '1');
6280 if ((RunDirection() = TDirection
.D_RIGHT
) and (Target
.X
> FObj
.X
)) then
6281 SetAIFlag('GOLEFT', '1');
6285 begin // Цель пока на "экране"
6286 // Если цель не загорожена стеной, то отмечаем, когда ее видели:
6287 if g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
) then
6288 FLastVisible
:= gTime
;
6289 // Если разница высот не велика, то догоняем:
6290 if (Abs(FObj
.Y
-Target
.Y
) <= 128) then
6292 if ((RunDirection() = TDirection
.D_LEFT
) and (Target
.X
> FObj
.X
)) then
6293 SetAIFlag('GORIGHT', '1');
6294 if ((RunDirection() = TDirection
.D_RIGHT
) and (Target
.X
< FObj
.X
)) then
6295 SetAIFlag('GOLEFT', '1');
6299 // Выбираем угол вверх:
6300 if FDirection
= TDirection
.D_LEFT
then
6301 angle
:= ANGLE_LEFTUP
6303 angle
:= ANGLE_RIGHTUP
;
6305 firew
:= Trunc(Cos(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
6306 fireh
:= Trunc(Sin(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
6308 // Если при угле вверх можно попасть в приблизительное положение цели:
6309 if g_CollideLine(x1
, y1
, x1
+firew
, y1
+fireh
,
6310 Target
.X
+Target
.Rect
.X
+GetInterval(FDifficult
.DiagPrecision
, 128), //96
6311 Target
.Y
+Target
.Rect
.Y
+GetInterval(FDifficult
.DiagPrecision
, 128),
6312 Target
.Rect
.Width
, Target
.Rect
.Height
) and
6313 g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
) then
6314 begin // то нужно стрелять вверх
6315 SetAIFlag('NEEDFIRE', '1');
6316 SetAIFlag('NEEDSEEUP', '1');
6319 // Выбираем угол вниз:
6320 if FDirection
= TDirection
.D_LEFT
then
6321 angle
:= ANGLE_LEFTDOWN
6323 angle
:= ANGLE_RIGHTDOWN
;
6325 firew
:= Trunc(Cos(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
6326 fireh
:= Trunc(Sin(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
6328 // Если при угле вниз можно попасть в приблизительное положение цели:
6329 if g_CollideLine(x1
, y1
, x1
+firew
, y1
+fireh
,
6330 Target
.X
+Target
.Rect
.X
+GetInterval(FDifficult
.DiagPrecision
, 128),
6331 Target
.Y
+Target
.Rect
.Y
+GetInterval(FDifficult
.DiagPrecision
, 128),
6332 Target
.Rect
.Width
, Target
.Rect
.Height
) and
6333 g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
) then
6334 begin // то нужно стрелять вниз
6335 SetAIFlag('NEEDFIRE', '1');
6336 SetAIFlag('NEEDSEEDOWN', '1');
6339 // Если цель видно и она на такой же высоте:
6340 if Target
.Visible
and
6341 (y1
+4 < Target
.Y
+Target
.Rect
.Y
+Target
.Rect
.Height
) and
6342 (y1
-4 > Target
.Y
+Target
.Rect
.Y
) then
6344 // Если идем в сторону цели, то надо стрелять:
6345 if ((FDirection
= TDirection
.D_LEFT
) and (Target
.X
< FObj
.X
)) or
6346 ((FDirection
= TDirection
.D_RIGHT
) and (Target
.X
> FObj
.X
)) then
6347 begin // то нужно стрелять вперед
6348 SetAIFlag('NEEDFIRE', '1');
6349 SetAIFlag('NEEDSEEDOWN', '');
6350 SetAIFlag('NEEDSEEUP', '');
6352 // Если цель в пределах "экрана" и сложность позволяет прыжки сближения:
6353 if Abs(FObj
.X
-Target
.X
) < Trunc(gPlayerScreenSize
.X
*0.75) then
6354 if GetRnd(FDifficult
.CloseJump
) then
6355 begin // то если повезет - прыгаем (особенно, если близко)
6356 if Abs(FObj
.X
-Target
.X
) < 128 then
6360 if Random(a
) = 0 then
6361 SetAIFlag('NEEDJUMP', '1');
6365 // Если цель все еще есть:
6366 if Target
.UID
<> 0 then
6367 if gTime
-FLastVisible
> 2000 then // Если видели давно
6368 Target
.UID
:= 0 // то забыть цель
6369 else // Если видели недавно
6370 begin // но цель убили
6371 if Target
.IsPlayer
then
6372 begin // Цель - игрок
6373 pla
:= g_Player_Get(Target
.UID
);
6374 if (pla
= nil) or (not pla
.alive
) or pla
.NoTarget
or
6375 (pla
.FMegaRulez
[MR_INVIS
] >= gTime
) then
6376 Target
.UID
:= 0; // то забыть цель
6379 begin // Цель - монстр
6380 mon
:= g_Monsters_ByUID(Target
.UID
);
6381 if (mon
= nil) or (not mon
.alive
) then
6382 Target
.UID
:= 0; // то забыть цель
6385 end; // if Target.UID <> 0
6387 FTargetUID
:= Target
.UID
;
6389 // Если возможных целей нет:
6390 // (Атака чего-нибудь слева или справа)
6391 if targets
= nil then
6392 if GetAIFlag('ATTACKLEFT') <> '' then
6393 begin // Если нужно атаковать налево
6394 RemoveAIFlag('ATTACKLEFT');
6396 SetAIFlag('NEEDJUMP', '1');
6398 if RunDirection() = TDirection
.D_RIGHT
then
6399 begin // Идем не в ту сторону
6400 if (Healthy() > 1) and GetRnd(FDifficult
.InvisFire
) then
6401 begin // Если здоровы, то, возможно, стреляем бежим влево и стреляем
6402 SetAIFlag('NEEDFIRE', '1');
6403 SetAIFlag('GOLEFT', '1');
6407 begin // Идем в нужную сторону
6408 if GetRnd(FDifficult
.InvisFire
) then // Возможно, стреляем вслепую
6409 SetAIFlag('NEEDFIRE', '1');
6410 if Healthy() <= 1 then // Побиты - убегаем
6411 SetAIFlag('GORIGHT', '1');
6415 if GetAIFlag('ATTACKRIGHT') <> '' then
6416 begin // Если нужно атаковать направо
6417 RemoveAIFlag('ATTACKRIGHT');
6419 SetAIFlag('NEEDJUMP', '1');
6421 if RunDirection() = TDirection
.D_LEFT
then
6422 begin // Идем не в ту сторону
6423 if (Healthy() > 1) and GetRnd(FDifficult
.InvisFire
) then
6424 begin // Если здоровы, то, возможно, бежим вправо и стреляем
6425 SetAIFlag('NEEDFIRE', '1');
6426 SetAIFlag('GORIGHT', '1');
6431 if GetRnd(FDifficult
.InvisFire
) then // Возможно, стреляем вслепую
6432 SetAIFlag('NEEDFIRE', '1');
6433 if Healthy() <= 1 then // Побиты - убегаем
6434 SetAIFlag('GOLEFT', '1');
6438 //HACK! (does it belongs there?)
6439 RealizeCurrentWeapon();
6441 // Если есть возможные цели:
6442 // (Стреляем по направлению к целям)
6443 if (targets
<> nil) and (GetAIFlag('NEEDFIRE') <> '') then
6444 for a
:= 0 to High(targets
) do
6446 // Если можем стрелять по диагонали:
6447 if GetRnd(FDifficult
.DiagFire
) then
6449 // Ищем цель сверху и стреляем, если есть:
6450 if FDirection
= TDirection
.D_LEFT
then
6451 angle
:= ANGLE_LEFTUP
6453 angle
:= ANGLE_RIGHTUP
;
6455 firew
:= Trunc(Cos(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
6456 fireh
:= Trunc(Sin(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
6458 if g_CollideLine(x1
, y1
, x1
+firew
, y1
+fireh
,
6459 targets
[a
].X
+targets
[a
].Rect
.X
+GetInterval(FDifficult
.DiagPrecision
, 128),
6460 targets
[a
].Y
+targets
[a
].Rect
.Y
+GetInterval(FDifficult
.DiagPrecision
, 128),
6461 targets
[a
].Rect
.Width
, targets
[a
].Rect
.Height
) and
6462 g_TraceVector(x1
, y1
, targets
[a
].cX
, targets
[a
].cY
) then
6464 SetAIFlag('NEEDFIRE', '1');
6465 SetAIFlag('NEEDSEEUP', '1');
6468 // Ищем цель снизу и стреляем, если есть:
6469 if FDirection
= TDirection
.D_LEFT
then
6470 angle
:= ANGLE_LEFTDOWN
6472 angle
:= ANGLE_RIGHTDOWN
;
6474 firew
:= Trunc(Cos(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
6475 fireh
:= Trunc(Sin(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
6477 if g_CollideLine(x1
, y1
, x1
+firew
, y1
+fireh
,
6478 targets
[a
].X
+targets
[a
].Rect
.X
+GetInterval(FDifficult
.DiagPrecision
, 128),
6479 targets
[a
].Y
+targets
[a
].Rect
.Y
+GetInterval(FDifficult
.DiagPrecision
, 128),
6480 targets
[a
].Rect
.Width
, targets
[a
].Rect
.Height
) and
6481 g_TraceVector(x1
, y1
, targets
[a
].cX
, targets
[a
].cY
) then
6483 SetAIFlag('NEEDFIRE', '1');
6484 SetAIFlag('NEEDSEEDOWN', '1');
6488 // Если цель "перед носом", то стреляем:
6489 if targets
[a
].Line
and targets
[a
].Visible
and
6490 (((FDirection
= TDirection
.D_LEFT
) and (targets
[a
].X
< FObj
.X
)) or
6491 ((FDirection
= TDirection
.D_RIGHT
) and (targets
[a
].X
> FObj
.X
))) then
6493 SetAIFlag('NEEDFIRE', '1');
6498 // Если летит пуля, то, возможно, подпрыгиваем:
6499 if g_Weapon_Danger(FUID
, FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+PLAYER_RECT
.Y
,
6500 PLAYER_RECT
.Width
, PLAYER_RECT
.Height
,
6501 40+GetInterval(FDifficult
.Cover
, 40)) then
6502 SetAIFlag('NEEDJUMP', '1');
6504 // Если кончились паторны, то нужно сменить оружие:
6505 ammo
:= GetAmmoByWeapon(FCurrWeap
);
6506 if ((FCurrWeap
= WEAPON_SHOTGUN2
) and (ammo
< 2)) or
6507 ((FCurrWeap
= WEAPON_BFG
) and (ammo
< 40)) or
6509 SetAIFlag('SELECTWEAPON', '1');
6511 // Если нужно сменить оружие, то выбираем нужное:
6512 if GetAIFlag('SELECTWEAPON') = '1' then
6515 RemoveAIFlag('SELECTWEAPON');
6519 procedure TBot
.Update();
6532 // Проверяем, отключён ли AI ботов
6533 if (g_debug_BotAIOff
= 1) and (Team
= TEAM_RED
) then
6535 if (g_debug_BotAIOff
= 2) and (Team
= TEAM_BLUE
) then
6537 if g_debug_BotAIOff
= 3 then
6547 RealizeCurrentWeapon();
6554 procedure TBot
.ReleaseKey(Key
: Byte);
6563 function TBot
.KeyPressed(Key
: Word): Boolean;
6565 Result
:= FKeys
[Key
].Pressed
;
6568 function TBot
.GetAIFlag(aName
: String20
): String20
;
6574 aName
:= LowerCase(aName
);
6576 if FAIFlags
<> nil then
6577 for a
:= 0 to High(FAIFlags
) do
6578 if LowerCase(FAIFlags
[a
].Name
) = aName
then
6580 Result
:= FAIFlags
[a
].Value
;
6585 procedure TBot
.RemoveAIFlag(aName
: String20
);
6589 if FAIFlags
= nil then Exit
;
6591 aName
:= LowerCase(aName
);
6593 for a
:= 0 to High(FAIFlags
) do
6594 if LowerCase(FAIFlags
[a
].Name
) = aName
then
6596 if a
<> High(FAIFlags
) then
6597 for b
:= a
to High(FAIFlags
)-1 do
6598 FAIFlags
[b
] := FAIFlags
[b
+1];
6600 SetLength(FAIFlags
, Length(FAIFlags
)-1);
6605 procedure TBot
.SetAIFlag(aName
, fValue
: String20
);
6613 aName
:= LowerCase(aName
);
6615 if FAIFlags
<> nil then
6616 for a
:= 0 to High(FAIFlags
) do
6617 if LowerCase(FAIFlags
[a
].Name
) = aName
then
6623 if ok
then FAIFlags
[a
].Value
:= fValue
6626 SetLength(FAIFlags
, Length(FAIFlags
)+1);
6627 with FAIFlags
[High(FAIFlags
)] do
6635 procedure TBot
.UpdateMove
;
6637 procedure GoLeft(Time
: Word = 1);
6639 ReleaseKey(KEY_LEFT
);
6640 ReleaseKey(KEY_RIGHT
);
6641 PressKey(KEY_LEFT
, Time
);
6642 SetDirection(TDirection
.D_LEFT
);
6645 procedure GoRight(Time
: Word = 1);
6647 ReleaseKey(KEY_LEFT
);
6648 ReleaseKey(KEY_RIGHT
);
6649 PressKey(KEY_RIGHT
, Time
);
6650 SetDirection(TDirection
.D_RIGHT
);
6653 function Rnd(a
: Word): Boolean;
6655 Result
:= Random(a
) = 0;
6658 procedure Turn(Time
: Word = 1200);
6660 if RunDirection() = TDirection
.D_LEFT
then GoRight(Time
) else GoLeft(Time
);
6665 ReleaseKey(KEY_LEFT
);
6666 ReleaseKey(KEY_RIGHT
);
6669 function CanRunLeft(): Boolean;
6671 Result
:= not CollideLevel(-1, 0);
6674 function CanRunRight(): Boolean;
6676 Result
:= not CollideLevel(1, 0);
6679 function CanRun(): Boolean;
6681 if RunDirection() = TDirection
.D_LEFT
then Result
:= CanRunLeft() else Result
:= CanRunRight();
6684 procedure Jump(Time
: Word = 30);
6686 PressKey(KEY_JUMP
, Time
);
6689 function NearHole(): Boolean;
6693 { TODO 5 : Лестницы }
6694 sx
:= IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1);
6695 for x
:= 1 to PLAYER_RECT
.Width
do
6696 if (not StayOnStep(x
*sx
, 0)) and
6697 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
)) and
6698 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
*2)) then
6707 function BorderHole(): Boolean;
6711 { TODO 5 : Лестницы }
6712 sx
:= IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1);
6713 for x
:= 1 to PLAYER_RECT
.Width
do
6714 if (not StayOnStep(x
*sx
, 0)) and
6715 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
)) and
6716 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
*2)) then
6718 for xx
:= x
to x
+32 do
6719 if CollideLevel(xx
*sx
, PLAYER_RECT
.Height
) then
6729 function NearDeepHole(): Boolean;
6735 sx
:= IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1);
6738 for x
:= 1 to PLAYER_RECT
.Width
do
6739 if (not StayOnStep(x
*sx
, 0)) and
6740 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
)) and
6741 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
*2)) then
6743 while FObj
.Y
+y
*PLAYER_RECT
.Height
< gMapInfo
.Height
do
6745 if CollideLevel(x
*sx
, PLAYER_RECT
.Height
*y
) then Exit
;
6750 end else Result
:= False;
6753 function OverDeepHole(): Boolean;
6760 while FObj
.Y
+y
*PLAYER_RECT
.Height
< gMapInfo
.Height
do
6762 if CollideLevel(0, PLAYER_RECT
.Height
*y
) then Exit
;
6769 function OnGround(): Boolean;
6771 Result
:= StayOnStep(0, 0) or CollideLevel(0, 1);
6774 function OnLadder(): Boolean;
6776 Result
:= FullInStep(0, 0);
6779 function BelowLadder(): Boolean;
6781 Result
:= (FullInStep(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -PLAYER_RECT
.Height
) and
6782 not CollideLevel(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -PLAYER_RECT
.Height
)) or
6783 (FullInStep(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -BOT_MAXJUMP
) and
6784 not CollideLevel(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -BOT_MAXJUMP
));
6787 function BelowLiftUp(): Boolean;
6789 Result
:= ((FullInLift(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -PLAYER_RECT
.Height
) = -1) and
6790 not CollideLevel(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -PLAYER_RECT
.Height
)) or
6791 ((FullInLift(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -BOT_MAXJUMP
) = -1) and
6792 not CollideLevel(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -BOT_MAXJUMP
));
6795 function OnTopLift(): Boolean;
6797 Result
:= (FullInLift(0, 0) = -1) and (FullInLift(0, -32) = 0);
6800 function CanJumpOver(): Boolean;
6804 sx
:= IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1);
6808 if not CollideLevel(sx
, 0) then Exit
;
6810 for y
:= 1 to BOT_MAXJUMP
do
6811 if CollideLevel(0, -y
) then Exit
else
6812 if not CollideLevel(sx
, -y
) then
6819 function CanJumpUp(Dist
: ShortInt): Boolean;
6826 if CollideLevel(Dist
, 0) then Exit
;
6829 for y
:= 0 to BOT_MAXJUMP
do
6830 if CollideLevel(Dist
, -y
) then
6839 for yy
:= y
+1 to BOT_MAXJUMP
do
6840 if not CollideLevel(Dist
, -yy
) then
6849 for y
:= 0 to BOT_MAXJUMP
do
6850 if CollideLevel(0, -y
) then
6858 if y
< yy
then Exit
;
6863 function IsSafeTrigger(): Boolean;
6868 if gTriggers
= nil then
6870 for a
:= 0 to High(gTriggers
) do
6871 if Collide(gTriggers
[a
].X
,
6874 gTriggers
[a
].Height
) and
6875 (gTriggers
[a
].TriggerType
in [TRIGGER_EXIT
, TRIGGER_CLOSEDOOR
,
6876 TRIGGER_CLOSETRAP
, TRIGGER_TRAP
,
6877 TRIGGER_PRESS
, TRIGGER_ON
, TRIGGER_OFF
,
6878 TRIGGER_ONOFF
, TRIGGER_SPAWNMONSTER
,
6879 TRIGGER_DAMAGE
, TRIGGER_SHOT
]) then
6884 // Возможно, нажимаем кнопку:
6885 if Rnd(16) and IsSafeTrigger() then
6888 // Если под лифтом или ступеньками, то, возможно, прыгаем:
6889 if OnLadder() or ((BelowLadder() or BelowLiftUp()) and Rnd(8)) then
6891 ReleaseKey(KEY_LEFT
);
6892 ReleaseKey(KEY_RIGHT
);
6896 // Идем влево, если надо было:
6897 if GetAIFlag('GOLEFT') <> '' then
6899 RemoveAIFlag('GOLEFT');
6900 if CanRunLeft() then
6904 // Идем вправо, если надо было:
6905 if GetAIFlag('GORIGHT') <> '' then
6907 RemoveAIFlag('GORIGHT');
6908 if CanRunRight() then
6912 // Если вылетели за карту, то пробуем вернуться:
6913 if FObj
.X
< -32 then
6916 if FObj
.X
+32 > gMapInfo
.Width
then
6919 // Прыгаем, если надо было:
6920 if GetAIFlag('NEEDJUMP') <> '' then
6923 RemoveAIFlag('NEEDJUMP');
6926 // Смотрим вверх, если надо было:
6927 if GetAIFlag('NEEDSEEUP') <> '' then
6930 ReleaseKey(KEY_DOWN
);
6931 PressKey(KEY_UP
, 20);
6932 RemoveAIFlag('NEEDSEEUP');
6935 // Смотрим вниз, если надо было:
6936 if GetAIFlag('NEEDSEEDOWN') <> '' then
6939 ReleaseKey(KEY_DOWN
);
6940 PressKey(KEY_DOWN
, 20);
6941 RemoveAIFlag('NEEDSEEDOWN');
6944 // Если нужно было в дыру и мы не на земле, то покорно летим:
6945 if GetAIFlag('GOINHOLE') <> '' then
6946 if not OnGround() then
6948 ReleaseKey(KEY_LEFT
);
6949 ReleaseKey(KEY_RIGHT
);
6950 RemoveAIFlag('GOINHOLE');
6951 SetAIFlag('FALLINHOLE', '1');
6954 // Если падали и достигли земли, то хватит падать:
6955 if GetAIFlag('FALLINHOLE') <> '' then
6957 RemoveAIFlag('FALLINHOLE');
6959 // Если летели прямо и сейчас не на лестнице или на вершине лифта, то отходим в сторону:
6960 if not (KeyPressed(KEY_LEFT
) or KeyPressed(KEY_RIGHT
)) then
6961 if GetAIFlag('FALLINHOLE') = '' then
6962 if (not OnLadder()) or (FObj
.Vel
.Y
>= 0) or (OnTopLift()) then
6968 // Если на земле и можно подпрыгнуть, то, возможно, прыгаем:
6970 CanJumpUp(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*32) and
6974 // Если на земле и возле дыры (глубина > 2 ростов игрока):
6975 if OnGround() and NearHole() then
6976 if NearDeepHole() then // Если это бездна
6978 0..3: Turn(); // Бежим обратно
6979 4: Jump(); // Прыгаем
6980 5: begin // Прыгаем обратно
6985 else // Это не бездна и мы еще не летим туда
6986 if GetAIFlag('GOINHOLE') = '' then
6988 0: Turn(); // Не нужно туда
6989 1: Jump(); // Вдруг повезет - прыгаем
6990 else // Если яма с границей, то при случае можно туда прыгнуть
6991 if BorderHole() then
6992 SetAIFlag('GOINHOLE', '1');
6995 // Если на земле, но некуда идти:
6996 if (not CanRun()) and OnGround() then
6998 // Если мы на лестнице или можно перепрыгнуть, то прыгаем:
6999 if CanJumpOver() or OnLadder() then
7001 else // иначе попытаемся в другую сторону
7002 if Random(2) = 0 then
7004 if IsSafeTrigger() then
7010 // Осталось мало воздуха:
7011 if FAir
< 36 * 2 then
7014 // Выбираемся из кислоты, если нет костюма, обожглись, или мало здоровья:
7015 if (FMegaRulez
[MR_SUIT
] < gTime
) and ((FLastHit
= HIT_ACID
) or (Healthy() <= 1)) then
7016 if BodyInAcid(0, 0) then
7020 function TBot
.FullInStep(XInc
, YInc
: Integer): Boolean;
7022 Result
:= g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
,
7023 PLAYER_RECT
.Width
, PLAYER_RECT
.Height
, PANEL_STEP
, False);
7026 {function TBot.NeedItem(Item: Byte): Byte;
7031 procedure TBot
.SelectWeapon(Dist
: Integer);
7035 function HaveAmmo(weapon
: Byte): Boolean;
7038 WEAPON_PISTOL
: Result
:= FAmmo
[A_BULLETS
] >= 1;
7039 WEAPON_SHOTGUN1
: Result
:= FAmmo
[A_SHELLS
] >= 1;
7040 WEAPON_SHOTGUN2
: Result
:= FAmmo
[A_SHELLS
] >= 2;
7041 WEAPON_CHAINGUN
: Result
:= FAmmo
[A_BULLETS
] >= 10;
7042 WEAPON_ROCKETLAUNCHER
: Result
:= FAmmo
[A_ROCKETS
] >= 1;
7043 WEAPON_PLASMA
: Result
:= FAmmo
[A_CELLS
] >= 10;
7044 WEAPON_BFG
: Result
:= FAmmo
[A_CELLS
] >= 40;
7045 WEAPON_SUPERPULEMET
: Result
:= FAmmo
[A_SHELLS
] >= 1;
7046 WEAPON_FLAMETHROWER
: Result
:= FAmmo
[A_FUEL
] >= 1;
7047 else Result
:= True;
7052 if Dist
= -1 then Dist
:= BOT_LONGDIST
;
7054 if Dist
> BOT_LONGDIST
then
7055 begin // Дальний бой
7057 if FWeapon
[FDifficult
.WeaponPrior
[a
]] and HaveAmmo(FDifficult
.WeaponPrior
[a
]) then
7059 FSelectedWeapon
:= FDifficult
.WeaponPrior
[a
];
7063 else //if Dist > BOT_UNSAFEDIST then
7064 begin // Ближний бой
7066 if FWeapon
[FDifficult
.CloseWeaponPrior
[a
]] and HaveAmmo(FDifficult
.CloseWeaponPrior
[a
]) then
7068 FSelectedWeapon
:= FDifficult
.CloseWeaponPrior
[a
];
7075 if FWeapon[FDifficult.SafeWeaponPrior[a]] and HaveAmmo(FDifficult.SafeWeaponPrior[a]) then
7077 FSelectedWeapon := FDifficult.SafeWeaponPrior[a];
7083 function TBot
.PickItem(ItemType
: Byte; force
: Boolean; var remove
: Boolean): Boolean;
7085 Result
:= inherited PickItem(ItemType
, force
, remove
);
7087 if Result
then SetAIFlag('SELECTWEAPON', '1');
7090 function TBot
.Heal(value
: Word; Soft
: Boolean): Boolean;
7092 Result
:= inherited Heal(value
, Soft
);
7095 function TBot
.Healthy(): Byte;
7097 if FMegaRulez
[MR_INVUL
] >= gTime
then Result
:= 3
7098 else if (FHealth
> 80) or ((FHealth
> 50) and (FArmor
> 20)) then Result
:= 3
7099 else if (FHealth
> 50) then Result
:= 2
7100 else if (FHealth
> 20) then Result
:= 1
7104 function TBot
.TargetOnScreen(TX
, TY
: Integer): Boolean;
7106 Result
:= (Abs(FObj
.X
-TX
) <= Trunc(gPlayerScreenSize
.X
*0.6)) and
7107 (Abs(FObj
.Y
-TY
) <= Trunc(gPlayerScreenSize
.Y
*0.6));
7110 procedure TBot
.OnDamage(Angle
: SmallInt);
7118 if (Angle
= 0) or (Angle
= 180) then
7121 if (g_GetUIDType(FLastSpawnerUID
) = UID_PLAYER
) and
7122 LongBool(gGameSettings
.Options
and GAME_OPTION_BOTVSPLAYER
) then
7124 pla
:= g_Player_Get(FLastSpawnerUID
);
7125 ok
:= not TargetOnScreen(pla
.FObj
.X
+ PLAYER_RECT
.X
,
7126 pla
.FObj
.Y
+ PLAYER_RECT
.Y
);
7129 if (g_GetUIDType(FLastSpawnerUID
) = UID_MONSTER
) and
7130 LongBool(gGameSettings
.Options
and GAME_OPTION_BOTVSMONSTER
) then
7132 mon
:= g_Monsters_ByUID(FLastSpawnerUID
);
7133 ok
:= not TargetOnScreen(mon
.Obj
.X
+ mon
.Obj
.Rect
.X
,
7134 mon
.Obj
.Y
+ mon
.Obj
.Rect
.Y
);
7139 SetAIFlag('ATTACKLEFT', '1')
7141 SetAIFlag('ATTACKRIGHT', '1');
7145 function TBot
.RunDirection(): TDirection
;
7147 if Abs(Vel
.X
) >= 1 then
7149 if Vel
.X
> 0 then Result
:= TDirection
.D_RIGHT
else Result
:= TDirection
.D_LEFT
;
7151 Result
:= FDirection
;
7154 function TBot
.GetRnd(a
: Byte): Boolean;
7156 if a
= 0 then Result
:= False
7157 else if a
= 255 then Result
:= True
7158 else Result
:= Random(256) > 255-a
;
7161 function TBot
.GetInterval(a
: Byte; radius
: SmallInt): SmallInt;
7163 Result
:= Round((255-a
)/255*radius
*(Random(2)-1));
7167 procedure TDifficult
.save (st
: TStream
);
7169 utils
.writeInt(st
, Byte(DiagFire
));
7170 utils
.writeInt(st
, Byte(InvisFire
));
7171 utils
.writeInt(st
, Byte(DiagPrecision
));
7172 utils
.writeInt(st
, Byte(FlyPrecision
));
7173 utils
.writeInt(st
, Byte(Cover
));
7174 utils
.writeInt(st
, Byte(CloseJump
));
7175 st
.WriteBuffer(WeaponPrior
[Low(WeaponPrior
)], sizeof(WeaponPrior
));
7176 st
.WriteBuffer(CloseWeaponPrior
[Low(CloseWeaponPrior
)], sizeof(CloseWeaponPrior
));
7179 procedure TDifficult
.load (st
: TStream
);
7181 DiagFire
:= utils
.readByte(st
);
7182 InvisFire
:= utils
.readByte(st
);
7183 DiagPrecision
:= utils
.readByte(st
);
7184 FlyPrecision
:= utils
.readByte(st
);
7185 Cover
:= utils
.readByte(st
);
7186 CloseJump
:= utils
.readByte(st
);
7187 st
.ReadBuffer(WeaponPrior
[Low(WeaponPrior
)], sizeof(WeaponPrior
));
7188 st
.ReadBuffer(CloseWeaponPrior
[Low(CloseWeaponPrior
)], sizeof(CloseWeaponPrior
));
7192 procedure TBot
.SaveState (st
: TStream
);
7197 inherited SaveState(st
);
7198 utils
.writeSign(st
, 'BOT0');
7200 utils
.writeInt(st
, Byte(FSelectedWeapon
));
7202 utils
.writeInt(st
, Word(FTargetUID
));
7203 // Время потери цели
7204 utils
.writeInt(st
, LongWord(FLastVisible
));
7205 // Количество флагов ИИ
7206 dw
:= Length(FAIFlags
);
7207 utils
.writeInt(st
, LongInt(dw
));
7209 for i
:= 0 to dw
-1 do
7211 utils
.writeStr(st
, FAIFlags
[i
].Name
, 20);
7212 utils
.writeStr(st
, FAIFlags
[i
].Value
, 20);
7214 // Настройки сложности
7215 FDifficult
.save(st
);
7219 procedure TBot
.LoadState (st
: TStream
);
7224 inherited LoadState(st
);
7225 if not utils
.checkSign(st
, 'BOT0') then raise XStreamError
.Create('invalid bot signature');
7227 FSelectedWeapon
:= utils
.readByte(st
);
7229 FTargetUID
:= utils
.readWord(st
);
7230 // Время потери цели
7231 FLastVisible
:= utils
.readLongWord(st
);
7232 // Количество флагов ИИ
7233 dw
:= utils
.readLongInt(st
);
7234 if (dw
< 0) or (dw
> 16384) then raise XStreamError
.Create('invalid number of bot AI flags');
7235 SetLength(FAIFlags
, dw
);
7237 for i
:= 0 to dw
-1 do
7239 FAIFlags
[i
].Name
:= utils
.readStr(st
, 20);
7240 FAIFlags
[i
].Value
:= utils
.readStr(st
, 20);
7242 // Настройки сложности
7243 FDifficult
.load(st
);
7248 conRegVar('cheat_berserk_autoswitch', @gBerserkAutoswitch
, 'autoswitch to fist when berserk pack taken', '', true, true);
7249 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');
7250 conRegVar('player_indicator_style', @gPlayerIndicatorStyle
, 'Visual appearance of indicator', 'Visual appearance of indicator');