diff --git a/.gitignore b/.gitignore index 9ab794a..a993d83 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ *.dylib !redist/windows/*.dll !redist/osx/*.dylib -!redist/linux/*.so \ No newline at end of file +!redist/linux/*.so +library/sdk/steam/ diff --git a/library/Makefile b/library/Makefile index 416dc31..dbac57f 100644 --- a/library/Makefile +++ b/library/Makefile @@ -1,4 +1,5 @@ -makeall: +SteamworksPy.so: SteamworksPy.cpp g++ -std=c++11 -o SteamworksPy.so -shared -fPIC SteamworksPy.cpp -l steam_api -L. + clean: rm SteamworksPy.so diff --git a/library/README.md b/library/README.md index f32ea35..e1ef502 100644 --- a/library/README.md +++ b/library/README.md @@ -10,4 +10,4 @@ The source files can be downloaded here (log-in required): https://partner.steam Unpack the archive and place the contents of: - /sdk/public/steam in SteamworksPy/library/sdk/steam -- /sdk/redistributable_bin/%your_os% in SteamworksPy/library/sdk/redist +- /sdk/redistributable_bin/%your_os% in SteamworksPy/library diff --git a/library/SteamworksPy.cpp b/library/SteamworksPy.cpp index 359ed7a..ed08799 100644 --- a/library/SteamworksPy.cpp +++ b/library/SteamworksPy.cpp @@ -13,6 +13,7 @@ #include "TargetConditionals.h" #define SW_PY extern "C" __attribute__ ((visibility("default"))) #elif defined( __linux__ ) +#include #include "sdk/steam/steam_api.h" #define SW_PY extern "C" __attribute__ ((visibility("default"))) #else @@ -104,6 +105,9 @@ typedef void(*RemoteStorageSubscribeFileResultCallback_t)(SubscriptionResult); typedef void(*RemoteStorageUnsubscribeFileResultCallback_t)(SubscriptionResult); typedef void(*LeaderboardFindResultCallback_t)(LeaderboardFindResult_t); typedef void(*MicroTxnAuthorizationResponseCallback_t)(MicroTxnAuthorizationResponse_t); +typedef void(*SteamUGCQueryCompletedCallback_t)(SteamUGCQueryCompleted_t); +typedef void(*GetAppDependenciesResultCallback_t)(GetAppDependenciesResult_t); +typedef void(*DownloadItemResultCallback_t)(DownloadItemResult_t); //----------------------------------------------- // Workshop Class @@ -116,12 +120,18 @@ class Workshop { GetAppDependenciesResultCallback_t _pyGetAppDependenciesCallback; RemoteStorageSubscribeFileResultCallback_t _pyItemSubscribedCallback; RemoteStorageUnsubscribeFileResultCallback_t _pyItemUnsubscribedCallback; + SteamUGCQueryCompletedCallback_t _pyQueryCompletedCallback; + GetAppDependenciesResultCallback_t _pyGetAppDependenciesCallback; + DownloadItemResultCallback_t _pyDownloadItemCallback; CCallResult _itemCreatedCallback; CCallResult _itemUpdatedCallback; CCallResult _getAppDependenciesCallback; CCallResult _itemSubscribedCallback; CCallResult _itemUnsubscribedCallback; + CCallResult _queryCompletedCallback; + CCallResult _getAppDependenciesCallback; + CCallResult _downloadItemCallback; CCallback _itemInstalledCallback; @@ -155,6 +165,18 @@ class Workshop { _pyItemUnsubscribedCallback = callback; } + void SetQueryCompletedCallback(SteamUGCQueryCompletedCallback_t callback) { + _pyQueryCompletedCallback = callback; + } + + void SetGetAppDependenciesCallback(GetAppDependenciesResultCallback_t callback) { + _pyGetAppDependenciesCallback = callback; + } + + void SetDownloadItemCallback(DownloadItemResultCallback_t callback) { + _pyDownloadItemCallback = callback; + } + void CreateItem(AppId_t consumerAppId, EWorkshopFileType fileType) { //TODO: Check if fileType is a valid value? SteamAPICall_t createItemCall = SteamUGC()->CreateItem(consumerAppId, fileType); @@ -181,6 +203,22 @@ class Workshop { _itemUnsubscribedCallback.Set(unsubscribeItemCall, this, &Workshop::OnItemUnsubscribed); } + void SendQueryRequest(UGCQueryHandle_t queryHandle) { + SteamAPICall_t queryRequestCall = SteamUGC()->SendQueryUGCRequest(queryHandle); + _queryCompletedCallback.Set(queryRequestCall, this, &Workshop::OnQueryCompleted); + } + + void GetAppDependencies(PublishedFileId_t publishedFileID) { + SteamAPICall_t getAppDependenciesCall = SteamUGC()->GetAppDependencies(publishedFileID); + _getAppDependenciesCallback.Set(getAppDependenciesCall, this, &Workshop::OnGetAppDependencies); + } + + bool DownloadItem(PublishedFileId_t publishedFileID, bool bHighPriority) { + SteamAPICall_t downloadItemCall = SteamUGC()->DownloadItem(publishedFileID, bHighPriority); + _downloadItemCallback.Set(downloadItemCall, this, &Workshop::OnDownloadItem); + return true; // Returns true if successfully queued + } + private: void OnWorkshopItemCreated(CreateItemResult_t *createItemResult, bool bIOFailure) { if (_pyItemCreatedCallback != nullptr) { @@ -229,6 +267,24 @@ class Workshop { _pyItemUnsubscribedCallback(result); } } + + void OnQueryCompleted(SteamUGCQueryCompleted_t *queryCompletedResult, bool bIOFailure) { + if (_pyQueryCompletedCallback != nullptr) { + _pyQueryCompletedCallback(*queryCompletedResult); + } + } + + void OnGetAppDependencies(GetAppDependenciesResult_t *result, bool bIOFailure) { + if (_pyGetAppDependenciesCallback != nullptr) { + _pyGetAppDependenciesCallback(*result); + } + } + + void OnDownloadItem(DownloadItemResult_t *result, bool bIOFailure) { + if (_pyDownloadItemCallback != nullptr) { + _pyDownloadItemCallback(*result); + } + } }; static Workshop workshop; @@ -301,10 +357,15 @@ SW_PY int SteamInit() { bool isInitSuccess = SteamAPI_Init(); // Set the default status response int status = FAILED; + // Steamworks initialized with no problems if (isInitSuccess) { status = OK; + }else + { + return status; } + // The Steam client is not running if (!SteamAPI_IsSteamRunning()) { status = ERR_NO_CLIENT; @@ -314,9 +375,11 @@ SW_PY int SteamInit() { status = ERR_NO_CONNECTION; } // Steam is connected and active, so load the stats and achievements - if (status == OK && SteamUserStats() != NULL) { - SteamUserStats()->RequestCurrentStats(); - } + // FULLY DEPRECATED, WILL NOT COMPILE + //if (status == OK && SteamUserStats() != NULL) { + //SteamUserStats()->RequestCurrentStats(); + //} + // Return the Steamworks status return status; } @@ -702,7 +765,7 @@ SW_PY uint64_t GetCurrentActionSet(uint64_t controllerHandle){ } return (uint64_t) SteamInput()->GetCurrentActionSet((InputHandle_t) controllerHandle); } -// Get the input type (device model) for the specified controller. +// Get the input type (device model) for the specified controller. SW_PY uint64_t GetInputTypeForHandle(uint64_t controllerHandle){ if(SteamInput() == NULL){ return 0; @@ -773,6 +836,13 @@ SW_PY bool ControllerInit(bool bExplicitlyCallRunFrame) { return SteamInput()->Init(bExplicitlyCallRunFrame); } +SW_PY bool SetInputActionManifestFilePath(const char *path) { + if (SteamInput() == NULL) { + return false; + } + return SteamInput()->SetInputActionManifestFilePath(path); +} + // Syncronize controllers. SW_PY void RunFrame() { if (SteamInput() == NULL) { @@ -1021,7 +1091,7 @@ SW_PY int GetAuthSessionTicket(char* buffer) { return 0; } uint32 size{}; - SteamUser()->GetAuthSessionTicket(buffer, 1024, &size); + SteamUser()->GetAuthSessionTicket(buffer, 1024, &size, nullptr); return size; } @@ -1089,7 +1159,7 @@ SW_PY bool RequestCurrentStats() { if (SteamUser() == NULL) { return false; } - return SteamUserStats()->RequestCurrentStats(); + return true; } SW_PY bool SetAchievement(const char *name) { @@ -1493,6 +1563,59 @@ SW_PY void Workshop_SuspendDownloads(bool bSuspend) { SteamUGC()->SuspendDownloads(bSuspend); } +SW_PY UGCQueryHandle_t Workshop_CreateQueryUGCDetailsRequest(PublishedFileId_t * pvecPublishedFileID, uint32 unNumPublishedFileIDs) { + return SteamUGC()->CreateQueryUGCDetailsRequest(pvecPublishedFileID, unNumPublishedFileIDs); +} + +SW_PY void Workshop_SetQueryCompletedCallback(SteamUGCQueryCompletedCallback_t callback) { + if (SteamUGC() == NULL) { + return; + } + workshop.SetQueryCompletedCallback(callback); +} + +SW_PY void Workshop_SendQueryUGCRequest(UGCQueryHandle_t handle) { + if(SteamUGC() == NULL){ + return; + } + workshop.SendQueryRequest(handle); +} + +SW_PY bool Workshop_GetQueryUGCResult(UGCQueryHandle_t handle, uint32 index, SteamUGCDetails_t * pDetails) { + if(SteamUGC() == NULL){ + return false; + } + return SteamUGC()->GetQueryUGCResult(handle, index, pDetails); +} + +SW_PY void Workshop_SetGetAppDependenciesCallback(GetAppDependenciesResultCallback_t callback) { + if (SteamUGC() == NULL) { + return; + } + workshop.SetGetAppDependenciesCallback(callback); +} + +SW_PY void Workshop_GetAppDependencies(PublishedFileId_t publishedFileID) { + if (SteamUGC() == NULL) { + return; + } + workshop.GetAppDependencies(publishedFileID); +} + +SW_PY void Workshop_SetDownloadItemCallback(DownloadItemResultCallback_t callback) { + if (SteamUGC() == NULL) { + return; + } + workshop.SetDownloadItemCallback(callback); +} + +SW_PY bool Workshop_DownloadItem(PublishedFileId_t publishedFileID, bool bHighPriority) { + if (SteamUGC() == NULL) { + return false; + } + return workshop.DownloadItem(publishedFileID, bHighPriority); +} + //----------------------------------------------- // Steam Leaderboard //----------------------------------------------- diff --git a/steamworks/interfaces/friends.py b/steamworks/interfaces/friends.py index 3e22a2f..3de9b26 100644 --- a/steamworks/interfaces/friends.py +++ b/steamworks/interfaces/friends.py @@ -129,7 +129,7 @@ def ActivateGameOverlayToStore(self, app_id: int) -> None: :param app_id: int :return: None """ - self.steam.ActivateGameOverlayToWebPage(app_id) + self.steam.ActivateGameOverlayToStore(app_id) def ActivateGameOverlayInviteDialog(self, steam_lobby_id: int) -> None: diff --git a/steamworks/interfaces/input.py b/steamworks/interfaces/input.py index e5820e5..0aa9af6 100644 --- a/steamworks/interfaces/input.py +++ b/steamworks/interfaces/input.py @@ -12,6 +12,9 @@ def __init__(self, steam: object): def Init(self, explicitlyCallRunFrame=False): return self.steam.ControllerInit(explicitlyCallRunFrame) + def SetInputActionManifestFilePath(self, path): + return self.steam.SetInputActionManifestFilePath(path.encode()) + def RunFrame(self): return self.steam.RunFrame() diff --git a/steamworks/interfaces/workshop.py b/steamworks/interfaces/workshop.py index 635b1ab..e0446ce 100644 --- a/steamworks/interfaces/workshop.py +++ b/steamworks/interfaces/workshop.py @@ -11,9 +11,11 @@ class SteamWorkshop(object): _CreateItemResult_t = CFUNCTYPE(None, CreateItemResult_t) _SubmitItemUpdateResult_t = CFUNCTYPE(None, SubmitItemUpdateResult_t) _ItemInstalled_t = CFUNCTYPE(None, ItemInstalled_t) - _GetAppDependenciesResult_t = CFUNCTYPE(None, GetAppDependenciesResult) _RemoteStorageSubscribePublishedFileResult_t = CFUNCTYPE(None, SubscriptionResult) _RemoteStorageUnsubscribePublishedFileResult_t = CFUNCTYPE(None, SubscriptionResult) + _SteamUGCQueryCompleted_t = CFUNCTYPE(None, SteamUGCQueryCompleted_t) + _GetAppDependenciesResult_t = CFUNCTYPE(None, GetAppDependenciesResult_t) + _DownloadItemResult_t = CFUNCTYPE(None, DownloadItemResult_t) _CreateItemResult = None _SubmitItemUpdateResult = None @@ -21,6 +23,9 @@ class SteamWorkshop(object): _GetAppDependenciesResult = None _RemoteStorageSubscribePublishedFileResult = None _RemoteStorageUnsubscribePublishedFileResult = None + _SteamUGCQueryCompleted = None + _GetAppDependenciesResult = None + _DownloadItemResult = None def __init__(self, steam: object): self.steam = steam @@ -438,3 +443,123 @@ def GetItemDownloadInfo(self, published_file_id: int) -> dict: } return {} + + + def CreateQueryUGCDetailsRequest(self, published_file_ids: list) -> int: + """Create UGC item details query request + + :param published_file_ids: int list + :return: int + """ + published_files_c = (c_uint64 * len(published_file_ids))() + for index, published_file_id in enumerate(published_file_ids): + published_files_c[index] = c_uint64(published_file_id) + + return self.steam.Workshop_CreateQueryUGCDetailsRequest(published_files_c, len(published_file_ids)) + + + def SetQueryUGCRequestCallback(self, callback: object) -> bool: + """Set callback for UGC query + + :param callback: callable + :return: bool + """ + self._SteamUGCQueryCompleted = SteamWorkshop._SteamUGCQueryCompleted_t(callback) + self.steam.Workshop_SetQueryCompletedCallback(self._SteamUGCQueryCompleted) + return True + + + def SendQueryUGCRequest(self, handle: int, callback: object = None, override_callback: bool = False) -> None: + """Create UGC item details query request + + :param handle: query handle + :param callback: callable + :param override_callback: bool + :return: + """ + if override_callback: + self.SetQueryUGCRequestCallback(callback) + + elif callback and not self._SteamUGCQueryCompleted: + self.SetQueryUGCRequestCallback(callback) + + if self._SteamUGCQueryCompleted is None: + raise SetupRequired('Call `SetQueryUGCRequestCallback` first or supply a `callback`') + + self.steam.Workshop_SendQueryUGCRequest(handle) + + + def GetQueryUGCResult(self, handle: int, index: int) -> SteamUGCDetails_t: + """Create UGC item details query request + + :param handle: query handle + :param index: int + :return: SteamUGCDetails_t + """ + details = SteamUGCDetails_t() + self.steam.Workshop_GetQueryUGCResult(handle, index, byref(details)) + return details + + + def SetGetAppDependenciesCallback(self, callback: object) -> bool: + """Set callback for GetAppDependencies result + + :param callback: callable + :return: bool + """ + self._GetAppDependenciesResult = self._GetAppDependenciesResult_t(callback) + self.steam.Workshop_SetGetAppDependenciesCallback(self._GetAppDependenciesResult) + return True + + + def GetAppDependencies(self, published_file_id: int, callback: object = None, + override_callback: bool = False) -> None: + """Get app dependencies for a workshop item + + Returns a list of app IDs that the workshop item depends on. + These are "soft" dependencies shown on the Steam Workshop web page. + The callback may be called multiple times if there are more than 32 dependencies. + + :param published_file_id: int + :param callback: callable - receives GetAppDependenciesResult_t + :param override_callback: bool + :return: None + """ + if override_callback: + self.SetGetAppDependenciesCallback(callback) + + elif callback and not self._GetAppDependenciesResult: + self.SetGetAppDependenciesCallback(callback) + + if self._GetAppDependenciesResult is None: + raise SetupRequired('Call `SetGetAppDependenciesCallback` first or supply a `callback`') + + self.steam.Workshop_GetAppDependencies(published_file_id) + + + def DownloadItem(self, published_file_id: int, high_priority: bool = False, + callback: object = None, override_callback: bool = False) -> bool: + """Initiate or prioritize download of a workshop item + + Downloads or updates a workshop item. If high_priority is True, this item will + be downloaded before any other items. The function returns immediately, and you + should wait for the callback before accessing the item on disk. + + NOTE: The callback will be triggered for all item downloads regardless of the + running application, so check the appID in the callback result. + + :param published_file_id: int + :param high_priority: bool - Set to True to pause other downloads and prioritize this one + :param callback: callable - receives DownloadItemResult_t when download completes (REQUIRED) + :param override_callback: bool + :return: bool - True if download initiated successfully + """ + # Callback is required - set it up internally + if callback is None: + raise ValueError('callback parameter is required for DownloadItem') + + if override_callback or not self._DownloadItemResult: + self._DownloadItemResult = self._DownloadItemResult_t(callback) + self.steam.Workshop_SetDownloadItemCallback(self._DownloadItemResult) + + return self.steam.Workshop_DownloadItem(published_file_id, high_priority) diff --git a/steamworks/methods.py b/steamworks/methods.py index b6f4452..86a30d3 100644 --- a/steamworks/methods.py +++ b/steamworks/methods.py @@ -58,6 +58,7 @@ class InputDigitalActionData_t(Structure): "ActivateGameOverlayToWebPage": {"restype": None, "argtypes": [c_char_p]}, "ActivateGameOverlayToStore": {"restype": None, "argtypes": [c_uint32]}, "ActivateGameOverlayInviteDialog": {"restype": None, "argtypes": [c_uint64]}, + "SetInputActionManifestFilePath": {"restype": bool, "argtypes": [c_char_p]}, "ActivateActionSet": {"restype": None, "argtypes": [c_uint64, c_uint64]}, "GetActionSetHandle": {"restype": c_uint64, "argtypes": [c_char_p]}, "GetAnalogActionHandle": {"restype": c_uint64, "argtypes": [c_char_p]}, @@ -180,7 +181,7 @@ class InputDigitalActionData_t(Structure): POINTER(c_uint32), ], }, - "Workshop_GetAppDependencies": {"restype": bool, "argtypes": [c_uint64]}, + "Workshop_GetAppDependencies": {"restype": None, "argtypes": [c_uint64]}, "Workshop_GetItemDownloadInfo": { "restype": bool, "argtypes": [c_uint64, POINTER(c_uint64), POINTER(c_uint64)], @@ -205,6 +206,28 @@ class InputDigitalActionData_t(Structure): "Workshop_SuspendDownloads": {"restype": None, "argtypes": [c_bool]}, "Workshop_SubscribeItem": {"restype": None, "argtypes": [c_uint64]}, "Workshop_UnsubscribeItem": {"restype": None, "argtypes": [c_uint64]}, + "Workshop_CreateQueryUGCDetailsRequest": { + "restype": c_uint64, + "argtypes": [POINTER(c_uint64), c_uint32], + }, + "Workshop_SetQueryCompletedCallback": { + "restype": None, + "argtypes": [MAKE_CALLBACK(None, structs.SteamUGCQueryCompleted_t)], + }, + "Workshop_SendQueryUGCRequest": {"argtypes": [c_uint64]}, + "Workshop_GetQueryUGCResult": { + "restype": bool, + "argtypes": [c_uint64, c_uint32, POINTER(structs.SteamUGCDetails_t)], + }, + "Workshop_SetGetAppDependenciesCallback": { + "restype": None, + "argtypes": [MAKE_CALLBACK(None, structs.GetAppDependenciesResult_t)], + }, + "Workshop_SetDownloadItemCallback": { + "restype": None, + "argtypes": [MAKE_CALLBACK(None, structs.DownloadItemResult_t)], + }, + "Workshop_DownloadItem": {"restype": c_bool, "argtypes": [c_uint64, c_bool]}, "MicroTxn_SetAuthorizationResponseCallback": { "restype": None, "argtypes": [MAKE_CALLBACK(None, structs.MicroTxnAuthorizationResponse_t)], diff --git a/steamworks/structs.py b/steamworks/structs.py index e93a432..53bdfa1 100644 --- a/steamworks/structs.py +++ b/steamworks/structs.py @@ -49,5 +49,65 @@ class SubscriptionResult(Structure): _fields_ = [("result", c_int32), ("publishedFileId", c_uint64)] + +class SteamUGCQueryCompleted_t(Structure): + _fields_ = [ + ("handle", c_uint64), + ("result", c_int), + ("numResultsReturned", c_uint32), + ("totalMatchingResults", c_uint32), + ("cachedData", c_bool) + ] + + +class SteamUGCDetails_t(Structure): + _fields_ = [ + ("publishedFileId", c_uint64), + ("result", c_int), + ("fileType", c_int), + ("creatorAppID", c_uint32), + ("consumerAppID", c_uint32), + ("title", c_char * 129), + ("description", c_char * 8000), + ("steamIDOwner", c_uint64), + ("timeCreated", c_uint32), + ("timeUpdated", c_uint32), + ("timeAddedToUserList", c_uint32), + ("visibility", c_int), + ("banned", c_bool), + ("acceptedForUse", c_bool), + ("tagsTruncated", c_bool), + ("tags", c_char * 1025), + ("file", c_uint64), + ("previewFile", c_uint64), + ("fileName", c_char * 260), + ("fileSize", c_uint32), + ("previewFileSize", c_uint32), + ("URL", c_char * 256), + ("votesUp", c_uint32), + ("votesDown", c_uint32), + ("score", c_float), + ("numChildren", c_uint32), + ] + + class MicroTxnAuthorizationResponse_t(Structure): _fields_ = [("appId", c_uint32), ("orderId", c_uint64), ("authorized", c_bool)] + + +class GetAppDependenciesResult_t(Structure): + _fields_ = [ + ("result", c_int), + ("publishedFileId", c_uint64), + ("rgAppIDs", c_uint32 * 32), + ("numAppDependencies", c_uint32), + ("totalNumAppDependencies", c_uint32), + ] + + +class DownloadItemResult_t(Structure): + _fields_ = [ + ("appID", c_uint32), + ("publishedFileId", c_uint64), + ("result", c_int), + ]