forked from mirrors/cobblemon
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4716b8e4fd | ||
|
|
1ef62a5a73 | ||
|
|
89f1f53664 | ||
|
|
62a7e1498b | ||
|
|
89b913fab7 | ||
|
|
d40317d9e0 | ||
|
|
91395461c8 | ||
|
|
505fb14b2a | ||
|
|
a8f2d15cea |
+1
-3
@@ -34,9 +34,7 @@ public class AbstractClientPlayerMixin {
|
||||
}
|
||||
|
||||
LocalPlayer player = (LocalPlayer) (Object) this;
|
||||
if (player != null && player.isPassenger() && player.getVehicle() instanceof PokemonEntity) {
|
||||
PokemonEntity ride = (PokemonEntity) player.getVehicle();
|
||||
|
||||
if (player != null && player.isPassenger() && player.getVehicle() instanceof PokemonEntity ride) {
|
||||
//Return custom fov mult
|
||||
cir.setReturnValue(ride.rideFovMult());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin.invoker;
|
||||
|
||||
import net.minecraft.world.level.GameRules;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(GameRules.IntegerValue.class)
|
||||
public interface IntegerRuleInvoker {
|
||||
@Invoker("create")
|
||||
static GameRules.Type<GameRules.IntegerValue> cobblemon$create(int defaultValue) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin.pokeball;
|
||||
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.projectile.Projectile;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(Projectile.class)
|
||||
public abstract class ProjectileMixin extends Entity {
|
||||
public ProjectileMixin(EntityType<? extends Projectile> type, Level world) {
|
||||
super(type, world);
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin.pokeball;
|
||||
|
||||
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeballMarker;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.projectile.ThrowableProjectile;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(ThrowableProjectile.class)
|
||||
public abstract class ThrowableProjectileMixin extends ProjectileMixin {
|
||||
public ThrowableProjectileMixin(EntityType<? extends ThrowableProjectile> type, Level world) {
|
||||
super(type, world);
|
||||
}
|
||||
|
||||
@Inject(method = "tick", at = @At("HEAD"), cancellable = true)
|
||||
private void cobblemon$tickPokeballs(CallbackInfo ci) {
|
||||
/// [ThrowableProjectile.tick] does a lot of things we actively do *not* want it to do. However its
|
||||
/// superclasses do a lot of stuff in tick() that we *need* them to do. Ergo we inject this tiny mixin which
|
||||
/// will check in essence `this instanceof EmptyPokeballEntity` and then call the minimal super.tick() && return
|
||||
/// marker.isBouncy() is required since we may not have bouncy balls all the time and if we don't then this
|
||||
/// mixin will break stuff.
|
||||
if (this instanceof EmptyPokeballMarker) {
|
||||
super.tick();
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin.pokeball;
|
||||
|
||||
import com.cobblemon.mod.common.entity.pokeball.WaterDragModifier;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.projectile.ThrowableProjectile;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
|
||||
@Mixin(ThrowableProjectile.class)
|
||||
public abstract class ThrownEntityMixin extends Entity {
|
||||
|
||||
public ThrownEntityMixin(EntityType<?> type, Level world) {
|
||||
super(type, world);
|
||||
}
|
||||
|
||||
@ModifyVariable(method = "tick", at = @At("STORE"), ordinal = 0)
|
||||
private float cobblemon$waterDragModifier(float value) {
|
||||
if (this.isInWater() && this instanceof WaterDragModifier) {
|
||||
return ((WaterDragModifier) this).waterDrag();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -47,7 +47,8 @@ object CobblemonEntities : PlatformRegistry<Registry<EntityType<*>>, ResourceKey
|
||||
@JvmField
|
||||
val EMPTY_POKEBALL: EntityType<EmptyPokeBallEntity> = this.create(
|
||||
EMPTY_POKEBALL_KEY.path,
|
||||
EntityType.Builder.of({ _, world -> EmptyPokeBallEntity(PokeBalls.POKE_BALL, world) }, MobCategory.MISC)
|
||||
EntityType.Builder.of(::EmptyPokeBallEntity, MobCategory.MISC)
|
||||
.sized(EmptyPokeBallEntity.DIMENSIONS.width, EmptyPokeBallEntity.DIMENSIONS.height)
|
||||
.build(EMPTY_POKEBALL_KEY.toString())
|
||||
)
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ object PokeBalls : JsonDataRegistry<PokeBall> {
|
||||
createDefault("moon_ball", CatchRateModifiers.MOON_PHASES)
|
||||
createDefault("sport_ball", MultiplierModifier(1.5F))
|
||||
createDefault("net_ball", CatchRateModifiers.typeBoosting(3F, ElementalTypes.BUG, ElementalTypes.WATER))
|
||||
createDefault("dive_ball", CatchRateModifiers.SUBMERGED_IN_WATER, waterDragValue = 0.99F)
|
||||
createDefault("dive_ball", CatchRateModifiers.SUBMERGED_IN_WATER, waterDragValue = 0.99)
|
||||
createDefault("nest_ball", CatchRateModifiers.NEST)
|
||||
createDefault("repeat_ball", CatchRateModifiers.REPEAT)
|
||||
createDefault("timer_ball", CatchRateModifiers.turnBased { turn -> (1F * turn * (1229F / 4096F)).coerceAtMost(4F) })
|
||||
@@ -192,12 +192,12 @@ object PokeBalls : JsonDataRegistry<PokeBall> {
|
||||
createDefault("ancient_ivory_ball", ancient = true)
|
||||
createDefault("ancient_great_ball", MultiplierModifier(1.5F), ancient = true)
|
||||
createDefault("ancient_ultra_ball", MultiplierModifier(2F), ancient = true)
|
||||
createDefault("ancient_heavy_ball", throwPower = 0.75f, ancient = true)
|
||||
createDefault("ancient_leaden_ball", throwPower = 0.75f, ancient = true)
|
||||
createDefault("ancient_gigaton_ball", throwPower = 0.75f, ancient = true)
|
||||
createDefault("ancient_feather_ball", throwPower = 2.5f, ancient = true)
|
||||
createDefault("ancient_wing_ball", throwPower = 2.5f, ancient = true)
|
||||
createDefault("ancient_jet_ball", throwPower = 2.5f, ancient = true)
|
||||
createDefault("ancient_heavy_ball", throwPower = 0.75f, ancient = true, waterDragValue = 0.9)
|
||||
createDefault("ancient_leaden_ball", throwPower = 0.75f, ancient = true, waterDragValue = 0.9)
|
||||
createDefault("ancient_gigaton_ball", throwPower = 0.75f, ancient = true, waterDragValue = 0.9)
|
||||
createDefault("ancient_feather_ball", throwPower = 2.5f, ancient = true, waterDragValue = 0.7)
|
||||
createDefault("ancient_wing_ball", throwPower = 2.5f, ancient = true, waterDragValue = 0.7)
|
||||
createDefault("ancient_jet_ball", throwPower = 2.5f, ancient = true, waterDragValue = 0.7)
|
||||
createDefault("ancient_origin_ball", GuaranteedModifier(), ancient = true)
|
||||
// Luxury ball effect, low priority as it must be triggered before soothe bell as of gen 4
|
||||
CobblemonEvents.FRIENDSHIP_UPDATED.subscribe(priority = Priority.LOW) { event ->
|
||||
@@ -233,15 +233,17 @@ object PokeBalls : JsonDataRegistry<PokeBall> {
|
||||
name: String,
|
||||
modifier: CatchRateModifier = MultiplierModifier(1F) { _, _ -> true },
|
||||
effects: List<CaptureEffect> = emptyList(),
|
||||
waterDragValue: Float = 0.8F,
|
||||
waterDragValue: Double = 0.8,
|
||||
model2d: ResourceLocation = cobblemonResource(name),
|
||||
model3d: ResourceLocation = cobblemonResource("item/${name}_model"),
|
||||
throwPower: Float = 1.25f,
|
||||
ancient: Boolean = false
|
||||
ancient: Boolean = false,
|
||||
maxBounces: Int = 1,
|
||||
bouncyness: Double = 0.4,
|
||||
): PokeBall {
|
||||
val identifier = cobblemonResource(name)
|
||||
//val finalModifiers = if (appendUltraBeastPenalty) modifiers + listOf(LabelModifier(0.1F, true, CobblemonPokemonLabels.ULTRA_BEAST)) else modifiers
|
||||
val pokeball = PokeBall(identifier, modifier, effects, waterDragValue, model2d, model3d, throwPower, ancient)
|
||||
val pokeball = PokeBall(identifier, modifier, effects, waterDragValue, model2d, model3d, throwPower, ancient, maxBounces, bouncyness)
|
||||
this.defaults[identifier] = pokeball
|
||||
return pokeball
|
||||
}
|
||||
|
||||
+291
-20
@@ -67,14 +67,18 @@ import net.minecraft.world.entity.*
|
||||
import net.minecraft.world.entity.decoration.LeashFenceKnotEntity
|
||||
import net.minecraft.world.entity.projectile.ThrowableItemProjectile
|
||||
import net.minecraft.world.item.Item
|
||||
import net.minecraft.world.level.ClipContext
|
||||
import net.minecraft.world.level.ClipContext.Block
|
||||
import net.minecraft.world.level.ClipContext.Fluid
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.phys.BlockHitResult
|
||||
import net.minecraft.world.phys.EntityHitResult
|
||||
import net.minecraft.world.phys.HitResult
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import net.minecraft.world.phys.*
|
||||
import org.joml.Quaterniond
|
||||
import org.joml.Vector3d
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.function.Predicate
|
||||
import kotlin.math.*
|
||||
|
||||
class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragModifier, Schedulable {
|
||||
class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, EmptyPokeballMarker, Schedulable {
|
||||
enum class CaptureState {
|
||||
NOT,
|
||||
HIT,
|
||||
@@ -91,11 +95,84 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
val HIT_VELOCITY = SynchedEntityData.defineId(EmptyPokeBallEntity::class.java, Vec3DataSerializer)
|
||||
val SHAKE = SynchedEntityData.defineId(EmptyPokeBallEntity::class.java, EntityDataSerializers.BOOLEAN)
|
||||
val ASPECTS = SynchedEntityData.defineId(EmptyPokeBallEntity::class.java, StringSetDataSerializer)
|
||||
val BOUNCES = SynchedEntityData.defineId(EmptyPokeBallEntity::class.java, EntityDataSerializers.INT)
|
||||
|
||||
// GAMERULE CONFIGURABLE
|
||||
val MAX_BOUNCES = SynchedEntityData.defineId(EmptyPokeBallEntity::class.java, EntityDataSerializers.INT)
|
||||
val BOUNCYNESS = SynchedEntityData.defineId(EmptyPokeBallEntity::class.java, EntityDataSerializers.INT)
|
||||
val BOUNCE_DRAG = SynchedEntityData.defineId(EmptyPokeBallEntity::class.java, EntityDataSerializers.INT)
|
||||
val WATER_SKIP_FACTOR = SynchedEntityData.defineId(EmptyPokeBallEntity::class.java, EntityDataSerializers.INT)
|
||||
// END GAMERULE CONFIGURABLE
|
||||
|
||||
const val SECONDS_BETWEEN_SHAKES = 1.25F
|
||||
const val SECONDS_BEFORE_SHAKE = 1F
|
||||
|
||||
val DIMENSIONS = EntityDimensions.fixed(0.4F, 0.4F)
|
||||
|
||||
/**
|
||||
* Calculate an aim vector with a given miss effect.
|
||||
*
|
||||
* @param missMagnitudeRad How far away from the aimed at point the output vector is, in radians. A value of 0 will result in a perfectly accurate throw, a value of 2π will result in a throw going exactly backwards.
|
||||
* @param missAngleRad The point on the circle spanned by [missMagnitudeRad] which the resulting miss vector will point at, given in radian. Since the miss vector needs to be rotated which direction a given number points at from the perspective of the player depends on their orientation
|
||||
*/
|
||||
fun getAimVector(playerAimX: Double, playerAimY: Double, playerAimZ: Double, missMagnitudeRad: Double, missAngleRad: Double): Vec3 {
|
||||
// We want to sample a uniform vector out of the cone of potential misses. We can't just sample a rotation
|
||||
// angle from [0, 360°] and a radius from [0, $inaccuracy] since at smaller radii the points would be much
|
||||
// closer to each other and thus much more frequent. The mathematical trick we use comes from the fact that
|
||||
// for a sphere all slices of equal height have an equal surface area. So we instead sample a value for a
|
||||
// slice height and a rotation angle on the resulting circle, giving us uniformly distributed outputs.
|
||||
// This means given a maximum miss angle θ, i.e. the maximum angle the adjusted vector will have from the
|
||||
// player's aim we first calculate the possible range for our height: [cos(θ), 1]
|
||||
// Given this range we then sample two values, z from [cos(θ), 1] and φ from [0, 2π] and calculate an offset
|
||||
// vector given those values.
|
||||
// Finally that offset vector is rotated to use the player aim as unit axis instead of [0, 0, 1], giving us
|
||||
// our final miss vector to output.
|
||||
|
||||
val Z = cos(missMagnitudeRad)
|
||||
val sqrtZ = sqrt(1.0 - Z.pow(2))
|
||||
|
||||
val miss = Vector3d(
|
||||
sqrtZ * cos(missAngleRad),
|
||||
sqrtZ * sin(missAngleRad),
|
||||
Z
|
||||
)
|
||||
|
||||
val rotAxis = Vector3d(0.0, 0.0, 1.0)
|
||||
val rotAngle = acos(rotAxis.dot(playerAimX, playerAimY, playerAimZ))
|
||||
rotAxis.cross(playerAimX, playerAimY, playerAimZ)
|
||||
|
||||
return Quaterniond()
|
||||
.fromAxisAngleRad(rotAxis, rotAngle)
|
||||
.transform(miss)
|
||||
.let { Vec3(it.x, it.y, it.z) }
|
||||
}
|
||||
|
||||
fun getEntityHitResult(
|
||||
level: Level,
|
||||
projectile: Entity,
|
||||
startVec: Vec3,
|
||||
endVec: Vec3,
|
||||
boundingBox: AABB,
|
||||
filter: Predicate<Entity>,
|
||||
inflationAmount: Double
|
||||
): EntityHitResult? {
|
||||
var entity: Pair<Entity, Vec3>? = null
|
||||
|
||||
level.getEntities(projectile, boundingBox, filter).forEach {
|
||||
val aABB = it.boundingBox.inflate(inflationAmount)
|
||||
val optional = aABB.clip(startVec, endVec)
|
||||
if (optional.isPresent) {
|
||||
val vec = optional.orElseThrow()
|
||||
val distanceToEntity = startVec.distanceToSqr(vec)
|
||||
if (distanceToEntity < Double.MAX_VALUE) {
|
||||
entity = it to vec
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entity?.let { (entity, location) -> EntityHitResult(entity, location) }
|
||||
}
|
||||
}
|
||||
|
||||
val dataTrackerEmitter = SimpleObservable<EntityDataAccessor<*>>()
|
||||
@@ -109,6 +186,24 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
var aspects: Set<String>
|
||||
get() = entityData.get(ASPECTS)
|
||||
set(value) { entityData.set(ASPECTS, value) }
|
||||
var bounces: Int
|
||||
get() = entityData.get(BOUNCES)
|
||||
set(value) { entityData.set(BOUNCES, value) }
|
||||
|
||||
// GAMERULE CONFIGURABLE
|
||||
var maxBounces: Int
|
||||
get() = entityData.get(MAX_BOUNCES)
|
||||
set(value) { entityData.set(MAX_BOUNCES, value) }
|
||||
var bouncynessPercent: Int
|
||||
get() = entityData.get(BOUNCYNESS)
|
||||
set(value) { entityData.set(BOUNCYNESS, value) }
|
||||
var bounceDragPercent: Int
|
||||
get() = entityData.get(BOUNCE_DRAG)
|
||||
set(value) { entityData.set(BOUNCE_DRAG, value) }
|
||||
var waterSkipFactor: Int
|
||||
get() = entityData.get(WATER_SKIP_FACTOR)
|
||||
set(value) { entityData.set(WATER_SKIP_FACTOR, value) }
|
||||
// END GAMERULE CONFIGURABLE
|
||||
|
||||
override val delegate = if (level().isClientSide) {
|
||||
EmptyPokeBallClientDelegate()
|
||||
@@ -129,11 +224,20 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
builder.define(HIT_TARGET_POSITION, Vec3.ZERO)
|
||||
builder.define(HIT_VELOCITY, Vec3.ZERO)
|
||||
builder.define(SHAKE, false)
|
||||
builder.define(BOUNCES, 0)
|
||||
|
||||
// GAMERULE CONFIGURABLE
|
||||
builder.define(MAX_BOUNCES, 0)
|
||||
builder.define(BOUNCYNESS, 0)
|
||||
builder.define(BOUNCE_DRAG, 0)
|
||||
builder.define(WATER_SKIP_FACTOR, 0)
|
||||
// END GAMERULE CONFIGURABLE
|
||||
}
|
||||
|
||||
override fun onSyncedDataUpdated(data: EntityDataAccessor<*>) {
|
||||
super.onSyncedDataUpdated(data)
|
||||
// See what I said about this in PokemonEntity
|
||||
@Suppress("SENSELESS_COMPARISON")
|
||||
if (delegate != null) {
|
||||
delegate.onSyncedDataUpdated(data)
|
||||
}
|
||||
@@ -155,7 +259,7 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
addPosableFunctions(struct)
|
||||
}
|
||||
|
||||
constructor(world: Level) : this(pokeBall = PokeBalls.POKE_BALL, world = world)
|
||||
constructor(entityType: EntityType<out EmptyPokeBallEntity> = EMPTY_POKEBALL, world: Level) : this(pokeBall = PokeBalls.POKE_BALL, world = world, entityType = entityType)
|
||||
constructor(
|
||||
pokeBall: PokeBall,
|
||||
world: Level,
|
||||
@@ -175,20 +279,174 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
|
||||
var pokeBall: PokeBall = PokeBalls.POKE_BALL
|
||||
|
||||
//fun getMaxBounces(): Int = pokeBall.maxBounces
|
||||
|
||||
override fun isPushedByFluid(): Boolean = false
|
||||
|
||||
override fun getMovementToShoot(x: Double, y: Double, z: Double, velocity: Float, inaccuracy: Float): Vec3? {
|
||||
// We multiply the inaccuracy value by 0.02 to have rough equivalence to vanilla, meaning an inaccuracy of
|
||||
// `1.0f` passed to getMovementToShoot will give you about as much of an inaccurate throw as a 1.0f would for a
|
||||
// bow or snowball.
|
||||
val missMagnitudeRad: Double = inaccuracy * 0.02 * this.random.nextDouble()
|
||||
val missAngleRad: Double = this.random.nextDouble() * 2 * Math.PI
|
||||
|
||||
// Also make velocity inaccurate by a linear factor. This will mean it becomes much more impactful on weak throws.
|
||||
val adjustedVelocity = velocity + 0.02 * (this.random.nextDouble() - this.random.nextDouble())
|
||||
|
||||
return getAimVector(x, y, z, missMagnitudeRad, missAngleRad).scale(adjustedVelocity)
|
||||
}
|
||||
|
||||
fun moveWithCollision() {
|
||||
/* ==============================
|
||||
* Gamerule configurables
|
||||
* ============================== */
|
||||
// The game rule bouncyWaterSkipAngle sets the default angle below which balls will skip.
|
||||
// Setting it to 90 will make all balls (minus dive ball) skip all the time, setting it to 0 will make no balls skip ever
|
||||
val waterSkipDegree = if (pokeBall.waterDragValue >= 0.99) {
|
||||
// 1 means never skip
|
||||
1.0
|
||||
} else {
|
||||
// 1 = cos(0), 0 = cos(90)
|
||||
cos(waterSkipFactor * (kotlin.math.PI / 180.0))
|
||||
}
|
||||
// bounce elasticity. Default is 60 which means a ball will bounce back with 60% of the impact momentum
|
||||
val bouncyness = bouncynessPercent / 100.0
|
||||
// Water skip elasticity. Things that skip well retain more of their vertical movement when skipping
|
||||
// The default ball has a water drag of 0.8, an extremely skippy ball has one of 0.6. This mans that a normal
|
||||
// ball applies 80% of the elasticity reduction on water bounces while a skippy ball applies 60%
|
||||
val water_bouncyness = 1.0 - (pokeBall.waterDragValue * bouncyness)
|
||||
// Ground bounce drag. Set to 0 to lose no speed on bounce other than air friction, set to 100 to lose all speed immediately.
|
||||
val bounce_drag = 1.0 - (bounceDragPercent / 100.0)
|
||||
// Water skip drag. Skippy balls apply 30% of the configured bounce drag on skip, normal ones 40%
|
||||
val water_bounce_drag = 1.0 - (pokeBall.waterDragValue * bounceDragPercent / 200.0)
|
||||
/* ==============================
|
||||
* END Gamerule configurables END
|
||||
* ============================== */
|
||||
|
||||
|
||||
// Account for floating point inaccuracies, always.
|
||||
val FLOAT_MARGIN = 0.01
|
||||
val MIN_MOVEMENT = 0.1
|
||||
|
||||
val entityFilter = this::canHitEntity
|
||||
val entityHitMargin = 0.3
|
||||
|
||||
val deltaMovementSqr = this.deltaMovement.lengthSqr()
|
||||
// The entire movement budget we have this tick (done as squared because it's cheaper)
|
||||
var remainingMovementSqr = this.deltaMovement.lengthSqr()
|
||||
|
||||
var cumulativeBounceDrag = Vec3(1.0, 1.0, 1.0)
|
||||
|
||||
var isInWaterSubtick = false
|
||||
|
||||
while (remainingMovementSqr > FLOAT_MARGIN) {
|
||||
/* This while loop checks ball movement in segments:
|
||||
* On the first run through moveRatio will be 1.0 and all isBounce* will be false, so movement will be
|
||||
* this.deltaMovement. That movement is then checked for collisions. If no, we're immediately done here as hitResult.location will be set to the end point we wanted to go anyway.
|
||||
* If yes we perform the bounce:
|
||||
* - Set the isBounce* variables which are tracking which direction the ball should en up flying *after the whole tick*.
|
||||
* - Reduce the cummulative drag (value: [0.0; 1.0]) depending on the nature of the bounce.
|
||||
* Lastly, set our new position to the position after a bounce and reduce our movement budget according to the current segment
|
||||
* If we have movement budget left (yes in case of a bounce, no in case of a miss), do more segmented collision checks.
|
||||
*/
|
||||
val moveRatio = remainingMovementSqr / deltaMovementSqr
|
||||
val movement = this.deltaMovement
|
||||
.multiply(cumulativeBounceDrag)
|
||||
.scale(moveRatio)
|
||||
|
||||
val fluidFilter = if (this.isInWater || isInWaterSubtick) Fluid.NONE else Fluid.WATER
|
||||
val hitResult = this.level().clip(ClipContext(this.position(), this.position().add(movement), Block.COLLIDER, fluidFilter, this))
|
||||
|
||||
val bounceSound = if (hitResult.type != HitResult.Type.MISS) {
|
||||
val hitBlock = this.level().getBlockState(hitResult.blockPos)
|
||||
|
||||
// Normal vector of the surface we bounce off
|
||||
val normal = hitResult.direction.normal
|
||||
|
||||
if (!hitBlock.fluidState.isEmpty) {
|
||||
// We calculate the bounce angle using normalised vectors, so this will be a value
|
||||
// between 0 (ball is moving parallel to the water) and 1 (ball is hitting the water straight on)
|
||||
val bounceAngle = abs(movement.normalize().dot(Vec3(normal.x.toDouble(), normal.y.toDouble(), normal.z.toDouble())))
|
||||
|
||||
// The theory is that a ball that has no water drag (waterDragValue = 1.0) moves through water like
|
||||
// air so it physically can't skip. A ball that moves very slow in water skips much easier.
|
||||
// `pow(3)` serves as a good curve since it keeps the [0; 1] output range but most drag values will
|
||||
// be much closer to 1 so should be easier to skip (e.g. 0.8^3 = 0.512 so about 45° or sharper angles will skip)
|
||||
val targetAngle = 1.0 - waterSkipDegree
|
||||
|
||||
// At a flat enough angle (we're comparing against the normal so > is correct) balls bounce off of water.
|
||||
// This does not happen if the ball has no remaining bounces so that a ball will never break on the
|
||||
// water surface but instead submerge
|
||||
if (bounceAngle < targetAngle && deltaMovementSqr > 0.4 && bounces < maxBounces) {
|
||||
bounces++
|
||||
cumulativeBounceDrag = cumulativeBounceDrag
|
||||
.multiply(
|
||||
if (normal.x != 0) { -water_bouncyness } else { water_bounce_drag },
|
||||
if (normal.y != 0) { -water_bouncyness } else { water_bounce_drag },
|
||||
if (normal.z != 0) { -water_bouncyness } else { water_bounce_drag },
|
||||
)
|
||||
} else {
|
||||
isInWaterSubtick = true
|
||||
}
|
||||
} else {
|
||||
bounces++
|
||||
cumulativeBounceDrag = cumulativeBounceDrag
|
||||
// The effect vector a bounce with this normal has. Note that *negative* bouncyness is taken here; if we
|
||||
// bounce off e.g. the ground we were going down before (y is negative) but are going *up* after (y is positive),
|
||||
// although with less upward speed than we had downward speed (bouncyness is less than 1.0)
|
||||
.multiply(
|
||||
if (normal.x != 0) { -bouncyness } else { bounce_drag },
|
||||
if (normal.y != 0) { -bouncyness } else { bounce_drag },
|
||||
if (normal.z != 0) { -bouncyness } else { bounce_drag },
|
||||
)
|
||||
}
|
||||
|
||||
if (captureState == CaptureState.NOT) {
|
||||
hitBlock.soundType.hitSound
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val entityHitResult = getEntityHitResult(this.level(), this, this.position(), hitResult.location, this.boundingBox.expandTowards(this.deltaMovement).inflate(1.0), entityFilter, entityHitMargin)
|
||||
|
||||
// The actual hit that happened
|
||||
val effectiveHitResult = entityHitResult ?: hitResult
|
||||
|
||||
remainingMovementSqr = if (effectiveHitResult.type != HitResult.Type.MISS) {
|
||||
remainingMovementSqr - this.position().distanceToSqr(effectiveHitResult.location)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
|
||||
val speedEstimateAfterBounce = deltaMovementSqr * cumulativeBounceDrag.lengthSqr()
|
||||
|
||||
if (bounces > maxBounces || effectiveHitResult.type == HitResult.Type.ENTITY || speedEstimateAfterBounce < MIN_MOVEMENT) {
|
||||
val deflection = this.hitTargetOrDeflectSelf(effectiveHitResult)
|
||||
val entity = (effectiveHitResult as? EntityHitResult)?.entity
|
||||
deflection.deflect(this, entity, this.random)
|
||||
} else {
|
||||
bounceSound?.let { playSound(it, 0.5f, 1f) }
|
||||
}
|
||||
|
||||
this.setPos(effectiveHitResult.location)
|
||||
}
|
||||
|
||||
this.deltaMovement = this.deltaMovement.multiply(cumulativeBounceDrag)
|
||||
}
|
||||
|
||||
override fun onHitBlock(hitResult: BlockHitResult) {
|
||||
if (captureState == CaptureState.NOT) {
|
||||
if (level().isServerSide()) {
|
||||
super.onHitBlock(hitResult)
|
||||
level().sendParticlesServer(ParticleTypes.CLOUD, hitResult.location, 2, hitResult.location.subtract(position()).normalize().scale(-0.1), 0.0)
|
||||
level().playSoundServer(position(), SoundEvents.WOOD_PLACE, pitch = 2.5F)
|
||||
discard()
|
||||
val player = this.owner as? ServerPlayer
|
||||
if (player?.isCreative == false) {
|
||||
spawnAtLocation(defaultItem)
|
||||
}
|
||||
playSound(SoundEvents.WOOD_BREAK, 0.5f, 2.5f)
|
||||
return drop()
|
||||
}
|
||||
} else {
|
||||
setNoGravity(false)
|
||||
isNoGravity = false
|
||||
deltaMovement = Vec3.ZERO
|
||||
}
|
||||
}
|
||||
@@ -279,9 +537,9 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
private fun drop() {
|
||||
val owner = owner
|
||||
discard()
|
||||
val player = owner?.takeIf { it is ServerPlayer } as? ServerPlayer
|
||||
if (player?.isCreative != true) spawnAtLocation(defaultItem)
|
||||
return
|
||||
if (owner is ServerPlayer && !owner.hasInfiniteMaterials()) {
|
||||
spawnAtLocation(defaultItem)?.deltaMovement = deltaMovement
|
||||
}
|
||||
}
|
||||
|
||||
// Poké Balls don't save to the world.
|
||||
@@ -289,13 +547,28 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
|
||||
override fun tick() {
|
||||
super.tick()
|
||||
moveWithCollision()
|
||||
|
||||
// Do the things that [ThrowableProjectile.tick] does that we want but skipped because of our mixin
|
||||
checkInsideBlocks()
|
||||
updateRotation()
|
||||
// We want water behaviour to be special so we don't do the this.isInWater() check the same way
|
||||
|
||||
val drag = if (this.isInWater) {
|
||||
pokeBall.waterDragValue
|
||||
} else {
|
||||
0.99
|
||||
}
|
||||
this.deltaMovement = this.deltaMovement.scale(drag)
|
||||
|
||||
this.applyGravity()
|
||||
|
||||
delegate.tick(this)
|
||||
|
||||
if (level().isServerSide()) {
|
||||
|
||||
|
||||
if (this.tickCount > 600 && this.capturingPokemon == null) {
|
||||
this.remove(RemovalReason.DISCARDED)
|
||||
drop()
|
||||
}
|
||||
|
||||
if (owner == null || !owner!!.isAlive || (captureState != CaptureState.NOT && capturingPokemon?.isAlive != true)) {
|
||||
@@ -549,6 +822,4 @@ class EmptyPokeBallEntity : ThrowableItemProjectile, PosableEntity, WaterDragMod
|
||||
)
|
||||
) as Packet<ClientGamePacketListener>
|
||||
|
||||
override fun waterDrag(): Float = this.pokeBall.waterDragValue
|
||||
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.entity.pokeball
|
||||
|
||||
/**
|
||||
* Marker interface so our mixin can special case ThrowableProjectile.tick for the thrown Pokéball entity.
|
||||
*/
|
||||
interface EmptyPokeballMarker
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.entity.pokeball
|
||||
|
||||
/**
|
||||
* A Cobblemon extension to make it possible to add water drag values to our Pokeballs without relying on the PersistentProjectileEntity implementation.
|
||||
*/
|
||||
interface WaterDragModifier {
|
||||
|
||||
/**
|
||||
* Calculates and returns the value of the water drag.
|
||||
*
|
||||
* @return The value of the water drag on the entity.
|
||||
*/
|
||||
fun waterDrag(): Float
|
||||
|
||||
}
|
||||
@@ -442,6 +442,7 @@ open class PokemonEntity(
|
||||
override fun onSyncedDataUpdated(data: EntityDataAccessor<*>) {
|
||||
super.onSyncedDataUpdated(data)
|
||||
// "But it's imposs-" shut up nerd, it happens during super construction and that's before delegate is assigned by class construction
|
||||
@Suppress("SENSELESS_COMPARISON")
|
||||
if (delegate != null) {
|
||||
delegate.onSyncedDataUpdated(data)
|
||||
}
|
||||
@@ -2402,7 +2403,7 @@ open class PokemonEntity(
|
||||
|
||||
/**
|
||||
* If the Pokémon is following another Pokémon, checks the herd size using the leader. Otherwise check this Pokémon's
|
||||
* herd count. Not necessarily strictly up to date but it should be good enough for typical purposes.
|
||||
* herd count. Not necessarily strictly up to datreturn super.getListenableEvents()e but it should be good enough for typical purposes.
|
||||
*/
|
||||
fun getHerdSize(): Int {
|
||||
val world = level() as? ServerLevel ?: return 0
|
||||
|
||||
@@ -10,17 +10,32 @@ package com.cobblemon.mod.common.item
|
||||
|
||||
import com.cobblemon.mod.common.api.pokeball.PokeBalls
|
||||
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
|
||||
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity.Companion.BOUNCES
|
||||
import com.cobblemon.mod.common.pokeball.PokeBall
|
||||
import com.cobblemon.mod.common.util.isServerSide
|
||||
import com.cobblemon.mod.common.util.math.geometry.toRadians
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import com.cobblemon.mod.common.util.random
|
||||
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
|
||||
import net.minecraft.client.multiplayer.ClientLevel
|
||||
import net.minecraft.core.component.DataComponents
|
||||
import net.minecraft.core.particles.DustParticleOptions
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.sounds.SoundEvents
|
||||
import net.minecraft.stats.Stats
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.InteractionResultHolder
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.Rarity
|
||||
import net.minecraft.world.item.UseAnim
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import org.joml.Vector3f
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.text.toDouble
|
||||
|
||||
class PokeBallItem(
|
||||
val pokeBall: PokeBall
|
||||
@@ -36,14 +51,48 @@ class PokeBallItem(
|
||||
|
||||
override fun use(world: Level, player: Player, usedHand: InteractionHand): InteractionResultHolder<ItemStack> {
|
||||
val itemStack = player.getItemInHand(usedHand)
|
||||
if (world.isServerSide()) {
|
||||
throwPokeBall(world, player as ServerPlayer)
|
||||
}
|
||||
itemStack.consume(1, player)
|
||||
return InteractionResultHolder.sidedSuccess(itemStack, world.isClientSide)
|
||||
player.startUsingItem(usedHand)
|
||||
return InteractionResultHolder.consume(itemStack)
|
||||
}
|
||||
|
||||
private fun throwPokeBall(world: Level, player: ServerPlayer) {
|
||||
override fun releaseUsing(stack: ItemStack, level: Level, player: LivingEntity, remainingUseDuration: Int) {
|
||||
if (player is ServerPlayer) {
|
||||
val ticksHeld = getUseDuration(stack, player) - remainingUseDuration
|
||||
val force = getPowerForTime(ticksHeld)
|
||||
|
||||
if (force > 0.1) {
|
||||
val inaccuracy = getInaccuracyForTime(ticksHeld)
|
||||
|
||||
throwPokeBall(level, player, force, inaccuracy)
|
||||
player.cooldowns.addCooldown(this, 10)
|
||||
stack.consume(1, player)
|
||||
|
||||
player.awardStat(Stats.ITEM_USED.get(this));
|
||||
}
|
||||
} else {
|
||||
super.releaseUsing(stack, level, player, remainingUseDuration)
|
||||
}
|
||||
}
|
||||
|
||||
fun getPowerForTime(charge: Int): Float {
|
||||
var f = charge.toFloat() / 20.0f
|
||||
f = (f * f + f * 2.0f) / 3.0f
|
||||
if (f > 1.0f) {
|
||||
f = 1.0f
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// Calculate the max inaccuracy (in radians over the entire screen space) for the given charge time.
|
||||
fun getInaccuracyForTime(charge: Int): Float = 1.00f
|
||||
|
||||
// How long the item can be used for before resetting. Not the time it takes to fully charge up
|
||||
override fun getUseDuration(stack: ItemStack, entity: LivingEntity): Int = 72000
|
||||
|
||||
override fun getUseAnimation(stack: ItemStack): UseAnim? = UseAnim.SPEAR
|
||||
|
||||
private fun throwPokeBall(world: Level, player: ServerPlayer, force: Float, inaccuracy: Float) {
|
||||
val pokeBallEntity = EmptyPokeBallEntity(pokeBall, player.level(), player).apply {
|
||||
val overhandFactor: Float = if (player.xRot < 0) {
|
||||
5f * cos(player.xRot.toRadians())
|
||||
@@ -51,8 +100,16 @@ class PokeBallItem(
|
||||
5f
|
||||
}
|
||||
|
||||
shootFromRotation(player, player.xRot - overhandFactor, player.yRot, 0.0f, pokeBall.throwPower, 1.0f)
|
||||
setPos(position().add(deltaMovement.normalize().scale(1.0)))
|
||||
// Query gamerules
|
||||
if (world.isServerSide()) {
|
||||
this.bouncynessPercent = world.gameRules.getInt(CobblemonGameRules.BOUNCY_BOUNCYNESS)
|
||||
this.waterSkipFactor = world.gameRules.getInt(CobblemonGameRules.BOUNCY_WATER_SKIP_FACTOR)
|
||||
this.maxBounces = world.gameRules.getInt(CobblemonGameRules.BOUNCY_BOUNCE_COUNT)
|
||||
this.bounceDragPercent = world.gameRules.getInt(CobblemonGameRules.BOUNCY_BOUNCE_DRAG)
|
||||
}
|
||||
|
||||
shootFromRotation(player, player.xRot - overhandFactor, player.yRot, 0.0f, pokeBall.throwPower * force, inaccuracy)
|
||||
setPos(position().add(deltaMovement.normalize()))
|
||||
owner = player
|
||||
}
|
||||
world.addFreshEntity(pokeBallEntity)
|
||||
|
||||
@@ -36,17 +36,21 @@ open class PokeBall(
|
||||
val name: ResourceLocation,
|
||||
val catchRateModifier: CatchRateModifier = CatchRateModifier.DUMMY,
|
||||
val effects: List<CaptureEffect> = listOf(),
|
||||
val waterDragValue: Float,
|
||||
val waterDragValue: Double,
|
||||
val model2d: ResourceLocation,
|
||||
val model3d: ResourceLocation,
|
||||
val throwPower: Float,
|
||||
val ancient: Boolean
|
||||
val ancient: Boolean,
|
||||
val maxBounces: Int,
|
||||
val bouncyness: Double,
|
||||
) {
|
||||
val struct = QueryStruct(hashMapOf())
|
||||
.addFunction("name") { StringValue(name.toString()) }
|
||||
.addFunction("water_drag_value") { DoubleValue(waterDragValue) }
|
||||
.addFunction("throw_power") { DoubleValue(throwPower) }
|
||||
.addFunction("is_ancient") { DoubleValue(ancient) }
|
||||
.addFunction("max_bounces") { DoubleValue(maxBounces) }
|
||||
.addFunction("bouncyness") { DoubleValue(bouncyness) }
|
||||
// .addFunction("item") { } // requires a registry which is hard
|
||||
|
||||
// This gets attached during item registry
|
||||
|
||||
+1
@@ -21,6 +21,7 @@ import org.joml.Vector3f
|
||||
* @property values the values of the matrix, in the form of a two-dimensional immutable array
|
||||
* @author landonjw
|
||||
*/
|
||||
@ConsistentCopyVisibility
|
||||
data class TransformationMatrix internal constructor(val values: ImmutableArray<ImmutableArray<Float>>) {
|
||||
|
||||
operator fun get(row: Int): ImmutableArray<Float> = values[row]
|
||||
|
||||
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.world.gamerules
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.mixin.invoker.BooleanRuleInvoker
|
||||
import com.cobblemon.mod.common.mixin.invoker.IntegerRuleInvoker
|
||||
import net.minecraft.world.level.GameRules
|
||||
|
||||
object CobblemonGameRules {
|
||||
@@ -25,4 +26,12 @@ object CobblemonGameRules {
|
||||
@JvmField
|
||||
val SHINY_STARTERS: GameRules.Key<GameRules.BooleanValue> = Cobblemon.implementation.registerGameRule("doShinyStarters", GameRules.Category.MISC, BooleanRuleInvoker.`cobblemon$create`(false))
|
||||
|
||||
@JvmField
|
||||
val BOUNCY_BOUNCYNESS: GameRules.Key<GameRules.IntegerValue> = Cobblemon.implementation.registerGameRule("bouncyElasticity", GameRules.Category.MISC, IntegerRuleInvoker.`cobblemon$create`(60))
|
||||
@JvmField
|
||||
val BOUNCY_WATER_SKIP_FACTOR: GameRules.Key<GameRules.IntegerValue> = Cobblemon.implementation.registerGameRule("bouncyWaterSkipAngle", GameRules.Category.MISC, IntegerRuleInvoker.`cobblemon$create`(40))
|
||||
@JvmField
|
||||
val BOUNCY_BOUNCE_COUNT: GameRules.Key<GameRules.IntegerValue> = Cobblemon.implementation.registerGameRule("bouncyBounceCount", GameRules.Category.MISC, IntegerRuleInvoker.`cobblemon$create`(0))
|
||||
@JvmField
|
||||
val BOUNCY_BOUNCE_DRAG: GameRules.Key<GameRules.IntegerValue> = Cobblemon.implementation.registerGameRule("bouncyBounceDrag", GameRules.Category.MISC, IntegerRuleInvoker.`cobblemon$create`(40))
|
||||
}
|
||||
@@ -91,9 +91,11 @@
|
||||
"invoker.BooleanRuleInvoker",
|
||||
"invoker.DoorBlockInvoker",
|
||||
"invoker.FireBlockInvoker",
|
||||
"invoker.IntegerRuleInvoker",
|
||||
"invoker.PressurePlateBlockInvoker",
|
||||
"invoker.StairsBlockInvoker",
|
||||
"invoker.TrapdoorBlockInvoker",
|
||||
"pokeball.ThrownEntityMixin"
|
||||
"pokeball.ProjectileMixin",
|
||||
"pokeball.ThrowableProjectileMixin"
|
||||
]
|
||||
}
|
||||
|
||||
+6
-15
@@ -30,21 +30,12 @@ public class GenericPacketSplitterMixin {
|
||||
|
||||
@Inject(method = "encode(Lio/netty/channel/ChannelHandlerContext;Lnet/minecraft/network/protocol/Packet;Ljava/util/List;)V", at=@At("HEAD"), cancellable = true)
|
||||
public void encode(ChannelHandlerContext channelHandlerContext, Packet packet, List<Object> list, CallbackInfo ci) {
|
||||
if (packet instanceof ClientboundCustomPayloadPacket) {
|
||||
ClientboundCustomPayloadPacket pac = (ClientboundCustomPayloadPacket) packet;
|
||||
if (((ClientboundCustomPayloadPacket) packet).payload() instanceof UnsplittablePacket) {
|
||||
list.add(packet);
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (packet instanceof ServerboundCustomPayloadPacket) {
|
||||
ServerboundCustomPayloadPacket pac = (ServerboundCustomPayloadPacket) packet;
|
||||
if (((ServerboundCustomPayloadPacket) packet).payload() instanceof UnsplittablePacket) {
|
||||
list.add(packet);
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
if (packet instanceof ClientboundCustomPayloadPacket(UnsplittablePacket payload )) {
|
||||
list.add(packet);
|
||||
ci.cancel();
|
||||
} else if (packet instanceof ServerboundCustomPayloadPacket(UnsplittablePacket payload)) {
|
||||
list.add(packet);
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user