Completed pokeball capturing outside of battle, improved pokeball collision and motion, added some useful maths for parabolas

This commit is contained in:
Paul
2021-12-29 16:53:38 +10:00
committed by Paul
parent 84d630b759
commit f3906147ce
33 changed files with 507 additions and 253 deletions
Binary file not shown.
@@ -0,0 +1,34 @@
package com.cablemc.pokemoncobbled.client.entity
import com.cablemc.pokemoncobbled.client.render.models.blockbench.PoseableEntityState
import com.cablemc.pokemoncobbled.client.render.models.blockbench.frame.PokeBallFrame
import com.cablemc.pokemoncobbled.client.render.pokeball.animation.OpenAnimation
import com.cablemc.pokemoncobbled.client.render.pokeball.animation.ShakeAnimation
import com.cablemc.pokemoncobbled.common.api.entity.EntitySideDelegate
import com.cablemc.pokemoncobbled.common.api.reactive.Observable.Companion.emitWhile
import com.cablemc.pokemoncobbled.common.entity.pokeball.EmptyPokeBallEntity
import com.cablemc.pokemoncobbled.common.entity.pokeball.EmptyPokeBallEntity.CaptureState
import com.cablemc.pokemoncobbled.common.entity.pokeball.EmptyPokeBallEntity.CaptureState.HIT
import com.cablemc.pokemoncobbled.common.entity.pokeball.EmptyPokeBallEntity.CaptureState.SHAKE
import com.cablemc.pokemoncobbled.common.entity.pokeball.PokeBallDelegate
class EmptyPokeBallClientDelegate : PoseableEntityState<EmptyPokeBallEntity>(), PokeBallDelegate<EmptyPokeBallEntity> {
override fun initialize(entity: EmptyPokeBallEntity) {
entity.captureState.subscribe {
when (CaptureState.values()[it.toInt()]) {
HIT -> {
statefulAnimations.add(OpenAnimation())
}
CaptureState.FALL -> {
statefulAnimations.clear()
}
SHAKE -> {
entity.shakeEmitter
.pipe(emitWhile { entity.captureState.get() == SHAKE.ordinal.toByte() })
.subscribe { statefulAnimations.add(ShakeAnimation(0.8F)) }
}
else -> {}
}
}
}
}
@@ -0,0 +1,7 @@
package com.cablemc.pokemoncobbled.client.entity
import com.cablemc.pokemoncobbled.client.render.models.blockbench.PoseableEntityState
import com.cablemc.pokemoncobbled.common.entity.pokeball.OccupiedPokeBallEntity
import com.cablemc.pokemoncobbled.common.entity.pokeball.PokeBallDelegate
class OccupiedPokeBallClientDelegate : PoseableEntityState<OccupiedPokeBallEntity>(), PokeBallDelegate<OccupiedPokeBallEntity>
@@ -1,32 +0,0 @@
package com.cablemc.pokemoncobbled.client.entity
import com.cablemc.pokemoncobbled.client.render.models.blockbench.PoseableEntityState
import com.cablemc.pokemoncobbled.client.render.models.blockbench.frame.PokeBallFrame
import com.cablemc.pokemoncobbled.client.render.pokeball.animation.OpenAnimation
import com.cablemc.pokemoncobbled.common.api.entity.EntitySideDelegate
import com.cablemc.pokemoncobbled.common.entity.pokeball.EmptyPokeBallEntity
import com.cablemc.pokemoncobbled.common.entity.pokeball.EmptyPokeBallEntity.CaptureState
import com.cablemc.pokemoncobbled.common.entity.pokeball.EmptyPokeBallEntity.CaptureState.HIT
import com.cablemc.pokemoncobbled.common.entity.pokeball.EmptyPokeBallEntity.CaptureState.SHAKE
import com.cablemc.pokemoncobbled.common.entity.pokeball.PokeBallEntity
class PokeBallClientDelegate : PoseableEntityState<PokeBallEntity>(), EntitySideDelegate<PokeBallEntity> {
override fun initialize(entity: PokeBallEntity) {
if (entity is EmptyPokeBallEntity) {
entity.captureState.subscribe {
when (CaptureState.values()[it.toInt()]) {
HIT -> {
statefulAnimations.add(OpenAnimation(currentModel as PokeBallFrame))
}
CaptureState.FALL -> {
}
SHAKE -> {
}
else -> {}
}
}
}
}
}
@@ -98,7 +98,6 @@ abstract class PoseableEntityModel<T : Entity> : EntityModel<T>(), ModelFrame {
if (previousPose != null && pose.transformTicks > 0) { if (previousPose != null && pose.transformTicks > 0) {
state.statefulAnimations.add( state.statefulAnimations.add(
PoseTransitionAnimation( PoseTransitionAnimation(
frame = this,
beforePose = previousPose, beforePose = previousPose,
afterPose = pose, afterPose = pose,
durationTicks = pose.transformTicks durationTicks = pose.transformTicks
@@ -10,7 +10,7 @@ import net.minecraft.world.entity.Entity
/** /**
* Represents the entity-specific state for a poseable model. The implementation is responsible for * Represents the entity-specific state for a poseable model. The implementation is responsible for
* handling all of the state for an entity's model, and needs to be conscious of the fact that the * handling all the state for an entity's model, and needs to be conscious of the fact that the
* model may change without this state changing. * model may change without this state changing.
* *
* @author Hiroku * @author Hiroku
@@ -49,7 +49,7 @@ abstract class PoseableEntityState<T : Entity> {
return return
} }
val animation = PoseTransitionAnimation(model, beforePose, afterPose, durationTicks) val animation = PoseTransitionAnimation(beforePose, afterPose, durationTicks)
statefulAnimations.add(animation) statefulAnimations.add(animation)
} }
@@ -16,11 +16,10 @@ import java.lang.Float.min
* @since December 5th, 2021 * @since December 5th, 2021
*/ */
class PoseTransitionAnimation<T : Entity>( class PoseTransitionAnimation<T : Entity>(
frame: ModelFrame,
beforePose: Pose<T, *>, beforePose: Pose<T, *>,
val afterPose: Pose<T, *>, val afterPose: Pose<T, *>,
private val durationTicks: Int = 20 private val durationTicks: Int = 20
) : StatefulAnimation<T, ModelFrame>(frame) { ) : StatefulAnimation<T, ModelFrame> {
var passedTicks = 0F var passedTicks = 0F
val transforms = mutableListOf<GradualTransform>() val transforms = mutableListOf<GradualTransform>()
@@ -0,0 +1,31 @@
package com.cablemc.pokemoncobbled.client.render.models.blockbench.animation
import com.cablemc.pokemoncobbled.client.render.models.blockbench.PoseableEntityModel
import com.cablemc.pokemoncobbled.client.render.models.blockbench.frame.PokeBallFrame
import com.cablemc.pokemoncobbled.common.entity.pokeball.EmptyPokeBallEntity
import com.cablemc.pokemoncobbled.common.entity.pokeball.PokeBallEntity
import net.minecraft.util.Mth
import net.minecraft.util.Mth.PI
/**
* A simple idle animation that will point the PokéBall in the direction of the target it hit. Does nothing
* for occupied PokéBalls.
*
* @author Hiroku
* @since December 25th, 2021
*/
class RootPokeBallLookAnimation(frame: PokeBallFrame) : StatelessAnimation<PokeBallEntity, PokeBallFrame>(frame) {
override val targetFrame = PokeBallFrame::class.java
override fun setAngles(entity: PokeBallEntity?, model: PoseableEntityModel<PokeBallEntity>, limbSwing: Float, limbSwingAmount: Float, ageInTicks: Float, headYaw: Float, headPitch: Float) {
val yRot = entity?.let {
if (it is EmptyPokeBallEntity) {
val dispX = it.hitTargetPosition.get().x - it.x
val dispZ = it.hitTargetPosition.get().z - it.z
Mth.atan2(-dispZ, dispX) + PI / 2
} else {
0F
}
}?.toFloat() ?: 0F
model.rootPart.yRot += yRot
}
}
@@ -12,14 +12,14 @@ import net.minecraft.world.entity.Entity
* @author Hiroku * @author Hiroku
* @since December 5th, 2021 * @since December 5th, 2021
*/ */
abstract class StatefulAnimation<T : Entity, F : ModelFrame>(val frame: F) { interface StatefulAnimation<T : Entity, F : ModelFrame> {
/** /**
* Whether this animation should prevent the given idle animation from occurring. * Whether this animation should prevent the given idle animation from occurring.
* *
* This is for cases where this animation and the idle animation work on the same parts * This is for cases where this animation and the idle animation work on the same parts
* of the model and would conflict. * of the model and would conflict.
*/ */
abstract fun preventsIdle(entity: T, idleAnimation: StatelessAnimation<T, *>): Boolean fun preventsIdle(entity: T, idleAnimation: StatelessAnimation<T, *>): Boolean
/** Runs the animation. You can access the frame from the class properties. Returns true if the animation should continue. */ /** Runs the animation. You can check that the model fits a particular frame. Returns true if the animation should continue. */
abstract fun run(entity: T, model: PoseableEntityModel<T>): Boolean fun run(entity: T, model: PoseableEntityModel<T>): Boolean
} }
@@ -3,5 +3,6 @@ package com.cablemc.pokemoncobbled.client.render.models.blockbench.frame
import net.minecraft.client.model.geom.ModelPart import net.minecraft.client.model.geom.ModelPart
interface PokeBallFrame : ModelFrame { interface PokeBallFrame : ModelFrame {
val subRoot: ModelPart
val lid: ModelPart val lid: ModelPart
} }
@@ -1,11 +1,15 @@
package com.cablemc.pokemoncobbled.client.render.models.blockbench.pokeball package com.cablemc.pokemoncobbled.client.render.models.blockbench.pokeball
import com.cablemc.pokemoncobbled.client.entity.PokeBallClientDelegate
import com.cablemc.pokemoncobbled.client.render.models.blockbench.PoseableEntityModel import com.cablemc.pokemoncobbled.client.render.models.blockbench.PoseableEntityModel
import com.cablemc.pokemoncobbled.client.render.models.blockbench.animation.RotationFunctionStatelessAnimation import com.cablemc.pokemoncobbled.client.render.models.blockbench.PoseableEntityState
import com.cablemc.pokemoncobbled.client.render.models.blockbench.animation.RootPokeBallLookAnimation
import com.cablemc.pokemoncobbled.client.render.models.blockbench.frame.PokeBallFrame import com.cablemc.pokemoncobbled.client.render.models.blockbench.frame.PokeBallFrame
import com.cablemc.pokemoncobbled.client.render.models.blockbench.getChildOf
import com.cablemc.pokemoncobbled.client.render.models.blockbench.pose.PoseType import com.cablemc.pokemoncobbled.client.render.models.blockbench.pose.PoseType
import com.cablemc.pokemoncobbled.client.render.models.blockbench.pose.TransformedModelPart.Companion.Y_AXIS import com.cablemc.pokemoncobbled.client.render.models.blockbench.pose.TransformedModelPart.Companion.Y_AXIS
import com.cablemc.pokemoncobbled.common.PokemonCobbled
import com.cablemc.pokemoncobbled.common.entity.pokeball.EmptyPokeBallEntity
import com.cablemc.pokemoncobbled.common.entity.pokeball.OccupiedPokeBallEntity
import com.cablemc.pokemoncobbled.common.entity.pokeball.PokeBallEntity import com.cablemc.pokemoncobbled.common.entity.pokeball.PokeBallEntity
import net.minecraft.client.model.geom.ModelLayerLocation import net.minecraft.client.model.geom.ModelLayerLocation
import net.minecraft.client.model.geom.ModelPart import net.minecraft.client.model.geom.ModelPart
@@ -18,18 +22,38 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth.PI import net.minecraft.util.Mth.PI
class PokeBallModel(root: ModelPart) : PoseableEntityModel<PokeBallEntity>(), PokeBallFrame { class PokeBallModel(root: ModelPart) : PoseableEntityModel<PokeBallEntity>(), PokeBallFrame {
override val rootPart = registerRelevantPart(root.getChild("pokeball")) override val rootPart = registerRelevantPart(root.getChild("root"))
override val lid = registerRelevantPart(root.getChild("pokeball").getChild("pokeball_lid")) override val subRoot = registerRelevantPart(rootPart.getChild("pokeball"))
override val lid = registerRelevantPart(subRoot.getChild("pokeball_lid"))
override fun registerPoses() { override fun registerPoses() {
registerPose( registerPose(
poseType = PoseType.NONE, poseType = PoseType.NONE,
condition = { true }, condition = { it is EmptyPokeBallEntity && it.captureState.get() != EmptyPokeBallEntity.CaptureState.NOT.ordinal.toByte() },
idleAnimations = arrayOf(RootPokeBallLookAnimation(this)),
transformTicks = 0,
transformedParts = arrayOf()
)
registerPose(
poseType = PoseType.WALK,
condition = { it is OccupiedPokeBallEntity || (it as EmptyPokeBallEntity).captureState.get() == EmptyPokeBallEntity.CaptureState.NOT.ordinal.toByte() },
transformTicks = 0,
idleAnimations = arrayOf( idleAnimations = arrayOf(
rootPart.rotation( rootPart.rotation(
function = { t -> t * PI / 10 }, // 1 rotation per second = 2pi per 20 ticks = 2pi / 20 = pi / 10 per tick function = { t -> t * PI / 10 }, // 1 rotation per second = 2pi per 20 ticks = 2pi / 20 = pi / 10 per tick
axis = Y_AXIS, axis = Y_AXIS,
timeVariable = { _, _, ageInTicks -> ageInTicks } timeVariable = { _, _, ageInTicks -> ageInTicks }
),
rootPart.translation(
function = { t ->
if (t > 8) {
0F
} else {
-(8F - t) * 3F
}
},
axis = Y_AXIS,
timeVariable = { _, _, ageInTicks -> ageInTicks }
) )
), ),
transformedParts = arrayOf() transformedParts = arrayOf()
@@ -38,15 +62,20 @@ class PokeBallModel(root: ModelPart) : PoseableEntityModel<PokeBallEntity>(), Po
companion object { companion object {
// This layer location should be baked with EntityRendererProvider.Context in the entity renderer and passed into this model's constructor // This layer location should be baked with EntityRendererProvider.Context in the entity renderer and passed into this model's constructor
val LAYER_LOCATION = ModelLayerLocation(ResourceLocation("modid", "pokeball"), "main") val LAYER_LOCATION = ModelLayerLocation(ResourceLocation(PokemonCobbled.MODID, "pokeball"), "main")
fun createBodyLayer(): LayerDefinition { fun createBodyLayer(): LayerDefinition {
val meshdefinition = MeshDefinition() val meshdefinition = MeshDefinition()
val partdefinition = meshdefinition.root val partdefinition = meshdefinition.root
val pokeball = partdefinition.addOrReplaceChild( val root = partdefinition.addOrReplaceChild(
"root",
CubeListBuilder.create(),
PartPose.offsetAndRotation(0.0f, 0.0f, 0.0f, PI, 0F, 0F)
)
val pokeball = root.addOrReplaceChild(
"pokeball", "pokeball",
CubeListBuilder.create().texOffs(0, 0) CubeListBuilder.create().texOffs(0, 0)
.addBox(-4.0f, -4.0f, -4.0f, 8.0f, 4.0f, 8.0f, CubeDeformation(0.0f)), .addBox(-4.0f, -4.0f, -4.0f, 8.0f, 4.0f, 8.0f, CubeDeformation(0.0f)),
PartPose.offsetAndRotation(0.0f, 24.0f, 0.0f, PI, 0F, 0F) PartPose.offsetAndRotation(0.0f, 0.0f, 0.0f, 0F, 0F, 0F)
) )
val pokeball_lid = pokeball.addOrReplaceChild( val pokeball_lid = pokeball.addOrReplaceChild(
"pokeball_lid", "pokeball_lid",
@@ -58,5 +87,5 @@ class PokeBallModel(root: ModelPart) : PoseableEntityModel<PokeBallEntity>(), Po
} }
} }
override fun getState(entity: PokeBallEntity) = entity.delegate as PokeBallClientDelegate override fun getState(entity: PokeBallEntity) = entity.delegate as PoseableEntityState<PokeBallEntity>
} }
@@ -1,8 +1,11 @@
package com.cablemc.pokemoncobbled.client.render.models.blockbench.wavefunction package com.cablemc.pokemoncobbled.client.render.models.blockbench.wavefunction
import net.minecraft.util.Mth.PI import net.minecraft.util.Mth.PI
import net.minecraft.util.Mth.sqrt
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sign
import kotlin.math.sin import kotlin.math.sin
typealias WaveFunction = (Float) -> Float typealias WaveFunction = (Float) -> Float
@@ -14,12 +17,65 @@ fun gradient(function: WaveFunction, t0: Float, t1: Float) = (function(t1) - fun
operator fun WaveFunction.plus(other: WaveFunction): WaveFunction = { t -> this(t) + other(t) } operator fun WaveFunction.plus(other: WaveFunction): WaveFunction = { t -> this(t) + other(t) }
operator fun WaveFunction.times(other: WaveFunction): WaveFunction = { t -> this(t) * other(t) } operator fun WaveFunction.times(other: WaveFunction): WaveFunction = { t -> this(t) * other(t) }
fun WaveFunction.aggregate(func: WaveFunction): WaveFunction = { t -> func(this(t)) } fun WaveFunction.aggregate(func: WaveFunction): WaveFunction = { t -> func(this(t)) }
fun linearFunction(gradient: Float = 1F, yIntercept: Float = 0F): WaveFunction = { t -> gradient * t + yIntercept }
fun sineFunction(amplitude: Float = 1F, period: Float = 1F, phaseShift: Float = 0F, verticalShift: Float = 0F): WaveFunction = { t -> sin(2*PI/period * (t - phaseShift)) * amplitude + verticalShift } fun sineFunction(amplitude: Float = 1F, period: Float = 1F, phaseShift: Float = 0F, verticalShift: Float = 0F): WaveFunction = { t -> sin(2*PI/period * (t - phaseShift)) * amplitude + verticalShift }
fun cosineFunction(amplitude: Float = 1F, period: Float = 1F, phaseShift: Float = 0F, verticalShift: Float = 0F): WaveFunction = { t -> cos(2*PI/period * (t - phaseShift)) * amplitude + verticalShift } fun cosineFunction(amplitude: Float = 1F, period: Float = 1F, phaseShift: Float = 0F, verticalShift: Float = 0F): WaveFunction = { t -> cos(2*PI/period * (t - phaseShift)) * amplitude + verticalShift }
fun triangleFunction(amplitude: Float = 1F, period: Float = 1F, phaseShift: Float = 0F, verticalShift: Float = 0F): WaveFunction = { t -> fun triangleFunction(amplitude: Float = 1F, period: Float = 1F, phaseShift: Float = 0F, verticalShift: Float = 0F): WaveFunction = { t ->
val timeTerm = ((t + 3 * period / 4 - phaseShift) % period) - period/2 var time = t
while (time < 0) {
time += period
}
val timeTerm = ((time + 3 * period / 4 - phaseShift) % period) - period/2
val value = 4 * amplitude / period * abs(timeTerm) - amplitude + verticalShift val value = 4 * amplitude / period * abs(timeTerm) - amplitude + verticalShift
value value
} }
/**
* Returns a parabola that intercepts the y axis at zero, intercepts the x axis again
* at [period], and at its furthest from y = 0 will reach [peak].
*
* The maths used to calculate this was a bit confusing but I've added my working to the
* docs/parabolaworking.pdf file in the Cobbled repository.
*/
fun parabolaFunction(
peak: Float,
period: Float
) = parabolaFunction(
tightness = -4 * peak / period.pow(2),
verticalShift = peak,
phaseShift = period / 2
)
fun parabolaFunction(
tightness: Float = -1F,
phaseShift: Float = 0F,
verticalShift: Float = 1F
): WaveFunction {
/*
* To understand the parameters for this, type y=t(x-p)+v into https://www.desmos.com/calculator and play
* around with tightness, phase shift, and vertical shift
*/
val a = tightness
val b = -2 * phaseShift * tightness
val c = tightness * phaseShift * phaseShift + verticalShift
val root1 = (-b - sqrt(b*b - 4 * a * c)) / (2 * tightness)
val root2 = (-b + sqrt(b*b - 4 * a * c)) / (2 * tightness)
val tMin = if (root1 < root2) root1 else root2
val tMax = if (root1 < root2) root2 else root1
val period = tMax - tMin
return { t ->
var time = t
while (time < tMin) {
time += period
}
while (time > tMax) {
time -= period
}
tightness * (time - phaseShift).pow(2) + verticalShift
}
}
@@ -30,9 +30,9 @@ class PokeBallRenderer<T : PokeBallEntity>(context: EntityRendererProvider.Conte
) { ) {
val model = PokeBallModelRepository.getModel(pEntity.pokeBall).entityModel val model = PokeBallModelRepository.getModel(pEntity.pokeBall).entityModel
pMatrixStack.pushPose() pMatrixStack.pushPose()
pMatrixStack.translate(0.0, -1.0, 0.0) pMatrixStack.scale(0.7F, 0.7F, 0.7F)
val vertexconsumer = ItemRenderer.getFoilBufferDirect(pBuffer, model.renderType(getTextureLocation(pEntity)), false, false) val vertexconsumer = ItemRenderer.getFoilBufferDirect(pBuffer, model.renderType(getTextureLocation(pEntity)), false, false)
model.setupAnim(pEntity, 0f, 0f, pEntity.tickCount + pPartialTicks, pEntity.yRot, pEntity.xRot) model.setupAnim(pEntity, 0f, 0f, pEntity.tickCount + pPartialTicks, pEntity.yRotO, pEntity.xRotO)
model.renderToBuffer(pMatrixStack, vertexconsumer, pPackedLight, OverlayTexture.NO_OVERLAY, 1.0f, 1.0f, 1.0f, 1.0f) model.renderToBuffer(pMatrixStack, vertexconsumer, pPackedLight, OverlayTexture.NO_OVERLAY, 1.0f, 1.0f, 1.0f, 1.0f)
pMatrixStack.popPose() pMatrixStack.popPose()
super.render(pEntity, pEntityYaw, pPartialTicks, pMatrixStack, pBuffer, pPackedLight) super.render(pEntity, pEntityYaw, pPartialTicks, pMatrixStack, pBuffer, pPackedLight)
@@ -1,15 +0,0 @@
package com.cablemc.pokemoncobbled.client.render.pokeball.animation
import net.minecraft.client.model.EntityModel
interface ModelAnimation<T : EntityModel<*>> {
/** The current frame the animation is on. If this is null, the animation is not currently running. */
val currentFrame: Int?
/** Animates the model. */
fun animate(model: T, limbSwing: Float, limbSwingAmout: Float, ageInTicks: Float, netHeadYaw: Float, headPitch: Float, frame: Int = 0)
fun resetAnimation()
}
@@ -4,18 +4,38 @@ import com.cablemc.pokemoncobbled.client.render.models.blockbench.PoseableEntity
import com.cablemc.pokemoncobbled.client.render.models.blockbench.animation.StatefulAnimation import com.cablemc.pokemoncobbled.client.render.models.blockbench.animation.StatefulAnimation
import com.cablemc.pokemoncobbled.client.render.models.blockbench.animation.StatelessAnimation import com.cablemc.pokemoncobbled.client.render.models.blockbench.animation.StatelessAnimation
import com.cablemc.pokemoncobbled.client.render.models.blockbench.frame.PokeBallFrame import com.cablemc.pokemoncobbled.client.render.models.blockbench.frame.PokeBallFrame
import com.cablemc.pokemoncobbled.common.entity.pokeball.PokeBallEntity import com.cablemc.pokemoncobbled.common.entity.pokeball.EmptyPokeBallEntity
import com.cablemc.pokemoncobbled.common.util.math.geometry.toRadians import com.cablemc.pokemoncobbled.common.util.math.geometry.toRadians
import com.cablemc.pokemoncobbled.common.util.math.toVec3
import net.minecraft.commands.arguments.EntityAnchorArgument
import net.minecraft.util.Mth
import net.minecraft.util.Mth.PI import net.minecraft.util.Mth.PI
import net.minecraft.world.phys.Vec3
import kotlin.math.min
class OpenAnimation(frame: PokeBallFrame) : StatefulAnimation<PokeBallEntity, PokeBallFrame>(frame) { /**
* Animation that opens a PokéBall upon hitting a Pokémon, and then shuts it.
*
* @author Hiroku
* @since December 24th, 2021
*/
class OpenAnimation : StatefulAnimation<EmptyPokeBallEntity, PokeBallFrame> {
companion object { companion object {
const val DURATION_SECONDS = 3F const val OPEN_START = 0.1F
const val OPEN_END = 0.3F
const val CLOSE_START = 1F
const val CLOSE_END = 1.2F
const val OPEN_ANGLE = PI / 3F
} }
var initialized = false var initialized = false
override fun preventsIdle(entity: PokeBallEntity, idleAnimation: StatelessAnimation<PokeBallEntity, *>) = true var startedClosing = false
override fun run(entity: PokeBallEntity, model: PoseableEntityModel<PokeBallEntity>): Boolean { var maxPitch = 0F
override fun preventsIdle(entity: EmptyPokeBallEntity, idleAnimation: StatelessAnimation<EmptyPokeBallEntity, *>) = true
override fun run(entity: EmptyPokeBallEntity, model: PoseableEntityModel<EmptyPokeBallEntity>): Boolean {
val frame = model as PokeBallFrame
if (!initialized) { if (!initialized) {
model.getState(entity).animationSeconds = 0F model.getState(entity).animationSeconds = 0F
initialized = true initialized = true
@@ -23,19 +43,29 @@ class OpenAnimation(frame: PokeBallFrame) : StatefulAnimation<PokeBallEntity, Po
val animationSeconds = model.getState(entity).animationSeconds val animationSeconds = model.getState(entity).animationSeconds
when { entity.lookAt(EntityAnchorArgument.Anchor.EYES, entity.hitTargetPosition.get().toVec3())
animationSeconds <= 1 -> { val xDist = entity.hitTargetPosition.get().x - entity.x
frame.lid.xRot = -animationSeconds * PI / 3 val zDist = entity.hitTargetPosition.get().z - entity.z
} frame.rootPart.yRot = Mth.atan2(-zDist, xDist).toFloat() + PI / 2
animationSeconds > 2 -> { frame.rootPart.xRot = -entity.xRotO.toRadians() - PI / 2
frame.lid.xRot = -30f.toRadians() + ((animationSeconds - 2) * (1.5f).toRadians()) / 20F
} val minPitch = -PI
else -> {
frame.lid.xRot = 30f.toRadians() if (animationSeconds >= OPEN_START && animationSeconds < CLOSE_START) {
return false val portion = min((animationSeconds - OPEN_START)/OPEN_END, 1F)
frame.lid.xRot = -portion * OPEN_ANGLE
} else if (animationSeconds >= CLOSE_START) {
if (!startedClosing) {
startedClosing = true
maxPitch = frame.rootPart.xRot
} }
val portion = min((animationSeconds - CLOSE_START) / (CLOSE_END - CLOSE_START), 1F)
frame.lid.xRot = (portion - 1) * OPEN_ANGLE
val dist = maxPitch - minPitch
frame.rootPart.xRot = minPitch + (1 - portion) * dist
} }
return true return entity.captureState.get() == EmptyPokeBallEntity.CaptureState.HIT.ordinal.toByte()
} }
} }
@@ -1,47 +1,51 @@
package com.cablemc.pokemoncobbled.client.render.pokeball.animation package com.cablemc.pokemoncobbled.client.render.pokeball.animation
import com.cablemc.pokemoncobbled.client.render.models.blockbench.pokeball.PokeBallModel import com.cablemc.pokemoncobbled.client.render.models.blockbench.PoseableEntityModel
import com.cablemc.pokemoncobbled.common.util.math.geometry.toRadians import com.cablemc.pokemoncobbled.client.render.models.blockbench.addRotation
import net.minecraft.util.Mth import com.cablemc.pokemoncobbled.client.render.models.blockbench.animation.StatefulAnimation
import com.cablemc.pokemoncobbled.client.render.models.blockbench.animation.StatelessAnimation
import com.cablemc.pokemoncobbled.client.render.models.blockbench.frame.PokeBallFrame
import com.cablemc.pokemoncobbled.client.render.models.blockbench.pose.TransformedModelPart.Companion.Z_AXIS
import com.cablemc.pokemoncobbled.client.render.models.blockbench.wavefunction.linearFunction
import com.cablemc.pokemoncobbled.client.render.models.blockbench.wavefunction.sineFunction
import com.cablemc.pokemoncobbled.client.render.models.blockbench.wavefunction.times
import com.cablemc.pokemoncobbled.common.entity.pokeball.EmptyPokeBallEntity
import net.minecraft.util.Mth.PI
class ShakeAnimation(val numShakes: Int) : ModelAnimation<PokeBallModel> { /**
* Animation that shakes a PokéBall on the specified axis, with the specified force.
* This will move it along that axis along a sine function whose highest amplitude is
* the force multiplied by PI / 3, meaning at force 1 it rotates 60 degrees to either
* side. This shaking will be dampened to nothing over the course of 1 second.
*
* @author Hiroku
* @since December 25th, 2021
*/
class ShakeAnimation(
val force: Float,
val axis: Int = Z_AXIS
) : StatefulAnimation<EmptyPokeBallEntity, PokeBallFrame> {
val shakeFunction = sineFunction(
amplitude = force * PI / 3,
period = 0.7F
) * linearFunction(
gradient = -1F,
yIntercept = 1F
)
override var currentFrame: Int? = null var initialized = false
private set
private var shakesComplete = 0
override fun animate( override fun preventsIdle(entity: EmptyPokeBallEntity, idleAnimation: StatelessAnimation<EmptyPokeBallEntity, *>) = false
model: PokeBallModel, override fun run(entity: EmptyPokeBallEntity, model: PoseableEntityModel<EmptyPokeBallEntity>): Boolean {
limbSwing: Float, val frame = model as PokeBallFrame
limbSwingAmout: Float, val state = model.getState(entity)
ageInTicks: Float, if (!initialized) {
netHeadYaw: Float, state.animationSeconds = 0F
headPitch: Float, initialized = true
frame: Int
) {
currentFrame = frame % 63
model.rootPart.y = 20f
model.rootPart.xRot = 180f.toRadians()
model.rootPart.yRot = 0f
model.lid.xRot = 0f
if (shakesComplete < numShakes) {
if (currentFrame == 62) {
currentFrame = 0
shakesComplete++
}
if (currentFrame!! <= 42) {
model.rootPart.zRot = Mth.sin(currentFrame!!.toFloat() * 0.15f) * 30f.toRadians()
}
else {
model.rootPart.zRot = 0f
}
} }
else {
model.rootPart.zRot = 0f
}
}
override fun resetAnimation() { frame.subRoot.addRotation(axis = axis, differenceInRadians = shakeFunction(state.animationSeconds))
currentFrame = 0
return state.animationSeconds < 1F
} }
} }
@@ -5,6 +5,7 @@ import com.cablemc.pokemoncobbled.common.api.pokemon.stats.Stats
import com.cablemc.pokemoncobbled.common.pokeball.PokeBall import com.cablemc.pokemoncobbled.common.pokeball.PokeBall
import com.cablemc.pokemoncobbled.common.pokemon.Pokemon import com.cablemc.pokemoncobbled.common.pokemon.Pokemon
import net.minecraft.server.level.ServerPlayer import net.minecraft.server.level.ServerPlayer
import kotlin.math.pow
import kotlin.random.Random.Default.nextInt import kotlin.random.Random.Default.nextInt
/** /**
@@ -22,7 +23,7 @@ class Gen7CaptureCalculator : CaptureCalculator {
return CaptureContext(isSuccessfulCapture = true, isCriticalCapture = true, numberOfShakes = 1) return CaptureContext(isSuccessfulCapture = true, isCriticalCapture = true, numberOfShakes = 1)
} }
else { else {
val shakeProbability = (25536 / Math.pow(255 / catchRate.toDouble(), 3.0 / 16)) val shakeProbability = (25536 / (255 / catchRate.toDouble()).pow(3.0 / 16))
var numShakes = 0 var numShakes = 0
for (i in 0..3) { for (i in 0..3) {
@@ -31,13 +32,13 @@ class Gen7CaptureCalculator : CaptureCalculator {
} }
numShakes++ numShakes++
} }
return CaptureContext(isSuccessfulCapture = true, isCriticalCapture = false, numberOfShakes = numShakes) return CaptureContext(isSuccessfulCapture = numShakes == 4, isCriticalCapture = false, numberOfShakes = numShakes)
} }
} }
fun getCatchRate(player: ServerPlayer, pokemon: Pokemon, pokeBall: PokeBall): Float { fun getCatchRate(player: ServerPlayer, pokemon: Pokemon, pokeBall: PokeBall): Float {
var catchRate = pokemon.species.catchRate.toFloat() var catchRate = pokemon.species.catchRate.toFloat()
pokeBall.catchRateModifiers.forEach { modifier -> catchRate = modifier.modifyCatchRate(catchRate, player, pokemon) } pokeBall.catchRateModifiers.forEach { catchRate = it.modifyCatchRate(catchRate, player, pokemon) }
val maxHealth = pokemon.getMaxHealth() val maxHealth = pokemon.getMaxHealth()
val currentHealth = pokemon.health val currentHealth = pokemon.health
val statusBonus = getStatusBonus(pokemon) val statusBonus = getStatusBonus(pokemon)
@@ -1,6 +1,7 @@
package com.cablemc.pokemoncobbled.common.api.scheduling package com.cablemc.pokemoncobbled.common.api.scheduling
import net.minecraft.util.Mth.ceil import net.minecraft.util.Mth.ceil
import kotlin.math.roundToInt
fun after(ticks: Int = 0, seconds: Float = 0F, action: () -> Unit) { fun after(ticks: Int = 0, seconds: Float = 0F, action: () -> Unit) {
ScheduledTaskTracker.addTask( ScheduledTaskTracker.addTask(
@@ -11,4 +12,19 @@ fun after(ticks: Int = 0, seconds: Float = 0F, action: () -> Unit) {
) )
} }
fun lerp(ticks: Int, action: (Float) -> Unit) {
var tick = 0F
action(tick / ticks)
taskBuilder().interval(1).iterations(Int.MAX_VALUE).execute { task ->
tick++
action(tick / ticks)
if (tick >= ticks) {
task.expire()
}
}
}
fun lerp(seconds: Float, action: (Float) -> Unit) = lerp(ticks = (seconds * 20F).roundToInt(), action)
fun taskBuilder() = ScheduledTask.Builder() fun taskBuilder() = ScheduledTask.Builder()
@@ -1,63 +0,0 @@
package com.cablemc.pokemoncobbled.common.entity.animation
import com.cablemc.pokemoncobbled.client.render.pokeball.animation.ModelAnimation
import net.minecraft.client.model.EntityModel
import net.minecraft.world.entity.Entity
import net.minecraftforge.eventbus.api.EventPriority
/**
* Controls which animation an entity is currently running for it's model.
* The idea here is to register animations with a predicate, to determine when the animation should run.
* Any time this is ticked, it will determine if the animation should change or continue based on the predicates.
*
* This likely will need to be expanded upon in the future or refactored.
*
* @author landonjw
* @since December 3, 2021
*/
class AnimationController<T : Entity> {
/**
* Animations registered in the controller based on priority of the animation.
* Low priorities should be for animations that will act as default animations, or animations that will very
* commonly run.
*/
private var animationsByPriority = Array(EventPriority.values().size) { mutableListOf<AnimationRegistry<T, ModelAnimation<out EntityModel<T>>>>() }
/** The current animation that is running. If this is null, there is currently no animation for the model and it will t-pose. */
var currentAnimation: ModelAnimation<out EntityModel<T>>? = null
private set
private var currentAnimationFrame = 0
/**
* Ticks the animation controller for a given entity.
* If given an animation, it will force the animation onto the entity, ticking it if necessary.
* Otherwise, this will determine the appropriate animation based on the priority sets.
*/
fun tick(entity: T, animation: ModelAnimation<EntityModel<T>>? = null) {
val animationToRun = animation ?: getAnimationFromPrioritySet(entity)
if (currentAnimation == animationToRun && animationToRun != null) {
currentAnimationFrame++
} else {
currentAnimationFrame = 0
currentAnimation = animationToRun
}
// TODO: Run the animation frame
}
private fun getAnimationFromPrioritySet(entity: T): ModelAnimation<out EntityModel<T>>? {
for (prioritySet in animationsByPriority) {
for (animationRegistry in prioritySet) {
if (animationRegistry.predicate(entity)) {
return animationRegistry.animation
}
}
}
return null
}
fun <M: EntityModel<T>, A : ModelAnimation<M>> registerAnimation(priority: EventPriority, animation: A, animationPredicate: (T) -> Boolean) {
animationsByPriority[priority.ordinal].add(AnimationRegistry(animationPredicate, animation))
}
}
@@ -1,11 +0,0 @@
package com.cablemc.pokemoncobbled.common.entity.animation
import com.cablemc.pokemoncobbled.client.render.pokeball.animation.ModelAnimation
import net.minecraft.client.model.EntityModel
import net.minecraft.world.entity.Entity
data class AnimationRegistry<T: Entity, A: ModelAnimation<out EntityModel<T>>>(
/** Determines if the animation can run or not. For example, a swim animation should only run if the entity is in water. */
val predicate: (T) -> Boolean,
val animation: A
)
@@ -2,16 +2,24 @@ package com.cablemc.pokemoncobbled.common.entity.pokeball
import com.cablemc.pokemoncobbled.common.api.pokeball.PokeBalls import com.cablemc.pokemoncobbled.common.api.pokeball.PokeBalls
import com.cablemc.pokemoncobbled.common.api.scheduling.after import com.cablemc.pokemoncobbled.common.api.scheduling.after
import com.cablemc.pokemoncobbled.common.api.scheduling.taskBuilder
import com.cablemc.pokemoncobbled.common.api.storage.PokemonStoreManager
import com.cablemc.pokemoncobbled.common.entity.EntityRegistry import com.cablemc.pokemoncobbled.common.entity.EntityRegistry
import com.cablemc.pokemoncobbled.common.entity.pokemon.PokemonEntity import com.cablemc.pokemoncobbled.common.entity.pokemon.PokemonEntity
import com.cablemc.pokemoncobbled.common.pokeball.PokeBall import com.cablemc.pokemoncobbled.common.pokeball.PokeBall
import com.cablemc.pokemoncobbled.common.util.isServerSide
import com.cablemc.pokemoncobbled.common.util.math.toRotations
import com.cablemc.pokemoncobbled.mod.PokemonCobbledMod
import net.minecraft.core.Rotations import net.minecraft.core.Rotations
import net.minecraft.network.syncher.EntityDataSerializers import net.minecraft.network.syncher.EntityDataSerializers
import net.minecraft.network.syncher.SynchedEntityData import net.minecraft.network.syncher.SynchedEntityData
import net.minecraft.server.level.ServerPlayer
import net.minecraft.util.Mth.PI
import net.minecraft.world.entity.EntityType import net.minecraft.world.entity.EntityType
import net.minecraft.world.level.Level import net.minecraft.world.level.Level
import net.minecraft.world.phys.BlockHitResult import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.EntityHitResult import net.minecraft.world.phys.EntityHitResult
import net.minecraft.world.phys.Vec3
class EmptyPokeBallEntity( class EmptyPokeBallEntity(
pokeBall: PokeBall, pokeBall: PokeBall,
@@ -28,20 +36,31 @@ class EmptyPokeBallEntity(
companion object { companion object {
private val CAPTURE_STATE = SynchedEntityData.defineId(EmptyPokeBallEntity::class.java, EntityDataSerializers.BYTE) private val CAPTURE_STATE = SynchedEntityData.defineId(EmptyPokeBallEntity::class.java, EntityDataSerializers.BYTE)
private val HIT_TARGET_POSITION = SynchedEntityData.defineId(EmptyPokeBallEntity::class.java, EntityDataSerializers.ROTATIONS) private val HIT_TARGET_POSITION = SynchedEntityData.defineId(EmptyPokeBallEntity::class.java, EntityDataSerializers.ROTATIONS)
private val HIT_VELOCITY = SynchedEntityData.defineId(EmptyPokeBallEntity::class.java, EntityDataSerializers.ROTATIONS)
private val SHAKE = SynchedEntityData.defineId(EmptyPokeBallEntity::class.java, EntityDataSerializers.BOOLEAN)
const val TICKS_BETWEEN_SHAKES = 40
const val TICKS_BEFORE_SHAKE = 10
} }
var capturingPokemon: PokemonEntity? = null
val captureState = addEntityProperty(CAPTURE_STATE, CaptureState.NOT.ordinal.toByte()) val captureState = addEntityProperty(CAPTURE_STATE, CaptureState.NOT.ordinal.toByte())
val hitTargetPosition = addEntityProperty(HIT_TARGET_POSITION, Rotations(0F, 0F, 0F)) val hitTargetPosition = addEntityProperty(HIT_TARGET_POSITION, Rotations(0F, 0F, 0F))
val hitVelocity = addEntityProperty(HIT_VELOCITY, Rotations(0F, 0F, 0F))
val shakeEmitter = addEntityProperty(SHAKE, false)
override val delegate = if (level.isClientSide) {
com.cablemc.pokemoncobbled.client.entity.EmptyPokeBallClientDelegate()
} else {
EmptyPokeBallServerDelegate()
}
init { init {
delegate.initialize(this) delegate.initialize(this)
captureState.subscribe { state -> captureState.subscribe { state ->
when (CaptureState.values()[state.toInt()]) { when (CaptureState.values()[state.toInt()]) {
CaptureState.NOT -> isNoGravity = false CaptureState.NOT -> isNoGravity = false
CaptureState.HIT -> { CaptureState.HIT -> {}
isNoGravity = true
setDeltaMovement(0.0, 0.0, 0.0)
}
CaptureState.FALL -> isNoGravity = false CaptureState.FALL -> isNoGravity = false
CaptureState.SHAKE -> isNoGravity = false CaptureState.SHAKE -> isNoGravity = false
} }
@@ -51,42 +70,121 @@ class EmptyPokeBallEntity(
private var isAttemptingCatch = false private var isAttemptingCatch = false
constructor(entityType: EntityType<out EmptyPokeBallEntity>, level: Level) : this(PokeBalls.POKE_BALL, entityType, level) constructor(entityType: EntityType<out EmptyPokeBallEntity>, level: Level) : this(PokeBalls.POKE_BALL, entityType, level)
constructor(pokeBall: PokeBall, level: Level) : this(pokeBall, EntityRegistry.EMPTY_POKEBALL.get(), level) constructor(pokeBall: PokeBall, level: Level) : this(pokeBall, EntityRegistry.EMPTY_POKEBALL.get(), level)
override fun onHitBlock(hitResult: BlockHitResult) { override fun onHitBlock(hitResult: BlockHitResult) {
super.onHitBlock(hitResult) if (captureState.get() == CaptureState.NOT.ordinal.toByte()) {
kill() if (level.isServerSide()) {
spawnAtLocation(defaultItem) super.onHitBlock(hitResult)
kill()
spawnAtLocation(defaultItem)
}
} else {
isNoGravity = false
deltaMovement = Vec3.ZERO
isOnGround = true
}
} }
override fun onHitEntity(hitResult: EntityHitResult) { override fun onHitEntity(hitResult: EntityHitResult) {
if (hitResult.entity is PokemonEntity) { if (captureState.get() == CaptureState.NOT.ordinal.toByte()) {
attemptCatch(hitResult.entity as PokemonEntity) if (hitResult.entity is PokemonEntity && level.isServerSide()) {
val pokemon = hitResult.entity as PokemonEntity
if (pokemon.isBeingCaptured || !pokemon.pokemon.isWild()) {
kill()
return
}
capturingPokemon = pokemon
hitVelocity.set(deltaMovement.normalize().toRotations())
hitTargetPosition.set(hitResult.location.toRotations())
attemptCatch(pokemon)
return
}
} }
super.onHitEntity(hitResult) super.onHitEntity(hitResult)
} }
override fun tick() { override fun tick() {
delegate.tick(this) delegate.tick(this)
// if (isAttemptingCatch) {
// setDeltaMovement(0.0, 0.0, 0.0)
// when {
// currentAnimation is OpenAnimation && (currentAnimation as OpenAnimation).isComplete() -> {
// currentAnimation = ShakeAnimation(3)
// }
// }
// }
super.tick() super.tick()
if (level.isServerSide()) {
if (owner == null || !owner!!.isAlive || (captureState.get() != CaptureState.NOT.ordinal.toByte() && capturingPokemon?.isAlive != true)) {
kill()
return
}
if (isOnGround && captureState.get() == CaptureState.FALL.ordinal.toByte()) {
captureState.set(CaptureState.SHAKE.ordinal.toByte())
val captureResult = PokemonCobbledMod.captureCalculator.processCapture(owner as ServerPlayer, capturingPokemon!!.pokemon, pokeBall)
var rollsRemaining = captureResult.numberOfShakes
if (rollsRemaining == 4) {
rollsRemaining--
}
taskBuilder()
.iterations(captureResult.numberOfShakes + 1)
.delay(TICKS_BEFORE_SHAKE)
.interval(TICKS_BETWEEN_SHAKES)
.execute {
if (capturingPokemon?.isAlive != true) {
kill()
}
if (!isAlive) {
it.expire()
return@execute
}
if (rollsRemaining <= 0) {
if (captureResult.isSuccessfulCapture) {
// Do a capture
capturingPokemon!!.kill()
val party = PokemonStoreManager.getParty(this.owner!!.uuid)
party.add(capturingPokemon!!.pokemon)
} else {
breakFree()
}
return@execute
}
rollsRemaining--
shakeEmitter.set(!shakeEmitter.get())
}
.build()
}
}
}
private fun breakFree() {
capturingPokemon!!.setPos(position())
capturingPokemon!!.isInvisible = false
capturingPokemon!!.isBeingCaptured = false
kill()
} }
private fun attemptCatch(pokemonEntity: PokemonEntity) { private fun attemptCatch(pokemonEntity: PokemonEntity) {
// TODO: Validate pokemon is not owned by a player // TODO: Validate pokemon is not owned by a player
if (!isAttemptingCatch) { if (!isAttemptingCatch) {
isAttemptingCatch = true isAttemptingCatch = true
pokemonEntity.isBeingCaptured = true
val displace = deltaMovement
captureState.set(CaptureState.HIT.ordinal.toByte()) captureState.set(CaptureState.HIT.ordinal.toByte())
pokemonEntity.isInvisible = true // TODO this is not the way val mul = if (random.nextBoolean()) 1 else -1
after(seconds = 2F) { captureState.set(CaptureState.FALL.ordinal.toByte()) } deltaMovement = displace.multiply(-1.0, 0.0, -1.0).normalize().yRot(mul * PI/3).multiply(0.1, 0.0, 0.1).add(0.0, 1.0 / 3, 0.0)
after(seconds = 0.7F) {
deltaMovement = Vec3.ZERO
isNoGravity = true
}
after(seconds = 2.2F) {
pokemonEntity.isInvisible = true
pokemonEntity.setPos(position())
// TODO can we turn off collision or something
captureState.set(CaptureState.FALL.ordinal.toByte())
}
} }
} }
} }
@@ -0,0 +1,4 @@
package com.cablemc.pokemoncobbled.common.entity.pokeball
class EmptyPokeBallServerDelegate : PokeBallDelegate<EmptyPokeBallEntity> {
}
@@ -13,6 +13,12 @@ class OccupiedPokeBallEntity(
entityType: EntityType<out OccupiedPokeBallEntity>, entityType: EntityType<out OccupiedPokeBallEntity>,
level: Level level: Level
) : PokeBallEntity(pokeBall, entityType, level) { ) : PokeBallEntity(pokeBall, entityType, level) {
override val delegate = if (level.isClientSide) {
com.cablemc.pokemoncobbled.client.entity.OccupiedPokeBallClientDelegate()
} else {
OccupiedPokeBallServerDelegate()
}
init { init {
delegate.initialize(this) delegate.initialize(this)
} }
@@ -0,0 +1,3 @@
package com.cablemc.pokemoncobbled.common.entity.pokeball
class OccupiedPokeBallServerDelegate : PokeBallDelegate<OccupiedPokeBallEntity>
@@ -2,6 +2,4 @@ package com.cablemc.pokemoncobbled.common.entity.pokeball
import com.cablemc.pokemoncobbled.common.api.entity.EntitySideDelegate import com.cablemc.pokemoncobbled.common.api.entity.EntitySideDelegate
class PokeBallServerDelegate : EntitySideDelegate<PokeBallEntity> { interface PokeBallDelegate<T : PokeBallEntity> : EntitySideDelegate<T>
}
@@ -1,13 +1,13 @@
package com.cablemc.pokemoncobbled.common.entity.pokeball package com.cablemc.pokemoncobbled.common.entity.pokeball
import com.cablemc.pokemoncobbled.client.render.models.blockbench.pokeball.PokeBallModel
import com.cablemc.pokemoncobbled.client.render.pokeball.animation.ModelAnimation
import com.cablemc.pokemoncobbled.common.entity.EntityProperty import com.cablemc.pokemoncobbled.common.entity.EntityProperty
import com.cablemc.pokemoncobbled.common.item.ItemRegistry import com.cablemc.pokemoncobbled.common.item.ItemRegistry
import com.cablemc.pokemoncobbled.common.pokeball.PokeBall import com.cablemc.pokemoncobbled.common.pokeball.PokeBall
import net.minecraft.network.protocol.Packet import net.minecraft.network.protocol.Packet
import net.minecraft.network.syncher.EntityDataAccessor import net.minecraft.network.syncher.EntityDataAccessor
import net.minecraft.world.entity.EntityDimensions
import net.minecraft.world.entity.EntityType import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.Pose
import net.minecraft.world.entity.projectile.ThrowableItemProjectile import net.minecraft.world.entity.projectile.ThrowableItemProjectile
import net.minecraft.world.item.Item import net.minecraft.world.item.Item
import net.minecraft.world.level.Level import net.minecraft.world.level.Level
@@ -18,18 +18,10 @@ abstract class PokeBallEntity(
entityType: EntityType<out PokeBallEntity>, entityType: EntityType<out PokeBallEntity>,
level: Level level: Level
) : ThrowableItemProjectile(entityType, level) { ) : ThrowableItemProjectile(entityType, level) {
val DIMENSIONS = EntityDimensions(0.4F, 0.4F, true)
val entityProperties = mutableListOf<EntityProperty<*>>() val entityProperties = mutableListOf<EntityProperty<*>>()
var currentAnimation: ModelAnimation<PokeBallModel>? = null abstract val delegate: PokeBallDelegate<out PokeBallEntity>
protected set
val delegate = if (level.isClientSide) {
// Don't import because scanning for imports is a CI job we'll do later to detect errant access to client from server
com.cablemc.pokemoncobbled.client.entity.PokeBallClientDelegate()
} else {
PokeBallServerDelegate()
}
override fun getDefaultItem(): Item = ItemRegistry.POKE_BALL.get() override fun getDefaultItem(): Item = ItemRegistry.POKE_BALL.get()
@@ -39,10 +31,10 @@ abstract class PokeBallEntity(
override fun tick() { override fun tick() {
super.tick() super.tick()
delegate.tick(this)
entityProperties.forEach { it.checkForUpdate() } entityProperties.forEach { it.checkForUpdate() }
} }
override fun getDimensions(pPose: Pose) = DIMENSIONS
fun <T> addEntityProperty(accessor: EntityDataAccessor<T>, initialValue: T): EntityProperty<T> { fun <T> addEntityProperty(accessor: EntityDataAccessor<T>, initialValue: T): EntityProperty<T> {
val property = EntityProperty( val property = EntityProperty(
entityData = entityData, entityData = entityData,
@@ -42,6 +42,8 @@ class PokemonEntity(
PokemonServerDelegate() PokemonServerDelegate()
} }
var isBeingCaptured = false
val entityProperties = mutableListOf<EntityProperty<*>>() val entityProperties = mutableListOf<EntityProperty<*>>()
val dexNumber = addEntityProperty(SPECIES_DEX, pokemon.species.nationalPokedexNumber) val dexNumber = addEntityProperty(SPECIES_DEX, pokemon.species.nationalPokedexNumber)
@@ -3,6 +3,7 @@ package com.cablemc.pokemoncobbled.common.item
import com.cablemc.pokemoncobbled.common.entity.pokeball.EmptyPokeBallEntity import com.cablemc.pokemoncobbled.common.entity.pokeball.EmptyPokeBallEntity
import com.cablemc.pokemoncobbled.common.item.creativetabs.PokeBallTab import com.cablemc.pokemoncobbled.common.item.creativetabs.PokeBallTab
import com.cablemc.pokemoncobbled.common.pokeball.PokeBall import com.cablemc.pokemoncobbled.common.pokeball.PokeBall
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResultHolder import net.minecraft.world.InteractionResultHolder
import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.player.Player
@@ -16,7 +17,7 @@ class PokeBallItem(
override fun use(level: Level, player: Player, usedHand: InteractionHand): InteractionResultHolder<ItemStack> { override fun use(level: Level, player: Player, usedHand: InteractionHand): InteractionResultHolder<ItemStack> {
val itemStack = player.getItemInHand(usedHand) val itemStack = player.getItemInHand(usedHand)
if (!level.isClientSide) { if (!level.isClientSide) {
throwPokeBall(level, player) throwPokeBall(level, player as ServerPlayer)
} }
if (!player.abilities.instabuild) { if (!player.abilities.instabuild) {
itemStack.shrink(1) itemStack.shrink(1)
@@ -24,12 +25,12 @@ class PokeBallItem(
return InteractionResultHolder.sidedSuccess(itemStack, level.isClientSide) return InteractionResultHolder.sidedSuccess(itemStack, level.isClientSide)
} }
private fun throwPokeBall(level: Level, player: Player) { private fun throwPokeBall(level: Level, player: ServerPlayer) {
val pokeBallEntity = EmptyPokeBallEntity(pokeBall, player.level).apply { val pokeBallEntity = EmptyPokeBallEntity(pokeBall, player.level).apply {
setPos(player.x, player.y, player.z) setPos(player.x, player.y + 1.5, player.z)
shootFromRotation(player, player.xRot - 7, player.yRot, 0.0f, 1.5f, 1.0f) shootFromRotation(player, player.xRot - 7, player.yRot, 0.0f, 1.5f, 1.0f)
owner = player
} }
level.addFreshEntity(pokeBallEntity) level.addFreshEntity(pokeBallEntity)
} }
} }
@@ -161,5 +161,8 @@ class Pokemon {
private val _level = registerObservable(SimpleObservable<Int>()) { LevelUpdatePacket(this, it) } private val _level = registerObservable(SimpleObservable<Int>()) { LevelUpdatePacket(this, it) }
private val _health = SimpleObservable<Int>() private val _health = SimpleObservable<Int>()
fun getMaxHealth(): Int = TODO("needs to be calculated properly. NOT just the base stat value") val ivHP = 1
val evHP = 1
fun getMaxHealth(): Int = (2 * stats[Stats.HP]!! + ivHP + (evHP / 4) * level) / 100 + level + 10
} }
@@ -2,6 +2,7 @@ package com.cablemc.pokemoncobbled.common.util
import com.cablemc.pokemoncobbled.common.api.reactive.Observable import com.cablemc.pokemoncobbled.common.api.reactive.Observable
import net.minecraft.util.thread.BlockableEventLoop import net.minecraft.util.thread.BlockableEventLoop
import net.minecraft.world.level.Level
import net.minecraftforge.api.distmarker.Dist import net.minecraftforge.api.distmarker.Dist
import net.minecraftforge.fml.DistExecutor import net.minecraftforge.fml.DistExecutor
import net.minecraftforge.fml.LogicalSide import net.minecraftforge.fml.LogicalSide
@@ -42,4 +43,6 @@ internal fun <T> runOnSide(side: LogicalSide, block: () -> T): CompletableFuture
val future = CompletableFuture<T>() val future = CompletableFuture<T>()
LogicalSidedProvider.WORKQUEUE.get<BlockableEventLoop<*>>(side).submit { future.complete(block()) } LogicalSidedProvider.WORKQUEUE.get<BlockableEventLoop<*>>(side).submit { future.complete(block()) }
return future return future
} }
fun Level.isServerSide() = !isClientSide
@@ -1,7 +1,12 @@
package com.cablemc.pokemoncobbled.common.util.math package com.cablemc.pokemoncobbled.common.util.math
import net.minecraft.core.Rotations
import net.minecraft.world.phys.Vec3
import kotlin.math.pow import kotlin.math.pow
infix fun Int.pow(power: Int): Int { infix fun Int.pow(power: Int): Int {
return toDouble().pow(power.toDouble()).toInt() return toDouble().pow(power.toDouble()).toInt()
} }
fun Vec3.toRotations() = Rotations(x.toFloat(), y.toFloat(), z.toFloat())
fun Rotations.toVec3() = Vec3(x.toDouble(), y.toDouble(), z.toDouble())
@@ -5,6 +5,8 @@ import com.cablemc.pokemoncobbled.client.render.models.blockbench.repository.Pok
import com.cablemc.pokemoncobbled.client.render.models.blockbench.repository.PokemonModelRepository import com.cablemc.pokemoncobbled.client.render.models.blockbench.repository.PokemonModelRepository
import com.cablemc.pokemoncobbled.common.CommandRegistrar import com.cablemc.pokemoncobbled.common.CommandRegistrar
import com.cablemc.pokemoncobbled.common.PokemonCobbled import com.cablemc.pokemoncobbled.common.PokemonCobbled
import com.cablemc.pokemoncobbled.common.api.pokeball.catching.calculators.CaptureCalculator
import com.cablemc.pokemoncobbled.common.api.pokeball.catching.calculators.Gen7CaptureCalculator
import com.cablemc.pokemoncobbled.common.api.pokemon.PokemonSpecies import com.cablemc.pokemoncobbled.common.api.pokemon.PokemonSpecies
import com.cablemc.pokemoncobbled.common.api.scheduling.ScheduledTaskListener import com.cablemc.pokemoncobbled.common.api.scheduling.ScheduledTaskListener
import com.cablemc.pokemoncobbled.common.api.storage.PokemonStoreManager import com.cablemc.pokemoncobbled.common.api.storage.PokemonStoreManager
@@ -38,6 +40,7 @@ import thedarkcolour.kotlinforforge.forge.MOD_CONTEXT
object PokemonCobbledMod { object PokemonCobbledMod {
val LOGGER = LogManager.getLogger() val LOGGER = LogManager.getLogger()
val EVENT_BUS = BusBuilder.builder().build() val EVENT_BUS = BusBuilder.builder().build()
var captureCalculator: CaptureCalculator = Gen7CaptureCalculator()
init { init {
with(MOD_CONTEXT.getKEventBus()) { with(MOD_CONTEXT.getKEventBus()) {
@@ -96,15 +99,4 @@ object PokemonCobbledMod {
fun on(event: EntityAttributeCreationEvent) { fun on(event: EntityAttributeCreationEvent) {
EntityRegistry.registerAttributes(event) EntityRegistry.registerAttributes(event)
} }
// // You can use EventBusSubscriber to automatically subscribe events on the contained class (this is subscribing to the MOD
// // Event bus for receiving Registry Events)
// @EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD)
// object RegistryEvents {
// @SubscribeEvent
// fun onBlocksRegistry(blockRegistryEvent: Register<Block?>?) {
// // register a new block here
// LOGGER.info("HELLO from Register Block")
// }
// }
} }
@@ -0,0 +1,61 @@
package com.cablemc.pokemoncobbled.client.render.models.blockbench.animation
import com.cablemc.pokemoncobbled.common.util.math.geometry.toDegrees
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.math.Vector3f
import net.minecraft.util.Mth.PI
import org.junit.jupiter.api.Test
import kotlin.math.acos
import kotlin.math.asin
import kotlin.math.atan2
import kotlin.math.cos
internal class RootPokeBallLookAnimationTest {
@Test
fun `attempts at getting rotation logic working, i am not done trying angryface`() {
val xRot = PI / 3
val yRot = PI / 3
val zRot = 0F
val desiredZ = PI / 3
val poseStack = PoseStack()
poseStack.mulPose(Vector3f.ZP.rotation(zRot))
poseStack.mulPose(Vector3f.YP.rotation(yRot))
poseStack.mulPose(Vector3f.XP.rotation(xRot))
val zAxis = Vector3f.ZP.copy()
zAxis.transform(poseStack.last().normal())
poseStack.mulPose(zAxis.rotation(desiredZ))
val y = Vector3f.YP.copy()
y.transform(poseStack.last().normal())
val py1 = y.x()
val z = Vector3f.ZP.copy()
z.transform(poseStack.last().normal())
val pz1 = z.x()
val pz3 = z.z()
// val beta = atan2(d.y(), d.x())
// val alpha = asin(d.z())
val beta = asin(pz1)
val alpha = acos(pz3 / cos(beta))
val gamma = asin(-py1 / cos(beta))
val poseStack2 = PoseStack()
poseStack2.mulPose(Vector3f.ZP.rotation(gamma))
poseStack2.mulPose(Vector3f.YP.rotation(beta))
poseStack2.mulPose(Vector3f.XP.rotation(alpha))
val y2 = Vector3f(3F, 0F, -4F)
y2.transform(poseStack.last().normal())
println(y2)
val alphaD = alpha.toDegrees()
val betaD = beta.toDegrees()
val gammaD = gamma.toDegrees()
println("xRot: $alphaD, yRot: $betaD, zRot: $gammaD")
}
}