feat(modloader): pdb mapping, dynamic invoke, mixins

This commit is contained in:
Jacobwasbeast
2026-03-10 17:45:25 -05:00
parent 70dbff3fac
commit be327befa4
34 changed files with 2535 additions and 7 deletions

View File

@@ -9,6 +9,8 @@ namespace ExampleMod;
Description = "A sample mod demonstrating the WeaveLoader API")]
public class ExampleMod : IMod
{
private static nint s_currentLevel;
private static bool s_hasLevel;
public static RegisteredBlock? RubyOre;
public static RegisteredBlock? RubyStone;
public static RegisteredBlock? RubyWoodPlanks;
@@ -189,6 +191,17 @@ public class ExampleMod : IMod
public void OnInitialize()
{
GameEvents.OnWorldLoaded += (_, e) =>
{
s_currentLevel = e.NativeLevelPointer;
s_hasLevel = s_currentLevel != 0;
};
GameEvents.OnWorldUnloaded += (_, __) =>
{
s_currentLevel = 0;
s_hasLevel = false;
};
RubyOre = Registry.Block.Register("examplemod:ruby_ore",
new BlockProperties()
.Material(MaterialType.Stone)
@@ -391,4 +404,10 @@ public class ExampleMod : IMod
{
Logger.Info("Example Mod shutting down.");
}
internal static bool TryGetCurrentLevel(out nint levelPtr)
{
levelPtr = s_currentLevel;
return s_hasLevel && levelPtr != 0;
}
}

View File

@@ -22,6 +22,7 @@
<ItemGroup>
<Content Include="assets\**\*" CopyToOutputDirectory="PreserveNewest" />
<Content Include="weave.mixins.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using WeaveLoader.API;
using WeaveLoader.API.Mixins;
using WeaveLoader.API.Native;
namespace ExampleMod.Mixins;
[Mixin("Creeper")]
public static class CreeperExplosionMixin
{
private const int Radius = 6;
private static readonly HashSet<nint> s_processed = new();
private static int s_triggerLogCount;
private static bool s_offsetsReady;
private static bool s_offsetsWarned;
private static int s_swellOffset;
private static int s_maxSwellOffset;
private static int s_isClientSideOffset;
[Inject("tick", At.Tail)]
private static void OnTick(MixinContext ctx)
{
nint thisPtr = ctx.ThisPtr;
if (thisPtr == 0 || ExampleMod.RubySand == null)
return;
nint levelPtr = 0;
if (NativeOffsets.TryGet("Entity", "level", out var levelOffset))
levelPtr = NativeMemory.ReadPtr(thisPtr, levelOffset);
if (levelPtr == 0 && !ExampleMod.TryGetCurrentLevel(out levelPtr))
return;
if (!EnsureOffsets())
return;
if (s_isClientSideOffset != 0 && NativeMemory.ReadBool(levelPtr, s_isClientSideOffset))
return;
int swell = NativeMemory.ReadInt32(thisPtr, s_swellOffset);
int maxSwell = NativeMemory.ReadInt32(thisPtr, s_maxSwellOffset);
if (swell < maxSwell)
return;
if (!NativeMemory.ReadBool(thisPtr, NativeOffsets.Entity.Removed))
return;
if (!s_processed.Add(thisPtr))
return;
if (s_processed.Count > 256)
s_processed.Clear();
if (s_triggerLogCount < 1)
{
Logger.Info($"CreeperExplosionMixin triggered at ({NativeMemory.ReadDouble(thisPtr, NativeOffsets.Entity.X):F2}, {NativeMemory.ReadDouble(thisPtr, NativeOffsets.Entity.Y):F2}, {NativeMemory.ReadDouble(thisPtr, NativeOffsets.Entity.Z):F2})");
s_triggerLogCount++;
}
int x = (int)Math.Floor(NativeMemory.ReadDouble(thisPtr, NativeOffsets.Entity.X));
int y = (int)Math.Floor(NativeMemory.ReadDouble(thisPtr, NativeOffsets.Entity.Y));
int z = (int)Math.Floor(NativeMemory.ReadDouble(thisPtr, NativeOffsets.Entity.Z));
PaintSphereTop(levelPtr, x, y, z);
}
private static bool EnsureOffsets()
{
if (s_offsetsReady)
return true;
bool ok = true;
ok &= NativeOffsets.TryGet("Creeper", "swell", out s_swellOffset);
ok &= NativeOffsets.TryGet("Creeper", "maxSwell", out s_maxSwellOffset);
NativeOffsets.TryGet("Level", "isClientSide", out s_isClientSideOffset);
s_offsetsReady = ok;
if (!s_offsetsReady && !s_offsetsWarned)
{
Logger.Warning("CreeperExplosionMixin: missing offsets for Creeper swell/maxSwell.");
s_offsetsWarned = true;
}
return s_offsetsReady;
}
private static void PaintSphereTop(nint levelPtr, int centerX, int centerY, int centerZ)
{
int minY = Math.Max(0, centerY - Radius);
int maxY = Math.Min(255, centerY + Radius);
int radiusSq = Radius * Radius;
var targets = new List<(int X, int Y, int Z)>();
for (int dx = -Radius; dx <= Radius; dx++)
{
int dxSq = dx * dx;
for (int dz = -Radius; dz <= Radius; dz++)
{
int distSq = dxSq + dz * dz;
if (distSq > radiusSq)
continue;
int worldX = centerX + dx;
int worldZ = centerZ + dz;
int verticalRadius = (int)Math.Floor(Math.Sqrt(radiusSq - distSq));
int startY = Math.Min(maxY, centerY + verticalRadius);
int endY = Math.Max(minY, centerY - verticalRadius);
int topY = -1;
for (int y = startY; y >= endY; --y)
{
int id = NativeLevel.GetTile(levelPtr, worldX, y, worldZ);
if (id != 0)
{
topY = y;
break;
}
}
if (topY >= 0)
targets.Add((worldX, topY, worldZ));
}
}
foreach (var (tx, ty, tz) in targets)
NativeLevel.SetTile(levelPtr, tx, ty, tz, ExampleMod.RubySand!.NumericId, 0, 2);
}
}

View File

@@ -0,0 +1,13 @@
{
"required": false,
"minVersion": "0.1",
"package": "ExampleMod.Mixins",
"mixins": [],
"client": [
"CreeperExplosionMixin"
],
"server": [],
"injectors": {
"defaultRequire": 1
}
}