feat(pickaxe): implement Hooked_PickaxeItemGetDestroySpeed and Hooked_PickaxeItemCanDestroySpecial
- Add hook implementations for custom pickaxe tier support - Hooked_PickaxeItemGetDestroySpeed: use CustomPickaxeRegistry destroy speed for configured pickaxes when mining effective blocks - Hooked_PickaxeItemCanDestroySpecial: use CustomPickaxeRegistry effective blocks and harvest level (obsidian requires level 3) - Add TryReadItemIdFromPickaxe and TryReadTileId helpers for reading item/tile IDs from native pointers
@@ -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)
|
||||
|
||||
BIN
ExampleMod/assets/blocks/orichalcum_ore.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
ExampleMod/assets/items/ruby_axe.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
ExampleMod/assets/items/ruby_hoe.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.9 KiB |
BIN
ExampleMod/assets/items/ruby_shovel.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
ExampleMod/assets/items/ruby_sword.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
@@ -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
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
namespace WeaveLoader.API.Block;
|
||||
|
||||
/// <summary>
|
||||
/// Tool type required to harvest a block. Used with <see cref="BlockProperties.RequiredTool"/>.
|
||||
/// </summary>
|
||||
public enum ToolType
|
||||
{
|
||||
/// <summary>No specific tool required; any tool or hand can harvest.</summary>
|
||||
None = 0,
|
||||
/// <summary>Requires a pickaxe.</summary>
|
||||
Pickaxe = 1,
|
||||
/// <summary>Requires an axe.</summary>
|
||||
Axe = 2,
|
||||
/// <summary>Requires a shovel.</summary>
|
||||
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; }
|
||||
/// <summary>Display name shown in-game (e.g. "Ruby Ore"). Used for localization.</summary>
|
||||
public BlockProperties Name(string displayName) { NameValue = displayName; return this; }
|
||||
/// <summary>Minimum harvest level required to properly mine this block (e.g. 3 for obsidian). -1 means no requirement.</summary>
|
||||
public BlockProperties RequiredHarvestLevel(int level) { RequiredHarvestLevelValue = level; return this; }
|
||||
/// <summary>Tool type required to harvest this block (e.g. Pickaxe for stone-like blocks).</summary>
|
||||
public BlockProperties RequiredTool(ToolType tool) { RequiredToolValue = tool; return this; }
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -63,9 +63,31 @@ public enum ToolTier
|
||||
/// Managed pickaxe base class.
|
||||
/// Override callbacks to customize behavior.
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
public ItemProperties MaxDamage(int damage) { MaxDamageValue = damage; MaxStackSizeValue = 1; return this; }
|
||||
/// <summary>Override the native attack damage value for tool items.</summary>
|
||||
public ItemProperties AttackDamage(float damage) { AttackDamageValue = damage; return this; }
|
||||
public ItemProperties InCreativeTab(CreativeTab tab) { CreativeTabValue = tab; return this; }
|
||||
/// <summary>Display name shown in-game (e.g. "Ruby"). Used for localization.</summary>
|
||||
public ItemProperties Name(string displayName) { NameValue = displayName; return this; }
|
||||
|
||||
@@ -29,6 +29,56 @@ public static class ItemRegistry
|
||||
private static readonly object s_lock = new();
|
||||
private static readonly Dictionary<int, Identifier> 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}'.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a new item with the game engine.
|
||||
/// </summary>
|
||||
@@ -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
|
||||
{
|
||||
|
||||
59
WeaveLoader.API/Item/PickaxeTierRegistry.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
72
WeaveLoader.API/Item/ToolMaterialRegistry.cs
Normal file
@@ -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<Identifier, ToolMaterialDefinition> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>Entity registration. Call Register() with a namespaced ID and EntityDefinition.</summary>
|
||||
|
||||
@@ -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
|
||||
|
||||
26
WeaveLoaderRuntime/src/CustomBlockRegistry.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "CustomBlockRegistry.h"
|
||||
#include <unordered_map>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<int, CustomBlockRegistry::Definition> g_definitions;
|
||||
}
|
||||
|
||||
namespace CustomBlockRegistry
|
||||
{
|
||||
void Register(int blockId, int requiredHarvestLevel, int requiredTool)
|
||||
{
|
||||
Definition def;
|
||||
def.requiredHarvestLevel = requiredHarvestLevel;
|
||||
def.requiredTool = static_cast<ToolType>(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;
|
||||
}
|
||||
}
|
||||
21
WeaveLoaderRuntime/src/CustomBlockRegistry.h
Normal file
@@ -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);
|
||||
}
|
||||
26
WeaveLoaderRuntime/src/CustomPickaxeRegistry.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "CustomPickaxeRegistry.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<int, CustomPickaxeRegistry::Definition> 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;
|
||||
}
|
||||
}
|
||||
14
WeaveLoaderRuntime/src/CustomPickaxeRegistry.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
namespace CustomPickaxeRegistry
|
||||
{
|
||||
struct Definition
|
||||
{
|
||||
int harvestLevel = 0;
|
||||
float destroySpeed = 1.0f;
|
||||
};
|
||||
|
||||
void Register(int itemId, int harvestLevel, float destroySpeed);
|
||||
const Definition* Find(int itemId);
|
||||
}
|
||||
27
WeaveLoaderRuntime/src/CustomToolMaterialRegistry.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#include "CustomToolMaterialRegistry.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<int, CustomToolMaterialRegistry::Definition> 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;
|
||||
}
|
||||
}
|
||||
26
WeaveLoaderRuntime/src/CustomToolMaterialRegistry.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -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 <Windows.h>
|
||||
#include <string>
|
||||
@@ -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<const int*>(static_cast<const char*>(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<const int*>(static_cast<const char*>(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<const void* const*>(s_tileTilesArray);
|
||||
if (arrayPtr && IsReadableRange(arrayPtr, TILE_NUM_COUNT * sizeof(void*)))
|
||||
{
|
||||
const void* const* tiles = reinterpret_cast<const void* const*>(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<const void* const*>(static_cast<const char*>(diggerItemPtr) + 0xA8);
|
||||
if (!tierPtr || !IsReadableRange(tierPtr, sizeof(int)))
|
||||
return -1;
|
||||
return *reinterpret_cast<const int*>(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<const char*>(diggerItemPtr) + 0xA0, sizeof(float)))
|
||||
return 1.0f;
|
||||
return *reinterpret_cast<const float*>(static_cast<const char*>(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<void* const*>(static_cast<const char*>(inv) + 0x8);
|
||||
int selected = *reinterpret_cast<const int*>(static_cast<const char*>(inv) + 0x28);
|
||||
if (!itemsData || selected < 0 || selected >= 36)
|
||||
return nullptr;
|
||||
// shared_ptr<ItemInstance> at itemsData[selected]; raw ptr is first 8 bytes
|
||||
const char* slotPtr = static_cast<const char*>(itemsData) + selected * 16;
|
||||
if (!IsReadableRange(slotPtr, 8))
|
||||
return nullptr;
|
||||
return *reinterpret_cast<void* const*>(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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <Windows.h>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
// 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<int, void*> 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"<none>", 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<int*>(static_cast<char*>(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<int*>(static_cast<char*>(item) + 0x3C) = material;
|
||||
*reinterpret_cast<int*>(static_cast<char*>(item) + 0x3C) = MapTierMaterial(tier);
|
||||
|
||||
// Tools should always stack to 1.
|
||||
*reinterpret_cast<int*>(static_cast<char*>(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"<none>", 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<PickaxeCtor_fn>(ctorRaw)(item, ctorParam, tierPtr);
|
||||
|
||||
*reinterpret_cast<int*>(static_cast<char*>(item) + 0x38) = baseType;
|
||||
*reinterpret_cast<int*>(static_cast<char*>(item) + 0x3C) = MapTierMaterial(tier);
|
||||
*reinterpret_cast<int*>(static_cast<char*>(item) + 0x24) = 1;
|
||||
|
||||
if (maxDamage > 0)
|
||||
*reinterpret_cast<int*>(static_cast<char*>(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<unsigned int*>(static_cast<char*>(item) + s_itemDescIdOffset) =
|
||||
static_cast<unsigned int>(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"<none>", 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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -236,6 +236,76 @@ bool HookManager::Install(const SymbolResolver& symbols)
|
||||
}
|
||||
}
|
||||
|
||||
if (symbols.pPickaxeItemGetDestroySpeed)
|
||||
{
|
||||
if (MH_CreateHook(symbols.pPickaxeItemGetDestroySpeed,
|
||||
reinterpret_cast<void*>(&GameHooks::Hooked_PickaxeItemGetDestroySpeed),
|
||||
reinterpret_cast<void**>(&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<void*>(&GameHooks::Hooked_PickaxeItemCanDestroySpecial),
|
||||
reinterpret_cast<void**>(&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<void*>(&GameHooks::Hooked_ShovelItemGetDestroySpeed),
|
||||
reinterpret_cast<void**>(&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<void*>(&GameHooks::Hooked_ShovelItemCanDestroySpecial),
|
||||
reinterpret_cast<void**>(&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<void*>(&GameHooks::Hooked_PlayerCanDestroy),
|
||||
reinterpret_cast<void**>(&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)
|
||||
{
|
||||
|
||||
@@ -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 <Windows.h>
|
||||
@@ -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<int>(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<CustomToolMaterialRegistry::ToolKind>(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<float*>(static_cast<char*>(itemPtr) + 0xA0) = destroySpeed;
|
||||
if (attackDamage > 0.0f)
|
||||
*reinterpret_cast<float*>(static_cast<char*>(itemPtr) + 0xA4) = attackDamage;
|
||||
break;
|
||||
case CustomToolMaterialRegistry::ToolKind::Sword:
|
||||
if (attackDamage > 0.0f)
|
||||
*reinterpret_cast<float*>(static_cast<char*>(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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -35,6 +35,11 @@ public:
|
||||
void* pItemInstanceMineBlock = nullptr; // ItemInstance::mineBlock(Level*,int,int,int,int,shared_ptr<Player>)
|
||||
void* pItemMineBlock = nullptr; // Item::mineBlock(shared_ptr<ItemInstance>,Level*,int,int,int,int,shared_ptr<LivingEntity>)
|
||||
void* pDiggerItemMineBlock = nullptr; // DiggerItem::mineBlock(shared_ptr<ItemInstance>,Level*,int,int,int,int,shared_ptr<LivingEntity>)
|
||||
void* pPickaxeItemGetDestroySpeed = nullptr; // PickaxeItem::getDestroySpeed(shared_ptr<ItemInstance>,Tile*)
|
||||
void* pPickaxeItemCanDestroySpecial = nullptr; // PickaxeItem::canDestroySpecial(Tile*)
|
||||
void* pShovelItemGetDestroySpeed = nullptr; // ShovelItem::getDestroySpeed(shared_ptr<ItemInstance>,Tile*)
|
||||
void* pShovelItemCanDestroySpecial = nullptr; // ShovelItem::canDestroySpecial(Tile*)
|
||||
void* pPlayerCanDestroy = nullptr; // Player::canDestroy(Tile*)
|
||||
void* pServerPlayerGameModeUseItem = nullptr; // ServerPlayerGameMode::useItem(shared_ptr<Player>,Level*,shared_ptr<ItemInstance>,bool)
|
||||
void* pMultiPlayerGameModeUseItem = nullptr; // MultiPlayerGameMode::useItem(shared_ptr<Player>,Level*,shared_ptr<ItemInstance>,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;
|
||||
|
||||