Implement more realistic flying mechanics

This commit is contained in:
Sollace 2020-10-02 11:55:59 +02:00
parent 86524c10aa
commit 12a56dfe79
4 changed files with 128 additions and 229 deletions

View file

@ -1,50 +0,0 @@
package com.minelittlepony.unicopia.entity.player;
import com.minelittlepony.unicopia.util.MutableVector;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.math.Vec3d;
public class AeronauticalPlayerPhysics extends PlayerPhysics {
private final Aeronautics auronautics = new Aeronautics();
private double gravity;
private int thrustCountdown;
public AeronauticalPlayerPhysics(Pony pony) {
super(pony);
}
@Override
public double calcGravity(double worldConstant) {
return gravity = super.calcGravity(worldConstant);
}
@Override
protected void moveFlying(Entity player, MutableVector velocity) {
PlayerEntity ply = (PlayerEntity)player;
auronautics.pitchAngle = 0;
auronautics.rollAngle = 0;
float yaw = player.getYaw(1);
Vec3d motion = auronautics.calcGravitationalAccelleration(gravity);
if (ply.forwardSpeed != 0 && thrustCountdown-- <= 0) {
thrustCountdown = 20;
player.playSound(getWingSound(), 0.4F, 1);
motion = motion.add(auronautics.calcThrustVelocity(-100));
}
motion = motion.rotateY(yaw).add(velocity.toImmutable()).multiply(1 - auronautics.getDrag());
velocity.x = motion.x;
velocity.y = motion.y;
velocity.z = motion.z;
}
}

View file

@ -1,73 +0,0 @@
package com.minelittlepony.unicopia.entity.player;
import net.minecraft.util.math.Vec3d;
// X - forward
// Y - vertical
// Z - sideways
public class Aeronautics {
public double rollAngle;
public double pitchAngle;
/**
* Gets the normalized direction vector
*/
private Vec3d getMomentVector() {
// sine(angle) = y/h
// cos(angle) = x/h
Vec3d climbVector = new Vec3d(Math.cos(pitchAngle), Math.sin(pitchAngle), 0);
Vec3d bankVector = new Vec3d(0, Math.sin(rollAngle), Math.cos(rollAngle));
return bankVector.add(climbVector).normalize();
}
/**
* Gets the normalized perpendicular vector.
*/
private Vec3d getNormal() {
// sine(angle) = y/h
// cos(angle) = x/h
Vec3d climbVector = new Vec3d(Math.cos(pitchAngle), Math.sin(pitchAngle), 0);
Vec3d bankVector = new Vec3d(0, Math.sin(rollAngle), Math.cos(rollAngle));
return bankVector.crossProduct(climbVector).normalize();
}
/**
* Returns the acceleration vector due to gravity
* parallel to the slope of the incline described
* by the roll and pitch components.
*
* @param gravity The global gravitation constant C
*/
public Vec3d calcGravitationalAccelleration(double gravity) {
return getMomentVector().multiply(
-gravity * Math.signum(pitchAngle),
-gravity,
-gravity * Math.signum(rollAngle)
);
}
/**
* Gets the added thrust vector for the given forwards motion
* and velocity projected against the direction of incline.
*
* @param forwards The forwards thrust speed
* @param velocity The current motion vector
*/
public Vec3d calcThrustVelocity(double forwards) {
return getNormal().add(getMomentVector())
.normalize()
.multiply(forwards * (1 - getDrag()));
}
/**
* The drag due to air resistance.
*/
public double getDrag() {
return 0.0078; // magic number until I figure out what tf I'm doing
}
}

View file

@ -49,13 +49,9 @@ public final class PlayerDimensions {
flyingDimensions = EntityDimensions.changing(dimensions.width, dimensions.height / 2); flyingDimensions = EntityDimensions.changing(dimensions.width, dimensions.height / 2);
} }
return getPredicate().flatMap(e -> e.getTargetDimensions(pony)).orElseGet(() -> { return getPredicate()
if (physics.isFlyingSurvival && physics.isRainboom()) { .flatMap(e -> e.getTargetDimensions(pony))
return flyingDimensions; .orElseGet(() -> physics.isFlyingSurvival ? flyingDimensions : defaultDimensions);
}
return defaultDimensions;
});
} }
private float calculateTargetEyeHeight() { private float calculateTargetEyeHeight() {
@ -65,10 +61,6 @@ public final class PlayerDimensions {
return height; return height;
} }
if (physics.isFlyingSurvival && physics.isRainboom()) {
return 0.5F;
}
return defaultEyeHeight; return defaultEyeHeight;
} }

View file

@ -15,20 +15,21 @@ import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvent; import net.minecraft.sound.SoundEvent;
import net.minecraft.sound.SoundEvents;
import net.minecraft.util.Tickable; import net.minecraft.util.Tickable;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
public class PlayerPhysics extends EntityPhysics<Pony> implements Tickable, Motion, NbtSerialisable { public class PlayerPhysics extends EntityPhysics<Pony> implements Tickable, Motion, NbtSerialisable {
private int ticksInAir; private int ticksInAir;
private float thrustScale = 0;
public boolean isFlyingEither = false; public boolean isFlyingEither = false;
public boolean isFlyingSurvival = false; public boolean isFlyingSurvival = false;
private double lastTickPosX = 0; private Vec3d lastPos = Vec3d.ZERO;
private double lastTickPosZ = 0;
private final PlayerDimensions dimensions; private final PlayerDimensions dimensions;
@ -47,30 +48,16 @@ public class PlayerPhysics extends EntityPhysics<Pony> implements Tickable, Moti
return super.getGravityModifier() * (float)pony.getOwner().getAttributeValue(PlayerAttributes.ENTITY_GRAVTY_MODIFIER); return super.getGravityModifier() * (float)pony.getOwner().getAttributeValue(PlayerAttributes.ENTITY_GRAVTY_MODIFIER);
} }
private boolean checkCanFly() {
if (pony.getOwner().abilities.creativeMode || pony.getOwner().isSpectator()) {
return true;
}
if (pony.hasSpell()) {
Spell effect = pony.getSpell(true);
if (!effect.isDead() && effect instanceof FlightPredicate) {
return ((FlightPredicate)effect).checkCanFly(pony);
}
}
return pony.getSpecies().canFly();
}
protected boolean isRainboom() {
return Math.sqrt(getHorizontalMotion(pony.getOwner())) > 0.4F;
}
@Override @Override
public PlayerDimensions getDimensions() { public PlayerDimensions getDimensions() {
return dimensions; return dimensions;
} }
@Override
public boolean isFlying() {
return isFlyingSurvival && !pony.getOwner().isFallFlying() && !pony.getOwner().hasVehicle();
}
@Override @Override
public void tick() { public void tick() {
PlayerEntity entity = pony.getOwner(); PlayerEntity entity = pony.getOwner();
@ -83,32 +70,37 @@ public class PlayerPhysics extends EntityPhysics<Pony> implements Tickable, Moti
entity.setPose(EntityPose.STANDING); entity.setPose(EntityPose.STANDING);
} }
MutableVector velocity = new MutableVector(entity.getVelocity()); final MutableVector velocity = new MutableVector(entity.getVelocity());
boolean creative = entity.abilities.creativeMode || pony.getOwner().isSpectator(); boolean creative = entity.abilities.creativeMode || pony.getOwner().isSpectator();
entity.abilities.allowFlying = checkCanFly(); boolean canFly = checkCanFly();
if (!creative) { if (!creative) {
entity.abilities.flying |= entity.abilities.allowFlying && isFlyingEither; entity.abilities.flying |= (canFly || entity.abilities.allowFlying) && isFlyingEither;
if ((entity.isOnGround() && entity.isSneaking()) || entity.isTouchingWater()) { if ((entity.isOnGround() && entity.isSneaking()) || entity.isTouchingWater() || entity.horizontalCollision || entity.verticalCollision) {
entity.abilities.flying = false; entity.abilities.flying = false;
} }
} }
isFlyingSurvival = entity.abilities.flying && !creative; isFlyingSurvival = entity.abilities.flying && !creative;
isFlyingEither = isFlyingSurvival || (creative && entity.abilities.flying); isFlyingEither = isFlyingSurvival || (creative && entity.abilities.flying);
if (!creative && !entity.isFallFlying() && isFlyingSurvival && !entity.hasVehicle()) { if (pony.getPhysics().isGravityNegative()) {
entity.setOnGround(!entity.world.isAir(new BlockPos(entity.getX(), entity.getY() + entity.getHeight() + 0.5F, entity.getZ())));
entity.fallDistance = 0; if (entity.isOnGround() || entity.horizontalCollision) {
entity.abilities.flying = false;
isFlyingEither = false;
isFlyingSurvival = false;
}
}
if (isFlying()) {
int level = pony.getLevel().get() + 1; int level = pony.getLevel().get() + 1;
if (ticksInAir > (level * 100)) { if (ticksInAir++ > (level * 100)) {
Bar mana = pony.getMagicalReserves().getMana(); Bar mana = pony.getMagicalReserves().getMana();
mana.add((int)(-getHorizontalMotion(entity) * 50 / level)); mana.add((int)(-getHorizontalMotion(entity) * 50 / level));
@ -128,55 +120,91 @@ public class PlayerPhysics extends EntityPhysics<Pony> implements Tickable, Moti
} }
} }
moveFlying(entity, velocity); entity.fallDistance = 0;
if (ticksInAir++ > 0 && ticksInAir % 30 == 0) { moveFlying(entity, velocity);
entity.playSound(getWingSound(), 0.5F, 1); if (entity.world.hasRain(entity.getBlockPos())) {
applyTurbulance(entity, velocity);
} }
if (entity.world.isClient && ticksInAir % 20 == 0 && entity.getVelocity().length() < 0.29) {
entity.playSound(getWingSound(), 0.5F, 1);
thrustScale = 1;
}
velocity.y -= 0.02;
} else { } else {
ticksInAir = 0; ticksInAir = 0;
if (!creative) {
double horMotion = getHorizontalMotion(entity);
double motion = entity.getPos().subtract(lastPos).lengthSquared();
if (velocity.y > 0 && (horMotion > 0.2 || (motion > 0.2 && velocity.y < -0.2))) {
entity.abilities.flying = true;
isFlyingEither = true;
isFlyingSurvival = true;
velocity.y += horMotion + 0.3;
applyThrust(entity, velocity);
} }
if (pony.getPhysics().isGravityNegative()) {
entity.setOnGround(!entity.world.isAir(new BlockPos(entity.getX(), entity.getY() + entity.getHeight() + 0.5F, entity.getZ())));
if (entity.isOnGround()) {
entity.abilities.flying = false;
isFlyingSurvival = false;
} }
} }
lastTickPosX = entity.getX(); lastPos = new Vec3d(entity.getX(), 0, entity.getZ());
lastTickPosZ = entity.getZ();
entity.setVelocity(velocity.toImmutable()); entity.setVelocity(velocity.toImmutable());
} }
public SoundEvent getWingSound() { private SoundEvent getWingSound() {
return pony.getSpecies() == Race.CHANGELING ? USounds.CHANGELING_BUZZ : USounds.WING_FLAP; return pony.getSpecies() == Race.CHANGELING ? USounds.CHANGELING_BUZZ : USounds.WING_FLAP;
} }
protected void moveFlying(Entity player, MutableVector velocity) { protected void moveFlying(PlayerEntity player, MutableVector velocity) {
applyThrust(player, velocity);
float forward = 0.000015F * (1 + (pony.getLevel().get() / 10F)) * (float)Math.sqrt(getHorizontalMotion(player)); double motion = getHorizontalMotion(player);
boolean sneak = !player.isSneaking();
float forward = 0.000015F * (1 + (pony.getLevel().get() / 10F)) * (float)Math.sqrt(motion);
// vertical drop due to gravity // vertical drop due to gravity
if (sneak) {
velocity.y -= (0.005F - getHorizontalMotion(player) / 100) * getGravitySignum();
} else {
forward += 0.005F; forward += 0.005F;
velocity.y -= 0.0001F * getGravitySignum();
} velocity.y -= (getGravityModifier() * 0.01F) / Math.max(motion * 100, 1);
velocity.x += - forward * MathHelper.sin(player.yaw * 0.017453292F); velocity.x += - forward * MathHelper.sin(player.yaw * 0.017453292F);
velocity.z += forward * MathHelper.cos(player.yaw * 0.017453292F); velocity.z += forward * MathHelper.cos(player.yaw * 0.017453292F);
}
if (player.world.hasRain(player.getBlockPos())) { protected void applyThrust(PlayerEntity player, MutableVector velocity) {
if (pony.sneakingChanged() && player.isSneaking()) {
thrustScale = 1;
player.playSound(getWingSound(), 0.5F, 1);
} else {
thrustScale *= 0.1889F;
}
float thrustStrength = 0.135F * thrustScale;
Vec3d direction = player.getRotationVec(1).normalize().multiply(thrustStrength);
velocity.x += direction.x;
velocity.z += direction.z;
velocity.y += direction.y * 2.45 + Math.abs(direction.y) * 10;
if (player.isSneaking()) {
velocity.y += 0.4 - 0.25;
if (pony.sneakingChanged()) {
velocity.y += 0.75;
}
} else {
velocity.y -= 0.1;
}
}
protected void applyTurbulance(Entity player, MutableVector velocity) {
float glance = 360 * player.world.random.nextFloat(); float glance = 360 * player.world.random.nextFloat();
float forward = 0.015F * player.world.random.nextFloat() * player.world.getRainGradient(1);
forward = 0.015F * player.world.random.nextFloat() * player.world.getRainGradient(1);
if (player.world.random.nextInt(30) == 0) { if (player.world.random.nextInt(30) == 0) {
forward *= 10; forward *= 10;
@ -203,25 +231,32 @@ public class PlayerPhysics extends EntityPhysics<Pony> implements Tickable, Moti
velocity.x += - forward * MathHelper.sin((player.yaw + glance) * 0.017453292F); velocity.x += - forward * MathHelper.sin((player.yaw + glance) * 0.017453292F);
velocity.z += forward * MathHelper.cos((player.yaw + glance) * 0.017453292F); velocity.z += forward * MathHelper.cos((player.yaw + glance) * 0.017453292F);
} }
}
protected double getHorizontalMotion(Entity e) { protected double getHorizontalMotion(Entity e) {
double motionX = e.getX() - lastTickPosX; return Entity.squaredHorizontalLength(e.getPos().subtract(lastPos));
double motionZ = e.getZ() - lastTickPosZ;
return (motionX * motionX) + (motionZ * motionZ);
} }
protected SoundEvent getFallSound(int distance) { private boolean checkCanFly() {
return distance > 4 ? SoundEvents.ENTITY_PLAYER_BIG_FALL : SoundEvents.ENTITY_PLAYER_SMALL_FALL; if (pony.getOwner().abilities.creativeMode || pony.getOwner().isSpectator()) {
return true;
}
if (pony.hasSpell()) {
Spell effect = pony.getSpell(true);
if (!effect.isDead() && effect instanceof FlightPredicate) {
return ((FlightPredicate)effect).checkCanFly(pony);
}
}
return pony.getSpecies().canFly();
} }
public void updateFlightStat(boolean flying) { public void updateFlightStat(boolean flying) {
PlayerEntity entity = pony.getOwner(); PlayerEntity entity = pony.getOwner();
entity.abilities.allowFlying = checkCanFly(); boolean canFly = checkCanFly();
if (entity.abilities.allowFlying) { if (canFly || entity.abilities.allowFlying) {
entity.abilities.flying |= flying; entity.abilities.flying |= flying;
isFlyingSurvival = entity.abilities.flying; isFlyingSurvival = entity.abilities.flying;
@ -248,9 +283,4 @@ public class PlayerPhysics extends EntityPhysics<Pony> implements Tickable, Moti
pony.getOwner().calculateDimensions(); pony.getOwner().calculateDimensions();
} }
@Override
public boolean isFlying() {
return isFlyingSurvival;
}
} }