diff --git a/MskGs/Classes/MapStats.uc b/MskGs/Classes/MapStats.uc new file mode 100644 index 0000000..0b208d0 --- /dev/null +++ b/MskGs/Classes/MapStats.uc @@ -0,0 +1,47 @@ +class MapStats extends Object + config(NextMapMut); + +struct MapStatEntry +{ + var config string Name; + var config int Counter; +}; +var config array MapStat; + +static function int CounterSortAsc (MapStatEntry A, MapStatEntry B) { return B.Counter < A.Counter ? -1 : 0; } +static function int CounterSortDesc (MapStatEntry A, MapStatEntry B) { return A.Counter < B.Counter ? -1 : 0; } +static function int NameSortAsc (MapStatEntry A, MapStatEntry B) { return B.Name < A.Name ? -1 : 0; } +static function int NameSortDesc (MapStatEntry A, MapStatEntry B) { return A.Name < B.Name ? -1 : 0; } + +static function IncMapStat(string Map, optional string SortPolicy = "False") +{ + local int MapStatEntryIndex; + local MapStatEntry NewEntry; + + MapStatEntryIndex = Default.MapStat.Find('Name', Map); + if (MapStatEntryIndex == INDEX_NONE) + { + NewEntry.Name = Map; + NewEntry.Counter = 1; + Default.MapStat.AddItem(NewEntry); + } + else + { + Default.MapStat[MapStatEntryIndex].Counter++; + } + + if (SortPolicy ~= "CounterAsc") + Default.MapStat.Sort(CounterSortAsc); + else if (SortPolicy ~= "CounterDesc") + Default.MapStat.Sort(CounterSortDesc); + else if (SortPolicy ~= "NameAsc") + Default.MapStat.Sort(NameSortAsc); + else if (SortPolicy ~= "NameDesc") + Default.MapStat.Sort(NameSortDesc); + + StaticSaveConfig(); +} + +DefaultProperties +{ +} diff --git a/MskGs/Classes/MskGsGFxMoviePlayer_Manager.uc b/MskGs/Classes/MskGsGFxMoviePlayer_Manager.uc index 35fa26c..3e59f96 100644 --- a/MskGs/Classes/MskGsGFxMoviePlayer_Manager.uc +++ b/MskGs/Classes/MskGsGFxMoviePlayer_Manager.uc @@ -3,6 +3,7 @@ class MskGsGFxMoviePlayer_Manager extends KFGFxMoviePlayer_Manager defaultproperties { + InGamePartyWidgetClass=class'MskGsGFxWidget_PartyInGame' WidgetBindings.Remove((WidgetName="traderMenu",WidgetClass=class'KFGFxMenu_Trader')) WidgetBindings.Add((WidgetName="traderMenu",WidgetClass=class'MskGsGFxMenu_Trader')) } diff --git a/MskGs/Classes/MskGsGFxTraderContainer_Store.uc b/MskGs/Classes/MskGsGFxTraderContainer_Store.uc index 0657d3d..f9381f6 100644 --- a/MskGs/Classes/MskGsGFxTraderContainer_Store.uc +++ b/MskGs/Classes/MskGsGFxTraderContainer_Store.uc @@ -22,8 +22,8 @@ function bool IsItemFiltered(STraderItem Item, optional bool bDebug) return true; if (!KFPC.GetPurchaseHelper().IsSellable(Item)) return true; - if (!GroupMember && Item.WeaponDef.default.SharedUnlockId != SCU_None && !class'KFUnlockManager'.static.IsSharedContentUnlocked(Item.WeaponDef.default.SharedUnlockId)) - return true; + //if (!GroupMember && Item.WeaponDef.default.SharedUnlockId != SCU_None && !class'KFUnlockManager'.static.IsSharedContentUnlocked(Item.WeaponDef.default.SharedUnlockId)) + // return true; if (Item.WeaponDef.default.PlatformRestriction != PR_All && class'KFUnlockManager'.static.IsPlatformRestricted(Item.WeaponDef.default.PlatformRestriction)) return true; diff --git a/MskGs/Classes/MskGsGFxWidget_PartyInGame.uc b/MskGs/Classes/MskGsGFxWidget_PartyInGame.uc new file mode 100644 index 0000000..0aac9bf --- /dev/null +++ b/MskGs/Classes/MskGsGFxWidget_PartyInGame.uc @@ -0,0 +1,6 @@ +class MskGsGFxWidget_PartyInGame extends KFGFxWidget_PartyInGame within GFxMoviePlayer; + +defaultproperties +{ + PlayerSlots=8 +} diff --git a/MskGs/Classes/MskGsMut.uc b/MskGs/Classes/MskGsMut.uc index fc27d94..7e4b140 100644 --- a/MskGs/Classes/MskGsMut.uc +++ b/MskGs/Classes/MskGsMut.uc @@ -4,7 +4,17 @@ Class MskGsMut extends KFMutator var const int SteamIDLen; var const int UniqueIDLen; +var const int CurrentVersion; +var config int ConfigVersion; + +var config bool bEnableMapStats; +var config string SortStats; +var config bool bOfficialNextMapOnly; +var config bool bRandomizeNextMap; +var config int WeapDespawnTime; + var config array ImportantPersonList; +var config array PerPlayerMaxMonsters; function InitMutator(string Options, out string ErrorMessage) { @@ -23,6 +33,57 @@ function InitMutator(string Options, out string ErrorMessage) MyKFGI.MaxPlayersAllowed = MaxPlayersAllowed; } +function InitConfig() +{ + // Update from config version to current version if needed + switch (ConfigVersion) + { + case 0: // which means there is no config right now + bEnableMapStats = True; + SortStats = "CounterDesc"; + bOfficialNextMapOnly = True; + bRandomizeNextMap = True; + WeapDespawnTime = 2147483647; + case 1: + if (PerPlayerMaxMonsters.Length != 6) + { + PerPlayerMaxMonsters.Length = 0; + PerPlayerMaxMonsters.AddItem(12); + PerPlayerMaxMonsters.AddItem(18); + PerPlayerMaxMonsters.AddItem(24); + PerPlayerMaxMonsters.AddItem(32); + PerPlayerMaxMonsters.AddItem(34); + PerPlayerMaxMonsters.AddItem(36); + } + case 2147483647: + `log("[MskGsMut] Config updated to version"@CurrentVersion); + break; + case CurrentVersion: + `log("[MskGsMut] Config is up-to-date"); + break; + default: + `log("[MskGsMut] Warn: The config version is higher than the current version (are you using an old mutator?)"); + `log("[MskGsMut] Warn: Config version is"@ConfigVersion@"but current version is"@CurrentVersion); + `log("[MskGsMut] Warn: The config version will be changed to "@CurrentVersion); + break; + } + + // Check and correct some values + if (!(SortStats ~= "CounterAsc" + || SortStats ~= "CounterDesc" + || SortStats ~= "NameAsc" + || SortStats ~= "NameDesc" + || SortStats ~= "False")) + { + `log("[MskGsMut] Warn: SortStats value not recognized ("$SortStats$") and will be set to False"); + `log("[MskGsMut] Warn: Valid values for SortStats: False CounterAsc CounterDesc NameAsc NameDesc"); + SortStats = "False"; + } + + ConfigVersion = CurrentVersion; + SaveConfig(); +} + simulated event PostBeginPlay() { super.PostBeginPlay(); @@ -32,8 +93,7 @@ simulated event PostBeginPlay() else WorldInfo.Game.BaseMutator.AddMutator(Self); - if (bDeleteMe) - return; + if (bDeleteMe) return; Initialize(); } @@ -47,22 +107,23 @@ function Initialize() if (MyKFGI == None || MyKFGI.MyKFGRI == None) { - SetTimer(2.f, false, nameof(Initialize)); + SetTimer(1.f, false, nameof(Initialize)); return; } + InitConfig(); + MyKFGI.KFGFxManagerClass = class'MskGsGFxMoviePlayer_Manager'; MyKFGI.MyKFGRI.VoteCollectorClass = class'MskGsVoteCollector'; - MyKFGI.MyKFGRI.PostBeginPlay(); + MyKFGI.MyKFGRI.VoteCollector = new(MyKFGI.MyKFGRI) MyKFGI.MyKFGRI.VoteCollectorClass; + + VoteCollector = MskGsVoteCollector(MyKFGI.MyKFGRI.VoteCollector); + VoteCollector.bEnableMapStats = bEnableMapStats; + VoteCollector.bOfficialNextMapOnly = bOfficialNextMapOnly; + VoteCollector.bRandomizeNextMap = bRandomizeNextMap; + VoteCollector.SortPolicy = SortStats; steamworks = class'GameEngine'.static.GetOnlineSubsystem(); - VoteCollector = MskGsVoteCollector(MyKFGI.MyKFGRI.VoteCollector); - - if (VoteCollector == None) - { - `Log("[MskGsMut] ERROR: VoteCollector is None!"); - return; - } foreach ImportantPersonList(Person) { @@ -78,14 +139,30 @@ function Initialize() } else `Log("[MskGsMut] WARN: Can't add person:"@Person); } + + ModifySpawnManager(); `Log("[MskGsMut] Mutator loaded."); } +function ModifySpawnManager() +{ + local int i, j; + + if (MyKFGI.SpawnManager == None) + { + SetTimer(1.f, false, nameof(ModifySpawnManager)); + return; + } + + for (i = 0; i < MyKFGI.SpawnManager.PerDifficultyMaxMonsters.Length; i++) + for (j = 0; j < MyKFGI.SpawnManager.PerDifficultyMaxMonsters[i].MaxMonsters.Length; j++) + MyKFGI.SpawnManager.PerDifficultyMaxMonsters[i].MaxMonsters[j] = PerPlayerMaxMonsters[j]; +} + function AddMutator(Mutator Mut) { - if (Mut == Self) - return; + if (Mut == Self) return; if (Mut.Class == Class) Mut.Destroy(); @@ -111,7 +188,7 @@ function bool CheckRelevance(Actor Other) // otherwise modify weapon lifespan if (PlayerWeap != None) { - PlayerWeap.Lifespan = 2147483647; + PlayerWeap.Lifespan = WeapDespawnTime; return SuperRelevant; } @@ -137,4 +214,5 @@ defaultproperties { SteamIDLen=17 UniqueIDLen=18 + CurrentVersion=2 // Config } diff --git a/MskGs/Classes/MskGsVoteCollector.uc b/MskGs/Classes/MskGsVoteCollector.uc index c12a7a5..7c27cef 100644 --- a/MskGs/Classes/MskGsVoteCollector.uc +++ b/MskGs/Classes/MskGsVoteCollector.uc @@ -1,4 +1,12 @@ -class MskGsVoteCollector extends KFVoteCollector; +class MskGsVoteCollector extends KFVoteCollector + dependson(MapStats); + +var string SortPolicy; +var bool bEnableMapStats; +var bool bOfficialNextMapOnly; +var bool bRandomizeNextMap; + +var private array ActiveMapCycle; var public array ImportantPersonList; var private array PunishList; @@ -200,37 +208,94 @@ reliable server function RecieveVoteKick(PlayerReplicationInfo PRI, bool bKick) } } -function int GetNextMap() +function LoadActiveMapCycle() +{ + local KFGameInfo KFGI; + + if (ActiveMapCycle.Length > 0) return; + + KFGI = KFGameInfo(WorldInfo.Game); + if (WorldInfo.NetMode == NM_Standalone) + ActiveMapCycle = Maplist; + else if (KFGI != None) + ActiveMapCycle = KFGI.GameMapCycles[KFGI.ActiveMapCycle].Maps; +} + +function bool IsOfficialMap(string MapName) +{ + local KFMapSummary MapData; + MapData = class'KFUIDataStore_GameResource'.static.GetMapSummaryFromMapName(MapName); + if (MapData == None) return False; + return (MapData.MapAssociation != EAI_Custom); +} + +function int GetNextMapIndex() { local KFGameInfo KFGI; - local array ActiveMapCycle; local array AviableMaps; local string Map; + local int CurrentMapIndex; + + KFGI = KFGameInfo(WorldInfo.Game); + if (KFGI == None) return INDEX_NONE; - if(MapVoteList.Length > 0) + LoadActiveMapCycle(); + if (bRandomizeNextMap) { - return MapVoteList[0].MapIndex; - } - else // random default map that exists in the active map cycle and allowed for current gamemode - { - KFGI = KFGameInfo(WorldInfo.Game); - if (KFGI == None) return -1; - - ActiveMapCycle = KFGI.GameMapCycles[KFGI.ActiveMapCycle].Maps; - - foreach class'KFGameViewportClient'.default.TripWireOfficialMaps(Map) - if (ActiveMapCycle.Find(Map) != -1 && KFGI.IsMapAllowedInCycle(Map)) + foreach ActiveMapCycle(Map) + { + if (bOfficialNextMapOnly && !IsOfficialMap(Map)) + continue; + if (KFGI.IsMapAllowedInCycle(Map)) AviableMaps.AddItem(Map); - - foreach class'KFGameViewportClient'.default.CommunityOfficialMaps(Map) - if (ActiveMapCycle.Find(Map) != -1 && KFGI.IsMapAllowedInCycle(Map)) - AviableMaps.AddItem(Map); - + } if (AviableMaps.Length > 0) return ActiveMapCycle.Find(AviableMaps[Rand(AviableMaps.Length)]); } + else if (ActiveMapCycle.Length > 0) + { + // I don't use KFGameInfo.GetNextMap() because + // it uses and changes global KFGameInfo.MapCycleIndex variable + CurrentMapIndex = ActiveMapCycle.Find(WorldInfo.GetMapName(true)); + if (CurrentMapIndex != INDEX_NONE) + { + for (CurrentMapIndex++; CurrentMapIndex < ActiveMapCycle.Length; CurrentMapIndex++) + { + if (bOfficialNextMapOnly && !IsOfficialMap(ActiveMapCycle[CurrentMapIndex])) + continue; + if (KFGI.IsMapAllowedInCycle(ActiveMapCycle[CurrentMapIndex])) + return CurrentMapIndex; + } + } + return 0; + } + + return INDEX_NONE; +} - return -1; +function int GetNextMap() +{ + local int MapIndex; + + if (MapVoteList.Length > 0) + MapIndex = MapVoteList[0].MapIndex; + else + MapIndex = GetNextMapIndex(); + + if (bEnableMapStats) + { + if (MapIndex == INDEX_NONE) + { + `log("[MskGsMut] Warn: MapIndex == INDEX_NONE, stats not saved"); + } + else + { + LoadActiveMapCycle(); + class'MapStats'.static.IncMapStat(ActiveMapCycle[MapIndex], SortPolicy); + } + } + + return MapIndex; } DefaultProperties diff --git a/make.sh b/make.sh index 59d3221..dc5f9f2 100644 --- a/make.sh +++ b/make.sh @@ -150,6 +150,7 @@ function upload () generate_wsinfo "$PackageDir" CMD //C "$(unixpath2win "$KFWorkshop")" "$MutWsInfoName" rm -rf "$PackageDir" + rm -rf "$MutPublish" rm -f "$MutWsInfo" }