Skip to content

Commit

Permalink
[2.0.3] Vanilla parity, slowdown adjustments, mod compatibility, and …
Browse files Browse the repository at this point in the history
…Minecraft 1.19.4 support

- Fix falling from cliffs to perpendicular tracks
- Slow down (8m/s) before descent and before detector/activator rails
- Overwrite moveOnRail only for LivingEntity ridden carts
- Mixin now targets AbstractMinecartEntity again
- Built for Minecraft 1.19.4
  • Loading branch information
audaki committed Apr 6, 2023
1 parent 7cf1e44 commit 54b95fd
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 72 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.0.3] - 2023-04-06
- Vanilla parity: When falling down after rail on a cliff the behaviour is now vanilla, that means you can now fall down from a cliff to a perpendicular rail track.
- A little more slowdown before descending rails. Due to the vanilla parity you now start descending with 8m/s instead of 9.2m/s
- A little more slowdown before detector / activator rails. On and directly before detector/activator rails you also move with 8m/s instead of 9.2m/s now.
- Mod Compatibility: We only overwrite the moveOnRail function now when there is actually a LivingEntity riding the cart
- (technical) Mixin is targeting AbstractMinecartEntity again so we can inject and cancel the overwrite
- Built for Minecraft 1.19.4

## [2.0.2] - 2023-03-15
- Starting with this version only mc 1.19+ receives mod updates (2.0.1 is very stable for mc 1.18 and 1.17)
- (technical) Refactored mixin to target MinecartEntity directly instead of AbstractMinecartEntity, therefore potentially increasing compatibility to other mods even more
Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ org.gradle.jvmargs=-Xmx4G
# Fabric Properties, @see https://fabricmc.net/develop
minecraft_version=1.19.4
yarn_mappings=1.19.4+build.1
loader_version=0.14.17
loader_version=0.14.19

# Mod Properties
mod_version = v2.0.2
mod_version = v2.0.3
maven_group = audaki.minecraft
archives_base_name = audaki_cart_engine
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package audaki.cart_engine.mixin;

import com.google.common.collect.Maps;
import com.mojang.datafixers.util.Pair;
import net.minecraft.block.AbstractRailBlock;
import net.minecraft.block.BlockState;
Expand All @@ -9,62 +8,84 @@
import net.minecraft.block.enums.RailShape;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.MovementType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.vehicle.AbstractMinecartEntity;
import net.minecraft.entity.vehicle.AbstractMinecartEntity.Type;
import net.minecraft.entity.vehicle.MinecartEntity;
import net.minecraft.util.Util;
import net.minecraft.util.math.*;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Supplier;

@Mixin(value = MinecartEntity.class, priority = 500)
public abstract class MinecartEntityMixin extends AbstractMinecartEntity {
public MinecartEntityMixin(EntityType<?> type, World world) {
@Mixin(AbstractMinecartEntity.class) // lower value, higher priority - apply first so other mods can still mixin
public abstract class AbstractMinecartEntityMixin extends Entity {
public AbstractMinecartEntityMixin(EntityType<?> type, World world) {
super(type, world);
}

private static final Map<RailShape, Pair<Vec3i, Vec3i>> ADJACENT_RAIL_POSITIONS_BY_SHAPE;

static {
ADJACENT_RAIL_POSITIONS_BY_SHAPE = (Map) Util.make(Maps.newEnumMap(RailShape.class), (map) -> {
Vec3i vec3i = Direction.WEST.getVector();
Vec3i vec3i2 = Direction.EAST.getVector();
Vec3i vec3i3 = Direction.NORTH.getVector();
Vec3i vec3i4 = Direction.SOUTH.getVector();
Vec3i vec3i5 = vec3i.down();
Vec3i vec3i6 = vec3i2.down();
Vec3i vec3i7 = vec3i3.down();
Vec3i vec3i8 = vec3i4.down();
map.put(RailShape.NORTH_SOUTH, Pair.of(vec3i3, vec3i4));
map.put(RailShape.EAST_WEST, Pair.of(vec3i, vec3i2));
map.put(RailShape.ASCENDING_EAST, Pair.of(vec3i5, vec3i2));
map.put(RailShape.ASCENDING_WEST, Pair.of(vec3i, vec3i6));
map.put(RailShape.ASCENDING_NORTH, Pair.of(vec3i3, vec3i8));
map.put(RailShape.ASCENDING_SOUTH, Pair.of(vec3i7, vec3i4));
map.put(RailShape.SOUTH_EAST, Pair.of(vec3i4, vec3i2));
map.put(RailShape.SOUTH_WEST, Pair.of(vec3i4, vec3i));
map.put(RailShape.NORTH_WEST, Pair.of(vec3i3, vec3i));
map.put(RailShape.NORTH_EAST, Pair.of(vec3i3, vec3i2));
});
@Shadow
protected abstract boolean willHitBlockAt(BlockPos pos);

@Shadow
public abstract Vec3d snapPositionToRail(double x, double y, double z);

@Shadow
protected abstract void applySlowdown();

@Shadow
protected abstract double getMaxSpeed();

@Shadow
protected abstract Type getMinecartType();

@Shadow
private static Pair<Vec3i, Vec3i> getAdjacentRailPositionsByShape(RailShape shape) {
return Pair.of(Direction.NORTH.getVector(), Direction.SOUTH.getVector());
}

private boolean willHitBlockAt(BlockPos pos) {
return this.world.getBlockState(pos).isSolidBlock(this.world, pos);
private static boolean isEligibleFastRail(BlockState state) {
return state.isOf(Blocks.RAIL) || (state.isOf(Blocks.POWERED_RAIL) && state.get(PoweredRailBlock.POWERED));
}

private static Pair<Vec3i, Vec3i> getAdjacentRailPositionsByShape(RailShape shape) {
return (Pair)ADJACENT_RAIL_POSITIONS_BY_SHAPE.get(shape);
private static RailShape getRailShape(BlockState state) {
if (!(state.getBlock() instanceof AbstractRailBlock railBlock))
throw new IllegalArgumentException("No rail shape found");

return state.get(railBlock.getShapeProperty());
}

public void moveOnRail(BlockPos pos, BlockState state) {
@Inject(at = @At("HEAD"), method = "moveOnRail", cancellable = true)
protected void moveOnRailOverwrite(BlockPos pos, BlockState state, CallbackInfo ci) {

// We only change logic for rideable minecarts so we don't break hopper/chest minecart creations
if (this.getMinecartType() != Type.RIDEABLE) {
return;
}

// We only change logic when the minecart is currently being ridden by a living entity (player/villager/mob)
boolean hasLivingRider = this.getFirstPassenger() instanceof LivingEntity;
if (!hasLivingRider) {
return;
}

this.modifiedMoveOnRail(pos, state);
ci.cancel();
}

protected void modifiedMoveOnRail(BlockPos pos, BlockState state) {
this.onLanding();
double d = this.getX();
double e = this.getY();
Expand All @@ -86,7 +107,7 @@ public void moveOnRail(BlockPos pos, BlockState state) {
}

Vec3d velocity = this.getVelocity();
RailShape railShape = state.get(((AbstractRailBlock) state.getBlock()).getShapeProperty());
RailShape railShape = getRailShape(state);
switch (railShape) {
case ASCENDING_EAST -> {
this.setVelocity(velocity.add(-g, 0.0D, 0.0D));
Expand All @@ -106,6 +127,7 @@ public void moveOnRail(BlockPos pos, BlockState state) {
}
}


velocity = this.getVelocity();
Pair<Vec3i, Vec3i> adjacentRailPositions = getAdjacentRailPositionsByShape(railShape);
Vec3i adjacentRail1RelPos = adjacentRailPositions.getFirst();
Expand All @@ -124,7 +146,6 @@ public void moveOnRail(BlockPos pos, BlockState state) {
double horizontalMomentumPerTick = this.getVelocity().horizontalLength();

Supplier<Double> calculateMaxHorizontalMovementPerTick = () -> {
final double fallbackSpeedFactor = 1.15D;
double fallback = this.getMaxSpeed();

if (!this.hasPassengers())
Expand All @@ -133,59 +154,56 @@ public void moveOnRail(BlockPos pos, BlockState state) {
if (horizontalMomentumPerTick < vanillaMaxHorizontalMovementPerTick)
return fallback;

if (!isEligibleFastRail(state))
return fallback;

for (Vec3i directlyAdjDiff: List.of(adjacentRailPositions.getFirst(), adjacentRailPositions.getSecond())) {
BlockPos directlyAdjPos = pos.add(directlyAdjDiff);
BlockState directlyAdjState = this.world.getBlockState(directlyAdjPos);

if (!isEligibleFastRail(directlyAdjState))
return fallback;
}

final double fallbackSpeedFactor = 1.15D;
// Allow faster fallback speed when there is rail around the cart, but we have rail shape changes
fallback *= fallbackSpeedFactor;

boolean hasEligibleShape = railShape == RailShape.NORTH_SOUTH || railShape == RailShape.EAST_WEST;
if (!hasEligibleShape)
return fallback;

boolean hasEligibleType = state.isOf(Blocks.RAIL) || (state.isOf(Blocks.POWERED_RAIL) && state.get(PoweredRailBlock.POWERED));
if (!hasEligibleType)
return fallback;

AtomicInteger eligibleNeighbors = new AtomicInteger();

HashSet<BlockPos> checkedPositions = new HashSet<>();
checkedPositions.add(pos);

BiFunction<BlockPos, RailShape, ArrayList<Pair<BlockPos, RailShape>>> checkNeighbors = (cPos, cRailShape) -> {
Pair<Vec3i, Vec3i> cAdjPosDiff = getAdjacentRailPositionsByShape(cRailShape);
ArrayList<Pair<BlockPos, RailShape>> newNeighbors = new ArrayList<>();

BlockPos n1Pos = cPos.add(cAdjPosDiff.getFirst());

if (!checkedPositions.contains(n1Pos)) {
BiFunction<BlockPos, RailShape, ArrayList<Pair<BlockPos, RailShape>>> checkNeighbors = (checkPos, checkRailShape) -> {

BlockState n1State = this.world.getBlockState(n1Pos);
boolean n1HasEligibleType = n1State.isOf(Blocks.RAIL) || (n1State.isOf(Blocks.POWERED_RAIL) && n1State.get(PoweredRailBlock.POWERED));
if (!n1HasEligibleType)
return new ArrayList<>();
Pair<Vec3i, Vec3i> adjDiffPair = getAdjacentRailPositionsByShape(checkRailShape);

RailShape n1RailShape = n1State.get(((AbstractRailBlock) n1State.getBlock()).getShapeProperty());
if (n1RailShape != railShape)
return new ArrayList<>();
ArrayList<Pair<BlockPos, RailShape>> newNeighbors = new ArrayList<>();

checkedPositions.add(n1Pos);
eligibleNeighbors.incrementAndGet();
newNeighbors.add(Pair.of(n1Pos, n1RailShape));
}
for (Vec3i adjDiff: List.of(adjDiffPair.getFirst(), adjDiffPair.getSecond())) {
BlockPos nborPos = checkPos.add(adjDiff);

BlockPos n2Pos = cPos.add(cAdjPosDiff.getSecond());
if (!checkedPositions.contains(n2Pos)) {
if (checkedPositions.contains(nborPos))
continue;

BlockState n2State = this.world.getBlockState(n2Pos);
boolean n2HasEligibleType = n2State.isOf(Blocks.RAIL) || (n2State.isOf(Blocks.POWERED_RAIL) && n2State.get(PoweredRailBlock.POWERED));
if (!n2HasEligibleType)
BlockState nborState = this.world.getBlockState(nborPos);
if (!isEligibleFastRail(nborState))
return new ArrayList<>();

RailShape n2RailShape = n2State.get(((AbstractRailBlock) n2State.getBlock()).getShapeProperty());

if (n2RailShape != railShape)
RailShape nborShape = getRailShape(nborState);
if (nborShape != railShape)
return new ArrayList<>();

checkedPositions.add(n2Pos);
checkedPositions.add(nborPos);
eligibleNeighbors.incrementAndGet();
newNeighbors.add(Pair.of(n2Pos, n2RailShape));
// Adding the neighbor rail shape currently has no use, since we abort on rail shape change anyway
// Code stays as is for now so we can differentiate between types of rail shape changes later
newNeighbors.add(Pair.of(nborPos, nborShape));
}

return newNeighbors;
Expand Down Expand Up @@ -309,9 +327,9 @@ public void moveOnRail(BlockPos pos, BlockState state) {
vec3d7 = this.getVelocity();
af = vec3d7.horizontalLength();
this.setVelocity(
af * MathHelper.clamp((double) (ac - pos.getX()), -1.0D, 1.0D),
af * MathHelper.clamp(ac - pos.getX(), -1.0D, 1.0D),
vec3d7.y,
af * MathHelper.clamp((double) (ad - pos.getZ()), -1.0D, 1.0D));
af * MathHelper.clamp(ad - pos.getZ(), -1.0D, 1.0D));
}

if (onPoweredRail) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/audaki_cart_engine.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"package": "audaki.cart_engine.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
"MinecartEntityMixin"
"AbstractMinecartEntityMixin"
],
"injectors": {
"defaultRequire": 1
Expand Down

0 comments on commit 54b95fd

Please sign in to comment.