Skip to content

Commit

Permalink
Fix projectile yaw & position synchronization issues
Browse files Browse the repository at this point in the history
  • Loading branch information
TogAr2 committed May 7, 2024
1 parent 158f22b commit a373305
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.jetbrains.annotations.Nullable;

import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicBoolean;

Expand Down Expand Up @@ -194,6 +195,18 @@ protected boolean canHit(Entity entity) {
return entity instanceof LivingEntity;
}

@Override
protected void synchronizePosition() {
// For some reason, sending a synchronization when stuck means the position of the arrow will change slightly
// on the client even though the position on the server has not changed at all. Why? No clue.
// This check does solve the issue though.
if (isStuck()) return;

super.synchronizePosition();
}

private float prevYaw, prevPitch;

@Override
protected void movementTick() {
// Mostly copied from Minestom
Expand All @@ -206,6 +219,8 @@ protected void movementTick() {
instance.getWorldBorder(), instance, hasPhysics, previousPhysicsResult, true);
this.previousPhysicsResult = physicsResult;

Pos newPosition = physicsResult.newPosition();

if (!noClip) {
// We won't check collisions with self for first ticks of projectile's life, because it spawns in the
// shooter and will immediately be triggered by him.
Expand All @@ -219,6 +234,8 @@ protected void movementTick() {
if (entityResult.hasCollision() && entityResult.collisionShapes()[0] instanceof Entity collided) {
AtomicBoolean entityCollisionSucceeded = new AtomicBoolean();

Vec prevVelocity = velocity;

var event = new ProjectileCollideWithEntityEvent(this, Pos.fromPoint(entityResult.newPosition()), collided);
EventDispatcher.callCancellable(event, () -> entityCollisionSucceeded.set(onHit(collided)));

Expand All @@ -227,14 +244,16 @@ protected void movementTick() {
scheduler().scheduleNextProcess(this::remove);
// Prevent hitting blocks
return;
} else {
// If velocity has been changed because of bounce, prevent projectile from moving further
if (velocity != prevVelocity) newPosition = position;
}
}
}

Chunk finalChunk = ChunkUtils.retrieve(instance, currentChunk, physicsResult.newPosition());
if (!ChunkUtils.isLoaded(finalChunk)) return;

Pos newPosition = physicsResult.newPosition();
if (physicsResult.hasCollision() && !isStuck()) {
double signumX = physicsResult.collisionX() ? Math.signum(velocity.x()) : 0;
double signumY = physicsResult.collisionY() ? Math.signum(velocity.y()) : 0;
Expand All @@ -244,16 +263,16 @@ protected void movementTick() {
Point collidedPosition = collisionDirection.add(physicsResult.newPosition()).apply(Vec.Operator.FLOOR);
Block block = instance.getBlock(collidedPosition);

AtomicBoolean collisionSucceeded = new AtomicBoolean();
AtomicBoolean shouldRemove = new AtomicBoolean();

var event = new ProjectileCollideWithBlockEvent(this, physicsResult.newPosition().withCoord(collidedPosition), block);
EventDispatcher.callCancellable(event, () -> {
setNoGravity(true);
this.collisionDirection = collisionDirection;
collisionSucceeded.set(onStuck());
shouldRemove.set(onStuck());
});

if (collisionSucceeded.get()) {
if (shouldRemove.get()) {
// Don't remove now because rest of Entity#tick might throw errors
scheduler().scheduleNextProcess(this::remove);
}
Expand All @@ -267,16 +286,47 @@ protected void movementTick() {
).sub(0, hasNoGravity() ? 0 : getAerodynamics().gravity() * ServerFlag.SERVER_TICKS_PER_SECOND, 0);
onGround = physicsResult.isOnGround();

refreshPosition(newPosition, true, false);
float yaw = position.yaw();
float pitch = position.pitch();

if (!noClip) {
float yaw = (float) Math.toDegrees(Math.atan2(diff.x(), diff.z()));
float pitch = (float) Math.toDegrees(Math.atan2(diff.y(), Math.sqrt(diff.x() * diff.x() + diff.z() * diff.z())));
refreshPosition(position.withView(yaw, pitch), false, false);
yaw = (float) Math.toDegrees(Math.atan2(diff.x(), diff.z()));
pitch = (float) Math.toDegrees(
Math.atan2(diff.y(), Math.sqrt(diff.x() * diff.x() + diff.z() * diff.z())));

// Vanilla really likes to use variables from the render code
// on the server side in a way that does not make sense at all
yaw = lerp(prevYaw, yaw);
pitch = lerp(prevPitch, pitch);
}

this.prevYaw = yaw;
this.prevPitch = pitch;

refreshPosition(newPosition.withView(yaw, pitch), noClip, isStuck());
}
}

private static float lerp(float first, float second) {
return first + (second - first) * 0.2f;
}

@Override
public void setView(float yaw, float pitch) {
this.prevYaw = yaw;
this.prevPitch = pitch;

super.setView(yaw, pitch);
}

@Override
public @NotNull CompletableFuture<Void> teleport(@NotNull Pos position) {
this.prevYaw = position.yaw();
this.prevPitch = position.pitch();

return super.teleport(position);
}

protected int getUpdateInterval() {
return 20;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,13 +323,11 @@ public static EventNode<PlayerInstanceEvent> events(ProjectileConfig config) {

// Arrow shooting
Pos position = player.getPosition().add(0D, player.getEyeHeight() - 0.1, 0D);
arrow.setInstance(Objects.requireNonNull(player.getInstance()), position);

arrow.shootFromRotation(position.pitch(), position.yaw(), 0 , power * 3, 1.0);

Vec playerVel = player.getVelocity();
arrow.setVelocity(arrow.getVelocity().add(playerVel.x(),
player.isOnGround() ? 0.0D : playerVel.y(), playerVel.z()));
arrow.setInstance(Objects.requireNonNull(player.getInstance()), position);

ThreadLocalRandom random = ThreadLocalRandom.current();
ViewUtil.viewersAndSelf(player).playSound(Sound.sound(
Expand Down
22 changes: 18 additions & 4 deletions src/test/java/io/github/togar2/pvp/test/PvpTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@
import net.minestom.server.entity.damage.EntityDamage;
import net.minestom.server.event.EventDispatcher;
import net.minestom.server.event.GlobalEventHandler;
import net.minestom.server.event.player.AsyncPlayerConfigurationEvent;
import net.minestom.server.event.player.PlayerSpawnEvent;
import net.minestom.server.event.player.PlayerTickEvent;
import net.minestom.server.event.player.PlayerUseItemOnBlockEvent;
import net.minestom.server.event.player.*;
import net.minestom.server.extras.lan.OpenToLAN;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.Material;
import net.minestom.server.network.packet.server.common.KeepAlivePacket;
import net.minestom.server.network.packet.server.play.*;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.time.TimeUnit;
import net.minestom.server.world.DimensionType;
Expand Down Expand Up @@ -95,6 +94,21 @@ public static void main(String[] args) {
//event.getPlayer().addEffect(new Potion(PotionEffect.REGENERATION, (byte) 10, CustomPotionEffect.PERMANENT));
});

MinecraftServer.getGlobalEventHandler().addListener(PlayerPacketOutEvent.class, event -> {
if (event.getPacket() instanceof ActionBarPacket) return;
if (event.getPacket() instanceof UpdateHealthPacket) return;
if (event.getPacket() instanceof TimeUpdatePacket) return;
if (event.getPacket() instanceof PlayerInfoUpdatePacket) return;
if (event.getPacket() instanceof ChunkDataPacket) return;
if (event.getPacket() instanceof KeepAlivePacket) return;
if (event.getPacket() instanceof SetSlotPacket) return;
if (event.getPacket() instanceof WindowItemsPacket) return;
if (event.getPacket() instanceof EntitySoundEffectPacket) return;
if (event.getPacket() instanceof CollectItemPacket) return;
if (event.getPacket() instanceof DestroyEntitiesPacket) return;
System.out.println(event.getPacket());
});

MinecraftServer.getCommandManager().register(new Command("shoot") {{
setDefaultExecutor((sender, args) -> {
if (sender instanceof Player player) {
Expand Down

0 comments on commit a373305

Please sign in to comment.