Add markers module, framecrash stub, debug console, build-flag wiring

Markers: client-side world object system using CreateGameObject. Places
M2 models at arbitrary world positions via Lua commands (/mark test,
/mark pos). Includes embedded addon, xyz.m2/blp assets served from DLL
memory, position helpers from unit movement struct, and object lifecycle
management (create, cleanup, reposition, alpha, animation).

Framecrash: stub module with reference to crash at 0x007A2452.

Console: debug output via AllocConsole/WriteConsoleA, compiles out
entirely in non-Debug builds. Used by markers and file serve logging.

Build: markers added to feature flag matrix and all-variants step.
Main: conditional markers import, Lua function registration, embedded
addon + asset file serving, console init/deinit lifecycle.

Outline README: added misc planned features and debug mode notes.
This commit is contained in:
MarcelineVQ
2026-02-26 15:30:21 -08:00
parent cd7e585412
commit 37d42d09c2
12 changed files with 548 additions and 5 deletions
+9 -5
View File
@@ -12,12 +12,14 @@ pub fn build(b: *std.Build) void {
const enable_screenshot = b.option(bool, "screenshot", "Enable screenshot module") orelse true;
const enable_interact = b.option(bool, "interact", "Enable interact module") orelse true;
const enable_outline = b.option(bool, "outline", "Enable outline module") orelse true;
const enable_markers = b.option(bool, "markers", "Enable markers module") orelse true;
// Create build options module
const build_options = b.addOptions();
build_options.addOption(bool, "enable_screenshot", enable_screenshot);
build_options.addOption(bool, "enable_interact", enable_interact);
build_options.addOption(bool, "enable_outline", enable_outline);
build_options.addOption(bool, "enable_markers", enable_markers);
const build_options_module = build_options.createModule();
const hook_mod = b.dependency("hook", .{
@@ -45,16 +47,18 @@ pub fn build(b: *std.Build) void {
const build_all_step = b.step("all-variants", "Build all DLL variants");
// Helper to create a single-module build
inline for (&[_]struct { name: []const u8, screenshot: bool, interact: bool, outline: bool }{
.{ .name = "full", .screenshot = true, .interact = true, .outline = true },
.{ .name = "screenshot", .screenshot = true, .interact = false, .outline = false },
.{ .name = "interact", .screenshot = false, .interact = true, .outline = false },
.{ .name = "outline", .screenshot = false, .interact = false, .outline = true },
inline for (&[_]struct { name: []const u8, screenshot: bool, interact: bool, outline: bool, markers: bool }{
.{ .name = "full", .screenshot = true, .interact = true, .outline = true, .markers = true },
.{ .name = "screenshot", .screenshot = true, .interact = false, .outline = false, .markers = false },
.{ .name = "interact", .screenshot = false, .interact = true, .outline = false, .markers = false },
.{ .name = "outline", .screenshot = false, .interact = false, .outline = true, .markers = false },
.{ .name = "markers", .screenshot = false, .interact = false, .outline = false, .markers = true },
}) |variant| {
const opts = b.addOptions();
opts.addOption(bool, "enable_screenshot", variant.screenshot);
opts.addOption(bool, "enable_interact", variant.interact);
opts.addOption(bool, "enable_outline", variant.outline);
opts.addOption(bool, "enable_markers", variant.markers);
const variant_lib = b.addLibrary(.{
.name = variant.name,
+65
View File
@@ -0,0 +1,65 @@
//! Debug console — compiles out entirely in non-Debug builds.
//!
//! Usage from any module:
//! const con = @import("../console.zig"); // or appropriate relative path
//! con.print("[markers] loaded\n");
//! con.fmt("[markers] pos = {d}, {d}, {d}\n", .{ x, y, z });
const std = @import("std");
const builtin = @import("builtin");
const debug = builtin.mode == .Debug;
const WINAPI = std.builtin.CallingConvention.winapi;
const win = if (debug) struct {
extern "kernel32" fn AllocConsole() callconv(WINAPI) i32;
extern "kernel32" fn FreeConsole() callconv(WINAPI) i32;
extern "kernel32" fn SetConsoleTitleA(title: [*:0]const u8) callconv(WINAPI) i32;
extern "kernel32" fn GetStdHandle(nStdHandle: u32) callconv(WINAPI) ?*anyopaque;
extern "kernel32" fn WriteConsoleA(
hOut: *anyopaque,
buf: [*]const u8,
len: u32,
written: ?*u32,
reserved: ?*anyopaque,
) callconv(WINAPI) i32;
const STD_OUTPUT_HANDLE: u32 = 0xFFFFFFF5;
var handle: ?*anyopaque = null;
} else void;
pub fn init() void {
if (debug) {
_ = win.AllocConsole();
_ = win.SetConsoleTitleA("weirdutils");
win.handle = win.GetStdHandle(win.STD_OUTPUT_HANDLE);
print("[weirdutils] Console attached\n");
}
}
pub fn deinit() void {
if (debug) {
if (win.handle != null) {
print("[weirdutils] Detaching\n");
_ = win.FreeConsole();
win.handle = null;
}
}
}
pub fn print(msg: []const u8) void {
if (debug) {
if (win.handle) |h| {
_ = win.WriteConsoleA(h, msg.ptr, @intCast(msg.len), null, null);
}
}
}
pub fn fmt(comptime f: []const u8, args: anytype) void {
if (debug) {
var buf: [512]u8 = undefined;
const msg = std.fmt.bufPrint(&buf, f, args) catch return;
print(msg);
}
}
+5
View File
@@ -0,0 +1,5 @@
//! Framecrash fix module.
//!
//! Reference crash log: /media/bigfaststore/games/twmoa_1172/Errors/2026-02-25 19.38.01 Crash.txt
//We are finally going to solve: The instruction at "0x007A2452" referenced memory at "0x16C0FFE8".
+38
View File
@@ -1,17 +1,20 @@
const std = @import("std");
const hook = @import("hook");
pub const con = @import("console.zig");
// Build options for conditional module compilation
const build_opts = struct {
const screenshot = @import("build_options").enable_screenshot;
const interact = @import("build_options").enable_interact;
const outline = @import("build_options").enable_outline;
const markers = @import("build_options").enable_markers;
};
// Conditional module imports
const screenshot = if (build_opts.screenshot) @import("screenshot/screenshot.zig") else struct {};
const interact = if (build_opts.interact) @import("interact/interact.zig") else struct {};
const outline = if (build_opts.outline) @import("outline/api.zig") else struct {};
const markers = if (build_opts.markers) @import("markers/markers.zig") else struct {};
const WINAPI = std.builtin.CallingConvention.winapi;
const fc: std.builtin.CallingConvention = .{ .x86_fastcall = .{} };
@@ -243,6 +246,12 @@ fn registerLuaFunctions() void {
if (build_opts.outline) {
registerFunction("OutlineCommand", @intFromPtr(&outline.outlineCommand));
}
if (build_opts.markers) {
registerFunction("TestMarkerCreate", @intFromPtr(&markers.luaTestMarkerCreate));
registerFunction("TestMarkerDestroy", @intFromPtr(&markers.luaTestMarkerDestroy));
registerFunction("TestMarkerToggle", @intFromPtr(&markers.luaTestMarkerToggle));
registerFunction("GetPlayerPosition", @intFromPtr(&markers.luaGetPlayerPosition));
}
}
// =============================================================================
@@ -285,12 +294,25 @@ const outline_files = if (build_opts.outline) [_]FileEntry{
.{ .name = "Bindings.xml", .data = @embedFile("outline/addon/Bindings.xml") },
} else [_]FileEntry{};
const markers_files = if (build_opts.markers) [_]FileEntry{
.{ .name = "Markers.toc", .data = @embedFile("markers/addon/Markers.toc") },
.{ .name = "Markers.lua", .data = @embedFile("markers/addon/Markers.lua") },
.{ .name = "Bindings.xml", .data = @embedFile("markers/addon/Bindings.xml") },
} else [_]FileEntry{};
const markers_assets = if (build_opts.markers) [_]FileEntry{
.{ .name = "xyz.m2", .data = @embedFile("markers/assets/World/ArtTest/Boxtest/xyz.m2") },
.{ .name = "xyz.blp", .data = @embedFile("markers/assets/World/ArtTest/Boxtest/xyz.blp") },
} else [_]FileEntry{};
// All addon prefixes to check
const addon_prefixes = [_]AddonPrefix{
.{ .prefix = core_prefix, .files = &core_files },
.{ .prefix = "Interface\\AddOns\\Screenshot\\", .files = &screenshot_files },
.{ .prefix = "Interface\\AddOns\\Interact\\", .files = &interact_files },
.{ .prefix = "Interface\\AddOns\\Outline\\", .files = &outline_files },
.{ .prefix = "Interface\\AddOns\\Markers\\", .files = &markers_files },
.{ .prefix = "World\\ArtTest\\Boxtest\\", .files = &markers_assets },
};
fn findEmbeddedFile(path: [*:0]const u8) ?*const FileEntry {
@@ -357,6 +379,7 @@ fn loadFileDetour(
buf_out.* = buf;
if (size_out) |s| s.* = data_len;
con.fmt("[file] served embedded: {s} ({d} bytes)\n", .{ std.mem.span(path), data_len });
return 1;
}
@@ -435,6 +458,18 @@ fn loadAddonsDetour(error_handler: u32, _edx: u32) callconv(.c) void {
error_handler,
);
}
if (build_opts.markers) {
callLoadFileListWithIncludes(
"Interface\\AddOns\\Markers\\Markers.toc",
&md5ctx,
error_handler,
);
callLoadUIBindingsFromFile(
"Interface\\AddOns\\Markers\\Bindings.xml",
&md5ctx,
error_handler,
);
}
}
fn callOrigLoadAddons(error_handler: u32) void {
@@ -511,6 +546,8 @@ fn shutdownDetour() callconv(sc) void {
// =============================================================================
fn install() void {
con.init();
con.print("[weirdutils] Installing hooks\n");
_ = protection_hook.install(0x42a320, 6, @intFromPtr(&luaProtectionDetour), &.{});
_ = file_hook.install(0x648620, 6, @intFromPtr(&loadFileDetour), &.{});
_ = lsf_hook.install(0x490250, 6, @intFromPtr(&loadScriptFunctionsDetour), &.{1});
@@ -547,6 +584,7 @@ fn uninstall() void {
lsf_hook.remove();
file_hook.remove();
protection_hook.remove();
con.deinit();
}
// =============================================================================
+5
View File
@@ -0,0 +1,5 @@
<Bindings>
<Binding name="Toggle Test Marker" header="MARKERS">
TestMarkerToggle()
</Binding>
</Bindings>
+46
View File
@@ -0,0 +1,46 @@
-- Markers addon (embedded in DLL, loaded from memory)
-- Part of WeirdUtils - only loaded when markers module is compiled
MARKERS_VERSION = 1
BINDING_HEADER_MARKERS = "Markers"
local frame = CreateFrame("Frame")
frame:RegisterEvent("ADDON_LOADED")
frame:RegisterEvent("PLAYER_LOGIN")
frame:SetScript("OnEvent", function()
if event == "PLAYER_LOGIN" then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00Markers|r v" .. MARKERS_VERSION .. " loaded")
end
end)
SLASH_MARKERS1 = "/markers"
SLASH_MARKERS2 = "/mark"
SlashCmdList["MARKERS"] = function(msg)
msg = string.lower(msg or "")
if msg == "" or msg == "help" then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00Markers|r commands:")
DEFAULT_CHAT_FRAME:AddMessage(" /mark test - Toggle test marker at player position")
DEFAULT_CHAT_FRAME:AddMessage(" /mark pos - Show player position")
DEFAULT_CHAT_FRAME:AddMessage(" /mark on - Create test marker")
DEFAULT_CHAT_FRAME:AddMessage(" /mark off - Destroy test marker")
elseif msg == "test" or msg == "toggle" then
TestMarkerToggle()
elseif msg == "on" or msg == "create" then
TestMarkerCreate()
elseif msg == "off" or msg == "destroy" then
TestMarkerDestroy()
elseif msg == "pos" or msg == "position" then
local x, y, z = GetPlayerPosition()
DEFAULT_CHAT_FRAME:AddMessage(string.format("Position: %.2f, %.2f, %.2f", x, y, z))
else
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000Unknown command:|r " .. msg)
end
end
+8
View File
@@ -0,0 +1,8 @@
## Interface: 11200
## Title: Markers
## Notes: Client-side raid markers and indicators
## Version: 1.0
## Dependencies: WeirdUtils
Markers.lua
Bindings.xml
Binary file not shown.
Binary file not shown.
+271
View File
@@ -0,0 +1,271 @@
//! Client-side marker system using CreateGameObject_WithProperties
//!
//! Creates world objects at arbitrary positions for raid markers,
//! player indicators, etc.
const hook = @import("hook");
const o = @import("offsets.zig");
const wow = @import("../outline/wow.zig");
const con = @import("../console.zig");
// =============================================================================
// Position helpers
// =============================================================================
pub const Vec3 = struct {
x: f32,
y: f32,
z: f32,
};
/// Get unit position from movement struct
pub fn getUnitPosition(unit: u32) Vec3 {
if (unit == 0) return .{ .x = 0, .y = 0, .z = 0 };
const movement = hook.readMem(u32, unit + o.UNIT_MOVEMENT_OFFSET);
if (movement == 0 or movement < 0x10000) return .{ .x = 0, .y = 0, .z = 0 };
return .{
.x = hook.readMem(f32, movement + o.MOVEMENT_POS_X),
.y = hook.readMem(f32, movement + o.MOVEMENT_POS_Y),
.z = hook.readMem(f32, movement + o.MOVEMENT_POS_Z),
};
}
// =============================================================================
// GameObject creation - function pointer wrappers
// =============================================================================
/// CreateGameObject_WithProperties — __fastcall:
/// ECX = model, EDX = callback1
/// Stack (callee-clean, RET 0x14): callback2, x, y, z, flags
pub fn createGameObject(
model: ?*anyopaque,
callback1: ?*anyopaque,
callback2: ?*anyopaque,
x: f32,
y: f32,
z: f32,
flags: u32,
) ?*anyopaque {
// 5 stack params (callee cleans via RET 0x14)
const stack_args = [5]u32{
if (callback2) |c| @intCast(@intFromPtr(c)) else 0,
@as(u32, @bitCast(x)),
@as(u32, @bitCast(y)),
@as(u32, @bitCast(z)),
flags,
};
const result: u32 = asm volatile (
\\ push 16(%[a])
\\ push 12(%[a])
\\ push 8(%[a])
\\ push 4(%[a])
\\ push (%[a])
\\ call *%[func]
: [ret] "={eax}" (-> u32),
: [_] "{ecx}" (if (model) |m| @intFromPtr(m) else @as(usize, 0)),
[_] "{edx}" (if (callback1) |c| @intFromPtr(c) else @as(usize, 0)),
[a] "r" (&stack_args),
[func] "r" (o.FN_CREATE_GAMEOBJECT),
: .{ .memory = true, .cc = true }
);
return if (result != 0) @ptrFromInt(result) else null;
}
/// CleanupWorldObject(obj) - __thiscall: ECX = obj, 0 stack params
pub fn cleanupWorldObject(obj: *anyopaque) void {
asm volatile ("call *%[func]"
:
: [_] "{ecx}" (@intFromPtr(obj)),
[func] "r" (o.FN_CLEANUP_WORLD_OBJECT),
: .{ .eax = true, .edx = true, .memory = true, .cc = true }
);
}
/// Update object position directly
pub fn setObjectPosition(obj: *anyopaque, pos: Vec3) void {
const ptr: u32 = @intCast(@intFromPtr(obj));
@as(*f32, @ptrFromInt(ptr + o.OBJ_POS_X)).* = pos.x;
@as(*f32, @ptrFromInt(ptr + o.OBJ_POS_Y)).* = pos.y;
@as(*f32, @ptrFromInt(ptr + o.OBJ_POS_Z)).* = pos.z;
}
/// Set object alpha (0-255)
pub fn setObjectAlpha(obj: *anyopaque, alpha: u8) void {
const ptr: u32 = @intCast(@intFromPtr(obj));
// ARGB format - alpha in high byte
const color: u32 = (@as(u32, alpha) << 24) | 0x00189680;
@as(*u32, @ptrFromInt(ptr + o.OBJ_COLOR)).* = color;
}
// =============================================================================
// Animation
// =============================================================================
/// PlayAnimation(obj, animId) - __thiscall: ECX = obj, 1 stack param (callee-clean, RET 4)
pub fn playAnimation(obj: *anyopaque, anim_id: u32) void {
asm volatile (
\\push %[anim]
\\call *%[func]
:
: [_] "{ecx}" (@intFromPtr(obj)),
[anim] "r" (anim_id),
[func] "r" (o.FN_PLAY_ANIMATION),
: .{ .eax = true, .edx = true, .memory = true, .cc = true }
);
}
// =============================================================================
// Marker management
// =============================================================================
var test_marker: ?*anyopaque = null;
const MODEL_PATH: [*:0]const u8 = "World\\ArtTest\\Boxtest\\xyz.m2";
/// loadModelByName — __fastcall(ECX=path), returns model cache entry or null.
fn loadModel(path: [*:0]const u8) ?*anyopaque {
con.fmt("[markers] loadModelByName(\"{s}\")\n", .{@as([*:0]const u8, path)});
const result: u32 = asm volatile ("call *%[func]"
: [ret] "={eax}" (-> u32),
: [_] "{ecx}" (@intFromPtr(path)),
[func] "r" (o.FN_LOAD_MODEL_BY_NAME),
: .{ .edx = true, .memory = true, .cc = true }
);
if (result != 0) {
con.fmt("[markers] -> model at 0x{X:0>8}\n", .{result});
} else {
con.print("[markers] -> FAILED (returned null)\n");
}
return if (result != 0) @ptrFromInt(result) else null;
}
/// Create a test marker at player position
pub fn createTestMarker() ?*anyopaque {
const player = wow.getLocalPlayer();
if (player == 0) {
con.print("[markers] createTestMarker: no local player\n");
return null;
}
const pos = getUnitPosition(player);
con.fmt("[markers] player pos = {d:.1}, {d:.1}, {d:.1}\n", .{ pos.x, pos.y, pos.z });
if (pos.x == 0 and pos.y == 0 and pos.z == 0) return null;
const model = loadModel(MODEL_PATH);
con.fmt("[markers] createGameObject(model={?}, pos=({d:.1},{d:.1},{d:.1}))\n", .{
@as(?usize, if (model) |m| @intFromPtr(m) else null),
pos.x, pos.y, pos.z + 1.0,
});
const marker = createGameObject(model, null, null, pos.x, pos.y, pos.z + 1.0, 0);
if (marker) |m| {
con.fmt("[markers] -> object at 0x{X:0>8}\n", .{@intFromPtr(m)});
test_marker = m;
} else {
con.print("[markers] -> createGameObject FAILED\n");
}
return marker;
}
/// Destroy test marker
pub fn destroyTestMarker() void {
if (test_marker) |m| {
cleanupWorldObject(m);
test_marker = null;
}
}
/// Update test marker position to follow player
pub fn updateTestMarker() void {
if (test_marker == null) return;
const player = wow.getLocalPlayer();
if (player == 0) return;
const pos = getUnitPosition(player);
if (pos.x == 0 and pos.y == 0 and pos.z == 0) return;
setObjectPosition(test_marker.?, .{ .x = pos.x, .y = pos.y, .z = pos.z + 1.0 });
}
// =============================================================================
// Lua helpers
// =============================================================================
/// lua_pushnumber at 0x6F3810: __fastcall(L_ECX, double_on_stack). Callee cleans (RET 8).
fn luaPushNumber(L: u32, n: f64) void {
const raw: [2]u32 = @bitCast(n);
asm volatile (
\\push %[hi]
\\push %[lo]
\\call *%[func]
:
: [_] "{ecx}" (L),
[lo] "r" (raw[0]),
[hi] "r" (raw[1]),
[func] "r" (@as(u32, 0x6F3810)),
: .{ .eax = true, .edx = true, .memory = true, .cc = true }
);
}
// =============================================================================
// Lua API
// =============================================================================
/// Lua: TestMarkerCreate() - create marker at player position
pub fn luaTestMarkerCreate(L: u32) callconv(.c) u32 {
if (createTestMarker()) |_| {
hook.fastcall(void, 0x6F3890, L, @intFromPtr(@as([*:0]const u8, "Marker created")));
} else {
hook.fastcall(void, 0x6F3890, L, @intFromPtr(@as([*:0]const u8, "Failed to create marker")));
}
return 1;
}
/// Lua: TestMarkerDestroy() - destroy test marker
pub fn luaTestMarkerDestroy(L: u32) callconv(.c) u32 {
destroyTestMarker();
hook.fastcall(void, 0x6F3890, L, @intFromPtr(@as([*:0]const u8, "Marker destroyed")));
return 1;
}
/// Lua: TestMarkerToggle() - toggle marker on/off
pub fn luaTestMarkerToggle(L: u32) callconv(.c) u32 {
if (test_marker != null) {
destroyTestMarker();
hook.fastcall(void, 0x6F3890, L, @intFromPtr(@as([*:0]const u8, "Marker off")));
} else {
_ = createTestMarker();
hook.fastcall(void, 0x6F3890, L, @intFromPtr(@as([*:0]const u8, "Marker on")));
}
return 1;
}
/// Lua: local x, y, z = GetPlayerPosition()
pub fn luaGetPlayerPosition(L: u32) callconv(.c) u32 {
const player = wow.getLocalPlayer();
if (player == 0) return 0;
const pos = getUnitPosition(player);
luaPushNumber(L, @floatCast(pos.x));
luaPushNumber(L, @floatCast(pos.y));
luaPushNumber(L, @floatCast(pos.z));
return 3;
}
// =============================================================================
// Install hooks
// =============================================================================
pub fn installHooks() void {
// Nothing to hook yet - markers are created via Lua commands
}
pub fn removeHooks() void {
destroyTestMarker();
}
+96
View File
@@ -0,0 +1,96 @@
//! Offsets for marker/game object system
// =============================================================================
// Unit position
// =============================================================================
/// Unit + this → movement struct pointer
pub const UNIT_MOVEMENT_OFFSET: usize = 0x118;
/// Movement struct + this → X coordinate (float)
pub const MOVEMENT_POS_X: usize = 0x10;
/// Movement struct + this → Y coordinate (float)
pub const MOVEMENT_POS_Y: usize = 0x14;
/// Movement struct + this → Z coordinate (float)
pub const MOVEMENT_POS_Z: usize = 0x18;
// =============================================================================
// GameObject creation functions
// =============================================================================
/// CreateGameObject_WithProperties(model_ECX, callback1_EDX, callback2, x, y, z, flags)
/// __fastcall — ECX=model, EDX=callback1, 5 stack params. Callee cleans (RET 0x14).
/// Ghidra: 55 8B EC 83 EC 14 ... 89 4D FC (saves ECX) ... 89 55 F8 (saves EDX) ... C2 14 00.
pub const FN_CREATE_GAMEOBJECT: usize = 0x00670db0;
/// AllocateAndInitializeWorldObject(initializeFlag)
/// __fastcall returns void**
pub const FN_ALLOCATE_WORLD_OBJECT: usize = 0x006a0930;
/// CleanupWorldObject(object)
/// __thiscall
pub const FN_CLEANUP_WORLD_OBJECT: usize = 0x0069d730;
/// DestroyWorldObjectAndRelease(object)
/// __fastcall
pub const FN_DESTROY_WORLD_OBJECT: usize = 0x006a0a70;
// =============================================================================
// Model loading
// =============================================================================
/// loadModelByName(path)
/// __fastcall returns model cache entry
pub const FN_LOAD_MODEL_BY_NAME: usize = 0x006d4640;
/// createModelAttachment(resourceManager, path, flags)
/// __thiscall returns render context
pub const FN_CREATE_MODEL_ATTACHMENT: usize = 0x00707350;
// =============================================================================
// Animation control
// =============================================================================
/// PlayAnimation(object, animId)
/// __thiscall
pub const FN_PLAY_ANIMATION: usize = 0x0076cf50;
/// CM2Model__PlayBoneAnimation(model, boneIndex, animId, seqIndex, animData, speed, blend, queue)
/// __thiscall
pub const FN_PLAY_BONE_ANIMATION: usize = 0x007121a0;
/// HasAnimation(model, animId)
/// __thiscall returns bool
pub const FN_HAS_ANIMATION: usize = 0x00711960;
// =============================================================================
// Animation IDs (from string table)
// =============================================================================
pub const ANIM_SPAWN: u32 = 0; // TODO: find actual ID
pub const ANIM_DESPAWN: u32 = 0; // TODO: find actual ID
pub const ANIM_BIRTH: u32 = 0; // TODO: find actual ID
// =============================================================================
// Object structure offsets (from CreateGameObject_WithProperties analysis)
// =============================================================================
/// WorldObject + this → model pointer
pub const OBJ_MODEL: usize = 0x88;
/// WorldObject + this → position X (float*)
pub const OBJ_POS_X: usize = 0xC0;
/// WorldObject + this → position Y (float*)
pub const OBJ_POS_Y: usize = 0xC4;
/// WorldObject + this → position Z (float*)
pub const OBJ_POS_Z: usize = 0xC8;
/// WorldObject + this → color ARGB (alpha in high byte)
pub const OBJ_COLOR: usize = 0x24;
/// WorldObject + this → flags
pub const OBJ_FLAGS: usize = 0x90;
+5
View File
@@ -219,6 +219,11 @@ OutlineCommand("off") -- disable outlines
(mount is parent, rider is attached, gear is attached to rider?). All child models
(rider body, gear) should be included in the silhouette.
- ** MISC **: target outlines on players should be diff color than on mobs. Also when a mob and you are in melee range your outline should merge with his probably, so show you who you'te attacking.
Also we should shade targeted units not just outline them. And each outline and shade be a setting toggle
- **debug**: we should add to our debug mode coloring that colors each object type a different solid color, so that they issues arise it's easy to determine what object type is causing issues
## Future Architecture Notes
- **Hook "should render" function for model tracking**: the perfboost system uses a