mirror of
https://github.com/Jacobwasbeast/LegacyWeaveLoader.git
synced 2026-05-21 21:24:30 +00:00
feat(models): add block model boxes + picking
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
13
ExampleMod/assets/examplemod/models/block/ruby_chair.json
Normal file
13
ExampleMod/assets/examplemod/models/block/ruby_chair.json
Normal 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" } } }
|
||||
]
|
||||
}
|
||||
14
WeaveLoader.API/Assets/ModelBox.cs
Normal file
14
WeaveLoader.API/Assets/ModelBox.cs
Normal 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;
|
||||
}
|
||||
311
WeaveLoader.API/Assets/ModelResolver.cs
Normal file
311
WeaveLoader.API/Assets/ModelResolver.cs
Normal 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}";
|
||||
}
|
||||
}
|
||||
@@ -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/<namespace>/models/block/<name>.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; }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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/<namespace>/models/item/<name>.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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
7
WeaveLoader.API/ModContext.cs
Normal file
7
WeaveLoader.API/ModContext.cs
Normal 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; }
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
38
WeaveLoaderRuntime/src/ModelRegistry.cpp
Normal file
38
WeaveLoaderRuntime/src/ModelRegistry.cpp
Normal 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;
|
||||
}
|
||||
19
WeaveLoaderRuntime/src/ModelRegistry.h
Normal file
19
WeaveLoaderRuntime/src/ModelRegistry.h
Normal 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);
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user