From 788b7167a2aa32e1253a4d57e0aff7993576ad70 Mon Sep 17 00:00:00 2001 From: Jacobwasbeast Date: Wed, 11 Mar 2026 15:12:35 -0500 Subject: [PATCH] feat(models): add block model boxes + picking --- ExampleMod/ExampleMod.cs | 11 + ExampleMod/assets/README.md | 11 + ExampleMod/assets/examplemod/lang/en-GB.lang | 1 + .../examplemod/models/block/ruby_chair.json | 13 + WeaveLoader.API/Assets/ModelBox.cs | 14 + WeaveLoader.API/Assets/ModelResolver.cs | 311 +++++++++ WeaveLoader.API/Block/BlockProperties.cs | 13 +- WeaveLoader.API/Block/BlockRegistry.cs | 40 ++ WeaveLoader.API/Item/ItemProperties.cs | 7 + WeaveLoader.API/Item/ItemRegistry.cs | 1 + WeaveLoader.API/ModContext.cs | 7 + WeaveLoader.API/NativeInterop.cs | 6 + WeaveLoader.Core/ModManager.cs | 19 +- WeaveLoaderRuntime/CMakeLists.txt | 1 + WeaveLoaderRuntime/src/GameHooks.cpp | 638 ++++++++++++++++++ WeaveLoaderRuntime/src/GameHooks.h | 42 ++ WeaveLoaderRuntime/src/HookManager.cpp | 134 ++++ WeaveLoaderRuntime/src/LogUtil.cpp | 9 +- WeaveLoaderRuntime/src/LogUtil.h | 3 + WeaveLoaderRuntime/src/ModelRegistry.cpp | 38 ++ WeaveLoaderRuntime/src/ModelRegistry.h | 19 + WeaveLoaderRuntime/src/NativeExports.cpp | 5 + WeaveLoaderRuntime/src/NativeExports.h | 6 + .../src/Symbols/SymbolGroups.cpp | 56 ++ WeaveLoaderRuntime/src/Symbols/SymbolGroups.h | 17 + docs/MODDING.md | 11 + 26 files changed, 1429 insertions(+), 4 deletions(-) create mode 100644 ExampleMod/assets/examplemod/models/block/ruby_chair.json create mode 100644 WeaveLoader.API/Assets/ModelBox.cs create mode 100644 WeaveLoader.API/Assets/ModelResolver.cs create mode 100644 WeaveLoader.API/ModContext.cs create mode 100644 WeaveLoaderRuntime/src/ModelRegistry.cpp create mode 100644 WeaveLoaderRuntime/src/ModelRegistry.h diff --git a/ExampleMod/ExampleMod.cs b/ExampleMod/ExampleMod.cs index 3fb0c06..7d98164 100644 --- a/ExampleMod/ExampleMod.cs +++ b/ExampleMod/ExampleMod.cs @@ -14,6 +14,7 @@ public class ExampleMod : IMod public static RegisteredBlock? RubyOre; public static RegisteredBlock? RubyStone; public static RegisteredBlock? RubyWoodPlanks; + public static RegisteredBlock? RubyChair; public static RegisteredBlock? RubySand; public static RegisteredSlabBlock? RubyStoneSlab; public static RegisteredSlabBlock? RubyWoodSlab; @@ -236,6 +237,16 @@ public class ExampleMod : IMod .Name(Text.Translatable("block.examplemod.ruby_wood_planks")) .InCreativeTab(CreativeTab.BuildingBlocks)); + RubyChair = Registry.Block.Register("examplemod:ruby_chair", + new BlockProperties() + .Material(MaterialType.Wood) + .Hardness(1.5f) + .Resistance(5f) + .Sound(SoundType.Wood) + .Model("examplemod:block/ruby_chair") + .Name(Text.Translatable("block.examplemod.ruby_chair")) + .InCreativeTab(CreativeTab.Decoration)); + RubySand = Registry.Block.Register("examplemod:ruby_sand", new RubySandBlock(), new BlockProperties() diff --git a/ExampleMod/assets/README.md b/ExampleMod/assets/README.md index 183506b..f366569 100644 --- a/ExampleMod/assets/README.md +++ b/ExampleMod/assets/README.md @@ -39,3 +39,14 @@ Block and item models are supported using Java-style JSON assets: - **Entities (future):** `assets/examplemod/models/entity/{name}.json` The `examplemod` namespace should match your mod ID (lowercase). + +To drive an icon from a model JSON, use: + +```csharp +.Model("examplemod:block/ruby_ore") +.Model("examplemod:item/ruby") +``` + +WeaveLoader reads the model JSON and uses its texture for the icon. + +For block items, WeaveLoader uses the block model by default. Item models are optional. diff --git a/ExampleMod/assets/examplemod/lang/en-GB.lang b/ExampleMod/assets/examplemod/lang/en-GB.lang index ae08fc8..cfdc972 100644 --- a/ExampleMod/assets/examplemod/lang/en-GB.lang +++ b/ExampleMod/assets/examplemod/lang/en-GB.lang @@ -3,6 +3,7 @@ block.examplemod.ruby_ore=Ruby Ore block.examplemod.ruby_stone=Ruby Stone block.examplemod.ruby_wood_planks=Ruby Wood Planks +block.examplemod.ruby_chair=Ruby Chair block.examplemod.ruby_stone_slab=Ruby Stone Slab block.examplemod.ruby_stone_slab_double=Ruby Stone Slab block.examplemod.ruby_wood_slab=Ruby Wood Slab diff --git a/ExampleMod/assets/examplemod/models/block/ruby_chair.json b/ExampleMod/assets/examplemod/models/block/ruby_chair.json new file mode 100644 index 0000000..f6d4cff --- /dev/null +++ b/ExampleMod/assets/examplemod/models/block/ruby_chair.json @@ -0,0 +1,13 @@ +{ + "textures": { + "all": "examplemod:block/ruby_wood_planks" + }, + "elements": [ + { "from": [2, 8, 2], "to": [14, 10, 14], "faces": { "north": { "texture": "#all" }, "south": { "texture": "#all" }, "west": { "texture": "#all" }, "east": { "texture": "#all" }, "up": { "texture": "#all" }, "down": { "texture": "#all" } } }, + { "from": [2, 10, 12], "to": [14, 22, 14], "faces": { "north": { "texture": "#all" }, "south": { "texture": "#all" }, "west": { "texture": "#all" }, "east": { "texture": "#all" }, "up": { "texture": "#all" }, "down": { "texture": "#all" } } }, + { "from": [2, 0, 2], "to": [4, 8, 4], "faces": { "north": { "texture": "#all" }, "south": { "texture": "#all" }, "west": { "texture": "#all" }, "east": { "texture": "#all" }, "up": { "texture": "#all" }, "down": { "texture": "#all" } } }, + { "from": [12, 0, 2], "to": [14, 8, 4], "faces": { "north": { "texture": "#all" }, "south": { "texture": "#all" }, "west": { "texture": "#all" }, "east": { "texture": "#all" }, "up": { "texture": "#all" }, "down": { "texture": "#all" } } }, + { "from": [2, 0, 12], "to": [4, 8, 14], "faces": { "north": { "texture": "#all" }, "south": { "texture": "#all" }, "west": { "texture": "#all" }, "east": { "texture": "#all" }, "up": { "texture": "#all" }, "down": { "texture": "#all" } } }, + { "from": [12, 0, 12], "to": [14, 8, 14], "faces": { "north": { "texture": "#all" }, "south": { "texture": "#all" }, "west": { "texture": "#all" }, "east": { "texture": "#all" }, "up": { "texture": "#all" }, "down": { "texture": "#all" } } } + ] +} diff --git a/WeaveLoader.API/Assets/ModelBox.cs b/WeaveLoader.API/Assets/ModelBox.cs new file mode 100644 index 0000000..673979b --- /dev/null +++ b/WeaveLoader.API/Assets/ModelBox.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace WeaveLoader.API.Assets; + +[StructLayout(LayoutKind.Sequential)] +internal struct ModelBox +{ + public float X0; + public float Y0; + public float Z0; + public float X1; + public float Y1; + public float Z1; +} diff --git a/WeaveLoader.API/Assets/ModelResolver.cs b/WeaveLoader.API/Assets/ModelResolver.cs new file mode 100644 index 0000000..4b63fb0 --- /dev/null +++ b/WeaveLoader.API/Assets/ModelResolver.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; + +namespace WeaveLoader.API.Assets; + +internal static class ModelResolver +{ + private sealed class ModelData + { + public string IconName = ""; + public List Boxes = new(); + } + + private enum ModelKind + { + Block, + Item + } + + internal static void ApplyBlockModel(Identifier id, Block.BlockProperties properties) + { + if (!ShouldResolveModel(properties.ModelValue, properties.IconValue, ModelKind.Block)) + return; + + if (TryLoadModel(id, properties.ModelValue, ModelKind.Block, out ModelData? model)) + { + if (!string.IsNullOrWhiteSpace(model.IconName)) + { + properties.IconValue = model.IconName; + Logger.Debug($"Model resolved for block '{id}' -> icon '{model.IconName}'"); + } + if (model.Boxes.Count > 0) + { + properties.ModelBoxes = model.Boxes; + properties.ModelIsFullCube = IsFullCube(model.Boxes); + } + } + else if (!string.IsNullOrWhiteSpace(properties.ModelValue)) + { + Logger.Warning($"Model not found for block '{id}' (model '{properties.ModelValue}')"); + } + } + + internal static void ApplyItemModel(Identifier id, Item.ItemProperties properties) + { + if (!ShouldResolveModel(properties.ModelValue, properties.IconValue, ModelKind.Item)) + return; + + if (TryLoadModel(id, properties.ModelValue, ModelKind.Item, out ModelData? model)) + { + if (!string.IsNullOrWhiteSpace(model.IconName)) + { + properties.IconValue = model.IconName; + Logger.Debug($"Model resolved for item '{id}' -> icon '{model.IconName}'"); + } + } + else if (!string.IsNullOrWhiteSpace(properties.ModelValue)) + { + Logger.Warning($"Model not found for item '{id}' (model '{properties.ModelValue}')"); + } + } + + private static bool ShouldResolveModel(string? modelValue, string iconValue, ModelKind kind) + { + if (!string.IsNullOrWhiteSpace(modelValue)) + return true; + + // Only auto-resolve if icon was not provided. + if (kind == ModelKind.Item && string.IsNullOrWhiteSpace(iconValue)) + return true; + + return false; + } + + private static bool TryLoadModel(Identifier id, string? modelValue, ModelKind kind, out ModelData? model) + { + model = null; + if (string.IsNullOrWhiteSpace(ModContext.ModFolder)) + return false; + + string modelPath; + string modelNamespace; + ModelKind effectiveKind = kind; + + if (kind == ModelKind.Item) + { + if (TryGetModelFilePath(id, modelValue, ModelKind.Block, out string blockModelPath, out string blockNamespace) && + File.Exists(blockModelPath)) + { + modelPath = blockModelPath; + modelNamespace = blockNamespace; + effectiveKind = ModelKind.Block; + } + else if (TryGetModelFilePath(id, modelValue, ModelKind.Item, out string itemModelPath, out string itemNamespace) && + File.Exists(itemModelPath)) + { + modelPath = itemModelPath; + modelNamespace = itemNamespace; + } + else + { + return false; + } + } + else + { + if (!TryGetModelFilePath(id, modelValue, kind, out modelPath, out modelNamespace)) + return false; + if (!File.Exists(modelPath)) + return false; + } + + if (!TryParseTextures(modelPath, out Dictionary textures)) + return false; + + var data = new ModelData(); + + string? texture = SelectTexture(textures, effectiveKind); + if (!string.IsNullOrWhiteSpace(texture)) + { + texture = ResolveTextureReference(texture, textures); + if (!string.IsNullOrWhiteSpace(texture)) + data.IconName = NormalizeTextureName(texture, modelNamespace); + } + + TryParseElements(modelPath, data.Boxes); + model = data; + return !string.IsNullOrWhiteSpace(data.IconName) || data.Boxes.Count > 0; + } + + private static bool TryGetModelFilePath(Identifier id, string? modelValue, ModelKind kind, out string modelPath, out string modelNamespace) + { + modelPath = ""; + modelNamespace = ""; + + string ns = id.Namespace; + string rel = id.Path; + + if (!string.IsNullOrWhiteSpace(modelValue)) + { + string raw = modelValue!; + int colon = raw.IndexOf(':'); + if (colon >= 0) + { + ns = raw[..colon]; + rel = raw[(colon + 1)..]; + } + else + { + rel = raw; + } + } + + rel = rel.Replace('\\', '/'); + if (rel.StartsWith("models/", StringComparison.OrdinalIgnoreCase)) + rel = rel["models/".Length..]; + if (!rel.StartsWith("block/", StringComparison.OrdinalIgnoreCase) && + !rel.StartsWith("item/", StringComparison.OrdinalIgnoreCase)) + { + rel = (kind == ModelKind.Block ? "block/" : "item/") + rel; + } + + string modRoot = ModContext.ModFolder ?? ""; + if (string.IsNullOrWhiteSpace(modRoot)) + return false; + + string file = Path.Combine(modRoot, "assets", ns, "models", rel.Replace('/', Path.DirectorySeparatorChar) + ".json"); + modelPath = file; + modelNamespace = ns; + return true; + } + + private static bool TryParseTextures(string modelPath, out Dictionary textures) + { + textures = new Dictionary(StringComparer.OrdinalIgnoreCase); + try + { + using var doc = JsonDocument.Parse(File.ReadAllText(modelPath)); + if (!doc.RootElement.TryGetProperty("textures", out JsonElement texElem)) + return false; + if (texElem.ValueKind != JsonValueKind.Object) + return false; + + foreach (var prop in texElem.EnumerateObject()) + { + if (prop.Value.ValueKind != JsonValueKind.String) + continue; + string? value = prop.Value.GetString(); + if (string.IsNullOrWhiteSpace(value)) + continue; + textures[prop.Name] = value!; + } + return textures.Count > 0; + } + catch (Exception ex) + { + Logger.Warning($"Failed to parse model JSON '{modelPath}': {ex.Message}"); + return false; + } + } + + private static void TryParseElements(string modelPath, List boxes) + { + try + { + using var doc = JsonDocument.Parse(File.ReadAllText(modelPath)); + if (!doc.RootElement.TryGetProperty("elements", out JsonElement elements) || + elements.ValueKind != JsonValueKind.Array) + return; + + foreach (var elem in elements.EnumerateArray()) + { + if (!elem.TryGetProperty("from", out JsonElement fromEl) || + !elem.TryGetProperty("to", out JsonElement toEl)) + continue; + + if (fromEl.ValueKind != JsonValueKind.Array || toEl.ValueKind != JsonValueKind.Array) + continue; + + if (fromEl.GetArrayLength() < 3 || toEl.GetArrayLength() < 3) + continue; + + float fx0 = (float)fromEl[0].GetDouble(); + float fy0 = (float)fromEl[1].GetDouble(); + float fz0 = (float)fromEl[2].GetDouble(); + float fx1 = (float)toEl[0].GetDouble(); + float fy1 = (float)toEl[1].GetDouble(); + float fz1 = (float)toEl[2].GetDouble(); + + float x0 = MathF.Min(fx0, fx1) / 16.0f; + float y0 = MathF.Min(fy0, fy1) / 16.0f; + float z0 = MathF.Min(fz0, fz1) / 16.0f; + float x1 = MathF.Max(fx0, fx1) / 16.0f; + float y1 = MathF.Max(fy0, fy1) / 16.0f; + float z1 = MathF.Max(fz0, fz1) / 16.0f; + + if (x1 <= x0 || y1 <= y0 || z1 <= z0) + continue; + + boxes.Add(new ModelBox + { + X0 = x0, + Y0 = y0, + Z0 = z0, + X1 = x1, + Y1 = y1, + Z1 = z1 + }); + } + } + catch (Exception ex) + { + Logger.Warning($"Failed to parse model elements in '{modelPath}': {ex.Message}"); + } + } + + private static bool IsFullCube(List boxes) + { + if (boxes.Count != 1) + return false; + ModelBox box = boxes[0]; + const float eps = 0.0001f; + return box.X0 <= 0.0f + eps && box.Y0 <= 0.0f + eps && box.Z0 <= 0.0f + eps && + box.X1 >= 1.0f - eps && box.Y1 >= 1.0f - eps && box.Z1 >= 1.0f - eps; + } + + private static string? SelectTexture(Dictionary textures, ModelKind kind) + { + string[] keys = kind == ModelKind.Item + ? new[] { "layer0", "layer1" } + : new[] { "all", "side", "top", "bottom", "particle" }; + + foreach (string key in keys) + { + if (textures.TryGetValue(key, out string value)) + return value; + } + + foreach (var kvp in textures) + return kvp.Value; + + return null; + } + + private static string ResolveTextureReference(string texture, Dictionary textures) + { + string current = texture; + for (int i = 0; i < 8; i++) + { + if (!current.StartsWith("#", StringComparison.Ordinal)) + return current; + string key = current[1..]; + if (!textures.TryGetValue(key, out string next) || string.IsNullOrWhiteSpace(next)) + return ""; + current = next; + } + return ""; + } + + private static string NormalizeTextureName(string texture, string modelNamespace) + { + string value = texture.Replace('\\', '/'); + if (value.Contains(':')) + return value; + if (string.IsNullOrWhiteSpace(modelNamespace)) + return value; + return $"{modelNamespace}:{value}"; + } +} diff --git a/WeaveLoader.API/Block/BlockProperties.cs b/WeaveLoader.API/Block/BlockProperties.cs index 5e1c7bc..2132ad8 100644 --- a/WeaveLoader.API/Block/BlockProperties.cs +++ b/WeaveLoader.API/Block/BlockProperties.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using WeaveLoader.API; namespace WeaveLoader.API.Block; @@ -61,14 +62,18 @@ public class BlockProperties internal float ResistanceValue = 5.0f; internal SoundType SoundValue = SoundType.Stone; internal string IconValue = "stone"; + internal string? ModelValue; internal float LightEmissionValue = 0.0f; internal int LightBlockValue = 255; + internal bool LightBlockExplicit; internal CreativeTab CreativeTabValue = CreativeTab.None; internal CreativePlacement? CreativePlacementValue; internal Text? NameValue; internal int RequiredHarvestLevelValue = -1; internal ToolType RequiredToolValue = ToolType.None; internal bool AcceptsRedstonePowerValue; + internal List? ModelBoxes; + internal bool ModelIsFullCube; public BlockProperties Material(MaterialType material) { MaterialValue = material; return this; } public BlockProperties Hardness(float hardness) { HardnessValue = hardness; return this; } @@ -79,8 +84,14 @@ public class BlockProperties /// from assets/examplemod/textures/block/ruby_ore.png, or vanilla names like "stone", "gold_ore". /// public BlockProperties Icon(string iconName) { IconValue = iconName; return this; } + /// + /// Optional Java-style model name (e.g. "examplemod:block/ruby_ore"). + /// When provided, WeaveLoader will read assets/<namespace>/models/block/<name>.json + /// and use its texture for the block icon. + /// + public BlockProperties Model(string modelName) { ModelValue = modelName; return this; } public BlockProperties LightLevel(float level) { LightEmissionValue = level; return this; } - public BlockProperties LightBlocking(int level) { LightBlockValue = level; return this; } + public BlockProperties LightBlocking(int level) { LightBlockValue = level; LightBlockExplicit = true; return this; } public BlockProperties Indestructible() { HardnessValue = -1.0f; ResistanceValue = 6000000f; return this; } public BlockProperties InCreativeTab(CreativeTab tab) { CreativeTabValue = tab; return this; } public BlockProperties CreativePlacement(CreativePlacement placement) { CreativePlacementValue = placement; return this; } diff --git a/WeaveLoader.API/Block/BlockRegistry.cs b/WeaveLoader.API/Block/BlockRegistry.cs index 035ec67..a2a62cb 100644 --- a/WeaveLoader.API/Block/BlockRegistry.cs +++ b/WeaveLoader.API/Block/BlockRegistry.cs @@ -50,6 +50,8 @@ public static class BlockRegistry /// A handle to the registered block. public static RegisteredBlock Register(Identifier id, BlockProperties properties) { + Assets.ModelResolver.ApplyBlockModel(id, properties); + ApplyModelLightDefaults(properties); int numericId = NativeInterop.native_register_block( id.ToString(), (int)properties.MaterialValue, @@ -70,6 +72,11 @@ public static class BlockRegistry throw new InvalidOperationException($"Failed to register block '{id}'. No free IDs or invalid parameters."); } + if (properties.ModelBoxes != null && properties.ModelBoxes.Count > 0) + { + NativeInterop.native_register_block_model(numericId, properties.ModelBoxes.ToArray(), properties.ModelBoxes.Count); + } + AddToCreative(id, numericId, properties); Logger.Debug($"Registered block '{id}' -> numeric ID {numericId}"); @@ -88,6 +95,8 @@ public static class BlockRegistry if (managedBlock is FallingBlock) return RegisterFalling(id, managedBlock, properties); + Assets.ModelResolver.ApplyBlockModel(id, properties); + ApplyModelLightDefaults(properties); int numericId = NativeInterop.native_register_managed_block( id.ToString(), (int)properties.MaterialValue, @@ -108,6 +117,11 @@ public static class BlockRegistry throw new InvalidOperationException($"Failed to register managed block '{id}'."); } + if (properties.ModelBoxes != null && properties.ModelBoxes.Count > 0) + { + NativeInterop.native_register_block_model(numericId, properties.ModelBoxes.ToArray(), properties.ModelBoxes.Count); + } + AddToCreative(id, numericId, properties); ManagedBlockDispatcher.RegisterBlock(id, numericId, managedBlock); @@ -134,6 +148,8 @@ public static class BlockRegistry private static RegisteredBlock RegisterFalling(Identifier id, Block? managedBlock, BlockProperties properties) { + Assets.ModelResolver.ApplyBlockModel(id, properties); + ApplyModelLightDefaults(properties); int numericId = NativeInterop.native_register_falling_block( id.ToString(), (int)properties.MaterialValue, @@ -154,6 +170,11 @@ public static class BlockRegistry throw new InvalidOperationException($"Failed to register falling block '{id}'."); } + if (properties.ModelBoxes != null && properties.ModelBoxes.Count > 0) + { + NativeInterop.native_register_block_model(numericId, properties.ModelBoxes.ToArray(), properties.ModelBoxes.Count); + } + AddToCreative(id, numericId, properties); if (managedBlock != null) @@ -180,6 +201,8 @@ public static class BlockRegistry public static RegisteredSlabBlock RegisterSlab(Identifier id, BlockProperties properties) { + Assets.ModelResolver.ApplyBlockModel(id, properties); + ApplyModelLightDefaults(properties); Identifier doubleId = new($"{id}_double"); int numericId = NativeInterop.native_register_slab_block( id.ToString(), @@ -207,6 +230,11 @@ public static class BlockRegistry throw new InvalidOperationException($"Failed to resolve generated slab pair '{doubleId}'."); } + if (properties.ModelBoxes != null && properties.ModelBoxes.Count > 0) + { + NativeInterop.native_register_block_model(numericId, properties.ModelBoxes.ToArray(), properties.ModelBoxes.Count); + } + AddToCreative(id, numericId, properties); lock (s_lock) @@ -217,6 +245,18 @@ public static class BlockRegistry return new RegisteredSlabBlock(id, doubleId, numericId, doubleNumericId); } + + private static void ApplyModelLightDefaults(BlockProperties properties) + { + if (properties.ModelBoxes == null || properties.ModelBoxes.Count == 0) + return; + + if (properties.LightBlockExplicit) + return; + + if (!properties.ModelIsFullCube) + properties.LightBlockValue = 0; + } internal static bool TryGetIdentifier(int numericId, out Identifier id) { lock (s_lock) diff --git a/WeaveLoader.API/Item/ItemProperties.cs b/WeaveLoader.API/Item/ItemProperties.cs index 294d41e..b18a28b 100644 --- a/WeaveLoader.API/Item/ItemProperties.cs +++ b/WeaveLoader.API/Item/ItemProperties.cs @@ -11,6 +11,7 @@ public class ItemProperties internal int MaxDamageValue = 0; internal float AttackDamageValue = 0.0f; internal string IconValue = ""; + internal string? ModelValue; internal CreativeTab CreativeTabValue = CreativeTab.None; internal CreativePlacement? CreativePlacementValue; internal Text? NameValue; @@ -21,6 +22,12 @@ public class ItemProperties /// from assets/examplemod/textures/item/ruby.png, or vanilla names like "diamond", "ingotIron". /// public ItemProperties Icon(string iconName) { IconValue = iconName; return this; } + /// + /// Optional Java-style model name (e.g. "examplemod:item/ruby"). + /// When provided, WeaveLoader will read assets/<namespace>/models/item/<name>.json + /// and use its texture for the item icon. + /// + public ItemProperties Model(string modelName) { ModelValue = modelName; return this; } /// /// Set max damage for a tool/armor item. Setting this to a positive value diff --git a/WeaveLoader.API/Item/ItemRegistry.cs b/WeaveLoader.API/Item/ItemRegistry.cs index fca7189..b7ad363 100644 --- a/WeaveLoader.API/Item/ItemRegistry.cs +++ b/WeaveLoader.API/Item/ItemRegistry.cs @@ -104,6 +104,7 @@ public static class ItemRegistry private static RegisteredItem RegisterInternal(Identifier id, ItemProperties properties, Item? managedItem) { + Assets.ModelResolver.ApplyItemModel(id, properties); int numericId; if (managedItem is PickaxeItem pickaxeItem) { diff --git a/WeaveLoader.API/ModContext.cs b/WeaveLoader.API/ModContext.cs new file mode 100644 index 0000000..d53b3b2 --- /dev/null +++ b/WeaveLoader.API/ModContext.cs @@ -0,0 +1,7 @@ +namespace WeaveLoader.API; + +public static class ModContext +{ + public static string? ModId { get; internal set; } + public static string? ModFolder { get; internal set; } +} diff --git a/WeaveLoader.API/NativeInterop.cs b/WeaveLoader.API/NativeInterop.cs index 15e3534..7c532e9 100644 --- a/WeaveLoader.API/NativeInterop.cs +++ b/WeaveLoader.API/NativeInterop.cs @@ -71,6 +71,12 @@ internal static class NativeInterop int acceptsRedstonePower, out int doubleNumericBlockId); + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern void native_register_block_model( + int blockId, + [In] Assets.ModelBox[] boxes, + int count); + [DllImport(RuntimeDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern int native_register_item( string namespacedId, diff --git a/WeaveLoader.Core/ModManager.cs b/WeaveLoader.Core/ModManager.cs index 866ea77..f7c115c 100644 --- a/WeaveLoader.Core/ModManager.cs +++ b/WeaveLoader.Core/ModManager.cs @@ -44,7 +44,7 @@ internal class ModManager { try { - mod.Instance.OnTick(); + WithContext(mod, () => mod.Instance.OnTick()); } catch (Exception ex) { @@ -64,7 +64,7 @@ internal class ModManager { try { - action(); + WithContext(mod, action); } catch (Exception ex) { @@ -72,4 +72,19 @@ internal class ModManager Logger.Debug(ex.StackTrace ?? ""); } } + + private static void WithContext(ModDiscovery.DiscoveredMod mod, Action action) + { + ModContext.ModId = mod.Metadata.Id; + ModContext.ModFolder = mod.Folder; + try + { + action(); + } + finally + { + ModContext.ModId = null; + ModContext.ModFolder = null; + } + } } diff --git a/WeaveLoaderRuntime/CMakeLists.txt b/WeaveLoaderRuntime/CMakeLists.txt index a0e1f35..f2ebc9f 100644 --- a/WeaveLoaderRuntime/CMakeLists.txt +++ b/WeaveLoaderRuntime/CMakeLists.txt @@ -96,6 +96,7 @@ add_library(WeaveLoaderRuntime SHARED src/CustomBlockRegistry.cpp src/CustomSlabRegistry.cpp src/ManagedBlockRegistry.cpp + src/ModelRegistry.cpp src/CreativeInventory.cpp src/FurnaceRecipeRegistry.cpp src/MainMenuOverlay.cpp diff --git a/WeaveLoaderRuntime/src/GameHooks.cpp b/WeaveLoaderRuntime/src/GameHooks.cpp index 29b5f0e..bc098c2 100644 --- a/WeaveLoaderRuntime/src/GameHooks.cpp +++ b/WeaveLoaderRuntime/src/GameHooks.cpp @@ -12,12 +12,14 @@ #include "CustomSlabRegistry.h" #include "LogUtil.h" #include "WorldIdRemap.h" +#include "ModelRegistry.h" #include #include #include #include #include #include +#include #include #include #include @@ -25,6 +27,8 @@ #include #include #include +#include +#include #include namespace GameHooks @@ -102,6 +106,290 @@ namespace GameHooks TextureTransferFromImage_fn Original_TextureTransferFromImage = nullptr; TexturePackGetImageResource_fn Original_AbstractTexturePackGetImageResource = nullptr; TexturePackGetImageResource_fn Original_DLCTexturePackGetImageResource = nullptr; + TileRendererTesselateInWorld_fn Original_TileRendererTesselateInWorld = nullptr; + TileRendererTesselateBlockInWorld_fn TileRenderer_TesselateBlockInWorld = nullptr; + TileRendererSetShape_fn TileRenderer_SetShape = nullptr; + TileRendererSetShapeTile_fn TileRenderer_SetShapeTile = nullptr; + TileSetShape_fn Tile_SetShape = nullptr; + AABBNewTemp_fn AABB_NewTemp = nullptr; + AABBClip_fn AABB_Clip = nullptr; + TileAddAABBs_fn Original_TileAddAABBs = nullptr; + TileUpdateDefaultShape_fn Original_TileUpdateDefaultShape = nullptr; + TileIsSolidRender_fn Original_TileIsSolidRender = nullptr; + TileIsCubeShaped_fn Original_TileIsCubeShaped = nullptr; + TileClip_fn Original_TileClip = nullptr; + Vec3NewTemp_fn Vec3_NewTemp = nullptr; + HitResultCtor_fn HitResult_Ctor = nullptr; + LevelClip_fn Original_LevelClip = nullptr; + LivingEntityPick_fn Original_LivingEntityPick = nullptr; + + namespace + { + struct AABBRaw + { + double x0, y0, z0; + double x1, y1, z1; + }; + + struct Vec3Raw + { + double x, y, z; + }; + + struct HitResultRaw + { + int type; + int x; + int y; + int z; + int f; + void* pos; + }; + + + static bool Intersects(const AABBRaw* box, double x0, double y0, double z0, double x1, double y1, double z1) + { + if (!box) + return false; + return !(box->x1 <= x0 || box->x0 >= x1 || + box->y1 <= y0 || box->y0 >= y1 || + box->z1 <= z0 || box->z0 >= z1); + } + + static bool IntersectSegmentAABB(const Vec3Raw& a, const Vec3Raw& b, + double x0, double y0, double z0, + double x1, double y1, double z1, + double& outT, int& outFace) + { + double tmin = 0.0; + double tmax = 1.0; + int faceNear = -1; + int faceFar = -1; + + auto axis = [&](double a0, double b0, double minV, double maxV, int minFace, int maxFace) -> bool + { + const double d = b0 - a0; + if (std::fabs(d) < 1e-12) + { + return !(a0 < minV || a0 > maxV); + } + + double tNear; + double tFar; + int nearFace; + int farFace; + + if (d > 0.0) + { + tNear = (minV - a0) / d; + tFar = (maxV - a0) / d; + nearFace = minFace; + farFace = maxFace; + } + else + { + tNear = (maxV - a0) / d; + tFar = (minV - a0) / d; + nearFace = maxFace; + farFace = minFace; + } + + if (tNear > tmin) + { + tmin = tNear; + faceNear = nearFace; + } + if (tFar < tmax) + { + tmax = tFar; + faceFar = farFace; + } + return tmin <= tmax; + }; + + if (!axis(a.x, b.x, x0, x1, 4, 5)) return false; // west/east + if (!axis(a.y, b.y, y0, y1, 0, 1)) return false; // down/up + if (!axis(a.z, b.z, z0, z1, 2, 3)) return false; // north/south + + if (tmax < 0.0 || tmin > 1.0) + return false; + + if (tmin >= 0.0) + { + outT = tmin; + outFace = faceNear; + } + else + { + outT = tmax; + outFace = faceFar; + } + return outFace >= 0; + } + + static bool IsFullCubeModel(const std::vector& boxes) + { + if (boxes.size() != 1) + return false; + const auto& b = boxes[0]; + const float eps = 0.0001f; + return (b.x0 <= 0.0f + eps && b.y0 <= 0.0f + eps && b.z0 <= 0.0f + eps && + b.x1 >= 1.0f - eps && b.y1 >= 1.0f - eps && b.z1 >= 1.0f - eps); + } + + + static std::atomic s_tileIdOffsetTried{false}; + static int s_tileIdOffset = -1; + + static bool ReadFileToString(const char* path, std::string& out) + { + out.clear(); + std::ifstream file(path, std::ios::binary); + if (!file) + return false; + out.assign(std::istreambuf_iterator(file), std::istreambuf_iterator()); + return !out.empty(); + } + + static bool ExtractOffsetForField(const std::string& json, const char* className, const char* fieldName, int& outOffset) + { + if (!className || !fieldName) + return false; + + const std::string classKey = std::string("\"") + className + "\""; + size_t classPos = json.find(classKey); + if (classPos == std::string::npos) + return false; + + size_t objStart = json.find('{', classPos); + if (objStart == std::string::npos) + return false; + size_t objEnd = json.find('}', objStart); + if (objEnd == std::string::npos) + return false; + + const std::string fieldKey = std::string("\"") + fieldName + "\""; + size_t fieldPos = json.find(fieldKey, objStart); + if (fieldPos == std::string::npos || fieldPos > objEnd) + return false; + + size_t colon = json.find(':', fieldPos + fieldKey.size()); + if (colon == std::string::npos) + return false; + + size_t numStart = json.find_first_of("0123456789", colon + 1); + if (numStart == std::string::npos) + return false; + + size_t numEnd = json.find_first_not_of("0123456789", numStart); + std::string num = json.substr(numStart, numEnd - numStart); + try + { + outOffset = std::stoi(num); + return true; + } + catch (...) + { + return false; + } + } + + static bool TryResolveTileIdOffset() + { + bool expected = false; + if (!s_tileIdOffsetTried.compare_exchange_strong(expected, true)) + return s_tileIdOffset >= 0; + + const char* baseDir = LogUtil::GetBaseDir(); + if (!baseDir || baseDir[0] == '\0') + { + LogUtil::Log("[WeaveLoader] ModelRegistry: base directory not set; cannot load offsets.json"); + return false; + } + + std::string json; + std::string path = std::string(baseDir) + "metadata\\offsets.json"; + if (!ReadFileToString(path.c_str(), json)) + { + path = std::string(baseDir) + "offsets.json"; + if (!ReadFileToString(path.c_str(), json)) + { + LogUtil::Log("[WeaveLoader] ModelRegistry: offsets.json not found; custom block models disabled"); + return false; + } + } + + int offset = -1; + if (!ExtractOffsetForField(json, "Tile", "id", offset)) + { + LogUtil::Log("[WeaveLoader] ModelRegistry: failed to read Tile.id offset; custom block models disabled"); + return false; + } + + s_tileIdOffset = offset; + LogUtil::Log("[WeaveLoader] ModelRegistry: Tile.id offset = 0x%X", s_tileIdOffset); + return true; + } + + int GetTileId(void* tilePtr) + { + if (!tilePtr) + return -1; + if (s_tileIdOffset < 0 && !TryResolveTileIdOffset()) + return -1; + return *reinterpret_cast(reinterpret_cast(tilePtr) + s_tileIdOffset); + } + + bool RenderModelInWorld(void* renderer, void* tilePtr, int x, int y, int z, const std::vector& boxes) + { + if (!renderer || !tilePtr || boxes.empty()) + return false; + if (!TileRenderer_SetShape || !TileRenderer_TesselateBlockInWorld) + return false; + + bool rendered = false; + float minX = 0.0f, minY = 0.0f, minZ = 0.0f; + float maxX = 0.0f, maxY = 0.0f, maxZ = 0.0f; + bool haveBounds = false; + for (const auto& box : boxes) + { + const float bx0 = box.x0 < box.x1 ? box.x0 : box.x1; + const float by0 = box.y0 < box.y1 ? box.y0 : box.y1; + const float bz0 = box.z0 < box.z1 ? box.z0 : box.z1; + const float bx1 = box.x0 < box.x1 ? box.x1 : box.x0; + const float by1 = box.y0 < box.y1 ? box.y1 : box.y0; + const float bz1 = box.z0 < box.z1 ? box.z1 : box.z0; + + if (!haveBounds) + { + minX = bx0; minY = by0; minZ = bz0; + maxX = bx1; maxY = by1; maxZ = bz1; + haveBounds = true; + } + else + { + if (bx0 < minX) minX = bx0; + if (by0 < minY) minY = by0; + if (bz0 < minZ) minZ = bz0; + if (bx1 > maxX) maxX = bx1; + if (by1 > maxY) maxY = by1; + if (bz1 > maxZ) maxZ = bz1; + } + + if (Tile_SetShape) + Tile_SetShape(tilePtr, bx0, by0, bz0, bx1, by1, bz1); + TileRenderer_SetShape(renderer, bx0, by0, bz0, bx1, by1, bz1); + rendered |= TileRenderer_TesselateBlockInWorld(renderer, tilePtr, x, y, z); + } + + if (Tile_SetShape && haveBounds) + Tile_SetShape(tilePtr, minX, minY, minZ, maxX, maxY, maxZ); + if (TileRenderer_SetShapeTile) + TileRenderer_SetShapeTile(renderer, tilePtr); + + return rendered; + } + } static int s_itemMineBlockHookCalls = 0; static void* s_currentLevel = nullptr; static thread_local void* s_activeUseLevel = nullptr; @@ -110,6 +398,7 @@ namespace GameHooks static EntityMoveTo_fn s_entityMoveTo = nullptr; static EntitySetPos_fn s_entitySetPos = nullptr; static EntityGetLookAngle_fn s_entityGetLookAngle = nullptr; + static LivingEntityGetPos_fn s_livingEntityGetPos = nullptr; static LivingEntityGetViewVector_fn s_livingEntityGetViewVector = nullptr; static EntityLerpMotion_fn s_entityLerpMotion = nullptr; static InventoryRemoveResource_fn s_inventoryRemoveResource = nullptr; @@ -746,6 +1035,7 @@ namespace GameHooks void* itemInstanceHurtAndBreak, void* containerBroadcastChanges, void* entityGetLookAngle, + void* livingEntityGetPos, void* livingEntityGetViewVector, void* entityLerpMotion, void* entitySetPos) @@ -754,6 +1044,7 @@ namespace GameHooks s_inventoryVtable = inventoryVtable; s_itemInstanceHurtAndBreak = reinterpret_cast(itemInstanceHurtAndBreak); s_entityGetLookAngle = reinterpret_cast(entityGetLookAngle); + s_livingEntityGetPos = reinterpret_cast(livingEntityGetPos); s_livingEntityGetViewVector = reinterpret_cast(livingEntityGetViewVector); s_entityLerpMotion = reinterpret_cast(entityLerpMotion); s_entitySetPos = reinterpret_cast(entitySetPos); @@ -2726,4 +3017,351 @@ namespace GameHooks Original_OutputDebugStringA(lpOutputString); } + + bool __fastcall Hooked_TileRendererTesselateInWorld(void* thisPtr, void* tilePtr, int x, int y, int z, int forceData, void* tileEntitySharedPtr) + { + const std::vector* boxes = nullptr; + int tileId = GetTileId(tilePtr); + if (tileId >= 0 && ModelRegistry::TryGetModel(tileId, boxes) && boxes && !boxes->empty()) + { + if (RenderModelInWorld(thisPtr, tilePtr, x, y, z, *boxes)) + return true; + } + + return Original_TileRendererTesselateInWorld + ? Original_TileRendererTesselateInWorld(thisPtr, tilePtr, x, y, z, forceData, tileEntitySharedPtr) + : false; + } + + void __fastcall Hooked_TileAddAABBs(void* thisPtr, void* levelPtr, int x, int y, int z, void* boxPtr, void* boxesPtr, void* sourcePtr) + { + const std::vector* boxes = nullptr; + int tileId = GetTileId(thisPtr); + if (tileId >= 0 && ModelRegistry::TryGetModel(tileId, boxes) && boxes && !boxes->empty() && AABB_NewTemp && boxesPtr && boxPtr) + { + auto list = reinterpret_cast*>(boxesPtr); + const AABBRaw* clipBox = reinterpret_cast(boxPtr); + + for (const auto& box : *boxes) + { + const double bx0 = box.x0 < box.x1 ? box.x0 : box.x1; + const double by0 = box.y0 < box.y1 ? box.y0 : box.y1; + const double bz0 = box.z0 < box.z1 ? box.z0 : box.z1; + const double bx1 = box.x0 < box.x1 ? box.x1 : box.x0; + const double by1 = box.y0 < box.y1 ? box.y1 : box.y0; + const double bz1 = box.z0 < box.z1 ? box.z1 : box.z0; + + const double wx0 = static_cast(x) + bx0; + const double wy0 = static_cast(y) + by0; + const double wz0 = static_cast(z) + bz0; + const double wx1 = static_cast(x) + bx1; + const double wy1 = static_cast(y) + by1; + const double wz1 = static_cast(z) + bz1; + + if (!Intersects(clipBox, wx0, wy0, wz0, wx1, wy1, wz1)) + continue; + + void* aabb = AABB_NewTemp(wx0, wy0, wz0, wx1, wy1, wz1); + if (aabb) + list->push_back(aabb); + } + return; + } + + if (Original_TileAddAABBs) + Original_TileAddAABBs(thisPtr, levelPtr, x, y, z, boxPtr, boxesPtr, sourcePtr); + } + + void __fastcall Hooked_TileUpdateDefaultShape(void* thisPtr) + { + const std::vector* boxes = nullptr; + int tileId = GetTileId(thisPtr); + if (tileId >= 0 && ModelRegistry::TryGetModel(tileId, boxes) && boxes && !boxes->empty() && Tile_SetShape) + { + float minX = 0.0f, minY = 0.0f, minZ = 0.0f; + float maxX = 0.0f, maxY = 0.0f, maxZ = 0.0f; + bool haveBounds = false; + for (const auto& box : *boxes) + { + const float bx0 = box.x0 < box.x1 ? box.x0 : box.x1; + const float by0 = box.y0 < box.y1 ? box.y0 : box.y1; + const float bz0 = box.z0 < box.z1 ? box.z0 : box.z1; + const float bx1 = box.x0 < box.x1 ? box.x1 : box.x0; + const float by1 = box.y0 < box.y1 ? box.y1 : box.y0; + const float bz1 = box.z0 < box.z1 ? box.z1 : box.z0; + + if (!haveBounds) + { + minX = bx0; minY = by0; minZ = bz0; + maxX = bx1; maxY = by1; maxZ = bz1; + haveBounds = true; + } + else + { + if (bx0 < minX) minX = bx0; + if (by0 < minY) minY = by0; + if (bz0 < minZ) minZ = bz0; + if (bx1 > maxX) maxX = bx1; + if (by1 > maxY) maxY = by1; + if (bz1 > maxZ) maxZ = bz1; + } + } + + if (haveBounds) + { + Tile_SetShape(thisPtr, minX, minY, minZ, maxX, maxY, maxZ); + return; + } + } + + if (Original_TileUpdateDefaultShape) + Original_TileUpdateDefaultShape(thisPtr); + } + + bool __fastcall Hooked_TileIsSolidRender(void* thisPtr, bool isServerLevel) + { + const std::vector* boxes = nullptr; + int tileId = GetTileId(thisPtr); + if (tileId >= 0 && ModelRegistry::TryGetModel(tileId, boxes) && boxes && !boxes->empty()) + { + if (IsFullCubeModel(*boxes)) + return Original_TileIsSolidRender ? Original_TileIsSolidRender(thisPtr, isServerLevel) : true; + return false; + } + + return Original_TileIsSolidRender ? Original_TileIsSolidRender(thisPtr, isServerLevel) : true; + } + + bool __fastcall Hooked_TileIsCubeShaped(void* thisPtr) + { + const std::vector* boxes = nullptr; + int tileId = GetTileId(thisPtr); + if (tileId >= 0 && ModelRegistry::TryGetModel(tileId, boxes) && boxes && !boxes->empty()) + { + if (IsFullCubeModel(*boxes)) + return Original_TileIsCubeShaped ? Original_TileIsCubeShaped(thisPtr) : true; + return false; + } + + return Original_TileIsCubeShaped ? Original_TileIsCubeShaped(thisPtr) : true; + } + + void* __fastcall Hooked_TileClip(void* thisPtr, void* levelPtr, int x, int y, int z, void* aPtr, void* bPtr) + { + if (!thisPtr || !aPtr || !bPtr) + return Original_TileClip ? Original_TileClip(thisPtr, levelPtr, x, y, z, aPtr, bPtr) : nullptr; + + const std::vector* boxes = nullptr; + int tileId = GetTileId(thisPtr); + if (tileId < 0 || !ModelRegistry::TryGetModel(tileId, boxes) || !boxes || boxes->empty()) + { + return Original_TileClip ? Original_TileClip(thisPtr, levelPtr, x, y, z, aPtr, bPtr) : nullptr; + } + + if (!Tile_SetShape || !Original_TileClip) + return Original_TileClip ? Original_TileClip(thisPtr, levelPtr, x, y, z, aPtr, bPtr) : nullptr; + + float minX = 0.0f, minY = 0.0f, minZ = 0.0f; + float maxX = 0.0f, maxY = 0.0f, maxZ = 0.0f; + bool haveBounds = false; + + for (const auto& box : *boxes) + { + const float bx0f = box.x0 < box.x1 ? box.x0 : box.x1; + const float by0f = box.y0 < box.y1 ? box.y0 : box.y1; + const float bz0f = box.z0 < box.z1 ? box.z0 : box.z1; + const float bx1f = box.x0 < box.x1 ? box.x1 : box.x0; + const float by1f = box.y0 < box.y1 ? box.y1 : box.y0; + const float bz1f = box.z0 < box.z1 ? box.z1 : box.z0; + + if (!haveBounds) + { + minX = bx0f; minY = by0f; minZ = bz0f; + maxX = bx1f; maxY = by1f; maxZ = bz1f; + haveBounds = true; + } + else + { + if (bx0f < minX) minX = bx0f; + if (by0f < minY) minY = by0f; + if (bz0f < minZ) minZ = bz0f; + if (bx1f > maxX) maxX = bx1f; + if (by1f > maxY) maxY = by1f; + if (bz1f > maxZ) maxZ = bz1f; + } + } + + if (haveBounds) + Tile_SetShape(thisPtr, minX, minY, minZ, maxX, maxY, maxZ); + + return Original_TileClip(thisPtr, levelPtr, x, y, z, aPtr, bPtr); + } + + static void* ApplyModelClipFallback(void* thisPtr, void* aPtr, void* bPtr, void* originalHit) + { + if (!thisPtr || !aPtr || !bPtr || !s_levelGetTile || !Vec3_NewTemp || !HitResult_Ctor) + return originalHit; + + const Vec3Raw& a = *reinterpret_cast(aPtr); + const Vec3Raw& b = *reinterpret_cast(bPtr); + const double dx = b.x - a.x; + const double dy = b.y - a.y; + const double dz = b.z - a.z; + const double rayLenSq = dx * dx + dy * dy + dz * dz; + if (rayLenSq < 1e-8) + return originalHit; + + const int minX = static_cast(std::floor(a.x < b.x ? a.x : b.x)); + const int minY = static_cast(std::floor(a.y < b.y ? a.y : b.y)); + const int minZ = static_cast(std::floor(a.z < b.z ? a.z : b.z)); + const int maxX = static_cast(std::floor(a.x > b.x ? a.x : b.x)); + const int maxY = static_cast(std::floor(a.y > b.y ? a.y : b.y)); + const int maxZ = static_cast(std::floor(a.z > b.z ? a.z : b.z)); + + double bestDistSq = 1e30; + int bestFace = -1; + int bestX = 0; + int bestY = 0; + int bestZ = 0; + double bestT = 2.0; + + for (int x = minX; x <= maxX; ++x) + { + for (int y = minY; y <= maxY; ++y) + { + for (int z = minZ; z <= maxZ; ++z) + { + const int tileId = s_levelGetTile(thisPtr, x, y, z); + if (tileId <= 0) + continue; + + const std::vector* boxes = nullptr; + if (!ModelRegistry::TryGetModel(tileId, boxes) || !boxes || boxes->empty()) + continue; + + for (const auto& box : *boxes) + { + const float bx0f = box.x0 < box.x1 ? box.x0 : box.x1; + const float by0f = box.y0 < box.y1 ? box.y0 : box.y1; + const float bz0f = box.z0 < box.z1 ? box.z0 : box.z1; + const float bx1f = box.x0 < box.x1 ? box.x1 : box.x0; + const float by1f = box.y0 < box.y1 ? box.y1 : box.y0; + const float bz1f = box.z0 < box.z1 ? box.z1 : box.z0; + + const double wx0 = static_cast(x) + bx0f; + const double wy0 = static_cast(y) + by0f; + const double wz0 = static_cast(z) + bz0f; + const double wx1 = static_cast(x) + bx1f; + const double wy1 = static_cast(y) + by1f; + const double wz1 = static_cast(z) + bz1f; + + void* aabb = AABB_NewTemp ? AABB_NewTemp(wx0, wy0, wz0, wx1, wy1, wz1) : nullptr; + void* hitPtr = (aabb && AABB_Clip) ? AABB_Clip(aabb, aPtr, bPtr) : nullptr; + if (hitPtr) + { + auto* hr = reinterpret_cast(hitPtr); + if (hr->pos) + { + const Vec3Raw& p = *reinterpret_cast(hr->pos); + const double ox = p.x - a.x; + const double oy = p.y - a.y; + const double oz = p.z - a.z; + const double distSq = ox * ox + oy * oy + oz * oz; + if (distSq < bestDistSq) + { + bestDistSq = distSq; + bestFace = hr->f; + bestX = x; + bestY = y; + bestZ = z; + } + } + } + else + { + double tHit = 0.0; + int face = -1; + if (IntersectSegmentAABB(a, b, wx0, wy0, wz0, wx1, wy1, wz1, tHit, face)) + { + const double distSq = (tHit * tHit) * rayLenSq; + if (distSq < bestDistSq) + { + bestDistSq = distSq; + bestFace = face; + bestX = x; + bestY = y; + bestZ = z; + bestT = tHit; + } + } + } + } + } + } + } + + if (bestFace < 0) + return originalHit; + + const double modelDistSq = bestDistSq; + if (originalHit) + { + auto* hr = reinterpret_cast(originalHit); + if (hr->type == 1) + return originalHit; + + if (hr->pos) + { + const Vec3Raw& p = *reinterpret_cast(hr->pos); + const double ox = p.x - a.x; + const double oy = p.y - a.y; + const double oz = p.z - a.z; + const double originalDistSq = ox * ox + oy * oy + oz * oz; + if (originalDistSq <= modelDistSq) + return originalHit; + } + } + + double t = bestT; + if (rayLenSq > 1e-8 && bestDistSq >= 0.0) + { + t = std::sqrt(bestDistSq / rayLenSq); + if (t < 0.0) t = 0.0; + if (t > 1.0) t = 1.0; + } + const double hx = a.x + dx * t; + const double hy = a.y + dy * t; + const double hz = a.z + dz * t; + void* pos = Vec3_NewTemp(hx, hy, hz); + void* hitMem = ::operator new(64); + HitResult_Ctor(hitMem, bestX, bestY, bestZ, bestFace, pos); + return hitMem; + } + + void* __fastcall Hooked_LevelClip(void* thisPtr, void* aPtr, void* bPtr, bool liquid, bool solidOnly) + { + void* originalHit = Original_LevelClip ? Original_LevelClip(thisPtr, aPtr, bPtr, liquid, solidOnly) : nullptr; + return ApplyModelClipFallback(thisPtr, aPtr, bPtr, originalHit); + } + + void* __fastcall Hooked_LivingEntityPick(void* thisPtr, double range, float partialTicks) + { + void* originalHit = Original_LivingEntityPick ? Original_LivingEntityPick(thisPtr, range, partialTicks) : nullptr; + + if (!thisPtr || !s_livingEntityGetPos || !s_livingEntityGetViewVector || !Vec3_NewTemp) + return originalHit; + + void* from = s_livingEntityGetPos(thisPtr, partialTicks); + void* dir = s_livingEntityGetViewVector(thisPtr, partialTicks); + if (!from || !dir) + return originalHit; + + const Vec3Raw& f = *reinterpret_cast(from); + const Vec3Raw& d = *reinterpret_cast(dir); + void* to = Vec3_NewTemp(f.x + d.x * range, f.y + d.y * range, f.z + d.z * range); + if (!to || !s_currentLevel) + return originalHit; + + return ApplyModelClipFallback(s_currentLevel, from, to, originalHit); + } } diff --git a/WeaveLoaderRuntime/src/GameHooks.h b/WeaveLoaderRuntime/src/GameHooks.h index 92daa69..275a142 100644 --- a/WeaveLoaderRuntime/src/GameHooks.h +++ b/WeaveLoaderRuntime/src/GameHooks.h @@ -71,6 +71,23 @@ typedef void (__fastcall *BufferedImageCtorDLCPack_fn)(void* thisPtr, void* dlcP typedef void* (__fastcall *TextureManagerCreateTexture_fn)(void* thisPtr, const std::wstring& name, int mode, int width, int height, int wrap, int format, int minFilter, int magFilter, bool mipmap, void* image); typedef void (__fastcall *TextureTransferFromImage_fn)(void* thisPtr, void* image); typedef void* (__fastcall *TexturePackGetImageResource_fn)(void* thisPtr, const std::wstring& file, bool filenameHasExtension, bool bTitleUpdateTexture, const std::wstring& drive); +typedef bool (__fastcall *TileRendererTesselateInWorld_fn)(void* thisPtr, void* tilePtr, int x, int y, int z, int forceData, void* tileEntitySharedPtr); +typedef bool (__fastcall *TileRendererTesselateBlockInWorld_fn)(void* thisPtr, void* tilePtr, int x, int y, int z); +typedef void (__fastcall *TileRendererSetShape_fn)(void* thisPtr, float x0, float y0, float z0, float x1, float y1, float z1); +typedef void (__fastcall *TileRendererSetShapeTile_fn)(void* thisPtr, void* tilePtr); +typedef void (__fastcall *TileSetShape_fn)(void* thisPtr, float x0, float y0, float z0, float x1, float y1, float z1); +typedef void (__fastcall *TileAddAABBs_fn)(void* thisPtr, void* levelPtr, int x, int y, int z, void* boxPtr, void* boxesPtr, void* sourcePtr); +typedef void (__fastcall *TileUpdateDefaultShape_fn)(void* thisPtr); +typedef void* (*AABBNewTemp_fn)(double x0, double y0, double z0, double x1, double y1, double z1); +typedef void* (__fastcall *AABBClip_fn)(void* thisPtr, void* aPtr, void* bPtr); +typedef bool (__fastcall *TileIsSolidRender_fn)(void* thisPtr, bool isServerLevel); +typedef bool (__fastcall *TileIsCubeShaped_fn)(void* thisPtr); +typedef void* (__fastcall *TileClip_fn)(void* thisPtr, void* levelPtr, int x, int y, int z, void* aPtr, void* bPtr); +typedef void* (*Vec3NewTemp_fn)(double x, double y, double z); +typedef void (__fastcall *HitResultCtor_fn)(void* thisPtr, int x, int y, int z, int f, void* posPtr); +typedef void* (__fastcall *LevelClip_fn)(void* thisPtr, void* aPtr, void* bPtr, bool liquid, bool solidOnly); +typedef void* (__fastcall *LivingEntityGetPos_fn)(void* thisPtr, float partialTicks); +typedef void* (__fastcall *LivingEntityPick_fn)(void* thisPtr, double range, float partialTicks); namespace GameHooks { @@ -147,6 +164,22 @@ namespace GameHooks extern TextureTransferFromImage_fn Original_TextureTransferFromImage; extern TexturePackGetImageResource_fn Original_AbstractTexturePackGetImageResource; extern TexturePackGetImageResource_fn Original_DLCTexturePackGetImageResource; + extern TileRendererTesselateInWorld_fn Original_TileRendererTesselateInWorld; + extern TileRendererTesselateBlockInWorld_fn TileRenderer_TesselateBlockInWorld; + extern TileRendererSetShape_fn TileRenderer_SetShape; + extern TileRendererSetShapeTile_fn TileRenderer_SetShapeTile; + extern TileSetShape_fn Tile_SetShape; + extern AABBNewTemp_fn AABB_NewTemp; + extern AABBClip_fn AABB_Clip; + extern TileAddAABBs_fn Original_TileAddAABBs; + extern TileUpdateDefaultShape_fn Original_TileUpdateDefaultShape; + extern TileIsSolidRender_fn Original_TileIsSolidRender; + extern TileIsCubeShaped_fn Original_TileIsCubeShaped; + extern TileClip_fn Original_TileClip; + extern Vec3NewTemp_fn Vec3_NewTemp; + extern HitResultCtor_fn HitResult_Ctor; + extern LevelClip_fn Original_LevelClip; + extern LivingEntityPick_fn Original_LivingEntityPick; void Hooked_RunStaticCtors(); void __fastcall Hooked_MinecraftTick(void* thisPtr, bool bFirst, bool bUpdateTextures); @@ -221,6 +254,14 @@ namespace GameHooks void __fastcall Hooked_TextureTransferFromImage(void* thisPtr, void* image); void* __fastcall Hooked_AbstractTexturePackGetImageResource(void* thisPtr, const std::wstring& file, bool filenameHasExtension, bool bTitleUpdateTexture, const std::wstring& drive); void* __fastcall Hooked_DLCTexturePackGetImageResource(void* thisPtr, const std::wstring& file, bool filenameHasExtension, bool bTitleUpdateTexture, const std::wstring& drive); + bool __fastcall Hooked_TileRendererTesselateInWorld(void* thisPtr, void* tilePtr, int x, int y, int z, int forceData, void* tileEntitySharedPtr); + void __fastcall Hooked_TileAddAABBs(void* thisPtr, void* levelPtr, int x, int y, int z, void* boxPtr, void* boxesPtr, void* sourcePtr); + void __fastcall Hooked_TileUpdateDefaultShape(void* thisPtr); + bool __fastcall Hooked_TileIsSolidRender(void* thisPtr, bool isServerLevel); + bool __fastcall Hooked_TileIsCubeShaped(void* thisPtr); + void* __fastcall Hooked_TileClip(void* thisPtr, void* levelPtr, int x, int y, int z, void* aPtr, void* bPtr); + void* __fastcall Hooked_LevelClip(void* thisPtr, void* aPtr, void* bPtr, bool liquid, bool solidOnly); + void* __fastcall Hooked_LivingEntityPick(void* thisPtr, double range, float partialTicks); void SetAtlasLocationPointers(void* blocksLocation, void* itemsLocation); void SetTileTilesArray(void* tilesArray); void SetSummonSymbols(void* levelAddEntity, @@ -232,6 +273,7 @@ namespace GameHooks void* itemInstanceHurtAndBreak, void* containerBroadcastChanges, void* entityGetLookAngle, + void* livingEntityGetPos, void* livingEntityGetViewVector, void* entityLerpMotion, void* entitySetPos); diff --git a/WeaveLoaderRuntime/src/HookManager.cpp b/WeaveLoaderRuntime/src/HookManager.cpp index b05ce1d..f6244ec 100644 --- a/WeaveLoaderRuntime/src/HookManager.cpp +++ b/WeaveLoaderRuntime/src/HookManager.cpp @@ -29,6 +29,23 @@ bool HookManager::Install(const SymbolResolver& symbols) symbols.Level.pLevelChunkGetHighestNonEmptyY); WorldIdRemap::SetCompressedTileStorageSetSymbol(symbols.Level.pCompressedTileStorageSet); + GameHooks::TileRenderer_TesselateBlockInWorld = + reinterpret_cast(symbols.Tile.pTileRendererTesselateBlockInWorld); + GameHooks::TileRenderer_SetShape = + reinterpret_cast(symbols.Tile.pTileRendererSetShape); + GameHooks::TileRenderer_SetShapeTile = + reinterpret_cast(symbols.Tile.pTileRendererSetShapeTile); + GameHooks::Tile_SetShape = + reinterpret_cast(symbols.Tile.pTileSetShape); + GameHooks::AABB_NewTemp = + reinterpret_cast(symbols.Tile.pAABBNewTemp); + GameHooks::AABB_Clip = + reinterpret_cast(symbols.Tile.pAABBClip); + GameHooks::Vec3_NewTemp = + reinterpret_cast(symbols.Tile.pVec3NewTemp); + GameHooks::HitResult_Ctor = + reinterpret_cast(symbols.Tile.pHitResultCtor); + if (symbols.Core.pRunStaticCtors) { if (MH_CreateHook(symbols.Core.pRunStaticCtors, @@ -443,6 +460,108 @@ bool HookManager::Install(const SymbolResolver& symbols) } } + if (symbols.Tile.pTileAddAABBs) + { + if (MH_CreateHook(symbols.Tile.pTileAddAABBs, + reinterpret_cast(&GameHooks::Hooked_TileAddAABBs), + reinterpret_cast(&GameHooks::Original_TileAddAABBs)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::addAABBs"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Tile::addAABBs (block model collisions)"); + } + } + + if (symbols.Tile.pTileUpdateDefaultShape) + { + if (MH_CreateHook(symbols.Tile.pTileUpdateDefaultShape, + reinterpret_cast(&GameHooks::Hooked_TileUpdateDefaultShape), + reinterpret_cast(&GameHooks::Original_TileUpdateDefaultShape)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::updateDefaultShape"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Tile::updateDefaultShape (block model bounds)"); + } + } + + if (symbols.Tile.pTileIsSolidRender) + { + if (MH_CreateHook(symbols.Tile.pTileIsSolidRender, + reinterpret_cast(&GameHooks::Hooked_TileIsSolidRender), + reinterpret_cast(&GameHooks::Original_TileIsSolidRender)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::isSolidRender"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Tile::isSolidRender (block model culling)"); + } + } + + if (symbols.Tile.pTileIsCubeShaped) + { + if (MH_CreateHook(symbols.Tile.pTileIsCubeShaped, + reinterpret_cast(&GameHooks::Hooked_TileIsCubeShaped), + reinterpret_cast(&GameHooks::Original_TileIsCubeShaped)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::isCubeShaped"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Tile::isCubeShaped (block model shape)"); + } + } + + if (symbols.Tile.pTileClip) + { + if (MH_CreateHook(symbols.Tile.pTileClip, + reinterpret_cast(&GameHooks::Hooked_TileClip), + reinterpret_cast(&GameHooks::Original_TileClip)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Tile::clip"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Tile::clip (block model picking)"); + } + } + + if (symbols.Level.pLevelClip) + { + if (MH_CreateHook(symbols.Level.pLevelClip, + reinterpret_cast(&GameHooks::Hooked_LevelClip), + reinterpret_cast(&GameHooks::Original_LevelClip)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook Level::clip"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked Level::clip (block model picking)"); + } + } + else + { + LogUtil::Log("[WeaveLoader] Warning: Level::clip symbol not found; model picking disabled"); + } + + if (symbols.Tile.pTileRendererTesselateInWorld) + { + if (MH_CreateHook(symbols.Tile.pTileRendererTesselateInWorld, + reinterpret_cast(&GameHooks::Hooked_TileRendererTesselateInWorld), + reinterpret_cast(&GameHooks::Original_TileRendererTesselateInWorld)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook TileRenderer::tesselateInWorld"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked TileRenderer::tesselateInWorld (block models)"); + } + } + if (symbols.Tile.pStoneSlabGetTexture) { if (MH_CreateHook(symbols.Tile.pStoneSlabGetTexture, @@ -863,10 +982,25 @@ bool HookManager::Install(const SymbolResolver& symbols) symbols.Item.pItemInstanceHurtAndBreak, symbols.Inventory.pAbstractContainerMenuBroadcastChanges, symbols.Entity.pEntityGetLookAngle, + symbols.Entity.pLivingEntityGetPos, symbols.Entity.pLivingEntityGetViewVector, symbols.Entity.pEntityLerpMotion, symbols.Entity.pEntitySetPos); + if (symbols.Entity.pLivingEntityPick) + { + if (MH_CreateHook(symbols.Entity.pLivingEntityPick, + reinterpret_cast(&GameHooks::Hooked_LivingEntityPick), + reinterpret_cast(&GameHooks::Original_LivingEntityPick)) != MH_OK) + { + LogUtil::Log("[WeaveLoader] Warning: Failed to hook LivingEntity::pick"); + } + else + { + LogUtil::Log("[WeaveLoader] Hooked LivingEntity::pick (block model picking)"); + } + } + if (symbols.Texture.pPreStitchedTextureMapStitch) { if (MH_CreateHook(symbols.Texture.pPreStitchedTextureMapStitch, diff --git a/WeaveLoaderRuntime/src/LogUtil.cpp b/WeaveLoaderRuntime/src/LogUtil.cpp index e53d0c6..811a8da 100644 --- a/WeaveLoaderRuntime/src/LogUtil.cpp +++ b/WeaveLoaderRuntime/src/LogUtil.cpp @@ -9,6 +9,7 @@ static std::string s_logsDir; static std::string s_logPath; static std::string s_gameLogPath; static std::string s_crashLogPath; +static std::string s_baseDir; static void GetTimestamp(char* buf, size_t bufSize) { @@ -24,13 +25,19 @@ namespace LogUtil void SetBaseDir(const char* baseDir) { - s_logsDir = std::string(baseDir) + "logs\\"; + s_baseDir = baseDir ? std::string(baseDir) : std::string(); + s_logsDir = s_baseDir + "logs\\"; CreateDirectoryA(s_logsDir.c_str(), nullptr); s_logPath = s_logsDir + "weaveloader.log"; s_gameLogPath = s_logsDir + "game_debug.log"; s_crashLogPath = s_logsDir + "crash.log"; } +const char* GetBaseDir() +{ + return s_baseDir.c_str(); +} + const char* GetLogsDir() { return s_logsDir.c_str(); diff --git a/WeaveLoaderRuntime/src/LogUtil.h b/WeaveLoaderRuntime/src/LogUtil.h index 4f3ca94..3a93900 100644 --- a/WeaveLoaderRuntime/src/LogUtil.h +++ b/WeaveLoaderRuntime/src/LogUtil.h @@ -7,6 +7,9 @@ namespace LogUtil // Creates a logs/ subdirectory and sets up all log file paths. void SetBaseDir(const char* baseDir); + // Returns the runtime DLL base directory (with trailing backslash) + const char* GetBaseDir(); + // Returns the logs directory path (with trailing backslash) const char* GetLogsDir(); diff --git a/WeaveLoaderRuntime/src/ModelRegistry.cpp b/WeaveLoaderRuntime/src/ModelRegistry.cpp new file mode 100644 index 0000000..0ce4750 --- /dev/null +++ b/WeaveLoaderRuntime/src/ModelRegistry.cpp @@ -0,0 +1,38 @@ +#include "ModelRegistry.h" +#include "LogUtil.h" +#include +#include + +namespace +{ + std::unordered_map> g_models; + std::mutex g_mutex; +} + +void ModelRegistry::RegisterBlockModel(int blockId, const ModelBox* boxes, int count) +{ + if (blockId < 0 || !boxes || count <= 0) + return; + + std::vector data; + data.reserve(static_cast(count)); + for (int i = 0; i < count; i++) + data.push_back(boxes[i]); + + { + std::lock_guard guard(g_mutex); + g_models[blockId] = std::move(data); + } + + LogUtil::Log("[WeaveLoader] ModelRegistry: registered %d box(es) for block %d", count, blockId); +} + +bool ModelRegistry::TryGetModel(int blockId, const std::vector*& outBoxes) +{ + std::lock_guard guard(g_mutex); + auto it = g_models.find(blockId); + if (it == g_models.end()) + return false; + outBoxes = &it->second; + return true; +} diff --git a/WeaveLoaderRuntime/src/ModelRegistry.h b/WeaveLoaderRuntime/src/ModelRegistry.h new file mode 100644 index 0000000..671250c --- /dev/null +++ b/WeaveLoaderRuntime/src/ModelRegistry.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +struct ModelBox +{ + float x0; + float y0; + float z0; + float x1; + float y1; + float z1; +}; + +namespace ModelRegistry +{ + void RegisterBlockModel(int blockId, const ModelBox* boxes, int count); + bool TryGetModel(int blockId, const std::vector*& outBoxes); +} diff --git a/WeaveLoaderRuntime/src/NativeExports.cpp b/WeaveLoaderRuntime/src/NativeExports.cpp index 26526bc..bff677a 100644 --- a/WeaveLoaderRuntime/src/NativeExports.cpp +++ b/WeaveLoaderRuntime/src/NativeExports.cpp @@ -307,6 +307,11 @@ int native_register_slab_block( return halfId; } +void native_register_block_model(int blockId, const ModelBox* boxes, int count) +{ + ModelRegistry::RegisterBlockModel(blockId, boxes, count); +} + void native_configure_managed_block(int numericBlockId, int dropNumericBlockId, int cloneNumericBlockId) { if (numericBlockId < 0) diff --git a/WeaveLoaderRuntime/src/NativeExports.h b/WeaveLoaderRuntime/src/NativeExports.h index c144c9b..4d606cc 100644 --- a/WeaveLoaderRuntime/src/NativeExports.h +++ b/WeaveLoaderRuntime/src/NativeExports.h @@ -2,6 +2,7 @@ #include #include +#include "ModelRegistry.h" namespace NativeExports { @@ -91,6 +92,11 @@ extern "C" int acceptsRedstonePower, int* outDoubleBlockNumericId); + __declspec(dllexport) void native_register_block_model( + int blockId, + const ModelBox* boxes, + int count); + __declspec(dllexport) int native_register_item( const char* namespacedId, int maxStackSize, diff --git a/WeaveLoaderRuntime/src/Symbols/SymbolGroups.cpp b/WeaveLoaderRuntime/src/Symbols/SymbolGroups.cpp index 4709bf3..fa6d836 100644 --- a/WeaveLoaderRuntime/src/Symbols/SymbolGroups.cpp +++ b/WeaveLoaderRuntime/src/Symbols/SymbolGroups.cpp @@ -93,11 +93,26 @@ namespace static const char* SYM_STONESLABITEM_GETDESCRIPTIONID = "?getDescriptionId@StoneSlabTileItem@@UEAAIV?$shared_ptr@VItemInstance@@@std@@@Z"; static const char* SYM_HALFSLAB_CLONETILEID = "?cloneTileId@HalfSlabTile@@UEAAHPEAVLevel@@HHH@Z"; static const char* SYM_TILE_TILES = "?tiles@Tile@@2PEAPEAV1@EA"; + static const char* SYM_TILE_ADDAABBS = "?addAABBs@Tile@@UEAAXPEAVLevel@@HHHPEAVAABB@@PEAV?$vector@PEAVAABB@@V?$allocator@PEAVAABB@@@std@@@std@@V?$shared_ptr@VEntity@@@5@@Z"; + static const char* SYM_TILE_UPDATEDEFAULTSHAPE = "?updateDefaultShape@Tile@@UEAAXXZ"; + static const char* SYM_TILE_SET_SHAPE = "?setShape@Tile@@UEAAXMMMMMM@Z"; + static const char* SYM_AABB_NEWTEMP = "?newTemp@AABB@@SAPEAV1@NNNNNN@Z"; + static const char* SYM_AABB_CLIP = "?clip@AABB@@QEAAPEAVHitResult@@PEAVVec3@@0@Z"; + static const char* SYM_TILE_ISSOLIDRENDER = "?isSolidRender@Tile@@UEAA_N_N@Z"; + static const char* SYM_TILE_ISCUBESHAPED = "?isCubeShaped@Tile@@UEAA_NXZ"; + static const char* SYM_TILE_CLIP = "?clip@Tile@@UEAAPEAVHitResult@@PEAVLevel@@HHHPEAVVec3@@1@Z"; + static const char* SYM_VEC3_NEWTEMP = "?newTemp@Vec3@@SAPEAV1@NNN@Z"; + static const char* SYM_HITRESULT_CTOR = "??0HitResult@@QEAA@HHHHPEAVVec3@@@Z"; + static const char* SYM_TILERENDERER_TESSELLATE_IN_WORLD = "?tesselateInWorld@TileRenderer@@QEAA_NPEAVTile@@HHHHV?$shared_ptr@VTileEntity@@@std@@@Z"; + static const char* SYM_TILERENDERER_TESSELLATE_BLOCK_IN_WORLD = "?tesselateBlockInWorld@TileRenderer@@QEAA_NPEAVTile@@HHH@Z"; + static const char* SYM_TILERENDERER_SET_SHAPE = "?setShape@TileRenderer@@QEAAXMMMMMM@Z"; + static const char* SYM_TILERENDERER_SET_SHAPE_TILE = "?setShape@TileRenderer@@QEAAXPEAVTile@@@Z"; static const char* SYM_LEVEL_UPDATE_NEIGHBORS_AT = "?updateNeighborsAt@Level@@QEAAXHHHH@Z"; static const char* SYM_SERVERLEVEL_TICKPENDINGTICKS = "?tickPendingTicks@ServerLevel@@UEAA_N_N@Z"; static const char* SYM_LEVEL_GETTILE = "?getTile@Level@@UEAAHHHH@Z"; static const char* SYM_LEVEL_SETDATA = "?setData@Level@@UEAA_NHHHHH_N@Z"; + static const char* SYM_LEVEL_CLIP = "?clip@Level@@QEAAPEAVHitResult@@PEAVVec3@@0_N1@Z"; static const char* SYM_MCREGIONCHUNKSTORAGE_LOAD = "?load@McRegionChunkStorage@@UEAAPEAVLevelChunk@@PEAVLevel@@HH@Z"; static const char* SYM_MCREGIONCHUNKSTORAGE_SAVE = "?save@McRegionChunkStorage@@UEAAXPEAVLevel@@PEAVLevelChunk@@@Z"; static const char* SYM_LEVEL_SETTILEANDDATA = "?setTileAndData@Level@@UEAA_NHHHHHH@Z"; @@ -115,6 +130,9 @@ namespace static const char* SYM_LIVINGENTITY_GETLOOKANGLE = "?getLookAngle@LivingEntity@@UEAAPEAVVec3@@XZ"; static const char* SYM_ENTITY_GETLOOKANGLE = "?getLookAngle@Entity@@UEAAPEAVVec3@@XZ"; static const char* SYM_LIVINGENTITY_GETVIEWVECTOR = "?getViewVector@LivingEntity@@UEAAPEAVVec3@@M@Z"; + static const char* SYM_LIVINGENTITY_GETPOS = "?getPos@LivingEntity@@QEAAPEAVVec3@@M@Z"; + static const char* SYM_LIVINGENTITY_GETPOS_V = "?getPos@LivingEntity@@UEAAPEAVVec3@@M@Z"; + static const char* SYM_LIVINGENTITY_PICK = "?pick@LivingEntity@@UEAAPEAVHitResult@@NM@Z"; static const char* SYM_ENTITY_LERPMOTION = "?lerpMotion@Entity@@UEAAXNNN@Z"; static const char* SYM_INVENTORY_REMOVERESOURCE = "?removeResource@Inventory@@QEAA_NH@Z"; @@ -328,6 +346,20 @@ bool TileSymbols::Resolve(SymbolResolver& resolver) pStoneSlabItemGetDescriptionId = resolver.Resolve(SYM_STONESLABITEM_GETDESCRIPTIONID); pHalfSlabCloneTileId = resolver.Resolve(SYM_HALFSLAB_CLONETILEID); pTileTiles = resolver.Resolve(SYM_TILE_TILES); + pTileAddAABBs = resolver.Resolve(SYM_TILE_ADDAABBS); + pTileUpdateDefaultShape = resolver.Resolve(SYM_TILE_UPDATEDEFAULTSHAPE); + pTileSetShape = resolver.Resolve(SYM_TILE_SET_SHAPE); + pAABBNewTemp = resolver.Resolve(SYM_AABB_NEWTEMP); + pAABBClip = resolver.Resolve(SYM_AABB_CLIP); + pTileIsSolidRender = resolver.Resolve(SYM_TILE_ISSOLIDRENDER); + pTileIsCubeShaped = resolver.Resolve(SYM_TILE_ISCUBESHAPED); + pTileClip = resolver.Resolve(SYM_TILE_CLIP); + pVec3NewTemp = resolver.Resolve(SYM_VEC3_NEWTEMP); + pHitResultCtor = resolver.Resolve(SYM_HITRESULT_CTOR); + pTileRendererTesselateInWorld = resolver.Resolve(SYM_TILERENDERER_TESSELLATE_IN_WORLD); + pTileRendererTesselateBlockInWorld = resolver.Resolve(SYM_TILERENDERER_TESSELLATE_BLOCK_IN_WORLD); + pTileRendererSetShape = resolver.Resolve(SYM_TILERENDERER_SET_SHAPE); + pTileRendererSetShapeTile = resolver.Resolve(SYM_TILERENDERER_SET_SHAPE_TILE); if (resolver.IsStub(pTileOnPlace)) pTileOnPlace = resolver.ResolveExact("Tile::onPlace"); @@ -337,6 +369,8 @@ bool TileSymbols::Resolve(SymbolResolver& resolver) pTileTick = resolver.ResolveExact("Tile::tick"); if (resolver.IsStub(pWoodSlabRegisterIcons)) pWoodSlabRegisterIcons = resolver.ResolveExact("WoodSlabTile::registerIcons"); + if (resolver.IsStub(pTileClip)) + pTileClip = resolver.ResolveExact("Tile::clip"); return true; } @@ -362,6 +396,20 @@ void TileSymbols::Log() const LogSym("StoneSlabTileItem::getDescriptionId", pStoneSlabItemGetDescriptionId); LogSym("HalfSlabTile::cloneTileId", pHalfSlabCloneTileId); LogSym("Tile::tiles", pTileTiles); + LogSym("Tile::addAABBs", pTileAddAABBs); + LogSym("Tile::updateDefaultShape", pTileUpdateDefaultShape); + LogSym("Tile::setShape", pTileSetShape); + LogSym("AABB::newTemp", pAABBNewTemp); + LogSym("AABB::clip", pAABBClip); + LogSym("Tile::isSolidRender", pTileIsSolidRender); + LogSym("Tile::isCubeShaped", pTileIsCubeShaped); + LogSym("Tile::clip", pTileClip); + LogSym("Vec3::newTemp", pVec3NewTemp); + LogSym("HitResult::HitResult", pHitResultCtor); + LogSym("TileRenderer::tesselateInWorld", pTileRendererTesselateInWorld); + LogSym("TileRenderer::tesselateBlockInWorld", pTileRendererTesselateBlockInWorld); + LogSym("TileRenderer::setShape(float)", pTileRendererSetShape); + LogSym("TileRenderer::setShape(Tile)", pTileRendererSetShapeTile); } bool LevelSymbols::Resolve(SymbolResolver& resolver) @@ -370,6 +418,7 @@ bool LevelSymbols::Resolve(SymbolResolver& resolver) pServerLevelTickPendingTicks = resolver.Resolve(SYM_SERVERLEVEL_TICKPENDINGTICKS); pLevelGetTile = resolver.Resolve(SYM_LEVEL_GETTILE); pLevelSetData = resolver.Resolve(SYM_LEVEL_SETDATA); + pLevelClip = resolver.Resolve(SYM_LEVEL_CLIP); pMcRegionChunkStorageLoad = resolver.Resolve(SYM_MCREGIONCHUNKSTORAGE_LOAD); if (!pMcRegionChunkStorageLoad) pMcRegionChunkStorageLoad = resolver.ResolveExact("McRegionChunkStorage::load"); @@ -394,6 +443,7 @@ void LevelSymbols::Log() const LogSym("ServerLevel::tickPendingTicks", pServerLevelTickPendingTicks); LogSym("Level::getTile", pLevelGetTile); LogSym("Level::setData", pLevelSetData); + LogSym("Level::clip", pLevelClip); LogSym("McRegionChunkStorage::load", pMcRegionChunkStorageLoad); LogSym("McRegionChunkStorage::save", pMcRegionChunkStorageSave); LogSym("Level::setTileAndData", pLevelSetTileAndData); @@ -417,7 +467,11 @@ bool EntitySymbols::Resolve(SymbolResolver& resolver) pEntityMoveTo = resolver.Resolve(SYM_ENTITY_MOVETO); pEntitySetPos = resolver.Resolve(SYM_ENTITY_SETPOS); pEntityGetLookAngle = resolver.Resolve(SYM_LIVINGENTITY_GETLOOKANGLE); + pLivingEntityGetPos = resolver.Resolve(SYM_LIVINGENTITY_GETPOS); + if (!pLivingEntityGetPos) + pLivingEntityGetPos = resolver.Resolve(SYM_LIVINGENTITY_GETPOS_V); pLivingEntityGetViewVector = resolver.Resolve(SYM_LIVINGENTITY_GETVIEWVECTOR); + pLivingEntityPick = resolver.Resolve(SYM_LIVINGENTITY_PICK); if (!pEntityGetLookAngle) pEntityGetLookAngle = resolver.Resolve(SYM_ENTITY_GETLOOKANGLE); pEntityLerpMotion = resolver.Resolve(SYM_ENTITY_LERPMOTION); @@ -434,7 +488,9 @@ void EntitySymbols::Log() const LogSym("Entity::moveTo", pEntityMoveTo); LogSym("Entity::setPos", pEntitySetPos); LogSym("LivingEntity/Entity::getLookAngle", pEntityGetLookAngle); + LogSym("LivingEntity::getPos", pLivingEntityGetPos); LogSym("LivingEntity::getViewVector", pLivingEntityGetViewVector); + LogSym("LivingEntity::pick", pLivingEntityPick); LogSym("Entity::lerpMotion", pEntityLerpMotion); } diff --git a/WeaveLoaderRuntime/src/Symbols/SymbolGroups.h b/WeaveLoaderRuntime/src/Symbols/SymbolGroups.h index ac83c92..ae46461 100644 --- a/WeaveLoaderRuntime/src/Symbols/SymbolGroups.h +++ b/WeaveLoaderRuntime/src/Symbols/SymbolGroups.h @@ -113,6 +113,20 @@ struct TileSymbols void* pStoneSlabItemGetDescriptionId = nullptr; void* pHalfSlabCloneTileId = nullptr; void* pTileTiles = nullptr; + void* pTileAddAABBs = nullptr; + void* pTileUpdateDefaultShape = nullptr; + void* pTileSetShape = nullptr; + void* pAABBNewTemp = nullptr; + void* pAABBClip = nullptr; + void* pTileIsSolidRender = nullptr; + void* pTileIsCubeShaped = nullptr; + void* pTileClip = nullptr; + void* pVec3NewTemp = nullptr; + void* pHitResultCtor = nullptr; + void* pTileRendererTesselateInWorld = nullptr; + void* pTileRendererTesselateBlockInWorld = nullptr; + void* pTileRendererSetShape = nullptr; + void* pTileRendererSetShapeTile = nullptr; bool Resolve(SymbolResolver& resolver); void Log() const; @@ -124,6 +138,7 @@ struct LevelSymbols void* pServerLevelTickPendingTicks = nullptr; void* pLevelGetTile = nullptr; void* pLevelSetData = nullptr; + void* pLevelClip = nullptr; void* pMcRegionChunkStorageLoad = nullptr; void* pMcRegionChunkStorageSave = nullptr; void* pLevelSetTileAndData = nullptr; @@ -150,7 +165,9 @@ struct EntitySymbols void* pEntityMoveTo = nullptr; void* pEntitySetPos = nullptr; void* pEntityGetLookAngle = nullptr; + void* pLivingEntityGetPos = nullptr; void* pLivingEntityGetViewVector = nullptr; + void* pLivingEntityPick = nullptr; void* pEntityLerpMotion = nullptr; bool Resolve(SymbolResolver& resolver); diff --git a/docs/MODDING.md b/docs/MODDING.md index b5496ee..9f66735 100644 --- a/docs/MODDING.md +++ b/docs/MODDING.md @@ -100,6 +100,17 @@ MyMod/ └── ... ``` +To drive an icon from a model JSON, call: + +```csharp +.Model("mymod:block/example_ore") +.Model("mymod:item/example_gem") +``` + +WeaveLoader reads the model JSON and uses its texture for the icon. + +For block items, WeaveLoader uses the block model by default. An item model JSON is optional. + ## Build and run ```bash