mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-11-14 12:18:54 -05:00
Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86d48df9fe | ||
|
|
3401ba9cbb | ||
|
|
dac9ccb461 | ||
|
|
191a62f693 | ||
|
|
d8d410e539 | ||
|
|
b7c6e1ea77 | ||
|
|
0af4588a14 | ||
|
|
186812d6fd | ||
|
|
df9dc8c029 | ||
|
|
51dc606246 | ||
|
|
790f8347f8 | ||
|
|
09de901b35 | ||
|
|
1c0bdc766a | ||
|
|
c697bcd2e9 | ||
|
|
dcf321da8f | ||
|
|
c32bd4d014 | ||
|
|
ab4e2f2540 | ||
|
|
741bfb3316 | ||
|
|
9d6ff01d80 | ||
|
|
1691853a19 | ||
|
|
4e9a4e1e5a | ||
|
|
6450255a0b |
No files matched your search
@@ -5,6 +5,7 @@
|
||||
- Added an in-game configuration screen, allowing all settings from `main.json` to be edited directly in-game.
|
||||
- Added `/cobblemonconfig reload` command to reload `main.json` configuration. **Note:** Some settings require a server restart to take effect; use this command cautiously.
|
||||
- Added `blacklisted_items_to_hold` and `whitelisted_items_to_hold` tags to allow for controlling which items players can give to their Pokémon. If the whitelist is empty, it will consider all item as allowed (unless they are in the blacklist).
|
||||
- Pokémon now follow the mouse cursor on the Summary screen, with an option to disable this in the settings.
|
||||
|
||||
### Changes
|
||||
- Renamed `chargeGainedPerTick` config to `secondsToChargeHealingMachine`.
|
||||
@@ -19,6 +20,7 @@
|
||||
- Fixed players getting disconnected when sidemods update a Pokémon's teratype
|
||||
- Fixed fling not using Item Names for minecraft held items that substitute Pokémon items
|
||||
- Fixed evolutions sometimes preventing players from logging in to servers
|
||||
- Fixed rendering of shoulder-mounted Pokémon desyncing between clients
|
||||
|
||||
### Developer
|
||||
- A finished battle now has winners and losers set inside of `PokemonBattle` instead of them always being empty.
|
||||
@@ -30,6 +32,8 @@
|
||||
- Renamed Cobblemon's creative tabs to start with "Cobblemon: " to distinguish Cobblemon's tabs from tabs for other mods.
|
||||
- Various items now have a rarity value.
|
||||
|
||||
### Changes
|
||||
- When using the `cobblemon` or `generation_9` capture calculators a critical capture with a single shake will always play for successful captures when you've already registered the Pokémon as caught in your Pokédex.
|
||||
### MoLang & Datapacks
|
||||
- The following usages for item predicates can now use item conditions like advancements do, you can learn about them in the [Minecraft wiki](https://minecraft.wiki/w/Advancement_definition#minecraft:filled_bucket)
|
||||
- The `requiredContext` for an item interaction evolution
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Cobblemon
|
||||
|
||||
An open-source Pokémon mod for Minecraft Java Edition, written in Kotlin for Fabric and Forge.
|
||||
An open-source Pokémon mod for Minecraft Java Edition, written in Kotlin for Fabric and NeoForge.
|
||||
|
||||
## Getting started
|
||||
|
||||
|
||||
@@ -36,6 +36,9 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
|
||||
@JvmField
|
||||
val PC: BlockEntityType<PCBlockEntity> = this.create("pc", BlockEntityType.Builder.of(::PCBlockEntity, CobblemonBlocks.PC).build(null))
|
||||
|
||||
@JvmField
|
||||
val STADIUM: BlockEntityType<StadiumBlockEntity> = this.create("stadium_terminal", BlockEntityType.Builder.of(::StadiumBlockEntity, CobblemonBlocks.STADIUM).build(null))
|
||||
|
||||
@JvmField
|
||||
val LECTERN: BlockEntityType<LecternBlockEntity> = this.create("lectern", BlockEntityType.Builder.of(::LecternBlockEntity, CobblemonBlocks.LECTERN).build(null))
|
||||
|
||||
|
||||
@@ -354,6 +354,9 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val PC = create("pc", PCBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2F).noOcclusion().lightLevel { if ((it.getValue(PCBlock.ON) as Boolean) && (it.getValue(PCBlock.PART) == PCBlock.PCPart.TOP)) 10 else 0 }))
|
||||
|
||||
@JvmField
|
||||
val STADIUM = create("stadium_terminal", StadiumBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2F)))
|
||||
|
||||
@JvmField
|
||||
val LECTERN = create("lectern", LecternBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).sound(SoundType.WOOD).strength(2.5F).ignitedByLava()))
|
||||
|
||||
|
||||
@@ -272,6 +272,8 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val PC = blockItem("pc", CobblemonBlocks.PC)
|
||||
@JvmField
|
||||
val STADIUM = blockItem("stadium_terminal", CobblemonBlocks.STADIUM)
|
||||
@JvmField
|
||||
val PASTURE = blockItem("pasture", CobblemonBlocks.PASTURE)
|
||||
@JvmField
|
||||
val DISPLAY_CASE = blockItem("display_case", CobblemonBlocks.DISPLAY_CASE)
|
||||
|
||||
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.events.pokemon
|
||||
import com.cobblemon.mod.common.api.events.Cancelable
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.EvSource
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stat
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
|
||||
/**
|
||||
* Fired during EV mutation.
|
||||
@@ -25,6 +26,11 @@ interface EvGainedEvent {
|
||||
*/
|
||||
val stat: Stat
|
||||
|
||||
/**
|
||||
* @see EvSource.pokemon
|
||||
*/
|
||||
val pokemon: Pokemon get() = this.source.pokemon
|
||||
|
||||
/**
|
||||
* The [EvSource] that fired this event.
|
||||
*/
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.pokeball.catching.calculators
|
||||
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.CaptureContext
|
||||
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.pokedex
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
|
||||
/**
|
||||
* An influencer on capture results based on Pokedex data.
|
||||
*/
|
||||
interface PokedexStatusCaptureInfluencer {
|
||||
|
||||
/**
|
||||
* Attempts to influence the [context] of a capture.
|
||||
*
|
||||
* @param thrower The [LivingEntity] that threw the Pokeball
|
||||
* @param target The [PokemonEntity] attempting to capture.
|
||||
* @param context The calculated [CaptureContext].
|
||||
* @return The final [CaptureContext].
|
||||
*/
|
||||
fun influence(thrower: LivingEntity, target: PokemonEntity, context: CaptureContext): CaptureContext {
|
||||
if (!context.isSuccessfulCapture) {
|
||||
return context
|
||||
}
|
||||
val player = thrower as? ServerPlayer ?: return context
|
||||
val pokedex = player.pokedex()
|
||||
val speciesId = target.pokemon.species.resourceIdentifier
|
||||
val formName = target.pokemon.form.name
|
||||
val hasCaughtStatus = pokedex.getOrCreateSpeciesRecord(speciesId)
|
||||
.getOrCreateFormRecord(formName)
|
||||
.knowledge == PokedexEntryProgress.CAUGHT
|
||||
// In modern games when the catch is a success and the Pokémon has already been registered to the Pokédex
|
||||
// the catch becomes a critical capture and only requires one shake
|
||||
return if (hasCaughtStatus) {
|
||||
CaptureContext(
|
||||
numberOfShakes = 1,
|
||||
isSuccessfulCapture = true,
|
||||
isCriticalCapture = true
|
||||
)
|
||||
} else context
|
||||
}
|
||||
|
||||
}
|
||||
+7
-4
@@ -167,10 +167,10 @@ open class PlayerPartyStore(
|
||||
}
|
||||
|
||||
// Shoulder validation code
|
||||
if (player.shoulderEntityLeft.isPokemonEntity() && !validateShoulder(player.shoulderEntityLeft, true)) {
|
||||
if (player.shoulderEntityLeft.isPokemonEntity() && !validateShoulder(player, true)) {
|
||||
player.respawnEntityOnShoulder(player.shoulderEntityLeft)
|
||||
}
|
||||
if (player.shoulderEntityRight.isPokemonEntity() && !validateShoulder(player.shoulderEntityRight, false)) {
|
||||
if (player.shoulderEntityRight.isPokemonEntity() && !validateShoulder(player, false)) {
|
||||
player.respawnEntityOnShoulder(player.shoulderEntityRight)
|
||||
}
|
||||
|
||||
@@ -182,11 +182,14 @@ open class PlayerPartyStore(
|
||||
}
|
||||
}
|
||||
|
||||
fun validateShoulder(shoulderEntity: CompoundTag, isLeft: Boolean): Boolean {
|
||||
private fun validateShoulder(player: ServerPlayer, isLeft: Boolean): Boolean {
|
||||
val shoulderEntity = if(isLeft) player.shoulderEntityLeft else player.shoulderEntityRight
|
||||
val pokemon = find { it.uuid == shoulderEntity.getCompound("Pokemon").getUUID(DataKeys.POKEMON_UUID) }
|
||||
if (pokemon == null || (pokemon.state as? ShoulderedState)?.isLeftShoulder != isLeft) {
|
||||
// No longer valid if (in order): not in party, not the correct shoulder, no longer shoulder mountable
|
||||
if (pokemon == null || (pokemon.state as? ShoulderedState)?.isLeftShoulder != isLeft || !pokemon.form.shoulderMountable) {
|
||||
return false
|
||||
}
|
||||
player.updateShoulderNbt(pokemon)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.block
|
||||
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.block.entity.StadiumBlockEntity
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.InteractionResult
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.block.BaseEntityBlock
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.HorizontalDirectionalBlock
|
||||
import net.minecraft.world.level.block.Mirror
|
||||
import net.minecraft.world.level.block.RenderShape
|
||||
import net.minecraft.world.level.block.Rotation
|
||||
import net.minecraft.world.level.block.entity.BlockEntity
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.phys.BlockHitResult
|
||||
|
||||
class StadiumBlock(properties: Properties): BaseEntityBlock(properties) {
|
||||
companion object {
|
||||
val CODEC = simpleCodec(::StadiumBlock)
|
||||
}
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any().setValue(HorizontalDirectionalBlock.FACING, Direction.NORTH))
|
||||
}
|
||||
|
||||
override fun codec() = CODEC
|
||||
|
||||
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity {
|
||||
return StadiumBlockEntity(pos, state)
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(blockPlaceContext: BlockPlaceContext): BlockState {
|
||||
return this.defaultBlockState().setValue(HorizontalDirectionalBlock.FACING, blockPlaceContext.horizontalDirection)
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(HorizontalDirectionalBlock.FACING)
|
||||
}
|
||||
|
||||
override fun rotate(blockState: BlockState, rotation: Rotation): BlockState {
|
||||
return blockState.setValue(
|
||||
HorizontalDirectionalBlock.FACING, rotation.rotate(blockState.getValue(
|
||||
HorizontalDirectionalBlock.FACING)))
|
||||
}
|
||||
|
||||
override fun mirror(blockState: BlockState, mirror: Mirror): BlockState {
|
||||
return blockState.rotate(mirror.getRotation(blockState.getValue(HorizontalDirectionalBlock.FACING)))
|
||||
}
|
||||
|
||||
override fun onRemove(state: BlockState, world: Level, pos: BlockPos, newState: BlockState, moved: Boolean) {
|
||||
if (!state.`is`(newState.block)) super.onRemove(state, world, pos, newState, moved)
|
||||
}
|
||||
|
||||
//TODO: override getTicker
|
||||
|
||||
override fun getRenderShape(state: BlockState) = RenderShape.MODEL
|
||||
|
||||
override fun useWithoutItem(
|
||||
blockState: BlockState,
|
||||
world: Level,
|
||||
blockPos: BlockPos,
|
||||
player: Player,
|
||||
blockHitResult: BlockHitResult
|
||||
): InteractionResult {
|
||||
if (player !is ServerPlayer) return InteractionResult.SUCCESS
|
||||
|
||||
val blockEntity = world.getBlockEntity(blockPos)
|
||||
if (blockEntity !is StadiumBlockEntity) return InteractionResult.SUCCESS
|
||||
|
||||
// Toggle isInUse state
|
||||
blockEntity.isInUse = !blockEntity.isInUse
|
||||
|
||||
// debug
|
||||
val activateMessage = if (blockEntity.isInUse) "Stadium Activated!" else "Stadium Deactivated!"
|
||||
player.sendSystemMessage(text(activateMessage).red())
|
||||
|
||||
if (blockEntity.isInUse) {
|
||||
// Generate flight path when activated
|
||||
blockEntity.generateFlightPath(world, radiusX = 5.0, radiusZ = 3.0, height = 10.0, steps = 20)
|
||||
|
||||
} else {
|
||||
// Clear path when deactivated
|
||||
blockEntity.pathPoints.clear()
|
||||
}
|
||||
|
||||
val message = "Stadium Pos: " + blockEntity.blockPos +
|
||||
"\nPlayer 1 Pos: " + blockEntity.getPlayer1Pos() +
|
||||
"\nPoke 1 Pos: " + blockEntity.getPoke1Pos() +
|
||||
"\nPlayer 2 Pos: " + blockEntity.getPlayer2Pos() +
|
||||
"\nPoke 2 Pos: " + blockEntity.getPoke2Pos()
|
||||
player.sendSystemMessage(text(message).red())
|
||||
|
||||
blockEntity.setChanged()
|
||||
world.sendBlockUpdated(blockPos, blockState, blockState, Block.UPDATE_CLIENTS)
|
||||
|
||||
return InteractionResult.SUCCESS
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.cobblemon.mod.common.block.entity
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonBlockEntities
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.drone.DroneState
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.nbt.ListTag
|
||||
import net.minecraft.network.protocol.Packet
|
||||
import net.minecraft.network.protocol.game.ClientGamePacketListener
|
||||
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerLookAtPacket
|
||||
import net.minecraft.network.protocol.game.ClientboundSetCarriedItemPacket
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.block.entity.BlockEntity
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class StadiumBlockEntity(
|
||||
pos: BlockPos,
|
||||
blockState: BlockState
|
||||
): BlockEntity(
|
||||
CobblemonBlockEntities.STADIUM,
|
||||
pos,
|
||||
blockState
|
||||
) {
|
||||
var isInUse = false
|
||||
|
||||
val droneState = DroneState()
|
||||
|
||||
val pathPoints: MutableList<Vec3> = mutableListOf()
|
||||
|
||||
fun generateFlightPath(world: Level, radiusX: Double, radiusZ: Double, height: Double, steps: Int) {
|
||||
pathPoints.clear()
|
||||
|
||||
for (i in 0 until steps) {
|
||||
val angle = (i.toDouble() / steps) * Math.PI * 2
|
||||
val xOffset = radiusX * Math.cos(angle)
|
||||
val zOffset = radiusZ * Math.sin(angle)
|
||||
|
||||
val basePos = Vec3(blockPos.x + xOffset, blockPos.y + height, blockPos.z + zOffset)
|
||||
val adjustedPos = getValidPoint(world, basePos)
|
||||
|
||||
pathPoints.add(adjustedPos)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getValidPoint(world: Level, start: Vec3): Vec3 {
|
||||
val maxHeight = 40 // Max height for drone path
|
||||
val stepHeight = 1.0 // Step size for raycast
|
||||
|
||||
var adjustedHeight = start.y
|
||||
for (h in 0..maxHeight step stepHeight.toInt()) {
|
||||
val checkPos = BlockPos(start.x.toInt(), start.y.toInt() - h, start.z.toInt()) // Convert to BlockPos
|
||||
if (!world.getBlockState(checkPos).isAir) { // Check if block is solid
|
||||
return Vec3(start.x, start.y - h - 1, start.z) // Adjust position
|
||||
}
|
||||
}
|
||||
return start // No obstruction found
|
||||
}
|
||||
|
||||
|
||||
fun getPlayer1Pos(): BlockPos {
|
||||
return blockPos.north(10)
|
||||
}
|
||||
fun getPlayer2Pos(): BlockPos {
|
||||
return blockPos.south(10)
|
||||
}
|
||||
fun getPoke1Pos(): BlockPos {
|
||||
return blockPos.north(5)
|
||||
}
|
||||
fun getPoke2Pos(): BlockPos {
|
||||
return blockPos.south(5)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Saves data to NBT.
|
||||
*/
|
||||
override fun saveAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
|
||||
super.saveAdditional(tag, registries)
|
||||
|
||||
// Save the isInUse state
|
||||
tag.putBoolean("IsInUse", isInUse)
|
||||
|
||||
// Save path points
|
||||
val pathTag = ListTag()
|
||||
for (point in pathPoints) {
|
||||
val pointTag = CompoundTag()
|
||||
pointTag.putDouble("x", point.x)
|
||||
pointTag.putDouble("y", point.y)
|
||||
pointTag.putDouble("z", point.z)
|
||||
pathTag.add(pointTag)
|
||||
}
|
||||
tag.put("PathPoints", pathTag)
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads data from NBT.
|
||||
*/
|
||||
override fun loadAdditional(tag: CompoundTag, registries: HolderLookup.Provider) {
|
||||
super.loadAdditional(tag, registries)
|
||||
|
||||
// Load the isInUse state
|
||||
isInUse = tag.getBoolean("IsInUse")
|
||||
|
||||
// Load path points
|
||||
pathPoints.clear()
|
||||
val pathTag = tag.getList("PathPoints", 10) // 10 is the ID for CompoundTag
|
||||
for (i in 0 until pathTag.size) {
|
||||
val pointTag = pathTag.getCompound(i)
|
||||
val point = Vec3(pointTag.getDouble("x"), pointTag.getDouble("y"), pointTag.getDouble("z"))
|
||||
pathPoints.add(point)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getUpdatePacket(): Packet<ClientGamePacketListener>? {
|
||||
//println("getUpdatePacket called for NestBlockEntity at $blockPos with egg: $eggComponent")
|
||||
return ClientboundBlockEntityDataPacket.create(this)
|
||||
}
|
||||
|
||||
override fun getUpdateTag(registryLookup: HolderLookup.Provider): CompoundTag {
|
||||
val tag = saveWithoutMetadata(registryLookup)
|
||||
//println("getUpdateTag called for NestBlockEntity at $blockPos with tag: $tag")
|
||||
return tag
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -25,13 +25,7 @@ import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
|
||||
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
|
||||
import com.cobblemon.mod.common.client.render.ClientPlayerIcon
|
||||
import com.cobblemon.mod.common.client.render.DeferredRenderer
|
||||
import com.cobblemon.mod.common.client.render.block.BerryBlockRenderer
|
||||
import com.cobblemon.mod.common.client.render.block.DisplayCaseRenderer
|
||||
import com.cobblemon.mod.common.client.render.block.FossilAnalyzerRenderer
|
||||
import com.cobblemon.mod.common.client.render.block.GildedChestBlockRenderer
|
||||
import com.cobblemon.mod.common.client.render.block.HealingMachineRenderer
|
||||
import com.cobblemon.mod.common.client.render.block.LecternBlockEntityRenderer
|
||||
import com.cobblemon.mod.common.client.render.block.RestorationTankRenderer
|
||||
import com.cobblemon.mod.common.client.render.block.*
|
||||
import com.cobblemon.mod.common.client.render.boat.CobblemonBoatRenderer
|
||||
import com.cobblemon.mod.common.client.render.entity.PokeBobberEntityRenderer
|
||||
import com.cobblemon.mod.common.client.render.generic.GenericBedrockRenderer
|
||||
@@ -256,7 +250,8 @@ object CobblemonClient {
|
||||
CobblemonBlocks.SKY_TUMBLESTONE_CLUSTER,
|
||||
CobblemonBlocks.GIMMIGHOUL_CHEST,
|
||||
CobblemonBlocks.DISPLAY_CASE,
|
||||
CobblemonBlocks.LECTERN
|
||||
CobblemonBlocks.LECTERN,
|
||||
CobblemonBlocks.STADIUM
|
||||
)
|
||||
|
||||
this.createBoatModelLayers()
|
||||
@@ -290,6 +285,7 @@ object CobblemonClient {
|
||||
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.GILDED_CHEST, ::GildedChestBlockRenderer)
|
||||
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.DISPLAY_CASE, ::DisplayCaseRenderer)
|
||||
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.LECTERN, ::LecternBlockEntityRenderer)
|
||||
this.implementation.registerBlockEntityRenderer(CobblemonBlockEntities.STADIUM, ::StadiumBlockRenderer)
|
||||
}
|
||||
|
||||
private fun registerEntityRenderers() {
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.client.render.models.blockbench.repository.Rende
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.pokemon.RenderablePokemon
|
||||
import com.cobblemon.mod.common.util.math.toEulerXYZDegrees
|
||||
import com.cobblemon.mod.common.util.toHex
|
||||
import com.mojang.blaze3d.platform.Lighting
|
||||
import com.mojang.blaze3d.systems.RenderSystem
|
||||
@@ -25,8 +26,10 @@ import net.minecraft.client.renderer.LightTexture
|
||||
import net.minecraft.client.renderer.RenderType
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.util.Mth
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
import kotlin.math.atan
|
||||
|
||||
fun drawProfilePokemon(
|
||||
renderablePokemon: RenderablePokemon,
|
||||
@@ -41,7 +44,9 @@ fun drawProfilePokemon(
|
||||
r: Float = 1F,
|
||||
g: Float = 1F,
|
||||
b: Float = 1F,
|
||||
a: Float = 1F
|
||||
a: Float = 1F,
|
||||
headYaw: Float = 0f,
|
||||
headPitch: Float = 0f,
|
||||
) = drawProfilePokemon(
|
||||
species = renderablePokemon.species.resourceIdentifier,
|
||||
matrixStack = matrixStack,
|
||||
@@ -56,6 +61,8 @@ fun drawProfilePokemon(
|
||||
g = g,
|
||||
b = b,
|
||||
a = a,
|
||||
headYaw = headYaw,
|
||||
headPitch = headPitch,
|
||||
)
|
||||
|
||||
fun drawProfilePokemon(
|
||||
@@ -71,7 +78,9 @@ fun drawProfilePokemon(
|
||||
r: Float = 1F,
|
||||
g: Float = 1F,
|
||||
b: Float = 1F,
|
||||
a: Float = 1F
|
||||
a: Float = 1F,
|
||||
headYaw: Float = 0f,
|
||||
headPitch: Float = 0f,
|
||||
) {
|
||||
RenderSystem.applyModelViewMatrix()
|
||||
matrixStack.scale(scale, scale, -scale)
|
||||
@@ -100,7 +109,7 @@ fun drawProfilePokemon(
|
||||
|
||||
state.setPoseToFirstSuitable(poseType)
|
||||
state.updatePartialTicks(partialTicks)
|
||||
model.applyAnimations(null, state, 0F, 0F, 0F, 0F, 0F)
|
||||
|
||||
if (applyProfileTransform) {
|
||||
matrixStack.translate(
|
||||
model.profileTranslation.x,
|
||||
@@ -114,6 +123,9 @@ fun drawProfilePokemon(
|
||||
}
|
||||
}
|
||||
matrixStack.mulPose(rotation)
|
||||
|
||||
model.applyAnimations(null, state, 0F, 0F, 0F, headYaw, headPitch)
|
||||
|
||||
Lighting.setupForEntityInInventory()
|
||||
val entityRenderDispatcher = Minecraft.getInstance().entityRenderDispatcher
|
||||
rotation.conjugate()
|
||||
@@ -140,3 +152,29 @@ fun drawProfilePokemon(
|
||||
}
|
||||
}
|
||||
|
||||
const val HEAD_YAW_FACTOR = 40f
|
||||
const val HEAD_PITCH_FACTOR = 20f
|
||||
const val SMOOTHING_FACTOR = 0.05f
|
||||
|
||||
fun calculateHeadYawAndPitch(
|
||||
xCoordinate: Float,
|
||||
yCoordinate: Float,
|
||||
rotation: Quaternionf,
|
||||
mouseX: Int,
|
||||
mouseY: Int,
|
||||
currentYaw: Float,
|
||||
currentPitch: Float,
|
||||
resetToCenter: Boolean,
|
||||
): Pair<Float, Float> {
|
||||
val horizontalAngle = atan(((xCoordinate - mouseX) / 40.0f).toDouble()).toFloat()
|
||||
val verticalAngle = atan(((yCoordinate - mouseY) / 40.0f).toDouble()).toFloat()
|
||||
|
||||
val eulerXYZRotationDegrees = rotation.toEulerXYZDegrees()
|
||||
val targetYaw = if (!resetToCenter) horizontalAngle * HEAD_YAW_FACTOR - eulerXYZRotationDegrees.y else 0f
|
||||
val targetPitch = if (!resetToCenter) -verticalAngle * HEAD_PITCH_FACTOR - eulerXYZRotationDegrees.x else 0f
|
||||
|
||||
val newYaw = Mth.rotLerp(SMOOTHING_FACTOR, currentYaw, targetYaw)
|
||||
val newPitch = Mth.rotLerp(SMOOTHING_FACTOR, currentPitch, targetPitch)
|
||||
|
||||
return Pair(newYaw, newPitch)
|
||||
}
|
||||
+3
-3
@@ -161,7 +161,7 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
|
||||
pX = x + 119, pY = height / 2 + 84,
|
||||
pWidth = StarterRoundabout.MODEL_WIDTH, pHeight = StarterRoundabout.MODEL_HEIGHT,
|
||||
pokemon = currentPokemon,
|
||||
rotationVector = this.modelWidget.rotVec
|
||||
rotationVector = this.modelWidget.rotationVector
|
||||
)
|
||||
|
||||
starterRoundaboutLeft = StarterRoundabout(
|
||||
@@ -169,7 +169,7 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
|
||||
pWidth = StarterRoundabout.MODEL_WIDTH, pHeight = StarterRoundabout.MODEL_HEIGHT,
|
||||
pokemon = currentCategory.pokemon[leftOfCurrentSelection()],
|
||||
clickAction = { _, _ -> this.left() },
|
||||
rotationVector = this.modelWidget.rotVec
|
||||
rotationVector = this.modelWidget.rotationVector
|
||||
)
|
||||
|
||||
starterRoundaboutRight = StarterRoundabout(
|
||||
@@ -177,7 +177,7 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
|
||||
pWidth = StarterRoundabout.MODEL_WIDTH, pHeight = StarterRoundabout.MODEL_HEIGHT,
|
||||
pokemon = currentCategory.pokemon[rightOfCurrentSelection()],
|
||||
clickAction = { _, _ -> this.right() },
|
||||
rotationVector = this.modelWidget.rotVec
|
||||
rotationVector = this.modelWidget.rotationVector
|
||||
)
|
||||
|
||||
addRenderableWidget(starterRoundaboutLeft)
|
||||
|
||||
@@ -235,7 +235,8 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
|
||||
pokemon = selectedPokemon.asRenderablePokemon(),
|
||||
baseScale = 2F,
|
||||
rotationY = 325F,
|
||||
offsetY = -10.0
|
||||
offsetY = -10.0,
|
||||
shouldFollowCursor = true,
|
||||
)
|
||||
addRenderableOnly(this.modelWidget)
|
||||
}
|
||||
@@ -274,9 +275,11 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
|
||||
listenToMoveSet()
|
||||
displayMainScreen(mainScreenIndex)
|
||||
children().find { it is EvolutionSelectScreen }?.let(this::removeWidget)
|
||||
if (this::modelWidget.isInitialized) {
|
||||
this.modelWidget.pokemon = selectedPokemon.asRenderablePokemon()
|
||||
|
||||
if (::modelWidget.isInitialized) {
|
||||
modelWidget.pokemon = selectedPokemon.asRenderablePokemon()
|
||||
}
|
||||
|
||||
if (this::nicknameEntryWidget.isInitialized) {
|
||||
this.nicknameEntryWidget.setSelectedPokemon(selectedPokemon)
|
||||
}
|
||||
@@ -592,12 +595,6 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
|
||||
return children().any { it.mouseScrolled(mouseX, mouseY, amount, verticalAmount) }
|
||||
}
|
||||
|
||||
/*
|
||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||
return children().any { it.mouseClicked(mouseX, mouseY, button) }
|
||||
}
|
||||
*/
|
||||
|
||||
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
|
||||
if (sideScreenIndex == MOVE_SWAP || sideScreenIndex == EVOLVE) sideScreen.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
|
||||
if (mainScreenIndex == MOVES) mainScreen.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
|
||||
|
||||
+52
-8
@@ -8,7 +8,9 @@
|
||||
|
||||
package com.cobblemon.mod.common.client.gui.summary.widgets
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
|
||||
import com.cobblemon.mod.common.client.gui.calculateHeadYawAndPitch
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
|
||||
import com.cobblemon.mod.common.pokemon.RenderablePokemon
|
||||
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
|
||||
@@ -20,29 +22,39 @@ import org.joml.Vector3f
|
||||
class ModelWidget(
|
||||
pX: Int, pY: Int,
|
||||
pWidth: Int, pHeight: Int,
|
||||
var pokemon: RenderablePokemon,
|
||||
pokemon: RenderablePokemon,
|
||||
val baseScale: Float = 2.7F,
|
||||
var rotationY: Float = 35F,
|
||||
var offsetY: Double = 0.0,
|
||||
val playCryOnClick: Boolean = false,
|
||||
val shouldFollowCursor: Boolean = false,
|
||||
): SoundlessWidget(pX, pY, pWidth, pHeight, Component.literal("Summary - ModelWidget")) {
|
||||
|
||||
companion object {
|
||||
var render = true
|
||||
const val MAX_SECONDS_LOOKING = 6L
|
||||
}
|
||||
|
||||
var pokemon: RenderablePokemon = pokemon
|
||||
set (value) {
|
||||
field = value
|
||||
currentYawAndPitch = Pair(0f, 0f)
|
||||
}
|
||||
|
||||
var state = FloatingState()
|
||||
val rotVec = Vector3f(13F, rotationY, 0F)
|
||||
var lookStartTime: Long? = null
|
||||
var currentYawAndPitch: Pair<Float, Float> = Pair(0f, 0f)
|
||||
val rotationVector = Vector3f(13F, rotationY, 0F)
|
||||
|
||||
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, partialTicks: Float) {
|
||||
if (!render) {
|
||||
return
|
||||
}
|
||||
isHovered = pMouseX >= x && pMouseY >= y && pMouseX < x + width && pMouseY < y + height
|
||||
renderPKM(context, partialTicks)
|
||||
renderPKM(context, partialTicks, pMouseX, pMouseY)
|
||||
}
|
||||
|
||||
private fun renderPKM(context: GuiGraphics, partialTicks: Float) {
|
||||
private fun renderPKM(context: GuiGraphics, partialTicks: Float, mouseX: Int, mouseY: Int) {
|
||||
val matrices = context.pose()
|
||||
matrices.pushPose()
|
||||
|
||||
@@ -50,19 +62,48 @@ class ModelWidget(
|
||||
x,
|
||||
y,
|
||||
x + width,
|
||||
y + height
|
||||
y + height
|
||||
)
|
||||
|
||||
matrices.translate(x + width * 0.5, y.toDouble() + offsetY, 0.0)
|
||||
matrices.scale(baseScale, baseScale, baseScale)
|
||||
matrices.pushPose()
|
||||
|
||||
if (isHovered) {
|
||||
lookStartTime = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
val timeLooking = lookStartTime?.let { (System.currentTimeMillis() - it) / 1000 } ?: MAX_SECONDS_LOOKING
|
||||
val lookedTooMuch = timeLooking >= MAX_SECONDS_LOOKING
|
||||
val resetToCenter = (!isHovered && lookedTooMuch) || !shouldFollowCursor || !Cobblemon.config.summaryPokemonFollowCursor
|
||||
|
||||
if (resetToCenter) {
|
||||
lookStartTime = null
|
||||
}
|
||||
|
||||
val rotation = Quaternionf().fromEulerXYZDegrees(rotationVector)
|
||||
|
||||
if (shouldFollowCursor) {
|
||||
currentYawAndPitch = calculateHeadYawAndPitch(
|
||||
x + width / 2f,
|
||||
y + height / 2f,
|
||||
rotation,
|
||||
mouseX,
|
||||
mouseY,
|
||||
currentYawAndPitch.first,
|
||||
currentYawAndPitch.second,
|
||||
resetToCenter
|
||||
)
|
||||
}
|
||||
|
||||
drawProfilePokemon(
|
||||
renderablePokemon = pokemon,
|
||||
matrixStack = matrices,
|
||||
rotation = Quaternionf().fromEulerXYZDegrees(rotVec),
|
||||
rotation = rotation,
|
||||
state = state,
|
||||
partialTicks = partialTicks
|
||||
partialTicks = partialTicks,
|
||||
headYaw = currentYawAndPitch.first,
|
||||
headPitch = currentYawAndPitch.second,
|
||||
)
|
||||
|
||||
matrices.popPose()
|
||||
@@ -72,7 +113,10 @@ class ModelWidget(
|
||||
}
|
||||
|
||||
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
|
||||
if (this.isHovered) playCry()
|
||||
if (this.isHovered) {
|
||||
playCry()
|
||||
}
|
||||
|
||||
return super.mouseClicked(pMouseX, pMouseY, pButton)
|
||||
}
|
||||
|
||||
|
||||
-3
@@ -9,14 +9,11 @@
|
||||
package com.cobblemon.mod.common.client.net.pokemon.update
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.client.render.layer.PokemonOnShoulderRenderer
|
||||
import com.cobblemon.mod.common.net.messages.client.PokemonUpdatePacket
|
||||
import net.minecraft.client.Minecraft
|
||||
|
||||
class PokemonUpdatePacketHandler<T : PokemonUpdatePacket<T>> : ClientNetworkPacketHandler<T> {
|
||||
override fun handle(packet: T, client: Minecraft) {
|
||||
packet.applyToPokemon()
|
||||
val player = client.player ?: return
|
||||
PokemonOnShoulderRenderer.clearCache(player)
|
||||
}
|
||||
}
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* 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.render.block
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import com.cobblemon.mod.common.block.entity.StadiumBlockEntity
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.drone.DroneModel
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.drone.DroneState
|
||||
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.util.cobblemonResource
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import com.mojang.math.Axis
|
||||
import net.minecraft.client.renderer.MultiBufferSource
|
||||
import net.minecraft.client.renderer.RenderType
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.level.block.HorizontalDirectionalBlock
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
|
||||
class StadiumBlockRenderer(ctx: BlockEntityRendererProvider.Context) : BlockEntityRenderer<StadiumBlockEntity> {
|
||||
val context = RenderContext().also {
|
||||
it.put(RenderContext.DO_QUIRKS, false)
|
||||
it.put(RenderContext.RENDER_STATE, RenderContext.RenderState.BLOCK)
|
||||
}
|
||||
|
||||
private var pathIndex = 0
|
||||
private var animationTime = 0.0
|
||||
private val droneSpeed = 0.10
|
||||
|
||||
override fun render(
|
||||
blockEntity: StadiumBlockEntity,
|
||||
tickDelta: Float,
|
||||
poseStack: PoseStack,
|
||||
multiBufferSource: MultiBufferSource,
|
||||
light: Int,
|
||||
overlay: Int
|
||||
) {
|
||||
val blockState = blockEntity.blockState
|
||||
val yRot = blockState.getValue(HorizontalDirectionalBlock.FACING).toYRot()
|
||||
|
||||
val isClientSide = blockEntity.level?.isClientSide == true
|
||||
if (!isClientSide) return
|
||||
|
||||
val stadiumBlockState = blockEntity.level?.getBlockState(blockEntity.blockPos) ?: return
|
||||
if (stadiumBlockState.block != CobblemonBlocks.STADIUM) {
|
||||
return
|
||||
}
|
||||
|
||||
val droneState = blockEntity.droneState
|
||||
|
||||
|
||||
poseStack.pushPose()
|
||||
if (blockEntity.isInUse) {
|
||||
if (blockEntity.pathPoints.isEmpty()) {
|
||||
|
||||
poseStack.popPose()
|
||||
return
|
||||
}
|
||||
|
||||
spawnDebugParticles(blockEntity) // debug particles
|
||||
|
||||
// Center the drone at the block's center
|
||||
poseStack.translate(0.5, -3.0, 0.5)
|
||||
//poseStack.mulPose(Axis.XP.rotationDegrees(180F))
|
||||
//poseStack.mulPose(Axis.YP.rotationDegrees(180F))
|
||||
|
||||
val points = blockEntity.pathPoints
|
||||
val nextIndex = (pathIndex + 1) % points.size
|
||||
|
||||
val currentPoint = points[pathIndex]
|
||||
val nextPoint = points[nextIndex]
|
||||
|
||||
animationTime += tickDelta * droneSpeed
|
||||
if (animationTime >= 1.0) {
|
||||
animationTime = 0.0
|
||||
pathIndex = nextIndex
|
||||
}
|
||||
|
||||
val interpolatedPos = currentPoint.lerp(nextPoint, animationTime.toDouble())
|
||||
|
||||
println("DEBUG: Drone moving between points:")
|
||||
println(" - Current Point: ${currentPoint}")
|
||||
println(" - Next Point: ${nextPoint}")
|
||||
println(" - Interpolated Position: $interpolatedPos")
|
||||
|
||||
poseStack.translate(interpolatedPos.x - blockEntity.blockPos.x,
|
||||
interpolatedPos.y - blockEntity.blockPos.y,
|
||||
interpolatedPos.z - blockEntity.blockPos.z)
|
||||
|
||||
println("DEBUG: Drone position interpolated to: $interpolatedPos")
|
||||
|
||||
poseStack.mulPose(Axis.YP.rotationDegrees(-yRot))
|
||||
poseStack.mulPose(Axis.ZP.rotationDegrees(180F)) // flip the drone right side up
|
||||
|
||||
renderDrone(droneState, "sleep", tickDelta, poseStack, 1.0F, multiBufferSource, light)
|
||||
} else {
|
||||
val yRot = blockState.getValue(HorizontalDirectionalBlock.FACING).toYRot()
|
||||
|
||||
val poseEntry = poseStack.last()
|
||||
val matrix = poseEntry.pose()
|
||||
|
||||
poseStack.translate(0.5, 0.0, 0.5)
|
||||
poseStack.mulPose(Axis.YP.rotationDegrees(yRot))
|
||||
|
||||
val posX = matrix.m03()
|
||||
val posY = matrix.m13()
|
||||
val posZ = matrix.m23()
|
||||
|
||||
println("DEBUG: Drone is NOT in use. Rendering default position at: x=$posX, y=$posY, z=$posZ")
|
||||
|
||||
poseStack.mulPose(Axis.ZP.rotationDegrees(180F))
|
||||
renderDrone(droneState, "sleep", tickDelta, poseStack, 1.0F, multiBufferSource, light)
|
||||
}
|
||||
|
||||
poseStack.popPose()
|
||||
}
|
||||
|
||||
fun renderDrone(
|
||||
state: DroneState,
|
||||
pose: String,
|
||||
tickDelta: Float,
|
||||
poseStack: PoseStack,
|
||||
scale: Float = 1F,
|
||||
multiBufferSource: MultiBufferSource,
|
||||
light: Int,
|
||||
) {
|
||||
state.updatePartialTicks(tickDelta)
|
||||
|
||||
val droneResource: ResourceLocation = cobblemonResource("rotom_drone")
|
||||
|
||||
val model = VaryingModelRepository.getPoser(droneResource, state) as DroneModel
|
||||
val texture = VaryingModelRepository.getTexture(droneResource, state)
|
||||
|
||||
val buffer = multiBufferSource.getBuffer(RenderType.entityCutout(texture))
|
||||
model.bufferProvider = multiBufferSource
|
||||
|
||||
state.currentModel = model
|
||||
state.setPose(pose)
|
||||
|
||||
model.context = context
|
||||
context.put(RenderContext.TEXTURE, texture)
|
||||
context.put(RenderContext.RENDER_STATE, RenderContext.RenderState.BLOCK)
|
||||
context.put(RenderContext.POSABLE_STATE, state)
|
||||
|
||||
model.applyAnimations(
|
||||
entity = null,
|
||||
state = state,
|
||||
headYaw = 0F,
|
||||
headPitch = 0F,
|
||||
limbSwing = 0F,
|
||||
limbSwingAmount = 0F,
|
||||
ageInTicks = state.animationSeconds * 20
|
||||
)
|
||||
|
||||
poseStack.scale(scale, scale, scale)
|
||||
|
||||
// TODO: Make drone lighting dynamic (simply using the light arg makes the drone always appear dark)
|
||||
model.withLayerContext(multiBufferSource, state, VaryingModelRepository.getLayers(droneResource, state)) {
|
||||
model.render(context, poseStack, buffer, 200, OverlayTexture.NO_OVERLAY, -0x1)
|
||||
}
|
||||
model.setDefault()
|
||||
}
|
||||
|
||||
private fun spawnDebugParticles(blockEntity: StadiumBlockEntity) {
|
||||
val world = blockEntity.level ?: return
|
||||
if (!world.isClientSide) return // Only run on the client-side
|
||||
|
||||
for (point in blockEntity.pathPoints) {
|
||||
world.addParticle(
|
||||
net.minecraft.core.particles.ParticleTypes.FLAME, // Test particle (changeable)
|
||||
point.x, point.y, point.z, // Position of the particle
|
||||
0.0, 0.02, 0.0 // Slight upward motion
|
||||
)
|
||||
}
|
||||
|
||||
// Spawn a special particle at the next interpolation point
|
||||
if (blockEntity.pathPoints.isNotEmpty()) {
|
||||
val nextPoint = blockEntity.pathPoints[(pathIndex + 1) % blockEntity.pathPoints.size]
|
||||
world.addParticle(
|
||||
net.minecraft.core.particles.ParticleTypes.END_ROD, // Different effect for next point
|
||||
nextPoint.x, nextPoint.y, nextPoint.z,
|
||||
0.0, 0.05, 0.0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-23
@@ -87,24 +87,18 @@ class PokemonOnShoulderRenderer<T : Player>(renderLayerParent: RenderLayerParent
|
||||
) {
|
||||
val compoundTag = if (pLeftShoulder) livingEntity.shoulderEntityLeft else livingEntity.shoulderEntityRight
|
||||
if (compoundTag.isPokemonEntity()) {
|
||||
matrixStack.pushPose()
|
||||
livingEntity.level().profiler.push("shoulder_render_".plus(if(pLeftShoulder) "left" else "right"))
|
||||
val uuid = this.extractUuid(compoundTag)
|
||||
val cache = playerCache.getOrPut(livingEntity.uuid) { ShoulderCache() }
|
||||
var shoulderData: ShoulderData? = null
|
||||
if (pLeftShoulder && cache.lastKnownLeft?.uuid != uuid) {
|
||||
shoulderData = this.extractData(compoundTag, uuid)
|
||||
cache.lastKnownLeft = shoulderData
|
||||
}
|
||||
else if (!pLeftShoulder && cache.lastKnownRight?.uuid != uuid) {
|
||||
shoulderData = this.extractData(compoundTag, uuid)
|
||||
cache.lastKnownRight = shoulderData
|
||||
}
|
||||
val shoulderData = this.extractData(compoundTag, uuid)
|
||||
if (pLeftShoulder) cache.lastKnownLeft = shoulderData else cache.lastKnownRight = shoulderData
|
||||
|
||||
if (shoulderData == null){
|
||||
// Could be null
|
||||
shoulderData = (if (pLeftShoulder) cache.lastKnownLeft else cache.lastKnownRight) ?: return
|
||||
// Nothing to do
|
||||
return
|
||||
}
|
||||
|
||||
matrixStack.pushPose()
|
||||
var state = FloatingState()
|
||||
state.currentAspects = shoulderData.aspects
|
||||
val model = VaryingModelRepository.getPoser(shoulderData.species.resourceIdentifier, state)
|
||||
@@ -129,6 +123,7 @@ class PokemonOnShoulderRenderer<T : Player>(renderLayerParent: RenderLayerParent
|
||||
)
|
||||
|
||||
matrixStack.scale(scale, scale, scale)
|
||||
matrixStack.translate(0f, 1.5f, 0f)
|
||||
|
||||
state = if (pLeftShoulder && shoulderData != lastRenderedLeft) {
|
||||
leftState = configureState(state, model, true)
|
||||
@@ -162,6 +157,11 @@ class PokemonOnShoulderRenderer<T : Player>(renderLayerParent: RenderLayerParent
|
||||
}
|
||||
model.setDefault()
|
||||
matrixStack.popPose()
|
||||
livingEntity.level().profiler.pop()
|
||||
}
|
||||
else {
|
||||
val entry = playerCache.getOrPut(livingEntity.uuid) { ShoulderCache() }
|
||||
if (pLeftShoulder) entry.lastKnownLeft = null else entry.lastKnownRight = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,16 +217,5 @@ class PokemonOnShoulderRenderer<T : Player>(renderLayerParent: RenderLayerParent
|
||||
return Pair(cache.lastKnownLeft, cache.lastKnownRight)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cache for the respective player.
|
||||
* This is required to be done whenever a Pokémon changes in any way (form, evolution, etc.)
|
||||
*
|
||||
* @param player The player whose cache to clear
|
||||
*/
|
||||
@JvmStatic
|
||||
fun clearCache(player: Player) {
|
||||
playerCache.remove(player.uuid)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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.render.models.blockbench.drone
|
||||
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.animation.PoseAnimation
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pose.Bone
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.quirk.ModelQuirk
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import net.minecraft.client.model.geom.ModelPart
|
||||
|
||||
/**
|
||||
* A model for rendering the Rotom drone used by the stadium block.
|
||||
*
|
||||
* @author Flarkinater
|
||||
* @since February 14th, 2025
|
||||
*/
|
||||
class DroneModel(root: Bone) : PosableModel(root) {
|
||||
@Transient
|
||||
@SerializedName("So this name can be literally anything, huh?")
|
||||
override val rootPart = (root as ModelPart).registerChildWithAllChildren(root.children.entries.first().key)
|
||||
var droneAnimations: Array<PoseAnimation> = emptyArray()
|
||||
var droneQuirks: Array<ModelQuirk<*>> = emptyArray()
|
||||
|
||||
override fun registerPoses() {
|
||||
registerPose(
|
||||
poseType = PoseType.SLEEP,
|
||||
animations = droneAnimations,
|
||||
quirks = droneQuirks
|
||||
)
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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.render.models.blockbench.drone
|
||||
|
||||
import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
|
||||
|
||||
/**
|
||||
* State for a drone Rotom attached to a stadium block.
|
||||
*
|
||||
* @author Flarkinater
|
||||
* @since February 12th, 2025
|
||||
*/
|
||||
class DroneState() : PosableState() {
|
||||
init {
|
||||
setPose("sleep")
|
||||
age = 0
|
||||
currentPartialTicks = 0F
|
||||
}
|
||||
|
||||
override fun getEntity() = null
|
||||
|
||||
override fun updatePartialTicks(partialTicks: Float) {
|
||||
currentPartialTicks += partialTicks / 2
|
||||
}
|
||||
|
||||
override val schedulingTracker = SchedulingTracker()
|
||||
}
|
||||
+4
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.client.render.models.blockbench.*
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockAnimationRepository
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.blockentity.BlockEntityModel
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.fossil.FossilModel
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.drone.DroneModel
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pokeball.AncientPokeBallModel
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pokeball.BeastBallModel
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.pokeball.PokeBallModel
|
||||
@@ -79,12 +80,14 @@ object VaryingModelRepository {
|
||||
"poke_balls",
|
||||
"generic",
|
||||
"block_entities",
|
||||
"drone"
|
||||
)
|
||||
|
||||
val poserDirectories: List<Pair<String, Class<out PosableModel>>> = listOf(
|
||||
"bedrock/posers" to PosableModel::class.java,
|
||||
"bedrock/pokemon/posers" to PokemonPosableModel::class.java,
|
||||
"bedrock/fossils/posers" to FossilModel::class.java,
|
||||
"bedrock/drone/posers" to DroneModel::class.java,
|
||||
"bedrock/block_entities/posers" to BlockEntityModel::class.java,
|
||||
"bedrock/npcs/posers" to PosableModel::class.java,
|
||||
"bedrock/poke_balls/posers" to PosableModel::class.java,
|
||||
@@ -116,6 +119,7 @@ object VaryingModelRepository {
|
||||
.registerTypeAdapter(PosableModel::class.java, JsonModelAdapter(::PosableModel))
|
||||
.registerTypeAdapter(PokemonPosableModel::class.java, JsonModelAdapter(::PokemonPosableModel))
|
||||
.registerTypeAdapter(FossilModel::class.java, JsonModelAdapter(::FossilModel))
|
||||
.registerTypeAdapter(DroneModel::class.java, JsonModelAdapter(::DroneModel))
|
||||
.registerTypeAdapter(BlockEntityModel::class.java, JsonModelAdapter(::BlockEntityModel))
|
||||
.registerTypeAdapter(Pose::class.java, PoseAdapter { JsonModelAdapter.model!! })
|
||||
.registerTypeAdapter(ModelPartTransformation::class.java, ModelPartTransformationAdapter)
|
||||
|
||||
+13
-8
@@ -21,8 +21,9 @@ import kotlin.random.Random
|
||||
abstract class PokeBallPosableState : PosableState(), Schedulable {
|
||||
abstract val stateEmitter: SettableObservable<EmptyPokeBallEntity.CaptureState>
|
||||
abstract val shakeEmitter: Observable<Unit>
|
||||
private val group: String
|
||||
protected open val group: String
|
||||
get() = if (this.currentModel is AncientPokeBallModel) "ancient_poke_ball" else "poke_ball"
|
||||
protected open var lastKnownState: EmptyPokeBallEntity.CaptureState? = null
|
||||
|
||||
open fun initSubscriptions() {
|
||||
stateEmitter.subscribe { state ->
|
||||
@@ -44,9 +45,17 @@ abstract class PokeBallPosableState : PosableState(), Schedulable {
|
||||
}
|
||||
|
||||
EmptyPokeBallEntity.CaptureState.FALL -> {}
|
||||
EmptyPokeBallEntity.CaptureState.SHAKE -> {
|
||||
EmptyPokeBallEntity.CaptureState.CRITICAL -> {
|
||||
doLater {
|
||||
setActiveAnimations(currentModel!!.bedrockStateful(group, "bounce"))
|
||||
setActiveAnimations(currentModel!!.bedrockStateful(group, "critical"))
|
||||
}
|
||||
}
|
||||
EmptyPokeBallEntity.CaptureState.SHAKE -> {
|
||||
if (this.lastKnownState != EmptyPokeBallEntity.CaptureState.CRITICAL) {
|
||||
// Skip the bounce on a critical capture, the animation replaces this step
|
||||
doLater {
|
||||
setActiveAnimations(currentModel!!.bedrockStateful(group, "bounce"))
|
||||
}
|
||||
}
|
||||
shakeEmitter
|
||||
.pipe(Observable.emitWhile { stateEmitter.get() == EmptyPokeBallEntity.CaptureState.SHAKE })
|
||||
@@ -60,11 +69,6 @@ abstract class PokeBallPosableState : PosableState(), Schedulable {
|
||||
setActiveAnimations(currentModel!!.bedrockStateful(group, "capture"))
|
||||
}
|
||||
}
|
||||
EmptyPokeBallEntity.CaptureState.CAPTURED_CRITICAL -> {
|
||||
doLater {
|
||||
setActiveAnimations(currentModel!!.bedrockStateful(group, "critical"))
|
||||
}
|
||||
}
|
||||
EmptyPokeBallEntity.CaptureState.BROKEN_FREE -> {
|
||||
doLater {
|
||||
setActiveAnimations(currentModel!!.bedrockStateful(group, "break"))
|
||||
@@ -72,6 +76,7 @@ abstract class PokeBallPosableState : PosableState(), Schedulable {
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
this.lastKnownState = state
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,5 +18,6 @@ enum class Category(val lang: String) {
|
||||
Pokedex("pokedex"),
|
||||
Storage("storage"),
|
||||
Starter("starter"),
|
||||
Interface("interface"),
|
||||
Debug("debug"),
|
||||
}
|
||||
@@ -182,7 +182,7 @@ class CobblemonConfig {
|
||||
var displayNameForUnknownPokemon = false
|
||||
|
||||
@CobblemonConfigField(Category.Pokemon, lang = "display_entity_labels_when_crouching_only")
|
||||
var displayEntityLabelsWhenCrouchingOnly = true
|
||||
var displayEntityLabelsWhenCrouchingOnly = false
|
||||
|
||||
@CobblemonConfigField(Category.Spawning, lang = "shiny_rate")
|
||||
var shinyRate = 8192F
|
||||
@@ -252,6 +252,9 @@ class CobblemonConfig {
|
||||
@CobblemonConfigField(Category.Pokedex, lang = "hide_unimplemented_pokemon_in_the_pokedex")
|
||||
var hideUnimplementedPokemonInThePokedex = false
|
||||
|
||||
@CobblemonConfigField(Category.Interface, lang = "summary_pokemon_follow_cursor")
|
||||
var summaryPokemonFollowCursor = true
|
||||
|
||||
@CobblemonConfigField(Category.Debug, lang = "enable_debug_keys")
|
||||
var enableDebugKeys = false
|
||||
|
||||
|
||||
+26
-15
@@ -72,16 +72,15 @@ import net.minecraft.world.phys.EntityHitResult
|
||||
import net.minecraft.world.phys.HitResult
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import kotlin.compareTo
|
||||
|
||||
class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragModifier, Schedulable {
|
||||
enum class CaptureState {
|
||||
NOT,
|
||||
HIT,
|
||||
FALL,
|
||||
CRITICAL,
|
||||
SHAKE,
|
||||
CAPTURED,
|
||||
CAPTURED_CRITICAL,
|
||||
BROKEN_FREE
|
||||
}
|
||||
|
||||
@@ -138,14 +137,13 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
delegate.onSyncedDataUpdated(data)
|
||||
}
|
||||
if (data == CAPTURE_STATE) {
|
||||
val newState = entityData.get(CAPTURE_STATE)
|
||||
val newState = entityData[CAPTURE_STATE]
|
||||
when (CaptureState.entries[newState.toInt()]) {
|
||||
CaptureState.NOT -> setNoGravity(false)
|
||||
CaptureState.HIT -> {}
|
||||
CaptureState.FALL -> setNoGravity(false)
|
||||
CaptureState.SHAKE -> setNoGravity(true)
|
||||
CaptureState.CAPTURED, CaptureState.CAPTURED_CRITICAL -> {}
|
||||
CaptureState.BROKEN_FREE -> {}
|
||||
CaptureState.NOT, CaptureState.FALL -> isNoGravity = false
|
||||
CaptureState.CRITICAL, CaptureState.SHAKE -> isNoGravity = true
|
||||
CaptureState.HIT, CaptureState.CAPTURED, CaptureState.BROKEN_FREE -> {
|
||||
// no op
|
||||
}
|
||||
}
|
||||
}
|
||||
dataTrackerEmitter.emit(data)
|
||||
@@ -339,14 +337,14 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
|
||||
if (rollsRemaining <= 0) {
|
||||
if (captureResult.isSuccessfulCapture) {
|
||||
captureState = if (captureResult.isCriticalCapture) CaptureState.CAPTURED_CRITICAL else CaptureState.CAPTURED
|
||||
captureState = CaptureState.CAPTURED
|
||||
// Do a capture
|
||||
val pokemon = capturingPokemon ?: return
|
||||
val player = this.owner as? ServerPlayer ?: return
|
||||
val captureTime = if (pokeBall.ancient == true) 1.8F else 1F
|
||||
|
||||
after(seconds = captureTime) {
|
||||
// Dupes occurred by double-adding Pokémon, this hopefully prevents it triple-condom style
|
||||
// wtf did I just read above me
|
||||
if (pokemon.pokemon.isWild() && pokemon.isAlive && !captureFuture.isDone) {
|
||||
pokemon.discard()
|
||||
discard()
|
||||
@@ -406,7 +404,7 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
pokemon.busyLocks.remove(this)
|
||||
captureFuture.complete(false)
|
||||
val ballType =
|
||||
this.pokeBall.name.path.toString().toLowerCase().replace("_", "")
|
||||
this.pokeBall.name.path.lowercase().replace("_", "")
|
||||
val mode = "casual"
|
||||
val sendflash =
|
||||
BedrockParticleOptionsRepository.getEffect(cobblemonResource("${ballType}/${mode}/sendflash"))
|
||||
@@ -473,7 +471,6 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
override fun onHit(hitResult: HitResult) {
|
||||
super.onHit(hitResult)
|
||||
if (captureState == CaptureState.FALL && hitResult.type == HitResult.Type.BLOCK) {
|
||||
captureState = CaptureState.SHAKE
|
||||
if (level().isServerSide()) {
|
||||
beginCapture()
|
||||
}
|
||||
@@ -494,12 +491,26 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
if (rollsRemaining == 4) {
|
||||
rollsRemaining--
|
||||
}
|
||||
|
||||
var animatedCritical = false
|
||||
// Critical capture despite their name actually animate first
|
||||
// They can still fail and don't actually signify a success
|
||||
val iterations: Int = if (captureResult.isCriticalCapture) {
|
||||
captureState = CaptureState.CRITICAL
|
||||
captureResult.numberOfShakes + 2
|
||||
} else {
|
||||
captureState = CaptureState.SHAKE
|
||||
captureResult.numberOfShakes + 1
|
||||
}
|
||||
taskBuilder()
|
||||
.iterations(captureResult.numberOfShakes + 1)
|
||||
.iterations(iterations)
|
||||
.delay(SECONDS_BEFORE_SHAKE)
|
||||
.interval(SECONDS_BETWEEN_SHAKES)
|
||||
.execute {
|
||||
if (captureResult.isCriticalCapture && !animatedCritical) {
|
||||
captureState = CaptureState.SHAKE
|
||||
animatedCritical = true
|
||||
return@execute
|
||||
}
|
||||
if (pokeBall.ancient && rollsRemaining > 1) {
|
||||
rollsRemaining = 1
|
||||
}
|
||||
|
||||
@@ -1178,12 +1178,17 @@ open class PokemonEntity(
|
||||
if (!super.setEntityOnShoulder(player)) {
|
||||
return false
|
||||
}
|
||||
var isLeft = false
|
||||
// Use copies because player doesn't expose a forceful update of shoulder data
|
||||
val nbt = when {
|
||||
player.shoulderEntityRight.isPokemonEntity() && player.shoulderEntityRight.getCompound(DataKeys.POKEMON)
|
||||
.getUUID(DataKeys.POKEMON_UUID) == this.pokemon.uuid -> player.shoulderEntityRight
|
||||
.getUUID(DataKeys.POKEMON_UUID) == this.pokemon.uuid -> player.shoulderEntityRight.copy()
|
||||
|
||||
player.shoulderEntityLeft.isPokemonEntity() && player.shoulderEntityLeft.getCompound(DataKeys.POKEMON)
|
||||
.getUUID(DataKeys.POKEMON_UUID) == this.pokemon.uuid -> player.shoulderEntityLeft
|
||||
.getUUID(DataKeys.POKEMON_UUID) == this.pokemon.uuid -> {
|
||||
isLeft = true
|
||||
player.shoulderEntityLeft.copy()
|
||||
}
|
||||
|
||||
else -> return true
|
||||
}
|
||||
@@ -1192,6 +1197,7 @@ open class PokemonEntity(
|
||||
nbt.putString(DataKeys.SHOULDER_FORM, this.pokemon.form.name)
|
||||
nbt.put(DataKeys.SHOULDER_ASPECTS, this.pokemon.aspects.map(StringTag::valueOf).toNbtList())
|
||||
nbt.putFloat(DataKeys.SHOULDER_SCALE_MODIFIER, this.pokemon.scaleModifier)
|
||||
if (isLeft) player.shoulderEntityLeft = nbt else player.shoulderEntityRight = nbt
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -238,6 +238,7 @@ object CobblemonItemGroups {
|
||||
entries.accept(CobblemonItems.PC)
|
||||
entries.accept(CobblemonItems.HEALING_MACHINE)
|
||||
entries.accept(CobblemonItems.PASTURE)
|
||||
entries.accept(CobblemonItems.STADIUM)
|
||||
|
||||
entries.accept(CobblemonItems.GILDED_CHEST)
|
||||
entries.accept(CobblemonItems.YELLOW_GILDED_CHEST)
|
||||
|
||||
+7
-5
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.pokeball.catching.CaptureContext
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CaptureCalculator
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CriticalCaptureProvider
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.calculators.PokedexProgressCaptureMultiplierProvider
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.calculators.PokedexStatusCaptureInfluencer
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
|
||||
@@ -32,7 +33,7 @@ import kotlin.random.Random
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
object CobblemonCaptureCalculator: CaptureCalculator, CriticalCaptureProvider, PokedexProgressCaptureMultiplierProvider {
|
||||
object CobblemonCaptureCalculator: CaptureCalculator, CriticalCaptureProvider, PokedexProgressCaptureMultiplierProvider, PokedexStatusCaptureInfluencer {
|
||||
|
||||
override fun id(): String = "cobblemon"
|
||||
|
||||
@@ -54,9 +55,9 @@ object CobblemonCaptureCalculator: CaptureCalculator, CriticalCaptureProvider, P
|
||||
val pokeBall = pokeBallEntity.pokeBall
|
||||
val pokemon = target.pokemon
|
||||
if (pokeBall.catchRateModifier.isGuaranteed()) {
|
||||
return CaptureContext.successful()
|
||||
return this.influence(thrower, target, CaptureContext.successful())
|
||||
}
|
||||
// We don't have dark grass so we're just gonna pretend everything is that. Scratch that, without the pokedex it has issues.
|
||||
// We don't have dark grass so we're just going to pretend everything is that. Scratch that, without the Pokédex it has issues.
|
||||
val darkGrass = 1F //if (thrower is ServerPlayer) this.caughtMultiplierFor(thrower).roundToInt() else 1
|
||||
val inBattleModifier = if (target.battleId != null) 1F else 0.5F
|
||||
val catchRate = getCatchRate(thrower, pokeBallEntity, target, pokemon.form.catchRate.toFloat())
|
||||
@@ -94,8 +95,8 @@ object CobblemonCaptureCalculator: CaptureCalculator, CriticalCaptureProvider, P
|
||||
return CaptureContext(numberOfShakes = 1, isSuccessfulCapture = shakes == 1, isCriticalCapture = true)
|
||||
}
|
||||
}
|
||||
// ToDo once pokedex is implemented if the target is registered and it's a success shorten to 1 shake and become critical capture
|
||||
return CaptureContext(numberOfShakes = shakes, isSuccessfulCapture = shakes == 4, isCriticalCapture = false)
|
||||
return this.influence(thrower, target, CaptureContext(numberOfShakes = shakes, isSuccessfulCapture = shakes == 4, isCriticalCapture = false))
|
||||
|
||||
}
|
||||
|
||||
private fun findHighestThrowerLevel(player: ServerPlayer, pokemon: Pokemon): Int? {
|
||||
@@ -109,4 +110,5 @@ object CobblemonCaptureCalculator: CaptureCalculator, CriticalCaptureProvider, P
|
||||
} ?: return null
|
||||
return actor.getSide().getOppositeSide().activePokemon.maxOfOrNull { it.battlePokemon?.effectedPokemon?.level ?: 1 }
|
||||
}
|
||||
|
||||
}
|
||||
+4
-4
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.pokeball.catching.CaptureContext
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CaptureCalculator
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CriticalCaptureProvider
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.calculators.PokedexProgressCaptureMultiplierProvider
|
||||
import com.cobblemon.mod.common.api.pokeball.catching.calculators.PokedexStatusCaptureInfluencer
|
||||
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.BurnStatus
|
||||
@@ -34,7 +35,7 @@ import net.minecraft.server.level.ServerPlayer
|
||||
* @author Licious
|
||||
* @since January 29th, 2022
|
||||
*/
|
||||
object Gen9CaptureCalculator : CaptureCalculator, CriticalCaptureProvider, PokedexProgressCaptureMultiplierProvider {
|
||||
object Gen9CaptureCalculator : CaptureCalculator, CriticalCaptureProvider, PokedexProgressCaptureMultiplierProvider, PokedexStatusCaptureInfluencer {
|
||||
|
||||
override fun id(): String = "generation_9"
|
||||
|
||||
@@ -42,7 +43,7 @@ object Gen9CaptureCalculator : CaptureCalculator, CriticalCaptureProvider, Poked
|
||||
val pokeBall = pokeBallEntity.pokeBall
|
||||
val pokemon = target.pokemon
|
||||
if (pokeBall.catchRateModifier.isGuaranteed()) {
|
||||
return CaptureContext.successful()
|
||||
return this.influence(thrower, target, CaptureContext.successful())
|
||||
}
|
||||
// We don't have dark grass so we're just gonna pretend everything is that.
|
||||
val darkGrass = if (thrower is ServerPlayer) this.caughtMultiplierFor(thrower).roundToInt() else 1
|
||||
@@ -70,8 +71,7 @@ object Gen9CaptureCalculator : CaptureCalculator, CriticalCaptureProvider, Poked
|
||||
return CaptureContext(numberOfShakes = 1, isSuccessfulCapture = shakes == 1, isCriticalCapture = true)
|
||||
}
|
||||
}
|
||||
// ToDo once pokedex is implemented if the target is registered and it's a success shorten to 1 shake and become critical capture
|
||||
return CaptureContext(numberOfShakes = shakes, isSuccessfulCapture = shakes == 4, isCriticalCapture = false)
|
||||
return this.influence(thrower, target, CaptureContext(numberOfShakes = shakes, isSuccessfulCapture = shakes == 4, isCriticalCapture = false))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -97,7 +97,6 @@ import com.cobblemon.mod.common.pokemon.properties.BattleCloneProperty
|
||||
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
|
||||
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
|
||||
import com.cobblemon.mod.common.pokemon.status.PersistentStatusContainer
|
||||
import com.cobblemon.mod.common.util.DataKeys
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.codec.internal.ClientPokemonP1
|
||||
import com.cobblemon.mod.common.util.codec.internal.ClientPokemonP2
|
||||
@@ -128,7 +127,6 @@ import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.RegistryAccess
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.nbt.NbtOps
|
||||
import net.minecraft.nbt.StringTag
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.network.chat.contents.PlainTextContents
|
||||
|
||||
@@ -25,10 +25,12 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.item.PokedexItem
|
||||
import com.cobblemon.mod.common.platform.events.PlatformEvents
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.pokemon.activestate.ShoulderedState
|
||||
import com.cobblemon.mod.common.trade.TradeManager
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.nbt.StringTag
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.sounds.SoundEvents
|
||||
@@ -457,4 +459,15 @@ fun Player.isPartyBusy() =
|
||||
|
||||
fun Player.isUsingPokedex() = isUsingItem &&
|
||||
((mainHandItem.item is PokedexItem && usedItemHand == InteractionHand.MAIN_HAND) ||
|
||||
(offhandItem.item is PokedexItem && usedItemHand == InteractionHand.OFF_HAND))
|
||||
(offhandItem.item is PokedexItem && usedItemHand == InteractionHand.OFF_HAND))
|
||||
|
||||
fun ServerPlayer.updateShoulderNbt(pokemon: Pokemon) {
|
||||
// Use copies because player doesn't expose a forceful update of shoulder data
|
||||
val nbt = if ((pokemon.state as ShoulderedState).isLeftShoulder) shoulderEntityLeft.copy() else shoulderEntityRight.copy()
|
||||
nbt.putUUID(DataKeys.SHOULDER_UUID, uuid)
|
||||
nbt.putString(DataKeys.SHOULDER_SPECIES, pokemon.species.resourceIdentifier.toString())
|
||||
nbt.putString(DataKeys.SHOULDER_FORM, pokemon.form.name)
|
||||
nbt.put(DataKeys.SHOULDER_ASPECTS, pokemon.aspects.map(StringTag::valueOf).toNbtList())
|
||||
nbt.putFloat(DataKeys.SHOULDER_SCALE_MODIFIER, pokemon.scaleModifier)
|
||||
if ((pokemon.state as ShoulderedState).isLeftShoulder) shoulderEntityLeft = nbt else shoulderEntityRight = nbt
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
package com.cobblemon.mod.common.util.math
|
||||
|
||||
import com.cobblemon.mod.common.util.math.geometry.toDegrees
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
import org.joml.Quaternionf
|
||||
@@ -38,4 +39,18 @@ fun Quaternionf.hamiltonProduct(other: Quaternionf): Quaternionf {
|
||||
this.z = i * l + f * k - g * j + h * m
|
||||
this.w = i * m - f * j - g * k - h * l
|
||||
return this
|
||||
}
|
||||
|
||||
fun Quaternionf.toEulerXYZ(): Vector3f {
|
||||
return getEulerAnglesXYZ(Vector3f(0F, 0F, 0F))
|
||||
}
|
||||
|
||||
fun Quaternionf.toEulerXYZDegrees(): Vector3f {
|
||||
val vector = toEulerXYZ()
|
||||
|
||||
vector.x = vector.x.toDegrees()
|
||||
vector.y = vector.y.toDegrees()
|
||||
vector.z = vector.z.toDegrees()
|
||||
|
||||
return vector
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"format_version": "1.8.0",
|
||||
"animations": {
|
||||
"animation.rotom_drone.sleep": {
|
||||
"loop": true,
|
||||
"bones": {
|
||||
"body": {
|
||||
"rotation": [
|
||||
"math.sin(query.anim_time * 120) * 6",
|
||||
0,
|
||||
"math.cos(query.anim_time * 100) * 4"
|
||||
],
|
||||
"position": [
|
||||
0,
|
||||
"18 + math.sin(query.anim_time * 90) * 1",
|
||||
0
|
||||
]
|
||||
},
|
||||
"head": {
|
||||
"relative_to": {
|
||||
"rotation": "entity"
|
||||
},
|
||||
"rotation": [
|
||||
0,
|
||||
0,
|
||||
0.01
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.rotom_drone.circle": {
|
||||
"loop": true,
|
||||
"bones": {
|
||||
"rotom_drone": {
|
||||
"rotation": [30, "90 - q.anim_time*90", 0],
|
||||
"position": ["48*math.cos(q.anim_time*90)", 64, "48*math.sin(q.anim_time*90)"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
{
|
||||
"format_version": "1.12.0",
|
||||
"minecraft:geometry": [
|
||||
{
|
||||
"description": {
|
||||
"identifier": "geometry.drone_rotom",
|
||||
"texture_width": 64,
|
||||
"texture_height": 32,
|
||||
"visible_bounds_width": 8,
|
||||
"visible_bounds_height": 6.5,
|
||||
"visible_bounds_offset": [0, 2.75, 0]
|
||||
},
|
||||
"bones": [
|
||||
{
|
||||
"name": "rotom_drone",
|
||||
"pivot": [0, 0, 0]
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"parent": "rotom_drone",
|
||||
"pivot": [0, 8, 0]
|
||||
},
|
||||
{
|
||||
"name": "head",
|
||||
"parent": "body",
|
||||
"pivot": [0, 6.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-5.5, 6.5, -3.5], "size": [11, 6, 7], "uv": [0, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eyes",
|
||||
"parent": "head",
|
||||
"pivot": [0.5, 6.5, -2.5]
|
||||
},
|
||||
{
|
||||
"name": "eye_right",
|
||||
"parent": "eyes",
|
||||
"pivot": [-2.9, 9.5, -3.5],
|
||||
"rotation": [0, 0, -7.5],
|
||||
"cubes": [
|
||||
{"origin": [-3.9, 7.5, -3.5], "size": [2, 4, 0], "inflate": 0.01, "uv": [0, 0], "mirror": true},
|
||||
{"origin": [-3.9, 9, -3.5], "size": [2, 1, 0], "inflate": 0.02, "uv": [0, 6], "mirror": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pupil_right",
|
||||
"parent": "eye_right",
|
||||
"pivot": [-2.9, 9.5, -3.5],
|
||||
"cubes": [
|
||||
{"origin": [-3.4, 8, -3.5], "size": [1, 3, 0], "inflate": 0.03, "uv": [5, 0], "mirror": true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eye_left",
|
||||
"parent": "eyes",
|
||||
"pivot": [2.9, 9.5, -3.5],
|
||||
"rotation": [0, 0, 7.5],
|
||||
"cubes": [
|
||||
{"origin": [1.9, 7.5, -3.5], "size": [2, 4, 0], "inflate": 0.01, "uv": [0, 13]},
|
||||
{"origin": [1.9, 9, -3.5], "size": [2, 1, 0], "inflate": 0.02, "uv": [0, 5]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pupil_left",
|
||||
"parent": "eye_left",
|
||||
"pivot": [2.9, 9.5, -3.5],
|
||||
"cubes": [
|
||||
{"origin": [2.4, 8, -3.5], "size": [1, 3, 0], "inflate": 0.03, "uv": [6, 13]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_smile",
|
||||
"parent": "head",
|
||||
"pivot": [0, 7.55, -3.5],
|
||||
"cubes": [
|
||||
{"origin": [-1, 6.95, -3.5], "size": [2, 1, 0], "inflate": 0.02, "uv": [36, 7]},
|
||||
{"origin": [-1.5, 7.25, -3.5], "size": [1, 1, 0], "inflate": 0.01, "uv": [40, 7]},
|
||||
{"origin": [0.5, 7.25, -3.5], "size": [1, 1, 0], "inflate": 0.01, "uv": [42, 7]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_closed",
|
||||
"parent": "head",
|
||||
"pivot": [0, 7.45, -3.4],
|
||||
"cubes": [
|
||||
{"origin": [-0.5, 6.95, -3.4], "size": [1, 1, 0], "inflate": 0.02, "uv": [38, 8]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_open",
|
||||
"parent": "head",
|
||||
"pivot": [0, 7.45, -3.4],
|
||||
"cubes": [
|
||||
{"origin": [-0.5, 6.95, -3.4], "size": [1, 1, 0], "inflate": 0.02, "uv": [36, 8]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "mouth_sad",
|
||||
"parent": "head",
|
||||
"pivot": [0, 7.6, -3.3],
|
||||
"cubes": [
|
||||
{"origin": [-1, 7.3, -3.3], "size": [2, 1, 0], "inflate": 0.02, "uv": [36, 9]},
|
||||
{"origin": [-1.5, 7, -3.3], "size": [1, 1, 0], "inflate": 0.01, "uv": [40, 9]},
|
||||
{"origin": [0.5, 7, -3.3], "size": [1, 1, 0], "inflate": 0.01, "uv": [42, 9]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "head_spike1",
|
||||
"parent": "head",
|
||||
"pivot": [0, 12.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-1.5, 11.5, -1.5], "size": [3, 4, 3], "uv": [29, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "head_spike2",
|
||||
"parent": "head_spike1",
|
||||
"pivot": [0, 15.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-1, 14.5, -1], "size": [2, 4, 2], "uv": [8, 13]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "head_spike3",
|
||||
"parent": "head_spike2",
|
||||
"pivot": [0, 18.5, 0],
|
||||
"cubes": [
|
||||
{"origin": [-0.5, 17.5, -0.5], "size": [1, 3, 1], "uv": [44, 6]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "torso",
|
||||
"parent": "body",
|
||||
"pivot": [0, 7, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2, 3, -1.5], "size": [4, 4, 3], "uv": [34, 10]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "camera",
|
||||
"parent": "torso",
|
||||
"pivot": [0, 4.65, -1.25],
|
||||
"cubes": [
|
||||
{"origin": [-1, 3.65, -2.75], "size": [2, 2, 2], "uv": [24, 13]},
|
||||
{"origin": [-0.5, 4.15, -2.75], "size": [1, 1, 0], "inflate": 0.01, "uv": [38, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "propeller_front_right",
|
||||
"parent": "torso",
|
||||
"pivot": [-1.5, 5, -1],
|
||||
"rotation": [0, 45, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2, 5, -3.5], "size": [1, 0, 3], "uv": [37, 0]},
|
||||
{"origin": [-5.5, 5, -11.5], "size": [8, 0, 8], "uv": [40, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "propeller_front_left",
|
||||
"parent": "torso",
|
||||
"pivot": [1.5, 5, -1],
|
||||
"rotation": [0, -45, 0],
|
||||
"cubes": [
|
||||
{"origin": [1, 5, -3.5], "size": [1, 0, 3], "uv": [27, 0]},
|
||||
{"origin": [-2.5, 5, -11.5], "size": [8, 0, 8], "uv": [40, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "propeller_back_right",
|
||||
"parent": "torso",
|
||||
"pivot": [-1.5, 5, 1],
|
||||
"rotation": [0, -45, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2, 5, 0.5], "size": [1, 0, 3], "uv": [39, 0]},
|
||||
{"origin": [-5.5, 5, 3.5], "size": [8, 0, 8], "uv": [40, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "propeller_back_left",
|
||||
"parent": "torso",
|
||||
"pivot": [1.5, 5, 1],
|
||||
"rotation": [0, 45, 0],
|
||||
"cubes": [
|
||||
{"origin": [1, 5, 0.5], "size": [1, 0, 3], "uv": [42, 3]},
|
||||
{"origin": [-2.5, 5, 3.5], "size": [8, 0, 8], "uv": [40, 0]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tail1",
|
||||
"parent": "torso",
|
||||
"pivot": [0, 3, 0],
|
||||
"cubes": [
|
||||
{"origin": [-1, 1, -1], "size": [2, 3, 2], "uv": [16, 13]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tail2",
|
||||
"parent": "tail1",
|
||||
"pivot": [0, 1, 0],
|
||||
"cubes": [
|
||||
{"origin": [-0.5, -1, -0.5], "size": [1, 3, 1], "uv": [41, 3]}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"poses": {
|
||||
"sleep": {
|
||||
"poseTypes": ["SLEEP"],
|
||||
"animations": ["q.bedrock('rotom_drone', 'sleep')"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "cobblemon:rotom_drone",
|
||||
"order": 0,
|
||||
"variations": [
|
||||
{
|
||||
"aspects": [],
|
||||
"poser": "cobblemon:rotom_drone",
|
||||
"model": "cobblemon:rotom_drone.geo",
|
||||
"texture": "cobblemon:textures/drone/rotom_drone.png",
|
||||
"layers": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"": {
|
||||
"model": "cobblemon:block/stadium_terminal"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -801,6 +801,7 @@
|
||||
"block.cobblemon.healing_machine.tooltip2": "Charges over time.",
|
||||
"block.cobblemon.pc": "PC",
|
||||
"block.cobblemon.lectern": "Lectern",
|
||||
"block.cobblemon.stadium_terminal": "Stadium Terminal",
|
||||
"block.cobblemon.restoration_tank": "Restoration Tank",
|
||||
"block.cobblemon.fossil_analyzer": "Fossil Analyzer",
|
||||
"block.cobblemon.monitor": "Data Monitor",
|
||||
@@ -1547,6 +1548,7 @@
|
||||
"cobblemon.config.ui.category.healing": "Healing",
|
||||
"cobblemon.config.ui.category.storage": "Storage",
|
||||
"cobblemon.config.ui.category.world": "World",
|
||||
"cobblemon.config.ui.category.interface": "Interface",
|
||||
"cobblemon.config.ui.category.debug": "Debug",
|
||||
|
||||
"cobblemon.config.ui.max_pokemon_level": "Max Pokémon Level",
|
||||
@@ -1693,6 +1695,8 @@
|
||||
"cobblemon.config.ui.max_pokedex_scanning_detection_range.tooltip": "The maximum distance at which the Pokédex can detect and scan Pokémon.",
|
||||
"cobblemon.config.ui.hide_unimplemented_pokemon_in_the_pokedex": "Hide Unimplemented Pokémon in the Pokédex",
|
||||
"cobblemon.config.ui.hide_unimplemented_pokemon_in_the_pokedex.tooltip": "Whether to hide unimplemented Pokémon from the Pokédex.",
|
||||
"cobblemon.config.ui.summary_pokemon_follow_cursor": "Make Pokémon Follow Cursor",
|
||||
"cobblemon.config.ui.summary_pokemon_follow_cursor.tooltip": "Whether or not Pokémon should follow the mouse cursor with their heads in the Summary screen.",
|
||||
"cobblemon.config.ui.enable_debug_keys": "Enable Debug Keys",
|
||||
"cobblemon.config.ui.enable_debug_keys.tooltip": "Enables additional commands and chat logging to assist with implementing Pokémon (see the wiki for a list of commands).",
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "minecraft:block/cube_all",
|
||||
"textures": {
|
||||
"all": "cobblemon:block/stadium_terminal"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"parent": "cobblemon:block/stadium_terminal"
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 291 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"type": "minecraft:block",
|
||||
"pools": [{
|
||||
"rolls": 1,
|
||||
"entries": [{
|
||||
"type": "minecraft:item",
|
||||
"name": "cobblemon:stadium_terminal"
|
||||
}],
|
||||
"conditions": [{
|
||||
"condition": "minecraft:survives_explosion"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
Reference in new issue
Block a user