Интерфейс ServerPerks: вкладки «Новости» и «Правила», часы в таблице, клановая надпись и категории магазина
Четыре доработки интерфейса, которые на форуме killingfloor.ru выкладывали scar (2012) и Flame (2013-2014). Все они правят классы ServerPerks, поэтому нужны исходники вашей версии ServerPerks и пересборка пакетов. После правок меняйте имя пакета, чтобы у игроков со старой версией в кэше не было Package version mismatch.
Вкладки «Новости» и «Правила»
В меню по Esc добавляются две вкладки с прокручиваемым текстом. Создайте в пакете ServerPerks класс NewsTab (и такой же RulesTab с массивом RulesLine):
class NewsTab extends MidGamePanel;
var automated GUISectionBackground i_BGSec;
var automated GUIScrollTextBox lb_Text;
var() config array<string> NewsLine;
function InitComponent(GUIController MyController, GUIComponent MyOwner)
{
local int i;
Super.InitComponent(MyController, MyOwner);
lb_Text.OnDraw = OnLabelDraw;
for (i = NewsLine.Length - 1; i >= 0; i--)
lb_Text.AddText(NewsLine[i]);
lb_Text.AddText(" ");
lb_Text.MyScrollBar.GripPos = 0;
lb_Text.MyScrollBar.CurPos = 0;
lb_Text.MyScrollBar.MyList.SetTopItem(0);
lb_Text.MyScrollBar.AlignThumb();
}
function bool OnLabelDraw(Canvas C)
{
C.Style = 1;
C.Font = class'ROHUD'.Static.GetSmallMenuFont(C);
return false;
}
defaultproperties
{
Begin Object Class=GUISectionBackground Name=BGSec
Caption="Server News"
WinTop=0.018
WinLeft=0.019
WinWidth=0.961
WinHeight=0.799
bFillClient=True
OnPreDraw=BGSec.InternalPreDraw
End Object
i_BGSec=GUISectionBackground'BGSec'
Begin Object Class=GUIScrollTextBox Name=InfoText
CharDelay=0.0025
WinTop=0.082
WinLeft=0.052
WinWidth=0.945
WinHeight=0.76
bNoTeletype=True
bBoundToParent=True
bScaleToParent=True
bNeverFocus=True
OnCreateComponent=InfoText.InternalOnCreateComponent
End Object
lb_Text=GUIScrollTextBox'InfoText'
NewsLine(0)="23.02.2012 Усилили броню горфастам"
NewsLine(1)="20.02.2012 Обновили урон M79"
}
Строки выводятся в обратном порядке (цикл идёт с конца массива), учитывайте это при заполнении. Затем подключите вкладки в SRInvasionLoginMenu.InitComponent и defaultproperties:
Panels[5].ClassName = string(Class'RulesTab');
Panels[6].ClassName = string(Class'NewsTab');
...
Panels(5)=(Caption="Rules",Hint="Server rules")
Panels(6)=(Caption="News",Hint="Server news")
Для кириллицы сохраните NewsTab.uc и RulesTab.uc в кодировке Unicode (UCS-2), например через Notepad++: в ANSI игра русские буквы не покажет. Чтобы не пересобирать весь ServerPerks при каждой новости, scar позже вынес вкладки в отдельный мутатор NewsRules. Идея основана на теме с форума Tripwire.
То же относится к переводу самого ServerPerks. Из темы «ServerPerks - нужна русификация» (форум killingfloor.ru): если после перевода ServerPerks v6.1 вместо русских букв видны «иероглифы», сохраните файлы с переводами в кодировке UCS-2 Little Endian (например, в Notepad++) и выставьте в настройках игры русский язык (Freddy, Titanishu).
Локальные часы в таблице игроков
Над заголовком таблицы (Tab) показывается время, и у каждого игрока своё, локальное. Правятся два класса.
SRPlayerReplicationInfo считает время на клиенте раз в секунду:
class SRPlayerReplicationInfo extends KFPlayerReplicationInfo;
var string PlayerLocalTime;
simulated event PostBeginPlay()
{
SetTimer(1.0, true);
Super.PostBeginPlay();
}
simulated function Timer()
{
if (Role < ROLE_Authority)
PlayerLocalTime = GetLocalTimeString();
Super.Timer();
}
simulated function string GetLocalTimeString()
{
local string Hour, Minute, Second;
Hour = string(Level.Hour); if (Level.Hour < 10) Hour = "0" $ Hour;
Minute = string(Level.Minute); if (Level.Minute < 10) Minute = "0" $ Minute;
Second = string(Level.Second); if (Level.Second < 10) Second = "0" $ Second;
return Hour $ ":" $ Minute $ ":" $ Second;
}
Если такого класса в вашем ServerPerks нет, создайте его и подключите в ServerPerksMut.CheckReplacement:
if (Controller(Other) != None)
Controller(Other).PlayerReplicationInfoClass = Class'SRPlayerReplicationInfo';
SRScoreBoard.UpdateScoreBoard: перед выводом заголовка нарисуйте часы крупным шрифтом, а строку заголовка (сложность, волна, карта, время) сдвиньте ниже на две строки (HeaderOffsetY += 2 * YL; вместо HeaderOffsetY = Canvas.ClipY * 0.11;):
S = SRPlayerReplicationInfo(OwnerPRI).PlayerLocalTime;
Canvas.Font = class'ROHud'.static.GetLargeMenuFont(Canvas);
Canvas.TextSize(S, XL, YL);
Canvas.DrawColor = HUDClass.default.RedColor;
Canvas.Style = ERenderStyle.STY_Normal;
HeaderOffsetY = Canvas.ClipY * 0.11;
Canvas.SetPos(0.5 * (Canvas.ClipX - XL), HeaderOffsetY);
Canvas.DrawTextClipped(S);
Flame отмечал, что то же можно сделать без правки ServerPerks: ловить нажатие Tab и рисовать часы в собственном Interaction.
Клановая надпись и логотип над игроком
Над головой игрока рисуется название клана его цветом и картинка-логотип клана. Данные хранятся в ini:
[ServerPerksMut.ClanLogoMut]
PlayerDataList=(Hash="76561198051378449",ClanName="FlameClan",ColorR=255,ColorG=0,ColorB=0,ColorA=0)
ClanDataList=(ClanName="FlameClan",ClanTexture="Asylum_T.sky.Moon",ClanTextureW=40,ClanTextureH=40,clanX=0,clanY=0,logoX=0,logoY=10)
Hash — SteamID64 игрока, ClanTexture — текстура логотипа (пакет.группа.имя), clanX/clanY и logoX/logoY — сдвиги надписи и картинки.
- Структуры и репликация в
SRPlayerReplicationInfo:
struct PlayerData { var string Hash; var string ClanName; var int ColorR, ColorG, ColorB, ColorA; };
struct ClanData
{
var string ClanName;
var string ClanTexture;
var int ClanTextureW, ClanTextureH;
var int clanX, clanY;
var int logoX, logoY;
};
var ClanData myClanData;
var string ClanName;
var int ColorR, ColorG, ColorB, ColorA;
replication
{
reliable if (bNetDirty && Role == ROLE_Authority)
myClanData, ClanName, ColorR, ColorG, ColorB, ColorA;
}
- Мутатор
ClanLogoMut(удобнее положить вServerPerksMutи хранить настройки вServerPerks.ini). ВPostBeginPlayон добавляет пакеты логотипов вAddToPackageMap, чтобы клиенты их скачали. ВCheckReplacementставит новых игроков в очередь и через 0.1 с ищет поGetPlayerIDHash()клан игрока, заполняет егоmyClanData,ClanNameи цвет. - Отрисовка в
SRHUDKillingFloor.DrawPlayerInfo(класс объявляется сdependson(SRPlayerReplicationInfo)): перед иконкой перка выводится название клана цветом игрока, а после — логотип:
if (SRPRI != none && SRPRI.ClanName != "")
{
C.SetDrawColor(SRPRI.ColorR, SRPRI.ColorG, SRPRI.ColorB, SRPRI.ColorA);
C.StrLen(SRPRI.ClanName, sXL, sYL);
C.SetPos(ScreenLocX - sXL * 0.83 - 10 + cData.clanX, ScreenLocY - sYL * 2.95 + cData.clanY);
C.DrawTextClipped(SRPRI.ClanName);
}
...
if (SRPRI.ClanName != "" && cData.ClanTexture != "")
{
TempMaterial = Texture(DynamicLoadObject(cData.ClanTexture, class'Texture'));
if (TempMaterial != none)
{
C.SetPos(ScreenLocX - cData.ClanTextureW - 62 + cData.logoX, ScreenLocY - cData.ClanTextureH / 2 - 34 + cData.logoY);
C.DrawTile(TempMaterial, cData.ClanTextureW, cData.ClanTextureH, 0, 0, TempMaterial.MaterialUSize(), TempMaterial.MaterialVSize());
}
}
Категории магазина TWI и ServerPerks вместе
С патча 1054 стандартный магазин делит оружие на категории, а у ServerPerks свои, более общие (часто по перкам). Flame связал обе панели, чтобы они синхронизировались: клик по значку перка вверху раскрывает нужную категорию внизу, и наоборот, а первое оружие категории прокручивается в начало списка. Правятся три класса:
SRGUIBuyMenuсоздаёт верхнюю панель (var automated SRBuyMenuFilter newBuyMenuFilter;) и вInitTabs()«знакомит» панели:newBuyMenuFilter.downPanel = buyMenu; buyMenu.upPanel = newBuyMenuFilter;;SRKFTab_BuyMenu(нижняя панель с категориями) при выборе категории сообщает верхней, какой перк подсветить;SRBuyMenuFilter(верхняя панель с перками) при клике раскрывает соответствующую категорию внизу.
Код всех трёх классов длинный и зависит от версии ServerPerks; за основу берите стандартные KFTab_BuyMenu и фильтр перков из вашего ServerPerks.
Другие правки ServerPerks
Оружие в магазине открывается с уровня перка
Способ AkycTuk (форум killingfloor.ru, тема «Блокируем оружие в магазине, по типу DLC»): оружие видно в магазине, но заблокировано с подписью, как DLC, пока перк не достигнет нужного уровня. Правятся два файла ServerPerks.
- В
SRVeterancyTypesдобавьте переменные и передdefaultpropertiesфункцию (здесьPerkIndex == 3— командос, уровень 6):
var() localized string DisableTag6lvl;
var() localized string DisableDescription6lvl;
static function bool AllowWeaponInTrader6lvl(class<KFWeaponPickup> Pickup, KFPlayerReplicationInfo KFPRI, byte Level)
{
if(Pickup == class'M41APickup')
{
if((KFPRI.ClientVeteranSkill.default.PerkIndex == 3) && KFPRI.ClientVeteranSkillLevel >=6)
{
return true;
}
else
{
return false;
}
}
return true;
}
и подписи в defaultproperties:
DisableTag6lvl="6 Lvl"
DisableDescription6lvl="Оружие будет доступно, при получении 6-го уровня перка."
- В
SRBuyMenuSaleList.UpdateForSaleBuyables()объявитеlocal class<SRVeterancyTypes> Blocker, Blocker6lvl;, после стандартной проверкиBlockerвставьте такую же для своей функции (в архиве темы индекс[i]послеCachePerksпропал из-за форумной разметки, в коде он нужен):
Blocker6lvl = none;
for( i=0; i<CPRL.CachePerks.Length; ++i )
if( !CPRL.CachePerks[i].PerkClass.Static.AllowWeaponInTrader6lvl(ForSalePickup,KFPRI,CPRL.CachePerks[i].CurrentLevel) )
{
Blocker6lvl = CPRL.CachePerks[i].PerkClass;
break;
}
if( Blocker6lvl!=None && Blocker6lvl.Default.DisableTag6lvl=="" )
continue;
а ниже блока if( DLCLocked==0 && Blocker!=None ) — пометку блокировки:
if((DLCLocked == 0) && Blocker6lvl != none)
{
ForSaleBuyable.ItemCategorie = (Blocker6lvl.default.DisableTag6lvl $ ":") $ Blocker6lvl.default.DisableDescription6lvl;
DLCLocked = 3;
}
Совет автора: оружие держите в отдельном пакете, который компилируется выше ServerPerks, а не в одном пакете с перками, иначе пакет может не собраться.
Двойные пистолеты в магазине ServerPerks 7.50
Статья Flame (тема «Добавляем двойные пистолеты в магазин. Нюансы покупки и продажи»). Без правки при продаже двойных пистолетов продаются оба сразу, при покупке двойных одинарный остаётся в магазине, бывают ошибки с весом и ценой. В SP 7.50 достаточно поправить класс DualWeaponsManager так, чтобы в defaultproperties были строки, а не классы (пакет с оружием может компилироваться после SP), а функции получали классы через DynamicLoadObject:
class DualWeaponsManager extends Object
abstract;
struct FDualList
{
var string Single,Dual;
};
var array<FDualList> DualMap;
static final function bool IsDualWeapon( class<Weapon> W, optional out class<KFWeapon> SingleType )
{
local int i;
local class<KFWeapon> SingeClass, DualClass;
if( W.Default.DemoReplacement!=None )
{
SingleType = class<KFWeapon>(W.Default.DemoReplacement);
return true;
}
for( i=(Default.DualMap.Length-1); i>=0; --i )
{
DualClass=Class<KFWeapon>(DynamicLoadObject(Default.DualMap[i].Dual,Class'Class'));
if( W==DualClass )
{
SingeClass=Class<KFWeapon>(DynamicLoadObject(Default.DualMap[i].Single,Class'Class'));
SingleType = SingeClass;
return true;
}
}
return false;
}
static final function bool HasDualies( class<Weapon> W, Inventory InvList, optional out class<KFWeapon> DualType )
{
local int i;
local Inventory In;
local class<KFWeapon> SingeClass, DualClass;
for ( In=InvList; In!=None; In=In.Inventory )
{
if( Weapon(In)!=None && Weapon(In).DemoReplacement==W )
{
DualType = class<KFWeapon>(In.Class);
return true;
}
}
for( i=(Default.DualMap.Length-1); i>=0; --i )
{
SingeClass=Class<KFWeapon>(DynamicLoadObject(Default.DualMap[i].Single,Class'Class'));
if( W==SingeClass )
{
DualClass=Class<KFWeapon>(DynamicLoadObject(Default.DualMap[i].Dual,Class'Class'));
DualType = DualClass;
W = DualClass;
for ( In=InvList; In!=None; In=In.Inventory )
{
if( In.Class==W )
return true;
}
return false;
}
}
return false;
}
defaultproperties
{
DualMap(0)=(Single="KFMod.Single",Dual="KFMod.Dualies")
DualMap(1)=(Single="KFMod.Magnum44Pistol",Dual="KFMod.Dual44Magnum")
DualMap(2)=(Single="KFMod.Deagle",Dual="KFMod.DualDeagle")
DualMap(3)=(Single="KFMod.FlareRevolver",Dual="KFMod.DualFlareRevolver")
DualMap(4)=(Single="KFMod.MK23Pistol",Dual="KFMod.DualMK23Pistol")
DualMap(5)=(Single="SteamPunkMagnum.SteamPunkMagnum44Pistol",Dual="SteamPunkMagnum.Dual44SteamPunkMagnum")
}
Свои пары добавляйте новыми строками DualMap(N). Для SP 7.10 Flame правил другие классы (SRBuyMenuSaleList, ещё один класс магазина и класс тела игрока).
Кастомные скины загружаются быстрее
Из статьи Flame «Ускорение загрузки Custom скинов» (тема «[Цикл статей] Server Perks и его редактирование»). В автоматическом состоянии RepSetup класса ClientPerkRepLink сервер сначала пересылает клиенту список оружия магазина и только потом данные о кастомных скинах. Поменяйте блоки местами, и скины будут загружаться значительно быстрее:
// Send client all the custom characters.
while( ClientAckSkinNum<CustomChars.Length )
{
ClientReceiveChar(CustomChars[ClientAckSkinNum],ClientAckSkinNum);
Sleep(0.15f);
}
// Now MAKE SURE client receives the full inventory list.
while( ClientAccknowledged[0]<ShopInventory.Length || ClientAccknowledged[1]<ShopCategories.Length )
{
for( SendIndex=0; SendIndex<ShopInventory.Length; ++SendIndex )
{
ClientReceiveWeapon(SendIndex,ShopInventory[SendIndex].PC,ShopInventory[SendIndex].CatNum);
Sleep(0.1f);
}
for( SendIndex=0; SendIndex<ShopCategories.Length; ++SendIndex )
{
ClientReceiveCategory(SendIndex,ShopCategories[SendIndex]);
Sleep(0.1f);
}
ClientSendAcknowledge();
Sleep(1.f);
}
Минус: при появлении игрока в логах появляются сообщения о подгрузке оружия магазина.
Скрытый админ
Из статьи Flame «Скрытый админ» в той же теме:
- Незаметный вход зрителем. Скопируйте
JoinedAsSpectatorOnlyизKFPlayerControllerвKFPCServи закомментируйте рассылку сообщения:
function JoinedAsSpectatorOnly()
{
if ( Pawn != None )
Pawn.Died(self, class'DamageType', Pawn.Location);
if ( PlayerReplicationInfo.Team != None )
PlayerReplicationInfo.Team.RemoveFromTeam(self);
PlayerReplicationInfo.Team = None;
ServerSpectate();
//BroadcastLocalizedMessage(Level.Game.GameMessageClass, 14, PlayerReplicationInfo);
ClientBecameSpectator();
}
Чтобы сообщение пропадало только для админов, оставьте вызов, но обёрните его проверкой ID: Hash=GetPlayerIDHash(); и if(Hash!="76561198051378449" && Hash!="76561198051378448") перед BroadcastLocalizedMessage(...).
- Без сообщения о логине. Если стоят Admin+ или AdminControl, правьте их код; иначе создайте свой класс админа без рассылки сообщений и подмените стандартный в
ServerPerksMut.PostBeginPlay()строкойLevel.Game.AccessControl.AdminClass = class'SRAdmin';:
class SRAdmin extends Admin;
function DoLogin( string Username, string Password )
{
if (Level.Game.AccessControl.AdminLogin(Outer, Username, Password))
bAdmin = true;
}
function DoLogout()
{
local bool bWasSilent;
bWasSilent = Outer.PlayerReplicationInfo.bSilentAdmin;
if (Level.Game.AccessControl.AdminLogout(Outer))
bAdmin = false;
}
Переименование ServerPerks
Flame советует переименовывать пакеты SP (например, в newServerPerks, newServerPerksP, newServerPerksMut), чтобы не было конфликтов со стандартным ServerPerks у клиента и при заходе на другие серверы. Кроме папок и имён пакетов придётся поправить все ссылки на пакет в defaultproperties, например в KFPCServ: LobbyMenuClassString="newServerPerks.SRLobbyMenu" и PawnClass=Class'newServerPerks.SRHumanPawn', а также ссылки на элементы меню в классах GUIHTMLTextBox, SRGUIBuyMenu, SRInvasionLoginMenu, SRKFTab_BuyMenu, SRKFTab_Perks, SRLobbyFooter, SRLobbyMenu (тема «[Цикл статей] Server Perks и его редактирование»).
Камера от третьего лица
AkycTuk (тема «Как поправить камеру при виде от 3 лица [ответ]»): за положение камеры отвечает функция GetShoulderCam в классе KFPCServ пакета ServerPerks. Он отодвинул камеру, поменяв одно значение в строке:
CamPos = vect(-160,20,10) >> Rotation;
В новых версиях ServerPerks «улучшенный» вид через плечо отключается в ServerPerks.ini параметром bEnhancedShoulderView=False (warpel). Stein предупредил, что в SP 6.75 при bEnhancedShoulderView=False отдача от третьего лица становится «статичной», и на некоторых серверах этим пользуются, чтобы легко попадать в голову.
Одни настройки — два сервера
Если два сервера из одной папки должны иметь разные настройки ServerPerks, Flame предлагает положить в пакет несколько одинаковых классов мутатора с разными именами и общим файлом настроек; у каждого будет своя секция в ini (тема «ServerPerks на два сервера»):
Class ServerPerksMut_01 extends Mutator Config(ServerPerks);
Class ServerPerksMut_02 extends Mutator Config(ServerPerks);
[ServerPerksMut.ServerPerksMut_01]
...
[ServerPerksMut.ServerPerksMut_02]
...
Свои текстуры HUD (деньги, патроны, волна)
Из темы «Edit» (форум killingfloor.ru). krestps: сделайте копию пакета, например KillingFloorHUD.utx, нарисуйте свои текстуры, упакуйте их обратно в .utx под своим именем (например, myhud) и подключайте мутатором - так сервер нормально играется по сети. LLIePLLIeHb: можно также переопределить HUDKillingFloor (например, в ServerPerksv5) и прописать там пути к своим текстурам; в ServerPerks он уже переопределён по умолчанию и называется SRHUDKillingFloor.