diff --git a/doomsday/client/include/ui/bindcontext.h b/doomsday/client/include/ui/bindcontext.h index 4c85e8c826..3c0d933c22 100644 --- a/doomsday/client/include/ui/bindcontext.h +++ b/doomsday/client/include/ui/bindcontext.h @@ -30,7 +30,7 @@ typedef int (*FallbackResponderFunc)(event_t *); typedef int (*DDFallbackResponderFunc)(ddevent_t const *); // todo ends -struct PlayerImpulse; +class PlayerImpulse; /** * Contextualized grouping of input (and windowing system) event bindings. diff --git a/doomsday/client/include/ui/inputsystem.h b/doomsday/client/include/ui/inputsystem.h index 19b64f9acc..ab9839da92 100644 --- a/doomsday/client/include/ui/inputsystem.h +++ b/doomsday/client/include/ui/inputsystem.h @@ -170,7 +170,7 @@ class InputSystem : public de::System DENG2_ERROR(MissingContextError); /** - * Try to make a new command binding. + * Try to make a new (console) command binding. * * @param eventDesc Textual descriptor for the event, with the relevant * context for the would-be binding encoded. @@ -214,14 +214,15 @@ class InputSystem : public de::System void clearAllContexts(); /** - * Returns the total number of binding contexts in the system. + * Returns @c true if the symbolic @a name references a known context. */ - int contextCount() const; + bool hasContext(de::String const &name) const; /** - * Returns @c true if the symbolic @a name references a known context. + * Creates a new binding context. The new context has the highest priority + * of all existing contexts, and is inactive. */ - bool hasContext(de::String const &name) const; + BindContext *newContext(de::String const &name); /** * Lookup a binding context by symbolic @a name. @@ -240,15 +241,14 @@ class InputSystem : public de::System int contextPositionOf(BindContext *context) const; /** - * Creates a new binding context. The new context has the highest priority - * of all existing contexts, and is inactive. + * Iterate through all the BindContexts from highest to lowest priority. */ - BindContext *newContext(de::String const &name); + de::LoopResult forAllContexts(std::function func) const; /** - * Iterate through all the BindContexts from highest to lowest priority. + * Returns the total number of binding contexts in the system. */ - de::LoopResult forAllContexts(std::function func) const; + int contextCount() const; // --- diff --git a/doomsday/client/include/ui/playerimpulse.h b/doomsday/client/include/ui/playerimpulse.h index 436aa4c043..9c07809156 100644 --- a/doomsday/client/include/ui/playerimpulse.h +++ b/doomsday/client/include/ui/playerimpulse.h @@ -25,76 +25,98 @@ /** * Describes a player interaction impulse. + * + * @todo Is "take" is the wrong verb in this context? + * Player impulses are acted upon by the player Brain (on game side). Does it make + * sense for a "brain" to "consume" an impulse? (Also note established convention + * in Qt containers for removing an element from the container). Perhaps we need + * another abstraction here? -ds + * + * @todo cleanup client/server confusion. On server side, each player will have a + * local model of a remote human player's impulses. However, Double-clicks can be + * handled entirely on client side. -ds */ -struct PlayerImpulse +class PlayerImpulse { - int id; +public: impulsetype_t type; - de::String name; - de::String bindContextName; - short booleanCounts[DDMAXPLAYERS]; +public: + PlayerImpulse(int id, impulsetype_t type, de::String const &name, + de::String bindContextName); + + bool isTriggerable() const; + + /** + * Returns the unique identifier of the impulse. + */ + int id() const; + + /** + * Returns the symbolic name of the impulse. This name is used for resolving + * or generating textual binding descriptors. + */ + de::String name() const; + + /** + * Returns the symbolic name of the attributed binding context. + */ + de::String bindContextName() const; #ifdef __CLIENT__ /** - * Double-"clicks" actually mean double activations that occur within the double-click - * threshold. This is to allow double-clicks also from the numeric impulses. + * Returns @c true if one or more ImpulseBindings exist in @em any bindContext. + * + * @param playerNum Console/player number. */ - struct DoubleClick - { - enum State - { - None, - Positive, - Negative - }; - - bool triggered = false; //< True if double-click has been detected. - uint previousClickTime = 0; //< Previous time an activation occurred. - State lastState = None; //< State at the previous time the check was made. - State previousClickState = None; /** Previous click state. When duplicated, triggers - the double click. */ - } doubleClicks[DDMAXPLAYERS]; + bool haveBindingsFor(int playerNum) const; #endif - PlayerImpulse(int id, impulsetype_t type, de::String const &name, de::String bindContextName) - : id (id) - , type (type) - , name (name) - , bindContextName(bindContextName) - { - de::zap(booleanCounts); - } +public: + /** + * @param playerNum Console/player number. + */ + void triggerBoolean(int playerNum); /** - * Returns @c true if the impulse is triggerable. + * @param playerNum Console/player number. */ - inline bool isTriggerable() const { - return (type == IT_NUMERIC_TRIGGERED || type == IT_BOOLEAN); - } + int takeBoolean(int playerNum); #ifdef __CLIENT__ /** - * Updates the double-click state of an impulse and marks it as double-clicked - * when the double-click condition is met. - * - * @param playerNum Player/console number. - * @param pos State of the impulse. + * @param playerNum Console/player number. + * @param pos + * @param relOffset */ - void maintainDoubleClicks(int playerNum, float pos); + void takeNumeric(int playerNum, float *pos = nullptr, float *relOffset = nullptr); + /** + * @param playerNum Console/player number. + */ int takeDoubleClick(int playerNum); + /** + * @param playerNum Console/player number. Use @c < 0 || >= DDMAXPLAYERS for all. + */ + void clearAccumulation(int playerNum = -1 /*all local players*/); + +public: + /** * Register the console commands and variables of this module. */ static void consoleRegister(); + #endif + +private: + DENG2_PRIVATE(d) }; void P_ImpulseShutdown(); -PlayerImpulse *P_ImpulseById(int id); +PlayerImpulse *P_ImpulsePtr(int id); PlayerImpulse *P_ImpulseByName(de::String const &name); diff --git a/doomsday/client/src/con_config.cpp b/doomsday/client/src/con_config.cpp index 29f66e29ce..40eb317bca 100644 --- a/doomsday/client/src/con_config.cpp +++ b/doomsday/client/src/con_config.cpp @@ -206,7 +206,7 @@ static bool writeBindingsState(Path const &filePath) isys.forAllContexts([&isys, &file] (BindContext &context) { // Commands. - context.forAllCommandBindings([&isys, &context, &file] (CommandBinding &bind) + context.forAllCommandBindings([&isys, &file, &context] (CommandBinding &bind) { fprintf(file, "bindevent \"%s:%s\" \"", context.name().toUtf8().constData(), isys.composeBindsFor(bind).toUtf8().constData()); @@ -216,13 +216,13 @@ static bool writeBindingsState(Path const &filePath) }); // Impulses. - context.forAllImpulseBindings([&isys, &context, &file] (ImpulseBinding &bind) + context.forAllImpulseBindings([&isys, &file, &context] (ImpulseBinding &bind) { - PlayerImpulse const *impulse = P_ImpulseById(bind.impulseId); + PlayerImpulse const *impulse = P_ImpulsePtr(bind.impulseId); DENG2_ASSERT(impulse); fprintf(file, "bindcontrol local%i-%s \"%s\"\n", - bind.localPlayer + 1, impulse->name.toUtf8().constData(), + bind.localPlayer + 1, impulse->name().toUtf8().constData(), isys.composeBindsFor(bind).toUtf8().constData()); return LoopContinue; }); diff --git a/doomsday/client/src/ui/bindcontext.cpp b/doomsday/client/src/ui/bindcontext.cpp index be39bd8fa9..0391e49839 100644 --- a/doomsday/client/src/ui/bindcontext.cpp +++ b/doomsday/client/src/ui/bindcontext.cpp @@ -280,10 +280,10 @@ ImpulseBinding *BindContext::bindImpulse(char const *ctrlDesc, try { std::unique_ptr newBind(new ImpulseBinding); - inputSys().configure(*newBind, ctrlDesc, impulse.id, localPlayer); // Don't assign a new ID. + inputSys().configure(*newBind, ctrlDesc, impulse.id(), localPlayer); // Don't assign a new ID. ImpulseBinding *bind = newBind.get(); - ControlGroup &group = *d->findControlGroup(impulse.id, true/*create if missing*/); + ControlGroup &group = *d->findControlGroup(impulse.id(), true/*create if missing*/); group.binds[localPlayer].append(newBind.release()); /// @todo: fix local player binding id management. @@ -295,7 +295,7 @@ ImpulseBinding *BindContext::bindImpulse(char const *ctrlDesc, LOG_INPUT_VERBOSE("Impulse " _E(b) "'%s'" _E(.) " of player%i now bound to \"%s\" in " _E(b) "'%s'" _E(.) " with binding Id " _E(b) "%i") - << impulse.name << (localPlayer + 1) << ctrlDesc << d->name << bind->id; + << impulse.name() << (localPlayer + 1) << ctrlDesc << d->name << bind->id; /// @todo: Notify interested parties. //DENG2_FOR_AUDIENCE2(BindingAddition, i) i->bindContextBindingAdded(*this, bind, false/*is-impulse*/); diff --git a/doomsday/client/src/ui/inputsystem.cpp b/doomsday/client/src/ui/inputsystem.cpp index 4079a0b3a4..d0c2039ba5 100644 --- a/doomsday/client/src/ui/inputsystem.cpp +++ b/doomsday/client/src/ui/inputsystem.cpp @@ -1404,7 +1404,7 @@ ImpulseBinding *InputSystem::bindImpulse(char const *ctrlDesc, char const *impul return nullptr; } - BindContext *context = contextPtr(impulse->bindContextName); + BindContext *context = contextPtr(impulse->bindContextName()); DENG2_ASSERT(context); // Should be known by now? if(!context) { @@ -1893,11 +1893,11 @@ D_CMD(ListBindings) for(int pl = 0; pl < DDMAXPLAYERS; ++pl) context.forAllImpulseBindings(pl, [&isys, &pl] (ImpulseBinding &bind) { - PlayerImpulse const *impulse = P_ImpulseById(bind.impulseId); + PlayerImpulse const *impulse = P_ImpulsePtr(bind.impulseId); DENG2_ASSERT(impulse); LOG_INPUT_MSG(" [%3i] " _E(>) _E(b) "%s" _E(.) " player%i %s") - << bind.id << isys.composeBindsFor(bind) << (pl + 1) << impulse->name; + << bind.id << isys.composeBindsFor(bind) << (pl + 1) << impulse->name(); return LoopContinue; }); @@ -2048,10 +2048,10 @@ DENG_EXTERN_C int B_BindingsForControl(int localPlayer, char const *impulseNameC { DENG2_ASSERT(bind.localPlayer == localPlayer); - PlayerImpulse const *impulse = P_ImpulseById(bind.impulseId); + PlayerImpulse const *impulse = P_ImpulsePtr(bind.impulseId); DENG2_ASSERT(impulse); - if(!impulse->name.compareWithoutCase(impulseName)) + if(!impulse->name().compareWithoutCase(impulseName)) { if(inverse == BFCI_BOTH || (inverse == BFCI_ONLY_NON_INVERSE && !(bind.flags & IBDF_INVERSE)) || diff --git a/doomsday/client/src/ui/playerimpulse.cpp b/doomsday/client/src/ui/playerimpulse.cpp index 1554692d30..77376bba89 100644 --- a/doomsday/client/src/ui/playerimpulse.cpp +++ b/doomsday/client/src/ui/playerimpulse.cpp @@ -17,6 +17,8 @@ * http://www.gnu.org/licenses */ +#define DENG_NO_API_MACROS_PLAYER + #include "ui/playerimpulse.h" #include @@ -39,91 +41,269 @@ using namespace de; +#ifdef __CLIENT__ +static inline InputSystem &inputSys() +{ + return ClientApp::inputSystem(); +} +#endif + #ifdef __CLIENT__ static int pimpDoubleClickThreshold = 300; ///< Milliseconds, cvar +#endif -void PlayerImpulse::maintainDoubleClicks(int playerNum, float pos) +DENG2_PIMPL_NOREF(PlayerImpulse) { - LOG_AS("PlayerImpulse"); + int id; + String name; + String bindContextName; - if(playerNum < 0 || playerNum >= DDMAXPLAYERS) - return; + short booleanCounts[DDMAXPLAYERS]; - DoubleClick &db = doubleClicks[playerNum]; - if(pimpDoubleClickThreshold <= 0) +#ifdef __CLIENT__ + /** + * Double-"clicks" actually mean double activations that occur within the double-click + * threshold. This is to allow double-clicks also from the numeric impulses. + */ + struct DoubleClick { - // Let's not waste time here. - db.triggered = false; - db.previousClickTime = 0; - db.previousClickState = DoubleClick::None; - return; - } + enum State + { + None, + Positive, + Negative + }; + + bool triggered = false; //< True if double-click has been detected. + uint previousClickTime = 0; //< Previous time an activation occurred. + State lastState = None; //< State at the previous time the check was made. + State previousClickState = None; /** Previous click state. When duplicated, triggers + the double click. */ + } doubleClicks[DDMAXPLAYERS]; +#endif - DoubleClick::State newState = DoubleClick::None; - if(pos > .5) - { - newState = DoubleClick::Positive; - } - else if(pos < -.5) - { - newState = DoubleClick::Negative; - } - else - { - db.lastState = newState; // Release. - return; - } + Instance() { de::zap(booleanCounts); } - // But has it actually changed? - if(newState == db.lastState) +#ifdef __CLIENT__ + /** + * Track the double-click state of the impulse and generate a symbolic + * input event if the trigger conditions are met. + * + * @param playerNum Console/player number. + * @param pos State of the impulse. + */ + void maintainDoubleClicks(int playerNum, float pos) { - return; + DENG2_ASSERT(playerNum >= 0 && playerNum < DDMAXPLAYERS); + + DoubleClick &db = doubleClicks[playerNum]; + if(pimpDoubleClickThreshold <= 0) + { + // Let's not waste time here. + db.triggered = false; + db.previousClickTime = 0; + db.previousClickState = DoubleClick::None; + return; + } + + DoubleClick::State newState = DoubleClick::None; + if(pos > .5) + { + newState = DoubleClick::Positive; + } + else if(pos < -.5) + { + newState = DoubleClick::Negative; + } + else + { + db.lastState = newState; // Release. + return; + } + + // But has it actually changed? + if(newState == db.lastState) + { + return; + } + + // We have an activation! + uint const nowTime = Timer_RealMilliseconds(); + + if(newState == db.previousClickState && + nowTime - db.previousClickTime < uint( de::clamp(0, pimpDoubleClickThreshold) )) + { + db.triggered = true; + + // Compose the name of the symbolic event. + String symbolicName; + switch(newState) + { + case DoubleClick::Positive: symbolicName += "control-doubleclick-positive-"; break; + case DoubleClick::Negative: symbolicName += "control-doubleclick-negative-"; break; + + default: break; + } + symbolicName += name; + + int const localPlayer = P_ConsoleToLocal(playerNum); + LOG_INPUT_XVERBOSE("Triggered " _E(b) "'%s'" _E(.) " for player%i state: %i threshold: %i\n %s") + << name << localPlayer << newState << (nowTime - db.previousClickTime) + << symbolicName; + + ddevent_t ev; de::zap(ev); + ev.device = uint(-1); + ev.type = E_SYMBOLIC; + ev.symbolic.id = localPlayer; + ev.symbolic.name = symbolicName.toUtf8().constData(); + + inputSys().postEvent(&ev); + } + + db.previousClickTime = nowTime; + db.previousClickState = newState; + db.lastState = newState; } +#endif +}; + +PlayerImpulse::PlayerImpulse(int id, impulsetype_t type, String const &name, String bindContextName) + : type(type), d(new Instance) +{ + d->id = id; + d->name = name; + d->bindContextName = bindContextName; +} + +bool PlayerImpulse::isTriggerable() const +{ + return (type == IT_NUMERIC_TRIGGERED || type == IT_BOOLEAN); +} + +int PlayerImpulse::id() const +{ + return d->id; +} + +String PlayerImpulse::name() const +{ + return d->name; +} - // We have an activation! - uint const nowTime = Timer_RealMilliseconds(); +String PlayerImpulse::bindContextName() const +{ + return d->bindContextName; +} - if(newState == db.previousClickState && - nowTime - db.previousClickTime < uint( de::clamp(0, pimpDoubleClickThreshold) )) +#ifdef __CLIENT__ +bool PlayerImpulse::haveBindingsFor(int playerNum) const +{ + // Ensure this is really a numeric impulse. + DENG2_ASSERT(type == IT_NUMERIC || type == IT_NUMERIC_TRIGGERED); + LOG_AS("PlayerImpulse"); + + InputSystem &isys = ClientApp::inputSystem(); + + // ImpulseBindings are associated with local player numbers rather than + // the player console number - translate. + int const localPlayer = P_ConsoleToLocal(playerNum); + if(localPlayer < 0) return false; + + BindContext *bindContext = isys.contextPtr(d->bindContextName); + if(!bindContext) return false; + + int found = bindContext->forAllImpulseBindings(localPlayer, [this, &isys] (ImpulseBinding &bind) { - db.triggered = true; + // Wrong impulse? + if(bind.impulseId != d->id) return LoopContinue; - // Compose the name of the symbolic event. - String symbolicName; - switch(newState) + if(InputDevice const *device = isys.devicePtr(bind.deviceId)) { - case DoubleClick::Positive: symbolicName += "control-doubleclick-positive-"; break; - case DoubleClick::Negative: symbolicName += "control-doubleclick-negative-"; break; - - default: break; + if(device->isActive()) + { + return LoopAbort; // found a binding. + } } - symbolicName += name; + return LoopContinue; + }); - LOG_INPUT_XVERBOSE("Triggered plr %i, imp %i, state %i - threshold %i (%s)") - << playerNum << id << newState << (nowTime - db.previousClickTime) - << symbolicName; + return found; +} +#endif // __CLIENT__ - ddevent_t ev; de::zap(ev); - ev.device = uint(-1); - ev.type = E_SYMBOLIC; - ev.symbolic.id = playerNum; - ev.symbolic.name = symbolicName.toUtf8().constData(); +void PlayerImpulse::triggerBoolean(int playerNum) +{ + // Ensure this is really a boolean impulse. + DENG2_ASSERT(type == IT_BOOLEAN); + LOG_AS("PlayerImpulse"); - ClientApp::inputSystem().postEvent(&ev); - } + if(playerNum < 0 || playerNum >= DDMAXPLAYERS) + return; + + d->booleanCounts[playerNum]++; + +#ifdef __CLIENT__ + // Mark for double clicks. + d->maintainDoubleClicks(playerNum, 1); + d->maintainDoubleClicks(playerNum, 0); +#endif +} + +int PlayerImpulse::takeBoolean(int playerNum) +{ + // Ensure this is really a boolean impulse. + DENG2_ASSERT(type == IT_BOOLEAN); + LOG_AS("PlayerImpulse"); + + if(playerNum < 0 || playerNum >= DDMAXPLAYERS) + return 0; + + short *counter = &d->booleanCounts[playerNum]; + int count = *counter; + *counter = 0; + return count; +} + +#ifdef __CLIENT__ + +void PlayerImpulse::takeNumeric(int playerNum, float *pos, float *relativeOffset) +{ + // Ensure this is really a numeric control. + DENG2_ASSERT(type == IT_NUMERIC || type == IT_NUMERIC_TRIGGERED); + LOG_AS("PlayerImpulse"); + + // Ignore NULLs. + float tmp; + if(!pos) pos = &tmp; + if(!relativeOffset) relativeOffset = &tmp; + + *pos = 0; + *relativeOffset = 0; + + if(playerNum < 0 || playerNum >= DDMAXPLAYERS) + return; + + if(BindContext *context = inputSys().contextPtr(d->bindContextName)) + { + // ImpulseBindings are associated with local player numbers rather than + // the player console number - translate. + B_EvaluateImpulseBindings(context, P_ConsoleToLocal(playerNum), d->id, + pos, relativeOffset, isTriggerable()); - db.previousClickTime = nowTime; - db.previousClickState = newState; - db.lastState = newState; + // Mark for double-clicks. + d->maintainDoubleClicks(playerNum, *pos); + } } int PlayerImpulse::takeDoubleClick(int playerNum) { + LOG_AS("PlayerImpulse"); + if(playerNum < 0 || playerNum >= DDMAXPLAYERS) return 0; bool triggered = false; - DoubleClick &db = doubleClicks[playerNum]; + Instance::DoubleClick &db = d->doubleClicks[playerNum]; if(db.triggered) { triggered = true; @@ -133,8 +313,47 @@ int PlayerImpulse::takeDoubleClick(int playerNum) return int(triggered); } +void PlayerImpulse::clearAccumulation(int playerNum) +{ + LOG_AS("PlayerImpulse"); + + bool const haveNamedPlayer = (playerNum < 0 || playerNum >= DDMAXPLAYERS); + if(haveNamedPlayer) + { + switch(type) + { + case IT_NUMERIC: takeNumeric(playerNum); break; + case IT_BOOLEAN: takeBoolean(playerNum); break; + + case IT_NUMERIC_TRIGGERED: break; + + default: DENG2_ASSERT(!"PlayerImpulse::clearAccumulation: Unknown type"); + } + // Also clear the double click state. + takeDoubleClick(playerNum); + return; + } + + // Clear accumulation for all local players. + for(int i = 0; i < DDMAXPLAYERS; ++i) + { + switch(type) + { + case IT_NUMERIC: takeNumeric(i); break; + case IT_BOOLEAN: takeBoolean(i); break; + + case IT_NUMERIC_TRIGGERED: break; + + default: DENG2_ASSERT(!"PlayerImpulse::clearAccumulation: Unknown type"); + } + // Also clear the double click state. + takeDoubleClick(i); + } +} + void PlayerImpulse::consoleRegister() // static { + LOG_AS("PlayerImpulse"); C_VAR_INT("input-doubleclick-threshold", &pimpDoubleClickThreshold, 0, 0, 2000); } #endif @@ -147,8 +366,8 @@ static ImpulsesByName impulsesByName; static void addImpulse(PlayerImpulse *imp) { if(!imp) return; - impulses.insert(imp->id, imp); - impulsesByName.insert(imp->name.toLower(), imp); + impulses.insert(imp->id(), imp); + impulsesByName.insert(imp->name().toLower(), imp); } void P_ImpulseShutdown() @@ -158,7 +377,7 @@ void P_ImpulseShutdown() impulsesByName.clear(); } -PlayerImpulse *P_ImpulseById(int id) +PlayerImpulse *P_ImpulsePtr(int id) { auto found = impulses.find(id); if(found != impulses.end()) return *found; @@ -184,7 +403,7 @@ D_CMD(ListImpulses) for(PlayerImpulse const *imp : impulsesByName) { LOG_MSG(" [%4i] " _E(>) _E(b) "%s " _E(.) "(%s) " _E(2) "%s%s") - << imp->id << imp->name << imp->bindContextName + << imp->id() << imp->name() << imp->bindContextName() << (imp->type == IT_BOOLEAN? "boolean" : "numeric") << (imp->isTriggerable()? ", triggerable" : ""); } @@ -202,11 +421,10 @@ D_CMD(Impulse) return true; } - int const playerNum = (argc == 3? P_LocalToConsole(String(argv[2]).toInt()) : consolePlayer); - + int const localPlayer = (argc == 3? String(argv[2]).toInt() : 0); if(PlayerImpulse *imp = P_ImpulseByName(argv[1])) { - P_Impulse(playerNum, imp->id); + imp->triggerBoolean(localPlayer); } return true; @@ -224,19 +442,8 @@ D_CMD(ClearImpulseAccumulation) }); for(PlayerImpulse *imp : impulses) - for(int p = 0; p < DDMAXPLAYERS; ++p) { - if(imp->type == IT_NUMERIC) - { - P_GetControlState(p, imp->id, nullptr, nullptr); - } - else if(imp->type == IT_BOOLEAN) - { - P_GetImpulseControlState(p, imp->id); - } - - // Also clear the double click state. - imp->takeDoubleClick(p); + imp->clearAccumulation(); // for all local players. } return true; @@ -255,7 +462,6 @@ void P_ConsoleRegister() #endif } -#undef P_NewPlayerControl DENG_EXTERN_C void P_NewPlayerControl(int id, impulsetype_t type, char const *name, char const *bindContext) { @@ -263,110 +469,66 @@ DENG_EXTERN_C void P_NewPlayerControl(int id, impulsetype_t type, char const *na LOG_AS("P_NewPlayerControl"); // Ensure the given id is unique. - if(PlayerImpulse const *existing = P_ImpulseById(id)) + if(PlayerImpulse const *existing = P_ImpulsePtr(id)) { LOG_INPUT_WARNING("Id: %i is already in use by impulse '%s' - Won't replace") - << id << existing->name; + << id << existing->name(); return; } // Ensure the given name is unique. if(PlayerImpulse const *existing = P_ImpulseByName(name)) { LOG_INPUT_WARNING("Name: '%s' is already in use by impulse Id: %i - Won't replace") - << name << existing->id; + << name << existing->id(); return; } addImpulse(new PlayerImpulse(id, type, name, bindContext)); } -#undef P_GetControlState -DENG_EXTERN_C void P_GetControlState(int playerNum, int impulseId, float *pos, - float *relativeOffset) +DENG_EXTERN_C int P_IsControlBound(int playerNum, int impulseId) { #ifdef __CLIENT__ - InputSystem &isys = ClientApp::inputSystem(); - - // Ignore NULLs. - float tmp; - if(!pos) pos = &tmp; - if(!relativeOffset) relativeOffset = &tmp; - - *pos = 0; - *relativeOffset = 0; - - // ImpulseBindings are associated with local player numbers rather than - // the player console number - translate. - int localPlayer = P_ConsoleToLocal(playerNum); - if(localPlayer < 0 || localPlayer >= DDMAXPLAYERS) - return; - - // Check that this is really a numeric control. - PlayerImpulse *imp = P_ImpulseById(impulseId); - DENG2_ASSERT(imp); - DENG2_ASSERT(imp->type == IT_NUMERIC || imp->type == IT_NUMERIC_TRIGGERED); - - BindContext *context = isys.contextPtr(imp->bindContextName); - DENG2_ASSERT(context); // must surely exist by now? - if(!context) return; + LOG_AS("P_IsControlBound"); - B_EvaluateImpulseBindings(context, localPlayer, impulseId, pos, relativeOffset, - imp->isTriggerable()); + if(playerNum < 0 || playerNum >= DDMAXPLAYERS) + return false; - // Mark for double-clicks. - imp->maintainDoubleClicks(playerNum, *pos); + if(PlayerImpulse const *imp = P_ImpulsePtr(impulseId)) + { + return imp->haveBindingsFor(playerNum); + } + return false; #else - DENG2_UNUSED4(playerNum, impulseId, pos, relativeOffset); + DENG2_UNUSED2(playerNum, impulseId); + return 0; #endif } -#undef P_IsControlBound -DENG_EXTERN_C int P_IsControlBound(int playerNum, int impulseId) +DENG_EXTERN_C void P_GetControlState(int playerNum, int impulseId, float *pos, + float *relativeOffset) { #ifdef __CLIENT__ - InputSystem &isys = ClientApp::inputSystem(); - - // ImpulseBindings are associated with local player numbers rather than - // the player console number - translate. - int const localPlayer = P_ConsoleToLocal(playerNum); - if(localPlayer < 0 || localPlayer >= DDMAXPLAYERS) - return false; - - // Ensure this is really a numeric impulse. - PlayerImpulse const *imp = P_ImpulseById(impulseId); - DENG2_ASSERT(imp); - DENG2_ASSERT(imp->type == IT_NUMERIC || imp->type == IT_NUMERIC_TRIGGERED); + // Ignore NULLs. + float tmp; + if(!pos) pos = &tmp; + if(!relativeOffset) relativeOffset = &tmp; - // There must be bindings to active input devices. - BindContext *context = isys.contextPtr(imp->bindContextName); - DENG2_ASSERT(context); // must surely exist by now? - if(!context) return false; + *pos = 0; + *relativeOffset = 0; - int found = context->forAllImpulseBindings(localPlayer, [&isys, &impulseId] (ImpulseBinding &bind) + if(PlayerImpulse *imp = P_ImpulsePtr(impulseId)) { - // Wrong impulse? - if(bind.impulseId != impulseId) return LoopContinue; - - if(InputDevice const *device = isys.devicePtr(bind.deviceId)) - { - if(device->isActive()) - { - return LoopAbort; // found a binding. - } - } - return LoopContinue; - }); - - return found; + imp->takeNumeric(playerNum, pos, relativeOffset); + return; + } #else - DENG2_UNUSED2(playerNum, impulseId); - return 0; + DENG2_UNUSED4(playerNum, impulseId, pos, relativeOffset); #endif } -#undef P_GetImpulseControlState DENG_EXTERN_C int P_GetImpulseControlState(int playerNum, int impulseId) { LOG_AS("P_GetImpulseControlState"); @@ -374,23 +536,19 @@ DENG_EXTERN_C int P_GetImpulseControlState(int playerNum, int impulseId) if(playerNum < 0 || playerNum >= DDMAXPLAYERS) return 0; - PlayerImpulse *imp = P_ImpulseById(impulseId); + PlayerImpulse *imp = P_ImpulsePtr(impulseId); if(!imp) return 0; // Ensure this is really a boolean impulse. if(imp->type != IT_BOOLEAN) { - LOG_INPUT_WARNING("Impulse '%s' is not boolean") << imp->name; + LOG_INPUT_WARNING("Impulse '%s' is not boolean") << imp->name(); return 0; } - short *counter = &imp->booleanCounts[playerNum]; - int count = *counter; - *counter = 0; - return count; + return imp->takeBoolean(playerNum); } -#undef P_Impulse DENG_EXTERN_C void P_Impulse(int playerNum, int impulseId) { LOG_AS("P_Impulse"); @@ -398,21 +556,15 @@ DENG_EXTERN_C void P_Impulse(int playerNum, int impulseId) if(playerNum < 0 || playerNum >= DDMAXPLAYERS) return; - PlayerImpulse *imp = P_ImpulseById(impulseId); + PlayerImpulse *imp = P_ImpulsePtr(impulseId); if(!imp) return; // Ensure this is really a boolean impulse. if(imp->type != IT_BOOLEAN) { - LOG_INPUT_WARNING("Impulse '%s' is not boolean") << imp->name; + LOG_INPUT_WARNING("Impulse '%s' is not boolean") << imp->name(); return; } - imp->booleanCounts[playerNum]++; - -#ifdef __CLIENT__ - // Mark for double clicks. - imp->maintainDoubleClicks(playerNum, 1); - imp->maintainDoubleClicks(playerNum, 0); -#endif + imp->triggerBoolean(playerNum); } diff --git a/doomsday/client/src/world/p_players.cpp b/doomsday/client/src/world/p_players.cpp index 3d988ce802..8cfcc6276b 100644 --- a/doomsday/client/src/world/p_players.cpp +++ b/doomsday/client/src/world/p_players.cpp @@ -186,7 +186,7 @@ DENG_EXTERN_C char const *Net_GetPlayerName(int player); DENG_EXTERN_C ident_t Net_GetPlayerID(int player); DENG_EXTERN_C Smoother *Net_PlayerSmoother(int player); -// p_control.c +// playerimpulse.cpp DENG_EXTERN_C void P_NewPlayerControl(int id, impulsetype_t type, char const *name, char const *bindContext); DENG_EXTERN_C int P_IsControlBound(int playerNum, int control); DENG_EXTERN_C void P_GetControlState(int playerNum, int control, float *pos, float *relativeOffset);