Added First Person VR item management

- Implemented DVDReadVrAsset function to read mapped disc paths for VR assets.
- Created HeldItem structure and ReadHeldItem function for managing held items in the game.
- Developed unit tests for ReadHeldItem to ensure correct functionality and edge case handling.
- Added cockpit item data tests to validate model indexing and parsing of archives.
This commit is contained in:
iChris4 committed 2026-09-29 23:55:52 +02:00
1 parent 75f075b234
commit db30945f4c
26 files changed
+1598 -19

No files matched your search

+9
View File
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <cstdint>
#include <vector>
// Reads the same mapped disc path that the guest sees, including active file
// replacements. Called on the guest thread after DVD initialization.
std::vector<uint8_t> DVDReadVrAsset(const char* dvdPath);
+16
View File
@@ -78,6 +78,7 @@ struct RuntimeUserConfig {
std::optional<bool> vrNativeSteeringWheel;
std::optional<bool> vrObjectCulling;
std::optional<bool> vrHandSteering;
std::optional<std::string> vrCockpitItemHand;
std::optional<bool> vrHandTracking;
std::optional<float> vrWheelKartDegrees;
std::optional<float> vrWheelBikeDegrees;
@@ -237,6 +238,7 @@ inline constexpr float kVrCockpitUnitsPerMeterMax = 400.0f;
inline constexpr bool kVrSteeringWheelDefault = true;
inline constexpr bool kVrNativeSteeringWheelDefault = true;
inline constexpr bool kVrHandSteeringDefault = true;
inline constexpr const char* kVrCockpitItemHandDefault = "left";
// The game hides karts and objects its own camera cannot see, which a head
// turn in VR reveals. object_culling false draws them anyway (see
// vr/mkw_vr_culling.h); it only takes effect while VR is enabled. The PC
@@ -605,6 +607,8 @@ inline void EnsureConfigFile() {
"# (seconds) a hand that loses tracking keeps hold, and a short\n"
"# pulse on grab and release. All changeable live from the F10 menu.\n"
"hand_steering = true\n"
"# Show the settled inventory item in one cockpit hand: left, right, or off.\n"
"cockpit_item_hand = \"left\"\n"
"wheel_kart_degrees = 90.0\n"
"wheel_bike_degrees = 45.0\n"
"wheel_grab_distance = 0.35\n"
@@ -880,6 +884,7 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
config.vrNativeSteeringWheel = FindConfigValue<bool>(document, "vr", "native_steering_wheel");
config.vrObjectCulling = FindConfigValue<bool>(document, "vr", "object_culling");
config.vrHandSteering = FindConfigValue<bool>(document, "vr", "hand_steering");
config.vrCockpitItemHand = FindConfigValue<std::string>(document, "vr", "cockpit_item_hand");
config.vrHandTracking = FindConfigValue<bool>(document, "vr", "hand_tracking");
config.vrWheelKartDegrees = readRangedFloat("wheel_kart_degrees", kVrWheelDegreesMin, kVrWheelDegreesMax);
config.vrWheelBikeDegrees = readRangedFloat("wheel_bike_degrees", kVrWheelDegreesMin, kVrWheelDegreesMax);
@@ -1282,6 +1287,12 @@ inline bool SetVrHandSteering(bool value) {
return WriteSetting("vr", "hand_steering", value ? "true" : "false");
}
inline bool SetVrCockpitItemHand(const std::string& value) {
if (value != "left" && value != "right" && value != "off") return false;
Mutable().vrCockpitItemHand = value;
return WriteSetting("vr", "cockpit_item_hand", "\"" + value + "\"");
}
inline bool SetVrHandTracking(bool value) {
Mutable().vrHandTracking = value;
return WriteSetting("vr", "hand_tracking", value ? "true" : "false");
@@ -1773,6 +1784,11 @@ inline bool VrHandSteering(bool fallback = kVrHandSteeringDefault) {
return Get().vrHandSteering.value_or(fallback);
}
inline std::string VrCockpitItemHand() {
const std::string value = Get().vrCockpitItemHand.value_or(kVrCockpitItemHandDefault);
return value == "left" || value == "right" || value == "off" ? value : kVrCockpitItemHandDefault;
}
inline bool VrHandTracking(bool fallback = kVrHandTrackingDefault) {
return Get().vrHandTracking.value_or(fallback);
}
+5
View File
@@ -3,6 +3,7 @@
#pragma once
#include "vr/steering_wheel.h"
#include "vr/mkw_vr_item.h"
#include <algorithm>
#include <array>
@@ -572,4 +573,8 @@ void MkwVRFirstPersonRecenter() noexcept;
// anchor has been missing long enough to give up holding the last one.
FirstPersonAnchor MkwVRFirstPersonGetAnchor() noexcept;
// Guest-frame inventory snapshot, sampled at the race draw boundary. A
// generation change invalidates any item retained by a prior race.
HeldItem MkwVRFirstPersonGetHeldItem() noexcept;
} // namespace mkw::vr
+47
View File
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <cstdint>
namespace mkw::vr {
struct HeldItem {
uint8_t id = 0x14;
uint8_t count = 0;
bool valid = false;
uint64_t race_generation = 0;
};
namespace detail {
// PAL RMCP01 Item::Manager and Item::Player. Item::PlayerInventory is at
// Player+0x88. Read the inventory, never PlayerRoulette::nextItemId: roulette
// teardown clears the latter, and an interrupted roulette may predict an item
// the player never receives.
template <typename GuestMemory>
HeldItem ReadHeldItem(uint32_t local_racer, uint64_t race_generation) noexcept {
HeldItem result{};
result.race_generation = race_generation;
if (local_racer >= 12) return result;
uint32_t manager = 0, players = 0;
if (!GuestMemory::TryRead32(0x809C3618u, manager) || !manager ||
!GuestMemory::TryRead32(manager + 0x14u, players) || !players) return result;
const uint32_t player = players + local_racer * 0x248u;
if (player < players || !GuestMemory::Contains(player, 0x94u)) return result;
try {
if (GuestMemory::Read8(player + 0x18u) != local_racer ||
GuestMemory::Read32(player + 0x58u) != 0) return result;
const uint32_t id = GuestMemory::Read32(player + 0x8Cu);
const uint32_t count = GuestMemory::Read32(player + 0x90u);
if (id > 0x12u || count == 0 || count > 3) return result;
result.id = static_cast<uint8_t>(id);
result.count = static_cast<uint8_t>(count);
result.valid = true;
} catch (const typename GuestMemory::AccessViolation&) {
return result;
}
return result;
}
} // namespace detail
} // namespace mkw::vr