Index: ps/trunk/binaries/data/config/default.cfg =================================================================== --- ps/trunk/binaries/data/config/default.cfg +++ ps/trunk/binaries/data/config/default.cfg @@ -297,7 +297,7 @@ move = "" ; Modifier to move to a point instead of another action (e.g. gather) attack = Ctrl ; Modifier to attack instead of another action (e.g. capture) attackmove = Ctrl ; Modifier to attackmove when clicking on a point -attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when clicking on a point (should contain the attackmove keys) +attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when clicking on a point garrison = Ctrl ; Modifier to garrison when clicking on building autorallypoint = Ctrl ; Modifier to set the rally point on the building itself guard = "G" ; Modifier to escort/guard when clicking on unit/building Index: ps/trunk/binaries/data/mods/public/gui/session/input.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/input.js +++ ps/trunk/binaries/data/mods/public/gui/session/input.js @@ -268,6 +268,12 @@ return entState && entState.player == g_ViewedPlayer; } +function isAttackMovePressed() +{ + return Engine.HotkeyIsPressed("session.attackmove") || + Engine.HotkeyIsPressed("session.attackmoveUnit"); +} + function isSnapToEdgesEnabled() { let config = Engine.ConfigDB_GetValue("user", "gui.session.snaptoedges"); @@ -1194,7 +1200,7 @@ entityDistribution.reverse(); Engine.PostNetworkCommand({ - "type": Engine.HotkeyIsPressed("session.attackmove") ? "attack-walk-custom" : "walk-custom", + "type": isAttackMovePressed() ? "attack-walk-custom" : "walk-custom", "entities": selection, "targetPositions": entityDistribution.map(pos => pos.toFixed(2)), "targetClasses": Engine.HotkeyIsPressed("session.attackmoveUnit") ? { "attack": ["Unit"] } : { "attack": ["Unit", "Structure"] }, Index: ps/trunk/binaries/data/mods/public/gui/session/unit_actions.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/unit_actions.js +++ ps/trunk/binaries/data/mods/public/gui/session/unit_actions.js @@ -124,7 +124,7 @@ }, "hotkeyActionCheck": function(target, selection) { - return Engine.HotkeyIsPressed("session.attackmove") && + return isAttackMovePressed() && this.actionCheck(target, selection); }, "actionCheck": function(target, selection) @@ -892,7 +892,7 @@ let data = { "command": "walk" }; let cursor = ""; - if (Engine.HotkeyIsPressed("session.attackmove")) + if (isAttackMovePressed()) { let targetClasses; if (Engine.HotkeyIsPressed("session.attackmoveUnit")) Index: ps/trunk/source/ps/Hotkey.cpp =================================================================== --- ps/trunk/source/ps/Hotkey.cpp +++ ps/trunk/source/ps/Hotkey.cpp @@ -310,9 +310,12 @@ if ((ev->ev.type == SDL_KEYDOWN) || (ev->ev.type == SDL_MOUSEBUTTONDOWN)) for (const SHotkeyMapping* hotkey : pressedHotkeys) { - if (hotkey->requires.size() + 1 < closestMapMatch) + if (std::find_if(newPressedHotkeys.begin(), newPressedHotkeys.end(), + [&hotkey](const SHotkeyMapping* v){ return v->name == hotkey->name; }) != newPressedHotkeys.end()) + continue; + else if (hotkey->requires.size() + 1 < closestMapMatch) releasedHotkeys.push_back(hotkey->name.c_str()); - else if (std::find(newPressedHotkeys.begin(), newPressedHotkeys.end(), hotkey) == newPressedHotkeys.end()) + else { // We need to check that all 'keys' are still pressed (because of mouse buttons). if (!isPressed(hotkey->primary)) Index: ps/trunk/source/ps/tests/test_Hotkeys.h =================================================================== --- ps/trunk/source/ps/tests/test_Hotkeys.h +++ ps/trunk/source/ps/tests/test_Hotkeys.h @@ -55,6 +55,8 @@ TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache")); configDB = new CConfigDB; + + g_scancodes = {}; } void tearDown() @@ -70,7 +72,7 @@ configDB->SetValueString(CFG_SYSTEM, "hotkey.A", "A"); configDB->SetValueString(CFG_SYSTEM, "hotkey.AB", "A+B"); configDB->SetValueString(CFG_SYSTEM, "hotkey.ABC", "A+B+C"); - configDB->SetValueString(CFG_SYSTEM, "hotkey.D", "D"); + configDB->SetValueList(CFG_SYSTEM, "hotkey.D", { "D", "D+E" }); configDB->WriteFile(CFG_SYSTEM, "config/conf.cfg"); configDB->Reload(CFG_SYSTEM); @@ -115,6 +117,7 @@ fakeInput("B", true); fakeInput("A", true); + // Activating the more precise hotkey AB untriggers "A" TS_ASSERT_EQUALS(HotkeyIsPressed("A"), false); TS_ASSERT_EQUALS(HotkeyIsPressed("AB"), true); TS_ASSERT_EQUALS(HotkeyIsPressed("ABC"), false); @@ -135,6 +138,36 @@ TS_ASSERT_EQUALS(HotkeyIsPressed("ABC"), false); TS_ASSERT_EQUALS(HotkeyIsPressed("D"), false); + fakeInput("A", false); + fakeInput("D", true); + TS_ASSERT_EQUALS(HotkeyIsPressed("A"), false); + TS_ASSERT_EQUALS(HotkeyIsPressed("AB"), false); + TS_ASSERT_EQUALS(HotkeyIsPressed("ABC"), false); + TS_ASSERT_EQUALS(HotkeyIsPressed("D"), true); + + fakeInput("E", true); + // Changing from one hotkey to another more specific combination of the same hotkey keeps it active + TS_ASSERT_EQUALS(HotkeyIsPressed("A"), false); + TS_ASSERT_EQUALS(HotkeyIsPressed("AB"), false); + TS_ASSERT_EQUALS(HotkeyIsPressed("ABC"), false); + TS_ASSERT_EQUALS(HotkeyIsPressed("D"), true); + fakeInput("E", false); + // Likewise going the other way. + TS_ASSERT_EQUALS(HotkeyIsPressed("D"), true); + } + + void test_quirk() + { + configDB->SetValueString(CFG_SYSTEM, "hotkey.A", "A"); + configDB->SetValueString(CFG_SYSTEM, "hotkey.AB", "A+B"); + configDB->SetValueString(CFG_SYSTEM, "hotkey.ABC", "A+B+C"); + configDB->SetValueList(CFG_SYSTEM, "hotkey.D", { "D", "D+E" }); + configDB->WriteFile(CFG_SYSTEM, "config/conf.cfg"); + configDB->Reload(CFG_SYSTEM); + + UnloadHotkeys(); + LoadHotkeys(*configDB); + /** * Quirk of the implementation: hotkeys are allowed to fire with too many keys. * Further, hotkeys of the same specificity (i.e. same # of required keys)