Types of mutators
1. A mutator that works only on the server (later I will add here a link to the article “client-server model for the unreal engine”).
2. A mutator that works on the server and the client (in this case, actions are performed only for clients, no useful code is executed for the server). A week ago I would have ended the classification here. But we did one mutator on February 23, and it turns out that one more case is worth mentioning:
3. A mutator that runs on the server, but at the same time requires that some resources be uploaded to the client (because implicit replication occurs there).
TYPE No. 1
A mutator that only works on the server
Perhaps most of the mutators on the forum are an example of such a mutator. A characteristic feature is complete independence from what is happening on the client. Since such a mutator runs only on the server, we do not need to upload it to the client, and there is no need to add bAddToServerPackages=true to defaultproperties, call the AddToPackageMap() function somewhere, or write ServerPackages=Mutator Package in KillingFloor.ini.
Note: You can create a file “PackageName.upkg” in the mutator folder. And add the AllowDownload, ClientOptional, ServerSideOnly flags in it. This text file is used by the compiler. By writing AllowDownload=false, we will prohibit downloading our package to the client, regardless of whether the admin has registered this package in ServerPackages or not. Marco has the following set of flags in ServerPerksMut:
[Flags]
AllowDownload=False
ClientOptional=False
ServerSideOnly=TrueI didn’t really worry about this before and added bAddToServerPackages=true to all mutators Beginners should probably do this so as not to rack their brains.
And one more note: This type of mutator can be launched not as a mutator, but as a ServerActor. I’ll dig into this topic later, because I don’t really understand the pros and cons of this step. Let me remind you that ServerActor is an object that is created on the server when the map is loaded.
TYPE No. 2
A mutator that is used on the client and possibly on the server.
Below are some of the cases when they are needed: - change the HUD (usually using Interaction) example: Patrick’s life bar link - processing of pressing keyboard/mouse buttons example: Interrupt recharge link - calling special effects on the client - the need to change the properties of objects used only on the client. For example: displaying the weight of a gun in a magazine - we’ll add more to the list later
In general, client mutators are not an easy topic. It requires an understanding of how objects are created on the server and client. This will be gradually revealed in other articles. For now, I’ll just describe the main characteristic features of the code.
1. For the mutator to work on the client, you need to add 2 lines to defaultproperties:
bAlwaysRelevant=True
RemoteRole=ROLE_SimulatedProxy2
The mutator must be downloaded to the client, so you need either the line
bAddToServerPackages=True
or another alternative adding the mutator to the package list.
3. Functions used on the client must have the prefix simulated
4. You can check whether a given mutator instance is running on the client or on the server:
Option 1:
if(Role == ROLE_Authority)
{
//КодДляСервера
}
else
{
//КодДляКлиента
}
Option 2:
local PlayerController PC;
PC=Level.GetLocalPlayerController();
if(PC!=none)
{
//КодДляКлиента;
}
else
{
//КодДляСервера;
}
Option 3:
if(Level.NetMode==NM_DedicatedServer)
{
//КодДляВыделенногоСервера
}
else
{
//КодДляКлиента (при условии, что мутатор установлен на выделенном сервере)
}
5. The mutator is executed on the client side, and it is possible to obtain a PlayerController.
Level.GetLocalPlayerController()
Accordingly, for each instance of the mutator there will be a PlayerController of its owner. For the server, Level.GetLocalPlayerController() will return none. This is precisely what the check in the previous paragraph (option 2) is based on. A very useful thing is Level.GetLocalPlayerController()
6. You cannot use Level.Game on the client
What does this mean? This means you can’t check on the client whether the wave is working or not:
if(KFGameType(Level.Game).bWaveInProgress)
{
...
}
You must use the KFGameReplicationInfo object.
local KFGameReplicationInfo KFGRI;
KFGRI = KFGameReplicationInfo(Level.GetLocalPlayerController().GameReplicationInfo);
if(KFGRI.bWaveInProgress)
{
...
}
7. You need to clearly understand that if you create a client mutator that reads information from an ini file, then the client versions of the mutator will look for ini on the client, and the server version will look for ini on the server. However, this is the topic of a more complex article.
8. The more I write, the more I remember little things. So far it’s good with the features - there’s more than one article of material here.
Let’s write a simple mutator to fix it and that’s enough for a start *smile*
Have the mutator write a greeting to the player at the beginning of the wave (the first wave he plays).
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
}
And this is what a server mutator with similar functionality looks like:
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"
}
In the first case, each client has a mutator who personally writes a message for him. In the second case, on the server we simply go through all the players and write a message to each of them. It is useful to compare these two mutators and clearly understand how each one works.
TYPE No. 3
A mutator that runs on the server but requires some resources to be downloaded to the client.
It seems to be server-based, but the problem is that if it is not uploaded to the client, it will not work fully. For example, a mutator that calls a function on the server that starts playing the ClientPlaySound sound. If you do not register the download of resources to the client, the sound will not be played. Therefore, if the resources of a package are needed for the mutator to work on the client, it is necessary to download this mutator to the client. That is, add bAddToServerPackages=True to defaultproperties.
---
Author of the article: Team 2/5