From f3d6c7ce287736c4ceda01e95f51d3b2f28f9b75 Mon Sep 17 00:00:00 2001 From: Sollace Date: Tue, 21 Dec 2021 17:28:47 +0200 Subject: [PATCH] Work on allowing more than one spell to be attached --- .../ability/PegasusRainboomAbility.java | 2 +- .../unicopia/ability/magic/Caster.java | 5 - .../ability/magic/SpellContainer.java | 42 +++++++ .../magic/spell/AbstractDelegatingSpell.java | 2 +- .../ability/magic/spell/PlaceableSpell.java | 2 +- .../ability/magic/spell/ThrowableSpell.java | 2 +- .../magic/spell/effect/AbstractSpell.java | 2 +- .../ability/magic/spell/effect/SpellType.java | 2 +- .../unicopia/entity/CastSpellEntity.java | 22 ++-- .../unicopia/entity/Creature.java | 2 +- .../unicopia/entity/Living.java | 8 +- .../unicopia/entity/behaviour/Disguise.java | 2 +- .../entity/effect/RaceChangeStatusEffect.java | 2 +- .../unicopia/entity/player/Pony.java | 12 +- .../unicopia/mixin/MixinMilkBucketItem.java | 2 +- .../unicopia/network/EffectSync.java | 106 ----------------- .../unicopia/network/datasync/EffectSync.java | 99 ++++++++++++++++ .../network/datasync/NetworkedReference.java | 19 +++ .../datasync/NetworkedReferenceSet.java | 110 ++++++++++++++++++ .../datasync/SpellNetworkedReference.java | 110 ++++++++++++++++++ .../network/{ => datasync}/Transmittable.java | 2 +- .../projectile/MagicProjectileEntity.java | 4 +- 22 files changed, 416 insertions(+), 143 deletions(-) delete mode 100644 src/main/java/com/minelittlepony/unicopia/network/EffectSync.java create mode 100644 src/main/java/com/minelittlepony/unicopia/network/datasync/EffectSync.java create mode 100644 src/main/java/com/minelittlepony/unicopia/network/datasync/NetworkedReference.java create mode 100644 src/main/java/com/minelittlepony/unicopia/network/datasync/NetworkedReferenceSet.java create mode 100644 src/main/java/com/minelittlepony/unicopia/network/datasync/SpellNetworkedReference.java rename src/main/java/com/minelittlepony/unicopia/network/{ => datasync}/Transmittable.java (58%) diff --git a/src/main/java/com/minelittlepony/unicopia/ability/PegasusRainboomAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/PegasusRainboomAbility.java index 77f95fc6..a841ab00 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/PegasusRainboomAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/PegasusRainboomAbility.java @@ -68,7 +68,7 @@ public class PegasusRainboomAbility implements Ability { if (player.getPhysics().isFlying() && !player.getSpellSlot().isPresent()) { player.getMagicalReserves().getMana().multiply(0.1F); player.addParticle(new OrientedBillboardParticleEffect(UParticles.RAINBOOM_RING, player.getPhysics().getMotionAngle()), player.getOriginVector(), Vec3d.ZERO); - player.setSpell(SpellType.RAINBOOM.create(SpellTraits.EMPTY)); + player.getSpellSlot().put(SpellType.RAINBOOM.create(SpellTraits.EMPTY)); } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java index f50f3bbe..ef2d25f8 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java @@ -8,7 +8,6 @@ import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.EquinePredicates; import com.minelittlepony.unicopia.Owned; -import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.entity.Physics; import com.minelittlepony.unicopia.entity.PonyContainer; import com.minelittlepony.unicopia.particle.ParticleSource; @@ -29,10 +28,6 @@ public interface Caster extends Owned, Levelled, Affi SpellContainer getSpellSlot(); - default void setSpell(@Nullable Spell spell) { - getSpellSlot().put(spell); - } - /** * Gets the entity directly responsible for casting. */ diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellContainer.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellContainer.java index a4681482..425716f5 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellContainer.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellContainer.java @@ -1,6 +1,7 @@ package com.minelittlepony.unicopia.ability.magic; import java.util.Optional; +import java.util.function.Predicate; import org.jetbrains.annotations.Nullable; @@ -15,6 +16,12 @@ public interface SpellContainer { @Override public void put(Spell effect) { } + + @Override + public void clear() { } + + @Override + public void removeIf(Predicate effect, boolean update) { } }; /** @@ -40,4 +47,39 @@ public interface SpellContainer { * Sets the active effect. */ void put(@Nullable Spell effect); + + /** + * Removes all matching active effects. + */ + void removeIf(Predicate effect, boolean update); + + /** + * Removes all effects currently active in this slot. + */ + void clear(); + + interface Delegate extends SpellContainer { + + SpellContainer delegate(); + + @Override + default Optional get(@Nullable SpellPredicate type, boolean update) { + return delegate().get(type, update); + } + + @Override + default void put(@Nullable Spell effect) { + delegate().put(effect); + } + + @Override + default void removeIf(Predicate effect, boolean update) { + delegate().removeIf(effect, update); + } + + @Override + default void clear() { + delegate().clear(); + } + } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractDelegatingSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractDelegatingSpell.java index d074cdd7..79f67c5a 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractDelegatingSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractDelegatingSpell.java @@ -63,7 +63,7 @@ public abstract class AbstractDelegatingSpell implements ProjectileSpell { @Override public boolean apply(Caster caster) { - caster.setSpell(this); + caster.getSpellSlot().put(this); return true; } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java index 6b63a581..425fbd7e 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java @@ -71,7 +71,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell { CastSpellEntity entity = UEntities.CAST_SPELL.create(source.getWorld()); Vec3d pos = castEntity.getPosition().orElse(source.getOriginVector()); entity.updatePositionAndAngles(pos.x, pos.y, pos.z, 0, 0); - entity.setSpell(this); + entity.getSpellSlot().put(this); entity.setMaster(source.getMaster()); entity.world.spawnEntity(entity); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/ThrowableSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/ThrowableSpell.java index fb0a73cc..43e98834 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/ThrowableSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/ThrowableSpell.java @@ -50,7 +50,7 @@ public final class ThrowableSpell extends AbstractDelegatingSpell { MagicProjectileEntity projectile = new MagicProjectileEntity(world, entity); projectile.setItem(GemstoneItem.enchanted(UItems.GEMSTONE.getDefaultStack(), spell.getType())); - projectile.setSpell(this); + projectile.getSpellSlot().put(this); projectile.setProperties(entity, entity.getPitch(), entity.getYaw(), 0, 1.5F, 1); projectile.setHydrophobic(); configureProjectile(projectile, caster); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AbstractSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AbstractSpell.java index a1dc7b52..e1058865 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AbstractSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AbstractSpell.java @@ -67,7 +67,7 @@ public abstract class AbstractSpell implements Spell { @Override public final boolean apply(Caster caster) { - caster.setSpell(this); + caster.getSpellSlot().put(this); return true; } 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 85a19585..94e28d3d 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 @@ -141,7 +141,7 @@ public final class SpellType implements Affine, SpellPredicate< @Nullable public T apply(Caster caster, SpellTraits traits) { if (isEmpty()) { - caster.setSpell(null); + caster.getSpellSlot().clear(); return null; } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/CastSpellEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/CastSpellEntity.java index 63e43759..c5f93cae 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/CastSpellEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/CastSpellEntity.java @@ -37,6 +37,19 @@ public class CastSpellEntity extends Entity implements Caster { private final EntityReference owner = new EntityReference<>(); + private final SpellContainer spell = new SpellContainer.Delegate() { + @Override + public SpellContainer delegate() { + return Caster.of(getMaster()).map(Caster::getSpellSlot).orElse(SpellContainer.EMPTY); + } + + @Override + public void put(Spell spell) { + getDataTracker().set(SPELL, Optional.ofNullable(spell).map(Spell::getUuid)); + SpellContainer.Delegate.super.put(spell); + } + }; + private BlockPos lastPos; private int orphanedTicks; @@ -94,7 +107,7 @@ public class CastSpellEntity extends Entity implements Caster { UUID spellId = dataTracker.get(SPELL).orElse(null); if (!c.getSpellSlot().get(true).filter(s -> s.getUuid().equals(spellId) && s.tick(this, Situation.GROUND_ENTITY)).isPresent()) { - c.setSpell(null); + c.getSpellSlot().clear(); return false; } @@ -118,11 +131,6 @@ public class CastSpellEntity extends Entity implements Caster { this.owner.set(owner); } - @Override - public void setSpell(Spell spell) { - getDataTracker().set(SPELL, Optional.ofNullable(spell).map(Spell::getUuid)); - } - @Override public Entity getEntity() { return this; @@ -150,7 +158,7 @@ public class CastSpellEntity extends Entity implements Caster { @Override public SpellContainer getSpellSlot() { - return Caster.of(getMaster()).map(Caster::getSpellSlot).orElse(SpellContainer.EMPTY); + return spell; } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/entity/Creature.java b/src/main/java/com/minelittlepony/unicopia/entity/Creature.java index 330d6f04..ec93db12 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/Creature.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/Creature.java @@ -106,7 +106,7 @@ public class Creature extends Living { public void fromNBT(NbtCompound compound) { super.fromNBT(compound); if (compound.contains("effect")) { - setSpell(SpellType.fromNBT(compound.getCompound("effect"))); + getSpellSlot().put(SpellType.fromNBT(compound.getCompound("effect"))); } physics.fromNBT(compound); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/Living.java b/src/main/java/com/minelittlepony/unicopia/entity/Living.java index 70b817e1..a8278005 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/Living.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/Living.java @@ -10,7 +10,7 @@ import com.minelittlepony.unicopia.ability.magic.SpellContainer; import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.item.UItems; -import com.minelittlepony.unicopia.network.EffectSync; +import com.minelittlepony.unicopia.network.datasync.EffectSync; import com.minelittlepony.unicopia.projectile.ProjectileImpactListener; import com.minelittlepony.unicopia.util.MagicalDamageSource; @@ -85,11 +85,7 @@ public abstract class Living implements Equine, Caste @Override public void tick() { - getSpellSlot().get(true).ifPresent(effect -> { - if (!effect.tick(this, Situation.BODY)) { - setSpell(null); - } - }); + getSpellSlot().removeIf(effect -> !effect.tick(this, Situation.BODY), true); if (invinsibilityTicks > 0) { invinsibilityTicks--; 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 48f205bb..421f0c02 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Disguise.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Disguise.java @@ -196,7 +196,7 @@ public class Disguise implements NbtSerialisable, PlayerDimensions.Provider, Fli return; } - Caster.of(entity).ifPresent(c -> c.setSpell(null)); + Caster.of(entity).ifPresent(c -> c.getSpellSlot().clear()); if (entity instanceof LivingEntity) { ((LivingEntity) entity).getAttributeInstance(PlayerAttributes.ENTITY_GRAVTY_MODIFIER).clearModifiers(); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/effect/RaceChangeStatusEffect.java b/src/main/java/com/minelittlepony/unicopia/entity/effect/RaceChangeStatusEffect.java index 34464d4c..be354d3e 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/effect/RaceChangeStatusEffect.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/effect/RaceChangeStatusEffect.java @@ -104,7 +104,7 @@ public class RaceChangeStatusEffect extends StatusEffect { eq.setSpecies(species); if (eq instanceof Caster) { - ((Caster)eq).setSpell(null); + ((Caster)eq).getSpellSlot().clear(); } if (eq instanceof Pony) { 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 8cfc8cac..7d894534 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java @@ -30,7 +30,8 @@ import com.minelittlepony.unicopia.item.toxin.Toxin; import com.minelittlepony.unicopia.network.Channel; import com.minelittlepony.unicopia.network.MsgOtherPlayerCapabilities; import com.minelittlepony.unicopia.network.MsgRequestSpeciesChange; -import com.minelittlepony.unicopia.network.Transmittable; +import com.minelittlepony.unicopia.network.datasync.Transmittable; +import com.minelittlepony.unicopia.network.datasync.EffectSync.UpdateCallback; import com.minelittlepony.unicopia.util.Copieable; import com.minelittlepony.unicopia.util.MagicalDamageSource; import com.minelittlepony.unicopia.util.Tickable; @@ -61,7 +62,7 @@ import net.minecraft.text.TranslatableText; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; -public class Pony extends Living implements Transmittable, Copieable { +public class Pony extends Living implements Transmittable, Copieable, UpdateCallback { private static final TrackedData RACE = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.INTEGER); @@ -505,17 +506,16 @@ public class Pony extends Living implements Transmittable, Copieab public void copyFrom(Pony oldPlayer) { speciesPersisted = oldPlayer.speciesPersisted; if (!oldPlayer.getEntity().isRemoved()) { - setSpell(oldPlayer.getSpellSlot().get(true).orElse(null)); + getSpellSlot().put(oldPlayer.getSpellSlot().get(true).orElse(null)); } - oldPlayer.setSpell(null); + oldPlayer.getSpellSlot().put(null); setSpecies(oldPlayer.getSpecies()); getDiscoveries().copyFrom(oldPlayer.getDiscoveries()); setDirty(); } @Override - public void setSpell(@Nullable Spell effect) { - super.setSpell(effect); + public void onSpellSet(@Nullable Spell spell) { setDirty(); } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinMilkBucketItem.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinMilkBucketItem.java index cda0d2ed..c89d5c3b 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinMilkBucketItem.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinMilkBucketItem.java @@ -18,6 +18,6 @@ abstract class MixinMilkBucketItem extends Item { @Inject(method = "finishUsing", at = @At("HEAD"), cancellable = true) private void finishUsing(ItemStack stack, World world, LivingEntity entity, CallbackInfoReturnable info) { - Caster.of(entity).ifPresent(c -> c.setSpell(null)); + Caster.of(entity).ifPresent(c -> c.getSpellSlot().clear()); } } diff --git a/src/main/java/com/minelittlepony/unicopia/network/EffectSync.java b/src/main/java/com/minelittlepony/unicopia/network/EffectSync.java deleted file mode 100644 index f91fec06..00000000 --- a/src/main/java/com/minelittlepony/unicopia/network/EffectSync.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.minelittlepony.unicopia.network; - -import java.util.Objects; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import com.minelittlepony.unicopia.ability.magic.Caster; -import com.minelittlepony.unicopia.ability.magic.SpellContainer; -import com.minelittlepony.unicopia.ability.magic.SpellPredicate; -import com.minelittlepony.unicopia.ability.magic.spell.Spell; -import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; - -import net.minecraft.entity.data.TrackedData; -import net.minecraft.nbt.NbtCompound; - -/** - * Synchronisation class for spells. - * Since we can't have our own serializers, we have to intelligently - * determine whether to update it from an nbt tag. - * - * @param The owning entity - */ -public class EffectSync implements SpellContainer { - - private Optional spell = Optional.empty(); - - private final Caster owner; - - private final TrackedData param; - - @Nullable - private NbtCompound lastValue; - - public EffectSync(Caster owner, TrackedData param) { - this.owner = owner; - this.param = param; - } - - @Override - @SuppressWarnings("unchecked") - public Optional get(@Nullable SpellPredicate type, boolean update) { - if (update) { - sync(true); - } - - if (checkReference() && (type == null || type.test(spell.get()))) { - return (Optional)spell; - } - - return Optional.empty(); - } - - @Override - public boolean isPresent() { - sync(false); - return checkReference(); - } - - private boolean checkReference() { - return spell.isPresent() && !spell.get().isDead(); - } - - private void sync(boolean force) { - @Nullable - NbtCompound comp = owner.getEntity().getDataTracker().get(param); - - @Nullable - Spell effect = spell.orElse(null); - - if (comp == null || !comp.contains("effect_id") || !comp.contains("uuid")) { - if (effect != null) { - updateReference(null); - } - } else if (effect == null || !effect.getUuid().equals(comp.getUuid("uuid"))) { - updateReference(SpellType.fromNBT(comp)); - } else if (owner.isClient()) { - if (!Objects.equals(lastValue, comp)) { - lastValue = comp; - effect.fromNBT(comp); - } - } else if (force && effect.isDirty()) { - put(effect); - } - } - - @Override - public void put(@Nullable Spell effect) { - effect = effect == null || effect.isDead() ? null : effect; - updateReference(effect); - owner.getEntity().getDataTracker().set(param, effect == null ? new NbtCompound() : SpellType.toNBT(effect)); - } - - private void updateReference(@Nullable Spell effect) { - @Nullable - Spell old = spell.orElse(null); - if (old != effect) { - spell = Optional.ofNullable(effect); - - if (old != null && (effect == null || !old.getUuid().equals(effect.getUuid()))) { - old.setDead(); - old.onDestroyed(owner); - } - } - } -} diff --git a/src/main/java/com/minelittlepony/unicopia/network/datasync/EffectSync.java b/src/main/java/com/minelittlepony/unicopia/network/datasync/EffectSync.java new file mode 100644 index 00000000..a499f3ca --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/network/datasync/EffectSync.java @@ -0,0 +1,99 @@ +package com.minelittlepony.unicopia.network.datasync; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.SpellContainer; +import com.minelittlepony.unicopia.ability.magic.SpellPredicate; +import com.minelittlepony.unicopia.ability.magic.spell.Spell; + +import net.minecraft.entity.data.TrackedData; +import net.minecraft.nbt.NbtCompound; + +/** + * Synchronisation class for spells. + * Since we can't have our own serializers, we have to intelligently + * determine whether to update it from an nbt tag. + * + * @param The owning entity + */ +public class EffectSync implements SpellContainer { + + private final NetworkedReferenceSet spells; + + private final Caster owner; + + private final TrackedData param; + + @Nullable + private NbtCompound lastValue; + + public EffectSync(Caster owner, TrackedData param) { + spells = new NetworkedReferenceSet<>(Spell::getUuid, () -> new SpellNetworkedReference<>(owner)); + this.owner = owner; + this.param = param; + } + + @Override + @SuppressWarnings("unchecked") + public Optional get(@Nullable SpellPredicate type, boolean update) { + return (Optional)(type == null ? read(update, true).findFirst() : read(update, true).filter(type).findFirst()); + } + + @Override + public boolean isPresent() { + return read(true, false).findFirst().isPresent(); + } + + @Override + public void put(@Nullable Spell effect) { + spells.addReference(effect); + write(); + if (owner instanceof UpdateCallback) { + ((UpdateCallback)owner).onSpellSet(effect); + } + } + + @Override + public void removeIf(Predicate test, boolean update) { + read(effect -> { + if (test.test(effect)) { + spells.removeReference(effect); + } + }); + } + + @Override + public void clear() { + put(null); + } + + private Stream read(boolean synchronize, boolean sendUpdate) { + if (synchronize && spells.fromNbt(owner.getEntity().getDataTracker().get(param)) && sendUpdate) { + write(); + } + + return spells.getReferences(); + } + + private void read(Consumer consumer) { + spells.fromNbt(owner.getEntity().getDataTracker().get(param)); + spells.getReferences().toList().forEach(consumer); + write(); + } + + private void write() { + if (spells.isDirty()) { + owner.getEntity().getDataTracker().set(param, spells.toNbt()); + } + } + + public interface UpdateCallback { + void onSpellSet(@Nullable Spell spell); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/network/datasync/NetworkedReference.java b/src/main/java/com/minelittlepony/unicopia/network/datasync/NetworkedReference.java new file mode 100644 index 00000000..6203b101 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/network/datasync/NetworkedReference.java @@ -0,0 +1,19 @@ +package com.minelittlepony.unicopia.network.datasync; + +import java.util.Optional; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.nbt.NbtCompound; + +public interface NetworkedReference { + Optional getReference(); + + Optional updateReference(@Nullable T newValue); + + boolean fromNbt(NbtCompound comp); + + NbtCompound toNbt(); + + boolean isDirty(); +} diff --git a/src/main/java/com/minelittlepony/unicopia/network/datasync/NetworkedReferenceSet.java b/src/main/java/com/minelittlepony/unicopia/network/datasync/NetworkedReferenceSet.java new file mode 100644 index 00000000..6cb7781c --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/network/datasync/NetworkedReferenceSet.java @@ -0,0 +1,110 @@ +package com.minelittlepony.unicopia.network.datasync; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.nbt.NbtList; +import net.minecraft.nbt.NbtString; + +public class NetworkedReferenceSet { + + private final List ids = new ArrayList<>(); + + private final Map> values = new HashMap<>(); + + private final Function uuidConverter; + private final Supplier> factory; + + private boolean dirty; + + public NetworkedReferenceSet(Function uuidConverter, Supplier> factory) { + this.uuidConverter = uuidConverter; + this.factory = factory; + } + + public Stream getReferences() { + return ids.stream().map(id -> values.get(id)) + .map(a -> a.getReference()) + .filter(Optional::isPresent) + .map(Optional::get); + } + + public void addReference(@Nullable T newValue) { + if (newValue != null) { + addReference(uuidConverter.apply(newValue)); + } + } + + private NetworkedReference addReference(UUID newValue) { + return values.computeIfAbsent(newValue, id -> { + dirty = true; + ids.remove(id); + ids.add(0, id); + return factory.get(); + }); + } + + public void removeReference(@Nullable T oldValue) { + if (oldValue != null) { + removeReference(uuidConverter.apply(oldValue)); + } + } + + private void removeReference(UUID id) { + dirty |= ids.remove(id); + NetworkedReference i = values.remove(id); + if (i != null) { + dirty = true; + i.updateReference(null); + } + } + + public boolean fromNbt(NbtCompound comp) { + + List incoming = new ArrayList<>(); + comp.getList("keys", NbtElement.STRING_TYPE).forEach(key -> { + incoming.add(UUID.fromString(key.asString())); + }); + + ids.stream().filter(id -> !incoming.contains(id)).forEach(this::removeReference); + + boolean[] send = new boolean[0]; + incoming.forEach(kept -> { + NetworkedReference i = addReference(kept); + send[0] |= i.fromNbt(comp.getCompound(kept.toString())); + if (i.getReference().isEmpty()) { + removeReference(kept); + } + }); + dirty = false; + return send[0]; + } + + public NbtCompound toNbt() { + NbtCompound tag = new NbtCompound(); + NbtList ids = new NbtList(); + this.ids.forEach(id -> { + String sid = id.toString(); + ids.add(NbtString.of(sid)); + tag.put(sid, values.get(id).toNbt()); + }); + tag.put("key", ids); + dirty = false; + return tag; + } + + public boolean isDirty() { + return dirty || values.values().stream().anyMatch(NetworkedReference::isDirty); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/network/datasync/SpellNetworkedReference.java b/src/main/java/com/minelittlepony/unicopia/network/datasync/SpellNetworkedReference.java new file mode 100644 index 00000000..f957c9b6 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/network/datasync/SpellNetworkedReference.java @@ -0,0 +1,110 @@ +package com.minelittlepony.unicopia.network.datasync; + +import java.util.Objects; +import java.util.Optional; + +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.spell.Spell; +import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; + +import net.minecraft.nbt.NbtCompound; + +public class SpellNetworkedReference implements NetworkedReference { + private Optional currentValue = Optional.empty(); + + @Nullable + private NbtCompound lastValue; + + private final Caster owner; + + private boolean dirty; + + public SpellNetworkedReference(Caster owner) { + this.owner = owner; + } + + @Override + public Optional getReference() { + return currentValue.filter(s -> !s.isDead()); + } + + private boolean mustDelete(@Nullable NbtCompound comp) { + return (comp == null || !comp.contains("effect_id") || !comp.contains("uuid")) && currentValue.isPresent(); + } + + private boolean mustReplace(NbtCompound comp) { + return currentValue.isEmpty() || !currentValue.get().getUuid().equals(comp.getUuid("uuid")); + } + + private boolean mustUpdate(NbtCompound comp) { + if (owner.isClient() && !Objects.equals(lastValue, comp)) { + lastValue = comp; + return true; + } + return false; + } + + private boolean mustSend() { + return currentValue.filter(Spell::isDirty).isPresent(); + } + + @Override + public Optional updateReference(@Nullable T newValue) { + newValue = newValue == null || newValue.isDead() ? null : newValue; + + @Nullable + T oldValue = currentValue.orElse(null); + if (oldValue != newValue) { + dirty = true; + currentValue = Optional.ofNullable(newValue); + + if (oldValue != null && (newValue == null || !oldValue.getUuid().equals(newValue.getUuid()))) { + oldValue.setDead(); + oldValue.onDestroyed(owner); + } + } + + return currentValue; + } + + @Override + @SuppressWarnings("unchecked") + public boolean fromNbt(NbtCompound comp) { + dirty = false; + + if (mustDelete(comp)) { + updateReference(null); + return false; + } + + if (mustReplace(comp)) { + updateReference((T)SpellType.fromNBT(comp)); + return false; + } + + if (mustUpdate(comp)) { + currentValue.ifPresent(s -> s.fromNBT(comp)); + return false; + } + + if (mustSend()) { + updateReference(getReference().orElse(null)); + return true; + } + + return false; + } + + @Override + public NbtCompound toNbt() { + dirty = false; + return getReference().map(SpellType::toNBT).orElseGet(NbtCompound::new); + } + + @Override + public boolean isDirty() { + return dirty || mustSend(); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/network/Transmittable.java b/src/main/java/com/minelittlepony/unicopia/network/datasync/Transmittable.java similarity index 58% rename from src/main/java/com/minelittlepony/unicopia/network/Transmittable.java rename to src/main/java/com/minelittlepony/unicopia/network/datasync/Transmittable.java index 73713e95..a45a23a2 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/Transmittable.java +++ b/src/main/java/com/minelittlepony/unicopia/network/datasync/Transmittable.java @@ -1,4 +1,4 @@ -package com.minelittlepony.unicopia.network; +package com.minelittlepony.unicopia.network.datasync; public interface Transmittable { void sendCapabilities(boolean full); diff --git a/src/main/java/com/minelittlepony/unicopia/projectile/MagicProjectileEntity.java b/src/main/java/com/minelittlepony/unicopia/projectile/MagicProjectileEntity.java index 027a8a03..487a8e62 100644 --- a/src/main/java/com/minelittlepony/unicopia/projectile/MagicProjectileEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/projectile/MagicProjectileEntity.java @@ -16,8 +16,8 @@ import com.minelittlepony.unicopia.entity.Physics; import com.minelittlepony.unicopia.entity.UEntities; import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.network.Channel; -import com.minelittlepony.unicopia.network.EffectSync; import com.minelittlepony.unicopia.network.MsgSpawnProjectile; +import com.minelittlepony.unicopia.network.datasync.EffectSync; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; @@ -220,7 +220,7 @@ public class MagicProjectileEntity extends ThrownItemEntity implements Caster