Input and offhand coordination
This family lets an addon observe ROCK's sampled controller state and coordinate temporary ownership of hand input. It does not provide a second controller hook or a shared press/release queue.
getRawWandButtonStateV1 · slot 35
bool getRawWandButtonStateV1(
RockProviderHand hand,
std::uint32_t buttonId,
RockProviderRawWandButtonStateV1* outState);
Reads the most recent level state sampled by ROCK's OpenVR controller hook.
Accepts explicit Left/Right, an OpenVR button ID in [0, 63], and an output
with the exact current structure size.
Output:
available— ROCK has a usable sample for that hand/button;held— physical level after menu/rearm policy;sampleSequence— changes as new controller samples arrive;sampleAgeMilliseconds— freshness at query time;availabilityReason— available, not sampled, blocking menu, release-to-rearm, or invalid button.
Returns false only for malformed query arguments. A valid query can return
true with available == 0; inspect the reason.
The SDK intentionally does not publish symbolic OpenVR button IDs. Use the button enum already owned by your OpenVR integration, then pass its numeric ID.
Derive your own edges
ROCK consumes its internal edge queues during its own frame. The public API therefore exposes level plus sample sequence, not a competing edge consumer.
struct ButtonTracker
{
bool previousHeld{false};
std::uint64_t previousSample{0};
};
bool pressedThisSample(
ButtonTracker& tracker,
const RockProviderRawWandButtonStateV1& state)
{
const bool newSample = state.sampleSequence != tracker.previousSample;
const bool pressed = newSample && state.available != 0 &&
state.held != 0 && !tracker.previousHeld;
if (newSample) {
tracker.previousHeld = state.held != 0;
tracker.previousSample = state.sampleSequence;
}
return pressed;
}
Reset your tracker when available becomes false or the provider generation
changes. Do not synthesize an edge from a stale sample.
isNativePipboyInputSuppressedV1 · slot 36
bool isNativePipboyInputSuppressedV1();
Returns true while ROCK suppresses the Pip-Boy hand trigger's remaining native
game actions: flashlight hold during a ROCK interaction, or all native game
input while an applicable provider suppression lease is active.
Only treat the trigger as exclusively available to your addon while this is true. Otherwise a hold may still toggle the native flashlight.
When ROCK input remapping is enabled, the legacy trigger-release Pip-Boy open is moved to a short release of the native Pause button. This call does not report that remapping; it reports effective native suppression.
setHandInputSuppressionV1 · slot 27
RockProviderResultV1 setHandInputSuppressionV1(
std::uint64_t ownerToken,
const RockProviderHandInputSuppressionRequestV1* request);
Requires HandInputSuppression. Creates or refreshes the caller's lease for one
explicit hand. The request requires exact size, V1 version, at least one known
flag, non-zero leaseFrames, and optional matching generations.
Flags:
| Flag | Suppressed ROCK/native path |
|---|---|
SuppressNormalGrabPress | Normal ROCK grab press for that hand. |
SuppressGrabRelease | Normal ROCK release handling. |
SuppressHeldWeaponTriggerEquip | Trigger-driven equip from a held weapon. |
SuppressGameplayCandidates | Normal gameplay interaction candidates. |
SuppressOpenVrGameInput | Matching native/OpenVR game input. |
SuppressNativeVats | Native primary-wand Pause release that opens VATS. |
SuppressNativeVans | Native held Pause samples that begin V.A.N.S. after Bethesda's threshold. |
SuppressConfigModeChord | Convenience combination of the first four non-native behaviors. |
VATS release and V.A.N.S. hold are separate phases of one native primary-wand
path. Set both flags to consume the whole gesture. ROCK aggregates these two
flags across active right- and left-hand leases because the native action is
not independently hand-addressable. Gate them with
supportsNativeVatsVansInputSuppressionV1().
Multiple owners may publish suppression for a hand; effective flags are the OR of active leases. The per-family capacity is 32 owner/hand rows. The requested lease is clamped to the published maximum, currently 120 frames.
Returns standard validation, generation, owner, capability, and capacity
results. Ok means active or refreshed.
clearHandInputSuppressionV1 · slot 28
RockProviderResultV1 clearHandInputSuppressionV1(
std::uint64_t ownerToken,
RockProviderHand hand);
Requires HandInputSuppression. Pass Left or Right to clear one caller
lease, or None to clear both. It is idempotent for a valid owner/capability and
returns Ok even when no matching active row exists.
getHandInputSuppressionStateV1 · slot 76
RockProviderResultV1 getHandInputSuppressionStateV1(
std::uint64_t ownerToken,
RockProviderHand hand,
RockProviderHandInputSuppressionStateV1* outState);
Requires the separate InputObservability capability. Returns:
effectiveFlags— OR of every active owner's flags for the hand;callerFlags,callerLeaseActive, expiry, and remaining frames;- caller's latest invalidation reason retained in its row;
- current frame and world/skeleton/provider generations.
Use this to distinguish “my lease is active” from “the hand is suppressed by someone.” Invalidation reasons include expiry, generation change, unregister, provider loss, explicit clear, and callback fault.
setOffhandInteractionReservation · slot 8 · legacy
bool setOffhandInteractionReservation(
std::uint64_t ownerToken,
RockProviderOffhandReservation reservation);
Requires OffhandReservation. ReloadReserved or ReloadPoseOverride claims
the single reservation for the caller; Normal releases it. Another owner's
active reservation makes the call fail.
The retained legacy call applies a fixed maximum-length rolling lease. It has no generation request or result detail. Prefer slots 77–80.
acquireOffhandReservationV1 · slot 77
RockProviderResultV1 acquireOffhandReservationV1(
std::uint64_t ownerToken,
const RockProviderOffhandReservationRequestV1* request);
Requires OffhandReservation. Acquires the single offhand lease if unowned, or
replaces/refreshes it when already owned by the caller. Returns OwnerConflict
when another owner holds it.
The request must choose ReloadReserved or ReloadPoseOverride; Normal is
not a valid acquire mode. Supply non-zero lease frames and optional current
generation guards.
Semantics:
ReloadReserved— reserve offhand interaction for a reload flow;ReloadPoseOverride— reserve it and signal that the consumer owns the reload-pose presentation path.
renewOffhandReservationV1 · slot 78
RockProviderResultV1 renewOffhandReservationV1(
std::uint64_t ownerToken,
const RockProviderOffhandReservationRequestV1* request);
Uses the same request contract as acquire, but requires the caller to own the
current lease. Returns TargetUnavailable if it has expired or was never
acquired, and OwnerConflict if another owner holds it.
Renew replaces expiry, mode, and generation guards. It does not extend the old expiry arithmetically.
releaseOffhandReservationV1 · slot 79
RockProviderResultV1 releaseOffhandReservationV1(
std::uint64_t ownerToken);
Explicitly releases the reservation. Returns OwnerConflict if another owner
holds it. It is otherwise idempotent and returns Ok even when currently free.
getOffhandReservationStateV1 · slot 80
RockProviderResultV1 getOffhandReservationStateV1(
std::uint64_t ownerToken,
RockProviderOffhandReservationStateV1* outState);
Requires OffhandReservation. Returns effective mode, whether active, current
owner token, exclusive expiry frame, remaining frames, and current generations.
The output size must be at least the current structure size.
It exposes the current owner so a coordinator can distinguish free, self-owned, and externally owned states. Never use the observed token to issue calls on another owner's behalf.
Example: reserve input for a reload interaction
RockProviderOffhandReservationRequestV1 reservation{};
reservation.reservation = RockProviderOffhandReservation::ReloadReserved;
reservation.leaseFrames = 8;
reservation.worldGeneration = frame.worldGeneration;
reservation.skeletonGeneration = frame.skeletonGeneration;
reservation.providerGeneration = frame.providerGeneration;
const auto reserved = RockProviderApi::inst->acquireOffhandReservationV1(
ownerToken, &reservation);
if (reserved != RockProviderResultV1::Ok) {
return;
}
RockProviderHandInputSuppressionRequestV1 suppression{};
suppression.hand = frame.offhandHand;
suppression.flags = static_cast<std::uint32_t>(
RockProviderHandInputSuppressionFlagV1::SuppressConfigModeChord);
suppression.leaseFrames = 8;
suppression.worldGeneration = frame.worldGeneration;
suppression.skeletonGeneration = frame.skeletonGeneration;
suppression.providerGeneration = frame.providerGeneration;
(void)RockProviderApi::inst->setHandInputSuppressionV1(
ownerToken, &suppression);
Refresh both while the interaction remains active. On normal exit, clear the suppression and release the reservation explicitly; expiry is the fail-closed backup.