#pragma once #include "../external/nlohmann/json.hpp" #include #include #include #include #include #include #include "../navigation/PathFinder.h" namespace FRG { inline constexpr int kGameProtocolVersion = 2; inline constexpr std::int64_t kSimulationTickMs = 100; inline constexpr std::uint32_t kDefaultInputDelayTicks = 2; struct InitialStateMessage { nlohmann::json state; std::string assignedPlayerId; std::uint64_t snapshotTick = 0; std::int64_t tickMs = kSimulationTickMs; std::uint32_t inputDelayTicks = kDefaultInputDelayTicks; std::string compatibilityFingerprint; }; struct DestinationInputMessage { std::uint64_t clientSequence = 0; Vec3fp target; }; struct CharacterActionInputMessage { std::uint64_t clientSequence = 0; std::string action; std::string targetId; }; struct ScheduledDestinationCommand { std::uint64_t commandSequence = 0; std::string playerId; Vec3fp target; }; struct ScheduledCharacterEnabledCommand { std::uint64_t commandSequence = 0; std::string locationId; std::string characterId; bool enabled = false; }; struct ScheduledCharacterActionCommand { std::uint64_t commandSequence = 0; std::string playerId; std::string action; std::string targetId; }; using ScheduledCommand = std::variant< ScheduledDestinationCommand, ScheduledCharacterEnabledCommand, ScheduledCharacterActionCommand>; struct TickMessage { std::uint64_t tickId = 0; std::vector commands; std::string stateHash; }; class LockstepTimeline { public: explicit LockstepTimeline( std::uint32_t inputDelayTicks = kDefaultInputDelayTicks) : inputDelayTicks_(inputDelayTicks) {} std::uint64_t currentTick() const { return currentTick_; } std::uint64_t schedule( const std::string& playerId, const Vec3fp& target); std::uint64_t scheduleCharacterEnabled( const std::string& locationId, const std::string& characterId, bool enabled); std::uint64_t scheduleCharacterAction( const std::string& playerId, std::string action, std::string targetId = {}); TickMessage advance(); private: std::uint32_t inputDelayTicks_; std::uint64_t currentTick_ = 0; std::uint64_t nextCommandSequence_ = 1; std::map> pending_; }; std::string makeInitialStateMessage(const InitialStateMessage& message); bool parseInitialStateMessage( const std::string& text, InitialStateMessage& message, std::string& error); std::string makeDestinationInputMessage(const DestinationInputMessage& message); bool parseDestinationInputMessage( const std::string& text, DestinationInputMessage& message, std::string& error); std::string makeCharacterActionInputMessage( const CharacterActionInputMessage& message); bool parseCharacterActionInputMessage( const std::string& text, CharacterActionInputMessage& message, std::string& error); std::string makeTickMessage(const TickMessage& message); bool parseTickMessage( const std::string& text, TickMessage& message, std::string& error); std::string makeErrorMessage(const std::string& code, const std::string& detail); bool parseErrorMessage( const std::string& text, std::string& code, std::string& detail); // Stable for the same executable/content build. Persistence-only wall-clock // fields are removed before hashing. std::string canonicalStateHash(nlohmann::json state); } // namespace FRG