New mods: AvatarSyncedLook and PlayersInstanceNotifier

Minor changes
This commit is contained in:
SDraw 2023-12-24 11:53:57 +03:00
parent 72aeffb656
commit e13bb8af23
No known key found for this signature in database
GPG key ID: BB95B4DAB2BB8BB5
28 changed files with 841 additions and 16 deletions

117
ml_pin/Main.cs Normal file
View file

@ -0,0 +1,117 @@
using ABI_RC.Core.AudioEffects;
using ABI_RC.Core.Networking.IO.Social;
using ABI_RC.Core.Player;
using ABI_RC.Core.Savior;
using ABI_RC.Systems.GameEventSystem;
using System;
using System.Collections;
namespace ml_pin
{
public class PlayersInstanceNotifier : MelonLoader.MelonMod
{
SoundManager m_soundManager = null;
public override void OnInitializeMelon()
{
Settings.Init();
ResourcesHandler.ExtractAudioResources();
MelonLoader.MelonCoroutines.Start(WaitForInstances());
}
public override void OnDeinitializeMelon()
{
m_soundManager = null;
}
IEnumerator WaitForInstances()
{
if(InterfaceAudio.Instance == null)
yield return null;
m_soundManager = new SoundManager();
m_soundManager.LoadSounds();
CVRGameEventSystem.Player.OnJoin.AddListener(OnPlayerJoin);
CVRGameEventSystem.Player.OnLeave.AddListener(OnPlayerLeave);
}
void OnPlayerJoin(PlayerDescriptor p_player)
{
try
{
bool l_isFriend = Friends.FriendsWith(p_player.ownerId);
bool l_notify = true;
switch(Settings.NotifyType)
{
case Settings.NotificationType.None:
l_notify = false;
break;
case Settings.NotificationType.Friends:
l_notify = (ShouldNotifyInCurrentInstance() && l_isFriend);
break;
case Settings.NotificationType.All:
l_notify = ShouldNotifyInCurrentInstance();
break;
}
l_notify |= (Settings.FriendsAlways && l_isFriend);
if(l_notify)
{
if(l_isFriend)
m_soundManager?.PlaySound(SoundManager.SoundType.FriendJoin);
else
m_soundManager?.PlaySound(SoundManager.SoundType.PlayerJoin);
}
}
catch(Exception e)
{
MelonLoader.MelonLogger.Warning(e);
}
}
void OnPlayerLeave(PlayerDescriptor p_player)
{
try
{
bool l_isFriend = Friends.FriendsWith(p_player.ownerId);
bool l_notify = true;
switch(Settings.NotifyType)
{
case Settings.NotificationType.None:
l_notify = false;
break;
case Settings.NotificationType.Friends:
l_notify = (ShouldNotifyInCurrentInstance() && l_isFriend);
break;
case Settings.NotificationType.All:
l_notify = ShouldNotifyInCurrentInstance();
break;
}
l_notify |= (Settings.FriendsAlways && l_isFriend);
if(l_notify)
{
if(l_isFriend)
m_soundManager?.PlaySound(SoundManager.SoundType.FriendLeave);
else
m_soundManager?.PlaySound(SoundManager.SoundType.PlayerLeave);
}
}
catch(Exception e)
{
MelonLoader.MelonLogger.Warning(e);
}
}
bool ShouldNotifyInCurrentInstance()
{
bool l_isInPublic = Settings.NotifyInPublic && MetaPort.Instance.CurrentInstancePrivacy.Contains("Public");
bool l_isInFriends = Settings.NotifyInFriends && MetaPort.Instance.CurrentInstancePrivacy.Contains("Friends");
bool l_isInPrivate = Settings.NotifyInPrivate && MetaPort.Instance.CurrentInstancePrivacy.Contains("invite");
return (l_isInPublic || l_isInFriends || l_isInPrivate);
}
}
}

View file

@ -0,0 +1,4 @@
[assembly: MelonLoader.MelonInfo(typeof(ml_pin.PlayersInstanceNotifier), "PlayersInstanceNotifier", "1.0.0", "SDraw", "https://github.com/SDraw/ml_mods_cvr")]
[assembly: MelonLoader.MelonGame(null, "ChilloutVR")]
[assembly: MelonLoader.MelonPlatform(MelonLoader.MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonLoader.MelonPlatformDomain(MelonLoader.MelonPlatformDomainAttribute.CompatibleDomains.MONO)]

View file

@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace ml_pin
{
static class ResourcesHandler
{
const string c_modName = "PlayersInstanceNotifier";
static readonly List<string> ms_audioResources = new List<string>()
{
"Chime.wav",
"DoorClose.wav"
};
public static void ExtractAudioResources()
{
string l_dirPath = MelonLoader.Utils.MelonEnvironment.UserDataDirectory;
if(!Directory.Exists(l_dirPath))
Directory.CreateDirectory(l_dirPath);
l_dirPath = Path.Combine(l_dirPath, c_modName);
if(!Directory.Exists(l_dirPath))
Directory.CreateDirectory(l_dirPath);
string l_filePath = Path.Combine(l_dirPath, "player_join.wav");
if(!File.Exists(l_filePath))
ExtractAudioFile(ms_audioResources[0], l_filePath);
l_filePath = Path.Combine(l_dirPath, "player_leave.wav");
if(!File.Exists(l_filePath))
ExtractAudioFile(ms_audioResources[1], l_filePath);
l_filePath = Path.Combine(l_dirPath, "friend_join.wav");
if(!File.Exists(l_filePath))
ExtractAudioFile(ms_audioResources[0], l_filePath);
l_filePath = Path.Combine(l_dirPath, "friend_leave.wav");
if(!File.Exists(l_filePath))
ExtractAudioFile(ms_audioResources[1], l_filePath);
}
static void ExtractAudioFile(string p_name, string p_path)
{
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
try
{
Stream l_resourceStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
Stream l_fileStream = File.Create(p_path);
l_resourceStream.CopyTo(l_fileStream);
l_fileStream.Flush();
l_fileStream.Close();
l_resourceStream.Close();
}
catch(Exception)
{
MelonLoader.MelonLogger.Warning("Unable to write '" + p_path + "' file, problems can occur.");
}
}
public static string GetEmbeddedResource(string p_name)
{
string l_result = "";
Assembly l_assembly = Assembly.GetExecutingAssembly();
string l_assemblyName = l_assembly.GetName().Name;
try
{
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
StreamReader l_streadReader = new StreamReader(l_libraryStream);
l_result = l_streadReader.ReadToEnd();
}
catch(Exception) { }
return l_result;
}
}
}

166
ml_pin/Settings.cs Normal file
View file

@ -0,0 +1,166 @@
using ABI_RC.Core.InteractionSystem;
using System;
using System.Collections.Generic;
namespace ml_pin
{
static class Settings
{
public enum NotificationType
{
None = 0,
Friends,
All
};
enum ModSetting
{
NotifyType,
Volume,
NotifyInPublic,
NotifyInFriends,
NotifyInPrivate,
FriendsAlways
};
public static NotificationType NotifyType { get; private set; } = NotificationType.All;
public static float Volume { get; private set; } = 1.0f;
public static bool NotifyInPublic { get; private set; } = true;
public static bool NotifyInFriends { get; private set; } = true;
public static bool NotifyInPrivate { get; private set; } = true;
public static bool FriendsAlways { get; private set; } = false;
static MelonLoader.MelonPreferences_Category ms_category = null;
static List<MelonLoader.MelonPreferences_Entry> ms_entries = null;
static public event Action<NotificationType> NotifyTypeChange;
static public event Action<float> VolumeChange;
static public event Action<bool> NotifyInPublicChange;
static public event Action<bool> NotifyInFriendsChange;
static public event Action<bool> NotifyInPrivateChange;
static public event Action<bool> FriendsAlwaysChange;
internal static void Init()
{
ms_category = MelonLoader.MelonPreferences.CreateCategory("PIN", null, true);
ms_entries = new List<MelonLoader.MelonPreferences_Entry>()
{
ms_category.CreateEntry(ModSetting.NotifyType.ToString(), (int)NotifyType),
ms_category.CreateEntry(ModSetting.Volume.ToString(), (int)(Volume * 100f)),
ms_category.CreateEntry(ModSetting.NotifyInPublic.ToString(), NotifyInPublic),
ms_category.CreateEntry(ModSetting.NotifyInFriends.ToString(), NotifyInFriends),
ms_category.CreateEntry(ModSetting.NotifyInPrivate.ToString(), NotifyInPrivate),
ms_category.CreateEntry(ModSetting.FriendsAlways.ToString(), FriendsAlways),
};
NotifyType = (NotificationType)(int)ms_entries[(int)ModSetting.NotifyType].BoxedValue;
Volume = (int)ms_entries[(int)ModSetting.Volume].BoxedValue * 0.01f;
NotifyInPublic = (bool)ms_entries[(int)ModSetting.NotifyInPublic].BoxedValue;
NotifyInFriends = (bool)ms_entries[(int)ModSetting.NotifyInFriends].BoxedValue;
NotifyInPrivate = (bool)ms_entries[(int)ModSetting.NotifyInPrivate].BoxedValue;
FriendsAlways = (bool)ms_entries[(int)ModSetting.FriendsAlways].BoxedValue;
MelonLoader.MelonCoroutines.Start(WaitMainMenuUi());
}
static System.Collections.IEnumerator WaitMainMenuUi()
{
while(ViewManager.Instance == null)
yield return null;
while(ViewManager.Instance.gameMenuView == null)
yield return null;
while(ViewManager.Instance.gameMenuView.Listener == null)
yield return null;
ViewManager.Instance.gameMenuView.Listener.ReadyForBindings += () =>
{
ViewManager.Instance.gameMenuView.View.BindCall("OnToggleUpdate_" + ms_category.Identifier, new Action<string, string>(OnToggleUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("OnSliderUpdate_" + ms_category.Identifier, new Action<string, string>(OnSliderUpdate));
ViewManager.Instance.gameMenuView.View.BindCall("OnDropdownUpdate_" + ms_category.Identifier, new Action<string, string>(OnDropdownUpdate));
};
ViewManager.Instance.gameMenuView.Listener.FinishLoad += (_) =>
{
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mods_extension.js"));
ViewManager.Instance.gameMenuView.View.ExecuteScript(ResourcesHandler.GetEmbeddedResource("mod_menu.js"));
foreach(var l_entry in ms_entries)
ViewManager.Instance.gameMenuView.View.TriggerEvent("updateModSetting", ms_category.Identifier, l_entry.DisplayName, l_entry.GetValueAsString());
};
}
static void OnToggleUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.NotifyInPublic:
{
NotifyInPublic = bool.Parse(p_value);
NotifyInPublicChange?.Invoke(NotifyInPublic);
}
break;
case ModSetting.NotifyInFriends:
{
NotifyInFriends = bool.Parse(p_value);
NotifyInFriendsChange?.Invoke(NotifyInFriends);
}
break;
case ModSetting.NotifyInPrivate:
{
NotifyInPrivate = bool.Parse(p_value);
NotifyInPrivateChange?.Invoke(NotifyInPrivate);
}
break;
case ModSetting.FriendsAlways:
{
FriendsAlways = bool.Parse(p_value);
FriendsAlwaysChange?.Invoke(FriendsAlways);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = bool.Parse(p_value);
}
}
static void OnSliderUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.Volume:
{
Volume = int.Parse(p_value) * 0.01f;
VolumeChange?.Invoke(Volume);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = int.Parse(p_value);
}
}
static void OnDropdownUpdate(string p_name, string p_value)
{
if(Enum.TryParse(p_name, out ModSetting l_setting))
{
switch(l_setting)
{
case ModSetting.NotifyType:
{
NotifyType = (NotificationType)int.Parse(p_value);
NotifyTypeChange?.Invoke(NotifyType);
}
break;
}
ms_entries[(int)l_setting].BoxedValue = int.Parse(p_value);
}
}
}
}

70
ml_pin/SoundManager.cs Normal file
View file

@ -0,0 +1,70 @@
using ABI_RC.Core.AudioEffects;
using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
namespace ml_pin
{
class SoundManager
{
public enum SoundType
{
PlayerJoin = 0,
PlayerLeave,
FriendJoin,
FriendLeave
}
const string c_modName = "PlayersInstanceNotifier";
bool m_loaded = false;
readonly AudioClip[] m_clips = null;
internal SoundManager()
{
m_clips = new AudioClip[4];
for(int i = 0; i < 4; i++)
m_clips[i] = null;
}
public void LoadSounds()
{
if(!m_loaded)
{
MelonLoader.MelonCoroutines.Start(LoadAudioClip(SoundType.PlayerJoin, Path.Combine(MelonLoader.Utils.MelonEnvironment.UserDataDirectory, c_modName, "player_join.wav")));
MelonLoader.MelonCoroutines.Start(LoadAudioClip(SoundType.PlayerLeave, Path.Combine(MelonLoader.Utils.MelonEnvironment.UserDataDirectory, c_modName, "player_leave.wav")));
MelonLoader.MelonCoroutines.Start(LoadAudioClip(SoundType.FriendJoin, Path.Combine(MelonLoader.Utils.MelonEnvironment.UserDataDirectory, c_modName, "friend_join.wav")));
MelonLoader.MelonCoroutines.Start(LoadAudioClip(SoundType.FriendLeave, Path.Combine(MelonLoader.Utils.MelonEnvironment.UserDataDirectory, c_modName, "friend_leave.wav")));
m_loaded = true;
}
}
IEnumerator LoadAudioClip(SoundType p_type, string p_path)
{
using UnityWebRequest l_uwr = UnityWebRequestMultimedia.GetAudioClip("file://" + p_path, AudioType.WAV);
((DownloadHandlerAudioClip)l_uwr.downloadHandler).streamAudio = true;
yield return l_uwr.SendWebRequest();
if(l_uwr.isNetworkError || l_uwr.isHttpError)
{
MelonLoader.MelonLogger.Warning(l_uwr.error);
yield break;
}
AudioClip l_content;
AudioClip l_clip = (l_content = DownloadHandlerAudioClip.GetContent(l_uwr));
yield return l_content;
if(!l_uwr.isDone || (l_clip == null))
yield break;
m_clips[(int)p_type] = l_clip;
}
public void PlaySound(SoundType p_type)
{
if(m_loaded && (m_clips[(int)p_type] != null))
InterfaceAudio.Instance.UserInterfaceAudio.PlayOneShot(m_clips[(int)p_type], Settings.Volume);
}
}
}

12
ml_pin/Utils.cs Normal file
View file

@ -0,0 +1,12 @@
using ABI_RC.Core.UI;
using System.Reflection;
namespace ml_pin
{
static class Utils
{
static readonly FieldInfo ms_view = typeof(CohtmlControlledViewWrapper).GetField("_view", BindingFlags.NonPublic | BindingFlags.Instance);
static public void ExecuteScript(this CohtmlControlledViewWrapper p_instance, string p_script) => ((cohtml.Net.View)ms_view.GetValue(p_instance)).ExecuteScript(p_script);
}
}

75
ml_pin/ml_pin.csproj Normal file
View file

@ -0,0 +1,75 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Platforms>x64</Platforms>
<PackageId>PlayersInstanceNotifier</PackageId>
<Authors>SDraw</Authors>
<Company>None</Company>
<Product>PlayersInstanceNotifier</Product>
</PropertyGroup>
<ItemGroup>
<None Remove="resources\Chime.wav" />
<None Remove="resources\DoorClose.wav" />
<None Remove="resources\mod_menu.js" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="resources\Chime.wav" />
<EmbeddedResource Include="resources\DoorClose.wav" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\js\mods_extension.js" Link="resources\mods_extension.js" />
<EmbeddedResource Include="resources\mod_menu.js" />
</ItemGroup>
<ItemGroup>
<Reference Include="0Harmony">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\0Harmony.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="cohtml.Net">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\cohtml.Net.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Cohtml.Runtime">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\Cohtml.Runtime.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="MelonLoader">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\MelonLoader\net35\MelonLoader.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.AudioModule">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.AudioModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.UnityWebRequestAudioModule">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.UnityWebRequestAudioModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.UnityWebRequestModule">
<HintPath>D:\Games\Steam\steamapps\common\ChilloutVR\ChilloutVR_Data\Managed\UnityEngine.UnityWebRequestModule.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy /y &quot;$(TargetPath)&quot; &quot;D:\Games\Steam\steamapps\common\ChilloutVR\Mods\&quot;" />
</Target>
</Project>

BIN
ml_pin/resources/Chime.wav Normal file

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,64 @@
{
let l_block = document.createElement('div');
l_block.innerHTML = `
<div class ="settings-subcategory">
<div class ="subcategory-name">Players Instance Notifier</div>
<div class ="subcategory-description"></div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Notify of: </div>
<div class ="option-input">
<div id="NotifyType" class ="inp_dropdown no-scroll" data-options="0:None,1:Friends,2:All" data-current="2"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Mixed volume: </div>
<div class ="option-input">
<div id="Volume" class ="inp_slider no-scroll" data-min="0" data-max="100" data-current="100"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Notify in public instances: </div>
<div class ="option-input">
<div id="NotifyInPublic" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Notify in friends instances: </div>
<div class ="option-input">
<div id="NotifyInFriends" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Notify in private instances: </div>
<div class ="option-input">
<div id="NotifyInPrivate" class ="inp_toggle no-scroll" data-current="true"></div>
</div>
</div>
<div class ="row-wrapper">
<div class ="option-caption">Always notify of friends: </div>
<div class ="option-input">
<div id="FriendsAlways" class ="inp_toggle no-scroll" data-current="false"></div>
</div>
</div>
`;
document.getElementById('settings-audio').appendChild(l_block);
// Toggles
for (let l_toggle of l_block.querySelectorAll('.inp_toggle'))
modsExtension.addSetting('PIN', l_toggle.id, modsExtension.createToggle(l_toggle, 'OnToggleUpdate_PIN'));
// Sliders
for (let l_slider of l_block.querySelectorAll('.inp_slider'))
modsExtension.addSetting('PIN', l_slider.id, modsExtension.createSlider(l_slider, 'OnSliderUpdate_PIN'));
// Dropdowns
for (let l_dropdown of l_block.querySelectorAll('.inp_dropdown'))
modsExtension.addSetting('PIN', l_dropdown.id, modsExtension.createDropdown(l_dropdown, 'OnDropdownUpdate_PIN'));
}