Implement data trackers for spells

This commit is contained in:
Sollace 2024-05-27 06:39:01 +01:00
parent d716d2e00b
commit 2edf2c6989
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
33 changed files with 383 additions and 325 deletions

View file

@ -1,18 +1,25 @@
package com.minelittlepony.unicopia.ability.magic; package com.minelittlepony.unicopia.ability.magic;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.SpellReference; import com.minelittlepony.unicopia.ability.magic.spell.SpellReference;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.network.track.MsgTrackedValues;
import com.minelittlepony.unicopia.network.track.ObjectTracker; import com.minelittlepony.unicopia.network.track.ObjectTracker;
import com.minelittlepony.unicopia.network.track.Trackable; import com.minelittlepony.unicopia.network.track.Trackable;
import com.minelittlepony.unicopia.network.track.TrackableObject; import com.minelittlepony.unicopia.network.track.TrackableObject;
import com.minelittlepony.unicopia.util.NbtSerialisable; import com.minelittlepony.unicopia.util.NbtSerialisable;
import com.minelittlepony.unicopia.util.serialization.PacketCodec;
import io.netty.buffer.Unpooled;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
/** /**
* Container for multiple spells * Container for multiple spells
@ -21,7 +28,7 @@ import net.minecraft.nbt.NbtCompound;
*/ */
class MultiSpellSlot implements SpellSlots, NbtSerialisable { class MultiSpellSlot implements SpellSlots, NbtSerialisable {
private final Caster<?> owner; private final Caster<?> owner;
private final ObjectTracker<Entry<?>> tracker; private final ObjectTracker<Entry<Spell>> tracker;
public MultiSpellSlot(Caster<?> owner) { public MultiSpellSlot(Caster<?> owner) {
this.owner = owner; this.owner = owner;
@ -41,7 +48,7 @@ class MultiSpellSlot implements SpellSlots, NbtSerialisable {
@Override @Override
public void put(@Nullable Spell effect) { public void put(@Nullable Spell effect) {
if (effect != null) { if (effect != null) {
tracker.add(new Entry<>(owner, effect)); tracker.add(effect.getUuid(), new Entry<>(owner, effect));
} }
} }
@ -62,18 +69,32 @@ class MultiSpellSlot implements SpellSlots, NbtSerialisable {
@Override @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
compound.put("spells", tracker.toNBT()); compound.put("spells", NbtSerialisable.writeMap(tracker.entries(), UUID::toString, entry -> entry.spell.toNBT()));
} }
@Override @Override
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
tracker.fromNBT(compound.getCompound("spells")); tracker.load(NbtSerialisable.readMap(compound.getCompound("spells"), key -> {
try {
return UUID.fromString(key);
} catch (Throwable ignore) {}
return null;
}, (key, nbt) -> {
try {
Entry<Spell> entry = new Entry<>(owner);
entry.spell.fromNBT((NbtCompound)nbt);
return entry;
} catch (Throwable t) {
Unicopia.LOGGER.warn("Exception loading tracked object: {}", t.getMessage());
}
return null;
}));
} }
static final class Entry<T extends Spell> implements TrackableObject { static final class Entry<T extends Spell> implements TrackableObject<Entry<T>> {
private final Caster<?> owner; private final Caster<?> owner;
final SpellReference<T> spell = new SpellReference<>(); final SpellReference<T> spell = new SpellReference<>();
private Status status = Status.NEW; private boolean hasValue;
public Entry(Caster<?> owner) { public Entry(Caster<?> owner) {
this.owner = owner; this.owner = owner;
@ -99,34 +120,63 @@ class MultiSpellSlot implements SpellSlots, NbtSerialisable {
} }
} }
@Override
public UUID getUuid() {
return spell.get().getUuid();
}
@Override @Override
public Status getStatus() { public Status getStatus() {
try { boolean hasValue = spell.get() != null;
if (spell.get() == null) { if (hasValue != this.hasValue) {
return Status.REMOVED; this.hasValue = hasValue;
} return hasValue ? Status.NEW : Status.REMOVED;
if (spell.hasDirtySpell()) {
return Status.UPDATED;
}
return status;
} finally {
status = Status.DEFAULT;
} }
return spell.hasDirtySpell() ? Status.UPDATED : Status.DEFAULT;
} }
@Override @Override
public NbtCompound toTrackedNbt() { public void readTrackedNbt(NbtCompound nbt) {
spell.fromNBT(nbt);
}
@Override
public NbtCompound writeTrackedNbt() {
return spell.toNBT(); return spell.toNBT();
} }
@Override @Override
public void readTrackedNbt(NbtCompound compound) { public void read(PacketByteBuf buffer) {
spell.fromNBT(compound); byte contentType = buffer.readByte();
if (contentType == 1) {
readTrackedNbt(PacketCodec.COMPRESSED_NBT.read(buffer));
} else {
T spell = this.spell.get();
if (spell != null) {
spell.getDataTracker().load(new MsgTrackedValues.TrackerEntries(buffer));
}
}
}
@Override
public Optional<PacketByteBuf> write(Status status) {
if (status != Status.DEFAULT) {
PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer());
buffer.writeByte(1);
PacketCodec.COMPRESSED_NBT.write(buffer, spell.toNBT());
return Optional.of(buffer);
}
@Nullable T spell = this.spell.get();
if (spell == null) {
return Optional.empty();
}
return spell.getDataTracker().getDirtyPairs().map(entries -> {
PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer());
buffer.writeByte(0);
entries.write(buffer);
return buffer;
});
}
@Override
public void copyTo(Entry<T> destination) {
destination.spell.set(spell.get());
} }
} }

View file

@ -58,12 +58,12 @@ class SingleSpellSlot implements SpellSlots, NbtSerialisable {
@Override @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
compound.put("effect", entry.toTrackedNbt()); compound.put("effect", entry.spell.toNBT());
} }
@Override @Override
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
entry.readTrackedNbt(compound.getCompound("effect")); entry.spell.fromNBT(compound.getCompound("effect"));
} }
@Override @Override

View file

@ -7,15 +7,12 @@ import com.google.common.base.MoreObjects;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.server.world.Ether; import com.minelittlepony.unicopia.server.world.Ether;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
public abstract class AbstractDelegatingSpell implements Spell { public abstract class AbstractDelegatingSpell implements Spell {
private boolean dirty;
private boolean hidden;
private boolean destroyed;
private UUID uuid = UUID.randomUUID(); private UUID uuid = UUID.randomUUID();
private final CustomisedSpellType<?> type; private final CustomisedSpellType<?> type;
@ -25,10 +22,20 @@ public abstract class AbstractDelegatingSpell implements Spell {
this.type = type; this.type = type;
} }
public AbstractDelegatingSpell(CustomisedSpellType<?> type, Spell delegate) {
this.type = type;
this.delegate.set(delegate);
}
public final Spell getDelegate() { public final Spell getDelegate() {
return delegate.get(); return delegate.get();
} }
@Override
public final DataTracker getDataTracker() {
return getOrEmpty().getDataTracker();
}
private Spell getOrEmpty() { private Spell getOrEmpty() {
return MoreObjects.firstNonNull(delegate.get(), EmptySpell.INSTANCE); return MoreObjects.firstNonNull(delegate.get(), EmptySpell.INSTANCE);
} }
@ -73,19 +80,21 @@ public abstract class AbstractDelegatingSpell implements Spell {
return getOrEmpty().isDying(); return getOrEmpty().isDying();
} }
@Deprecated
@Override @Override
public boolean isDirty() { public boolean isDirty() {
return dirty || delegate.hasDirtySpell(); return delegate.hasDirtySpell();
} }
@Deprecated
@Override @Override
public void setDirty() { public void setDirty() {
dirty = true; getOrEmpty().setDirty();
} }
@Override @Override
public boolean isHidden() { public boolean isHidden() {
return hidden || getOrEmpty().isHidden(); return getOrEmpty().isHidden();
} }
@Override @Override
@ -95,15 +104,6 @@ public abstract class AbstractDelegatingSpell implements Spell {
@Override @Override
public final void destroy(Caster<?> caster) { public final void destroy(Caster<?> caster) {
if (destroyed) {
return;
}
destroyed = true;
setDead();
onDestroyed(caster);
}
protected void onDestroyed(Caster<?> caster) {
if (!caster.isClient()) { if (!caster.isClient()) {
Ether.get(caster.asWorld()).remove(this, caster); Ether.get(caster.asWorld()).remove(this, caster);
} }
@ -123,14 +123,11 @@ public abstract class AbstractDelegatingSpell implements Spell {
@Override @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
compound.putUuid("uuid", uuid); compound.putUuid("uuid", uuid);
compound.putBoolean("hidden", hidden);
compound.put("spell", delegate.toNBT()); compound.put("spell", delegate.toNBT());
} }
@Override @Override
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
dirty = false;
hidden = compound.getBoolean("hidden");
if (compound.contains("uuid")) { if (compound.contains("uuid")) {
uuid = compound.getUuid("uuid"); uuid = compound.getUuid("uuid");
} }
@ -139,6 +136,6 @@ public abstract class AbstractDelegatingSpell implements Spell {
@Override @Override
public final String toString() { public final String toString() {
return "Delegate{" + getTypeAndTraits() + "}[uuid=" + uuid + ", destroyed=" + destroyed + ", hidden=" + hidden + "][spell=" + delegate.get() + "]"; return "Delegate{" + getTypeAndTraits() + "}[uuid=" + uuid + "][spell=" + delegate.get() + "]";
} }
} }

View file

@ -9,6 +9,8 @@ import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellTyp
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance; import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.network.track.TrackableDataType;
import com.minelittlepony.unicopia.particle.MagicParticleEffect; import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.particle.UParticles;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
@ -21,6 +23,7 @@ import net.minecraft.nbt.NbtCompound;
*/ */
public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements IllusionarySpell { public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements IllusionarySpell {
private final DataTracker.Entry<Boolean> suppressed = dataTracker.startTracking(TrackableDataType.BOOLEAN, false);
private int suppressionCounter; private int suppressionCounter;
public DispersableDisguiseSpell(CustomisedSpellType<?> type) { public DispersableDisguiseSpell(CustomisedSpellType<?> type) {
@ -37,7 +40,7 @@ public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements I
public void onSuppressed(Caster<?> otherSource, float time) { public void onSuppressed(Caster<?> otherSource, float time) {
time /= getTraits().getOrDefault(Trait.STRENGTH, 1); time /= getTraits().getOrDefault(Trait.STRENGTH, 1);
suppressionCounter = (int)time; suppressionCounter = (int)time;
setDirty(); suppressed.set(true);
} }
@Override @Override
@ -64,7 +67,9 @@ public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements I
Entity appearance = getDisguise().getAppearance(); Entity appearance = getDisguise().getAppearance();
if (isSuppressed()) { if (isSuppressed()) {
suppressionCounter--; if (--suppressionCounter <= 0) {
suppressed.set(false);
}
owner.setInvisible(false); owner.setInvisible(false);
if (source instanceof Pony) { if (source instanceof Pony) {
@ -92,6 +97,9 @@ public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements I
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
super.fromNBT(compound); super.fromNBT(compound);
suppressionCounter = compound.getInt("suppressionCounter"); suppressionCounter = compound.getInt("suppressionCounter");
if (suppressionCounter > 0) {
suppressed.set(true);
}
} }
@Override @Override

View file

@ -5,6 +5,7 @@ import java.util.UUID;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.network.track.DataTracker;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
import net.minecraft.util.Util; import net.minecraft.util.Util;
@ -74,4 +75,9 @@ public final class EmptySpell implements Spell {
public String toString() { public String toString() {
return "EmptySpell{}"; return "EmptySpell{}";
} }
@Override
public DataTracker getDataTracker() {
return null;
}
} }

View file

@ -7,6 +7,7 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.AbstractSpell; import com.minelittlepony.unicopia.ability.magic.spell.effect.AbstractSpell;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; import com.minelittlepony.unicopia.entity.mob.CastSpellEntity;
import com.minelittlepony.unicopia.server.world.Ether; import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.NbtSerialisable; import com.minelittlepony.unicopia.util.NbtSerialisable;
@ -34,8 +35,8 @@ public class PlacementControlSpell extends AbstractSpell implements OrientedSpel
super(type); super(type);
} }
PlacementControlSpell(CustomisedSpellType<?> type, Spell delegate) { PlacementControlSpell(Spell delegate) {
this(type); this(SpellType.PLACE_CONTROL_SPELL.withTraits(delegate.getTypeAndTraits().traits()));
this.delegate = delegate; this.delegate = delegate;
} }
@ -50,10 +51,12 @@ public class PlacementControlSpell extends AbstractSpell implements OrientedSpel
public void setDimension(RegistryKey<World> dimension) { public void setDimension(RegistryKey<World> dimension) {
this.dimension = Optional.of(dimension); this.dimension = Optional.of(dimension);
setDirty();
} }
public void setPosition(Vec3d position) { public void setPosition(Vec3d position) {
this.position = Optional.of(position); this.position = Optional.of(position);
setDirty();
} }
@Override @Override
@ -98,7 +101,6 @@ public class PlacementControlSpell extends AbstractSpell implements OrientedSpel
entity.getWorld().spawnEntity(entity); entity.getWorld().spawnEntity(entity);
placedEntityId = entity.getUuid(); placedEntityId = entity.getUuid();
setDirty();
} }
return result; return result;
} }

View file

@ -54,7 +54,6 @@ public class RageAbilitySpell extends AbstractSpell {
ticksExtenguishing++; ticksExtenguishing++;
source.playSound(USounds.Vanilla.ENTITY_GENERIC_EXTINGUISH_FIRE, 1); source.playSound(USounds.Vanilla.ENTITY_GENERIC_EXTINGUISH_FIRE, 1);
source.spawnParticles(ParticleTypes.CLOUD, 12); source.spawnParticles(ParticleTypes.CLOUD, 12);
setDirty();
} else { } else {
ticksExtenguishing = 0; ticksExtenguishing = 0;
} }
@ -127,9 +126,6 @@ public class RageAbilitySpell extends AbstractSpell {
age++; age++;
source.asEntity().setInvulnerable(age < 25); source.asEntity().setInvulnerable(age < 25);
setDirty();
return true; return true;
} }

View file

@ -13,6 +13,7 @@ import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.server.world.Ether; import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.NbtSerialisable; import com.minelittlepony.unicopia.util.NbtSerialisable;
@ -30,6 +31,8 @@ public interface Spell extends NbtSerialisable, Affine {
*/ */
CustomisedSpellType<?> getTypeAndTraits(); CustomisedSpellType<?> getTypeAndTraits();
DataTracker getDataTracker();
default boolean isOf(SpellType<?> type) { default boolean isOf(SpellType<?> type) {
return getTypeAndTraits().type() == type; return getTypeAndTraits().type() == type;
} }
@ -74,8 +77,15 @@ public interface Spell extends NbtSerialisable, Affine {
/** /**
* Returns true if this effect has changes that need to be sent to the client. * Returns true if this effect has changes that need to be sent to the client.
*/ */
@Deprecated
boolean isDirty(); boolean isDirty();
/**
* Marks this effect as dirty.
*/
@Deprecated
void setDirty();
/** /**
* Applies this spell to the supplied caster. * Applies this spell to the supplied caster.
* @param caster The caster to apply the spell to * @param caster The caster to apply the spell to
@ -110,11 +120,6 @@ public interface Spell extends NbtSerialisable, Affine {
*/ */
void tickDying(Caster<?> caster); void tickDying(Caster<?> caster);
/**
* Marks this effect as dirty.
*/
void setDirty();
boolean isHidden(); boolean isHidden();
void setHidden(boolean hidden); void setHidden(boolean hidden);
@ -128,7 +133,7 @@ public interface Spell extends NbtSerialisable, Affine {
* Converts this spell into a placeable spell. * Converts this spell into a placeable spell.
*/ */
default PlacementControlSpell toPlaceable() { default PlacementControlSpell toPlaceable() {
return new PlacementControlSpell(SpellType.PLACE_CONTROL_SPELL.withTraits(), this); return new PlacementControlSpell(this);
} }
/** /**
@ -136,7 +141,7 @@ public interface Spell extends NbtSerialisable, Affine {
* @return * @return
*/ */
default ThrowableSpell toThrowable() { default ThrowableSpell toThrowable() {
return SpellType.THROWN_SPELL.withTraits().create().setSpell(this); return new ThrowableSpell(this);
} }
@Nullable @Nullable

View file

@ -15,7 +15,6 @@ import net.minecraft.nbt.NbtCompound;
public final class SpellReference<T extends Spell> implements NbtSerialisable { public final class SpellReference<T extends Spell> implements NbtSerialisable {
@Nullable @Nullable
private transient T spell; private transient T spell;
private int nbtHash;
@Nullable @Nullable
public T get() { public T get() {
@ -26,6 +25,7 @@ public final class SpellReference<T extends Spell> implements NbtSerialisable {
set(spell, null); set(spell, null);
} }
@Deprecated
public boolean hasDirtySpell() { public boolean hasDirtySpell() {
return spell != null && spell.isDirty(); return spell != null && spell.isDirty();
} }
@ -37,7 +37,6 @@ public final class SpellReference<T extends Spell> implements NbtSerialisable {
} }
T oldValue = this.spell; T oldValue = this.spell;
this.spell = spell; this.spell = spell;
nbtHash = 0;
if (owner != null && oldValue != null && (spell == null || !oldValue.getUuid().equals(spell.getUuid()))) { if (owner != null && oldValue != null && (spell == null || !oldValue.getUuid().equals(spell.getUuid()))) {
oldValue.destroy(owner); oldValue.destroy(owner);
} }
@ -65,19 +64,9 @@ public final class SpellReference<T extends Spell> implements NbtSerialisable {
@Override @Override
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
fromNBT(compound, true);
}
public void fromNBT(NbtCompound compound, boolean force) {
final int hash = compound.hashCode();
if (nbtHash == hash) {
return;
}
nbtHash = hash;
if (spell == null || !Objects.equals(Spell.getUuid(compound), spell.getUuid())) { if (spell == null || !Objects.equals(Spell.getUuid(compound), spell.getUuid())) {
spell = Spell.readNbt(compound); spell = Spell.readNbt(compound);
} else if (force || !spell.isDirty()) { } else {
spell.fromNBT(compound); spell.fromNBT(compound);
} }
} }

View file

@ -5,6 +5,7 @@ import java.util.Optional;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.projectile.MagicBeamEntity; import com.minelittlepony.unicopia.projectile.MagicBeamEntity;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate; import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
@ -20,9 +21,8 @@ public final class ThrowableSpell extends AbstractDelegatingSpell implements
super(type); super(type);
} }
public ThrowableSpell setSpell(Spell spell) { public ThrowableSpell(Spell delegate) {
delegate.set(spell); super(SpellType.THROWN_SPELL.withTraits(delegate.getTypeAndTraits().traits()), delegate);
return this;
} }
@Override @Override

View file

@ -5,26 +5,34 @@ import java.util.UUID;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.network.track.TrackableDataType;
import com.minelittlepony.unicopia.server.world.Ether; import com.minelittlepony.unicopia.server.world.Ether;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
public abstract class AbstractSpell implements Spell { public abstract class AbstractSpell implements Spell {
private boolean dead; private UUID uuid = UUID.randomUUID();
private boolean dying;
private boolean dirty;
private boolean hidden;
private boolean destroyed;
private final CustomisedSpellType<?> type; private final CustomisedSpellType<?> type;
private UUID uuid = UUID.randomUUID(); protected final DataTracker dataTracker = new DataTracker(0);
private final DataTracker.Entry<Boolean> dead = dataTracker.startTracking(TrackableDataType.BOOLEAN, false);
private final DataTracker.Entry<Boolean> dying = dataTracker.startTracking(TrackableDataType.BOOLEAN, false);
private boolean dirty;
private final DataTracker.Entry<Boolean> hidden = dataTracker.startTracking(TrackableDataType.BOOLEAN, false);
private boolean destroyed;
protected AbstractSpell(CustomisedSpellType<?> type) { protected AbstractSpell(CustomisedSpellType<?> type) {
this.type = type; this.type = type;
} }
@Override
public final DataTracker getDataTracker() {
return dataTracker;
}
@Override @Override
public final UUID getUuid() { public final UUID getUuid() {
return uuid; return uuid;
@ -45,25 +53,26 @@ public abstract class AbstractSpell implements Spell {
@Override @Override
public final void setDead() { public final void setDead() {
dying = true; dying.set(true);
setDirty();
} }
@Override @Override
public final boolean isDead() { public final boolean isDead() {
return dead; return dead.get();
} }
@Override @Override
public final boolean isDying() { public final boolean isDying() {
return dying; return dying.get();
} }
@Deprecated
@Override @Override
public final boolean isDirty() { public final boolean isDirty() {
return dirty; return dirty;
} }
@Deprecated
@Override @Override
public final void setDirty() { public final void setDirty() {
dirty = true; dirty = true;
@ -71,17 +80,17 @@ public abstract class AbstractSpell implements Spell {
@Override @Override
public final boolean isHidden() { public final boolean isHidden() {
return hidden; return hidden.get();
} }
@Override @Override
public final void setHidden(boolean hidden) { public final void setHidden(boolean hidden) {
this.hidden = hidden; this.hidden.set(hidden);
} }
@Override @Override
public void tickDying(Caster<?> caster) { public void tickDying(Caster<?> caster) {
dead = true; dead.set(true);
} }
@Override @Override
@ -102,9 +111,9 @@ public abstract class AbstractSpell implements Spell {
@Override @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
compound.putBoolean("dying", dying); compound.putBoolean("dying", dying.get());
compound.putBoolean("dead", dead); compound.putBoolean("dead", dead.get());
compound.putBoolean("hidden", hidden); compound.putBoolean("hidden", hidden.get());
compound.putUuid("uuid", uuid); compound.putUuid("uuid", uuid);
compound.put("traits", getTraits().toNbt()); compound.put("traits", getTraits().toNbt());
} }
@ -115,9 +124,9 @@ public abstract class AbstractSpell implements Spell {
if (compound.containsUuid("uuid")) { if (compound.containsUuid("uuid")) {
uuid = compound.getUuid("uuid"); uuid = compound.getUuid("uuid");
} }
dying = compound.getBoolean("dying"); dying.set(compound.getBoolean("dying"));
dead = compound.getBoolean("dead"); dead.set(compound.getBoolean("dead"));
hidden = compound.getBoolean("hidden"); hidden.set(compound.getBoolean("hidden"));
} }
@Override @Override

View file

@ -42,6 +42,7 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp
protected AttractiveSpell(CustomisedSpellType<?> type) { protected AttractiveSpell(CustomisedSpellType<?> type) {
super(type); super(type);
timer = new Timer(BASE_DURATION + TimedSpell.getExtraDuration(getTraits())); timer = new Timer(BASE_DURATION + TimedSpell.getExtraDuration(getTraits()));
dataTracker.startTracking(target);
} }
@Override @Override
@ -57,8 +58,6 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp
if (timer.getTicksRemaining() <= 0) { if (timer.getTicksRemaining() <= 0) {
return false; return false;
} }
setDirty();
} }
target.getOrEmpty(caster.asWorld()) target.getOrEmpty(caster.asWorld())

View file

@ -40,8 +40,6 @@ public class AwkwardSpell extends AbstractSpell implements TimedSpell {
if (timer.getTicksRemaining() <= 0) { if (timer.getTicksRemaining() <= 0) {
return false; return false;
} }
setDirty();
} }
if (source.isClient()) { if (source.isClient()) {

View file

@ -12,6 +12,8 @@ import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.*; import com.minelittlepony.unicopia.entity.*;
import com.minelittlepony.unicopia.entity.mob.UEntityAttributes; import com.minelittlepony.unicopia.entity.mob.UEntityAttributes;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.network.track.TrackableDataType;
import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.particle.UParticles;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate; import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
@ -55,15 +57,15 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
private final Timer timer; private final Timer timer;
private int struggles;
private float prevRadius; private float prevRadius;
private float radius; private DataTracker.Entry<Float> radius;
private DataTracker.Entry<Integer> struggles;
protected BubbleSpell(CustomisedSpellType<?> type) { protected BubbleSpell(CustomisedSpellType<?> type) {
super(type); super(type);
timer = new Timer(BASE_DURATION + TimedSpell.getExtraDuration(getTraits())); timer = new Timer(BASE_DURATION + TimedSpell.getExtraDuration(getTraits()));
struggles = (int)(getTraits().get(Trait.POWER) * 2); radius = dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
struggles = dataTracker.startTracking(TrackableDataType.INT, (int)(getTraits().get(Trait.POWER) * 2));
} }
@Override @Override
@ -72,7 +74,7 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
} }
public float getRadius(float tickDelta) { public float getRadius(float tickDelta) {
return MathHelper.lerp(tickDelta, prevRadius, radius); return MathHelper.lerp(tickDelta, prevRadius, radius.get());
} }
@Override @Override
@ -91,7 +93,7 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
} }
}); });
} }
radius = Math.max(entity.getHeight(), entity.getWidth()) * 1.2F; radius.set(Math.max(entity.getHeight(), entity.getWidth()) * 1.2F);
source.playSound(USounds.ENTITY_PLAYER_UNICORN_TELEPORT, 1); source.playSound(USounds.ENTITY_PLAYER_UNICORN_TELEPORT, 1);
entity.addVelocity(0, 0.2F * source.getPhysics().getGravitySignum(), 0); entity.addVelocity(0, 0.2F * source.getPhysics().getGravitySignum(), 0);
Living.updateVelocity(entity); Living.updateVelocity(entity);
@ -111,7 +113,7 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
boolean done = timer.getTicksRemaining() <= 0; boolean done = timer.getTicksRemaining() <= 0;
source.spawnParticles(source.getOriginVector().add(0, 1, 0), new Sphere(true, radius * (done ? 0.25F : 0.5F)), done ? 13 : 1, pos -> { source.spawnParticles(source.getOriginVector().add(0, 1, 0), new Sphere(true, radius.get() * (done ? 0.25F : 0.5F)), done ? 13 : 1, pos -> {
source.addParticle(done ? ParticleTypes.BUBBLE_POP : UParticles.BUBBLE, pos, Vec3d.ZERO); source.addParticle(done ? ParticleTypes.BUBBLE_POP : UParticles.BUBBLE, pos, Vec3d.ZERO);
}); });
@ -119,8 +121,6 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
return false; return false;
} }
setDirty();
source.asEntity().addVelocity( source.asEntity().addVelocity(
MathHelper.sin(source.asEntity().age / 6F) / 50F, MathHelper.sin(source.asEntity().age / 6F) / 50F,
MathHelper.sin(source.asEntity().age / 6F) / 50F, MathHelper.sin(source.asEntity().age / 6F) / 50F,
@ -129,13 +129,14 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
source.asEntity().fallDistance = 0; source.asEntity().fallDistance = 0;
prevRadius = radius; prevRadius = radius.get();
if (source instanceof Pony pony && pony.sneakingChanged() && pony.asEntity().isSneaking()) { if (source instanceof Pony pony && pony.sneakingChanged() && pony.asEntity().isSneaking()) {
setDirty(); radius.set(radius.get() + 0.5F);
radius += 0.5F;
source.playSound(USounds.SPELL_BUBBLE_DISTURB, 1); source.playSound(USounds.SPELL_BUBBLE_DISTURB, 1);
if (struggles-- <= 0) { int s = struggles.get() - 1;
struggles.set(s);
if (s <= 0) {
setDead(); setDead();
return false; return false;
} }
@ -168,16 +169,16 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
@Override @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
super.toNBT(compound); super.toNBT(compound);
compound.putInt("struggles", struggles); compound.putInt("struggles", struggles.get());
compound.putFloat("radius", radius); compound.putFloat("radius", radius.get());
timer.toNBT(compound); timer.toNBT(compound);
} }
@Override @Override
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
super.fromNBT(compound); super.fromNBT(compound);
struggles = compound.getInt("struggles"); struggles.set(compound.getInt("struggles"));
radius = compound.getFloat("radius"); radius.set(compound.getFloat("radius"));
timer.fromNBT(compound); timer.fromNBT(compound);
} }
} }

View file

@ -17,6 +17,8 @@ import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; import com.minelittlepony.unicopia.entity.mob.CastSpellEntity;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.network.track.TrackableDataType;
import com.minelittlepony.unicopia.particle.FollowingParticleEffect; import com.minelittlepony.unicopia.particle.FollowingParticleEffect;
import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect; import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.particle.ParticleUtils;
@ -56,7 +58,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
.with(Trait.DARKNESS, 100) .with(Trait.DARKNESS, 100)
.build(); .build();
private float accumulatedMass = 0; private final DataTracker.Entry<Float> accumulatedMass = this.dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
private final TargetSelecter targetSelecter = new TargetSelecter(this).setFilter(this::isValidTarget).setTargetowner(true).setTargetAllies(true); private final TargetSelecter targetSelecter = new TargetSelecter(this).setFilter(this::isValidTarget).setTargetowner(true).setTargetAllies(true);
@ -70,7 +72,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
// 3. force reaches 0 at distance of drawDropOffRange // 3. force reaches 0 at distance of drawDropOffRange
private double getMass() { private double getMass() {
return 0.1F + accumulatedMass / 10F; return 0.1F + accumulatedMass.get() / 10F;
} }
public double getEventHorizonRadius() { public double getEventHorizonRadius() {
@ -174,11 +176,12 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
@Override @Override
public void tickDying(Caster<?> source) { public void tickDying(Caster<?> source) {
accumulatedMass -= 0.8F; float m = accumulatedMass.get() - 0.8F;
accumulatedMass.set(m);
double mass = getMass() * 0.1; double mass = getMass() * 0.1;
double logarithm = 1 - (1D / (1 + (mass * mass))); double logarithm = 1 - (1D / (1 + (mass * mass)));
radius.update((float)Math.max(0.1, logarithm * source.asWorld().getGameRules().getInt(UGameRules.MAX_DARK_VORTEX_SIZE)), 200L); radius.update((float)Math.max(0.1, logarithm * source.asWorld().getGameRules().getInt(UGameRules.MAX_DARK_VORTEX_SIZE)), 200L);
if (accumulatedMass < 1) { if (m < 1) {
super.tickDying(source); super.tickDying(source);
} }
@ -202,7 +205,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
@Override @Override
public boolean isFriendlyTogether(Affine other) { public boolean isFriendlyTogether(Affine other) {
return accumulatedMass < 4; return accumulatedMass.get() < 4;
} }
private boolean isValidTarget(Caster<?> source, Entity entity) { private boolean isValidTarget(Caster<?> source, Entity entity) {
@ -274,8 +277,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
double massOfTarget = AttractionUtils.getMass(target); double massOfTarget = AttractionUtils.getMass(target);
if (!source.isClient() && massOfTarget != 0) { if (!source.isClient() && massOfTarget != 0) {
accumulatedMass += massOfTarget; accumulatedMass.set((float)(accumulatedMass.get() + massOfTarget));
setDirty();
} }
target.damage(source.damageOf(UDamageTypes.GAVITY_WELL_RECOIL, source), Integer.MAX_VALUE); target.damage(source.damageOf(UDamageTypes.GAVITY_WELL_RECOIL, source), Integer.MAX_VALUE);
@ -303,12 +305,12 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
@Override @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
super.toNBT(compound); super.toNBT(compound);
compound.putFloat("accumulatedMass", accumulatedMass); compound.putFloat("accumulatedMass", accumulatedMass.get());
} }
@Override @Override
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
super.fromNBT(compound); super.fromNBT(compound);
accumulatedMass = compound.getFloat("accumulatedMass"); accumulatedMass.set(compound.getFloat("accumulatedMass"));
} }
} }

View file

@ -69,8 +69,6 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell {
return false; return false;
} }
setDirty();
List<Entity> targets = getTargets(caster).toList(); List<Entity> targets = getTargets(caster).toList();
if (targets.isEmpty()) { if (targets.isEmpty()) {

View file

@ -63,8 +63,6 @@ public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileD
return false; return false;
} }
setDirty();
if (!caster.isClient()) { if (!caster.isClient()) {
if (lights.isEmpty()) { if (lights.isEmpty()) {
int size = 2 + caster.asWorld().random.nextInt(2) + (int)(getTraits().get(Trait.LIFE, 10, 20) - 10)/10; int size = 2 + caster.asWorld().random.nextInt(2) + (int)(getTraits().get(Trait.LIFE, 10, 20) - 10)/10;
@ -83,7 +81,6 @@ public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileD
entity.getWorld().spawnEntity(entity); entity.getWorld().spawnEntity(entity);
ref.set(entity); ref.set(entity);
setDirty();
} }
}); });
} }

View file

@ -35,8 +35,6 @@ public class MimicSpell extends AbstractDisguiseSpell implements HomingSpell, Ti
return false; return false;
} }
setDirty();
return super.tick(caster, situation); return super.tick(caster, situation);
} }

View file

@ -135,7 +135,6 @@ public class MindSwapSpell extends MimicSpell implements ProjectileDelegate.Enti
caster.playSound(USounds.SPELL_MINDSWAP_SWAP, 1); caster.playSound(USounds.SPELL_MINDSWAP_SWAP, 1);
}); });
initialized = true; initialized = true;
setDirty();
} }
if (counterpart.isSet()) { if (counterpart.isSet()) {
@ -143,13 +142,12 @@ public class MindSwapSpell extends MimicSpell implements ProjectileDelegate.Enti
if (other == null) { if (other == null) {
caster.getOriginatingCaster().asEntity().damage(caster.asWorld().getDamageSources().magic(), Float.MAX_VALUE); caster.getOriginatingCaster().asEntity().damage(caster.asWorld().getDamageSources().magic(), Float.MAX_VALUE);
setDead(); destroy(caster);
return false; return false;
} }
if (!Caster.of(other).get().getSpellSlot().contains(SpellType.MIMIC)) { if (!Caster.of(other).get().getSpellSlot().contains(SpellType.MIMIC)) {
onDestroyed(caster); destroy(caster);
setDead();
return false; return false;
} }
} }
@ -158,8 +156,7 @@ public class MindSwapSpell extends MimicSpell implements ProjectileDelegate.Enti
counterpart.ifPresent(caster.asWorld(), e -> { counterpart.ifPresent(caster.asWorld(), e -> {
e.damage(e.getDamageSources().magic(), Float.MAX_VALUE); e.damage(e.getDamageSources().magic(), Float.MAX_VALUE);
}); });
onDestroyed(caster); destroy(caster);
setDead();
return false; return false;
} }
} }

View file

@ -131,7 +131,6 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti
float additional = source.asWorld().getLocalDifficulty(source.getOrigin()).getLocalDifficulty() + getTraits().get(Trait.CHAOS, 0, 10); float additional = source.asWorld().getLocalDifficulty(source.getOrigin()).getLocalDifficulty() + getTraits().get(Trait.CHAOS, 0, 10);
setDirty();
if (--spawnCountdown > 0 && !summonedEntities.isEmpty()) { if (--spawnCountdown > 0 && !summonedEntities.isEmpty()) {
return true; return true;
} }
@ -213,7 +212,6 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti
source.asWorld().spawnEntity(minion); source.asWorld().spawnEntity(minion);
summonedEntities.add(new EntityReference<>(minion)); summonedEntities.add(new EntityReference<>(minion));
setDirty();
} }
@Override @Override

View file

@ -14,6 +14,8 @@ import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.network.Channel; import com.minelittlepony.unicopia.network.Channel;
import com.minelittlepony.unicopia.network.MsgCasterLookRequest; import com.minelittlepony.unicopia.network.MsgCasterLookRequest;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.network.track.TrackableDataType;
import com.minelittlepony.unicopia.particle.*; import com.minelittlepony.unicopia.particle.*;
import com.minelittlepony.unicopia.server.world.Ether; import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.shape.*; import com.minelittlepony.unicopia.util.shape.*;
@ -26,6 +28,7 @@ import net.minecraft.network.packet.s2c.play.PositionFlag;
import net.minecraft.particle.ParticleTypes; import net.minecraft.particle.ParticleTypes;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Util;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
@ -40,15 +43,13 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
private static final Shape PARTICLE_AREA = new Sphere(true, 2, 1, 1, 0); private static final Shape PARTICLE_AREA = new Sphere(true, 2, 1, 1, 0);
@Nullable @Nullable
private UUID targetPortalId; private final DataTracker.Entry<UUID> targetPortalId = dataTracker.startTracking(TrackableDataType.UUID, Util.NIL_UUID);
private float targetPortalPitch; private final DataTracker.Entry<Float> targetPortalPitch = dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
private float targetPortalYaw; private final DataTracker.Entry<Float> targetPortalYaw = dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
private final EntityReference<Entity> teleportationTarget = new EntityReference<>(); private final EntityReference<Entity> teleportationTarget = new EntityReference<>();
private boolean publishedPosition; private final DataTracker.Entry<Float> pitch = dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
private final DataTracker.Entry<Float> yaw = dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
private float pitch;
private float yaw;
private Shape particleArea = PARTICLE_AREA; private Shape particleArea = PARTICLE_AREA;
@ -61,30 +62,30 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
} }
public float getPitch() { public float getPitch() {
return pitch; return pitch.get();
} }
public float getYaw() { public float getYaw() {
return yaw; return yaw.get();
} }
public float getTargetPitch() { public float getTargetPitch() {
return targetPortalPitch; return targetPortalPitch.get();
} }
public float getTargetYaw() { public float getTargetYaw() {
return targetPortalYaw; return targetPortalYaw.get();
} }
public float getYawDifference() { public float getYawDifference() {
return MathHelper.wrapDegrees(180 + targetPortalYaw - yaw); return MathHelper.wrapDegrees(180 + getTargetYaw() - getYaw());
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Ether.Entry<PortalSpell> getDestination(Caster<?> source) { private Ether.Entry<PortalSpell> getDestination(Caster<?> source) {
return targetPortalId == null ? null : getDestinationReference() return Util.NIL_UUID.equals(targetPortalId.get()) ? null : getDestinationReference()
.getTarget() .getTarget()
.map(target -> Ether.get(source.asWorld()).get((SpellType<PortalSpell>)getType(), target.uuid(), targetPortalId)) .map(target -> Ether.get(source.asWorld()).get((SpellType<PortalSpell>)getType(), target.uuid(), targetPortalId.get()))
.filter(destination -> destination.isClaimedBy(getUuid())) .filter(destination -> destination.isClaimedBy(getUuid()))
.orElse(null); .orElse(null);
} }
@ -94,6 +95,18 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
return toPlaceable().apply(caster); return toPlaceable().apply(caster);
} }
protected void setDestination(@Nullable Ether.Entry<?> destination) {
if (destination == null) {
teleportationTarget.set(null);
targetPortalId.set(Util.NIL_UUID);
} else {
teleportationTarget.copyFrom(destination.entity);
targetPortalId.set(destination.getSpellId());
targetPortalPitch.set(destination.getPitch());
targetPortalYaw.set(destination.getYaw());
}
}
@Override @Override
public boolean tick(Caster<?> source, Situation situation) { public boolean tick(Caster<?> source, Situation situation) {
if (situation == Situation.GROUND) { if (situation == Situation.GROUND) {
@ -108,9 +121,7 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
if (targetEntry == null) { if (targetEntry == null) {
if (teleportationTarget.isSet()) { if (teleportationTarget.isSet()) {
teleportationTarget.set(null); setDestination(null);
targetPortalId = null;
setDirty();
source.asWorld().syncWorldEvent(WorldEvents.BLOCK_BROKEN, source.getOrigin(), Block.getRawIdFromState(Blocks.GLASS.getDefaultState())); source.asWorld().syncWorldEvent(WorldEvents.BLOCK_BROKEN, source.getOrigin(), Block.getRawIdFromState(Blocks.GLASS.getDefaultState()));
} else { } else {
Ether.get(source.asWorld()).anyMatch(getType(), entry -> { Ether.get(source.asWorld()).anyMatch(getType(), entry -> {
@ -119,18 +130,10 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
ownEntry.claim(entry.getSpellId()); ownEntry.claim(entry.getSpellId());
synchronized (entry) { synchronized (entry) {
if (entry.getSpell() instanceof PortalSpell portal) { if (entry.getSpell() instanceof PortalSpell portal) {
portal.teleportationTarget.copyFrom(ownEntry.entity); portal.setDestination(ownEntry);
portal.targetPortalId = getUuid();
portal.targetPortalPitch = pitch;
portal.targetPortalYaw = yaw;
portal.setDirty();
} }
} }
teleportationTarget.copyFrom(entry.entity); setDestination(entry);
targetPortalId = entry.getSpellId();
targetPortalPitch = entry.getPitch();
targetPortalYaw = entry.getYaw();
setDirty();
} }
return false; return false;
}); });
@ -142,8 +145,8 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
} }
var entry = Ether.get(source.asWorld()).getOrCreate(this, source); var entry = Ether.get(source.asWorld()).getOrCreate(this, source);
entry.setPitch(pitch); entry.setPitch(pitch.get());
entry.setYaw(yaw); entry.setYaw(yaw.get());
} }
return !isDead(); return !isDead();
@ -154,7 +157,7 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
source.findAllEntitiesInRange(1).forEach(entity -> { source.findAllEntitiesInRange(1).forEach(entity -> {
if (!entity.hasPortalCooldown()) { if (!entity.hasPortalCooldown()) {
float approachYaw = Math.abs(MathHelper.wrapDegrees(entity.getYaw() - this.yaw)); float approachYaw = Math.abs(MathHelper.wrapDegrees(entity.getYaw() - this.yaw.get()));
if (approachYaw > 80) { if (approachYaw > 80) {
return; return;
} }
@ -177,7 +180,6 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
entity.getWorld().playSoundFromEntity(null, entity, USounds.ENTITY_PLAYER_UNICORN_TELEPORT, entity.getSoundCategory(), 1, 1); entity.getWorld().playSoundFromEntity(null, entity, USounds.ENTITY_PLAYER_UNICORN_TELEPORT, entity.getSoundCategory(), 1, 1);
entity.teleport((ServerWorld)entity.getWorld(), dest.x, dest.y, dest.z, PositionFlag.VALUES, yaw, entity.getPitch()); entity.teleport((ServerWorld)entity.getWorld(), dest.x, dest.y, dest.z, PositionFlag.VALUES, yaw, entity.getPitch());
entity.getWorld().playSoundFromEntity(null, entity, USounds.ENTITY_PLAYER_UNICORN_TELEPORT, entity.getSoundCategory(), 1, 1); entity.getWorld().playSoundFromEntity(null, entity, USounds.ENTITY_PLAYER_UNICORN_TELEPORT, entity.getSoundCategory(), 1, 1);
setDirty();
Living.updateVelocity(entity); Living.updateVelocity(entity);
@ -193,13 +195,12 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
@Override @Override
public void setOrientation(Caster<?> caster, float pitch, float yaw) { public void setOrientation(Caster<?> caster, float pitch, float yaw) {
this.pitch = 90 - pitch; this.pitch.set(90 - pitch);
this.yaw = -yaw; this.yaw.set(-yaw);
particleArea = PARTICLE_AREA.rotate( particleArea = PARTICLE_AREA.rotate(
this.pitch * MathHelper.RADIANS_PER_DEGREE, this.pitch.get() * MathHelper.RADIANS_PER_DEGREE,
this.yaw * MathHelper.RADIANS_PER_DEGREE (180 - this.yaw.get()) * MathHelper.RADIANS_PER_DEGREE
); );
setDirty();
} }
@Override @Override
@ -227,30 +228,26 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
@Override @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
super.toNBT(compound); super.toNBT(compound);
if (targetPortalId != null) { compound.putUuid("targetPortalId", targetPortalId.get());
compound.putUuid("targetPortalId", targetPortalId);
}
compound.putBoolean("publishedPosition", publishedPosition);
compound.put("teleportationTarget", teleportationTarget.toNBT()); compound.put("teleportationTarget", teleportationTarget.toNBT());
compound.putFloat("pitch", pitch); compound.putFloat("pitch", getPitch());
compound.putFloat("yaw", yaw); compound.putFloat("yaw", getYaw());
compound.putFloat("targetPortalPitch", targetPortalPitch); compound.putFloat("targetPortalPitch", getTargetPitch());
compound.putFloat("targetPortalYaw", targetPortalYaw); compound.putFloat("targetPortalYaw", getTargetYaw());
} }
@Override @Override
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
super.fromNBT(compound); super.fromNBT(compound);
targetPortalId = compound.containsUuid("targetPortalId") ? compound.getUuid("targetPortalId") : null; targetPortalId.set(compound.containsUuid("targetPortalId") ? compound.getUuid("targetPortalId") : Util.NIL_UUID);
publishedPosition = compound.getBoolean("publishedPosition");
teleportationTarget.fromNBT(compound.getCompound("teleportationTarget")); teleportationTarget.fromNBT(compound.getCompound("teleportationTarget"));
pitch = compound.getFloat("pitch"); pitch.set(compound.getFloat("pitch"));
yaw = compound.getFloat("yaw"); yaw.set(compound.getFloat("yaw"));
targetPortalPitch = compound.getFloat("targetPortalPitch"); targetPortalPitch.set(compound.getFloat("targetPortalPitch"));
targetPortalYaw = compound.getFloat("targetPortalYaw"); targetPortalYaw.set(compound.getFloat("targetPortalYaw"));
particleArea = PARTICLE_AREA.rotate( particleArea = PARTICLE_AREA.rotate(
pitch * MathHelper.RADIANS_PER_DEGREE, pitch.get() * MathHelper.RADIANS_PER_DEGREE,
(180 - yaw) * MathHelper.RADIANS_PER_DEGREE (180 - yaw.get()) * MathHelper.RADIANS_PER_DEGREE
); );
} }
} }

View file

@ -16,6 +16,8 @@ import com.minelittlepony.unicopia.ability.magic.spell.SpellAttributes;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.entity.damage.UDamageTypes;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.network.track.TrackableDataType;
import com.minelittlepony.unicopia.particle.FollowingParticleEffect; import com.minelittlepony.unicopia.particle.FollowingParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.particle.UParticles;
@ -43,6 +45,7 @@ public class SiphoningSpell extends AbstractAreaEffectSpell {
tooltip.add(SpellAttributes.of(SpellAttributes.RANGE, 4)); tooltip.add(SpellAttributes.of(SpellAttributes.RANGE, 4));
} }
private final DataTracker.Entry<Boolean> upset = dataTracker.startTracking(TrackableDataType.BOOLEAN, false);
private int ticksUpset; private int ticksUpset;
protected SiphoningSpell(CustomisedSpellType<?> type) { protected SiphoningSpell(CustomisedSpellType<?> type) {
@ -57,8 +60,8 @@ public class SiphoningSpell extends AbstractAreaEffectSpell {
@Override @Override
public boolean tick(Caster<?> source, Situation situation) { public boolean tick(Caster<?> source, Situation situation) {
if (ticksUpset > 0) { if (ticksUpset > 0 && --ticksUpset <= 0) {
ticksUpset--; upset.set(false);
} }
if (source.isClient()) { if (source.isClient()) {
@ -108,7 +111,7 @@ public class SiphoningSpell extends AbstractAreaEffectSpell {
} else { } else {
e.damage(damage, e.getHealth() / 4); e.damage(damage, e.getHealth() / 4);
ticksUpset = 100; ticksUpset = 100;
setDirty(); upset.set(true);
} }
} else { } else {
e.heal((float)Math.min(source.getLevel().getScaled(e.getHealth()) / 2F, maxHealthGain * 0.6)); e.heal((float)Math.min(source.getLevel().getScaled(e.getHealth()) / 2F, maxHealthGain * 0.6));
@ -174,5 +177,8 @@ public class SiphoningSpell extends AbstractAreaEffectSpell {
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
super.fromNBT(compound); super.fromNBT(compound);
ticksUpset = compound.getInt("upset"); ticksUpset = compound.getInt("upset");
if (ticksUpset > 0) {
upset.set(true);
}
} }
} }

View file

@ -45,7 +45,7 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
private static final DynamicCommandExceptionType UNKNOWN_SPELL_TYPE_EXCEPTION = new DynamicCommandExceptionType(id -> Text.translatable("spell_type.unknown", id)); private static final DynamicCommandExceptionType UNKNOWN_SPELL_TYPE_EXCEPTION = new DynamicCommandExceptionType(id -> Text.translatable("spell_type.unknown", id));
public static final SpellType<PlacementControlSpell> PLACE_CONTROL_SPELL = register("place_controller", SpellType.<PlacementControlSpell>builder(PlacementControlSpell::new).affinity(Affinity.NEUTRAL).unobtainable().stackable().shape(GemstoneItem.Shape.DONUT)); public static final SpellType<PlacementControlSpell> PLACE_CONTROL_SPELL = register("place_controller", SpellType.<PlacementControlSpell>builder(PlacementControlSpell::new).affinity(Affinity.NEUTRAL).unobtainable().stackable().shape(GemstoneItem.Shape.DONUT));
public static final SpellType<ThrowableSpell> THROWN_SPELL = register("thrown", builder(ThrowableSpell::new).affinity(Affinity.NEUTRAL).unobtainable().shape(GemstoneItem.Shape.DONUT)); public static final SpellType<ThrowableSpell> THROWN_SPELL = register("thrown", SpellType.<ThrowableSpell>builder(ThrowableSpell::new).affinity(Affinity.NEUTRAL).unobtainable().shape(GemstoneItem.Shape.DONUT));
public static final SpellType<DispersableDisguiseSpell> CHANGELING_DISGUISE = register("disguise", builder(DispersableDisguiseSpell::new).affinity(Affinity.BAD).color(0x19E48E).unobtainable().shape(GemstoneItem.Shape.ARROW)); public static final SpellType<DispersableDisguiseSpell> CHANGELING_DISGUISE = register("disguise", builder(DispersableDisguiseSpell::new).affinity(Affinity.BAD).color(0x19E48E).unobtainable().shape(GemstoneItem.Shape.ARROW));
public static final SpellType<ChangelingFeedingSpell> FEED = register("feed", SpellType.<ChangelingFeedingSpell>builder(ChangelingFeedingSpell::new).affinity(Affinity.BAD).color(0xBDBDF9).unobtainable().shape(GemstoneItem.Shape.ARROW)); public static final SpellType<ChangelingFeedingSpell> FEED = register("feed", SpellType.<ChangelingFeedingSpell>builder(ChangelingFeedingSpell::new).affinity(Affinity.BAD).color(0xBDBDF9).unobtainable().shape(GemstoneItem.Shape.ARROW));

View file

@ -30,7 +30,7 @@ import net.minecraft.world.World;
* *
* @param <T> The type of the entity this reference points to. * @param <T> The type of the entity this reference points to.
*/ */
public class EntityReference<T extends Entity> implements NbtSerialisable, TrackableObject { public class EntityReference<T extends Entity> implements NbtSerialisable, TrackableObject<EntityReference<T>> {
private static final Serializer<?> SERIALIZER = Serializer.of(EntityReference::new); private static final Serializer<?> SERIALIZER = Serializer.of(EntityReference::new);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -122,12 +122,7 @@ public class EntityReference<T extends Entity> implements NbtSerialisable, Track
@Override @Override
public int hashCode() { public int hashCode() {
return getUuid().hashCode(); return getTarget().map(EntityValues::uuid).orElse(Util.NIL_UUID).hashCode();
}
@Override
public UUID getUuid() {
return getTarget().map(EntityValues::uuid).orElse(Util.NIL_UUID);
} }
@Override @Override
@ -140,7 +135,7 @@ public class EntityReference<T extends Entity> implements NbtSerialisable, Track
} }
@Override @Override
public NbtCompound toTrackedNbt() { public NbtCompound writeTrackedNbt() {
return toNBT(); return toNBT();
} }
@ -149,6 +144,12 @@ public class EntityReference<T extends Entity> implements NbtSerialisable, Track
fromNBT(compound); fromNBT(compound);
} }
@Override
public void copyTo(EntityReference<T> destination) {
destination.reference = reference;
destination.directReference = directReference;
}
@Override @Override
public void discard(boolean immediate) { public void discard(boolean immediate) {
set(null); set(null);

View file

@ -38,12 +38,10 @@ public class CorruptionHandler implements Tickable {
if (entity.age % (10 * ItemTracker.SECONDS) == 0) { if (entity.age % (10 * ItemTracker.SECONDS) == 0) {
if (random.nextInt(100) == 0) { if (random.nextInt(100) == 0) {
pony.getCorruption().add(-1); pony.getCorruption().add(-1);
pony.setDirty();
} }
if (entity.getHealth() >= entity.getMaxHealth() - 1 && !entity.getHungerManager().isNotFull()) { if (entity.getHealth() >= entity.getMaxHealth() - 1 && !entity.getHungerManager().isNotFull()) {
pony.getCorruption().add(-random.nextInt(4)); pony.getCorruption().add(-random.nextInt(4));
pony.setDirty();
} }
} }
} }
@ -79,6 +77,5 @@ public class CorruptionHandler implements Tickable {
MagicReserves reserves = pony.getMagicalReserves(); MagicReserves reserves = pony.getMagicalReserves();
reserves.getExertion().addPercent(10); reserves.getExertion().addPercent(10);
reserves.getEnergy().add(10); reserves.getEnergy().add(10);
pony.setDirty();
} }
} }

View file

@ -1,37 +1,33 @@
package com.minelittlepony.unicopia.network.track; package com.minelittlepony.unicopia.network.track;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
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.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketByteBuf;
public class DataTracker { public class DataTracker {
private final List<Pair<?>> codecs = new ObjectArrayList<>(); private final List<Pair<?>> codecs = new ObjectArrayList<>();
private IntSet dirtyIndices = new IntOpenHashSet(); private IntSet dirtyIndices = new IntOpenHashSet();
private Int2ObjectMap<TrackableObject> persistentObjects = new Int2ObjectOpenHashMap<>(); private List<TrackableObject<?>> persistentObjects = new ObjectArrayList<>();
private final DataTrackerManager manager;
private boolean initial = true; private boolean initial = true;
final int id; final int id;
public DataTracker(DataTrackerManager manager, int id) { public DataTracker(int id) {
this.manager = manager;
this.id = id; this.id = id;
} }
public <T extends TrackableObject> Entry<NbtCompound> startTracking(T value) { public <T extends TrackableObject<T>> T startTracking(T value) {
Entry<NbtCompound> entry = startTracking(TrackableDataType.COMPRESSED_NBT, value.toTrackedNbt()); persistentObjects.add(value);
persistentObjects.put(entry.id(), value); return value;
return entry;
} }
public <T> Entry<T> startTracking(TrackableDataType<T> type, T initialValue) { public <T> Entry<T> startTracking(TrackableDataType<T> type, T initialValue) {
@ -50,10 +46,6 @@ public class DataTracker {
} }
private <T> void set(Entry<T> entry, T value) { private <T> void set(Entry<T> entry, T value) {
if (manager.isClient) {
return;
}
Pair<T> pair = getPair(entry); Pair<T> pair = getPair(entry);
if (!Objects.equals(pair.value, value)) { if (!Objects.equals(pair.value, value)) {
synchronized (this) { synchronized (this) {
@ -63,25 +55,14 @@ public class DataTracker {
} }
} }
@SuppressWarnings("unchecked") @SuppressWarnings({ "unchecked", "rawtypes" })
private void updateTrackables() {
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<Object>)codecs.get(key)).value = entry.getValue().toTrackedNbt();
dirtyIndices.add(key);
}
}
}
@SuppressWarnings("unchecked")
synchronized void copyTo(DataTracker destination) { synchronized void copyTo(DataTracker destination) {
for (int i = 0; i < codecs.size(); i++) { for (int i = 0; i < codecs.size(); i++) {
((Pair<Object>)destination.codecs.get(i)).value = codecs.get(i).value; ((Pair<Object>)destination.codecs.get(i)).value = codecs.get(i).value;
TrackableObject o = destination.persistentObjects.get(i); TrackableObject<?> a = persistentObjects.get(i);
if (o != null) { TrackableObject<?> b = destination.persistentObjects.get(i);
o.readTrackedNbt((NbtCompound)codecs.get(i).value); if (a != null && b != null) {
((TrackableObject)a).copyTo(b);
} }
} }
} }
@ -89,18 +70,17 @@ public class DataTracker {
synchronized Optional<MsgTrackedValues.TrackerEntries> getInitialPairs() { synchronized Optional<MsgTrackedValues.TrackerEntries> getInitialPairs() {
initial = false; initial = false;
dirtyIndices = new IntOpenHashSet(); dirtyIndices = new IntOpenHashSet();
updateTrackables(); return Optional.of(new MsgTrackedValues.TrackerEntries(id, true, codecs, writePersistentObjects(true)));
return Optional.of(new MsgTrackedValues.TrackerEntries(id, true, codecs));
} }
synchronized Optional<MsgTrackedValues.TrackerEntries> getDirtyPairs() { public synchronized Optional<MsgTrackedValues.TrackerEntries> getDirtyPairs() {
if (initial) { if (initial) {
return getInitialPairs(); return getInitialPairs();
} }
updateTrackables(); Map<Integer, PacketByteBuf> updates = writePersistentObjects(false);
if (dirtyIndices.isEmpty()) { if (dirtyIndices.isEmpty() && updates.isEmpty()) {
return Optional.empty(); return Optional.empty();
} }
@ -110,32 +90,40 @@ public class DataTracker {
for (int i : toSend) { for (int i : toSend) {
pairs.add(codecs.get(i)); pairs.add(codecs.get(i));
} }
return Optional.of(new MsgTrackedValues.TrackerEntries(id, false, pairs)); return Optional.of(new MsgTrackedValues.TrackerEntries(id, false, pairs, updates));
} }
synchronized void load(MsgTrackedValues.TrackerEntries values) { private Map<Integer, PacketByteBuf> writePersistentObjects(boolean initial) {
Map<Integer, PacketByteBuf> updates = new HashMap<>();
for (int i = 0; i < persistentObjects.size(); i++) {
TrackableObject<?> o = persistentObjects.get(i);
TrackableObject.Status status = initial ? TrackableObject.Status.NEW : o.getStatus();
int id = i;
o.write(status).ifPresent(data -> updates.put(id, data));
}
return updates;
}
public synchronized void load(MsgTrackedValues.TrackerEntries values) {
if (values.wipe()) { if (values.wipe()) {
codecs.clear(); codecs.clear();
codecs.addAll(values.values()); 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 { } else {
for (var value : values.values()) { for (var value : values.values()) {
if (value.id >= 0 && value.id < codecs.size()) { if (value.id >= 0 && value.id < codecs.size()) {
if (codecs.get(value.id).type == value.type) { if (codecs.get(value.id).type == value.type) {
codecs.set(value.id, value); codecs.set(value.id, value);
TrackableObject o = persistentObjects.get(value.id);
if (o != null) {
o.readTrackedNbt((NbtCompound)value.value);
}
} }
} }
} }
} }
for (var entry : values.objects().entrySet()) {
TrackableObject<?> o = persistentObjects.get(entry.getKey());
if (o != null) {
o.read(entry.getValue());
}
}
} }
public record Entry<T>(DataTracker tracker, int id) { public record Entry<T>(DataTracker tracker, int id) {

View file

@ -41,7 +41,7 @@ public class DataTrackerManager {
} }
public synchronized DataTracker checkoutTracker() { public synchronized DataTracker checkoutTracker() {
DataTracker tracker = new DataTracker(this, trackers.size()); DataTracker tracker = new DataTracker(trackers.size());
trackers.add(tracker); trackers.add(tracker);
packetEmitters.add((sender, initial) -> { packetEmitters.add((sender, initial) -> {
var update = initial ? tracker.getInitialPairs() : tracker.getDirtyPairs(); var update = initial ? tracker.getInitialPairs() : tracker.getDirtyPairs();
@ -56,7 +56,7 @@ public class DataTrackerManager {
return tracker; return tracker;
} }
public synchronized <T extends TrackableObject> ObjectTracker<T> checkoutTracker(Supplier<T> objFunction) { public synchronized <T extends TrackableObject<T>> ObjectTracker<T> checkoutTracker(Supplier<T> objFunction) {
ObjectTracker<T> tracker = new ObjectTracker<>(objectTrackers.size(), objFunction); ObjectTracker<T> tracker = new ObjectTracker<>(objectTrackers.size(), objFunction);
objectTrackers.add(tracker); objectTrackers.add(tracker);
packetEmitters.add((sender, initial) -> { packetEmitters.add((sender, initial) -> {

View file

@ -14,7 +14,6 @@ import com.sollace.fabwork.api.packets.HandledPacket;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketByteBuf;
public record MsgTrackedValues( public record MsgTrackedValues(
@ -45,12 +44,12 @@ public record MsgTrackedValues(
} }
} }
public record TrackerObjects(int id, Set<UUID> removedValues, Map<UUID, NbtCompound> values) { public record TrackerObjects(int id, Set<UUID> removedValues, Map<UUID, PacketByteBuf> values) {
public TrackerObjects(PacketByteBuf buffer) { public TrackerObjects(PacketByteBuf buffer) {
this( this(
buffer.readInt(), buffer.readInt(),
buffer.readCollection(HashSet::new, PacketByteBuf::readUuid), buffer.readCollection(HashSet::new, PacketByteBuf::readUuid),
buffer.readMap(HashMap::new, PacketByteBuf::readUuid, PacketCodec.COMPRESSED_NBT::read) buffer.readMap(HashMap::new, PacketByteBuf::readUuid, PacketCodec.RAW_BYTES::read)
); );
} }
@ -58,19 +57,25 @@ public record MsgTrackedValues(
public void write(PacketByteBuf buffer) { public void write(PacketByteBuf buffer) {
buffer.writeInt(id); buffer.writeInt(id);
buffer.writeCollection(removedValues, PacketByteBuf::writeUuid); buffer.writeCollection(removedValues, PacketByteBuf::writeUuid);
buffer.writeMap(values, PacketByteBuf::writeUuid, PacketCodec.COMPRESSED_NBT::write); buffer.writeMap(values, PacketByteBuf::writeUuid, PacketCodec.RAW_BYTES::write);
} }
} }
public record TrackerEntries(int id, boolean wipe, List<DataTracker.Pair<?>> values) { public record TrackerEntries(int id, boolean wipe, List<DataTracker.Pair<?>> values, Map<Integer, PacketByteBuf> objects) {
public TrackerEntries(PacketByteBuf buffer) { public TrackerEntries(PacketByteBuf buffer) {
this(buffer.readInt(), buffer.readBoolean(), buffer.readCollection(ArrayList::new, DataTracker.Pair::new)); this(
buffer.readInt(),
buffer.readBoolean(),
buffer.readCollection(ArrayList::new, DataTracker.Pair::new),
buffer.readMap(PacketByteBuf::readInt, PacketCodec.RAW_BYTES::read)
);
} }
public void write(PacketByteBuf buffer) { public void write(PacketByteBuf buffer) {
buffer.writeInt(id); buffer.writeInt(id);
buffer.writeBoolean(wipe); buffer.writeBoolean(wipe);
buffer.writeCollection(values, (buf, pair) -> pair.write(buf)); buffer.writeCollection(values, (buf, pair) -> pair.write(buf));
buffer.writeMap(objects, PacketByteBuf::writeInt, PacketCodec.RAW_BYTES::write);
} }
} }
} }

View file

@ -11,14 +11,11 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.network.track.TrackableObject.Status;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.nbt.NbtCompound; import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.Util;
public class ObjectTracker<T extends TrackableObject> implements NbtSerialisable { public class ObjectTracker<T extends TrackableObject<T>> {
private final Map<UUID, T> trackedObjects = new Object2ObjectOpenHashMap<>(); private final Map<UUID, T> trackedObjects = new Object2ObjectOpenHashMap<>();
private volatile Map<UUID, T> quickAccess = Map.of(); private volatile Map<UUID, T> quickAccess = Map.of();
@ -30,6 +27,10 @@ public class ObjectTracker<T extends TrackableObject> implements NbtSerialisable
this.constructor = constructor; this.constructor = constructor;
} }
public Map<UUID, T> entries() {
return quickAccess;
}
public Set<UUID> keySet() { public Set<UUID> keySet() {
return quickAccess.keySet(); return quickAccess.keySet();
} }
@ -68,15 +69,15 @@ public class ObjectTracker<T extends TrackableObject> implements NbtSerialisable
return true; return true;
} }
public synchronized void add(T obj) { public synchronized void add(UUID id, T obj) {
trackedObjects.put(obj.getUuid(), obj); trackedObjects.put(id, obj);
quickAccess = Map.copyOf(trackedObjects); quickAccess = Map.copyOf(trackedObjects);
} }
synchronized void copyTo(ObjectTracker<T> destination) { synchronized void copyTo(ObjectTracker<T> destination) {
for (var entry : trackedObjects.entrySet()) { for (var entry : trackedObjects.entrySet()) {
T copy = destination.constructor.get(); T copy = destination.constructor.get();
copy.readTrackedNbt(entry.getValue().toTrackedNbt()); entry.getValue().copyTo(copy);
destination.trackedObjects.put(entry.getKey(), copy); destination.trackedObjects.put(entry.getKey(), copy);
} }
destination.quickAccess = Map.copyOf(destination.trackedObjects); destination.quickAccess = Map.copyOf(destination.trackedObjects);
@ -87,31 +88,35 @@ public class ObjectTracker<T extends TrackableObject> implements NbtSerialisable
return Optional.empty(); return Optional.empty();
} }
Map<UUID, NbtCompound> trackableCompounds = new HashMap<>(); Map<UUID, PacketByteBuf> updates = new HashMap<>();
quickAccess.entrySet().forEach(object -> { quickAccess.entrySet().forEach(object -> {
trackableCompounds.put(object.getKey(), object.getValue().toTrackedNbt()); object.getValue().write(Status.NEW).ifPresent(data -> {
updates.put(object.getKey(), data);
});
}); });
return Optional.of(new MsgTrackedValues.TrackerObjects(id, Set.of(), trackableCompounds)); return Optional.of(new MsgTrackedValues.TrackerObjects(id, Set.of(), updates));
} }
synchronized Optional<MsgTrackedValues.TrackerObjects> getDirtyPairs() { synchronized Optional<MsgTrackedValues.TrackerObjects> getDirtyPairs() {
if (!trackedObjects.isEmpty()) { if (!trackedObjects.isEmpty()) {
Map<UUID, NbtCompound> trackableCompounds = new HashMap<>(); Map<UUID, PacketByteBuf> updates = new HashMap<>();
Set<UUID> removedTrackableObjects = new HashSet<>(); Set<UUID> removedTrackableObjects = new HashSet<>();
trackedObjects.entrySet().removeIf(object -> { trackedObjects.entrySet().removeIf(object -> {
TrackableObject.Status status = object.getValue().getStatus(); TrackableObject.Status status = object.getValue().getStatus();
if (status == TrackableObject.Status.REMOVED) { if (status == TrackableObject.Status.REMOVED) {
removedTrackableObjects.add(object.getKey()); removedTrackableObjects.add(object.getKey());
} else if (status != TrackableObject.Status.DEFAULT) { return true;
trackableCompounds.put(object.getKey(), object.getValue().toTrackedNbt());
} }
return status == TrackableObject.Status.REMOVED; object.getValue().write(status).ifPresent(data -> {
updates.put(object.getKey(), data);
});
return false;
}); });
quickAccess = Map.copyOf(trackedObjects); quickAccess = Map.copyOf(trackedObjects);
if (!trackableCompounds.isEmpty() || !removedTrackableObjects.isEmpty()) { if (!updates.isEmpty() || !removedTrackableObjects.isEmpty()) {
return Optional.of(new MsgTrackedValues.TrackerObjects(id, removedTrackableObjects, trackableCompounds)); return Optional.of(new MsgTrackedValues.TrackerObjects(id, removedTrackableObjects, updates));
} }
} }
@ -125,44 +130,18 @@ public class ObjectTracker<T extends TrackableObject> implements NbtSerialisable
o.discard(true); o.discard(true);
} }
}); });
objects.values().forEach((id, nbt) -> { objects.values().forEach((id, data) -> {
T o = trackedObjects.get(id); T o = trackedObjects.get(id);
if (o == null) { if (o == null) {
o = constructor.get(); o = constructor.get();
trackedObjects.put(id, o); trackedObjects.put(id, o);
} }
o.readTrackedNbt(nbt); o.read(data);
}); });
quickAccess = Map.copyOf(trackedObjects); quickAccess = Map.copyOf(trackedObjects);
} }
@Override public void load(Map<UUID, T> values) {
public synchronized void toNBT(NbtCompound compound) {
quickAccess.forEach((id, value) -> {
compound.put(id.toString(), value.toTrackedNbt());
});
}
@Override
public void fromNBT(NbtCompound compound) {
Map<UUID, T> values = new Object2ObjectOpenHashMap<>();
compound.getKeys().forEach(key -> {
try {
UUID id = Util.NIL_UUID;
try {
id = UUID.fromString(key);
} catch (Throwable ignore) {}
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.getMessage());
}
});
synchronized (this) { synchronized (this) {
trackedObjects.clear(); trackedObjects.clear();
trackedObjects.putAll(values); trackedObjects.putAll(values);

View file

@ -23,6 +23,7 @@ public record TrackableDataType<T>(int id, PacketCodec<T> codec) {
public static final TrackableDataType<UUID> UUID = of(new Identifier("uuid"), PacketCodec.UUID); public static final TrackableDataType<UUID> UUID = of(new Identifier("uuid"), PacketCodec.UUID);
public static final TrackableDataType<NbtCompound> NBT = of(new Identifier("nbt"), PacketCodec.NBT); public static final TrackableDataType<NbtCompound> NBT = of(new Identifier("nbt"), PacketCodec.NBT);
public static final TrackableDataType<NbtCompound> COMPRESSED_NBT = of(new Identifier("compressed_nbt"), PacketCodec.COMPRESSED_NBT); public static final TrackableDataType<NbtCompound> COMPRESSED_NBT = of(new Identifier("compressed_nbt"), PacketCodec.COMPRESSED_NBT);
public static final TrackableDataType<Optional<PacketByteBuf>> RAW_BYTES = of(new Identifier("raw_bytes"), PacketCodec.RAW_BYTES.asOptional());
public static final TrackableDataType<Optional<BlockPos>> OPTIONAL_POS = of(new Identifier("optional_pos"), PacketCodec.OPTIONAL_POS); public static final TrackableDataType<Optional<BlockPos>> OPTIONAL_POS = of(new Identifier("optional_pos"), PacketCodec.OPTIONAL_POS);
public static final TrackableDataType<Race> RACE = TrackableDataType.of(Unicopia.id("race"), PacketCodec.ofRegistry(Race.REGISTRY)); public static final TrackableDataType<Race> RACE = TrackableDataType.of(Unicopia.id("race"), PacketCodec.ofRegistry(Race.REGISTRY));

View file

@ -1,20 +1,37 @@
package com.minelittlepony.unicopia.network.track; package com.minelittlepony.unicopia.network.track;
import java.util.UUID; import java.util.Optional;
import com.minelittlepony.unicopia.util.serialization.PacketCodec;
import io.netty.buffer.Unpooled;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
public interface TrackableObject { public interface TrackableObject<T extends TrackableObject<T>> {
UUID getUuid();
Status getStatus(); Status getStatus();
NbtCompound toTrackedNbt(); default void read(PacketByteBuf buffer) {
readTrackedNbt(PacketCodec.COMPRESSED_NBT.read(buffer));
}
void readTrackedNbt(NbtCompound compound); default Optional<PacketByteBuf> write(Status status) {
if (status == Status.NEW || status == Status.UPDATED) {
PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer());
PacketCodec.COMPRESSED_NBT.write(buffer, writeTrackedNbt());
return Optional.of(buffer);
}
return Optional.empty();
}
void readTrackedNbt(NbtCompound nbt);
NbtCompound writeTrackedNbt();
void discard(boolean immediate); void discard(boolean immediate);
void copyTo(T destination);
public enum Status { public enum Status {
DEFAULT, DEFAULT,
NEW, NEW,

View file

@ -37,7 +37,7 @@ public class Ether extends PersistentState {
this.world = world; this.world = world;
this.endpoints = NbtSerialisable.readMap(compound.getCompound("endpoints"), Identifier::tryParse, typeNbt -> { this.endpoints = NbtSerialisable.readMap(compound.getCompound("endpoints"), Identifier::tryParse, typeNbt -> {
return NbtSerialisable.readMap((NbtCompound)typeNbt, UUID::fromString, entityNbt -> { return NbtSerialisable.readMap((NbtCompound)typeNbt, UUID::fromString, entityNbt -> {
return NbtSerialisable.readMap((NbtCompound)entityNbt, UUID::fromString, Entry::new); return NbtSerialisable.readMap((NbtCompound)entityNbt, UUID::fromString, nbt -> new Entry<>(nbt));
}); });
}); });
} }

View file

@ -69,14 +69,31 @@ public interface NbtSerialisable {
} }
static <K, V> Map<K, V> readMap(NbtCompound nbt, Function<String, K> keyFunction, Function<NbtElement, V> valueFunction) { static <K, V> Map<K, V> readMap(NbtCompound nbt, Function<String, K> keyFunction, Function<NbtElement, V> valueFunction) {
return nbt.getKeys().stream().collect(Collectors.toMap(keyFunction, k -> valueFunction.apply(nbt.get(k)))); return readMap(nbt, keyFunction, (k, v) -> valueFunction.apply(v));
}
static <K, V> Map<K, V> readMap(NbtCompound nbt, Function<String, K> keyFunction, BiFunction<K, NbtElement, V> valueFunction) {
return nbt.getKeys().stream().map(k -> {
K key = keyFunction.apply(k);
if (key == null) {
return null;
}
V value = valueFunction.apply(key, nbt.get(k));
if (value == null) {
return null;
}
return Map.entry(key, value);
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
} }
static <K, V> NbtCompound writeMap(Map<K, V> map, Function<K, String> keyFunction, Function<V, ? extends NbtElement> valueFunction) { static <K, V> NbtCompound writeMap(Map<K, V> map, Function<K, String> keyFunction, Function<V, ? extends NbtElement> valueFunction) {
NbtCompound nbt = new NbtCompound(); return writeMap(new NbtCompound(), map, keyFunction, valueFunction);
map.forEach((k, v) -> { }
nbt.put(keyFunction.apply(k), valueFunction.apply(v));
}); static <K, V> NbtCompound writeMap(NbtCompound nbt, Map<K, V> map, Function<K, String> keyFunction, Function<V, ? extends NbtElement> valueFunction) {
map.forEach((k, v) -> nbt.put(keyFunction.apply(k), valueFunction.apply(v)));
return nbt; return nbt;
} }