Execute Function After Match Begin

Discussions about Coding and Scripting
1337GameDev
Experienced
Posts: 85
Joined: Thu Apr 16, 2020 3:23 pm
Personal rank: GameDev

Execute Function After Match Begin

Post by 1337GameDev » Mon Nov 16, 2020 5:50 am

I am trying to initialize some object that has to loop over PlayerPawns and associate a Hud Mutator to them and other modifications.

What event / callback can be used for this?

Would I need special code for each gametype and their events / callbacks for a match beginning and PlayerPawns just spawning?

User avatar
Barbie
Godlike
Posts: 2069
Joined: Fri Sep 25, 2015 9:01 pm
Location: moved without proper hashing

Re: Execute Function After Match Begin

Post by Barbie » Mon Nov 16, 2020 11:56 am

1337GameDev wrote:
Mon Nov 16, 2020 5:50 am
I am trying to initialize some object that has to loop over PlayerPawns and associate a Hud Mutator to them and other modifications.
Polling is a bad idea... Because players can join at every time, event driven functions are better, and I think Mutator.ModifyLogin() or Mutator.ModifyPlayer() is best for this. Former is called on player login, latter on every player respawn.


If you are modifying the game type, event GameInfo.PostLogin() could also be a proper place.

PS: I remember a description of the login chain in the Wiki, but it is down again. :barf:
"Multiple exclamation marks," he went on, shaking his head, "are a sure sign of a diseased mind." --Terry Pratchett

User avatar
sektor2111
Godlike
Posts: 5209
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Execute Function After Match Begin

Post by sektor2111 » Mon Nov 16, 2020 7:22 pm

You can find the moment when game is being started and then you can setup a bool bGameIsRunning. bGameEnded is already embedded in GameInfo. All you need it's a ModifyPlayer capturing DeathMatchPlus.bStartMatch in that moment because it calls ModifyPlayer - Function StartMatch() from DeathMatchPlus is the key here. So you need something in ModifyPlayer because this is called too in that moment due to loading needs for match started. After starting match variable bStartMatch from DeathMatchPlus is turned back to false, but you can capture the start this way.

Code: Select all

if ( DeathMatchPlus(Level.Game).bStartMatch ) //even adding <if DeathMatchPlus(Level.Game) != None>, against Coop Null calls
{
	if ( !bGameIsRunning && !Level.Game.bGameEnded )
	{
		log("Game Starts right now...",'StartHooking');
		bGameIsRunning = True; //assign variable
	}
}
And then everything goes controlled under boolean bGameIsRunning which can be used all time in ModifyPlayer or another loop through Pawns, etc.

1337GameDev
Experienced
Posts: 85
Joined: Thu Apr 16, 2020 3:23 pm
Personal rank: GameDev

Re: Execute Function After Match Begin

Post by 1337GameDev » Tue Nov 17, 2020 6:49 am

Hmm, im curious on a way to do this agnostic to a game mode / having to select a mutator. I think a script spawned mutator in PreBeginPlay, with a "ModifyPlayer" function will work best for this. Then i can register anything to be called when a player is spawned.

1337GameDev
Experienced
Posts: 85
Joined: Thu Apr 16, 2020 3:23 pm
Personal rank: GameDev

Re: Execute Function After Match Begin

Post by 1337GameDev » Thu Nov 19, 2020 6:33 am

I believe I found a good way to do this, but there are some questions.

I created 2 Actors:
A PlayerSpawnMutator - Has methods "ModifyPlayer" and "ModifyLogin"
A PlayerSpawnNotify (subclass with

Code: Select all

ActorClass=Class'Engine.PlayerPawn'
in default properties)

I noticed that spawning does these events:

PlayerSpawnNotify is ONLY notified on the FIRST SPAWN. I assumed this would be for every RESPAWN(as I assume the PlayerPawn is destroyed when it dies).

For the mutator, ModifyLogin happens before the player spawns, but has joined the game. Seems normal and expected.
ModifyPlayer happens for every RESPAWN, even the first spawn (I originally miscounted and was confused, but this works as expected)

Can anybody shed some light on why the SpawnNotify is only triggered once?

User avatar
Feralidragon
Godlike
Posts: 5300
Joined: Wed Feb 27, 2008 6:24 pm
Personal rank: Work In Progress
Location: Liandri

Re: Execute Function After Match Begin

Post by Feralidragon » Thu Nov 19, 2020 12:11 pm

1337GameDev wrote:
Thu Nov 19, 2020 6:33 am
PlayerSpawnNotify is ONLY notified on the FIRST SPAWN. I assumed this would be for every RESPAWN(as I assume the PlayerPawn is destroyed when it dies).
It isn't.

When the player dies, the player is simply hidden and enters something close to a spectator state until it is "respawned" again, with respawning simply being the user health being reset back to 100, and made visible again at a certain position.
That's why SpawnNotify only picks it up once (the same with bots btw, they work in a similar fashion).

One thing to remember is that the PlayerPawn class not only represents the player character as a pawn, but it also represents the player connection to the game in a match.
Meaning that if the PlayerPawn is destroyed, the player leaves the match entirely (since that connection is lost), so it cannot really be destroyed during a match.

In later engines the connection itself is separated into a different class, but in UE1 the PlayerPawn has every role merged into the same class: the character, the connection and the controller.

As for the Unreal Wiki, the original one is likely gone for good, but there are a few mirrors already, namely this one (which is somewhat up to date):
https://unrealwiki.unrealsp.org/index.php/Main_Page
(the search works and everything)

But the events already mentioned should suffice to make it work for you,

Although, in case of the HUD mutator, you don't need to go all that trouble for a mutator specifically: all you have to do is to add it to the mutators chain server-side.
From there, if the mutator is an HUD mutator, then it should be set with RemoteRole=ROLE_SimulatedProxy and bAlwaysRelevant=True, so the mutator can be replicated to all players when they enter the server.

1337GameDev
Experienced
Posts: 85
Joined: Thu Apr 16, 2020 3:23 pm
Personal rank: GameDev

Re: Execute Function After Match Begin

Post by 1337GameDev » Thu Nov 19, 2020 9:10 pm

Feralidragon wrote:
Thu Nov 19, 2020 12:11 pm
1337GameDev wrote:
Thu Nov 19, 2020 6:33 am
PlayerSpawnNotify is ONLY notified on the FIRST SPAWN. I assumed this would be for every RESPAWN(as I assume the PlayerPawn is destroyed when it dies).
It isn't.

When the player dies, the player is simply hidden and enters something close to a spectator state until it is "respawned" again, with respawning simply being the user health being reset back to 100, and made visible again at a certain position.
That's why SpawnNotify only picks it up once (the same with bots btw, they work in a similar fashion).

One thing to remember is that the PlayerPawn class not only represents the player character as a pawn, but it also represents the player connection to the game in a match.
Meaning that if the PlayerPawn is destroyed, the player leaves the match entirely (since that connection is lost), so it cannot really be destroyed during a match.

In later engines the connection itself is separated into a different class, but in UE1 the PlayerPawn has every role merged into the same class: the character, the connection and the controller.

As for the Unreal Wiki, the original one is likely gone for good, but there are a few mirrors already, namely this one (which is somewhat up to date):
https://unrealwiki.unrealsp.org/index.php/Main_Page
(the search works and everything)

But the events already mentioned should suffice to make it work for you,

Although, in case of the HUD mutator, you don't need to go all that trouble for a mutator specifically: all you have to do is to add it to the mutators chain server-side.
From there, if the mutator is an HUD mutator, then it should be set with RemoteRole=ROLE_SimulatedProxy and bAlwaysRelevant=True, so the mutator can be replicated to all players when they enter the server.
That makes a ton of sense!

I was guessing it did that, but wasn't sure.

Makes sense.

So for a hudmutator, all I have to do is instantiate it server side, and then set it to a simulated proxy and relevant in its default properties block?

Thanks for explaining this. You are a huge help. Much appreciated.

User avatar
Feralidragon
Godlike
Posts: 5300
Joined: Wed Feb 27, 2008 6:24 pm
Personal rank: Work In Progress
Location: Liandri

Re: Execute Function After Match Begin

Post by Feralidragon » Fri Nov 20, 2020 12:56 pm

Yes, you set these properties in the default properties block itself, and then instantiate it server-side.

But you also need to add it to the HUD mutators linked list so that your mutator is actually used.
For this you just need to call the RegisterHUDMutator() function within the mutator itself, and it should be called from a simulated function, like a simulated PostBeginPlay in the mutator itself, which should do the trick.

Simulated functions are functions which are callable client-side (well, more strictly on the remote "simulated" copy of the actor instance, which normally happens to be the client version of the actor, but I can elaborate more on that later if needed), as long as the actor's local role is ROLE_SimulatedProxy or above.

That is to say that if you don't declare a function as simulated, if the local client copy of the actor attempts to call this function, it will just fail silently (if the function returns anything, it will just return the corresponding "zero" value to that type, like empty string for strings, false for booleans, 0 for integers, None for objects, etc), and in this case this is needed since HUD mutators, while instantiated in the server, they must be registered locally in the client HUD since it's the client HUD which is going to make the rendering function calls of the HUD mutators.

Normally you wouldn't need to do this last step, provided that it was a normal HUD mutator meant to be activated by the player from the game itself (from the in-game mutators list), as the game would just do this step for you, but since it sounds like a mutator you want to automatically add from something else (which is a perfectly legit case), you have to do this last step, otherwise it won't work.

1337GameDev
Experienced
Posts: 85
Joined: Thu Apr 16, 2020 3:23 pm
Personal rank: GameDev

Re: Execute Function After Match Begin

Post by 1337GameDev » Fri Nov 20, 2020 3:34 pm

Feralidragon wrote:
Fri Nov 20, 2020 12:56 pm
Yes, you set these properties in the default properties block itself, and then instantiate it server-side.

But you also need to add it to the HUD mutators linked list so that your mutator is actually used.
For this you just need to call the RegisterHUDMutator() function within the mutator itself, and it should be called from a simulated function, like a simulated PostBeginPlay in the mutator itself, which should do the trick.

Simulated functions are functions which are callable client-side (well, more strictly on the remote "simulated" copy of the actor instance, which normally happens to be the client version of the actor, but I can elaborate more on that later if needed), as long as the actor's local role is ROLE_SimulatedProxy or above.

That is to say that if you don't declare a function as simulated, if the local client copy of the actor attempts to call this function, it will just fail silently (if the function returns anything, it will just return the corresponding "zero" value to that type, like empty string for strings, false for booleans, 0 for integers, None for objects, etc), and in this case this is needed since HUD mutators, while instantiated in the server, they must be registered locally in the client HUD since it's the client HUD which is going to make the rendering function calls of the HUD mutators.

Normally you wouldn't need to do this last step, provided that it was a normal HUD mutator meant to be activated by the player from the game itself (from the in-game mutators list), as the game would just do this step for you, but since it sounds like a mutator you want to automatically add from something else (which is a perfectly legit case), you have to do this last step, otherwise it won't work.
This does make sense.

For ensuring it's instantiated server side, I just do a server check before instantiating? I can't remember the exact code, but I've seen it before for a dedicated server.

And I would just have my PreBeginPlay function (that registers the hud Mutator) have the simulated keyword, and then that function is replicated to each client?

If so, that's pretty reasonable and makes sense.

User avatar
Feralidragon
Godlike
Posts: 5300
Joined: Wed Feb 27, 2008 6:24 pm
Personal rank: Work In Progress
Location: Liandri

Re: Execute Function After Match Begin

Post by Feralidragon » Fri Nov 20, 2020 10:43 pm

1337GameDev wrote:
Fri Nov 20, 2020 3:34 pm
For ensuring it's instantiated server side, I just do a server check before instantiating? I can't remember the exact code, but I've seen it before for a dedicated server.
It depends on where you instantiate it from, and how you do it.

The first thing to understand in that regard is that there are 2 possible factors you can use in that kind of check, namely by checking one of the following:
  • NetMode, which indicates the environment (client, dedicated server, listen server, standalone);
  • Role, which indicates the role of the actor in that environment (none, dumb proxy, simulated proxy, autonomous proxy, authority).
Generally speaking, unlike what it may seem at first, you don't actually want to check for the environment (NetMode) most of the time, and instead what is generally checked is the actor role (Role), which should become clear why once you understand what each net mode and role actually means.


NetMode:
As aforementioned, this indicates the environment you're currently running code at, namely:
  • NM_Client is the client environment, as in a client which is connected to a remote server;
  • NM_DedicatedServer is a dedicated server environment, working only as a server with clients connecting to it;
  • NM_ListenServer is a listen server environment, working both as a server and a client at the same time, to which other clients connect to it as well, where if the client leaves (the host) the server also disappears, which is like games like CoD where there's a player who's the host and the other players connect to it, but unlike CoD the host doesn't transfer to someone else if the host player leaves;
  • NM_Standalone is a standalone environment, as in there is no client or server, just the player himself playing offline without any connection (single player).
If you use NetMode to make that check, based on the above you will quickly realize that you have to check for multiple net modes, or to accept any except one (NM_Client), since the dedicated server, listen server and standalone net modes are all environments you want to spawn the mutator in, otherwise if you just limit to the dedicated server, then the mutator won't be spawned if the player starts a listen server to play with friends, nor in single player.

Instantiating only when the net mode is not NM_Client would work, but it's theoretically problematic to do that type of condition, since you open the code to unforeseen issues if another net mode happens to be added in a later update (unlikely, but it's generally bad coding practice to exclude environments in a condition when you want to limit to a specific overall environment instead).

Therefore the Role is used for these kinds of checks instead, since the conditions are much simpler.


Role:
As aforementioned, this indicates the role of the local actor instance, and as such there's also RemoteRole which indicates the role of the remote actor instance on the other side of the network.

In this case I won't explain how each one of them works, unless needed later, so I will limit to the ones you're going to use in your case, namely:
  • ROLE_Authority indicates that the local actor is the authoritative version, meaning that it's the original instance and not a replicated copy (or proxy) from the other side of the network;
  • ROLE_SimulatedProxy indicates that the local actor is a simulated proxy, meaning that the actor didn't spawn here, and instead it was replicated from the other side of the network, thus it has the corresponding authoritative and original version of it on the other side, and this local version is simply a simulated version of it (with the "simulated" part implying that it's capable of running its own code locally, but only code tagged as "simulated", which is the simulated keyword you see in function definitions).
Notice how when I talk about roles, I am not specifying the local environment to necessarily be the server or client, meaning that the role depends exclusively on whether the actor is a replicated local copy or the original instance.

As an example, if you instantiate an actor in the server, and that actor is replicated to clients, then the actor role in the server will be ROLE_Authority, and the remote role (RemoteRole) will be something else (like ROLE_SimulatedProxy), whereas in each client the same actor will have those two switched around (Role = ROLE_SimulatedProxy, RemoteRole = ROLE_Authority).

Although generally actors are mostly instantiated in the server, therefore their original versions are server-side, thus actors with ROLE_Authority are generally server-side actors.
However actors only instantiated client-side, such as effects, HUD, etc, are ROLE_Authority in the client, and since they do not generally replicate anything to the server, the RemoteRole will be ROLE_None (meaning that there's no remote replicated copy of the actor).

Therefore, all actors instantiated in single-player (offline) environment are also ROLE_Authority, with ROLE_None as their RemoteRole.

Therefore, what is generally done is to check if the actor role is ROLE_Authority in order to ensure that it spawns only from the authoritative version of the actor, since it's generally what you actually want to do (and what we actually generally mean with "spawn it server-side"), regardless of the actual environment it's instantiated in (be it online or offline, dedicated or listen server, etc).


Having that said however, as I mentioned initially, you don't necessarily need this check depending from where you instantiate it, and here is where "simulated" functions come into play into that decision, and that ties directly into your next question:
1337GameDev wrote:
Fri Nov 20, 2020 3:34 pm
And I would just have my PreBeginPlay function (that registers the hud Mutator) have the simulated keyword, and then that function is replicated to each client?
Being "simulated" doesn't mean that the function itself is replicated, but rather it decides whether or not the function is allowed to be executed locally or not depending on the actor's role.

It boils down to the following:
  • non-simulated functions, can only be executed when the role is ROLE_Authority, meaning that if such a function is called in a local actor instance which role is anything different from ROLE_Authority, then it won't be executed at all;
  • simulated functions (functions with the simulated keyword), can only be executed when the role is ROLE_SimulatedProxy or higher (ROLE_Authority is the highest role, so simulated functions also work with this role all the same), meaning that if such a function is called in a local actor instance which role is anything lower than ROLE_SimulatedProxy, then it won't be executed at all, but if it's ROLE_SimulatedProxy or higher, then it will be executed, but it's executed locally (no replication here, just normal code execution).
For example, functions which execute weapon animations are simulated in order to run both server and client-side, but independently, because both sides need to be aware when the animation ends (as it's generally how the firing of weapons is controlled), but since both are running the same code with the same values, the end result is exactly the same in both sides of the network (simulation).

So, answering first to your first question, if you're instantiating an actor from a non-simulated function, then you don't need to add any check as long as the function being called is from an actor instance which has its original or authoritative version of the instance in the server.
However, if you're instantiating it from a simulated function, then you need to add that check.

As for the PreBeginPlay function specifically, it's a function which is automatically called by the engine itself (an event) when the actor is instantiated, so the function is always "called" in all sides of the network (server, client, standalone), but it's only "executed" depending exactly on the local role of the actor, and whether the function is declared as simulated or not.

In the case of your HUD mutator, this function needs to be simulated because the clients will get the replicated version of the mutator which will be simulated (ROLE_SimulatedProxy), otherwise that function won't be executed where it actually needs to be executed in this case.

If you spawned your HUD mutator in the clients only however, you wouldn't need to declare it as simulated, because in that case the local role would be authoritative.
And while this may sound better (to avoid all the replication stuff), the reason why I advised you to instantiate it on the server, is because the engine expects all mutators to be instantiated server-side, and is generally better to go along the way the game expects things to be, so you don't get any unpleasant surprises.


Well, this ended up being a huge reply, but hopefully this way it should be more understandable how things actually work when dealing with multiplayer, as far as roles, environments and simulated functions go.

1337GameDev
Experienced
Posts: 85
Joined: Thu Apr 16, 2020 3:23 pm
Personal rank: GameDev

Re: Execute Function After Match Begin

Post by 1337GameDev » Sat Nov 21, 2020 12:54 am

Feralidragon wrote:
Fri Nov 20, 2020 10:43 pm
1337GameDev wrote:
Fri Nov 20, 2020 3:34 pm
For ensuring it's instantiated server side, I just do a server check before instantiating? I can't remember the exact code, but I've seen it before for a dedicated server.
It depends on where you instantiate it from, and how you do it.

The first thing to understand in that regard is that there are 2 possible factors you can use in that kind of check, namely by checking one of the following:
  • NetMode, which indicates the environment (client, dedicated server, listen server, standalone);
  • Role, which indicates the role of the actor in that environment (none, dumb proxy, simulated proxy, autonomous proxy, authority).
Generally speaking, unlike what it may seem at first, you don't actually want to check for the environment (NetMode) most of the time, and instead what is generally checked is the actor role (Role), which should become clear why once you understand what each net mode and role actually means.


NetMode:
As aforementioned, this indicates the environment you're currently running code at, namely:
  • NM_Client is the client environment, as in a client which is connected to a remote server;
  • NM_DedicatedServer is a dedicated server environment, working only as a server with clients connecting to it;
  • NM_ListenServer is a listen server environment, working both as a server and a client at the same time, to which other clients connect to it as well, where if the client leaves (the host) the server also disappears, which is like games like CoD where there's a player who's the host and the other players connect to it, but unlike CoD the host doesn't transfer to someone else if the host player leaves;
  • NM_Standalone is a standalone environment, as in there is no client or server, just the player himself playing offline without any connection (single player).
If you use NetMode to make that check, based on the above you will quickly realize that you have to check for multiple net modes, or to accept any except one (NM_Client), since the dedicated server, listen server and standalone net modes are all environments you want to spawn the mutator in, otherwise if you just limit to the dedicated server, then the mutator won't be spawned if the player starts a listen server to play with friends, nor in single player.

Instantiating only when the net mode is not NM_Client would work, but it's theoretically problematic to do that type of condition, since you open the code to unforeseen issues if another net mode happens to be added in a later update (unlikely, but it's generally bad coding practice to exclude environments in a condition when you want to limit to a specific overall environment instead).

Therefore the Role is used for these kinds of checks instead, since the conditions are much simpler.


Role:
As aforementioned, this indicates the role of the local actor instance, and as such there's also RemoteRole which indicates the role of the remote actor instance on the other side of the network.

In this case I won't explain how each one of them works, unless needed later, so I will limit to the ones you're going to use in your case, namely:
  • ROLE_Authority indicates that the local actor is the authoritative version, meaning that it's the original instance and not a replicated copy (or proxy) from the other side of the network;
  • ROLE_SimulatedProxy indicates that the local actor is a simulated proxy, meaning that the actor didn't spawn here, and instead it was replicated from the other side of the network, thus it has the corresponding authoritative and original version of it on the other side, and this local version is simply a simulated version of it (with the "simulated" part implying that it's capable of running its own code locally, but only code tagged as "simulated", which is the simulated keyword you see in function definitions).
Notice how when I talk about roles, I am not specifying the local environment to necessarily be the server or client, meaning that the role depends exclusively on whether the actor is a replicated local copy or the original instance.

As an example, if you instantiate an actor in the server, and that actor is replicated to clients, then the actor role in the server will be ROLE_Authority, and the remote role (RemoteRole) will be something else (like ROLE_SimulatedProxy), whereas in each client the same actor will have those two switched around (Role = ROLE_SimulatedProxy, RemoteRole = ROLE_Authority).

Although generally actors are mostly instantiated in the server, therefore their original versions are server-side, thus actors with ROLE_Authority are generally server-side actors.
However actors only instantiated client-side, such as effects, HUD, etc, are ROLE_Authority in the client, and since they do not generally replicate anything to the server, the RemoteRole will be ROLE_None (meaning that there's no remote replicated copy of the actor).

Therefore, all actors instantiated in single-player (offline) environment are also ROLE_Authority, with ROLE_None as their RemoteRole.

Therefore, what is generally done is to check if the actor role is ROLE_Authority in order to ensure that it spawns only from the authoritative version of the actor, since it's generally what you actually want to do (and what we actually generally mean with "spawn it server-side"), regardless of the actual environment it's instantiated in (be it online or offline, dedicated or listen server, etc).


Having that said however, as I mentioned initially, you don't necessarily need this check depending from where you instantiate it, and here is where "simulated" functions come into play into that decision, and that ties directly into your next question:
1337GameDev wrote:
Fri Nov 20, 2020 3:34 pm
And I would just have my PreBeginPlay function (that registers the hud Mutator) have the simulated keyword, and then that function is replicated to each client?
Being "simulated" doesn't mean that the function itself is replicated, but rather it decides whether or not the function is allowed to be executed locally or not depending on the actor's role.

It boils down to the following:
  • non-simulated functions, can only be executed when the role is ROLE_Authority, meaning that if such a function is called in a local actor instance which role is anything different from ROLE_Authority, then it won't be executed at all;
  • simulated functions (functions with the simulated keyword), can only be executed when the role is ROLE_SimulatedProxy or higher (ROLE_Authority is the highest role, so simulated functions also work with this role all the same), meaning that if such a function is called in a local actor instance which role is anything lower than ROLE_SimulatedProxy, then it won't be executed at all, but if it's ROLE_SimulatedProxy or higher, then it will be executed, but it's executed locally (no replication here, just normal code execution).
For example, functions which execute weapon animations are simulated in order to run both server and client-side, but independently, because both sides need to be aware when the animation ends (as it's generally how the firing of weapons is controlled), but since both are running the same code with the same values, the end result is exactly the same in both sides of the network (simulation).

So, answering first to your first question, if you're instantiating an actor from a non-simulated function, then you don't need to add any check as long as the function being called is from an actor instance which has its original or authoritative version of the instance in the server.
However, if you're instantiating it from a simulated function, then you need to add that check.

As for the PreBeginPlay function specifically, it's a function which is automatically called by the engine itself (an event) when the actor is instantiated, so the function is always "called" in all sides of the network (server, client, standalone), but it's only "executed" depending exactly on the local role of the actor, and whether the function is declared as simulated or not.

In the case of your HUD mutator, this function needs to be simulated because the clients will get the replicated version of the mutator which will be simulated (ROLE_SimulatedProxy), otherwise that function won't be executed where it actually needs to be executed in this case.

If you spawned your HUD mutator in the clients only however, you wouldn't need to declare it as simulated, because in that case the local role would be authoritative.
And while this may sound better (to avoid all the replication stuff), the reason why I advised you to instantiate it on the server, is because the engine expects all mutators to be instantiated server-side, and is generally better to go along the way the game expects things to be, so you don't get any unpleasant surprises.


Well, this ended up being a huge reply, but hopefully this way it should be more understandable how things actually work when dealing with multiplayer, as far as roles, environments and simulated functions go.
This is a great reply!
I appreciate this explanation a lot. It helps me understand network roles and replication more.

Hmm, so my understanding is any Mutator should be instantiated from a simulated function, and the function execution will be replicated on clients?

In the context of a hud Mutator, or something that doesn't need to be executed on the server (eg: the server doesn't care about the game state in the context of code for a hud), if this is registered / instantiated in a simulated function, it'll be executed on the server too?

When would you generally check the role of an actor? Just check if the role is an authority and then execute code? (Because an authority actor state will be replicated to the simulated proxy versions of the actor on the other end of the network)?

When would you manually set the role? Or is the role just set by the game normally, based on if it's called inside a simulated function?

Thanks for taking the time to reply, this is very helpful, and I feel it'd be helpful to others too :)

User avatar
Feralidragon
Godlike
Posts: 5300
Joined: Wed Feb 27, 2008 6:24 pm
Personal rank: Work In Progress
Location: Liandri

Re: Execute Function After Match Begin

Post by Feralidragon » Sat Nov 21, 2020 4:08 am

Before I go on, let me just note ahead of time that replication has already been documented at great length in the wiki I mentioned before, and is quite a heavy subject on its own which is generally only recommended to look at when you already got the hang of how the engine overall works, so if you want to know more details, here you go:
https://unrealwiki.unrealsp.org/index.p ... n_overview

1337GameDev wrote:
Sat Nov 21, 2020 12:54 am
Hmm, so my understanding is any Mutator should be instantiated from a simulated function, and the function execution will be replicated on clients?
No.

First and foremost, function execution is not "replicated" at all.
While there is such a thing as "replicated functions" (which you may know as RPCs if you used other engines or technologies overall with this concept), that's a completely different subject and has nothing to do with a "simulated function", so let's not call them "replicated".

Simulation here means that the server and the client have independent instances of the same actor, which are only connected to one another over the network with something like a shared ID, thus any function calls that happen in either side of the network only run on that side of the network alone, independently from one another.

In order to better understand this, the way an instance is replicated from the server to the client, can be somewhat summarized to the client just instantiating the same class and then just remain identified with an ID shared between client and server for that instance (there's more to it of course, but for the sake of simplicity this is the gist of it).

Mutators are generally server-side only actors, since generally a mutator only affects things like inventory replacement, set the map to low gravity, and such, so basic game logic if you will, nothing that the client has anything to do about, so they are not replicated at all in most cases (what ends up being replicated are the effects of those changes, such as the state of the gravity value will be replicated to clients).

However, HUD mutators have to be replicated, because they do run client-side code, and in that case is not the function that instantiates this mutator that has to be simulated (because we already determined that the mutator is instantiated server-side), but rather the functions that run client-side code have to be simulated instead (such as to register the instantiated mutator as an HUD mutator to be used).

I cannot really go much in depth here without making a super giant post, as at that point I might as well do an in-depth explanation of replication with pictures and such (no time for that now though), so I really advise you to go look in the wiki to see how these things work, as well check the game source code (UnrealScript side), to see how things like mutators work (the code is very easy to follow, and mutators are relatively simple classes).

Also I also advise you to read my previous post again concerning the roles: I understand your confusion, since replication is a subject that may be hard to grasp at first, but read it again and read the wiki, start trying to do some experiments to see things working, and you should start to get the handle of it.

1337GameDev wrote:
Sat Nov 21, 2020 12:54 am
In the context of a hud Mutator, or something that doesn't need to be executed on the server (eg: the server doesn't care about the game state in the context of code for a hud), if this is registered / instantiated in a simulated function, it'll be executed on the server too?
Yes and no.

Let's say you do this in your HUD mutator:

Code: Select all

class MyHUDMutator extends Mutator
{
    simulated function PostBeginPlay()
    {
        RegisterHUDMutator();
    }
}
Since PostBeginPlay is an engine event, which is automatically called from the engine itself when the actor is instantiated, it will be called on both the client and server, but independently (remember: the "same" instance between client and server are actually separate instances of the same class, but just connected over the network with an ID).

By spawning this mutator server-side, you're essentially saying that the authoritative instance is the one in the server, and an authoritative instance is allowed to execute all functions from that instance, so in this case, yes, the RegisterHUDMutator will be called and executed in the server.

Then, by defining the mutator remote role as a simulated proxy, this means that the corresponding mutator instance in the client is simulated, and a simulated instance is only allowed to execute simulated functions.
Since PostBeginPlay is simulated, it's executed, and since RegisterHUDMutator is also simulated (if you check it), then it's also executed.

So in practice, yeah, both functions are called, independently, on each side of the network (client and server).

But then, when you look at the code of RegisterHUDMutator:
https://www.madrixis.de/undox/Source_en ... r.html#213
you will see that it iterates over all players, but only actually adds the mutator to the HUD mutators linked list if the player HUD instance is itself not None (not null), which is only true for the player corresponding to that client, and is false concerning other players, and is also false server-side for all players, meaning that in practice the HUD mutator is only added to the local player of that client.

1337GameDev wrote:
Sat Nov 21, 2020 12:54 am
When would you generally check the role of an actor? Just check if the role is an authority and then execute code? (Because an authority actor state will be replicated to the simulated proxy versions of the actor on the other end of the network)?
If the code is well structured enough, very rarely actually.
Generally, you would only add such a condition in a function which is meant to be executed in both sides of the network (client and server), but somewhere in there you have a tiny little thing which you want to ensure only gets executed in the authoritative instance, like instantiating a specific actor, like so:

Code: Select all

class SimPotato extends Actor
{
    simulated function mySimulatedFunction()
    {
        //do stuff...
        
        if (Role == ROLE_Authority) {
            spawn(class'Veggie');
        }
        
        //do more stuff...
    }
}
since without the condition that actor would be instantiated once in the authoritative side (server), and twice in the simulated side (client): the one explicitly instantiated on the simulated side, and other one instantiated as a result of the replication of the one from the authoritative side.

But generally speaking, you can easily imagine that you could completely prevent that kind of condition by simply creating a non-simulated function to do that, like so:

Code: Select all

class SimPotato extends Actor
{
    simulated function mySimulatedFunction()
    {
        //do stuff...
        
        spawnVeggie();
        
        //do more stuff...
    }
    
    function spawnVeggie()
    {
        spawn(class'Veggie');
    }
}
which would result in the exact same behavior, given that spawnVeggie is not simulated, thus it will only be executed when the Role is ROLE_Authority in the exact same way.

So it's hard to imagine this, but in the original UT99 source code you can check this condition being used a lot throughout the code, that being because Epic didn't really know to structure their code properly back then.

1337GameDev wrote:
Sat Nov 21, 2020 12:54 am
When would you manually set the role? Or is the role just set by the game normally, based on if it's called inside a simulated function?
The Role is something that you don't really set, ever.
What you do set is the RemoteRole, and generally this is done in the default properties block alone and you don't usually change it in the code.

The RemoteRole decides what kind of proxy the instance will act as remotely on the other side of the network, which goes all the way from ROLE_None (do not replicate the instance at all, keep it only in the authority side) to ROLE_AutonomousProxy (generally reserved for the player itself).

Usually, if you want an instance to be replicated, you set the RemoteRole to either ROLE_DumbProxy or ROLE_SimulatedProxy in the default properties, with one of the differences between the two being that DumbProxy does not run any code at all (not even simulated) in that side of the network.

The RemoteRole can be changed later in the code itself, which will modify the behavior of the remote instance accordingly, but it may not work as good as you may believe it to, due to a multitude of other replication factors I didn't cover here (relevance, tear-off replication, net priority, replication conditions of properties, etc), so is generally just best to set the RemoteRole in the default properties, and keep the same one throughout the entire lifetime of the instance.