multi-module cleanup: daily logs, luaalloc enable, minimap texture fix
- logsessions: switch from timestamped per-session files to daily rotation (WoWCombatLog_YYYY_MM_DD.txt), remove path pointer overwrites, persist paths across logout so redirects keep working - logging: add append file mode (openAppend) for daily log rotation - minimapicons: clear texture binding after blip render to fix stale texture with Silicon d3d9 layer - weirdperformance: enable luaalloc slab allocator (was disabled for testing) - worldmarkers: extract FN_SET_UNIT_POSITION to offsets, use lua.typeOf helper, update doc comments for renamed API - delete unused guidcache standalone test module
This commit is contained in:
@@ -1,84 +0,0 @@
|
||||
//! Standalone GUID cache module for isolated testing.
|
||||
//! Hooks FindObjectByGUID (0x464890) with a per-frame-flushed direct-mapped cache.
|
||||
|
||||
const hook = @import("zhook");
|
||||
const logging = @import("../logging.zig");
|
||||
const mod_mutex = @import("../mutex.zig");
|
||||
|
||||
pub const module_name: [*:0]const u8 = "guidcache";
|
||||
|
||||
var g_mutex: ?*anyopaque = null;
|
||||
var g_is_hook_owner: bool = false;
|
||||
var log: logging.Logger = .{};
|
||||
|
||||
const FindGuidFn = fn (u32, u32, u32, u32) callconv(hook.cc.fastcall) ?*anyopaque;
|
||||
var findguid_hook: hook.Detour(FindGuidFn) = .{};
|
||||
|
||||
const WorldUpdateFn = fn (u32) callconv(hook.cc.fastcall) void;
|
||||
var world_update_hook: hook.Detour(WorldUpdateFn) = .{};
|
||||
|
||||
var destroy_objmgr_hook: hook.Detour(fn () callconv(hook.cc.stdcall) void) = .{};
|
||||
|
||||
const GUID_CACHE_BITS = 12;
|
||||
const GUID_CACHE_SIZE = 1 << GUID_CACHE_BITS;
|
||||
const GUID_CACHE_MASK = GUID_CACHE_SIZE - 1;
|
||||
const GuidCacheEntry = struct { guid_lo: u32 = 0, guid_hi: u32 = 0, result: u32 = 0 };
|
||||
var guid_cache: [GUID_CACHE_SIZE]GuidCacheEntry = [_]GuidCacheEntry{.{}} ** GUID_CACHE_SIZE;
|
||||
|
||||
fn findguidDetour(a: u32, b: u32, c: u32, d: u32) callconv(hook.cc.fastcall) ?*anyopaque {
|
||||
const idx = (c ^ d) & GUID_CACHE_MASK;
|
||||
const entry = &guid_cache[idx];
|
||||
|
||||
if (entry.guid_lo == c and entry.guid_hi == d and entry.result != 0) {
|
||||
return @ptrFromInt(entry.result);
|
||||
}
|
||||
|
||||
const ret = findguid_hook.callOriginal(.{ a, b, c, d });
|
||||
const result = @intFromPtr(ret);
|
||||
if (result != 0) {
|
||||
entry.* = .{ .guid_lo = c, .guid_hi = d, .result = result };
|
||||
} else {
|
||||
entry.* = .{};
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
fn worldUpdateDetour(frame_count: u32) callconv(hook.cc.fastcall) void {
|
||||
guid_cache = [_]GuidCacheEntry{.{}} ** GUID_CACHE_SIZE;
|
||||
world_update_hook.callOriginal(.{frame_count});
|
||||
}
|
||||
|
||||
fn destroyObjMgrDetour() callconv(hook.cc.stdcall) void {
|
||||
guid_cache = [_]GuidCacheEntry{.{}} ** GUID_CACHE_SIZE;
|
||||
destroy_objmgr_hook.callOriginal(.{});
|
||||
}
|
||||
|
||||
pub fn isActive() bool {
|
||||
return g_is_hook_owner;
|
||||
}
|
||||
|
||||
pub fn installHooks() void {
|
||||
const result = mod_mutex.acquire(module_name);
|
||||
g_mutex = result.handle;
|
||||
g_is_hook_owner = result.is_owner;
|
||||
if (!g_is_hook_owner) return;
|
||||
|
||||
log = logging.Logger.open(module_name, .both);
|
||||
_ = findguid_hook.attach(0x464890, &findguidDetour);
|
||||
_ = world_update_hook.attach(0x482EA0, &worldUpdateDetour);
|
||||
_ = destroy_objmgr_hook.attach(0x467700, &destroyObjMgrDetour);
|
||||
log.print("guidcache: GUID lookup cache active\n");
|
||||
}
|
||||
|
||||
pub fn removeHooks() void {
|
||||
if (g_is_hook_owner) {
|
||||
findguid_hook.detach();
|
||||
world_update_hook.detach();
|
||||
destroy_objmgr_hook.detach();
|
||||
log.close();
|
||||
mod_mutex.release(&g_mutex);
|
||||
}
|
||||
g_is_hook_owner = false;
|
||||
}
|
||||
|
||||
pub fn onShutdown() void {}
|
||||
+19
-2
@@ -67,6 +67,9 @@ const win = if (debug) struct {
|
||||
const GENERIC_WRITE: u32 = 0x40000000;
|
||||
const FILE_SHARE_READ: u32 = 0x00000001;
|
||||
const CREATE_ALWAYS: u32 = 2;
|
||||
const OPEN_ALWAYS: u32 = 4;
|
||||
const FILE_END: u32 = 2;
|
||||
extern "kernel32" fn SetFilePointer(hFile: *anyopaque, lDistanceToMove: i32, lpDistanceToMoveHigh: ?*i32, dwMoveMethod: u32) callconv(WINAPI) u32;
|
||||
const FILE_ATTRIBUTE_NORMAL: u32 = 0x80;
|
||||
|
||||
var console_handle: ?*anyopaque = null;
|
||||
@@ -100,6 +103,7 @@ pub fn deinit() void {
|
||||
// =============================================================================
|
||||
|
||||
pub const Output = enum { console, file, both };
|
||||
pub const FileMode = enum { truncate, append };
|
||||
|
||||
// =============================================================================
|
||||
// Logger
|
||||
@@ -108,6 +112,7 @@ pub const Output = enum { console, file, both };
|
||||
pub const Logger = struct {
|
||||
file_handle: if (debug) ?*anyopaque else void = if (debug) null else {},
|
||||
default_dest: if (debug) Output else void = if (debug) .console else {},
|
||||
file_mode: if (debug) FileMode else void = if (debug) .truncate else {},
|
||||
/// Module name for lazy file creation. Null = no file output.
|
||||
file_name: if (debug) ?[*:0]const u8 else void = if (debug) null else {},
|
||||
/// Auto-prefix: "[name] " prepended to every log line.
|
||||
@@ -118,8 +123,16 @@ pub const Logger = struct {
|
||||
/// first write, so modules that never log to file produce no `.log` file.
|
||||
/// Pass null for no file (console-only even when dest is .both/.file).
|
||||
pub fn open(module_name: ?[*:0]const u8, default: Output) Logger {
|
||||
return openWithMode(module_name, default, .truncate);
|
||||
}
|
||||
|
||||
pub fn openAppend(module_name: ?[*:0]const u8, default: Output) Logger {
|
||||
return openWithMode(module_name, default, .append);
|
||||
}
|
||||
|
||||
pub fn openWithMode(module_name: ?[*:0]const u8, default: Output, mode: FileMode) Logger {
|
||||
if (debug) {
|
||||
var l: Logger = .{ .default_dest = default, .file_name = module_name };
|
||||
var l: Logger = .{ .default_dest = default, .file_name = module_name, .file_mode = mode };
|
||||
if (module_name) |name| {
|
||||
const span = std.mem.span(name);
|
||||
if (span.len + 3 <= l.prefix.len) {
|
||||
@@ -217,17 +230,21 @@ pub const Logger = struct {
|
||||
@memcpy(buf[0..span.len], span);
|
||||
@memcpy(buf[span.len..][0..4], ".log");
|
||||
buf[span.len + 4] = 0;
|
||||
const disposition = if (self.file_mode == .append) win.OPEN_ALWAYS else win.CREATE_ALWAYS;
|
||||
const fh = win.CreateFileA(
|
||||
@ptrCast(buf[0 .. span.len + 4 :0]),
|
||||
win.GENERIC_WRITE,
|
||||
win.FILE_SHARE_READ,
|
||||
null,
|
||||
win.CREATE_ALWAYS,
|
||||
disposition,
|
||||
win.FILE_ATTRIBUTE_NORMAL,
|
||||
null,
|
||||
);
|
||||
if (fh) |h| {
|
||||
if (@intFromPtr(h) != 0xFFFFFFFF) {
|
||||
if (self.file_mode == .append) {
|
||||
_ = win.SetFilePointer(h, 0, null, win.FILE_END);
|
||||
}
|
||||
self.file_handle = h;
|
||||
return h;
|
||||
}
|
||||
|
||||
+37
-101
@@ -1,12 +1,13 @@
|
||||
//! Log session rotation with per-character directories.
|
||||
//!
|
||||
//! Redirects combat log, raw combat log, and chat log to per-character
|
||||
//! directories: `Logs\<realm>\<character>\<Type>_<timestamp>_<PID>.txt`.
|
||||
//! directories: `Logs\<realm>\<character>\WoW<Type>Log_YYYY_MM_DD.txt`.
|
||||
//!
|
||||
//! Features:
|
||||
//! - Early path setup: hooks HandleCharacterSelection to resolve character/realm
|
||||
//! from the select screen data before world loading begins
|
||||
//! - Session continuation: reuses files modified < 60 min ago
|
||||
//! - Daily rotation: one log file per character per day, no timestamps
|
||||
//! - Session continuation: appends to today's file on each login (same character)
|
||||
//! - Session marker: writes `COMBATLOG_SESSION: <char> <realm>` on first combat write
|
||||
//!
|
||||
//! All DLL-side - no Lua addon needed.
|
||||
@@ -118,10 +119,6 @@ var g_raw_marker_written: bool = false;
|
||||
/// statically; SuperWoW passes it as handle_out.
|
||||
var g_raw_combat_handle_addr: u32 = 0;
|
||||
|
||||
/// Saved original path pointers for restoration.
|
||||
var g_original_combat_path_ptr: u32 = 0;
|
||||
var g_original_chat_path_ptr: u32 = 0;
|
||||
|
||||
// =============================================================================
|
||||
// Character / realm identity
|
||||
// =============================================================================
|
||||
@@ -196,88 +193,60 @@ fn setupSessionDir(realm: []const u8, char_name: []const u8) bool {
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Session continuation - find recent file to reuse
|
||||
// Daily log file lookup
|
||||
// =============================================================================
|
||||
|
||||
/// Scan directory for files matching `<prefix>_*.txt`, return the newest if
|
||||
/// modified within 60 minutes. Writes full path into result_buf, returns length.
|
||||
fn findRecentFile(prefix: []const u8, result_buf: *[260]u8) ?usize {
|
||||
/// Scan directory for today's daily log file matching `<prefix>_YYYY_MM_DD.txt`.
|
||||
/// Returns full path if found, null if not.
|
||||
fn findDailyFile(prefix: []const u8, result_buf: *[260]u8) ?usize {
|
||||
if (g_dir_path_len == 0) return null;
|
||||
|
||||
// Build search pattern: <dir><prefix>_*.txt
|
||||
var search_buf: [300]u8 = undefined;
|
||||
const pattern = std.fmt.bufPrint(&search_buf, "{s}{s}_*.txt", .{
|
||||
g_dir_path[0..g_dir_path_len],
|
||||
var st: SYSTEMTIME = undefined;
|
||||
GetLocalTime(&st);
|
||||
|
||||
// Build exact filename: <dir><prefix>_YYYY_MM_DD.txt
|
||||
var filename_buf: [260]u8 = undefined;
|
||||
const filename = std.fmt.bufPrint(&filename_buf, "{s}_{d:0>4}_{d:0>2}_{d:0>2}.txt", .{
|
||||
prefix,
|
||||
st.wYear,
|
||||
st.wMonth,
|
||||
st.wDay,
|
||||
}) catch return null;
|
||||
search_buf[pattern.len] = 0;
|
||||
|
||||
// Build full path: <dir><filename>
|
||||
const total_len = g_dir_path_len + filename.len;
|
||||
if (total_len >= result_buf.len) return null;
|
||||
@memcpy(result_buf[0..g_dir_path_len], g_dir_path[0..g_dir_path_len]);
|
||||
@memcpy(result_buf[g_dir_path_len..total_len], filename);
|
||||
result_buf[total_len] = 0;
|
||||
|
||||
// Check if file exists
|
||||
var find_data: WIN32_FIND_DATAA = undefined;
|
||||
const find_handle = FindFirstFileA(@ptrCast(search_buf[0..pattern.len :0]), &find_data);
|
||||
const find_handle = FindFirstFileA(@ptrCast(result_buf[0..total_len :0]), &find_data);
|
||||
if (find_handle == INVALID_HANDLE_VALUE) return null;
|
||||
defer _ = FindClose(find_handle);
|
||||
_ = FindClose(find_handle);
|
||||
|
||||
var newest_time: u64 = 0;
|
||||
var newest_name: [260]u8 = undefined;
|
||||
var newest_name_len: usize = 0;
|
||||
|
||||
// Iterate all matching files, track the newest by write time
|
||||
var has_result: bool = true;
|
||||
while (has_result) {
|
||||
const ft: u64 = @bitCast(find_data.ftLastWriteTime);
|
||||
if (ft > newest_time) {
|
||||
newest_time = ft;
|
||||
const name_len = std.mem.indexOfScalar(u8, &find_data.cFileName, 0) orelse 0;
|
||||
if (name_len > 0) {
|
||||
newest_name_len = name_len;
|
||||
@memcpy(newest_name[0..name_len], find_data.cFileName[0..name_len]);
|
||||
}
|
||||
}
|
||||
has_result = FindNextFileA(find_handle, &find_data) != 0;
|
||||
}
|
||||
|
||||
if (newest_name_len == 0) return null;
|
||||
|
||||
// Compare against current time - both UTC FILETIME (100ns units)
|
||||
var current_ft: FILETIME = undefined;
|
||||
GetSystemTimeAsFileTime(¤t_ft);
|
||||
const current: u64 = @bitCast(current_ft);
|
||||
const threshold: u64 = 60 * 60 * 10_000_000; // 60 minutes
|
||||
|
||||
if (current > newest_time and (current - newest_time) < threshold) {
|
||||
// Build full path: dir + filename
|
||||
const total_len = g_dir_path_len + newest_name_len;
|
||||
if (total_len >= result_buf.len) return null;
|
||||
@memcpy(result_buf[0..g_dir_path_len], g_dir_path[0..g_dir_path_len]);
|
||||
@memcpy(result_buf[g_dir_path_len..total_len], newest_name[0..newest_name_len]);
|
||||
result_buf[total_len] = 0;
|
||||
return total_len;
|
||||
}
|
||||
|
||||
return null;
|
||||
return total_len;
|
||||
}
|
||||
|
||||
/// Resolve a log file path: reuse recent file or generate new timestamped name.
|
||||
fn resolveLogPath(prefix: []const u8, result_buf: *[260]u8) usize {
|
||||
// Try to reuse a recent file (modified < 60 min ago)
|
||||
if (findRecentFile(prefix, result_buf)) |len| {
|
||||
/// Resolve a log file path: reuse today's daily file or create new one.
|
||||
fn resolveDailyLogPath(prefix: []const u8, result_buf: *[260]u8) usize {
|
||||
// Try to find today's daily file
|
||||
if (findDailyFile(prefix, result_buf)) |len| {
|
||||
log.fmt("reusing: {s}\n", .{result_buf[0..len]});
|
||||
return len;
|
||||
}
|
||||
|
||||
// Generate new timestamped filename
|
||||
// Generate new daily filename: <dir><prefix>_YYYY_MM_DD.txt
|
||||
var st: SYSTEMTIME = undefined;
|
||||
GetLocalTime(&st);
|
||||
|
||||
const path = std.fmt.bufPrint(result_buf, "{s}{s}_{d:0>4}{d:0>2}{d:0>2}_{d:0>2}{d:0>2}{d:0>2}.txt", .{
|
||||
const path = std.fmt.bufPrint(result_buf, "{s}{s}_{d:0>4}_{d:0>2}_{d:0>2}.txt", .{
|
||||
g_dir_path[0..g_dir_path_len],
|
||||
prefix,
|
||||
st.wYear,
|
||||
st.wMonth,
|
||||
st.wDay,
|
||||
st.wHour,
|
||||
st.wMinute,
|
||||
st.wSecond,
|
||||
}) catch return 0;
|
||||
result_buf[path.len] = 0;
|
||||
log.fmt("new: {s}\n", .{path});
|
||||
@@ -310,22 +279,9 @@ fn configureSession(char_span: []const u8, realm_span: []const u8) void {
|
||||
}
|
||||
|
||||
// Resolve paths for all three log types
|
||||
g_combat_path_len = resolveLogPath("WoWCombatLog", &g_combat_path);
|
||||
g_raw_combat_path_len = resolveLogPath("WoWRawCombatLog", &g_raw_combat_path);
|
||||
g_chat_path_len = resolveLogPath("WoWChatLog", &g_chat_path);
|
||||
|
||||
// Belt-and-suspenders: overwrite path pointer table for game code paths
|
||||
// that read the table directly before calling InitializeLogBuffer.
|
||||
if (g_combat_path_len > 0) {
|
||||
g_original_combat_path_ptr = hook.readMem(u32, o.COMBAT_LOG_PATH_PTR);
|
||||
const ptr_bytes: [4]u8 = @bitCast(@intFromPtr(&g_combat_path));
|
||||
hook.writeMem(o.COMBAT_LOG_PATH_PTR, &ptr_bytes);
|
||||
}
|
||||
if (g_chat_path_len > 0) {
|
||||
g_original_chat_path_ptr = hook.readMem(u32, o.CHAT_LOG_PATH_PTR);
|
||||
const ptr_bytes: [4]u8 = @bitCast(@intFromPtr(&g_chat_path));
|
||||
hook.writeMem(o.CHAT_LOG_PATH_PTR, &ptr_bytes);
|
||||
}
|
||||
g_combat_path_len = resolveDailyLogPath("WoWCombatLog", &g_combat_path);
|
||||
g_raw_combat_path_len = resolveDailyLogPath("WoWRawCombatLog", &g_raw_combat_path);
|
||||
g_chat_path_len = resolveDailyLogPath("WoWChatLog", &g_chat_path);
|
||||
|
||||
g_paths_configured = true;
|
||||
}
|
||||
@@ -356,19 +312,6 @@ fn enterWorldDetour() callconv(hook.cc.stdcall) void {
|
||||
enter_world_hook.callOriginal(.{});
|
||||
}
|
||||
|
||||
fn restorePathPointers() void {
|
||||
if (g_original_combat_path_ptr != 0) {
|
||||
const ptr_bytes: [4]u8 = @bitCast(g_original_combat_path_ptr);
|
||||
hook.writeMem(o.COMBAT_LOG_PATH_PTR, &ptr_bytes);
|
||||
g_original_combat_path_ptr = 0;
|
||||
}
|
||||
if (g_original_chat_path_ptr != 0) {
|
||||
const ptr_bytes: [4]u8 = @bitCast(g_original_chat_path_ptr);
|
||||
hook.writeMem(o.CHAT_LOG_PATH_PTR, &ptr_bytes);
|
||||
g_original_chat_path_ptr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// InitializeLogBuffer hook - lazy setup + path redirect
|
||||
// =============================================================================
|
||||
@@ -473,14 +416,9 @@ pub fn onShutdown() void {
|
||||
g_chat_marker_written = false;
|
||||
g_raw_marker_written = false;
|
||||
g_raw_combat_handle_addr = 0;
|
||||
g_combat_path_len = 0;
|
||||
g_raw_combat_path_len = 0;
|
||||
g_chat_path_len = 0;
|
||||
g_dir_path_len = 0;
|
||||
// Keep paths set so initLogDetour keeps redirecting on next login
|
||||
g_session_char_len = 0;
|
||||
g_session_realm_len = 0;
|
||||
// Restore original path pointers so next session starts clean
|
||||
restorePathPointers();
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -512,7 +450,6 @@ pub fn luaGetChatLogPath(L: lua.State) callconv(.c) u32 {
|
||||
// =============================================================================
|
||||
|
||||
pub fn installHooks() void {
|
||||
|
||||
const result = mod_mutex.acquire(module_name);
|
||||
g_mutex = result.handle;
|
||||
g_is_hook_owner = result.is_owner;
|
||||
@@ -546,7 +483,6 @@ pub fn removeHooks() void {
|
||||
write_log_hook.detach();
|
||||
init_log_hook.detach();
|
||||
enter_world_hook.detach();
|
||||
restorePathPointers();
|
||||
log.close();
|
||||
mod_mutex.release(&g_mutex);
|
||||
}
|
||||
|
||||
@@ -971,6 +971,11 @@ fn renderObjectBlipsDetour(thisptr: u32, _edx: u32, dn_info: u32) callconv(hook.
|
||||
drawMinimapBlip(blips[i].pos, blips[i].blip.scale);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear texture binding so downstream renders don't use our stale texture.
|
||||
// Silicon's d3d9 translation layer does not reset this between render passes.
|
||||
const gxRsSet: *const fn (u32, u32) callconv(hook.cc.fastcall) void = @ptrFromInt(ADDR.GxRsSet);
|
||||
gxRsSet(23, 0);
|
||||
}
|
||||
|
||||
// ClntObjMgrEnumVisibleObjects: __fastcall(callback ECX, context EDX)
|
||||
|
||||
@@ -327,7 +327,7 @@ pub fn installHooks() void {
|
||||
if (inflate_hook.install()) installed += 1;
|
||||
|
||||
// Lua slab allocator replacement
|
||||
// installed += luaalloc.install(); // disabled for testing
|
||||
installed += luaalloc.install();
|
||||
|
||||
// GC phase profiler
|
||||
installed += luagc.install();
|
||||
|
||||
@@ -100,3 +100,10 @@ pub const LEADER_GUID: usize = 0x00bc75f8;
|
||||
pub const RAID_ROSTER_ARRAY: usize = 0x00b712a8;
|
||||
pub const RAID_MEMBER_COUNT: usize = 0x00b713e0;
|
||||
pub const ROSTER_ENTRY_RANK: usize = 0x0C;
|
||||
|
||||
// =============================================================================
|
||||
// Transform and position (additional)
|
||||
// =============================================================================
|
||||
|
||||
/// SetUnitPositionAndOrientation - __fastcall(ECX=entity, EDX=pos), 1 stack param (facing).
|
||||
pub const FN_SET_UNIT_POSITION: usize = 0x00698e20;
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
//! WorldMarker(index) - place marker at cursor terrain position
|
||||
//! ClearWorldMarker(index) - remove specific marker (1-5)
|
||||
//! ClearWorldMarker() - remove all markers
|
||||
//! CanSetWorldMarkers() - returns 1 if leader/assist, nil otherwise
|
||||
//! GetWorldMarker(index) - returns x, y, z, areaId or nil
|
||||
//! CanSetWorldMarker() - returns 1 if leader/assist, nil otherwise
|
||||
//!
|
||||
//! Lua API (WorldMarkers table - internal, used by addon):
|
||||
//! WorldMarkers.SetMarkerDef(i, x, y, z, area, sender)
|
||||
//! WorldMarkers.ClearMarkerDef([index,] sender)
|
||||
//! WorldMarkers.GetMarkerDef(index) - returns x, y, z, areaId or nil
|
||||
//! WorldMarkers.SetMarkerSync(i, x, y, z, area, sender)
|
||||
//! WorldMarkers.ClearMarkerSync([index,] sender)
|
||||
|
||||
const std = @import("std");
|
||||
const hook = @import("zhook");
|
||||
@@ -388,7 +388,7 @@ fn spawnEntity(index: usize, pos: Vec3) bool {
|
||||
|
||||
/// SetUnitPositionAndOrientation - __fastcall(ECX=positionData, EDX=pos), 1 stack param.
|
||||
fn setUnitPositionAndOrientation(entity: *anyopaque, pos: *[3]f32, facing: f32) void {
|
||||
hook.call(fn (*anyopaque, *[3]f32, f32) callconv(hook.cc.fastcall) void, 0x698e20, .{ entity, pos, facing });
|
||||
hook.call(fn (*anyopaque, *[3]f32, f32) callconv(hook.cc.fastcall) void, o.FN_SET_UNIT_POSITION, .{ entity, pos, facing });
|
||||
}
|
||||
|
||||
/// Remove only the live entity for a marker slot (def untouched).
|
||||
@@ -504,9 +504,7 @@ pub fn luaClearWorldMarker(L: lua.State) callconv(.c) u32 {
|
||||
}
|
||||
|
||||
const nargs = lua.gettop(L);
|
||||
const lua_type: *const fn (lua.State, i32) callconv(hook.cc.fastcall) i32 = @ptrFromInt(0x6F3400);
|
||||
|
||||
if (nargs == 0 or lua_type(L, 1) == 0) {
|
||||
if (nargs == 0 or lua.typeOf(L, 1) == 0) {
|
||||
// No args or nil - clear all
|
||||
clearAllMarkers();
|
||||
lua.pushnumber(L, 1.0);
|
||||
|
||||
Reference in New Issue
Block a user