From: otta8634 Date: Tue, 4 Feb 2025 06:27:05 +0000 (+0800) Subject: Allow binding weapons individually in the menu X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=fc9687796da35b9b5ace347199b35fba812f893e;p=xonotic%2Fxonotic-data.pk3dir.git Allow binding weapons individually in the menu Previously weapon-groups could only be bound via the menu, meaning a player can't set a separate bind for Crylink and HLAC (for example). Added individual weapon bind keys under the group key in the menu. - Binding a group key will unbind all the child weapon binds, so that the user doesn't end up with multiple binds set for the one weapon. This is of course a legitimate thing to do, but is likely too confusing for inexperienced players. Added tree icons to make it more obvious which weapons are a part of the group. - Currently there's no tree icons for wickedx or luminos. Since this now shows weapon icons along with their names, this is enough to complete the menu-side of #2065. --- diff --git a/gfx/menu/luma/tree_branch.tga b/gfx/menu/luma/tree_branch.tga new file mode 100644 index 000000000..398048992 Binary files /dev/null and b/gfx/menu/luma/tree_branch.tga differ diff --git a/gfx/menu/luma/tree_branch_start.tga b/gfx/menu/luma/tree_branch_start.tga new file mode 100644 index 000000000..2c1235659 Binary files /dev/null and b/gfx/menu/luma/tree_branch_start.tga differ diff --git a/gfx/menu/luma/tree_elbow.tga b/gfx/menu/luma/tree_elbow.tga new file mode 100644 index 000000000..96c3c93e6 Binary files /dev/null and b/gfx/menu/luma/tree_elbow.tga differ diff --git a/gfx/menu/xaw/tree_branch.tga b/gfx/menu/xaw/tree_branch.tga new file mode 100644 index 000000000..095d2ba14 Binary files /dev/null and b/gfx/menu/xaw/tree_branch.tga differ diff --git a/gfx/menu/xaw/tree_branch_start.tga b/gfx/menu/xaw/tree_branch_start.tga new file mode 100644 index 000000000..cc13a2d36 Binary files /dev/null and b/gfx/menu/xaw/tree_branch_start.tga differ diff --git a/gfx/menu/xaw/tree_elbow.tga b/gfx/menu/xaw/tree_elbow.tga new file mode 100644 index 000000000..fb23d2854 Binary files /dev/null and b/gfx/menu/xaw/tree_elbow.tga differ diff --git a/qcsrc/menu/xonotic/keybinder.qc b/qcsrc/menu/xonotic/keybinder.qc index 93d245fb9..569a7ddef 100644 --- a/qcsrc/menu/xonotic/keybinder.qc +++ b/qcsrc/menu/xonotic/keybinder.qc @@ -10,21 +10,38 @@ const string KEY_NOT_BOUND_CMD = "// not bound"; const int MAX_KEYS_PER_FUNCTION = 2; const int MAX_KEYBINDS = 256; +int KeyBinds_Count = -1; string KeyBinds_Functions[MAX_KEYBINDS]; +// If the first character of the func is `*` it's a special keybind +// If the first character of the func is `;` it's an overrider keybind string KeyBinds_Descriptions[MAX_KEYBINDS]; -int KeyBinds_Count = -1; +// If the first character of the descr is `$` it's a user bind +string KeyBinds_Icons[MAX_KEYBINDS]; // Allow all keybinds to have an icon, although currently only weapons do +// The first character of the icon string is either ` ` for no tree-icon, `+` for a branch, `T` for the first branch, `L` for an elbow +// Remove this character before drawing the icon + +#define KEYBIND_IS_USERBIND(descr) (substring(descr, 0, 1) == "$") + +string XonoticKeyBinder_cb_name, XonoticKeyBinder_cb_icon; +void XonoticKeyBinder_cb(string _name, string _icon) +{ + XonoticKeyBinder_cb_name = _name; + XonoticKeyBinder_cb_icon = _icon; +} void KeyBinds_BuildList() { KeyBinds_Count = 0; - #define KEYBIND_DEF(func, desc) MACRO_BEGIN \ + #define KEYBIND_DEF_WITH_ICON(func, desc, icon) MACRO_BEGIN \ if((KeyBinds_Count < MAX_KEYBINDS)) { \ KeyBinds_Functions[KeyBinds_Count] = strzone(func); \ KeyBinds_Descriptions[KeyBinds_Count] = strzone(desc); \ + KeyBinds_Icons[KeyBinds_Count] = strzone(icon); \ ++KeyBinds_Count; \ } \ MACRO_END + #define KEYBIND_DEF(func, desc) KEYBIND_DEF_WITH_ICON(func, desc, "") #define KEYBIND_EMPTY_LINE() KEYBIND_DEF("", "") #define KEYBIND_HEADER(str) KEYBIND_DEF("", str) @@ -33,7 +50,14 @@ void KeyBinds_BuildList() // If a special keybind description matches the previous one // then it's considered as an alternative keybind of the previous one #define KEYBIND_IS_SPECIAL(func) (substring(func, 0 ,1) == "*") - #define KEYBIND_SPECIAL_DEF(key, desc) KEYBIND_DEF(strcat("*", key), desc) + #define KEYBIND_SPECIAL_DEF_WITH_ICON(func, desc, icon) KEYBIND_DEF_WITH_ICON(strcat("*", func), desc, icon) + + // Overriding keybinds unset other binds when they're set + // ... for the purposes of the menu, or for other reasons + // The other binds that need to be unset are concatenated to func with a ";" separator + #define KEYBIND_IS_OVERRIDER(func) (substring(func, 0 ,1) == ";") + #define KEYBIND_OVERRIDER_DEF_WITH_ICON(func, desc, icon) KEYBIND_DEF_WITH_ICON(strcat(";", func), desc, icon) + KEYBIND_HEADER(_("Moving")); KEYBIND_DEF("+forward" , _("move forwards")); @@ -59,27 +83,57 @@ void KeyBinds_BuildList() KEYBIND_DEF("reload" , _("reload")); KEYBIND_DEF("dropweapon" , _("drop weapon / throw nade")); - #define ADD_TO_W_LIST(pred) \ - FOREACH(Weapons, it != WEP_Null, { \ - if (it.impulse != imp) continue; \ - if (!(pred)) continue; \ - w_list = strcat(w_list, it.m_name, " / "); \ - }) + #define SHOWABLE_WEAPON(w) \ + (( !(it.spawnflags & (WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_SUPERWEAPON))) /* normal weapons */ \ + || ((it.spawnflags & WEP_FLAG_SUPERWEAPON) && !(it.spawnflags & WEP_FLAG_HIDDEN)) /* non-hidden superweapons */ \ + || ((it.spawnflags & WEP_FLAG_MUTATORBLOCKED) && !(it.spawnflags & WEP_FLAG_HIDDEN)) /* non-hidden mutator weapons */ \ + ) for(int imp = 1; imp <= 9; ++imp) { - string w_list = ""; - ADD_TO_W_LIST(!(it.spawnflags & (WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_SUPERWEAPON))); - ADD_TO_W_LIST((it.spawnflags & WEP_FLAG_SUPERWEAPON) && !(it.spawnflags & WEP_FLAG_HIDDEN)); - ADD_TO_W_LIST((it.spawnflags & WEP_FLAG_MUTATORBLOCKED) && !(it.spawnflags & WEP_FLAG_HIDDEN)); + string w_list = "", w_override_list = "", w_icon = ""; + int w_count = 0; + + FOREACH(Weapons, it != WEP_Null, { + if (it.impulse != imp || !SHOWABLE_WEAPON(it)) + continue; + it.display(it, XonoticKeyBinder_cb); + if (w_count) + w_list = strcat(w_list, " / ", XonoticKeyBinder_cb_name); + else + w_list = XonoticKeyBinder_cb_name; + w_override_list = strcat(w_override_list, ";weapon_", it.netname); + w_icon = XonoticKeyBinder_cb_icon; + ++w_count; + }); if(w_list) - KEYBIND_DEF(strcat("weapon_group_", itos(imp)), substring(w_list, 0, -4)); + { + if(w_count == 1) // group only has 1 weapon (like Blaster's group), use the group bind, and add the icon + { // no overrides needed + KEYBIND_DEF_WITH_ICON(strcat("weapon_group_", itos(imp)), w_list, strcat(" ", w_icon)); + } + else // group has multiple weapons, allow binding them separately or together + { // setting the group bind overrides the individual binds, and vice versa + string w_group = strcat(";weapon_group_", itos(imp)); + KEYBIND_DEF(strcat(w_group, w_override_list), w_list); + + int w_counter = 0; + FOREACH(Weapons, it != WEP_Null, { + if (it.impulse != imp || !SHOWABLE_WEAPON(it)) + continue; + it.display(it, XonoticKeyBinder_cb); + ++w_counter; + KEYBIND_DEF_WITH_ICON(strcat(";weapon_", it.netname, w_group), XonoticKeyBinder_cb_name, + strcat((w_counter == 1 ? "T" : w_counter == w_count ? "L" : "+"), XonoticKeyBinder_cb_icon)); + }); + } + } if(imp == 0) break; if(imp == 9) imp = -1; } - #undef ADD_TO_W_LIST + #undef SHOWABLE_WEAPON KEYBIND_EMPTY_LINE(); @@ -108,7 +162,7 @@ void KeyBinds_BuildList() // non-English keyboard layouts, unlike default keys (` and ~) KEYBIND_DEF("toggleconsole" , _("enter console")); string console_shortcut = strcat(translate_key("SHIFT"), "+", translate_key("ESCAPE")); - KEYBIND_SPECIAL_DEF(console_shortcut , _("enter console")); + KEYBIND_SPECIAL_DEF_WITH_ICON(console_shortcut , _("enter console"), ""); KEYBIND_DEF("menu_showquitdialog" , _("quit")); KEYBIND_EMPTY_LINE(); @@ -123,7 +177,7 @@ void KeyBinds_BuildList() KEYBIND_DEF("kill" , _("suicide / respawn")); KEYBIND_DEF("quickmenu" , _("quick menu")); string scoreboard_ui_shortcut = strcat(translate_key("TAB"), "+", translate_key("ESCAPE")); - KEYBIND_SPECIAL_DEF(scoreboard_ui_shortcut , _("scoreboard user interface")); + KEYBIND_SPECIAL_DEF_WITH_ICON(scoreboard_ui_shortcut, _("scoreboard user interface"), ""); KEYBIND_EMPTY_LINE(); KEYBIND_HEADER(_("User defined")); @@ -198,14 +252,17 @@ void XonoticKeyBinder_resizeNotify(entity me, vector relOrigin, vector relSize, { SUPER(XonoticKeyBinder).resizeNotify(me, relOrigin, relSize, absOrigin, absSize); - me.realFontSize_y = me.fontSize / (absSize.y * me.itemHeight); - me.realFontSize_x = me.fontSize / (absSize.x * (1 - me.controlWidth)); + me.itemAbsSize.y = absSize.y * me.itemHeight; + me.itemAbsSize.x = absSize.x * (1 - me.controlWidth); + me.realFontSize.y = me.fontSize / me.itemAbsSize.y; + me.realFontSize.x = me.fontSize / me.itemAbsSize.x; me.realUpperMargin = 0.5 * (1 - me.realFontSize.y); - me.columnFunctionOrigin = 0; + me.columnIconSize = me.itemAbsSize.y / me.itemAbsSize.x; me.columnKeysSize = me.realFontSize.x * 12; me.columnFunctionSize = 1 - me.columnKeysSize - 2 * me.realFontSize.x; - me.columnKeysOrigin = me.columnFunctionOrigin + me.columnFunctionSize + me.realFontSize.x; + // columnFunctionSize will need to be shortened if an icon is drawn + me.columnKeysOrigin = me.columnFunctionSize + me.realFontSize.x; } void KeyBinder_Bind_Change(entity btn, entity me) { @@ -220,7 +277,7 @@ void KeyBinder_Bind_Change(entity btn, entity me) } void XonoticKeyBinder_keyGrabbed(entity me, int key, bool ascii) { - int n, j, nvalid; + int n, j, i, nvalid; float k; me.keyGrabButton.forcePressed = 0; @@ -239,6 +296,29 @@ void XonoticKeyBinder_keyGrabbed(entity me, int key, bool ascii) string func = KeyBinds_Functions[me.selectedItem]; if(func == "" || KEYBIND_IS_SPECIAL(func)) return; + else if(KEYBIND_IS_OVERRIDER(func)) + { + n = tokenizebyseparator(func, ";"); + if(n <= 1) + return; + func = argv(1); + + // unset all binds for the functions this key overrides + for(j = 2; j < n; ++j) + { + n = tokenize(findkeysforcommand(argv(j), 0)); // uses '...' strings + for(i = 0; i < n; ++i) + { + k = stof(argv(i)); + if(k != -1) + { + // bind to empty cmd instead of using unbind so it gets saved in config and overrides any default binds + localcmd("\nbind \"", keynumtostring(k), "\" \"", KEY_NOT_BOUND_CMD, "\"\n"); + } + } + n = tokenizebyseparator(func, ";"); // reset argv back to the overridden functions + } + } n = tokenize(findkeysforcommand(func, 0)); // uses '...' strings nvalid = 0; @@ -274,6 +354,7 @@ void XonoticKeyBinder_destroy(entity me) { strfree(KeyBinds_Functions[i]); strfree(KeyBinds_Descriptions[i]); + strfree(KeyBinds_Icons[i]); } KeyBinds_Count = 0; } @@ -287,7 +368,7 @@ void XonoticKeyBinder_editUserbind(entity me, string theName, string theCommandP return; string descr = KeyBinds_Descriptions[me.selectedItem]; - if(substring(descr, 0, 1) != "$") + if(!KEYBIND_IS_USERBIND(descr)) return; descr = substring(descr, 1, strlen(descr) - 1); @@ -306,7 +387,7 @@ void KeyBinder_Bind_Edit(entity btn, entity me) return; string descr = KeyBinds_Descriptions[me.selectedItem]; - if(substring(descr, 0, 1) != "$") + if(!KEYBIND_IS_USERBIND(descr)) return; me.setSelected(me, me.selectedItem); // make it visible if it's hidden @@ -324,6 +405,13 @@ void KeyBinder_Bind_Clear(entity btn, entity me) string func = KeyBinds_Functions[me.selectedItem]; if(func == "" || KEYBIND_IS_SPECIAL(func)) return; + else if(KEYBIND_IS_OVERRIDER(func)) + { + n = tokenizebyseparator(func, ";"); + if(n <= 1) + return; + func = argv(1); + } me.setSelected(me, me.selectedItem); // make it visible if it's hidden @@ -383,7 +471,7 @@ void XonoticKeyBinder_setSelected(entity me, int i) if(KeyBinds_Functions[i] != "") me.previouslySelected = i; if(me.userbindEditButton) - me.userbindEditButton.disabled = (substring(KeyBinds_Descriptions[i], 0, 1) != "$"); + me.userbindEditButton.disabled = !KEYBIND_IS_USERBIND(KeyBinds_Descriptions[i]); SUPER(XonoticKeyBinder).setSelected(me, i); } float XonoticKeyBinder_keyDown(entity me, int key, bool ascii, float shift) @@ -416,9 +504,11 @@ void XonoticKeyBinder_drawListBoxItem(entity me, int i, vector absSize, bool isS vector theColor; float theAlpha; float extraMargin; + float descrWidth; string descr = KeyBinds_Descriptions[i]; string func = KeyBinds_Functions[i]; + string icon = KeyBinds_Icons[i]; if(func == "") { @@ -445,8 +535,9 @@ void XonoticKeyBinder_drawListBoxItem(entity me, int i, vector absSize, bool isS theColor = SKINCOLOR_KEYGRABBER_KEYS; extraMargin = me.realFontSize.x * 0.5; } + descrWidth = me.columnFunctionSize; - if(substring(descr, 0, 1) == "$") + if(KEYBIND_IS_USERBIND(descr)) { string s = substring(descr, 1, strlen(descr) - 1); descr = cvar_string(strcat(s, "_description")); @@ -457,19 +548,47 @@ void XonoticKeyBinder_drawListBoxItem(entity me, int i, vector absSize, bool isS theAlpha *= SKINALPHA_DISABLED; } - string s = draw_TextShortenToWidth(descr, me.columnFunctionSize, 0, me.realFontSize); + if (icon != "") + { + string tree_type = substring(icon, 0, 1); + float addedMargin = me.columnIconSize + 0.25 * me.realFontSize.x; + if (tree_type == "+" || tree_type == "T" || tree_type == "L") + { + draw_Picture(extraMargin * eX, + sprintf("/gfx/menu/%s/%s", cvar_string("menu_skin"), (tree_type == "+" ? "tree_branch" : tree_type == "T" ? "tree_branch_start" : "tree_elbow")), + me.columnIconSize * eX + eY, '1 1 1', SKINALPHA_LISTBOX_SELECTED); + extraMargin += addedMargin; + descrWidth -= addedMargin; + } + if (strlen(icon) > 1) // not just the tree icon + { + draw_Picture(extraMargin * eX, substring(icon, 1, strlen(icon) - 1), me.columnIconSize * eX + eY, '1 1 1', SKINALPHA_LISTBOX_SELECTED); + extraMargin += addedMargin; + descrWidth -= addedMargin; + } + } + + string s = draw_TextShortenToWidth(descr, descrWidth, 0, me.realFontSize); draw_Text(me.realUpperMargin * eY + extraMargin * eX, s, me.realFontSize, theColor, theAlpha, 0); if (func == "") return; + int n; s = ""; + if (KEYBIND_IS_OVERRIDER(func)) + { + n = tokenizebyseparator(func, ";"); + if(n <= 1) + return; + func = argv(1); + } if (KEYBIND_IS_SPECIAL(func)) s = substring(func, 1, -1); else { bool joy_active = cvar("joy_active"); - int n = tokenize(findkeysforcommand(func, 0)); // uses '...' strings + n = tokenize(findkeysforcommand(func, 0)); // uses '...' strings for(int j = 0; j < n; ++j) { float k = stof(argv(j)); @@ -487,3 +606,5 @@ void XonoticKeyBinder_drawListBoxItem(entity me, int i, vector absSize, bool isS s = draw_TextShortenToWidth(s, me.columnKeysSize, 0, me.realFontSize); draw_CenterText(me.realUpperMargin * eY + (me.columnKeysOrigin + 0.5 * me.columnKeysSize) * eX, s, me.realFontSize, theColor, theAlpha, 0); } + +#undef KEYBIND_IS_USERBIND diff --git a/qcsrc/menu/xonotic/keybinder.qh b/qcsrc/menu/xonotic/keybinder.qh index 7bdc8d695..bad24ab1d 100644 --- a/qcsrc/menu/xonotic/keybinder.qh +++ b/qcsrc/menu/xonotic/keybinder.qh @@ -13,9 +13,10 @@ CLASS(XonoticKeyBinder, XonoticListBox) METHOD(XonoticKeyBinder, keyGrabbed, void(entity, float, float)); METHOD(XonoticKeyBinder, destroy, void(entity)); + ATTRIB(XonoticKeyBinder, itemAbsSize, vector, '0 0 0'); ATTRIB(XonoticKeyBinder, realFontSize, vector, '0 0 0'); ATTRIB(XonoticKeyBinder, realUpperMargin, float, 0); - ATTRIB(XonoticKeyBinder, columnFunctionOrigin, float, 0); + ATTRIB(XonoticKeyBinder, columnIconSize, float, 0); ATTRIB(XonoticKeyBinder, columnFunctionSize, float, 0); ATTRIB(XonoticKeyBinder, columnKeysOrigin, float, 0); ATTRIB(XonoticKeyBinder, columnKeysSize, float, 0); diff --git a/qcsrc/menu/xonotic/weaponslist.qc b/qcsrc/menu/xonotic/weaponslist.qc index 4b5f7d5be..44845cdfc 100644 --- a/qcsrc/menu/xonotic/weaponslist.qc +++ b/qcsrc/menu/xonotic/weaponslist.qc @@ -58,6 +58,7 @@ void XonoticWeaponsList_resizeNotify(entity me, vector relOrigin, vector relSize me.realFontSize.y = me.fontSize / me.itemAbsSize.y; me.realFontSize.x = me.fontSize / me.itemAbsSize.x; me.realUpperMargin = 0.5 * (1 - me.realFontSize.y); + me.columnIconOrigin = 0; me.columnIconSize = me.itemAbsSize.y / me.itemAbsSize.x; me.columnNameOrigin = me.columnIconOrigin + me.columnIconSize + (0.5 * me.realFontSize.x); diff --git a/qcsrc/menu/xonotic/weaponslist.qh b/qcsrc/menu/xonotic/weaponslist.qh index 66128efda..05a2f0ae0 100644 --- a/qcsrc/menu/xonotic/weaponslist.qh +++ b/qcsrc/menu/xonotic/weaponslist.qh @@ -10,6 +10,7 @@ CLASS(XonoticWeaponsList, XonoticListBox) METHOD(XonoticWeaponsList, resizeNotify, void(entity, vector, vector, vector, vector)); METHOD(XonoticWeaponsList, keyDown, float(entity, float, float, float)); + ATTRIB(XonoticWeaponsList, itemAbsSize, vector, '0 0 0'); ATTRIB(XonoticWeaponsList, realFontSize, vector, '0 0 0'); ATTRIB(XonoticWeaponsList, realUpperMargin, float, 0); ATTRIB(XonoticWeaponsList, columnIconOrigin, float, 0);