Compare commits

...

12 commits

Author SHA1 Message Date
Sollace
b59c765197
Catch exceptions when tearing down block entities 2023-10-30 22:00:49 +00:00
Sollace
1f46b08742
Fixed the recipe for the inferno spell 2023-10-28 23:19:16 +01:00
Sollace
e196b3c226
Fix error ticking sprout blocks 2023-10-28 22:14:22 +01:00
Sollace
4e88d84524
Remove debug code 2023-10-28 22:13:19 +01:00
Sollace
47a6a8e7da
Fix for weather vane selection box 2023-10-28 22:13:03 +01:00
Sollace
b4a595550a
Fixed flight velocity/control state going out of sync with the server and add some behavioural tweaks
Note to Fauli: SPACE TO FLAP/ASCENT, SHIFT TO DESCEND

Also changed the behaviour for changeling flight (again).

Fixes #205
Fixes #179
Fixes #165
Fixes #176
2023-10-28 22:12:44 +01:00
Sollace
427fc7d210
Fixed floating changelings. Closes #204 2023-10-28 22:12:23 +01:00
Sollace
23cf8bd702
Fixed crash without trinkets 2023-10-28 22:11:47 +01:00
Sollace
ba0fba791e
Fixed regression from earlier 2023-10-28 22:11:19 +01:00
Sollace
d1b5a73b18
Add handling to update the player's race if we're unable to force a respawn 2023-10-28 22:10:35 +01:00
Sollace
3aebdee8c2
Fixed missing translation for the self variant of the tribe_swap death message 2023-10-28 22:10:23 +01:00
Sollace
716281853c
Fixed crash 2023-10-28 22:09:41 +01:00
11 changed files with 140 additions and 60 deletions

View file

@ -217,6 +217,9 @@ public record Race (Supplier<Composite> compositeSupplier, boolean canCast, Flig
}
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
public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random 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
public void applyGrowth(World world, BlockPos pos, BlockState 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
@ -81,7 +87,7 @@ public class SproutBlock extends CropBlock implements TintedBlock {
protected void mature(World world, BlockState state, BlockPos pos) {
state = matureState.get();
world.setBlockState(pos, matureState.get());
world.setBlockState(pos, state);
BlockSoundGroup group = state.getSoundGroup();
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;
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, 0, 7, 9, 1, 9)
);*/
);
protected WeatherVaneBlock(Settings settings) {
super(settings);
@ -32,10 +32,7 @@ public class WeatherVaneBlock extends BlockWithEntity {
@Deprecated
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return VoxelShapes.union(
Block.createCuboidShape(7.5F, 0, 7.5F, 8.5F, 14, 8.5F),
Block.createCuboidShape(7, 0, 7, 9, 1, 9)
);
return SHAPE;
}
@Override

View file

@ -127,7 +127,9 @@ public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provi
entity = null;
}
if (blockEntity != null) {
blockEntity.markRemoved();
try {
blockEntity.markRemoved();
} catch (Throwable ignored) {}
blockEntity = null;
}
}

View file

@ -112,7 +112,11 @@ public class RaceChangeStatusEffect extends StatusEffect {
magic.getExertion().set(0);
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 {
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.UItems;
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.projectile.ProjectileUtil;
import com.minelittlepony.unicopia.server.world.BlockDestructionManager;
@ -130,7 +132,7 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
@Override
public boolean isGliding() {
return ticksToGlide <= 0 && isFlying() && !entity.isSneaking();
return ticksToGlide <= 0 && isFlying() && !pony.getJumpingHeuristic().getState();
}
@Override
@ -228,6 +230,10 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
public void tick() {
super.tick();
if (pony.isClient() && this.isFlying()) {
Channel.FLIGHT_CONTROLS_INPUT.sendToServer(new MsgPlayerFlightControlsInput(pony));
}
prevThrustScale = thrustScale;
if (wallHitCooldown > 0) {
@ -254,7 +260,7 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
boolean typeChanged = type != lastFlightType;
if (typeChanged && lastFlightType.isArtifical() || type.isArtifical()) {
if (typeChanged && (lastFlightType.isArtifical() || type.isArtifical())) {
pony.spawnParticles(ParticleTypes.CLOUD, 10);
playSound(entity.getWorld().getDimension().ultrawarm() ? USounds.ITEM_ICARUS_WINGS_CORRUPT : USounds.ITEM_ICARUS_WINGS_PURIFY, 0.1125F, 1.5F);
@ -404,16 +410,14 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
entity.fallDistance = 0;
if (type.isAvian()) {
applyThrust(velocity);
applyThrust(velocity);
if (type.isAvian()) {
if (pony.getObservedSpecies() != Race.BAT && entity.getWorld().random.nextInt(9000) == 0) {
entity.dropItem(UItems.PEGASUS_FEATHER);
playSound(USounds.ENTITY_PLAYER_PEGASUS_MOLT, 0.3F, 1);
UCriteria.SHED_FEATHER.trigger(entity);
}
} else {
applyThrust(velocity);
}
moveFlying(velocity);
@ -429,8 +433,6 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
InteractionManager.instance().playLoopingSound(entity, InteractionManager.SOUND_GLIDING, entity.getId());
}
}
} else if (type == FlightType.INSECTOID && !SpellPredicate.IS_DISGUISE.isOn(pony)) {
if (entity.getWorld().isClient && !soundPlaying) {
soundPlaying = true;
@ -477,7 +479,7 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
}
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) {
@ -556,12 +558,14 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
isFlyingEither = true;
isFlyingSurvival = true;
thrustScale = 0;
descentRate = 0;
entity.calculateDimensions();
if (entity.isOnGround() || !force) {
Supplier<Vec3d> vec = VecHelper.sphere(pony.asWorld().getRandom(), 0.5D);
pony.spawnParticles(ParticleTypes.CAMPFIRE_COSY_SMOKE, vec, vec, 5);
pony.spawnParticles(ParticleTypes.CLOUD, vec, vec, 5);
Supplier<Vec3d> pos = VecHelper.sphere(pony.asWorld().getRandom(), 0.5D);
Supplier<Vec3d> vel = VecHelper.sphere(pony.asWorld().getRandom(), 0.15D);
pony.spawnParticles(ParticleTypes.CAMPFIRE_COSY_SMOKE, pos, vel, 5);
pony.spawnParticles(ParticleTypes.CLOUD, pos, vel, 5);
}
}
@ -627,11 +631,12 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
if (descentRate < 0) {
descentRate *= 0.8F;
}
velocity.y -= descentRate * getGravityModifier();
}
private void applyThrust(MutableVector velocity) {
boolean manualFlap = pony.sneakingChanged() && entity.isSneaking();
boolean manualFlap = pony.getJumpingHeuristic().hasChanged(Heuristic.ONCE) && pony.getJumpingHeuristic().getState();
if (manualFlap) {
flapping = true;
ticksToGlide = MAX_TICKS_TO_GLIDE;
@ -641,24 +646,26 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
boolean hovering = entity.getVelocity().horizontalLength() < 0.1;
if (thrustScale <= 0.000001F & flapping) {
flapping = false;
if (!SpellPredicate.IS_DISGUISE.isOn(pony)) {
if (lastFlightType != FlightType.INSECTOID) {
if (lastFlightType == FlightType.INSECTOID) {
descentRate = pony.getJumpingHeuristic().getState() ? -0.5F : 0;
} else {
if (thrustScale <= 0.000001F & flapping) {
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 thrustStrength = 0.135F * thrustScale;
float thrustStrength = 0.235F * thrustScale;
if (heavyness > 0) {
thrustStrength /= 1 + heavyness;
@ -666,9 +673,13 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
Vec3d direction = entity.getRotationVec(1).normalize().multiply(thrustStrength);
if (!hovering) {
velocity.x += direction.x;
velocity.z += direction.z;
if (hovering) {
if (entity.isSneaking()) {
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
}

View file

@ -94,8 +94,7 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
private final Interpolator interpolator = new LinearInterpolator();
@Nullable
private Race.Composite compositeRace;
private Race.Composite compositeRace = Race.UNSET.composite();
private Race respawnRace = Race.UNSET;
private boolean dirty;
@ -220,19 +219,6 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
*/
@Override
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;
}
@ -352,6 +338,20 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
@Override
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 (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.setSneaking(false);
}
} else if (attachDirection != null && isFaceClimbable(entity.getWorld(), entity.getBlockPos(), attachDirection)) {
entity.setBodyYaw(attachDirection.asRotation());
entity.prevBodyYaw = attachDirection.asRotation();
} else if (attachDirection != null) {
if (isFaceClimbable(entity.getWorld(), entity.getBlockPos(), attachDirection)) {
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
public Optional<BlockPos> chooseClimbingPos() {
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();
}

View file

@ -15,6 +15,7 @@ public interface Channel {
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<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<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.whilst_flying": "%1$s whilst flying",
"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.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.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.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.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.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.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.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.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.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.self": "%1$s spooked themselves",
"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.self": "%1$s spooked themselves",
"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.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.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.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.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.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.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.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.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.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.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.player": "%1$s was pummelled by %2$s",

View file

@ -2,7 +2,7 @@
"type": "unicopia:spellbook/crafting",
"material": { "item": "unicopia:gemstone", "spell": "unicopia:none" },
"traits": {
"fire": 50, "dark": 10
"fire": 50, "darkness": 10
},
"ingredients": [
{ "item": "unicopia:gemstone", "spell": "unicopia:flame" }