Merge branch 'sdl-gamecontroller' (pull request GH-17)

liquid_container
Lukas Werling 2016-03-21 16:39:28 +01:00
commit 19caa65b7b
45 changed files with 966 additions and 813 deletions

View File

@ -304,7 +304,7 @@ endif()
# SDL
if(USE_SDL_MAINLOOP)
find_package(SDL2 REQUIRED)
elseif(NOT WIN32)
else()
# for gamepads
find_package(SDL2)
endif()
@ -908,8 +908,6 @@ elseif(USE_WIN32_WINDOWS)
list(APPEND OC_GUI_SOURCES
src/editor/C4ConsoleWin32.cpp
src/platform/C4WindowWin32.cpp
src/platform/StdJoystick.cpp
src/platform/StdJoystick.h
)
elseif(USE_COCOA)
list(APPEND OC_GUI_SOURCES
@ -1131,6 +1129,7 @@ target_link_libraries(openclonk-server
${PNG_LIBRARIES}
${JPEG_LIBRARIES}
${EXECINFO_LIBRARY}
${SDL2_LIBRARIES}
${READLINE_LIBRARIES}
${Audio_LIBRARIES}
${GETOPT_LIBRARIES}

View File

@ -17,9 +17,19 @@
# SDL2_LIBRARIES - a list of libraries to link against to use SDL2
# SDL2_FOUND - if false, SDL2 cannot be used
find_path(SDL2_INCLUDE_DIR SDL.h PATH_SUFFIXES SDL2 HINTS ENV SDL2DIR)
find_path(SDL2_INCLUDE_DIR SDL.h
HINTS
$ENV{SDL2DIR}
PATH_SUFFIXES SDL2 include
)
mark_as_advanced(SDL2_INCLUDE_DIR)
find_library(SDL2_LIBRARY SDL2 HINTS ENV SDL2DIR)
find_library(SDL2_LIBRARY
SDL2
HINTS
$ENV{SDL2DIR}
PATH_SUFFIXES lib
)
mark_as_advanced(SDL2_LIBRARY)
include(FindPackageHandleStandardArgs)

View File

@ -273,7 +273,7 @@
return true;
}</code>
<text>most commands (except for asynchronous commands in the player menu) call a global script function:</text>
<code>global func PlayerControl(int player, int control, C4ID control_extra, int x, int y, int strength, bool repeated, bool release)</code>
<code>global func PlayerControl(int player, int control, C4ID control_extra, int x, int y, int strength, bool repeated, int state)</code>
<text>For an explanation of the parameters see <funclink>PlayerControl</funclink>. Amongst others, the function receives the calling player in player as well as the command to be executed in control.</text>
<text>As a simple example let's assume that in the global <em>PlayerControls.txt</em> the following command has been defined:</text>
<code>[ControlDefs]
@ -293,11 +293,11 @@
Control=Jump
Priority=50</code>
<text>This defines a Jump key and the corresponding standard mapping on the keyboard for the first player. The following script is used to handle the control:</text>
<code>global func PlayerControl(int player, int control, C4ID control_extra, int x, int y, int strength, bool repeated, bool release)
<code>global func PlayerControl(int player, int control, C4ID control_extra, int x, int y, int strength, bool repeated, int state)
{
// Which command has been issued?
// The constant CON_Jump has been declared automatically through the definition in PlayerControls.txt
if (control == CON_Jump &amp;&amp; !release)
if (control == CON_Jump &amp;&amp; state == CONS_Down)
{
// pressed the jump button. The clonk selected by the player shall jump
var player_clonk = GetCursor(player);
@ -319,17 +319,17 @@
GUIDesc=Going underground
ExtraData=Shovel</code>
<text>Let shovel be the ID of a shovel object. In the global script there could be the following, generic handling for unknown commands, for example:</text>
<code>global func PlayerControl(int player, int control, C4ID control_extra, int x, int y, int strength, bool repeated, bool release)
<code>global func PlayerControl(int player, int control, C4ID control_extra, int x, int y, int strength, bool repeated, int state)
{
// Handling of known controls
// [...]
// control with own handling
if (control_extra) return control_extra-&gt;PlayerControl(player, control, x, y, strength, repeat, release);
if (control_extra) return control_extra-&gt;PlayerControl(player, control, x, y, strength, repeat, state);
// unkown control
return false;
}</code>
<text>And in the script of the shovel:</text>
<code>func PlayerControl(int player, int control, int x, int y, int strength, bool repeated, bool release)
<code>func PlayerControl(int player, int control, int x, int y, int strength, bool repeated, int state)
{
// Handling of known controls
// Control dig directly in the shovel
@ -353,6 +353,7 @@
<li>Mappings can emulate permanent key presses using the <em>Hold</em>/<em>Release</em> flags.</li>
<li><emlink href="playercontrols.xml#Repeat">Key repeats</emlink> are generated.</li>
<li>The held state of the key can be queried in the script via <funclink>GetPlayerControlState</funclink>.</li>
<li>If the command is bound to an analog stick or trigger on a controller, every change in position causes in a call to PlayerControl() with state = CONS_Moved.</li>
</ul>
</text>
<text>A good example for this functionality is a directional command:</text>
@ -362,7 +363,7 @@
GUIDesc=Walk left
Hold=1</code>
<text>In the script the direction is transferred to the Clonk:</text>
<code>global func PlayerControl(int player, int control, C4ID control_extra, int x, int y, int strength, bool repeated, bool release)
<code>global func PlayerControl(int player, int control, C4ID control_extra, int x, int y, int strength, bool repeated, int state)
{
if (control == CON_Left) return UpdateControlDir(player);
// ...

View File

@ -6,7 +6,7 @@
<func>
<title>GetPlayerControlState</title>
<category>Player</category>
<version>5.1 OC</version>
<version>5.1 OC (extended in 8.0 OC)</version>
<syntax>
<rtype>int</rtype>
<params>
@ -20,9 +20,15 @@
<name>control</name>
<desc>Control to query. A CON_* constant should be used here.</desc>
</param>
<param>
<type>bool</type>
<name>analog_strength</name>
<desc>If true: Query current state of an analog control on a gamepad instead of the emulated button state.</desc>
<optional />
</param>
</params>
</syntax>
<desc>Returns the current state of a control for a certain player. The return value is the strength of the control (e.g. for gamepad joysticks). If the control is assigned to a key, a value not equal to 0 means that the key is currently held down by the player.</desc>
<desc>Returns the current state of a control for a certain player. If the control is assigned to a key, a value not equal to 0 means that the key is currently held down by the player. For analog controls on gamepads, the function either queries the current emulated button state (analog_strength = false), or the current position of the stick or trigger (analog_strength = true).</desc>
<examples>
<example>
<code>
@ -36,4 +42,5 @@ if (GetPlayerControlState(GetOwner(), CON_Left) != 0)
</related>
</func>
<author>Zapper</author><date>2015-10</date>
<author>Luchs</author><date>2016-02</date>
</funcs>

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE funcs
SYSTEM '../../../clonk.dtd'>
<?xml-stylesheet type="text/xsl" href="../../../clonk.xsl"?>
<funcs>
<func>
<title>PlayRumble</title>
<category>Player</category>
<version>8.0 OC</version>
<syntax>
<rtype>bool</rtype>
<params>
<param>
<type>int</type>
<name>player</name>
<desc>Number of the player whose controller should rumble. Can be NO_OWNER to make all controllers rumble.</desc>
</param>
<param>
<type>int</type>
<name>strength</name>
<desc>Strength of the rumble, between 0 and 1000.</desc>
</param>
<param>
<type>int</type>
<name>length</name>
<desc>Duration of the rumble in milliseconds.</desc>
</param>
</params>
</syntax>
<desc>Plays a haptic effect on the given player's gamepad. Returns true if all parameters are valid; there is no way to know whether the rumble was actually played.</desc>
<examples>
<example>
<code>
<funclink>ShakeObjects</funclink>(<funclink>LandscapeWidth</funclink>()/2, <funclink>LandscapeHeight</funclink>()/2, <funclink>Distance</funclink>(<funclink>LandscapeWidth</funclink>(), <funclink>LandscapeHeight</funclink>())/2);
PlayRumble(NO_OWNER, 1000, 2000);
</code>
<text>Earthquake: Shakes all Clonks and rumbles all controllers at full strength for two seconds.</text>
</example>
</examples>
<related>
<funclink>StopRumble</funclink>
</related>
</func>
<author>Luchs</author><date>2016-02</date>
</funcs>

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE funcs
SYSTEM '../../../clonk.dtd'>
<?xml-stylesheet type="text/xsl" href="../../../clonk.xsl"?>
<funcs>
<func>
<title>PlayerControl</title>
<category>Callbacks</category>
<version>5.1 OC</version>
<syntax>
<rtype>bool</rtype>
<params>
<param>
<type>int</type>
<name>player</name>
<desc>Number of the player who pressed the control.</desc>
</param>
<param>
<type>int</type>
<name>control</name>
<desc>Number of the pressed control, defined as a CON_ constant via PlayerControls.txt.</desc>
</param>
<param>
<type>id</type>
<name>control_extra</name>
<desc>Optional id defined with ExtraData in PlayerControls.txt.</desc>
</param>
<param>
<type>int</type>
<name>x</name>
<desc>X coordinate for mouse controls.</desc>
</param>
<param>
<type>int</type>
<name>y</name>
<desc>Y coordinate for mouse controls.</desc>
</param>
<param>
<type>int</type>
<name>strength</name>
<desc>Current strength of the control. For key presses: 0 or 100. For analog stick or trigger movement (state = CONS_Moved): 0 to <code>PLRCON_MaxStrength</code>.</desc>
</param>
<param>
<type>bool</type>
<name>repeated</name>
<desc>Whether the call is generated because of a held button.</desc>
</param>
<param>
<type>int</type>
<name>state</name>
<desc>
State of the key press. Possible values:
<table>
<rowh>
<col>Constant</col>
<col>Description</col>
</rowh>
<row>
<literal_col>CONS_Down</literal_col>
<col>Key has been pressed down.</col>
</row>
<row>
<literal_col>CONS_Up</literal_col>
<col>Key has been released. Only generated for held keys.</col>
</row>
<row>
<literal_col>CONS_Moved</literal_col>
<col>An analog control on a gamepad has been moved. Only generated for held keys.</col>
</row>
</table>
</desc>
</param>
</params>
</syntax>
<desc>Called globally for each control command by players. See <emlink href="script/playercontrols.html">Player Controls</emlink>.</desc>
<related><funclink>GetPlayerControlState</funclink></related>
<related><emlink href="playercontrols.html">Player Controls</emlink></related>
</func>
<author>Luchs</author><date>2016-02</date>
</funcs>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE funcs
SYSTEM '../../../clonk.dtd'>
<?xml-stylesheet type="text/xsl" href="../../../clonk.xsl"?>
<funcs>
<func>
<title>StopRumble</title>
<category>Player</category>
<version>8.0 OC</version>
<syntax>
<rtype>bool</rtype>
<params>
<param>
<type>int</type>
<name>player</name>
<desc>Number of the player whose controller should stop rumbling. Can be NO_OWNER to make all controllers stop.</desc>
</param>
</params>
</syntax>
<desc>Stops a rumble effect that was started with <funclink>PlayRumble</funclink>. Returns true if the given player is valid; there is no way to know whether there was actually a playing rumble effect.</desc>
<related>
<funclink>PlayRumble</funclink>
</related>
</func>
<author>Luchs</author><date>2016-02</date>
</funcs>

View File

@ -13,3 +13,6 @@ StartupPlrSelBG.jpg
StartupScenSelBG.jpg)
Loader1.jpg - Nachtfalter
ControllerIcons.png - Nicolae Berbece (http://opengameart.org/content/free-keyboard-and-controllers-prompts-pack)
License: CC0

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -14,9 +14,11 @@ static g_player_cursor_pos; // array of [x,y] pos arrays; indexed by player. las
// Called by engine whenever a control is issued
// Forwards control to special handler or cursor
// Return whether handled
global func PlayerControl(int plr, int ctrl, id spec_id, int x, int y, int strength, bool repeat, bool release)
global func PlayerControl(int plr, int ctrl, id spec_id, int x, int y, int strength, bool repeat, int status)
{
//Log("%d, %s, %i, %d, %d, %d, %v, %v", plr, GetPlayerControlName(ctrl), spec_id, x,y,strength, repeat, release);
var release = status == CONS_Up;
//Log("%d, %s, %i, %d, %d, %d, %v, %v", plr, GetPlayerControlName(ctrl), spec_id, x,y,strength, repeat, status);
if (status == CONS_Moved) return false;
// Control handled by definition? Forward
if (spec_id) return spec_id->PlayerControl(plr, ctrl, x, y, strength, repeat, release);
@ -550,4 +552,4 @@ global func Library_ClonkInventoryControl_Sort_Priority(int x_position)
var priority_x = GetX() - x_position;
if (priority_x < 0) priority_x += 1000;
return priority_x;
}
}

View File

@ -836,31 +836,41 @@
# Contents Menu
[Assignment]
Key=Joy1F
Key=ControllerButtonB
GUIGroup=50
Control=Contents
[Assignment]
Key=ControllerButtonLeftShoulder
GUIGroup=50
Control=InventoryShiftBackward
[Assignment]
Key=ControllerButtonRightShoulder
GUIGroup=50
Control=InventoryShiftForward
# Menu
[Assignment]
Key=Joy1J
Key=ControllerButtonY
GUIGroup=50
Control=PlayerMenu
[Assignment]
Key=Joy1C
Key=ControllerButtonA
GUIGroup=50
Control=MenuOK
[Assignment]
Key=Joy1B
Key=ControllerButtonB
GUIGroup=50
Control=MenuCancel
# Movement
[Assignment]
Key=Joy1Left
Key=ControllerLeftStickLeft
Priority=50
GUIName=$KEY_Left$
GUIDesc=$KEY_Left_Desc$
@ -868,7 +878,7 @@
Control=Left
[Assignment]
Key=Joy1Right
Key=ControllerLeftStickRight
Priority=50
GUIName=$KEY_Right$
GUIDesc=$KEY_Right_Desc$
@ -876,7 +886,7 @@
Control=Right
[Assignment]
Key=Joy1Down
Key=ControllerLeftStickDown
Priority=50
GUIName=$KEY_Down$
GUIDesc=$KEY_Down_Desc$
@ -884,7 +894,7 @@
Control=Down
[Assignment]
Key=Joy1Up
Key=ControllerLeftStickUp
Priority=50
GUIName=$KEY_Up$
GUIDesc=$KEY_Up_Desc$
@ -892,34 +902,40 @@
Control=Up
[Assignment]
Key=Joy1C
Key=ControllerButtonRightStick
Priority=10
GUIGroup=10
Control=Jump
[Assignment]
Key=Joy1Axis1Min
Key=ControllerButtonDpadDown
Priority=10
GUIGroup=10
Control=FallThrough
[Assignment]
Key=ControllerRightStickLeft
GUIDesc=$KEY_AimAxis_Desc$
Priority=80
GUIGroup=30
Control=AimAxisLeft
[Assignment]
Key=Joy1Axis1Max
Key=ControllerRightStickRight
GUIDesc=$KEY_AimAxis_Desc$
Priority=80
GUIGroup=30
Control=AimAxisRight
[Assignment]
Key=Joy1Axis2Max
Key=ControllerRightStickDown
GUIDesc=$KEY_AimAxis_Desc$
Priority=80
GUIGroup=30
Control=AimAxisDown
[Assignment]
Key=Joy1Axis2Min
Key=ControllerRightStickUp
GUIDesc=$KEY_AimAxis_Desc$
Priority=80
GUIGroup=30
@ -928,41 +944,57 @@
# Object interaction
[Assignment]
Key=Joy1B
Key=ControllerButtonA
Priority=35
GUIGroup=40
Control=Interact
[Assignment]
Key=ControllerButtonX
Control=PickUp
GUIGroup=50
# Crew
[Assignment]
Key=Joy1I
Key=ControllerButtonDpadRight
Control=NextCrew
GUIGroup=70
[Assignment]
Key=ControllerButtonDpadLeft
Control=PreviousCrew
GUIGroup=70
# Use, Throw, Drop
[Assignment]
Key=Joy1A
Key=ControllerRightTrigger
GUIName=$KEY_GamepadUse$
GUIDesc=$KEY_GamepadUse_Desc$
GUIGroup=20
Priority=100
Control=UseDelayed
# Zoom
[Assignment]
Key=Joy1H
Key=ControllerLeftTrigger
GUIGroup=20
Priority=100
GUIGroup=60
Control=ZoomIn
Control=ThrowDelayed
[Assignment]
Key=Joy1G
Priority=100
GUIGroup=60
Control=ZoomOut
# TODO: Zoom
#[Assignment]
#Key=Joy1H
#Priority=100
#GUIGroup=60
#Control=ZoomIn
#[Assignment]
#Key=Joy1G
#Priority=100
#GUIGroup=60
#Control=ZoomOut

View File

@ -423,7 +423,7 @@ void C4ControlPlayerControl::ControlItem::CompileFunc(StdCompiler *pComp)
void C4ControlPlayerControl::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iPlr), "Player", -1));
pComp->Value(mkNamingAdapt(fRelease, "Release", false));
pComp->Value(mkNamingAdapt(mkIntAdapt(state), "State", 0));
pComp->Value(mkNamingAdapt(ExtraData, "ExtraData", C4KeyEventData()));
pComp->Value(mkNamingAdapt(mkSTLContainerAdapt(ControlItems), "Controls", ControlItemVec()));
C4ControlPacket::CompileFunc(pComp);

View File

@ -203,11 +203,11 @@ public:
class C4ControlPlayerControl : public C4ControlPacket // sync
{
public:
C4ControlPlayerControl() : iPlr(-1), fRelease(false) {}
C4ControlPlayerControl(int32_t iPlr, bool fRelease, const C4KeyEventData &rExtraData)
: iPlr(iPlr), fRelease(fRelease), ExtraData(rExtraData) { }
C4ControlPlayerControl() : iPlr(-1), state(C4PlayerControl::CONS_Down) {}
C4ControlPlayerControl(int32_t iPlr, C4PlayerControl::ControlState state, const C4KeyEventData &rExtraData)
: iPlr(iPlr), state(state), ExtraData(rExtraData) { }
C4ControlPlayerControl(int32_t iPlr, int32_t iControl, int32_t iExtraData) // old-style menu com emulation
: iPlr(iPlr), fRelease(false), ExtraData(iExtraData,0,0,0,0) { AddControl(iControl,0); }
: iPlr(iPlr), state(C4PlayerControl::CONS_Down), ExtraData(iExtraData,0,0,0,0) { AddControl(iControl,0); }
struct ControlItem
{
@ -221,7 +221,7 @@ public:
typedef std::vector<ControlItem> ControlItemVec;
protected:
int32_t iPlr;
bool fRelease;
int32_t state;
C4KeyEventData ExtraData;
ControlItemVec ControlItems;
public:
@ -229,7 +229,7 @@ public:
void AddControl(int32_t iControl, int32_t iTriggerMode)
{ ControlItems.push_back(ControlItem(iControl, iTriggerMode)); }
const ControlItemVec &GetControlItems() const { return ControlItems; }
bool IsReleaseControl() const { return fRelease; }
C4PlayerControl::ControlState GetState() const { return static_cast<C4PlayerControl::ControlState>(state); }
const C4KeyEventData &GetExtraData() const { return ExtraData; }
void SetExtraData(const C4KeyEventData &new_extra_data) { ExtraData = new_extra_data; }
};

View File

@ -24,6 +24,7 @@
#include "C4PlayerList.h"
#include "C4Control.h"
#include "C4Game.h"
#include "C4GamePadCon.h"
#include "C4Log.h"
#include "C4GraphicsResource.h"
#include "C4MouseControl.h"
@ -706,7 +707,7 @@ C4Facet C4PlayerControlAssignmentSet::GetPicture() const
{
// get image to be drawn to represent this control set
// picture per set not implemented yet. So just default to out standard images
if (HasGamepad()) return ::GraphicsResource.fctGamepad.GetPhase(GetGamepadIndex());
if (HasGamepad()) return ::GraphicsResource.fctGamepad.GetPhase(0);
// if (HasMouse()) return ::GraphicsResource.fctMouse; // might be useful again with changing control sets
if (HasKeyboard()) return ::GraphicsResource.fctKeyboard.GetPhase(Game.PlayerControlUserAssignmentSets.GetSetIndex(this));
return C4Facet();
@ -734,21 +735,6 @@ void C4PlayerControlAssignmentSets::CompileFunc(StdCompiler *pComp)
}
pComp->Value(mkNamingAdapt(clear_previous, "ClearPrevious", false));
pComp->Value(mkSTLContainerAdapt(Sets, StdCompiler::SEP_NONE));
// Remove all sets that have gamepad controls, since gamepad
// support is broken at the moment. Disable this once we have gamepad
// support again!
if (pComp->isCompiler())
{
AssignmentSetList::iterator iter = Sets.begin();
for (AssignmentSetList::iterator iter = Sets.begin(); iter != Sets.end(); )
{
if (iter->HasGamepad())
iter = Sets.erase(iter);
else
++iter;
}
}
}
bool C4PlayerControlAssignmentSets::operator ==(const C4PlayerControlAssignmentSets &cmp) const
@ -908,14 +894,18 @@ void C4PlayerControl::CSync::ControlDownState::CompileFunc(StdCompiler *pComp)
{
pComp->Value(DownState);
pComp->Separator();
pComp->Value(MovedState);
pComp->Separator();
pComp->Value(iDownFrame);
pComp->Separator();
pComp->Value(iMovedFrame);
pComp->Separator();
pComp->Value(fDownByUser);
}
bool C4PlayerControl::CSync::ControlDownState::operator ==(const ControlDownState &cmp) const
{
return DownState == cmp.DownState && iDownFrame == cmp.iDownFrame && fDownByUser == cmp.fDownByUser;
return DownState == cmp.DownState && MovedState == cmp.MovedState && iDownFrame == cmp.iDownFrame && iMovedFrame == cmp.iMovedFrame && fDownByUser == cmp.fDownByUser;
}
const C4PlayerControl::CSync::ControlDownState *C4PlayerControl::CSync::GetControlDownState(int32_t iControl) const
@ -943,6 +933,16 @@ void C4PlayerControl::CSync::SetControlDownState(int32_t iControl, const C4KeyEv
rState.fDownByUser = fDownByUser;
}
void C4PlayerControl::CSync::SetControlMovedState(int32_t iControl, const C4KeyEventData &rMovedState, int32_t iMovedFrame)
{
// update state
if (iControl < 0) return;
if (iControl >= int32_t(ControlDownStates.size())) ControlDownStates.resize(iControl+1);
ControlDownState &rState = ControlDownStates[iControl];
rState.MovedState = rMovedState;
rState.iMovedFrame = iMovedFrame;
}
bool C4PlayerControl::CSync::SetControlDisabled(int32_t iControl, int32_t iVal)
{
// disable control
@ -963,6 +963,7 @@ void C4PlayerControl::CSync::ResetControlDownState(int32_t iControl)
C4KeyEventData KeyDownState = pDownState->DownState;
KeyDownState.iStrength = 0;
SetControlDownState(iControl, KeyDownState, 0, false);
SetControlMovedState(iControl, KeyDownState, 0);
}
}
@ -1009,12 +1010,19 @@ void C4PlayerControl::CompileFunc(StdCompiler *pComp)
pComp->Value(mkNamingAdapt(Sync, "PlayerControl", DefaultSync));
}
bool C4PlayerControl::ProcessKeyEvent(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key, bool fUp, const C4KeyEventData &rKeyExtraData, bool reset_down_states_only, bool *clear_recent_keys)
bool C4PlayerControl::ProcessKeyEvent(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key, ControlState state, const C4KeyEventData &rKeyExtraData, bool reset_down_states_only, bool *clear_recent_keys)
{
if (Key_IsGamepad(pressed_key.Key))
{
// We have to filter gamepad events here.
C4Player *plr = ::Players.Get(iPlr);
if (!plr || !plr->pGamepad || plr->pGamepad->GetID() != pressed_key.deviceId)
return false;
}
// collect all matching keys
C4PlayerControlAssignmentPVec Matches;
assert(pControlSet); // shouldn't get this callback for players without control set
pControlSet->GetAssignmentsByKey(ControlDefs, matched_key, fUp, &Matches, DownKeys, RecentKeys);
pControlSet->GetAssignmentsByKey(ControlDefs, matched_key, state != CONS_Down, &Matches, DownKeys, RecentKeys);
// process async controls
C4ControlPlayerControl *pControlPacket = NULL;
for (C4PlayerControlAssignmentPVec::const_iterator i = Matches.begin(); i != Matches.end(); ++i)
@ -1023,7 +1031,7 @@ bool C4PlayerControl::ProcessKeyEvent(const C4KeyCodeEx &pressed_key, const C4Ke
assert(pAssignment);
int32_t iControlIndex = pAssignment->GetControl();
const C4PlayerControlDef *pControlDef = ControlDefs.GetControlByIndex(iControlIndex);
if (pControlDef && pControlDef->IsValid() && !Sync.IsControlDisabled(iControlIndex) && (!fUp || pControlDef->IsHoldKey()))
if (pControlDef && pControlDef->IsValid() && !Sync.IsControlDisabled(iControlIndex) && (state == CONS_Down || pControlDef->IsHoldKey()))
{
// clear RecentKeys if requested by this assignment. Must be done before sync queue, so multiple combos can be issued in a single control frame.
if (clear_recent_keys && (pAssignment->GetTriggerMode() & C4PlayerControlAssignment::CTM_ClearRecentKeys)) *clear_recent_keys = true;
@ -1031,7 +1039,7 @@ bool C4PlayerControl::ProcessKeyEvent(const C4KeyCodeEx &pressed_key, const C4Ke
if (pControlDef->IsAsync() && !pControlPacket)
{
if (pControlDef->IsSendCursorPos()) IsCursorPosRequested = true; // async cursor pos request - doesn't really make sense to set this flag for async controls
if (ExecuteControl(iControlIndex, fUp, rKeyExtraData, pAssignment->GetTriggerMode(), pressed_key.IsRepeated(), reset_down_states_only))
if (ExecuteControl(iControlIndex, state, rKeyExtraData, pAssignment->GetTriggerMode(), pressed_key.IsRepeated(), reset_down_states_only))
return true;
}
else
@ -1041,7 +1049,7 @@ bool C4PlayerControl::ProcessKeyEvent(const C4KeyCodeEx &pressed_key, const C4Ke
if (pressed_key.IsRepeated()) return false;
// sync control has higher priority - no more async execution then
// build a control packet and add control data instead. even for async controls later in chain, as they may be blocked by a sync handler
if (!pControlPacket) pControlPacket = new C4ControlPlayerControl(iPlr, fUp, rKeyExtraData);
if (!pControlPacket) pControlPacket = new C4ControlPlayerControl(iPlr, state, rKeyExtraData);
int32_t extra_trigger_mode = 0;
if (reset_down_states_only) extra_trigger_mode |= C4PlayerControlAssignment::CTM_HandleDownStatesOnly;
pControlPacket->AddControl(iControlIndex, pAssignment->GetTriggerMode() | extra_trigger_mode);
@ -1071,7 +1079,7 @@ bool C4PlayerControl::ProcessKeyDown(const C4KeyCodeEx &pressed_key, const C4Key
}
// process!
bool clear_recent_keys = false;
bool fResult = ProcessKeyEvent(pressed_key, matched_key, false, Game.KeyboardInput.GetLastKeyExtraData(), false, &clear_recent_keys);
bool fResult = ProcessKeyEvent(pressed_key, matched_key, CONS_Down, Game.KeyboardInput.GetLastKeyExtraData(), false, &clear_recent_keys);
// unless assignment requests a clear, always add keys to recent list even if not handled
if (clear_recent_keys)
RecentKeys.clear();
@ -1090,7 +1098,13 @@ bool C4PlayerControl::ProcessKeyUp(const C4KeyCodeEx &pressed_key, const C4KeyCo
if (i != DownKeys.end()) DownKeys.erase(i);
}
// process!
return ProcessKeyEvent(pressed_key, matched_key, true, Game.KeyboardInput.GetLastKeyExtraData());
return ProcessKeyEvent(pressed_key, matched_key, CONS_Up, Game.KeyboardInput.GetLastKeyExtraData());
}
bool C4PlayerControl::ProcessKeyMoved(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key)
{
// process!
return ProcessKeyEvent(pressed_key, matched_key, CONS_Moved, Game.KeyboardInput.GetLastKeyExtraData());
}
void C4PlayerControl::ExecuteControlPacket(const class C4ControlPlayerControl *pCtrl)
@ -1111,10 +1125,10 @@ void C4PlayerControl::ExecuteControlPacket(const class C4ControlPlayerControl *p
AddDbgRec(RCT_PlrCom, &rItem.iControl, sizeof(rItem.iControl));
}
}
if (ExecuteControl(rItem.iControl, pCtrl->IsReleaseControl(), pCtrl->GetExtraData(), rItem.iTriggerMode, false, fHandleDownStateOnly))
if (ExecuteControl(rItem.iControl, pCtrl->GetState(), pCtrl->GetExtraData(), rItem.iTriggerMode, false, fHandleDownStateOnly))
if (pCtrlDef->IsSync())
{
if (pCtrl->IsReleaseControl())
if (pCtrl->GetState() == CONS_Up)
{
// control processed. however, for key releases, overriden keys are released silently so following down events aren't handled as key repeats
// note this does not affect CTM_Hold/CTM_Release, because they ignore release controls anyway
@ -1129,7 +1143,7 @@ void C4PlayerControl::ExecuteControlPacket(const class C4ControlPlayerControl *p
}
}
bool C4PlayerControl::ExecuteControl(int32_t iControl, bool fUp, const C4KeyEventData &rKeyExtraData, int32_t iTriggerMode, bool fRepeated, bool fHandleDownStateOnly)
bool C4PlayerControl::ExecuteControl(int32_t iControl, ControlState state, const C4KeyEventData &rKeyExtraData, int32_t iTriggerMode, bool fRepeated, bool fHandleDownStateOnly)
{
// execute single control. return if handled
const C4PlayerControlDef *pControlDef = ControlDefs.GetControlByIndex(iControl);
@ -1147,7 +1161,7 @@ bool C4PlayerControl::ExecuteControl(int32_t iControl, bool fUp, const C4KeyEven
{
if (eAction != C4PlayerControlDef::CDA_Script) return false;
if (!pControlDef->IsHoldKey()) return false;
if (fUp) return false; // hold triggers have no "up"-event
if (state == CONS_Up) return false; // hold triggers have no "up"-event
// perform hold/release
if (fWasDown)
{
@ -1157,7 +1171,7 @@ bool C4PlayerControl::ExecuteControl(int32_t iControl, bool fUp, const C4KeyEven
KeyExtraData.iStrength = 0;
Sync.SetControlDownState(iControl, KeyExtraData, Game.FrameCounter, false);
// now process as a regular "Up" event
fUp = true;
state = CONS_Up;
fRepeated = false;
}
else
@ -1186,7 +1200,7 @@ bool C4PlayerControl::ExecuteControl(int32_t iControl, bool fUp, const C4KeyEven
}
}
}
else if (fUp)
else if (state == CONS_Up)
{
// regular ControlUp: Only valid if that control was down
if (!fWasDown) return false;
@ -1194,14 +1208,22 @@ bool C4PlayerControl::ExecuteControl(int32_t iControl, bool fUp, const C4KeyEven
}
else if (pControlDef->IsHoldKey())
{
// regular ControlDown on Hold Key: Set in down list
Sync.SetControlDownState(iControl, KeyExtraData, Game.FrameCounter, true);
fRepeated = fWasDown;
if (state == CONS_Moved)
{
Sync.SetControlMovedState(iControl, KeyExtraData, Game.FrameCounter);
fRepeated = true;
}
else
{
// regular ControlDown on Hold Key: Set in down list
Sync.SetControlDownState(iControl, KeyExtraData, Game.FrameCounter, true);
fRepeated = fWasDown;
}
}
// down state handling done
if (fHandleDownStateOnly) return false;
// perform action for this control
bool fHandled = ExecuteControlAction(iControl, eAction, pControlDef->GetExtraData(), fUp, KeyExtraData, fRepeated);
bool fHandled = ExecuteControlAction(iControl, eAction, pControlDef->GetExtraData(), state, KeyExtraData, fRepeated);
// handled controls hide control display
C4Player *pPlr;
if ((pPlr = ::Players.Get(iPlr))) if (pPlr->ShowStartup) pPlr->ShowStartup = false;
@ -1209,8 +1231,10 @@ bool C4PlayerControl::ExecuteControl(int32_t iControl, bool fUp, const C4KeyEven
return fHandled && !(iTriggerMode & C4PlayerControlAssignment::CTM_AlwaysUnhandled);
}
bool C4PlayerControl::ExecuteControlAction(int32_t iControl, C4PlayerControlDef::Actions eAction, C4ID idControlExtraData, bool fUp, const C4KeyEventData &rKeyExtraData, bool fRepeated)
bool C4PlayerControl::ExecuteControlAction(int32_t iControl, C4PlayerControlDef::Actions eAction, C4ID idControlExtraData, ControlState state, const C4KeyEventData &rKeyExtraData, bool fRepeated)
{
// moved events don't make sense for menus and are only handled by script
if (state == CONS_Moved && eAction != C4PlayerControlDef::CDA_Script) return false;
// get affected player
C4Player *pPlr = NULL;
C4Viewport *pVP;
@ -1223,12 +1247,13 @@ bool C4PlayerControl::ExecuteControlAction(int32_t iControl, C4PlayerControlDef:
pCursor = pPlr->Cursor;
if (pCursor && pCursor->Menu && pCursor->Menu->IsActive()) pCursorMenu = pCursor->Menu;
}
bool fUp = state == CONS_Up;
// exec action (on player)
switch (eAction)
{
// scripted player control
case C4PlayerControlDef::CDA_Script:
return ExecuteControlScript(iControl, idControlExtraData, fUp, rKeyExtraData, fRepeated);
return ExecuteControlScript(iControl, idControlExtraData, state, rKeyExtraData, fRepeated);
// menu controls
case C4PlayerControlDef::CDA_Menu: if (!pPlr || fUp) return false; if (pPlr->Menu.IsActive()) pPlr->Menu.TryClose(false, true); else pPlr->ActivateMenuMain(); return true; // toggle
@ -1256,15 +1281,16 @@ bool C4PlayerControl::ExecuteControlAction(int32_t iControl, C4PlayerControlDef:
}
}
bool C4PlayerControl::ExecuteControlScript(int32_t iControl, C4ID idControlExtraData, bool fUp, const C4KeyEventData &rKeyExtraData, bool fRepeated)
bool C4PlayerControl::ExecuteControlScript(int32_t iControl, C4ID idControlExtraData, ControlState state, const C4KeyEventData &rKeyExtraData, bool fRepeated)
{
C4Player *pPlr = ::Players.Get(iPlr);
if (pPlr)
{
// Not for eliminated (checked again in DirectCom, but make sure no control is generated for eliminated players!)
if (pPlr->Eliminated) return false;
// control count for statistics
pPlr->CountControl(C4Player::PCID_DirectCom, iControl*2+fUp);
// control count for statistics (but don't count analog stick wiggles)
if (state != CONS_Moved)
pPlr->CountControl(C4Player::PCID_DirectCom, iControl*2+state);
}
else if (iPlr > -1)
{
@ -1285,7 +1311,7 @@ bool C4PlayerControl::ExecuteControlScript(int32_t iControl, C4ID idControlExtra
C4Value vx = (x == C4KeyEventData::KeyPos_None) ? C4VNull : C4VInt(x);
C4Value vy = (y == C4KeyEventData::KeyPos_None) ? C4VNull : C4VInt(y);
// exec control function
C4AulParSet Pars(iPlr, iControl, C4Id2Def(idControlExtraData), vx, vy, rKeyExtraData.iStrength, fRepeated, fUp);
C4AulParSet Pars(iPlr, iControl, C4Id2Def(idControlExtraData), vx, vy, rKeyExtraData.iStrength, fRepeated, C4VInt(state));
return ::ScriptEngine.GetPropList()->Call(PSF_PlayerControl, &Pars).getBool();
}
@ -1310,7 +1336,7 @@ void C4PlayerControl::Execute()
if (!((iFrameDiff-iCtrlInitialRepeatDelay) % iCtrlRepeatDelay))
{
// it's RepeatTime for this key!
ExecuteControlAction(i, pCtrlDef->GetAction(), pCtrlDef->GetExtraData(), false, pControlDownState->DownState, true);
ExecuteControlAction(i, pCtrlDef->GetAction(), pCtrlDef->GetExtraData(), CONS_Down, pControlDownState->DownState, true);
}
}
}
@ -1363,7 +1389,7 @@ void C4PlayerControl::AddKeyBinding(const C4KeyCodeEx &key, bool fHoldKey, int32
{
KeyBindings.push_back(new C4KeyBinding(
key, FormatString("PlrKey%02d", idx).getData(), KEYSCOPE_Control,
new C4KeyCBExPassKey<C4PlayerControl, C4KeyCodeEx>(*this, key, &C4PlayerControl::ProcessKeyDown, fHoldKey ? &C4PlayerControl::ProcessKeyUp : NULL),
new C4KeyCBExPassKey<C4PlayerControl, C4KeyCodeEx>(*this, key, &C4PlayerControl::ProcessKeyDown, fHoldKey ? &C4PlayerControl::ProcessKeyUp : NULL, NULL, fHoldKey ? &C4PlayerControl::ProcessKeyMoved : NULL),
C4CustomKey::PRIO_PlrControl));
}
@ -1455,7 +1481,7 @@ void C4PlayerControl::PrepareInput()
ev.iStrength = 0;
ev.vp_x = x; ev.vp_y = y;
ev.game_x = game_x; ev.game_y = game_y;
C4ControlPlayerControl *pControlPacket = new C4ControlPlayerControl(iPlr, false, ev);
C4ControlPlayerControl *pControlPacket = new C4ControlPlayerControl(iPlr, CONS_Down, ev);
pControlPacket->AddControl(ControlDefs.InternalCons.CON_CursorPos, C4PlayerControlAssignment::CTM_Default);
// make sure it's added at head, because controls that have SendCursorPos=1 set will follow, which will rely
// on the cursor pos being known

View File

@ -277,7 +277,6 @@ public:
bool HasMouse() const { return has_mouse; }
bool HasGamepad() const { return has_gamepad; }
int32_t GetLayoutOrder() const { return 0; } // returns position on keyboard (increasing from left to right) for viewport sorting
int32_t GetGamepadIndex() const { return 0; }
bool IsMouseControlAssigned(int32_t mouseevent) const;
};
@ -333,6 +332,12 @@ class C4PlayerControl
public:
enum { MaxRecentKeyLookback = 3000, MaxSequenceKeyDelay = 800 }; // milliseconds: Time to press key combos
enum ControlState {
CONS_Down = 0,
CONS_Up,
CONS_Moved,
};
private:
C4PlayerControlDefs &ControlDefs; // shortcut
@ -353,8 +358,8 @@ public:
{
struct ControlDownState
{
C4KeyEventData DownState; // control is down if DownState.iStrength>0
int32_t iDownFrame; // frame when control was pressed
C4KeyEventData DownState, MovedState; // control is down if DownState.iStrength>0
int32_t iDownFrame, iMovedFrame; // frame when control was pressed
bool fDownByUser; // if true, the key is actually pressed. Otherwise, it's triggered as down by another key
ControlDownState(const C4KeyEventData &rDownState, int32_t iDownFrame, bool fDownByUser)
: DownState(rDownState), iDownFrame(iDownFrame), fDownByUser(fDownByUser) {}
@ -373,6 +378,7 @@ public:
int32_t GetControlDisabled(int32_t iControl) const;
bool IsControlDisabled(int32_t iControl) const { return GetControlDisabled(iControl)>0; }
void SetControlDownState(int32_t iControl, const C4KeyEventData &rDownState, int32_t iDownFrame, bool fDownByUser);
void SetControlMovedState(int32_t iControl, const C4KeyEventData &rMovedState, int32_t iMovedFrame);
void ResetControlDownState(int32_t iControl);
bool SetControlDisabled(int32_t iControl, int32_t iVal);
@ -386,14 +392,15 @@ private:
CSync Sync;
// callbacks from Game.KeyboardInput
bool ProcessKeyEvent(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key, bool fUp, const C4KeyEventData &rKeyExtraData, bool reset_down_states_only=false, bool *clear_recent_keys=NULL);
bool ProcessKeyEvent(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key, ControlState state, const C4KeyEventData &rKeyExtraData, bool reset_down_states_only=false, bool *clear_recent_keys=NULL);
bool ProcessKeyDown(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key);
bool ProcessKeyUp(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key);
bool ProcessKeyMoved(const C4KeyCodeEx &pressed_key, const C4KeyCodeEx &matched_key);
// execute single control. return if handled.
bool ExecuteControl(int32_t iControl, bool fUp, const C4KeyEventData &rKeyExtraData, int32_t iTriggerMode, bool fRepeated, bool fHandleDownStateOnly);
bool ExecuteControlAction(int32_t iControl, C4PlayerControlDef::Actions eAction, C4ID idControlExtraData, bool fUp, const C4KeyEventData &rKeyExtraData, bool fRepeated);
bool ExecuteControlScript(int32_t iControl, C4ID idControlExtraData, bool fUp, const C4KeyEventData &rKeyExtraData, bool fRepeated);
bool ExecuteControl(int32_t iControl, ControlState state, const C4KeyEventData &rKeyExtraData, int32_t iTriggerMode, bool fRepeated, bool fHandleDownStateOnly);
bool ExecuteControlAction(int32_t iControl, C4PlayerControlDef::Actions eAction, C4ID idControlExtraData, ControlState state, const C4KeyEventData &rKeyExtraData, bool fRepeated);
bool ExecuteControlScript(int32_t iControl, C4ID idControlExtraData, ControlState state, const C4KeyEventData &rKeyExtraData, bool fRepeated);
// init
void AddKeyBinding(const C4KeyCodeEx &key, bool fHoldKey, int32_t idx);

View File

@ -677,6 +677,7 @@ void C4Application::GameTick()
case C4AS_Startup:
SoundSystem.Execute();
MusicSystem.Execute();
if (pGamePadControl) pGamePadControl->Execute();
// wait for the user to start a game
break;
case C4AS_StartGame:
@ -697,6 +698,10 @@ void C4Application::GameTick()
Application.SetVideoMode(GetConfigWidth(), GetConfigHeight(), Config.Graphics.RefreshRate, Config.Graphics.Monitor, true);
if (!isEditor)
pWindow->GrabMouse(true);
// Gamepad events have to be polled here so that the controller
// connection state is always up-to-date before players are
// joining.
if (pGamePadControl) pGamePadControl->Execute();
break;
case C4AS_AfterGame:
// stop game

View File

@ -75,6 +75,8 @@
#include <C4SolidMask.h>
#include <C4FoW.h>
#include <unordered_map>
class C4GameSec1Timer : public C4ApplicationSec1Timer
{
public:
@ -1889,6 +1891,12 @@ bool C4Game::DoKeyboardInput(C4KeyCode vk_code, C4KeyEventType eEventType, bool
#endif
// compose key
C4KeyCodeEx Key(vk_code, C4KeyShiftState(fAlt*KEYS_Alt + fCtrl*KEYS_Control + fShift*KEYS_Shift), fRepeated);
return DoKeyboardInput(Key, eEventType, pForDialog, fPlrCtrlOnly, iStrength);
}
bool C4Game::DoKeyboardInput(C4KeyCodeEx Key, C4KeyEventType eEventType, class C4GUI::Dialog *pForDialog, bool fPlrCtrlOnly, int32_t iStrength)
{
Key.FixShiftKeys();
// compose keyboard scope
DWORD InScope = 0;
@ -2966,25 +2974,25 @@ bool C4Game::InitKeyboard()
// fullscreen menu
Keys.clear(); Keys.push_back(C4KeyCodeEx(K_LEFT));
if (Config.Controls.GamepadGuiControl) Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Left)));
if (Config.Controls.GamepadGuiControl) ControllerKeys::Left(Keys);
KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuLeft", KEYSCOPE_FullSMenu, new C4KeyCBEx<C4FullScreen, BYTE> (FullScreen, COM_MenuLeft, &C4FullScreen::MenuKeyControl)));
Keys.clear(); Keys.push_back(C4KeyCodeEx(K_RIGHT));
if (Config.Controls.GamepadGuiControl) Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Right)));
if (Config.Controls.GamepadGuiControl) ControllerKeys::Right(Keys);
KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuRight", KEYSCOPE_FullSMenu, new C4KeyCBEx<C4FullScreen, BYTE> (FullScreen, COM_MenuRight, &C4FullScreen::MenuKeyControl)));
Keys.clear(); Keys.push_back(C4KeyCodeEx(K_UP));
if (Config.Controls.GamepadGuiControl) Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Up)));
if (Config.Controls.GamepadGuiControl) ControllerKeys::Up(Keys);
KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuUp", KEYSCOPE_FullSMenu, new C4KeyCBEx<C4FullScreen, BYTE> (FullScreen, COM_MenuUp, &C4FullScreen::MenuKeyControl)));
Keys.clear(); Keys.push_back(C4KeyCodeEx(K_DOWN));
if (Config.Controls.GamepadGuiControl) Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Down)));
if (Config.Controls.GamepadGuiControl) ControllerKeys::Down(Keys);
KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuDown", KEYSCOPE_FullSMenu, new C4KeyCBEx<C4FullScreen, BYTE> (FullScreen, COM_MenuDown, &C4FullScreen::MenuKeyControl)));
Keys.clear(); Keys.push_back(C4KeyCodeEx(K_SPACE)); Keys.push_back(C4KeyCodeEx(K_RETURN));
if (Config.Controls.GamepadGuiControl) Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_AnyLowButton)));
if (Config.Controls.GamepadGuiControl) ControllerKeys::Ok(Keys);
KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuOK", KEYSCOPE_FullSMenu, new C4KeyCBEx<C4FullScreen, BYTE> (FullScreen, COM_MenuEnter, &C4FullScreen::MenuKeyControl))); // name used by PlrControlKeyName
Keys.clear(); Keys.push_back(C4KeyCodeEx(K_ESCAPE));
if (Config.Controls.GamepadGuiControl) Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_AnyHighButton)));
if (Config.Controls.GamepadGuiControl) ControllerKeys::Cancel(Keys);
KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuCancel", KEYSCOPE_FullSMenu, new C4KeyCBEx<C4FullScreen, BYTE> (FullScreen, COM_MenuClose, &C4FullScreen::MenuKeyControl))); // name used by PlrControlKeyName
Keys.clear(); Keys.push_back(C4KeyCodeEx(K_SPACE));
if (Config.Controls.GamepadGuiControl) Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_AnyButton)));
if (Config.Controls.GamepadGuiControl) ControllerKeys::Any(Keys);
KeyboardInput.RegisterKey(new C4CustomKey(Keys, "FullscreenMenuOpen", KEYSCOPE_FreeView, new C4KeyCB <C4FullScreen> (FullScreen, &C4FullScreen::ActivateMenuMain))); // name used by C4MainMenu!
KeyboardInput.RegisterKey(new C4CustomKey(C4KeyCodeEx(K_RIGHT ), "FilmNextPlayer", KEYSCOPE_FilmView, new C4KeyCB <C4ViewportList>(::Viewports, &C4ViewportList::ViewportNextPlayer)));
@ -3609,6 +3617,33 @@ void C4Game::Abort(bool fApproved)
Application.QuitGame();
}
static const std::unordered_map<std::string, C4GUI::Icons> str_to_icon =
{
{ "Locked", C4GUI::Ico_Ex_LockedFrontal },
{ "League", C4GUI::Ico_Ex_League },
{ "GameRunning", C4GUI::Ico_GameRunning },
{ "Lobby", C4GUI::Ico_Lobby },
{ "RuntimeJoin", C4GUI::Ico_RuntimeJoin },
{ "A", C4GUI::Ico_Controller_A },
{ "B", C4GUI::Ico_Controller_B },
{ "X", C4GUI::Ico_Controller_X },
{ "Y", C4GUI::Ico_Controller_Y },
{ "Back", C4GUI::Ico_Controller_Back },
{ "Start", C4GUI::Ico_Controller_Start },
{ "Dpad", C4GUI::Ico_Controller_Dpad },
{ "DpadLeft", C4GUI::Ico_Controller_DpadLeft },
{ "DpadRight", C4GUI::Ico_Controller_DpadRight },
{ "DpadDown", C4GUI::Ico_Controller_DpadDown },
{ "DpadUp", C4GUI::Ico_Controller_DpadUp },
{ "LeftShoulder", C4GUI::Ico_Controller_LeftShoulder },
{ "RightShoulder", C4GUI::Ico_Controller_RightShoulder },
{ "LeftTrigger", C4GUI::Ico_Controller_LeftTrigger },
{ "RightTrigger", C4GUI::Ico_Controller_RightTrigger },
{ "LeftStick", C4GUI::Ico_Controller_LeftStick },
{ "RightStick", C4GUI::Ico_Controller_RightStick },
};
bool GetTextSpecFacet(const char* szSpec, C4Facet& fct)
{
// safety
@ -3618,19 +3653,12 @@ bool GetTextSpecFacet(const char* szSpec, C4Facet& fct)
if (SEqual2(szSpec, "@Ico:"))
{
szSpec += 5;
if (SEqual2(szSpec, "Locked"))
fct = C4GUI::Icon::GetIconFacet(C4GUI::Ico_Ex_LockedFrontal);
else if (SEqual2(szSpec, "League"))
fct = C4GUI::Icon::GetIconFacet(C4GUI::Ico_Ex_League);
else if (SEqual2(szSpec, "GameRunning"))
fct = C4GUI::Icon::GetIconFacet(C4GUI::Ico_GameRunning);
else if (SEqual2(szSpec, "Lobby"))
fct = C4GUI::Icon::GetIconFacet(C4GUI::Ico_Lobby);
else if (SEqual2(szSpec, "RuntimeJoin"))
fct = C4GUI::Icon::GetIconFacet(C4GUI::Ico_RuntimeJoin);
else
return false;
return true;
auto it = str_to_icon.find(szSpec);
if (it != str_to_icon.end())
{
fct = C4GUI::Icon::GetIconFacet(it->second);
return true;
}
}
return false;

View File

@ -146,6 +146,7 @@ public:
void Evaluate();
void ShowGameOverDlg();
bool DoKeyboardInput(C4KeyCode vk_code, C4KeyEventType eEventType, bool fAlt, bool fCtrl, bool fShift, bool fRepeated, class C4GUI::Dialog *pForDialog=NULL, bool fPlrCtrlOnly=false, int32_t iStrength=-1);
bool DoKeyboardInput(C4KeyCodeEx Key, C4KeyEventType eEventType, class C4GUI::Dialog *pForDialog=NULL, bool fPlrCtrlOnly=false, int32_t iStrength=-1);
void DrawCrewOverheadText(C4TargetFacet &cgo, int32_t iPlayer);
void FixRandom(int32_t iSeed);
bool Init();

View File

@ -29,6 +29,7 @@
#include <C4GameObjects.h>
#include <C4GameControl.h>
#include <C4GameMessage.h>
#include <C4GamePadCon.h>
#include <C4GraphicsSystem.h>
#include <C4Log.h>
#include <C4MessageInput.h>
@ -2599,7 +2600,7 @@ static bool FnSetNextMission(C4PropList * _this, C4String *szNextMission, C4Stri
return true;
}
static long FnGetPlayerControlState(C4PropList * _this, long iPlr, long iControl)
static long FnGetPlayerControlState(C4PropList * _this, long iPlr, long iControl, bool fMovedState)
{
// get control set to check
C4PlayerControl *pCheckCtrl = NULL;
@ -2617,8 +2618,8 @@ static long FnGetPlayerControlState(C4PropList * _this, long iPlr, long iControl
const C4PlayerControl::CSync::ControlDownState *pControlState = pCheckCtrl->GetControlDownState(iControl);
// no state means not down
if (!pControlState) return 0;
// otherwise take down-value
return pControlState->DownState.iStrength;
// otherwise take either down-value or moved-value
return fMovedState ? pControlState->MovedState.iStrength : pControlState->DownState.iStrength;
}
// undocumented!
@ -2675,6 +2676,46 @@ static C4String *FnGetPlayerControlAssignment(C4PropList * _this, long player, l
return String(assignment->GetKeysAsString(human_readable, short_name).getData());
}
// strength: 0-1000, length: milliseconds
static bool FnPlayRumble(C4PropList * _this, long player, long strength, long length)
{
// Check parameters.
if (strength <= 0 || strength > 1000) return false;
if (length <= 0) return false;
// NO_OWNER: play rumble for all players (e.g. earthquakes)
if (player == NO_OWNER)
{
for (C4Player *plr = ::Players.First; plr; plr=plr->Next)
if (plr->Number != NO_OWNER) // can't happen, but would be a crash if it did...
FnPlayRumble(_this, plr->Number, strength, length);
return true;
}
C4Player *plr = ::Players.Get(player);
if (!plr) return false;
if (plr->pGamepad)
plr->pGamepad->PlayRumble(strength / 1000.f, length);
// We can't return whether the rumble was actually played.
return true;
}
static bool FnStopRumble(C4PropList * _this, long player)
{
// NO_OWNER: stop rumble for all players
// Not sure whether this makes sense to do - mainly provided for symmetry with PlayRumble().
if (player == NO_OWNER)
{
for (C4Player *plr = ::Players.First; plr; plr=plr->Next)
if (plr->Number != NO_OWNER) // can't happen, but would be a crash if it did...
FnStopRumble(_this, plr->Number);
return true;
}
C4Player *plr = ::Players.Get(player);
if (!plr) return false;
if (plr->pGamepad)
plr->pGamepad->StopRumble();
return true;
}
static int32_t FnGetStartupPlayerCount(C4PropList * _this)
{
// returns number of players when game was initially started
@ -2902,6 +2943,8 @@ void InitGameFunctionMap(C4AulScriptEngine *pEngine)
F(SetPlayerControlEnabled);
F(GetPlayerControlEnabled);
F(GetPlayerControlAssignment);
F(PlayRumble);
F(StopRumble);
F(GetStartupPlayerCount);
F(GetStartupTeamCount);
F(EditCursor);
@ -3091,6 +3134,14 @@ C4ScriptConstDef C4ScriptGameConstMap[]=
{ "GUI_Multiple" ,C4V_Int, C4ScriptGuiWindowStyleFlag::Multiple },
{ "GUI_IgnoreMouse" ,C4V_Int, C4ScriptGuiWindowStyleFlag::IgnoreMouse },
{ "GUI_NoCrop" ,C4V_Int, C4ScriptGuiWindowStyleFlag::NoCrop },
// control states
{ "CONS_Down" ,C4V_Int, C4PlayerControl::CONS_Down },
{ "CONS_Up" ,C4V_Int, C4PlayerControl::CONS_Up },
{ "CONS_Moved" ,C4V_Int, C4PlayerControl::CONS_Moved },
{ "PLRCON_MaxStrength" ,C4V_Int, C4GamePadControl::MaxStrength },
{ NULL, C4V_Nil, 0}
};

View File

@ -128,6 +128,7 @@ void C4GraphicsResource::Clear()
idSfcCaption = idSfcButton = idSfcButtonD = idSfcScroll = idSfcContext = 0;
barCaption.Clear(); barButton.Clear(); barButtonD.Clear();
fctButtonHighlight.Clear(); fctIcons.Clear(); fctIconsEx.Clear();
fctControllerIcons.Clear();
fctButtonHighlightRound.Clear();
fctSubmenu.Clear();
fctCheckbox.Clear();
@ -218,6 +219,8 @@ bool C4GraphicsResource::Init()
fctIcons.Set(fctIcons.Surface,0,0,C4GUI_IconWdt,C4GUI_IconHgt);
if (!LoadFile(fctIconsEx, "GUIIcons2", Files, C4FCT_Full, C4FCT_Full, false, 0)) return false;
fctIconsEx.Set(fctIconsEx.Surface,0,0,C4GUI_IconExWdt,C4GUI_IconExHgt);
if (!LoadFile(fctControllerIcons, "ControllerIcons", Files, C4FCT_Full, C4FCT_Full, false, 0)) return false;
fctControllerIcons.Set(fctControllerIcons.Surface,0,0,C4GUI_ControllerIconWdt,C4GUI_ControllerIconHgt);
if (!LoadFile(sfcScroll, "GUIScroll", Files, idSfcScroll, 0)) return false;
sfctScroll.Set(C4Facet(&sfcScroll,0,0,32,32));
if (!LoadFile(sfcContext, "GUIContext", Files, idSfcContext, 0)) return false;

View File

@ -83,6 +83,7 @@ public:
C4FacetID fctButtonHighlight;
C4FacetID fctButtonHighlightRound;
C4FacetID fctIcons, fctIconsEx;
C4FacetID fctControllerIcons;
C4FacetID fctSubmenu;
C4FacetID fctCheckbox;
C4FacetID fctBigArrows;

View File

@ -569,7 +569,7 @@ namespace C4GUI
}
}
Screen::Screen() : Window(), Mouse(0, 0), pContext(NULL), fExclusive(true), pGamePadOpener(NULL), fZoom(1.0f)
Screen::Screen() : Window(), Mouse(0, 0), pContext(NULL), fExclusive(true), fZoom(1.0f)
{
// no dialog active
pActiveDlg = NULL;
@ -585,9 +585,6 @@ namespace C4GUI
// set size - calcs client area as well
SetBounds(C4Rect(tx,ty,twdt,thgt));
SetPreferredDlgRect(C4Rect(0,0,twdt,thgt));
// GamePad
if (Application.pGamePadControl && Config.Controls.GamepadGuiControl)
pGamePadOpener = new C4GamePadOpener(0);
}
void Screen::Clear()
@ -595,8 +592,6 @@ namespace C4GUI
Container::Clear();
// dtor: Close context menu
AbortContext(false);
// GamePad
if (pGamePadOpener) delete pGamePadOpener;
// fields reset
fExclusive = true;
fZoom = 1.0f;
@ -1055,15 +1050,7 @@ namespace C4GUI
void Screen::UpdateGamepadGUIControlEnabled()
{
// update pGamePadOpener to config value
if (pGamePadOpener && (!Config.Controls.GamepadGuiControl || !Application.pGamePadControl))
{
delete pGamePadOpener; pGamePadOpener = NULL;
}
else if (!pGamePadOpener && (Config.Controls.GamepadGuiControl && Application.pGamePadControl))
{
pGamePadOpener = new C4GamePadOpener(0);
}
// Gamepad is always kept open now.
}
Screen TheScreen;

View File

@ -94,6 +94,8 @@
#define C4GUI_IconHgt 40
#define C4GUI_IconExWdt 64
#define C4GUI_IconExHgt 64
#define C4GUI_ControllerIconWdt 100
#define C4GUI_ControllerIconHgt 100
#define C4GUI_IconLabelSpacing 2 // space between an icon and its text
@ -627,7 +629,11 @@ namespace C4GUI
};
// icon indices
enum { Ico_Extended = 0x100 }; // icon index offset for extended icons
enum
{
Ico_Extended = 0x100, // icon index offset for extended icons
Ico_Controller = 0x200,
};
enum Icons
{
Ico_Empty = -2, // for context menus only
@ -705,7 +711,25 @@ namespace C4GUI
Ico_Ex_Update = Ico_Extended + 14,
Ico_Ex_Chat = Ico_Extended + 15,
Ico_Ex_GameList = Ico_Extended + 16,
Ico_Ex_Comment = Ico_Extended + 17
Ico_Ex_Comment = Ico_Extended + 17,
Ico_Controller_A = Ico_Controller + 0,
Ico_Controller_B = Ico_Controller + 3,
Ico_Controller_X = Ico_Controller + 17,
Ico_Controller_Y = Ico_Controller + 18,
Ico_Controller_Back = Ico_Controller + 1,
Ico_Controller_Start = Ico_Controller + 16,
Ico_Controller_Dpad = Ico_Controller + 6,
Ico_Controller_DpadLeft = Ico_Controller + 5,
Ico_Controller_DpadRight = Ico_Controller + 7,
Ico_Controller_DpadDown = Ico_Controller + 4,
Ico_Controller_DpadUp = Ico_Controller + 8,
Ico_Controller_LeftShoulder = Ico_Controller + 9,
Ico_Controller_RightShoulder = Ico_Controller + 12,
Ico_Controller_LeftTrigger = Ico_Controller + 11,
Ico_Controller_RightTrigger = Ico_Controller + 14,
Ico_Controller_LeftStick = Ico_Controller + 10,
Ico_Controller_RightStick = Ico_Controller + 13,
};
// cute, litte, useless thingy
@ -2566,7 +2590,6 @@ namespace C4GUI
ContextMenu *pContext; // currently opened context menu (lowest submenu)
bool fExclusive; // default true. if false, input is shared with the game
C4Rect PreferredDlgRect; // rectangle in which dialogs should be placed
C4GamePadOpener * pGamePadOpener;
float fZoom;
static Screen *pScreen; // static singleton var

View File

@ -39,7 +39,7 @@ namespace C4GUI
keys.push_back(C4KeyCodeEx(K_SPACE));
keys.push_back(C4KeyCodeEx(K_RETURN));
if (Config.Controls.GamepadGuiControl)
keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_AnyLowButton)));
ControllerKeys::Ok(keys);
pKeyButton = new C4KeyBinding(keys, "GUIButtonPress", KEYSCOPE_Gui,
new ControlKeyCB<Button>(*this, &Button::KeyButtonDown, &Button::KeyButtonUp), C4CustomKey::PRIO_Ctrl);
sText = "";

View File

@ -45,7 +45,7 @@ namespace C4GUI
Keys.push_back(C4KeyCodeEx(K_SPACE));
if (Config.Controls.GamepadGuiControl)
{
Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_AnyLowButton)));
ControllerKeys::Ok(Keys);
}
pKeyCheck = new C4KeyBinding(Keys, "GUICheckboxToggle", KEYSCOPE_Gui,
new ControlKeyCB<CheckBox>(*this, &CheckBox::KeyCheck), C4CustomKey::PRIO_Ctrl);

View File

@ -76,8 +76,8 @@ namespace C4GUI
cbKeys.push_back(C4KeyCodeEx(K_SPACE, KEYS_Alt));
if (Config.Controls.GamepadGuiControl)
{
cbKeys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_AnyLowButton)));
cbKeys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Down)));
ControllerKeys::Ok(cbKeys);
ControllerKeys::Down(cbKeys);
}
pKeyOpenCombo = new C4KeyBinding(cbKeys, "GUIComboOpen", KEYSCOPE_Gui,
new ControlKeyCB<ComboBox>(*this, &ComboBox::KeyDropDown), C4CustomKey::PRIO_Ctrl);
@ -85,7 +85,7 @@ namespace C4GUI
cbKeys.push_back(C4KeyCodeEx(K_ESCAPE));
if (Config.Controls.GamepadGuiControl)
{
cbKeys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_AnyHighButton)));
ControllerKeys::Cancel(cbKeys);
}
pKeyCloseCombo = new C4KeyBinding(cbKeys, "GUIComboClose", KEYSCOPE_Gui,
new ControlKeyCB<ComboBox>(*this, &ComboBox::KeyAbortDropDown), C4CustomKey::PRIO_Ctrl);

View File

@ -281,7 +281,7 @@ namespace C4GUI
Keys.push_back(C4KeyCodeEx(K_TAB));
if (Config.Controls.GamepadGuiControl)
{
Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Right)));
ControllerKeys::Right(Keys);
}
pKeyAdvanceControl = new C4KeyBinding(Keys, "GUIAdvanceFocus", KEYSCOPE_Gui,
new DlgKeyCBEx<Dialog, bool>(*this, false, &Dialog::KeyAdvanceFocus), C4CustomKey::PRIO_Dlg);
@ -289,7 +289,7 @@ namespace C4GUI
Keys.push_back(C4KeyCodeEx(K_TAB, KEYS_Shift));
if (Config.Controls.GamepadGuiControl)
{
Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Left)));
ControllerKeys::Left(Keys);
}
pKeyAdvanceControlB = new C4KeyBinding(Keys, "GUIAdvanceFocusBack", KEYSCOPE_Gui,
new DlgKeyCBEx<Dialog, bool>(*this, true, &Dialog::KeyAdvanceFocus), C4CustomKey::PRIO_Dlg);
@ -302,7 +302,7 @@ namespace C4GUI
Keys.push_back(C4KeyCodeEx(K_RETURN));
if (Config.Controls.GamepadGuiControl)
{
Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_AnyLowButton)));
ControllerKeys::Ok(Keys);
}
pKeyEnter = new C4KeyBinding(Keys, "GUIDialogOkay", KEYSCOPE_Gui,
new DlgKeyCB<Dialog>(*this, &Dialog::KeyEnter), C4CustomKey::PRIO_Dlg);
@ -310,7 +310,7 @@ namespace C4GUI
Keys.push_back(C4KeyCodeEx(K_ESCAPE));
if (Config.Controls.GamepadGuiControl)
{
Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_AnyHighButton)));
ControllerKeys::Cancel(Keys);
}
pKeyEscape = new C4KeyBinding(Keys, "GUIDialogAbort", KEYSCOPE_Gui,
new DlgKeyCB<Dialog>(*this, &Dialog::KeyEscape), C4CustomKey::PRIO_Dlg);

View File

@ -710,7 +710,7 @@ namespace C4GUI
keys.push_back(C4KeyCodeEx(K_ESCAPE));
if (Config.Controls.GamepadGuiControl)
{
keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_AnyHighButton)));
ControllerKeys::Cancel(keys);
}
pKeyAbort = new C4KeyBinding(keys, "GUIRenameEditAbort", KEYSCOPE_Gui,
new ControlKeyCB<RenameEdit>(*this, &RenameEdit::KeyAbort), C4CustomKey::PRIO_FocusCtrl);

View File

@ -428,12 +428,18 @@ namespace C4GUI
C4Facet Icon::GetIconFacet(Icons icoIconIndex)
{
if (icoIconIndex == Ico_None) return C4Facet();
C4Facet &rFacet = (icoIconIndex & Ico_Extended) ? ::GraphicsResource.fctIconsEx : ::GraphicsResource.fctIcons;
icoIconIndex = Icons(icoIconIndex & (Ico_Extended-1));
C4Facet *rFacet;
switch (icoIconIndex & ~0xff)
{
case Ico_Extended: rFacet = &::GraphicsResource.fctIconsEx; break;
case Ico_Controller: rFacet = &::GraphicsResource.fctControllerIcons; break;
default: rFacet = &::GraphicsResource.fctIcons;
}
icoIconIndex = Icons(icoIconIndex & 0xff);
int32_t iXMax, iYMax;
rFacet.GetPhaseNum(iXMax, iYMax);
rFacet->GetPhaseNum(iXMax, iYMax);
if (!iXMax) iXMax = 6;
return rFacet.GetPhase(icoIconIndex % iXMax, icoIconIndex / iXMax);
return rFacet->GetPhase(icoIconIndex % iXMax, icoIconIndex / iXMax);
}

View File

@ -43,22 +43,22 @@ namespace C4GUI
new ControlKeyCB<ListBox>(*this, &ListBox::KeyContext), C4CustomKey::PRIO_Ctrl);
C4CustomKey::CodeList keys;
keys.push_back(C4KeyCodeEx(K_UP));
if (Config.Controls.GamepadGuiControl) keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Up)));
if (Config.Controls.GamepadGuiControl) ControllerKeys::Up(keys);
pKeyUp = new C4KeyBinding(keys, "GUIListBoxUp", KEYSCOPE_Gui,
new ControlKeyCB<ListBox>(*this, &ListBox::KeyUp), C4CustomKey::PRIO_Ctrl);
keys.clear();
keys.push_back(C4KeyCodeEx(K_DOWN));
if (Config.Controls.GamepadGuiControl) keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Down)));
if (Config.Controls.GamepadGuiControl) ControllerKeys::Down(keys);
pKeyDown = new C4KeyBinding(keys, "GUIListBoxDown", KEYSCOPE_Gui,
new ControlKeyCB<ListBox>(*this, &ListBox::KeyDown), C4CustomKey::PRIO_Ctrl);
keys.clear();
keys.push_back(C4KeyCodeEx(K_LEFT));
if (Config.Controls.GamepadGuiControl) keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Left)));
if (Config.Controls.GamepadGuiControl) ControllerKeys::Left(keys);
pKeyLeft = new C4KeyBinding(keys, "GUIListBoxLeft", KEYSCOPE_Gui,
new ControlKeyCB<ListBox>(*this, &ListBox::KeyLeft), C4CustomKey::PRIO_Ctrl);
keys.clear();
keys.push_back(C4KeyCodeEx(K_RIGHT));
if (Config.Controls.GamepadGuiControl) keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Right)));
if (Config.Controls.GamepadGuiControl) ControllerKeys::Right(keys);
pKeyRight = new C4KeyBinding(keys, "GUIListBoxRight", KEYSCOPE_Gui,
new ControlKeyCB<ListBox>(*this, &ListBox::KeyRight), C4CustomKey::PRIO_Ctrl);
pKeyPageUp = new C4KeyBinding(C4KeyCodeEx(K_PAGEUP), "GUIListBoxPageUp", KEYSCOPE_Gui,
@ -75,7 +75,7 @@ namespace C4GUI
keys.push_back(C4KeyCodeEx(K_RETURN, KEYS_Alt));
if (Config.Controls.GamepadGuiControl)
{
keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_AnyLowButton)));
ControllerKeys::Ok(keys);
}
pKeyActivate = new C4KeyBinding(keys, "GUIListActivate", KEYSCOPE_Gui,
new ControlKeyCB<ListBox>(*this, &ListBox::KeyActivate), C4CustomKey::PRIO_Ctrl);

View File

@ -94,7 +94,7 @@ namespace C4GUI
Keys.push_back(C4KeyCodeEx(K_UP));
if (Config.Controls.GamepadGuiControl)
{
Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Up)));
ControllerKeys::Up(Keys);
}
pKeySelUp = new C4KeyBinding(Keys, "GUIContextSelUp", KEYSCOPE_Gui,
new C4KeyCB<ContextMenu>(*this, &ContextMenu::KeySelUp), C4CustomKey::PRIO_Context);
@ -103,7 +103,7 @@ namespace C4GUI
Keys.push_back(C4KeyCodeEx(K_DOWN));
if (Config.Controls.GamepadGuiControl)
{
Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Down)));
ControllerKeys::Down(Keys);
}
pKeySelDown = new C4KeyBinding(Keys, "GUIContextSelDown", KEYSCOPE_Gui,
new C4KeyCB<ContextMenu>(*this, &ContextMenu::KeySelDown), C4CustomKey::PRIO_Context);
@ -112,7 +112,7 @@ namespace C4GUI
Keys.push_back(C4KeyCodeEx(K_RIGHT));
if (Config.Controls.GamepadGuiControl)
{
Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Right)));
ControllerKeys::Right(Keys);
}
pKeySubmenu = new C4KeyBinding(Keys, "GUIContextSubmenu", KEYSCOPE_Gui,
new C4KeyCB<ContextMenu>(*this, &ContextMenu::KeySubmenu), C4CustomKey::PRIO_Context);
@ -121,7 +121,7 @@ namespace C4GUI
Keys.push_back(C4KeyCodeEx(K_LEFT));
if (Config.Controls.GamepadGuiControl)
{
Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Left)));
ControllerKeys::Left(Keys);
}
pKeyBack = new C4KeyBinding(Keys, "GUIContextBack", KEYSCOPE_Gui,
new C4KeyCB<ContextMenu>(*this, &ContextMenu::KeyBack), C4CustomKey::PRIO_Context);
@ -130,7 +130,7 @@ namespace C4GUI
Keys.push_back(C4KeyCodeEx(K_ESCAPE));
if (Config.Controls.GamepadGuiControl)
{
Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_AnyHighButton)));
ControllerKeys::Cancel(Keys);
}
pKeyAbort = new C4KeyBinding(Keys, "GUIContextAbort", KEYSCOPE_Gui,
new C4KeyCB<ContextMenu>(*this, &ContextMenu::KeyAbort), C4CustomKey::PRIO_Context);
@ -139,7 +139,7 @@ namespace C4GUI
Keys.push_back(C4KeyCodeEx(K_RETURN));
if (Config.Controls.GamepadGuiControl)
{
Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_AnyLowButton)));
ControllerKeys::Ok(Keys);
}
pKeyConfirm = new C4KeyBinding(Keys, "GUIContextConfirm", KEYSCOPE_Gui,
new C4KeyCB<ContextMenu>(*this, &ContextMenu::KeyConfirm), C4CustomKey::PRIO_Context);

View File

@ -181,7 +181,7 @@ namespace C4GUI
Keys.push_back(C4KeyCodeEx(K_UP));
if (Config.Controls.GamepadGuiControl)
{
Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Up)));
ControllerKeys::Up(Keys);
}
pKeySelUp = new C4KeyBinding(Keys, "GUITabularSelUp", KEYSCOPE_Gui,
new ControlKeyCB<Tabular>(*this, &Tabular::KeySelUp), C4CustomKey::PRIO_Ctrl);
@ -190,7 +190,7 @@ namespace C4GUI
Keys.push_back(C4KeyCodeEx(K_DOWN));
if (Config.Controls.GamepadGuiControl)
{
Keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Down)));
ControllerKeys::Down(Keys);
}
pKeySelDown = new C4KeyBinding(Keys, "GUITabularSelDown", KEYSCOPE_Gui,
new ControlKeyCB<Tabular>(*this, &Tabular::KeySelDown), C4CustomKey::PRIO_Ctrl);

View File

@ -31,8 +31,11 @@
#endif
#include <algorithm>
#include <regex>
#include <string>
#include <unordered_map>
#ifdef USE_SDL_MAINLOOP
#ifdef HAVE_SDL
#include <SDL.h>
#endif
@ -190,6 +193,11 @@ const C4KeyCodeMapEntry KeyCodeMap[] = {
};
#endif
C4KeyCodeEx::C4KeyCodeEx(C4KeyCode key, C4KeyShiftState Shift, bool fIsRepeated, int32_t deviceId)
: Key(key), dwShift(Shift), fRepeated(fIsRepeated), deviceId(deviceId)
{
}
void C4KeyCodeEx::FixShiftKeys()
{
// reduce stuff like Ctrl+RightCtrl to simply RightCtrl
@ -205,6 +213,36 @@ C4KeyCode C4KeyCodeEx::GetKeyByScanCode(const char *scan_code)
return scan_code_int;
}
static const std::unordered_map<std::string, C4KeyCode> controllercodes =
{
{ "ButtonA", KEY_CONTROLLER_ButtonA },
{ "ButtonB", KEY_CONTROLLER_ButtonB },
{ "ButtonX", KEY_CONTROLLER_ButtonX },
{ "ButtonY", KEY_CONTROLLER_ButtonY },
{ "ButtonBack", KEY_CONTROLLER_ButtonBack },
{ "ButtonGuide", KEY_CONTROLLER_ButtonGuide },
{ "ButtonStart", KEY_CONTROLLER_ButtonStart },
{ "ButtonLeftStick", KEY_CONTROLLER_ButtonLeftStick },
{ "ButtonRightStick", KEY_CONTROLLER_ButtonRightStick },
{ "ButtonLeftShoulder", KEY_CONTROLLER_ButtonLeftShoulder },
{ "ButtonRightShoulder", KEY_CONTROLLER_ButtonRightShoulder },
{ "ButtonDpadUp", KEY_CONTROLLER_ButtonDpadUp },
{ "ButtonDpadDown", KEY_CONTROLLER_ButtonDpadDown },
{ "ButtonDpadLeft", KEY_CONTROLLER_ButtonDpadLeft },
{ "ButtonDpadRight", KEY_CONTROLLER_ButtonDpadRight },
{ "AnyButton", KEY_CONTROLLER_AnyButton },
{ "LeftStickLeft", KEY_CONTROLLER_AxisLeftXLeft },
{ "LeftStickRight", KEY_CONTROLLER_AxisLeftXRight },
{ "LeftStickUp", KEY_CONTROLLER_AxisLeftYUp },
{ "LeftStickDown", KEY_CONTROLLER_AxisLeftYDown },
{ "RightStickLeft", KEY_CONTROLLER_AxisRightXLeft },
{ "RightStickRight", KEY_CONTROLLER_AxisRightXRight },
{ "RightStickUp", KEY_CONTROLLER_AxisRightYUp },
{ "RightStickDown", KEY_CONTROLLER_AxisRightYDown },
{ "LeftTrigger", KEY_CONTROLLER_AxisTriggerLeft },
{ "RightTrigger", KEY_CONTROLLER_AxisTriggerRight },
};
C4KeyCode C4KeyCodeEx::String2KeyCode(const StdStrBuf &sName)
{
// direct key code?
@ -215,47 +253,16 @@ C4KeyCode C4KeyCodeEx::String2KeyCode(const StdStrBuf &sName)
// scan code
if (*sName.getData() == '$') return GetKeyByScanCode(sName.getData());
// direct gamepad code
#ifdef _WIN32
if (!strnicmp(sName.getData(), "Joy", 3))
#else
if (!strncasecmp(sName.getData(), "Joy", 3))
#endif
std::regex controller_re(R"/(^Controller(\w+)$)/");
std::cmatch matches;
if (std::regex_match(sName.getData(), matches, controller_re))
{
int iGamepad;
if (sscanf(sName.getData(), "Joy%d", &iGamepad) == 1)
{
// skip Joy[number]
const char *key_str = sName.getData()+4;
while (isdigit(*key_str)) ++key_str;
// check for button (single, uppercase letter) (e.g. Joy1A)
if (*key_str && !key_str[1])
{
char cGamepadButton = toupper(*key_str);
if (Inside(cGamepadButton, 'A', 'Z'))
{
cGamepadButton = cGamepadButton - 'A';
return KEY_Gamepad(iGamepad-1, KEY_JOY_Button(cGamepadButton));
}
}
else
{
// check for standard axis (e.g. Joy1Left)
if (!stricmp(key_str, "Left")) return KEY_Gamepad(iGamepad-1, KEY_JOY_Left);
if (!stricmp(key_str, "Up")) return KEY_Gamepad(iGamepad-1, KEY_JOY_Up);
if (!stricmp(key_str, "Down")) return KEY_Gamepad(iGamepad-1, KEY_JOY_Down);
if (!stricmp(key_str, "Right")) return KEY_Gamepad(iGamepad-1, KEY_JOY_Right);
// check for specific axis (e.g. Joy1Axis1Min)
int iAxis;
if (sscanf(key_str, "Axis%d", &iAxis) == 1 && iAxis>0)
{
--iAxis; // axis is 0-based internally but written 1-based in config
key_str += 5;
while (isdigit(*key_str)) ++key_str;
if (!stricmp(key_str, "Min")) return KEY_Gamepad(iGamepad-1, KEY_JOY_Axis(iAxis, false));
if (!stricmp(key_str, "Max")) return KEY_Gamepad(iGamepad-1, KEY_JOY_Axis(iAxis, true));
}
}
}
auto keycode_it = controllercodes.find(matches[1].str());
if (keycode_it != controllercodes.end())
return KEY_Gamepad(keycode_it->second);
else
return KEY_Undefined;
}
bool is_mouse_key;
#ifdef _WIN32
@ -326,34 +333,51 @@ StdStrBuf C4KeyCodeEx::KeyCode2String(C4KeyCode wCode, bool fHumanReadable, bool
// Gamepad keys
if (Key_IsGamepad(wCode))
{
int iGamepad = Key_GetGamepad(wCode);
int gamepad_event = Key_GetGamepadEvent(wCode);
switch (gamepad_event)
if (fHumanReadable)
{
case KEY_JOY_Left: return FormatString("Joy%dLeft", iGamepad+1);
case KEY_JOY_Up: return FormatString("Joy%dUp", iGamepad+1);
case KEY_JOY_Down: return FormatString("Joy%dDown", iGamepad+1);
case KEY_JOY_Right: return FormatString("Joy%dRight", iGamepad+1);
default:
if (Key_IsGamepadAxis(wCode))
switch (Key_GetGamepadEvent(wCode))
{
if (fHumanReadable)
// This is still not great, but it is not really possible to assign unknown axes to "left/right" "up/down"...
return FormatString("[%d] %s", int(1 + Key_GetGamepadAxisIndex(wCode)), Key_IsGamepadAxisHigh(wCode) ? "Max" : "Min");
else
return FormatString("Joy%dAxis%d%s", iGamepad+1, static_cast<int>(Key_GetGamepadAxisIndex(wCode)+1), Key_IsGamepadAxisHigh(wCode) ? "Max" : "Min");
}
else
{
// button
if (fHumanReadable)
// If there should be gamepads around with A B C D... on the buttons, we might create a display option to show letters instead...
return FormatString("< %d >", int(1 + Key_GetGamepadButtonIndex(wCode)));
else
return FormatString("Joy%d%c", iGamepad+1, static_cast<char>(Key_GetGamepadButtonIndex(wCode) + 'A'));
case KEY_CONTROLLER_ButtonA : return StdStrBuf("{{@Ico:A}}");
case KEY_CONTROLLER_ButtonB : return StdStrBuf("{{@Ico:B}}");
case KEY_CONTROLLER_ButtonX : return StdStrBuf("{{@Ico:X}}");
case KEY_CONTROLLER_ButtonY : return StdStrBuf("{{@Ico:Y}}");
case KEY_CONTROLLER_ButtonBack : return StdStrBuf("{{@Ico:Back}}");
case KEY_CONTROLLER_ButtonGuide : return StdStrBuf("Guide");
case KEY_CONTROLLER_ButtonStart : return StdStrBuf("{{@Ico:Start}}");
case KEY_CONTROLLER_ButtonLeftStick : return StdStrBuf("{{@Ico:LeftStick}}");
case KEY_CONTROLLER_ButtonRightStick : return StdStrBuf("{{@Ico:RightStick}}");
case KEY_CONTROLLER_ButtonLeftShoulder : return StdStrBuf("{{@Ico:LeftShoulder}}");
case KEY_CONTROLLER_ButtonRightShoulder : return StdStrBuf("{{@Ico:RightShoulder}}");
case KEY_CONTROLLER_ButtonDpadUp : return StdStrBuf("{{@Ico:DpadUp}}");
case KEY_CONTROLLER_ButtonDpadDown : return StdStrBuf("{{@Ico:DpadDown}}");
case KEY_CONTROLLER_ButtonDpadLeft : return StdStrBuf("{{@Ico:DpadLeft}}");
case KEY_CONTROLLER_ButtonDpadRight : return StdStrBuf("{{@Ico:DpadRight}}");
case KEY_CONTROLLER_AnyButton : return StdStrBuf("Any Button");
case KEY_CONTROLLER_AxisLeftXLeft : return StdStrBuf("{{@Ico:LeftStick}} Left");
case KEY_CONTROLLER_AxisLeftXRight : return StdStrBuf("{{@Ico:LeftStick}} Right");
case KEY_CONTROLLER_AxisLeftYUp : return StdStrBuf("{{@Ico:LeftStick}} Up");
case KEY_CONTROLLER_AxisLeftYDown : return StdStrBuf("{{@Ico:LeftStick}} Down");
case KEY_CONTROLLER_AxisRightXLeft : return StdStrBuf("{{@Ico:RightStick}} Left");
case KEY_CONTROLLER_AxisRightXRight : return StdStrBuf("{{@Ico:RightStick}} Right");
case KEY_CONTROLLER_AxisRightYUp : return StdStrBuf("{{@Ico:RightStick}} Up");
case KEY_CONTROLLER_AxisRightYDown : return StdStrBuf("{{@Ico:RightStick}} Down");
case KEY_CONTROLLER_AxisTriggerLeft : return StdStrBuf("{{@Ico:LeftTrigger}}");
case KEY_CONTROLLER_AxisTriggerRight : return StdStrBuf("{{@Ico:RightTrigger}}");
}
}
else
{
// A linear search in our small map is probably fast enough.
auto it = std::find_if(controllercodes.begin(), controllercodes.end(), [wCode](const auto &p)
{
return p.second == Key_GetGamepadEvent(wCode);
});
if (it != controllercodes.end())
return FormatString("Controller%s", it->first.c_str());
}
return StdStrBuf("Unknown");
}
// Mouse keys
if (Key_IsMouse(wCode))
{
@ -602,6 +626,15 @@ C4CustomKey::~C4CustomKey()
(*i)->Deref();
}
bool C4CustomKey::IsCodeMatched(const C4KeyCodeEx &key) const
{
const CodeList &codes = GetCodes();
for (const auto &code : codes)
if (code == key)
return true;
return false;
}
void C4CustomKey::Update(const C4CustomKey *pByKey)
{
assert(pByKey);
@ -812,28 +845,12 @@ bool C4KeyboardInput::DoInput(const C4KeyCodeEx &InKey, C4KeyEventType InEvent,
FallbackKeys[iKeyRangeCnt++] = InKey.Key;
if (Key_IsGamepadButton(InKey.Key))
{
uint8_t byGamepad = Key_GetGamepad(InKey.Key);
uint8_t byBtnIndex = Key_GetGamepadButtonIndex(InKey.Key);
// even/odd button events: Add even button indices as odd events, because byBtnIndex is zero-based and the event naming scheme is for one-based button indices
if (byBtnIndex % 2) FallbackKeys[iKeyRangeCnt++] = KEY_Gamepad(byGamepad, KEY_JOY_AnyEvenButton);
else FallbackKeys[iKeyRangeCnt++] = KEY_Gamepad(byGamepad, KEY_JOY_AnyOddButton);
// high/low button events
if (byBtnIndex < 4) FallbackKeys[iKeyRangeCnt++] = KEY_Gamepad(byGamepad, KEY_JOY_AnyLowButton);
else FallbackKeys[iKeyRangeCnt++] = KEY_Gamepad(byGamepad, KEY_JOY_AnyHighButton);
// "any gamepad button"-event
FallbackKeys[iKeyRangeCnt++] = KEY_Gamepad(byGamepad, KEY_JOY_AnyButton);
FallbackKeys[iKeyRangeCnt++] = KEY_Gamepad(KEY_CONTROLLER_AnyButton);
}
else if (Key_IsGamepadAxis(InKey.Key))
{
// xy-axis-events for all even/odd axises
uint8_t byGamepad = Key_GetGamepad(InKey.Key);
uint8_t byAxis = Key_GetGamepadAxisIndex(InKey.Key);
bool fHigh = Key_IsGamepadAxisHigh(InKey.Key);
C4KeyCode keyAxisDir;
if (byAxis % 2)
if (fHigh) keyAxisDir = KEY_JOY_Down; else keyAxisDir = KEY_JOY_Up;
else if (fHigh) keyAxisDir = KEY_JOY_Right; else keyAxisDir = KEY_JOY_Left;
FallbackKeys[iKeyRangeCnt++] = KEY_Gamepad(byGamepad, (uint8_t)keyAxisDir);
// TODO: do we need "any axis" events?
}
if (InKey.Key != KEY_Any) FallbackKeys[iKeyRangeCnt++] = KEY_Any;
// now get key ranges for fallback chain

View File

@ -46,14 +46,15 @@ enum C4KeyEventType
KEYEV_None = 0, // no event
KEYEV_Down = 1, // in response to WM_KEYDOWN or joypad button pressed
KEYEV_Up = 2, // in response to WM_KEYUP or joypad button released
KEYEV_Pressed = 3 // in response to WM_KEYPRESSED
KEYEV_Pressed = 3, // in response to WM_KEYPRESSED
KEYEV_Moved = 4, // when moving a gamepad stick
};
// keyboard code
typedef unsigned long C4KeyCode;
// Gamepad codes (KEY_JOY_*): Masked as 0x420000; bit 8-15 used for gamepad index
const C4KeyCode KEY_JOY_Mask = 0x420000;
// Gamepad codes (KEY_CONTROLLER_*): Masked as 0x420000; bit 8-15 used for gamepad index
const C4KeyCode KEY_CONTROLLER_Mask = 0x420000;
// Mouse codes (KEY_MOUSE_*): Masked as 0x430000; bit 8-15 used for mouse index
const C4KeyCode KEY_MOUSE_Mask = 0x430000;
@ -62,20 +63,36 @@ const C4KeyCode
KEY_Default = 0, // no key
KEY_Any = ~0, // used for default key processing
KEY_Undefined = (~0)^1, // used to indicate an unknown key
KEY_JOY_Left = 1, // joypad axis control: Any x axis min
KEY_JOY_Up = 2, // joypad axis control: Any y axis min
KEY_JOY_Right = 3, // joypad axis control: Any x axis max
KEY_JOY_Down = 4, // joypad axis control: Any y axis max
KEY_JOY_Button1 = 0x10, // key index of joypad buttons + button index for more buttons
KEY_JOY_ButtonMax = KEY_JOY_Button1+0x1f, // maximum number of supported buttons on a gamepad
KEY_JOY_Axis1Min = 0x30,
KEY_JOY_Axis1Max = 0x31,
KEY_JOY_AxisMax = KEY_JOY_Axis1Min + 0x20,
KEY_JOY_AnyButton = 0xff, // any joypad button (not axis)
KEY_JOY_AnyOddButton = 0xfe, // joypad buttons 1, 3, 5, etc.
KEY_JOY_AnyEvenButton = 0xfd, // joypad buttons 2, 4, 6, etc.
KEY_JOY_AnyLowButton = 0xfc, // joypad buttons 1 - 4
KEY_JOY_AnyHighButton = 0xfb, // joypad buttons > 4
KEY_CONTROLLER_ButtonMin = 0x10, // first button
KEY_CONTROLLER_ButtonA = 0x10,
KEY_CONTROLLER_ButtonB = 0x11,
KEY_CONTROLLER_ButtonX = 0x12,
KEY_CONTROLLER_ButtonY = 0x13,
KEY_CONTROLLER_ButtonBack = 0x14,
KEY_CONTROLLER_ButtonGuide = 0x15,
KEY_CONTROLLER_ButtonStart = 0x16,
KEY_CONTROLLER_ButtonLeftStick = 0x17,
KEY_CONTROLLER_ButtonRightStick = 0x18,
KEY_CONTROLLER_ButtonLeftShoulder = 0x19,
KEY_CONTROLLER_ButtonRightShoulder = 0x1a,
KEY_CONTROLLER_ButtonDpadUp = 0x1b,
KEY_CONTROLLER_ButtonDpadDown = 0x1c,
KEY_CONTROLLER_ButtonDpadLeft = 0x1d,
KEY_CONTROLLER_ButtonDpadRight = 0x1e,
KEY_CONTROLLER_ButtonMax = 0x1e, // last button
KEY_CONTROLLER_AnyButton = 0xff, // any of the buttons above
KEY_CONTROLLER_AxisMin = 0x30, // first axis
KEY_CONTROLLER_AxisLeftXLeft = 0x30,
KEY_CONTROLLER_AxisLeftXRight = 0x31,
KEY_CONTROLLER_AxisLeftYUp = 0x32,
KEY_CONTROLLER_AxisLeftYDown = 0x33,
KEY_CONTROLLER_AxisRightXLeft = 0x34,
KEY_CONTROLLER_AxisRightXRight = 0x35,
KEY_CONTROLLER_AxisRightYUp = 0x36,
KEY_CONTROLLER_AxisRightYDown = 0x37,
KEY_CONTROLLER_AxisTriggerLeft = 0x39, // triggers are only positive
KEY_CONTROLLER_AxisTriggerRight = 0x3b,
KEY_CONTROLLER_AxisMax = 0x3b, // last axis
KEY_MOUSE_Move = 1, // mouse control: mouse movement
KEY_MOUSE_Button1 = 0x10, // key index of mouse buttons + button index for more buttons
KEY_MOUSE_ButtonLeft = KEY_MOUSE_Button1 + 0,
@ -90,18 +107,18 @@ const C4KeyCode
KEY_MOUSE_Wheel1Up = 0x40, // mouse control: wheel up
KEY_MOUSE_Wheel1Down = 0x41; // mouse control: wheel down
inline uint8_t KEY_JOY_Button(uint8_t idx) { return KEY_JOY_Button1+idx; }
inline uint8_t KEY_JOY_Axis(uint8_t idx, bool fMax) { return KEY_JOY_Axis1Min+2*idx+fMax; }
inline uint8_t KEY_CONTROLLER_Button(uint8_t idx) { return KEY_CONTROLLER_ButtonMin+idx; }
inline uint8_t KEY_CONTROLLER_Axis(uint8_t idx, bool fMax) { return KEY_CONTROLLER_AxisMin+2*idx+fMax; }
inline C4KeyCode KEY_Gamepad(uint8_t idGamepad, uint8_t idButton) // convert gamepad key to Clonk-gamepad-keycode
inline C4KeyCode KEY_Gamepad(uint8_t idButton) // convert gamepad key to Clonk-gamepad-keycode
{
// mask key as 0x0042ggbb, where gg is gamepad ID and bb is button ID.
return KEY_JOY_Mask + (idGamepad<<8) + idButton;
// mask key as 0x004200bb, where 00 used to be the gamepad ID and bb is button ID.
return KEY_CONTROLLER_Mask + idButton;
}
inline bool Key_IsGamepad(C4KeyCode key)
{
return (0xff0000 & key) == KEY_JOY_Mask;
return (0xff0000 & key) == KEY_CONTROLLER_Mask;
}
inline uint8_t Key_GetGamepad(C4KeyCode key)
@ -117,25 +134,25 @@ inline uint8_t Key_GetGamepadEvent(C4KeyCode key)
inline bool Key_IsGamepadButton(C4KeyCode key)
{
// whether this is a unique button event (AnyButton not included)
return Key_IsGamepad(key) && Inside<uint8_t>(Key_GetGamepadEvent(key), KEY_JOY_Button1, KEY_JOY_ButtonMax);
return Key_IsGamepad(key) && Inside<uint8_t>(Key_GetGamepadEvent(key), KEY_CONTROLLER_ButtonMin, KEY_CONTROLLER_ButtonMax);
}
inline bool Key_IsGamepadAxis(C4KeyCode key)
{
// whether this is a unique button event (AnyButton not included)
return Key_IsGamepad(key) && Inside<uint8_t>(Key_GetGamepadEvent(key), KEY_JOY_Axis1Min, KEY_JOY_AxisMax);
return Key_IsGamepad(key) && Inside<uint8_t>(Key_GetGamepadEvent(key), KEY_CONTROLLER_AxisMin, KEY_CONTROLLER_AxisMax);
}
inline uint8_t Key_GetGamepadButtonIndex(C4KeyCode key)
{
// get zero-based button index
return Key_GetGamepadEvent(key) - KEY_JOY_Button1;
return Key_GetGamepadEvent(key) - KEY_CONTROLLER_ButtonMin;
}
inline uint8_t Key_GetGamepadAxisIndex(C4KeyCode key)
{
// get zero-based axis index
return (Key_GetGamepadEvent(key) - KEY_JOY_Axis1Min) / 2;
return (Key_GetGamepadEvent(key) - KEY_CONTROLLER_AxisMin) / 2;
}
inline bool Key_IsGamepadAxisHigh(C4KeyCode key)
@ -188,6 +205,8 @@ struct C4KeyCodeEx
C4KeyCode Key; // the key
DWORD dwShift; // the status of Alt, Shift, Control
int32_t deviceId;
// if set, the keycode was generated by a key that has been held down
// this flag is ignored in comparison operations
bool fRepeated;
@ -212,8 +231,7 @@ struct C4KeyCodeEx
void CompileFunc(StdCompiler *pComp, StdStrBuf *pOutBuf=NULL);
C4KeyCodeEx(C4KeyCode Key = KEY_Default, C4KeyShiftState Shift = KEYS_None, bool fIsRepeated = false)
: Key(Key), dwShift(Shift), fRepeated(fIsRepeated) {}
C4KeyCodeEx(C4KeyCode Key = KEY_Default, C4KeyShiftState Shift = KEYS_None, bool fIsRepeated = false, int32_t deviceId = -1);
bool IsRepeated() const { return fRepeated; }
@ -234,6 +252,21 @@ struct C4KeyEventData
bool operator ==(const struct C4KeyEventData &cmp) const;
};
// Helper functions for high-level GUI control mappings.
namespace ControllerKeys {
template<class T> void Any(T &keys) { keys.push_back(C4KeyCodeEx(KEY_Gamepad(KEY_CONTROLLER_AnyButton))); }
template<class T> void Cancel(T &keys) { keys.push_back(C4KeyCodeEx(KEY_Gamepad(KEY_CONTROLLER_ButtonB))); }
template<class T> void Ok(T &keys) { keys.push_back(C4KeyCodeEx(KEY_Gamepad(KEY_CONTROLLER_ButtonA))); }
template<class T> void Left(T &keys) { keys.push_back(C4KeyCodeEx(KEY_Gamepad(KEY_CONTROLLER_AxisLeftXLeft)));
keys.push_back(C4KeyCodeEx(KEY_Gamepad(KEY_CONTROLLER_ButtonDpadLeft))); }
template<class T> void Right(T &keys) { keys.push_back(C4KeyCodeEx(KEY_Gamepad(KEY_CONTROLLER_AxisLeftXRight)));
keys.push_back(C4KeyCodeEx(KEY_Gamepad(KEY_CONTROLLER_ButtonDpadRight))); }
template<class T> void Up(T &keys) { keys.push_back(C4KeyCodeEx(KEY_Gamepad(KEY_CONTROLLER_AxisLeftYUp)));
keys.push_back(C4KeyCodeEx(KEY_Gamepad(KEY_CONTROLLER_ButtonDpadUp))); }
template<class T> void Down(T &keys) { keys.push_back(C4KeyCodeEx(KEY_Gamepad(KEY_CONTROLLER_AxisLeftYDown)));
keys.push_back(C4KeyCodeEx(KEY_Gamepad(KEY_CONTROLLER_ButtonDpadDown))); }
}
// callback interface
class C4KeyboardCallbackInterface
{
@ -265,7 +298,7 @@ public:
protected:
TargetClass &rTarget;
CallbackFunc pFuncDown, pFuncUp, pFuncPressed;
CallbackFunc pFuncDown, pFuncUp, pFuncPressed, pFuncMoved;
protected:
virtual bool OnKeyEvent(const C4KeyCodeEx &key, C4KeyEventType eEv)
@ -276,6 +309,7 @@ protected:
case KEYEV_Down: return pFuncDown ? (rTarget.*pFuncDown)() : false;
case KEYEV_Up: return pFuncUp ? (rTarget.*pFuncUp)() : false;
case KEYEV_Pressed: return pFuncPressed ? (rTarget.*pFuncPressed)() : false;
case KEYEV_Moved: return pFuncMoved ? (rTarget.*pFuncMoved)() : false;
default: return false;
}
}
@ -283,8 +317,8 @@ protected:
virtual bool CheckCondition() { return true; }
public:
C4KeyCB(TargetClass &rTarget, CallbackFunc pFuncDown, CallbackFunc pFuncUp=NULL, CallbackFunc pFuncPressed=NULL)
: rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed) {}
C4KeyCB(TargetClass &rTarget, CallbackFunc pFuncDown, CallbackFunc pFuncUp=NULL, CallbackFunc pFuncPressed=NULL, CallbackFunc pFuncMoved=NULL)
: rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed), pFuncMoved(pFuncMoved) {}
};
// callback interface that passes the pressed key as a parameter
@ -295,7 +329,7 @@ public:
protected:
TargetClass &rTarget;
CallbackFunc pFuncDown, pFuncUp, pFuncPressed;
CallbackFunc pFuncDown, pFuncUp, pFuncPressed, pFuncMoved;
protected:
virtual bool OnKeyEvent(const C4KeyCodeEx &key, C4KeyEventType eEv)
@ -306,6 +340,7 @@ protected:
case KEYEV_Down: return pFuncDown ? (rTarget.*pFuncDown)(key) : false;
case KEYEV_Up: return pFuncUp ? (rTarget.*pFuncUp)(key) : false;
case KEYEV_Pressed: return pFuncPressed ? (rTarget.*pFuncPressed)(key) : false;
case KEYEV_Moved: return pFuncMoved ? (rTarget.*pFuncMoved)(key) : false;
default: return false;
}
}
@ -313,8 +348,8 @@ protected:
virtual bool CheckCondition() { return true; }
public:
C4KeyCBPassKey(TargetClass &rTarget, CallbackFunc pFuncDown, CallbackFunc pFuncUp=NULL, CallbackFunc pFuncPressed=NULL)
: rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed) {}
C4KeyCBPassKey(TargetClass &rTarget, CallbackFunc pFuncDown, CallbackFunc pFuncUp=NULL, CallbackFunc pFuncPressed=NULL, CallbackFunc pFuncMoved=NULL)
: rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed), pFuncMoved(pFuncMoved) {}
};
// parameterized callback interface
@ -325,7 +360,7 @@ public:
protected:
TargetClass &rTarget;
CallbackFunc pFuncDown, pFuncUp, pFuncPressed;
CallbackFunc pFuncDown, pFuncUp, pFuncPressed, pFuncMoved;
ParameterType par;
protected:
@ -337,6 +372,7 @@ protected:
case KEYEV_Down: return pFuncDown ? (rTarget.*pFuncDown)(par) : false;
case KEYEV_Up: return pFuncUp ? (rTarget.*pFuncUp)(par) : false;
case KEYEV_Pressed: return pFuncPressed ? (rTarget.*pFuncPressed)(par) : false;
case KEYEV_Moved: return pFuncMoved ? (rTarget.*pFuncMoved)(par) : false;
default: return false;
}
}
@ -344,8 +380,8 @@ protected:
virtual bool CheckCondition() { return true; }
public:
C4KeyCBEx(TargetClass &rTarget, const ParameterType &par, CallbackFunc pFuncDown, CallbackFunc pFuncUp=NULL, CallbackFunc pFuncPressed=NULL)
: rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed), par(par) {}
C4KeyCBEx(TargetClass &rTarget, const ParameterType &par, CallbackFunc pFuncDown, CallbackFunc pFuncUp=NULL, CallbackFunc pFuncPressed=NULL, CallbackFunc pFuncMoved=NULL)
: rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed), pFuncMoved(pFuncMoved), par(par) {}
};
template <class TargetClass, class ParameterType> class C4KeyCBExPassKey : public C4KeyboardCallbackInterface
@ -355,7 +391,7 @@ public:
protected:
TargetClass &rTarget;
CallbackFunc pFuncDown, pFuncUp, pFuncPressed;
CallbackFunc pFuncDown, pFuncUp, pFuncPressed, pFuncMoved;
ParameterType par;
protected:
@ -367,6 +403,7 @@ protected:
case KEYEV_Down: return pFuncDown ? (rTarget.*pFuncDown)(key, par) : false;
case KEYEV_Up: return pFuncUp ? (rTarget.*pFuncUp)(key, par) : false;
case KEYEV_Pressed: return pFuncPressed ? (rTarget.*pFuncPressed)(key, par) : false;
case KEYEV_Moved: return pFuncMoved ? (rTarget.*pFuncMoved)(key, par) : false;
default: return false;
}
}
@ -374,8 +411,8 @@ protected:
virtual bool CheckCondition() { return true; }
public:
C4KeyCBExPassKey(TargetClass &rTarget, const ParameterType &par, CallbackFunc pFuncDown, CallbackFunc pFuncUp=NULL, CallbackFunc pFuncPressed=NULL)
: rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed), par(par) {}
C4KeyCBExPassKey(TargetClass &rTarget, const ParameterType &par, CallbackFunc pFuncDown, CallbackFunc pFuncUp=NULL, CallbackFunc pFuncPressed=NULL, CallbackFunc pFuncMoved=NULL)
: rTarget(rTarget), pFuncDown(pFuncDown), pFuncUp(pFuncUp), pFuncPressed(pFuncPressed), pFuncMoved(pFuncMoved), par(par) {}
};
// one mapped keyboard entry
@ -424,7 +461,7 @@ public:
const StdStrBuf &GetName() const { return Name; }
C4KeyScope GetScope() const { return Scope; }
unsigned int GetPriority() const { return uiPriority; }
bool IsCodeMatched(const C4KeyCodeEx &key) const { const CodeList &codes = GetCodes(); return (std::find(codes.begin(),codes.end(),key) != codes.end()); }
bool IsCodeMatched(const C4KeyCodeEx &key) const;
void Update(const C4CustomKey *pByKey); // merge given key into this
bool Execute(C4KeyEventType eEv, C4KeyCodeEx key);

View File

@ -76,14 +76,14 @@ C4StartupMainDlg::C4StartupMainDlg() : C4StartupDlg(NULL) // create w/o title; i
keys.push_back(C4KeyCodeEx(K_DOWN)); keys.push_back(C4KeyCodeEx(K_RIGHT));
if (Config.Controls.GamepadGuiControl)
{
keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Down))); // right will be done by Dialog already
ControllerKeys::Down(keys); // right will be done by Dialog already
}
pKeyDown = new C4KeyBinding(keys, "StartupMainCtrlNext", KEYSCOPE_Gui,
new C4GUI::DlgKeyCBEx<C4StartupMainDlg, bool>(*this, false, &C4StartupMainDlg::KeyAdvanceFocus), C4CustomKey::PRIO_CtrlOverride);
keys.clear(); keys.push_back(C4KeyCodeEx(K_UP)); keys.push_back(C4KeyCodeEx(K_LEFT));
if (Config.Controls.GamepadGuiControl)
{
keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_Up))); // left will be done by Dialog already
ControllerKeys::Up(keys); // left will be done by Dialog already
}
pKeyUp = new C4KeyBinding(keys, "StartupMainCtrlPrev", KEYSCOPE_Gui,
new C4GUI::DlgKeyCBEx<C4StartupMainDlg, bool>(*this, true, &C4StartupMainDlg::KeyAdvanceFocus), C4CustomKey::PRIO_CtrlOverride);

View File

@ -212,7 +212,7 @@ bool C4StartupOptionsDlg::KeySelDialog::KeyDown(const C4KeyCodeEx &key)
// --- C4StartupOptionsDlg::ControlConfigListBox::ControlAssignmentLabel
C4StartupOptionsDlg::ControlConfigListBox::ControlAssignmentLabel::ControlAssignmentLabel(class C4PlayerControlAssignment *assignment, class C4PlayerControlAssignmentSet *assignment_set, const C4Rect &bounds)
: C4GUI::Label("", bounds, ALeft, 0xffffffff, NULL, false, false, false), assignment(assignment), assignment_set(assignment_set)
: C4GUI::Label("", bounds, ALeft, 0xffffffff, NULL, false, false, true), assignment(assignment), assignment_set(assignment_set)
{
UpdateAssignmentString();
}
@ -391,7 +391,7 @@ void C4StartupOptionsDlg::ControlConfigListBox::SetUserKey(class C4PlayerControl
// --- C4StartupOptionsDlg::ControlConfigArea
C4StartupOptionsDlg::ControlConfigArea::ControlConfigArea(const C4Rect &rcArea, int32_t iHMargin, int32_t iVMargin, bool fGamepad, C4StartupOptionsDlg *pOptionsDlg)
: C4GUI::Window(), fGamepad(fGamepad), pGamepadOpener(NULL), pOptionsDlg(pOptionsDlg), pGUICtrl(NULL)
: C4GUI::Window(), fGamepad(fGamepad), pOptionsDlg(pOptionsDlg), pGUICtrl(NULL)
{
CStdFont *pUseFontSmall = &(C4Startup::Get()->Graphics.BookSmallFont);
SetBounds(rcArea);
@ -436,7 +436,6 @@ C4StartupOptionsDlg::ControlConfigArea::ControlConfigArea(const C4Rect &rcArea,
C4StartupOptionsDlg::ControlConfigArea::~ControlConfigArea()
{
delete [] ppKeyControlSetBtns;
if (pGamepadOpener) delete pGamepadOpener;
}
void C4StartupOptionsDlg::ControlConfigArea::OnCtrlSetBtn(C4GUI::Control *btn)

View File

@ -227,7 +227,6 @@ private:
int32_t iSelectedCtrlSet; // keyboard or gamepad set that is currently being configured
class C4GUI::IconButton ** ppKeyControlSetBtns; // buttons to select configured control set - array in length of iMaxControlSets
class KeySelButton * KeyControlBtns[C4MaxKey]; // buttons to configure individual kbd set buttons
C4GamePadOpener *pGamepadOpener; // opened gamepad for configuration
C4StartupOptionsDlg *pOptionsDlg;
ControlConfigListBox *control_list;
class C4GUI::CheckBox *pGUICtrl;

View File

@ -527,7 +527,7 @@ C4StartupPlrSelDlg::C4StartupPlrSelDlg() : C4StartupDlg("W"), eMode(PSDM_Player)
keys.push_back(C4KeyCodeEx(K_ESCAPE));
if (Config.Controls.GamepadGuiControl)
{
keys.push_back(C4KeyCodeEx(KEY_Gamepad(0, KEY_JOY_AnyHighButton)));
ControllerKeys::Cancel(keys);
}
pKeyBack = new C4KeyBinding(keys, "StartupPlrSelBack", KEYSCOPE_Gui,
new C4GUI::DlgKeyCB<C4StartupPlrSelDlg>(*this, &C4StartupPlrSelDlg::KeyBack), C4CustomKey::PRIO_CtrlOverride);

View File

@ -162,12 +162,15 @@ void C4AbstractApp::HandleSDLEvent(SDL_Event& e)
SDL_GetMouseState(&x, &y);
C4GUI::MouseMove(C4MC_Button_Wheel, x, y, flags, NULL);
break;
case SDL_JOYAXISMOTION:
case SDL_JOYHATMOTION:
case SDL_JOYBALLMOTION:
case SDL_JOYBUTTONDOWN:
case SDL_JOYBUTTONUP:
Application.pGamePadControl->FeedEvent(e);
case SDL_CONTROLLERAXISMOTION:
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
Application.pGamePadControl->FeedEvent(e, C4GamePadControl::FEED_BUTTONS);
break;
case SDL_JOYDEVICEADDED:
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
Application.pGamePadControl->CheckGamePad(e);
break;
}
}

View File

@ -25,178 +25,26 @@
#include <C4Log.h>
#include <C4Game.h>
// regardless of WIN32 or SDL
void C4GamePadControl::DoAxisInput()
{
// Send axis strength changes
Execute(true);
}
#ifdef USE_WIN32_WINDOWS
C4GamePadControl *C4GamePadControl::pInstance = NULL;
C4GamePadControl::C4GamePadControl()
{
for (int i=0; i<CStdGamepad_MaxGamePad; ++i)
{
Gamepads[i].pGamepad = NULL;
Gamepads[i].iRefCount = 0;
}
iNumGamepads = 0;
// singleton
if (!pInstance) pInstance = this;
}
C4GamePadControl::~C4GamePadControl()
{
if (pInstance == this) pInstance = NULL;
Clear();
}
void C4GamePadControl::Clear()
{
for (int i=0; i<CStdGamepad_MaxGamePad; ++i)
while (Gamepads[i].iRefCount) CloseGamepad(i);
}
void C4GamePadControl::OpenGamepad(int id)
{
if (!Inside(id, 0, CStdGamepad_MaxGamePad-1)) return;
// add gamepad ref
if (!(Gamepads[id].iRefCount++))
{
// this is the first gamepad opening: Init it
Pad &rPad = Gamepads[id];
rPad.pGamepad = new CStdGamePad(id);
rPad.Buttons= 0;
for (int i=0; i< CStdGamepad_MaxAxis; ++i)
{
rPad.AxisPosis[i] = CStdGamePad::Mid;
rPad.AxisStrengths[i] = 0;
}
rPad.pGamepad->SetCalibration(&(Config.Gamepads[id].AxisMin[0]), &(Config.Gamepads[id].AxisMax[0]), &(Config.Gamepads[id].AxisCalibrated[0]));
++iNumGamepads;
}
}
void C4GamePadControl::CloseGamepad(int id)
{
if (!Inside(id, 0, CStdGamepad_MaxGamePad-1)) return;
// del gamepad ref
if (!--Gamepads[id].iRefCount)
{
Gamepads[id].pGamepad->GetCalibration(&(Config.Gamepads[id].AxisMin[0]), &(Config.Gamepads[id].AxisMax[0]), &(Config.Gamepads[id].AxisCalibrated[0]));
delete Gamepads[id].pGamepad; Gamepads[id].pGamepad = NULL;
--iNumGamepads;
}
}
int C4GamePadControl::GetGamePadCount()
{
JOYINFOEX joy;
ZeroMem(&joy, sizeof(JOYINFOEX)); joy.dwSize = sizeof(JOYINFOEX); joy.dwFlags = JOY_RETURNALL;
int iCnt=0;
while (iCnt<CStdGamepad_MaxGamePad && ::joyGetPosEx(iCnt, &joy) == JOYERR_NOERROR) ++iCnt;
return iCnt;
}
const int MaxGamePadButton=10;
void C4GamePadControl::Execute(bool send_axis_strength_changes)
{
// Get gamepad inputs
int iNum = iNumGamepads;
for (int idGamepad=0; iNum && idGamepad<CStdGamepad_MaxGamePad; ++idGamepad)
{
Pad &rPad = Gamepads[idGamepad];
if (!rPad.iRefCount) continue;
--iNum;
if (!rPad.pGamepad->Update()) continue;
for (int iAxis = 0; iAxis < CStdGamepad_MaxAxis; ++iAxis)
{
int32_t iStrength = 100;
CStdGamePad::AxisPos eAxisPos = rPad.pGamepad->GetAxisPos(iAxis, &iStrength), ePrevAxisPos = rPad.AxisPosis[iAxis];
int32_t iPrevStrength = rPad.AxisStrengths[iAxis];
// Evaluate changes and pass single controls
// this is a generic Gamepad-control: Create events
if (eAxisPos != ePrevAxisPos || (send_axis_strength_changes && Abs(iPrevStrength-iStrength) > AxisStrengthChangeThreshold))
{
rPad.AxisPosis[iAxis] = eAxisPos;
rPad.AxisStrengths[iAxis] = iStrength;
if (ePrevAxisPos != CStdGamePad::Mid && eAxisPos != ePrevAxisPos)
Game.DoKeyboardInput(KEY_Gamepad(idGamepad, KEY_JOY_Axis(iAxis, (ePrevAxisPos==CStdGamePad::High))), KEYEV_Up, false, false, false, false);
// it's tempting to send fRepeated here for eAxisPos == ePrevAxisPos, but it would cause the key to be ignored for sync controls
// might improve the check in sync controls so they accept repeated keys if strength is updated?
if (eAxisPos != CStdGamePad::Mid)
Game.DoKeyboardInput(KEY_Gamepad(idGamepad, KEY_JOY_Axis(iAxis, (eAxisPos==CStdGamePad::High))), KEYEV_Down, false, false, false, false, NULL, false, iStrength);
}
}
uint32_t Buttons = rPad.pGamepad->GetButtons();
uint32_t PrevButtons = rPad.Buttons;
if (Buttons != PrevButtons)
{
rPad.Buttons = Buttons;
for (int iButton = 0; iButton < MaxGamePadButton; ++iButton)
if ((Buttons & (1 << iButton)) != (PrevButtons & (1 << iButton)))
{
bool fRelease = ((Buttons & (1 << iButton)) == 0);
Game.DoKeyboardInput(KEY_Gamepad(idGamepad, KEY_JOY_Button(iButton)), fRelease ? KEYEV_Up : KEYEV_Down, false, false, false, false);
}
}
}
}
bool C4GamePadControl::AnyButtonDown()
{
return false;
}
C4GamePadOpener::C4GamePadOpener(int iGamepad)
{
assert(C4GamePadControl::pInstance);
this->iGamePad = iGamepad;
C4GamePadControl::pInstance->OpenGamepad(iGamePad);
}
C4GamePadOpener::~C4GamePadOpener()
{
if (C4GamePadControl::pInstance)
C4GamePadControl::pInstance->CloseGamepad(iGamePad);
}
void C4GamePadOpener::SetGamePad(int iNewGamePad)
{
if (iNewGamePad == iGamePad) return;
assert(C4GamePadControl::pInstance);
C4GamePadControl::pInstance->CloseGamepad(iGamePad);
C4GamePadControl::pInstance->OpenGamepad(iGamePad = iNewGamePad);
}
#elif defined(HAVE_SDL) && !defined(USE_CONSOLE)
#if defined(HAVE_SDL) && !defined(USE_CONSOLE)
#include <SDL.h>
bool C4GamePadControl::AnyButtonDown()
{
return false;
}
C4GamePadControl::C4GamePadControl()
{
// FIXME: Port to SDL_INIT_GAMECONTROLLER
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_EVENTS) != 0)
LogF("SDL_InitSubSystem(SDL_INIT_JOYSTICK): %s", SDL_GetError());
SDL_JoystickEventState(SDL_ENABLE);
if (!SDL_NumJoysticks()) Log("No Gamepad found");
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC | SDL_INIT_EVENTS) != 0)
LogF("SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER): %s", SDL_GetError());
SDL_GameControllerEventState(SDL_ENABLE);
if (!GetGamePadCount()) Log("No Gamepad found");
}
C4GamePadControl::~C4GamePadControl()
{
SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_EVENTS);
// All gamepads have to be released before quitting SDL.
Gamepads.clear();
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_EVENTS);
}
void C4GamePadControl::Execute(bool)
void C4GamePadControl::Execute()
{
#ifndef USE_SDL_MAINLOOP
SDL_Event event;
@ -204,12 +52,15 @@ void C4GamePadControl::Execute(bool)
{
switch (event.type)
{
case SDL_JOYAXISMOTION:
case SDL_JOYBALLMOTION:
case SDL_JOYHATMOTION:
case SDL_JOYBUTTONDOWN:
case SDL_JOYBUTTONUP:
FeedEvent(event);
case SDL_CONTROLLERAXISMOTION:
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
FeedEvent(event, FEED_BUTTONS);
break;
case SDL_JOYDEVICEADDED:
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
CheckGamePad(event);
break;
}
}
@ -218,143 +69,185 @@ void C4GamePadControl::Execute(bool)
namespace
{
const int deadZone = 13337;
const int deadZone = 16000;
int amplify(int i)
// Axis strength uses the full signed 16 bit integer range. As we're
// splitting axes in left/right and up/down, it's preferable to have
// symmetrical ranges [0, 2^15 - 1] in both directions.
inline int32_t abs_strength(int32_t strength)
{
if (i < 0)
return -(deadZone + 1);
if (i > 0)
return deadZone + 1;
return 0;
return strength >= 0 ? strength : -(strength + 1);
}
}
void C4GamePadControl::FeedEvent(SDL_Event& event)
void C4GamePadControl::FeedEvent(const SDL_Event& event, int feed)
{
switch (event.type)
{
case SDL_JOYHATMOTION:
case SDL_CONTROLLERAXISMOTION:
{
SDL_Event fakeX;
fakeX.jaxis.type = SDL_JOYAXISMOTION;
fakeX.jaxis.which = event.jhat.which;
fakeX.jaxis.axis = event.jhat.hat * 2 + 6; /* *magic*number* */
fakeX.jaxis.value = 0;
SDL_Event fakeY = fakeX;
fakeY.jaxis.axis += 1;
switch (event.jhat.value)
{
case SDL_HAT_LEFTUP: fakeX.jaxis.value = amplify(-1); fakeY.jaxis.value = amplify(-1); break;
case SDL_HAT_LEFT: fakeX.jaxis.value = amplify(-1); break;
case SDL_HAT_LEFTDOWN: fakeX.jaxis.value = amplify(-1); fakeY.jaxis.value = amplify(+1); break;
case SDL_HAT_UP: fakeY.jaxis.value = amplify(-1); break;
case SDL_HAT_DOWN: fakeY.jaxis.value = amplify(+1); break;
case SDL_HAT_RIGHTUP: fakeX.jaxis.value = amplify(+1); fakeY.jaxis.value = amplify(-1); break;
case SDL_HAT_RIGHT: fakeX.jaxis.value = amplify(+1); break;
case SDL_HAT_RIGHTDOWN: fakeX.jaxis.value = amplify(+1); fakeY.jaxis.value = amplify(+1); break;
}
FeedEvent(fakeX);
FeedEvent(fakeY);
return;
}
case SDL_JOYBALLMOTION:
{
SDL_Event fake;
fake.jaxis.type = SDL_JOYAXISMOTION;
fake.jaxis.which = event.jball.which;
fake.jaxis.axis = event.jball.ball * 2 + 12; /* *magic*number* */
fake.jaxis.value = amplify(event.jball.xrel);
FeedEvent(event);
fake.jaxis.axis += 1;
fake.jaxis.value = amplify(event.jball.yrel);
FeedEvent(event);
return;
}
case SDL_JOYAXISMOTION:
{
C4KeyCode minCode = KEY_Gamepad(event.jaxis.which, KEY_JOY_Axis(event.jaxis.axis, false));
C4KeyCode maxCode = KEY_Gamepad(event.jaxis.which, KEY_JOY_Axis(event.jaxis.axis, true));
C4KeyCode minCode = KEY_Gamepad(KEY_CONTROLLER_Axis(event.caxis.axis, false));
C4KeyCode maxCode = KEY_Gamepad(KEY_CONTROLLER_Axis(event.caxis.axis, true));
int32_t value = abs_strength(event.caxis.value);
uint8_t which = event.caxis.which;
C4KeyCode keyCode = event.caxis.value >= 0 ? maxCode : minCode;
// FIXME: This assumes that the axis really rests around (0, 0) if it is not used, which is not always true.
if (event.jaxis.value < -deadZone)
auto doInput = [&](C4KeyEventType event, int32_t strength)
{
if (PressedAxis.count(minCode) == 0)
Game.DoKeyboardInput(
C4KeyCodeEx(KEY_Gamepad(keyCode), KEYS_None, false, which),
event, NULL, false, strength);
};
if (feed & FEED_BUTTONS)
{
// Also emulate button presses.
if (PressedAxis.count(keyCode) && value <= deadZone)
{
Game.DoKeyboardInput(
KEY_Gamepad(event.jaxis.which, minCode),
KEYEV_Down, false, false, false, false);
PressedAxis.insert(minCode);
}
}
else
{
if (PressedAxis.count(minCode) != 0)
{
Game.DoKeyboardInput(
KEY_Gamepad(event.jaxis.which, minCode),
KEYEV_Up, false, false, false, false);
PressedAxis.erase(minCode);
}
}
if (event.jaxis.value > +deadZone)
{
if (PressedAxis.count(maxCode) == 0)
{
Game.DoKeyboardInput(
KEY_Gamepad(event.jaxis.which, maxCode),
KEYEV_Down, false, false, false, false);
PressedAxis.insert(maxCode);
}
}
else
{
if (PressedAxis.count(maxCode) != 0)
{
Game.DoKeyboardInput(
KEY_Gamepad(event.jaxis.which, maxCode),
KEYEV_Up, false, false, false, false);
PressedAxis.erase(maxCode);
PressedAxis.erase(keyCode);
doInput(KEYEV_Up, -1);
}
else if (!PressedAxis.count(keyCode) && value > deadZone)
{
PressedAxis.insert(keyCode);
doInput(KEYEV_Down, -1);
}
}
if (feed & FEED_MOVED)
doInput(KEYEV_Moved, value);
AxisEvents[keyCode] = event;
break;
}
case SDL_JOYBUTTONDOWN:
Game.DoKeyboardInput(
KEY_Gamepad(event.jbutton.which, KEY_JOY_Button(event.jbutton.button)),
KEYEV_Down, false, false, false, false);
case SDL_CONTROLLERBUTTONDOWN:
if (feed & FEED_BUTTONS)
Game.DoKeyboardInput(
C4KeyCodeEx(KEY_Gamepad(KEY_CONTROLLER_Button(event.cbutton.button)), KEYS_None, false, event.cbutton.which),
KEYEV_Down);
break;
case SDL_JOYBUTTONUP:
Game.DoKeyboardInput(
KEY_Gamepad(event.jbutton.which, KEY_JOY_Button(event.jbutton.button)),
KEYEV_Up, false, false, false, false);
case SDL_CONTROLLERBUTTONUP:
if (feed & FEED_BUTTONS)
Game.DoKeyboardInput(
C4KeyCodeEx(KEY_Gamepad(KEY_CONTROLLER_Button(event.cbutton.button)), KEYS_None, false, event.cbutton.which),
KEYEV_Up);
break;
}
}
void C4GamePadControl::CheckGamePad(const SDL_Event& e)
{
switch (e.type)
{
case SDL_JOYDEVICEADDED:
// Report that an unsupported joystick device has been detected, to help with controller issues.
if (!SDL_IsGameController(e.jdevice.which))
LogF("Gamepad %s isn't supported.", SDL_JoystickNameForIndex(e.jdevice.which));
break;
case SDL_CONTROLLERDEVICEADDED:
{
auto device = std::make_shared<C4GamePadOpener>(e.cdevice.which);
Gamepads[device->GetID()] = device;
LogF("Gamepad #%d connected: %s", device->GetID(), SDL_JoystickNameForIndex(e.cdevice.which));
break;
}
case SDL_CONTROLLERDEVICEREMOVED:
LogF("Gamepad #%d disconnected.", e.cdevice.which);
Gamepads.erase(e.cdevice.which);
break;
}
}
void C4GamePadControl::DoAxisInput()
{
for (auto const &e : AxisEvents)
{
FeedEvent(e.second, FEED_MOVED);
}
AxisEvents.clear();
}
int C4GamePadControl::GetGamePadCount()
{
return(SDL_NumJoysticks());
// Not all Joysticks are game controllers.
int count = 0;
for (int i = 0; i < SDL_NumJoysticks(); i++)
if (SDL_IsGameController(i))
count++;
return count;
}
std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetGamePad(int gamepad)
{
if (gamepad >= 0)
for (const auto& p : Gamepads)
if (gamepad-- == 0)
return p.second;
return nullptr;
}
std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetGamePadByID(int32_t id)
{
auto it = Gamepads.find(id);
if (it != Gamepads.end())
return it->second;
return nullptr;
}
std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetAvailableGamePad()
{
for (const auto& p : Gamepads)
if (p.second->GetPlayer() < 0)
return p.second;
return nullptr;
}
C4GamePadOpener::C4GamePadOpener(int iGamepad)
{
Joy = SDL_JoystickOpen(iGamepad);
if (!Joy) LogF("SDL: %s", SDL_GetError());
int n = iGamepad;
for (int i = 0; i < SDL_NumJoysticks(); i++)
if (SDL_IsGameController(i) && n-- == 0)
{
controller = SDL_GameControllerOpen(i);
if (!controller) LogF("SDL: %s", SDL_GetError());
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(controller);
haptic = SDL_HapticOpenFromJoystick(joystick);
if (haptic && SDL_HapticRumbleSupported(haptic))
SDL_HapticRumbleInit(haptic);
else
LogF("Gamepad #%d %s does not support rumbling.", SDL_JoystickInstanceID(joystick), SDL_JoystickName(joystick));
break;
}
if (!controller) LogF("Gamepad %d not available", iGamepad);
}
C4GamePadOpener::~C4GamePadOpener()
{
if (Joy) SDL_JoystickClose(Joy);
if (haptic) SDL_HapticClose(haptic);
if (controller) SDL_GameControllerClose(controller);
}
void C4GamePadOpener::SetGamePad(int iGamepad)
int32_t C4GamePadOpener::GetID()
{
if (Joy)
SDL_JoystickClose(Joy);
Joy = SDL_JoystickOpen(iGamepad);
if (!Joy)
LogF("SDL: %s", SDL_GetError());
return SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller));
}
bool C4GamePadOpener::IsAttached()
{
return !!SDL_GameControllerGetAttached(controller);
}
void C4GamePadOpener::PlayRumble(float strength, uint32_t length)
{
if (SDL_HapticRumbleSupported(haptic))
SDL_HapticRumblePlay(haptic, strength, length);
}
void C4GamePadOpener::StopRumble()
{
if (SDL_HapticRumbleSupported(haptic))
SDL_HapticRumbleStop(haptic);
}
#else
@ -363,12 +256,18 @@ void C4GamePadOpener::SetGamePad(int iGamepad)
C4GamePadControl::C4GamePadControl() { Log("WARNING: Engine without Gamepad support"); }
C4GamePadControl::~C4GamePadControl() { }
void C4GamePadControl::Execute(bool) { }
void C4GamePadControl::Execute() { }
void C4GamePadControl::DoAxisInput() { }
int C4GamePadControl::GetGamePadCount() { return 0; }
bool C4GamePadControl::AnyButtonDown() { return false; }
std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetGamePad(int gamepad) { return nullptr; }
std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetGamePadByID(int32_t id) { return nullptr; }
std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetAvailableGamePad() { return nullptr; }
C4GamePadOpener::C4GamePadOpener(int iGamepad) { }
C4GamePadOpener::~C4GamePadOpener() {}
void C4GamePadOpener::SetGamePad(int iGamepad) { }
int32_t C4GamePadOpener::GetID() { return -1; }
bool C4GamePadOpener::IsAttached() { return false; }
void C4GamePadOpener::PlayRumble(float strength, uint32_t length) { }
void C4GamePadOpener::StopRumble() { }
#endif //_WIN32
#endif

View File

@ -20,70 +20,71 @@
#ifndef INC_C4GamePadCon
#define INC_C4GamePadCon
#ifdef USE_WIN32_WINDOWS
#include <StdJoystick.h>
#endif
#include <memory>
#ifdef HAVE_SDL
#include <C4KeyboardInput.h>
#include <set>
#include <map>
#include <SDL.h>
#endif
struct _SDL_Joystick;
typedef struct _SDL_Joystick SDL_Joystick;
union SDL_Event;
typedef union SDL_Event SDL_Event;
class C4GamePadOpener;
class C4GamePadControl
{
#ifdef USE_WIN32_WINDOWS
private:
struct Pad
{
CStdGamePad *pGamepad;
int iRefCount;
uint32_t Buttons;
CStdGamePad::AxisPos AxisPosis[CStdGamepad_MaxAxis];
int32_t AxisStrengths[CStdGamepad_MaxAxis];
#ifdef HAVE_SDL
public:
enum {
FEED_BUTTONS = 1,
FEED_MOVED = 2,
};
Pad Gamepads[CStdGamepad_MaxGamePad];
int iNumGamepads;
enum { AxisStrengthChangeThreshold = 2 }; // if axis strength change > this value, a new control is issued
public:
void OpenGamepad(int id); // add gamepad ref
void CloseGamepad(int id); // del gamepad ref
static C4GamePadControl *pInstance; // singleton
#elif defined(HAVE_SDL)
public:
void FeedEvent(SDL_Event& e);
// Called from C4AppSDL
void FeedEvent(const SDL_Event& e, int feed);
void CheckGamePad(const SDL_Event& e);
private:
std::set<C4KeyCode> PressedAxis;
std::set<C4KeyCode> PressedAxis; // for button emulation
std::map<C4KeyCode, SDL_Event> AxisEvents; // for analog movement events
std::map<int32_t, std::shared_ptr<C4GamePadOpener> > Gamepads; // gamepad instance id -> gamepad
#endif
public:
static const int32_t MaxStrength = 32767; // 2^15 - 1
C4GamePadControl();
~C4GamePadControl();
void Clear();
int GetGamePadCount();
void Execute(bool send_axis_strength_changes=false);
void Execute();
void DoAxisInput(); // period axis strength update controls sent on each control frame creation
static bool AnyButtonDown();
std::shared_ptr<C4GamePadOpener> GetGamePad(int gamepad); // Gets the nth gamepad.
std::shared_ptr<C4GamePadOpener> GetGamePadByID(int32_t id); // Gets a gamepad by its instance id.
std::shared_ptr<C4GamePadOpener> GetAvailableGamePad(); // Looks for a gamepad that doesn't have an assigned player.
};
class C4GamePadOpener
{
#ifdef USE_WIN32_WINDOWS
int iGamePad;
int GetGamePadIndex() const { return iGamePad; }
#endif
int32_t player = -1;
public:
C4GamePadOpener(int iGamePad);
~C4GamePadOpener();
void SetGamePad(int iNewGamePad);
// A gamepad can be assigned to a player.
int32_t GetPlayer() const { return player; }
void SetPlayer(int32_t plr) { player = plr; }
int32_t GetID(); // Returns the gamepad's instance id.
bool IsAttached(); // Returns whether the gamepad is currently attached.
// Force feedback: simple rumbling
void PlayRumble(float strength, uint32_t length); // strength: 0-1, length: milliseconds
void StopRumble();
#ifdef HAVE_SDL
SDL_Joystick *Joy;
SDL_GameController *controller;
SDL_Haptic *haptic;
#endif
};

View File

@ -1,135 +0,0 @@
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 1998-2000, Matthes Bender
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2010-2013, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
/* Simple joystick handling with DirectInput 1 */
#include "C4Include.h"
#include <StdJoystick.h>
#include <C4windowswrapper.h>
#include <windowsx.h>
uint32_t POV2Position(DWORD dwPOV, bool fVertical)
{
// POV value is a 360° angle multiplied by 100
double dAxis;
// Centered
if (dwPOV == JOY_POVCENTERED)
dAxis = 0.0;
// Angle: convert to linear value -100 to +100
else
dAxis = (fVertical ? -cos((dwPOV/100) * M_PI / 180.0) : sin((dwPOV/100) * M_PI / 180.0)) * 100.0;
// Gamepad configuration wants unsigned and gets 0 to 200
return (uint32_t) (dAxis + 100.0);
}
CStdGamePad::CStdGamePad(int id) : id(id)
{
ResetCalibration();
}
void CStdGamePad::ResetCalibration()
{
// no calibration yet
for (int i=0; i<CStdGamepad_MaxCalAxis; ++i)
{
fAxisCalibrated[i]=false;
}
}
void CStdGamePad::SetCalibration(uint32_t *pdwAxisMin, uint32_t *pdwAxisMax, bool *pfAxisCalibrated)
{
// params to calibration
for (int i=0; i<CStdGamepad_MaxCalAxis; ++i)
{
dwAxisMin[i] = pdwAxisMin[i];
dwAxisMax[i] = pdwAxisMax[i];
fAxisCalibrated[i] = pfAxisCalibrated[i];
}
}
void CStdGamePad::GetCalibration(uint32_t *pdwAxisMin, uint32_t *pdwAxisMax, bool *pfAxisCalibrated)
{
// calibration to params
for (int i=0; i<CStdGamepad_MaxCalAxis; ++i)
{
pdwAxisMin[i] = dwAxisMin[i];
pdwAxisMax[i] = dwAxisMax[i];
pfAxisCalibrated[i] = fAxisCalibrated[i];
}
}
bool CStdGamePad::Update()
{
joynfo.dwSize=sizeof(joynfo);
joynfo.dwFlags=JOY_RETURNBUTTONS | JOY_RETURNRAWDATA | JOY_RETURNX | JOY_RETURNY | JOY_RETURNZ | JOY_RETURNR | JOY_RETURNU | JOY_RETURNV | JOY_RETURNPOV;
return joyGetPosEx(JOYSTICKID1+id,&joynfo) == JOYERR_NOERROR;
}
uint32_t CStdGamePad::GetButtons()
{
return joynfo.dwButtons;
}
CStdGamePad::AxisPos CStdGamePad::GetAxisPos(int idAxis, int32_t *out_strength)
{
if (out_strength) *out_strength = 0; // default no strength
if (idAxis<0 || idAxis>=CStdGamepad_MaxAxis) return Mid; // wrong axis
// get raw axis data
if (idAxis<CStdGamepad_MaxCalAxis)
{
uint32_t dwPos = (&joynfo.dwXpos)[idAxis];
// evaluate axis calibration
if (fAxisCalibrated[idAxis])
{
// update it
dwAxisMin[idAxis] = std::min(dwAxisMin[idAxis], dwPos);
dwAxisMax[idAxis] = std::max(dwAxisMax[idAxis], dwPos);
// Calculate center
DWORD dwCenter = (dwAxisMin[idAxis] + dwAxisMax[idAxis]) / 2;
// Axis strength
DWORD dwRange = (dwAxisMax[idAxis] - dwCenter);
// Trigger range is 20% off center
DWORD dwThresh = dwRange / 5;
if (dwPos < dwCenter - dwThresh)
{
if (out_strength && dwRange) *out_strength = (dwCenter-dwPos)*100/dwRange;
return Low;
}
if (dwPos > dwCenter + dwThresh)
{
if (out_strength && dwRange) *out_strength = (dwPos-dwCenter)*100/dwRange;
return High;
}
}
else
{
// init it
dwAxisMin[idAxis] = dwAxisMax[idAxis] = dwPos;
fAxisCalibrated[idAxis] = true;
}
}
else
{
// It's a POV head
DWORD dwPos = POV2Position(joynfo.dwPOV, idAxis==PAD_Axis_POVy);
if (out_strength) *out_strength = Abs(int32_t(dwPos) - 100);
if (dwPos > 130) return High; else if (dwPos < 70) return Low;
}
return Mid;
}

View File

@ -1,56 +0,0 @@
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 1998-2000, Matthes Bender
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2013, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
/* Simple joystick handling with DirectInput 1 */
#ifndef INC_StdJoystick
#define INC_StdJoystick
#include <C4windowswrapper.h>
#include <mmsystem.h>
const int32_t PAD_Axis_POVx = 6;
const int32_t PAD_Axis_POVy = 7; // virtual axises of the coolie hat
const int CStdGamepad_MaxGamePad = 15, // maximum number of supported gamepads
CStdGamepad_MaxCalAxis = 6, // maximum number of calibrated axises
CStdGamepad_MaxAxis = 8; // number of axises plus coolie hat axises
class CStdGamePad
{
public:
enum AxisPos { Low, Mid, High, }; // quantized axis positions
private:
int id; // gamepad number
JOYINFOEX joynfo; // WIN32 gamepad info
public:
uint32_t dwAxisMin[CStdGamepad_MaxCalAxis], dwAxisMax[CStdGamepad_MaxCalAxis]; // axis ranges - auto calibrated
bool fAxisCalibrated[CStdGamepad_MaxCalAxis]; // set if an initial value for axis borders has been determined already
CStdGamePad(int id); // ctor
void ResetCalibration(); // resets axis min and max
void SetCalibration(uint32_t *pdwAxisMin, uint32_t *pdwAxisMax, bool *pfAxisCalibrated);
void GetCalibration(uint32_t *pdwAxisMin, uint32_t *pdwAxisMax, bool *pfAxisCalibrated);
bool Update(); // read current gamepad data
uint32_t GetButtons(); // returns bitmask of pressed buttons for last retrieved info
AxisPos GetAxisPos(int idAxis, int32_t *out_strength=NULL); // return axis extension - mid for error or center position
};
#endif

View File

@ -66,7 +66,6 @@ C4Player::C4Player() : C4PlayerInfoCore()
LastControlType = PCID_None;
LastControlID = 0;
pMsgBoardQuery = NULL;
pGamepad = NULL;
NoEliminationCheck = false;
Evaluated = false;
ZoomLimitMinWdt = ZoomLimitMinHgt = ZoomLimitMaxWdt = ZoomLimitMaxHgt = ZoomWdt = ZoomHgt = 0;
@ -86,7 +85,6 @@ C4Player::~C4Player()
delete pMsgBoardQuery;
pMsgBoardQuery = pNext;
}
delete pGamepad; pGamepad = NULL;
ClearControl();
}
@ -212,6 +210,30 @@ void C4Player::Execute()
Menu.TryClose(false, false);
}
// Do we have a gamepad?
if (pGamepad)
{
// Check whether it's still connected.
if (!pGamepad->IsAttached())
{
// Allow the player to plug the gamepad back in. This allows
// battery replacement or plugging the controller back
// in after someone tripped over the wire.
if (!FindGamepad())
{
LogF("%s: No gamepad available.", Name.getData());
::Game.Pause();
}
}
}
// Should we have one? The player may have started the game
// without turning their controller on, only noticing this
// after the game started.
else if (LocalControl && ControlSet && ControlSet->HasGamepad())
{
FindGamepad();
}
// Tick1
UpdateView();
ExecuteControl();
@ -1349,8 +1371,12 @@ void C4Player::ClearControl()
LocalControl = false;
ControlSetName.Clear();
ControlSet=NULL;
if (pGamepad) { delete pGamepad; pGamepad=NULL; }
MouseControl = false;
if (pGamepad)
{
pGamepad->SetPlayer(NO_OWNER);
pGamepad.reset();
}
// no controls issued yet
ControlCount = ActionCount = 0;
LastControlType = PCID_None;
@ -1382,7 +1408,11 @@ void C4Player::InitControl()
// init gamepad
if (ControlSet && ControlSet->HasGamepad())
{
pGamepad = new C4GamePadOpener(ControlSet->GetGamepadIndex());
if (!FindGamepad())
{
LogF("No gamepad available for %s, please plug one in!", Name.getData());
::Game.Pause();
}
}
// Mouse
if (ControlSet && ControlSet->HasMouse() && PrefMouse)
@ -1396,6 +1426,18 @@ void C4Player::InitControl()
Control.RegisterKeyset(Number, ControlSet);
}
bool C4Player::FindGamepad()
{
auto newPad = Application.pGamePadControl->GetAvailableGamePad();
if (!newPad) return false;
newPad->SetPlayer(ID);
// Release the old gamepad.
if (pGamepad) pGamepad->SetPlayer(NO_OWNER);
pGamepad = newPad;
LogF("%s: Using gamepad #%d.", Name.getData(), pGamepad->GetID());
return true;
}
int igOffX, igOffY;
int VisibilityCheck(int iVis, int sx, int sy, int cx, int cy)

View File

@ -28,6 +28,7 @@
#include "C4PlayerControl.h"
#include <C4Value.h>
#include <set>
#include <memory>
const int32_t C4PVM_Cursor = 0,
C4PVM_Target = 1,
@ -130,7 +131,7 @@ public:
C4PlayerControl Control;
C4ObjectPtr Cursor, ViewCursor;
int32_t CursorFlash;
class C4GamePadOpener *pGamepad;
std::shared_ptr<class C4GamePadOpener> pGamepad;
// Message
int32_t MessageStatus;
char MessageBuf[256+1];
@ -265,6 +266,9 @@ private:
bool AdjustZoomParameter(int32_t *range_par, int32_t new_val, bool no_increase, bool no_decrease);
bool AdjustZoomParameter(C4Fixed *zoom_par, C4Fixed new_val, bool no_increase, bool no_decrease);
// Finds a new gamepad to use, returning true on success.
bool FindGamepad();
public:
// custom scenario achievements
bool GainScenarioAchievement(const char *achievement_id, int32_t value, const char *scen_name_override=NULL);