From 7edd65b3e0bcd5c96d4c8b3becc9d2d50c92351f Mon Sep 17 00:00:00 2001 From: GenZmeY Date: Tue, 5 Jul 2022 16:09:48 +0300 Subject: [PATCH] first version --- .gitmodules | 3 + CTI/Classes/AddItems.uc | 66 +++++ CTI/Classes/CTI.uc | 247 ++++++++++++++++ CTI/Classes/CTI.upkg | 4 + CTI/Classes/CTIMut.uc | 62 ++++ CTI/Classes/CTI_GFxMenu_Trader.uc | 8 + CTI/Classes/CTI_GFxMoviePlayer_Manager.uc | 8 + CTI/Classes/CTI_GFxTraderContainer_Store.uc | 20 ++ CTI/Classes/CTI_RepInfo.uc | 309 ++++++++++++++++++++ CTI/Classes/Helper.uc | 73 +++++ CTI/Classes/RemoveItems.uc | 75 +++++ CTI/Classes/_Logger.uc | 19 ++ CTI/Constants.uci | 2 + CTI/Globals.uci | 3 + CTI/Logger.uci | 11 + PublicationContent/description.txt | 9 + PublicationContent/preview.png | Bin 0 -> 14467 bytes PublicationContent/tags.txt | 1 + PublicationContent/title.txt | 1 + builder.cfg | 52 ++++ tools | 1 + 21 files changed, 974 insertions(+) create mode 100644 .gitmodules create mode 100644 CTI/Classes/AddItems.uc create mode 100644 CTI/Classes/CTI.uc create mode 100644 CTI/Classes/CTI.upkg create mode 100644 CTI/Classes/CTIMut.uc create mode 100644 CTI/Classes/CTI_GFxMenu_Trader.uc create mode 100644 CTI/Classes/CTI_GFxMoviePlayer_Manager.uc create mode 100644 CTI/Classes/CTI_GFxTraderContainer_Store.uc create mode 100644 CTI/Classes/CTI_RepInfo.uc create mode 100644 CTI/Classes/Helper.uc create mode 100644 CTI/Classes/RemoveItems.uc create mode 100644 CTI/Classes/_Logger.uc create mode 100644 CTI/Constants.uci create mode 100644 CTI/Globals.uci create mode 100644 CTI/Logger.uci create mode 100644 PublicationContent/description.txt create mode 100644 PublicationContent/preview.png create mode 100644 PublicationContent/tags.txt create mode 100644 PublicationContent/title.txt create mode 100644 builder.cfg create mode 160000 tools diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..27ed978 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tools"] + path = tools + url = https://github.com/GenZmeY/KF2-BuildTools diff --git a/CTI/Classes/AddItems.uc b/CTI/Classes/AddItems.uc new file mode 100644 index 0000000..f2a8fe4 --- /dev/null +++ b/CTI/Classes/AddItems.uc @@ -0,0 +1,66 @@ +class AddItems extends Object + dependson(CTI) + config(CTI); + +var private config Array Item; + +public static function InitConfig(int Version, int LatestVersion) +{ + switch (Version) + { + case `NO_CONFIG: + ApplyDefault(); + + default: break; + } + + if (LatestVersion != Version) + { + StaticSaveConfig(); + } +} + +private static function ApplyDefault() +{ + default.Item.Length = 0; + default.Item.AddItem("SomePackage.SomeWeapon"); +} + +public static function Array > Load(E_LogLevel LogLevel) +{ + local Array > ItemList; + local class ItemClass; + local String ItemRaw; + local int Line; + + `Log_Info("Load Items to add:"); + foreach default.Item(ItemRaw, Line) + { + ItemClass = class(DynamicLoadObject(ItemRaw, class'Class')); + if (ItemClass == None) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "Can't load Item class:" @ ItemRaw); + } + else + { + ItemList.AddItem(ItemClass); + `Log_Debug("[" $ Line + 1 $ "]" @ "Loaded successfully:" @ ItemRaw); + } + } + + if (ItemList.Length == default.Item.Length) + { + `Log_Info("Items to add list loaded successfully (" $ default.Item.Length @ "entries)"); + } + else + { + `Log_Info("Items to add list: loaded" @ ItemList.Length @ "of" @ default.Item.Length @ "entries"); + } + + return ItemList; +} + +defaultproperties +{ + +} diff --git a/CTI/Classes/CTI.uc b/CTI/Classes/CTI.uc new file mode 100644 index 0000000..c2d2c64 --- /dev/null +++ b/CTI/Classes/CTI.uc @@ -0,0 +1,247 @@ +class CTI extends Info + config(CTI); + +const LatestVersion = 1; + +const CfgRemoveItems = class'RemoveItems'; +const CfgAddItems = class'AddItems'; +const Helper = class'Helper'; + +var private config int Version; +var private config E_LogLevel LogLevel; +var private config bool bPreloadContent; +var private config bool bForcePreloadContent; +var private config bool UnlockDLC; + +var private KFGameInfo KFGI; +var private KFGameReplicationInfo KFGRI; + +var private Array > RemoveItems; +var private Array > AddItems; + +var private Array RepInfos; + +var private bool ReadyToSync; + +public simulated function bool SafeDestroy() +{ + `Log_Trace(`Location); + + return (bPendingDelete || bDeleteMe || Destroy()); +} + +public event PreBeginPlay() +{ + `Log_Trace(`Location); + + `Log_Debug("PreBeginPlay readyToSync" @ ReadyToSync); + + if (WorldInfo.NetMode == NM_Client) + { + `Log_Fatal("NetMode == NM_Client, Destroy..."); + SafeDestroy(); + return; + } + + Super.PreBeginPlay(); + + PreInit(); +} + +public event PostBeginPlay() +{ + `Log_Trace(`Location); + + if (bPendingDelete || bDeleteMe) return; + + Super.PostBeginPlay(); + + PostInit(); +} + +private function PreInit() +{ + `Log_Trace(`Location); + + if (Version == `NO_CONFIG) + { + LogLevel = LL_Info; + bPreloadContent = true; + bForcePreloadContent = true; + UnlockDLC = false; + SaveConfig(); + } + + CfgRemoveItems.static.InitConfig(Version, LatestVersion); + CfgAddItems.static.InitConfig(Version, LatestVersion); + + switch (Version) + { + case `NO_CONFIG: + `Log_Info("Config created"); + + case MaxInt: + `Log_Info("Config updated to version"@LatestVersion); + break; + + case LatestVersion: + `Log_Info("Config is up-to-date"); + break; + + default: + `Log_Warn("The config version is higher than the current version (are you using an old mutator?)"); + `Log_Warn("Config version is" @ Version @ "but current version is" @ LatestVersion); + `Log_Warn("The config version will be changed to" @ LatestVersion); + break; + } + + if (LatestVersion != Version) + { + Version = LatestVersion; + SaveConfig(); + } + + if (LogLevel == LL_WrongLevel) + { + LogLevel = LL_Info; + `Log_Warn("Wrong 'LogLevel', return to default value"); + SaveConfig(); + } + `Log_Base("LogLevel:" @ LogLevel); + + RemoveItems = CfgRemoveItems.static.Load(LogLevel); + AddItems = CfgAddItems.static.Load(LogLevel); +} + +private function PostInit() +{ + local CTI_RepInfo RepLink; + + `Log_Trace(`Location); + + if (WorldInfo == None || WorldInfo.Game == None) + { + SetTimer(1.0f, false, nameof(PostInit)); + return; + } + + KFGI = KFGameInfo(WorldInfo.Game); + if (KFGI == None) + { + `Log_Fatal("Incompatible gamemode:" @ WorldInfo.Game); + SafeDestroy(); + return; + } + + if (UnlockDLC && KFGI.KFGFxManagerClass != class'CTI_GFxMoviePlayer_Manager') + { + KFGI.KFGFxManagerClass = class'CTI_GFxMoviePlayer_Manager'; + `Log_Info("DLC unlocked"); + } + + if (KFGI.GameReplicationInfo == None) + { + SetTimer(1.0f, false, nameof(PostInit)); + return; + } + + KFGRI = KFGameReplicationInfo(KFGI.GameReplicationInfo); + if (KFGRI == None) + { + `Log_Fatal("Incompatible Replication info:" @ KFGI.GameReplicationInfo); + SafeDestroy(); + return; + } + + Helper.static.ModifyTrader(KFGRI, RemoveItems, AddItems, CfgRemoveItems.default.bAll); + + if (bPreloadContent) + { + Helper.static.PreloadContent(AddItems); + } + + ReadyToSync = true; + + foreach RepInfos(RepLink) + { + if (RepLink.PendingSync) + { + RepLink.ServerSync(); + } + } +} + +public function NotifyLogin(Controller C) +{ + `Log_Trace(`Location); + + CreateRepLink(C); +} + +public function NotifyLogout(Controller C) +{ + `Log_Trace(`Location); + + DestroyRepLink(C); +} + +public function bool CreateRepLink(Controller C) +{ + local CTI_RepInfo RepLink; + + `Log_Trace(`Location); + + if (C == None) return false; + + RepLink = Spawn(class'CTI_RepInfo', C); + + if (RepLink == None) return false; + + RepLink.PrepareSync( + Self, + LogLevel, + RemoveItems, + AddItems, + CfgRemoveItems.default.bAll, + bPreloadContent, + bForcePreloadContent); + + RepInfos.AddItem(RepLink); + + if (ReadyToSync) + { + RepLink.ServerSync(); + } + else + { + RepLink.PendingSync = true; + } + + return true; +} + +public function bool DestroyRepLink(Controller C) +{ + local CTI_RepInfo RepLink; + + `Log_Trace(`Location); + + if (C == None) return false; + + foreach RepInfos(RepLink) + { + if (RepLink.Owner == C) + { + RepLink.SafeDestroy(); + RepInfos.RemoveItem(RepLink); + return true; + } + } + + return false; +} + +DefaultProperties +{ + ReadyToSync = false +} \ No newline at end of file diff --git a/CTI/Classes/CTI.upkg b/CTI/Classes/CTI.upkg new file mode 100644 index 0000000..29cb156 --- /dev/null +++ b/CTI/Classes/CTI.upkg @@ -0,0 +1,4 @@ +[Flags] +AllowDownload=True +ClientOptional=False +ServerSideOnly=False diff --git a/CTI/Classes/CTIMut.uc b/CTI/Classes/CTIMut.uc new file mode 100644 index 0000000..b4da5ee --- /dev/null +++ b/CTI/Classes/CTIMut.uc @@ -0,0 +1,62 @@ +class CTIMut extends KFMutator; + +var private CTI CTI; + +public simulated function bool SafeDestroy() +{ + return (bPendingDelete || bDeleteMe || Destroy()); +} + +public event PreBeginPlay() +{ + Super.PreBeginPlay(); + + if (WorldInfo.NetMode == NM_Client) return; + + foreach WorldInfo.DynamicActors(class'CTI', CTI) + { + `Log_Base("Found 'CTI'"); + break; + } + + if (CTI == None) + { + `Log_Base("Spawn 'CTI'"); + CTI = WorldInfo.Spawn(class'CTI'); + } + + if (CTI == None) + { + `Log_Base("Can't Spawn 'CTI', Destroy..."); + SafeDestroy(); + } +} + +public function AddMutator(Mutator Mut) +{ + if (Mut == Self) return; + + if (Mut.Class == Class) + Mut.Destroy(); + else + Super.AddMutator(Mut); +} + +public function NotifyLogin(Controller C) +{ + Super.NotifyLogin(C); + + CTI.NotifyLogin(C); +} + +public function NotifyLogout(Controller C) +{ + Super.NotifyLogout(C); + + CTI.NotifyLogout(C); +} + +DefaultProperties +{ + +} \ No newline at end of file diff --git a/CTI/Classes/CTI_GFxMenu_Trader.uc b/CTI/Classes/CTI_GFxMenu_Trader.uc new file mode 100644 index 0000000..09faf43 --- /dev/null +++ b/CTI/Classes/CTI_GFxMenu_Trader.uc @@ -0,0 +1,8 @@ +class CTI_GFxMenu_Trader extends KFGFxMenu_Trader + dependsOn(CTI_GFxTraderContainer_Store); + +defaultproperties +{ + SubWidgetBindings.Remove((WidgetName="shopContainer",WidgetClass=class'KFGFxTraderContainer_Store')) + SubWidgetBindings.Add((WidgetName="shopContainer",WidgetClass=class'CTI_GFxTraderContainer_Store')) +} diff --git a/CTI/Classes/CTI_GFxMoviePlayer_Manager.uc b/CTI/Classes/CTI_GFxMoviePlayer_Manager.uc new file mode 100644 index 0000000..78604f3 --- /dev/null +++ b/CTI/Classes/CTI_GFxMoviePlayer_Manager.uc @@ -0,0 +1,8 @@ +class CTI_GFxMoviePlayer_Manager extends KFGFxMoviePlayer_Manager + dependsOn(CTI_GFxMenu_Trader); + +defaultproperties +{ + WidgetBindings.Remove((WidgetName="traderMenu",WidgetClass=class'KFGFxMenu_Trader')) + WidgetBindings.Add((WidgetName="traderMenu",WidgetClass=class'CTI_GFxMenu_Trader')) +} diff --git a/CTI/Classes/CTI_GFxTraderContainer_Store.uc b/CTI/Classes/CTI_GFxTraderContainer_Store.uc new file mode 100644 index 0000000..37ce4be --- /dev/null +++ b/CTI/Classes/CTI_GFxTraderContainer_Store.uc @@ -0,0 +1,20 @@ +class CTI_GFxTraderContainer_Store extends KFGFxTraderContainer_Store; + +function bool IsItemFiltered(STraderItem Item, optional bool bDebug) +{ + if (KFPC.GetPurchaseHelper().IsInOwnedItemList(Item.ClassName)) + return true; + if (KFPC.GetPurchaseHelper().IsInOwnedItemList(Item.DualClassName)) + return true; + if (!KFPC.GetPurchaseHelper().IsSellable(Item)) + return true; + if (Item.WeaponDef.default.PlatformRestriction != PR_All && class'KFUnlockManager'.static.IsPlatformRestricted(Item.WeaponDef.default.PlatformRestriction)) + return true; + + return false; +} + +defaultproperties +{ + +} diff --git a/CTI/Classes/CTI_RepInfo.uc b/CTI/Classes/CTI_RepInfo.uc new file mode 100644 index 0000000..a2bda06 --- /dev/null +++ b/CTI/Classes/CTI_RepInfo.uc @@ -0,0 +1,309 @@ +class CTI_RepInfo extends ReplicationInfo; + +const Helper = class'Helper'; + +var public bool PendingSync; + +var private CTI CTI; +var private E_LogLevel LogLevel; +var private Array > RemoveItems; +var private Array > AddItems; +var private bool ReplaceMode; +var private bool PreloadContent; +var private bool ForcePreloadContent; + +var private int Recieved; +var private int SyncSize; + +var private KFGFxWidget_PartyInGame PartyInGameWidget; +var private GFxObject Notification; + +replication +{ + if (bNetInitial && Role == ROLE_Authority) + LogLevel, ReplaceMode, PreloadContent, ForcePreloadContent, SyncSize; +} + +public simulated function bool SafeDestroy() +{ + `Log_Trace(`Location); + + return (bPendingDelete || bDeleteMe || Destroy()); +} + +public function PrepareSync( + CTI _CTI, + E_LogLevel _LogLevel, + Array > _RemoveItems, + Array > _AddItems, + bool _ReplaceMode, + bool _PreloadContent, + bool _ForcePreloadContent) +{ + CTI = _CTI; + LogLevel = _LogLevel; + RemoveItems = _RemoveItems; + AddItems = _AddItems; + ReplaceMode = _ReplaceMode; + PreloadContent = _PreloadContent; + ForcePreloadContent = _ForcePreloadContent; + SyncSize = RemoveItems.Length + AddItems.Length; +} + +private simulated function PlayerController GetPlayerController() +{ + local PlayerController PC; + + PC = PlayerController(Owner); + + if (PC == None && ROLE < ROLE_Authority) + { + PC = GetALocalPlayerController(); + } + + return PC; +} + +private simulated function SetPartyInGameWidget() +{ + local KFPlayerController KFPC; + + `Log_Trace(`Location); + + KFPC = KFPlayerController(GetPlayerController()); + if (KFPC == None) return; + if (KFPC.MyGFxManager == None) return; + if (KFPC.MyGFxManager.PartyWidget == None) return; + + PartyInGameWidget = KFGFxWidget_PartyInGame(KFPC.MyGFxManager.PartyWidget); + Notification = PartyInGameWidget.Notification; +} + +private simulated function bool CheckPartyInGameWidget() +{ + if (PartyInGameWidget == None) + { + SetPartyInGameWidget(); + } + + return (PartyInGameWidget != None); +} + +private simulated function UpdateNotification(String Title, String Downloading, String Remainig, int Percent) +{ + if (Notification != None) + { + Notification.SetString("itemName", Title); + Notification.SetFloat("percent", Percent); + Notification.SetInt("queue", 0); + Notification.SetString("downLoading", Downloading); + Notification.SetString("remaining", Remainig); + Notification.SetObject("notificationInfo", Notification); + Notification.SetVisible(true); + } +} + +private reliable client function ClientSync(class WeapDef, optional bool Remove = false) +{ + `Log_Trace(`Location); + + if (WeapDef == None) + { + `Log_Fatal("WeapDef is:" @ WeapDef); + SafeDestroy(); + return; + } + + if (CheckPartyInGameWidget()) + { + PartyInGameWidget.SetReadyButtonVisibility(false); + } + + if (Remove) + { + RemoveItems.AddItem(WeapDef); + } + else + { + AddItems.AddItem(WeapDef); + } + + Recieved = RemoveItems.Length + AddItems.Length; + if (CheckPartyInGameWidget()) + { + UpdateNotification( + "Sync items, please wait...", + Remove ? "-" : "+" @ Repl(String(WeapDef), "KFWeapDef_", ""), + Recieved @ "/" @ SyncSize, + (float(Recieved) / float(SyncSize)) * 100); + } + + if (Recieved == SyncSize && (PreloadContent || ForcePreloadContent)) + { + if (CheckPartyInGameWidget()) + { + UpdateNotification( + "Preload Content, please wait...", + "Game isn't frozen", + "Don't panic", + 0); + } + } + + ServerSync(); +} + +private simulated reliable client function SyncFinished() +{ + local KFGameReplicationInfo KFGRI; + + `Log_Trace(`Location); + + if (WorldInfo == None || WorldInfo.GRI == None) + { + SetTimer(1.0f, false, nameof(SyncFinished)); + return; + } + + KFGRI = KFGameReplicationInfo(WorldInfo.GRI); + if (KFGRI == None) + { + `Log_Fatal("Incompatible Replication info:" @ WorldInfo.GRI); + SafeDestroy(); + return; + } + + Helper.static.ModifyTrader(KFGRI, RemoveItems, AddItems, ReplaceMode); + + if (PreloadContent) + { + Helper.static.PreloadContent(AddItems); + } + if (ForcePreloadContent) + { + PreloadContentWorkaround(); + } + + if (CheckPartyInGameWidget()) + { + Notification.SetVisible(false); + PartyInGameWidget.SetReadyButtonVisibility(true); + PartyInGameWidget.UpdateReadyButtonText(); + PartyInGameWidget.UpdateReadyButtonVisibility(); + } + + SafeDestroy(); +} + +public reliable server function ServerSync() +{ + `Log_Trace(`Location); + + PendingSync = false; + + if (bPendingDelete || bDeleteMe) return; + + if (SyncSize <= Recieved || WorldInfo.NetMode == NM_StandAlone) + { + SyncFinished(); + if (!CTI.DestroyRepLink(Controller(Owner))) + { + SafeDestroy(); + } + } + else + { + if (Recieved < RemoveItems.Length) + { + ClientSync(RemoveItems[Recieved++], true); + } + else + { + ClientSync(AddItems[Recieved++ - RemoveItems.Length], false); + } + } +} + +private simulated function PreloadContentWorkaround() +{ + local PlayerController PC; + local Pawn P; + local KFInventoryManager KFIM; + local class CW; + local Weapon W; + local int Index; + local DroppedPickup DP; + local float Time; + + `Log_Trace(`Location); + + PC = GetPlayerController(); + + if (PC == None) + { + SetTimer(0.1f, false, nameof(PreloadContentWorkaround)); + return; + } + + P = PC.Pawn; + if (P == None) + { + SetTimer(0.1f, false, nameof(PreloadContentWorkaround)); + return; + } + + KFIM = KFInventoryManager(P.InvManager); + if (KFIM == None) + { + SetTimer(0.1f, false, nameof(PreloadContentWorkaround)); + return; + } + + KFIM.bInfiniteWeight = true; + Time = WorldInfo.TimeSeconds - 1.0f; + + for (Index = 0; Index < AddItems.Length; Index++) + { + CW = class (DynamicLoadObject(AddItems[Index].default.WeaponClassPath, class'Class')); + if (CW != None && Weapon(P.FindInventoryType(CW)) == None) + { + P.CreateInventory(CW); + } + } + + foreach KFIM.InventoryActors(class'Weapon', W) + { + if (W != None) + { + KFIM.PendingWeapon = W; + KFIM.ChangedWeapon(); + if (W.CanThrow()) + { + P.TossInventory(W); + W.Destroy(); + } + } + } + + foreach WorldInfo.DynamicActors(class'DroppedPickup', DP) + { + if (DP.Instigator == P && DP.CreationTime > Time) + { + DP.Destroy(); + } + } + + KFIM.bInfiniteWeight = false; + + `Log_Info("Force Preload Finished"); +} + +defaultproperties +{ + bAlwaysRelevant = false + bOnlyRelevantToOwner = true + bSkipActorPropertyReplication = false + + PendingSync = false + Recieved = 0 +} diff --git a/CTI/Classes/Helper.uc b/CTI/Classes/Helper.uc new file mode 100644 index 0000000..83a2a36 --- /dev/null +++ b/CTI/Classes/Helper.uc @@ -0,0 +1,73 @@ +class Helper extends Object; + +private delegate int ByPrice(class A, class B) +{ + return A.default.BuyPrice > B.default.BuyPrice ? -1 : 0; +} + +public static simulated function ModifyTrader( + KFGameReplicationInfo KFGRI, + Array > RemoveItems, + Array > AddItems, + bool ReplaceMode) +{ + local KFGFxObject_TraderItems TraderItems; + local STraderItem Item; + local class WeapDef; + local Array > WeapDefs; + local int Index; + local int MaxItemID; + + if (KFGRI == None) return; + + TraderItems = KFGFxObject_TraderItems(DynamicLoadObject(KFGRI.TraderItemsPath, class'KFGFxObject_TraderItems')); + + if (!ReplaceMode) + { + foreach TraderItems.SaleItems(Item) + { + if (Item.WeaponDef != None && RemoveItems.Find(Item.WeaponDef) == INDEX_NONE) + { + WeapDefs.AddItem(Item.WeaponDef); + } + } + } + + for (Index = 0; Index < AddItems.Length; Index++) + WeapDefs.AddItem(AddItems[Index]); + + WeapDefs.Sort(ByPrice); + + TraderItems.SaleItems.Length = 0; + MaxItemID = 0; + foreach WeapDefs(WeapDef) + { + Item.WeaponDef = WeapDef; + Item.ItemID = ++MaxItemID; + TraderItems.SaleItems.AddItem(Item); + } + + TraderItems.SetItemsInfo(TraderItems.SaleItems); + + KFGRI.TraderItems = TraderItems; +} + +public static function PreloadContent(Array > WeapDefs) +{ + local class KFW; + local int Index; + + for (Index = 0; Index < WeapDefs.Length; Index++) + { + KFW = class (DynamicLoadObject(WeapDefs[Index].default.WeaponClassPath, class'Class')); + if (KFW != None) + { + class'KFWeapon'.static.TriggerAsyncContentLoad(KFW); + } + } +} + +defaultproperties +{ + +} \ No newline at end of file diff --git a/CTI/Classes/RemoveItems.uc b/CTI/Classes/RemoveItems.uc new file mode 100644 index 0000000..46fdb50 --- /dev/null +++ b/CTI/Classes/RemoveItems.uc @@ -0,0 +1,75 @@ +class RemoveItems extends Object + dependson(CTI) + config(CTI); + +var public config bool bAll; +var private config Array Item; + +public static function InitConfig(int Version, int LatestVersion) +{ + switch (Version) + { + case `NO_CONFIG: + ApplyDefault(); + + default: break; + } + + if (LatestVersion != Version) + { + StaticSaveConfig(); + } +} + +private static function ApplyDefault() +{ + default.bAll = false; + default.Item.Length = 0; + default.Item.AddItem("KFGame.KFWeapDef_9mmDual"); +} + +public static function Array > Load(E_LogLevel LogLevel) +{ + local Array > ItemList; + local class ItemClass; + local String ItemRaw; + local int Line; + + `Log_Info("Load items to remove:"); + if (default.bAll) + { + `Log_Info("Remove all default items"); + } + else + { + foreach default.Item(ItemRaw, Line) + { + ItemClass = class(DynamicLoadObject(ItemRaw, class'Class')); + if (ItemClass == None) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "Can't load item class:" @ ItemRaw); + } + else + { + ItemList.AddItem(ItemClass); + `Log_Debug("[" $ Line + 1 $ "]" @ "Loaded successfully:" @ ItemRaw); + } + } + + if (ItemList.Length == default.Item.Length) + { + `Log_Info("Items to remove list loaded successfully (" $ default.Item.Length @ "entries)"); + } + else + { + `Log_Info("Items to remove list: loaded" @ ItemList.Length @ "of" @ default.Item.Length @ "entries"); + } + } + + return ItemList; +} + +defaultproperties +{ + +} diff --git a/CTI/Classes/_Logger.uc b/CTI/Classes/_Logger.uc new file mode 100644 index 0000000..69fb0e7 --- /dev/null +++ b/CTI/Classes/_Logger.uc @@ -0,0 +1,19 @@ +class _Logger extends Object + abstract; + +enum E_LogLevel +{ + LL_WrongLevel, + LL_Fatal, + LL_Error, + LL_Warning, + LL_Info, + LL_Debug, + LL_Trace, + LL_All +}; + +defaultproperties +{ + +} diff --git a/CTI/Constants.uci b/CTI/Constants.uci new file mode 100644 index 0000000..1003f19 --- /dev/null +++ b/CTI/Constants.uci @@ -0,0 +1,2 @@ +// Constants +`define NO_CONFIG 0 diff --git a/CTI/Globals.uci b/CTI/Globals.uci new file mode 100644 index 0000000..a48ac52 --- /dev/null +++ b/CTI/Globals.uci @@ -0,0 +1,3 @@ +// Imports +`include(Logger.uci) +`include(Constants.uci) diff --git a/CTI/Logger.uci b/CTI/Logger.uci new file mode 100644 index 0000000..cb2e407 --- /dev/null +++ b/CTI/Logger.uci @@ -0,0 +1,11 @@ +// Logger +`define Log_Tag 'CTI' + +`define Log_Base(msg, cond) `log(`msg `if(`cond), `cond`{endif}, `Log_Tag) + +`define Log_Fatal(msg) `log("FATAL:" @ `msg, (LogLevel >= LL_Fatal), `Log_Tag) +`define Log_Error(msg) `log("ERROR:" @ `msg, (LogLevel >= LL_Error), `Log_Tag) +`define Log_Warn(msg) `log("WARN:" @ `msg, (LogLevel >= LL_Warning), `Log_Tag) +`define Log_Info(msg) `log("INFO:" @ `msg, (LogLevel >= LL_Info), `Log_Tag) +`define Log_Debug(msg) `log("DEBUG:" @ `msg, (LogLevel >= LL_Debug), `Log_Tag) +`define Log_Trace(msg) `log("TRACE:" @ `msg, (LogLevel >= LL_Trace), `Log_Tag) diff --git a/PublicationContent/description.txt b/PublicationContent/description.txt new file mode 100644 index 0000000..3139b14 --- /dev/null +++ b/PublicationContent/description.txt @@ -0,0 +1,9 @@ +[h1]Custom Trader Inventory[/h1] + +[h1]Description[/h1] +description will come later... + +[b]Mutator:[/b] CTI.CTIMut + +[h1]Sources[/h1] +[url=https://github.com/GenZmeY/KF2-CustomTraderInventory]https://github.com/GenZmeY/KF2-CustomTraderInventory[/url] diff --git a/PublicationContent/preview.png b/PublicationContent/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..c1fc1c321d566d4bcc6c21d15630e8976f65eda6 GIT binary patch literal 14467 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