Skip to content

Commit

Permalink
Improved Dragging & Entity Interactions on Ships [Pathfinding, POIs, …
Browse files Browse the repository at this point in the history
…Raids, etc.] (#1027)

- Mobs & Players are now dragged perfectly, even at ludicrous speed
- Mob spawning on ships has been fixed
- Mob pathfinding on ships has been significantly improved
- Mob rotation on ships has been fixed
- POI finding on ships has been fixed (Villager stations, portals, bee hives, etc)
- Bee flower pathing on ships has been fixed.
- Villages can now be on ships and are tracked correctly.
- Raids can now target ships, and spawn around them.
- Zombie Sieges can now target ships, and spawn around them.
- Player targeting of blocks now works more reliably on ships while at high speeds or rotations.
- Players can now hold open GUIs while on ships at high speeds or rotations.
  • Loading branch information
ThePlasticPotato authored Dec 27, 2024
1 parent 6958099 commit 8f853da
Show file tree
Hide file tree
Showing 58 changed files with 1,915 additions and 36 deletions.
2 changes: 1 addition & 1 deletion common/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

dependencies {
annotationProcessor(implementation("com.github.LlamaLad7:MixinExtras:0.1.1"))
annotationProcessor(implementation("io.github.llamalad7:mixinextras-common:0.3.5"))

compileOnly 'com.google.code.findbugs:jsr305:3.0.2'
// We depend on fabric loader here to use the fabric @Environment annotations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,17 @@ public ClientShipWorldCore getShipObjectWorld() {
@Shadow
public abstract ClientPacketListener getConnection();

@Inject(
method = "tick",
at = @At("HEAD")
)
public void preTick(final CallbackInfo ci) {
// Tick the ship world and then drag entities
if (!pause && shipObjectWorld != null && level != null && getConnection() != null) {
//EntityDragger.INSTANCE.dragEntitiesWithShips(level.entitiesForRendering(), true);
}
}

@Inject(
method = "tick",
at = @At("TAIL")
Expand All @@ -112,7 +123,8 @@ public void postTick(final CallbackInfo ci) {
if (!pause && shipObjectWorld != null && level != null && getConnection() != null) {
shipObjectWorld.tickNetworking(getConnection().getConnection().getRemoteAddress());
shipObjectWorld.postTick();
EntityDragger.INSTANCE.dragEntitiesWithShips(level.entitiesForRendering());
//EntityDragger.INSTANCE.dragEntitiesWithShips(level.entitiesForRendering());
EntityDragger.INSTANCE.dragEntitiesWithShips(level.entitiesForRendering(), false);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.valkyrienskies.mod.mixin.client.renderer;

import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.AABB;
import org.joml.Vector3d;
import org.joml.Vector3dc;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.valkyrienskies.core.api.ships.ClientShip;
import org.valkyrienskies.mod.common.VSGameUtilsKt;
import org.valkyrienskies.mod.common.util.EntityDraggingInformation;
import org.valkyrienskies.mod.common.util.IEntityDraggingInformationProvider;

@Mixin(EntityRenderer.class)
public class MixinEntityRenderer {

/**
* This is necessary to avoid the vanilla flickering that occurs when entities are at high speeds.
* <p>
* Presumably, it is caused by the culling AABB only being updated on a subsequent tick, so we bypass that.
* @param instance
* @param original
* @return
*/
@WrapOperation(method = "shouldRender", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;getBoundingBoxForCulling()Lnet/minecraft/world/phys/AABB;"))
private AABB redirectAABBConstructor(Entity instance, Operation<AABB> original) {
if (instance instanceof IEntityDraggingInformationProvider dragProvider && dragProvider.getDraggingInformation().isEntityBeingDraggedByAShip()) {
EntityDraggingInformation dragInfo = dragProvider.getDraggingInformation();
ClientShip ship = VSGameUtilsKt.getShipObjectWorld((ClientLevel) instance.level).getAllShips().getById(dragInfo.getLastShipStoodOn());
if (ship == null) {
return original.call(instance);
}
if (dragInfo.getLastShipStoodOn() != null && (dragInfo.getRelativePositionOnShip() != null || dragInfo.getServerRelativePlayerPosition() != null)) {
Vector3dc positionToTransform = dragInfo.bestRelativeEntityPosition();
if (positionToTransform != null) {
Vector3dc transformed = ship.getRenderTransform().getShipToWorld().transformPosition(positionToTransform,
new Vector3d());
return instance.getDimensions(instance.getPose()).makeBoundingBox(transformed.x(), transformed.y(), transformed.z()).inflate(0.5D);
}
}
}
return original.call(instance);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ private void preRender(final float tickDelta, final long startTime, final boolea
((IEntityDraggingInformationProvider) entity).getDraggingInformation();
final Long lastShipStoodOn = entityDraggingInformation.getLastShipStoodOn();
// Then try getting [entityShouldBeHere] from [entityDraggingInformation]
if (lastShipStoodOn != null && entityDraggingInformation.isEntityBeingDraggedByAShip()) {
if (lastShipStoodOn != null && entityDraggingInformation.isEntityBeingDraggedByAShip()) { //for testing
final ClientShip shipObject =
VSGameUtilsKt.getShipObjectWorld(clientWorld).getLoadedShips().getById(lastShipStoodOn);
if (shipObject != null) {
Expand Down Expand Up @@ -200,6 +200,10 @@ private void preRender(final float tickDelta, final long startTime, final boolea
entity.xo = (entityShouldBeHere.x() - (entity.getX() * tickDelta)) / (1.0 - tickDelta);
entity.yo = (entityShouldBeHere.y() - (entity.getY() * tickDelta)) / (1.0 - tickDelta);
entity.zo = (entityShouldBeHere.z() - (entity.getZ() * tickDelta)) / (1.0 - tickDelta);
//why the fuck do we do this

//what if i just...
//dont
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
Expand Down Expand Up @@ -33,6 +35,7 @@
import org.valkyrienskies.core.api.ships.properties.ShipTransform;
import org.valkyrienskies.mod.common.entity.ShipMountedToData;
import org.valkyrienskies.mod.common.VSGameUtilsKt;
import org.valkyrienskies.mod.common.util.EntityDragger;
import org.valkyrienskies.mod.common.util.EntityDraggingInformation;
import org.valkyrienskies.mod.common.util.IEntityDraggingInformationProvider;
import org.valkyrienskies.mod.common.util.VectorConversionsMCKt;
Expand Down Expand Up @@ -127,11 +130,67 @@ private void preGetEyePosition(final float partialTicks, final CallbackInfoRetur

/**
* @reason Needed for players to pick blocks correctly when mounted to a ship
*
* Needed, because before we only fixed the clientside one.
*/
@Inject(method = "getEyePosition()Lnet/minecraft/world/phys/Vec3;", at = @At("HEAD"), cancellable = true)
private void preGetEyePositionServer(final CallbackInfoReturnable<Vec3> cir) {
final ShipMountedToData shipMountedToData = VSGameUtilsKt.getShipMountedToData(Entity.class.cast(this), null);
if (shipMountedToData == null) {
return;
}
final LoadedShip shipMountedTo = shipMountedToData.getShipMountedTo();

final ShipTransform shipTransform;
if (shipMountedTo instanceof ClientShip) {
shipTransform = ((ClientShip) shipMountedTo).getRenderTransform();
} else {
shipTransform = shipMountedTo.getShipTransform();
}
final Vector3dc basePos = shipTransform.getShipToWorldMatrix()
.transformPosition(shipMountedToData.getMountPosInShip(), new Vector3d());
final Vector3dc eyeRelativePos = shipTransform.getShipCoordinatesToWorldCoordinatesRotation().transform(
new Vector3d(0.0, getEyeHeight(), 0.0)
);
final Vec3 newEyePos = VectorConversionsMCKt.toMinecraft(basePos.add(eyeRelativePos, new Vector3d()));
cir.setReturnValue(newEyePos);
}

/**
* @reason Needed for players to pick blocks correctly when mounted to a ship
*
* Additionally, this has to have dragging information included or it breaks. This is because of reasons that I literally
* do not know or understand, but minecraft's rendering pipeline is like that.
*/
@Inject(method = "calculateViewVector", at = @At("HEAD"), cancellable = true)
private void preCalculateViewVector(final float xRot, final float yRot, final CallbackInfoReturnable<Vec3> cir) {
final LoadedShip shipMountedTo = VSGameUtilsKt.getShipMountedTo(Entity.class.cast(this));
if (shipMountedTo == null) {
if (Entity.class.cast(this) instanceof final ServerPlayer sPlayer && sPlayer instanceof final IEntityDraggingInformationProvider dragProvider) {
if (dragProvider.getDraggingInformation().isEntityBeingDraggedByAShip() && dragProvider.getDraggingInformation().getServerRelativePlayerYaw() != null) {
final Ship shipDraggedBy = VSGameUtilsKt.getAllShips(level).getById(dragProvider.getDraggingInformation().getLastShipStoodOn());
if (shipDraggedBy != null) {
final float realYRot = (float) EntityDragger.INSTANCE.serversideEyeRotationOrDefault(sPlayer, yRot);
final float f = xRot * (float) (Math.PI / 180.0);
final float g = -realYRot * (float) (Math.PI / 180.0);
final float h = Mth.cos(g);
final float i = Mth.sin(g);
final float j = Mth.cos(f);
final float k = Mth.sin(f);
final Vector3dc originalViewVector = new Vector3d(i * j, -k, h * j);

final ShipTransform shipTransform;
if (shipDraggedBy instanceof ClientShip) {
shipTransform = ((ClientShip) shipDraggedBy).getRenderTransform();
} else {
shipTransform = shipDraggedBy.getShipTransform();
}
final Vec3 newViewVector = VectorConversionsMCKt.toMinecraft(
shipTransform.getShipCoordinatesToWorldCoordinatesRotation().transform(originalViewVector, new Vector3d()));
cir.setReturnValue(newViewVector);
}
}
}
return;
}
final float f = xRot * (float) (Math.PI / 180.0);
Expand All @@ -153,6 +212,28 @@ private void preCalculateViewVector(final float xRot, final float yRot, final Ca
cir.setReturnValue(newViewVector);
}

/**
* @reason Without this and that other mixin, things don't render correctly at high speeds.
* @see org.valkyrienskies.mod.mixin.client.renderer.MixinEntityRenderer
*/
@Inject(method = "shouldRender", at = @At("HEAD"), cancellable = true)
private void onShouldRender(double d, double e, double f, CallbackInfoReturnable<Boolean> cir) {
if (this.draggingInformation.isEntityBeingDraggedByAShip() && this.level.isClientSide) {
final ClientShip ship = VSGameUtilsKt.getShipObjectWorld((ClientLevel) this.level).getAllShips().getById(this.draggingInformation.getLastShipStoodOn());
if (ship != null) {
final ShipTransform shipTransform = ship.getRenderTransform();
if (this.draggingInformation.getRelativePositionOnShip() != null) {
Vector3dc redir = shipTransform.getShipToWorld().transformPosition(this.draggingInformation.getRelativePositionOnShip(), new Vector3d());
double distX = redir.x() - d;
double distY = redir.y() - e;
double distZ = redir.z() - f;
double sqrDist = distX * distX + distY * distY + distZ * distZ;
cir.setReturnValue(shouldRenderAtSqrDistance(sqrDist));
}
}
}
}

// region shadow functions and fields
@Shadow
public Level level;
Expand Down Expand Up @@ -183,6 +264,12 @@ private void preCalculateViewVector(final float xRot, final float yRot, final Ca
@Shadow
public abstract EntityType<?> getType();

@Shadow
private float yRot;

@Shadow
public abstract boolean shouldRenderAtSqrDistance(double d);

@Override
@NotNull
public EntityDraggingInformation getDraggingInformation() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.valkyrienskies.mod.mixin.feature.ai.goal;

import java.util.Random;
import net.minecraft.world.entity.ai.behavior.AcquirePoi.JitteredLinearRetry;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;

@Mixin(JitteredLinearRetry.class)
public interface JitteredLinearRetryAccessor {
@Invoker("<init>")
static JitteredLinearRetry create(Random random, long l) {
throw new AssertionError();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.valkyrienskies.mod.mixin.feature.ai.goal;

import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Position;
import net.minecraft.world.entity.PathfinderMob;
import net.minecraft.world.entity.ai.goal.MoveToBlockGoal;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.valkyrienskies.mod.common.VSGameUtilsKt;

@Mixin(MoveToBlockGoal.class)
public class MixinMoveToBlockGoal {
@Shadow
@Final
protected PathfinderMob mob;

@WrapOperation(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;closerToCenterThan(Lnet/minecraft/core/Position;D)Z"))
private boolean onCloserToCenterThan(BlockPos instance, Position position, double v, Operation<Boolean> original) {
return original.call(new BlockPos(VSGameUtilsKt.toWorldCoordinates(this.mob.level, instance)), position, v);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.valkyrienskies.mod.mixin.feature.ai.goal;

import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.ai.behavior.MoveToTargetSink;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.valkyrienskies.mod.common.VSGameUtilsKt;

@Mixin(MoveToTargetSink.class)
public class MixinMoveToTargetSink {
@WrapOperation(method = "reachedTarget", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;distManhattan(Lnet/minecraft/core/Vec3i;)I"))
private int onDistManhattan(BlockPos instance, Vec3i vec3i, Operation<Integer> original, @Local(argsOnly = true) Mob mob) {
return original.call(new BlockPos(VSGameUtilsKt.toWorldCoordinates(mob.level, instance)), vec3i);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.valkyrienskies.mod.mixin.feature.ai.goal;

import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Position;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.behavior.ValidateNearbyPoi;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.valkyrienskies.mod.common.VSGameUtilsKt;

@Mixin(ValidateNearbyPoi.class)
public class MixinValidateNearbyPoi {
@WrapOperation(method = "checkExtraStartConditions", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;closerToCenterThan(Lnet/minecraft/core/Position;D)Z"))
private boolean onCloserToCenterThan(BlockPos instance, Position position, double v, Operation<Boolean> original, @Local
LivingEntity livingEntity) {
return original.call(new BlockPos(VSGameUtilsKt.toWorldCoordinates(livingEntity.level, instance)), position, v);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.valkyrienskies.mod.mixin.feature.ai.goal.bees;

import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.animal.Bee;
import net.minecraft.world.level.Level;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.valkyrienskies.mod.common.VSGameUtilsKt;

@Mixin(Bee.class)
public abstract class MixinBee extends Entity {

public MixinBee(EntityType<?> entityType, Level level) {
super(entityType, level);
}

@WrapOperation(method = "closerThan", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;closerThan(Lnet/minecraft/core/Vec3i;D)Z"))
private boolean onCloserThan(BlockPos instance, Vec3i vec3i, double v, Operation<Boolean> original) {
return original.call(new BlockPos(VSGameUtilsKt.toWorldCoordinates(this.level, instance)), vec3i, v);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.valkyrienskies.mod.mixin.feature.ai.goal.bees;

import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Position;
import net.minecraft.world.entity.animal.Bee;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.valkyrienskies.mod.common.VSGameUtilsKt;

@Mixin(Bee.BeeEnterHiveGoal.class)
public class MixinEnterHiveGoal {
@Shadow
@Final
Bee field_20367;

@WrapOperation(method = "canBeeUse", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;closerToCenterThan(Lnet/minecraft/core/Position;D)Z"))
private boolean onCloserToCenterThan(BlockPos instance, Position position, double v, Operation<Boolean> original) {
return original.call(new BlockPos(VSGameUtilsKt.toWorldCoordinates(this.field_20367.level, instance)), position, v);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.valkyrienskies.mod.mixin.feature.ai.goal.bees;

import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import java.util.List;
import net.minecraft.core.BlockPos;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.entity.animal.Bee;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import org.joml.Vector3d;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.valkyrienskies.mod.common.VSGameUtilsKt;

@Mixin(Bee.BeeGrowCropGoal.class)
public class MixinGrowCropGoal {
@WrapOperation(method = "tick", at = @At(value = "INVOKE",
target = "Lnet/minecraft/world/level/Level;getBlockState(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/state/BlockState;"))
private BlockState onTick(Level instance, BlockPos blockPos, Operation<BlockState> original) {
List<Vector3d> possibleCandidates = VSGameUtilsKt.transformToNearbyShipsAndWorld(instance, blockPos.getX(), blockPos.getY(), blockPos.getZ(), 1.5);
for (Vector3d candidate : possibleCandidates) {
BlockState blockState = instance.getBlockState(new BlockPos(candidate.x, candidate.y, candidate.z));
if (blockState.is(BlockTags.BEE_GROWABLES)) {
return blockState;
}
}
return original.call(instance, blockPos);
}
}
Loading

0 comments on commit 8f853da

Please sign in to comment.