Skip to content

Adding Key Bindings for CQUI

the-m4a edited this page Dec 29, 2020 · 1 revision

Background

This page provides details on what CQUI (and other mods) do in order to add additional key handlers to actions in the game, beyond those offered in-box from Firaxis. Reviewing this information below is useful for understanding the example at the bottom of this article.

Key Concepts to Know

  • Keys and mouse clicks in Civ 6 are split into two phases: "down" and "up". Down is the act of pressing a key or mouse button down, up is the act of releasing that key or button. Most times, work associated with the in-game action bound to a key is happens in the "up" phase.

  • Keys and clicks can be handled by anything that registers for an input handler, by using the API: ContextPtr:SetInputHandler.

    • The important thing to know is if your function has handled the input it received, it should return true. If other code should handle that key action, then your function should return false.
    • The Civ6 document that is packaged with the Civ6 SDK (Civ6Docs.html) provides more detail on SetInputHandler.
  • The default message handlers are defined in WorldInput.lua.

    • Any files that had set up their own input handlers via ContextPtr:SetInputHandler will be called before the default handlers are called in WorldInput.lua.
  • CQUI implements many of its custom key handlers in the DefaultKeyUpHandler in worldinput_CQUI.lua.

CQUI and WorldInput.lua

CQUI extends the base WorldInput.lua functionality implemented by Firaxis, implementing this logic in worldinput_CQUI.lua. There are a few important things to notice with what CQUI does:

CQUI uses the ReplaceUIScript action in the modinfo file to tell Civ6 that the WorldInput LuaContext should load worldinput_CQUI_basegame.lua.

        <ReplaceUIScript id="CQUI_WorldInput">
            <Properties>
                <LuaContext>WorldInput</LuaContext>
                <LuaReplace>Assets/UI/worldinput_CQUI_basegame.lua</LuaReplace>
            </Properties>
        </ReplaceUIScript>

worldinput_CQUI_basegame.lua consists of two functional lines of code, the first of which loads the Firaxis version of WorldInput.lua, the second of which then loads the CQUI version of the same file:

include("WorldInput");
include("worldinput_CQUI.lua");

In the worldinput_CQUI.lua file, CQUI (basically) creates copies of a few functions defined in the Firaxis WorldInput.lua, so that these existing functions can be called later on.

-- ===========================================================================
-- Cached Base Functions
-- ===========================================================================
BASE_CQUI_UpdateDragMap            = UpdateDragMap;
BASE_CQUI_OnUnitSelectionChanged   = OnUnitSelectionChanged;
BASE_CQUI_DefaultKeyDownHandler    = DefaultKeyDownHandler;
BASE_CQUI_DefaultKeyUpHandler      = DefaultKeyUpHandler;
BASE_CQUI_ClearAllCachedInputState = ClearAllCachedInputState;
BASE_CQUI_OnUserRequestClose       = OnUserRequestClose;

Of interest to this article are the DefaultKeyDownHandler and DefaultKeyUpHandler functions. CQUI will define functions with the same name later on in worldinput_CQUI.lua, and call those original functions from the Firaxis WorldInput.lua by using this new name (see below).

In DefaultKeyDownHandler, CQUI first calls the function in the Firaxis WorldInput.lua by using this "cached" base function. For CQUI, the primary use of DefaultKeyDownHandler is to know if SHIFT or ALT have been pressed down, and to keep track of that.

-- ===========================================================================
function DefaultKeyDownHandler( uiKey:number )
    -- print_debug("** Function Entry: DefaultKeyDownHandler (CQUI Hook).  uiKey: "..tostring(uiKey));
    -- Note: This function always returns false, by design in Base game
    BASE_CQUI_DefaultKeyDownHandler( uiKey );

    --CQUI Keybinds
    if uiKey == Keys.VK_SHIFT then
        -- print_debug("CQUI_isShiftDown = true");
        CQUI_isShiftDown = true;
    end

    if uiKey == Keys.VK_ALT then
        -- print_debug("CQUI_isAltDown = true");
        CQUI_isAltDown = true;
    end

    -- Allow other handlers of KeyDown events to capture input, if any exist after World Input
    return false;
end

In DefaultKeyUpHandler, CQUI goes through the keys it may have definitions to handle, and only call the Firaxis DefaultKeyUpHandler if CQUI did not handle that key. For example, CQUI binds the "Q" key to build a Quarry, but only if the selected unit is a builder.

function DefaultKeyUpHandler( uiKey:number )
    -- code snipped for Wiki example
    local action :table = CQUI_GetActionFromKey(uiKey);
    local cquiHandledKey :boolean = false;

    local selectedUnit = UI.GetHeadSelectedUnit();

    -- code snipped for Wiki example

    if (action["BUILD_QUARRY"] and unitType == "UNIT_BUILDER") then
        CQUI_BuildImprovement(UI.GetHeadSelectedUnit(), GameInfo.Improvements["IMPROVEMENT_QUARRY"].Hash);
        cquiHandledKey = true;
    end

   -- code snipped for Wiki example

    if (cquiHandledKey == false) then
        -- print_debug("** CQUI DefaultKeyUpHandler did not handle key: "..tostring(uiKey));
        cquiHandledKey = BASE_CQUI_DefaultKeyUpHandler(uiKey);
    end

    return cquiHandledKey;
end

Key Pieces to Key Bindings for CQUI

In the code snippet below, the "CANCEL_COMMAND" index is referenced in the action table. The indexes of this action table are defined by values found in cqui_settings.sql.

    if (action["CANCEL_COMMAND"]) then
        if (unitType ~= nil) then
            UnitManager.RequestCommand(UI.GetHeadSelectedUnit(), UnitCommandTypes.CANCEL);
            cquiHandledKey = true;
        end
    end
INSERT OR REPLACE INTO CQUI_Bindings -- Don't touch this line!
    VALUES
        ("CANCEL_COMMAND", "VK_BACK", "LOC_CQUI_CANCEL_COMMAND"),
        ("REMOVE_HARVEST", "Alt+C", "LOC_CQUI_REMOVE_HARVEST"),
        ("BUILD_FISHING", "F", "LOC_CQUI_BUILD_FISHING"),
        ("BUILD_FORT", "F", "LOC_CQUI_BUILD_FORT"),
        ("BUILD_CAMP", "H", "LOC_CQUI_BUILD_CAMP"),
        ("BUILD_FARM", "I", "LOC_CQUI_BUILD_FARM"),
        ("BUILD_MILL", "L", "LOC_CQUI_BUILD_MILL"),
        ("BUILD_OIL", "O", "LOC_CQUI_BUILD_OIL"),
        ("BUILD_PASTURE", "P", "LOC_CQUI_BUILD_PASTURE"),
        ("BUILD_PLANTATION", "P", "LOC_CQUI_BUILD_PLANTATION"),
        ("BUILD_QUARRY", "Q", "LOC_CQUI_BUILD_QUARRY"),
        ("BUILD_RAILROAD", "R", "LOC_CQUI_BUILD_RAILROAD"),
        ("BUILD_MINE", "N", "LOC_CQUI_BUILD_MINE"),
        ("NUKE", "N", "LOC_CQUI_NUKE"),
        ("THERMO_NUKE", "Alt+N", "LOC_CQUI_THERMO_NUKE"),
        ("REBASE", "Alt+R", "LOC_CQUI_REBASE"),
        ("PLACE_PIN", "Shift+P", "LOC_CQUI_PLACE_PIN");

Each line in the parenthesis defines an action: ("IDENTIFIER", "HOTKEY", "LOCALIZED_STRING_ID").

  • The identifier ("BUILD_FORT", "BUILD_CAMP", etc...) does not necessarily match something that is defined by Firaxis, it is just a friendly name that makes for easier code reading if it is defined to be something meaningful.

    • For example, using "BUILD_RAILROAD" instead of say "RR" or the identifier of the command for building a railroad makes for a better time when reviewing code.
  • The HotKey is the key, or key combination that will be bound to this action. When creating something that requires ALT or SHIFT, use the notation "Modifier+Key".

    • The code for parsing this can be found in the Initialize function of worldinput_CQUI.lua.
  • The localized string id maps to a value defined in the various cqui_text_settings XML files. cqui_text_settings.xml is contains the "en_US" (English-US) strings, and is the default used when that localized string is not found in one of the other language files.

  • The CQUI Key Bindings Menu is generated based on the values found in cqui_settings.xml, so it is not necessary to add anything to the cqui_settingselement.lua or cqui_settingselement.xml files when adding new hot key handlers.

Example of Adding a New Key Handler

Adding a new key handler requires updates to at least 3 files, more if you wish to also do the localization work.

  1. Add the new key definition to cqui_settings.sql (note each line except the last ends with a comma, the last line ends with a semi-colon)
INSERT OR REPLACE INTO CQUI_Bindings -- Don't touch this line!
    VALUES
        ("CANCEL_COMMAND", "VK_BACK", "LOC_CQUI_CANCEL_COMMAND"),
        -- values snipped for purpose of showing this example
        ("PLACE_PIN", "Shift+P", "LOC_CQUI_PLACE_PIN"),
        ("SOME_NEW_THING", "Shift+S", "LOC_CQUI_SOME_NEW_THING");
  1. Add the code into the DefaultKeyUpHandler in worldinput_CQUI.lua, before the lines where the code is checking if SHIFT or ALT were pressed:
   -- snipped code for this example
    if (action["SOME_NEW_THING"]) then
        DoSomeNewThing();
        cquiHandledKey = true;
    end

    if uiKey == Keys.VK_SHIFT then
        -- We need to let the base function also handle the Shift Up action
        CQUI_isShiftDown = false;
    end
  -- snipped rest of function for this example
  1. Add the text to show on the Settings page into the cqui_text_settings.xml file
    <!-- snipped file above this for this example -->
    <Row Tag="LOC_CQUI_LENSES_RELIGIONLENSUNITFLAGSTYLE_TOOLTIP" Language="en_US">
      <Text>The style for unit flag icons of non-Religious units[NEWLINE]when the Religion Lens is on[NEWLINE]Solid: Unit Flags remain visible, unchanged[NEWLINE]Transparent: Unit Flags become transparent[NEWLINE]Hidden:Unit flags are hidden</Text>
    </Row>
    <Row Tag="LOC_CQUI_SOME_NEW_THING" Language="en_US">
      <Text>Some super cool new thing for CQUI</Text>
    </Row>
  </LocalizedText>
</GameData>