mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-11 12:27:35 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8bd0bce4e | ||
|
|
c3a80053e9 | ||
|
|
5a3badb5bd |
@@ -2,7 +2,26 @@ if GetLocale() ~= "deDE" then return end
|
||||
|
||||
BINDING_NAME_FOCUSTARGET = "Fokus setzen";
|
||||
BINDING_NAME_TARGETFOCUS = "Fokus anvisieren";
|
||||
CONFIRM_DELETE_EQUIPMENT_SET = "Seid Ihr sicher, dass Ihr das Ausrüstungsset \"%s\" löschen wollt?";
|
||||
CONFIRM_SAVE_EQUIPMENT_SET = "Möchtet Ihr das Ausrüstungsset \"%s\" speichern?";
|
||||
EQUIPMENT_MANAGER = "Ausrüstungsmanager";
|
||||
EQUIPMENT_MANAGER_IGNORE_SLOT = "Diesen Platz ignorieren";
|
||||
EQUIPMENT_MANAGER_IS_DISABLED = "Der Ausrüstungsmanager ist abgeschaltet.";
|
||||
EQUIPMENT_MANAGER_ITEMS_MISSING_TOOLTIP = "%d %s |cffff0000(%d fehlen)|r";
|
||||
EQUIPMENT_MANAGER_PLACE_IN_BAGS = "In Beuteln platzieren";
|
||||
EQUIPMENT_MANAGER_UNIGNORE_SLOT = "Diesen Platz einbeziehen";
|
||||
EQUIPMENT_SETS = "Ausrüstungs-Sets: |cFFFFFFFF%s|r";
|
||||
EQUIPMENT_SETS_CANT_RENAME = "Ein Ausrüstungsset mit diesem Namen gibt es bereits.";
|
||||
EQUIPMENT_SETS_TOO_MANY = "Ihr könnt keine weiteren Ausrüstungssets erstellen.";
|
||||
EQUIPMENT_SET_ASSIGN_TO_SPEC = "Spezialisierung zuweisen:";
|
||||
EQUIPMENT_SET_EDIT = "Name/Icon ändern";
|
||||
EQUIPMENT_SET_SETTINGS = "Einstellungen";
|
||||
EQUIPSET_EQUIP = "Anlegen";
|
||||
ERR_EQUIPMENT_MANAGER_BAGS_FULL = "Ausrüstungstausch fehlgeschlagen. Inventar ist voll.";
|
||||
ERR_EQUIPMENT_MANAGER_COMBAT_SWAP_S = "Einige Gegenstände des Sets '%s' können nicht gewechselt werden, da Ihr Euch im Kampf befindet.";
|
||||
ERR_EQUIPMENT_MANAGER_MISSING_ITEM_S = "Mindestens ein Teil des Sets '%s' fehlt und kann nicht ausgerüstet werden.";
|
||||
FOCUSTARGET = "Ziel als Fokus festlegen";
|
||||
GEARSETS_POPUP_TEXT = "Setnamen eingeben (Max. 16 Zeichen):";
|
||||
ITEMS_VARIABLE_QUANTITY = "%d |4IGegenstand:Gegenstände;";
|
||||
ITEMS_EQUIPPED = "%d |4Gegenstand:Gegenstände; ausgerüstet";
|
||||
ITEMS_IN_INVENTORY = "%d |4Gegenstand:Gegenstände; im Inventar";
|
||||
@@ -15,6 +34,10 @@ COPPER_AMOUNT_SYMBOL = "K";
|
||||
GENERIC_FRACTION_STRING = "%d/%d";
|
||||
GOLD_AMOUNT_SYMBOL = "G";
|
||||
HOURS_MINUTES_SECONDS = "%.2d:%.2d:%.2d";
|
||||
ICON_FILTER_ALL = "Alle Symbole";
|
||||
ICON_FILTER_ITEM = "Gegenstände";
|
||||
ICON_FILTER_SPELL = "Zauber";
|
||||
ICON_SELECTION_TITLE_CURRENT = "Aktuell ausgewählt";
|
||||
ITEM_DELTA_DESCRIPTION = "Wenn Ihr diesen Gegenstand ersetzt, werden sich folgende Werte ändern:";
|
||||
ITEM_MOD_MANA_SHORT = "Mana";
|
||||
ITEM_MOD_HEALTH_SHORT = "Gesundheit";
|
||||
@@ -42,6 +65,8 @@ ITEM_MOD_BLOCK_VALUE_SHORT = "Blocken";
|
||||
ITEM_MOD_DAMAGE_PER_SECOND_SHORT = "Schaden pro Sekunde";
|
||||
LARGE_NUMBER_SEPERATOR = ".";
|
||||
MINUTES_SECONDS = "%.2d:%.2d";
|
||||
PAPERDOLL_EQUIPMENTMANAGER = "Ausrüstungsmanager";
|
||||
PAPERDOLL_NEWEQUIPMENTSET = "Neues Set";
|
||||
PERCENTAGE_STRING = "%d%%";
|
||||
SILVER_AMOUNT_SYMBOL = "S";
|
||||
SLASH_CLEARFOCUS1 = "/fokusaufheben";
|
||||
|
||||
@@ -1,11 +1,34 @@
|
||||
BINDING_NAME_AOELOOT = "AoE Loot";
|
||||
BINDING_NAME_FOCUSTARGET = "Focus Target";
|
||||
BINDING_NAME_TARGETFOCUS = "Target Focus";
|
||||
CONFIRM_DELETE_EQUIPMENT_SET = "Are you sure you want to delete the equipment set %s?";
|
||||
CONFIRM_SAVE_EQUIPMENT_SET = "Would you like to save the equipment set \"%s\"?";
|
||||
COPPER_AMOUNT_SYMBOL = "c";
|
||||
EQUIPMENT_MANAGER = "Equipment Manager";
|
||||
EQUIPMENT_MANAGER_IGNORE_SLOT = "Ignore This Slot";
|
||||
EQUIPMENT_MANAGER_IS_DISABLED = "The Equipment Manager is disabled.";
|
||||
EQUIPMENT_MANAGER_ITEMS_MISSING_TOOLTIP = "%d %s |cffff0000(%d missing)|r";
|
||||
EQUIPMENT_MANAGER_PLACE_IN_BAGS = "Place In Bags";
|
||||
EQUIPMENT_MANAGER_UNIGNORE_SLOT = "Include This Slot";
|
||||
EQUIPMENT_SETS = "Equipment Sets: |cFFFFFFFF%s|r";
|
||||
EQUIPMENT_SETS_CANT_RENAME = "An equipment set with that name already exists.";
|
||||
EQUIPMENT_SETS_TOO_MANY = "You cannot create any more new equipment sets.";
|
||||
EQUIPMENT_SET_ASSIGN_TO_SPEC = "Assign to Specialization:";
|
||||
EQUIPMENT_SET_EDIT = "Change Name/Icon";
|
||||
EQUIPMENT_SET_SETTINGS = "Settings";
|
||||
EQUIPSET_EQUIP = "Equip";
|
||||
ERR_EQUIPMENT_MANAGER_BAGS_FULL = "Equipment swap failed. Inventory is full.";
|
||||
ERR_EQUIPMENT_MANAGER_COMBAT_SWAP_S = "Some items from set %s cannot be swapped because you are in combat.";
|
||||
ERR_EQUIPMENT_MANAGER_MISSING_ITEM_S = "One or more items from set %s were missing and could not be equipped.";
|
||||
FOCUSTARGET = "Focus Target";
|
||||
GEARSETS_POPUP_TEXT = "Enter Set Name (Max 16 Characters):";
|
||||
GENERIC_FRACTION_STRING = "%d/%d";
|
||||
GOLD_AMOUNT_SYMBOL = "g";
|
||||
HOURS_MINUTES_SECONDS = "%.2d:%.2d:%.2d";
|
||||
ICON_FILTER_ALL = "All Icons";
|
||||
ICON_FILTER_ITEM = "Items";
|
||||
ICON_FILTER_SPELL = "Spells";
|
||||
ICON_SELECTION_TITLE_CURRENT = "Currently Selected";
|
||||
ITEM_DELTA_DESCRIPTION = "If you replace this item, the following stat changes will occur:";
|
||||
ITEM_MOD_MANA_SHORT = "Mana";
|
||||
ITEM_MOD_HEALTH_SHORT = "Health";
|
||||
@@ -39,6 +62,8 @@ ITEM_MISSING = "%s missing";
|
||||
ITEM_SLOTS_IGNORED = "%d |4slot:slots; ignored";
|
||||
LARGE_NUMBER_SEPERATOR = ",";
|
||||
MINUTES_SECONDS = "%.2d:%.2d";
|
||||
PAPERDOLL_EQUIPMENTMANAGER = "Equipment Manager";
|
||||
PAPERDOLL_NEWEQUIPMENTSET = "New Set";
|
||||
PERCENTAGE_STRING = "%d%%";
|
||||
SILVER_AMOUNT_SYMBOL = "s";
|
||||
SLASH_CLEARFOCUS1 = "/clearfocus";
|
||||
|
||||
@@ -2,7 +2,26 @@ if GetLocale() ~= "esES" then return end
|
||||
|
||||
BINDING_NAME_FOCUSTARGET = "Establecer objetivo";
|
||||
BINDING_NAME_TARGETFOCUS = "Seleccionar enfocado";
|
||||
CONFIRM_DELETE_EQUIPMENT_SET = "¿Seguro que quieres eliminar el equipamiento %s?";
|
||||
CONFIRM_SAVE_EQUIPMENT_SET = "¿Quieres guardar el conjunto de equipamiento \"%s\"?";
|
||||
EQUIPMENT_MANAGER = "Gestor de equipamiento";
|
||||
EQUIPMENT_MANAGER_IGNORE_SLOT = "Ignorar esta casilla";
|
||||
EQUIPMENT_MANAGER_IS_DISABLED = "El Gestor de equipamiento está desactivado.";
|
||||
EQUIPMENT_MANAGER_ITEMS_MISSING_TOOLTIP = "%d %s |cffff0000(falta %d)|r";
|
||||
EQUIPMENT_MANAGER_PLACE_IN_BAGS = "Colocar en bolsas";
|
||||
EQUIPMENT_MANAGER_UNIGNORE_SLOT = "Incluir esta casilla";
|
||||
EQUIPMENT_SETS = "Conjuntos de equipamiento: |cFFFFFFFF%s|r";
|
||||
EQUIPMENT_SETS_CANT_RENAME = "Ya existe un conjunto de equipamiento con ese nombre.";
|
||||
EQUIPMENT_SETS_TOO_MANY = "Ya no puedes crear más equipamientos.";
|
||||
EQUIPMENT_SET_ASSIGN_TO_SPEC = "Asignar a especialización:";
|
||||
EQUIPMENT_SET_EDIT = "Cambiar nombre/icono";
|
||||
EQUIPMENT_SET_SETTINGS = "Config.";
|
||||
EQUIPSET_EQUIP = "Equipar";
|
||||
ERR_EQUIPMENT_MANAGER_BAGS_FULL = "No se ha podido cambiar el equipamiento. Inventario lleno.";
|
||||
ERR_EQUIPMENT_MANAGER_COMBAT_SWAP_S = "Algunos objetos del equipo %s no se cambiarán porque estás en combate.";
|
||||
ERR_EQUIPMENT_MANAGER_MISSING_ITEM_S = "Falta uno o más objetos del equipo %s y no se ha podido equipar.";
|
||||
FOCUSTARGET = "Establecer objetivo";
|
||||
GEARSETS_POPUP_TEXT = "Nombra este equipamiento (Máx. 16 carac.):";
|
||||
ITEMS_VARIABLE_QUANTITY = "%d |4objeto:objetos;";
|
||||
ITEMS_EQUIPPED = "Equipado: %d |4objeto:objetos;";
|
||||
ITEMS_IN_INVENTORY = "En inventario: %d |4objeto:objetos;";
|
||||
@@ -15,6 +34,10 @@ COPPER_AMOUNT_SYMBOL = "c";
|
||||
GENERIC_FRACTION_STRING = "%d/%d";
|
||||
GOLD_AMOUNT_SYMBOL = "o";
|
||||
HOURS_MINUTES_SECONDS = "%.2d:%.2d:%.2d";
|
||||
ICON_FILTER_ALL = "Todos los iconos";
|
||||
ICON_FILTER_ITEM = "Objetos";
|
||||
ICON_FILTER_SPELL = "Hechizos";
|
||||
ICON_SELECTION_TITLE_CURRENT = "Selección actual";
|
||||
ITEM_DELTA_DESCRIPTION = "Si reemplazas este objeto, se producirán los siguientes cambios en las estadísticas:";
|
||||
ITEM_MOD_MANA_SHORT = "Maná";
|
||||
ITEM_MOD_HEALTH_SHORT = "Salud";
|
||||
@@ -42,6 +65,8 @@ ITEM_MOD_BLOCK_VALUE_SHORT = "Valor de bloqueo";
|
||||
ITEM_MOD_DAMAGE_PER_SECOND_SHORT = "daño por segundo";
|
||||
LARGE_NUMBER_SEPERATOR = "";
|
||||
MINUTES_SECONDS = "%.2d:%.2d";
|
||||
PAPERDOLL_EQUIPMENTMANAGER = "Gestor de equipamiento";
|
||||
PAPERDOLL_NEWEQUIPMENTSET = "Nuevo conjunto";
|
||||
PERCENTAGE_STRING = "%d%%";
|
||||
SILVER_AMOUNT_SYMBOL = "p";
|
||||
SLASH_CLEARFOCUS1 = "/quitarenfoque";
|
||||
|
||||
@@ -2,6 +2,24 @@ if GetLocale() ~= "frFR" then return end
|
||||
|
||||
BINDING_NAME_FOCUSTARGET = "Définir cible";
|
||||
BINDING_NAME_TARGETFOCUS = "Reprendre cible";
|
||||
CONFIRM_DELETE_EQUIPMENT_SET = "Voulez-vous vraiment supprimer l'ensemble d'équipement %s ?";
|
||||
CONFIRM_SAVE_EQUIPMENT_SET = "Voulez-vous sauvegarder l'ensemble d'équipement %s ?";
|
||||
EQUIPMENT_MANAGER = "Gestionnaire d'équipement";
|
||||
EQUIPMENT_MANAGER_IGNORE_SLOT = "Ignorer cet emplacement";
|
||||
EQUIPMENT_MANAGER_IS_DISABLED = "Le Gestionnaire d'équipement est désactivé.";
|
||||
EQUIPMENT_MANAGER_ITEMS_MISSING_TOOLTIP = "%d %s |cffff0000(objet manquant : %d)|r";
|
||||
EQUIPMENT_MANAGER_PLACE_IN_BAGS = "Mettre en sac";
|
||||
EQUIPMENT_MANAGER_UNIGNORE_SLOT = "Inclure cet emplacement";
|
||||
EQUIPMENT_SETS = "Ensembles d'équipement : |cFFFFFFFF%s|r";
|
||||
EQUIPMENT_SETS_CANT_RENAME = "Un ensemble d'équipement portant ce nom existe déjà.";
|
||||
EQUIPMENT_SETS_TOO_MANY = "Vous ne pouvez plus créer de nouveaux ensembles d'équipement.";
|
||||
EQUIPMENT_SET_ASSIGN_TO_SPEC = "Assigner à la spécialisation :";
|
||||
EQUIPMENT_SET_EDIT = "Modifier le nom/l'icône";
|
||||
EQUIPMENT_SET_SETTINGS = "Réglages";
|
||||
EQUIPSET_EQUIP = "Equiper";
|
||||
ERR_EQUIPMENT_MANAGER_BAGS_FULL = "Échec de l'échange d'équipement. L'inventaire est plein.";
|
||||
ERR_EQUIPMENT_MANAGER_COMBAT_SWAP_S = "Certains objets de l'ensemble %s ne peuvent pas être échangés car vous êtes en combat.";
|
||||
ERR_EQUIPMENT_MANAGER_MISSING_ITEM_S = "Une ou plusieurs pièces de l'ensemble %s manquaient et n'ont pu être équipées.";
|
||||
FOCUSTARGET = "Définir la cible de focalisation";
|
||||
ITEMS_VARIABLE_QUANTITY = "%d |4objet:objets;";
|
||||
ITEMS_EQUIPPED = "%d |4objet:objets; équipés";
|
||||
@@ -12,9 +30,14 @@ ITEMS_NOT_IN_INVENTORY = "%d |4objet:objets; manquants";
|
||||
TARGETFOCUS = "Cibler l'unité focalisée";
|
||||
-- BINDING_NAME_AOELOOT = "AoE Loot";
|
||||
COPPER_AMOUNT_SYMBOL = "c";
|
||||
GEARSETS_POPUP_TEXT = "Nom de l'ensemble (16 caractères max.) :";
|
||||
GENERIC_FRACTION_STRING = "%d/%d";
|
||||
GOLD_AMOUNT_SYMBOL = "o";
|
||||
HOURS_MINUTES_SECONDS = "%.2d:%.2d:%.2d";
|
||||
ICON_FILTER_ALL = "Toutes les icônes";
|
||||
ICON_FILTER_ITEM = "Objets";
|
||||
ICON_FILTER_SPELL = "Sorts";
|
||||
ICON_SELECTION_TITLE_CURRENT = "Sélection actuelle";
|
||||
ITEM_DELTA_DESCRIPTION = "Si vous remplacez cet objet, les caractéristiques suivantes seront modifiées :";
|
||||
ITEM_MOD_MANA_SHORT = "Mana";
|
||||
ITEM_MOD_HEALTH_SHORT = "Vie";
|
||||
@@ -42,6 +65,8 @@ ITEM_MOD_BLOCK_VALUE_SHORT = "Valeur de blocage";
|
||||
ITEM_MOD_DAMAGE_PER_SECOND_SHORT = "Dégâts par seconde";
|
||||
LARGE_NUMBER_SEPERATOR = "";
|
||||
MINUTES_SECONDS = "%.2d:%.2d";
|
||||
PAPERDOLL_EQUIPMENTMANAGER = "Gestionnaire d'équipement";
|
||||
PAPERDOLL_NEWEQUIPMENTSET = "Nouvel ensemble";
|
||||
PERCENTAGE_STRING = "%d%%";
|
||||
SILVER_AMOUNT_SYMBOL = "a";
|
||||
SLASH_CLEARFOCUS1 = "/clearfocus";
|
||||
|
||||
@@ -2,6 +2,24 @@ if GetLocale() ~= "ruRU" then return end
|
||||
|
||||
BINDING_NAME_FOCUSTARGET = "Запомненная цель";
|
||||
BINDING_NAME_TARGETFOCUS = "Восстановить цель";
|
||||
CONFIRM_DELETE_EQUIPMENT_SET = "Вы действительно хотите удалить комплект экипировки \"%s\"?";
|
||||
CONFIRM_SAVE_EQUIPMENT_SET = "Хотите сохранить комплект экипировки \"%s\"?";
|
||||
EQUIPMENT_MANAGER = "Комплекты экипировки";
|
||||
EQUIPMENT_MANAGER_IGNORE_SLOT = "Пропустить ячейку";
|
||||
EQUIPMENT_MANAGER_IS_DISABLED = "Управление экипировкой отключено.";
|
||||
EQUIPMENT_MANAGER_ITEMS_MISSING_TOOLTIP = "%d %s |cffff0000(предмет для ячейки \"%d\" отсутствует)|r";
|
||||
EQUIPMENT_MANAGER_PLACE_IN_BAGS = "Поместить в сумку";
|
||||
EQUIPMENT_MANAGER_UNIGNORE_SLOT = "Учитывать ячейку";
|
||||
EQUIPMENT_SETS = "Комплекты экипировки: |cFFFFFFFF%s|r";
|
||||
EQUIPMENT_SETS_CANT_RENAME = "Это название уже используется для другого комплекта.";
|
||||
EQUIPMENT_SETS_TOO_MANY = "Вы не можете создавать новые комплекты экипировки.";
|
||||
EQUIPMENT_SET_ASSIGN_TO_SPEC = "Выбрать специализацию:";
|
||||
EQUIPMENT_SET_EDIT = "Изменить название/значок";
|
||||
EQUIPMENT_SET_SETTINGS = "Параметры";
|
||||
EQUIPSET_EQUIP = "Надеть";
|
||||
ERR_EQUIPMENT_MANAGER_BAGS_FULL = "Не удалось заменить комплект обмундирования. Инвентарь переполнен.";
|
||||
ERR_EQUIPMENT_MANAGER_COMBAT_SWAP_S = "Некоторые предметы из набора \"%s\" не будут экипированы, поскольку вы участвуете в бою.";
|
||||
ERR_EQUIPMENT_MANAGER_MISSING_ITEM_S = "Недостает одного или более предметов из набора \"%s\".";
|
||||
FOCUSTARGET = "Запомнить цель";
|
||||
ITEMS_VARIABLE_QUANTITY = "%d |4предмет:предмета:предметов;";
|
||||
ITEMS_EQUIPPED = "%d |4предмет экипирован:предмета экипировано:предметов экипировано;";
|
||||
@@ -12,9 +30,14 @@ ITEMS_NOT_IN_INVENTORY = "%d |4предмет отсутствует:предм
|
||||
TARGETFOCUS = "Восстановить цель";
|
||||
-- BINDING_NAME_AOELOOT = "AoE Loot";
|
||||
COPPER_AMOUNT_SYMBOL = "м";
|
||||
GEARSETS_POPUP_TEXT = "Введите название комплекта (не более 16 символов):";
|
||||
GENERIC_FRACTION_STRING = "%d/%d";
|
||||
GOLD_AMOUNT_SYMBOL = "з";
|
||||
HOURS_MINUTES_SECONDS = "%.2d:%.2d:%.2d";
|
||||
ICON_FILTER_ALL = "Все значки";
|
||||
ICON_FILTER_ITEM = "Предметы";
|
||||
ICON_FILTER_SPELL = "Заклинания";
|
||||
ICON_SELECTION_TITLE_CURRENT = "Текущий выбор";
|
||||
ITEM_DELTA_DESCRIPTION = "Если вы замените этот предмет, произойдут следующие изменения:";
|
||||
ITEM_MOD_MANA_SHORT = "к мане";
|
||||
ITEM_MOD_HEALTH_SHORT = "к здоровью";
|
||||
@@ -42,6 +65,8 @@ ITEM_MOD_BLOCK_VALUE_SHORT = "к показателю блокирования";
|
||||
ITEM_MOD_DAMAGE_PER_SECOND_SHORT = "к урону в секунду";
|
||||
LARGE_NUMBER_SEPERATOR = "";
|
||||
MINUTES_SECONDS = "%.2d:%.2d";
|
||||
PAPERDOLL_EQUIPMENTMANAGER = "Комплекты экипировки";
|
||||
PAPERDOLL_NEWEQUIPMENTSET = "Новый комплект";
|
||||
PERCENTAGE_STRING = "%d%%";
|
||||
SILVER_AMOUNT_SYMBOL = "с";
|
||||
SLASH_CLEARFOCUS1 = "/clearfocus";
|
||||
|
||||
@@ -2,6 +2,24 @@ if GetLocale() ~= "zhCN" then return end
|
||||
|
||||
BINDING_NAME_FOCUSTARGET = "焦点目标";
|
||||
BINDING_NAME_TARGETFOCUS = "目标焦点";
|
||||
CONFIRM_DELETE_EQUIPMENT_SET = "你确认要删除装备方案%s吗?";
|
||||
CONFIRM_SAVE_EQUIPMENT_SET = "你想要保存装备方案\"%s\"吗?";
|
||||
EQUIPMENT_MANAGER = "装备管理";
|
||||
EQUIPMENT_MANAGER_IGNORE_SLOT = "忽略此栏位";
|
||||
EQUIPMENT_MANAGER_IS_DISABLED = "装备管理已禁用。";
|
||||
EQUIPMENT_MANAGER_ITEMS_MISSING_TOOLTIP = "%d %s |cffff0000(缺少%d)|r";
|
||||
EQUIPMENT_MANAGER_PLACE_IN_BAGS = "放入背包";
|
||||
EQUIPMENT_MANAGER_UNIGNORE_SLOT = "包括此栏位";
|
||||
EQUIPMENT_SETS = "装备配置方案:|cFFFFFFFF%s|r";
|
||||
EQUIPMENT_SETS_CANT_RENAME = "一个重名的装备方案已经存在。";
|
||||
EQUIPMENT_SETS_TOO_MANY = "你不能再创建新的装备方案了。";
|
||||
EQUIPMENT_SET_ASSIGN_TO_SPEC = "指定专精:";
|
||||
EQUIPMENT_SET_EDIT = "修改名称/图标";
|
||||
EQUIPMENT_SET_SETTINGS = "设置";
|
||||
EQUIPSET_EQUIP = "装备";
|
||||
ERR_EQUIPMENT_MANAGER_BAGS_FULL = "装备更换失败。背包已满。";
|
||||
ERR_EQUIPMENT_MANAGER_COMBAT_SWAP_S = "装备方案%s中的某些物品不能在战斗中切换。";
|
||||
ERR_EQUIPMENT_MANAGER_MISSING_ITEM_S = "未找到套装方案%s中的一件或多件物品,无法装备。";
|
||||
FOCUSTARGET = "设定焦点目标";
|
||||
ITEMS_VARIABLE_QUANTITY = "%d件物品";
|
||||
ITEMS_EQUIPPED = "已装备%d件物品";
|
||||
@@ -12,9 +30,14 @@ ITEMS_NOT_IN_INVENTORY = "缺少%d件物品";
|
||||
TARGETFOCUS = "选中焦点目标";
|
||||
-- BINDING_NAME_AOELOOT = "AoE Loot";
|
||||
COPPER_AMOUNT_SYMBOL = "铜";
|
||||
GEARSETS_POPUP_TEXT = "输入方案名称(最多16个字符):";
|
||||
GENERIC_FRACTION_STRING = "%d/%d";
|
||||
GOLD_AMOUNT_SYMBOL = "金";
|
||||
HOURS_MINUTES_SECONDS = "%.2d:%.2d:%.2d";
|
||||
ICON_FILTER_ALL = "所有图标";
|
||||
ICON_FILTER_ITEM = "物品";
|
||||
ICON_FILTER_SPELL = "法术";
|
||||
ICON_SELECTION_TITLE_CURRENT = "当前已选择";
|
||||
ITEM_DELTA_DESCRIPTION = "如果你替换该物品,将会产生以下的属性变更:";
|
||||
ITEM_MOD_MANA_SHORT = "法力值";
|
||||
ITEM_MOD_HEALTH_SHORT = "生命值";
|
||||
@@ -42,6 +65,8 @@ ITEM_MOD_BLOCK_VALUE_SHORT = "格挡值";
|
||||
ITEM_MOD_DAMAGE_PER_SECOND_SHORT = "每秒伤害";
|
||||
LARGE_NUMBER_SEPERATOR = ",";
|
||||
MINUTES_SECONDS = "%.2d:%.2d";
|
||||
PAPERDOLL_EQUIPMENTMANAGER = "装备管理";
|
||||
PAPERDOLL_NEWEQUIPMENTSET = "新的方案";
|
||||
PERCENTAGE_STRING = "%d%%";
|
||||
SILVER_AMOUNT_SYMBOL = "银";
|
||||
SLASH_CLEARFOCUS1 = "/clearfocus";
|
||||
|
||||
@@ -2,6 +2,24 @@ if GetLocale() ~= "zhTW" then return end
|
||||
|
||||
BINDING_NAME_FOCUSTARGET = "鎖定目標";
|
||||
BINDING_NAME_TARGETFOCUS = "選定鎖定目標";
|
||||
CONFIRM_DELETE_EQUIPMENT_SET = "你確定要刪除裝備設定:%s嗎?";
|
||||
CONFIRM_SAVE_EQUIPMENT_SET = "你想要儲存裝備設定「%s」嗎?";
|
||||
EQUIPMENT_MANAGER = "裝備管理員";
|
||||
EQUIPMENT_MANAGER_IGNORE_SLOT = "忽略此欄位";
|
||||
EQUIPMENT_MANAGER_IS_DISABLED = "裝備管理員已關閉。";
|
||||
EQUIPMENT_MANAGER_ITEMS_MISSING_TOOLTIP = "%d件%s|cffff0000(缺少%d件)|r";
|
||||
EQUIPMENT_MANAGER_PLACE_IN_BAGS = "放進背包";
|
||||
EQUIPMENT_MANAGER_UNIGNORE_SLOT = "包含此欄位";
|
||||
EQUIPMENT_SETS = "裝備設定:|cFFFFFFFF%s|r";
|
||||
EQUIPMENT_SETS_CANT_RENAME = "您所選擇的名稱已經有裝備設定使用了。";
|
||||
EQUIPMENT_SETS_TOO_MANY = "你無法製造更多的新裝備設定。";
|
||||
EQUIPMENT_SET_ASSIGN_TO_SPEC = "指定專精:";
|
||||
EQUIPMENT_SET_EDIT = "改變名稱/圖示";
|
||||
EQUIPMENT_SET_SETTINGS = "設定";
|
||||
EQUIPSET_EQUIP = "裝備";
|
||||
ERR_EQUIPMENT_MANAGER_BAGS_FULL = "裝備切換失敗。物品欄已滿。";
|
||||
ERR_EQUIPMENT_MANAGER_COMBAT_SWAP_S = "%s設定中的某些物品必須等到你脫離戰鬥後才能更換。";
|
||||
ERR_EQUIPMENT_MANAGER_MISSING_ITEM_S = "%s設定中的一個或多個物品未尋獲,因此無法裝備。";
|
||||
FOCUSTARGET = "設定鎖定目標";
|
||||
ITEMS_VARIABLE_QUANTITY = "%d件物品";
|
||||
ITEMS_EQUIPPED = "裝備%d件物品";
|
||||
@@ -12,9 +30,14 @@ ITEMS_NOT_IN_INVENTORY = "缺少%d件物品";
|
||||
TARGETFOCUS = "選定鎖定單位";
|
||||
-- BINDING_NAME_AOELOOT = "AoE Loot";
|
||||
COPPER_AMOUNT_SYMBOL = "銅";
|
||||
GEARSETS_POPUP_TEXT = "輸入設定名稱(最多16個字元):";
|
||||
GENERIC_FRACTION_STRING = "%d/%d";
|
||||
GOLD_AMOUNT_SYMBOL = "金";
|
||||
HOURS_MINUTES_SECONDS = "%.2d:%.2d:%.2d";
|
||||
ICON_FILTER_ALL = "所有圖示";
|
||||
ICON_FILTER_ITEM = "物品";
|
||||
ICON_FILTER_SPELL = "法術";
|
||||
ICON_SELECTION_TITLE_CURRENT = "目前選擇";
|
||||
ITEM_DELTA_DESCRIPTION = "若你更換此裝備,下列屬性將會改變:";
|
||||
ITEM_MOD_MANA_SHORT = "法力";
|
||||
ITEM_MOD_HEALTH_SHORT = "生命力";
|
||||
@@ -42,6 +65,8 @@ ITEM_MOD_BLOCK_VALUE_SHORT = "格擋值";
|
||||
ITEM_MOD_DAMAGE_PER_SECOND_SHORT = "每秒傷害";
|
||||
LARGE_NUMBER_SEPERATOR = ",";
|
||||
MINUTES_SECONDS = "%.2d:%.2d";
|
||||
PAPERDOLL_EQUIPMENTMANAGER = "裝備管理員";
|
||||
PAPERDOLL_NEWEQUIPMENTSET = "新設定";
|
||||
PERCENTAGE_STRING = "%d%%";
|
||||
SILVER_AMOUNT_SYMBOL = "銀";
|
||||
SLASH_CLEARFOCUS1 = "/clearfocus";
|
||||
|
||||
@@ -49,7 +49,7 @@ Full per-function reference: **[docs/API.md](docs/API.md)**.
|
||||
| [LootHistory](docs/API.md#loothistory) | `C_LootHistory.GetNumItems`, `C_LootHistory.GetItem`, `C_LootHistory.GetPlayerInfo`, `C_LootHistory.Clear` |
|
||||
| [LossOfControl](docs/API.md#lossofcontrol) | `C_LossOfControl.GetActiveLossOfControlData`, `C_LossOfControl.GetActiveLossOfControlDataCount` |
|
||||
| [Lua](docs/API.md#lua) | `coroutine.create`, `coroutine.resume`, `coroutine.status`, `coroutine.wrap`, `coroutine.yield`, `CreateFromMixins`, `math.fmod`, `math.huge`, `math.modf`, `Mixin`, `select`, `string.gmatch`, `string.match`, `string.reverse`, `strjoin`, `strreplace`, `strrev`, `strsplit`, `strtrim`, `table.count`, `table.wipe` |
|
||||
| [Macros](docs/API.md#macros) | `GetLooseMacroIcons`, `GetLooseMacroItemIcons`, `GetMacroIcons`, `GetMacroItemIcons`, `GetMacroSpell` |
|
||||
| [Macros](docs/API.md#macros) | `C_Macro.CreateMacro`, `C_Macro.EditMacro`, `GetLooseMacroIcons`, `GetLooseMacroItemIcons`, `GetMacroIcons`, `GetMacroItemIcons`, `GetMacroSpell` |
|
||||
| [Mail](docs/API.md#mail) | `GetInboxItemLink`, `GetSendMailItemLink` |
|
||||
| [Map](docs/API.md#map) | `C_Map.GetAreaInfo`, `C_Map.GetAreas`, `C_Map.GetAreaTriggerInfo`, `C_Map.GetAreaTriggers`, `C_Map.GetBestMapForUnit`, `C_Map.GetMapAreaIDs`, `C_Map.GetMapOverlays`, `C_Map.GetMapWorldSize` |
|
||||
| [MapExplorationInfo](docs/API.md#mapexplorationinfo) | `C_MapExplorationInfo.GetExploredMapTextures`, `C_MapExplorationInfo.GetUnexploredMapTextures` |
|
||||
|
||||
+55
@@ -335,6 +335,7 @@ build instructions.
|
||||
- [`CastSpellNoToggle` as a macro cast line](#castspellnotoggle-as-a-macro-cast-line)
|
||||
- [`GetMacroSpell(macroSlot)`](#getmacrospellmacroslot)
|
||||
- [`GetMacroIcons` / `GetMacroItemIcons` / `GetLooseMacroIcons` / `GetLooseMacroItemIcons`](#getmacroicons--getmacroitemicons--getloosemacroicons--getloosemacroitemicons)
|
||||
- [`C_Macro.CreateMacro` / `C_Macro.EditMacro`](#c_macrocreatemacro--c_macroeditmacro)
|
||||
|
||||
- [Mail](#mail)
|
||||
- [`GetSendMailItemLink([attachmentIndex])`](#getsendmailitemlinkattachmentindex)
|
||||
@@ -8117,6 +8118,60 @@ MPQ will appear in both the loose and mpq lists).
|
||||
The vanilla legacy globals `GetNumMacroIcons` / `GetMacroIconInfo`
|
||||
remain available unchanged.
|
||||
|
||||
### `C_Macro.CreateMacro` / `C_Macro.EditMacro`
|
||||
|
||||
These functions create or edit a macro. You give the icon as a texture
|
||||
string — a full `Interface\Icons\<name>` path or a bare `<name>`
|
||||
basename — instead of a `GetMacroIconInfo` index.
|
||||
|
||||
```lua
|
||||
C_Macro.CreateMacro(name, iconTexture, body, isCharacterMacro) -> index
|
||||
C_Macro.EditMacro(index, name, iconTexture, body) -> index
|
||||
```
|
||||
|
||||
Why this exists: the vanilla `CreateMacro` / `EditMacro` globals take an
|
||||
icon index into the `GetMacroIconInfo` list. That list does not include
|
||||
`INV_*` item icons. As a result, the stock API cannot set an item icon,
|
||||
even though the engine stores and shows it correctly. These two functions
|
||||
write the icon basename directly. Any texture under `Interface\Icons\`,
|
||||
including an item icon, can be saved.
|
||||
|
||||
`CreateMacro(name, iconTexture, body, isCharacterMacro)`:
|
||||
- `name` — the macro name. This argument is necessary.
|
||||
- `iconTexture` — a string, for example `"INV_Sword_25"` or
|
||||
`"Interface\\Icons\\INV_Sword_25"`. The function removes the
|
||||
`Interface\Icons\` prefix, and adds it again when a caller reads the
|
||||
icon. A nil value sets no icon.
|
||||
- `body` — the macro text. A nil value sets an empty body.
|
||||
- `isCharacterMacro` — a true value puts the macro in the per-character
|
||||
tab. A false or nil value uses the general tab.
|
||||
- The function returns the 1-based index of the new macro. It returns nil
|
||||
when the list is full (18 macros per tab).
|
||||
|
||||
`EditMacro(index, name, iconTexture, body)`:
|
||||
- `index` — a 1-based slot number or a macro name.
|
||||
- `name` / `iconTexture` / `body` — a nil argument keeps that field
|
||||
unchanged.
|
||||
- The function returns the 1-based index of the macro. It returns nil when
|
||||
`index` does not match a macro that exists.
|
||||
|
||||
```lua
|
||||
-- item icon that legacy CreateMacro cannot set:
|
||||
local i = C_Macro.CreateMacro("Bag", "INV_Misc_Bag_08", "/say hi", false)
|
||||
-- change only the icon; name and body stay:
|
||||
C_Macro.EditMacro(i, nil, "INV_Potion_51", nil)
|
||||
-- edit by name:
|
||||
C_Macro.EditMacro("Bag", nil, nil, "/wave")
|
||||
```
|
||||
|
||||
The `iconTexture` argument must be a string. `CreateMacro` reads a number
|
||||
as no icon. `EditMacro` reads a number as no change. For the numeric-index
|
||||
path, use the legacy globals.
|
||||
|
||||
The legacy `CreateMacro` / `EditMacro` globals do not change. They stay
|
||||
index-only. String-icon callers use the `C_Macro` namespace instead.
|
||||
Edits persist across sessions, the same as edits in the Macro UI.
|
||||
|
||||
## Mail
|
||||
|
||||
Backports of modern WoW's mail-attachment link getters. Both follow
|
||||
|
||||
@@ -3407,6 +3407,54 @@ enum Offsets {
|
||||
// without re-parsing the body. See `Macro::Spell::Script_GetMacroSpell`.
|
||||
FUN_MACRO_SLOT_TO_ENTRY = 0x004F0E40,
|
||||
|
||||
// Macro create/edit workers — back `C_Macro.CreateMacro` /
|
||||
// `C_Macro.EditMacro` (see [[src/macro/Edit.cpp]]). Both store the
|
||||
// icon as a BARE basename at `entry + OFF_MACRO_ICON` (256 bytes,
|
||||
// no validation); `Script_GetMacroInfo` re-prefixes it as
|
||||
// `Interface\Icons\%s` on read, which is why any texture that lives
|
||||
// under `Interface\Icons\` — including index-less `INV_*` item icons
|
||||
// — round-trips even though vanilla's index-based UI can't pick it.
|
||||
//
|
||||
// Create — `__fastcall(char *name /*ECX*/, char *iconBasename /*EDX,
|
||||
// may be null*/, char *body /*may be null*/, uint local, int
|
||||
// perCharacter) -> uint macroID`. SStrCopys name→`+0x24` (0x40),
|
||||
// icon→`+0x64` (0x100) when non-null, body via `FUN_004f1550`
|
||||
// (`+0x164`), runs the primary-spell parser `FUN_004EFE00`, sets the
|
||||
// dirty flag `DAT_00bdcbf8=1`, and refreshes the UI. Returns 0 on
|
||||
// failure (empty name, or the 18-per-scope list is full). Pass
|
||||
// `local=0` to match the legacy `CreateMacro` default.
|
||||
FUN_MACRO_CREATE = 0x004F1010,
|
||||
|
||||
// Edit — `__fastcall(uint macroID, char *name, char *iconBasename,
|
||||
// char *body, uint flag)`. Hash-finds the entry by macroID (no-op if
|
||||
// absent); each of name/icon/body is written only when its pointer is
|
||||
// non-null (so nil = leave that field unchanged), then reparse + dirty
|
||||
// flag + UI refresh, exactly like the create worker. It ALWAYS writes
|
||||
// `entry + OFF_MACRO_LOCAL_FLAG = flag`, so read the current flag and
|
||||
// pass it back to leave it unchanged. Chosen over replicating the
|
||||
// field writes because it also fires the action-button refresh
|
||||
// (`FUN_004e5cc0`/`FUN_004e5c00`) that direct writes would miss.
|
||||
FUN_MACRO_EDIT = 0x004F1280,
|
||||
|
||||
// macroID → 0-based slot (linear scan of VAR_MACRO_SLOT_MAP);
|
||||
// `0xFFFFFFFF` on miss. `slot = FUN_MACRO_ID_TO_SLOT(macroID) + 1`
|
||||
// is how the legacy `CreateMacro` wrapper turns the worker's return
|
||||
// into the 1-based index it hands back to Lua.
|
||||
FUN_MACRO_ID_TO_SLOT = 0x004F0EC0,
|
||||
|
||||
// Macro name → 0-based slot; `0xFFFFFFFF` on miss (the resolver
|
||||
// behind `GetMacroIndexByName`). Feeding its miss value straight to
|
||||
// `FUN_MACRO_SLOT_TO_ENTRY` yields NULL (bounds-rejected), so the
|
||||
// by-name and by-index EditMacro paths share one validation.
|
||||
FUN_MACRO_NAME_TO_SLOT = 0x004F0FA0,
|
||||
|
||||
// MacroEntry field offsets. Name/body are handled by the workers; the
|
||||
// icon offset is documentation, and the local flag must be read to be
|
||||
// preserved across an EditMacro that doesn't mean to change it.
|
||||
OFF_MACRO_NAME = 0x24, // char[0x40] inline
|
||||
OFF_MACRO_ICON = 0x64, // char[0x100] inline bare basename
|
||||
OFF_MACRO_LOCAL_FLAG = 0x20, // uint32 `local` flag (echoed by GetMacroInfo)
|
||||
|
||||
// Macro-icon database. Populated lazily by `FUN_LOAD_MACRO_ICONS`
|
||||
// on the first `GetNumMacroIcons` call — enumerates `Interface\Icons\`
|
||||
// for `*.blp` files plus a wildcard match, sorts, and de-dupes. Each
|
||||
|
||||
@@ -106,14 +106,17 @@ static int __fastcall Script_GetInventoryItemID(void *L) {
|
||||
int itemID = 0;
|
||||
if (unitPtr == playerPtr) {
|
||||
itemID = ItemIDForLocalPlayer(playerPtr, slot);
|
||||
} else if (Unit::Flags::IsPlayerControlled(
|
||||
} else if (Unit::Flags::IsPlayerObject(
|
||||
static_cast<const uint8_t *>(unitPtr))) {
|
||||
itemID = ItemIDForOtherUnit(unitPtr, slot);
|
||||
} else {
|
||||
// NPC / creature — no visible-items array. Returning nil here
|
||||
// also matches `GetInventoryItemLink`'s practical behavior on
|
||||
// NPCs (it crashes the client without this gate; addons that
|
||||
// don't pre-filter the unit don't typically reach this path).
|
||||
// Non-player object — no CGPlayer visible-items array at +0xE68.
|
||||
// Gate on the object being a player, not merely player-controlled:
|
||||
// a pet/totem/MC'd creature sets UNIT_FLAG_PLAYER_CONTROLLED but
|
||||
// has no sub-struct there, so IsPlayerControlled would let it
|
||||
// through and `ItemIDForOtherUnit` would deref garbage and crash
|
||||
// (same root cause as pfUI issue #34). Returning nil also matches
|
||||
// `GetInventoryItemLink`'s practical behavior on non-players.
|
||||
return 0;
|
||||
}
|
||||
if (itemID == 0)
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// `C_Macro.CreateMacro` / `C_Macro.EditMacro` — create/edit macros whose
|
||||
// icon is given as a texture STRING (a full `Interface\Icons\<name>` path
|
||||
// or a bare `<name>` basename), not a `GetMacroIconInfo` index.
|
||||
//
|
||||
// C_Macro.CreateMacro(name, iconTexture, body, isCharacterMacro) -> index
|
||||
// C_Macro.EditMacro(index, name, iconTexture, body) -> index
|
||||
//
|
||||
// Why this exists: vanilla `CreateMacro`/`EditMacro` take an icon INDEX
|
||||
// into the `GetMacroIconInfo` list, and that list omits `INV_*` item
|
||||
// icons entirely — so item icons are unreachable through the stock API
|
||||
// even though the engine stores and renders them fine. The macro struct
|
||||
// keeps the icon as a bare basename at `entry + OFF_MACRO_ICON` and
|
||||
// `GetMacroInfo` reads it back as `Interface\Icons\%s`, so ANY texture
|
||||
// living under `Interface\Icons\` round-trips once you can write the
|
||||
// basename directly. These two functions do exactly that, unblocking a
|
||||
// pfUI macro-UI remodel that wants arbitrary icons.
|
||||
//
|
||||
// The legacy globals `CreateMacro`/`EditMacro` are left untouched
|
||||
// (index-only) — no overloading; string-icon callers opt in via the
|
||||
// `C_Macro` namespace.
|
||||
//
|
||||
// Implementation: both go straight to the engine's own workers
|
||||
// (`FUN_MACRO_CREATE` / `FUN_MACRO_EDIT`, see [[src/Offsets.h]]), which do
|
||||
// the bounded SStrCopy into the struct, re-run the primary-spell parser,
|
||||
// set the persist-on-logout dirty flag, and refresh the action bars.
|
||||
// We only translate the icon string to a basename and, for edits,
|
||||
// preserve the entry's `local` flag (the edit worker rewrites it
|
||||
// unconditionally).
|
||||
//
|
||||
// Icon input is STRING-only by design (that's the whole point — item
|
||||
// icons have no index). A numeric icon arg is treated as "no icon" on
|
||||
// create and "leave unchanged" on edit; callers who want the numeric
|
||||
// index path already have the legacy globals.
|
||||
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Macro::Edit {
|
||||
|
||||
namespace {
|
||||
|
||||
using MacroCreate_t = uint32_t(__fastcall *)(const char *name,
|
||||
const char *iconBasename,
|
||||
const char *body, uint32_t local,
|
||||
int perCharacter);
|
||||
using MacroEdit_t = void(__fastcall *)(uint32_t macroID, const char *name,
|
||||
const char *iconBasename,
|
||||
const char *body, uint32_t flag);
|
||||
using MacroIdToSlot_t = uint32_t(__fastcall *)(uint32_t macroID);
|
||||
using MacroNameToSlot_t = uint32_t(__fastcall *)(const char *name);
|
||||
using MacroSlotToEntry_t = uint8_t *(__fastcall *)(uint32_t slot0Based);
|
||||
|
||||
constexpr uint32_t kSlotMiss = 0xFFFFFFFFu;
|
||||
|
||||
// Last path component of `s` (after the final `\` or `/`). Turns
|
||||
// "Interface\\Icons\\INV_Sword_25" and a bare "INV_Sword_25" alike into
|
||||
// the basename the macro struct stores; the engine re-prefixes it with
|
||||
// `Interface\Icons\` on read. Aliases into `s` — no copy.
|
||||
const char *IconBasename(const char *s) {
|
||||
const char *bn = s;
|
||||
for (const char *p = s; *p != '\0'; ++p) {
|
||||
if (*p == '\\' || *p == '/')
|
||||
bn = p + 1;
|
||||
}
|
||||
return bn;
|
||||
}
|
||||
|
||||
// The icon to store, or nullptr (create: no icon; edit: leave unchanged).
|
||||
// String-only — see the file header.
|
||||
const char *IconArg(void *L, int idx) {
|
||||
if (!Game::Lua::IsString(L, idx))
|
||||
return nullptr;
|
||||
return IconBasename(Game::Lua::ToString(L, idx));
|
||||
}
|
||||
|
||||
// A body/name string arg, or nullptr when absent (edit: leave unchanged).
|
||||
const char *OptString(void *L, int idx) {
|
||||
return Game::Lua::IsString(L, idx) ? Game::Lua::ToString(L, idx) : nullptr;
|
||||
}
|
||||
|
||||
uint8_t *EntryForSlot(uint32_t slot0) {
|
||||
auto fn = reinterpret_cast<MacroSlotToEntry_t>(
|
||||
Offsets::FUN_MACRO_SLOT_TO_ENTRY);
|
||||
return fn(slot0);
|
||||
}
|
||||
|
||||
// C_Macro.CreateMacro(name, iconTexture, body, isCharacterMacro) -> index
|
||||
// Returns the new macro's 1-based slot, or nil when creation fails (empty
|
||||
// name, or the per-scope 18-macro list is full).
|
||||
int __fastcall Script_CreateMacro(void *L) {
|
||||
if (!Game::Lua::IsString(L, 1)) {
|
||||
Game::Lua::Error(
|
||||
L, "Usage: C_Macro.CreateMacro(name, iconTexture, body, "
|
||||
"isCharacterMacro)");
|
||||
return 0;
|
||||
}
|
||||
const char *name = Game::Lua::ToString(L, 1);
|
||||
const char *icon = IconArg(L, 2);
|
||||
const char *body = OptString(L, 3);
|
||||
const int perCharacter = Game::Lua::ToBoolean(L, 4);
|
||||
|
||||
auto create = reinterpret_cast<MacroCreate_t>(Offsets::FUN_MACRO_CREATE);
|
||||
const uint32_t macroID = create(name, icon, body, /*local*/ 0, perCharacter);
|
||||
if (macroID == 0) {
|
||||
Game::Lua::PushNil(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto idToSlot =
|
||||
reinterpret_cast<MacroIdToSlot_t>(Offsets::FUN_MACRO_ID_TO_SLOT);
|
||||
const uint32_t slot0 = idToSlot(macroID);
|
||||
if (slot0 == kSlotMiss) {
|
||||
Game::Lua::PushNil(L);
|
||||
return 1;
|
||||
}
|
||||
Game::Lua::PushNumber(L, static_cast<double>(slot0 + 1));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// C_Macro.EditMacro(index, name, iconTexture, body) -> index
|
||||
// `index` is a 1-based slot number or a macro name. Any of name /
|
||||
// iconTexture / body left nil is unchanged. Returns the macro's 1-based
|
||||
// slot, or nil when `index` doesn't resolve to an existing macro.
|
||||
int __fastcall Script_EditMacro(void *L) {
|
||||
uint32_t slot0;
|
||||
if (Game::Lua::IsNumber(L, 1)) {
|
||||
slot0 = static_cast<uint32_t>(static_cast<int>(Game::Lua::ToNumber(L, 1)) - 1);
|
||||
} else if (Game::Lua::IsString(L, 1)) {
|
||||
auto nameToSlot = reinterpret_cast<MacroNameToSlot_t>(
|
||||
Offsets::FUN_MACRO_NAME_TO_SLOT);
|
||||
slot0 = nameToSlot(Game::Lua::ToString(L, 1));
|
||||
} else {
|
||||
Game::Lua::Error(
|
||||
L, "Usage: C_Macro.EditMacro(index, name, iconTexture, body)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// One validation covers both a bad slot number and a name miss:
|
||||
// FUN_MACRO_NAME_TO_SLOT returns 0xFFFFFFFF on miss, which the slot
|
||||
// resolver bounds-rejects to NULL just like an out-of-range index.
|
||||
uint8_t *entry = EntryForSlot(slot0);
|
||||
if (entry == nullptr) {
|
||||
Game::Lua::PushNil(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const uint32_t macroID = *reinterpret_cast<const uint32_t *>(entry);
|
||||
// The edit worker rewrites entry+OFF_MACRO_LOCAL_FLAG unconditionally;
|
||||
// read the current value and hand it back so an edit that only touches
|
||||
// name/icon/body leaves the flag genuinely unchanged.
|
||||
const uint32_t flag = *reinterpret_cast<const uint32_t *>(
|
||||
entry + Offsets::OFF_MACRO_LOCAL_FLAG);
|
||||
|
||||
const char *name = OptString(L, 2);
|
||||
const char *icon = IconArg(L, 3);
|
||||
const char *body = OptString(L, 4);
|
||||
|
||||
auto edit = reinterpret_cast<MacroEdit_t>(Offsets::FUN_MACRO_EDIT);
|
||||
edit(macroID, name, icon, body, flag);
|
||||
|
||||
Game::Lua::PushNumber(L, static_cast<double>(slot0 + 1));
|
||||
return 1;
|
||||
}
|
||||
|
||||
void RegisterLuaFunctions() {
|
||||
Game::Lua::RegisterTableFunction("C_Macro", "CreateMacro",
|
||||
&Script_CreateMacro);
|
||||
Game::Lua::RegisterTableFunction("C_Macro", "EditMacro", &Script_EditMacro);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterLuaFunctions};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace Macro::Edit
|
||||
+7
-1
@@ -27,7 +27,13 @@ using ResolveUnitToken_t = void *(__fastcall *)(const char *token);
|
||||
// 0xC) and returns true on first match. Mirrors the engine's loop in
|
||||
// `Script_IsUnitOnQuest` (`0x004DFE10`).
|
||||
bool UnitHasQuest(const uint8_t *unit, int target) {
|
||||
if (target <= 0 || !Unit::Flags::IsPlayerControlled(unit))
|
||||
// The quest list lives in the CGPlayer sub-struct at +0xE68, which
|
||||
// only exists on real player objects. Gate on the object being a
|
||||
// player, not merely player-controlled — a pet/totem/MC'd creature
|
||||
// sets UNIT_FLAG_PLAYER_CONTROLLED but has no sub-struct there, so
|
||||
// IsPlayerControlled would deref garbage and crash (same root cause
|
||||
// as pfUI issue #34).
|
||||
if (target <= 0 || !Unit::Flags::IsPlayerObject(unit))
|
||||
return false;
|
||||
|
||||
auto *info = *reinterpret_cast<const uint8_t *const *>(
|
||||
|
||||
+23
-6
@@ -34,14 +34,23 @@ bool IsPlayerControlled(const uint8_t *unit) {
|
||||
return (unitFlags & Offsets::UNIT_FLAG_PLAYER_CONTROLLED) != 0;
|
||||
}
|
||||
|
||||
bool IsPlayerObject(const uint8_t *unit) {
|
||||
if (unit == nullptr)
|
||||
return false;
|
||||
return *reinterpret_cast<const int *>(unit + Offsets::OFF_CGOBJECT_TYPE_ID) ==
|
||||
Offsets::OBJECT_TYPE_PLAYER;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using ResolveUnitToken_t = void *(__fastcall *)(const char *token);
|
||||
|
||||
// Reads a PLAYER_FLAGS bit for any player-controlled unit. Path:
|
||||
// - Resolve unit; gate on `UNIT_FLAG_PLAYER_CONTROLLED` to avoid
|
||||
// reading the +0xE68 pointer on a creature/NPC (it's uninitialized
|
||||
// for non-player units).
|
||||
// Reads a PLAYER_FLAGS bit for a player. Path:
|
||||
// - Resolve unit; gate on the object actually being a player
|
||||
// (`OBJECT_TYPE_PLAYER`) so we never read the +0xE68 CGPlayer
|
||||
// sub-struct on a non-player object. UNIT_FLAG_PLAYER_CONTROLLED is
|
||||
// insufficient — pets/totems/MC'd creatures set it but have no
|
||||
// sub-struct there.
|
||||
// - Read `[unit + 0xE68] + 0x08` — the unit's CGPlayer-side info
|
||||
// struct, where byte +0x08 is PLAYER_FLAGS. Same struct
|
||||
// `Script_IsResting` reads at +0x05 bit (RESTING = 0x20) for the
|
||||
@@ -64,7 +73,10 @@ bool TestPlayerFlag(void *L, uint32_t flagMask) {
|
||||
|
||||
auto resolve = reinterpret_cast<ResolveUnitToken_t>(Offsets::FUN_RESOLVE_UNIT_TOKEN);
|
||||
auto *unit = static_cast<const uint8_t *>(resolve(token));
|
||||
if (!IsPlayerControlled(unit))
|
||||
// PLAYER_FLAGS lives in the CGPlayer sub-struct — gate on the object
|
||||
// actually being a player, not merely player-controlled (pets/totems
|
||||
// set that flag but have no sub-struct → garbage +0xE68 deref).
|
||||
if (!IsPlayerObject(unit))
|
||||
return false;
|
||||
|
||||
auto *playerInfo = *reinterpret_cast<const uint8_t *const *>(
|
||||
@@ -134,7 +146,12 @@ int __fastcall Script_UnitIsFeignDeath(void *L) {
|
||||
// Returns 0 if the unit isn't player-controlled, isn't a player, or
|
||||
// hasn't had its guild data synced.
|
||||
uint32_t ReadGuildKey(const uint8_t *unit) {
|
||||
if (!IsPlayerControlled(unit))
|
||||
// The guild key lives in the CGPlayer sub-struct at +0xE68, which
|
||||
// only exists on real player objects. A player-controlled *creature*
|
||||
// (pet, totem, MC'd mob) has UNIT_FLAG_PLAYER_CONTROLLED set but no
|
||||
// sub-struct, so gating on IsPlayerControlled here would deref garbage
|
||||
// and crash (issue: UnitIsInMyGuild on a nameplate pet/totem).
|
||||
if (!IsPlayerObject(unit))
|
||||
return 0;
|
||||
auto *info = *reinterpret_cast<const uint8_t *const *>(
|
||||
unit + Offsets::OFF_CGPLAYER_INFO);
|
||||
|
||||
+22
-5
@@ -16,14 +16,31 @@ namespace Unit::Flags {
|
||||
|
||||
// Tests `UNIT_FIELD_FLAGS & UNIT_FLAG_PLAYER_CONTROLLED` on the unit's
|
||||
// `m_objectFields` descriptor — same predicate
|
||||
// `Script_UnitPlayerControlled` (`0x00516410`) implements. Required
|
||||
// gate before dereferencing the CGPlayer-side sub-struct at
|
||||
// `unit + OFF_CGPLAYER_INFO` (= +0xE68) because that slot is
|
||||
// uninitialized for non-player CGUnit subclasses (CGCreature_C
|
||||
// instances) and reading it AVs on the subsequent deref.
|
||||
// `Script_UnitPlayerControlled` (`0x00516410`) implements. True for
|
||||
// players AND their pets/totems/guardians/companions/charms (the
|
||||
// server sets this flag on all of them — see Turtle
|
||||
// `UnitDefines.h` UNIT_FLAG_PLAYER_CONTROLLED).
|
||||
//
|
||||
// NOT a safe gate for reading the CGPlayer sub-struct at
|
||||
// `unit + OFF_CGPLAYER_INFO` (= +0xE68): a player-controlled *creature*
|
||||
// (pet/totem/MC'd mob) is a CGCreature_C with no such sub-struct, so
|
||||
// its +0xE68 slot is garbage-but-non-null and the subsequent deref AVs
|
||||
// (pfUI issue #34 — `UnitIsInMyGuild` on a nameplate pet). Use
|
||||
// `IsPlayerObject` for that. This predicate is for minion/ownership
|
||||
// classification, where the broad flag is what's wanted.
|
||||
//
|
||||
// Returns `false` for `nullptr` units and units with no
|
||||
// descriptor pointer yet (transient pre-spawn / out-of-sync state).
|
||||
bool IsPlayerControlled(const uint8_t *unit);
|
||||
|
||||
// True only for real player objects (`OBJECT_TYPE_PLAYER`), read from
|
||||
// the base CGObject `m_objectType` field at `[obj + 0x14]`
|
||||
// (`OFF_CGOBJECT_TYPE_ID`). This is the correct gate before reading the
|
||||
// CGPlayer sub-struct at `unit + OFF_CGPLAYER_INFO` (guild key,
|
||||
// PLAYER_FLAGS, visible items, quest list) — that sub-struct exists only
|
||||
// on player objects, never on player-controlled creatures.
|
||||
//
|
||||
// Returns `false` for `nullptr` units.
|
||||
bool IsPlayerObject(const uint8_t *unit);
|
||||
|
||||
} // namespace Unit::Flags
|
||||
|
||||
Reference in New Issue
Block a user