Merge branch 'trade_friendship' into 'main'

Caching trade friendships to player data

Closes #1898

See merge request cable-mc/cobblemon!2119
This commit is contained in:
Polymeta
2026-04-30 22:55:22 +00:00
6 changed files with 136 additions and 6 deletions
+1
View File
@@ -209,6 +209,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.
- 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
@@ -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
@@ -543,6 +545,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 +2271,19 @@ open class Pokemon : ShowdownIdentifiable {
onChange(packet)
}
fun cacheFriendship(playerID: UUID) {
if ((OriginalTrainerType.PLAYER == originalTrainerType) && (UUID.fromString(originalTrainer) == playerID)) originalTrainerFriendship = friendship
}
fun restoreFriendship(playerID: UUID) : Boolean {
if ((OriginalTrainerType.PLAYER == originalTrainerType) && (UUID.fromString(originalTrainer) == playerID)) {
originalTrainerFriendship?.let { friendship = it }
originalTrainerFriendship = null
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
@@ -2356,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()
}
}
@@ -2381,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()
}
}
@@ -118,8 +118,21 @@ object TradeManager : RequestManager<TradeManager.TradeRequest>() {
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
pokemon1.cacheFriendship(player1.uuid)
// If player2 is the OT, keep track of the friendship
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) ) {
// 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) ) {
// Otherwise, reset the friendship
pokemon1.setFriendship(pokemon1.form.baseFriendship)
}
party2.add(pokemon1)
party1.add(pokemon2)
@@ -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"
@@ -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<Int>
) : Partial<Pokemon> {
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<ClientPokemonP4> = 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)
)
}
}
@@ -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<Int>
) : Partial<Pokemon> {
override fun into(other: Pokemon): Pokemon {
this.originalTrainerFriendship.ifPresent { other.originalTrainerFriendship = it }
other.refreshOriginalTrainer()
return other
}
companion object {
internal val CODEC: MapCodec<PokemonP4> = 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)
)
}
}