//============================================================================== // Single Player Game Profile for storing the additional info // // Written by Michiel Hendriks // (c) 2003, 2004, Epic Games, Inc. All Rights Reserved //============================================================================== class UT2K4GameProfile extends UT2003GameProfile; /** used to keep track of future changes */ var protected int revision; var bool bDebug; var class UT2K4GameLadder; /** Progression in the ladders */ var array LadderProgress; /** The map that will be played, set by the "play" button */ var CacheManager.MapRecord ActiveMap; /** seed to select the random level */ var int AltPath; /** map selection record, this will override the default behavior of map selection for a match */ struct LevelPathRecord { /** the selected ladder */ var byte ladder; /** match in the selected ladder */ var byte rung; /** the offset in the AltLevel array */ var byte selection; }; /** the selected path */ var array LevelRoute; /** The actual team size Warning, absolute max size is 7 (set by GameProfile) Don't use this variable, call GetMaxTeamSize() instead */ var protected int MaxTeamSize; /** Statistics for each bot in this profile. Saved per profile because they change during the tournament */ struct BotStatsRecord { var string Name; /** updated price to 'buy' the player, this is also used to calculate the fee and injury treatment */ var int Price; /** injury stats, 0 = dead (you don't want that ;)) */ var byte Health; /** is this a free agent, e.g. can you hire him/her */ var bool FreeAgent; /** location of his team in the TeamStats list */ var int TeamId; }; var array BotStats; /** Stats for each team */ struct TeamStatsRecord { /** the fully qualified name of the team class */ var string Name; /** the difficulty level of this team */ var int Level; /** number of games played against this team */ var int Matches; /** number of games this team won from you */ var int Won; /** the rating of this team compared to you */ var float Rating; }; var array TeamStats; /** The current Player Balance */ var int Balance; /** the minimal balance */ var int MinBalance; /** total ingame time */ var float TotalTime; /** Set to 'true' when the SP has been completed */ var bool bCompleted; /** the best team played against, used for championship */ var string FinalEnemyTeam; /** Player used a cheat */ var protected bool bCheater; /** if set to true users can't unlock characters with this profile */ var protected bool bLocked; /** prevent some cheating */ var protected string PlayerIDHash; /** Team percentage modifier This value is multiplied with the fee for each player, lower value = lower take of the earnings */ var float TeamPercentage; /** Percentage of the Match price that counts as bonus (per player) **/ var float MatchBonus; /** The percentage of the team players fee to pay out when the team loses */ var float LoserFee; /** Every time a bot played a match and won his fee increases with this */ var float FeeIncrease; /** The chance a team player get's injured */ var float InjuryChance; /** the offset in the BotStats array of the last injured player */ var int LastInjured; /** injury treatment modifier */ var float InjuryTreatment; /** Chance the losing enemy team will challenge you for a rematch */ var float ChallengeChance; // Last match details -- /** if set to true the details are new and the detail window should be displayed */ var bool lmdFreshInfo; /** player details from the last match */ struct PlayerMatchDetailsRecord { var int ID; var string Name; var float Kills; var float Score; var float Deaths; var int Team; /** special awards won by this player, like Flag Monkey */ var array SpecialAwards; }; /** per player details */ var array PlayerMatchDetails; /** total change in the balance, includes payment and bonuses */ var int lmdBalanceChange; /** true if the player won the match */ var bool lmdWonMatch; /** enemy team class */ var string lmdEnemyTeam; /** game type string */ var string lmdGameType; /** true if the last match was a challenge match */ var bool lmdbChallengeGame; /** map title */ var string lmdMap; /** total prizemoney */ var int lmdPrizeMoney; /** total bonus money */ var int lmdTotalBonusMoney; /** last spree count */ var int lmdSpree[6]; /** last multikill count */ var int lmdMultiKills[7]; /** player ID of injured player */ var int lmdInjury; /** health, stored here because player could have been healed */ var byte lmdInjuryHealth; /** injury treatment gost */ var int lmdInjuryTreatment; /** total match time */ var float lmdGameTime; /** true if it was a team game */ var bool lmdTeamGame; /** teamID of the player's team (usualy 1) */ var int lmdMyTeam; struct PayCheckRecord { var int BotId; var int Payment; }; /** payment overview of you team mates */ var array PayCheck; struct PhantomMatchRecord { var int Team1; // team id in the team stats array var int Team2; var float ScoreTeam1; var float ScoreTeam2; var int LadderId; var int MatchId; var float GameTime; }; /** phantom match overview */ var array PhantomMatches; // -- Last match details struct TeamMateRankingRecord { var int BotID; var float Rank; }; /** Used for payment and injuries, only valid during RegisterGame() */ var array TeamMateRanking; /** Phantom Teams to choose from */ var array PhantomTeams; /** when true use ChallengeInfo for information */ var bool bIsChallenge; /** the challenge match info, obsolete? */ var UT2K4MatchInfo ChallengeInfo; /** will be set to the challenge game class used if bIsChallenge = true*/ var class ChallengeGameClass; /** true we we got challenged, false if we took the initiative */ var bool bGotChallenged; /** contains data about the challenge variables, used for fighthistory */ var string ChallengeVariable; /** When set override the default LoginMenuClass with this one */ var string LoginMenuClass; /** @ignore */ var name LogPrefix; // more all time stats -- var int Spree[6]; var int MultiKills[7]; var int SpecialAwards[6]; // -- more all time stats /** bonus received for each killing spree */ var int SpreeBonus[6]; /** bonus received for each multikill */ var int MultiKillBonus[7]; /** Special Awards labels */ var localized string msgSpecialAward[6]; /** special award levels */ var int sae_flackmonkey, sae_combowhore, sae_headhunter, sae_roadrampage, sae_hattrick, sae_untouchable; var localized string msgCheater, msgCredits, msgCredit; /** The fee you have to pay when you forfeit the game */ var protected float ForFeitFee; /** percentage of the match prize a map challenge costs */ var float MapChallengeCost; struct FightHistoryRecord { /** yyyy-mm-dd */ var int Date[3]; /** hh:mm */ var int Time[2]; /** magic string, fields seperated by ';'
field 1: match type (Ladder game, challenge game, custom ladder game)
field 2: match description */ var string MatchData; /** additional data, depends on the MatchData type */ var string MatchExtra; /** map name */ var string Level; /** gametype class, fully qualified */ var string GameType; /** enemy team class, fully qualified */ var string EnemyTeam; /** price money */ var float PriceMoney; /** balance change */ var float BalanceChange; /** bonus money won */ var float BonusMoney; /** game time */ var float GameTime; var bool WonGame; /** team scores, 1 = our team, 0 = enemy team */ var float TeamScore[2]; /** gee, what would this be then */ var bool TeamGame; /** team layout */ var string TeamLayout[2]; /** player's score in this game */ var float MyScore; /** number of kills */ var int MyKills; /** number of deaths */ var int MyDeaths; /** my awards */ var string MyAwards; /** final position in the scoring table */ var byte MyRating; }; /** the history of all fights */ var array FightHistory; /** localized strings used for the fighthistory */ var localized string msgChallengeGame, msgAdditionalLadder, msgMatch, msgLadderGame, msgChampionship; /** team roster override record */ struct TeamRosterRecord { /** fully qualified name of the team roster class */ var string name; /** the alternative roster */ var array roster; }; /** team roster override records */ var array AltTeamRoster; /** information about a custom ladder */ struct CustomLadderRecord { /** the fully qualified name to the ladder class */ var string LadderClass; /** current progress */ var int progress; }; /** custom ladders */ var array CustomLadders; /** will be set when a custom ladder has been played, and unset when not */ var class LastCustomCladder; /** if true match details will be shown after a match */ var bool bShowDetails; /** call this when you create a new profile, this will set the protection data */ function CreateProfile(PlayerController PlayerOwner) { if (PlayerIDHash == "") PlayerIDHash = PlayerOwner.GetPlayerIDHash(); } /** call this when you load the profile, it will lock the profile when the PlayerIDHash has changed */ function LoadProfile(PlayerController PlayerOwner) { if (PlayerIDHash != PlayerOwner.GetPlayerIDHash()) { bLocked = true; PlayerIDHash = PlayerOwner.GetPlayerIDHash(); } } /** initialize the profile, called at the beginning of each game */ function Initialize(GameInfo currentGame, string pn) { local Controller C; if (UT2K4GameLadder == none) { UT2K4GameLadder = class(DynamicLoadObject(GameLadderName, class'Class')); GameLadder = UT2K4GameLadder; } PackageName=pn; UpgradeGP(); // set character, player in current game for ( C=currentGame.Level.ControllerList; C!=None; C=C.NextController ) { if ( PlayerController(C) != None ) { currentGame.ChangeName (PlayerController(C), PlayerName, false); break; } } NextMatchObject=None; ChampBorderObject=None; Playerteam.length = GetMaxTeamSize(); // make sure it's never larger than this if ((DeathMatch(currentGame) != none) && (LoginMenuClass != "")) { DeathMatch(currentGame).LoginMenuClass = LoginMenuClass; } //if (Balance < MinBalance) Balance = MinBalance; } /** Called on gamerestart */ function ContinueSinglePlayerGame(LevelInfo level, optional bool bReplace) { local Controller C; local PlayerController PC; // set character, player in current game PC = none; for ( C=level.ControllerList; C!=None; C=C.NextController ) { if ( PlayerController(C) != None ) { PC = PlayerController(C); break; } } if ( PC == none ) { return; } //if (Balance < MinBalance) Balance = MinBalance; if (!level.game.SavePackage(PackageName)) { Warn("level.game.SavePackage("@PackageName@") FAILED."); } bIsChallenge = false; PC.ConsoleCommand("disconnect"); } function CheatSkipMatch(GameInfo CurrentGame) { local UT2K4MatchInfo MI; MI = UT2K4MatchInfo(GetMatchInfo(CurrentLadder, CurrentMenuRung)); Balance = Balance + ForfeitFee + MI.PrizeMoney;; Super.CheatSkipMatch(CurrentGame); } /** After a game is completed, this function should be called to record the player's statistics and update the ladder. Currently called from Deathmatch 'MatchOver' state */ function RegisterGame(GameInfo currentGame, PlayerReplicationInfo PRI) { local UT2K4MatchInfo MI; local int i, j, OldBalance; local float TeamRating; local array PRIarray; local bool TempbIsChallenge, doPayTeamMates; local string tmp; MI = UT2K4MatchInfo(GetMatchInfo(CurrentLadder, CurrentMenuRung)); Balance += ForFeitFee; // payback forfeitfee OldBalance = Balance; PayCheck.length = 0; SpecialEvent = ""; TempbIsChallenge = bIsChallenge; if (bIsChallenge && ChallengeGameClass != none) ChallengeGameClass.static.PreRegisterGame(self, currentGame, PRI); Kills += PRI.Kills; Goals += PRI.GoalsScored; Deaths += PRI.Deaths; Matches++; TotalTime += currentGame.Level.TimeSeconds-currentGame.StartTime; lmdTotalBonusMoney = 0; LastInjured = -1; PayCheck.length = 0; PlayerMatchDetails.length = 0; if (TeamPlayerReplicationInfo(PRI) != none) { for (i = 0; i < 6; i++) { Spree[i] += TeamPlayerReplicationInfo(PRI).Spree[i]; lmdSpree[i] = TeamPlayerReplicationInfo(PRI).Spree[i]; lmdTotalBonusMoney += TeamPlayerReplicationInfo(PRI).Spree[i]*SpreeBonus[i]; } for (i = 0; i < 7; i++) { MultiKills[i] += TeamPlayerReplicationInfo(PRI).MultiKills[i]; lmdMultiKills[i] = TeamPlayerReplicationInfo(PRI).MultiKills[i]; lmdTotalBonusMoney += TeamPlayerReplicationInfo(PRI).MultiKills[i]*MultiKillBonus[i]; } } Balance += lmdTotalBonusMoney; if (CurrentLadder != UT2K4GameLadder.default.LID_DM) { i = GetTeamPosition(EnemyTeam, true); TeamStats[i].Matches++; if (!bWonMatch) TeamStats[i].Won++; TeamRating = 0; if (currentGame.bTeamGame) { // Rating is: team score / other team score * (team won match) // Rating will increase faster than it will decrease TeamRating = (TeamGame(currentGame).Teams[(PRI.Team.TeamIndex+1) % 2].Score+1.0)/(TeamGame(currentGame).Teams[PRI.Team.TeamIndex].Score+1.0); RankTeamMates(currentGame, PRI); if (TempbIsChallenge && ChallengeGameClass != none) doPayTeamMates = ChallengeGameClass.static.injureTeamMate(self); else doPayTeamMates = true; if (doPayTeamMates) InjureTeamMate(); } else { // won from the whole team currentGame.GameReplicationInfo.GetPRIArray(PRIarray); for (j = 0; j < PRIarray.length; j++) { if ((PRIarray[j] != PRI) && (TeamRating < PRIarray[j].Score)) { TeamRating = PRIarray[j].Score; } } TeamRating /= (PRI.Score+1.0); } if (bDebug) log("TeamRating ="@TeamRating, LogPrefix); if (bWonMatch) TeamRating *= -1; // enemy lost to rating is negative TeamStats[i].Rating += TeamRating; } if (currentGame.bTeamGame && !bIsChallenge /*&& (completedLadder(class'UT2K4LadderInfo'.default.LID_TDM))*/) { /* rating -3/4 -- increase chance -2/4 -- decrease chance -1/4 -- increase chance 4/3 ->3/4 -- increase chance 4/2 ->3/2 -- decrease chance 4/1 ->1/4 -- decrease chance */ if (TeamRating > 0) TeamRating = 0.5/TeamRating; else if (TeamRating > -0.5) TeamRating = -1-TeamRating; TeamRating = 0.5+abs(TeamRating); if (bDebug) Log("Challenge chance ="@TeamRating@(ChallengeChance*TeamRating)); if ((frand() < ChallengeChance*TeamRating )) { // challene enemyteam game tmp = string(GetChallengeGame()); if (tmp != "") SpecialEvent = "CHALLENGE"@EnemyTeam@tmp$";"$SpecialEvent; // make first because of possible message window } } if (TempbIsChallenge && ChallengeGameClass != none) doPayTeamMates = ChallengeGameClass.static.payTeamMates(self); else doPayTeamMates = true; if ( bWonMatch ) { SpecialEvent $= ";"$GameLadder.static.UpdateLadders(self,CurrentLadder); // updates LadderRungs appropriately Wins++; // calculate new balance Balance += MI.PrizeMoney; if (currentGame.bTeamGame && doPayTeamMates) PayTeamMates(MI.PrizeMoney, TeamPercentage, (TeamMateRanking.length/2), FeeIncrease); // increase fee of 50% of the best players } else { // team mates take an percentage of the current balance if (currentGame.bTeamGame && doPayTeamMates) PayTeamMates(Balance, LoserFee, (TeamMateRanking.length/3), (FeeIncrease/2)); } // Stats -- lmdBalanceChange = Balance-OldBalance; procLastMatchDetails(currentGame, PRI, MI); AddHistoryRecord(currentGame, PRI, MI); // bIsChallenge must not been reset // only create phantom matches when we entered the tournament bIsChallenge = false; // to get proper match info if (completedLadder(UT2K4GameLadder.default.LID_DM)) procPhantomMatches(ceil(float(TeamStats.length-1)/2.0 * frand())); // -- Stats if (TempbIsChallenge && ChallengeGameClass != none) ChallengeGameClass.static.PostRegisterGame(self, currentGame, PRI); if (Balance < MinBalance) { Balance = MinBalance; SpecialEvent $= ";DONATION"; } bWonMatch = false; } /** Send the player to the next match in the given ladder */ function StartNewMatch(int PickedLadder, LevelInfo CurrentLevel) { local Controller C; local string NewURL; local int i; local bool doCancelFee; lmdFreshInfo= false; bWonMatch = false; bInLadderGame = true; CurrentLadder = PickedLadder; if (PickedLadder >= 10) LastCustomCladder = UT2K4GameLadder.default.AdditionalLadders[PickedLadder - 10]; else LastCustomCladder = none; if (!bIsChallenge) ChallengeGameClass = none; NewURL = GameLadder.static.MakeURLFor(self); EnemyTeam = GetEnemyTeamName(EnemyTeam); // find enemy team, if needed if (bDebug) Log("Selected EnemyTeam ="@EnemyTeam, LogPrefix); if (!bIsChallenge) SpecialEvent = ""; // challenge class will/should do this doCancelFee = true; if (bIsChallenge && (ChallengeGameClass != none)) doCancelFee = ChallengeGameClass.static.payTeamMates(self); // calculate lamer forfeitfee TeamMateRanking.length = 0; if (doCancelFee) { for (i = 0; i < GetNumTeammatesForMatch(); i++) { TeamMateRanking.length = TeamMateRanking.length+1; TeamMateRanking[TeamMateRanking.length-1].BotID = GetBotPosition(PlayerTeam[PlayerLineup[i]]); } ForFeitFee = PayTeamMates(Balance, LoserFee); if (bDebug) log("ForFeitFee ="@ForFeitFee); } else ForFeitFee = 0; CurrentLevel.Game.SavePackage(PackageName); // open game for ( C=currentLevel.ControllerList; C!=None; C=C.NextController ) { if ( PlayerController(C) != None ) { PlayerController(C).ConsoleCommand("START"@NewURL); return; } } Warn("No local player controller found"); } /** Will be called in case of a cheat */ function ReportCheat(PlayerController Cheater, string cheat) { local string s; s = msgCheater; if (Cheater != none) { Cheater.ClearProgressMessages(); Cheater.SetProgressTime(6); Cheater.SetProgressMessage(0, s, class'Canvas'.Static.MakeColor(255,0,0)); } if (cheat != "") s @= "Player used cheat:"@cheat; Log(s, LogPrefix); bCheater = true; } /** return true of the Cheat flag has been set */ function bool IsCheater() { return bCheater; } /** never allow team change unless we're god */ function bool CanChangeTeam(Controller Other, int NewTeam) { if (Other.bGodMode) return true; return ( !Other.Level.Game.GameReplicationInfo.bMatchHasBegun && (Other.PlayerReplicationInfo != None) && (Other.PlayerReplicationInfo.Team == None) ); } /** Overwritten to get alt matches */ function MatchInfo GetMatchInfo(int ladder, int rung) { local int i; if ( bIsChallenge ) return ChallengeInfo; if ( UT2K4GameLadder != none ) { i = GetSelectedLevel(ladder, rung); if (i > -1) return UT2K4GameLadder.static.GetUT2K4MatchInfo(ladder, rung, i, true); else return UT2K4GameLadder.static.GetUT2K4MatchInfo(ladder, rung, AltPath); } else { Warn("UT2K4GameLadder == none"); return none; } } /** return a selected UT2K4MatchInfo record */ function UT2K4MatchInfo GetSelectedMatchInfo(int ladder, int rung, int selection, optional bool bOrig) { if ( UT2K4GameLadder != none ) { if (bOrig) return UT2K4GameLadder.static.GetUT2K4MatchInfo(ladder, rung, AltPath); else return UT2K4GameLadder.static.GetUT2K4MatchInfo(ladder, rung, selection, true); } Warn("UT2K4GameLadder == none"); return none; } /** check if there is an alternative level */ function bool HasAltLevel(int ladder, int rung) { if ( UT2K4GameLadder != none ) return UT2K4GameLadder.static.HasAltLevel(ladder, rung); Warn("UT2K4GameLadder == none"); return false; } /** get the alternative match ID */ function byte GetAltLevel(int ladder, int rung) { if ( UT2K4GameLadder != none ) return UT2K4GameLadder.static.GetAltLevel(ladder, rung, AltPath, GetSelectedLevel(ladder, rung)); Warn("UT2K4GameLadder == none"); return -1; } /** Get the friendly game type name */ function string GetMatchDescription() { local int i; local array gr; if ( bIsChallenge ) { class'CacheManager'.static.GetGameTypeList(gr); for (i = 0; i < gr.length; i++) { if (gr[i].classname ~= ChallengeInfo.GameType) return gr[i].GameName; } return ""; } return GameLadder.static.GetMatchDescription(self); } /** return number of teammates needed for currently selected match assumes player team always gets an odd player */ function int GetNumTeammatesForMatch() { local MatchInfo M; if ( bIsChallenge ) M = ChallengeInfo; else M = GameLadder.static.GetCurrentMatchInfo(self); return GetNumTeammatesForMatchInfo(m); } /** return number of teammates needed for the provided assumes player team always gets an odd player */ function int GetNumTeammatesForMatchInfo(MatchInfo M) { if ( !IsTeamGametype(M.GameType) ) return 0; else return M.NumBots / 2; } /** return true if it's a team game type */ function bool IsTeamGametype(string gametype) { local CacheManager.GameRecord GR; if (gametype == "") return false; GR = class'CacheManager'.static.GetGameRecord(gametype); return GR.bTeamGame; } /** return the length of a selected ladder */ function int LengthOfLadder(int LadderId) { if (GameLadder != none) { return GameLadder.static.LengthOfLadder(LadderId); } else { Warn("PC Load Letter"); return -1; } } /** return the maximum size of a team that you manage */ function int GetMaxTeamSize() { return MaxTeamSize; } /** Enable a ladder, this makes sure a ladder is not reset Special case, ladderid=-1 this will set all ladders except the championship */ function enableLadder(int ladderId) { if (ladderId > LadderProgress.length) return; if (ladderId == -1) { enableLadder(UT2K4GameLadder.default.LID_DM); // DM enableLadder(UT2K4GameLadder.default.LID_TDM); // TDM enableLadder(UT2K4GameLadder.default.LID_CTF); // CTF enableLadder(UT2K4GameLadder.default.LID_BR); // BR enableLadder(UT2K4GameLadder.default.LID_DOM); // DDOM enableLadder(UT2K4GameLadder.default.LID_AS); // AS } else { if (LadderProgress[ladderId] == -1) LadderProgress[ladderId] = 0; } } /** get the progres of a custom ladder */ function int GetCustomLadderProgress(string LadderName) { local int i; i = GetCustomLadder(LadderName); if (i == -1) return -1; return CustomLadders[i].progress; } /** set the progress of a custom ladder */ function SetCustomLadderProgress(string LadderName, int increase) { local int i; i = GetCustomLadder(LadderName); if (i == -1) return; CustomLadders[i].progress += increase; } /** add a new custom ladder */ function RegisterCustomLadder(string LadderName) { if (GetCustomLadder(LadderName) > -1) return; CustomLadders.length = CustomLadders.length+1; CustomLadders[CustomLadders.length-1].LadderClass = LadderName; CustomLadders[CustomLadders.length-1].progress = 0; } /** return the id of a custom ladder */ function int GetCustomLadder(string LadderName) { local int i; for (i = 0; i < CustomLadders.length; i++) { if (CustomLadders[i].LadderClass ~= LadderName) return i; } return -1; } /** add teammate to the next available position on the team return false if not added because already on team or no room assumes it's a legal player record */ function bool AddTeammate(string botname) { local int i; if ( botname == "" ) return false; for ( i=0; i < GetMaxTeamSize(); i++ ) { if ( i >= PlayerTeam.Length || PlayerTeam[i] == "" ) { Playerteam[i] = botname; i = GetBotPosition(botname, true); BotStats[i].FreeAgent = false; BotStats[i].TeamId = -1; if (bDebug) log("Added team mate:"@botname, LogPrefix); return true; } if ( PlayerTeam[i] ~= botname ) { // already on team return false; } } return false; // never found space } /** remove teammate from the team return false if not removed because not on team */ function bool ReleaseTeammate(string botname) { local int i, j; if ( botname == "" ) return false; for ( i=0; i < GetMaxTeamSize(); i++ ) { if ( PlayerTeam[i] ~= botname ) { // player is on team, shuffle list for ( j=i; j= LengthOfLadder(LadderId); } /** return true when the ChampionshipLadder is within reach*/ function bool openChampionshipLadder() { return completedLadder(UT2K4GameLadder.default.LID_DOM) && completedLadder(UT2K4GameLadder.default.LID_CTF) && completedLadder(UT2K4GameLadder.default.LID_AS) && completedLadder(UT2K4GameLadder.default.LID_BR); } /** Rank team mates based on how they did in the last match */ function RankTeamMates(GameInfo Game, PlayerReplicationInfo Me) { local array PRI; local int i, j; local float Rank; local TeamMateRankingRecord temp; TeamMateRanking.length = 0; Game.GameReplicationInfo.GetPRIArray(PRI); // create index for (i = 0; i < PRI.length; i++) { if ((PRI[i].Team == Me.Team) && (PRI[i] != Me)) { Rank = (PRI[i].Kills+1)/(PRI[i].Deaths+1); TeamMateRanking.length = TeamMateRanking.length+1; TeamMateRanking[TeamMateRanking.length-1].BotID = GetBotPosition(PRI[i].PlayerName, true); TeamMateRanking[TeamMateRanking.length-1].Rank = Rank; } } // sort, since it's a small array, just use selection sort for (i = 0; i < TeamMateRanking.length; i++) { for (j = i+1; j < TeamMateRanking.length; j++) { if (TeamMateRanking[i].Rank < TeamMateRanking[j].Rank) // swap { temp = TeamMateRanking[i]; TeamMateRanking[i] = TeamMateRanking[j]; TeamMateRanking[j] = temp; } } } if (bDebug) { log("Rand team mates", LogPrefix); for (i = 0; i < TeamMateRanking.length; i++) { log(i$"]"@BotStats[TeamMateRanking[i].BotID].Name@TeamMateRanking[i].Rank); } } } /** Pay out the participating team mates and increase their fee A percentage of the match earnings is added/removed from their fee IncreaseFeeOf = number of team mates to pay updateFee = percentage to increase the fee Return the total payment */ function float PayTeamMates(int Money, float FeeModifier, optional int IncreaseFeeOf, optional float updateFee) { local int i, botid; local float fee, totalFee; totalFee = 0; PayCheck.length = TeamMateRanking.Length; for (i = 0; i < TeamMateRanking.Length; i++) { botid = TeamMateRanking[i].BotID; fee = GetBotPrice(, botid) * FeeModifier; // Bonus: MatchPrize * MaxMatchBonus * Rank fee += Money * MatchBonus * ((TeamMateRanking.Length - i) / TeamMateRanking.Length); PayCheck[i].BotId = BotId; PayCheck[i].Payment = Fee; if (bDebug) log("Paycheck: "$i$"]"@BotStats[PayCheck[i].BotID].Name@PayCheck[i].Payment); balance -= Fee; totalFee += Fee; //Log("UT2K4GameProfile::PayTeamMates() - "@PlayerTeam[PlayerLineup[i]]@"earns"@fee, LogPrefix); } // increase payment of IncreaseFeeOf best team mates IncreaseFeeOf = TeamMateRanking.length-Min(IncreaseFeeOf, TeamMateRanking.length); for (i = TeamMateRanking.length-1; i >= IncreaseFeeOf; i--) { GetBotPrice(, TeamMateRanking[i].BotId, updateFee); if (bDebug) log("Increase fee: "$i$"]"@BotStats[TeamMateRanking[i].BotID].Name@updateFee); } return totalFee; } /** Find the price of this player If increase > 1 increase the fee with that amouth if 1 > increase > 0 multiply the fee with that amouth if increase == 0 do nothing */ function int GetBotPrice(optional string botname, optional int botid, optional float increase, optional bool bAdd) { if (botname != "") botid = GetBotPosition(botname, bAdd); if ((botid == -1) || (botid > BotStats.length)) return -1; if (increase > 1) BotStats[botid].Price += increase; else if (increase > 0) BotStats[botid].Price *= (1+increase); return BotStats[botid].Price; } /** return the position of the bot in the botstats array */ function int GetBotPosition(string botname, optional bool bAdd) { local int i; for (i = 0; i < BotStats.length; i++) { if (BotStats[i].Name ~= botname) return i; } if (bAdd) { //log("UT2K4GameProfile::GetBotPosition() - adding bot:"@botname, LogPrefix); BotStats.length = i+1; BotStats[i].Name = botname; BotStats[i].Price = class'xUtil'.static.GetSalaryFor(class'xUtil'.static.FindPlayerRecord(botname)); BotStats[i].Health = 100; BotStats[i].FreeAgent = true; BotStats[i].TeamId = -1; return i; } return -1; } /** Find the worst team mate(s) and injure it Only one injury is supported */ function InjureTeamMate(optional int Number) { local int i; if (Number <= 0) Number = 1; Number = TeamMateRanking.length-Min(Number, TeamMateRanking.length); for (i = TeamMateRanking.length-1; i >= Number; i--) { if (FRand() > InjuryChance) continue; // injury at least 5% // injury not more than 75% // player did bad = rank is low (kills/deaths) BotStats[TeamMateRanking[i].BotID].Health = min(TeamMateRanking[i].Rank*25, 60)+25+(10*frand()); LastInjured = TeamMateRanking[i].BotID; if (bDebug) log("InjureTeamMate ="@BotStats[TeamMateRanking[i].BotID].Name@"got injured, health"@BotStats[TeamMateRanking[i].BotID].Health$"%", LogPrefix); } } /** Returns true when the player has a full team */ function bool HasFullTeam() { local int i; for (i = 0; i < GetMaxTeamSize(); i++) { if (PlayerTeam[i] == "") return false; } return true; } /** Return the team name based on a magic string A magic string starts with a ';', if not asume it's a real team name */ function string GetEnemyTeamName(string MagicString) { local array parts, teams; local class RGclass; local float X, mod; local int i, j; if (Left(MagicString, 1) != ";") return MagicString; if (Split(MagicString, ";", parts) < 3) { Warn("invalid magic string:"@MagicString); return ""; } RGclass = class(DynamicLoadObject(parts[1], class'Class')); if (RGclass == none) { Warn("Invalid team roster class:"@parts[1]); return ""; } teams.length = 0; // final is the best team but set statically for each profile at the end if ((parts[2] ~= "final") && (FinalEnemyTeam != "")) { return FinalEnemyTeam; } if ((parts[2] ~= "least") || (parts[2] ~= "most")) // least/most played with this team { if (parts[2] ~= "most") mod = -1; else mod = 1; X = mod*MaxInt; for (i = 0; i < RGclass.default.Rosters.length; i++) { j = GetTeamPosition(RGclass.default.Rosters[i], true); if ((TeamStats[j].Matches*mod) == X) // same, so add { teams[teams.length] = RGclass.default.Rosters[i]; } else if ((TeamStats[j].Matches*mod) < X) // reset list { X = TeamStats[j].Matches*mod; teams.length = 1; teams[0] = RGclass.default.Rosters[i]; } } } else if ((parts[2] ~= "best") || (parts[2] ~= "worst") || (parts[2] ~= "final")) // best/worst team you played against { if (parts[2] ~= "worst") mod = -1; else mod = 1; X = mod*MaxInt*-1; for (i = 0; i < RGclass.default.Rosters.length; i++) { j = GetTeamPosition(RGclass.default.Rosters[i], true); if ((TeamStats[j].Rating*mod) == X) // same, so add { teams[teams.length] = RGclass.default.Rosters[i]; } else if ((TeamStats[j].Rating*mod) > X) // reset list { X = TeamStats[j].Rating*mod; teams.length = 1; teams[0] = RGclass.default.Rosters[i]; } } } else if (parts[2] ~= "random") { teams = RGclass.default.Rosters; } else { Warn("unsupported magic string:"@parts[2]); } if (teams.length == 0) { X = rand(RGclass.default.Rosters.length); Warn("empty team list, will use:"@RGclass.default.Rosters[X]); return RGclass.default.Rosters[X]; } if (parts[2] ~= "final") // set final team { FinalEnemyTeam = teams[rand(teams.length)]; return FinalEnemyTeam; } return teams[rand(teams.length)]; // return a random team } /** Find the position of this team in the list */ function int GetTeamPosition(string teamname, optional bool bAdd) { local int j, i, k; local class tr; for (j = 0; j < TeamStats.length; j++) { if (TeamStats[j].Name ~= teamname) return j; } if (bAdd) // team not found, add it { //log("UT2K4GameProfile::GetTeamPosition() - adding team:"@teamname, LogPrefix); TeamStats.length = j+1; TeamStats[j].Name = teamname; TeamStats[j].Matches = 0; TeamStats[j].Won = 0; // Add ream bots tr = class(DynamicLoadObject(teamname, class'Class')); TeamStats[j].Level = tr.default.TeamLevel; for (i = 0; i < tr.default.RosterNames.length; i++) { k = GetBotPosition(tr.default.RosterNames[i], true); BotStats[k].TeamId = j; BotStats[k].FreeAgent = false; // default not free } return j; } return -1; } /** Generate stats for the last match, all except the balance change */ function procLastMatchDetails(GameInfo currentGame, PlayerReplicationInfo PRI, UT2K4MatchInfo MI) { local array PRIArray; local int i; lmdFreshInfo= true; lmdbChallengeGame = bIsChallenge; lmdTeamGame = currentGame.bTeamGame; lmdWonMatch = bWonMatch; lmdEnemyTeam = EnemyTeam; lmdGameType = currentGame.GameName; lmdGameTime = currentGame.Level.TimeSeconds-currentGame.StartTime; lmdMap = Left(string(currentGame.Level), InStr(string(currentGame.Level), ".")); if (PRI.Team != none) lmdMyTeam = PRI.Team.TeamIndex; else lmdMyTeam = 0; if (MI != none) { lmdPrizeMoney = MI.PrizeMoney; } else { lmdPrizeMoney = 0; } lmdInjury = LastInjured; if (lmdInjury > -1) { lmdInjuryHealth = BotStats[lmdInjury].Health; lmdInjuryTreatment = int(round((100-lmdInjuryHealth)*BotStats[lmdInjury].Price/100*InjuryTreatment)); } currentGame.GameReplicationInfo.GetPRIArray(PRIArray); PlayerMatchDetails.Length = PRIArray.Length; for (i = 0; i < PRIArray.Length; i++) { PlayerMatchDetails[i].ID = PRIArray[i].PlayerID; PlayerMatchDetails[i].Name = PRIArray[i].PlayerName; PlayerMatchDetails[i].Kills = PRIArray[i].Kills; //PlayerMatchDetails[i].Score = PRIArray[i].GoalsScored; PlayerMatchDetails[i].Score = PRIArray[i].Score; // use score instead PlayerMatchDetails[i].Deaths = PRIArray[i].Deaths; if (PRIArray[i].Team != none) PlayerMatchDetails[i].Team = PRIArray[i].Team.TeamIndex; else if (PRIArray[i] == PRI) PlayerMatchDetails[i].Team = 0; else { PlayerMatchDetails[i].Team = -1; } PlayerMatchDetails[i].SpecialAwards.Length = 0; if (TeamPlayerReplicationInfo(PRIArray[i]) != none) { if ( TeamPlayerReplicationInfo(PRIArray[i]).flakcount >= sae_flackmonkey ) { PlayerMatchDetails[i].SpecialAwards[PlayerMatchDetails[i].SpecialAwards.length] = msgSpecialAward[0]; if (PRIArray[i] == PRI) SpecialAwards[0]++; } if ( TeamPlayerReplicationInfo(PRIArray[i]).combocount >= sae_combowhore ) { PlayerMatchDetails[i].SpecialAwards[PlayerMatchDetails[i].SpecialAwards.length] = msgSpecialAward[1]; if (PRIArray[i] == PRI) SpecialAwards[1]++; } if ( TeamPlayerReplicationInfo(PRIArray[i]).headcount >= sae_headhunter ) { PlayerMatchDetails[i].SpecialAwards[PlayerMatchDetails[i].SpecialAwards.length] = msgSpecialAward[2]; if (PRIArray[i] == PRI) SpecialAwards[2]++; } if ( TeamPlayerReplicationInfo(PRIArray[i]).ranovercount >= sae_roadrampage ) { PlayerMatchDetails[i].SpecialAwards[PlayerMatchDetails[i].SpecialAwards.length] = msgSpecialAward[3]; if (PRIArray[i] == PRI) SpecialAwards[3]++; } } if ( PRIArray[i].GoalsScored >= sae_hattrick ) { PlayerMatchDetails[i].SpecialAwards[PlayerMatchDetails[i].SpecialAwards.length] = msgSpecialAward[4]; if (PRIArray[i] == PRI) SpecialAwards[4]++; } if (( PRIArray[i].Deaths == sae_untouchable ) && !lmdTeamGame) { PlayerMatchDetails[i].SpecialAwards[PlayerMatchDetails[i].SpecialAwards.length] = msgSpecialAward[5]; if (PRIArray[i] == PRI) SpecialAwards[5]++; } } } /** Create information about other matches played by other teams */ function procPhantomMatches(int games) { local array MatchHistory; local array PhantomEnemies; local UT2K4MatchInfo MI; local int i, j, n1, n2; PhantomMatches.Length = games; if (games == 0) return; // to save time PhantomEnemies = TeamStats; for (i = 0; i < PhantomEnemies.length; i++) { if (PhantomEnemies[i].Name ~= EnemyTeam) { PhantomEnemies.Remove(i, 1); break; } } MatchHistory[MatchHistory.length] = CurrentLadder*100+CurrentMenuRung; // remove own match while (games > 0) { if (PhantomEnemies.length <= 1) break; // no more enemies // find a random match do { PhantomMatches[games-1].LadderId = UT2K4GameLadder.static.GetRandomLadder(); PhantomMatches[games-1].MatchId = rand(LengthOfLadder(PhantomMatches[games-1].LadderId)); n1 = PhantomMatches[games-1].LadderId*100+PhantomMatches[games-1].MatchId; for (j = 0; j < MatchHistory.length; j++) { if (MatchHistory[j] == n1) break; } } until (j >= MatchHistory.length); MatchHistory[MatchHistory.length] = n1; // add this item MI = UT2K4MatchInfo(GetMatchInfo(PhantomMatches[games-1].LadderId, PhantomMatches[games-1].MatchId)); // find team 1 j = rand(PhantomEnemies.length); PhantomMatches[games-1].Team1 = GetTeamPosition(PhantomEnemies[j].Name); n1 = PhantomEnemies[j].Level; PhantomEnemies.Remove(j, 1); // find team 2 j = rand(PhantomEnemies.length); PhantomMatches[games-1].Team2 = GetTeamPosition(PhantomEnemies[j].Name); n2 = PhantomEnemies[j].Level; PhantomEnemies.Remove(j, 1); // normalize levels, -1 -> equal team strength if (n1 == -1) n1 = max(n2, 0); if (n2 == -1) n2 = max(n1, 0); n1++; n2++; // calculate the odds that team1 wins if (rand(n1+n2) >= n2) { n2 = PhantomMatches[games-1].Team1; PhantomMatches[games-1].ScoreTeam1 = MI.GoalScore; PhantomMatches[games-1].ScoreTeam2 = (MI.GoalScore-1)*frand(); } else { n2 = PhantomMatches[games-1].Team2; PhantomMatches[games-1].ScoreTeam1 = (MI.GoalScore-1)*frand(); PhantomMatches[games-1].ScoreTeam2 = MI.GoalScore; } PhantomMatches[games-1].GameTime = (PhantomMatches[games-1].ScoreTeam1+1)*(PhantomMatches[games-1].ScoreTeam2+1)+(frand()*MI.PrizeMoney); // n2 is set to the winning team RandomIncreaseBotFee(n2, FeeIncrease/2); games--; } } /** Increase fee of a few bots that belong to TeamId */ function RandomIncreaseBotFee(int TeamId, float updateFee) { local int i; for (i = 0; i < Botstats.length; i++) { if ((BotStats[i].TeamId == TeamId) && (frand() > 0.5)) { GetBotPrice(, i, updateFee); } } } /** converts money to a formatted string */ static function string MoneyToString(int money) { local string res, tmp, prefix; if (money == 0) return "0"@default.msgCredits; if (money < 0) { prefix = "-"; money *= -1; } else prefix = ""; res = ""; if (money == 1) return prefix$"1"@default.msgCredit; while (money > 0) { if (tmp != "") res = ","$res; tmp = string(int(money%1000)); money = money / 1000; if (money > 0) tmp = Right("00"$tmp,3); res = tmp$res; } return prefix$res@default.msgCredits; } /** return the stored player id, use this to check a profile before loading it */ function string StoredPlayerID() { return PlayerIDHash; } /** return if a profile has been locked out for character unlocking */ function bool IsLocked() { return bLocked; } /** return the level ID selected, or -1 if no selection */ function int GetSelectedLevel(int ladder, int rung) { local int i; for (i = 0; i < LevelRoute.length; i++) { if (LevelRoute[i].ladder == ladder && LevelRoute[i].rung == rung) return LevelRoute[i].selection; } return -1; } /** set a level selection */ function SetSelectedLevel(int ladder, int rung, byte id) { local int i; for (i = 0; i < LevelRoute.length; i++) { if (LevelRoute[i].ladder == ladder && LevelRoute[i].rung == rung) break; } if (i <= LevelRoute.length) LevelRoute.length = i+1; LevelRoute[i].ladder = ladder; LevelRoute[i].rung = rung; LevelRoute[i].selection = id; } /** remove a previous set selection */ function ResetSelectedLevel(int ladder, int rung) { local int i; for (i = 0; i < LevelRoute.length; i++) { if (LevelRoute[i].ladder == ladder && LevelRoute[i].rung == rung) { LevelRoute.remove(i, 1); return; } } } /** get the alternative team roster */ function bool GetAltTeamRoster(string TeamRosterName, out array result) { local int i; result.length = 0; if (TeamRosterName == "") return false; //Log("GetAltTeamRoster"@TeamRosterName); for (i = 0; i < AltTeamRoster.length; i++) { if (AltTeamRoster[i].name ~= TeamRosterName) { result = AltTeamRoster[i].roster; return true; } } return false; } /** set the alternative team roster */ function SetAltTeamRoster(string TeamRosterName, array NewRoster) { local int i; if (TeamRosterName == "") return; for (i = 0; i < AltTeamRoster.length; i++) { if (AltTeamRoster[i].name ~= TeamRosterName) break; } if (AltTeamRoster.length <= i) { AltTeamRoster.length = i+1; AltTeamRoster[i].name = TeamRosterName; } AltTeamRoster[i].roster = NewRoster; } /** Adds the current match info to the history table */ function AddHistoryRecord(GameInfo Game, PlayerReplicationInfo PRI, UT2K4MatchInfo MI) { local int i, j, n; local array PRIArray; local string tmp; i = FightHistory.length; FightHistory.length = FightHistory.length+1; FightHistory[i].Date[0] = Game.Level.Year; FightHistory[i].Date[1] = Game.Level.Month; FightHistory[i].Date[2] = Game.Level.Day; FightHistory[i].Time[0] = Game.Level.Hour; FightHistory[i].Time[1] = Game.Level.Minute; FightHistory[i].Level = Left(string(Game.Level), InStr(string(Game.Level), ".")); FightHistory[i].GameType = Game.GameName; FightHistory[i].EnemyTeam = EnemyTeam; if (MI != none) FightHistory[i].PriceMoney = MI.PrizeMoney; FightHistory[i].BalanceChange = lmdBalanceChange; FightHistory[i].BonusMoney = lmdTotalBonusMoney; FightHistory[i].GameTime = Game.Level.TimeSeconds-Game.StartTime; FightHistory[i].WonGame = bWonMatch; FightHistory[i].TeamGame = Game.bTeamGame; if (Game.bTeamGame) { Game.GameReplicationInfo.GetPRIArray(PRIArray); tmp = ""; for (j = 0; j < PRIArray.Length; j++) { if (PRIArray[j].Team.TeamIndex == ((PRI.Team.TeamIndex+1) % 2)) { if (tmp != "") tmp $= ", "; tmp $= PRIArray[j].PlayerName; } } FightHistory[i].TeamScore[0] = TeamGame(Game).Teams[(PRI.Team.TeamIndex+1) % 2].Score; FightHistory[i].TeamLayout[0] = tmp; tmp = ""; for (j = 0; j < PRIArray.Length; j++) { if (PRIArray[j].Team.TeamIndex == PRI.Team.TeamIndex) { if (tmp != "") tmp $= ", "; tmp $= PRIArray[j].PlayerName; } } FightHistory[i].TeamScore[1] = TeamGame(Game).Teams[PRI.Team.TeamIndex].Score; FightHistory[i].TeamLayout[1] = tmp; } for (j = 0; j < PlayerMatchDetails.length; j++) { if (PlayerMatchDetails[j].ID == PRI.PlayerID) { FightHistory[i].MyScore = PlayerMatchDetails[j].Score; FightHistory[i].MyKills = PlayerMatchDetails[j].Kills; FightHistory[i].MyDeaths = PlayerMatchDetails[j].Deaths; for (n = 0; n < PlayerMatchDetails[j].SpecialAwards.length; n++) { if (FightHistory[i].MyAwards != "") FightHistory[i].MyAwards $= ", "; FightHistory[i].MyAwards $= PlayerMatchDetails[j].SpecialAwards[n]; } break; } } if (bIsChallenge) { FightHistory[i].MatchData = msgChallengeGame$";"$ChallengeGameClass.default.ChallengeName; ChallengeGameClass.static.AddHistoryRecord(self, i, Game, PRI, MI); } else if (CurrentLadder >= 10) { FightHistory[i].MatchData = msgAdditionalLadder$";"$LastCustomCladder.default.LadderName@msgMatch@CurrentMenuRung; LastCustomCladder.static.AddHistoryRecord(self, i, Game, PRI, MI); } else { FightHistory[i].MatchData = msgLadderGame$";"; switch (CurrentLadder) { case UT2K4GameLadder.default.LID_DM: case UT2K4GameLadder.default.LID_TDM: case UT2K4GameLadder.default.LID_CTF: case UT2K4GameLadder.default.LID_DOM: case UT2K4GameLadder.default.LID_BR: case UT2K4GameLadder.default.LID_AS: FightHistory[i].MatchData $= GetLadderDescription(CurrentLadder); break; case UT2K4GameLadder.default.LID_CHAMP: FightHistory[i].MatchData $= msgChampionship; } FightHistory[i].MatchData @= msgMatch@CurrentMenuRung; } } /** return a random or selected challenge game */ function class GetChallengeGame(optional string ClassName) { return UT2K4GameLadder.static.GetChallengeGame(ClassName, self); } /** return the minimal fee for x team members, only uses team mates that are healthy */ function float getMinimalTeamFee(int members, optional bool bIgnoreHealth) { local array fees; local int i, j, n, tmp; for (i = 0; i < GetMaxTeamSize(); i++) { j = GetBotPosition(PlayerTeam[i]); if ((j > -1) && ((BotStats[j].Health >= 100) || bIgnoreHealth)) { tmp = BotStats[j].Price*LoserFee; for (n = 0; n < fees.Length; n++) { if (fees[n] > tmp) { fees.Insert(n, 1); fees[n] = tmp; break; } } if (n == fees.length) { fees.length = fees.length+1; fees[fees.length-1] = tmp; } } } if (fees.length < members) return 2147483647; j = 0; for (i = 0; i < members; i++) { j += fees[i]; } return j; } /** get the minimal fee for this match, based on the entry fee for the match and the minimal fee to pay out to your team mates if you lose. If you don't have enough team mates the result is negative */ function int getMinimalEntryFeeFor(UT2K4MatchInfo MI, optional bool bIgnoreHealth) { local int res; res = MI.EntryFee; if (IsTeamGametype(MI.GameType)) { res += getMinimalTeamFee(MI.NumBots / 2, bIgnoreHealth); } if (bDebug) log("getMinimalEntryFeeFor ="@res); return res; } function UpgradeGP() { local int i; if (revision == 0) { log("Revision upgrade to 1", LogPrefix); MinBalance = 75; InjuryChance = 0.30+(BaseDifficulty / 30); ChallengeChance = 0.30+(BaseDifficulty / 30); FeeIncrease = 0.02+(BaseDifficulty / 300); TeamPercentage = 0.35+(BaseDifficulty / 30); MatchBonus = 0.03+(TeamPercentage / 10); revision = 1; } if (revision == 1) { log("Revision upgrade to 2", LogPrefix); TeamPercentage = 0.25+(BaseDifficulty / 30); MatchBonus = 0.05+(TeamPercentage / 10); revision = 2; } if (revision == 2) { log("Revision upgrade to 3", LogPrefix); InjuryTreatment = 0.75+(BaseDifficulty / 50); MapChallengeCost = 0.10+(BaseDifficulty / 100); revision = 3; } if (revision == 3) { log("Revision upgrade to 4", LogPrefix); for (i = TeamStats.Length-1; i >= 0; i--) { if (TeamStats[i].Name == "") TeamStats.Remove(i, 1); } for (i = BotStats.Length-1; i >= 0; i--) { if (BotStats[i].Name == "") BotStats.Remove(i, 1); } revision = 4; } if (revision == 4) { log("Revision upgrade to 5", LogPrefix); if (bCompleted && !bLocked && !bCheater) { if (completedLadder(UT2K4GameLadder.default.LID_CHAMP)) { bCompleted = false; SpecialEvent = "COMPLETED CHAMP"; } } revision = 5; } } defaultproperties { GameLadderName="xGame.UT2K4LadderInfo" LadderProgress(0)=1 // DM - skip tutorial LadderProgress(1)=-1 // TDM LadderProgress(2)=-1 // CTF LadderProgress(3)=-1 // BR LadderProgress(4)=-1 // DDOM LadderProgress(5)=-1 // AS LadderProgress(6)=-1 // CHAMP // to override the init in UT2003GameInfo Playerteam(0)="" Playerteam(1)="" Playerteam(2)="" Playerteam(3)="" Playerteam(4)="" MaxTeamSize=5 Balance=250 //default balance MinBalance=75 TeamPercentage=0.25 MatchBonus=0.05 LoserFee=0.1 FeeIncrease=0.02 InjuryChance=0.30 LastInjured=-1 bCompleted=false ChallengeChance=0.30 MapChallengeCost=0.1 InjuryTreatment=0.75 bCheater=false LoginMenuClass="GUI2K4.UT2K4SinglePlayerLoginMenu" LogPrefix="SinglePlayer" bShowDetails=true SpreeBonus[0]=20 SpreeBonus[1]=60 SpreeBonus[2]=120 SpreeBonus[3]=200 SpreeBonus[4]=300 SpreeBonus[5]=420 MultiKillBonus[0]=10 MultiKillBonus[1]=30 MultiKillBonus[2]=60 MultiKillBonus[3]=100 MultiKillBonus[4]=150 MultiKillBonus[5]=210 MultiKillBonus[6]=280 msgSpecialAward[0]="Flak Monkey" msgSpecialAward[1]="Combo Whore" msgSpecialAward[2]="Head Hunter" msgSpecialAward[3]="Road Rampage" msgSpecialAward[4]="Hat Trick" msgSpecialAward[5]="Untouchable" sae_flackmonkey=15 sae_combowhore=15 sae_headhunter=15 sae_roadrampage=10 sae_hattrick=3 sae_untouchable=0 msgCheater="CHEATER!!!" msgChallengeGame="Challenge game" msgAdditionalLadder="Additional ladder game" msgMatch="match" msgLadderGame="Ladder game" msgChampionship="Championship" msgCredits="credits" msgCredit="credit" revision=5 bDebug=false Begin Object Class=UT2K4MatchInfo Name=GPCHALINFO End Object ChallengeInfo=GPCHALINFO }