X-Git-Url: http://deadsoftware.ru/gitweb?a=blobdiff_plain;f=src%2Fgame%2Fg_player.pas;h=4429fd0957c2efda42813ebbf0a3801217cea65f;hb=abda6900c041e39944de6a49aa088a60c170715e;hp=612a4fb0aee855f664ae797deb1113959ba59583;hpb=1b0f87b9a64f04be222df8d8d2de059cb139c98d;p=d2df-sdl.git diff --git a/src/game/g_player.pas b/src/game/g_player.pas index 612a4fb..92afc63 100644 --- a/src/game/g_player.pas +++ b/src/game/g_player.pas @@ -1,9 +1,8 @@ -(* Copyright (C) DooM 2D:Forever Developers +(* Copyright (C) Doom 2D: Forever Developers * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * the Free Software Foundation, version 3 of the License ONLY. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -14,14 +13,17 @@ * along with this program. If not, see . *) {$INCLUDE ../shared/a_modes.inc} +{$M+} unit g_player; interface uses - e_graphics, g_playermodel, g_basic, g_textures, - g_weapons, g_phys, g_sound, g_saveload, MAPSTRUCT, - BinEditor, g_panel; + SysUtils, Classes, + {$IFDEF USE_MEMPOOL}mempool,{$ENDIF} + g_base, g_playermodel, g_basic, g_textures, + g_weapons, g_phys, g_sound, g_saveload, MAPDEF, + g_panel; const KEY_LEFT = 1; @@ -92,12 +94,27 @@ const PLAYER_AP_SOFT = 100; PLAYER_AP_LIMIT = 200; SUICIDE_DAMAGE = 112; + WEAPON_DELAY = 5; + + PLAYER_BURN_TIME = 110; PLAYER1_DEF_COLOR: TRGB = (R:64; G:175; B:48); PLAYER2_DEF_COLOR: TRGB = (R:96; G:96; B:96); + AIR_DEF = 360; + AIR_MAX = 1091; + JET_MAX = 540; // ~30 sec + ANGLE_RIGHTUP = 55; + ANGLE_RIGHTDOWN = -35; + ANGLE_LEFTUP = 125; + ANGLE_LEFTDOWN = -145; + WEAPONPOINT: Array [TDirection] of TDFPoint = ((X:16; Y:32), (X:47; Y:32)); + TEAMCOLOR: Array [TEAM_RED..TEAM_BLUE] of TRGB = ((R:255; G:0; B:0), + (R:0; G:0; B:255)); + type TPlayerStat = record + Num: Integer; Ping: Word; Loss: Byte; Name: String; @@ -108,6 +125,7 @@ type Kills: Word; Color: TRGB; Spectator: Boolean; + UID: Word; end; TPlayerStatArray = Array of TPlayerStat; @@ -124,7 +142,7 @@ type MaxAmmo: Array [A_BULLETS..A_HIGH] of Word; Weapon: Array [WP_FIRST..WP_LAST] of Boolean; Rulez: Set of R_ITEM_BACKPACK..R_BERSERK; - WaitRecall: Boolean; + Used: Boolean; end; TKeyState = record @@ -132,13 +150,13 @@ type Time: Word; end; - TPlayer = class (TObject) + TPlayer = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF} private FIamBot: Boolean; FUID: Word; FName: String; FTeam: Byte; - FLive: Boolean; + FAlive: Boolean; FSpawned: Boolean; FDirection: TDirection; FHealth: Integer; @@ -170,22 +188,28 @@ type FFirePainTime: Integer; FFireAttacker: Word; - FSavedState: TPlayerSavedState; + FSavedStateNum: Integer; FModel: TPlayerModel; + FPunchAnim: TAnimationState; FActionPrior: Byte; FActionAnim: Byte; FActionForce: Boolean; FActionChanged: Boolean; FAngle: SmallInt; FFireAngle: SmallInt; + FIncCamOld: Integer; FIncCam: Integer; + FSlopeOld: Integer; FShellTimer: Integer; FShellType: Byte; FSawSound: TPlayableSound; FSawSoundIdle: TPlayableSound; FSawSoundHit: TPlayableSound; FSawSoundSelect: TPlayableSound; + FFlameSoundOn: TPlayableSound; + FFlameSoundOff: TPlayableSound; + FFlameSoundWork: TPlayableSound; FJetSoundOn: TPlayableSound; FJetSoundOff: TPlayableSound; FJetSoundFly: TPlayableSound; @@ -194,34 +218,40 @@ type FNoReload: Boolean; FJustTeleported: Boolean; FNetTime: LongWord; - - function CollideLevel(XInc, YInc: Integer): Boolean; - function StayOnStep(XInc, YInc: Integer): Boolean; - function HeadInLiquid(XInc, YInc: Integer): Boolean; - function BodyInLiquid(XInc, YInc: Integer): Boolean; - function BodyInAcid(XInc, YInc: Integer): Boolean; - function FullInLift(XInc, YInc: Integer): Integer; - {procedure CollideItem();} - procedure FlySmoke(Times: DWORD = 1); - procedure OnFireFlame(Times: DWORD = 1); - function GetAmmoByWeapon(Weapon: Byte): Word; - procedure SetAction(Action: Byte; Force: Boolean = False); - procedure OnDamage(Angle: SmallInt); virtual; - function firediry(): Integer; - - procedure Run(Direction: TDirection); - procedure NextWeapon(); - procedure PrevWeapon(); - procedure SeeUp(); - procedure SeeDown(); - procedure Fire(); - procedure Jump(); - procedure Use(); + mEDamageType: Integer; + + + function CollideLevel(XInc, YInc: Integer): Boolean; + function StayOnStep(XInc, YInc: Integer): Boolean; + function HeadInLiquid(XInc, YInc: Integer): Boolean; + function BodyInLiquid(XInc, YInc: Integer): Boolean; + function BodyInAcid(XInc, YInc: Integer): Boolean; + function FullInLift(XInc, YInc: Integer): Integer; + {procedure CollideItem();} + procedure FlySmoke(Times: DWORD = 1); + procedure OnFireFlame(Times: DWORD = 1); + procedure SetAction(Action: Byte; Force: Boolean = False); + procedure OnDamage(Angle: SmallInt); virtual; + function firediry(): Integer; + procedure DoPunch(); + + procedure Run(Direction: TDirection); + procedure NextWeapon(); + procedure PrevWeapon(); + procedure SeeUp(); + procedure SeeDown(); + procedure Fire(); + procedure Jump(); + procedure Use(); function getNextWeaponIndex (): Byte; // return 255 for "no switch" procedure resetWeaponQueue (); function hasAmmoForWeapon (weapon: Byte): Boolean; + procedure doDamage (v: Integer); + + function refreshCorpse(): Boolean; + public FDamageBuffer: Integer; @@ -241,13 +271,24 @@ type FWantsInGame: Boolean; FGhost: Boolean; FPhysics: Boolean; + FFlaming: Boolean; FJetpack: Boolean; FActualModelName: string; FClientID: SmallInt; FPing: Word; FLoss: Byte; + FReady: Boolean; FDummy: Boolean; FFireTime: Integer; + FSpawnInvul: Integer; + FHandicap: Integer; + FWaitForFirstSpawn: Boolean; // set to `true` in server, used to spawn a player on first full state request + FCorpse: Integer; + + // debug: viewport offset + viewPortX, viewPortY, viewPortW, viewPortH: Integer; + + function isValidViewPort (): Boolean; inline; constructor Create(); virtual; destructor Destroy(); override; @@ -257,6 +298,7 @@ type procedure ReleaseKeys(); procedure SetModel(ModelName: String); procedure SetColor(Color: TRGB); + function GetColor(): TRGB; procedure SetWeapon(W: Byte); function IsKeyPressed(K: Byte): Boolean; function GetKeys(): Byte; @@ -275,7 +317,7 @@ type procedure BFGHit(); function GetFlag(Flag: Byte): Boolean; procedure SetFlag(Flag: Byte); - function DropFlag(): Boolean; + function DropFlag(Silent: Boolean = True): Boolean; procedure AllRulez(Health: Boolean); procedure RestoreHealthArmor(); procedure FragCombo(); @@ -289,30 +331,37 @@ type procedure Spectate(NoMove: Boolean = False); procedure SwitchNoClip; procedure SoftReset(); - procedure Draw(); virtual; - procedure DrawPain(); - procedure DrawPickup(); - procedure DrawRulez(); - procedure DrawAim(); - procedure DrawBubble(); - procedure DrawGUI(); + procedure PreUpdate(); procedure Update(); virtual; procedure RememberState(); procedure RecallState(); - procedure SaveState(var Mem: TBinMemoryWriter); virtual; - procedure LoadState(var Mem: TBinMemoryReader); virtual; + procedure SaveState (st: TStream); virtual; + procedure LoadState (st: TStream); virtual; procedure PauseSounds(Enable: Boolean); procedure NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1); procedure DoLerp(Level: Integer = 2); procedure SetLerp(XTo, YTo: Integer); procedure QueueWeaponSwitch(Weapon: Byte); procedure RealizeCurrentWeapon(); + procedure FlamerOn; + procedure FlamerOff; procedure JetpackOn; procedure JetpackOff; - procedure CatchFire(Attacker: Word); + procedure CatchFire(Attacker: Word; Timeout: Integer = PLAYER_BURN_TIME); //WARNING! this does nothing for now, but still call it! - procedure positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right! + procedure positionChanged (); //WARNING! call this after entity position was changed, or coldet will not work right! + + procedure getMapBox (out x, y, w, h: Integer); inline; + procedure moveBy (dx, dy: Integer); inline; + + function getCameraObj(): TObj; + + function GetAmmoByWeapon(Weapon: Byte): Word; // private state + + public + property Vel: TPoint2i read FObj.Vel; + property Obj: TObj read FObj; property Name: String read FName write FName; property Model: TPlayerModel read FModel; @@ -330,7 +379,7 @@ type property GodMode: Boolean read FGodMode write FGodMode; property NoTarget: Boolean read FNoTarget write FNoTarget; property NoReload: Boolean read FNoReload write FNoReload; - property Live: Boolean read FLive write FLive; + property alive: Boolean read FAlive write FAlive; property Flag: Byte read FFlag; property Team: Byte read FTeam write FTeam; property Direction: TDirection read FDirection; @@ -340,24 +389,75 @@ type property GameVelY: Integer read FObj.Vel.Y write FObj.Vel.Y; property GameAccelX: Integer read FObj.Accel.X write FObj.Accel.X; property GameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y; - property Vel: TPoint2i read FObj.Vel; - property Obj: TObj read FObj; property IncCam: Integer read FIncCam write FIncCam; + property IncCamOld: Integer read FIncCamOld write FIncCamOld; + property SlopeOld: Integer read FSlopeOld write FSlopeOld; property UID: Word read FUID write FUID; property JustTeleported: Boolean read FJustTeleported write FJustTeleported; property NetTime: LongWord read FNetTime write FNetTime; + + (* internal state *) + property Angle_: SmallInt read FAngle; + property Spectator: Boolean read FSpectator; + property NoRespawn: Boolean read FNoRespawn; + property Berserk: Integer read FBerserk; + property Pain: Integer read FPain; + property Pickup: Integer read FPickup; + property PunchAnim: TAnimationState read FPunchAnim write FPunchAnim; + property SpawnInvul: Integer read FSpawnInvul; + property Ghost: Boolean read FGhost; + + published + property eName: String read FName write FName; + property eHealth: Integer read FHealth write FHealth; + property eLives: Byte read FLives write FLives; + property eArmor: Integer read FArmor write FArmor; + property eAir: Integer read FAir write FAir; + property eJetFuel: Integer read FJetFuel write FJetFuel; + property eFrags: Integer read FFrags write FFrags; + property eDeath: Integer read FDeath write FDeath; + property eKills: Integer read FKills write FKills; + property eCurrWeap: Byte read FCurrWeap write FCurrWeap; + property eMonsterKills: Integer read FMonsterKills write FMonsterKills; + property eSecrets: Integer read FSecrets write FSecrets; + property eGodMode: Boolean read FGodMode write FGodMode; + property eNoTarget: Boolean read FNoTarget write FNoTarget; + property eNoReload: Boolean read FNoReload write FNoReload; + property eAlive: Boolean read FAlive write FAlive; + property eFlag: Byte read FFlag; + property eTeam: Byte read FTeam write FTeam; + property eDirection: TDirection read FDirection; + property eGameX: Integer read FObj.X write FObj.X; + property eGameY: Integer read FObj.Y write FObj.Y; + property eGameVelX: Integer read FObj.Vel.X write FObj.Vel.X; + property eGameVelY: Integer read FObj.Vel.Y write FObj.Vel.Y; + property eGameAccelX: Integer read FObj.Accel.X write FObj.Accel.X; + property eGameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y; + property eIncCam: Integer read FIncCam write FIncCam; + property eUID: Word read FUID; + property eJustTeleported: Boolean read FJustTeleported; + property eNetTime: LongWord read FNetTime; + + // set this before assigning something to `eDamage` + property eDamageType: Integer read mEDamageType write mEDamageType; + property eDamage: Integer write doDamage; end; TDifficult = record + public DiagFire: Byte; InvisFire: Byte; DiagPrecision: Byte; FlyPrecision: Byte; Cover: Byte; CloseJump: Byte; - WeaponPrior: Array [WP_FIRST..WP_LAST] of Byte; - CloseWeaponPrior: Array [WP_FIRST..WP_LAST] of Byte; + WeaponPrior: packed array [WP_FIRST..WP_LAST] of Byte; + CloseWeaponPrior: packed array [WP_FIRST..WP_LAST] of Byte; //SafeWeaponPrior: Array [WP_FIRST..WP_LAST] of Byte; + + public + procedure save (st: TStream); + procedure load (st: TStream); end; TAIFlag = record @@ -365,7 +465,7 @@ type Value: String; end; - TBot = class (TPlayer) + TBot = class(TPlayer) private FSelectedWeapon: Byte; FTargetUID: Word; @@ -394,63 +494,72 @@ type procedure Respawn(Silent: Boolean; Force: Boolean = False); override; constructor Create(); override; destructor Destroy(); override; - procedure Draw(); override; function PickItem(ItemType: Byte; force: Boolean; var remove: Boolean): Boolean; override; function Heal(value: Word; Soft: Boolean): Boolean; override; procedure Update(); override; - procedure SaveState(var Mem: TBinMemoryWriter); override; - procedure LoadState(var Mem: TBinMemoryReader); override; + procedure SaveState (st: TStream); override; + procedure LoadState (st: TStream); override; end; + PGib = ^TGib; TGib = record - Live: Boolean; - ID: DWORD; - MaskID: DWORD; + alive: Boolean; RAngle: Integer; Color: TRGB; Obj: TObj; - procedure positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right! + ModelID: Integer; + GibID: Integer; + + procedure getMapBox (out x, y, w, h: Integer); inline; + procedure moveBy (dx, dy: Integer); inline; + + procedure positionChanged (); inline; //WARNING! call this after entity position was changed, or coldet will not work right! end; + PShell = ^TShell; TShell = record - SpriteID: DWORD; - Live: Boolean; + alive: Boolean; SType: Byte; RAngle: Integer; Timeout: Cardinal; - CX, CY: Integer; Obj: TObj; - procedure positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right! + procedure getMapBox (out x, y, w, h: Integer); inline; + procedure moveBy (dx, dy: Integer); inline; + + procedure positionChanged (); inline; //WARNING! call this after entity position was changed, or coldet will not work right! end; - TCorpse = class (TObject) + TCorpse = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF} private - FModelName: String; FMess: Boolean; FState: Byte; FDamage: Byte; - FColor: TRGB; FObj: TObj; - FAnimation: TAnimation; - FAnimationMask: TAnimation; + FPlayerUID: Word; + FModel: TPlayerModel; public constructor Create(X, Y: Integer; ModelName: String; aMess: Boolean); destructor Destroy(); override; - procedure Damage(Value: Word; vx, vy: Integer); + procedure Damage(Value: Word; SpawnerUID: Word; vx, vy: Integer); procedure Update(); - procedure Draw(); - procedure SaveState(var Mem: TBinMemoryWriter); - procedure LoadState(var Mem: TBinMemoryReader); + procedure SaveState (st: TStream); + procedure LoadState (st: TStream); - procedure positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right! + procedure getMapBox (out x, y, w, h: Integer); inline; + procedure moveBy (dx, dy: Integer); inline; - property Obj: TObj read FObj; + procedure positionChanged (); inline; //WARNING! call this after entity position was changed, or coldet will not work right! + + function ObjPtr (): PObj; inline; + + property Obj: TObj read FObj; // copies object property State: Byte read FState; property Mess: Boolean read FMess; + property Model: TPlayerModel read FModel; end; TTeamStat = Array [TEAM_RED..TEAM_BLUE] of @@ -466,10 +575,12 @@ var gTeamStat: TTeamStat; gFly: Boolean = False; gAimLine: Boolean = False; - gChatBubble: Byte = 0; + gChatBubble: Integer = 0; + gPlayerIndicator: Integer = 1; + gPlayerIndicatorStyle: Integer = 0; gNumBots: Word = 0; - gLMSPID1: Word = 0; - gLMSPID2: Word = 0; + gSpectLatchPID1: Word = 0; + gSpectLatchPID2: Word = 0; MAX_RUNVEL: Integer = 8; VEL_JUMP: Integer = 10; SHELL_TIMEOUT: Cardinal = 60000; @@ -486,39 +597,43 @@ function g_Shells_GetMax(): Word; procedure g_Player_Init(); procedure g_Player_Free(); function g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boolean): Word; -function g_Player_CreateFromState(var Mem: TBinMemoryReader): Word; +function g_Player_CreateFromState (st: TStream): Word; procedure g_Player_Remove(UID: Word); procedure g_Player_ResetTeams(); +procedure g_Player_PreUpdate(); procedure g_Player_UpdateAll(); -procedure g_Player_DrawAll(); -procedure g_Player_DrawDebug(p: TPlayer); -procedure g_Player_DrawHealth(); procedure g_Player_RememberAll(); procedure g_Player_ResetAll(Force, Silent: Boolean); function g_Player_Get(UID: Word): TPlayer; function g_Player_GetCount(): Byte; function g_Player_GetStats(): TPlayerStatArray; function g_Player_ValidName(Name: String): Boolean; -procedure g_Player_CreateCorpse(Player: TPlayer); -procedure g_Player_CreateGibs(fX, fY: Integer; ModelName: String; fColor: TRGB); +function g_Player_CreateCorpse(Player: TPlayer): Integer; +procedure g_Player_CreateGibs (fX, fY, mid: Integer; fColor: TRGB); procedure g_Player_CreateShell(fX, fY, dX, dY: Integer; T: Byte); procedure g_Player_UpdatePhysicalObjects(); -procedure g_Player_DrawCorpses(); -procedure g_Player_DrawShells(); procedure g_Player_RemoveAllCorpses(); -procedure g_Player_Corpses_SaveState(var Mem: TBinMemoryWriter); -procedure g_Player_Corpses_LoadState(var Mem: TBinMemoryReader); -procedure g_Bot_Add(Team, Difficult: Byte); -procedure g_Bot_AddList(Team: Byte; lname: ShortString; num: Integer = -1); +procedure g_Player_Corpses_SaveState (st: TStream); +procedure g_Player_Corpses_LoadState (st: TStream); +procedure g_Player_ResetReady(); +procedure g_Bot_Add(Team, Difficult: Byte; Handicap: Integer = 100); +procedure g_Bot_AddList(Team: Byte; lname: ShortString; num: Integer = -1; Handicap: Integer = 100); procedure g_Bot_MixNames(); procedure g_Bot_RemoveAll(); implementation uses - e_log, g_map, g_items, g_console, SysUtils, g_gfx, Math, - g_options, g_triggers, g_menu, MAPDEF, g_game, - wadreader, g_main, g_monsters, CONFIG, g_language, g_net, g_netmsg; +{$IFDEF ENABLE_HOLMES} + g_holmes, +{$ENDIF} + e_log, g_map, g_items, g_console, g_gfx, Math, r_playermodel, r_gfx, + g_options, g_triggers, g_menu, g_game, g_grid, e_res, + wadreader, g_monsters, CONFIG, g_language, + g_net, g_netmsg, + utils, xstreams; + +const PLR_SAVE_VERSION = 0; type TBotProfile = record @@ -541,26 +656,16 @@ const TIME_RESPAWN1 = 1500; TIME_RESPAWN2 = 2000; TIME_RESPAWN3 = 3000; - AIR_DEF = 360; - AIR_MAX = 1091; - JET_MAX = 540; // ~30 sec PLAYER_SUIT_TIME = 30000; PLAYER_INVUL_TIME = 30000; PLAYER_INVIS_TIME = 35000; FRAG_COMBO_TIME = 3000; VEL_SW = 4; VEL_FLY = 6; - ANGLE_RIGHTUP = 55; - ANGLE_RIGHTDOWN = -35; - ANGLE_LEFTUP = 125; - ANGLE_LEFTDOWN = -145; PLAYER_HEADRECT: TRectWH = (X:24; Y:12; Width:20; Height:12); - WEAPONPOINT: Array [TDirection] of TPoint = ((X:16; Y:32), (X:47; Y:32)); BOT_MAXJUMP = 84; BOT_LONGDIST = 300; BOT_UNSAFEDIST = 128; - TEAMCOLOR: Array [TEAM_RED..TEAM_BLUE] of TRGB = ((R:255; G:0; B:0), - (R:0; G:0; B:255)); DIFFICULT_EASY: TDifficult = (DiagFire: 32; InvisFire: 32; DiagPrecision: 32; FlyPrecision: 32; Cover: 32; CloseJump: 32; WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0)); @@ -602,10 +707,7 @@ var CurrentShell: Integer = 0; BotNames: Array of String; BotList: Array of TBotProfile; - - -procedure TGib.positionChanged (); begin end; -procedure TShell.positionChanged (); begin end; + SavedStates: Array of TPlayerSavedState; function Lerp(X, Y, Factor: Integer): Integer; @@ -678,7 +780,7 @@ begin ok := False; a := 0; -// Åñòü ëè ìåñòî â gPlayers: +// Есть ли место в gPlayers: if gPlayers <> nil then for a := 0 to High(gPlayers) do if gPlayers[a] = nil then @@ -687,14 +789,14 @@ begin Break; end; -// Íåò ìåñòà - ðàñøèðÿåì gPlayers: +// Нет места - расширяем gPlayers: if not ok then begin SetLength(gPlayers, Length(gPlayers)+1); a := High(gPlayers); end; -// Ñîçäàåì îáúåêò èãðîêà: +// Создаем объект игрока: if Bot then gPlayers[a] := TBot.Create() else @@ -704,7 +806,7 @@ begin gPlayers[a].FActualModelName := ModelName; gPlayers[a].SetModel(ModelName); -// Íåò ìîäåëè - ñîçäàíèå íå âîçìîæíî: +// Нет модели - создание не возможно: if gPlayers[a].FModel = nil then begin gPlayers[a].Free(); @@ -728,7 +830,7 @@ begin GM_COOP: gPlayers[a].FTeam := TEAM_COOP; end; -// Åñëè êîìàíäíàÿ èãðà - êðàñèì ìîäåëü â öâåò êîìàíäû: +// Если командная игра - красим модель в цвет команды: gPlayers[a].FColor := Color; if gPlayers[a].FTeam in [TEAM_RED, TEAM_BLUE] then gPlayers[a].FModel.Color := TEAMCOLOR[gPlayers[a].FTeam] @@ -736,52 +838,41 @@ begin gPlayers[a].FModel.Color := Color; gPlayers[a].FUID := g_CreateUID(UID_PLAYER); - gPlayers[a].FLive := False; + gPlayers[a].FAlive := False; Result := gPlayers[a].FUID; end; -function g_Player_CreateFromState(var Mem: TBinMemoryReader): Word; +function g_Player_CreateFromState (st: TStream): Word; var a, i: Integer; ok, Bot: Boolean; - sig: DWORD; b: Byte; begin - Result := 0; - if Mem = nil then - Exit; + result := 0; + if (st = nil) then exit; //??? -// Ñèãíàòóðà èãðîêà: - Mem.ReadDWORD(sig); - if sig <> PLAYER_SIGNATURE then // 'PLYR' - begin - raise EBinSizeError.Create('g_Player_CreateFromState: Wrong Player Signature'); - end; + // Сигнатура игрока + if not utils.checkSign(st, 'PLYR') then raise XStreamError.Create('invalid player signature'); + if (utils.readByte(st) <> PLR_SAVE_VERSION) then raise XStreamError.Create('invalid player version'); -// Áîò èëè ÷åëîâåê: - Mem.ReadBoolean(Bot); + // Бот или человек: + Bot := utils.readBool(st); - ok := False; + ok := false; a := 0; -// Åñòü ëè ìåñòî â gPlayers: - if gPlayers <> nil then - for a := 0 to High(gPlayers) do - if gPlayers[a] = nil then - begin - ok := True; - Break; - end; + // Есть ли место в gPlayers: + for a := 0 to High(gPlayers) do if (gPlayers[a] = nil) then begin ok := true; break; end; -// Íåò ìåñòà - ðàñøèðÿåì gPlayers: + // Нет места - расширяем gPlayers if not ok then begin SetLength(gPlayers, Length(gPlayers)+1); a := High(gPlayers); end; -// Ñîçäàåì îáúåêò èãðîêà: + // Создаем объект игрока if Bot then gPlayers[a] := TBot.Create() else @@ -789,133 +880,117 @@ begin gPlayers[a].FIamBot := Bot; gPlayers[a].FPhysics := True; -// UID èãðîêà: - Mem.ReadWord(gPlayers[a].FUID); -// Èìÿ èãðîêà: - Mem.ReadString(gPlayers[a].FName); -// Êîìàíäà: - Mem.ReadByte(gPlayers[a].FTeam); + // UID игрока + gPlayers[a].FUID := utils.readWord(st); + // Имя игрока + gPlayers[a].FName := utils.readStr(st); + // Команда + gPlayers[a].FTeam := utils.readByte(st); gPlayers[a].FPreferredTeam := gPlayers[a].FTeam; -// Æèâ ëè: - Mem.ReadBoolean(gPlayers[a].FLive); -// Èçðàñõîäîâàë ëè âñå æèçíè: - Mem.ReadBoolean(gPlayers[a].FNoRespawn); -// Íàïðàâëåíèå: - Mem.ReadByte(b); - if b = 1 then - gPlayers[a].FDirection := D_LEFT - else // b = 2 - gPlayers[a].FDirection := D_RIGHT; -// Çäîðîâüå: - Mem.ReadInt(gPlayers[a].FHealth); -// Æèçíè: - Mem.ReadByte(gPlayers[a].FLives); -// Áðîíÿ: - Mem.ReadInt(gPlayers[a].FArmor); -// Çàïàñ âîçäóõà: - Mem.ReadInt(gPlayers[a].FAir); -// Çàïàñ ãîðþ÷åãî: - Mem.ReadInt(gPlayers[a].FJetFuel); -// Áîëü: - Mem.ReadInt(gPlayers[a].FPain); -// Óáèë: - Mem.ReadInt(gPlayers[a].FKills); -// Óáèë ìîíñòðîâ: - Mem.ReadInt(gPlayers[a].FMonsterKills); -// Ôðàãîâ: - Mem.ReadInt(gPlayers[a].FFrags); -// Ôðàãîâ ïîäðÿä: - Mem.ReadByte(gPlayers[a].FFragCombo); -// Âðåìÿ ïîñëåäíåãî ôðàãà: - Mem.ReadDWORD(gPlayers[a].FLastFrag); -// Ñìåðòåé: - Mem.ReadInt(gPlayers[a].FDeath); -// Êàêîé ôëàã íåñåò: - Mem.ReadByte(gPlayers[a].FFlag); -// Íàøåë ñåêðåòîâ: - Mem.ReadInt(gPlayers[a].FSecrets); -// Òåêóùåå îðóæèå: - Mem.ReadByte(gPlayers[a].FCurrWeap); -// Ñëåäóþùåå æåëàåìîå îðóæèå: - Mem.ReadWord(gPlayers[a].FNextWeap); -// ...è ïàóçà: - Mem.ReadByte(gPlayers[a].FNextWeapDelay); -// Âðåìÿ çàðÿäêè BFG: - Mem.ReadSmallInt(gPlayers[a].FBFGFireCounter); -// Áóôåð óðîíà: - Mem.ReadInt(gPlayers[a].FDamageBuffer); -// Ïîñëåäíèé óäàðèâøèé: - Mem.ReadWord(gPlayers[a].FLastSpawnerUID); -// Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà: - Mem.ReadByte(gPlayers[a].FLastHit); -// Îáúåêò èãðîêà: - Obj_LoadState(@gPlayers[a].FObj, Mem); -// Òåêóùåå êîëè÷åñòâî ïàòðîíîâ: - for i := A_BULLETS to A_HIGH do - Mem.ReadWord(gPlayers[a].FAmmo[i]); -// Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ: - for i := A_BULLETS to A_HIGH do - Mem.ReadWord(gPlayers[a].FMaxAmmo[i]); -// Íàëè÷èå îðóæèÿ: - for i := WP_FIRST to WP_LAST do - Mem.ReadBoolean(gPlayers[a].FWeapon[i]); -// Âðåìÿ ïåðåçàðÿäêè îðóæèÿ: - for i := WP_FIRST to WP_LAST do - Mem.ReadWord(gPlayers[a].FReloading[i]); -// Íàëè÷èå ðþêçàêà: - Mem.ReadByte(b); - if b = 1 then - Include(gPlayers[a].FRulez, R_ITEM_BACKPACK); -// Íàëè÷èå êðàñíîãî êëþ÷à: - Mem.ReadByte(b); - if b = 1 then - Include(gPlayers[a].FRulez, R_KEY_RED); -// Íàëè÷èå çåëåíîãî êëþ÷à: - Mem.ReadByte(b); - if b = 1 then - Include(gPlayers[a].FRulez, R_KEY_GREEN); -// Íàëè÷èå ñèíåãî êëþ÷à: - Mem.ReadByte(b); - if b = 1 then - Include(gPlayers[a].FRulez, R_KEY_BLUE); -// Íàëè÷èå áåðñåðêà: - Mem.ReadByte(b); - if b = 1 then - Include(gPlayers[a].FRulez, R_BERSERK); -// Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ: - for i := MR_SUIT to MR_MAX do - Mem.ReadDWORD(gPlayers[a].FMegaRulez[i]); -// Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà: - for i := T_RESPAWN to T_FLAGCAP do - Mem.ReadDWORD(gPlayers[a].FTime[i]); - -// Íàçâàíèå ìîäåëè: - Mem.ReadString(gPlayers[a].FActualModelName); -// Öâåò ìîäåëè: - Mem.ReadByte(gPlayers[a].FColor.R); - Mem.ReadByte(gPlayers[a].FColor.G); - Mem.ReadByte(gPlayers[a].FColor.B); -// Îáíîâëÿåì ìîäåëü èãðîêà: + // Жив ли + gPlayers[a].FAlive := utils.readBool(st); + // Израсходовал ли все жизни + gPlayers[a].FNoRespawn := utils.readBool(st); + // Направление + b := utils.readByte(st); + if b = 1 then gPlayers[a].FDirection := TDirection.D_LEFT else gPlayers[a].FDirection := TDirection.D_RIGHT; // b = 2 + // Здоровье + gPlayers[a].FHealth := utils.readLongInt(st); + // Фора + gPlayers[a].FHandicap := utils.readLongInt(st); + // Жизни + gPlayers[a].FLives := utils.readByte(st); + // Броня + gPlayers[a].FArmor := utils.readLongInt(st); + // Запас воздуха + gPlayers[a].FAir := utils.readLongInt(st); + // Запас горючего + gPlayers[a].FJetFuel := utils.readLongInt(st); + // Боль + gPlayers[a].FPain := utils.readLongInt(st); + // Убил + gPlayers[a].FKills := utils.readLongInt(st); + // Убил монстров + gPlayers[a].FMonsterKills := utils.readLongInt(st); + // Фрагов + gPlayers[a].FFrags := utils.readLongInt(st); + // Фрагов подряд + gPlayers[a].FFragCombo := utils.readByte(st); + // Время последнего фрага + gPlayers[a].FLastFrag := utils.readLongWord(st); + // Смертей + gPlayers[a].FDeath := utils.readLongInt(st); + // Какой флаг несет + gPlayers[a].FFlag := utils.readByte(st); + // Нашел секретов + gPlayers[a].FSecrets := utils.readLongInt(st); + // Текущее оружие + gPlayers[a].FCurrWeap := utils.readByte(st); + // Следующее желаемое оружие + gPlayers[a].FNextWeap := utils.readWord(st); + // ...и пауза + gPlayers[a].FNextWeapDelay := utils.readByte(st); + // Время зарядки BFG + gPlayers[a].FBFGFireCounter := utils.readSmallInt(st); + // Буфер урона + gPlayers[a].FDamageBuffer := utils.readLongInt(st); + // Последний ударивший + gPlayers[a].FLastSpawnerUID := utils.readWord(st); + // Тип последнего полученного урона + gPlayers[a].FLastHit := utils.readByte(st); + // Объект игрока: + Obj_LoadState(@gPlayers[a].FObj, st); + // Текущее количество патронов + for i := A_BULLETS to A_HIGH do gPlayers[a].FAmmo[i] := utils.readWord(st); + // Максимальное количество патронов + for i := A_BULLETS to A_HIGH do gPlayers[a].FMaxAmmo[i] := utils.readWord(st); + // Наличие оружия + for i := WP_FIRST to WP_LAST do gPlayers[a].FWeapon[i] := utils.readBool(st); + // Время перезарядки оружия + for i := WP_FIRST to WP_LAST do gPlayers[a].FReloading[i] := utils.readWord(st); + // Наличие рюкзака + if utils.readBool(st) then Include(gPlayers[a].FRulez, R_ITEM_BACKPACK); + // Наличие красного ключа + if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_RED); + // Наличие зеленого ключа + if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_GREEN); + // Наличие синего ключа + if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_BLUE); + // Наличие берсерка + if utils.readBool(st) then Include(gPlayers[a].FRulez, R_BERSERK); + // Время действия специальных предметов + for i := MR_SUIT to MR_MAX do gPlayers[a].FMegaRulez[i] := utils.readLongWord(st); + // Время до повторного респауна, смены оружия, исользования, захвата флага + for i := T_RESPAWN to T_FLAGCAP do gPlayers[a].FTime[i] := utils.readLongWord(st); + + // Название модели: + gPlayers[a].FActualModelName := utils.readStr(st); + // Цвет модели + gPlayers[a].FColor.R := utils.readByte(st); + gPlayers[a].FColor.G := utils.readByte(st); + gPlayers[a].FColor.B := utils.readByte(st); + // Обновляем модель игрока gPlayers[a].SetModel(gPlayers[a].FActualModelName); -// Íåò ìîäåëè - ñîçäàíèå íå âîçìîæíî: - if gPlayers[a].FModel = nil then + // Нет модели - создание невозможно + if (gPlayers[a].FModel = nil) then begin gPlayers[a].Free(); gPlayers[a] := nil; g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], [gPlayers[a].FActualModelName])); - Exit; + exit; end; -// Åñëè êîìàíäíàÿ èãðà - êðàñèì ìîäåëü â öâåò êîìàíäû: + // Если командная игра - красим модель в цвет команды if gGameSettings.GameMode in [GM_TDM, GM_CTF] then gPlayers[a].FModel.Color := TEAMCOLOR[gPlayers[a].FTeam] else gPlayers[a].FModel.Color := gPlayers[a].FColor; - Result := gPlayers[a].FUID; + result := gPlayers[a].FUID; end; + procedure g_Player_ResetTeams(); var a: Integer; @@ -944,20 +1019,20 @@ begin end; end; -procedure g_Bot_Add(Team, Difficult: Byte); +procedure g_Bot_Add(Team, Difficult: Byte; Handicap: Integer = 100); var - m: SArray; + m: SSArray; _name, _model: String; a, tr, tb: Integer; begin if not g_Game_IsServer then Exit; -// Ñïèñîê íàçâàíèé ìîäåëåé: +// Список названий моделей: m := g_PlayerModel_GetNames(); if m = nil then Exit; -// Êîìàíäà: +// Команда: if (gGameSettings.GameType = GT_SINGLE) or (gGameSettings.GameMode = GM_COOP) then Team := TEAM_COOP // COOP else @@ -966,7 +1041,7 @@ begin else if Team = TEAM_NONE then // CTF / TDM begin - // Àâòîáàëàíñ êîìàíä: + // Автобаланс команд: tr := 0; tb := 0; @@ -992,7 +1067,7 @@ begin Team := TEAM_BLUE; end; -// Âûáèðàåì áîòó èìÿ: +// Выбираем боту имя: _name := ''; if BotNames <> nil then for a := 0 to High(BotNames) do @@ -1002,23 +1077,21 @@ begin Break; end; -// Èìåíè íåò, çàäàåì ñëó÷àéíîå: - if _name = '' then - repeat - _name := Format('DFBOT%.2d', [Random(100)]); - until g_Player_ValidName(_name); - -// Âûáèðàåì ñëó÷àéíóþ ìîäåëü: +// Выбираем случайную модель: _model := m[Random(Length(m))]; -// Ñîçäàåì áîòà: +// Создаем бота: with g_Player_Get(g_Player_Create(_model, _RGB(Min(Random(9)*32, 255), Min(Random(9)*32, 255), Min(Random(9)*32, 255)), Team, True)) as TBot do begin - Name := _name; + // Если имени нет, делаем его из UID бота + if _name = '' then + Name := Format('DFBOT%.5d', [UID]) + else + Name := _name; case Difficult of 1: FDifficult := DIFFICULT_EASY; @@ -1033,6 +1106,8 @@ begin //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a]; end; + FHandicap := Handicap; + g_Console_Add(Format(_lc[I_PLAYER_JOIN], [Name]), True); if g_Game_IsNet then MH_SEND_PlayerCreate(UID); @@ -1041,20 +1116,20 @@ begin end; end; -procedure g_Bot_AddList(Team: Byte; lName: ShortString; num: Integer = -1); +procedure g_Bot_AddList(Team: Byte; lName: ShortString; num: Integer = -1; Handicap: Integer = 100); var - m: SArray; + m: SSArray; _name, _model: String; a: Integer; begin if not g_Game_IsServer then Exit; -// Ñïèñîê íàçâàíèé ìîäåëåé: +// Список названий моделей: m := g_PlayerModel_GetNames(); if m = nil then Exit; -// Êîìàíäà: +// Команда: if (gGameSettings.GameType = GT_SINGLE) or (gGameSettings.GameMode = GM_COOP) then Team := TEAM_COOP // COOP else @@ -1064,7 +1139,7 @@ begin if Team = TEAM_NONE then Team := BotList[num].team; // CTF / TDM -// Âûáèðàåì íàñòðîéêè áîòà èç ñïèñêà ïî íîìåðó èëè èìåíè: +// Выбираем настройки бота из списка по номеру или имени: lName := AnsiLowerCase(lName); if (num < 0) or (num > Length(BotList)-1) then num := -1; @@ -1078,21 +1153,21 @@ begin if num = -1 then Exit; -// Èìÿ áîòà: +// Имя бота: _name := BotList[num].name; -// Çàíÿòî - âûáèðàåì ñëó÷àéíîå: +// Занято - выбираем случайное: if not g_Player_ValidName(_name) then repeat _name := Format('DFBOT%.2d', [Random(100)]); until g_Player_ValidName(_name); -// Ìîäåëü: +// Модель: _model := BotList[num].model; -// Íåò òàêîé - âûáèðàåì ñëó÷àéíóþ: +// Нет такой - выбираем случайную: if not InSArray(_model, m) then _model := m[Random(Length(m))]; -// Ñîçäàåì áîòà: +// Создаем бота: with g_Player_Get(g_Player_Create(_model, BotList[num].color, Team, True)) as TBot do begin Name := _name; @@ -1104,6 +1179,8 @@ begin FDifficult.Cover := BotList[num].cover; FDifficult.CloseJump := BotList[num].close_jump; + FHandicap := Handicap; + for a := WP_FIRST to WP_LAST do begin FDifficult.WeaponPrior[a] := BotList[num].w_prior1[a]; @@ -1180,15 +1257,17 @@ var s: String; a, b: Integer; config: TConfig; - sa: SArray; + sa: SSArray; + path: AnsiString; begin BotNames := nil; - if not FileExists(DataDir + BOTNAMES_FILENAME) then + path := BOTNAMES_FILENAME; + if e_FindResource(DataDirs, path) = false then Exit; -// ×èòàåì âîçìîæíûå èìåíà áîòîâ èç ôàéëà: - AssignFile(F, DataDir + BOTNAMES_FILENAME); +// Читаем возможные имена ботов из файла: + AssignFile(F, path); Reset(F); while not EOF(F) do @@ -1205,11 +1284,11 @@ begin CloseFile(F); -// Ïåðåìåøèâàåì èõ: +// Перемешиваем их: g_Bot_MixNames(); -// ×èòàåì ôàéë ñ ïàðàìåòðàìè áîòîâ: - config := TConfig.CreateFile(DataDir + BOTLIST_FILENAME); +// Читаем файл с параметрами ботов: + config := TConfig.CreateFile(path); BotList := nil; a := 0; @@ -1219,38 +1298,38 @@ begin with BotList[High(BotList)] do begin - // Èìÿ áîòà: + // Имя бота: name := config.ReadStr(IntToStr(a), 'name', ''); - // Ìîäåëü: + // Модель: model := config.ReadStr(IntToStr(a), 'model', ''); - // Êîìàíäà: + // Команда: if config.ReadStr(IntToStr(a), 'team', 'red') = 'red' then team := TEAM_RED else team := TEAM_BLUE; - // Öâåò ìîäåëè: + // Цвет модели: sa := parse(config.ReadStr(IntToStr(a), 'color', '')); color.R := StrToIntDef(sa[0], 0); color.G := StrToIntDef(sa[1], 0); color.B := StrToIntDef(sa[2], 0); - // Âåðîÿòíîñòü ñòðåëüáû ïîä óãëîì: + // Вероятность стрельбы под углом: diag_fire := config.ReadInt(IntToStr(a), 'diag_fire', 0); - // Âåðîÿòíîñòü îòâåòíîãî îãíÿ ïî íåâèäèìîìó ñîïåðíèêó: + // Вероятность ответного огня по невидимому сопернику: invis_fire := config.ReadInt(IntToStr(a), 'invis_fire', 0); - // Òî÷íîñòü ñòðåëüáû ïîä óãëîì: + // Точность стрельбы под углом: diag_precision := config.ReadInt(IntToStr(a), 'diag_precision', 0); - // Òî÷íîñòü ñòðåëüáû â ïîëåòå: + // Точность стрельбы в полете: fly_precision := config.ReadInt(IntToStr(a), 'fly_precision', 0); - // Òî÷íîñòü óêëîíåíèÿ îò ñíàðÿäîâ: + // Точность уклонения от снарядов: cover := config.ReadInt(IntToStr(a), 'cover', 0); - // Âåðîÿòíîñòü ïðûæêà ïðè ïðèáëèæåíèè ñîïåðíèêà: + // Вероятность прыжка при приближении соперника: close_jump := config.ReadInt(IntToStr(a), 'close_jump', 0); - // Ïðèîðèòåòû îðóæèÿ äëÿ äàëüíåãî áîÿ: + // Приоритеты оружия для дальнего боя: sa := parse(config.ReadStr(IntToStr(a), 'w_prior1', '')); if Length(sa) = 10 then for b := 0 to 9 do w_prior1[b] := EnsureRange(StrToInt(sa[b]), 0, 9); - // Ïðèîðèòåòû îðóæèÿ äëÿ áëèæíåãî áîÿ: + // Приоритеты оружия для ближнего боя: sa := parse(config.ReadStr(IntToStr(a), 'w_prior2', '')); if Length(sa) = 10 then for b := 0 to 9 do @@ -1266,6 +1345,7 @@ begin end; config.Free(); + SetLength(SavedStates, 0); end; procedure g_Player_Free(); @@ -1289,6 +1369,17 @@ begin gPlayer1 := nil; gPlayer2 := nil; + SetLength(SavedStates, 0); +end; + +procedure g_Player_PreUpdate(); +var + i: Integer; +begin + if gPlayers = nil then Exit; + for i := 0 to High(gPlayers) do + if gPlayers[i] <> nil then + gPlayers[i].PreUpdate(); end; procedure g_Player_UpdateAll(); @@ -1317,55 +1408,6 @@ begin //e_WriteLog('***g_Player_UpdateAll: EXIT', MSG_WARNING); end; -procedure g_Player_DrawAll(); -var - i: Integer; -begin - if gPlayers = nil then Exit; - - for i := 0 to High(gPlayers) do - if gPlayers[i] <> nil then - if gPlayers[i] is TPlayer then gPlayers[i].Draw() - else TBot(gPlayers[i]).Draw(); -end; - -procedure g_Player_DrawDebug(p: TPlayer); -var - fW, fH: Byte; -begin - if p = nil then Exit; - if (@p.FObj) = nil then Exit; - - e_TextureFontGetSize(gStdFont, fW, fH); - - e_TextureFontPrint(0, 0 , 'Pos X: ' + IntToStr(p.FObj.X), gStdFont); - e_TextureFontPrint(0, fH , 'Pos Y: ' + IntToStr(p.FObj.Y), gStdFont); - e_TextureFontPrint(0, fH * 2, 'Vel X: ' + IntToStr(p.FObj.Vel.X), gStdFont); - e_TextureFontPrint(0, fH * 3, 'Vel Y: ' + IntToStr(p.FObj.Vel.Y), gStdFont); - e_TextureFontPrint(0, fH * 4, 'Acc X: ' + IntToStr(p.FObj.Accel.X), gStdFont); - e_TextureFontPrint(0, fH * 5, 'Acc Y: ' + IntToStr(p.FObj.Accel.Y), gStdFont); -end; - -procedure g_Player_DrawHealth(); -var - i: Integer; - fW, fH: Byte; -begin - if gPlayers = nil then Exit; - e_TextureFontGetSize(gStdFont, fW, fH); - - for i := 0 to High(gPlayers) do - if gPlayers[i] <> nil then - begin - e_TextureFontPrint(gPlayers[i].FObj.X + gPlayers[i].FObj.Rect.X, - gPlayers[i].FObj.Y + gPlayers[i].FObj.Rect.Y + gPlayers[i].FObj.Rect.Height - fH * 2, - IntToStr(gPlayers[i].FHealth), gStdFont); - e_TextureFontPrint(gPlayers[i].FObj.X + gPlayers[i].FObj.Rect.X, - gPlayers[i].FObj.Y + gPlayers[i].FObj.Rect.Y + gPlayers[i].FObj.Rect.Height - fH, - IntToStr(gPlayers[i].FArmor), gStdFont); - end; -end; - function g_Player_Get(UID: Word): TPlayer; var a: Integer; @@ -1412,6 +1454,7 @@ begin SetLength(Result, Length(Result)+1); with Result[High(Result)] do begin + Num := a; Ping := gPlayers[a].FPing; Loss := gPlayers[a].FLoss; Name := gPlayers[a].FName; @@ -1422,16 +1465,33 @@ begin Color := gPlayers[a].FModel.Color; Lives := gPlayers[a].FLives; Spectator := gPlayers[a].FSpectator; + UID := gPlayers[a].FUID; end; end; end; +procedure g_Player_ResetReady(); +var + a: Integer; +begin + if not g_Game_IsServer then Exit; + if gPlayers = nil then Exit; + + for a := 0 to High(gPlayers) do + if gPlayers[a] <> nil then + begin + gPlayers[a].FReady := False; + if g_Game_IsNet then + MH_SEND_GameEvent(NET_EV_INTER_READY, gPlayers[a].UID, 'N'); + end; +end; + procedure g_Player_RememberAll; var i: Integer; begin for i := Low(gPlayers) to High(gPlayers) do - if (gPlayers[i] <> nil) and gPlayers[i].Live then + if (gPlayers[i] <> nil) and gPlayers[i].alive then gPlayers[i].RememberState; end; @@ -1460,13 +1520,25 @@ begin end; end; -procedure g_Player_CreateCorpse(Player: TPlayer); +function g_Player_CreateCorpse(Player: TPlayer): Integer; var + i: Integer; find_id: DWORD; ok: Boolean; begin - if Player.Live then + Result := -1; + + if Player.alive then Exit; + +// Разрываем связь с прежним трупом: + i := Player.FCorpse; + if (i >= 0) and (i < Length(gCorpses)) then + begin + if (gCorpses[i] <> nil) and (gCorpses[i].FPlayerUID = Player.FUID) then + gCorpses[i].FPlayerUID := 0; + end; + if Player.FObj.Y >= gMapInfo.Height+128 then Exit; @@ -1488,51 +1560,41 @@ begin if not ok then find_id := Random(Length(gCorpses)); - gCorpses[find_id] := TCorpse.Create(FObj.X, FObj.Y, FModel.Name, FHealth < -20); - gCorpses[find_id].FColor := FModel.Color; + gCorpses[find_id] := TCorpse.Create(FObj.X, FObj.Y, FModel.GetName(), FHealth < -20); + gCorpses[find_id].FModel.Color := FModel.Color; gCorpses[find_id].FObj.Vel := FObj.Vel; gCorpses[find_id].FObj.Accel := FObj.Accel; + gCorpses[find_id].FPlayerUID := FUID; + + Result := find_id; end else - g_Player_CreateGibs(FObj.X + PLAYER_RECT_CX, - FObj.Y + PLAYER_RECT_CY, - FModel.Name, FModel.Color); + g_Player_CreateGibs(FObj.X + PLAYER_RECT_CX, FObj.Y + PLAYER_RECT_CY, FModel.id, FModel.Color); end; end; procedure g_Player_CreateShell(fX, fY, dX, dY: Integer; T: Byte); -var - SID: DWORD; begin if (gShells = nil) or (Length(gShells) = 0) then Exit; with gShells[CurrentShell] do begin - SpriteID := 0; g_Obj_Init(@Obj); Obj.Rect.X := 0; Obj.Rect.Y := 0; if T = SHELL_BULLET then begin - if g_Texture_Get('TEXTURE_SHELL_BULLET', SID) then - SpriteID := SID; - CX := 2; - CY := 1; Obj.Rect.Width := 4; Obj.Rect.Height := 2; end else begin - if g_Texture_Get('TEXTURE_SHELL_SHELL', SID) then - SpriteID := SID; - CX := 4; - CY := 2; Obj.Rect.Width := 7; Obj.Rect.Height := 3; end; SType := T; - Live := True; + alive := True; Obj.X := fX; Obj.Y := fY; g_Obj_Push(@Obj, dX + Random(4)-Random(4), dY-Random(4)); @@ -1547,34 +1609,38 @@ begin end; end; -procedure g_Player_CreateGibs(fX, fY: Integer; ModelName: string; fColor: TRGB); +procedure g_Player_CreateGibs (fX, fY, mid: Integer; fColor: TRGB); var a: Integer; GibsArray: TGibsArray; + Blood: TModelBlood; begin + if mid = -1 then + Exit; if (gGibs = nil) or (Length(gGibs) = 0) then Exit; - if not g_PlayerModel_GetGibs(ModelName, GibsArray) then + if not g_PlayerModel_GetGibs(mid, GibsArray) then Exit; + Blood := PlayerModelsArray[mid].Blood; for a := 0 to High(GibsArray) do with gGibs[CurrentGib] do begin + ModelID := mid; + GibID := GibsArray[a]; Color := fColor; - ID := GibsArray[a].ID; - MaskID := GibsArray[a].MaskID; - Live := True; + alive := True; g_Obj_Init(@Obj); - Obj.Rect := GibsArray[a].Rect; - Obj.X := fX-GibsArray[a].Rect.X-(GibsArray[a].Rect.Width div 2); - Obj.Y := fY-GibsArray[a].Rect.Y-(GibsArray[a].Rect.Height div 2); + Obj.Rect := r_PlayerModel_GetGibRect(ModelID, GibID); + Obj.X := fX - Obj.Rect.X - (Obj.Rect.Width div 2); + Obj.Y := fY - Obj.Rect.Y - (Obj.Rect.Height div 2); g_Obj_PushA(@Obj, 25 + Random(10), Random(361)); positionChanged(); // this updates spatial accelerators RAngle := Random(360); if gBloodCount > 0 then g_GFX_Blood(fX, fY, 16*gBloodCount+Random(5*gBloodCount), -16+Random(33), -16+Random(33), - Random(48), Random(48), 150, 0, 0); + Random(48), Random(48), Blood.R, Blood.G, Blood.B, Blood.Kind); if CurrentGib >= High(gGibs) then CurrentGib := 0 @@ -1601,23 +1667,26 @@ var end; begin -// Êóñêè ìÿñà: +// Куски мяса: if gGibs <> nil then for i := 0 to High(gGibs) do - if gGibs[i].Live then + if gGibs[i].alive then with gGibs[i] do begin + Obj.oldX := Obj.X; + Obj.oldY := Obj.Y; + vel := Obj.Vel; mr := g_Obj_Move(@Obj, True, False, True); positionChanged(); // this updates spatial accelerators if WordBool(mr and MOVE_FALLOUT) then begin - Live := False; + alive := False; Continue; end; - // Îòëåòàåò îò óäàðà î ñòåíó/ïîòîëîê/ïîë: + // Отлетает от удара о стену/потолок/пол: if WordBool(mr and MOVE_HITWALL) then Obj.Vel.X := -(vel.X div 2); if WordBool(mr and (MOVE_HITCEIL or MOVE_HITLAND)) then @@ -1634,12 +1703,12 @@ begin RAngle := (360 - (Abs(RAngle) mod 360)) mod 360; end; - // Ñîïðîòèâëåíèå âîçäóõà äëÿ êóñêà òðóïà: + // Сопротивление воздуха для куска трупа: if gTime mod (GAME_TICK*3) = 0 then Obj.Vel.X := z_dec(Obj.Vel.X, 1); end; -// Òðóïû: +// Трупы: if gCorpses <> nil then for i := 0 to High(gCorpses) do if gCorpses[i] <> nil then @@ -1651,23 +1720,26 @@ begin else gCorpses[i].Update(); -// Ãèëüçû: +// Гильзы: if gShells <> nil then for i := 0 to High(gShells) do - if gShells[i].Live then + if gShells[i].alive then with gShells[i] do begin + Obj.oldX := Obj.X; + Obj.oldY := Obj.Y; + vel := Obj.Vel; mr := g_Obj_Move(@Obj, True, False, True); positionChanged(); // this updates spatial accelerators if WordBool(mr and MOVE_FALLOUT) or (gShells[i].Timeout < gTime) then begin - Live := False; + alive := False; Continue; end; - // Îòëåòàåò îò óäàðà î ñòåíó/ïîòîëîê/ïîë: + // Отлетает от удара о стену/потолок/пол: if WordBool(mr and MOVE_HITWALL) then begin Obj.Vel.X := -(vel.X div 2); @@ -1700,56 +1772,48 @@ begin end; end; -procedure g_Player_DrawCorpses(); -var - i: Integer; - a: TPoint; -begin - if gGibs <> nil then - for i := 0 to High(gGibs) do - if gGibs[i].Live then - with gGibs[i] do - begin - if not g_Obj_Collide(sX, sY, sWidth, sHeight, @Obj) then - Continue; - a.X := Obj.Rect.X+(Obj.Rect.Width div 2); - a.y := Obj.Rect.Y+(Obj.Rect.Height div 2); +procedure TGib.getMapBox (out x, y, w, h: Integer); inline; +begin + x := Obj.X+Obj.Rect.X; + y := Obj.Y+Obj.Rect.Y; + w := Obj.Rect.Width; + h := Obj.Rect.Height; +end; - e_DrawAdv(ID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, M_NONE); +procedure TGib.moveBy (dx, dy: Integer); inline; +begin + if (dx <> 0) or (dy <> 0) then + begin + Obj.X += dx; + Obj.Y += dy; + positionChanged(); + end; +end; - e_Colors := Color; - e_DrawAdv(MaskID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, M_NONE); - e_Colors.R := 255; - e_Colors.G := 255; - e_Colors.B := 255; - end; - if gCorpses <> nil then - for i := 0 to High(gCorpses) do - if gCorpses[i] <> nil then - gCorpses[i].Draw(); +procedure TShell.getMapBox (out x, y, w, h: Integer); inline; +begin + x := Obj.X; + y := Obj.Y; + w := Obj.Rect.Width; + h := Obj.Rect.Height; end; -procedure g_Player_DrawShells(); -var - i: Integer; - a: TPoint; +procedure TShell.moveBy (dx, dy: Integer); inline; begin - if gShells <> nil then - for i := 0 to High(gShells) do - if gShells[i].Live then - with gShells[i] do - begin - if not g_Obj_Collide(sX, sY, sWidth, sHeight, @Obj) then - Continue; + if (dx <> 0) or (dy <> 0) then + begin + Obj.X += dx; + Obj.Y += dy; + positionChanged(); + end; +end; - a.X := CX; - a.Y := CY; - e_DrawAdv(SpriteID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, M_NONE); - end; -end; +procedure TGib.positionChanged (); inline; begin end; +procedure TShell.positionChanged (); inline; begin end; + procedure g_Player_RemoveAllCorpses(); var @@ -1770,78 +1834,70 @@ begin SetLength(gCorpses, MaxCorpses); end; -procedure g_Player_Corpses_SaveState(var Mem: TBinMemoryWriter); +procedure g_Player_Corpses_SaveState (st: TStream); var count, i: Integer; - b: Boolean; begin -// Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ òðóïîâ: + // Считаем количество существующих трупов count := 0; - if gCorpses <> nil then - for i := 0 to High(gCorpses) do - if gCorpses[i] <> nil then - count := count + 1; - - Mem := TBinMemoryWriter.Create((count+1) * 128); + for i := 0 to High(gCorpses) do if (gCorpses[i] <> nil) then Inc(count); -// Êîëè÷åñòâî òðóïîâ: - Mem.WriteInt(count); + // Количество трупов + utils.writeInt(st, LongInt(count)); - if count = 0 then - Exit; + if (count = 0) then exit; -// Ñîõðàíÿåì òðóïû: + // Сохраняем трупы for i := 0 to High(gCorpses) do + begin if gCorpses[i] <> nil then begin - // Íàçâàíèå ìîäåëè: - Mem.WriteString(gCorpses[i].FModelName); - // Òèï ñìåðòè: - b := gCorpses[i].Mess; - Mem.WriteBoolean(b); - // Ñîõðàíÿåì äàííûå òðóïà: - gCorpses[i].SaveState(Mem); + // Название модели + utils.writeStr(st, gCorpses[i].FModel.GetName()); + // Тип смерти + utils.writeBool(st, gCorpses[i].Mess); + // Сохраняем данные трупа: + gCorpses[i].SaveState(st); end; + end; end; -procedure g_Player_Corpses_LoadState(var Mem: TBinMemoryReader); + +procedure g_Player_Corpses_LoadState (st: TStream); var count, i: Integer; str: String; b: Boolean; begin - if Mem = nil then - Exit; + assert(st <> nil); g_Player_RemoveAllCorpses(); -// Êîëè÷åñòâî òðóïîâ: - Mem.ReadInt(count); + // Количество трупов: + count := utils.readLongInt(st); + if (count < 0) or (count > Length(gCorpses)) then raise XStreamError.Create('invalid number of corpses'); - if count > Length(gCorpses) then - begin - raise EBinSizeError.Create('g_Player_Corpses_LoadState: Too Many Corpses'); - end; + if (count = 0) then exit; - if count = 0 then - Exit; - -// Çàãðóæàåì òðóïû: + // Загружаем трупы for i := 0 to count-1 do begin - // Íàçâàíèå ìîäåëè: - Mem.ReadString(str); - // Òèï ñìåðòè: - Mem.ReadBoolean(b); - // Ñîçäàåì òðóï: + // Название модели: + str := utils.readStr(st); + // Тип смерти + b := utils.readBool(st); + // Создаем труп gCorpses[i] := TCorpse.Create(0, 0, str, b); - // Çàãðóæàåì äàííûå òðóïà: - gCorpses[i].LoadState(Mem); + // Загружаем данные трупа + gCorpses[i].LoadState(st); end; end; + { T P l a y e r : } +function TPlayer.isValidViewPort (): Boolean; inline; begin result := (viewPortW > 0) and (viewPortH > 0); end; + procedure TPlayer.BFGHit(); begin g_Weapon_BFGHit(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2), @@ -1900,13 +1956,18 @@ begin if FModel <> nil then FModel.Color := Color; end; +function TPlayer.GetColor(): TRGB; +begin + result := FModel.Color; +end; + procedure TPlayer.SwitchTeam; begin if g_Game_IsClient then Exit; if not (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then Exit; - if gGameOn and FLive then + if gGameOn and FAlive then Kill(K_SIMPLEKILL, FUID, HIT_SELF); if FTeam = TEAM_RED then @@ -1949,12 +2010,12 @@ var r: Boolean; begin if gItems = nil then Exit; - if not FLive then Exit; + if not FAlive then Exit; for i := 0 to High(gItems) do with gItems[i] do begin - if (ItemType <> ITEM_NONE) and Live then + if (ItemType <> ITEM_NONE) and alive then if g_Obj_Collide(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width, PLAYER_RECT.Height, @Obj) then begin @@ -1966,7 +2027,7 @@ begin g_Sound_PlayExAt('SOUND_ITEM_GETMED', FObj.X, FObj.Y) else g_Sound_PlayExAt('SOUND_ITEM_GETITEM', FObj.X, FObj.Y); - // Íàäî óáðàòü ñ êàðòû, åñëè ýòî íå êëþ÷, êîòîðûì íóæíî ïîäåëèòñÿ ñ äðóãèì èãðîêîì: + // Надо убрать с карты, если это не ключ, которым нужно поделится с другим игроком: if r and not ((ItemType in [ITEM_KEY_RED, ITEM_KEY_GREEN, ITEM_KEY_BLUE]) and (gGameSettings.GameType = GT_SINGLE) and (g_Player_GetCount() > 1)) then @@ -1985,6 +2046,12 @@ end; constructor TPlayer.Create(); begin + viewPortX := 0; + viewPortY := 0; + viewPortW := 0; + viewPortH := 0; + mEDamageType := HIT_SOME; + FIamBot := False; FDummy := False; FSpawned := False; @@ -1993,6 +2060,9 @@ begin FSawSoundIdle := TPlayableSound.Create(); FSawSoundHit := TPlayableSound.Create(); FSawSoundSelect := TPlayableSound.Create(); + FFlameSoundOn := TPlayableSound.Create(); + FFlameSoundOff := TPlayableSound.Create(); + FFlameSoundWork := TPlayableSound.Create(); FJetSoundFly := TPlayableSound.Create(); FJetSoundOn := TPlayableSound.Create(); FJetSoundOff := TPlayableSound.Create(); @@ -2001,6 +2071,9 @@ begin FSawSoundIdle.SetByName('SOUND_WEAPON_IDLESAW'); FSawSoundHit.SetByName('SOUND_WEAPON_HITSAW'); FSawSoundSelect.SetByName('SOUND_WEAPON_SELECTSAW'); + FFlameSoundOn.SetByName('SOUND_WEAPON_FLAMEON'); + FFlameSoundOff.SetByName('SOUND_WEAPON_FLAMEOFF'); + FFlameSoundWork.SetByName('SOUND_WEAPON_FLAMEWORK'); FJetSoundFly.SetByName('SOUND_PLAYER_JETFLY'); FJetSoundOn.SetByName('SOUND_PLAYER_JETON'); FJetSoundOff.SetByName('SOUND_PLAYER_JETOFF'); @@ -2009,11 +2082,13 @@ begin FClientID := -1; FPing := 0; FLoss := 0; - FSavedState.WaitRecall := False; + FSavedStateNum := -1; FShellTimer := -1; FFireTime := 0; FFirePainTime := 0; FFireAttacker := 0; + FHandicap := 100; + FCorpse := -1; FActualModelName := 'doomer'; @@ -2024,23 +2099,34 @@ begin FJustTeleported := False; FNetTime := 0; + FWaitForFirstSpawn := false; + FPunchAnim := TAnimationState.Create(False, 1, 4); + FPunchAnim.Disable; + resetWeaponQueue(); end; -procedure TPlayer.positionChanged (); +procedure TPlayer.positionChanged (); inline; begin end; +procedure TPlayer.doDamage (v: Integer); +begin + if (v <= 0) then exit; + if (v > 32767) then v := 32767; + Damage(v, 0, 0, 0, mEDamageType); +end; + procedure TPlayer.Damage(value: Word; SpawnerUID: Word; vx, vy: Integer; t: Byte); var c: Word; begin - if (not g_Game_IsClient) and (not FLive) then + if (not g_Game_IsClient) and (not FAlive) then Exit; FLastHit := t; -// Íåóÿçâèìîñòü íå ñïàñàåò îò ëîâóøåê: +// Неуязвимость не спасает от ловушек: if ((t = HIT_TRAP) or (t = HIT_SELF)) and (not FGodMode) then begin if not g_Game_IsClient then @@ -2048,40 +2134,41 @@ begin FArmor := 0; if t = HIT_TRAP then begin - // Ëîâóøêà óáèâàåò ñðàçó: + // Ловушка убивает сразу: FHealth := -100; Kill(K_EXTRAHARDKILL, SpawnerUID, t); end; if t = HIT_SELF then begin - // Ñàìîóáèéñòâî: + // Самоубийство: FHealth := 0; Kill(K_SIMPLEKILL, SpawnerUID, t); end; end; - // Îáíóëèòü äåéñòâèÿ ïðèìî÷åê, ÷òîáû ôîí ïðîïàë + // Обнулить действия примочек, чтобы фон пропал FMegaRulez[MR_SUIT] := 0; FMegaRulez[MR_INVUL] := 0; FMegaRulez[MR_INVIS] := 0; + FSpawnInvul := 0; FBerserk := 0; end; -// Íî îò îñòàëüíîãî ñïàñàåò: +// Но от остального спасает: if FMegaRulez[MR_INVUL] >= gTime then Exit; -// ×èò-êîä "ÃÎÐÅÖ": +// Чит-код "ГОРЕЦ": if FGodMode then Exit; -// Åñëè åñòü óðîí ñâîèì, èëè ðàíèë ñàì ñåáÿ, èëè òåáÿ ðàíèë ïðîòèâíèê: +// Если есть урон своим, или ранил сам себя, или тебя ранил противник: if LongBool(gGameSettings.Options and GAME_OPTION_TEAMDAMAGE) or (SpawnerUID = FUID) or (not SameTeam(FUID, SpawnerUID)) then begin FLastSpawnerUID := SpawnerUID; - // Êðîâü (ïóçûðüêè, åñëè â âîäå): + // Кровь (пузырьки, если в воде): if gBloodCount > 0 then begin c := Min(value, 200)*gBloodCount + Random(Min(value, 200) div 2); @@ -2103,11 +2190,11 @@ begin FObj.Y+PLAYER_RECT.Y-4, value div 2, 8, 4); end; - // Áóôåð óðîíà: - if FLive then + // Буфер урона: + if FAlive then Inc(FDamageBuffer, value); - // Âñïûøêà áîëè: + // Вспышка боли: if gFlash <> 0 then FPain := FPain + value; end; @@ -2125,7 +2212,7 @@ begin Result := False; if g_Game_IsClient then Exit; - if not FLive then + if not FAlive then Exit; if Soft and (FHealth < PLAYER_HP_SOFT) then @@ -2153,510 +2240,23 @@ begin FSawSound.Free(); FSawSoundIdle.Free(); FSawSoundHit.Free(); + FSawSoundSelect.Free(); + FFlameSoundOn.Free(); + FFlameSoundOff.Free(); + FFlameSoundWork.Free(); FJetSoundFly.Free(); FJetSoundOn.Free(); FJetSoundOff.Free(); FModel.Free(); + FPunchAnim.Free(); inherited; end; -procedure TPlayer.DrawBubble(); -var - bubX, bubY: Integer; - ID: LongWord; - Rb, Gb, Bb, - Rw, Gw, Bw: SmallInt; - Dot: Byte; -begin - bubX := FObj.X+FObj.Rect.X + IfThen(FDirection = D_LEFT, -4, 18); - bubY := FObj.Y+FObj.Rect.Y - 18; - Rb := 64; - Gb := 64; - Bb := 64; - Rw := 240; - Gw := 240; - Bw := 240; - case gChatBubble of - 1: // simple textual non-bubble - begin - bubX := FObj.X+FObj.Rect.X - 11; - bubY := FObj.Y+FObj.Rect.Y - 17; - e_TextureFontPrint(bubX, bubY, '[...]', gStdFont); - Exit; - end; - 2: // advanced pixel-perfect bubble - begin - if FTeam = TEAM_RED then - Rb := 255 - else - if FTeam = TEAM_BLUE then - Bb := 255; - end; - 3: // colored bubble - begin - Rb := FModel.Color.R; - Gb := FModel.Color.G; - Bb := FModel.Color.B; - Rw := Min(Rb * 2 + 64, 255); - Gw := Min(Gb * 2 + 64, 255); - Bw := Min(Bb * 2 + 64, 255); - if (Abs(Rw - Rb) < 32) - or (Abs(Gw - Gb) < 32) - or (Abs(Bw - Bb) < 32) then - begin - Rb := Max(Rw div 2 - 16, 0); - Gb := Max(Gw div 2 - 16, 0); - Bb := Max(Bw div 2 - 16, 0); - end; - end; - 4: // custom textured bubble - begin - if g_Texture_Get('TEXTURE_PLAYER_TALKBUBBLE', ID) then - if FDirection = D_RIGHT then - e_Draw(ID, bubX - 6, bubY - 7, 0, True, False) - else - e_Draw(ID, bubX - 6, bubY - 7, 0, True, False, M_HORIZONTAL); - Exit; - end; - end; - - // Outer borders - e_DrawQuad(bubX + 1, bubY , bubX + 18, bubY + 13, Rb, Gb, Bb); - e_DrawQuad(bubX , bubY + 1, bubX + 19, bubY + 12, Rb, Gb, Bb); - // Inner box - e_DrawFillQuad(bubX + 1, bubY + 1, bubX + 18, bubY + 12, Rw, Gw, Bw, 0); - - // Tail - Dot := IfThen(FDirection = D_LEFT, 14, 5); - e_DrawLine(1, bubX + Dot, bubY + 14, bubX + Dot, bubY + 16, Rb, Gb, Bb); - e_DrawLine(1, bubX + IfThen(FDirection = D_LEFT, Dot - 1, Dot + 1), bubY + 13, bubX + IfThen(FDirection = D_LEFT, Dot - 1, Dot + 1), bubY + 15, Rw, Gw, Bw); - e_DrawLine(1, bubX + IfThen(FDirection = D_LEFT, Dot - 2, Dot + 2), bubY + 13, bubX + IfThen(FDirection = D_LEFT, Dot - 2, Dot + 2), bubY + 14, Rw, Gw, Bw); - e_DrawLine(1, bubX + IfThen(FDirection = D_LEFT, Dot - 3, Dot + 3), bubY + 13, bubX + IfThen(FDirection = D_LEFT, Dot - 3, Dot + 3), bubY + 13, Rw, Gw, Bw); - e_DrawLine(1, bubX + IfThen(FDirection = D_LEFT, Dot - 3, Dot + 3), bubY + 14, bubX + IfThen(FDirection = D_LEFT, Dot - 1, Dot + 1), bubY + 16, Rb, Gb, Bb); - - // Dots - Dot := 6; - e_DrawFillQuad(bubX + Dot, bubY + 8, bubX + Dot + 1, bubY + 9, Rb, Gb, Bb, 0); - e_DrawFillQuad(bubX + Dot + 3, bubY + 8, bubX + Dot + 4, bubY + 9, Rb, Gb, Bb, 0); - e_DrawFillQuad(bubX + Dot + 6, bubY + 8, bubX + Dot + 7, bubY + 9, Rb, Gb, Bb, 0); -end; - -procedure TPlayer.Draw(); -var - ID: DWORD; - w, h: Word; - dr: Boolean; +procedure TPlayer.DoPunch(); begin - if FLive then - begin - if (FMegaRulez[MR_INVUL] > gTime) and (gPlayerDrawn <> Self) then - if g_Texture_Get('TEXTURE_PLAYER_INVULPENTA', ID) then - begin - e_GetTextureSize(ID, @w, @h); - if FDirection = D_LEFT then - e_Draw(ID, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)+4, - FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7, 0, True, False) - else - e_Draw(ID, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)-2, - FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7, 0, True, False); - end; - - if FMegaRulez[MR_INVIS] > gTime then - begin - if (gPlayerDrawn <> nil) and ((Self = gPlayerDrawn) or - ((FTeam = gPlayerDrawn.Team) and (gGameSettings.GameMode <> GM_DM))) then - begin - if (FMegaRulez[MR_INVIS] - gTime) <= 2100 then - dr := not Odd((FMegaRulez[MR_INVIS] - gTime) div 300) - else - dr := True; - if dr then - FModel.Draw(FObj.X, FObj.Y, 200) - else - FModel.Draw(FObj.X, FObj.Y); - end - else - FModel.Draw(FObj.X, FObj.Y, 254); - end - else - FModel.Draw(FObj.X, FObj.Y); - end; - - if g_debug_Frames then - begin - e_DrawQuad(FObj.X+FObj.Rect.X, - FObj.Y+FObj.Rect.Y, - FObj.X+FObj.Rect.X+FObj.Rect.Width-1, - FObj.Y+FObj.Rect.Y+FObj.Rect.Height-1, - 0, 255, 0); - end; - - if (gChatBubble > 0) and (FKeys[KEY_CHAT].Pressed) and not FGhost then - DrawBubble(); - // e_DrawPoint(5, 335, 288, 255, 0, 0); // DL, UR, DL, UR - if gAimLine and Live and - ((Self = gPlayer1) or (Self = gPlayer2)) then - DrawAim(); -end; - -procedure TPlayer.DrawAim(); -var - wx, wy, xx, yy: Integer; - angle: SmallInt; - sz, len: Word; -begin - wx := FObj.X + WEAPONPOINT[FDirection].X + IfThen(FDirection = D_LEFT, 7, -7); - wy := FObj.Y + WEAPONPOINT[FDirection].Y; - angle := FAngle; - len := 1024; - sz := 2; - case FCurrWeap of - 0: begin // Punch - len := 12; - sz := 4; - end; - 1: begin // Chainsaw - len := 24; - sz := 6; - end; - 2: begin // Pistol - len := 1024; - sz := 2; - if angle = ANGLE_RIGHTUP then Dec(angle, 2); - if angle = ANGLE_RIGHTDOWN then Inc(angle, 4); - if angle = ANGLE_LEFTUP then Inc(angle, 2); - if angle = ANGLE_LEFTDOWN then Dec(angle, 4); - end; - 3: begin // Shotgun - len := 1024; - sz := 3; - if angle = ANGLE_RIGHTUP then Dec(angle, 2); - if angle = ANGLE_RIGHTDOWN then Inc(angle, 4); - if angle = ANGLE_LEFTUP then Inc(angle, 2); - if angle = ANGLE_LEFTDOWN then Dec(angle, 4); - end; - 4: begin // Double Shotgun - len := 1024; - sz := 4; - if angle = ANGLE_RIGHTUP then Dec(angle, 2); - if angle = ANGLE_RIGHTDOWN then Inc(angle, 4); - if angle = ANGLE_LEFTUP then Inc(angle, 2); - if angle = ANGLE_LEFTDOWN then Dec(angle, 4); - end; - 5: begin // Chaingun - len := 1024; - sz := 3; - if angle = ANGLE_RIGHTUP then Dec(angle, 2); - if angle = ANGLE_RIGHTDOWN then Inc(angle, 4); - if angle = ANGLE_LEFTUP then Inc(angle, 2); - if angle = ANGLE_LEFTDOWN then Dec(angle, 4); - end; - 6: begin // Rocket Launcher - len := 1024; - sz := 7; - if angle = ANGLE_RIGHTUP then Inc(angle, 2); - if angle = ANGLE_RIGHTDOWN then Inc(angle, 4); - if angle = ANGLE_LEFTUP then Dec(angle, 2); - if angle = ANGLE_LEFTDOWN then Dec(angle, 4); - end; - 7: begin // Plasmagun - len := 1024; - sz := 5; - if angle = ANGLE_RIGHTUP then Inc(angle); - if angle = ANGLE_RIGHTDOWN then Inc(angle, 3); - if angle = ANGLE_LEFTUP then Dec(angle); - if angle = ANGLE_LEFTDOWN then Dec(angle, 3); - end; - 8: begin // BFG - len := 1024; - sz := 12; - if angle = ANGLE_RIGHTUP then Inc(angle, 1); - if angle = ANGLE_RIGHTDOWN then Inc(angle, 2); - if angle = ANGLE_LEFTUP then Dec(angle, 1); - if angle = ANGLE_LEFTDOWN then Dec(angle, 2); - end; - 9: begin // Super Chaingun - len := 1024; - sz := 4; - if angle = ANGLE_RIGHTUP then Dec(angle, 2); - if angle = ANGLE_RIGHTDOWN then Inc(angle, 4); - if angle = ANGLE_LEFTUP then Inc(angle, 2); - if angle = ANGLE_LEFTDOWN then Dec(angle, 4); - end; - end; - xx := Trunc(Cos(-DegToRad(angle)) * len) + wx; - yy := Trunc(Sin(-DegToRad(angle)) * len) + wy; - e_DrawLine(sz, wx, wy, xx, yy, 255, 0, 0, 96); -end; - -procedure TPlayer.DrawGUI(); -var - ID: DWORD; - X, Y, SY, a, p, m: Integer; - tw, th: Word; - cw, ch: Byte; - s: string; - stat: TPlayerStatArray; -begin - X := gPlayerScreenSize.X; - SY := gPlayerScreenSize.Y; - Y := 0; - - if gShowGoals and (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then - begin - if gGameSettings.GameMode = GM_CTF then - a := 32 + 8 - else - a := 0; - if gGameSettings.GameMode = GM_CTF then - begin - s := 'TEXTURE_PLAYER_REDFLAG'; - if gFlags[FLAG_RED].State = FLAG_STATE_CAPTURED then - s := 'TEXTURE_PLAYER_REDFLAG_S'; - if gFlags[FLAG_RED].State = FLAG_STATE_DROPPED then - s := 'TEXTURE_PLAYER_REDFLAG_D'; - if g_Texture_Get(s, ID) then - e_Draw(ID, X-16-32, 240-72-4, 0, True, False); - end; - - s := IntToStr(gTeamStat[TEAM_RED].Goals); - e_CharFont_GetSize(gMenuFont, s, tw, th); - e_CharFont_PrintEx(gMenuFont, X-16-a-tw, 240-72-4, s, TEAMCOLOR[TEAM_RED]); - - if gGameSettings.GameMode = GM_CTF then - begin - s := 'TEXTURE_PLAYER_BLUEFLAG'; - if gFlags[FLAG_BLUE].State = FLAG_STATE_CAPTURED then - s := 'TEXTURE_PLAYER_BLUEFLAG_S'; - if gFlags[FLAG_BLUE].State = FLAG_STATE_DROPPED then - s := 'TEXTURE_PLAYER_BLUEFLAG_D'; - if g_Texture_Get(s, ID) then - e_Draw(ID, X-16-32, 240-32-4, 0, True, False); - end; - - s := IntToStr(gTeamStat[TEAM_BLUE].Goals); - e_CharFont_GetSize(gMenuFont, s, tw, th); - e_CharFont_PrintEx(gMenuFont, X-16-a-tw, 240-32-4, s, TEAMCOLOR[TEAM_BLUE]); - end; - - if g_Texture_Get('TEXTURE_PLAYER_HUDBG', ID) then - e_DrawFill(ID, X, 0, 1, (gPlayerScreenSize.Y div 256)+IfThen(gPlayerScreenSize.Y mod 256 > 0, 1, 0), - 0, False, False); - - if g_Texture_Get('TEXTURE_PLAYER_HUD', ID) then - e_Draw(ID, X+2, Y, 0, True, False); - - if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then - begin - if gShowStat then - begin - s := IntToStr(Frags); - e_CharFont_GetSize(gMenuFont, s, tw, th); - e_CharFont_PrintEx(gMenuFont, X-16-tw, Y, s, _RGB(255, 0, 0)); - - s := ''; - p := 1; - m := 0; - stat := g_Player_GetStats(); - if stat <> nil then - begin - p := 1; - - for a := 0 to High(stat) do - if stat[a].Name <> Name then - begin - if stat[a].Frags > m then m := stat[a].Frags; - if stat[a].Frags > Frags then p := p+1; - end; - end; - - s := IntToStr(p)+' / '+IntToStr(Length(stat))+' '; - if Frags >= m then s := s+'+' else s := s+'-'; - s := s+IntToStr(Abs(Frags-m)); - - e_CharFont_GetSize(gMenuSmallFont, s, tw, th); - e_CharFont_PrintEx(gMenuSmallFont, X-16-tw, Y+32, s, _RGB(255, 0, 0)); - end; - - if gShowLives and (gGameSettings.MaxLives > 0) then - begin - s := IntToStr(Lives); - e_CharFont_GetSize(gMenuFont, s, tw, th); - e_CharFont_PrintEx(gMenuFont, X-16-tw, SY-32, s, _RGB(0, 255, 0)); - end; - end; - - e_CharFont_GetSize(gMenuSmallFont, FName, tw, th); - e_CharFont_PrintEx(gMenuSmallFont, X+98-(tw div 2), Y+8, FName, _RGB(255, 0, 0)); - - if R_BERSERK in FRulez then - e_Draw(gItemsTexturesID[ITEM_MEDKIT_BLACK], X+37, Y+45, 0, True, False) - else - e_Draw(gItemsTexturesID[ITEM_MEDKIT_LARGE], X+37, Y+45, 0, True, False); - - if g_Texture_Get('TEXTURE_PLAYER_ARMORHUD', ID) then - e_Draw(ID, X+36, Y+77, 0, True, False); - - s := IntToStr(IfThen(FHealth > 0, FHealth, 0)); - e_CharFont_GetSize(gMenuFont, s, tw, th); - e_CharFont_PrintEx(gMenuFont, X+178-tw, Y+40, s, _RGB(255, 0, 0)); - - s := IntToStr(FArmor); - e_CharFont_GetSize(gMenuFont, s, tw, th); - e_CharFont_PrintEx(gMenuFont, X+178-tw, Y+68, s, _RGB(255, 0, 0)); - - s := IntToStr(GetAmmoByWeapon(FCurrWeap)); - - case FCurrWeap of - WEAPON_KASTET: - begin - s := '--'; - ID := gItemsTexturesID[ITEM_WEAPON_KASTET]; - end; - WEAPON_SAW: - begin - s := '--'; - ID := gItemsTexturesID[ITEM_WEAPON_SAW]; - end; - WEAPON_PISTOL: ID := gItemsTexturesID[ITEM_WEAPON_PISTOL]; - WEAPON_CHAINGUN: ID := gItemsTexturesID[ITEM_WEAPON_CHAINGUN]; - WEAPON_SHOTGUN1: ID := gItemsTexturesID[ITEM_WEAPON_SHOTGUN1]; - WEAPON_SHOTGUN2: ID := gItemsTexturesID[ITEM_WEAPON_SHOTGUN2]; - WEAPON_SUPERPULEMET: ID := gItemsTexturesID[ITEM_WEAPON_SUPERPULEMET]; - WEAPON_ROCKETLAUNCHER: ID := gItemsTexturesID[ITEM_WEAPON_ROCKETLAUNCHER]; - WEAPON_PLASMA: ID := gItemsTexturesID[ITEM_WEAPON_PLASMA]; - WEAPON_BFG: ID := gItemsTexturesID[ITEM_WEAPON_BFG]; - WEAPON_FLAMETHROWER: ID := gItemsTexturesID[ITEM_WEAPON_FLAMETHROWER]; - end; - - e_CharFont_GetSize(gMenuFont, s, tw, th); - e_CharFont_PrintEx(gMenuFont, X+178-tw, Y+158, s, _RGB(255, 0, 0)); - e_Draw(ID, X+20, Y+160, 0, True, False); - - if R_KEY_RED in FRulez then - e_Draw(gItemsTexturesID[ITEM_KEY_RED], X+78, Y+214, 0, True, False); - - if R_KEY_GREEN in FRulez then - e_Draw(gItemsTexturesID[ITEM_KEY_GREEN], X+95, Y+214, 0, True, False); - - if R_KEY_BLUE in FRulez then - e_Draw(gItemsTexturesID[ITEM_KEY_BLUE], X+112, Y+214, 0, True, False); - - if FJetFuel > 0 then - begin - if g_Texture_Get('TEXTURE_PLAYER_HUDAIR', ID) then - e_Draw(ID, X+2, Y+116, 0, True, False); - if g_Texture_Get('TEXTURE_PLAYER_HUDJET', ID) then - e_Draw(ID, X+2, Y+126, 0, True, False); - e_DrawLine(4, X+16, Y+122, X+16+Trunc(168*IfThen(FAir > 0, FAir, 0)/AIR_MAX), Y+122, 0, 0, 196); - e_DrawLine(4, X+16, Y+132, X+16+Trunc(168*FJetFuel/JET_MAX), Y+132, 208, 0, 0); - end - else - begin - if g_Texture_Get('TEXTURE_PLAYER_HUDAIR', ID) then - e_Draw(ID, X+2, Y+124, 0, True, False); - e_DrawLine(4, X+16, Y+130, X+16+Trunc(168*IfThen(FAir > 0, FAir, 0)/AIR_MAX), Y+130, 0, 0, 196); - end; - - if gShowPing and g_Game_IsClient then - begin - s := _lc[I_GAME_PING_HUD] + IntToStr(NetPeer.lastRoundTripTime) + _lc[I_NET_SLIST_PING_MS]; - e_TextureFontPrint(X + 4, Y + 242, s, gStdFont); - Y := Y + 16; - end; - - if FSpectator then - begin - e_TextureFontPrint(X + 4, Y + 242, _lc[I_PLAYER_SPECT], gStdFont); - e_TextureFontPrint(X + 4, Y + 258, _lc[I_PLAYER_SPECT2], gStdFont); - e_TextureFontPrint(X + 4, Y + 274, _lc[I_PLAYER_SPECT1], gStdFont); - if FNoRespawn then - begin - e_TextureFontGetSize(gStdFont, cw, ch); - s := _lc[I_PLAYER_SPECT4]; - e_TextureFontPrintEx(gScreenWidth div 2 - cw*(Length(s) div 2), - gScreenHeight-4-ch, s, gStdFont, 255, 255, 255, 1, True); - e_TextureFontPrint(X + 4, Y + 290, _lc[I_PLAYER_SPECT1S], gStdFont); - end; - - end; -end; - -procedure TPlayer.DrawRulez(); -var - dr: Boolean; -begin - // Ïðè âçÿòèè íåóÿçâèìîñòè ðèñóåòñÿ èíâåðñèîííûé áåëûé ôîí - if FMegaRulez[MR_INVUL] >= gTime then - begin - if (FMegaRulez[MR_INVUL]-gTime) <= 2100 then - dr := not Odd((FMegaRulez[MR_INVUL]-gTime) div 300) - else - dr := True; - - if dr then - e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, - 191, 191, 191, 0, B_INVERT); - end; - - // Ïðè âçÿòèè çàùèòíîãî êîñòþìà ðèñóåòñÿ çåëåíîâàòûé ôîí - if FMegaRulez[MR_SUIT] >= gTime then - begin - if (FMegaRulez[MR_SUIT]-gTime) <= 2100 then - dr := not Odd((FMegaRulez[MR_SUIT]-gTime) div 300) - else - dr := True; - - if dr then - e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, - 0, 96, 0, 200, B_NONE); - end; - - // Ïðè âçÿòèè áåðñåðêà ðèñóåòñÿ êðàñíîâàòûé ôîí - if (FBerserk >= 0) and (LongWord(FBerserk) >= gTime) and (gFlash = 2) then - begin - e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, - 255, 0, 0, 200, B_NONE); - end; -end; - -procedure TPlayer.DrawPain(); -var - a, h: Integer; -begin - if FPain = 0 then Exit; - - a := FPain; - - if a < 15 then h := 0 - else if a < 35 then h := 1 - else if a < 55 then h := 2 - else if a < 75 then h := 3 - else if a < 95 then h := 4 - else h := 5; - - //if a > 255 then a := 255; - - e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, 255, 0, 0, 255-h*50); - //e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, 255-min(128, a), 255-a, 255-a, 0, B_FILTER); -end; - -procedure TPlayer.DrawPickup(); -var - a, h: Integer; -begin - if FPickup = 0 then Exit; - - a := FPickup; - - if a < 15 then h := 1 - else if a < 35 then h := 2 - else if a < 55 then h := 3 - else if a < 75 then h := 4 - else h := 5; - - e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, 150, 200, 150, 255-h*50); + FPunchAnim.Reset; + FPunchAnim.Enable; end; procedure TPlayer.Fire(); @@ -2666,8 +2266,8 @@ var locobj: TObj; begin if g_Game_IsClient then Exit; -// FBFGFireCounter - âðåìÿ ïåðåä âûñòðåëîì (äëÿ BFG) -// FReloading - âðåìÿ ïîñëå âûñòðåëà (äëÿ âñåãî) +// FBFGFireCounter - время перед выстрелом (для BFG) +// FReloading - время после выстрела (для всего) if FSpectator then begin @@ -2682,12 +2282,13 @@ begin f := False; wx := FObj.X+WEAPONPOINT[FDirection].X; wy := FObj.Y+WEAPONPOINT[FDirection].Y; - xd := wx+IfThen(FDirection = D_LEFT, -30, 30); + xd := wx+IfThen(FDirection = TDirection.D_LEFT, -30, 30); yd := wy+firediry(); case FCurrWeap of WEAPON_KASTET: begin + DoPunch(); if R_BERSERK in FRulez then begin //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID); @@ -2707,10 +2308,12 @@ begin else g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj.X, FObj.Y); - if gFlash = 1 then - if FPain < 50 then - FPain := min(FPain + 25, 50); - end else g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 3, FUID); + if (gFlash = 1) and (FPain < 50) then FPain := min(FPain + 25, 50); + end + else + begin + g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 3, FUID); + end; DidFire := True; FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap]; @@ -2840,11 +2443,17 @@ begin if FAmmo[A_FUEL] > 0 then begin g_Weapon_flame(wx, wy, xd, yd, FUID); + FlamerOn; FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap]; Dec(FAmmo[A_FUEL]); FFireAngle := FAngle; f := True; DidFire := True; + end + else + begin + FlamerOff; + if g_Game_IsNet and g_Game_IsServer then MH_SEND_PlayerStats(FUID); end; end; @@ -2888,6 +2497,35 @@ begin PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, True); end; +procedure TPlayer.FlamerOn; +begin + FFlameSoundOff.Stop(); + FFlameSoundOff.SetPosition(0); + if FFlaming then + begin + if (not FFlameSoundOn.IsPlaying()) and (not FFlameSoundWork.IsPlaying()) then + FFlameSoundWork.PlayAt(FObj.X, FObj.Y); + end + else + begin + FFlameSoundOn.PlayAt(FObj.X, FObj.Y); + FFlaming := True; + end; +end; + +procedure TPlayer.FlamerOff; +begin + if FFlaming then + begin + FFlameSoundOn.Stop(); + FFlameSoundOn.SetPosition(0); + FFlameSoundWork.Stop(); + FFlameSoundWork.SetPosition(0); + FFlameSoundOff.PlayAt(FObj.X, FObj.Y); + FFlaming := False; + end; +end; + procedure TPlayer.JetpackOn; begin FJetSoundFly.Stop; @@ -2905,9 +2543,17 @@ begin FJetSoundOff.PlayAt(FObj.X, FObj.Y); end; -procedure TPlayer.CatchFire(Attacker: Word); +procedure TPlayer.CatchFire(Attacker: Word; Timeout: Integer = PLAYER_BURN_TIME); begin - FFireTime := 100; + if Timeout <= 0 then + exit; + if (FMegaRulez[MR_SUIT] > gTime) or (FMegaRulez[MR_INVUL] > gTime) then + exit; // Не загораемся когда есть защита + if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2) then + exit; // Не подгораем в воде на всякий случай + if FFireTime <= 0 then + g_Sound_PlayExAt('SOUND_IGNITE', FObj.X, FObj.Y); + FFireTime := Timeout; FFireAttacker := Attacker; if g_Game_IsNet and g_Game_IsServer then MH_SEND_PlayerStats(FUID); @@ -2917,7 +2563,7 @@ procedure TPlayer.Jump(); begin if gFly or FJetpack then begin - // Ïîëåò (÷èò-êîä èëè äæåòïàê): + // Полет (чит-код или джетпак): if FObj.Vel.Y > -VEL_FLY then FObj.Vel.Y := FObj.Vel.Y - 3; if FJetpack then @@ -2935,15 +2581,15 @@ begin Exit; end; -// Íå âêëþ÷àòü äæåòïàê â ðåæèìå ïðîõîæäåíèÿ ñêâîçü ñòåíû +// Не включать джетпак в режиме прохождения сквозь стены if FGhost then FCanJetpack := False; -// Ïðûãàåì èëè âñïëûâàåì: +// Прыгаем или всплываем: if (CollideLevel(0, 1) or g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y+36, PLAYER_RECT.Width, PLAYER_RECT.Height-33, PANEL_STEP, False) - ) and (FObj.Accel.Y = 0) then // Íå ïðûãàòü, åñëè åñòü âåðòèêàëüíîå óñêîðåíèå + ) and (FObj.Accel.Y = 0) then // Не прыгать, если есть вертикальное ускорение begin FObj.Vel.Y := -VEL_JUMP; FCanJetpack := False; @@ -3011,13 +2657,13 @@ begin Srv := g_Game_IsServer; Netsrv := g_Game_IsServer and g_Game_IsNet; if Srv then FDeath := FDeath + 1; - if FLive then + if FAlive then begin if FGhost then FGhost := False; if not FPhysics then FPhysics := True; - FLive := False; + FAlive := False; end; FShellTimer := -1; @@ -3027,7 +2673,7 @@ begin if FLives = 0 then FNoRespawn := True; end; -// Íîìåð òèïà ñìåðòè: +// Номер типа смерти: a := 1; case KillType of K_SIMPLEKILL: a := 1; @@ -3036,13 +2682,13 @@ begin K_FALLKILL: a := 4; end; -// Çâóê ñìåðòè: +// Звук смерти: if not FModel.PlaySound(MODELSOUND_DIE, a, FObj.X, FObj.Y) then for i := 1 to 3 do if FModel.PlaySound(MODELSOUND_DIE, i, FObj.X, FObj.Y) then Break; -// Âðåìÿ ðåñïàóíà: +// Время респауна: if Srv then case KillType of K_SIMPLEKILL: @@ -3053,7 +2699,7 @@ begin FTime[T_RESPAWN] := gTime + TIME_RESPAWN3; end; -// Ïåðåêëþ÷àåì ñîñòîÿíèå: +// Переключаем состояние: case KillType of K_SIMPLEKILL: SetAction(A_DIE1); @@ -3061,12 +2707,12 @@ begin SetAction(A_DIE2); end; -// Ðåàêöèÿ ìîíñòðîâ íà ñìåðòü èãðîêà: +// Реакция монстров на смерть игрока: if (KillType <> K_FALLKILL) and (Srv) then g_Monsters_killedp(); if SpawnerUID = FUID then - begin // Ñàìîóáèëñÿ + begin // Самоубился if Srv and (DoFrags or (gGameSettings.GameMode = GM_TDM)) then begin Dec(FFrags); @@ -3076,7 +2722,7 @@ begin end else if g_GetUIDType(SpawnerUID) = UID_PLAYER then - begin // Óáèò äðóãèì èãðîêîì + begin // Убит другим игроком KP := g_Player_Get(SpawnerUID); if (KP <> nil) and Srv then begin @@ -3120,12 +2766,12 @@ begin end; end else if g_GetUIDType(SpawnerUID) = UID_MONSTER then - begin // Óáèò ìîíñòðîì + begin // Убит монстром mon := g_Monsters_ByUID(SpawnerUID); if mon = nil then s := '?' else - s := g_Monsters_GetKilledBy(mon.MonsterType); + s := g_Mons_GetKilledByTypeId(mon.MonsterType); case KillType of K_HARDKILL: @@ -3142,7 +2788,7 @@ begin gShowKillMsg); end; end - else // Îñîáûå òèïû ñìåðòè + else // Особые типы смерти case t of HIT_DISCON: ; HIT_SELF: g_Console_Add(Format(_lc[I_PLAYER_KILL_SELF], [FName]), True); @@ -3155,7 +2801,7 @@ begin if Srv then begin -// Âûáðîñ îðóæèÿ: +// Выброс оружия: for a := WP_FIRST to WP_LAST do if FWeapon[a] then begin @@ -3176,16 +2822,17 @@ begin PushItem(i); end; -// Âûáðîñ ðþêçàêà: +// Выброс рюкзака: if R_ITEM_BACKPACK in FRulez then PushItem(ITEM_AMMO_BACKPACK); -// Âûáðîñ ðàêåòíîãî ðàíöà: +// Выброс ракетного ранца: if FJetFuel > 0 then PushItem(ITEM_JETPACK); -// Âûáðîñ êëþ÷åé: - if not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) then +// Выброс ключей: + if (not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) or + (not LongBool(gGameSettings.Options and GAME_OPTION_DMKEYS)) then begin if R_KEY_RED in FRulez then PushItem(ITEM_KEY_RED); @@ -3197,11 +2844,11 @@ begin PushItem(ITEM_KEY_BLUE); end; -// Âûáðîñ ôëàãà: - DropFlag(); +// Выброс флага: + DropFlag(KillType = K_FALLKILL); end; - g_Player_CreateCorpse(Self); + FCorpse := g_Player_CreateCorpse(Self); if Srv and (gGameSettings.MaxLives > 0) and FNoRespawn and (gLMSRespawn = LMS_RESPAWN_NONE) then @@ -3305,7 +2952,7 @@ begin if srv and (OldLR = LMS_RESPAWN_NONE) and (gLMSRespawn > LMS_RESPAWN_NONE) then begin if NetMode = NET_SERVER then - MH_SEND_GameEvent(NET_EV_LMS_WARMUP, (gLMSRespawnTime - gTime) div 1000) + MH_SEND_GameEvent(NET_EV_LMS_WARMUP, gLMSRespawnTime - gTime) else g_Console_Add(Format(_lc[I_MSG_WARMUP_START], [(gLMSRespawnTime - gTime) div 1000]), True); end; @@ -3335,23 +2982,27 @@ begin end; procedure TPlayer.MakeBloodSimple(Count: Word); + var Blood: TModelBlood; begin + Blood := SELF.FModel.GetBlood(); g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)+8, FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2), Count div 2, 3, -1, 16, (PLAYER_RECT.Height*2 div 3), - 150, 0, 0); + Blood.R, Blood.G, Blood.B, Blood.Kind); g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-8, FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2), Count div 2, -3, -1, 16, (PLAYER_RECT.Height*2) div 3, - 150, 0, 0); + Blood.R, Blood.G, Blood.B, Blood.Kind); end; procedure TPlayer.MakeBloodVector(Count: Word; VelX, VelY: Integer); + var Blood: TModelBlood; begin + Blood := SELF.FModel.GetBlood(); g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2), FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2), Count, VelX, VelY, 16, (PLAYER_RECT.Height*2) div 3, - 150, 0, 0); + Blood.R, Blood.G, Blood.B, Blood.Kind); end; procedure TPlayer.QueueWeaponSwitch(Weapon: Byte); @@ -3372,8 +3023,8 @@ begin result := false; case weapon of WEAPON_KASTET, WEAPON_SAW: result := true; - WEAPON_SHOTGUN1, WEAPON_SHOTGUN2: result := (FAmmo[A_SHELLS] > 0); - WEAPON_PISTOL, WEAPON_CHAINGUN, WEAPON_SUPERPULEMET: result := (FAmmo[A_BULLETS] > 0); + WEAPON_SHOTGUN1, WEAPON_SHOTGUN2, WEAPON_SUPERPULEMET: result := (FAmmo[A_SHELLS] > 0); + WEAPON_PISTOL, WEAPON_CHAINGUN: result := (FAmmo[A_BULLETS] > 0); WEAPON_ROCKETLAUNCHER: result := (FAmmo[A_ROCKETS] > 0); WEAPON_PLASMA, WEAPON_BFG: result := (FAmmo[A_CELLS] > 0); WEAPON_FLAMETHROWER: result := (FAmmo[A_FUEL] > 0); @@ -3414,7 +3065,7 @@ begin begin //e_WriteLog(Format(' SWITCH: cur=%d; new=%d', [FCurrWeap, cwi]), MSG_WARNING); result := Byte(cwi); - FNextWeapDelay := 10; + FNextWeapDelay := WEAPON_DELAY; exit; end; end; @@ -3459,7 +3110,7 @@ begin // i found her! result := Byte(i); resetWeaponQueue(); - FNextWeapDelay := 10; // anyway, 'cause why not + FNextWeapDelay := WEAPON_DELAY * 2; // anyway, 'cause why not exit; end; end; @@ -3493,7 +3144,7 @@ begin if not switchAllowed then begin //HACK for weapon cycling - if (FNextWeap and $7000) <> 0 then FNextWeap := 0; + if (FNextWeap and $E000) <> 0 then FNextWeap := 0; exit; end; @@ -3541,21 +3192,31 @@ begin end; function TPlayer.PickItem(ItemType: Byte; arespawn: Boolean; var remove: Boolean): Boolean; + + function allowBerserkSwitching (): Boolean; + begin + if (FBFGFireCounter <> -1) then begin result := false; exit; end; + result := true; + if gBerserkAutoswitch then exit; + if not conIsCheatsEnabled then exit; + result := false; + end; + var a: Boolean; begin Result := False; if g_Game_IsClient then Exit; - // a = true - ìåñòî ñïàâíà ïðåäìåòà: + // a = true - место спавна предмета: a := LongBool(gGameSettings.Options and GAME_OPTION_WEAPONSTAY) and arespawn; remove := not a; case ItemType of ITEM_MEDKIT_SMALL: - if FHealth < PLAYER_HP_SOFT then + if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then begin - IncMax(FHealth, 10, PLAYER_HP_SOFT); + if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 10, PLAYER_HP_SOFT); Result := True; remove := True; FFireTime := 0; @@ -3563,9 +3224,9 @@ begin end; ITEM_MEDKIT_LARGE: - if FHealth < PLAYER_HP_SOFT then + if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then begin - IncMax(FHealth, 25, PLAYER_HP_SOFT); + if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 25, PLAYER_HP_SOFT); Result := True; remove := True; FFireTime := 0; @@ -3591,9 +3252,9 @@ begin end; ITEM_SPHERE_BLUE: - if FHealth < PLAYER_HP_LIMIT then + if (FHealth < PLAYER_HP_LIMIT) or (FFireTime > 0) then begin - IncMax(FHealth, 100, PLAYER_HP_LIMIT); + if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 100, PLAYER_HP_LIMIT); Result := True; remove := True; FFireTime := 0; @@ -3601,7 +3262,7 @@ begin end; ITEM_SPHERE_WHITE: - if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) then + if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) or (FFireTime > 0) then begin if FHealth < PLAYER_HP_LIMIT then FHealth := PLAYER_HP_LIMIT; @@ -3625,7 +3286,7 @@ begin ITEM_WEAPON_SHOTGUN1: if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SHOTGUN1] then begin - // Íóæíî, ÷òîáû íå âçÿòü âñå ïóëè ñðàçó: + // Нужно, чтобы не взять все пули сразу: if a and FWeapon[WEAPON_SHOTGUN1] then Exit; IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]); @@ -3806,7 +3467,7 @@ begin (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or - (FMaxAmmo[A_FUEL] < AmmoLimits[1, A_FUEL]) then + (FAmmo[A_FUEL] < FMaxAmmo[A_FUEL]) then begin FMaxAmmo[A_BULLETS] := AmmoLimits[1, A_BULLETS]; FMaxAmmo[A_SHELLS] := AmmoLimits[1, A_SHELLS]; @@ -3822,6 +3483,8 @@ begin IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]); if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]); + if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then + IncMax(FAmmo[A_FUEL], 50, FMaxAmmo[A_FUEL]); FRulez := FRulez + [R_ITEM_BACKPACK]; Result := True; @@ -3883,23 +3546,25 @@ begin if not (R_BERSERK in FRulez) then begin Include(FRulez, R_BERSERK); - if FBFGFireCounter = -1 then + if allowBerserkSwitching then begin FCurrWeap := WEAPON_KASTET; resetWeaponQueue(); FModel.SetWeapon(WEAPON_KASTET); end; if gFlash <> 0 then + begin Inc(FPain, 100); if gFlash = 2 then Inc(FPickup, 5); + end; FBerserk := gTime+30000; Result := True; remove := True; FFireTime := 0; end; - if FHealth < PLAYER_HP_SOFT then + if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then begin - FHealth := PLAYER_HP_SOFT; + if FHealth < PLAYER_HP_SOFT then FHealth := PLAYER_HP_SOFT; FBerserk := gTime+30000; Result := True; remove := True; @@ -3911,15 +3576,16 @@ begin if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then begin FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME; + FSpawnInvul := 0; Result := True; remove := True; if gFlash = 2 then Inc(FPickup, 5); end; ITEM_BOTTLE: - if FHealth < PLAYER_HP_LIMIT then + if (FHealth < PLAYER_HP_LIMIT) or (FFireTime > 0) then begin - IncMax(FHealth, 4, PLAYER_HP_LIMIT); + if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 4, PLAYER_HP_LIMIT); Result := True; remove := True; FFireTime := 0; @@ -3957,12 +3623,12 @@ end; procedure TPlayer.Touch(); begin - if not FLive then + if not FAlive then Exit; //FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y); if FIamBot then begin - // Áðîñèòü ôëàã òîâàðèùó: + // Бросить флаг товарищу: if gGameSettings.GameMode = GM_CTF then DropFlag(); end; @@ -3981,7 +3647,7 @@ end; procedure TPlayer.Reset(Force: Boolean); begin if Force then - FLive := False; + FAlive := False; FSpawned := False; FTime[T_RESPAWN] := 0; @@ -3996,6 +3662,9 @@ begin FMonsterKills := 0; FDeath := 0; FSecrets := 0; + FSpawnInvul := 0; + FCorpse := -1; + FReady := False; if FNoRespawn then begin FSpectator := False; @@ -4014,6 +3683,8 @@ begin ReleaseKeys(); FDamageBuffer := 0; + FSlopeOld := 0; + FIncCamOld := 0; FIncCam := 0; FBFGFireCounter := -1; FShellTimer := -1; @@ -4022,166 +3693,89 @@ begin FLastFrag := 0; FComboEvnt := -1; - SetFlag(FLAG_NONE); - SetAction(A_STAND, True); -end; - -function TPlayer.GetRespawnPoint(): Byte; -var - c: Byte; -begin - Result := 255; - // Íà áóäóùåå: FSpawn - èãðîê óæå èãðàë è ïåðåðîæäàåòñÿ - - // Îäèíî÷íàÿ èãðà/êîîïåðàòèâ - if gGameSettings.GameMode in [GM_COOP, GM_SINGLE] then - begin - if (Self = gPlayer1) or (Self = gPlayer2) then - begin - // Òî÷êà ïîÿâëåíèÿ ñâîåãî èãðîêà - if Self = gPlayer1 then - c := RESPAWNPOINT_PLAYER1 - else - c := RESPAWNPOINT_PLAYER2; - if g_Map_GetPointCount(c) > 0 then - begin - Result := c; - Exit; - end; - - // Òî÷êà ïîÿâëåíèÿ äðóãîãî èãðîêà - if Self = gPlayer1 then - c := RESPAWNPOINT_PLAYER2 - else - c := RESPAWNPOINT_PLAYER1; - if g_Map_GetPointCount(c) > 0 then - begin - Result := c; - Exit; - end; - end else - begin - // Òî÷êà ïîÿâëåíèÿ ëþáîãî èãðîêà (áîòà) - if Random(2) = 0 then - c := RESPAWNPOINT_PLAYER1 - else - c := RESPAWNPOINT_PLAYER2; - if g_Map_GetPointCount(c) > 0 then - begin - Result := c; - Exit; - end; - end; - - // Òî÷êà ëþáîé èç êîìàíä - if Random(2) = 0 then - c := RESPAWNPOINT_RED - else - c := RESPAWNPOINT_BLUE; - if g_Map_GetPointCount(c) > 0 then - begin - Result := c; - Exit; - end; + SetFlag(FLAG_NONE); + SetAction(A_STAND, True); +end; - // Òî÷êà DM - c := RESPAWNPOINT_DM; - if g_Map_GetPointCount(c) > 0 then - begin - Result := c; - Exit; - end; - end; +function TPlayer.GetRespawnPoint(): Byte; +var + c: Byte; +begin + Result := 255; + // На будущее: FSpawn - игрок уже играл и перерождается - // Ìÿñîïîâàë - if gGameSettings.GameMode = GM_DM then + // Одиночная игра/кооператив + if gGameSettings.GameMode in [GM_COOP, GM_SINGLE] then begin - // Òî÷êà DM - c := RESPAWNPOINT_DM; - if g_Map_GetPointCount(c) > 0 then + if Self = gPlayer1 then begin - Result := c; - Exit; - end; - - // Òî÷êà ïîÿâëåíèÿ ëþáîãî èãðîêà - if Random(2) = 0 then - c := RESPAWNPOINT_PLAYER1 - else - c := RESPAWNPOINT_PLAYER2; - if g_Map_GetPointCount(c) > 0 then + // player 1 should try to spawn on the player 1 point + if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1) > 0 then + Exit(RESPAWNPOINT_PLAYER1) + else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2) > 0 then + Exit(RESPAWNPOINT_PLAYER2); + end + else if Self = gPlayer2 then begin - Result := c; - Exit; - end; - - // Òî÷êà ëþáîé èç êîìàíä - if Random(2) = 0 then - c := RESPAWNPOINT_RED + // player 2 should try to spawn on the player 2 point + if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2) > 0 then + Exit(RESPAWNPOINT_PLAYER2) + else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1) > 0 then + Exit(RESPAWNPOINT_PLAYER1); + end else - c := RESPAWNPOINT_BLUE; - if g_Map_GetPointCount(c) > 0 then begin - Result := c; - Exit; + // other players randomly pick either the first or the second point + c := IfThen((Random(2) = 0), RESPAWNPOINT_PLAYER1, RESPAWNPOINT_PLAYER2); + if g_Map_GetPointCount(c) > 0 then + Exit(c); + // try the other one + c := IfThen((c = RESPAWNPOINT_PLAYER1), RESPAWNPOINT_PLAYER2, RESPAWNPOINT_PLAYER1); + if g_Map_GetPointCount(c) > 0 then + Exit(c); end; end; - // Êîìàíäíûå - if gGameSettings.GameMode in [GM_TDM, GM_CTF] then + // Мясоповал + if gGameSettings.GameMode = GM_DM then begin - // Òî÷êà ñâîåé êîìàíäû - c := RESPAWNPOINT_DM; - if FTeam = TEAM_RED then - c := RESPAWNPOINT_RED; - if FTeam = TEAM_BLUE then - c := RESPAWNPOINT_BLUE; - if g_Map_GetPointCount(c) > 0 then - begin - Result := c; - Exit; - end; - - // Òî÷êà DM - c := RESPAWNPOINT_DM; - if g_Map_GetPointCount(c) > 0 then - begin - Result := c; - Exit; - end; - - // Òî÷êà ïîÿâëåíèÿ ëþáîãî èãðîêà - if Random(2) = 0 then - c := RESPAWNPOINT_PLAYER1 - else - c := RESPAWNPOINT_PLAYER2; - if g_Map_GetPointCount(c) > 0 then - begin - Result := c; - Exit; - end; + // try DM points first + if g_Map_GetPointCount(RESPAWNPOINT_DM) > 0 then + Exit(RESPAWNPOINT_DM); + end; - // Òî÷êà äðóãîé êîìàíäû + // Командные + if gGameSettings.GameMode in [GM_TDM, GM_CTF] then + begin + // try team points first c := RESPAWNPOINT_DM; if FTeam = TEAM_RED then + c := RESPAWNPOINT_RED + else if FTeam = TEAM_BLUE then c := RESPAWNPOINT_BLUE; - if FTeam = TEAM_BLUE then - c := RESPAWNPOINT_RED; if g_Map_GetPointCount(c) > 0 then - begin - Result := c; - Exit; - end; + Exit(c); end; + + // still haven't found a spawnpoint, try random shit + Result := g_Map_GetRandomPointType(); end; procedure TPlayer.Respawn(Silent: Boolean; Force: Boolean = False); var RespawnPoint: TRespawnPoint; a, b, c: Byte; - Anim: TAnimation; - ID: DWORD; begin + FSlopeOld := 0; + FIncCamOld := 0; + FIncCam := 0; + FBFGFireCounter := -1; + FShellTimer := -1; + FPain := 0; + FLastHit := 0; + FSpawnInvul := 0; + FCorpse := -1; + if not g_Game_IsServer then Exit; if FDummy then @@ -4191,17 +3785,17 @@ begin if Force then begin FTime[T_RESPAWN] := 0; - FLive := False; + FAlive := False; end; FNetTime := 0; // if server changes MaxLives we gotta be ready if gGameSettings.MaxLives = 0 then FNoRespawn := False; -// Åùå íåëüçÿ âîçðîäèòüñÿ: +// Еще нельзя возродиться: if FTime[T_RESPAWN] > gTime then Exit; -// Ïðîñðàë âñå æèçíè: +// Просрал все жизни: if FNoRespawn then begin if not FSpectator then Spectate(True); @@ -4210,28 +3804,28 @@ begin end; if (gGameSettings.GameType <> GT_SINGLE) and (gGameSettings.GameMode <> GM_COOP) then - begin // "Ñâîÿ èãðà" - // Áåðñåðê íå ñîõðàíÿåòñÿ ìåæäó óðîâíÿìè: + begin // "Своя игра" + // Берсерк не сохраняется между уровнями: FRulez := FRulez-[R_BERSERK]; end - else // "Îäèíî÷íàÿ èãðà"/"Êîîï" + else // "Одиночная игра"/"Кооп" begin - // Áåðñåðê è êëþ÷è íå ñîõðàíÿþòñÿ ìåæäó óðîâíÿìè: + // Берсерк и ключи не сохраняются между уровнями: FRulez := FRulez-[R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE, R_BERSERK]; end; -// Ïîëó÷àåì òî÷êó ñïàóíà èãðîêà: +// Получаем точку спауна игрока: c := GetRespawnPoint(); ReleaseKeys(); SetFlag(FLAG_NONE); -// Âîñêðåøåíèå áåç îðóæèÿ: - if not FLive then +// Воскрешение без оружия: + if not FAlive then begin - FHealth := PLAYER_HP_SOFT; + FHealth := Round(PLAYER_HP_SOFT * (FHandicap / 100)); FArmor := 0; - FLive := True; + FAlive := True; FAir := AIR_DEF; FJetFuel := 0; @@ -4259,39 +3853,36 @@ begin FMaxAmmo[A_CELLS] := AmmoLimits[0, A_CELLS]; FMaxAmmo[A_FUEL] := AmmoLimits[0, A_FUEL]; - if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then + if (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) and + LongBool(gGameSettings.Options and GAME_OPTION_DMKEYS) then FRulez := [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE] else FRulez := []; end; -// Ïîëó÷àåì êîîðäèíàòû òî÷êè âîçðîæäåíèÿ: +// Получаем координаты точки возрождения: if not g_Map_GetPoint(c, RespawnPoint) then begin g_FatalError(_lc[I_GAME_ERROR_GET_SPAWN]); Exit; end; -// Óñòàíîâêà êîîðäèíàò è ñáðîñ âñåõ ïàðàìåòðîâ: +// Установка координат и сброс всех параметров: FObj.X := RespawnPoint.X-PLAYER_RECT.X; FObj.Y := RespawnPoint.Y-PLAYER_RECT.Y; + FObj.oldX := FObj.X; // don't interpolate after respawn + FObj.oldY := FObj.Y; FObj.Vel.X := 0; FObj.Vel.Y := 0; FObj.Accel.X := 0; FObj.Accel.Y := 0; FDirection := RespawnPoint.Direction; - if FDirection = D_LEFT then + if FDirection = TDirection.D_LEFT then FAngle := 180 else FAngle := 0; - FIncCam := 0; - FBFGFireCounter := -1; - FShellTimer := -1; - FPain := 0; - FLastHit := 0; - SetAction(A_STAND, True); FModel.Direction := FDirection; @@ -4301,22 +3892,28 @@ begin for a := Low(FMegaRulez) to High(FMegaRulez) do FMegaRulez[a] := 0; +// Respawn invulnerability + if (gGameSettings.GameType <> GT_SINGLE) and (gGameSettings.SpawnInvul > 0) then + begin + FMegaRulez[MR_INVUL] := gTime + gGameSettings.SpawnInvul * 1000; + FSpawnInvul := FMegaRulez[MR_INVUL]; + end; + FDamageBuffer := 0; FJetpack := False; FCanJetpack := False; + FFlaming := False; FFireTime := 0; FFirePainTime := 0; FFireAttacker := 0; -// Àíèìàöèÿ âîçðîæäåíèÿ: +// Анимация возрождения: if (not gLoadGameMode) and (not Silent) then - if g_Frames_Get(ID, 'FRAMES_TELEPORT') then - begin - Anim := TAnimation.Create(ID, False, 3); - g_GFX_OnceAnim(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32, - FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, Anim); - Anim.Free(); - end; + r_GFX_OnceAnim( + R_GFX_TELEPORT_FAST, + FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32, + FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32 + ); FSpectator := False; FGhost := False; @@ -4324,6 +3921,11 @@ begin FSpectatePlayer := -1; FSpawned := True; + if (gPlayer1 = nil) and (gSpectLatchPID1 = FUID) then + gPlayer1 := self; + if (gPlayer2 = nil) and (gSpectLatchPID2 = FUID) then + gPlayer2 := self; + if g_Game_IsNet then begin MH_SEND_PlayerPos(True, FUID, NET_EVERYONE); @@ -4337,7 +3939,7 @@ end; procedure TPlayer.Spectate(NoMove: Boolean = False); begin - if FLive then + if FAlive then Kill(K_EXTRAHARDKILL, FUID, HIT_SOME) else if (not NoMove) then begin @@ -4347,23 +3949,24 @@ begin FXTo := GameX; FYTo := GameY; - FLive := False; + FAlive := False; FSpectator := True; FGhost := True; FPhysics := False; FWantsInGame := False; FSpawned := False; + FCorpse := -1; if FNoRespawn then begin if Self = gPlayer1 then begin - gLMSPID1 := FUID; + gSpectLatchPID1 := FUID; gPlayer1 := nil; - end; - if Self = gPlayer2 then + end + else if Self = gPlayer2 then begin - gLMSPID2 := FUID; + gSpectLatchPID2 := FUID; gPlayer2 := nil; end; end; @@ -4374,7 +3977,7 @@ end; procedure TPlayer.SwitchNoClip; begin - if not FLive then + if not FAlive then Exit; FGhost := not FGhost; FPhysics := not FGhost; @@ -4396,8 +3999,8 @@ begin if MAX_RUNVEL > 8 then FlySmoke(); -// Áåæèì: - if Direction = D_LEFT then +// Бежим: + if Direction = TDirection.D_LEFT then begin if FObj.Vel.X > -MAX_RUNVEL then FObj.Vel.X := FObj.Vel.X - (MAX_RUNVEL shr 3); @@ -4406,25 +4009,25 @@ begin if FObj.Vel.X < MAX_RUNVEL then FObj.Vel.X := FObj.Vel.X + (MAX_RUNVEL shr 3); -// Âîçìîæíî, ïèíàåì êóñêè: +// Возможно, пинаем куски: if (FObj.Vel.X <> 0) and (gGibs <> nil) then begin b := Abs(FObj.Vel.X); if b > 1 then b := b * (Random(8 div b) + 1); for a := 0 to High(gGibs) do begin - if gGibs[a].Live and + if gGibs[a].alive and g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4, FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then begin - // Ïèíàåì êóñêè + // Пинаем куски if FObj.Vel.X < 0 then begin - g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120) // íàëåâî + g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120) // налево end else begin - g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // íàïðàâî + g_Obj_PushA(@gGibs[a].Obj, b, Random(61)); // направо end; gGibs[a].positionChanged(); // this updates spatial accelerators end; @@ -4438,7 +4041,7 @@ procedure TPlayer.SeeDown(); begin SetAction(A_SEEDOWN); - if FDirection = D_LEFT then FAngle := ANGLE_LEFTDOWN else FAngle := ANGLE_RIGHTDOWN; + if FDirection = TDirection.D_LEFT then FAngle := ANGLE_LEFTDOWN else FAngle := ANGLE_RIGHTDOWN; if FIncCam > -120 then DecMin(FIncCam, 5, -120); end; @@ -4447,7 +4050,7 @@ procedure TPlayer.SeeUp(); begin SetAction(A_SEEUP); - if FDirection = D_LEFT then FAngle := ANGLE_LEFTUP else FAngle := ANGLE_RIGHTUP; + if FDirection = TDirection.D_LEFT then FAngle := ANGLE_LEFTUP else FAngle := ANGLE_RIGHTUP; if FIncCam < 120 then IncMax(FIncCam, 5, 120); end; @@ -4490,9 +4093,6 @@ begin end; function TPlayer.TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean; -var - Anim: TAnimation; - ID: DWORD; begin Result := False; @@ -4506,17 +4106,14 @@ begin FJustTeleported := True; - Anim := nil; if not silent then begin - if g_Frames_Get(ID, 'FRAMES_TELEPORT') then - begin - Anim := TAnimation.Create(ID, False, 3); - end; - g_Sound_PlayExAt('SOUND_GAME_TELEPORT', FObj.X, FObj.Y); - g_GFX_OnceAnim(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32, - FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, Anim); + r_GFX_OnceAnim( + R_GFX_TELEPORT_FAST, + FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32, + FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32 + ); if g_Game_IsServer and g_Game_IsNet then MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32, FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 1, @@ -4525,7 +4122,9 @@ begin FObj.X := X-PLAYER_RECT.X; FObj.Y := Y-PLAYER_RECT.Y; - if FLive and FGhost then + FObj.oldX := FObj.X; // don't interpolate after respawn + FObj.oldY := FObj.Y; + if FAlive and FGhost then begin FXTo := FObj.X; FYTo := FObj.Y; @@ -4535,37 +4134,38 @@ begin begin if dir = 1 then begin - SetDirection(D_LEFT); + SetDirection(TDirection.D_LEFT); FAngle := 180; end else if dir = 2 then begin - SetDirection(D_RIGHT); + SetDirection(TDirection.D_RIGHT); FAngle := 0; end else if dir = 3 then - begin // îáðàòíîå - if FDirection = D_RIGHT then + begin // обратное + if FDirection = TDirection.D_RIGHT then begin - SetDirection(D_LEFT); + SetDirection(TDirection.D_LEFT); FAngle := 180; end else begin - SetDirection(D_RIGHT); + SetDirection(TDirection.D_RIGHT); FAngle := 0; end; end; end; - if not silent and (Anim <> nil) then + if not silent then begin - g_GFX_OnceAnim(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32, - FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, Anim); - Anim.Free(); - + r_GFX_OnceAnim( + R_GFX_TELEPORT_FAST, + FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32, + FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32 + ); if g_Game_IsServer and g_Game_IsNet then MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32, FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 0, @@ -4583,6 +4183,49 @@ begin Result := 1; end; +function TPlayer.refreshCorpse(): Boolean; +var + i: Integer; +begin + Result := False; + FCorpse := -1; + if FAlive or FSpectator then + Exit; + if (gCorpses = nil) or (Length(gCorpses) = 0) then + Exit; + for i := 0 to High(gCorpses) do + if gCorpses[i] <> nil then + if gCorpses[i].FPlayerUID = FUID then + begin + Result := True; + FCorpse := i; + break; + end; +end; + +function TPlayer.getCameraObj(): TObj; +begin + if (not FAlive) and (not FSpectator) and + (FCorpse >= 0) and (FCorpse < Length(gCorpses)) and + (gCorpses[FCorpse] <> nil) and (gCorpses[FCorpse].FPlayerUID = FUID) then + begin + gCorpses[FCorpse].FObj.slopeUpLeft := FObj.slopeUpLeft; + Result := gCorpses[FCorpse].FObj; + end + else + begin + Result := FObj; + end; +end; + +procedure TPlayer.PreUpdate(); +begin + FSlopeOld := FObj.slopeUpLeft; + FIncCamOld := FIncCam; + FObj.oldX := FObj.X; + FObj.oldY := FObj.Y; +end; + procedure TPlayer.Update(); var b: Byte; @@ -4615,15 +4258,20 @@ begin FLoss := 0; end; - if FLive and (gFly or FJetpack) then + if FAlive then + FPunchAnim.Update; + if FPunchAnim.played then + FPunchAnim.Disable; + + if FAlive and (gFly or FJetpack) then FlySmoke(); - if FDirection = D_LEFT then + if FDirection = TDirection.D_LEFT then FAngle := 180 else FAngle := 0; - if FLive and (not FGhost) then + if FAlive and (not FGhost) then begin if FKeys[KEY_UP].Pressed then SeeUp(); @@ -4641,17 +4289,17 @@ begin end; // no need to do that each second frame, weapon queue will take care of it - if FLive and FKeys[KEY_NEXTWEAPON].Pressed and AnyServer then NextWeapon(); - if FLive and FKeys[KEY_PREVWEAPON].Pressed and AnyServer then PrevWeapon(); + if FAlive and FKeys[KEY_NEXTWEAPON].Pressed and AnyServer then NextWeapon(); + if FAlive and FKeys[KEY_PREVWEAPON].Pressed and AnyServer then PrevWeapon(); if gTime mod (GAME_TICK*2) <> 0 then begin - if (FObj.Vel.X = 0) and FLive then + if (FObj.Vel.X = 0) and FAlive then begin if FKeys[KEY_LEFT].Pressed then - Run(D_LEFT); + Run(TDirection.D_LEFT); if FKeys[KEY_RIGHT].Pressed then - Run(D_RIGHT); + Run(TDirection.D_RIGHT); end; if FPhysics then @@ -4665,14 +4313,22 @@ begin FActionChanged := False; - if FLive then + if FAlive then begin // Let alive player do some actions - if FKeys[KEY_LEFT].Pressed then Run(D_LEFT); - if FKeys[KEY_RIGHT].Pressed then Run(D_RIGHT); + if FKeys[KEY_LEFT].Pressed then Run(TDirection.D_LEFT); + if FKeys[KEY_RIGHT].Pressed then Run(TDirection.D_RIGHT); //if FKeys[KEY_NEXTWEAPON].Pressed and AnyServer then NextWeapon(); //if FKeys[KEY_PREVWEAPON].Pressed and AnyServer then PrevWeapon(); - if FKeys[KEY_FIRE].Pressed and AnyServer then Fire(); + if FKeys[KEY_FIRE].Pressed and AnyServer then Fire() + else + begin + if AnyServer then + begin + FlamerOff; + if NetServer then MH_SEND_PlayerStats(FUID); + end; + end; if FKeys[KEY_OPEN].Pressed and AnyServer then Use(); if FKeys[KEY_JUMP].Pressed then Jump() else @@ -4704,7 +4360,7 @@ begin Respawn(False) else // Single if (FTime[T_RESPAWN] <= gTime) and - gGameOn and (not FLive) then + gGameOn and (not FAlive) then begin if (g_Player_GetCount() > 1) then Respawn(False) @@ -4730,7 +4386,7 @@ begin SetSpect := False; for I := FSpectatePlayer + 1 to High(gPlayers) do if gPlayers[I] <> nil then - if gPlayers[I].Live then + if gPlayers[I].alive then if gPlayers[I].UID <> FUID then begin FSpectatePlayer := I; @@ -4792,7 +4448,7 @@ begin if FSpectator then if (FSpectatePlayer <= High(gPlayers)) and (FSpectatePlayer >= 0) then if gPlayers[FSpectatePlayer] <> nil then - if gPlayers[FSpectatePlayer].Live then + if gPlayers[FSpectatePlayer].alive then begin FXTo := gPlayers[FSpectatePlayer].GameX; FYTo := gPlayers[FSpectatePlayer].GameY; @@ -4804,8 +4460,8 @@ begin PANEL_BLOCKMON, True); headwater := HeadInLiquid(0, 0); -// Ñîïðîòèâëåíèå âîçäóõà: - if (not FLive) or not (FKeys[KEY_LEFT].Pressed or FKeys[KEY_RIGHT].Pressed) then +// Сопротивление воздуха: + if (not FAlive) or not (FKeys[KEY_LEFT].Pressed or FKeys[KEY_RIGHT].Pressed) then if FObj.Vel.X <> 0 then FObj.Vel.X := z_dec(FObj.Vel.X, 1); @@ -4813,9 +4469,9 @@ begin DecMin(FPain, 5, 0); DecMin(FPickup, 1, 0); - if FLive and (FObj.Y > gMapInfo.Height+128) and AnyServer then + if FAlive and (FObj.Y > Integer(gMapInfo.Height)+128) and AnyServer then begin - // Îáíóëèòü äåéñòâèÿ ïðèìî÷åê, ÷òîáû ôîí ïðîïàë + // Обнулить действия примочек, чтобы фон пропал FMegaRulez[MR_SUIT] := 0; FMegaRulez[MR_INVUL] := 0; FMegaRulez[MR_INVIS] := 0; @@ -4824,7 +4480,7 @@ begin i := 9; - if FLive then + if FAlive then begin if FCurrWeap = WEAPON_SAW then if not (FSawSound.IsPlaying() or FSawSoundHit.IsPlaying() or @@ -4869,7 +4525,7 @@ begin begin wx := FObj.X+WEAPONPOINT[FDirection].X; wy := FObj.Y+WEAPONPOINT[FDirection].Y; - xd := wx+IfThen(FDirection = D_LEFT, -30, 30); + xd := wx+IfThen(FDirection = TDirection.D_LEFT, -30, 30); yd := wy+firediry(); g_Weapon_bfgshot(wx, wy, xd, yd, FUID); if NetServer then MH_SEND_PlayerFire(FUID, WEAPON_BFG, wx, wy, xd, yd); @@ -4932,11 +4588,13 @@ begin if FFirePainTime <= 0 then begin if g_Game_IsServer then - Damage(5, FFireAttacker, 0, 0, HIT_FLAME); - FFirePainTime := 18; + Damage(2, FFireAttacker, 0, 0, HIT_FLAME); + FFirePainTime := 12 - FFireTime div 12; end; FFirePainTime := FFirePainTime - 1; FFireTime := FFireTime - 1; + if ((FFireTime mod 33) = 0) and (FMegaRulez[MR_INVUL] < gTime) then + FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y); if (FFireTime = 0) and g_Game_IsNet and g_Game_IsServer then MH_SEND_PlayerStats(FUID); end; @@ -4968,7 +4626,7 @@ begin else if FHealth > -50 then Kill(K_HARDKILL, FLastSpawnerUID, FLastHit) else Kill(K_EXTRAHARDKILL, FLastSpawnerUID, FLastHit); - if FLive then + if FAlive and ((FLastHit <> HIT_FLAME) or (FFireTime <= 0)) then begin if FDamageBuffer <= 20 then FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y) else if FDamageBuffer <= 55 then FModel.PlaySound(MODELSOUND_PAIN, 2, FObj.X, FObj.Y) @@ -4980,24 +4638,44 @@ begin end; {CollideItem();} - end; // if FLive then ... + end; // if FAlive then ... if (FActionAnim = A_PAIN) and (FModel.Animation <> A_PAIN) then begin FModel.ChangeAnimation(FActionAnim, FActionForce); - FModel.GetCurrentAnimation.MinLength := i; - FModel.GetCurrentAnimationMask.MinLength := i; + FModel.AnimState.MinLength := i; end else FModel.ChangeAnimation(FActionAnim, FActionForce and (FModel.Animation <> A_STAND)); - if (FModel.GetCurrentAnimation.Played or ((not FActionChanged) and (FModel.Animation = A_WALK))) + if (FModel.AnimState.Played or ((not FActionChanged) and (FModel.Animation = A_WALK))) then SetAction(A_STAND, True); - if not ((FModel.Animation = A_WALK) and (Abs(FObj.Vel.X) < 4) and not FModel.Fire) then FModel.Update; + if not ((FModel.Animation = A_WALK) and (Abs(FObj.Vel.X) < 4) and not FModel.GetFire()) then FModel.Update; for b := Low(FKeys) to High(FKeys) do if FKeys[b].Time = 0 then FKeys[b].Pressed := False else Dec(FKeys[b].Time); end; + +procedure TPlayer.getMapBox (out x, y, w, h: Integer); inline; +begin + x := FObj.X+PLAYER_RECT.X; + y := FObj.Y+PLAYER_RECT.Y; + w := PLAYER_RECT.Width; + h := PLAYER_RECT.Height; +end; + + +procedure TPlayer.moveBy (dx, dy: Integer); inline; +begin + if (dx <> 0) or (dy <> 0) then + begin + FObj.X += dx; + FObj.Y += dy; + positionChanged(); + end; +end; + + function TPlayer.Collide(X, Y: Integer; Width, Height: Word): Boolean; begin Result := g_Collide(FObj.X+PLAYER_RECT.X, @@ -5078,7 +4756,7 @@ begin for a := 0 to High(gPlayers) do if (gPlayers[a] <> nil) and (gPlayers[a] <> Self) and - gPlayers[a].Live and SameTeam(FUID, gPlayers[a].FUID) and + gPlayers[a].alive and SameTeam(FUID, gPlayers[a].FUID) and g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, FObj.Rect.Width, FObj.Rect.Height, @gPlayers[a].FObj) then begin @@ -5105,6 +4783,7 @@ begin case FCurrWeap of WEAPON_KASTET: begin + DoPunch(); if R_BERSERK in FRulez then begin //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID); @@ -5217,6 +4896,7 @@ begin WEAPON_FLAMETHROWER: begin g_Weapon_flame(wx, wy, xd, yd, FUID, WID); + FlamerOn; FFireAngle := FAngle; f := True; end; @@ -5239,16 +4919,20 @@ procedure TPlayer.SetLerp(XTo, YTo: Integer); var AX, AY: Integer; begin - if NetInterpLevel < 1 then + FXTo := XTo; + FYTo := YTo; + if FJustTeleported or (NetInterpLevel < 1) then begin FObj.X := XTo; FObj.Y := YTo; + if FJustTeleported then + begin + FObj.oldX := FObj.X; + FObj.oldY := FObj.Y; + end; end else begin - FXTo := XTo; - FYTo := YTo; - AX := Abs(FXTo - FObj.X); AY := Abs(FYTo - FObj.Y); if (AX > 32) or (AX <= NetInterpLevel) then @@ -5273,7 +4957,7 @@ end; function TPlayer.GetFlag(Flag: Byte): Boolean; var s, ts: String; - evtype: Byte; + evtype, a: Byte; begin Result := False; @@ -5282,7 +4966,7 @@ begin if not g_Game_IsServer then Exit; -// Ïðèíåñ ÷óæîé ôëàã íà ñâîþ áàçó: +// Принес чужой флаг на свою базу: if (Flag = FTeam) and (gFlags[Flag].State = FLAG_STATE_NORMAL) and (FFlag <> FLAG_NONE) then @@ -5301,6 +4985,16 @@ begin g_Map_ResetFlag(FFlag); g_Game_Message(Format(_lc[I_MESSAGE_FLAG_CAPTURE], [AnsiUpperCase(s)]), 144); + if ((Self = gPlayer1) or (Self = gPlayer2) + or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam)) + or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then + a := 0 + else + a := 1; + + if not sound_cap_flag[a].IsPlaying() then + sound_cap_flag[a].Play(); + gTeamStat[FTeam].Goals := gTeamStat[FTeam].Goals + 1; Result := True; @@ -5315,7 +5009,7 @@ begin Exit; end; -// Ïîäîáðàë ñâîé ôëàã - âåðíóë åãî íà áàçó: +// Подобрал свой флаг - вернул его на базу: if (Flag = FTeam) and (gFlags[Flag].State = FLAG_STATE_DROPPED) then begin @@ -5332,6 +5026,16 @@ begin g_Map_ResetFlag(Flag); g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144); + if ((Self = gPlayer1) or (Self = gPlayer2) + or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam)) + or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then + a := 0 + else + a := 1; + + if not sound_ret_flag[a].IsPlaying() then + sound_ret_flag[a].Play(); + Result := True; if g_Game_IsNet then begin @@ -5341,7 +5045,7 @@ begin Exit; end; -// Ïîäîáðàë ÷óæîé ôëàã: +// Подобрал чужой флаг: if (Flag <> FTeam) and (FTime[T_FLAGCAP] <= gTime) then begin SetFlag(Flag); @@ -5359,6 +5063,16 @@ begin gFlags[Flag].State := FLAG_STATE_CAPTURED; + if ((Self = gPlayer1) or (Self = gPlayer2) + or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam)) + or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then + a := 0 + else + a := 1; + + if not sound_get_flag[a].IsPlaying() then + sound_get_flag[a].Play(); + Result := True; if g_Game_IsNet then begin @@ -5375,9 +5089,10 @@ begin FModel.SetFlag(FFlag); end; -function TPlayer.DropFlag(): Boolean; +function TPlayer.DropFlag(Silent: Boolean = True): Boolean; var s: String; + a: Byte; begin Result := False; if (not g_Game_IsServer) or (FFlag = FLAG_NONE) then @@ -5402,6 +5117,16 @@ begin g_Console_Add(Format(_lc[I_PLAYER_FLAG_DROP], [FName, s]), True); g_Game_Message(Format(_lc[I_MESSAGE_FLAG_DROP], [AnsiUpperCase(s)]), 144); + if ((Self = gPlayer1) or (Self = gPlayer2) + or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam)) + or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then + a := 0 + else + a := 1; + + if (not Silent) and (not sound_lost_flag[a].IsPlaying()) then + sound_lost_flag[a].Play(); + if g_Game_IsNet then MH_SEND_FlagEvent(FLAG_STATE_DROPPED, Flag, FUID, False); end; @@ -5411,6 +5136,11 @@ end; procedure TPlayer.GetSecret(); begin + if (self = gPlayer1) or (self = gPlayer2) then + begin + g_Console_Add(Format(_lc[I_PLAYER_SECRET], [FName]), True); + g_Sound_PlayEx('SOUND_GAME_SECRET'); + end; Inc(FSecrets); end; @@ -5452,324 +5182,283 @@ end; procedure TPlayer.RememberState(); var i: Integer; -begin - FSavedState.Health := FHealth; - FSavedState.Armor := FArmor; - FSavedState.Air := FAir; - FSavedState.JetFuel := FJetFuel; - FSavedState.CurrWeap := FCurrWeap; - FSavedState.NextWeap := FNextWeap; - FSavedState.NextWeapDelay := FNextWeapDelay; - - for i := 0 to 3 do - FSavedState.Ammo[i] := FAmmo[i]; - for i := 0 to 3 do - FSavedState.MaxAmmo[i] := FMaxAmmo[i]; + SavedState: TPlayerSavedState; +begin + SavedState.Health := FHealth; + SavedState.Armor := FArmor; + SavedState.Air := FAir; + SavedState.JetFuel := FJetFuel; + SavedState.CurrWeap := FCurrWeap; + SavedState.NextWeap := FNextWeap; + SavedState.NextWeapDelay := FNextWeapDelay; + for i := Low(FWeapon) to High(FWeapon) do + SavedState.Weapon[i] := FWeapon[i]; + for i := Low(FAmmo) to High(FAmmo) do + SavedState.Ammo[i] := FAmmo[i]; + for i := Low(FMaxAmmo) to High(FMaxAmmo) do + SavedState.MaxAmmo[i] := FMaxAmmo[i]; + SavedState.Rulez := FRulez - [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE]; + + FSavedStateNum := -1; + for i := Low(SavedStates) to High(SavedStates) do + if not SavedStates[i].Used then + begin + FSavedStateNum := i; + break; + end; + if FSavedStateNum < 0 then + begin + SetLength(SavedStates, Length(SavedStates) + 1); + FSavedStateNum := High(SavedStates); + end; - FSavedState.Rulez := FRulez; - FSavedState.WaitRecall := True; + SavedState.Used := True; + SavedStates[FSavedStateNum] := SavedState; end; procedure TPlayer.RecallState(); var i: Integer; + SavedState: TPlayerSavedState; begin - if not FSavedState.WaitRecall then Exit; - - FHealth := FSavedState.Health; - FArmor := FSavedState.Armor; - FAir := FSavedState.Air; - FJetFuel := FSavedState.JetFuel; - FCurrWeap := FSavedState.CurrWeap; - FNextWeap := FSavedState.NextWeap; - FNextWeapDelay := FSavedState.NextWeapDelay; - - for i := 0 to 3 do - FAmmo[i] := FSavedState.Ammo[i]; - for i := 0 to 3 do - FMaxAmmo[i] := FSavedState.MaxAmmo[i]; + if(FSavedStateNum < 0) or (FSavedStateNum > High(SavedStates)) then + Exit; - FRulez := FSavedState.Rulez; - FSavedState.WaitRecall := False; + SavedState := SavedStates[FSavedStateNum]; + SavedStates[FSavedStateNum].Used := False; + FSavedStateNum := -1; + + FHealth := SavedState.Health; + FArmor := SavedState.Armor; + FAir := SavedState.Air; + FJetFuel := SavedState.JetFuel; + FCurrWeap := SavedState.CurrWeap; + FNextWeap := SavedState.NextWeap; + FNextWeapDelay := SavedState.NextWeapDelay; + for i := Low(FWeapon) to High(FWeapon) do + FWeapon[i] := SavedState.Weapon[i]; + for i := Low(FAmmo) to High(FAmmo) do + FAmmo[i] := SavedState.Ammo[i]; + for i := Low(FMaxAmmo) to High(FMaxAmmo) do + FMaxAmmo[i] := SavedState.MaxAmmo[i]; + FRulez := SavedState.Rulez; if gGameSettings.GameType = GT_SERVER then MH_SEND_PlayerStats(FUID); end; -procedure TPlayer.SaveState(var Mem: TBinMemoryWriter); +procedure TPlayer.SaveState (st: TStream); var i: Integer; - sig: DWORD; - str: String; b: Byte; begin - if FIamBot then - i := 512 - else - i := 256; - - Mem := TBinMemoryWriter.Create(i); - -// Ñèãíàòóðà èãðîêà: - sig := PLAYER_SIGNATURE; // 'PLYR' - Mem.WriteDWORD(sig); -// Áîò èëè ÷åëîâåê: - Mem.WriteBoolean(FIamBot); -// UID èãðîêà: - Mem.WriteWord(FUID); -// Èìÿ èãðîêà: - Mem.WriteString(FName, 32); -// Êîìàíäà: - Mem.WriteByte(FTeam); -// Æèâ ëè: - Mem.WriteBoolean(FLive); -// Èçðàñõîäîâàë ëè âñå æèçíè: - Mem.WriteBoolean(FNoRespawn); -// Íàïðàâëåíèå: - if FDirection = D_LEFT then - b := 1 - else // D_RIGHT - b := 2; - Mem.WriteByte(b); -// Çäîðîâüå: - Mem.WriteInt(FHealth); -// Æèçíè: - Mem.WriteByte(FLives); -// Áðîíÿ: - Mem.WriteInt(FArmor); -// Çàïàñ âîçäóõà: - Mem.WriteInt(FAir); -// Çàïàñ ãîðþ÷åãî: - Mem.WriteInt(FJetFuel); -// Áîëü: - Mem.WriteInt(FPain); -// Óáèë: - Mem.WriteInt(FKills); -// Óáèë ìîíñòðîâ: - Mem.WriteInt(FMonsterKills); -// Ôðàãîâ: - Mem.WriteInt(FFrags); -// Ôðàãîâ ïîäðÿä: - Mem.WriteByte(FFragCombo); -// Âðåìÿ ïîñëåäíåãî ôðàãà: - Mem.WriteDWORD(FLastFrag); -// Ñìåðòåé: - Mem.WriteInt(FDeath); -// Êàêîé ôëàã íåñåò: - Mem.WriteByte(FFlag); -// Íàøåë ñåêðåòîâ: - Mem.WriteInt(FSecrets); -// Òåêóùåå îðóæèå: - Mem.WriteByte(FCurrWeap); -// Æåëàåìîå îðóæèå: - Mem.WriteWord(FNextWeap); -// ...è ïàóçà - Mem.WriteByte(FNextWeapDelay); -// Âðåìÿ çàðÿäêè BFG: - Mem.WriteSmallInt(FBFGFireCounter); -// Áóôåð óðîíà: - Mem.WriteInt(FDamageBuffer); -// Ïîñëåäíèé óäàðèâøèé: - Mem.WriteWord(FLastSpawnerUID); -// Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà: - Mem.WriteByte(FLastHit); -// Îáúåêò èãðîêà: - Obj_SaveState(@FObj, Mem); -// Òåêóùåå êîëè÷åñòâî ïàòðîíîâ: - for i := A_BULLETS to A_HIGH do - Mem.WriteWord(FAmmo[i]); -// Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ: - for i := A_BULLETS to A_HIGH do - Mem.WriteWord(FMaxAmmo[i]); -// Íàëè÷èå îðóæèÿ: - for i := WP_FIRST to WP_LAST do - Mem.WriteBoolean(FWeapon[i]); -// Âðåìÿ ïåðåçàðÿäêè îðóæèÿ: - for i := WP_FIRST to WP_LAST do - Mem.WriteWord(FReloading[i]); -// Íàëè÷èå ðþêçàêà: - if R_ITEM_BACKPACK in FRulez then - b := 1 - else - b := 0; - Mem.WriteByte(b); -// Íàëè÷èå êðàñíîãî êëþ÷à: - if R_KEY_RED in FRulez then - b := 1 - else - b := 0; - Mem.WriteByte(b); -// Íàëè÷èå çåëåíîãî êëþ÷à: - if R_KEY_GREEN in FRulez then - b := 1 - else - b := 0; - Mem.WriteByte(b); -// Íàëè÷èå ñèíåãî êëþ÷à: - if R_KEY_BLUE in FRulez then - b := 1 - else - b := 0; - Mem.WriteByte(b); -// Íàëè÷èå áåðñåðêà: - if R_BERSERK in FRulez then - b := 1 - else - b := 0; - Mem.WriteByte(b); -// Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ: - for i := MR_SUIT to MR_MAX do - Mem.WriteDWORD(FMegaRulez[i]); -// Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà: - for i := T_RESPAWN to T_FLAGCAP do - Mem.WriteDWORD(FTime[i]); -// Íàçâàíèå ìîäåëè: - str := FModel.Name; - Mem.WriteString(str); -// Öâåò ìîäåëè: - b := FColor.R; - Mem.WriteByte(b); - b := FColor.G; - Mem.WriteByte(b); - b := FColor.B; - Mem.WriteByte(b); -end; - -procedure TPlayer.LoadState(var Mem: TBinMemoryReader); + // Сигнатура игрока + utils.writeSign(st, 'PLYR'); + utils.writeInt(st, Byte(PLR_SAVE_VERSION)); // version + // Бот или человек + utils.writeBool(st, FIamBot); + // UID игрока + utils.writeInt(st, Word(FUID)); + // Имя игрока + utils.writeStr(st, FName); + // Команда + utils.writeInt(st, Byte(FTeam)); + // Жив ли + utils.writeBool(st, FAlive); + // Израсходовал ли все жизни + utils.writeBool(st, FNoRespawn); + // Направление + if FDirection = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT + utils.writeInt(st, Byte(b)); + // Здоровье + utils.writeInt(st, LongInt(FHealth)); + // Коэффициент инвалидности + utils.writeInt(st, LongInt(FHandicap)); + // Жизни + utils.writeInt(st, Byte(FLives)); + // Броня + utils.writeInt(st, LongInt(FArmor)); + // Запас воздуха + utils.writeInt(st, LongInt(FAir)); + // Запас горючего + utils.writeInt(st, LongInt(FJetFuel)); + // Боль + utils.writeInt(st, LongInt(FPain)); + // Убил + utils.writeInt(st, LongInt(FKills)); + // Убил монстров + utils.writeInt(st, LongInt(FMonsterKills)); + // Фрагов + utils.writeInt(st, LongInt(FFrags)); + // Фрагов подряд + utils.writeInt(st, Byte(FFragCombo)); + // Время последнего фрага + utils.writeInt(st, LongWord(FLastFrag)); + // Смертей + utils.writeInt(st, LongInt(FDeath)); + // Какой флаг несет + utils.writeInt(st, Byte(FFlag)); + // Нашел секретов + utils.writeInt(st, LongInt(FSecrets)); + // Текущее оружие + utils.writeInt(st, Byte(FCurrWeap)); + // Желаемое оружие + utils.writeInt(st, Word(FNextWeap)); + // ...и пауза + utils.writeInt(st, Byte(FNextWeapDelay)); + // Время зарядки BFG + utils.writeInt(st, SmallInt(FBFGFireCounter)); + // Буфер урона + utils.writeInt(st, LongInt(FDamageBuffer)); + // Последний ударивший + utils.writeInt(st, Word(FLastSpawnerUID)); + // Тип последнего полученного урона + utils.writeInt(st, Byte(FLastHit)); + // Объект игрока + Obj_SaveState(st, @FObj); + // Текущее количество патронов + for i := A_BULLETS to A_HIGH do utils.writeInt(st, Word(FAmmo[i])); + // Максимальное количество патронов + for i := A_BULLETS to A_HIGH do utils.writeInt(st, Word(FMaxAmmo[i])); + // Наличие оружия + for i := WP_FIRST to WP_LAST do utils.writeBool(st, FWeapon[i]); + // Время перезарядки оружия + for i := WP_FIRST to WP_LAST do utils.writeInt(st, Word(FReloading[i])); + // Наличие рюкзака + utils.writeBool(st, (R_ITEM_BACKPACK in FRulez)); + // Наличие красного ключа + utils.writeBool(st, (R_KEY_RED in FRulez)); + // Наличие зеленого ключа + utils.writeBool(st, (R_KEY_GREEN in FRulez)); + // Наличие синего ключа + utils.writeBool(st, (R_KEY_BLUE in FRulez)); + // Наличие берсерка + utils.writeBool(st, (R_BERSERK in FRulez)); + // Время действия специальных предметов + for i := MR_SUIT to MR_MAX do utils.writeInt(st, LongWord(FMegaRulez[i])); + // Время до повторного респауна, смены оружия, исользования, захвата флага + for i := T_RESPAWN to T_FLAGCAP do utils.writeInt(st, LongWord(FTime[i])); + // Название модели + utils.writeStr(st, FModel.GetName()); + // Цвет модели + utils.writeInt(st, Byte(FColor.R)); + utils.writeInt(st, Byte(FColor.G)); + utils.writeInt(st, Byte(FColor.B)); +end; + + +procedure TPlayer.LoadState (st: TStream); var i: Integer; - sig: DWORD; str: String; b: Byte; begin - if Mem = nil then - Exit; - -// Ñèãíàòóðà èãðîêà: - Mem.ReadDWORD(sig); - if sig <> PLAYER_SIGNATURE then // 'PLYR' - begin - raise EBinSizeError.Create('TPlayer.LoadState: Wrong Player Signature'); - end; -// Áîò èëè ÷åëîâåê: - Mem.ReadBoolean(FIamBot); -// UID èãðîêà: - Mem.ReadWord(FUID); -// Èìÿ èãðîêà: - Mem.ReadString(str); - if (Self <> gPlayer1) and (Self <> gPlayer2) then - FName := str; -// Êîìàíäà: - Mem.ReadByte(FTeam); -// Æèâ ëè: - Mem.ReadBoolean(FLive); -// Èçðàñõîäîâàë ëè âñå æèçíè: - Mem.ReadBoolean(FNoRespawn); -// Íàïðàâëåíèå: - Mem.ReadByte(b); - if b = 1 then - FDirection := D_LEFT - else // b = 2 - FDirection := D_RIGHT; -// Çäîðîâüå: - Mem.ReadInt(FHealth); -// Æèçíè: - Mem.ReadByte(FLives); -// Áðîíÿ: - Mem.ReadInt(FArmor); -// Çàïàñ âîçäóõà: - Mem.ReadInt(FAir); -// Çàïàñ ãîðþ÷åãî: - Mem.ReadInt(FJetFuel); -// Áîëü: - Mem.ReadInt(FPain); -// Óáèë: - Mem.ReadInt(FKills); -// Óáèë ìîíñòðîâ: - Mem.ReadInt(FMonsterKills); -// Ôðàãîâ: - Mem.ReadInt(FFrags); -// Ôðàãîâ ïîäðÿä: - Mem.ReadByte(FFragCombo); -// Âðåìÿ ïîñëåäíåãî ôðàãà: - Mem.ReadDWORD(FLastFrag); -// Ñìåðòåé: - Mem.ReadInt(FDeath); -// Êàêîé ôëàã íåñåò: - Mem.ReadByte(FFlag); -// Íàøåë ñåêðåòîâ: - Mem.ReadInt(FSecrets); -// Òåêóùåå îðóæèå: - Mem.ReadByte(FCurrWeap); -// Æåëàåìîå îðóæèå: - Mem.ReadWord(FNextWeap); -// ...è ïàóçà - Mem.ReadByte(FNextWeapDelay); -// Âðåìÿ çàðÿäêè BFG: - Mem.ReadSmallInt(FBFGFireCounter); -// Áóôåð óðîíà: - Mem.ReadInt(FDamageBuffer); -// Ïîñëåäíèé óäàðèâøèé: - Mem.ReadWord(FLastSpawnerUID); -// Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà: - Mem.ReadByte(FLastHit); -// Îáúåêò èãðîêà: - Obj_LoadState(@FObj, Mem); -// Òåêóùåå êîëè÷åñòâî ïàòðîíîâ: - for i := A_BULLETS to A_HIGH do - Mem.ReadWord(FAmmo[i]); -// Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ: - for i := A_BULLETS to A_HIGH do - Mem.ReadWord(FMaxAmmo[i]); -// Íàëè÷èå îðóæèÿ: - for i := WP_FIRST to WP_LAST do - Mem.ReadBoolean(FWeapon[i]); -// Âðåìÿ ïåðåçàðÿäêè îðóæèÿ: - for i := WP_FIRST to WP_LAST do - Mem.ReadWord(FReloading[i]); -// Íàëè÷èå ðþêçàêà: - Mem.ReadByte(b); - if b = 1 then - Include(FRulez, R_ITEM_BACKPACK); -// Íàëè÷èå êðàñíîãî êëþ÷à: - Mem.ReadByte(b); - if b = 1 then - Include(FRulez, R_KEY_RED); -// Íàëè÷èå çåëåíîãî êëþ÷à: - Mem.ReadByte(b); - if b = 1 then - Include(FRulez, R_KEY_GREEN); -// Íàëè÷èå ñèíåãî êëþ÷à: - Mem.ReadByte(b); - if b = 1 then - Include(FRulez, R_KEY_BLUE); -// Íàëè÷èå áåðñåðêà: - Mem.ReadByte(b); - if b = 1 then - Include(FRulez, R_BERSERK); -// Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ: - for i := MR_SUIT to MR_MAX do - Mem.ReadDWORD(FMegaRulez[i]); -// Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà: - for i := T_RESPAWN to T_FLAGCAP do - Mem.ReadDWORD(FTime[i]); -// Íàçâàíèå ìîäåëè: - Mem.ReadString(str); -// Öâåò ìîäåëè: - Mem.ReadByte(FColor.R); - Mem.ReadByte(FColor.G); - Mem.ReadByte(FColor.B); - if Self = gPlayer1 then + assert(st <> nil); + + // Сигнатура игрока + if not utils.checkSign(st, 'PLYR') then raise XStreamError.Create('invalid player signature'); + if (utils.readByte(st) <> PLR_SAVE_VERSION) then raise XStreamError.Create('invalid player version'); + // Бот или человек: + FIamBot := utils.readBool(st); + // UID игрока + FUID := utils.readWord(st); + // Имя игрока + str := utils.readStr(st); + if (self <> gPlayer1) and (self <> gPlayer2) then FName := str; + // Команда + FTeam := utils.readByte(st); + // Жив ли + FAlive := utils.readBool(st); + // Израсходовал ли все жизни + FNoRespawn := utils.readBool(st); + // Направление + b := utils.readByte(st); + if b = 1 then FDirection := TDirection.D_LEFT else FDirection := TDirection.D_RIGHT; // b = 2 + // Здоровье + FHealth := utils.readLongInt(st); + // Коэффициент инвалидности + FHandicap := utils.readLongInt(st); + // Жизни + FLives := utils.readByte(st); + // Броня + FArmor := utils.readLongInt(st); + // Запас воздуха + FAir := utils.readLongInt(st); + // Запас горючего + FJetFuel := utils.readLongInt(st); + // Боль + FPain := utils.readLongInt(st); + // Убил + FKills := utils.readLongInt(st); + // Убил монстров + FMonsterKills := utils.readLongInt(st); + // Фрагов + FFrags := utils.readLongInt(st); + // Фрагов подряд + FFragCombo := utils.readByte(st); + // Время последнего фрага + FLastFrag := utils.readLongWord(st); + // Смертей + FDeath := utils.readLongInt(st); + // Какой флаг несет + FFlag := utils.readByte(st); + // Нашел секретов + FSecrets := utils.readLongInt(st); + // Текущее оружие + FCurrWeap := utils.readByte(st); + // Желаемое оружие + FNextWeap := utils.readWord(st); + // ...и пауза + FNextWeapDelay := utils.readByte(st); + // Время зарядки BFG + FBFGFireCounter := utils.readSmallInt(st); + // Буфер урона + FDamageBuffer := utils.readLongInt(st); + // Последний ударивший + FLastSpawnerUID := utils.readWord(st); + // Тип последнего полученного урона + FLastHit := utils.readByte(st); + // Объект игрока + Obj_LoadState(@FObj, st); + // Текущее количество патронов + for i := A_BULLETS to A_HIGH do FAmmo[i] := utils.readWord(st); + // Максимальное количество патронов + for i := A_BULLETS to A_HIGH do FMaxAmmo[i] := utils.readWord(st); + // Наличие оружия + for i := WP_FIRST to WP_LAST do FWeapon[i] := utils.readBool(st); + // Время перезарядки оружия + for i := WP_FIRST to WP_LAST do FReloading[i] := utils.readWord(st); + // Наличие рюкзака + if utils.readBool(st) then Include(FRulez, R_ITEM_BACKPACK); + // Наличие красного ключа + if utils.readBool(st) then Include(FRulez, R_KEY_RED); + // Наличие зеленого ключа + if utils.readBool(st) then Include(FRulez, R_KEY_GREEN); + // Наличие синего ключа + if utils.readBool(st) then Include(FRulez, R_KEY_BLUE); + // Наличие берсерка + if utils.readBool(st) then Include(FRulez, R_BERSERK); + // Время действия специальных предметов + for i := MR_SUIT to MR_MAX do FMegaRulez[i] := utils.readLongWord(st); + // Время до повторного респауна, смены оружия, исользования, захвата флага + for i := T_RESPAWN to T_FLAGCAP do FTime[i] := utils.readLongWord(st); + // Название модели + str := utils.readStr(st); + // Цвет модели + FColor.R := utils.readByte(st); + FColor.G := utils.readByte(st); + FColor.B := utils.readByte(st); + if (self = gPlayer1) then begin str := gPlayer1Settings.Model; FColor := gPlayer1Settings.Color; - end; - if Self = gPlayer2 then + end + else if (self = gPlayer2) then begin str := gPlayer2Settings.Model; FColor := gPlayer2Settings.Color; end; -// Îáíîâëÿåì ìîäåëü èãðîêà: + // Обновляем модель игрока SetModel(str); if gGameSettings.GameMode in [GM_TDM, GM_CTF] then FModel.Color := TEAMCOLOR[FTeam] @@ -5777,6 +5466,7 @@ begin FModel.Color := FColor; end; + procedure TPlayer.AllRulez(Health: Boolean); var a: Integer; @@ -5869,6 +5559,7 @@ begin if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then begin FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME; + FSpawnInvul := 0; end; ITEM_INVIS: @@ -5953,9 +5644,7 @@ begin end; procedure TPlayer.FlySmoke(Times: DWORD = 1); -var - id, i: DWORD; - Anim: TAnimation; + var i: DWORD; begin if (Random(5) = 1) and (Times = 1) then Exit; @@ -5971,37 +5660,29 @@ begin Exit; end; - if g_Frames_Get(id, 'FRAMES_SMOKE') then + for i := 1 to Times do begin - for i := 1 to Times do - begin - Anim := TAnimation.Create(id, False, 3); - Anim.Alpha := 150; - g_GFX_OnceAnim(Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(Anim.Width div 2), - Obj.Y+Obj.Rect.Height-4+Random(8+Times*2), Anim, ONCEANIM_SMOKE); - Anim.Free(); - end; + r_GFX_OnceAnim( + R_GFX_SMOKE_TRANS, + Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(R_GFX_SMOKE_WIDTH div 2), + Obj.Y+Obj.Rect.Height-4+Random(8+Times*2) + ); end; end; procedure TPlayer.OnFireFlame(Times: DWORD = 1); -var - id, i: DWORD; - Anim: TAnimation; + var i: DWORD; begin if (Random(10) = 1) and (Times = 1) then Exit; - if g_Frames_Get(id, 'FRAMES_FLAME') then + for i := 1 to Times do begin - for i := 1 to Times do - begin - Anim := TAnimation.Create(id, False, 3); - Anim.Alpha := 0; - g_GFX_OnceAnim(Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(Anim.Width div 2), - Obj.Y+8+Random(8+Times*2), Anim, ONCEANIM_SMOKE); - Anim.Free(); - end; + r_GFX_OnceAnim( + R_GFX_FLAME, + Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(R_GFX_FLAME_WIDTH div 2), + Obj.Y+8+Random(8+Times*2) + ); end; end; @@ -6011,6 +5692,12 @@ begin FSawSoundIdle.Pause(Enable); FSawSoundHit.Pause(Enable); FSawSoundSelect.Pause(Enable); + FFlameSoundOn.Pause(Enable); + FFlameSoundOff.Pause(Enable); + FFlameSoundWork.Pause(Enable); + FJetSoundFly.Pause(Enable); + FJetSoundOn.Pause(Enable); + FJetSoundOff.Pause(Enable); end; { T C o r p s e : } @@ -6021,33 +5708,53 @@ begin FObj.X := X; FObj.Y := Y; FObj.Rect := PLAYER_CORPSERECT; - FModelName := ModelName; FMess := aMess; + FModel := g_PlayerModel_Get(ModelName); if FMess then - begin - FState := CORPSE_STATE_MESS; - g_PlayerModel_GetAnim(ModelName, A_DIE2, FAnimation, FAnimationMask); - end + begin + FState := CORPSE_STATE_MESS; + FModel.ChangeAnimation(A_DIE2); + end else - begin - FState := CORPSE_STATE_NORMAL; - g_PlayerModel_GetAnim(ModelName, A_DIE1, FAnimation, FAnimationMask); - end; + begin + FState := CORPSE_STATE_NORMAL; + FModel.ChangeAnimation(A_DIE1); + end; end; destructor TCorpse.Destroy(); begin - FAnimation.Free(); - + FModel.Free; inherited; end; -procedure TCorpse.positionChanged (); begin end; +function TCorpse.ObjPtr (): PObj; inline; begin result := @FObj; end; -procedure TCorpse.Damage(Value: Word; vx, vy: Integer); -var - pm: TPlayerModel; +procedure TCorpse.positionChanged (); inline; begin end; + +procedure TCorpse.moveBy (dx, dy: Integer); inline; +begin + if (dx <> 0) or (dy <> 0) then + begin + FObj.X += dx; + FObj.Y += dy; + positionChanged(); + end; +end; + + +procedure TCorpse.getMapBox (out x, y, w, h: Integer); inline; +begin + x := FObj.X+PLAYER_CORPSERECT.X; + y := FObj.Y+PLAYER_CORPSERECT.Y; + w := PLAYER_CORPSERECT.Width; + h := PLAYER_CORPSERECT.Height; +end; + + +procedure TCorpse.Damage(Value: Word; SpawnerUID: Word; vx, vy: Integer); + var Blood: TModelBlood; begin if FState = CORPSE_STATE_REMOVEME then Exit; @@ -6055,52 +5762,42 @@ begin FDamage := FDamage + Value; if FDamage > 150 then + begin + if FModel <> nil then begin - if FAnimation <> nil then - begin - FAnimation.Free(); - FAnimation := nil; - - FState := CORPSE_STATE_REMOVEME; - - g_Player_CreateGibs(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2), - FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2), - FModelName, FColor); - // Çâóê ìÿñà îò òðóïà: - pm := g_PlayerModel_Get(FModelName); - pm.PlaySound(MODELSOUND_DIE, 5, FObj.X, FObj.Y); - pm.Free; - end; + FState := CORPSE_STATE_REMOVEME; + + g_Player_CreateGibs( + FObj.X + FObj.Rect.X + (FObj.Rect.Width div 2), + FObj.Y + FObj.Rect.Y + (FObj.Rect.Height div 2), + FModel.id, + FModel.Color + ); + + // Звук мяса от трупа: + FModel.PlaySound(MODELSOUND_DIE, 5, FObj.X, FObj.Y); + + // Зловещий смех: + if (gBodyKillEvent <> -1) and gDelayedEvents[gBodyKillEvent].Pending then + gDelayedEvents[gBodyKillEvent].Pending := False; + gBodyKillEvent := g_Game_DelayEvent(DE_BODYKILL, 1050, SpawnerUID); + + FModel.Free; + FModel := nil; end + end else begin + Blood := FModel.GetBlood(); FObj.Vel.X := FObj.Vel.X + vx; FObj.Vel.Y := FObj.Vel.Y + vy; g_GFX_Blood(FObj.X+PLAYER_CORPSERECT.X+(PLAYER_CORPSERECT.Width div 2), FObj.Y+PLAYER_CORPSERECT.Y+(PLAYER_CORPSERECT.Height div 2), Value, vx, vy, 16, (PLAYER_CORPSERECT.Height*2) div 3, - 150, 0, 0); + Blood.R, Blood.G, Blood.B, Blood.Kind); end; end; -procedure TCorpse.Draw(); -begin - if FState = CORPSE_STATE_REMOVEME then - Exit; - - if FAnimation <> nil then - FAnimation.Draw(FObj.X, FObj.Y, M_NONE); - - if FAnimationMask <> nil then - begin - e_Colors := FColor; - FAnimationMask.Draw(FObj.X, FObj.Y, M_NONE); - e_Colors.R := 255; - e_Colors.G := 255; - e_Colors.B := 255; - end; -end; - procedure TCorpse.Update(); var st: Word; @@ -6108,6 +5805,9 @@ begin if FState = CORPSE_STATE_REMOVEME then Exit; + FObj.oldX := FObj.X; + FObj.oldY := FObj.Y; + if gTime mod (GAME_TICK*2) <> 0 then begin g_Obj_Move(@FObj, True, True, True); @@ -6115,7 +5815,7 @@ begin Exit; end; -// Ñîïðîòèâëåíèå âîçäóõà äëÿ òðóïà: +// Сопротивление воздуха для трупа: FObj.Vel.X := z_dec(FObj.Vel.X, 1); st := g_Obj_Move(@FObj, True, True, True); @@ -6127,87 +5827,78 @@ begin Exit; end; - if FAnimation <> nil then - FAnimation.Update(); - if FAnimationMask <> nil then - FAnimationMask.Update(); -end; - -procedure TCorpse.SaveState(var Mem: TBinMemoryWriter); -var - sig: DWORD; - anim: Boolean; -begin - if Mem = nil then - Exit; - -// Ñèãíàòóðà òðóïà: - sig := CORPSE_SIGNATURE; // 'CORP' - Mem.WriteDWORD(sig); -// Ñîñòîÿíèå: - Mem.WriteByte(FState); -// Íàêîïëåííûé óðîí: - Mem.WriteByte(FDamage); -// Öâåò: - Mem.WriteByte(FColor.R); - Mem.WriteByte(FColor.G); - Mem.WriteByte(FColor.B); -// Îáúåêò òðóïà: - Obj_SaveState(@FObj, Mem); -// Åñòü ëè àíèìàöèÿ: - anim := FAnimation <> nil; - Mem.WriteBoolean(anim); -// Åñëè åñòü - ñîõðàíÿåì: - if anim then - FAnimation.SaveState(Mem); -// Åñòü ëè ìàñêà àíèìàöèè: - anim := FAnimationMask <> nil; - Mem.WriteBoolean(anim); -// Åñëè åñòü - ñîõðàíÿåì: - if anim then - FAnimationMask.SaveState(Mem); -end; - -procedure TCorpse.LoadState(var Mem: TBinMemoryReader); -var - sig: DWORD; - anim: Boolean; -begin - if Mem = nil then - Exit; - -// Ñèãíàòóðà òðóïà: - Mem.ReadDWORD(sig); - if sig <> CORPSE_SIGNATURE then // 'CORP' - begin - raise EBinSizeError.Create('TCorpse.LoadState: Wrong Corpse Signature'); - end; -// Ñîñòîÿíèå: - Mem.ReadByte(FState); -// Íàêîïëåííûé óðîí: - Mem.ReadByte(FDamage); -// Öâåò: - Mem.ReadByte(FColor.R); - Mem.ReadByte(FColor.G); - Mem.ReadByte(FColor.B); -// Îáúåêò òðóïà: - Obj_LoadState(@FObj, Mem); -// Åñòü ëè àíèìàöèÿ: - Mem.ReadBoolean(anim); -// Åñëè åñòü - çàãðóæàåì: + if FModel <> nil then + FModel.Update; +end; + + +procedure TCorpse.SaveState (st: TStream); + var anim: Boolean; +begin + assert(st <> nil); + + // Сигнатура трупа + utils.writeSign(st, 'CORP'); + utils.writeInt(st, Byte(0)); + // Состояние + utils.writeInt(st, Byte(FState)); + // Накопленный урон + utils.writeInt(st, Byte(FDamage)); + // Цвет + utils.writeInt(st, Byte(FModel.Color.R)); + utils.writeInt(st, Byte(FModel.Color.G)); + utils.writeInt(st, Byte(FModel.Color.B)); + // Объект трупа + Obj_SaveState(st, @FObj); + utils.writeInt(st, Word(FPlayerUID)); + // animation + anim := (FModel <> nil); + utils.writeBool(st, anim); + if anim then FModel.AnimState.SaveState(st); + // animation for mask (same as animation, compat with older saves) + anim := (FModel <> nil); + utils.writeBool(st, anim); + if anim then FModel.AnimState.SaveState(st); +end; + + +procedure TCorpse.LoadState (st: TStream); + var anim: Boolean; r, g, b: Byte; stub: TAnimationState; +begin + assert(st <> nil); + + // Сигнатура трупа + if not utils.checkSign(st, 'CORP') then raise XStreamError.Create('invalid corpse signature'); + if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid corpse version'); + // Состояние + FState := utils.readByte(st); + // Накопленный урон + FDamage := utils.readByte(st); + // Цвет + r := utils.readByte(st); + g := utils.readByte(st); + b := utils.readByte(st); + FModel.SetColor(r, g, b); + // Объект трупа + Obj_LoadState(@FObj, st); + FPlayerUID := utils.readWord(st); + // animation + stub := TAnimationState.Create(False, 0, 0); + anim := utils.readBool(st); if anim then begin - Assert(FAnimation <> nil, 'TCorpse.LoadState: no FAnimation'); - FAnimation.LoadState(Mem); - end; -// Åñòü ëè ìàñêà àíèìàöèè: - Mem.ReadBoolean(anim); -// Åñëè åñòü - çàãðóæàåì: - if anim then + stub.LoadState(st); + FModel.AnimState.CurrentFrame := Min(stub.CurrentFrame, FModel.AnimState.Length); + end + else begin - Assert(FAnimationMask <> nil, 'TCorpse.LoadState: no FAnimationMask'); - FAnimationMask.LoadState(Mem); + FModel.Free; + FModel := nil end; + // animation for mask (same as animation, compat with older saves) + anim := utils.readBool(st); + if anim then stub.LoadState(st); + stub.Free; end; { T B o t : } @@ -6240,14 +5931,6 @@ begin inherited Destroy(); end; -procedure TBot.Draw(); -begin - inherited Draw(); - - //if FTargetUID <> 0 then e_DrawLine(1, FObj.X, FObj.Y, g_Player_Get(FTargetUID).FObj.X, - // g_Player_Get(FTargetUID).FObj.Y, 255, 0, 0); -end; - procedure TBot.Respawn(Silent: Boolean; Force: Boolean = False); begin inherited Respawn(Silent, Force); @@ -6275,20 +5958,20 @@ type function Compare(a, b: TTarget): Integer; begin - if a.Line and not b.Line then // A íà ëèíèè îãíÿ + if a.Line and not b.Line then // A на линии огня Result := -1 else - if not a.Line and b.Line then // B íà ëèíèè îãíÿ + if not a.Line and b.Line then // B на линии огня Result := 1 - else // È A, è B íà ëèíèè èëè íå íà ëèíèè îãíÿ + else // И A, и B на линии или не на линии огня if (a.Line and b.Line) or ((not a.Line) and (not b.Line)) then begin - if a.Dist > b.Dist then // B áëèæå + if a.Dist > b.Dist then // B ближе Result := 1 - else // A áëèæå èëè ðàâíîóäàëåííî ñ B + else // A ближе или равноудаленно с B Result := -1; end - else // Ñòðàííî -> A + else // Странно -> A Result := -1; end; @@ -6304,20 +5987,20 @@ var vsPlayer, vsMonster, ok: Boolean; - function monsUpdate (monidx: Integer; mon: TMonster): Boolean; + function monsUpdate (mon: TMonster): Boolean; begin result := false; // don't stop - if (mon <> nil) and (mon.Live) and (mon.MonsterType <> MONSTER_BARREL) then + if mon.alive and (mon.MonsterType <> MONSTER_BARREL) then begin if not TargetOnScreen(mon.Obj.X+mon.Obj.Rect.X, mon.Obj.Y+mon.Obj.Rect.Y) then exit; x2 := mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2); y2 := mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2); - // Åñëè ìîíñòð íà ýêðàíå è íå ïðèêðûò ñòåíîé + // Если монстр на экране и не прикрыт стеной if g_TraceVector(x1, y1, x2, y2) then begin - // Äîáàâëÿåì ê ñïèñêó âîçìîæíûõ öåëåé + // Добавляем к списку возможных целей SetLength(targets, Length(targets)+1); with targets[High(targets)] do begin @@ -6341,11 +6024,11 @@ begin vsPlayer := LongBool(gGameSettings.Options and GAME_OPTION_BOTVSPLAYER); vsMonster := LongBool(gGameSettings.Options and GAME_OPTION_BOTVSMONSTER); -// Åñëè òåêóùåå îðóæèå íå òî, ÷òî íóæíî, òî ìåíÿåì: +// Если текущее оружие не то, что нужно, то меняем: if FCurrWeap <> FSelectedWeapon then NextWeapon(); -// Åñëè íóæíî ñòðåëÿòü è íóæíîå îðóæèå, òî íàæàòü "Ñòðåëÿòü": +// Если нужно стрелять и нужное оружие, то нажать "Стрелять": if (GetAIFlag('NEEDFIRE') <> '') and (FCurrWeap = FSelectedWeapon) then begin RemoveAIFlag('NEEDFIRE'); @@ -6357,7 +6040,7 @@ begin end; end; -// Êîîðäèíàòû ñòâîëà: +// Координаты ствола: x1 := FObj.X + WEAPONPOINT[FDirection].X; y1 := FObj.Y + WEAPONPOINT[FDirection].Y; @@ -6365,10 +6048,10 @@ begin ok := False; if Target.UID <> 0 then - begin // Öåëü åñòü - íàñòðàèâàåì + begin // Цель есть - настраиваем if (g_GetUIDType(Target.UID) = UID_PLAYER) and vsPlayer then - begin // Èãðîê + begin // Игрок tpla := g_Player_Get(Target.UID); if tpla <> nil then with tpla do @@ -6392,7 +6075,7 @@ begin else if (g_GetUIDType(Target.UID) = UID_MONSTER) and vsMonster then - begin // Ìîíñòð + begin // Монстр mon := g_Monsters_ByUID(Target.UID); if mon <> nil then begin @@ -6412,7 +6095,7 @@ begin end; if not ok then - begin // Öåëè íåò - îáíóëÿåì + begin // Цели нет - обнуляем Target.X := 0; Target.Y := 0; Target.cX := 0; @@ -6424,13 +6107,13 @@ begin targets := nil; -// Åñëè öåëü íå âèäèìà èëè íå íà ëèíèè îãíÿ, òî èùåì âñå âîçìîæíûå öåëè: +// Если цель не видима или не на линии огня, то ищем все возможные цели: if (not Target.Line) or (not Target.Visible) then begin - // Èãðîêè: + // Игроки: if vsPlayer then for a := 0 to High(gPlayers) do - if (gPlayers[a] <> nil) and (gPlayers[a].Live) and + if (gPlayers[a] <> nil) and (gPlayers[a].alive) and (gPlayers[a].FUID <> FUID) and (not SameTeam(FUID, gPlayers[a].FUID)) and (not gPlayers[a].NoTarget) and @@ -6443,10 +6126,10 @@ begin x2 := gPlayers[a].FObj.X + PLAYER_RECT_CX; y2 := gPlayers[a].FObj.Y + PLAYER_RECT_CY; - // Åñëè èãðîê íà ýêðàíå è íå ïðèêðûò ñòåíîé: + // Если игрок на экране и не прикрыт стеной: if g_TraceVector(x1, y1, x2, y2) then begin - // Äîáàâëÿåì ê ñïèñêó âîçìîæíûõ öåëåé: + // Добавляем к списку возможных целей: SetLength(targets, Length(targets)+1); with targets[High(targets)] do begin @@ -6465,88 +6148,88 @@ begin end; end; - // Ìîíñòðû: + // Монстры: if vsMonster then g_Mons_ForEach(monsUpdate); end; -// Åñëè åñòü âîçìîæíûå öåëè: -// (Âûáèðàåì ëó÷øóþ, ìåíÿåì îðóæèå è áåæèì ê íåé/îò íåå) +// Если есть возможные цели: +// (Выбираем лучшую, меняем оружие и бежим к ней/от нее) if targets <> nil then begin - // Âûáèðàåì íàèëó÷øóþ öåëü: + // Выбираем наилучшую цель: BestTarget := targets[0]; if Length(targets) > 1 then for a := 1 to High(targets) do if Compare(BestTarget, targets[a]) = 1 then BestTarget := targets[a]; - // Åñëè ëó÷øàÿ öåëü "âèäíåå" òåêóùåé, òî òåêóùàÿ := ëó÷øàÿ: + // Если лучшая цель "виднее" текущей, то текущая := лучшая: if ((not Target.Visible) and BestTarget.Visible and (Target.UID <> BestTarget.UID)) or ((not Target.Line) and BestTarget.Line and BestTarget.Visible) then begin Target := BestTarget; if (Healthy() = 3) or ((Healthy() = 2)) then - begin // Åñëè çäîðîâû - äîãîíÿåì - if ((RunDirection() = D_LEFT) and (Target.X > FObj.X)) then + begin // Если здоровы - догоняем + if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then SetAIFlag('GORIGHT', '1'); - if ((RunDirection() = D_RIGHT) and (Target.X < FObj.X)) then + if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then SetAIFlag('GOLEFT', '1'); end else - begin // Åñëè ïîáèòû - óáåãàåì - if ((RunDirection() = D_LEFT) and (Target.X < FObj.X)) then + begin // Если побиты - убегаем + if ((RunDirection() = TDirection.D_LEFT) and (Target.X < FObj.X)) then SetAIFlag('GORIGHT', '1'); - if ((RunDirection() = D_RIGHT) and (Target.X > FObj.X)) then + if ((RunDirection() = TDirection.D_RIGHT) and (Target.X > FObj.X)) then SetAIFlag('GOLEFT', '1'); end; - // Âûáèðàåì îðóæèå íà îñíîâå ðàññòîÿíèÿ è ïðèîðèòåòîâ: + // Выбираем оружие на основе расстояния и приоритетов: SelectWeapon(Abs(x1-Target.cX)); end; end; -// Åñëè åñòü öåëü: -// (Äîãîíÿåì/óáåãàåì, ñòðåëÿåì ïî íàïðàâëåíèþ ê öåëè) -// (Åñëè öåëü äàëåêî, òî õâàòèò ñëåäèòü çà íåé) +// Если есть цель: +// (Догоняем/убегаем, стреляем по направлению к цели) +// (Если цель далеко, то хватит следить за ней) if Target.UID <> 0 then begin if not TargetOnScreen(Target.X + Target.Rect.X, Target.Y + Target.Rect.Y) then - begin // Öåëü ñáåæàëà ñ "ýêðàíà" + begin // Цель сбежала с "экрана" if (Healthy() = 3) or ((Healthy() = 2)) then - begin // Åñëè çäîðîâû - äîãîíÿåì - if ((RunDirection() = D_LEFT) and (Target.X > FObj.X)) then + begin // Если здоровы - догоняем + if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then SetAIFlag('GORIGHT', '1'); - if ((RunDirection() = D_RIGHT) and (Target.X < FObj.X)) then + if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then SetAIFlag('GOLEFT', '1'); end else - begin // Åñëè ïîáèòû - çàáûâàåì î öåëè è óáåãàåì + begin // Если побиты - забываем о цели и убегаем Target.UID := 0; - if ((RunDirection() = D_LEFT) and (Target.X < FObj.X)) then + if ((RunDirection() = TDirection.D_LEFT) and (Target.X < FObj.X)) then SetAIFlag('GORIGHT', '1'); - if ((RunDirection() = D_RIGHT) and (Target.X > FObj.X)) then + if ((RunDirection() = TDirection.D_RIGHT) and (Target.X > FObj.X)) then SetAIFlag('GOLEFT', '1'); end; end else - begin // Öåëü ïîêà íà "ýêðàíå" - // Åñëè öåëü íå çàãîðîæåíà ñòåíîé, òî îòìå÷àåì, êîãäà åå âèäåëè: + begin // Цель пока на "экране" + // Если цель не загорожена стеной, то отмечаем, когда ее видели: if g_TraceVector(x1, y1, Target.cX, Target.cY) then FLastVisible := gTime; - // Åñëè ðàçíèöà âûñîò íå âåëèêà, òî äîãîíÿåì: + // Если разница высот не велика, то догоняем: if (Abs(FObj.Y-Target.Y) <= 128) then begin - if ((RunDirection() = D_LEFT) and (Target.X > FObj.X)) then + if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then SetAIFlag('GORIGHT', '1'); - if ((RunDirection() = D_RIGHT) and (Target.X < FObj.X)) then + if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then SetAIFlag('GOLEFT', '1'); end; end; - // Âûáèðàåì óãîë ââåðõ: - if FDirection = D_LEFT then + // Выбираем угол вверх: + if FDirection = TDirection.D_LEFT then angle := ANGLE_LEFTUP else angle := ANGLE_RIGHTUP; @@ -6554,19 +6237,19 @@ begin firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6); fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6); - // Åñëè ïðè óãëå ââåðõ ìîæíî ïîïàñòü â ïðèáëèçèòåëüíîå ïîëîæåíèå öåëè: + // Если при угле вверх можно попасть в приблизительное положение цели: if g_CollideLine(x1, y1, x1+firew, y1+fireh, Target.X+Target.Rect.X+GetInterval(FDifficult.DiagPrecision, 128), //96 Target.Y+Target.Rect.Y+GetInterval(FDifficult.DiagPrecision, 128), Target.Rect.Width, Target.Rect.Height) and g_TraceVector(x1, y1, Target.cX, Target.cY) then - begin // òî íóæíî ñòðåëÿòü ââåðõ + begin // то нужно стрелять вверх SetAIFlag('NEEDFIRE', '1'); SetAIFlag('NEEDSEEUP', '1'); end; - // Âûáèðàåì óãîë âíèç: - if FDirection = D_LEFT then + // Выбираем угол вниз: + if FDirection = TDirection.D_LEFT then angle := ANGLE_LEFTDOWN else angle := ANGLE_RIGHTDOWN; @@ -6574,34 +6257,34 @@ begin firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6); fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6); - // Åñëè ïðè óãëå âíèç ìîæíî ïîïàñòü â ïðèáëèçèòåëüíîå ïîëîæåíèå öåëè: + // Если при угле вниз можно попасть в приблизительное положение цели: if g_CollideLine(x1, y1, x1+firew, y1+fireh, Target.X+Target.Rect.X+GetInterval(FDifficult.DiagPrecision, 128), Target.Y+Target.Rect.Y+GetInterval(FDifficult.DiagPrecision, 128), Target.Rect.Width, Target.Rect.Height) and g_TraceVector(x1, y1, Target.cX, Target.cY) then - begin // òî íóæíî ñòðåëÿòü âíèç + begin // то нужно стрелять вниз SetAIFlag('NEEDFIRE', '1'); SetAIFlag('NEEDSEEDOWN', '1'); end; - // Åñëè öåëü âèäíî è îíà íà òàêîé æå âûñîòå: + // Если цель видно и она на такой же высоте: if Target.Visible and (y1+4 < Target.Y+Target.Rect.Y+Target.Rect.Height) and (y1-4 > Target.Y+Target.Rect.Y) then begin - // Åñëè èäåì â ñòîðîíó öåëè, òî íàäî ñòðåëÿòü: - if ((FDirection = D_LEFT) and (Target.X < FObj.X)) or - ((FDirection = D_RIGHT) and (Target.X > FObj.X)) then - begin // òî íóæíî ñòðåëÿòü âïåðåä + // Если идем в сторону цели, то надо стрелять: + if ((FDirection = TDirection.D_LEFT) and (Target.X < FObj.X)) or + ((FDirection = TDirection.D_RIGHT) and (Target.X > FObj.X)) then + begin // то нужно стрелять вперед SetAIFlag('NEEDFIRE', '1'); SetAIFlag('NEEDSEEDOWN', ''); SetAIFlag('NEEDSEEUP', ''); end; - // Åñëè öåëü â ïðåäåëàõ "ýêðàíà" è ñëîæíîñòü ïîçâîëÿåò ïðûæêè ñáëèæåíèÿ: + // Если цель в пределах "экрана" и сложность позволяет прыжки сближения: if Abs(FObj.X-Target.X) < Trunc(gPlayerScreenSize.X*0.75) then if GetRnd(FDifficult.CloseJump) then - begin // òî åñëè ïîâåçåò - ïðûãàåì (îñîáåííî, åñëè áëèçêî) + begin // то если повезет - прыгаем (особенно, если близко) if Abs(FObj.X-Target.X) < 128 then a := 4 else @@ -6611,75 +6294,75 @@ begin end; end; - // Åñëè öåëü âñå åùå åñòü: + // Если цель все еще есть: if Target.UID <> 0 then - if gTime-FLastVisible > 2000 then // Åñëè âèäåëè äàâíî - Target.UID := 0 // òî çàáûòü öåëü - else // Åñëè âèäåëè íåäàâíî - begin // íî öåëü óáèëè + if gTime-FLastVisible > 2000 then // Если видели давно + Target.UID := 0 // то забыть цель + else // Если видели недавно + begin // но цель убили if Target.IsPlayer then - begin // Öåëü - èãðîê + begin // Цель - игрок pla := g_Player_Get(Target.UID); - if (pla = nil) or (not pla.Live) or pla.NoTarget or + if (pla = nil) or (not pla.alive) or pla.NoTarget or (pla.FMegaRulez[MR_INVIS] >= gTime) then - Target.UID := 0; // òî çàáûòü öåëü + Target.UID := 0; // то забыть цель end else - begin // Öåëü - ìîíñòð + begin // Цель - монстр mon := g_Monsters_ByUID(Target.UID); - if (mon = nil) or (not mon.Live) then - Target.UID := 0; // òî çàáûòü öåëü + if (mon = nil) or (not mon.alive) then + Target.UID := 0; // то забыть цель end; end; end; // if Target.UID <> 0 FTargetUID := Target.UID; -// Åñëè âîçìîæíûõ öåëåé íåò: -// (Àòàêà ÷åãî-íèáóäü ñëåâà èëè ñïðàâà) +// Если возможных целей нет: +// (Атака чего-нибудь слева или справа) if targets = nil then if GetAIFlag('ATTACKLEFT') <> '' then - begin // Åñëè íóæíî àòàêîâàòü íàëåâî + begin // Если нужно атаковать налево RemoveAIFlag('ATTACKLEFT'); SetAIFlag('NEEDJUMP', '1'); - if RunDirection() = D_RIGHT then - begin // Èäåì íå â òó ñòîðîíó + if RunDirection() = TDirection.D_RIGHT then + begin // Идем не в ту сторону if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then - begin // Åñëè çäîðîâû, òî, âîçìîæíî, ñòðåëÿåì áåæèì âëåâî è ñòðåëÿåì + begin // Если здоровы, то, возможно, стреляем бежим влево и стреляем SetAIFlag('NEEDFIRE', '1'); SetAIFlag('GOLEFT', '1'); end; end else - begin // Èäåì â íóæíóþ ñòîðîíó - if GetRnd(FDifficult.InvisFire) then // Âîçìîæíî, ñòðåëÿåì âñëåïóþ + begin // Идем в нужную сторону + if GetRnd(FDifficult.InvisFire) then // Возможно, стреляем вслепую SetAIFlag('NEEDFIRE', '1'); - if Healthy() <= 1 then // Ïîáèòû - óáåãàåì + if Healthy() <= 1 then // Побиты - убегаем SetAIFlag('GORIGHT', '1'); end; end else if GetAIFlag('ATTACKRIGHT') <> '' then - begin // Åñëè íóæíî àòàêîâàòü íàïðàâî + begin // Если нужно атаковать направо RemoveAIFlag('ATTACKRIGHT'); SetAIFlag('NEEDJUMP', '1'); - if RunDirection() = D_LEFT then - begin // Èäåì íå â òó ñòîðîíó + if RunDirection() = TDirection.D_LEFT then + begin // Идем не в ту сторону if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then - begin // Åñëè çäîðîâû, òî, âîçìîæíî, áåæèì âïðàâî è ñòðåëÿåì + begin // Если здоровы, то, возможно, бежим вправо и стреляем SetAIFlag('NEEDFIRE', '1'); SetAIFlag('GORIGHT', '1'); end; end else begin - if GetRnd(FDifficult.InvisFire) then // Âîçìîæíî, ñòðåëÿåì âñëåïóþ + if GetRnd(FDifficult.InvisFire) then // Возможно, стреляем вслепую SetAIFlag('NEEDFIRE', '1'); - if Healthy() <= 1 then // Ïîáèòû - óáåãàåì + if Healthy() <= 1 then // Побиты - убегаем SetAIFlag('GOLEFT', '1'); end; end; @@ -6687,16 +6370,16 @@ begin //HACK! (does it belongs there?) RealizeCurrentWeapon(); -// Åñëè åñòü âîçìîæíûå öåëè: -// (Ñòðåëÿåì ïî íàïðàâëåíèþ ê öåëÿì) +// Если есть возможные цели: +// (Стреляем по направлению к целям) if (targets <> nil) and (GetAIFlag('NEEDFIRE') <> '') then for a := 0 to High(targets) do begin - // Åñëè ìîæåì ñòðåëÿòü ïî äèàãîíàëè: + // Если можем стрелять по диагонали: if GetRnd(FDifficult.DiagFire) then begin - // Èùåì öåëü ñâåðõó è ñòðåëÿåì, åñëè åñòü: - if FDirection = D_LEFT then + // Ищем цель сверху и стреляем, если есть: + if FDirection = TDirection.D_LEFT then angle := ANGLE_LEFTUP else angle := ANGLE_RIGHTUP; @@ -6714,8 +6397,8 @@ begin SetAIFlag('NEEDSEEUP', '1'); end; - // Èùåì öåëü ñíèçó è ñòðåëÿåì, åñëè åñòü: - if FDirection = D_LEFT then + // Ищем цель снизу и стреляем, если есть: + if FDirection = TDirection.D_LEFT then angle := ANGLE_LEFTDOWN else angle := ANGLE_RIGHTDOWN; @@ -6734,30 +6417,30 @@ begin end; end; - // Åñëè öåëü "ïåðåä íîñîì", òî ñòðåëÿåì: + // Если цель "перед носом", то стреляем: if targets[a].Line and targets[a].Visible and - (((FDirection = D_LEFT) and (targets[a].X < FObj.X)) or - ((FDirection = D_RIGHT) and (targets[a].X > FObj.X))) then + (((FDirection = TDirection.D_LEFT) and (targets[a].X < FObj.X)) or + ((FDirection = TDirection.D_RIGHT) and (targets[a].X > FObj.X))) then begin SetAIFlag('NEEDFIRE', '1'); Break; end; end; -// Åñëè ëåòèò ïóëÿ, òî, âîçìîæíî, ïîäïðûãèâàåì: +// Если летит пуля, то, возможно, подпрыгиваем: if g_Weapon_Danger(FUID, FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width, PLAYER_RECT.Height, 40+GetInterval(FDifficult.Cover, 40)) then SetAIFlag('NEEDJUMP', '1'); -// Åñëè êîí÷èëèñü ïàòîðíû, òî íóæíî ñìåíèòü îðóæèå: +// Если кончились паторны, то нужно сменить оружие: ammo := GetAmmoByWeapon(FCurrWeap); if ((FCurrWeap = WEAPON_SHOTGUN2) and (ammo < 2)) or ((FCurrWeap = WEAPON_BFG) and (ammo < 40)) or (ammo = 0) then SetAIFlag('SELECTWEAPON', '1'); -// Åñëè íóæíî ñìåíèòü îðóæèå, òî âûáèðàåì íóæíîå: +// Если нужно сменить оружие, то выбираем нужное: if GetAIFlag('SELECTWEAPON') = '1' then begin SelectWeapon(-1); @@ -6769,7 +6452,7 @@ procedure TBot.Update(); var EnableAI: Boolean; begin - if not FLive then + if not FAlive then begin // Respawn ReleaseKeys(); PressKey(KEY_UP); @@ -6778,7 +6461,7 @@ begin begin EnableAI := True; - // Ïðîâåðÿåì, îòêëþ÷¸í ëè AI áîòîâ + // Проверяем, отключён ли AI ботов if (g_debug_BotAIOff = 1) and (Team = TEAM_RED) then EnableAI := False; if (g_debug_BotAIOff = 2) and (Team = TEAM_BLUE) then @@ -6888,7 +6571,7 @@ procedure TBot.UpdateMove; ReleaseKey(KEY_LEFT); ReleaseKey(KEY_RIGHT); PressKey(KEY_LEFT, Time); - SetDirection(D_LEFT); + SetDirection(TDirection.D_LEFT); end; procedure GoRight(Time: Word = 1); @@ -6896,7 +6579,7 @@ procedure TBot.UpdateMove; ReleaseKey(KEY_LEFT); ReleaseKey(KEY_RIGHT); PressKey(KEY_RIGHT, Time); - SetDirection(D_RIGHT); + SetDirection(TDirection.D_RIGHT); end; function Rnd(a: Word): Boolean; @@ -6906,7 +6589,7 @@ procedure TBot.UpdateMove; procedure Turn(Time: Word = 1200); begin - if RunDirection() = D_LEFT then GoRight(Time) else GoLeft(Time); + if RunDirection() = TDirection.D_LEFT then GoRight(Time) else GoLeft(Time); end; procedure Stop(); @@ -6927,7 +6610,7 @@ procedure TBot.UpdateMove; function CanRun(): Boolean; begin - if RunDirection() = D_LEFT then Result := CanRunLeft() else Result := CanRunRight(); + if RunDirection() = TDirection.D_LEFT then Result := CanRunLeft() else Result := CanRunRight(); end; procedure Jump(Time: Word = 30); @@ -6939,8 +6622,8 @@ procedure TBot.UpdateMove; var x, sx: Integer; begin - { TODO 5 : Ëåñòíèöû } - sx := IfThen(RunDirection() = D_LEFT, -1, 1); + { TODO 5 : Лестницы } + sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1); for x := 1 to PLAYER_RECT.Width do if (not StayOnStep(x*sx, 0)) and (not CollideLevel(x*sx, PLAYER_RECT.Height)) and @@ -6957,8 +6640,8 @@ procedure TBot.UpdateMove; var x, sx, xx: Integer; begin - { TODO 5 : Ëåñòíèöû } - sx := IfThen(RunDirection() = D_LEFT, -1, 1); + { TODO 5 : Лестницы } + sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1); for x := 1 to PLAYER_RECT.Width do if (not StayOnStep(x*sx, 0)) and (not CollideLevel(x*sx, PLAYER_RECT.Height)) and @@ -6981,7 +6664,7 @@ procedure TBot.UpdateMove; begin Result := False; - sx := IfThen(RunDirection() = D_LEFT, -1, 1); + sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1); y := 3; for x := 1 to PLAYER_RECT.Width do @@ -7027,18 +6710,18 @@ procedure TBot.UpdateMove; function BelowLadder(): Boolean; begin - Result := (FullInStep(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) and - not CollideLevel(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or - (FullInStep(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) and - not CollideLevel(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP)); + Result := (FullInStep(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) and + not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or + (FullInStep(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) and + not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP)); end; function BelowLiftUp(): Boolean; begin - Result := ((FullInLift(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) = -1) and - not CollideLevel(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or - ((FullInLift(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) = -1) and - not CollideLevel(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP)); + Result := ((FullInLift(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) = -1) and + not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or + ((FullInLift(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) = -1) and + not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP)); end; function OnTopLift(): Boolean; @@ -7050,7 +6733,7 @@ procedure TBot.UpdateMove; var sx, y: Integer; begin - sx := IfThen(RunDirection() = D_LEFT, -1, 1); + sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1); Result := False; @@ -7130,11 +6813,11 @@ procedure TBot.UpdateMove; end; begin -// Âîçìîæíî, íàæèìàåì êíîïêó: +// Возможно, нажимаем кнопку: if Rnd(16) and IsSafeTrigger() then PressKey(KEY_OPEN); -// Åñëè ïîä ëèôòîì èëè ñòóïåíüêàìè, òî, âîçìîæíî, ïðûãàåì: +// Если под лифтом или ступеньками, то, возможно, прыгаем: if OnLadder() or ((BelowLadder() or BelowLiftUp()) and Rnd(8)) then begin ReleaseKey(KEY_LEFT); @@ -7142,7 +6825,7 @@ begin Jump(); end; -// Èäåì âëåâî, åñëè íàäî áûëî: +// Идем влево, если надо было: if GetAIFlag('GOLEFT') <> '' then begin RemoveAIFlag('GOLEFT'); @@ -7150,7 +6833,7 @@ begin GoLeft(360); end; -// Èäåì âïðàâî, åñëè íàäî áûëî: +// Идем вправо, если надо было: if GetAIFlag('GORIGHT') <> '' then begin RemoveAIFlag('GORIGHT'); @@ -7158,21 +6841,21 @@ begin GoRight(360); end; -// Åñëè âûëåòåëè çà êàðòó, òî ïðîáóåì âåðíóòüñÿ: +// Если вылетели за карту, то пробуем вернуться: if FObj.X < -32 then GoRight(360) else if FObj.X+32 > gMapInfo.Width then GoLeft(360); -// Ïðûãàåì, åñëè íàäî áûëî: +// Прыгаем, если надо было: if GetAIFlag('NEEDJUMP') <> '' then begin Jump(0); RemoveAIFlag('NEEDJUMP'); end; -// Ñìîòðèì ââåðõ, åñëè íàäî áûëî: +// Смотрим вверх, если надо было: if GetAIFlag('NEEDSEEUP') <> '' then begin ReleaseKey(KEY_UP); @@ -7181,7 +6864,7 @@ begin RemoveAIFlag('NEEDSEEUP'); end; -// Ñìîòðèì âíèç, åñëè íàäî áûëî: +// Смотрим вниз, если надо было: if GetAIFlag('NEEDSEEDOWN') <> '' then begin ReleaseKey(KEY_UP); @@ -7190,7 +6873,7 @@ begin RemoveAIFlag('NEEDSEEDOWN'); end; -// Åñëè íóæíî áûëî â äûðó è ìû íå íà çåìëå, òî ïîêîðíî ëåòèì: +// Если нужно было в дыру и мы не на земле, то покорно летим: if GetAIFlag('GOINHOLE') <> '' then if not OnGround() then begin @@ -7200,12 +6883,12 @@ begin SetAIFlag('FALLINHOLE', '1'); end; -// Åñëè ïàäàëè è äîñòèãëè çåìëè, òî õâàòèò ïàäàòü: +// Если падали и достигли земли, то хватит падать: if GetAIFlag('FALLINHOLE') <> '' then if OnGround() then RemoveAIFlag('FALLINHOLE'); -// Åñëè ëåòåëè ïðÿìî è ñåé÷àñ íå íà ëåñòíèöå èëè íà âåðøèíå ëèôòà, òî îòõîäèì â ñòîðîíó: +// Если летели прямо и сейчас не на лестнице или на вершине лифта, то отходим в сторону: if not (KeyPressed(KEY_LEFT) or KeyPressed(KEY_RIGHT)) then if GetAIFlag('FALLINHOLE') = '' then if (not OnLadder()) or (FObj.Vel.Y >= 0) or (OnTopLift()) then @@ -7214,40 +6897,40 @@ begin else GoRight(360); -// Åñëè íà çåìëå è ìîæíî ïîäïðûãíóòü, òî, âîçìîæíî, ïðûãàåì: +// Если на земле и можно подпрыгнуть, то, возможно, прыгаем: if OnGround() and - CanJumpUp(IfThen(RunDirection() = D_LEFT, -1, 1)*32) and + CanJumpUp(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*32) and Rnd(8) then Jump(); -// Åñëè íà çåìëå è âîçëå äûðû (ãëóáèíà > 2 ðîñòîâ èãðîêà): +// Если на земле и возле дыры (глубина > 2 ростов игрока): if OnGround() and NearHole() then - if NearDeepHole() then // Åñëè ýòî áåçäíà + if NearDeepHole() then // Если это бездна case Random(6) of - 0..3: Turn(); // Áåæèì îáðàòíî - 4: Jump(); // Ïðûãàåì - 5: begin // Ïðûãàåì îáðàòíî + 0..3: Turn(); // Бежим обратно + 4: Jump(); // Прыгаем + 5: begin // Прыгаем обратно Turn(); Jump(); end; end - else // Ýòî íå áåçäíà è ìû åùå íå ëåòèì òóäà + else // Это не бездна и мы еще не летим туда if GetAIFlag('GOINHOLE') = '' then case Random(6) of - 0: Turn(); // Íå íóæíî òóäà - 1: Jump(); // Âäðóã ïîâåçåò - ïðûãàåì - else // Åñëè ÿìà ñ ãðàíèöåé, òî ïðè ñëó÷àå ìîæíî òóäà ïðûãíóòü + 0: Turn(); // Не нужно туда + 1: Jump(); // Вдруг повезет - прыгаем + else // Если яма с границей, то при случае можно туда прыгнуть if BorderHole() then SetAIFlag('GOINHOLE', '1'); end; -// Åñëè íà çåìëå, íî íåêóäà èäòè: +// Если на земле, но некуда идти: if (not CanRun()) and OnGround() then begin - // Åñëè ìû íà ëåñòíèöå èëè ìîæíî ïåðåïðûãíóòü, òî ïðûãàåì: + // Если мы на лестнице или можно перепрыгнуть, то прыгаем: if CanJumpOver() or OnLadder() then Jump() - else // èíà÷å ïîïûòàåìñÿ â äðóãóþ ñòîðîíó + else // иначе попытаемся в другую сторону if Random(2) = 0 then begin if IsSafeTrigger() then @@ -7256,11 +6939,11 @@ begin Turn(); end; -// Îñòàëîñü ìàëî âîçäóõà: +// Осталось мало воздуха: if FAir < 36 * 2 then Jump(20); -// Âûáèðàåìñÿ èç êèñëîòû, åñëè íåò êîñòþìà, îáîæãëèñü, èëè ìàëî çäîðîâüÿ: +// Выбираемся из кислоты, если нет костюма, обожглись, или мало здоровья: if (FMegaRulez[MR_SUIT] < gTime) and ((FLastHit = HIT_ACID) or (Healthy() <= 1)) then if BodyInAcid(0, 0) then Jump(); @@ -7301,7 +6984,7 @@ begin if Dist = -1 then Dist := BOT_LONGDIST; if Dist > BOT_LONGDIST then - begin // Äàëüíèé áîé + begin // Дальний бой for a := 0 to 9 do if FWeapon[FDifficult.WeaponPrior[a]] and HaveAmmo(FDifficult.WeaponPrior[a]) then begin @@ -7310,7 +6993,7 @@ begin end; end else //if Dist > BOT_UNSAFEDIST then - begin // Áëèæíèé áîé + begin // Ближний бой for a := 0 to 9 do if FWeapon[FDifficult.CloseWeaponPrior[a]] and HaveAmmo(FDifficult.CloseWeaponPrior[a]) then begin @@ -7369,7 +7052,7 @@ begin ok := False; if (g_GetUIDType(FLastSpawnerUID) = UID_PLAYER) and LongBool(gGameSettings.Options and GAME_OPTION_BOTVSPLAYER) then - begin // Èãðîê + begin // Игрок pla := g_Player_Get(FLastSpawnerUID); ok := not TargetOnScreen(pla.FObj.X + PLAYER_RECT.X, pla.FObj.Y + PLAYER_RECT.Y); @@ -7377,7 +7060,7 @@ begin else if (g_GetUIDType(FLastSpawnerUID) = UID_MONSTER) and LongBool(gGameSettings.Options and GAME_OPTION_BOTVSMONSTER) then - begin // Ìîíñòð + begin // Монстр mon := g_Monsters_ByUID(FLastSpawnerUID); ok := not TargetOnScreen(mon.Obj.X + mon.Obj.Rect.X, mon.Obj.Y + mon.Obj.Rect.Y); @@ -7395,7 +7078,7 @@ function TBot.RunDirection(): TDirection; begin if Abs(Vel.X) >= 1 then begin - if Vel.X > 0 then Result := D_RIGHT else Result := D_LEFT; + if Vel.X > 0 then Result := TDirection.D_RIGHT else Result := TDirection.D_LEFT; end else Result := FDirection; end; @@ -7412,64 +7095,89 @@ begin Result := Round((255-a)/255*radius*(Random(2)-1)); end; -procedure TBot.SaveState(var Mem: TBinMemoryWriter); + +procedure TDifficult.save (st: TStream); +begin + utils.writeInt(st, Byte(DiagFire)); + utils.writeInt(st, Byte(InvisFire)); + utils.writeInt(st, Byte(DiagPrecision)); + utils.writeInt(st, Byte(FlyPrecision)); + utils.writeInt(st, Byte(Cover)); + utils.writeInt(st, Byte(CloseJump)); + st.WriteBuffer(WeaponPrior[Low(WeaponPrior)], sizeof(WeaponPrior)); + st.WriteBuffer(CloseWeaponPrior[Low(CloseWeaponPrior)], sizeof(CloseWeaponPrior)); +end; + +procedure TDifficult.load (st: TStream); +begin + DiagFire := utils.readByte(st); + InvisFire := utils.readByte(st); + DiagPrecision := utils.readByte(st); + FlyPrecision := utils.readByte(st); + Cover := utils.readByte(st); + CloseJump := utils.readByte(st); + st.ReadBuffer(WeaponPrior[Low(WeaponPrior)], sizeof(WeaponPrior)); + st.ReadBuffer(CloseWeaponPrior[Low(CloseWeaponPrior)], sizeof(CloseWeaponPrior)); +end; + + +procedure TBot.SaveState (st: TStream); var i: Integer; - dw: DWORD; - p: Pointer; -begin - inherited SaveState(Mem); - -// Âûáðàííîå îðóæèå: - Mem.WriteByte(FSelectedWeapon); -// UID öåëè: - Mem.WriteWord(FTargetUID); -// Âðåìÿ ïîòåðè öåëè: - Mem.WriteDWORD(FLastVisible); -// Êîëè÷åñòâî ôëàãîâ ÈÈ: + dw: Integer; +begin + inherited SaveState(st); + utils.writeSign(st, 'BOT0'); + // Выбранное оружие + utils.writeInt(st, Byte(FSelectedWeapon)); + // UID цели + utils.writeInt(st, Word(FTargetUID)); + // Время потери цели + utils.writeInt(st, LongWord(FLastVisible)); + // Количество флагов ИИ dw := Length(FAIFlags); - Mem.WriteDWORD(dw); -// Ôëàãè ÈÈ: - for i := 0 to Integer(dw)-1 do + utils.writeInt(st, LongInt(dw)); + // Флаги ИИ + for i := 0 to dw-1 do begin - Mem.WriteString(FAIFlags[i].Name, 20); - Mem.WriteString(FAIFlags[i].Value, 20); + utils.writeStr(st, FAIFlags[i].Name, 20); + utils.writeStr(st, FAIFlags[i].Value, 20); end; -// Íàñòðîéêè ñëîæíîñòè: - p := @FDifficult; - Mem.WriteMemory(p, SizeOf(TDifficult)); + // Настройки сложности + FDifficult.save(st); end; -procedure TBot.LoadState(var Mem: TBinMemoryReader); + +procedure TBot.LoadState (st: TStream); var i: Integer; - dw: DWORD; - p: Pointer; -begin - inherited LoadState(Mem); - -// Âûáðàííîå îðóæèå: - Mem.ReadByte(FSelectedWeapon); -// UID öåëè: - Mem.ReadWord(FTargetUID); -// Âðåìÿ ïîòåðè öåëè: - Mem.ReadDWORD(FLastVisible); -// Êîëè÷åñòâî ôëàãîâ ÈÈ: - Mem.ReadDWORD(dw); + dw: Integer; +begin + inherited LoadState(st); + if not utils.checkSign(st, 'BOT0') then raise XStreamError.Create('invalid bot signature'); + // Выбранное оружие + FSelectedWeapon := utils.readByte(st); + // UID цели + FTargetUID := utils.readWord(st); + // Время потери цели + FLastVisible := utils.readLongWord(st); + // Количество флагов ИИ + dw := utils.readLongInt(st); + if (dw < 0) or (dw > 16384) then raise XStreamError.Create('invalid number of bot AI flags'); SetLength(FAIFlags, dw); -// Ôëàãè ÈÈ: - for i := 0 to Integer(dw)-1 do + // Флаги ИИ + for i := 0 to dw-1 do begin - Mem.ReadString(FAIFlags[i].Name); - Mem.ReadString(FAIFlags[i].Value); + FAIFlags[i].Name := utils.readStr(st, 20); + FAIFlags[i].Value := utils.readStr(st, 20); end; -// Íàñòðîéêè ñëîæíîñòè: - Mem.ReadMemory(p, dw); - if dw <> SizeOf(TDifficult) then - begin - raise EBinSizeError.Create('TBot.LoadState: Wrong FDifficult Size'); - end; - FDifficult := TDifficult(p^); + // Настройки сложности + FDifficult.load(st); end; + +begin + conRegVar('cheat_berserk_autoswitch', @gBerserkAutoswitch, 'autoswitch to fist when berserk pack taken', '', true, true); + 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'); + conRegVar('player_indicator_style', @gPlayerIndicatorStyle, 'Visual appearance of indicator', 'Visual appearance of indicator'); end.