diff --git a/ExampleMod/ExampleMod.cs b/ExampleMod/ExampleMod.cs
index 47f5828..4f51280 100644
--- a/ExampleMod/ExampleMod.cs
+++ b/ExampleMod/ExampleMod.cs
@@ -10,19 +10,15 @@ namespace ExampleMod;
public class ExampleMod : IMod
{
public static RegisteredBlock? RubyOre;
+ public static RegisteredBlock? Orichalcum;
public static RegisteredItem? Ruby;
public static RegisteredItem? RubyPickaxeItem;
+ public static RegisteredItem? RubyShovelItem;
+ public static RegisteredItem? RubyHoeItem;
+ public static RegisteredItem? RubyAxeItem;
+ public static RegisteredItem? RubySwordItem;
public static RegisteredItem? RubyWandItem;
- private sealed class RubyPickaxe : PickaxeItem
- {
- public override MineBlockResult OnMineBlock(MineBlockContext context)
- {
- Logger.Info($"RubyPickaxe mined tile={context.TileId} at ({context.X}, {context.Y}, {context.Z})");
- return base.OnMineBlock(context);
- }
- }
-
private sealed class RubyWand : Item
{
private const long CooldownMs = 1500;
@@ -70,6 +66,31 @@ public class ExampleMod : IMod
}
}
+ private sealed class RubyShovel : ShovelItem
+ {
+ }
+
+ private sealed class RubyPickaxe : PickaxeItem
+ {
+ public override MineBlockResult OnMineBlock(MineBlockContext context)
+ {
+ Logger.Info($"RubyPickaxe mined tile={context.TileId} at ({context.X}, {context.Y}, {context.Z})");
+ return base.OnMineBlock(context);
+ }
+ }
+
+ private sealed class RubyAxe : AxeItem
+ {
+ }
+
+ private sealed class RubyHoe : HoeItem
+ {
+ }
+
+ private sealed class RubySword : SwordItem
+ {
+ }
+
public void OnInitialize()
{
RubyOre = Registry.Block.Register("examplemod:ruby_ore",
@@ -80,6 +101,20 @@ public class ExampleMod : IMod
.Sound(SoundType.Stone)
.Icon("examplemod:ruby_ore") // From assets/blocks/ruby_ore.png
.Name("Ruby Ore")
+ .RequiredHarvestLevel(2)
+ .RequiredTool(ToolType.Pickaxe)
+ .InCreativeTab(CreativeTab.BuildingBlocks));
+
+ Orichalcum = Registry.Block.Register("examplemod:orichalcum_ore",
+ new BlockProperties()
+ .Material(MaterialType.Metal)
+ .Hardness(5.0f)
+ .Resistance(30f)
+ .Sound(SoundType.Metal)
+ .Icon("examplemod:orichalcum_ore") // From assets/blocks/orichalcum.png
+ .Name("Orichalcum Ore")
+ .RequiredHarvestLevel(4)
+ .RequiredTool(ToolType.Pickaxe)
.InCreativeTab(CreativeTab.BuildingBlocks));
Ruby = Registry.Item.Register("examplemod:ruby",
@@ -89,14 +124,57 @@ public class ExampleMod : IMod
.Name("Ruby")
.InCreativeTab(CreativeTab.Materials));
- RubyPickaxeItem = Registry.Item.Register("examplemod:ruby_pickaxe", new RubyPickaxe(),
+ Registry.Item.RegisterToolMaterial("examplemod:ruby_material",
+ new ToolMaterialDefinition()
+ .BaseTier(ToolTier.Diamond)
+ .HarvestLevel(4)
+ .DestroySpeed(10.0f));
+
+ RubySwordItem = Registry.Item.Register("examplemod:ruby_sword", new RubySword { CustomMaterialId = "examplemod:ruby_material" },
new ItemProperties()
.MaxStackSize(1)
.MaxDamage(512)
+ .AttackDamage(8.0f)
+ .Icon("examplemod:ruby_sword")
+ .Name("Ruby Sword")
+ .InCreativeTab(CreativeTab.ToolsAndWeapons));
+
+ RubyShovelItem = Registry.Item.Register("examplemod:ruby_shovel", new RubyShovel { CustomMaterialId = "examplemod:ruby_material" },
+ new ItemProperties()
+ .MaxStackSize(1)
+ .MaxDamage(512)
+ .AttackDamage(4.5f)
+ .Icon("examplemod:ruby_shovel")
+ .Name("Ruby Shovel")
+ .InCreativeTab(CreativeTab.ToolsAndWeapons));
+
+ RubyPickaxeItem = Registry.Item.Register("examplemod:ruby_pickaxe", new RubyPickaxe { CustomMaterialId = "examplemod:ruby_material" },
+ new ItemProperties()
+ .MaxStackSize(1)
+ .MaxDamage(512)
+ .AttackDamage(5.0f)
.Icon("examplemod:ruby_pickaxe") // From assets/items/ruby_pickaxe.png
.Name("Ruby Pickaxe")
.InCreativeTab(CreativeTab.ToolsAndWeapons));
+ RubyAxeItem = Registry.Item.Register("examplemod:ruby_axe", new RubyAxe { CustomMaterialId = "examplemod:ruby_material" },
+ new ItemProperties()
+ .MaxStackSize(1)
+ .MaxDamage(512)
+ .AttackDamage(7.0f)
+ .Icon("examplemod:ruby_axe")
+ .Name("Ruby Axe")
+ .InCreativeTab(CreativeTab.ToolsAndWeapons));
+
+ RubyHoeItem = Registry.Item.Register("examplemod:ruby_hoe", new RubyHoe { CustomMaterialId = "examplemod:ruby_material" },
+ new ItemProperties()
+ .MaxStackSize(1)
+ .MaxDamage(512)
+ .AttackDamage(1.0f)
+ .Icon("examplemod:ruby_hoe")
+ .Name("Ruby Hoe")
+ .InCreativeTab(CreativeTab.ToolsAndWeapons));
+
RubyWandItem = Registry.Item.Register("examplemod:ruby_wand", new RubyWand(),
new ItemProperties()
.MaxStackSize(1)
diff --git a/ExampleMod/assets/blocks/orichalcum_ore.png b/ExampleMod/assets/blocks/orichalcum_ore.png
new file mode 100644
index 0000000..b325e81
Binary files /dev/null and b/ExampleMod/assets/blocks/orichalcum_ore.png differ
diff --git a/ExampleMod/assets/items/ruby_axe.png b/ExampleMod/assets/items/ruby_axe.png
new file mode 100644
index 0000000..7278c5a
Binary files /dev/null and b/ExampleMod/assets/items/ruby_axe.png differ
diff --git a/ExampleMod/assets/items/ruby_hoe.png b/ExampleMod/assets/items/ruby_hoe.png
new file mode 100644
index 0000000..fa043d4
Binary files /dev/null and b/ExampleMod/assets/items/ruby_hoe.png differ
diff --git a/ExampleMod/assets/items/ruby_pickaxe.png b/ExampleMod/assets/items/ruby_pickaxe.png
index b019ba4..0bab1f0 100644
Binary files a/ExampleMod/assets/items/ruby_pickaxe.png and b/ExampleMod/assets/items/ruby_pickaxe.png differ
diff --git a/ExampleMod/assets/items/ruby_shovel.png b/ExampleMod/assets/items/ruby_shovel.png
new file mode 100644
index 0000000..0b05c3e
Binary files /dev/null and b/ExampleMod/assets/items/ruby_shovel.png differ
diff --git a/ExampleMod/assets/items/ruby_sword.png b/ExampleMod/assets/items/ruby_sword.png
new file mode 100644
index 0000000..413842d
Binary files /dev/null and b/ExampleMod/assets/items/ruby_sword.png differ
diff --git a/ExampleMod/assets/lang/en-GB.lang b/ExampleMod/assets/lang/en-GB.lang
index 9685ffa..794d23f 100644
--- a/ExampleMod/assets/lang/en-GB.lang
+++ b/ExampleMod/assets/lang/en-GB.lang
@@ -4,4 +4,11 @@
# This file documents the expected format for future multi-locale support.
block.examplemod.ruby_ore=Ruby Ore
+block.examplemod.orichalcum_ore=Orichalcum Ore
item.examplemod.ruby=Ruby
+item.examplemod.ruby_sword=Ruby Sword
+item.examplemod.ruby_shovel=Ruby Shovel
+item.examplemod.ruby_pickaxe=Ruby Pickaxe
+item.examplemod.ruby_axe=Ruby Axe
+item.examplemod.ruby_hoe=Ruby Hoe
+item.examplemod.ruby_wand=Ruby Wand
diff --git a/WeaveLoader.API/Block/BlockProperties.cs b/WeaveLoader.API/Block/BlockProperties.cs
index cb0be13..81ed605 100644
--- a/WeaveLoader.API/Block/BlockProperties.cs
+++ b/WeaveLoader.API/Block/BlockProperties.cs
@@ -1,5 +1,20 @@
namespace WeaveLoader.API.Block;
+///
+/// Tool type required to harvest a block. Used with .
+///
+public enum ToolType
+{
+ /// No specific tool required; any tool or hand can harvest.
+ None = 0,
+ /// Requires a pickaxe.
+ Pickaxe = 1,
+ /// Requires an axe.
+ Axe = 2,
+ /// Requires a shovel.
+ Shovel = 3,
+}
+
public enum MaterialType
{
Air = 0,
@@ -48,6 +63,8 @@ public class BlockProperties
internal int LightBlockValue = 255;
internal CreativeTab CreativeTabValue = CreativeTab.None;
internal string? NameValue;
+ internal int RequiredHarvestLevelValue = -1;
+ internal ToolType RequiredToolValue = ToolType.None;
public BlockProperties Material(MaterialType material) { MaterialValue = material; return this; }
public BlockProperties Hardness(float hardness) { HardnessValue = hardness; return this; }
@@ -61,4 +78,8 @@ public class BlockProperties
public BlockProperties InCreativeTab(CreativeTab tab) { CreativeTabValue = tab; return this; }
/// Display name shown in-game (e.g. "Ruby Ore"). Used for localization.
public BlockProperties Name(string displayName) { NameValue = displayName; return this; }
+ /// Minimum harvest level required to properly mine this block (e.g. 3 for obsidian). -1 means no requirement.
+ public BlockProperties RequiredHarvestLevel(int level) { RequiredHarvestLevelValue = level; return this; }
+ /// Tool type required to harvest this block (e.g. Pickaxe for stone-like blocks).
+ public BlockProperties RequiredTool(ToolType tool) { RequiredToolValue = tool; return this; }
}
diff --git a/WeaveLoader.API/Block/BlockRegistry.cs b/WeaveLoader.API/Block/BlockRegistry.cs
index 67c44ca..8fd8434 100644
--- a/WeaveLoader.API/Block/BlockRegistry.cs
+++ b/WeaveLoader.API/Block/BlockRegistry.cs
@@ -46,7 +46,9 @@ public static class BlockRegistry
properties.IconValue,
properties.LightEmissionValue,
properties.LightBlockValue,
- properties.NameValue ?? "");
+ properties.NameValue ?? "",
+ properties.RequiredHarvestLevelValue,
+ (int)properties.RequiredToolValue);
if (numericId < 0)
throw new InvalidOperationException($"Failed to register block '{id}'. No free IDs or invalid parameters.");
diff --git a/WeaveLoader.API/Item/CustomItem.cs b/WeaveLoader.API/Item/CustomItem.cs
index da7c6af..7a9ce27 100644
--- a/WeaveLoader.API/Item/CustomItem.cs
+++ b/WeaveLoader.API/Item/CustomItem.cs
@@ -63,9 +63,31 @@ public enum ToolTier
/// Managed pickaxe base class.
/// Override callbacks to customize behavior.
///
-public class PickaxeItem : Item
+public abstract class ToolItem : Item
{
public ToolTier Tier { get; init; } = ToolTier.Diamond;
+ public Identifier? CustomMaterialId { get; init; }
+}
+
+public class PickaxeItem : ToolItem
+{
+ public Identifier? CustomTierId { get; init; }
+}
+
+public class ShovelItem : ToolItem
+{
+}
+
+public class HoeItem : ToolItem
+{
+}
+
+public class AxeItem : ToolItem
+{
+}
+
+public class SwordItem : ToolItem
+{
}
///
diff --git a/WeaveLoader.API/Item/ItemProperties.cs b/WeaveLoader.API/Item/ItemProperties.cs
index 0d07b9b..3065af5 100644
--- a/WeaveLoader.API/Item/ItemProperties.cs
+++ b/WeaveLoader.API/Item/ItemProperties.cs
@@ -7,6 +7,7 @@ public class ItemProperties
{
internal int MaxStackSizeValue = 64;
internal int MaxDamageValue = 0;
+ internal float AttackDamageValue = 0.0f;
internal string IconValue = "";
internal CreativeTab CreativeTabValue = CreativeTab.None;
internal string? NameValue;
@@ -20,6 +21,8 @@ public class ItemProperties
/// makes the item damageable with a durability bar.
///
public ItemProperties MaxDamage(int damage) { MaxDamageValue = damage; MaxStackSizeValue = 1; return this; }
+ /// Override the native attack damage value for tool items.
+ public ItemProperties AttackDamage(float damage) { AttackDamageValue = damage; return this; }
public ItemProperties InCreativeTab(CreativeTab tab) { CreativeTabValue = tab; return this; }
/// Display name shown in-game (e.g. "Ruby"). Used for localization.
public ItemProperties Name(string displayName) { NameValue = displayName; return this; }
diff --git a/WeaveLoader.API/Item/ItemRegistry.cs b/WeaveLoader.API/Item/ItemRegistry.cs
index c71589d..ae133ea 100644
--- a/WeaveLoader.API/Item/ItemRegistry.cs
+++ b/WeaveLoader.API/Item/ItemRegistry.cs
@@ -29,6 +29,56 @@ public static class ItemRegistry
private static readonly object s_lock = new();
private static readonly Dictionary s_idByNumeric = new();
+ private enum ToolKind
+ {
+ Pickaxe = 1,
+ Shovel = 2,
+ Hoe = 3,
+ Sword = 4,
+ Axe = 5,
+ }
+
+ private static Identifier? ResolveCustomMaterialId(Item managedItem)
+ {
+ if (managedItem is ToolItem toolItem && toolItem.CustomMaterialId is Identifier customMaterialId)
+ return customMaterialId;
+
+ if (managedItem is PickaxeItem pickaxeItem && pickaxeItem.CustomTierId is Identifier legacyPickaxeTierId)
+ return legacyPickaxeTierId;
+
+ return null;
+ }
+
+ private static ToolMaterialDefinition? ResolveToolMaterial(Identifier itemId, Item managedItem)
+ {
+ Identifier? customMaterialId = ResolveCustomMaterialId(managedItem);
+ if (customMaterialId == null)
+ return null;
+
+ Identifier resolvedMaterialId = (Identifier)customMaterialId;
+
+ if (!ToolMaterialRegistry.TryGetDefinition(resolvedMaterialId, out ToolMaterialDefinition? definition) || definition == null)
+ throw new InvalidOperationException($"Unknown tool material '{resolvedMaterialId}' for item '{itemId}'.");
+
+ return definition;
+ }
+
+ private static void ConfigureToolMaterial(Identifier itemId, int numericId, ToolKind toolKind, ToolMaterialDefinition? material, ItemProperties properties)
+ {
+ if (material == null && properties.AttackDamageValue <= 0.0f)
+ return;
+
+ int configured = NativeInterop.native_configure_custom_tool_item(
+ numericId,
+ (int)toolKind,
+ material?.HarvestLevelValue ?? 0,
+ material?.DestroySpeedValue ?? 1.0f,
+ properties.AttackDamageValue);
+
+ if (configured == 0)
+ throw new InvalidOperationException($"Failed to configure custom tool material for item '{itemId}'.");
+ }
+
///
/// Register a new item with the game engine.
///
@@ -57,12 +107,71 @@ public static class ItemRegistry
int numericId;
if (managedItem is PickaxeItem pickaxeItem)
{
+ ToolMaterialDefinition? material = ResolveToolMaterial(id, pickaxeItem);
+ ToolTier nativeTier = material?.BaseTierValue ?? pickaxeItem.Tier;
+ int maxDamage = properties.MaxDamageValue;
+
numericId = NativeInterop.native_register_pickaxe_item(
id.ToString(),
- (int)pickaxeItem.Tier,
+ (int)nativeTier,
+ maxDamage,
+ properties.IconValue,
+ properties.NameValue ?? "");
+
+ if (numericId >= 0)
+ ConfigureToolMaterial(id, numericId, ToolKind.Pickaxe, material, properties);
+ }
+ else if (managedItem is ShovelItem shovelItem)
+ {
+ ToolMaterialDefinition? material = ResolveToolMaterial(id, shovelItem);
+ numericId = NativeInterop.native_register_shovel_item(
+ id.ToString(),
+ (int)(material?.BaseTierValue ?? shovelItem.Tier),
properties.MaxDamageValue,
properties.IconValue,
properties.NameValue ?? "");
+
+ if (numericId >= 0)
+ ConfigureToolMaterial(id, numericId, ToolKind.Shovel, material, properties);
+ }
+ else if (managedItem is HoeItem hoeItem)
+ {
+ ToolMaterialDefinition? material = ResolveToolMaterial(id, hoeItem);
+ numericId = NativeInterop.native_register_hoe_item(
+ id.ToString(),
+ (int)(material?.BaseTierValue ?? hoeItem.Tier),
+ properties.MaxDamageValue,
+ properties.IconValue,
+ properties.NameValue ?? "");
+
+ if (numericId >= 0)
+ ConfigureToolMaterial(id, numericId, ToolKind.Hoe, material, properties);
+ }
+ else if (managedItem is AxeItem axeItem)
+ {
+ ToolMaterialDefinition? material = ResolveToolMaterial(id, axeItem);
+ numericId = NativeInterop.native_register_axe_item(
+ id.ToString(),
+ (int)(material?.BaseTierValue ?? axeItem.Tier),
+ properties.MaxDamageValue,
+ properties.IconValue,
+ properties.NameValue ?? "");
+
+ if (numericId >= 0)
+ ConfigureToolMaterial(id, numericId, ToolKind.Axe, material, properties);
+ }
+ else if (managedItem is SwordItem swordItem)
+ {
+ ToolMaterialDefinition? material = ResolveToolMaterial(id, swordItem);
+ numericId = NativeInterop.native_register_sword_item(
+ id.ToString(),
+ (int)(material?.BaseTierValue ?? swordItem.Tier),
+ properties.MaxDamageValue,
+ properties.IconValue,
+ properties.NameValue ?? "");
+
+ if (numericId >= 0)
+ ConfigureToolMaterial(id, numericId, ToolKind.Sword, material, properties);
}
else
{
diff --git a/WeaveLoader.API/Item/PickaxeTierRegistry.cs b/WeaveLoader.API/Item/PickaxeTierRegistry.cs
new file mode 100644
index 0000000..d36e755
--- /dev/null
+++ b/WeaveLoader.API/Item/PickaxeTierRegistry.cs
@@ -0,0 +1,59 @@
+namespace WeaveLoader.API.Item;
+
+public sealed class PickaxeTierDefinition
+{
+ private readonly ToolMaterialDefinition _inner = new();
+
+ internal ToolMaterialDefinition ToToolMaterialDefinition() => _inner;
+
+ public PickaxeTierDefinition BaseTier(ToolTier tier)
+ {
+ _inner.BaseTier(tier);
+ return this;
+ }
+
+ public PickaxeTierDefinition HarvestLevel(int harvestLevel)
+ {
+ if (harvestLevel < 0)
+ throw new ArgumentOutOfRangeException(nameof(harvestLevel));
+
+ _inner.HarvestLevel(harvestLevel);
+ return this;
+ }
+
+ public PickaxeTierDefinition DestroySpeed(float destroySpeed)
+ {
+ if (destroySpeed <= 0.0f)
+ throw new ArgumentOutOfRangeException(nameof(destroySpeed));
+
+ _inner.DestroySpeed(destroySpeed);
+ return this;
+ }
+}
+
+public sealed class RegisteredPickaxeTier
+{
+ public Identifier StringId { get; }
+
+ internal RegisteredPickaxeTier(Identifier stringId)
+ {
+ StringId = stringId;
+ }
+}
+
+public static class PickaxeTierRegistry
+{
+ public static RegisteredPickaxeTier Register(Identifier id, PickaxeTierDefinition definition)
+ {
+ ArgumentNullException.ThrowIfNull(definition);
+ ToolMaterialRegistry.Register(id, definition.ToToolMaterialDefinition());
+
+ return new RegisteredPickaxeTier(id);
+ }
+
+ internal static bool TryGetDefinition(Identifier id, out PickaxeTierDefinition? definition)
+ {
+ definition = null;
+ return false;
+ }
+}
diff --git a/WeaveLoader.API/Item/ToolMaterialRegistry.cs b/WeaveLoader.API/Item/ToolMaterialRegistry.cs
new file mode 100644
index 0000000..9567db9
--- /dev/null
+++ b/WeaveLoader.API/Item/ToolMaterialRegistry.cs
@@ -0,0 +1,72 @@
+namespace WeaveLoader.API.Item;
+
+public sealed class ToolMaterialDefinition
+{
+ public ToolTier BaseTierValue { get; private set; } = ToolTier.Diamond;
+ public int HarvestLevelValue { get; private set; } = 3;
+ public float DestroySpeedValue { get; private set; } = 8.0f;
+
+ public ToolMaterialDefinition BaseTier(ToolTier tier)
+ {
+ BaseTierValue = tier;
+ return this;
+ }
+
+ public ToolMaterialDefinition HarvestLevel(int harvestLevel)
+ {
+ if (harvestLevel < 0)
+ throw new ArgumentOutOfRangeException(nameof(harvestLevel));
+
+ HarvestLevelValue = harvestLevel;
+ return this;
+ }
+
+ public ToolMaterialDefinition DestroySpeed(float destroySpeed)
+ {
+ if (destroySpeed <= 0.0f)
+ throw new ArgumentOutOfRangeException(nameof(destroySpeed));
+
+ DestroySpeedValue = destroySpeed;
+ return this;
+ }
+
+}
+
+public sealed class RegisteredToolMaterial
+{
+ public Identifier StringId { get; }
+
+ internal RegisteredToolMaterial(Identifier stringId)
+ {
+ StringId = stringId;
+ }
+}
+
+public static class ToolMaterialRegistry
+{
+ private static readonly object s_lock = new();
+ private static readonly Dictionary s_materials = new();
+
+ public static RegisteredToolMaterial Register(Identifier id, ToolMaterialDefinition definition)
+ {
+ ArgumentNullException.ThrowIfNull(definition);
+
+ lock (s_lock)
+ {
+ if (s_materials.ContainsKey(id))
+ throw new InvalidOperationException($"Tool material '{id}' is already registered.");
+
+ s_materials[id] = definition;
+ }
+
+ return new RegisteredToolMaterial(id);
+ }
+
+ internal static bool TryGetDefinition(Identifier id, out ToolMaterialDefinition? definition)
+ {
+ lock (s_lock)
+ {
+ return s_materials.TryGetValue(id, out definition);
+ }
+ }
+}
diff --git a/WeaveLoader.API/NativeInterop.cs b/WeaveLoader.API/NativeInterop.cs
index c0b6e06..d09cd0d 100644
--- a/WeaveLoader.API/NativeInterop.cs
+++ b/WeaveLoader.API/NativeInterop.cs
@@ -20,7 +20,9 @@ internal static class NativeInterop
string iconName,
float lightEmission,
int lightBlock,
- string displayName);
+ string displayName,
+ int requiredHarvestLevel,
+ int requiredTool);
[DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern int native_register_item(
@@ -38,6 +40,52 @@ internal static class NativeInterop
string iconName,
string displayName);
+ [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+ internal static extern int native_register_shovel_item(
+ string namespacedId,
+ int tier,
+ int maxDamage,
+ string iconName,
+ string displayName);
+
+ [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+ internal static extern int native_register_hoe_item(
+ string namespacedId,
+ int tier,
+ int maxDamage,
+ string iconName,
+ string displayName);
+
+ [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+ internal static extern int native_register_axe_item(
+ string namespacedId,
+ int tier,
+ int maxDamage,
+ string iconName,
+ string displayName);
+
+ [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+ internal static extern int native_register_sword_item(
+ string namespacedId,
+ int tier,
+ int maxDamage,
+ string iconName,
+ string displayName);
+
+ [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern int native_configure_custom_pickaxe_item(
+ int numericItemId,
+ int harvestLevel,
+ float destroySpeed);
+
+ [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern int native_configure_custom_tool_item(
+ int numericItemId,
+ int toolKind,
+ int harvestLevel,
+ float destroySpeed,
+ float attackDamage);
+
[DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern int native_allocate_description_id();
diff --git a/WeaveLoader.API/Registry.cs b/WeaveLoader.API/Registry.cs
index 6777ad9..a2c0807 100644
--- a/WeaveLoader.API/Registry.cs
+++ b/WeaveLoader.API/Registry.cs
@@ -27,6 +27,12 @@ public static class Registry
public static RegisteredItem Register(Identifier id, WeaveLoader.API.Item.Item item, ItemProperties properties)
=> ItemRegistry.Register(id, item, properties);
+
+ public static RegisteredToolMaterial RegisterToolMaterial(Identifier id, ToolMaterialDefinition definition)
+ => ToolMaterialRegistry.Register(id, definition);
+
+ public static RegisteredPickaxeTier RegisterPickaxeTier(Identifier id, PickaxeTierDefinition definition)
+ => PickaxeTierRegistry.Register(id, definition);
}
/// Entity registration. Call Register() with a namespaced ID and EntityDefinition.
diff --git a/WeaveLoaderRuntime/CMakeLists.txt b/WeaveLoaderRuntime/CMakeLists.txt
index 25c8b3b..d7a073c 100644
--- a/WeaveLoaderRuntime/CMakeLists.txt
+++ b/WeaveLoaderRuntime/CMakeLists.txt
@@ -87,6 +87,9 @@ add_library(WeaveLoaderRuntime SHARED
src/DotNetHost.cpp
src/IdRegistry.cpp
src/NativeExports.cpp
+ src/CustomPickaxeRegistry.cpp
+ src/CustomToolMaterialRegistry.cpp
+ src/CustomBlockRegistry.cpp
src/CreativeInventory.cpp
src/FurnaceRecipeRegistry.cpp
src/MainMenuOverlay.cpp
diff --git a/WeaveLoaderRuntime/src/CustomBlockRegistry.cpp b/WeaveLoaderRuntime/src/CustomBlockRegistry.cpp
new file mode 100644
index 0000000..0e668af
--- /dev/null
+++ b/WeaveLoaderRuntime/src/CustomBlockRegistry.cpp
@@ -0,0 +1,26 @@
+#include "CustomBlockRegistry.h"
+#include
+
+namespace
+{
+ std::unordered_map g_definitions;
+}
+
+namespace CustomBlockRegistry
+{
+ void Register(int blockId, int requiredHarvestLevel, int requiredTool)
+ {
+ Definition def;
+ def.requiredHarvestLevel = requiredHarvestLevel;
+ def.requiredTool = static_cast(requiredTool);
+ g_definitions[blockId] = def;
+ }
+
+ const Definition* Find(int blockId)
+ {
+ const auto it = g_definitions.find(blockId);
+ if (it == g_definitions.end())
+ return nullptr;
+ return &it->second;
+ }
+}
diff --git a/WeaveLoaderRuntime/src/CustomBlockRegistry.h b/WeaveLoaderRuntime/src/CustomBlockRegistry.h
new file mode 100644
index 0000000..de35d0a
--- /dev/null
+++ b/WeaveLoaderRuntime/src/CustomBlockRegistry.h
@@ -0,0 +1,21 @@
+#pragma once
+
+namespace CustomBlockRegistry
+{
+ enum class ToolType : int
+ {
+ None = 0,
+ Pickaxe = 1,
+ Axe = 2,
+ Shovel = 3,
+ };
+
+ struct Definition
+ {
+ int requiredHarvestLevel = -1;
+ ToolType requiredTool = ToolType::None;
+ };
+
+ void Register(int blockId, int requiredHarvestLevel, int requiredTool);
+ const Definition* Find(int blockId);
+}
diff --git a/WeaveLoaderRuntime/src/CustomPickaxeRegistry.cpp b/WeaveLoaderRuntime/src/CustomPickaxeRegistry.cpp
new file mode 100644
index 0000000..5ab531c
--- /dev/null
+++ b/WeaveLoaderRuntime/src/CustomPickaxeRegistry.cpp
@@ -0,0 +1,26 @@
+#include "CustomPickaxeRegistry.h"
+
+namespace
+{
+ std::unordered_map g_definitions;
+}
+
+namespace CustomPickaxeRegistry
+{
+ void Register(int itemId, int harvestLevel, float destroySpeed)
+ {
+ Definition definition;
+ definition.harvestLevel = harvestLevel;
+ definition.destroySpeed = destroySpeed;
+
+ g_definitions[itemId] = std::move(definition);
+ }
+
+ const Definition* Find(int itemId)
+ {
+ const auto it = g_definitions.find(itemId);
+ if (it == g_definitions.end())
+ return nullptr;
+ return &it->second;
+ }
+}
diff --git a/WeaveLoaderRuntime/src/CustomPickaxeRegistry.h b/WeaveLoaderRuntime/src/CustomPickaxeRegistry.h
new file mode 100644
index 0000000..b233645
--- /dev/null
+++ b/WeaveLoaderRuntime/src/CustomPickaxeRegistry.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include
+namespace CustomPickaxeRegistry
+{
+ struct Definition
+ {
+ int harvestLevel = 0;
+ float destroySpeed = 1.0f;
+ };
+
+ void Register(int itemId, int harvestLevel, float destroySpeed);
+ const Definition* Find(int itemId);
+}
diff --git a/WeaveLoaderRuntime/src/CustomToolMaterialRegistry.cpp b/WeaveLoaderRuntime/src/CustomToolMaterialRegistry.cpp
new file mode 100644
index 0000000..8a58615
--- /dev/null
+++ b/WeaveLoaderRuntime/src/CustomToolMaterialRegistry.cpp
@@ -0,0 +1,27 @@
+#include "CustomToolMaterialRegistry.h"
+
+namespace
+{
+ std::unordered_map g_definitions;
+}
+
+namespace CustomToolMaterialRegistry
+{
+ void Register(int itemId, ToolKind toolKind, int harvestLevel, float destroySpeed, float attackDamage)
+ {
+ Definition definition;
+ definition.toolKind = toolKind;
+ definition.harvestLevel = harvestLevel;
+ definition.destroySpeed = destroySpeed;
+ definition.attackDamage = attackDamage;
+ g_definitions[itemId] = definition;
+ }
+
+ const Definition* Find(int itemId)
+ {
+ const auto it = g_definitions.find(itemId);
+ if (it == g_definitions.end())
+ return nullptr;
+ return &it->second;
+ }
+}
diff --git a/WeaveLoaderRuntime/src/CustomToolMaterialRegistry.h b/WeaveLoaderRuntime/src/CustomToolMaterialRegistry.h
new file mode 100644
index 0000000..94d9745
--- /dev/null
+++ b/WeaveLoaderRuntime/src/CustomToolMaterialRegistry.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include
+
+namespace CustomToolMaterialRegistry
+{
+ enum class ToolKind : int
+ {
+ Pickaxe = 1,
+ Shovel = 2,
+ Hoe = 3,
+ Sword = 4,
+ Axe = 5,
+ };
+
+ struct Definition
+ {
+ ToolKind toolKind = ToolKind::Pickaxe;
+ int harvestLevel = 0;
+ float destroySpeed = 1.0f;
+ float attackDamage = 0.0f;
+ };
+
+ void Register(int itemId, ToolKind toolKind, int harvestLevel, float destroySpeed, float attackDamage);
+ const Definition* Find(int itemId);
+}
diff --git a/WeaveLoaderRuntime/src/GameHooks.cpp b/WeaveLoaderRuntime/src/GameHooks.cpp
index 67e0c0c..268dc8b 100644
--- a/WeaveLoaderRuntime/src/GameHooks.cpp
+++ b/WeaveLoaderRuntime/src/GameHooks.cpp
@@ -4,6 +4,9 @@
#include "MainMenuOverlay.h"
#include "ModStrings.h"
#include "ModAtlas.h"
+#include "CustomPickaxeRegistry.h"
+#include "CustomToolMaterialRegistry.h"
+#include "CustomBlockRegistry.h"
#include "LogUtil.h"
#include
#include
@@ -40,6 +43,11 @@ namespace GameHooks
ItemInstanceMineBlock_fn Original_ItemInstanceMineBlock = nullptr;
ItemMineBlock_fn Original_ItemMineBlock = nullptr;
ItemMineBlock_fn Original_DiggerItemMineBlock = nullptr;
+ PickaxeGetDestroySpeed_fn Original_PickaxeItemGetDestroySpeed = nullptr;
+ PickaxeCanDestroySpecial_fn Original_PickaxeItemCanDestroySpecial = nullptr;
+ PickaxeGetDestroySpeed_fn Original_ShovelItemGetDestroySpeed = nullptr;
+ PickaxeCanDestroySpecial_fn Original_ShovelItemCanDestroySpecial = nullptr;
+ PlayerCanDestroy_fn Original_PlayerCanDestroy = nullptr;
GameModeUseItem_fn Original_ServerPlayerGameModeUseItem = nullptr;
GameModeUseItem_fn Original_MultiPlayerGameModeUseItem = nullptr;
MinecraftSetLevel_fn Original_MinecraftSetLevel = nullptr;
@@ -66,6 +74,8 @@ namespace GameHooks
// Verified from compiled Player::inventory accesses in this game build.
static constexpr ptrdiff_t kPlayerInventoryOffset = 0x340;
static constexpr ptrdiff_t kLevelIsClientSideOffset = 0x268;
+ static constexpr ptrdiff_t kItemIdOffset = 0x20;
+ static constexpr ptrdiff_t kTileIdOffset = 0x28;
static constexpr ptrdiff_t kEntityXOffset = 0x78;
static constexpr ptrdiff_t kEntityYOffset = 0x80;
static constexpr ptrdiff_t kEntityZOffset = 0x88;
@@ -78,6 +88,7 @@ namespace GameHooks
static void* s_textureAtlasLocationItems = nullptr;
static int s_textureAtlasIdBlocks = -1;
static int s_textureAtlasIdItems = -1;
+ static void* s_tileTilesArray = nullptr;
static thread_local bool s_inLoadTextureByNameHook = false;
static thread_local bool s_hasForcedBillboardRoute = false;
static thread_local int s_forcedBillboardAtlas = -1;
@@ -758,6 +769,11 @@ namespace GameHooks
LogUtil::Log("[WeaveLoader] Atlas IDs: blocks=%d items=%d", s_textureAtlasIdBlocks, s_textureAtlasIdItems);
}
+ void SetTileTilesArray(void* tilesArray)
+ {
+ s_tileTilesArray = tilesArray;
+ }
+
static bool TryReadVec3(void* vecPtr, double& x, double& y, double& z)
{
if (!IsReadableRange(vecPtr, sizeof(double) * 3))
@@ -1207,6 +1223,232 @@ namespace GameHooks
return false;
}
+ static bool TryReadItemIdFromPickaxe(void* pickaxeItemPtr, int& outItemId)
+ {
+ if (!pickaxeItemPtr || !IsReadableRange(pickaxeItemPtr, kItemIdOffset + sizeof(int)))
+ return false;
+ int itemId = *reinterpret_cast(static_cast(pickaxeItemPtr) + kItemIdOffset);
+ if (itemId >= 0 && itemId < 32000)
+ {
+ outItemId = itemId;
+ return true;
+ }
+ return false;
+ }
+
+ static constexpr int TILE_NUM_COUNT = 4096;
+
+ static bool TryReadTileId(void* tilePtr, int& outTileId)
+ {
+ if (!tilePtr)
+ return false;
+ if (IsReadableRange(tilePtr, kTileIdOffset + sizeof(int)))
+ {
+ int id = *reinterpret_cast(static_cast(tilePtr) + kTileIdOffset);
+ if (id >= 0 && id < TILE_NUM_COUNT)
+ {
+ outTileId = id;
+ return true;
+ }
+ }
+ // Fallback: resolve via Tile::tiles (Tile** - pointer to array). Must dereference once.
+ if (s_tileTilesArray && IsReadableRange(s_tileTilesArray, sizeof(void*)))
+ {
+ const void* arrayPtr = *reinterpret_cast(s_tileTilesArray);
+ if (arrayPtr && IsReadableRange(arrayPtr, TILE_NUM_COUNT * sizeof(void*)))
+ {
+ const void* const* tiles = reinterpret_cast(arrayPtr);
+ for (int i = 0; i < TILE_NUM_COUNT; i++)
+ {
+ if (tiles[i] == tilePtr)
+ {
+ outTileId = i;
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ static int GetToolHarvestLevel(void* diggerItemPtr, int itemId)
+ {
+ const CustomToolMaterialRegistry::Definition* def = CustomToolMaterialRegistry::Find(itemId);
+ if (def)
+ return def->harvestLevel;
+ if (!diggerItemPtr || !IsReadableRange(diggerItemPtr, 0xA8 + sizeof(void*)))
+ return -1;
+ const void* tierPtr = *reinterpret_cast(static_cast(diggerItemPtr) + 0xA8);
+ if (!tierPtr || !IsReadableRange(tierPtr, sizeof(int)))
+ return -1;
+ return *reinterpret_cast(tierPtr);
+ }
+
+ static float GetToolDestroySpeed(void* diggerItemPtr, int itemId)
+ {
+ const CustomToolMaterialRegistry::Definition* def = CustomToolMaterialRegistry::Find(itemId);
+ if (def)
+ return def->destroySpeed;
+ if (!diggerItemPtr || !IsReadableRange(static_cast(diggerItemPtr) + 0xA0, sizeof(float)))
+ return 1.0f;
+ return *reinterpret_cast(static_cast(diggerItemPtr) + 0xA0);
+ }
+
+ float __fastcall Hooked_PickaxeItemGetDestroySpeed(void* thisPtr, void* itemInstanceSharedPtr, void* tilePtr)
+ {
+ void* itemInstancePtr = DecodeItemInstancePtrFromSharedArg(itemInstanceSharedPtr);
+ int itemId = 0;
+ if (!TryReadItemId(itemInstancePtr, itemId))
+ {
+ if (Original_PickaxeItemGetDestroySpeed)
+ return Original_PickaxeItemGetDestroySpeed(thisPtr, itemInstanceSharedPtr, tilePtr);
+ return 1.0f;
+ }
+
+ int tileId = 0;
+ if (tilePtr && TryReadTileId(tilePtr, tileId))
+ {
+ const CustomBlockRegistry::Definition* blockDef = CustomBlockRegistry::Find(tileId);
+ if (blockDef && blockDef->requiredTool == CustomBlockRegistry::ToolType::Pickaxe)
+ {
+ int harvestLevel = GetToolHarvestLevel(thisPtr, itemId);
+ if (harvestLevel >= 0 &&
+ (blockDef->requiredHarvestLevel < 0 || harvestLevel >= blockDef->requiredHarvestLevel))
+ {
+ return GetToolDestroySpeed(thisPtr, itemId);
+ }
+ // Block requires pickaxe but harvest level insufficient - return slow speed
+ return 1.0f;
+ }
+ }
+
+ if (Original_PickaxeItemGetDestroySpeed)
+ return Original_PickaxeItemGetDestroySpeed(thisPtr, itemInstanceSharedPtr, tilePtr);
+ return 1.0f;
+ }
+
+ bool __fastcall Hooked_PickaxeItemCanDestroySpecial(void* thisPtr, void* tilePtr)
+ {
+ int itemId = 0;
+ if (!TryReadItemIdFromPickaxe(thisPtr, itemId))
+ {
+ if (Original_PickaxeItemCanDestroySpecial)
+ return Original_PickaxeItemCanDestroySpecial(thisPtr, tilePtr);
+ return false;
+ }
+
+ int tileId = 0;
+ if (tilePtr && TryReadTileId(tilePtr, tileId))
+ {
+ const CustomBlockRegistry::Definition* blockDef = CustomBlockRegistry::Find(tileId);
+ if (blockDef && blockDef->requiredTool == CustomBlockRegistry::ToolType::Pickaxe)
+ {
+ int harvestLevel = GetToolHarvestLevel(thisPtr, itemId);
+ if (harvestLevel >= 0 &&
+ (blockDef->requiredHarvestLevel < 0 || harvestLevel >= blockDef->requiredHarvestLevel))
+ {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ if (Original_PickaxeItemCanDestroySpecial)
+ return Original_PickaxeItemCanDestroySpecial(thisPtr, tilePtr);
+ return false;
+ }
+
+ float __fastcall Hooked_ShovelItemGetDestroySpeed(void* thisPtr, void* itemInstanceSharedPtr, void* tilePtr)
+ {
+ void* itemInstancePtr = DecodeItemInstancePtrFromSharedArg(itemInstanceSharedPtr);
+ int itemId = 0;
+ if (!TryReadItemId(itemInstancePtr, itemId))
+ {
+ if (Original_ShovelItemGetDestroySpeed)
+ return Original_ShovelItemGetDestroySpeed(thisPtr, itemInstanceSharedPtr, tilePtr);
+ return 1.0f;
+ }
+
+ int tileId = 0;
+ if (tilePtr && TryReadTileId(tilePtr, tileId))
+ {
+ const CustomBlockRegistry::Definition* blockDef = CustomBlockRegistry::Find(tileId);
+ if (blockDef && blockDef->requiredTool == CustomBlockRegistry::ToolType::Shovel)
+ {
+ int harvestLevel = GetToolHarvestLevel(thisPtr, itemId);
+ if (harvestLevel >= 0 &&
+ (blockDef->requiredHarvestLevel < 0 || harvestLevel >= blockDef->requiredHarvestLevel))
+ {
+ return GetToolDestroySpeed(thisPtr, itemId);
+ }
+ return 1.0f;
+ }
+ }
+
+ if (Original_ShovelItemGetDestroySpeed)
+ return Original_ShovelItemGetDestroySpeed(thisPtr, itemInstanceSharedPtr, tilePtr);
+ return 1.0f;
+ }
+
+ bool __fastcall Hooked_ShovelItemCanDestroySpecial(void* thisPtr, void* tilePtr)
+ {
+ int itemId = 0;
+ if (!TryReadItemIdFromPickaxe(thisPtr, itemId))
+ {
+ if (Original_ShovelItemCanDestroySpecial)
+ return Original_ShovelItemCanDestroySpecial(thisPtr, tilePtr);
+ return false;
+ }
+
+ int tileId = 0;
+ if (tilePtr && TryReadTileId(tilePtr, tileId))
+ {
+ const CustomBlockRegistry::Definition* blockDef = CustomBlockRegistry::Find(tileId);
+ if (blockDef && blockDef->requiredTool == CustomBlockRegistry::ToolType::Shovel)
+ {
+ int harvestLevel = GetToolHarvestLevel(thisPtr, itemId);
+ if (harvestLevel >= 0 &&
+ (blockDef->requiredHarvestLevel < 0 || harvestLevel >= blockDef->requiredHarvestLevel))
+ {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ if (Original_ShovelItemCanDestroySpecial)
+ return Original_ShovelItemCanDestroySpecial(thisPtr, tilePtr);
+ return false;
+ }
+
+ // Inventory layout: items.data at +0x8, selected at +0x28. shared_ptr is 16 bytes.
+ static void* GetSelectedItemInstanceFromPlayer(void* playerPtr)
+ {
+ void* inv = FindInventoryPtrFromPlayer(playerPtr);
+ if (!inv || !IsReadableRange(inv, 0x30))
+ return nullptr;
+ void* itemsData = *reinterpret_cast(static_cast(inv) + 0x8);
+ int selected = *reinterpret_cast(static_cast(inv) + 0x28);
+ if (!itemsData || selected < 0 || selected >= 36)
+ return nullptr;
+ // shared_ptr at itemsData[selected]; raw ptr is first 8 bytes
+ const char* slotPtr = static_cast(itemsData) + selected * 16;
+ if (!IsReadableRange(slotPtr, 8))
+ return nullptr;
+ return *reinterpret_cast(slotPtr);
+ }
+
+ bool __fastcall Hooked_PlayerCanDestroy(void* thisPtr, void* tilePtr)
+ {
+ // For pickaxe harvest rules, Inventory::canDestroy -> ItemInstance::canDestroySpecial
+ // already gives the correct source behavior:
+ // proper tool/tier => normal speed + drops
+ // insufficient tool/tier => slow break + no drops
+ if (Original_PlayerCanDestroy)
+ return Original_PlayerCanDestroy(thisPtr, tilePtr);
+ return false;
+ }
+
bool __fastcall Hooked_ServerPlayerGameModeUseItem(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, bool bTestUseOnly)
{
void* previousLevel = s_activeUseLevel;
diff --git a/WeaveLoaderRuntime/src/GameHooks.h b/WeaveLoaderRuntime/src/GameHooks.h
index d37668e..b8d02ec 100644
--- a/WeaveLoaderRuntime/src/GameHooks.h
+++ b/WeaveLoaderRuntime/src/GameHooks.h
@@ -25,6 +25,9 @@ typedef void (__fastcall *AnimatedTextureCycleFrames_fn)(void* thisPtr);
typedef int (__fastcall *TextureGetSourceDim_fn)(void* thisPtr);
typedef void (__fastcall *ItemInstanceMineBlock_fn)(void* thisPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr);
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 bool (__fastcall *PlayerCanDestroy_fn)(void* thisPtr, void* tilePtr);
typedef bool (__fastcall *GameModeUseItem_fn)(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, bool bTestUseOnly);
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);
@@ -68,6 +71,11 @@ namespace GameHooks
extern ItemInstanceMineBlock_fn Original_ItemInstanceMineBlock;
extern ItemMineBlock_fn Original_ItemMineBlock;
extern ItemMineBlock_fn Original_DiggerItemMineBlock;
+ extern PickaxeGetDestroySpeed_fn Original_PickaxeItemGetDestroySpeed;
+ extern PickaxeCanDestroySpecial_fn Original_PickaxeItemCanDestroySpecial;
+ extern PickaxeGetDestroySpeed_fn Original_ShovelItemGetDestroySpeed;
+ extern PickaxeCanDestroySpecial_fn Original_ShovelItemCanDestroySpecial;
+ extern PlayerCanDestroy_fn Original_PlayerCanDestroy;
extern GameModeUseItem_fn Original_ServerPlayerGameModeUseItem;
extern GameModeUseItem_fn Original_MultiPlayerGameModeUseItem;
extern MinecraftSetLevel_fn Original_MinecraftSetLevel;
@@ -103,6 +111,11 @@ namespace GameHooks
void __fastcall Hooked_ItemInstanceMineBlock(void* thisPtr, void* level, int tile, int x, int y, int z, void* ownerSharedPtr);
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);
+ bool __fastcall Hooked_PickaxeItemCanDestroySpecial(void* thisPtr, void* tilePtr);
+ float __fastcall Hooked_ShovelItemGetDestroySpeed(void* thisPtr, void* itemInstanceSharedPtr, void* tilePtr);
+ bool __fastcall Hooked_ShovelItemCanDestroySpecial(void* thisPtr, void* tilePtr);
+ bool __fastcall Hooked_PlayerCanDestroy(void* thisPtr, void* tilePtr);
bool __fastcall Hooked_ServerPlayerGameModeUseItem(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, bool bTestUseOnly);
bool __fastcall Hooked_MultiPlayerGameModeUseItem(void* thisPtr, void* playerSharedPtr, void* level, void* itemInstanceSharedPtr, bool bTestUseOnly);
void __fastcall Hooked_MinecraftSetLevel(void* thisPtr, void* level, int message, void* forceInsertPlayerSharedPtr, bool doForceStatsSave, bool bPrimaryPlayerSignedOut);
@@ -114,6 +127,7 @@ namespace GameHooks
float __fastcall Hooked_StitchedGetV0(void* thisPtr, bool adjust);
float __fastcall Hooked_StitchedGetV1(void* thisPtr, bool adjust);
void SetAtlasLocationPointers(void* blocksLocation, void* itemsLocation);
+ void SetTileTilesArray(void* tilesArray);
void SetSummonSymbols(void* levelAddEntity,
void* entityIoNewById,
void* entityMoveTo,
diff --git a/WeaveLoaderRuntime/src/GameObjectFactory.cpp b/WeaveLoaderRuntime/src/GameObjectFactory.cpp
index 265fe9e..7e3337a 100644
--- a/WeaveLoaderRuntime/src/GameObjectFactory.cpp
+++ b/WeaveLoaderRuntime/src/GameObjectFactory.cpp
@@ -5,6 +5,7 @@
#include
#include
#include
+#include
// Tile::Tile(int id, Material* material, bool isSolidRender) — protected ctor
typedef void (__fastcall *TileCtor_fn)(void* thisPtr, int id, void* material, bool isSolidRender);
@@ -24,6 +25,10 @@ typedef void (__fastcall *TileItemCtor_fn)(void* thisPtr, int id);
typedef void (__fastcall *ItemCtor_fn)(void* thisPtr, int id);
// PickaxeItem::PickaxeItem(int id, const Item::Tier* tier)
typedef void (__fastcall *PickaxeCtor_fn)(void* thisPtr, int id, const void* tier);
+typedef void (__fastcall *ShovelCtor_fn)(void* thisPtr, int id, const void* tier);
+typedef void (__fastcall *HoeCtor_fn)(void* thisPtr, int id, const void* tier);
+typedef void (__fastcall *HatchetCtor_fn)(void* thisPtr, int id, const void* tier);
+typedef void (__fastcall *WeaponCtor_fn)(void* thisPtr, int id, const void* tier);
// Item* Item::setIconName(const std::wstring&)
typedef void* (__fastcall *ItemSetIconName_fn)(void* thisPtr, const std::wstring& name);
// Item::getDescriptionId(int) — used to extract the descriptionId field offset
@@ -40,6 +45,10 @@ static TileItemCtor_fn fnTileItemCtor = nullptr;
static ItemCtor_fn fnItemCtor = nullptr;
static PickaxeCtor_fn fnPickaxeCtor = nullptr;
+static ShovelCtor_fn fnShovelCtor = nullptr;
+static HoeCtor_fn fnHoeCtor = nullptr;
+static HatchetCtor_fn fnHatchetCtor = nullptr;
+static WeaponCtor_fn fnWeaponCtor = nullptr;
static ItemSetIconName_fn fnItemSetIconName= nullptr;
static int s_itemDescIdOffset = -1; // offset of descriptionId field in Item, extracted from getDescriptionId
@@ -54,6 +63,20 @@ static const int ITEM_ALLOC_SIZE = 1024;
static const int TILEITEM_ALLOC_SIZE = 1024;
static bool s_resolved = false;
+static std::unordered_map s_createdItems;
+
+static int MapTierMaterial(int tier)
+{
+ switch (tier)
+ {
+ case 0: return 1; // wood
+ case 1: return 2; // stone
+ case 2: return 3; // iron
+ case 3: return 5; // diamond
+ case 4: return 4; // gold
+ default: return 5;
+ }
+}
static void* GetMaterial(int idx)
{
@@ -101,6 +124,10 @@ bool ResolveSymbols(SymbolResolver& resolver)
// Item constructor — protected (IEAA not QEAA)
fnItemCtor = (ItemCtor_fn)resolver.Resolve("??0Item@@IEAA@H@Z");
fnPickaxeCtor = (PickaxeCtor_fn)resolver.Resolve("??0PickaxeItem@@QEAA@HPEBVTier@Item@@@Z");
+ fnShovelCtor = (ShovelCtor_fn)resolver.Resolve("??0ShovelItem@@QEAA@HPEBVTier@Item@@@Z");
+ fnHoeCtor = (HoeCtor_fn)resolver.Resolve("??0HoeItem@@QEAA@HPEBVTier@Item@@@Z");
+ fnHatchetCtor = (HatchetCtor_fn)resolver.Resolve("??0HatchetItem@@QEAA@HPEBVTier@Item@@@Z");
+ fnWeaponCtor = (WeaponCtor_fn)resolver.Resolve("??0WeaponItem@@QEAA@HPEBVTier@Item@@@Z");
// Item::setIconName
fnItemSetIconName = (ItemSetIconName_fn)resolver.Resolve(
@@ -190,6 +217,10 @@ bool ResolveSymbols(SymbolResolver& resolver)
logSym("TileItem::TileItem", (void*)fnTileItemCtor);
logSym("Item::Item", (void*)fnItemCtor);
logSym("PickaxeItem::PickaxeItem", (void*)fnPickaxeCtor);
+ logSym("ShovelItem::ShovelItem", (void*)fnShovelCtor);
+ logSym("HoeItem::HoeItem", (void*)fnHoeCtor);
+ logSym("HatchetItem::HatchetItem", (void*)fnHatchetCtor);
+ logSym("WeaponItem::WeaponItem", (void*)fnWeaponCtor);
logSym("Item::setIconName", (void*)fnItemSetIconName);
logSym("Material::stone addr", (void*)s_materialAddrs[1]);
logSym("SOUND_STONE addr", (void*)s_soundAddrs[1]);
@@ -320,6 +351,7 @@ bool CreateItem(int itemId, int maxStackSize, int maxDamage, const wchar_t* icon
itemId, ctorParam, maxStackSize, maxDamage,
iconName ? iconName : L"", descriptionId);
+ s_createdItems[itemId] = item;
return true;
}
@@ -346,17 +378,7 @@ bool CreatePickaxeItem(int itemId, int tier, int maxDamage, const wchar_t* iconN
// Ensure pickaxe category/material for crafting menus:
// baseType=pickaxe(3), material depends on tier.
*reinterpret_cast(static_cast(item) + 0x38) = 3;
- int material = 5; // diamond default
- switch (tier)
- {
- case 0: material = 1; break; // wood
- case 1: material = 2; break; // stone
- case 2: material = 3; break; // iron
- case 3: material = 5; break; // diamond
- case 4: material = 4; break; // gold
- default: break;
- }
- *reinterpret_cast(static_cast(item) + 0x3C) = material;
+ *reinterpret_cast(static_cast(item) + 0x3C) = MapTierMaterial(tier);
// Tools should always stack to 1.
*reinterpret_cast(static_cast(item) + 0x24) = 1;
@@ -381,7 +403,89 @@ bool CreatePickaxeItem(int itemId, int tier, int maxDamage, const wchar_t* iconN
LogUtil::Log("[WeaveLoader] Created PickaxeItem id=%d (ctorParam=%d, tier=%d, damage=%d, icon=%ls, descId=%d)",
itemId, ctorParam, tier, maxDamage, iconName ? iconName : L"", descriptionId);
+ s_createdItems[itemId] = item;
return true;
}
+static bool CreateTieredItem(
+ const char* typeName,
+ void* ctorRaw,
+ int baseType,
+ int itemId,
+ int tier,
+ int maxDamage,
+ const wchar_t* iconName,
+ int descriptionId)
+{
+ if (!s_resolved || !ctorRaw)
+ {
+ LogUtil::Log("[WeaveLoader] %s: symbols not resolved", typeName);
+ return false;
+ }
+
+ const void* tierPtr = GetTier(tier);
+ if (!tierPtr)
+ {
+ LogUtil::Log("[WeaveLoader] %s: invalid tier %d", typeName, tier);
+ return false;
+ }
+
+ int ctorParam = itemId - 256;
+ void* item = ::operator new(ITEM_ALLOC_SIZE);
+ memset(item, 0, ITEM_ALLOC_SIZE);
+ reinterpret_cast(ctorRaw)(item, ctorParam, tierPtr);
+
+ *reinterpret_cast(static_cast(item) + 0x38) = baseType;
+ *reinterpret_cast(static_cast(item) + 0x3C) = MapTierMaterial(tier);
+ *reinterpret_cast(static_cast(item) + 0x24) = 1;
+
+ if (maxDamage > 0)
+ *reinterpret_cast(static_cast(item) + 0x28) = maxDamage;
+
+ if (fnItemSetIconName)
+ {
+ std::wstring name = (iconName && iconName[0]) ? iconName : L"MISSING_ICON_ITEM";
+ fnItemSetIconName(item, name);
+ }
+
+ if (s_itemDescIdOffset > 0 && descriptionId >= 0)
+ {
+ *reinterpret_cast(static_cast(item) + s_itemDescIdOffset) =
+ static_cast(descriptionId);
+ }
+
+ LogUtil::Log("[WeaveLoader] Created %s id=%d (ctorParam=%d, tier=%d, damage=%d, icon=%ls, descId=%d)",
+ typeName, itemId, ctorParam, tier, maxDamage, iconName ? iconName : L"", descriptionId);
+ s_createdItems[itemId] = item;
+ return true;
+}
+
+bool CreateShovelItem(int itemId, int tier, int maxDamage, const wchar_t* iconName, int descriptionId)
+{
+ return CreateTieredItem("ShovelItem", fnShovelCtor, 2, itemId, tier, maxDamage, iconName, descriptionId);
+}
+
+bool CreateHoeItem(int itemId, int tier, int maxDamage, const wchar_t* iconName, int descriptionId)
+{
+ return CreateTieredItem("HoeItem", fnHoeCtor, 5, itemId, tier, maxDamage, iconName, descriptionId);
+}
+
+bool CreateAxeItem(int itemId, int tier, int maxDamage, const wchar_t* iconName, int descriptionId)
+{
+ return CreateTieredItem("HatchetItem", fnHatchetCtor, 4, itemId, tier, maxDamage, iconName, descriptionId);
+}
+
+bool CreateSwordItem(int itemId, int tier, int maxDamage, const wchar_t* iconName, int descriptionId)
+{
+ return CreateTieredItem("WeaponItem", fnWeaponCtor, 1, itemId, tier, maxDamage, iconName, descriptionId);
+}
+
+void* FindItem(int itemId)
+{
+ const auto it = s_createdItems.find(itemId);
+ if (it == s_createdItems.end())
+ return nullptr;
+ return it->second;
+}
+
} // namespace GameObjectFactory
diff --git a/WeaveLoaderRuntime/src/GameObjectFactory.h b/WeaveLoaderRuntime/src/GameObjectFactory.h
index fd97e21..08c3553 100644
--- a/WeaveLoaderRuntime/src/GameObjectFactory.h
+++ b/WeaveLoaderRuntime/src/GameObjectFactory.h
@@ -26,4 +26,13 @@ namespace GameObjectFactory
// maxDamage: if > 0, overrides the tier default durability.
bool CreatePickaxeItem(int itemId, int tier, int maxDamage,
const wchar_t* iconName, int descriptionId = -1);
+ bool CreateShovelItem(int itemId, int tier, int maxDamage,
+ const wchar_t* iconName, int descriptionId = -1);
+ bool CreateHoeItem(int itemId, int tier, int maxDamage,
+ const wchar_t* iconName, int descriptionId = -1);
+ bool CreateAxeItem(int itemId, int tier, int maxDamage,
+ const wchar_t* iconName, int descriptionId = -1);
+ bool CreateSwordItem(int itemId, int tier, int maxDamage,
+ const wchar_t* iconName, int descriptionId = -1);
+ void* FindItem(int itemId);
}
diff --git a/WeaveLoaderRuntime/src/HookManager.cpp b/WeaveLoaderRuntime/src/HookManager.cpp
index f8c5a01..fd9687f 100644
--- a/WeaveLoaderRuntime/src/HookManager.cpp
+++ b/WeaveLoaderRuntime/src/HookManager.cpp
@@ -236,6 +236,76 @@ bool HookManager::Install(const SymbolResolver& symbols)
}
}
+ if (symbols.pPickaxeItemGetDestroySpeed)
+ {
+ if (MH_CreateHook(symbols.pPickaxeItemGetDestroySpeed,
+ reinterpret_cast(&GameHooks::Hooked_PickaxeItemGetDestroySpeed),
+ reinterpret_cast(&GameHooks::Original_PickaxeItemGetDestroySpeed)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook PickaxeItem::getDestroySpeed");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked PickaxeItem::getDestroySpeed (custom pickaxe tiers)");
+ }
+ }
+
+ if (symbols.pPickaxeItemCanDestroySpecial)
+ {
+ if (MH_CreateHook(symbols.pPickaxeItemCanDestroySpecial,
+ reinterpret_cast(&GameHooks::Hooked_PickaxeItemCanDestroySpecial),
+ reinterpret_cast(&GameHooks::Original_PickaxeItemCanDestroySpecial)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook PickaxeItem::canDestroySpecial");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked PickaxeItem::canDestroySpecial (custom pickaxe tiers)");
+ }
+ }
+
+ if (symbols.pShovelItemGetDestroySpeed)
+ {
+ if (MH_CreateHook(symbols.pShovelItemGetDestroySpeed,
+ reinterpret_cast(&GameHooks::Hooked_ShovelItemGetDestroySpeed),
+ reinterpret_cast(&GameHooks::Original_ShovelItemGetDestroySpeed)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook ShovelItem::getDestroySpeed");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked ShovelItem::getDestroySpeed (custom tool materials)");
+ }
+ }
+
+ if (symbols.pShovelItemCanDestroySpecial)
+ {
+ if (MH_CreateHook(symbols.pShovelItemCanDestroySpecial,
+ reinterpret_cast(&GameHooks::Hooked_ShovelItemCanDestroySpecial),
+ reinterpret_cast(&GameHooks::Original_ShovelItemCanDestroySpecial)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook ShovelItem::canDestroySpecial");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked ShovelItem::canDestroySpecial (custom tool materials)");
+ }
+ }
+
+ if (symbols.pPlayerCanDestroy)
+ {
+ if (MH_CreateHook(symbols.pPlayerCanDestroy,
+ reinterpret_cast(&GameHooks::Hooked_PlayerCanDestroy),
+ reinterpret_cast(&GameHooks::Original_PlayerCanDestroy)) != MH_OK)
+ {
+ LogUtil::Log("[WeaveLoader] Warning: Failed to hook Player::canDestroy");
+ }
+ else
+ {
+ LogUtil::Log("[WeaveLoader] Hooked Player::canDestroy (block harvest enforcement)");
+ }
+ }
+
if (symbols.pServerPlayerGameModeUseItem)
{
if (MH_CreateHook(symbols.pServerPlayerGameModeUseItem,
@@ -265,6 +335,7 @@ bool HookManager::Install(const SymbolResolver& symbols)
}
GameHooks::SetAtlasLocationPointers(symbols.pTextureAtlasLocationBlocks, symbols.pTextureAtlasLocationItems);
+ GameHooks::SetTileTilesArray(symbols.pTileTiles);
if (symbols.pTexturesBindTextureResource)
{
diff --git a/WeaveLoaderRuntime/src/NativeExports.cpp b/WeaveLoaderRuntime/src/NativeExports.cpp
index 384af5f..a50dfac 100644
--- a/WeaveLoaderRuntime/src/NativeExports.cpp
+++ b/WeaveLoaderRuntime/src/NativeExports.cpp
@@ -4,6 +4,9 @@
#include "GameObjectFactory.h"
#include "FurnaceRecipeRegistry.h"
#include "GameHooks.h"
+#include "CustomPickaxeRegistry.h"
+#include "CustomToolMaterialRegistry.h"
+#include "CustomBlockRegistry.h"
#include "ModStrings.h"
#include "LogUtil.h"
#include
@@ -45,7 +48,9 @@ int native_register_block(
const char* iconName,
float lightEmission,
int lightBlock,
- const char* displayName)
+ const char* displayName,
+ int requiredHarvestLevel,
+ int requiredTool)
{
if (!namespacedId) return -1;
@@ -75,6 +80,11 @@ int native_register_block(
LogUtil::Log("[WeaveLoader] Warning: failed to create game Tile for block '%s' id=%d", namespacedId, id);
}
+ if (requiredHarvestLevel >= 0 || requiredTool != 0)
+ {
+ CustomBlockRegistry::Register(id, requiredHarvestLevel, requiredTool);
+ }
+
return id;
}
@@ -154,6 +164,171 @@ int native_register_pickaxe_item(
return id;
}
+int native_register_shovel_item(
+ const char* namespacedId,
+ int tier,
+ int maxDamage,
+ const char* iconName,
+ const char* displayName)
+{
+ if (!namespacedId) return -1;
+
+ int id = IdRegistry::Instance().Register(IdRegistry::Type::Item, namespacedId);
+ if (id < 0) return -1;
+
+ std::wstring wIcon = Utf8ToWide(iconName);
+ int descId = -1;
+ if (displayName && displayName[0])
+ {
+ descId = ModStrings::AllocateId();
+ std::wstring wName = Utf8ToWide(displayName);
+ ModStrings::Register(descId, wName.c_str());
+ }
+
+ if (!GameObjectFactory::CreateShovelItem(id, tier, maxDamage, wIcon.empty() ? nullptr : wIcon.c_str(), descId))
+ LogUtil::Log("[WeaveLoader] Warning: failed to create native ShovelItem for '%s' id=%d", namespacedId, id);
+
+ return id;
+}
+
+int native_register_hoe_item(
+ const char* namespacedId,
+ int tier,
+ int maxDamage,
+ const char* iconName,
+ const char* displayName)
+{
+ if (!namespacedId) return -1;
+
+ int id = IdRegistry::Instance().Register(IdRegistry::Type::Item, namespacedId);
+ if (id < 0) return -1;
+
+ std::wstring wIcon = Utf8ToWide(iconName);
+ int descId = -1;
+ if (displayName && displayName[0])
+ {
+ descId = ModStrings::AllocateId();
+ std::wstring wName = Utf8ToWide(displayName);
+ ModStrings::Register(descId, wName.c_str());
+ }
+
+ if (!GameObjectFactory::CreateHoeItem(id, tier, maxDamage, wIcon.empty() ? nullptr : wIcon.c_str(), descId))
+ LogUtil::Log("[WeaveLoader] Warning: failed to create native HoeItem for '%s' id=%d", namespacedId, id);
+
+ return id;
+}
+
+int native_register_axe_item(
+ const char* namespacedId,
+ int tier,
+ int maxDamage,
+ const char* iconName,
+ const char* displayName)
+{
+ if (!namespacedId) return -1;
+
+ int id = IdRegistry::Instance().Register(IdRegistry::Type::Item, namespacedId);
+ if (id < 0) return -1;
+
+ std::wstring wIcon = Utf8ToWide(iconName);
+ int descId = -1;
+ if (displayName && displayName[0])
+ {
+ descId = ModStrings::AllocateId();
+ std::wstring wName = Utf8ToWide(displayName);
+ ModStrings::Register(descId, wName.c_str());
+ }
+
+ if (!GameObjectFactory::CreateAxeItem(id, tier, maxDamage, wIcon.empty() ? nullptr : wIcon.c_str(), descId))
+ LogUtil::Log("[WeaveLoader] Warning: failed to create native HatchetItem for '%s' id=%d", namespacedId, id);
+
+ return id;
+}
+
+int native_register_sword_item(
+ const char* namespacedId,
+ int tier,
+ int maxDamage,
+ const char* iconName,
+ const char* displayName)
+{
+ if (!namespacedId) return -1;
+
+ int id = IdRegistry::Instance().Register(IdRegistry::Type::Item, namespacedId);
+ if (id < 0) return -1;
+
+ std::wstring wIcon = Utf8ToWide(iconName);
+ int descId = -1;
+ if (displayName && displayName[0])
+ {
+ descId = ModStrings::AllocateId();
+ std::wstring wName = Utf8ToWide(displayName);
+ ModStrings::Register(descId, wName.c_str());
+ }
+
+ if (!GameObjectFactory::CreateSwordItem(id, tier, maxDamage, wIcon.empty() ? nullptr : wIcon.c_str(), descId))
+ LogUtil::Log("[WeaveLoader] Warning: failed to create native WeaponItem for '%s' id=%d", namespacedId, id);
+
+ return id;
+}
+
+int native_configure_custom_pickaxe_item(
+ int numericItemId,
+ int harvestLevel,
+ float destroySpeed)
+{
+ return native_configure_custom_tool_item(
+ numericItemId,
+ static_cast(CustomToolMaterialRegistry::ToolKind::Pickaxe),
+ harvestLevel,
+ destroySpeed,
+ 0.0f);
+}
+
+int native_configure_custom_tool_item(
+ int numericItemId,
+ int toolKind,
+ int harvestLevel,
+ float destroySpeed,
+ float attackDamage)
+{
+ if (numericItemId < 0 || harvestLevel < 0 || destroySpeed <= 0.0f)
+ return 0;
+
+ const auto kind = static_cast(toolKind);
+ CustomToolMaterialRegistry::Register(numericItemId, kind, harvestLevel, destroySpeed, attackDamage);
+ if (kind == CustomToolMaterialRegistry::ToolKind::Pickaxe)
+ {
+ CustomPickaxeRegistry::Register(numericItemId, harvestLevel, destroySpeed);
+ }
+
+ void* itemPtr = GameObjectFactory::FindItem(numericItemId);
+ if (itemPtr)
+ {
+ switch (kind)
+ {
+ case CustomToolMaterialRegistry::ToolKind::Pickaxe:
+ case CustomToolMaterialRegistry::ToolKind::Shovel:
+ case CustomToolMaterialRegistry::ToolKind::Axe:
+ if (destroySpeed > 0.0f)
+ *reinterpret_cast(static_cast(itemPtr) + 0xA0) = destroySpeed;
+ if (attackDamage > 0.0f)
+ *reinterpret_cast(static_cast(itemPtr) + 0xA4) = attackDamage;
+ break;
+ case CustomToolMaterialRegistry::ToolKind::Sword:
+ if (attackDamage > 0.0f)
+ *reinterpret_cast(static_cast(itemPtr) + 0x98) = attackDamage;
+ break;
+ case CustomToolMaterialRegistry::ToolKind::Hoe:
+ break;
+ }
+ }
+
+ LogUtil::Log("[WeaveLoader] Configured custom tool item id=%d (kind=%d harvest=%d speed=%.2f attack=%.2f)",
+ numericItemId, toolKind, harvestLevel, destroySpeed, attackDamage);
+ return 1;
+}
+
int native_allocate_description_id()
{
return ModStrings::AllocateId();
diff --git a/WeaveLoaderRuntime/src/NativeExports.h b/WeaveLoaderRuntime/src/NativeExports.h
index ce7615d..3d8cf8a 100644
--- a/WeaveLoaderRuntime/src/NativeExports.h
+++ b/WeaveLoaderRuntime/src/NativeExports.h
@@ -14,7 +14,9 @@ extern "C"
const char* iconName,
float lightEmission,
int lightBlock,
- const char* displayName);
+ const char* displayName,
+ int requiredHarvestLevel,
+ int requiredTool);
__declspec(dllexport) int native_register_item(
const char* namespacedId,
@@ -29,6 +31,41 @@ extern "C"
int maxDamage,
const char* iconName,
const char* displayName);
+ __declspec(dllexport) int native_register_shovel_item(
+ const char* namespacedId,
+ int tier,
+ int maxDamage,
+ const char* iconName,
+ const char* displayName);
+ __declspec(dllexport) int native_register_hoe_item(
+ const char* namespacedId,
+ int tier,
+ int maxDamage,
+ const char* iconName,
+ const char* displayName);
+ __declspec(dllexport) int native_register_axe_item(
+ const char* namespacedId,
+ int tier,
+ int maxDamage,
+ const char* iconName,
+ const char* displayName);
+ __declspec(dllexport) int native_register_sword_item(
+ const char* namespacedId,
+ int tier,
+ int maxDamage,
+ const char* iconName,
+ const char* displayName);
+
+ __declspec(dllexport) int native_configure_custom_pickaxe_item(
+ int numericItemId,
+ int harvestLevel,
+ float destroySpeed);
+ __declspec(dllexport) int native_configure_custom_tool_item(
+ int numericItemId,
+ int toolKind,
+ int harvestLevel,
+ float destroySpeed,
+ float attackDamage);
__declspec(dllexport) int native_allocate_description_id();
__declspec(dllexport) void native_register_string(int descriptionId, const char* displayName);
diff --git a/WeaveLoaderRuntime/src/SymbolResolver.cpp b/WeaveLoaderRuntime/src/SymbolResolver.cpp
index 31ca576..2db242d 100644
--- a/WeaveLoaderRuntime/src/SymbolResolver.cpp
+++ b/WeaveLoaderRuntime/src/SymbolResolver.cpp
@@ -30,6 +30,11 @@ static const char* SYM_CLOCK_GETSOURCEHEIGHT = "?getSourceHeight@ClockTexture@@U
static const char* SYM_ITEMINSTANCE_MINEBLOCK = "?mineBlock@ItemInstance@@QEAAXPEAVLevel@@HHHHV?$shared_ptr@VPlayer@@@std@@@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";
+static const char* SYM_PICKAXEITEM_CANDESTROYSPECIAL = "?canDestroySpecial@PickaxeItem@@UEAA_NPEAVTile@@@Z";
+static const char* SYM_SHOVELITEM_GETDESTROYSPEED = "?getDestroySpeed@ShovelItem@@UEAAMV?$shared_ptr@VItemInstance@@@std@@PEAVTile@@@Z";
+static const char* SYM_SHOVELITEM_CANDESTROYSPECIAL = "?canDestroySpecial@ShovelItem@@UEAA_NPEAVTile@@@Z";
+static const char* SYM_PLAYER_CANDESTROY = "?canDestroy@Player@@QEAA_NPEAVTile@@@Z";
static const char* SYM_SERVER_PLAYER_GAMEMODE_USEITEM = "?useItem@ServerPlayerGameMode@@QEAA_NV?$shared_ptr@VPlayer@@@std@@PEAVLevel@@V?$shared_ptr@VItemInstance@@@3@_N@Z";
static const char* SYM_MULTI_PLAYER_GAMEMODE_USEITEM = "?useItem@MultiPlayerGameMode@@UEAA_NV?$shared_ptr@VPlayer@@@std@@PEAVLevel@@V?$shared_ptr@VItemInstance@@@3@_N@Z";
static const char* SYM_TEXTURES_BIND_RESOURCE = "?bindTexture@Textures@@QEAAXPEAVResourceLocation@@@Z";
@@ -55,6 +60,7 @@ static const char* SYM_ITEMINSTANCE_HURTANDBREAK = "?hurtAndBreak@ItemInstance@@
static const char* SYM_ABSTRACTCONTAINERMENU_BROADCASTCHANGES = "?broadcastChanges@AbstractContainerMenu@@UEAAXXZ";
static const char* SYM_TEXATLAS_BLOCKS = "?LOCATION_BLOCKS@TextureAtlas@@2VResourceLocation@@A";
static const char* SYM_TEXATLAS_ITEMS = "?LOCATION_ITEMS@TextureAtlas@@2VResourceLocation@@A";
+static const char* SYM_TILE_TILES = "?tiles@Tile@@2PEAPEAV1@EA";
bool SymbolResolver::Initialize()
{
@@ -137,6 +143,11 @@ bool SymbolResolver::ResolveGameFunctions()
pItemInstanceMineBlock = Resolve(SYM_ITEMINSTANCE_MINEBLOCK);
pItemMineBlock = Resolve(SYM_ITEM_MINEBLOCK);
pDiggerItemMineBlock = Resolve(SYM_DIGGERITEM_MINEBLOCK);
+ pPickaxeItemGetDestroySpeed = Resolve(SYM_PICKAXEITEM_GETDESTROYSPEED);
+ pPickaxeItemCanDestroySpecial = Resolve(SYM_PICKAXEITEM_CANDESTROYSPECIAL);
+ pShovelItemGetDestroySpeed = Resolve(SYM_SHOVELITEM_GETDESTROYSPEED);
+ pShovelItemCanDestroySpecial = Resolve(SYM_SHOVELITEM_CANDESTROYSPECIAL);
+ pPlayerCanDestroy = Resolve(SYM_PLAYER_CANDESTROY);
pServerPlayerGameModeUseItem = Resolve(SYM_SERVER_PLAYER_GAMEMODE_USEITEM);
pMultiPlayerGameModeUseItem = Resolve(SYM_MULTI_PLAYER_GAMEMODE_USEITEM);
pTexturesBindTextureResource = Resolve(SYM_TEXTURES_BIND_RESOURCE);
@@ -164,6 +175,7 @@ bool SymbolResolver::ResolveGameFunctions()
pAbstractContainerMenuBroadcastChanges = Resolve(SYM_ABSTRACTCONTAINERMENU_BROADCASTCHANGES);
pTextureAtlasLocationBlocks = Resolve(SYM_TEXATLAS_BLOCKS);
pTextureAtlasLocationItems = Resolve(SYM_TEXATLAS_ITEMS);
+ pTileTiles = Resolve(SYM_TILE_TILES);
if (!pOperatorNew) pOperatorNew = GetProcAddress(GetModuleHandleA("vcruntime140.dll"), SYM_OPERATOR_NEW);
if (!pOperatorNew) pOperatorNew = GetProcAddress(GetModuleHandleA("vcruntime140d.dll"), SYM_OPERATOR_NEW);
if (!pOperatorNew) pOperatorNew = GetProcAddress(GetModuleHandle(nullptr), SYM_OPERATOR_NEW);
@@ -202,6 +214,11 @@ bool SymbolResolver::ResolveGameFunctions()
logSym("ItemInstance::mineBlock", pItemInstanceMineBlock);
logSym("Item::mineBlock", pItemMineBlock);
logSym("DiggerItem::mineBlock", pDiggerItemMineBlock);
+ logSym("PickaxeItem::getDestroySpeed", pPickaxeItemGetDestroySpeed);
+ logSym("PickaxeItem::canDestroySpecial", pPickaxeItemCanDestroySpecial);
+ logSym("ShovelItem::getDestroySpeed", pShovelItemGetDestroySpeed);
+ logSym("ShovelItem::canDestroySpecial", pShovelItemCanDestroySpecial);
+ logSym("Player::canDestroy", pPlayerCanDestroy);
logSym("ServerPlayerGameMode::useItem", pServerPlayerGameModeUseItem);
logSym("MultiPlayerGameMode::useItem", pMultiPlayerGameModeUseItem);
logSym("Textures::bindTexture(ResourceLocation)", pTexturesBindTextureResource);
@@ -225,6 +242,7 @@ bool SymbolResolver::ResolveGameFunctions()
logSym("AbstractContainerMenu::broadcastChanges", pAbstractContainerMenuBroadcastChanges);
logSym("TextureAtlas::LOCATION_BLOCKS", pTextureAtlasLocationBlocks);
logSym("TextureAtlas::LOCATION_ITEMS", pTextureAtlasLocationItems);
+ logSym("Tile::tiles", pTileTiles);
bool ok = pRunStaticCtors && pMinecraftTick && pMinecraftInit;
if (ok)
diff --git a/WeaveLoaderRuntime/src/SymbolResolver.h b/WeaveLoaderRuntime/src/SymbolResolver.h
index 5111dc9..ddfe491 100644
--- a/WeaveLoaderRuntime/src/SymbolResolver.h
+++ b/WeaveLoaderRuntime/src/SymbolResolver.h
@@ -35,6 +35,11 @@ public:
void* pItemInstanceMineBlock = nullptr; // ItemInstance::mineBlock(Level*,int,int,int,int,shared_ptr)
void* pItemMineBlock = nullptr; // Item::mineBlock(shared_ptr,Level*,int,int,int,int,shared_ptr)
void* pDiggerItemMineBlock = nullptr; // DiggerItem::mineBlock(shared_ptr,Level*,int,int,int,int,shared_ptr)
+ void* pPickaxeItemGetDestroySpeed = nullptr; // PickaxeItem::getDestroySpeed(shared_ptr,Tile*)
+ void* pPickaxeItemCanDestroySpecial = nullptr; // PickaxeItem::canDestroySpecial(Tile*)
+ void* pShovelItemGetDestroySpeed = nullptr; // ShovelItem::getDestroySpeed(shared_ptr,Tile*)
+ void* pShovelItemCanDestroySpecial = nullptr; // ShovelItem::canDestroySpecial(Tile*)
+ void* pPlayerCanDestroy = nullptr; // Player::canDestroy(Tile*)
void* pServerPlayerGameModeUseItem = nullptr; // ServerPlayerGameMode::useItem(shared_ptr,Level*,shared_ptr,bool)
void* pMultiPlayerGameModeUseItem = nullptr; // MultiPlayerGameMode::useItem(shared_ptr,Level*,shared_ptr,bool)
void* pTexturesBindTextureResource = nullptr; // Textures::bindTexture(ResourceLocation*)
@@ -58,6 +63,7 @@ public:
void* pAbstractContainerMenuBroadcastChanges = nullptr; // AbstractContainerMenu::broadcastChanges()
void* pTextureAtlasLocationBlocks = nullptr; // TextureAtlas::LOCATION_BLOCKS
void* pTextureAtlasLocationItems = nullptr; // TextureAtlas::LOCATION_ITEMS
+ void* pTileTiles = nullptr; // Tile::tiles (Tile*[]) for tile id lookup
private:
uintptr_t m_moduleBase = 0;