/* 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); // timeline starts with a default event as seen here: https://www.youtube.com/watch?v=YwBD0E4-EsI SetTimelineGameMode(ETimelineGameMode::k_ETimelineGameMode_Invalid); } 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); } // Sets a description for the current game state in the timeline. These help the user to find specific // moments in the timeline when saving clips. Setting a new state description replaces any previous // description. // // Examples could include: // * Where the user is in the world in a single player game // * Which round is happening in a multiplayer game // * The current score for a sports game // // Parameters: // - pchDescription: provide a localized string in the language returned by SteamUtils()->GetSteamUILanguage() // - flTimeDelta: The time offset in seconds to apply to this event. Negative times indicate an // event that happened in the past. 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(); } } // Use this to mark an event on the Timeline. The event can be instantaneous or take some amount of time // to complete, depending on the value passed in flDurationSeconds // // Examples could include: // * a boss battle // * a cut scene // * a large team fight // * picking up a new weapon or ammunition // * scoring a goal // // Parameters: // // - pchIcon: specify the name of the icon uploaded through the Steamworks Partner Site for your title // or one of the provided icons that start with steam_ // - pchTitle & pchDescription: provide a localized string in the language returned by // SteamUtils()->GetSteamUILanguage() // - unPriority: specify how important this range is compared to other markers provided by the game. // Ranges with larger priority values will be displayed more prominently in the UI. This value // may be between 0 and k_unMaxTimelinePriority. // - flStartOffsetSeconds: The time that this range started relative to now. Negative times // indicate an event that happened in the past. // - flDurationSeconds: How long the time range should be in seconds. For instantaneous events, this // should be 0 // - ePossibleClip: By setting this parameter to Featured or Standard, the game indicates to Steam that it // would be appropriate to offer this range as a clip to the user. For instantaneous events, the // suggested clip will be for a short time before and after the event itself. void Steam_Timeline::AddTimelineEvent( const char *pchIcon, const char *pchTitle, const char *pchDescription, uint32 unPriority, float flStartOffsetSeconds, float flDurationSeconds, ETimelineEventClipPriority ePossibleClip ) { PRINT_DEBUG("icon='%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; // make events last at least 1 sec if (static_cast(flDurationSeconds * 1000) < 1000) { // < 1000ms flDurationSeconds = 1; } // for events with priority=ETimelineEventClipPriority::k_ETimelineEventClipPriority_Featured steam creates ~8 sec clip // seen here: https://www.youtube.com/watch?v=YwBD0E4-EsI if (flDurationSeconds < PRIORITY_CLIP_MIN_SEC && ePossibleClip == ETimelineEventClipPriority::k_ETimelineEventClipPriority_Featured) { flDurationSeconds = PRIORITY_CLIP_MIN_SEC; } new_event.flDurationSeconds = flDurationSeconds; new_event.ePossibleClip = ePossibleClip; } // Changes the color of the timeline bar. See ETimelineGameMode comments for how to use each value void Steam_Timeline::SetTimelineGameMode( ETimelineGameMode eMode ) { PRINT_DEBUG("%i", (int)eMode); std::lock_guard lock(global_mutex); auto &new_timeline_state = timeline_states.emplace_back(TimelineState_t{}); new_timeline_state.bar_color = eMode; } void Steam_Timeline::SetTimelineTooltip(const char* pchDescription, float flTimeDelta) { SetTimelineStateDescription(pchDescription, flTimeDelta); } void Steam_Timeline::ClearTimelineTooltip(float flTimeDelta) { ClearTimelineStateDescription(flTimeDelta); } TimelineEventHandle_t Steam_Timeline::AddInstantaneousTimelineEvent(const char* pchTitle, const char* pchDescription, const char* pchIcon, uint32 unIconPriority, float flStartOffsetSeconds, ETimelineEventClipPriority ePossibleClip) { return 0; } TimelineEventHandle_t Steam_Timeline::AddRangeTimelineEvent(const char* pchTitle, const char* pchDescription, const char* pchIcon, uint32 unIconPriority, float flStartOffsetSeconds, float flDuration, ETimelineEventClipPriority ePossibleClip) { return 0; } TimelineEventHandle_t Steam_Timeline::StartRangeTimelineEvent(const char* pchTitle, const char* pchDescription, const char* pchIcon, uint32 unPriority, float flStartOffsetSeconds, ETimelineEventClipPriority ePossibleClip) { return 0; } void Steam_Timeline::UpdateRangeTimelineEvent(TimelineEventHandle_t ulEvent, const char* pchTitle, const char* pchDescription, const char* pchIcon, uint32 unPriority, ETimelineEventClipPriority ePossibleClip) { } void Steam_Timeline::EndRangeTimelineEvent(TimelineEventHandle_t ulEvent, float flEndOffsetSeconds) { } void Steam_Timeline::RemoveTimelineEvent(TimelineEventHandle_t ulEvent) { } SteamAPICall_t Steam_Timeline::DoesEventRecordingExist(TimelineEventHandle_t ulEvent) { return 0; } void Steam_Timeline::StartGamePhase() { } void Steam_Timeline::EndGamePhase() { } void Steam_Timeline::SetGamePhaseID(const char* pchPhaseID) { } SteamAPICall_t Steam_Timeline::DoesGamePhaseRecordingExist(const char* pchPhaseID) { return 0; } void Steam_Timeline::AddGamePhaseTag(const char* pchTagName, const char* pchTagIcon, const char* pchTagGroup, uint32 unPriority) { } void Steam_Timeline::SetGamePhaseAttribute(const char* pchAttributeGroup, const char* pchAttributeValue, uint32 unPriority) { } void Steam_Timeline::OpenOverlayToGamePhase(const char* pchPhaseID) { } void Steam_Timeline::OpenOverlayToTimelineEvent(const TimelineEventHandle_t ulEvent) { } 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) { } } }