diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..80643f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.psd +/ignore \ No newline at end of file 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/LTI/Classes/LTI.uc b/LTI/Classes/LTI.uc new file mode 100644 index 0000000..c7b9021 --- /dev/null +++ b/LTI/Classes/LTI.uc @@ -0,0 +1,228 @@ +class LTI extends Info + config(LTI); + +const LatestVersion = 1; + +const CfgRemoveItems = class'RemoveItems'; +const CfgOfficialWeapons = class'OfficialWeapons'; +const Trader = class'Trader'; + +var private config int Version; +var private config E_LogLevel LogLevel; +var private config bool bOfficialWeaponsList; + +var private KFGameInfo KFGI; +var private KFGameReplicationInfo KFGRI; + +var private Array > RemoveItems; +var private Array RepInfos; +var private bool ReadyToSync; + +public simulated function bool SafeDestroy() +{ + `Log_Trace(); + + return (bPendingDelete || bDeleteMe || Destroy()); +} + +public event PreBeginPlay() +{ + `Log_Trace(); + + `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(); + + if (bPendingDelete || bDeleteMe) return; + + Super.PostBeginPlay(); + + PostInit(); +} + +private function PreInit() +{ + `Log_Trace(); + + if (Version == `NO_CONFIG) + { + LogLevel = LL_Info; + SaveConfig(); + } + + CfgRemoveItems.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; + } + + CfgOfficialWeapons.static.Update(bOfficialWeaponsList); + + 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); +} + +private function PostInit() +{ + local LTI_RepInfo RepInfo; + + `Log_Trace(); + + 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 (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; + } + + Trader.static.ModifyTrader(KFGRI, RemoveItems, CfgRemoveItems.default.bAll, LogLevel); + + ReadyToSync = true; + + foreach RepInfos(RepInfo) + { + if (RepInfo.PendingSync) + { + RepInfo.ServerSync(); + } + } +} + +public function NotifyLogin(Controller C) +{ + `Log_Trace(); + + if (!CreateRepInfo(C)) + { + `Log_Error("Can't create RepInfo for:" @ C); + } +} + +public function NotifyLogout(Controller C) +{ + `Log_Trace(); + + DestroyRepInfo(C); +} + +public function bool CreateRepInfo(Controller C) +{ + local LTI_RepInfo RepInfo; + + `Log_Trace(); + + if (C == None) return false; + + RepInfo = Spawn(class'LTI_RepInfo', C); + + if (RepInfo == None) return false; + + RepInfo.PrepareSync( + Self, + LogLevel, + RemoveItems, + CfgRemoveItems.default.bAll); + + RepInfos.AddItem(RepInfo); + + if (ReadyToSync) + { + RepInfo.ServerSync(); + } + else + { + RepInfo.PendingSync = true; + } + + return true; +} + +public function bool DestroyRepInfo(Controller C) +{ + local LTI_RepInfo RepInfo; + + `Log_Trace(); + + if (C == None) return false; + + foreach RepInfos(RepInfo) + { + if (RepInfo.Owner == C) + { + RepInfos.RemoveItem(RepInfo); + RepInfo.SafeDestroy(); + return true; + } + } + + return false; +} + +DefaultProperties +{ + ReadyToSync = false +} \ No newline at end of file diff --git a/LTI/Classes/LTI.upkg b/LTI/Classes/LTI.upkg new file mode 100644 index 0000000..29cb156 --- /dev/null +++ b/LTI/Classes/LTI.upkg @@ -0,0 +1,4 @@ +[Flags] +AllowDownload=True +ClientOptional=False +ServerSideOnly=False diff --git a/LTI/Classes/LTIMut.uc b/LTI/Classes/LTIMut.uc new file mode 100644 index 0000000..63f166c --- /dev/null +++ b/LTI/Classes/LTIMut.uc @@ -0,0 +1,60 @@ +class LTIMut extends KFMutator; + +var private LTI LTI; + +public simulated function bool SafeDestroy() +{ + return (bPendingDelete || bDeleteMe || Destroy()); +} + +public event PreBeginPlay() +{ + Super.PreBeginPlay(); + + if (WorldInfo.NetMode == NM_Client) return; + + foreach WorldInfo.DynamicActors(class'LTI', LTI) + { + break; + } + + if (LTI == None) + { + LTI = WorldInfo.Spawn(class'LTI'); + } + + if (LTI == None) + { + `Log_Base("FATAL: Can't Spawn 'LTI'"); + 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) +{ + LTI.NotifyLogin(C); + + Super.NotifyLogin(C); +} + +public function NotifyLogout(Controller C) +{ + LTI.NotifyLogout(C); + + Super.NotifyLogout(C); +} + +DefaultProperties +{ + +} \ No newline at end of file diff --git a/LTI/Classes/LTI_LocalMessage.uc b/LTI/Classes/LTI_LocalMessage.uc new file mode 100644 index 0000000..ab5614f --- /dev/null +++ b/LTI/Classes/LTI_LocalMessage.uc @@ -0,0 +1,73 @@ +class LTI_LocalMessage extends Object + abstract; + +var const String SyncItemsDefault; +var private localized String SyncItems; + +var const String SyncFinishedDefault; +var private localized String SyncFinished; + +var const String WaitingGRIDefault; +var private localized String WaitingGRI; + +var const String IncompatibleGRIDefault; +var private localized String IncompatibleGRI; + +var const String DisconnectDefault; +var private localized String Disconnect; + +var const String SecondsShortDefault; +var private localized String SecondsShort; + +enum E_LTI_LocalMessageType +{ + LTI_SyncItems, + LTI_SyncFinished, + LTI_WaitingGRI, + LTI_IncompatibleGRI, + LTI_Disconnect, + LTI_SecondsShort +}; + +public static function String GetLocalizedString( + E_LogLevel LogLevel, + E_LTI_LocalMessageType LMT, + optional String String1, + optional String String2, + optional String String3) +{ + `Log_TraceStatic(); + + switch (LMT) + { + case LTI_SyncItems: + return (default.SyncItems != "" ? default.SyncItems : default.SyncItemsDefault); + + case LTI_SyncFinished: + return (default.SyncFinished != "" ? default.SyncFinished : default.SyncFinishedDefault); + + case LTI_WaitingGRI: + return (default.WaitingGRI != "" ? default.WaitingGRI : default.WaitingGRIDefault); + + case LTI_IncompatibleGRI: + return (default.IncompatibleGRI != "" ? default.IncompatibleGRI : default.IncompatibleGRIDefault); + + case LTI_Disconnect: + return (default.Disconnect != "" ? default.Disconnect : default.DisconnectDefault); + + case LTI_SecondsShort: + return (default.SecondsShort != "" ? default.SecondsShort : default.SecondsShortDefault); + } + + return ""; +} + +defaultproperties +{ + SyncItemsDefault = "Sync items:" + SyncFinishedDefault = "Sync finished." + WaitingGRIDefault = "Waiting GRI..." + IncompatibleGRIDefault = "Incompatible GRI:" + DisconnectDefault = "Disconnect..." + SecondsShortDefault = "s" +} \ No newline at end of file diff --git a/LTI/Classes/LTI_RepInfo.uc b/LTI/Classes/LTI_RepInfo.uc new file mode 100644 index 0000000..2de3bd1 --- /dev/null +++ b/LTI/Classes/LTI_RepInfo.uc @@ -0,0 +1,276 @@ +class LTI_RepInfo extends ReplicationInfo; + +const Trader = class'Trader'; +const LocalMessage = class'LTI_LocalMessage'; + +var public bool PendingSync; + +var private LTI LTI; +var private E_LogLevel LogLevel; +var private Array > RemoveItems; +var private bool ReplaceMode; + +var private int Recieved; +var private int SyncSize; + +var private KFPlayerController KFPC; +var private KFGFxWidget_PartyInGame PartyInGameWidget; +var private GFxObject Notification; + +var private String NotificationHeaderText; +var private String NotificationLeftText; +var private String NotificationRightText; +var private int NotificationPercent; + +var private int WaitingGRI; + +replication +{ + if (bNetInitial && Role == ROLE_Authority) + LogLevel, ReplaceMode, SyncSize; +} + +public simulated function bool SafeDestroy() +{ + `Log_Trace(); + + return (bPendingDelete || bDeleteMe || Destroy()); +} + +public function PrepareSync( + LTI _LTI, + E_LogLevel _LogLevel, + Array > _RemoveItems, + bool _ReplaceMode) +{ + `Log_Trace(); + + LTI = _LTI; + LogLevel = _LogLevel; + RemoveItems = _RemoveItems; + ReplaceMode = _ReplaceMode; + SyncSize = RemoveItems.Length; +} + +private simulated function KFPlayerController GetKFPC() +{ + `Log_Trace(); + + if (KFPC != None) return KFPC; + + KFPC = KFPlayerController(Owner); + + if (KFPC == None && ROLE < ROLE_Authority) + { + KFPC = KFPlayerController(GetALocalPlayerController()); + } + + return KFPC; +} + +private simulated function SetPartyInGameWidget() +{ + `Log_Trace(); + + if (GetKFPC() == 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() +{ + `Log_Trace(); + + if (PartyInGameWidget == None) + { + SetPartyInGameWidget(); + } + + return (PartyInGameWidget != None); +} + +private simulated function HideReadyButton() +{ + `Log_Trace(); + + if (CheckPartyInGameWidget()) + { + PartyInGameWidget.SetReadyButtonVisibility(false); + } +} + +private simulated function ShowReadyButton() +{ + `Log_Trace(); + + if (CheckPartyInGameWidget()) + { + Notification.SetVisible(false); + PartyInGameWidget.SetReadyButtonVisibility(true); + PartyInGameWidget.UpdateReadyButtonText(); + PartyInGameWidget.UpdateReadyButtonVisibility(); + } +} + +private simulated function UpdateNotification(String Title, String Left, String Right, int Percent) +{ + `Log_Trace(); + + if (CheckPartyInGameWidget() && Notification != None) + { + Notification.SetString("itemName", Title); + Notification.SetFloat("percent", Percent); + Notification.SetInt("queue", 0); + Notification.SetString("downLoading", Left); + Notification.SetString("remaining", Right); + Notification.SetObject("notificationInfo", Notification); + Notification.SetVisible(true); + } +} + +private reliable client function ClientSync(class WeapDef) +{ + `Log_Trace(); + + if (WeapDef == None) + { + `Log_Fatal("WeapDef is:" @ WeapDef); + Cleanup(); + ConsoleCommand("Disconnect"); + SafeDestroy(); + return; + } + + if (!IsTimerActive(nameof(KeepNotification))) + { + SetTimer(0.1f, true, nameof(KeepNotification)); + } + + RemoveItems.AddItem(WeapDef); + + Recieved = RemoveItems.Length; + + NotificationHeaderText = "-" @ WeapDef.static.GetItemName(); + NotificationLeftText = LocalMessage.static.GetLocalizedString(LogLevel, LTI_SyncItems); + NotificationRightText = Recieved @ "/" @ SyncSize; + if (SyncSize != 0) + { + NotificationPercent = (float(Recieved) / float(SyncSize)) * 100; + } + + `Log_Debug("ClientSync: -" @ String(WeapDef) @ NotificationRightText); + + ServerSync(); +} + +private simulated function KeepNotification() +{ + HideReadyButton(); + UpdateNotification( + NotificationHeaderText, + NotificationLeftText, + NotificationRightText, + NotificationPercent); +} + +private simulated reliable client function ClientSyncFinished() +{ + local KFGameReplicationInfo KFGRI; + + `Log_Trace(); + + NotificationLeftText = ""; + NotificationRightText = ""; + NotificationPercent = 0; + + if (WorldInfo.GRI == None) + { + `Log_Debug("ClientSyncFinished: Waiting GRI"); + NotificationHeaderText = LocalMessage.static.GetLocalizedString(LogLevel, LTI_WaitingGRI); + NotificationLeftText = String(++WaitingGRI) $ LocalMessage.static.GetLocalizedString(LogLevel, LTI_SecondsShort); + NotificationRightText = ""; + SetTimer(1.0f, false, nameof(ClientSyncFinished)); + return; + } + + KFGRI = KFGameReplicationInfo(WorldInfo.GRI); + if (KFGRI == None) + { + `Log_Fatal("Incompatible Replication info:" @ String(WorldInfo.GRI)); + ClearTimer(nameof(KeepNotification)); + UpdateNotification( + LocalMessage.static.GetLocalizedString(LogLevel, LTI_IncompatibleGRI) @ String(WorldInfo.GRI), + LocalMessage.static.GetLocalizedString(LogLevel, LTI_Disconnect), "", 0); + Cleanup(); + ConsoleCommand("Disconnect"); + SafeDestroy(); + return; + } + + NotificationHeaderText = LocalMessage.static.GetLocalizedString(LogLevel, LTI_SyncFinished); + NotificationLeftText = ""; + NotificationRightText = ""; + NotificationPercent = 0; + + Trader.static.ModifyTrader(KFGRI, RemoveItems, ReplaceMode, LogLevel); + `Log_Debug("ClientSyncFinished: Trader.static.ModifyTrader"); + + ClearTimer(nameof(KeepNotification)); + ShowReadyButton(); + + Cleanup(); + + SafeDestroy(); +} + +private reliable server function Cleanup() +{ + `Log_Trace(); + + `Log_Debug("Cleanup"); + if (!LTI.DestroyRepInfo(Controller(Owner))) + { + `Log_Debug("Cleanup (forced)"); + SafeDestroy(); + } +} + +public reliable server function ServerSync() +{ + `Log_Trace(); + + PendingSync = false; + + if (bPendingDelete || bDeleteMe) return; + + if (SyncSize <= Recieved || WorldInfo.NetMode == NM_StandAlone) + { + `Log_Debug("ServerSync: Finished"); + ClientSyncFinished(); + } + else + { + if (Recieved < RemoveItems.Length) + { + `Log_Debug("ServerSync[-]:" @ (Recieved + 1) @ "/" @ SyncSize @ RemoveItems[Recieved]); + ClientSync(RemoveItems[Recieved++]); + } + } +} + +defaultproperties +{ + bAlwaysRelevant = false + bOnlyRelevantToOwner = true + bSkipActorPropertyReplication = false + + PendingSync = false + Recieved = 0 + + NotificationPercent = 0 + WaitingGRI = 0 +} diff --git a/LTI/Classes/OfficialWeapons.uc b/LTI/Classes/OfficialWeapons.uc new file mode 100644 index 0000000..eacf954 --- /dev/null +++ b/LTI/Classes/OfficialWeapons.uc @@ -0,0 +1,43 @@ +class OfficialWeapons extends Object + config(LTI); + +const Trader = class'Trader'; +const DefaultComment = "Auto-generated list of official weapons for your convenience, copy-paste ready"; + +var private config String Comment; +var private config Array Item; + +private delegate int ByName(String A, String B) +{ + return A > B ? -1 : 0; +} + +public static function Update(bool Enabled) +{ + local Array > KFWeapDefs; + local class KFWeapDef; + + if (!Enabled) return; + + KFWeapDefs = Trader.static.GetTraderWeapDefs(); + + if (default.Item.Length != KFWeapDefs.Length || default.Comment != DefaultComment) + { + default.Comment = DefaultComment; + default.Item.Length = 0; + + foreach KFWeapDefs(KFWeapDef) + { + default.Item.AddItem(KFWeapDef.GetPackageName() $ "." $ KFWeapDef); + } + + default.Item.Sort(ByName); + + StaticSaveConfig(); + } +} + +defaultproperties +{ + +} diff --git a/LTI/Classes/RemoveItems.uc b/LTI/Classes/RemoveItems.uc new file mode 100644 index 0000000..47f3508 --- /dev/null +++ b/LTI/Classes/RemoveItems.uc @@ -0,0 +1,88 @@ +class RemoveItems extends Object + dependson(LTI) + config(LTI); + +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 ItemWeapDef; + local class ItemWeapon; + 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) + { + ItemWeapDef = class(DynamicLoadObject(ItemRaw, class'Class')); + if (ItemWeapDef == None) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "Can't load weapon definition:" @ ItemRaw); + continue; + } + + ItemWeapon = class(DynamicLoadObject(ItemWeapDef.default.WeaponClassPath, class'Class')); + if (ItemWeapon == None) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "Can't load weapon:" @ ItemWeapDef.default.WeaponClassPath); + continue; + } + + if (ItemList.Find(ItemWeapDef) != INDEX_NONE) + { + `Log_Warn("[" $ Line + 1 $ "]" @ "Duplicate item:" @ ItemRaw @ "(skip)"); + continue; + } + + ItemList.AddItem(ItemWeapDef); + `Log_Debug("[" $ Line + 1 $ "]" @ "Loaded successfully:" @ ItemRaw); + } + + if (ItemList.Length == default.Item.Length) + { + `Log_Info("Items to remove list loaded successfully (" $ ItemList.Length @ "entries)"); + } + else + { + `Log_Info("Items to remove list: loaded" @ ItemList.Length @ "of" @ default.Item.Length @ "entries"); + } + } + + return ItemList; +} + +defaultproperties +{ + +} diff --git a/LTI/Classes/Trader.uc b/LTI/Classes/Trader.uc new file mode 100644 index 0000000..c5ea165 --- /dev/null +++ b/LTI/Classes/Trader.uc @@ -0,0 +1,115 @@ +class Trader extends Object + abstract; + +private delegate int ByPrice(class A, class B) +{ + return A.default.BuyPrice > B.default.BuyPrice ? -1 : 0; +} + +public static function KFGFxObject_TraderItems GetTraderItems(optional KFGameReplicationInfo KFGRI = None, optional E_LogLevel LogLevel = LL_Trace) +{ + local String TraderItemsPath; + + if (KFGRI == None) + { + TraderItemsPath = class'KFGameReplicationInfo'.default.TraderItemsPath; + } + else + { + TraderItemsPath = KFGRI.TraderItemsPath; + } + + return KFGFxObject_TraderItems(DynamicLoadObject(TraderItemsPath, class'KFGFxObject_TraderItems')); +} + +public static function Array > GetTraderWeapDefs(optional KFGameReplicationInfo KFGRI = None,optional E_LogLevel LogLevel = LL_Trace) +{ + local Array > KFWeapDefs; + local KFGFxObject_TraderItems TraderItems; + local STraderItem Item; + + TraderItems = GetTraderItems(KFGRI, LogLevel); + + foreach TraderItems.SaleItems(Item) + { + if (Item.WeaponDef != None) + { + KFWeapDefs.AddItem(Item.WeaponDef); + } + } + + return KFWeapDefs; +} + +public static function Array > GetTraderWeapons(optional KFGameReplicationInfo KFGRI = None,optional E_LogLevel LogLevel = LL_Trace) +{ + local Array > KFWeapons; + local class KFWeapon; + local KFGFxObject_TraderItems TraderItems; + local STraderItem Item; + + TraderItems = GetTraderItems(KFGRI, LogLevel); + + foreach TraderItems.SaleItems(Item) + { + if (Item.WeaponDef != None) + { + KFWeapon = class (DynamicLoadObject(Item.WeaponDef.default.WeaponClassPath, class'Class')); + if (KFWeapon != None) + { + KFWeapons.AddItem(KFWeapon); + } + } + } + + return KFWeapons; +} + +public static simulated function ModifyTrader( + KFGameReplicationInfo KFGRI, + Array > RemoveItems, + bool ReplaceMode, + E_LogLevel LogLevel) +{ + local KFGFxObject_TraderItems TraderItems; + local STraderItem Item; + local class WeapDef; + local Array > WeapDefs; + local int MaxItemID; + + `Log_TraceStatic(); + + TraderItems = GetTraderItems(KFGRI, LogLevel); + + if (!ReplaceMode) + { + foreach TraderItems.SaleItems(Item) + { + if (Item.WeaponDef != None + && RemoveItems.Find(Item.WeaponDef) == INDEX_NONE) + { + WeapDefs.AddItem(Item.WeaponDef); + } + } + } + + 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; +} + +defaultproperties +{ + +} diff --git a/LTI/Classes/_Logger.uc b/LTI/Classes/_Logger.uc new file mode 100644 index 0000000..93fc28a --- /dev/null +++ b/LTI/Classes/_Logger.uc @@ -0,0 +1,20 @@ +class _Logger extends Object + abstract; + +enum E_LogLevel +{ + LL_WrongLevel, + LL_None, + LL_Fatal, + LL_Error, + LL_Warning, + LL_Info, + LL_Debug, + LL_Trace, + LL_All +}; + +defaultproperties +{ + +} diff --git a/LTI/Constants.uci b/LTI/Constants.uci new file mode 100644 index 0000000..1003f19 --- /dev/null +++ b/LTI/Constants.uci @@ -0,0 +1,2 @@ +// Constants +`define NO_CONFIG 0 diff --git a/LTI/Globals.uci b/LTI/Globals.uci new file mode 100644 index 0000000..a48ac52 --- /dev/null +++ b/LTI/Globals.uci @@ -0,0 +1,3 @@ +// Imports +`include(Logger.uci) +`include(Constants.uci) diff --git a/LTI/Logger.uci b/LTI/Logger.uci new file mode 100644 index 0000000..a2b3d54 --- /dev/null +++ b/LTI/Logger.uci @@ -0,0 +1,15 @@ +// Logger +`define Log_Tag 'LTI' + +`define LocationStatic "`{ClassName}::" $ GetFuncName() + +`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:" @ `Location `if(`msg) @ `msg`{endif}, (LogLevel >= LL_Trace), `Log_Tag) +`define Log_TraceStatic(msg) `log("TRACE:" @ `LocationStatic `if(`msg) @ `msg`{endif}, (LogLevel >= LL_Trace), `Log_Tag) diff --git a/Localization/INT/LTI.int b/Localization/INT/LTI.int new file mode 100644 index 0000000..ddc3126 Binary files /dev/null and b/Localization/INT/LTI.int differ diff --git a/Localization/RUS/LTI.rus b/Localization/RUS/LTI.rus new file mode 100644 index 0000000..272f2fe Binary files /dev/null and b/Localization/RUS/LTI.rus differ diff --git a/PublicationContent/description.txt b/PublicationContent/description.txt new file mode 100644 index 0000000..73cd672 --- /dev/null +++ b/PublicationContent/description.txt @@ -0,0 +1,58 @@ +[img]https://img.shields.io/static/v1?logo=GitHub&labelColor=gray&color=blue&logoColor=white&label=&message=Open Source[/img] [img]https://img.shields.io/github/license/GenZmeY/KF2-LootedTraderInventory[/img] [img]https://img.shields.io/steam/downloads/2864857909[/img] [img]https://img.shields.io/steam/favorites/2864857909[/img] [img]https://img.shields.io/steam/update-date/2864857909[/img] [url=https://steamcommunity.com/sharedfiles/filedetails/changelog/2864857909][img]https://img.shields.io/github/v/tag/GenZmeY/KF2-LootedTraderInventory[/img][/url] + +[h1]Features[/h1] +[list] +[*]remove items from trader. +[/list] + +[h1]Description[/h1] +This is a heavily stripped-down version of [url=https://steamcommunity.com/sharedfiles/filedetails/?id=2830826239]CTI[/url] that only allows you to remove weapons, not add them. +[url=https://steamcommunity.com/sharedfiles/filedetails/?id=2830826239]CTI[/url] has no chance of being whitelisted, but this version has a chance because it can't add anything unbalanced to the game - and that's the only reason this version exists. +If we're lucky with that then server operators will have more tools to fine-tune the server. + +[h1]Whitelisted?[/h1] +No. But I really hope that it will be whitelisted. + +[b]⚠️ I submitted whitelist request here:[/b] +https://forums.tripwireinteractive.com/index.php?threads/whitelisting-mods-and-mutators.120340/ + +[h1]Usage (single player)[/h1] +[olist] +[*]Subscribe to this mutator; +[*]Start KF2; +[*]Open console (`) and input: +[b]open KF-BioticsLab?Mutator=LTI.LTIMut[/b] +(replace the map and add the parameters you need) +[*]. +[/olist] +[h1]Usage (server)[/h1] +[b]Note:[/b] [i]If you don't understand what is written here, read the article [url=https://wiki.killingfloor2.com/index.php?title=Dedicated_Server_(Killing_Floor_2)][u]Dedicated Server (KF2 wiki)[/u][/url] before following these instructions.[/i] +[olist] +[*]Open your [b]PCServer-KFEngine.ini[/b] / [b]LinuxServer-KFEngine.ini[/b]; +[*]Find the [b][IpDrv.TcpNetDriver][/b] section and make sure that there is a line (add if not): +[b]DownloadManagers=OnlineSubsystemSteamworks.SteamWorkshopDownload[/b] +❗️ If there are several [b]DownloadManagers=[/b] then the line above should be the first ❗️ +[*]Add the following string to the [b][OnlineSubsystemSteamworks.KFWorkshopSteamworks][/b] section (create one if it doesn't exist): +[b]ServerSubscribedWorkshopItems=2864857909[/b] +[*]Start the server and wait until the mutator is downloading; +[*]Add mutator to server start parameters: [b]?Mutator=LTI.LTIMut[/b] and restart the server. +[/olist] + +[h1]Setup (KFLTI.ini)[/h1] +Config will be created at the first start[b]*[/b]. +[list] +[*]Set [b]bOfficialWeaponsList=True[/b] to have an auto-updated list of all official weapons in the config (for a convenient copy-paste). +[*]Use [b][LTI.RemoveItems][/b] to remove items from the trader inventory. +example: [b]Item=KFGame.KFWeapDef_Mac10[/b] will remove MAC10 from sale. +[/list] + +[h1]Troubleshooting[/h1] +[b](*)[/b] If your config is not created for some reason, create it manually with the following content: +[b][LTI.LTI] +Version=0 +[/b] + +Then start the server and check the file again - config content should be generated. + +[h1]Sources[/h1] +[url=https://github.com/GenZmeY/KF2-LootedTraderInventory]https://github.com/GenZmeY/KF2-LootedTraderInventory[/url] [b](GNU GPLv3)[/b] diff --git a/PublicationContent/preview.png b/PublicationContent/preview.png new file mode 100644 index 0000000..96dd39f Binary files /dev/null and b/PublicationContent/preview.png differ diff --git a/PublicationContent/tags.txt b/PublicationContent/tags.txt new file mode 100644 index 0000000..bcf8fe8 --- /dev/null +++ b/PublicationContent/tags.txt @@ -0,0 +1 @@ +Mutators diff --git a/PublicationContent/title.txt b/PublicationContent/title.txt new file mode 100644 index 0000000..24549c9 --- /dev/null +++ b/PublicationContent/title.txt @@ -0,0 +1 @@ +Looted Trader Inventory \ No newline at end of file diff --git a/README.md b/README.md index 55a4b7b..cf151a4 100644 --- a/README.md +++ b/README.md @@ -1 +1,31 @@ -# KF2-LootedTraderInventory \ No newline at end of file +# Looted Trader Inventory + +[![Steam Workshop](https://img.shields.io/static/v1?message=workshop&logo=steam&labelColor=gray&color=blue&logoColor=white&label=steam%20)](https://steamcommunity.com/sharedfiles/filedetails/?id=2864857909) +[![Steam Downloads](https://img.shields.io/steam/downloads/2864857909)](https://steamcommunity.com/sharedfiles/filedetails/?id=2864857909) +[![Steam Favorites](https://img.shields.io/steam/favorites/2864857909)](https://steamcommunity.com/sharedfiles/filedetails/?id=2864857909) +[![Steam Update Date](https://img.shields.io/steam/update-date/2864857909)](https://steamcommunity.com/sharedfiles/filedetails/?id=2864857909) +[![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/GenZmeY/KF2-LootedTraderInventory)](https://github.com/GenZmeY/KF2-LootedTraderInventory/tags) +[![GitHub](https://img.shields.io/github/license/GenZmeY/KF2-LootedTraderInventory)](LICENSE) + +# Description +This is a heavily stripped down version of [CTI](https://github.com/GenZmeY/KF2-CustomTraderInventory) that only allows you to remove the trader's weapons, not add them. This only exists in hopes of being whitelisted. + +# Usage & Setup +[See steam workshop page](https://steamcommunity.com/sharedfiles/filedetails/?id=2864857909) + +# Build +**Note:** If you want to build/test/brew/publish a mutator without git-bash and/or scripts, follow [these instructions](https://tripwireinteractive.atlassian.net/wiki/spaces/KF2SW/pages/26247172/KF2+Code+Modding+How-to) instead of what is described here. +1. Install [Killing Floor 2](https://store.steampowered.com/app/232090/Killing_Floor_2/), Killing Floor 2 - SDK and [git for windows](https://git-scm.com/download/win); +2. open git-bash and go to any folder where you want to store sources: +`cd ` +3. Clone this repository and go to the source folder: +`git clone https://github.com/GenZmeY/KF2-LootedTraderInventory && cd KF2-LootedTraderInventory` +4. Download dependencies: +`git submodule init && git submodule update` +5. Compile: +`./tools/builder -c` +5. The compiled files will be here: +`C:\Users\\Documents\My Games\KillingFloor2\KFGame\Unpublished\BrewedPC\Script\` + +# License +[GNU GPLv3](LICENSE) diff --git a/builder.cfg b/builder.cfg new file mode 100644 index 0000000..3d74deb --- /dev/null +++ b/builder.cfg @@ -0,0 +1,61 @@ +### Build parameters ### + +# If True - compresses the mutator when compiling +# Scripts will be stored in binary form +# (reduces the size of the output file) +StripSource="True" + +# Mutators to be compiled +# Specify them with a space as a separator, +# Mutators will be compiled in the specified order +PackageBuildOrder="LTI" + + +### Brew parameters ### + +# Packages you want to brew using @peelz's patched KFEditor. +# Useful for cases where regular brew doesn't put *.upk inside the package. +# Specify them with a space as a separator, +# The order doesn't matter +PackagePeelzBrew="" + + +### Steam Workshop upload parameters ### + +# Mutators that will be uploaded to the workshop +# Specify them with a space as a separator, +# The order doesn't matter +PackageUpload="LTI" + + +### Test parameters ### + +# Map: +Map="KF-Nuked" + +# Game: +# Survival: KFGameContent.KFGameInfo_Survival +# WeeklyOutbreak: KFGameContent.KFGameInfo_WeeklySurvival +# Endless: KFGameContent.KFGameInfo_Endless +# Objective: KFGameContent.KFGameInfo_Objective +# Versus: KFGameContent.KFGameInfo_VersusSurvival +Game="KFGameContent.KFGameInfo_Survival" + +# Difficulty: +# Normal: 0 +# Hard: 1 +# Suicide: 2 +# Hell: 3 +Difficulty="0" + +# GameLength: +# 4 waves: 0 +# 7 waves: 1 +# 10 waves: 2 +GameLength="0" + +# Mutators +Mutators="LTI.LTIMut" + +# Additional parameters +Args="" diff --git a/tools b/tools new file mode 160000 index 0000000..0e821f3 --- /dev/null +++ b/tools @@ -0,0 +1 @@ +Subproject commit 0e821f3dbbc6b3528f2028b0060d3b6f7f1c4b93