mirror of
https://github.com/Jacobwasbeast/LegacyWeaveLoader.git
synced 2026-07-02 09:04:22 +00:00
feat(items): add managed custom item callbacks and native pickaxe support
Introduce a managed custom item API with mine-block callbacks and cancellation semantics, plus native runtime support for registering pickaxe items. Key changes: - add WeaveLoader.API Item base/PickaxeItem and dispatcher plumbing - register managed item instances in ItemRegistry - add native export for pickaxe registration and wire through GameObjectFactory - resolve/hook item mineBlock paths (ItemInstance/Item/DiggerItem) and dispatch to managed host - expose managed OnItemMineBlock entry in WeaveLoader.Core and DotNetHost - add Ruby Pickaxe example item + placeholder texture - keep logger usable even before managed handler setup via native fallback
This commit is contained in:
128
WeaveLoader.API/Item/CustomItem.cs
Normal file
128
WeaveLoader.API/Item/CustomItem.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace WeaveLoader.API.Item;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for managed custom items.
|
||||
/// Mods can inherit and override callbacks for item behavior.
|
||||
/// </summary>
|
||||
public abstract class Item
|
||||
{
|
||||
/// <summary>The namespaced ID used during registration.</summary>
|
||||
public Identifier? Id { get; internal set; }
|
||||
|
||||
/// <summary>The numeric runtime ID allocated by the game.</summary>
|
||||
public int NumericId { get; internal set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Called when this item is used to mine a block.
|
||||
/// Return <see cref="MineBlockResult.ContinueVanilla"/> to run vanilla logic (equivalent to calling super),
|
||||
/// or <see cref="MineBlockResult.CancelVanilla"/> to skip vanilla handling.
|
||||
/// </summary>
|
||||
public virtual MineBlockResult OnMineBlock(MineBlockContext context) => MineBlockResult.ContinueVanilla;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of managed mine-block callback.
|
||||
/// </summary>
|
||||
public enum MineBlockResult
|
||||
{
|
||||
ContinueVanilla = 0,
|
||||
CancelVanilla = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tool tier used by native tool constructors.
|
||||
/// </summary>
|
||||
public enum ToolTier
|
||||
{
|
||||
Wood = 0,
|
||||
Stone = 1,
|
||||
Iron = 2,
|
||||
Diamond = 3,
|
||||
Gold = 4
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Managed pickaxe base class.
|
||||
/// Override callbacks to customize behavior.
|
||||
/// </summary>
|
||||
public class PickaxeItem : Item
|
||||
{
|
||||
public ToolTier Tier { get; init; } = ToolTier.Diamond;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runtime context for item mine-block callback.
|
||||
/// </summary>
|
||||
public readonly struct MineBlockContext
|
||||
{
|
||||
public int ItemId { get; }
|
||||
public int TileId { get; }
|
||||
public int X { get; }
|
||||
public int Y { get; }
|
||||
public int Z { get; }
|
||||
|
||||
internal MineBlockContext(int itemId, int tileId, int x, int y, int z)
|
||||
{
|
||||
ItemId = itemId;
|
||||
TileId = tileId;
|
||||
X = x;
|
||||
Y = y;
|
||||
Z = z;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct MineBlockNativeArgs
|
||||
{
|
||||
public int ItemId;
|
||||
public int TileId;
|
||||
public int X;
|
||||
public int Y;
|
||||
public int Z;
|
||||
}
|
||||
|
||||
internal static class ManagedItemDispatcher
|
||||
{
|
||||
private static readonly object s_lock = new();
|
||||
private static readonly Dictionary<int, Item> s_items = new();
|
||||
|
||||
internal static void RegisterItem(Identifier id, int numericId, Item item)
|
||||
{
|
||||
item.Id = id;
|
||||
item.NumericId = numericId;
|
||||
|
||||
lock (s_lock)
|
||||
{
|
||||
s_items[numericId] = item;
|
||||
}
|
||||
}
|
||||
|
||||
internal static int HandleMineBlock(IntPtr args, int sizeBytes)
|
||||
{
|
||||
if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf<MineBlockNativeArgs>())
|
||||
return 0;
|
||||
|
||||
MineBlockNativeArgs nativeArgs = Marshal.PtrToStructure<MineBlockNativeArgs>(args);
|
||||
|
||||
Item? item;
|
||||
lock (s_lock)
|
||||
{
|
||||
s_items.TryGetValue(nativeArgs.ItemId, out item);
|
||||
}
|
||||
|
||||
if (item == null)
|
||||
return 0;
|
||||
|
||||
var result = item.OnMineBlock(new MineBlockContext(
|
||||
nativeArgs.ItemId,
|
||||
nativeArgs.TileId,
|
||||
nativeArgs.X,
|
||||
nativeArgs.Y,
|
||||
nativeArgs.Z));
|
||||
|
||||
// 0 = no managed item, 1 = continue vanilla, 2 = cancel vanilla.
|
||||
return result == MineBlockResult.CancelVanilla ? 2 : 1;
|
||||
}
|
||||
}
|
||||
@@ -32,12 +32,42 @@ public static class ItemRegistry
|
||||
/// <returns>A handle to the registered item.</returns>
|
||||
public static RegisteredItem Register(Identifier id, ItemProperties properties)
|
||||
{
|
||||
int numericId = NativeInterop.native_register_item(
|
||||
id.ToString(),
|
||||
properties.MaxStackSizeValue,
|
||||
properties.MaxDamageValue,
|
||||
properties.IconValue,
|
||||
properties.NameValue ?? "");
|
||||
return RegisterInternal(id, properties, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a managed custom item implementation.
|
||||
/// </summary>
|
||||
/// <param name="id">Namespaced identifier (e.g. "mymod:ruby_pickaxe").</param>
|
||||
/// <param name="item">Managed item instance that can override callbacks.</param>
|
||||
/// <param name="properties">Item properties built with <see cref="ItemProperties"/>.</param>
|
||||
/// <returns>A handle to the registered item.</returns>
|
||||
public static RegisteredItem Register(Identifier id, Item item, ItemProperties properties)
|
||||
{
|
||||
return RegisterInternal(id, properties, item);
|
||||
}
|
||||
|
||||
private static RegisteredItem RegisterInternal(Identifier id, ItemProperties properties, Item? managedItem)
|
||||
{
|
||||
int numericId;
|
||||
if (managedItem is PickaxeItem pickaxeItem)
|
||||
{
|
||||
numericId = NativeInterop.native_register_pickaxe_item(
|
||||
id.ToString(),
|
||||
(int)pickaxeItem.Tier,
|
||||
properties.MaxDamageValue,
|
||||
properties.IconValue,
|
||||
properties.NameValue ?? "");
|
||||
}
|
||||
else
|
||||
{
|
||||
numericId = NativeInterop.native_register_item(
|
||||
id.ToString(),
|
||||
properties.MaxStackSizeValue,
|
||||
properties.MaxDamageValue,
|
||||
properties.IconValue,
|
||||
properties.NameValue ?? "");
|
||||
}
|
||||
|
||||
if (numericId < 0)
|
||||
throw new InvalidOperationException($"Failed to register item '{id}'. No free IDs or invalid parameters.");
|
||||
@@ -48,6 +78,12 @@ public static class ItemRegistry
|
||||
Logger.Debug($"Item '{id}' added to creative tab {properties.CreativeTabValue}");
|
||||
}
|
||||
|
||||
if (managedItem != null)
|
||||
{
|
||||
ManagedItemDispatcher.RegisterItem(id, numericId, managedItem);
|
||||
Logger.Debug($"Managed item dispatcher mapped '{id}' -> numeric ID {numericId} ({managedItem.GetType().FullName})");
|
||||
}
|
||||
|
||||
Logger.Debug($"Registered item '{id}' -> numeric ID {numericId}");
|
||||
return new RegisteredItem(id, numericId);
|
||||
}
|
||||
|
||||
@@ -29,8 +29,21 @@ public static class Logger
|
||||
public static void Log(string message, LogLevel level = LogLevel.Info)
|
||||
{
|
||||
if (LogHandler != null)
|
||||
{
|
||||
LogHandler(message, level);
|
||||
else
|
||||
Console.WriteLine($"[WeaveLoader/{level}] {message}");
|
||||
return;
|
||||
}
|
||||
|
||||
string formatted = $"[WeaveLoader/{level}] {message}";
|
||||
try
|
||||
{
|
||||
// Fallback path: write directly to native runtime logging so mod logs
|
||||
// still appear even if the managed log handler was not initialized.
|
||||
NativeInterop.native_log(formatted, (int)level);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine(formatted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,14 @@ internal static class NativeInterop
|
||||
string iconName,
|
||||
string displayName);
|
||||
|
||||
[DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
internal static extern int native_register_pickaxe_item(
|
||||
string namespacedId,
|
||||
int tier,
|
||||
int maxDamage,
|
||||
string iconName,
|
||||
string displayName);
|
||||
|
||||
[DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
internal static extern int native_allocate_description_id();
|
||||
|
||||
|
||||
@@ -24,6 +24,9 @@ public static class Registry
|
||||
{
|
||||
public static RegisteredItem Register(Identifier id, ItemProperties properties)
|
||||
=> ItemRegistry.Register(id, properties);
|
||||
|
||||
public static RegisteredItem Register(Identifier id, WeaveLoader.API.Item.Item item, ItemProperties properties)
|
||||
=> ItemRegistry.Register(id, item, properties);
|
||||
}
|
||||
|
||||
/// <summary>Entity registration. Call Register() with a namespaced ID and EntityDefinition.</summary>
|
||||
|
||||
Reference in New Issue
Block a user