[NAK_CVR_Mods] Unfucked for 2026r182
12
.Deprecated/ComfortAlignment/ComfortAlignment.csproj
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<RootNamespace>ASTExtension</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="BTKUILib">
|
||||
<HintPath>..\.ManagedLibs\BTKUILib.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
166
.Deprecated/ComfortAlignment/Main.cs
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
using System.Reflection;
|
||||
using ABI_RC.Core;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Systems.Movement;
|
||||
using ABI_RC.Systems.PersonalPen;
|
||||
using ABI_RC.Systems.UI.UILib;
|
||||
using ABI_RC.Systems.UI.UILib.UIObjects;
|
||||
using ABI_RC.Systems.UI.UILib.UIObjects.Components;
|
||||
using ABI_RC.Systems.XRManagement;
|
||||
using HarmonyLib;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.ComfortAlignment;
|
||||
|
||||
public class ComfortAlignmentMod : MelonMod
|
||||
{
|
||||
private static readonly MelonPreferences_Category Category =
|
||||
MelonPreferences.CreateCategory(nameof(ComfortAlignment));
|
||||
|
||||
private static readonly MelonPreferences_Entry<bool> HiddenAcceptedMotionSicknessWarning =
|
||||
Category.CreateEntry(
|
||||
identifier: "accepted_warning",
|
||||
false,
|
||||
display_name: "Motion Sickness Warning",
|
||||
description: string.Empty,
|
||||
is_hidden: true);
|
||||
|
||||
private static readonly MelonPreferences_Entry<bool> EntrySnapAlignment =
|
||||
Category.CreateEntry(
|
||||
identifier: "snap_alignment",
|
||||
false,
|
||||
display_name: "Snap Alignment",
|
||||
description: "Should the alignment be locked to 90 degrees.");
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
HarmonyInstance.Patch(
|
||||
typeof(PenManager).GetMethod(nameof(PenManager.SetupUILib),
|
||||
BindingFlags.Public | BindingFlags.Static),
|
||||
postfix: new HarmonyMethod(typeof(ComfortAlignmentMod).GetMethod(nameof(OnSetupUILib),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
XRDeviceEvents.OnPostXRModeSwitch.AddListener(OnPostXRModeSwitch);
|
||||
}
|
||||
|
||||
private static Category _category;
|
||||
private static ToggleButton _enableToggle;
|
||||
private static Button _alignToHorizon;
|
||||
private static Button _resetHorizon;
|
||||
private static ToggleButton _snappingToggle;
|
||||
|
||||
private static void OnSetupUILib()
|
||||
{
|
||||
_category = QuickMenuAPI.CVRUtilsPage.AddCategory("Comfort Alignment", true, true);
|
||||
_category.Hidden = !MetaPort.Instance.isUsingVr;
|
||||
|
||||
_enableToggle = _category.AddToggle("Accepted Warning", "Enables the comfort alignment feature.", HiddenAcceptedMotionSicknessWarning.Value);
|
||||
_enableToggle.OnValueUpdated += OnEnableToggled;
|
||||
|
||||
_alignToHorizon = _category.AddButton("Align To Horizon", "Visibility", "Aligns your view to the horizon");
|
||||
_alignToHorizon.OnPress += () => RootLogic.RunInMainThread(AlignHorizon); // scheduled to apply early next frame, to avoid visual jitter
|
||||
_alignToHorizon.Disabled = !HiddenAcceptedMotionSicknessWarning.Value;
|
||||
|
||||
_resetHorizon = _category.AddButton("Reset Horizon", "Visibility", "Resets your view");
|
||||
_resetHorizon.OnPress += () => RootLogic.RunInMainThread(ResetHorizon); // scheduled to apply early next frame, to avoid visual jitter
|
||||
_resetHorizon.Disabled = true;
|
||||
|
||||
_snappingToggle = _category.AddToggle(EntrySnapAlignment.DisplayName, EntrySnapAlignment.Description, EntrySnapAlignment.Value);
|
||||
_snappingToggle.OnValueUpdated += (t) => EntrySnapAlignment.Value = t;
|
||||
_snappingToggle.Disabled = !HiddenAcceptedMotionSicknessWarning.Value;
|
||||
}
|
||||
|
||||
private static void OnEnableToggled(bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
QuickMenuAPI.ShowConfirm(
|
||||
"Motion Sickness Warning",
|
||||
"This feature adjusts your playspace orientation to align your view with the world horizon, improving accessibility when playing while reclining or lying down. It may cause motion sickness, dizziness, disorientation, or vertigo, particularly for users sensitive to motion or those new to virtual reality. Enable anyway?",
|
||||
() => SetFeatureEnabled(true),
|
||||
() => _enableToggle.ToggleValue = false);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetFeatureEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetFeatureEnabled(bool enabled)
|
||||
{
|
||||
_alignToHorizon.Disabled = !enabled;
|
||||
_snappingToggle.Disabled = !enabled;
|
||||
HiddenAcceptedMotionSicknessWarning.Value = enabled;
|
||||
if (!enabled && !_resetHorizon.Disabled) ResetHorizon();
|
||||
}
|
||||
|
||||
private static void OnPostXRModeSwitch(XRModeSwitchEventArgs events)
|
||||
{
|
||||
_category.Hidden = !events.IsUsingVr;
|
||||
if (events.WasUsingVr) ResetHorizonWithoutTeleport();
|
||||
}
|
||||
|
||||
private static void AlignHorizon()
|
||||
{
|
||||
_resetHorizon.Disabled = false;
|
||||
|
||||
// cache original pos to reapply after offsetting vr rig
|
||||
Vector3 playerPos = PlayerSetup.Instance.GetPlayerPosition();
|
||||
Quaternion playerRot = PlayerSetup.Instance.GetPlayerRotation();
|
||||
|
||||
bool snapAlignment = EntrySnapAlignment.Value;
|
||||
|
||||
// pivot point
|
||||
Vector3 camPos = PlayerSetup.Instance.vrCam.transform.position;
|
||||
|
||||
// what we are aligning from
|
||||
Vector3 camUp = PlayerSetup.Instance.vrCam.transform.up;
|
||||
Vector3 camForward = PlayerSetup.Instance.vrCam.transform.forward;
|
||||
|
||||
// what we're aligning to
|
||||
Vector3 playerUp = PlayerSetup.Instance.transform.up;
|
||||
Vector3 playerForward = PlayerSetup.Instance.GetPlayerForward();
|
||||
|
||||
Quaternion correction = Quaternion.FromToRotation(camUp, playerUp);
|
||||
|
||||
if (snapAlignment)
|
||||
{
|
||||
Vector3 refForward = Vector3.ProjectOnPlane(playerForward, playerUp);
|
||||
Vector3 flatForward = Vector3.ProjectOnPlane(correction * camForward, playerUp);
|
||||
if (refForward.sqrMagnitude > 1e-6f && flatForward.sqrMagnitude > 1e-6f)
|
||||
{
|
||||
float yaw = Vector3.SignedAngle(refForward, flatForward, playerUp);
|
||||
float deltaYaw = Mathf.DeltaAngle(yaw, Mathf.Round(yaw / 90f) * 90f);
|
||||
correction = Quaternion.AngleAxis(deltaYaw, playerUp) * correction;
|
||||
}
|
||||
}
|
||||
|
||||
correction.ToAngleAxis(out float angle, out Vector3 axis);
|
||||
if (angle != 0f) PlayerSetup.Instance.vrCameraRig.transform.RotateAround(camPos, axis, angle);
|
||||
|
||||
// reapply player positions (internally re-centers and whatever)
|
||||
BetterBetterCharacterController.Instance.TeleportPlayerTo(playerPos, playerRot.eulerAngles, false, false);
|
||||
}
|
||||
|
||||
private static void ResetHorizon()
|
||||
{
|
||||
_resetHorizon.Disabled = true;
|
||||
|
||||
// cache original pos to reapply after offsetting vr rig
|
||||
Vector3 playerPos = PlayerSetup.Instance.GetPlayerPosition();
|
||||
Quaternion playerRot = PlayerSetup.Instance.GetPlayerRotation();
|
||||
|
||||
PlayerSetup.Instance.vrCameraRig.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
|
||||
|
||||
// reapply player positions (internally re-centers and whatever)
|
||||
BetterBetterCharacterController.Instance.TeleportPlayerTo(playerPos, playerRot.eulerAngles, false, false);
|
||||
}
|
||||
|
||||
private static void ResetHorizonWithoutTeleport()
|
||||
{
|
||||
_resetHorizon.Disabled = true;
|
||||
PlayerSetup.Instance.vrCameraRig.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
|
||||
}
|
||||
}
|
||||
32
.Deprecated/ComfortAlignment/Properties/AssemblyInfo.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using MelonLoader;
|
||||
using NAK.ComfortAlignment.Properties;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.ComfortAlignment))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.ComfortAlignment))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.ComfortAlignment.ComfortAlignmentMod),
|
||||
nameof(NAK.ComfortAlignment),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ComfortAlignment"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
|
||||
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
|
||||
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
|
||||
[assembly: HarmonyDontPatchAll]
|
||||
|
||||
namespace NAK.ComfortAlignment.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.0";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
||||
54
.Deprecated/ComfortAlignment/README.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# ASTExtension
|
||||
|
||||
Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool):
|
||||
- VR Gesture to scale
|
||||
- Persistent height
|
||||
- Copy height from others
|
||||
|
||||
Best used with Avatar Scale Tool, but will attempt to work with found scaling setups.
|
||||
Requires already having Avatar Scaling on the avatar. This is **not** Universal Scaling.
|
||||
|
||||
## Supported Setups
|
||||
|
||||
ASTExtension will attempt to work with the following setups:
|
||||
|
||||
**Parameter Names:**
|
||||
- AvatarScale
|
||||
- Scale
|
||||
- Scaler
|
||||
- Scale/Scale
|
||||
- Height
|
||||
- LoliModifier
|
||||
- AvatarSize
|
||||
- Size
|
||||
- SizeScale
|
||||
- Scaling
|
||||
|
||||
These parameter names are not case sensitive and have been gathered from polling the community for common parameter names.
|
||||
|
||||
Assuming the parameter is a float, ASTExtension will attempt to use it as the height parameter. Will automatically calibrate to the height range of the found parameter, assuming the scaling animation is in a blend tree / state using motion time & is linear. The scaling animation state **must be active** at time of avatar load.
|
||||
|
||||
The max value ASTExtension will drive the parameter to is 100. As the mod is having to guess the max height, it may not be accurate if the max height is not capped at a multiple of 10.
|
||||
|
||||
Examples:
|
||||
- `AvatarScale` - 0 to 1 (slider)
|
||||
- This is the default setup for Avatar Scale Tool and will work perfectly.
|
||||
- `Scale` - 0 to 100 (input single)
|
||||
- This will also work perfectly as the max height is a multiple of 10.
|
||||
- `Height` - 0 to 2 (input single)
|
||||
- This will not work properly. The max value to drive the parameter to is not a multiple of 10, and as such ASTExtension will believe the parameter range is 0 to 1.
|
||||
- `BurntToast` - 0 to 10 (input single)
|
||||
- This will not work properly. The parameter name is not recognized by ASTExtension.
|
||||
|
||||
If your setup is theoretically supported but not working, it is likely the scaling animation is not linear or has loop enabled if using Motion Time, making the first and last frame identical height. In this case, you will need to fix your animation clip curves / blend tree to be linear &|| not loop, or use Avatar Scale Tool to generate a new scaling animation.
|
||||
|
||||
---
|
||||
|
||||
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.
|
||||
24
.Deprecated/ComfortAlignment/format.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"_id": 223,
|
||||
"name": "ASTExtension",
|
||||
"modversion": "1.0.5",
|
||||
"gameversion": "2025r181",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool):\n- VR Gesture to scale\n- Persistent height\n- Copy height from others\n\nBest used with Avatar Scale Tool, but will attempt to work with found scaling setups.\nRequires already having Avatar Scaling on the avatar. This is **not** Universal Scaling.",
|
||||
"searchtags": [
|
||||
"tool",
|
||||
"scaling",
|
||||
"height",
|
||||
"extension",
|
||||
"avatar"
|
||||
],
|
||||
"requirements": [
|
||||
"BTKUILib"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/ASTExtension.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ASTExtension/",
|
||||
"changelog": "- Rebuilt for CVR 2025r181",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<RootNamespace>YouAreMineNow</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Plugins\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Tomlet">
|
||||
<HintPath>Plugins\Tomlet.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
30
.Deprecated/DefaultDynamicBoneWind/Main.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
using ABI_RC.Core.Savior;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.DefaultDynamicBoneWind;
|
||||
|
||||
public class DefaultDynamicBoneWindMod : MelonMod
|
||||
{
|
||||
internal static MelonLogger.Instance Logger;
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
Logger = LoggerInstance;
|
||||
|
||||
CVRGameSettings.Init();
|
||||
|
||||
CVRGameSettings.CoolBool.OnChanged += ShitChanged;
|
||||
CVRGameSettings.Microphone.OnChanged += ShitChanged;
|
||||
}
|
||||
|
||||
private static void ShitChanged(Color old, Color now) => Logger.Msg($"Shit changed: {old} has now become {now}");
|
||||
private static void ShitChanged(string old, string now) => Logger.Msg($"ShitAudio changed: {old} has now become {now}");
|
||||
|
||||
public override void OnUpdate()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.P)) CVRGameSettings.CoolBool.Value = CVRGameSettings.CoolBool.Value == Color.white ? Color.black : Color.white;
|
||||
if (Input.GetKeyDown(KeyCode.C)) CVRGameSettings.TestSettingsCategory.ResetAll();
|
||||
if (Input.GetKeyDown(KeyCode.O)) Logger.Msg($"Setting value: {CVRGameSettings.CoolBool}");
|
||||
}
|
||||
}
|
||||
BIN
.Deprecated/DefaultDynamicBoneWind/Plugins/Tomlet.dll
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using MelonLoader;
|
||||
using NAK.DefaultDynamicBoneWind.Properties;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.DefaultDynamicBoneWind))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.DefaultDynamicBoneWind))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.DefaultDynamicBoneWind.DefaultDynamicBoneWindMod),
|
||||
nameof(NAK.DefaultDynamicBoneWind),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/DefaultDynamicBoneWind"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
|
||||
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
|
||||
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
|
||||
[assembly: HarmonyDontPatchAll]
|
||||
|
||||
namespace NAK.DefaultDynamicBoneWind.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.3";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
||||
14
.Deprecated/DefaultDynamicBoneWind/README.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# DefaultDynamicBoneWind
|
||||
|
||||
Makes wind influence default enabled for Dynamic Bones.
|
||||
|
||||
---
|
||||
|
||||
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.
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
using ABI_RC.Core.Networking.GameServer;
|
||||
using ABI_RC.Core.Networking.IO.Instancing;
|
||||
using ABI_RC.Systems.XRManagement;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ABI_RC.Core.Savior
|
||||
{
|
||||
public static class CVRGameSettings
|
||||
{
|
||||
public static readonly CVRSettingsFile GameSettingsFile = new("GameSettings");
|
||||
|
||||
public static readonly CVRSettingsCategory TestSettingsCategory =
|
||||
GameSettingsFile.CreateCategory("General", comment: "Test settings")
|
||||
.WithUI(sortOrder: 0, new UIFilters{ Platform = UIFilters.PlatformType.PCVR });
|
||||
|
||||
public static readonly CVRSetting<Color> CoolBool =
|
||||
TestSettingsCategory.Create("Setting", Color.white, comment: "A cool toggle")
|
||||
.WithUI(sortOrder: 0, tooltip: "Pick a color",
|
||||
filters: new UIFilters { Level = UIFilters.SettingsLevel.Basic });
|
||||
|
||||
public static readonly CVRSetting<bool> NotABool =
|
||||
TestSettingsCategory.Create("Setting 2", defaultValue: false, comment: "A really cool toggle");
|
||||
|
||||
public static readonly CVRSettingsCategory Audio =
|
||||
GameSettingsFile.CreateCategory("Audio", comment: "Audio volume levels", page: "Audio")
|
||||
.WithUI(sortOrder: 2);
|
||||
|
||||
public static readonly CVRSetting<string> Microphone =
|
||||
Audio.Create("Microphone", "default", comment: "Active microphone device")
|
||||
.WithVariant("VR", string.Empty)
|
||||
.WithUI(sortOrder: 0, tooltip: "Select your microphone device");
|
||||
|
||||
/// <summary>
|
||||
/// Settings with .WithVariant("VR", ...) or [Category.VR] in TOML respond to this.
|
||||
/// </summary>
|
||||
public static readonly CVRSettingsContext DeviceMode = new("DeviceMode");
|
||||
public static readonly CVRSettingsContext InstancePrivacy = new("InstancePrivacy");
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
GameSettingsFile.SetPath(Path.Combine(Application.persistentDataPath, "GameSettings.toml"));
|
||||
GameSettingsFile.RegisterContext(DeviceMode); // Listen for .VR setting variants
|
||||
GameSettingsFile.RegisterContext(InstancePrivacy); // Listen for .Public/.Private setting variants
|
||||
GameSettingsFile.Load();
|
||||
|
||||
SetDeviceModeActive(false);
|
||||
XRDeviceEvents.OnPostXRModeSwitch.AddListener(OnPostXRModeSwitch);
|
||||
GSInfoHandler.OnGSInfoUpdate += OnGSInfoUpdate;
|
||||
}
|
||||
|
||||
private static void OnPostXRModeSwitch(XRModeSwitchEventArgs eventArgs)
|
||||
=> SetDeviceModeActive(eventArgs.IsUsingVr);
|
||||
|
||||
private static void OnGSInfoUpdate(GSInfoUpdate gsInfoUpdate, GSInfoChanged gsInfoChanged)
|
||||
{
|
||||
if (gsInfoChanged != GSInfoChanged.InstancePrivacy) return;
|
||||
SetInstancePrivateActive(Instances.IsInPrivateInstance());
|
||||
}
|
||||
|
||||
private static void SetDeviceModeActive(bool isUsingVr)
|
||||
=> DeviceMode.Active = isUsingVr ? "VR" : null;
|
||||
private static void SetInstancePrivateActive(bool isPrivate)
|
||||
=> InstancePrivacy.Active = isPrivate ? "Private" : "Public";
|
||||
}
|
||||
}
|
||||
564
.Deprecated/DefaultDynamicBoneWind/Settings/CVRSettings.cs
Normal file
|
|
@ -0,0 +1,564 @@
|
|||
using ABI_RC.Core.IO;
|
||||
using ABI_RC.Core.Logger;
|
||||
using Tomlet;
|
||||
using Tomlet.Models;
|
||||
|
||||
namespace ABI_RC.Core.Savior
|
||||
{
|
||||
/// <summary>
|
||||
/// A named runtime context. The game sets Active at runtime.
|
||||
/// Settings with matching variant names update automatically.
|
||||
/// </summary>
|
||||
public sealed class CVRSettingsContext
|
||||
{
|
||||
public readonly string Name;
|
||||
|
||||
private string _active;
|
||||
|
||||
public event Action<string, string> OnChanged;
|
||||
|
||||
public CVRSettingsContext(string name) => Name = name;
|
||||
|
||||
public string Active
|
||||
{
|
||||
get => _active;
|
||||
set
|
||||
{
|
||||
if (_active == value) return;
|
||||
var old = _active;
|
||||
_active = value;
|
||||
OnChanged?.Invoke(old, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A .toml file on disk. Contains categories, each a [section].
|
||||
/// Variant sub-tables like [Category.VR] are stored within category tables.
|
||||
/// </summary>
|
||||
public sealed class CVRSettingsFile
|
||||
{
|
||||
public readonly string Name;
|
||||
|
||||
internal readonly Dictionary<string, CVRSettingsCategory> Categories = new();
|
||||
internal readonly List<CVRSettingsContext> Contexts = new();
|
||||
internal readonly HashSet<CVRSettingBase> VariantSettings = new();
|
||||
|
||||
private string _path;
|
||||
private TomlDocument _cached;
|
||||
private bool _failed;
|
||||
private bool _threwOnce;
|
||||
private bool _savePending;
|
||||
|
||||
public bool HasFailed => _failed;
|
||||
|
||||
public CVRSettingsFile(string name)
|
||||
{
|
||||
Name = name;
|
||||
CVRSettingsRegistry.Register(this);
|
||||
}
|
||||
|
||||
public void SetPath(string filePath) => _path = filePath;
|
||||
|
||||
public CVRSettingsCategory CreateCategory(string name, string comment = null, string page = null)
|
||||
{
|
||||
var cat = new CVRSettingsCategory(name, comment, page, this);
|
||||
Categories[name] = cat;
|
||||
return cat;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a context. When Active changes, only settings with variants are notified.
|
||||
/// </summary>
|
||||
public void RegisterContext(CVRSettingsContext context)
|
||||
{
|
||||
if (Contexts.Contains(context)) return;
|
||||
Contexts.Add(context);
|
||||
context.OnChanged += (_, _) =>
|
||||
{
|
||||
foreach (var setting in VariantSettings)
|
||||
setting.OnContextChanged();
|
||||
};
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
if (_path == null || !File.Exists(_path)) return;
|
||||
|
||||
try
|
||||
{
|
||||
var content = File.ReadAllText(_path);
|
||||
if (string.IsNullOrWhiteSpace(content)) return;
|
||||
|
||||
_cached = new TomlParser().Parse(content);
|
||||
|
||||
foreach (var cat in Categories.Values)
|
||||
cat.PopulateFromDocument(_cached);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_failed = true;
|
||||
CVRLogger.LogError($"[CVRSettings] Failed to load '{_path}': {e}. " +
|
||||
"Settings will not be saved this session.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
if (_path == null || _failed) return;
|
||||
|
||||
var doc = TomlDocument.CreateEmpty();
|
||||
|
||||
foreach (var cat in Categories.Values)
|
||||
{
|
||||
var table = new TomlTable();
|
||||
if (cat.Comment != null)
|
||||
table.Comments.PrecedingComment = cat.Comment;
|
||||
|
||||
foreach (var setting in cat.Settings.Values)
|
||||
{
|
||||
if (setting.SkipWrite) continue;
|
||||
var val = setting.ToTomlValue();
|
||||
if (val == null) continue;
|
||||
if (setting.Comment != null)
|
||||
val.Comments.PrecedingComment = setting.Comment;
|
||||
table.PutValue(setting.Name, val);
|
||||
}
|
||||
|
||||
var variantNames = new HashSet<string>();
|
||||
foreach (var setting in cat.Settings.Values)
|
||||
setting.CollectVariantNames(variantNames);
|
||||
|
||||
foreach (var variantName in variantNames)
|
||||
{
|
||||
var variantTable = new TomlTable { ForceNoInline = true };
|
||||
variantTable.Comments.PrecedingComment = $"Active when context is: {variantName}";
|
||||
|
||||
foreach (var setting in cat.Settings.Values)
|
||||
{
|
||||
var val = setting.GetVariantToml(variantName);
|
||||
if (val != null)
|
||||
variantTable.PutValue(setting.Name, val);
|
||||
}
|
||||
|
||||
if (variantTable.Entries.Count > 0)
|
||||
table.PutValue(variantName, variantTable);
|
||||
}
|
||||
|
||||
doc.PutValue(cat.Name, table);
|
||||
}
|
||||
|
||||
File.WriteAllText(_path, doc.SerializedValue);
|
||||
_cached = null;
|
||||
}
|
||||
|
||||
public void SaveImmediate() => Save();
|
||||
|
||||
public void ResetAll()
|
||||
{
|
||||
foreach (var cat in Categories.Values)
|
||||
cat.ResetAll();
|
||||
}
|
||||
|
||||
internal void GetActiveVariants(List<string> results)
|
||||
{
|
||||
results.Clear();
|
||||
foreach (var ctx in Contexts)
|
||||
if (ctx.Active != null)
|
||||
results.Add(ctx.Active);
|
||||
}
|
||||
|
||||
internal TomlValue GetCachedValue(string category, string key)
|
||||
{
|
||||
if (_cached == null) return null;
|
||||
if (!_cached.ContainsKey(category)) return null;
|
||||
var table = _cached.GetSubTable(category);
|
||||
return table.ContainsKey(key) ? table.GetValue(key) : null;
|
||||
}
|
||||
|
||||
internal TomlTable GetCachedCategoryTable(string category)
|
||||
{
|
||||
if (_cached == null) return null;
|
||||
return _cached.ContainsKey(category) ? _cached.GetSubTable(category) : null;
|
||||
}
|
||||
|
||||
internal void MarkDirty()
|
||||
{
|
||||
if (_path == null || _savePending || _failed) return;
|
||||
_savePending = true;
|
||||
BetterScheduleSystem.AddJob(() => { _savePending = false; Save(); }, 5f, 0f, 0);
|
||||
}
|
||||
|
||||
internal void ThrowIfFailed()
|
||||
{
|
||||
if (!_failed || _threwOnce) return;
|
||||
_threwOnce = true;
|
||||
throw new InvalidOperationException(
|
||||
$"[CVRSettings] File '{Name}' failed to load. " +
|
||||
"Subsequent access returns defaults.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A [section] within a .toml file.
|
||||
/// </summary>
|
||||
public sealed class CVRSettingsCategory
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly string Comment;
|
||||
public readonly string Page;
|
||||
|
||||
internal readonly CVRSettingsFile File;
|
||||
internal readonly Dictionary<string, CVRSettingBase> Settings = new();
|
||||
|
||||
public CVRCategoryUI UI;
|
||||
|
||||
internal CVRSettingsCategory(string name, string comment, string page, CVRSettingsFile file)
|
||||
{
|
||||
Name = name;
|
||||
Comment = comment;
|
||||
Page = page ?? file.Name;
|
||||
File = file;
|
||||
CVRSettingsRegistry.Register(this);
|
||||
}
|
||||
|
||||
public CVRSetting<T> Create<T>(string key, T defaultValue, string comment = null, bool writeDefault = true)
|
||||
{
|
||||
var s = new CVRSetting<T>(key, defaultValue, comment, writeDefault, this);
|
||||
|
||||
var cached = File.GetCachedValue(Name, key);
|
||||
if (cached != null)
|
||||
s.LoadFromToml(cached);
|
||||
|
||||
s.LoadVariantsFromCache();
|
||||
|
||||
Settings[key] = s;
|
||||
return s;
|
||||
}
|
||||
|
||||
public CVRSettingsCategory WithUI(int sortOrder = 0, UIFilters filters = null)
|
||||
{
|
||||
UI = new CVRCategoryUI { SortOrder = sortOrder, Filters = filters };
|
||||
return this;
|
||||
}
|
||||
|
||||
public void ResetAll()
|
||||
{
|
||||
foreach (var s in Settings.Values)
|
||||
s.ResetToDefault();
|
||||
}
|
||||
|
||||
internal void PopulateFromDocument(TomlDocument doc)
|
||||
{
|
||||
if (!doc.ContainsKey(Name)) return;
|
||||
var table = doc.GetSubTable(Name);
|
||||
|
||||
foreach (var setting in Settings.Values)
|
||||
{
|
||||
if (table.ContainsKey(setting.Name))
|
||||
setting.LoadFromToml(table.GetValue(setting.Name));
|
||||
setting.LoadVariantsFromCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for settings.
|
||||
/// </summary>
|
||||
public abstract class CVRSettingBase
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly string Comment;
|
||||
public readonly bool WriteDefault;
|
||||
|
||||
internal readonly CVRSettingsCategory Category;
|
||||
|
||||
public CVRSettingUI UI;
|
||||
|
||||
protected CVRSettingBase(string name, string comment, bool writeDefault, CVRSettingsCategory category)
|
||||
{
|
||||
Name = name;
|
||||
Comment = comment;
|
||||
WriteDefault = writeDefault;
|
||||
Category = category;
|
||||
}
|
||||
|
||||
internal abstract bool SkipWrite { get; }
|
||||
internal abstract TomlValue ToTomlValue();
|
||||
internal abstract void LoadFromToml(TomlValue value);
|
||||
internal abstract void LoadVariantsFromCache();
|
||||
internal abstract void ResetToDefault();
|
||||
internal abstract void OnContextChanged();
|
||||
internal abstract void CollectVariantNames(HashSet<string> names);
|
||||
internal abstract TomlValue GetVariantToml(string variantName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Typed setting. Supports any type Tomlet can serialize.
|
||||
/// Optionally has named variants that activate based on context state.
|
||||
/// </summary>
|
||||
public sealed class CVRSetting<T> : CVRSettingBase
|
||||
{
|
||||
public readonly T Default;
|
||||
|
||||
private T _value;
|
||||
private T _lastEffective;
|
||||
private Dictionary<string, T> _variants;
|
||||
|
||||
// ReSharper disable once StaticMemberInGenericType
|
||||
private static readonly List<string> ActiveBuffer = new();
|
||||
|
||||
public event Action<T, T> OnChanged;
|
||||
|
||||
internal CVRSetting(string name, T defaultValue, string comment, bool writeDefault,
|
||||
CVRSettingsCategory category) : base(name, comment, writeDefault, category)
|
||||
{
|
||||
Default = defaultValue;
|
||||
_value = defaultValue;
|
||||
_lastEffective = defaultValue;
|
||||
}
|
||||
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
Category.File.ThrowIfFailed();
|
||||
return Resolve();
|
||||
}
|
||||
set
|
||||
{
|
||||
Category.File.ThrowIfFailed();
|
||||
var activeVariant = FindActiveVariant();
|
||||
if (activeVariant != null)
|
||||
{
|
||||
SetVariant(activeVariant, value);
|
||||
return;
|
||||
}
|
||||
SetBase(value);
|
||||
}
|
||||
}
|
||||
|
||||
public T BaseValue
|
||||
{
|
||||
get => _value;
|
||||
set => SetBase(value);
|
||||
}
|
||||
|
||||
public void SetSilent(T value) => _value = value;
|
||||
|
||||
public static implicit operator T(CVRSetting<T> s) => s.Value;
|
||||
|
||||
public override string ToString() => Resolve()?.ToString() ?? "";
|
||||
|
||||
// UI
|
||||
|
||||
public CVRSetting<T> WithUI(int sortOrder = 0, string tooltip = null, UIFilters filters = null)
|
||||
{
|
||||
UI = new CVRSettingUI { SortOrder = sortOrder, Tooltip = tooltip, Filters = filters };
|
||||
return this;
|
||||
}
|
||||
|
||||
// Variants
|
||||
|
||||
/// <summary>
|
||||
/// Provide a code-default variant value. TOML values take precedence.
|
||||
/// Returns self for chaining.
|
||||
/// </summary>
|
||||
public CVRSetting<T> WithVariant(string variantName, T value)
|
||||
{
|
||||
_variants ??= new Dictionary<string, T>();
|
||||
_variants.TryAdd(variantName, value);
|
||||
Category.File.VariantSettings.Add(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void SetVariant(string variantName, T value)
|
||||
{
|
||||
_variants ??= new Dictionary<string, T>();
|
||||
if (_variants.TryGetValue(variantName, out var existing)
|
||||
&& EqualityComparer<T>.Default.Equals(existing, value))
|
||||
return;
|
||||
|
||||
var oldEffective = Resolve();
|
||||
_variants[variantName] = value;
|
||||
Category.File.VariantSettings.Add(this);
|
||||
var newEffective = Resolve();
|
||||
FireIfChanged(oldEffective, newEffective);
|
||||
Category.File.MarkDirty();
|
||||
}
|
||||
|
||||
public T GetVariant(string variantName)
|
||||
{
|
||||
if (_variants != null && _variants.TryGetValue(variantName, out var val))
|
||||
return val;
|
||||
return _value;
|
||||
}
|
||||
|
||||
public bool HasVariant(string variantName)
|
||||
=> _variants != null && _variants.ContainsKey(variantName);
|
||||
|
||||
// Resolution
|
||||
|
||||
private T Resolve()
|
||||
{
|
||||
if (_variants != null)
|
||||
{
|
||||
Category.File.GetActiveVariants(ActiveBuffer);
|
||||
foreach (var name in ActiveBuffer)
|
||||
if (_variants.TryGetValue(name, out var val))
|
||||
return val;
|
||||
}
|
||||
return _value;
|
||||
}
|
||||
|
||||
private string FindActiveVariant()
|
||||
{
|
||||
if (_variants == null) return null;
|
||||
Category.File.GetActiveVariants(ActiveBuffer);
|
||||
foreach (var name in ActiveBuffer)
|
||||
if (_variants.ContainsKey(name))
|
||||
return name;
|
||||
return null;
|
||||
}
|
||||
|
||||
private void SetBase(T value)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(_value, value)) return;
|
||||
var oldEffective = Resolve();
|
||||
_value = value;
|
||||
var newEffective = Resolve();
|
||||
FireIfChanged(oldEffective, newEffective);
|
||||
Category.File.MarkDirty();
|
||||
}
|
||||
|
||||
private void FireIfChanged(T oldEffective, T newEffective)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(oldEffective, newEffective)) return;
|
||||
_lastEffective = newEffective;
|
||||
OnChanged?.Invoke(oldEffective, newEffective);
|
||||
}
|
||||
|
||||
internal override void OnContextChanged()
|
||||
{
|
||||
var newEffective = Resolve();
|
||||
if (EqualityComparer<T>.Default.Equals(_lastEffective, newEffective)) return;
|
||||
var old = _lastEffective;
|
||||
_lastEffective = newEffective;
|
||||
OnChanged?.Invoke(old, newEffective);
|
||||
}
|
||||
|
||||
// Serialization
|
||||
|
||||
internal override bool SkipWrite
|
||||
=> !WriteDefault && EqualityComparer<T>.Default.Equals(_value, Default);
|
||||
|
||||
internal override void ResetToDefault()
|
||||
{
|
||||
var oldEffective = Resolve();
|
||||
_value = Default;
|
||||
_variants?.Clear();
|
||||
var newEffective = Resolve();
|
||||
FireIfChanged(oldEffective, newEffective);
|
||||
Category.File.MarkDirty();
|
||||
}
|
||||
|
||||
internal override TomlValue ToTomlValue() => TomletMain.ValueFrom(_value);
|
||||
|
||||
internal override void LoadFromToml(TomlValue val)
|
||||
{
|
||||
try { _value = TomletMain.To<T>(val); _lastEffective = _value; }
|
||||
catch { /* keep default */ }
|
||||
}
|
||||
|
||||
internal override void LoadVariantsFromCache()
|
||||
{
|
||||
var categoryTable = Category.File.GetCachedCategoryTable(Category.Name);
|
||||
if (categoryTable == null) return;
|
||||
|
||||
foreach (var key in categoryTable.Keys)
|
||||
{
|
||||
TomlValue entry;
|
||||
try { entry = categoryTable.GetValue(key); }
|
||||
catch { continue; }
|
||||
|
||||
if (entry is not TomlTable variantTable) continue;
|
||||
if (!variantTable.ContainsKey(Name)) continue;
|
||||
|
||||
try
|
||||
{
|
||||
_variants ??= new Dictionary<string, T>();
|
||||
_variants[key] = TomletMain.To<T>(variantTable.GetValue(Name));
|
||||
Category.File.VariantSettings.Add(this);
|
||||
}
|
||||
catch { /* skip bad values */ }
|
||||
}
|
||||
}
|
||||
|
||||
internal override void CollectVariantNames(HashSet<string> names)
|
||||
{
|
||||
if (_variants == null) return;
|
||||
foreach (var name in _variants.Keys)
|
||||
names.Add(name);
|
||||
}
|
||||
|
||||
internal override TomlValue GetVariantToml(string variantName)
|
||||
{
|
||||
if (_variants == null || !_variants.TryGetValue(variantName, out var val))
|
||||
return null;
|
||||
return TomletMain.ValueFrom(val);
|
||||
}
|
||||
}
|
||||
|
||||
// UI metadata shit for ui lib
|
||||
|
||||
public sealed class CVRCategoryUI
|
||||
{
|
||||
public int SortOrder;
|
||||
public UIFilters Filters;
|
||||
}
|
||||
|
||||
public sealed class CVRSettingUI
|
||||
{
|
||||
public int SortOrder;
|
||||
public string Tooltip;
|
||||
public UIFilters Filters;
|
||||
}
|
||||
|
||||
public sealed class UIFilters
|
||||
{
|
||||
[Flags] public enum SettingsLevel { None = 0, Basic = 1, Advanced = 2, Niche = 4 }
|
||||
[Flags] public enum PlatformType { None = 0, PCVR = 1, PCDesktop = 2, AndroidVR = 4 }
|
||||
[Flags] public enum ContextType { None = 0, Player = 1, Editor = 2 }
|
||||
[Flags] public enum LoaderType { None = 0, OpenVR = 1, OpenXR = 2, MetaSDK = 4, PicoSDK = 8 }
|
||||
[Flags] public enum ControllerType { None = 0, Others = 1, Index = 2, ViveWands = 4 }
|
||||
[Flags] public enum ContentType { None = 0, Default = 1, Mature = 2 }
|
||||
[Flags] public enum AccountRank { None = 0, User = 1, Moderator = 2, Developer = 4 }
|
||||
|
||||
// None means no filter applied, ignore
|
||||
public SettingsLevel Level = SettingsLevel.None;
|
||||
public PlatformType Platform = PlatformType.None;
|
||||
public ContextType Context = ContextType.None;
|
||||
public LoaderType Loader = LoaderType.None;
|
||||
public ControllerType Controller = ControllerType.None;
|
||||
public ContentType Content = ContentType.None;
|
||||
public AccountRank Rank = AccountRank.None;
|
||||
}
|
||||
|
||||
public static class CVRSettingsRegistry
|
||||
{
|
||||
public static readonly Dictionary<string, CVRSettingsFile> Files = new();
|
||||
public static readonly Dictionary<string, List<CVRSettingsCategory>> Pages = new();
|
||||
|
||||
internal static void Register(CVRSettingsFile file) => Files[file.Name] = file;
|
||||
|
||||
internal static void Register(CVRSettingsCategory category)
|
||||
{
|
||||
if (!Pages.TryGetValue(category.Page, out var list))
|
||||
{
|
||||
list = new List<CVRSettingsCategory>();
|
||||
Pages[category.Page] = list;
|
||||
}
|
||||
list.Add(category);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
.Deprecated/DefaultDynamicBoneWind/format.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"_id": -1,
|
||||
"name": "DefaultDynamicBoneWind",
|
||||
"modversion": "1.0.0",
|
||||
"gameversion": "2025r181-hf2",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Enables Wind Zone influence on Dynamic Bones by default.",
|
||||
"searchtags": [
|
||||
"dynamic",
|
||||
"bone",
|
||||
"wind",
|
||||
"zone"
|
||||
],
|
||||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/DefaultDynamicBoneWind.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/DefaultDynamicBoneWind/",
|
||||
"changelog": "- Initial release",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
||||
6
.Deprecated/FastStartup/FastStartup.csproj
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<RootNamespace>ASTExtension</RootNamespace>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
237
.Deprecated/FastStartup/Main.cs
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Xml.Serialization;
|
||||
using ABI_RC.Core.Base;
|
||||
using ABI_RC.Core.EventSystem;
|
||||
using ABI_RC.Core.Networking;
|
||||
using ABI_RC.Core.Networking.API;
|
||||
using ABI_RC.Core.Networking.API.Responses;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Core.Savior.SceneManagers;
|
||||
using ABI_RC.Systems.InputManagement;
|
||||
using ABI_RC.Systems.UI;
|
||||
using HarmonyLib;
|
||||
using MelonLoader;
|
||||
using Newtonsoft.Json;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace NAK.FastStartup;
|
||||
|
||||
public class FastStartupMod : MelonMod
|
||||
{
|
||||
private const string APIAddress = "https://api.chilloutvr.net";
|
||||
private const string APIVersion = "1";
|
||||
|
||||
private static MelonLogger.Instance Logger;
|
||||
private static LoginProfile _profile;
|
||||
private static Task<BaseResponse<UserAuthResponse>> _pendingAuth;
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
Logger = LoggerInstance;
|
||||
|
||||
LoginRoom.IsPlayerIn = true;
|
||||
|
||||
// Load profile and fire auth request as early as possible
|
||||
_profile = LoadFirstProfile();
|
||||
if (_profile != null)
|
||||
{
|
||||
Logger.Msg($"Firing early auth for {_profile.Username}...");
|
||||
_pendingAuth = AuthenticateAsync(_profile.Username, _profile.AccessKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error("No valid profile found.");
|
||||
}
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(Preparation).GetMethod(nameof(Preparation.Start),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
prefix: new HarmonyMethod(typeof(FastStartupMod).GetMethod(nameof(OnPrePreparationStart),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(Init).GetMethod(nameof(Init.Start),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
prefix: new HarmonyMethod(typeof(FastStartupMod).GetMethod(nameof(OnMethodWeDontCareAboutAndWantToFuckOff),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
HarmonyInstance.Patch(
|
||||
typeof(IntroManager).GetMethod(nameof(IntroManager.Start),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
prefix: new HarmonyMethod(typeof(FastStartupMod).GetMethod(nameof(OnMethodWeDontCareAboutAndWantToFuckOff),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
HarmonyInstance.Patch(
|
||||
typeof(IntroManager).GetMethod(nameof(IntroManager.OnDestroy),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
prefix: new HarmonyMethod(typeof(FastStartupMod).GetMethod(nameof(OnMethodWeDontCareAboutAndWantToFuckOff),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
HarmonyInstance.Patch(
|
||||
typeof(LoginRoom).GetMethod(nameof(LoginRoom.Start),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
prefix: new HarmonyMethod(typeof(FastStartupMod).GetMethod(nameof(OnLoginRoomStart),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
|
||||
Logger.Msg("FastStartup initialized");
|
||||
}
|
||||
|
||||
private static bool OnPrePreparationStart()
|
||||
{
|
||||
MelonCoroutines.Start(WaitForAuthAndContinue());
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool OnMethodWeDontCareAboutAndWantToFuckOff() => false;
|
||||
|
||||
private static void OnLoginRoomStart() => WorldTransitionSystem.Instance.ContinueTransition();
|
||||
|
||||
private static IEnumerator WaitForAuthAndContinue()
|
||||
{
|
||||
Logger.Msg("Waiting for early auth to complete...");
|
||||
|
||||
// Load init scene
|
||||
SceneManager.LoadScene("Init");
|
||||
yield return null;
|
||||
|
||||
PlayerSetup.Instance.ToggleCameras(true, true);
|
||||
WorldTransitionSystem.Instance.StartTransition(true);
|
||||
CursorLockManager.Instance.UpdateCursorState();
|
||||
|
||||
if (_pendingAuth == null)
|
||||
{
|
||||
MelonCoroutines.Start(LoginRoom.LoadScene());
|
||||
yield break;
|
||||
}
|
||||
|
||||
while (!_pendingAuth.IsCompleted)
|
||||
yield return null;
|
||||
|
||||
if (_pendingAuth.IsFaulted || _pendingAuth.Result?.Data == null)
|
||||
{
|
||||
Logger.Error($"Early auth failed: {_pendingAuth.Exception?.Message ?? "null response"}");
|
||||
MelonCoroutines.Start(LoginRoom.LoadScene());
|
||||
yield break;
|
||||
}
|
||||
|
||||
var authData = _pendingAuth.Result.Data;
|
||||
Logger.Msg($"Early auth succeeded: {authData.Username} ({authData.UserId})");
|
||||
|
||||
MelonCoroutines.Start(AuthManager.AuthenticationSucceeded(_pendingAuth.Result, AuthManager.AuthEntity.ABIProfile));
|
||||
MelonCoroutines.Start(LoadOurContent());
|
||||
}
|
||||
|
||||
private static IEnumerator LoadOurContent()
|
||||
{
|
||||
yield return null;
|
||||
Content.LoadIntoWorld(MetaPort.Instance.homeWorldGuid, true);
|
||||
AssetManagement.Instance.LoadLocalAvatar(MetaPort.Instance.currentAvatarGuid);
|
||||
}
|
||||
|
||||
#region Profile Loading
|
||||
|
||||
private static LoginProfile LoadFirstProfile()
|
||||
{
|
||||
try
|
||||
{
|
||||
/*var profileFiles = Directory.GetFiles(Application.dataPath, "*.profile");
|
||||
if (profileFiles.Length == 0)
|
||||
{
|
||||
Logger.Warning("No .profile files found");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Load first valid profile found
|
||||
foreach (var file in profileFiles)
|
||||
{*/
|
||||
string file = Path.Combine(Application.dataPath, "autologin.profile");
|
||||
if (File.Exists(file))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var reader = new StreamReader(file);
|
||||
var serializer = new XmlSerializer(typeof(LoginProfile));
|
||||
var profile = (LoginProfile)serializer.Deserialize(reader);
|
||||
|
||||
if (!string.IsNullOrEmpty(profile?.Username) && !string.IsNullOrEmpty(profile?.AccessKey))
|
||||
{
|
||||
Logger.Msg($"Loaded profile: {profile.Username}");
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning($"Failed to load {Path.GetFileName(file)}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"Profile loading failed: {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion Profile Loading
|
||||
|
||||
#region Auth Request
|
||||
|
||||
private static async Task<BaseResponse<UserAuthResponse>> AuthenticateAsync(string username, string accessKey)
|
||||
{
|
||||
using var client = new HttpClient();
|
||||
client.Timeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
client.DefaultRequestHeaders.Add(ApiConnection.HeaderUsername, username);
|
||||
client.DefaultRequestHeaders.Add(ApiConnection.HeaderAccessKey, accessKey);
|
||||
client.DefaultRequestHeaders.Add(ApiConnection.HeaderUserAgent, $"ChilloutVR/{Application.version} (fuck you)");
|
||||
client.DefaultRequestHeaders.Add(ApiConnection.HeaderPlatform, MetaPort.Platform);
|
||||
client.DefaultRequestHeaders.Add(ApiConnection.HeaderCompatibleVersions, MetaPort.CompatibleVersions);
|
||||
|
||||
var payload = JsonConvert.SerializeObject(new
|
||||
{
|
||||
Username = username,
|
||||
Password = accessKey,
|
||||
AuthType = 1,
|
||||
get = "?acceptTOS=true"
|
||||
});
|
||||
|
||||
var url = $"{APIAddress}/{APIVersion}/users/auth?acceptTOS=true";
|
||||
|
||||
try
|
||||
{
|
||||
var response = await client.PostAsync(url, new StringContent(payload, Encoding.UTF8, "application/json"));
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Logger.Error($"Auth failed: {response.StatusCode}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<BaseResponse<UserAuthResponse>>(body);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"Auth request failed: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Auth Request
|
||||
}
|
||||
|
||||
[XmlRoot("LoginProfile")]
|
||||
public class LoginProfile
|
||||
{
|
||||
[XmlElement("Username")] public string Username { get; set; }
|
||||
[XmlElement("AccessKey")] public string AccessKey { get; set; }
|
||||
}
|
||||
32
.Deprecated/FastStartup/Properties/AssemblyInfo.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using MelonLoader;
|
||||
using NAK.FastStartup.Properties;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.FastStartup))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.FastStartup))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.FastStartup.FastStartupMod),
|
||||
nameof(NAK.FastStartup),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/FastStartup"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
|
||||
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
|
||||
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
|
||||
[assembly: HarmonyDontPatchAll]
|
||||
|
||||
namespace NAK.FastStartup.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.5";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
||||
54
.Deprecated/FastStartup/README.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# ASTExtension
|
||||
|
||||
Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool):
|
||||
- VR Gesture to scale
|
||||
- Persistent height
|
||||
- Copy height from others
|
||||
|
||||
Best used with Avatar Scale Tool, but will attempt to work with found scaling setups.
|
||||
Requires already having Avatar Scaling on the avatar. This is **not** Universal Scaling.
|
||||
|
||||
## Supported Setups
|
||||
|
||||
ASTExtension will attempt to work with the following setups:
|
||||
|
||||
**Parameter Names:**
|
||||
- AvatarScale
|
||||
- Scale
|
||||
- Scaler
|
||||
- Scale/Scale
|
||||
- Height
|
||||
- LoliModifier
|
||||
- AvatarSize
|
||||
- Size
|
||||
- SizeScale
|
||||
- Scaling
|
||||
|
||||
These parameter names are not case sensitive and have been gathered from polling the community for common parameter names.
|
||||
|
||||
Assuming the parameter is a float, ASTExtension will attempt to use it as the height parameter. Will automatically calibrate to the height range of the found parameter, assuming the scaling animation is in a blend tree / state using motion time & is linear. The scaling animation state **must be active** at time of avatar load.
|
||||
|
||||
The max value ASTExtension will drive the parameter to is 100. As the mod is having to guess the max height, it may not be accurate if the max height is not capped at a multiple of 10.
|
||||
|
||||
Examples:
|
||||
- `AvatarScale` - 0 to 1 (slider)
|
||||
- This is the default setup for Avatar Scale Tool and will work perfectly.
|
||||
- `Scale` - 0 to 100 (input single)
|
||||
- This will also work perfectly as the max height is a multiple of 10.
|
||||
- `Height` - 0 to 2 (input single)
|
||||
- This will not work properly. The max value to drive the parameter to is not a multiple of 10, and as such ASTExtension will believe the parameter range is 0 to 1.
|
||||
- `BurntToast` - 0 to 10 (input single)
|
||||
- This will not work properly. The parameter name is not recognized by ASTExtension.
|
||||
|
||||
If your setup is theoretically supported but not working, it is likely the scaling animation is not linear or has loop enabled if using Motion Time, making the first and last frame identical height. In this case, you will need to fix your animation clip curves / blend tree to be linear &|| not loop, or use Avatar Scale Tool to generate a new scaling animation.
|
||||
|
||||
---
|
||||
|
||||
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.
|
||||
24
.Deprecated/FastStartup/format.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"_id": 223,
|
||||
"name": "ASTExtension",
|
||||
"modversion": "1.0.5",
|
||||
"gameversion": "2025r181",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool):\n- VR Gesture to scale\n- Persistent height\n- Copy height from others\n\nBest used with Avatar Scale Tool, but will attempt to work with found scaling setups.\nRequires already having Avatar Scaling on the avatar. This is **not** Universal Scaling.",
|
||||
"searchtags": [
|
||||
"tool",
|
||||
"scaling",
|
||||
"height",
|
||||
"extension",
|
||||
"avatar"
|
||||
],
|
||||
"requirements": [
|
||||
"BTKUILib"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/ASTExtension.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ASTExtension/",
|
||||
"changelog": "- Rebuilt for CVR 2025r181",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
||||
78
.Deprecated/MirroredReflection/Main.cs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
using System.Reflection;
|
||||
using ABI_RC.Core;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Systems.ContentClones;
|
||||
using ABI_RC.Systems.GameEventSystem;
|
||||
using ABI.CCK.Components;
|
||||
using HarmonyLib;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.MirroredReflection;
|
||||
|
||||
public class MirroredReflectionMod : MelonMod
|
||||
{
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
CVRGameEventSystem.Avatar.OnLocalAvatarLoad.AddListener(OnLocalAvatarLoad);
|
||||
CVRGameEventSystem.Avatar.OnLocalAvatarClear.AddListener(OnLocalAvatarClear);
|
||||
CVRGameEventSystem.Avatar.OnLocalAvatarHeightScale.AddListener(OnLocalAvatarHeightScale);
|
||||
HarmonyInstance.Patch(
|
||||
typeof(CVRMirror).GetMethod(nameof(CVRMirror.Start),
|
||||
BindingFlags.NonPublic | BindingFlags.Instance),
|
||||
postfix: new HarmonyMethod(typeof(MirroredReflectionMod).GetMethod(nameof(OnPostCVRMirrorStart),
|
||||
BindingFlags.NonPublic | BindingFlags.Static))
|
||||
);
|
||||
}
|
||||
|
||||
private static readonly ContentCloneManager.CloneOptions PlayerClone = new()
|
||||
{
|
||||
SyncTransforms = true,
|
||||
SyncRootPosition = true,
|
||||
SyncRootScale = false,
|
||||
SyncMaterialSwaps = true,
|
||||
SyncProbeAnchors = false,
|
||||
OverrideLayers = true,
|
||||
CloneLayer = CVRLayers.PlayerClone,
|
||||
OnlyRenderIfOriginalNotInCamera = true,
|
||||
ShowCameraTypes = CVRCameraTypeFlags.UserGeneratedContent | CVRCameraTypeFlags.PortableCamera | CVRCameraTypeFlags.MirrorCamera | CVRCameraTypeFlags.CaptureCamera
|
||||
};
|
||||
|
||||
private static ContentCloneManager.CloneData _mirrorClone;
|
||||
|
||||
private static void OnLocalAvatarLoad(CVRAvatar avatar)
|
||||
{
|
||||
if (!avatar) return;
|
||||
_mirrorClone = ContentCloneManager.CreateClone(avatar.gameObject, PlayerClone);
|
||||
if (_mirrorClone != null)
|
||||
{
|
||||
_mirrorClone.CloneRootTransform.localScale = new Vector3(-1f, 1f, 1f);
|
||||
_mirrorClone.LastSourceRootScale = avatar.transform.localScale;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnLocalAvatarClear(CVRAvatar avatar)
|
||||
{
|
||||
if (_mirrorClone is { IsDestroyed: false })
|
||||
{
|
||||
UnityEngine.Object.Destroy(_mirrorClone.CloneRoot);
|
||||
_mirrorClone = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnLocalAvatarHeightScale(float height, float scale)
|
||||
{
|
||||
if (_mirrorClone is { IsDestroyed: false })
|
||||
{
|
||||
Vector3 localScale = PlayerSetup.Instance.AvatarTransform.localScale;
|
||||
localScale.x *= -1f;
|
||||
_mirrorClone.CloneRootTransform.localScale = localScale;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnPostCVRMirrorStart(CVRMirror __instance)
|
||||
{
|
||||
__instance.m_ReflectLayers &= ~(1 << CVRLayers.PlayerLocal); // remove PlayerLocal
|
||||
__instance.m_ReflectLayers |= (1 << CVRLayers.PlayerClone); // add PlayerClone
|
||||
}
|
||||
}
|
||||
6
.Deprecated/MirroredReflection/MirroredReflection.csproj
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<RootNamespace>ASTExtension</RootNamespace>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
32
.Deprecated/MirroredReflection/Properties/AssemblyInfo.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using MelonLoader;
|
||||
using NAK.MirroredReflection.Properties;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.MirroredReflection))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.MirroredReflection))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.MirroredReflection.MirroredReflectionMod),
|
||||
nameof(NAK.MirroredReflection),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/MirroredReflection"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("ChilloutVR", "ChilloutVR")]
|
||||
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||
[assembly: MelonColor(255, 246, 25, 99)] // red-pink
|
||||
[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
|
||||
[assembly: HarmonyDontPatchAll]
|
||||
|
||||
namespace NAK.MirroredReflection.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.0";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
||||
54
.Deprecated/MirroredReflection/README.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# ASTExtension
|
||||
|
||||
Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool):
|
||||
- VR Gesture to scale
|
||||
- Persistent height
|
||||
- Copy height from others
|
||||
|
||||
Best used with Avatar Scale Tool, but will attempt to work with found scaling setups.
|
||||
Requires already having Avatar Scaling on the avatar. This is **not** Universal Scaling.
|
||||
|
||||
## Supported Setups
|
||||
|
||||
ASTExtension will attempt to work with the following setups:
|
||||
|
||||
**Parameter Names:**
|
||||
- AvatarScale
|
||||
- Scale
|
||||
- Scaler
|
||||
- Scale/Scale
|
||||
- Height
|
||||
- LoliModifier
|
||||
- AvatarSize
|
||||
- Size
|
||||
- SizeScale
|
||||
- Scaling
|
||||
|
||||
These parameter names are not case sensitive and have been gathered from polling the community for common parameter names.
|
||||
|
||||
Assuming the parameter is a float, ASTExtension will attempt to use it as the height parameter. Will automatically calibrate to the height range of the found parameter, assuming the scaling animation is in a blend tree / state using motion time & is linear. The scaling animation state **must be active** at time of avatar load.
|
||||
|
||||
The max value ASTExtension will drive the parameter to is 100. As the mod is having to guess the max height, it may not be accurate if the max height is not capped at a multiple of 10.
|
||||
|
||||
Examples:
|
||||
- `AvatarScale` - 0 to 1 (slider)
|
||||
- This is the default setup for Avatar Scale Tool and will work perfectly.
|
||||
- `Scale` - 0 to 100 (input single)
|
||||
- This will also work perfectly as the max height is a multiple of 10.
|
||||
- `Height` - 0 to 2 (input single)
|
||||
- This will not work properly. The max value to drive the parameter to is not a multiple of 10, and as such ASTExtension will believe the parameter range is 0 to 1.
|
||||
- `BurntToast` - 0 to 10 (input single)
|
||||
- This will not work properly. The parameter name is not recognized by ASTExtension.
|
||||
|
||||
If your setup is theoretically supported but not working, it is likely the scaling animation is not linear or has loop enabled if using Motion Time, making the first and last frame identical height. In this case, you will need to fix your animation clip curves / blend tree to be linear &|| not loop, or use Avatar Scale Tool to generate a new scaling animation.
|
||||
|
||||
---
|
||||
|
||||
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ChilloutVR.
|
||||
https://docs.chilloutvr.net/official/legal/tos/#6-modding-our-game
|
||||
|
||||
> This mod is an independent creation not affiliated with, supported by, or approved by ChilloutVR.
|
||||
|
||||
> 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 ChilloutVR.
|
||||
24
.Deprecated/MirroredReflection/format.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"_id": 223,
|
||||
"name": "ASTExtension",
|
||||
"modversion": "1.0.5",
|
||||
"gameversion": "2025r181",
|
||||
"loaderversion": "0.7.2",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Extension mod for [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool):\n- VR Gesture to scale\n- Persistent height\n- Copy height from others\n\nBest used with Avatar Scale Tool, but will attempt to work with found scaling setups.\nRequires already having Avatar Scaling on the avatar. This is **not** Universal Scaling.",
|
||||
"searchtags": [
|
||||
"tool",
|
||||
"scaling",
|
||||
"height",
|
||||
"extension",
|
||||
"avatar"
|
||||
],
|
||||
"requirements": [
|
||||
"BTKUILib"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r48/ASTExtension.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/ASTExtension/",
|
||||
"changelog": "- Rebuilt for CVR 2025r181",
|
||||
"embedcolor": "#f61963"
|
||||
}
|
||||
|
|
@ -8,11 +8,11 @@ Can use Delete Mode ~~& The Clapper~~ on loading hexagons to cancel stuck downlo
|
|||
|
||||
---
|
||||
|
||||
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
|
||||
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ChilloutVR.
|
||||
https://docs.chilloutvr.net/official/legal/tos/#6-modding-our-game
|
||||
|
||||
> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive.
|
||||
> This mod is an independent creation not affiliated with, supported by, or approved by ChilloutVR.
|
||||
|
||||
> 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.
|
||||
> To the best of my knowledge, I have adhered to the Modding Guidelines established by ChilloutVR.
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
using ABI_RC.Core.Networking;
|
||||
using ABI_RC.Core.Util;
|
||||
using ABI.CCK.Components;
|
||||
|
||||
namespace NAK.PropsButBetter;
|
||||
|
||||
public static class PropDataExtensions
|
||||
{
|
||||
public static bool IsSpawnedByMe(this CVRSyncHelper.PropData prop)
|
||||
=> prop.SpawnedBy == AuthManager.UserId;
|
||||
|
||||
public static bool IsSpawnedByAdmin(this CVRSyncHelper.PropData prop)
|
||||
=> prop.SpawnedBy is CVRSyncHelper.OWNERID_LOCALSERVER or CVRSyncHelper.OWNERID_SYSTEM;
|
||||
|
||||
public static void CopyFrom(this CVRSyncHelper.PropData prop, CVRSyncHelper.PropData sourceData)
|
||||
{
|
||||
prop.ObjectId = sourceData.ObjectId;
|
||||
prop.InstanceId = sourceData.InstanceId;
|
||||
prop.PositionX = sourceData.PositionX;
|
||||
prop.PositionY = sourceData.PositionY;
|
||||
prop.PositionZ = sourceData.PositionZ;
|
||||
prop.RotationX = sourceData.RotationX;
|
||||
prop.RotationY = sourceData.RotationY;
|
||||
prop.RotationZ = sourceData.RotationZ;
|
||||
prop.ScaleX = sourceData.ScaleX;
|
||||
prop.ScaleY = sourceData.ScaleY;
|
||||
prop.ScaleZ = sourceData.ScaleZ;
|
||||
prop.CustomFloatsAmount = sourceData.CustomFloatsAmount;
|
||||
prop.CustomFloats = sourceData.CustomFloats;
|
||||
prop.SpawnedBy = sourceData.SpawnedBy;
|
||||
prop.syncedBy = sourceData.syncedBy;
|
||||
prop.syncType = sourceData.syncType;
|
||||
prop.ContentMetadata = sourceData.ContentMetadata;
|
||||
prop.CanFireDecommissionEvents = sourceData.CanFireDecommissionEvents;
|
||||
}
|
||||
|
||||
public static void RecycleSafe(this CVRSyncHelper.PropData prop)
|
||||
{
|
||||
if (prop.IsSpawnedByMe())
|
||||
CVRSyncHelper.DeleteMyPropByInstanceIdOverNetwork(prop.InstanceId);
|
||||
prop.Recycle();
|
||||
}
|
||||
}
|
||||
|
|
@ -151,6 +151,7 @@ public static class PropHelper
|
|||
for (int i = propsCount - 1; i >= 0; i--)
|
||||
{
|
||||
CVRSyncHelper.PropData prop = CVRSyncHelper.Props[i];
|
||||
if (prop.IsSpawnedByAdmin()) continue;
|
||||
if (!prop.IsSpawnedByMe()) prop.RecycleSafe();
|
||||
}
|
||||
|
||||
|
|
@ -169,6 +170,7 @@ public static class PropHelper
|
|||
for (int i = propsCount - 1; i >= 0; i--)
|
||||
{
|
||||
CVRSyncHelper.PropData prop = CVRSyncHelper.Props[i];
|
||||
if (prop.IsSpawnedByAdmin()) continue;
|
||||
prop.RecycleSafe();
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 374 KiB After Width: | Height: | Size: 374 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 937 B After Width: | Height: | Size: 937 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
|
@ -39,11 +39,11 @@ If you are looking for a similar open-source asset to generate decals at runtime
|
|||
|
||||
---
|
||||
|
||||
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
|
||||
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ChilloutVR.
|
||||
https://docs.chilloutvr.net/official/legal/tos/#6-modding-our-game
|
||||
|
||||
> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive.
|
||||
> This mod is an independent creation not affiliated with, supported by, or approved by ChilloutVR.
|
||||
|
||||
> 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.
|
||||
> To the best of my knowledge, I have adhered to the Modding Guidelines established by ChilloutVR.
|
||||
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |