mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-10-20 14:16:26 -04:00
Seat generator
This commit is contained in:
+2
-1
@@ -1,3 +1,4 @@
|
||||
### Additions
|
||||
|
||||
- Added the ability to ride Pokémon by shift-right clicking and selecting the ride option.
|
||||
- Added the ability to ride Pokémon by shift-right clicking and selecting the ride option.
|
||||
- Added /calculateseatpositions command for approximating hitbox locations for riding configurations.
|
||||
@@ -66,5 +66,6 @@ object CobblemonCommands {
|
||||
// Possibly lock down registration if and only if under dev environment or running in an environment
|
||||
// with a certain system environment variable set
|
||||
CobblemonInfoCommand.register(dispatcher)
|
||||
CalculateSeatPositionCommand.register(dispatcher)
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.client.net.CalculateSeatPositionsHandler
|
||||
import com.cobblemon.mod.common.client.net.PlayerInteractOptionsHandler
|
||||
import com.cobblemon.mod.common.client.net.SetClientPlayerDataHandler
|
||||
import com.cobblemon.mod.common.client.net.animation.PlayPosableAnimationHandler
|
||||
@@ -56,6 +57,7 @@ import com.cobblemon.mod.common.client.net.trade.TradeProcessStartedHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeStartedHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeUpdatedHandler
|
||||
import com.cobblemon.mod.common.net.PacketRegisterInfo
|
||||
import com.cobblemon.mod.common.net.messages.client.CalculateSeatPositionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
|
||||
@@ -407,6 +409,10 @@ object CobblemonNetwork {
|
||||
|
||||
// Pokédex scanning
|
||||
list.add(PacketRegisterInfo(ServerConfirmedRegisterPacket.ID, ServerConfirmedRegisterPacket::decode, ServerConfirmedRegisterHandler))
|
||||
|
||||
// Debug / cheats
|
||||
list.add(PacketRegisterInfo(CalculateSeatPositionsPacket.ID, CalculateSeatPositionsPacket::decode, CalculateSeatPositionsHandler))
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
|
||||
@@ -129,6 +129,8 @@ object CobblemonPermissions {
|
||||
@JvmStatic
|
||||
val COBBLEMON_CONFIG_RELOAD = this.create("${COMMAND_PREFIX}cobblemonconfig.reload", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
|
||||
|
||||
val CALCULATE_SEAT_POSITIONS = this.create("${COMMAND_PREFIX}calculateseatpositions", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
|
||||
|
||||
fun all(): Iterable<Permission> = this.permissions
|
||||
|
||||
private fun create(node: String, level: PermissionLevel): Permission {
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.text.green
|
||||
import com.cobblemon.mod.common.api.text.plus
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
|
||||
import com.cobblemon.mod.common.net.messages.client.CalculateSeatPositionsPacket
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.network.chat.ClickEvent
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
object CalculateSeatPositionsHandler : ClientNetworkPacketHandler<CalculateSeatPositionsPacket> {
|
||||
private const val FORMAT = "%.2f"
|
||||
private const val SEAT_PREFIX = "seat"
|
||||
private const val SIMULATION_PERIOD_TICKS = 15 * 20F
|
||||
|
||||
class SeatRanges(
|
||||
var minX: Float = 9999F,
|
||||
var minY: Float = 9999F,
|
||||
var minZ: Float = 9999F,
|
||||
var maxX: Float = -9999F,
|
||||
var maxY: Float = -9999F,
|
||||
var maxZ: Float = -9999F
|
||||
)
|
||||
|
||||
override fun handle(packet: CalculateSeatPositionsPacket, client: Minecraft) {
|
||||
val state = FloatingState()
|
||||
state.currentAspects = packet.aspects
|
||||
val model = VaryingModelRepository.getPoser(packet.speciesIdentifier, state)
|
||||
model.context = RenderContext()
|
||||
model.context.put(RenderContext.SPECIES, packet.speciesIdentifier)
|
||||
model.context.put(RenderContext.ASPECTS, packet.aspects)
|
||||
model.context.put(RenderContext.POSABLE_STATE, state)
|
||||
model.context.put(RenderContext.RENDER_STATE, RenderContext.RenderState.WORLD)
|
||||
|
||||
|
||||
state.setPoseToFirstSuitable(packet.poseType)
|
||||
|
||||
val seatData = mutableMapOf<Int, SeatRanges>()
|
||||
|
||||
var ticks = 0F
|
||||
|
||||
// This will loop through the animation and find the extremes of the seat positions so we can later find the avg.
|
||||
// Most animations are going to loop in like 3 seconds but we can't know that, so just run through 15s to be safe.
|
||||
while (ticks < SIMULATION_PERIOD_TICKS) {
|
||||
state.updatePartialTicks(ticks)
|
||||
|
||||
model.applyAnimations(null, state, 0F, 0F, 0F, 0F, 0F)
|
||||
model.updateLocators(null, state)
|
||||
|
||||
for ((key, locatorState) in state.locatorStates.entries.filter { it.key.startsWith(SEAT_PREFIX) }.sortedBy { it.key }) {
|
||||
val index = key.replace(SEAT_PREFIX, "").toInt()
|
||||
val seatPosition = locatorState.transformPosition(Vec3.ZERO)
|
||||
val seatRanges = seatData.getOrPut(index, ::SeatRanges)
|
||||
if (seatPosition.x < seatRanges.minX) seatRanges.minX = seatPosition.x.toFloat()
|
||||
if (seatPosition.y < seatRanges.minY) seatRanges.minY = seatPosition.y.toFloat()
|
||||
if (seatPosition.z < seatRanges.minZ) seatRanges.minZ = seatPosition.z.toFloat()
|
||||
if (seatPosition.x > seatRanges.maxX) seatRanges.maxX = seatPosition.x.toFloat()
|
||||
if (seatPosition.y > seatRanges.maxY) seatRanges.maxY = seatPosition.y.toFloat()
|
||||
if (seatPosition.z > seatRanges.maxZ) seatRanges.maxZ = seatPosition.z.toFloat()
|
||||
}
|
||||
|
||||
ticks++
|
||||
}
|
||||
|
||||
val species = PokemonSpecies.getByIdentifier(packet.speciesIdentifier)!!
|
||||
val form = species.getForm(packet.aspects)
|
||||
val scale = form.baseScale
|
||||
|
||||
var text = "Seats:".text()
|
||||
for ((index, seatRanges) in seatData) {
|
||||
val x = String.format(FORMAT, ((seatRanges.minX + seatRanges.maxX) / 2 * scale))
|
||||
val y = String.format(FORMAT, ((seatRanges.minY + seatRanges.maxY) / 2 * -1 * scale)) // Y is inverted between model and world
|
||||
val z = String.format(FORMAT, ((seatRanges.minZ + seatRanges.maxZ) / 2 * scale))
|
||||
|
||||
val str = """
|
||||
{
|
||||
"x": $x,
|
||||
"y": $y,
|
||||
"z": $z
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
text.plus(" [$index]".green().also {
|
||||
it.style = it.style.withClickEvent(ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, str))
|
||||
})
|
||||
}
|
||||
Minecraft.getInstance().player!!.sendSystemMessage(text)
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.command
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
|
||||
import com.cobblemon.mod.common.command.argument.SpeciesArgumentType
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.net.messages.client.CalculateSeatPositionsPacket
|
||||
import com.cobblemon.mod.common.util.permission
|
||||
import com.mojang.brigadier.Command
|
||||
import com.mojang.brigadier.CommandDispatcher
|
||||
import com.mojang.brigadier.arguments.StringArgumentType
|
||||
import com.mojang.brigadier.context.CommandContext
|
||||
import net.minecraft.commands.CommandSourceStack
|
||||
import net.minecraft.commands.Commands
|
||||
|
||||
object CalculateSeatPositionCommand {
|
||||
fun register(dispatcher : CommandDispatcher<CommandSourceStack>) {
|
||||
dispatcher.register(
|
||||
Commands.literal("calculateseatpositions")
|
||||
.permission(CobblemonPermissions.CALCULATE_SEAT_POSITIONS)
|
||||
.then(
|
||||
Commands.argument("species", SpeciesArgumentType.species())
|
||||
.then(
|
||||
Commands.argument("aspects", StringArgumentType.string())
|
||||
.then(
|
||||
Commands.argument("pose", StringArgumentType.string()).executes(::run)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun run(context: CommandContext<CommandSourceStack>) : Int {
|
||||
val player = context.source.playerOrException
|
||||
val species = SpeciesArgumentType.getPokemon(context, "species")
|
||||
val aspects = StringArgumentType.getString(context, "aspects").split(",").toSet()
|
||||
val pose = StringArgumentType.getString(context, "pose")
|
||||
val poseType = PoseType.valueOf(pose.uppercase())
|
||||
player.sendPacket(CalculateSeatPositionsPacket(species.resourceIdentifier, aspects, poseType))
|
||||
return Command.SINGLE_SUCCESS
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ enum class PoseType {
|
||||
PROFILE,
|
||||
/** A pose for rendering in the party overlay and in minor spaces like the battle tiles. */
|
||||
PORTRAIT,
|
||||
/** For guilded chest */
|
||||
/** For gilded chest */
|
||||
OPEN,
|
||||
/** A simple type for non-living entities or errant cases. */
|
||||
NONE;
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.readString
|
||||
import com.cobblemon.mod.common.util.writeString
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
/**
|
||||
* Packet sent to the client to tell it to simulate animations on the client and generate the most logical
|
||||
* positions for the hitboxes.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since March 12th, 2025
|
||||
*/
|
||||
class CalculateSeatPositionsPacket(val speciesIdentifier: ResourceLocation, val aspects: Set<String>, val poseType: PoseType) : NetworkPacket<CalculateSeatPositionsPacket> {
|
||||
override val id: ResourceLocation = ID
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeResourceLocation(speciesIdentifier)
|
||||
buffer.writeCollection(aspects) { _, aspect -> buffer.writeString(aspect) }
|
||||
buffer.writeString(poseType.name)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("calculate_seat_positions")
|
||||
|
||||
fun decode(buffer: RegistryFriendlyByteBuf): CalculateSeatPositionsPacket {
|
||||
return CalculateSeatPositionsPacket(
|
||||
buffer.readResourceLocation(),
|
||||
buffer.readList { _ -> buffer.readString() }.toSet(),
|
||||
PoseType.valueOf(buffer.readString().uppercase()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
## Calculating Seat Positions for Riding
|
||||
|
||||
The idea of the /calculateseatpositions command is that it ask for a pose type and it will simulate the animations
|
||||
for the Pokémon in whatever the first pose is for that type. That will help it find the average position that each seat
|
||||
locator was for the pose, allowing the user to click each seat index to get the JSON of the offset data onto the
|
||||
clipboard for easy pasting into the species data.
|
||||
|
||||
Usage: `/calculateseatpositions <species> <aspects> <pose>`
|
||||
|
||||
Example 1: `/calculateseatpositions araquanid . STAND`
|
||||
|
||||
Example 2: `/calculateseatpositions charizard megax,shiny FLY`
|
||||
|
||||
The aspects are optional and can be a comma separated list of aspects to apply to the Pokémon. The pose is required. If
|
||||
there are no seats for the pose, it will just list out no seats.
|
||||
|
||||
The seat data for a species is a little bit complicated, because you can register a seat that applies regardless of poses
|
||||
or you can specify for specific sets of pose types what the offset will be.
|
||||
|
||||
Here is an example of a seat object that has one offset used in general but a specific one for when they are swimming.
|
||||
|
||||
```json
|
||||
{
|
||||
"offset": {
|
||||
"x": 0.0,
|
||||
"y": 0.4,
|
||||
"z": 0.0
|
||||
},
|
||||
"poseOffsets": [
|
||||
{
|
||||
"poseTypes": ["FLOAT", "SWIM"],
|
||||
"offset": {
|
||||
"x": 0.0,
|
||||
"y": 0.7,
|
||||
"z": 0.2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The command will copy to clipboard just the `x`, `y`, `z` part, e.g. `{ "x": 0.0, "y": 0.4, "z": 0.0 }`. How you choose
|
||||
to implement this data in the seat data is up to you based on how close you think different poses are to having the
|
||||
player in the same position.
|
||||
Reference in New Issue
Block a user