Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>0.29.0</Version>
<Version>0.30.0</Version>
<RootNamespace>MonkeyLoader.Resonite</RootNamespace>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>

Expand Down
6 changes: 6 additions & 0 deletions MonkeyLoader.GamePacks.Resonite.sln
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
.github\workflows\publish.yml = .github\workflows\publish.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonkeyLoader.Resonite.Core", "MonkeyLoader.Resonite.Core\MonkeyLoader.Resonite.Core.csproj", "{1DD99E73-5628-4400-84B3-D175B03A99C5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -54,6 +56,10 @@ Global
{E5612DD7-954D-43A8-A0F0-F51E3144A994}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5612DD7-954D-43A8-A0F0-F51E3144A994}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E5612DD7-954D-43A8-A0F0-F51E3144A994}.Release|Any CPU.Build.0 = Release|Any CPU
{1DD99E73-5628-4400-84B3-D175B03A99C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1DD99E73-5628-4400-84B3-D175B03A99C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1DD99E73-5628-4400-84B3-D175B03A99C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1DD99E73-5628-4400-84B3-D175B03A99C5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using FrooxEngine;
using System.Linq;
using System.Runtime.CompilerServices;

namespace MonkeyLoader.Resonite
{
/// <summary>
/// Contains an extension method to add <see cref="DestroyOnUserLeave"/> components targeting
/// the <see cref="World.LocalUser">local user</see> to mod additions that rely on the local user being there to work.
/// </summary>
[TypeForwardedFrom("MonkeyLoader.Resonite.Integration")]
public static class DestroyOnUserLeaveExtensions
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

Expand All @@ -15,6 +16,7 @@ namespace MonkeyLoader.Resonite
/// Contains extension methods related to <see cref="DynamicVariableSpace"/>s
/// and their <see cref="IDynamicVariable">variables</see>.
/// </summary>
[TypeForwardedFrom("MonkeyLoader.Resonite.Integration")]
public static class DynamicVariableExtensions
{
//public static DynamicReference<T> CreateReferenceVariable<T>(this SyncRef<T> syncRef, string name, bool overrideOnLink = false, bool persistent = true)
Expand Down Expand Up @@ -121,7 +123,7 @@ public static IEnumerable<DynamicVariableSpace> GetAvailableSpaces(this Slot slo
/// <returns>All full Dynamic Variable identities that apply to this <see cref="Slot"/> that the given <see cref="Type"/> can be assigned to.</returns>
/// <inheritdoc cref="GetAvailableVariableIdentities(Slot)"/>
public static IEnumerable<DynamicVariableIdentity> GetAvailableVariableIdentities(this Slot slot, Type type)
=> slot.GetAvailableSpaces().SelectMany(space => space.GetVariableIdentities(type)).ToArray();
=> [.. slot.GetAvailableSpaces().SelectMany(space => space.GetVariableIdentities(type))];

/// <summary>
/// Gets all <see cref="DynamicVariableIdentity">full Dynamic Variable
Expand All @@ -133,7 +135,7 @@ public static IEnumerable<DynamicVariableIdentity> GetAvailableVariableIdentitie
/// <returns>All full Dynamic Variable identities that apply to this <see cref="Slot"/> that have the given <paramref name="name"/>.</returns>
/// <inheritdoc cref="GetAvailableVariableIdentities(Slot)"/>
public static IEnumerable<DynamicVariableIdentity> GetAvailableVariableIdentities(this Slot slot, string name)
=> slot.GetAvailableSpaces().SelectMany(space => space.GetVariableIdentities(name)).ToArray();
=> [.. slot.GetAvailableSpaces().SelectMany(space => space.GetVariableIdentities(name))];

/// <summary>
/// Gets all <see cref="DynamicVariableIdentity">full Dynamic Variable
Expand All @@ -144,7 +146,7 @@ public static IEnumerable<DynamicVariableIdentity> GetAvailableVariableIdentitie
/// <returns>All full Dynamic Variable identities that apply to this <see cref="Slot"/> that <typeparamref name="T"/> can be assigned to.</returns>
/// <inheritdoc cref="GetAvailableVariableIdentities(Slot)"/>
public static IEnumerable<DynamicVariableIdentity> GetAvailableVariableIdentities<T>(this Slot slot)
=> slot.GetAvailableSpaces().SelectMany(GetVariableIdentities<T>).ToArray();
=> [.. slot.GetAvailableSpaces().SelectMany(GetVariableIdentities<T>)];

/// <summary>
/// Gets all <see cref="DynamicVariableIdentity">full Dynamic Variable
Expand All @@ -161,7 +163,7 @@ public static IEnumerable<DynamicVariableIdentity> GetAvailableVariableIdentitie
/// <param name="slot">The <see cref="Slot"/> to find all applicable full Dynamic Variable identities for.</param>
/// <returns>All full Dynamic Variable identities that apply to this <see cref="Slot"/>.</returns>
public static IEnumerable<DynamicVariableIdentity> GetAvailableVariableIdentities(this Slot slot)
=> slot.GetAvailableSpaces().SelectMany(GetVariableIdentities).ToArray();
=> [.. slot.GetAvailableSpaces().SelectMany(GetVariableIdentities)];

/// <summary>
/// Gets the <see cref="DynamicVariableHandler{T}"/> of this
Expand Down Expand Up @@ -337,7 +339,7 @@ public static IEnumerable<DynamicVariableIdentity> GetVariableIdentities<T>(this
/// <param name="type">The type that must be assignable to the variables.</param>
/// <returns>All full Dynamic Variable identities associated with this <paramref name="space"/> that the given <see cref="Type"/> can be assigned to.</returns>
public static IEnumerable<DynamicVariableIdentity> GetVariableIdentities(this DynamicVariableSpace space, Type type)
=> space.GetVariableIdentities().Where(id => id.Type.IsAssignableFrom(type)).ToArray();
=> [.. space.GetVariableIdentities().Where(id => id.Type.IsAssignableFrom(type))];

/// <summary>
/// Gets all <see cref="DynamicVariableIdentity">full Dynamic Variable identities</see>
Expand All @@ -348,7 +350,7 @@ public static IEnumerable<DynamicVariableIdentity> GetVariableIdentities(this Dy
/// <param name="name">The name that the variable identities must have.</param>
/// <returns>All full Dynamic Variable identities associated with this <paramref name="space"/> that have the given <paramref name="name"/>.</returns>
public static IEnumerable<DynamicVariableIdentity> GetVariableIdentities(this DynamicVariableSpace space, string name)
=> space.GetVariableIdentities().Where(id => id.Name == name).ToArray();
=> [.. space.GetVariableIdentities().Where(id => id.Name == name)];

//public static DynamicField<T>? CreateVariable<T>(this IField<T> field, string name, bool overrideOnLink = false, bool persistent = true)
//{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
using FrooxEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;

namespace MonkeyLoader.Resonite
{
/// <summary>
/// Fully describes the identity of a Dynamic Variable based on itsn <see cref="Type">Type</see>,
/// Fully describes the identity of a Dynamic Variable based on its <see cref="Type">Type</see>,
/// <see cref="Name">Name</see>, and the <see cref="Space">Space</see> it's a part of.
/// </summary>
[TypeForwardedFrom("MonkeyLoader.Resonite.Integration")]
public readonly struct DynamicVariableIdentity : IEquatable<DynamicVariableIdentity>
{
/// <summary>
Expand Down Expand Up @@ -83,6 +86,22 @@ public override readonly int GetHashCode()

/// <inheritdoc/>
public override readonly string ToString()
=> $"Dynamic Variable {Name} of Type {Type.CompactDescription()} on {Space.GetReferenceLabel()}";
=> $"Dynamic Variable {Name} of Type {CompactDescription(Type)} on {Space.GetReferenceLabel()}";

/// <summary>
/// Gets a compact, human-readable description of a type.
/// </summary>
/// <param name="type">The type to format.</param>
/// <returns>The human-readable description of the type.</returns>
private static string CompactDescription(Type type)
{
if (type is null)
return "null";

if (type.IsGenericType)
return $"{type.Name}<{string.Join(", ", type.GetGenericArguments().Select(CompactDescription))}>";

return type.Name;
}
}
}
36 changes: 36 additions & 0 deletions MonkeyLoader.Resonite.Core/FieldExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using FrooxEngine;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;

namespace MonkeyLoader.Resonite
{
/// <summary>
/// Contains extension methods for <see cref="IField{T}">fields</see>
/// and other <see cref="IWorldElement">world elements</see>
/// </summary>
[TypeForwardedFrom("MonkeyLoader.Resonite.Integration")]
public static class FieldExtensions
{
/// <summary>
/// Creates a label describing the <paramref name="target"/> reference as a <see cref="RefEditor"/> would.
/// </summary>
/// <param name="target">The reference to label.</param>
/// <returns>A label for the <paramref name="target"/> reference if it is not <c>null</c>; otherwise, <c>&lt;i&gt;null&lt;/i&gt;</c>.</returns>
public static string GetReferenceLabel(this IWorldElement? target)
{
if (target is null)
return "<i>null</i>";

if (target is Slot targetSlot)
return $"{targetSlot.Name} ({target.ReferenceID})";

var component = target.FindNearestParent<Component>();
var slot = component?.Slot ?? target.FindNearestParent<Slot>();

var arg = (component is not null && component != target) ? ("on " + component.Name + " on " + slot.Name) : ((slot is null) ? "" : ("on " + slot.Name));
return (target is not SyncElement syncElement) ? $"{target.Name ?? target.GetType().Name} {arg} ({target.ReferenceID})" : $"{syncElement.NameWithPath} {arg} ({target.ReferenceID})";
}
}
}
102 changes: 102 additions & 0 deletions MonkeyLoader.Resonite.Core/KeyedComponentHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using FrooxEngine;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace MonkeyLoader.Resonite
{
/// <summary>
/// Contains extension methods to deal with <see cref="World.Keys">keyed</see>
/// <see cref="Component"/>s in <see cref="World"/>s.
/// </summary>
[TypeForwardedFrom("MonkeyLoader.Resonite.Integration")]
public static class KeyedComponentHelper
{
/// <summary>
/// <see cref="World.KeyOwner">Gets</see> or <see cref="World.RequestKey">creates</see>
/// a keyed <typeparamref name="T"/> component, based on the situation and parameters.
/// </summary>
/// <returns>The existing or newly created <typeparamref name="T"/> component.</returns>
/// <exception cref="InvalidOperationException">
/// When an incompatible component is associated with the given <paramref name="key"/>
/// and <paramref name="replaceExisting"/> was <see langword="false"/>.
/// </exception>
/// <inheritdoc cref="TryGetKeyedComponentOrCreate{T}"/>
public static T GetKeyedComponentOrCreate<T>(this Slot slot, string key, Action<T>? onCreated = null,
int version = 0, bool replaceExisting = false, bool updateExisting = false)
where T : Component, new()
{
if (!slot.TryGetKeyedComponentOrCreate(key, out var component, onCreated, version, replaceExisting, updateExisting))
throw new InvalidOperationException("An incompatible component is stored under the key " + key + $"\nExisting: {component}");

return component;
}

/// <summary>
/// Tries to <see cref="World.KeyOwner">get</see> or <see cref="World.RequestKey">create</see>
/// a keyed <typeparamref name="T"/> component, based on the situation and parameters.
/// </summary>
/// <remarks><para>
/// This behavior was adapted from <see cref="ContainerWorker{C}.GetComponentOrAttach{T}(Predicate{T})"/>.
/// </para><para>
/// Consider that these component-associations will be saved with the <see cref="World"/>.
/// Make sure that this is really necessary for what you're attempting to do.
/// </para></remarks>
/// <typeparam name="T">The type of the component to get or create.</typeparam>
/// <param name="slot">The slot to attach the <typeparamref name="T"/> component to when necessary.</param>
/// <param name="key">The unique key (to) associate(d) with the <typeparamref name="T"/> component.</param>
/// <param name="component">
/// The <typeparamref name="T"/> component associated with the given <paramref name="key"/> if this call returns <see langword="true"/>;
/// otherwise, <see langword="null"/> when an incompatible component is associated with it
/// and <paramref name="replaceExisting"/> was <see langword="false"/>.
/// </param>
/// <param name="onCreated">
/// The optional configuration action to call when the <typeparamref name="T"/> component had to be created,
/// or when its saved version is lower than the given <paramref name="version"/> number.
/// </param>
/// <param name="version">The version number to associate with the <typeparamref name="T"/> component after this call.</param>
/// <param name="replaceExisting">Whether to replace a different component associated with the given <paramref name="key"/> with this one.</param>
/// <param name="updateExisting">
/// Whether to call <paramref name="onCreated"/> even if the <typeparamref name="T"/> component already exists.<br/>
/// If the given <paramref name="version"/> number is greater than zero and the saved version is lower, this will always be done.
/// </param>
/// <returns><see langword="true"/> if the keyed <typeparamref name="T"/> component was found or created; otherwise, <see langword="false"/>.</returns>
public static bool TryGetKeyedComponentOrCreate<T>(this Slot slot, string key, [NotNullWhen(true)] out T? component,
Action<T>? onCreated = null, int version = 0, bool replaceExisting = false, bool updateExisting = false)
where T : Component, new()
{
var keyedComponent = slot.World.KeyOwner(key);

if (keyedComponent is not null)
{
if (keyedComponent is T typedComponent)
{
if (version > 0 && slot.World.KeyVersion(key) < version)
{
updateExisting = true;
slot.World.RequestKey(typedComponent, key, version, false);
}

if (updateExisting)
onCreated?.Invoke(typedComponent);

component = typedComponent;
return true;
}

if (!replaceExisting)
{
component = null;
return false;
}
}

component = slot.AttachComponent<T>();

slot.World.RequestKey(component, key, version, false);
onCreated?.Invoke(component);

return true;
}
}
}
39 changes: 39 additions & 0 deletions MonkeyLoader.Resonite.Core/MonkeyLoader.Resonite.Core.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<Project Sdk="Remora.Resonite.Sdk">
<PropertyGroup>
<ResoniteProjectType>library</ResoniteProjectType>
<ResoniteInstallOnBuild>false</ResoniteInstallOnBuild>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>

<PropertyGroup>
<PackageId>MonkeyLoader.GamePacks.Resonite.Core</PackageId>
<Title>Resonite Game Pack Core</Title>
<Authors>Banane9</Authors>
<Description>This package provides the non-ML specific helpers for modding the game Resonite, which uses FrooxEngine.
It contains many useful features for the Developers of mods and their Users alike.

This package is only intended for use with non-ML mods.
For mods targetting ML, this library is part of the Resonite Game Pack.</Description>
<PackageTags>mod;mods;modding;mod;loader;monkeyloader;resonite;tools;tool;toolkit;helper;helpers;standalone</PackageTags>
</PropertyGroup>

<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="" />
<None Include="../Icon.png" Pack="true" PackagePath="" />
<None Include="Locale/**/*.json" Pack="true" PackagePath="content/Locale" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="MonkeyLoader.Resonite.Integration" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Lib.Harmony.Thin" Version="2.4.2" />
</ItemGroup>

<ItemGroup>
<ResoniteReference Include="FrooxEngine" UsePublicized="true" />
<ResoniteReference Include="Elements.Core" />
<ResoniteReference Include="Renderite.Shared" />
</ItemGroup>
</Project>
8 changes: 8 additions & 0 deletions MonkeyLoader.Resonite.Core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Resonite Game Pack Toolkit
==========================

This package provides the non-ML specific helpers for modding the game [Resonite](https://resonite.com/), which uses FrooxEngine.
It contains many useful features for the Developers of mods and their Users alike.

This package is only intended for use with non-ML mods.
For mods targetting ML, this library is part of the Resonite Game Pack.
Loading
Loading