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
,
61 AmmoLimits
: Array [0..1] of Array [A_BULLETS
..A_HIGH
] of Word =
62 ((200, 50, 50, 300, 100),
63 (400, 100, 100, 600, 200));
80 ANGLE_NONE
= Low(SmallInt);
82 PLAYER_RECT
: TRectWH
= (X
:15; Y
:12; Width
:34; Height
:52);
83 PLAYER_RECT_CX
= 15+(34 div 2);
84 PLAYER_RECT_CY
= 12+(52 div 2);
87 PLAYER_HP_LIMIT
= 200;
89 PLAYER_AP_LIMIT
= 200;
93 PLAYER_BURN_TIME
= 110;
95 PLAYER1_DEF_COLOR
: TRGB
= (R
:64; G
:175; B
:48);
96 PLAYER2_DEF_COLOR
: TRGB
= (R
:96; G
:96; B
:96);
100 JET_MAX
= 540; // ~30 sec
102 ANGLE_RIGHTDOWN
= -35;
104 ANGLE_LEFTDOWN
= -145;
105 WEAPONPOINT
: Array [TDirection
] of TDFPoint
= ((X
:16; Y
:32), (X
:47; Y
:32));
106 TEAMCOLOR
: Array [TEAM_RED
..TEAM_BLUE
] of TRGB
= ((R
:255; G
:0; B
:0),
125 TPlayerStatArray
= Array of TPlayerStat
;
127 TPlayerSavedState
= record
135 Ammo
: Array [A_BULLETS
..A_HIGH
] of Word;
136 MaxAmmo
: Array [A_BULLETS
..A_HIGH
] of Word;
137 Weapon
: Array [WP_FIRST
..WP_LAST
] of Boolean;
138 Rulez
: Set of R_ITEM_BACKPACK
..R_BERSERK
;
147 TPlayer
= class{$IFDEF USE_MEMPOOL}(TPoolObject
){$ENDIF}
155 FDirection
: TDirection
;
163 FMonsterKills
: Integer;
169 FCanJetpack
: Boolean;
175 FNextWeapDelay
: Byte; // frames
176 FBFGFireCounter
: SmallInt;
177 FLastSpawnerUID
: Word;
181 FSpectatePlayer
: Integer;
182 FFirePainTime
: Integer;
185 FSavedStateNum
: Integer;
187 FModel
: TPlayerModel
;
188 FPunchAnim
: TAnimationState
;
191 FActionForce
: Boolean;
192 FActionChanged
: Boolean;
194 FFireAngle
: SmallInt;
198 {$IFDEF ENABLE_SHELLS}
199 FShellTimer
: Integer;
202 FSawSound
: TPlayableSound
;
203 FSawSoundIdle
: TPlayableSound
;
204 FSawSoundHit
: TPlayableSound
;
205 FSawSoundSelect
: TPlayableSound
;
206 FFlameSoundOn
: TPlayableSound
;
207 FFlameSoundOff
: TPlayableSound
;
208 FFlameSoundWork
: TPlayableSound
;
209 FJetSoundOn
: TPlayableSound
;
210 FJetSoundOff
: TPlayableSound
;
211 FJetSoundFly
: TPlayableSound
;
215 FJustTeleported
: Boolean;
217 mEDamageType
: Integer;
220 function CollideLevel(XInc
, YInc
: Integer): Boolean;
221 function StayOnStep(XInc
, YInc
: Integer): Boolean;
222 function HeadInLiquid(XInc
, YInc
: Integer): Boolean;
223 function BodyInLiquid(XInc
, YInc
: Integer): Boolean;
224 function BodyInAcid(XInc
, YInc
: Integer): Boolean;
225 function FullInLift(XInc
, YInc
: Integer): Integer;
226 {procedure CollideItem();}
227 procedure FlySmoke(Times
: DWORD
= 1);
228 procedure OnFireFlame(Times
: DWORD
= 1);
229 procedure SetAction(Action
: Byte; Force
: Boolean = False);
230 procedure OnDamage(Angle
: SmallInt); virtual;
231 function firediry(): Integer;
234 procedure Run(Direction
: TDirection
);
235 procedure NextWeapon();
236 procedure PrevWeapon();
243 function getNextWeaponIndex (): Byte; // return 255 for "no switch"
244 procedure resetWeaponQueue ();
245 function hasAmmoForWeapon (weapon
: Byte): Boolean;
246 function hasAmmoForShooting (weapon
: Byte): Boolean;
247 function shouldSwitch (weapon
: Byte; hadWeapon
: Boolean) : Boolean;
249 procedure doDamage (v
: Integer);
252 FDamageBuffer
: Integer;
254 FAmmo
: Array [A_BULLETS
..A_HIGH
] of Word;
255 FMaxAmmo
: Array [A_BULLETS
..A_HIGH
] of Word;
256 FWeapon
: Array [WP_FIRST
..WP_LAST
] of Boolean;
257 FRulez
: Set of R_ITEM_BACKPACK
..R_BERSERK
;
259 FMegaRulez
: Array [MR_SUIT
..MR_MAX
] of DWORD
;
260 FReloading
: Array [WP_FIRST
..WP_LAST
] of Word;
261 FTime
: Array [T_RESPAWN
..T_FLAGCAP
] of DWORD
;
262 FKeys
: Array [KEY_LEFT
..KEY_CHAT
] of TKeyState
;
263 FWeapSwitchMode
: Byte;
264 FWeapPreferences
: Array [WP_FIRST
.. WP_LAST
+1] of Byte;
265 FSwitchToEmpty
: Byte;
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
287 {$IFDEF ENABLE_CORPSES}
291 // debug: viewport offset
292 viewPortX
, viewPortY
, viewPortW
, viewPortH
: Integer;
294 function isValidViewPort (): Boolean; inline;
296 constructor Create(); virtual;
297 destructor Destroy(); override;
298 procedure Respawn(Silent
: Boolean; Force
: Boolean = False); virtual;
299 function GetRespawnPoint(): Byte;
300 procedure PressKey(Key
: Byte; Time
: Word = 1);
301 procedure ReleaseKeys();
302 procedure SetModel(ModelName
: String);
303 procedure SetColor(Color
: TRGB
);
304 function GetColor(): TRGB
;
305 procedure SetWeapon(W
: Byte);
306 function IsKeyPressed(K
: Byte): Boolean;
307 function GetKeys(): Byte;
308 function PickItem(ItemType
: Byte; arespawn
: Boolean; var remove
: Boolean): Boolean; virtual;
309 procedure SetWeaponPrefs(Prefs
: Array of Byte);
310 procedure SetWeaponPref(Weapon
, Pref
: Byte);
311 function GetWeaponPref(Weapon
: Byte) : Byte;
312 function GetMorePrefered() : Byte;
313 function MaySwitch(Weapon
: Byte) : Boolean;
314 function Collide(X
, Y
: Integer; Width
, Height
: Word): Boolean; overload
;
315 function Collide(Panel
: TPanel
): Boolean; overload
;
316 function Collide(X
, Y
: Integer): Boolean; overload
;
317 procedure SetDirection(Direction
: TDirection
);
318 procedure GetSecret();
319 function TeleportTo(X
, Y
: Integer; silent
: Boolean; dir
: Byte): Boolean;
321 procedure Push(vx
, vy
: Integer);
322 procedure ChangeModel(ModelName
: String);
323 procedure SwitchTeam
;
324 procedure ChangeTeam(Team
: Byte);
326 function GetFlag(Flag
: Byte): Boolean;
327 procedure SetFlag(Flag
: Byte);
328 function DropFlag(Silent
: Boolean = True; DoThrow
: Boolean = False): Boolean;
329 function TryDropFlag(): Boolean;
330 procedure AllRulez(Health
: Boolean);
331 procedure RestoreHealthArmor();
332 procedure FragCombo();
333 procedure GiveItem(ItemType
: Byte);
334 procedure Damage(value
: Word; SpawnerUID
: Word; vx
, vy
: Integer; t
: Byte); virtual;
335 function Heal(value
: Word; Soft
: Boolean): Boolean; virtual;
336 procedure MakeBloodVector(Count
: Word; VelX
, VelY
: Integer);
337 procedure MakeBloodSimple(Count
: Word);
338 procedure Kill(KillType
: Byte; SpawnerUID
: Word; t
: Byte);
339 procedure Reset(Force
: Boolean);
340 procedure Spectate(NoMove
: Boolean = False);
341 procedure SwitchNoClip
;
342 procedure SoftReset();
343 procedure PreUpdate();
344 procedure Update(); virtual;
345 procedure RememberState();
346 procedure RecallState();
347 procedure SaveState (st
: TStream
); virtual;
348 procedure LoadState (st
: TStream
); virtual;
349 procedure PauseSounds(Enable
: Boolean);
350 procedure NetFire(Wpn
: Byte; X
, Y
, AX
, AY
: Integer; WID
: Integer = -1);
351 procedure DoLerp(Level
: Integer = 2);
352 procedure SetLerp(XTo
, YTo
: Integer);
353 procedure ProcessWeaponAction(Action
: Byte);
354 procedure QueueWeaponSwitch(Weapon
: Byte);
355 procedure RealizeCurrentWeapon();
359 procedure JetpackOff
;
360 procedure CatchFire(Attacker
: Word; Timeout
: Integer = PLAYER_BURN_TIME
);
362 //WARNING! this does nothing for now, but still call it!
363 procedure positionChanged (); //WARNING! call this after entity position was changed, or coldet will not work right!
365 procedure getMapBox (out x
, y
, w
, h
: Integer); inline;
366 procedure moveBy (dx
, dy
: Integer); inline;
368 function GetAmmoByWeapon(Weapon
: Byte): Word; // private state
371 property Vel
: TPoint2i read FObj
.Vel
;
372 property Obj
: TObj read FObj
;
374 property Name
: String read FName write FName
;
375 property Model
: TPlayerModel read FModel
;
376 property Health
: Integer read FHealth write FHealth
;
377 property Lives
: Byte read FLives write FLives
;
378 property Armor
: Integer read FArmor write FArmor
;
379 property Air
: Integer read FAir write FAir
;
380 property JetFuel
: Integer read FJetFuel write FJetFuel
;
381 property Frags
: Integer read FFrags write FFrags
;
382 property Death
: Integer read FDeath write FDeath
;
383 property Kills
: Integer read FKills write FKills
;
384 property CurrWeap
: Byte read FCurrWeap write FCurrWeap
;
385 property WeapSwitchMode
: Byte read FWeapSwitchMode write FWeapSwitchMode
;
386 property SwitchToEmpty
: Byte read FSwitchToEmpty write FSwitchToEmpty
;
387 property SkipFist
: Byte read FSkipFist write FSkipFist
;
388 property MonsterKills
: Integer read FMonsterKills write FMonsterKills
;
389 property Secrets
: Integer read FSecrets
;
390 property GodMode
: Boolean read FGodMode write FGodMode
;
391 property NoTarget
: Boolean read FNoTarget write FNoTarget
;
392 property NoReload
: Boolean read FNoReload write FNoReload
;
393 property alive
: Boolean read FAlive write FAlive
;
394 property Flag
: Byte read FFlag
;
395 property Team
: Byte read FTeam write FTeam
;
396 property Direction
: TDirection read FDirection
;
397 property GameX
: Integer read FObj
.X write FObj
.X
;
398 property GameY
: Integer read FObj
.Y write FObj
.Y
;
399 property GameVelX
: Integer read FObj
.Vel
.X write FObj
.Vel
.X
;
400 property GameVelY
: Integer read FObj
.Vel
.Y write FObj
.Vel
.Y
;
401 property GameAccelX
: Integer read FObj
.Accel
.X write FObj
.Accel
.X
;
402 property GameAccelY
: Integer read FObj
.Accel
.Y write FObj
.Accel
.Y
;
403 property IncCam
: Integer read FIncCam write FIncCam
;
404 property IncCamOld
: Integer read FIncCamOld write FIncCamOld
;
405 property SlopeOld
: Integer read FSlopeOld write FSlopeOld
;
406 property UID
: Word read FUID write FUID
;
407 property JustTeleported
: Boolean read FJustTeleported write FJustTeleported
;
408 property NetTime
: LongWord read FNetTime write FNetTime
;
411 property Angle_
: SmallInt read FAngle
;
412 property Spectator
: Boolean read FSpectator
;
413 property NoRespawn
: Boolean read FNoRespawn
;
414 property Berserk
: Integer read FBerserk
;
415 property Pain
: Integer read FPain
;
416 property Pickup
: Integer read FPickup
;
417 property PunchAnim
: TAnimationState read FPunchAnim write FPunchAnim
;
418 property SpawnInvul
: Integer read FSpawnInvul
;
419 property Ghost
: Boolean read FGhost
;
422 property eName
: String read FName write FName
;
423 property eHealth
: Integer read FHealth write FHealth
;
424 property eLives
: Byte read FLives write FLives
;
425 property eArmor
: Integer read FArmor write FArmor
;
426 property eAir
: Integer read FAir write FAir
;
427 property eJetFuel
: Integer read FJetFuel write FJetFuel
;
428 property eFrags
: Integer read FFrags write FFrags
;
429 property eDeath
: Integer read FDeath write FDeath
;
430 property eKills
: Integer read FKills write FKills
;
431 property eCurrWeap
: Byte read FCurrWeap write FCurrWeap
;
432 property eMonsterKills
: Integer read FMonsterKills write FMonsterKills
;
433 property eSecrets
: Integer read FSecrets write FSecrets
;
434 property eGodMode
: Boolean read FGodMode write FGodMode
;
435 property eNoTarget
: Boolean read FNoTarget write FNoTarget
;
436 property eNoReload
: Boolean read FNoReload write FNoReload
;
437 property eAlive
: Boolean read FAlive write FAlive
;
438 property eFlag
: Byte read FFlag
;
439 property eTeam
: Byte read FTeam write FTeam
;
440 property eDirection
: TDirection read FDirection
;
441 property eGameX
: Integer read FObj
.X write FObj
.X
;
442 property eGameY
: Integer read FObj
.Y write FObj
.Y
;
443 property eGameVelX
: Integer read FObj
.Vel
.X write FObj
.Vel
.X
;
444 property eGameVelY
: Integer read FObj
.Vel
.Y write FObj
.Vel
.Y
;
445 property eGameAccelX
: Integer read FObj
.Accel
.X write FObj
.Accel
.X
;
446 property eGameAccelY
: Integer read FObj
.Accel
.Y write FObj
.Accel
.Y
;
447 property eIncCam
: Integer read FIncCam write FIncCam
;
448 property eUID
: Word read FUID
;
449 property eJustTeleported
: Boolean read FJustTeleported
;
450 property eNetTime
: LongWord read FNetTime
;
452 // set this before assigning something to `eDamage`
453 property eDamageType
: Integer read mEDamageType write mEDamageType
;
454 property eDamage
: Integer write doDamage
;
456 {$IFDEF ENABLE_CORPSES}
457 property Corpse
: Integer read FCorpse
;
469 WeaponPrior
: packed array [WP_FIRST
..WP_LAST
] of Byte;
470 CloseWeaponPrior
: packed array [WP_FIRST
..WP_LAST
] of Byte;
471 //SafeWeaponPrior: Array [WP_FIRST..WP_LAST] of Byte;
474 procedure save (st
: TStream
);
475 procedure load (st
: TStream
);
483 TBot
= class(TPlayer
)
485 FSelectedWeapon
: Byte;
488 FAIFlags
: Array of TAIFlag
;
489 FDifficult
: TDifficult
;
491 function GetRnd(a
: Byte): Boolean;
492 function GetInterval(a
: Byte; radius
: SmallInt): SmallInt;
493 function RunDirection(): TDirection
;
494 function FullInStep(XInc
, YInc
: Integer): Boolean;
495 //function NeedItem(Item: Byte): Byte;
496 procedure SelectWeapon(Dist
: Integer);
497 procedure SetAIFlag(aName
, fValue
: String20
);
498 function GetAIFlag(aName
: String20
): String20
;
499 procedure RemoveAIFlag(aName
: String20
);
500 function Healthy(): Byte;
501 procedure UpdateMove();
502 procedure UpdateCombat();
503 function KeyPressed(Key
: Word): Boolean;
504 procedure ReleaseKey(Key
: Byte);
505 function TargetOnScreen(TX
, TY
: Integer): Boolean;
506 procedure OnDamage(Angle
: SmallInt); override;
509 procedure Respawn(Silent
: Boolean; Force
: Boolean = False); override;
510 constructor Create(); override;
511 destructor Destroy(); override;
512 function PickItem(ItemType
: Byte; force
: Boolean; var remove
: Boolean): Boolean; override;
513 function Heal(value
: Word; Soft
: Boolean): Boolean; override;
514 procedure Update(); override;
515 procedure SaveState (st
: TStream
); override;
516 procedure LoadState (st
: TStream
); override;
519 TTeamStat
= Array [TEAM_RED
..TEAM_BLUE
] of
525 gPlayers
: Array of TPlayer
;
526 gTeamStat
: TTeamStat
;
527 gFly
: Boolean = False;
528 gAimLine
: Boolean = False;
529 gChatBubble
: Integer = 0;
530 gPlayerIndicator
: Integer = 1;
531 gPlayerIndicatorStyle
: Integer = 0;
533 gSpectLatchPID1
: Word = 0;
534 gSpectLatchPID2
: Word = 0;
535 MAX_RUNVEL
: Integer = 8;
536 VEL_JUMP
: Integer = 10;
538 function Lerp(X
, Y
, Factor
: Integer): Integer;
540 procedure g_Force_Model_Set(Mode
: Word);
541 function g_Force_Model_Get(): Word;
542 procedure g_Forced_Model_SetName(Model
: String);
543 function g_Forced_Model_GetName(): String;
545 procedure g_Player_Init();
546 procedure g_Player_Free();
547 function g_Player_Create(ModelName
: String; Color
: TRGB
; Team
: Byte; Bot
: Boolean): Word;
548 function g_Player_CreateFromState (st
: TStream
): Word;
549 procedure g_Player_Remove(UID
: Word);
550 procedure g_Player_ResetTeams();
551 procedure g_Player_PreUpdate();
552 procedure g_Player_UpdateAll();
553 procedure g_Player_RememberAll();
554 procedure g_Player_ResetAll(Force
, Silent
: Boolean);
555 function g_Player_Get(UID
: Word): TPlayer
;
556 function g_Player_GetCount(): Byte;
557 function g_Player_GetStats(): TPlayerStatArray
;
558 function g_Player_ValidName(Name
: String): Boolean;
559 procedure g_Player_ResetReady();
560 procedure g_Bot_Add(Team
, Difficult
: Byte; Handicap
: Integer = 100);
561 procedure g_Bot_AddList(Team
: Byte; lname
: ShortString; num
: Integer = -1; Handicap
: Integer = 100);
562 procedure g_Bot_MixNames();
563 procedure g_Bot_RemoveAll();
564 function g_Bot_GetCount(): Integer;
569 {$IFDEF ENABLE_HOLMES}
575 {$IFDEF ENABLE_RENDER}
584 {$IFDEF ENABLE_SHELLS}
587 {$IFDEF ENABLE_CORPSES}
590 e_log
, g_map
, g_items
, g_console
, Math
,
591 g_options
, g_triggers
, g_game
, g_grid
, e_res
,
592 wadreader
, g_monsters
, CONFIG
, g_language
,
596 const PLR_SAVE_VERSION
= 0;
606 diag_precision
: Byte;
610 w_prior1
: Array [WP_FIRST
..WP_LAST
] of Byte;
611 w_prior2
: Array [WP_FIRST
..WP_LAST
] of Byte;
612 w_prior3
: Array [WP_FIRST
..WP_LAST
] of Byte;
616 TIME_RESPAWN1
= 1500;
617 TIME_RESPAWN2
= 2000;
618 TIME_RESPAWN3
= 3000;
619 PLAYER_SUIT_TIME
= 30000;
620 PLAYER_INVUL_TIME
= 30000;
621 PLAYER_INVIS_TIME
= 35000;
622 FRAG_COMBO_TIME
= 3000;
625 PLAYER_HEADRECT
: TRectWH
= (X
:24; Y
:12; Width
:20; Height
:12);
628 BOT_UNSAFEDIST
= 128;
629 DIFFICULT_EASY
: TDifficult
= (DiagFire
: 32; InvisFire
: 32; DiagPrecision
: 32;
630 FlyPrecision
: 32; Cover
: 32; CloseJump
: 32;
631 WeaponPrior
:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior
:(0,0,0,0,0,0,0,0,0,0,0));
632 DIFFICULT_MEDIUM
: TDifficult
= (DiagFire
: 127; InvisFire
: 127; DiagPrecision
: 127;
633 FlyPrecision
: 127; Cover
: 127; CloseJump
: 127;
634 WeaponPrior
:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior
:(0,0,0,0,0,0,0,0,0,0,0));
635 DIFFICULT_HARD
: TDifficult
= (DiagFire
: 255; InvisFire
: 255; DiagPrecision
: 255;
636 FlyPrecision
: 255; Cover
: 255; CloseJump
: 255;
637 WeaponPrior
:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior
:(0,0,0,0,0,0,0,0,0,0,0));
638 WEAPON_PRIOR1
: Array [WP_FIRST
..WP_LAST
] of Byte =
639 (WEAPON_FLAMETHROWER
, WEAPON_SUPERPULEMET
,
640 WEAPON_SHOTGUN2
, WEAPON_SHOTGUN1
,
641 WEAPON_CHAINGUN
, WEAPON_PLASMA
, WEAPON_ROCKETLAUNCHER
,
642 WEAPON_BFG
, WEAPON_PISTOL
, WEAPON_SAW
, WEAPON_KASTET
);
643 WEAPON_PRIOR2
: Array [WP_FIRST
..WP_LAST
] of Byte =
644 (WEAPON_FLAMETHROWER
, WEAPON_SUPERPULEMET
,
645 WEAPON_BFG
, WEAPON_ROCKETLAUNCHER
,
646 WEAPON_SHOTGUN2
, WEAPON_PLASMA
, WEAPON_SHOTGUN1
,
647 WEAPON_CHAINGUN
, WEAPON_PISTOL
, WEAPON_SAW
, WEAPON_KASTET
);
648 //WEAPON_PRIOR3: Array [WP_FIRST..WP_LAST] of Byte =
649 // (WEAPON_FLAMETHROWER, WEAPON_SUPERPULEMET,
650 // WEAPON_BFG, WEAPON_PLASMA, WEAPON_SHOTGUN2,
651 // WEAPON_CHAINGUN, WEAPON_SHOTGUN1, WEAPON_SAW,
652 // WEAPON_ROCKETLAUNCHER, WEAPON_PISTOL, WEAPON_KASTET);
653 WEAPON_RELOAD
: Array [WP_FIRST
..WP_LAST
] of Byte =
654 (5, 2, 6, 18, 36, 2, 12, 2, 14, 2, 2);
656 PLAYER_SIGNATURE
= $52594C50; // 'PLYR'
657 CORPSE_SIGNATURE
= $50524F43; // 'CORP'
659 BOTNAMES_FILENAME
= 'botnames.txt';
660 BOTLIST_FILENAME
= 'botlist.txt';
663 ForceModel
: Word = 0;
664 ForcedModelName
: String = STD_PLAYER_MODEL
;
665 BotNames
: Array of String;
666 BotList
: Array of TBotProfile
;
667 SavedStates
: Array of TPlayerSavedState
;
670 function Lerp(X
, Y
, Factor
: Integer): Integer;
672 Result
:= X
+ ((Y
- X
) div Factor
);
675 function SameTeam(UID1
, UID2
: Word): Boolean;
679 if (UID1
> UID_MAX_PLAYER
) or (UID1
<= UID_MAX_GAME
) or
680 (UID2
> UID_MAX_PLAYER
) or (UID2
<= UID_MAX_GAME
) then Exit
;
682 if (g_Player_Get(UID1
) = nil) or (g_Player_Get(UID2
) = nil) then Exit
;
684 if ((g_Player_Get(UID1
).Team
= TEAM_NONE
) or
685 (g_Player_Get(UID2
).Team
= TEAM_NONE
)) then Exit
;
687 Result
:= g_Player_Get(UID1
).FTeam
= g_Player_Get(UID2
).FTeam
;
690 procedure g_Force_Model_Set(Mode
: Word);
695 function g_Force_Model_Get(): Word;
697 Result
:= ForceModel
;
700 procedure g_Forced_Model_SetName(Model
: String);
702 ForcedModelName
:= Model
;
705 function g_Forced_Model_GetName(): String;
707 Result
:= ForcedModelName
;
710 function g_Player_Create(ModelName
: String; Color
: TRGB
; Team
: Byte; Bot
: Boolean): Word;
720 // Есть ли место в gPlayers:
721 if gPlayers
<> nil then
722 for a
:= 0 to High(gPlayers
) do
723 if gPlayers
[a
] = nil then
729 // Нет места - расширяем gPlayers:
732 SetLength(gPlayers
, Length(gPlayers
)+1);
736 // Создаем объект игрока:
738 gPlayers
[a
] := TBot
.Create()
740 gPlayers
[a
] := TPlayer
.Create();
743 gPlayers
[a
].FActualModelName
:= ModelName
;
744 gPlayers
[a
].SetModel(ModelName
);
745 if Bot
and (g_Force_Model_Get() <> 0) then
746 gPlayers
[a
].SetModel(g_Forced_Model_GetName());
748 // Нет модели - создание не возможно:
749 if gPlayers
[a
].FModel
= nil then
753 g_FatalError(Format(_lc
[I_GAME_ERROR_MODEL
], [ModelName
]));
757 if not (Team
in [TEAM_RED
, TEAM_BLUE
]) then
758 if Random(2) = 0 then
762 gPlayers
[a
].FPreferredTeam
:= Team
;
764 case gGameSettings
.GameMode
of
765 GM_DM
: gPlayers
[a
].FTeam
:= TEAM_NONE
;
767 GM_CTF
: gPlayers
[a
].FTeam
:= gPlayers
[a
].FPreferredTeam
;
769 GM_COOP
: gPlayers
[a
].FTeam
:= TEAM_COOP
;
772 // Если командная игра - красим модель в цвет команды:
773 gPlayers
[a
].FColor
:= Color
;
774 if gPlayers
[a
].FTeam
in [TEAM_RED
, TEAM_BLUE
] then
775 gPlayers
[a
].FModel
.Color
:= TEAMCOLOR
[gPlayers
[a
].FTeam
]
777 gPlayers
[a
].FModel
.Color
:= Color
;
779 gPlayers
[a
].FUID
:= g_CreateUID(UID_PLAYER
);
780 gPlayers
[a
].FAlive
:= False;
782 Result
:= gPlayers
[a
].FUID
;
785 function g_Player_CreateFromState (st
: TStream
): Word;
786 var a
: Integer; ok
, Bot
: Boolean; pos
: Int64;
790 // check signature and entity type
792 if not utils
.checkSign(st
, 'PLYR') then raise XStreamError
.Create('invalid player signature');
793 if (utils
.readByte(st
) <> PLR_SAVE_VERSION
) then raise XStreamError
.Create('invalid player version');
794 Bot
:= utils
.readBool(st
);
797 // find free player slot
799 for a
:= 0 to High(gPlayers
) do
800 if gPlayers
[a
] = nil then
806 // allocate player slot
809 SetLength(gPlayers
, Length(gPlayers
)+1);
813 // create entity and load state
816 gPlayers
[a
] := TBot
.Create();
817 if (g_Force_Model_Get() <> 0) then
818 gPlayers
[a
].SetModel(g_Forced_Model_GetName());
821 gPlayers
[a
] := TPlayer
.Create();
822 gPlayers
[a
].FPhysics
:= True; // ???
823 gPlayers
[a
].LoadState(st
);
825 result
:= gPlayers
[a
].FUID
;
829 procedure g_Player_ResetTeams();
833 if g_Game_IsClient
then
835 if gPlayers
= nil then
837 for a
:= Low(gPlayers
) to High(gPlayers
) do
838 if gPlayers
[a
] <> nil then
839 case gGameSettings
.GameMode
of
841 gPlayers
[a
].ChangeTeam(TEAM_NONE
);
843 if not (gPlayers
[a
].Team
in [TEAM_RED
, TEAM_BLUE
]) then
844 if gPlayers
[a
].FPreferredTeam
in [TEAM_RED
, TEAM_BLUE
] then
845 gPlayers
[a
].ChangeTeam(gPlayers
[a
].FPreferredTeam
)
848 gPlayers
[a
].ChangeTeam(TEAM_RED
)
850 gPlayers
[a
].ChangeTeam(TEAM_BLUE
);
853 gPlayers
[a
].ChangeTeam(TEAM_COOP
);
857 procedure g_Bot_Add(Team
, Difficult
: Byte; Handicap
: Integer = 100);
860 _name
, _model
: String;
863 if not g_Game_IsServer
then Exit
;
865 if (g_Bot_GetCount() >= gMaxBots
) then Exit
;
867 // Список названий моделей:
868 m
:= g_PlayerModel_GetNames();
873 if (gGameSettings
.GameType
= GT_SINGLE
) or (gGameSettings
.GameMode
= GM_COOP
) then
874 Team
:= TEAM_COOP
// COOP
876 if gGameSettings
.GameMode
= GM_DM
then
877 Team
:= TEAM_NONE
// DM
879 if Team
= TEAM_NONE
then // CTF / TDM
881 // Автобаланс команд:
885 for a
:= 0 to High(gPlayers
) do
886 if gPlayers
[a
] <> nil then
888 if gPlayers
[a
].Team
= TEAM_RED
then
891 if gPlayers
[a
].Team
= TEAM_BLUE
then
901 if Random(2) = 0 then
907 // Выбираем боту имя:
909 if BotNames
<> nil then
910 for a
:= 0 to High(BotNames
) do
911 if g_Player_ValidName(BotNames
[a
]) then
913 _name
:= BotNames
[a
];
917 // Выбираем случайную модель:
918 _model
:= m
[Random(Length(m
))];
921 with g_Player_Get(g_Player_Create(_model
,
922 _RGB(Min(Random(9)*32, 255),
923 Min(Random(9)*32, 255),
924 Min(Random(9)*32, 255)),
925 Team
, True)) as TBot
do
927 // Если имени нет, делаем его из UID бота
929 Name
:= Format('DFBOT%.5d', [UID
])
934 1: FDifficult
:= DIFFICULT_EASY
;
935 2: FDifficult
:= DIFFICULT_MEDIUM
;
936 else FDifficult
:= DIFFICULT_HARD
;
939 for a
:= WP_FIRST
to WP_LAST
do
941 FDifficult
.WeaponPrior
[a
] := WEAPON_PRIOR1
[a
];
942 FDifficult
.CloseWeaponPrior
[a
] := WEAPON_PRIOR2
[a
];
943 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
946 FHandicap
:= Handicap
;
948 g_Console_Add(Format(_lc
[I_PLAYER_JOIN
], [Name
]), True);
950 if g_Game_IsNet
then MH_SEND_PlayerCreate(UID
);
951 if g_Game_IsServer
and (gGameSettings
.MaxLives
> 0) then
956 procedure g_Bot_AddList(Team
: Byte; lName
: ShortString; num
: Integer = -1; Handicap
: Integer = 100);
959 _name
, _model
: String;
962 if not g_Game_IsServer
then Exit
;
964 if (g_Bot_GetCount() >= gMaxBots
) then Exit
;
966 // Список названий моделей:
967 m
:= g_PlayerModel_GetNames();
972 if (gGameSettings
.GameType
= GT_SINGLE
) or (gGameSettings
.GameMode
= GM_COOP
) then
973 Team
:= TEAM_COOP
// COOP
975 if gGameSettings
.GameMode
= GM_DM
then
976 Team
:= TEAM_NONE
// DM
978 if Team
= TEAM_NONE
then
979 Team
:= BotList
[num
].team
; // CTF / TDM
981 // Выбираем настройки бота из списка по номеру или имени:
982 lName
:= AnsiLowerCase(lName
);
983 if (num
< 0) or (num
> Length(BotList
)-1) then
985 if (num
= -1) and (lName
<> '') and (BotList
<> nil) then
986 for a
:= 0 to High(BotList
) do
987 if AnsiLowerCase(BotList
[a
].name
) = lName
then
996 _name
:= BotList
[num
].name
;
997 // Занято - выбираем случайное:
998 if not g_Player_ValidName(_name
) then
1000 _name
:= Format('DFBOT%.2d', [Random(100)]);
1001 until g_Player_ValidName(_name
);
1004 _model
:= BotList
[num
].model
;
1005 // Нет такой - выбираем случайную:
1006 if not InSArray(_model
, m
) then
1007 _model
:= m
[Random(Length(m
))];
1010 with g_Player_Get(g_Player_Create(_model
, BotList
[num
].color
, Team
, True)) as TBot
do
1014 FDifficult
.DiagFire
:= BotList
[num
].diag_fire
;
1015 FDifficult
.InvisFire
:= BotList
[num
].invis_fire
;
1016 FDifficult
.DiagPrecision
:= BotList
[num
].diag_precision
;
1017 FDifficult
.FlyPrecision
:= BotList
[num
].fly_precision
;
1018 FDifficult
.Cover
:= BotList
[num
].cover
;
1019 FDifficult
.CloseJump
:= BotList
[num
].close_jump
;
1021 FHandicap
:= Handicap
;
1023 for a
:= WP_FIRST
to WP_LAST
do
1025 FDifficult
.WeaponPrior
[a
] := BotList
[num
].w_prior1
[a
];
1026 FDifficult
.CloseWeaponPrior
[a
] := BotList
[num
].w_prior2
[a
];
1027 //FDifficult.SafeWeaponPrior[a] := BotList[num].w_prior3[a];
1030 g_Console_Add(Format(_lc
[I_PLAYER_JOIN
], [Name
]), True);
1032 if g_Game_IsNet
then MH_SEND_PlayerCreate(UID
);
1036 procedure g_Bot_RemoveAll();
1040 if not g_Game_IsServer
then Exit
;
1041 if gPlayers
= nil then Exit
;
1043 for a
:= 0 to High(gPlayers
) do
1044 if gPlayers
[a
] <> nil then
1045 if gPlayers
[a
] is TBot
then
1047 gPlayers
[a
].Lives
:= 0;
1048 gPlayers
[a
].Kill(K_SIMPLEKILL
, 0, HIT_DISCON
);
1049 g_Console_Add(Format(_lc
[I_PLAYER_LEAVE
], [gPlayers
[a
].Name
]), True);
1050 g_Player_Remove(gPlayers
[a
].FUID
);
1056 procedure g_Bot_MixNames();
1061 if BotNames
<> nil then
1062 for a
:= 0 to High(BotNames
) do
1064 b
:= Random(Length(BotNames
));
1066 Botnames
[a
] := BotNames
[b
];
1071 procedure g_Player_Remove(UID
: Word);
1075 if gPlayers
= nil then Exit
;
1077 if g_Game_IsServer
and g_Game_IsNet
then
1078 MH_SEND_PlayerDelete(UID
);
1080 for i
:= 0 to High(gPlayers
) do
1081 if gPlayers
[i
] <> nil then
1082 if gPlayers
[i
].FUID
= UID
then
1084 if gPlayers
[i
] is TPlayer
then
1085 TPlayer(gPlayers
[i
]).Free()
1087 TBot(gPlayers
[i
]).Free();
1093 procedure g_Player_Init();
1104 path
:= BOTNAMES_FILENAME
;
1105 if e_FindResource(DataDirs
, path
) = false then
1108 // Читаем возможные имена ботов из файла:
1109 AssignFile(F
, path
);
1120 SetLength(BotNames
, Length(BotNames
)+1);
1121 BotNames
[High(BotNames
)] := s
;
1129 // Читаем файл с параметрами ботов:
1130 config
:= TConfig
.CreateFile(path
);
1134 while config
.SectionExists(IntToStr(a
)) do
1136 SetLength(BotList
, Length(BotList
)+1);
1138 with BotList
[High(BotList
)] do
1141 name
:= config
.ReadStr(IntToStr(a
), 'name', '');
1143 model
:= config
.ReadStr(IntToStr(a
), 'model', '');
1145 if config
.ReadStr(IntToStr(a
), 'team', 'red') = 'red' then
1150 sa
:= parse(config
.ReadStr(IntToStr(a
), 'color', ''));
1151 color
.R
:= StrToIntDef(sa
[0], 0);
1152 color
.G
:= StrToIntDef(sa
[1], 0);
1153 color
.B
:= StrToIntDef(sa
[2], 0);
1154 // Вероятность стрельбы под углом:
1155 diag_fire
:= config
.ReadInt(IntToStr(a
), 'diag_fire', 0);
1156 // Вероятность ответного огня по невидимому сопернику:
1157 invis_fire
:= config
.ReadInt(IntToStr(a
), 'invis_fire', 0);
1158 // Точность стрельбы под углом:
1159 diag_precision
:= config
.ReadInt(IntToStr(a
), 'diag_precision', 0);
1160 // Точность стрельбы в полете:
1161 fly_precision
:= config
.ReadInt(IntToStr(a
), 'fly_precision', 0);
1162 // Точность уклонения от снарядов:
1163 cover
:= config
.ReadInt(IntToStr(a
), 'cover', 0);
1164 // Вероятность прыжка при приближении соперника:
1165 close_jump
:= config
.ReadInt(IntToStr(a
), 'close_jump', 0);
1166 // Приоритеты оружия для дальнего боя:
1167 sa
:= parse(config
.ReadStr(IntToStr(a
), 'w_prior1', ''));
1168 if Length(sa
) = 10 then
1170 w_prior1
[b
] := EnsureRange(StrToInt(sa
[b
]), 0, 9);
1171 // Приоритеты оружия для ближнего боя:
1172 sa
:= parse(config
.ReadStr(IntToStr(a
), 'w_prior2', ''));
1173 if Length(sa
) = 10 then
1175 w_prior2
[b
] := EnsureRange(StrToInt(sa
[b
]), 0, 9);
1177 {sa := parse(config.ReadStr(IntToStr(a), 'w_prior3', ''));
1178 if Length(sa) = 10 then
1180 w_prior3[b] := EnsureRange(StrToInt(sa[b]), 0, 9);}
1187 SetLength(SavedStates
, 0);
1190 procedure g_Player_Free();
1194 if gPlayers
<> nil then
1196 for i
:= 0 to High(gPlayers
) do
1197 if gPlayers
[i
] <> nil then
1199 if gPlayers
[i
] is TPlayer
then
1200 TPlayer(gPlayers
[i
]).Free()
1202 TBot(gPlayers
[i
]).Free();
1211 SetLength(SavedStates
, 0);
1214 procedure g_Player_PreUpdate();
1218 if gPlayers
= nil then Exit
;
1219 for i
:= 0 to High(gPlayers
) do
1220 if gPlayers
[i
] <> nil then
1221 gPlayers
[i
].PreUpdate();
1224 procedure g_Player_UpdateAll();
1228 if gPlayers
= nil then Exit
;
1230 //e_WriteLog('***g_Player_UpdateAll: ENTER', MSG_WARNING);
1231 for i
:= 0 to High(gPlayers
) do
1233 if gPlayers
[i
] <> nil then
1235 if gPlayers
[i
] is TPlayer
then
1237 gPlayers
[i
].Update();
1238 gPlayers
[i
].RealizeCurrentWeapon(); // WARNING! DO NOT MOVE THIS INTO `Update()`!
1242 // bot updates weapons in `UpdateCombat()`
1243 TBot(gPlayers
[i
]).Update();
1247 //e_WriteLog('***g_Player_UpdateAll: EXIT', MSG_WARNING);
1250 function g_Player_Get(UID
: Word): TPlayer
;
1256 if gPlayers
= nil then
1259 for a
:= 0 to High(gPlayers
) do
1260 if gPlayers
[a
] <> nil then
1261 if gPlayers
[a
].FUID
= UID
then
1263 Result
:= gPlayers
[a
];
1268 function g_Player_GetCount(): Byte;
1274 if gPlayers
= nil then
1277 for a
:= 0 to High(gPlayers
) do
1278 if gPlayers
[a
] <> nil then
1279 Result
:= Result
+ 1;
1282 function g_Bot_GetCount(): Integer;
1288 if gPlayers
= nil then
1291 for a
:= 0 to High(gPlayers
) do
1292 if (gPlayers
[a
] <> nil) and (gPlayers
[a
] is TBot
) then
1293 Result
:= Result
+ 1;
1296 function g_Player_GetStats(): TPlayerStatArray
;
1302 if gPlayers
= nil then Exit
;
1304 for a
:= 0 to High(gPlayers
) do
1305 if gPlayers
[a
] <> nil then
1307 SetLength(Result
, Length(Result
)+1);
1308 with Result
[High(Result
)] do
1311 Ping
:= gPlayers
[a
].FPing
;
1312 Loss
:= gPlayers
[a
].FLoss
;
1313 Name
:= gPlayers
[a
].FName
;
1314 Team
:= gPlayers
[a
].FTeam
;
1315 Frags
:= gPlayers
[a
].FFrags
;
1316 Deaths
:= gPlayers
[a
].FDeath
;
1317 Kills
:= gPlayers
[a
].FKills
;
1318 Color
:= gPlayers
[a
].FModel
.Color
;
1319 Lives
:= gPlayers
[a
].FLives
;
1320 Spectator
:= gPlayers
[a
].FSpectator
;
1321 UID
:= gPlayers
[a
].FUID
;
1326 procedure g_Player_ResetReady();
1330 if not g_Game_IsServer
then Exit
;
1331 if gPlayers
= nil then Exit
;
1333 for a
:= 0 to High(gPlayers
) do
1334 if gPlayers
[a
] <> nil then
1336 gPlayers
[a
].FReady
:= False;
1337 if g_Game_IsNet
then
1338 MH_SEND_GameEvent(NET_EV_INTER_READY
, gPlayers
[a
].UID
, 'N');
1342 procedure g_Player_RememberAll
;
1346 for i
:= Low(gPlayers
) to High(gPlayers
) do
1347 if (gPlayers
[i
] <> nil) and gPlayers
[i
].alive
then
1348 gPlayers
[i
].RememberState
;
1351 procedure g_Player_ResetAll(Force
, Silent
: Boolean);
1355 gTeamStat
[TEAM_RED
].Score
:= 0;
1356 gTeamStat
[TEAM_BLUE
].Score
:= 0;
1358 if gPlayers
<> nil then
1359 for i
:= 0 to High(gPlayers
) do
1360 if gPlayers
[i
] <> nil then
1362 gPlayers
[i
].Reset(Force
);
1364 if gPlayers
[i
] is TPlayer
then
1366 if (not gPlayers
[i
].FSpectator
) or gPlayers
[i
].FWantsInGame
then
1367 gPlayers
[i
].Respawn(Silent
)
1369 gPlayers
[i
].Spectate();
1372 TBot(gPlayers
[i
]).Respawn(Silent
);
1378 function TPlayer
.isValidViewPort (): Boolean; inline; begin result
:= (viewPortW
> 0) and (viewPortH
> 0); end;
1380 procedure TPlayer
.BFGHit();
1382 g_Weapon_BFGHit(FObj
.X
+FObj
.Rect
.X
+(FObj
.Rect
.Width
div 2),
1383 FObj
.Y
+FObj
.Rect
.Y
+(FObj
.Rect
.Height
div 2));
1384 if g_Game_IsServer
and g_Game_IsNet
then
1385 MH_SEND_Effect(FObj
.X
+FObj
.Rect
.X
+(FObj
.Rect
.Width
div 2),
1386 FObj
.Y
+FObj
.Rect
.Y
+(FObj
.Rect
.Height
div 2),
1390 procedure TPlayer
.ChangeModel(ModelName
: string);
1392 locModel
: TPlayerModel
;
1394 locModel
:= g_PlayerModel_Get(ModelName
);
1395 if locModel
= nil then Exit
;
1401 procedure TPlayer
.SetModel(ModelName
: string);
1405 m
:= g_PlayerModel_Get(ModelName
);
1408 g_SimpleError(Format(_lc
[I_GAME_ERROR_MODEL_FALLBACK
], [ModelName
]));
1409 m
:= g_PlayerModel_Get('doomer');
1412 g_FatalError(Format(_lc
[I_GAME_ERROR_MODEL
], ['doomer']));
1417 if FModel
<> nil then
1422 if not (gGameSettings
.GameMode
in [GM_TDM
, GM_CTF
]) then
1423 FModel
.Color
:= FColor
1425 FModel
.Color
:= TEAMCOLOR
[FTeam
];
1426 FModel
.SetWeapon(FCurrWeap
);
1427 FModel
.SetFlag(FFlag
);
1428 SetDirection(FDirection
);
1431 procedure TPlayer
.SetColor(Color
: TRGB
);
1434 if not (gGameSettings
.GameMode
in [GM_TDM
, GM_CTF
]) then
1435 if FModel
<> nil then FModel
.Color
:= Color
;
1440 function TPlayer
.GetColor(): TRGB
;
1442 result
:= FModel
.Color
;
1445 procedure TPlayer
.SetWeaponPrefs(Prefs
: Array of Byte);
1449 for i
:= WP_FIRST
to WP_LAST
+ 1 do
1451 if (Prefs
[i
] > WP_LAST
+ 1) then
1452 FWeapPreferences
[i
] := 0
1454 FWeapPreferences
[i
] := Prefs
[i
];
1458 procedure TPlayer
.SetWeaponPref(Weapon
, Pref
: Byte);
1460 if (Weapon
> WP_LAST
+ 1) then
1462 else if (Pref
<= WP_LAST
+ 1) and (Weapon
<= WP_LAST
+ 1) then
1463 FWeapPreferences
[Weapon
] := Pref
1464 else if (Weapon
<= WP_LAST
+ 1) and (Pref
> WP_LAST
+ 1) then
1465 FWeapPreferences
[Weapon
] := 0;
1468 function TPlayer
.GetWeaponPref(Weapon
: Byte) : Byte;
1470 if (Weapon
> WP_LAST
+ 1) then
1472 else if (FWeapPreferences
[Weapon
] > WP_LAST
+ 1) then
1475 result
:= FWeapPreferences
[Weapon
];
1478 function TPlayer
.GetMorePrefered() : Byte;
1480 testedWeap
, i
: Byte;
1482 testedWeap
:= FCurrWeap
;
1483 for i
:= WP_FIRST
to WP_LAST
do
1484 if FWeapon
[i
] and maySwitch(i
) and (FWeapPreferences
[i
] > FWeapPreferences
[testedWeap
]) then
1486 if (R_BERSERK
in FRulez
) and (FWeapPreferences
[WP_LAST
+ 1] > FWeapPreferences
[testedWeap
]) then
1487 testedWeap
:= WEAPON_KASTET
;
1488 result
:= testedWeap
;
1491 function TPlayer
.maySwitch(Weapon
: Byte) : Boolean;
1494 if (Weapon
= WEAPON_KASTET
) and (FSkipFist
<> 0) then
1496 if (FSkipFist
= 1) and (not (R_BERSERK
in FRulez
)) then
1499 else if (FSwitchToEmpty
= 0) and (not hasAmmoForShooting(Weapon
)) then
1503 procedure TPlayer
.SwitchTeam
;
1505 if g_Game_IsClient
then
1507 if not (gGameSettings
.GameMode
in [GM_TDM
, GM_CTF
]) then Exit
;
1509 if gGameOn
and FAlive
then
1510 Kill(K_SIMPLEKILL
, FUID
, HIT_SELF
);
1512 if FTeam
= TEAM_RED
then
1514 ChangeTeam(TEAM_BLUE
);
1515 g_Console_Add(Format(_lc
[I_PLAYER_CHTEAM_BLUE
], [FName
]), True);
1516 if g_Game_IsNet
then
1517 MH_SEND_GameEvent(NET_EV_CHANGE_TEAM
, TEAM_BLUE
, FName
);
1521 ChangeTeam(TEAM_RED
);
1522 g_Console_Add(Format(_lc
[I_PLAYER_CHTEAM_RED
], [FName
]), True);
1523 if g_Game_IsNet
then
1524 MH_SEND_GameEvent(NET_EV_CHANGE_TEAM
, TEAM_RED
, FName
);
1526 FPreferredTeam
:= FTeam
;
1529 procedure TPlayer
.ChangeTeam(Team
: Byte);
1536 TEAM_RED
, TEAM_BLUE
:
1537 FModel
.Color
:= TEAMCOLOR
[Team
];
1539 FModel
.Color
:= FColor
;
1541 if (FTeam
<> OldTeam
) and g_Game_IsNet
and g_Game_IsServer
then
1542 MH_SEND_PlayerStats(FUID
);
1546 procedure TPlayer.CollideItem();
1551 if gItems = nil then Exit;
1552 if not FAlive then Exit;
1554 for i := 0 to High(gItems) do
1557 if (ItemType <> ITEM_NONE) and alive then
1558 if g_Obj_Collide(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width,
1559 PLAYER_RECT.Height, @Obj) then
1561 if not PickItem(ItemType, gItems[i].Respawnable, r) then Continue;
1563 if ItemType in [ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_INVUL] then
1564 g_Sound_PlayExAt('SOUND_ITEM_GETRULEZ', FObj.X, FObj.Y)
1565 else if ItemType in [ITEM_MEDKIT_SMALL, ITEM_MEDKIT_LARGE, ITEM_MEDKIT_BLACK] then
1566 g_Sound_PlayExAt('SOUND_ITEM_GETMED', FObj.X, FObj.Y)
1567 else g_Sound_PlayExAt('SOUND_ITEM_GETITEM', FObj.X, FObj.Y);
1569 // Надо убрать с карты, если это не ключ, которым нужно поделится с другим игроком:
1570 if r and not ((ItemType in [ITEM_KEY_RED, ITEM_KEY_GREEN, ITEM_KEY_BLUE]) and
1571 (gGameSettings.GameType = GT_SINGLE) and
1572 (g_Player_GetCount() > 1)) then
1573 if not Respawnable then g_Items_Remove(i) else g_Items_Pick(i);
1579 function TPlayer
.CollideLevel(XInc
, YInc
: Integer): Boolean;
1581 Result
:= g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
,
1582 PLAYER_RECT
.Width
, PLAYER_RECT
.Height
, PANEL_WALL
,
1586 constructor TPlayer
.Create();
1592 mEDamageType
:= HIT_SOME
;
1598 FSawSound
:= TPlayableSound
.Create();
1599 FSawSoundIdle
:= TPlayableSound
.Create();
1600 FSawSoundHit
:= TPlayableSound
.Create();
1601 FSawSoundSelect
:= TPlayableSound
.Create();
1602 FFlameSoundOn
:= TPlayableSound
.Create();
1603 FFlameSoundOff
:= TPlayableSound
.Create();
1604 FFlameSoundWork
:= TPlayableSound
.Create();
1605 FJetSoundFly
:= TPlayableSound
.Create();
1606 FJetSoundOn
:= TPlayableSound
.Create();
1607 FJetSoundOff
:= TPlayableSound
.Create();
1609 FSawSound
.SetByName('SOUND_WEAPON_FIRESAW');
1610 FSawSoundIdle
.SetByName('SOUND_WEAPON_IDLESAW');
1611 FSawSoundHit
.SetByName('SOUND_WEAPON_HITSAW');
1612 FSawSoundSelect
.SetByName('SOUND_WEAPON_SELECTSAW');
1613 FFlameSoundOn
.SetByName('SOUND_WEAPON_FLAMEON');
1614 FFlameSoundOff
.SetByName('SOUND_WEAPON_FLAMEOFF');
1615 FFlameSoundWork
.SetByName('SOUND_WEAPON_FLAMEWORK');
1616 FJetSoundFly
.SetByName('SOUND_PLAYER_JETFLY');
1617 FJetSoundOn
.SetByName('SOUND_PLAYER_JETON');
1618 FJetSoundOff
.SetByName('SOUND_PLAYER_JETOFF');
1620 FSpectatePlayer
:= -1;
1624 FSavedStateNum
:= -1;
1625 {$IFDEF ENABLE_SHELLS}
1633 {$IFDEF ENABLE_CORPSES}
1637 FActualModelName
:= 'doomer';
1640 FObj
.Rect
:= PLAYER_RECT
;
1642 FBFGFireCounter
:= -1;
1643 FJustTeleported
:= False;
1646 FWaitForFirstSpawn
:= false;
1647 FPunchAnim
:= TAnimationState
.Create(False, 1, 4);
1653 procedure TPlayer
.positionChanged (); inline;
1657 procedure TPlayer
.doDamage (v
: Integer);
1659 if (v
<= 0) then exit
;
1660 if (v
> 32767) then v
:= 32767;
1661 Damage(v
, 0, 0, 0, mEDamageType
);
1664 procedure TPlayer
.Damage(value
: Word; SpawnerUID
: Word; vx
, vy
: Integer; t
: Byte);
1668 if (not g_Game_IsClient
) and (not FAlive
) then
1673 // Неуязвимость не спасает от ловушек:
1674 if ((t
= HIT_TRAP
) or (t
= HIT_SELF
)) and (not FGodMode
) then
1676 if not g_Game_IsClient
then
1679 if t
= HIT_TRAP
then
1681 // Ловушка убивает сразу:
1683 Kill(K_EXTRAHARDKILL
, SpawnerUID
, t
);
1685 if t
= HIT_SELF
then
1689 Kill(K_SIMPLEKILL
, SpawnerUID
, t
);
1692 // Обнулить действия примочек, чтобы фон пропал
1693 FMegaRulez
[MR_SUIT
] := 0;
1694 FMegaRulez
[MR_INVUL
] := 0;
1695 FMegaRulez
[MR_INVIS
] := 0;
1700 // Но от остального спасает:
1701 if FMegaRulez
[MR_INVUL
] >= gTime
then
1708 // Если есть урон своим, или ранил сам себя, или тебя ранил противник:
1709 if LongBool(gGameSettings
.Options
and GAME_OPTION_TEAMDAMAGE
) or
1710 (SpawnerUID
= FUID
) or
1711 (not SameTeam(FUID
, SpawnerUID
)) then
1713 FLastSpawnerUID
:= SpawnerUID
;
1715 // Кровь (пузырьки, если в воде):
1716 if gBloodCount
> 0 then
1718 c
:= Min(value
, 200)*gBloodCount
+ Random(Min(value
, 200) div 2);
1719 if value
div 4 <= c
then
1720 c
:= c
- (value
div 4)
1724 if (t
= HIT_SOME
) and (vx
= 0) and (vy
= 0) then
1728 HIT_TRAP
, HIT_ACID
, HIT_FLAME
, HIT_SELF
: MakeBloodSimple(c
);
1729 HIT_BFG
, HIT_ROCKET
, HIT_SOME
: MakeBloodVector(c
, vx
, vy
);
1733 if t
= HIT_WATER
then
1735 g_GFX_Bubbles(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2),
1736 FObj
.Y
+PLAYER_RECT
.Y
-4, value
div 2, 8, 4);
1738 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj
.X
, FObj
.Y
)
1739 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj
.X
, FObj
.Y
);
1746 Inc(FDamageBuffer
, value
);
1750 FPain
:= FPain
+ value
;
1753 if g_Game_IsServer
and g_Game_IsNet
then
1755 MH_SEND_PlayerDamage(FUID
, t
, SpawnerUID
, value
, vx
, vy
);
1756 MH_SEND_PlayerStats(FUID
);
1757 MH_SEND_PlayerPos(False, FUID
);
1761 function TPlayer
.Heal(value
: Word; Soft
: Boolean): Boolean;
1764 if g_Game_IsClient
then
1769 if Soft
and (FHealth
< PLAYER_HP_SOFT
) then
1771 IncMax(FHealth
, value
, PLAYER_HP_SOFT
);
1774 if (not Soft
) and (FHealth
< PLAYER_HP_LIMIT
) then
1776 IncMax(FHealth
, value
, PLAYER_HP_LIMIT
);
1780 if Result
and g_Game_IsServer
and g_Game_IsNet
then
1781 MH_SEND_PlayerStats(FUID
);
1784 destructor TPlayer
.Destroy();
1786 if (gPlayer1
<> nil) and (gPlayer1
.FUID
= FUID
) then
1788 if (gPlayer2
<> nil) and (gPlayer2
.FUID
= FUID
) then
1792 FSawSoundIdle
.Free();
1793 FSawSoundHit
.Free();
1794 FSawSoundSelect
.Free();
1795 FFlameSoundOn
.Free();
1796 FFlameSoundOff
.Free();
1797 FFlameSoundWork
.Free();
1798 FJetSoundFly
.Free();
1800 FJetSoundOff
.Free();
1807 procedure TPlayer
.DoPunch();
1813 procedure TPlayer
.Fire();
1815 f
, DidFire
: Boolean;
1816 wx
, wy
, xd
, yd
: Integer;
1819 if g_Game_IsClient
then Exit
;
1820 // FBFGFireCounter - время перед выстрелом (для BFG)
1821 // FReloading - время после выстрела (для всего)
1829 if FReloading
[FCurrWeap
] <> 0 then Exit
;
1834 wx
:= FObj
.X
+WEAPONPOINT
[FDirection
].X
;
1835 wy
:= FObj
.Y
+WEAPONPOINT
[FDirection
].Y
;
1836 xd
:= wx
+IfThen(FDirection
= TDirection
.D_LEFT
, -30, 30);
1837 yd
:= wy
+firediry();
1843 if R_BERSERK
in FRulez
then
1845 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
1846 locobj
.X
:= FObj
.X
+FObj
.Rect
.X
;
1847 locobj
.Y
:= FObj
.Y
+FObj
.Rect
.Y
;
1850 locobj
.rect
.Width
:= 39;
1851 locobj
.rect
.Height
:= 52;
1852 locobj
.Vel
.X
:= (xd
-wx
) div 2;
1853 locobj
.Vel
.Y
:= (yd
-wy
) div 2;
1854 locobj
.Accel
.X
:= xd
-wx
;
1855 locobj
.Accel
.y
:= yd
-wy
;
1857 if g_Weapon_Hit(@locobj
, 50, FUID
, HIT_SOME
) <> 0 then
1858 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj
.X
, FObj
.Y
)
1860 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj
.X
, FObj
.Y
);
1862 if (gFlash
= 1) and (FPain
< 50) then FPain
:= min(FPain
+ 25, 50);
1866 g_Weapon_punch(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
, 3, FUID
);
1870 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
1875 if g_Weapon_chainsaw(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
,
1876 IfThen(gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
], 9, 3), FUID
) <> 0 then
1878 FSawSoundSelect
.Stop();
1880 FSawSoundHit
.PlayAt(FObj
.X
, FObj
.Y
);
1882 else if not FSawSoundHit
.IsPlaying() then
1884 FSawSoundSelect
.Stop();
1885 FSawSound
.PlayAt(FObj
.X
, FObj
.Y
);
1888 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
1894 if FAmmo
[A_BULLETS
] > 0 then
1896 g_Weapon_pistol(wx
, wy
, xd
, yd
, FUID
);
1897 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
1898 Dec(FAmmo
[A_BULLETS
]);
1899 FFireAngle
:= FAngle
;
1902 {$IFDEF ENABLE_SHELLS}
1903 g_Shells_Create(GameX
+ PLAYER_RECT_CX
, GameY
+ PLAYER_RECT_CX
, GameVelX
, GameVelY
- 2, SHELL_BULLET
);
1908 if FAmmo
[A_SHELLS
] > 0 then
1910 g_Weapon_shotgun(wx
, wy
, xd
, yd
, FUID
);
1911 if not gSoundEffectsDF
then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', wx
, wy
);
1912 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
1913 Dec(FAmmo
[A_SHELLS
]);
1914 FFireAngle
:= FAngle
;
1917 {$IFDEF ENABLE_SHELLS}
1919 FShellType
:= SHELL_SHELL
;
1924 if FAmmo
[A_SHELLS
] >= 2 then
1926 g_Weapon_dshotgun(wx
, wy
, xd
, yd
, FUID
);
1927 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
1928 Dec(FAmmo
[A_SHELLS
], 2);
1929 FFireAngle
:= FAngle
;
1932 {$IFDEF ENABLE_SHELLS}
1934 FShellType
:= SHELL_DBLSHELL
;
1939 if FAmmo
[A_BULLETS
] > 0 then
1941 g_Weapon_mgun(wx
, wy
, xd
, yd
, FUID
);
1942 if not gSoundEffectsDF
then g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', wx
, wy
);
1943 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
1944 Dec(FAmmo
[A_BULLETS
]);
1945 FFireAngle
:= FAngle
;
1948 {$IFDEF ENABLE_SHELLS}
1949 g_Shells_Create(GameX
+ PLAYER_RECT_CX
, GameY
+ PLAYER_RECT_CX
, GameVelX
, GameVelY
- 2, SHELL_BULLET
);
1953 WEAPON_ROCKETLAUNCHER
:
1954 if FAmmo
[A_ROCKETS
] > 0 then
1956 g_Weapon_rocket(wx
, wy
, xd
, yd
, FUID
);
1957 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
1958 Dec(FAmmo
[A_ROCKETS
]);
1959 FFireAngle
:= FAngle
;
1965 if FAmmo
[A_CELLS
] > 0 then
1967 g_Weapon_plasma(wx
, wy
, xd
, yd
, FUID
);
1968 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
1969 Dec(FAmmo
[A_CELLS
]);
1970 FFireAngle
:= FAngle
;
1976 if (FAmmo
[A_CELLS
] >= 40) and (FBFGFireCounter
= -1) then
1978 FBFGFireCounter
:= 17;
1979 if not FNoReload
then
1980 g_Sound_PlayExAt('SOUND_WEAPON_STARTFIREBFG', FObj
.X
, FObj
.Y
);
1981 Dec(FAmmo
[A_CELLS
], 40);
1985 WEAPON_SUPERPULEMET
:
1986 if FAmmo
[A_SHELLS
] > 0 then
1988 g_Weapon_shotgun(wx
, wy
, xd
, yd
, FUID
);
1989 if not gSoundEffectsDF
then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx
, wy
);
1990 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
1991 Dec(FAmmo
[A_SHELLS
]);
1992 FFireAngle
:= FAngle
;
1995 {$IFDEF ENABLE_SHELLS}
1996 g_Shells_Create(GameX
+ PLAYER_RECT_CX
, GameY
+ PLAYER_RECT_CX
, GameVelX
, GameVelY
- 2, SHELL_SHELL
);
2000 WEAPON_FLAMETHROWER
:
2001 if FAmmo
[A_FUEL
] > 0 then
2003 g_Weapon_flame(wx
, wy
, xd
, yd
, FUID
);
2005 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2007 FFireAngle
:= FAngle
;
2014 if g_Game_IsNet
and g_Game_IsServer
then MH_SEND_PlayerStats(FUID
);
2018 if g_Game_IsNet
then
2022 if FCurrWeap
<> WEAPON_BFG
then
2023 MH_SEND_PlayerFire(FUID
, FCurrWeap
, wx
, wy
, xd
, yd
, LastShotID
)
2025 if not FNoReload
then
2026 MH_SEND_Sound(FObj
.X
, FObj
.Y
, 'SOUND_WEAPON_STARTFIREBFG');
2029 MH_SEND_PlayerStats(FUID
);
2034 if (FAngle
= 0) or (FAngle
= 180) then SetAction(A_ATTACK
)
2035 else if (FAngle
= ANGLE_LEFTDOWN
) or (FAngle
= ANGLE_RIGHTDOWN
) then SetAction(A_ATTACKDOWN
)
2036 else if (FAngle
= ANGLE_LEFTUP
) or (FAngle
= ANGLE_RIGHTUP
) then SetAction(A_ATTACKUP
);
2039 function TPlayer
.GetAmmoByWeapon(Weapon
: Byte): Word;
2042 WEAPON_PISTOL
, WEAPON_CHAINGUN
: Result
:= FAmmo
[A_BULLETS
];
2043 WEAPON_SHOTGUN1
, WEAPON_SHOTGUN2
, WEAPON_SUPERPULEMET
: Result
:= FAmmo
[A_SHELLS
];
2044 WEAPON_ROCKETLAUNCHER
: Result
:= FAmmo
[A_ROCKETS
];
2045 WEAPON_PLASMA
, WEAPON_BFG
: Result
:= FAmmo
[A_CELLS
];
2046 WEAPON_FLAMETHROWER
: Result
:= FAmmo
[A_FUEL
];
2051 function TPlayer
.HeadInLiquid(XInc
, YInc
: Integer): Boolean;
2053 Result
:= g_Map_CollidePanel(FObj
.X
+PLAYER_HEADRECT
.X
+XInc
, FObj
.Y
+PLAYER_HEADRECT
.Y
+YInc
,
2054 PLAYER_HEADRECT
.Width
, PLAYER_HEADRECT
.Height
,
2055 PANEL_WATER
or PANEL_ACID1
or PANEL_ACID2
, True);
2058 procedure TPlayer
.FlamerOn
;
2060 FFlameSoundOff
.Stop();
2061 FFlameSoundOff
.SetPosition(0);
2064 if (not FFlameSoundOn
.IsPlaying()) and (not FFlameSoundWork
.IsPlaying()) then
2065 FFlameSoundWork
.PlayAt(FObj
.X
, FObj
.Y
);
2069 FFlameSoundOn
.PlayAt(FObj
.X
, FObj
.Y
);
2074 procedure TPlayer
.FlamerOff
;
2078 FFlameSoundOn
.Stop();
2079 FFlameSoundOn
.SetPosition(0);
2080 FFlameSoundWork
.Stop();
2081 FFlameSoundWork
.SetPosition(0);
2082 FFlameSoundOff
.PlayAt(FObj
.X
, FObj
.Y
);
2087 procedure TPlayer
.JetpackOn
;
2091 FJetSoundOn
.SetPosition(0);
2092 FJetSoundOn
.PlayAt(FObj
.X
, FObj
.Y
);
2096 procedure TPlayer
.JetpackOff
;
2100 FJetSoundOff
.SetPosition(0);
2101 FJetSoundOff
.PlayAt(FObj
.X
, FObj
.Y
);
2104 procedure TPlayer
.CatchFire(Attacker
: Word; Timeout
: Integer = PLAYER_BURN_TIME
);
2106 if Timeout
<= 0 then
2108 if (FMegaRulez
[MR_SUIT
] > gTime
) or (FMegaRulez
[MR_INVUL
] > gTime
) then
2109 exit
; // Не загораемся когда есть защита
2110 if g_Obj_CollidePanel(@FObj
, 0, 0, PANEL_WATER
or PANEL_ACID1
or PANEL_ACID2
) then
2111 exit
; // Не подгораем в воде на всякий случай
2112 if FFireTime
<= 0 then
2113 g_Sound_PlayExAt('SOUND_IGNITE', FObj
.X
, FObj
.Y
);
2114 FFireTime
:= Timeout
;
2115 FFireAttacker
:= Attacker
;
2116 if g_Game_IsNet
and g_Game_IsServer
then
2117 MH_SEND_PlayerStats(FUID
);
2120 procedure TPlayer
.Jump();
2122 if gFly
or FJetpack
then
2124 // Полет (чит-код или джетпак):
2125 if FObj
.Vel
.Y
> -VEL_FLY
then
2126 FObj
.Vel
.Y
:= FObj
.Vel
.Y
- 3;
2129 if FJetFuel
> 0 then
2131 if (FJetFuel
< 1) and g_Game_IsServer
then
2135 if g_Game_IsNet
then
2136 MH_SEND_PlayerStats(FUID
);
2142 // Не включать джетпак в режиме прохождения сквозь стены
2144 FCanJetpack
:= False;
2146 // Прыгаем или всплываем:
2147 if (CollideLevel(0, 1) or
2148 g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+PLAYER_RECT
.Y
+36, PLAYER_RECT
.Width
,
2149 PLAYER_RECT
.Height
-33, PANEL_STEP
, False)
2150 ) and (FObj
.Accel
.Y
= 0) then // Не прыгать, если есть вертикальное ускорение
2152 FObj
.Vel
.Y
:= -VEL_JUMP
;
2153 FCanJetpack
:= False;
2157 if BodyInLiquid(0, 0) then
2158 FObj
.Vel
.Y
:= -VEL_SW
2159 else if (FJetFuel
> 0) and FCanJetpack
and
2160 g_Game_IsServer
and (not g_Obj_CollideLiquid(@FObj
, 0, 0)) then
2164 if g_Game_IsNet
then
2165 MH_SEND_PlayerStats(FUID
);
2170 procedure TPlayer
.Kill(KillType
: Byte; SpawnerUID
: Word; t
: Byte);
2172 a
, i
, k
, ab
, ar
: Byte;
2176 srv
, netsrv
: Boolean;
2182 procedure PushItem(t
: Byte);
2186 id
:= g_Items_Create(FObj
.X
, FObj
.Y
, t
, True, False);
2187 it
:= g_Items_ByIdx(id
);
2188 if KillType
= K_EXTRAHARDKILL
then // -7..+7; -8..0
2190 g_Obj_Push(@it
.Obj
, (FObj
.Vel
.X
div 2)-7+Random(15),
2191 (FObj
.Vel
.Y
div 2)-Random(9));
2192 it
.positionChanged(); // this updates spatial accelerators
2196 if KillType
= K_HARDKILL
then // -5..+5; -5..0
2198 g_Obj_Push(@it
.Obj
, (FObj
.Vel
.X
div 2)-5+Random(11),
2199 (FObj
.Vel
.Y
div 2)-Random(6));
2201 else // -3..+3; -3..0
2203 g_Obj_Push(@it
.Obj
, (FObj
.Vel
.X
div 2)-3+Random(7),
2204 (FObj
.Vel
.Y
div 2)-Random(4));
2206 it
.positionChanged(); // this updates spatial accelerators
2209 if g_Game_IsNet
and g_Game_IsServer
then
2210 MH_SEND_ItemSpawn(True, id
);
2214 DoFrags
:= (gGameSettings
.MaxLives
= 0) or (gGameSettings
.GameMode
= GM_COOP
);
2215 Srv
:= g_Game_IsServer
;
2216 Netsrv
:= g_Game_IsServer
and g_Game_IsNet
;
2217 if Srv
then FDeath
:= FDeath
+ 1;
2222 if not FPhysics
then
2227 {$IFDEF ENABLE_SHELLS}
2231 if (gGameSettings
.MaxLives
> 0) and Srv
and (gLMSRespawn
= LMS_RESPAWN_NONE
) then
2233 if FLives
> 0 then FLives
:= FLives
- 1;
2234 if FLives
= 0 then FNoRespawn
:= True;
2237 // Номер типа смерти:
2240 K_SIMPLEKILL
: a
:= 1;
2242 K_EXTRAHARDKILL
: a
:= 3;
2247 if not FModel
.PlaySound(MODELSOUND_DIE
, a
, FObj
.X
, FObj
.Y
) then
2249 if FModel
.PlaySound(MODELSOUND_DIE
, i
, FObj
.X
, FObj
.Y
) then
2256 FTime
[T_RESPAWN
] := gTime
+ TIME_RESPAWN1
;
2258 FTime
[T_RESPAWN
] := gTime
+ TIME_RESPAWN2
;
2259 K_EXTRAHARDKILL
, K_FALLKILL
:
2260 FTime
[T_RESPAWN
] := gTime
+ TIME_RESPAWN3
;
2263 // Переключаем состояние:
2267 K_HARDKILL
, K_EXTRAHARDKILL
:
2271 // Реакция монстров на смерть игрока:
2272 if (KillType
<> K_FALLKILL
) and (Srv
) then
2273 g_Monsters_killedp();
2275 if SpawnerUID
= FUID
then
2279 if gGameSettings
.GameMode
= GM_TDM
then
2280 Dec(gTeamStat
[FTeam
].Score
);
2281 if DoFrags
or (gGameSettings
.GameMode
= GM_TDM
) then
2287 g_Console_Add(Format(_lc
[I_PLAYER_KILL_SELF
], [FName
]), True);
2290 if g_GetUIDType(SpawnerUID
) = UID_PLAYER
then
2291 begin // Убит другим игроком
2292 KP
:= g_Player_Get(SpawnerUID
);
2293 if (KP
<> nil) and Srv
then
2295 if (DoFrags
or (gGameSettings
.GameMode
= GM_TDM
)) then
2296 if SameTeam(FUID
, SpawnerUID
) then
2306 if (gGameSettings
.GameMode
= GM_TDM
) and DoFrags
then
2307 Inc(gTeamStat
[KP
.Team
].Score
,
2308 IfThen(SameTeam(FUID
, SpawnerUID
), -1, 1));
2310 if netsrv
then MH_SEND_PlayerStats(SpawnerUID
);
2313 plr
:= g_Player_Get(SpawnerUID
);
2321 g_Console_Add(Format(_lc
[I_PLAYER_KILL_EXTRAHARD_2
],
2325 g_Console_Add(Format(_lc
[I_PLAYER_KILL_EXTRAHARD_1
],
2329 g_Console_Add(Format(_lc
[I_PLAYER_KILL
],
2334 else if g_GetUIDType(SpawnerUID
) = UID_MONSTER
then
2335 begin // Убит монстром
2336 mon
:= g_Monsters_ByUID(SpawnerUID
);
2340 s
:= g_Mons_GetKilledByTypeId(mon
.MonsterType
);
2344 g_Console_Add(Format(_lc
[I_PLAYER_KILL_EXTRAHARD_2
],
2348 g_Console_Add(Format(_lc
[I_PLAYER_KILL_EXTRAHARD_1
],
2352 g_Console_Add(Format(_lc
[I_PLAYER_KILL
],
2357 else // Особые типы смерти
2360 HIT_SELF
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_SELF
], [FName
]), True);
2361 HIT_FALL
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_FALL
], [FName
]), True);
2362 HIT_WATER
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_WATER
], [FName
]), True);
2363 HIT_ACID
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_ACID
], [FName
]), True);
2364 HIT_TRAP
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_TRAP
], [FName
]), True);
2365 else g_Console_Add(Format(_lc
[I_PLAYER_DIED
], [FName
]), True);
2371 for a
:= WP_FIRST
to WP_LAST
do
2375 WEAPON_SAW
: i
:= ITEM_WEAPON_SAW
;
2376 WEAPON_SHOTGUN1
: i
:= ITEM_WEAPON_SHOTGUN1
;
2377 WEAPON_SHOTGUN2
: i
:= ITEM_WEAPON_SHOTGUN2
;
2378 WEAPON_CHAINGUN
: i
:= ITEM_WEAPON_CHAINGUN
;
2379 WEAPON_ROCKETLAUNCHER
: i
:= ITEM_WEAPON_ROCKETLAUNCHER
;
2380 WEAPON_PLASMA
: i
:= ITEM_WEAPON_PLASMA
;
2381 WEAPON_BFG
: i
:= ITEM_WEAPON_BFG
;
2382 WEAPON_SUPERPULEMET
: i
:= ITEM_WEAPON_SUPERPULEMET
;
2383 WEAPON_FLAMETHROWER
: i
:= ITEM_WEAPON_FLAMETHROWER
;
2392 if R_ITEM_BACKPACK
in FRulez
then
2393 PushItem(ITEM_AMMO_BACKPACK
);
2395 // Выброс ракетного ранца:
2396 if FJetFuel
> 0 then
2397 PushItem(ITEM_JETPACK
);
2400 if (not (gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
])) or
2401 (not LongBool(gGameSettings
.Options
and GAME_OPTION_DMKEYS
)) then
2403 if R_KEY_RED
in FRulez
then
2404 PushItem(ITEM_KEY_RED
);
2406 if R_KEY_GREEN
in FRulez
then
2407 PushItem(ITEM_KEY_GREEN
);
2409 if R_KEY_BLUE
in FRulez
then
2410 PushItem(ITEM_KEY_BLUE
);
2414 DropFlag(KillType
= K_FALLKILL
);
2417 {$IFDEF ENABLE_CORPSES}
2418 FCorpse
:= g_Corpses_Create(Self
);
2421 if Srv
and (gGameSettings
.MaxLives
> 0) and FNoRespawn
and
2422 (gLMSRespawn
= LMS_RESPAWN_NONE
) then
2428 for i
:= Low(gPlayers
) to High(gPlayers
) do
2430 if gPlayers
[i
] = nil then continue
;
2431 if (not gPlayers
[i
].FNoRespawn
) and (not gPlayers
[i
].FSpectator
) then
2434 if gPlayers
[i
].FTeam
= TEAM_RED
then Inc(ar
)
2435 else if gPlayers
[i
].FTeam
= TEAM_BLUE
then Inc(ab
);
2440 OldLR
:= gLMSRespawn
;
2441 if (gGameSettings
.GameMode
= GM_COOP
) then
2445 // everyone is dead, restart the map
2446 g_Game_Message(_lc
[I_MESSAGE_LMS_LOSE
], 144);
2448 MH_SEND_GameEvent(NET_EV_LMS_LOSE
);
2449 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2450 gLMSRespawnTime
:= gTime
+ 5000;
2452 else if (a
= 1) then
2454 if (gPlayers
[k
] <> nil) and not (gPlayers
[k
] is TBot
) then
2455 if (gPlayers
[k
] = gPlayer1
) or
2456 (gPlayers
[k
] = gPlayer2
) then
2457 g_Console_Add('*** ' + _lc
[I_MESSAGE_LMS_SURVIVOR
] + ' ***', True)
2458 else if Netsrv
and (gPlayers
[k
].FClientID
>= 0) then
2459 MH_SEND_GameEvent(NET_EV_LMS_SURVIVOR
, 0, 'N', gPlayers
[k
].FClientID
);
2462 else if (gGameSettings
.GameMode
= GM_TDM
) then
2464 if (ab
= 0) and (ar
<> 0) then
2467 g_Game_Message(Format(_lc
[I_MESSAGE_TLMS_WIN
], [AnsiUpperCase(_lc
[I_GAME_TEAM_RED
])]), 144);
2469 MH_SEND_GameEvent(NET_EV_TLMS_WIN
, TEAM_RED
);
2470 Inc(gTeamStat
[TEAM_RED
].Score
);
2471 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2472 gLMSRespawnTime
:= gTime
+ 5000;
2474 else if (ar
= 0) and (ab
<> 0) then
2477 g_Game_Message(Format(_lc
[I_MESSAGE_TLMS_WIN
], [AnsiUpperCase(_lc
[I_GAME_TEAM_BLUE
])]), 144);
2479 MH_SEND_GameEvent(NET_EV_TLMS_WIN
, TEAM_BLUE
);
2480 Inc(gTeamStat
[TEAM_BLUE
].Score
);
2481 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2482 gLMSRespawnTime
:= gTime
+ 5000;
2484 else if (ar
= 0) and (ab
= 0) then
2487 g_Game_Message(_lc
[I_GAME_WIN_DRAW
], 144);
2489 MH_SEND_GameEvent(NET_EV_LMS_DRAW
, 0, FName
);
2490 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2491 gLMSRespawnTime
:= gTime
+ 5000;
2494 else if (gGameSettings
.GameMode
= GM_DM
) then
2498 if gPlayers
[k
] <> nil then
2501 // survivor is the winner
2502 g_Game_Message(Format(_lc
[I_MESSAGE_LMS_WIN
], [AnsiUpperCase(FName
)]), 144);
2504 MH_SEND_GameEvent(NET_EV_LMS_WIN
, 0, FName
);
2507 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2508 gLMSRespawnTime
:= gTime
+ 5000;
2510 else if (a
= 0) then
2512 // everyone is dead, restart the map
2513 g_Game_Message(_lc
[I_GAME_WIN_DRAW
], 144);
2515 MH_SEND_GameEvent(NET_EV_LMS_DRAW
, 0, FName
);
2516 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2517 gLMSRespawnTime
:= gTime
+ 5000;
2520 if srv
and (OldLR
= LMS_RESPAWN_NONE
) and (gLMSRespawn
> LMS_RESPAWN_NONE
) then
2522 if NetMode
= NET_SERVER
then
2523 MH_SEND_GameEvent(NET_EV_LMS_WARMUP
, gLMSRespawnTime
- gTime
)
2525 g_Console_Add(Format(_lc
[I_MSG_WARMUP_START
], [(gLMSRespawnTime
- gTime
) div 1000]), True);
2531 MH_SEND_PlayerStats(FUID
);
2532 MH_SEND_PlayerDeath(FUID
, KillType
, t
, SpawnerUID
);
2533 if gGameSettings
.GameMode
= GM_TDM
then MH_SEND_GameStats
;
2536 if srv
and FNoRespawn
then Spectate(True);
2537 FWantsInGame
:= True;
2540 function TPlayer
.BodyInLiquid(XInc
, YInc
: Integer): Boolean;
2542 Result
:= g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
, PLAYER_RECT
.Width
,
2543 PLAYER_RECT
.Height
-20, PANEL_WATER
or PANEL_ACID1
or PANEL_ACID2
, False);
2546 function TPlayer
.BodyInAcid(XInc
, YInc
: Integer): Boolean;
2548 Result
:= g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
, PLAYER_RECT
.Width
,
2549 PLAYER_RECT
.Height
-20, PANEL_ACID1
or PANEL_ACID2
, False);
2552 procedure TPlayer
.MakeBloodSimple(Count
: Word);
2554 var Blood
: TModelBlood
;
2558 Blood
:= SELF
.FModel
.GetBlood();
2559 g_GFX_Blood(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)+8,
2560 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2),
2561 Count
div 2, 3, -1, 16, (PLAYER_RECT
.Height
*2 div 3),
2562 Blood
.R
, Blood
.G
, Blood
.B
, Blood
.Kind
);
2563 g_GFX_Blood(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)-8,
2564 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2),
2565 Count
div 2, -3, -1, 16, (PLAYER_RECT
.Height
*2) div 3,
2566 Blood
.R
, Blood
.G
, Blood
.B
, Blood
.Kind
);
2570 procedure TPlayer
.MakeBloodVector(Count
: Word; VelX
, VelY
: Integer);
2572 var Blood
: TModelBlood
;
2576 Blood
:= SELF
.FModel
.GetBlood();
2577 g_GFX_Blood(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2),
2578 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2),
2579 Count
, VelX
, VelY
, 16, (PLAYER_RECT
.Height
*2) div 3,
2580 Blood
.R
, Blood
.G
, Blood
.B
, Blood
.Kind
);
2584 procedure TPlayer
.ProcessWeaponAction(Action
: Byte);
2586 if g_Game_IsClient
then Exit
;
2588 WP_PREV
: PrevWeapon();
2589 WP_NEXT
: NextWeapon();
2593 procedure TPlayer
.QueueWeaponSwitch(Weapon
: Byte);
2595 if g_Game_IsClient
then Exit
;
2596 if Weapon
> High(FWeapon
) then Exit
;
2597 FNextWeap
:= FNextWeap
or (1 shl Weapon
);
2600 procedure TPlayer
.resetWeaponQueue ();
2603 FNextWeapDelay
:= 0;
2606 function TPlayer
.hasAmmoForWeapon (weapon
: Byte): Boolean;
2610 WEAPON_KASTET
, WEAPON_SAW
: result
:= true;
2611 WEAPON_SHOTGUN1
, WEAPON_SHOTGUN2
, WEAPON_SUPERPULEMET
: result
:= (FAmmo
[A_SHELLS
] > 0);
2612 WEAPON_PISTOL
, WEAPON_CHAINGUN
: result
:= (FAmmo
[A_BULLETS
] > 0);
2613 WEAPON_ROCKETLAUNCHER
: result
:= (FAmmo
[A_ROCKETS
] > 0);
2614 WEAPON_PLASMA
, WEAPON_BFG
: result
:= (FAmmo
[A_CELLS
] > 0);
2615 WEAPON_FLAMETHROWER
: result
:= (FAmmo
[A_FUEL
] > 0);
2616 else result
:= (weapon
< length(FWeapon
));
2620 function TPlayer
.hasAmmoForShooting (weapon
: Byte): Boolean;
2624 WEAPON_KASTET
, WEAPON_SAW
: result
:= true;
2625 WEAPON_SHOTGUN1
, WEAPON_SUPERPULEMET
: result
:= (FAmmo
[A_SHELLS
] > 0);
2626 WEAPON_SHOTGUN2
: result
:= (FAmmo
[A_SHELLS
] > 1);
2627 WEAPON_PISTOL
, WEAPON_CHAINGUN
: result
:= (FAmmo
[A_BULLETS
] > 0);
2628 WEAPON_ROCKETLAUNCHER
: result
:= (FAmmo
[A_ROCKETS
] > 0);
2629 WEAPON_PLASMA
: result
:= (FAmmo
[A_CELLS
] > 0);
2630 WEAPON_BFG
: result
:= (FAmmo
[A_CELLS
] >= 40);
2631 WEAPON_FLAMETHROWER
: result
:= (FAmmo
[A_FUEL
] > 0);
2632 else result
:= (weapon
< length(FWeapon
));
2636 function TPlayer
.shouldSwitch (weapon
: Byte; hadWeapon
: Boolean): Boolean;
2639 if (weapon
> WP_LAST
+ 1) then
2644 if (FWeapSwitchMode
= 1) and not hadWeapon
then
2646 else if (FWeapSwitchMode
= 2) then
2647 result
:= (FWeapPreferences
[weapon
] > FWeapPreferences
[FCurrWeap
]);
2650 // return 255 for "no switch"
2651 function TPlayer
.getNextWeaponIndex (): Byte;
2654 wantThisWeapon
: array[0..64] of Boolean;
2655 wwc
: Integer = 0; //HACK!
2658 result
:= 255; // default result: "no switch"
2659 //e_LogWriteFln('FSWITCHTOEMPTY: %s', [FSwitchToEmpty], TMsgType.Notify);
2660 // had weapon cycling on previous frame? remove that flag
2661 if (FNextWeap
and $2000) <> 0 then
2663 FNextWeap
:= FNextWeap
and $1FFF;
2664 FNextWeapDelay
:= 0;
2666 // cycling has priority
2667 if (FNextWeap
and $C000) <> 0 then
2669 if (FNextWeap
and $8000) <> 0 then
2673 FNextWeap
:= FNextWeap
or $2000; // we need this
2674 if FNextWeapDelay
> 0 then
2675 exit
; // cooldown time
2677 for i
:= 0 to High(FWeapon
) do
2679 cwi
:= (cwi
+length(FWeapon
)+dir
) mod length(FWeapon
);
2680 if FWeapon
[cwi
] and maySwitch(cwi
) then
2682 //e_LogWriteFln(' SWITCH: cur=%d; new=%d %s %s', [FCurrWeap, cwi, FSwitchToEmpty, hasAmmoForWeapon(cwi)], TMsgType.Notify);
2683 result
:= Byte(cwi
);
2684 FNextWeapDelay
:= WEAPON_DELAY
;
2692 for i
:= 0 to High(wantThisWeapon
) do
2693 wantThisWeapon
[i
] := false;
2694 for i
:= 0 to High(FWeapon
) do
2695 if (FNextWeap
and (1 shl i
)) <> 0 then
2697 wantThisWeapon
[i
] := true;
2701 // exclude currently selected weapon from the set
2702 wantThisWeapon
[FCurrWeap
] := false;
2703 // slow down alterations a little
2706 //e_WriteLog(Format(' FNextWeap=%x; delay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
2707 // more than one weapon requested, assume "alteration" and check alteration delay
2708 if FNextWeapDelay
> 0 then
2714 // do not reset weapon queue, it will be done in `RealizeCurrentWeapon()`
2715 // but clear all counters if no weapon should be switched
2721 //e_WriteLog(Format('wwc=%d', [wwc]), MSG_WARNING);
2722 // try weapons in descending order
2723 for i
:= High(FWeapon
) downto 0 do
2725 if wantThisWeapon
[i
] and FWeapon
[i
] and ((wwc
= 1) or hasAmmoForWeapon(i
)) then
2730 FNextWeapDelay
:= WEAPON_DELAY
* 2; // anyway, 'cause why not
2731 //e_LogWriteFln('FOUND %s %s %s', [result, FSwitchToEmpty, hasAmmoForWeapon(i)], TMsgType.Notify);
2735 // no suitable weapon found, so reset the queue, to avoid accidental "queuing" of weapon w/o ammo
2739 procedure TPlayer
.RealizeCurrentWeapon();
2740 function switchAllowed (): Boolean;
2745 if FBFGFireCounter
<> -1 then
2747 if FTime
[T_SWITCH
] > gTime
then
2749 for i
:= WP_FIRST
to WP_LAST
do
2750 if FReloading
[i
] > 0 then
2758 //e_WriteLog(Format('***RealizeCurrentWeapon: FNextWeap=%x; FNextWeapDelay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
2759 //FNextWeap := FNextWeap and $1FFF;
2760 if FNextWeapDelay
> 0 then Dec(FNextWeapDelay
); // "alteration delay"
2762 if not switchAllowed
then
2764 //HACK for weapon cycling
2765 if (FNextWeap
and $E000) <> 0 then FNextWeap
:= 0;
2769 nw
:= getNextWeaponIndex();
2771 if nw
= 255 then exit
; // don't reset anything here
2772 if nw
> High(FWeapon
) then
2774 // don't forget to reset queue here!
2775 //e_WriteLog(' RealizeCurrentWeapon: WUTAFUUUU', MSG_WARNING);
2783 FTime
[T_SWITCH
] := gTime
+156;
2784 if FCurrWeap
= WEAPON_SAW
then FSawSoundSelect
.PlayAt(FObj
.X
, FObj
.Y
);
2785 FModel
.SetWeapon(FCurrWeap
);
2786 if g_Game_IsNet
then MH_SEND_PlayerStats(FUID
);
2790 procedure TPlayer
.NextWeapon();
2792 if g_Game_IsClient
then Exit
;
2796 procedure TPlayer
.PrevWeapon();
2798 if g_Game_IsClient
then Exit
;
2802 procedure TPlayer
.SetWeapon(W
: Byte);
2804 if FCurrWeap
<> W
then
2805 if W
= WEAPON_SAW
then
2806 FSawSoundSelect
.PlayAt(FObj
.X
, FObj
.Y
);
2809 FModel
.SetWeapon(CurrWeap
);
2813 function TPlayer
.PickItem(ItemType
: Byte; arespawn
: Boolean; var remove
: Boolean): Boolean;
2816 switchWeapon
: Byte = 255;
2817 hadWeapon
: Boolean = False;
2820 if g_Game_IsClient
then Exit
;
2822 // a = true - место спавна предмета:
2823 a
:= LongBool(gGameSettings
.Options
and GAME_OPTION_WEAPONSTAY
) and arespawn
;
2827 if (FHealth
< PLAYER_HP_SOFT
) or (FFireTime
> 0) then
2829 if FHealth
< PLAYER_HP_SOFT
then IncMax(FHealth
, 10, PLAYER_HP_SOFT
);
2833 if gFlash
= 2 then Inc(FPickup
, 5);
2837 if (FHealth
< PLAYER_HP_SOFT
) or (FFireTime
> 0) then
2839 if FHealth
< PLAYER_HP_SOFT
then IncMax(FHealth
, 25, PLAYER_HP_SOFT
);
2843 if gFlash
= 2 then Inc(FPickup
, 5);
2847 if FArmor
< PLAYER_AP_SOFT
then
2849 FArmor
:= PLAYER_AP_SOFT
;
2852 if gFlash
= 2 then Inc(FPickup
, 5);
2856 if FArmor
< PLAYER_AP_LIMIT
then
2858 FArmor
:= PLAYER_AP_LIMIT
;
2861 if gFlash
= 2 then Inc(FPickup
, 5);
2865 if (FHealth
< PLAYER_HP_LIMIT
) or (FFireTime
> 0) then
2867 if FHealth
< PLAYER_HP_LIMIT
then IncMax(FHealth
, 100, PLAYER_HP_LIMIT
);
2871 if gFlash
= 2 then Inc(FPickup
, 5);
2875 if (FHealth
< PLAYER_HP_LIMIT
) or (FArmor
< PLAYER_AP_LIMIT
) or (FFireTime
> 0) then
2877 if FHealth
< PLAYER_HP_LIMIT
then
2878 FHealth
:= PLAYER_HP_LIMIT
;
2879 if FArmor
< PLAYER_AP_LIMIT
then
2880 FArmor
:= PLAYER_AP_LIMIT
;
2884 if gFlash
= 2 then Inc(FPickup
, 5);
2888 if (not FWeapon
[WEAPON_SAW
]) or ((not arespawn
) and (gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
])) then
2890 hadWeapon
:= FWeapon
[WEAPON_SAW
];
2891 switchWeapon
:= WEAPON_SAW
;
2892 FWeapon
[WEAPON_SAW
] := True;
2894 if gFlash
= 2 then Inc(FPickup
, 5);
2895 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
2898 ITEM_WEAPON_SHOTGUN1
:
2899 if (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or not FWeapon
[WEAPON_SHOTGUN1
] then
2901 // Нужно, чтобы не взять все пули сразу:
2902 if a
and FWeapon
[WEAPON_SHOTGUN1
] then Exit
;
2903 hadWeapon
:= FWeapon
[WEAPON_SHOTGUN1
];
2904 switchWeapon
:= WEAPON_SHOTGUN1
;
2905 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
2906 FWeapon
[WEAPON_SHOTGUN1
] := True;
2908 if gFlash
= 2 then Inc(FPickup
, 5);
2909 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
2912 ITEM_WEAPON_SHOTGUN2
:
2913 if (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or not FWeapon
[WEAPON_SHOTGUN2
] then
2915 if a
and FWeapon
[WEAPON_SHOTGUN2
] then Exit
;
2916 hadWeapon
:= FWeapon
[WEAPON_SHOTGUN2
];
2917 switchWeapon
:= WEAPON_SHOTGUN2
;
2918 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
2919 FWeapon
[WEAPON_SHOTGUN2
] := True;
2921 if gFlash
= 2 then Inc(FPickup
, 5);
2922 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
2925 ITEM_WEAPON_CHAINGUN
:
2926 if (FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
]) or not FWeapon
[WEAPON_CHAINGUN
] then
2928 if a
and FWeapon
[WEAPON_CHAINGUN
] then Exit
;
2929 hadWeapon
:= FWeapon
[WEAPON_CHAINGUN
];
2930 switchWeapon
:= WEAPON_CHAINGUN
;
2931 IncMax(FAmmo
[A_BULLETS
], 50, FMaxAmmo
[A_BULLETS
]);
2932 FWeapon
[WEAPON_CHAINGUN
] := True;
2934 if gFlash
= 2 then Inc(FPickup
, 5);
2935 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
2938 ITEM_WEAPON_ROCKETLAUNCHER
:
2939 if (FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
]) or not FWeapon
[WEAPON_ROCKETLAUNCHER
] then
2941 if a
and FWeapon
[WEAPON_ROCKETLAUNCHER
] then Exit
;
2942 switchWeapon
:= WEAPON_ROCKETLAUNCHER
;
2943 hadWeapon
:= FWeapon
[WEAPON_ROCKETLAUNCHER
];
2944 IncMax(FAmmo
[A_ROCKETS
], 2, FMaxAmmo
[A_ROCKETS
]);
2945 FWeapon
[WEAPON_ROCKETLAUNCHER
] := True;
2947 if gFlash
= 2 then Inc(FPickup
, 5);
2948 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
2952 if (FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
]) or not FWeapon
[WEAPON_PLASMA
] then
2954 if a
and FWeapon
[WEAPON_PLASMA
] then Exit
;
2955 switchWeapon
:= WEAPON_PLASMA
;
2956 hadWeapon
:= FWeapon
[WEAPON_PLASMA
];
2957 IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
2958 FWeapon
[WEAPON_PLASMA
] := True;
2960 if gFlash
= 2 then Inc(FPickup
, 5);
2961 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
2965 if (FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
]) or not FWeapon
[WEAPON_BFG
] then
2967 if a
and FWeapon
[WEAPON_BFG
] then Exit
;
2968 switchWeapon
:= WEAPON_BFG
;
2969 hadWeapon
:= FWeapon
[WEAPON_BFG
];
2970 IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
2971 FWeapon
[WEAPON_BFG
] := True;
2973 if gFlash
= 2 then Inc(FPickup
, 5);
2974 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
2977 ITEM_WEAPON_SUPERPULEMET
:
2978 if (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or not FWeapon
[WEAPON_SUPERPULEMET
] then
2980 if a
and FWeapon
[WEAPON_SUPERPULEMET
] then Exit
;
2981 switchWeapon
:= WEAPON_SUPERPULEMET
;
2982 hadWeapon
:= FWeapon
[WEAPON_SUPERPULEMET
];
2983 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
2984 FWeapon
[WEAPON_SUPERPULEMET
] := True;
2986 if gFlash
= 2 then Inc(FPickup
, 5);
2987 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
2990 ITEM_WEAPON_FLAMETHROWER
:
2991 if (FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
]) or not FWeapon
[WEAPON_FLAMETHROWER
] then
2993 if a
and FWeapon
[WEAPON_FLAMETHROWER
] then Exit
;
2994 switchWeapon
:= WEAPON_FLAMETHROWER
;
2995 hadWeapon
:= FWeapon
[WEAPON_FLAMETHROWER
];
2996 IncMax(FAmmo
[A_FUEL
], 100, FMaxAmmo
[A_FUEL
]);
2997 FWeapon
[WEAPON_FLAMETHROWER
] := True;
2999 if gFlash
= 2 then Inc(FPickup
, 5);
3000 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3004 if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then
3006 IncMax(FAmmo
[A_BULLETS
], 10, FMaxAmmo
[A_BULLETS
]);
3009 if gFlash
= 2 then Inc(FPickup
, 5);
3012 ITEM_AMMO_BULLETS_BOX
:
3013 if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then
3015 IncMax(FAmmo
[A_BULLETS
], 50, FMaxAmmo
[A_BULLETS
]);
3018 if gFlash
= 2 then Inc(FPickup
, 5);
3022 if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then
3024 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
3027 if gFlash
= 2 then Inc(FPickup
, 5);
3030 ITEM_AMMO_SHELLS_BOX
:
3031 if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then
3033 IncMax(FAmmo
[A_SHELLS
], 25, FMaxAmmo
[A_SHELLS
]);
3036 if gFlash
= 2 then Inc(FPickup
, 5);
3040 if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then
3042 IncMax(FAmmo
[A_ROCKETS
], 1, FMaxAmmo
[A_ROCKETS
]);
3045 if gFlash
= 2 then Inc(FPickup
, 5);
3048 ITEM_AMMO_ROCKET_BOX
:
3049 if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then
3051 IncMax(FAmmo
[A_ROCKETS
], 5, FMaxAmmo
[A_ROCKETS
]);
3054 if gFlash
= 2 then Inc(FPickup
, 5);
3058 if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then
3060 IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
3063 if gFlash
= 2 then Inc(FPickup
, 5);
3067 if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then
3069 IncMax(FAmmo
[A_CELLS
], 100, FMaxAmmo
[A_CELLS
]);
3072 if gFlash
= 2 then Inc(FPickup
, 5);
3076 if FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
] then
3078 IncMax(FAmmo
[A_FUEL
], 100, FMaxAmmo
[A_FUEL
]);
3081 if gFlash
= 2 then Inc(FPickup
, 5);
3085 if not(R_ITEM_BACKPACK
in FRulez
) or
3086 (FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
]) or
3087 (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or
3088 (FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
]) or
3089 (FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
]) or
3090 (FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
]) then
3092 FMaxAmmo
[A_BULLETS
] := AmmoLimits
[1, A_BULLETS
];
3093 FMaxAmmo
[A_SHELLS
] := AmmoLimits
[1, A_SHELLS
];
3094 FMaxAmmo
[A_ROCKETS
] := AmmoLimits
[1, A_ROCKETS
];
3095 FMaxAmmo
[A_CELLS
] := AmmoLimits
[1, A_CELLS
];
3096 FMaxAmmo
[A_FUEL
] := AmmoLimits
[1, A_FUEL
];
3098 if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then
3099 IncMax(FAmmo
[A_BULLETS
], 10, FMaxAmmo
[A_BULLETS
]);
3100 if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then
3101 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
3102 if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then
3103 IncMax(FAmmo
[A_ROCKETS
], 1, FMaxAmmo
[A_ROCKETS
]);
3104 if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then
3105 IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
3106 if FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
] then
3107 IncMax(FAmmo
[A_FUEL
], 50, FMaxAmmo
[A_FUEL
]);
3109 FRulez
:= FRulez
+ [R_ITEM_BACKPACK
];
3112 if gFlash
= 2 then Inc(FPickup
, 5);
3116 if not(R_KEY_RED
in FRulez
) then
3118 Include(FRulez
, R_KEY_RED
);
3120 remove
:= (gGameSettings
.GameMode
<> GM_COOP
) and (g_Player_GetCount() < 2);
3121 if gFlash
= 2 then Inc(FPickup
, 5);
3122 if (not remove
) and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETITEM');
3126 if not(R_KEY_GREEN
in FRulez
) then
3128 Include(FRulez
, R_KEY_GREEN
);
3130 remove
:= (gGameSettings
.GameMode
<> GM_COOP
) and (g_Player_GetCount() < 2);
3131 if gFlash
= 2 then Inc(FPickup
, 5);
3132 if (not remove
) and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETITEM');
3136 if not(R_KEY_BLUE
in FRulez
) then
3138 Include(FRulez
, R_KEY_BLUE
);
3140 remove
:= (gGameSettings
.GameMode
<> GM_COOP
) and (g_Player_GetCount() < 2);
3141 if gFlash
= 2 then Inc(FPickup
, 5);
3142 if (not remove
) and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETITEM');
3146 if FMegaRulez
[MR_SUIT
] < gTime
+PLAYER_SUIT_TIME
then
3148 FMegaRulez
[MR_SUIT
] := gTime
+PLAYER_SUIT_TIME
;
3152 if gFlash
= 2 then Inc(FPickup
, 5);
3156 if FAir
< AIR_MAX
then
3161 if gFlash
= 2 then Inc(FPickup
, 5);
3166 if not (R_BERSERK
in FRulez
) then
3168 Include(FRulez
, R_BERSERK
);
3169 if (FBFGFireCounter
= -1) then
3171 FCurrWeap
:= WEAPON_KASTET
;
3173 FModel
.SetWeapon(WEAPON_KASTET
);
3178 if gFlash
= 2 then Inc(FPickup
, 5);
3180 FBerserk
:= gTime
+30000;
3185 if (FHealth
< PLAYER_HP_SOFT
) or (FFireTime
> 0) then
3187 if FHealth
< PLAYER_HP_SOFT
then FHealth
:= PLAYER_HP_SOFT
;
3188 FBerserk
:= gTime
+30000;
3196 if FMegaRulez
[MR_INVUL
] < gTime
+PLAYER_INVUL_TIME
then
3198 FMegaRulez
[MR_INVUL
] := gTime
+PLAYER_INVUL_TIME
;
3202 if gFlash
= 2 then Inc(FPickup
, 5);
3206 if (FHealth
< PLAYER_HP_LIMIT
) or (FFireTime
> 0) then
3208 if FHealth
< PLAYER_HP_LIMIT
then IncMax(FHealth
, 4, PLAYER_HP_LIMIT
);
3212 if gFlash
= 2 then Inc(FPickup
, 5);
3216 if FArmor
< PLAYER_AP_LIMIT
then
3218 IncMax(FArmor
, 5, PLAYER_AP_LIMIT
);
3221 if gFlash
= 2 then Inc(FPickup
, 5);
3225 if FJetFuel
< JET_MAX
then
3227 FJetFuel
:= JET_MAX
;
3230 if gFlash
= 2 then Inc(FPickup
, 5);
3234 if FMegaRulez
[MR_INVIS
] < gTime
+PLAYER_INVIS_TIME
then
3236 FMegaRulez
[MR_INVIS
] := gTime
+PLAYER_INVIS_TIME
;
3239 if gFlash
= 2 then Inc(FPickup
, 5);
3243 if (shouldSwitch(switchWeapon
, hadWeapon
)) then
3244 QueueWeaponSwitch(switchWeapon
);
3247 procedure TPlayer
.Touch();
3251 //FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
3254 // Бросить флаг товарищу:
3255 if gGameSettings
.GameMode
= GM_CTF
then
3260 procedure TPlayer
.Push(vx
, vy
: Integer);
3262 if (not FPhysics
) and FGhost
then
3264 FObj
.Accel
.X
:= FObj
.Accel
.X
+ vx
;
3265 FObj
.Accel
.Y
:= FObj
.Accel
.Y
+ vy
;
3266 if g_Game_IsNet
and g_Game_IsServer
then
3267 MH_SEND_PlayerPos(True, FUID
, NET_EVERYONE
);
3270 procedure TPlayer
.Reset(Force
: Boolean);
3276 FTime
[T_RESPAWN
] := 0;
3277 FTime
[T_FLAGCAP
] := 0;
3289 {$IFDEF ENABLE_CORPSES}
3295 FSpectator
:= False;
3298 FSpectatePlayer
:= -1;
3299 FNoRespawn
:= False;
3301 FLives
:= gGameSettings
.MaxLives
;
3306 procedure TPlayer
.SoftReset();
3314 FBFGFireCounter
:= -1;
3315 {$IFDEF ENABLE_SHELLS}
3324 SetAction(A_STAND
, True);
3327 function TPlayer
.GetRespawnPoint(): Byte;
3332 // На будущее: FSpawn - игрок уже играл и перерождается
3334 // Одиночная игра/кооператив
3335 if gGameSettings
.GameMode
in [GM_COOP
, GM_SINGLE
] then
3337 if Self
= gPlayer1
then
3339 // player 1 should try to spawn on the player 1 point
3340 if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1
) > 0 then
3341 Exit(RESPAWNPOINT_PLAYER1
)
3342 else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2
) > 0 then
3343 Exit(RESPAWNPOINT_PLAYER2
);
3345 else if Self
= gPlayer2
then
3347 // player 2 should try to spawn on the player 2 point
3348 if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2
) > 0 then
3349 Exit(RESPAWNPOINT_PLAYER2
)
3350 else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1
) > 0 then
3351 Exit(RESPAWNPOINT_PLAYER1
);
3355 // other players randomly pick either the first or the second point
3356 c
:= IfThen((Random(2) = 0), RESPAWNPOINT_PLAYER1
, RESPAWNPOINT_PLAYER2
);
3357 if g_Map_GetPointCount(c
) > 0 then
3359 // try the other one
3360 c
:= IfThen((c
= RESPAWNPOINT_PLAYER1
), RESPAWNPOINT_PLAYER2
, RESPAWNPOINT_PLAYER1
);
3361 if g_Map_GetPointCount(c
) > 0 then
3367 if gGameSettings
.GameMode
= GM_DM
then
3369 // try DM points first
3370 if g_Map_GetPointCount(RESPAWNPOINT_DM
) > 0 then
3371 Exit(RESPAWNPOINT_DM
);
3375 if gGameSettings
.GameMode
in [GM_TDM
, GM_CTF
] then
3377 // try team points first
3378 c
:= RESPAWNPOINT_DM
;
3379 if FTeam
= TEAM_RED
then
3380 c
:= RESPAWNPOINT_RED
3381 else if FTeam
= TEAM_BLUE
then
3382 c
:= RESPAWNPOINT_BLUE
;
3383 if g_Map_GetPointCount(c
) > 0 then
3387 // still haven't found a spawnpoint, try random shit
3388 Result
:= g_Map_GetRandomPointType();
3391 procedure TPlayer
.Respawn(Silent
: Boolean; Force
: Boolean = False);
3393 RespawnPoint
: TRespawnPoint
;
3399 FBFGFireCounter
:= -1;
3400 {$IFDEF ENABLE_SHELLS}
3407 {$IFDEF ENABLE_CORPSES}
3411 if not g_Game_IsServer
then
3415 FWantsInGame
:= True;
3416 FJustTeleported
:= True;
3419 FTime
[T_RESPAWN
] := 0;
3423 // if server changes MaxLives we gotta be ready
3424 if gGameSettings
.MaxLives
= 0 then FNoRespawn
:= False;
3426 // Еще нельзя возродиться:
3427 if FTime
[T_RESPAWN
] > gTime
then
3430 // Просрал все жизни:
3433 if not FSpectator
then Spectate(True);
3434 FWantsInGame
:= True;
3438 if (gGameSettings
.GameType
<> GT_SINGLE
) and (gGameSettings
.GameMode
<> GM_COOP
) then
3439 begin // "Своя игра"
3440 // Берсерк не сохраняется между уровнями:
3441 FRulez
:= FRulez
-[R_BERSERK
];
3443 else // "Одиночная игра"/"Кооп"
3445 // Берсерк и ключи не сохраняются между уровнями:
3446 FRulez
:= FRulez
-[R_KEY_RED
, R_KEY_GREEN
, R_KEY_BLUE
, R_BERSERK
];
3449 // Получаем точку спауна игрока:
3450 c
:= GetRespawnPoint();
3455 // Воскрешение без оружия:
3458 FHealth
:= Round(PLAYER_HP_SOFT
* (FHandicap
/ 100));
3464 for a
:= WP_FIRST
to WP_LAST
do
3466 FWeapon
[a
] := False;
3470 FWeapon
[WEAPON_PISTOL
] := True;
3471 FWeapon
[WEAPON_KASTET
] := True;
3472 FCurrWeap
:= WEAPON_PISTOL
;
3475 FModel
.SetWeapon(FCurrWeap
);
3477 for b
:= A_BULLETS
to A_HIGH
do
3480 FAmmo
[A_BULLETS
] := 50;
3482 FMaxAmmo
[A_BULLETS
] := AmmoLimits
[0, A_BULLETS
];
3483 FMaxAmmo
[A_SHELLS
] := AmmoLimits
[0, A_SHELLS
];
3484 FMaxAmmo
[A_ROCKETS
] := AmmoLimits
[0, A_SHELLS
];
3485 FMaxAmmo
[A_CELLS
] := AmmoLimits
[0, A_CELLS
];
3486 FMaxAmmo
[A_FUEL
] := AmmoLimits
[0, A_FUEL
];
3488 if (gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
]) and
3489 LongBool(gGameSettings
.Options
and GAME_OPTION_DMKEYS
) then
3490 FRulez
:= [R_KEY_RED
, R_KEY_GREEN
, R_KEY_BLUE
]
3495 // Получаем координаты точки возрождения:
3496 if not g_Map_GetPoint(c
, RespawnPoint
) then
3498 g_FatalError(_lc
[I_GAME_ERROR_GET_SPAWN
]);
3502 // Установка координат и сброс всех параметров:
3503 FObj
.X
:= RespawnPoint
.X
-PLAYER_RECT
.X
;
3504 FObj
.Y
:= RespawnPoint
.Y
-PLAYER_RECT
.Y
;
3505 FObj
.oldX
:= FObj
.X
; // don't interpolate after respawn
3506 FObj
.oldY
:= FObj
.Y
;
3512 FDirection
:= RespawnPoint
.Direction
;
3513 if FDirection
= TDirection
.D_LEFT
then
3518 SetAction(A_STAND
, True);
3519 FModel
.Direction
:= FDirection
;
3521 for a
:= Low(FTime
) to High(FTime
) do
3524 for a
:= Low(FMegaRulez
) to High(FMegaRulez
) do
3527 // Respawn invulnerability
3528 if (gGameSettings
.GameType
<> GT_SINGLE
) and (gGameSettings
.SpawnInvul
> 0) then
3530 FMegaRulez
[MR_INVUL
] := gTime
+ gGameSettings
.SpawnInvul
* 1000;
3531 FSpawnInvul
:= FMegaRulez
[MR_INVUL
];
3536 FCanJetpack
:= False;
3543 // Анимация возрождения:
3544 if (not gLoadGameMode
) and (not Silent
) then
3547 R_GFX_TELEPORT_FAST
,
3548 FObj
.X
+ PLAYER_RECT
.X
+ (PLAYER_RECT
.Width
div 2) - 32,
3549 FObj
.Y
+ PLAYER_RECT
.Y
+ (PLAYER_RECT
.Height
div 2) - 32
3554 FSpectator
:= False;
3557 FSpectatePlayer
:= -1;
3560 if (gPlayer1
= nil) and (gSpectLatchPID1
= FUID
) then
3562 if (gPlayer2
= nil) and (gSpectLatchPID2
= FUID
) then
3565 if g_Game_IsNet
then
3567 MH_SEND_PlayerPos(True, FUID
, NET_EVERYONE
);
3568 MH_SEND_PlayerStats(FUID
, NET_EVERYONE
);
3570 MH_SEND_Effect(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)-32,
3571 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2)-32,
3576 procedure TPlayer
.Spectate(NoMove
: Boolean = False);
3579 Kill(K_EXTRAHARDKILL
, FUID
, HIT_SOME
)
3580 else if (not NoMove
) then
3582 GameX
:= gMapInfo
.Width
div 2;
3583 GameY
:= gMapInfo
.Height
div 2;
3592 FWantsInGame
:= False;
3595 {$IFDEF ENABLE_CORPSES}
3601 if Self
= gPlayer1
then
3603 gSpectLatchPID1
:= FUID
;
3606 else if Self
= gPlayer2
then
3608 gSpectLatchPID2
:= FUID
;
3613 if g_Game_IsNet
then
3614 MH_SEND_PlayerStats(FUID
);
3617 procedure TPlayer
.SwitchNoClip
;
3621 FGhost
:= not FGhost
;
3622 FPhysics
:= not FGhost
;
3634 procedure TPlayer
.Run(Direction
: TDirection
);
3635 {$IFDEF ENABLE_GIBS}
3639 if MAX_RUNVEL
> 8 then
3643 if Direction
= TDirection
.D_LEFT
then
3645 if FObj
.Vel
.X
> -MAX_RUNVEL
then
3646 FObj
.Vel
.X
:= FObj
.Vel
.X
- (MAX_RUNVEL
shr 3);
3649 if FObj
.Vel
.X
< MAX_RUNVEL
then
3650 FObj
.Vel
.X
:= FObj
.Vel
.X
+ (MAX_RUNVEL
shr 3);
3652 {$IFDEF ENABLE_GIBS}
3653 // Возможно, пинаем куски:
3654 if (FObj
.Vel
.X
<> 0) and (gGibs
<> nil) then
3656 b
:= Abs(FObj
.Vel
.X
);
3657 if b
> 1 then b
:= b
* (Random(8 div b
) + 1);
3658 for a
:= 0 to High(gGibs
) do
3660 if gGibs
[a
].alive
and
3661 g_Obj_Collide(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
+FObj
.Rect
.Height
-4,
3662 FObj
.Rect
.Width
, 8, @gGibs
[a
].Obj
) and (Random(3) = 0) then
3665 if FObj
.Vel
.X
< 0 then
3667 g_Obj_PushA(@gGibs
[a
].Obj
, b
, Random(61)+120) // налево
3671 g_Obj_PushA(@gGibs
[a
].Obj
, b
, Random(61)); // направо
3673 gGibs
[a
].positionChanged(); // this updates spatial accelerators
3682 procedure TPlayer
.SeeDown();
3684 SetAction(A_SEEDOWN
);
3686 if FDirection
= TDirection
.D_LEFT
then FAngle
:= ANGLE_LEFTDOWN
else FAngle
:= ANGLE_RIGHTDOWN
;
3688 if FIncCam
> -120 then DecMin(FIncCam
, 5, -120);
3691 procedure TPlayer
.SeeUp();
3695 if FDirection
= TDirection
.D_LEFT
then FAngle
:= ANGLE_LEFTUP
else FAngle
:= ANGLE_RIGHTUP
;
3697 if FIncCam
< 120 then IncMax(FIncCam
, 5, 120);
3700 procedure TPlayer
.SetAction(Action
: Byte; Force
: Boolean = False);
3708 A_ATTACK
: Prior
:= 2;
3709 A_SEEUP
: Prior
:= 1;
3710 A_SEEDOWN
: Prior
:= 1;
3711 A_ATTACKUP
: Prior
:= 2;
3712 A_ATTACKDOWN
: Prior
:= 2;
3717 if (Prior
> FActionPrior
) or Force
then
3718 if not ((Prior
= 2) and (FCurrWeap
= WEAPON_SAW
)) then
3720 FActionPrior
:= Prior
;
3721 FActionAnim
:= Action
;
3722 FActionForce
:= Force
;
3723 FActionChanged
:= True;
3726 if Action
in [A_ATTACK
, A_ATTACKUP
, A_ATTACKDOWN
] then FModel
.SetFire(True);
3729 function TPlayer
.StayOnStep(XInc
, YInc
: Integer): Boolean;
3731 Result
:= not g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+YInc
+PLAYER_RECT
.Y
+PLAYER_RECT
.Height
-1,
3732 PLAYER_RECT
.Width
, 1, PANEL_STEP
, False)
3733 and g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+YInc
+PLAYER_RECT
.Y
+PLAYER_RECT
.Height
,
3734 PLAYER_RECT
.Width
, 1, PANEL_STEP
, False);
3737 function TPlayer
.TeleportTo(X
, Y
: Integer; silent
: Boolean; dir
: Byte): Boolean;
3741 if g_CollideLevel(X
, Y
, PLAYER_RECT
.Width
, PLAYER_RECT
.Height
) then
3743 g_Sound_PlayExAt('SOUND_GAME_NOTELEPORT', FObj
.X
, FObj
.Y
);
3744 if g_Game_IsServer
and g_Game_IsNet
then
3745 MH_SEND_Sound(FObj
.X
, FObj
.Y
, 'SOUND_GAME_NOTELEPORT');
3749 FJustTeleported
:= True;
3753 g_Sound_PlayExAt('SOUND_GAME_TELEPORT', FObj
.X
, FObj
.Y
);
3756 R_GFX_TELEPORT_FAST
,
3757 FObj
.X
+ PLAYER_RECT
.X
+ (PLAYER_RECT
.Width
div 2) - 32,
3758 FObj
.Y
+ PLAYER_RECT
.Y
+ (PLAYER_RECT
.Height
div 2) - 32
3761 if g_Game_IsServer
and g_Game_IsNet
then
3762 MH_SEND_Effect(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)-32,
3763 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2)-32, 1,
3767 FObj
.X
:= X
-PLAYER_RECT
.X
;
3768 FObj
.Y
:= Y
-PLAYER_RECT
.Y
;
3769 FObj
.oldX
:= FObj
.X
; // don't interpolate after respawn
3770 FObj
.oldY
:= FObj
.Y
;
3771 if FAlive
and FGhost
then
3777 if not g_Game_IsNet
then
3781 SetDirection(TDirection
.D_LEFT
);
3787 SetDirection(TDirection
.D_RIGHT
);
3793 if FDirection
= TDirection
.D_RIGHT
then
3795 SetDirection(TDirection
.D_LEFT
);
3800 SetDirection(TDirection
.D_RIGHT
);
3810 R_GFX_TELEPORT_FAST
,
3811 FObj
.X
+ PLAYER_RECT
.X
+ (PLAYER_RECT
.Width
div 2) - 32,
3812 FObj
.Y
+ PLAYER_RECT
.Y
+ (PLAYER_RECT
.Height
div 2) - 32
3815 if g_Game_IsServer
and g_Game_IsNet
then
3816 MH_SEND_Effect(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)-32,
3817 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2)-32, 0,
3824 function nonz(a
: Single): Single;
3832 procedure TPlayer
.PreUpdate();
3834 FSlopeOld
:= FObj
.slopeUpLeft
;
3835 FIncCamOld
:= FIncCam
;
3836 FObj
.oldX
:= FObj
.X
;
3837 FObj
.oldY
:= FObj
.Y
;
3840 procedure TPlayer
.Update();
3843 i
, ii
, wx
, wy
, xd
, yd
, k
: Integer;
3844 blockmon
, headwater
, dospawn
: Boolean;
3849 NetServer
:= g_Game_IsNet
and g_Game_IsServer
;
3850 AnyServer
:= g_Game_IsServer
;
3852 if g_Game_IsClient
and (NetInterpLevel
> 0) then
3853 DoLerp(NetInterpLevel
+ 1)
3859 if (FClientID
>= 0) and (NetClients
[FClientID
].Peer
<> nil) then
3861 FPing
:= NetClients
[FClientID
].Peer
^.lastRoundTripTime
;
3862 if NetClients
[FClientID
].Peer
^.packetsSent
> 0 then
3863 FLoss
:= Round(100*NetClients
[FClientID
].Peer
^.packetsLost
/NetClients
[FClientID
].Peer
^.packetsSent
)
3874 if FPunchAnim
.played
then
3877 if FAlive
and (gFly
or FJetpack
) then
3880 if FDirection
= TDirection
.D_LEFT
then
3885 if FAlive
and (not FGhost
) then
3887 if FKeys
[KEY_UP
].Pressed
then
3889 if FKeys
[KEY_DOWN
].Pressed
then
3893 if (not (FKeys
[KEY_UP
].Pressed
or FKeys
[KEY_DOWN
].Pressed
)) and
3896 i
:= g_basic
.Sign(FIncCam
);
3897 FIncCam
:= Abs(FIncCam
);
3898 DecMin(FIncCam
, 5, 0);
3899 FIncCam
:= FIncCam
*i
;
3902 if gTime
mod (GAME_TICK
*2) <> 0 then
3904 if (FObj
.Vel
.X
= 0) and FAlive
then
3906 if FKeys
[KEY_LEFT
].Pressed
then
3907 Run(TDirection
.D_LEFT
);
3908 if FKeys
[KEY_RIGHT
].Pressed
then
3909 Run(TDirection
.D_RIGHT
);
3914 g_Obj_Move(@FObj
, True, True, True);
3915 positionChanged(); // this updates spatial accelerators
3921 FActionChanged
:= False;
3925 // Let alive player do some actions
3926 if FKeys
[KEY_LEFT
].Pressed
then Run(TDirection
.D_LEFT
);
3927 if FKeys
[KEY_RIGHT
].Pressed
then Run(TDirection
.D_RIGHT
);
3928 if FKeys
[KEY_FIRE
].Pressed
and AnyServer
then Fire()
3934 if NetServer
then MH_SEND_PlayerStats(FUID
);
3937 if FKeys
[KEY_OPEN
].Pressed
and AnyServer
then Use();
3938 if FKeys
[KEY_JUMP
].Pressed
then Jump()
3941 if AnyServer
and FJetpack
then
3945 if NetServer
then MH_SEND_PlayerStats(FUID
);
3947 FCanJetpack
:= True;
3954 for k
:= Low(FKeys
) to KEY_CHAT
-1 do
3956 if FKeys
[k
].Pressed
then
3964 if gGameSettings
.GameType
in [GT_CUSTOM
, GT_SERVER
, GT_CLIENT
] then
3967 if (FTime
[T_RESPAWN
] <= gTime
) and
3968 gGameOn
and (not FAlive
) then
3970 if (g_Player_GetCount() > 1) then
3974 gExit
:= EXIT_RESTART
;
3979 // Dead spectator actions
3982 if FKeys
[KEY_OPEN
].Pressed
and AnyServer
then Fire();
3983 if FKeys
[KEY_FIRE
].Pressed
and AnyServer
then
3987 if (FSpectatePlayer
>= High(gPlayers
)) then
3988 FSpectatePlayer
:= -1
3992 for I
:= FSpectatePlayer
+ 1 to High(gPlayers
) do
3993 if gPlayers
[I
] <> nil then
3994 if gPlayers
[I
].alive
then
3995 if gPlayers
[I
].UID
<> FUID
then
3997 FSpectatePlayer
:= I
;
4002 if not SetSpect
then FSpectatePlayer
:= -1;
4013 if FKeys
[KEY_UP
].Pressed
or FKeys
[KEY_JUMP
].Pressed
then
4015 FYTo
:= FObj
.Y
- 32;
4016 FSpectatePlayer
:= -1;
4018 if FKeys
[KEY_DOWN
].Pressed
then
4020 FYTo
:= FObj
.Y
+ 32;
4021 FSpectatePlayer
:= -1;
4023 if FKeys
[KEY_LEFT
].Pressed
then
4025 FXTo
:= FObj
.X
- 32;
4026 FSpectatePlayer
:= -1;
4028 if FKeys
[KEY_RIGHT
].Pressed
then
4030 FXTo
:= FObj
.X
+ 32;
4031 FSpectatePlayer
:= -1;
4034 if (FXTo
< -64) then
4036 else if (FXTo
> gMapInfo
.Width
+ 32) then
4037 FXTo
:= gMapInfo
.Width
+ 32;
4038 if (FYTo
< -72) then
4040 else if (FYTo
> gMapInfo
.Height
+ 32) then
4041 FYTo
:= gMapInfo
.Height
+ 32;
4046 g_Obj_Move(@FObj
, True, True, True);
4047 positionChanged(); // this updates spatial accelerators
4054 if (FSpectatePlayer
<= High(gPlayers
)) and (FSpectatePlayer
>= 0) then
4055 if gPlayers
[FSpectatePlayer
] <> nil then
4056 if gPlayers
[FSpectatePlayer
].alive
then
4058 FXTo
:= gPlayers
[FSpectatePlayer
].GameX
;
4059 FYTo
:= gPlayers
[FSpectatePlayer
].GameY
;
4063 blockmon
:= g_Map_CollidePanel(FObj
.X
+PLAYER_HEADRECT
.X
, FObj
.Y
+PLAYER_HEADRECT
.Y
,
4064 PLAYER_HEADRECT
.Width
, PLAYER_HEADRECT
.Height
,
4065 PANEL_BLOCKMON
, True);
4066 headwater
:= HeadInLiquid(0, 0);
4068 // Сопротивление воздуха:
4069 if (not FAlive
) or not (FKeys
[KEY_LEFT
].Pressed
or FKeys
[KEY_RIGHT
].Pressed
) then
4070 if FObj
.Vel
.X
<> 0 then
4071 FObj
.Vel
.X
:= z_dec(FObj
.Vel
.X
, 1);
4073 if (FLastHit
= HIT_TRAP
) and (FPain
> 90) then FPain
:= 90;
4074 DecMin(FPain
, 5, 0);
4075 DecMin(FPickup
, 1, 0);
4077 if FAlive
and (FObj
.Y
> Integer(gMapInfo
.Height
)+128) and AnyServer
then
4079 // Обнулить действия примочек, чтобы фон пропал
4080 FMegaRulez
[MR_SUIT
] := 0;
4081 FMegaRulez
[MR_INVUL
] := 0;
4082 FMegaRulez
[MR_INVIS
] := 0;
4083 Kill(K_FALLKILL
, 0, HIT_FALL
);
4090 if FCurrWeap
= WEAPON_SAW
then
4091 if not (FSawSound
.IsPlaying() or FSawSoundHit
.IsPlaying() or
4092 FSawSoundSelect
.IsPlaying()) then
4093 FSawSoundIdle
.PlayAt(FObj
.X
, FObj
.Y
);
4096 if (not FJetSoundFly
.IsPlaying()) and (not FJetSoundOn
.IsPlaying()) and
4097 (not FJetSoundOff
.IsPlaying()) then
4099 FJetSoundFly
.SetPosition(0);
4100 FJetSoundFly
.PlayAt(FObj
.X
, FObj
.Y
);
4103 for b
:= WP_FIRST
to WP_LAST
do
4104 if FReloading
[b
] > 0 then
4110 {$IFDEF ENABLE_SHELLS}
4111 if FShellTimer
> -1 then
4112 if FShellTimer
= 0 then
4114 if FShellType
= SHELL_SHELL
then
4115 g_Shells_Create(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
4116 GameVelX
, GameVelY
-2, SHELL_SHELL
)
4117 else if FShellType
= SHELL_DBLSHELL
then
4119 g_Shells_Create(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
4120 GameVelX
+1, GameVelY
-2, SHELL_SHELL
);
4121 g_Shells_Create(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
4122 GameVelX
-1, GameVelY
-2, SHELL_SHELL
);
4125 end else Dec(FShellTimer
);
4128 if (FBFGFireCounter
> -1) then
4129 if FBFGFireCounter
= 0 then
4133 wx
:= FObj
.X
+WEAPONPOINT
[FDirection
].X
;
4134 wy
:= FObj
.Y
+WEAPONPOINT
[FDirection
].Y
;
4135 xd
:= wx
+IfThen(FDirection
= TDirection
.D_LEFT
, -30, 30);
4136 yd
:= wy
+firediry();
4137 g_Weapon_bfgshot(wx
, wy
, xd
, yd
, FUID
);
4138 if NetServer
then MH_SEND_PlayerFire(FUID
, WEAPON_BFG
, wx
, wy
, xd
, yd
);
4139 if (FAngle
= 0) or (FAngle
= 180) then SetAction(A_ATTACK
)
4140 else if (FAngle
= ANGLE_LEFTDOWN
) or (FAngle
= ANGLE_RIGHTDOWN
) then SetAction(A_ATTACKDOWN
)
4141 else if (FAngle
= ANGLE_LEFTUP
) or (FAngle
= ANGLE_RIGHTUP
) then SetAction(A_ATTACKUP
);
4144 FReloading
[WEAPON_BFG
] := WEAPON_RELOAD
[WEAPON_BFG
];
4145 FBFGFireCounter
:= -1;
4148 FBFGFireCounter
:= 0
4150 Dec(FBFGFireCounter
);
4152 if (FMegaRulez
[MR_SUIT
] < gTime
) and AnyServer
then
4154 b
:= g_GetAcidHit(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+PLAYER_RECT
.Y
, PLAYER_RECT
.Width
, PLAYER_RECT
.Height
);
4156 if (b
> 0) and (gTime
mod (15*GAME_TICK
) = 0) then Damage(b
, 0, 0, 0, HIT_ACID
);
4159 if (headwater
or blockmon
) then
4165 if AnyServer
then Damage(10, 0, 0, 0, HIT_WATER
);
4168 else if (FAir
mod 31 = 0) and not blockmon
then
4171 g_GFX_Bubbles(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2), FObj
.Y
+PLAYER_RECT
.Y
-4, 5+Random(6), 8, 4);
4174 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj
.X
, FObj
.Y
)
4175 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj
.X
, FObj
.Y
);
4177 end else if FAir
< AIR_DEF
then
4180 if FFireTime
> 0 then
4182 if BodyInLiquid(0, 0) then
4187 else if FMegaRulez
[MR_SUIT
] >= gTime
then
4189 if FMegaRulez
[MR_SUIT
] = gTime
then
4196 if FFirePainTime
<= 0 then
4198 if g_Game_IsServer
then
4199 Damage(2, FFireAttacker
, 0, 0, HIT_FLAME
);
4200 FFirePainTime
:= 12 - FFireTime
div 12;
4202 FFirePainTime
:= FFirePainTime
- 1;
4203 FFireTime
:= FFireTime
- 1;
4204 if ((FFireTime
mod 33) = 0) and (FMegaRulez
[MR_INVUL
] < gTime
) then
4205 FModel
.PlaySound(MODELSOUND_PAIN
, 1, FObj
.X
, FObj
.Y
);
4206 if (FFireTime
= 0) and g_Game_IsNet
and g_Game_IsServer
then
4207 MH_SEND_PlayerStats(FUID
);
4211 if FDamageBuffer
> 0 then
4213 if FDamageBuffer
>= 9 then
4217 if FDamageBuffer
< 30 then i
:= 9
4218 else if FDamageBuffer
< 100 then i
:= 18
4222 ii
:= Round(FDamageBuffer
*FHealth
/ nonz(FArmor
*(3/4)+FHealth
));
4223 FArmor
:= FArmor
-(FDamageBuffer
-ii
);
4224 FHealth
:= FHealth
-ii
;
4227 FHealth
:= FHealth
+FArmor
;
4232 if FHealth
<= 0 then
4233 if FHealth
> -30 then Kill(K_SIMPLEKILL
, FLastSpawnerUID
, FLastHit
)
4234 else if FHealth
> -50 then Kill(K_HARDKILL
, FLastSpawnerUID
, FLastHit
)
4235 else Kill(K_EXTRAHARDKILL
, FLastSpawnerUID
, FLastHit
);
4237 if FAlive
and ((FLastHit
<> HIT_FLAME
) or (FFireTime
<= 0)) then
4239 if FDamageBuffer
<= 20 then FModel
.PlaySound(MODELSOUND_PAIN
, 1, FObj
.X
, FObj
.Y
)
4240 else if FDamageBuffer
<= 55 then FModel
.PlaySound(MODELSOUND_PAIN
, 2, FObj
.X
, FObj
.Y
)
4241 else if FDamageBuffer
<= 120 then FModel
.PlaySound(MODELSOUND_PAIN
, 3, FObj
.X
, FObj
.Y
)
4242 else FModel
.PlaySound(MODELSOUND_PAIN
, 4, FObj
.X
, FObj
.Y
);
4249 end; // if FAlive then ...
4251 if (FActionAnim
= A_PAIN
) and (FModel
.Animation
<> A_PAIN
) then
4253 FModel
.ChangeAnimation(FActionAnim
, FActionForce
);
4254 FModel
.AnimState
.MinLength
:= i
;
4255 end else FModel
.ChangeAnimation(FActionAnim
, FActionForce
and (FModel
.Animation
<> A_STAND
));
4257 if (FModel
.AnimState
.Played
or ((not FActionChanged
) and (FModel
.Animation
= A_WALK
)))
4258 then SetAction(A_STAND
, True);
4260 if not ((FModel
.Animation
= A_WALK
) and (Abs(FObj
.Vel
.X
) < 4) and not FModel
.GetFire()) then FModel
.Update
;
4262 for b
:= Low(FKeys
) to High(FKeys
) do
4263 if FKeys
[b
].Time
= 0 then FKeys
[b
].Pressed
:= False else Dec(FKeys
[b
].Time
);
4267 procedure TPlayer
.getMapBox (out x
, y
, w
, h
: Integer); inline;
4269 x
:= FObj
.X
+PLAYER_RECT
.X
;
4270 y
:= FObj
.Y
+PLAYER_RECT
.Y
;
4271 w
:= PLAYER_RECT
.Width
;
4272 h
:= PLAYER_RECT
.Height
;
4276 procedure TPlayer
.moveBy (dx
, dy
: Integer); inline;
4278 if (dx
<> 0) or (dy
<> 0) then
4287 function TPlayer
.Collide(X
, Y
: Integer; Width
, Height
: Word): Boolean;
4289 Result
:= g_Collide(FObj
.X
+PLAYER_RECT
.X
,
4290 FObj
.Y
+PLAYER_RECT
.Y
,
4297 function TPlayer
.Collide(Panel
: TPanel
): Boolean;
4299 Result
:= g_Collide(FObj
.X
+PLAYER_RECT
.X
,
4300 FObj
.Y
+PLAYER_RECT
.Y
,
4304 Panel
.Width
, Panel
.Height
);
4307 function TPlayer
.Collide(X
, Y
: Integer): Boolean;
4309 X
:= X
-FObj
.X
-PLAYER_RECT
.X
;
4310 Y
:= Y
-FObj
.Y
-PLAYER_RECT
.Y
;
4311 Result
:= (x
>= 0) and (x
<= PLAYER_RECT
.Width
) and
4312 (y
>= 0) and (y
<= PLAYER_RECT
.Height
);
4315 function g_Player_ValidName(Name
: string): Boolean;
4321 if gPlayers
= nil then Exit
;
4323 for a
:= 0 to High(gPlayers
) do
4324 if gPlayers
[a
] <> nil then
4325 if LowerCase(Name
) = LowerCase(gPlayers
[a
].FName
) then
4332 procedure TPlayer
.SetDirection(Direction
: TDirection
);
4336 d
:= FModel
.Direction
;
4338 FModel
.Direction
:= Direction
;
4339 if d
<> Direction
then FModel
.ChangeAnimation(FModel
.Animation
, True);
4341 FDirection
:= Direction
;
4344 function TPlayer
.GetKeys(): Byte;
4348 if R_KEY_RED
in FRulez
then Result
:= KEY_RED
;
4349 if R_KEY_GREEN
in FRulez
then Result
:= Result
or KEY_GREEN
;
4350 if R_KEY_BLUE
in FRulez
then Result
:= Result
or KEY_BLUE
;
4352 if FTeam
= TEAM_RED
then Result
:= Result
or KEY_REDTEAM
;
4353 if FTeam
= TEAM_BLUE
then Result
:= Result
or KEY_BLUETEAM
;
4356 procedure TPlayer
.Use();
4360 if FTime
[T_USE
] > gTime
then Exit
;
4362 g_Triggers_PressR(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+PLAYER_RECT
.Y
, PLAYER_RECT
.Width
,
4363 PLAYER_RECT
.Height
, FUID
, ACTIVATE_PLAYERPRESS
);
4365 for a
:= 0 to High(gPlayers
) do
4366 if (gPlayers
[a
] <> nil) and (gPlayers
[a
] <> Self
) and
4367 gPlayers
[a
].alive
and SameTeam(FUID
, gPlayers
[a
].FUID
) and
4368 g_Obj_Collide(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
,
4369 FObj
.Rect
.Width
, FObj
.Rect
.Height
, @gPlayers
[a
].FObj
) then
4371 gPlayers
[a
].Touch();
4372 if g_Game_IsNet
and g_Game_IsServer
then
4373 MH_SEND_GameEvent(NET_EV_PLAYER_TOUCH
, gPlayers
[a
].FUID
);
4376 FTime
[T_USE
] := gTime
+120;
4379 procedure TPlayer
.NetFire(Wpn
: Byte; X
, Y
, AX
, AY
: Integer; WID
: Integer = -1);
4382 visible
: Boolean = True;
4383 WX
, WY
, XD
, YD
: Integer;
4395 if R_BERSERK
in FRulez
then
4397 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
4398 locobj
.X
:= FObj
.X
+FObj
.Rect
.X
;
4399 locobj
.Y
:= FObj
.Y
+FObj
.Rect
.Y
;
4402 locobj
.rect
.Width
:= 39;
4403 locobj
.rect
.Height
:= 52;
4404 locobj
.Vel
.X
:= (xd
-wx
) div 2;
4405 locobj
.Vel
.Y
:= (yd
-wy
) div 2;
4406 locobj
.Accel
.X
:= xd
-wx
;
4407 locobj
.Accel
.y
:= yd
-wy
;
4409 if g_Weapon_Hit(@locobj
, 50, FUID
, HIT_SOME
) <> 0 then
4410 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj
.X
, FObj
.Y
)
4412 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj
.X
, FObj
.Y
);
4416 FPain
:= min(FPain
+ 25, 50);
4418 g_Weapon_punch(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
, 3, FUID
);
4423 if g_Weapon_chainsaw(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
,
4424 IfThen(gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
], 9, 3), FUID
) <> 0 then
4426 FSawSoundSelect
.Stop();
4428 FSawSoundHit
.PlayAt(FObj
.X
, FObj
.Y
);
4430 else if not FSawSoundHit
.IsPlaying() then
4432 FSawSoundSelect
.Stop();
4433 FSawSound
.PlayAt(FObj
.X
, FObj
.Y
);
4439 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', GameX
, Gamey
);
4440 FFireAngle
:= FAngle
;
4441 {$IFDEF ENABLE_SHELLS}
4442 g_Shells_Create(GameX
+ PLAYER_RECT_CX
, GameY
+ PLAYER_RECT_CX
, GameVelX
, GameVelY
- 2, SHELL_BULLET
);
4448 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex
, Gamey
);
4449 FFireAngle
:= FAngle
;
4450 {$IFDEF ENABLE_SHELLS}
4452 FShellType
:= SHELL_SHELL
;
4458 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', Gamex
, Gamey
);
4459 FFireAngle
:= FAngle
;
4460 {$IFDEF ENABLE_SHELLS}
4462 FShellType
:= SHELL_DBLSHELL
;
4468 g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', Gamex
, Gamey
);
4469 FFireAngle
:= FAngle
;
4470 {$IFDEF ENABLE_SHELLS}
4471 g_Shells_Create(GameX
+ PLAYER_RECT_CX
, GameY
+ PLAYER_RECT_CX
, GameVelX
, GameVelY
- 2, SHELL_BULLET
);
4475 WEAPON_ROCKETLAUNCHER
:
4477 g_Weapon_Rocket(wx
, wy
, xd
, yd
, FUID
, WID
);
4478 FFireAngle
:= FAngle
;
4483 g_Weapon_Plasma(wx
, wy
, xd
, yd
, FUID
, WID
);
4484 FFireAngle
:= FAngle
;
4489 g_Weapon_BFGShot(wx
, wy
, xd
, yd
, FUID
, WID
);
4490 FFireAngle
:= FAngle
;
4493 WEAPON_SUPERPULEMET
:
4495 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex
, Gamey
);
4496 FFireAngle
:= FAngle
;
4497 {$IFDEF ENABLE_SHELLS}
4498 g_Shells_Create(GameX
+ PLAYER_RECT_CX
, GameY
+ PLAYER_RECT_CX
, GameVelX
, GameVelY
- 2, SHELL_SHELL
);
4502 WEAPON_FLAMETHROWER
:
4504 g_Weapon_flame(wx
, wy
, xd
, yd
, FUID
, WID
);
4506 FFireAngle
:= FAngle
;
4510 if not visible
then Exit
;
4512 if (FAngle
= 0) or (FAngle
= 180) then SetAction(A_ATTACK
)
4513 else if (FAngle
= ANGLE_LEFTDOWN
) or (FAngle
= ANGLE_RIGHTDOWN
) then SetAction(A_ATTACKDOWN
)
4514 else if (FAngle
= ANGLE_LEFTUP
) or (FAngle
= ANGLE_RIGHTUP
) then SetAction(A_ATTACKUP
);
4517 procedure TPlayer
.DoLerp(Level
: Integer = 2);
4519 if FObj
.X
<> FXTo
then FObj
.X
:= Lerp(FObj
.X
, FXTo
, Level
);
4520 if FObj
.Y
<> FYTo
then FObj
.Y
:= Lerp(FObj
.Y
, FYTo
, Level
);
4523 procedure TPlayer
.SetLerp(XTo
, YTo
: Integer);
4529 if FJustTeleported
or (NetInterpLevel
< 1) then
4533 if FJustTeleported
then
4535 FObj
.oldX
:= FObj
.X
;
4536 FObj
.oldY
:= FObj
.Y
;
4541 AX
:= Abs(FXTo
- FObj
.X
);
4542 AY
:= Abs(FYTo
- FObj
.Y
);
4543 if (AX
> 32) or (AX
<= NetInterpLevel
) then
4545 if (AY
> 32) or (AY
<= NetInterpLevel
) then
4550 function TPlayer
.FullInLift(XInc
, YInc
: Integer): Integer;
4552 if g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
,
4553 PLAYER_RECT
.Width
, PLAYER_RECT
.Height
-8,
4554 PANEL_LIFTUP
, False) then Result
:= -1
4556 if g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
,
4557 PLAYER_RECT
.Width
, PLAYER_RECT
.Height
-8,
4558 PANEL_LIFTDOWN
, False) then Result
:= 1
4562 function TPlayer
.GetFlag(Flag
: Byte): Boolean;
4569 if Flag
= FLAG_NONE
then
4572 if not g_Game_IsServer
then Exit
;
4574 // Принес чужой флаг на свою базу:
4575 if (Flag
= FTeam
) and
4576 (gFlags
[Flag
].State
= FLAG_STATE_NORMAL
) and
4577 (FFlag
<> FLAG_NONE
) then
4579 if FFlag
= FLAG_RED
then
4580 s
:= _lc
[I_PLAYER_FLAG_RED
]
4582 s
:= _lc
[I_PLAYER_FLAG_BLUE
];
4584 evtype
:= FLAG_STATE_SCORED
;
4586 ts
:= Format('%.4d', [gFlags
[FFlag
].CaptureTime
]);
4587 Insert('.', ts
, Length(ts
) + 1 - 3);
4588 g_Console_Add(Format(_lc
[I_PLAYER_FLAG_CAPTURE
], [FName
, s
, ts
]), True);
4590 g_Map_ResetFlag(FFlag
);
4591 g_Game_Message(Format(_lc
[I_MESSAGE_FLAG_CAPTURE
], [AnsiUpperCase(s
)]), 144);
4593 if ((Self
= gPlayer1
) or (Self
= gPlayer2
)
4594 or ((gPlayer1
<> nil) and (gPlayer1
.Team
= FTeam
))
4595 or ((gPlayer2
<> nil) and (gPlayer2
.Team
= FTeam
))) then
4600 if not sound_cap_flag
[a
].IsPlaying() then
4601 sound_cap_flag
[a
].Play();
4603 gTeamStat
[FTeam
].Score
+= 1;
4606 if g_Game_IsNet
then
4608 MH_SEND_FlagEvent(evtype
, FFlag
, FUID
, False);
4612 gFlags
[FFlag
].CaptureTime
:= 0;
4617 // Подобрал свой флаг - вернул его на базу:
4618 if (Flag
= FTeam
) and
4619 (gFlags
[Flag
].State
= FLAG_STATE_DROPPED
) then
4621 if Flag
= FLAG_RED
then
4622 s
:= _lc
[I_PLAYER_FLAG_RED
]
4624 s
:= _lc
[I_PLAYER_FLAG_BLUE
];
4626 evtype
:= FLAG_STATE_RETURNED
;
4627 gFlags
[Flag
].CaptureTime
:= 0;
4629 g_Console_Add(Format(_lc
[I_PLAYER_FLAG_RETURN
], [FName
, s
]), True);
4631 g_Map_ResetFlag(Flag
);
4632 g_Game_Message(Format(_lc
[I_MESSAGE_FLAG_RETURN
], [AnsiUpperCase(s
)]), 144);
4634 if ((Self
= gPlayer1
) or (Self
= gPlayer2
)
4635 or ((gPlayer1
<> nil) and (gPlayer1
.Team
= FTeam
))
4636 or ((gPlayer2
<> nil) and (gPlayer2
.Team
= FTeam
))) then
4641 if not sound_ret_flag
[a
].IsPlaying() then
4642 sound_ret_flag
[a
].Play();
4645 if g_Game_IsNet
then
4647 MH_SEND_FlagEvent(evtype
, Flag
, FUID
, False);
4653 // Подобрал чужой флаг:
4654 if (Flag
<> FTeam
) and (FTime
[T_FLAGCAP
] <= gTime
) then
4658 if Flag
= FLAG_RED
then
4659 s
:= _lc
[I_PLAYER_FLAG_RED
]
4661 s
:= _lc
[I_PLAYER_FLAG_BLUE
];
4663 evtype
:= FLAG_STATE_CAPTURED
;
4665 g_Console_Add(Format(_lc
[I_PLAYER_FLAG_GET
], [FName
, s
]), True);
4667 g_Game_Message(Format(_lc
[I_MESSAGE_FLAG_GET
], [AnsiUpperCase(s
)]), 144);
4669 gFlags
[Flag
].State
:= FLAG_STATE_CAPTURED
;
4671 if ((Self
= gPlayer1
) or (Self
= gPlayer2
)
4672 or ((gPlayer1
<> nil) and (gPlayer1
.Team
= FTeam
))
4673 or ((gPlayer2
<> nil) and (gPlayer2
.Team
= FTeam
))) then
4678 if not sound_get_flag
[a
].IsPlaying() then
4679 sound_get_flag
[a
].Play();
4682 if g_Game_IsNet
then
4684 MH_SEND_FlagEvent(evtype
, Flag
, FUID
, False);
4690 procedure TPlayer
.SetFlag(Flag
: Byte);
4693 if FModel
<> nil then
4694 FModel
.SetFlag(FFlag
);
4697 function TPlayer
.TryDropFlag(): Boolean;
4699 if LongBool(gGameSettings
.Options
and GAME_OPTION_ALLOWDROPFLAG
) then
4700 Result
:= DropFlag(False, LongBool(gGameSettings
.Options
and GAME_OPTION_THROWFLAG
))
4705 function TPlayer
.DropFlag(Silent
: Boolean = True; DoThrow
: Boolean = False): Boolean;
4712 if (not g_Game_IsServer
) or (FFlag
= FLAG_NONE
) then
4714 FTime
[T_FLAGCAP
] := gTime
+ 2000;
4715 with gFlags
[FFlag
] do
4719 Direction
:= FDirection
;
4720 State
:= FLAG_STATE_DROPPED
;
4724 xv
:= FObj
.Vel
.X
+ IfThen(Direction
= TDirection
.D_RIGHT
, 10, -10);
4725 yv
:= FObj
.Vel
.Y
- 2;
4729 xv
:= (FObj
.Vel
.X
div 2);
4730 yv
:= (FObj
.Vel
.Y
div 2) - 2;
4732 g_Obj_Push(@Obj
, xv
, yv
);
4734 positionChanged(); // this updates spatial accelerators
4736 if FFlag
= FLAG_RED
then
4737 s
:= _lc
[I_PLAYER_FLAG_RED
]
4739 s
:= _lc
[I_PLAYER_FLAG_BLUE
];
4741 g_Console_Add(Format(_lc
[I_PLAYER_FLAG_DROP
], [FName
, s
]), True);
4742 g_Game_Message(Format(_lc
[I_MESSAGE_FLAG_DROP
], [AnsiUpperCase(s
)]), 144);
4744 if ((Self
= gPlayer1
) or (Self
= gPlayer2
)
4745 or ((gPlayer1
<> nil) and (gPlayer1
.Team
= FTeam
))
4746 or ((gPlayer2
<> nil) and (gPlayer2
.Team
= FTeam
))) then
4751 if (not Silent
) and (not sound_lost_flag
[a
].IsPlaying()) then
4752 sound_lost_flag
[a
].Play();
4754 if g_Game_IsNet
then
4755 MH_SEND_FlagEvent(FLAG_STATE_DROPPED
, Flag
, FUID
, False);
4761 procedure TPlayer
.GetSecret();
4763 if (self
= gPlayer1
) or (self
= gPlayer2
) then
4765 g_Console_Add(Format(_lc
[I_PLAYER_SECRET
], [FName
]), True);
4766 g_Sound_PlayEx('SOUND_GAME_SECRET');
4771 procedure TPlayer
.PressKey(Key
: Byte; Time
: Word = 1);
4773 Assert(Key
<= High(FKeys
));
4775 FKeys
[Key
].Pressed
:= True;
4776 FKeys
[Key
].Time
:= Time
;
4779 function TPlayer
.IsKeyPressed(K
: Byte): Boolean;
4781 Result
:= FKeys
[K
].Pressed
;
4784 procedure TPlayer
.ReleaseKeys();
4788 for a
:= Low(FKeys
) to High(FKeys
) do
4790 FKeys
[a
].Pressed
:= False;
4795 procedure TPlayer
.OnDamage(Angle
: SmallInt);
4799 function TPlayer
.firediry(): Integer;
4801 if FKeys
[KEY_UP
].Pressed
then Result
:= -42
4802 else if FKeys
[KEY_DOWN
].Pressed
then Result
:= 19
4806 procedure TPlayer
.RememberState();
4809 SavedState
: TPlayerSavedState
;
4811 SavedState
.Health
:= FHealth
;
4812 SavedState
.Armor
:= FArmor
;
4813 SavedState
.Air
:= FAir
;
4814 SavedState
.JetFuel
:= FJetFuel
;
4815 SavedState
.CurrWeap
:= FCurrWeap
;
4816 SavedState
.NextWeap
:= FNextWeap
;
4817 SavedState
.NextWeapDelay
:= FNextWeapDelay
;
4818 for i
:= Low(FWeapon
) to High(FWeapon
) do
4819 SavedState
.Weapon
[i
] := FWeapon
[i
];
4820 for i
:= Low(FAmmo
) to High(FAmmo
) do
4821 SavedState
.Ammo
[i
] := FAmmo
[i
];
4822 for i
:= Low(FMaxAmmo
) to High(FMaxAmmo
) do
4823 SavedState
.MaxAmmo
[i
] := FMaxAmmo
[i
];
4824 SavedState
.Rulez
:= FRulez
- [R_KEY_RED
, R_KEY_GREEN
, R_KEY_BLUE
];
4826 FSavedStateNum
:= -1;
4827 for i
:= Low(SavedStates
) to High(SavedStates
) do
4828 if not SavedStates
[i
].Used
then
4830 FSavedStateNum
:= i
;
4833 if FSavedStateNum
< 0 then
4835 SetLength(SavedStates
, Length(SavedStates
) + 1);
4836 FSavedStateNum
:= High(SavedStates
);
4839 SavedState
.Used
:= True;
4840 SavedStates
[FSavedStateNum
] := SavedState
;
4843 procedure TPlayer
.RecallState();
4846 SavedState
: TPlayerSavedState
;
4848 if(FSavedStateNum
< 0) or (FSavedStateNum
> High(SavedStates
)) then
4851 SavedState
:= SavedStates
[FSavedStateNum
];
4852 SavedStates
[FSavedStateNum
].Used
:= False;
4853 FSavedStateNum
:= -1;
4855 FHealth
:= SavedState
.Health
;
4856 FArmor
:= SavedState
.Armor
;
4857 FAir
:= SavedState
.Air
;
4858 FJetFuel
:= SavedState
.JetFuel
;
4859 FCurrWeap
:= SavedState
.CurrWeap
;
4860 FNextWeap
:= SavedState
.NextWeap
;
4861 FNextWeapDelay
:= SavedState
.NextWeapDelay
;
4862 for i
:= Low(FWeapon
) to High(FWeapon
) do
4863 FWeapon
[i
] := SavedState
.Weapon
[i
];
4864 for i
:= Low(FAmmo
) to High(FAmmo
) do
4865 FAmmo
[i
] := SavedState
.Ammo
[i
];
4866 for i
:= Low(FMaxAmmo
) to High(FMaxAmmo
) do
4867 FMaxAmmo
[i
] := SavedState
.MaxAmmo
[i
];
4868 FRulez
:= SavedState
.Rulez
;
4870 if gGameSettings
.GameType
= GT_SERVER
then
4871 MH_SEND_PlayerStats(FUID
);
4874 procedure TPlayer
.SaveState (st
: TStream
);
4880 utils
.writeSign(st
, 'PLYR');
4881 utils
.writeInt(st
, Byte(PLR_SAVE_VERSION
)); // version
4883 utils
.writeBool(st
, FIamBot
);
4885 utils
.writeInt(st
, Word(FUID
));
4887 utils
.writeStr(st
, FName
);
4889 utils
.writeInt(st
, Byte(FTeam
));
4891 utils
.writeBool(st
, FAlive
);
4892 // Израсходовал ли все жизни
4893 utils
.writeBool(st
, FNoRespawn
);
4895 if FDirection
= TDirection
.D_LEFT
then b
:= 1 else b
:= 2; // D_RIGHT
4896 utils
.writeInt(st
, Byte(b
));
4898 utils
.writeInt(st
, LongInt(FHealth
));
4899 // Коэффициент инвалидности
4900 utils
.writeInt(st
, LongInt(FHandicap
));
4902 utils
.writeInt(st
, Byte(FLives
));
4904 utils
.writeInt(st
, LongInt(FArmor
));
4906 utils
.writeInt(st
, LongInt(FAir
));
4908 utils
.writeInt(st
, LongInt(FJetFuel
));
4910 utils
.writeInt(st
, LongInt(FPain
));
4912 utils
.writeInt(st
, LongInt(FKills
));
4914 utils
.writeInt(st
, LongInt(FMonsterKills
));
4916 utils
.writeInt(st
, LongInt(FFrags
));
4918 utils
.writeInt(st
, Byte(FFragCombo
));
4919 // Время последнего фрага
4920 utils
.writeInt(st
, LongWord(FLastFrag
));
4922 utils
.writeInt(st
, LongInt(FDeath
));
4924 utils
.writeInt(st
, Byte(FFlag
));
4926 utils
.writeInt(st
, LongInt(FSecrets
));
4928 utils
.writeInt(st
, Byte(FCurrWeap
));
4930 utils
.writeInt(st
, Word(FNextWeap
));
4932 utils
.writeInt(st
, Byte(FNextWeapDelay
));
4933 // Время зарядки BFG
4934 utils
.writeInt(st
, SmallInt(FBFGFireCounter
));
4936 utils
.writeInt(st
, LongInt(FDamageBuffer
));
4937 // Последний ударивший
4938 utils
.writeInt(st
, Word(FLastSpawnerUID
));
4939 // Тип последнего полученного урона
4940 utils
.writeInt(st
, Byte(FLastHit
));
4942 Obj_SaveState(st
, @FObj
);
4943 // Текущее количество патронов
4944 for i
:= A_BULLETS
to A_HIGH
do utils
.writeInt(st
, Word(FAmmo
[i
]));
4945 // Максимальное количество патронов
4946 for i
:= A_BULLETS
to A_HIGH
do utils
.writeInt(st
, Word(FMaxAmmo
[i
]));
4948 for i
:= WP_FIRST
to WP_LAST
do utils
.writeBool(st
, FWeapon
[i
]);
4949 // Время перезарядки оружия
4950 for i
:= WP_FIRST
to WP_LAST
do utils
.writeInt(st
, Word(FReloading
[i
]));
4952 utils
.writeBool(st
, (R_ITEM_BACKPACK
in FRulez
));
4953 // Наличие красного ключа
4954 utils
.writeBool(st
, (R_KEY_RED
in FRulez
));
4955 // Наличие зеленого ключа
4956 utils
.writeBool(st
, (R_KEY_GREEN
in FRulez
));
4957 // Наличие синего ключа
4958 utils
.writeBool(st
, (R_KEY_BLUE
in FRulez
));
4960 utils
.writeBool(st
, (R_BERSERK
in FRulez
));
4961 // Время действия специальных предметов
4962 for i
:= MR_SUIT
to MR_MAX
do utils
.writeInt(st
, LongWord(FMegaRulez
[i
]));
4963 // Время до повторного респауна, смены оружия, исользования, захвата флага
4964 for i
:= T_RESPAWN
to T_FLAGCAP
do utils
.writeInt(st
, LongWord(FTime
[i
]));
4966 utils
.writeStr(st
, FModel
.GetName());
4968 utils
.writeInt(st
, Byte(FColor
.R
));
4969 utils
.writeInt(st
, Byte(FColor
.G
));
4970 utils
.writeInt(st
, Byte(FColor
.B
));
4974 procedure TPlayer
.LoadState (st
: TStream
);
4983 if not utils
.checkSign(st
, 'PLYR') then raise XStreamError
.Create('invalid player signature');
4984 if (utils
.readByte(st
) <> PLR_SAVE_VERSION
) then raise XStreamError
.Create('invalid player version');
4986 FIamBot
:= utils
.readBool(st
);
4988 FUID
:= utils
.readWord(st
);
4990 str
:= utils
.readStr(st
);
4991 if (self
<> gPlayer1
) and (self
<> gPlayer2
) then FName
:= str
;
4993 FTeam
:= utils
.readByte(st
);
4995 FAlive
:= utils
.readBool(st
);
4996 // Израсходовал ли все жизни
4997 FNoRespawn
:= utils
.readBool(st
);
4999 b
:= utils
.readByte(st
);
5000 if b
= 1 then FDirection
:= TDirection
.D_LEFT
else FDirection
:= TDirection
.D_RIGHT
; // b = 2
5002 FHealth
:= utils
.readLongInt(st
);
5003 // Коэффициент инвалидности
5004 FHandicap
:= utils
.readLongInt(st
);
5006 FLives
:= utils
.readByte(st
);
5008 FArmor
:= utils
.readLongInt(st
);
5010 FAir
:= utils
.readLongInt(st
);
5012 FJetFuel
:= utils
.readLongInt(st
);
5014 FPain
:= utils
.readLongInt(st
);
5016 FKills
:= utils
.readLongInt(st
);
5018 FMonsterKills
:= utils
.readLongInt(st
);
5020 FFrags
:= utils
.readLongInt(st
);
5022 FFragCombo
:= utils
.readByte(st
);
5023 // Время последнего фрага
5024 FLastFrag
:= utils
.readLongWord(st
);
5026 FDeath
:= utils
.readLongInt(st
);
5028 FFlag
:= utils
.readByte(st
);
5030 FSecrets
:= utils
.readLongInt(st
);
5032 FCurrWeap
:= utils
.readByte(st
);
5034 FNextWeap
:= utils
.readWord(st
);
5036 FNextWeapDelay
:= utils
.readByte(st
);
5037 // Время зарядки BFG
5038 FBFGFireCounter
:= utils
.readSmallInt(st
);
5040 FDamageBuffer
:= utils
.readLongInt(st
);
5041 // Последний ударивший
5042 FLastSpawnerUID
:= utils
.readWord(st
);
5043 // Тип последнего полученного урона
5044 FLastHit
:= utils
.readByte(st
);
5046 Obj_LoadState(@FObj
, st
);
5047 // Текущее количество патронов
5048 for i
:= A_BULLETS
to A_HIGH
do FAmmo
[i
] := utils
.readWord(st
);
5049 // Максимальное количество патронов
5050 for i
:= A_BULLETS
to A_HIGH
do FMaxAmmo
[i
] := utils
.readWord(st
);
5052 for i
:= WP_FIRST
to WP_LAST
do FWeapon
[i
] := utils
.readBool(st
);
5053 // Время перезарядки оружия
5054 for i
:= WP_FIRST
to WP_LAST
do FReloading
[i
] := utils
.readWord(st
);
5056 if utils
.readBool(st
) then Include(FRulez
, R_ITEM_BACKPACK
);
5057 // Наличие красного ключа
5058 if utils
.readBool(st
) then Include(FRulez
, R_KEY_RED
);
5059 // Наличие зеленого ключа
5060 if utils
.readBool(st
) then Include(FRulez
, R_KEY_GREEN
);
5061 // Наличие синего ключа
5062 if utils
.readBool(st
) then Include(FRulez
, R_KEY_BLUE
);
5064 if utils
.readBool(st
) then Include(FRulez
, R_BERSERK
);
5065 // Время действия специальных предметов
5066 for i
:= MR_SUIT
to MR_MAX
do FMegaRulez
[i
] := utils
.readLongWord(st
);
5067 // Время до повторного респауна, смены оружия, исользования, захвата флага
5068 for i
:= T_RESPAWN
to T_FLAGCAP
do FTime
[i
] := utils
.readLongWord(st
);
5070 str
:= utils
.readStr(st
);
5072 FColor
.R
:= utils
.readByte(st
);
5073 FColor
.G
:= utils
.readByte(st
);
5074 FColor
.B
:= utils
.readByte(st
);
5075 if (self
= gPlayer1
) then
5077 str
:= gPlayer1Settings
.Model
;
5078 FColor
:= gPlayer1Settings
.Color
;
5080 else if (self
= gPlayer2
) then
5082 str
:= gPlayer2Settings
.Model
;
5083 FColor
:= gPlayer2Settings
.Color
;
5085 // Обновляем модель игрока
5087 if gGameSettings
.GameMode
in [GM_TDM
, GM_CTF
] then
5088 FModel
.Color
:= TEAMCOLOR
[FTeam
]
5090 FModel
.Color
:= FColor
;
5094 procedure TPlayer
.AllRulez(Health
: Boolean);
5100 FHealth
:= PLAYER_HP_LIMIT
;
5101 FArmor
:= PLAYER_AP_LIMIT
;
5105 for a
:= WP_FIRST
to WP_LAST
do FWeapon
[a
] := True;
5106 for a
:= A_BULLETS
to A_HIGH
do FAmmo
[a
] := 30000;
5107 FRulez
:= FRulez
+[R_KEY_RED
, R_KEY_GREEN
, R_KEY_BLUE
];
5110 procedure TPlayer
.RestoreHealthArmor();
5112 FHealth
:= PLAYER_HP_LIMIT
;
5113 FArmor
:= PLAYER_AP_LIMIT
;
5116 procedure TPlayer
.FragCombo();
5120 if (gGameSettings
.GameMode
in [GM_COOP
, GM_SINGLE
]) or g_Game_IsClient
then
5122 if gTime
- FLastFrag
< FRAG_COMBO_TIME
then
5124 if FFragCombo
< 5 then
5126 Param
:= FUID
or (FFragCombo
shl 16);
5127 if (FComboEvnt
>= Low(gDelayedEvents
)) and
5128 (FComboEvnt
<= High(gDelayedEvents
)) and
5129 gDelayedEvents
[FComboEvnt
].Pending
and
5130 (gDelayedEvents
[FComboEvnt
].DEType
= DE_KILLCOMBO
) and
5131 (gDelayedEvents
[FComboEvnt
].DENum
and $FFFF = FUID
) then
5133 gDelayedEvents
[FComboEvnt
].Time
:= gTime
+ 500;
5134 gDelayedEvents
[FComboEvnt
].DENum
:= Param
;
5137 FComboEvnt
:= g_Game_DelayEvent(DE_KILLCOMBO
, 500, Param
);
5145 procedure TPlayer
.GiveItem(ItemType
: Byte);
5149 if FMegaRulez
[MR_SUIT
] < gTime
+PLAYER_SUIT_TIME
then
5151 FMegaRulez
[MR_SUIT
] := gTime
+PLAYER_SUIT_TIME
;
5155 if FAir
< AIR_MAX
then
5162 if not (R_BERSERK
in FRulez
) then
5164 Include(FRulez
, R_BERSERK
);
5165 if FBFGFireCounter
< 1 then
5167 FCurrWeap
:= WEAPON_KASTET
;
5169 FModel
.SetWeapon(WEAPON_KASTET
);
5173 FBerserk
:= gTime
+30000;
5175 if FHealth
< PLAYER_HP_SOFT
then
5177 FHealth
:= PLAYER_HP_SOFT
;
5178 FBerserk
:= gTime
+30000;
5183 if FMegaRulez
[MR_INVUL
] < gTime
+PLAYER_INVUL_TIME
then
5185 FMegaRulez
[MR_INVUL
] := gTime
+PLAYER_INVUL_TIME
;
5190 if FMegaRulez
[MR_INVIS
] < gTime
+PLAYER_INVIS_TIME
then
5192 FMegaRulez
[MR_INVIS
] := gTime
+PLAYER_INVIS_TIME
;
5196 if FJetFuel
< JET_MAX
then
5198 FJetFuel
:= JET_MAX
;
5201 ITEM_MEDKIT_SMALL
: if FHealth
< PLAYER_HP_SOFT
then IncMax(FHealth
, 10, PLAYER_HP_SOFT
);
5202 ITEM_MEDKIT_LARGE
: if FHealth
< PLAYER_HP_SOFT
then IncMax(FHealth
, 25, PLAYER_HP_SOFT
);
5204 ITEM_ARMOR_GREEN
: if FArmor
< PLAYER_AP_SOFT
then FArmor
:= PLAYER_AP_SOFT
;
5205 ITEM_ARMOR_BLUE
: if FArmor
< PLAYER_AP_LIMIT
then FArmor
:= PLAYER_AP_LIMIT
;
5207 ITEM_SPHERE_BLUE
: if FHealth
< PLAYER_HP_LIMIT
then IncMax(FHealth
, 100, PLAYER_HP_LIMIT
);
5209 if (FHealth
< PLAYER_HP_LIMIT
) or (FArmor
< PLAYER_AP_LIMIT
) then
5211 if FHealth
< PLAYER_HP_LIMIT
then FHealth
:= PLAYER_HP_LIMIT
;
5212 if FArmor
< PLAYER_AP_LIMIT
then FArmor
:= PLAYER_AP_LIMIT
;
5215 ITEM_WEAPON_SAW
: FWeapon
[WEAPON_SAW
] := True;
5216 ITEM_WEAPON_SHOTGUN1
: FWeapon
[WEAPON_SHOTGUN1
] := True;
5217 ITEM_WEAPON_SHOTGUN2
: FWeapon
[WEAPON_SHOTGUN2
] := True;
5218 ITEM_WEAPON_CHAINGUN
: FWeapon
[WEAPON_CHAINGUN
] := True;
5219 ITEM_WEAPON_ROCKETLAUNCHER
: FWeapon
[WEAPON_ROCKETLAUNCHER
] := True;
5220 ITEM_WEAPON_PLASMA
: FWeapon
[WEAPON_PLASMA
] := True;
5221 ITEM_WEAPON_BFG
: FWeapon
[WEAPON_BFG
] := True;
5222 ITEM_WEAPON_SUPERPULEMET
: FWeapon
[WEAPON_SUPERPULEMET
] := True;
5223 ITEM_WEAPON_FLAMETHROWER
: FWeapon
[WEAPON_FLAMETHROWER
] := True;
5225 ITEM_AMMO_BULLETS
: if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then IncMax(FAmmo
[A_BULLETS
], 10, FMaxAmmo
[A_BULLETS
]);
5226 ITEM_AMMO_BULLETS_BOX
: if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then IncMax(FAmmo
[A_BULLETS
], 50, FMaxAmmo
[A_BULLETS
]);
5227 ITEM_AMMO_SHELLS
: if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
5228 ITEM_AMMO_SHELLS_BOX
: if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then IncMax(FAmmo
[A_SHELLS
], 25, FMaxAmmo
[A_SHELLS
]);
5229 ITEM_AMMO_ROCKET
: if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then IncMax(FAmmo
[A_ROCKETS
], 1, FMaxAmmo
[A_ROCKETS
]);
5230 ITEM_AMMO_ROCKET_BOX
: if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then IncMax(FAmmo
[A_ROCKETS
], 5, FMaxAmmo
[A_ROCKETS
]);
5231 ITEM_AMMO_CELL
: if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
5232 ITEM_AMMO_CELL_BIG
: if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then IncMax(FAmmo
[A_CELLS
], 100, FMaxAmmo
[A_CELLS
]);
5233 ITEM_AMMO_FUELCAN
: if FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
] then IncMax(FAmmo
[A_FUEL
], 100, FMaxAmmo
[A_FUEL
]);
5236 if (FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
]) or
5237 (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or
5238 (FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
]) or
5239 (FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
]) or
5240 (FMaxAmmo
[A_FUEL
] < AmmoLimits
[1, A_FUEL
]) then
5242 FMaxAmmo
[A_BULLETS
] := AmmoLimits
[1, A_BULLETS
];
5243 FMaxAmmo
[A_SHELLS
] := AmmoLimits
[1, A_SHELLS
];
5244 FMaxAmmo
[A_ROCKETS
] := AmmoLimits
[1, A_ROCKETS
];
5245 FMaxAmmo
[A_CELLS
] := AmmoLimits
[1, A_CELLS
];
5246 FMaxAmmo
[A_FUEL
] := AmmoLimits
[1, A_FUEL
];
5248 if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then IncMax(FAmmo
[A_BULLETS
], 10, FMaxAmmo
[A_BULLETS
]);
5249 if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
5250 if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then IncMax(FAmmo
[A_ROCKETS
], 1, FMaxAmmo
[A_ROCKETS
]);
5251 if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
5253 FRulez
:= FRulez
+ [R_ITEM_BACKPACK
];
5256 ITEM_KEY_RED
: if not (R_KEY_RED
in FRulez
) then Include(FRulez
, R_KEY_RED
);
5257 ITEM_KEY_GREEN
: if not (R_KEY_GREEN
in FRulez
) then Include(FRulez
, R_KEY_GREEN
);
5258 ITEM_KEY_BLUE
: if not (R_KEY_BLUE
in FRulez
) then Include(FRulez
, R_KEY_BLUE
);
5260 ITEM_BOTTLE
: if FHealth
< PLAYER_HP_LIMIT
then IncMax(FHealth
, 4, PLAYER_HP_LIMIT
);
5261 ITEM_HELMET
: if FArmor
< PLAYER_AP_LIMIT
then IncMax(FArmor
, 5, PLAYER_AP_LIMIT
);
5266 if g_Game_IsNet
and g_Game_IsServer
then
5267 MH_SEND_PlayerStats(FUID
);
5270 procedure TPlayer
.FlySmoke(Times
: DWORD
= 1);
5273 if (Random(5) = 1) and (Times
= 1) then
5276 if BodyInLiquid(0, 0) then
5279 g_GFX_Bubbles(Obj
.X
+Obj
.Rect
.X
+(Obj
.Rect
.Width
div 2)+Random(3)-1,
5280 Obj
.Y
+Obj
.Rect
.Height
+8, 1, 8, 4);
5283 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj
.X
, FObj
.Y
)
5284 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj
.X
, FObj
.Y
);
5288 for i
:= 1 to Times
do
5293 Obj
.X
+Obj
.Rect
.X
+Random(Obj
.Rect
.Width
+Times
*2)-(R_GFX_SMOKE_WIDTH
div 2),
5294 Obj
.Y
+Obj
.Rect
.Height
-4+Random(8+Times
*2)
5300 procedure TPlayer
.OnFireFlame(Times
: DWORD
= 1);
5303 if (Random(10) = 1) and (Times
= 1) then
5306 for i
:= 1 to Times
do
5311 Obj
.X
+Obj
.Rect
.X
+Random(Obj
.Rect
.Width
+Times
*2)-(R_GFX_FLAME_WIDTH
div 2),
5312 Obj
.Y
+8+Random(8+Times
*2)
5318 procedure TPlayer
.PauseSounds(Enable
: Boolean);
5320 FSawSound
.Pause(Enable
);
5321 FSawSoundIdle
.Pause(Enable
);
5322 FSawSoundHit
.Pause(Enable
);
5323 FSawSoundSelect
.Pause(Enable
);
5324 FFlameSoundOn
.Pause(Enable
);
5325 FFlameSoundOff
.Pause(Enable
);
5326 FFlameSoundWork
.Pause(Enable
);
5327 FJetSoundFly
.Pause(Enable
);
5328 FJetSoundOn
.Pause(Enable
);
5329 FJetSoundOff
.Pause(Enable
);
5334 constructor TBot
.Create();
5341 FSpectator
:= False;
5348 for a
:= WP_FIRST
to WP_LAST
do
5350 FDifficult
.WeaponPrior
[a
] := WEAPON_PRIOR1
[a
];
5351 FDifficult
.CloseWeaponPrior
[a
] := WEAPON_PRIOR2
[a
];
5352 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
5356 destructor TBot
.Destroy();
5359 inherited Destroy();
5362 procedure TBot
.Respawn(Silent
: Boolean; Force
: Boolean = False);
5364 inherited Respawn(Silent
, Force
);
5367 FSelectedWeapon
:= FCurrWeap
;
5372 procedure TBot
.UpdateCombat();
5385 TTargetRecord
= array of TTarget
;
5387 function Compare(a
, b
: TTarget
): Integer;
5389 if a
.Line
and not b
.Line
then // A на линии огня
5392 if not a
.Line
and b
.Line
then // B на линии огня
5394 else // И A, и B на линии или не на линии огня
5395 if (a
.Line
and b
.Line
) or ((not a
.Line
) and (not b
.Line
)) then
5397 if a
.Dist
> b
.Dist
then // B ближе
5399 else // A ближе или равноудаленно с B
5402 else // Странно -> A
5407 a
, x1
, y1
, x2
, y2
: Integer;
5408 targets
: TTargetRecord
;
5410 Target
, BestTarget
: TTarget
;
5411 firew
, fireh
: Integer;
5415 vsPlayer
, vsMonster
, ok
: Boolean;
5418 function monsUpdate (mon
: TMonster
): Boolean;
5420 result
:= false; // don't stop
5421 if mon
.alive
and (mon
.MonsterType
<> MONSTER_BARREL
) then
5423 if not TargetOnScreen(mon
.Obj
.X
+mon
.Obj
.Rect
.X
, mon
.Obj
.Y
+mon
.Obj
.Rect
.Y
) then exit
;
5425 x2
:= mon
.Obj
.X
+mon
.Obj
.Rect
.X
+(mon
.Obj
.Rect
.Width
div 2);
5426 y2
:= mon
.Obj
.Y
+mon
.Obj
.Rect
.Y
+(mon
.Obj
.Rect
.Height
div 2);
5428 // Если монстр на экране и не прикрыт стеной
5429 if g_TraceVector(x1
, y1
, x2
, y2
) then
5431 // Добавляем к списку возможных целей
5432 SetLength(targets
, Length(targets
)+1);
5433 with targets
[High(targets
)] do
5440 Rect
:= mon
.Obj
.Rect
;
5441 Dist
:= g_PatchLength(x1
, y1
, x2
, y2
);
5442 Line
:= (y1
+4 < Target
.Y
+ mon
.Obj
.Rect
.Y
+ mon
.Obj
.Rect
.Height
) and
5443 (y1
-4 > Target
.Y
+ mon
.Obj
.Rect
.Y
);
5452 vsPlayer
:= LongBool(gGameSettings
.Options
and GAME_OPTION_BOTVSPLAYER
);
5453 vsMonster
:= LongBool(gGameSettings
.Options
and GAME_OPTION_BOTVSMONSTER
);
5455 // Если текущее оружие не то, что нужно, то меняем:
5456 if FCurrWeap
<> FSelectedWeapon
then
5459 // Если нужно стрелять и нужное оружие, то нажать "Стрелять":
5460 if (GetAIFlag('NEEDFIRE') <> '') and (FCurrWeap
= FSelectedWeapon
) then
5462 RemoveAIFlag('NEEDFIRE');
5465 WEAPON_PLASMA
, WEAPON_SUPERPULEMET
, WEAPON_CHAINGUN
: PressKey(KEY_FIRE
, 20);
5466 WEAPON_SAW
, WEAPON_KASTET
, WEAPON_FLAMETHROWER
: PressKey(KEY_FIRE
, 40);
5467 else PressKey(KEY_FIRE
);
5471 // Координаты ствола:
5472 x1
:= FObj
.X
+ WEAPONPOINT
[FDirection
].X
;
5473 y1
:= FObj
.Y
+ WEAPONPOINT
[FDirection
].Y
;
5475 Target
.UID
:= FTargetUID
;
5478 if Target
.UID
<> 0 then
5479 begin // Цель есть - настраиваем
5480 if (g_GetUIDType(Target
.UID
) = UID_PLAYER
) and
5483 tpla
:= g_Player_Get(Target
.UID
);
5487 if (@FObj
) <> nil then
5494 Target
.cX
:= Target
.X
+ PLAYER_RECT_CX
;
5495 Target
.cY
:= Target
.Y
+ PLAYER_RECT_CY
;
5496 Target
.Rect
:= PLAYER_RECT
;
5497 Target
.Visible
:= g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
);
5498 Target
.Line
:= (y1
+4 < Target
.Y
+PLAYER_RECT
.Y
+PLAYER_RECT
.Height
) and
5499 (y1
-4 > Target
.Y
+PLAYER_RECT
.Y
);
5500 Target
.IsPlayer
:= True;
5504 if (g_GetUIDType(Target
.UID
) = UID_MONSTER
) and
5507 mon
:= g_Monsters_ByUID(Target
.UID
);
5510 Target
.X
:= mon
.Obj
.X
;
5511 Target
.Y
:= mon
.Obj
.Y
;
5513 Target
.cX
:= Target
.X
+ mon
.Obj
.Rect
.X
+ (mon
.Obj
.Rect
.Width
div 2);
5514 Target
.cY
:= Target
.Y
+ mon
.Obj
.Rect
.Y
+ (mon
.Obj
.Rect
.Height
div 2);
5515 Target
.Rect
:= mon
.Obj
.Rect
;
5516 Target
.Visible
:= g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
);
5517 Target
.Line
:= (y1
+4 < Target
.Y
+ mon
.Obj
.Rect
.Y
+ mon
.Obj
.Rect
.Height
) and
5518 (y1
-4 > Target
.Y
+ mon
.Obj
.Rect
.Y
);
5519 Target
.IsPlayer
:= False;
5526 begin // Цели нет - обнуляем
5531 Target
.Visible
:= False;
5532 Target
.Line
:= False;
5533 Target
.IsPlayer
:= False;
5538 // Если цель не видима или не на линии огня, то ищем все возможные цели:
5539 if (not Target
.Line
) or (not Target
.Visible
) then
5543 for a
:= 0 to High(gPlayers
) do
5544 if (gPlayers
[a
] <> nil) and (gPlayers
[a
].alive
) and
5545 (gPlayers
[a
].FUID
<> FUID
) and
5546 (not SameTeam(FUID
, gPlayers
[a
].FUID
)) and
5547 (not gPlayers
[a
].NoTarget
) and
5548 (gPlayers
[a
].FMegaRulez
[MR_INVIS
] < gTime
) then
5550 if not TargetOnScreen(gPlayers
[a
].FObj
.X
+ PLAYER_RECT
.X
,
5551 gPlayers
[a
].FObj
.Y
+ PLAYER_RECT
.Y
) then
5554 x2
:= gPlayers
[a
].FObj
.X
+ PLAYER_RECT_CX
;
5555 y2
:= gPlayers
[a
].FObj
.Y
+ PLAYER_RECT_CY
;
5557 // Если игрок на экране и не прикрыт стеной:
5558 if g_TraceVector(x1
, y1
, x2
, y2
) then
5560 // Добавляем к списку возможных целей:
5561 SetLength(targets
, Length(targets
)+1);
5562 with targets
[High(targets
)] do
5564 UID
:= gPlayers
[a
].FUID
;
5565 X
:= gPlayers
[a
].FObj
.X
;
5566 Y
:= gPlayers
[a
].FObj
.Y
;
5569 Rect
:= PLAYER_RECT
;
5570 Dist
:= g_PatchLength(x1
, y1
, x2
, y2
);
5571 Line
:= (y1
+4 < Target
.Y
+PLAYER_RECT
.Y
+PLAYER_RECT
.Height
) and
5572 (y1
-4 > Target
.Y
+PLAYER_RECT
.Y
);
5580 if vsMonster
then g_Mons_ForEach(monsUpdate
);
5583 // Если есть возможные цели:
5584 // (Выбираем лучшую, меняем оружие и бежим к ней/от нее)
5585 if targets
<> nil then
5587 // Выбираем наилучшую цель:
5588 BestTarget
:= targets
[0];
5589 if Length(targets
) > 1 then
5590 for a
:= 1 to High(targets
) do
5591 if Compare(BestTarget
, targets
[a
]) = 1 then
5592 BestTarget
:= targets
[a
];
5594 // Если лучшая цель "виднее" текущей, то текущая := лучшая:
5595 if ((not Target
.Visible
) and BestTarget
.Visible
and (Target
.UID
<> BestTarget
.UID
)) or
5596 ((not Target
.Line
) and BestTarget
.Line
and BestTarget
.Visible
) then
5598 Target
:= BestTarget
;
5600 if (Healthy() = 3) or ((Healthy() = 2)) then
5601 begin // Если здоровы - догоняем
5602 if ((RunDirection() = TDirection
.D_LEFT
) and (Target
.X
> FObj
.X
)) then
5603 SetAIFlag('GORIGHT', '1');
5604 if ((RunDirection() = TDirection
.D_RIGHT
) and (Target
.X
< FObj
.X
)) then
5605 SetAIFlag('GOLEFT', '1');
5608 begin // Если побиты - убегаем
5609 if ((RunDirection() = TDirection
.D_LEFT
) and (Target
.X
< FObj
.X
)) then
5610 SetAIFlag('GORIGHT', '1');
5611 if ((RunDirection() = TDirection
.D_RIGHT
) and (Target
.X
> FObj
.X
)) then
5612 SetAIFlag('GOLEFT', '1');
5615 // Выбираем оружие на основе расстояния и приоритетов:
5616 SelectWeapon(Abs(x1
-Target
.cX
));
5621 // (Догоняем/убегаем, стреляем по направлению к цели)
5622 // (Если цель далеко, то хватит следить за ней)
5623 if Target
.UID
<> 0 then
5625 if not TargetOnScreen(Target
.X
+ Target
.Rect
.X
,
5626 Target
.Y
+ Target
.Rect
.Y
) then
5627 begin // Цель сбежала с "экрана"
5628 if (Healthy() = 3) or ((Healthy() = 2)) then
5629 begin // Если здоровы - догоняем
5630 if ((RunDirection() = TDirection
.D_LEFT
) and (Target
.X
> FObj
.X
)) then
5631 SetAIFlag('GORIGHT', '1');
5632 if ((RunDirection() = TDirection
.D_RIGHT
) and (Target
.X
< FObj
.X
)) then
5633 SetAIFlag('GOLEFT', '1');
5636 begin // Если побиты - забываем о цели и убегаем
5638 if ((RunDirection() = TDirection
.D_LEFT
) and (Target
.X
< FObj
.X
)) then
5639 SetAIFlag('GORIGHT', '1');
5640 if ((RunDirection() = TDirection
.D_RIGHT
) and (Target
.X
> FObj
.X
)) then
5641 SetAIFlag('GOLEFT', '1');
5645 begin // Цель пока на "экране"
5646 // Если цель не загорожена стеной, то отмечаем, когда ее видели:
5647 if g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
) then
5648 FLastVisible
:= gTime
;
5649 // Если разница высот не велика, то догоняем:
5650 if (Abs(FObj
.Y
-Target
.Y
) <= 128) then
5652 if ((RunDirection() = TDirection
.D_LEFT
) and (Target
.X
> FObj
.X
)) then
5653 SetAIFlag('GORIGHT', '1');
5654 if ((RunDirection() = TDirection
.D_RIGHT
) and (Target
.X
< FObj
.X
)) then
5655 SetAIFlag('GOLEFT', '1');
5659 // Выбираем угол вверх:
5660 if FDirection
= TDirection
.D_LEFT
then
5661 angle
:= ANGLE_LEFTUP
5663 angle
:= ANGLE_RIGHTUP
;
5665 firew
:= Trunc(Cos(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
5666 fireh
:= Trunc(Sin(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
5668 // Если при угле вверх можно попасть в приблизительное положение цели:
5669 if g_CollideLine(x1
, y1
, x1
+firew
, y1
+fireh
,
5670 Target
.X
+Target
.Rect
.X
+GetInterval(FDifficult
.DiagPrecision
, 128), //96
5671 Target
.Y
+Target
.Rect
.Y
+GetInterval(FDifficult
.DiagPrecision
, 128),
5672 Target
.Rect
.Width
, Target
.Rect
.Height
) and
5673 g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
) then
5674 begin // то нужно стрелять вверх
5675 SetAIFlag('NEEDFIRE', '1');
5676 SetAIFlag('NEEDSEEUP', '1');
5679 // Выбираем угол вниз:
5680 if FDirection
= TDirection
.D_LEFT
then
5681 angle
:= ANGLE_LEFTDOWN
5683 angle
:= ANGLE_RIGHTDOWN
;
5685 firew
:= Trunc(Cos(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
5686 fireh
:= Trunc(Sin(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
5688 // Если при угле вниз можно попасть в приблизительное положение цели:
5689 if g_CollideLine(x1
, y1
, x1
+firew
, y1
+fireh
,
5690 Target
.X
+Target
.Rect
.X
+GetInterval(FDifficult
.DiagPrecision
, 128),
5691 Target
.Y
+Target
.Rect
.Y
+GetInterval(FDifficult
.DiagPrecision
, 128),
5692 Target
.Rect
.Width
, Target
.Rect
.Height
) and
5693 g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
) then
5694 begin // то нужно стрелять вниз
5695 SetAIFlag('NEEDFIRE', '1');
5696 SetAIFlag('NEEDSEEDOWN', '1');
5699 // Если цель видно и она на такой же высоте:
5700 if Target
.Visible
and
5701 (y1
+4 < Target
.Y
+Target
.Rect
.Y
+Target
.Rect
.Height
) and
5702 (y1
-4 > Target
.Y
+Target
.Rect
.Y
) then
5704 // Если идем в сторону цели, то надо стрелять:
5705 if ((FDirection
= TDirection
.D_LEFT
) and (Target
.X
< FObj
.X
)) or
5706 ((FDirection
= TDirection
.D_RIGHT
) and (Target
.X
> FObj
.X
)) then
5707 begin // то нужно стрелять вперед
5708 SetAIFlag('NEEDFIRE', '1');
5709 SetAIFlag('NEEDSEEDOWN', '');
5710 SetAIFlag('NEEDSEEUP', '');
5712 // Если цель в пределах "экрана" и сложность позволяет прыжки сближения:
5713 if Abs(FObj
.X
-Target
.X
) < Trunc(gPlayerScreenSize
.X
*0.75) then
5714 if GetRnd(FDifficult
.CloseJump
) then
5715 begin // то если повезет - прыгаем (особенно, если близко)
5716 if Abs(FObj
.X
-Target
.X
) < 128 then
5720 if Random(a
) = 0 then
5721 SetAIFlag('NEEDJUMP', '1');
5725 // Если цель все еще есть:
5726 if Target
.UID
<> 0 then
5727 if gTime
-FLastVisible
> 2000 then // Если видели давно
5728 Target
.UID
:= 0 // то забыть цель
5729 else // Если видели недавно
5730 begin // но цель убили
5731 if Target
.IsPlayer
then
5732 begin // Цель - игрок
5733 pla
:= g_Player_Get(Target
.UID
);
5734 if (pla
= nil) or (not pla
.alive
) or pla
.NoTarget
or
5735 (pla
.FMegaRulez
[MR_INVIS
] >= gTime
) then
5736 Target
.UID
:= 0; // то забыть цель
5739 begin // Цель - монстр
5740 mon
:= g_Monsters_ByUID(Target
.UID
);
5741 if (mon
= nil) or (not mon
.alive
) then
5742 Target
.UID
:= 0; // то забыть цель
5745 end; // if Target.UID <> 0
5747 FTargetUID
:= Target
.UID
;
5749 // Если возможных целей нет:
5750 // (Атака чего-нибудь слева или справа)
5751 if targets
= nil then
5752 if GetAIFlag('ATTACKLEFT') <> '' then
5753 begin // Если нужно атаковать налево
5754 RemoveAIFlag('ATTACKLEFT');
5756 SetAIFlag('NEEDJUMP', '1');
5758 if RunDirection() = TDirection
.D_RIGHT
then
5759 begin // Идем не в ту сторону
5760 if (Healthy() > 1) and GetRnd(FDifficult
.InvisFire
) then
5761 begin // Если здоровы, то, возможно, стреляем бежим влево и стреляем
5762 SetAIFlag('NEEDFIRE', '1');
5763 SetAIFlag('GOLEFT', '1');
5767 begin // Идем в нужную сторону
5768 if GetRnd(FDifficult
.InvisFire
) then // Возможно, стреляем вслепую
5769 SetAIFlag('NEEDFIRE', '1');
5770 if Healthy() <= 1 then // Побиты - убегаем
5771 SetAIFlag('GORIGHT', '1');
5775 if GetAIFlag('ATTACKRIGHT') <> '' then
5776 begin // Если нужно атаковать направо
5777 RemoveAIFlag('ATTACKRIGHT');
5779 SetAIFlag('NEEDJUMP', '1');
5781 if RunDirection() = TDirection
.D_LEFT
then
5782 begin // Идем не в ту сторону
5783 if (Healthy() > 1) and GetRnd(FDifficult
.InvisFire
) then
5784 begin // Если здоровы, то, возможно, бежим вправо и стреляем
5785 SetAIFlag('NEEDFIRE', '1');
5786 SetAIFlag('GORIGHT', '1');
5791 if GetRnd(FDifficult
.InvisFire
) then // Возможно, стреляем вслепую
5792 SetAIFlag('NEEDFIRE', '1');
5793 if Healthy() <= 1 then // Побиты - убегаем
5794 SetAIFlag('GOLEFT', '1');
5798 //HACK! (does it belongs there?)
5799 RealizeCurrentWeapon();
5801 // Если есть возможные цели:
5802 // (Стреляем по направлению к целям)
5803 if (targets
<> nil) and (GetAIFlag('NEEDFIRE') <> '') then
5804 for a
:= 0 to High(targets
) do
5806 // Если можем стрелять по диагонали:
5807 if GetRnd(FDifficult
.DiagFire
) then
5809 // Ищем цель сверху и стреляем, если есть:
5810 if FDirection
= TDirection
.D_LEFT
then
5811 angle
:= ANGLE_LEFTUP
5813 angle
:= ANGLE_RIGHTUP
;
5815 firew
:= Trunc(Cos(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
5816 fireh
:= Trunc(Sin(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
5818 if g_CollideLine(x1
, y1
, x1
+firew
, y1
+fireh
,
5819 targets
[a
].X
+targets
[a
].Rect
.X
+GetInterval(FDifficult
.DiagPrecision
, 128),
5820 targets
[a
].Y
+targets
[a
].Rect
.Y
+GetInterval(FDifficult
.DiagPrecision
, 128),
5821 targets
[a
].Rect
.Width
, targets
[a
].Rect
.Height
) and
5822 g_TraceVector(x1
, y1
, targets
[a
].cX
, targets
[a
].cY
) then
5824 SetAIFlag('NEEDFIRE', '1');
5825 SetAIFlag('NEEDSEEUP', '1');
5828 // Ищем цель снизу и стреляем, если есть:
5829 if FDirection
= TDirection
.D_LEFT
then
5830 angle
:= ANGLE_LEFTDOWN
5832 angle
:= ANGLE_RIGHTDOWN
;
5834 firew
:= Trunc(Cos(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
5835 fireh
:= Trunc(Sin(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
5837 if g_CollideLine(x1
, y1
, x1
+firew
, y1
+fireh
,
5838 targets
[a
].X
+targets
[a
].Rect
.X
+GetInterval(FDifficult
.DiagPrecision
, 128),
5839 targets
[a
].Y
+targets
[a
].Rect
.Y
+GetInterval(FDifficult
.DiagPrecision
, 128),
5840 targets
[a
].Rect
.Width
, targets
[a
].Rect
.Height
) and
5841 g_TraceVector(x1
, y1
, targets
[a
].cX
, targets
[a
].cY
) then
5843 SetAIFlag('NEEDFIRE', '1');
5844 SetAIFlag('NEEDSEEDOWN', '1');
5848 // Если цель "перед носом", то стреляем:
5849 if targets
[a
].Line
and targets
[a
].Visible
and
5850 (((FDirection
= TDirection
.D_LEFT
) and (targets
[a
].X
< FObj
.X
)) or
5851 ((FDirection
= TDirection
.D_RIGHT
) and (targets
[a
].X
> FObj
.X
))) then
5853 SetAIFlag('NEEDFIRE', '1');
5858 // Если летит пуля, то, возможно, подпрыгиваем:
5859 if g_Weapon_Danger(FUID
, FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+PLAYER_RECT
.Y
,
5860 PLAYER_RECT
.Width
, PLAYER_RECT
.Height
,
5861 40+GetInterval(FDifficult
.Cover
, 40)) then
5862 SetAIFlag('NEEDJUMP', '1');
5864 // Если кончились паторны, то нужно сменить оружие:
5865 ammo
:= GetAmmoByWeapon(FCurrWeap
);
5866 if ((FCurrWeap
= WEAPON_SHOTGUN2
) and (ammo
< 2)) or
5867 ((FCurrWeap
= WEAPON_BFG
) and (ammo
< 40)) or
5869 SetAIFlag('SELECTWEAPON', '1');
5871 // Если нужно сменить оружие, то выбираем нужное:
5872 if GetAIFlag('SELECTWEAPON') = '1' then
5875 RemoveAIFlag('SELECTWEAPON');
5879 procedure TBot
.Update();
5892 // Проверяем, отключён ли AI ботов
5893 if (g_debug_BotAIOff
= 1) and (Team
= TEAM_RED
) then
5895 if (g_debug_BotAIOff
= 2) and (Team
= TEAM_BLUE
) then
5897 if g_debug_BotAIOff
= 3 then
5907 RealizeCurrentWeapon();
5914 procedure TBot
.ReleaseKey(Key
: Byte);
5923 function TBot
.KeyPressed(Key
: Word): Boolean;
5925 Result
:= FKeys
[Key
].Pressed
;
5928 function TBot
.GetAIFlag(aName
: String20
): String20
;
5934 aName
:= LowerCase(aName
);
5936 if FAIFlags
<> nil then
5937 for a
:= 0 to High(FAIFlags
) do
5938 if LowerCase(FAIFlags
[a
].Name
) = aName
then
5940 Result
:= FAIFlags
[a
].Value
;
5945 procedure TBot
.RemoveAIFlag(aName
: String20
);
5949 if FAIFlags
= nil then Exit
;
5951 aName
:= LowerCase(aName
);
5953 for a
:= 0 to High(FAIFlags
) do
5954 if LowerCase(FAIFlags
[a
].Name
) = aName
then
5956 if a
<> High(FAIFlags
) then
5957 for b
:= a
to High(FAIFlags
)-1 do
5958 FAIFlags
[b
] := FAIFlags
[b
+1];
5960 SetLength(FAIFlags
, Length(FAIFlags
)-1);
5965 procedure TBot
.SetAIFlag(aName
, fValue
: String20
);
5973 aName
:= LowerCase(aName
);
5975 if FAIFlags
<> nil then
5976 for a
:= 0 to High(FAIFlags
) do
5977 if LowerCase(FAIFlags
[a
].Name
) = aName
then
5983 if ok
then FAIFlags
[a
].Value
:= fValue
5986 SetLength(FAIFlags
, Length(FAIFlags
)+1);
5987 with FAIFlags
[High(FAIFlags
)] do
5995 procedure TBot
.UpdateMove
;
5997 procedure GoLeft(Time
: Word = 1);
5999 ReleaseKey(KEY_LEFT
);
6000 ReleaseKey(KEY_RIGHT
);
6001 PressKey(KEY_LEFT
, Time
);
6002 SetDirection(TDirection
.D_LEFT
);
6005 procedure GoRight(Time
: Word = 1);
6007 ReleaseKey(KEY_LEFT
);
6008 ReleaseKey(KEY_RIGHT
);
6009 PressKey(KEY_RIGHT
, Time
);
6010 SetDirection(TDirection
.D_RIGHT
);
6013 function Rnd(a
: Word): Boolean;
6015 Result
:= Random(a
) = 0;
6018 procedure Turn(Time
: Word = 1200);
6020 if RunDirection() = TDirection
.D_LEFT
then GoRight(Time
) else GoLeft(Time
);
6025 ReleaseKey(KEY_LEFT
);
6026 ReleaseKey(KEY_RIGHT
);
6029 function CanRunLeft(): Boolean;
6031 Result
:= not CollideLevel(-1, 0);
6034 function CanRunRight(): Boolean;
6036 Result
:= not CollideLevel(1, 0);
6039 function CanRun(): Boolean;
6041 if RunDirection() = TDirection
.D_LEFT
then Result
:= CanRunLeft() else Result
:= CanRunRight();
6044 procedure Jump(Time
: Word = 30);
6046 PressKey(KEY_JUMP
, Time
);
6049 function NearHole(): Boolean;
6053 { TODO 5 : Лестницы }
6054 sx
:= IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1);
6055 for x
:= 1 to PLAYER_RECT
.Width
do
6056 if (not StayOnStep(x
*sx
, 0)) and
6057 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
)) and
6058 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
*2)) then
6067 function BorderHole(): Boolean;
6071 { TODO 5 : Лестницы }
6072 sx
:= IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1);
6073 for x
:= 1 to PLAYER_RECT
.Width
do
6074 if (not StayOnStep(x
*sx
, 0)) and
6075 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
)) and
6076 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
*2)) then
6078 for xx
:= x
to x
+32 do
6079 if CollideLevel(xx
*sx
, PLAYER_RECT
.Height
) then
6089 function NearDeepHole(): Boolean;
6095 sx
:= IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1);
6098 for x
:= 1 to PLAYER_RECT
.Width
do
6099 if (not StayOnStep(x
*sx
, 0)) and
6100 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
)) and
6101 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
*2)) then
6103 while FObj
.Y
+y
*PLAYER_RECT
.Height
< gMapInfo
.Height
do
6105 if CollideLevel(x
*sx
, PLAYER_RECT
.Height
*y
) then Exit
;
6110 end else Result
:= False;
6113 function OverDeepHole(): Boolean;
6120 while FObj
.Y
+y
*PLAYER_RECT
.Height
< gMapInfo
.Height
do
6122 if CollideLevel(0, PLAYER_RECT
.Height
*y
) then Exit
;
6129 function OnGround(): Boolean;
6131 Result
:= StayOnStep(0, 0) or CollideLevel(0, 1);
6134 function OnLadder(): Boolean;
6136 Result
:= FullInStep(0, 0);
6139 function BelowLadder(): Boolean;
6141 Result
:= (FullInStep(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -PLAYER_RECT
.Height
) and
6142 not CollideLevel(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -PLAYER_RECT
.Height
)) or
6143 (FullInStep(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -BOT_MAXJUMP
) and
6144 not CollideLevel(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -BOT_MAXJUMP
));
6147 function BelowLiftUp(): Boolean;
6149 Result
:= ((FullInLift(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -PLAYER_RECT
.Height
) = -1) and
6150 not CollideLevel(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -PLAYER_RECT
.Height
)) or
6151 ((FullInLift(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -BOT_MAXJUMP
) = -1) and
6152 not CollideLevel(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -BOT_MAXJUMP
));
6155 function OnTopLift(): Boolean;
6157 Result
:= (FullInLift(0, 0) = -1) and (FullInLift(0, -32) = 0);
6160 function CanJumpOver(): Boolean;
6164 sx
:= IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1);
6168 if not CollideLevel(sx
, 0) then Exit
;
6170 for y
:= 1 to BOT_MAXJUMP
do
6171 if CollideLevel(0, -y
) then Exit
else
6172 if not CollideLevel(sx
, -y
) then
6179 function CanJumpUp(Dist
: ShortInt): Boolean;
6186 if CollideLevel(Dist
, 0) then Exit
;
6189 for y
:= 0 to BOT_MAXJUMP
do
6190 if CollideLevel(Dist
, -y
) then
6199 for yy
:= y
+1 to BOT_MAXJUMP
do
6200 if not CollideLevel(Dist
, -yy
) then
6209 for y
:= 0 to BOT_MAXJUMP
do
6210 if CollideLevel(0, -y
) then
6218 if y
< yy
then Exit
;
6223 function IsSafeTrigger(): Boolean;
6228 if gTriggers
= nil then
6230 for a
:= 0 to High(gTriggers
) do
6231 if Collide(gTriggers
[a
].X
,
6234 gTriggers
[a
].Height
) and
6235 (gTriggers
[a
].TriggerType
in [TRIGGER_EXIT
, TRIGGER_CLOSEDOOR
,
6236 TRIGGER_CLOSETRAP
, TRIGGER_TRAP
,
6237 TRIGGER_PRESS
, TRIGGER_ON
, TRIGGER_OFF
,
6238 TRIGGER_ONOFF
, TRIGGER_SPAWNMONSTER
,
6239 TRIGGER_DAMAGE
, TRIGGER_SHOT
]) then
6244 // Возможно, нажимаем кнопку:
6245 if Rnd(16) and IsSafeTrigger() then
6248 // Если под лифтом или ступеньками, то, возможно, прыгаем:
6249 if OnLadder() or ((BelowLadder() or BelowLiftUp()) and Rnd(8)) then
6251 ReleaseKey(KEY_LEFT
);
6252 ReleaseKey(KEY_RIGHT
);
6256 // Идем влево, если надо было:
6257 if GetAIFlag('GOLEFT') <> '' then
6259 RemoveAIFlag('GOLEFT');
6260 if CanRunLeft() then
6264 // Идем вправо, если надо было:
6265 if GetAIFlag('GORIGHT') <> '' then
6267 RemoveAIFlag('GORIGHT');
6268 if CanRunRight() then
6272 // Если вылетели за карту, то пробуем вернуться:
6273 if FObj
.X
< -32 then
6276 if FObj
.X
+32 > gMapInfo
.Width
then
6279 // Прыгаем, если надо было:
6280 if GetAIFlag('NEEDJUMP') <> '' then
6283 RemoveAIFlag('NEEDJUMP');
6286 // Смотрим вверх, если надо было:
6287 if GetAIFlag('NEEDSEEUP') <> '' then
6290 ReleaseKey(KEY_DOWN
);
6291 PressKey(KEY_UP
, 20);
6292 RemoveAIFlag('NEEDSEEUP');
6295 // Смотрим вниз, если надо было:
6296 if GetAIFlag('NEEDSEEDOWN') <> '' then
6299 ReleaseKey(KEY_DOWN
);
6300 PressKey(KEY_DOWN
, 20);
6301 RemoveAIFlag('NEEDSEEDOWN');
6304 // Если нужно было в дыру и мы не на земле, то покорно летим:
6305 if GetAIFlag('GOINHOLE') <> '' then
6306 if not OnGround() then
6308 ReleaseKey(KEY_LEFT
);
6309 ReleaseKey(KEY_RIGHT
);
6310 RemoveAIFlag('GOINHOLE');
6311 SetAIFlag('FALLINHOLE', '1');
6314 // Если падали и достигли земли, то хватит падать:
6315 if GetAIFlag('FALLINHOLE') <> '' then
6317 RemoveAIFlag('FALLINHOLE');
6319 // Если летели прямо и сейчас не на лестнице или на вершине лифта, то отходим в сторону:
6320 if not (KeyPressed(KEY_LEFT
) or KeyPressed(KEY_RIGHT
)) then
6321 if GetAIFlag('FALLINHOLE') = '' then
6322 if (not OnLadder()) or (FObj
.Vel
.Y
>= 0) or (OnTopLift()) then
6328 // Если на земле и можно подпрыгнуть, то, возможно, прыгаем:
6330 CanJumpUp(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*32) and
6334 // Если на земле и возле дыры (глубина > 2 ростов игрока):
6335 if OnGround() and NearHole() then
6336 if NearDeepHole() then // Если это бездна
6338 0..3: Turn(); // Бежим обратно
6339 4: Jump(); // Прыгаем
6340 5: begin // Прыгаем обратно
6345 else // Это не бездна и мы еще не летим туда
6346 if GetAIFlag('GOINHOLE') = '' then
6348 0: Turn(); // Не нужно туда
6349 1: Jump(); // Вдруг повезет - прыгаем
6350 else // Если яма с границей, то при случае можно туда прыгнуть
6351 if BorderHole() then
6352 SetAIFlag('GOINHOLE', '1');
6355 // Если на земле, но некуда идти:
6356 if (not CanRun()) and OnGround() then
6358 // Если мы на лестнице или можно перепрыгнуть, то прыгаем:
6359 if CanJumpOver() or OnLadder() then
6361 else // иначе попытаемся в другую сторону
6362 if Random(2) = 0 then
6364 if IsSafeTrigger() then
6370 // Осталось мало воздуха:
6371 if FAir
< 36 * 2 then
6374 // Выбираемся из кислоты, если нет костюма, обожглись, или мало здоровья:
6375 if (FMegaRulez
[MR_SUIT
] < gTime
) and ((FLastHit
= HIT_ACID
) or (Healthy() <= 1)) then
6376 if BodyInAcid(0, 0) then
6380 function TBot
.FullInStep(XInc
, YInc
: Integer): Boolean;
6382 Result
:= g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
,
6383 PLAYER_RECT
.Width
, PLAYER_RECT
.Height
, PANEL_STEP
, False);
6386 {function TBot.NeedItem(Item: Byte): Byte;
6391 procedure TBot
.SelectWeapon(Dist
: Integer);
6395 function HaveAmmo(weapon
: Byte): Boolean;
6398 WEAPON_PISTOL
: Result
:= FAmmo
[A_BULLETS
] >= 1;
6399 WEAPON_SHOTGUN1
: Result
:= FAmmo
[A_SHELLS
] >= 1;
6400 WEAPON_SHOTGUN2
: Result
:= FAmmo
[A_SHELLS
] >= 2;
6401 WEAPON_CHAINGUN
: Result
:= FAmmo
[A_BULLETS
] >= 10;
6402 WEAPON_ROCKETLAUNCHER
: Result
:= FAmmo
[A_ROCKETS
] >= 1;
6403 WEAPON_PLASMA
: Result
:= FAmmo
[A_CELLS
] >= 10;
6404 WEAPON_BFG
: Result
:= FAmmo
[A_CELLS
] >= 40;
6405 WEAPON_SUPERPULEMET
: Result
:= FAmmo
[A_SHELLS
] >= 1;
6406 WEAPON_FLAMETHROWER
: Result
:= FAmmo
[A_FUEL
] >= 1;
6407 else Result
:= True;
6412 if Dist
= -1 then Dist
:= BOT_LONGDIST
;
6414 if Dist
> BOT_LONGDIST
then
6415 begin // Дальний бой
6417 if FWeapon
[FDifficult
.WeaponPrior
[a
]] and HaveAmmo(FDifficult
.WeaponPrior
[a
]) then
6419 FSelectedWeapon
:= FDifficult
.WeaponPrior
[a
];
6423 else //if Dist > BOT_UNSAFEDIST then
6424 begin // Ближний бой
6426 if FWeapon
[FDifficult
.CloseWeaponPrior
[a
]] and HaveAmmo(FDifficult
.CloseWeaponPrior
[a
]) then
6428 FSelectedWeapon
:= FDifficult
.CloseWeaponPrior
[a
];
6435 if FWeapon[FDifficult.SafeWeaponPrior[a]] and HaveAmmo(FDifficult.SafeWeaponPrior[a]) then
6437 FSelectedWeapon := FDifficult.SafeWeaponPrior[a];
6443 function TBot
.PickItem(ItemType
: Byte; force
: Boolean; var remove
: Boolean): Boolean;
6445 Result
:= inherited PickItem(ItemType
, force
, remove
);
6447 if Result
then SetAIFlag('SELECTWEAPON', '1');
6450 function TBot
.Heal(value
: Word; Soft
: Boolean): Boolean;
6452 Result
:= inherited Heal(value
, Soft
);
6455 function TBot
.Healthy(): Byte;
6457 if FMegaRulez
[MR_INVUL
] >= gTime
then Result
:= 3
6458 else if (FHealth
> 80) or ((FHealth
> 50) and (FArmor
> 20)) then Result
:= 3
6459 else if (FHealth
> 50) then Result
:= 2
6460 else if (FHealth
> 20) then Result
:= 1
6464 function TBot
.TargetOnScreen(TX
, TY
: Integer): Boolean;
6466 Result
:= (Abs(FObj
.X
-TX
) <= Trunc(gPlayerScreenSize
.X
*0.6)) and
6467 (Abs(FObj
.Y
-TY
) <= Trunc(gPlayerScreenSize
.Y
*0.6));
6470 procedure TBot
.OnDamage(Angle
: SmallInt);
6478 if (Angle
= 0) or (Angle
= 180) then
6481 if (g_GetUIDType(FLastSpawnerUID
) = UID_PLAYER
) and
6482 LongBool(gGameSettings
.Options
and GAME_OPTION_BOTVSPLAYER
) then
6484 pla
:= g_Player_Get(FLastSpawnerUID
);
6485 ok
:= not TargetOnScreen(pla
.FObj
.X
+ PLAYER_RECT
.X
,
6486 pla
.FObj
.Y
+ PLAYER_RECT
.Y
);
6489 if (g_GetUIDType(FLastSpawnerUID
) = UID_MONSTER
) and
6490 LongBool(gGameSettings
.Options
and GAME_OPTION_BOTVSMONSTER
) then
6492 mon
:= g_Monsters_ByUID(FLastSpawnerUID
);
6493 ok
:= not TargetOnScreen(mon
.Obj
.X
+ mon
.Obj
.Rect
.X
,
6494 mon
.Obj
.Y
+ mon
.Obj
.Rect
.Y
);
6499 SetAIFlag('ATTACKLEFT', '1')
6501 SetAIFlag('ATTACKRIGHT', '1');
6505 function TBot
.RunDirection(): TDirection
;
6507 if Abs(Vel
.X
) >= 1 then
6509 if Vel
.X
> 0 then Result
:= TDirection
.D_RIGHT
else Result
:= TDirection
.D_LEFT
;
6511 Result
:= FDirection
;
6514 function TBot
.GetRnd(a
: Byte): Boolean;
6516 if a
= 0 then Result
:= False
6517 else if a
= 255 then Result
:= True
6518 else Result
:= Random(256) > 255-a
;
6521 function TBot
.GetInterval(a
: Byte; radius
: SmallInt): SmallInt;
6523 Result
:= Round((255-a
)/255*radius
*(Random(2)-1));
6527 procedure TDifficult
.save (st
: TStream
);
6529 utils
.writeInt(st
, Byte(DiagFire
));
6530 utils
.writeInt(st
, Byte(InvisFire
));
6531 utils
.writeInt(st
, Byte(DiagPrecision
));
6532 utils
.writeInt(st
, Byte(FlyPrecision
));
6533 utils
.writeInt(st
, Byte(Cover
));
6534 utils
.writeInt(st
, Byte(CloseJump
));
6535 st
.WriteBuffer(WeaponPrior
[Low(WeaponPrior
)], sizeof(WeaponPrior
));
6536 st
.WriteBuffer(CloseWeaponPrior
[Low(CloseWeaponPrior
)], sizeof(CloseWeaponPrior
));
6539 procedure TDifficult
.load (st
: TStream
);
6541 DiagFire
:= utils
.readByte(st
);
6542 InvisFire
:= utils
.readByte(st
);
6543 DiagPrecision
:= utils
.readByte(st
);
6544 FlyPrecision
:= utils
.readByte(st
);
6545 Cover
:= utils
.readByte(st
);
6546 CloseJump
:= utils
.readByte(st
);
6547 st
.ReadBuffer(WeaponPrior
[Low(WeaponPrior
)], sizeof(WeaponPrior
));
6548 st
.ReadBuffer(CloseWeaponPrior
[Low(CloseWeaponPrior
)], sizeof(CloseWeaponPrior
));
6552 procedure TBot
.SaveState (st
: TStream
);
6557 inherited SaveState(st
);
6558 utils
.writeSign(st
, 'BOT0');
6560 utils
.writeInt(st
, Byte(FSelectedWeapon
));
6562 utils
.writeInt(st
, Word(FTargetUID
));
6563 // Время потери цели
6564 utils
.writeInt(st
, LongWord(FLastVisible
));
6565 // Количество флагов ИИ
6566 dw
:= Length(FAIFlags
);
6567 utils
.writeInt(st
, LongInt(dw
));
6569 for i
:= 0 to dw
-1 do
6571 utils
.writeStr(st
, FAIFlags
[i
].Name
, 20);
6572 utils
.writeStr(st
, FAIFlags
[i
].Value
, 20);
6574 // Настройки сложности
6575 FDifficult
.save(st
);
6579 procedure TBot
.LoadState (st
: TStream
);
6584 inherited LoadState(st
);
6585 if not utils
.checkSign(st
, 'BOT0') then raise XStreamError
.Create('invalid bot signature');
6587 FSelectedWeapon
:= utils
.readByte(st
);
6589 FTargetUID
:= utils
.readWord(st
);
6590 // Время потери цели
6591 FLastVisible
:= utils
.readLongWord(st
);
6592 // Количество флагов ИИ
6593 dw
:= utils
.readLongInt(st
);
6594 if (dw
< 0) or (dw
> 16384) then raise XStreamError
.Create('invalid number of bot AI flags');
6595 SetLength(FAIFlags
, dw
);
6597 for i
:= 0 to dw
-1 do
6599 FAIFlags
[i
].Name
:= utils
.readStr(st
, 20);
6600 FAIFlags
[i
].Value
:= utils
.readStr(st
, 20);
6602 // Настройки сложности
6603 FDifficult
.load(st
);
6608 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');
6609 conRegVar('player_indicator_style', @gPlayerIndicatorStyle
, 'Visual appearance of indicator', 'Visual appearance of indicator');