Skip to main content

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:

FlagSuppressed ROCK/native path
SuppressNormalGrabPressNormal ROCK grab press for that hand.
SuppressGrabReleaseNormal ROCK release handling.
SuppressHeldWeaponTriggerEquipTrigger-driven equip from a held weapon.
SuppressGameplayCandidatesNormal gameplay interaction candidates.
SuppressOpenVrGameInputMatching native/OpenVR game input.
SuppressNativeVatsNative primary-wand Pause release that opens VATS.
SuppressNativeVansNative held Pause samples that begin V.A.N.S. after Bethesda's threshold.
SuppressConfigModeChordConvenience 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.