Skip to content
Closed
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
73 changes: 73 additions & 0 deletions API/IExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;

namespace Bloodstone.API;
public static class IExtensions
{
public static Dictionary<TValue, TKey> Reverse<TKey, TValue>(

Check warning on line 7 in API/IExtensions.cs

View workflow job for this annotation

GitHub Actions / build

The type 'TValue' cannot be used as type parameter 'TKey' in the generic type or method 'Dictionary<TKey, TValue>'. Nullability of type argument 'TValue' doesn't match 'notnull' constraint.

Check warning on line 7 in API/IExtensions.cs

View workflow job for this annotation

GitHub Actions / build

The type 'TValue' cannot be used as type parameter 'TKey' in the generic type or method 'Dictionary<TKey, TValue>'. Nullability of type argument 'TValue' doesn't match 'notnull' constraint.
this IDictionary<TKey, TValue> source)
{
var reversed = new Dictionary<TValue, TKey>();

Check warning on line 10 in API/IExtensions.cs

View workflow job for this annotation

GitHub Actions / build

The type 'TValue' cannot be used as type parameter 'TKey' in the generic type or method 'Dictionary<TKey, TValue>'. Nullability of type argument 'TValue' doesn't match 'notnull' constraint.

foreach (var kvp in source)
{
reversed[kvp.Value] = kvp.Key;
}

return reversed;
}
public static Dictionary<TValue, TKey> ReverseIl2CppDictionary<TKey, TValue>(

Check warning on line 19 in API/IExtensions.cs

View workflow job for this annotation

GitHub Actions / build

The type 'TValue' cannot be used as type parameter 'TKey' in the generic type or method 'Dictionary<TKey, TValue>'. Nullability of type argument 'TValue' doesn't match 'notnull' constraint.
this Il2CppSystem.Collections.Generic.Dictionary<TKey, TValue> source)
{
var reversed = new Dictionary<TValue, TKey>();

Check warning on line 22 in API/IExtensions.cs

View workflow job for this annotation

GitHub Actions / build

The type 'TValue' cannot be used as type parameter 'TKey' in the generic type or method 'Dictionary<TKey, TValue>'. Nullability of type argument 'TValue' doesn't match 'notnull' constraint.

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<T>(this IEnumerable<T> collection, Action<T> action)
{
foreach (var item in collection)
{
action(item);
}
}
public static bool IsIndexWithinRange<T>(this IList<T> list, int index)
{
return index >= 0 && index < list.Count;
}
public static bool ContainsAll(this string stringChars, List<string> strings)
{
foreach (string str in strings)
{
if (!stringChars.Contains(str, StringComparison.OrdinalIgnoreCase))
{
return false;
}
}

return true;
}
public static bool ContainsAny(this string stringChars, List<string> strings)
{
foreach (string str in strings)
{
if (stringChars.Contains(str, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}

return false;
}
}
63 changes: 57 additions & 6 deletions API/Keybindings.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<ButtonInputAction>())
{
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).
Expand All @@ -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.
Expand Down
154 changes: 151 additions & 3 deletions API/VExtensions.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -9,28 +13,172 @@ namespace Bloodstone.API;
/// </summary>
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;

/// <summary>
/// Send the given system message to the user.
/// </summary>
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);
}

/// <summary>
/// Modify the given component on the given entity. The argument is passed
/// as a reference, so it can be modified in place. The resulting struct
/// is written back to the entity.
/// </summary>
public static void WithComponentData<T>(this Entity entity, ActionRef<T> action)
public static void WithComponentData<T>(this Entity entity, ActionRefHandler<T> action)
where T : struct
{
var component = VWorld.Game.EntityManager.GetComponentData<T>(entity);
action(ref component);
VWorld.Game.EntityManager.SetComponentData<T>(entity, component);
}

public delegate void ActionRef<T>(ref T item);
public delegate void ActionRefHandler<T>(ref T item);
static void With<T>(this Entity entity, ActionRefHandler<T> action) where T : struct
{
T item = entity.Read<T>();
action(ref item);

EntityManager.SetComponentData(entity, item);
}
public static void AddWith<T>(this Entity entity, ActionRefHandler<T> action) where T : struct
{
if (!entity.Has<T>())
{
entity.Add<T>();
}

entity.With(action);
}
public static void HasWith<T>(this Entity entity, ActionRefHandler<T> action) where T : struct
{
if (entity.Has<T>())
{
entity.With(action);
}
}
public unsafe static void Write<T>(this Entity entity, T componentData) where T : struct
{
EntityManager.SetComponentData(entity, componentData);
}
public static T Read<T>(this Entity entity) where T : struct
{
return EntityManager.GetComponentData<T>(entity);
}
public static DynamicBuffer<T> ReadBuffer<T>(this Entity entity) where T : struct
{
return EntityManager.GetBuffer<T>(entity);
}
public static DynamicBuffer<T> AddBuffer<T>(this Entity entity) where T : struct
{
return EntityManager.AddBuffer<T>(entity);
}
public static bool TryGetComponent<T>(this Entity entity, out T componentData) where T : struct
{
componentData = default;

if (entity.Has<T>())
{
componentData = entity.Read<T>();
return true;
}

return false;
}
public static bool TryGetComponent<T>(this Entity entity) where T : struct
{
if (entity.Has<T>())
{
entity.Remove<T>();

return true;
}

return false;
}
public static bool Has<T>(this Entity entity) where T : struct
{
return EntityManager.HasComponent(entity, new(Il2CppType.Of<T>()));
}
public static void TryAdd<T>(this Entity entity) where T : struct
{
if (!entity.Has<T>()) entity.Add<T>();
}
public static void Add<T>(this Entity entity) where T : struct
{
EntityManager.AddComponent(entity, new(Il2CppType.Of<T>()));
}
public static void Remove<T>(this Entity entity) where T : struct
{
EntityManager.RemoveComponent(entity, new(Il2CppType.Of<T>()));
}
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<char> span = entityStr.AsSpan();

if (!span.StartsWith(PREFIX)) return false;
span = span[LENGTH..];

int colon = span.IndexOf(':');
if (colon <= 0) return false;

ReadOnlySpan<char> 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<Disabled>();
}
public static bool IsPlayer(this Entity entity)
{
if (entity.Has<PlayerCharacter>())
{
return true;
}

return false;
}
public static bool IsVBlood(this Entity entity)
{
return entity.Has<VBloodConsumeSource>();
}
public static bool IsGateBoss(this Entity entity)
{
return entity.Has<VBloodUnit>() && !entity.Has<VBloodConsumeSource>();
}
public static bool IsVBloodOrGateBoss(this Entity entity)
{
return entity.Has<VBloodUnit>();
}
}
5 changes: 4 additions & 1 deletion API/VNetwork.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ namespace Bloodstone.API;
/// VNetwork have built-in support for sending blittable structs without any
/// additional effort.
/// </summary>

/*
public interface VNetworkMessage
{
/// <summary>Serialize this value to the given writer.</summary>
Expand Down Expand Up @@ -173,4 +175,5 @@ public static void SendToClientStruct<T>(FromCharacter fromCharacter, T msg)
/// <summary>Send the given blittable struct to the server.</summary>
public static void SendToServerStruct<T>(T msg)
where T : unmanaged => SendToServer(new VBlittableNetworkMessage<T>(msg));
}
}
*/
2 changes: 2 additions & 0 deletions API/VWorld.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ namespace Bloodstone.API;
/// </summary>
public static class VWorld
{
public static EntityManager EntityManager => Game.EntityManager;

private static World? _clientWorld;
private static World? _serverWorld;

Expand Down
Loading
Loading