From cb47377f72011f16c1f8b3d649b3bd7e327e0663 Mon Sep 17 00:00:00 2001 From: ApfelTeeSaft <91074565+ApfelTeeSaft@users.noreply.github.com> Date: Wed, 22 Apr 2026 11:24:23 +0200 Subject: [PATCH] implement proper countdown actor --- Aeon-740/EventHandler.h | 76 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/Aeon-740/EventHandler.h b/Aeon-740/EventHandler.h index a68628cd..3ce48ed5 100644 --- a/Aeon-740/EventHandler.h +++ b/Aeon-740/EventHandler.h @@ -31,13 +31,13 @@ namespace EventHandler { - // Event target time – edit these six values to reschedule the event. + // Event target time // All values are interpreted as UTC. static constexpr int TargetYear = 2026; static constexpr int TargetMonth = 4; // 1 – 12 static constexpr int TargetDay = 22; - static constexpr int TargetHour = 9; // 24-hour clock - static constexpr int TargetMinute = 45; + static constexpr int TargetHour = 12; // 24-hour clock + static constexpr int TargetMinute = 0; static constexpr int TargetSecond = 0; enum class ECustomEventPhase : uint8 @@ -95,12 +95,72 @@ namespace EventHandler return std::difftime(targetEpoch, nowEpoch); } + // BP_EventCountdownDisplay helpers + // + // The server writes a single replicated float (CountdownEndTime, a world-time + // stamp) onto the placed Blueprint Actor. Clients receive the new value via + // normal Actor replication and compute (CountdownEndTime - GetGameTimeInSeconds()) + // each second to derive the D/H/M/S display. + // + // CountdownEndTime semantics: + // > 0 countdown is active; value is the world-time second when it expires + // -1 event is live (server sets this at EventEnd to trigger "EVENT IS LIVE!") + + static AActor* FindCountdownActor() + { + for (int32 i = 0; i < UObject::GObjects->Num(); i++) + { + UObject* Obj = UObject::GObjects->GetByIndex(i); + if (!Obj || !Obj->Class) continue; + if (Obj->Class->GetName().find("BP_EventCountdownDisplay") != string::npos) + return static_cast(Obj); + } + return nullptr; + } + + static UProperty* FindClassProperty(UClass* Class, const string& PropName) + { + for (UStruct* S = Class; S; S = static_cast(S->Super)) + { + for (UField* F = S->Children; F; F = F->Next) + { + if (F->IsA(UProperty::StaticClass()) && F->GetName() == PropName) + return static_cast(F); + } + } + return nullptr; + } + + // Write a float value into the BP Actor's CountdownEndTime replicated property + // and call ForceNetUpdate() so the engine immediately schedules a replication + // packet rather than waiting for the next network tick. + static void SetCountdownActorEndTime(float endTime) + { + AActor* Actor = FindCountdownActor(); + if (!Actor) + { + LogInfo("EventHandler: BP_EventCountdownDisplay actor not found in world."); + return; + } + + UProperty* Prop = FindClassProperty(Actor->Class, "CountdownEndTime"); + if (!Prop) + { + LogInfo("EventHandler: CountdownEndTime property not found on BP_EventCountdownDisplay."); + return; + } + + *reinterpret_cast(reinterpret_cast(Actor) + Prop->Offset) = endTime; + Actor->ForceNetUpdate(); + LogInfo("EventHandler: Set BP_EventCountdownDisplay::CountdownEndTime = {:.2f}", endTime); + } + // Countdown state helpers // WarmupCountdownEndTime is a Net-replicated float on AFortGameStateAthena. // Clients read (WarmupCountdownEndTime - GetTimeSeconds()) each tick to // drive the HUD timer without any additional server -> client messaging. - // The BP_EventCountdownDisplay Blueprint reads the system clock directly - // for the D/H/M/S breakdown visible in world space. + // BP_EventCountdownDisplay is kept in sync via SetCountdownActorEndTime() + // which writes the same world-time stamp into the actor's replicated property. // Set the centralised countdown timestamp and return the world-time end value. // durationSeconds is the wall-clock delta already converted to world seconds. @@ -117,6 +177,8 @@ namespace EventHandler GS->bIsInCountdown = true; } + SetCountdownActorEndTime(endTime); // replicate to BP display actor + LogInfo("EventHandler: Countdown started. World end time = {:.1f} ({:.0f}s from now)", endTime, durationSeconds); return endTime; @@ -382,6 +444,10 @@ namespace EventHandler GS->bIsInFinalCountdown = false; } + // Signal the Blueprint display actor that the event is now live. + // Setting -1.0 causes clients to show "EVENT IS LIVE!" instead of a timer. + SetCountdownActorEndTime(-1.0f); + LogInfo("EventHandler: Event complete."); return 0; }