Add vendor subcategories with locale-aware filtering and ammo tracking
Heap-allocated pipe-delimited subname filters for vendor subcategories: Poison, Reagent, Trade Goods, Ammunition — each with 6-locale support. Fix same-priority same-flag classification to first-match-wins ordering. Rename Vendor to General Vendor, fix Reagent icon path to Reagents.blp.
This commit is contained in:
@@ -21,9 +21,66 @@ local SPELL_CATEGORIES = {
|
||||
}},
|
||||
}
|
||||
|
||||
-- =============================================================================
|
||||
-- Locale-aware vendor subname filters (pipe-delimited exact subnames)
|
||||
-- DLL matches ANY pipe segment as a substring against the NPC's subname.
|
||||
-- English subnames always included as fallback for unlocalized custom NPCs.
|
||||
-- =============================================================================
|
||||
|
||||
-- Build a pipe-delimited filter: locale-specific first, then enUS as fallback.
|
||||
local function getLocaleFilter(filters)
|
||||
local locale = GetLocale and GetLocale() or "enUS"
|
||||
local extra = locale ~= "enUS" and filters[locale] or nil
|
||||
if extra then
|
||||
return extra .. "|" .. filters.enUS
|
||||
end
|
||||
return filters.enUS
|
||||
end
|
||||
|
||||
-- Reagent vendors: NPCs that sell class reagents (Arcane Powder, Ankh, Candles, Seeds, etc.)
|
||||
-- enUS = base subnames (always included); other locales = additional localized subnames only.
|
||||
local REAGENT_FILTERS = {
|
||||
enUS = "Apprentice Witch Doctor|Arcane Goods|Arcane Goods Vendor|Arcane Trinkets Vendor|Ered Ruin|Exotic Reagent Merchant|Lorekeeper|Poisons & Reagents|Potions, Scrolls and Reagents|Reagent Supplier|Reagent Supplies|Reagent Vendor|Reagents|Reagents & Poisons|Reagents and Herbs|Reagents Vendor|Scrolls & Potions|Voodoo Hexxer",
|
||||
frFR = "Apprentie sorcier-docteur|Fournitures arcaniques|Alchimie & composants|Marchande de bijoux magiques|Marchand de r\195\169actifs exotiques|Gardien du savoir|Poisons & composants|Potions, Parchemins & Composants|Composants|Composants & fournitures pour poisons|Composants, herbes & fournitures pour poisons",
|
||||
deDE = "Hexendoktorlehrling|Arkanarien|Alchemiebedarf & Reagenzien|Verk\195\164uferin arkaner Gegenst\195\164nde|H\195\164ndler f\195\188r exotische Reagenzien|Wissensh\195\188ter|Gifte & Reagenzien|Druidenlehrerin|Reagenzien|Reagenzienbedarf|Reagenzien & Gifte|Reagenzien, Kr\195\164uter & Gifte",
|
||||
zhCN = "\232\167\129\228\185\160\229\183\171\229\140\187|\233\173\148\230\179\149\232\180\167\231\137\169|\231\130\188\233\135\145\230\156\175\229\146\140\230\157\144\230\150\153\228\190\155\229\186\148\229\149\134|\233\173\148\230\179\149\232\180\167\231\137\169\229\149\134\228\186\186|\233\173\148\230\179\149\233\165\176\229\147\129\229\149\134\228\186\186|\229\159\131\233\155\183\230\157\156\229\155\160|\231\137\185\230\174\138\230\157\144\230\150\153\229\149\134\228\186\186|\229\141\154\229\173\166\232\128\133|\230\175\146\232\141\175\229\146\140\230\157\144\230\150\153|\232\141\175\229\137\130\227\128\129\229\141\183\232\189\180\229\146\140\230\157\144\230\150\153|\232\175\149\232\141\175\228\190\155\229\186\148\229\149\134|\230\150\189\230\179\149\230\157\144\230\150\153\228\190\155\229\186\148\229\149\134|\230\150\189\230\179\149\230\157\144\230\150\153\229\149\134|\230\157\144\230\150\153\229\149\134|\230\150\189\230\179\149\230\157\144\230\150\153|\230\157\144\230\150\153\228\184\142\230\175\146\232\141\175\229\149\134|\232\176\131\233\133\146\229\184\136|\230\150\189\230\179\149\230\157\144\230\150\153\229\146\140\232\141\175\232\141\137\229\149\134|\229\141\183\232\189\180\229\146\140\232\141\175\229\137\130|\229\183\171\230\175\146\229\166\150\230\156\175\229\184\136",
|
||||
esES = "Aprendiza m\195\169dico brujo|Art\195\173culos Arcanos|Suministros de alquimia y componentes|Vendedora de abalorios Arcanos|Mercader de componentes ex\195\179ticos|Tradicionalista|Venenos y componentes|Instructora de druidas|Componentes|Suministros de componentes|Suministros de venenos y componentes|Componentes, hierbas y suministros de venenos",
|
||||
ruRU = "\208\163\209\135\208\181\208\189\208\184\209\134\208\176 \208\183\208\189\208\176\209\133\208\176\209\128\209\143|\208\167\208\176\209\128\208\190\208\180\208\181\208\185\209\129\208\186\208\184\208\181 \209\130\208\190\208\178\208\176\209\128\209\139|\208\162\208\190\208\178\208\176\209\128\209\139 \208\180\208\187\209\143 \208\176\208\187\209\133\208\184\208\188\208\184\208\186\208\176 \208\184 \209\128\208\181\208\176\208\179\208\181\208\189\209\130\209\139|\208\159\209\128\208\190\208\180\208\176\208\178\208\181\209\134 \209\135\208\176\209\128\208\190\208\180\208\181\208\185\209\129\208\186\208\184\209\133 \208\176\208\186\209\129\208\181\209\129\209\129\209\131\208\176\209\128\208\190\208\178|\208\162\208\190\209\128\208\179\208\190\208\178\208\181\209\134 \209\141\208\186\208\183\208\190\209\130\208\184\209\135\208\181\209\129\208\186\208\184\208\188\208\184 \209\128\208\181\208\176\208\179\208\181\208\189\209\130\208\176\208\188\208\184|\208\161\208\186\208\176\208\183\208\184\209\130\208\181\208\187\209\140|\208\175\208\180\209\139 \208\184 \209\128\208\181\208\176\208\179\208\181\208\189\209\130\209\139|\208\151\208\181\208\187\209\140\209\143, \209\129\208\178\208\184\209\130\208\186\208\184 \208\184 \209\128\208\181\208\176\208\179\208\181\208\189\209\130\209\139|\208\160\208\181\208\176\208\179\208\181\208\189\209\130\209\139|\208\160\208\181\208\176\208\179\208\181\208\189\209\130\209\139 \208\184 \209\143\208\180\209\139|\208\160\208\181\208\176\208\179\208\181\208\189\209\130\209\139, \209\130\209\128\208\176\208\178\209\139 \208\184 \209\143\208\180\209\139",
|
||||
}
|
||||
|
||||
-- Trade goods vendors: NPCs that sell thread, dye, flux, tools, etc.
|
||||
local TRADE_FILTERS = {
|
||||
enUS = "Druid of the Claw|East Oceanic Trading Co|Farmers Market|Flaxwhisker Front|General Trade Goods Merchant|General Trade Goods Vendor|General Trade Supplier|Get Rich Or Die Grinding|Harborage Supplies|Hard Chores|Hard Knocks Society|Schmetterlingsbrigade|SQUAD|Superior Tradesman|Thunderhorn Clan|Trade Goods|Trade Goods Supplier|Trade Goods Supplies|Trade Supplier|Trade Supplies",
|
||||
frFR = "Fournitures d'artisanat|Excellent travailleur du cuir|Excellent commer\195\167ant",
|
||||
deDE = "Handwerkswaren|\195\156berragender Lederer|\195\156berragender Handwerker",
|
||||
zhCN = "\229\136\169\231\136\170\229\190\183\233\178\129\228\188\138|\229\134\156\232\180\184\229\184\130\229\156\186|\229\149\134\228\186\186|\230\157\130\232\180\167\228\190\155\229\186\148\229\149\134|\232\180\184\230\152\147\229\149\134\228\186\186|\230\184\148\229\133\183\228\190\155\229\186\148\229\149\134|\228\184\141\229\175\140\229\176\177\232\130\157|\233\171\152\231\186\167\231\154\174\229\140\160|\232\137\176\232\139\166\231\154\132\229\174\182\229\138\161\230\180\187|\231\161\172\229\135\187\229\141\143\228\188\154|\230\150\189\230\162\133\231\137\185\230\158\151\230\151\133|\229\176\143\231\187\132|\233\171\152\231\186\167\229\149\134\228\186\186|\233\155\183\232\167\146\230\176\143\230\151\143|\229\149\134\229\147\129\228\190\155\229\186\148\229\149\134|\232\180\184\230\152\147\228\190\155\229\186\148\229\149\134",
|
||||
esES = "Suministros comerciales|Peletero superior|Mercader superior|Objetos comerciables",
|
||||
ruRU = "\208\165\208\190\208\183\209\143\208\185\209\129\209\130\208\178\208\181\208\189\208\189\209\139\208\181 \208\191\209\128\208\184\208\191\208\176\209\129\209\139|\208\158\208\191\209\139\209\130\208\189\209\139\208\185 \208\186\208\190\208\182\208\181\208\178\208\189\208\184\208\186|\208\158\208\191\209\139\209\130\208\189\209\139\208\185 \209\130\208\190\209\128\208\179\208\190\208\178\208\181\209\134|\208\165\208\190\208\183\209\143\208\185\209\129\209\130\208\178\208\181\208\189\208\189\209\139\208\181 \209\130\208\190\208\178\208\176\209\128\209\139",
|
||||
}
|
||||
|
||||
-- Poison vendors: NPCs that sell poison reagents (Flash Powder, Deathweed, Thieves' Tools, etc.)
|
||||
local POISON_FILTERS = {
|
||||
enUS = "Ered Ruin|Poison Supplier|Poison Supplies|Poison Vendor|Poisons & Reagents|Reagents & Poisons|Shady Dealer|Shady Goods|Tools & Supplies",
|
||||
frFR = "Fournitures pour poisons|Poisons & composants|Composants & fournitures pour poisons|Marchand douteux|Fournitures douteuses|Outils & fournitures",
|
||||
deDE = "Gifte|Gifte & Reagenzien|Reagenzien & Gifte|Zwielichtiger H\195\164ndler|Zweifelhafte Waren|Giftverk\195\164ufer",
|
||||
zhCN = "\229\159\131\233\155\183\230\157\156\229\155\160|\230\175\146\232\141\175\229\149\134|\230\175\146\232\141\175\229\146\140\230\157\144\230\150\153|\230\157\144\230\150\153\228\184\142\230\175\146\232\141\175\229\149\134|\232\176\131\233\133\146\229\184\136|\229\183\165\229\133\183\229\146\140\232\161\165\231\187\153\229\147\129",
|
||||
esES = "Suministros de venenos|Venenos y componentes|Suministros de venenos y componentes|Vendedor sospechoso|Art\195\173culos sospechosos|Vendedor de venenos",
|
||||
ruRU = "\208\162\208\190\209\128\208\179\208\190\208\178\208\181\209\134 \209\143\208\180\208\176\208\188\208\184|\208\175\208\180\209\139 \208\184 \209\128\208\181\208\176\208\179\208\181\208\189\209\130\209\139|\208\160\208\181\208\176\208\179\208\181\208\189\209\130\209\139 \208\184 \209\143\208\180\209\139|\208\161\208\190\208\188\208\189\208\184\209\130\208\181\208\187\209\140\208\189\209\139\208\185 \208\180\208\181\208\187\208\181\209\134|\208\161\208\190\208\188\208\189\208\184\209\130\208\181\208\187\209\140\208\189\209\139\208\181 \209\130\208\190\208\178\208\176\209\128\209\139|\208\152\208\189\209\129\209\130\209\128\209\131\208\188\208\181\208\189\209\130\209\139 \208\184 \208\191\209\128\208\184\208\191\208\176\209\129\209\139",
|
||||
}
|
||||
|
||||
-- Ammunition vendors: NPCs that sell arrows, bullets, and shot
|
||||
local AMMO_FILTERS = {
|
||||
enUS = "Ammunition|Ammunition Vendor|Bow & Arrow Merchant|Bow & Gun Merchant|Bow Merchant|Bowyer|Bowyer & Fletching Goods|Bowyer & Gunsmith|Fletcher|Gun Merchant|Guns and Ammo|Guns and Ammo Merchant|Guns Merchant|Guns Vendor|Gunsmith|Gunsmith & Bowyer|Superior Bowyer|Weaponsmith & Gunsmith",
|
||||
frFR = "Vendeur de munitions|Marchand d'arcs et de fl\195\168ches|Marchande d'arcs & de fusils|Marchand d'arcs|Marchande d'arcs|Fabricant d'arcs|Fabricante d'arcs|Fournitures pour arcs et fl\195\168ches|Fabricant d'arcs & armes \195\160 feu|Marchand d'armes \195\160 feu|Marchande d'armes \195\160 feu|Armes \195\160 feu & munitions|Fabricant d'armes \195\160 feu|Fabricante d'armes \195\160 feu|Excellent fabricant d'arcs|Fabricant d'armes & de fusils",
|
||||
deDE = "Bogenh\195\164ndler|Bogenh\195\164ndlerin|Bogen- & Schusswaffenh\195\164ndlerin|Bogenmacher|J\195\164gerlehrer & Bogenmacher|Bogenmacherin|Bogen- & Pfeilmacherbedarf|Bogen- & B\195\188chsenmacher|Schusswaffenh\195\164ndler|Schusswaffenh\195\164ndlerin|Verk\195\164uferin f\195\188r B\195\182gen & Gewehre|Schusswaffenverk\195\164ufer|B\195\188chsenmacher|B\195\188chsenmacherin|\195\156berragender Bogenmacher|Waffenschmied & B\195\188chsenmacher",
|
||||
zhCN = "\229\188\185\232\141\175|\229\188\185\232\141\175\229\149\134\228\186\186|\229\188\132\231\174\173\229\149\134|\229\188\132\231\174\173\229\146\140\230\158\170\230\162\176|\229\188\132\231\174\173\229\149\134\228\186\186|\233\128\160\231\174\173\229\184\136|\233\128\160\229\137\170\229\184\136|\230\158\170\230\162\176\229\149\134|\230\158\170\230\162\176\229\149\134\228\186\186|\230\158\170\230\162\176\229\146\140\229\188\185\232\141\175\229\149\134|\230\158\170\230\162\176\229\146\140\229\136\182\229\188\132\229\149\134|\233\171\152\231\186\167\229\136\182\229\188\132\229\184\136|\230\173\166\229\153\168\233\148\187\233\128\160\229\146\140\230\158\170\230\162\176\229\149\134",
|
||||
esES = "Mercader de arcos|Mercader de arcos y armas de fuego|Fabricante de arcos|Instructor de cazadores y arquero|Arcos, flechas y otros art\195\173culos de tiro con arco|Fabricante de arcos y forjador de armas de fuego|Mercader de armas de fuego|Vendedora de arcos y rifles|Vendedor de armas de fuego|Forjador de armas de fuego|Forjadora de armas de fuego|Forjador de armas de fuego y fabricante de arcos|Fabricante de arcos superior|Forjador de armas de fuego y armas en general",
|
||||
ruRU = "\208\159\209\128\208\190\208\180\208\176\208\178\208\181\209\134 \208\177\208\190\208\181\208\191\209\128\208\184\208\191\208\176\209\129\208\190\208\178|\208\162\208\190\209\128\208\179\208\190\208\178\208\181\209\134 \208\187\209\131\208\186\208\176\208\188\208\184 \208\184 \209\129\209\130\209\128\208\181\208\187\208\176\208\188\208\184|\208\162\208\190\209\128\208\179\208\190\208\178\208\181\209\134 \208\187\209\131\208\186\208\176\208\188\208\184 \208\184 \209\128\209\131\208\182\209\140\209\143\208\188\208\184|\208\162\208\190\209\128\208\179\208\190\208\178\208\181\209\134 \208\187\209\131\208\186\208\176\208\188\208\184|\208\162\208\190\209\128\208\179\208\190\208\178\208\181\209\134 \209\129\209\130\209\128\208\181\208\187\208\176\208\188\208\184|\208\162\208\190\209\128\208\179\208\190\208\178\208\181\209\134 \208\190\208\179\208\189\208\181\209\129\209\130\209\128\208\181\208\187\209\140\208\189\209\139\208\188 \208\190\209\128\209\131\208\182\208\184\208\181\208\188|\208\160\209\131\208\182\209\140\209\143 \208\184 \208\177\208\190\208\181\208\191\209\128\208\184\208\191\208\176\209\129\209\139|\208\159\209\128\208\190\208\180\208\176\208\178\208\181\209\134 \208\190\208\179\208\189\208\181\209\129\209\130\209\128\208\181\208\187\209\140\208\189\208\190\208\179\208\190 \208\190\209\128\209\131\208\182\208\184\209\143|\208\160\209\131\208\182\208\181\208\185\208\189\208\184\208\186|\208\160\209\131\208\182\209\140\209\143 \208\184 \208\187\209\131\208\186\208\184|\208\158\208\191\209\139\209\130\208\189\209\139\208\185 \208\187\209\131\209\135\208\189\208\184\208\186|\208\165\208\190\208\187\208\190\208\180\208\189\208\190\208\181 \208\184 \208\190\208\179\208\189\208\181\209\129\209\130\209\128\208\181\208\187\209\140\208\189\208\190\208\181 \208\190\209\128\209\131\208\182\208\184\208\181",
|
||||
}
|
||||
|
||||
-- NPC tracking categories (icons embedded from Wrath client)
|
||||
-- trackingType maps to DLL SetObjectTypeBlip() type names.
|
||||
-- subnameFilter: substring match on NPC subname/title (e.g. "Druid" matches "Druid Trainer").
|
||||
-- Filter: pipe-delimited exact subname match (DLL matches ANY segment as substring).
|
||||
-- Filtered entries take priority over unfiltered catch-all entries with the same flag.
|
||||
-- "dynamic" filter is resolved at runtime via getFilter().
|
||||
local NPC_CATEGORIES = {
|
||||
@@ -35,11 +92,19 @@ local NPC_CATEGORIES = {
|
||||
{ name = "Flight Master", trackingType = "flightmaster", icon = "Interface\\Minimap\\Tracking\\FlightMaster" },
|
||||
{ name = "Innkeeper", trackingType = "innkeeper", icon = "Interface\\Minimap\\Tracking\\Innkeeper" },
|
||||
{ name = "Mailbox", trackingType = "mailbox", icon = "Interface\\Minimap\\Tracking\\Mailbox" },
|
||||
{ name = "Poison Vendor", trackingType = "vendor", icon = "Interface\\Icons\\INV_Potion_19",
|
||||
getFilter = function() return getLocaleFilter(POISON_FILTERS) end },
|
||||
{ name = "Profession Trainer", trackingType = "trainer", icon = "Interface\\Minimap\\Tracking\\Profession",
|
||||
getExclude = function() return UnitClass("player") end },
|
||||
{ name = "Reagent Vendor", trackingType = "vendor", icon = "Interface\\Minimap\\Tracking\\Reagents",
|
||||
getFilter = function() return getLocaleFilter(REAGENT_FILTERS) end },
|
||||
{ name = "Repair", trackingType = "repair", icon = "Interface\\Minimap\\Tracking\\Repair" },
|
||||
{ name = "Stable Master", trackingType = "stablemaster", icon = "Interface\\Minimap\\Tracking\\StableMaster" },
|
||||
{ name = "Vendor", trackingType = "vendor", icon = "Interface\\Minimap\\Tracking\\Food" },
|
||||
{ name = "Trade Goods", trackingType = "vendor", icon = "Interface\\Minimap\\Tracking\\Profession",
|
||||
getFilter = function() return getLocaleFilter(TRADE_FILTERS) end },
|
||||
{ name = "Ammunition", trackingType = "vendor", icon = "Interface\\Minimap\\Tracking\\Ammunition",
|
||||
getFilter = function() return getLocaleFilter(AMMO_FILTERS) end },
|
||||
{ name = "General Vendor", trackingType = "vendor", icon = "Interface\\Minimap\\Tracking\\Food" },
|
||||
}
|
||||
|
||||
-- =============================================================================
|
||||
|
||||
@@ -118,37 +118,87 @@ const TrackedBlip = struct {
|
||||
gray: bool,
|
||||
};
|
||||
|
||||
const FILTER_MAX = 32;
|
||||
const allocator = std.heap.page_allocator;
|
||||
|
||||
const FlagEntry = struct {
|
||||
flag: u32 = 0,
|
||||
blip: Blip = .{ .texture = 0, .scale = 1.0 },
|
||||
active: bool = false,
|
||||
filter: [FILTER_MAX]u8 = .{0} ** FILTER_MAX, // include: subname must contain
|
||||
filter_len: u8 = 0,
|
||||
exclude: [FILTER_MAX]u8 = .{0} ** FILTER_MAX, // exclude: subname must NOT contain
|
||||
exclude_len: u8 = 0,
|
||||
filter: ?[]u8 = null, // include: subname must contain ANY pipe-delimited segment
|
||||
exclude: ?[]u8 = null, // exclude: subname must NOT contain ANY pipe-delimited segment
|
||||
|
||||
fn hasFilter(self: *const FlagEntry) bool {
|
||||
return self.filter_len > 0 or self.exclude_len > 0;
|
||||
return self.filter != null or self.exclude != null;
|
||||
}
|
||||
|
||||
/// Match subname against pipe-delimited filter segments.
|
||||
/// Include: subname must contain ANY segment (OR).
|
||||
/// Exclude: subname must NOT contain ANY segment (OR).
|
||||
fn matchesSubName(self: *const FlagEntry, subname: [*:0]const u8) bool {
|
||||
if (self.filter_len > 0 and !containsInsensitive(subname, self.filter[0..self.filter_len]))
|
||||
return false;
|
||||
if (self.exclude_len > 0 and containsInsensitive(subname, self.exclude[0..self.exclude_len]))
|
||||
return false;
|
||||
if (self.filter) |f| {
|
||||
if (!pipeMatchAny(subname, f)) return false;
|
||||
}
|
||||
if (self.exclude) |e| {
|
||||
if (pipeMatchAny(subname, e)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn filtersEqual(self: *const FlagEntry, inc: []const u8, exc: []const u8) bool {
|
||||
return sliceEqual(self.filter[0..self.filter_len], inc) and
|
||||
sliceEqual(self.exclude[0..self.exclude_len], exc);
|
||||
/// Returns true if haystack contains ANY pipe-delimited segment from pattern.
|
||||
fn pipeMatchAny(haystack: [*:0]const u8, pattern: []const u8) bool {
|
||||
var start: usize = 0;
|
||||
for (pattern, 0..) |c, i| {
|
||||
if (c == '|') {
|
||||
if (i > start and containsInsensitive(haystack, pattern[start..i]))
|
||||
return true;
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
// Last (or only) segment
|
||||
if (pattern.len > start and containsInsensitive(haystack, pattern[start..]))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
fn sliceEqual(a: []const u8, b: []const u8) bool {
|
||||
if (a.len != b.len) return false;
|
||||
for (a, b) |x, y| {
|
||||
fn filtersEqual(self: *const FlagEntry, inc: ?[]const u8, exc: ?[]const u8) bool {
|
||||
return sliceEqual(self.filterSlice(), inc) and sliceEqual(self.excludeSlice(), exc);
|
||||
}
|
||||
|
||||
fn filterSlice(self: *const FlagEntry) ?[]const u8 {
|
||||
return self.filter;
|
||||
}
|
||||
|
||||
fn excludeSlice(self: *const FlagEntry) ?[]const u8 {
|
||||
return self.exclude;
|
||||
}
|
||||
|
||||
fn setFilter(self: *FlagEntry, src: ?[]const u8) void {
|
||||
if (self.filter) |old| allocator.free(old);
|
||||
self.filter = dupeStr(src);
|
||||
}
|
||||
|
||||
fn setExclude(self: *FlagEntry, src: ?[]const u8) void {
|
||||
if (self.exclude) |old| allocator.free(old);
|
||||
self.exclude = dupeStr(src);
|
||||
}
|
||||
|
||||
fn clear(self: *FlagEntry) void {
|
||||
if (self.filter) |f| allocator.free(f);
|
||||
if (self.exclude) |e| allocator.free(e);
|
||||
self.* = .{};
|
||||
}
|
||||
|
||||
fn dupeStr(src: ?[]const u8) ?[]u8 {
|
||||
const s = src orelse return null;
|
||||
if (s.len == 0) return null;
|
||||
return allocator.dupe(u8, s) catch return null;
|
||||
}
|
||||
|
||||
fn sliceEqual(a: ?[]const u8, b: ?[]const u8) bool {
|
||||
const sa = a orelse return b == null or b.?.len == 0;
|
||||
const sb = b orelse return sa.len == 0;
|
||||
if (sa.len != sb.len) return false;
|
||||
for (sa, sb) |x, y| {
|
||||
if (x != y) return false;
|
||||
}
|
||||
return true;
|
||||
@@ -588,15 +638,16 @@ fn checkObject(info: u32, guid_lo: u32, guid_hi: u32) bool {
|
||||
if (!entry.active) continue;
|
||||
if (npc_flags & entry.flag == 0) continue;
|
||||
|
||||
const priority: u8 = if (entry.filter_len > 0)
|
||||
const priority: u8 = if (entry.filter != null)
|
||||
3
|
||||
else if (entry.exclude_len > 0)
|
||||
else if (entry.exclude != null)
|
||||
2
|
||||
else
|
||||
1;
|
||||
|
||||
if (priority < best_priority) continue;
|
||||
if (priority == best_priority and entry.flag <= best_flag) continue;
|
||||
if (priority == best_priority and entry.flag < best_flag) continue;
|
||||
if (priority == best_priority and entry.flag == best_flag) continue; // first match wins at same tier
|
||||
|
||||
// Check subname match
|
||||
if (entry.hasFilter()) {
|
||||
@@ -731,6 +782,16 @@ fn enumVisibleObjectsDetour(callback: u32, context: u32) callconv(fc) i32 {
|
||||
// Lua API
|
||||
// =============================================================================
|
||||
|
||||
/// Read a Lua string arg as a slice (null if absent/empty).
|
||||
fn luaStringToSlice(L: lua.State, idx: i32) ?[]const u8 {
|
||||
if (!lua.isstring(L, idx)) return null;
|
||||
const s = lua.tostring(L, idx) orelse return null;
|
||||
var len: usize = 0;
|
||||
while (s[len] != 0) : (len += 1) {}
|
||||
if (len == 0) return null;
|
||||
return s[0..len];
|
||||
}
|
||||
|
||||
// SetObjectTypeBlip(typeName [, texturePath [, scale [, includeFilter [, excludeFilter]]]])
|
||||
pub fn luaSetObjectTypeBlip(L: lua.State) callconv(fc) i32 {
|
||||
if (!lua.isstring(L, 1)) {
|
||||
@@ -745,25 +806,9 @@ pub fn luaSetObjectTypeBlip(L: lua.State) callconv(fc) i32 {
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Parse optional include filter (arg 4) and exclude filter (arg 5)
|
||||
var inc_buf: [FILTER_MAX]u8 = .{0} ** FILTER_MAX;
|
||||
var inc_len: u8 = 0;
|
||||
var exc_buf: [FILTER_MAX]u8 = .{0} ** FILTER_MAX;
|
||||
var exc_len: u8 = 0;
|
||||
if (lua.isstring(L, 4)) {
|
||||
if (lua.tostring(L, 4)) |f| {
|
||||
while (inc_len < FILTER_MAX and f[inc_len] != 0) : (inc_len += 1) {
|
||||
inc_buf[inc_len] = f[inc_len];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lua.isstring(L, 5)) {
|
||||
if (lua.tostring(L, 5)) |f| {
|
||||
while (exc_len < FILTER_MAX and f[exc_len] != 0) : (exc_len += 1) {
|
||||
exc_buf[exc_len] = f[exc_len];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Parse optional include filter (arg 4) and exclude filter (arg 5) as slices
|
||||
const inc = luaStringToSlice(L, 4);
|
||||
const exc = luaStringToSlice(L, 5);
|
||||
|
||||
if (!lua.isstring(L, 2)) {
|
||||
// Disable tracking for this type + filter combo
|
||||
@@ -777,9 +822,9 @@ pub fn luaSetObjectTypeBlip(L: lua.State) callconv(fc) i32 {
|
||||
} else {
|
||||
for (&g_flag_tracking) |*entry| {
|
||||
if (entry.active and entry.flag == mapping.value and
|
||||
entry.filtersEqual(inc_buf[0..inc_len], exc_buf[0..exc_len]))
|
||||
entry.filtersEqual(inc, exc))
|
||||
{
|
||||
entry.active = false;
|
||||
entry.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -816,27 +861,22 @@ pub fn luaSetObjectTypeBlip(L: lua.State) callconv(fc) i32 {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Match by flag + include/exclude combo
|
||||
// Match by flag + include/exclude combo — update existing or add new
|
||||
for (&g_flag_tracking) |*entry| {
|
||||
if (entry.active and entry.flag == mapping.value and
|
||||
entry.filtersEqual(inc_buf[0..inc_len], exc_buf[0..exc_len]))
|
||||
entry.filtersEqual(inc, exc))
|
||||
{
|
||||
entry.blip = blip;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// Add new entry
|
||||
for (&g_flag_tracking) |*entry| {
|
||||
if (!entry.active) {
|
||||
entry.* = .{
|
||||
.flag = mapping.value,
|
||||
.blip = blip,
|
||||
.active = true,
|
||||
.filter = inc_buf,
|
||||
.filter_len = inc_len,
|
||||
.exclude = exc_buf,
|
||||
.exclude_len = exc_len,
|
||||
};
|
||||
entry.flag = mapping.value;
|
||||
entry.blip = blip;
|
||||
entry.active = true;
|
||||
entry.setFilter(inc);
|
||||
entry.setExclude(exc);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -911,6 +951,12 @@ pub fn installHooks() void {
|
||||
con.print("[minimapicons] Hooks installed\n");
|
||||
}
|
||||
|
||||
/// Called from CGGameUI_Shutdown (logout/exit) — frees heap-allocated filter strings.
|
||||
pub fn onShutdown() void {
|
||||
for (&g_flag_tracking) |*entry| entry.clear();
|
||||
con.print("[minimapicons] filters freed (shutdown)\n");
|
||||
}
|
||||
|
||||
pub fn removeHooks() void {
|
||||
if (g_is_hook_owner) {
|
||||
enum_vis_hook.detach();
|
||||
|
||||
Reference in New Issue
Block a user