Convert HHmm to Fallout game-hour float

Add parsing and application of HHmm time strings as Fallout game-hour floats so GUI time presets apply correctly. Introduce ParseHHmmToGameHour in plugin/src/F4TWorldStateSync.cpp and use it when building the console command (formats as set gamehour to <float> with fallback to the raw HHmm string if parsing fails). Improve logging to indicate when a parsed HHmm was used. Update protocol/server-world-state.md, changelog.md, and docs/dev-log.md to document the behavior and rationale.
This commit is contained in:
2026-06-24 12:34:18 +12:00
parent 1493e68349
commit c4466718a3
4 changed files with 87 additions and 5 deletions
+53 -2
View File
@@ -195,6 +195,47 @@ namespace
return value;
}
std::optional<float> ParseHHmmToGameHour(std::string_view a_hhmm)
{
if (a_hhmm.empty() || a_hhmm.size() > 4) {
return std::nullopt;
}
std::string digits;
digits.reserve(4);
for (const auto ch : a_hhmm) {
if (ch < '0' || ch > '9') {
return std::nullopt;
}
digits.push_back(ch);
}
while (digits.size() < 4) {
digits.insert(digits.begin(), '0');
}
int hours = 0;
int minutes = 0;
{
const auto* begin = digits.data();
const auto* split = begin + 2;
const auto* end = begin + 4;
const auto hourResult = std::from_chars(begin, split, hours);
const auto minuteResult = std::from_chars(split, end, minutes);
if (hourResult.ec != std::errc{} || hourResult.ptr != split ||
minuteResult.ec != std::errc{} || minuteResult.ptr != end) {
return std::nullopt;
}
}
if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
return std::nullopt;
}
return static_cast<float>(hours) + static_cast<float>(minutes) / 60.0F;
}
bool ForceHostWorldStateBroadcast(std::optional<std::uint32_t> a_forcedWeatherFormId, bool a_sendTime)
{
if (!F4T::WorldStateSync::IsWorldStateHost() || !F4T::Networking::IsConnectedToServer()) {
@@ -394,7 +435,10 @@ namespace
void ApplyServerWorldState(const F4T::WorldStateSync::PendingServerWorldState& a_state)
{
if (a_state.timeHHmm && !a_state.timeHHmm->empty()) {
const auto command = std::format("set gamehour to {}", *a_state.timeHHmm);
const auto parsedGameHour = ParseHHmmToGameHour(*a_state.timeHHmm);
const auto command = parsedGameHour
? std::format("set gamehour to {:.4f}", *parsedGameHour)
: std::format("set gamehour to {}", *a_state.timeHHmm);
RE::Console::ExecuteCommand(command.c_str());
float gameHour = 0.0F;
@@ -415,7 +459,14 @@ namespace
}
if (ShouldLog("server_world_state_time_apply")) {
LogInfo(std::format("Applied server time command: {}", command));
if (parsedGameHour) {
LogInfo(std::format(
"Applied server time command: {} (from HHmm={}).",
command,
*a_state.timeHHmm));
} else {
LogInfo(std::format("Applied server time command: {}", command));
}
}
}