Christmas Patriarch

Типы мутаторов

1. Мутатор, который работает только на сервере (потом добавлю сюда ссылку на статью “модель клиент-сервер для unreal движка”).

2. Мутатор, который работает на сервере и клиенте (при этом возможно действия выполняются только для клиентов, для сервера никакого полезного кода не выполняется).
Неделю назад я бы на этом классификацию и закончил. Но мы тут делали один мутатор на 23 февраля, и получается, что стоит упомянуть ещё один случай:

3. Мутатор, который работает на сервере, но при этом требует, чтобы некоторые ресурсы закачивались на клиент (ибо туда идёт неявная репликация).

ТИП №1

Мутатор, который работает только на сервере

Примером такого мутатора являются пожалуй большинство мутаторов на форуме.
Характерной особенностью является полная независимость от того, что происходит на клиенте.
Так как такой мутатор выполняется только на сервере, нам нет необходимости закачивать его на клиент, и нет никакой необходимости добавлять bAddToServerPackages=true в defaultproperties, вызывать где-то функцию AddToPackageMap() или писать в KillingFloor.ini ServerPackages=ПакетМутатора.

Замечание:
Можно создать файл “НазваниеПакета.upkg” в папке мутатора. И в нём прописать флаги AllowDownload, ClientOptional, ServerSideOnly. Этот текстовый файл используется компилятором.
Написав AllowDownload=false, мы запретим закачивать наш пакет на клиент, независимо от того, прописал админ этот наш пакет в ServerPackages или нет.
У Марко в ServerPerksMut такой набор флагов:

[Flags]
AllowDownload=False
ClientOptional=False

ServerSideOnly=TrueЯ раньше особо не парился по этому поводу и во все мутаторы добавлял bAddToServerPackages=true
Новичкам стоит, пожалуй, так и делать, чтобы не ломать лишний раз голову.

И ещё замечание:
Данный тип мутатора можно запускать не как мутатор, а как ServerActor. Данную тему я потом покопаю, ибо не очень понимаю плюсы и минусы данного шага. Напоминаю, ServerActor - это объект, который создаётся на сервере при загрузке карты.

ТИП №2

Мутатор, который используется на клиенте и возможно на сервере.

Ниже приведены некоторые из случаев, когда они нужны:
- изменение HUD (обычно с помощью Interaction)
пример: Полоска жизни патрика ссылка
- обработка нажатия кнопок клавиатуры/мыши
пример: Прерывание перезарядки ссылка
- вызов спецэффектов на клиенте
- необходимость поменять свойства объектов, используемых только на клиенте. Например: отображение веса пушки в магазине
- потом ещё дополним список

Вообще клиентские мутаторы тема непростая. Она требует понимания того, как происходит создание объектов на сервере и клиенте. Это будет потихоньку раскрываться в других статьях. Пока просто опишу основные характерные особенности кода.

1. Чтобы мутатор работал на клиенте в defaultproperties необходимо добавить 2 строчки:

bAlwaysRelevant=True
RemoteRole=ROLE_SimulatedProxy2

Мутатор должен быть закачен на клиент, поэтому нужна либо строчка

bAddToServerPackages=True

либо другое альтернативное добавление мутатора в список пакетов.

3. Используемые на клиенте функции должны иметь префикс simulated

4. Проверку того, на клиенте выполняется данный экземляр мутатора или на сервере, можно сделать такую:

Вариант 1:

if(Role == ROLE_Authority)
{
    //КодДляСервера
}
else
{
    //КодДляКлиента
}

Вариант 2:

local PlayerController PC;
PC=Level.GetLocalPlayerController();
if(PC!=none)
{
    //КодДляКлиента;
}
else
{
    //КодДляСервера;
}

Вариант 3:

if(Level.NetMode==NM_DedicatedServer)
{
    //КодДляВыделенногоСервера
}
else
{
    //КодДляКлиента (при условии, что мутатор установлен на выделенном сервере)
}

5. Мутатор выполняется на стороне клиента, и есть возможность получить PlayerController.

Level.GetLocalPlayerController()

Соответственно для каждого экземпляра мутатора будет PlayerController его владельца.
Для сервера Level.GetLocalPlayerController() вернёт none.
Именно на этом основана проверка в предыдущем пункте (вариант 2).
Очень полезная штука этот Level.GetLocalPlayerController()

6. На клиенте нельзя использовать Level.Game

Что это значит? Это значит нельзя на клиенте так проверять идёт волна или нет:

if(KFGameType(Level.Game).bWaveInProgress)
{
...
}

Надо использовать KFGameReplicationInfo объект.

local KFGameReplicationInfo KFGRI;
KFGRI = KFGameReplicationInfo(Level.GetLocalPlayerController().GameReplicationInfo);
if(KFGRI.bWaveInProgress)
{
...
}

7. Надо чётко понимать, что, если создать клиентский мутатор с чтением информации из ini файла, то клиентские версии мутатора будут искать ini на клиенте, а серверный - на сервере. Впрочем, это тема более сложной статьи.

8. Чем больше я пишу, тем больше вспоминаю разных мелочей. Пока хорош тогда с особенностями - тут не на одну статью материала.

Напишем для закрепления простенький мутатор и хватит для начала *smile*

Пусть мутатор пишет игроку приветствие в начале волны (первой волны, которую он играет).

class TestClientMut extends Mutator;
//Функция выполняется на клиенте и сервере благодаря пунктам 1 и 2
//Для сервера сразу зовётся Disable('Tick') и для севрера функция больше не вызывается
//Для клиента же мы ждём начала волны, постим сообщение и отключаем функцию
//В принципе можно было бы и мутатор удалить для сервера и клиента, а не только отключить Tick, ну да не суть важно
simulated function Tick(float dt)
{
    local PlayerController PC;
    local KFGameReplicationInfo KFGRI;
    PC=Level.GetLocalPlayerController();
    if(PC!=none)
    {
        KFGRI = KFGameReplicationInfo(PC.GameReplicationInfo);
        if(KFGRI.bWaveInProgress)
        {
            PC.ClientMessage("Greetings Mortal, are you ready to die?");
            Disable('Tick');
        }
    }
    else
        Disable('Tick');
}
defaultproperties
{
    GroupName="KF-TestClientMut"
    FriendlyName="TestClient"
    Description="Testing base client mutator"
    bAlwaysRelevant=True
    RemoteRole=ROLE_SimulatedProxy
    bNetNotify=True
}

А вот так выглядит серверный мутатор с аналогичным функционалом:

class GreetingsMut extends Mutator;
function Tick(float dt)
{
    local Controller C;
    if(KFGameType(Level.Game).bWaveInProgress)
    {
        for( C = Level.ControllerList; C != None; C = C.nextController )
        {
            if(C.IsA('PlayerController') && C.PlayerReplicationInfo.PlayerID>0)
                PlayerController(C).ClientMessage("Greetings Mortal, are you ready to die?");
        }
        Disable('Tick');
    }
}
defaultproperties
{
    GroupName="KF-GreetingsMut"
    FriendlyName="Greetings"
    Description="GreetingsMut"
}

В первом случае у каждого клиента есть мутатор, который лично для него пишет сообщение. Во втором случае мы на сервере просто пробегаем по всем игрокам и каждому из них пишем сообщение.
Полезно сравнить эти два мутатора и чётко понять как каждый из них работает.

ТИП №3

Мутатор, который работает на сервере, но при этом требует, чтобы некоторые ресурсы закачивались на клиент.

Он, вроде, как серверный, но вот беда - если его не закачать клиенту, то он не будет полноценно работать.
Например, мутатор, который на сервере вызывает функцию запускающую проигрывание звука ClientPlaySound.
Если не прописать закачку ресурсов на клиент, звук проигрываться не будет.
Поэтому, если для работы мутатора на клиенте нужны ресурсы какого-либо пакета, необходимо скачивать этот мутатор на клиент.
То есть добавить bAddToServerPackages=True в defaultproperties.

---

Автор статьи: Команда 2/5