// Server extension mutator, by Marco. Class ServerExtMut extends KFMutator config(ServerExtMut); // Webadmin var array WebConfigs; struct FInventory { var class ItemClass; var int Values[4]; }; struct FSavedInvEntry { var Controller OwnerPlayer; var byte Gren; var array Inv; }; var array PlayerInv; var config array PerkClasses,CustomChars,AdminCommands,CustomItems,BonusGameSongs,BonusGameFX; var array< class > LoadedPerks; var array CustomCharList; var ExtPlayerStat ServerStatLoader; var KFPawn LastHitZed; var int LastHitHP; var ExtPlayerController LastDamageDealer; var vector LastDamagePosition; var private const array DevList; var transient private array DevNetID; var ExtXMLOutput FileOutput; var transient class LastKillDamageType; var SoundCue BonusGameCue; var Object BonusGameFXObj; var array CustomItemList; var KFGFxObject_TraderItems CustomTrader; const SettingsTagVer=12; var KFGameReplicationInfo KF; var config int SettingsInit; var config int ForcedMaxPlayers,PlayerRespawnTime,LargeMonsterHP,StatAutoSaveWaves,MinUnloadPerkLevel,PostGameRespawnCost,MaxTopPlayers; var config float UnloadPerkExpCost; var globalconfig string ServerMOTD,StatFileDir; var array PendingSpawners; var int LastWaveNum,NumWaveSwitches; var ExtSpawnPointHelper SpawnPointer; var bool bRespawnCheck,bSpecialSpawn,bGameHasEnded,bIsPostGame; var config bool bKillMessages,bDamageMessages,bEnableMapVote,bNoAdminCommands,bNoWebAdmin,bNoBoomstickJumping,bDumpXMLStats,bRagdollFromFall,bRagdollFromMomentum,bRagdollFromBackhit,bAddCountryTags; function PostBeginPlay() { local xVotingHandler MV; local int i,j; local class PK; local UniqueNetId Id; local KFCharacterInfo_Human CH; local ObjectReferencer OR; local Object O; local string S; local FCustomTraderItem CI; local bool bLock; Super.PostBeginPlay(); if( WorldInfo.Game.BaseMutator==None ) WorldInfo.Game.BaseMutator = Self; else WorldInfo.Game.BaseMutator.AddMutator(Self); if( bDeleteMe ) // This was a duplicate instance of the mutator. return; SpawnPointer = class'ExtSpawnPointHelper'.Static.FindHelper(WorldInfo); // Start init world pathlist. //OnlineSubsystemSteamworks(class'GameEngine'.Static.GetOnlineSubsystem()).Int64ToUniqueNetId("",Id); //`Log("TEST"@class'OnlineSubsystem'.Static.UniqueNetIdToString(Id)); DevNetID.Length = DevList.Length; for( i=0; i(DynamicLoadObject(PerkClasses[i],class'Class')); if( PK!=None ) { LoadedPerks.AddItem(PK); PK.Static.CheckConfig(); } } j = 0; for( i=0; i0 ) { BonusGameCue = SoundCue(DynamicLoadObject(BonusGameSongs[Rand(BonusGameSongs.Length)],class'SoundCue')); } if( BonusGameFX.Length>0 ) { BonusGameFXObj = DynamicLoadObject(BonusGameFX[Rand(BonusGameFX.Length)],class'Object'); if( SoundCue(BonusGameFXObj)==None && ObjectReferencer(BonusGameFXObj)==None ) // Check valid type. BonusGameFXObj = None; } for( i=0; i(DynamicLoadObject(CustomItems[i],class'Class')); if( CI.WeaponDef==None ) continue; CI.WeaponClass = class(DynamicLoadObject(CI.WeaponDef.Default.WeaponClassPath,class'Class')); if( CI.WeaponClass==None ) continue; CustomItemList.AddItem(CI); if( CustomTrader==None ) { CustomTrader = class'ExtPlayerReplicationInfo'.Static.CreateNewList(); SetTimer(0.1,false,'InitGRIList'); } class'ExtPlayerReplicationInfo'.Static.SetWeaponInfo(WorldInfo.NetMode==NM_DedicatedServer,CustomTrader.SaleItems.Length,CI,CustomTrader); } if( ForcedMaxPlayers>0 ) { SetMaxPlayers(); SetTimer(0.001,false,'SetMaxPlayers'); } bRespawnCheck = (PlayerRespawnTime>0); if( bRespawnCheck ) SetTimer(1,true); if( bEnableMapVote ) { foreach DynamicActors(class'xVotingHandler',MV) break; if( MV==None ) MV = Spawn(class'xVotingHandler'); MV.BaseMutator = Class; } SetTimer(1,true,'CheckWave'); if( !bNoWebAdmin && WorldInfo.NetMode!=NM_StandAlone ) SetTimer(0.1,false,'SetupWebAdmin'); if( bDumpXMLStats ) FileOutput = Spawn(class'ExtXMLOutput'); } static final function string GetStatFile( const out UniqueNetId UID ) { return Repl(Default.StatFileDir,"%s","U_"$class'OnlineSubsystem'.Static.UniqueNetIdToString(UID)); } final function bool IsDev( const out UniqueNetId UID ) { local int i; for( i=(DevNetID.Length-1); i>=0; --i ) if( DevNetID[i]==UID ) return true; return false; } function InitGRIList() { local ExtPlayerController PC; KFGameReplicationInfo(WorldInfo.GRI).TraderItems = CustomTrader; // Must sync up local client. if( WorldInfo.NetMode==NM_StandAlone ) { foreach LocalPlayerControllers(class'ExtPlayerController',PC) if( PC.PurchaseHelper!=None ) PC.PurchaseHelper.TraderItems = CustomTrader; } } function CheckWave() { if( KF==None ) { KF = KFGameReplicationInfo(WorldInfo.GRI); if( KF==None ) return; } if( LastWaveNum!=KF.WaveNum ) { LastWaveNum = KF.WaveNum; NotifyWaveChange(); } if( !bGameHasEnded && KF.bMatchIsOver ) // HACK, since KFGameInfo_Survival doesn't properly notify mutators of this! { SaveAllPerks(true); bGameHasEnded = true; } } function NotifyWaveChange() { if( bRespawnCheck ) { bIsPostGame = (KF.WaveMax=0); if( bRespawnCheck ) SavePlayerInventory(); } if( StatAutoSaveWaves>0 && ++NumWaveSwitches>=StatAutoSaveWaves ) { NumWaveSwitches = 0; SaveAllPerks(); } } function SetupWebAdmin() { local WebServer W; local WebAdmin A; local ExtWebApp xW; local byte i; foreach AllActors(class'WebServer',W) break; if( W!=None ) { for( i=0; (i<10 && A==None); ++i ) A = WebAdmin(W.ApplicationObjects[i]); if( A!=None ) { xW = new (None) class'ExtWebApp'; xW.MyMutator = Self; A.addQueryHandler(xW); } else `Log("ExtWebAdmin ERROR: No valid WebAdmin application found!"); } else `Log("ExtWebAdmin ERROR: No WebServer object found!"); } function SetMaxPlayers() { local OnlineGameSettings GameSettings; WorldInfo.Game.MaxPlayers = ForcedMaxPlayers; WorldInfo.Game.MaxPlayersAllowed = ForcedMaxPlayers; if( WorldInfo.Game.GameInterface!=None ) { GameSettings = WorldInfo.Game.GameInterface.GetGameSettings(WorldInfo.Game.PlayerReplicationInfoClass.default.SessionName); if( GameSettings!=None ) GameSettings.NumPublicConnections = ForcedMaxPlayers; } } function AddMutator(Mutator M) { if( M!=Self ) // Make sure we don't get added twice. { if( M.Class==Class ) M.Destroy(); else Super.AddMutator(M); } } function ScoreKill(Controller Killer, Controller Killed) { if( bRespawnCheck && Killed.bIsPlayer ) CheckRespawn(Killed); if( KFPawn_Monster(Killed.Pawn)!=None && Killed.GetTeamNum()!=0 && Killer.bIsPlayer && Killer.GetTeamNum()==0 ) { if( ExtPlayerController(Killer)!=None && ExtPlayerController(Killer).ActivePerkManager!=None ) ExtPlayerController(Killer).ActivePerkManager.PlayerKilled(KFPawn_Monster(Killed.Pawn),LastKillDamageType); if( bKillMessages && Killer.PlayerReplicationInfo!=None ) BroadcastKillMessage(Killed.Pawn,Killer); //else if( Killer!=None && Killer!=Killed && Killer.GetTeamNum()==0 && Ext_T_MonsterPRI(Killer.PlayerReplicationInfo)!=None ) // BroadcastKillMessage(Killed.Pawn,Ext_T_MonsterPRI(Killer.PlayerReplicationInfo).OwnerController); } if( ExtPlayerController(Killed)!=None ) CheckPerkChange(ExtPlayerController(Killed)); if (NextMutator != None) NextMutator.ScoreKill(Killer, Killed); } function bool PreventDeath(Pawn Killed, Controller Killer, class damageType, vector HitLocation) { if( (KFPawn_Human(Killed)!=None && CheckPreventDeath(KFPawn_Human(Killed),Killer,damageType)) || Super.PreventDeath(Killed,Killer,damageType,HitLocation) ) return true; LastKillDamageType = damageType; if( Killed.Controller!=None && KFPawn_Monster(Killed)!=None ) { // Hack for when pet kills a zed. if( Killed.GetTeamNum()!=0 ) { if( Killer!=None && Killer!=Killed.Controller && Killer.GetTeamNum()==0 && Ext_T_MonsterPRI(Killer.PlayerReplicationInfo)!=None ) GT_PlayerKilled(Ext_T_MonsterPRI(Killer.PlayerReplicationInfo).OwnerController,Killed.Controller,damageType); } // Broadcast pet's deathmessage. else if( Killed.PlayerReplicationInfo!=None && PlayerController(Killed.Controller)==None && damageType!=class'KFDT_Healing' ) BroadcastFFDeath(Killer,Killed,damageType); } return false; } // Replica of KFGameInfo.Killed base. final function GT_PlayerKilled( Controller Killer, Controller Killed, class damageType ) { local ExtPlayerController KFPC; local KFPawn_Monster MonsterPawn; local KFGameInfo KFG; KFG = KFGameInfo(WorldInfo.Game); ScoreKill(Killer,Killed); // Broadcast kill message. KFPC = ExtPlayerController(Killer); MonsterPawn = KFPawn_Monster(Killed.Pawn); if( KFG!=None && KFPC != none && MonsterPawn!=none ) { //Chris: We have to do it earlier here because we need a damage type KFPC.AddZedKill( MonsterPawn.class, KFG.GameDifficulty, damageType ); if( KFPC.ActivePerkManager!=none && KFPC.ActivePerkManager.CanEarnSmallRadiusKillXP(damageType) ) KFG.CheckForBerserkerSmallRadiusKill( MonsterPawn, KFPC ); } } final function bool CheckPreventDeath( KFPawn_Human Victim, Controller Killer, class damageType ) { local ExtPlayerController E; if( Victim.IsA('KFPawn_Customization') ) return false; E = ExtPlayerController(Victim.Controller); return (E!=None && E.ActivePerkManager!=None && E.ActivePerkManager.CurrentPerk!=None && E.ActivePerkManager.CurrentPerk.PreventDeath(Victim,Killer,damageType)); } final function BroadcastKillMessage( Pawn Killed, Controller Killer ) { local ExtPlayerController E; if( Killer==None || Killer.PlayerReplicationInfo==None ) return; if( Killed.Default.Health>=LargeMonsterHP ) { foreach WorldInfo.AllControllers(class'ExtPlayerController',E) if( !E.bClientHideKillMsg ) E.ReceiveKillMessage(Killed.Class,true,Killer.PlayerReplicationInfo); } else if( ExtPlayerController(Killer)!=None && !ExtPlayerController(Killer).bClientHideKillMsg ) ExtPlayerController(Killer).ReceiveKillMessage(Killed.Class); } final function BroadcastFFDeath( Controller Killer, Pawn Killed, class damageType ) { local ExtPlayerController E; local PlayerReplicationInfo KillerPRI; local string P; local bool bFF; P = Killed.PlayerReplicationInfo.PlayerName; if( Killer==None || Killer==Killed.Controller ) { foreach WorldInfo.AllControllers(class'ExtPlayerController',E) E.ClientZedKillMessage(damageType,P); return; } bFF = (Killer.GetTeamNum()==0); KillerPRI = Killer.PlayerReplicationInfo; if( PlayerController(Killer)==None ) KillerPRI = None; foreach WorldInfo.AllControllers(class'ExtPlayerController',E) E.ClientZedKillMessage(damageType,P,KillerPRI,Killer.Pawn.Class,bFF); } function NetDamage(int OriginalDamage, out int Damage, Pawn Injured, Controller InstigatedBy, vector HitLocation, out vector Momentum, class DamageType, Actor DamageCauser) { if (NextMutator != None) NextMutator.NetDamage(OriginalDamage, Damage, Injured, InstigatedBy, HitLocation, Momentum, DamageType, DamageCauser); if( LastDamageDealer!=None ) // Make sure no other damagers interfear with the old thing going on. { ClearTimer('CheckDamageDone'); CheckDamageDone(); } if( Damage>0 && InstigatedBy!=None ) { if( KFPawn_Monster(Injured)!=None && Injured.GetTeamNum()!=0 ) { LastDamageDealer = ExtPlayerController(InstigatedBy); if( bDamageMessages && LastDamageDealer!=None && !LastDamageDealer.bNoDamageTracking ) { // Must delay this until next to get accurate damage dealt result. LastHitZed = KFPawn(Injured); LastHitHP = LastHitZed.Health; LastDamagePosition = HitLocation; SetTimer(0.001,false,'CheckDamageDone'); } else { LastDamageDealer = None; // Give credits to pet's owner. if( Ext_T_MonsterPRI(InstigatedBy.PlayerReplicationInfo)!=None ) HackSetHistory(KFPawn(Injured),Injured,Ext_T_MonsterPRI(InstigatedBy.PlayerReplicationInfo).OwnerController,Damage,HitLocation); } } else if( bDamageMessages && KFPawn_Human(Injured)!=None && Injured.GetTeamNum()==0 && InstigatedBy.GetTeamNum()!=0 && ExtPlayerController(InstigatedBy)!=None ) { LastDamageDealer = ExtPlayerController(InstigatedBy); if( bDamageMessages && !LastDamageDealer.bClientHideNumbers ) { // Must delay this until next to get accurate damage dealt result. LastHitZed = KFPawn(Injured); LastHitHP = LastHitZed.Health; LastDamagePosition = HitLocation; SetTimer(0.001,false,'CheckDamageDone'); } } } } final function CheckDamageDone() { local int Damage; if( LastDamageDealer!=None && LastHitZed!=None && LastHitHP!=LastHitZed.Health ) { Damage = LastHitHP-Max(LastHitZed.Health,0); if( Damage>0 ) { if( !LastDamageDealer.bClientHideDamageMsg && KFPawn_Monster(LastHitZed)!=None ) LastDamageDealer.ReceiveDamageMessage(LastHitZed.Class,Damage); if( !LastDamageDealer.bClientHideNumbers ) LastDamageDealer.ClientNumberMsg(Damage,LastDamagePosition,DMG_PawnDamage); } } LastDamageDealer = None; } final function HackSetHistory( KFPawn C, Pawn Injured, Controller Player, int Damage, vector HitLocation ) { local int i; local ExtPlayerController PC; if( Player==None ) return; PC = ExtPlayerController(Player); if( bDamageMessages && PC!=None ) { if( !PC.bClientHideDamageMsg ) PC.ReceiveDamageMessage(Injured.Class,Damage); if( !PC.bClientHideNumbers ) PC.ClientNumberMsg(Damage,HitLocation,DMG_PawnDamage); } i = C.DamageHistory.Find('DamagerController',Player); if( i==-1 ) { i = C.DamageHistory.Length; C.DamageHistory.Length = i+1; C.DamageHistory[i].DamagerController = Player; C.DamageHistory[i].DamagerPRI = Player.PlayerReplicationInfo; C.DamageHistory[i].DamagePerks.AddItem(class'ExtPerkManager'); C.DamageHistory[i].Damage = Damage; } else if( (WorldInfo.TimeSeconds-C.DamageHistory[i].LastTimeDamaged)<10 ) C.DamageHistory[i].Damage += Damage; else C.DamageHistory[i].Damage = Damage; C.DamageHistory[i].LastTimeDamaged = WorldInfo.TimeSeconds; C.DamageHistory[i].TotalDamage += Damage; } function bool HandleRestartGame() { if( !bGameHasEnded ) { SaveAllPerks(true); bGameHasEnded = true; } return Super.HandleRestartGame(); } function NotifyLogout(Controller Exiting) { if( KFPlayerController(Exiting)!=None ) RemoveRespawn(Exiting); if( !bGameHasEnded && ExtPlayerController(Exiting)!=None ) { CheckPerkChange(ExtPlayerController(Exiting)); SavePlayerPerk(ExtPlayerController(Exiting)); } if ( NextMutator != None ) NextMutator.NotifyLogout(Exiting); } function NotifyLogin(Controller NewPlayer) { if( ExtPlayerController(NewPlayer)!=None ) { if( ExtPlayerReplicationInfo(NewPlayer.PlayerReplicationInfo)!=None ) InitCustomChars(ExtPlayerReplicationInfo(NewPlayer.PlayerReplicationInfo)); if( bAddCountryTags && NetConnection(PlayerController(NewPlayer).Player)!=None ) ExtPlayerReplicationInfo(NewPlayer.PlayerReplicationInfo).SetPlayerNameTag(class'CtryDatabase'.Static.GetClientCountryStr(PlayerController(NewPlayer).GetPlayerNetworkAddress())); ExtPlayerReplicationInfo(NewPlayer.PlayerReplicationInfo).bIsDev = IsDev(NewPlayer.PlayerReplicationInfo.UniqueId); if( WorldInfo.NetMode!=NM_StandAlone ) ExtPlayerReplicationInfo(NewPlayer.PlayerReplicationInfo).OnRepNextItem = GetNextItem; if( BonusGameCue!=None || BonusGameFXObj!=None ) ExtPlayerController(NewPlayer).ClientSetBonus(BonusGameCue,BonusGameFXObj); if( bRespawnCheck ) CheckRespawn(NewPlayer); if( !bGameHasEnded ) InitializePerks(ExtPlayerController(NewPlayer)); SendMOTD(ExtPlayerController(NewPlayer)); } if ( NextMutator != None ) NextMutator.NotifyLogin(NewPlayer); } final function InitializePerks( ExtPlayerController Other ) { local ExtPerkManager PM; local Ext_PerkBase P; local int i; Other.OnChangePerk = PlayerChangePerk; Other.OnBoughtStats = PlayerBuyStats; Other.OnBoughtTrait = PlayerBoughtTrait; Other.OnPerkReset = ResetPlayerPerk; Other.OnAdminHandle = AdminCommand; Other.OnSetMOTD = AdminSetMOTD; Other.OnRequestUnload = PlayerUnloadInfo; Other.OnSpectateChange = PlayerChangeSpec; Other.OnClientGetStat = class'ExtStatList'.Static.GetStat; PM = Other.ActivePerkManager; PM.InitPerks(); for( i=0; i0 ) class'ExtStatList'.Static.SetTopPlayers(Other); } PM.ServerInitPerks(); PM.InitiateClientRep(); } final function SendMOTD( ExtPlayerController PC ) { local string S; local int i; S = ServerMOTD; while( Len(S)>510 ) { PC.ReceiveServerMOTD(Left(S,500),false); S = Mid(S,500); } PC.ReceiveServerMOTD(S,true); for( i=0; i=0; --i ) if( PlayerInv[i].OwnerPlayer==Other.Controller ) { KFInventoryManager(Other.InvManager).bInfiniteWeight = true; KFInventoryManager(Other.InvManager).GrenadeCount = PlayerInv[i].Gren; for( j=(PlayerInv[i].Inv.Length-1); j>=0; --j ) { Inv = Other.InvManager.FindInventoryType(PlayerInv[i].Inv[j].ItemClass,false); if( Inv==None ) { Inv = Other.InvManager.CreateInventory(PlayerInv[i].Inv[j].ItemClass); } K = KFWeapon(Inv); if( K!=None ) { K.SpareAmmoCount[0] = PlayerInv[i].Inv[j].Values[0]; K.SpareAmmoCount[1] = PlayerInv[i].Inv[j].Values[1]; K.AmmoCount[0] = PlayerInv[i].Inv[j].Values[2]; K.AmmoCount[1] = PlayerInv[i].Inv[j].Values[3]; K.ClientForceAmmoUpdate(K.AmmoCount[0],K.SpareAmmoCount[0]); K.ClientForceSecondaryAmmoUpdate(K.AmmoCount[1]); } } if( Other.InvManager.FindInventoryType(class'KFInventory_Money',true)==None ) Other.InvManager.CreateInventory(class'KFInventory_Money'); KFInventoryManager(Other.InvManager).bInfiniteWeight = false; return true; } return false; } final function Pawn SpawnDefaultPawnFor(Controller NewPlayer, Actor StartSpot) // Clone of GameInfo one, but with Actor StartSpot. { local class PlayerClass; local Rotator R; local Pawn ResultPawn; PlayerClass = WorldInfo.Game.GetDefaultPlayerClass(NewPlayer); R.Yaw = StartSpot.Rotation.Yaw; ResultPawn = Spawn(PlayerClass,,,StartSpot.Location,R,,true); return ResultPawn; } final function bool RespawnPlayer( Controller NewPlayer ) { local KFPlayerReplicationInfo KFPRI; local KFPlayerController KFPC; local Actor startSpot; local int Idx; local array Events; local SeqEvent_PlayerSpawned SpawnedEvent; local LocalPlayer LP; if( NewPlayer.Pawn!=None ) NewPlayer.Pawn.Destroy(); // figure out the team number and find the start spot StartSpot = SpawnPointer.PickBestSpawn(); // if a start spot wasn't found, if (startSpot == None) { // check for a previously assigned spot if (NewPlayer.StartSpot != None) { StartSpot = NewPlayer.StartSpot; `warn("Player start not found, using last start spot"); } else { // otherwise abort `warn("Player start not found, failed to restart player"); return false; } } // try to create a pawn to use of the default class for this player NewPlayer.Pawn = SpawnDefaultPawnFor(NewPlayer, StartSpot); if (NewPlayer.Pawn == None) { NewPlayer.GotoState('Dead'); if ( PlayerController(NewPlayer) != None ) PlayerController(NewPlayer).ClientGotoState('Dead','Begin'); return false; } else { // initialize and start it up if( NavigationPoint(startSpot)!=None ) NewPlayer.Pawn.SetAnchor(NavigationPoint(startSpot)); if ( PlayerController(NewPlayer) != None ) { PlayerController(NewPlayer).TimeMargin = -0.1; if( NavigationPoint(startSpot)!=None ) NavigationPoint(startSpot).AnchoredPawn = None; // SetAnchor() will set this since IsHumanControlled() won't return true for the Pawn yet } NewPlayer.Pawn.LastStartSpot = PlayerStart(startSpot); NewPlayer.Pawn.LastStartTime = WorldInfo.TimeSeconds; NewPlayer.Possess(NewPlayer.Pawn, false); NewPlayer.Pawn.PlayTeleportEffect(true, true); NewPlayer.ClientSetRotation(NewPlayer.Pawn.Rotation, TRUE); if ( !WorldInfo.bNoDefaultInventoryForPlayer ) { AddPlayerSpecificInv(NewPlayer.Pawn); WorldInfo.Game.AddDefaultInventory(NewPlayer.Pawn); } WorldInfo.Game.SetPlayerDefaults(NewPlayer.Pawn); // activate spawned events if (WorldInfo.GetGameSequence() != None) { WorldInfo.GetGameSequence().FindSeqObjectsByClass(class'SeqEvent_PlayerSpawned',TRUE,Events); for (Idx = 0; Idx < Events.Length; Idx++) { SpawnedEvent = SeqEvent_PlayerSpawned(Events[Idx]); if (SpawnedEvent != None && SpawnedEvent.CheckActivate(NewPlayer,NewPlayer)) { SpawnedEvent.SpawnPoint = startSpot; SpawnedEvent.PopulateLinkedVariableValues(); } } } } KFPC = KFPlayerController(NewPlayer); KFPRI = KFPlayerReplicationInfo(NewPlayer.PlayerReplicationInfo); // To fix custom post processing chain when not running in editor or PIE. if (KFPC != none) { LP = LocalPlayer(KFPC.Player); if(LP != None) { LP.RemoveAllPostProcessingChains(); LP.InsertPostProcessingChain(LP.Outer.GetWorldPostProcessChain(),INDEX_NONE,true); if(KFPC.myHUD != None) { KFPC.myHUD.NotifyBindPostProcessEffects(); } } } KFGameInfo(WorldInfo.Game).SetTeam( NewPlayer, KFGameInfo(WorldInfo.Game).Teams[0] ); if( KFPC != none ) { // Initialize game play post process effects such as damage, low health, etc. KFPC.InitGameplayPostProcessFX(); } if( KFPRI!=None ) { if( KFPRI.Deaths == 0 ) KFPRI.Score = KFGameInfo(WorldInfo.Game).DifficultyInfo.GetAdjustedStartingCash(); KFPRI.PlayerHealth = NewPlayer.Pawn.Health; KFPRI.PlayerHealthPercent = FloatToByte( float(NewPlayer.Pawn.Health) / float(NewPlayer.Pawn.HealthMax) ); } return true; } function PlayerBuyStats( ExtPlayerController PC, class Perk, int iStat, int Amount ) { local Ext_PerkBase P; local int i; if( bGameHasEnded ) return; P = PC.ActivePerkManager.FindPerk(Perk); if( P==None || !P.bPerkNetReady || iStat>=P.PerkStats.Length ) return; Amount = Min(Amount,P.PerkStats[iStat].MaxValue-P.PerkStats[iStat].CurrentValue); if( Amount<=0 ) return; i = Amount*P.PerkStats[iStat].CostPerValue; if( i>P.CurrentSP ) { Amount = P.CurrentSP/P.PerkStats[iStat].CostPerValue; if( Amount<=0 ) return; i = Amount*P.PerkStats[iStat].CostPerValue; } P.CurrentSP-=i; if( !P.IncrementStat(iStat,Amount) ) PC.ClientMessage("Failed to buy stat."); } function PlayerChangePerk( ExtPlayerController PC, class NewPerk ) { if( bGameHasEnded ) return; if( NewPerk==PC.ActivePerkManager.CurrentPerk.Class ) { if( PC.PendingPerkClass!=None ) { PC.ClientMessage("You will remain the same perk now."); PC.PendingPerkClass = None; } } else if( PC.ActivePerkManager.CurrentPerk==None || PC.Pawn==None || !PC.Pawn.IsAliveAndWell() ) { if( PC.ActivePerkManager.ApplyPerkClass(NewPerk) ) PC.ClientMessage("You have changed your perk to "$NewPerk.Default.PerkName); else PC.ClientMessage("Invalid perk "$NewPerk.Default.PerkName); } else { PC.ClientMessage("You will change to perk '"$NewPerk.Default.PerkName$"' next time you have died."); PC.PendingPerkClass = NewPerk; } } function CheckPerkChange( ExtPlayerController PC ) { if( PC.PendingPerkClass!=None ) { if( PC.ActivePerkManager.ApplyPerkClass(PC.PendingPerkClass) ) PC.ClientMessage("You have changed your perk to "$PC.PendingPerkClass.Default.PerkName); else PC.ClientMessage("Invalid perk "$PC.PendingPerkClass.Default.PerkName); PC.PendingPerkClass = None; } } function PlayerBoughtTrait( ExtPlayerController PC, class PerkClass, class Trait ) { local Ext_PerkBase P; local int i,cost; if( bGameHasEnded ) return; P = PC.ActivePerkManager.FindPerk(PerkClass); if( P==None || !P.bPerkNetReady ) return; for( i=0; i=Trait.Default.NumLevels ) return; cost = Trait.Static.GetTraitCost(P.PerkTraits[i].CurrentLevel); if( cost>P.CurrentSP || !Trait.Static.MeetsRequirements(P.PerkTraits[i].CurrentLevel,P) ) return; PC.ActivePerkManager.bStatsDirty = true; P.CurrentSP-=cost; P.bForceNetUpdate = true; ++P.PerkTraits[i].CurrentLevel; P.ClientReceiveTraitLvl(i,P.PerkTraits[i].CurrentLevel); if( P.PerkTraits[i].CurrentLevel==1 ) P.PerkTraits[i].Data = Trait.Static.InitializeFor(P,PC); if( PC.ActivePerkManager.CurrentPerk==P ) { Trait.Static.TraitDeActivate(P,P.PerkTraits[i].CurrentLevel-1,P.PerkTraits[i].Data); Trait.Static.TraitActivate(P,P.PerkTraits[i].CurrentLevel,P.PerkTraits[i].Data); if( KFPawn_Human(PC.Pawn)!=None ) { Trait.Static.CancelEffectOn(KFPawn_Human(PC.Pawn),P,P.PerkTraits[i].CurrentLevel-1,P.PerkTraits[i].Data); Trait.Static.ApplyEffectOn(KFPawn_Human(PC.Pawn),P,P.PerkTraits[i].CurrentLevel,P.PerkTraits[i].Data); } } break; } } } function PlayerUnloadInfo( ExtPlayerController PC, byte CallID, class PerkClass, bool bUnload ) { local Ext_PerkBase P; local int LostExp,NewLvl; // Verify if client tries to cause errors. if( PC==None || PerkClass==None || PC.ActivePerkManager==None ) return; // Perk unloading disabled on this server. if( MinUnloadPerkLevel==-1 ) { if( !bUnload ) PC.ClientGotUnloadInfo(CallID,0); return; } P = PC.ActivePerkManager.FindPerk(PerkClass); if( P==None ) // More client hack attempts. return; if( P.CurrentLevel PerkClass, bool bPrestige ) { local Ext_PerkBase P; if( bGameHasEnded ) return; P = PC.ActivePerkManager.FindPerk(PerkClass); if( P==None || !P.bPerkNetReady ) return; if( bPrestige ) { if( !P.CanPrestige() ) { PC.ClientMessage("Prestige for this perk is not allowed."); return; } ++P.CurrentPrestige; } P.FullReset(bPrestige); } function bool CheckReplacement(Actor Other) { if( bNoBoomstickJumping && KFWeap_Shotgun_DoubleBarrel(Other)!=None ) KFWeap_Shotgun_DoubleBarrel(Other).DoubleBarrelKickMomentum = 5.f; return true; } final function InitCustomChars( ExtPlayerReplicationInfo PRI ) { PRI.CustomCharList = CustomCharList; } final function bool HasPrivs( ExtPlayerReplicationInfo P ) { return WorldInfo.NetMode==NM_StandAlone || (P!=None && P.ShowAdminName() && (P.AdminType<=1 || P.AdminType==255)); } function AdminCommand( ExtPlayerController PC, int PlayerID, int Action ) { local ExtPlayerController E; local int i; if( bNoAdminCommands ) { PC.ClientMessage("Admin level commands are disabled.",'Priority'); return; } if( !HasPrivs(ExtPlayerReplicationInfo(PC.PlayerReplicationInfo)) ) { PC.ClientMessage("You do not have enough admin priveleges.",'Priority'); return; } foreach WorldInfo.AllControllers(class'ExtPlayerController',E) if( E.PlayerReplicationInfo.PlayerID==PlayerID ) break; if( E==None ) { PC.ClientMessage("Action failed, missing playerID: "$PlayerID,'Priority'); return; } if( Action>=100 ) // Set perk level. { if( E.ActivePerkManager.CurrentPerk==None ) { PC.ClientMessage(E.PlayerReplicationInfo.PlayerName$" has no perk selected!!!",'Priority'); return; } if( Action>=100000 ) // Set prestige level. { if( E.ActivePerkManager.CurrentPerk.MinLevelForPrestige<0 ) { PC.ClientMessage("Perk "$E.ActivePerkManager.CurrentPerk.Default.PerkName$" has prestige disabled!",'Priority'); return; } Action = Min(Action-100000,E.ActivePerkManager.CurrentPerk.MaxPrestige); E.ActivePerkManager.CurrentPerk.CurrentPrestige = Action; PC.ClientMessage("Set "$E.PlayerReplicationInfo.PlayerName$"' perk "$E.ActivePerkManager.CurrentPerk.Default.PerkName$" prestige level to "$Action,'Priority'); E.ActivePerkManager.CurrentPerk.FullReset(true); } else { Action = Clamp(Action-100,E.ActivePerkManager.CurrentPerk.MinimumLevel,E.ActivePerkManager.CurrentPerk.MaximumLevel); E.ActivePerkManager.CurrentPerk.CurrentEXP = E.ActivePerkManager.CurrentPerk.GetNeededExp(Action-1); PC.ClientMessage("Set "$E.PlayerReplicationInfo.PlayerName$"' perk "$E.ActivePerkManager.CurrentPerk.Default.PerkName$" level to "$Action,'Priority'); E.ActivePerkManager.CurrentPerk.SetInitialLevel(); E.ActivePerkManager.CurrentPerk.UpdatePRILevel(); } return; } switch( Action ) { case 0: // Reset ALL Stats for( i=0; iWorldInfo.TimeSeconds ) return; PC.NextSpectateChange = WorldInfo.TimeSeconds+0.5; if( WorldInfo.Game.bGameEnded ) PC.ClientMessage("Can't change spectate mode after end-game."); else if( WorldInfo.Game.bWaitingToStartMatch ) PC.ClientMessage("Can't change spectate mode before game has started."); else if( WorldInfo.Game.AtCapacity(bSpectator,PC.PlayerReplicationInfo.UniqueId) ) PC.ClientMessage("Can't change spectate mode because game is at its maximum capacity."); else if( bSpectator ) { PC.NextSpectateChange = WorldInfo.TimeSeconds+2.5; if( PC.PlayerReplicationInfo.Team!=None ) PC.PlayerReplicationInfo.Team.RemoveFromTeam(PC); PC.PlayerReplicationInfo.bOnlySpectator = true; if( PC.Pawn!=None ) PC.Pawn.KilledBy(None); PC.Reset(); --WorldInfo.Game.NumPlayers; ++WorldInfo.Game.NumSpectators; WorldInfo.Game.Broadcast(PC,PC.PlayerReplicationInfo.GetHumanReadableName()@"became a spectator"); RemoveRespawn(PC); } else { PC.PlayerReplicationInfo.bOnlySpectator = false; if( !WorldInfo.Game.ChangeTeam(PC,WorldInfo.Game.PickTeam(0,PC,PC.PlayerReplicationInfo.UniqueId),false) ) { PC.PlayerReplicationInfo.bOnlySpectator = true; PC.ClientMessage("Can't become an active player, failed to set a team."); return; } PC.NextSpectateChange = WorldInfo.TimeSeconds+2.5; ++WorldInfo.Game.NumPlayers; --WorldInfo.Game.NumSpectators; PC.Reset(); WorldInfo.Game.Broadcast(PC,PC.PlayerReplicationInfo.GetHumanReadableName()@"became an active player"); if( bRespawnCheck ) CheckRespawn(PC); } } function bool GetNextItem( ExtPlayerReplicationInfo PRI, int RepIndex ) { if( RepIndex>=CustomItemList.Length ) return false; PRI.ClientAddTraderItem(class'KFGameReplicationInfo'.Default.TraderItems.SaleItems.Length+RepIndex,CustomItemList[RepIndex]); return true; } function InitWebAdmin( ExtWebAdmin_UI UI ) { local int i; UI.AddSettingsPage("Main Server Ext",Class,WebConfigs,WebAdminGetValue,WebAdminSetValue); for( i=0; i Ar, int Index, const out string Value ) { if( Value=="#DELETE" ) Ar.Remove(Index,1); else { if( Index>=Ar.Length ) Ar.Length = Index+1; Ar[Index] = Value; } } function WebAdminSetValue( name PropName, int ElementIndex, string Value ) { switch( PropName ) { case 'StatFileDir': StatFileDir = Value; break; case 'ForcedMaxPlayers': ForcedMaxPlayers = int(Value); break; case 'PlayerRespawnTime': PlayerRespawnTime = int(Value); break; case 'StatAutoSaveWaves': StatAutoSaveWaves = int(Value); break; case 'PostGameRespawnCost': PostGameRespawnCost = int(Value); break; case 'bKillMessages': bKillMessages = bool(Value); break; case 'LargeMonsterHP': LargeMonsterHP = int(Value); break; case 'MinUnloadPerkLevel': MinUnloadPerkLevel = int(Value); break; case 'UnloadPerkExpCost': UnloadPerkExpCost = float(Value); break; case 'bDamageMessages': bDamageMessages = bool(Value); break; case 'bEnableMapVote': bEnableMapVote = bool(Value); break; case 'bNoAdminCommands': bNoAdminCommands = bool(Value); break; case 'bDumpXMLStats': bDumpXMLStats = bool(Value); break; case 'bNoBoomstickJumping': bNoBoomstickJumping = bool(Value); break; case 'bRagdollFromFall': bRagdollFromFall = bool(Value); break; case 'bRagdollFromMomentum': bRagdollFromMomentum = bool(Value); break; case 'bRagdollFromBackhit': bRagdollFromBackhit = bool(Value); break; case 'bAddCountryTags': bAddCountryTags = bool(Value); break; case 'MaxTopPlayers': MaxTopPlayers = int(Value); break; case 'ServerMOTD': ServerMOTD = Repl(Value,Chr(13)$Chr(10),"|"); break; case 'PerkClasses': UpdateArray(PerkClasses,ElementIndex,Value); break; case 'CustomChars': UpdateArray(CustomChars,ElementIndex,Value); break; case 'AdminCommands': UpdateArray(AdminCommands,ElementIndex,Value); break; case 'CustomItems': UpdateArray(CustomItems,ElementIndex,Value); break; case 'BonusGameSongs': UpdateArray(BonusGameSongs,ElementIndex,Value); break; case 'BonusGameFX': UpdateArray(BonusGameFX,ElementIndex,Value); break; default: return; } SaveConfig(); } defaultproperties { DevList.Add("0x0110000100E8984E") DevList.Add("0x01100001023DF8A8") WebConfigs.Add((PropType=0,PropName="StatFileDir",UIName="Stat File Dir",UIDesc="Location of the stat files on the HDD (%s = unique player ID)")) WebConfigs.Add((PropType=0,PropName="ForcedMaxPlayers",UIName="Server Max Players",UIDesc="A forced max players value of the server (0 = use standard KF2 setting)")) WebConfigs.Add((PropType=0,PropName="PlayerRespawnTime",UIName="Respawn Time",UIDesc="Players respawn time in seconds after they die (0 = no respawning)")) WebConfigs.Add((PropType=0,PropName="PostGameRespawnCost",UIName="Post-Game Respawn Cost",UIDesc="Amount of dosh it'll cost to be respawned after end-game (only for custom gametypes that support this).")) WebConfigs.Add((PropType=0,PropName="StatAutoSaveWaves",UIName="Stat Auto-Save Waves",UIDesc="How often should stats be auto-saved (1 = every wave, 2 = every second wave etc)")) WebConfigs.Add((PropType=0,PropName="MinUnloadPerkLevel",UIName="Min Unload Perk Level",UIDesc="Minimum level a player should be on before they can use the perk stat unload (-1 = never).")) WebConfigs.Add((PropType=0,PropName="UnloadPerkExpCost",UIName="Perk Unload XP Cost",UIDesc="The percent of XP it costs for a player to use a perk unload (1 = all XP, 0 = none).")) WebConfigs.Add((PropType=1,PropName="bKillMessages",UIName="Show Kill Messages",UIDesc="Display on players HUD a kill counter every time they kill something")) WebConfigs.Add((PropType=0,PropName="LargeMonsterHP",UIName="Large Monster HP",UIDesc="If the enemy kill a monster with more HP then this, broadcast kill message to everyone")) WebConfigs.Add((PropType=1,PropName="bDamageMessages",UIName="Show Damage Messages",UIDesc="Display on players HUD a damage counter every time they damage an enemy")) WebConfigs.Add((PropType=1,PropName="bEnableMapVote",UIName="Enable MapVote",UIDesc="Enable MapVote X on this server")) WebConfigs.Add((PropType=1,PropName="bNoBoomstickJumping",UIName="No Boomstick Jumps",UIDesc="Disable boomstick knockback, so people can't glitch with it on maps")) WebConfigs.Add((PropType=1,PropName="bNoAdminCommands",UIName="Disable Admin menu",UIDesc="Disable admin menu commands so admins can't modify XP or levels of players")) WebConfigs.Add((PropType=1,PropName="bDumpXMLStats",UIName="Dump XML stats",UIDesc="Dump XML stat files for some external stat loggers")) WebConfigs.Add((PropType=1,PropName="bRagdollFromFall",UIName="Ragdoll From Fall",UIDesc="Make players ragdoll if they fall from a high place")) WebConfigs.Add((PropType=1,PropName="bRagdollFromMomentum",UIName="Ragdoll From Momentum",UIDesc="Make players ragdoll if they take a damage with high momentum transfer")) WebConfigs.Add((PropType=1,PropName="bRagdollFromBackhit",UIName="Ragdoll From Backhit",UIDesc="Make players ragdoll if they take a big hit to their back")) WebConfigs.Add((PropType=1,PropName="bAddCountryTags",UIName="Add Country Tags",UIDesc="Add player country tags to their names")) WebConfigs.Add((PropType=0,PropName="MaxTopPlayers",UIName="Max top players",UIDesc="Maximum top players to broadcast of and to keep track of.")) WebConfigs.Add((PropType=2,PropName="PerkClasses",UIName="Perk Classes",UIDesc="List of RPG perks players can play as (careful with removing them, because any perks removed will permanently delete the gained XP for every player for that perk)!",NumElements=-1)) WebConfigs.Add((PropType=2,PropName="CustomChars",UIName="Custom Chars",UIDesc="List of custom characters for this server (prefix with * to mark as admin character).",NumElements=-1)) WebConfigs.Add((PropType=2,PropName="AdminCommands",UIName="Admin Commands",UIDesc="List of Admin commands to show on scoreboard UI for admins (use : to split actual command with display name for the command)",NumElements=-1)) WebConfigs.Add((PropType=2,PropName="CustomItems",UIName="Custom Inventory",UIDesc="List of custom inventory to add to trader (must be KFWeaponDefinition class).",NumElements=-1)) WebConfigs.Add((PropType=3,PropName="ServerMOTD",UIName="MOTD",UIDesc="Message of the Day")) WebConfigs.Add((PropType=2,PropName="BonusGameSongs",UIName="Bonus Game Songs",UIDesc="List of custom musics to play during level change pong game.",NumElements=-1)) WebConfigs.Add((PropType=2,PropName="BonusGameFX",UIName="Bonus Game FX",UIDesc="List of custom FX to play on pong game.",NumElements=-1)) }