diff --git a/src/main/java/com/minelittlepony/unicopia/ability/CarryAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/CarryAbility.java index 957502ca..0bf4a551 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/CarryAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/CarryAbility.java @@ -2,13 +2,12 @@ package com.minelittlepony.unicopia.ability; import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.ability.data.Hit; +import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.util.TraceHelper; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.network.packet.s2c.play.EntityPassengersSetS2CPacket; -import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.world.World; /** @@ -76,9 +75,7 @@ public class CarryAbility implements Ability { rider.startRiding(player, true); } - if (player instanceof ServerPlayerEntity) { - ((ServerPlayerEntity)player).networkHandler.sendPacket(new EntityPassengersSetS2CPacket(player)); - } + Living.transmitPassengers(player); } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/ability/UnicornTeleportAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/UnicornTeleportAbility.java index f5dca965..a5780406 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/UnicornTeleportAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/UnicornTeleportAbility.java @@ -5,6 +5,7 @@ import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.data.Hit; import com.minelittlepony.unicopia.ability.data.Pos; import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.particle.MagicParticleEffect; import com.minelittlepony.unicopia.util.Trace; @@ -17,9 +18,7 @@ import net.minecraft.block.WallBlock; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.network.packet.s2c.play.EntityPassengersSetS2CPacket; import net.minecraft.predicate.entity.EntityPredicates; -import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.sound.SoundCategory; import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; @@ -142,10 +141,7 @@ public class UnicornTeleportAbility implements Ability { Entity mount = player.getVehicle(); player.stopRiding(); - - if (mount instanceof ServerPlayerEntity) { - ((ServerPlayerEntity)player).networkHandler.sendPacket(new EntityPassengersSetS2CPacket(player)); - } + Living.transmitPassengers(mount); } Vec3d offset = teleportee.getOriginVector().subtract(teleporter.getOriginVector()); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/CasterView.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/CasterView.java new file mode 100644 index 00000000..572f4542 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/CasterView.java @@ -0,0 +1,44 @@ +package com.minelittlepony.unicopia.ability.magic; + +import java.util.Map; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.EquinePredicates; +import com.minelittlepony.unicopia.ability.magic.spell.Spell; +import com.minelittlepony.unicopia.util.VecHelper; + +import net.minecraft.entity.Entity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.*; + +public interface CasterView { + EntityView getWorld(); + + default Stream, S>> findAllSpellsInRange(BlockPos pos, double radius, SpellPredicate type) { + return findAllCastersInRange(pos, radius).flatMap(caster -> { + return caster.getSpellSlot().stream(type, false).map(spell -> { + return Map.entry(caster, spell); + }); + }); + } + + default Stream> findAllCastersInRange(BlockPos pos, double radius) { + return findAllCastersInRange(pos, radius, null); + } + + default Stream> findAllCastersInRange(BlockPos pos, double radius, @Nullable Predicate test) { + return Caster.stream(findAllEntitiesInRange(pos, radius, test == null ? EquinePredicates.IS_CASTER : EquinePredicates.IS_CASTER.and(test))); + } + + default Stream findAllEntitiesInRange(BlockPos pos, double radius, @Nullable Predicate test) { + return VecHelper.findInRange(null, getWorld(), Vec3d.ofCenter(pos), radius, test).stream(); + } + + default Stream findAllEntitiesInRange(BlockPos pos, double radius) { + return findAllEntitiesInRange(pos, radius, null); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellPredicate.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellPredicate.java index 5904ac9f..7ebe1f97 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellPredicate.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellPredicate.java @@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.ability.magic; import java.util.function.Predicate; import com.minelittlepony.unicopia.ability.magic.spell.*; +import com.minelittlepony.unicopia.ability.magic.spell.effect.MimicSpell; import com.minelittlepony.unicopia.ability.magic.spell.effect.ShieldSpell; import net.minecraft.entity.Entity; @@ -12,6 +13,7 @@ public interface SpellPredicate extends Predicate { SpellPredicate IS_PLACED = s -> s instanceof PlaceableSpell; SpellPredicate HAS_PROJECTILE_EVENTS = s -> s instanceof ProjectileSpell; SpellPredicate IS_DISGUISE = s -> s instanceof AbstractDisguiseSpell; + SpellPredicate IS_MIMIC = s -> s instanceof MimicSpell; SpellPredicate IS_SHIELD_LIKE = spell -> spell instanceof ShieldSpell; SpellPredicate IS_TIMED = spell -> spell instanceof TimedSpell; diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/TimedSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/TimedSpell.java index 03a10cba..9e19b8ae 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/TimedSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/TimedSpell.java @@ -1,6 +1,7 @@ package com.minelittlepony.unicopia.ability.magic.spell; import com.minelittlepony.unicopia.util.NbtSerialisable; +import com.minelittlepony.unicopia.util.Tickable; import net.minecraft.nbt.NbtCompound; import net.minecraft.util.math.MathHelper; @@ -11,16 +12,17 @@ import net.minecraft.util.math.MathHelper; public interface TimedSpell extends Spell { Timer getTimer(); - class Timer implements NbtSerialisable { - public int maxDuration; - public int duration; - public int prevDuration; + class Timer implements Tickable, NbtSerialisable { + private int maxDuration; + private int duration; + private int prevDuration; public Timer(int initial) { maxDuration = initial; duration = initial; } + @Override public void tick() { prevDuration = duration; duration--; diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractiveSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractiveSpell.java index e8aef920..3ec4a475 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractiveSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractiveSpell.java @@ -37,7 +37,7 @@ public class AttractiveSpell extends ShieldSpell implements ProjectileSpell, Hom public boolean tick(Caster caster, Situation situation) { timer.tick(); - if (timer.duration <= 0) { + if (timer.getTicksRemaining() <= 0) { return false; } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AwkwardSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AwkwardSpell.java index 629d0bb1..97f3d63f 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AwkwardSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AwkwardSpell.java @@ -37,9 +37,11 @@ public class AwkwardSpell extends AbstractSpell implements TimedSpell { if (situation != Situation.PROJECTILE) { timer.tick(); - if (timer.duration <= 0) { + if (timer.getTicksRemaining() <= 0) { return false; } + + setDirty(); } if (source.isClient()) { diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FeatherFallSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FeatherFallSpell.java index 3b3a609b..72b4dac4 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FeatherFallSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FeatherFallSpell.java @@ -52,7 +52,7 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell { public boolean tick(Caster caster, Situation situation) { timer.tick(); - if (timer.duration <= 0) { + if (timer.getTicksRemaining() <= 0) { return false; } @@ -91,7 +91,7 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell { ParticleUtils.spawnParticles(new MagicParticleEffect(getType().getColor()), target, 7); }); - return caster.subtractEnergyCost(timer.duration % 50 == 0 ? getCostPerEntity() * targets.size() : 0); + return caster.subtractEnergyCost(timer.getTicksRemaining() % 50 == 0 ? getCostPerEntity() * targets.size() : 0); } protected double getCostPerEntity() { diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java new file mode 100644 index 00000000..3f7d728e --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java @@ -0,0 +1,162 @@ +package com.minelittlepony.unicopia.ability.magic.spell.effect; + +import java.util.HashSet; +import java.util.Set; + +import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.spell.Situation; +import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; +import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; +import com.minelittlepony.unicopia.block.data.Ether; +import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.particle.UParticles; +import com.minelittlepony.unicopia.util.NbtSerialisable; +import com.minelittlepony.unicopia.util.shape.*; + +import net.minecraft.block.*; +import net.minecraft.fluid.*; +import net.minecraft.nbt.*; +import net.minecraft.sound.SoundEvents; +import net.minecraft.state.property.Properties; +import net.minecraft.tag.TagKey; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public class HydrophobicSpell extends AbstractSpell { + public static final SpellTraits DEFAULT_TRAITS = new SpellTraits.Builder() + .with(Trait.FOCUS, 5) + .with(Trait.KNOWLEDGE, 1) + .build(); + + private final TagKey affectedFluid; + + private final Set storedFluidPositions = new HashSet<>(); + + protected HydrophobicSpell(CustomisedSpellType type, TagKey affectedFluid) { + super(type); + this.affectedFluid = affectedFluid; + } + + @Override + public boolean apply(Caster source) { + if (getTraits().get(Trait.GENEROSITY) > 0) { + return toPlaceable().apply(source); + } + return super.apply(source); + } + + @Override + public boolean tick(Caster source, Situation situation) { + if (!source.isClient()) { + World world = source.getReferenceWorld(); + + Shape area = new Sphere(false, getRange(source)).offset(source.getOriginVector()); + + storedFluidPositions.removeIf(entry -> { + if (!area.isPointInside(Vec3d.ofCenter(entry.pos()))) { + entry.restore(world); + return true; + } + + return false; + }); + + area.getBlockPositions().forEach(pos -> { + BlockState state = world.getBlockState(pos); + + if (state.getFluidState().isIn(affectedFluid)) { + Block block = state.getBlock(); + + if (block instanceof FluidBlock) { + world.setBlockState(pos, Blocks.AIR.getDefaultState(), Block.NOTIFY_LISTENERS); + storedFluidPositions.add(new Entry(pos, state.getFluidState())); + } else if (state.contains(Properties.WATERLOGGED)) { + world.setBlockState(pos, state.cycle(Properties.WATERLOGGED), Block.NOTIFY_LISTENERS); + storedFluidPositions.add(new Entry(pos, state.getFluidState())); + } + } + }); + + source.subtractEnergyCost(storedFluidPositions.isEmpty() ? 0.001F : 0.02F); + source.spawnParticles(new Sphere(true, getRange(source)), 10, pos -> { + BlockPos bp = new BlockPos(pos); + if (source.getReferenceWorld().getFluidState(bp.up()).isIn(affectedFluid)) { + source.addParticle(UParticles.RAIN_DROPS, pos, Vec3d.ZERO); + } + }); + + if (source.getMaster().age % 200 == 0) { + source.playSound(SoundEvents.BLOCK_BEACON_AMBIENT, 0.5F); + } + } + + return !isDead(); + } + + @Override + public void onDestroyed(Caster caster) { + storedFluidPositions.removeIf(entry -> { + entry.restore(caster.getReferenceWorld()); + return true; + }); + } + + @Override + public void toNBT(NbtCompound compound) { + super.toNBT(compound); + compound.put("storedFluidPositions", Entry.SERIALIZER.writeAll(storedFluidPositions)); + } + + @Override + public void fromNBT(NbtCompound compound) { + super.fromNBT(compound); + storedFluidPositions.clear(); + storedFluidPositions.addAll(Entry.SERIALIZER.readAll(compound.getList("storedFluidPositions", NbtElement.COMPOUND_TYPE)).toList()); + } + /** + * Calculates the maximum radius of the shield. aka The area of effect. + */ + public double getRange(Caster source) { + float multiplier = 1; + float min = (source instanceof Pony ? 4 : 6) + getTraits().get(Trait.POWER); + double range = (min + (source.getLevel().getScaled(source instanceof Pony ? 4 : 40) * (source instanceof Pony ? 2 : 10))) / multiplier; + + return range; + } + + record Entry (BlockPos pos, FluidState fluidState) { + public static final Serializer SERIALIZER = Serializer.of(compound -> new Entry( + NbtSerialisable.BLOCK_POS.read(compound.getCompound("pos")), + NbtSerialisable.decode(FluidState.CODEC, compound.get("state")) + ), entry -> { + NbtCompound compound = new NbtCompound(); + compound.put("pos", NbtSerialisable.BLOCK_POS.write(entry.pos)); + compound.put("state", NbtSerialisable.encode(FluidState.CODEC, entry.fluidState)); + return compound; + }); + + void restore(World world) { + BlockState state = world.getBlockState(pos); + + if (state.isAir()) { + world.setBlockState(pos, Fluids.WATER.getDefaultState().getBlockState(), Block.NOTIFY_LISTENERS); + } else if (state.contains(Properties.WATERLOGGED)) { + world.setBlockState(pos, state.cycle(Properties.WATERLOGGED), Block.NOTIFY_LISTENERS); + } + } + } + + public boolean blocksFlow(Caster caster, BlockPos pos, FluidState fluid) { + if (fluid.isIn(affectedFluid) && pos.isWithinDistance(caster.getOrigin(), getRange(caster) + 1)) { + System.out.println("AHA!"); + } + return fluid.isIn(affectedFluid) && pos.isWithinDistance(caster.getOrigin(), getRange(caster) + 1); + } + + public static boolean blocksFluidFlow(World world, BlockPos pos, BlockState state, Fluid fluid) { + return Ether.get(world).findAllSpellsInRange(pos, 500, SpellType.HYDROPHOBIC).anyMatch(pair -> { + return pair.getValue().blocksFlow(pair.getKey(), pos, fluid.getDefaultState()); + }); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/LightSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/LightSpell.java index 621cf3a5..641891bf 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/LightSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/LightSpell.java @@ -48,7 +48,7 @@ public class LightSpell extends AbstractSpell implements TimedSpell { timer.tick(); - if (timer.duration <= 0) { + if (timer.getTicksRemaining() <= 0) { return false; } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/MimicSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/MimicSpell.java new file mode 100644 index 00000000..b48866b7 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/MimicSpell.java @@ -0,0 +1,53 @@ +package com.minelittlepony.unicopia.ability.magic.spell.effect; + +import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.spell.*; +import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; +import net.minecraft.entity.Entity; +import net.minecraft.nbt.NbtCompound; + +public class MimicSpell extends AbstractDisguiseSpell implements HomingSpell, TimedSpell { + + private final Timer timer; + + protected MimicSpell(CustomisedSpellType type) { + super(type); + timer = new Timer((120 + (int)(getTraits().get(Trait.FOCUS, 0, 160) * 19)) * 20); + } + + @Override + public Timer getTimer() { + return timer; + } + + @Override + public boolean tick(Caster caster, Situation situation) { + timer.tick(); + + if (timer.getTicksRemaining() <= 0) { + return false; + } + + setDirty(); + + return super.tick(caster, situation); + } + + @Override + public boolean setTarget(Entity target) { + setDisguise(target); + return true; + } + + @Override + public void toNBT(NbtCompound compound) { + super.toNBT(compound); + timer.toNBT(compound); + } + + @Override + public void fromNBT(NbtCompound compound) { + super.fromNBT(compound); + timer.fromNBT(compound); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/MindSwapSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/MindSwapSpell.java new file mode 100644 index 00000000..85a4ba72 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/MindSwapSpell.java @@ -0,0 +1,124 @@ +package com.minelittlepony.unicopia.ability.magic.spell.effect; + +import java.util.Optional; + +import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.spell.Situation; +import com.minelittlepony.unicopia.entity.EntityReference; +import com.minelittlepony.unicopia.entity.behaviour.EntitySwap; +import com.minelittlepony.unicopia.entity.behaviour.Inventory; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.sound.SoundEvents; + +public class MindSwapSpell extends MimicSpell { + + private final EntityReference counterpart = new EntityReference<>(); + + private Optional myStoredInventory = Optional.empty(); + private Optional theirStoredInventory = Optional.empty(); + + private boolean initialized; + + protected MindSwapSpell(CustomisedSpellType type) { + super(type); + } + + + @Override + public void onDestroyed(Caster caster) { + super.onDestroyed(caster); + if (initialized && !caster.isClient()) { + counterpart.ifPresent(caster.getReferenceWorld(), e -> { + EntitySwap.ALL.accept(e, caster.getMaster()); + Inventory.swapInventories( + e, myStoredInventory.or(() -> Inventory.of(e)), + caster.getMaster(), theirStoredInventory.or(() -> Inventory.of(caster.getMaster())), + a -> {}, + a -> {} + ); + + Caster other = Caster.of(e).get(); + other.getSpellSlot().removeIf(SpellType.MIMIC, true); + + other.playSound(SoundEvents.ENTITY_ZOMBIE_INFECT, 1); + caster.playSound(SoundEvents.ENTITY_ZOMBIE_INFECT, 1); + }); + } + } + + @Override + public boolean tick(Caster caster, Situation situation) { + + if (!caster.isClient()) { + if (!initialized) { + initialized = true; + setDirty(); + counterpart.ifPresent(caster.getReferenceWorld(), e -> { + setDisguise(e); + Caster other = Caster.of(e).get(); + SpellType.MIMIC.withTraits().apply(other).setDisguise(caster.getMaster()); + + EntitySwap.ALL.accept(caster.getMaster(), e); + Inventory.swapInventories( + caster.getMaster(), Inventory.of(caster.getMaster()), + e, Inventory.of(e), + a -> myStoredInventory = Optional.of(a), + a -> theirStoredInventory = Optional.of(a) + ); + + other.playSound(SoundEvents.ENTITY_ZOMBIE_INFECT, 1); + caster.playSound(SoundEvents.ENTITY_ZOMBIE_INFECT, 1); + }); + } + + if (counterpart.getId().isPresent() && counterpart.get(caster.getReferenceWorld()) == null) { + caster.getMaster().damage(DamageSource.MAGIC, Float.MAX_VALUE); + } + } + + return super.tick(caster, situation); + } + + @Override + public boolean setTarget(Entity target) { + if (target instanceof LivingEntity living && Caster.of(target).isPresent()) { + counterpart.set(living); + } + + return false; + } + + @Override + public void toNBT(NbtCompound compound) { + super.toNBT(compound); + compound.put("counterpart", counterpart.toNBT()); + compound.putBoolean("initialized", initialized); + myStoredInventory.ifPresent(mine -> compound.put("myStoredInventory", mine.toNBT(new NbtCompound()))); + theirStoredInventory.ifPresent(mine -> compound.put("theirStoredInventory", mine.toNBT(new NbtCompound()))); + } + + @Override + public void fromNBT(NbtCompound compound) { + super.fromNBT(compound); + counterpart.fromNBT(compound.getCompound("counterpart")); + initialized = compound.getBoolean("initialized"); + myStoredInventory = Optional.ofNullable(compound.contains("myStoredInventory", NbtElement.COMPOUND_TYPE) ? Inventory.fromNBT(compound.getCompound("myStoredInventory")) : null); + theirStoredInventory = Optional.ofNullable(compound.contains("theirStoredInventory", NbtElement.COMPOUND_TYPE) ? Inventory.fromNBT(compound.getCompound("theirStoredInventory")) : null); + } +} + + + + + + + + + + + diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SpellType.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SpellType.java index 5c87fa24..42846909 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SpellType.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SpellType.java @@ -11,7 +11,6 @@ import com.minelittlepony.unicopia.Affinity; import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.ability.magic.Affine; import com.minelittlepony.unicopia.ability.magic.SpellPredicate; -import com.minelittlepony.unicopia.ability.magic.spell.AbstractDisguiseSpell; import com.minelittlepony.unicopia.ability.magic.spell.DispersableDisguiseSpell; import com.minelittlepony.unicopia.ability.magic.spell.RainboomAbilitySpell; import com.minelittlepony.unicopia.ability.magic.spell.PlaceableSpell; @@ -24,6 +23,7 @@ import com.minelittlepony.unicopia.util.Registries; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; +import net.minecraft.tag.FluidTags; import net.minecraft.text.Text; import net.minecraft.util.Identifier; import net.minecraft.util.Util; @@ -39,7 +39,7 @@ public final class SpellType implements Affine, SpellPredicate< public static final SpellType PLACED_SPELL = register("placed", Affinity.NEUTRAL, 0, false, SpellTraits.EMPTY, PlaceableSpell::new); public static final SpellType THROWN_SPELL = register("thrown", Affinity.NEUTRAL, 0, false, SpellTraits.EMPTY, ThrowableSpell::new); - public static final SpellType CHANGELING_DISGUISE = register("disguise", Affinity.BAD, 0x19E48E, false, SpellTraits.EMPTY, DispersableDisguiseSpell::new); + public static final SpellType CHANGELING_DISGUISE = register("disguise", Affinity.BAD, 0x19E48E, false, SpellTraits.EMPTY, DispersableDisguiseSpell::new); public static final SpellType RAINBOOM = register("rainboom", Affinity.GOOD, 0xBDBDF9, false, SpellTraits.EMPTY, RainboomAbilitySpell::new); public static final SpellType FROST = register("frost", Affinity.GOOD, 0xEABBFF, true, IceSpell.DEFAULT_TRAITS, IceSpell::new); @@ -62,6 +62,9 @@ public final class SpellType implements Affine, SpellPredicate< public static final SpellType LIGHT = register("light", Affinity.GOOD, 0xEEFFAA, true, LightSpell.DEFAULT_TRAITS, LightSpell::new); public static final SpellType DISPLACEMENT = register("displacement", Affinity.NEUTRAL, 0x9900FF, true, PortalSpell.DEFAULT_TRAITS, DisplacementSpell::new); public static final SpellType PORTAL = register("portal", Affinity.GOOD, 0x99FFFF, true, PortalSpell.DEFAULT_TRAITS, PortalSpell::new); + public static final SpellType MIMIC = register("mimic", Affinity.GOOD, 0xFFFF00, true, SpellTraits.EMPTY, MimicSpell::new); + public static final SpellType MIND_SWAP = register("mind_swap", Affinity.BAD, 0xF9FF99, true, SpellTraits.EMPTY, MindSwapSpell::new); + public static final SpellType HYDROPHOBIC = register("hydrophobic", Affinity.NEUTRAL, 0xF999FF, true, SpellTraits.EMPTY, s -> new HydrophobicSpell(s, FluidTags.WATER)); public static void bootstrap() {} diff --git a/src/main/java/com/minelittlepony/unicopia/block/data/Ether.java b/src/main/java/com/minelittlepony/unicopia/block/data/Ether.java index 31bb45e1..391c89e9 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/data/Ether.java +++ b/src/main/java/com/minelittlepony/unicopia/block/data/Ether.java @@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.block.data; import java.util.*; import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.ability.magic.CasterView; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.entity.EntityReference; @@ -14,7 +15,7 @@ import net.minecraft.util.Util; import net.minecraft.world.PersistentState; import net.minecraft.world.World; -public class Ether extends PersistentState { +public class Ether extends PersistentState implements CasterView { private static final Identifier ID = Unicopia.id("ether"); public static Ether get(World world) { @@ -25,6 +26,8 @@ public class Ether extends PersistentState { private final Object locker = new Object(); + private final World world; + Ether(World world, NbtCompound compound) { this(world); compound.getKeys().forEach(key -> { @@ -41,7 +44,7 @@ public class Ether extends PersistentState { } Ether(World world) { - + this.world = world; } @Override @@ -116,6 +119,11 @@ public class Ether extends PersistentState { } } + @Override + public World getWorld() { + return world; + } + public class Entry implements NbtSerialisable { public final EntityReference entity; private boolean removed; diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/DismissSpellScreen.java b/src/main/java/com/minelittlepony/unicopia/client/gui/DismissSpellScreen.java index f5ff53b9..c2dc992d 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/DismissSpellScreen.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/DismissSpellScreen.java @@ -84,7 +84,7 @@ public class DismissSpellScreen extends GameGui { DrawableUtil.drawArc(matrices, 160, 1600, 0, DrawableUtil.TAU, 0x00000020, false); super.render(matrices, mouseX, mouseY, delta); - DrawableUtil.renderRaceIcon(matrices, pony.getActualSpecies(), 0, 0, 16); + DrawableUtil.renderRaceIcon(matrices, pony.getSpecies(), 0, 0, 16); matrices.pop(); DrawableUtil.drawLine(matrices, mouseX, mouseY - 4, mouseX, mouseY + 4, 0xFFAAFF99); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/ButterflyEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/ButterflyEntity.java index deb7f9ba..4cd679fd 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/ButterflyEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/ButterflyEntity.java @@ -309,8 +309,8 @@ public class ButterflyEntity extends AmbientEntity { super.writeCustomDataToNbt(nbt); nbt.putInt("ticksResting", ticksResting); nbt.putInt("breedingCooldown", breedingCooldown); - NbtSerialisable.writeBlockPos("hoveringPosition", hoveringPosition, nbt); - NbtSerialisable.writeBlockPos("flowerPosition", flowerPosition, nbt); + NbtSerialisable.BLOCK_POS.writeOptional("hoveringPosition", nbt, hoveringPosition); + NbtSerialisable.BLOCK_POS.writeOptional("flowerPosition", nbt, flowerPosition); NbtCompound visited = new NbtCompound(); this.visited.forEach((pos, time) -> { visited.putLong(String.valueOf(pos.asLong()), time); @@ -323,8 +323,8 @@ public class ButterflyEntity extends AmbientEntity { super.readCustomDataFromNbt(nbt); ticksResting = nbt.getInt("ticksResting"); breedingCooldown = nbt.getInt("breedingCooldown"); - hoveringPosition = NbtSerialisable.readBlockPos("hoveringPosition", nbt); - flowerPosition = NbtSerialisable.readBlockPos("flowerPosition", nbt); + hoveringPosition = NbtSerialisable.BLOCK_POS.readOptional("hoveringPosition", nbt); + flowerPosition = NbtSerialisable.BLOCK_POS.readOptional("flowerPosition", nbt); NbtCompound visited = nbt.getCompound("visited"); this.visited.clear(); visited.getKeys().forEach(key -> { diff --git a/src/main/java/com/minelittlepony/unicopia/entity/Living.java b/src/main/java/com/minelittlepony/unicopia/entity/Living.java index 45cb9179..6a86cd00 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/Living.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/Living.java @@ -30,7 +30,9 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.projectile.ProjectileEntity; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.packet.s2c.play.EntityPassengersSetS2CPacket; import net.minecraft.particle.ParticleTypes; +import net.minecraft.server.world.ServerWorld; import net.minecraft.sound.SoundEvents; import net.minecraft.util.Hand; import net.minecraft.util.math.BlockPos; @@ -247,4 +249,10 @@ public abstract class Living implements Equine, Caste // ply.networkHandler.sendPacket(new EntityVelocityUpdateS2CPacket(ply)); //} } + + public static void transmitPassengers(Entity entity) { + if (entity.world instanceof ServerWorld sw) { + sw.getChunkManager().sendToNearbyPlayers(entity, new EntityPassengersSetS2CPacket(entity)); + } + } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Disguise.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Disguise.java index d0f7e9ba..ad663521 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Disguise.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Disguise.java @@ -112,9 +112,9 @@ public interface Disguise extends FlightType.Provider, PlayerDimensions.Provider return !isDead() && !source.getMaster().isDead(); } - static abstract class PlayerAccess extends PlayerEntity { + public static abstract class PlayerAccess extends PlayerEntity { public PlayerAccess() { super(null, null, 0, null, null); } - static TrackedData getModelBitFlag() { + public static TrackedData getModelBitFlag() { return PLAYER_MODEL_PARTS; } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityAppearance.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityAppearance.java index 3020a5c5..715c6861 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityAppearance.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityAppearance.java @@ -42,6 +42,7 @@ import net.minecraft.entity.mob.VexEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.projectile.ShulkerBulletEntity; import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; import net.minecraft.util.shape.VoxelShape; public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provider, FlightType.Provider, EntityCollisions.ComplexCollidable { @@ -132,6 +133,9 @@ public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provi entity = InteractionManager.instance().createPlayer(source.getEntity(), profile); entity.setCustomName(source.getMaster().getName()); ((PlayerEntity)entity).readNbt(nbt.getCompound("playerNbt")); + if (nbt.contains("playerVisibleParts", NbtElement.BYTE_TYPE)) { + entity.getDataTracker().set(Disguise.PlayerAccess.getModelBitFlag(), nbt.getByte("playerVisibleParts")); + } entity.setUuid(UUID.randomUUID()); entity.extinguish(); @@ -292,7 +296,7 @@ public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provi String newId = compound.getString("entityId"); String newPlayerName = null; - if (compound.contains("entity") && compound.getCompound("entity").contains("playerName")) { + if (compound.contains("entity", NbtElement.COMPOUND_TYPE) && compound.getCompound("entity").contains("playerName", NbtElement.STRING_TYPE)) { newPlayerName = compound.getCompound("entity").getString("playerName"); } @@ -303,13 +307,11 @@ public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provi remove(); } - if (compound.contains("entity")) { + if (compound.contains("entity", NbtElement.COMPOUND_TYPE)) { entityId = newId; entityNbt = compound.getCompound("entity"); - compound.getString("entityData"); - if (entity != null) { try { entity.readNbt(entityNbt); @@ -332,20 +334,16 @@ public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provi private static NbtCompound encodeEntityToNBT(Entity entity) { NbtCompound entityNbt = new NbtCompound(); - if (entity instanceof PlayerEntity) { - GameProfile profile = ((PlayerEntity)entity).getGameProfile(); + if (entity instanceof PlayerEntity player) { + GameProfile profile = player.getGameProfile(); entityNbt.putString("id", "player"); if (profile.getId() != null) { entityNbt.putUuid("playerId", profile.getId()); } entityNbt.putString("playerName", profile.getName()); - - NbtCompound tag = new NbtCompound(); - - entity.writeNbt(tag); - - entityNbt.put("playerNbt", tag); + entityNbt.putByte("playerVisibleParts", player.getDataTracker().get(Disguise.PlayerAccess.getModelBitFlag())); + entityNbt.put("playerNbt", player.writeNbt(new NbtCompound())); } else { entity.saveSelfNbt(entityNbt); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityBehaviour.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityBehaviour.java index 5adc1772..c5c9acb2 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityBehaviour.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityBehaviour.java @@ -169,6 +169,7 @@ public class EntityBehaviour { to.prevYaw = from.prevYaw; to.horizontalSpeed = from.horizontalSpeed; to.prevHorizontalSpeed = from.prevHorizontalSpeed; + to.fallDistance = 0; to.setOnGround(from.isOnGround()); to.setInvulnerable(from.isInvulnerable() || (from instanceof PlayerEntity player && player.getAbilities().creativeMode)); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntitySwap.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntitySwap.java new file mode 100644 index 00000000..c16f825e --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntitySwap.java @@ -0,0 +1,57 @@ +package com.minelittlepony.unicopia.entity.behaviour; + +import java.util.*; + +import com.minelittlepony.unicopia.entity.Living; +import com.minelittlepony.unicopia.util.Swap; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.mob.PathAwareEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.packet.s2c.play.EntitySetHeadYawS2CPacket; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.MathHelper; + +public interface EntitySwap { + Swap POSITION = Swap.of(Entity::getPos, (entity, pos) -> { + entity.teleport(pos.getX(), pos.getY(), pos.getZ()); + if (entity instanceof PathAwareEntity pae && !(entity instanceof PlayerEntity)) { + pae.getNavigation().stop(); + } + }); + Swap VELOCITY = Swap.of(Entity::getVelocity, (entity, vel) -> { + entity.setVelocity(vel); + Living.updateVelocity(entity); + }); + Swap PITCH = Swap.of(Entity::getPitch, Entity::setPitch); + Swap YAW = Swap.of(Entity::getYaw, Entity::setYaw); + Swap HEAD_YAW = Swap.of(Entity::getHeadYaw, (entity, headYaw) -> { + entity.setHeadYaw(headYaw); + if (entity.world instanceof ServerWorld sw) { + sw.getChunkManager().sendToNearbyPlayers(entity, new EntitySetHeadYawS2CPacket(entity, (byte)MathHelper.floor(entity.getHeadYaw() * 256F / 360F))); + } + }); + Swap BODY_YAW = Swap.of(Entity::getBodyYaw, Entity::setBodyYaw); + Swap FIRE_TICKS = Swap.of(Entity::getFireTicks, Entity::setFireTicks); + Swap PASSENGERS = Swap.of(entity -> new ArrayList<>(entity.getPassengerList()), (entity, passengers) -> { + entity.removeAllPassengers(); + passengers.forEach(passenger -> passenger.startRiding(entity)); + Living.transmitPassengers(entity); + }); + Swap STATUS_EFFECTS = Swap.of( + entity -> Set.copyOf(entity.getActiveStatusEffects().values()), + (entity, effects) -> { + entity.clearStatusEffects(); + effects.forEach(entity::addStatusEffect); + } + ); + Swap HEALTH = Swap.of(LivingEntity::getHealth, LivingEntity::getMaxHealth, LivingEntity::setHealth, Number::floatValue); + Swap AIR = Swap.of(LivingEntity::getAir, LivingEntity::getMaxAir, LivingEntity::setAir, Number::intValue); + + List> REGISTRY = new ArrayList<>(List.of( + Swap.union(POSITION, VELOCITY, PITCH, YAW, HEAD_YAW, BODY_YAW, FIRE_TICKS, PASSENGERS), + Swap.union(STATUS_EFFECTS, HEALTH, AIR).upcast(e -> e instanceof LivingEntity) + )); + Swap ALL = Swap.union(REGISTRY); +} diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Inventory.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Inventory.java new file mode 100644 index 00000000..13637cef --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Inventory.java @@ -0,0 +1,117 @@ +package com.minelittlepony.unicopia.entity.behaviour; + +import java.util.*; +import java.util.function.Consumer; + +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.*; +import net.minecraft.util.collection.DefaultedList; + +public record Inventory ( + Map equipment, + Optional> mainInventory + ) { + + public static Optional of(LivingEntity entity) { + Map equipment = new EnumMap<>(EquipmentSlot.class); + for (var slot : EquipmentSlot.values()) { + equipment.put(slot, entity.getEquippedStack(slot)); + } + + if (entity instanceof PlayerEntity player) { + PlayerInventory inventory = player.getInventory(); + DefaultedList mainInventory = DefaultedList.ofSize(inventory.size(), ItemStack.EMPTY); + for (int i = 0; i < mainInventory.size(); i++) { + mainInventory.set(i, inventory.getStack(i)); + } + + return Optional.of(new Inventory(equipment, Optional.of(mainInventory))); + } + + return Optional.of(new Inventory(equipment, Optional.empty())); + } + + /** + * Copies the inventory into another entity. + * + * @return Returns the left overs that could not be copied + */ + public Inventory copyInto(LivingEntity into) { + if (into instanceof PlayerEntity player) { + mainInventory().ifPresentOrElse(main -> { + PlayerInventory pe = player.getInventory(); + int i = 0; + for (; i < pe.size(); i++) { + pe.setStack(i, i < main.size() ? main.get(i) : ItemStack.EMPTY); + } + for (; i < main.size(); i++) { + into.dropStack(main.get(i)); + } + }, () -> { + PlayerInventory pe = player.getInventory(); + for (int i = 0; i < pe.size(); i++) { + pe.setStack(i, ItemStack.EMPTY); + } + equipment().forEach(player::equipStack); + }); + + return null; + } + + equipment().forEach(into::equipStack); + return this; + } + + public NbtCompound toNBT(NbtCompound compound) { + NbtCompound eq = new NbtCompound(); + equipment().forEach((slot, stack) -> { + eq.put(slot.getName(), stack.writeNbt(new NbtCompound())); + }); + compound.put("equipment", eq); + mainInventory().ifPresent(main -> { + NbtList list = new NbtList(); + main.forEach(stack -> { + list.add(stack.writeNbt(new NbtCompound())); + }); + compound.put("main", list); + }); + return compound; + } + + public static void swapInventories(LivingEntity me, Optional myInv, LivingEntity them, Optional theirInv, + Consumer outOverflowConsumer, + Consumer inOverflowConsumer) { + Optional outOverflow = Inventory.copyInventoryInto(myInv, them); + Optional inOverflow = Inventory.copyInventoryInto(theirInv, me); + + outOverflow.ifPresent(outOverflowConsumer); + inOverflow.ifPresent(inOverflowConsumer); + } + + public static Optional copyInventoryInto(Optional inventory, LivingEntity to) { + return inventory.map(inv -> inv.copyInto(to)); + } + + public static Inventory fromNBT(NbtCompound compound) { + Map equipment = new EnumMap<>(EquipmentSlot.class); + NbtCompound eq = compound.getCompound("equipment"); + eq.getKeys().forEach(key -> { + equipment.put(EquipmentSlot.byName(key), ItemStack.fromNbt(eq.getCompound(key))); + }); + + if (!compound.contains("main", NbtElement.LIST_TYPE)) { + return new Inventory(equipment, Optional.empty()); + } + + NbtList list = compound.getList("main", NbtElement.COMPOUND_TYPE); + DefaultedList main = DefaultedList.ofSize(list.size(), ItemStack.EMPTY); + for (int i = 0; i < list.size(); i++) { + main.set(i, ItemStack.fromNbt(list.getCompound(i))); + } + return new Inventory(equipment, Optional.of(main)); + } +} \ No newline at end of file diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java index 445cfc8f..1bba15e7 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java @@ -15,11 +15,13 @@ import com.minelittlepony.unicopia.*; import com.minelittlepony.unicopia.ability.AbilityDispatcher; import com.minelittlepony.unicopia.ability.EarthPonyStompAbility; import com.minelittlepony.unicopia.ability.magic.*; +import com.minelittlepony.unicopia.ability.magic.spell.AbstractDisguiseSpell; import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.ability.magic.spell.trait.TraitDiscovery; import com.minelittlepony.unicopia.advancement.UCriteria; import com.minelittlepony.unicopia.entity.*; +import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance; import com.minelittlepony.unicopia.entity.duck.LivingEntityDuck; import com.minelittlepony.unicopia.entity.effect.SunBlindnessStatusEffect; import com.minelittlepony.unicopia.entity.effect.UEffects; @@ -52,7 +54,6 @@ import net.minecraft.entity.effect.StatusEffectInstance; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; -import net.minecraft.network.packet.s2c.play.EntityPassengersSetS2CPacket; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.sound.SoundEvents; @@ -167,7 +168,13 @@ public class Pony extends Living implements Transmittable, Copieab @Override public Race getSpecies() { - return getActualSpecies(); + return getSpellSlot() + .get(SpellPredicate.IS_MIMIC, true) + .map(AbstractDisguiseSpell::getDisguise) + .map(EntityAppearance::getAppearance) + .flatMap(Pony::of) + .map(Pony::getActualSpecies) + .orElse(getActualSpecies()); } public Race getActualSpecies() { @@ -318,10 +325,7 @@ public class Pony extends Living implements Transmittable, Copieab } } else { entity.stopRiding(); - - if (ridee instanceof ServerPlayerEntity) { - ((ServerPlayerEntity)ridee).networkHandler.sendPacket(new EntityPassengersSetS2CPacket(ridee)); - } + Living.transmitPassengers(ridee); } } } @@ -567,7 +571,7 @@ public class Pony extends Living implements Transmittable, Copieab compound.putString("playerSpecies", Race.REGISTRY.getId(getActualSpecies()).toString()); compound.putFloat("magicExhaustion", magicExhaustion); compound.putInt("ticksHanging", ticksHanging); - NbtSerialisable.writeBlockPos("hangingPosition", hangingPosition, compound); + BLOCK_POS.writeOptional("hangingPosition", compound, hangingPosition); compound.putInt("ticksInSun", ticksInSun); compound.putBoolean("hasShades", hasShades); compound.put("powers", powers.toNBT()); @@ -604,7 +608,7 @@ public class Pony extends Living implements Transmittable, Copieab magicExhaustion = compound.getFloat("magicExhaustion"); ticksHanging = compound.getInt("ticksHanging"); - hangingPosition = NbtSerialisable.readBlockPos("hangingPosition", compound); + hangingPosition = NbtSerialisable.BLOCK_POS.readOptional("hangingPosition", compound); ticksInSun = compound.getInt("ticksInSun"); hasShades = compound.getBoolean("hasShades"); diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinFlowableFluid.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinFlowableFluid.java new file mode 100644 index 00000000..9f81743e --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinFlowableFluid.java @@ -0,0 +1,25 @@ +package com.minelittlepony.unicopia.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import com.minelittlepony.unicopia.ability.magic.spell.effect.HydrophobicSpell; + +import net.minecraft.block.BlockState; +import net.minecraft.fluid.FlowableFluid; +import net.minecraft.fluid.Fluid; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.BlockView; +import net.minecraft.world.World; + +@Mixin(FlowableFluid.class) +abstract class MixinFlowableFluid { + @Inject(method = "canFill", at = @At("HEAD"), cancellable = true) + private void onCanFill(BlockView world, BlockPos pos, BlockState state, Fluid fluid, CallbackInfoReturnable info) { + if (world instanceof World w && HydrophobicSpell.blocksFluidFlow(w, pos, state, fluid)) { + info.setReturnValue(false); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/particle/ParticleSource.java b/src/main/java/com/minelittlepony/unicopia/particle/ParticleSource.java index 796aa5a4..f7e9f88b 100644 --- a/src/main/java/com/minelittlepony/unicopia/particle/ParticleSource.java +++ b/src/main/java/com/minelittlepony/unicopia/particle/ParticleSource.java @@ -39,6 +39,6 @@ public interface ParticleSource extends ParticleSpawner { @Override default void addParticle(ParticleEffect effect, Vec3d position, Vec3d velocity) { - getReferenceWorld().addParticle(effect, position.x, position.y, position.z, velocity.x, velocity.y, velocity.z); + ParticleUtils.spawnParticle(getReferenceWorld(), effect, position, velocity); } } diff --git a/src/main/java/com/minelittlepony/unicopia/util/NbtSerialisable.java b/src/main/java/com/minelittlepony/unicopia/util/NbtSerialisable.java index aab0b204..08f0abcd 100644 --- a/src/main/java/com/minelittlepony/unicopia/util/NbtSerialisable.java +++ b/src/main/java/com/minelittlepony/unicopia/util/NbtSerialisable.java @@ -4,11 +4,15 @@ import java.util.*; import java.util.function.Function; import java.util.stream.Stream; +import com.mojang.serialization.Codec; + import net.minecraft.nbt.*; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; public interface NbtSerialisable { + Serializer BLOCK_POS = Serializer.of(NbtHelper::toBlockPos, NbtHelper::fromBlockPos); + /** * Called to save this to nbt to persist state on file or to transmit over the network * @@ -41,12 +45,12 @@ public interface NbtSerialisable { return new Vec3d(list.getDouble(0), list.getDouble(1), list.getDouble(2)); } - static void writeBlockPos(String name, Optional pos, NbtCompound nbt) { - pos.map(NbtHelper::fromBlockPos).ifPresent(p -> nbt.put(name, p)); + static T decode(Codec codec, NbtElement nbt) { + return codec.decode(NbtOps.INSTANCE, nbt).result().get().getFirst(); } - static Optional readBlockPos(String name, NbtCompound nbt) { - return nbt.contains(name) ? Optional.ofNullable(NbtHelper.toBlockPos(nbt.getCompound(name))) : Optional.empty(); + static NbtElement encode(Codec codec, T value) { + return codec.encodeStart(NbtOps.INSTANCE, value).result().get(); } interface Serializer { @@ -54,6 +58,16 @@ public interface NbtSerialisable { NbtCompound write(T t); + default Optional readOptional(String name, NbtCompound compound) { + return compound.contains(name, NbtElement.COMPOUND_TYPE) + ? Optional.ofNullable(read(compound.getCompound(name))) + : Optional.empty(); + } + + default void writeOptional(String name, NbtCompound compound, Optional t) { + t.map(this::write).ifPresent(tag -> compound.put(name, tag)); + } + default T read(NbtElement element) { return read((NbtCompound)element); } diff --git a/src/main/java/com/minelittlepony/unicopia/util/Swap.java b/src/main/java/com/minelittlepony/unicopia/util/Swap.java new file mode 100644 index 00000000..a9fc8f47 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/util/Swap.java @@ -0,0 +1,67 @@ +package com.minelittlepony.unicopia.util; + +import java.util.*; +import java.util.function.*; + +/** + * Utility for performing an atomic swap of two values. + */ +@FunctionalInterface +public interface Swap extends BiConsumer { + /** + * Returns a new swap that performs the same action as this one only if the passed in shouldRun check passes on both inputs. + */ + @SuppressWarnings("unchecked") + default Swap upcast(Predicate shouldRun) { + Swap swap = this; + return (a, b) -> { + if (shouldRun.test(a) && shouldRun.test(b)) { + swap.accept((T)a, (T)b); + } + }; + } + + @SafeVarargs + static Swap union(Swap... swaps) { + return union(List.of(swaps)); + } + + /** + * Creates a swap from a collection of multiple swaps. + * Executes them in the order they are presented by Iterable#forEach + * + * Changes to the underlying list an equivalent change to the swap returned by this method. + */ + static Swap union(Iterable> swaps) { + return (a, b) -> swaps.forEach(consumer -> consumer.accept(a, b)); + } + + /** + * Creates a swap for switching numerical values. + */ + static Swap of(Function getter, BiConsumer setter, Function converter) { + return of(getter, e -> converter.apply(1F), setter, converter); + } + + /** + * Creates a swap for converting numerical values where the source and destination may have different scales. + */ + static Swap of(Function getter, Function maxGetter, BiConsumer setter, Function converter) { + return of( + e -> getter.apply(e).floatValue() / maxGetter.apply(e).floatValue(), + (e, value) -> setter.accept(e, converter.apply(value.floatValue() * maxGetter.apply(e).floatValue())) + ); + } + + /** + * Creates a swap from a getter and setter. + */ + static Swap of(Function getter, BiConsumer setter) { + return (a, b) -> { + T aa = getter.apply(a); + T bb = getter.apply(b); + setter.accept(a, bb); + setter.accept(b, aa); + }; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/util/VecHelper.java b/src/main/java/com/minelittlepony/unicopia/util/VecHelper.java index 113bd994..cb5b5daf 100644 --- a/src/main/java/com/minelittlepony/unicopia/util/VecHelper.java +++ b/src/main/java/com/minelittlepony/unicopia/util/VecHelper.java @@ -8,7 +8,7 @@ import org.jetbrains.annotations.Nullable; import net.minecraft.entity.Entity; import net.minecraft.util.math.Box; import net.minecraft.util.math.Vec3d; -import net.minecraft.world.World; +import net.minecraft.world.EntityView; public interface VecHelper { @@ -24,7 +24,7 @@ public interface VecHelper { }; } - static List findInRange(@Nullable Entity origin, World w, Vec3d pos, double radius, @Nullable Predicate predicate) { + static List findInRange(@Nullable Entity origin, EntityView w, Vec3d pos, double radius, @Nullable Predicate predicate) { double diameter = radius * 2; return w.getOtherEntities(origin, Box.of(pos, diameter, diameter, diameter), predicate == null ? inRange(pos, radius) : inRange(pos, radius).and(predicate)); } diff --git a/src/main/java/com/minelittlepony/unicopia/util/shape/PointGenerator.java b/src/main/java/com/minelittlepony/unicopia/util/shape/PointGenerator.java index 71d90f4e..00c0c155 100644 --- a/src/main/java/com/minelittlepony/unicopia/util/shape/PointGenerator.java +++ b/src/main/java/com/minelittlepony/unicopia/util/shape/PointGenerator.java @@ -15,7 +15,6 @@ import net.minecraft.util.math.random.Random; *Interface for a 3d shape, used for spawning particles in a designated area (or anything else you need shapes for). */ public interface PointGenerator { - /** * Get the volume of space filled by this shape, or the surface area if hollow. * @@ -72,7 +71,6 @@ public interface PointGenerator { default PointGenerator offset(Vec3d offset) { final PointGenerator source = this; return new PointGenerator() { - @Override public double getVolumeOfSpawnableSpace() { return source.getVolumeOfSpawnableSpace(); diff --git a/src/main/java/com/minelittlepony/unicopia/util/shape/Shape.java b/src/main/java/com/minelittlepony/unicopia/util/shape/Shape.java index cb6a38ba..b7cea532 100644 --- a/src/main/java/com/minelittlepony/unicopia/util/shape/Shape.java +++ b/src/main/java/com/minelittlepony/unicopia/util/shape/Shape.java @@ -2,8 +2,8 @@ package com.minelittlepony.unicopia.util.shape; import java.util.stream.Stream; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.*; +import net.minecraft.util.math.random.Random; /** * @@ -45,4 +45,51 @@ public interface Shape extends PointGenerator { } return new RotatedShape(this, pitch, yaw); } + + /** + * Returns a new point generator where all of its points are offset by the specified amount. + */ + @Override + default Shape offset(Vec3i offset) { + return offset(Vec3d.of(offset)); + } + + /** + * Returns a new point generator where all of its points are offset by the specified amount. + */ + @Override + default Shape offset(Vec3d offset) { + final Shape source = this; + return new Shape() { + @Override + public double getVolumeOfSpawnableSpace() { + return source.getVolumeOfSpawnableSpace(); + } + + @Override + public Vec3d computePoint(Random rand) { + return source.computePoint(rand).add(offset); + } + + @Override + public Stream getBlockPositions() { + return source.getBlockPositions().map(pos -> pos.add(offset.x, offset.y, offset.z)); + } + + @Override + public Vec3d getLowerBound() { + return source.getLowerBound().add(offset); + } + + @Override + public Vec3d getUpperBound() { + return source.getLowerBound().add(offset); + } + + @Override + public boolean isPointInside(Vec3d point) { + return source.isPointInside(point.subtract(offset)); + } + }; + } } diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index e0c83334..bb9abb7c 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -184,6 +184,8 @@ "spell.unicopia.frost": "Frost", "spell.unicopia.frost.lore": "Chilling to the touch, this gem will freeze whatever it is used on", + "spell.unicopia.hydrophobic": "Repel Water", + "spell.unicopia.hydrophobic.lore": "Creates a protective bubble around the user that prevents water from entering", "spell.unicopia.chilling_breath": "Chilling Breath", "spell.unicopia.chilling_breath.lore": "Alters the ability of certain objects to distenguish between hot and cold", "spell.unicopia.scorch": "Scorching", @@ -202,6 +204,10 @@ "spell.unicopia.vortex.lore": "Creates a magnetic force that pulls in other targets", "spell.unicopia.dark_vortex": "Dark Vortex", "spell.unicopia.dark_vortex.lore": "Creates a black hole from which nothing can escape", + "spell.unicopia.mimic": "Mimic", + "spell.unicopia.mimic.lore": "Temporarily changes the caster's appearance to look like another entity or player", + "spell.unicopia.mind_swap": "Mind Swap", + "spell.unicopia.mind_swap.lore": "Temporarily swaps the caster's mind into the body of another entity or player", "spell.unicopia.displacement": "Displacement", "spell.unicopia.displacement.lore": "Swaps the caster's locatin with that of another entity", "spell.unicopia.portal": "Arcane Rift", diff --git a/src/main/resources/unicopia.mixin.json b/src/main/resources/unicopia.mixin.json index 7925c67b..6acd7d0d 100644 --- a/src/main/resources/unicopia.mixin.json +++ b/src/main/resources/unicopia.mixin.json @@ -15,6 +15,7 @@ "MixinEntity", "MixinFallingBlock", "MixinFallingBlockEntity", + "MixinFlowableFluid", "MixinItem", "MixinItemEntity", "MixinLivingEntity",