Skip to content

Instantly share code, notes, and snippets.

@daftmugi
Last active October 30, 2023 17:01
Show Gist options
  • Save daftmugi/41d0324107e8734f364bb3e50ff00794 to your computer and use it in GitHub Desktop.
Save daftmugi/41d0324107e8734f364bb3e50ff00794 to your computer and use it in GitHub Desktop.
Patch: Add Hold Frob for Alternate Interaction
diff --git game/Grabber.cpp game/Grabber.cpp
index b3c08eb..c70baf8 100755
--- game/Grabber.cpp
+++ game/Grabber.cpp
@@ -1612,10 +1612,48 @@ bool CGrabber::ToggleEquip( void )
return rc;
}
+bool CGrabber::EquipFrobEntity( idPlayer *player )
+{
+ idEntity* frobEnt = player->m_FrobEntity.GetEntity();
+
+ // If attachment, such as head, get its body.
+ idEntity* ent = (frobEnt && frobEnt->IsType(idAFAttachment::Type))
+ ? static_cast<idAFAttachment*>(frobEnt)->GetBindMaster()
+ : frobEnt;
+
+ if (!(ent && ent->spawnArgs.GetBool("equippable")))
+ return false;
+
+ if (EquipEntity(ent))
+ {
+ if (ent->IsType(idAFEntity_Base::Type))
+ {
+ // If the body ent frob state is not set to 'false' after shouldering,
+ // the body will be stuck in a highlighted state after dropping it
+ // until the player highlights the body again. So, ensure 'false'.
+ ent->SetFrobbed(false);
+ }
+ else
+ {
+ // Unless shouldering a body, make sure nothing is equipped.
+ Forget(ent);
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
bool CGrabber::Equip( void )
{
- idStr str;
idEntity *ent = m_dragEnt.GetEntity();
+ return EquipEntity(ent);
+}
+
+bool CGrabber::EquipEntity( idEntity *ent )
+{
+ idStr str;
if( !ent || !ent->spawnArgs.GetBool("equippable") )
return false;
diff --git game/Grabber.h game/Grabber.h
index c09bcdd..b68aeba 100755
--- game/Grabber.h
+++ game/Grabber.h
@@ -147,9 +147,16 @@ public:
**/
bool ToggleEquip( void );
/**
+ * Daft Mugi #6316
+ * Try to Use/Equip frob-highlighted item without holding it.
+ * Examples: bodies, candles, lanterns, and food
+ **/
+ bool EquipFrobEntity( idPlayer *player );
+ /**
* Actual functions for equipping and dequipping
**/
bool Equip( void );
+ bool EquipEntity( idEntity *ent );
bool Dequip( void );
/**
diff --git game/Player.cpp game/Player.cpp
index 305b900..b0b2ee3 100644
--- game/Player.cpp
+++ game/Player.cpp
@@ -706,6 +706,12 @@ idPlayer::idPlayer() :
multiloot = false;
multiloot_lastfrob = 0;
+ // Daft Mugi #6316
+ holdFrobEntity = NULL;
+ holdFrobDraggedBodyEntity = NULL;
+ holdFrobStartTime = 0;
+ holdFrobStartViewAxis.Zero();
+
// greebo: Initialise the frob trace contact material to avoid
// crashing during map save when nothing has been frobbed yet
memset(&m_FrobTrace, 0, sizeof(trace_t));
@@ -2423,6 +2429,13 @@ void idPlayer::Restore( idRestoreGame *savefile ) {
multiloot = false;
multiloot_lastfrob = 0;
+ // Daft Mugi #6316: Hold Frob for alternate interaction
+ // The hold frob values don't get saved, but reset on load.
+ holdFrobEntity = NULL;
+ holdFrobDraggedBodyEntity = NULL;
+ holdFrobStartTime = 0;
+ holdFrobStartViewAxis.Zero();
+
savefile->ReadInt( buttonMask );
savefile->ReadInt( oldButtons );
savefile->ReadInt( oldFlags );
@@ -6021,13 +6034,7 @@ void idPlayer::PerformKeyRepeat(int impulse, int holdTime)
case IMPULSE_INVENTORY_USE: // Inventory Use Item
{
- const CInventoryCursorPtr& crsr = InventoryCursor();
- CInventoryItemPtr it = crsr->GetCurrentItem();
-
- if (it != NULL && it->GetType() != CInventoryItem::IT_DUMMY)
- {
- UseInventoryItem(ERepeat, it, holdTime, false);
- }
+ InventoryUseKeyRepeat(holdTime);
}
break;
}
@@ -8818,6 +8825,56 @@ void idPlayer::SetInfluenceFov( float fov ) {
influenceFov = fov;
}
+/*
+================
+idPlayer::IsHoldFrobEnabled
+================
+*/
+bool idPlayer::IsHoldFrobEnabled( void )
+{
+ // Hold-frob delay of 0 matches TDM v2.11 (and prior) behavior
+ return cv_holdfrob_delay.GetInteger() > 0;
+}
+
+/*
+================
+idPlayer::CanHoldFrobAction
+================
+*/
+bool idPlayer::CanHoldFrobAction( void )
+{
+ int delay = cv_holdfrob_delay.GetInteger();
+ // Is hold frob enabled and has enough time elapsed?
+ return (delay > 0)
+ && (gameLocal.time - holdFrobStartTime >= delay);
+}
+
+/*
+================
+idPlayer::SetHoldFrobView
+================
+*/
+void idPlayer::SetHoldFrobView( void )
+{
+ holdFrobStartViewAxis = renderView->viewaxis;
+}
+
+/*
+================
+idPlayer::HoldFrobViewDistance
+================
+*/
+float idPlayer::HoldFrobViewDistance( void )
+{
+ // Relative to the player's view axis (x,y,z = forward,left,up).
+ // Use a point in front of the player.
+ idVec3 frobStartView = idVec3(100, 0, 0) * holdFrobStartViewAxis;
+ idVec3 frobCurrentView = idVec3(100, 0, 0) * renderView->viewaxis;
+ float holdFrobDistance = (frobCurrentView - frobStartView).Length();
+
+ return holdFrobDistance;
+}
+
/*
================
idPlayer::OnLadder
@@ -9893,6 +9950,17 @@ float idPlayer::GetMovementVolMod( void )
return returnval;
}
+void idPlayer::InventoryUseKeyRepeat(int holdTime)
+{
+ const CInventoryCursorPtr& crsr = InventoryCursor();
+ CInventoryItemPtr it = crsr->GetCurrentItem();
+
+ if (it != NULL && it->GetType() != CInventoryItem::IT_DUMMY)
+ {
+ UseInventoryItem(ERepeat, it, holdTime, false);
+ }
+}
+
void idPlayer::InventoryUseKeyRelease(int holdTime)
{
const CInventoryCursorPtr& crsr = InventoryCursor();
@@ -11457,7 +11525,7 @@ void idPlayer::PerformFrob(EImpulseState impulseState, idEntity* target, bool al
if ( (GetImmobilization() & EIM_FROB_COMPLEX) && !target->m_bFrobSimple )
{
// TODO: Rename this "uh-uh" sound to something more general?
- StartSound( "snd_drop_item_failed", SND_CHANNEL_ITEM, 0, false, NULL );
+ StartSound( "snd_drop_item_failed", SND_CHANNEL_ITEM, 0, false, NULL );
return;
}
@@ -11465,7 +11533,7 @@ void idPlayer::PerformFrob(EImpulseState impulseState, idEntity* target, bool al
// Retrieve the entity before trying to add it to the inventory, the pointer
// might be cleared after calling AddToInventory().
idEntity* highlightedEntity = m_FrobEntity.GetEntity();
-
+
if (impulseState == EPressed)
{
// Fire the STIM_FROB response on key down (if defined) on this entity
@@ -11503,10 +11571,9 @@ void idPlayer::PerformFrob(EImpulseState impulseState, idEntity* target, bool al
// Inventory item could not be used with the highlighted entity, proceed with ordinary frob action
- // These actions are only applicable for EPressed buttonstate
+ // Try to add world item to inventory
if (impulseState == EPressed || ((impulseState == ERepeat) && multiloot))
{
-
// First we have to check whether that entity is an inventory
// item. In that case, we have to add it to the inventory and
// hide the entity.
@@ -11548,63 +11615,163 @@ void idPlayer::PerformFrob(EImpulseState impulseState, idEntity* target, bool al
// grayman #3011 - is anything sitting on this inventory item?
target->ActivateContacts();
+
+ // Item added to inventory, so skip other frob code
+ return;
}
- if (impulseState == EPressed)
+ }
+
+
+ // Item could not be added to inventory, so handle body and equip/use frob.
+
+ const bool grabableType = target->spawnArgs.GetBool("grabable", "1"); // allow override
+ const bool bodyType = grabableType
+ && (target->IsType(idAFEntity_Base::Type) || target->IsType(idAFAttachment::Type));
+ const bool moveableType = grabableType
+ && (target->IsType(idMoveable::Type) || target->IsType(idMoveableItem::Type));
+
+ // If an attachment, such as a head, get its body.
+ idEntity* bodyTarget = target->IsType(idAFAttachment::Type)
+ ? static_cast<idAFAttachment*>(target)->GetBindMaster()
+ : target;
+
+ const bool holdFrobBodyType = bodyType
+ && bodyTarget
+ && bodyTarget->spawnArgs.GetBool("shoulderable", "0")
+ && IsHoldFrobEnabled();
+ const bool holdFrobUsableType = moveableType
+ && target->spawnArgs.GetBool("equippable", "0")
+ && IsHoldFrobEnabled();
+
+ // Do not pick up live, conscious AI
+ if (target->IsType(idAI::Type))
+ {
+ idAI* AItarget = static_cast<idAI*>(target);
+ if ((AItarget->health > 0) && !AItarget->IsKnockedOut())
+ return;
+ }
+
+ // Daft Mugi #6257: Auto-Search Bodies
+ if (bodyType && cv_tdm_autosearch_bodies.GetBool())
+ {
+ // delay > 0 and shoulderable, hold-frob behavior (on EReleased)
+ bool isHoldFrob = holdFrobBodyType && impulseState == EReleased;
+ // delay > 0 and non-shoulderable, regular behavior (on EPressed)
+ // delay == 0, TDM v2.11 (and prior) regular behavior (on EPressed)
+ bool isRegular = !holdFrobBodyType && impulseState == EPressed;
+
+ if (isHoldFrob || isRegular)
{
- // Grab it if it's a grabable class
- if (target->IsType(idMoveable::Type) || target->IsType(idAFEntity_Base::Type) ||
- target->IsType(idMoveableItem::Type) || target->IsType(idAFAttachment::Type))
+ // If looted body this time, do not shoulder/pick up body.
+ // NOTE: The body being frobbed might not be an idAI.
+ if (bodyTarget
+ && bodyTarget->IsType(idAFEntity_Base::Type)
+ && bodyTarget->AddAttachmentsToInventory(this))
{
- // allow override of default grabbing behavior
- if (!target->spawnArgs.GetBool("grabable", "1"))
- {
- return;
- }
-
- // Do not pick up live, conscious AI
- if (target->IsType(idAI::Type))
- {
- idAI* AItarget = static_cast<idAI*>(target);
- if ((AItarget->health > 0) && !AItarget->IsKnockedOut())
- {
- return;
- }
- }
+ return;
+ }
+ }
+ }
- if (cv_tdm_autosearch_bodies.GetBool())
- {
- // If attachment, such as head, get its body.
- idEntity* body = target->IsType(idAFAttachment::Type) ?
- static_cast<idAFAttachment*>(target)->GetBindMaster() :
- target;
+ if (impulseState == EPressed)
+ {
+ if (holdFrobBodyType || holdFrobUsableType)
+ {
+ // Store frobbed entity and start time tracking.
+ holdFrobEntity = highlightedEntity;
+ holdFrobDraggedBodyEntity = NULL;
+ holdFrobStartTime = gameLocal.time;
+ SetHoldFrobView();
+ return;
+ }
+ }
- // NOTE: The body being looted might not be an idAI.
- if (body && body->IsType(idAFEntity_Base::Type))
- {
- // Daft Mugi #6257
- // If looted body this time, do not pick up.
- if (body->AddAttachmentsToInventory(this))
- return;
- }
- }
+ if (impulseState == ERepeat && holdFrobEntity.GetEntity() == highlightedEntity)
+ {
+ if (holdFrobBodyType)
+ {
+ // Drag body if enough time has passed or view has moved outside of bounds.
+ if (CanHoldFrobAction()
+ || (HoldFrobViewDistance() > cv_holdfrob_bounds.GetFloat()))
+ {
+ gameLocal.m_Grabber->Update(this, false, true);
+ holdFrobEntity = NULL;
+ // Store grabber entity of dragged body, so the body can be released later.
+ holdFrobDraggedBodyEntity = gameLocal.m_Grabber->GetSelected();
+ return;
+ }
+ }
- gameLocal.m_Grabber->Update(this, false, true); // preservePosition = true #4149
+ if (holdFrobUsableType)
+ {
+ // Equip/Use, if enough time has passed.
+ if (CanHoldFrobAction())
+ {
+ gameLocal.m_Grabber->EquipFrobEntity(this);
+ holdFrobEntity = NULL;
+ return;
}
}
}
+
+ if (impulseState == EReleased && holdFrobEntity.GetEntity() == highlightedEntity)
+ {
+ if (holdFrobBodyType)
+ {
+ // Shoulder/pick up body
+ gameLocal.m_Grabber->EquipFrobEntity(this);
+ holdFrobEntity = NULL;
+ return;
+ }
+
+ if (holdFrobUsableType)
+ {
+ // Pick up, since it was not equipped/used (or toggled on/off).
+ gameLocal.m_Grabber->Update(this, false, true);
+ holdFrobEntity = NULL;
+ return;
+ }
+ }
+
+
+ // Item could not be added to inventory, so try to pick it up.
+
+ if (impulseState == EPressed && (moveableType || bodyType))
+ {
+ // Grab it if it's a grabable class and not overridden
+ gameLocal.m_Grabber->Update(this, false, true); // preservePosition = true #4149
+ return;
+ }
}
void idPlayer::PerformFrob()
{
+ // Initialize/reset hold frob
+ holdFrobEntity = NULL;
+ holdFrobDraggedBodyEntity = NULL;
+
// Ignore frobs if player-frobbing is immobilized.
if ( GetImmobilization() & EIM_FROB )
{
return;
}
- // if the grabber is currently holding something and frob is pressed,
+ idEntity* grabberEnt = gameLocal.m_Grabber->GetSelected();
+
+ // If holding an equippable item, begin tracking frob for later
+ // equip/use or drop.
+ if (IsHoldFrobEnabled()
+ && grabberEnt
+ && grabberEnt->spawnArgs.GetBool("equippable", "0"))
+ {
+ holdFrobEntity = grabberEnt;
+ holdFrobStartTime = gameLocal.time;
+ return;
+ }
+
+ // If the grabber is currently holding something and frob is pressed,
// release it. Do not frob anything new since you're holding an item.
- if ( gameLocal.m_Grabber->GetSelected() )
+ if (grabberEnt)
{
gameLocal.m_Grabber->Update( this );
return;
@@ -11613,6 +11780,16 @@ void idPlayer::PerformFrob()
// Get the currently frobbed entity
idEntity* frob = m_FrobEntity.GetEntity();
+ // If there is nothing highlighted and shouldering a body,
+ // drop the body.
+ if (IsHoldFrobEnabled()
+ && !frob
+ && IsShoulderingBody())
+ {
+ gameLocal.m_Grabber->Dequip();
+ return;
+ }
+
// Relay the function to the specialised method
PerformFrob(EPressed, frob, true);
}
@@ -11625,6 +11802,18 @@ void idPlayer::PerformFrobKeyRepeat(int holdTime)
return;
}
+ idEntity* grabberEnt = gameLocal.m_Grabber->GetSelected();
+
+ // If holding an equippable item, use it if frob held long enough.
+ if (holdFrobEntity.GetEntity()
+ && holdFrobEntity.GetEntity() == grabberEnt
+ && CanHoldFrobAction())
+ {
+ gameLocal.m_Grabber->ToggleEquip();
+ holdFrobEntity = NULL;
+ return;
+ }
+
// Get the currently frobbed entity
idEntity* frob = m_FrobEntity.GetEntity();
@@ -11643,12 +11832,39 @@ void idPlayer::PerformFrobKeyRelease(int holdTime)
{
// Obsttorte: multilooting
multiloot = false;
+
// Ignore frobs if player-frobbing is immobilized.
if ( GetImmobilization() & EIM_FROB )
{
return;
}
+ idEntity* grabberEnt = gameLocal.m_Grabber->GetSelected();
+
+ if (IsHoldFrobEnabled())
+ {
+ idEntity* holdFrobEnt = holdFrobEntity.GetEntity();
+
+ // NOTE: When hold-frob drag body behavior is false, do not
+ // stop dragging. (Matches original TDM behavior)
+ if (holdFrobDraggedBodyEntity.GetEntity()
+ && cv_holdfrob_drag_body_behavior.GetBool())
+ {
+ holdFrobEnt = holdFrobDraggedBodyEntity.GetEntity();
+ }
+
+ // If currently dragging a body, stop dragging.
+ // If currently holding an equippable item, drop it.
+ // When hold-frob delay is 0, behavior matches TDM v2.11 (and prior).
+ if (holdFrobEnt && holdFrobEnt == grabberEnt)
+ {
+ gameLocal.m_Grabber->Update(this);
+ holdFrobDraggedBodyEntity = NULL;
+ holdFrobEntity = NULL;
+ return;
+ }
+ }
+
// Get the currently frobbed entity
idEntity* frob = m_FrobEntity.GetEntity();
diff --git game/Player.h game/Player.h
index b4f2fd5..8bafba6 100644
--- game/Player.h
+++ game/Player.h
@@ -827,6 +827,12 @@ public:
bool IsShoulderingBody( void ) { return m_bShoulderingBody; };
+ // Daft Mugi #6316: Hold Frob for alternate interaction
+ bool IsHoldFrobEnabled( void );
+ bool CanHoldFrobAction( void );
+ void SetHoldFrobView( void );
+ float HoldFrobViewDistance( void );
+
bool OnLadder( void ) const;
// Virtal override of idActor::OnElevator()
virtual CMultiStateMover* OnElevator(bool mustBeMoving) const;
@@ -871,6 +877,12 @@ public:
bool multiloot;
int multiloot_lastfrob;
+ // Daft Mugi #6316: Hold Frob for alternate interaction
+ idEntityPtr<idEntity> holdFrobEntity;
+ idEntityPtr<idEntity> holdFrobDraggedBodyEntity;
+ int holdFrobStartTime;
+ idMat3 holdFrobStartViewAxis;
+
// angua: Set ideal crouch state
void EvaluateCrouch();
@@ -946,6 +958,9 @@ public:
**/
bool DropToHands( idEntity *ent, CInventoryItemPtr item = CInventoryItemPtr() );
+ // Performs the inventory action for onButtonRepeat
+ void InventoryUseKeyRepeat(int holdTime);
+
// Performs the inventory action for onButtonRelease
void InventoryUseKeyRelease(int holdTime);
diff --git game/gamesys/SysCvar.cpp game/gamesys/SysCvar.cpp
index 0c174e5..d5984c5 100644
--- game/gamesys/SysCvar.cpp
+++ game/gamesys/SysCvar.cpp
@@ -344,9 +344,29 @@ idCVar cv_frobhelper_fadein_duration( "tdm_frobhelper_fadein_duration", "1500",
idCVar cv_frobhelper_fadeout_duration( "tdm_frobhelper_fadeout_duration", "500", CVAR_GAME | CVAR_ARCHIVE | CVAR_INTEGER | CVAR_NOCHEAT, "The FrobHelper cursor is faded out for this duration specified in ms.", 0.0f, 5000.0f);
idCVar cv_frobhelper_ignore_size( "tdm_frobhelper_ignore_size", "40.0", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT | CVAR_NOCHEAT, "The FrobHelper is not activated for entites that are bigger than this ignore size along one dimension. Set to 0, to disable ignoring entities.", 0.0f, 10000.0f);
+// Daft Mugi #6316: Hold Frob for alternate interaction
+idCVar cv_holdfrob_delay(
+ "tdm_holdfrob_delay", "300", CVAR_GAME | CVAR_ARCHIVE | CVAR_INTEGER | CVAR_NOCHEAT,
+ "The hold-frob delay (in ms) before drag body or use world item.\n"
+ "Set to 0 for original TDM behavior.",
+ 0, 2000
+);
+idCVar cv_holdfrob_bounds(
+ "tdm_holdfrob_bounds", "7", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT | CVAR_NOCHEAT,
+ "The view position must stay within the bounds in order to perform an interaction.",
+ 0.0f, 1000.0f
+);
+idCVar cv_holdfrob_drag_body_behavior(
+ "tdm_holdfrob_drag_body_behavior", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL | CVAR_NOCHEAT,
+ "Which drag body behavior?\n"
+ " 1 --- on first frob release (key up), let go of body.\n"
+ " 0 --- on second frob, let go of body. (original TDM behavior)"
+);
+
// Obsttorte: #5984 (multilooting)
idCVar cv_multiloot_min_interval("tdm_multiloot_min_interval", "300", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT | CVAR_NOCHEAT, "The minimum interval between two consecutive frobs when multifrobbing.");
idCVar cv_multiloot_max_interval("tdm_multiloot_max_interval", "2000", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT | CVAR_NOCHEAT, "The amount of time after which multilooting gets disabled again.");
+
// #4289
idCVar cv_pm_blackjack_indicate("tdm_blackjack_indicate", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL | CVAR_NOCHEAT, "Set to 1 to activate blackjack indicator.", 0, 1);
diff --git game/gamesys/SysCvar.h game/gamesys/SysCvar.h
index 73c51f4..5ec646a 100644
--- game/gamesys/SysCvar.h
+++ game/gamesys/SysCvar.h
@@ -280,6 +280,11 @@ extern idCVar cv_frobhelper_fadein_duration;
extern idCVar cv_frobhelper_fadeout_duration;
extern idCVar cv_frobhelper_ignore_size;
+// Daft Mugi #6316: Hold Frob for alternate interaction
+extern idCVar cv_holdfrob_delay;
+extern idCVar cv_holdfrob_bounds;
+extern idCVar cv_holdfrob_drag_body_behavior;
+
//Obsttorte: #5984 (multilooting)
extern idCVar cv_multiloot_min_interval;
extern idCVar cv_multiloot_max_interval;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment