diff --git a/doomsday/engine/api/dd_share.h b/doomsday/engine/api/dd_share.h index 7bbad5eae0..224ea0b737 100644 --- a/doomsday/engine/api/dd_share.h +++ b/doomsday/engine/api/dd_share.h @@ -1712,8 +1712,9 @@ enum { /// Control type. typedef enum controltype_e { - CTLT_NUMERIC, - CTLT_IMPULSE + CTLT_NUMERIC, ///< Control with a numeric value determined by current device state. + CTLT_NUMERIC_TRIGGERED, ///< Numeric, but accepts triggered states as well. + CTLT_IMPULSE ///< Always accepts triggered states. } controltype_t; /** diff --git a/doomsday/engine/portable/include/b_device.h b/doomsday/engine/portable/include/b_device.h index add04781df..709bacddbd 100644 --- a/doomsday/engine/portable/include/b_device.h +++ b/doomsday/engine/portable/include/b_device.h @@ -65,7 +65,8 @@ void B_DestroyDeviceBinding(dbinding_t* cb); void B_DeviceBindingToString(const dbinding_t* b, ddstring_t* str); void B_EvaluateDeviceBindingList(int localNum, dbinding_t* listRoot, float* pos, float* relativeOffset, - struct bcontext_s* controlClass); + struct bcontext_s* controlClass, + boolean allowTriggered); #endif // __DOOMSDAY_BIND_DEVICE_H__ diff --git a/doomsday/engine/portable/include/dd_input.h b/doomsday/engine/portable/include/dd_input.h index f6958ae415..fd8f8ab07d 100644 --- a/doomsday/engine/portable/include/dd_input.h +++ b/doomsday/engine/portable/include/dd_input.h @@ -122,6 +122,8 @@ typedef struct inputdevassoc_s { #define IDAF_EXPIRED 0x1 // The state has expired. The device is considered to remain // in default state until the flag gets cleared (which happens when // the real device state returns to its default). +#define IDAF_TRIGGERED 0x2 // The state has been triggered. This is cleared when someone checks + // the device state. (Only for toggles.) // Input device axis types. enum diff --git a/doomsday/engine/portable/include/p_control.h b/doomsday/engine/portable/include/p_control.h index 043dce619e..b363cbd9e2 100644 --- a/doomsday/engine/portable/include/p_control.h +++ b/doomsday/engine/portable/include/p_control.h @@ -38,6 +38,7 @@ int P_GetImpulseControlState(int playerNum, int control); typedef struct playercontrol_s { int id; controltype_t type; + boolean isTriggerable; ///< Accepts triggered states in addition to active ones. char* name; char* bindContextName; } playercontrol_t; diff --git a/doomsday/engine/portable/src/b_command.c b/doomsday/engine/portable/src/b_command.c index 899f18c8a4..c701075c6f 100644 --- a/doomsday/engine/portable/src/b_command.c +++ b/doomsday/engine/portable/src/b_command.c @@ -429,6 +429,9 @@ boolean B_TryCommandBinding(evbinding_t* eb, ddevent_t* event, struct bcontext_s if(eventClass && dev->keys[eb->id].assoc.bContext != eventClass) return false; // Shadowed by a more important active class. + // We're checking it, so clear the triggered flag. + dev->keys[eb->id].assoc.flags &= ~IDAF_TRIGGERED; + // Is the state as required? switch(eb->state) { diff --git a/doomsday/engine/portable/src/b_context.c b/doomsday/engine/portable/src/b_context.c index db2f5c6ffa..c523ea42b9 100644 --- a/doomsday/engine/portable/src/b_context.c +++ b/doomsday/engine/portable/src/b_context.c @@ -237,6 +237,7 @@ void B_UpdateDeviceStateAssociations(void) { // No longer valid. dev->keys[j].assoc.flags |= IDAF_EXPIRED; + dev->keys[j].assoc.flags &= ~IDAF_TRIGGERED; // Not any more. DD_ClearKeyRepeaterForKey(j); } } diff --git a/doomsday/engine/portable/src/b_device.c b/doomsday/engine/portable/src/b_device.c index 509d5cc9aa..208c37b6ea 100644 --- a/doomsday/engine/portable/src/b_device.c +++ b/doomsday/engine/portable/src/b_device.c @@ -278,7 +278,8 @@ void B_DestroyDeviceBinding(dbinding_t* cb) } } -void B_EvaluateDeviceBindingList(int localNum, dbinding_t* listRoot, float* pos, float* relativeOffset, bcontext_t* controlClass) +void B_EvaluateDeviceBindingList(int localNum, dbinding_t* listRoot, float* pos, float* relativeOffset, + bcontext_t* controlClass, boolean allowTriggered) { dbinding_t* cb; int i; @@ -332,8 +333,12 @@ void B_EvaluateDeviceBindingList(int localNum, dbinding_t* listRoot, float* pos, if(dev->keys[cb->id].assoc.flags & IDAF_EXPIRED) break; - devicePos = (dev->keys[cb->id].isDown? 1.0f : 0.0f); + devicePos = (dev->keys[cb->id].isDown || + (allowTriggered && (dev->keys[cb->id].assoc.flags & IDAF_TRIGGERED))? 1.0f : 0.0f); deviceTime = dev->keys[cb->id].time; + + // We've checked it, so clear the flag. + dev->keys[cb->id].assoc.flags &= ~IDAF_TRIGGERED; break; case CBD_AXIS: @@ -422,7 +427,7 @@ void B_EvaluateDeviceBindingList(int localNum, dbinding_t* listRoot, float* pos, *pos = 0; } - // Clamp appropriately. + // Clamp to the normalized range. *pos = MINMAX_OF(-1.0f, *pos, 1.0f); } diff --git a/doomsday/engine/portable/src/b_util.c b/doomsday/engine/portable/src/b_util.c index d1c0ac0b4a..89e8e719eb 100644 --- a/doomsday/engine/portable/src/b_util.c +++ b/doomsday/engine/portable/src/b_util.c @@ -466,7 +466,7 @@ boolean B_CheckCondition(statecondition_t* cond, int localNum, bcontext_t* conte // Evaluate the current state of the modifier (in this context). float pos = 0, relative = 0; dbinding_t* binds = &B_GetControlBinding(context, cond->id)->deviceBinds[localNum]; - B_EvaluateDeviceBindingList(localNum, binds, &pos, &relative, context); + B_EvaluateDeviceBindingList(localNum, binds, &pos, &relative, context, false /*no triggered*/); if((cond->state == EBTOG_DOWN && fabs(pos) > .5) || (cond->state == EBTOG_UP && fabs(pos) < .5)) return fulfilled; diff --git a/doomsday/engine/portable/src/dd_input.c b/doomsday/engine/portable/src/dd_input.c index 8164c2b277..a8a73bf925 100644 --- a/doomsday/engine/portable/src/dd_input.c +++ b/doomsday/engine/portable/src/dd_input.c @@ -625,9 +625,14 @@ void I_TrackInput(ddevent_t *ev, timespan_t ticLength) key->time = Sys_GetRealTime(); } - // We can clear the expiration when the key is released. - if(!key->isDown) + if(key->isDown) { + // This will get cleared after the state is checked by someone. + key->assoc.flags |= IDAF_TRIGGERED; + } + else + { + // We can clear the expiration when the key is released. key->assoc.flags &= ~IDAF_EXPIRED; } } @@ -662,6 +667,7 @@ void I_ClearDeviceContextAssociations(void) { dev->keys[j].assoc.prevBContext = dev->keys[j].assoc.bContext; dev->keys[j].assoc.bContext = NULL; + dev->keys[j].assoc.flags &= ~IDAF_TRIGGERED; } // Axes. for(j = 0; j < dev->numAxes; ++j) @@ -1491,6 +1497,7 @@ void Rend_RenderKeyStateVisual(inputdev_t* device, uint keyID, const Point2Raw* const float upColor[] = { .3f, .3f, .3f, .6f }; const float downColor[] = { .3f, .3f, 1, .6f }; const float expiredMarkColor[] = { 1, 0, 0, 1 }; + const float triggeredMarkColor[] = { 1, 0, 1, 1 }; const char* keyLabel = NULL; const inputdevkey_t* key; char keyLabelBuf[2]; @@ -1574,6 +1581,19 @@ void Rend_RenderKeyStateVisual(inputdev_t* device, uint keyID, const Point2Raw* glEnd(); } + // Mark triggered? + if(key->assoc.flags & IDAF_TRIGGERED) + { + const int markSize = .5f + MIN_OF(textGeom.size.width, textGeom.size.height) / 3.f; + + glColor3fv(triggeredMarkColor); + glBegin(GL_TRIANGLES); + glVertex2i(0, 0); + glVertex2i(markSize, 0); + glVertex2i(0, markSize); + glEnd(); + } + endDrawStateForVisual(&origin); Str_Free(&label); diff --git a/doomsday/engine/portable/src/p_control.c b/doomsday/engine/portable/src/p_control.c index d85152d5d4..80810335f2 100644 --- a/doomsday/engine/portable/src/p_control.c +++ b/doomsday/engine/portable/src/p_control.c @@ -170,6 +170,7 @@ void P_NewPlayerControl(int id, controltype_t type, const char *name, const char pc->id = id; pc->type = type; pc->name = strdup(name); + pc->isTriggerable = (type == CTLT_NUMERIC_TRIGGERED || type == CTLT_IMPULSE); pc->bindContextName = strdup(bindContext); // Also allocate the impulse and double-click counters. controlCounts[pc - playerControls] = M_Calloc(sizeof(controlcounter_t)); @@ -333,15 +334,11 @@ void P_GetControlState(int playerNum, int control, float* pos, float* relativeOf struct bcontext_s* bc = 0; struct dbinding_s* binds = 0; int localNum; + playercontrol_t* pc = P_PlayerControlById(control); -#if _DEBUG // Check that this is really a numeric control. - { - playercontrol_t* pc = P_PlayerControlById(control); - assert(pc); - assert(pc->type == CTLT_NUMERIC); - } -#endif + assert(pc); + assert(pc->type == CTLT_NUMERIC || pc->type == CTLT_NUMERIC_TRIGGERED); // Ignore NULLs. if(!pos) pos = &tmp; @@ -352,7 +349,7 @@ void P_GetControlState(int playerNum, int control, float* pos, float* relativeOf // P_ConsoleToLocal() is called here. localNum = P_ConsoleToLocal(playerNum); binds = B_GetControlDeviceBindings(localNum, control, &bc); - B_EvaluateDeviceBindingList(localNum, binds, pos, relativeOffset, bc); + B_EvaluateDeviceBindingList(localNum, binds, pos, relativeOffset, bc, pc->isTriggerable); // Mark for double-clicks. P_MaintainControlDoubleClicks(playerNum, P_PlayerControlIndexForId(control), *pos); diff --git a/doomsday/plugins/common/src/g_controls.c b/doomsday/plugins/common/src/g_controls.c index 485ae32d4d..decc353999 100644 --- a/doomsday/plugins/common/src/g_controls.c +++ b/doomsday/plugins/common/src/g_controls.c @@ -185,7 +185,7 @@ void G_ControlRegister(void) P_NewPlayerControl(CTL_LOOK, CTLT_NUMERIC, "look", "game"); P_NewPlayerControl(CTL_SPEED, CTLT_NUMERIC, "speed", "game"); P_NewPlayerControl(CTL_MODIFIER_1, CTLT_NUMERIC, "strafe", "game"); - P_NewPlayerControl(CTL_ATTACK, CTLT_NUMERIC, "attack", "game"); + P_NewPlayerControl(CTL_ATTACK, CTLT_NUMERIC_TRIGGERED, "attack", "game"); P_NewPlayerControl(CTL_USE, CTLT_IMPULSE, "use", "game"); P_NewPlayerControl(CTL_LOOK_CENTER, CTLT_IMPULSE, "lookcenter", "game"); P_NewPlayerControl(CTL_FALL_DOWN, CTLT_IMPULSE, "falldown", "game");