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;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Stream;
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.SpellReference;
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.Trackable;
import com.minelittlepony.unicopia.network.track.TrackableObject;
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.network.PacketByteBuf;
/**
* Container for multiple spells
@ -21,7 +28,7 @@ import net.minecraft.nbt.NbtCompound;
*/
class MultiSpellSlot implements SpellSlots, NbtSerialisable {
private final Caster<?> owner;
private final ObjectTracker<Entry<?>> tracker;
private final ObjectTracker<Entry<Spell>> tracker;
public MultiSpellSlot(Caster<?> owner) {
this.owner = owner;
@ -41,7 +48,7 @@ class MultiSpellSlot implements SpellSlots, NbtSerialisable {
@Override
public void put(@Nullable Spell effect) {
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
public void toNBT(NbtCompound compound) {
compound.put("spells", tracker.toNBT());
compound.put("spells", NbtSerialisable.writeMap(tracker.entries(), UUID::toString, entry -> entry.spell.toNBT()));
}
@Override
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;
final SpellReference<T> spell = new SpellReference<>();
private Status status = Status.NEW;
private boolean hasValue;
public Entry(Caster<?> owner) {
this.owner = owner;
@ -99,34 +120,63 @@ class MultiSpellSlot implements SpellSlots, NbtSerialisable {
}
}
@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;
boolean hasValue = spell.get() != null;
if (hasValue != this.hasValue) {
this.hasValue = hasValue;
return hasValue ? Status.NEW : Status.REMOVED;
}
return spell.hasDirtySpell() ? Status.UPDATED : Status.DEFAULT;
}
@Override
public NbtCompound toTrackedNbt() {
public void readTrackedNbt(NbtCompound nbt) {
spell.fromNBT(nbt);
}
@Override
public NbtCompound writeTrackedNbt() {
return spell.toNBT();
}
@Override
public void readTrackedNbt(NbtCompound compound) {
spell.fromNBT(compound);
public void read(PacketByteBuf buffer) {
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
public void toNBT(NbtCompound compound) {
compound.put("effect", entry.toTrackedNbt());
compound.put("effect", entry.spell.toNBT());
}
@Override
public void fromNBT(NbtCompound compound) {
entry.readTrackedNbt(compound.getCompound("effect"));
entry.spell.fromNBT(compound.getCompound("effect"));
}
@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.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.server.world.Ether;
import net.minecraft.nbt.NbtCompound;
public abstract class AbstractDelegatingSpell implements Spell {
private boolean dirty;
private boolean hidden;
private boolean destroyed;
private UUID uuid = UUID.randomUUID();
private final CustomisedSpellType<?> type;
@ -25,10 +22,20 @@ public abstract class AbstractDelegatingSpell implements Spell {
this.type = type;
}
public AbstractDelegatingSpell(CustomisedSpellType<?> type, Spell delegate) {
this.type = type;
this.delegate.set(delegate);
}
public final Spell getDelegate() {
return delegate.get();
}
@Override
public final DataTracker getDataTracker() {
return getOrEmpty().getDataTracker();
}
private Spell getOrEmpty() {
return MoreObjects.firstNonNull(delegate.get(), EmptySpell.INSTANCE);
}
@ -73,19 +80,21 @@ public abstract class AbstractDelegatingSpell implements Spell {
return getOrEmpty().isDying();
}
@Deprecated
@Override
public boolean isDirty() {
return dirty || delegate.hasDirtySpell();
return delegate.hasDirtySpell();
}
@Deprecated
@Override
public void setDirty() {
dirty = true;
getOrEmpty().setDirty();
}
@Override
public boolean isHidden() {
return hidden || getOrEmpty().isHidden();
return getOrEmpty().isHidden();
}
@Override
@ -95,15 +104,6 @@ public abstract class AbstractDelegatingSpell implements Spell {
@Override
public final void destroy(Caster<?> caster) {
if (destroyed) {
return;
}
destroyed = true;
setDead();
onDestroyed(caster);
}
protected void onDestroyed(Caster<?> caster) {
if (!caster.isClient()) {
Ether.get(caster.asWorld()).remove(this, caster);
}
@ -123,14 +123,11 @@ public abstract class AbstractDelegatingSpell implements Spell {
@Override
public void toNBT(NbtCompound compound) {
compound.putUuid("uuid", uuid);
compound.putBoolean("hidden", hidden);
compound.put("spell", delegate.toNBT());
}
@Override
public void fromNBT(NbtCompound compound) {
dirty = false;
hidden = compound.getBoolean("hidden");
if (compound.contains("uuid")) {
uuid = compound.getUuid("uuid");
}
@ -139,6 +136,6 @@ public abstract class AbstractDelegatingSpell implements Spell {
@Override
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.entity.behaviour.EntityAppearance;
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.UParticles;
import net.minecraft.entity.Entity;
@ -21,6 +23,7 @@ import net.minecraft.nbt.NbtCompound;
*/
public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements IllusionarySpell {
private final DataTracker.Entry<Boolean> suppressed = dataTracker.startTracking(TrackableDataType.BOOLEAN, false);
private int suppressionCounter;
public DispersableDisguiseSpell(CustomisedSpellType<?> type) {
@ -37,7 +40,7 @@ public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements I
public void onSuppressed(Caster<?> otherSource, float time) {
time /= getTraits().getOrDefault(Trait.STRENGTH, 1);
suppressionCounter = (int)time;
setDirty();
suppressed.set(true);
}
@Override
@ -64,7 +67,9 @@ public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements I
Entity appearance = getDisguise().getAppearance();
if (isSuppressed()) {
suppressionCounter--;
if (--suppressionCounter <= 0) {
suppressed.set(false);
}
owner.setInvisible(false);
if (source instanceof Pony) {
@ -92,6 +97,9 @@ public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements I
public void fromNBT(NbtCompound compound) {
super.fromNBT(compound);
suppressionCounter = compound.getInt("suppressionCounter");
if (suppressionCounter > 0) {
suppressed.set(true);
}
}
@Override

View file

@ -5,6 +5,7 @@ import java.util.UUID;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.network.track.DataTracker;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.util.Util;
@ -74,4 +75,9 @@ public final class EmptySpell implements Spell {
public String toString() {
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.spell.effect.AbstractSpell;
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.server.world.Ether;
import com.minelittlepony.unicopia.util.NbtSerialisable;
@ -34,8 +35,8 @@ public class PlacementControlSpell extends AbstractSpell implements OrientedSpel
super(type);
}
PlacementControlSpell(CustomisedSpellType<?> type, Spell delegate) {
this(type);
PlacementControlSpell(Spell delegate) {
this(SpellType.PLACE_CONTROL_SPELL.withTraits(delegate.getTypeAndTraits().traits()));
this.delegate = delegate;
}
@ -50,10 +51,12 @@ public class PlacementControlSpell extends AbstractSpell implements OrientedSpel
public void setDimension(RegistryKey<World> dimension) {
this.dimension = Optional.of(dimension);
setDirty();
}
public void setPosition(Vec3d position) {
this.position = Optional.of(position);
setDirty();
}
@Override
@ -98,7 +101,6 @@ public class PlacementControlSpell extends AbstractSpell implements OrientedSpel
entity.getWorld().spawnEntity(entity);
placedEntityId = entity.getUuid();
setDirty();
}
return result;
}

View file

@ -54,7 +54,6 @@ public class RageAbilitySpell extends AbstractSpell {
ticksExtenguishing++;
source.playSound(USounds.Vanilla.ENTITY_GENERIC_EXTINGUISH_FIRE, 1);
source.spawnParticles(ParticleTypes.CLOUD, 12);
setDirty();
} else {
ticksExtenguishing = 0;
}
@ -127,9 +126,6 @@ public class RageAbilitySpell extends AbstractSpell {
age++;
source.asEntity().setInvulnerable(age < 25);
setDirty();
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.spell.effect.CustomisedSpellType;
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.util.NbtSerialisable;
@ -30,6 +31,8 @@ public interface Spell extends NbtSerialisable, Affine {
*/
CustomisedSpellType<?> getTypeAndTraits();
DataTracker getDataTracker();
default boolean isOf(SpellType<?> 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.
*/
@Deprecated
boolean isDirty();
/**
* Marks this effect as dirty.
*/
@Deprecated
void setDirty();
/**
* Applies this spell to the supplied caster.
* @param caster The caster to apply the spell to
@ -110,11 +120,6 @@ public interface Spell extends NbtSerialisable, Affine {
*/
void tickDying(Caster<?> caster);
/**
* Marks this effect as dirty.
*/
void setDirty();
boolean isHidden();
void setHidden(boolean hidden);
@ -128,7 +133,7 @@ public interface Spell extends NbtSerialisable, Affine {
* Converts this spell into a placeable spell.
*/
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
*/
default ThrowableSpell toThrowable() {
return SpellType.THROWN_SPELL.withTraits().create().setSpell(this);
return new ThrowableSpell(this);
}
@Nullable

View file

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

View file

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

View file

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

View file

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

View file

@ -40,8 +40,6 @@ public class AwkwardSpell extends AbstractSpell implements TimedSpell {
if (timer.getTicksRemaining() <= 0) {
return false;
}
setDirty();
}
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.mob.UEntityAttributes;
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.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
@ -55,15 +57,15 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
private final Timer timer;
private int struggles;
private float prevRadius;
private float radius;
private DataTracker.Entry<Float> radius;
private DataTracker.Entry<Integer> struggles;
protected BubbleSpell(CustomisedSpellType<?> type) {
super(type);
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
@ -72,7 +74,7 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
}
public float getRadius(float tickDelta) {
return MathHelper.lerp(tickDelta, prevRadius, radius);
return MathHelper.lerp(tickDelta, prevRadius, radius.get());
}
@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);
entity.addVelocity(0, 0.2F * source.getPhysics().getGravitySignum(), 0);
Living.updateVelocity(entity);
@ -111,7 +113,7 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
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);
});
@ -119,8 +121,6 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
return false;
}
setDirty();
source.asEntity().addVelocity(
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;
prevRadius = radius;
prevRadius = radius.get();
if (source instanceof Pony pony && pony.sneakingChanged() && pony.asEntity().isSneaking()) {
setDirty();
radius += 0.5F;
radius.set(radius.get() + 0.5F);
source.playSound(USounds.SPELL_BUBBLE_DISTURB, 1);
if (struggles-- <= 0) {
int s = struggles.get() - 1;
struggles.set(s);
if (s <= 0) {
setDead();
return false;
}
@ -168,16 +169,16 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell,
@Override
public void toNBT(NbtCompound compound) {
super.toNBT(compound);
compound.putInt("struggles", struggles);
compound.putFloat("radius", radius);
compound.putInt("struggles", struggles.get());
compound.putFloat("radius", radius.get());
timer.toNBT(compound);
}
@Override
public void fromNBT(NbtCompound compound) {
super.fromNBT(compound);
struggles = compound.getInt("struggles");
radius = compound.getFloat("radius");
struggles.set(compound.getInt("struggles"));
radius.set(compound.getFloat("radius"));
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.damage.UDamageTypes;
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.LightningBoltParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleUtils;
@ -56,7 +58,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
.with(Trait.DARKNESS, 100)
.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);
@ -70,7 +72,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
// 3. force reaches 0 at distance of drawDropOffRange
private double getMass() {
return 0.1F + accumulatedMass / 10F;
return 0.1F + accumulatedMass.get() / 10F;
}
public double getEventHorizonRadius() {
@ -174,11 +176,12 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
@Override
public void tickDying(Caster<?> source) {
accumulatedMass -= 0.8F;
float m = accumulatedMass.get() - 0.8F;
accumulatedMass.set(m);
double mass = getMass() * 0.1;
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);
if (accumulatedMass < 1) {
if (m < 1) {
super.tickDying(source);
}
@ -202,7 +205,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
@Override
public boolean isFriendlyTogether(Affine other) {
return accumulatedMass < 4;
return accumulatedMass.get() < 4;
}
private boolean isValidTarget(Caster<?> source, Entity entity) {
@ -274,8 +277,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
double massOfTarget = AttractionUtils.getMass(target);
if (!source.isClient() && massOfTarget != 0) {
accumulatedMass += massOfTarget;
setDirty();
accumulatedMass.set((float)(accumulatedMass.get() + massOfTarget));
}
target.damage(source.damageOf(UDamageTypes.GAVITY_WELL_RECOIL, source), Integer.MAX_VALUE);
@ -303,12 +305,12 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate
@Override
public void toNBT(NbtCompound compound) {
super.toNBT(compound);
compound.putFloat("accumulatedMass", accumulatedMass);
compound.putFloat("accumulatedMass", accumulatedMass.get());
}
@Override
public void fromNBT(NbtCompound 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;
}
setDirty();
List<Entity> targets = getTargets(caster).toList();
if (targets.isEmpty()) {

View file

@ -63,8 +63,6 @@ public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileD
return false;
}
setDirty();
if (!caster.isClient()) {
if (lights.isEmpty()) {
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);
ref.set(entity);
setDirty();
}
});
}

View file

@ -35,8 +35,6 @@ public class MimicSpell extends AbstractDisguiseSpell implements HomingSpell, Ti
return false;
}
setDirty();
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);
});
initialized = true;
setDirty();
}
if (counterpart.isSet()) {
@ -143,13 +142,12 @@ public class MindSwapSpell extends MimicSpell implements ProjectileDelegate.Enti
if (other == null) {
caster.getOriginatingCaster().asEntity().damage(caster.asWorld().getDamageSources().magic(), Float.MAX_VALUE);
setDead();
destroy(caster);
return false;
}
if (!Caster.of(other).get().getSpellSlot().contains(SpellType.MIMIC)) {
onDestroyed(caster);
setDead();
destroy(caster);
return false;
}
}
@ -158,8 +156,7 @@ public class MindSwapSpell extends MimicSpell implements ProjectileDelegate.Enti
counterpart.ifPresent(caster.asWorld(), e -> {
e.damage(e.getDamageSources().magic(), Float.MAX_VALUE);
});
onDestroyed(caster);
setDead();
destroy(caster);
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);
setDirty();
if (--spawnCountdown > 0 && !summonedEntities.isEmpty()) {
return true;
}
@ -213,7 +212,6 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti
source.asWorld().spawnEntity(minion);
summonedEntities.add(new EntityReference<>(minion));
setDirty();
}
@Override

View file

@ -14,6 +14,8 @@ import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.network.Channel;
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.server.world.Ether;
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.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Util;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
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);
@Nullable
private UUID targetPortalId;
private float targetPortalPitch;
private float targetPortalYaw;
private final DataTracker.Entry<UUID> targetPortalId = dataTracker.startTracking(TrackableDataType.UUID, Util.NIL_UUID);
private final DataTracker.Entry<Float> targetPortalPitch = dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
private final DataTracker.Entry<Float> targetPortalYaw = dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
private final EntityReference<Entity> teleportationTarget = new EntityReference<>();
private boolean publishedPosition;
private float pitch;
private float yaw;
private final DataTracker.Entry<Float> pitch = dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
private final DataTracker.Entry<Float> yaw = dataTracker.startTracking(TrackableDataType.FLOAT, 0F);
private Shape particleArea = PARTICLE_AREA;
@ -61,30 +62,30 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
}
public float getPitch() {
return pitch;
return pitch.get();
}
public float getYaw() {
return yaw;
return yaw.get();
}
public float getTargetPitch() {
return targetPortalPitch;
return targetPortalPitch.get();
}
public float getTargetYaw() {
return targetPortalYaw;
return targetPortalYaw.get();
}
public float getYawDifference() {
return MathHelper.wrapDegrees(180 + targetPortalYaw - yaw);
return MathHelper.wrapDegrees(180 + getTargetYaw() - getYaw());
}
@SuppressWarnings("unchecked")
private Ether.Entry<PortalSpell> getDestination(Caster<?> source) {
return targetPortalId == null ? null : getDestinationReference()
return Util.NIL_UUID.equals(targetPortalId.get()) ? null : getDestinationReference()
.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()))
.orElse(null);
}
@ -94,6 +95,18 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
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
public boolean tick(Caster<?> source, Situation situation) {
if (situation == Situation.GROUND) {
@ -108,9 +121,7 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
if (targetEntry == null) {
if (teleportationTarget.isSet()) {
teleportationTarget.set(null);
targetPortalId = null;
setDirty();
setDestination(null);
source.asWorld().syncWorldEvent(WorldEvents.BLOCK_BROKEN, source.getOrigin(), Block.getRawIdFromState(Blocks.GLASS.getDefaultState()));
} else {
Ether.get(source.asWorld()).anyMatch(getType(), entry -> {
@ -119,18 +130,10 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
ownEntry.claim(entry.getSpellId());
synchronized (entry) {
if (entry.getSpell() instanceof PortalSpell portal) {
portal.teleportationTarget.copyFrom(ownEntry.entity);
portal.targetPortalId = getUuid();
portal.targetPortalPitch = pitch;
portal.targetPortalYaw = yaw;
portal.setDirty();
portal.setDestination(ownEntry);
}
}
teleportationTarget.copyFrom(entry.entity);
targetPortalId = entry.getSpellId();
targetPortalPitch = entry.getPitch();
targetPortalYaw = entry.getYaw();
setDirty();
setDestination(entry);
}
return false;
});
@ -142,8 +145,8 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
}
var entry = Ether.get(source.asWorld()).getOrCreate(this, source);
entry.setPitch(pitch);
entry.setYaw(yaw);
entry.setPitch(pitch.get());
entry.setYaw(yaw.get());
}
return !isDead();
@ -154,7 +157,7 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
source.findAllEntitiesInRange(1).forEach(entity -> {
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) {
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.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);
setDirty();
Living.updateVelocity(entity);
@ -193,13 +195,12 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
@Override
public void setOrientation(Caster<?> caster, float pitch, float yaw) {
this.pitch = 90 - pitch;
this.yaw = -yaw;
this.pitch.set(90 - pitch);
this.yaw.set(-yaw);
particleArea = PARTICLE_AREA.rotate(
this.pitch * MathHelper.RADIANS_PER_DEGREE,
this.yaw * MathHelper.RADIANS_PER_DEGREE
this.pitch.get() * MathHelper.RADIANS_PER_DEGREE,
(180 - this.yaw.get()) * MathHelper.RADIANS_PER_DEGREE
);
setDirty();
}
@Override
@ -227,30 +228,26 @@ public class PortalSpell extends AbstractSpell implements PlacementControlSpell.
@Override
public void toNBT(NbtCompound compound) {
super.toNBT(compound);
if (targetPortalId != null) {
compound.putUuid("targetPortalId", targetPortalId);
}
compound.putBoolean("publishedPosition", publishedPosition);
compound.putUuid("targetPortalId", targetPortalId.get());
compound.put("teleportationTarget", teleportationTarget.toNBT());
compound.putFloat("pitch", pitch);
compound.putFloat("yaw", yaw);
compound.putFloat("targetPortalPitch", targetPortalPitch);
compound.putFloat("targetPortalYaw", targetPortalYaw);
compound.putFloat("pitch", getPitch());
compound.putFloat("yaw", getYaw());
compound.putFloat("targetPortalPitch", getTargetPitch());
compound.putFloat("targetPortalYaw", getTargetYaw());
}
@Override
public void fromNBT(NbtCompound compound) {
super.fromNBT(compound);
targetPortalId = compound.containsUuid("targetPortalId") ? compound.getUuid("targetPortalId") : null;
publishedPosition = compound.getBoolean("publishedPosition");
targetPortalId.set(compound.containsUuid("targetPortalId") ? compound.getUuid("targetPortalId") : Util.NIL_UUID);
teleportationTarget.fromNBT(compound.getCompound("teleportationTarget"));
pitch = compound.getFloat("pitch");
yaw = compound.getFloat("yaw");
targetPortalPitch = compound.getFloat("targetPortalPitch");
targetPortalYaw = compound.getFloat("targetPortalYaw");
pitch.set(compound.getFloat("pitch"));
yaw.set(compound.getFloat("yaw"));
targetPortalPitch.set(compound.getFloat("targetPortalPitch"));
targetPortalYaw.set(compound.getFloat("targetPortalYaw"));
particleArea = PARTICLE_AREA.rotate(
pitch * MathHelper.RADIANS_PER_DEGREE,
(180 - yaw) * MathHelper.RADIANS_PER_DEGREE
pitch.get() * 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.entity.damage.UDamageTypes;
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.ParticleUtils;
import com.minelittlepony.unicopia.particle.UParticles;
@ -43,6 +45,7 @@ public class SiphoningSpell extends AbstractAreaEffectSpell {
tooltip.add(SpellAttributes.of(SpellAttributes.RANGE, 4));
}
private final DataTracker.Entry<Boolean> upset = dataTracker.startTracking(TrackableDataType.BOOLEAN, false);
private int ticksUpset;
protected SiphoningSpell(CustomisedSpellType<?> type) {
@ -57,8 +60,8 @@ public class SiphoningSpell extends AbstractAreaEffectSpell {
@Override
public boolean tick(Caster<?> source, Situation situation) {
if (ticksUpset > 0) {
ticksUpset--;
if (ticksUpset > 0 && --ticksUpset <= 0) {
upset.set(false);
}
if (source.isClient()) {
@ -108,7 +111,7 @@ public class SiphoningSpell extends AbstractAreaEffectSpell {
} else {
e.damage(damage, e.getHealth() / 4);
ticksUpset = 100;
setDirty();
upset.set(true);
}
} else {
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) {
super.fromNBT(compound);
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));
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<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.
*/
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);
@SuppressWarnings("unchecked")
@ -122,12 +122,7 @@ public class EntityReference<T extends Entity> implements NbtSerialisable, Track
@Override
public int hashCode() {
return getUuid().hashCode();
}
@Override
public UUID getUuid() {
return getTarget().map(EntityValues::uuid).orElse(Util.NIL_UUID);
return getTarget().map(EntityValues::uuid).orElse(Util.NIL_UUID).hashCode();
}
@Override
@ -140,7 +135,7 @@ public class EntityReference<T extends Entity> implements NbtSerialisable, Track
}
@Override
public NbtCompound toTrackedNbt() {
public NbtCompound writeTrackedNbt() {
return toNBT();
}
@ -149,6 +144,12 @@ public class EntityReference<T extends Entity> implements NbtSerialisable, Track
fromNBT(compound);
}
@Override
public void copyTo(EntityReference<T> destination) {
destination.reference = reference;
destination.directReference = directReference;
}
@Override
public void discard(boolean immediate) {
set(null);

View file

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

View file

@ -1,37 +1,33 @@
package com.minelittlepony.unicopia.network.track;
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 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<Pair<?>> codecs = new ObjectArrayList<>();
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;
final int id;
public DataTracker(DataTrackerManager manager, int id) {
this.manager = manager;
public DataTracker(int id) {
this.id = id;
}
public <T extends TrackableObject> Entry<NbtCompound> startTracking(T value) {
Entry<NbtCompound> entry = startTracking(TrackableDataType.COMPRESSED_NBT, value.toTrackedNbt());
persistentObjects.put(entry.id(), value);
return entry;
public <T extends TrackableObject<T>> T startTracking(T value) {
persistentObjects.add(value);
return value;
}
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) {
if (manager.isClient) {
return;
}
Pair<T> pair = getPair(entry);
if (!Objects.equals(pair.value, value)) {
synchronized (this) {
@ -63,25 +55,14 @@ public class DataTracker {
}
}
@SuppressWarnings("unchecked")
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")
@SuppressWarnings({ "unchecked", "rawtypes" })
synchronized void copyTo(DataTracker destination) {
for (int i = 0; i < codecs.size(); i++) {
((Pair<Object>)destination.codecs.get(i)).value = codecs.get(i).value;
TrackableObject o = destination.persistentObjects.get(i);
if (o != null) {
o.readTrackedNbt((NbtCompound)codecs.get(i).value);
TrackableObject<?> a = persistentObjects.get(i);
TrackableObject<?> b = destination.persistentObjects.get(i);
if (a != null && b != null) {
((TrackableObject)a).copyTo(b);
}
}
}
@ -89,18 +70,17 @@ public class DataTracker {
synchronized Optional<MsgTrackedValues.TrackerEntries> getInitialPairs() {
initial = false;
dirtyIndices = new IntOpenHashSet();
updateTrackables();
return Optional.of(new MsgTrackedValues.TrackerEntries(id, true, codecs));
return Optional.of(new MsgTrackedValues.TrackerEntries(id, true, codecs, writePersistentObjects(true)));
}
synchronized Optional<MsgTrackedValues.TrackerEntries> getDirtyPairs() {
public synchronized Optional<MsgTrackedValues.TrackerEntries> getDirtyPairs() {
if (initial) {
return getInitialPairs();
}
updateTrackables();
Map<Integer, PacketByteBuf> updates = writePersistentObjects(false);
if (dirtyIndices.isEmpty()) {
if (dirtyIndices.isEmpty() && updates.isEmpty()) {
return Optional.empty();
}
@ -110,30 +90,38 @@ public class DataTracker {
for (int i : toSend) {
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()) {
codecs.clear();
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 {
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);
TrackableObject o = persistentObjects.get(value.id);
}
}
}
}
for (var entry : values.objects().entrySet()) {
TrackableObject<?> o = persistentObjects.get(entry.getKey());
if (o != null) {
o.readTrackedNbt((NbtCompound)value.value);
}
}
}
o.read(entry.getValue());
}
}
}

View file

@ -41,7 +41,7 @@ public class DataTrackerManager {
}
public synchronized DataTracker checkoutTracker() {
DataTracker tracker = new DataTracker(this, trackers.size());
DataTracker tracker = new DataTracker(trackers.size());
trackers.add(tracker);
packetEmitters.add((sender, initial) -> {
var update = initial ? tracker.getInitialPairs() : tracker.getDirtyPairs();
@ -56,7 +56,7 @@ public class DataTrackerManager {
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);
objectTrackers.add(tracker);
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.player.PlayerEntity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
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) {
this(
buffer.readInt(),
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) {
buffer.writeInt(id);
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) {
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) {
buffer.writeInt(id);
buffer.writeBoolean(wipe);
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 com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import com.minelittlepony.unicopia.network.track.TrackableObject.Status;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.util.Util;
import net.minecraft.network.PacketByteBuf;
public class ObjectTracker<T extends TrackableObject> implements NbtSerialisable {
public class ObjectTracker<T extends TrackableObject<T>> {
private final Map<UUID, T> trackedObjects = new Object2ObjectOpenHashMap<>();
private volatile Map<UUID, T> quickAccess = Map.of();
@ -30,6 +27,10 @@ public class ObjectTracker<T extends TrackableObject> implements NbtSerialisable
this.constructor = constructor;
}
public Map<UUID, T> entries() {
return quickAccess;
}
public Set<UUID> keySet() {
return quickAccess.keySet();
}
@ -68,15 +69,15 @@ public class ObjectTracker<T extends TrackableObject> implements NbtSerialisable
return true;
}
public synchronized void add(T obj) {
trackedObjects.put(obj.getUuid(), obj);
public synchronized void add(UUID id, T obj) {
trackedObjects.put(id, obj);
quickAccess = Map.copyOf(trackedObjects);
}
synchronized void copyTo(ObjectTracker<T> destination) {
for (var entry : trackedObjects.entrySet()) {
T copy = destination.constructor.get();
copy.readTrackedNbt(entry.getValue().toTrackedNbt());
entry.getValue().copyTo(copy);
destination.trackedObjects.put(entry.getKey(), copy);
}
destination.quickAccess = Map.copyOf(destination.trackedObjects);
@ -87,31 +88,35 @@ public class ObjectTracker<T extends TrackableObject> implements NbtSerialisable
return Optional.empty();
}
Map<UUID, NbtCompound> trackableCompounds = new HashMap<>();
Map<UUID, PacketByteBuf> updates = new HashMap<>();
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() {
if (!trackedObjects.isEmpty()) {
Map<UUID, NbtCompound> trackableCompounds = new HashMap<>();
Map<UUID, PacketByteBuf> updates = new HashMap<>();
Set<UUID> 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 true;
}
return status == TrackableObject.Status.REMOVED;
object.getValue().write(status).ifPresent(data -> {
updates.put(object.getKey(), data);
});
return false;
});
quickAccess = Map.copyOf(trackedObjects);
if (!trackableCompounds.isEmpty() || !removedTrackableObjects.isEmpty()) {
return Optional.of(new MsgTrackedValues.TrackerObjects(id, removedTrackableObjects, trackableCompounds));
if (!updates.isEmpty() || !removedTrackableObjects.isEmpty()) {
return Optional.of(new MsgTrackedValues.TrackerObjects(id, removedTrackableObjects, updates));
}
}
@ -125,44 +130,18 @@ public class ObjectTracker<T extends TrackableObject> implements NbtSerialisable
o.discard(true);
}
});
objects.values().forEach((id, nbt) -> {
objects.values().forEach((id, data) -> {
T o = trackedObjects.get(id);
if (o == null) {
o = constructor.get();
trackedObjects.put(id, o);
}
o.readTrackedNbt(nbt);
o.read(data);
});
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<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());
}
});
public void load(Map<UUID, T> values) {
synchronized (this) {
trackedObjects.clear();
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<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<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<Race> RACE = TrackableDataType.of(Unicopia.id("race"), PacketCodec.ofRegistry(Race.REGISTRY));

View file

@ -1,20 +1,37 @@
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.network.PacketByteBuf;
public interface TrackableObject {
UUID getUuid();
public interface TrackableObject<T extends TrackableObject<T>> {
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 copyTo(T destination);
public enum Status {
DEFAULT,
NEW,

View file

@ -37,7 +37,7 @@ public class Ether extends PersistentState {
this.world = world;
this.endpoints = NbtSerialisable.readMap(compound.getCompound("endpoints"), Identifier::tryParse, typeNbt -> {
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) {
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) {
NbtCompound nbt = new NbtCompound();
map.forEach((k, v) -> {
nbt.put(keyFunction.apply(k), valueFunction.apply(v));
});
return writeMap(new NbtCompound(), map, keyFunction, valueFunction);
}
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;
}