Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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" }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package `in`.dragonbra.javasteam.steam.handlers.steamuserstats

// JavaSteam Addition
/**
* 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<Int>)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package `in`.dragonbra.javasteam.steam.handlers.steamuserstats

// JavaSteam Addition
/**
* 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)
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -147,6 +150,29 @@ class SteamUserStats : ClientMsgHandler() {
return AsyncJobSingle(this.client, msg.sourceJobID)
}

// JavaSteam addition.
/**
* 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].
*/
fun getUserStats(appId: Int, steamID: SteamID): AsyncJobSingle<UserStatsCallback> {
val msg = ClientMsgProtobuf<CMsgClientGetUserStats.Builder>(
CMsgClientGetUserStats::class.java,
EMsg.ClientGetUserStats
).apply {
sourceJobID = client.getNextJobID()

body.gameId = appId.toLong()
body.steamIdForUser = steamID.convertToUInt64()
}

client.send(msg)

return AsyncJobSingle(this.client, msg.sourceJobID)
}

/**
* Handles a client message. This should not be called directly.
* @param packetMsg The packet message that contains the data.
Expand All @@ -163,6 +189,7 @@ class SteamUserStats : ClientMsgHandler() {
EMsg.ClientGetNumberOfCurrentPlayersDPResponse -> NumberOfPlayersCallback(packetMsg)
EMsg.ClientLBSFindOrCreateLBResponse -> FindOrCreateLeaderboardCallback(packetMsg)
EMsg.ClientLBSGetLBEntriesResponse -> LeaderboardEntriesCallback(packetMsg)
EMsg.ClientGetUserStatsResponse -> UserStatsCallback(packetMsg)
else -> null
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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() {

/**
* Gets the result.
*/
val result: EResult

/**
* The game id of the stats.
*/
val gameId: Long

/**
* The crc of the stats.
*/
val crcStats: Int

/**
* The raw schema in [ByteString].
*/
val schema: ByteString

/**
* A [List] of [Stats].
*/
val stats: List<Stats>

/**
* A [List] of [AchievementBlocks].
*/
val achievementBlocks: List<AchievementBlocks>

/**
* The schema converted to [KeyValue].
*/
val schemaKeyValues: KeyValue = KeyValue()

init {
val msg = ClientMsgProtobuf<CMsgClientGetUserStatsResponse.Builder>(
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)
}
}
}