Best way to execute code ONCE on each PlayerPawn in a game?

Discussions about Coding and Scripting
Post Reply
nullbyte
Novice
Posts: 27
Joined: Sat Sep 03, 2016 3:40 am

Best way to execute code ONCE on each PlayerPawn in a game?

Post by nullbyte »

I want to find the best way of executing some code on each player who joins a match. The code should only be executed one time, and then it can be forgotten.

Here is some basic pseudocode:

Code: Select all

function Tick {
	for each PlayerPawn in the map {
		// Execute some code on PlayerPawn
		// ...
		// Execution is finished - no need to repeat
	}

	// Now disable the Tick function but only for the current player, somehow?
	// ??????
}
I tried using the Tick() function and then calling

Code: Select all

Disable('Tick')
but then the code didn't run on any players who joined the match afterwards.

I ran it as a ServerActor - does this make a difference?
Is there a way I can disable the tick function but only for the current player and not for all players?
User avatar
Barbie
Godlike
Posts: 2792
Joined: Fri Sep 25, 2015 9:01 pm
Location: moved without proper hashing

Re: Best way to execute code ONCE on each PlayerPawn in a ga

Post by Barbie »

Depending on where you want to put the code I'd try it with GameInfo's Login() or Mutator's ModifyLogin().

See also: Wiki: Chain Of Events When A Player Logs In
"Multiple exclamation marks," he went on, shaking his head, "are a sure sign of a diseased mind." --Terry Pratchett
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Best way to execute code ONCE on each PlayerPawn in a ga

Post by sektor2111 »

I think you should read forum often, Higor has something to capture a new player - VIA linked lists, is attaching some actor owned and... there you can setup whatever thing for player Once, Twice or how many times you want.

Another simple noob style (as I do), in ModifyPlayer I can attach an actor owned by player and having a specific TAG. When ModifyPlayer is called again, if player doesn't have this actor give it one by iterating such actors, or if one of them is being owned by "Pawn Other", else skip it if has already one - there are a few mutators player trackers using this way. Also this actor can work once and go to sleeping or permanent checking something. Sample ? WTriggerer it uses to track player when it fires weapon for those old weapons which doesn't have Net codes. Else actor can have an ONCE-ONLY action sitting in server as long as player is there and self destructing if owner is being gone. These are simple. A friendly advice: Go and lecture some server-tools mutators, actors, whatever... is helpful for getting more ideas.

Aside: I don't get what you do by disabling Player's tick...
nullbyte
Novice
Posts: 27
Joined: Sat Sep 03, 2016 3:40 am

Re: Best way to execute code ONCE on each PlayerPawn in a ga

Post by nullbyte »

Thank you both.
sektor2111 wrote:Aside: I don't get what you do by disabling Player's tick...
Let me give you an example.

Imagine I want to create a mutator which displays a message to each player once they reach 100 frags. The message will say something like "Congratulations, you reached 100 frags!" and will only display once per player. Or, for example, a message which displays "You have played for too long - go outside!" once the player has been playing for over 60 minutes.

A simple way to do this would be to check the player's frag count (or time played) on each Tick(), and when the player reaches 100 frags (or 60 minutes), show them a message. After the message has been shown, it won't be shown again, so there is no reason to keep checking any more. Therefore the mutator's Tick() function should be disabled for that player only.

How would you do that? I assume each player would have to load his own copy of the mutator and run it on the client-side. Is this related to Replication?
ShaiHulud
Adept
Posts: 459
Joined: Sat Dec 22, 2012 6:37 am

Re: Best way to execute code ONCE on each PlayerPawn in a ga

Post by ShaiHulud »

Tick is extremely useful, but because it's part of the main game loop, you want to limit your activities in there as much as possible when alternative methods are available.

I think the ScoreKill event in Mutator.uc would probably be a good place to do your check for player frag count:

Code: Select all

function ScoreKill(Pawn Killer, Pawn Other)
{
  // Example ('killCount' is a variable in Pawn.uc, and all players - PlayerPawns - descend from this class):
  if (Killer.killCount == 100)
    Killer.clientMessage("Congratulations, you reached 100 frags!");
  
  // Original lines below...
  if (NextMutator != None)
    NextMutator.ScoreKill(Killer, Other);
}
I believe you can use elmuerte's UnCodeX to generate the same files locally, but there are several online repositories of HTML formatted files for easy browsing of the Unreal Script core classes. The one I use most often - here with a preloaded search term - is: here. Handy for reference.

The mutator you want to build would be server-side only. The server is authoritative for PlayerPawns, and all PlayerPawns and their variables are automatically replicated between clients and server - the information you're interested in is available to you on the server side.
Last edited by ShaiHulud on Thu May 04, 2017 2:13 am, edited 1 time in total.
ShaiHulud
Adept
Posts: 459
Joined: Sat Dec 22, 2012 6:37 am

Re: Best way to execute code ONCE on each PlayerPawn in a ga

Post by ShaiHulud »

As for time played, the information is available in the PlayerReplicationInfo class which exists for every player in the server.

Not sure of the best way to go about checking a long duration game time off-hand, but you could certainly use a timer() for this purpose. You could set it to fire every second, but since we're not talking about something game-critical here, you can probably sacrifice a bit of fidelity for the sake of reduced function calls. So perhaps start a timer in your mutator that executes every 10 seconds or so. Something like (untested)...

Code: Select all

function PreBeginPlay()
{
  super.PreBeginPlay();

  // Sets up and starts a timer - only one per class allowed.
  // The first parameter is the interval - or how often the timer fires
  // The second parameter determines whether the timer fires repeatedly, or just once (if you only want it to fire once, you supply 'false' here instead)
  SetTimer(10.0, true);
}

function Timer()
{
  local Pawn P;

  for (p = level.pawnList; p != none; p = p.nextPawn)
  {
    if (p.isA('PlayerPawn') && (Level.TimeSeconds - playerPawn(p).playerReplicationInfo.StartTime) / 60 >= 60)
      p.clientMessage("You have played for too long - go outside!");
  }
}
ShaiHulud
Adept
Posts: 459
Joined: Sat Dec 22, 2012 6:37 am

Re: Best way to execute code ONCE on each PlayerPawn in a ga

Post by ShaiHulud »

...er, except the problem with the above, as currently written, is that the message would continue to flash up once every 10 seconds after a player has reached 60 minutes of game time.

In that case things get a little more messy because you're talking about storing a variable for each player to indicate whether the message has been shown for them yet or not, taking into account disconnected players. There are probably better ways of going about it.
Higor
Godlike
Posts: 1866
Joined: Sun Mar 04, 2012 6:47 pm

Re: Best way to execute code ONCE on each PlayerPawn in a ga

Post by Higor »

Gunloc
Check how non-spectators are detected and their 'attached' info stored and accessed.
viewtopic.php?f=15&t=11947&p=97075&hilit=gunloc#p97075
User avatar
sektor2111
Godlike
Posts: 6403
Joined: Sun May 09, 2010 6:15 pm
Location: On the roof.

Re: Best way to execute code ONCE on each PlayerPawn in a ga

Post by sektor2111 »

@Shaihulud
I thought That you could see when epic said "Don't call actor's PreBeginPlay" I don't get why we are ignoring these simple rules - triple posting included and not using Edit button....
ShaiHulud
Adept
Posts: 459
Joined: Sat Dec 22, 2012 6:37 am

Re: Best way to execute code ONCE on each PlayerPawn in a ga

Post by ShaiHulud »

Ah, yeah, I'd forgotten about the double-posting, apologies for that.

I separated out the first two posts for clarity, and thought that the update note that I added subsequently could be missed if it was added below the main text, which might cause frustration if it wasn't read until after the unrealscript snippet had been used and found to be faulty.

About PreBeginPlay - I'd never come across that, do you have a citation? Not that I don't trust your word, it just seems peculiar that it's a public function in that case. Specifically why is this so?
User avatar
Barbie
Godlike
Posts: 2792
Joined: Fri Sep 25, 2015 9:01 pm
Location: moved without proper hashing

Re: Best way to execute code ONCE on each PlayerPawn in a ga

Post by Barbie »

ShaiHulud wrote:About PreBeginPlay - I'd never come across that, do you have a citation?
Have a look in stock code of Mutator.uc, function PreBeginPlay():

Code: Select all

//=============================================================================
// Mutator.
// called by the IsRelevant() function of DeathMatchPlus
// by adding new mutators, you can change actors in the level without requiring
// a new game class.  Multiple mutators can be linked together. 
//=============================================================================
class Mutator expands Info
	native;

var Mutator NextMutator;
var Mutator NextDamageMutator;
var Mutator NextMessageMutator;
var Mutator NextHUDMutator;

var bool bHUDMutator;

var class<Weapon> DefaultWeapon;

event PreBeginPlay()
{
	//Don't call Actor PreBeginPlay()
}

simulated event PostRender( canvas Canvas );
...
Specifically why is this so?
No reason is given here, but if you follow the chain of parent classes, you'll land in Actor.PreBeginPlay() (Info.PreBeginPlay() does not exist):

Code: Select all

class Actor extends Object abstract native nativereplication;
...
event PreBeginPlay()
{
	// Handle autodestruction if desired.
	if( !bGameRelevant && (Level.NetMode != NM_Client) && !Level.Game.IsRelevant(Self) )
		Destroy();
}
Probably the reason is that Level.Game.IsRelevant() calls Mutator's chain of AlwaysKeep()/IsRelevant(), and so we can get the situation that a Mutator decided not to allow itself.
"Multiple exclamation marks," he went on, shaking his head, "are a sure sign of a diseased mind." --Terry Pratchett
Chris
Experienced
Posts: 134
Joined: Mon Nov 24, 2014 9:27 am

Re: Best way to execute code ONCE on each PlayerPawn in a ga

Post by Chris »

Barbie wrote:
ShaiHulud wrote:About PreBeginPlay - I'd never come across that, do you have a citation?
Have a look in stock code of Mutator.uc, function PreBeginPlay():

Code: Select all

//=============================================================================
// Mutator.
// called by the IsRelevant() function of DeathMatchPlus
// by adding new mutators, you can change actors in the level without requiring
// a new game class.  Multiple mutators can be linked together. 
//=============================================================================
class Mutator expands Info
	native;

var Mutator NextMutator;
var Mutator NextDamageMutator;
var Mutator NextMessageMutator;
var Mutator NextHUDMutator;

var bool bHUDMutator;

var class<Weapon> DefaultWeapon;

event PreBeginPlay()
{
	//Don't call Actor PreBeginPlay()
}

simulated event PostRender( canvas Canvas );
...
Specifically why is this so?
No reason is given here, but if you follow the chain of parent classes, you'll land in Actor.PreBeginPlay() (Info.PreBeginPlay() does not exist):

Code: Select all

class Actor extends Object abstract native nativereplication;
...
event PreBeginPlay()
{
	// Handle autodestruction if desired.
	if( !bGameRelevant && (Level.NetMode != NM_Client) && !Level.Game.IsRelevant(Self) )
		Destroy();
}
Probably the reason is that Level.Game.IsRelevant() calls Mutator's chain of AlwaysKeep()/IsRelevant(), and so we can get the situation that a Mutator decided not to allow itself.
Good thought, this means that it's perfectly fine to use PreBeginPlay in mutator, all this comment says is that the Actor PreBeginPlay will never be called. Nulled in other words.

Code: Select all

event PreBeginPlay()
{
	//Don't call Actor PreBeginPlay()
}
Unless ofcourse you intentionally do it like

Code: Select all

Super(Actor).PreBeginPlay();
Post Reply