Skip to content

Commit

Permalink
[PropCompanion] Initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
kafeijao committed May 5, 2024
1 parent eecb1be commit ea1e0b8
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Kafe_CVR_Mods.sln
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 48,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RetroCVR", "RetroCVR\RetroC
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamAudioOverrides", "SteamAudioOverrides\SteamAudioOverrides.csproj", "{B2B79B1E-FF4B-4B02-8487-46280EEA00CD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PropCompanion", "PropCompanion\PropCompanion.csproj", "{5B6D7650-5454-45B4-A8CC-A3E06DC35AC1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -150,5 152,9 @@ Global
{B2B79B1E-FF4B-4B02-8487-46280EEA00CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B2B79B1E-FF4B-4B02-8487-46280EEA00CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B2B79B1E-FF4B-4B02-8487-46280EEA00CD}.Release|Any CPU.Build.0 = Release|Any CPU
{5B6D7650-5454-45B4-A8CC-A3E06DC35AC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5B6D7650-5454-45B4-A8CC-A3E06DC35AC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5B6D7650-5454-45B4-A8CC-A3E06DC35AC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5B6D7650-5454-45B4-A8CC-A3E06DC35AC1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
172 changes: 172 additions & 0 deletions PropCompanion/Main.cs
Original file line number Diff line number Diff line change
@@ -0,0 1,172 @@
using System.Collections;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Core.Util;
using ABI_RC.Systems.GameEventSystem;
using ABI.CCK.Components;
using HarmonyLib;
using MelonLoader;
using UnityEngine;

namespace Kafe.CompanionProp;

public class CompanionProp : MelonMod {


public override void OnInitializeMelon() {

ModConfig.InitializeMelonPrefs();

CheckGuid(ModConfig.MePropGuid.Value, false);

object spawning = null;

CVRGameEventSystem.Instance.OnConnected.AddListener(instanceID => {
if (!ShouldSpawnProp) return;
// Handle can't spawn notification
if (!CVRWorld.Instance.allowSpawnables) {
if (ModConfig.MeSendHudSpawnNotAllowedNotification.Value)
ViewManager.Instance.NotifyUser("(Local) Client", "CompanionProp - This world doesn't allow props :c", 2f);
return;
}
// Cancel previous
if (spawning != null)
MelonCoroutines.Stop(spawning);
// Start the spawning coroutine
spawning = MelonCoroutines.Start(DelaySpawnProp(instanceID));
});
}

private IEnumerator DelaySpawnProp(string instanceIdToSpawn) {

int delaySeconds = ModConfig.MeSpawnDelay.Value;

MelonLogger.Msg($"Connected to instance: {instanceIdToSpawn}, spawning {ModConfig.MePropGuid.Value} in {delaySeconds} seconds...");

yield return new WaitForSeconds(delaySeconds);

// Double-check our settings
if (!ShouldSpawnProp) yield break;

// Ensure we're still on the same instance
if (instanceIdToSpawn != MetaPort.Instance.CurrentInstanceId) {
MelonLogger.Warning($"Prop started spawning when we were on the instance {instanceIdToSpawn}, but now we're on {MetaPort.Instance.CurrentInstanceId}. Ignoring...");
yield break;
}

// Handle spawning notification
if (ModConfig.MeSendHudSpawnNotification.Value)
ViewManager.Instance.NotifyUser("(Local) Client", "CompanionProp - Spawning our prop c:", 2f);

PlayerSetup.Instance.DropProp(ModConfig.MePropGuid.Value);

MelonLogger.Msg($"Spawned our precious {ModConfig.MePropGuid.Value} prop!");
}

public static void CheckGuid(string guidValue, bool forceCheck) {
if (!ModConfig.SpawnPropOnJoin.Value && !forceCheck) return;
if (Guid.TryParse(guidValue, out _)) {
MelonLogger.Msg($"Using the prop's guid to {guidValue}");
}
else {
MelonLogger.Warning($"The current prop's guid \"{guidValue}\" is not valid. Ensure to input a valid guid on the configuration.");
}
}

private bool ShouldSpawnProp => ModConfig.MeEnabled.Value && ModConfig.SpawnPropOnJoin.Value && Guid.TryParse(ModConfig.MePropGuid.Value, out _);


[HarmonyPatch]
internal class HarmonyPatches {

private static bool MatchesOurProp(CVRSyncHelper.PropData prop) {
return prop.SpawnedBy == MetaPort.Instance.ownerId && prop.ObjectId == ModConfig.MePropGuid.Value;
}

private static bool MatchesOurPropInstanceID(string propInstanceId) {
return propInstanceId.StartsWith("p " ModConfig.MePropGuid.Value, StringComparison.OrdinalIgnoreCase);
}

[HarmonyPrefix]
[HarmonyPatch(typeof(CVRSyncHelper), nameof(CVRSyncHelper.DeleteMyProps))]
static void Before_CVRSyncHelper_DeleteMyProps(out (IEnumerable<CVRSyncHelper.PropData>, IEnumerable<string>) __state) {
__state = default;

// Ignore and execute the normal method like usual
if (!ModConfig.MeEnabled.Value || !ModConfig.MePreventRemoveAllMyProps.Value) {
return;
}

try {
__state = (CVRSyncHelper.Props.Where(MatchesOurProp).ToArray(), CVRSyncHelper.MySpawnedPropInstanceIds.Where(MatchesOurPropInstanceID).ToArray());
CVRSyncHelper.Props.RemoveAll(MatchesOurProp);
CVRSyncHelper.MySpawnedPropInstanceIds.RemoveWhere(MatchesOurPropInstanceID);
}
catch (Exception e) {
MelonLogger.Error($"Error executing {nameof(Before_CVRSyncHelper_DeleteMyProps)} Patch");
MelonLogger.Error(e);
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(CVRSyncHelper), nameof(CVRSyncHelper.DeleteMyProps))]
static void After_CVRSyncHelper_DeleteMyProps((IEnumerable<CVRSyncHelper.PropData>, IEnumerable<string>) __state) {

// Ignore and execute the normal method like usual
if (!ModConfig.MeEnabled.Value || !ModConfig.MePreventRemoveAllMyProps.Value)
return;

try {
CVRSyncHelper.Props.AddRange(__state.Item1);
CVRSyncHelper.MySpawnedPropInstanceIds.UnionWith(__state.Item2);
}
catch (Exception e) {
MelonLogger.Error($"Error executing {nameof(After_CVRSyncHelper_DeleteMyProps)} Patch");
MelonLogger.Error(e);
}
}

[HarmonyPrefix]
[HarmonyPatch(typeof(CVRSyncHelper), nameof(CVRSyncHelper.DeleteAllProps))]
static void Before_CVRSyncHelper_DeleteAllProps(out (IEnumerable<CVRSyncHelper.PropData>, IEnumerable<string>) __state) {
__state = default;

// Ignore and execute the normal method like usual
if (!ModConfig.MeEnabled.Value || !ModConfig.MePreventRemoveAllProps.Value) {
return;
}

try {
__state = (CVRSyncHelper.Props.Where(MatchesOurProp).ToArray(), CVRSyncHelper.MySpawnedPropInstanceIds.Where(MatchesOurPropInstanceID).ToArray());
CVRSyncHelper.Props.RemoveAll(MatchesOurProp);
CVRSyncHelper.MySpawnedPropInstanceIds.RemoveWhere(MatchesOurPropInstanceID);
}
catch (Exception e) {
MelonLogger.Error($"Error executing {nameof(Before_CVRSyncHelper_DeleteAllProps)} Patch");
MelonLogger.Error(e);
}
}

[HarmonyPostfix]
[HarmonyPatch(typeof(CVRSyncHelper), nameof(CVRSyncHelper.DeleteAllProps))]
static void After_CVRSyncHelper_DeleteAllProps((IEnumerable<CVRSyncHelper.PropData>, IEnumerable<string>) __state) {

// Ignore and execute the normal method like usual
if (!ModConfig.MeEnabled.Value || !ModConfig.MePreventRemoveAllProps.Value)
return;

try {
CVRSyncHelper.Props.AddRange(__state.Item1);
CVRSyncHelper.MySpawnedPropInstanceIds.UnionWith(__state.Item2);
}
catch (Exception e) {
MelonLogger.Error($"Error executing {nameof(After_CVRSyncHelper_DeleteAllProps)} Patch");
MelonLogger.Error(e);
}
}
}
}
56 changes: 56 additions & 0 deletions PropCompanion/ModConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 1,56 @@
using MelonLoader;

namespace Kafe.CompanionProp;

public static class ModConfig {

private static MelonPreferences_Category _melonCategory;

internal static MelonPreferences_Entry<bool> MeEnabled;
internal static MelonPreferences_Entry<string> MePropGuid;

internal static MelonPreferences_Entry<bool> SpawnPropOnJoin;
internal static MelonPreferences_Entry<int> MeSpawnDelay;

internal static MelonPreferences_Entry<bool> MePreventRemoveAllProps;
internal static MelonPreferences_Entry<bool> MePreventRemoveAllMyProps;

internal static MelonPreferences_Entry<bool> MeSendHudSpawnNotification;
internal static MelonPreferences_Entry<bool> MeSendHudSpawnNotAllowedNotification;

public static void InitializeMelonPrefs() {

_melonCategory = MelonPreferences.CreateCategory(nameof(CompanionProp));

MeEnabled = _melonCategory.CreateEntry("Enabled", true,
description: "Whether all the mod functionality should be enabled or not.");

MePropGuid = _melonCategory.CreateEntry("PropGuid", "",
description: "The guid of the prop we're settings as our companion.");
MePropGuid.OnEntryValueChanged.Subscribe((_, newValue) => {
CompanionProp.CheckGuid(newValue, true);
});


SpawnPropOnJoin = _melonCategory.CreateEntry("SpawnPropOnJoin", true,
description: "Whether to spawn the prop on joining an instance that allows props or not.");

MeSpawnDelay = _melonCategory.CreateEntry("SpawnDelay", 5,
description: "Time in seconds to spawn the prop after joining the instance.");


MePreventRemoveAllProps = _melonCategory.CreateEntry("PreventRemoveAllProps", true,
description: "Whether to prevent removing our prop when pressing the remove all props button.");

MePreventRemoveAllMyProps = _melonCategory.CreateEntry("PreventRemoveAllMyProps", true,
description: "Whether to prevent removing our prop when pressed the remove my props button.");


MeSendHudSpawnNotification = _melonCategory.CreateEntry("SendHudSpawnNotification", true,
description: "Whether to send a notification to the hud when the prop is spawned or not.");

MeSendHudSpawnNotAllowedNotification = _melonCategory.CreateEntry("SendHudSpawnNotAllowedNotification", false,
description: "Whether to send a notification to the hud when the prop is was not allowed to be spawned.");
}

}
2 changes: 2 additions & 0 deletions PropCompanion/PropCompanion.csproj
Original file line number Diff line number Diff line change
@@ -0,0 1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk"/>
32 changes: 32 additions & 0 deletions PropCompanion/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 1,32 @@
using System.Reflection;
using Kafe.CompanionProp;
using Kafe.CompanionProp.Properties;
using MelonLoader;


[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
[assembly: AssemblyTitle(nameof(Kafe.CompanionProp))]
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
[assembly: AssemblyProduct(nameof(Kafe.CompanionProp))]

[assembly: MelonInfo(
typeof(CompanionProp),
nameof(Kafe.CompanionProp),
AssemblyInfoParams.Version,
AssemblyInfoParams.Author,
downloadLink: "https://github.com/kafeijao/Kafe_CVR_Mods"
)]
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
[assembly: VerifyLoaderVersion(0, 6, 1, true)]
[assembly: MelonColor(255, 0, 255, 0)]
[assembly: MelonAuthorColor(255, 119, 77, 79)]

namespace Kafe.CompanionProp.Properties;
internal static class AssemblyInfoParams {
public const string Version = "0.0.1";
public const string Author = "kafeijao";
}
19 changes: 19 additions & 0 deletions PropCompanion/Properties/CVRMG.json
Original file line number Diff line number Diff line change
@@ -0,0 1,19 @@
{
"_id": -1,
"name": "CompanionProp",
"modversion": "0.0.1",
"gameversion": "2024r175",
"loaderversion": "v0.6.1",
"modtype": "Mod",
"author": "kafeijao",
"description": "Automatically spawn a single prop upon joining an instance (that allows props), while being able to optionally exclude them from being removed when pressing the Remove All Props or Remove my Props buttons.\n\nYou can start using by setting the prop Guid on the melon preferences. Either by editing the config file or by using the `UIExpansionKit` mod.\n\nCheck [README.md](https://github.com/kafeijao/Kafe_CVR_Mods/tree/master/CompanionProp) for more in-depth info.\n\n",
"searchtags": [
"prop",
"companion"
],
"requirements": [],
"downloadlink": "https://github.com/kafeijao/Kafe_CVR_Mods/releases/download/r68/CompanionProp.dll",
"sourcelink": "https://github.com/kafeijao/Kafe_CVR_Mods/tree/master/CompanionProp",
"changelog": "- Initial Implementation",
"embedcolor": "16C60C"
}
11 changes: 11 additions & 0 deletions PropCompanion/README.md
Original file line number Diff line number Diff line change
@@ -0,0 1,11 @@
# PropCompanion

[![Download Latest PropCompanion.dll](../.Resources/DownloadButtonEnabled.svg "Download Latest PropCompanion.dll")](https://github.com/kafeijao/Kafe_CVR_Mods/releases/latest/download/PropCompanion.dll)

Very simple mod that automatically spawn a prop upon joining an instance (that allows props), while being able to
optionally exclude them from being removed when pressing the Remove All Props or Remove my Props buttons.

You can start using by setting the prop Guid on the melon preferences. Either by editing the config file or by using the
`UIExpansionKit` mod.

You can only have a single prop companion. This is by design to avoid people keeping a ton of props on all the time.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 29,7 @@ Welcome to my little collection of mods, feel free to leave bug reports or featu
| PickupOverrides | [README.md](PickupOverrides/README.md) | ![Download Latest PickupOverrides.dll](.Resources/DownloadButtonDisabled.svg "Download Latest PickupOverrides.dll") | *Retired* Override the QOL settings for pickups |
| PostProcessingOverrides | [README.md](PostProcessingOverrides/README.md) | [![Download Latest PostProcessingOverrides.dll](.Resources/DownloadButtonEnabled.svg "Download Latest PostProcessingOverrides.dll")](https://github.com/kafeijao/Kafe_CVR_Mods/releases/latest/download/PostProcessingOverrides.dll) | Improve PostProcessing control |
| ProfilesExtended | [README.md](ProfilesExtended/README.md) | [![Download Latest ProfilesExtended.dll](.Resources/DownloadButtonEnabled.svg "Download Latest ProfilesExtended.dll")](https://github.com/kafeijao/Kafe_CVR_Mods/releases/latest/download/ProfilesExtended.dll) | Improve the avatar profiles behavior |
| PropCompanion | [README.md](PropCompanion/README.md) | [![Download Latest PropCompanion.dll](.Resources/DownloadButtonEnabled.svg "Download Latest PropCompanion.dll")](https://github.com/kafeijao/Kafe_CVR_Mods/releases/latest/download/PropCompanion.dll) | Spawn prop on join |
| QRCode | [README.md](QRCode/README.md) | [![Download Latest QRCode.dll](.Resources/DownloadButtonEnabled.svg "Download Latest QRCode.dll")](https://github.com/kafeijao/Kafe_CVR_Mods/releases/latest/download/QRCode.dll) | Adds a QRCode Reader to the CVR Camera |
| QuickMenuAccessibility | [README.md](QuickMenuAccessibility/README.md) | ![Download Latest QuickMenuAccessibility.dll](.Resources/DownloadButtonDisabled.svg "Download Latest QuickMenuAccessibility.dll") | *Retired* Quick Menu accessibility options |
| RealisticFlight | [README.md](RealisticFlight/README.md) | [![Download Latest RealisticFlight.dll](.Resources/DownloadButtonEnabled.svg "Download Latest RealisticFlight.dll")](https://github.com/kafeijao/Kafe_CVR_Mods/releases/latest/download/RealisticFlight.dll) | Improve physics and add alternative flight |
Expand Down

0 comments on commit ea1e0b8

Please sign in to comment.