From 6c43c3eb3fd7b827cb340787a49961c19e492268 Mon Sep 17 00:00:00 2001 From: danij Date: Wed, 12 Nov 2014 23:45:22 +0000 Subject: [PATCH] Refactor: Moved ImpulseAccumulator to the World domain, cleanup --- doomsday/api/api_player.h | 5 +- doomsday/client/client.pro | 4 +- doomsday/client/include/de_play.h | 1 - .../impulseaccumulator.h} | 42 +- doomsday/client/include/world/p_players.h | 21 +- doomsday/client/src/con_config.cpp | 7 +- doomsday/client/src/dd_main.cpp | 5 +- doomsday/client/src/dd_pinit.cpp | 2 +- doomsday/client/src/ui/bindcontext.cpp | 3 +- doomsday/client/src/ui/inputsystem.cpp | 9 +- doomsday/client/src/ui/playerimpulse.cpp | 570 ------------------ .../client/src/world/impulseaccumulator.cpp | 281 +++++++++ doomsday/client/src/world/p_players.cpp | 305 +++++++++- doomsday/server/server.pro | 4 +- 14 files changed, 626 insertions(+), 633 deletions(-) rename doomsday/client/include/{ui/playerimpulse.h => world/impulseaccumulator.h} (67%) delete mode 100644 doomsday/client/src/ui/playerimpulse.cpp create mode 100644 doomsday/client/src/world/impulseaccumulator.cpp diff --git a/doomsday/api/api_player.h b/doomsday/api/api_player.h index 7e1b9a805a..56583d185d 100644 --- a/doomsday/api/api_player.h +++ b/doomsday/api/api_player.h @@ -196,10 +196,13 @@ DENG_API_TYPEDEF(Player) void (*NewControl)(int id, impulsetype_t type, char const *name, char const *bindContext); /** - * Determines if an impulse has been bound to anything. + * Determines if one or more bindings exist for a player and impulse Id in + * the associated binding context. * * @param playerNum Console/player number. * @param impulseId Unique identifier of the impulse to lookup bindings for. + * + * @return @c true if one or more bindings exist. */ int (*IsControlBound)(int playerNum, int impulseId); diff --git a/doomsday/client/client.pro b/doomsday/client/client.pro index dcc9e54ea0..5241be00bd 100644 --- a/doomsday/client/client.pro +++ b/doomsday/client/client.pro @@ -415,7 +415,6 @@ DENG_HEADERS += \ include/ui/joystick.h \ include/ui/mouse_qt.h \ include/ui/nativeui.h \ - include/ui/playerimpulse.h \ include/ui/styledlogsinkformatter.h \ include/ui/sys_input.h \ include/ui/ui_main.h \ @@ -448,6 +447,7 @@ DENG_HEADERS += \ include/world/grabbable.h \ include/world/hand.h \ include/world/huecircle.h \ + include/world/impulseaccumulator.h \ include/world/interceptor.h \ include/world/line.h \ include/world/lineblockmap.h \ @@ -717,7 +717,6 @@ SOURCES += \ src/ui/inputsystem.cpp \ src/ui/mouse_qt.cpp \ src/ui/nativeui.cpp \ - src/ui/playerimpulse.cpp \ src/ui/progress.cpp \ src/ui/styledlogsinkformatter.cpp \ src/ui/sys_input.cpp \ @@ -773,6 +772,7 @@ SOURCES += \ src/world/grabbable.cpp \ src/world/hand.cpp \ src/world/huecircle.cpp \ + src/world/impulseaccumulator.cpp \ src/world/interceptor.cpp \ src/world/line.cpp \ src/world/lineblockmap.cpp \ diff --git a/doomsday/client/include/de_play.h b/doomsday/client/include/de_play.h index d420fa3d7e..2ae9b92c5b 100644 --- a/doomsday/client/include/de_play.h +++ b/doomsday/client/include/de_play.h @@ -43,7 +43,6 @@ #include "world/p_players.h" #include "world/thinkers.h" #include "Material" -#include "ui/playerimpulse.h" #include "r_util.h" #include "api_map.h" diff --git a/doomsday/client/include/ui/playerimpulse.h b/doomsday/client/include/world/impulseaccumulator.h similarity index 67% rename from doomsday/client/include/ui/playerimpulse.h rename to doomsday/client/include/world/impulseaccumulator.h index 1ceea0d864..327b29fa76 100644 --- a/doomsday/client/include/ui/playerimpulse.h +++ b/doomsday/client/include/world/impulseaccumulator.h @@ -1,4 +1,4 @@ -/** @file playerimpulse.h Player interaction impulse. +/** @file impulseaccumulator.h Player impulse accumulation. * * @authors Copyright © 2003-2013 Jaakko Keränen * @authors Copyright © 2006-2014 Daniel Swanson @@ -17,11 +17,11 @@ * http://www.gnu.org/licenses */ -#ifndef CLIENT_PLAY_PLAYERIMPULSE_H -#define CLIENT_PLAY_PLAYERIMPULSE_H +#ifndef CLIENT_PLAY_IMPULSEACCUMULATOR_H +#define CLIENT_PLAY_IMPULSEACCUMULATOR_H #include -#include "api_player.h" +#include "api_player.h" // impulsetype_t /** * Receives player interaction impulses and normalizes them for later consumption @@ -29,10 +29,6 @@ * * @todo The player Brain should have ownership of it's ImpulseAccumulators. * - * @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 - * * @ingroup playsim */ class ImpulseAccumulator @@ -82,7 +78,6 @@ class ImpulseAccumulator * Register the console commands and variables of this module. */ static void consoleRegister(); - #endif private: @@ -97,30 +92,9 @@ class ImpulseAccumulator struct PlayerImpulse { int id = 0; - impulsetype_t type; - de::String name; ///< Symbolic. Used when resolving or generating textual binding descriptors. - de::String bindContextName; ///< Symbolic name of the associated binding context. - -#ifdef __CLIENT__ - /** - * Returns @c true if one or more bindings for this impulse exist, for the - * given @a localPlayer number in the associated BindContext. - * - * @param localPlayer Local player number. - */ - bool haveBindingsFor(int playerNumber) const; -#endif + impulsetype_t type = IT_ANALOG; + de::String name; ///< Symbolic. Used when resolving or generating textual binding descriptors. + de::String bindContextName; ///< Symbolic name of the associated binding context. }; -void P_ImpulseShutdown(); - -PlayerImpulse *P_ImpulsePtr(int id); - -PlayerImpulse *P_ImpulseByName(de::String const &name); - -/** - * Register the console commands and variables of this module. - */ -void P_ImpulseConsoleRegister(); - -#endif // CLIENT_PLAY_PLAYERIMPULSE_H +#endif // CLIENT_PLAY_IMPULSEACCUMULATOR_H diff --git a/doomsday/client/include/world/p_players.h b/doomsday/client/include/world/p_players.h index 78deec6fcf..91e421efa8 100644 --- a/doomsday/client/include/world/p_players.h +++ b/doomsday/client/include/world/p_players.h @@ -1,7 +1,7 @@ -/** @file p_players.h World player entities. +/** @file p_players.h World player entities. * * @authors Copyright © 2003-2013 Jaakko Keränen - * @authors Copyright © 2005-2013 Daniel Swanson + * @authors Copyright © 2005-2014 Daniel Swanson * * @par License * GPL: http://www.gnu.org/licenses/gpl.html @@ -59,6 +59,21 @@ float P_ShortToLookDir(short s); #ifdef __cplusplus } // extern "C" -#endif +#include + +struct PlayerImpulse; + +void P_ClearPlayerImpulses(); + +PlayerImpulse *P_PlayerImpulsePtr(int id); + +PlayerImpulse *P_PlayerImpulseByName(de::String const &name); + +/** + * Register the console commands and variables of this module. + */ +void P_ConsoleRegister(); + +#endif // __cplusplus #endif // DENG_WORLD_P_PLAYERS_H diff --git a/doomsday/client/src/con_config.cpp b/doomsday/client/src/con_config.cpp index c30f37ad3b..c0cfce397a 100644 --- a/doomsday/client/src/con_config.cpp +++ b/doomsday/client/src/con_config.cpp @@ -38,10 +38,13 @@ #ifdef __CLIENT__ # include "clientapp.h" + +# include "world/impulseaccumulator.h" +# include "world/p_players.h" + # include "BindContext" # include "CommandBinding" # include "ImpulseBinding" -# include "ui/playerimpulse.h" #endif using namespace de; @@ -222,7 +225,7 @@ static bool writeBindingsState(Path const &filePath) context.forAllImpulseBindings([&file, &context] (Record &rec) { ImpulseBinding bind(rec); - PlayerImpulse const *impulse = P_ImpulsePtr(bind.geti("impulseId")); + PlayerImpulse const *impulse = P_PlayerImpulsePtr(bind.geti("impulseId")); DENG2_ASSERT(impulse); fprintf(file, "bindcontrol local%i-%s \"%s\"\n", diff --git a/doomsday/client/src/dd_main.cpp b/doomsday/client/src/dd_main.cpp index f79e64e576..f786ef88c1 100644 --- a/doomsday/client/src/dd_main.cpp +++ b/doomsday/client/src/dd_main.cpp @@ -56,7 +56,6 @@ #include "world/worldsystem.h" #include "world/map.h" #include "ui/infine/infinesystem.h" -#include "ui/playerimpulse.h" #include "ui/progress.h" #include "ui/nativeui.h" @@ -1513,7 +1512,7 @@ bool App_ChangeGame(Game &game, bool allowReload) #ifdef __CLIENT__ R_ClearViewData(); R_DestroyContactLists(); - P_ImpulseShutdown(); + P_ClearPlayerImpulses(); Con_Execute(CMDS_DDAY, "clearbindings", true, false); ClientApp::inputSystem().bindDefaults(); @@ -3284,7 +3283,7 @@ static void consoleRegister() GL_Register(); UI_Register(); Demo_Register(); - P_ImpulseConsoleRegister(); + P_ConsoleRegister(); I_Register(); #endif diff --git a/doomsday/client/src/dd_pinit.cpp b/doomsday/client/src/dd_pinit.cpp index 6d34922a5b..a398553a7d 100644 --- a/doomsday/client/src/dd_pinit.cpp +++ b/doomsday/client/src/dd_pinit.cpp @@ -163,7 +163,7 @@ void DD_ShutdownAll() } #endif - P_ImpulseShutdown(); + P_ClearPlayerImpulses(); #ifdef __SERVER__ Sv_Shutdown(); #endif diff --git a/doomsday/client/src/ui/bindcontext.cpp b/doomsday/client/src/ui/bindcontext.cpp index 2065252a92..2577764778 100644 --- a/doomsday/client/src/ui/bindcontext.cpp +++ b/doomsday/client/src/ui/bindcontext.cpp @@ -25,10 +25,11 @@ #include #include "clientapp.h" +#include "world/impulseaccumulator.h" + #include "CommandBinding" #include "ImpulseBinding" #include "ui/inputdevice.h" -#include "ui/playerimpulse.h" using namespace de; diff --git a/doomsday/client/src/ui/inputsystem.cpp b/doomsday/client/src/ui/inputsystem.cpp index 407bef6195..56c8146f97 100644 --- a/doomsday/client/src/ui/inputsystem.cpp +++ b/doomsday/client/src/ui/inputsystem.cpp @@ -38,6 +38,8 @@ #include "m_misc.h" // M_WriteTextEsc #include "render/vr.h" + +#include "world/impulseaccumulator.h" #include "world/p_players.h" #include "BindContext" @@ -52,7 +54,6 @@ #include "ui/inputdeviceaxiscontrol.h" #include "ui/inputdevicebuttoncontrol.h" #include "ui/inputdevicehatcontrol.h" -#include "ui/playerimpulse.h" #include "ui/sys_input.h" #include "sys_system.h" // novideo @@ -1447,7 +1448,7 @@ Record *InputSystem::bindImpulse(char const *ctrlDesc, char const *impulseDesc) // The next part must be the impulse name. impulseDesc = Str_CopyDelim(str, impulseDesc, '-'); - PlayerImpulse const *impulse = P_ImpulseByName(Str_Text(str)); + PlayerImpulse const *impulse = P_PlayerImpulseByName(Str_Text(str)); if(!impulse) { LOG_INPUT_WARNING("Player impulse \"%s\" not defined") << Str_Text(str); @@ -1626,7 +1627,7 @@ D_CMD(ListBindings) context.forAllImpulseBindings(pl, [&pl] (Record &rec) { ImpulseBinding bind(rec); - PlayerImpulse const *impulse = P_ImpulsePtr(bind.geti("impulseId")); + PlayerImpulse const *impulse = P_PlayerImpulsePtr(bind.geti("impulseId")); DENG2_ASSERT(impulse); LOG_INPUT_MSG(" [%3i] " _E(>) _E(b) "%s" _E(.) " player%i %s") @@ -1776,7 +1777,7 @@ DENG_EXTERN_C int B_BindingsForControl(int localPlayer, char const *impulseNameC ImpulseBinding bind(rec); DENG2_ASSERT(bind.geti("localPlayer") == localPlayer); - PlayerImpulse const *impulse = P_ImpulsePtr(bind.geti("impulseId")); + PlayerImpulse const *impulse = P_PlayerImpulsePtr(bind.geti("impulseId")); DENG2_ASSERT(impulse); if(!impulse->name.compareWithoutCase(impulseName)) diff --git a/doomsday/client/src/ui/playerimpulse.cpp b/doomsday/client/src/ui/playerimpulse.cpp deleted file mode 100644 index 3c4fed6498..0000000000 --- a/doomsday/client/src/ui/playerimpulse.cpp +++ /dev/null @@ -1,570 +0,0 @@ -/** @file playerimpulse.cpp Player interaction impulses. - * - * @authors Copyright © 2003-2013 Jaakko Keränen - * @authors Copyright © 2006-2014 Daniel Swanson - * - * @par License - * GPL: http://www.gnu.org/licenses/gpl.html - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. This program is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. You should have received a copy of the GNU - * General Public License along with this program; if not, see: - * http://www.gnu.org/licenses - */ - -#define DENG_NO_API_MACROS_PLAYER - -#include "ui/playerimpulse.h" - -#include -#include -#include -#include -#include -#ifdef __CLIENT__ -# include "clientapp.h" -#endif - -#include "api_player.h" -#include "world/p_players.h" - -#ifdef __CLIENT__ -# include "BindContext" -# include "ui/b_util.h" -# include "ui/inputdevice.h" -#endif - -using namespace de; - -#ifdef __CLIENT__ -static inline InputSystem &inputSys() -{ - return ClientApp::inputSystem(); -} -#endif - -#ifdef __CLIENT__ -static int pimpDoubleClickThreshold = 300; ///< Milliseconds, cvar -#endif - -DENG2_PIMPL_NOREF(ImpulseAccumulator) -{ - int impulseId = 0; - AccumulatorType type = Analog; - bool expireBeforeSharpTick = false; - - int playerNum = 0; - - short binaryAccum = 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 - * analog impulses. - */ - 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. */ - } db; - - /** - * Track the double-click state of the impulse and generate a bindable - * symbolic event if the trigger conditions are met. - * - * @param pos State of the impulse. - */ - void maintainDoubleClick(float pos) - { - 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 potential activation! - uint const threshold = uint( de::max(0, pimpDoubleClickThreshold) ); - uint const nowTime = Timer_RealMilliseconds(); - - if(newState == db.previousClickState && nowTime - db.previousClickTime < threshold) - { - db.triggered = true; - - PlayerImpulse *impulse = P_ImpulsePtr(impulseId); - DENG2_ASSERT(impulse); - - // 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 += impulse->name; - - int const localPlayer = P_ConsoleToLocal(playerNum); - DENG2_ASSERT(localPlayer >= 0); - LOG_INPUT_XVERBOSE("Triggered " _E(b) "'%s'" _E(.) " for player%i state: %i threshold: %i\n %s") - << impulse->name << (localPlayer + 1) << newState << (nowTime - db.previousClickTime) - << symbolicName; - - Block symbolicNameUtf8 = symbolicName.toUtf8(); - ddevent_t ev; de::zap(ev); - ev.device = uint(-1); - ev.type = E_SYMBOLIC; - ev.symbolic.id = playerNum; - ev.symbolic.name = symbolicNameUtf8.constData(); - - inputSys().postEvent(&ev); // makes a copy. - } - - db.previousClickTime = nowTime; - db.previousClickState = newState; - db.lastState = newState; - } - - void clearDoubleClick() - { - db.triggered = false; - } -#endif -}; - -ImpulseAccumulator::ImpulseAccumulator(int impulseId, AccumulatorType type, bool expireBeforeSharpTick) - : d(new Instance) -{ - d->impulseId = impulseId; - d->type = type; - d->expireBeforeSharpTick = expireBeforeSharpTick; -} - -void ImpulseAccumulator::setPlayerNum(int newPlayerNum) -{ - d->playerNum = newPlayerNum; -} - -int ImpulseAccumulator::impulseId() const -{ - return d->impulseId; -} - -ImpulseAccumulator::AccumulatorType ImpulseAccumulator::type() const -{ - return d->type; -} - -bool ImpulseAccumulator::expireBeforeSharpTick() const -{ - return d->expireBeforeSharpTick; -} - -void ImpulseAccumulator::receiveBinary() -{ - // Ensure this is really a binary accumulator. - DENG2_ASSERT(d->type == Binary); - LOG_AS("ImpulseAccumulator"); - - d->binaryAccum++; - -#ifdef __CLIENT__ - // Mark for double click. - d->maintainDoubleClick(1); - d->maintainDoubleClick(0); -#endif -} - -int ImpulseAccumulator::takeBinary() -{ - // Ensure this is really a binary accumulator. - DENG2_ASSERT(d->type == Binary); - LOG_AS("ImpulseAccumulator"); - short *counter = &d->binaryAccum; - int count = *counter; - *counter = 0; - return count; -} - -#ifdef __CLIENT__ - -void ImpulseAccumulator::takeAnalog(float *pos, float *relOffset) -{ - // Ensure this is really an analog accumulator. - DENG2_ASSERT(d->type == Analog); - LOG_AS("ImpulseAccumulator"); - - if(pos) *pos = 0; - if(relOffset) *relOffset = 0; - - PlayerImpulse *impulse = P_ImpulsePtr(d->impulseId); - DENG2_ASSERT(impulse); - - if(BindContext *bindContext = inputSys().contextPtr(impulse->bindContextName)) - { - // Impulse bindings are associated with local player numbers rather than - // the player console number - translate. - float position, relative; - B_EvaluateImpulseBindings(bindContext, P_ConsoleToLocal(d->playerNum), d->impulseId, - &position, &relative, d->expireBeforeSharpTick); - - // Mark for double-clicks. - d->maintainDoubleClick(position); - - if(pos) *pos = position; - if(relOffset) *relOffset = relative; - } -} - -void ImpulseAccumulator::clearAll() -{ - LOG_AS("ImpulseAccumulator"); - switch(d->type) - { - case Analog: - if(!d->expireBeforeSharpTick) - { - takeAnalog(); - } - break; - - case Binary: - takeBinary(); - break; - - default: DENG2_ASSERT(!"ImpulseAccumulator::clearAll: Unknown type"); - } - - // Also clear the double click state. - d->clearDoubleClick(); -} - -void ImpulseAccumulator::consoleRegister() // static -{ - LOG_AS("ImpulseAccumulator"); - C_VAR_INT("input-doubleclick-threshold", &pimpDoubleClickThreshold, 0, 0, 2000); -} - -// ------------------------------------------------------------------------------- - -bool PlayerImpulse::haveBindingsFor(int localPlayer) const -{ - LOG_AS("PlayerImpulse"); - - if(localPlayer < 0 || localPlayer >= DDMAXPLAYERS) - return false; - - InputSystem &isys = ClientApp::inputSystem(); - BindContext *bindContext = isys.contextPtr(bindContextName); - if(!bindContext) - { - LOG_INPUT_WARNING("Unknown binding context '%s'") << bindContextName; - return false; - } - - int found = bindContext->forAllImpulseBindings(localPlayer, [this, &isys] (Record &rec) - { - ImpulseBinding bind(rec); - // Wrong impulse? - if(bind.geti("impulseId") != id) return LoopContinue; - - if(InputDevice const *device = isys.devicePtr(bind.geti("deviceId"))) - { - if(device->isActive()) - { - return LoopAbort; // found a binding. - } - } - return LoopContinue; - }); - - return found; -} -#endif // __CLIENT__ - -typedef QMap Impulses; // ID lookup. -static Impulses impulses; - -typedef QMap ImpulseNameMap; // Name lookup -static ImpulseNameMap impulsesByName; - -typedef QMap ImpulseAccumulators; // ImpulseId lookup. -static ImpulseAccumulators accumulators[DDMAXPLAYERS]; - -static PlayerImpulse *addImpulse(int id, impulsetype_t type, String name, String bindContextName) -{ - auto *imp = new PlayerImpulse; - - imp->id = id; - imp->type = type; - imp->name = name; - imp->bindContextName = bindContextName; - - impulses.insert(imp->id, imp); - impulsesByName.insert(imp->name.toLower(), imp); - - // Generate impulse accumulators for each player. - for(int i = 0; i < DDMAXPLAYERS; ++i) - { - ImpulseAccumulator::AccumulatorType accumType = (type == IT_BINARY? ImpulseAccumulator::Binary : ImpulseAccumulator::Analog); - auto *accum = new ImpulseAccumulator(imp->id, accumType, type != IT_ANALOG); - accum->setPlayerNum(i); - accumulators[i].insert(accum->impulseId(), accum); - } - - return imp; -} - -static ImpulseAccumulator *accumulator(int impulseId, int playerNum) -{ - if(playerNum < 0 || playerNum >= DDMAXPLAYERS) - return nullptr; - - if(!accumulators[playerNum].contains(impulseId)) - return nullptr; - - return accumulators[playerNum][impulseId]; -} - -void P_ImpulseShutdown() -{ - for(int i = 0; i < DDMAXPLAYERS; ++i) - { - qDeleteAll(accumulators[i]); - accumulators[i].clear(); - } - qDeleteAll(impulses); - impulses.clear(); - impulsesByName.clear(); -} - -PlayerImpulse *P_ImpulsePtr(int id) -{ - auto found = impulses.find(id); - if(found != impulses.end()) return *found; - return nullptr; -} - -PlayerImpulse *P_ImpulseByName(String const &name) -{ - if(!name.isEmpty()) - { - auto found = impulsesByName.find(name.toLower()); - if(found != impulsesByName.end()) return *found; - } - return nullptr; -} - -/// @todo: Group impulses by binding context. -D_CMD(ListImpulses) -{ - DENG2_UNUSED3(argv, argc, src); - LOG_MSG(_E(b) "%i player impulses defined:") << impulses.count(); - - 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->type == IT_BINARY? "binary" : "analog") - << (IMPULSETYPE_IS_TRIGGERABLE(imp->type)? ", triggerable" : ""); - } - return true; -} - -D_CMD(Impulse) -{ - DENG2_UNUSED(src); - - if(argc < 2 || argc > 3) - { - LOG_SCR_NOTE("Usage:\n %s (impulse-name)\n %s (impulse-name) (player-ordinal)") - << argv[0] << argv[0]; - return true; - } - - if(PlayerImpulse *imp = P_ImpulseByName(argv[1])) - { - int const playerNum = (argc == 3? String(argv[2]).toInt() : 0); - if(ImpulseAccumulator *accum = accumulator(imp->id, playerNum)) - { - accum->receiveBinary(); - } - } - - return true; -} - -#ifdef __CLIENT__ -D_CMD(ClearImpulseAccumulation) -{ - DENG2_UNUSED3(argv, argc, src); - - ClientApp::inputSystem().forAllDevices([] (InputDevice &device) - { - device.reset(); - return LoopContinue; - }); - - // For all players. - for(int i = 0; i < DDMAXPLAYERS; ++i) - for(ImpulseAccumulator *accum : accumulators[i]) - { - accum->clearAll(); - } - - return true; -} -#endif - -void P_ImpulseConsoleRegister() -{ - C_CMD("listcontrols", "", ListImpulses); - C_CMD("impulse", nullptr, Impulse); - -#ifdef __CLIENT__ - C_CMD("resetctlaccum", "", ClearImpulseAccumulation); - - ImpulseAccumulator::consoleRegister(); -#endif -} - -DENG_EXTERN_C void P_NewPlayerControl(int id, impulsetype_t type, char const *name, - char const *bindContextName) -{ - DENG2_ASSERT(name && bindContextName); - LOG_AS("P_NewPlayerControl"); - - // Ensure the given id is unique. - 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; - 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; - return; - } - - addImpulse(id, type, name, bindContextName); -} - -DENG_EXTERN_C int P_IsControlBound(int playerNum, int impulseId) -{ -#ifdef __CLIENT__ - LOG_AS("P_IsControlBound"); - - // Impulse bindings are associated with local player numbers rather than - // the player console number - translate. - int const localPlayer = P_ConsoleToLocal(playerNum); - if(localPlayer < 0) return false; - - if(PlayerImpulse const *imp = P_ImpulsePtr(impulseId)) - { - return imp->haveBindingsFor(localPlayer); - } - return false; - -#else - DENG2_UNUSED2(playerNum, impulseId); - return 0; -#endif -} - -DENG_EXTERN_C void P_GetControlState(int playerNum, int impulseId, float *pos, - float *relativeOffset) -{ -#ifdef __CLIENT__ - // Ignore NULLs. - float tmp; - if(!pos) pos = &tmp; - if(!relativeOffset) relativeOffset = &tmp; - - *pos = 0; - *relativeOffset = 0; - - if(ImpulseAccumulator *accum = accumulator(impulseId, playerNum)) - { - accum->takeAnalog(pos, relativeOffset); - } -#else - DENG2_UNUSED4(playerNum, impulseId, pos, relativeOffset); -#endif -} - -DENG_EXTERN_C int P_GetImpulseControlState(int playerNum, int impulseId) -{ - LOG_AS("P_GetImpulseControlState"); - - ImpulseAccumulator *accum = accumulator(impulseId, playerNum); - if(!accum) return 0; - - // Ensure this is really a binary accumulator. - if(accum->type() != ImpulseAccumulator::Binary) - { - LOG_INPUT_WARNING("ImpulseAccumulator '%s' is not binary") << impulses[impulseId]->name; - return 0; - } - - return accum->takeBinary(); -} - -DENG_EXTERN_C void P_Impulse(int playerNum, int impulseId) -{ - LOG_AS("P_Impulse"); - - ImpulseAccumulator *accum = accumulator(impulseId, playerNum); - if(!accum) return; - - // Ensure this is really a binary accumulator. - if(accum->type() != ImpulseAccumulator::Binary) - { - LOG_INPUT_WARNING("ImpulseAccumulator '%s' is not binary") << impulses[impulseId]->name; - return; - } - - accum->receiveBinary(); -} diff --git a/doomsday/client/src/world/impulseaccumulator.cpp b/doomsday/client/src/world/impulseaccumulator.cpp new file mode 100644 index 0000000000..254e95bc09 --- /dev/null +++ b/doomsday/client/src/world/impulseaccumulator.cpp @@ -0,0 +1,281 @@ +/** @file impulseaccumulator.cpp Player impulse accumulation. + * + * @authors Copyright © 2003-2013 Jaakko Keränen + * @authors Copyright © 2006-2014 Daniel Swanson + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#include "world/impulseaccumulator.h" + +#include +#include +#ifdef __CLIENT__ +# include "clientapp.h" +#endif + +#include "world/p_players.h" + +#ifdef __CLIENT__ +# include "BindContext" +# include "ui/b_util.h" +#endif + +using namespace de; + +#ifdef __CLIENT__ +static inline InputSystem &inputSys() +{ + return ClientApp::inputSystem(); +} +#endif + +#ifdef __CLIENT__ +static int pimpDoubleClickThreshold = 300; ///< Milliseconds, cvar +#endif + +DENG2_PIMPL_NOREF(ImpulseAccumulator) +{ + int impulseId = 0; + AccumulatorType type = Analog; + bool expireBeforeSharpTick = false; + + int playerNum = 0; + + short binaryAccum = 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 + * analog impulses. + */ + 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. */ + } db; + + /** + * Track the double-click state of the impulse and generate a bindable + * symbolic event if the trigger conditions are met. + * + * @param pos State of the impulse. + */ + void maintainDoubleClick(float pos) + { + 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 potential activation! + uint const threshold = uint( de::max(0, pimpDoubleClickThreshold) ); + uint const nowTime = Timer_RealMilliseconds(); + + if(newState == db.previousClickState && nowTime - db.previousClickTime < threshold) + { + db.triggered = true; + + PlayerImpulse *impulse = P_PlayerImpulsePtr(impulseId); + DENG2_ASSERT(impulse); + + // 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 += impulse->name; + + int const localPlayer = P_ConsoleToLocal(playerNum); + DENG2_ASSERT(localPlayer >= 0); + LOG_INPUT_XVERBOSE("Triggered " _E(b) "'%s'" _E(.) " for player%i state: %i threshold: %i\n %s") + << impulse->name << (localPlayer + 1) << newState << (nowTime - db.previousClickTime) + << symbolicName; + + Block symbolicNameUtf8 = symbolicName.toUtf8(); + ddevent_t ev; de::zap(ev); + ev.device = uint(-1); + ev.type = E_SYMBOLIC; + ev.symbolic.id = playerNum; + ev.symbolic.name = symbolicNameUtf8.constData(); + + inputSys().postEvent(&ev); // makes a copy. + } + + db.previousClickTime = nowTime; + db.previousClickState = newState; + db.lastState = newState; + } + + void clearDoubleClick() + { + db.triggered = false; + } +#endif +}; + +ImpulseAccumulator::ImpulseAccumulator(int impulseId, AccumulatorType type, bool expireBeforeSharpTick) + : d(new Instance) +{ + d->impulseId = impulseId; + d->type = type; + d->expireBeforeSharpTick = expireBeforeSharpTick; +} + +void ImpulseAccumulator::setPlayerNum(int newPlayerNum) +{ + d->playerNum = newPlayerNum; +} + +int ImpulseAccumulator::impulseId() const +{ + return d->impulseId; +} + +ImpulseAccumulator::AccumulatorType ImpulseAccumulator::type() const +{ + return d->type; +} + +bool ImpulseAccumulator::expireBeforeSharpTick() const +{ + return d->expireBeforeSharpTick; +} + +void ImpulseAccumulator::receiveBinary() +{ + // Ensure this is really a binary accumulator. + DENG2_ASSERT(d->type == Binary); + LOG_AS("ImpulseAccumulator"); + + d->binaryAccum++; + +#ifdef __CLIENT__ + // Mark for double click. + d->maintainDoubleClick(1); + d->maintainDoubleClick(0); +#endif +} + +int ImpulseAccumulator::takeBinary() +{ + // Ensure this is really a binary accumulator. + DENG2_ASSERT(d->type == Binary); + LOG_AS("ImpulseAccumulator"); + short *counter = &d->binaryAccum; + int count = *counter; + *counter = 0; + return count; +} + +#ifdef __CLIENT__ + +void ImpulseAccumulator::takeAnalog(float *pos, float *relOffset) +{ + // Ensure this is really an analog accumulator. + DENG2_ASSERT(d->type == Analog); + LOG_AS("ImpulseAccumulator"); + + if(pos) *pos = 0; + if(relOffset) *relOffset = 0; + + PlayerImpulse *impulse = P_PlayerImpulsePtr(d->impulseId); + DENG2_ASSERT(impulse); + + if(BindContext *bindContext = inputSys().contextPtr(impulse->bindContextName)) + { + // Impulse bindings are associated with local player numbers rather than + // the player console number - translate. + float position, relative; + B_EvaluateImpulseBindings(bindContext, P_ConsoleToLocal(d->playerNum), d->impulseId, + &position, &relative, d->expireBeforeSharpTick); + + // Mark for double-clicks. + d->maintainDoubleClick(position); + + if(pos) *pos = position; + if(relOffset) *relOffset = relative; + } +} + +void ImpulseAccumulator::clearAll() +{ + LOG_AS("ImpulseAccumulator"); + switch(d->type) + { + case Analog: + if(!d->expireBeforeSharpTick) + { + takeAnalog(); + } + break; + + case Binary: + takeBinary(); + break; + + default: DENG2_ASSERT(!"ImpulseAccumulator::clearAll: Unknown type"); + } + + // Also clear the double click state. + d->clearDoubleClick(); +} + +void ImpulseAccumulator::consoleRegister() // static +{ + LOG_AS("ImpulseAccumulator"); + C_VAR_INT("input-doubleclick-threshold", &pimpDoubleClickThreshold, 0, 0, 2000); +} + +#endif // __CLIENT__ diff --git a/doomsday/client/src/world/p_players.cpp b/doomsday/client/src/world/p_players.cpp index 8cfcc6276b..4d89d3db63 100644 --- a/doomsday/client/src/world/p_players.cpp +++ b/doomsday/client/src/world/p_players.cpp @@ -19,12 +19,27 @@ #define DENG_NO_API_MACROS_PLAYER -#include "de_base.h" -#include "de_play.h" -#include "de_network.h" +#include "world/p_players.h" +#include +#include +#include +#include +#ifdef __CLIENT__ +# include "clientapp.h" +#endif + +#include "world/impulseaccumulator.h" #include "world/map.h" +#include "world/p_object.h" #include "SectorCluster" +#include "Surface" + +#ifdef __CLIENT__ +# include "BindContext" +# include "ui/b_util.h" +# include "ui/inputdevice.h" +#endif using namespace de; @@ -33,6 +48,57 @@ player_t ddPlayers[DDMAXPLAYERS]; int consolePlayer; int displayPlayer; +typedef QMap Impulses; // ID lookup. +static Impulses impulses; + +typedef QMap ImpulseNameMap; // Name lookup +static ImpulseNameMap impulsesByName; + +typedef QMap ImpulseAccumulators; // ImpulseId lookup. +static ImpulseAccumulators accumulators[DDMAXPLAYERS]; + +#ifdef __CLIENT__ +static inline InputSystem &inputSys() +{ + return ClientApp::inputSystem(); +} +#endif + +static PlayerImpulse *addImpulse(int id, impulsetype_t type, String name, String bindContextName) +{ + auto *imp = new PlayerImpulse; + + imp->id = id; + imp->type = type; + imp->name = name; + imp->bindContextName = bindContextName; + + impulses.insert(imp->id, imp); + impulsesByName.insert(imp->name.toLower(), imp); + + // Generate impulse accumulators for each player. + for(int i = 0; i < DDMAXPLAYERS; ++i) + { + ImpulseAccumulator::AccumulatorType accumType = (type == IT_BINARY? ImpulseAccumulator::Binary : ImpulseAccumulator::Analog); + auto *accum = new ImpulseAccumulator(imp->id, accumType, type != IT_ANALOG); + accum->setPlayerNum(i); + accumulators[i].insert(accum->impulseId(), accum); + } + + return imp; +} + +static ImpulseAccumulator *accumulator(int impulseId, int playerNum) +{ + if(playerNum < 0 || playerNum >= DDMAXPLAYERS) + return nullptr; + + if(!accumulators[playerNum].contains(impulseId)) + return nullptr; + + return accumulators[playerNum][impulseId]; +} + /** * Determine which console is used by the given local player. Local players * are numbered starting from zero. @@ -175,6 +241,108 @@ float P_ShortToLookDir(short s) return s / float( DDMAXSHORT ) * 110.f; } +void P_ClearPlayerImpulses() +{ + for(int i = 0; i < DDMAXPLAYERS; ++i) + { + qDeleteAll(accumulators[i]); + accumulators[i].clear(); + } + qDeleteAll(impulses); + impulses.clear(); + impulsesByName.clear(); +} + +PlayerImpulse *P_PlayerImpulsePtr(int id) +{ + auto found = impulses.find(id); + if(found != impulses.end()) return *found; + return nullptr; +} + +PlayerImpulse *P_PlayerImpulseByName(String const &name) +{ + if(!name.isEmpty()) + { + auto found = impulsesByName.find(name.toLower()); + if(found != impulsesByName.end()) return *found; + } + return nullptr; +} + +/// @todo: Group impulses by binding context. +D_CMD(ListImpulses) +{ + DENG2_UNUSED3(argv, argc, src); + LOG_MSG(_E(b) "%i player impulses defined:") << impulses.count(); + + 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->type == IT_BINARY? "binary" : "analog") + << (IMPULSETYPE_IS_TRIGGERABLE(imp->type)? ", triggerable" : ""); + } + return true; +} + +D_CMD(Impulse) +{ + DENG2_UNUSED(src); + + if(argc < 2 || argc > 3) + { + LOG_SCR_NOTE("Usage:\n %s (impulse-name)\n %s (impulse-name) (player-ordinal)") + << argv[0] << argv[0]; + return true; + } + + if(PlayerImpulse *imp = P_PlayerImpulseByName(argv[1])) + { + int const playerNum = (argc == 3? String(argv[2]).toInt() : 0); + if(ImpulseAccumulator *accum = accumulator(imp->id, playerNum)) + { + accum->receiveBinary(); + } + } + + return true; +} + +#ifdef __CLIENT__ +D_CMD(ClearImpulseAccumulation) +{ + DENG2_UNUSED3(argv, argc, src); + + ClientApp::inputSystem().forAllDevices([] (InputDevice &device) + { + device.reset(); + return LoopContinue; + }); + + // For all players. + for(int i = 0; i < DDMAXPLAYERS; ++i) + for(ImpulseAccumulator *accum : accumulators[i]) + { + accum->clearAll(); + } + + return true; +} +#endif + +void P_ConsoleRegister() +{ + C_CMD("listcontrols", "", ListImpulses); + C_CMD("impulse", nullptr, Impulse); + +#ifdef __CLIENT__ + C_CMD("resetctlaccum", "", ClearImpulseAccumulation); + + ImpulseAccumulator::consoleRegister(); +#endif +} + #undef DD_GetPlayer DENG_EXTERN_C ddplayer_t *DD_GetPlayer(int number) { @@ -186,12 +354,131 @@ 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); -// 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); -DENG_EXTERN_C int P_GetImpulseControlState(int playerNum, int control); -DENG_EXTERN_C void P_Impulse(int playerNum, int control); +DENG_EXTERN_C void P_NewPlayerControl(int id, impulsetype_t type, char const *name, + char const *bindContextName) +{ + DENG2_ASSERT(name && bindContextName); + LOG_AS("P_NewPlayerControl"); + + // Ensure the given id is unique. + if(PlayerImpulse const *existing = P_PlayerImpulsePtr(id)) + { + LOG_INPUT_WARNING("Id: %i is already in use by impulse '%s' - Won't replace") + << id << existing->name; + return; + } + // Ensure the given name is unique. + if(PlayerImpulse const *existing = P_PlayerImpulseByName(name)) + { + LOG_INPUT_WARNING("Name: '%s' is already in use by impulse Id: %i - Won't replace") + << name << existing->id; + return; + } + + addImpulse(id, type, name, bindContextName); +} + +DENG_EXTERN_C int P_IsControlBound(int playerNum, int impulseId) +{ +#ifdef __CLIENT__ + LOG_AS("P_IsControlBound"); + + // Impulse bindings are associated with local player numbers rather than + // the player console number - translate. + int const localPlayer = P_ConsoleToLocal(playerNum); + if(localPlayer < 0) return false; + + if(PlayerImpulse const *imp = P_PlayerImpulsePtr(impulseId)) + { + InputSystem &isys = ClientApp::inputSystem(); + + BindContext *bindContext = isys.contextPtr(imp->bindContextName); + if(!bindContext) + { + LOGDEV_INPUT_WARNING("Unknown binding context '%s'") << imp->bindContextName; + return false; + } + + int found = bindContext->forAllImpulseBindings(localPlayer, [&isys, &impulseId] (Record &rec) + { + ImpulseBinding bind(rec); + // Wrong impulse? + if(bind.geti("impulseId") != impulseId) return LoopContinue; + + if(InputDevice const *device = isys.devicePtr(bind.geti("deviceId"))) + { + if(device->isActive()) + { + return LoopAbort; // found a binding. + } + } + return LoopContinue; + }); + + return (found? 1 : 0); + } + return false; + +#else + DENG2_UNUSED2(playerNum, impulseId); + return 0; +#endif +} + +DENG_EXTERN_C void P_GetControlState(int playerNum, int impulseId, float *pos, + float *relativeOffset) +{ +#ifdef __CLIENT__ + // Ignore NULLs. + float tmp; + if(!pos) pos = &tmp; + if(!relativeOffset) relativeOffset = &tmp; + + *pos = 0; + *relativeOffset = 0; + + if(ImpulseAccumulator *accum = accumulator(impulseId, playerNum)) + { + accum->takeAnalog(pos, relativeOffset); + } +#else + DENG2_UNUSED4(playerNum, impulseId, pos, relativeOffset); +#endif +} + +DENG_EXTERN_C int P_GetImpulseControlState(int playerNum, int impulseId) +{ + LOG_AS("P_GetImpulseControlState"); + + ImpulseAccumulator *accum = accumulator(impulseId, playerNum); + if(!accum) return 0; + + // Ensure this is really a binary accumulator. + if(accum->type() != ImpulseAccumulator::Binary) + { + LOG_INPUT_WARNING("ImpulseAccumulator '%s' is not binary") << impulses[impulseId]->name; + return 0; + } + + return accum->takeBinary(); +} + +DENG_EXTERN_C void P_Impulse(int playerNum, int impulseId) +{ + LOG_AS("P_Impulse"); + + ImpulseAccumulator *accum = accumulator(impulseId, playerNum); + if(!accum) return; + + // Ensure this is really a binary accumulator. + if(accum->type() != ImpulseAccumulator::Binary) + { + LOG_INPUT_WARNING("ImpulseAccumulator '%s' is not binary") << impulses[impulseId]->name; + return; + } + + accum->receiveBinary(); +} DENG_DECLARE_API(Player) = { diff --git a/doomsday/server/server.pro b/doomsday/server/server.pro index 99bb962a00..2d992cc835 100644 --- a/doomsday/server/server.pro +++ b/doomsday/server/server.pro @@ -208,7 +208,6 @@ DENG_HEADERS += \ $$SRC/include/ui/infine/finalepagewidget.h \ $$SRC/include/ui/infine/finaletextwidget.h \ $$SRC/include/ui/infine/finalewidget.h \ - $$SRC/include/ui/playerimpulse.h \ $$SRC/include/world/dmuargs.h \ $$SRC/include/world/blockmap.h \ $$SRC/include/world/bsp/convexsubspaceproxy.h \ @@ -223,6 +222,7 @@ DENG_HEADERS += \ $$SRC/include/world/convexsubspace.h \ $$SRC/include/world/entitydatabase.h \ $$SRC/include/world/entitydef.h \ + $$SRC/include/world/impulseaccumulator.h \ $$SRC/include/world/interceptor.h \ $$SRC/include/world/line.h \ $$SRC/include/world/lineblockmap.h \ @@ -359,7 +359,6 @@ SOURCES += \ $$SRC/src/ui/infine/finaletextwidget.cpp \ $$SRC/src/ui/infine/finalewidget.cpp \ $$SRC/src/ui/infine/infinesystem.cpp \ - $$SRC/src/ui/playerimpulse.cpp \ $$SRC/src/world/api_map.cpp \ $$SRC/src/world/api_mapedit.cpp \ $$SRC/src/world/blockmap.cpp \ @@ -375,6 +374,7 @@ SOURCES += \ $$SRC/src/world/dmuargs.cpp \ $$SRC/src/world/entitydatabase.cpp \ $$SRC/src/world/entitydef.cpp \ + $$SRC/src/world/impulseaccumulator.cpp \ $$SRC/src/world/interceptor.cpp \ $$SRC/src/world/line.cpp \ $$SRC/src/world/lineblockmap.cpp \