Since early 2002, Shee Labs has been a group of individuals working to produce free game and media downloads. With ever-fluctuate staffing, Shee Labs continues working behind the scenes on various exciting projects for a bewildering array of areas. Shee Labs ceased to exist in September 2009.

Report article RSS Feed Unreal Learning #7: Game Rules

This tutorial will show you how to write a mutator that uses Game Rules. The code was written by Erwan 'Xiongmao' Allain for Shee Labs Mutator Week.

Posted by ambershee on Feb 28th, 2008
Basic Server Side Coding.

Today we're going to recreate a mutator Erwan 'Xiongmao' Allain wrote for Shee Labs Mutator Week! You can read about it here: Moddb.com

It's very short, very sweet and easy to modify to suit your needs. This time, we're going to use Game Rules. Game Rules are special, meaning you can deliberately effect game mode behavious without having to write a whole new game mode for a few little changes. If you were to make drastic changes, then perhaps it's time to write a new game mode instead.

Today, we're going to use the Game Rules to determine the best player and the worst player. We're also going to attach a light to the best and worst players, and we're going to give bonuses and maluses for killing them respectively.

First we need a mutator that implements a new game rules - this is easy. Don't forget to make sure your code is in a folder called 'BestWorst', or rename the code packages as you see fit.

uscript code:
class BestWorst extends UTMutator;

var class GRClass< SEMI >

function InitMutator(string Options, out string ErrorMessage)
{
WorldInfo.Game.AddGameRules(GRClass);

Super.InitMutator(Options, ErrorMessage);
}

DefaultProperties
{
GRClass=class'BestWorst.BestWorst_Rules'
}

Now the hard part. We want a new game rules class, so we derive from GameRules. We also want to get the controller of the best player, and the worst player. For our best and worst players, we need a PointLightComponent, both of which need a colour. Finally, we want a variable to tell us how many points to add or take away from a score.

uscript code:
class BestWorst_Rules extends GameRules;

var Controller m_bestPlayer;
var Controller m_worstPlayer;

var PointLightComponent m_bestPlayerLight;
var PointLightComponent m_worstPlayerLight;

var int m_bonus;
var int m_malus;

var color redColor;
var color greenColor;

defaultproperties
{
m_bonus = 2
m_malus = 2

redColor = (R=255,G=0,B=0,A=255);
greenColor = (R=0,G=255,B=0,A=255);
}

Well, that's not too nasty. Next thing we're going to do, is to determine when a player is killed - and for that we have the ScoreKill function, which is called everytime a player is killed. We're also going to invent some new function to contain most of our logic (so our main function doesn't get huge!) as we write our code, so watch out for those in red.

uscript code:
function ScoreKill( Controller Killer, Controller Killed )
{
local int killerId, killedId, bestPlayerId, worstPlayerId;

killerId = Killer.playerReplicationInfo.PlayerId;
killedId = Killed.playerReplicationInfo.PlayerId;
bestPlayerId = getBestPlayerId( );
worstPlayerId = getWorstPlayerId( );

//check if the killed player is the worst player => malus
if(killedId == worstPlayerId)
{
setMalus(killer);
}

//check if the killed player is the best player = >bonus
if(killedId == bestPlayerId)
{
setBonus(killer);
}

//check if the killer was the worst player => bonus
if(killerId == bestPlayerId)
{
setBonus(killer);
}

//update status best and worst player
updateControllersStatus(killedId);

if ( NextGameRules != None )
{
NextGameRules.ScoreKill(Killer,Killed);
}
}

So what happens? This is all quite easy. First things first, we want to get the Ids of both the player that is killed, and the player that kills him. We then also want to find the Ids of the 'best' and 'worst' players. We check to see if if the killed player is then the worst or the best, and set bonuses accordingly, and we also check if the killer is the best player and offer a bonus too. Finally we update the controllers based on the best and worst player, and check to see if there are any more Game Rules to be executed.

Next we need to write those sub functions. Let's get the best and worst player Ids. The best player can be the player with the highest score, but the least deaths, and the worst player can be the one with the lowest score and the most deaths.

uscript code:
function int getBestPlayerId()
{
local int i;
local PlayerReplicationInfo bestPlayer, currentPlayer;
for(i=0;i
{
currentPlayer = worldInfo.GRI.PRIArray[i];
if(i==0)
bestPlayer = currentPlayer;

if(bestPlayer!=none &amp;&amp; bestPlayer.score < currentPlayer.score)
bestPlayer = currentPlayer;

else if(bestPlayer!=none &amp;&amp; bestPlayer.score == currentPlayer.score)
{
if(bestPlayer.deaths > currentPlayer.deaths)
bestPlayer = currentPlayer;
}

}
return bestPlayer.playerId;
}

function int getWorstPlayerId()
{
local int i;
local PlayerReplicationInfo worstPlayer, currentPlayer;
for(i=0;i
{
currentPlayer = worldInfo.GRI.PRIArray[i];
if(i==0)
worstPlayer = currentPlayer;

if(worstPlayer!=none &amp;&amp; worstPlayer.score > currentPlayer.score )
worstPlayer = currentPlayer;

else if(worstPlayer!=none &amp;&amp; worstPlayer.score == currentPlayer.score)
{
if(worstPlayer.deaths < currentPlayer.deaths)
worstPlayer = currentPlayer;
}
}
return worstPlayer.playerId;
}

Both functions essentially work the same way, so let's dissect getBestPlayerId, and I'll let you work out the rest. We get the GameReplicationInfo (GRI) from the worldinfo, and from that we can get an array of PlayerReplicationInfos (PRI) for every player in the game.

We cycle through this array, and on the first pass, we check to see if the beat player is the current player we're looking at. On successive passes, we check to see if there is a best player, and if their score is less than the current player we're looking at - if so, the best player is actually the current player. Otherwise, we check to see if there is a best player, and if the best player's score is the same as the current player we're looking at, then we compare their deaths. When we're finished checking through every player, then we return the playerId of the best player.

Read it through a few times and wrap your head around it, it's not as horrible as it sounds.

We also need to be able to set the player score relative to the bonus and the malus. This is really easy.We'll set the bonus points for the killing the best player to 2, and we'll set the malus points for killing the worst points to 2 as well - get 2 bonus points or lose 2 extra points.

uscript code:
function setBonus(Controller c)
{
c.PlayerReplicationInfo.score += m_bonus;
c.PlayerReplicationInfo.bForceNetUpdate = true;
c.PlayerReplicationInfo.Kills+= m_bonus;
}

function setMalus(Controller c)
{
c.PlayerReplicationInfo.score -= m_malus;
c.PlayerReplicationInfo.bForceNetUpdate = true;
c.PlayerReplicationInfo.Kills -= m_malus;
}
 

In these functions, we take the controller and find the score in the PRI. From there, we change their scores, force these to be updated on all clients and then update their kills.

Next we'll write the function that updates the controller status.
 

uscript code:
function updateControllersStatus(int killedId)
{
local Controller controller;
local int bestPlayerId, worstPlayerId, currentPlayerId;

bestPlayerId = getBestPlayerId();
worstPlayerId = getWorstPlayerId();

if(bestPlayerId == worstPlayerId)
return;

//detach previous light component
if(m_bestPlayer!=None &amp;&amp; m_bestPlayer.pawn != None)
m_bestPlayer.pawn.detachComponent(m_bestPlayerLight);

if(m_worstPlayer!=None &amp;&amp; m_worstPlayer.Pawn != None)
m_worstPlayer.pawn.detachComponent(m_worstPlayerLight);

m_bestPlayer = None;
m_worstPlayer = None;

//create the point light
if(m_bestPlayerLight == None)
m_bestPlayerLight = createPointLight(false);

if(m_worstPlayerLight == None)
m_worstPlayerLight = createPointLight(true);

//retrieve best and worst controller
ForEach DynamicActors(class'Controller', controller)
{
currentPlayerId = controller.PlayerReplicationInfo.PlayerId;

if(m_bestPlayer != None &amp;&amp; m_worstPlayer != None) //if best and worst found break
break;

if(currentPlayerId == bestPlayerId &amp;&amp; currentPlayerId != killedId)
m_bestPlayer = controller;

if(currentPlayerId == worstPlayerId &amp;&amp; currentPlayerId != killedId)
m_worstPlayer = controller;
}

//attach the light ccmponent
if(m_bestPlayer!=none &amp;&amp; m_bestPlayer.Pawn != None)
m_bestPlayer.pawn.attachComponent(m_bestPlayerLight);

if(m_worstPlayer!=none &amp;&amp; m_worstPlayer.pawn!=None)
m_worstPlayer.pawn.attachComponent(m_worstPlayerLight);
}

This is quite a long one, but it's relatively incomplex. First things first, we get the best and worst playerIds. If the best player is the worst player, then there's no point doing anything, so we just return and do nothing.

We then remove any lights that we might have already attached from the previous best and worst players (we don't want players marked incorrectly!) and create a new point light for the current best and worst players. We cycle through the controllers, find the best and worst controller based on playerId (and if the same, just break and end the function), and finally actually attach the lights.

What's this in red? We haven't written a point light function yet!

uscript code:
function PointLightComponent createPointLight(bool red)
{
  local PointLightComponent plc;

  local class plcClass< SEMI >

  plcClass = class'Engine.PointLightComponent';
  plc = new (self)plcClass< SEMI >

plc.Radius=250;
plc.SetEnabled(true);
if(red)
plc.SetLightProperties(1000, redColor);
else
plc.SetLightProperties(1000, greenColor);
return plc;
}

Nice an simple. We just create a new PointLightComponent, set it's radius, brightness and colour, enable it and return the light. That's it! We're done. Compile and try and stay in the green!

Post comment Comments
mikep
mikep Mar 2 2008, 5:48pm says:

Thanks for the tutrial first off.

I think your for loops got clipped in your editing in getBestPlayerId() and getWorstPlayerId()

Shouldn't it be something like for(i=0;i < WorldInfo.Game.NumPlayers; i ++)

Also there are some missing semi-colons in the default propertis after assigning 2 to m_bonus and m_malus.

I had to typecast var class<GameRules> myClass; to get the WorldInfo.Game.AddGameRules(GRClass); to take the argument as well.

Can you put your files up?

+1 vote     reply to comment
fuNGoo
fuNGoo Mar 10 2008, 5:04pm replied:

First of all, I'd like to say how appreciative I am of the time you put into these great tutorials for beginners. However, it would a lot more helpful if the code was explained more thoroughly for our feeble minds.

Personally I'm having trouble remembering which functions and classes are the ones we're creating, and the ones already there for us to use. Perhaps you could create a primer tutorial like your third tutorial "Delving Deeper into UnrealScript\" except giving us an overview of common functions and classes we use often and explaining how they work or their usefulness, etc.

Or maybe a primer exists already that I'm not aware of? I know of the UnrealWiki but that database of information without actually applying to an exercise makes it hard for me to grasp the concepts.

Anyway just some (hopefully constructive) criticism to help others like me understand and learn better.

+1 vote     reply to comment
fuNGoo
fuNGoo Mar 10 2008, 5:15pm replied:

EDIT: Oops didn't mean to reply to you with my first post, mikep.

But it seems ambershee didn't address your last problem with the typecasting GameRules class. I'm also having that issue.

+1 vote     reply to comment
ambershee
ambershee Mar 10 2008, 5:53pm replied:

Yeah, looked into it. You should use
var class<GameRules> GRClass;

I'm not sure why it compiles for me, but it shouldn't. Updated tutorial.

+1 vote     reply to comment
ambershee
ambershee Mar 3 2008, 5:47am says:

Well spotted, the loops have indeed been truncated. I'll fix that right away.

Also, you do not put parsing braces (semi colons) in default properties. I won't put the files up, because all the code should be right here :)

+1 vote     reply to comment
ambershee
ambershee Mar 3 2008, 5:51am says:

Fixed. Loops are:

for(i=0;i<worldInfo.GRI.PRIArray.length;i++)
{
//code
}

+1 vote     reply to comment
fuNGoo
fuNGoo Mar 10 2008, 7:47pm says:

Hmm... seems I have another issue. In the PointLightComponent function, at the line:

plc = new (self)plcClass;

The compile window is telling me: Error, Type mismatch in '='

+1 vote     reply to comment
ambershee
ambershee Mar 12 2008, 9:29am says:

I've spotted precisely why that is - the text editor has treated something like an HTML tag when clearly it isn't (I've had no end of trouble with it).

the line that appears to read 'local class plcClass' should be 'local class<PointLightComponent> plcClass'.

+1 vote     reply to comment
ambershee
ambershee Mar 12 2008, 9:35am replied:

Ack. This is so frustrating. It cannot and will not accept the correct line, despite formatting, or even writing it into HTML.

+1 vote     reply to comment
fuNGoo
fuNGoo Mar 13 2008, 7:11pm says:

Cool, works now. Sucks though that the text formatting is causing so many typos and frustration. All that's left is for me to read through it all and try to understand what's going on.

+1 vote     reply to comment
ChainsawFilms
ChainsawFilms Apr 25 2008, 6:51am says:

I've altered the code with the new line 'local class<PointLightComponent> plcClass' but now I'm getting plc errors when I compile. Any chance the source code be downloaded?

I wsa looking at learning some uscript and was thinking of doing a mutator with more points for killing the best player - and then I come across this tutorial, so it's perfect LOL :)

Thanks!

+1 vote     reply to comment
p3gamer
p3gamer Jun 6 2008, 9:30pm says:

//check if the killer was the worst player => bonus
if(killerId == bestPlayerId)
{
setBonus(killer);
}

Should it be:

"(killerId == worstPlayerId)" since we are rewarding the worst player?

In the GameInfo.uc class, you are awarded 1 point for killing someone no matter what, since this GameRule is applied/called later in the ScoreKill() via the "GameRulesModifiers.ScoreKill(Killer, Other);". So if your malus/penalty is 2, you will actually lose only 1 point. (at least in my case)

Has anyone tried testing this mutator when you have only 2 players? I have been getting strange logical issues. Ex. both players score and deaths = 0, so after the I kill the other player(first kill of the match), I tend to lose points, also it would make sense that one player will be the best player and the other will be the worst most of the time, but at times one player is green or red, and the other player is not.

I am working on modifying the if statements/logic


+1 vote     reply to comment
p3gamer
p3gamer Jun 6 2008, 11:28pm says:

The if statemensts/logic look fine to me. I found that this mutator works better when there are more than 2 players. Also, I figured out that when the score is tied (ex. 0 to 0), you get 1 point for killing your opponent, but then the new game rules sees that the score is now (1 to 0), so you get penalized for killing first which then makes you the worst player. I believe that creating a class that extends the GameInfo.uc and modifying/overriding the scoreKill() function in that class, will fix the minor issues.

Thanks, I learned a lot from digging into this mutator

+1 vote     reply to comment
ambershee
ambershee Jun 7 2008, 6:53am says:

Thanks for the input - I'll look into it all =]

+1 vote     reply to comment
axlefoley
axlefoley Apr 17 2010, 3:38am says:

Hello I have done the tutorial and it compiles fine but it doesn't seem to do much, the players don't change colour the points seem to only go up by 1 rather than what ever

+1 vote     reply to comment
axlefoley
axlefoley Apr 17 2010, 3:50am says:

accidently double posted

+1 vote     reply to comment
Post a Comment
click to sign in

You are not logged in, your comment will be anonymous unless you join the community today (totally free - or sign in with your social account on the right) which we encourage all contributors to do.

2000 characters limit; HTML formatting and smileys are not supported - text only

Established
Nov 9, 2007
Privacy
Public
Subscription
Must apply to join
Contact
Send Message
Email
Members Only
Membership
Join this group
Group Watch
Track this company
Tutorial
Browse
Tutorials
Report Abuse
Report article
Related Games
Unreal Tournament 3
Unreal Tournament 3 Single & Multiplayer First Person Shooter
Related Engines
Unreal Engine 3
Unreal Engine 3 Commercial Released Aug 31, 2007
Related Groups
Epic Games
Epic Games Developer & Publisher
Shee Labs Game Development
Shee Labs Game Development Developer & Publisher with 14 members
Unreal Tournament 3 Mod Developers
Unreal Tournament 3 Mod Developers Fans & Clans group with 234 members