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).
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.
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.
[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.
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.
- 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.
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
if(Role == ROLE_Authority)
{
//КодДляСервера
}
else
{
//КодДляКлиента
}
local PlayerController PC;
PC=Level.GetLocalPlayerController();
if(PC!=none)
{
//КодДляКлиента;
}
else
{
//КодДляСервера;
}
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
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
}
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.
Additions
- A spawned weapon can't be picked up. From the "Не поднимается заспавненное оружие" thread: Titanishu added this line at the start of
simulated function Explodein a class copied fromNade:
Spawn(Class'KFMod.AK47Pickup',,, HitLocation, rotator(vect(0,0,1)));
The item appeared on the ground and glowed green, but could not be picked up, with no message at all. It worked once the spawn was moved to ProcessTouch (baikal_87's advice). Flame's explanation: the item was most likely spawned on the client, and it has to be spawned on the server.
- Your own object in BroadcastLocalizedMessage. From the "пересылка обьекта" thread: the last parameter,
OptionalObject, ofBroadcastLocalizedMessagealways arrived empty inGetStringof idpro2'sLocalMessagesubclass, and an object created withnew class'MyObject'did not help either. The beyondunreal article he linked says such an object must already exist on the players' side before it is sent. Flame passed anActorsubclass instead of a plainObject, becauseActorsupports replication:
class PatHPLeftObject extends Actor;
var int Damage;
var string PlayerName;
replication
{
reliable if(bNetDirty && Role == Role_Authority)
Damage,PlayerName;
}
The object is created on the server but used on the client, so his example (the MessageMut mutator) works in two Timer() calls: the first creates the objects for the players with Spawn(class'MyObject'), the second, once they have had time to replicate, sends the messages.
---
Author of the article: Team 2/5