Skip to content

Commit 989f6f6

Browse files
committed
Add runtime API URL refreshing
1 parent 1463a39 commit 989f6f6

File tree

7 files changed

+99
-52
lines changed

7 files changed

+99
-52
lines changed

src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusClient.kt

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,14 @@ import kotlinx.coroutines.SupervisorJob
1414
import kotlinx.coroutines.launch
1515
import kotlinx.serialization.json.Json
1616
import org.apache.logging.log4j.LogManager
17-
import org.polyfrost.oneconfig.api.event.v1.EventManager
18-
import org.polyfrost.oneconfig.api.event.v1.events.InitializationEvent
1917
import org.polyfrost.oneconfig.utils.v1.dsl.addDefaultCommand
2018
import org.polyfrost.polyplus.PolyPlusConstants
2119
import org.polyfrost.polyplus.client.cosmetics.CosmeticManager
2220
import org.polyfrost.polyplus.client.discord.DiscordPresence
21+
import org.polyfrost.polyplus.client.network.http.PolyAuthorization
2322
import org.polyfrost.polyplus.client.network.http.PolyCosmetics
2423
import org.polyfrost.polyplus.client.network.websocket.PolyConnection
2524
import org.polyfrost.polyplus.client.network.websocket.ServerboundPacket
26-
import java.util.function.Consumer
2725

2826
object PolyPlusClient {
2927
private val LOGGER = LogManager.getLogger(PolyPlusConstants.NAME)
@@ -50,22 +48,42 @@ object PolyPlusClient {
5048
fun initialize() {
5149
PolyPlusConfig.preload()
5250
DiscordPresence.initialize()
53-
PolyConnection.initialize()
51+
PolyConnection.initialize {
52+
LOGGER.info("Connected to PolyPlus WebSocket server.")
5453

55-
SCOPE.launch {
56-
val all = PolyCosmetics.getAll()
57-
.await()
58-
.getOrNull() ?: return@launch
59-
CosmeticManager.putAll(all.contents)
60-
PolyCosmetics.updateOwned()
61-
}
62-
63-
EventManager.register(InitializationEvent::class.java, Consumer {
54+
// Request the local player's active cosmetics
6455
SCOPE.launch {
6556
PolyConnection.sendPacket(ServerboundPacket.GetActiveCosmetics(playerUuid.toString()))
6657
}
67-
})
58+
}
6859

60+
refresh()
6961
PolyPlusConfig.addDefaultCommand(PolyPlusConstants.ID)
7062
}
63+
64+
fun refresh() {
65+
LOGGER.info("Refreshing PolyPlus Client...")
66+
67+
// Synchronously (yet asynchronously) refresh all API data in such a way that we authenticate first,
68+
// then give ourselves time to cache cosmetics, then use said known cached cosmetics to update owned cosmetics.
69+
SCOPE.launch {
70+
// Reset authentication
71+
runCatching { PolyAuthorization.reset() }
72+
73+
// Reset existing caches
74+
runCatching { PolyCosmetics.reset() }
75+
runCatching { CosmeticManager.reset() }
76+
77+
// Cache all available cosmetics
78+
runCatching {
79+
val all = PolyCosmetics.getAll()
80+
.await()
81+
.getOrNull() ?: return@runCatching
82+
CosmeticManager.putAll(all.contents)
83+
}
84+
85+
// Update the local player's owned cosmetics
86+
runCatching { PolyCosmetics.updateOwned() }
87+
}
88+
}
7189
}
Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.polyfrost.polyplus.client
22

3+
import org.apache.logging.log4j.LogManager
34
import org.polyfrost.oneconfig.api.config.v1.Config
45
import org.polyfrost.oneconfig.api.config.v1.annotations.Dropdown
56
import org.polyfrost.oneconfig.api.config.v1.annotations.Switch
@@ -9,20 +10,24 @@ import org.polyfrost.polyplus.client.discord.DiscordPresence
910
import org.polyfrost.polyplus.client.network.websocket.PolyConnection
1011

1112
object PolyPlusConfig : Config("${PolyPlusConstants.ID}.json", PolyPlusConstants.NAME, Category.OTHER) {
12-
@JvmStatic
13-
@Switch(title = "Discord RPC")
13+
@Transient
14+
private val LOGGER = LogManager.getLogger()
15+
16+
@JvmStatic @Switch(title = "Discord RPC")
1417
var isDiscordEnabled = true
1518

1619
@Dropdown(title = "API URL", description = "The URL used for the PolyPlus API. Only change if you know what you're doing.")
1720
var apiUrl: BackendUrl = BackendUrl.PRODUCTION
1821

1922
init {
2023
addCallback("isDiscordEnabled") {
21-
DiscordPresence.start()
24+
DiscordPresence.start() // Ensure that Discord RPC is restarted if the setting was toggled
2225
}
2326

2427
addCallback("apiUrl") {
25-
PolyConnection.reconnect()
28+
LOGGER.info("API URL changed to $apiUrl, refreshing API data...")
29+
PolyConnection.reconnect() // Reconnect WebSocket under new URL
30+
PolyPlusClient.refresh() // Refresh API tokens, cosmetic data, etc.
2631
}
2732
}
2833
}

src/main/kotlin/org/polyfrost/polyplus/client/cosmetics/CosmeticManager.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ object CosmeticManager {
2929
return CACHE[type]?.get(id)?.asResource()
3030
}
3131

32+
fun reset() {
33+
CACHE.clear()
34+
}
35+
3236
suspend fun putAll(cosmetics: List<Cosmetic>) {
3337
withContext(Dispatchers.IO) {
3438
hashManager.awaitHashes()

src/main/kotlin/org/polyfrost/polyplus/client/network/http/PolyAuthorization.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ object PolyAuthorization {
5555
return lockedJob.await().token
5656
}
5757

58+
suspend fun reset() {
59+
LOCK.withLock {
60+
cachedResponse = null
61+
currentJob = null
62+
}
63+
}
64+
5865
private suspend fun authorize(): AuthResponse {
5966
val serverId = generateServerId()
6067
authorizeSessionService(serverId)

src/main/kotlin/org/polyfrost/polyplus/client/network/http/PolyCosmetics.kt

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import io.ktor.client.call.body
55
import io.ktor.client.request.get
66
import kotlinx.coroutines.Deferred
77
import kotlinx.coroutines.async
8-
import kotlinx.coroutines.launch
98
import org.apache.logging.log4j.LogManager
109
import org.polyfrost.polyplus.client.PolyPlusClient
1110
import org.polyfrost.polyplus.client.PolyPlusConfig
@@ -17,31 +16,35 @@ object PolyCosmetics {
1716
private val LOGGER = LogManager.getLogger()
1817
private val CACHE = HashMap<UUID, HashMap<String, Int>>()
1918

20-
fun updateOwned() {
21-
PolyPlusClient.SCOPE.launch {
22-
val cosmetics = PolyPlusClient.HTTP
23-
.getBodyAuthorized<PlayerCosmetics>("${PolyPlusConfig.apiUrl}/cosmetics/player")
24-
.onFailure { LOGGER.error("Failed to fetch owned cosmetics", it) }
25-
.getOrElse { return@launch LOGGER.warn("Could not fetch owned cosmetics for player $playerUuid") }
26-
.owned
27-
for (cosmetic in cosmetics) {
28-
CACHE[playerUuid] = CACHE.getOrPut(playerUuid) {
29-
HashMap()
30-
}.apply {
31-
set(cosmetic.type, cosmetic.id)
32-
}
19+
suspend fun updateOwned() {
20+
val cosmetics = PolyPlusClient.HTTP
21+
.getBodyAuthorized<PlayerCosmetics>("${PolyPlusConfig.apiUrl}/cosmetics/player")
22+
.onFailure { LOGGER.error("Failed to fetch owned cosmetics", it) }
23+
.getOrElse { return LOGGER.warn("Could not fetch owned cosmetics for player $playerUuid") }
24+
.owned
25+
for (cosmetic in cosmetics) {
26+
CACHE[playerUuid] = CACHE.getOrPut(playerUuid) {
27+
HashMap()
28+
}.apply {
29+
set(cosmetic.type, cosmetic.id)
3330
}
3431
}
3532
}
3633

3734
fun getAll(): Deferred<Result<CosmeticList>> = PolyPlusClient.SCOPE.async {
3835
runCatching {
39-
PolyPlusClient.HTTP.get("${PolyPlusConfig.apiUrl}/cosmetics").body<CosmeticList>()
36+
PolyPlusClient.HTTP
37+
.get("${PolyPlusConfig.apiUrl}/cosmetics")
38+
.body<CosmeticList>()
4039
}.onFailure {
4140
LOGGER.warn("Failed to fetch all cosmetics: ${it.message}")
4241
}
4342
}
4443

44+
fun reset() {
45+
CACHE.clear()
46+
}
47+
4548
fun getFor(uuid: UUID): HashMap<String, Int>? {
4649
return CACHE[uuid]
4750
}

src/main/kotlin/org/polyfrost/polyplus/client/network/websocket/PolyConnection.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@ import org.polyfrost.polyplus.client.PolyPlusConfig
1616
object PolyConnection {
1717
private val LOGGER = LogManager.getLogger()
1818

19+
private var connectionCallback: (() -> Unit)? = null
1920
private var job: Job? = null
2021
private var session: DefaultClientWebSocketSession? = null
2122
private val _outgoing = Channel<String>(Channel.Factory.UNLIMITED)
2223

2324
val isConnected: Boolean
2425
get() = session != null
2526

26-
fun initialize() {
27+
fun initialize(callback: (() -> Unit)? = null) {
28+
this.connectionCallback = callback
2729
start() // Just cold start and set up
2830
}
2931

@@ -80,6 +82,7 @@ object PolyConnection {
8082
}
8183
}
8284

85+
connectionCallback?.invoke()
8386
for (frame in incoming) {
8487
val text = (frame as? Frame.Text)?.readText() ?: continue
8588
process(this, text)

versions/1.21.1-fabric/src/main/java/org/polyfrost/polyplus/client/mixin/Mixin_ReplaceCapeTexture.java

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,38 @@
11
package org.polyfrost.polyplus.client.mixin;
22

3-
import net.minecraft.client.network.AbstractClientPlayerEntity;
4-
import net.minecraft.client.network.PlayerListEntry;
5-
import net.minecraft.client.util.SkinTextures;
6-
import net.minecraft.util.Identifier;
7-
import org.polyfrost.polyplus.PolyPlus;
3+
import net.minecraft.client.multiplayer.PlayerInfo;
4+
import net.minecraft.client.player.AbstractClientPlayer;
5+
import net.minecraft.client.resources.PlayerSkin;
6+
import org.polyfrost.polyplus.client.cosmetics.CosmeticManager;
87
import org.spongepowered.asm.mixin.Mixin;
98
import org.spongepowered.asm.mixin.Shadow;
109
import org.spongepowered.asm.mixin.injection.At;
1110
import org.spongepowered.asm.mixin.injection.Inject;
1211
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
1312

14-
@Mixin(AbstractClientPlayerEntity.class)
13+
@Mixin(AbstractClientPlayer.class)
1514
public class Mixin_ReplaceCapeTexture {
16-
@Shadow private PlayerListEntry playerListEntry;
15+
@Shadow private PlayerInfo playerInfo;
1716

18-
@Inject(method = "getSkinTextures", at = @At("HEAD"), cancellable = true)
19-
void polyplus$onGetSkinTextures(CallbackInfoReturnable<SkinTextures> cir) {
20-
if (this.playerListEntry == null) return;
21-
var oldskinTexures = this.playerListEntry.getSkinTextures();
22-
var newSkinTextures = new SkinTextures(
23-
oldskinTexures.comp_1626(),
24-
oldskinTexures.comp_1911(),
25-
Identifier.of(PolyPlus.ID, "moon.png"),
26-
oldskinTexures.comp_1628(),
27-
oldskinTexures.comp_1629(),
28-
oldskinTexures.comp_1630()
17+
@Inject(method = "getSkin", at = @At("HEAD"), cancellable = true)
18+
void polyplus$onGetSkinTextures(CallbackInfoReturnable<PlayerSkin> cir) {
19+
if (this.playerInfo == null) {
20+
return;
21+
}
22+
23+
var capeLocation = CosmeticManager.get(this.playerInfo.getProfile().getId(), "cape");
24+
if (capeLocation == null) {
25+
return;
26+
}
27+
28+
var currentTextures = this.playerInfo.getSkin();
29+
var newSkinTextures = new PlayerSkin(
30+
currentTextures.texture(),
31+
currentTextures.textureUrl(),
32+
capeLocation,
33+
currentTextures.elytraTexture(),
34+
currentTextures.model(),
35+
currentTextures.secure()
2936
);
3037

3138
cir.setReturnValue(newSkinTextures);

0 commit comments

Comments
 (0)