Merge branch '1.20-bugfix' into 1.20.2-bugfix

This commit is contained in:
Sollace 2023-10-28 22:27:34 +01:00
commit 10227325d5
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
10 changed files with 167 additions and 83 deletions

View file

@ -42,6 +42,10 @@ public enum FlightType {
return this == INSECTOID ? 0.66F : 1; return this == INSECTOID ? 0.66F : 1;
} }
public FlightType or(FlightType other) {
return ordinal() > other.ordinal() ? this : other;
}
/** /**
* Predicate for abilities to control whether a player can fly. * Predicate for abilities to control whether a player can fly.
* *

View file

@ -103,8 +103,7 @@ public record Race (Supplier<Composite> compositeSupplier, boolean canCast, Flig
} }
public Identifier getId() { public Identifier getId() {
Identifier id = REGISTRY.getId(this); return REGISTRY.getId(this);
return id;
} }
public Text getDisplayName() { public Text getDisplayName() {
@ -126,10 +125,6 @@ public record Race (Supplier<Composite> compositeSupplier, boolean canCast, Flig
} }
public boolean isPermitted(@Nullable PlayerEntity sender) { public boolean isPermitted(@Nullable PlayerEntity sender) {
if (isOp() && (sender == null || !sender.getAbilities().creativeMode)) {
return false;
}
Set<String> whitelist = Unicopia.getConfig().speciesWhiteList.get(); Set<String> whitelist = Unicopia.getConfig().speciesWhiteList.get();
return isUnset() return isUnset()
@ -220,6 +215,13 @@ public record Race (Supplier<Composite> compositeSupplier, boolean canCast, Flig
public boolean canCast() { public boolean canCast() {
return any(Race::canCast); return any(Race::canCast);
} }
public FlightType flightType() {
if (pseudo() == null) {
return physical().flightType();
}
return physical().flightType().or(pseudo().flightType());
}
} }
} }

View file

@ -54,13 +54,19 @@ public class SproutBlock extends CropBlock implements TintedBlock {
@Override @Override
public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
super.randomTick(state, world, pos, random); super.randomTick(state, world, pos, random);
onGrow(world, world.getBlockState(pos), pos); state = world.getBlockState(pos);
if (state.isOf(this)) {
onGrow(world, state, pos);
}
} }
@Override @Override
public void applyGrowth(World world, BlockPos pos, BlockState state) { public void applyGrowth(World world, BlockPos pos, BlockState state) {
super.applyGrowth(world, pos, state); super.applyGrowth(world, pos, state);
onGrow(world, world.getBlockState(pos), pos); state = world.getBlockState(pos);
if (state.isOf(this)) {
onGrow(world, world.getBlockState(pos), pos);
}
} }
@Override @Override
@ -81,7 +87,7 @@ public class SproutBlock extends CropBlock implements TintedBlock {
protected void mature(World world, BlockState state, BlockPos pos) { protected void mature(World world, BlockState state, BlockPos pos) {
state = matureState.get(); state = matureState.get();
world.setBlockState(pos, matureState.get()); world.setBlockState(pos, state);
BlockSoundGroup group = state.getSoundGroup(); BlockSoundGroup group = state.getSoundGroup();
world.playSound(null, pos, group.getPlaceSound(), SoundCategory.BLOCKS, group.getVolume(), group.getPitch()); world.playSound(null, pos, group.getPlaceSound(), SoundCategory.BLOCKS, group.getVolume(), group.getPitch());
} }

View file

@ -20,10 +20,10 @@ import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
public class WeatherVaneBlock extends BlockWithEntity { public class WeatherVaneBlock extends BlockWithEntity {
/*private static final VoxelShape SHAPE = VoxelShapes.union( private static final VoxelShape SHAPE = VoxelShapes.union(
Block.createCuboidShape(7.5F, 0, 7.5F, 8.5F, 14, 8.5F), Block.createCuboidShape(7.5F, 0, 7.5F, 8.5F, 14, 8.5F),
Block.createCuboidShape(7, 0, 7, 9, 1, 9) Block.createCuboidShape(7, 0, 7, 9, 1, 9)
);*/ );
protected WeatherVaneBlock(Settings settings) { protected WeatherVaneBlock(Settings settings) {
super(settings); super(settings);
@ -32,10 +32,7 @@ public class WeatherVaneBlock extends BlockWithEntity {
@Deprecated @Deprecated
@Override @Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return VoxelShapes.union( return SHAPE;
Block.createCuboidShape(7.5F, 0, 7.5F, 8.5F, 14, 8.5F),
Block.createCuboidShape(7, 0, 7, 9, 1, 9)
);
} }
@Override @Override

View file

@ -112,7 +112,11 @@ public class RaceChangeStatusEffect extends StatusEffect {
magic.getExertion().set(0); magic.getExertion().set(0);
if (!pony.asEntity().isCreative()) { if (!pony.asEntity().isCreative()) {
entity.damage(Living.living(entity).damageOf(UDamageTypes.TRIBE_SWAP), Float.MAX_VALUE); if (!entity.damage(Living.living(entity).damageOf(UDamageTypes.TRIBE_SWAP), Float.MAX_VALUE)) {
entity.setHealth(0);
pony.setRespawnRace(Race.UNSET);
pony.setSpecies(race);
}
} }
} else { } else {
eq.setSpecies(race); eq.setSpecies(race);

View file

@ -19,6 +19,8 @@ import com.minelittlepony.unicopia.item.AmuletItem;
import com.minelittlepony.unicopia.item.ChargeableItem; import com.minelittlepony.unicopia.item.ChargeableItem;
import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.item.UItems;
import com.minelittlepony.unicopia.item.enchantment.UEnchantments; import com.minelittlepony.unicopia.item.enchantment.UEnchantments;
import com.minelittlepony.unicopia.network.Channel;
import com.minelittlepony.unicopia.network.MsgPlayerFlightControlsInput;
import com.minelittlepony.unicopia.particle.*; import com.minelittlepony.unicopia.particle.*;
import com.minelittlepony.unicopia.projectile.ProjectileUtil; import com.minelittlepony.unicopia.projectile.ProjectileUtil;
import com.minelittlepony.unicopia.server.world.BlockDestructionManager; import com.minelittlepony.unicopia.server.world.BlockDestructionManager;
@ -130,7 +132,7 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
@Override @Override
public boolean isGliding() { public boolean isGliding() {
return ticksToGlide <= 0 && isFlying() && !entity.isSneaking(); return ticksToGlide <= 0 && isFlying() && !pony.getJumpingHeuristic().getState();
} }
@Override @Override
@ -153,7 +155,7 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
float spreadAmount = -0.5F; float spreadAmount = -0.5F;
if (isFlying()) { if (isFlying()) {
if (getFlightType() == FlightType.INSECTOID) { if (lastFlightType == FlightType.INSECTOID) {
spreadAmount += Math.sin(pony.asEntity().age * 4F) * 8; spreadAmount += Math.sin(pony.asEntity().age * 4F) * 8;
} else { } else {
if (isGliding()) { if (isGliding()) {
@ -181,6 +183,10 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
} }
public FlightType getFlightType() { public FlightType getFlightType() {
return lastFlightType;
}
private FlightType recalculateFlightType() {
DimensionType dimension = entity.getWorld().getDimension(); DimensionType dimension = entity.getWorld().getDimension();
if ((RegistryUtils.isIn(entity.getWorld(), dimension, RegistryKeys.DIMENSION_TYPE, UTags.HAS_NO_ATMOSPHERE) if ((RegistryUtils.isIn(entity.getWorld(), dimension, RegistryKeys.DIMENSION_TYPE, UTags.HAS_NO_ATMOSPHERE)
@ -197,7 +203,7 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
.filter(effect -> !effect.isDead() && effect instanceof FlightType.Provider) .filter(effect -> !effect.isDead() && effect instanceof FlightType.Provider)
.map(effect -> ((FlightType.Provider)effect).getFlightType()) .map(effect -> ((FlightType.Provider)effect).getFlightType())
.filter(FlightType::isPresent) .filter(FlightType::isPresent)
.orElse(pony.getObservedSpecies().flightType()); .orElse(pony.getCompositeRace().flightType());
} }
public void cancelFlight(boolean force) { public void cancelFlight(boolean force) {
@ -224,6 +230,10 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
public void tick() { public void tick() {
super.tick(); super.tick();
if (pony.isClient() && this.isFlying()) {
Channel.FLIGHT_CONTROLS_INPUT.sendToServer(new MsgPlayerFlightControlsInput(pony));
}
prevThrustScale = thrustScale; prevThrustScale = thrustScale;
if (wallHitCooldown > 0) { if (wallHitCooldown > 0) {
@ -246,20 +256,21 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
entity.setPose(EntityPose.STANDING); entity.setPose(EntityPose.STANDING);
} }
FlightType type = getFlightType(); FlightType type = recalculateFlightType();
boolean typeChanged = type != lastFlightType && (lastFlightType.isArtifical() || type.isArtifical()); boolean typeChanged = type != lastFlightType;
if (typeChanged) { if (typeChanged && (lastFlightType.isArtifical() || type.isArtifical())) {
pony.spawnParticles(ParticleTypes.CLOUD, 10); pony.spawnParticles(ParticleTypes.CLOUD, 10);
playSound(entity.getWorld().getDimension().ultrawarm() ? USounds.ITEM_ICARUS_WINGS_CORRUPT : USounds.ITEM_ICARUS_WINGS_PURIFY, 0.1125F, 1.5F); playSound(entity.getWorld().getDimension().ultrawarm() ? USounds.ITEM_ICARUS_WINGS_CORRUPT : USounds.ITEM_ICARUS_WINGS_PURIFY, 0.1125F, 1.5F);
} }
entity.getAbilities().allowFlying = type.canFlyCreative(entity); lastFlightType = type;
entity.getAbilities().allowFlying = lastFlightType.canFlyCreative(entity);
boolean creative = entity.isCreative() || entity.isSpectator(); boolean creative = entity.isCreative() || entity.isSpectator();
boolean startedFlyingCreative = !creative && isFlyingEither != entity.getAbilities().flying; boolean startedFlyingCreative = !creative && isFlyingEither != entity.getAbilities().flying;
if (!creative) { if (!creative) {
@ -271,8 +282,8 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
isCancelled = false; isCancelled = false;
} }
entity.getAbilities().flying |= (type.canFly() || entity.getAbilities().allowFlying) && isFlyingEither; entity.getAbilities().flying |= (lastFlightType.canFly() || entity.getAbilities().allowFlying) && isFlyingEither;
if (!type.canFly() && (type != lastFlightType)) { if (!lastFlightType.canFly() && typeChanged) {
entity.getAbilities().flying = false; entity.getAbilities().flying = false;
} }
@ -288,8 +299,6 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
cancelFlight(false); cancelFlight(false);
} }
} }
if (isGravityNegative()) { if (isGravityNegative()) {
@ -302,7 +311,6 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
} }
} }
lastFlightType = type;
isFlyingSurvival = entity.getAbilities().flying && !creative; isFlyingSurvival = entity.getAbilities().flying && !creative;
isFlyingEither = isFlyingSurvival || (creative && entity.getAbilities().flying); isFlyingEither = isFlyingSurvival || (creative && entity.getAbilities().flying);
@ -310,10 +318,10 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
entity.calculateDimensions(); entity.calculateDimensions();
} }
if (type.canFly()) { if (lastFlightType.canFly()) {
if (isFlying()) { if (isFlying()) {
ticksInAir++; ticksInAir++;
tickFlight(type, velocity); tickFlight(lastFlightType, velocity);
int strafing = (int)Math.signum(entity.sidewaysSpeed); int strafing = (int)Math.signum(entity.sidewaysSpeed);
if (strafing != prevStrafe) { if (strafing != prevStrafe) {
@ -321,8 +329,8 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
strafe = 1; strafe = 1;
ticksToGlide = MAX_TICKS_TO_GLIDE; ticksToGlide = MAX_TICKS_TO_GLIDE;
if (!SpellPredicate.IS_DISGUISE.isOn(pony)) { if (!SpellPredicate.IS_DISGUISE.isOn(pony)) {
if (type != FlightType.INSECTOID) { if (lastFlightType != FlightType.INSECTOID) {
playSound(type.getWingFlapSound(), 0.25F, entity.getSoundPitch() * type.getWingFlapSoundPitch()); playSound(lastFlightType.getWingFlapSound(), 0.25F, entity.getSoundPitch() * lastFlightType.getWingFlapSoundPitch());
} }
entity.getWorld().emitGameEvent(entity, GameEvent.ELYTRA_GLIDE, entity.getPos()); entity.getWorld().emitGameEvent(entity, GameEvent.ELYTRA_GLIDE, entity.getPos());
} }
@ -356,7 +364,7 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
descentRate = 0; descentRate = 0;
ticksDiving = 0; ticksDiving = 0;
if (Abilities.RAINBOOM.canUse(pony.getSpecies()) && entity.isOnGround()) { if (Abilities.RAINBOOM.canUse(pony.getCompositeRace()) && entity.isOnGround()) {
pony.getMagicalReserves().getCharge().set(0); pony.getMagicalReserves().getCharge().set(0);
} }
@ -402,16 +410,14 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
entity.fallDistance = 0; entity.fallDistance = 0;
if (type.isAvian()) { applyThrust(velocity);
applyThrust(velocity);
if (type.isAvian()) {
if (pony.getObservedSpecies() != Race.BAT && entity.getWorld().random.nextInt(9000) == 0) { if (pony.getObservedSpecies() != Race.BAT && entity.getWorld().random.nextInt(9000) == 0) {
entity.dropItem(UItems.PEGASUS_FEATHER); entity.dropItem(UItems.PEGASUS_FEATHER);
playSound(USounds.ENTITY_PLAYER_PEGASUS_MOLT, 0.3F, 1); playSound(USounds.ENTITY_PLAYER_PEGASUS_MOLT, 0.3F, 1);
UCriteria.SHED_FEATHER.trigger(entity); UCriteria.SHED_FEATHER.trigger(entity);
} }
} else {
applyThrust(velocity);
} }
moveFlying(velocity); moveFlying(velocity);
@ -427,8 +433,6 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
InteractionManager.instance().playLoopingSound(entity, InteractionManager.SOUND_GLIDING, entity.getId()); InteractionManager.instance().playLoopingSound(entity, InteractionManager.SOUND_GLIDING, entity.getId());
} }
} }
} else if (type == FlightType.INSECTOID && !SpellPredicate.IS_DISGUISE.isOn(pony)) { } else if (type == FlightType.INSECTOID && !SpellPredicate.IS_DISGUISE.isOn(pony)) {
if (entity.getWorld().isClient && !soundPlaying) { if (entity.getWorld().isClient && !soundPlaying) {
soundPlaying = true; soundPlaying = true;
@ -467,7 +471,7 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
stack.damage(minDamage + entity.getWorld().random.nextInt(50), entity, e -> e.sendEquipmentBreakStatus(EquipmentSlot.CHEST)); stack.damage(minDamage + entity.getWorld().random.nextInt(50), entity, e -> e.sendEquipmentBreakStatus(EquipmentSlot.CHEST));
} }
if (!getFlightType().canFly()) { if (!lastFlightType.canFly()) {
playSound(USounds.ITEM_ICARUS_WINGS_EXHAUSTED, 1, 2); playSound(USounds.ITEM_ICARUS_WINGS_EXHAUSTED, 1, 2);
cancelFlight(false); cancelFlight(false);
} }
@ -475,7 +479,7 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
} }
private void playSound(SoundEvent sound, float volume, float pitch) { private void playSound(SoundEvent sound, float volume, float pitch) {
entity.getWorld().playSoundFromEntity(null, entity, sound, SoundCategory.PLAYERS, volume, pitch); entity.getWorld().playSoundFromEntity(entity, entity, sound, SoundCategory.PLAYERS, volume, pitch);
} }
private void tickNaturalFlight(MutableVector velocity) { private void tickNaturalFlight(MutableVector velocity) {
@ -554,12 +558,14 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
isFlyingEither = true; isFlyingEither = true;
isFlyingSurvival = true; isFlyingSurvival = true;
thrustScale = 0; thrustScale = 0;
descentRate = 0;
entity.calculateDimensions(); entity.calculateDimensions();
if (entity.isOnGround() || !force) { if (entity.isOnGround() || !force) {
Supplier<Vec3d> vec = VecHelper.sphere(pony.asWorld().getRandom(), 0.5D); Supplier<Vec3d> pos = VecHelper.sphere(pony.asWorld().getRandom(), 0.5D);
pony.spawnParticles(ParticleTypes.CAMPFIRE_COSY_SMOKE, vec, vec, 5); Supplier<Vec3d> vel = VecHelper.sphere(pony.asWorld().getRandom(), 0.15D);
pony.spawnParticles(ParticleTypes.CLOUD, vec, vec, 5); pony.spawnParticles(ParticleTypes.CAMPFIRE_COSY_SMOKE, pos, vel, 5);
pony.spawnParticles(ParticleTypes.CLOUD, pos, vel, 5);
} }
} }
@ -625,11 +631,12 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
if (descentRate < 0) { if (descentRate < 0) {
descentRate *= 0.8F; descentRate *= 0.8F;
} }
velocity.y -= descentRate * getGravityModifier(); velocity.y -= descentRate * getGravityModifier();
} }
private void applyThrust(MutableVector velocity) { private void applyThrust(MutableVector velocity) {
boolean manualFlap = pony.sneakingChanged() && entity.isSneaking(); boolean manualFlap = pony.getJumpingHeuristic().hasChanged(Heuristic.ONCE) && pony.getJumpingHeuristic().getState();
if (manualFlap) { if (manualFlap) {
flapping = true; flapping = true;
ticksToGlide = MAX_TICKS_TO_GLIDE; ticksToGlide = MAX_TICKS_TO_GLIDE;
@ -639,24 +646,26 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
boolean hovering = entity.getVelocity().horizontalLength() < 0.1; boolean hovering = entity.getVelocity().horizontalLength() < 0.1;
if (thrustScale <= 0.000001F & flapping) { if (lastFlightType == FlightType.INSECTOID) {
flapping = false; descentRate = pony.getJumpingHeuristic().getState() ? -0.5F : 0;
if (!SpellPredicate.IS_DISGUISE.isOn(pony)) { } else {
if (getFlightType() != FlightType.INSECTOID) { if (thrustScale <= 0.000001F & flapping) {
playSound(getFlightType().getWingFlapSound(), 0.25F, entity.getSoundPitch() * getFlightType().getWingFlapSoundPitch()); flapping = false;
if (!SpellPredicate.IS_DISGUISE.isOn(pony)) {
playSound(lastFlightType.getWingFlapSound(), 0.25F, entity.getSoundPitch() * lastFlightType.getWingFlapSoundPitch());
entity.getWorld().emitGameEvent(entity, GameEvent.ELYTRA_GLIDE, entity.getPos());
}
thrustScale = 1;
if (manualFlap) {
descentRate -= 0.5;
} else {
descentRate = Math.max(0, descentRate / 2);
} }
entity.getWorld().emitGameEvent(entity, GameEvent.ELYTRA_GLIDE, entity.getPos());
}
thrustScale = 1;
if (manualFlap) {
descentRate -= 0.5;
} else {
descentRate = Math.max(0, descentRate / 2);
} }
} }
float heavyness = EnchantmentHelper.getEquipmentLevel(UEnchantments.HEAVY, entity); float heavyness = EnchantmentHelper.getEquipmentLevel(UEnchantments.HEAVY, entity);
float thrustStrength = 0.135F * thrustScale; float thrustStrength = 0.235F * thrustScale;
if (heavyness > 0) { if (heavyness > 0) {
thrustStrength /= 1 + heavyness; thrustStrength /= 1 + heavyness;
@ -664,9 +673,13 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
Vec3d direction = entity.getRotationVec(1).normalize().multiply(thrustStrength); Vec3d direction = entity.getRotationVec(1).normalize().multiply(thrustStrength);
if (!hovering) { if (hovering) {
velocity.x += direction.x; if (entity.isSneaking()) {
velocity.z += direction.z; velocity.y -= 0.2F;
}
} else {
velocity.x += direction.x * 1.3F;
velocity.z += direction.z * 1.3F;
velocity.y += ((direction.y * 2.45 + Math.abs(direction.y) * 10)) * getGravitySignum();// - heavyness / 5F velocity.y += ((direction.y * 2.45 + Math.abs(direction.y) * 10)) * getGravitySignum();// - heavyness / 5F
} }
@ -709,10 +722,11 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
* Called when a player's species changes to update whether they can fly or not * Called when a player's species changes to update whether they can fly or not
*/ */
public void updateFlightState() { public void updateFlightState() {
FlightType type = getFlightType(); FlightType type = recalculateFlightType();
entity.getAbilities().allowFlying = type.canFlyCreative(entity); entity.getAbilities().allowFlying = type.canFlyCreative(entity);
entity.getAbilities().flying &= type.canFly() || entity.getAbilities().allowFlying; entity.getAbilities().flying &= type.canFly() || entity.getAbilities().allowFlying;
isFlyingSurvival = entity.getAbilities().flying; isFlyingSurvival = entity.getAbilities().flying;
lastFlightType = type;
} }
public void dashForward(float speed) { public void dashForward(float speed) {

View file

@ -94,8 +94,7 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
private final Interpolator interpolator = new LinearInterpolator(); private final Interpolator interpolator = new LinearInterpolator();
@Nullable private Race.Composite compositeRace = Race.UNSET.composite();
private Race.Composite compositeRace;
private Race respawnRace = Race.UNSET; private Race respawnRace = Race.UNSET;
private boolean dirty; private boolean dirty;
@ -220,19 +219,6 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
*/ */
@Override @Override
public Race.Composite getCompositeRace() { public Race.Composite getCompositeRace() {
if (compositeRace == null || entity.age % 2 == 0) {
compositeRace = getSpellSlot()
.get(SpellPredicate.IS_MIMIC, true)
.map(AbstractDisguiseSpell::getDisguise)
.map(EntityAppearance::getAppearance)
.flatMap(Pony::of)
.map(Pony::getSpecies)
.orElseGet(this::getSpecies).composite(
AmuletSelectors.UNICORN_AMULET.test(entity) ? Race.UNICORN
: AmuletSelectors.ALICORN_AMULET.test(entity) ? Race.ALICORN
: null
);
}
return compositeRace; return compositeRace;
} }
@ -352,6 +338,20 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
@Override @Override
public boolean beforeUpdate() { public boolean beforeUpdate() {
if (compositeRace.includes(Race.UNSET) || entity.age % 2 == 0) {
compositeRace = getSpellSlot()
.get(SpellPredicate.IS_MIMIC, true)
.map(AbstractDisguiseSpell::getDisguise)
.map(EntityAppearance::getAppearance)
.flatMap(Pony::of)
.map(Pony::getSpecies)
.orElseGet(this::getSpecies).composite(
AmuletSelectors.UNICORN_AMULET.test(entity) ? Race.UNICORN
: AmuletSelectors.ALICORN_AMULET.test(entity) ? Race.ALICORN
: null
);
}
if (isClient()) { if (isClient()) {
if (entity.hasVehicle() && entity.isSneaking()) { if (entity.hasVehicle() && entity.isSneaking()) {
@ -420,9 +420,14 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
entity.setVelocity(entity.getVelocity().multiply(1, 0, 1)); entity.setVelocity(entity.getVelocity().multiply(1, 0, 1));
entity.setSneaking(false); entity.setSneaking(false);
} }
} else if (attachDirection != null && isFaceClimbable(entity.getWorld(), entity.getBlockPos(), attachDirection)) { } else if (attachDirection != null) {
entity.setBodyYaw(attachDirection.asRotation()); if (isFaceClimbable(entity.getWorld(), entity.getBlockPos(), attachDirection)) {
entity.prevBodyYaw = attachDirection.asRotation(); entity.setBodyYaw(attachDirection.asRotation());
entity.prevBodyYaw = attachDirection.asRotation();
} else {
entity.setVelocity(vel);
entity.isClimbing();
}
} }
} }
@ -480,7 +485,9 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
@Override @Override
public Optional<BlockPos> chooseClimbingPos() { public Optional<BlockPos> chooseClimbingPos() {
if (getObservedSpecies() == Race.CHANGELING && getSpellSlot().get(SpellPredicate.IS_DISGUISE, false).isEmpty()) { if (getObservedSpecies() == Race.CHANGELING && getSpellSlot().get(SpellPredicate.IS_DISGUISE, false).isEmpty()) {
return Optional.of(entity.getBlockPos()); if (isFaceClimbable(entity.getWorld(), entity.getBlockPos(), entity.getHorizontalFacing()) || canHangAt(entity.getBlockPos())) {
return Optional.of(entity.getBlockPos());
}
} }
return super.chooseClimbingPos(); return super.chooseClimbingPos();
} }

View file

@ -15,6 +15,7 @@ public interface Channel {
C2SPacketType<MsgRequestSpeciesChange> CLIENT_REQUEST_SPECIES_CHANGE = SimpleNetworking.clientToServer(Unicopia.id("request_capabilities"), MsgRequestSpeciesChange::new); C2SPacketType<MsgRequestSpeciesChange> CLIENT_REQUEST_SPECIES_CHANGE = SimpleNetworking.clientToServer(Unicopia.id("request_capabilities"), MsgRequestSpeciesChange::new);
C2SPacketType<MsgMarkTraitRead> MARK_TRAIT_READ = SimpleNetworking.clientToServer(Unicopia.id("mark_trait_read"), MsgMarkTraitRead::new); C2SPacketType<MsgMarkTraitRead> MARK_TRAIT_READ = SimpleNetworking.clientToServer(Unicopia.id("mark_trait_read"), MsgMarkTraitRead::new);
C2SPacketType<MsgRemoveSpell> REMOVE_SPELL = SimpleNetworking.clientToServer(Unicopia.id("remove_spell"), MsgRemoveSpell::new); C2SPacketType<MsgRemoveSpell> REMOVE_SPELL = SimpleNetworking.clientToServer(Unicopia.id("remove_spell"), MsgRemoveSpell::new);
C2SPacketType<MsgPlayerFlightControlsInput> FLIGHT_CONTROLS_INPUT = SimpleNetworking.clientToServer(Unicopia.id("flight_controls"), MsgPlayerFlightControlsInput::new);
S2CPacketType<MsgPlayerCapabilities> SERVER_PLAYER_CAPABILITIES = SimpleNetworking.serverToClient(Unicopia.id("player_capabilities"), MsgPlayerCapabilities::new); S2CPacketType<MsgPlayerCapabilities> SERVER_PLAYER_CAPABILITIES = SimpleNetworking.serverToClient(Unicopia.id("player_capabilities"), MsgPlayerCapabilities::new);
S2CPacketType<MsgSpawnProjectile> SERVER_SPAWN_PROJECTILE = SimpleNetworking.serverToClient(Unicopia.id("projectile_entity"), MsgSpawnProjectile::new); S2CPacketType<MsgSpawnProjectile> SERVER_SPAWN_PROJECTILE = SimpleNetworking.serverToClient(Unicopia.id("projectile_entity"), MsgSpawnProjectile::new);

View file

@ -0,0 +1,32 @@
package com.minelittlepony.unicopia.network;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.sollace.fabwork.api.packets.HandledPacket;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.network.ServerPlayerEntity;
public record MsgPlayerFlightControlsInput (
boolean ascending,
boolean descending
) implements HandledPacket<ServerPlayerEntity> {
public MsgPlayerFlightControlsInput(Pony pony) {
this(pony.getJumpingHeuristic().getState(), pony.asEntity().isSneaking());
}
public MsgPlayerFlightControlsInput(PacketByteBuf buffer) {
this(buffer.readBoolean(), buffer.readBoolean());
}
@Override
public void toBuffer(PacketByteBuf buffer) {
buffer.writeBoolean(ascending);
buffer.writeBoolean(descending);
}
@Override
public void handle(ServerPlayerEntity sender) {
sender.setSneaking(descending);
sender.setJumping(ascending);
}
}

View file

@ -708,48 +708,65 @@
"death.attack.unicopia.generic.and_also": "%1$s and %2$s", "death.attack.unicopia.generic.and_also": "%1$s and %2$s",
"death.attack.unicopia.generic.whilst_flying": "%1$s whilst flying", "death.attack.unicopia.generic.whilst_flying": "%1$s whilst flying",
"death.attack.unicopia.tribe_swap": "%1$s was reborn into a different tribe", "death.attack.unicopia.tribe_swap": "%1$s was reborn into a different tribe",
"death.attack.unicopia.tribe_swap.self": "%1$s was doomed themselved to be reborn into a different tribe",
"death.attack.unicopia.tribe_swap.player": "%1$s was doomed to be reborn into a different tribe by %2$s", "death.attack.unicopia.tribe_swap.player": "%1$s was doomed to be reborn into a different tribe by %2$s",
"death.attack.unicopia.sun": "%1$s stared into the sun", "death.attack.unicopia.sun": "%1$s stared into the sun",
"death.attack.unicopia.sun.self": "%1$s stared into the sun",
"death.attack.unicopia.sun.player": "%1$s stared into the sun whilst fighting %2$s", "death.attack.unicopia.sun.player": "%1$s stared into the sun whilst fighting %2$s",
"death.attack.unicopia.sunlight": "%1$s was burned by the sun", "death.attack.unicopia.sunlight": "%1$s was burned by the sun",
"death.attack.unicopia.sunlight.self": "%1$s was burned by the sun",
"death.attack.unicopia.sunlight.player": "%1$s was burned by the sun whilst fighting %2$s", "death.attack.unicopia.sunlight.player": "%1$s was burned by the sun whilst fighting %2$s",
"death.attack.unicopia.petrified": "%1$s turned to stone", "death.attack.unicopia.petrified": "%1$s turned to stone",
"death.attack.unicopia.petrified.self": "%1$s turned to stone",
"death.attack.unicopia.petrified.player": "%1$s turned to stone whilst fighting %2$s", "death.attack.unicopia.petrified.player": "%1$s turned to stone whilst fighting %2$s",
"death.attack.unicopia.magical_exhaustion": "%1$s exhausted themselves", "death.attack.unicopia.magical_exhaustion": "%1$s exhausted themselves",
"death.attack.unicopia.magical_exhaustion.self": "%1$s exhausted themselves",
"death.attack.unicopia.magical_exhaustion.player": "%1$s exhausted themselves whilst fighting %2$s", "death.attack.unicopia.magical_exhaustion.player": "%1$s exhausted themselves whilst fighting %2$s",
"death.attack.unicopia.alicorn_amulet": "%1$s was driven insane", "death.attack.unicopia.alicorn_amulet": "%1$s was driven insane",
"death.attack.unicopia.alicorn_amulet.self": "%1$s drove themselves insane",
"death.attack.unicopia.alicorn_amulet.player": "%1$s went insane whilst fighting %2$s", "death.attack.unicopia.alicorn_amulet.player": "%1$s went insane whilst fighting %2$s",
"death.attack.unicopia.darkness": "%1$s went missing", "death.attack.unicopia.darkness": "%1$s went missing",
"death.attack.unicopia.darkness.self": "%1$s went missing",
"death.attack.unicopia.darkness.player": "%1$s went missing whilst fighting %2$s",
"death.attack.unicopia.love_draining": "%1$s was drained of all love", "death.attack.unicopia.love_draining": "%1$s was drained of all love",
"death.attack.unicopia.love_draining.self": "%1$s expelled all of their own love",
"death.attack.unicopia.love_draining.player": "%2$s drained %1$s of all their love", "death.attack.unicopia.love_draining.player": "%2$s drained %1$s of all their love",
"death.attack.unicopia.life_draining": "%1$s was sucked dry", "death.attack.unicopia.life_draining": "%1$s was sucked dry",
"death.attack.unicopia.life_draining.self": "%1$s was killed by their own spell", "death.attack.unicopia.life_draining.self": "%1$s was killed by their own spell",
"death.attack.unicopia.life_draining.player": "%1$s was killed by a spell cast by %2$s", "death.attack.unicopia.life_draining.player": "%1$s was killed by a spell cast by %2$s",
"death.attack.unicopia.bat_screech": "%1$s was spooked", "death.attack.unicopia.bat_screech": "%1$s was spooked",
"death.attack.unicopia.bat_screech.self": "%1$s spooked themselves",
"death.attack.unicopia.bat_screech.player": "%2$s spooked %1$s", "death.attack.unicopia.bat_screech.player": "%2$s spooked %1$s",
"death.attack.unicopia.bat_screech.item": "%1$s was spooked by %2$s using %3$s", "death.attack.unicopia.bat_screech.item": "%1$s was spooked by %2$s using %3$s",
"death.attack.unicopia.bat_screech.self": "%1$s spooked themselves",
"death.attack.unicopia.gravity_well_recoil": "%1$s turned into spaghetti", "death.attack.unicopia.gravity_well_recoil": "%1$s turned into spaghetti",
"death.attack.unicopia.gravity_well_recoil.player": "%1$s turned into spaghetti by a spell cast by %2$s", "death.attack.unicopia.gravity_well_recoil.player": "%1$s turned into spaghetti by a spell cast by %2$s",
"death.attack.unicopia.gravity_well_recoil.item": "%1$s turned into spaghetti by a spell cast by %2$s using %3$s", "death.attack.unicopia.gravity_well_recoil.item": "%1$s turned into spaghetti by a spell cast by %2$s using %3$s",
"death.attack.unicopia.gravity_well_recoil.self": "%1$s cast a spell that turned them into spaghetti", "death.attack.unicopia.gravity_well_recoil.self": "%1$s cast a spell that turned them into spaghetti",
"death.attack.unicopia.smash": "%1$s was crushed under hoof", "death.attack.unicopia.smash": "%1$s was crushed under hoof",
"death.attack.unicopia.smash.self": "%1$s got themselves crushed under a hoof",
"death.attack.unicopia.smash.player": "%1$s was crushed by %2$s", "death.attack.unicopia.smash.player": "%1$s was crushed by %2$s",
"death.attack.unicopia.zap": "%1$s bit into a Zap Apple", "death.attack.unicopia.zap": "%1$s bit into a Zap Apple",
"death.attack.unicopia.zap.self": "%1$s bit into a Zap Apple",
"death.attack.unicopia.zap.player": "%1$s bit into a Zap Apple whilst fighting %2$s", "death.attack.unicopia.zap.player": "%1$s bit into a Zap Apple whilst fighting %2$s",
"death.attack.unicopia.paradox": "%1$s imploded", "death.attack.unicopia.paradox": "%1$s imploded",
"death.attack.unicopia.paradox.self": "%1$s imploded themselves",
"death.attack.unicopia.paradox.player": "%1$s imploded with some help from %2$s", "death.attack.unicopia.paradox.player": "%1$s imploded with some help from %2$s",
"death.attack.unicopia.food_poisoning": "%1$s died of food poisoning", "death.attack.unicopia.food_poisoning": "%1$s died of food poisoning",
"death.attack.unicopia.food_poisoning.self": "%1$s died of food poisoning",
"death.attack.unicopia.food_poisoning.player": "%2$s poisoned %1$s to death", "death.attack.unicopia.food_poisoning.player": "%2$s poisoned %1$s to death",
"death.attack.unicopia.black_hole": "%1$s was sucked into a black hole", "death.attack.unicopia.black_hole": "%1$s was sucked into a black hole",
"death.attack.unicopia.black_hole.self": "%1$s was sucked into a black hole",
"death.attack.unicopia.black_hole.player": "%1$s got sucked into %2$s's black hole", "death.attack.unicopia.black_hole.player": "%1$s got sucked into %2$s's black hole",
"death.attack.unicopia.kick": "%1$s was kicked really hard", "death.attack.unicopia.kick": "%1$s was kicked really hard",
"death.attack.unicopia.kick.self": "%1$s kicked themselves really hard",
"death.attack.unicopia.kick.player": "%2$s kicked %1$s really hard", "death.attack.unicopia.kick.player": "%2$s kicked %1$s really hard",
"death.attack.unicopia.steamroller": "%1$s was flattened", "death.attack.unicopia.steamroller": "%1$s was flattened",
"death.attack.unicopia.steamroller.self": "%1$s was flattened",
"death.attack.unicopia.steamroller.player": "%2$s steamrolled %1$s", "death.attack.unicopia.steamroller.player": "%2$s steamrolled %1$s",
"death.attack.unicopia.stalagmite.pegasus": "%1$s tried to perch on a stalagmite", "death.attack.unicopia.stalagmite.pegasus": "%1$s tried to perch on a stalagmite",
"death.attack.unicopia.stalagmite.pegasus.player": "%1$s flew into a stalagmite whilst fighting %2$s", "death.attack.unicopia.stalagmite.pegasus.player": "%1$s flew into a stalagmite whilst fighting %2$s",
"death.attack.unicopia.rock": "%1$s was pummeled", "death.attack.unicopia.rock": "%1$s was pummeled",
"death.attack.unicopia.rock.self": "%1$s was pummeled",
"death.attack.unicopia.rock.item": "%1$s was pummelled by %2$s using %3$s", "death.attack.unicopia.rock.item": "%1$s was pummelled by %2$s using %3$s",
"death.attack.unicopia.rock.player": "%1$s was pummelled by %2$s", "death.attack.unicopia.rock.player": "%1$s was pummelled by %2$s",