From 4c68b0f837ec74d0de9331e13b254aa9c21d04b6 Mon Sep 17 00:00:00 2001 From: aeroeng15 Date: Tue, 13 Jan 2026 20:43:32 -0700 Subject: [PATCH 01/14] Caching trade friendships to player data --- .../com/cobblemon/mod/common/Cobblemon.kt | 1 + .../api/storage/player/GeneralPlayerData.kt | 4 +- .../player/adapter/PlayerDataJsonBackend.kt | 3 +- .../player/adapter/PlayerDataMongoBackend.kt | 3 +- .../player/client/ClientGeneralPlayerData.kt | 8 +++- .../mod/common/events/StorageHandler.kt | 33 ++++++++++++++ .../mod/common/trade/TradeManager.kt | 45 ++++++++++++++++++- 7 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 common/src/main/kotlin/com/cobblemon/mod/common/events/StorageHandler.kt diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt b/common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt index e358931c0b..4866e7fd11 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt @@ -482,6 +482,7 @@ object Cobblemon { AdvancementHandler.registerListeners() PokedexHandler.registerListeners() StatHandler.registerListeners() + StorageHandler.registerListeners() } fun getLevel(dimension: ResourceKey): Level? { diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/GeneralPlayerData.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/GeneralPlayerData.kt index 8ebe44dde0..6b1399c955 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/GeneralPlayerData.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/GeneralPlayerData.kt @@ -30,6 +30,7 @@ data class GeneralPlayerData( var battleTheme: ResourceLocation?, var partySelectTutorialDone: Boolean = false, val extraData: MutableMap, + var tradedUUIDs: MutableMap = mutableMapOf() ) : InstancedPlayerData { var advancementData: PlayerAdvancementData = PlayerAdvancementData() @@ -55,7 +56,8 @@ data class GeneralPlayerData( starterUUID, advancementData.totalBattleVictoryCount == 0, battleTheme, - partySelectTutorialDone + partySelectTutorialDone, + tradedUUIDs ) } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataJsonBackend.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataJsonBackend.kt index f0accdb145..efed8a9b87 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataJsonBackend.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataJsonBackend.kt @@ -37,7 +37,8 @@ class PlayerDataJsonBackend: JsonBackedPlayerDataStoreBackend keyItems = Cobblemon.config.defaultKeyItems, extraData = mutableMapOf(), battleTheme = CobblemonSounds.PVP_BATTLE.location, - partySelectTutorialDone = false + partySelectTutorialDone = false, + tradedUUIDs = mutableMapOf() )} override val gson = GsonBuilder() diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataMongoBackend.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataMongoBackend.kt index 8f25bb943a..8ad2580f7d 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataMongoBackend.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataMongoBackend.kt @@ -38,7 +38,8 @@ class PlayerDataMongoBackend(mongoClient: MongoClient, databaseName: String, col keyItems = Cobblemon.config.defaultKeyItems, extraData = mutableMapOf(), battleTheme = CobblemonSounds.PVP_BATTLE.location, - partySelectTutorialDone = false + partySelectTutorialDone = false, + tradedUUIDs = mutableMapOf() )} override val gson = GsonBuilder() diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/client/ClientGeneralPlayerData.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/client/ClientGeneralPlayerData.kt index b3e8ba2e2c..e6b9ee50f3 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/client/ClientGeneralPlayerData.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/client/ClientGeneralPlayerData.kt @@ -35,7 +35,8 @@ data class ClientGeneralPlayerData( var starterUUID: UUID? = null, var showChallengeLabel: Boolean = true, val battleTheme: ResourceLocation? = null, - var partySelectTutorialDone: Boolean = false + var partySelectTutorialDone: Boolean = false, + val tradedUUIDs: Map = mutableMapOf() ) : ClientInstancedPlayerData { override fun encode(buf: RegistryFriendlyByteBuf) { @@ -48,6 +49,7 @@ data class ClientGeneralPlayerData( buf.writeNullable(resetStarters) { pb, value -> pb.writeBoolean(value) } buf.writeNullable(battleTheme) {pb, value -> pb.writeIdentifier(value)} buf.writeBoolean(partySelectTutorialDone) + buf.writeMap(tradedUUIDs, { _, key -> buf.writeUUID(key) }, { _, value -> buf.writeInt(value) }) } companion object { fun decode(buffer: RegistryFriendlyByteBuf): SetClientPlayerDataPacket { @@ -60,6 +62,7 @@ data class ClientGeneralPlayerData( val resetStarterPrompt = buffer.readNullable { it.readBoolean() } val battleTheme = buffer.readNullable { it.readIdentifier() } val partySelectTutorialDone = buffer.readBoolean() + val tradedUUIDs = buffer.readMap({ buffer.readUUID() }, { buffer.readInt() }) val data = ClientGeneralPlayerData( resetStarterPrompt, promptStarter, @@ -68,7 +71,8 @@ data class ClientGeneralPlayerData( starterUUID, showChallengeLabel, battleTheme, - partySelectTutorialDone + partySelectTutorialDone, + tradedUUIDs ) //Weird to do this, but since the flag doesn't get passed to the decoded obj, do it here //Should be fine, as long as decode doesn't get run on the server for some reason diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/events/StorageHandler.kt b/common/src/main/kotlin/com/cobblemon/mod/common/events/StorageHandler.kt new file mode 100644 index 0000000000..08bcfe268c --- /dev/null +++ b/common/src/main/kotlin/com/cobblemon/mod/common/events/StorageHandler.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 Cobblemon Contributors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.cobblemon.mod.common.events + +import com.cobblemon.mod.common.Cobblemon +import com.cobblemon.mod.common.api.Priority +import com.cobblemon.mod.common.api.events.CobblemonEvents.POKEMON_RELEASED_EVENT_POST +import com.cobblemon.mod.common.api.events.storage.ReleasePokemonEvent +import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes +import com.cobblemon.mod.common.util.getPlayer +import java.util.UUID + +object StorageHandler : EventHandler { + override fun registerListeners() { + POKEMON_RELEASED_EVENT_POST.subscribe(Priority.NORMAL, ::onRelease) + } + + fun onRelease(event: ReleasePokemonEvent.Post) { + val player = UUID.fromString(event.pokemon.originalTrainer).getPlayer() + if (player != null) { + val playerData = Cobblemon.playerDataManager.getGenericData(player) + playerData.tradedUUIDs.remove(event.pokemon.uuid) + Cobblemon.playerDataManager.saveSingle(playerData, PlayerInstancedDataStoreTypes.GENERAL) + playerData.sendToPlayer(player) + } + } +} \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/trade/TradeManager.kt b/common/src/main/kotlin/com/cobblemon/mod/common/trade/TradeManager.kt index 8a4de22704..7b312d29ed 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/trade/TradeManager.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/trade/TradeManager.kt @@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.events.CobblemonEvents import com.cobblemon.mod.common.api.events.pokemon.TradeEvent import com.cobblemon.mod.common.api.interaction.ServerPlayerActionRequest import com.cobblemon.mod.common.api.net.NetworkPacket +import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes import com.cobblemon.mod.common.api.text.aqua import com.cobblemon.mod.common.net.messages.client.trade.TradeOfferExpiredPacket import com.cobblemon.mod.common.net.messages.client.trade.TradeOfferNotificationPacket @@ -108,6 +109,15 @@ object TradeManager : RequestManager() { val party1 = player1.party val party2 = player2.party + val player1Data = when(player1) { + is PlayerTradeParticipant -> Cobblemon.playerDataManager.getGenericData(player1.player) + else -> null + } + val player2Data = when(player2) { + is PlayerTradeParticipant -> Cobblemon.playerDataManager.getGenericData(player2.player) + else -> null + } + if (pokemon1 !in party1 || pokemon2 !in party2) { Cobblemon.LOGGER.warn("Attempted to perform a trade with Pokémon that aren't in the party anymore. Could be attempted duping between ${player1.name} and ${player2.name}") activeTrade.cancelTrade() @@ -118,8 +128,39 @@ object TradeManager : RequestManager() { party1.remove(pokemon1) party2.remove(pokemon2) - pokemon1.setFriendship(pokemon1.form.baseFriendship) - pokemon2.setFriendship(pokemon2.form.baseFriendship) + // If player1 is the OT, keep track of the friendship + if (pokemon1.originalTrainer == player1Data?.uuid.toString()) { + player1Data?.tradedUUIDs[pokemon1.uuid] = pokemon1.friendship + } + // If player2 is the OT, keep track of the friendship + if (pokemon2.originalTrainer == player2Data?.uuid.toString()) { + player2Data?.tradedUUIDs[pokemon2.uuid] = pokemon2.friendship + } + + // If pokemon2 was originally player1's, set the friendship back to what it was at point of trading + if ( (player1Data != null) && (pokemon2.originalTrainer == player1Data.uuid.toString()) && (pokemon2.uuid in player1Data.tradedUUIDs) ) { + val friendship = player1Data.tradedUUIDs.remove(pokemon2.uuid)!! + pokemon2.setFriendship(friendship) + } else { // Otherwise, reset the friendship + pokemon2.setFriendship(pokemon2.form.baseFriendship) + } + // If pokemon1 was originally player2's, set the friendship back to what it was at point of trading + if ( (player2Data != null) && (pokemon1.originalTrainer == player2Data.uuid.toString()) && (pokemon1.uuid in player2Data.tradedUUIDs) ) { + val friendship = player2Data.tradedUUIDs.remove(pokemon1.uuid)!! + pokemon1.setFriendship(friendship) + } else { // Otherwise, reset the friendship + pokemon1.setFriendship(pokemon1.form.baseFriendship) + } + + // Save & update + if (player1Data != null) { + Cobblemon.playerDataManager.saveSingle(player1Data, PlayerInstancedDataStoreTypes.GENERAL) + player1Data.sendToPlayer((player1 as PlayerTradeParticipant).player) + } + if (player2Data != null) { + Cobblemon.playerDataManager.saveSingle(player2Data, PlayerInstancedDataStoreTypes.GENERAL) + player2Data.sendToPlayer((player2 as PlayerTradeParticipant).player) + } party2.add(pokemon1) party1.add(pokemon2) From 4b79d51f7b67d5473d9bae10fe6f6506b057e54d Mon Sep 17 00:00:00 2001 From: aeroeng15 Date: Wed, 14 Jan 2026 18:56:11 -0700 Subject: [PATCH 02/14] Cache management while players are offline --- .../com/cobblemon/mod/common/events/StorageHandler.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/events/StorageHandler.kt b/common/src/main/kotlin/com/cobblemon/mod/common/events/StorageHandler.kt index 08bcfe268c..c88a87467c 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/events/StorageHandler.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/events/StorageHandler.kt @@ -22,12 +22,11 @@ object StorageHandler : EventHandler { } fun onRelease(event: ReleasePokemonEvent.Post) { - val player = UUID.fromString(event.pokemon.originalTrainer).getPlayer() - if (player != null) { - val playerData = Cobblemon.playerDataManager.getGenericData(player) + val playerUUID = UUID.fromString(event.pokemon.originalTrainer) + Cobblemon.playerDataManager.getGenericData(playerUUID).let { playerData -> playerData.tradedUUIDs.remove(event.pokemon.uuid) Cobblemon.playerDataManager.saveSingle(playerData, PlayerInstancedDataStoreTypes.GENERAL) - playerData.sendToPlayer(player) + playerUUID.getPlayer()?.let { player -> playerData.sendToPlayer(player) } } } } \ No newline at end of file From ba15ca3d025d7ca4b2e135f17d5c413749c06b01 Mon Sep 17 00:00:00 2001 From: aeroeng15 Date: Wed, 14 Jan 2026 19:12:48 -0700 Subject: [PATCH 03/14] Changelog --- CHANGELOG.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7c56764e4..a254777879 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -194,6 +194,42 @@ - Resolved the niche issue where Structures would spawn in an incorrect location, creating very funky world gen. - Fixed Galarian Ponyta missing its cry. - Fixed various log warnings caused by redundant files and sound events. + - Fixes https://gitlab.com/cable-mc/cobblemon/-/issues/1943 + - Fix occasional crash on retrieving revived fossils +- Fixed an issue in the OmniPathNodeMaker/Navigation that prevented Combees from pathing into and out of Saccharine leaves. +- Fixed orientation not getting reset after crashing while flying and getting back on the Pokémon. +- Fixed Furfrou not being trimmable on NeoForge. +- Fixed shiny Pokémon not respecting the silent flag on spawn. +- Fixed honey from Saccharine Leaves being harvestable at less than the max age. +- Items given from interacting with Saccharine Leaves are placed in the active hotbar slot if possible. +- Fix all Pokémon being saved to chunks and never despawning. +- Fixed possible error on world generation when Combees are placed in naturally generated bee nests. +- Fixed PokeSnacks crashing if there was no available spawn. +- Fixed Pokémon spawn influences not being specific to regional forms +- Fixed crashing when viewing another player in spectator mode +- Fixed missing apricorn textures for Adorn blocks +- Fixed crashing when riding due to malformed json file, instead resetting it +- Fixed mochi items consuming two items at a time +- Fix item interaction sometimes not working properly when playing on servers. +- Fixed Soothe Bell not being properly tagged as a held item. +- Fixed Mint Leaves not being usable for filling the Resurrection Machine. +- Added Saccharine Boats and Saccharine Boat with Chests to relevant boat tags. +- Fixed the `run_script` Molang function to not fail if the environment's context is null. +- Fixed the movesets of certain Pokémon being incorrectly sanitized upon reload. +- Fixed the ordering of Aprijuice's riding stat boosts to always have the same order. +- Fixed the Molang functions `date_local_time`, `date_of`, `date_is_after` to use the correct date format. +- Fixed the bottom half of pasture blocks having an incorrect hit-box when facing north or south. +- Fixed Galarian Mr. Mime incorrectly being rideable. +- Fixed gimmicks not being usable in double / triple battles +- Fixed gimmick buttons showing when already used in the same turn in double / triple battles +- Fixed Apricorn block model rotation for counters/sinks/cupboards/drawers to match that of most recent version of Adorn. +- Fixed Fresh Start Mochi EV reset not syncing health changes properly. +- Fixed requirements such as `biomeCondition` crashing the game when used to define Pokémon interactions. +- Fixed Pokémon interactions not playing sound effects when the volume was not specified. +- Fixed Pokémon interaction cooldowns taking longer than intended when the Pokémon was in the player’s party. +- Fixed Pokémon interactions occasionally crashing the game when spamming an interaction with a 0 cooldown. +- Fixed Pokémon fullness decreasing more than intended when the Pokémon was pastured. +- Traded Pokémon will now retain the original friendship level when traded back to its OT ### Developer - Added `PokeSnackSpawnerFactory` which allows influence registration on PokeSnack encounters From 64a3f2dd2858987c411de430ad701d2fbd4f5e98 Mon Sep 17 00:00:00 2001 From: aeroeng15 Date: Sat, 7 Feb 2026 19:57:32 -0700 Subject: [PATCH 04/14] Fix after rebase --- CHANGELOG.md | 35 +------------------ .../com/cobblemon/mod/common/Cobblemon.kt | 1 + 2 files changed, 2 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a254777879..c55ebd45e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -196,40 +196,7 @@ - Fixed various log warnings caused by redundant files and sound events. - Fixes https://gitlab.com/cable-mc/cobblemon/-/issues/1943 - Fix occasional crash on retrieving revived fossils -- Fixed an issue in the OmniPathNodeMaker/Navigation that prevented Combees from pathing into and out of Saccharine leaves. -- Fixed orientation not getting reset after crashing while flying and getting back on the Pokémon. -- Fixed Furfrou not being trimmable on NeoForge. -- Fixed shiny Pokémon not respecting the silent flag on spawn. -- Fixed honey from Saccharine Leaves being harvestable at less than the max age. -- Items given from interacting with Saccharine Leaves are placed in the active hotbar slot if possible. -- Fix all Pokémon being saved to chunks and never despawning. -- Fixed possible error on world generation when Combees are placed in naturally generated bee nests. -- Fixed PokeSnacks crashing if there was no available spawn. -- Fixed Pokémon spawn influences not being specific to regional forms -- Fixed crashing when viewing another player in spectator mode -- Fixed missing apricorn textures for Adorn blocks -- Fixed crashing when riding due to malformed json file, instead resetting it -- Fixed mochi items consuming two items at a time -- Fix item interaction sometimes not working properly when playing on servers. -- Fixed Soothe Bell not being properly tagged as a held item. -- Fixed Mint Leaves not being usable for filling the Resurrection Machine. -- Added Saccharine Boats and Saccharine Boat with Chests to relevant boat tags. -- Fixed the `run_script` Molang function to not fail if the environment's context is null. -- Fixed the movesets of certain Pokémon being incorrectly sanitized upon reload. -- Fixed the ordering of Aprijuice's riding stat boosts to always have the same order. -- Fixed the Molang functions `date_local_time`, `date_of`, `date_is_after` to use the correct date format. -- Fixed the bottom half of pasture blocks having an incorrect hit-box when facing north or south. -- Fixed Galarian Mr. Mime incorrectly being rideable. -- Fixed gimmicks not being usable in double / triple battles -- Fixed gimmick buttons showing when already used in the same turn in double / triple battles -- Fixed Apricorn block model rotation for counters/sinks/cupboards/drawers to match that of most recent version of Adorn. -- Fixed Fresh Start Mochi EV reset not syncing health changes properly. -- Fixed requirements such as `biomeCondition` crashing the game when used to define Pokémon interactions. -- Fixed Pokémon interactions not playing sound effects when the volume was not specified. -- Fixed Pokémon interaction cooldowns taking longer than intended when the Pokémon was in the player’s party. -- Fixed Pokémon interactions occasionally crashing the game when spamming an interaction with a 0 cooldown. -- Fixed Pokémon fullness decreasing more than intended when the Pokémon was pastured. -- Traded Pokémon will now retain the original friendship level when traded back to its OT + - Traded Pokémon will now retain the original friendship level when traded back to its OT ### Developer - Added `PokeSnackSpawnerFactory` which allows influence registration on PokeSnack encounters diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt b/common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt index 4866e7fd11..2d16bbb26a 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt @@ -90,6 +90,7 @@ import com.cobblemon.mod.common.events.EntityCallbackHandler import com.cobblemon.mod.common.events.PokedexHandler import com.cobblemon.mod.common.events.ServerTickHandler import com.cobblemon.mod.common.events.StatHandler +import com.cobblemon.mod.common.events.StorageHandler import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket import com.cobblemon.mod.common.permission.LaxPermissionValidator import com.cobblemon.mod.common.platform.events.PlatformEvents From e489bcde0751336fb58d9d62db9cf1069cf1db4f Mon Sep 17 00:00:00 2001 From: aeroeng15 Date: Fri, 20 Feb 2026 11:46:03 -0700 Subject: [PATCH 05/14] Change from player-based to pokemon-based storage --- .../com/cobblemon/mod/common/Cobblemon.kt | 2 - .../api/storage/player/GeneralPlayerData.kt | 6 +-- .../player/adapter/PlayerDataJsonBackend.kt | 3 +- .../player/adapter/PlayerDataMongoBackend.kt | 3 +- .../player/client/ClientGeneralPlayerData.kt | 8 +--- .../mod/common/events/StorageHandler.kt | 32 --------------- .../cobblemon/mod/common/pokemon/Pokemon.kt | 18 +++++++++ .../mod/common/trade/TradeManager.kt | 40 +++---------------- .../com/cobblemon/mod/common/util/DataKeys.kt | 1 + .../util/codec/internal/ClientPokemonP3.kt | 4 ++ .../common/util/codec/internal/PokemonP3.kt | 6 +++ 11 files changed, 41 insertions(+), 82 deletions(-) delete mode 100644 common/src/main/kotlin/com/cobblemon/mod/common/events/StorageHandler.kt diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt b/common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt index 2d16bbb26a..e358931c0b 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/Cobblemon.kt @@ -90,7 +90,6 @@ import com.cobblemon.mod.common.events.EntityCallbackHandler import com.cobblemon.mod.common.events.PokedexHandler import com.cobblemon.mod.common.events.ServerTickHandler import com.cobblemon.mod.common.events.StatHandler -import com.cobblemon.mod.common.events.StorageHandler import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket import com.cobblemon.mod.common.permission.LaxPermissionValidator import com.cobblemon.mod.common.platform.events.PlatformEvents @@ -483,7 +482,6 @@ object Cobblemon { AdvancementHandler.registerListeners() PokedexHandler.registerListeners() StatHandler.registerListeners() - StorageHandler.registerListeners() } fun getLevel(dimension: ResourceKey): Level? { diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/GeneralPlayerData.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/GeneralPlayerData.kt index 6b1399c955..c2201953ed 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/GeneralPlayerData.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/GeneralPlayerData.kt @@ -29,8 +29,7 @@ data class GeneralPlayerData( var keyItems: MutableSet, var battleTheme: ResourceLocation?, var partySelectTutorialDone: Boolean = false, - val extraData: MutableMap, - var tradedUUIDs: MutableMap = mutableMapOf() + val extraData: MutableMap ) : InstancedPlayerData { var advancementData: PlayerAdvancementData = PlayerAdvancementData() @@ -56,8 +55,7 @@ data class GeneralPlayerData( starterUUID, advancementData.totalBattleVictoryCount == 0, battleTheme, - partySelectTutorialDone, - tradedUUIDs + partySelectTutorialDone ) } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataJsonBackend.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataJsonBackend.kt index efed8a9b87..f0accdb145 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataJsonBackend.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataJsonBackend.kt @@ -37,8 +37,7 @@ class PlayerDataJsonBackend: JsonBackedPlayerDataStoreBackend keyItems = Cobblemon.config.defaultKeyItems, extraData = mutableMapOf(), battleTheme = CobblemonSounds.PVP_BATTLE.location, - partySelectTutorialDone = false, - tradedUUIDs = mutableMapOf() + partySelectTutorialDone = false )} override val gson = GsonBuilder() diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataMongoBackend.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataMongoBackend.kt index 8ad2580f7d..8f25bb943a 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataMongoBackend.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/adapter/PlayerDataMongoBackend.kt @@ -38,8 +38,7 @@ class PlayerDataMongoBackend(mongoClient: MongoClient, databaseName: String, col keyItems = Cobblemon.config.defaultKeyItems, extraData = mutableMapOf(), battleTheme = CobblemonSounds.PVP_BATTLE.location, - partySelectTutorialDone = false, - tradedUUIDs = mutableMapOf() + partySelectTutorialDone = false )} override val gson = GsonBuilder() diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/client/ClientGeneralPlayerData.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/client/ClientGeneralPlayerData.kt index e6b9ee50f3..b3e8ba2e2c 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/client/ClientGeneralPlayerData.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/client/ClientGeneralPlayerData.kt @@ -35,8 +35,7 @@ data class ClientGeneralPlayerData( var starterUUID: UUID? = null, var showChallengeLabel: Boolean = true, val battleTheme: ResourceLocation? = null, - var partySelectTutorialDone: Boolean = false, - val tradedUUIDs: Map = mutableMapOf() + var partySelectTutorialDone: Boolean = false ) : ClientInstancedPlayerData { override fun encode(buf: RegistryFriendlyByteBuf) { @@ -49,7 +48,6 @@ data class ClientGeneralPlayerData( buf.writeNullable(resetStarters) { pb, value -> pb.writeBoolean(value) } buf.writeNullable(battleTheme) {pb, value -> pb.writeIdentifier(value)} buf.writeBoolean(partySelectTutorialDone) - buf.writeMap(tradedUUIDs, { _, key -> buf.writeUUID(key) }, { _, value -> buf.writeInt(value) }) } companion object { fun decode(buffer: RegistryFriendlyByteBuf): SetClientPlayerDataPacket { @@ -62,7 +60,6 @@ data class ClientGeneralPlayerData( val resetStarterPrompt = buffer.readNullable { it.readBoolean() } val battleTheme = buffer.readNullable { it.readIdentifier() } val partySelectTutorialDone = buffer.readBoolean() - val tradedUUIDs = buffer.readMap({ buffer.readUUID() }, { buffer.readInt() }) val data = ClientGeneralPlayerData( resetStarterPrompt, promptStarter, @@ -71,8 +68,7 @@ data class ClientGeneralPlayerData( starterUUID, showChallengeLabel, battleTheme, - partySelectTutorialDone, - tradedUUIDs + partySelectTutorialDone ) //Weird to do this, but since the flag doesn't get passed to the decoded obj, do it here //Should be fine, as long as decode doesn't get run on the server for some reason diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/events/StorageHandler.kt b/common/src/main/kotlin/com/cobblemon/mod/common/events/StorageHandler.kt deleted file mode 100644 index c88a87467c..0000000000 --- a/common/src/main/kotlin/com/cobblemon/mod/common/events/StorageHandler.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2023 Cobblemon Contributors - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - -package com.cobblemon.mod.common.events - -import com.cobblemon.mod.common.Cobblemon -import com.cobblemon.mod.common.api.Priority -import com.cobblemon.mod.common.api.events.CobblemonEvents.POKEMON_RELEASED_EVENT_POST -import com.cobblemon.mod.common.api.events.storage.ReleasePokemonEvent -import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes -import com.cobblemon.mod.common.util.getPlayer -import java.util.UUID - -object StorageHandler : EventHandler { - override fun registerListeners() { - POKEMON_RELEASED_EVENT_POST.subscribe(Priority.NORMAL, ::onRelease) - } - - fun onRelease(event: ReleasePokemonEvent.Post) { - val playerUUID = UUID.fromString(event.pokemon.originalTrainer) - Cobblemon.playerDataManager.getGenericData(playerUUID).let { playerData -> - playerData.tradedUUIDs.remove(event.pokemon.uuid) - Cobblemon.playerDataManager.saveSingle(playerData, PlayerInstancedDataStoreTypes.GENERAL) - playerUUID.getPlayer()?.let { player -> playerData.sendToPlayer(player) } - } - } -} \ No newline at end of file diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt index d013e9637e..787a1f7a27 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt @@ -543,6 +543,12 @@ open class Pokemon : ShowdownIdentifiable { onChange(OriginalTrainerUpdatePacket({ this }, value)) } + /** + * Cache friendship on trade for OT + */ + var originalTrainerFriendship: Int? = null + internal set + /** * All moves that the Pokémon has, at some point, known. This is to allow players to * swap in moves they've used before at any time, while holding onto the remaining PP @@ -2263,6 +2269,18 @@ open class Pokemon : ShowdownIdentifiable { onChange(packet) } + fun cacheFriendship(playerID: String) { + if (originalTrainer == playerID) originalTrainerFriendship = friendship + } + + fun restoreFriendship(playerID: String) : Boolean { + if (originalTrainer == playerID) { + originalTrainerFriendship?.let { friendship = it } + return true + } + return false + } + /** * Function to run when a save-able change has been made to the Pokémon. This takes a packet to send to watching * players just for convenience, but the main thing is that this will notify the store that this Pokémon is in diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/trade/TradeManager.kt b/common/src/main/kotlin/com/cobblemon/mod/common/trade/TradeManager.kt index 7b312d29ed..a8e9b90454 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/trade/TradeManager.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/trade/TradeManager.kt @@ -14,7 +14,6 @@ import com.cobblemon.mod.common.api.events.CobblemonEvents import com.cobblemon.mod.common.api.events.pokemon.TradeEvent import com.cobblemon.mod.common.api.interaction.ServerPlayerActionRequest import com.cobblemon.mod.common.api.net.NetworkPacket -import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes import com.cobblemon.mod.common.api.text.aqua import com.cobblemon.mod.common.net.messages.client.trade.TradeOfferExpiredPacket import com.cobblemon.mod.common.net.messages.client.trade.TradeOfferNotificationPacket @@ -109,15 +108,6 @@ object TradeManager : RequestManager() { val party1 = player1.party val party2 = player2.party - val player1Data = when(player1) { - is PlayerTradeParticipant -> Cobblemon.playerDataManager.getGenericData(player1.player) - else -> null - } - val player2Data = when(player2) { - is PlayerTradeParticipant -> Cobblemon.playerDataManager.getGenericData(player2.player) - else -> null - } - if (pokemon1 !in party1 || pokemon2 !in party2) { Cobblemon.LOGGER.warn("Attempted to perform a trade with Pokémon that aren't in the party anymore. Could be attempted duping between ${player1.name} and ${player2.name}") activeTrade.cancelTrade() @@ -129,39 +119,21 @@ object TradeManager : RequestManager() { party2.remove(pokemon2) // If player1 is the OT, keep track of the friendship - if (pokemon1.originalTrainer == player1Data?.uuid.toString()) { - player1Data?.tradedUUIDs[pokemon1.uuid] = pokemon1.friendship - } + pokemon1.cacheFriendship(player1.uuid.toString()) // If player2 is the OT, keep track of the friendship - if (pokemon2.originalTrainer == player2Data?.uuid.toString()) { - player2Data?.tradedUUIDs[pokemon2.uuid] = pokemon2.friendship - } + pokemon2.cacheFriendship(player2.uuid.toString()) // If pokemon2 was originally player1's, set the friendship back to what it was at point of trading - if ( (player1Data != null) && (pokemon2.originalTrainer == player1Data.uuid.toString()) && (pokemon2.uuid in player1Data.tradedUUIDs) ) { - val friendship = player1Data.tradedUUIDs.remove(pokemon2.uuid)!! - pokemon2.setFriendship(friendship) - } else { // Otherwise, reset the friendship + if ( !pokemon2.restoreFriendship(player1.uuid.toString()) ) { + // Otherwise, reset the friendship pokemon2.setFriendship(pokemon2.form.baseFriendship) } // If pokemon1 was originally player2's, set the friendship back to what it was at point of trading - if ( (player2Data != null) && (pokemon1.originalTrainer == player2Data.uuid.toString()) && (pokemon1.uuid in player2Data.tradedUUIDs) ) { - val friendship = player2Data.tradedUUIDs.remove(pokemon1.uuid)!! - pokemon1.setFriendship(friendship) - } else { // Otherwise, reset the friendship + if ( !pokemon1.restoreFriendship(player2.uuid.toString()) ) { + // Otherwise, reset the friendship pokemon1.setFriendship(pokemon1.form.baseFriendship) } - // Save & update - if (player1Data != null) { - Cobblemon.playerDataManager.saveSingle(player1Data, PlayerInstancedDataStoreTypes.GENERAL) - player1Data.sendToPlayer((player1 as PlayerTradeParticipant).player) - } - if (player2Data != null) { - Cobblemon.playerDataManager.saveSingle(player2Data, PlayerInstancedDataStoreTypes.GENERAL) - player2Data.sendToPlayer((player2 as PlayerTradeParticipant).player) - } - party2.add(pokemon1) party1.add(pokemon2) diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/util/DataKeys.kt b/common/src/main/kotlin/com/cobblemon/mod/common/util/DataKeys.kt index cb199de652..0d02170052 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/util/DataKeys.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/util/DataKeys.kt @@ -85,6 +85,7 @@ object DataKeys { const val POKEMON_ORIGINAL_TRAINER = "PokemonOriginalTrainer" const val POKEMON_ORIGINAL_TRAINER_NAME = "PokemonOriginalTrainerName" const val POKEMON_ORIGINAL_TRAINER_TYPE = "PokemonOriginalTrainerType" + const val POKEMON_ORIGINAL_TRAINER_FRIENDSHIP = "PokemonOriginalTrainerFriendship" const val POKEMON_PROPERTIES_MOVES = "Moves" const val POKEMON_PROPERTIES_HELDITEM = "HeldItem" diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/ClientPokemonP3.kt b/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/ClientPokemonP3.kt index 9563955b82..b7d5e236d4 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/ClientPokemonP3.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/ClientPokemonP3.kt @@ -25,6 +25,7 @@ internal data class ClientPokemonP3( val originalTrainerType: OriginalTrainerType, val originalTrainer: Optional, val originalTrainerName: Optional, + val originalTrainerFriendship: Optional, val aspects: Set, val heldItemVisible: Optional, val cosmeticItem: Optional, @@ -42,6 +43,7 @@ internal data class ClientPokemonP3( other.originalTrainerType = this.originalTrainerType this.originalTrainer.ifPresent { other.originalTrainer = it } this.originalTrainerName.ifPresent { other.originalTrainerName = it } + this.originalTrainerFriendship.ifPresent { other.originalTrainerFriendship = it } other.forcedAspects = this.aspects this.heldItemVisible.ifPresent { other.heldItemVisible = it } other.cosmeticItem = this.cosmeticItem.orElse(ItemStack.EMPTY) @@ -67,6 +69,7 @@ internal data class ClientPokemonP3( OriginalTrainerType.CODEC.optionalFieldOf(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE, OriginalTrainerType.NONE).forGetter(ClientPokemonP3::originalTrainerType), Codec.STRING.optionalFieldOf(DataKeys.POKEMON_ORIGINAL_TRAINER).forGetter(ClientPokemonP3::originalTrainer), Codec.STRING.optionalFieldOf(DataKeys.POKEMON_ORIGINAL_TRAINER_NAME).forGetter(ClientPokemonP3::originalTrainerName), + Codec.INT.optionalFieldOf(DataKeys.POKEMON_ORIGINAL_TRAINER_FRIENDSHIP).forGetter(ClientPokemonP3::originalTrainerFriendship), Codec.list(Codec.STRING).optionalFieldOf(DataKeys.POKEMON_FORCED_ASPECTS, emptyList()).xmap({ it.toSet() }, { it.toMutableList() }).forGetter(ClientPokemonP3::aspects), Codec.BOOL.optionalFieldOf(DataKeys.HELD_ITEM_VISIBLE).forGetter(ClientPokemonP3::heldItemVisible), ItemStack.CODEC.optionalFieldOf(DataKeys.POKEMON_COSMETIC_ITEM).forGetter(ClientPokemonP3::cosmeticItem), @@ -85,6 +88,7 @@ internal data class ClientPokemonP3( pokemon.originalTrainerType, Optional.ofNullable(pokemon.originalTrainer), Optional.ofNullable(pokemon.originalTrainerName), + Optional.ofNullable(pokemon.originalTrainerFriendship), pokemon.aspects + pokemon.forcedAspects, Optional.ofNullable(pokemon.heldItemVisible), Optional.ofNullable(pokemon.cosmeticItem.takeIf { !it.isEmpty }), diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/PokemonP3.kt b/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/PokemonP3.kt index 84184514b0..d9f4bf54c8 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/PokemonP3.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/PokemonP3.kt @@ -30,6 +30,7 @@ import java.util.* internal data class PokemonP3( val originalTrainerType: OriginalTrainerType, val originalTrainer: Optional, + val originalTrainerFriendship: Optional, val forcedAspects: Set, val features: List, val heldItemVisible: Optional, @@ -49,6 +50,7 @@ internal data class PokemonP3( override fun into(other: Pokemon): Pokemon { other.originalTrainerType = this.originalTrainerType this.originalTrainer.ifPresent { other.originalTrainer = it } + this.originalTrainerFriendship.ifPresent { other.originalTrainerFriendship = it } other.refreshOriginalTrainer() other.forcedAspects = this.forcedAspects this.features.forEach { featureNbt -> @@ -91,6 +93,7 @@ internal data class PokemonP3( instance.group( OriginalTrainerType.CODEC.optionalFieldOfWithDefault(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE, OriginalTrainerType.NONE).forGetter(PokemonP3::originalTrainerType), Codec.STRING.optionalFieldOf(DataKeys.POKEMON_ORIGINAL_TRAINER).forGetter(PokemonP3::originalTrainer), + Codec.INT.optionalFieldOf(DataKeys.POKEMON_ORIGINAL_TRAINER_FRIENDSHIP).forGetter(PokemonP3::originalTrainerFriendship), Codec.list(Codec.STRING).optionalFieldOf(DataKeys.POKEMON_FORCED_ASPECTS, emptyList()).forGetter { it.forcedAspects.toMutableList() }, Codec.list(CompoundTag.CODEC).optionalFieldOf(FEATURES, emptyList()).forGetter(PokemonP3::features), Codec.BOOL.optionalFieldOf(DataKeys.HELD_ITEM_VISIBLE).forGetter(PokemonP3::heldItemVisible), @@ -108,6 +111,7 @@ internal data class PokemonP3( ).apply(instance) { originalTrainerType, originalTrainer, + originalTrainerFriendship, forcedAspects, features, heldItemVisible, @@ -124,6 +128,7 @@ internal data class PokemonP3( isAlpha -> PokemonP3( originalTrainerType, originalTrainer, + originalTrainerFriendship, forcedAspects.toSet(), features, heldItemVisible, @@ -145,6 +150,7 @@ internal data class PokemonP3( internal fun from(pokemon: Pokemon): PokemonP3 = PokemonP3( pokemon.originalTrainerType, Optional.ofNullable(pokemon.originalTrainer), + Optional.ofNullable(pokemon.originalTrainerFriendship), pokemon.forcedAspects, pokemon.features.map { feature -> val nbt = CompoundTag() From ebe93f531cf9adc6378d0199b9953e0108c70848 Mon Sep 17 00:00:00 2001 From: aeroeng15 Date: Fri, 20 Feb 2026 11:51:44 -0700 Subject: [PATCH 06/14] Restore file --- .../mod/common/api/storage/player/GeneralPlayerData.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/GeneralPlayerData.kt b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/GeneralPlayerData.kt index c2201953ed..8ebe44dde0 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/GeneralPlayerData.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/api/storage/player/GeneralPlayerData.kt @@ -29,7 +29,7 @@ data class GeneralPlayerData( var keyItems: MutableSet, var battleTheme: ResourceLocation?, var partySelectTutorialDone: Boolean = false, - val extraData: MutableMap + val extraData: MutableMap, ) : InstancedPlayerData { var advancementData: PlayerAdvancementData = PlayerAdvancementData() From ebbe4bb0e198f4a2d64dac7b80cc7a48e2b5101e Mon Sep 17 00:00:00 2001 From: aeroeng15 Date: Fri, 20 Feb 2026 22:26:58 -0700 Subject: [PATCH 07/14] Re-null friendship after restoration --- .../src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt index 787a1f7a27..f100b96298 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt @@ -2276,6 +2276,7 @@ open class Pokemon : ShowdownIdentifiable { fun restoreFriendship(playerID: String) : Boolean { if (originalTrainer == playerID) { originalTrainerFriendship?.let { friendship = it } + originalTrainerFriendship = null return true } return false From ae5021a851df55f60c401e1b34d4503f0e8f037d Mon Sep 17 00:00:00 2001 From: aeroeng15 Date: Tue, 28 Apr 2026 18:41:22 -0700 Subject: [PATCH 08/14] Changelog shenanigans --- CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c55ebd45e8..383fd30cd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -193,10 +193,7 @@ - Fixed rare Pokémon being far too likely to spawn multiple times in a single spawn cycle. The dev told me that the exact bug is too complicated to explain. - Resolved the niche issue where Structures would spawn in an incorrect location, creating very funky world gen. - Fixed Galarian Ponyta missing its cry. -- Fixed various log warnings caused by redundant files and sound events. - - Fixes https://gitlab.com/cable-mc/cobblemon/-/issues/1943 - - Fix occasional crash on retrieving revived fossils - - Traded Pokémon will now retain the original friendship level when traded back to its OT +- Traded Pokémon will now retain the original friendship level when traded back to its OT ### Developer - Added `PokeSnackSpawnerFactory` which allows influence registration on PokeSnack encounters From af6405b078cc53617e27bc008da52118a3012a38 Mon Sep 17 00:00:00 2001 From: aeroeng15 Date: Tue, 28 Apr 2026 18:42:58 -0700 Subject: [PATCH 09/14] Revert "Changelog shenanigans" This reverts commit ae5021a851df55f60c401e1b34d4503f0e8f037d. --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 383fd30cd4..c55ebd45e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -193,7 +193,10 @@ - Fixed rare Pokémon being far too likely to spawn multiple times in a single spawn cycle. The dev told me that the exact bug is too complicated to explain. - Resolved the niche issue where Structures would spawn in an incorrect location, creating very funky world gen. - Fixed Galarian Ponyta missing its cry. -- Traded Pokémon will now retain the original friendship level when traded back to its OT +- Fixed various log warnings caused by redundant files and sound events. + - Fixes https://gitlab.com/cable-mc/cobblemon/-/issues/1943 + - Fix occasional crash on retrieving revived fossils + - Traded Pokémon will now retain the original friendship level when traded back to its OT ### Developer - Added `PokeSnackSpawnerFactory` which allows influence registration on PokeSnack encounters From a83546a1da28a614a2f92d4995e774ad8569c5df Mon Sep 17 00:00:00 2001 From: aeroeng15 Date: Tue, 28 Apr 2026 18:43:20 -0700 Subject: [PATCH 10/14] Changelog shenanigans --- CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c55ebd45e8..0e8ba1a057 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -194,9 +194,7 @@ - Resolved the niche issue where Structures would spawn in an incorrect location, creating very funky world gen. - Fixed Galarian Ponyta missing its cry. - Fixed various log warnings caused by redundant files and sound events. - - Fixes https://gitlab.com/cable-mc/cobblemon/-/issues/1943 - - Fix occasional crash on retrieving revived fossils - - Traded Pokémon will now retain the original friendship level when traded back to its OT +- Traded Pokémon will now retain the original friendship level when traded back to its OT ### Developer - Added `PokeSnackSpawnerFactory` which allows influence registration on PokeSnack encounters From b06c2253aeb48148209e46bed37949699141e4da Mon Sep 17 00:00:00 2001 From: aeroeng15 Date: Wed, 29 Apr 2026 19:14:33 -0700 Subject: [PATCH 11/14] MR Updates --- .../kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt | 8 ++++---- .../kotlin/com/cobblemon/mod/common/trade/TradeManager.kt | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt index f100b96298..88a1facf9f 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt @@ -2269,12 +2269,12 @@ open class Pokemon : ShowdownIdentifiable { onChange(packet) } - fun cacheFriendship(playerID: String) { - if (originalTrainer == playerID) originalTrainerFriendship = friendship + fun cacheFriendship(playerID: UUID) { + if (getOwnerUUID() == playerID) originalTrainerFriendship = friendship } - fun restoreFriendship(playerID: String) : Boolean { - if (originalTrainer == playerID) { + fun restoreFriendship(playerID: UUID) : Boolean { + if (getOwnerUUID() == playerID) { originalTrainerFriendship?.let { friendship = it } originalTrainerFriendship = null return true diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/trade/TradeManager.kt b/common/src/main/kotlin/com/cobblemon/mod/common/trade/TradeManager.kt index a8e9b90454..8c46ab0495 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/trade/TradeManager.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/trade/TradeManager.kt @@ -119,17 +119,17 @@ object TradeManager : RequestManager() { party2.remove(pokemon2) // If player1 is the OT, keep track of the friendship - pokemon1.cacheFriendship(player1.uuid.toString()) + pokemon1.cacheFriendship(player1.uuid) // If player2 is the OT, keep track of the friendship - pokemon2.cacheFriendship(player2.uuid.toString()) + pokemon2.cacheFriendship(player2.uuid) // If pokemon2 was originally player1's, set the friendship back to what it was at point of trading - if ( !pokemon2.restoreFriendship(player1.uuid.toString()) ) { + if ( !pokemon2.restoreFriendship(player1.uuid) ) { // Otherwise, reset the friendship pokemon2.setFriendship(pokemon2.form.baseFriendship) } // If pokemon1 was originally player2's, set the friendship back to what it was at point of trading - if ( !pokemon1.restoreFriendship(player2.uuid.toString()) ) { + if ( !pokemon1.restoreFriendship(player2.uuid) ) { // Otherwise, reset the friendship pokemon1.setFriendship(pokemon1.form.baseFriendship) } From 487b59f7b512e9330b4abac03d2dc8e841483c01 Mon Sep 17 00:00:00 2001 From: aeroeng15 Date: Wed, 29 Apr 2026 19:23:21 -0700 Subject: [PATCH 12/14] Fix compile issues --- .../cobblemon/mod/common/pokemon/Pokemon.kt | 14 ++++-- .../util/codec/internal/ClientPokemonP3.kt | 4 -- .../util/codec/internal/ClientPokemonP4.kt | 47 +++++++++++++++++++ .../common/util/codec/internal/PokemonP3.kt | 6 --- .../common/util/codec/internal/PokemonP4.kt | 43 +++++++++++++++++ 5 files changed, 100 insertions(+), 14 deletions(-) create mode 100644 common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/ClientPokemonP4.kt create mode 100644 common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/PokemonP4.kt diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt index 88a1facf9f..e4056e867b 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt @@ -109,9 +109,11 @@ import com.cobblemon.mod.common.util.cobblemonResource import com.cobblemon.mod.common.util.codec.internal.ClientPokemonP1 import com.cobblemon.mod.common.util.codec.internal.ClientPokemonP2 import com.cobblemon.mod.common.util.codec.internal.ClientPokemonP3 +import com.cobblemon.mod.common.util.codec.internal.ClientPokemonP4 import com.cobblemon.mod.common.util.codec.internal.PokemonP1 import com.cobblemon.mod.common.util.codec.internal.PokemonP2 import com.cobblemon.mod.common.util.codec.internal.PokemonP3 +import com.cobblemon.mod.common.util.codec.internal.PokemonP4 import com.cobblemon.mod.common.util.nextBetween import com.cobblemon.mod.common.util.playSoundServer import com.cobblemon.mod.common.util.server @@ -2375,13 +2377,15 @@ open class Pokemon : ShowdownIdentifiable { instance.group( PokemonP1.CODEC.forGetter(PokemonP1::from), PokemonP2.CODEC.forGetter(PokemonP2::from), - PokemonP3.CODEC.forGetter(PokemonP3::from) - ).apply(instance) { p1, p2, p3-> + PokemonP3.CODEC.forGetter(PokemonP3::from), + PokemonP4.CODEC.forGetter(PokemonP4::from) + ).apply(instance) { p1, p2, p3, p4 -> val pokemon = Pokemon() pokemon.isClient = false p1.into(pokemon) p2.into(pokemon) p3.into(pokemon) + p4.into(pokemon) pokemon.initialize() } } @@ -2400,13 +2404,15 @@ open class Pokemon : ShowdownIdentifiable { instance.group( ClientPokemonP1.CODEC.forGetter(ClientPokemonP1::from), ClientPokemonP2.CODEC.forGetter(ClientPokemonP2::from), - ClientPokemonP3.CODEC.forGetter(ClientPokemonP3::from) - ).apply(instance) { p1, p2, p3-> + ClientPokemonP3.CODEC.forGetter(ClientPokemonP3::from), + ClientPokemonP4.CODEC.forGetter(ClientPokemonP4::from) + ).apply(instance) { p1, p2, p3, p4 -> val pokemon = Pokemon() pokemon.isClient = true p1.into(pokemon) p2.into(pokemon) p3.into(pokemon) + p4.into(pokemon) pokemon.initialize() } } diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/ClientPokemonP3.kt b/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/ClientPokemonP3.kt index b7d5e236d4..9563955b82 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/ClientPokemonP3.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/ClientPokemonP3.kt @@ -25,7 +25,6 @@ internal data class ClientPokemonP3( val originalTrainerType: OriginalTrainerType, val originalTrainer: Optional, val originalTrainerName: Optional, - val originalTrainerFriendship: Optional, val aspects: Set, val heldItemVisible: Optional, val cosmeticItem: Optional, @@ -43,7 +42,6 @@ internal data class ClientPokemonP3( other.originalTrainerType = this.originalTrainerType this.originalTrainer.ifPresent { other.originalTrainer = it } this.originalTrainerName.ifPresent { other.originalTrainerName = it } - this.originalTrainerFriendship.ifPresent { other.originalTrainerFriendship = it } other.forcedAspects = this.aspects this.heldItemVisible.ifPresent { other.heldItemVisible = it } other.cosmeticItem = this.cosmeticItem.orElse(ItemStack.EMPTY) @@ -69,7 +67,6 @@ internal data class ClientPokemonP3( OriginalTrainerType.CODEC.optionalFieldOf(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE, OriginalTrainerType.NONE).forGetter(ClientPokemonP3::originalTrainerType), Codec.STRING.optionalFieldOf(DataKeys.POKEMON_ORIGINAL_TRAINER).forGetter(ClientPokemonP3::originalTrainer), Codec.STRING.optionalFieldOf(DataKeys.POKEMON_ORIGINAL_TRAINER_NAME).forGetter(ClientPokemonP3::originalTrainerName), - Codec.INT.optionalFieldOf(DataKeys.POKEMON_ORIGINAL_TRAINER_FRIENDSHIP).forGetter(ClientPokemonP3::originalTrainerFriendship), Codec.list(Codec.STRING).optionalFieldOf(DataKeys.POKEMON_FORCED_ASPECTS, emptyList()).xmap({ it.toSet() }, { it.toMutableList() }).forGetter(ClientPokemonP3::aspects), Codec.BOOL.optionalFieldOf(DataKeys.HELD_ITEM_VISIBLE).forGetter(ClientPokemonP3::heldItemVisible), ItemStack.CODEC.optionalFieldOf(DataKeys.POKEMON_COSMETIC_ITEM).forGetter(ClientPokemonP3::cosmeticItem), @@ -88,7 +85,6 @@ internal data class ClientPokemonP3( pokemon.originalTrainerType, Optional.ofNullable(pokemon.originalTrainer), Optional.ofNullable(pokemon.originalTrainerName), - Optional.ofNullable(pokemon.originalTrainerFriendship), pokemon.aspects + pokemon.forcedAspects, Optional.ofNullable(pokemon.heldItemVisible), Optional.ofNullable(pokemon.cosmeticItem.takeIf { !it.isEmpty }), diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/ClientPokemonP4.kt b/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/ClientPokemonP4.kt new file mode 100644 index 0000000000..039de68bba --- /dev/null +++ b/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/ClientPokemonP4.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 Cobblemon Contributors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.cobblemon.mod.common.util.codec.internal + +import com.cobblemon.mod.common.api.mark.Marks +import com.cobblemon.mod.common.api.riding.stats.RidingStat +import com.cobblemon.mod.common.client.settings.ServerSettings +import com.cobblemon.mod.common.pokemon.OriginalTrainerType +import com.cobblemon.mod.common.pokemon.Pokemon +import com.cobblemon.mod.common.util.DataKeys +import com.mojang.serialization.Codec +import com.mojang.serialization.MapCodec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.ItemStack +import java.util.* + +internal data class ClientPokemonP4( + val originalTrainerFriendship: Optional +) : Partial { + + override fun into(other: Pokemon): Pokemon { + this.originalTrainerFriendship.ifPresent { other.originalTrainerFriendship = it } + return other + } + + companion object { + /** + * do not use cobblemon.config in here, as this is used by the client whose config is different to server, always use [ServerSettings] + */ + internal val CODEC: MapCodec = RecordCodecBuilder.mapCodec { instance -> + instance.group( + Codec.INT.optionalFieldOf(DataKeys.POKEMON_ORIGINAL_TRAINER_FRIENDSHIP).forGetter(ClientPokemonP4::originalTrainerFriendship) + ).apply(instance, ::ClientPokemonP4) + } + + internal fun from(pokemon: Pokemon): ClientPokemonP4 = ClientPokemonP4( + Optional.ofNullable(pokemon.originalTrainerFriendship) + ) + } +} diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/PokemonP3.kt b/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/PokemonP3.kt index d9f4bf54c8..84184514b0 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/PokemonP3.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/PokemonP3.kt @@ -30,7 +30,6 @@ import java.util.* internal data class PokemonP3( val originalTrainerType: OriginalTrainerType, val originalTrainer: Optional, - val originalTrainerFriendship: Optional, val forcedAspects: Set, val features: List, val heldItemVisible: Optional, @@ -50,7 +49,6 @@ internal data class PokemonP3( override fun into(other: Pokemon): Pokemon { other.originalTrainerType = this.originalTrainerType this.originalTrainer.ifPresent { other.originalTrainer = it } - this.originalTrainerFriendship.ifPresent { other.originalTrainerFriendship = it } other.refreshOriginalTrainer() other.forcedAspects = this.forcedAspects this.features.forEach { featureNbt -> @@ -93,7 +91,6 @@ internal data class PokemonP3( instance.group( OriginalTrainerType.CODEC.optionalFieldOfWithDefault(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE, OriginalTrainerType.NONE).forGetter(PokemonP3::originalTrainerType), Codec.STRING.optionalFieldOf(DataKeys.POKEMON_ORIGINAL_TRAINER).forGetter(PokemonP3::originalTrainer), - Codec.INT.optionalFieldOf(DataKeys.POKEMON_ORIGINAL_TRAINER_FRIENDSHIP).forGetter(PokemonP3::originalTrainerFriendship), Codec.list(Codec.STRING).optionalFieldOf(DataKeys.POKEMON_FORCED_ASPECTS, emptyList()).forGetter { it.forcedAspects.toMutableList() }, Codec.list(CompoundTag.CODEC).optionalFieldOf(FEATURES, emptyList()).forGetter(PokemonP3::features), Codec.BOOL.optionalFieldOf(DataKeys.HELD_ITEM_VISIBLE).forGetter(PokemonP3::heldItemVisible), @@ -111,7 +108,6 @@ internal data class PokemonP3( ).apply(instance) { originalTrainerType, originalTrainer, - originalTrainerFriendship, forcedAspects, features, heldItemVisible, @@ -128,7 +124,6 @@ internal data class PokemonP3( isAlpha -> PokemonP3( originalTrainerType, originalTrainer, - originalTrainerFriendship, forcedAspects.toSet(), features, heldItemVisible, @@ -150,7 +145,6 @@ internal data class PokemonP3( internal fun from(pokemon: Pokemon): PokemonP3 = PokemonP3( pokemon.originalTrainerType, Optional.ofNullable(pokemon.originalTrainer), - Optional.ofNullable(pokemon.originalTrainerFriendship), pokemon.forcedAspects, pokemon.features.map { feature -> val nbt = CompoundTag() diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/PokemonP4.kt b/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/PokemonP4.kt new file mode 100644 index 0000000000..63ad8eface --- /dev/null +++ b/common/src/main/kotlin/com/cobblemon/mod/common/util/codec/internal/PokemonP4.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 Cobblemon Contributors + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package com.cobblemon.mod.common.util.codec.internal + +import com.cobblemon.mod.common.pokemon.Pokemon +import com.cobblemon.mod.common.util.DataKeys +import com.mojang.serialization.Codec +import com.mojang.serialization.MapCodec +import com.mojang.serialization.codecs.RecordCodecBuilder +import java.util.* + +internal data class PokemonP4( + val originalTrainerFriendship: Optional +) : Partial { + + override fun into(other: Pokemon): Pokemon { + this.originalTrainerFriendship.ifPresent { other.originalTrainerFriendship = it } + other.refreshOriginalTrainer() + return other + } + + companion object { + internal val CODEC: MapCodec = RecordCodecBuilder.mapCodec { instance -> + instance.group( + Codec.INT.optionalFieldOf(DataKeys.POKEMON_ORIGINAL_TRAINER_FRIENDSHIP).forGetter(PokemonP4::originalTrainerFriendship) + ).apply(instance) { + originalTrainerFriendship -> PokemonP4( + originalTrainerFriendship + ) + } + } + + internal fun from(pokemon: Pokemon): PokemonP4 = PokemonP4( + Optional.ofNullable(pokemon.originalTrainerFriendship) + ) + } +} From 2fb019ce8a6800114aa66f759f8b20d86315a1be Mon Sep 17 00:00:00 2001 From: aeroeng15 Date: Wed, 29 Apr 2026 19:45:46 -0700 Subject: [PATCH 13/14] Check the right field! --- .../main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt index e4056e867b..7a81d971e3 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt @@ -2272,11 +2272,11 @@ open class Pokemon : ShowdownIdentifiable { } fun cacheFriendship(playerID: UUID) { - if (getOwnerUUID() == playerID) originalTrainerFriendship = friendship + if (UUID.fromString(originalTrainer) == playerID) originalTrainerFriendship = friendship } fun restoreFriendship(playerID: UUID) : Boolean { - if (getOwnerUUID() == playerID) { + if (UUID.fromString(originalTrainer) == playerID) { originalTrainerFriendship?.let { friendship = it } originalTrainerFriendship = null return true From e1551ba70b8566c672071cf7d3e882030a060f43 Mon Sep 17 00:00:00 2001 From: aeroeng15 Date: Thu, 30 Apr 2026 15:41:28 -0700 Subject: [PATCH 14/14] MR Updates --- .../main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt index 7a81d971e3..c93c10f1b7 100644 --- a/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt +++ b/common/src/main/kotlin/com/cobblemon/mod/common/pokemon/Pokemon.kt @@ -2272,11 +2272,11 @@ open class Pokemon : ShowdownIdentifiable { } fun cacheFriendship(playerID: UUID) { - if (UUID.fromString(originalTrainer) == playerID) originalTrainerFriendship = friendship + if ((OriginalTrainerType.PLAYER == originalTrainerType) && (UUID.fromString(originalTrainer) == playerID)) originalTrainerFriendship = friendship } fun restoreFriendship(playerID: UUID) : Boolean { - if (UUID.fromString(originalTrainer) == playerID) { + if ((OriginalTrainerType.PLAYER == originalTrainerType) && (UUID.fromString(originalTrainer) == playerID)) { originalTrainerFriendship?.let { friendship = it } originalTrainerFriendship = null return true