#pragma once #include #include #include #include namespace utils::afk { struct request { std::string id, action, body; }; struct session { std::string body, phase; int authorized_seconds = 0, consumed_seconds = 0; }; struct acknowledgement { bool valid = false; std::string reason = "schema", state, code, id, session_id; int free_seconds = 0, saved_seconds = 0, available_seconds = 0, reserved_seconds = 0, capacity_seconds = 0, authorized_seconds = 0, bank_balance = 0, purchase_points = 0, purchase_seconds = 0, max_purchase_packs = 0, awarded_seconds = 0, consumed_seconds = -1; }; acknowledgement parse_acknowledgement(const std::string& response); // Single game-thread writer. Active clocks are process-local, so recovery // can never accidentally replay protection after a process restart. class journal { public: using clock = std::chrono::steady_clock; journal(std::filesystem::path root, std::string server, std::string endpoint); std::vector load() const; void store(const std::string& body) const; void erase(const std::string& id) const; void prepare(const std::string& reserve_body) const; void authorize(const std::string& id, int seconds) const; void begin(const std::string& id, clock::time_point now = clock::time_point{}) const; int checkpoint(const std::string& id, clock::time_point now = clock::now()) const; // Hot path for the damage hook/countdown: no disk I/O or clock reset. // Missing/restarted clocks return -1; stopped/expired clocks return 0. int remaining(const std::string& id, clock::time_point now = clock::now()) const; void stop(const std::string& id, const std::string& reason, clock::time_point now = clock::now()) const; // Call only at map initialization, with its new persistent match ID. // Active current-map sessions are rejected. Unknown crash intervals // remain unresolved at EC, rather than refunding spendable authority. void recover(const std::string& current_match) const; private: struct snapshot { std::vector requests; std::vector sessions; }; std::filesystem::path file_; std::string server_, endpoint_, scope_; request decode(const std::string& body) const; snapshot read() const; void save(const snapshot& value) const; std::string key(const std::string& id) const; int elapsed(const session& value, clock::time_point now, bool round_up = false) const; request settlement(const session& value, const std::string& reason, int consumed) const; }; }