X-Git-Url: http://deadsoftware.ru/gitweb?a=blobdiff_plain;f=src%2Fgame%2Fg_player.pas;h=92afc636f73aa29a5a0922fbc8493cb8b8b7bf28;hb=abda6900c041e39944de6a49aa088a60c170715e;hp=9605c6de6e1a3c9c97b6b14165941b866023fb87;hpb=8bc91b0c803ac0934ca9e32ca05d9294d7b2a913;p=d2df-sdl.git diff --git a/src/game/g_player.pas b/src/game/g_player.pas index 9605c6d..92afc63 100644 --- a/src/game/g_player.pas +++ b/src/game/g_player.pas @@ -2,8 +2,7 @@ * * 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 @@ -22,7 +21,7 @@ interface uses SysUtils, Classes, {$IFDEF USE_MEMPOOL}mempool,{$ENDIF} - e_graphics, g_playermodel, g_basic, g_textures, + g_base, g_playermodel, g_basic, g_textures, g_weapons, g_phys, g_sound, g_saveload, MAPDEF, g_panel; @@ -97,11 +96,25 @@ const 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; @@ -112,6 +125,7 @@ type Kills: Word; Color: TRGB; Spectator: Boolean; + UID: Word; end; TPlayerStatArray = Array of TPlayerStat; @@ -128,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 @@ -174,17 +188,19 @@ type FFirePainTime: Integer; FFireAttacker: Word; - FSavedState: TPlayerSavedState; + FSavedStateNum: Integer; FModel: TPlayerModel; - FPunchAnim: TAnimation; + 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; @@ -214,7 +230,6 @@ type {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; @@ -235,7 +250,7 @@ type procedure doDamage (v: Integer); - function followCorpse(): Boolean; + function refreshCorpse(): Boolean; public FDamageBuffer: Integer; @@ -262,8 +277,13 @@ type 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; @@ -278,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; @@ -310,14 +331,7 @@ type procedure Spectate(NoMove: Boolean = False); procedure SwitchNoClip; procedure SoftReset(); - procedure Draw(); virtual; - procedure DrawPain(); - procedure DrawPickup(); - procedure DrawRulez(); - procedure DrawAim(); - procedure DrawIndicator(); - procedure DrawBubble(); - procedure DrawGUI(); + procedure PreUpdate(); procedure Update(); virtual; procedure RememberState(); procedure RecallState(); @@ -333,7 +347,7 @@ type 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 entity position was changed, or coldet will not work right! @@ -341,6 +355,10 @@ type 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; @@ -372,10 +390,23 @@ type property GameAccelX: Integer read FObj.Accel.X write FObj.Accel.X; property GameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y; 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; @@ -463,7 +494,6 @@ 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; @@ -474,12 +504,13 @@ type PGib = ^TGib; TGib = record alive: Boolean; - ID: DWORD; - MaskID: DWORD; RAngle: Integer; Color: TRGB; Obj: TObj; + ModelID: Integer; + GibID: Integer; + procedure getMapBox (out x, y, w, h: Integer); inline; procedure moveBy (dx, dy: Integer); inline; @@ -489,12 +520,10 @@ type PShell = ^TShell; TShell = record - SpriteID: DWORD; - alive: Boolean; + alive: Boolean; SType: Byte; RAngle: Integer; Timeout: Cardinal; - CX, CY: Integer; Obj: TObj; procedure getMapBox (out x, y, w, h: Integer); inline; @@ -505,22 +534,18 @@ type TCorpse = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF} private - FModelName: String; FMess: Boolean; FState: Byte; FDamage: Byte; - FColor: TRGB; FObj: TObj; FPlayerUID: Word; - FAnimation: TAnimation; - FAnimationMask: TAnimation; + 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 (st: TStream); procedure LoadState (st: TStream); @@ -534,6 +559,7 @@ type 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 @@ -549,11 +575,12 @@ var gTeamStat: TTeamStat; gFly: Boolean = False; gAimLine: Boolean = False; - gChatBubble: Byte = 0; - gPlayerIndicator: Boolean = True; + 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; @@ -573,41 +600,37 @@ function g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boole 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 (st: TStream); procedure g_Player_Corpses_LoadState (st: TStream); -procedure g_Bot_Add(Team, Difficult: Byte); -procedure g_Bot_AddList(Team: Byte; lname: ShortString; num: Integer = -1); +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 -{$INCLUDE ../nogl/noGLuses.inc} {$IFDEF ENABLE_HOLMES} g_holmes, {$ENDIF} - e_log, g_map, g_items, g_console, g_gfx, Math, - g_options, g_triggers, g_menu, g_game, g_grid, - wadreader, g_main, g_monsters, CONFIG, g_language, - g_net, g_netmsg, g_window, + 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; @@ -633,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 TDFPoint = ((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)); @@ -694,6 +707,7 @@ var CurrentShell: Integer = 0; BotNames: Array of String; BotList: Array of TBotProfile; + SavedStates: Array of TPlayerSavedState; function Lerp(X, Y, Factor: Integer): Integer; @@ -766,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 @@ -775,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 @@ -792,7 +806,7 @@ begin gPlayers[a].FActualModelName := ModelName; gPlayers[a].SetModel(ModelName); -// Íåò ìîäåëè - ñîçäàíèå íå âîçìîæíî: +// Нет модели - создание не возможно: if gPlayers[a].FModel = nil then begin gPlayers[a].Free(); @@ -816,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] @@ -838,27 +852,27 @@ begin result := 0; if (st = nil) then exit; //??? - // Ñèãíàòóðà èãðîêà + // Сигнатура игрока 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'); - // Áîò èëè ÷åëîâåê: + // Бот или человек: Bot := utils.readBool(st); ok := false; a := 0; - // Åñòü ëè ìåñòî â gPlayers: + // Есть ли место в 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 @@ -866,97 +880,99 @@ begin gPlayers[a].FIamBot := Bot; gPlayers[a].FPhysics := True; - // UID èãðîêà + // 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; - // Æèâ ëè + // Жив ли 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 + // Время зарядки 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 begin gPlayers[a].Free(); @@ -965,7 +981,7 @@ begin exit; end; - // Åñëè êîìàíäíàÿ èãðà - êðàñèì ìîäåëü â öâåò êîìàíäû + // Если командная игра - красим модель в цвет команды if gGameSettings.GameMode in [GM_TDM, GM_CTF] then gPlayers[a].FModel.Color := TEAMCOLOR[gPlayers[a].FTeam] else @@ -1003,7 +1019,7 @@ begin end; end; -procedure g_Bot_Add(Team, Difficult: Byte); +procedure g_Bot_Add(Team, Difficult: Byte; Handicap: Integer = 100); var m: SSArray; _name, _model: String; @@ -1011,12 +1027,12 @@ var 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 @@ -1025,7 +1041,7 @@ begin else if Team = TEAM_NONE then // CTF / TDM begin - // Àâòîáàëàíñ êîìàíä: + // Автобаланс команд: tr := 0; tb := 0; @@ -1051,7 +1067,7 @@ begin Team := TEAM_BLUE; end; -// Âûáèðàåì áîòó èìÿ: +// Выбираем боту имя: _name := ''; if BotNames <> nil then for a := 0 to High(BotNames) do @@ -1061,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; @@ -1092,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); @@ -1100,7 +1116,7 @@ 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: SSArray; _name, _model: String; @@ -1108,12 +1124,12 @@ var 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 @@ -1123,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; @@ -1137,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; @@ -1163,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]; @@ -1240,14 +1258,16 @@ var a, b: Integer; config: TConfig; 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 @@ -1264,11 +1284,11 @@ begin CloseFile(F); -// Ïåðåìåøèâàåì èõ: +// Перемешиваем их: g_Bot_MixNames(); -// ×èòàåì ôàéë ñ ïàðàìåòðàìè áîòîâ: - config := TConfig.CreateFile(DataDir + BOTLIST_FILENAME); +// Читаем файл с параметрами ботов: + config := TConfig.CreateFile(path); BotList := nil; a := 0; @@ -1278,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 @@ -1325,6 +1345,7 @@ begin end; config.Free(); + SetLength(SavedStates, 0); end; procedure g_Player_Free(); @@ -1348,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(); @@ -1376,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; @@ -1471,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; @@ -1481,10 +1465,27 @@ 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; @@ -1519,21 +1520,24 @@ 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 + Result := -1; + if Player.alive then Exit; -// Ðàçðûâàåì ñâÿçü ñ ïðåæíèì òðóïîì: - if gCorpses <> nil then - for i := 0 to High(gCorpses) do - if gCorpses[i] <> nil then - if gCorpses[i].FPlayerUID = Player.FUID then - gCorpses[i].FPlayerUID := 0; +// Разрываем связь с прежним трупом: + 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; @@ -1556,47 +1560,36 @@ 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; @@ -1616,29 +1609,31 @@ 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 := g_PlayerModel_GetBlood(ModelName); + 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; 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); @@ -1672,12 +1667,15 @@ var end; begin -// Êóñêè ìÿñà: +// Куски мяса: if gGibs <> nil then for i := 0 to High(gGibs) do 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 @@ -1688,7 +1686,7 @@ begin 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 @@ -1705,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 @@ -1722,12 +1720,15 @@ begin else gCorpses[i].Update(); -// Ãèëüçû: +// Гильзы: if gShells <> nil then for i := 0 to High(gShells) do 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 @@ -1738,7 +1739,7 @@ begin Continue; end; - // Îòëåòàåò îò óäàðà î ñòåíó/ïîòîëîê/ïîë: + // Отлетает от удара о стену/потолок/пол: if WordBool(mr and MOVE_HITWALL) then begin Obj.Vel.X := -(vel.X div 2); @@ -1814,57 +1815,6 @@ procedure TGib.positionChanged (); inline; begin end; procedure TShell.positionChanged (); inline; begin end; -procedure g_Player_DrawCorpses(); -var - i: Integer; - a: TDFPoint; -begin - if gGibs <> nil then - for i := 0 to High(gGibs) do - if gGibs[i].alive 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); - - e_DrawAdv(ID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, TMirrorType.None); - - e_Colors := Color; - e_DrawAdv(MaskID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, TMirrorType.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(); -end; - -procedure g_Player_DrawShells(); -var - i: Integer; - a: TDFPoint; -begin - if gShells <> nil then - for i := 0 to High(gShells) do - if gShells[i].alive then - with gShells[i] do - begin - if not g_Obj_Collide(sX, sY, sWidth, sHeight, @Obj) then - Continue; - - a.X := CX; - a.Y := CY; - - e_DrawAdv(SpriteID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, TMirrorType.None); - end; -end; - procedure g_Player_RemoveAllCorpses(); var i: Integer; @@ -1888,25 +1838,25 @@ procedure g_Player_Corpses_SaveState (st: TStream); var count, i: Integer; begin - // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ òðóïîâ + // Считаем количество существующих трупов count := 0; for i := 0 to High(gCorpses) do if (gCorpses[i] <> nil) then Inc(count); - // Êîëè÷åñòâî òðóïîâ + // Количество трупов utils.writeInt(st, LongInt(count)); if (count = 0) then exit; - // Ñîõðàíÿåì òðóïû + // Сохраняем трупы for i := 0 to High(gCorpses) do begin if gCorpses[i] <> nil then begin - // Íàçâàíèå ìîäåëè - utils.writeStr(st, gCorpses[i].FModelName); - // Òèï ñìåðòè + // Название модели + utils.writeStr(st, gCorpses[i].FModel.GetName()); + // Тип смерти utils.writeBool(st, gCorpses[i].Mess); - // Ñîõðàíÿåì äàííûå òðóïà: + // Сохраняем данные трупа: gCorpses[i].SaveState(st); end; end; @@ -1923,22 +1873,22 @@ begin g_Player_RemoveAllCorpses(); - // Êîëè÷åñòâî òðóïîâ: + // Количество трупов: count := utils.readLongInt(st); if (count < 0) or (count > Length(gCorpses)) then raise XStreamError.Create('invalid number of corpses'); if (count = 0) then exit; - // Çàãðóæàåì òðóïû + // Загружаем трупы for i := 0 to count-1 do begin - // Íàçâàíèå ìîäåëè: + // Название модели: str := utils.readStr(st); - // Òèï ñìåðòè + // Тип смерти b := utils.readBool(st); - // Ñîçäàåì òðóï + // Создаем труп gCorpses[i] := TCorpse.Create(0, 0, str, b); - // Çàãðóæàåì äàííûå òðóïà + // Загружаем данные трупа gCorpses[i].LoadState(st); end; end; @@ -2006,6 +1956,11 @@ 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 @@ -2072,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 @@ -2127,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'; @@ -2142,6 +2099,10 @@ begin FJustTeleported := False; FNetTime := 0; + FWaitForFirstSpawn := false; + FPunchAnim := TAnimationState.Create(False, 1, 4); + FPunchAnim.Disable; + resetWeaponQueue(); end; @@ -2165,7 +2126,7 @@ begin FLastHit := t; -// Íåóÿçâèìîñòü íå ñïàñàåò îò ëîâóøåê: +// Неуязвимость не спасает от ловушек: if ((t = HIT_TRAP) or (t = HIT_SELF)) and (not FGodMode) then begin if not g_Game_IsClient then @@ -2173,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); @@ -2228,11 +2190,11 @@ begin FObj.Y+PLAYER_RECT.Y-4, value div 2, 8, 4); end; - // Áóôåð óðîíà: + // Буфер урона: if FAlive then Inc(FDamageBuffer, value); - // Âñïûøêà áîëè: + // Вспышка боли: if gFlash <> 0 then FPain := FPain + value; end; @@ -2286,595 +2248,15 @@ begin FJetSoundOn.Free(); FJetSoundOff.Free(); FModel.Free(); - if FPunchAnim <> nil then - FPunchAnim.Free(); + FPunchAnim.Free(); inherited; end; -procedure TPlayer.DrawIndicator(); -var - indX, indY: Integer; - indW, indH: Word; - ID: DWORD; -begin - if FAlive then - begin - indX := FObj.X+FObj.Rect.X; - indY := FObj.Y; - if g_Texture_Get('TEXTURE_PLAYER_INDICATOR', ID) then - begin - e_GetTextureSize(ID, @indW, @indH); - e_Draw(ID, indX + indW div 2, indY - indH, 0, True, False); - end; - end; - //e_TextureFontPrint(indX, indY, FName, gStdFont); // Shows player name overhead -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 = TDirection.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 = TDirection.D_RIGHT then - e_Draw(ID, bubX - 6, bubY - 7, 0, True, False) - else - e_Draw(ID, bubX - 6, bubY - 7, 0, True, False, TMirrorType.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 = TDirection.D_LEFT, 14, 5); - e_DrawLine(1, bubX + Dot, bubY + 14, bubX + Dot, bubY + 16, Rb, Gb, Bb); - e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 1, Dot + 1), bubY + 13, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 1, Dot + 1), bubY + 15, Rw, Gw, Bw); - e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 2, Dot + 2), bubY + 13, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 2, Dot + 2), bubY + 14, Rw, Gw, Bw); - e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 3, Dot + 3), bubY + 13, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 3, Dot + 3), bubY + 13, Rw, Gw, Bw); - e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 3, Dot + 3), bubY + 14, bubX + IfThen(FDirection = TDirection.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; - Mirror: TMirrorType; -begin - if FAlive then - begin - if Direction = TDirection.D_RIGHT then - Mirror := TMirrorType.None - else - Mirror := TMirrorType.Horizontal; - - if FPunchAnim <> nil then - begin - FPunchAnim.Draw(FObj.X+IfThen(Direction = TDirection.D_LEFT, 15-FObj.Rect.X, FObj.Rect.X-15), - FObj.Y+FObj.Rect.Y-11, Mirror); - if FPunchAnim.played then - begin - FPunchAnim.Free; - FPunchAnim := nil; - end; - end; - - 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 = TDirection.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+FObj.slopeUpLeft, 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+FObj.slopeUpLeft, 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+FObj.slopeUpLeft, 200) - else - FModel.Draw(FObj.X, FObj.Y+FObj.slopeUpLeft); - end - else - FModel.Draw(FObj.X, FObj.Y+FObj.slopeUpLeft, 254); - end - else - FModel.Draw(FObj.X, FObj.Y+FObj.slopeUpLeft); - 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 - if (FMegaRulez[MR_INVIS] <= gTime) or ((gPlayerDrawn <> nil) and ((Self = gPlayerDrawn) or - ((FTeam = gPlayerDrawn.Team) and (gGameSettings.GameMode <> GM_DM)))) then - DrawBubble(); - // e_DrawPoint(5, 335, 288, 255, 0, 0); // DL, UR, DL, UR - if gAimLine and alive and - ((Self = gPlayer1) or (Self = gPlayer2)) then - DrawAim(); -end; - - -procedure TPlayer.DrawAim(); - procedure drawCast (sz: Integer; ax0, ay0, ax1, ay1: Integer); - var - ex, ey: Integer; - begin - -{$IFDEF ENABLE_HOLMES} - if isValidViewPort and (self = gPlayer1) then - begin - g_Holmes_plrLaser(ax0, ay0, ax1, ay1); - end; -{$ENDIF} - - e_DrawLine(sz, ax0, ay0, ax1, ay1, 255, 0, 0, 96); - if (g_Map_traceToNearestWall(ax0, ay0, ax1, ay1, @ex, @ey) <> nil) then - begin - e_DrawLine(sz, ax0, ay0, ex, ey, 0, 255, 0, 96); - end - else - begin - e_DrawLine(sz, ax0, ay0, ex, ey, 0, 0, 255, 96); - end; - end; - -var - wx, wy, xx, yy: Integer; - angle: SmallInt; - sz, len: Word; -begin - wx := FObj.X + WEAPONPOINT[FDirection].X + IfThen(FDirection = TDirection.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; - {$IF DEFINED(D2F_DEBUG)} - drawCast(sz, wx, wy, xx, yy); - {$ELSE} - e_DrawLine(sz, wx, wy, xx, yy, 255, 0, 0, 96); - {$ENDIF} -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, TBlending.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, TBlending.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, TBlending.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); -end; - procedure TPlayer.DoPunch(); -var - id: DWORD; - st: String; begin - if FPunchAnim <> nil then begin - FPunchAnim.reset(); - FPunchAnim.Free; - FPunchAnim := nil; - end; - st := 'FRAMES_PUNCH'; - if R_BERSERK in FRulez then - st := st + '_BERSERK'; - if FKeys[KEY_UP].Pressed then - st := st + '_UP' - else if FKeys[KEY_DOWN].Pressed then - st := st + '_DN'; - g_Frames_Get(id, st); - FPunchAnim := TAnimation.Create(id, False, 1); + FPunchAnim.Reset; + FPunchAnim.Enable; end; procedure TPlayer.Fire(); @@ -2884,8 +2266,8 @@ var locobj: TObj; begin if g_Game_IsClient then Exit; -// FBFGFireCounter - âðåìÿ ïåðåä âûñòðåëîì (äëÿ BFG) -// FReloading - âðåìÿ ïîñëå âûñòðåëà (äëÿ âñåãî) +// FBFGFireCounter - время перед выстрелом (для BFG) +// FReloading - время после выстрела (для всего) if FSpectator then begin @@ -3161,11 +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 + 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 := 100; + FFireTime := Timeout; FFireAttacker := Attacker; if g_Game_IsNet and g_Game_IsServer then MH_SEND_PlayerStats(FUID); @@ -3175,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 @@ -3193,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; @@ -3285,7 +2673,7 @@ begin if FLives = 0 then FNoRespawn := True; end; -// Íîìåð òèïà ñìåðòè: +// Номер типа смерти: a := 1; case KillType of K_SIMPLEKILL: a := 1; @@ -3294,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: @@ -3311,7 +2699,7 @@ begin FTime[T_RESPAWN] := gTime + TIME_RESPAWN3; end; -// Ïåðåêëþ÷àåì ñîñòîÿíèå: +// Переключаем состояние: case KillType of K_SIMPLEKILL: SetAction(A_DIE1); @@ -3319,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); @@ -3334,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 @@ -3378,7 +2766,7 @@ begin end; end else if g_GetUIDType(SpawnerUID) = UID_MONSTER then - begin // Óáèò ìîíñòðîì + begin // Убит монстром mon := g_Monsters_ByUID(SpawnerUID); if mon = nil then s := '?' @@ -3400,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); @@ -3413,7 +2801,7 @@ begin if Srv then begin -// Âûáðîñ îðóæèÿ: +// Выброс оружия: for a := WP_FIRST to WP_LAST do if FWeapon[a] then begin @@ -3434,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); @@ -3455,11 +2844,11 @@ begin PushItem(ITEM_KEY_BLUE); end; -// Âûáðîñ ôëàãà: +// Выброс флага: 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 @@ -3563,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; @@ -3593,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), - FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind); + 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, - FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind); + 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, - FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind); + Blood.R, Blood.G, Blood.B, Blood.Kind); end; procedure TPlayer.QueueWeaponSwitch(Weapon: Byte); @@ -3630,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); @@ -3717,7 +3110,7 @@ begin // i found her! result := Byte(i); resetWeaponQueue(); - FNextWeapDelay := WEAPON_DELAY; // anyway, 'cause why not + FNextWeapDelay := WEAPON_DELAY * 2; // anyway, 'cause why not exit; end; end; @@ -3815,15 +3208,15 @@ 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; @@ -3831,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; @@ -3859,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; @@ -3869,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; @@ -3893,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]); @@ -4074,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]; @@ -4090,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; @@ -4167,9 +3562,9 @@ begin 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; @@ -4181,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; @@ -4232,7 +3628,7 @@ begin //FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y); if FIamBot then begin - // Áðîñèòü ôëàã òîâàðèùó: + // Бросить флаг товарищу: if gGameSettings.GameMode = GM_CTF then DropFlag(); end; @@ -4266,6 +3662,9 @@ begin FMonsterKills := 0; FDeath := 0; FSecrets := 0; + FSpawnInvul := 0; + FCorpse := -1; + FReady := False; if FNoRespawn then begin FSpectator := False; @@ -4284,6 +3683,8 @@ begin ReleaseKeys(); FDamageBuffer := 0; + FSlopeOld := 0; + FIncCamOld := 0; FIncCam := 0; FBFGFireCounter := -1; FShellTimer := -1; @@ -4301,162 +3702,79 @@ var c: Byte; begin Result := 255; - // Íà áóäóùåå: FSpawn - èãðîê óæå èãðàë è ïåðåðîæäàåòñÿ + // На будущее: FSpawn - игрок уже играл и перерождается - // Îäèíî÷íàÿ èãðà/êîîïåðàòèâ + // Одиночная игра/кооператив if gGameSettings.GameMode in [GM_COOP, GM_SINGLE] then begin - if (Self = gPlayer1) or (Self = gPlayer2) then + if Self = gPlayer1 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 + // 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 - // Òî÷êà ïîÿâëåíèÿ ëþáîãî èãðîêà (áîòà) - 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 + // 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; - end; - - // Òî÷êà DM - c := RESPAWNPOINT_DM; - 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 = GM_DM then begin - // Òî÷êà 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; - - // Òî÷êà ëþáîé èç êîìàíä - if Random(2) = 0 then - c := RESPAWNPOINT_RED - else - c := RESPAWNPOINT_BLUE; - 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 - // Òî÷êà ñâîåé êîìàíäû - 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 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; @@ -4473,11 +3791,11 @@ begin // 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); @@ -4486,26 +3804,26 @@ 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 FAlive then begin - FHealth := PLAYER_HP_SOFT; + FHealth := Round(PLAYER_HP_SOFT * (FHandicap / 100)); FArmor := 0; FAlive := True; FAir := AIR_DEF; @@ -4535,22 +3853,25 @@ 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; @@ -4571,6 +3892,13 @@ 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; @@ -4579,15 +3907,13 @@ begin 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; @@ -4595,9 +3921,9 @@ begin FSpectatePlayer := -1; FSpawned := True; - if (gPlayer1 = nil) and (gLMSPID1 = FUID) then + if (gPlayer1 = nil) and (gSpectLatchPID1 = FUID) then gPlayer1 := self; - if (gPlayer2 = nil) and (gLMSPID2 = FUID) then + if (gPlayer2 = nil) and (gSpectLatchPID2 = FUID) then gPlayer2 := self; if g_Game_IsNet then @@ -4629,17 +3955,18 @@ begin 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; @@ -4672,7 +3999,7 @@ begin if MAX_RUNVEL > 8 then FlySmoke(); -// Áåæèì: +// Бежим: if Direction = TDirection.D_LEFT then begin if FObj.Vel.X > -MAX_RUNVEL then @@ -4682,7 +4009,7 @@ 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); @@ -4693,14 +4020,14 @@ begin 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; @@ -4766,9 +4093,6 @@ begin end; function TPlayer.TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean; -var - Anim: TAnimation; - ID: DWORD; begin Result := False; @@ -4782,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, @@ -4801,6 +4122,8 @@ begin FObj.X := X-PLAYER_RECT.X; FObj.Y := Y-PLAYER_RECT.Y; + FObj.oldX := FObj.X; // don't interpolate after respawn + FObj.oldY := FObj.Y; if FAlive and FGhost then begin FXTo := FObj.X; @@ -4822,7 +4145,7 @@ begin end else if dir = 3 then - begin // îáðàòíîå + begin // обратное if FDirection = TDirection.D_RIGHT then begin SetDirection(TDirection.D_LEFT); @@ -4836,12 +4159,13 @@ begin 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, @@ -4859,11 +4183,12 @@ begin Result := 1; end; -function TPlayer.followCorpse(): Boolean; +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 @@ -4873,16 +4198,34 @@ begin if gCorpses[i].FPlayerUID = FUID then begin Result := True; - FObj.X := gCorpses[i].FObj.X; - FObj.Y := gCorpses[i].FObj.Y; - FObj.Vel.X := gCorpses[i].FObj.Vel.X; - FObj.Vel.Y := gCorpses[i].FObj.Vel.Y; - FObj.Accel.X := gCorpses[i].FObj.Accel.X; - FObj.Accel.Y := gCorpses[i].FObj.Accel.Y; + 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; @@ -4915,8 +4258,10 @@ begin FLoss := 0; end; - if FAlive and (FPunchAnim <> nil) then - FPunchAnim.Update(); + if FAlive then + FPunchAnim.Update; + if FPunchAnim.played then + FPunchAnim.Disable; if FAlive and (gFly or FJetpack) then FlySmoke(); @@ -4959,8 +4304,7 @@ begin if FPhysics then begin - if not followCorpse() then - g_Obj_Move(@FObj, True, True, True); + g_Obj_Move(@FObj, True, True, True); positionChanged(); // this updates spatial accelerators end; @@ -5094,8 +4438,7 @@ begin if FPhysics then begin - if not followCorpse() then - g_Obj_Move(@FObj, True, True, True); + g_Obj_Move(@FObj, True, True, True); positionChanged(); // this updates spatial accelerators end else @@ -5117,7 +4460,7 @@ begin PANEL_BLOCKMON, True); headwater := HeadInLiquid(0, 0); -// Ñîïðîòèâëåíèå âîçäóõà: +// Сопротивление воздуха: 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); @@ -5128,7 +4471,7 @@ begin 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; @@ -5245,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; @@ -5281,7 +4626,7 @@ begin else if FHealth > -50 then Kill(K_HARDKILL, FLastSpawnerUID, FLastHit) else Kill(K_EXTRAHARDKILL, FLastSpawnerUID, FLastHit); - if FAlive 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) @@ -5298,14 +4643,13 @@ begin 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); @@ -5575,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 @@ -5618,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 @@ -5661,7 +5009,7 @@ begin Exit; end; -// Ïîäîáðàë ñâîé ôëàã - âåðíóë åãî íà áàçó: +// Подобрал свой флаг - вернул его на базу: if (Flag = FTeam) and (gFlags[Flag].State = FLAG_STATE_DROPPED) then begin @@ -5697,7 +5045,7 @@ begin Exit; end; -// Ïîäîáðàë ÷óæîé ôëàã: +// Подобрал чужой флаг: if (Flag <> FTeam) and (FTime[T_FLAGCAP] <= gTime) then begin SetFlag(Flag); @@ -5788,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; @@ -5829,45 +5182,66 @@ 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); @@ -5878,93 +5252,95 @@ var i: Integer; b: Byte; begin - // Ñèãíàòóðà èãðîêà + // Сигнатура игрока utils.writeSign(st, 'PLYR'); utils.writeInt(st, Byte(PLR_SAVE_VERSION)); // version - // Áîò èëè ÷åëîâåê + // Бот или человек utils.writeBool(st, FIamBot); - // UID èãðîêà + // 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 + // Время зарядки 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.Name); - // Öâåò ìîäåëè + // Название модели + utils.writeStr(st, FModel.GetName()); + // Цвет модели utils.writeInt(st, Byte(FColor.R)); utils.writeInt(st, Byte(FColor.G)); utils.writeInt(st, Byte(FColor.B)); @@ -5979,94 +5355,96 @@ var begin 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 èãðîêà + // 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 + // Время зарядки 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); @@ -6080,7 +5458,7 @@ begin str := gPlayer2Settings.Model; FColor := gPlayer2Settings.Color; end; - // Îáíîâëÿåì ìîäåëü èãðîêà + // Обновляем модель игрока SetModel(str); if gGameSettings.GameMode in [GM_TDM, GM_CTF] then FModel.Color := TEAMCOLOR[FTeam] @@ -6181,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: @@ -6265,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; @@ -6283,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; @@ -6339,25 +5708,24 @@ 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; @@ -6385,10 +5753,8 @@ begin end; -procedure TCorpse.Damage(Value: Word; vx, vy: Integer); -var - pm: TPlayerModel; - Blood: TModelBlood; +procedure TCorpse.Damage(Value: Word; SpawnerUID: Word; vx, vy: Integer); + var Blood: TModelBlood; begin if FState = CORPSE_STATE_REMOVEME then Exit; @@ -6396,32 +5762,33 @@ 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; - - // Çëîâåùèé ñìåõ: - if (gBodyKillEvent <> -1) - and gDelayedEvents[gBodyKillEvent].Pending then - gDelayedEvents[gBodyKillEvent].Pending := False; - gBodyKillEvent := g_Game_DelayEvent(DE_BODYKILL, 1050, 0); - 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 := g_PlayerModel_GetBlood(FModelName); + 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), @@ -6431,24 +5798,6 @@ begin end; end; -procedure TCorpse.Draw(); -begin - if FState = CORPSE_STATE_REMOVEME then - Exit; - - if FAnimation <> nil then - FAnimation.Draw(FObj.X, FObj.Y, TMirrorType.None); - - if FAnimationMask <> nil then - begin - e_Colors := FColor; - FAnimationMask.Draw(FObj.X, FObj.Y, TMirrorType.None); - e_Colors.R := 255; - e_Colors.G := 255; - e_Colors.B := 255; - end; -end; - procedure TCorpse.Update(); var st: Word; @@ -6456,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); @@ -6463,7 +5815,7 @@ begin Exit; end; -// Ñîïðîòèâëåíèå âîçäóõà äëÿ òðóïà: +// Сопротивление воздуха для трупа: FObj.Vel.X := z_dec(FObj.Vel.X, 1); st := g_Obj_Move(@FObj, True, True, True); @@ -6475,82 +5827,78 @@ begin Exit; end; - if FAnimation <> nil then - FAnimation.Update(); - if FAnimationMask <> nil then - FAnimationMask.Update(); + if FModel <> nil then + FModel.Update; end; procedure TCorpse.SaveState (st: TStream); -var - anim: Boolean; + 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(FColor.R)); - utils.writeInt(st, Byte(FColor.G)); - utils.writeInt(st, Byte(FColor.B)); - // Îáúåêò òðóïà + // Цвет + 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)); - // Åñòü ëè àíèìàöèÿ - anim := (FAnimation <> nil); + // animation + anim := (FModel <> nil); utils.writeBool(st, anim); - // Åñëè åñòü - ñîõðàíÿåì - if anim then FAnimation.SaveState(st); - // Åñòü ëè ìàñêà àíèìàöèè - anim := (FAnimationMask <> nil); + 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 FAnimationMask.SaveState(st); + if anim then FModel.AnimState.SaveState(st); end; procedure TCorpse.LoadState (st: TStream); -var - anim: Boolean; + 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); - // Öâåò - FColor.R := utils.readByte(st); - FColor.G := utils.readByte(st); - FColor.B := 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(st); - end; - // Åñòü ëè ìàñêà àíèìàöèè - anim := utils.readBool(st); - // Åñëè åñòü - çàãðóæàåì - 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(st); + 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 : } @@ -6583,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); @@ -6618,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; @@ -6657,10 +5997,10 @@ var 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 @@ -6684,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'); @@ -6700,7 +6040,7 @@ begin end; end; -// Êîîðäèíàòû ñòâîëà: +// Координаты ствола: x1 := FObj.X + WEAPONPOINT[FDirection].X; y1 := FObj.Y + WEAPONPOINT[FDirection].Y; @@ -6708,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 @@ -6735,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 @@ -6755,7 +6095,7 @@ begin end; if not ok then - begin // Öåëè íåò - îáíóëÿåì + begin // Цели нет - обнуляем Target.X := 0; Target.Y := 0; Target.cX := 0; @@ -6767,10 +6107,10 @@ 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].alive) and @@ -6786,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 @@ -6808,64 +6148,64 @@ 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 // Åñëè çäîðîâû - äîãîíÿåì + begin // Если здоровы - догоняем if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then SetAIFlag('GORIGHT', '1'); if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then SetAIFlag('GOLEFT', '1'); end else - begin // Åñëè ïîáèòû - óáåãàåì + begin // Если побиты - убегаем if ((RunDirection() = TDirection.D_LEFT) and (Target.X < FObj.X)) then SetAIFlag('GORIGHT', '1'); 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 // Åñëè çäîðîâû - äîãîíÿåì + begin // Если здоровы - догоняем if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then SetAIFlag('GORIGHT', '1'); if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then SetAIFlag('GOLEFT', '1'); end else - begin // Åñëè ïîáèòû - çàáûâàåì î öåëè è óáåãàåì + begin // Если побиты - забываем о цели и убегаем Target.UID := 0; if ((RunDirection() = TDirection.D_LEFT) and (Target.X < FObj.X)) then SetAIFlag('GORIGHT', '1'); @@ -6874,11 +6214,11 @@ begin 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() = TDirection.D_LEFT) and (Target.X > FObj.X)) then @@ -6888,7 +6228,7 @@ begin end; end; - // Âûáèðàåì óãîë ââåðõ: + // Выбираем угол вверх: if FDirection = TDirection.D_LEFT then angle := ANGLE_LEFTUP else @@ -6897,18 +6237,18 @@ 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 = TDirection.D_LEFT then angle := ANGLE_LEFTDOWN else @@ -6917,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 = TDirection.D_LEFT) and (Target.X < FObj.X)) or ((FDirection = TDirection.D_RIGHT) and (Target.X > FObj.X)) then - begin // òî íóæíî ñòðåëÿòü âïåðåä + 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 @@ -6954,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.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.alive) then - Target.UID := 0; // òî çàáûòü öåëü + 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() = TDirection.D_RIGHT then - begin // Èäåì íå â òó ñòîðîíó + 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() = TDirection.D_LEFT then - begin // Èäåì íå â òó ñòîðîíó + 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; @@ -7030,15 +6370,15 @@ 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 = TDirection.D_LEFT then angle := ANGLE_LEFTUP else @@ -7057,7 +6397,7 @@ begin SetAIFlag('NEEDSEEUP', '1'); end; - // Èùåì öåëü ñíèçó è ñòðåëÿåì, åñëè åñòü: + // Ищем цель снизу и стреляем, если есть: if FDirection = TDirection.D_LEFT then angle := ANGLE_LEFTDOWN else @@ -7077,7 +6417,7 @@ begin end; end; - // Åñëè öåëü "ïåðåä íîñîì", òî ñòðåëÿåì: + // Если цель "перед носом", то стреляем: if targets[a].Line and targets[a].Visible and (((FDirection = TDirection.D_LEFT) and (targets[a].X < FObj.X)) or ((FDirection = TDirection.D_RIGHT) and (targets[a].X > FObj.X))) then @@ -7087,20 +6427,20 @@ begin 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); @@ -7121,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 @@ -7282,7 +6622,7 @@ procedure TBot.UpdateMove; var x, sx: Integer; begin - { TODO 5 : Ëåñòíèöû } + { 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 @@ -7300,7 +6640,7 @@ procedure TBot.UpdateMove; var x, sx, xx: Integer; begin - { TODO 5 : Ëåñòíèöû } + { 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 @@ -7473,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); @@ -7485,7 +6825,7 @@ begin Jump(); end; -// Èäåì âëåâî, åñëè íàäî áûëî: +// Идем влево, если надо было: if GetAIFlag('GOLEFT') <> '' then begin RemoveAIFlag('GOLEFT'); @@ -7493,7 +6833,7 @@ begin GoLeft(360); end; -// Èäåì âïðàâî, åñëè íàäî áûëî: +// Идем вправо, если надо было: if GetAIFlag('GORIGHT') <> '' then begin RemoveAIFlag('GORIGHT'); @@ -7501,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); @@ -7524,7 +6864,7 @@ begin RemoveAIFlag('NEEDSEEUP'); end; -// Ñìîòðèì âíèç, åñëè íàäî áûëî: +// Смотрим вниз, если надо было: if GetAIFlag('NEEDSEEDOWN') <> '' then begin ReleaseKey(KEY_UP); @@ -7533,7 +6873,7 @@ begin RemoveAIFlag('NEEDSEEDOWN'); end; -// Åñëè íóæíî áûëî â äûðó è ìû íå íà çåìëå, òî ïîêîðíî ëåòèì: +// Если нужно было в дыру и мы не на земле, то покорно летим: if GetAIFlag('GOINHOLE') <> '' then if not OnGround() then begin @@ -7543,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 @@ -7557,40 +6897,40 @@ begin else GoRight(360); -// Åñëè íà çåìëå è ìîæíî ïîäïðûãíóòü, òî, âîçìîæíî, ïðûãàåì: +// Если на земле и можно подпрыгнуть, то, возможно, прыгаем: if OnGround() 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 @@ -7599,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(); @@ -7644,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 @@ -7653,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 @@ -7712,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); @@ -7720,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); @@ -7788,22 +7128,22 @@ var begin inherited SaveState(st); utils.writeSign(st, 'BOT0'); - // Âûáðàííîå îðóæèå + // Выбранное оружие utils.writeInt(st, Byte(FSelectedWeapon)); - // UID öåëè + // UID цели utils.writeInt(st, Word(FTargetUID)); - // Âðåìÿ ïîòåðè öåëè + // Время потери цели utils.writeInt(st, LongWord(FLastVisible)); - // Êîëè÷åñòâî ôëàãîâ ÈÈ + // Количество флагов ИИ dw := Length(FAIFlags); utils.writeInt(st, LongInt(dw)); - // Ôëàãè ÈÈ + // Флаги ИИ for i := 0 to dw-1 do begin utils.writeStr(st, FAIFlags[i].Name, 20); utils.writeStr(st, FAIFlags[i].Value, 20); end; - // Íàñòðîéêè ñëîæíîñòè + // Настройки сложности FDifficult.save(st); end; @@ -7815,27 +7155,29 @@ var begin inherited LoadState(st); if not utils.checkSign(st, 'BOT0') then raise XStreamError.Create('invalid bot signature'); - // Âûáðàííîå îðóæèå + // Выбранное оружие FSelectedWeapon := utils.readByte(st); - // UID öåëè + // 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 dw-1 do begin FAIFlags[i].Name := utils.readStr(st, 20); FAIFlags[i].Value := utils.readStr(st, 20); end; - // Íàñòðîéêè ñëîæíîñòè + // Настройки сложности 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.