mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-10 20:07:37 +00:00
feat(options): key binding rows for ClassicAPI's bindings
The Interact Key page gets rows for Interact With Target and Interact With Mouseover, and Bags and Items gets AoE Loot under a Loot header. A row is retail's settings-page binding row: the binding's name, then a button for each of its two keys. Left click a button and press a key, mouse button 3-5 or the wheel to bind it; Escape cancels; right click unbinds that key. The tooltip on a bound key and the page's output line use retail's strings, added to every locale with Blizzard's translations. The binding is 1.12's Key Bindings window's (Blizzard_BindingUI): the same ALT-CTRL-SHIFT chord order, the same skipped modifier keys, the same KEY_UNBOUND_ERROR check for an action that loses its last key, and the same put-back when SetBinding refuses a key. A change saves at once with SaveBindings(GetCurrentBindingSet()), so it lands in the account or character set in use, as every other control on these pages writes at once. While a button listens, a full-screen button on FULLSCREEN_DIALOG takes keys, clicks and the wheel, as retail's KeybindListener and its input blocker do. A left or right click on it cancels, and so does hiding the page.
This commit is contained in:
@@ -211,8 +211,8 @@ for what each command does, and
|
||||
|
||||
Each setting is saved in `WTF\Config.wtf`. Read and write them with `GetCVar`
|
||||
/ `SetCVar` or `/console`. Click a group name for the full rules. To change
|
||||
these settings and the player cache in the game, open the game menu and
|
||||
click AddOn Options, then ClassicAPI.
|
||||
these settings, the player cache and the ClassicAPI key bindings in the game,
|
||||
open the game menu and click AddOn Options, then ClassicAPI.
|
||||
|
||||
| Group | CVar | Default | What it does |
|
||||
|-------|------|---------|--------------|
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
-- ClassicAPI's own entry in the AddOn Options window
|
||||
-- (FrameXML\InterfaceOptions.lua): a ClassicAPI page with one page under it
|
||||
-- for each feature. Every cvar that ClassicAPI registers has a control here,
|
||||
-- and so does the player cache.
|
||||
-- and so do the player cache and ClassicAPI's key bindings.
|
||||
--
|
||||
-- Each control reads and writes exactly one setting, and writes it only when
|
||||
-- the player uses that control. So the pages have no okay, cancel or default:
|
||||
-- Okay, Cancel and Escape only close the window, and Defaults does nothing
|
||||
-- here. A page reads its settings again each time it is shown.
|
||||
-- here. A page reads its settings again each time it is shown. A key binding
|
||||
-- saves at once into the binding set in use (account or character).
|
||||
--
|
||||
-- A page's controls scroll under its heading in a ScrollBox, so a page holds
|
||||
-- as many as its feature has. Dropdowns and number boxes sit on the same row
|
||||
-- as their label.
|
||||
--
|
||||
-- Every string is a global from locales\enUS.lua: retail's GlobalStrings key
|
||||
-- where retail has the string (ENABLE_INTERACT_TEXT and the like), a
|
||||
-- CLASSICAPI_ one where it does not.
|
||||
-- Every string is a global: the client's own (KEY_BOUND, NOT_BOUND), or one
|
||||
-- from locales\enUS.lua - retail's GlobalStrings key where retail has the
|
||||
-- string (ENABLE_INTERACT_TEXT and the like), a CLASSICAPI_ one where it
|
||||
-- does not.
|
||||
--
|
||||
-- Lua 5.0: no `...` expression or select().
|
||||
|
||||
@@ -302,6 +304,215 @@ local function AddSlider(page, label, tooltip, setting, minValue, maxValue, step
|
||||
AddControl(page, slider, setting, 56)
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Key bindings
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- A key binding row, as on retail's settings pages: the binding's name, then
|
||||
-- a button for each of its two keys. Left click a button and press a key, a
|
||||
-- mouse button or the wheel to bind it there; Escape cancels. Right click a
|
||||
-- button to unbind its key.
|
||||
--
|
||||
-- While a button listens, `listener` covers the screen above the window and
|
||||
-- takes every key, click and wheel turn, as retail's KeybindListener and its
|
||||
-- input blocker do. A left or right click on it cancels. The binding itself
|
||||
-- is 1.12's Key Bindings window's: the same chord order, the same check for
|
||||
-- an action that loses its last key, the same put-back when SetBinding
|
||||
-- refuses a key.
|
||||
--
|
||||
-- Messages go on the page's output line, under its controls.
|
||||
|
||||
local LISTEN_BUTTONS = {
|
||||
MiddleButton = "BUTTON3",
|
||||
Button4 = "BUTTON4",
|
||||
Button5 = "BUTTON5",
|
||||
}
|
||||
|
||||
-- Keys that only modify another key, as the Key Bindings window skips them.
|
||||
local IGNORED_KEYS = { UNKNOWN = true, SHIFT = true, CTRL = true, ALT = true }
|
||||
|
||||
local listener = CreateFrame("Button", nil, UIParent)
|
||||
listener:SetAllPoints(UIParent)
|
||||
listener:SetFrameStrata("FULLSCREEN_DIALOG")
|
||||
listener:EnableMouse(true)
|
||||
listener:EnableMouseWheel(true)
|
||||
listener:EnableKeyboard(true)
|
||||
listener:RegisterForClicks("LeftButtonUp", "RightButtonUp", "MiddleButtonUp", "Button4Up", "Button5Up")
|
||||
listener:Hide()
|
||||
|
||||
local function BindingName(action)
|
||||
return GetBindingText(action, "BINDING_NAME_")
|
||||
end
|
||||
|
||||
-- GetBindingKey's keys as a list: a row's button `i` shows keys[i].
|
||||
local function BindingKeys(action)
|
||||
return { GetBindingKey(action) }
|
||||
end
|
||||
|
||||
local function StopListening()
|
||||
local row = listener.row
|
||||
if not row then return end
|
||||
listener.row = nil
|
||||
listener:Hide()
|
||||
row.page.output:SetText("")
|
||||
RefreshControls(row.page)
|
||||
end
|
||||
|
||||
local function StartListening(row, index)
|
||||
-- A number box that keeps the focus would take the key instead.
|
||||
for _, control in ipairs(row.page.controls) do
|
||||
if control.ClearFocus then control:ClearFocus() end
|
||||
end
|
||||
listener.row = row
|
||||
listener.index = index
|
||||
listener:Show()
|
||||
row.page.output:SetText(format(SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL, BindingName(row.action), GetBindingText("ESCAPE", "KEY_")))
|
||||
RefreshControls(row.page)
|
||||
end
|
||||
|
||||
-- 1.12's chord order: ALT-CTRL-SHIFT-key.
|
||||
local function Chord(key)
|
||||
if IsShiftKeyDown() then key = "SHIFT-" .. key end
|
||||
if IsControlKeyDown() then key = "CTRL-" .. key end
|
||||
if IsAltKeyDown() then key = "ALT-" .. key end
|
||||
return key
|
||||
end
|
||||
|
||||
local function Bind(key)
|
||||
if IGNORED_KEYS[key] then return end
|
||||
local chord = Chord(key)
|
||||
local current = GetBindingAction(chord)
|
||||
if current == "SCREENSHOT" then
|
||||
Screenshot()
|
||||
return
|
||||
end
|
||||
|
||||
local row, index = listener.row, listener.index
|
||||
StopListening()
|
||||
if key == "ESCAPE" and current == "TOGGLEGAMEMENU" then return end
|
||||
|
||||
local action = row.action
|
||||
local message = KEY_BOUND
|
||||
if current ~= "" and current ~= action then
|
||||
local old1, old2 = GetBindingKey(current)
|
||||
if (not old1 or old1 == chord) and (not old2 or old2 == chord) then
|
||||
message = format(KEY_UNBOUND_ERROR, BindingName(current))
|
||||
end
|
||||
end
|
||||
|
||||
-- Unbind both keys, then bind them back in order with the new key in
|
||||
-- the slot that was clicked.
|
||||
local keys = BindingKeys(action)
|
||||
for i = 1, 2 do
|
||||
if keys[i] then SetBinding(keys[i]) end
|
||||
end
|
||||
for i = 1, 2 do
|
||||
local bind = (i == index) and chord or keys[i]
|
||||
if bind and not SetBinding(bind, action) then
|
||||
if keys[i] then SetBinding(keys[i], action) end
|
||||
message = KEYBINDINGFRAME_MOUSEWHEEL_ERROR
|
||||
end
|
||||
end
|
||||
SaveBindings(GetCurrentBindingSet())
|
||||
|
||||
RefreshControls(row.page)
|
||||
row.page.output:SetText(message)
|
||||
end
|
||||
|
||||
listener:SetScript("OnKeyDown", function() Bind(arg1) end)
|
||||
listener:SetScript("OnMouseWheel", function()
|
||||
Bind(arg1 > 0 and "MOUSEWHEELUP" or "MOUSEWHEELDOWN")
|
||||
end)
|
||||
listener:SetScript("OnClick", function()
|
||||
local button = LISTEN_BUTTONS[arg1]
|
||||
if button then Bind(button) else StopListening() end
|
||||
end)
|
||||
|
||||
local function Unbind(row, index)
|
||||
local key = BindingKeys(row.action)[index]
|
||||
if key then
|
||||
SetBinding(key)
|
||||
SaveBindings(GetCurrentBindingSet())
|
||||
end
|
||||
GameTooltip:Hide()
|
||||
row.page.output:SetText("")
|
||||
RefreshControls(row.page)
|
||||
end
|
||||
|
||||
-- The page's output line, made with its first key binding row. It takes the
|
||||
-- bottom of the page from the ScrollBox.
|
||||
local function EnsureOutput(page)
|
||||
if page.output then return end
|
||||
local output = page:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
|
||||
output:SetPoint("BOTTOMLEFT", page, "BOTTOMLEFT", 16, 12)
|
||||
output:SetPoint("RIGHT", page, "RIGHT", -16, 0)
|
||||
output:SetJustifyH("LEFT")
|
||||
page.scrollBox:SetPoint("BOTTOMRIGHT", page, "BOTTOMRIGHT", -20, 30)
|
||||
page.output = output
|
||||
page:SetScript("OnHide", function()
|
||||
if listener.row and listener.row.page == this then StopListening() end
|
||||
this.output:SetText("")
|
||||
end)
|
||||
end
|
||||
|
||||
local function AddKeybinding(page, action)
|
||||
EnsureOutput(page)
|
||||
AddRowLabel(page, BindingName(action))
|
||||
|
||||
local row = { page = page, action = action, buttons = {} }
|
||||
for i = 1, 2 do
|
||||
local button = CreateFrame("Button", NextName(), page.content, "UIPanelButtonTemplate2")
|
||||
button:SetWidth(86)
|
||||
button:SetHeight(22)
|
||||
if i == 1 then
|
||||
button:SetPoint("TOPLEFT", page.content, "TOPLEFT", CONTROL_X, page.y - 5)
|
||||
else
|
||||
button:SetPoint("LEFT", row.buttons[1], "RIGHT", 0, 0)
|
||||
end
|
||||
button:SetTextFontObject(GameFontHighlightSmall)
|
||||
button:SetHighlightFontObject(GameFontHighlightSmall)
|
||||
button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
||||
button.index = i
|
||||
button:SetScript("OnClick", function()
|
||||
if arg1 == "RightButton" then
|
||||
Unbind(row, this.index)
|
||||
else
|
||||
StartListening(row, this.index)
|
||||
end
|
||||
end)
|
||||
button:SetScript("OnEnter", function()
|
||||
local key = BindingKeys(action)[this.index]
|
||||
if not key then return end
|
||||
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
|
||||
GameTooltip:SetText(format(KEY_BINDING_NAME_AND_KEY, BindingName(action), GetBindingText(key, "KEY_")),
|
||||
HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
|
||||
GameTooltip:AddLine(KEY_BINDING_TOOLTIP, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1)
|
||||
GameTooltip:Show()
|
||||
end)
|
||||
button:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
||||
row.buttons[i] = button
|
||||
end
|
||||
|
||||
row.Update = function(self)
|
||||
local keys = BindingKeys(action)
|
||||
for i, button in ipairs(self.buttons) do
|
||||
if keys[i] then
|
||||
button:SetText(GetBindingText(keys[i], "KEY_"))
|
||||
button:SetAlpha(1)
|
||||
else
|
||||
button:SetText(NORMAL_FONT_COLOR_CODE .. NOT_BOUND .. FONT_COLOR_CODE_CLOSE)
|
||||
button:SetAlpha(0.8)
|
||||
end
|
||||
if listener.row == self and listener.index == i then
|
||||
button:LockHighlight()
|
||||
else
|
||||
button:UnlockHighlight()
|
||||
end
|
||||
end
|
||||
end
|
||||
AddControl(page, row, nil, 32)
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- ClassicAPI
|
||||
-- ---------------------------------------------------------------------------
|
||||
@@ -316,6 +527,8 @@ local main = NewPage("ClassicAPI", nil, "ClassicAPI " .. version, CLASSICAPI_OPT
|
||||
local interact = NewPage(CLASSICAPI_INTERACT_KEY, main.name)
|
||||
|
||||
AddCheckbox(interact, ENABLE_INTERACT_TEXT, OPTION_TOOLTIP_ENABLE_INTERACT, SoftTargetSetting("SoftTargetInteract"))
|
||||
AddKeybinding(interact, "INTERACTTARGET")
|
||||
AddKeybinding(interact, "INTERACTMOUSEOVER")
|
||||
AddDropDown(interact, CLASSICAPI_INTERACT_DIRECTION, CLASSICAPI_INTERACT_DIRECTION_TOOLTIP, NumberCVarSetting("SoftTargetInteractArc"), ARC_CHOICES)
|
||||
AddNumberBox(interact, CLASSICAPI_INTERACT_RANGE, CLASSICAPI_RANGE_TOOLTIP, NumberCVarSetting("SoftTargetInteractRange"))
|
||||
AddCheckbox(interact, CLASSICAPI_STRICT_INTERACT_RANGE, CLASSICAPI_STRICT_INTERACT_RANGE_TOOLTIP, CVarSetting("SoftTargetInteractRangeIsHard"))
|
||||
@@ -378,6 +591,9 @@ AddCheckbox(bags, CLASSICAPI_IGNORE_BACKPACK, CLASSICAPI_IGNORE_BACKPACK_TOOLTIP
|
||||
AddCheckbox(bags, CLASSICAPI_IGNORE_BANK, CLASSICAPI_IGNORE_BANK_TOOLTIP, CVarSetting("bankAutosortDisabled"))
|
||||
AddCheckbox(bags, CLASSICAPI_HIDE_EQUIPMENT_SETS, CLASSICAPI_HIDE_EQUIPMENT_SETS_TOOLTIP, CVarSetting("dontShowEquipmentSetsOnItems"))
|
||||
|
||||
AddHeader(bags, LOOT)
|
||||
AddKeybinding(bags, "AOELOOT")
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Interface
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
@@ -69,6 +69,9 @@ OPTION_TOOLTIP_COUNTDOWN_FOR_COOLDOWNS = "Markieren, um für Abklingzeiten einen
|
||||
OPTION_TOOLTIP_ENABLE_INTERACT = "Erlaubt Interaktion mit NSCs und Objekten per Tastendruck";
|
||||
OPTION_TOOLTIP_INTERACT_ICONS = "Legt fest, welche Symbole über NSCs und Gegenständen angezeigt werden, wenn die Interaktionstastenfunktion aktiviert ist.";
|
||||
OPTION_TOOLTIP_STOP_AUTO_ATTACK = "Automatischen Angriff stoppen, sobald das Ziel gewechselt wird.";
|
||||
KEY_BINDING_NAME_AND_KEY = "%s (%s)";
|
||||
KEY_BINDING_TOOLTIP = "<Zum Löschen der Belegung rechtsklicken>";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "Tastaturbelegung für \"%s\" zuweisen oder mit %s abbrechen";
|
||||
SET_FOCUS = "Fokus setzen";
|
||||
FOCUSTARGET = "Ziel als Fokus festlegen";
|
||||
GEARSETS_POPUP_TEXT = "Setnamen eingeben (Max. 16 Zeichen):";
|
||||
|
||||
@@ -159,6 +159,9 @@ OPTION_TOOLTIP_COUNTDOWN_FOR_COOLDOWNS = "Check this to show a number countdown
|
||||
OPTION_TOOLTIP_ENABLE_INTERACT = "Allows you to interact with NPCs and objects using a keypress";
|
||||
OPTION_TOOLTIP_INTERACT_ICONS = "Controls which icons appear above NPCs and objects when the Interact Key feature is enabled";
|
||||
OPTION_TOOLTIP_STOP_AUTO_ATTACK = "Stop auto attacking when you switch targets.";
|
||||
KEY_BINDING_NAME_AND_KEY = "%s (%s)";
|
||||
KEY_BINDING_TOOLTIP = "<Right click to unbind>";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "Assign Binding for \"%s\" or Press %s to Cancel";
|
||||
SET_FOCUS = "Set Focus";
|
||||
FOCUS_TOKEN_NOT_FOUND = "<no focus>";
|
||||
FOCUSTARGET = "Focus Target";
|
||||
|
||||
@@ -69,6 +69,9 @@ OPTION_TOOLTIP_COUNTDOWN_FOR_COOLDOWNS = "Activa esta opción para mostrar una c
|
||||
OPTION_TOOLTIP_ENABLE_INTERACT = "Te permite interactuar con PNJ y objetos pulsando una tecla.";
|
||||
OPTION_TOOLTIP_INTERACT_ICONS = "Controla los iconos que aparecen sobre los PNJ y objetos cuando se habilita la opción de tecla Interactuar";
|
||||
OPTION_TOOLTIP_STOP_AUTO_ATTACK = "Detiene el ataque automático al cambiar de objetivo.";
|
||||
KEY_BINDING_NAME_AND_KEY = "%s (%s)";
|
||||
KEY_BINDING_TOOLTIP = "<Haz clic derecho para quitar la asignación>";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "Asignar atajo para \"%s\" o pulsar %s para cancelar";
|
||||
SET_FOCUS = "Establecer foco";
|
||||
FOCUSTARGET = "Establecer objetivo";
|
||||
GEARSETS_POPUP_TEXT = "Nombra este equipamiento (Máx. 16 carac.):";
|
||||
|
||||
@@ -69,6 +69,9 @@ OPTION_TOOLTIP_COUNTDOWN_FOR_COOLDOWNS = "Cochez pour afficher les temps de rech
|
||||
OPTION_TOOLTIP_ENABLE_INTERACT = "Vous permet d’interagir avec les PNJ et les objets en appuyant sur une touche";
|
||||
OPTION_TOOLTIP_INTERACT_ICONS = "Contrôle les icônes qui s’affichent au-dessus des PNJ et des objets quand la fonction Commande d’interaction est activée";
|
||||
OPTION_TOOLTIP_STOP_AUTO_ATTACK = "Interrompt l'attaque automatique lorsque vous changez de cible.";
|
||||
KEY_BINDING_NAME_AND_KEY = "%s (%s)";
|
||||
KEY_BINDING_TOOLTIP = "<Clic droit pour retirer>";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "Assigner un raccourci pour « %s » ou appuyer sur %s pour annuler";
|
||||
SET_FOCUS = "Définir la focalisation";
|
||||
FOCUSTARGET = "Définir la cible de focalisation";
|
||||
ITEMS_VARIABLE_QUANTITY = "%d |4objet:objets;";
|
||||
|
||||
@@ -10,3 +10,6 @@ SECOND_NUMBER_CAP = "억";
|
||||
SECOND_NUMBER_CAP_NO_SPACE = "억";
|
||||
THIRD_NUMBER = "억";
|
||||
FOURTH_NUMBER = "조";
|
||||
KEY_BINDING_NAME_AND_KEY = "%s (%s)";
|
||||
KEY_BINDING_TOOLTIP = "<지정을 해제하려면 오른쪽 클릭>";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "\"%s\" 단축키 지정 또는 %s 눌러 취소";
|
||||
|
||||
@@ -69,6 +69,9 @@ OPTION_TOOLTIP_COUNTDOWN_FOR_COOLDOWNS = "Отображение обратно
|
||||
OPTION_TOOLTIP_ENABLE_INTERACT = "Позволяет взаимодействовать с NPC и объектами по нажатию клавиши.";
|
||||
OPTION_TOOLTIP_INTERACT_ICONS = "Определяет, какие значки появляются над NPC и объектами, если включен параметр клавиши взаимодействия.";
|
||||
OPTION_TOOLTIP_STOP_AUTO_ATTACK = "Прекращение автоматической атаки при смене цели.";
|
||||
KEY_BINDING_NAME_AND_KEY = "%s (%s)";
|
||||
KEY_BINDING_TOOLTIP = "<ПКМ, чтобы отвязать>";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "Назначьте клавишу \"%s\"или нажмите %s для отмены";
|
||||
SET_FOCUS = "Запомнить цель";
|
||||
FOCUSTARGET = "Запомнить цель";
|
||||
ITEMS_VARIABLE_QUANTITY = "%d |4предмет:предмета:предметов;";
|
||||
|
||||
@@ -69,6 +69,9 @@ OPTION_TOOLTIP_COUNTDOWN_FOR_COOLDOWNS = "勾选此项后,动作条上的按
|
||||
OPTION_TOOLTIP_ENABLE_INTERACT = "使你可以通过按键与NPC和物体进行交互";
|
||||
OPTION_TOOLTIP_INTERACT_ICONS = "控制交互按键启用后NPC和物体上方出现的图标";
|
||||
OPTION_TOOLTIP_STOP_AUTO_ATTACK = "当你切换目标时停止自动攻击。";
|
||||
KEY_BINDING_NAME_AND_KEY = "%s(%s)";
|
||||
KEY_BINDING_TOOLTIP = "<右键解除键位>";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "设置 \"%s\"的快捷键,或者按 %s 取消";
|
||||
SET_FOCUS = "设置焦点";
|
||||
FOCUSTARGET = "设定焦点目标";
|
||||
ITEMS_VARIABLE_QUANTITY = "%d件物品";
|
||||
|
||||
@@ -69,6 +69,9 @@ OPTION_TOOLTIP_COUNTDOWN_FOR_COOLDOWNS = "勾選此選項可在快捷鍵上面
|
||||
OPTION_TOOLTIP_ENABLE_INTERACT = "可透過按鍵與NPC和物件互動";
|
||||
OPTION_TOOLTIP_INTERACT_ICONS = "在啟用互動鍵功能後,控制出現在NPC和物件上方的圖示";
|
||||
OPTION_TOOLTIP_STOP_AUTO_ATTACK = "當你切換目標時停止自動攻擊。";
|
||||
KEY_BINDING_NAME_AND_KEY = "%s(%s)";
|
||||
KEY_BINDING_TOOLTIP = "<右鍵點擊以解除綁定>";
|
||||
SETTINGS_BIND_KEY_TO_COMMAND_OR_CANCEL = "為\"%s\"指配綁定或是按下%s以取消";
|
||||
SET_FOCUS = "設定專注目標";
|
||||
FOCUSTARGET = "設定鎖定目標";
|
||||
ITEMS_VARIABLE_QUANTITY = "%d件物品";
|
||||
|
||||
Reference in New Issue
Block a user