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
,
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 FPunchTime
: LongWord;
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 PunchTime
: LongWord read FPunchTime
;
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;
1652 procedure TPlayer
.positionChanged (); inline;
1656 procedure TPlayer
.doDamage (v
: Integer);
1658 if (v
<= 0) then exit
;
1659 if (v
> 32767) then v
:= 32767;
1660 Damage(v
, 0, 0, 0, mEDamageType
);
1663 procedure TPlayer
.Damage(value
: Word; SpawnerUID
: Word; vx
, vy
: Integer; t
: Byte);
1667 if (not g_Game_IsClient
) and (not FAlive
) then
1672 // Неуязвимость не спасает от ловушек:
1673 if ((t
= HIT_TRAP
) or (t
= HIT_SELF
)) and (not FGodMode
) then
1675 if not g_Game_IsClient
then
1678 if t
= HIT_TRAP
then
1680 // Ловушка убивает сразу:
1682 Kill(K_EXTRAHARDKILL
, SpawnerUID
, t
);
1684 if t
= HIT_SELF
then
1688 Kill(K_SIMPLEKILL
, SpawnerUID
, t
);
1691 // Обнулить действия примочек, чтобы фон пропал
1692 FMegaRulez
[MR_SUIT
] := 0;
1693 FMegaRulez
[MR_INVUL
] := 0;
1694 FMegaRulez
[MR_INVIS
] := 0;
1699 // Но от остального спасает:
1700 if FMegaRulez
[MR_INVUL
] >= gTime
then
1707 // Если есть урон своим, или ранил сам себя, или тебя ранил противник:
1708 if LongBool(gGameSettings
.Options
and GAME_OPTION_TEAMDAMAGE
) or
1709 (SpawnerUID
= FUID
) or
1710 (not SameTeam(FUID
, SpawnerUID
)) then
1712 FLastSpawnerUID
:= SpawnerUID
;
1714 // Кровь (пузырьки, если в воде):
1715 if gBloodCount
> 0 then
1717 c
:= Min(value
, 200)*gBloodCount
+ Random(Min(value
, 200) div 2);
1718 if value
div 4 <= c
then
1719 c
:= c
- (value
div 4)
1723 if (t
= HIT_SOME
) and (vx
= 0) and (vy
= 0) then
1727 HIT_TRAP
, HIT_ACID
, HIT_FLAME
, HIT_SELF
: MakeBloodSimple(c
);
1728 HIT_BFG
, HIT_ROCKET
, HIT_SOME
: MakeBloodVector(c
, vx
, vy
);
1732 if t
= HIT_WATER
then
1734 g_GFX_Bubbles(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2),
1735 FObj
.Y
+PLAYER_RECT
.Y
-4, value
div 2, 8, 4);
1737 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj
.X
, FObj
.Y
)
1738 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj
.X
, FObj
.Y
);
1745 Inc(FDamageBuffer
, value
);
1749 FPain
:= FPain
+ value
;
1752 if g_Game_IsServer
and g_Game_IsNet
then
1754 MH_SEND_PlayerDamage(FUID
, t
, SpawnerUID
, value
, vx
, vy
);
1755 MH_SEND_PlayerStats(FUID
);
1756 MH_SEND_PlayerPos(False, FUID
);
1760 function TPlayer
.Heal(value
: Word; Soft
: Boolean): Boolean;
1763 if g_Game_IsClient
then
1768 if Soft
and (FHealth
< PLAYER_HP_SOFT
) then
1770 IncMax(FHealth
, value
, PLAYER_HP_SOFT
);
1773 if (not Soft
) and (FHealth
< PLAYER_HP_LIMIT
) then
1775 IncMax(FHealth
, value
, PLAYER_HP_LIMIT
);
1779 if Result
and g_Game_IsServer
and g_Game_IsNet
then
1780 MH_SEND_PlayerStats(FUID
);
1783 destructor TPlayer
.Destroy();
1785 if (gPlayer1
<> nil) and (gPlayer1
.FUID
= FUID
) then
1787 if (gPlayer2
<> nil) and (gPlayer2
.FUID
= FUID
) then
1791 FSawSoundIdle
.Free();
1792 FSawSoundHit
.Free();
1793 FSawSoundSelect
.Free();
1794 FFlameSoundOn
.Free();
1795 FFlameSoundOff
.Free();
1796 FFlameSoundWork
.Free();
1797 FJetSoundFly
.Free();
1799 FJetSoundOff
.Free();
1806 procedure TPlayer
.DoPunch();
1808 FPunchTime
:= gTime
;
1811 procedure TPlayer
.Fire();
1813 f
, DidFire
: Boolean;
1814 wx
, wy
, xd
, yd
: Integer;
1817 if g_Game_IsClient
then Exit
;
1818 // FBFGFireCounter - время перед выстрелом (для BFG)
1819 // FReloading - время после выстрела (для всего)
1827 if FReloading
[FCurrWeap
] <> 0 then Exit
;
1832 wx
:= FObj
.X
+WEAPONPOINT
[FDirection
].X
;
1833 wy
:= FObj
.Y
+WEAPONPOINT
[FDirection
].Y
;
1834 xd
:= wx
+IfThen(FDirection
= TDirection
.D_LEFT
, -30, 30);
1835 yd
:= wy
+firediry();
1841 if R_BERSERK
in FRulez
then
1843 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
1844 locobj
.X
:= FObj
.X
+FObj
.Rect
.X
;
1845 locobj
.Y
:= FObj
.Y
+FObj
.Rect
.Y
;
1848 locobj
.rect
.Width
:= 39;
1849 locobj
.rect
.Height
:= 52;
1850 locobj
.Vel
.X
:= (xd
-wx
) div 2;
1851 locobj
.Vel
.Y
:= (yd
-wy
) div 2;
1852 locobj
.Accel
.X
:= xd
-wx
;
1853 locobj
.Accel
.y
:= yd
-wy
;
1855 if g_Weapon_Hit(@locobj
, 50, FUID
, HIT_SOME
) <> 0 then
1856 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj
.X
, FObj
.Y
)
1858 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj
.X
, FObj
.Y
);
1860 if (gFlash
= 1) and (FPain
< 50) then FPain
:= min(FPain
+ 25, 50);
1864 g_Weapon_punch(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
, 3, FUID
);
1868 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
1873 if g_Weapon_chainsaw(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
,
1874 IfThen(gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
], 9, 3), FUID
) <> 0 then
1876 FSawSoundSelect
.Stop();
1878 FSawSoundHit
.PlayAt(FObj
.X
, FObj
.Y
);
1880 else if not FSawSoundHit
.IsPlaying() then
1882 FSawSoundSelect
.Stop();
1883 FSawSound
.PlayAt(FObj
.X
, FObj
.Y
);
1886 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
1892 if FAmmo
[A_BULLETS
] > 0 then
1894 g_Weapon_pistol(wx
, wy
, xd
, yd
, FUID
);
1895 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
1896 Dec(FAmmo
[A_BULLETS
]);
1897 FFireAngle
:= FAngle
;
1900 {$IFDEF ENABLE_SHELLS}
1901 g_Shells_Create(GameX
+ PLAYER_RECT_CX
, GameY
+ PLAYER_RECT_CX
, GameVelX
, GameVelY
- 2, SHELL_BULLET
);
1906 if FAmmo
[A_SHELLS
] > 0 then
1908 g_Weapon_shotgun(wx
, wy
, xd
, yd
, FUID
);
1909 if not gSoundEffectsDF
then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', wx
, wy
);
1910 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
1911 Dec(FAmmo
[A_SHELLS
]);
1912 FFireAngle
:= FAngle
;
1915 {$IFDEF ENABLE_SHELLS}
1917 FShellType
:= SHELL_SHELL
;
1922 if FAmmo
[A_SHELLS
] >= 2 then
1924 g_Weapon_dshotgun(wx
, wy
, xd
, yd
, FUID
);
1925 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
1926 Dec(FAmmo
[A_SHELLS
], 2);
1927 FFireAngle
:= FAngle
;
1930 {$IFDEF ENABLE_SHELLS}
1932 FShellType
:= SHELL_DBLSHELL
;
1937 if FAmmo
[A_BULLETS
] > 0 then
1939 g_Weapon_mgun(wx
, wy
, xd
, yd
, FUID
);
1940 if not gSoundEffectsDF
then g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', wx
, wy
);
1941 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
1942 Dec(FAmmo
[A_BULLETS
]);
1943 FFireAngle
:= FAngle
;
1946 {$IFDEF ENABLE_SHELLS}
1947 g_Shells_Create(GameX
+ PLAYER_RECT_CX
, GameY
+ PLAYER_RECT_CX
, GameVelX
, GameVelY
- 2, SHELL_BULLET
);
1951 WEAPON_ROCKETLAUNCHER
:
1952 if FAmmo
[A_ROCKETS
] > 0 then
1954 g_Weapon_rocket(wx
, wy
, xd
, yd
, FUID
);
1955 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
1956 Dec(FAmmo
[A_ROCKETS
]);
1957 FFireAngle
:= FAngle
;
1963 if FAmmo
[A_CELLS
] > 0 then
1965 g_Weapon_plasma(wx
, wy
, xd
, yd
, FUID
);
1966 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
1967 Dec(FAmmo
[A_CELLS
]);
1968 FFireAngle
:= FAngle
;
1974 if (FAmmo
[A_CELLS
] >= 40) and (FBFGFireCounter
= -1) then
1976 FBFGFireCounter
:= 17;
1977 if not FNoReload
then
1978 g_Sound_PlayExAt('SOUND_WEAPON_STARTFIREBFG', FObj
.X
, FObj
.Y
);
1979 Dec(FAmmo
[A_CELLS
], 40);
1983 WEAPON_SUPERPULEMET
:
1984 if FAmmo
[A_SHELLS
] > 0 then
1986 g_Weapon_shotgun(wx
, wy
, xd
, yd
, FUID
);
1987 if not gSoundEffectsDF
then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx
, wy
);
1988 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
1989 Dec(FAmmo
[A_SHELLS
]);
1990 FFireAngle
:= FAngle
;
1993 {$IFDEF ENABLE_SHELLS}
1994 g_Shells_Create(GameX
+ PLAYER_RECT_CX
, GameY
+ PLAYER_RECT_CX
, GameVelX
, GameVelY
- 2, SHELL_SHELL
);
1998 WEAPON_FLAMETHROWER
:
1999 if FAmmo
[A_FUEL
] > 0 then
2001 g_Weapon_flame(wx
, wy
, xd
, yd
, FUID
);
2003 FReloading
[FCurrWeap
] := WEAPON_RELOAD
[FCurrWeap
];
2005 FFireAngle
:= FAngle
;
2012 if g_Game_IsNet
and g_Game_IsServer
then MH_SEND_PlayerStats(FUID
);
2016 if g_Game_IsNet
then
2020 if FCurrWeap
<> WEAPON_BFG
then
2021 MH_SEND_PlayerFire(FUID
, FCurrWeap
, wx
, wy
, xd
, yd
, LastShotID
)
2023 if not FNoReload
then
2024 MH_SEND_Sound(FObj
.X
, FObj
.Y
, 'SOUND_WEAPON_STARTFIREBFG');
2027 MH_SEND_PlayerStats(FUID
);
2032 if (FAngle
= 0) or (FAngle
= 180) then SetAction(A_ATTACK
)
2033 else if (FAngle
= ANGLE_LEFTDOWN
) or (FAngle
= ANGLE_RIGHTDOWN
) then SetAction(A_ATTACKDOWN
)
2034 else if (FAngle
= ANGLE_LEFTUP
) or (FAngle
= ANGLE_RIGHTUP
) then SetAction(A_ATTACKUP
);
2037 function TPlayer
.GetAmmoByWeapon(Weapon
: Byte): Word;
2040 WEAPON_PISTOL
, WEAPON_CHAINGUN
: Result
:= FAmmo
[A_BULLETS
];
2041 WEAPON_SHOTGUN1
, WEAPON_SHOTGUN2
, WEAPON_SUPERPULEMET
: Result
:= FAmmo
[A_SHELLS
];
2042 WEAPON_ROCKETLAUNCHER
: Result
:= FAmmo
[A_ROCKETS
];
2043 WEAPON_PLASMA
, WEAPON_BFG
: Result
:= FAmmo
[A_CELLS
];
2044 WEAPON_FLAMETHROWER
: Result
:= FAmmo
[A_FUEL
];
2049 function TPlayer
.HeadInLiquid(XInc
, YInc
: Integer): Boolean;
2051 Result
:= g_Map_CollidePanel(FObj
.X
+PLAYER_HEADRECT
.X
+XInc
, FObj
.Y
+PLAYER_HEADRECT
.Y
+YInc
,
2052 PLAYER_HEADRECT
.Width
, PLAYER_HEADRECT
.Height
,
2053 PANEL_WATER
or PANEL_ACID1
or PANEL_ACID2
, True);
2056 procedure TPlayer
.FlamerOn
;
2058 FFlameSoundOff
.Stop();
2059 FFlameSoundOff
.SetPosition(0);
2062 if (not FFlameSoundOn
.IsPlaying()) and (not FFlameSoundWork
.IsPlaying()) then
2063 FFlameSoundWork
.PlayAt(FObj
.X
, FObj
.Y
);
2067 FFlameSoundOn
.PlayAt(FObj
.X
, FObj
.Y
);
2072 procedure TPlayer
.FlamerOff
;
2076 FFlameSoundOn
.Stop();
2077 FFlameSoundOn
.SetPosition(0);
2078 FFlameSoundWork
.Stop();
2079 FFlameSoundWork
.SetPosition(0);
2080 FFlameSoundOff
.PlayAt(FObj
.X
, FObj
.Y
);
2085 procedure TPlayer
.JetpackOn
;
2089 FJetSoundOn
.SetPosition(0);
2090 FJetSoundOn
.PlayAt(FObj
.X
, FObj
.Y
);
2094 procedure TPlayer
.JetpackOff
;
2098 FJetSoundOff
.SetPosition(0);
2099 FJetSoundOff
.PlayAt(FObj
.X
, FObj
.Y
);
2102 procedure TPlayer
.CatchFire(Attacker
: Word; Timeout
: Integer = PLAYER_BURN_TIME
);
2104 if Timeout
<= 0 then
2106 if (FMegaRulez
[MR_SUIT
] > gTime
) or (FMegaRulez
[MR_INVUL
] > gTime
) then
2107 exit
; // Не загораемся когда есть защита
2108 if g_Obj_CollidePanel(@FObj
, 0, 0, PANEL_WATER
or PANEL_ACID1
or PANEL_ACID2
) then
2109 exit
; // Не подгораем в воде на всякий случай
2110 if FFireTime
<= 0 then
2111 g_Sound_PlayExAt('SOUND_IGNITE', FObj
.X
, FObj
.Y
);
2112 FFireTime
:= Timeout
;
2113 FFireAttacker
:= Attacker
;
2114 if g_Game_IsNet
and g_Game_IsServer
then
2115 MH_SEND_PlayerStats(FUID
);
2118 procedure TPlayer
.Jump();
2120 if gFly
or FJetpack
then
2122 // Полет (чит-код или джетпак):
2123 if FObj
.Vel
.Y
> -VEL_FLY
then
2124 FObj
.Vel
.Y
:= FObj
.Vel
.Y
- 3;
2127 if FJetFuel
> 0 then
2129 if (FJetFuel
< 1) and g_Game_IsServer
then
2133 if g_Game_IsNet
then
2134 MH_SEND_PlayerStats(FUID
);
2140 // Не включать джетпак в режиме прохождения сквозь стены
2142 FCanJetpack
:= False;
2144 // Прыгаем или всплываем:
2145 if (CollideLevel(0, 1) or
2146 g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+PLAYER_RECT
.Y
+36, PLAYER_RECT
.Width
,
2147 PLAYER_RECT
.Height
-33, PANEL_STEP
, False)
2148 ) and (FObj
.Accel
.Y
= 0) then // Не прыгать, если есть вертикальное ускорение
2150 FObj
.Vel
.Y
:= -VEL_JUMP
;
2151 FCanJetpack
:= False;
2155 if BodyInLiquid(0, 0) then
2156 FObj
.Vel
.Y
:= -VEL_SW
2157 else if (FJetFuel
> 0) and FCanJetpack
and
2158 g_Game_IsServer
and (not g_Obj_CollideLiquid(@FObj
, 0, 0)) then
2162 if g_Game_IsNet
then
2163 MH_SEND_PlayerStats(FUID
);
2168 procedure TPlayer
.Kill(KillType
: Byte; SpawnerUID
: Word; t
: Byte);
2170 a
, i
, k
, ab
, ar
: Byte;
2174 srv
, netsrv
: Boolean;
2180 procedure PushItem(t
: Byte);
2184 id
:= g_Items_Create(FObj
.X
, FObj
.Y
, t
, True, False);
2185 it
:= g_Items_ByIdx(id
);
2186 if KillType
= K_EXTRAHARDKILL
then // -7..+7; -8..0
2188 g_Obj_Push(@it
.Obj
, (FObj
.Vel
.X
div 2)-7+Random(15),
2189 (FObj
.Vel
.Y
div 2)-Random(9));
2190 it
.positionChanged(); // this updates spatial accelerators
2194 if KillType
= K_HARDKILL
then // -5..+5; -5..0
2196 g_Obj_Push(@it
.Obj
, (FObj
.Vel
.X
div 2)-5+Random(11),
2197 (FObj
.Vel
.Y
div 2)-Random(6));
2199 else // -3..+3; -3..0
2201 g_Obj_Push(@it
.Obj
, (FObj
.Vel
.X
div 2)-3+Random(7),
2202 (FObj
.Vel
.Y
div 2)-Random(4));
2204 it
.positionChanged(); // this updates spatial accelerators
2207 if g_Game_IsNet
and g_Game_IsServer
then
2208 MH_SEND_ItemSpawn(True, id
);
2212 DoFrags
:= (gGameSettings
.MaxLives
= 0) or (gGameSettings
.GameMode
= GM_COOP
);
2213 Srv
:= g_Game_IsServer
;
2214 Netsrv
:= g_Game_IsServer
and g_Game_IsNet
;
2215 if Srv
then FDeath
:= FDeath
+ 1;
2220 if not FPhysics
then
2225 {$IFDEF ENABLE_SHELLS}
2229 if (gGameSettings
.MaxLives
> 0) and Srv
and (gLMSRespawn
= LMS_RESPAWN_NONE
) then
2231 if FLives
> 0 then FLives
:= FLives
- 1;
2232 if FLives
= 0 then FNoRespawn
:= True;
2235 // Номер типа смерти:
2238 K_SIMPLEKILL
: a
:= 1;
2240 K_EXTRAHARDKILL
: a
:= 3;
2245 if not FModel
.PlaySound(MODELSOUND_DIE
, a
, FObj
.X
, FObj
.Y
) then
2247 if FModel
.PlaySound(MODELSOUND_DIE
, i
, FObj
.X
, FObj
.Y
) then
2254 FTime
[T_RESPAWN
] := gTime
+ TIME_RESPAWN1
;
2256 FTime
[T_RESPAWN
] := gTime
+ TIME_RESPAWN2
;
2257 K_EXTRAHARDKILL
, K_FALLKILL
:
2258 FTime
[T_RESPAWN
] := gTime
+ TIME_RESPAWN3
;
2261 // Переключаем состояние:
2265 K_HARDKILL
, K_EXTRAHARDKILL
:
2269 // Реакция монстров на смерть игрока:
2270 if (KillType
<> K_FALLKILL
) and (Srv
) then
2271 g_Monsters_killedp();
2273 if SpawnerUID
= FUID
then
2277 if gGameSettings
.GameMode
= GM_TDM
then
2278 Dec(gTeamStat
[FTeam
].Score
);
2279 if DoFrags
or (gGameSettings
.GameMode
= GM_TDM
) then
2285 g_Console_Add(Format(_lc
[I_PLAYER_KILL_SELF
], [FName
]), True);
2288 if g_GetUIDType(SpawnerUID
) = UID_PLAYER
then
2289 begin // Убит другим игроком
2290 KP
:= g_Player_Get(SpawnerUID
);
2291 if (KP
<> nil) and Srv
then
2293 if (DoFrags
or (gGameSettings
.GameMode
= GM_TDM
)) then
2294 if SameTeam(FUID
, SpawnerUID
) then
2304 if (gGameSettings
.GameMode
= GM_TDM
) and DoFrags
then
2305 Inc(gTeamStat
[KP
.Team
].Score
,
2306 IfThen(SameTeam(FUID
, SpawnerUID
), -1, 1));
2308 if netsrv
then MH_SEND_PlayerStats(SpawnerUID
);
2311 plr
:= g_Player_Get(SpawnerUID
);
2319 g_Console_Add(Format(_lc
[I_PLAYER_KILL_EXTRAHARD_2
],
2323 g_Console_Add(Format(_lc
[I_PLAYER_KILL_EXTRAHARD_1
],
2327 g_Console_Add(Format(_lc
[I_PLAYER_KILL
],
2332 else if g_GetUIDType(SpawnerUID
) = UID_MONSTER
then
2333 begin // Убит монстром
2334 mon
:= g_Monsters_ByUID(SpawnerUID
);
2338 s
:= g_Mons_GetKilledByTypeId(mon
.MonsterType
);
2342 g_Console_Add(Format(_lc
[I_PLAYER_KILL_EXTRAHARD_2
],
2346 g_Console_Add(Format(_lc
[I_PLAYER_KILL_EXTRAHARD_1
],
2350 g_Console_Add(Format(_lc
[I_PLAYER_KILL
],
2355 else // Особые типы смерти
2358 HIT_SELF
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_SELF
], [FName
]), True);
2359 HIT_FALL
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_FALL
], [FName
]), True);
2360 HIT_WATER
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_WATER
], [FName
]), True);
2361 HIT_ACID
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_ACID
], [FName
]), True);
2362 HIT_TRAP
: g_Console_Add(Format(_lc
[I_PLAYER_KILL_TRAP
], [FName
]), True);
2363 else g_Console_Add(Format(_lc
[I_PLAYER_DIED
], [FName
]), True);
2369 for a
:= WP_FIRST
to WP_LAST
do
2373 WEAPON_SAW
: i
:= ITEM_WEAPON_SAW
;
2374 WEAPON_SHOTGUN1
: i
:= ITEM_WEAPON_SHOTGUN1
;
2375 WEAPON_SHOTGUN2
: i
:= ITEM_WEAPON_SHOTGUN2
;
2376 WEAPON_CHAINGUN
: i
:= ITEM_WEAPON_CHAINGUN
;
2377 WEAPON_ROCKETLAUNCHER
: i
:= ITEM_WEAPON_ROCKETLAUNCHER
;
2378 WEAPON_PLASMA
: i
:= ITEM_WEAPON_PLASMA
;
2379 WEAPON_BFG
: i
:= ITEM_WEAPON_BFG
;
2380 WEAPON_SUPERPULEMET
: i
:= ITEM_WEAPON_SUPERPULEMET
;
2381 WEAPON_FLAMETHROWER
: i
:= ITEM_WEAPON_FLAMETHROWER
;
2390 if R_ITEM_BACKPACK
in FRulez
then
2391 PushItem(ITEM_AMMO_BACKPACK
);
2393 // Выброс ракетного ранца:
2394 if FJetFuel
> 0 then
2395 PushItem(ITEM_JETPACK
);
2398 if (not (gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
])) or
2399 (not LongBool(gGameSettings
.Options
and GAME_OPTION_DMKEYS
)) then
2401 if R_KEY_RED
in FRulez
then
2402 PushItem(ITEM_KEY_RED
);
2404 if R_KEY_GREEN
in FRulez
then
2405 PushItem(ITEM_KEY_GREEN
);
2407 if R_KEY_BLUE
in FRulez
then
2408 PushItem(ITEM_KEY_BLUE
);
2412 DropFlag(KillType
= K_FALLKILL
);
2415 {$IFDEF ENABLE_CORPSES}
2416 FCorpse
:= g_Corpses_Create(Self
);
2419 if Srv
and (gGameSettings
.MaxLives
> 0) and FNoRespawn
and
2420 (gLMSRespawn
= LMS_RESPAWN_NONE
) then
2426 for i
:= Low(gPlayers
) to High(gPlayers
) do
2428 if gPlayers
[i
] = nil then continue
;
2429 if (not gPlayers
[i
].FNoRespawn
) and (not gPlayers
[i
].FSpectator
) then
2432 if gPlayers
[i
].FTeam
= TEAM_RED
then Inc(ar
)
2433 else if gPlayers
[i
].FTeam
= TEAM_BLUE
then Inc(ab
);
2438 OldLR
:= gLMSRespawn
;
2439 if (gGameSettings
.GameMode
= GM_COOP
) then
2443 // everyone is dead, restart the map
2444 g_Game_Message(_lc
[I_MESSAGE_LMS_LOSE
], 144);
2446 MH_SEND_GameEvent(NET_EV_LMS_LOSE
);
2447 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2448 gLMSRespawnTime
:= gTime
+ 5000;
2450 else if (a
= 1) then
2452 if (gPlayers
[k
] <> nil) and not (gPlayers
[k
] is TBot
) then
2453 if (gPlayers
[k
] = gPlayer1
) or
2454 (gPlayers
[k
] = gPlayer2
) then
2455 g_Console_Add('*** ' + _lc
[I_MESSAGE_LMS_SURVIVOR
] + ' ***', True)
2456 else if Netsrv
and (gPlayers
[k
].FClientID
>= 0) then
2457 MH_SEND_GameEvent(NET_EV_LMS_SURVIVOR
, 0, 'N', gPlayers
[k
].FClientID
);
2460 else if (gGameSettings
.GameMode
= GM_TDM
) then
2462 if (ab
= 0) and (ar
<> 0) then
2465 g_Game_Message(Format(_lc
[I_MESSAGE_TLMS_WIN
], [AnsiUpperCase(_lc
[I_GAME_TEAM_RED
])]), 144);
2467 MH_SEND_GameEvent(NET_EV_TLMS_WIN
, TEAM_RED
);
2468 Inc(gTeamStat
[TEAM_RED
].Score
);
2469 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2470 gLMSRespawnTime
:= gTime
+ 5000;
2472 else if (ar
= 0) and (ab
<> 0) then
2475 g_Game_Message(Format(_lc
[I_MESSAGE_TLMS_WIN
], [AnsiUpperCase(_lc
[I_GAME_TEAM_BLUE
])]), 144);
2477 MH_SEND_GameEvent(NET_EV_TLMS_WIN
, TEAM_BLUE
);
2478 Inc(gTeamStat
[TEAM_BLUE
].Score
);
2479 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2480 gLMSRespawnTime
:= gTime
+ 5000;
2482 else if (ar
= 0) and (ab
= 0) then
2485 g_Game_Message(_lc
[I_GAME_WIN_DRAW
], 144);
2487 MH_SEND_GameEvent(NET_EV_LMS_DRAW
, 0, FName
);
2488 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2489 gLMSRespawnTime
:= gTime
+ 5000;
2492 else if (gGameSettings
.GameMode
= GM_DM
) then
2496 if gPlayers
[k
] <> nil then
2499 // survivor is the winner
2500 g_Game_Message(Format(_lc
[I_MESSAGE_LMS_WIN
], [AnsiUpperCase(FName
)]), 144);
2502 MH_SEND_GameEvent(NET_EV_LMS_WIN
, 0, FName
);
2505 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2506 gLMSRespawnTime
:= gTime
+ 5000;
2508 else if (a
= 0) then
2510 // everyone is dead, restart the map
2511 g_Game_Message(_lc
[I_GAME_WIN_DRAW
], 144);
2513 MH_SEND_GameEvent(NET_EV_LMS_DRAW
, 0, FName
);
2514 gLMSRespawn
:= LMS_RESPAWN_FINAL
;
2515 gLMSRespawnTime
:= gTime
+ 5000;
2518 if srv
and (OldLR
= LMS_RESPAWN_NONE
) and (gLMSRespawn
> LMS_RESPAWN_NONE
) then
2520 if NetMode
= NET_SERVER
then
2521 MH_SEND_GameEvent(NET_EV_LMS_WARMUP
, gLMSRespawnTime
- gTime
)
2523 g_Console_Add(Format(_lc
[I_MSG_WARMUP_START
], [(gLMSRespawnTime
- gTime
) div 1000]), True);
2529 MH_SEND_PlayerStats(FUID
);
2530 MH_SEND_PlayerDeath(FUID
, KillType
, t
, SpawnerUID
);
2531 if gGameSettings
.GameMode
= GM_TDM
then MH_SEND_GameStats
;
2534 if srv
and FNoRespawn
then Spectate(True);
2535 FWantsInGame
:= True;
2538 function TPlayer
.BodyInLiquid(XInc
, YInc
: Integer): Boolean;
2540 Result
:= g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
, PLAYER_RECT
.Width
,
2541 PLAYER_RECT
.Height
-20, PANEL_WATER
or PANEL_ACID1
or PANEL_ACID2
, False);
2544 function TPlayer
.BodyInAcid(XInc
, YInc
: Integer): Boolean;
2546 Result
:= g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
, PLAYER_RECT
.Width
,
2547 PLAYER_RECT
.Height
-20, PANEL_ACID1
or PANEL_ACID2
, False);
2550 procedure TPlayer
.MakeBloodSimple(Count
: Word);
2552 var Blood
: TModelBlood
;
2556 Blood
:= SELF
.FModel
.GetBlood();
2557 g_GFX_Blood(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)+8,
2558 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2),
2559 Count
div 2, 3, -1, 16, (PLAYER_RECT
.Height
*2 div 3),
2560 Blood
.R
, Blood
.G
, Blood
.B
, Blood
.Kind
);
2561 g_GFX_Blood(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)-8,
2562 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2),
2563 Count
div 2, -3, -1, 16, (PLAYER_RECT
.Height
*2) div 3,
2564 Blood
.R
, Blood
.G
, Blood
.B
, Blood
.Kind
);
2568 procedure TPlayer
.MakeBloodVector(Count
: Word; VelX
, VelY
: Integer);
2570 var Blood
: TModelBlood
;
2574 Blood
:= SELF
.FModel
.GetBlood();
2575 g_GFX_Blood(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2),
2576 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2),
2577 Count
, VelX
, VelY
, 16, (PLAYER_RECT
.Height
*2) div 3,
2578 Blood
.R
, Blood
.G
, Blood
.B
, Blood
.Kind
);
2582 procedure TPlayer
.ProcessWeaponAction(Action
: Byte);
2584 if g_Game_IsClient
then Exit
;
2586 WP_PREV
: PrevWeapon();
2587 WP_NEXT
: NextWeapon();
2591 procedure TPlayer
.QueueWeaponSwitch(Weapon
: Byte);
2593 if g_Game_IsClient
then Exit
;
2594 if Weapon
> High(FWeapon
) then Exit
;
2595 FNextWeap
:= FNextWeap
or (1 shl Weapon
);
2598 procedure TPlayer
.resetWeaponQueue ();
2601 FNextWeapDelay
:= 0;
2604 function TPlayer
.hasAmmoForWeapon (weapon
: Byte): Boolean;
2608 WEAPON_KASTET
, WEAPON_SAW
: result
:= true;
2609 WEAPON_SHOTGUN1
, WEAPON_SHOTGUN2
, WEAPON_SUPERPULEMET
: result
:= (FAmmo
[A_SHELLS
] > 0);
2610 WEAPON_PISTOL
, WEAPON_CHAINGUN
: result
:= (FAmmo
[A_BULLETS
] > 0);
2611 WEAPON_ROCKETLAUNCHER
: result
:= (FAmmo
[A_ROCKETS
] > 0);
2612 WEAPON_PLASMA
, WEAPON_BFG
: result
:= (FAmmo
[A_CELLS
] > 0);
2613 WEAPON_FLAMETHROWER
: result
:= (FAmmo
[A_FUEL
] > 0);
2614 else result
:= (weapon
< length(FWeapon
));
2618 function TPlayer
.hasAmmoForShooting (weapon
: Byte): Boolean;
2622 WEAPON_KASTET
, WEAPON_SAW
: result
:= true;
2623 WEAPON_SHOTGUN1
, WEAPON_SUPERPULEMET
: result
:= (FAmmo
[A_SHELLS
] > 0);
2624 WEAPON_SHOTGUN2
: result
:= (FAmmo
[A_SHELLS
] > 1);
2625 WEAPON_PISTOL
, WEAPON_CHAINGUN
: result
:= (FAmmo
[A_BULLETS
] > 0);
2626 WEAPON_ROCKETLAUNCHER
: result
:= (FAmmo
[A_ROCKETS
] > 0);
2627 WEAPON_PLASMA
: result
:= (FAmmo
[A_CELLS
] > 0);
2628 WEAPON_BFG
: result
:= (FAmmo
[A_CELLS
] >= 40);
2629 WEAPON_FLAMETHROWER
: result
:= (FAmmo
[A_FUEL
] > 0);
2630 else result
:= (weapon
< length(FWeapon
));
2634 function TPlayer
.shouldSwitch (weapon
: Byte; hadWeapon
: Boolean): Boolean;
2637 if (weapon
> WP_LAST
+ 1) then
2642 if (FWeapSwitchMode
= 1) and not hadWeapon
then
2644 else if (FWeapSwitchMode
= 2) then
2645 result
:= (FWeapPreferences
[weapon
] > FWeapPreferences
[FCurrWeap
]);
2648 // return 255 for "no switch"
2649 function TPlayer
.getNextWeaponIndex (): Byte;
2652 wantThisWeapon
: array[0..64] of Boolean;
2653 wwc
: Integer = 0; //HACK!
2656 result
:= 255; // default result: "no switch"
2657 //e_LogWriteFln('FSWITCHTOEMPTY: %s', [FSwitchToEmpty], TMsgType.Notify);
2658 // had weapon cycling on previous frame? remove that flag
2659 if (FNextWeap
and $2000) <> 0 then
2661 FNextWeap
:= FNextWeap
and $1FFF;
2662 FNextWeapDelay
:= 0;
2664 // cycling has priority
2665 if (FNextWeap
and $C000) <> 0 then
2667 if (FNextWeap
and $8000) <> 0 then
2671 FNextWeap
:= FNextWeap
or $2000; // we need this
2672 if FNextWeapDelay
> 0 then
2673 exit
; // cooldown time
2675 for i
:= 0 to High(FWeapon
) do
2677 cwi
:= (cwi
+length(FWeapon
)+dir
) mod length(FWeapon
);
2678 if FWeapon
[cwi
] and maySwitch(cwi
) then
2680 //e_LogWriteFln(' SWITCH: cur=%d; new=%d %s %s', [FCurrWeap, cwi, FSwitchToEmpty, hasAmmoForWeapon(cwi)], TMsgType.Notify);
2681 result
:= Byte(cwi
);
2682 FNextWeapDelay
:= WEAPON_DELAY
;
2690 for i
:= 0 to High(wantThisWeapon
) do
2691 wantThisWeapon
[i
] := false;
2692 for i
:= 0 to High(FWeapon
) do
2693 if (FNextWeap
and (1 shl i
)) <> 0 then
2695 wantThisWeapon
[i
] := true;
2699 // exclude currently selected weapon from the set
2700 wantThisWeapon
[FCurrWeap
] := false;
2701 // slow down alterations a little
2704 //e_WriteLog(Format(' FNextWeap=%x; delay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
2705 // more than one weapon requested, assume "alteration" and check alteration delay
2706 if FNextWeapDelay
> 0 then
2712 // do not reset weapon queue, it will be done in `RealizeCurrentWeapon()`
2713 // but clear all counters if no weapon should be switched
2719 //e_WriteLog(Format('wwc=%d', [wwc]), MSG_WARNING);
2720 // try weapons in descending order
2721 for i
:= High(FWeapon
) downto 0 do
2723 if wantThisWeapon
[i
] and FWeapon
[i
] and ((wwc
= 1) or hasAmmoForWeapon(i
)) then
2728 FNextWeapDelay
:= WEAPON_DELAY
* 2; // anyway, 'cause why not
2729 //e_LogWriteFln('FOUND %s %s %s', [result, FSwitchToEmpty, hasAmmoForWeapon(i)], TMsgType.Notify);
2733 // no suitable weapon found, so reset the queue, to avoid accidental "queuing" of weapon w/o ammo
2737 procedure TPlayer
.RealizeCurrentWeapon();
2738 function switchAllowed (): Boolean;
2743 if FBFGFireCounter
<> -1 then
2745 if FTime
[T_SWITCH
] > gTime
then
2747 for i
:= WP_FIRST
to WP_LAST
do
2748 if FReloading
[i
] > 0 then
2756 //e_WriteLog(Format('***RealizeCurrentWeapon: FNextWeap=%x; FNextWeapDelay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
2757 //FNextWeap := FNextWeap and $1FFF;
2758 if FNextWeapDelay
> 0 then Dec(FNextWeapDelay
); // "alteration delay"
2760 if not switchAllowed
then
2762 //HACK for weapon cycling
2763 if (FNextWeap
and $E000) <> 0 then FNextWeap
:= 0;
2767 nw
:= getNextWeaponIndex();
2769 if nw
= 255 then exit
; // don't reset anything here
2770 if nw
> High(FWeapon
) then
2772 // don't forget to reset queue here!
2773 //e_WriteLog(' RealizeCurrentWeapon: WUTAFUUUU', MSG_WARNING);
2781 FTime
[T_SWITCH
] := gTime
+156;
2782 if FCurrWeap
= WEAPON_SAW
then FSawSoundSelect
.PlayAt(FObj
.X
, FObj
.Y
);
2783 FModel
.SetWeapon(FCurrWeap
);
2784 if g_Game_IsNet
then MH_SEND_PlayerStats(FUID
);
2788 procedure TPlayer
.NextWeapon();
2790 if g_Game_IsClient
then Exit
;
2794 procedure TPlayer
.PrevWeapon();
2796 if g_Game_IsClient
then Exit
;
2800 procedure TPlayer
.SetWeapon(W
: Byte);
2802 if FCurrWeap
<> W
then
2803 if W
= WEAPON_SAW
then
2804 FSawSoundSelect
.PlayAt(FObj
.X
, FObj
.Y
);
2807 FModel
.SetWeapon(CurrWeap
);
2811 function TPlayer
.PickItem(ItemType
: Byte; arespawn
: Boolean; var remove
: Boolean): Boolean;
2814 switchWeapon
: Byte = 255;
2815 hadWeapon
: Boolean = False;
2818 if g_Game_IsClient
then Exit
;
2820 // a = true - место спавна предмета:
2821 a
:= LongBool(gGameSettings
.Options
and GAME_OPTION_WEAPONSTAY
) and arespawn
;
2825 if (FHealth
< PLAYER_HP_SOFT
) or (FFireTime
> 0) then
2827 if FHealth
< PLAYER_HP_SOFT
then IncMax(FHealth
, 10, PLAYER_HP_SOFT
);
2831 if gFlash
= 2 then Inc(FPickup
, 5);
2835 if (FHealth
< PLAYER_HP_SOFT
) or (FFireTime
> 0) then
2837 if FHealth
< PLAYER_HP_SOFT
then IncMax(FHealth
, 25, PLAYER_HP_SOFT
);
2841 if gFlash
= 2 then Inc(FPickup
, 5);
2845 if FArmor
< PLAYER_AP_SOFT
then
2847 FArmor
:= PLAYER_AP_SOFT
;
2850 if gFlash
= 2 then Inc(FPickup
, 5);
2854 if FArmor
< PLAYER_AP_LIMIT
then
2856 FArmor
:= PLAYER_AP_LIMIT
;
2859 if gFlash
= 2 then Inc(FPickup
, 5);
2863 if (FHealth
< PLAYER_HP_LIMIT
) or (FFireTime
> 0) then
2865 if FHealth
< PLAYER_HP_LIMIT
then IncMax(FHealth
, 100, PLAYER_HP_LIMIT
);
2869 if gFlash
= 2 then Inc(FPickup
, 5);
2873 if (FHealth
< PLAYER_HP_LIMIT
) or (FArmor
< PLAYER_AP_LIMIT
) or (FFireTime
> 0) then
2875 if FHealth
< PLAYER_HP_LIMIT
then
2876 FHealth
:= PLAYER_HP_LIMIT
;
2877 if FArmor
< PLAYER_AP_LIMIT
then
2878 FArmor
:= PLAYER_AP_LIMIT
;
2882 if gFlash
= 2 then Inc(FPickup
, 5);
2886 if (not FWeapon
[WEAPON_SAW
]) or ((not arespawn
) and (gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
])) then
2888 hadWeapon
:= FWeapon
[WEAPON_SAW
];
2889 switchWeapon
:= WEAPON_SAW
;
2890 FWeapon
[WEAPON_SAW
] := True;
2892 if gFlash
= 2 then Inc(FPickup
, 5);
2893 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
2896 ITEM_WEAPON_SHOTGUN1
:
2897 if (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or not FWeapon
[WEAPON_SHOTGUN1
] then
2899 // Нужно, чтобы не взять все пули сразу:
2900 if a
and FWeapon
[WEAPON_SHOTGUN1
] then Exit
;
2901 hadWeapon
:= FWeapon
[WEAPON_SHOTGUN1
];
2902 switchWeapon
:= WEAPON_SHOTGUN1
;
2903 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
2904 FWeapon
[WEAPON_SHOTGUN1
] := True;
2906 if gFlash
= 2 then Inc(FPickup
, 5);
2907 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
2910 ITEM_WEAPON_SHOTGUN2
:
2911 if (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or not FWeapon
[WEAPON_SHOTGUN2
] then
2913 if a
and FWeapon
[WEAPON_SHOTGUN2
] then Exit
;
2914 hadWeapon
:= FWeapon
[WEAPON_SHOTGUN2
];
2915 switchWeapon
:= WEAPON_SHOTGUN2
;
2916 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
2917 FWeapon
[WEAPON_SHOTGUN2
] := True;
2919 if gFlash
= 2 then Inc(FPickup
, 5);
2920 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
2923 ITEM_WEAPON_CHAINGUN
:
2924 if (FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
]) or not FWeapon
[WEAPON_CHAINGUN
] then
2926 if a
and FWeapon
[WEAPON_CHAINGUN
] then Exit
;
2927 hadWeapon
:= FWeapon
[WEAPON_CHAINGUN
];
2928 switchWeapon
:= WEAPON_CHAINGUN
;
2929 IncMax(FAmmo
[A_BULLETS
], 50, FMaxAmmo
[A_BULLETS
]);
2930 FWeapon
[WEAPON_CHAINGUN
] := True;
2932 if gFlash
= 2 then Inc(FPickup
, 5);
2933 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
2936 ITEM_WEAPON_ROCKETLAUNCHER
:
2937 if (FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
]) or not FWeapon
[WEAPON_ROCKETLAUNCHER
] then
2939 if a
and FWeapon
[WEAPON_ROCKETLAUNCHER
] then Exit
;
2940 switchWeapon
:= WEAPON_ROCKETLAUNCHER
;
2941 hadWeapon
:= FWeapon
[WEAPON_ROCKETLAUNCHER
];
2942 IncMax(FAmmo
[A_ROCKETS
], 2, FMaxAmmo
[A_ROCKETS
]);
2943 FWeapon
[WEAPON_ROCKETLAUNCHER
] := True;
2945 if gFlash
= 2 then Inc(FPickup
, 5);
2946 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
2950 if (FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
]) or not FWeapon
[WEAPON_PLASMA
] then
2952 if a
and FWeapon
[WEAPON_PLASMA
] then Exit
;
2953 switchWeapon
:= WEAPON_PLASMA
;
2954 hadWeapon
:= FWeapon
[WEAPON_PLASMA
];
2955 IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
2956 FWeapon
[WEAPON_PLASMA
] := True;
2958 if gFlash
= 2 then Inc(FPickup
, 5);
2959 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
2963 if (FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
]) or not FWeapon
[WEAPON_BFG
] then
2965 if a
and FWeapon
[WEAPON_BFG
] then Exit
;
2966 switchWeapon
:= WEAPON_BFG
;
2967 hadWeapon
:= FWeapon
[WEAPON_BFG
];
2968 IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
2969 FWeapon
[WEAPON_BFG
] := True;
2971 if gFlash
= 2 then Inc(FPickup
, 5);
2972 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
2975 ITEM_WEAPON_SUPERPULEMET
:
2976 if (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or not FWeapon
[WEAPON_SUPERPULEMET
] then
2978 if a
and FWeapon
[WEAPON_SUPERPULEMET
] then Exit
;
2979 switchWeapon
:= WEAPON_SUPERPULEMET
;
2980 hadWeapon
:= FWeapon
[WEAPON_SUPERPULEMET
];
2981 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
2982 FWeapon
[WEAPON_SUPERPULEMET
] := True;
2984 if gFlash
= 2 then Inc(FPickup
, 5);
2985 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
2988 ITEM_WEAPON_FLAMETHROWER
:
2989 if (FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
]) or not FWeapon
[WEAPON_FLAMETHROWER
] then
2991 if a
and FWeapon
[WEAPON_FLAMETHROWER
] then Exit
;
2992 switchWeapon
:= WEAPON_FLAMETHROWER
;
2993 hadWeapon
:= FWeapon
[WEAPON_FLAMETHROWER
];
2994 IncMax(FAmmo
[A_FUEL
], 100, FMaxAmmo
[A_FUEL
]);
2995 FWeapon
[WEAPON_FLAMETHROWER
] := True;
2997 if gFlash
= 2 then Inc(FPickup
, 5);
2998 if a
and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETWEAPON');
3002 if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then
3004 IncMax(FAmmo
[A_BULLETS
], 10, FMaxAmmo
[A_BULLETS
]);
3007 if gFlash
= 2 then Inc(FPickup
, 5);
3010 ITEM_AMMO_BULLETS_BOX
:
3011 if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then
3013 IncMax(FAmmo
[A_BULLETS
], 50, FMaxAmmo
[A_BULLETS
]);
3016 if gFlash
= 2 then Inc(FPickup
, 5);
3020 if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then
3022 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
3025 if gFlash
= 2 then Inc(FPickup
, 5);
3028 ITEM_AMMO_SHELLS_BOX
:
3029 if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then
3031 IncMax(FAmmo
[A_SHELLS
], 25, FMaxAmmo
[A_SHELLS
]);
3034 if gFlash
= 2 then Inc(FPickup
, 5);
3038 if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then
3040 IncMax(FAmmo
[A_ROCKETS
], 1, FMaxAmmo
[A_ROCKETS
]);
3043 if gFlash
= 2 then Inc(FPickup
, 5);
3046 ITEM_AMMO_ROCKET_BOX
:
3047 if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then
3049 IncMax(FAmmo
[A_ROCKETS
], 5, FMaxAmmo
[A_ROCKETS
]);
3052 if gFlash
= 2 then Inc(FPickup
, 5);
3056 if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then
3058 IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
3061 if gFlash
= 2 then Inc(FPickup
, 5);
3065 if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then
3067 IncMax(FAmmo
[A_CELLS
], 100, FMaxAmmo
[A_CELLS
]);
3070 if gFlash
= 2 then Inc(FPickup
, 5);
3074 if FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
] then
3076 IncMax(FAmmo
[A_FUEL
], 100, FMaxAmmo
[A_FUEL
]);
3079 if gFlash
= 2 then Inc(FPickup
, 5);
3083 if not(R_ITEM_BACKPACK
in FRulez
) or
3084 (FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
]) or
3085 (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or
3086 (FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
]) or
3087 (FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
]) or
3088 (FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
]) then
3090 FMaxAmmo
[A_BULLETS
] := AmmoLimits
[1, A_BULLETS
];
3091 FMaxAmmo
[A_SHELLS
] := AmmoLimits
[1, A_SHELLS
];
3092 FMaxAmmo
[A_ROCKETS
] := AmmoLimits
[1, A_ROCKETS
];
3093 FMaxAmmo
[A_CELLS
] := AmmoLimits
[1, A_CELLS
];
3094 FMaxAmmo
[A_FUEL
] := AmmoLimits
[1, A_FUEL
];
3096 if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then
3097 IncMax(FAmmo
[A_BULLETS
], 10, FMaxAmmo
[A_BULLETS
]);
3098 if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then
3099 IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
3100 if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then
3101 IncMax(FAmmo
[A_ROCKETS
], 1, FMaxAmmo
[A_ROCKETS
]);
3102 if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then
3103 IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
3104 if FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
] then
3105 IncMax(FAmmo
[A_FUEL
], 50, FMaxAmmo
[A_FUEL
]);
3107 FRulez
:= FRulez
+ [R_ITEM_BACKPACK
];
3110 if gFlash
= 2 then Inc(FPickup
, 5);
3114 if not(R_KEY_RED
in FRulez
) then
3116 Include(FRulez
, R_KEY_RED
);
3118 remove
:= (gGameSettings
.GameMode
<> GM_COOP
) and (g_Player_GetCount() < 2);
3119 if gFlash
= 2 then Inc(FPickup
, 5);
3120 if (not remove
) and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETITEM');
3124 if not(R_KEY_GREEN
in FRulez
) then
3126 Include(FRulez
, R_KEY_GREEN
);
3128 remove
:= (gGameSettings
.GameMode
<> GM_COOP
) and (g_Player_GetCount() < 2);
3129 if gFlash
= 2 then Inc(FPickup
, 5);
3130 if (not remove
) and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETITEM');
3134 if not(R_KEY_BLUE
in FRulez
) then
3136 Include(FRulez
, R_KEY_BLUE
);
3138 remove
:= (gGameSettings
.GameMode
<> GM_COOP
) and (g_Player_GetCount() < 2);
3139 if gFlash
= 2 then Inc(FPickup
, 5);
3140 if (not remove
) and g_Game_IsNet
then MH_SEND_Sound(GameX
, GameY
, 'SOUND_ITEM_GETITEM');
3144 if FMegaRulez
[MR_SUIT
] < gTime
+PLAYER_SUIT_TIME
then
3146 FMegaRulez
[MR_SUIT
] := gTime
+PLAYER_SUIT_TIME
;
3150 if gFlash
= 2 then Inc(FPickup
, 5);
3154 if FAir
< AIR_MAX
then
3159 if gFlash
= 2 then Inc(FPickup
, 5);
3164 if not (R_BERSERK
in FRulez
) then
3166 Include(FRulez
, R_BERSERK
);
3167 if (FBFGFireCounter
= -1) then
3169 FCurrWeap
:= WEAPON_KASTET
;
3171 FModel
.SetWeapon(WEAPON_KASTET
);
3176 if gFlash
= 2 then Inc(FPickup
, 5);
3178 FBerserk
:= gTime
+30000;
3183 if (FHealth
< PLAYER_HP_SOFT
) or (FFireTime
> 0) then
3185 if FHealth
< PLAYER_HP_SOFT
then FHealth
:= PLAYER_HP_SOFT
;
3186 FBerserk
:= gTime
+30000;
3194 if FMegaRulez
[MR_INVUL
] < gTime
+PLAYER_INVUL_TIME
then
3196 FMegaRulez
[MR_INVUL
] := gTime
+PLAYER_INVUL_TIME
;
3200 if gFlash
= 2 then Inc(FPickup
, 5);
3204 if (FHealth
< PLAYER_HP_LIMIT
) or (FFireTime
> 0) then
3206 if FHealth
< PLAYER_HP_LIMIT
then IncMax(FHealth
, 4, PLAYER_HP_LIMIT
);
3210 if gFlash
= 2 then Inc(FPickup
, 5);
3214 if FArmor
< PLAYER_AP_LIMIT
then
3216 IncMax(FArmor
, 5, PLAYER_AP_LIMIT
);
3219 if gFlash
= 2 then Inc(FPickup
, 5);
3223 if FJetFuel
< JET_MAX
then
3225 FJetFuel
:= JET_MAX
;
3228 if gFlash
= 2 then Inc(FPickup
, 5);
3232 if FMegaRulez
[MR_INVIS
] < gTime
+PLAYER_INVIS_TIME
then
3234 FMegaRulez
[MR_INVIS
] := gTime
+PLAYER_INVIS_TIME
;
3237 if gFlash
= 2 then Inc(FPickup
, 5);
3241 if (shouldSwitch(switchWeapon
, hadWeapon
)) then
3242 QueueWeaponSwitch(switchWeapon
);
3245 procedure TPlayer
.Touch();
3249 //FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
3252 // Бросить флаг товарищу:
3253 if gGameSettings
.GameMode
= GM_CTF
then
3258 procedure TPlayer
.Push(vx
, vy
: Integer);
3260 if (not FPhysics
) and FGhost
then
3262 FObj
.Accel
.X
:= FObj
.Accel
.X
+ vx
;
3263 FObj
.Accel
.Y
:= FObj
.Accel
.Y
+ vy
;
3264 if g_Game_IsNet
and g_Game_IsServer
then
3265 MH_SEND_PlayerPos(True, FUID
, NET_EVERYONE
);
3268 procedure TPlayer
.Reset(Force
: Boolean);
3274 FTime
[T_RESPAWN
] := 0;
3275 FTime
[T_FLAGCAP
] := 0;
3287 {$IFDEF ENABLE_CORPSES}
3293 FSpectator
:= False;
3296 FSpectatePlayer
:= -1;
3297 FNoRespawn
:= False;
3299 FLives
:= gGameSettings
.MaxLives
;
3304 procedure TPlayer
.SoftReset();
3312 FBFGFireCounter
:= -1;
3313 {$IFDEF ENABLE_SHELLS}
3322 SetAction(A_STAND
, True);
3325 function TPlayer
.GetRespawnPoint(): Byte;
3330 // На будущее: FSpawn - игрок уже играл и перерождается
3332 // Одиночная игра/кооператив
3333 if gGameSettings
.GameMode
in [GM_COOP
, GM_SINGLE
] then
3335 if Self
= gPlayer1
then
3337 // player 1 should try to spawn on the player 1 point
3338 if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1
) > 0 then
3339 Exit(RESPAWNPOINT_PLAYER1
)
3340 else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2
) > 0 then
3341 Exit(RESPAWNPOINT_PLAYER2
);
3343 else if Self
= gPlayer2
then
3345 // player 2 should try to spawn on the player 2 point
3346 if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2
) > 0 then
3347 Exit(RESPAWNPOINT_PLAYER2
)
3348 else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1
) > 0 then
3349 Exit(RESPAWNPOINT_PLAYER1
);
3353 // other players randomly pick either the first or the second point
3354 c
:= IfThen((Random(2) = 0), RESPAWNPOINT_PLAYER1
, RESPAWNPOINT_PLAYER2
);
3355 if g_Map_GetPointCount(c
) > 0 then
3357 // try the other one
3358 c
:= IfThen((c
= RESPAWNPOINT_PLAYER1
), RESPAWNPOINT_PLAYER2
, RESPAWNPOINT_PLAYER1
);
3359 if g_Map_GetPointCount(c
) > 0 then
3365 if gGameSettings
.GameMode
= GM_DM
then
3367 // try DM points first
3368 if g_Map_GetPointCount(RESPAWNPOINT_DM
) > 0 then
3369 Exit(RESPAWNPOINT_DM
);
3373 if gGameSettings
.GameMode
in [GM_TDM
, GM_CTF
] then
3375 // try team points first
3376 c
:= RESPAWNPOINT_DM
;
3377 if FTeam
= TEAM_RED
then
3378 c
:= RESPAWNPOINT_RED
3379 else if FTeam
= TEAM_BLUE
then
3380 c
:= RESPAWNPOINT_BLUE
;
3381 if g_Map_GetPointCount(c
) > 0 then
3385 // still haven't found a spawnpoint, try random shit
3386 Result
:= g_Map_GetRandomPointType();
3389 procedure TPlayer
.Respawn(Silent
: Boolean; Force
: Boolean = False);
3391 RespawnPoint
: TRespawnPoint
;
3397 FBFGFireCounter
:= -1;
3398 {$IFDEF ENABLE_SHELLS}
3405 {$IFDEF ENABLE_CORPSES}
3409 if not g_Game_IsServer
then
3413 FWantsInGame
:= True;
3414 FJustTeleported
:= True;
3417 FTime
[T_RESPAWN
] := 0;
3421 // if server changes MaxLives we gotta be ready
3422 if gGameSettings
.MaxLives
= 0 then FNoRespawn
:= False;
3424 // Еще нельзя возродиться:
3425 if FTime
[T_RESPAWN
] > gTime
then
3428 // Просрал все жизни:
3431 if not FSpectator
then Spectate(True);
3432 FWantsInGame
:= True;
3436 if (gGameSettings
.GameType
<> GT_SINGLE
) and (gGameSettings
.GameMode
<> GM_COOP
) then
3437 begin // "Своя игра"
3438 // Берсерк не сохраняется между уровнями:
3439 FRulez
:= FRulez
-[R_BERSERK
];
3441 else // "Одиночная игра"/"Кооп"
3443 // Берсерк и ключи не сохраняются между уровнями:
3444 FRulez
:= FRulez
-[R_KEY_RED
, R_KEY_GREEN
, R_KEY_BLUE
, R_BERSERK
];
3447 // Получаем точку спауна игрока:
3448 c
:= GetRespawnPoint();
3453 // Воскрешение без оружия:
3456 FHealth
:= Round(PLAYER_HP_SOFT
* (FHandicap
/ 100));
3462 for a
:= WP_FIRST
to WP_LAST
do
3464 FWeapon
[a
] := False;
3468 FWeapon
[WEAPON_PISTOL
] := True;
3469 FWeapon
[WEAPON_KASTET
] := True;
3470 FCurrWeap
:= WEAPON_PISTOL
;
3473 FModel
.SetWeapon(FCurrWeap
);
3475 for b
:= A_BULLETS
to A_HIGH
do
3478 FAmmo
[A_BULLETS
] := 50;
3480 FMaxAmmo
[A_BULLETS
] := AmmoLimits
[0, A_BULLETS
];
3481 FMaxAmmo
[A_SHELLS
] := AmmoLimits
[0, A_SHELLS
];
3482 FMaxAmmo
[A_ROCKETS
] := AmmoLimits
[0, A_SHELLS
];
3483 FMaxAmmo
[A_CELLS
] := AmmoLimits
[0, A_CELLS
];
3484 FMaxAmmo
[A_FUEL
] := AmmoLimits
[0, A_FUEL
];
3486 if (gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
]) and
3487 LongBool(gGameSettings
.Options
and GAME_OPTION_DMKEYS
) then
3488 FRulez
:= [R_KEY_RED
, R_KEY_GREEN
, R_KEY_BLUE
]
3493 // Получаем координаты точки возрождения:
3494 if not g_Map_GetPoint(c
, RespawnPoint
) then
3496 g_FatalError(_lc
[I_GAME_ERROR_GET_SPAWN
]);
3500 // Установка координат и сброс всех параметров:
3501 FObj
.X
:= RespawnPoint
.X
-PLAYER_RECT
.X
;
3502 FObj
.Y
:= RespawnPoint
.Y
-PLAYER_RECT
.Y
;
3503 FObj
.oldX
:= FObj
.X
; // don't interpolate after respawn
3504 FObj
.oldY
:= FObj
.Y
;
3510 FDirection
:= RespawnPoint
.Direction
;
3511 if FDirection
= TDirection
.D_LEFT
then
3516 SetAction(A_STAND
, True);
3517 FModel
.Direction
:= FDirection
;
3519 for a
:= Low(FTime
) to High(FTime
) do
3522 for a
:= Low(FMegaRulez
) to High(FMegaRulez
) do
3525 // Respawn invulnerability
3526 if (gGameSettings
.GameType
<> GT_SINGLE
) and (gGameSettings
.SpawnInvul
> 0) then
3528 FMegaRulez
[MR_INVUL
] := gTime
+ gGameSettings
.SpawnInvul
* 1000;
3529 FSpawnInvul
:= FMegaRulez
[MR_INVUL
];
3534 FCanJetpack
:= False;
3541 // Анимация возрождения:
3542 if (not gLoadGameMode
) and (not Silent
) then
3545 R_GFX_TELEPORT_FAST
,
3546 FObj
.X
+ PLAYER_RECT
.X
+ (PLAYER_RECT
.Width
div 2) - 32,
3547 FObj
.Y
+ PLAYER_RECT
.Y
+ (PLAYER_RECT
.Height
div 2) - 32
3552 FSpectator
:= False;
3555 FSpectatePlayer
:= -1;
3558 if (gPlayer1
= nil) and (gSpectLatchPID1
= FUID
) then
3560 if (gPlayer2
= nil) and (gSpectLatchPID2
= FUID
) then
3563 if g_Game_IsNet
then
3565 MH_SEND_PlayerPos(True, FUID
, NET_EVERYONE
);
3566 MH_SEND_PlayerStats(FUID
, NET_EVERYONE
);
3568 MH_SEND_Effect(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)-32,
3569 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2)-32,
3574 procedure TPlayer
.Spectate(NoMove
: Boolean = False);
3577 Kill(K_EXTRAHARDKILL
, FUID
, HIT_SOME
)
3578 else if (not NoMove
) then
3580 GameX
:= gMapInfo
.Width
div 2;
3581 GameY
:= gMapInfo
.Height
div 2;
3590 FWantsInGame
:= False;
3593 {$IFDEF ENABLE_CORPSES}
3599 if Self
= gPlayer1
then
3601 gSpectLatchPID1
:= FUID
;
3604 else if Self
= gPlayer2
then
3606 gSpectLatchPID2
:= FUID
;
3611 if g_Game_IsNet
then
3612 MH_SEND_PlayerStats(FUID
);
3615 procedure TPlayer
.SwitchNoClip
;
3619 FGhost
:= not FGhost
;
3620 FPhysics
:= not FGhost
;
3632 procedure TPlayer
.Run(Direction
: TDirection
);
3633 {$IFDEF ENABLE_GIBS}
3637 if MAX_RUNVEL
> 8 then
3641 if Direction
= TDirection
.D_LEFT
then
3643 if FObj
.Vel
.X
> -MAX_RUNVEL
then
3644 FObj
.Vel
.X
:= FObj
.Vel
.X
- (MAX_RUNVEL
shr 3);
3647 if FObj
.Vel
.X
< MAX_RUNVEL
then
3648 FObj
.Vel
.X
:= FObj
.Vel
.X
+ (MAX_RUNVEL
shr 3);
3650 {$IFDEF ENABLE_GIBS}
3651 // Возможно, пинаем куски:
3652 if (FObj
.Vel
.X
<> 0) and (gGibs
<> nil) then
3654 b
:= Abs(FObj
.Vel
.X
);
3655 if b
> 1 then b
:= b
* (Random(8 div b
) + 1);
3656 for a
:= 0 to High(gGibs
) do
3658 if gGibs
[a
].alive
and
3659 g_Obj_Collide(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
+FObj
.Rect
.Height
-4,
3660 FObj
.Rect
.Width
, 8, @gGibs
[a
].Obj
) and (Random(3) = 0) then
3663 if FObj
.Vel
.X
< 0 then
3665 g_Obj_PushA(@gGibs
[a
].Obj
, b
, Random(61)+120) // налево
3669 g_Obj_PushA(@gGibs
[a
].Obj
, b
, Random(61)); // направо
3671 gGibs
[a
].positionChanged(); // this updates spatial accelerators
3680 procedure TPlayer
.SeeDown();
3682 SetAction(A_SEEDOWN
);
3684 if FDirection
= TDirection
.D_LEFT
then FAngle
:= ANGLE_LEFTDOWN
else FAngle
:= ANGLE_RIGHTDOWN
;
3686 if FIncCam
> -120 then DecMin(FIncCam
, 5, -120);
3689 procedure TPlayer
.SeeUp();
3693 if FDirection
= TDirection
.D_LEFT
then FAngle
:= ANGLE_LEFTUP
else FAngle
:= ANGLE_RIGHTUP
;
3695 if FIncCam
< 120 then IncMax(FIncCam
, 5, 120);
3698 procedure TPlayer
.SetAction(Action
: Byte; Force
: Boolean = False);
3706 A_ATTACK
: Prior
:= 2;
3707 A_SEEUP
: Prior
:= 1;
3708 A_SEEDOWN
: Prior
:= 1;
3709 A_ATTACKUP
: Prior
:= 2;
3710 A_ATTACKDOWN
: Prior
:= 2;
3715 if (Prior
> FActionPrior
) or Force
then
3716 if not ((Prior
= 2) and (FCurrWeap
= WEAPON_SAW
)) then
3718 FActionPrior
:= Prior
;
3719 FActionAnim
:= Action
;
3720 FActionForce
:= Force
;
3721 FActionChanged
:= True;
3724 if Action
in [A_ATTACK
, A_ATTACKUP
, A_ATTACKDOWN
] then FModel
.SetFire(True);
3727 function TPlayer
.StayOnStep(XInc
, YInc
: Integer): Boolean;
3729 Result
:= not g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+YInc
+PLAYER_RECT
.Y
+PLAYER_RECT
.Height
-1,
3730 PLAYER_RECT
.Width
, 1, PANEL_STEP
, False)
3731 and g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+YInc
+PLAYER_RECT
.Y
+PLAYER_RECT
.Height
,
3732 PLAYER_RECT
.Width
, 1, PANEL_STEP
, False);
3735 function TPlayer
.TeleportTo(X
, Y
: Integer; silent
: Boolean; dir
: Byte): Boolean;
3739 if g_CollideLevel(X
, Y
, PLAYER_RECT
.Width
, PLAYER_RECT
.Height
) then
3741 g_Sound_PlayExAt('SOUND_GAME_NOTELEPORT', FObj
.X
, FObj
.Y
);
3742 if g_Game_IsServer
and g_Game_IsNet
then
3743 MH_SEND_Sound(FObj
.X
, FObj
.Y
, 'SOUND_GAME_NOTELEPORT');
3747 FJustTeleported
:= True;
3751 g_Sound_PlayExAt('SOUND_GAME_TELEPORT', FObj
.X
, FObj
.Y
);
3754 R_GFX_TELEPORT_FAST
,
3755 FObj
.X
+ PLAYER_RECT
.X
+ (PLAYER_RECT
.Width
div 2) - 32,
3756 FObj
.Y
+ PLAYER_RECT
.Y
+ (PLAYER_RECT
.Height
div 2) - 32
3759 if g_Game_IsServer
and g_Game_IsNet
then
3760 MH_SEND_Effect(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)-32,
3761 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2)-32, 1,
3765 FObj
.X
:= X
-PLAYER_RECT
.X
;
3766 FObj
.Y
:= Y
-PLAYER_RECT
.Y
;
3767 FObj
.oldX
:= FObj
.X
; // don't interpolate after respawn
3768 FObj
.oldY
:= FObj
.Y
;
3769 if FAlive
and FGhost
then
3775 if not g_Game_IsNet
then
3779 SetDirection(TDirection
.D_LEFT
);
3785 SetDirection(TDirection
.D_RIGHT
);
3791 if FDirection
= TDirection
.D_RIGHT
then
3793 SetDirection(TDirection
.D_LEFT
);
3798 SetDirection(TDirection
.D_RIGHT
);
3808 R_GFX_TELEPORT_FAST
,
3809 FObj
.X
+ PLAYER_RECT
.X
+ (PLAYER_RECT
.Width
div 2) - 32,
3810 FObj
.Y
+ PLAYER_RECT
.Y
+ (PLAYER_RECT
.Height
div 2) - 32
3813 if g_Game_IsServer
and g_Game_IsNet
then
3814 MH_SEND_Effect(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2)-32,
3815 FObj
.Y
+PLAYER_RECT
.Y
+(PLAYER_RECT
.Height
div 2)-32, 0,
3822 function nonz(a
: Single): Single;
3830 procedure TPlayer
.PreUpdate();
3832 FSlopeOld
:= FObj
.slopeUpLeft
;
3833 FIncCamOld
:= FIncCam
;
3834 FObj
.oldX
:= FObj
.X
;
3835 FObj
.oldY
:= FObj
.Y
;
3838 procedure TPlayer
.Update();
3841 i
, ii
, wx
, wy
, xd
, yd
, k
: Integer;
3842 blockmon
, headwater
, dospawn
: Boolean;
3847 NetServer
:= g_Game_IsNet
and g_Game_IsServer
;
3848 AnyServer
:= g_Game_IsServer
;
3850 if g_Game_IsClient
and (NetInterpLevel
> 0) then
3851 DoLerp(NetInterpLevel
+ 1)
3857 if (FClientID
>= 0) and (NetClients
[FClientID
].Peer
<> nil) then
3859 FPing
:= NetClients
[FClientID
].Peer
^.lastRoundTripTime
;
3860 if NetClients
[FClientID
].Peer
^.packetsSent
> 0 then
3861 FLoss
:= Round(100*NetClients
[FClientID
].Peer
^.packetsLost
/NetClients
[FClientID
].Peer
^.packetsSent
)
3870 if FAlive
and (gFly
or FJetpack
) then
3873 if FDirection
= TDirection
.D_LEFT
then
3878 if FAlive
and (not FGhost
) then
3880 if FKeys
[KEY_UP
].Pressed
then
3882 if FKeys
[KEY_DOWN
].Pressed
then
3886 if (not (FKeys
[KEY_UP
].Pressed
or FKeys
[KEY_DOWN
].Pressed
)) and
3889 i
:= g_basic
.Sign(FIncCam
);
3890 FIncCam
:= Abs(FIncCam
);
3891 DecMin(FIncCam
, 5, 0);
3892 FIncCam
:= FIncCam
*i
;
3895 if gTime
mod (GAME_TICK
*2) <> 0 then
3897 if (FObj
.Vel
.X
= 0) and FAlive
then
3899 if FKeys
[KEY_LEFT
].Pressed
then
3900 Run(TDirection
.D_LEFT
);
3901 if FKeys
[KEY_RIGHT
].Pressed
then
3902 Run(TDirection
.D_RIGHT
);
3907 g_Obj_Move(@FObj
, True, True, True);
3908 positionChanged(); // this updates spatial accelerators
3914 FActionChanged
:= False;
3918 // Let alive player do some actions
3919 if FKeys
[KEY_LEFT
].Pressed
then Run(TDirection
.D_LEFT
);
3920 if FKeys
[KEY_RIGHT
].Pressed
then Run(TDirection
.D_RIGHT
);
3921 if FKeys
[KEY_FIRE
].Pressed
and AnyServer
then Fire()
3927 if NetServer
then MH_SEND_PlayerStats(FUID
);
3930 if FKeys
[KEY_OPEN
].Pressed
and AnyServer
then Use();
3931 if FKeys
[KEY_JUMP
].Pressed
then Jump()
3934 if AnyServer
and FJetpack
then
3938 if NetServer
then MH_SEND_PlayerStats(FUID
);
3940 FCanJetpack
:= True;
3947 for k
:= Low(FKeys
) to KEY_CHAT
-1 do
3949 if FKeys
[k
].Pressed
then
3957 if gGameSettings
.GameType
in [GT_CUSTOM
, GT_SERVER
, GT_CLIENT
] then
3960 if (FTime
[T_RESPAWN
] <= gTime
) and
3961 gGameOn
and (not FAlive
) then
3963 if (g_Player_GetCount() > 1) then
3967 gExit
:= EXIT_RESTART
;
3972 // Dead spectator actions
3975 if FKeys
[KEY_OPEN
].Pressed
and AnyServer
then Fire();
3976 if FKeys
[KEY_FIRE
].Pressed
and AnyServer
then
3980 if (FSpectatePlayer
>= High(gPlayers
)) then
3981 FSpectatePlayer
:= -1
3985 for I
:= FSpectatePlayer
+ 1 to High(gPlayers
) do
3986 if gPlayers
[I
] <> nil then
3987 if gPlayers
[I
].alive
then
3988 if gPlayers
[I
].UID
<> FUID
then
3990 FSpectatePlayer
:= I
;
3995 if not SetSpect
then FSpectatePlayer
:= -1;
4006 if FKeys
[KEY_UP
].Pressed
or FKeys
[KEY_JUMP
].Pressed
then
4008 FYTo
:= FObj
.Y
- 32;
4009 FSpectatePlayer
:= -1;
4011 if FKeys
[KEY_DOWN
].Pressed
then
4013 FYTo
:= FObj
.Y
+ 32;
4014 FSpectatePlayer
:= -1;
4016 if FKeys
[KEY_LEFT
].Pressed
then
4018 FXTo
:= FObj
.X
- 32;
4019 FSpectatePlayer
:= -1;
4021 if FKeys
[KEY_RIGHT
].Pressed
then
4023 FXTo
:= FObj
.X
+ 32;
4024 FSpectatePlayer
:= -1;
4027 if (FXTo
< -64) then
4029 else if (FXTo
> gMapInfo
.Width
+ 32) then
4030 FXTo
:= gMapInfo
.Width
+ 32;
4031 if (FYTo
< -72) then
4033 else if (FYTo
> gMapInfo
.Height
+ 32) then
4034 FYTo
:= gMapInfo
.Height
+ 32;
4039 g_Obj_Move(@FObj
, True, True, True);
4040 positionChanged(); // this updates spatial accelerators
4047 if (FSpectatePlayer
<= High(gPlayers
)) and (FSpectatePlayer
>= 0) then
4048 if gPlayers
[FSpectatePlayer
] <> nil then
4049 if gPlayers
[FSpectatePlayer
].alive
then
4051 FXTo
:= gPlayers
[FSpectatePlayer
].GameX
;
4052 FYTo
:= gPlayers
[FSpectatePlayer
].GameY
;
4056 blockmon
:= g_Map_CollidePanel(FObj
.X
+PLAYER_HEADRECT
.X
, FObj
.Y
+PLAYER_HEADRECT
.Y
,
4057 PLAYER_HEADRECT
.Width
, PLAYER_HEADRECT
.Height
,
4058 PANEL_BLOCKMON
, True);
4059 headwater
:= HeadInLiquid(0, 0);
4061 // Сопротивление воздуха:
4062 if (not FAlive
) or not (FKeys
[KEY_LEFT
].Pressed
or FKeys
[KEY_RIGHT
].Pressed
) then
4063 if FObj
.Vel
.X
<> 0 then
4064 FObj
.Vel
.X
:= z_dec(FObj
.Vel
.X
, 1);
4066 if (FLastHit
= HIT_TRAP
) and (FPain
> 90) then FPain
:= 90;
4067 DecMin(FPain
, 5, 0);
4068 DecMin(FPickup
, 1, 0);
4070 if FAlive
and (FObj
.Y
> Integer(gMapInfo
.Height
)+128) and AnyServer
then
4072 // Обнулить действия примочек, чтобы фон пропал
4073 FMegaRulez
[MR_SUIT
] := 0;
4074 FMegaRulez
[MR_INVUL
] := 0;
4075 FMegaRulez
[MR_INVIS
] := 0;
4076 Kill(K_FALLKILL
, 0, HIT_FALL
);
4083 if FCurrWeap
= WEAPON_SAW
then
4084 if not (FSawSound
.IsPlaying() or FSawSoundHit
.IsPlaying() or
4085 FSawSoundSelect
.IsPlaying()) then
4086 FSawSoundIdle
.PlayAt(FObj
.X
, FObj
.Y
);
4089 if (not FJetSoundFly
.IsPlaying()) and (not FJetSoundOn
.IsPlaying()) and
4090 (not FJetSoundOff
.IsPlaying()) then
4092 FJetSoundFly
.SetPosition(0);
4093 FJetSoundFly
.PlayAt(FObj
.X
, FObj
.Y
);
4096 for b
:= WP_FIRST
to WP_LAST
do
4097 if FReloading
[b
] > 0 then
4103 {$IFDEF ENABLE_SHELLS}
4104 if FShellTimer
> -1 then
4105 if FShellTimer
= 0 then
4107 if FShellType
= SHELL_SHELL
then
4108 g_Shells_Create(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
4109 GameVelX
, GameVelY
-2, SHELL_SHELL
)
4110 else if FShellType
= SHELL_DBLSHELL
then
4112 g_Shells_Create(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
4113 GameVelX
+1, GameVelY
-2, SHELL_SHELL
);
4114 g_Shells_Create(GameX
+PLAYER_RECT_CX
, GameY
+PLAYER_RECT_CX
,
4115 GameVelX
-1, GameVelY
-2, SHELL_SHELL
);
4118 end else Dec(FShellTimer
);
4121 if (FBFGFireCounter
> -1) then
4122 if FBFGFireCounter
= 0 then
4126 wx
:= FObj
.X
+WEAPONPOINT
[FDirection
].X
;
4127 wy
:= FObj
.Y
+WEAPONPOINT
[FDirection
].Y
;
4128 xd
:= wx
+IfThen(FDirection
= TDirection
.D_LEFT
, -30, 30);
4129 yd
:= wy
+firediry();
4130 g_Weapon_bfgshot(wx
, wy
, xd
, yd
, FUID
);
4131 if NetServer
then MH_SEND_PlayerFire(FUID
, WEAPON_BFG
, wx
, wy
, xd
, yd
);
4132 if (FAngle
= 0) or (FAngle
= 180) then SetAction(A_ATTACK
)
4133 else if (FAngle
= ANGLE_LEFTDOWN
) or (FAngle
= ANGLE_RIGHTDOWN
) then SetAction(A_ATTACKDOWN
)
4134 else if (FAngle
= ANGLE_LEFTUP
) or (FAngle
= ANGLE_RIGHTUP
) then SetAction(A_ATTACKUP
);
4137 FReloading
[WEAPON_BFG
] := WEAPON_RELOAD
[WEAPON_BFG
];
4138 FBFGFireCounter
:= -1;
4141 FBFGFireCounter
:= 0
4143 Dec(FBFGFireCounter
);
4145 if (FMegaRulez
[MR_SUIT
] < gTime
) and AnyServer
then
4147 b
:= g_GetAcidHit(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+PLAYER_RECT
.Y
, PLAYER_RECT
.Width
, PLAYER_RECT
.Height
);
4149 if (b
> 0) and (gTime
mod (15*GAME_TICK
) = 0) then Damage(b
, 0, 0, 0, HIT_ACID
);
4152 if (headwater
or blockmon
) then
4158 if AnyServer
then Damage(10, 0, 0, 0, HIT_WATER
);
4161 else if (FAir
mod 31 = 0) and not blockmon
then
4164 g_GFX_Bubbles(FObj
.X
+PLAYER_RECT
.X
+(PLAYER_RECT
.Width
div 2), FObj
.Y
+PLAYER_RECT
.Y
-4, 5+Random(6), 8, 4);
4167 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj
.X
, FObj
.Y
)
4168 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj
.X
, FObj
.Y
);
4170 end else if FAir
< AIR_DEF
then
4173 if FFireTime
> 0 then
4175 if BodyInLiquid(0, 0) then
4180 else if FMegaRulez
[MR_SUIT
] >= gTime
then
4182 if FMegaRulez
[MR_SUIT
] = gTime
then
4189 if FFirePainTime
<= 0 then
4191 if g_Game_IsServer
then
4192 Damage(2, FFireAttacker
, 0, 0, HIT_FLAME
);
4193 FFirePainTime
:= 12 - FFireTime
div 12;
4195 FFirePainTime
:= FFirePainTime
- 1;
4196 FFireTime
:= FFireTime
- 1;
4197 if ((FFireTime
mod 33) = 0) and (FMegaRulez
[MR_INVUL
] < gTime
) then
4198 FModel
.PlaySound(MODELSOUND_PAIN
, 1, FObj
.X
, FObj
.Y
);
4199 if (FFireTime
= 0) and g_Game_IsNet
and g_Game_IsServer
then
4200 MH_SEND_PlayerStats(FUID
);
4204 if FDamageBuffer
> 0 then
4206 if FDamageBuffer
>= 9 then
4210 if FDamageBuffer
< 30 then i
:= 9
4211 else if FDamageBuffer
< 100 then i
:= 18
4215 ii
:= Round(FDamageBuffer
*FHealth
/ nonz(FArmor
*(3/4)+FHealth
));
4216 FArmor
:= FArmor
-(FDamageBuffer
-ii
);
4217 FHealth
:= FHealth
-ii
;
4220 FHealth
:= FHealth
+FArmor
;
4225 if FHealth
<= 0 then
4226 if FHealth
> -30 then Kill(K_SIMPLEKILL
, FLastSpawnerUID
, FLastHit
)
4227 else if FHealth
> -50 then Kill(K_HARDKILL
, FLastSpawnerUID
, FLastHit
)
4228 else Kill(K_EXTRAHARDKILL
, FLastSpawnerUID
, FLastHit
);
4230 if FAlive
and ((FLastHit
<> HIT_FLAME
) or (FFireTime
<= 0)) then
4232 if FDamageBuffer
<= 20 then FModel
.PlaySound(MODELSOUND_PAIN
, 1, FObj
.X
, FObj
.Y
)
4233 else if FDamageBuffer
<= 55 then FModel
.PlaySound(MODELSOUND_PAIN
, 2, FObj
.X
, FObj
.Y
)
4234 else if FDamageBuffer
<= 120 then FModel
.PlaySound(MODELSOUND_PAIN
, 3, FObj
.X
, FObj
.Y
)
4235 else FModel
.PlaySound(MODELSOUND_PAIN
, 4, FObj
.X
, FObj
.Y
);
4242 end; // if FAlive then ...
4244 if (FActionAnim
= A_PAIN
) and (FModel
.Animation
<> A_PAIN
) then
4246 FModel
.ChangeAnimation(FActionAnim
, FActionForce
);
4247 FModel
.AnimState
.MinLength
:= i
;
4248 end else FModel
.ChangeAnimation(FActionAnim
, FActionForce
and (FModel
.Animation
<> A_STAND
));
4250 if (FModel
.AnimState
.Played
or ((not FActionChanged
) and (FModel
.Animation
= A_WALK
)))
4251 then SetAction(A_STAND
, True);
4253 if not ((FModel
.Animation
= A_WALK
) and (Abs(FObj
.Vel
.X
) < 4) and not FModel
.GetFire()) then FModel
.Update
;
4255 for b
:= Low(FKeys
) to High(FKeys
) do
4256 if FKeys
[b
].Time
= 0 then FKeys
[b
].Pressed
:= False else Dec(FKeys
[b
].Time
);
4260 procedure TPlayer
.getMapBox (out x
, y
, w
, h
: Integer); inline;
4262 x
:= FObj
.X
+PLAYER_RECT
.X
;
4263 y
:= FObj
.Y
+PLAYER_RECT
.Y
;
4264 w
:= PLAYER_RECT
.Width
;
4265 h
:= PLAYER_RECT
.Height
;
4269 procedure TPlayer
.moveBy (dx
, dy
: Integer); inline;
4271 if (dx
<> 0) or (dy
<> 0) then
4280 function TPlayer
.Collide(X
, Y
: Integer; Width
, Height
: Word): Boolean;
4282 Result
:= g_Collide(FObj
.X
+PLAYER_RECT
.X
,
4283 FObj
.Y
+PLAYER_RECT
.Y
,
4290 function TPlayer
.Collide(Panel
: TPanel
): Boolean;
4292 Result
:= g_Collide(FObj
.X
+PLAYER_RECT
.X
,
4293 FObj
.Y
+PLAYER_RECT
.Y
,
4297 Panel
.Width
, Panel
.Height
);
4300 function TPlayer
.Collide(X
, Y
: Integer): Boolean;
4302 X
:= X
-FObj
.X
-PLAYER_RECT
.X
;
4303 Y
:= Y
-FObj
.Y
-PLAYER_RECT
.Y
;
4304 Result
:= (x
>= 0) and (x
<= PLAYER_RECT
.Width
) and
4305 (y
>= 0) and (y
<= PLAYER_RECT
.Height
);
4308 function g_Player_ValidName(Name
: string): Boolean;
4314 if gPlayers
= nil then Exit
;
4316 for a
:= 0 to High(gPlayers
) do
4317 if gPlayers
[a
] <> nil then
4318 if LowerCase(Name
) = LowerCase(gPlayers
[a
].FName
) then
4325 procedure TPlayer
.SetDirection(Direction
: TDirection
);
4329 d
:= FModel
.Direction
;
4331 FModel
.Direction
:= Direction
;
4332 if d
<> Direction
then FModel
.ChangeAnimation(FModel
.Animation
, True);
4334 FDirection
:= Direction
;
4337 function TPlayer
.GetKeys(): Byte;
4341 if R_KEY_RED
in FRulez
then Result
:= KEY_RED
;
4342 if R_KEY_GREEN
in FRulez
then Result
:= Result
or KEY_GREEN
;
4343 if R_KEY_BLUE
in FRulez
then Result
:= Result
or KEY_BLUE
;
4345 if FTeam
= TEAM_RED
then Result
:= Result
or KEY_REDTEAM
;
4346 if FTeam
= TEAM_BLUE
then Result
:= Result
or KEY_BLUETEAM
;
4349 procedure TPlayer
.Use();
4353 if FTime
[T_USE
] > gTime
then Exit
;
4355 g_Triggers_PressR(FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+PLAYER_RECT
.Y
, PLAYER_RECT
.Width
,
4356 PLAYER_RECT
.Height
, FUID
, ACTIVATE_PLAYERPRESS
);
4358 for a
:= 0 to High(gPlayers
) do
4359 if (gPlayers
[a
] <> nil) and (gPlayers
[a
] <> Self
) and
4360 gPlayers
[a
].alive
and SameTeam(FUID
, gPlayers
[a
].FUID
) and
4361 g_Obj_Collide(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
,
4362 FObj
.Rect
.Width
, FObj
.Rect
.Height
, @gPlayers
[a
].FObj
) then
4364 gPlayers
[a
].Touch();
4365 if g_Game_IsNet
and g_Game_IsServer
then
4366 MH_SEND_GameEvent(NET_EV_PLAYER_TOUCH
, gPlayers
[a
].FUID
);
4369 FTime
[T_USE
] := gTime
+120;
4372 procedure TPlayer
.NetFire(Wpn
: Byte; X
, Y
, AX
, AY
: Integer; WID
: Integer = -1);
4375 visible
: Boolean = True;
4376 WX
, WY
, XD
, YD
: Integer;
4388 if R_BERSERK
in FRulez
then
4390 //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
4391 locobj
.X
:= FObj
.X
+FObj
.Rect
.X
;
4392 locobj
.Y
:= FObj
.Y
+FObj
.Rect
.Y
;
4395 locobj
.rect
.Width
:= 39;
4396 locobj
.rect
.Height
:= 52;
4397 locobj
.Vel
.X
:= (xd
-wx
) div 2;
4398 locobj
.Vel
.Y
:= (yd
-wy
) div 2;
4399 locobj
.Accel
.X
:= xd
-wx
;
4400 locobj
.Accel
.y
:= yd
-wy
;
4402 if g_Weapon_Hit(@locobj
, 50, FUID
, HIT_SOME
) <> 0 then
4403 g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj
.X
, FObj
.Y
)
4405 g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj
.X
, FObj
.Y
);
4409 FPain
:= min(FPain
+ 25, 50);
4411 g_Weapon_punch(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
, 3, FUID
);
4416 if g_Weapon_chainsaw(FObj
.X
+FObj
.Rect
.X
, FObj
.Y
+FObj
.Rect
.Y
,
4417 IfThen(gGameSettings
.GameMode
in [GM_DM
, GM_TDM
, GM_CTF
], 9, 3), FUID
) <> 0 then
4419 FSawSoundSelect
.Stop();
4421 FSawSoundHit
.PlayAt(FObj
.X
, FObj
.Y
);
4423 else if not FSawSoundHit
.IsPlaying() then
4425 FSawSoundSelect
.Stop();
4426 FSawSound
.PlayAt(FObj
.X
, FObj
.Y
);
4432 g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', GameX
, Gamey
);
4433 FFireAngle
:= FAngle
;
4434 {$IFDEF ENABLE_SHELLS}
4435 g_Shells_Create(GameX
+ PLAYER_RECT_CX
, GameY
+ PLAYER_RECT_CX
, GameVelX
, GameVelY
- 2, SHELL_BULLET
);
4441 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex
, Gamey
);
4442 FFireAngle
:= FAngle
;
4443 {$IFDEF ENABLE_SHELLS}
4445 FShellType
:= SHELL_SHELL
;
4451 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', Gamex
, Gamey
);
4452 FFireAngle
:= FAngle
;
4453 {$IFDEF ENABLE_SHELLS}
4455 FShellType
:= SHELL_DBLSHELL
;
4461 g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', Gamex
, Gamey
);
4462 FFireAngle
:= FAngle
;
4463 {$IFDEF ENABLE_SHELLS}
4464 g_Shells_Create(GameX
+ PLAYER_RECT_CX
, GameY
+ PLAYER_RECT_CX
, GameVelX
, GameVelY
- 2, SHELL_BULLET
);
4468 WEAPON_ROCKETLAUNCHER
:
4470 g_Weapon_Rocket(wx
, wy
, xd
, yd
, FUID
, WID
);
4471 FFireAngle
:= FAngle
;
4476 g_Weapon_Plasma(wx
, wy
, xd
, yd
, FUID
, WID
);
4477 FFireAngle
:= FAngle
;
4482 g_Weapon_BFGShot(wx
, wy
, xd
, yd
, FUID
, WID
);
4483 FFireAngle
:= FAngle
;
4486 WEAPON_SUPERPULEMET
:
4488 g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex
, Gamey
);
4489 FFireAngle
:= FAngle
;
4490 {$IFDEF ENABLE_SHELLS}
4491 g_Shells_Create(GameX
+ PLAYER_RECT_CX
, GameY
+ PLAYER_RECT_CX
, GameVelX
, GameVelY
- 2, SHELL_SHELL
);
4495 WEAPON_FLAMETHROWER
:
4497 g_Weapon_flame(wx
, wy
, xd
, yd
, FUID
, WID
);
4499 FFireAngle
:= FAngle
;
4503 if not visible
then Exit
;
4505 if (FAngle
= 0) or (FAngle
= 180) then SetAction(A_ATTACK
)
4506 else if (FAngle
= ANGLE_LEFTDOWN
) or (FAngle
= ANGLE_RIGHTDOWN
) then SetAction(A_ATTACKDOWN
)
4507 else if (FAngle
= ANGLE_LEFTUP
) or (FAngle
= ANGLE_RIGHTUP
) then SetAction(A_ATTACKUP
);
4510 procedure TPlayer
.DoLerp(Level
: Integer = 2);
4512 if FObj
.X
<> FXTo
then FObj
.X
:= Lerp(FObj
.X
, FXTo
, Level
);
4513 if FObj
.Y
<> FYTo
then FObj
.Y
:= Lerp(FObj
.Y
, FYTo
, Level
);
4516 procedure TPlayer
.SetLerp(XTo
, YTo
: Integer);
4522 if FJustTeleported
or (NetInterpLevel
< 1) then
4526 if FJustTeleported
then
4528 FObj
.oldX
:= FObj
.X
;
4529 FObj
.oldY
:= FObj
.Y
;
4534 AX
:= Abs(FXTo
- FObj
.X
);
4535 AY
:= Abs(FYTo
- FObj
.Y
);
4536 if (AX
> 32) or (AX
<= NetInterpLevel
) then
4538 if (AY
> 32) or (AY
<= NetInterpLevel
) then
4543 function TPlayer
.FullInLift(XInc
, YInc
: Integer): Integer;
4545 if g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
,
4546 PLAYER_RECT
.Width
, PLAYER_RECT
.Height
-8,
4547 PANEL_LIFTUP
, False) then Result
:= -1
4549 if g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
,
4550 PLAYER_RECT
.Width
, PLAYER_RECT
.Height
-8,
4551 PANEL_LIFTDOWN
, False) then Result
:= 1
4555 function TPlayer
.GetFlag(Flag
: Byte): Boolean;
4562 if Flag
= FLAG_NONE
then
4565 if not g_Game_IsServer
then Exit
;
4567 // Принес чужой флаг на свою базу:
4568 if (Flag
= FTeam
) and
4569 (gFlags
[Flag
].State
= FLAG_STATE_NORMAL
) and
4570 (FFlag
<> FLAG_NONE
) then
4572 if FFlag
= FLAG_RED
then
4573 s
:= _lc
[I_PLAYER_FLAG_RED
]
4575 s
:= _lc
[I_PLAYER_FLAG_BLUE
];
4577 evtype
:= FLAG_STATE_SCORED
;
4579 ts
:= Format('%.4d', [gFlags
[FFlag
].CaptureTime
]);
4580 Insert('.', ts
, Length(ts
) + 1 - 3);
4581 g_Console_Add(Format(_lc
[I_PLAYER_FLAG_CAPTURE
], [FName
, s
, ts
]), True);
4583 g_Map_ResetFlag(FFlag
);
4584 g_Game_Message(Format(_lc
[I_MESSAGE_FLAG_CAPTURE
], [AnsiUpperCase(s
)]), 144);
4586 if ((Self
= gPlayer1
) or (Self
= gPlayer2
)
4587 or ((gPlayer1
<> nil) and (gPlayer1
.Team
= FTeam
))
4588 or ((gPlayer2
<> nil) and (gPlayer2
.Team
= FTeam
))) then
4593 if not sound_cap_flag
[a
].IsPlaying() then
4594 sound_cap_flag
[a
].Play();
4596 gTeamStat
[FTeam
].Score
+= 1;
4599 if g_Game_IsNet
then
4601 MH_SEND_FlagEvent(evtype
, FFlag
, FUID
, False);
4605 gFlags
[FFlag
].CaptureTime
:= 0;
4610 // Подобрал свой флаг - вернул его на базу:
4611 if (Flag
= FTeam
) and
4612 (gFlags
[Flag
].State
= FLAG_STATE_DROPPED
) then
4614 if Flag
= FLAG_RED
then
4615 s
:= _lc
[I_PLAYER_FLAG_RED
]
4617 s
:= _lc
[I_PLAYER_FLAG_BLUE
];
4619 evtype
:= FLAG_STATE_RETURNED
;
4620 gFlags
[Flag
].CaptureTime
:= 0;
4622 g_Console_Add(Format(_lc
[I_PLAYER_FLAG_RETURN
], [FName
, s
]), True);
4624 g_Map_ResetFlag(Flag
);
4625 g_Game_Message(Format(_lc
[I_MESSAGE_FLAG_RETURN
], [AnsiUpperCase(s
)]), 144);
4627 if ((Self
= gPlayer1
) or (Self
= gPlayer2
)
4628 or ((gPlayer1
<> nil) and (gPlayer1
.Team
= FTeam
))
4629 or ((gPlayer2
<> nil) and (gPlayer2
.Team
= FTeam
))) then
4634 if not sound_ret_flag
[a
].IsPlaying() then
4635 sound_ret_flag
[a
].Play();
4638 if g_Game_IsNet
then
4640 MH_SEND_FlagEvent(evtype
, Flag
, FUID
, False);
4646 // Подобрал чужой флаг:
4647 if (Flag
<> FTeam
) and (FTime
[T_FLAGCAP
] <= gTime
) then
4651 if Flag
= FLAG_RED
then
4652 s
:= _lc
[I_PLAYER_FLAG_RED
]
4654 s
:= _lc
[I_PLAYER_FLAG_BLUE
];
4656 evtype
:= FLAG_STATE_CAPTURED
;
4658 g_Console_Add(Format(_lc
[I_PLAYER_FLAG_GET
], [FName
, s
]), True);
4660 g_Game_Message(Format(_lc
[I_MESSAGE_FLAG_GET
], [AnsiUpperCase(s
)]), 144);
4662 gFlags
[Flag
].State
:= FLAG_STATE_CAPTURED
;
4664 if ((Self
= gPlayer1
) or (Self
= gPlayer2
)
4665 or ((gPlayer1
<> nil) and (gPlayer1
.Team
= FTeam
))
4666 or ((gPlayer2
<> nil) and (gPlayer2
.Team
= FTeam
))) then
4671 if not sound_get_flag
[a
].IsPlaying() then
4672 sound_get_flag
[a
].Play();
4675 if g_Game_IsNet
then
4677 MH_SEND_FlagEvent(evtype
, Flag
, FUID
, False);
4683 procedure TPlayer
.SetFlag(Flag
: Byte);
4686 if FModel
<> nil then
4687 FModel
.SetFlag(FFlag
);
4690 function TPlayer
.TryDropFlag(): Boolean;
4692 if LongBool(gGameSettings
.Options
and GAME_OPTION_ALLOWDROPFLAG
) then
4693 Result
:= DropFlag(False, LongBool(gGameSettings
.Options
and GAME_OPTION_THROWFLAG
))
4698 function TPlayer
.DropFlag(Silent
: Boolean = True; DoThrow
: Boolean = False): Boolean;
4705 if (not g_Game_IsServer
) or (FFlag
= FLAG_NONE
) then
4707 FTime
[T_FLAGCAP
] := gTime
+ 2000;
4708 with gFlags
[FFlag
] do
4712 Direction
:= FDirection
;
4713 State
:= FLAG_STATE_DROPPED
;
4717 xv
:= FObj
.Vel
.X
+ IfThen(Direction
= TDirection
.D_RIGHT
, 10, -10);
4718 yv
:= FObj
.Vel
.Y
- 2;
4722 xv
:= (FObj
.Vel
.X
div 2);
4723 yv
:= (FObj
.Vel
.Y
div 2) - 2;
4725 g_Obj_Push(@Obj
, xv
, yv
);
4727 positionChanged(); // this updates spatial accelerators
4729 if FFlag
= FLAG_RED
then
4730 s
:= _lc
[I_PLAYER_FLAG_RED
]
4732 s
:= _lc
[I_PLAYER_FLAG_BLUE
];
4734 g_Console_Add(Format(_lc
[I_PLAYER_FLAG_DROP
], [FName
, s
]), True);
4735 g_Game_Message(Format(_lc
[I_MESSAGE_FLAG_DROP
], [AnsiUpperCase(s
)]), 144);
4737 if ((Self
= gPlayer1
) or (Self
= gPlayer2
)
4738 or ((gPlayer1
<> nil) and (gPlayer1
.Team
= FTeam
))
4739 or ((gPlayer2
<> nil) and (gPlayer2
.Team
= FTeam
))) then
4744 if (not Silent
) and (not sound_lost_flag
[a
].IsPlaying()) then
4745 sound_lost_flag
[a
].Play();
4747 if g_Game_IsNet
then
4748 MH_SEND_FlagEvent(FLAG_STATE_DROPPED
, Flag
, FUID
, False);
4754 procedure TPlayer
.GetSecret();
4756 if (self
= gPlayer1
) or (self
= gPlayer2
) then
4758 g_Console_Add(Format(_lc
[I_PLAYER_SECRET
], [FName
]), True);
4759 g_Sound_PlayEx('SOUND_GAME_SECRET');
4764 procedure TPlayer
.PressKey(Key
: Byte; Time
: Word = 1);
4766 Assert(Key
<= High(FKeys
));
4768 FKeys
[Key
].Pressed
:= True;
4769 FKeys
[Key
].Time
:= Time
;
4772 function TPlayer
.IsKeyPressed(K
: Byte): Boolean;
4774 Result
:= FKeys
[K
].Pressed
;
4777 procedure TPlayer
.ReleaseKeys();
4781 for a
:= Low(FKeys
) to High(FKeys
) do
4783 FKeys
[a
].Pressed
:= False;
4788 procedure TPlayer
.OnDamage(Angle
: SmallInt);
4792 function TPlayer
.firediry(): Integer;
4794 if FKeys
[KEY_UP
].Pressed
then Result
:= -42
4795 else if FKeys
[KEY_DOWN
].Pressed
then Result
:= 19
4799 procedure TPlayer
.RememberState();
4802 SavedState
: TPlayerSavedState
;
4804 SavedState
.Health
:= FHealth
;
4805 SavedState
.Armor
:= FArmor
;
4806 SavedState
.Air
:= FAir
;
4807 SavedState
.JetFuel
:= FJetFuel
;
4808 SavedState
.CurrWeap
:= FCurrWeap
;
4809 SavedState
.NextWeap
:= FNextWeap
;
4810 SavedState
.NextWeapDelay
:= FNextWeapDelay
;
4811 for i
:= Low(FWeapon
) to High(FWeapon
) do
4812 SavedState
.Weapon
[i
] := FWeapon
[i
];
4813 for i
:= Low(FAmmo
) to High(FAmmo
) do
4814 SavedState
.Ammo
[i
] := FAmmo
[i
];
4815 for i
:= Low(FMaxAmmo
) to High(FMaxAmmo
) do
4816 SavedState
.MaxAmmo
[i
] := FMaxAmmo
[i
];
4817 SavedState
.Rulez
:= FRulez
- [R_KEY_RED
, R_KEY_GREEN
, R_KEY_BLUE
];
4819 FSavedStateNum
:= -1;
4820 for i
:= Low(SavedStates
) to High(SavedStates
) do
4821 if not SavedStates
[i
].Used
then
4823 FSavedStateNum
:= i
;
4826 if FSavedStateNum
< 0 then
4828 SetLength(SavedStates
, Length(SavedStates
) + 1);
4829 FSavedStateNum
:= High(SavedStates
);
4832 SavedState
.Used
:= True;
4833 SavedStates
[FSavedStateNum
] := SavedState
;
4836 procedure TPlayer
.RecallState();
4839 SavedState
: TPlayerSavedState
;
4841 if(FSavedStateNum
< 0) or (FSavedStateNum
> High(SavedStates
)) then
4844 SavedState
:= SavedStates
[FSavedStateNum
];
4845 SavedStates
[FSavedStateNum
].Used
:= False;
4846 FSavedStateNum
:= -1;
4848 FHealth
:= SavedState
.Health
;
4849 FArmor
:= SavedState
.Armor
;
4850 FAir
:= SavedState
.Air
;
4851 FJetFuel
:= SavedState
.JetFuel
;
4852 FCurrWeap
:= SavedState
.CurrWeap
;
4853 FNextWeap
:= SavedState
.NextWeap
;
4854 FNextWeapDelay
:= SavedState
.NextWeapDelay
;
4855 for i
:= Low(FWeapon
) to High(FWeapon
) do
4856 FWeapon
[i
] := SavedState
.Weapon
[i
];
4857 for i
:= Low(FAmmo
) to High(FAmmo
) do
4858 FAmmo
[i
] := SavedState
.Ammo
[i
];
4859 for i
:= Low(FMaxAmmo
) to High(FMaxAmmo
) do
4860 FMaxAmmo
[i
] := SavedState
.MaxAmmo
[i
];
4861 FRulez
:= SavedState
.Rulez
;
4863 if gGameSettings
.GameType
= GT_SERVER
then
4864 MH_SEND_PlayerStats(FUID
);
4867 procedure TPlayer
.SaveState (st
: TStream
);
4873 utils
.writeSign(st
, 'PLYR');
4874 utils
.writeInt(st
, Byte(PLR_SAVE_VERSION
)); // version
4876 utils
.writeBool(st
, FIamBot
);
4878 utils
.writeInt(st
, Word(FUID
));
4880 utils
.writeStr(st
, FName
);
4882 utils
.writeInt(st
, Byte(FTeam
));
4884 utils
.writeBool(st
, FAlive
);
4885 // Израсходовал ли все жизни
4886 utils
.writeBool(st
, FNoRespawn
);
4888 if FDirection
= TDirection
.D_LEFT
then b
:= 1 else b
:= 2; // D_RIGHT
4889 utils
.writeInt(st
, Byte(b
));
4891 utils
.writeInt(st
, LongInt(FHealth
));
4892 // Коэффициент инвалидности
4893 utils
.writeInt(st
, LongInt(FHandicap
));
4895 utils
.writeInt(st
, Byte(FLives
));
4897 utils
.writeInt(st
, LongInt(FArmor
));
4899 utils
.writeInt(st
, LongInt(FAir
));
4901 utils
.writeInt(st
, LongInt(FJetFuel
));
4903 utils
.writeInt(st
, LongInt(FPain
));
4905 utils
.writeInt(st
, LongInt(FKills
));
4907 utils
.writeInt(st
, LongInt(FMonsterKills
));
4909 utils
.writeInt(st
, LongInt(FFrags
));
4911 utils
.writeInt(st
, Byte(FFragCombo
));
4912 // Время последнего фрага
4913 utils
.writeInt(st
, LongWord(FLastFrag
));
4915 utils
.writeInt(st
, LongInt(FDeath
));
4917 utils
.writeInt(st
, Byte(FFlag
));
4919 utils
.writeInt(st
, LongInt(FSecrets
));
4921 utils
.writeInt(st
, Byte(FCurrWeap
));
4923 utils
.writeInt(st
, Word(FNextWeap
));
4925 utils
.writeInt(st
, Byte(FNextWeapDelay
));
4926 // Время зарядки BFG
4927 utils
.writeInt(st
, SmallInt(FBFGFireCounter
));
4929 utils
.writeInt(st
, LongInt(FDamageBuffer
));
4930 // Последний ударивший
4931 utils
.writeInt(st
, Word(FLastSpawnerUID
));
4932 // Тип последнего полученного урона
4933 utils
.writeInt(st
, Byte(FLastHit
));
4935 Obj_SaveState(st
, @FObj
);
4936 // Текущее количество патронов
4937 for i
:= A_BULLETS
to A_HIGH
do utils
.writeInt(st
, Word(FAmmo
[i
]));
4938 // Максимальное количество патронов
4939 for i
:= A_BULLETS
to A_HIGH
do utils
.writeInt(st
, Word(FMaxAmmo
[i
]));
4941 for i
:= WP_FIRST
to WP_LAST
do utils
.writeBool(st
, FWeapon
[i
]);
4942 // Время перезарядки оружия
4943 for i
:= WP_FIRST
to WP_LAST
do utils
.writeInt(st
, Word(FReloading
[i
]));
4945 utils
.writeBool(st
, (R_ITEM_BACKPACK
in FRulez
));
4946 // Наличие красного ключа
4947 utils
.writeBool(st
, (R_KEY_RED
in FRulez
));
4948 // Наличие зеленого ключа
4949 utils
.writeBool(st
, (R_KEY_GREEN
in FRulez
));
4950 // Наличие синего ключа
4951 utils
.writeBool(st
, (R_KEY_BLUE
in FRulez
));
4953 utils
.writeBool(st
, (R_BERSERK
in FRulez
));
4954 // Время действия специальных предметов
4955 for i
:= MR_SUIT
to MR_MAX
do utils
.writeInt(st
, LongWord(FMegaRulez
[i
]));
4956 // Время до повторного респауна, смены оружия, исользования, захвата флага
4957 for i
:= T_RESPAWN
to T_FLAGCAP
do utils
.writeInt(st
, LongWord(FTime
[i
]));
4959 utils
.writeStr(st
, FModel
.GetName());
4961 utils
.writeInt(st
, Byte(FColor
.R
));
4962 utils
.writeInt(st
, Byte(FColor
.G
));
4963 utils
.writeInt(st
, Byte(FColor
.B
));
4967 procedure TPlayer
.LoadState (st
: TStream
);
4976 if not utils
.checkSign(st
, 'PLYR') then raise XStreamError
.Create('invalid player signature');
4977 if (utils
.readByte(st
) <> PLR_SAVE_VERSION
) then raise XStreamError
.Create('invalid player version');
4979 FIamBot
:= utils
.readBool(st
);
4981 FUID
:= utils
.readWord(st
);
4983 str
:= utils
.readStr(st
);
4984 if (self
<> gPlayer1
) and (self
<> gPlayer2
) then FName
:= str
;
4986 FTeam
:= utils
.readByte(st
);
4988 FAlive
:= utils
.readBool(st
);
4989 // Израсходовал ли все жизни
4990 FNoRespawn
:= utils
.readBool(st
);
4992 b
:= utils
.readByte(st
);
4993 if b
= 1 then FDirection
:= TDirection
.D_LEFT
else FDirection
:= TDirection
.D_RIGHT
; // b = 2
4995 FHealth
:= utils
.readLongInt(st
);
4996 // Коэффициент инвалидности
4997 FHandicap
:= utils
.readLongInt(st
);
4999 FLives
:= utils
.readByte(st
);
5001 FArmor
:= utils
.readLongInt(st
);
5003 FAir
:= utils
.readLongInt(st
);
5005 FJetFuel
:= utils
.readLongInt(st
);
5007 FPain
:= utils
.readLongInt(st
);
5009 FKills
:= utils
.readLongInt(st
);
5011 FMonsterKills
:= utils
.readLongInt(st
);
5013 FFrags
:= utils
.readLongInt(st
);
5015 FFragCombo
:= utils
.readByte(st
);
5016 // Время последнего фрага
5017 FLastFrag
:= utils
.readLongWord(st
);
5019 FDeath
:= utils
.readLongInt(st
);
5021 FFlag
:= utils
.readByte(st
);
5023 FSecrets
:= utils
.readLongInt(st
);
5025 FCurrWeap
:= utils
.readByte(st
);
5027 FNextWeap
:= utils
.readWord(st
);
5029 FNextWeapDelay
:= utils
.readByte(st
);
5030 // Время зарядки BFG
5031 FBFGFireCounter
:= utils
.readSmallInt(st
);
5033 FDamageBuffer
:= utils
.readLongInt(st
);
5034 // Последний ударивший
5035 FLastSpawnerUID
:= utils
.readWord(st
);
5036 // Тип последнего полученного урона
5037 FLastHit
:= utils
.readByte(st
);
5039 Obj_LoadState(@FObj
, st
);
5040 // Текущее количество патронов
5041 for i
:= A_BULLETS
to A_HIGH
do FAmmo
[i
] := utils
.readWord(st
);
5042 // Максимальное количество патронов
5043 for i
:= A_BULLETS
to A_HIGH
do FMaxAmmo
[i
] := utils
.readWord(st
);
5045 for i
:= WP_FIRST
to WP_LAST
do FWeapon
[i
] := utils
.readBool(st
);
5046 // Время перезарядки оружия
5047 for i
:= WP_FIRST
to WP_LAST
do FReloading
[i
] := utils
.readWord(st
);
5049 if utils
.readBool(st
) then Include(FRulez
, R_ITEM_BACKPACK
);
5050 // Наличие красного ключа
5051 if utils
.readBool(st
) then Include(FRulez
, R_KEY_RED
);
5052 // Наличие зеленого ключа
5053 if utils
.readBool(st
) then Include(FRulez
, R_KEY_GREEN
);
5054 // Наличие синего ключа
5055 if utils
.readBool(st
) then Include(FRulez
, R_KEY_BLUE
);
5057 if utils
.readBool(st
) then Include(FRulez
, R_BERSERK
);
5058 // Время действия специальных предметов
5059 for i
:= MR_SUIT
to MR_MAX
do FMegaRulez
[i
] := utils
.readLongWord(st
);
5060 // Время до повторного респауна, смены оружия, исользования, захвата флага
5061 for i
:= T_RESPAWN
to T_FLAGCAP
do FTime
[i
] := utils
.readLongWord(st
);
5063 str
:= utils
.readStr(st
);
5065 FColor
.R
:= utils
.readByte(st
);
5066 FColor
.G
:= utils
.readByte(st
);
5067 FColor
.B
:= utils
.readByte(st
);
5068 if (self
= gPlayer1
) then
5070 str
:= gPlayer1Settings
.Model
;
5071 FColor
:= gPlayer1Settings
.Color
;
5073 else if (self
= gPlayer2
) then
5075 str
:= gPlayer2Settings
.Model
;
5076 FColor
:= gPlayer2Settings
.Color
;
5078 // Обновляем модель игрока
5080 if gGameSettings
.GameMode
in [GM_TDM
, GM_CTF
] then
5081 FModel
.Color
:= TEAMCOLOR
[FTeam
]
5083 FModel
.Color
:= FColor
;
5087 procedure TPlayer
.AllRulez(Health
: Boolean);
5093 FHealth
:= PLAYER_HP_LIMIT
;
5094 FArmor
:= PLAYER_AP_LIMIT
;
5098 for a
:= WP_FIRST
to WP_LAST
do FWeapon
[a
] := True;
5099 for a
:= A_BULLETS
to A_HIGH
do FAmmo
[a
] := 30000;
5100 FRulez
:= FRulez
+[R_KEY_RED
, R_KEY_GREEN
, R_KEY_BLUE
];
5103 procedure TPlayer
.RestoreHealthArmor();
5105 FHealth
:= PLAYER_HP_LIMIT
;
5106 FArmor
:= PLAYER_AP_LIMIT
;
5109 procedure TPlayer
.FragCombo();
5113 if (gGameSettings
.GameMode
in [GM_COOP
, GM_SINGLE
]) or g_Game_IsClient
then
5115 if gTime
- FLastFrag
< FRAG_COMBO_TIME
then
5117 if FFragCombo
< 5 then
5119 Param
:= FUID
or (FFragCombo
shl 16);
5120 if (FComboEvnt
>= Low(gDelayedEvents
)) and
5121 (FComboEvnt
<= High(gDelayedEvents
)) and
5122 gDelayedEvents
[FComboEvnt
].Pending
and
5123 (gDelayedEvents
[FComboEvnt
].DEType
= DE_KILLCOMBO
) and
5124 (gDelayedEvents
[FComboEvnt
].DENum
and $FFFF = FUID
) then
5126 gDelayedEvents
[FComboEvnt
].Time
:= gTime
+ 500;
5127 gDelayedEvents
[FComboEvnt
].DENum
:= Param
;
5130 FComboEvnt
:= g_Game_DelayEvent(DE_KILLCOMBO
, 500, Param
);
5138 procedure TPlayer
.GiveItem(ItemType
: Byte);
5142 if FMegaRulez
[MR_SUIT
] < gTime
+PLAYER_SUIT_TIME
then
5144 FMegaRulez
[MR_SUIT
] := gTime
+PLAYER_SUIT_TIME
;
5148 if FAir
< AIR_MAX
then
5155 if not (R_BERSERK
in FRulez
) then
5157 Include(FRulez
, R_BERSERK
);
5158 if FBFGFireCounter
< 1 then
5160 FCurrWeap
:= WEAPON_KASTET
;
5162 FModel
.SetWeapon(WEAPON_KASTET
);
5166 FBerserk
:= gTime
+30000;
5168 if FHealth
< PLAYER_HP_SOFT
then
5170 FHealth
:= PLAYER_HP_SOFT
;
5171 FBerserk
:= gTime
+30000;
5176 if FMegaRulez
[MR_INVUL
] < gTime
+PLAYER_INVUL_TIME
then
5178 FMegaRulez
[MR_INVUL
] := gTime
+PLAYER_INVUL_TIME
;
5183 if FMegaRulez
[MR_INVIS
] < gTime
+PLAYER_INVIS_TIME
then
5185 FMegaRulez
[MR_INVIS
] := gTime
+PLAYER_INVIS_TIME
;
5189 if FJetFuel
< JET_MAX
then
5191 FJetFuel
:= JET_MAX
;
5194 ITEM_MEDKIT_SMALL
: if FHealth
< PLAYER_HP_SOFT
then IncMax(FHealth
, 10, PLAYER_HP_SOFT
);
5195 ITEM_MEDKIT_LARGE
: if FHealth
< PLAYER_HP_SOFT
then IncMax(FHealth
, 25, PLAYER_HP_SOFT
);
5197 ITEM_ARMOR_GREEN
: if FArmor
< PLAYER_AP_SOFT
then FArmor
:= PLAYER_AP_SOFT
;
5198 ITEM_ARMOR_BLUE
: if FArmor
< PLAYER_AP_LIMIT
then FArmor
:= PLAYER_AP_LIMIT
;
5200 ITEM_SPHERE_BLUE
: if FHealth
< PLAYER_HP_LIMIT
then IncMax(FHealth
, 100, PLAYER_HP_LIMIT
);
5202 if (FHealth
< PLAYER_HP_LIMIT
) or (FArmor
< PLAYER_AP_LIMIT
) then
5204 if FHealth
< PLAYER_HP_LIMIT
then FHealth
:= PLAYER_HP_LIMIT
;
5205 if FArmor
< PLAYER_AP_LIMIT
then FArmor
:= PLAYER_AP_LIMIT
;
5208 ITEM_WEAPON_SAW
: FWeapon
[WEAPON_SAW
] := True;
5209 ITEM_WEAPON_SHOTGUN1
: FWeapon
[WEAPON_SHOTGUN1
] := True;
5210 ITEM_WEAPON_SHOTGUN2
: FWeapon
[WEAPON_SHOTGUN2
] := True;
5211 ITEM_WEAPON_CHAINGUN
: FWeapon
[WEAPON_CHAINGUN
] := True;
5212 ITEM_WEAPON_ROCKETLAUNCHER
: FWeapon
[WEAPON_ROCKETLAUNCHER
] := True;
5213 ITEM_WEAPON_PLASMA
: FWeapon
[WEAPON_PLASMA
] := True;
5214 ITEM_WEAPON_BFG
: FWeapon
[WEAPON_BFG
] := True;
5215 ITEM_WEAPON_SUPERPULEMET
: FWeapon
[WEAPON_SUPERPULEMET
] := True;
5216 ITEM_WEAPON_FLAMETHROWER
: FWeapon
[WEAPON_FLAMETHROWER
] := True;
5218 ITEM_AMMO_BULLETS
: if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then IncMax(FAmmo
[A_BULLETS
], 10, FMaxAmmo
[A_BULLETS
]);
5219 ITEM_AMMO_BULLETS_BOX
: if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then IncMax(FAmmo
[A_BULLETS
], 50, FMaxAmmo
[A_BULLETS
]);
5220 ITEM_AMMO_SHELLS
: if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
5221 ITEM_AMMO_SHELLS_BOX
: if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then IncMax(FAmmo
[A_SHELLS
], 25, FMaxAmmo
[A_SHELLS
]);
5222 ITEM_AMMO_ROCKET
: if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then IncMax(FAmmo
[A_ROCKETS
], 1, FMaxAmmo
[A_ROCKETS
]);
5223 ITEM_AMMO_ROCKET_BOX
: if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then IncMax(FAmmo
[A_ROCKETS
], 5, FMaxAmmo
[A_ROCKETS
]);
5224 ITEM_AMMO_CELL
: if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
5225 ITEM_AMMO_CELL_BIG
: if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then IncMax(FAmmo
[A_CELLS
], 100, FMaxAmmo
[A_CELLS
]);
5226 ITEM_AMMO_FUELCAN
: if FAmmo
[A_FUEL
] < FMaxAmmo
[A_FUEL
] then IncMax(FAmmo
[A_FUEL
], 100, FMaxAmmo
[A_FUEL
]);
5229 if (FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
]) or
5230 (FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
]) or
5231 (FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
]) or
5232 (FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
]) or
5233 (FMaxAmmo
[A_FUEL
] < AmmoLimits
[1, A_FUEL
]) then
5235 FMaxAmmo
[A_BULLETS
] := AmmoLimits
[1, A_BULLETS
];
5236 FMaxAmmo
[A_SHELLS
] := AmmoLimits
[1, A_SHELLS
];
5237 FMaxAmmo
[A_ROCKETS
] := AmmoLimits
[1, A_ROCKETS
];
5238 FMaxAmmo
[A_CELLS
] := AmmoLimits
[1, A_CELLS
];
5239 FMaxAmmo
[A_FUEL
] := AmmoLimits
[1, A_FUEL
];
5241 if FAmmo
[A_BULLETS
] < FMaxAmmo
[A_BULLETS
] then IncMax(FAmmo
[A_BULLETS
], 10, FMaxAmmo
[A_BULLETS
]);
5242 if FAmmo
[A_SHELLS
] < FMaxAmmo
[A_SHELLS
] then IncMax(FAmmo
[A_SHELLS
], 4, FMaxAmmo
[A_SHELLS
]);
5243 if FAmmo
[A_ROCKETS
] < FMaxAmmo
[A_ROCKETS
] then IncMax(FAmmo
[A_ROCKETS
], 1, FMaxAmmo
[A_ROCKETS
]);
5244 if FAmmo
[A_CELLS
] < FMaxAmmo
[A_CELLS
] then IncMax(FAmmo
[A_CELLS
], 40, FMaxAmmo
[A_CELLS
]);
5246 FRulez
:= FRulez
+ [R_ITEM_BACKPACK
];
5249 ITEM_KEY_RED
: if not (R_KEY_RED
in FRulez
) then Include(FRulez
, R_KEY_RED
);
5250 ITEM_KEY_GREEN
: if not (R_KEY_GREEN
in FRulez
) then Include(FRulez
, R_KEY_GREEN
);
5251 ITEM_KEY_BLUE
: if not (R_KEY_BLUE
in FRulez
) then Include(FRulez
, R_KEY_BLUE
);
5253 ITEM_BOTTLE
: if FHealth
< PLAYER_HP_LIMIT
then IncMax(FHealth
, 4, PLAYER_HP_LIMIT
);
5254 ITEM_HELMET
: if FArmor
< PLAYER_AP_LIMIT
then IncMax(FArmor
, 5, PLAYER_AP_LIMIT
);
5259 if g_Game_IsNet
and g_Game_IsServer
then
5260 MH_SEND_PlayerStats(FUID
);
5263 procedure TPlayer
.FlySmoke(Times
: DWORD
= 1);
5266 if (Random(5) = 1) and (Times
= 1) then
5269 if BodyInLiquid(0, 0) then
5272 g_GFX_Bubbles(Obj
.X
+Obj
.Rect
.X
+(Obj
.Rect
.Width
div 2)+Random(3)-1,
5273 Obj
.Y
+Obj
.Rect
.Height
+8, 1, 8, 4);
5276 then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj
.X
, FObj
.Y
)
5277 else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj
.X
, FObj
.Y
);
5281 for i
:= 1 to Times
do
5286 Obj
.X
+Obj
.Rect
.X
+Random(Obj
.Rect
.Width
+Times
*2)-(R_GFX_SMOKE_WIDTH
div 2),
5287 Obj
.Y
+Obj
.Rect
.Height
-4+Random(8+Times
*2)
5293 procedure TPlayer
.OnFireFlame(Times
: DWORD
= 1);
5296 if (Random(10) = 1) and (Times
= 1) then
5299 for i
:= 1 to Times
do
5304 Obj
.X
+Obj
.Rect
.X
+Random(Obj
.Rect
.Width
+Times
*2)-(R_GFX_FLAME_WIDTH
div 2),
5305 Obj
.Y
+8+Random(8+Times
*2)
5311 procedure TPlayer
.PauseSounds(Enable
: Boolean);
5313 FSawSound
.Pause(Enable
);
5314 FSawSoundIdle
.Pause(Enable
);
5315 FSawSoundHit
.Pause(Enable
);
5316 FSawSoundSelect
.Pause(Enable
);
5317 FFlameSoundOn
.Pause(Enable
);
5318 FFlameSoundOff
.Pause(Enable
);
5319 FFlameSoundWork
.Pause(Enable
);
5320 FJetSoundFly
.Pause(Enable
);
5321 FJetSoundOn
.Pause(Enable
);
5322 FJetSoundOff
.Pause(Enable
);
5327 constructor TBot
.Create();
5334 FSpectator
:= False;
5341 for a
:= WP_FIRST
to WP_LAST
do
5343 FDifficult
.WeaponPrior
[a
] := WEAPON_PRIOR1
[a
];
5344 FDifficult
.CloseWeaponPrior
[a
] := WEAPON_PRIOR2
[a
];
5345 //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
5349 destructor TBot
.Destroy();
5352 inherited Destroy();
5355 procedure TBot
.Respawn(Silent
: Boolean; Force
: Boolean = False);
5357 inherited Respawn(Silent
, Force
);
5360 FSelectedWeapon
:= FCurrWeap
;
5365 procedure TBot
.UpdateCombat();
5378 TTargetRecord
= array of TTarget
;
5380 function Compare(a
, b
: TTarget
): Integer;
5382 if a
.Line
and not b
.Line
then // A на линии огня
5385 if not a
.Line
and b
.Line
then // B на линии огня
5387 else // И A, и B на линии или не на линии огня
5388 if (a
.Line
and b
.Line
) or ((not a
.Line
) and (not b
.Line
)) then
5390 if a
.Dist
> b
.Dist
then // B ближе
5392 else // A ближе или равноудаленно с B
5395 else // Странно -> A
5400 a
, x1
, y1
, x2
, y2
: Integer;
5401 targets
: TTargetRecord
;
5403 Target
, BestTarget
: TTarget
;
5404 firew
, fireh
: Integer;
5408 vsPlayer
, vsMonster
, ok
: Boolean;
5411 function monsUpdate (mon
: TMonster
): Boolean;
5413 result
:= false; // don't stop
5414 if mon
.alive
and (mon
.MonsterType
<> MONSTER_BARREL
) then
5416 if not TargetOnScreen(mon
.Obj
.X
+mon
.Obj
.Rect
.X
, mon
.Obj
.Y
+mon
.Obj
.Rect
.Y
) then exit
;
5418 x2
:= mon
.Obj
.X
+mon
.Obj
.Rect
.X
+(mon
.Obj
.Rect
.Width
div 2);
5419 y2
:= mon
.Obj
.Y
+mon
.Obj
.Rect
.Y
+(mon
.Obj
.Rect
.Height
div 2);
5421 // Если монстр на экране и не прикрыт стеной
5422 if g_TraceVector(x1
, y1
, x2
, y2
) then
5424 // Добавляем к списку возможных целей
5425 SetLength(targets
, Length(targets
)+1);
5426 with targets
[High(targets
)] do
5433 Rect
:= mon
.Obj
.Rect
;
5434 Dist
:= g_PatchLength(x1
, y1
, x2
, y2
);
5435 Line
:= (y1
+4 < Target
.Y
+ mon
.Obj
.Rect
.Y
+ mon
.Obj
.Rect
.Height
) and
5436 (y1
-4 > Target
.Y
+ mon
.Obj
.Rect
.Y
);
5445 vsPlayer
:= LongBool(gGameSettings
.Options
and GAME_OPTION_BOTVSPLAYER
);
5446 vsMonster
:= LongBool(gGameSettings
.Options
and GAME_OPTION_BOTVSMONSTER
);
5448 // Если текущее оружие не то, что нужно, то меняем:
5449 if FCurrWeap
<> FSelectedWeapon
then
5452 // Если нужно стрелять и нужное оружие, то нажать "Стрелять":
5453 if (GetAIFlag('NEEDFIRE') <> '') and (FCurrWeap
= FSelectedWeapon
) then
5455 RemoveAIFlag('NEEDFIRE');
5458 WEAPON_PLASMA
, WEAPON_SUPERPULEMET
, WEAPON_CHAINGUN
: PressKey(KEY_FIRE
, 20);
5459 WEAPON_SAW
, WEAPON_KASTET
, WEAPON_FLAMETHROWER
: PressKey(KEY_FIRE
, 40);
5460 else PressKey(KEY_FIRE
);
5464 // Координаты ствола:
5465 x1
:= FObj
.X
+ WEAPONPOINT
[FDirection
].X
;
5466 y1
:= FObj
.Y
+ WEAPONPOINT
[FDirection
].Y
;
5468 Target
.UID
:= FTargetUID
;
5471 if Target
.UID
<> 0 then
5472 begin // Цель есть - настраиваем
5473 if (g_GetUIDType(Target
.UID
) = UID_PLAYER
) and
5476 tpla
:= g_Player_Get(Target
.UID
);
5480 if (@FObj
) <> nil then
5487 Target
.cX
:= Target
.X
+ PLAYER_RECT_CX
;
5488 Target
.cY
:= Target
.Y
+ PLAYER_RECT_CY
;
5489 Target
.Rect
:= PLAYER_RECT
;
5490 Target
.Visible
:= g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
);
5491 Target
.Line
:= (y1
+4 < Target
.Y
+PLAYER_RECT
.Y
+PLAYER_RECT
.Height
) and
5492 (y1
-4 > Target
.Y
+PLAYER_RECT
.Y
);
5493 Target
.IsPlayer
:= True;
5497 if (g_GetUIDType(Target
.UID
) = UID_MONSTER
) and
5500 mon
:= g_Monsters_ByUID(Target
.UID
);
5503 Target
.X
:= mon
.Obj
.X
;
5504 Target
.Y
:= mon
.Obj
.Y
;
5506 Target
.cX
:= Target
.X
+ mon
.Obj
.Rect
.X
+ (mon
.Obj
.Rect
.Width
div 2);
5507 Target
.cY
:= Target
.Y
+ mon
.Obj
.Rect
.Y
+ (mon
.Obj
.Rect
.Height
div 2);
5508 Target
.Rect
:= mon
.Obj
.Rect
;
5509 Target
.Visible
:= g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
);
5510 Target
.Line
:= (y1
+4 < Target
.Y
+ mon
.Obj
.Rect
.Y
+ mon
.Obj
.Rect
.Height
) and
5511 (y1
-4 > Target
.Y
+ mon
.Obj
.Rect
.Y
);
5512 Target
.IsPlayer
:= False;
5519 begin // Цели нет - обнуляем
5524 Target
.Visible
:= False;
5525 Target
.Line
:= False;
5526 Target
.IsPlayer
:= False;
5531 // Если цель не видима или не на линии огня, то ищем все возможные цели:
5532 if (not Target
.Line
) or (not Target
.Visible
) then
5536 for a
:= 0 to High(gPlayers
) do
5537 if (gPlayers
[a
] <> nil) and (gPlayers
[a
].alive
) and
5538 (gPlayers
[a
].FUID
<> FUID
) and
5539 (not SameTeam(FUID
, gPlayers
[a
].FUID
)) and
5540 (not gPlayers
[a
].NoTarget
) and
5541 (gPlayers
[a
].FMegaRulez
[MR_INVIS
] < gTime
) then
5543 if not TargetOnScreen(gPlayers
[a
].FObj
.X
+ PLAYER_RECT
.X
,
5544 gPlayers
[a
].FObj
.Y
+ PLAYER_RECT
.Y
) then
5547 x2
:= gPlayers
[a
].FObj
.X
+ PLAYER_RECT_CX
;
5548 y2
:= gPlayers
[a
].FObj
.Y
+ PLAYER_RECT_CY
;
5550 // Если игрок на экране и не прикрыт стеной:
5551 if g_TraceVector(x1
, y1
, x2
, y2
) then
5553 // Добавляем к списку возможных целей:
5554 SetLength(targets
, Length(targets
)+1);
5555 with targets
[High(targets
)] do
5557 UID
:= gPlayers
[a
].FUID
;
5558 X
:= gPlayers
[a
].FObj
.X
;
5559 Y
:= gPlayers
[a
].FObj
.Y
;
5562 Rect
:= PLAYER_RECT
;
5563 Dist
:= g_PatchLength(x1
, y1
, x2
, y2
);
5564 Line
:= (y1
+4 < Target
.Y
+PLAYER_RECT
.Y
+PLAYER_RECT
.Height
) and
5565 (y1
-4 > Target
.Y
+PLAYER_RECT
.Y
);
5573 if vsMonster
then g_Mons_ForEach(monsUpdate
);
5576 // Если есть возможные цели:
5577 // (Выбираем лучшую, меняем оружие и бежим к ней/от нее)
5578 if targets
<> nil then
5580 // Выбираем наилучшую цель:
5581 BestTarget
:= targets
[0];
5582 if Length(targets
) > 1 then
5583 for a
:= 1 to High(targets
) do
5584 if Compare(BestTarget
, targets
[a
]) = 1 then
5585 BestTarget
:= targets
[a
];
5587 // Если лучшая цель "виднее" текущей, то текущая := лучшая:
5588 if ((not Target
.Visible
) and BestTarget
.Visible
and (Target
.UID
<> BestTarget
.UID
)) or
5589 ((not Target
.Line
) and BestTarget
.Line
and BestTarget
.Visible
) then
5591 Target
:= BestTarget
;
5593 if (Healthy() = 3) or ((Healthy() = 2)) then
5594 begin // Если здоровы - догоняем
5595 if ((RunDirection() = TDirection
.D_LEFT
) and (Target
.X
> FObj
.X
)) then
5596 SetAIFlag('GORIGHT', '1');
5597 if ((RunDirection() = TDirection
.D_RIGHT
) and (Target
.X
< FObj
.X
)) then
5598 SetAIFlag('GOLEFT', '1');
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 // Выбираем оружие на основе расстояния и приоритетов:
5609 SelectWeapon(Abs(x1
-Target
.cX
));
5614 // (Догоняем/убегаем, стреляем по направлению к цели)
5615 // (Если цель далеко, то хватит следить за ней)
5616 if Target
.UID
<> 0 then
5618 if not TargetOnScreen(Target
.X
+ Target
.Rect
.X
,
5619 Target
.Y
+ Target
.Rect
.Y
) then
5620 begin // Цель сбежала с "экрана"
5621 if (Healthy() = 3) or ((Healthy() = 2)) then
5622 begin // Если здоровы - догоняем
5623 if ((RunDirection() = TDirection
.D_LEFT
) and (Target
.X
> FObj
.X
)) then
5624 SetAIFlag('GORIGHT', '1');
5625 if ((RunDirection() = TDirection
.D_RIGHT
) and (Target
.X
< FObj
.X
)) then
5626 SetAIFlag('GOLEFT', '1');
5629 begin // Если побиты - забываем о цели и убегаем
5631 if ((RunDirection() = TDirection
.D_LEFT
) and (Target
.X
< FObj
.X
)) then
5632 SetAIFlag('GORIGHT', '1');
5633 if ((RunDirection() = TDirection
.D_RIGHT
) and (Target
.X
> FObj
.X
)) then
5634 SetAIFlag('GOLEFT', '1');
5638 begin // Цель пока на "экране"
5639 // Если цель не загорожена стеной, то отмечаем, когда ее видели:
5640 if g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
) then
5641 FLastVisible
:= gTime
;
5642 // Если разница высот не велика, то догоняем:
5643 if (Abs(FObj
.Y
-Target
.Y
) <= 128) then
5645 if ((RunDirection() = TDirection
.D_LEFT
) and (Target
.X
> FObj
.X
)) then
5646 SetAIFlag('GORIGHT', '1');
5647 if ((RunDirection() = TDirection
.D_RIGHT
) and (Target
.X
< FObj
.X
)) then
5648 SetAIFlag('GOLEFT', '1');
5652 // Выбираем угол вверх:
5653 if FDirection
= TDirection
.D_LEFT
then
5654 angle
:= ANGLE_LEFTUP
5656 angle
:= ANGLE_RIGHTUP
;
5658 firew
:= Trunc(Cos(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
5659 fireh
:= Trunc(Sin(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
5661 // Если при угле вверх можно попасть в приблизительное положение цели:
5662 if g_CollideLine(x1
, y1
, x1
+firew
, y1
+fireh
,
5663 Target
.X
+Target
.Rect
.X
+GetInterval(FDifficult
.DiagPrecision
, 128), //96
5664 Target
.Y
+Target
.Rect
.Y
+GetInterval(FDifficult
.DiagPrecision
, 128),
5665 Target
.Rect
.Width
, Target
.Rect
.Height
) and
5666 g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
) then
5667 begin // то нужно стрелять вверх
5668 SetAIFlag('NEEDFIRE', '1');
5669 SetAIFlag('NEEDSEEUP', '1');
5672 // Выбираем угол вниз:
5673 if FDirection
= TDirection
.D_LEFT
then
5674 angle
:= ANGLE_LEFTDOWN
5676 angle
:= ANGLE_RIGHTDOWN
;
5678 firew
:= Trunc(Cos(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
5679 fireh
:= Trunc(Sin(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
5681 // Если при угле вниз можно попасть в приблизительное положение цели:
5682 if g_CollideLine(x1
, y1
, x1
+firew
, y1
+fireh
,
5683 Target
.X
+Target
.Rect
.X
+GetInterval(FDifficult
.DiagPrecision
, 128),
5684 Target
.Y
+Target
.Rect
.Y
+GetInterval(FDifficult
.DiagPrecision
, 128),
5685 Target
.Rect
.Width
, Target
.Rect
.Height
) and
5686 g_TraceVector(x1
, y1
, Target
.cX
, Target
.cY
) then
5687 begin // то нужно стрелять вниз
5688 SetAIFlag('NEEDFIRE', '1');
5689 SetAIFlag('NEEDSEEDOWN', '1');
5692 // Если цель видно и она на такой же высоте:
5693 if Target
.Visible
and
5694 (y1
+4 < Target
.Y
+Target
.Rect
.Y
+Target
.Rect
.Height
) and
5695 (y1
-4 > Target
.Y
+Target
.Rect
.Y
) then
5697 // Если идем в сторону цели, то надо стрелять:
5698 if ((FDirection
= TDirection
.D_LEFT
) and (Target
.X
< FObj
.X
)) or
5699 ((FDirection
= TDirection
.D_RIGHT
) and (Target
.X
> FObj
.X
)) then
5700 begin // то нужно стрелять вперед
5701 SetAIFlag('NEEDFIRE', '1');
5702 SetAIFlag('NEEDSEEDOWN', '');
5703 SetAIFlag('NEEDSEEUP', '');
5705 // Если цель в пределах "экрана" и сложность позволяет прыжки сближения:
5706 if Abs(FObj
.X
-Target
.X
) < Trunc(gPlayerScreenSize
.X
*0.75) then
5707 if GetRnd(FDifficult
.CloseJump
) then
5708 begin // то если повезет - прыгаем (особенно, если близко)
5709 if Abs(FObj
.X
-Target
.X
) < 128 then
5713 if Random(a
) = 0 then
5714 SetAIFlag('NEEDJUMP', '1');
5718 // Если цель все еще есть:
5719 if Target
.UID
<> 0 then
5720 if gTime
-FLastVisible
> 2000 then // Если видели давно
5721 Target
.UID
:= 0 // то забыть цель
5722 else // Если видели недавно
5723 begin // но цель убили
5724 if Target
.IsPlayer
then
5725 begin // Цель - игрок
5726 pla
:= g_Player_Get(Target
.UID
);
5727 if (pla
= nil) or (not pla
.alive
) or pla
.NoTarget
or
5728 (pla
.FMegaRulez
[MR_INVIS
] >= gTime
) then
5729 Target
.UID
:= 0; // то забыть цель
5732 begin // Цель - монстр
5733 mon
:= g_Monsters_ByUID(Target
.UID
);
5734 if (mon
= nil) or (not mon
.alive
) then
5735 Target
.UID
:= 0; // то забыть цель
5738 end; // if Target.UID <> 0
5740 FTargetUID
:= Target
.UID
;
5742 // Если возможных целей нет:
5743 // (Атака чего-нибудь слева или справа)
5744 if targets
= nil then
5745 if GetAIFlag('ATTACKLEFT') <> '' then
5746 begin // Если нужно атаковать налево
5747 RemoveAIFlag('ATTACKLEFT');
5749 SetAIFlag('NEEDJUMP', '1');
5751 if RunDirection() = TDirection
.D_RIGHT
then
5752 begin // Идем не в ту сторону
5753 if (Healthy() > 1) and GetRnd(FDifficult
.InvisFire
) then
5754 begin // Если здоровы, то, возможно, стреляем бежим влево и стреляем
5755 SetAIFlag('NEEDFIRE', '1');
5756 SetAIFlag('GOLEFT', '1');
5760 begin // Идем в нужную сторону
5761 if GetRnd(FDifficult
.InvisFire
) then // Возможно, стреляем вслепую
5762 SetAIFlag('NEEDFIRE', '1');
5763 if Healthy() <= 1 then // Побиты - убегаем
5764 SetAIFlag('GORIGHT', '1');
5768 if GetAIFlag('ATTACKRIGHT') <> '' then
5769 begin // Если нужно атаковать направо
5770 RemoveAIFlag('ATTACKRIGHT');
5772 SetAIFlag('NEEDJUMP', '1');
5774 if RunDirection() = TDirection
.D_LEFT
then
5775 begin // Идем не в ту сторону
5776 if (Healthy() > 1) and GetRnd(FDifficult
.InvisFire
) then
5777 begin // Если здоровы, то, возможно, бежим вправо и стреляем
5778 SetAIFlag('NEEDFIRE', '1');
5779 SetAIFlag('GORIGHT', '1');
5784 if GetRnd(FDifficult
.InvisFire
) then // Возможно, стреляем вслепую
5785 SetAIFlag('NEEDFIRE', '1');
5786 if Healthy() <= 1 then // Побиты - убегаем
5787 SetAIFlag('GOLEFT', '1');
5791 //HACK! (does it belongs there?)
5792 RealizeCurrentWeapon();
5794 // Если есть возможные цели:
5795 // (Стреляем по направлению к целям)
5796 if (targets
<> nil) and (GetAIFlag('NEEDFIRE') <> '') then
5797 for a
:= 0 to High(targets
) do
5799 // Если можем стрелять по диагонали:
5800 if GetRnd(FDifficult
.DiagFire
) then
5802 // Ищем цель сверху и стреляем, если есть:
5803 if FDirection
= TDirection
.D_LEFT
then
5804 angle
:= ANGLE_LEFTUP
5806 angle
:= ANGLE_RIGHTUP
;
5808 firew
:= Trunc(Cos(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
5809 fireh
:= Trunc(Sin(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
5811 if g_CollideLine(x1
, y1
, x1
+firew
, y1
+fireh
,
5812 targets
[a
].X
+targets
[a
].Rect
.X
+GetInterval(FDifficult
.DiagPrecision
, 128),
5813 targets
[a
].Y
+targets
[a
].Rect
.Y
+GetInterval(FDifficult
.DiagPrecision
, 128),
5814 targets
[a
].Rect
.Width
, targets
[a
].Rect
.Height
) and
5815 g_TraceVector(x1
, y1
, targets
[a
].cX
, targets
[a
].cY
) then
5817 SetAIFlag('NEEDFIRE', '1');
5818 SetAIFlag('NEEDSEEUP', '1');
5821 // Ищем цель снизу и стреляем, если есть:
5822 if FDirection
= TDirection
.D_LEFT
then
5823 angle
:= ANGLE_LEFTDOWN
5825 angle
:= ANGLE_RIGHTDOWN
;
5827 firew
:= Trunc(Cos(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
5828 fireh
:= Trunc(Sin(DegToRad(-angle
))*gPlayerScreenSize
.X
*0.6);
5830 if g_CollideLine(x1
, y1
, x1
+firew
, y1
+fireh
,
5831 targets
[a
].X
+targets
[a
].Rect
.X
+GetInterval(FDifficult
.DiagPrecision
, 128),
5832 targets
[a
].Y
+targets
[a
].Rect
.Y
+GetInterval(FDifficult
.DiagPrecision
, 128),
5833 targets
[a
].Rect
.Width
, targets
[a
].Rect
.Height
) and
5834 g_TraceVector(x1
, y1
, targets
[a
].cX
, targets
[a
].cY
) then
5836 SetAIFlag('NEEDFIRE', '1');
5837 SetAIFlag('NEEDSEEDOWN', '1');
5841 // Если цель "перед носом", то стреляем:
5842 if targets
[a
].Line
and targets
[a
].Visible
and
5843 (((FDirection
= TDirection
.D_LEFT
) and (targets
[a
].X
< FObj
.X
)) or
5844 ((FDirection
= TDirection
.D_RIGHT
) and (targets
[a
].X
> FObj
.X
))) then
5846 SetAIFlag('NEEDFIRE', '1');
5851 // Если летит пуля, то, возможно, подпрыгиваем:
5852 if g_Weapon_Danger(FUID
, FObj
.X
+PLAYER_RECT
.X
, FObj
.Y
+PLAYER_RECT
.Y
,
5853 PLAYER_RECT
.Width
, PLAYER_RECT
.Height
,
5854 40+GetInterval(FDifficult
.Cover
, 40)) then
5855 SetAIFlag('NEEDJUMP', '1');
5857 // Если кончились паторны, то нужно сменить оружие:
5858 ammo
:= GetAmmoByWeapon(FCurrWeap
);
5859 if ((FCurrWeap
= WEAPON_SHOTGUN2
) and (ammo
< 2)) or
5860 ((FCurrWeap
= WEAPON_BFG
) and (ammo
< 40)) or
5862 SetAIFlag('SELECTWEAPON', '1');
5864 // Если нужно сменить оружие, то выбираем нужное:
5865 if GetAIFlag('SELECTWEAPON') = '1' then
5868 RemoveAIFlag('SELECTWEAPON');
5872 procedure TBot
.Update();
5885 // Проверяем, отключён ли AI ботов
5886 if (g_debug_BotAIOff
= 1) and (Team
= TEAM_RED
) then
5888 if (g_debug_BotAIOff
= 2) and (Team
= TEAM_BLUE
) then
5890 if g_debug_BotAIOff
= 3 then
5900 RealizeCurrentWeapon();
5907 procedure TBot
.ReleaseKey(Key
: Byte);
5916 function TBot
.KeyPressed(Key
: Word): Boolean;
5918 Result
:= FKeys
[Key
].Pressed
;
5921 function TBot
.GetAIFlag(aName
: String20
): String20
;
5927 aName
:= LowerCase(aName
);
5929 if FAIFlags
<> nil then
5930 for a
:= 0 to High(FAIFlags
) do
5931 if LowerCase(FAIFlags
[a
].Name
) = aName
then
5933 Result
:= FAIFlags
[a
].Value
;
5938 procedure TBot
.RemoveAIFlag(aName
: String20
);
5942 if FAIFlags
= nil then Exit
;
5944 aName
:= LowerCase(aName
);
5946 for a
:= 0 to High(FAIFlags
) do
5947 if LowerCase(FAIFlags
[a
].Name
) = aName
then
5949 if a
<> High(FAIFlags
) then
5950 for b
:= a
to High(FAIFlags
)-1 do
5951 FAIFlags
[b
] := FAIFlags
[b
+1];
5953 SetLength(FAIFlags
, Length(FAIFlags
)-1);
5958 procedure TBot
.SetAIFlag(aName
, fValue
: String20
);
5966 aName
:= LowerCase(aName
);
5968 if FAIFlags
<> nil then
5969 for a
:= 0 to High(FAIFlags
) do
5970 if LowerCase(FAIFlags
[a
].Name
) = aName
then
5976 if ok
then FAIFlags
[a
].Value
:= fValue
5979 SetLength(FAIFlags
, Length(FAIFlags
)+1);
5980 with FAIFlags
[High(FAIFlags
)] do
5988 procedure TBot
.UpdateMove
;
5990 procedure GoLeft(Time
: Word = 1);
5992 ReleaseKey(KEY_LEFT
);
5993 ReleaseKey(KEY_RIGHT
);
5994 PressKey(KEY_LEFT
, Time
);
5995 SetDirection(TDirection
.D_LEFT
);
5998 procedure GoRight(Time
: Word = 1);
6000 ReleaseKey(KEY_LEFT
);
6001 ReleaseKey(KEY_RIGHT
);
6002 PressKey(KEY_RIGHT
, Time
);
6003 SetDirection(TDirection
.D_RIGHT
);
6006 function Rnd(a
: Word): Boolean;
6008 Result
:= Random(a
) = 0;
6011 procedure Turn(Time
: Word = 1200);
6013 if RunDirection() = TDirection
.D_LEFT
then GoRight(Time
) else GoLeft(Time
);
6018 ReleaseKey(KEY_LEFT
);
6019 ReleaseKey(KEY_RIGHT
);
6022 function CanRunLeft(): Boolean;
6024 Result
:= not CollideLevel(-1, 0);
6027 function CanRunRight(): Boolean;
6029 Result
:= not CollideLevel(1, 0);
6032 function CanRun(): Boolean;
6034 if RunDirection() = TDirection
.D_LEFT
then Result
:= CanRunLeft() else Result
:= CanRunRight();
6037 procedure Jump(Time
: Word = 30);
6039 PressKey(KEY_JUMP
, Time
);
6042 function NearHole(): Boolean;
6046 { TODO 5 : Лестницы }
6047 sx
:= IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1);
6048 for x
:= 1 to PLAYER_RECT
.Width
do
6049 if (not StayOnStep(x
*sx
, 0)) and
6050 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
)) and
6051 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
*2)) then
6060 function BorderHole(): Boolean;
6064 { TODO 5 : Лестницы }
6065 sx
:= IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1);
6066 for x
:= 1 to PLAYER_RECT
.Width
do
6067 if (not StayOnStep(x
*sx
, 0)) and
6068 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
)) and
6069 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
*2)) then
6071 for xx
:= x
to x
+32 do
6072 if CollideLevel(xx
*sx
, PLAYER_RECT
.Height
) then
6082 function NearDeepHole(): Boolean;
6088 sx
:= IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1);
6091 for x
:= 1 to PLAYER_RECT
.Width
do
6092 if (not StayOnStep(x
*sx
, 0)) and
6093 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
)) and
6094 (not CollideLevel(x
*sx
, PLAYER_RECT
.Height
*2)) then
6096 while FObj
.Y
+y
*PLAYER_RECT
.Height
< gMapInfo
.Height
do
6098 if CollideLevel(x
*sx
, PLAYER_RECT
.Height
*y
) then Exit
;
6103 end else Result
:= False;
6106 function OverDeepHole(): Boolean;
6113 while FObj
.Y
+y
*PLAYER_RECT
.Height
< gMapInfo
.Height
do
6115 if CollideLevel(0, PLAYER_RECT
.Height
*y
) then Exit
;
6122 function OnGround(): Boolean;
6124 Result
:= StayOnStep(0, 0) or CollideLevel(0, 1);
6127 function OnLadder(): Boolean;
6129 Result
:= FullInStep(0, 0);
6132 function BelowLadder(): Boolean;
6134 Result
:= (FullInStep(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -PLAYER_RECT
.Height
) and
6135 not CollideLevel(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -PLAYER_RECT
.Height
)) or
6136 (FullInStep(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -BOT_MAXJUMP
) and
6137 not CollideLevel(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -BOT_MAXJUMP
));
6140 function BelowLiftUp(): Boolean;
6142 Result
:= ((FullInLift(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -PLAYER_RECT
.Height
) = -1) and
6143 not CollideLevel(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -PLAYER_RECT
.Height
)) or
6144 ((FullInLift(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -BOT_MAXJUMP
) = -1) and
6145 not CollideLevel(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*(PLAYER_RECT
.Width
div 2), -BOT_MAXJUMP
));
6148 function OnTopLift(): Boolean;
6150 Result
:= (FullInLift(0, 0) = -1) and (FullInLift(0, -32) = 0);
6153 function CanJumpOver(): Boolean;
6157 sx
:= IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1);
6161 if not CollideLevel(sx
, 0) then Exit
;
6163 for y
:= 1 to BOT_MAXJUMP
do
6164 if CollideLevel(0, -y
) then Exit
else
6165 if not CollideLevel(sx
, -y
) then
6172 function CanJumpUp(Dist
: ShortInt): Boolean;
6179 if CollideLevel(Dist
, 0) then Exit
;
6182 for y
:= 0 to BOT_MAXJUMP
do
6183 if CollideLevel(Dist
, -y
) then
6192 for yy
:= y
+1 to BOT_MAXJUMP
do
6193 if not CollideLevel(Dist
, -yy
) then
6202 for y
:= 0 to BOT_MAXJUMP
do
6203 if CollideLevel(0, -y
) then
6211 if y
< yy
then Exit
;
6216 function IsSafeTrigger(): Boolean;
6221 if gTriggers
= nil then
6223 for a
:= 0 to High(gTriggers
) do
6224 if Collide(gTriggers
[a
].X
,
6227 gTriggers
[a
].Height
) and
6228 (gTriggers
[a
].TriggerType
in [TRIGGER_EXIT
, TRIGGER_CLOSEDOOR
,
6229 TRIGGER_CLOSETRAP
, TRIGGER_TRAP
,
6230 TRIGGER_PRESS
, TRIGGER_ON
, TRIGGER_OFF
,
6231 TRIGGER_ONOFF
, TRIGGER_SPAWNMONSTER
,
6232 TRIGGER_DAMAGE
, TRIGGER_SHOT
]) then
6237 // Возможно, нажимаем кнопку:
6238 if Rnd(16) and IsSafeTrigger() then
6241 // Если под лифтом или ступеньками, то, возможно, прыгаем:
6242 if OnLadder() or ((BelowLadder() or BelowLiftUp()) and Rnd(8)) then
6244 ReleaseKey(KEY_LEFT
);
6245 ReleaseKey(KEY_RIGHT
);
6249 // Идем влево, если надо было:
6250 if GetAIFlag('GOLEFT') <> '' then
6252 RemoveAIFlag('GOLEFT');
6253 if CanRunLeft() then
6257 // Идем вправо, если надо было:
6258 if GetAIFlag('GORIGHT') <> '' then
6260 RemoveAIFlag('GORIGHT');
6261 if CanRunRight() then
6265 // Если вылетели за карту, то пробуем вернуться:
6266 if FObj
.X
< -32 then
6269 if FObj
.X
+32 > gMapInfo
.Width
then
6272 // Прыгаем, если надо было:
6273 if GetAIFlag('NEEDJUMP') <> '' then
6276 RemoveAIFlag('NEEDJUMP');
6279 // Смотрим вверх, если надо было:
6280 if GetAIFlag('NEEDSEEUP') <> '' then
6283 ReleaseKey(KEY_DOWN
);
6284 PressKey(KEY_UP
, 20);
6285 RemoveAIFlag('NEEDSEEUP');
6288 // Смотрим вниз, если надо было:
6289 if GetAIFlag('NEEDSEEDOWN') <> '' then
6292 ReleaseKey(KEY_DOWN
);
6293 PressKey(KEY_DOWN
, 20);
6294 RemoveAIFlag('NEEDSEEDOWN');
6297 // Если нужно было в дыру и мы не на земле, то покорно летим:
6298 if GetAIFlag('GOINHOLE') <> '' then
6299 if not OnGround() then
6301 ReleaseKey(KEY_LEFT
);
6302 ReleaseKey(KEY_RIGHT
);
6303 RemoveAIFlag('GOINHOLE');
6304 SetAIFlag('FALLINHOLE', '1');
6307 // Если падали и достигли земли, то хватит падать:
6308 if GetAIFlag('FALLINHOLE') <> '' then
6310 RemoveAIFlag('FALLINHOLE');
6312 // Если летели прямо и сейчас не на лестнице или на вершине лифта, то отходим в сторону:
6313 if not (KeyPressed(KEY_LEFT
) or KeyPressed(KEY_RIGHT
)) then
6314 if GetAIFlag('FALLINHOLE') = '' then
6315 if (not OnLadder()) or (FObj
.Vel
.Y
>= 0) or (OnTopLift()) then
6321 // Если на земле и можно подпрыгнуть, то, возможно, прыгаем:
6323 CanJumpUp(IfThen(RunDirection() = TDirection
.D_LEFT
, -1, 1)*32) and
6327 // Если на земле и возле дыры (глубина > 2 ростов игрока):
6328 if OnGround() and NearHole() then
6329 if NearDeepHole() then // Если это бездна
6331 0..3: Turn(); // Бежим обратно
6332 4: Jump(); // Прыгаем
6333 5: begin // Прыгаем обратно
6338 else // Это не бездна и мы еще не летим туда
6339 if GetAIFlag('GOINHOLE') = '' then
6341 0: Turn(); // Не нужно туда
6342 1: Jump(); // Вдруг повезет - прыгаем
6343 else // Если яма с границей, то при случае можно туда прыгнуть
6344 if BorderHole() then
6345 SetAIFlag('GOINHOLE', '1');
6348 // Если на земле, но некуда идти:
6349 if (not CanRun()) and OnGround() then
6351 // Если мы на лестнице или можно перепрыгнуть, то прыгаем:
6352 if CanJumpOver() or OnLadder() then
6354 else // иначе попытаемся в другую сторону
6355 if Random(2) = 0 then
6357 if IsSafeTrigger() then
6363 // Осталось мало воздуха:
6364 if FAir
< 36 * 2 then
6367 // Выбираемся из кислоты, если нет костюма, обожглись, или мало здоровья:
6368 if (FMegaRulez
[MR_SUIT
] < gTime
) and ((FLastHit
= HIT_ACID
) or (Healthy() <= 1)) then
6369 if BodyInAcid(0, 0) then
6373 function TBot
.FullInStep(XInc
, YInc
: Integer): Boolean;
6375 Result
:= g_Map_CollidePanel(FObj
.X
+PLAYER_RECT
.X
+XInc
, FObj
.Y
+PLAYER_RECT
.Y
+YInc
,
6376 PLAYER_RECT
.Width
, PLAYER_RECT
.Height
, PANEL_STEP
, False);
6379 {function TBot.NeedItem(Item: Byte): Byte;
6384 procedure TBot
.SelectWeapon(Dist
: Integer);
6388 function HaveAmmo(weapon
: Byte): Boolean;
6391 WEAPON_PISTOL
: Result
:= FAmmo
[A_BULLETS
] >= 1;
6392 WEAPON_SHOTGUN1
: Result
:= FAmmo
[A_SHELLS
] >= 1;
6393 WEAPON_SHOTGUN2
: Result
:= FAmmo
[A_SHELLS
] >= 2;
6394 WEAPON_CHAINGUN
: Result
:= FAmmo
[A_BULLETS
] >= 10;
6395 WEAPON_ROCKETLAUNCHER
: Result
:= FAmmo
[A_ROCKETS
] >= 1;
6396 WEAPON_PLASMA
: Result
:= FAmmo
[A_CELLS
] >= 10;
6397 WEAPON_BFG
: Result
:= FAmmo
[A_CELLS
] >= 40;
6398 WEAPON_SUPERPULEMET
: Result
:= FAmmo
[A_SHELLS
] >= 1;
6399 WEAPON_FLAMETHROWER
: Result
:= FAmmo
[A_FUEL
] >= 1;
6400 else Result
:= True;
6405 if Dist
= -1 then Dist
:= BOT_LONGDIST
;
6407 if Dist
> BOT_LONGDIST
then
6408 begin // Дальний бой
6410 if FWeapon
[FDifficult
.WeaponPrior
[a
]] and HaveAmmo(FDifficult
.WeaponPrior
[a
]) then
6412 FSelectedWeapon
:= FDifficult
.WeaponPrior
[a
];
6416 else //if Dist > BOT_UNSAFEDIST then
6417 begin // Ближний бой
6419 if FWeapon
[FDifficult
.CloseWeaponPrior
[a
]] and HaveAmmo(FDifficult
.CloseWeaponPrior
[a
]) then
6421 FSelectedWeapon
:= FDifficult
.CloseWeaponPrior
[a
];
6428 if FWeapon[FDifficult.SafeWeaponPrior[a]] and HaveAmmo(FDifficult.SafeWeaponPrior[a]) then
6430 FSelectedWeapon := FDifficult.SafeWeaponPrior[a];
6436 function TBot
.PickItem(ItemType
: Byte; force
: Boolean; var remove
: Boolean): Boolean;
6438 Result
:= inherited PickItem(ItemType
, force
, remove
);
6440 if Result
then SetAIFlag('SELECTWEAPON', '1');
6443 function TBot
.Heal(value
: Word; Soft
: Boolean): Boolean;
6445 Result
:= inherited Heal(value
, Soft
);
6448 function TBot
.Healthy(): Byte;
6450 if FMegaRulez
[MR_INVUL
] >= gTime
then Result
:= 3
6451 else if (FHealth
> 80) or ((FHealth
> 50) and (FArmor
> 20)) then Result
:= 3
6452 else if (FHealth
> 50) then Result
:= 2
6453 else if (FHealth
> 20) then Result
:= 1
6457 function TBot
.TargetOnScreen(TX
, TY
: Integer): Boolean;
6459 Result
:= (Abs(FObj
.X
-TX
) <= Trunc(gPlayerScreenSize
.X
*0.6)) and
6460 (Abs(FObj
.Y
-TY
) <= Trunc(gPlayerScreenSize
.Y
*0.6));
6463 procedure TBot
.OnDamage(Angle
: SmallInt);
6471 if (Angle
= 0) or (Angle
= 180) then
6474 if (g_GetUIDType(FLastSpawnerUID
) = UID_PLAYER
) and
6475 LongBool(gGameSettings
.Options
and GAME_OPTION_BOTVSPLAYER
) then
6477 pla
:= g_Player_Get(FLastSpawnerUID
);
6478 ok
:= not TargetOnScreen(pla
.FObj
.X
+ PLAYER_RECT
.X
,
6479 pla
.FObj
.Y
+ PLAYER_RECT
.Y
);
6482 if (g_GetUIDType(FLastSpawnerUID
) = UID_MONSTER
) and
6483 LongBool(gGameSettings
.Options
and GAME_OPTION_BOTVSMONSTER
) then
6485 mon
:= g_Monsters_ByUID(FLastSpawnerUID
);
6486 ok
:= not TargetOnScreen(mon
.Obj
.X
+ mon
.Obj
.Rect
.X
,
6487 mon
.Obj
.Y
+ mon
.Obj
.Rect
.Y
);
6492 SetAIFlag('ATTACKLEFT', '1')
6494 SetAIFlag('ATTACKRIGHT', '1');
6498 function TBot
.RunDirection(): TDirection
;
6500 if Abs(Vel
.X
) >= 1 then
6502 if Vel
.X
> 0 then Result
:= TDirection
.D_RIGHT
else Result
:= TDirection
.D_LEFT
;
6504 Result
:= FDirection
;
6507 function TBot
.GetRnd(a
: Byte): Boolean;
6509 if a
= 0 then Result
:= False
6510 else if a
= 255 then Result
:= True
6511 else Result
:= Random(256) > 255-a
;
6514 function TBot
.GetInterval(a
: Byte; radius
: SmallInt): SmallInt;
6516 Result
:= Round((255-a
)/255*radius
*(Random(2)-1));
6520 procedure TDifficult
.save (st
: TStream
);
6522 utils
.writeInt(st
, Byte(DiagFire
));
6523 utils
.writeInt(st
, Byte(InvisFire
));
6524 utils
.writeInt(st
, Byte(DiagPrecision
));
6525 utils
.writeInt(st
, Byte(FlyPrecision
));
6526 utils
.writeInt(st
, Byte(Cover
));
6527 utils
.writeInt(st
, Byte(CloseJump
));
6528 st
.WriteBuffer(WeaponPrior
[Low(WeaponPrior
)], sizeof(WeaponPrior
));
6529 st
.WriteBuffer(CloseWeaponPrior
[Low(CloseWeaponPrior
)], sizeof(CloseWeaponPrior
));
6532 procedure TDifficult
.load (st
: TStream
);
6534 DiagFire
:= utils
.readByte(st
);
6535 InvisFire
:= utils
.readByte(st
);
6536 DiagPrecision
:= utils
.readByte(st
);
6537 FlyPrecision
:= utils
.readByte(st
);
6538 Cover
:= utils
.readByte(st
);
6539 CloseJump
:= utils
.readByte(st
);
6540 st
.ReadBuffer(WeaponPrior
[Low(WeaponPrior
)], sizeof(WeaponPrior
));
6541 st
.ReadBuffer(CloseWeaponPrior
[Low(CloseWeaponPrior
)], sizeof(CloseWeaponPrior
));
6545 procedure TBot
.SaveState (st
: TStream
);
6550 inherited SaveState(st
);
6551 utils
.writeSign(st
, 'BOT0');
6553 utils
.writeInt(st
, Byte(FSelectedWeapon
));
6555 utils
.writeInt(st
, Word(FTargetUID
));
6556 // Время потери цели
6557 utils
.writeInt(st
, LongWord(FLastVisible
));
6558 // Количество флагов ИИ
6559 dw
:= Length(FAIFlags
);
6560 utils
.writeInt(st
, LongInt(dw
));
6562 for i
:= 0 to dw
-1 do
6564 utils
.writeStr(st
, FAIFlags
[i
].Name
, 20);
6565 utils
.writeStr(st
, FAIFlags
[i
].Value
, 20);
6567 // Настройки сложности
6568 FDifficult
.save(st
);
6572 procedure TBot
.LoadState (st
: TStream
);
6577 inherited LoadState(st
);
6578 if not utils
.checkSign(st
, 'BOT0') then raise XStreamError
.Create('invalid bot signature');
6580 FSelectedWeapon
:= utils
.readByte(st
);
6582 FTargetUID
:= utils
.readWord(st
);
6583 // Время потери цели
6584 FLastVisible
:= utils
.readLongWord(st
);
6585 // Количество флагов ИИ
6586 dw
:= utils
.readLongInt(st
);
6587 if (dw
< 0) or (dw
> 16384) then raise XStreamError
.Create('invalid number of bot AI flags');
6588 SetLength(FAIFlags
, dw
);
6590 for i
:= 0 to dw
-1 do
6592 FAIFlags
[i
].Name
:= utils
.readStr(st
, 20);
6593 FAIFlags
[i
].Value
:= utils
.readStr(st
, 20);
6595 // Настройки сложности
6596 FDifficult
.load(st
);
6601 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');
6602 conRegVar('player_indicator_style', @gPlayerIndicatorStyle
, 'Visual appearance of indicator', 'Visual appearance of indicator');