Sweavo/MutYouDontQuitThatEasy
The mutator has a reference to a ScoreHolder which subclasses actor. On prebeginplay it creates one called dummy and sets its playerID to 0.
23/02/2006 Bug found
It has been intermittently giving people a score when they join the server. I think this is due to different players sharing a playerhash. Try GameStats.GetStatsIdentifier() instead.
/***************************************************************************** * * $Id: MutYouDontQuitThatEasy.uc 87 2006-01-21 18:48:59Z sweavo $ * * Email: public at sweavo.34sp.com * * If a player quits and returns again, they keep their score. Especially * useful if a mutator may cause negative scores, like the SASKillsMinusDeaths * *****************************************************************************/ class MutYouDontQuitThatEasy extends Mutator; var ScoreHolder Dummy; event PreBeginPlay() { if ( !MutatorIsAllowed() ) Destroy(); else if (bAddToServerPackages) AddToPackageMap(); Dummy=spawn(class'ScoreHolder'); } // When PlayerReplicationInfos are created, load their scores if available function bool CheckReplacement(Actor Other,out byte SuperRelevant) { if(TeamPlayerReplicationInfo(Other)!=None && PlayerController(Other.Owner)!=None) { //log("$Id: MutYouDontQuitThatEasy.uc 87 2006-01-21 18:48:59Z sweavo $ mutator calls load: "$PlayerReplicationInfo(Other).PlayerName$" - "$PlayerController(Other.Owner).GetPlayerIdHash()); Dummy.LoadScore(PlayerController(Other.Owner).GetPlayerIdHash(),TeamPlayerReplicationInfo(Other)); } return super.CheckReplacement(Other,SuperRelevant); } function NotifyLogout(Controller Exiting) { if (PlayerController(Exiting)!=None) { //log("$Id: MutYouDontQuitThatEasy.uc 87 2006-01-21 18:48:59Z sweavo $ mutator calls save: "$PlayerController(Exiting).PlayerReplicationInfo.PlayerName$" "$PlayerController(Exiting).PlayerReplicationInfo.PlayerId); Dummy.SaveScore(PlayerController(Exiting).GetPlayerIdHash(),TeamPlayerReplicationInfo(Exiting.PlayerReplicationInfo)); } } function Destroyed() { //log("$Id: MutYouDontQuitThatEasy.uc 87 2006-01-21 18:48:59Z sweavo $ method 'Destroyed' was called."); Super.Destroyed(); } defaultproperties { bHidden=true GroupName="Score Mods" FriendlyName="[SAS]You Don't Quit That Easy $Revision: 64 $" Description="If a player quits and returns, they keep their score. By sweavo for SAS http://wiki.beyondunreal.com/wiki/Sweavo http://www.sassniperclan.com/" }
When CheckReplacement spots a TeamPlayerReplicationInfo being created, it calls Dummy.LoadScore passing in the player's stats ID and the PRI itself. If the player's score is not stored, this has no effect on the PRI.
When NotifyLogout is called, Dummy.SaveScore is called with the same parameters.
/***************************************************************************** * * $Id: ScoreHolder.uc 87 2006-01-21 18:48:59Z sweavo $ * * Email: public at sweavo.34sp.com * * Part of MutYouDontQuitThatEasy * * There is one of these present for each player who has been in this level. * *****************************************************************************/ class ScoreHolder extends Actor; var string PlayerHash; var int Score; var int Deaths; var int Kills; var int Headcount; var ScoreHolder Next; function SaveScore(String Hash,TeamPlayerReplicationInfo PRI) { if (PlayerHash == Hash) { Score=PRI.Score; Deaths=PRI.Deaths; Kills=PRI.Kills; HeadCount=PRI.HeadCount; //log("$Id: ScoreHolder.uc 87 2006-01-21 18:48:59Z sweavo $ saved score of "$kills$"+"$headcount$"-"$deaths$"="$score$" for ID "$hash); } else { if (Next == None) { Next = Spawn(class'ScoreHolder');//TODO isn't this going to get tidied up unexpectedly? Next.PlayerHash=Hash; //log("$Id: ScoreHolder.uc 87 2006-01-21 18:48:59Z sweavo $ spawned new ScoreHolder for ID "$hash); Next.SaveScore(Hash,PRI); } else { //log("$Id: ScoreHolder.uc 87 2006-01-21 18:48:59Z sweavo $ save recursing "$hash); Next.SaveScore(Hash,PRI); } } } function LoadScore(string Hash, TeamPlayerReplicationInfo PRI) { if (PlayerHash == Hash) { PRI.Score=Score; PRI.Deaths=Deaths; PRI.Kills=Kills; PRI.HeadCount=HeadCount; //log("$Id: ScoreHolder.uc 87 2006-01-21 18:48:59Z sweavo $ loaded score of "$kills$"+"$headcount$"-"$deaths$"="$score$" for ID "$Hash); } else { if (Next == None) { //log("$Id: ScoreHolder.uc 87 2006-01-21 18:48:59Z sweavo $ no saved score for "$Hash); } else { //log("$Id: ScoreHolder.uc 87 2006-01-21 18:48:59Z sweavo $ load recursing "$Hash); Next.LoadScore(Hash,PRI); } } } defaultproperties { bHidden=true }
Recursively, if the ScoreHolder.PlayerID matches the stats id then it sets the score, kills, deaths, and headcount; else it recurses on its Next. If Next is nothing then it simply returns without loading anything into the PRI.
Savescore recurses in the same manner except if the Next==Nothing then it spawns a new ScoreHolder and sets its PlayerID to the passed-in Stats ID, then when it recurses one more time it will match, and save the score in that slot. By recursing one more time we keep all the actual save logic in one place. The recursion only happens when someone quits the game so it's hardly going to being the server to its knees
Since the ScoreHolders are all actors, they are destroyed at level end.
There may be a bug in the load: I don't know whether there are other reasons for a PRI to be created? If this happens the player's score will be loaded from the buffer (if present - i.e. if the player has quit at any stage). I can't imagine that PRIs are created at any time other than player joining the server, but hey.
Here's the source (and makefile, if you use cygwin): http://www.sweavo.34sp.com/misc/utmods/MutYouDontQuitThatEasy_87_src.tgz