mirror of
https://github.com/Jacobwasbeast/LegacyWeaveLoader.git
synced 2026-06-22 20:35:33 +00:00
feat(items): item display transforms and custom renderers
This commit is contained in:
@@ -392,6 +392,7 @@ public class ExampleMod : IMod
|
||||
new ItemProperties()
|
||||
.MaxStackSize(1)
|
||||
.Icon("examplemod:item/ruby_wand")
|
||||
.Model("examplemod:item/ruby_wand")
|
||||
.Name(Text.Translatable("item.examplemod.ruby_wand"))
|
||||
.InCreativeTab(CreativeTab.ToolsAndWeapons));
|
||||
|
||||
|
||||
25
ExampleMod/assets/examplemod/models/item/handheld.json
Normal file
25
ExampleMod/assets/examplemod/models/item/handheld.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"parent": "item/generated",
|
||||
"display": {
|
||||
"thirdperson_righthand": {
|
||||
"rotation": [ 0, -90, 55 ],
|
||||
"translation": [ 0, 4.0, 0.5 ],
|
||||
"scale": [ 0.85, 0.85, 0.85 ]
|
||||
},
|
||||
"thirdperson_lefthand": {
|
||||
"rotation": [ 0, 90, -55 ],
|
||||
"translation": [ 0, 4.0, 0.5 ],
|
||||
"scale": [ 0.85, 0.85, 0.85 ]
|
||||
},
|
||||
"firstperson_righthand": {
|
||||
"rotation": [ 0, -90, 25 ],
|
||||
"translation": [ 1.13, 3.2, 1.13 ],
|
||||
"scale": [ 0.68, 0.68, 0.68 ]
|
||||
},
|
||||
"firstperson_lefthand": {
|
||||
"rotation": [ 0, 90, -25 ],
|
||||
"translation": [ 1.13, 3.2, 1.13 ],
|
||||
"scale": [ 0.68, 0.68, 0.68 ]
|
||||
}
|
||||
}
|
||||
}
|
||||
6
ExampleMod/assets/examplemod/models/item/ruby_wand.json
Normal file
6
ExampleMod/assets/examplemod/models/item/ruby_wand.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "item/handheld",
|
||||
"textures": {
|
||||
"layer0": "examplemod:item/ruby_wand"
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using WeaveLoader.API.Item;
|
||||
|
||||
namespace WeaveLoader.API.Assets;
|
||||
|
||||
@@ -12,6 +13,7 @@ internal static class ModelResolver
|
||||
{
|
||||
public string IconName = "";
|
||||
public List<ModelBox> Boxes = new();
|
||||
public Dictionary<ItemDisplayContext, ItemDisplayTransform> DisplayTransforms = new();
|
||||
}
|
||||
|
||||
private enum ModelKind
|
||||
@@ -132,6 +134,18 @@ internal static class ModelResolver
|
||||
properties.IconValue = model.IconName;
|
||||
Logger.Debug($"Model resolved for item '{id}' -> icon '{model.IconName}'");
|
||||
}
|
||||
if (model.DisplayTransforms.Count > 0)
|
||||
{
|
||||
properties.DisplayTransforms ??= new Dictionary<ItemDisplayContext, ItemDisplayTransform>();
|
||||
foreach (var entry in model.DisplayTransforms)
|
||||
{
|
||||
if (!properties.DisplayTransforms.ContainsKey(entry.Key))
|
||||
properties.DisplayTransforms[entry.Key] = entry.Value;
|
||||
}
|
||||
Logger.Info($"Item display transforms loaded for '{id}': {model.DisplayTransforms.Count}");
|
||||
if (!properties.HandEquippedValue.HasValue && HasHandTransforms(model.DisplayTransforms))
|
||||
properties.HandEquippedValue = true;
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(properties.ModelValue))
|
||||
{
|
||||
@@ -203,6 +217,8 @@ internal static class ModelResolver
|
||||
}
|
||||
|
||||
TryParseElements(modelPath, data.Boxes);
|
||||
if (kind == ModelKind.Item)
|
||||
TryParseDisplay(modelPath, modelNamespace, data.DisplayTransforms, new HashSet<string>(StringComparer.OrdinalIgnoreCase));
|
||||
model = data;
|
||||
return !string.IsNullOrWhiteSpace(data.IconName) || data.Boxes.Count > 0;
|
||||
}
|
||||
@@ -349,13 +365,160 @@ internal static class ModelResolver
|
||||
continue;
|
||||
textures[prop.Name] = value!;
|
||||
}
|
||||
return textures.Count > 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning($"Failed to parse model JSON '{modelPath}': {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return textures.Count > 0;
|
||||
}
|
||||
|
||||
private static void TryParseDisplay(string modelPath, string modelNamespace, Dictionary<ItemDisplayContext, ItemDisplayTransform> display, HashSet<string> visited)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(modelPath) || string.IsNullOrWhiteSpace(modelNamespace))
|
||||
return;
|
||||
if (visited.Contains(modelPath))
|
||||
return;
|
||||
|
||||
visited.Add(modelPath);
|
||||
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(File.ReadAllText(modelPath));
|
||||
JsonElement root = doc.RootElement;
|
||||
|
||||
if (root.TryGetProperty("parent", out JsonElement parentEl) && parentEl.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
string? parentValue = parentEl.GetString();
|
||||
if (!string.IsNullOrWhiteSpace(parentValue) &&
|
||||
TryGetParentModelFilePath(parentValue!, modelNamespace, out string parentPath, out string parentNamespace) &&
|
||||
File.Exists(parentPath))
|
||||
{
|
||||
TryParseDisplay(parentPath, parentNamespace, display, visited);
|
||||
}
|
||||
}
|
||||
|
||||
if (root.TryGetProperty("display", out JsonElement displayEl) && displayEl.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
foreach (var entry in displayEl.EnumerateObject())
|
||||
{
|
||||
if (!TryMapDisplayContext(entry.Name, out ItemDisplayContext context))
|
||||
continue;
|
||||
if (entry.Value.ValueKind != JsonValueKind.Object)
|
||||
continue;
|
||||
ItemDisplayTransform transform = ParseDisplayTransform(entry.Value);
|
||||
display[context] = transform;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning($"Failed to parse item display data '{modelPath}': {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetParentModelFilePath(string parentValue, string currentNamespace, out string modelPath, out string modelNamespace)
|
||||
{
|
||||
modelPath = "";
|
||||
modelNamespace = "";
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ModContext.ModFolder))
|
||||
return false;
|
||||
|
||||
string value = parentValue.Replace('\\', '/');
|
||||
string ns = currentNamespace;
|
||||
int colon = value.IndexOf(':');
|
||||
if (colon >= 0)
|
||||
{
|
||||
ns = value.Substring(0, colon);
|
||||
value = value.Substring(colon + 1);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return false;
|
||||
|
||||
string relative = value.Replace('/', Path.DirectorySeparatorChar);
|
||||
string fullPath = Path.Combine(ModContext.ModFolder!, "assets", ns, "models", relative + ".json");
|
||||
modelPath = fullPath;
|
||||
modelNamespace = ns;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryMapDisplayContext(string name, out ItemDisplayContext context)
|
||||
{
|
||||
switch (name.Trim().ToLowerInvariant())
|
||||
{
|
||||
case "gui":
|
||||
context = ItemDisplayContext.Gui;
|
||||
return true;
|
||||
case "ground":
|
||||
context = ItemDisplayContext.Ground;
|
||||
return true;
|
||||
case "fixed":
|
||||
context = ItemDisplayContext.Fixed;
|
||||
return true;
|
||||
case "head":
|
||||
context = ItemDisplayContext.Head;
|
||||
return true;
|
||||
case "firstperson_righthand":
|
||||
context = ItemDisplayContext.FirstPersonRightHand;
|
||||
return true;
|
||||
case "firstperson_lefthand":
|
||||
context = ItemDisplayContext.FirstPersonLeftHand;
|
||||
return true;
|
||||
case "thirdperson_righthand":
|
||||
context = ItemDisplayContext.ThirdPersonRightHand;
|
||||
return true;
|
||||
case "thirdperson_lefthand":
|
||||
context = ItemDisplayContext.ThirdPersonLeftHand;
|
||||
return true;
|
||||
default:
|
||||
context = ItemDisplayContext.Gui;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static ItemDisplayTransform ParseDisplayTransform(JsonElement element)
|
||||
{
|
||||
float rX = 0.0f;
|
||||
float rY = 0.0f;
|
||||
float rZ = 0.0f;
|
||||
float tX = 0.0f;
|
||||
float tY = 0.0f;
|
||||
float tZ = 0.0f;
|
||||
float sX = 1.0f;
|
||||
float sY = 1.0f;
|
||||
float sZ = 1.0f;
|
||||
|
||||
ReadVec3(element, "rotation", ref rX, ref rY, ref rZ);
|
||||
ReadVec3(element, "translation", ref tX, ref tY, ref tZ);
|
||||
ReadVec3(element, "scale", ref sX, ref sY, ref sZ);
|
||||
|
||||
return new ItemDisplayTransform(rX, rY, rZ, tX, tY, tZ, sX, sY, sZ);
|
||||
}
|
||||
|
||||
private static void ReadVec3(JsonElement parent, string name, ref float x, ref float y, ref float z)
|
||||
{
|
||||
if (!parent.TryGetProperty(name, out JsonElement el) || el.ValueKind != JsonValueKind.Array)
|
||||
return;
|
||||
|
||||
int count = el.GetArrayLength();
|
||||
if (count > 0 && el[0].ValueKind == JsonValueKind.Number)
|
||||
x = (float)el[0].GetDouble();
|
||||
if (count > 1 && el[1].ValueKind == JsonValueKind.Number)
|
||||
y = (float)el[1].GetDouble();
|
||||
if (count > 2 && el[2].ValueKind == JsonValueKind.Number)
|
||||
z = (float)el[2].GetDouble();
|
||||
}
|
||||
|
||||
private static bool HasHandTransforms(Dictionary<ItemDisplayContext, ItemDisplayTransform> display)
|
||||
{
|
||||
return display.ContainsKey(ItemDisplayContext.FirstPersonRightHand) ||
|
||||
display.ContainsKey(ItemDisplayContext.FirstPersonLeftHand) ||
|
||||
display.ContainsKey(ItemDisplayContext.ThirdPersonRightHand) ||
|
||||
display.ContainsKey(ItemDisplayContext.ThirdPersonLeftHand);
|
||||
}
|
||||
|
||||
private static void TryParseElements(string modelPath, List<ModelBox> boxes)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using WeaveLoader.API;
|
||||
|
||||
namespace WeaveLoader.API.Item;
|
||||
@@ -12,6 +13,9 @@ public class ItemProperties
|
||||
internal float AttackDamageValue = 0.0f;
|
||||
internal string IconValue = "";
|
||||
internal string? ModelValue;
|
||||
internal Dictionary<ItemDisplayContext, ItemDisplayTransform>? DisplayTransforms;
|
||||
internal IItemRenderer? RendererValue;
|
||||
internal bool? HandEquippedValue;
|
||||
internal CreativeTab CreativeTabValue = CreativeTab.None;
|
||||
internal CreativePlacement? CreativePlacementValue;
|
||||
internal Text? NameValue;
|
||||
@@ -28,6 +32,20 @@ public class ItemProperties
|
||||
/// and use its texture for the item icon.
|
||||
/// </summary>
|
||||
public ItemProperties Model(string modelName) { ModelValue = modelName; return this; }
|
||||
/// <summary>
|
||||
/// Override the Java-style display transform for a rendering context (gui, ground, first/third person).
|
||||
/// Values are interpreted the same way as Minecraft Java item model "display" transforms.
|
||||
/// </summary>
|
||||
public ItemProperties DisplayTransform(ItemDisplayContext context, ItemDisplayTransform transform)
|
||||
{
|
||||
DisplayTransforms ??= new Dictionary<ItemDisplayContext, ItemDisplayTransform>();
|
||||
DisplayTransforms[context] = transform;
|
||||
return this;
|
||||
}
|
||||
/// <summary>Register a custom renderer for this item.</summary>
|
||||
public ItemProperties Renderer(IItemRenderer renderer) { RendererValue = renderer; return this; }
|
||||
/// <summary>Force the item to be treated as hand-equipped (sword-like pose) by the renderer.</summary>
|
||||
public ItemProperties HandEquipped(bool handEquipped = true) { HandEquippedValue = handEquipped; return this; }
|
||||
|
||||
/// <summary>
|
||||
/// Set max damage for a tool/armor item. Setting this to a positive value
|
||||
|
||||
@@ -190,6 +190,19 @@ public static class ItemRegistry
|
||||
throw new InvalidOperationException($"Failed to register item '{id}'. No free IDs or invalid parameters.");
|
||||
}
|
||||
|
||||
if (properties.DisplayTransforms != null && properties.DisplayTransforms.Count > 0)
|
||||
{
|
||||
foreach (var entry in properties.DisplayTransforms)
|
||||
{
|
||||
NativeInterop.native_register_item_display_transform(numericId, (int)entry.Key, entry.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (properties.RendererValue != null)
|
||||
ManagedItemRendererDispatcher.Register(numericId, properties.RendererValue);
|
||||
if (properties.HandEquippedValue.HasValue)
|
||||
NativeInterop.native_set_item_hand_equipped(numericId, properties.HandEquippedValue.Value ? 1 : 0);
|
||||
|
||||
if (properties.CreativeTabValue != CreativeTab.None)
|
||||
{
|
||||
bool added = false;
|
||||
|
||||
102
WeaveLoader.API/Item/ItemRendering.cs
Normal file
102
WeaveLoader.API/Item/ItemRendering.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace WeaveLoader.API.Item;
|
||||
|
||||
public enum ItemDisplayContext
|
||||
{
|
||||
Gui = 0,
|
||||
Ground = 1,
|
||||
Fixed = 2,
|
||||
Head = 3,
|
||||
FirstPersonRightHand = 4,
|
||||
FirstPersonLeftHand = 5,
|
||||
ThirdPersonRightHand = 6,
|
||||
ThirdPersonLeftHand = 7,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ItemDisplayTransform
|
||||
{
|
||||
public float RotationX;
|
||||
public float RotationY;
|
||||
public float RotationZ;
|
||||
public float TranslationX;
|
||||
public float TranslationY;
|
||||
public float TranslationZ;
|
||||
public float ScaleX;
|
||||
public float ScaleY;
|
||||
public float ScaleZ;
|
||||
|
||||
public ItemDisplayTransform(
|
||||
float rotationX,
|
||||
float rotationY,
|
||||
float rotationZ,
|
||||
float translationX,
|
||||
float translationY,
|
||||
float translationZ,
|
||||
float scaleX,
|
||||
float scaleY,
|
||||
float scaleZ)
|
||||
{
|
||||
RotationX = rotationX;
|
||||
RotationY = rotationY;
|
||||
RotationZ = rotationZ;
|
||||
TranslationX = translationX;
|
||||
TranslationY = translationY;
|
||||
TranslationZ = translationZ;
|
||||
ScaleX = scaleX;
|
||||
ScaleY = scaleY;
|
||||
ScaleZ = scaleZ;
|
||||
}
|
||||
|
||||
public static ItemDisplayTransform Identity => new(
|
||||
0.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f,
|
||||
1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
public readonly struct ItemRenderContext
|
||||
{
|
||||
public int ItemId { get; }
|
||||
public ItemDisplayContext DisplayContext { get; }
|
||||
public IntPtr RendererPtr { get; }
|
||||
public IntPtr ItemInstancePtr { get; }
|
||||
public float X { get; }
|
||||
public float Y { get; }
|
||||
public float ScaleX { get; }
|
||||
public float ScaleY { get; }
|
||||
public float Alpha { get; }
|
||||
|
||||
internal ItemRenderContext(ItemRenderNativeArgs args)
|
||||
{
|
||||
ItemId = args.ItemId;
|
||||
DisplayContext = (ItemDisplayContext)args.Context;
|
||||
RendererPtr = args.RendererPtr;
|
||||
ItemInstancePtr = args.ItemInstancePtr;
|
||||
X = args.X;
|
||||
Y = args.Y;
|
||||
ScaleX = args.ScaleX;
|
||||
ScaleY = args.ScaleY;
|
||||
Alpha = args.Alpha;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IItemRenderer
|
||||
{
|
||||
bool Render(ItemRenderContext context);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct ItemRenderNativeArgs
|
||||
{
|
||||
public int ItemId;
|
||||
public int Context;
|
||||
public IntPtr RendererPtr;
|
||||
public IntPtr ItemInstancePtr;
|
||||
public float X;
|
||||
public float Y;
|
||||
public float ScaleX;
|
||||
public float ScaleY;
|
||||
public float Alpha;
|
||||
}
|
||||
42
WeaveLoader.API/Item/ManagedItemRendererDispatcher.cs
Normal file
42
WeaveLoader.API/Item/ManagedItemRendererDispatcher.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace WeaveLoader.API.Item;
|
||||
|
||||
internal static class ManagedItemRendererDispatcher
|
||||
{
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate int NativeItemRenderDelegate(IntPtr args, int sizeBytes);
|
||||
|
||||
private static readonly Dictionary<int, IItemRenderer> s_renderers = new();
|
||||
private static readonly NativeItemRenderDelegate s_callback = HandleRender;
|
||||
private static readonly IntPtr s_callbackPtr = Marshal.GetFunctionPointerForDelegate(s_callback);
|
||||
private static readonly int s_argsSize = Marshal.SizeOf<ItemRenderNativeArgs>();
|
||||
|
||||
internal static void Register(int itemId, IItemRenderer renderer)
|
||||
{
|
||||
s_renderers[itemId] = renderer;
|
||||
NativeInterop.native_register_item_renderer(itemId, s_callbackPtr);
|
||||
}
|
||||
|
||||
private static int HandleRender(IntPtr args, int sizeBytes)
|
||||
{
|
||||
if (args == IntPtr.Zero || sizeBytes < s_argsSize)
|
||||
return 0;
|
||||
|
||||
ItemRenderNativeArgs nativeArgs = Marshal.PtrToStructure<ItemRenderNativeArgs>(args);
|
||||
if (!s_renderers.TryGetValue(nativeArgs.ItemId, out IItemRenderer? renderer) || renderer == null)
|
||||
return 0;
|
||||
|
||||
try
|
||||
{
|
||||
return renderer.Render(new ItemRenderContext(nativeArgs)) ? 1 : 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"Item renderer for {nativeArgs.ItemId} threw: {ex.Message}");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,22 @@ internal static class NativeInterop
|
||||
string iconName,
|
||||
string displayName);
|
||||
|
||||
[DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void native_register_item_display_transform(
|
||||
int numericItemId,
|
||||
int context,
|
||||
Item.ItemDisplayTransform transform);
|
||||
|
||||
[DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void native_register_item_renderer(
|
||||
int numericItemId,
|
||||
nint rendererFnPtr);
|
||||
|
||||
[DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void native_set_item_hand_equipped(
|
||||
int numericItemId,
|
||||
int isHandEquipped);
|
||||
|
||||
[DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
internal static extern int native_register_pickaxe_item(
|
||||
string namespacedId,
|
||||
|
||||
@@ -103,6 +103,7 @@ add_library(WeaveLoaderRuntime SHARED
|
||||
src/GameObjectFactory.cpp
|
||||
src/ModStrings.cpp
|
||||
src/ModAtlas.cpp
|
||||
src/ItemRenderRegistry.cpp
|
||||
)
|
||||
|
||||
target_include_directories(WeaveLoaderRuntime PRIVATE
|
||||
@@ -115,6 +116,7 @@ target_link_libraries(WeaveLoaderRuntime PRIVATE
|
||||
minhook
|
||||
raw_pdb
|
||||
bcrypt
|
||||
opengl32
|
||||
)
|
||||
|
||||
if(EXISTS "${NETHOST_LIB}")
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
#include "LogUtil.h"
|
||||
#include "WorldIdRemap.h"
|
||||
#include "ModelRegistry.h"
|
||||
#include "ItemRenderRegistry.h"
|
||||
#include <Windows.h>
|
||||
#include <gl/GL.h>
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
@@ -51,6 +53,9 @@ namespace GameHooks
|
||||
ItemInstanceGetIcon_fn Original_ItemInstanceGetIcon = nullptr;
|
||||
EntityRendererBindTextureResource_fn Original_EntityRendererBindTextureResource = nullptr;
|
||||
ItemRendererRenderItemBillboard_fn Original_ItemRendererRenderItemBillboard = nullptr;
|
||||
ItemRendererRenderGuiItem_fn Original_ItemRendererRenderGuiItem = nullptr;
|
||||
ItemInHandRendererRender_fn Original_ItemInHandRendererRender = nullptr;
|
||||
ItemInHandRendererRenderItem_fn Original_ItemInHandRendererRenderItem = nullptr;
|
||||
AnimatedTextureCycleFrames_fn Original_CompassTextureCycleFrames = nullptr;
|
||||
AnimatedTextureCycleFrames_fn Original_ClockTextureCycleFrames = nullptr;
|
||||
TextureGetSourceDim_fn Original_CompassTextureGetSourceWidth = nullptr;
|
||||
@@ -658,11 +663,14 @@ namespace GameHooks
|
||||
static InventoryRemoveResource_fn s_inventoryRemoveResource = nullptr;
|
||||
static void* s_inventoryVtable = nullptr;
|
||||
static ItemInstanceHurtAndBreak_fn s_itemInstanceHurtAndBreak = nullptr;
|
||||
static ItemEntityGetItem_fn s_itemEntityGetItem = nullptr;
|
||||
static thread_local int s_firstPersonRenderDepth = 0;
|
||||
static std::string s_modsPath;
|
||||
static std::unordered_map<std::string, std::string> s_modAssetRoots;
|
||||
static bool s_modAssetsIndexed = false;
|
||||
static std::atomic<bool> s_modAssetsIndexing{false};
|
||||
static std::mutex s_modAssetsMutex;
|
||||
static std::unordered_map<int, int> s_itemRenderLogCount;
|
||||
// Verified from compiled Player::inventory accesses in this game build.
|
||||
static constexpr ptrdiff_t kPlayerInventoryOffset = 0x340;
|
||||
static constexpr ptrdiff_t kLevelIsClientSideOffset = 0x268;
|
||||
@@ -1210,7 +1218,21 @@ namespace GameHooks
|
||||
void* playerSharedPtr;
|
||||
};
|
||||
|
||||
struct ItemRenderNativeArgs
|
||||
{
|
||||
int itemId;
|
||||
int context;
|
||||
void* rendererPtr;
|
||||
void* itemInstancePtr;
|
||||
float x;
|
||||
float y;
|
||||
float scaleX;
|
||||
float scaleY;
|
||||
float alpha;
|
||||
};
|
||||
|
||||
static bool IsFireballFamilyEntityId(int entityNumericId);
|
||||
static bool LooksLikeEntityPtr(void* candidate);
|
||||
static void* DecodeItemInstancePtrFromSharedArg(void* sharedArg);
|
||||
static void* DecodePlayerPtrFromSharedArg(void* sharedArg);
|
||||
|
||||
@@ -1239,6 +1261,136 @@ namespace GameHooks
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool TryGetItemIdFromItemInstanceShared(void* itemInstanceSharedPtr, int& outItemId, void** outItemInstancePtr)
|
||||
{
|
||||
void* itemInstancePtr = DecodeItemInstancePtrFromSharedArg(itemInstanceSharedPtr);
|
||||
if (!itemInstancePtr)
|
||||
return false;
|
||||
int itemId = 0;
|
||||
if (!TryReadItemId(itemInstancePtr, itemId))
|
||||
return false;
|
||||
outItemId = itemId;
|
||||
if (outItemInstancePtr)
|
||||
*outItemInstancePtr = itemInstancePtr;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void* DecodeEntityPtrFromSharedArg(void* sharedArg)
|
||||
{
|
||||
if (!sharedArg || !IsCanonicalUserPtr(sharedArg))
|
||||
return nullptr;
|
||||
|
||||
__try
|
||||
{
|
||||
void* p = *reinterpret_cast<void**>(sharedArg);
|
||||
if (LooksLikeEntityPtr(p))
|
||||
return p;
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) {}
|
||||
|
||||
if (LooksLikeEntityPtr(sharedArg))
|
||||
return sharedArg;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool TryGetItemIdFromItemEntityShared(void* itemEntitySharedPtr, int& outItemId, void** outItemInstancePtr)
|
||||
{
|
||||
if (!s_itemEntityGetItem)
|
||||
return false;
|
||||
|
||||
void* itemEntityPtr = DecodeEntityPtrFromSharedArg(itemEntitySharedPtr);
|
||||
if (!itemEntityPtr)
|
||||
return false;
|
||||
|
||||
std::shared_ptr<void> itemShared;
|
||||
s_itemEntityGetItem(&itemShared, itemEntityPtr);
|
||||
void* itemInstancePtr = DecodeItemInstancePtrFromSharedArg(&itemShared);
|
||||
if (!itemInstancePtr)
|
||||
return false;
|
||||
|
||||
int itemId = 0;
|
||||
if (!TryReadItemId(itemInstancePtr, itemId))
|
||||
return false;
|
||||
|
||||
outItemId = itemId;
|
||||
if (outItemInstancePtr)
|
||||
*outItemInstancePtr = itemInstancePtr;
|
||||
return true;
|
||||
}
|
||||
|
||||
static float GetTranslationScaleForContext(int context)
|
||||
{
|
||||
return context == ItemDisplay_Gui ? 1.0f : (1.0f / 16.0f);
|
||||
}
|
||||
|
||||
static void ApplyItemDisplayTransform(const ItemDisplayTransformNative& transform, int context)
|
||||
{
|
||||
float tScale = GetTranslationScaleForContext(context);
|
||||
glTranslatef(transform.transX * tScale, transform.transY * tScale, transform.transZ * tScale);
|
||||
glRotatef(transform.rotZ, 0.0f, 0.0f, 1.0f);
|
||||
glRotatef(transform.rotY, 0.0f, 1.0f, 0.0f);
|
||||
glRotatef(transform.rotX, 1.0f, 0.0f, 0.0f);
|
||||
glScalef(transform.scaleX, transform.scaleY, transform.scaleZ);
|
||||
}
|
||||
|
||||
static int BeginModelViewTransform()
|
||||
{
|
||||
GLint prevMode = GL_MODELVIEW;
|
||||
glGetIntegerv(GL_MATRIX_MODE, &prevMode);
|
||||
if (prevMode != GL_MODELVIEW)
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
return prevMode;
|
||||
}
|
||||
|
||||
static void EndModelViewTransform(int prevMode)
|
||||
{
|
||||
if (prevMode != GL_MODELVIEW)
|
||||
glMatrixMode(prevMode);
|
||||
}
|
||||
|
||||
static bool TryGetDisplayTransformWithFallback(int itemId, int context, ItemDisplayTransformNative& outTransform)
|
||||
{
|
||||
if (ItemRenderRegistry::TryGetDisplayTransform(itemId, context, outTransform))
|
||||
return true;
|
||||
|
||||
if (context == ItemDisplay_FirstPersonRightHand)
|
||||
return ItemRenderRegistry::TryGetDisplayTransform(itemId, ItemDisplay_FirstPersonLeftHand, outTransform);
|
||||
if (context == ItemDisplay_ThirdPersonRightHand)
|
||||
return ItemRenderRegistry::TryGetDisplayTransform(itemId, ItemDisplay_ThirdPersonLeftHand, outTransform);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool TryRenderCustomItem(int itemId,
|
||||
int context,
|
||||
void* rendererPtr,
|
||||
void* itemInstancePtr,
|
||||
float x,
|
||||
float y,
|
||||
float scaleX,
|
||||
float scaleY,
|
||||
float alpha)
|
||||
{
|
||||
ManagedItemRenderFn fn = ItemRenderRegistry::GetCustomRenderer(itemId);
|
||||
if (!fn)
|
||||
return false;
|
||||
|
||||
ItemRenderNativeArgs args{};
|
||||
args.itemId = itemId;
|
||||
args.context = context;
|
||||
args.rendererPtr = rendererPtr;
|
||||
args.itemInstancePtr = itemInstancePtr;
|
||||
args.x = x;
|
||||
args.y = y;
|
||||
args.scaleX = scaleX;
|
||||
args.scaleY = scaleY;
|
||||
args.alpha = alpha;
|
||||
|
||||
int handled = fn(&args, sizeof(args));
|
||||
return handled != 0;
|
||||
}
|
||||
|
||||
static int TryDispatchMineBlockFromItemInstancePtr(void* itemInstancePtr, int tile, int x, int y, int z, const char* sourceTag)
|
||||
{
|
||||
if (!itemInstancePtr)
|
||||
@@ -1287,6 +1439,11 @@ namespace GameHooks
|
||||
s_entitySetPos = reinterpret_cast<EntitySetPos_fn>(entitySetPos);
|
||||
}
|
||||
|
||||
void SetItemRenderSymbols(void* itemEntityGetItem)
|
||||
{
|
||||
s_itemEntityGetItem = reinterpret_cast<ItemEntityGetItem_fn>(itemEntityGetItem);
|
||||
}
|
||||
|
||||
void SetUseActionSymbols(void* inventoryRemoveResource,
|
||||
void* inventoryVtable,
|
||||
void* itemInstanceHurtAndBreak,
|
||||
@@ -1458,6 +1615,9 @@ namespace GameHooks
|
||||
{
|
||||
if (!candidate || !IsReadableRange(candidate, sizeof(void*)))
|
||||
return false;
|
||||
// Reject pointers that are already in game code space (likely vtable pointers).
|
||||
if (IsGameCodePtr(candidate))
|
||||
return false;
|
||||
void* vt = *reinterpret_cast<void**>(candidate);
|
||||
if (!IsCanonicalUserPtr(vt))
|
||||
return false;
|
||||
@@ -2645,6 +2805,113 @@ namespace GameHooks
|
||||
s_forcedBillboardPage = prevPage;
|
||||
}
|
||||
|
||||
void __fastcall Hooked_ItemRendererRenderGuiItem(void* thisPtr, void* fontPtr, void* texturesPtr, void* itemInstanceSharedPtr, float x, float y, float scaleX, float scaleY, float alpha, bool useCompiled)
|
||||
{
|
||||
if (!Original_ItemRendererRenderGuiItem)
|
||||
return;
|
||||
|
||||
int itemId = -1;
|
||||
void* itemInstancePtr = nullptr;
|
||||
bool hasItemId = TryGetItemIdFromItemInstanceShared(itemInstanceSharedPtr, itemId, &itemInstancePtr);
|
||||
if (!hasItemId)
|
||||
{
|
||||
Original_ItemRendererRenderGuiItem(thisPtr, fontPtr, texturesPtr, itemInstanceSharedPtr, x, y, scaleX, scaleY, alpha, useCompiled);
|
||||
return;
|
||||
}
|
||||
|
||||
ItemDisplayTransformNative transform{};
|
||||
bool hasTransform = TryGetDisplayTransformWithFallback(itemId, ItemDisplay_Gui, transform);
|
||||
bool hasCustom = ItemRenderRegistry::GetCustomRenderer(itemId) != nullptr;
|
||||
|
||||
if (!hasTransform && !hasCustom)
|
||||
{
|
||||
Original_ItemRendererRenderGuiItem(thisPtr, fontPtr, texturesPtr, itemInstanceSharedPtr, x, y, scaleX, scaleY, alpha, useCompiled);
|
||||
return;
|
||||
}
|
||||
|
||||
int prevMode = BeginModelViewTransform();
|
||||
glPushMatrix();
|
||||
glTranslatef(x, y, 0.0f);
|
||||
glScalef(scaleX, scaleY, 1.0f);
|
||||
|
||||
if (hasTransform)
|
||||
ApplyItemDisplayTransform(transform, ItemDisplay_Gui);
|
||||
|
||||
bool handled = hasCustom
|
||||
? TryRenderCustomItem(itemId, ItemDisplay_Gui, thisPtr, itemInstancePtr, x, y, scaleX, scaleY, alpha)
|
||||
: false;
|
||||
|
||||
if (!handled)
|
||||
Original_ItemRendererRenderGuiItem(thisPtr, fontPtr, texturesPtr, itemInstanceSharedPtr, 0.0f, 0.0f, 1.0f, 1.0f, alpha, useCompiled);
|
||||
|
||||
glPopMatrix();
|
||||
EndModelViewTransform(prevMode);
|
||||
}
|
||||
|
||||
void __fastcall Hooked_ItemInHandRendererRender(void* thisPtr, float a)
|
||||
{
|
||||
s_firstPersonRenderDepth++;
|
||||
if (Original_ItemInHandRendererRender)
|
||||
Original_ItemInHandRendererRender(thisPtr, a);
|
||||
s_firstPersonRenderDepth--;
|
||||
}
|
||||
|
||||
void __fastcall Hooked_ItemInHandRendererRenderItem(void* thisPtr, void* entitySharedPtr, void* itemInstanceSharedPtr, int layer, bool setColor)
|
||||
{
|
||||
if (!Original_ItemInHandRendererRenderItem)
|
||||
return;
|
||||
|
||||
int itemId = -1;
|
||||
void* itemInstancePtr = nullptr;
|
||||
bool hasItemId = TryGetItemIdFromItemInstanceShared(itemInstanceSharedPtr, itemId, &itemInstancePtr);
|
||||
if (!hasItemId)
|
||||
{
|
||||
Original_ItemInHandRendererRenderItem(thisPtr, entitySharedPtr, itemInstanceSharedPtr, layer, setColor);
|
||||
return;
|
||||
}
|
||||
|
||||
int context = s_firstPersonRenderDepth > 0 ? ItemDisplay_FirstPersonRightHand : ItemDisplay_ThirdPersonRightHand;
|
||||
ItemDisplayTransformNative transform{};
|
||||
bool hasTransform = TryGetDisplayTransformWithFallback(itemId, context, transform);
|
||||
bool hasCustom = ItemRenderRegistry::GetCustomRenderer(itemId) != nullptr;
|
||||
|
||||
if (hasTransform)
|
||||
{
|
||||
int& count = s_itemRenderLogCount[itemId];
|
||||
if (count < 5)
|
||||
{
|
||||
LogUtil::Log("[WeaveLoader] ItemRender: item=%d ctx=%d rot=(%.2f,%.2f,%.2f) trans=(%.2f,%.2f,%.2f) scale=(%.2f,%.2f,%.2f)",
|
||||
itemId,
|
||||
context,
|
||||
transform.rotX, transform.rotY, transform.rotZ,
|
||||
transform.transX, transform.transY, transform.transZ,
|
||||
transform.scaleX, transform.scaleY, transform.scaleZ);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasTransform && !hasCustom)
|
||||
{
|
||||
Original_ItemInHandRendererRenderItem(thisPtr, entitySharedPtr, itemInstanceSharedPtr, layer, setColor);
|
||||
return;
|
||||
}
|
||||
|
||||
int prevMode = BeginModelViewTransform();
|
||||
glPushMatrix();
|
||||
if (hasTransform)
|
||||
ApplyItemDisplayTransform(transform, context);
|
||||
|
||||
bool handled = hasCustom
|
||||
? TryRenderCustomItem(itemId, context, thisPtr, itemInstancePtr, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f)
|
||||
: false;
|
||||
|
||||
if (!handled)
|
||||
Original_ItemInHandRendererRenderItem(thisPtr, entitySharedPtr, itemInstanceSharedPtr, layer, setColor);
|
||||
|
||||
glPopMatrix();
|
||||
EndModelViewTransform(prevMode);
|
||||
}
|
||||
|
||||
static void LogAnimatedTextureGuard(const char* what, void* thisPtr)
|
||||
{
|
||||
if (s_animatedTextureGuardLogCount >= 12)
|
||||
|
||||
@@ -22,6 +22,10 @@ typedef void* (__fastcall *RegisterIcon_fn)(void* thisPtr, const std::wstring& n
|
||||
typedef void* (__fastcall *ItemInstanceGetIcon_fn)(void* thisPtr);
|
||||
typedef void (__fastcall *EntityRendererBindTextureResource_fn)(void* thisPtr, void* resourcePtr);
|
||||
typedef void (__fastcall *ItemRendererRenderItemBillboard_fn)(void* thisPtr, void* entitySharedPtr, void* iconPtr, int count, float a, float red, float green, float blue);
|
||||
typedef void (__fastcall *ItemRendererRenderGuiItem_fn)(void* thisPtr, void* fontPtr, void* texturesPtr, void* itemInstanceSharedPtr, float x, float y, float scaleX, float scaleY, float alpha, bool useCompiled);
|
||||
typedef void (__fastcall *ItemInHandRendererRender_fn)(void* thisPtr, float a);
|
||||
typedef void (__fastcall *ItemInHandRendererRenderItem_fn)(void* thisPtr, void* entitySharedPtr, void* itemInstanceSharedPtr, int layer, bool setColor);
|
||||
typedef void (__fastcall *ItemEntityGetItem_fn)(void* outSharedPtr, void* thisPtr);
|
||||
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);
|
||||
@@ -113,6 +117,9 @@ namespace GameHooks
|
||||
extern ItemInstanceGetIcon_fn Original_ItemInstanceGetIcon;
|
||||
extern EntityRendererBindTextureResource_fn Original_EntityRendererBindTextureResource;
|
||||
extern ItemRendererRenderItemBillboard_fn Original_ItemRendererRenderItemBillboard;
|
||||
extern ItemRendererRenderGuiItem_fn Original_ItemRendererRenderGuiItem;
|
||||
extern ItemInHandRendererRender_fn Original_ItemInHandRendererRender;
|
||||
extern ItemInHandRendererRenderItem_fn Original_ItemInHandRendererRenderItem;
|
||||
extern AnimatedTextureCycleFrames_fn Original_CompassTextureCycleFrames;
|
||||
extern AnimatedTextureCycleFrames_fn Original_ClockTextureCycleFrames;
|
||||
extern TextureGetSourceDim_fn Original_CompassTextureGetSourceWidth;
|
||||
@@ -209,6 +216,9 @@ namespace GameHooks
|
||||
void* __fastcall Hooked_ItemInstanceGetIcon(void* thisPtr);
|
||||
void __fastcall Hooked_EntityRendererBindTextureResource(void* thisPtr, void* resourcePtr);
|
||||
void __fastcall Hooked_ItemRendererRenderItemBillboard(void* thisPtr, void* entitySharedPtr, void* iconPtr, int count, float a, float red, float green, float blue);
|
||||
void __fastcall Hooked_ItemRendererRenderGuiItem(void* thisPtr, void* fontPtr, void* texturesPtr, void* itemInstanceSharedPtr, float x, float y, float scaleX, float scaleY, float alpha, bool useCompiled);
|
||||
void __fastcall Hooked_ItemInHandRendererRender(void* thisPtr, float a);
|
||||
void __fastcall Hooked_ItemInHandRendererRenderItem(void* thisPtr, void* entitySharedPtr, void* itemInstanceSharedPtr, int layer, bool setColor);
|
||||
void __fastcall Hooked_CompassTextureCycleFrames(void* thisPtr);
|
||||
void __fastcall Hooked_ClockTextureCycleFrames(void* thisPtr);
|
||||
int __fastcall Hooked_CompassTextureGetSourceWidth(void* thisPtr);
|
||||
@@ -285,6 +295,7 @@ namespace GameHooks
|
||||
void* entityIoNewById,
|
||||
void* entityMoveTo,
|
||||
void* entitySetPos);
|
||||
void SetItemRenderSymbols(void* itemEntityGetItem);
|
||||
void SetUseActionSymbols(void* inventoryRemoveResource,
|
||||
void* inventoryVtable,
|
||||
void* itemInstanceHurtAndBreak,
|
||||
|
||||
@@ -226,6 +226,48 @@ bool HookManager::Install(const SymbolResolver& symbols)
|
||||
}
|
||||
}
|
||||
|
||||
if (symbols.Item.pItemRendererRenderGuiItem)
|
||||
{
|
||||
if (MH_CreateHook(symbols.Item.pItemRendererRenderGuiItem,
|
||||
reinterpret_cast<void*>(&GameHooks::Hooked_ItemRendererRenderGuiItem),
|
||||
reinterpret_cast<void**>(&GameHooks::Original_ItemRendererRenderGuiItem)) != MH_OK)
|
||||
{
|
||||
LogUtil::Log("[WeaveLoader] Warning: Failed to hook ItemRenderer::renderGuiItem");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogUtil::Log("[WeaveLoader] Hooked ItemRenderer::renderGuiItem (item display transforms)");
|
||||
}
|
||||
}
|
||||
|
||||
if (symbols.Item.pItemInHandRendererRender)
|
||||
{
|
||||
if (MH_CreateHook(symbols.Item.pItemInHandRendererRender,
|
||||
reinterpret_cast<void*>(&GameHooks::Hooked_ItemInHandRendererRender),
|
||||
reinterpret_cast<void**>(&GameHooks::Original_ItemInHandRendererRender)) != MH_OK)
|
||||
{
|
||||
LogUtil::Log("[WeaveLoader] Warning: Failed to hook ItemInHandRenderer::render");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogUtil::Log("[WeaveLoader] Hooked ItemInHandRenderer::render (first-person context)");
|
||||
}
|
||||
}
|
||||
|
||||
if (symbols.Item.pItemInHandRendererRenderItem)
|
||||
{
|
||||
if (MH_CreateHook(symbols.Item.pItemInHandRendererRenderItem,
|
||||
reinterpret_cast<void*>(&GameHooks::Hooked_ItemInHandRendererRenderItem),
|
||||
reinterpret_cast<void**>(&GameHooks::Original_ItemInHandRendererRenderItem)) != MH_OK)
|
||||
{
|
||||
LogUtil::Log("[WeaveLoader] Warning: Failed to hook ItemInHandRenderer::renderItem");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogUtil::Log("[WeaveLoader] Hooked ItemInHandRenderer::renderItem (item display transforms)");
|
||||
}
|
||||
}
|
||||
|
||||
if (symbols.Texture.pCompassTextureCycleFrames)
|
||||
{
|
||||
if (MH_CreateHook(symbols.Texture.pCompassTextureCycleFrames,
|
||||
@@ -1050,6 +1092,7 @@ bool HookManager::Install(const SymbolResolver& symbols)
|
||||
symbols.Entity.pEntityIONewById,
|
||||
symbols.Entity.pEntityMoveTo,
|
||||
symbols.Entity.pEntitySetPos);
|
||||
GameHooks::SetItemRenderSymbols(symbols.Item.pItemEntityGetItem);
|
||||
GameHooks::SetUseActionSymbols(
|
||||
symbols.Inventory.pInventoryRemoveResource,
|
||||
symbols.Inventory.pInventoryVtable,
|
||||
|
||||
69
WeaveLoaderRuntime/src/ItemRenderRegistry.cpp
Normal file
69
WeaveLoaderRuntime/src/ItemRenderRegistry.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#include "ItemRenderRegistry.h"
|
||||
#include "LogUtil.h"
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace
|
||||
{
|
||||
struct ItemRenderEntry
|
||||
{
|
||||
std::array<ItemDisplayTransformNative, ItemDisplay_ContextCount> transforms{};
|
||||
std::array<uint8_t, ItemDisplay_ContextCount> hasTransform{};
|
||||
ManagedItemRenderFn renderer = nullptr;
|
||||
};
|
||||
|
||||
std::unordered_map<int, ItemRenderEntry> g_entries;
|
||||
std::mutex g_mutex;
|
||||
}
|
||||
|
||||
void ItemRenderRegistry::RegisterDisplayTransform(int itemId, int context, const ItemDisplayTransformNative& transform)
|
||||
{
|
||||
if (itemId < 0)
|
||||
return;
|
||||
if (context < 0 || context >= ItemDisplay_ContextCount)
|
||||
return;
|
||||
|
||||
std::lock_guard<std::mutex> guard(g_mutex);
|
||||
ItemRenderEntry& entry = g_entries[itemId];
|
||||
entry.transforms[context] = transform;
|
||||
entry.hasTransform[context] = 1;
|
||||
LogUtil::Log("[WeaveLoader] ItemRenderRegistry: display transform %d registered for item %d", context, itemId);
|
||||
}
|
||||
|
||||
bool ItemRenderRegistry::TryGetDisplayTransform(int itemId, int context, ItemDisplayTransformNative& outTransform)
|
||||
{
|
||||
if (itemId < 0)
|
||||
return false;
|
||||
if (context < 0 || context >= ItemDisplay_ContextCount)
|
||||
return false;
|
||||
|
||||
std::lock_guard<std::mutex> guard(g_mutex);
|
||||
auto it = g_entries.find(itemId);
|
||||
if (it == g_entries.end())
|
||||
return false;
|
||||
if (!it->second.hasTransform[context])
|
||||
return false;
|
||||
outTransform = it->second.transforms[context];
|
||||
return true;
|
||||
}
|
||||
|
||||
void ItemRenderRegistry::RegisterCustomRenderer(int itemId, ManagedItemRenderFn fn)
|
||||
{
|
||||
if (itemId < 0)
|
||||
return;
|
||||
std::lock_guard<std::mutex> guard(g_mutex);
|
||||
g_entries[itemId].renderer = fn;
|
||||
LogUtil::Log("[WeaveLoader] ItemRenderRegistry: custom renderer registered for item %d", itemId);
|
||||
}
|
||||
|
||||
ManagedItemRenderFn ItemRenderRegistry::GetCustomRenderer(int itemId)
|
||||
{
|
||||
if (itemId < 0)
|
||||
return nullptr;
|
||||
std::lock_guard<std::mutex> guard(g_mutex);
|
||||
auto it = g_entries.find(itemId);
|
||||
if (it == g_entries.end())
|
||||
return nullptr;
|
||||
return it->second.renderer;
|
||||
}
|
||||
39
WeaveLoaderRuntime/src/ItemRenderRegistry.h
Normal file
39
WeaveLoaderRuntime/src/ItemRenderRegistry.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
struct ItemDisplayTransformNative
|
||||
{
|
||||
float rotX;
|
||||
float rotY;
|
||||
float rotZ;
|
||||
float transX;
|
||||
float transY;
|
||||
float transZ;
|
||||
float scaleX;
|
||||
float scaleY;
|
||||
float scaleZ;
|
||||
};
|
||||
|
||||
enum ItemDisplayContextNative : int
|
||||
{
|
||||
ItemDisplay_Gui = 0,
|
||||
ItemDisplay_Ground = 1,
|
||||
ItemDisplay_Fixed = 2,
|
||||
ItemDisplay_Head = 3,
|
||||
ItemDisplay_FirstPersonRightHand = 4,
|
||||
ItemDisplay_FirstPersonLeftHand = 5,
|
||||
ItemDisplay_ThirdPersonRightHand = 6,
|
||||
ItemDisplay_ThirdPersonLeftHand = 7,
|
||||
ItemDisplay_ContextCount = 8
|
||||
};
|
||||
|
||||
using ManagedItemRenderFn = int(__cdecl *)(const void* args, int sizeBytes);
|
||||
|
||||
namespace ItemRenderRegistry
|
||||
{
|
||||
void RegisterDisplayTransform(int itemId, int context, const ItemDisplayTransformNative& transform);
|
||||
bool TryGetDisplayTransform(int itemId, int context, ItemDisplayTransformNative& outTransform);
|
||||
void RegisterCustomRenderer(int itemId, ManagedItemRenderFn fn);
|
||||
ManagedItemRenderFn GetCustomRenderer(int itemId);
|
||||
}
|
||||
@@ -367,6 +367,30 @@ int native_register_item(
|
||||
return id;
|
||||
}
|
||||
|
||||
void native_register_item_display_transform(int numericItemId, int context, ItemDisplayTransformNative transform)
|
||||
{
|
||||
ItemRenderRegistry::RegisterDisplayTransform(numericItemId, context, transform);
|
||||
}
|
||||
|
||||
void native_register_item_renderer(int numericItemId, void* rendererFn)
|
||||
{
|
||||
ItemRenderRegistry::RegisterCustomRenderer(numericItemId, reinterpret_cast<ManagedItemRenderFn>(rendererFn));
|
||||
}
|
||||
|
||||
void native_set_item_hand_equipped(int numericItemId, int isHandEquipped)
|
||||
{
|
||||
void* itemPtr = GameObjectFactory::FindItem(numericItemId);
|
||||
if (!itemPtr)
|
||||
{
|
||||
LogUtil::Log("[WeaveLoader] HandEquipped: item %d not found", numericItemId);
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr ptrdiff_t kHandEquippedOffset = 0x40;
|
||||
*reinterpret_cast<unsigned char*>(static_cast<char*>(itemPtr) + kHandEquippedOffset) = (isHandEquipped != 0) ? 1 : 0;
|
||||
LogUtil::Log("[WeaveLoader] HandEquipped: item %d -> %d", numericItemId, isHandEquipped != 0);
|
||||
}
|
||||
|
||||
int native_register_pickaxe_item(
|
||||
const char* namespacedId,
|
||||
int tier,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include "ModelRegistry.h"
|
||||
#include "ItemRenderRegistry.h"
|
||||
|
||||
namespace NativeExports
|
||||
{
|
||||
@@ -111,6 +112,16 @@ extern "C"
|
||||
int maxDamage,
|
||||
const char* iconName,
|
||||
const char* displayName);
|
||||
__declspec(dllexport) void native_register_item_display_transform(
|
||||
int numericItemId,
|
||||
int context,
|
||||
ItemDisplayTransformNative transform);
|
||||
__declspec(dllexport) void native_register_item_renderer(
|
||||
int numericItemId,
|
||||
void* rendererFn);
|
||||
__declspec(dllexport) void native_set_item_hand_equipped(
|
||||
int numericItemId,
|
||||
int isHandEquipped);
|
||||
|
||||
__declspec(dllexport) int native_register_pickaxe_item(
|
||||
const char* namespacedId,
|
||||
|
||||
@@ -73,6 +73,10 @@ namespace
|
||||
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_ITEMENTITY_GETITEM = "?getItem@ItemEntity@@QEAA?AV?$shared_ptr@VItemInstance@@@std@@XZ";
|
||||
static const char* SYM_ITEMRENDERER_RENDERGUIITEM = "?renderGuiItem@ItemRenderer@@QEAAXPEAVFont@@PEAVTextures@@V?$shared_ptr@VItemInstance@@@std@@MMMMM_N@Z";
|
||||
static const char* SYM_ITEMINHANDRENDERER_RENDER = "?render@ItemInHandRenderer@@QEAAXM@Z";
|
||||
static const char* SYM_ITEMINHANDRENDERER_RENDERITEM = "?renderItem@ItemInHandRenderer@@QEAAXV?$shared_ptr@VLivingEntity@@@std@@V?$shared_ptr@VItemInstance@@@3@H_N@Z";
|
||||
|
||||
static const char* SYM_TILE_ONPLACE = "?onPlace@Tile@@UEAAXPEAVLevel@@HHH@Z";
|
||||
static const char* SYM_TILE_NEIGHBORCHANGED = "?neighborChanged@Tile@@UEAAXPEAVLevel@@HHHH@Z";
|
||||
@@ -313,6 +317,10 @@ bool ItemSymbols::Resolve(SymbolResolver& resolver)
|
||||
pPickaxeItemCanDestroySpecial = resolver.Resolve(SYM_PICKAXEITEM_CANDESTROYSPECIAL);
|
||||
pShovelItemGetDestroySpeed = resolver.Resolve(SYM_SHOVELITEM_GETDESTROYSPEED);
|
||||
pShovelItemCanDestroySpecial = resolver.Resolve(SYM_SHOVELITEM_CANDESTROYSPECIAL);
|
||||
pItemEntityGetItem = resolver.Resolve(SYM_ITEMENTITY_GETITEM);
|
||||
pItemRendererRenderGuiItem = resolver.Resolve(SYM_ITEMRENDERER_RENDERGUIITEM);
|
||||
pItemInHandRendererRender = resolver.Resolve(SYM_ITEMINHANDRENDERER_RENDER);
|
||||
pItemInHandRendererRenderItem = resolver.Resolve(SYM_ITEMINHANDRENDERER_RENDERITEM);
|
||||
if (!pShovelItemGetDestroySpeed)
|
||||
pShovelItemGetDestroySpeed = resolver.ResolveExact("DiggerItem::getDestroySpeed");
|
||||
return true;
|
||||
@@ -332,6 +340,10 @@ void ItemSymbols::Log() const
|
||||
LogSym("PickaxeItem::canDestroySpecial", pPickaxeItemCanDestroySpecial);
|
||||
LogSym("ShovelItem::getDestroySpeed", pShovelItemGetDestroySpeed);
|
||||
LogSym("ShovelItem::canDestroySpecial", pShovelItemCanDestroySpecial);
|
||||
LogSym("ItemEntity::getItem", pItemEntityGetItem);
|
||||
LogSym("ItemRenderer::renderGuiItem", pItemRendererRenderGuiItem);
|
||||
LogSym("ItemInHandRenderer::render", pItemInHandRendererRender);
|
||||
LogSym("ItemInHandRenderer::renderItem", pItemInHandRendererRenderItem);
|
||||
}
|
||||
|
||||
bool TileSymbols::Resolve(SymbolResolver& resolver)
|
||||
|
||||
@@ -87,6 +87,10 @@ struct ItemSymbols
|
||||
void* pPickaxeItemCanDestroySpecial = nullptr;
|
||||
void* pShovelItemGetDestroySpeed = nullptr;
|
||||
void* pShovelItemCanDestroySpecial = nullptr;
|
||||
void* pItemEntityGetItem = nullptr;
|
||||
void* pItemRendererRenderGuiItem = nullptr;
|
||||
void* pItemInHandRendererRender = nullptr;
|
||||
void* pItemInHandRendererRenderItem = nullptr;
|
||||
|
||||
bool Resolve(SymbolResolver& resolver);
|
||||
void Log() const;
|
||||
|
||||
Reference in New Issue
Block a user