From d2be935adacfedf0015d7a59ebe4f89a313e71ac Mon Sep 17 00:00:00 2001 From: Jacobwasbeast Date: Sun, 8 Mar 2026 00:16:15 -0600 Subject: [PATCH] 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 --- ExampleMod/ExampleMod.cs | 98 ++++++- ExampleMod/assets/blocks/orichalcum_ore.png | Bin 0 -> 2819 bytes ExampleMod/assets/items/ruby_axe.png | Bin 0 -> 2503 bytes ExampleMod/assets/items/ruby_hoe.png | Bin 0 -> 2340 bytes ExampleMod/assets/items/ruby_pickaxe.png | Bin 3178 -> 2991 bytes ExampleMod/assets/items/ruby_shovel.png | Bin 0 -> 3692 bytes ExampleMod/assets/items/ruby_sword.png | Bin 0 -> 2406 bytes ExampleMod/assets/lang/en-GB.lang | 7 + WeaveLoader.API/Block/BlockProperties.cs | 21 ++ WeaveLoader.API/Block/BlockRegistry.cs | 4 +- WeaveLoader.API/Item/CustomItem.cs | 24 +- WeaveLoader.API/Item/ItemProperties.cs | 3 + WeaveLoader.API/Item/ItemRegistry.cs | 111 +++++++- WeaveLoader.API/Item/PickaxeTierRegistry.cs | 59 +++++ WeaveLoader.API/Item/ToolMaterialRegistry.cs | 72 ++++++ WeaveLoader.API/NativeInterop.cs | 50 +++- WeaveLoader.API/Registry.cs | 6 + WeaveLoaderRuntime/CMakeLists.txt | 3 + .../src/CustomBlockRegistry.cpp | 26 ++ WeaveLoaderRuntime/src/CustomBlockRegistry.h | 21 ++ .../src/CustomPickaxeRegistry.cpp | 26 ++ .../src/CustomPickaxeRegistry.h | 14 + .../src/CustomToolMaterialRegistry.cpp | 27 ++ .../src/CustomToolMaterialRegistry.h | 26 ++ WeaveLoaderRuntime/src/GameHooks.cpp | 242 ++++++++++++++++++ WeaveLoaderRuntime/src/GameHooks.h | 14 + WeaveLoaderRuntime/src/GameObjectFactory.cpp | 126 ++++++++- WeaveLoaderRuntime/src/GameObjectFactory.h | 9 + WeaveLoaderRuntime/src/HookManager.cpp | 71 +++++ WeaveLoaderRuntime/src/NativeExports.cpp | 177 ++++++++++++- WeaveLoaderRuntime/src/NativeExports.h | 39 ++- WeaveLoaderRuntime/src/SymbolResolver.cpp | 18 ++ WeaveLoaderRuntime/src/SymbolResolver.h | 6 + 33 files changed, 1273 insertions(+), 27 deletions(-) create mode 100644 ExampleMod/assets/blocks/orichalcum_ore.png create mode 100644 ExampleMod/assets/items/ruby_axe.png create mode 100644 ExampleMod/assets/items/ruby_hoe.png create mode 100644 ExampleMod/assets/items/ruby_shovel.png create mode 100644 ExampleMod/assets/items/ruby_sword.png create mode 100644 WeaveLoader.API/Item/PickaxeTierRegistry.cs create mode 100644 WeaveLoader.API/Item/ToolMaterialRegistry.cs create mode 100644 WeaveLoaderRuntime/src/CustomBlockRegistry.cpp create mode 100644 WeaveLoaderRuntime/src/CustomBlockRegistry.h create mode 100644 WeaveLoaderRuntime/src/CustomPickaxeRegistry.cpp create mode 100644 WeaveLoaderRuntime/src/CustomPickaxeRegistry.h create mode 100644 WeaveLoaderRuntime/src/CustomToolMaterialRegistry.cpp create mode 100644 WeaveLoaderRuntime/src/CustomToolMaterialRegistry.h 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 0000000000000000000000000000000000000000..b325e810c4cc95d85b75ee8bb56dfc7218d20bd8 GIT binary patch literal 2819 zcmc&#X;c&E8Xg6a#c)9=aoxz z-iy=Ts0{!B&fU$$2m9AkKAIZXuj%9;TmaArg8hQfAhs76fr!Xl9uf|b6(R|S1Ax;m zg@hXs38DCKNC1l>3N30v_fu2!Xb2DqF&Wgs@wj6!MMp_KS#%jBw-;cJ0)5Qh-tsS#C{6qyh@f(b17YrBmp15@tb?#fnj`f+Uu0QHEHE;R4Abq_6~q z5iwpFlN*jiqYMH8!|{v3AqBh~T`XH{A0vXIL?|>emGY*!M2Mh>Oo&L9DtgOj30~od z!u&tUqeNDiWAwd0gMur0pD~;L-_1p$x7v`Qt}z%9i=@8Q!s`SwzgP)G@quJWv@`;8 zje*4I78MgpUO*NSg-CrdX+R7DZF!pmE1;+#7gz*|Wf)bZzZ@y+Co$|!oz}E ztNfNBcNU8yMfh+OhRJ;PI^*44S#~r>J3A7cyikCWN6=lw6+m8)Sbz$bk}m^+Fz?-c zWrI=CN`?4~VT=;GDzC~VXc=P=?CF#@((HRLgbfX3W9sH{QLYOY#VVy!>2@TlHHqrr zN2P)`4j_$AqS}H~sw#`h9^t|K*#Bj%R5acRvuCqGH&})u(pc5jg3NuPLkp{gWfZK^ zw^HFsdv1iXI|e}_MR?H>5Kom_rGF(VL-?qiD}|T>Y`_cxlh21S9%AtvrO@$oJe_Pu zwtd@0E`+dfORd@Y(dnR#HE3;1qS`52qx^-%sCcGfRKMo=FE7T14$^3gLw|oUs*mwc z$FzX{Ut?Nud1p*36u4N76GeG4zLt;^WrfHV4Bj|OvGFeKVMjD})>D=adu*|ET|i>& zj*w!PL7H249{{XMfw2#G#fJ+3+mA;dzv!vPG|bp#aD9-eP9hRhDzev_=+)O8Q6vXn z(>!pSUstH-aW$^mmHP5E3Dj~1iS$np_rt*Bp@$!yy{x5qQ12%UM zKe(CD%&O$_(|$;^2&_$S`KcLq(D|rViMITtlsOc#_t>SmUnIScC>gii6vj@`&B@c0 zocVBDZh7jXM;FTS9P1^f!&_~G<6`!N*C`B)xUHJ{VJZ6QTK8JMX=j2XXFUV*Z;Z{i zm2BH`v9ev`A18?h7R&v(PgHzC_)cDCzruc2^%TNXfMLDKRqbCg}#! z^J4h4X!zLGSG-BTFkxJ7-^6DA2 zS61^n`_axuj)z%Us zpzl!Z`fA&9xGN~N_j1L>nwa(x8-5Hpgo39>9%tpO6;@kL9zg~+O+N3;t2|=V@~qaT z?aBD|FFxG7`R)sX___Dhq{Bri&x>!l1h02BYrNBMVH#u{P?HNsR_i zJEwC60ZmoLHE~0$?Ks8ubya1ZbfcFkVVM>`hl~^+-g=tE*ugkAucn_8pmkpB4yJ1W NaCh}~Iqw{n@*mVo5#|5@ literal 0 HcmV?d00001 diff --git a/ExampleMod/assets/items/ruby_axe.png b/ExampleMod/assets/items/ruby_axe.png new file mode 100644 index 0000000000000000000000000000000000000000..7278c5aecb03cf1e4ace8ec8f2f364c4ec5f84b4 GIT binary patch literal 2503 zcmd5;Yfuwc6yB(yKs8z)I9M^PRRA#vObfA=kA*kh_{xsTs9$39hA#0*#JJAp};V z8Q>5U7H&0bwCM!HOCeH8lML=YbsFZ8IvKoDtVES&Igv(2+bl%9ZK+CYOV>(uaQHmW zFe?rc7zswhvlr{)L4*PndC}dRMllqfMwth9^is?qJS>nQ z^>3)dBE#GPeXp-z)tF!BtW^HJyV3Yk8#EKO9uVOo^`#aD3ec)7Gl9erG?ih|5>e|3 z6EoAn1j`F9rwo)O9!P_b!NMVPENLYSY6WQ|Of(=}21|lo0TYLVgQPjLDk&XEDssdP zK{0Z9tcB8(27u9V3nO_kQF4h;Dv|IpfpY^ak9dqxlS(WhOsP!TVDTYfC8>MW&NgVs zU=@;JA^|0sqpl-FJ%q{NU<`SoOyX-HFf?2V)UDGn8ij@dJH=2;!be4XG(?38aiIto z2lG)ej-rk#4u48V>a+gIn^iO~4EQURcr-~flqJhya>_iOSm#_hmj=?IZ&u-~zedY; zCxguvN|&J}bdJ&v{j(@d>6uK8g@{N61D3%NdOZnv$l}GaLg!&TOdt`6Uk=GkBS5;r z)+8zn!-Ir4hVoGf+Zys8ZYEd-NES))AnE^LGhpZ_E&`ljuA#r48OO``#xXh3e>Em& z$SY$Sra;$b!Vvak9FR~LGE8JnhZl}gFkYt(9MRycM+Ogjuo%292oty?EZ{OoKd{Xo zf;gK<@B$BeaUsaRFr%;~K(Tz%g>aGld_jsvBlHMpU+CerqvY9pf%nr;CB)4~(d41ydDWT>Dk4xOTwTIoB=X5D&-{~Fo;NTVOSh1I>DP`k~7>+Wnb~@eCVAAMLbUZNs8bKaI1TmqZ4Te@|ws#9H?~PR4}1}7)6B2y6?AQ zzh#ZFleUKD`_Z!&kJqUK57kXM+oH}dE!~*Bk+XSK{_XI$HdU5K!f1NOl=h&*+p`ta zBD*cEtMBHe^iO`+o!zz}J8|}wf{O)wD66S@e_gMAmv}}-pH{ALuX3xB>b=ffyd`V+ z@pM&jN#~6&k2|6BS^NDB+(%e{O>xQ7djFya9=AByM*c?Sp9POXuKIRxW2HR=wnI)?`Cmx@3`?_!Ev!t4C)v^I7eDs=*9bd#BfBv*V z+?!ex?DGx1CHRlRfwH$o)=QIfbIN=~`%7Imro6VlJZo0Y=qBhSB&qCaePU>t?ON^Z z2{|F{p3pE-2PHw%g7?e!_xQS6Ue{xjPH$~%oy8-^oNf1BJ~MJChWQAm7vX}(0P^x1^ zM#Neb#wzL$5bA4eiy$pNhjCCrtEjDtxL!kPM}m(6D+tw__e_DASj_#Cu05{ZMLvpQU>FyEEllcLA-ZX(gr07df>MYS6rCv-;qo|0Fb7#ALl6|yB-Ws|qH3axO*crOTb# z@26cLcP}_JDV?*skT>79657F5@4ox8rzZSD=RylOFS%xZPV_R(6~o+L53G@Q53K(4 z(czs5Qd!g9{T~)fTO+!T7#`_rlBx%5^N#X-Yh;y`z5&ZUf&&FJ1mEor@_*p1Px*3b zd|&+Ord{rCZT#$;?S5N)puF(R#pe${BtGbwvvg6~`E@sLZfTi$CaifM@I9pp7%dXbZwR_t|v%$a?98Q>A+9_6#q z^Uk!KvrBK4R(V!MXo8pLU1>Z7ZBOnnQwM63n+LK`@2u57i2f?ZgbyQ}LI4in zk?YxlksUpex3)F7e(&l?gC)1+T5}5PI`-dw#gNM?Z??Mkx|Ta>owR9PHOIbw?mHa0 z{(j!7NE_$goP1WfOF40`cw_j;DQMtSm#u#Dz=RgyTOdT;0NoIG4E6N4KA-NO+T>O6 Z*i*DCX~9|5@j~X$jW{+zbS!3V!QcOmF-ZUb literal 0 HcmV?d00001 diff --git a/ExampleMod/assets/items/ruby_pickaxe.png b/ExampleMod/assets/items/ruby_pickaxe.png index b019ba4ef0f80b90777400d68657bedbe8407afa..0bab1f0413118c0c18268953ad58856018ca238a 100644 GIT binary patch delta 1535 zcmVHVKHe~4UJJ^V=-bVEXGp?o0&KBX4K4hy*H90a2HvdP<@pdP;vrmKD|izkRih z-cYf+Hw-n8j`P|9o=y1LaGZ|=$N4%0_XC{gOZkiY(Eld-MoIJULE{>nUX--NHk_?P z>%AGb;y;pG++`T>atsYIXug8Fxnj?L_c^))ZB@SN5_Wc>Q_eTee*@{&XRX&F8O;Cy z010qNS#tmY3ljhU3lo0<0GgZ_000C2Nkls$9Z?1Z(ot|{zeh1+dpv8|S=r96_xzRa#xtIs znfIOdYsRo)!-fqTHf*@9kfkU1R>TDm_lYA3it~8pHS3&%^`U?8OnAx46a#IFQ_Z}# zFz&*T_=jAa(PI02{P(TrWdh~H;{s;Hp>(sx0`FhZ2%uYbM*X|&yK#3zDf>7Zi2!gA{ zo&w_GUF?Tcq1}H8WX18qZ)0T!&05w396)esr>f$Nm84Ur@A4(3b)j&dA)>UHT?6Xf zgt%i~cAR_R-IAVnDe@*q2`*6(nNGy)1U$hdU3KB+!G;q*E*9*(F7ZH`YuitZtBaDS zH;D$g^0k(z21+Xz^s|pT^FM#}$3g2|Km#(k;aq2JS($%7$NJ^|KstpS_5)WI@nlKcR`&DohV6zo<{fM&LB z0+qIVU4`o-ph*!rc;`M~EY9!S{J(>_im@+PO^T?ZR*UbQ*wFMHU-G)34iYa>-K

91LnhEC?CJ@y0Rs_Lb_lf->=SlzA8>e_AGY}%xf85S!0W4YAzq=MX&CHt zY;~)f#na#!hrt}d2Lu8{6_Hl>v&;ndKBIUzFx5z%V4n%h?gQqI^J6gS`=AoOFSr?M zwG-?=4Xs}TMlwMk$plwVKyP-JW`ao=?CXDf^yBbUTsoA^iBBn92?B>}vQk?0zx4ep zk;!+0OUL;`@eG2P@f+=v5gAu+0yzvC;opYovm*e~s4z~!*%9D`*q?g;AslRvV{!h+ z-~Q~>m$vz-I6DnE6dM7@Ucj+2YM-YT=jX<)*MNg delta 1847 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 Y8W*Hr`LsYcg#CLYPZ%fLGdd&x9|{RtGXMYp literal 0 HcmV?d00001 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;