diff --git a/API/IExtensions.cs b/API/IExtensions.cs new file mode 100644 index 0000000..76108de --- /dev/null +++ b/API/IExtensions.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; + +namespace Bloodstone.API; +public static class IExtensions +{ + public static Dictionary Reverse( + this IDictionary source) + { + var reversed = new Dictionary(); + + foreach (var kvp in source) + { + reversed[kvp.Value] = kvp.Key; + } + + return reversed; + } + public static Dictionary ReverseIl2CppDictionary( + this Il2CppSystem.Collections.Generic.Dictionary source) + { + var reversed = new Dictionary(); + + if (source == null) return reversed; + + foreach (var kvp in source) + { + if (reversed.ContainsKey(kvp.Value)) + { + continue; + } + + reversed[kvp.Value] = kvp.Key; + } + + return reversed; + } + public static void ForEach(this IEnumerable collection, Action action) + { + foreach (var item in collection) + { + action(item); + } + } + public static bool IsIndexWithinRange(this IList list, int index) + { + return index >= 0 && index < list.Count; + } + public static bool ContainsAll(this string stringChars, List strings) + { + foreach (string str in strings) + { + if (!stringChars.Contains(str, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + + return true; + } + public static bool ContainsAny(this string stringChars, List strings) + { + foreach (string str in strings) + { + if (stringChars.Contains(str, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } +} diff --git a/API/Keybindings.cs b/API/Keybindings.cs index 6c47d96..71af40a 100644 --- a/API/Keybindings.cs +++ b/API/Keybindings.cs @@ -1,11 +1,10 @@ +using BepInEx; +using ProjectM; using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Text.Json; -using BepInEx; -using ProjectM; -using Standart.Hash.xxHash; using UnityEngine; namespace Bloodstone.API; @@ -185,14 +184,66 @@ public class Keybinding // Unique XXHash-based quarter-of-an-assetguid for identification. internal int AssetGuid { get; private set; } + const ulong HASH_LONG = 14695981039346656037UL; + const uint HASH_INT = 2166136261U; public Keybinding(KeybindingDescription description) { Description = description; - ComputeInputFlag(); - ComputeAssetGuid(); + ComputeInputFlag(description.Id); + ComputeAssetGuid(description.Id); } + static ButtonInputAction ComputeInputFlag(string descriptionId) + { + byte[] bytes = Encoding.UTF8.GetBytes(descriptionId); + ulong num = Hash64(bytes); + bool flag = false; + do + { + foreach (ButtonInputAction buttonInputAction in Enum.GetValues()) + { + if (num == (ulong)buttonInputAction) + { + flag = true; + num--; + } + } + } while (flag); + + return (ButtonInputAction)num; + } + static int ComputeAssetGuid(string descriptionId) + { + byte[] bytes = Encoding.UTF8.GetBytes(descriptionId); + return (int)Hash32(bytes); + } + static ulong Hash64(byte[] data) + { + ulong hash = HASH_LONG; + + foreach (var b in data) + { + hash ^= b; + hash *= 1099511628211UL; + } + + return hash; + } + static uint Hash32(byte[] data) + { + uint hash = HASH_INT; + + foreach (var b in data) + { + hash ^= b; + hash *= 16777619U; + } + + return hash; + } + + /* // Stubborn V Rising internals expect us to have a unique InputFlag // for every input. We deterministically generate a random one here, // and ensure it is not already in use (by the game). @@ -216,13 +267,13 @@ private void ComputeInputFlag() InputFlag = (ButtonInputAction)hash; } - // Ditto, but for asset GUIDs. private void ComputeAssetGuid() { var idBytes = Encoding.UTF8.GetBytes(Description.Id); AssetGuid = (int)xxHash32.ComputeHash(idBytes, idBytes.Length); } + */ } // Internal class used for data persistence. diff --git a/API/VExtensions.cs b/API/VExtensions.cs index ffdc34b..eb517c4 100644 --- a/API/VExtensions.cs +++ b/API/VExtensions.cs @@ -1,5 +1,9 @@ +using Il2CppInterop.Runtime; using ProjectM; using ProjectM.Network; +using Stunlock.Core; +using System; +using Unity.Collections; using Unity.Entities; namespace Bloodstone.API; @@ -9,6 +13,12 @@ namespace Bloodstone.API; /// public static class VExtensions { + static EntityManager EntityManager => VWorld.EntityManager; + + // for checking entity indexes by string to verify within entityManager capacity + const string PREFIX = "Entity("; + const int LENGTH = 7; + /// /// Send the given system message to the user. /// @@ -16,7 +26,8 @@ public static void SendSystemMessage(this User user, string message) { if (!VWorld.IsServer) throw new System.Exception("SendSystemMessage can only be called on the server."); - ServerChatUtils.SendSystemMessageToClient(VWorld.Server.EntityManager, user, message); + FixedString512Bytes fixedMessage = new(message); + ServerChatUtils.SendSystemMessageToClient(VWorld.Server.EntityManager, user, ref fixedMessage); } /// @@ -24,7 +35,7 @@ public static void SendSystemMessage(this User user, string message) /// as a reference, so it can be modified in place. The resulting struct /// is written back to the entity. /// - public static void WithComponentData(this Entity entity, ActionRef action) + public static void WithComponentData(this Entity entity, ActionRefHandler action) where T : struct { var component = VWorld.Game.EntityManager.GetComponentData(entity); @@ -32,5 +43,142 @@ public static void WithComponentData(this Entity entity, ActionRef action) VWorld.Game.EntityManager.SetComponentData(entity, component); } - public delegate void ActionRef(ref T item); + public delegate void ActionRefHandler(ref T item); + static void With(this Entity entity, ActionRefHandler action) where T : struct + { + T item = entity.Read(); + action(ref item); + + EntityManager.SetComponentData(entity, item); + } + public static void AddWith(this Entity entity, ActionRefHandler action) where T : struct + { + if (!entity.Has()) + { + entity.Add(); + } + + entity.With(action); + } + public static void HasWith(this Entity entity, ActionRefHandler action) where T : struct + { + if (entity.Has()) + { + entity.With(action); + } + } + public unsafe static void Write(this Entity entity, T componentData) where T : struct + { + EntityManager.SetComponentData(entity, componentData); + } + public static T Read(this Entity entity) where T : struct + { + return EntityManager.GetComponentData(entity); + } + public static DynamicBuffer ReadBuffer(this Entity entity) where T : struct + { + return EntityManager.GetBuffer(entity); + } + public static DynamicBuffer AddBuffer(this Entity entity) where T : struct + { + return EntityManager.AddBuffer(entity); + } + public static bool TryGetComponent(this Entity entity, out T componentData) where T : struct + { + componentData = default; + + if (entity.Has()) + { + componentData = entity.Read(); + return true; + } + + return false; + } + public static bool TryGetComponent(this Entity entity) where T : struct + { + if (entity.Has()) + { + entity.Remove(); + + return true; + } + + return false; + } + public static bool Has(this Entity entity) where T : struct + { + return EntityManager.HasComponent(entity, new(Il2CppType.Of())); + } + public static void TryAdd(this Entity entity) where T : struct + { + if (!entity.Has()) entity.Add(); + } + public static void Add(this Entity entity) where T : struct + { + EntityManager.AddComponent(entity, new(Il2CppType.Of())); + } + public static void Remove(this Entity entity) where T : struct + { + EntityManager.RemoveComponent(entity, new(Il2CppType.Of())); + } + public static bool Exists(this Entity entity) + { + return entity.HasValue() && entity.IndexWithinCapacity() && EntityManager.Exists(entity); + } + public static bool HasValue(this Entity entity) + { + return entity != Entity.Null; + } + public static bool IndexWithinCapacity(this Entity entity) + { + string entityStr = entity.ToString(); + ReadOnlySpan span = entityStr.AsSpan(); + + if (!span.StartsWith(PREFIX)) return false; + span = span[LENGTH..]; + + int colon = span.IndexOf(':'); + if (colon <= 0) return false; + + ReadOnlySpan tail = span[(colon + 1)..]; + + int closeRel = tail.IndexOf(')'); + if (closeRel <= 0) return false; + + // Parse numbers + if (!int.TryParse(span[..colon], out int index)) return false; + if (!int.TryParse(tail[..closeRel], out _)) return false; + + // Single unsigned capacity check + int capacity = EntityManager.EntityCapacity; + bool isValid = (uint)index < (uint)capacity; + + return isValid; + } + public static bool IsDisabled(this Entity entity) + { + return entity.Has(); + } + public static bool IsPlayer(this Entity entity) + { + if (entity.Has()) + { + return true; + } + + return false; + } + public static bool IsVBlood(this Entity entity) + { + return entity.Has(); + } + public static bool IsGateBoss(this Entity entity) + { + return entity.Has() && !entity.Has(); + } + public static bool IsVBloodOrGateBoss(this Entity entity) + { + return entity.Has(); + } } \ No newline at end of file diff --git a/API/VNetwork.cs b/API/VNetwork.cs index 0d725e5..8b51a50 100644 --- a/API/VNetwork.cs +++ b/API/VNetwork.cs @@ -13,6 +13,8 @@ namespace Bloodstone.API; /// VNetwork have built-in support for sending blittable structs without any /// additional effort. /// + +/* public interface VNetworkMessage { /// Serialize this value to the given writer. @@ -173,4 +175,5 @@ public static void SendToClientStruct(FromCharacter fromCharacter, T msg) /// Send the given blittable struct to the server. public static void SendToServerStruct(T msg) where T : unmanaged => SendToServer(new VBlittableNetworkMessage(msg)); -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/API/VWorld.cs b/API/VWorld.cs index 49f6a42..44bb85a 100644 --- a/API/VWorld.cs +++ b/API/VWorld.cs @@ -8,6 +8,8 @@ namespace Bloodstone.API; /// public static class VWorld { + public static EntityManager EntityManager => Game.EntityManager; + private static World? _clientWorld; private static World? _serverWorld; diff --git a/Bloodstone.csproj b/Bloodstone.csproj index 21769fd..900630f 100644 --- a/Bloodstone.csproj +++ b/Bloodstone.csproj @@ -30,18 +30,17 @@ + + + + + + + + - - - + - - - all - - - all - - + diff --git a/Bloodstone.sln b/Bloodstone.sln new file mode 100644 index 0000000..1ab2243 --- /dev/null +++ b/Bloodstone.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35913.81 d17.13 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bloodstone", "Bloodstone.csproj", "{45F88F5D-BFFB-47D5-A59A-C33FF8D9999E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {45F88F5D-BFFB-47D5-A59A-C33FF8D9999E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45F88F5D-BFFB-47D5-A59A-C33FF8D9999E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45F88F5D-BFFB-47D5-A59A-C33FF8D9999E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45F88F5D-BFFB-47D5-A59A-C33FF8D9999E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3C8B638A-D9EC-4AAB-AC79-EF41A8EA5447} + EndGlobalSection +EndGlobal diff --git a/BloodstonePlugin.cs b/BloodstonePlugin.cs index f4c62f5..3b4a4c2 100644 --- a/BloodstonePlugin.cs +++ b/BloodstonePlugin.cs @@ -45,7 +45,7 @@ public override void Load() Hooks.OnInitialize.Initialize(); Hooks.GameFrame.Initialize(); - Network.SerializationHooks.Initialize(); + // Network.SerializationHooks.Initialize(); Logger.LogInfo($"Bloodstone v{MyPluginInfo.PLUGIN_VERSION} loaded."); @@ -72,7 +72,7 @@ public override bool Unload() Hooks.OnInitialize.Uninitialize(); Hooks.GameFrame.Uninitialize(); - Network.SerializationHooks.Uninitialize(); + // Network.SerializationHooks.Uninitialize(); return true; } diff --git a/Hooks/Keybindings.cs b/Hooks/Keybindings.cs index 649ad04..7dd1fd3 100644 --- a/Hooks/Keybindings.cs +++ b/Hooks/Keybindings.cs @@ -43,7 +43,7 @@ public static void Initialize() { if (!VWorld.IsClient) return; - BloodstonePlugin.Logger.LogWarning("Client Keybinding support has been disabled for 1.0 release change compatability. It may be rewritten in the future as needed."); + BloodstonePlugin.Logger.LogWarning("Client Keybinding support has been disabled for 1.1 release change compatability and will be reimplemented soon!"); _harmony = Harmony.CreateAndPatchAll(typeof(Keybindings)); } diff --git a/Network/BlittableNetworkMessage.cs b/Network/BlittableNetworkMessage.cs index 9dac11a..0232d96 100644 --- a/Network/BlittableNetworkMessage.cs +++ b/Network/BlittableNetworkMessage.cs @@ -7,6 +7,8 @@ namespace Bloodstone.Network; // Helper class that serializes a blittable type. + +/* internal class VBlittableNetworkMessage : VNetworkMessage where T : unmanaged { @@ -55,4 +57,5 @@ public void Serialize(ref NetBufferOut writer) writer.Write(bytes[i]); } } -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/Network/CustomNetworkEvent.cs b/Network/CustomNetworkEvent.cs index 3d8abce..ab10d19 100644 --- a/Network/CustomNetworkEvent.cs +++ b/Network/CustomNetworkEvent.cs @@ -11,6 +11,8 @@ namespace Bloodstone.Network; /// VRising into sending it over the network. If entities with this /// component are ever saved to disk, it will brick the save. Take caution! /// + +/* internal class CustomNetworkEvent : UnityEngine.Object { private static bool _isInitialized = false; @@ -73,4 +75,5 @@ private static void Inject() _componentType = new(il2cppty); _isInitialized = true; } -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/Network/EventDispatcher.cs b/Network/EventDispatcher.cs index f6a93ca..a8475da 100644 --- a/Network/EventDispatcher.cs +++ b/Network/EventDispatcher.cs @@ -9,6 +9,8 @@ namespace Bloodstone.Network; // Helper class that creates entities to dispatch // network events to VRising's network system, so that // our serialization hooks can later handle them. + +/* internal static class EventDispatcher { internal static void SendToClient(int userIndex, VNetworkMessage msg) @@ -76,4 +78,5 @@ internal static void SendToServer(VNetworkMessage msg) Message = msg }); } -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/Network/SerializationHooks.cs b/Network/SerializationHooks.cs index 2391603..33be481 100644 --- a/Network/SerializationHooks.cs +++ b/Network/SerializationHooks.cs @@ -14,6 +14,8 @@ namespace Bloodstone.Network; /// Contains the serialization hooks for custom packets. + +/* internal static class SerializationHooks { // chosen by fair dice roll @@ -180,4 +182,5 @@ private static void OnUpdatePrefix(SerializeAndSendServerEventsSystem __instance } } } -} \ No newline at end of file +} +*/ \ No newline at end of file