diff --git a/ExampleMod/ExampleMod.cs b/ExampleMod/ExampleMod.cs
index 0f536e7..81063e7 100644
--- a/ExampleMod/ExampleMod.cs
+++ b/ExampleMod/ExampleMod.cs
@@ -16,12 +16,14 @@ public class ExampleMod : IMod
public static RegisteredBlock? RubyWoodPlanks;
public static RegisteredBlock? RubyChair;
public static RegisteredBlock? RubySand;
+ public static RegisteredBlock? DebugHooksBlock;
public static RegisteredSlabBlock? RubyStoneSlab;
public static RegisteredSlabBlock? RubyWoodSlab;
public static RegisteredBlock? RubyLamp;
public static RegisteredBlock? RubyLampLit;
public static RegisteredBlock? Orichalcum;
public static RegisteredItem? Ruby;
+ public static RegisteredItem? DebugHooksItemEntry;
public static RegisteredItem? RubyPickaxeItem;
public static RegisteredItem? RubyShovelItem;
public static RegisteredItem? RubyHoeItem;
@@ -37,9 +39,6 @@ public class ExampleMod : IMod
public override UseItemResult OnUseItem(UseItemContext context)
{
- if (context.IsTestUseOnly)
- return UseItemResult.CancelVanilla;
-
long now = Environment.TickCount64;
ref long nextUseAtMs = ref context.IsClientSide ? ref _nextClientUseAtMs : ref _nextServerUseAtMs;
if (now < nextUseAtMs)
@@ -190,6 +189,153 @@ public class ExampleMod : IMod
}
}
+ private sealed class DebugHooks : WeaveLoader.API.Block.Block
+ {
+ private static void Log(string hook, BlockUpdateContext block, string? extra = null)
+ {
+ if (string.IsNullOrWhiteSpace(extra))
+ Logger.Info($"DebugHooks::{hook} at ({block.X}, {block.Y}, {block.Z}) blockId={block.BlockId} client={block.IsClientSide}");
+ else
+ Logger.Info($"DebugHooks::{hook} at ({block.X}, {block.Y}, {block.Z}) blockId={block.BlockId} client={block.IsClientSide} {extra}");
+ }
+
+ public override void OnPlace(BlockUpdateContext context)
+ {
+ Log("OnPlace", context);
+ }
+
+ public override void OnNeighborChanged(BlockNeighborChangedContext context)
+ {
+ Log("OnNeighborChanged", context.Block, $"neighborId={context.NeighborBlockId}");
+ }
+
+ public override void OnScheduledTick(BlockTickContext context)
+ {
+ Log("OnScheduledTick", context.Block);
+ }
+
+ public override BlockActionResult OnUse(BlockUseContext context)
+ {
+ Log("OnUse", context.Block,
+ $"face={context.Face} click=({context.ClickX:0.00},{context.ClickY:0.00},{context.ClickZ:0.00}) soundOnly={context.SoundOnly}");
+ return BlockActionResult.ContinueVanilla;
+ }
+
+ public override void OnStepOn(BlockEntityContext context)
+ {
+ Log("OnStepOn", context.Block, $"entityPtr=0x{context.NativeEntityPtr.ToString("X")}");
+ }
+
+ public override void OnEntityInsideTile(BlockEntityContext context)
+ {
+ Log("OnEntityInsideTile", context.Block, $"entityPtr=0x{context.NativeEntityPtr.ToString("X")}");
+ }
+
+ public override void OnFallOn(BlockFallContext context)
+ {
+ Log("OnFallOn", context.Block, $"entityPtr=0x{context.NativeEntityPtr.ToString("X")} fall={context.FallDistance:0.00}");
+ }
+
+ public override void OnRemoving(BlockRemovingContext context)
+ {
+ Log("OnRemoving", context.Block, $"blockData={context.BlockData}");
+ }
+
+ public override void OnRemoved(BlockRemoveContext context)
+ {
+ Log("OnRemoved", context.Block, $"removedId={context.RemovedBlockId} removedData={context.RemovedBlockData}");
+ }
+
+ public override void OnDestroyed(BlockDestroyContext context)
+ {
+ Log("OnDestroyed", context.Block, $"blockData={context.BlockData}");
+ }
+
+ public override void OnPlayerDestroy(BlockPlayerDestroyContext context)
+ {
+ Log("OnPlayerDestroy", context.Block,
+ $"playerPtr=0x{context.NativePlayerPtr.ToString("X")} blockData={context.BlockData}");
+ }
+
+ public override void OnPlayerWillDestroy(BlockPlayerWillDestroyContext context)
+ {
+ Log("OnPlayerWillDestroy", context.Block,
+ $"playerPtr=0x{context.NativePlayerPtr.ToString("X")} blockData={context.BlockData}");
+ }
+
+ public override void OnPlacedBy(BlockPlacedByContext context)
+ {
+ Log("OnPlacedBy", context.Block,
+ $"placerPtr=0x{context.NativePlacerPtr.ToString("X")} itemPtr=0x{context.NativeItemInstancePtr.ToString("X")}");
+ }
+
+ }
+
+ private sealed class DebugHooksItem : Item
+ {
+ private static void Log(string hook, string? extra = null)
+ {
+ if (string.IsNullOrWhiteSpace(extra))
+ Logger.Info($"DebugItem::{hook}");
+ else
+ Logger.Info($"DebugItem::{hook} {extra}");
+ }
+
+ public override MineBlockResult OnMineBlock(MineBlockContext context)
+ {
+ Log("OnMineBlock", $"itemId={context.ItemId} tileId={context.TileId} pos=({context.X},{context.Y},{context.Z})");
+ return MineBlockResult.ContinueVanilla;
+ }
+
+ public override UseItemResult OnUseItem(UseItemContext context)
+ {
+ Log("OnUseItem",
+ $"itemId={context.ItemId} client={context.IsClientSide} " +
+ $"itemPtr=0x{context.NativeItemInstancePtr.ToString("X")} playerPtr=0x{context.NativePlayerPtr.ToString("X")}");
+ return UseItemResult.ContinueVanilla;
+ }
+
+ public override ItemActionResult OnUseOn(UseOnItemContext context)
+ {
+ Log("OnUseOn",
+ $"itemId={context.ItemId} client={context.IsClientSide} " +
+ $"pos=({context.X},{context.Y},{context.Z}) face={context.Face} " +
+ $"click=({context.ClickX:0.00},{context.ClickY:0.00},{context.ClickZ:0.00}) " +
+ $"playerPtr=0x{context.NativePlayerPtr.ToString("X")} itemPtr=0x{context.NativeItemInstancePtr.ToString("X")}");
+ return ItemActionResult.ContinueVanilla;
+ }
+
+ public override ItemActionResult OnInteractEntity(ItemEntityInteractionContext context)
+ {
+ Log("OnInteractEntity",
+ $"itemId={context.ItemId} playerPtr=0x{context.NativePlayerPtr.ToString("X")} " +
+ $"targetPtr=0x{context.NativeTargetEntityPtr.ToString("X")} itemPtr=0x{context.NativeItemInstancePtr.ToString("X")}");
+ return ItemActionResult.ContinueVanilla;
+ }
+
+ public override ItemActionResult OnHurtEntity(ItemEntityInteractionContext context)
+ {
+ Log("OnHurtEntity",
+ $"itemId={context.ItemId} playerPtr=0x{context.NativePlayerPtr.ToString("X")} " +
+ $"targetPtr=0x{context.NativeTargetEntityPtr.ToString("X")} itemPtr=0x{context.NativeItemInstancePtr.ToString("X")}");
+ return ItemActionResult.ContinueVanilla;
+ }
+
+ public override void OnInventoryTick(ItemInventoryTickContext context)
+ {
+ Log("OnInventoryTick",
+ $"itemId={context.ItemId} slot={context.Slot} selected={context.IsSelected} client={context.IsClientSide} " +
+ $"ownerPtr=0x{context.NativeOwnerEntityPtr.ToString("X")} itemPtr=0x{context.NativeItemInstancePtr.ToString("X")}");
+ }
+
+ public override void OnCraftedBy(ItemCraftedByContext context)
+ {
+ Log("OnCraftedBy",
+ $"itemId={context.ItemId} amount={context.Amount} client={context.IsClientSide} " +
+ $"playerPtr=0x{context.NativePlayerPtr.ToString("X")} itemPtr=0x{context.NativeItemInstancePtr.ToString("X")}");
+ }
+ }
+
public void OnInitialize()
{
GameEvents.OnWorldLoaded += (_, e) =>
@@ -262,6 +408,17 @@ public class ExampleMod : IMod
.InCreativeTab(CreativeTab.BuildingBlocks)
.Prepend());
+ DebugHooksBlock = Registry.Block.Register("examplemod:debug_hooks",
+ new DebugHooks(),
+ new BlockProperties()
+ .Material(MaterialType.Stone)
+ .Hardness(1.0f)
+ .Resistance(5.0f)
+ .Sound(SoundType.Stone)
+ .Icon("examplemod:block/ruby_stone")
+ .Name(Text.Translatable("block.examplemod.debug_hooks"))
+ .InCreativeTab(CreativeTab.BuildingBlocks));
+
RubyStoneSlab = (RegisteredSlabBlock)Registry.Block.Register("examplemod:ruby_stone_slab",
new SlabBlock(),
new BlockProperties()
@@ -336,6 +493,14 @@ public class ExampleMod : IMod
.Name(Text.Translatable("item.examplemod.ruby"))
.InCreativeTab(CreativeTab.Materials));
+ DebugHooksItemEntry = Registry.Item.Register("examplemod:debug_item",
+ new DebugHooksItem(),
+ new ItemProperties()
+ .MaxStackSize(1)
+ .Icon("examplemod:item/ruby")
+ .Name(Text.Translatable("item.examplemod.debug_item"))
+ .InCreativeTab(CreativeTab.ToolsAndWeapons));
+
Registry.Item.RegisterToolMaterial("examplemod:ruby_material",
new ToolMaterialDefinition()
.BaseTier(ToolTier.Diamond)
diff --git a/ExampleMod/assets/examplemod/lang/en-GB.lang b/ExampleMod/assets/examplemod/lang/en-GB.lang
index cfdc972..bfa7238 100644
--- a/ExampleMod/assets/examplemod/lang/en-GB.lang
+++ b/ExampleMod/assets/examplemod/lang/en-GB.lang
@@ -11,7 +11,9 @@ block.examplemod.ruby_wood_slab_double=Ruby Wood Slab
block.examplemod.ruby_lamp=Ruby Lamp
block.examplemod.ruby_lamp_lit=Ruby Lamp
block.examplemod.orichalcum_ore=Orichalcum Ore
+block.examplemod.debug_hooks=Debug Hooks Block
item.examplemod.ruby=Ruby
+item.examplemod.debug_item=Debug Hooks Item
item.examplemod.ruby_sword=Ruby Sword
item.examplemod.ruby_shovel=Ruby Shovel
item.examplemod.ruby_pickaxe=Ruby Pickaxe
diff --git a/WeaveLoader.API/Block/CustomBlock.cs b/WeaveLoader.API/Block/CustomBlock.cs
index 2c49757..49fa965 100644
--- a/WeaveLoader.API/Block/CustomBlock.cs
+++ b/WeaveLoader.API/Block/CustomBlock.cs
@@ -2,35 +2,139 @@ using System.Runtime.InteropServices;
namespace WeaveLoader.API.Block;
+///
+/// Base class for managed custom blocks.
+/// Mods can inherit and override callbacks for block behavior.
+///
public abstract class Block
{
+ /// The namespaced ID used during registration.
public Identifier? Id { get; internal set; }
+
+ /// The numeric runtime ID allocated by the game.
public int NumericId { get; internal set; } = -1;
+ /// Optional override for the block to drop when broken.
public Identifier? DropAsBlockId { get; init; }
+
+ /// Optional override for the block to clone via pick-block.
public Identifier? CloneAsBlockId { get; init; }
+ ///
+ /// Called when the block is placed in the world.
+ ///
public virtual void OnPlace(BlockUpdateContext context)
{
}
+ ///
+ /// Called when a neighboring block changes.
+ ///
public virtual void OnNeighborChanged(BlockNeighborChangedContext context)
{
}
+ ///
+ /// Called when a scheduled tick for this block fires.
+ ///
public virtual void OnScheduledTick(BlockTickContext context)
{
}
+
+ ///
+ /// Called when the block is used (right-click).
+ /// Return to run vanilla logic,
+ /// or to skip vanilla handling.
+ ///
+ public virtual BlockActionResult OnUse(BlockUseContext context) => BlockActionResult.ContinueVanilla;
+
+ ///
+ /// Called when an entity steps on the block.
+ ///
+ public virtual void OnStepOn(BlockEntityContext context)
+ {
+ }
+
+ ///
+ /// Called when an entity is inside the block's volume.
+ ///
+ public virtual void OnEntityInsideTile(BlockEntityContext context)
+ {
+ }
+
+ ///
+ /// Called when an entity falls onto the block.
+ ///
+ public virtual void OnFallOn(BlockFallContext context)
+ {
+ }
+
+ ///
+ /// Called before the block is removed from the world.
+ ///
+ public virtual void OnRemoving(BlockRemovingContext context)
+ {
+ }
+
+ ///
+ /// Called after the block is removed from the world.
+ ///
+ public virtual void OnRemoved(BlockRemoveContext context)
+ {
+ }
+
+ ///
+ /// Called when the block is destroyed by the world (non-player).
+ ///
+ public virtual void OnDestroyed(BlockDestroyContext context)
+ {
+ }
+
+ ///
+ /// Called when the block is destroyed by a player.
+ ///
+ public virtual void OnPlayerDestroy(BlockPlayerDestroyContext context)
+ {
+ }
+
+ ///
+ /// Called just before a player destroys the block.
+ ///
+ public virtual void OnPlayerWillDestroy(BlockPlayerWillDestroyContext context)
+ {
+ }
+
+ ///
+ /// Called when the block is placed by a player with an item.
+ ///
+ public virtual void OnPlacedBy(BlockPlacedByContext context)
+ {
+ }
+
}
+/// Managed falling block base class.
public class FallingBlock : Block
{
}
+/// Managed slab block base class.
public class SlabBlock : Block
{
}
+///
+/// Result of managed block action callbacks.
+///
+public enum BlockActionResult
+{
+ ContinueVanilla = 0,
+ CancelVanilla = 1
+}
+
+///
+/// Runtime context for block update callbacks.
+///
public readonly struct BlockUpdateContext
{
public int BlockId { get; }
@@ -58,6 +162,9 @@ public readonly struct BlockUpdateContext
return NativeInterop.native_level_has_neighbor_signal(NativeLevelPtr, X, Y, Z) != 0;
}
+ ///
+ /// Set the block at this context's position.
+ ///
public bool SetBlock(Identifier id, int data = 0, int flags = 2)
{
int numericId = IdHelper.GetBlockNumericId(id);
@@ -67,6 +174,9 @@ public readonly struct BlockUpdateContext
return SetBlock(numericId, data, flags);
}
+ ///
+ /// Set the block at this context's position using a numeric ID.
+ ///
public bool SetBlock(int numericBlockId, int data = 0, int flags = 2)
{
if (NativeLevelPtr == 0 || numericBlockId < 0)
@@ -75,6 +185,9 @@ public readonly struct BlockUpdateContext
return NativeInterop.native_level_set_tile(NativeLevelPtr, X, Y, Z, numericBlockId, data, flags) != 0;
}
+ ///
+ /// Schedule a tick for this block after a delay (in ticks).
+ ///
public bool ScheduleTick(int delay)
{
if (NativeLevelPtr == 0 || delay < 0)
@@ -83,6 +196,9 @@ public readonly struct BlockUpdateContext
return NativeInterop.native_level_schedule_tick(NativeLevelPtr, X, Y, Z, BlockId, delay) != 0;
}
+ ///
+ /// Read a block ID relative to this context's position.
+ ///
public int GetBlockId(int offsetX = 0, int offsetY = 0, int offsetZ = 0)
{
if (NativeLevelPtr == 0)
@@ -92,6 +208,9 @@ public readonly struct BlockUpdateContext
}
}
+///
+/// Runtime context for block neighbor-change callback.
+///
public readonly struct BlockNeighborChangedContext
{
public BlockUpdateContext Block { get; }
@@ -104,6 +223,9 @@ public readonly struct BlockNeighborChangedContext
}
}
+///
+/// Runtime context for scheduled tick callback.
+///
public readonly struct BlockTickContext
{
public BlockUpdateContext Block { get; }
@@ -114,6 +236,161 @@ public readonly struct BlockTickContext
}
}
+///
+/// Runtime context for block use callback.
+///
+public readonly struct BlockUseContext
+{
+ public BlockUpdateContext Block { get; }
+ public nint NativePlayerPtr { get; }
+ public int Face { get; }
+ public float ClickX { get; }
+ public float ClickY { get; }
+ public float ClickZ { get; }
+ public bool SoundOnly { get; }
+
+ internal BlockUseContext(BlockUpdateContext block, nint nativePlayerPtr, int face, float clickX, float clickY, float clickZ, bool soundOnly)
+ {
+ Block = block;
+ NativePlayerPtr = nativePlayerPtr;
+ Face = face;
+ ClickX = clickX;
+ ClickY = clickY;
+ ClickZ = clickZ;
+ SoundOnly = soundOnly;
+ }
+}
+
+///
+/// Runtime context for entity-on-block callbacks.
+///
+public readonly struct BlockEntityContext
+{
+ public BlockUpdateContext Block { get; }
+ public nint NativeEntityPtr { get; }
+
+ internal BlockEntityContext(BlockUpdateContext block, nint nativeEntityPtr)
+ {
+ Block = block;
+ NativeEntityPtr = nativeEntityPtr;
+ }
+}
+
+///
+/// Runtime context for block fall-on callback.
+///
+public readonly struct BlockFallContext
+{
+ public BlockUpdateContext Block { get; }
+ public nint NativeEntityPtr { get; }
+ public float FallDistance { get; }
+
+ internal BlockFallContext(BlockUpdateContext block, nint nativeEntityPtr, float fallDistance)
+ {
+ Block = block;
+ NativeEntityPtr = nativeEntityPtr;
+ FallDistance = fallDistance;
+ }
+}
+
+///
+/// Runtime context for block removing callback.
+///
+public readonly struct BlockRemovingContext
+{
+ public BlockUpdateContext Block { get; }
+ public int BlockData { get; }
+
+ internal BlockRemovingContext(BlockUpdateContext block, int blockData)
+ {
+ Block = block;
+ BlockData = blockData;
+ }
+}
+
+///
+/// Runtime context for block removed callback.
+///
+public readonly struct BlockRemoveContext
+{
+ public BlockUpdateContext Block { get; }
+ public int RemovedBlockId { get; }
+ public int RemovedBlockData { get; }
+
+ internal BlockRemoveContext(BlockUpdateContext block, int removedBlockId, int removedBlockData)
+ {
+ Block = block;
+ RemovedBlockId = removedBlockId;
+ RemovedBlockData = removedBlockData;
+ }
+}
+
+///
+/// Runtime context for block destroyed callback.
+///
+public readonly struct BlockDestroyContext
+{
+ public BlockUpdateContext Block { get; }
+ public int BlockData { get; }
+
+ internal BlockDestroyContext(BlockUpdateContext block, int blockData)
+ {
+ Block = block;
+ BlockData = blockData;
+ }
+}
+
+///
+/// Runtime context for player-destroyed callback.
+///
+public readonly struct BlockPlayerDestroyContext
+{
+ public BlockUpdateContext Block { get; }
+ public nint NativePlayerPtr { get; }
+ public int BlockData { get; }
+
+ internal BlockPlayerDestroyContext(BlockUpdateContext block, nint nativePlayerPtr, int blockData)
+ {
+ Block = block;
+ NativePlayerPtr = nativePlayerPtr;
+ BlockData = blockData;
+ }
+}
+
+///
+/// Runtime context for player-will-destroy callback.
+///
+public readonly struct BlockPlayerWillDestroyContext
+{
+ public BlockUpdateContext Block { get; }
+ public nint NativePlayerPtr { get; }
+ public int BlockData { get; }
+
+ internal BlockPlayerWillDestroyContext(BlockUpdateContext block, nint nativePlayerPtr, int blockData)
+ {
+ Block = block;
+ NativePlayerPtr = nativePlayerPtr;
+ BlockData = blockData;
+ }
+}
+
+///
+/// Runtime context for block placed-by callback.
+///
+public readonly struct BlockPlacedByContext
+{
+ public BlockUpdateContext Block { get; }
+ public nint NativePlacerPtr { get; }
+ public nint NativeItemInstancePtr { get; }
+
+ internal BlockPlacedByContext(BlockUpdateContext block, nint nativePlacerPtr, nint nativeItemInstancePtr)
+ {
+ Block = block;
+ NativePlacerPtr = nativePlacerPtr;
+ NativeItemInstancePtr = nativeItemInstancePtr;
+ }
+}
+
[StructLayout(LayoutKind.Sequential)]
internal struct BlockUpdateNativeArgs
{
@@ -132,6 +409,79 @@ internal struct BlockNeighborChangedNativeArgs
public int NeighborBlockId;
}
+[StructLayout(LayoutKind.Sequential)]
+internal struct BlockUseNativeArgs
+{
+ public BlockUpdateNativeArgs Block;
+ public nint PlayerPtr;
+ public int Face;
+ public float ClickX;
+ public float ClickY;
+ public float ClickZ;
+ public int SoundOnly;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal struct BlockEntityNativeArgs
+{
+ public BlockUpdateNativeArgs Block;
+ public nint EntityPtr;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal struct BlockFallNativeArgs
+{
+ public BlockUpdateNativeArgs Block;
+ public nint EntityPtr;
+ public float FallDistance;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal struct BlockRemovingNativeArgs
+{
+ public BlockUpdateNativeArgs Block;
+ public int BlockData;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal struct BlockRemoveNativeArgs
+{
+ public BlockUpdateNativeArgs Block;
+ public int RemovedBlockId;
+ public int RemovedBlockData;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal struct BlockDestroyNativeArgs
+{
+ public BlockUpdateNativeArgs Block;
+ public int BlockData;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal struct BlockPlayerDestroyNativeArgs
+{
+ public BlockUpdateNativeArgs Block;
+ public nint PlayerPtr;
+ public int BlockData;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal struct BlockPlayerWillDestroyNativeArgs
+{
+ public BlockUpdateNativeArgs Block;
+ public nint PlayerPtr;
+ public int BlockData;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal struct BlockPlacedByNativeArgs
+{
+ public BlockUpdateNativeArgs Block;
+ public nint PlacerPtr;
+ public nint ItemInstancePtr;
+}
+
internal static class ManagedBlockDispatcher
{
private static readonly object s_lock = new();
@@ -223,4 +573,275 @@ internal static class ManagedBlockDispatcher
nativeArgs.Z)));
return 1;
}
+
+ internal static int HandleUse(IntPtr args, int sizeBytes)
+ {
+ if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf())
+ return 0;
+
+ BlockUseNativeArgs nativeArgs = Marshal.PtrToStructure(args);
+ Block? block;
+ lock (s_lock)
+ {
+ s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block);
+ }
+
+ if (block == null)
+ return 0;
+
+ var update = new BlockUpdateContext(
+ nativeArgs.Block.BlockId,
+ nativeArgs.Block.IsClientSide != 0,
+ nativeArgs.Block.LevelPtr,
+ nativeArgs.Block.X,
+ nativeArgs.Block.Y,
+ nativeArgs.Block.Z);
+
+ var result = block.OnUse(new BlockUseContext(
+ update,
+ nativeArgs.PlayerPtr,
+ nativeArgs.Face,
+ nativeArgs.ClickX,
+ nativeArgs.ClickY,
+ nativeArgs.ClickZ,
+ nativeArgs.SoundOnly != 0));
+
+ return result == BlockActionResult.CancelVanilla ? 2 : 1;
+ }
+
+ internal static int HandleStepOn(IntPtr args, int sizeBytes)
+ {
+ if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf())
+ return 0;
+
+ BlockEntityNativeArgs nativeArgs = Marshal.PtrToStructure(args);
+ Block? block;
+ lock (s_lock)
+ {
+ s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block);
+ }
+
+ if (block == null)
+ return 0;
+
+ var update = new BlockUpdateContext(
+ nativeArgs.Block.BlockId,
+ nativeArgs.Block.IsClientSide != 0,
+ nativeArgs.Block.LevelPtr,
+ nativeArgs.Block.X,
+ nativeArgs.Block.Y,
+ nativeArgs.Block.Z);
+ block.OnStepOn(new BlockEntityContext(update, nativeArgs.EntityPtr));
+ return 1;
+ }
+
+ internal static int HandleEntityInsideTile(IntPtr args, int sizeBytes)
+ {
+ if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf())
+ return 0;
+
+ BlockEntityNativeArgs nativeArgs = Marshal.PtrToStructure(args);
+ Block? block;
+ lock (s_lock)
+ {
+ s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block);
+ }
+
+ if (block == null)
+ return 0;
+
+ var update = new BlockUpdateContext(
+ nativeArgs.Block.BlockId,
+ nativeArgs.Block.IsClientSide != 0,
+ nativeArgs.Block.LevelPtr,
+ nativeArgs.Block.X,
+ nativeArgs.Block.Y,
+ nativeArgs.Block.Z);
+ block.OnEntityInsideTile(new BlockEntityContext(update, nativeArgs.EntityPtr));
+ return 1;
+ }
+
+ internal static int HandleFallOn(IntPtr args, int sizeBytes)
+ {
+ if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf())
+ return 0;
+
+ BlockFallNativeArgs nativeArgs = Marshal.PtrToStructure(args);
+ Block? block;
+ lock (s_lock)
+ {
+ s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block);
+ }
+
+ if (block == null)
+ return 0;
+
+ var update = new BlockUpdateContext(
+ nativeArgs.Block.BlockId,
+ nativeArgs.Block.IsClientSide != 0,
+ nativeArgs.Block.LevelPtr,
+ nativeArgs.Block.X,
+ nativeArgs.Block.Y,
+ nativeArgs.Block.Z);
+ block.OnFallOn(new BlockFallContext(update, nativeArgs.EntityPtr, nativeArgs.FallDistance));
+ return 1;
+ }
+
+ internal static int HandleRemoving(IntPtr args, int sizeBytes)
+ {
+ if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf())
+ return 0;
+
+ BlockRemovingNativeArgs nativeArgs = Marshal.PtrToStructure(args);
+ Block? block;
+ lock (s_lock)
+ {
+ s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block);
+ }
+
+ if (block == null)
+ return 0;
+
+ var update = new BlockUpdateContext(
+ nativeArgs.Block.BlockId,
+ nativeArgs.Block.IsClientSide != 0,
+ nativeArgs.Block.LevelPtr,
+ nativeArgs.Block.X,
+ nativeArgs.Block.Y,
+ nativeArgs.Block.Z);
+ block.OnRemoving(new BlockRemovingContext(update, nativeArgs.BlockData));
+ return 1;
+ }
+
+ internal static int HandleRemoved(IntPtr args, int sizeBytes)
+ {
+ if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf())
+ return 0;
+
+ BlockRemoveNativeArgs nativeArgs = Marshal.PtrToStructure(args);
+ Block? block;
+ lock (s_lock)
+ {
+ s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block);
+ }
+
+ if (block == null)
+ return 0;
+
+ var update = new BlockUpdateContext(
+ nativeArgs.Block.BlockId,
+ nativeArgs.Block.IsClientSide != 0,
+ nativeArgs.Block.LevelPtr,
+ nativeArgs.Block.X,
+ nativeArgs.Block.Y,
+ nativeArgs.Block.Z);
+ block.OnRemoved(new BlockRemoveContext(update, nativeArgs.RemovedBlockId, nativeArgs.RemovedBlockData));
+ return 1;
+ }
+
+ internal static int HandleDestroyed(IntPtr args, int sizeBytes)
+ {
+ if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf())
+ return 0;
+
+ BlockDestroyNativeArgs nativeArgs = Marshal.PtrToStructure(args);
+ Block? block;
+ lock (s_lock)
+ {
+ s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block);
+ }
+
+ if (block == null)
+ return 0;
+
+ var update = new BlockUpdateContext(
+ nativeArgs.Block.BlockId,
+ nativeArgs.Block.IsClientSide != 0,
+ nativeArgs.Block.LevelPtr,
+ nativeArgs.Block.X,
+ nativeArgs.Block.Y,
+ nativeArgs.Block.Z);
+ block.OnDestroyed(new BlockDestroyContext(update, nativeArgs.BlockData));
+ return 1;
+ }
+
+ internal static int HandlePlayerDestroy(IntPtr args, int sizeBytes)
+ {
+ if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf())
+ return 0;
+
+ BlockPlayerDestroyNativeArgs nativeArgs = Marshal.PtrToStructure(args);
+ Block? block;
+ lock (s_lock)
+ {
+ s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block);
+ }
+
+ if (block == null)
+ return 0;
+
+ var update = new BlockUpdateContext(
+ nativeArgs.Block.BlockId,
+ nativeArgs.Block.IsClientSide != 0,
+ nativeArgs.Block.LevelPtr,
+ nativeArgs.Block.X,
+ nativeArgs.Block.Y,
+ nativeArgs.Block.Z);
+ block.OnPlayerDestroy(new BlockPlayerDestroyContext(update, nativeArgs.PlayerPtr, nativeArgs.BlockData));
+ return 1;
+ }
+
+ internal static int HandlePlayerWillDestroy(IntPtr args, int sizeBytes)
+ {
+ if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf())
+ return 0;
+
+ BlockPlayerWillDestroyNativeArgs nativeArgs = Marshal.PtrToStructure(args);
+ Block? block;
+ lock (s_lock)
+ {
+ s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block);
+ }
+
+ if (block == null)
+ return 0;
+
+ var update = new BlockUpdateContext(
+ nativeArgs.Block.BlockId,
+ nativeArgs.Block.IsClientSide != 0,
+ nativeArgs.Block.LevelPtr,
+ nativeArgs.Block.X,
+ nativeArgs.Block.Y,
+ nativeArgs.Block.Z);
+ block.OnPlayerWillDestroy(new BlockPlayerWillDestroyContext(update, nativeArgs.PlayerPtr, nativeArgs.BlockData));
+ return 1;
+ }
+
+ internal static int HandlePlacedBy(IntPtr args, int sizeBytes)
+ {
+ if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf())
+ return 0;
+
+ BlockPlacedByNativeArgs nativeArgs = Marshal.PtrToStructure(args);
+ Block? block;
+ lock (s_lock)
+ {
+ s_blocks.TryGetValue(nativeArgs.Block.BlockId, out block);
+ }
+
+ if (block == null)
+ return 0;
+
+ var update = new BlockUpdateContext(
+ nativeArgs.Block.BlockId,
+ nativeArgs.Block.IsClientSide != 0,
+ nativeArgs.Block.LevelPtr,
+ nativeArgs.Block.X,
+ nativeArgs.Block.Y,
+ nativeArgs.Block.Z);
+ block.OnPlacedBy(new BlockPlacedByContext(update, nativeArgs.PlacerPtr, nativeArgs.ItemInstancePtr));
+ return 1;
+ }
+
+
}
diff --git a/WeaveLoader.API/Item/CustomItem.cs b/WeaveLoader.API/Item/CustomItem.cs
index 7a9ce27..368a108 100644
--- a/WeaveLoader.API/Item/CustomItem.cs
+++ b/WeaveLoader.API/Item/CustomItem.cs
@@ -27,6 +27,41 @@ public abstract class Item
/// or to skip vanilla handling.
///
public virtual UseItemResult OnUseItem(UseItemContext context) => UseItemResult.ContinueVanilla;
+
+ ///
+ /// Called when this item is used on a block.
+ /// Return to run vanilla logic,
+ /// or to skip vanilla handling.
+ ///
+ public virtual ItemActionResult OnUseOn(UseOnItemContext context) => ItemActionResult.ContinueVanilla;
+
+ ///
+ /// Called when this item interacts with an entity (right-click interaction).
+ /// Return to run vanilla logic,
+ /// or to skip vanilla handling.
+ ///
+ public virtual ItemActionResult OnInteractEntity(ItemEntityInteractionContext context) => ItemActionResult.ContinueVanilla;
+
+ ///
+ /// Called when this item hurts an entity.
+ /// Return to run vanilla logic,
+ /// or to skip vanilla handling.
+ ///
+ public virtual ItemActionResult OnHurtEntity(ItemEntityInteractionContext context) => ItemActionResult.ContinueVanilla;
+
+ ///
+ /// Called each tick while this item is in an inventory.
+ ///
+ public virtual void OnInventoryTick(ItemInventoryTickContext context)
+ {
+ }
+
+ ///
+ /// Called when this item is crafted by a player.
+ ///
+ public virtual void OnCraftedBy(ItemCraftedByContext context)
+ {
+ }
}
///
@@ -47,6 +82,15 @@ public enum UseItemResult
CancelVanilla = 1
}
+///
+/// Result of managed item action callbacks.
+///
+public enum ItemActionResult
+{
+ ContinueVanilla = 0,
+ CancelVanilla = 1
+}
+
///
/// Tool tier used by native tool constructors.
///
@@ -117,16 +161,14 @@ public readonly struct MineBlockContext
public readonly struct UseItemContext
{
public int ItemId { get; }
- public bool IsTestUseOnly { get; }
public bool IsClientSide { get; }
public nint NativeItemInstancePtr { get; }
public nint NativePlayerPtr { get; }
public nint NativePlayerSharedPtr { get; }
- internal UseItemContext(int itemId, bool isTestUseOnly, bool isClientSide, nint nativeItemInstancePtr, nint nativePlayerPtr, nint nativePlayerSharedPtr)
+ internal UseItemContext(int itemId, bool isClientSide, nint nativeItemInstancePtr, nint nativePlayerPtr, nint nativePlayerSharedPtr)
{
ItemId = itemId;
- IsTestUseOnly = isTestUseOnly;
IsClientSide = isClientSide;
NativeItemInstancePtr = nativeItemInstancePtr;
NativePlayerPtr = nativePlayerPtr;
@@ -177,6 +219,197 @@ public readonly struct UseItemContext
}
}
+///
+/// Runtime context for item use-on-block callback.
+///
+public readonly struct UseOnItemContext
+{
+ public int ItemId { get; }
+ public bool IsClientSide { get; }
+ public nint NativeItemInstancePtr { get; }
+ public nint NativePlayerPtr { get; }
+ public nint NativePlayerSharedPtr { get; }
+ public nint NativeLevelPtr { get; }
+ public int X { get; }
+ public int Y { get; }
+ public int Z { get; }
+ public int Face { get; }
+ public float ClickX { get; }
+ public float ClickY { get; }
+ public float ClickZ { get; }
+
+ internal UseOnItemContext(
+ int itemId,
+ bool isClientSide,
+ nint nativeItemInstancePtr,
+ nint nativePlayerPtr,
+ nint nativePlayerSharedPtr,
+ nint nativeLevelPtr,
+ int x,
+ int y,
+ int z,
+ int face,
+ float clickX,
+ float clickY,
+ float clickZ)
+ {
+ ItemId = itemId;
+ IsClientSide = isClientSide;
+ NativeItemInstancePtr = nativeItemInstancePtr;
+ NativePlayerPtr = nativePlayerPtr;
+ NativePlayerSharedPtr = nativePlayerSharedPtr;
+ NativeLevelPtr = nativeLevelPtr;
+ X = x;
+ Y = y;
+ Z = z;
+ Face = face;
+ ClickX = clickX;
+ ClickY = clickY;
+ ClickZ = clickZ;
+ }
+
+ public bool ConsumeInventoryItem(Identifier id, int count = 1)
+ {
+ if (NativePlayerPtr == 0 || count <= 0)
+ return false;
+
+ int numericId = IdHelper.GetItemNumericId(id);
+ if (numericId < 0)
+ return false;
+
+ return NativeInterop.native_consume_item_from_player(NativePlayerPtr, numericId, count) != 0;
+ }
+
+ public bool DamageItem(int amount)
+ {
+ if (NativeItemInstancePtr == 0 || NativePlayerSharedPtr == 0 || amount <= 0)
+ return false;
+
+ return NativeInterop.native_damage_item_instance(NativeItemInstancePtr, amount, NativePlayerSharedPtr) != 0;
+ }
+
+ public bool SpawnEntityFromLook(Identifier id, double speed = 1.4, double spawnForward = 1.0, double spawnUp = 1.2)
+ {
+ int numericEntityId = IdHelper.GetEntityNumericId(id);
+ if (numericEntityId < 0)
+ return false;
+
+ return SpawnEntityFromLook(numericEntityId, speed, spawnForward, spawnUp);
+ }
+
+ public bool SpawnEntityFromLook(int numericEntityId, double speed = 1.4, double spawnForward = 1.0, double spawnUp = 1.2)
+ {
+ if (NativePlayerPtr == 0 || numericEntityId < 0)
+ return false;
+
+ return NativeInterop.native_spawn_entity_from_player_look(
+ NativePlayerPtr,
+ NativePlayerSharedPtr,
+ numericEntityId,
+ speed,
+ spawnForward,
+ spawnUp) != 0;
+ }
+}
+
+///
+/// Runtime context for item/entity interactions.
+///
+public readonly struct ItemEntityInteractionContext
+{
+ public int ItemId { get; }
+ public nint NativeItemInstancePtr { get; }
+ public nint NativePlayerPtr { get; }
+ public nint NativePlayerSharedPtr { get; }
+ public nint NativeTargetEntityPtr { get; }
+
+ internal ItemEntityInteractionContext(
+ int itemId,
+ nint nativeItemInstancePtr,
+ nint nativePlayerPtr,
+ nint nativePlayerSharedPtr,
+ nint nativeTargetEntityPtr)
+ {
+ ItemId = itemId;
+ NativeItemInstancePtr = nativeItemInstancePtr;
+ NativePlayerPtr = nativePlayerPtr;
+ NativePlayerSharedPtr = nativePlayerSharedPtr;
+ NativeTargetEntityPtr = nativeTargetEntityPtr;
+ }
+
+ public bool DamageItem(int amount)
+ {
+ if (NativeItemInstancePtr == 0 || NativePlayerSharedPtr == 0 || amount <= 0)
+ return false;
+
+ return NativeInterop.native_damage_item_instance(NativeItemInstancePtr, amount, NativePlayerSharedPtr) != 0;
+ }
+}
+
+///
+/// Runtime context for inventory tick callbacks.
+///
+public readonly struct ItemInventoryTickContext
+{
+ public int ItemId { get; }
+ public nint NativeItemInstancePtr { get; }
+ public nint NativeLevelPtr { get; }
+ public nint NativeOwnerEntityPtr { get; }
+ public int Slot { get; }
+ public bool IsSelected { get; }
+ public bool IsClientSide { get; }
+
+ internal ItemInventoryTickContext(
+ int itemId,
+ nint nativeItemInstancePtr,
+ nint nativeLevelPtr,
+ nint nativeOwnerEntityPtr,
+ int slot,
+ bool isSelected,
+ bool isClientSide)
+ {
+ ItemId = itemId;
+ NativeItemInstancePtr = nativeItemInstancePtr;
+ NativeLevelPtr = nativeLevelPtr;
+ NativeOwnerEntityPtr = nativeOwnerEntityPtr;
+ Slot = slot;
+ IsSelected = isSelected;
+ IsClientSide = isClientSide;
+ }
+}
+
+///
+/// Runtime context for crafted-by callbacks.
+///
+public readonly struct ItemCraftedByContext
+{
+ public int ItemId { get; }
+ public nint NativeItemInstancePtr { get; }
+ public nint NativeLevelPtr { get; }
+ public nint NativePlayerPtr { get; }
+ public nint NativePlayerSharedPtr { get; }
+ public int Amount { get; }
+ public bool IsClientSide { get; }
+
+ internal ItemCraftedByContext(
+ int itemId,
+ nint nativeItemInstancePtr,
+ nint nativeLevelPtr,
+ nint nativePlayerPtr,
+ nint nativePlayerSharedPtr,
+ int amount,
+ bool isClientSide)
+ {
+ ItemId = itemId;
+ NativeItemInstancePtr = nativeItemInstancePtr;
+ NativeLevelPtr = nativeLevelPtr;
+ NativePlayerPtr = nativePlayerPtr;
+ NativePlayerSharedPtr = nativePlayerSharedPtr;
+ Amount = amount;
+ IsClientSide = isClientSide;
+ }
+}
+
[StructLayout(LayoutKind.Sequential)]
internal struct MineBlockNativeArgs
{
@@ -191,13 +424,64 @@ internal struct MineBlockNativeArgs
internal struct UseItemNativeArgs
{
public int ItemId;
- public int IsTestUseOnly;
public int IsClientSide;
public nint ItemInstancePtr;
public nint PlayerPtr;
public nint PlayerSharedPtr;
}
+[StructLayout(LayoutKind.Sequential)]
+internal struct UseOnItemNativeArgs
+{
+ public int ItemId;
+ public int IsClientSide;
+ public nint ItemInstancePtr;
+ public nint PlayerPtr;
+ public nint PlayerSharedPtr;
+ public nint LevelPtr;
+ public int X;
+ public int Y;
+ public int Z;
+ public int Face;
+ public float ClickX;
+ public float ClickY;
+ public float ClickZ;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal struct ItemEntityInteractionNativeArgs
+{
+ public int ItemId;
+ public nint ItemInstancePtr;
+ public nint PlayerPtr;
+ public nint PlayerSharedPtr;
+ public nint TargetEntityPtr;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal struct ItemInventoryTickNativeArgs
+{
+ public int ItemId;
+ public nint ItemInstancePtr;
+ public nint LevelPtr;
+ public nint OwnerEntityPtr;
+ public int Slot;
+ public int IsSelected;
+ public int IsClientSide;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal struct ItemCraftedByNativeArgs
+{
+ public int ItemId;
+ public nint ItemInstancePtr;
+ public nint LevelPtr;
+ public nint PlayerPtr;
+ public nint PlayerSharedPtr;
+ public int Amount;
+ public int IsClientSide;
+}
+
internal static class ManagedItemDispatcher
{
private static readonly object s_lock = new();
@@ -259,7 +543,6 @@ internal static class ManagedItemDispatcher
var result = item.OnUseItem(new UseItemContext(
nativeArgs.ItemId,
- nativeArgs.IsTestUseOnly != 0,
nativeArgs.IsClientSide != 0,
nativeArgs.ItemInstancePtr,
nativeArgs.PlayerPtr,
@@ -268,4 +551,144 @@ internal static class ManagedItemDispatcher
// 0 = no managed item, 1 = continue vanilla, 2 = cancel vanilla.
return result == UseItemResult.CancelVanilla ? 2 : 1;
}
+
+ internal static int HandleUseOn(IntPtr args, int sizeBytes)
+ {
+ if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf())
+ return 0;
+
+ UseOnItemNativeArgs nativeArgs = Marshal.PtrToStructure(args);
+
+ Item? item;
+ lock (s_lock)
+ {
+ s_items.TryGetValue(nativeArgs.ItemId, out item);
+ }
+
+ if (item == null)
+ return 0;
+
+ var result = item.OnUseOn(new UseOnItemContext(
+ nativeArgs.ItemId,
+ nativeArgs.IsClientSide != 0,
+ nativeArgs.ItemInstancePtr,
+ nativeArgs.PlayerPtr,
+ nativeArgs.PlayerSharedPtr,
+ nativeArgs.LevelPtr,
+ nativeArgs.X,
+ nativeArgs.Y,
+ nativeArgs.Z,
+ nativeArgs.Face,
+ nativeArgs.ClickX,
+ nativeArgs.ClickY,
+ nativeArgs.ClickZ));
+
+ return result == ItemActionResult.CancelVanilla ? 2 : 1;
+ }
+
+ internal static int HandleInteractEntity(IntPtr args, int sizeBytes)
+ {
+ if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf())
+ return 0;
+
+ ItemEntityInteractionNativeArgs nativeArgs = Marshal.PtrToStructure(args);
+
+ Item? item;
+ lock (s_lock)
+ {
+ s_items.TryGetValue(nativeArgs.ItemId, out item);
+ }
+
+ if (item == null)
+ return 0;
+
+ var result = item.OnInteractEntity(new ItemEntityInteractionContext(
+ nativeArgs.ItemId,
+ nativeArgs.ItemInstancePtr,
+ nativeArgs.PlayerPtr,
+ nativeArgs.PlayerSharedPtr,
+ nativeArgs.TargetEntityPtr));
+
+ return result == ItemActionResult.CancelVanilla ? 2 : 1;
+ }
+
+ internal static int HandleHurtEntity(IntPtr args, int sizeBytes)
+ {
+ if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf())
+ return 0;
+
+ ItemEntityInteractionNativeArgs nativeArgs = Marshal.PtrToStructure(args);
+
+ Item? item;
+ lock (s_lock)
+ {
+ s_items.TryGetValue(nativeArgs.ItemId, out item);
+ }
+
+ if (item == null)
+ return 0;
+
+ var result = item.OnHurtEntity(new ItemEntityInteractionContext(
+ nativeArgs.ItemId,
+ nativeArgs.ItemInstancePtr,
+ nativeArgs.PlayerPtr,
+ nativeArgs.PlayerSharedPtr,
+ nativeArgs.TargetEntityPtr));
+
+ return result == ItemActionResult.CancelVanilla ? 2 : 1;
+ }
+
+ internal static int HandleInventoryTick(IntPtr args, int sizeBytes)
+ {
+ if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf())
+ return 0;
+
+ ItemInventoryTickNativeArgs nativeArgs = Marshal.PtrToStructure(args);
+
+ Item? item;
+ lock (s_lock)
+ {
+ s_items.TryGetValue(nativeArgs.ItemId, out item);
+ }
+
+ if (item == null)
+ return 0;
+
+ item.OnInventoryTick(new ItemInventoryTickContext(
+ nativeArgs.ItemId,
+ nativeArgs.ItemInstancePtr,
+ nativeArgs.LevelPtr,
+ nativeArgs.OwnerEntityPtr,
+ nativeArgs.Slot,
+ nativeArgs.IsSelected != 0,
+ nativeArgs.IsClientSide != 0));
+ return 1;
+ }
+
+ internal static int HandleCraftedBy(IntPtr args, int sizeBytes)
+ {
+ if (args == IntPtr.Zero || sizeBytes < Marshal.SizeOf())
+ return 0;
+
+ ItemCraftedByNativeArgs nativeArgs = Marshal.PtrToStructure(args);
+
+ Item? item;
+ lock (s_lock)
+ {
+ s_items.TryGetValue(nativeArgs.ItemId, out item);
+ }
+
+ if (item == null)
+ return 0;
+
+ item.OnCraftedBy(new ItemCraftedByContext(
+ nativeArgs.ItemId,
+ nativeArgs.ItemInstancePtr,
+ nativeArgs.LevelPtr,
+ nativeArgs.PlayerPtr,
+ nativeArgs.PlayerSharedPtr,
+ nativeArgs.Amount,
+ nativeArgs.IsClientSide != 0));
+ return 1;
+ }
}
diff --git a/WeaveLoader.Core/WeaveLoaderCore.cs b/WeaveLoader.Core/WeaveLoaderCore.cs
index 3834b95..b78c53d 100644
--- a/WeaveLoader.Core/WeaveLoaderCore.cs
+++ b/WeaveLoader.Core/WeaveLoaderCore.cs
@@ -123,6 +123,71 @@ public static class WeaveLoaderCore
}
}
+ public static int OnItemUseOn(IntPtr args, int sizeBytes)
+ {
+ try
+ {
+ return ManagedItemDispatcher.HandleUseOn(args, sizeBytes);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"OnItemUseOn EXCEPTION: {ex}");
+ return 0;
+ }
+ }
+
+ public static int OnItemInteractEntity(IntPtr args, int sizeBytes)
+ {
+ try
+ {
+ return ManagedItemDispatcher.HandleInteractEntity(args, sizeBytes);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"OnItemInteractEntity EXCEPTION: {ex}");
+ return 0;
+ }
+ }
+
+ public static int OnItemHurtEntity(IntPtr args, int sizeBytes)
+ {
+ try
+ {
+ return ManagedItemDispatcher.HandleHurtEntity(args, sizeBytes);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"OnItemHurtEntity EXCEPTION: {ex}");
+ return 0;
+ }
+ }
+
+ public static int OnItemInventoryTick(IntPtr args, int sizeBytes)
+ {
+ try
+ {
+ return ManagedItemDispatcher.HandleInventoryTick(args, sizeBytes);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"OnItemInventoryTick EXCEPTION: {ex}");
+ return 0;
+ }
+ }
+
+ public static int OnItemCraftedBy(IntPtr args, int sizeBytes)
+ {
+ try
+ {
+ return ManagedItemDispatcher.HandleCraftedBy(args, sizeBytes);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"OnItemCraftedBy EXCEPTION: {ex}");
+ return 0;
+ }
+ }
+
public static int OnBlockPlace(IntPtr args, int sizeBytes)
{
try
@@ -162,6 +227,136 @@ public static class WeaveLoaderCore
}
}
+ public static int OnBlockUse(IntPtr args, int sizeBytes)
+ {
+ try
+ {
+ return ManagedBlockDispatcher.HandleUse(args, sizeBytes);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"OnBlockUse EXCEPTION: {ex}");
+ return 0;
+ }
+ }
+
+ public static int OnBlockStepOn(IntPtr args, int sizeBytes)
+ {
+ try
+ {
+ return ManagedBlockDispatcher.HandleStepOn(args, sizeBytes);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"OnBlockStepOn EXCEPTION: {ex}");
+ return 0;
+ }
+ }
+
+ public static int OnBlockEntityInsideTile(IntPtr args, int sizeBytes)
+ {
+ try
+ {
+ return ManagedBlockDispatcher.HandleEntityInsideTile(args, sizeBytes);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"OnBlockEntityInsideTile EXCEPTION: {ex}");
+ return 0;
+ }
+ }
+
+ public static int OnBlockFallOn(IntPtr args, int sizeBytes)
+ {
+ try
+ {
+ return ManagedBlockDispatcher.HandleFallOn(args, sizeBytes);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"OnBlockFallOn EXCEPTION: {ex}");
+ return 0;
+ }
+ }
+
+ public static int OnBlockRemoving(IntPtr args, int sizeBytes)
+ {
+ try
+ {
+ return ManagedBlockDispatcher.HandleRemoving(args, sizeBytes);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"OnBlockRemoving EXCEPTION: {ex}");
+ return 0;
+ }
+ }
+
+ public static int OnBlockRemoved(IntPtr args, int sizeBytes)
+ {
+ try
+ {
+ return ManagedBlockDispatcher.HandleRemoved(args, sizeBytes);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"OnBlockRemoved EXCEPTION: {ex}");
+ return 0;
+ }
+ }
+
+ public static int OnBlockDestroyed(IntPtr args, int sizeBytes)
+ {
+ try
+ {
+ return ManagedBlockDispatcher.HandleDestroyed(args, sizeBytes);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"OnBlockDestroyed EXCEPTION: {ex}");
+ return 0;
+ }
+ }
+
+ public static int OnBlockPlayerDestroy(IntPtr args, int sizeBytes)
+ {
+ try
+ {
+ return ManagedBlockDispatcher.HandlePlayerDestroy(args, sizeBytes);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"OnBlockPlayerDestroy EXCEPTION: {ex}");
+ return 0;
+ }
+ }
+
+ public static int OnBlockPlayerWillDestroy(IntPtr args, int sizeBytes)
+ {
+ try
+ {
+ return ManagedBlockDispatcher.HandlePlayerWillDestroy(args, sizeBytes);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"OnBlockPlayerWillDestroy EXCEPTION: {ex}");
+ return 0;
+ }
+ }
+
+ public static int OnBlockPlacedBy(IntPtr args, int sizeBytes)
+ {
+ try
+ {
+ return ManagedBlockDispatcher.HandlePlacedBy(args, sizeBytes);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error($"OnBlockPlacedBy EXCEPTION: {ex}");
+ return 0;
+ }
+ }
+
[StructLayout(LayoutKind.Sequential)]
private struct WorldLoadedNativeArgs
{
diff --git a/WeaveLoaderRuntime/src/DotNetHost.cpp b/WeaveLoaderRuntime/src/DotNetHost.cpp
index b87ff5c..04f426f 100644
--- a/WeaveLoaderRuntime/src/DotNetHost.cpp
+++ b/WeaveLoaderRuntime/src/DotNetHost.cpp
@@ -24,9 +24,24 @@ static managed_entry_fn fn_Tick = nullptr;
static managed_entry_fn fn_Shutdown = nullptr;
static managed_entry_fn fn_ItemMineBlock = nullptr;
static managed_entry_fn fn_ItemUse = nullptr;
+static managed_entry_fn fn_ItemUseOn = nullptr;
+static managed_entry_fn fn_ItemInteractEntity = nullptr;
+static managed_entry_fn fn_ItemHurtEntity = nullptr;
+static managed_entry_fn fn_ItemInventoryTick = nullptr;
+static managed_entry_fn fn_ItemCraftedBy = nullptr;
static managed_entry_fn fn_BlockOnPlace = nullptr;
static managed_entry_fn fn_BlockNeighborChanged = nullptr;
static managed_entry_fn fn_BlockTick = nullptr;
+static managed_entry_fn fn_BlockUse = nullptr;
+static managed_entry_fn fn_BlockStepOn = nullptr;
+static managed_entry_fn fn_BlockEntityInsideTile = nullptr;
+static managed_entry_fn fn_BlockFallOn = nullptr;
+static managed_entry_fn fn_BlockRemoving = nullptr;
+static managed_entry_fn fn_BlockRemoved = nullptr;
+static managed_entry_fn fn_BlockDestroyed = nullptr;
+static managed_entry_fn fn_BlockPlayerDestroy = nullptr;
+static managed_entry_fn fn_BlockPlayerWillDestroy = nullptr;
+static managed_entry_fn fn_BlockPlacedBy = nullptr;
static managed_entry_fn fn_EntitySummoned = nullptr;
static bool LoadHostfxr()
@@ -185,9 +200,24 @@ bool DotNetHost::Initialize()
ok &= resolve(L"Shutdown", &fn_Shutdown);
ok &= resolve(L"OnItemMineBlock", &fn_ItemMineBlock);
ok &= resolve(L"OnItemUse", &fn_ItemUse);
+ ok &= resolve(L"OnItemUseOn", &fn_ItemUseOn);
+ ok &= resolve(L"OnItemInteractEntity", &fn_ItemInteractEntity);
+ ok &= resolve(L"OnItemHurtEntity", &fn_ItemHurtEntity);
+ ok &= resolve(L"OnItemInventoryTick", &fn_ItemInventoryTick);
+ ok &= resolve(L"OnItemCraftedBy", &fn_ItemCraftedBy);
ok &= resolve(L"OnBlockPlace", &fn_BlockOnPlace);
ok &= resolve(L"OnBlockNeighborChanged", &fn_BlockNeighborChanged);
ok &= resolve(L"OnBlockTick", &fn_BlockTick);
+ ok &= resolve(L"OnBlockUse", &fn_BlockUse);
+ ok &= resolve(L"OnBlockStepOn", &fn_BlockStepOn);
+ ok &= resolve(L"OnBlockEntityInsideTile", &fn_BlockEntityInsideTile);
+ ok &= resolve(L"OnBlockFallOn", &fn_BlockFallOn);
+ ok &= resolve(L"OnBlockRemoving", &fn_BlockRemoving);
+ ok &= resolve(L"OnBlockRemoved", &fn_BlockRemoved);
+ ok &= resolve(L"OnBlockDestroyed", &fn_BlockDestroyed);
+ ok &= resolve(L"OnBlockPlayerDestroy", &fn_BlockPlayerDestroy);
+ ok &= resolve(L"OnBlockPlayerWillDestroy", &fn_BlockPlayerWillDestroy);
+ ok &= resolve(L"OnBlockPlacedBy", &fn_BlockPlacedBy);
ok &= resolve(L"OnEntitySummoned", &fn_EntitySummoned);
if (!ok)
@@ -253,6 +283,41 @@ int DotNetHost::CallItemUse(const void* args, int sizeBytes)
return fn_ItemUse(const_cast(args), sizeBytes);
}
+int DotNetHost::CallItemUseOn(const void* args, int sizeBytes)
+{
+ if (!fn_ItemUseOn || !args || sizeBytes <= 0)
+ return 0;
+ return fn_ItemUseOn(const_cast(args), sizeBytes);
+}
+
+int DotNetHost::CallItemInteractEntity(const void* args, int sizeBytes)
+{
+ if (!fn_ItemInteractEntity || !args || sizeBytes <= 0)
+ return 0;
+ return fn_ItemInteractEntity(const_cast(args), sizeBytes);
+}
+
+int DotNetHost::CallItemHurtEntity(const void* args, int sizeBytes)
+{
+ if (!fn_ItemHurtEntity || !args || sizeBytes <= 0)
+ return 0;
+ return fn_ItemHurtEntity(const_cast(args), sizeBytes);
+}
+
+int DotNetHost::CallItemInventoryTick(const void* args, int sizeBytes)
+{
+ if (!fn_ItemInventoryTick || !args || sizeBytes <= 0)
+ return 0;
+ return fn_ItemInventoryTick(const_cast(args), sizeBytes);
+}
+
+int DotNetHost::CallItemCraftedBy(const void* args, int sizeBytes)
+{
+ if (!fn_ItemCraftedBy || !args || sizeBytes <= 0)
+ return 0;
+ return fn_ItemCraftedBy(const_cast(args), sizeBytes);
+}
+
int DotNetHost::CallBlockOnPlace(const void* args, int sizeBytes)
{
if (!fn_BlockOnPlace || !args || sizeBytes <= 0)
@@ -274,6 +339,77 @@ int DotNetHost::CallBlockTick(const void* args, int sizeBytes)
return fn_BlockTick(const_cast(args), sizeBytes);
}
+int DotNetHost::CallBlockUse(const void* args, int sizeBytes)
+{
+ if (!fn_BlockUse || !args || sizeBytes <= 0)
+ return 0;
+ return fn_BlockUse(const_cast(args), sizeBytes);
+}
+
+int DotNetHost::CallBlockStepOn(const void* args, int sizeBytes)
+{
+ if (!fn_BlockStepOn || !args || sizeBytes <= 0)
+ return 0;
+ return fn_BlockStepOn(const_cast(args), sizeBytes);
+}
+
+int DotNetHost::CallBlockEntityInsideTile(const void* args, int sizeBytes)
+{
+ if (!fn_BlockEntityInsideTile || !args || sizeBytes <= 0)
+ return 0;
+ return fn_BlockEntityInsideTile(const_cast(args), sizeBytes);
+}
+
+int DotNetHost::CallBlockFallOn(const void* args, int sizeBytes)
+{
+ if (!fn_BlockFallOn || !args || sizeBytes <= 0)
+ return 0;
+ return fn_BlockFallOn(const_cast(args), sizeBytes);
+}
+
+int DotNetHost::CallBlockRemoving(const void* args, int sizeBytes)
+{
+ if (!fn_BlockRemoving || !args || sizeBytes <= 0)
+ return 0;
+ return fn_BlockRemoving(const_cast(args), sizeBytes);
+}
+
+int DotNetHost::CallBlockRemoved(const void* args, int sizeBytes)
+{
+ if (!fn_BlockRemoved || !args || sizeBytes <= 0)
+ return 0;
+ return fn_BlockRemoved(const_cast(args), sizeBytes);
+}
+
+int DotNetHost::CallBlockDestroyed(const void* args, int sizeBytes)
+{
+ if (!fn_BlockDestroyed || !args || sizeBytes <= 0)
+ return 0;
+ return fn_BlockDestroyed(const_cast(args), sizeBytes);
+}
+
+int DotNetHost::CallBlockPlayerDestroy(const void* args, int sizeBytes)
+{
+ if (!fn_BlockPlayerDestroy || !args || sizeBytes <= 0)
+ return 0;
+ return fn_BlockPlayerDestroy(const_cast(args), sizeBytes);
+}
+
+int DotNetHost::CallBlockPlayerWillDestroy(const void* args, int sizeBytes)
+{
+ if (!fn_BlockPlayerWillDestroy || !args || sizeBytes <= 0)
+ return 0;
+ return fn_BlockPlayerWillDestroy(const_cast(args), sizeBytes);
+}
+
+int DotNetHost::CallBlockPlacedBy(const void* args, int sizeBytes)
+{
+ if (!fn_BlockPlacedBy || !args || sizeBytes <= 0)
+ return 0;
+ return fn_BlockPlacedBy(const_cast(args), sizeBytes);
+}
+
+
void DotNetHost::CallEntitySummoned(int entityNumericId, float x, float y, float z)
{
if (!fn_EntitySummoned)
diff --git a/WeaveLoaderRuntime/src/DotNetHost.h b/WeaveLoaderRuntime/src/DotNetHost.h
index 7afca6c..40471e4 100644
--- a/WeaveLoaderRuntime/src/DotNetHost.h
+++ b/WeaveLoaderRuntime/src/DotNetHost.h
@@ -17,8 +17,23 @@ namespace DotNetHost
void CallShutdown();
int CallItemMineBlock(const void* args, int sizeBytes);
int CallItemUse(const void* args, int sizeBytes);
+ int CallItemUseOn(const void* args, int sizeBytes);
+ int CallItemInteractEntity(const void* args, int sizeBytes);
+ int CallItemHurtEntity(const void* args, int sizeBytes);
+ int CallItemInventoryTick(const void* args, int sizeBytes);
+ int CallItemCraftedBy(const void* args, int sizeBytes);
int CallBlockOnPlace(const void* args, int sizeBytes);
int CallBlockNeighborChanged(const void* args, int sizeBytes);
int CallBlockTick(const void* args, int sizeBytes);
+ int CallBlockUse(const void* args, int sizeBytes);
+ int CallBlockStepOn(const void* args, int sizeBytes);
+ int CallBlockEntityInsideTile(const void* args, int sizeBytes);
+ int CallBlockFallOn(const void* args, int sizeBytes);
+ int CallBlockRemoving(const void* args, int sizeBytes);
+ int CallBlockRemoved(const void* args, int sizeBytes);
+ int CallBlockDestroyed(const void* args, int sizeBytes);
+ int CallBlockPlayerDestroy(const void* args, int sizeBytes);
+ int CallBlockPlayerWillDestroy(const void* args, int sizeBytes);
+ int CallBlockPlacedBy(const void* args, int sizeBytes);
void CallEntitySummoned(int entityNumericId, float x, float y, float z);
}
diff --git a/WeaveLoaderRuntime/src/GameHooks.cpp b/WeaveLoaderRuntime/src/GameHooks.cpp
index 1e8606c..4f80b19 100644
--- a/WeaveLoaderRuntime/src/GameHooks.cpp
+++ b/WeaveLoaderRuntime/src/GameHooks.cpp
@@ -14,6 +14,7 @@
#include "WorldIdRemap.h"
#include "ModelRegistry.h"
#include "ItemRenderRegistry.h"
+#include "PdbParser.h"
#include
#include
#include
@@ -33,6 +34,7 @@
#include
#include
#include
+#include
namespace GameHooks
{
@@ -66,6 +68,10 @@ namespace GameHooks
ItemInstanceUseOn_fn Original_ItemInstanceUseOn = nullptr;
ItemInstanceSave_fn Original_ItemInstanceSave = nullptr;
ItemInstanceLoad_fn Original_ItemInstanceLoad = nullptr;
+ ItemInstanceInventoryTick_fn Original_ItemInstanceInventoryTick = nullptr;
+ ItemInstanceOnCraftedBy_fn Original_ItemInstanceOnCraftedBy = nullptr;
+ ItemInstanceInteractEnemy_fn Original_ItemInstanceInteractEnemy = nullptr;
+ ItemInstanceHurtEnemy_fn Original_ItemInstanceHurtEnemy = nullptr;
ItemMineBlock_fn Original_ItemMineBlock = nullptr;
ItemMineBlock_fn Original_DiggerItemMineBlock = nullptr;
PickaxeGetDestroySpeed_fn Original_PickaxeItemGetDestroySpeed = nullptr;
@@ -75,6 +81,18 @@ namespace GameHooks
TileOnPlace_fn Original_TileOnPlace = nullptr;
TileNeighborChanged_fn Original_TileNeighborChanged = nullptr;
TileTick_fn Original_TileTick = nullptr;
+ TileUse_fn Original_TileUse = nullptr;
+ TileStepOn_fn Original_TileStepOn = nullptr;
+ TileEntityInside_fn Original_TileEntityInside = nullptr;
+ TileFallOn_fn Original_TileFallOn = nullptr;
+ TileOnRemoving_fn Original_TileOnRemoving = nullptr;
+ TileOnRemove_fn Original_TileOnRemove = nullptr;
+ TileDestroy_fn Original_TileDestroy = nullptr;
+ TilePlayerDestroy_fn Original_TilePlayerDestroy = nullptr;
+ TilePlayerWillDestroy_fn Original_TilePlayerWillDestroy = nullptr;
+ TileSetPlacedBy_fn Original_TileSetPlacedBy = nullptr;
+ TileSharedAction_fn Original_TileSharedAction = nullptr;
+ TileSharedLifecycle_fn Original_TileSharedLifecycle = nullptr;
LevelSetTileAndDataDispatch_fn Original_LevelSetTileAndData = nullptr;
LevelSetDataDispatch_fn Original_LevelSetData = nullptr;
LevelUpdateNeighborsAtDispatch_fn Original_LevelUpdateNeighborsAt = nullptr;
@@ -104,6 +122,8 @@ namespace GameHooks
ServerPlayerGameModeUseItemOn_fn Original_ServerPlayerGameModeUseItemOn = nullptr;
MultiPlayerGameModeUseItemOn_fn Original_MultiPlayerGameModeUseItemOn = nullptr;
MinecraftSetLevel_fn Original_MinecraftSetLevel = nullptr;
+ EntityPlayStepSound_fn Original_EntityPlayStepSound = nullptr;
+ EntityCheckInsideTiles_fn Original_EntityCheckInsideTiles = nullptr;
TexturesBindTextureResource_fn Original_TexturesBindTextureResource = nullptr;
TexturesLoadTextureByName_fn Original_TexturesLoadTextureByName = nullptr;
TexturesLoadTextureByIndex_fn Original_TexturesLoadTextureByIndex = nullptr;
@@ -257,6 +277,10 @@ namespace GameHooks
static int s_tileRendererLevelOffset = -1;
static std::atomic s_entityYawOffsetTried{false};
static int s_entityYawOffset = -1;
+ static std::atomic s_entityLevelOffsetTried{false};
+ static int s_entityLevelOffset = -1;
+ static std::atomic s_entityBbOffsetTried{false};
+ static int s_entityBbOffset = -1;
static int s_rotationLogCount = 0;
static bool ReadFileToString(const char* path, std::string& out)
@@ -414,6 +438,67 @@ namespace GameHooks
return true;
}
+ static bool TryResolveEntityLevelOffset()
+ {
+ bool expected = false;
+ if (!s_entityLevelOffsetTried.compare_exchange_strong(expected, true))
+ return s_entityLevelOffset >= 0;
+
+ const char* baseDir = LogUtil::GetBaseDir();
+ if (!baseDir || baseDir[0] == '\0')
+ return false;
+
+ std::string json;
+ std::string path = std::string(baseDir) + "metadata\\offsets.json";
+ if (!ReadFileToString(path.c_str(), json))
+ {
+ path = std::string(baseDir) + "offsets.json";
+ if (!ReadFileToString(path.c_str(), json))
+ return false;
+ }
+
+ int offset = -1;
+ if (!ExtractOffsetForField(json, "Entity", "level", offset))
+ return false;
+ if (offset <= 0)
+ return false;
+
+ s_entityLevelOffset = offset;
+ LogUtil::Log("[WeaveLoader] ModelRegistry: Entity.level offset = 0x%X", s_entityLevelOffset);
+ return true;
+ }
+
+ static bool TryResolveEntityBbOffset()
+ {
+ bool expected = false;
+ if (!s_entityBbOffsetTried.compare_exchange_strong(expected, true))
+ return s_entityBbOffset >= 0;
+
+ const char* baseDir = LogUtil::GetBaseDir();
+ if (!baseDir || baseDir[0] == '\0')
+ return false;
+
+ std::string json;
+ std::string path = std::string(baseDir) + "metadata\\offsets.json";
+ if (!ReadFileToString(path.c_str(), json))
+ {
+ path = std::string(baseDir) + "offsets.json";
+ if (!ReadFileToString(path.c_str(), json))
+ return false;
+ }
+
+ int offset = -1;
+ if (!ExtractOffsetForField(json, "Entity", "bb", offset))
+ return false;
+ if (offset <= 0)
+ return false;
+
+ s_entityBbOffset = offset;
+ LogUtil::Log("[WeaveLoader] ModelRegistry: Entity.bb offset = 0x%X", s_entityBbOffset);
+ return true;
+ }
+
+
int GetTileId(void* tilePtr)
{
if (!tilePtr)
@@ -449,6 +534,52 @@ namespace GameHooks
return true;
}
+ static bool TryGetEntityLevel(void* entityPtr, void*& outLevelPtr)
+ {
+ outLevelPtr = nullptr;
+ if (!entityPtr)
+ return false;
+ if (s_entityLevelOffset < 0 && !TryResolveEntityLevelOffset())
+ return false;
+
+ const char* base = reinterpret_cast(entityPtr) + s_entityLevelOffset;
+ if (!IsReadableRange(base, sizeof(void*)))
+ return false;
+
+ outLevelPtr = *reinterpret_cast(base);
+ return outLevelPtr != nullptr;
+ }
+
+ static bool TryGetEntityAABB(void* entityPtr, AABBRaw& outBox)
+ {
+ if (!entityPtr)
+ return false;
+ if (s_entityBbOffset < 0 && !TryResolveEntityBbOffset())
+ return false;
+
+ const char* base = reinterpret_cast(entityPtr) + s_entityBbOffset;
+ if (!IsReadableRange(base, sizeof(void*)))
+ return false;
+
+ void* boxPtr = *reinterpret_cast(base);
+ if (!IsReadableRange(boxPtr, sizeof(AABBRaw)))
+ return false;
+
+ outBox = *reinterpret_cast(boxPtr);
+ return true;
+ }
+
+ static uint64_t MakeEntityInsideKey(void* levelPtr, void* entityPtr, int x, int y, int z)
+ {
+ uint64_t h = static_cast(reinterpret_cast(entityPtr));
+ h ^= static_cast(reinterpret_cast(levelPtr)) * 0x9E3779B97F4A7C15ULL;
+ h ^= static_cast(static_cast(x) * 73856093LL);
+ h ^= static_cast(static_cast(y) * 19349663LL);
+ h ^= static_cast(static_cast(z) * 83492791LL);
+ return h;
+ }
+
+
static const char* FacingFromDoorDir(int dir)
{
switch (dir & 3)
@@ -651,7 +782,13 @@ namespace GameHooks
static thread_local void* s_activeUseLevel = nullptr;
static thread_local void* s_lastUsePlayer = nullptr;
static thread_local void* s_lastUseLevel = nullptr;
+ static thread_local void* s_lastUseItemInstance = nullptr;
static thread_local ULONGLONG s_lastUseTimeMs = 0;
+ static thread_local void* s_lastPlacedLevel = nullptr;
+ static thread_local int s_lastPlacedX = 0;
+ static thread_local int s_lastPlacedY = 0;
+ static thread_local int s_lastPlacedZ = 0;
+ static thread_local ULONGLONG s_lastPlacedTimeMs = 0;
static LevelAddEntity_fn s_levelAddEntity = nullptr;
static EntityIONewById_fn s_entityIoNewById = nullptr;
static EntityMoveTo_fn s_entityMoveTo = nullptr;
@@ -720,6 +857,9 @@ namespace GameHooks
static int s_outOfWorldGuardLogCount = 0;
static int s_pendingServerUseItemId = -1;
static LevelGetTile_fn s_levelGetTile = nullptr;
+ static std::atomic s_tickCounter{0};
+ static std::unordered_map s_entityInsideSeen;
+ static uint32_t s_entityInsideCleanupTick = 0;
struct ManagedScheduledTick
{
@@ -1211,13 +1351,60 @@ namespace GameHooks
struct UseItemNativeArgs
{
int itemId;
- int isTestUseOnly;
int isClientSide;
void* itemInstancePtr;
void* playerPtr;
void* playerSharedPtr;
};
+ struct UseOnItemNativeArgs
+ {
+ int itemId;
+ int isClientSide;
+ void* itemInstancePtr;
+ void* playerPtr;
+ void* playerSharedPtr;
+ void* levelPtr;
+ int x;
+ int y;
+ int z;
+ int face;
+ float clickX;
+ float clickY;
+ float clickZ;
+ };
+
+ struct ItemEntityInteractionNativeArgs
+ {
+ int itemId;
+ void* itemInstancePtr;
+ void* playerPtr;
+ void* playerSharedPtr;
+ void* targetEntityPtr;
+ };
+
+ struct ItemInventoryTickNativeArgs
+ {
+ int itemId;
+ void* itemInstancePtr;
+ void* levelPtr;
+ void* ownerEntityPtr;
+ int slot;
+ int isSelected;
+ int isClientSide;
+ };
+
+ struct ItemCraftedByNativeArgs
+ {
+ int itemId;
+ void* itemInstancePtr;
+ void* levelPtr;
+ void* playerPtr;
+ void* playerSharedPtr;
+ int amount;
+ int isClientSide;
+ };
+
struct ItemRenderNativeArgs
{
int itemId;
@@ -1231,10 +1418,86 @@ namespace GameHooks
float alpha;
};
+ struct BlockUpdateNativeArgs
+ {
+ int blockId;
+ int isClientSide;
+ void* levelPtr;
+ int x;
+ int y;
+ int z;
+ };
+
+ struct BlockUseNativeArgs
+ {
+ BlockUpdateNativeArgs block;
+ void* playerPtr;
+ int face;
+ float clickX;
+ float clickY;
+ float clickZ;
+ int soundOnly;
+ };
+
+
+ struct BlockEntityNativeArgs
+ {
+ BlockUpdateNativeArgs block;
+ void* entityPtr;
+ };
+
+ struct BlockFallNativeArgs
+ {
+ BlockUpdateNativeArgs block;
+ void* entityPtr;
+ float fallDistance;
+ };
+
+ struct BlockRemovingNativeArgs
+ {
+ BlockUpdateNativeArgs block;
+ int blockData;
+ };
+
+ struct BlockRemoveNativeArgs
+ {
+ BlockUpdateNativeArgs block;
+ int removedBlockId;
+ int removedBlockData;
+ };
+
+ struct BlockDestroyNativeArgs
+ {
+ BlockUpdateNativeArgs block;
+ int blockData;
+ };
+
+ struct BlockPlayerDestroyNativeArgs
+ {
+ BlockUpdateNativeArgs block;
+ void* playerPtr;
+ int blockData;
+ };
+
+ struct BlockPlayerWillDestroyNativeArgs
+ {
+ BlockUpdateNativeArgs block;
+ void* playerPtr;
+ int blockData;
+ };
+
+ struct BlockPlacedByNativeArgs
+ {
+ BlockUpdateNativeArgs block;
+ void* placerPtr;
+ void* itemInstancePtr;
+ };
+
static bool IsFireballFamilyEntityId(int entityNumericId);
static bool LooksLikeEntityPtr(void* candidate);
static void* DecodeItemInstancePtrFromSharedArg(void* sharedArg);
static void* DecodePlayerPtrFromSharedArg(void* sharedArg);
+ static bool WasRecentlyPlaced(void* levelPtr, int x, int y, int z);
static bool TryReadItemId(void* itemInstancePtr, int& outItemId)
{
@@ -1407,6 +1670,9 @@ namespace GameHooks
static int TryDispatchUseItemFromSharedItemArg(void* itemInstanceSharedPtr, void* playerSharedPtr, bool bTestUseOnly, const char* sourceTag)
{
+ if (bTestUseOnly)
+ return 0;
+
void* itemInstancePtr = DecodeItemInstancePtrFromSharedArg(itemInstanceSharedPtr);
void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr);
if (!itemInstancePtr)
@@ -1424,7 +1690,7 @@ namespace GameHooks
*reinterpret_cast(static_cast(s_activeUseLevel) + kLevelIsClientSideOffset) ? 1 : 0;
}
- UseItemNativeArgs args{ itemId, bTestUseOnly ? 1 : 0, isClientSide, itemInstancePtr, playerPtr, playerSharedPtr };
+ UseItemNativeArgs args{ itemId, isClientSide, itemInstancePtr, playerPtr, playerSharedPtr };
return DotNetHost::CallItemUse(&args, sizeof(args));
}
@@ -1945,15 +2211,6 @@ namespace GameHooks
if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool)))
isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0;
- struct BlockUpdateNativeArgs
- {
- int blockId;
- int isClientSide;
- void* levelPtr;
- int x;
- int y;
- int z;
- };
struct BlockNeighborChangedNativeArgs
{
BlockUpdateNativeArgs block;
@@ -1988,15 +2245,6 @@ namespace GameHooks
if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool)))
isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0;
- struct BlockUpdateNativeArgs
- {
- int blockId;
- int isClientSide;
- void* levelPtr;
- int x;
- int y;
- int z;
- };
struct BlockNeighborChangedNativeArgs
{
BlockUpdateNativeArgs block;
@@ -2022,6 +2270,104 @@ namespace GameHooks
}
}
+ enum class TileSharedActionKind
+ {
+ Unknown = 0,
+ StepOn,
+ EntityInside,
+ FallOn
+ };
+
+ enum class TileSharedLifecycleKind
+ {
+ Unknown = 0,
+ Destroyed
+ };
+
+ static std::unordered_map s_tileSharedActionCache;
+ static std::unordered_map s_tileSharedLifecycleCache;
+ static std::mutex s_tileSharedActionMutex;
+ static std::mutex s_tileSharedLifecycleMutex;
+ static uintptr_t s_moduleBase = 0;
+
+ static bool TryResolveCallerName(void* returnAddr, char* outName, size_t nameSize, uint32_t* outOffset)
+ {
+ if (!returnAddr)
+ return false;
+ if (!s_moduleBase)
+ s_moduleBase = reinterpret_cast(GetModuleHandleA(nullptr));
+ if (!s_moduleBase)
+ return false;
+
+ const uintptr_t addr = reinterpret_cast(returnAddr);
+ if (addr < s_moduleBase)
+ return false;
+
+ const uint32_t rva = static_cast(addr - s_moduleBase);
+ return PdbParser::FindNameByRVA(rva, outName, nameSize, outOffset);
+ }
+
+ static TileSharedActionKind ResolveTileSharedActionKind(void* returnAddr)
+ {
+ if (!returnAddr)
+ return TileSharedActionKind::Unknown;
+
+ {
+ std::lock_guard guard(s_tileSharedActionMutex);
+ auto it = s_tileSharedActionCache.find(returnAddr);
+ if (it != s_tileSharedActionCache.end())
+ return it->second;
+ }
+
+ TileSharedActionKind kind = TileSharedActionKind::Unknown;
+ char name[128] = {};
+ uint32_t offset = 0;
+ if (TryResolveCallerName(returnAddr, name, sizeof(name), &offset))
+ {
+ if (std::strstr(name, "Entity::move") != nullptr)
+ kind = TileSharedActionKind::StepOn;
+ else if (std::strstr(name, "Entity::checkInsideTiles") != nullptr)
+ kind = TileSharedActionKind::EntityInside;
+ else if (std::strstr(name, "LivingEntity::checkFallDamage") != nullptr)
+ kind = TileSharedActionKind::FallOn;
+ }
+
+ {
+ std::lock_guard guard(s_tileSharedActionMutex);
+ s_tileSharedActionCache[returnAddr] = kind;
+ }
+ return kind;
+ }
+
+ static TileSharedLifecycleKind ResolveTileSharedLifecycleKind(void* returnAddr)
+ {
+ if (!returnAddr)
+ return TileSharedLifecycleKind::Unknown;
+
+ {
+ std::lock_guard guard(s_tileSharedLifecycleMutex);
+ auto it = s_tileSharedLifecycleCache.find(returnAddr);
+ if (it != s_tileSharedLifecycleCache.end())
+ return it->second;
+ }
+
+ TileSharedLifecycleKind kind = TileSharedLifecycleKind::Unknown;
+ char name[128] = {};
+ uint32_t offset = 0;
+ if (TryResolveCallerName(returnAddr, name, sizeof(name), &offset))
+ {
+ if (std::strstr(name, "ServerPlayerGameMode::destroyBlock") != nullptr ||
+ std::strstr(name, "MultiPlayerGameMode::destroyBlock") != nullptr)
+ kind = TileSharedLifecycleKind::Destroyed;
+ }
+
+ {
+ std::lock_guard guard(s_tileSharedLifecycleMutex);
+ s_tileSharedLifecycleCache[returnAddr] = kind;
+ }
+ return kind;
+ }
+
void __fastcall Hooked_TileOnPlace(void* thisPtr, void* level, int x, int y, int z)
{
if (Original_TileOnPlace)
@@ -2043,10 +2389,385 @@ namespace GameHooks
DispatchManagedBlockUpdate(thisPtr, level, x, y, z, 2, 0);
}
- static bool TryGetPlacementPlayer(void* levelPtr, void*& outPlayerPtr)
+ bool __fastcall Hooked_TileUse(void* thisPtr, void* level, int x, int y, int z, void* playerSharedPtr, int face, float clickX, float clickY, float clickZ, bool soundOnly)
{
- (void)levelPtr;
- if (!s_lastUsePlayer)
+ const int blockId = TryReadTileId(thisPtr);
+ if (ManagedBlockRegistry::IsManaged(blockId))
+ {
+ int isClientSide = 0;
+ if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool)))
+ isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0;
+
+ void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr);
+ BlockUseNativeArgs args{};
+ args.block = { blockId, isClientSide, level, x, y, z };
+ args.playerPtr = playerPtr;
+ args.face = face;
+ args.clickX = clickX;
+ args.clickY = clickY;
+ args.clickZ = clickZ;
+ args.soundOnly = soundOnly ? 1 : 0;
+
+ int action = DotNetHost::CallBlockUse(&args, sizeof(args));
+ if (action == 2)
+ return true;
+ }
+
+ if (Original_TileUse)
+ return Original_TileUse(thisPtr, level, x, y, z, playerSharedPtr, face, clickX, clickY, clickZ, soundOnly);
+ return false;
+ }
+
+ void __fastcall Hooked_TileSharedAction(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr, float fallDistance)
+ {
+ const TileSharedActionKind kind = ResolveTileSharedActionKind(_ReturnAddress());
+ if (kind == TileSharedActionKind::Unknown)
+ {
+ if (Original_TileSharedAction)
+ Original_TileSharedAction(thisPtr, level, x, y, z, entitySharedPtr, fallDistance);
+ return;
+ }
+
+ const int blockId = TryReadTileId(thisPtr);
+ if (!ManagedBlockRegistry::IsManaged(blockId))
+ {
+ if (Original_TileSharedAction)
+ Original_TileSharedAction(thisPtr, level, x, y, z, entitySharedPtr, fallDistance);
+ return;
+ }
+
+ int isClientSide = 0;
+ if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool)))
+ isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0;
+
+ if (Original_TileSharedAction)
+ Original_TileSharedAction(thisPtr, level, x, y, z, entitySharedPtr, fallDistance);
+
+ if (kind == TileSharedActionKind::StepOn)
+ {
+ void* entityPtr = DecodeEntityPtrFromSharedArg(entitySharedPtr);
+ BlockEntityNativeArgs args{};
+ args.block = { blockId, isClientSide, level, x, y, z };
+ args.entityPtr = entityPtr;
+ DotNetHost::CallBlockStepOn(&args, sizeof(args));
+ }
+ else if (kind == TileSharedActionKind::FallOn)
+ {
+ void* entityPtr = DecodeEntityPtrFromSharedArg(entitySharedPtr);
+ BlockFallNativeArgs args{};
+ args.block = { blockId, isClientSide, level, x, y, z };
+ args.entityPtr = entityPtr;
+ args.fallDistance = fallDistance;
+ DotNetHost::CallBlockFallOn(&args, sizeof(args));
+ }
+ }
+
+ void __fastcall Hooked_TileSharedLifecycle(void* thisPtr, void* level, int x, int y, int z, void* arg5, void* arg6)
+ {
+ if (Original_TileSharedLifecycle)
+ Original_TileSharedLifecycle(thisPtr, level, x, y, z, arg5, arg6);
+
+ TileSharedLifecycleKind kind = ResolveTileSharedLifecycleKind(_ReturnAddress());
+
+ const int blockId = TryReadTileId(thisPtr);
+ if (!ManagedBlockRegistry::IsManaged(blockId))
+ return;
+
+ int isClientSide = 0;
+ if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool)))
+ isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0;
+
+ if (kind == TileSharedLifecycleKind::Unknown)
+ return;
+
+ if (kind == TileSharedLifecycleKind::Destroyed)
+ {
+ BlockDestroyNativeArgs args{};
+ args.block = { blockId, isClientSide, level, x, y, z };
+ args.blockData = static_cast(reinterpret_cast(arg5));
+ DotNetHost::CallBlockDestroyed(&args, sizeof(args));
+ }
+ }
+
+ void __fastcall Hooked_EntityPlayStepSound(void* thisPtr, int xt, int yt, int zt, int t)
+ {
+ if (Original_EntityPlayStepSound)
+ Original_EntityPlayStepSound(thisPtr, xt, yt, zt, t);
+
+ if (t <= 0 || !ManagedBlockRegistry::IsManaged(t))
+ return;
+
+ void* level = nullptr;
+ if (!TryGetEntityLevel(thisPtr, level))
+ return;
+
+ int isClientSide = 0;
+ if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool)))
+ isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0;
+
+ BlockEntityNativeArgs args{};
+ args.block = { t, isClientSide, level, xt, yt, zt };
+ args.entityPtr = thisPtr;
+ DotNetHost::CallBlockStepOn(&args, sizeof(args));
+ }
+
+ void __fastcall Hooked_EntityCheckInsideTiles(void* thisPtr)
+ {
+ if (Original_EntityCheckInsideTiles)
+ Original_EntityCheckInsideTiles(thisPtr);
+
+ if (!thisPtr || !s_levelGetTile)
+ return;
+
+ void* level = nullptr;
+ if (!TryGetEntityLevel(thisPtr, level))
+ return;
+
+ AABBRaw box{};
+ if (!TryGetEntityAABB(thisPtr, box))
+ return;
+
+ int isClientSide = 0;
+ if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool)))
+ isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0;
+
+ const double eps = 1e-7;
+ int minX = static_cast(std::floor(box.x0 + eps));
+ int minY = static_cast(std::floor(box.y0 + eps));
+ int minZ = static_cast(std::floor(box.z0 + eps));
+ int maxX = static_cast(std::floor(box.x1 - eps));
+ int maxY = static_cast(std::floor(box.y1 - eps));
+ int maxZ = static_cast(std::floor(box.z1 - eps));
+
+ if (maxY < 0 || minY > 255)
+ return;
+ minY = (std::max)(minY, 0);
+ maxY = (std::min)(maxY, 255);
+
+ const uint32_t tick = s_tickCounter.load(std::memory_order_relaxed);
+ if (tick != s_entityInsideCleanupTick && s_entityInsideSeen.size() > 200000)
+ {
+ s_entityInsideSeen.clear();
+ s_entityInsideCleanupTick = tick;
+ }
+
+ for (int y = minY; y <= maxY; ++y)
+ {
+ for (int z = minZ; z <= maxZ; ++z)
+ {
+ for (int x = minX; x <= maxX; ++x)
+ {
+ const int blockId = s_levelGetTile(level, x, y, z);
+ if (blockId <= 0 || !ManagedBlockRegistry::IsManaged(blockId))
+ continue;
+
+ if (!Intersects(&box, static_cast(x), static_cast(y), static_cast(z),
+ static_cast(x + 1), static_cast(y + 1), static_cast(z + 1)))
+ continue;
+
+ const uint64_t key = MakeEntityInsideKey(level, thisPtr, x, y, z);
+ auto it = s_entityInsideSeen.find(key);
+ if (it != s_entityInsideSeen.end() && it->second == tick)
+ continue;
+ s_entityInsideSeen[key] = tick;
+
+ BlockEntityNativeArgs args{};
+ args.block = { blockId, isClientSide, level, x, y, z };
+ args.entityPtr = thisPtr;
+ DotNetHost::CallBlockEntityInsideTile(&args, sizeof(args));
+ }
+ }
+ }
+ }
+
+ void __fastcall Hooked_TileStepOn(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr)
+ {
+ if (Original_TileStepOn)
+ Original_TileStepOn(thisPtr, level, x, y, z, entitySharedPtr);
+
+ const int blockId = TryReadTileId(thisPtr);
+ if (!ManagedBlockRegistry::IsManaged(blockId))
+ return;
+
+ int isClientSide = 0;
+ if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool)))
+ isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0;
+
+ void* entityPtr = DecodeEntityPtrFromSharedArg(entitySharedPtr);
+ BlockEntityNativeArgs args{};
+ args.block = { blockId, isClientSide, level, x, y, z };
+ args.entityPtr = entityPtr;
+ DotNetHost::CallBlockStepOn(&args, sizeof(args));
+ }
+
+ void __fastcall Hooked_TileEntityInside(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr)
+ {
+ if (Original_TileEntityInside)
+ Original_TileEntityInside(thisPtr, level, x, y, z, entitySharedPtr);
+
+ (void)thisPtr;
+ (void)level;
+ (void)x;
+ (void)y;
+ (void)z;
+ (void)entitySharedPtr;
+ }
+
+ void __fastcall Hooked_TileFallOn(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr, float fallDistance)
+ {
+ if (Original_TileFallOn)
+ Original_TileFallOn(thisPtr, level, x, y, z, entitySharedPtr, fallDistance);
+
+ const int blockId = TryReadTileId(thisPtr);
+ if (!ManagedBlockRegistry::IsManaged(blockId))
+ return;
+
+ int isClientSide = 0;
+ if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool)))
+ isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0;
+
+ void* entityPtr = DecodeEntityPtrFromSharedArg(entitySharedPtr);
+ BlockFallNativeArgs args{};
+ args.block = { blockId, isClientSide, level, x, y, z };
+ args.entityPtr = entityPtr;
+ args.fallDistance = fallDistance;
+ DotNetHost::CallBlockFallOn(&args, sizeof(args));
+ }
+
+ void __fastcall Hooked_TileOnRemoving(void* thisPtr, void* level, int x, int y, int z, int data)
+ {
+ if (Original_TileOnRemoving)
+ Original_TileOnRemoving(thisPtr, level, x, y, z, data);
+
+ const int blockId = TryReadTileId(thisPtr);
+ if (!ManagedBlockRegistry::IsManaged(blockId))
+ return;
+
+ int isClientSide = 0;
+ if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool)))
+ isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0;
+
+ BlockRemovingNativeArgs args{};
+ args.block = { blockId, isClientSide, level, x, y, z };
+ args.blockData = data;
+ DotNetHost::CallBlockRemoving(&args, sizeof(args));
+ }
+
+ void __fastcall Hooked_TileOnRemove(void* thisPtr, void* level, int x, int y, int z, int id, int data)
+ {
+ if (Original_TileOnRemove)
+ Original_TileOnRemove(thisPtr, level, x, y, z, id, data);
+
+ const int blockId = TryReadTileId(thisPtr);
+ if (!ManagedBlockRegistry::IsManaged(blockId))
+ return;
+
+ int isClientSide = 0;
+ if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool)))
+ isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0;
+
+ BlockRemoveNativeArgs args{};
+ args.block = { blockId, isClientSide, level, x, y, z };
+ args.removedBlockId = id;
+ args.removedBlockData = data;
+ DotNetHost::CallBlockRemoved(&args, sizeof(args));
+ }
+
+ void __fastcall Hooked_TileDestroy(void* thisPtr, void* level, int x, int y, int z, int data)
+ {
+ if (Original_TileDestroy)
+ Original_TileDestroy(thisPtr, level, x, y, z, data);
+
+ const int blockId = TryReadTileId(thisPtr);
+ if (!ManagedBlockRegistry::IsManaged(blockId))
+ return;
+
+ int isClientSide = 0;
+ if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool)))
+ isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0;
+
+ BlockDestroyNativeArgs args{};
+ args.block = { blockId, isClientSide, level, x, y, z };
+ args.blockData = data;
+ DotNetHost::CallBlockDestroyed(&args, sizeof(args));
+ }
+
+ void __fastcall Hooked_TilePlayerDestroy(void* thisPtr, void* level, void* playerSharedPtr, int x, int y, int z, int data)
+ {
+ if (Original_TilePlayerDestroy)
+ Original_TilePlayerDestroy(thisPtr, level, playerSharedPtr, x, y, z, data);
+
+ const int blockId = TryReadTileId(thisPtr);
+ if (!ManagedBlockRegistry::IsManaged(blockId))
+ return;
+
+ int isClientSide = 0;
+ if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool)))
+ isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0;
+
+ void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr);
+ BlockPlayerDestroyNativeArgs args{};
+ args.block = { blockId, isClientSide, level, x, y, z };
+ args.playerPtr = playerPtr;
+ args.blockData = data;
+ DotNetHost::CallBlockPlayerDestroy(&args, sizeof(args));
+ }
+
+ void __fastcall Hooked_TilePlayerWillDestroy(void* thisPtr, void* level, int x, int y, int z, int data, void* playerSharedPtr)
+ {
+ if (Original_TilePlayerWillDestroy)
+ Original_TilePlayerWillDestroy(thisPtr, level, x, y, z, data, playerSharedPtr);
+
+ const int blockId = TryReadTileId(thisPtr);
+ if (!ManagedBlockRegistry::IsManaged(blockId))
+ return;
+
+ int isClientSide = 0;
+ if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool)))
+ isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0;
+
+ void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr);
+ BlockPlayerWillDestroyNativeArgs args{};
+ args.block = { blockId, isClientSide, level, x, y, z };
+ args.playerPtr = playerPtr;
+ args.blockData = data;
+ DotNetHost::CallBlockPlayerWillDestroy(&args, sizeof(args));
+ }
+
+ void __fastcall Hooked_TileSetPlacedBy(void* thisPtr, void* level, int x, int y, int z, void* livingEntitySharedPtr, void* itemInstanceSharedPtr)
+ {
+ if (Original_TileSetPlacedBy)
+ Original_TileSetPlacedBy(thisPtr, level, x, y, z, livingEntitySharedPtr, itemInstanceSharedPtr);
+
+ if (WasRecentlyPlaced(level, x, y, z))
+ return;
+
+ const int blockId = TryReadTileId(thisPtr);
+ if (!ManagedBlockRegistry::IsManaged(blockId))
+ return;
+
+ int isClientSide = 0;
+ if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool)))
+ isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0;
+
+ void* placerPtr = DecodeEntityPtrFromSharedArg(livingEntitySharedPtr);
+ void* itemInstancePtr = DecodeItemInstancePtrFromSharedArg(itemInstanceSharedPtr);
+ BlockPlacedByNativeArgs args{};
+ args.block = { blockId, isClientSide, level, x, y, z };
+ args.placerPtr = placerPtr;
+ args.itemInstancePtr = itemInstancePtr;
+ DotNetHost::CallBlockPlacedBy(&args, sizeof(args));
+
+ s_lastPlacedLevel = level;
+ s_lastPlacedX = x;
+ s_lastPlacedY = y;
+ s_lastPlacedZ = z;
+ s_lastPlacedTimeMs = GetTickCount64();
+ }
+
+ static bool TryGetPlacementContext(void* levelPtr, void*& outPlayerPtr, void*& outItemInstancePtr)
+ {
+ if (!s_lastUsePlayer || !s_lastUseLevel || s_lastUseLevel != levelPtr)
return false;
const ULONGLONG nowMs = GetTickCount64();
@@ -2054,9 +2775,25 @@ namespace GameHooks
return false;
outPlayerPtr = s_lastUsePlayer;
+ outItemInstancePtr = s_lastUseItemInstance;
return true;
}
+ static bool TryGetPlacementPlayer(void* levelPtr, void*& outPlayerPtr)
+ {
+ void* unusedItem = nullptr;
+ return TryGetPlacementContext(levelPtr, outPlayerPtr, unusedItem);
+ }
+
+ static bool WasRecentlyPlaced(void* levelPtr, int x, int y, int z)
+ {
+ if (s_lastPlacedLevel != levelPtr || s_lastPlacedX != x || s_lastPlacedY != y || s_lastPlacedZ != z)
+ return false;
+
+ const ULONGLONG nowMs = GetTickCount64();
+ return nowMs >= s_lastPlacedTimeMs && (nowMs - s_lastPlacedTimeMs) <= 500;
+ }
+
static int ComputeFacingDataFromYaw(float yaw)
{
return static_cast(std::floor(((yaw + 180.0f) * 4.0f) / 360.0f - 0.5f)) & 3;
@@ -2070,6 +2807,10 @@ namespace GameHooks
bool __fastcall Hooked_LevelSetTileAndData(void* thisPtr, int x, int y, int z, int tile, int data, int updateFlags)
{
const int oldBlockId = s_levelGetTile ? s_levelGetTile(thisPtr, x, y, z) : -1;
+ const int oldBlockData = Level_GetData && thisPtr ? Level_GetData(thisPtr, x, y, z) : 0;
+ int isClientSide = 0;
+ if (thisPtr && IsReadableRange(static_cast(thisPtr) + kLevelIsClientSideOffset, sizeof(bool)))
+ isClientSide = *reinterpret_cast(static_cast(thisPtr) + kLevelIsClientSideOffset) ? 1 : 0;
int effectiveData = data;
if (tile > 0)
{
@@ -2112,6 +2853,15 @@ namespace GameHooks
}
}
+ const bool isChange = oldBlockId > 0 && (oldBlockId != tile || oldBlockData != effectiveData);
+ if (isChange && ManagedBlockRegistry::IsManaged(oldBlockId))
+ {
+ BlockRemovingNativeArgs args{};
+ args.block = { oldBlockId, isClientSide, thisPtr, x, y, z };
+ args.blockData = oldBlockData;
+ DotNetHost::CallBlockRemoving(&args, sizeof(args));
+ }
+
const bool result = Original_LevelSetTileAndData
? Original_LevelSetTileAndData(thisPtr, x, y, z, tile, effectiveData, updateFlags)
: false;
@@ -2119,9 +2869,40 @@ namespace GameHooks
if (result && s_levelGetTile)
WorldIdRemap::MarkChunkDirtyByBlockUpdate(x, z, oldBlockId, tile);
+ if (result && isChange && ManagedBlockRegistry::IsManaged(oldBlockId))
+ {
+ BlockRemoveNativeArgs args{};
+ args.block = { oldBlockId, isClientSide, thisPtr, x, y, z };
+ args.removedBlockId = oldBlockId;
+ args.removedBlockData = oldBlockData;
+ DotNetHost::CallBlockRemoved(&args, sizeof(args));
+ }
+
if (result && tile > 0)
+ {
DispatchManagedBlockById(tile, thisPtr, x, y, z, 0, 0);
+ if (ManagedBlockRegistry::IsManaged(tile) && !WasRecentlyPlaced(thisPtr, x, y, z))
+ {
+ void* placerPtr = nullptr;
+ void* itemInstancePtr = nullptr;
+ if (TryGetPlacementContext(thisPtr, placerPtr, itemInstancePtr))
+ {
+ BlockPlacedByNativeArgs args{};
+ args.block = { tile, isClientSide, thisPtr, x, y, z };
+ args.placerPtr = placerPtr;
+ args.itemInstancePtr = itemInstancePtr;
+ DotNetHost::CallBlockPlacedBy(&args, sizeof(args));
+
+ s_lastPlacedLevel = thisPtr;
+ s_lastPlacedX = x;
+ s_lastPlacedY = y;
+ s_lastPlacedZ = z;
+ s_lastPlacedTimeMs = GetTickCount64();
+ }
+ }
+ }
+
return result;
}
@@ -3031,10 +3812,41 @@ namespace GameHooks
{
s_lastUsePlayer = playerPtr;
s_lastUseLevel = level;
+ s_lastUseItemInstance = thisPtr;
s_lastUseTimeMs = GetTickCount64();
}
}
+ int itemId = 0;
+ if (!bTestUseOnOnly && TryReadItemId(thisPtr, itemId))
+ {
+ int isClientSide = 0;
+ if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool)))
+ {
+ isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0;
+ }
+
+ void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr);
+ UseOnItemNativeArgs args{};
+ args.itemId = itemId;
+ args.isClientSide = isClientSide;
+ args.itemInstancePtr = thisPtr;
+ args.playerPtr = playerPtr;
+ args.playerSharedPtr = playerSharedPtr;
+ args.levelPtr = level;
+ args.x = x;
+ args.y = y;
+ args.z = z;
+ args.face = face;
+ args.clickX = clickX;
+ args.clickY = clickY;
+ args.clickZ = clickZ;
+
+ int action = DotNetHost::CallItemUseOn(&args, sizeof(args));
+ if (action == 2)
+ return true;
+ }
+
if (Original_ItemInstanceUseOn)
return Original_ItemInstanceUseOn(thisPtr, playerSharedPtr, level, x, y, z, face, clickX, clickY, clickZ, bTestUseOnOnly);
return false;
@@ -3058,6 +3870,105 @@ namespace GameHooks
WorldIdRemap::RemapItemInstanceFromTag(thisPtr, compoundTagPtr);
}
+ void __fastcall Hooked_ItemInstanceInventoryTick(void* thisPtr, void* level, void* ownerSharedPtr, int slot, bool selected)
+ {
+ if (Original_ItemInstanceInventoryTick)
+ Original_ItemInstanceInventoryTick(thisPtr, level, ownerSharedPtr, slot, selected);
+
+ int itemId = 0;
+ if (!TryReadItemId(thisPtr, itemId))
+ return;
+
+ void* ownerPtr = DecodeEntityPtrFromSharedArg(ownerSharedPtr);
+ int isClientSide = 0;
+ if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool)))
+ isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0;
+
+ ItemInventoryTickNativeArgs args{};
+ args.itemId = itemId;
+ args.itemInstancePtr = thisPtr;
+ args.levelPtr = level;
+ args.ownerEntityPtr = ownerPtr;
+ args.slot = slot;
+ args.isSelected = selected ? 1 : 0;
+ args.isClientSide = isClientSide;
+ DotNetHost::CallItemInventoryTick(&args, sizeof(args));
+ }
+
+ void __fastcall Hooked_ItemInstanceOnCraftedBy(void* thisPtr, void* level, void* playerSharedPtr, int amount)
+ {
+ if (Original_ItemInstanceOnCraftedBy)
+ Original_ItemInstanceOnCraftedBy(thisPtr, level, playerSharedPtr, amount);
+
+ int itemId = 0;
+ if (!TryReadItemId(thisPtr, itemId))
+ return;
+
+ void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr);
+ int isClientSide = 0;
+ if (level && IsReadableRange(static_cast(level) + kLevelIsClientSideOffset, sizeof(bool)))
+ isClientSide = *reinterpret_cast(static_cast(level) + kLevelIsClientSideOffset) ? 1 : 0;
+
+ ItemCraftedByNativeArgs args{};
+ args.itemId = itemId;
+ args.itemInstancePtr = thisPtr;
+ args.levelPtr = level;
+ args.playerPtr = playerPtr;
+ args.playerSharedPtr = playerSharedPtr;
+ args.amount = amount;
+ args.isClientSide = isClientSide;
+ DotNetHost::CallItemCraftedBy(&args, sizeof(args));
+ }
+
+ bool __fastcall Hooked_ItemInstanceInteractEnemy(void* thisPtr, void* playerSharedPtr, void* targetSharedPtr)
+ {
+ int itemId = 0;
+ if (TryReadItemId(thisPtr, itemId))
+ {
+ void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr);
+ void* targetPtr = DecodeEntityPtrFromSharedArg(targetSharedPtr);
+
+ ItemEntityInteractionNativeArgs args{};
+ args.itemId = itemId;
+ args.itemInstancePtr = thisPtr;
+ args.playerPtr = playerPtr;
+ args.playerSharedPtr = playerSharedPtr;
+ args.targetEntityPtr = targetPtr;
+
+ int action = DotNetHost::CallItemInteractEntity(&args, sizeof(args));
+ if (action == 2)
+ return true;
+ }
+
+ if (Original_ItemInstanceInteractEnemy)
+ return Original_ItemInstanceInteractEnemy(thisPtr, playerSharedPtr, targetSharedPtr);
+ return false;
+ }
+
+ void __fastcall Hooked_ItemInstanceHurtEnemy(void* thisPtr, void* targetSharedPtr, void* playerSharedPtr)
+ {
+ int itemId = 0;
+ if (TryReadItemId(thisPtr, itemId))
+ {
+ void* playerPtr = DecodePlayerPtrFromSharedArg(playerSharedPtr);
+ void* targetPtr = DecodeEntityPtrFromSharedArg(targetSharedPtr);
+
+ ItemEntityInteractionNativeArgs args{};
+ args.itemId = itemId;
+ args.itemInstancePtr = thisPtr;
+ args.playerPtr = playerPtr;
+ args.playerSharedPtr = playerSharedPtr;
+ args.targetEntityPtr = targetPtr;
+
+ int action = DotNetHost::CallItemHurtEntity(&args, sizeof(args));
+ if (action == 2)
+ return;
+ }
+
+ if (Original_ItemInstanceHurtEnemy)
+ Original_ItemInstanceHurtEnemy(thisPtr, targetSharedPtr, playerSharedPtr);
+ }
+
bool __fastcall Hooked_ItemMineBlock(void* thisPtr, void* itemInstanceSharedPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr)
{
s_itemMineBlockHookCalls++;
@@ -3338,6 +4249,7 @@ namespace GameHooks
{
s_lastUsePlayer = playerPtr;
s_lastUseLevel = level;
+ s_lastUseItemInstance = itemInstancePtr;
s_lastUseTimeMs = nowMs;
}
}
@@ -3375,6 +4287,7 @@ namespace GameHooks
{
s_lastUsePlayer = playerPtr;
s_lastUseLevel = level;
+ s_lastUseItemInstance = itemInstancePtr;
s_lastUseTimeMs = GetTickCount64();
}
}
@@ -3404,6 +4317,7 @@ namespace GameHooks
{
s_lastUsePlayer = playerPtr;
s_lastUseLevel = level;
+ s_lastUseItemInstance = DecodeItemInstancePtrFromSharedArg(itemInstanceSharedPtr);
s_lastUseTimeMs = GetTickCount64();
}
}
@@ -3422,6 +4336,7 @@ namespace GameHooks
{
s_lastUsePlayer = playerPtr;
s_lastUseLevel = level;
+ s_lastUseItemInstance = DecodeItemInstancePtrFromSharedArg(itemInstanceSharedPtr);
s_lastUseTimeMs = GetTickCount64();
}
}
@@ -3571,6 +4486,7 @@ namespace GameHooks
void __fastcall Hooked_MinecraftTick(void* thisPtr, bool bFirst, bool bUpdateTextures)
{
+ s_tickCounter.fetch_add(1, std::memory_order_relaxed);
ModAtlas::PollAsyncBuild();
CullSpawnedEntitiesBelowWorld();
Original_MinecraftTick(thisPtr, bFirst, bUpdateTextures);
diff --git a/WeaveLoaderRuntime/src/GameHooks.h b/WeaveLoaderRuntime/src/GameHooks.h
index 4e091f6..2a7a129 100644
--- a/WeaveLoaderRuntime/src/GameHooks.h
+++ b/WeaveLoaderRuntime/src/GameHooks.h
@@ -32,12 +32,28 @@ typedef void (__fastcall *ItemInstanceMineBlock_fn)(void* thisPtr, void* level,
typedef bool (__fastcall *ItemInstanceUseOn_fn)(void* thisPtr, void* playerSharedPtr, void* level, int x, int y, int z, int face, float clickX, float clickY, float clickZ, bool bTestUseOnOnly);
typedef void* (__fastcall *ItemInstanceSave_fn)(void* thisPtr, void* compoundTagPtr);
typedef void (__fastcall *ItemInstanceLoad_fn)(void* thisPtr, void* compoundTagPtr);
+typedef void (__fastcall *ItemInstanceInventoryTick_fn)(void* thisPtr, void* level, void* ownerSharedPtr, int slot, bool selected);
+typedef void (__fastcall *ItemInstanceOnCraftedBy_fn)(void* thisPtr, void* level, void* playerSharedPtr, int amount);
+typedef bool (__fastcall *ItemInstanceInteractEnemy_fn)(void* thisPtr, void* playerSharedPtr, void* targetSharedPtr);
+typedef void (__fastcall *ItemInstanceHurtEnemy_fn)(void* thisPtr, void* targetSharedPtr, void* playerSharedPtr);
typedef bool (__fastcall *ItemMineBlock_fn)(void* thisPtr, void* itemInstanceSharedPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr);
typedef float (__fastcall *PickaxeGetDestroySpeed_fn)(void* thisPtr, void* itemInstanceSharedPtr, void* tilePtr);
typedef bool (__fastcall *PickaxeCanDestroySpecial_fn)(void* thisPtr, void* tilePtr);
typedef void (__fastcall *TileOnPlace_fn)(void* thisPtr, void* level, int x, int y, int z);
typedef void (__fastcall *TileNeighborChanged_fn)(void* thisPtr, void* level, int x, int y, int z, int type);
typedef void (__fastcall *TileTick_fn)(void* thisPtr, void* level, int x, int y, int z, void* random);
+typedef bool (__fastcall *TileUse_fn)(void* thisPtr, void* level, int x, int y, int z, void* playerSharedPtr, int face, float clickX, float clickY, float clickZ, bool soundOnly);
+typedef void (__fastcall *TileStepOn_fn)(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr);
+typedef void (__fastcall *TileEntityInside_fn)(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr);
+typedef void (__fastcall *TileFallOn_fn)(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr, float fallDistance);
+typedef void (__fastcall *TileOnRemoving_fn)(void* thisPtr, void* level, int x, int y, int z, int data);
+typedef void (__fastcall *TileOnRemove_fn)(void* thisPtr, void* level, int x, int y, int z, int id, int data);
+typedef void (__fastcall *TileDestroy_fn)(void* thisPtr, void* level, int x, int y, int z, int data);
+typedef void (__fastcall *TilePlayerDestroy_fn)(void* thisPtr, void* level, void* playerSharedPtr, int x, int y, int z, int data);
+typedef void (__fastcall *TilePlayerWillDestroy_fn)(void* thisPtr, void* level, int x, int y, int z, int data, void* playerSharedPtr);
+typedef void (__fastcall *TileSetPlacedBy_fn)(void* thisPtr, void* level, int x, int y, int z, void* livingEntitySharedPtr, void* itemInstanceSharedPtr);
+typedef void (__fastcall *TileSharedAction_fn)(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr, float fallDistance);
+typedef void (__fastcall *TileSharedLifecycle_fn)(void* thisPtr, void* level, int x, int y, int z, void* arg5, void* arg6);
typedef bool (__fastcall *LevelSetTileAndDataDispatch_fn)(void* thisPtr, int x, int y, int z, int tile, int data, int updateFlags);
typedef bool (__fastcall *LevelSetDataDispatch_fn)(void* thisPtr, int x, int y, int z, int data, int updateFlags, bool forceUpdate);
typedef void (__fastcall *LevelUpdateNeighborsAtDispatch_fn)(void* thisPtr, int x, int y, int z, int type);
@@ -61,6 +77,8 @@ typedef bool (__fastcall *MultiPlayerGameModeUseItemOn_fn)(void* thisPtr, void*
typedef bool (__fastcall *ServerPlayerGameModeUseItemOn_fn)(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, int x, int y, int z, int face, float clickX, float clickY, float clickZ, bool bTestUseOnOnly, bool* pbUsedItem);
typedef void (__fastcall *MinecraftSetLevel_fn)(void* thisPtr, void* level, int message, void* forceInsertPlayerSharedPtr, bool doForceStatsSave, bool bPrimaryPlayerSignedOut);
typedef bool (__fastcall *LevelAddEntity_fn)(void* thisPtr, void* entitySharedPtr);
+typedef void (__fastcall *EntityPlayStepSound_fn)(void* thisPtr, int xt, int yt, int zt, int t);
+typedef void (__fastcall *EntityCheckInsideTiles_fn)(void* thisPtr);
typedef void (__fastcall *EntityMoveTo_fn)(void* thisPtr, double x, double y, double z, float yRot, float xRot);
typedef void (__fastcall *EntityIONewById_fn)(void* outSharedPtr, int entityNumericId, void* level);
typedef void (__fastcall *EntitySetPos_fn)(void* thisPtr, double x, double y, double z);
@@ -130,6 +148,10 @@ namespace GameHooks
extern ItemInstanceUseOn_fn Original_ItemInstanceUseOn;
extern ItemInstanceSave_fn Original_ItemInstanceSave;
extern ItemInstanceLoad_fn Original_ItemInstanceLoad;
+ extern ItemInstanceInventoryTick_fn Original_ItemInstanceInventoryTick;
+ extern ItemInstanceOnCraftedBy_fn Original_ItemInstanceOnCraftedBy;
+ extern ItemInstanceInteractEnemy_fn Original_ItemInstanceInteractEnemy;
+ extern ItemInstanceHurtEnemy_fn Original_ItemInstanceHurtEnemy;
extern ItemMineBlock_fn Original_ItemMineBlock;
extern ItemMineBlock_fn Original_DiggerItemMineBlock;
extern PickaxeGetDestroySpeed_fn Original_PickaxeItemGetDestroySpeed;
@@ -139,6 +161,18 @@ namespace GameHooks
extern TileOnPlace_fn Original_TileOnPlace;
extern TileNeighborChanged_fn Original_TileNeighborChanged;
extern TileTick_fn Original_TileTick;
+ extern TileUse_fn Original_TileUse;
+ extern TileStepOn_fn Original_TileStepOn;
+ extern TileEntityInside_fn Original_TileEntityInside;
+ extern TileFallOn_fn Original_TileFallOn;
+ extern TileOnRemoving_fn Original_TileOnRemoving;
+ extern TileOnRemove_fn Original_TileOnRemove;
+ extern TileDestroy_fn Original_TileDestroy;
+ extern TilePlayerDestroy_fn Original_TilePlayerDestroy;
+ extern TilePlayerWillDestroy_fn Original_TilePlayerWillDestroy;
+ extern TileSetPlacedBy_fn Original_TileSetPlacedBy;
+ extern TileSharedAction_fn Original_TileSharedAction;
+ extern TileSharedLifecycle_fn Original_TileSharedLifecycle;
extern LevelSetTileAndDataDispatch_fn Original_LevelSetTileAndData;
extern LevelSetDataDispatch_fn Original_LevelSetData;
extern LevelUpdateNeighborsAtDispatch_fn Original_LevelUpdateNeighborsAt;
@@ -168,6 +202,8 @@ namespace GameHooks
extern ServerPlayerGameModeUseItemOn_fn Original_ServerPlayerGameModeUseItemOn;
extern MultiPlayerGameModeUseItemOn_fn Original_MultiPlayerGameModeUseItemOn;
extern MinecraftSetLevel_fn Original_MinecraftSetLevel;
+ extern EntityPlayStepSound_fn Original_EntityPlayStepSound;
+ extern EntityCheckInsideTiles_fn Original_EntityCheckInsideTiles;
extern TexturesBindTextureResource_fn Original_TexturesBindTextureResource;
extern TexturesLoadTextureByName_fn Original_TexturesLoadTextureByName;
extern TexturesLoadTextureByIndex_fn Original_TexturesLoadTextureByIndex;
@@ -229,6 +265,10 @@ namespace GameHooks
bool __fastcall Hooked_ItemInstanceUseOn(void* thisPtr, void* playerSharedPtr, void* level, int x, int y, int z, int face, float clickX, float clickY, float clickZ, bool bTestUseOnOnly);
void* __fastcall Hooked_ItemInstanceSave(void* thisPtr, void* compoundTagPtr);
void __fastcall Hooked_ItemInstanceLoad(void* thisPtr, void* compoundTagPtr);
+ void __fastcall Hooked_ItemInstanceInventoryTick(void* thisPtr, void* level, void* ownerSharedPtr, int slot, bool selected);
+ void __fastcall Hooked_ItemInstanceOnCraftedBy(void* thisPtr, void* level, void* playerSharedPtr, int amount);
+ bool __fastcall Hooked_ItemInstanceInteractEnemy(void* thisPtr, void* playerSharedPtr, void* targetSharedPtr);
+ void __fastcall Hooked_ItemInstanceHurtEnemy(void* thisPtr, void* targetSharedPtr, void* playerSharedPtr);
bool __fastcall Hooked_ItemMineBlock(void* thisPtr, void* itemInstanceSharedPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr);
bool __fastcall Hooked_DiggerItemMineBlock(void* thisPtr, void* itemInstanceSharedPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr);
float __fastcall Hooked_PickaxeItemGetDestroySpeed(void* thisPtr, void* itemInstanceSharedPtr, void* tilePtr);
@@ -238,6 +278,18 @@ namespace GameHooks
void __fastcall Hooked_TileOnPlace(void* thisPtr, void* level, int x, int y, int z);
void __fastcall Hooked_TileNeighborChanged(void* thisPtr, void* level, int x, int y, int z, int type);
void __fastcall Hooked_TileTick(void* thisPtr, void* level, int x, int y, int z, void* random);
+ bool __fastcall Hooked_TileUse(void* thisPtr, void* level, int x, int y, int z, void* playerSharedPtr, int face, float clickX, float clickY, float clickZ, bool soundOnly);
+ void __fastcall Hooked_TileStepOn(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr);
+ void __fastcall Hooked_TileEntityInside(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr);
+ void __fastcall Hooked_TileFallOn(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr, float fallDistance);
+ void __fastcall Hooked_TileOnRemoving(void* thisPtr, void* level, int x, int y, int z, int data);
+ void __fastcall Hooked_TileOnRemove(void* thisPtr, void* level, int x, int y, int z, int id, int data);
+ void __fastcall Hooked_TileDestroy(void* thisPtr, void* level, int x, int y, int z, int data);
+ void __fastcall Hooked_TilePlayerDestroy(void* thisPtr, void* level, void* playerSharedPtr, int x, int y, int z, int data);
+ void __fastcall Hooked_TilePlayerWillDestroy(void* thisPtr, void* level, int x, int y, int z, int data, void* playerSharedPtr);
+ void __fastcall Hooked_TileSetPlacedBy(void* thisPtr, void* level, int x, int y, int z, void* livingEntitySharedPtr, void* itemInstanceSharedPtr);
+ void __fastcall Hooked_TileSharedAction(void* thisPtr, void* level, int x, int y, int z, void* entitySharedPtr, float fallDistance);
+ void __fastcall Hooked_TileSharedLifecycle(void* thisPtr, void* level, int x, int y, int z, void* arg5, void* arg6);
bool __fastcall Hooked_LevelSetTileAndData(void* thisPtr, int x, int y, int z, int tile, int data, int updateFlags);
bool __fastcall Hooked_LevelSetData(void* thisPtr, int x, int y, int z, int data, int updateFlags, bool forceUpdate);
void __fastcall Hooked_LevelUpdateNeighborsAt(void* thisPtr, int x, int y, int z, int type);
@@ -266,6 +318,8 @@ namespace GameHooks
bool __fastcall Hooked_ServerPlayerGameModeUseItemOn(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, int x, int y, int z, int face, float clickX, float clickY, float clickZ, bool bTestUseOnOnly, bool* pbUsedItem);
bool __fastcall Hooked_MultiPlayerGameModeUseItemOn(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, int x, int y, int z, int face, void* hitVec3Ptr, bool bTestUseOnly, bool* pbUsedItem);
void __fastcall Hooked_MinecraftSetLevel(void* thisPtr, void* level, int message, void* forceInsertPlayerSharedPtr, bool doForceStatsSave, bool bPrimaryPlayerSignedOut);
+ void __fastcall Hooked_EntityPlayStepSound(void* thisPtr, int xt, int yt, int zt, int t);
+ void __fastcall Hooked_EntityCheckInsideTiles(void* thisPtr);
void __fastcall Hooked_TexturesBindTextureResource(void* thisPtr, void* resourcePtr);
int __fastcall Hooked_TexturesLoadTextureByName(void* thisPtr, int texId, const std::wstring& resourceName);
int __fastcall Hooked_TexturesLoadTextureByIndex(void* thisPtr, int idx);
diff --git a/WeaveLoaderRuntime/src/HookManager.cpp b/WeaveLoaderRuntime/src/HookManager.cpp
index c4f3c9d..088fb22 100644
--- a/WeaveLoaderRuntime/src/HookManager.cpp
+++ b/WeaveLoaderRuntime/src/HookManager.cpp
@@ -184,6 +184,91 @@ bool HookManager::Install(const SymbolResolver& symbols)
}
}
+ if (symbols.Item.pItemInstanceInventoryTick)
+ {
+ if (MH_CreateHook(symbols.Item.pItemInstanceInventoryTick,
+ reinterpret_cast(&GameHooks::Hooked_ItemInstanceInventoryTick),
+ reinterpret_cast(&GameHooks::Original_ItemInstanceInventoryTick)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook ItemInstance::inventoryTick");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked ItemInstance::inventoryTick (managed item callbacks)");
+ }
+ }
+
+ if (symbols.Item.pItemInstanceOnCraftedBy)
+ {
+ if (MH_CreateHook(symbols.Item.pItemInstanceOnCraftedBy,
+ reinterpret_cast(&GameHooks::Hooked_ItemInstanceOnCraftedBy),
+ reinterpret_cast(&GameHooks::Original_ItemInstanceOnCraftedBy)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook ItemInstance::onCraftedBy");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked ItemInstance::onCraftedBy (managed item callbacks)");
+ }
+ }
+
+ if (symbols.Item.pItemInstanceInteractEnemy)
+ {
+ if (MH_CreateHook(symbols.Item.pItemInstanceInteractEnemy,
+ reinterpret_cast(&GameHooks::Hooked_ItemInstanceInteractEnemy),
+ reinterpret_cast(&GameHooks::Original_ItemInstanceInteractEnemy)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook ItemInstance::interactEnemy");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked ItemInstance::interactEnemy (managed item callbacks)");
+ }
+ }
+
+ if (symbols.Item.pItemInstanceHurtEnemy)
+ {
+ if (MH_CreateHook(symbols.Item.pItemInstanceHurtEnemy,
+ reinterpret_cast(&GameHooks::Hooked_ItemInstanceHurtEnemy),
+ reinterpret_cast(&GameHooks::Original_ItemInstanceHurtEnemy)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook ItemInstance::hurtEnemy");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked ItemInstance::hurtEnemy (managed item callbacks)");
+ }
+ }
+
+ if (symbols.Entity.pEntityPlayStepSound)
+ {
+ if (MH_CreateHook(symbols.Entity.pEntityPlayStepSound,
+ reinterpret_cast(&GameHooks::Hooked_EntityPlayStepSound),
+ reinterpret_cast(&GameHooks::Original_EntityPlayStepSound)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook Entity::playStepSound");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked Entity::playStepSound (step-on callbacks)");
+ }
+ }
+
+ if (symbols.Entity.pEntityCheckInsideTiles)
+ {
+ if (MH_CreateHook(symbols.Entity.pEntityCheckInsideTiles,
+ reinterpret_cast(&GameHooks::Hooked_EntityCheckInsideTiles),
+ reinterpret_cast(&GameHooks::Original_EntityCheckInsideTiles)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook Entity::checkInsideTiles");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked Entity::checkInsideTiles (entity-inside callbacks)");
+ }
+ }
+
+
if (symbols.Item.pItemInstanceGetIcon)
{
if (MH_CreateHook(symbols.Item.pItemInstanceGetIcon,
@@ -478,6 +563,162 @@ bool HookManager::Install(const SymbolResolver& symbols)
}
}
+ if (symbols.Tile.pTileUse)
+ {
+ if (MH_CreateHook(symbols.Tile.pTileUse,
+ reinterpret_cast(&GameHooks::Hooked_TileUse),
+ reinterpret_cast(&GameHooks::Original_TileUse)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::use");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked Tile::use (managed block callbacks)");
+ }
+ }
+
+
+ void* sharedActionTarget = symbols.Tile.pTileStepOn
+ ? symbols.Tile.pTileStepOn
+ : symbols.Tile.pTileFallOn;
+ int sharedActionCount = 0;
+ if (sharedActionTarget)
+ {
+ if (symbols.Tile.pTileStepOn == sharedActionTarget) sharedActionCount++;
+ if (symbols.Tile.pTileFallOn == sharedActionTarget) sharedActionCount++;
+ }
+
+ const bool useSharedActionHook = sharedActionTarget && sharedActionCount >= 2;
+ if (useSharedActionHook)
+ {
+ if (MH_CreateHook(sharedActionTarget,
+ reinterpret_cast(&GameHooks::Hooked_TileSharedAction),
+ reinterpret_cast(&GameHooks::Original_TileSharedAction)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook shared Tile action stub");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked shared Tile action stub (stepOn/fallOn)");
+ }
+ }
+ else
+ {
+ if (symbols.Tile.pTileStepOn)
+ {
+ if (MH_CreateHook(symbols.Tile.pTileStepOn,
+ reinterpret_cast(&GameHooks::Hooked_TileStepOn),
+ reinterpret_cast(&GameHooks::Original_TileStepOn)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::stepOn");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked Tile::stepOn (managed block callbacks)");
+ }
+ }
+
+ if (symbols.Tile.pTileFallOn)
+ {
+ if (MH_CreateHook(symbols.Tile.pTileFallOn,
+ reinterpret_cast(&GameHooks::Hooked_TileFallOn),
+ reinterpret_cast(&GameHooks::Original_TileFallOn)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::fallOn");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked Tile::fallOn (managed block callbacks)");
+ }
+ }
+ }
+
+ void* sharedLifecycleTarget = symbols.Tile.pTileDestroy
+ ? symbols.Tile.pTileDestroy
+ : symbols.Tile.pTileTick;
+ int sharedLifecycleCount = 0;
+ if (sharedLifecycleTarget)
+ {
+ if (symbols.Tile.pTileDestroy == sharedLifecycleTarget) sharedLifecycleCount++;
+ if (symbols.Tile.pTileOnRemoving == sharedLifecycleTarget) sharedLifecycleCount++;
+ if (symbols.Tile.pTileOnRemove == sharedLifecycleTarget) sharedLifecycleCount++;
+ if (symbols.Tile.pTileTick == sharedLifecycleTarget) sharedLifecycleCount++;
+ }
+
+ const bool useSharedLifecycleHook = sharedLifecycleTarget && sharedLifecycleCount >= 2;
+ if (useSharedLifecycleHook)
+ {
+ if (MH_CreateHook(sharedLifecycleTarget,
+ reinterpret_cast(&GameHooks::Hooked_TileSharedLifecycle),
+ reinterpret_cast(&GameHooks::Original_TileSharedLifecycle)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook shared Tile lifecycle stub");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked shared Tile lifecycle stub (destroy)");
+ }
+ }
+ else
+ {
+ if (symbols.Tile.pTileDestroy)
+ {
+ if (MH_CreateHook(symbols.Tile.pTileDestroy,
+ reinterpret_cast(&GameHooks::Hooked_TileDestroy),
+ reinterpret_cast(&GameHooks::Original_TileDestroy)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::destroy");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked Tile::destroy (managed block callbacks)");
+ }
+ }
+
+ }
+
+ if (symbols.Tile.pTilePlayerDestroy)
+ {
+ if (MH_CreateHook(symbols.Tile.pTilePlayerDestroy,
+ reinterpret_cast(&GameHooks::Hooked_TilePlayerDestroy),
+ reinterpret_cast(&GameHooks::Original_TilePlayerDestroy)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::playerDestroy");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked Tile::playerDestroy (managed block callbacks)");
+ }
+ }
+
+ if (symbols.Tile.pTilePlayerWillDestroy)
+ {
+ if (MH_CreateHook(symbols.Tile.pTilePlayerWillDestroy,
+ reinterpret_cast(&GameHooks::Hooked_TilePlayerWillDestroy),
+ reinterpret_cast(&GameHooks::Original_TilePlayerWillDestroy)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::playerWillDestroy");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked Tile::playerWillDestroy (managed block callbacks)");
+ }
+ }
+
+ if (symbols.Tile.pTileSetPlacedBy)
+ {
+ if (MH_CreateHook(symbols.Tile.pTileSetPlacedBy,
+ reinterpret_cast(&GameHooks::Hooked_TileSetPlacedBy),
+ reinterpret_cast(&GameHooks::Original_TileSetPlacedBy)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::setPlacedBy");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked Tile::setPlacedBy (managed block callbacks)");
+ }
+ }
+
if (symbols.Level.pServerLevelTickPendingTicks)
{
if (MH_CreateHook(symbols.Level.pServerLevelTickPendingTicks,
diff --git a/WeaveLoaderRuntime/src/Symbols/SymbolGroups.cpp b/WeaveLoaderRuntime/src/Symbols/SymbolGroups.cpp
index e2d739e..5b54793 100644
--- a/WeaveLoaderRuntime/src/Symbols/SymbolGroups.cpp
+++ b/WeaveLoaderRuntime/src/Symbols/SymbolGroups.cpp
@@ -67,6 +67,10 @@ namespace
static const char* SYM_ITEMINSTANCE_SAVE = "?save@ItemInstance@@QEAAPEAVCompoundTag@@PEAV2@@Z";
static const char* SYM_ITEMINSTANCE_LOAD = "?load@ItemInstance@@QEAAXPEAVCompoundTag@@@Z";
static const char* SYM_ITEMINSTANCE_HURTANDBREAK = "?hurtAndBreak@ItemInstance@@QEAAXHV?$shared_ptr@VLivingEntity@@@std@@@Z";
+ static const char* SYM_ITEMINSTANCE_INVENTORYTICK = "?inventoryTick@ItemInstance@@QEAAXPEAVLevel@@V?$shared_ptr@VEntity@@@std@@H_N@Z";
+ static const char* SYM_ITEMINSTANCE_ONCRAFTEDBY = "?onCraftedBy@ItemInstance@@QEAAXPEAVLevel@@V?$shared_ptr@VPlayer@@@std@@H@Z";
+ static const char* SYM_ITEMINSTANCE_INTERACTENEMY = "?interactEnemy@ItemInstance@@QEAA_NV?$shared_ptr@VPlayer@@@std@@V?$shared_ptr@VLivingEntity@@@3@@Z";
+ static const char* SYM_ITEMINSTANCE_HURTENEMY = "?hurtEnemy@ItemInstance@@QEAAXV?$shared_ptr@VLivingEntity@@@std@@V?$shared_ptr@VPlayer@@@3@@Z";
static const char* SYM_ITEM_MINEBLOCK = "?mineBlock@Item@@UEAA_NV?$shared_ptr@VItemInstance@@@std@@PEAVLevel@@HHHHV?$shared_ptr@VLivingEntity@@@3@@Z";
static const char* SYM_DIGGERITEM_MINEBLOCK = "?mineBlock@DiggerItem@@UEAA_NV?$shared_ptr@VItemInstance@@@std@@PEAVLevel@@HHHHV?$shared_ptr@VLivingEntity@@@3@@Z";
static const char* SYM_PICKAXEITEM_GETDESTROYSPEED = "?getDestroySpeed@PickaxeItem@@UEAAMV?$shared_ptr@VItemInstance@@@std@@PEAVTile@@@Z";
@@ -81,6 +85,16 @@ namespace
static const char* SYM_TILE_ONPLACE = "?onPlace@Tile@@UEAAXPEAVLevel@@HHH@Z";
static const char* SYM_TILE_NEIGHBORCHANGED = "?neighborChanged@Tile@@UEAAXPEAVLevel@@HHHH@Z";
static const char* SYM_TILE_TICK = "?tick@Tile@@UEAAXPEAVLevel@@HHHPEAVRandom@@@Z";
+ static const char* SYM_TILE_USE = "?use@Tile@@UEAA_NPEAVLevel@@HHHV?$shared_ptr@VPlayer@@@std@@HMMM_N@Z";
+ static const char* SYM_TILE_STEPON = "?stepOn@Tile@@UEAAXPEAVLevel@@HHHV?$shared_ptr@VEntity@@@std@@@Z";
+ static const char* SYM_TILE_ENTITYINSIDE = "?entityInside@Tile@@UEAAXPEAVLevel@@HHHV?$shared_ptr@VEntity@@@std@@@Z";
+ static const char* SYM_TILE_FALLON = "?fallOn@Tile@@UEAAXPEAVLevel@@HHHV?$shared_ptr@VEntity@@@std@@M@Z";
+ static const char* SYM_TILE_ONREMOVING = "?onRemoving@Tile@@UEAAXPEAVLevel@@HHHH@Z";
+ static const char* SYM_TILE_ONREMOVE = "?onRemove@Tile@@UEAAXPEAVLevel@@HHHHH@Z";
+ static const char* SYM_TILE_DESTROY = "?destroy@Tile@@UEAAXPEAVLevel@@HHHH@Z";
+ static const char* SYM_TILE_PLAYERDESTROY = "?playerDestroy@Tile@@UEAAXPEAVLevel@@V?$shared_ptr@VPlayer@@@std@@HHHH@Z";
+ static const char* SYM_TILE_PLAYERWILLDESTROY = "?playerWillDestroy@Tile@@UEAAXPEAVLevel@@HHHHV?$shared_ptr@VPlayer@@@std@@@Z";
+ static const char* SYM_TILE_SETPLACEDBY = "?setPlacedBy@Tile@@UEAAXPEAVLevel@@HHHV?$shared_ptr@VLivingEntity@@@std@@V?$shared_ptr@VItemInstance@@@4@@Z";
static const char* SYM_TILE_GETRESOURCE = "?getResource@Tile@@UEAAHHPEAVRandom@@H@Z";
static const char* SYM_TILE_GETPLACEDONFACEDATAVALUE = "?getPlacedOnFaceDataValue@Tile@@UEAAHPEAVLevel@@HHHHMMMH@Z";
static const char* SYM_TILE_CLONETILEID = "?cloneTileId@Tile@@UEAAHPEAVLevel@@HHH@Z";
@@ -138,7 +152,9 @@ namespace
static const char* SYM_LEVEL_ADDENTITY = "?addEntity@Level@@UEAA_NV?$shared_ptr@VEntity@@@std@@@Z";
static const char* SYM_ENTITYIO_NEWBYID = "?newById@EntityIO@@SA?AV?$shared_ptr@VEntity@@@std@@HPEAVLevel@@@Z";
static const char* SYM_ENTITY_MOVETO = "?moveTo@Entity@@QEAAXNNNMM@Z";
+ static const char* SYM_ENTITY_CHECKINSIDETILES = "?checkInsideTiles@Entity@@MEAAXXZ";
static const char* SYM_ENTITY_SETPOS = "?setPos@Entity@@QEAAXNNN@Z";
+ static const char* SYM_ENTITY_PLAYSTEPSOUND = "?playStepSound@Entity@@MEAAXHHHH@Z";
static const char* SYM_LIVINGENTITY_GETLOOKANGLE = "?getLookAngle@LivingEntity@@UEAAPEAVVec3@@XZ";
static const char* SYM_ENTITY_GETLOOKANGLE = "?getLookAngle@Entity@@UEAAPEAVVec3@@XZ";
static const char* SYM_LIVINGENTITY_GETVIEWVECTOR = "?getViewVector@LivingEntity@@UEAAPEAVVec3@@M@Z";
@@ -311,6 +327,10 @@ bool ItemSymbols::Resolve(SymbolResolver& resolver)
pItemInstanceSave = resolver.Resolve(SYM_ITEMINSTANCE_SAVE);
pItemInstanceLoad = resolver.Resolve(SYM_ITEMINSTANCE_LOAD);
pItemInstanceHurtAndBreak = resolver.Resolve(SYM_ITEMINSTANCE_HURTANDBREAK);
+ pItemInstanceInventoryTick = resolver.Resolve(SYM_ITEMINSTANCE_INVENTORYTICK);
+ pItemInstanceOnCraftedBy = resolver.Resolve(SYM_ITEMINSTANCE_ONCRAFTEDBY);
+ pItemInstanceInteractEnemy = resolver.Resolve(SYM_ITEMINSTANCE_INTERACTENEMY);
+ pItemInstanceHurtEnemy = resolver.Resolve(SYM_ITEMINSTANCE_HURTENEMY);
pItemMineBlock = resolver.Resolve(SYM_ITEM_MINEBLOCK);
pDiggerItemMineBlock = resolver.Resolve(SYM_DIGGERITEM_MINEBLOCK);
pPickaxeItemGetDestroySpeed = resolver.Resolve(SYM_PICKAXEITEM_GETDESTROYSPEED);
@@ -334,6 +354,10 @@ void ItemSymbols::Log() const
LogSym("ItemInstance::save", pItemInstanceSave);
LogSym("ItemInstance::load", pItemInstanceLoad);
LogSym("ItemInstance::hurtAndBreak", pItemInstanceHurtAndBreak);
+ LogSym("ItemInstance::inventoryTick", pItemInstanceInventoryTick);
+ LogSym("ItemInstance::onCraftedBy", pItemInstanceOnCraftedBy);
+ LogSym("ItemInstance::interactEnemy", pItemInstanceInteractEnemy);
+ LogSym("ItemInstance::hurtEnemy", pItemInstanceHurtEnemy);
LogSym("Item::mineBlock", pItemMineBlock);
LogSym("DiggerItem::mineBlock", pDiggerItemMineBlock);
LogSym("PickaxeItem::getDestroySpeed", pPickaxeItemGetDestroySpeed);
@@ -351,6 +375,16 @@ bool TileSymbols::Resolve(SymbolResolver& resolver)
pTileOnPlace = resolver.Resolve(SYM_TILE_ONPLACE);
pTileNeighborChanged = resolver.Resolve(SYM_TILE_NEIGHBORCHANGED);
pTileTick = resolver.Resolve(SYM_TILE_TICK);
+ pTileUse = resolver.Resolve(SYM_TILE_USE);
+ pTileStepOn = resolver.Resolve(SYM_TILE_STEPON);
+ pTileEntityInside = resolver.Resolve(SYM_TILE_ENTITYINSIDE);
+ pTileFallOn = resolver.Resolve(SYM_TILE_FALLON);
+ pTileOnRemoving = resolver.Resolve(SYM_TILE_ONREMOVING);
+ pTileOnRemove = resolver.Resolve(SYM_TILE_ONREMOVE);
+ pTileDestroy = resolver.Resolve(SYM_TILE_DESTROY);
+ pTilePlayerDestroy = resolver.Resolve(SYM_TILE_PLAYERDESTROY);
+ pTilePlayerWillDestroy = resolver.Resolve(SYM_TILE_PLAYERWILLDESTROY);
+ pTileSetPlacedBy = resolver.Resolve(SYM_TILE_SETPLACEDBY);
pTileGetResource = resolver.Resolve(SYM_TILE_GETRESOURCE);
pTileGetPlacedOnFaceDataValue = resolver.Resolve(SYM_TILE_GETPLACEDONFACEDATAVALUE);
pTileCloneTileId = resolver.Resolve(SYM_TILE_CLONETILEID);
@@ -391,6 +425,26 @@ bool TileSymbols::Resolve(SymbolResolver& resolver)
pTileNeighborChanged = resolver.ResolveExact("Tile::neighborChanged");
if (resolver.IsStub(pTileTick))
pTileTick = resolver.ResolveExact("Tile::tick");
+ if (resolver.IsStub(pTileUse))
+ pTileUse = resolver.ResolveExact("Tile::use");
+ if (resolver.IsStub(pTileStepOn))
+ pTileStepOn = resolver.ResolveExact("Tile::stepOn");
+ if (resolver.IsStub(pTileEntityInside))
+ pTileEntityInside = resolver.ResolveExact("Tile::entityInside");
+ if (resolver.IsStub(pTileFallOn))
+ pTileFallOn = resolver.ResolveExact("Tile::fallOn");
+ if (resolver.IsStub(pTileOnRemoving))
+ pTileOnRemoving = resolver.ResolveExact("Tile::onRemoving");
+ if (resolver.IsStub(pTileOnRemove))
+ pTileOnRemove = resolver.ResolveExact("Tile::onRemove");
+ if (resolver.IsStub(pTileDestroy))
+ pTileDestroy = resolver.ResolveExact("Tile::destroy");
+ if (resolver.IsStub(pTilePlayerDestroy))
+ pTilePlayerDestroy = resolver.ResolveExact("Tile::playerDestroy");
+ if (resolver.IsStub(pTilePlayerWillDestroy))
+ pTilePlayerWillDestroy = resolver.ResolveExact("Tile::playerWillDestroy");
+ if (resolver.IsStub(pTileSetPlacedBy))
+ pTileSetPlacedBy = resolver.ResolveExact("Tile::setPlacedBy");
if (resolver.IsStub(pWoodSlabRegisterIcons))
pWoodSlabRegisterIcons = resolver.ResolveExact("WoodSlabTile::registerIcons");
if (resolver.IsStub(pTileClip))
@@ -403,6 +457,16 @@ void TileSymbols::Log() const
LogSym("Tile::onPlace", pTileOnPlace);
LogSym("Tile::neighborChanged", pTileNeighborChanged);
LogSym("Tile::tick", pTileTick);
+ LogSym("Tile::use", pTileUse);
+ LogSym("Tile::stepOn", pTileStepOn);
+ LogSym("Tile::entityInside", pTileEntityInside);
+ LogSym("Tile::fallOn", pTileFallOn);
+ LogSym("Tile::onRemoving", pTileOnRemoving);
+ LogSym("Tile::onRemove", pTileOnRemove);
+ LogSym("Tile::destroy", pTileDestroy);
+ LogSym("Tile::playerDestroy", pTilePlayerDestroy);
+ LogSym("Tile::playerWillDestroy", pTilePlayerWillDestroy);
+ LogSym("Tile::setPlacedBy", pTileSetPlacedBy);
LogSym("Tile::getResource", pTileGetResource);
LogSym("Tile::getPlacedOnFaceDataValue", pTileGetPlacedOnFaceDataValue);
LogSym("Tile::cloneTileId", pTileCloneTileId);
@@ -501,8 +565,10 @@ bool EntitySymbols::Resolve(SymbolResolver& resolver)
pServerPlayerGameModeUseItemOn = resolver.Resolve(SYM_SERVER_PLAYER_GAMEMODE_USEITEMON);
pMultiPlayerGameModeUseItemOn = resolver.Resolve(SYM_MULTI_PLAYER_GAMEMODE_USEITEMON);
pLevelAddEntity = resolver.Resolve(SYM_LEVEL_ADDENTITY);
+ pEntityPlayStepSound = resolver.Resolve(SYM_ENTITY_PLAYSTEPSOUND);
pEntityIONewById = resolver.Resolve(SYM_ENTITYIO_NEWBYID);
pEntityMoveTo = resolver.Resolve(SYM_ENTITY_MOVETO);
+ pEntityCheckInsideTiles = resolver.Resolve(SYM_ENTITY_CHECKINSIDETILES);
pEntitySetPos = resolver.Resolve(SYM_ENTITY_SETPOS);
pEntityGetLookAngle = resolver.Resolve(SYM_LIVINGENTITY_GETLOOKANGLE);
pLivingEntityGetPos = resolver.Resolve(SYM_LIVINGENTITY_GETPOS);
@@ -524,8 +590,10 @@ void EntitySymbols::Log() const
LogSym("ServerPlayerGameMode::useItemOn", pServerPlayerGameModeUseItemOn);
LogSym("MultiPlayerGameMode::useItemOn", pMultiPlayerGameModeUseItemOn);
LogSym("Level::addEntity", pLevelAddEntity);
+ LogSym("Entity::playStepSound", pEntityPlayStepSound);
LogSym("EntityIO::newById", pEntityIONewById);
LogSym("Entity::moveTo", pEntityMoveTo);
+ LogSym("Entity::checkInsideTiles", pEntityCheckInsideTiles);
LogSym("Entity::setPos", pEntitySetPos);
LogSym("LivingEntity/Entity::getLookAngle", pEntityGetLookAngle);
LogSym("LivingEntity::getPos", pLivingEntityGetPos);
diff --git a/WeaveLoaderRuntime/src/Symbols/SymbolGroups.h b/WeaveLoaderRuntime/src/Symbols/SymbolGroups.h
index a0f07af..c09b7e3 100644
--- a/WeaveLoaderRuntime/src/Symbols/SymbolGroups.h
+++ b/WeaveLoaderRuntime/src/Symbols/SymbolGroups.h
@@ -81,6 +81,10 @@ struct ItemSymbols
void* pItemInstanceSave = nullptr;
void* pItemInstanceLoad = nullptr;
void* pItemInstanceHurtAndBreak = nullptr;
+ void* pItemInstanceInventoryTick = nullptr;
+ void* pItemInstanceOnCraftedBy = nullptr;
+ void* pItemInstanceInteractEnemy = nullptr;
+ void* pItemInstanceHurtEnemy = nullptr;
void* pItemMineBlock = nullptr;
void* pDiggerItemMineBlock = nullptr;
void* pPickaxeItemGetDestroySpeed = nullptr;
@@ -101,6 +105,16 @@ struct TileSymbols
void* pTileOnPlace = nullptr;
void* pTileNeighborChanged = nullptr;
void* pTileTick = nullptr;
+ void* pTileUse = nullptr;
+ void* pTileStepOn = nullptr;
+ void* pTileEntityInside = nullptr;
+ void* pTileFallOn = nullptr;
+ void* pTileOnRemoving = nullptr;
+ void* pTileOnRemove = nullptr;
+ void* pTileDestroy = nullptr;
+ void* pTilePlayerDestroy = nullptr;
+ void* pTilePlayerWillDestroy = nullptr;
+ void* pTileSetPlacedBy = nullptr;
void* pTileGetResource = nullptr;
void* pTileGetPlacedOnFaceDataValue = nullptr;
void* pTileCloneTileId = nullptr;
@@ -173,9 +187,11 @@ struct EntitySymbols
void* pServerPlayerGameModeUseItemOn = nullptr;
void* pMultiPlayerGameModeUseItemOn = nullptr;
void* pLevelAddEntity = nullptr;
+ void* pEntityPlayStepSound = nullptr;
void* pEntityIONewById = nullptr;
void* pEntityMoveTo = nullptr;
void* pEntitySetPos = nullptr;
+ void* pEntityCheckInsideTiles = nullptr;
void* pEntityGetLookAngle = nullptr;
void* pLivingEntityGetPos = nullptr;
void* pLivingEntityGetViewVector = nullptr;