forked from mirrors/cobblemon
Completed pokeball capturing outside of battle, improved pokeball collision and motion, added some useful maths for parabolas
This commit is contained in:
Binary file not shown.
+34
@@ -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 -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
@@ -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 -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-1
@@ -98,7 +98,6 @@ abstract class PoseableEntityModel<T : Entity> : EntityModel<T>(), ModelFrame {
|
||||
if (previousPose != null && pose.transformTicks > 0) {
|
||||
state.statefulAnimations.add(
|
||||
PoseTransitionAnimation(
|
||||
frame = this,
|
||||
beforePose = previousPose,
|
||||
afterPose = pose,
|
||||
durationTicks = pose.transformTicks
|
||||
|
||||
+2
-2
@@ -10,7 +10,7 @@ import net.minecraft.world.entity.Entity
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @author Hiroku
|
||||
@@ -49,7 +49,7 @@ abstract class PoseableEntityState<T : Entity> {
|
||||
return
|
||||
}
|
||||
|
||||
val animation = PoseTransitionAnimation(model, beforePose, afterPose, durationTicks)
|
||||
val animation = PoseTransitionAnimation(beforePose, afterPose, durationTicks)
|
||||
statefulAnimations.add(animation)
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -16,11 +16,10 @@ import java.lang.Float.min
|
||||
* @since December 5th, 2021
|
||||
*/
|
||||
class PoseTransitionAnimation<T : Entity>(
|
||||
frame: ModelFrame,
|
||||
beforePose: Pose<T, *>,
|
||||
val afterPose: Pose<T, *>,
|
||||
private val durationTicks: Int = 20
|
||||
) : StatefulAnimation<T, ModelFrame>(frame) {
|
||||
) : StatefulAnimation<T, ModelFrame> {
|
||||
var passedTicks = 0F
|
||||
|
||||
val transforms = mutableListOf<GradualTransform>()
|
||||
|
||||
+31
@@ -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
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -12,14 +12,14 @@ import net.minecraft.world.entity.Entity
|
||||
* @author Hiroku
|
||||
* @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.
|
||||
*
|
||||
* This is for cases where this animation and the idle animation work on the same parts
|
||||
* of the model and would conflict.
|
||||
*/
|
||||
abstract 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. */
|
||||
abstract fun run(entity: T, model: PoseableEntityModel<T>): Boolean
|
||||
fun preventsIdle(entity: T, idleAnimation: StatelessAnimation<T, *>): Boolean
|
||||
/** Runs the animation. You can check that the model fits a particular frame. Returns true if the animation should continue. */
|
||||
fun run(entity: T, model: PoseableEntityModel<T>): Boolean
|
||||
}
|
||||
+1
@@ -3,5 +3,6 @@ package com.cablemc.pokemoncobbled.client.render.models.blockbench.frame
|
||||
import net.minecraft.client.model.geom.ModelPart
|
||||
|
||||
interface PokeBallFrame : ModelFrame {
|
||||
val subRoot: ModelPart
|
||||
val lid: ModelPart
|
||||
}
|
||||
+38
-9
@@ -1,11 +1,15 @@
|
||||
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.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.getChildOf
|
||||
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.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 net.minecraft.client.model.geom.ModelLayerLocation
|
||||
import net.minecraft.client.model.geom.ModelPart
|
||||
@@ -18,18 +22,38 @@ import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.util.Mth.PI
|
||||
|
||||
class PokeBallModel(root: ModelPart) : PoseableEntityModel<PokeBallEntity>(), PokeBallFrame {
|
||||
override val rootPart = registerRelevantPart(root.getChild("pokeball"))
|
||||
override val lid = registerRelevantPart(root.getChild("pokeball").getChild("pokeball_lid"))
|
||||
override val rootPart = registerRelevantPart(root.getChild("root"))
|
||||
override val subRoot = registerRelevantPart(rootPart.getChild("pokeball"))
|
||||
override val lid = registerRelevantPart(subRoot.getChild("pokeball_lid"))
|
||||
|
||||
override fun registerPoses() {
|
||||
registerPose(
|
||||
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(
|
||||
rootPart.rotation(
|
||||
function = { t -> t * PI / 10 }, // 1 rotation per second = 2pi per 20 ticks = 2pi / 20 = pi / 10 per tick
|
||||
axis = Y_AXIS,
|
||||
timeVariable = { _, _, ageInTicks -> ageInTicks }
|
||||
),
|
||||
rootPart.translation(
|
||||
function = { t ->
|
||||
if (t > 8) {
|
||||
0F
|
||||
} else {
|
||||
-(8F - t) * 3F
|
||||
}
|
||||
},
|
||||
axis = Y_AXIS,
|
||||
timeVariable = { _, _, ageInTicks -> ageInTicks }
|
||||
)
|
||||
),
|
||||
transformedParts = arrayOf()
|
||||
@@ -38,15 +62,20 @@ class PokeBallModel(root: ModelPart) : PoseableEntityModel<PokeBallEntity>(), Po
|
||||
|
||||
companion object {
|
||||
// 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 {
|
||||
val meshdefinition = MeshDefinition()
|
||||
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",
|
||||
CubeListBuilder.create().texOffs(0, 0)
|
||||
.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(
|
||||
"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>
|
||||
}
|
||||
+57
-1
@@ -1,8 +1,11 @@
|
||||
package com.cablemc.pokemoncobbled.client.render.models.blockbench.wavefunction
|
||||
|
||||
import net.minecraft.util.Mth.PI
|
||||
import net.minecraft.util.Mth.sqrt
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sign
|
||||
import kotlin.math.sin
|
||||
|
||||
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.times(other: WaveFunction): WaveFunction = { t -> this(t) * other(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 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 ->
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -30,9 +30,9 @@ class PokeBallRenderer<T : PokeBallEntity>(context: EntityRendererProvider.Conte
|
||||
) {
|
||||
val model = PokeBallModelRepository.getModel(pEntity.pokeBall).entityModel
|
||||
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)
|
||||
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)
|
||||
pMatrixStack.popPose()
|
||||
super.render(pEntity, pEntityYaw, pPartialTicks, pMatrixStack, pBuffer, pPackedLight)
|
||||
|
||||
-15
@@ -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()
|
||||
|
||||
}
|
||||
+46
-16
@@ -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.StatelessAnimation
|
||||
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.toVec3
|
||||
import net.minecraft.commands.arguments.EntityAnchorArgument
|
||||
import net.minecraft.util.Mth
|
||||
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 {
|
||||
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
|
||||
override fun preventsIdle(entity: PokeBallEntity, idleAnimation: StatelessAnimation<PokeBallEntity, *>) = true
|
||||
override fun run(entity: PokeBallEntity, model: PoseableEntityModel<PokeBallEntity>): Boolean {
|
||||
var startedClosing = false
|
||||
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) {
|
||||
model.getState(entity).animationSeconds = 0F
|
||||
initialized = true
|
||||
@@ -23,19 +43,29 @@ class OpenAnimation(frame: PokeBallFrame) : StatefulAnimation<PokeBallEntity, Po
|
||||
|
||||
val animationSeconds = model.getState(entity).animationSeconds
|
||||
|
||||
when {
|
||||
animationSeconds <= 1 -> {
|
||||
frame.lid.xRot = -animationSeconds * PI / 3
|
||||
}
|
||||
animationSeconds > 2 -> {
|
||||
frame.lid.xRot = -30f.toRadians() + ((animationSeconds - 2) * (1.5f).toRadians()) / 20F
|
||||
}
|
||||
else -> {
|
||||
frame.lid.xRot = 30f.toRadians()
|
||||
return false
|
||||
entity.lookAt(EntityAnchorArgument.Anchor.EYES, entity.hitTargetPosition.get().toVec3())
|
||||
val xDist = entity.hitTargetPosition.get().x - entity.x
|
||||
val zDist = entity.hitTargetPosition.get().z - entity.z
|
||||
frame.rootPart.yRot = Mth.atan2(-zDist, xDist).toFloat() + PI / 2
|
||||
frame.rootPart.xRot = -entity.xRotO.toRadians() - PI / 2
|
||||
|
||||
val minPitch = -PI
|
||||
|
||||
if (animationSeconds >= OPEN_START && animationSeconds < CLOSE_START) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
+42
-38
@@ -1,47 +1,51 @@
|
||||
package com.cablemc.pokemoncobbled.client.render.pokeball.animation
|
||||
|
||||
import com.cablemc.pokemoncobbled.client.render.models.blockbench.pokeball.PokeBallModel
|
||||
import com.cablemc.pokemoncobbled.common.util.math.geometry.toRadians
|
||||
import net.minecraft.util.Mth
|
||||
import com.cablemc.pokemoncobbled.client.render.models.blockbench.PoseableEntityModel
|
||||
import com.cablemc.pokemoncobbled.client.render.models.blockbench.addRotation
|
||||
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
|
||||
private set
|
||||
private var shakesComplete = 0
|
||||
var initialized = false
|
||||
|
||||
override fun animate(
|
||||
model: PokeBallModel,
|
||||
limbSwing: Float,
|
||||
limbSwingAmout: Float,
|
||||
ageInTicks: Float,
|
||||
netHeadYaw: Float,
|
||||
headPitch: Float,
|
||||
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
|
||||
}
|
||||
override fun preventsIdle(entity: EmptyPokeBallEntity, idleAnimation: StatelessAnimation<EmptyPokeBallEntity, *>) = false
|
||||
override fun run(entity: EmptyPokeBallEntity, model: PoseableEntityModel<EmptyPokeBallEntity>): Boolean {
|
||||
val frame = model as PokeBallFrame
|
||||
val state = model.getState(entity)
|
||||
if (!initialized) {
|
||||
state.animationSeconds = 0F
|
||||
initialized = true
|
||||
}
|
||||
else {
|
||||
model.rootPart.zRot = 0f
|
||||
}
|
||||
}
|
||||
|
||||
override fun resetAnimation() {
|
||||
currentFrame = 0
|
||||
frame.subRoot.addRotation(axis = axis, differenceInRadians = shakeFunction(state.animationSeconds))
|
||||
|
||||
return state.animationSeconds < 1F
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -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.pokemon.Pokemon
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import kotlin.math.pow
|
||||
import kotlin.random.Random.Default.nextInt
|
||||
|
||||
/**
|
||||
@@ -22,7 +23,7 @@ class Gen7CaptureCalculator : CaptureCalculator {
|
||||
return CaptureContext(isSuccessfulCapture = true, isCriticalCapture = true, numberOfShakes = 1)
|
||||
}
|
||||
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
|
||||
for (i in 0..3) {
|
||||
@@ -31,13 +32,13 @@ class Gen7CaptureCalculator : CaptureCalculator {
|
||||
}
|
||||
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 {
|
||||
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 currentHealth = pokemon.health
|
||||
val statusBonus = getStatusBonus(pokemon)
|
||||
|
||||
+16
@@ -1,6 +1,7 @@
|
||||
package com.cablemc.pokemoncobbled.common.api.scheduling
|
||||
|
||||
import net.minecraft.util.Mth.ceil
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun after(ticks: Int = 0, seconds: Float = 0F, action: () -> Unit) {
|
||||
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()
|
||||
-63
@@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
-11
@@ -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
|
||||
)
|
||||
+118
-20
@@ -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.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.pokemon.PokemonEntity
|
||||
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.network.syncher.EntityDataSerializers
|
||||
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.level.Level
|
||||
import net.minecraft.world.phys.BlockHitResult
|
||||
import net.minecraft.world.phys.EntityHitResult
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class EmptyPokeBallEntity(
|
||||
pokeBall: PokeBall,
|
||||
@@ -28,20 +36,31 @@ class EmptyPokeBallEntity(
|
||||
companion object {
|
||||
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_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 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 {
|
||||
delegate.initialize(this)
|
||||
captureState.subscribe { state ->
|
||||
when (CaptureState.values()[state.toInt()]) {
|
||||
CaptureState.NOT -> isNoGravity = false
|
||||
CaptureState.HIT -> {
|
||||
isNoGravity = true
|
||||
setDeltaMovement(0.0, 0.0, 0.0)
|
||||
}
|
||||
CaptureState.HIT -> {}
|
||||
CaptureState.FALL -> isNoGravity = false
|
||||
CaptureState.SHAKE -> isNoGravity = false
|
||||
}
|
||||
@@ -51,42 +70,121 @@ class EmptyPokeBallEntity(
|
||||
private var isAttemptingCatch = false
|
||||
|
||||
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)
|
||||
|
||||
override fun onHitBlock(hitResult: BlockHitResult) {
|
||||
super.onHitBlock(hitResult)
|
||||
kill()
|
||||
spawnAtLocation(defaultItem)
|
||||
if (captureState.get() == CaptureState.NOT.ordinal.toByte()) {
|
||||
if (level.isServerSide()) {
|
||||
super.onHitBlock(hitResult)
|
||||
kill()
|
||||
spawnAtLocation(defaultItem)
|
||||
}
|
||||
} else {
|
||||
isNoGravity = false
|
||||
deltaMovement = Vec3.ZERO
|
||||
isOnGround = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onHitEntity(hitResult: EntityHitResult) {
|
||||
if (hitResult.entity is PokemonEntity) {
|
||||
attemptCatch(hitResult.entity as PokemonEntity)
|
||||
if (captureState.get() == CaptureState.NOT.ordinal.toByte()) {
|
||||
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)
|
||||
}
|
||||
|
||||
override fun tick() {
|
||||
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()
|
||||
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) {
|
||||
// TODO: Validate pokemon is not owned by a player
|
||||
if (!isAttemptingCatch) {
|
||||
isAttemptingCatch = true
|
||||
pokemonEntity.isBeingCaptured = true
|
||||
val displace = deltaMovement
|
||||
captureState.set(CaptureState.HIT.ordinal.toByte())
|
||||
pokemonEntity.isInvisible = true // TODO this is not the way
|
||||
after(seconds = 2F) { captureState.set(CaptureState.FALL.ordinal.toByte()) }
|
||||
val mul = if (random.nextBoolean()) 1 else -1
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.cablemc.pokemoncobbled.common.entity.pokeball
|
||||
|
||||
class EmptyPokeBallServerDelegate : PokeBallDelegate<EmptyPokeBallEntity> {
|
||||
}
|
||||
+6
@@ -13,6 +13,12 @@ class OccupiedPokeBallEntity(
|
||||
entityType: EntityType<out OccupiedPokeBallEntity>,
|
||||
level: Level
|
||||
) : PokeBallEntity(pokeBall, entityType, level) {
|
||||
override val delegate = if (level.isClientSide) {
|
||||
com.cablemc.pokemoncobbled.client.entity.OccupiedPokeBallClientDelegate()
|
||||
} else {
|
||||
OccupiedPokeBallServerDelegate()
|
||||
}
|
||||
|
||||
init {
|
||||
delegate.initialize(this)
|
||||
}
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.cablemc.pokemoncobbled.common.entity.pokeball
|
||||
|
||||
class OccupiedPokeBallServerDelegate : PokeBallDelegate<OccupiedPokeBallEntity>
|
||||
+1
-3
@@ -2,6 +2,4 @@ package com.cablemc.pokemoncobbled.common.entity.pokeball
|
||||
|
||||
import com.cablemc.pokemoncobbled.common.api.entity.EntitySideDelegate
|
||||
|
||||
class PokeBallServerDelegate : EntitySideDelegate<PokeBallEntity> {
|
||||
|
||||
}
|
||||
interface PokeBallDelegate<T : PokeBallEntity> : EntitySideDelegate<T>
|
||||
+5
-13
@@ -1,13 +1,13 @@
|
||||
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.item.ItemRegistry
|
||||
import com.cablemc.pokemoncobbled.common.pokeball.PokeBall
|
||||
import net.minecraft.network.protocol.Packet
|
||||
import net.minecraft.network.syncher.EntityDataAccessor
|
||||
import net.minecraft.world.entity.EntityDimensions
|
||||
import net.minecraft.world.entity.EntityType
|
||||
import net.minecraft.world.entity.Pose
|
||||
import net.minecraft.world.entity.projectile.ThrowableItemProjectile
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.level.Level
|
||||
@@ -18,18 +18,10 @@ abstract class PokeBallEntity(
|
||||
entityType: EntityType<out PokeBallEntity>,
|
||||
level: Level
|
||||
) : ThrowableItemProjectile(entityType, level) {
|
||||
|
||||
val DIMENSIONS = EntityDimensions(0.4F, 0.4F, true)
|
||||
val entityProperties = mutableListOf<EntityProperty<*>>()
|
||||
|
||||
var currentAnimation: ModelAnimation<PokeBallModel>? = null
|
||||
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()
|
||||
}
|
||||
abstract val delegate: PokeBallDelegate<out PokeBallEntity>
|
||||
|
||||
override fun getDefaultItem(): Item = ItemRegistry.POKE_BALL.get()
|
||||
|
||||
@@ -39,10 +31,10 @@ abstract class PokeBallEntity(
|
||||
|
||||
override fun tick() {
|
||||
super.tick()
|
||||
delegate.tick(this)
|
||||
entityProperties.forEach { it.checkForUpdate() }
|
||||
}
|
||||
|
||||
override fun getDimensions(pPose: Pose) = DIMENSIONS
|
||||
fun <T> addEntityProperty(accessor: EntityDataAccessor<T>, initialValue: T): EntityProperty<T> {
|
||||
val property = EntityProperty(
|
||||
entityData = entityData,
|
||||
|
||||
@@ -42,6 +42,8 @@ class PokemonEntity(
|
||||
PokemonServerDelegate()
|
||||
}
|
||||
|
||||
var isBeingCaptured = false
|
||||
|
||||
val entityProperties = mutableListOf<EntityProperty<*>>()
|
||||
|
||||
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.item.creativetabs.PokeBallTab
|
||||
import com.cablemc.pokemoncobbled.common.pokeball.PokeBall
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.InteractionResultHolder
|
||||
import net.minecraft.world.entity.player.Player
|
||||
@@ -16,7 +17,7 @@ class PokeBallItem(
|
||||
override fun use(level: Level, player: Player, usedHand: InteractionHand): InteractionResultHolder<ItemStack> {
|
||||
val itemStack = player.getItemInHand(usedHand)
|
||||
if (!level.isClientSide) {
|
||||
throwPokeBall(level, player)
|
||||
throwPokeBall(level, player as ServerPlayer)
|
||||
}
|
||||
if (!player.abilities.instabuild) {
|
||||
itemStack.shrink(1)
|
||||
@@ -24,12 +25,12 @@ class PokeBallItem(
|
||||
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 {
|
||||
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)
|
||||
owner = player
|
||||
}
|
||||
level.addFreshEntity(pokeBallEntity)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -161,5 +161,8 @@ class Pokemon {
|
||||
private val _level = registerObservable(SimpleObservable<Int>()) { LevelUpdatePacket(this, it) }
|
||||
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 net.minecraft.util.thread.BlockableEventLoop
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraftforge.api.distmarker.Dist
|
||||
import net.minecraftforge.fml.DistExecutor
|
||||
import net.minecraftforge.fml.LogicalSide
|
||||
@@ -42,4 +43,6 @@ internal fun <T> runOnSide(side: LogicalSide, block: () -> T): CompletableFuture
|
||||
val future = CompletableFuture<T>()
|
||||
LogicalSidedProvider.WORKQUEUE.get<BlockableEventLoop<*>>(side).submit { future.complete(block()) }
|
||||
return future
|
||||
}
|
||||
}
|
||||
|
||||
fun Level.isServerSide() = !isClientSide
|
||||
@@ -1,7 +1,12 @@
|
||||
package com.cablemc.pokemoncobbled.common.util.math
|
||||
|
||||
import net.minecraft.core.Rotations
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.pow
|
||||
|
||||
infix fun Int.pow(power: Int): Int {
|
||||
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.common.CommandRegistrar
|
||||
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.scheduling.ScheduledTaskListener
|
||||
import com.cablemc.pokemoncobbled.common.api.storage.PokemonStoreManager
|
||||
@@ -38,6 +40,7 @@ import thedarkcolour.kotlinforforge.forge.MOD_CONTEXT
|
||||
object PokemonCobbledMod {
|
||||
val LOGGER = LogManager.getLogger()
|
||||
val EVENT_BUS = BusBuilder.builder().build()
|
||||
var captureCalculator: CaptureCalculator = Gen7CaptureCalculator()
|
||||
|
||||
init {
|
||||
with(MOD_CONTEXT.getKEventBus()) {
|
||||
@@ -96,15 +99,4 @@ object PokemonCobbledMod {
|
||||
fun on(event: EntityAttributeCreationEvent) {
|
||||
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")
|
||||
// }
|
||||
// }
|
||||
}
|
||||
+61
@@ -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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user