feat(models): add block model boxes + picking

This commit is contained in:
Jacobwasbeast
2026-03-11 15:12:35 -05:00
parent 6fabb8fd39
commit 788b7167a2
26 changed files with 1429 additions and 4 deletions

View File

@@ -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()

View File

@@ -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.

View File

@@ -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

View File

@@ -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" } } }
]
}

View File

@@ -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;
}

View File

@@ -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<ModelBox> 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<string, string> 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<string, string> textures)
{
textures = new Dictionary<string, string>(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<ModelBox> 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<ModelBox> 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<string, string> 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<string, string> 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}";
}
}

View File

@@ -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<Assets.ModelBox>? 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".
/// </summary>
public BlockProperties Icon(string iconName) { IconValue = iconName; return this; }
/// <summary>
/// Optional Java-style model name (e.g. "examplemod:block/ruby_ore").
/// When provided, WeaveLoader will read assets/&lt;namespace&gt;/models/block/&lt;name&gt;.json
/// and use its texture for the block icon.
/// </summary>
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; }

View File

@@ -50,6 +50,8 @@ public static class BlockRegistry
/// <returns>A handle to the registered block.</returns>
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)

View File

@@ -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".
/// </summary>
public ItemProperties Icon(string iconName) { IconValue = iconName; return this; }
/// <summary>
/// Optional Java-style model name (e.g. "examplemod:item/ruby").
/// When provided, WeaveLoader will read assets/&lt;namespace&gt;/models/item/&lt;name&gt;.json
/// and use its texture for the item icon.
/// </summary>
public ItemProperties Model(string modelName) { ModelValue = modelName; return this; }
/// <summary>
/// Set max damage for a tool/armor item. Setting this to a positive value

View File

@@ -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)
{

View File

@@ -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; }
}

View File

@@ -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,

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -12,12 +12,14 @@
#include "CustomSlabRegistry.h"
#include "LogUtil.h"
#include "WorldIdRemap.h"
#include "ModelRegistry.h"
#include <Windows.h>
#include <string>
#include <cstdio>
#include <cstring>
#include <cwchar>
#include <cwctype>
#include <vector>
#include <cctype>
#include <memory>
#include <mutex>
@@ -25,6 +27,8 @@
#include <thread>
#include <unordered_map>
#include <cstddef>
#include <fstream>
#include <cmath>
#include <vector>
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<ModelBox>& 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<bool> 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<char>(file), std::istreambuf_iterator<char>());
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<int*>(reinterpret_cast<char*>(tilePtr) + s_tileIdOffset);
}
bool RenderModelInWorld(void* renderer, void* tilePtr, int x, int y, int z, const std::vector<ModelBox>& 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_fn>(itemInstanceHurtAndBreak);
s_entityGetLookAngle = reinterpret_cast<EntityGetLookAngle_fn>(entityGetLookAngle);
s_livingEntityGetPos = reinterpret_cast<LivingEntityGetPos_fn>(livingEntityGetPos);
s_livingEntityGetViewVector = reinterpret_cast<LivingEntityGetViewVector_fn>(livingEntityGetViewVector);
s_entityLerpMotion = reinterpret_cast<EntityLerpMotion_fn>(entityLerpMotion);
s_entitySetPos = reinterpret_cast<EntitySetPos_fn>(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<ModelBox>* 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<ModelBox>* boxes = nullptr;
int tileId = GetTileId(thisPtr);
if (tileId >= 0 && ModelRegistry::TryGetModel(tileId, boxes) && boxes && !boxes->empty() && AABB_NewTemp && boxesPtr && boxPtr)
{
auto list = reinterpret_cast<std::vector<void*>*>(boxesPtr);
const AABBRaw* clipBox = reinterpret_cast<const AABBRaw*>(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<double>(x) + bx0;
const double wy0 = static_cast<double>(y) + by0;
const double wz0 = static_cast<double>(z) + bz0;
const double wx1 = static_cast<double>(x) + bx1;
const double wy1 = static_cast<double>(y) + by1;
const double wz1 = static_cast<double>(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<ModelBox>* 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<ModelBox>* 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<ModelBox>* 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<ModelBox>* 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<const Vec3Raw*>(aPtr);
const Vec3Raw& b = *reinterpret_cast<const Vec3Raw*>(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<int>(std::floor(a.x < b.x ? a.x : b.x));
const int minY = static_cast<int>(std::floor(a.y < b.y ? a.y : b.y));
const int minZ = static_cast<int>(std::floor(a.z < b.z ? a.z : b.z));
const int maxX = static_cast<int>(std::floor(a.x > b.x ? a.x : b.x));
const int maxY = static_cast<int>(std::floor(a.y > b.y ? a.y : b.y));
const int maxZ = static_cast<int>(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<ModelBox>* 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<double>(x) + bx0f;
const double wy0 = static_cast<double>(y) + by0f;
const double wz0 = static_cast<double>(z) + bz0f;
const double wx1 = static_cast<double>(x) + bx1f;
const double wy1 = static_cast<double>(y) + by1f;
const double wz1 = static_cast<double>(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<const HitResultRaw*>(hitPtr);
if (hr->pos)
{
const Vec3Raw& p = *reinterpret_cast<const Vec3Raw*>(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<const HitResultRaw*>(originalHit);
if (hr->type == 1)
return originalHit;
if (hr->pos)
{
const Vec3Raw& p = *reinterpret_cast<const Vec3Raw*>(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<const Vec3Raw*>(from);
const Vec3Raw& d = *reinterpret_cast<const Vec3Raw*>(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);
}
}

View File

@@ -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);

View File

@@ -29,6 +29,23 @@ bool HookManager::Install(const SymbolResolver& symbols)
symbols.Level.pLevelChunkGetHighestNonEmptyY);
WorldIdRemap::SetCompressedTileStorageSetSymbol(symbols.Level.pCompressedTileStorageSet);
GameHooks::TileRenderer_TesselateBlockInWorld =
reinterpret_cast<TileRendererTesselateBlockInWorld_fn>(symbols.Tile.pTileRendererTesselateBlockInWorld);
GameHooks::TileRenderer_SetShape =
reinterpret_cast<TileRendererSetShape_fn>(symbols.Tile.pTileRendererSetShape);
GameHooks::TileRenderer_SetShapeTile =
reinterpret_cast<TileRendererSetShapeTile_fn>(symbols.Tile.pTileRendererSetShapeTile);
GameHooks::Tile_SetShape =
reinterpret_cast<TileSetShape_fn>(symbols.Tile.pTileSetShape);
GameHooks::AABB_NewTemp =
reinterpret_cast<AABBNewTemp_fn>(symbols.Tile.pAABBNewTemp);
GameHooks::AABB_Clip =
reinterpret_cast<AABBClip_fn>(symbols.Tile.pAABBClip);
GameHooks::Vec3_NewTemp =
reinterpret_cast<Vec3NewTemp_fn>(symbols.Tile.pVec3NewTemp);
GameHooks::HitResult_Ctor =
reinterpret_cast<HitResultCtor_fn>(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<void*>(&GameHooks::Hooked_TileAddAABBs),
reinterpret_cast<void**>(&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<void*>(&GameHooks::Hooked_TileUpdateDefaultShape),
reinterpret_cast<void**>(&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<void*>(&GameHooks::Hooked_TileIsSolidRender),
reinterpret_cast<void**>(&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<void*>(&GameHooks::Hooked_TileIsCubeShaped),
reinterpret_cast<void**>(&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<void*>(&GameHooks::Hooked_TileClip),
reinterpret_cast<void**>(&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<void*>(&GameHooks::Hooked_LevelClip),
reinterpret_cast<void**>(&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<void*>(&GameHooks::Hooked_TileRendererTesselateInWorld),
reinterpret_cast<void**>(&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<void*>(&GameHooks::Hooked_LivingEntityPick),
reinterpret_cast<void**>(&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,

View File

@@ -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();

View File

@@ -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();

View File

@@ -0,0 +1,38 @@
#include "ModelRegistry.h"
#include "LogUtil.h"
#include <unordered_map>
#include <mutex>
namespace
{
std::unordered_map<int, std::vector<ModelBox>> 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<ModelBox> data;
data.reserve(static_cast<size_t>(count));
for (int i = 0; i < count; i++)
data.push_back(boxes[i]);
{
std::lock_guard<std::mutex> 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<ModelBox>*& outBoxes)
{
std::lock_guard<std::mutex> guard(g_mutex);
auto it = g_models.find(blockId);
if (it == g_models.end())
return false;
outBoxes = &it->second;
return true;
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include <vector>
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<ModelBox>*& outBoxes);
}

View File

@@ -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)

View File

@@ -2,6 +2,7 @@
#include <string>
#include <cstdint>
#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,

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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