Rename plugin and add player position logging

Replace CommonLibF4 template identity with Fallout4Together across README, setup and xmake; update developer log with build/test entries. Implement player transform tracking in src: add PlayerTransform struct, change detection (position + wrapped Z rotation), throttling (minimum interval), and logging to Fallout4Together.log. Wire up F4SE messaging listener and a permanent task to poll on the game thread; include <chrono> and update startup log message. Add warnings for missing task/messaging interfaces and when player reference is unavailable.
This commit is contained in:
2026-05-31 17:55:27 +12:00
parent b61043a3fe
commit 88506e0415
6 changed files with 281 additions and 11 deletions
+151 -1
View File
@@ -1,8 +1,158 @@
namespace
{
struct PlayerTransform
{
float x;
float y;
float z;
float angleZ;
};
constexpr auto kPositionLogThreshold = 10.0F;
constexpr auto kRotationLogThresholdDegrees = 1.0F;
constexpr auto kRotationLogThresholdRadians = kRotationLogThresholdDegrees * 3.14159265358979323846F / 180.0F;
constexpr auto kMinimumLogInterval = 1s;
RE::PlayerCharacter* TryGetLocalPlayer()
{
return RE::PlayerCharacter::GetSingleton();
}
PlayerTransform GetPlayerTransform(const RE::PlayerCharacter& a_player)
{
const auto position = a_player.GetPosition();
return {
position.x,
position.y,
position.z,
a_player.data.angle.z
};
}
bool HasTransformChanged(const PlayerTransform& a_previous, const PlayerTransform& a_current)
{
const auto deltaX = a_current.x - a_previous.x;
const auto deltaY = a_current.y - a_previous.y;
const auto deltaZ = a_current.z - a_previous.z;
auto deltaAngleZ = a_current.angleZ - a_previous.angleZ;
constexpr auto fullRotationRadians = 360.0F * 3.14159265358979323846F / 180.0F;
constexpr auto halfRotationRadians = fullRotationRadians / 2.0F;
while (deltaAngleZ > halfRotationRadians) {
deltaAngleZ -= fullRotationRadians;
}
while (deltaAngleZ < -halfRotationRadians) {
deltaAngleZ += fullRotationRadians;
}
const auto squaredPositionDelta = (deltaX * deltaX) + (deltaY * deltaY) + (deltaZ * deltaZ);
constexpr auto squaredPositionThreshold = kPositionLogThreshold * kPositionLogThreshold;
return squaredPositionDelta > squaredPositionThreshold ||
deltaAngleZ > kRotationLogThresholdRadians || deltaAngleZ < -kRotationLogThresholdRadians;
}
void LogPlayerTransform(const PlayerTransform& a_transform)
{
REX::INFO("Player position: X={:.2f}, Y={:.2f}, Z={:.2f}, AngleZ={:.2f}",
a_transform.x,
a_transform.y,
a_transform.z,
a_transform.angleZ);
}
void CheckAndLogPlayerPositionChange()
{
const auto* player = TryGetLocalPlayer();
if (!player) {
static bool playerUnavailableWarningLogged = false;
if (!playerUnavailableWarningLogged) {
REX::WARN("Player position unavailable: local player reference is not available yet.");
playerUnavailableWarningLogged = true;
}
return;
}
static bool hasLastTransform = false;
static PlayerTransform lastTransform{};
static auto lastLogTime = std::chrono::steady_clock::now();
const auto currentTransform = GetPlayerTransform(*player);
if (!hasLastTransform) {
lastTransform = currentTransform;
hasLastTransform = true;
return;
}
if (!HasTransformChanged(lastTransform, currentTransform)) {
return;
}
const auto currentTime = std::chrono::steady_clock::now();
if (currentTime - lastLogTime < kMinimumLogInterval) {
return;
}
// Player transforms can change by tiny amounts every frame. Throttling keeps the
// readout useful for debugging movement without flooding Fallout4Together.log.
LogPlayerTransform(currentTransform);
lastTransform = currentTransform;
lastLogTime = currentTime;
}
void StartPlayerPositionPolling()
{
static bool pollingStarted = false;
if (pollingStarted) {
return;
}
const auto* taskInterface = F4SE::GetTaskInterface();
if (!taskInterface) {
REX::WARN("Failed to get F4SE task interface; player position changes will not be logged.");
return;
}
// The player reference may not exist at plugin load time. A permanent task lets us
// check on the game thread after load, while the logging gate keeps updates readable.
taskInterface->AddTaskPermanent([]() {
CheckAndLogPlayerPositionChange();
});
pollingStarted = true;
}
void F4SEAPI OnF4SEMessage(F4SE::MessagingInterface::Message* a_msg)
{
if (!a_msg) {
return;
}
switch (a_msg->type) {
case F4SE::MessagingInterface::kNewGame:
case F4SE::MessagingInterface::kPostLoadGame:
case F4SE::MessagingInterface::kGameLoaded:
// Plugin load happens before a save/new game has finished creating the player.
// These messages are the minimal safe timing point to start polling for movement.
StartPlayerPositionPolling();
break;
default:
break;
}
}
}
F4SE_PLUGIN_LOAD(const F4SE::LoadInterface* a_f4se)
{
F4SE::Init(a_f4se);
REX::INFO("Hello World!");
REX::INFO("Fallout 4 Together plugin loaded successfully.");
const auto* messaging = F4SE::GetMessagingInterface();
if (!messaging || !messaging->RegisterListener(OnF4SEMessage)) {
REX::WARN("Failed to register F4SE message listener; player position will not be logged.");
}
return true;
}
+2
View File
@@ -1,5 +1,7 @@
#pragma once
#include <chrono>
#include <RE/Fallout.h>
#include <F4SE/F4SE.h>