diff --git a/.gitignore b/.gitignore index bc78471..2e90c7a 100644 --- a/.gitignore +++ b/.gitignore @@ -482,3 +482,9 @@ $RECYCLE.BIN/ # Vim temporary swap files *.swp + +# VS Code +.vscode + +# Build artifacts +Packages/ diff --git a/Editor/HookahAssetBuilder.cs b/Editor/HookahAssetBuilder.cs new file mode 100644 index 0000000..a9e4794 --- /dev/null +++ b/Editor/HookahAssetBuilder.cs @@ -0,0 +1,158 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEngine; +using System.Windows; + +public class HookahAssetBuilder +{ + static readonly string ModManagerProfilesPath = + @"C:\Users\user\AppData\Roaming\com.kesomannen.gale\lethal-company\profiles"; + static readonly string ModPackProfileName = "HookahPlace Test"; + static readonly string ModPackModName = "HookahPlace-DEV"; + static readonly string ModPackProfilePath = $@"{ModManagerProfilesPath}/{ModPackProfileName}"; + static readonly string ModPackHookahPlacePath = @$"{ModPackProfilePath}/BepInEx/plugins/{ModPackModName}"; + + static readonly string ModProjectPath = @"D:\Code\LC-Decompiled\Mods\HookahPlace"; + static readonly string DllName = "Ratijas.HookahPlace.dll"; + static readonly string DllSourcePath = $@"{ModProjectPath}/HookahPlace/bin/Debug/netstandard2.1/{DllName}"; + static readonly string[] DllDestinationPaths = new string[] + { + // relative to Unity project + $@"Assets/LethalCompany/Mods/plugins/HookahPlace/Dependencies/{ModPackModName}", + ModPackHookahPlacePath, + }; + + static readonly string AssetBundlesPath = "Assets/LethalCompany/Mods/plugins/HookahPlace/AssetBundles"; + static readonly string[] AssetBundlesNames = new string[] + { + "hookahplaceasset", + "hookahunlockableassets", + }; + static readonly string[] AssetBundlesDestinationPaths = new string[] + { + @$"{ModPackHookahPlacePath}/Assets", + @$"{ModProjectPath}/HookahPlace/res", + }; + + [MenuItem("HookahPlace/All")] + public static void DoAll() + { + CompileScripts(); + BuildAssetBundles(); + InstallAssetBundles(); + LaunchGame(); + } + + [MenuItem("HookahPlace/1. Compile Scripts")] + public static void CompileScripts() + { + var psi = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = "build", + UseShellExecute = false, + }; + + var proc = Process.Start(psi); + proc.WaitForExit(); + + if (proc.ExitCode != 0) + { + UnityEngine.Debug.Log($"Process exited with non-zero code: {proc.ExitCode}"); + // proc.StandardOutput + return; + } + + foreach (var destinationPath in DllDestinationPaths) + { + Directory.CreateDirectory(destinationPath); + + var sourceFileName = DllSourcePath; + var destFileName = $"{destinationPath}/{DllName}"; + FileCopy(sourceFileName, destFileName); + } + } + + [MenuItem("HookahPlace/2. Build Asset Bundles")] + public static void BuildAssetBundles() + { + UnityEngine.Debug.Log("Building asset bundles for HookahPlace..."); + + Directory.CreateDirectory(AssetBundlesPath); + + BuildPipeline.BuildAssetBundles(AssetBundlesPath, BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.StandaloneWindows); + + if (Application.isBatchMode) + { + EditorApplication.Exit(0); + } + } + + [MenuItem("HookahPlace/3. Install Asset Bundles")] + public static void InstallAssetBundles() + { + UnityEngine.Debug.Log("Installing asset bundles for HookahPlace..."); + + foreach (var destinationPath in AssetBundlesDestinationPaths) + { + Directory.CreateDirectory(destinationPath); + + foreach (var name in AssetBundlesNames) + { + var sourceFileName = $"{AssetBundlesPath}/{name}"; + var destFileName = $"{destinationPath}/{name}"; + FileCopy(sourceFileName, destFileName); + } + } + } + + [MenuItem("HookahPlace/4. Launch Game")] + public static void LaunchGame() + { + UnityEngine.Debug.Log($"Launching Gale profile with profile '{ModPackProfileName}'..."); + + string BepInExPreloaderDllPath = $@"{ModPackProfilePath}/BepInEx/core/BepInEx.Preloader.dll"; + + string[] args = new string[] + { + @"C:\Program Files (x86)\Steam\steam.exe", + "-applaunch", + "1966720", + "--doorstop-enabled", + "true", + "--doorstop-target-assembly", + BepInExPreloaderDllPath, + }; + + var psi = new ProcessStartInfo + { + FileName = args[0], + Arguments = string.Join(" ", args.Skip(1).Select(QuoteArgument)), + UseShellExecute = false, + }; + + Process.Start(psi); + + GUIUtility.systemCopyBuffer = "hookah"; + } + + static void FileCopy(string sourceFileName, string destFileName) + { + UnityEngine.Debug.Log($"File.Copy {sourceFileName} => {destFileName}"); + File.Copy(sourceFileName, destFileName, overwrite: true); + } + + static string QuoteArgument(string arg) + { + if (string.IsNullOrEmpty(arg)) + return "\"\""; + + if (arg.IndexOfAny(new char[] { ' ', '\t', '"' }) == -1) + return arg; + + return "\"" + arg.Replace("\"", "\\\"") + "\""; + } +} diff --git a/Editor/README.md b/Editor/README.md new file mode 100644 index 0000000..80b460e --- /dev/null +++ b/Editor/README.md @@ -0,0 +1 @@ +This folder contains C# scripts for Unity project with the decompiled game. diff --git a/HookahPlace/DEVELOPMENT.md b/HookahPlace/DEVELOPMENT.md new file mode 100644 index 0000000..51bf7d1 --- /dev/null +++ b/HookahPlace/DEVELOPMENT.md @@ -0,0 +1,5 @@ +# Development + +- Keep in sync versions of DawnLib in `thunderstore.toml` and `*.csproj`. +- Build AssetBundles in Unity, and copy them to the `res` folder. +- Build in the `Release` configuration to create the zip diff --git a/HookahPlace/HookahPlace.csproj b/HookahPlace/HookahPlace.csproj index 0798e29..50330a8 100644 --- a/HookahPlace/HookahPlace.csproj +++ b/HookahPlace/HookahPlace.csproj @@ -4,13 +4,12 @@ Ratijas.HookahPlace HookahPlace - - 1.0.0 + 4.1.9 netstandard2.1 - CRLib._ModTemplate + HookahPlace true latest @@ -37,16 +36,15 @@ true + embedded - - $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)'))=./ + + + $(UserProfile)=~,$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)'))=$(PackageId)/ - 0.4.* + 0.7.3 @@ -58,7 +56,7 @@ - + diff --git a/HookahPlace/HookahPlace.csproj.user.template b/HookahPlace/HookahPlace.csproj.user.template index 1b05b60..5866c8f 100644 --- a/HookahPlace/HookahPlace.csproj.user.template +++ b/HookahPlace/HookahPlace.csproj.user.template @@ -1,17 +1,11 @@ - .../Lethal Company/BepInEx/plugins/ - - .../Lethal Comapny/BepInEx/plugins/MMHOOK/ - - - - diff --git a/HookahPlace/README.md b/HookahPlace/README.md index b59b1a7..2fb385a 100644 --- a/HookahPlace/README.md +++ b/HookahPlace/README.md @@ -1,12 +1,21 @@ -# TODO before release: -- `thunderstore.toml`: - - **Important**: Set `namespace` (this is your author name) and `description` - - Set `websiteUrl` - - Update `DawnLib` version - - Note that the name of your mod is determined by the name of the project, and the version is determined in your `.csproj` -- `.csproj`: - - Update `DawnLib` version -- `HookahPlaceKeys.cs`: - - Look in this file -- Update this README! (it gets used to generate your mods readme) -- Finally, build in the `Release` configuration to create the zip \ No newline at end of file +# Hookah Place + +_Relaxing hookah as a ship upgrade_ + +Welcome to your very own Lethal Hookah Place! + +Become a ship-mom (or a ship-dad) and smoke shisha while guiding your crew over a walkie-talkie. Let 'em know you're having a good time! 🗣💯💭 + +![Hookah at ship](https://ratijas.me/share/public/LC/HookahPlace/HookahPlace-demo-1.jpg) + +## Store + +The mod adds a ship decor/furniture to the store called "Hookah". + +The store rotates between random 4-6 entries every quota. To make the hookah always available in the store, use another mod like [`StoreRotationConfig` by `pacoito`](https://thunderstore.io/c/lethal-company/p/pacoito/StoreRotationConfig/) with the `stockAll` option toggled ON. + +## Made by Ratijas + +Also check out my other mod, 🎵 [Muzika Gromche — The ultimate Jester party music mod](https://thunderstore.io/c/lethal-company/p/Ratijas/MuzikaGromche/)! + +Hookah model made in Blender: [HookahPlace.blend](https://ratijas.me/share/public/LC/HookahPlace/HookahPlace.blend) diff --git a/HookahPlace/Thunderstore/CHANGELOG.md b/HookahPlace/Thunderstore/CHANGELOG.md index 6185211..d1bf2b3 100644 --- a/HookahPlace/Thunderstore/CHANGELOG.md +++ b/HookahPlace/Thunderstore/CHANGELOG.md @@ -1,2 +1,3 @@ -# 1.0.0 -Inital release \ No newline at end of file +# 4.1.9 + +Inital release diff --git a/HookahPlace/Thunderstore/HookahPlace-demo-1.jpg b/HookahPlace/Thunderstore/HookahPlace-demo-1.jpg new file mode 100644 index 0000000..ee9d7e7 Binary files /dev/null and b/HookahPlace/Thunderstore/HookahPlace-demo-1.jpg differ diff --git a/HookahPlace/Thunderstore/icon.png b/HookahPlace/Thunderstore/icon.png new file mode 100644 index 0000000..6cf3ba6 Binary files /dev/null and b/HookahPlace/Thunderstore/icon.png differ diff --git a/HookahPlace/Thunderstore/thunderstore.toml b/HookahPlace/Thunderstore/thunderstore.toml index 72defb0..b0192c2 100644 --- a/HookahPlace/Thunderstore/thunderstore.toml +++ b/HookahPlace/Thunderstore/thunderstore.toml @@ -2,13 +2,16 @@ schemaVersion = "0.0.1" [package] -namespace = "" -description = "" -websiteUrl = "" +namespace = "Ratijas" +name = "HookahPlace" +versionNumber = "4.1.9" +description = "Relaxing hookah as a ship upgrade" +websiteUrl = "https://git.vilunov.me/ratijas/HookahPlace" containsNsfwContent = false + [package.dependencies] -BepInEx-BepInExPack = "5.4.2100" -TeamXiaolan-DawnLib = "0.4.0" +BepInEx-BepInExPack = "5.4.2304" +TeamXiaolan-DawnLib = "0.7.3" [build] icon = "./icon.png" @@ -29,6 +32,7 @@ target = "/" [publish] repository = "https://thunderstore.io" -communities = [ "lethal-company", ] +communities = [ "lethal-company" ] [publish.categories] -lethal-company = [ "mods", "tools", "libraries", "clientside", "serverside" ] \ No newline at end of file +# https://thunderstore.io/api/experimental/community/lethal-company/category/ +lethal-company = [ "mods", "furniture", "clientside", "serverside" ] diff --git a/HookahPlace/res/ASSETBUNDLES HERE b/HookahPlace/res/ASSETBUNDLES HERE deleted file mode 100644 index e69de29..0000000 diff --git a/HookahPlace/res/hookahplaceasset b/HookahPlace/res/hookahplaceasset new file mode 100644 index 0000000..873d629 Binary files /dev/null and b/HookahPlace/res/hookahplaceasset differ diff --git a/HookahPlace/res/hookahunlockableassets b/HookahPlace/res/hookahunlockableassets new file mode 100644 index 0000000..e779806 Binary files /dev/null and b/HookahPlace/res/hookahunlockableassets differ diff --git a/HookahPlace/src/Content/ExampleContentHandler.cs b/HookahPlace/src/Content/ExampleContentHandler.cs deleted file mode 100644 index 1fe0a70..0000000 --- a/HookahPlace/src/Content/ExampleContentHandler.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Dawn; -using Dusk; - -namespace HookahPlace.Content; - -// REMOVE THIS FILE IF NOT USING DUSK -public class ExampleContentHandler : ContentHandler -{ - public ExampleContentHandler(DuskMod mod) : base(mod) - { - RegisterContent("content bundle name here", out DefaultBundle? bundle); - } -} \ No newline at end of file diff --git a/HookahPlace/src/Content/HookahContentHandler.cs b/HookahPlace/src/Content/HookahContentHandler.cs new file mode 100644 index 0000000..26a96d0 --- /dev/null +++ b/HookahPlace/src/Content/HookahContentHandler.cs @@ -0,0 +1,19 @@ +using Dusk; + +namespace HookahPlace.Content; + +public class HookahContentHandler : ContentHandler +{ + const int HOOKAH_PRICE = 130; + + public HookahContentHandler(DuskMod mod) : base(mod) + { + NukeDawnLibConfig.NukeUnlockable(HookahPlace.Config, "HookahUnlockable", "Hookah", HOOKAH_PRICE); + + if (!RegisterContent("hookahunlockableassets", out DefaultBundle? bundle, forceEnabled: true) || bundle == null) + { + HookahPlace.Logger.LogError($"Failed to register content"); + return; + } + } +} diff --git a/HookahPlace/src/Content/NukeDawnLibConfig.cs b/HookahPlace/src/Content/NukeDawnLibConfig.cs new file mode 100644 index 0000000..d24402e --- /dev/null +++ b/HookahPlace/src/Content/NukeDawnLibConfig.cs @@ -0,0 +1,22 @@ +using BepInEx.Configuration; +using Dusk; + +namespace HookahPlace.Content; + +internal static class NukeDawnLibConfig +{ + static internal void NukeUnlockable(ConfigFile config, string heading, string name, int cost) + { + heading = $"{heading} Options"; + var costName = $"{name} | Cost"; + var disclaimer = "Sorry, this is not configurable. This is a dirty hack to suppress DawnLib/DuskMod from allowing any configurability here."; + + var ctx = new ConfigContext(config, heading); + + var enabledEntry = ctx.Bind("Enabled", disclaimer, true); + enabledEntry.Value = true; + + var costEntry = ctx.Bind(costName, disclaimer, cost); + costEntry.Value = cost; + } +} diff --git a/HookahPlace/src/HookahBehaviour.cs b/HookahPlace/src/HookahBehaviour.cs new file mode 100644 index 0000000..873e18b --- /dev/null +++ b/HookahPlace/src/HookahBehaviour.cs @@ -0,0 +1,78 @@ +using Unity.Netcode; +using UnityEngine; +using UnityEngine.Rendering.HighDefinition; + +namespace HookahPlace; + +class HookahBehaviour : NetworkBehaviour +{ + public Transform? smokePosition; + public GameObject? smokePrefab; + + public Color[] Palette = []; + + private Color? ApplyLaterSmokeColor = null; + private LocalVolumetricFog? ApplyLaterSmokeVolumetricFog; + + // Hookah smoke animation emits an event that invokes this method. + // The prefab that it spawns self-destructs at the end of its animation. + // + // The fog works through LocalVolumetricFog components. + // Inside the ship and under any roof outdoors there are placed FogExclusionZone objects, + // with Blending set to Override which essentially removes the fog in those zones. + // + // The solution is to set your Hookah smoke blending to Additive (to preserve seamless transitions when fading out), + // but crank up priority to something unreasonably high like 9001, so that it adds to that Override layer. + public void SpawnSmoke() + { + if (smokePosition == null || smokePrefab == null) + { + return; + } + var ship = transform.parent.parent; + var smoke = Instantiate(smokePrefab, smokePosition.position, Quaternion.identity, ship); + var fog = smoke.GetComponentInChildren(); + // Randomize smoke color. + // Clients set a client-side random color just in case, but it should be overridden by server rpc shortly. + var index = Random.RandomRangeInt(0, Palette.Length); + Color randomColor = Palette[index]; + if (IsServer) + { + SetSmokeColorClientRpc(randomColor); + ApplySmokeColor(fog, randomColor); + } + else if (ApplyLaterSmokeColor is { } serverColor) + { + ApplySmokeColor(fog, serverColor); + ApplyLaterSmokeColor = null; + } + else + { + // color has not arrived from server (yet), for now set local random color + ApplySmokeColor(fog, randomColor); + ApplyLaterSmokeVolumetricFog = fog; + } + } + + [Rpc(SendTo.NotServer)] + public void SetSmokeColorClientRpc(Color color) + { + if (ApplyLaterSmokeVolumetricFog is { } fog) + { + ApplySmokeColor(fog, color); + ApplyLaterSmokeVolumetricFog = null; + } + else + { + // smoke has not been spawned (yet), remember the color for later. + ApplyLaterSmokeColor = color; + } + } + + private static void ApplySmokeColor(LocalVolumetricFog fog, Color color) + { + var parameters = fog.parameters; + parameters.albedo = color; + fog.parameters = parameters; + } +} diff --git a/HookahPlace/src/HookahPlace.cs b/HookahPlace/src/HookahPlace.cs index de984f9..4fac385 100644 --- a/HookahPlace/src/HookahPlace.cs +++ b/HookahPlace/src/HookahPlace.cs @@ -4,6 +4,9 @@ using System.Reflection; using UnityEngine; using Dawn; using Dawn.Utils; +using HarmonyLib; +using Dusk; +using BepInEx.Configuration; namespace HookahPlace; @@ -12,16 +15,18 @@ namespace HookahPlace; public class HookahPlace : BaseUnityPlugin { internal new static ManualLogSource Logger { get; private set; } = null!; + internal new static ConfigFile Config { get; private set; } = null!; internal static PersistentDataContainer PersistentData { get; private set; } = null!; + internal static DuskMod Mod { get; private set; } = null!; private void Awake() { Logger = base.Logger; + Config = base.Config; - // Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), MyPluginInfo.PLUGIN_GUID) // uncomment if using Harmony to patch - + Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), MyPluginInfo.PLUGIN_GUID); // Example Persistent Data Container Usage // You can do anything you want with this DataContainer, there are a few additonal ones under the DawnLib class that pertain to actual save files @@ -31,6 +36,10 @@ public class HookahPlace : BaseUnityPlugin // if you want to do config migration you should use DawnLib.GetCurrentInstallSave instead. PersistentData.Set(HookahPlaceKeys.LastVersion, MyPluginInfo.PLUGIN_VERSION); + AssetBundle mainBundle = AssetBundleUtils.LoadBundle(Assembly.GetExecutingAssembly(), "hookahplaceasset"); + Mod = DuskMod.RegisterMod(this, mainBundle); + Mod.RegisterContentHandlers(); + Logger.LogInfo($"{MyPluginInfo.PLUGIN_GUID} v{MyPluginInfo.PLUGIN_VERSION} has loaded!"); } } diff --git a/HookahPlace/src/HookahPlaceKeys.cs b/HookahPlace/src/HookahPlaceKeys.cs index b2f44ba..fe56de2 100644 --- a/HookahPlace/src/HookahPlaceKeys.cs +++ b/HookahPlace/src/HookahPlaceKeys.cs @@ -12,8 +12,6 @@ namespace HookahPlace; // If you contain lots of keys, you might want to use child classes e.g: HookahPlaceKeys.Items.MyItem // Note: the use of `partial` here is because of the DawnLib SourceGenerator, which you might not be using. public static partial class HookahPlaceKeys { - // You may want to update this namespace. It should be "Snake case", e.g: FacilityMeltdown's namespace should be `facility_meltdown` - // You can also shorten it if you want, e.g: `meltdown` public const string Namespace = "hookah_place"; // Data Keys