From 88cf90842f77ef4b1e3acfafcbca53a47199db25 Mon Sep 17 00:00:00 2001 From: Sollace Date: Tue, 21 May 2024 23:33:40 +0100 Subject: [PATCH] Sync each spell individually --- .../ability/UnicornDispellAbility.java | 7 +- .../unicopia/ability/magic/Caster.java | 2 +- .../ability/magic/MultiSpellSlot.java | 131 ++++++++++++++ .../ability/magic/SingleSpellSlot.java | 68 +++++++ .../ability/magic/SpellInventory.java | 75 ++++++++ .../{SpellContainer.java => SpellSlots.java} | 102 ++++++----- .../magic/spell/AbstractDelegatingSpell.java | 89 ++++----- .../ability/magic/spell/PlaceableSpell.java | 43 ++--- .../unicopia/ability/magic/spell/Spell.java | 7 +- .../ability/magic/spell/SpellReference.java | 15 ++ .../ability/magic/spell/ThrowableSpell.java | 53 +++--- .../client/gui/DismissSpellScreen.java | 5 +- .../render/spell/PlacedSpellRenderer.java | 3 +- .../spell/SpellEffectsRenderDispatcher.java | 4 +- .../unicopia/entity/Creature.java | 2 +- .../unicopia/entity/EntityPhysics.java | 6 +- .../unicopia/entity/ItemImpl.java | 2 +- .../unicopia/entity/ItemPhysics.java | 6 +- .../unicopia/entity/Living.java | 18 +- .../unicopia/entity/mob/CastSpellEntity.java | 24 +-- .../unicopia/entity/player/PlayerPhysics.java | 2 +- .../unicopia/entity/player/Pony.java | 13 +- .../unicopia/mixin/MixinMilkBucketItem.java | 4 +- .../unicopia/network/datasync/EffectSync.java | 170 ------------------ .../network/datasync/NetworkedReference.java | 19 -- .../datasync/NetworkedReferenceSet.java | 153 ---------------- .../datasync/SpellNetworkedReference.java | 48 ----- .../unicopia/network/track/DataTracker.java | 90 +++++----- .../network/track/DataTrackerManager.java | 68 ++++--- .../network/track/MsgTrackedValues.java | 35 +++- .../unicopia/network/track/ObjectTracker.java | 144 +++++++++++++++ .../network/track/TrackableObject.java | 24 +++ .../unicopia/projectile/MagicBeamEntity.java | 29 ++- 33 files changed, 762 insertions(+), 699 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/ability/magic/MultiSpellSlot.java create mode 100644 src/main/java/com/minelittlepony/unicopia/ability/magic/SingleSpellSlot.java create mode 100644 src/main/java/com/minelittlepony/unicopia/ability/magic/SpellInventory.java rename src/main/java/com/minelittlepony/unicopia/ability/magic/{SpellContainer.java => SpellSlots.java} (56%) delete mode 100644 src/main/java/com/minelittlepony/unicopia/network/datasync/EffectSync.java delete mode 100644 src/main/java/com/minelittlepony/unicopia/network/datasync/NetworkedReference.java delete mode 100644 src/main/java/com/minelittlepony/unicopia/network/datasync/NetworkedReferenceSet.java delete mode 100644 src/main/java/com/minelittlepony/unicopia/network/datasync/SpellNetworkedReference.java create mode 100644 src/main/java/com/minelittlepony/unicopia/network/track/ObjectTracker.java create mode 100644 src/main/java/com/minelittlepony/unicopia/network/track/TrackableObject.java diff --git a/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java index 61f4a3e8..36c603c2 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java @@ -7,7 +7,6 @@ import com.minelittlepony.unicopia.InteractionManager; import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.ability.data.Pos; import com.minelittlepony.unicopia.ability.magic.Caster; -import com.minelittlepony.unicopia.ability.magic.SpellContainer.Operation; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; import com.minelittlepony.unicopia.entity.player.Pony; @@ -93,11 +92,7 @@ public class UnicornDispellAbility implements Ability { public boolean apply(Pony player, Pos data) { player.setAnimation(Animation.WOLOLO, Animation.Recipient.ANYONE); Caster.stream(VecHelper.findInRange(player.asEntity(), player.asWorld(), data.vec(), 3, EquinePredicates.IS_PLACED_SPELL).stream()).forEach(target -> { - target.getSpellSlot().forEach(spell -> { - spell.setDead(); - spell.tickDying(target); - return Operation.ofBoolean(!spell.isDead()); - }); + target.getSpellSlot().clear(false); }); return true; } 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 dc6f4ec3..86c225f1 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java @@ -40,7 +40,7 @@ public interface Caster extends Physics getPhysics(); - SpellContainer getSpellSlot(); + SpellSlots getSpellSlot(); /** * Removes the desired amount of mana or health from this caster in exchange for a spell's benefits. diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/MultiSpellSlot.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/MultiSpellSlot.java new file mode 100644 index 00000000..c9763388 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/MultiSpellSlot.java @@ -0,0 +1,131 @@ +package com.minelittlepony.unicopia.ability.magic; + +import java.util.UUID; +import java.util.stream.Stream; + +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.ability.magic.spell.Spell; +import com.minelittlepony.unicopia.ability.magic.spell.SpellReference; +import com.minelittlepony.unicopia.network.track.ObjectTracker; +import com.minelittlepony.unicopia.network.track.Trackable; +import com.minelittlepony.unicopia.network.track.TrackableObject; +import com.minelittlepony.unicopia.util.NbtSerialisable; +import net.minecraft.nbt.NbtCompound; + +/** + * Container for multiple spells + * + * @param The owning entity + */ +class MultiSpellSlot implements SpellSlots, NbtSerialisable { + private final Caster owner; + private final ObjectTracker> tracker; + + public MultiSpellSlot(Caster owner) { + this.owner = owner; + this.tracker = Trackable.of(owner.asEntity()).getDataTrackers().checkoutTracker(() -> new Entry<>(owner)); + } + + public ObjectTracker getTracker() { + return tracker; + } + + @Override + public boolean contains(UUID id) { + return tracker.contains(id) + || tracker.values().stream().anyMatch(s -> s.spell.equalsOrContains(id)); + } + + @Override + public void put(@Nullable Spell effect) { + if (effect != null) { + tracker.add(new Entry<>(owner, effect)); + } + } + + @Override + public void remove(UUID id, boolean force) { + tracker.remove(id, force); + } + + @Override + public Stream stream(@Nullable SpellPredicate type) { + return tracker.values().stream().flatMap(s -> s.spell.findMatches(type)); + } + + @Override + public boolean clear(boolean force) { + return tracker.clear(force); + } + + @Override + public void toNBT(NbtCompound compound) { + compound.put("spells", tracker.toNBT()); + } + + @Override + public void fromNBT(NbtCompound compound) { + tracker.fromNBT(compound.getCompound("spells")); + } + + static final class Entry implements TrackableObject { + private final Caster owner; + final SpellReference spell = new SpellReference<>(); + private Status status = Status.NEW; + + public Entry(Caster owner) { + this.owner = owner; + } + public Entry(Caster owner, T spell) { + this.owner = owner; + this.spell.set(spell); + if (owner instanceof UpdateCallback callback) { + callback.onSpellAdded(spell); + } + } + + @Override + public void discard(boolean immediate) { + if (immediate) { + spell.set(null, owner); + } else { + Spell s = spell.get(); + if (s != null) { + s.setDead(); + s.tickDying(owner); + } + } + } + + @Override + public UUID getUuid() { + return spell.get().getUuid(); + } + + @Override + public Status getStatus() { + try { + if (spell.get() == null) { + return Status.REMOVED; + } + if (spell.hasDirtySpell()) { + return Status.UPDATED; + } + return status; + } finally { + status = Status.DEFAULT; + } + } + + @Override + public NbtCompound toTrackedNbt() { + return spell.toNBT(); + } + + @Override + public void readTrackedNbt(NbtCompound compound) { + spell.fromNBT(compound); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/SingleSpellSlot.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/SingleSpellSlot.java new file mode 100644 index 00000000..b1ba5633 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/SingleSpellSlot.java @@ -0,0 +1,68 @@ +package com.minelittlepony.unicopia.ability.magic; + +import java.util.UUID; +import java.util.stream.Stream; + +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.ability.magic.spell.Spell; +import com.minelittlepony.unicopia.network.track.Trackable; +import com.minelittlepony.unicopia.util.NbtSerialisable; +import net.minecraft.nbt.NbtCompound; + +/** + * Container for a single spell + * + * @param The owning entity + */ +class SingleSpellSlot implements SpellSlots, NbtSerialisable { + private final Caster owner; + private final MultiSpellSlot.Entry entry; + + public SingleSpellSlot(Caster owner) { + this.owner = owner; + this.entry = new MultiSpellSlot.Entry<>(owner); + Trackable.of(owner.asEntity()).getDataTrackers().getPrimaryTracker().startTracking(entry); + } + + @Override + public boolean contains(UUID id) { + return entry.spell.equalsOrContains(id); + } + + @Override + public void put(@Nullable Spell effect) { + entry.spell.set(effect, owner); + } + + @Override + public void remove(UUID id, boolean force) { + if (contains(id)) { + entry.discard(force); + } + } + + @Override + public Stream stream(@Nullable SpellPredicate type) { + return entry.spell.findMatches(type); + } + + @Override + public boolean clear(boolean force) { + if (entry.spell.get() != null) { + entry.discard(force); + return true; + } + return false; + } + + @Override + public void toNBT(NbtCompound compound) { + compound.put("effect", entry.toTrackedNbt()); + } + + @Override + public void fromNBT(NbtCompound compound) { + entry.readTrackedNbt(compound.getCompound("effect")); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellInventory.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellInventory.java new file mode 100644 index 00000000..c5fdfc19 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellInventory.java @@ -0,0 +1,75 @@ +package com.minelittlepony.unicopia.ability.magic; + +import java.util.function.Function; + +import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.ability.magic.spell.Situation; +import com.minelittlepony.unicopia.ability.magic.spell.Spell; + +public class SpellInventory { + + private final Caster owner; + private final SpellSlots slots; + + public SpellInventory(Caster owner, SpellSlots slots) { + this.owner = owner; + this.slots = slots; + } + + public SpellSlots getSlots() { + return slots; + } + + public boolean tick(Situation situation) { + return tick(spell -> { + if (spell.isDying()) { + spell.tickDying(owner); + return Operation.ofBoolean(!spell.isDead()); + } + return Operation.ofBoolean(spell.tick(owner, situation)); + }); + } + + public boolean tick(Function tickAction) { + try { + return forEach(spell -> { + try { + return tickAction.apply(spell); + } catch (Throwable t) { + Unicopia.LOGGER.error("Error whilst ticking spell on entity {}", owner, t); + } + return Operation.REMOVE; + }); + } catch (Exception e) { + Unicopia.LOGGER.error("Error whilst ticking spell on entity {}", owner.asEntity(), e); + } + return false; + } + + /** + * Iterates active spells and optionally removes matching ones. + * + * @return True if any matching spells remain active + */ + public boolean forEach(Function test) { + return slots.reduce((initial, spell) -> { + Operation op = test.apply(spell); + if (op == Operation.REMOVE) { + slots.remove(spell.getUuid(), true); + } else { + initial |= op != Operation.SKIP; + } + return initial; + }); + } + + public enum Operation { + SKIP, + KEEP, + REMOVE; + + public static Operation ofBoolean(boolean result) { + return result ? KEEP : REMOVE; + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellContainer.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellSlots.java similarity index 56% rename from src/main/java/com/minelittlepony/unicopia/ability/magic/SpellContainer.java rename to src/main/java/com/minelittlepony/unicopia/ability/magic/SpellSlots.java index bfb00b07..97d30882 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellContainer.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellSlots.java @@ -2,19 +2,56 @@ package com.minelittlepony.unicopia.ability.magic; import java.util.Optional; import java.util.UUID; -import java.util.function.Function; -import java.util.function.Predicate; +import java.util.function.BiFunction; import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.ability.magic.spell.Spell; +import com.minelittlepony.unicopia.util.NbtSerialisable; + +public interface SpellSlots extends NbtSerialisable { + static SpellInventory ofUnbounded(Caster caster) { + return new SpellInventory(caster, new MultiSpellSlot(caster)); + } + + static SpellInventory ofSingle(Caster caster) { + return new SpellInventory(caster, new SingleSpellSlot(caster)); + } + + /** + * Gets all active effects for this caster that match the given type. + */ + Stream stream(@Nullable SpellPredicate type); + + /** + * Sets the active effect. + */ + void put(@Nullable Spell effect); + + /** + * Removes all effects currently active in this slot. + */ + boolean clear(boolean force); + + /** + * Cleanly removes a spell from this spell container. + * + * @param spellid ID of the spell to remove. + */ + void remove(UUID spellid, boolean force); -public interface SpellContainer { /** * Checks if a spell with the given uuid is present. */ boolean contains(UUID id); + /** + * Gets the active effect for this caster + */ + default Optional get() { + return get(null); + } + /** * Checks if any matching spells are active. */ @@ -22,13 +59,6 @@ public interface SpellContainer { return get(type).isPresent(); } - /** - * Gets the active effect for this caster updating it if needed. - */ - default Optional get() { - return get(null); - } - /** * Gets the active effect for this caster updating it if needed. */ @@ -37,23 +67,27 @@ public interface SpellContainer { } /** - * Sets the active effect. + * Removes all effects currently active in this slot. */ - void put(@Nullable Spell effect); + default boolean clear() { + return clear(false); + } /** * Cleanly removes a spell from this spell container. * * @param spellid ID of the spell to remove. */ - void remove(UUID spellid); + default void remove(UUID spellid) { + remove(spellid, false); + } /** * Removes all active effects that match or contain a matching effect. * * @return True if the collection was changed */ - default boolean removeIf(Predicate test) { + default boolean removeIf(SpellPredicate test) { return removeWhere(spell -> spell.findMatches(test).findFirst().isPresent()); } @@ -62,15 +96,15 @@ public interface SpellContainer { * * @return True if the collection was changed */ - boolean removeWhere(Predicate test); - - /** - * Iterates active spells and optionally removes matching ones. - * - * @return True if any matching spells remain active - */ - boolean forEach(Function action); - + default boolean removeWhere(SpellPredicate test) { + return reduce((initial, spell) -> { + if (test.test(spell)) { + remove(spell.getUuid()); + return true; + } + return initial; + }); + } /** * Gets all active effects for this caster updating it if needed. @@ -79,23 +113,11 @@ public interface SpellContainer { return stream(null); } - /** - * Gets all active effects for this caster that match the given type. - */ - Stream stream(@Nullable SpellPredicate type); + default boolean reduce(BiFunction alteration) { + return stream().reduce(false, alteration, (a, b) -> b); + } - /** - * Removes all effects currently active in this slot. - */ - boolean clear(); - - public enum Operation { - SKIP, - KEEP, - REMOVE; - - public static Operation ofBoolean(boolean result) { - return result ? KEEP : REMOVE; - } + public interface UpdateCallback { + void onSpellAdded(Spell spell); } } 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 c27fb13c..70cbef36 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 @@ -1,22 +1,14 @@ package com.minelittlepony.unicopia.ability.magic.spell; import java.util.*; -import java.util.function.Function; -import java.util.function.Predicate; import java.util.stream.Stream; import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; -import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; -import com.minelittlepony.unicopia.projectile.ProjectileDelegate; - import net.minecraft.nbt.NbtCompound; -import net.minecraft.util.hit.BlockHitResult; -import net.minecraft.util.hit.EntityHitResult; - -public abstract class AbstractDelegatingSpell implements Spell, - ProjectileDelegate.ConfigurationListener, ProjectileDelegate.BlockHitListener, ProjectileDelegate.EntityHitListener { +public abstract class AbstractDelegatingSpell implements Spell { private boolean dirty; private boolean hidden; private boolean destroyed; @@ -24,21 +16,24 @@ public abstract class AbstractDelegatingSpell implements Spell, private UUID uuid = UUID.randomUUID(); private final CustomisedSpellType type; + protected final SpellReference delegate = new SpellReference<>(); public AbstractDelegatingSpell(CustomisedSpellType type) { this.type = type; } - public abstract Collection getDelegates(); - - @Override - public boolean equalsOrContains(UUID id) { - return Spell.super.equalsOrContains(id) || getDelegates().stream().anyMatch(s -> s.equalsOrContains(id)); + public final Spell getDelegate() { + return delegate.get(); } @Override - public Stream findMatches(Predicate predicate) { - return Stream.concat(Spell.super.findMatches(predicate), getDelegates().stream().flatMap(s -> s.findMatches(predicate))); + public boolean equalsOrContains(UUID id) { + return Spell.super.equalsOrContains(id) || delegate.equalsOrContains(id); + } + + @Override + public Stream findMatches(SpellPredicate predicate) { + return Stream.concat(Spell.super.findMatches(predicate), delegate.findMatches(predicate)); } @Override @@ -53,7 +48,10 @@ public abstract class AbstractDelegatingSpell implements Spell, @Override public void setDead() { - getDelegates().forEach(Spell::setDead); + Spell spell = delegate.get(); + if (spell != null) { + spell.setDead(); + } } @Override @@ -62,7 +60,7 @@ public abstract class AbstractDelegatingSpell implements Spell, @Override public boolean isDead() { - return getDelegates().isEmpty() || getDelegates().stream().allMatch(Spell::isDead); + return delegate.get() == null || delegate.get().isDead(); } @Override @@ -72,7 +70,7 @@ public abstract class AbstractDelegatingSpell implements Spell, @Override public boolean isDirty() { - return dirty || getDelegates().stream().anyMatch(Spell::isDirty); + return dirty || (delegate.get() instanceof Spell p && p.isDirty()); } @Override @@ -82,7 +80,7 @@ public abstract class AbstractDelegatingSpell implements Spell, @Override public boolean isHidden() { - return hidden || getDelegates().stream().allMatch(Spell::isHidden); + return hidden || (delegate.get() instanceof Spell p && p.isHidden()); } @Override @@ -101,40 +99,28 @@ public abstract class AbstractDelegatingSpell implements Spell, } protected void onDestroyed(Caster caster) { - getDelegates().forEach(a -> a.destroy(caster)); + if (delegate.get() instanceof Spell s) { + s.destroy(caster); + } } @Override public boolean tick(Caster source, Situation situation) { - return execute(getDelegates().stream(), a -> { - if (a.isDying()) { - a.tickDying(source); - return !a.isDead(); + if (delegate.get() instanceof Spell s) { + if (s.isDying()) { + s.tickDying(source); + return !s.isDead(); } - return a.tick(source, situation); - }); - } - - @Override - public void onImpact(MagicProjectileEntity projectile, BlockHitResult hit) { - getDelegates(BlockHitListener.PREDICATE).forEach(a -> a.onImpact(projectile, hit)); - } - - @Override - public void onImpact(MagicProjectileEntity projectile, EntityHitResult hit) { - getDelegates(EntityHitListener.PREDICATE).forEach(a -> a.onImpact(projectile, hit)); - } - - @Override - public void configureProjectile(MagicProjectileEntity projectile, Caster caster) { - getDelegates(ConfigurationListener.PREDICATE).forEach(a -> a.configureProjectile(projectile, caster)); + return s.tick(source, situation) && !isDead(); + } + return !isDead(); } @Override public void toNBT(NbtCompound compound) { compound.putUuid("uuid", uuid); compound.putBoolean("hidden", hidden); - saveDelegates(compound); + compound.put("spell", delegate.toNBT()); } @Override @@ -144,18 +130,11 @@ public abstract class AbstractDelegatingSpell implements Spell, if (compound.contains("uuid")) { uuid = compound.getUuid("uuid"); } - loadDelegates(compound); + delegate.fromNBT(compound.getCompound("spell")); } - protected abstract void loadDelegates(NbtCompound compound); - - protected abstract void saveDelegates(NbtCompound compound); - - protected Stream getDelegates(Function cast) { - return getDelegates().stream().map(cast).filter(Objects::nonNull); - } - - protected static boolean execute(Stream spells, Function action) { - return spells.reduce(false, (u, a) -> action.apply(a), (a, b) -> a || b); + @Override + public final String toString() { + return "Delegate{" + getTypeAndTraits() + "}[uuid=" + uuid + ", destroyed=" + destroyed + ", hidden=" + hidden + "][spell=" + delegate.get() + "]"; } } 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 ab724eaa..652009c1 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 @@ -29,6 +29,11 @@ import net.minecraft.world.World; *

* The spell's effects are still powered by the casting player, so if the player dies or leaves the area, their * spell loses affect until they return. + *

+ * When cast two copies of this spell are created. One is attached to the player and is the controlling spell, + * the other is attached to a cast spell entity and placed in the world. + * + * TODO: Split this up into separate classes. */ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedSpell { /** @@ -48,11 +53,6 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS */ private final EntityReference castEntity = new EntityReference<>(); - /** - * The spell being cast - */ - private final SpellReference spell = new SpellReference<>(); - public float pitch; public float yaw; @@ -70,7 +70,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS } public PlaceableSpell setSpell(Spell spell) { - this.spell.set(spell); + delegate.set(spell); return this; } @@ -101,14 +101,9 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS return dead && deathTicks <= 0 && super.isDead(); } - @Override - public Collection getDelegates() { - Spell spell = this.spell.get(); - return spell == null ? List.of() : List.of(spell); - } - @Override public boolean tick(Caster source, Situation situation) { + System.out.println("Placed Tick: " + source + " " + source.isClient() + " " + situation); if (situation == Situation.BODY) { if (!source.isClient()) { if (dimension == null) { @@ -163,8 +158,8 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS CastSpellEntity entity = UEntities.CAST_SPELL.create(source.asWorld()); Vec3d pos = getPosition().orElse(position.orElse(source.asEntity().getPos())); entity.updatePositionAndAngles(pos.x, pos.y, pos.z, source.asEntity().getYaw(), source.asEntity().getPitch()); - PlaceableSpell copy = spell.get().toPlaceable(); - if (spell.get() instanceof PlacementDelegate delegate) { + PlaceableSpell copy = delegate.get().toPlaceable(); + if (delegate.get() instanceof PlacementDelegate delegate) { delegate.onPlaced(source, copy, entity); } entity.getSpellSlot().put(copy); @@ -181,8 +176,9 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS public void setOrientation(float pitch, float yaw) { this.pitch = -90 - pitch; this.yaw = -yaw; - getDelegates(spell -> spell instanceof OrientedSpell o ? o : null) - .forEach(spell -> spell.setOrientation(pitch, yaw)); + if (delegate.get() instanceof OrientedSpell o) { + o.setOrientation(pitch, yaw); + } setDirty(); } @@ -192,8 +188,9 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS castEntity.ifPresent(source.asWorld(), entity -> { entity.updatePositionAndAngles(position.x, position.y, position.z, entity.getYaw(), entity.getPitch()); }); - getDelegates(spell -> spell instanceof PlaceableSpell o ? o : null) - .forEach(spell -> spell.setPosition(source, position)); + if (delegate.get() instanceof PlaceableSpell o) { + o.setPosition(source, position); + } setDirty(); } @@ -271,16 +268,6 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS } } - @Override - protected void loadDelegates(NbtCompound compound) { - spell.fromNBT(compound.getCompound("spell")); - } - - @Override - protected void saveDelegates(NbtCompound compound) { - compound.put("spell", spell.toNBT()); - } - @Override public PlaceableSpell toPlaceable() { return this; diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/Spell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/Spell.java index 30a93dd2..4258b079 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/Spell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/Spell.java @@ -1,7 +1,6 @@ package com.minelittlepony.unicopia.ability.magic.spell; import java.util.UUID; -import java.util.function.Predicate; import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; @@ -11,6 +10,7 @@ import com.minelittlepony.unicopia.Affinity; import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.ability.magic.Affine; import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.util.NbtSerialisable; @@ -53,8 +53,9 @@ public interface Spell extends NbtSerialisable, Affine { /** * Returns an optional containing the spell that matched the given predicate. */ - default Stream findMatches(Predicate predicate) { - return predicate.test(this) ? Stream.of(this) : Stream.empty(); + @SuppressWarnings("unchecked") + default Stream findMatches(SpellPredicate predicate) { + return predicate == null || predicate.test(this) ? Stream.of((T)this) : Stream.empty(); } /** diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/SpellReference.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/SpellReference.java index c222de90..6c3d22e9 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/SpellReference.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/SpellReference.java @@ -1,9 +1,13 @@ package com.minelittlepony.unicopia.ability.magic.spell; import java.util.Objects; +import java.util.UUID; +import java.util.stream.Stream; + import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.util.NbtSerialisable; import net.minecraft.nbt.NbtCompound; @@ -40,6 +44,17 @@ public final class SpellReference implements NbtSerialisable { return true; } + public boolean equalsOrContains(UUID id) { + @Nullable T spell = get(); + return spell != null && spell.equalsOrContains(id); + } + + @SuppressWarnings("unchecked") + public Stream findMatches(SpellPredicate predicate) { + @Nullable T spell = get(); + return spell != null ? (predicate == null ? Stream.of((V)spell) : spell.findMatches(predicate)) : Stream.empty(); + } + @Override public void toNBT(NbtCompound compound) { if (spell != null && !spell.isDead()) { 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 3e60cba0..b823cf6f 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 @@ -1,34 +1,30 @@ package com.minelittlepony.unicopia.ability.magic.spell; -import java.util.Collection; -import java.util.List; import java.util.Optional; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.projectile.MagicBeamEntity; -import net.minecraft.nbt.NbtCompound; +import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; +import com.minelittlepony.unicopia.projectile.ProjectileDelegate; + +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.EntityHitResult; import net.minecraft.world.World; -public final class ThrowableSpell extends AbstractDelegatingSpell { - - private final SpellReference spell = new SpellReference<>(); +public final class ThrowableSpell extends AbstractDelegatingSpell implements + ProjectileDelegate.ConfigurationListener, ProjectileDelegate.BlockHitListener, ProjectileDelegate.EntityHitListener { public ThrowableSpell(CustomisedSpellType type) { super(type); } public ThrowableSpell setSpell(Spell spell) { - this.spell.set(spell); + delegate.set(spell); return this; } - @Override - public Collection getDelegates() { - return List.of(spell.get()); - } - @Override public boolean apply(Caster source) { return throwProjectile(source).isPresent(); @@ -57,7 +53,7 @@ public final class ThrowableSpell extends AbstractDelegatingSpell { return Optional.empty(); } - return Optional.ofNullable(spell.get().prepareForCast(caster, CastingMethod.STORED)).map(s -> { + return Optional.ofNullable(delegate.get().prepareForCast(caster, CastingMethod.STORED)).map(s -> { MagicBeamEntity projectile = new MagicBeamEntity(world, caster.asEntity(), divergance, s); configureProjectile(projectile, caster); @@ -67,16 +63,6 @@ public final class ThrowableSpell extends AbstractDelegatingSpell { }); } - @Override - protected void loadDelegates(NbtCompound compound) { - spell.fromNBT(compound.getCompound("spell")); - } - - @Override - protected void saveDelegates(NbtCompound compound) { - compound.put("spell", spell.toNBT()); - } - @Override public ThrowableSpell toThrowable() { return this; @@ -90,4 +76,25 @@ public final class ThrowableSpell extends AbstractDelegatingSpell { @Override public void setHidden(boolean hidden) { } + + @Override + public void onImpact(MagicProjectileEntity projectile, BlockHitResult hit) { + if (delegate.get() instanceof BlockHitListener listener) { + listener.onImpact(projectile, hit); + } + } + + @Override + public void onImpact(MagicProjectileEntity projectile, EntityHitResult hit) { + if (delegate.get() instanceof EntityHitListener listener) { + listener.onImpact(projectile, hit); + } + } + + @Override + public void configureProjectile(MagicProjectileEntity projectile, Caster caster) { + if (delegate.get() instanceof ConfigurationListener listener) { + listener.configureProjectile(projectile, caster); + } + } } 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 4910981f..85028253 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/DismissSpellScreen.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/DismissSpellScreen.java @@ -137,10 +137,7 @@ public class DismissSpellScreen extends GameGui { } private Spell getActualSpell() { - if (spell instanceof AbstractDelegatingSpell) { - return ((AbstractDelegatingSpell)spell).getDelegates().stream().findFirst().orElse(spell); - } - return spell; + return spell instanceof AbstractDelegatingSpell s && s.getDelegate() instanceof Spell p ? p : spell; } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/PlacedSpellRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/PlacedSpellRenderer.java index ee08654d..f0676140 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/spell/PlacedSpellRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/PlacedSpellRenderer.java @@ -34,8 +34,9 @@ public class PlacedSpellRenderer extends SpellRenderer { matrices.push(); matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(-castSpell.getYaw())); + Spell delegate = spell.getDelegate(); - for (Spell delegate : spell.getDelegates()) { + if (delegate != null) { renderAmbientEffects(matrices, vertices, spell, delegate, caster, light, animationProgress, tickDelta); matrices.push(); diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellEffectsRenderDispatcher.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellEffectsRenderDispatcher.java index b2bcf87a..423a8888 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellEffectsRenderDispatcher.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellEffectsRenderDispatcher.java @@ -10,7 +10,6 @@ import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.ability.magic.Caster; -import com.minelittlepony.unicopia.ability.magic.SpellContainer.Operation; import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; @@ -87,9 +86,8 @@ public class SpellEffectsRenderDispatcher implements SynchronousResourceReloader return; } - caster.getSpellSlot().forEach(spell -> { + caster.getSpellSlot().stream().forEach(spell -> { render(matrices, vertices, spell, caster, light, limbAngle, limbDistance, tickDelta, animationProgress, headYaw, headPitch); - return Operation.SKIP; }); if (client.getEntityRenderDispatcher().shouldRenderHitboxes() diff --git a/src/main/java/com/minelittlepony/unicopia/entity/Creature.java b/src/main/java/com/minelittlepony/unicopia/entity/Creature.java index 810297d9..bd0929c6 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/Creature.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/Creature.java @@ -69,7 +69,7 @@ public class Creature extends Living implements WeaklyOwned.Mutabl public Creature(LivingEntity entity) { super(entity); - physics = new EntityPhysics<>(entity, tracker); + physics = new EntityPhysics<>(entity); addTicker(physics); addTicker(this::updateConsumption); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/EntityPhysics.java b/src/main/java/com/minelittlepony/unicopia/entity/EntityPhysics.java index 2aba94c8..b08ce4cd 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/EntityPhysics.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/EntityPhysics.java @@ -2,6 +2,7 @@ package com.minelittlepony.unicopia.entity; import com.minelittlepony.unicopia.entity.mob.UEntityAttributes; import com.minelittlepony.unicopia.network.track.DataTracker; +import com.minelittlepony.unicopia.network.track.Trackable; import com.minelittlepony.unicopia.network.track.TrackableDataType; import com.minelittlepony.unicopia.util.Copyable; import com.minelittlepony.unicopia.util.Tickable; @@ -27,10 +28,9 @@ public class EntityPhysics implements Physics, Copyable gravity; - public EntityPhysics(T entity, DataTracker tracker) { + public EntityPhysics(T entity) { this.entity = entity; - this.tracker = tracker; - //this.gravity = gravity; + this.tracker = Trackable.of(entity).getDataTrackers().getPrimaryTracker(); gravity = tracker.startTracking(TrackableDataType.of(PacketCodec.FLOAT), 1F); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/ItemImpl.java b/src/main/java/com/minelittlepony/unicopia/entity/ItemImpl.java index ae51c08c..a4c30f52 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/ItemImpl.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/ItemImpl.java @@ -42,7 +42,7 @@ public class ItemImpl implements Equine { this.entity = owner; this.trackers = Trackable.of(entity).getDataTrackers(); this.tracker = trackers.getPrimaryTracker(); - this.physics = new ItemPhysics(owner, tracker); + this.physics = new ItemPhysics(owner); race = tracker.startTracking(TrackableDataType.of(Race.PACKET_CODEC), Race.HUMAN); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/ItemPhysics.java b/src/main/java/com/minelittlepony/unicopia/entity/ItemPhysics.java index 04b5425d..77af67da 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/ItemPhysics.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/ItemPhysics.java @@ -1,12 +1,10 @@ package com.minelittlepony.unicopia.entity; -import com.minelittlepony.unicopia.network.track.DataTracker; - import net.minecraft.entity.ItemEntity; class ItemPhysics extends EntityPhysics { - public ItemPhysics(ItemEntity entity, DataTracker tracker) { - super(entity, tracker); + public ItemPhysics(ItemEntity entity) { + super(entity); } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/entity/Living.java b/src/main/java/com/minelittlepony/unicopia/entity/Living.java index d6f14bdb..029922b9 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/Living.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/Living.java @@ -12,7 +12,8 @@ import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.ability.Abilities; import com.minelittlepony.unicopia.ability.Ability; import com.minelittlepony.unicopia.ability.magic.Caster; -import com.minelittlepony.unicopia.ability.magic.SpellContainer; +import com.minelittlepony.unicopia.ability.magic.SpellInventory; +import com.minelittlepony.unicopia.ability.magic.SpellSlots; import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.spell.AbstractDisguiseSpell; import com.minelittlepony.unicopia.ability.magic.spell.Situation; @@ -30,7 +31,6 @@ import com.minelittlepony.unicopia.input.Interactable; import com.minelittlepony.unicopia.item.GlassesItem; import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.item.enchantment.UEnchantments; -import com.minelittlepony.unicopia.network.datasync.EffectSync; import com.minelittlepony.unicopia.network.datasync.Transmittable; import com.minelittlepony.unicopia.network.track.DataTracker; import com.minelittlepony.unicopia.network.track.DataTrackerManager; @@ -78,7 +78,7 @@ import net.minecraft.util.math.Vec3d; public abstract class Living implements Equine, Caster, Transmittable { protected final T entity; - private final EffectSync effectDelegate; + private final SpellInventory spells; private final Interactable sneakingHeuristic; private final Interactable landedHeuristic; @@ -111,7 +111,7 @@ public abstract class Living implements Equine, Caste this.entity = entity; this.trackers = Trackable.of(entity).getDataTrackers(); this.tracker = trackers.getPrimaryTracker(); - this.effectDelegate = new EffectSync(this, tracker); + this.spells = SpellSlots.ofUnbounded(this); this.sneakingHeuristic = addTicker(new Interactable(entity::isSneaking)); this.landedHeuristic = addTicker(new Interactable(entity::isOnGround)); this.jumpingHeuristic = addTicker(new Interactable(((LivingEntityDuck)entity)::isJumping)); @@ -149,8 +149,8 @@ public abstract class Living implements Equine, Caste } @Override - public SpellContainer getSpellSlot() { - return effectDelegate; + public SpellSlots getSpellSlot() { + return spells.getSlots(); } public Enchantments getEnchants() { @@ -216,7 +216,7 @@ public abstract class Living implements Equine, Caste @Override public void tick() { tickers.forEach(Tickable::tick); - effectDelegate.tick(Situation.BODY); + spells.tick(Situation.BODY); if (!(entity instanceof PlayerEntity)) { if (!entity.hasVehicle() && getCarrierId().isPresent() && !asWorld().isClient && entity.age % 10 == 0) { @@ -486,14 +486,14 @@ public abstract class Living implements Equine, Caste @Override public void toNBT(NbtCompound compound) { enchants.toNBT(compound); - effectDelegate.toNBT(compound); + spells.getSlots().toNBT(compound); toSyncronisedNbt(compound); } @Override public void fromNBT(NbtCompound compound) { enchants.fromNBT(compound); - effectDelegate.fromNBT(compound); + spells.getSlots().fromNBT(compound); fromSynchronizedNbt(compound); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/CastSpellEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/CastSpellEntity.java index ea3896c8..b5ec2dd4 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/mob/CastSpellEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/CastSpellEntity.java @@ -3,7 +3,8 @@ package com.minelittlepony.unicopia.entity.mob; import com.minelittlepony.unicopia.*; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Levelled; -import com.minelittlepony.unicopia.ability.magic.SpellContainer; +import com.minelittlepony.unicopia.ability.magic.SpellInventory; +import com.minelittlepony.unicopia.ability.magic.SpellSlots; import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; @@ -11,9 +12,6 @@ import com.minelittlepony.unicopia.entity.EntityPhysics; import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.MagicImmune; import com.minelittlepony.unicopia.entity.Physics; -import com.minelittlepony.unicopia.network.datasync.EffectSync; -import com.minelittlepony.unicopia.network.track.Trackable; - import net.minecraft.entity.Entity; import net.minecraft.entity.EntityDimensions; import net.minecraft.entity.EntityPose; @@ -25,9 +23,9 @@ import net.minecraft.world.World; public class CastSpellEntity extends LightEmittingEntity implements Caster, WeaklyOwned.Mutable, MagicImmune { - private final EntityPhysics physics = new EntityPhysics<>(this, Trackable.of(this).getDataTrackers().getPrimaryTracker()); + private final EntityPhysics physics = new EntityPhysics<>(this); - private final EffectSync effectDelegate = new EffectSync(this, Trackable.of(this).getDataTrackers().getPrimaryTracker()); + private final SpellInventory spells = SpellSlots.ofSingle(this); private final EntityReference owner = new EntityReference<>(); @@ -65,7 +63,7 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster { - tag.put("effect", Spell.writeNbt(effect)); - }); + spells.getSlots().toNBT(tag); } @Override @@ -147,9 +143,7 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster implements Tickab private Lerp windStrength = new Lerp(0); public PlayerPhysics(Pony pony, DataTracker tracker) { - super(pony.asEntity(), tracker); + super(pony.asEntity()); this.pony = pony; dimensions = new PlayerDimensions(pony, this); } 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 2527bf47..b819834e 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java @@ -11,6 +11,7 @@ import com.minelittlepony.unicopia.client.render.PlayerPoser.AnimationInstance; import com.minelittlepony.unicopia.*; import com.minelittlepony.unicopia.ability.*; import com.minelittlepony.unicopia.ability.magic.*; +import com.minelittlepony.unicopia.ability.magic.SpellSlots.UpdateCallback; import com.minelittlepony.unicopia.ability.magic.spell.AbstractDisguiseSpell; import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; import com.minelittlepony.unicopia.ability.magic.spell.RageAbilitySpell; @@ -33,7 +34,6 @@ import com.minelittlepony.unicopia.item.enchantment.EnchantmentUtil; import com.minelittlepony.unicopia.item.enchantment.UEnchantments; import com.minelittlepony.unicopia.util.*; import com.minelittlepony.unicopia.network.*; -import com.minelittlepony.unicopia.network.datasync.EffectSync.UpdateCallback; import com.minelittlepony.unicopia.network.track.DataTracker; import com.minelittlepony.unicopia.network.track.TrackableDataType; import com.minelittlepony.unicopia.server.world.UGameRules; @@ -929,14 +929,11 @@ public class Pony extends Living implements Copyable, Update } @Override - public void onSpellSet(@Nullable Spell spell) { - if (spell != null) { - if (spell.getAffinity() == Affinity.BAD && entity.getWorld().random.nextInt(20) == 0) { - getCorruption().add(entity.getRandom().nextBetween(1, 10)); - } - getCorruption().add(((int)spell.getTypeAndTraits().traits().getCorruption() * 10) + spell.getTypeAndTraits().type().getAffinity().getCorruption()); - setDirty(); + public void onSpellAdded(Spell spell) { + if (spell.getAffinity() == Affinity.BAD && entity.getWorld().random.nextInt(20) == 0) { + getCorruption().add(entity.getRandom().nextBetween(1, 10)); } + getCorruption().add(((int)spell.getTypeAndTraits().traits().getCorruption() * 10) + spell.getTypeAndTraits().type().getAffinity().getCorruption()); } public boolean isClientPlayer() { diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinMilkBucketItem.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinMilkBucketItem.java index 7c9bd778..9bf38549 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinMilkBucketItem.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinMilkBucketItem.java @@ -6,7 +6,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import com.minelittlepony.unicopia.ability.magic.Caster; -import com.minelittlepony.unicopia.ability.magic.SpellContainer; +import com.minelittlepony.unicopia.ability.magic.SpellSlots; import net.minecraft.entity.LivingEntity; import net.minecraft.item.Item; @@ -20,6 +20,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).map(Caster::getSpellSlot).ifPresent(SpellContainer::clear); + Caster.of(entity).map(Caster::getSpellSlot).ifPresent(SpellSlots::clear); } } diff --git a/src/main/java/com/minelittlepony/unicopia/network/datasync/EffectSync.java b/src/main/java/com/minelittlepony/unicopia/network/datasync/EffectSync.java deleted file mode 100644 index b2d8fb97..00000000 --- a/src/main/java/com/minelittlepony/unicopia/network/datasync/EffectSync.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.minelittlepony.unicopia.network.datasync; - -import java.util.UUID; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Stream; - -import org.jetbrains.annotations.Nullable; - -import com.minelittlepony.unicopia.Unicopia; -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.Situation; -import com.minelittlepony.unicopia.ability.magic.spell.Spell; -import com.minelittlepony.unicopia.network.track.DataTracker; -import com.minelittlepony.unicopia.network.track.TrackableDataType; -import com.minelittlepony.unicopia.util.NbtSerialisable; -import com.minelittlepony.unicopia.util.serialization.PacketCodec; - -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, NbtSerialisable { - - private final NetworkedReferenceSet spells; - - private final Caster owner; - - private final DataTracker tracker; - private final DataTracker.Entry param; - - public EffectSync(Caster owner, DataTracker tracker) { - this.owner = owner; - this.tracker = tracker; - this.param = tracker.startTracking(TrackableDataType.of(PacketCodec.NBT), new NbtCompound()); - spells = new NetworkedReferenceSet<>(Spell::getUuid, () -> new SpellNetworkedReference<>(owner)); - - tracker.onBeforeSend(param, () -> { - if (spells.isDirty()) { - tracker.set(param, spells.toNbt()); - } - }); - tracker.onReceive(param, nbt -> spells.fromNbt(nbt)); - } - - public boolean tick(Situation situation) { - return tick(spell -> { - if (spell.isDying()) { - spell.tickDying(owner); - return Operation.ofBoolean(!spell.isDead()); - } - return Operation.ofBoolean(spell.tick(owner, situation)); - }); - } - - public boolean tick(Function tickAction) { - try { - return forEach(spell -> { - try { - return tickAction.apply(spell); - } catch (Throwable t) { - Unicopia.LOGGER.error("Error whilst ticking spell on entity {}", owner, t); - } - return Operation.REMOVE; - }); - } catch (Exception e) { - Unicopia.LOGGER.error("Error whilst ticking spell on entity {}", owner.asEntity(), e); - } - return false; - } - - @Override - public boolean contains(UUID id) { - return spells.containsReference(id) || spells.getReferences().anyMatch(s -> s.equalsOrContains(id)); - } - - @Override - public void put(@Nullable Spell effect) { - spells.addReference(effect); - if (owner instanceof UpdateCallback callback) { - callback.onSpellSet(effect); - } - } - - @Override - public void remove(UUID id) { - discard(spells.getReference(id)); - } - - @Override - public boolean removeWhere(Predicate test) { - return reduce((initial, spell) -> { - if (!test.test(spell)) { - return initial; - } - discard(spell); - return true; - }); - } - - private void discard(Spell spell) { - if (spell != null) { - spell.setDead(); - spell.tickDying(owner); - if (spell.isDead()) { - spells.removeReference(spell); - } - } - } - - @Override - public boolean forEach(Function test) { - return reduce((initial, effect) -> { - Operation op = test.apply(effect); - if (op == Operation.REMOVE) { - spells.removeReference(effect); - } else { - initial |= op != Operation.SKIP; - } - return initial; - }); - } - - @SuppressWarnings("unchecked") - @Override - public Stream stream(@Nullable SpellPredicate type) { - if (type == null) { - return (Stream)spells.getReferences(); - } - return (Stream)spells.getReferences().flatMap(s -> s.findMatches(type)); - } - - @Override - public boolean clear() { - if (spells.clear()) { - if (owner instanceof UpdateCallback c) { - c.onSpellSet(null); - } - return true; - } - return false; - } - - private boolean reduce(BiFunction alteration) { - return stream().toList().stream().reduce(false, alteration, (a, b) -> b); - } - - @Override - public void toNBT(NbtCompound compound) { - compound.put("spells", spells.toNbt()); - } - - @Override - public void fromNBT(NbtCompound compound) { - spells.fromNbt(compound.getCompound("spells")); - tracker.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 deleted file mode 100644 index cb1a42d3..00000000 --- a/src/main/java/com/minelittlepony/unicopia/network/datasync/NetworkedReference.java +++ /dev/null @@ -1,19 +0,0 @@ -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(); - - void 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 deleted file mode 100644 index 90582e52..00000000 --- a/src/main/java/com/minelittlepony/unicopia/network/datasync/NetworkedReferenceSet.java +++ /dev/null @@ -1,153 +0,0 @@ -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.Objects; -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; - private boolean reading; - - public NetworkedReferenceSet(Function uuidConverter, Supplier> factory) { - this.uuidConverter = uuidConverter; - this.factory = factory; - } - - public synchronized boolean containsReference(UUID id) { - return ids.contains(id); - } - - public synchronized Stream getReferences() { - return ids.stream().map(id -> values.get(id)) - .filter(Objects::nonNull) - .map(a -> a.getReference()) - .filter(Optional::isPresent) - .map(Optional::get); - } - - public synchronized boolean clear() { - dirty |= !ids.isEmpty() || !values.isEmpty(); - ids.clear(); - try { - reading = true; - for (NetworkedReference reference : values.values()) { - reference.updateReference(null); - } - } finally { - reading = false; - values.clear(); - } - return dirty; - } - - public void addReference(@Nullable T newValue) { - if (newValue != null) { - addReference(uuidConverter.apply(newValue)).updateReference(newValue); - } - } - - private synchronized 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)); - } - } - - @Nullable - synchronized T getReference(UUID id) { - NetworkedReference i = values.get(id); - return i == null ? null : i.getReference().orElse(null); - } - - synchronized void removeReference(UUID id) { - dirty |= ids.remove(id); - NetworkedReference i = values.remove(id); - if (i != null) { - dirty = true; - try { - reading = true; - i.updateReference(null); - } finally { - reading = false; - } - } - } - - public boolean fromNbt(NbtCompound comp) { - if (reading) { - return false; - } - reading = true; - try { - 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)).toList().forEach(this::removeReference); - - boolean[] send = new boolean[1]; - incoming.forEach(key -> { - NetworkedReference i = addReference(key); - send[0] |= i.fromNbt(comp.getCompound(key.toString())); - if (i.getReference().isEmpty()) { - removeReference(key); - } - }); - dirty = send[0]; - return send[0]; - } finally { - reading = false; - } - } - - public synchronized NbtCompound toNbt() { - NbtCompound tag = new NbtCompound(); - NbtList ids = new NbtList(); - this.ids.forEach(id -> { - String sid = id.toString(); - NetworkedReference ref = values.get(id); - if (ref != null) { - ids.add(NbtString.of(sid)); - tag.put(sid, values.get(id).toNbt()); - } - }); - tag.put("keys", ids); - dirty = false; - return tag; - } - - public synchronized 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 deleted file mode 100644 index fbc8ba3b..00000000 --- a/src/main/java/com/minelittlepony/unicopia/network/datasync/SpellNetworkedReference.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.minelittlepony.unicopia.network.datasync; - -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.SpellReference; - -import net.minecraft.nbt.NbtCompound; - -public class SpellNetworkedReference implements NetworkedReference { - private final SpellReference currentValue = new SpellReference<>(); - private final Caster owner; - private boolean dirty; - - public SpellNetworkedReference(Caster owner) { - this.owner = owner; - } - - @Override - public Optional getReference() { - return Optional.ofNullable(currentValue.get()); - } - - @Override - public void updateReference(@Nullable T newValue) { - dirty |= currentValue.set(newValue, owner); - } - - @Override - public boolean fromNbt(NbtCompound comp) { - dirty = false; - currentValue.fromNBT(comp, owner.isClient()); - return isDirty(); - } - - @Override - public NbtCompound toNbt() { - dirty = false; - return currentValue.toNBT(); - } - - @Override - public boolean isDirty() { - return !owner.isClient() && (dirty || currentValue.hasDirtySpell()); - } -} diff --git a/src/main/java/com/minelittlepony/unicopia/network/track/DataTracker.java b/src/main/java/com/minelittlepony/unicopia/network/track/DataTracker.java index 7aa6dc99..1c86e164 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/track/DataTracker.java +++ b/src/main/java/com/minelittlepony/unicopia/network/track/DataTracker.java @@ -3,21 +3,22 @@ package com.minelittlepony.unicopia.network.track; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.function.Consumer; - import org.jetbrains.annotations.Nullable; +import com.minelittlepony.unicopia.util.serialization.PacketCodec; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.nbt.NbtCompound; import net.minecraft.network.PacketByteBuf; public class DataTracker { private final List> codecs = new ObjectArrayList<>(); - private final Int2ObjectOpenHashMap> loadCallbacks = new Int2ObjectOpenHashMap<>(); - private final Int2ObjectOpenHashMap writethroughCallback = new Int2ObjectOpenHashMap<>(); private IntSet dirtyIndices = new IntOpenHashSet(); + private Int2ObjectMap persistentObjects = new Int2ObjectOpenHashMap<>(); private final DataTrackerManager manager; private boolean initial = true; @@ -29,20 +30,18 @@ public class DataTracker { this.id = id; } + public Entry startTracking(T value) { + Entry entry = startTracking(TrackableDataType.of(PacketCodec.NBT), value.toTrackedNbt()); + persistentObjects.put(entry.id(), value); + return entry; + } + public Entry startTracking(TrackableDataType type, T initialValue) { Entry entry = new Entry<>(codecs.size()); codecs.add(new Pair<>(entry.id(), type, initialValue)); return entry; } - public void onReceive(Entry entry, Consumer loadCallback) { - loadCallbacks.put(entry.id(), loadCallback); - } - - public void onBeforeSend(Entry entry, Runnable action) { - writethroughCallback.put(entry.id(), action); - } - @SuppressWarnings("unchecked") private Pair getPair(Entry entry) { return (Pair)codecs.get(entry.id()); @@ -66,59 +65,58 @@ public class DataTracker { } } + @SuppressWarnings("unchecked") @Nullable - synchronized MsgTrackedValues.TrackerEntries getDirtyPairs() { - writethroughCallback.values().forEach(Runnable::run); + synchronized void getDirtyPairs(List output) { + for (var entry : persistentObjects.int2ObjectEntrySet()) { + int key = entry.getIntKey(); + TrackableObject.Status status = entry.getValue().getStatus(); + if (status == TrackableObject.Status.NEW || status == TrackableObject.Status.UPDATED) { + ((Pair)codecs.get(key)).value = entry.getValue().toTrackedNbt(); + dirtyIndices.add(key); + } + } if (initial) { initial = false; dirtyIndices = new IntOpenHashSet(); - return new MsgTrackedValues.TrackerEntries(id, true, codecs); + output.add(new MsgTrackedValues.TrackerEntries(id, true, codecs)); + } else if (!dirtyIndices.isEmpty()) { + IntSet toSend = dirtyIndices; + dirtyIndices = new IntOpenHashSet(); + List> pairs = new ArrayList<>(); + for (int i : toSend) { + pairs.add(codecs.get(i)); + } + output.add(new MsgTrackedValues.TrackerEntries(id, false, pairs)); } - - if (dirtyIndices.isEmpty()) { - return null; - } - - IntSet toSend = dirtyIndices; - dirtyIndices = new IntOpenHashSet(); - List> pairs = new ArrayList<>(); - for (int i : toSend) { - pairs.add(codecs.get(i)); - } - return new MsgTrackedValues.TrackerEntries(id, false, pairs); } - @SuppressWarnings("unchecked") - synchronized void load(boolean wipe, List> values) { - if (wipe) { + synchronized void load(MsgTrackedValues.TrackerEntries values) { + if (values.wipe()) { codecs.clear(); - codecs.addAll(values); - for (var value : values) { - Consumer callback = loadCallbacks.get(value.id); - if (callback != null) { - ((Consumer)callback).accept(value.value); + codecs.addAll(values.values()); + for (var entry : persistentObjects.int2ObjectEntrySet()) { + Pair pair = codecs.get(entry.getIntKey()); + if (pair != null) { + entry.getValue().readTrackedNbt((NbtCompound)pair.value); } } } else { - values.forEach(value -> { + for (var value : values.values()) { if (value.id >= 0 && value.id < codecs.size()) { if (codecs.get(value.id).type == value.type) { codecs.set(value.id, value); - Consumer callback = loadCallbacks.get(value.id); - if (callback != null) { - ((Consumer)callback).accept(value.value); + TrackableObject o = persistentObjects.get(value.id); + if (o != null) { + o.readTrackedNbt((NbtCompound)value.value); } } } - }); + } } } - public void close() { - manager.closeTracker(id); - } - public record Entry(int id) {} static class Pair { private final TrackableDataType type; @@ -142,4 +140,8 @@ public class DataTracker { type.write(buffer, value); } } + + public interface Updater { + void update(T t); + } } diff --git a/src/main/java/com/minelittlepony/unicopia/network/track/DataTrackerManager.java b/src/main/java/com/minelittlepony/unicopia/network/track/DataTrackerManager.java index 10f7b83a..4f945917 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/track/DataTrackerManager.java +++ b/src/main/java/com/minelittlepony/unicopia/network/track/DataTrackerManager.java @@ -2,22 +2,19 @@ package com.minelittlepony.unicopia.network.track; import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; import com.minelittlepony.unicopia.network.Channel; import com.minelittlepony.unicopia.util.Tickable; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.minecraft.entity.Entity; public class DataTrackerManager implements Tickable { private final Entity entity; final boolean isClient; - private final Int2ObjectOpenHashMap trackers = new Int2ObjectOpenHashMap<>(); - - private IntSet discardedTrackers = new IntOpenHashSet(); - private int nextId = 0; + private final List trackers = new ObjectArrayList<>(); + private final List> objectTrackers = new ObjectArrayList<>(); private final DataTracker primaryTracker = checkoutTracker(); @@ -31,20 +28,15 @@ public class DataTrackerManager implements Tickable { } public synchronized DataTracker checkoutTracker() { - DataTracker tracker = new DataTracker(this, nextId++); - trackers.put(tracker.id, tracker); + DataTracker tracker = new DataTracker(this, trackers.size()); + trackers.add(tracker); return tracker; } - synchronized void closeTracker(int id) { - if (id <= 0) { - return; - } - - trackers.remove(id); - if (!isClient) { - discardedTrackers.add(id); - } + public synchronized ObjectTracker checkoutTracker(Supplier objFunction) { + ObjectTracker tracker = new ObjectTracker<>(objectTrackers.size(), objFunction); + objectTrackers.add(tracker); + return tracker; } @Override @@ -53,35 +45,41 @@ public class DataTrackerManager implements Tickable { return; } - synchronized (this) { - List toTransmit = new ArrayList<>(); + List toTransmit = new ArrayList<>(); + List objToTransmit = new ArrayList<>(); - for (var entry : trackers.int2ObjectEntrySet()) { - MsgTrackedValues.TrackerEntries dirtyPairs = entry.getValue().getDirtyPairs(); - if (dirtyPairs != null) { - toTransmit.add(dirtyPairs); - } + synchronized (this) { + for (var entry : trackers) { + entry.getDirtyPairs(toTransmit); } - if (!toTransmit.isEmpty() || !discardedTrackers.isEmpty()) { - MsgTrackedValues packet = new MsgTrackedValues(entity.getId(), toTransmit, discardedTrackers.toIntArray()); - discardedTrackers = new IntOpenHashSet(); + for (var entry : objectTrackers) { + entry.getDirtyPairs(objToTransmit); + } + + if (!toTransmit.isEmpty() || !objToTransmit.isEmpty()) { + MsgTrackedValues packet = new MsgTrackedValues( + entity.getId(), + objToTransmit, + toTransmit + ); Channel.SERVER_TRACKED_ENTITY_DATA.sendToSurroundingPlayers(packet, entity); } } } synchronized void load(MsgTrackedValues packet) { - for (int id : packet.removedTrackers()) { - closeTracker(id); - } for (var update : packet.updatedTrackers()) { DataTracker tracker = trackers.get(update.id()); - if (tracker == null) { - tracker = new DataTracker(this, update.id()); - trackers.put(update.id(), tracker); + if (tracker != null) { + tracker.load(update); + } + } + for (var update : packet.updatedObjects()) { + ObjectTracker tracker = objectTrackers.get(update.id()); + if (tracker != null) { + tracker.load(update); } - tracker.load(update.wipe(), update.values()); } } } diff --git a/src/main/java/com/minelittlepony/unicopia/network/track/MsgTrackedValues.java b/src/main/java/com/minelittlepony/unicopia/network/track/MsgTrackedValues.java index 7f8d16e4..2a2800fb 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/track/MsgTrackedValues.java +++ b/src/main/java/com/minelittlepony/unicopia/network/track/MsgTrackedValues.java @@ -1,27 +1,38 @@ package com.minelittlepony.unicopia.network.track; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + import com.sollace.fabwork.api.packets.HandledPacket; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.nbt.NbtCompound; import net.minecraft.network.PacketByteBuf; public record MsgTrackedValues( int owner, - List updatedTrackers, - int[] removedTrackers + List updatedObjects, + List updatedTrackers ) implements HandledPacket { public MsgTrackedValues(PacketByteBuf buffer) { - this(buffer.readInt(), buffer.readCollection(ArrayList::new, TrackerEntries::new), buffer.readIntArray()); + this( + buffer.readInt(), + buffer.readCollection(ArrayList::new, TrackerObjects::new), + buffer.readCollection(ArrayList::new, TrackerEntries::new) + ); } @Override public void toBuffer(PacketByteBuf buffer) { buffer.writeInt(owner); + buffer.writeCollection(updatedObjects, (buf, obj) -> obj.write(buf)); buffer.writeCollection(updatedTrackers, (buf, tracker) -> tracker.write(buf)); - buffer.writeIntArray(removedTrackers); } @Override @@ -32,6 +43,22 @@ public record MsgTrackedValues( } } + public record TrackerObjects(int id, Set removedValues, Map values) { + public TrackerObjects(PacketByteBuf buffer) { + this( + buffer.readInt(), + buffer.readCollection(HashSet::new, PacketByteBuf::readUuid), + buffer.readMap(HashMap::new, PacketByteBuf::readUuid, PacketByteBuf::readNbt) + ); + } + + public void write(PacketByteBuf buffer) { + buffer.writeInt(id); + buffer.writeCollection(removedValues, PacketByteBuf::writeUuid); + buffer.writeMap(values, PacketByteBuf::writeUuid, PacketByteBuf::writeNbt); + } + } + public record TrackerEntries(int id, boolean wipe, List> values) { public TrackerEntries(PacketByteBuf buffer) { this(buffer.readInt(), buffer.readBoolean(), buffer.readCollection(ArrayList::new, DataTracker.Pair::new)); diff --git a/src/main/java/com/minelittlepony/unicopia/network/track/ObjectTracker.java b/src/main/java/com/minelittlepony/unicopia/network/track/ObjectTracker.java new file mode 100644 index 00000000..b4d00151 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/network/track/ObjectTracker.java @@ -0,0 +1,144 @@ +package com.minelittlepony.unicopia.network.track; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.Supplier; + +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.util.NbtSerialisable; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.util.Util; + +public class ObjectTracker implements NbtSerialisable { + private final Map trackedObjects = new Object2ObjectOpenHashMap<>(); + private volatile Map quickAccess = Map.of(); + + private final int id; + private final Supplier constructor; + + public ObjectTracker(int id, Supplier constructor) { + this.id = id; + this.constructor = constructor; + } + + public Set keySet() { + return quickAccess.keySet(); + } + + public Collection values() { + return quickAccess.values(); + } + + @Nullable + public T get(UUID id) { + return quickAccess.get(id); + } + + @Nullable + public T remove(UUID id, boolean immediate) { + T entry = quickAccess.get(id); + if (entry != null) { + entry.discard(immediate); + } + return entry; + } + + public boolean contains(UUID id) { + return quickAccess.containsKey(id); + } + + public boolean isEmpty() { + return quickAccess.isEmpty(); + } + + public boolean clear(boolean immediate) { + if (isEmpty()) { + return false; + } + values().forEach(value -> value.discard(immediate)); + return true; + } + + public synchronized void add(T obj) { + trackedObjects.put(obj.getUuid(), obj); + quickAccess = Map.copyOf(trackedObjects); + } + + synchronized void getDirtyPairs(List output) { + if (!trackedObjects.isEmpty()) { + Map trackableCompounds = new HashMap<>(); + Set removedTrackableObjects = new HashSet<>(); + trackedObjects.entrySet().removeIf(object -> { + TrackableObject.Status status = object.getValue().getStatus(); + if (status == TrackableObject.Status.REMOVED) { + removedTrackableObjects.add(object.getKey()); + } else if (status != TrackableObject.Status.DEFAULT) { + trackableCompounds.put(object.getKey(), object.getValue().toTrackedNbt()); + } + return status == TrackableObject.Status.REMOVED; + }); + quickAccess = Map.copyOf(trackedObjects); + + if (!trackableCompounds.isEmpty() || !removedTrackableObjects.isEmpty()) { + output.add(new MsgTrackedValues.TrackerObjects(id, removedTrackableObjects, trackableCompounds)); + } + } + } + + synchronized void load(MsgTrackedValues.TrackerObjects objects) { + objects.removedValues().forEach(removedId -> { + T o = trackedObjects.remove(removedId); + if (o != null) { + o.discard(true); + } + }); + objects.values().forEach((id, nbt) -> { + T o = trackedObjects.get(id); + if (o == null) { + o = constructor.get(); + trackedObjects.put(id, o); + } + o.readTrackedNbt(nbt); + }); + quickAccess = Map.copyOf(trackedObjects); + } + + @Override + public synchronized void toNBT(NbtCompound compound) { + quickAccess.forEach((id, value) -> { + compound.put(id.toString(), value.toTrackedNbt()); + }); + } + + @Override + public void fromNBT(NbtCompound compound) { + Map values = new Object2ObjectOpenHashMap<>(); + compound.getKeys().forEach(key -> { + try { + UUID id = UUID.fromString(key); + if (id != null && !Util.NIL_UUID.equals(id)) { + NbtCompound nbt = compound.getCompound(key); + T entry = constructor.get(); + entry.readTrackedNbt(nbt); + values.put(id, entry); + } + } catch (Throwable t) { + Unicopia.LOGGER.warn("Exception loading tracked object", t); + } + }); + synchronized (this) { + trackedObjects.clear(); + trackedObjects.putAll(values); + quickAccess = Map.copyOf(trackedObjects); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/network/track/TrackableObject.java b/src/main/java/com/minelittlepony/unicopia/network/track/TrackableObject.java new file mode 100644 index 00000000..40439f93 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/network/track/TrackableObject.java @@ -0,0 +1,24 @@ +package com.minelittlepony.unicopia.network.track; + +import java.util.UUID; + +import net.minecraft.nbt.NbtCompound; + +public interface TrackableObject { + UUID getUuid(); + + Status getStatus(); + + NbtCompound toTrackedNbt(); + + void readTrackedNbt(NbtCompound compound); + + void discard(boolean immediate); + + public enum Status { + DEFAULT, + NEW, + UPDATED, + REMOVED + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/projectile/MagicBeamEntity.java b/src/main/java/com/minelittlepony/unicopia/projectile/MagicBeamEntity.java index 28d0da3e..fa3c7d82 100644 --- a/src/main/java/com/minelittlepony/unicopia/projectile/MagicBeamEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/projectile/MagicBeamEntity.java @@ -9,8 +9,9 @@ import com.minelittlepony.unicopia.InteractionManager; import com.minelittlepony.unicopia.ability.magic.Affine; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Levelled; -import com.minelittlepony.unicopia.ability.magic.SpellContainer; -import com.minelittlepony.unicopia.ability.magic.SpellContainer.Operation; +import com.minelittlepony.unicopia.ability.magic.SpellInventory; +import com.minelittlepony.unicopia.ability.magic.SpellInventory.Operation; +import com.minelittlepony.unicopia.ability.magic.SpellSlots; import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.block.state.StatePredicate; @@ -18,9 +19,6 @@ import com.minelittlepony.unicopia.entity.EntityPhysics; import com.minelittlepony.unicopia.entity.MagicImmune; import com.minelittlepony.unicopia.entity.Physics; import com.minelittlepony.unicopia.entity.mob.UEntities; -import com.minelittlepony.unicopia.network.datasync.EffectSync; -import com.minelittlepony.unicopia.network.track.Trackable; - import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.data.DataTracker; @@ -34,8 +32,8 @@ import net.minecraft.world.World; public class MagicBeamEntity extends MagicProjectileEntity implements Caster, MagicImmune { private static final TrackedData HYDROPHOBIC = DataTracker.registerData(MagicBeamEntity.class, TrackedDataHandlerRegistry.BOOLEAN); - private final EffectSync effectDelegate = new EffectSync(this, Trackable.of(this).getDataTrackers().getPrimaryTracker()); - private final EntityPhysics physics = new EntityPhysics<>(this, Trackable.of(this).getDataTrackers().getPrimaryTracker()); + private final SpellInventory spells = SpellSlots.ofSingle(this); + private final EntityPhysics physics = new EntityPhysics<>(this); public MagicBeamEntity(EntityType type, World world) { super(type, world); @@ -61,10 +59,9 @@ public class MagicBeamEntity extends MagicProjectileEntity implements Caster void forEachDelegates(Consumer consumer, Function predicate) { - effectDelegate.tick(spell -> { + spells.tick(spell -> { Optional.ofNullable(predicate.apply(spell)).ifPresent(consumer); return Operation.SKIP; }); @@ -151,9 +148,7 @@ public class MagicBeamEntity extends MagicProjectileEntity implements Caster { - compound.put("effect", Spell.writeNbt(effect)); - }); + spells.getSlots().toNBT(compound); } }