mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-02 06:19:22 +00:00
[PropSpawnTweaks] Initial builds
This commit is contained in:
parent
e2e4463663
commit
cafd24bd2f
9 changed files with 402 additions and 0 deletions
|
@ -63,6 +63,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OriginShift", "OriginShift\
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScrollFlight", "ScrollFlight\ScrollFlight.csproj", "{1B5D7DCB-01A4-4988-8B25-211948AEED76}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Portals", "Portals\Portals.csproj", "{BE9629C2-8461-481C-B267-1B8A1805DCD7}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PropSpawnTweaks", "PropSpawnTweaks\PropSpawnTweaks.csproj", "{642A2BC7-C027-4F8F-969C-EF0F867936FD}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -189,6 +193,14 @@ Global
|
|||
{1B5D7DCB-01A4-4988-8B25-211948AEED76}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1B5D7DCB-01A4-4988-8B25-211948AEED76}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1B5D7DCB-01A4-4988-8B25-211948AEED76}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BE9629C2-8461-481C-B267-1B8A1805DCD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BE9629C2-8461-481C-B267-1B8A1805DCD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BE9629C2-8461-481C-B267-1B8A1805DCD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BE9629C2-8461-481C-B267-1B8A1805DCD7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{642A2BC7-C027-4F8F-969C-EF0F867936FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{642A2BC7-C027-4F8F-969C-EF0F867936FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{642A2BC7-C027-4F8F-969C-EF0F867936FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{642A2BC7-C027-4F8F-969C-EF0F867936FD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
32
PropSpawnTweaks/Components/PropLoadingHexagon.cs
Normal file
32
PropSpawnTweaks/Components/PropLoadingHexagon.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace NAK.PropSpawnTweaks.Components;
|
||||
|
||||
public class PropLoadingHexagon : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private SkinnedMeshRenderer _hexRenderer;
|
||||
[SerializeField] private TMPro.TextMeshPro _loadingText;
|
||||
[SerializeField] private Transform[] _hexTransforms;
|
||||
private float _scale;
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_scale < 1f)
|
||||
{
|
||||
_scale += Time.deltaTime * 4f;
|
||||
transform.GetChild(0).localScale = Vector3.one * _scale;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
// give slightly different rotation to each hexagon
|
||||
_hexTransforms[i].Rotate(Vector3.up, 30f * Time.deltaTime * (i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetLoadingText(string text)
|
||||
=> _loadingText.text = text; // SetAllDirty
|
||||
|
||||
public void SetLoadingShape(float value)
|
||||
=> _hexRenderer.SetBlendShapeWeight(0, value);
|
||||
}
|
234
PropSpawnTweaks/Main.cs
Normal file
234
PropSpawnTweaks/Main.cs
Normal file
|
@ -0,0 +1,234 @@
|
|||
using System.Reflection;
|
||||
using ABI_RC.Core.IO;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Util;
|
||||
using DarkRift;
|
||||
using HarmonyLib;
|
||||
using MelonLoader;
|
||||
using NAK.PropSpawnTweaks.Components;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace NAK.PropSpawnTweaks;
|
||||
|
||||
public class PropSpawnTweaksMod : MelonMod
|
||||
{
|
||||
private static readonly ObjectPool<LoadingPropHex> Loading_Hex_Pool = new(0, () => new LoadingPropHex());
|
||||
private static readonly List<LoadingPropHex> Loading_Hex_List = new();
|
||||
private static GameObject loadingHexContainer;
|
||||
private static GameObject loadingHexPrefab;
|
||||
|
||||
#region Melon Events
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
HarmonyInstance.Patch( // create prop placeholder container
|
||||
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.Start),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
postfix: new HarmonyMethod(typeof(PropSpawnTweaksMod).GetMethod(nameof(OnPlayerSetupStart),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
HarmonyInstance.Patch( // make drop prop actually usable
|
||||
typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.DropProp),
|
||||
BindingFlags.Public | BindingFlags.Instance),
|
||||
new HarmonyMethod(typeof(PropSpawnTweaksMod).GetMethod(nameof(OnDropProp),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
HarmonyInstance.Patch( // spawn prop placeholder
|
||||
typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.SpawnPropFromNetwork),
|
||||
BindingFlags.Public | BindingFlags.Static),
|
||||
postfix: new HarmonyMethod(typeof(PropSpawnTweaksMod).GetMethod(nameof(OnPropSpawnedFromNetwork),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
LoadAssetBundle();
|
||||
}
|
||||
|
||||
public override void OnUpdate()
|
||||
{
|
||||
if (Loading_Hex_List.Count <= 0)
|
||||
return;
|
||||
|
||||
for (int i = Loading_Hex_List.Count - 1; i >= 0; i--)
|
||||
{
|
||||
LoadingPropHex loadingHex = Loading_Hex_List[i];
|
||||
if (loadingHex.propData == null || (loadingHex.propData.Wrapper != null
|
||||
&& loadingHex.propData.Spawnable != null))
|
||||
{
|
||||
loadingHex.Reset();
|
||||
Loading_Hex_Pool.Give(loadingHex);
|
||||
Loading_Hex_List.RemoveAt(i);
|
||||
return;
|
||||
}
|
||||
|
||||
loadingHex.Update();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Melon Events
|
||||
|
||||
#region Asset Bundle Loading
|
||||
|
||||
private const string LoadingHexagonAssets = "loading_hexagon.assets";
|
||||
private const string LoadingHexagonPrefab = "Assets/Mods/PropSpawnTweaks/Loading_Hexagon_Root.prefab";
|
||||
|
||||
private void LoadAssetBundle()
|
||||
{
|
||||
LoggerInstance.Msg($"Loading required asset bundle...");
|
||||
using Stream resourceStream = MelonAssembly.Assembly.GetManifestResourceStream(LoadingHexagonAssets);
|
||||
using MemoryStream memoryStream = new();
|
||||
if (resourceStream == null) {
|
||||
LoggerInstance.Error($"Failed to load {LoadingHexagonAssets}!");
|
||||
return;
|
||||
}
|
||||
|
||||
resourceStream.CopyTo(memoryStream);
|
||||
AssetBundle assetBundle = AssetBundle.LoadFromMemory(memoryStream.ToArray());
|
||||
if (assetBundle == null) {
|
||||
LoggerInstance.Error($"Failed to load {LoadingHexagonAssets}! Asset bundle is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
loadingHexPrefab = assetBundle.LoadAsset<GameObject>(LoadingHexagonPrefab);
|
||||
if (loadingHexPrefab == null) {
|
||||
LoggerInstance.Error($"Failed to load {LoadingHexagonPrefab}! Prefab is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
// modify prefab so nameplate billboard tmp shader is used
|
||||
MeshRenderer tmp = loadingHexPrefab.GetComponentInChildren<MeshRenderer>();
|
||||
tmp.sharedMaterial.shader = Shader.Find("Alpha Blend Interactive/TextMeshPro/Mobile/Distance Field-BillboardFacing");
|
||||
|
||||
LoggerInstance.Msg("Asset bundle successfully loaded!");
|
||||
}
|
||||
|
||||
#endregion Asset Bundle Loading
|
||||
|
||||
#region Harmony Patches
|
||||
|
||||
private static void OnPlayerSetupStart()
|
||||
{
|
||||
if (loadingHexContainer != null) return;
|
||||
loadingHexContainer = new GameObject("NAK.LoadingHexContainer");
|
||||
Object.DontDestroyOnLoad(loadingHexContainer);
|
||||
}
|
||||
|
||||
private static bool OnDropProp(string propGuid, ref PlayerSetup __instance)
|
||||
{
|
||||
Vector3 position = __instance.activeCam.transform.position + __instance.GetPlayerForward() * 1.5f; // 1f -> 1.5f
|
||||
|
||||
if (Physics.Raycast(position,
|
||||
__instance.CharacterController.GetGravityDirection(), // align with gravity, not player up
|
||||
out RaycastHit raycastHit, 4f, __instance.dropPlacementMask))
|
||||
{
|
||||
// native method passes false, so DropProp doesn't align with gravity :)
|
||||
CVRSyncHelper.SpawnProp(propGuid, raycastHit.point.x, raycastHit.point.y, raycastHit.point.z, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// unlike original, we will still spawn prop even if raycast fails, giving the method actual utility :3
|
||||
|
||||
// hack- we want to align with *our* rotation, not affecting gravity
|
||||
Vector3 ogGravity = __instance.CharacterController.GetGravityDirection();
|
||||
__instance.CharacterController.gravity = -__instance.transform.up; // align with our rotation
|
||||
|
||||
// spawn prop with useTargetLocationGravity false, so it pulls our gravity dir we've modified
|
||||
CVRSyncHelper.SpawnProp(propGuid, position.x, position.y, position.z, false);
|
||||
|
||||
__instance.CharacterController.gravity = ogGravity; // restore gravity
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void OnPropSpawnedFromNetwork(Message message)
|
||||
{
|
||||
// thank
|
||||
// https://feedback.abinteractive.net/p/gameeventsystem-spawnable-onload-is-kinda-useless
|
||||
|
||||
using DarkRiftReader reader = message.GetReader();
|
||||
var assetId = reader.ReadString();
|
||||
var instanceId = reader.ReadString();
|
||||
CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find(match => match.InstanceId == instanceId);
|
||||
if (propData == null)
|
||||
return; // props blocked by filter or player blocks, or just broken
|
||||
|
||||
if (!CVRDownloadManager.Instance._downloadTasks.TryGetValue(assetId, out DownloadTask downloadTask))
|
||||
return; // no download task, no prop placeholder
|
||||
|
||||
// create loading hex
|
||||
LoadingPropHex loadingHex = Loading_Hex_Pool.Take();
|
||||
loadingHex.downloadTask = downloadTask;
|
||||
loadingHex.Initialize(propData);
|
||||
|
||||
// add to list
|
||||
Loading_Hex_List.Add(loadingHex);
|
||||
}
|
||||
|
||||
#endregion Harmony Patches
|
||||
|
||||
#region LoadingPropHex Class
|
||||
|
||||
private class LoadingPropHex
|
||||
{
|
||||
public DownloadTask downloadTask;
|
||||
public CVRSyncHelper.PropData propData;
|
||||
private GameObject loadingObject;
|
||||
private PropLoadingHexagon loadingHexComponent;
|
||||
|
||||
// ReSharper disable once ParameterHidesMember
|
||||
public void Initialize(CVRSyncHelper.PropData propData)
|
||||
{
|
||||
this.propData = propData;
|
||||
if (loadingObject == null)
|
||||
{
|
||||
loadingObject = Object.Instantiate(loadingHexPrefab, Vector3.zero, Quaternion.identity, loadingHexContainer.transform);
|
||||
loadingHexComponent = loadingObject.GetComponent<PropLoadingHexagon>();
|
||||
|
||||
Update(); // set initial position
|
||||
|
||||
float avatarHeight = PlayerSetup.Instance._avatarHeight;
|
||||
Transform hexTransform = loadingObject.transform;
|
||||
hexTransform.localScale = Vector3.one * avatarHeight / 4f; // scale modifier
|
||||
hexTransform.GetChild(0).localPosition = Vector3.up * avatarHeight * 2f; // position modifier
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
string text;
|
||||
float progress = downloadTask.Progress;
|
||||
|
||||
if (downloadTask == null
|
||||
|| downloadTask.Status == DownloadTask.ExecutionStatus.Complete
|
||||
|| downloadTask.Progress >= 100f)
|
||||
text = "LOADING";
|
||||
else if (downloadTask.Status == DownloadTask.ExecutionStatus.Failed)
|
||||
text = "ERROR";
|
||||
else
|
||||
text = $"{progress} %";
|
||||
|
||||
loadingHexComponent.SetLoadingText(text);
|
||||
loadingHexComponent.SetLoadingShape(progress);
|
||||
loadingObject.transform.SetPositionAndRotation(
|
||||
new Vector3(propData.PositionX, propData.PositionY, propData.PositionZ),
|
||||
Quaternion.Euler(propData.RotationX, propData.RotationY, propData.RotationZ));
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
if (loadingObject != null)
|
||||
{
|
||||
Object.Destroy(loadingObject);
|
||||
loadingObject = null;
|
||||
}
|
||||
|
||||
propData = null;
|
||||
downloadTask = null;
|
||||
loadingObject = null;
|
||||
loadingHexComponent = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion LoadingPropHex Class
|
||||
}
|
45
PropSpawnTweaks/ObjectPool.cs
Normal file
45
PropSpawnTweaks/ObjectPool.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
namespace NAK.PropSpawnTweaks;
|
||||
|
||||
public class ObjectPool<T>
|
||||
{
|
||||
public int Count { get; private set; }
|
||||
|
||||
private readonly Queue<T> _available;
|
||||
private readonly Func<T> _createObjectFunc;
|
||||
|
||||
public ObjectPool(int numPreallocated = 0, Func<T> createObjectFunc = null)
|
||||
{
|
||||
_available = new Queue<T>();
|
||||
_createObjectFunc = createObjectFunc;
|
||||
|
||||
if (numPreallocated > 0) Give(MakeObject());
|
||||
}
|
||||
|
||||
public T Take()
|
||||
{
|
||||
T t;
|
||||
if (_available.Count > 0)
|
||||
{
|
||||
t = _available.Dequeue();
|
||||
Count--;
|
||||
}
|
||||
else
|
||||
{
|
||||
t = MakeObject();
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
public void Give(T t)
|
||||
{
|
||||
_available.Enqueue(t);
|
||||
Count++;
|
||||
}
|
||||
|
||||
private T MakeObject()
|
||||
{
|
||||
if (_createObjectFunc != null) return _createObjectFunc();
|
||||
throw new InvalidOperationException("Cannot automatically create new objects without a factory method");
|
||||
}
|
||||
}
|
10
PropSpawnTweaks/PropSpawnTweaks.csproj
Normal file
10
PropSpawnTweaks/PropSpawnTweaks.csproj
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\loading_hexagon.assets">
|
||||
<LogicalName>loading_hexagon.assets</LogicalName>
|
||||
</EmbeddedResource> </ItemGroup>
|
||||
</Project>
|
32
PropSpawnTweaks/Properties/AssemblyInfo.cs
Normal file
32
PropSpawnTweaks/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using NAK.PropSpawnTweaks.Properties;
|
||||
using MelonLoader;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.PropSpawnTweaks))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.PropSpawnTweaks))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.PropSpawnTweaks.PropSpawnTweaksMod),
|
||||
nameof(NAK.PropSpawnTweaks),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PropSpawnTweaks"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||
[assembly: MelonColor(255, 181, 137, 236)]
|
||||
[assembly: MelonAuthorColor(255, 158, 21, 32)]
|
||||
[assembly: HarmonyDontPatchAll]
|
||||
|
||||
namespace NAK.PropSpawnTweaks.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.0";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
14
PropSpawnTweaks/README.md
Normal file
14
PropSpawnTweaks/README.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
# PropSpawnTweaks
|
||||
|
||||
Gives the Drop Prop button more utility by allowing you to drop props in the air, as well as providing a prop loading indicator.
|
||||
|
||||
---
|
||||
|
||||
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
|
||||
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
|
||||
|
||||
> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive.
|
||||
|
||||
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
|
||||
|
||||
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.
|
BIN
PropSpawnTweaks/Resources/loading_hexagon.assets
Normal file
BIN
PropSpawnTweaks/Resources/loading_hexagon.assets
Normal file
Binary file not shown.
23
PropSpawnTweaks/format.json
Normal file
23
PropSpawnTweaks/format.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"_id": -1,
|
||||
"name": "PropSpawnTweaks",
|
||||
"modversion": "1.0.0",
|
||||
"gameversion": "2024r175",
|
||||
"loaderversion": "0.6.1",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Gives the Drop Prop button more utility by allowing you to drop props in the air, as well as providing a prop loading indicator.\n",
|
||||
"searchtags": [
|
||||
"prop",
|
||||
"spawn",
|
||||
"indicator",
|
||||
"loading",
|
||||
],
|
||||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r26/PropSpawnTweaks.dll",
|
||||
"sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PropSpawnTweaks/",
|
||||
"changelog": "- Initial Release",
|
||||
"embedcolor": "#b589ec"
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue