mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-10-31 14:28:54 -04:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
166cc32a24 | ||
|
|
4980ffc151 | ||
|
|
fb7d2a96b3 | ||
|
|
f6d3071a44 | ||
|
|
73f27ec732 |
No files matched your search
@@ -42,9 +42,14 @@ import com.cobblemon.mod.common.client.net.npc.OpenNPCEditorHandler
|
||||
import com.cobblemon.mod.common.client.net.orientation.ClientboundUpdateOrientationHandler
|
||||
import com.cobblemon.mod.common.client.net.pasture.ClosePastureHandler
|
||||
import com.cobblemon.mod.common.client.net.pasture.OpenPastureHandler
|
||||
import com.cobblemon.mod.common.client.net.pasture.UpdatePastureRangeHandler
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureRangePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pasture.AdjustPastureRangePacket
|
||||
import com.cobblemon.mod.common.net.serverhandling.pasture.AdjustPastureRangeHandler
|
||||
import com.cobblemon.mod.common.client.net.pasture.PokemonPasturedHandler
|
||||
import com.cobblemon.mod.common.client.net.pasture.PokemonUnpasturedHandler
|
||||
import com.cobblemon.mod.common.client.net.pasture.UpdatePastureConflictFlagHandler
|
||||
import com.cobblemon.mod.common.client.net.pasture.UpdatePastureRecoveryHandler
|
||||
import com.cobblemon.mod.common.client.net.pokedex.ServerConfirmedRegisterHandler
|
||||
import com.cobblemon.mod.common.client.net.pokemon.update.ClientboundSeatAssignmentPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.pokemon.update.ClientboundUpdateRidingStateHandler
|
||||
@@ -101,6 +106,7 @@ import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonPasturedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonUnpasturedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureConflictFlagPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureRecoveryPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pokedex.ServerConfirmedRegisterPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pokemon.update.*
|
||||
import com.cobblemon.mod.common.net.messages.client.pokemon.update.evolution.AddEvolutionPacket
|
||||
@@ -473,10 +479,12 @@ object CobblemonNetwork {
|
||||
|
||||
// Pasture
|
||||
list.add(PacketRegisterInfo(OpenPasturePacket.ID, OpenPasturePacket::decode, OpenPastureHandler))
|
||||
list.add(PacketRegisterInfo(UpdatePastureRangePacket.ID, UpdatePastureRangePacket::decode, UpdatePastureRangeHandler))
|
||||
list.add(PacketRegisterInfo(ClosePasturePacket.ID, ClosePasturePacket::decode, ClosePastureHandler))
|
||||
list.add(PacketRegisterInfo(PokemonPasturedPacket.ID, PokemonPasturedPacket::decode, PokemonPasturedHandler))
|
||||
list.add(PacketRegisterInfo(PokemonUnpasturedPacket.ID, PokemonUnpasturedPacket::decode, PokemonUnpasturedHandler))
|
||||
list.add(PacketRegisterInfo(UpdatePastureConflictFlagPacket.ID, UpdatePastureConflictFlagPacket::decode, UpdatePastureConflictFlagHandler))
|
||||
list.add(PacketRegisterInfo(UpdatePastureRecoveryPacket.ID, UpdatePastureRecoveryPacket::decode, UpdatePastureRecoveryHandler))
|
||||
|
||||
// Orientation
|
||||
list.add(PacketRegisterInfo(ClientboundUpdateOrientationPacket.ID, ClientboundUpdateOrientationPacket::decode, ClientboundUpdateOrientationHandler))
|
||||
@@ -591,6 +599,7 @@ object CobblemonNetwork {
|
||||
|
||||
// Pasture
|
||||
list.add(PacketRegisterInfo(PasturePokemonPacket.ID, PasturePokemonPacket::decode, PasturePokemonHandler))
|
||||
list.add(PacketRegisterInfo(AdjustPastureRangePacket.ID, AdjustPastureRangePacket::decode, AdjustPastureRangeHandler))
|
||||
list.add(PacketRegisterInfo(UnpasturePokemonPacket.ID, UnpasturePokemonPacket::decode, UnpasturePokemonHandler))
|
||||
list.add(PacketRegisterInfo(UnpastureAllPokemonPacket.ID, UnpastureAllPokemonPacket::decode, UnpastureAllPokemonHandler))
|
||||
list.add(PacketRegisterInfo(SetPastureConflictPacket.ID, SetPastureConflictPacket::decode, SetPastureConflictHandler))
|
||||
@@ -657,4 +666,4 @@ object CobblemonNetwork {
|
||||
fun sendPacketToPlayer(player: ServerPlayer, packet: NetworkPacket<*>) {
|
||||
Cobblemon.implementation.networkManager.sendPacketToPlayer(player, packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-2
@@ -15,11 +15,13 @@ import com.cobblemon.mod.common.api.ai.asVariables
|
||||
import com.cobblemon.mod.common.api.ai.config.task.WanderTaskConfig.Companion.WANDER
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.mojang.datafixers.util.Either
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
@@ -69,7 +71,14 @@ class AirWanderTaskConfig : SingleTaskConfig {
|
||||
|
||||
val rotVec = entity.getViewVector(0F)
|
||||
pathCooldown.setWithExpiry(true, 60L)
|
||||
val target = HoverRandomPos.getPos(entity, horizontalRange.resolveInt(runtime), verticalRange.resolveInt(runtime), rotVec.x, rotVec.y, 1.5707964f, minUpwardsMovement.resolveInt(runtime), minDownwardsMovement.resolveInt(runtime))
|
||||
val tethering = (entity as? PokemonEntity)?.tethering
|
||||
val horizontal = horizontalRange.resolveInt(runtime)
|
||||
val vertical = verticalRange.resolveInt(runtime)
|
||||
val minUpwards = minUpwardsMovement.resolveInt(runtime)
|
||||
val minDownwards = minDownwardsMovement.resolveInt(runtime)
|
||||
val target = (0 until 10).asSequence()
|
||||
.mapNotNull { HoverRandomPos.getPos(entity, horizontal, vertical, rotVec.x, rotVec.y, 1.5707964f, minUpwards, minDownwards) }
|
||||
.firstOrNull { tethering?.canRoamTo(BlockPos.containing(it)) != false }
|
||||
?: return@Trigger false
|
||||
walkTarget.set(WalkTarget(target, speedMultiplier.resolveFloat(runtime), 3))
|
||||
lookTarget.set(BlockPosTracker(target))
|
||||
@@ -78,4 +87,4 @@ class AirWanderTaskConfig : SingleTaskConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.api.ai.config.task.WanderTaskConfig.Companion.WA
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
@@ -95,6 +96,7 @@ class WaterWanderTaskConfig : SingleTaskConfig {
|
||||
attempts++
|
||||
target = BehaviorUtils.getRandomSwimmablePos(entity, horizontalRange.resolveInt(mainThreadRuntime), verticalRange.resolveInt(mainThreadRuntime))
|
||||
?: continue
|
||||
if ((entity as? PokemonEntity)?.tethering?.canRoamTo(BlockPos.containing(target)) == false) continue
|
||||
pos = BlockPos.containing(target).takeIf(wanderControl::isSuitable)
|
||||
}
|
||||
|
||||
@@ -143,4 +145,4 @@ class WaterWanderTaskConfig : SingleTaskConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ object PastureLinkManager {
|
||||
fun getLinkByPlayer(player: ServerPlayer): PastureLink? {
|
||||
val link = getLinkByPlayerId(player.uuid)
|
||||
if (link != null) {
|
||||
if (!player.level().dimensionTypeRegistration().`is`(link.dimension) || !link.pos.closerToCenterThan(player.position(), 10.0)) {
|
||||
if (player.level().dimension().location() != link.dimension || !link.pos.closerToCenterThan(player.position(), 10.0)) {
|
||||
links.remove(player.uuid)
|
||||
return null
|
||||
}
|
||||
@@ -46,8 +46,8 @@ object PastureLinkManager {
|
||||
@JvmStatic
|
||||
fun removeAt(world: ServerLevel, pos: BlockPos) {
|
||||
links.removeIf { (uuid, pastureLink) ->
|
||||
val shouldRemove = world.dimensionTypeRegistration().`is`(pastureLink.dimension) && pastureLink.pos == pos
|
||||
uuid.getPlayer()?.sendPacket(ClosePasturePacket())
|
||||
val shouldRemove = world.dimension().location() == pastureLink.dimension && pastureLink.pos == pos
|
||||
if (shouldRemove) uuid.getPlayer()?.sendPacket(ClosePasturePacket())
|
||||
return@removeIf shouldRemove
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.api.pasture
|
||||
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
|
||||
data class PastureRange(val north: Int, val east: Int, val south: Int, val west: Int) {
|
||||
fun isValid(maximum: Int): Boolean =
|
||||
listOf(north, east, south, west).all { it in 0..maximum } &&
|
||||
north.toLong() + south >= 8 && east.toLong() + west >= 8
|
||||
|
||||
operator fun get(direction: Direction): Int = when (direction) {
|
||||
Direction.NORTH -> north
|
||||
Direction.EAST -> east
|
||||
Direction.SOUTH -> south
|
||||
Direction.WEST -> west
|
||||
else -> error("Pasture range directions must be horizontal")
|
||||
}
|
||||
|
||||
fun adjusted(direction: Direction, amount: Int): PastureRange = when (direction) {
|
||||
Direction.NORTH -> copy(north = north + amount)
|
||||
Direction.EAST -> copy(east = east + amount)
|
||||
Direction.SOUTH -> copy(south = south + amount)
|
||||
Direction.WEST -> copy(west = west + amount)
|
||||
else -> this
|
||||
}
|
||||
|
||||
fun minPos(origin: BlockPos) = origin.offset(-west, 0, -north)
|
||||
fun maxPos(origin: BlockPos) = origin.offset(east, 0, south)
|
||||
|
||||
fun nearbyBlocks(origin: BlockPos, preferred: BlockPos, width: Double): Sequence<BlockPos> = sequence {
|
||||
val halfWidth = width / 2.0
|
||||
val minX = ceil(origin.x - west + halfWidth - 0.5).toInt()
|
||||
val maxX = floor(origin.x + east - halfWidth - 0.5).toInt()
|
||||
val minZ = ceil(origin.z - north + halfWidth - 0.5).toInt()
|
||||
val maxZ = floor(origin.z + south - halfWidth - 0.5).toInt()
|
||||
if (minX > maxX || minZ > maxZ) return@sequence
|
||||
|
||||
val centerX = preferred.x.coerceIn(minX, maxX)
|
||||
val centerZ = preferred.z.coerceIn(minZ, maxZ)
|
||||
for (distance in 0..16) {
|
||||
for (dx in -distance..distance) {
|
||||
val x = centerX + dx
|
||||
if (x !in minX..maxX) continue
|
||||
val dz = distance - abs(dx)
|
||||
val z = centerZ + dz
|
||||
if (z in minZ..maxZ) yield(BlockPos(x, origin.y, z))
|
||||
if (dz != 0 && centerZ - dz in minZ..maxZ) yield(BlockPos(x, origin.y, centerZ - dz))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeVarInt(north)
|
||||
buffer.writeVarInt(east)
|
||||
buffer.writeVarInt(south)
|
||||
buffer.writeVarInt(west)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MAX_DISTANCE = 64
|
||||
|
||||
fun fromBounds(origin: BlockPos, min: BlockPos, max: BlockPos) = PastureRange(
|
||||
origin.z - min.z, max.x - origin.x, max.z - origin.z, origin.x - min.x
|
||||
)
|
||||
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = PastureRange(
|
||||
buffer.readVarInt(), buffer.readVarInt(), buffer.readVarInt(), buffer.readVarInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import com.cobblemon.mod.common.util.rotateShape
|
||||
import com.mojang.serialization.MapCodec
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.util.StringRepresentable
|
||||
@@ -283,11 +282,13 @@ class PastureBlock(settings: Properties): BaseEntityBlock(settings), SimpleWater
|
||||
pastureId = linkId,
|
||||
permissions = perms,
|
||||
limit = baseEntity.getMaxTethered(),
|
||||
tetheredPokemon = baseEntity.tetheredPokemon.mapNotNull { it.toDTO(player) }
|
||||
tetheredPokemon = baseEntity.tetheredPokemon.mapNotNull { it.toDTO(player) },
|
||||
range = baseEntity.getRange(),
|
||||
facing = state.getValue(HorizontalDirectionalBlock.FACING)
|
||||
)
|
||||
)
|
||||
|
||||
PastureLinkManager.createLink(player.uuid, PastureLink(linkId, pcId, ResourceLocation.tryParse(world.dimensionTypeRegistration().registeredName)!!, getBasePosition(state, pos), perms))
|
||||
PastureLinkManager.createLink(player.uuid, PastureLink(linkId, pcId, world.dimension().location(), getBasePosition(state, pos), perms))
|
||||
|
||||
world.playSoundServer(
|
||||
position = pos.bottomCenter,
|
||||
@@ -366,4 +367,4 @@ class PastureBlock(settings: Properties): BaseEntityBlock(settings), SimpleWater
|
||||
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
+219
-64
@@ -14,15 +14,19 @@ import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.advancement.CobblemonCriteria
|
||||
import com.cobblemon.mod.common.api.pasture.PastureLinkManager
|
||||
import com.cobblemon.mod.common.api.pasture.PastureRange
|
||||
import com.cobblemon.mod.common.api.scheduling.afterOnServer
|
||||
import com.cobblemon.mod.common.api.text.italicise
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.block.PastureBlock
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.ClosePasturePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonPasturedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureRecoveryPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureConflictFlagPacket
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.SendOutPokemonHandler
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.DataKeys
|
||||
@@ -39,7 +43,6 @@ import net.minecraft.core.Vec3i
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.nbt.ListTag
|
||||
import net.minecraft.nbt.NbtUtils
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.tags.FluidTags
|
||||
@@ -48,38 +51,61 @@ import net.minecraft.world.entity.item.ItemEntity
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.HorizontalDirectionalBlock
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.level.block.entity.BlockEntity
|
||||
import net.minecraft.world.level.block.entity.BlockEntityTicker
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.entity.EntityTypeTest
|
||||
import net.minecraft.world.phys.AABB
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
|
||||
BlockEntity(CobblemonBlockEntities.PASTURE, pos, state) {
|
||||
open class Tethering(
|
||||
val minRoamPos: BlockPos,
|
||||
val maxRoamPos: BlockPos,
|
||||
var minRoamPos: BlockPos,
|
||||
var maxRoamPos: BlockPos,
|
||||
val playerId: UUID,
|
||||
val playerName: String,
|
||||
val tetheringId: UUID,
|
||||
val pokemonId: UUID,
|
||||
val pcId: UUID,
|
||||
val entityId: Int,
|
||||
val pasturePos: BlockPos
|
||||
var entityId: Int,
|
||||
val pasturePos: BlockPos,
|
||||
var entityUuid: UUID? = null,
|
||||
var lastEntityPos: BlockPos? = null,
|
||||
var respawnAt: Long = 0L,
|
||||
var healingAfterFaint: Boolean = false,
|
||||
var conflictEnabled: Boolean? = null
|
||||
) {
|
||||
fun getPokemon(): Pokemon? {
|
||||
val srv = server() ?: return null
|
||||
val pc = Cobblemon.storage.getPC(pcId, srv.registryAccess())
|
||||
return pc[pokemonId]
|
||||
}
|
||||
val box = AABB(minRoamPos.toVec3d(), maxRoamPos.toVec3d())
|
||||
open fun canRoamTo(pos: BlockPos) = box.contains(pos.center)
|
||||
val box get() = AABB(minRoamPos.toVec3d(), maxRoamPos.toVec3d())
|
||||
fun canRoamTo(x: Double, z: Double) = x >= minRoamPos.x && x < maxRoamPos.x && z >= minRoamPos.z && z < maxRoamPos.z
|
||||
open fun canRoamTo(pos: BlockPos) = canRoamTo(pos.x + 0.5, pos.z + 0.5)
|
||||
|
||||
internal fun beginRecovery(now: Long, fainted: Boolean) {
|
||||
if (fainted && !healingAfterFaint) {
|
||||
healingAfterFaint = true
|
||||
respawnAt = now + FAINT_COOLDOWN
|
||||
} else if (respawnAt == 0L) {
|
||||
respawnAt = now + MISSING_ENTITY_COOLDOWN
|
||||
}
|
||||
}
|
||||
|
||||
fun toDTO(player: ServerPlayer): OpenPasturePacket.PasturePokemonDataDTO? {
|
||||
val pokemon = getPokemon() ?: return null
|
||||
val entity = pokemon.entity
|
||||
|
||||
val behaviourFlags = entity?.getActiveBehaviourFlags() ?: emptySet()
|
||||
val behaviourFlags = (entity?.getActiveBehaviourFlags() ?: emptySet()).toMutableSet()
|
||||
when (conflictEnabled) {
|
||||
true -> behaviourFlags.add(PokemonBehaviourFlag.PASTURE_CONFLICT)
|
||||
false -> behaviourFlags.remove(PokemonBehaviourFlag.PASTURE_CONFLICT)
|
||||
null -> Unit
|
||||
}
|
||||
|
||||
return OpenPasturePacket.PasturePokemonDataDTO(
|
||||
pokemonId = pokemonId,
|
||||
@@ -91,12 +117,16 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
|
||||
heldItem = pokemon.heldItem(),
|
||||
level = pokemon.level,
|
||||
entityKnown = (entity?.tethering?.tetheringId == tetheringId),
|
||||
behaviourFlags = behaviourFlags
|
||||
behaviourFlags = behaviourFlags,
|
||||
healingAfterFaint = healingAfterFaint,
|
||||
respawnAt = respawnAt
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val FAINT_COOLDOWN = 24000L
|
||||
private const val MISSING_ENTITY_COOLDOWN = 200L
|
||||
internal val TICKER = BlockEntityTicker<PokemonPastureBlockEntity> { world, _, _, blockEntity ->
|
||||
if (world.isClientSide) return@BlockEntityTicker
|
||||
blockEntity.ticksUntilCheck--
|
||||
@@ -134,6 +164,26 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
|
||||
|
||||
fun getMaxTethered() = Cobblemon.config.defaultPasturedPokemonLimit
|
||||
|
||||
fun getRange() = PastureRange.fromBounds(blockPos, minRoamPos, maxRoamPos)
|
||||
|
||||
fun setRange(range: PastureRange) {
|
||||
minRoamPos = range.minPos(blockPos).atY(minRoamPos.y)
|
||||
maxRoamPos = range.maxPos(blockPos).atY(maxRoamPos.y)
|
||||
tetheredPokemon.forEach { tethering ->
|
||||
tethering.minRoamPos = minRoamPos
|
||||
tethering.maxRoamPos = maxRoamPos
|
||||
tethering.getPokemon()?.entity?.let { entity ->
|
||||
if (entity.tethering?.tetheringId == tethering.tetheringId) {
|
||||
entity.tethering = tethering
|
||||
entity.navigation.stop()
|
||||
entity.brain.eraseMemory(MemoryModuleType.WALK_TARGET)
|
||||
entity.checkPastureTether()
|
||||
}
|
||||
}
|
||||
}
|
||||
setChanged()
|
||||
}
|
||||
|
||||
fun canAddPokemon(player: ServerPlayer, pokemon: Pokemon, maxPerPlayer: Int): Boolean {
|
||||
val forThisPlayer = tetheredPokemon.count { it.playerId == player.uuid }
|
||||
// Shouldn't be possible, client should've prevented it
|
||||
@@ -158,57 +208,64 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
|
||||
|
||||
|
||||
fun tether(player: ServerPlayer, pokemon: Pokemon, directionToBehind: Direction): Boolean {
|
||||
val tethering = Tethering(
|
||||
minRoamPos, maxRoamPos, player.uuid, player.gameProfile.name, UUID.randomUUID(),
|
||||
pokemon.uuid, Cobblemon.storage.getPC(player).uuid, -1, blockPos
|
||||
)
|
||||
if (!spawnPokemon(pokemon, tethering, directionToBehind)) return false
|
||||
pokemon.tetheringId = tethering.tetheringId
|
||||
pokemon.onChange()
|
||||
tetheredPokemon.add(tethering)
|
||||
tethering.toDTO(player)?.let { player.sendPacket(PokemonPasturedPacket(it)) }
|
||||
setChanged()
|
||||
CobblemonCriteria.PASTURE_USE.trigger(player, pokemon)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun spawnPokemon(pokemon: Pokemon, tethering: Tethering, directionToBehind: Direction): Boolean {
|
||||
val world = level ?: return false
|
||||
val entity = PokemonEntity(world, pokemon = pokemon)
|
||||
entity.refreshDimensions()
|
||||
val width = entity.boundingBox.xsize
|
||||
|
||||
val idealPlace = blockPos.offset(directionToBehind.normal.multiply(ceil(width).toInt() + 1))
|
||||
var box = entity.getDimensions(Pose.STANDING).makeBoundingBox(idealPlace.center.subtract(0.0, 0.5, 0.0))
|
||||
|
||||
for (i in 0..5) {
|
||||
box = box.move(directionToBehind.normal.x.toDouble(), 0.0, directionToBehind.normal.z.toDouble())
|
||||
val fixedPosition = makeSuitableY(world, idealPlace.offset(directionToBehind.normal.multiply(i + 1)), entity, box)
|
||||
if (fixedPosition != null) {
|
||||
entity.setPos(fixedPosition.center.subtract(0.0, 0.5, 0.0))
|
||||
val pc = Cobblemon.storage.getPC(player)
|
||||
entity.beamMode = 2
|
||||
afterOnServer(seconds = 0.5F) {
|
||||
if(entity.pokemon.shiny) {
|
||||
SpawnSnowstormEntityParticlePacket(cobblemonResource("shiny_ring"), entity.id, listOf("shiny_particles", "middle"))
|
||||
.sendToPlayersAround(entity.x, entity.y, entity.z, 64.0, entity.level().dimension())
|
||||
}
|
||||
entity.after(seconds = SendOutPokemonHandler.SEND_OUT_DURATION) {
|
||||
entity.beamMode = 0
|
||||
}
|
||||
}
|
||||
if (world.addFreshEntity(entity)) {
|
||||
val tethering = Tethering(
|
||||
minRoamPos = minRoamPos,
|
||||
maxRoamPos = maxRoamPos,
|
||||
playerId = player.uuid,
|
||||
playerName = player.gameProfile.name,
|
||||
tetheringId = UUID.randomUUID(),
|
||||
pokemonId = pokemon.uuid,
|
||||
pcId = pc.uuid,
|
||||
entityId = entity.id,
|
||||
pasturePos = blockPos
|
||||
)
|
||||
pokemon.tetheringId = tethering.tetheringId
|
||||
tetheredPokemon.add(tethering)
|
||||
entity.tethering = tethering
|
||||
tethering.toDTO(player)?.let { player.sendPacket(PokemonPasturedPacket(it)) }
|
||||
setChanged()
|
||||
CobblemonCriteria.PASTURE_USE.trigger(player, pokemon)
|
||||
return true
|
||||
} else {
|
||||
Cobblemon.LOGGER.warn("Couldn't spawn pastured Pokémon for some reason")
|
||||
}
|
||||
break
|
||||
val position = findSpawnPosition(entity, tethering.lastEntityPos, directionToBehind) ?: return false
|
||||
entity.setPos(position)
|
||||
entity.beamMode = 2
|
||||
entity.tethering = tethering
|
||||
entity.setBehaviourFlag(PokemonBehaviourFlag.PASTURE_CONFLICT, tethering.conflictEnabled == true)
|
||||
if (!world.addFreshEntity(entity)) return false
|
||||
if (tethering.conflictEnabled == true) entity.remakeBrain()
|
||||
tethering.entityId = entity.id
|
||||
tethering.entityUuid = entity.uuid
|
||||
tethering.lastEntityPos = entity.blockPosition()
|
||||
tethering.respawnAt = 0L
|
||||
syncRecovery(tethering)
|
||||
afterOnServer(seconds = 0.5F) {
|
||||
if (!entity.isRemoved && entity.pokemon.shiny) {
|
||||
SpawnSnowstormEntityParticlePacket(cobblemonResource("shiny_ring"), entity.id, listOf("shiny_particles", "middle"))
|
||||
.sendToPlayersAround(entity.x, entity.y, entity.z, 64.0, entity.level().dimension())
|
||||
}
|
||||
entity.after(seconds = SendOutPokemonHandler.SEND_OUT_DURATION) {
|
||||
entity.beamMode = 0
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
fun findSpawnPosition(entity: PokemonEntity, preferred: BlockPos? = null, direction: Direction = blockState.getValue(HorizontalDirectionalBlock.FACING)): Vec3? {
|
||||
val world = level ?: return null
|
||||
val dimensions = entity.getDimensions(Pose.STANDING)
|
||||
val idealPlace = preferred ?: blockPos.offset(direction.normal.multiply(ceil(dimensions.width / 2.0).toInt() + 1))
|
||||
for (candidate in getRange().nearbyBlocks(blockPos, idealPlace, dimensions.width.toDouble())) {
|
||||
if (!world.hasChunkAt(candidate)) continue
|
||||
val box = dimensions.makeBoundingBox(candidate.center.subtract(0.0, 0.5, 0.0))
|
||||
val fixedPosition = makeSuitableY(world, candidate, entity, box) ?: continue
|
||||
val position = fixedPosition.center.subtract(0.0, 0.5, 0.0)
|
||||
val spawnBox = dimensions.makeBoundingBox(position)
|
||||
if (world.noCollision(entity, spawnBox) &&
|
||||
world.getEntitiesOfClass(PokemonEntity::class.java, spawnBox) { it != entity }.isEmpty()) {
|
||||
return position
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun togglePastureOn(on: Boolean) {
|
||||
@@ -289,6 +346,7 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
|
||||
}
|
||||
|
||||
fun checkPokemon() {
|
||||
val world = level as? ServerLevel ?: return
|
||||
val deadLinks = mutableListOf<UUID>()
|
||||
tetheredPokemon.forEach {
|
||||
val pokemon = it.getPokemon()
|
||||
@@ -296,6 +354,8 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
|
||||
deadLinks.add(it.pokemonId)
|
||||
} else if (pokemon.tetheringId == null || pokemon.tetheringId != it.tetheringId) {
|
||||
deadLinks.add(it.pokemonId)
|
||||
} else {
|
||||
checkDeployment(world, it, pokemon)
|
||||
}
|
||||
}
|
||||
deadLinks.forEach(::releasePokemon)
|
||||
@@ -303,6 +363,85 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
|
||||
setChanged()
|
||||
}
|
||||
|
||||
private fun checkDeployment(world: ServerLevel, tethering: Tethering, pokemon: Pokemon) {
|
||||
val now = world.gameTime
|
||||
if (pokemon.isFainted() && !tethering.healingAfterFaint) {
|
||||
tethering.beginRecovery(now, fainted = true)
|
||||
syncRecovery(tethering)
|
||||
}
|
||||
val entity = (pokemon.entity ?: tethering.entityUuid?.let(world::getEntity) as? PokemonEntity)
|
||||
?.takeIf { !it.isRemoved && it.tethering?.tetheringId == tethering.tetheringId }
|
||||
if (tethering.conflictEnabled == null && entity != null) {
|
||||
tethering.conflictEnabled = entity.getBehaviourFlag(PokemonBehaviourFlag.PASTURE_CONFLICT)
|
||||
}
|
||||
if (entity != null && pokemon.isFainted()) entity.discard()
|
||||
if (entity != null && !entity.isRemoved) {
|
||||
if (tethering.conflictEnabled != null && entity.getBehaviourFlag(PokemonBehaviourFlag.PASTURE_CONFLICT) != tethering.conflictEnabled) {
|
||||
entity.setBehaviourFlag(PokemonBehaviourFlag.PASTURE_CONFLICT, tethering.conflictEnabled == true)
|
||||
entity.remakeBrain()
|
||||
}
|
||||
tethering.entityUuid = entity.uuid
|
||||
tethering.lastEntityPos = entity.blockPosition()
|
||||
entity.tethering = tethering
|
||||
if (tethering.respawnAt != 0L || tethering.healingAfterFaint) {
|
||||
tethering.respawnAt = 0L
|
||||
tethering.healingAfterFaint = false
|
||||
syncRecovery(tethering)
|
||||
}
|
||||
return
|
||||
}
|
||||
// A Pokémon in an unloaded chunk is still deployed. Never create its replacement.
|
||||
val lastPosition = tethering.lastEntityPos
|
||||
if (lastPosition != null) {
|
||||
if (!world.hasChunkAt(lastPosition)) return
|
||||
}
|
||||
// The last saved position may predate a chunk crossing. Require the entire range
|
||||
// to be entity-ticking before deciding that its missing Pokémon has disappeared.
|
||||
for (chunkX in (minRoamPos.x shr 4)..(maxRoamPos.x shr 4)) {
|
||||
for (chunkZ in (minRoamPos.z shr 4)..(maxRoamPos.z shr 4)) {
|
||||
if (!world.isPositionEntityTicking(BlockPos(chunkX shl 4, blockPos.y, chunkZ shl 4))) return
|
||||
}
|
||||
}
|
||||
if (tethering.respawnAt == 0L) {
|
||||
tethering.beginRecovery(now, fainted = false)
|
||||
syncRecovery(tethering)
|
||||
return
|
||||
}
|
||||
if (now < tethering.respawnAt) return
|
||||
if (tethering.healingAfterFaint) {
|
||||
pokemon.heal()
|
||||
if (pokemon.isFainted()) return
|
||||
tethering.healingAfterFaint = false
|
||||
syncRecovery(tethering)
|
||||
}
|
||||
val radius = Cobblemon.config.pastureMaxWanderDistance.toDouble()
|
||||
val nearby = world.getEntitiesOfClass(PokemonEntity::class.java, AABB.ofSize(blockPos.toVec3d(), radius * 2, world.height.toDouble(), radius * 2)).size
|
||||
val chunkDiameter = (radius / 16) * 2
|
||||
if (nearby >= Cobblemon.config.pastureMaxPerChunk * chunkDiameter * chunkDiameter ||
|
||||
!spawnPokemon(pokemon, tethering, blockState.getValue(HorizontalDirectionalBlock.FACING))) {
|
||||
tethering.respawnAt = now + MISSING_ENTITY_COOLDOWN
|
||||
syncRecovery(tethering)
|
||||
}
|
||||
}
|
||||
|
||||
private fun syncRecovery(tethering: Tethering) {
|
||||
val world = level as? ServerLevel ?: return
|
||||
val packet = UpdatePastureRecoveryPacket(tethering.pokemonId, tethering.healingAfterFaint, tethering.respawnAt)
|
||||
world.server.playerList.players.filter(::isPlayerViewing).forEach { it.sendPacket(packet) }
|
||||
}
|
||||
|
||||
fun setConflict(tethering: Tethering, enabled: Boolean) {
|
||||
tethering.conflictEnabled = enabled
|
||||
tethering.getPokemon()?.entity?.takeIf { it.tethering?.tetheringId == tethering.tetheringId }?.let {
|
||||
it.setBehaviourFlag(PokemonBehaviourFlag.PASTURE_CONFLICT, enabled)
|
||||
it.remakeBrain()
|
||||
}
|
||||
setChanged()
|
||||
val world = level as? ServerLevel ?: return
|
||||
val packet = UpdatePastureConflictFlagPacket(tethering.pokemonId, enabled)
|
||||
world.server.playerList.players.filter(::isPlayerViewing).forEach { it.sendPacket(packet) }
|
||||
}
|
||||
|
||||
fun onBroken() {
|
||||
if (level is ServerLevel) {
|
||||
tetheredPokemon.toList().forEach { releasePokemon(it.pokemonId) }
|
||||
@@ -312,7 +451,10 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
|
||||
|
||||
fun releasePokemon(pokemonId: UUID) {
|
||||
val tethering = tetheredPokemon.find { it.pokemonId == pokemonId } ?: return
|
||||
tethering.getPokemon()?.tetheringId = null
|
||||
tethering.getPokemon()?.let { pokemon ->
|
||||
pokemon.tetheringId = null
|
||||
pokemon.onChange()
|
||||
}
|
||||
tetheredPokemon.remove(tethering)
|
||||
setChanged()
|
||||
}
|
||||
@@ -320,7 +462,10 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
|
||||
fun releaseAllPokemon(playerId: UUID): List<UUID> {
|
||||
val unpastured = mutableListOf<UUID>()
|
||||
tetheredPokemon.filter { it.playerId == playerId }.forEach {
|
||||
it.getPokemon()?.tetheringId = null
|
||||
it.getPokemon()?.let { pokemon ->
|
||||
pokemon.tetheringId = null
|
||||
pokemon.onChange()
|
||||
}
|
||||
tetheredPokemon.remove(it)
|
||||
unpastured.add(it.pokemonId)
|
||||
}
|
||||
@@ -343,9 +488,7 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
|
||||
|
||||
private fun isPlayerViewing(player: ServerPlayer): Boolean {
|
||||
val pastureLink = PastureLinkManager.getLinkByPlayer(player)
|
||||
return pastureLink != null && pastureLink.pos == blockPos && pastureLink.dimension == ResourceLocation.tryParse(
|
||||
player.level().dimensionTypeRegistration().registeredName // todo (techdaan): confirm this is good
|
||||
)
|
||||
return pastureLink != null && pastureLink.pos == blockPos && pastureLink.dimension == player.level().dimension().location()
|
||||
}
|
||||
|
||||
override fun loadAdditional(nbt: CompoundTag, registryLookup: HolderLookup.Provider) {
|
||||
@@ -353,6 +496,9 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
|
||||
val list = nbt.getList(DataKeys.TETHER_POKEMON, CompoundTag.TAG_COMPOUND.toInt())
|
||||
this.ownerId = if (nbt.hasUUID(DataKeys.TETHER_OWNER_ID)) nbt.getUUID(DataKeys.TETHER_OWNER_ID) else null
|
||||
this.ownerName = nbt.getString(DataKeys.TETHER_OWNER_NAME).takeIf { it.isNotEmpty() } ?: ""
|
||||
this.minRoamPos = NbtUtils.readBlockPos(nbt, DataKeys.TETHER_MIN_ROAM_POS).orElse(minRoamPos)
|
||||
this.maxRoamPos = NbtUtils.readBlockPos(nbt, DataKeys.TETHER_MAX_ROAM_POS).orElse(maxRoamPos)
|
||||
tetheredPokemon.clear()
|
||||
for (tetheringNBT in list) {
|
||||
tetheringNBT as CompoundTag
|
||||
val tetheringId = tetheringNBT.getUUID(DataKeys.TETHERING_ID)
|
||||
@@ -365,17 +511,20 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
|
||||
minRoamPos = minRoamPos,
|
||||
maxRoamPos = maxRoamPos,
|
||||
playerId = playerId,
|
||||
playerName = ownerName,
|
||||
playerName = tetheringNBT.getString("PlayerName").ifEmpty { ownerName },
|
||||
tetheringId = tetheringId,
|
||||
pokemonId = pokemonId,
|
||||
pcId = pcId,
|
||||
entityId = entityId,
|
||||
pasturePos = blockPos
|
||||
pasturePos = blockPos,
|
||||
entityUuid = if (tetheringNBT.hasUUID("EntityUUID")) tetheringNBT.getUUID("EntityUUID") else null,
|
||||
lastEntityPos = NbtUtils.readBlockPos(tetheringNBT, "LastEntityPos").orElse(null),
|
||||
respawnAt = tetheringNBT.getLong("RespawnAt"),
|
||||
healingAfterFaint = tetheringNBT.getBoolean("HealingAfterFaint"),
|
||||
conflictEnabled = if (tetheringNBT.contains("PastureConflict")) tetheringNBT.getBoolean("PastureConflict") else null
|
||||
)
|
||||
)
|
||||
}
|
||||
this.minRoamPos = NbtUtils.readBlockPos(nbt, DataKeys.TETHER_MIN_ROAM_POS).get()
|
||||
this.maxRoamPos = NbtUtils.readBlockPos(nbt, DataKeys.TETHER_MAX_ROAM_POS).get()
|
||||
}
|
||||
|
||||
override fun saveAdditional(nbt: CompoundTag, registryLookup: HolderLookup.Provider) {
|
||||
@@ -388,6 +537,12 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
|
||||
tetheringNBT.putUUID(DataKeys.POKEMON_UUID, tethering.pokemonId)
|
||||
tetheringNBT.putUUID(DataKeys.PC_ID, tethering.pcId)
|
||||
tetheringNBT.putInt(DataKeys.TETHERING_ENTITY_ID, tethering.entityId)
|
||||
tetheringNBT.putString("PlayerName", tethering.playerName)
|
||||
tethering.entityUuid?.let { tetheringNBT.putUUID("EntityUUID", it) }
|
||||
tethering.lastEntityPos?.let { tetheringNBT.put("LastEntityPos", NbtUtils.writeBlockPos(it)) }
|
||||
tetheringNBT.putLong("RespawnAt", tethering.respawnAt)
|
||||
tetheringNBT.putBoolean("HealingAfterFaint", tethering.healingAfterFaint)
|
||||
tethering.conflictEnabled?.let { tetheringNBT.putBoolean("PastureConflict", it) }
|
||||
list.add(tetheringNBT)
|
||||
}
|
||||
nbt.put(DataKeys.TETHER_POKEMON, list)
|
||||
@@ -396,4 +551,4 @@ class PokemonPastureBlockEntity(pos: BlockPos, state: BlockState) :
|
||||
ownerId?.let { nbt.putUUID(DataKeys.TETHER_OWNER_ID, it) }
|
||||
nbt.putString(DataKeys.TETHER_OWNER_NAME, this.ownerName)
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-2
@@ -11,6 +11,8 @@ package com.cobblemon.mod.common.client.gui.pasture
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.pasture.PasturePermissions
|
||||
import com.cobblemon.mod.common.api.pasture.PastureRange
|
||||
import net.minecraft.core.Direction
|
||||
import com.cobblemon.mod.common.api.reactive.SettableObservable
|
||||
import com.cobblemon.mod.common.client.gui.pc.PCGUIConfiguration
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
|
||||
@@ -21,7 +23,9 @@ class PasturePCGUIConfiguration(
|
||||
val pastureId: UUID,
|
||||
val limit: Int,
|
||||
val pasturedPokemon: SettableObservable<List<OpenPasturePacket.PasturePokemonDataDTO>>,
|
||||
var permissions: PasturePermissions
|
||||
var permissions: PasturePermissions,
|
||||
var range: PastureRange,
|
||||
val facing: Direction
|
||||
) : PCGUIConfiguration(
|
||||
exitFunction = { it.closeNormally(unlink = true) },
|
||||
showParty = false,
|
||||
@@ -32,4 +36,6 @@ class PasturePCGUIConfiguration(
|
||||
}
|
||||
},
|
||||
canSelect = { pokemon -> !pokemon.isFainted() && pokemon.tetheringId == null }
|
||||
)
|
||||
) {
|
||||
var editingRange = false
|
||||
}
|
||||
+23
-14
@@ -58,6 +58,7 @@ class PasturePokemonScrollList(
|
||||
private val scrollOverlayResource = cobblemonResource("textures/gui/pasture/pasture_scroll_overlay.png")
|
||||
private val slotResource = cobblemonResource("textures/gui/pasture/pasture_slot.png")
|
||||
private val slotOwnerResource = cobblemonResource("textures/gui/pasture/pasture_slot_owner.png")
|
||||
private val slotFaintedResource = cobblemonResource("textures/gui/pasture/pasture_slot_fainted.png")
|
||||
}
|
||||
|
||||
private var scrolling = false
|
||||
@@ -194,7 +195,7 @@ class PasturePokemonScrollList(
|
||||
}
|
||||
)
|
||||
|
||||
override fun getNarration() = pokemon.displayName
|
||||
override fun getNarration() = if (pokemon.healingAfterFaint) lang("ui.pasture.fainted").append(" ").append(pokemon.displayName) else pokemon.displayName
|
||||
|
||||
override fun render(
|
||||
context: GuiGraphics,
|
||||
@@ -214,7 +215,7 @@ class PasturePokemonScrollList(
|
||||
val matrixStack = context.pose()
|
||||
blitk(
|
||||
matrixStack = matrixStack,
|
||||
texture = if (isOwned()) slotOwnerResource else slotResource,
|
||||
texture = if (pokemon.healingAfterFaint) slotFaintedResource else if (isOwned()) slotOwnerResource else slotResource,
|
||||
x = x,
|
||||
y = y,
|
||||
height = SLOT_HEIGHT,
|
||||
@@ -234,7 +235,8 @@ class PasturePokemonScrollList(
|
||||
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
|
||||
state = state,
|
||||
partialTicks = partialTicks,
|
||||
scale = 4.5F
|
||||
scale = 4.5F,
|
||||
a = if (pokemon.healingAfterFaint) 0.7F else 1F
|
||||
)
|
||||
matrixStack.popPose()
|
||||
|
||||
@@ -249,14 +251,21 @@ class PasturePokemonScrollList(
|
||||
)
|
||||
}
|
||||
|
||||
drawScaledTextJustifiedRight(
|
||||
context = context,
|
||||
text = lang("ui.lv.number", pokemon.level),
|
||||
x = x + 59,
|
||||
y = y + 17,
|
||||
shadow = true,
|
||||
scale = SCALE
|
||||
)
|
||||
val recovering = pokemon.healingAfterFaint || pokemon.respawnAt > 0L
|
||||
if (recovering) {
|
||||
if (!pokemon.healingAfterFaint) {
|
||||
drawScaledText(context, text = lang("ui.pasture.returning"),
|
||||
x = x + 18, y = y + 7, scale = SCALE, colour = 0xFFFF8585.toInt(), shadow = true)
|
||||
}
|
||||
val remainingTicks = (pokemon.respawnAt - (client.level?.gameTime ?: 0L)).coerceAtLeast(0L)
|
||||
val seconds = (remainingTicks + 19L) / 20L
|
||||
val timer = if (remainingTicks == 0L) lang("ui.pasture.ready") else "%d:%02d".format(seconds / 60L, seconds % 60L).text()
|
||||
drawScaledTextJustifiedRight(context = context, text = timer, x = x + 59, y = y + 17,
|
||||
shadow = true, scale = SCALE)
|
||||
} else {
|
||||
drawScaledTextJustifiedRight(context = context, text = lang("ui.lv.number", pokemon.level),
|
||||
x = x + 59, y = y + 17, shadow = true, scale = SCALE)
|
||||
}
|
||||
|
||||
drawScaledText(
|
||||
context = context,
|
||||
@@ -279,7 +288,7 @@ class PasturePokemonScrollList(
|
||||
}
|
||||
|
||||
if (canUnpasture()) {
|
||||
if (canDefend() == true) {
|
||||
if (isOwned() && canDefend() == true) {
|
||||
conflictButton.setPos(x + 44, y + 3)
|
||||
conflictButton.render(context, mouseX, mouseY, partialTicks)
|
||||
}
|
||||
@@ -291,7 +300,7 @@ class PasturePokemonScrollList(
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, delta: Int): Boolean {
|
||||
if (canUnpasture()) {
|
||||
if (canDefend() == true && conflictButton.isHovered(mouseX, mouseY)) {
|
||||
if (isOwned() && canDefend() == true && conflictButton.isHovered(mouseX, mouseY)) {
|
||||
conflictButton.onPress()
|
||||
return true
|
||||
}
|
||||
@@ -303,4 +312,4 @@ class PasturePokemonScrollList(
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* 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.client.gui.pasture
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.gui.blitk
|
||||
import com.cobblemon.mod.common.api.text.bold
|
||||
import com.cobblemon.mod.common.api.text.font
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.gui.pc.StorageWidget
|
||||
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.client.render.gui.PCBoxWallpaperRepository
|
||||
import com.cobblemon.mod.common.net.messages.server.pasture.AdjustPastureRangePacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.network.chat.Component
|
||||
|
||||
class PastureRangeWidget(x: Int, y: Int, private val configuration: PasturePCGUIConfiguration) :
|
||||
SoundlessWidget(x, y, StorageWidget.SCREEN_WIDTH, StorageWidget.SCREEN_HEIGHT, lang("ui.pasture.range")) {
|
||||
|
||||
private val rangeResource = cobblemonResource("textures/gui/pasture/pasture_range.png")
|
||||
private val glowResource = cobblemonResource("textures/gui/pc/wallpaper/basic/glow/wallpaper_basic_05.png")
|
||||
private val overlayResource = cobblemonResource("textures/gui/pc/pc_screen_overlay.png")
|
||||
private val infoResource = cobblemonResource("textures/gui/pasture/pasture_range_help.png")
|
||||
private val infoButtonResource = cobblemonResource("textures/gui/pasture/pasture_button.png")
|
||||
private val infoButtonSize = 17
|
||||
private val directions = listOf(configuration.facing, configuration.facing.clockWise, configuration.facing.opposite, configuration.facing.counterClockWise)
|
||||
private val arrowBounds = listOf(
|
||||
intArrayOf(75, 17, 103, 46), intArrayOf(116, 64, 152, 96),
|
||||
intArrayOf(75, 109, 103, 143), intArrayOf(26, 64, 62, 96)
|
||||
)
|
||||
// Centers of the numeral shadows in pasture_range.png, relative to its (85, 27) panel origin.
|
||||
private val numberPositions = listOf(89 to 37, 125 to 77, 89 to 114, 53 to 77)
|
||||
private var hoveredDirection: Direction? = null
|
||||
private var showInfo = false
|
||||
val isInfoOpen get() = showInfo
|
||||
fun closeInfo() { showInfo = false }
|
||||
|
||||
private val infoButtonX get() = x + 5
|
||||
private val infoButtonY get() = y + 5
|
||||
private val infoPanelX get() = x + 7
|
||||
private val infoPanelY get() = y + 40
|
||||
private val infoPanelWidth = 160
|
||||
private val infoPanelHeight = 74
|
||||
|
||||
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
blitk(
|
||||
matrixStack = context.pose(), texture = PCBoxWallpaperRepository.defaultWallpaper,
|
||||
x = x, y = y, width = width, height = height
|
||||
)
|
||||
blitk(
|
||||
matrixStack = context.pose(), texture = rangeResource,
|
||||
x = x + 3, y = y + 3, width = width - 6, height = height - 6,
|
||||
uOffset = 88, vOffset = 30, textureWidth = 349, textureHeight = 205
|
||||
)
|
||||
blitk(
|
||||
matrixStack = context.pose(), texture = glowResource,
|
||||
x = x - 17, y = y - 17, width = 208, height = 189
|
||||
)
|
||||
blitk(matrixStack = context.pose(), texture = overlayResource, x = x, y = y, width = width, height = height)
|
||||
hoveredDirection = if (showInfo) null else directionAt(mouseX.toDouble(), mouseY.toDouble())
|
||||
directions.indexOf(hoveredDirection).takeIf { it >= 0 }?.let { index ->
|
||||
renderArrowHighlight(context, index)
|
||||
}
|
||||
context.pose().pushPose()
|
||||
context.pose().translate(-0.5F, 0F, 0F)
|
||||
directions.forEachIndexed { index, direction ->
|
||||
val (numberX, numberY) = numberPositions[index]
|
||||
drawScaledText(context, text = Component.literal(configuration.range[direction].toString()),
|
||||
x = x + numberX + 1, y = y + numberY - 7, centered = true,
|
||||
colour = 0xFFFFFFFF.toInt())
|
||||
}
|
||||
context.pose().popPose()
|
||||
if (showInfo) context.fill(x + 3, y + 3, x + width - 3, y + height - 3, 0xAA002730.toInt())
|
||||
val infoHovered = isOverInfoButton(mouseX.toDouble(), mouseY.toDouble())
|
||||
// Use the same button face and persistent open-state highlight as the gear.
|
||||
blitk(matrixStack = context.pose(), texture = infoButtonResource,
|
||||
x = infoButtonX, y = infoButtonY, width = 13, height = infoButtonSize,
|
||||
textureWidth = 70, textureHeight = 34)
|
||||
blitk(matrixStack = context.pose(), texture = infoButtonResource,
|
||||
x = infoButtonX + 13, y = infoButtonY, width = 4, height = infoButtonSize,
|
||||
uOffset = 66, textureWidth = 70, textureHeight = 34)
|
||||
if (showInfo || infoHovered) context.fill(infoButtonX + 2, infoButtonY + 2,
|
||||
infoButtonX + 16, infoButtonY + 15, 0x88FFFFFF.toInt())
|
||||
blitk(matrixStack = context.pose(), texture = infoResource,
|
||||
x = infoButtonX + 4.5F, y = infoButtonY + 3.5F, width = 8, height = 10)
|
||||
if (showInfo) {
|
||||
val options = Minecraft.getInstance().options
|
||||
val boxX = infoPanelX
|
||||
val boxY = infoPanelY
|
||||
val boxWidth = infoPanelWidth
|
||||
val boxHeight = infoPanelHeight
|
||||
context.fill(boxX + 1, boxY, boxX + boxWidth - 1, boxY + boxHeight, 0x96FFFFFF.toInt())
|
||||
context.fill(boxX, boxY + 1, boxX + boxWidth, boxY + boxHeight - 1, 0x96FFFFFF.toInt())
|
||||
blitk(matrixStack = context.pose(), texture = PCBoxWallpaperRepository.defaultWallpaper,
|
||||
x = boxX + 1, y = boxY + 1, width = boxWidth - 2, height = boxHeight - 2,
|
||||
textureWidth = StorageWidget.SCREEN_WIDTH, textureHeight = StorageWidget.SCREEN_HEIGHT)
|
||||
val increaseBinding = if (options.keyAttack.matchesMouse(0)) lang("ui.pasture.range.left_click")
|
||||
else options.keyAttack.translatedKeyMessage.copy()
|
||||
val decreaseBinding = if (options.keyUse.matchesMouse(1)) lang("ui.pasture.range.right_click")
|
||||
else options.keyUse.translatedKeyMessage.copy()
|
||||
val columns = listOf(
|
||||
Triple(boxX + 43, lang("ui.pasture.range.increase").bold(), listOf(increaseBinding, lang("ui.pasture.range.scroll_up"))),
|
||||
Triple(boxX + 117, lang("ui.pasture.range.decrease").bold(), listOf(decreaseBinding, lang("ui.pasture.range.scroll_down")))
|
||||
)
|
||||
val detailWidth = columns.flatMap { it.third }.maxOf {
|
||||
Minecraft.getInstance().font.width(it.copy().font(CobblemonResources.DEFAULT_LARGE))
|
||||
}
|
||||
val detailScale = minOf(1F, 72F / detailWidth)
|
||||
drawScaledText(context, font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = lang("ui.pasture.range.help_title").bold(), x = boxX + 80, y = boxY + 8,
|
||||
centered = true, shadow = true)
|
||||
context.fill(boxX + 8, boxY + 22, boxX + boxWidth - 8, boxY + 23, 0x96FFFFFF.toInt())
|
||||
context.fill(boxX + 80, boxY + 27, boxX + 81, boxY + 66, 0x96FFFFFF.toInt())
|
||||
columns.forEach { (centerX, title, details) ->
|
||||
drawScaledText(context, font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = title, x = centerX, y = boxY + 29, centered = true, shadow = true)
|
||||
details.forEachIndexed { index, line ->
|
||||
drawScaledText(context, font = CobblemonResources.DEFAULT_LARGE,
|
||||
text = line, x = centerX, y = boxY + 44 + index * 14,
|
||||
scale = detailScale, centered = true, shadow = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderArrowHighlight(context: GuiGraphics, index: Int) {
|
||||
fun fill(left: Int, top: Int, right: Int, bottom: Int) {
|
||||
context.fill(x + left, y + top, x + right, y + bottom, 0x66FFFFFF)
|
||||
}
|
||||
// Trace the arrow faces in pasture_range.png; horizontal arrows have longer stems.
|
||||
// Keep their offset drop shadows visible instead of brightening the surrounding background.
|
||||
when (index) {
|
||||
0 -> {
|
||||
for (row in 0..14) {
|
||||
val halfWidth = 3 + minOf(row, 11)
|
||||
fill(89 - halfWidth, 17 + row, 89 + halfWidth, 18 + row)
|
||||
}
|
||||
fill(81, 32, 97, 41)
|
||||
}
|
||||
1, 3 -> {
|
||||
for (row in 0..26) {
|
||||
val tip = 141 + minOf(row, 11, 26 - row)
|
||||
if (index == 1) fill(137, 64 + row, tip, 65 + row)
|
||||
else fill(178 - tip, 64 + row, 41, 65 + row)
|
||||
}
|
||||
if (index == 1) fill(116, 72, 137, 83)
|
||||
else fill(41, 72, 62, 83)
|
||||
}
|
||||
2 -> {
|
||||
fill(81, 109, 97, 123)
|
||||
for (row in 0..14) {
|
||||
val halfWidth = 14 - maxOf(0, row - 3)
|
||||
fill(89 - halfWidth, 123 + row, 89 + halfWidth, 124 + row)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun directionAt(mouseX: Double, mouseY: Double): Direction? {
|
||||
val index = arrowBounds.indexOfFirst { (left, top, right, bottom) ->
|
||||
mouseX >= x + left && mouseX < x + right &&
|
||||
mouseY >= y + top && mouseY < y + bottom
|
||||
}
|
||||
return directions.getOrNull(index)
|
||||
}
|
||||
|
||||
private fun adjust(direction: Direction, increase: Boolean): Boolean {
|
||||
AdjustPastureRangePacket(configuration.pastureId, direction, increase).sendToServer()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isOverInfoButton(mouseX: Double, mouseY: Double) =
|
||||
mouseX >= infoButtonX && mouseX < infoButtonX + infoButtonSize &&
|
||||
mouseY >= infoButtonY && mouseY < infoButtonY + infoButtonSize
|
||||
|
||||
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
|
||||
if (pButton == 0 && isOverInfoButton(pMouseX, pMouseY)) {
|
||||
showInfo = !showInfo
|
||||
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.PC_CLICK, 1F))
|
||||
return true
|
||||
}
|
||||
if (showInfo) {
|
||||
if (pMouseX < infoPanelX || pMouseX >= infoPanelX + infoPanelWidth ||
|
||||
pMouseY < infoPanelY || pMouseY >= infoPanelY + infoPanelHeight) closeInfo()
|
||||
return true
|
||||
}
|
||||
val direction = directionAt(pMouseX, pMouseY) ?: return false
|
||||
val options = Minecraft.getInstance().options
|
||||
return when {
|
||||
options.keyAttack.matchesMouse(pButton) -> adjust(direction, true)
|
||||
options.keyUse.matchesMouse(pButton) -> adjust(direction, false)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseScrolled(mouseX: Double, mouseY: Double, scrollX: Double, scrollY: Double): Boolean {
|
||||
if (showInfo) return true
|
||||
val direction = directionAt(mouseX, mouseY) ?: return false
|
||||
if (scrollY == 0.0) return false
|
||||
return adjust(direction, scrollY > 0)
|
||||
}
|
||||
|
||||
override fun keyPressed(pKeyCode: Int, pScanCode: Int, pModifiers: Int): Boolean {
|
||||
if (showInfo) return !Minecraft.getInstance().options.keyInventory.matches(pKeyCode, pScanCode)
|
||||
val direction = hoveredDirection ?: return false
|
||||
val options = Minecraft.getInstance().options
|
||||
return when {
|
||||
options.keyAttack.matches(pKeyCode, pScanCode) -> adjust(direction, true)
|
||||
options.keyUse.matches(pKeyCode, pScanCode) -> adjust(direction, false)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
-1
@@ -20,7 +20,9 @@ import com.cobblemon.mod.common.net.messages.server.pasture.UnpastureAllPokemonP
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.sounds.SoundManager
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.client.gui.components.Button
|
||||
|
||||
class PastureWidget(
|
||||
val storageWidget: StorageWidget,
|
||||
@@ -32,6 +34,8 @@ class PastureWidget(
|
||||
|
||||
companion object {
|
||||
private val baseResource = cobblemonResource("textures/gui/pasture/pasture_panel.png")
|
||||
private val buttonResource = cobblemonResource("textures/gui/pasture/pasture_button.png")
|
||||
private val gearResource = cobblemonResource("textures/gui/pasture/pasture_range_gear.png")
|
||||
}
|
||||
|
||||
private val recallButton = RecallButton(
|
||||
@@ -42,6 +46,14 @@ class PastureWidget(
|
||||
UnpastureAllPokemonPacket(pasturePCGUIConfiguration.pastureId).sendToServer()
|
||||
}
|
||||
|
||||
private val rangeButton = object : Button(x + 6, y + 174, 27, 17, lang("ui.pasture.range"), {
|
||||
storageWidget.pcGui.setPastureRangeEditing(!pasturePCGUIConfiguration.editingRange)
|
||||
}, DEFAULT_NARRATION) {
|
||||
override fun playDownSound(soundManager: SoundManager) {
|
||||
storageWidget.pcGui.playSound(CobblemonSounds.PC_CLICK)
|
||||
}
|
||||
}
|
||||
|
||||
val pastureScrollList = PasturePokemonScrollList(
|
||||
listX = x + 6,
|
||||
listY = y + 31,
|
||||
@@ -70,11 +82,29 @@ class PastureWidget(
|
||||
pastureScrollList.render(context, mouseX, mouseY, delta)
|
||||
|
||||
recallButton.render(context, mouseX, mouseY, delta)
|
||||
rangeButton.message = lang(if (pasturePCGUIConfiguration.editingRange) "ui.pasture.storage" else "ui.pasture.range")
|
||||
val buttonX = rangeButton.x
|
||||
val buttonY = rangeButton.y
|
||||
val highlighted = pasturePCGUIConfiguration.editingRange || rangeButton.isMouseOver(mouseX.toDouble(), mouseY.toDouble())
|
||||
blitk(matrixStack = context.pose(), texture = buttonResource, x = buttonX, y = buttonY,
|
||||
width = 23, height = 17, textureWidth = 70, textureHeight = 34)
|
||||
blitk(matrixStack = context.pose(), texture = buttonResource, x = buttonX + 23, y = buttonY,
|
||||
width = 4, height = 17, uOffset = 66, textureWidth = 70, textureHeight = 34)
|
||||
if (highlighted) context.fill(buttonX + 2, buttonY + 2, buttonX + 26, buttonY + 15, 0x88FFFFFF.toInt())
|
||||
val gearSize = 11
|
||||
val gearX = buttonX + (rangeButton.width - gearSize) / 2
|
||||
val gearY = buttonY + (rangeButton.height - gearSize) / 2
|
||||
if (highlighted) blitk(matrixStack = context.pose(), texture = gearResource,
|
||||
x = gearX + 1, y = gearY, width = gearSize, height = gearSize,
|
||||
red = 0.2F, green = 0.2F, blue = 0.2F)
|
||||
blitk(matrixStack = context.pose(), texture = gearResource,
|
||||
x = gearX, y = gearY, width = gearSize, height = gearSize)
|
||||
}
|
||||
|
||||
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
|
||||
if (rangeButton.mouseClicked(pMouseX, pMouseY, pButton)) return true
|
||||
if (recallButton.isHovered(pMouseX, pMouseY)) recallButton.mouseClicked(pMouseX, pMouseY, pButton)
|
||||
if (pastureScrollList.isHovered(pMouseX, pMouseY)) pastureScrollList.mouseClicked(pMouseX, pMouseY, pButton)
|
||||
return super.mouseClicked(pMouseX, pMouseY, pButton)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||
@@ -65,9 +64,6 @@ class FilterWidget(
|
||||
|
||||
renderCursor(context, input)
|
||||
|
||||
if (iconHovered(mouseX, mouseY)) {
|
||||
context.renderTooltip(Minecraft.getInstance().font, lang("ui.pc.filter.tooltip"), mouseX, mouseY)
|
||||
}
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
@@ -81,4 +77,4 @@ class FilterWidget(
|
||||
|
||||
fun iconHovered(mouseX: Int, mouseY: Int): Boolean = mouseX >= (x - 9) && mouseY >= (y + 3)
|
||||
&& mouseX <= ((x - 9) + (ICON_SIZE * SCALE)) && mouseY <= ((y + 3) + (ICON_SIZE * SCALE))
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
|
||||
import com.cobblemon.mod.common.client.gui.ExitButton
|
||||
import com.cobblemon.mod.common.client.gui.pasture.PasturePCGUIConfiguration
|
||||
import com.cobblemon.mod.common.client.gui.TypeIcon
|
||||
import com.cobblemon.mod.common.client.gui.summary.Summary
|
||||
import com.cobblemon.mod.common.client.gui.summary.Summary.Companion.iconCosmeticItemResource
|
||||
@@ -140,7 +141,10 @@ class PCGUI(
|
||||
// Add Exit Button
|
||||
this.addRenderableWidget(
|
||||
ExitButton(pX = x + 320, pY = y + 186) {
|
||||
if (::wallpaperWidget.isInitialized && wallpaperWidget.visible) {
|
||||
if (storageWidget.editingPastureRange) {
|
||||
setPastureRangeEditing(false)
|
||||
playSound(CobblemonSounds.PC_CLICK)
|
||||
} else if (::wallpaperWidget.isInitialized && wallpaperWidget.visible) {
|
||||
wallpaperWidget.visible = false
|
||||
configuration.showParty = true
|
||||
for (button in optionButtons) button.highlighted = false
|
||||
@@ -158,7 +162,7 @@ class PCGUI(
|
||||
pX = x + 220,
|
||||
pY = y + 16,
|
||||
forward = true
|
||||
) { storageWidget.box += 1 }
|
||||
) { if (!storageWidget.editingPastureRange) storageWidget.box += 1 }
|
||||
)
|
||||
|
||||
// Add Backwards Button
|
||||
@@ -167,7 +171,7 @@ class PCGUI(
|
||||
pX = x + 117,
|
||||
pY = y + 16,
|
||||
forward = false
|
||||
) { storageWidget.box -= 1 }
|
||||
) { if (!storageWidget.editingPastureRange) storageWidget.box -= 1 }
|
||||
)
|
||||
|
||||
// Add Filter Widget
|
||||
@@ -197,6 +201,8 @@ class PCGUI(
|
||||
storageWidget = storageWidget
|
||||
)
|
||||
this.addRenderableWidget(boxNameWidget)
|
||||
filterWidget.active = !storageWidget.editingPastureRange
|
||||
boxNameWidget.active = !storageWidget.editingPastureRange
|
||||
|
||||
// Initialise box options if not pasture
|
||||
if (storageWidget.pastureWidget == null) {
|
||||
@@ -691,6 +697,10 @@ class PCGUI(
|
||||
|
||||
super.render(context, mouseX, mouseY, delta)
|
||||
|
||||
if (!storageWidget.editingPastureRange && filterWidget.iconHovered(mouseX, mouseY)) {
|
||||
context.renderTooltip(Minecraft.getInstance().font, lang("ui.pc.filter.tooltip"), mouseX, mouseY)
|
||||
}
|
||||
|
||||
// Item Tooltip
|
||||
if (pokemon != null) {
|
||||
val displayedItem = if (showCosmeticItem) pokemon.cosmeticItem else pokemon.heldItemNoCopy()
|
||||
@@ -711,17 +721,34 @@ class PCGUI(
|
||||
}
|
||||
}
|
||||
|
||||
fun setPastureRangeEditing(editing: Boolean) {
|
||||
val pasture = configuration as? PasturePCGUIConfiguration ?: return
|
||||
pasture.editingRange = editing
|
||||
if (!editing) storageWidget.pastureRangeWidget?.closeInfo()
|
||||
filterWidget.active = !editing
|
||||
boxNameWidget.active = !editing
|
||||
focused = null
|
||||
storageWidget.resetSelected()
|
||||
}
|
||||
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
val rangeWidget = storageWidget.pastureRangeWidget
|
||||
if (storageWidget.editingPastureRange && rangeWidget?.isInfoOpen == true &&
|
||||
children().none { it is ExitButton && it.isHovered(mouseX, mouseY) }) {
|
||||
return rangeWidget.mouseClicked(mouseX, mouseY, button)
|
||||
}
|
||||
// Trigger child component function as they will need to check the entire screen click area
|
||||
if (::boxNameWidget.isInitialized) boxNameWidget.mouseClicked(mouseX, mouseY, button)
|
||||
if (::boxNameWidget.isInitialized && !storageWidget.editingPastureRange) boxNameWidget.mouseClicked(mouseX, mouseY, button)
|
||||
return super.mouseClicked(mouseX, mouseY, button)
|
||||
}
|
||||
|
||||
override fun mouseScrolled(mouseX: Double, mouseY: Double, amount: Double, verticalAmount: Double): Boolean {
|
||||
if (storageWidget.editingPastureRange &&
|
||||
storageWidget.pastureRangeWidget?.mouseScrolled(mouseX, mouseY, amount, verticalAmount) == true) return true
|
||||
storageWidget.pastureWidget?.let { pasture ->
|
||||
if (pasture.pastureScrollList.isHovered) pasture.pastureScrollList.mouseScrolled(mouseX, mouseY, amount, verticalAmount)
|
||||
}
|
||||
if (storageWidget.isHovered && mouseX < (storageWidget.x + StorageWidget.SCREEN_WIDTH)) this.storageWidget.box -= verticalAmount.toInt()
|
||||
if (!storageWidget.editingPastureRange && storageWidget.isHovered && mouseX < (storageWidget.x + StorageWidget.SCREEN_WIDTH)) this.storageWidget.box -= verticalAmount.toInt()
|
||||
if (::wallpaperWidget.isInitialized && wallpaperWidget.isHovered) wallpaperWidget.mouseScrolled(mouseX, mouseY, amount, verticalAmount)
|
||||
|
||||
val infoBoxX = ((width - BASE_WIDTH) / 2) + 9
|
||||
@@ -744,6 +771,11 @@ class PCGUI(
|
||||
}
|
||||
|
||||
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
|
||||
if (storageWidget.editingPastureRange) {
|
||||
if (keyCode != InputConstants.KEY_ESCAPE &&
|
||||
storageWidget.pastureRangeWidget?.keyPressed(keyCode, scanCode, modifiers) == true) return true
|
||||
if (keyCode == InputConstants.KEY_RIGHT || keyCode == InputConstants.KEY_LEFT) return true
|
||||
}
|
||||
val boxNameFocused = this::boxNameWidget.isInitialized && boxNameWidget.isFocused
|
||||
val filterFocused = this::filterWidget.isInitialized && filterWidget.isFocused
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.CobblemonResources
|
||||
import com.cobblemon.mod.common.client.gui.pasture.PasturePCGUIConfiguration
|
||||
import com.cobblemon.mod.common.client.gui.pasture.PastureWidget
|
||||
import com.cobblemon.mod.common.client.gui.pasture.PastureRangeWidget
|
||||
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.client.render.gui.PCBoxWallpaperRepository
|
||||
@@ -74,6 +75,9 @@ class StorageWidget(
|
||||
private val releaseNoButton: ReleaseConfirmButton
|
||||
|
||||
var pastureWidget: PastureWidget? = null
|
||||
val pastureRangeWidget = (pcGui.configuration as? PasturePCGUIConfiguration)?.let { PastureRangeWidget(x, y, it) }
|
||||
val editingPastureRange: Boolean
|
||||
get() = (pcGui.configuration as? PasturePCGUIConfiguration)?.editingRange == true
|
||||
|
||||
var displayConfirmRelease = false
|
||||
var screenLoaded = false
|
||||
@@ -218,6 +222,11 @@ class StorageWidget(
|
||||
}
|
||||
|
||||
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
if (editingPastureRange) {
|
||||
pastureRangeWidget?.render(context, mouseX, mouseY, delta)
|
||||
pastureWidget?.render(context, mouseX, mouseY, delta)
|
||||
return
|
||||
}
|
||||
val matrices = context.pose()
|
||||
// Party Label
|
||||
if (pcGui.configuration.showParty) {
|
||||
@@ -340,6 +349,10 @@ class StorageWidget(
|
||||
}
|
||||
|
||||
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
|
||||
if (editingPastureRange) {
|
||||
return pastureRangeWidget?.mouseClicked(pMouseX, pMouseY, pButton) == true ||
|
||||
pastureWidget?.mouseClicked(pMouseX, pMouseY, pButton) == true
|
||||
}
|
||||
if (displayConfirmRelease) {
|
||||
if (releaseYesButton.isHovered(pMouseX, pMouseY)) releaseYesButton.mouseClicked(pMouseX, pMouseY, pButton)
|
||||
if (releaseNoButton.isHovered(pMouseX, pMouseY)) releaseNoButton.mouseClicked(pMouseX, pMouseY, pButton)
|
||||
|
||||
+2
@@ -23,6 +23,8 @@ object OpenPastureHandler : ClientNetworkPacketHandler<OpenPasturePacket> {
|
||||
pastureId = packet.pastureId,
|
||||
limit = packet.limit,
|
||||
permissions = packet.permissions,
|
||||
range = packet.range,
|
||||
facing = packet.facing,
|
||||
pasturedPokemon = SettableObservable(packet.tetheredPokemon)
|
||||
)
|
||||
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.client.net.pasture
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.client.gui.pasture.PasturePCGUIConfiguration
|
||||
import com.cobblemon.mod.common.client.gui.pc.PCGUI
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureRangePacket
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||
|
||||
object UpdatePastureRangeHandler : ClientNetworkPacketHandler<UpdatePastureRangePacket> {
|
||||
override fun handle(packet: UpdatePastureRangePacket, client: Minecraft) {
|
||||
val configuration = (client.screen as? PCGUI)?.configuration as? PasturePCGUIConfiguration ?: return
|
||||
if (configuration.pastureId != packet.pastureId) return
|
||||
configuration.range = packet.range
|
||||
client.soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.PC_CLICK, 1F))
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.client.net.pasture
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.client.gui.pc.PCGUI
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureRecoveryPacket
|
||||
import net.minecraft.client.Minecraft
|
||||
|
||||
object UpdatePastureRecoveryHandler : ClientNetworkPacketHandler<UpdatePastureRecoveryPacket> {
|
||||
override fun handle(packet: UpdatePastureRecoveryPacket, client: Minecraft) {
|
||||
val slots = (client.screen as? PCGUI)?.storage?.pastureWidget?.pastureScrollList?.children() ?: return
|
||||
slots.firstOrNull { it.pokemon.pokemonId == packet.pokemonId }?.pokemon?.apply {
|
||||
healingAfterFaint = packet.fainted
|
||||
respawnAt = packet.respawnAt
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,7 +166,6 @@ import net.minecraft.world.item.Items
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.LightLayer
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.HorizontalDirectionalBlock
|
||||
import net.minecraft.world.level.block.SuspiciousEffectHolder
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.gameevent.GameEvent
|
||||
@@ -304,6 +303,7 @@ open class PokemonEntity(
|
||||
var drops: DropTable? = null
|
||||
|
||||
var tethering: PokemonPastureBlockEntity.Tethering? = null
|
||||
private var nextPastureReturnAttempt = 0
|
||||
|
||||
// TODO review if we still want this
|
||||
var queuedToDespawn = false
|
||||
@@ -738,34 +738,29 @@ open class PokemonEntity(
|
||||
|
||||
// Check to see if the entity needs recalled or repositioned if pastured.
|
||||
fun checkPastureTether() {
|
||||
// Teleport a pastured mon back to the pasture block if they are out of range
|
||||
if (this.tethering != null && !this.tethering!!.box.contains(this.x, this.y, this.z)) {
|
||||
|
||||
// Gather the ideal position (behind the pastureBlock)
|
||||
val pastureBlockPos = this.tethering!!.pasturePos
|
||||
val state = this.level().getBlockState(pastureBlockPos)
|
||||
|
||||
// Check to ensure we are working with a pastureblock here and it hasn't been broken
|
||||
if (state.block == CobblemonBlocks.PASTURE) {
|
||||
val direction = state.getValue(HorizontalDirectionalBlock.FACING)
|
||||
val width = this.boundingBox.xsize
|
||||
val tpPos = pastureBlockPos.offset(direction.normal.multiply(ceil(width).toInt() + 1)).center
|
||||
val heightAllowance = 4.0 // Allow spawning up to x blocks above the pastureBlock y level
|
||||
|
||||
// Attempt tp and recall if it fails
|
||||
if (!this.randomTeleport(tpPos.x, tpPos.y + heightAllowance, tpPos.z, false)) {
|
||||
// If the teleport was not successful then recall the mon
|
||||
this.tethering = null
|
||||
this.pokemon.recall()
|
||||
} else {
|
||||
// Stop any now invalid navigation post teleport
|
||||
this.navigation.stop()
|
||||
}
|
||||
} else {
|
||||
// If the block is destroyed or something else went wrong.
|
||||
this.tethering = null
|
||||
this.pokemon.recall()
|
||||
}
|
||||
val currentTethering = tethering ?: return
|
||||
val world = level() as? ServerLevel ?: return
|
||||
if (ticksLived == 1 && world.hasChunkAt(currentTethering.pasturePos)) {
|
||||
val pasture = world.getBlockEntity(currentTethering.pasturePos) as? PokemonPastureBlockEntity
|
||||
pasture?.tetheredPokemon?.find { it.tetheringId == currentTethering.tetheringId }?.let { tethering = it }
|
||||
}
|
||||
if (tethering!!.canRoamTo(x, z) || !world.hasChunkAt(currentTethering.pasturePos)) return
|
||||
navigation.stop()
|
||||
(moveControl as? PokemonMoveControl)?.apply { stop(); stopBanking() }
|
||||
brain.eraseMemory(MemoryModuleType.WALK_TARGET)
|
||||
deltaMovement = Vec3.ZERO
|
||||
if (ticksLived < nextPastureReturnAttempt) return
|
||||
val pasture = world.getBlockEntity(currentTethering.pasturePos) as? PokemonPastureBlockEntity
|
||||
val position = pasture?.findSpawnPosition(this, blockPosition())
|
||||
if (position != null) {
|
||||
teleportTo(position.x, position.y, position.z)
|
||||
nextPastureReturnAttempt = 0
|
||||
} else if (pasture == null) {
|
||||
tethering = null
|
||||
pokemon.recall()
|
||||
return
|
||||
} else {
|
||||
nextPastureReturnAttempt = ticksLived + 20
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1800,7 +1795,7 @@ open class PokemonEntity(
|
||||
removalObservable.emit(reason)
|
||||
this.brain.clearMemories()
|
||||
|
||||
if (reason.shouldDestroy() && pokemon.tetheringId != null) {
|
||||
if (reason.shouldDestroy() && pokemon.tetheringId != null && tethering == null) {
|
||||
pokemon.tetheringId = null
|
||||
}
|
||||
if (evolutionEntity != null) {
|
||||
|
||||
+20
-2
@@ -29,6 +29,7 @@ import net.minecraft.tags.FluidTags
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes
|
||||
import net.minecraft.world.entity.ai.control.MoveControl
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
import net.minecraft.world.level.material.FluidState
|
||||
import net.minecraft.world.level.pathfinder.PathType
|
||||
import net.minecraft.world.phys.AABB
|
||||
@@ -106,7 +107,14 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
|
||||
|
||||
if (banking) {
|
||||
pokemonEntity.yRot += bankRightDegreesPerTick
|
||||
pokemonEntity.travel(Vec3(0.0, bankUpwardsBlocksPerTick.toDouble(), bankForwardBlocksPerTick.toDouble()).scale(mediumSpeed.toDouble()))
|
||||
val forward = bankForwardBlocksPerTick * mediumSpeed
|
||||
val tethering = pokemonEntity.tethering
|
||||
if (tethering?.canRoamTo(
|
||||
pokemonEntity.x - Mth.sin(pokemonEntity.yRot.toRadians()) * forward,
|
||||
pokemonEntity.z + Mth.cos(pokemonEntity.yRot.toRadians()) * forward
|
||||
) != false) {
|
||||
pokemonEntity.travel(Vec3(0.0, bankUpwardsBlocksPerTick.toDouble(), bankForwardBlocksPerTick.toDouble()).scale(mediumSpeed.toDouble()))
|
||||
}
|
||||
if (bankDurationTicks > 0) {
|
||||
bankDurationTicks--
|
||||
}
|
||||
@@ -280,6 +288,16 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
|
||||
}
|
||||
}
|
||||
}
|
||||
val tethering = pokemonEntity.tethering
|
||||
val motion = mob.deltaMovement
|
||||
if (tethering?.canRoamTo(mob.x, mob.z) == true &&
|
||||
!tethering.canRoamTo(mob.x + motion.x, mob.z + motion.z)) {
|
||||
mob.navigation.stop()
|
||||
pokemonEntity.brain.eraseMemory(MemoryModuleType.WALK_TARGET)
|
||||
stop()
|
||||
stopBanking()
|
||||
mob.deltaMovement = Vec3(0.0, motion.y, 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -369,4 +387,4 @@ class PokemonMoveControl(val pokemonEntity: PokemonEntity) : MoveControl(pokemon
|
||||
strafeForwards = 0.0f
|
||||
strafeRight = 0.0f
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
-4
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.net.messages.client.pasture
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.api.pasture.PasturePermissions
|
||||
import com.cobblemon.mod.common.api.pasture.PastureRange
|
||||
import com.cobblemon.mod.common.client.net.pasture.OpenPastureHandler
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
|
||||
import com.cobblemon.mod.common.net.IntSize
|
||||
@@ -19,6 +20,7 @@ import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.core.Direction
|
||||
|
||||
/**
|
||||
* Opens a pasture GUI using the provided data.
|
||||
@@ -28,7 +30,7 @@ import net.minecraft.resources.ResourceLocation
|
||||
* @author Hiroku
|
||||
* @since April 9th, 2023
|
||||
*/
|
||||
class OpenPasturePacket(val pcId: UUID, val pastureId: UUID, val limit: Int, val tetheredPokemon: List<PasturePokemonDataDTO>, val permissions: PasturePermissions) : NetworkPacket<OpenPasturePacket> {
|
||||
class OpenPasturePacket(val pcId: UUID, val pastureId: UUID, val limit: Int, val tetheredPokemon: List<PasturePokemonDataDTO>, val permissions: PasturePermissions, val range: PastureRange, val facing: Direction) : NetworkPacket<OpenPasturePacket> {
|
||||
class PasturePokemonDataDTO(
|
||||
val pokemonId: UUID,
|
||||
val playerId: UUID,
|
||||
@@ -39,7 +41,9 @@ class OpenPasturePacket(val pcId: UUID, val pastureId: UUID, val limit: Int, val
|
||||
val heldItem: ItemStack,
|
||||
val level: Int,
|
||||
val entityKnown: Boolean,
|
||||
var behaviourFlags: Set<PokemonBehaviourFlag> = emptySet()
|
||||
var behaviourFlags: Set<PokemonBehaviourFlag> = emptySet(),
|
||||
var healingAfterFaint: Boolean = false,
|
||||
var respawnAt: Long = 0L
|
||||
) {
|
||||
companion object {
|
||||
fun decode(buffer: RegistryFriendlyByteBuf): PasturePokemonDataDTO {
|
||||
@@ -53,6 +57,8 @@ class OpenPasturePacket(val pcId: UUID, val pastureId: UUID, val limit: Int, val
|
||||
val level = buffer.readSizedInt(IntSize.U_SHORT)
|
||||
val entityKnown = buffer.readBoolean()
|
||||
val behaviourFlags = buffer.readList { it.readEnumConstant(PokemonBehaviourFlag::class.java) }.toSet()
|
||||
val healingAfterFaint = buffer.readBoolean()
|
||||
val respawnAt = buffer.readLong()
|
||||
|
||||
return PasturePokemonDataDTO(
|
||||
pokemonId = pokemonId,
|
||||
@@ -64,7 +70,9 @@ class OpenPasturePacket(val pcId: UUID, val pastureId: UUID, val limit: Int, val
|
||||
heldItem = heldItem,
|
||||
level = level,
|
||||
entityKnown = entityKnown,
|
||||
behaviourFlags = behaviourFlags
|
||||
behaviourFlags = behaviourFlags,
|
||||
healingAfterFaint = healingAfterFaint,
|
||||
respawnAt = respawnAt
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -80,6 +88,8 @@ class OpenPasturePacket(val pcId: UUID, val pastureId: UUID, val limit: Int, val
|
||||
buffer.writeSizedInt(IntSize.U_SHORT, level)
|
||||
buffer.writeBoolean(entityKnown)
|
||||
buffer.writeCollection(behaviourFlags) { _, flag -> buffer.writeEnumConstant(flag) }
|
||||
buffer.writeBoolean(healingAfterFaint)
|
||||
buffer.writeLong(respawnAt)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +105,7 @@ class OpenPasturePacket(val pcId: UUID, val pastureId: UUID, val limit: Int, val
|
||||
dtos.add(PasturePokemonDataDTO.decode(buffer))
|
||||
}
|
||||
val permissions = PasturePermissions.decode(buffer)
|
||||
return OpenPasturePacket(pcId, pastureId, limit, dtos, permissions)
|
||||
return OpenPasturePacket(pcId, pastureId, limit, dtos, permissions, PastureRange.decode(buffer), buffer.readEnum(Direction::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,5 +120,7 @@ class OpenPasturePacket(val pcId: UUID, val pastureId: UUID, val limit: Int, val
|
||||
tethered.encode(buffer)
|
||||
}
|
||||
permissions.encode(buffer)
|
||||
range.encode(buffer)
|
||||
buffer.writeEnum(facing)
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.net.messages.client.pasture
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.api.pasture.PastureRange
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import java.util.UUID
|
||||
|
||||
class UpdatePastureRangePacket(val pastureId: UUID, val range: PastureRange) : NetworkPacket<UpdatePastureRangePacket> {
|
||||
override val id = ID
|
||||
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeUUID(pastureId)
|
||||
range.encode(buffer)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("update_pasture_range")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = UpdatePastureRangePacket(
|
||||
buffer.readUUID(), PastureRange.decode(buffer)
|
||||
)
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.net.messages.client.pasture
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import java.util.UUID
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
|
||||
data class UpdatePastureRecoveryPacket(val pokemonId: UUID, val fainted: Boolean, val respawnAt: Long) : NetworkPacket<UpdatePastureRecoveryPacket> {
|
||||
override val id = ID
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("update_pasture_recovery")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = UpdatePastureRecoveryPacket(buffer.readUUID(), buffer.readBoolean(), buffer.readLong())
|
||||
}
|
||||
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeUUID(pokemonId)
|
||||
buffer.writeBoolean(fainted)
|
||||
buffer.writeLong(respawnAt)
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.net.messages.server.pasture
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import java.util.UUID
|
||||
|
||||
class AdjustPastureRangePacket(val pastureId: UUID, val direction: Direction, val increase: Boolean) : NetworkPacket<AdjustPastureRangePacket> {
|
||||
override val id = ID
|
||||
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeUUID(pastureId)
|
||||
buffer.writeEnum(direction)
|
||||
buffer.writeBoolean(increase)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("adjust_pasture_range")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = AdjustPastureRangePacket(
|
||||
buffer.readUUID(), buffer.readEnum(Direction::class.java), buffer.readBoolean()
|
||||
)
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.net.serverhandling.pasture
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.api.pasture.PastureLinkManager
|
||||
import com.cobblemon.mod.common.api.pasture.PastureRange
|
||||
import com.cobblemon.mod.common.block.entity.PokemonPastureBlockEntity
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureRangePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.pasture.AdjustPastureRangePacket
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.server.MinecraftServer
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
object AdjustPastureRangeHandler : ServerNetworkPacketHandler<AdjustPastureRangePacket> {
|
||||
override fun handle(packet: AdjustPastureRangePacket, server: MinecraftServer, player: ServerPlayer) {
|
||||
val link = PastureLinkManager.getLinkByPlayer(player) ?: return
|
||||
if (link.linkId != packet.pastureId || packet.direction.axis == Direction.Axis.Y) return
|
||||
val pasture = player.serverLevel().getBlockEntity(link.pos) as? PokemonPastureBlockEntity ?: return
|
||||
val range = pasture.getRange().adjusted(packet.direction, if (packet.increase) 1 else -1)
|
||||
if (!range.isValid(PastureRange.MAX_DISTANCE)) {
|
||||
UpdatePastureRangePacket(link.linkId, pasture.getRange()).sendToPlayer(player)
|
||||
return
|
||||
}
|
||||
pasture.setRange(range)
|
||||
for (viewer in player.serverLevel().players()) {
|
||||
val viewerLink = PastureLinkManager.getLinkByPlayer(viewer) ?: continue
|
||||
if (viewerLink.pos == link.pos && viewerLink.dimension == link.dimension) {
|
||||
UpdatePastureRangePacket(viewerLink.linkId, range).sendToPlayer(viewer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-13
@@ -9,24 +9,18 @@
|
||||
package com.cobblemon.mod.common.net.serverhandling.pasture
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.api.pasture.PastureLinkManager
|
||||
import com.cobblemon.mod.common.block.entity.PokemonPastureBlockEntity
|
||||
import com.cobblemon.mod.common.net.messages.server.pasture.SetPastureConflictPacket
|
||||
import net.minecraft.server.MinecraftServer
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import com.cobblemon.mod.common.net.messages.client.pasture.UpdatePastureConflictFlagPacket
|
||||
|
||||
object SetPastureConflictHandler : ServerNetworkPacketHandler<SetPastureConflictPacket> {
|
||||
override fun handle(packet: SetPastureConflictPacket, server: MinecraftServer, player: ServerPlayer) {
|
||||
val world = player.level()
|
||||
val entity = world.getEntitiesOfClass(PokemonEntity::class.java, player.boundingBox.inflate(64.0))
|
||||
.firstOrNull { it.pokemon.uuid == packet.pokemonId }
|
||||
?: return
|
||||
val link = PastureLinkManager.getLinkByPlayer(player) ?: return
|
||||
val pasture = player.level().getBlockEntity(link.pos) as? PokemonPastureBlockEntity ?: return
|
||||
val tethering = pasture.tetheredPokemon.firstOrNull { it.pokemonId == packet.pokemonId && it.playerId == player.uuid } ?: return
|
||||
pasture.setConflict(tethering, packet.enabled)
|
||||
|
||||
entity.setBehaviourFlag(PokemonBehaviourFlag.PASTURE_CONFLICT, packet.enabled)
|
||||
entity.remakeBrain()
|
||||
|
||||
// Send packet from the server to client GUI to update the toggle button
|
||||
UpdatePastureConflictFlagPacket(packet.pokemonId, packet.enabled).sendToPlayer(player)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1307,6 +1307,18 @@
|
||||
"cobblemon.ui.sort.discovered": "Sort by discovered status",
|
||||
"cobblemon.ui.pasture": "Pasture",
|
||||
"cobblemon.ui.pasture.recall_all": "Recall All",
|
||||
"cobblemon.ui.pasture.range": "Range",
|
||||
"cobblemon.ui.pasture.storage": "PC",
|
||||
"cobblemon.ui.pasture.fainted": "Fainted",
|
||||
"cobblemon.ui.pasture.returning": "Returning",
|
||||
"cobblemon.ui.pasture.ready": "Waiting",
|
||||
"cobblemon.ui.pasture.range.left_click": "Left click",
|
||||
"cobblemon.ui.pasture.range.right_click": "Right click",
|
||||
"cobblemon.ui.pasture.range.help_title": "Range controls",
|
||||
"cobblemon.ui.pasture.range.increase": "Increase",
|
||||
"cobblemon.ui.pasture.range.decrease": "Decrease",
|
||||
"cobblemon.ui.pasture.range.scroll_up": "Scroll up",
|
||||
"cobblemon.ui.pasture.range.scroll_down": "Scroll down",
|
||||
"cobblemon.pasture.too_many_nearby": "There are too many Pokémon nearby.",
|
||||
"cobblemon.ui.interact.give.item": "Change held item",
|
||||
"cobblemon.ui.interact.give.cosmetic_item": "Change cosmetic item",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.9 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 177 B |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 145 B |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 347 B |
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.api.pasture
|
||||
|
||||
import com.cobblemon.mod.common.block.entity.PokemonPastureBlockEntity.Tethering
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.util.UUID
|
||||
|
||||
class PastureRangeTest {
|
||||
@Test
|
||||
fun `spec examples allow zero sides while requiring eight blocks on both axes`() {
|
||||
listOf(
|
||||
PastureRange(0, 8, 8, 0), PastureRange(4, 4, 4, 4), PastureRange(2, 8, 6, 0)
|
||||
).forEach { assertTrue(it.isValid(64), it.toString()) }
|
||||
listOf(
|
||||
PastureRange(8, 7, 8, 0), PastureRange(64, 4, 64, 3)
|
||||
).forEach { assertFalse(it.isValid(64), it.toString()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `every side obeys the configured maximum and rejects negative or overflowing input`() {
|
||||
assertTrue(PastureRange(32, 32, 32, 32).isValid(32))
|
||||
assertTrue(PastureRange(64, 64, 64, 64).isValid(PastureRange.MAX_DISTANCE))
|
||||
assertFalse(PastureRange(65, 64, 64, 64).isValid(PastureRange.MAX_DISTANCE))
|
||||
listOf(
|
||||
PastureRange(33, 32, 32, 32), PastureRange(32, 33, 32, 32),
|
||||
PastureRange(32, 32, 33, 32), PastureRange(32, 32, 32, 33),
|
||||
PastureRange(-1, 8, 9, 0), PastureRange(Int.MAX_VALUE, 8, Int.MAX_VALUE, 0)
|
||||
).forEach { assertFalse(it.isValid(32), it.toString()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an adjustment changes only the requested cardinal side`() {
|
||||
val range = PastureRange(4, 4, 4, 4)
|
||||
Direction.Plane.HORIZONTAL.forEach { direction ->
|
||||
val changed = range.adjusted(direction, 1)
|
||||
assertEquals(5, changed[direction])
|
||||
assertEquals(4, changed[direction.opposite])
|
||||
assertTrue(changed.isValid(32))
|
||||
assertFalse(range.adjusted(direction, -1).isValid(32))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `saved bounds round trip at negative world coordinates`() {
|
||||
val origin = BlockPos(-100, 70, -200)
|
||||
val range = PastureRange(2, 8, 6, 0)
|
||||
assertEquals(range, PastureRange.fromBounds(origin, range.minPos(origin), range.maxPos(origin)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a zero length facing side still offers spawn blocks elsewhere in the minimum range`() {
|
||||
val range = PastureRange(0, 8, 8, 0)
|
||||
val candidates = range.nearbyBlocks(BlockPos.ZERO, BlockPos(0, 0, -4), 1.0).toList()
|
||||
assertEquals(BlockPos.ZERO, candidates.first())
|
||||
assertTrue(BlockPos(7, 0, 7) in candidates)
|
||||
assertTrue(candidates.all { it.x in 0..7 && it.z in 0..7 })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `return search starts at the nearest block that fits the pokemon inside the edge`() {
|
||||
val range = PastureRange(4, 4, 4, 4)
|
||||
val candidates = range.nearbyBlocks(BlockPos.ZERO, BlockPos(99, 0, 1), 2.0).toList()
|
||||
assertEquals(BlockPos(2, 0, 1), candidates.first())
|
||||
assertTrue(candidates.all { it.x in -3..2 && it.z in -3..2 })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a pokemon wider than the entire range has no safe center`() {
|
||||
assertTrue(PastureRange(0, 8, 8, 0).nearbyBlocks(BlockPos.ZERO, BlockPos.ZERO, 9.0).none())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `pasture bounds constrain horizontal movement without a ceiling`() {
|
||||
val tether = tether()
|
||||
assertTrue(tether.canRoamTo(BlockPos(0, -10000, 0)))
|
||||
assertTrue(tether.canRoamTo(BlockPos(0, 10000, 0)))
|
||||
assertFalse(tether.canRoamTo(-4.01, 0.0))
|
||||
assertFalse(tether.canRoamTo(4.0, 0.0))
|
||||
tether.maxRoamPos = BlockPos(8, 4, 4)
|
||||
assertTrue(tether.canRoamTo(7.5, 0.0))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fainting starts one day of recovery without restarting it each check`() {
|
||||
val tether = tether()
|
||||
tether.beginRecovery(100L, fainted = true)
|
||||
assertEquals(24100L, tether.respawnAt)
|
||||
assertTrue(tether.healingAfterFaint)
|
||||
tether.beginRecovery(140L, fainted = true)
|
||||
assertEquals(24100L, tether.respawnAt)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `accidental disappearance has a shorter cooldown and fainting takes precedence`() {
|
||||
val tether = tether()
|
||||
tether.beginRecovery(100L, fainted = false)
|
||||
assertEquals(300L, tether.respawnAt)
|
||||
assertFalse(tether.healingAfterFaint)
|
||||
tether.beginRecovery(140L, fainted = true)
|
||||
assertEquals(24140L, tether.respawnAt)
|
||||
tether.beginRecovery(180L, fainted = false)
|
||||
assertEquals(24140L, tether.respawnAt)
|
||||
assertTrue(tether.healingAfterFaint)
|
||||
}
|
||||
|
||||
private fun tether() = Tethering(
|
||||
BlockPos(-4, -4, -4), BlockPos(4, 4, 4), UUID.randomUUID(), "Owner",
|
||||
UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), 1, BlockPos.ZERO
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user