From 091b561c1769c60b0bdebcbf1c2eff09399793b9 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Sat, 12 Jul 2025 21:20:49 -0500 Subject: [PATCH 1/3] Add SteamUserStats.getUserStats --- .../steamuserstats/AchievementBlocks.kt | 7 ++ .../steam/handlers/steamuserstats/Stats.kt | 7 ++ .../handlers/steamuserstats/SteamUserStats.kt | 23 ++++++ .../callback/UserStatsCallback.kt | 80 +++++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/AchievementBlocks.kt create mode 100644 src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/Stats.kt create mode 100644 src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/callback/UserStatsCallback.kt diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/AchievementBlocks.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/AchievementBlocks.kt new file mode 100644 index 00000000..7096d102 --- /dev/null +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/AchievementBlocks.kt @@ -0,0 +1,7 @@ +package `in`.dragonbra.javasteam.steam.handlers.steamuserstats + +// JavaSteam Addition +/** + * TODO + */ +data class AchievementBlocks(val achievementId: Int, val unlockTime: List) diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/Stats.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/Stats.kt new file mode 100644 index 00000000..d78b679b --- /dev/null +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/Stats.kt @@ -0,0 +1,7 @@ +package `in`.dragonbra.javasteam.steam.handlers.steamuserstats + +// JavaSteam Addition +/** + * TODO + */ +data class Stats(val statId: Int, val statValue: Int) diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/SteamUserStats.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/SteamUserStats.kt index 5666a59c..3a91daa1 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/SteamUserStats.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/SteamUserStats.kt @@ -9,12 +9,15 @@ import `in`.dragonbra.javasteam.enums.EMsg import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesClientserver2.CMsgDPGetNumberOfCurrentPlayers import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesClientserverLbs.CMsgClientLBSFindOrCreateLB import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesClientserverLbs.CMsgClientLBSGetLBEntries +import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesClientserverUserstats.CMsgClientGetUserStats import `in`.dragonbra.javasteam.steam.handlers.ClientMsgHandler import `in`.dragonbra.javasteam.steam.handlers.steamuserstats.callback.FindOrCreateLeaderboardCallback import `in`.dragonbra.javasteam.steam.handlers.steamuserstats.callback.LeaderboardEntriesCallback import `in`.dragonbra.javasteam.steam.handlers.steamuserstats.callback.NumberOfPlayersCallback +import `in`.dragonbra.javasteam.steam.handlers.steamuserstats.callback.UserStatsCallback import `in`.dragonbra.javasteam.steam.steamclient.callbackmgr.CallbackMsg import `in`.dragonbra.javasteam.types.AsyncJobSingle +import `in`.dragonbra.javasteam.types.SteamID /** * This handler handles Steam user statistic related actions. @@ -147,6 +150,25 @@ class SteamUserStats : ClientMsgHandler() { return AsyncJobSingle(this.client, msg.sourceJobID) } + // JavaSteam addition. + /** + * TODO + */ + @JvmOverloads + fun getUserStats(appId: Int, steamID: SteamID = client.steamID!!) { + val msg = ClientMsgProtobuf( + CMsgClientGetUserStats::class.java, + EMsg.ClientGetUserStats + ).apply { + sourceJobID = client.getNextJobID() + + body.gameId = appId.toLong() + body.steamIdForUser = steamID.convertToUInt64() + } + + client.send(msg) + } + /** * Handles a client message. This should not be called directly. * @param packetMsg The packet message that contains the data. @@ -163,6 +185,7 @@ class SteamUserStats : ClientMsgHandler() { EMsg.ClientGetNumberOfCurrentPlayersDPResponse -> NumberOfPlayersCallback(packetMsg) EMsg.ClientLBSFindOrCreateLBResponse -> FindOrCreateLeaderboardCallback(packetMsg) EMsg.ClientLBSGetLBEntriesResponse -> LeaderboardEntriesCallback(packetMsg) + EMsg.ClientGetUserStatsResponse -> UserStatsCallback(packetMsg) else -> null } } diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/callback/UserStatsCallback.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/callback/UserStatsCallback.kt new file mode 100644 index 00000000..f2e4602c --- /dev/null +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/callback/UserStatsCallback.kt @@ -0,0 +1,80 @@ +package `in`.dragonbra.javasteam.steam.handlers.steamuserstats.callback + +import com.google.protobuf.ByteString +import `in`.dragonbra.javasteam.base.ClientMsgProtobuf +import `in`.dragonbra.javasteam.base.IPacketMsg +import `in`.dragonbra.javasteam.enums.EResult +import `in`.dragonbra.javasteam.protobufs.steamclient.SteammessagesClientserverUserstats.CMsgClientGetUserStatsResponse +import `in`.dragonbra.javasteam.steam.handlers.steamuserstats.AchievementBlocks +import `in`.dragonbra.javasteam.steam.handlers.steamuserstats.Stats +import `in`.dragonbra.javasteam.steam.handlers.steamuserstats.SteamUserStats +import `in`.dragonbra.javasteam.steam.steamclient.callbackmgr.CallbackMsg +import `in`.dragonbra.javasteam.types.KeyValue +import `in`.dragonbra.javasteam.util.stream.MemoryStream + +// JavaSteam Addition +/** + * This callback is fired in response to [SteamUserStats.getUserStats]. + */ +class UserStatsCallback(packetMsg: IPacketMsg?) : CallbackMsg() { + + /** + * TODO + */ + val result: EResult + + /** + * TODO + */ + val gameId: Long + + /** + * TODO + */ + val crcStats: Int + + /** + * TODO + */ + val schema: ByteString + + /** + * TODO + */ + val stats: List + + /** + * TODO + */ + val achievementBlocks: List + + /** + * TODO + */ + val schemaKeyValues: KeyValue = KeyValue() + + init { + val msg = ClientMsgProtobuf( + CMsgClientGetUserStatsResponse::class.java, + packetMsg + ) + val resp = msg.body + + jobID = msg.targetJobID + result = EResult.from(resp.eresult) + + gameId = resp.gameId + crcStats = resp.crcStats + schema = resp.schema + stats = resp.statsList.map { + Stats(statId = it.statId, statValue = it.statValue) + } + achievementBlocks = resp.achievementBlocksList.map { + AchievementBlocks(achievementId = it.achievementId, unlockTime = it.unlockTimeList) + } + + MemoryStream(schema.toByteArray()).use { + schemaKeyValues.tryReadAsBinary(it) + } + } +} From 291dfb4e25ce57049a6b66915cb6478f1cee1884 Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Fri, 18 Jul 2025 01:24:20 -0500 Subject: [PATCH 2/3] Add kdoc and tweak user stats method. --- .../handlers/steamuserstats/AchievementBlocks.kt | 5 ++++- .../steam/handlers/steamuserstats/Stats.kt | 5 ++++- .../handlers/steamuserstats/SteamUserStats.kt | 10 +++++++--- .../steamuserstats/callback/UserStatsCallback.kt | 14 +++++++------- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/AchievementBlocks.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/AchievementBlocks.kt index 7096d102..94a57dfd 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/AchievementBlocks.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/AchievementBlocks.kt @@ -2,6 +2,9 @@ package `in`.dragonbra.javasteam.steam.handlers.steamuserstats // JavaSteam Addition /** - * TODO + * A Block of achievements with the timestamp of when the achievement (in order of the schema) is unlocked. + * @param achievementId the achievement id. + * @param unlockTime a [List] of integers containing when an achievement was unlocked. + * An unlockTime of 0 means it has not been achieved, unlocked achievements are displayed as valve-timestamps. */ data class AchievementBlocks(val achievementId: Int, val unlockTime: List) diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/Stats.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/Stats.kt index d78b679b..0c5ac8ba 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/Stats.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/Stats.kt @@ -2,6 +2,9 @@ package `in`.dragonbra.javasteam.steam.handlers.steamuserstats // JavaSteam Addition /** - * TODO + * A Class representing stat values of a game. + * This data is commonly used for richer stats in games that support it. For example: Left 4 Dead 2. + * @param statId The id of the stat. This is used to reference the id in the schema. + * @param statValue The value of the stat. */ data class Stats(val statId: Int, val statValue: Int) diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/SteamUserStats.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/SteamUserStats.kt index 3a91daa1..722d2c53 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/SteamUserStats.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/SteamUserStats.kt @@ -152,10 +152,12 @@ class SteamUserStats : ClientMsgHandler() { // JavaSteam addition. /** - * TODO + * Gets the Stats-Schema for the specified app. This schema includes Global Achievements and Stats, + * @param appId The appID of the game. + * @param steamID The [SteamID] that owns the game. Note the SteamID user has to have a public profile. + * @return The Job ID of the request. This can be used to find the appropriate [UserStatsCallback]. */ - @JvmOverloads - fun getUserStats(appId: Int, steamID: SteamID = client.steamID!!) { + fun getUserStats(appId: Int, steamID: SteamID): AsyncJobSingle { val msg = ClientMsgProtobuf( CMsgClientGetUserStats::class.java, EMsg.ClientGetUserStats @@ -167,6 +169,8 @@ class SteamUserStats : ClientMsgHandler() { } client.send(msg) + + return AsyncJobSingle(this.client, msg.sourceJobID) } /** diff --git a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/callback/UserStatsCallback.kt b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/callback/UserStatsCallback.kt index f2e4602c..de8eca6b 100644 --- a/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/callback/UserStatsCallback.kt +++ b/src/main/java/in/dragonbra/javasteam/steam/handlers/steamuserstats/callback/UserStatsCallback.kt @@ -19,37 +19,37 @@ import `in`.dragonbra.javasteam.util.stream.MemoryStream class UserStatsCallback(packetMsg: IPacketMsg?) : CallbackMsg() { /** - * TODO + * Gets the result. */ val result: EResult /** - * TODO + * The game id of the stats. */ val gameId: Long /** - * TODO + * The crc of the stats. */ val crcStats: Int /** - * TODO + * The raw schema in [ByteString]. */ val schema: ByteString /** - * TODO + * A [List] of [Stats]. */ val stats: List /** - * TODO + * A [List] of [AchievementBlocks]. */ val achievementBlocks: List /** - * TODO + * The schema converted to [KeyValue]. */ val schemaKeyValues: KeyValue = KeyValue() From b81efc19c39eda53bc61c394c51257c9c266a2bd Mon Sep 17 00:00:00 2001 From: LossyDragon Date: Fri, 18 Jul 2025 10:23:32 -0500 Subject: [PATCH 3/3] Simplify ktor's dependency references. --- gradle/libs.versions.toml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c9e72842..3236a389 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,6 +18,7 @@ commons-validator = "1.9.0" # https://mvnrepository.com/artifact/commons-validat gson = "2.13.1" # https://mvnrepository.com/artifact/com.google.code.gson/gson jacoco = "0.8.13" # https://www.eclemma.org/jacoco kotlin-coroutines = "1.10.2" # https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core +ktor = "3.2.1" # https://mvnrepository.com/artifact/io.ktor/ktor-client-cio okHttp = "5.0.0-alpha.14" # https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp protobuf = "4.30.2" # https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java protobuf-gradle = "0.9.5" # https://mvnrepository.com/artifact/com.google.protobuf/protobuf-gradle-plugin @@ -40,9 +41,9 @@ commons-validator = { module = "commons-validator:commons-validator", version.re gson = { module = "com.google.code.gson:gson", version.ref = "gson" } kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" } kotlin-stdib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } -ktor-client-cio = { module = "io.ktor:ktor-client-cio", version = "3.0.3" } -ktor-client-core = { module = "io.ktor:ktor-client-core", version = "3.0.3" } -ktor-client-websocket = { module = "io.ktor:ktor-client-websockets", version = "3.0.3" } +ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } +ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } +ktor-client-websocket = { module = "io.ktor:ktor-client-websockets", version.ref = "ktor" } okHttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okHttp" } protobuf-java = { module = "com.google.protobuf:protobuf-java", version.ref = "protobuf" } protobuf-protoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" }