From f5ae22a1e63eec4ec0a11c5bac5087279f2186a5 Mon Sep 17 00:00:00 2001 From: otavepto <153766569+otavepto@users.noreply.github.com> Date: Mon, 1 Jul 2024 11:52:32 +0300 Subject: [PATCH] basic impl for steam timeline --- dll/dll/steam_client.h | 5 + dll/dll/steam_timeline.h | 54 +++++++-- dll/steam_client.cpp | 2 + dll/steam_client_interface_getter.cpp | 13 +++ dll/steam_timeline.cpp | 155 ++++++++++++++++++++++++++ 5 files changed, 222 insertions(+), 7 deletions(-) create mode 100644 dll/steam_timeline.cpp diff --git a/dll/dll/steam_client.h b/dll/dll/steam_client.h index 48377538..36d84a7b 100644 --- a/dll/dll/steam_client.h +++ b/dll/dll/steam_client.h @@ -55,6 +55,7 @@ #include "steam_gameserver.h" #include "steam_gameserverstats.h" #include "steam_gamestats.h" +#include "steam_timeline.h" #include "steam_masterserver_updater.h" #include "overlay/steam_overlay.h" @@ -138,6 +139,7 @@ public: Steam_RemotePlay *steam_remoteplay{}; Steam_TV *steam_tv{}; Steam_GameStats *steam_gamestats{}; + Steam_Timeline *steam_timeline{}; Steam_GameServer *steam_gameserver{}; Steam_Utils *steam_gameserver_utils{}; @@ -236,6 +238,9 @@ public: // game stats ISteamGameStats *GetISteamGameStats( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // game timeline + ISteamTimeline *GetISteamTimeline( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // Deprecated. Applications should use SteamAPI_RunCallbacks() or SteamGameServer_RunCallbacks() instead. STEAM_PRIVATE_API( void RunFrame() ); diff --git a/dll/dll/steam_timeline.h b/dll/dll/steam_timeline.h index ad5c02e4..8a62dc84 100644 --- a/dll/dll/steam_timeline.h +++ b/dll/dll/steam_timeline.h @@ -23,13 +23,57 @@ class Steam_Timeline : public ISteamTimeline { +private: + struct TimelineEvent_t + { + // emu specific: time when this event was added to the list via 'Steam_Timeline::AddTimelineEvent()' + const std::chrono::system_clock::time_point time_added = std::chrono::system_clock::now(); + + // The name of the icon to show at the timeline at this point. This can be one of the icons uploaded through the Steamworks partner Site for your title, or one of the provided icons that start with steam_. The Steam Timelines overview includes a list of available icons. + // https://partner.steamgames.com/doc/features/timeline#icons + std::string pchIcon{}; + + // Title-provided localized string in the language returned by SteamUtils()->GetSteamUILanguage(). + std::string pchTitle{}; + + // Title-provided localized string in the language returned by SteamUtils()->GetSteamUILanguage(). + std::string pchDescription{}; + + // Provide the priority to use when the UI is deciding which icons to display in crowded parts of the timeline. Events with larger priority values will be displayed more prominently than events with smaller priority values. This value must be between 0 and k_unMaxTimelinePriority. + uint32 unPriority{}; + + // One use of this parameter is to handle events whose significance is not clear until after the fact. For instance if the player starts a damage over time effect on another player, which kills them 3.5 seconds later, the game could pass -3.5 as the start offset and cause the event to appear in the timeline where the effect started. + float flStartOffsetSeconds{}; + + // The duration of the event, in seconds. Pass 0 for instantaneous events. + float flDurationSeconds{}; + + // Allows the game to describe events that should be suggested to the user as possible video clips. + ETimelineEventClipPriority ePossibleClip{}; + }; + + struct TimelineState_t + { + // emu specific: time when this state was changed via 'Steam_Timeline::SetTimelineGameMode()' + const std::chrono::system_clock::time_point time_added = std::chrono::system_clock::now(); + + std::string description{}; // A localized string in the language returned by SteamUtils()->GetSteamUILanguage() + ETimelineGameMode bar_color{}; // the color of the timeline bar + }; + class Settings *settings{}; class Networking *network{}; class SteamCallResults *callback_results{}; class SteamCallBacks *callbacks{}; class RunEveryRunCB *run_every_runcb{}; - std::chrono::time_point initialized_time = std::chrono::steady_clock::now(); - FSteamNetworkingSocketsDebugOutput debug_function{}; + + std::vector timeline_events{}; + std::vector timeline_states{TimelineState_t{}}; // it seems to always start with a default event + + // unconditional periodic callback + void RunCallbacks(); + // network callback, triggered once we have a network message + void Callback(Common_Message *msg); static void steam_callback(void *object, Common_Message *msg); static void steam_run_every_runcb(void *object); @@ -46,10 +90,6 @@ public: void SetTimelineGameMode( ETimelineGameMode eMode ); - void RunCallbacks(); - - void Callback(Common_Message *msg); - }; -#endif // __INCLUDED_STEAM_TIMELINE_H__ \ No newline at end of file +#endif // __INCLUDED_STEAM_TIMELINE_H__ diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index d64d0896..1aaebc46 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -123,6 +123,7 @@ Steam_Client::Steam_Client() steam_remoteplay = new Steam_RemotePlay(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); steam_tv = new Steam_TV(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); steam_gamestats = new Steam_GameStats(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); + steam_timeline = new Steam_Timeline(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); // server PRINT_DEBUG("init gameserver"); @@ -202,6 +203,7 @@ Steam_Client::~Steam_Client() DEL_INST(steam_remoteplay); DEL_INST(steam_tv); DEL_INST(steam_gamestats); + DEL_INST(steam_timeline); DEL_INST(steam_utils); DEL_INST(steam_friends); diff --git a/dll/steam_client_interface_getter.cpp b/dll/steam_client_interface_getter.cpp index bd49dc58..9257b6e1 100644 --- a/dll/steam_client_interface_getter.cpp +++ b/dll/steam_client_interface_getter.cpp @@ -18,6 +18,19 @@ #include "dll/steam_client.h" +// retrieves the ISteamTimeline interface associated with the handle +ISteamTimeline *Steam_Client::GetISteamTimeline( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) +{ + PRINT_DEBUG("%s", pchVersion); + if (!steam_pipes.count(hSteamPipe) || !hSteamUser) return nullptr; + + if (strcmp(pchVersion, STEAMTIMELINE_INTERFACE_VERSION) == 0) { + return reinterpret_cast(static_cast(steam_timeline)); + } + + report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); +} + // retrieves the ISteamGameStats interface associated with the handle ISteamGameStats *Steam_Client::GetISteamGameStats( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) { diff --git a/dll/steam_timeline.cpp b/dll/steam_timeline.cpp new file mode 100644 index 00000000..9aa13c6a --- /dev/null +++ b/dll/steam_timeline.cpp @@ -0,0 +1,155 @@ +/* Copyright (C) 2019 Mr Goldberg + This file is part of the Goldberg Emulator + + The Goldberg Emulator is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + The Goldberg Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the Goldberg Emulator; if not, see + . */ + +#include "dll/steam_timeline.h" + +// https://partner.steamgames.com/doc/api/ISteamTimeline + + +void Steam_Timeline::steam_callback(void *object, Common_Message *msg) +{ + // PRINT_DEBUG_ENTRY(); + + auto instance = (Steam_Timeline *)object; + instance->Callback(msg); +} + +void Steam_Timeline::steam_run_every_runcb(void *object) +{ + // PRINT_DEBUG_ENTRY(); + + auto instance = (Steam_Timeline *)object; + instance->RunCallbacks(); +} + + +Steam_Timeline::Steam_Timeline(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb) +{ + this->settings = settings; + this->network = network; + this->callback_results = callback_results; + this->callbacks = callbacks; + this->run_every_runcb = run_every_runcb; + + // this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Timeline::steam_callback, this); + this->run_every_runcb->add(&Steam_Timeline::steam_run_every_runcb, this); +} + +Steam_Timeline::~Steam_Timeline() +{ + // this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Timeline::steam_callback, this); + this->run_every_runcb->remove(&Steam_Timeline::steam_run_every_runcb, this); +} + +void Steam_Timeline::SetTimelineStateDescription( const char *pchDescription, float flTimeDelta ) +{ + PRINT_DEBUG("'%s' %f", pchDescription, flTimeDelta); + std::lock_guard lock(global_mutex); + + const auto target_timepoint = std::chrono::system_clock::now() + std::chrono::milliseconds(static_cast(flTimeDelta * 1000)); + + // reverse iterators to search from end + auto event_it = std::find_if(timeline_states.rbegin(), timeline_states.rend(), [this, &target_timepoint](const TimelineState_t &item) { + return target_timepoint >= item.time_added; + }); + + if (timeline_states.rend() != event_it) { + PRINT_DEBUG("setting timeline state description"); + if (pchDescription) { + event_it->description = pchDescription; + } else { + event_it->description.clear(); + } + } + +} + + +void Steam_Timeline::ClearTimelineStateDescription( float flTimeDelta ) +{ + PRINT_DEBUG("%f", flTimeDelta); + std::lock_guard lock(global_mutex); + + const auto target_timepoint = std::chrono::system_clock::now() + std::chrono::milliseconds(static_cast(flTimeDelta * 1000)); + + // reverse iterators to search from end + auto event_it = std::find_if(timeline_states.rbegin(), timeline_states.rend(), [this, &target_timepoint](const TimelineState_t &item) { + return target_timepoint >= item.time_added; + }); + + if (timeline_states.rend() != event_it) { + PRINT_DEBUG("clearing timeline state description"); + event_it->description.clear(); + } + +} + + +void Steam_Timeline::AddTimelineEvent( const char *pchIcon, const char *pchTitle, const char *pchDescription, uint32 unPriority, float flStartOffsetSeconds, float flDurationSeconds, ETimelineEventClipPriority ePossibleClip ) +{ + PRINT_DEBUG("'%s' | '%s' - '%s', %u, [%f, %f) %i", pchIcon, pchTitle, pchDescription, unPriority, flStartOffsetSeconds, flDurationSeconds, (int)ePossibleClip); + std::lock_guard lock(global_mutex); + + auto &new_event = timeline_events.emplace_back(TimelineEvent_t{}); + new_event.pchIcon = pchIcon; + new_event.pchTitle = pchTitle; + new_event.pchDescription = pchDescription; + new_event.unPriority = unPriority; + + new_event.flStartOffsetSeconds = flStartOffsetSeconds; + + // for instantanious event with flDurationSeconds=0 steam creates 8 sec clip + if (static_cast(flDurationSeconds * 1000) <= 100) { // <= 100ms + flDurationSeconds = 8; + } + new_event.flDurationSeconds = flDurationSeconds; + + new_event.ePossibleClip = ePossibleClip; +} + + +void Steam_Timeline::SetTimelineGameMode( ETimelineGameMode eMode ) +{ + PRINT_DEBUG("%i", (int)eMode); + std::lock_guard lock(global_mutex); + + if (timeline_states.empty()) return; + + timeline_states.back().bar_color = eMode; +} + + + +void Steam_Timeline::RunCallbacks() +{ + +} + + + +void Steam_Timeline::Callback(Common_Message *msg) +{ + if (msg->has_low_level()) { + if (msg->low_level().type() == Low_Level::CONNECT) { + + } + + if (msg->low_level().type() == Low_Level::DISCONNECT) { + + } + } +}