Sync each spell individually

This commit is contained in:
Sollace 2024-05-21 23:33:40 +01:00
parent 771da650c4
commit 88cf90842f
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
33 changed files with 762 additions and 699 deletions

View file

@ -7,7 +7,6 @@ import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.ability.data.Pos;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellContainer.Operation;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation;
import com.minelittlepony.unicopia.entity.player.Pony;
@ -93,11 +92,7 @@ public class UnicornDispellAbility implements Ability<Pos> {
public boolean apply(Pony player, Pos data) {
player.setAnimation(Animation.WOLOLO, Animation.Recipient.ANYONE);
Caster.stream(VecHelper.findInRange(player.asEntity(), player.asWorld(), data.vec(), 3, EquinePredicates.IS_PLACED_SPELL).stream()).forEach(target -> {
target.getSpellSlot().forEach(spell -> {
spell.setDead();
spell.tickDying(target);
return Operation.ofBoolean(!spell.isDead());
});
target.getSpellSlot().clear(false);
});
return true;
}

View file

@ -40,7 +40,7 @@ public interface Caster<E extends Entity> extends
Physics getPhysics();
SpellContainer getSpellSlot();
SpellSlots getSpellSlot();
/**
* Removes the desired amount of mana or health from this caster in exchange for a spell's benefits.

View file

@ -0,0 +1,131 @@
package com.minelittlepony.unicopia.ability.magic;
import java.util.UUID;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.SpellReference;
import com.minelittlepony.unicopia.network.track.ObjectTracker;
import com.minelittlepony.unicopia.network.track.Trackable;
import com.minelittlepony.unicopia.network.track.TrackableObject;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import net.minecraft.nbt.NbtCompound;
/**
* Container for multiple spells
*
* @param <T> The owning entity
*/
class MultiSpellSlot implements SpellSlots, NbtSerialisable {
private final Caster<?> owner;
private final ObjectTracker<Entry<?>> tracker;
public MultiSpellSlot(Caster<?> owner) {
this.owner = owner;
this.tracker = Trackable.of(owner.asEntity()).getDataTrackers().checkoutTracker(() -> new Entry<>(owner));
}
public ObjectTracker<?> getTracker() {
return tracker;
}
@Override
public boolean contains(UUID id) {
return tracker.contains(id)
|| tracker.values().stream().anyMatch(s -> s.spell.equalsOrContains(id));
}
@Override
public void put(@Nullable Spell effect) {
if (effect != null) {
tracker.add(new Entry<>(owner, effect));
}
}
@Override
public void remove(UUID id, boolean force) {
tracker.remove(id, force);
}
@Override
public <T extends Spell> Stream<T> stream(@Nullable SpellPredicate<T> type) {
return tracker.values().stream().flatMap(s -> s.spell.findMatches(type));
}
@Override
public boolean clear(boolean force) {
return tracker.clear(force);
}
@Override
public void toNBT(NbtCompound compound) {
compound.put("spells", tracker.toNBT());
}
@Override
public void fromNBT(NbtCompound compound) {
tracker.fromNBT(compound.getCompound("spells"));
}
static final class Entry<T extends Spell> implements TrackableObject {
private final Caster<?> owner;
final SpellReference<T> spell = new SpellReference<>();
private Status status = Status.NEW;
public Entry(Caster<?> owner) {
this.owner = owner;
}
public Entry(Caster<?> owner, T spell) {
this.owner = owner;
this.spell.set(spell);
if (owner instanceof UpdateCallback callback) {
callback.onSpellAdded(spell);
}
}
@Override
public void discard(boolean immediate) {
if (immediate) {
spell.set(null, owner);
} else {
Spell s = spell.get();
if (s != null) {
s.setDead();
s.tickDying(owner);
}
}
}
@Override
public UUID getUuid() {
return spell.get().getUuid();
}
@Override
public Status getStatus() {
try {
if (spell.get() == null) {
return Status.REMOVED;
}
if (spell.hasDirtySpell()) {
return Status.UPDATED;
}
return status;
} finally {
status = Status.DEFAULT;
}
}
@Override
public NbtCompound toTrackedNbt() {
return spell.toNBT();
}
@Override
public void readTrackedNbt(NbtCompound compound) {
spell.fromNBT(compound);
}
}
}

View file

@ -0,0 +1,68 @@
package com.minelittlepony.unicopia.ability.magic;
import java.util.UUID;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.network.track.Trackable;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import net.minecraft.nbt.NbtCompound;
/**
* Container for a single spell
*
* @param <T> The owning entity
*/
class SingleSpellSlot implements SpellSlots, NbtSerialisable {
private final Caster<?> owner;
private final MultiSpellSlot.Entry<Spell> entry;
public SingleSpellSlot(Caster<?> owner) {
this.owner = owner;
this.entry = new MultiSpellSlot.Entry<>(owner);
Trackable.of(owner.asEntity()).getDataTrackers().getPrimaryTracker().startTracking(entry);
}
@Override
public boolean contains(UUID id) {
return entry.spell.equalsOrContains(id);
}
@Override
public void put(@Nullable Spell effect) {
entry.spell.set(effect, owner);
}
@Override
public void remove(UUID id, boolean force) {
if (contains(id)) {
entry.discard(force);
}
}
@Override
public <T extends Spell> Stream<T> stream(@Nullable SpellPredicate<T> type) {
return entry.spell.findMatches(type);
}
@Override
public boolean clear(boolean force) {
if (entry.spell.get() != null) {
entry.discard(force);
return true;
}
return false;
}
@Override
public void toNBT(NbtCompound compound) {
compound.put("effect", entry.toTrackedNbt());
}
@Override
public void fromNBT(NbtCompound compound) {
entry.readTrackedNbt(compound.getCompound("effect"));
}
}

View file

@ -0,0 +1,75 @@
package com.minelittlepony.unicopia.ability.magic;
import java.util.function.Function;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
public class SpellInventory {
private final Caster<?> owner;
private final SpellSlots slots;
public SpellInventory(Caster<?> owner, SpellSlots slots) {
this.owner = owner;
this.slots = slots;
}
public SpellSlots getSlots() {
return slots;
}
public boolean tick(Situation situation) {
return tick(spell -> {
if (spell.isDying()) {
spell.tickDying(owner);
return Operation.ofBoolean(!spell.isDead());
}
return Operation.ofBoolean(spell.tick(owner, situation));
});
}
public boolean tick(Function<Spell, Operation> tickAction) {
try {
return forEach(spell -> {
try {
return tickAction.apply(spell);
} catch (Throwable t) {
Unicopia.LOGGER.error("Error whilst ticking spell on entity {}", owner, t);
}
return Operation.REMOVE;
});
} catch (Exception e) {
Unicopia.LOGGER.error("Error whilst ticking spell on entity {}", owner.asEntity(), e);
}
return false;
}
/**
* Iterates active spells and optionally removes matching ones.
*
* @return True if any matching spells remain active
*/
public boolean forEach(Function<Spell, Operation> test) {
return slots.reduce((initial, spell) -> {
Operation op = test.apply(spell);
if (op == Operation.REMOVE) {
slots.remove(spell.getUuid(), true);
} else {
initial |= op != Operation.SKIP;
}
return initial;
});
}
public enum Operation {
SKIP,
KEEP,
REMOVE;
public static Operation ofBoolean(boolean result) {
return result ? KEEP : REMOVE;
}
}
}

View file

@ -2,19 +2,56 @@ package com.minelittlepony.unicopia.ability.magic;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.BiFunction;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.util.NbtSerialisable;
public interface SpellSlots extends NbtSerialisable {
static SpellInventory ofUnbounded(Caster<?> caster) {
return new SpellInventory(caster, new MultiSpellSlot(caster));
}
static SpellInventory ofSingle(Caster<?> caster) {
return new SpellInventory(caster, new SingleSpellSlot(caster));
}
/**
* Gets all active effects for this caster that match the given type.
*/
<T extends Spell> Stream<T> stream(@Nullable SpellPredicate<T> type);
/**
* Sets the active effect.
*/
void put(@Nullable Spell effect);
/**
* Removes all effects currently active in this slot.
*/
boolean clear(boolean force);
/**
* Cleanly removes a spell from this spell container.
*
* @param spellid ID of the spell to remove.
*/
void remove(UUID spellid, boolean force);
public interface SpellContainer {
/**
* Checks if a spell with the given uuid is present.
*/
boolean contains(UUID id);
/**
* Gets the active effect for this caster
*/
default <T extends Spell> Optional<T> get() {
return get(null);
}
/**
* Checks if any matching spells are active.
*/
@ -22,13 +59,6 @@ public interface SpellContainer {
return get(type).isPresent();
}
/**
* Gets the active effect for this caster updating it if needed.
*/
default <T extends Spell> Optional<T> get() {
return get(null);
}
/**
* Gets the active effect for this caster updating it if needed.
*/
@ -37,23 +67,27 @@ public interface SpellContainer {
}
/**
* Sets the active effect.
* Removes all effects currently active in this slot.
*/
void put(@Nullable Spell effect);
default boolean clear() {
return clear(false);
}
/**
* Cleanly removes a spell from this spell container.
*
* @param spellid ID of the spell to remove.
*/
void remove(UUID spellid);
default void remove(UUID spellid) {
remove(spellid, false);
}
/**
* Removes all active effects that match or contain a matching effect.
*
* @return True if the collection was changed
*/
default boolean removeIf(Predicate<Spell> test) {
default boolean removeIf(SpellPredicate<?> test) {
return removeWhere(spell -> spell.findMatches(test).findFirst().isPresent());
}
@ -62,15 +96,15 @@ public interface SpellContainer {
*
* @return True if the collection was changed
*/
boolean removeWhere(Predicate<Spell> test);
/**
* Iterates active spells and optionally removes matching ones.
*
* @return True if any matching spells remain active
*/
boolean forEach(Function<Spell, Operation> action);
default boolean removeWhere(SpellPredicate<?> test) {
return reduce((initial, spell) -> {
if (test.test(spell)) {
remove(spell.getUuid());
return true;
}
return initial;
});
}
/**
* Gets all active effects for this caster updating it if needed.
@ -79,23 +113,11 @@ public interface SpellContainer {
return stream(null);
}
/**
* Gets all active effects for this caster that match the given type.
*/
<T extends Spell> Stream<T> stream(@Nullable SpellPredicate<T> type);
default boolean reduce(BiFunction<Boolean, Spell, Boolean> alteration) {
return stream().reduce(false, alteration, (a, b) -> b);
}
/**
* Removes all effects currently active in this slot.
*/
boolean clear();
public enum Operation {
SKIP,
KEEP,
REMOVE;
public static Operation ofBoolean(boolean result) {
return result ? KEEP : REMOVE;
}
public interface UpdateCallback {
void onSpellAdded(Spell spell);
}
}

View file

@ -1,22 +1,14 @@
package com.minelittlepony.unicopia.ability.magic.spell;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.EntityHitResult;
public abstract class AbstractDelegatingSpell implements Spell,
ProjectileDelegate.ConfigurationListener, ProjectileDelegate.BlockHitListener, ProjectileDelegate.EntityHitListener {
public abstract class AbstractDelegatingSpell implements Spell {
private boolean dirty;
private boolean hidden;
private boolean destroyed;
@ -24,21 +16,24 @@ public abstract class AbstractDelegatingSpell implements Spell,
private UUID uuid = UUID.randomUUID();
private final CustomisedSpellType<?> type;
protected final SpellReference<Spell> delegate = new SpellReference<>();
public AbstractDelegatingSpell(CustomisedSpellType<?> type) {
this.type = type;
}
public abstract Collection<Spell> getDelegates();
@Override
public boolean equalsOrContains(UUID id) {
return Spell.super.equalsOrContains(id) || getDelegates().stream().anyMatch(s -> s.equalsOrContains(id));
public final Spell getDelegate() {
return delegate.get();
}
@Override
public Stream<Spell> findMatches(Predicate<Spell> predicate) {
return Stream.concat(Spell.super.findMatches(predicate), getDelegates().stream().flatMap(s -> s.findMatches(predicate)));
public boolean equalsOrContains(UUID id) {
return Spell.super.equalsOrContains(id) || delegate.equalsOrContains(id);
}
@Override
public <T extends Spell> Stream<T> findMatches(SpellPredicate<T> predicate) {
return Stream.concat(Spell.super.findMatches(predicate), delegate.findMatches(predicate));
}
@Override
@ -53,7 +48,10 @@ public abstract class AbstractDelegatingSpell implements Spell,
@Override
public void setDead() {
getDelegates().forEach(Spell::setDead);
Spell spell = delegate.get();
if (spell != null) {
spell.setDead();
}
}
@Override
@ -62,7 +60,7 @@ public abstract class AbstractDelegatingSpell implements Spell,
@Override
public boolean isDead() {
return getDelegates().isEmpty() || getDelegates().stream().allMatch(Spell::isDead);
return delegate.get() == null || delegate.get().isDead();
}
@Override
@ -72,7 +70,7 @@ public abstract class AbstractDelegatingSpell implements Spell,
@Override
public boolean isDirty() {
return dirty || getDelegates().stream().anyMatch(Spell::isDirty);
return dirty || (delegate.get() instanceof Spell p && p.isDirty());
}
@Override
@ -82,7 +80,7 @@ public abstract class AbstractDelegatingSpell implements Spell,
@Override
public boolean isHidden() {
return hidden || getDelegates().stream().allMatch(Spell::isHidden);
return hidden || (delegate.get() instanceof Spell p && p.isHidden());
}
@Override
@ -101,40 +99,28 @@ public abstract class AbstractDelegatingSpell implements Spell,
}
protected void onDestroyed(Caster<?> caster) {
getDelegates().forEach(a -> a.destroy(caster));
if (delegate.get() instanceof Spell s) {
s.destroy(caster);
}
}
@Override
public boolean tick(Caster<?> source, Situation situation) {
return execute(getDelegates().stream(), a -> {
if (a.isDying()) {
a.tickDying(source);
return !a.isDead();
if (delegate.get() instanceof Spell s) {
if (s.isDying()) {
s.tickDying(source);
return !s.isDead();
}
return a.tick(source, situation);
});
}
@Override
public void onImpact(MagicProjectileEntity projectile, BlockHitResult hit) {
getDelegates(BlockHitListener.PREDICATE).forEach(a -> a.onImpact(projectile, hit));
}
@Override
public void onImpact(MagicProjectileEntity projectile, EntityHitResult hit) {
getDelegates(EntityHitListener.PREDICATE).forEach(a -> a.onImpact(projectile, hit));
}
@Override
public void configureProjectile(MagicProjectileEntity projectile, Caster<?> caster) {
getDelegates(ConfigurationListener.PREDICATE).forEach(a -> a.configureProjectile(projectile, caster));
return s.tick(source, situation) && !isDead();
}
return !isDead();
}
@Override
public void toNBT(NbtCompound compound) {
compound.putUuid("uuid", uuid);
compound.putBoolean("hidden", hidden);
saveDelegates(compound);
compound.put("spell", delegate.toNBT());
}
@Override
@ -144,18 +130,11 @@ public abstract class AbstractDelegatingSpell implements Spell,
if (compound.contains("uuid")) {
uuid = compound.getUuid("uuid");
}
loadDelegates(compound);
delegate.fromNBT(compound.getCompound("spell"));
}
protected abstract void loadDelegates(NbtCompound compound);
protected abstract void saveDelegates(NbtCompound compound);
protected <T> Stream<T> getDelegates(Function<? super Spell, T> cast) {
return getDelegates().stream().map(cast).filter(Objects::nonNull);
}
protected static boolean execute(Stream<Spell> spells, Function<Spell, Boolean> action) {
return spells.reduce(false, (u, a) -> action.apply(a), (a, b) -> a || b);
@Override
public final String toString() {
return "Delegate{" + getTypeAndTraits() + "}[uuid=" + uuid + ", destroyed=" + destroyed + ", hidden=" + hidden + "][spell=" + delegate.get() + "]";
}
}

View file

@ -29,6 +29,11 @@ import net.minecraft.world.World;
* <p>
* The spell's effects are still powered by the casting player, so if the player dies or leaves the area, their
* spell loses affect until they return.
* <p>
* When cast two copies of this spell are created. One is attached to the player and is the controlling spell,
* the other is attached to a cast spell entity and placed in the world.
*
* TODO: Split this up into separate classes.
*/
public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedSpell {
/**
@ -48,11 +53,6 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
*/
private final EntityReference<CastSpellEntity> castEntity = new EntityReference<>();
/**
* The spell being cast
*/
private final SpellReference<Spell> spell = new SpellReference<>();
public float pitch;
public float yaw;
@ -70,7 +70,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
}
public PlaceableSpell setSpell(Spell spell) {
this.spell.set(spell);
delegate.set(spell);
return this;
}
@ -101,14 +101,9 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
return dead && deathTicks <= 0 && super.isDead();
}
@Override
public Collection<Spell> getDelegates() {
Spell spell = this.spell.get();
return spell == null ? List.of() : List.of(spell);
}
@Override
public boolean tick(Caster<?> source, Situation situation) {
System.out.println("Placed Tick: " + source + " " + source.isClient() + " " + situation);
if (situation == Situation.BODY) {
if (!source.isClient()) {
if (dimension == null) {
@ -163,8 +158,8 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
CastSpellEntity entity = UEntities.CAST_SPELL.create(source.asWorld());
Vec3d pos = getPosition().orElse(position.orElse(source.asEntity().getPos()));
entity.updatePositionAndAngles(pos.x, pos.y, pos.z, source.asEntity().getYaw(), source.asEntity().getPitch());
PlaceableSpell copy = spell.get().toPlaceable();
if (spell.get() instanceof PlacementDelegate delegate) {
PlaceableSpell copy = delegate.get().toPlaceable();
if (delegate.get() instanceof PlacementDelegate delegate) {
delegate.onPlaced(source, copy, entity);
}
entity.getSpellSlot().put(copy);
@ -181,8 +176,9 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
public void setOrientation(float pitch, float yaw) {
this.pitch = -90 - pitch;
this.yaw = -yaw;
getDelegates(spell -> spell instanceof OrientedSpell o ? o : null)
.forEach(spell -> spell.setOrientation(pitch, yaw));
if (delegate.get() instanceof OrientedSpell o) {
o.setOrientation(pitch, yaw);
}
setDirty();
}
@ -192,8 +188,9 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
castEntity.ifPresent(source.asWorld(), entity -> {
entity.updatePositionAndAngles(position.x, position.y, position.z, entity.getYaw(), entity.getPitch());
});
getDelegates(spell -> spell instanceof PlaceableSpell o ? o : null)
.forEach(spell -> spell.setPosition(source, position));
if (delegate.get() instanceof PlaceableSpell o) {
o.setPosition(source, position);
}
setDirty();
}
@ -271,16 +268,6 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
}
}
@Override
protected void loadDelegates(NbtCompound compound) {
spell.fromNBT(compound.getCompound("spell"));
}
@Override
protected void saveDelegates(NbtCompound compound) {
compound.put("spell", spell.toNBT());
}
@Override
public PlaceableSpell toPlaceable() {
return this;

View file

@ -1,7 +1,6 @@
package com.minelittlepony.unicopia.ability.magic.spell;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
@ -11,6 +10,7 @@ import com.minelittlepony.unicopia.Affinity;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.Affine;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.util.NbtSerialisable;
@ -53,8 +53,9 @@ public interface Spell extends NbtSerialisable, Affine {
/**
* Returns an optional containing the spell that matched the given predicate.
*/
default Stream<Spell> findMatches(Predicate<Spell> predicate) {
return predicate.test(this) ? Stream.of(this) : Stream.empty();
@SuppressWarnings("unchecked")
default <T extends Spell> Stream<T> findMatches(SpellPredicate<T> predicate) {
return predicate == null || predicate.test(this) ? Stream.of((T)this) : Stream.empty();
}
/**

View file

@ -1,9 +1,13 @@
package com.minelittlepony.unicopia.ability.magic.spell;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import net.minecraft.nbt.NbtCompound;
@ -40,6 +44,17 @@ public final class SpellReference<T extends Spell> implements NbtSerialisable {
return true;
}
public boolean equalsOrContains(UUID id) {
@Nullable T spell = get();
return spell != null && spell.equalsOrContains(id);
}
@SuppressWarnings("unchecked")
public <V extends Spell> Stream<V> findMatches(SpellPredicate<V> predicate) {
@Nullable T spell = get();
return spell != null ? (predicate == null ? Stream.of((V)spell) : spell.findMatches(predicate)) : Stream.empty();
}
@Override
public void toNBT(NbtCompound compound) {
if (spell != null && !spell.isDead()) {

View file

@ -1,34 +1,30 @@
package com.minelittlepony.unicopia.ability.magic.spell;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.projectile.MagicBeamEntity;
import net.minecraft.nbt.NbtCompound;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.world.World;
public final class ThrowableSpell extends AbstractDelegatingSpell {
private final SpellReference<Spell> spell = new SpellReference<>();
public final class ThrowableSpell extends AbstractDelegatingSpell implements
ProjectileDelegate.ConfigurationListener, ProjectileDelegate.BlockHitListener, ProjectileDelegate.EntityHitListener {
public ThrowableSpell(CustomisedSpellType<?> type) {
super(type);
}
public ThrowableSpell setSpell(Spell spell) {
this.spell.set(spell);
delegate.set(spell);
return this;
}
@Override
public Collection<Spell> getDelegates() {
return List.of(spell.get());
}
@Override
public boolean apply(Caster<?> source) {
return throwProjectile(source).isPresent();
@ -57,7 +53,7 @@ public final class ThrowableSpell extends AbstractDelegatingSpell {
return Optional.empty();
}
return Optional.ofNullable(spell.get().prepareForCast(caster, CastingMethod.STORED)).map(s -> {
return Optional.ofNullable(delegate.get().prepareForCast(caster, CastingMethod.STORED)).map(s -> {
MagicBeamEntity projectile = new MagicBeamEntity(world, caster.asEntity(), divergance, s);
configureProjectile(projectile, caster);
@ -67,16 +63,6 @@ public final class ThrowableSpell extends AbstractDelegatingSpell {
});
}
@Override
protected void loadDelegates(NbtCompound compound) {
spell.fromNBT(compound.getCompound("spell"));
}
@Override
protected void saveDelegates(NbtCompound compound) {
compound.put("spell", spell.toNBT());
}
@Override
public ThrowableSpell toThrowable() {
return this;
@ -90,4 +76,25 @@ public final class ThrowableSpell extends AbstractDelegatingSpell {
@Override
public void setHidden(boolean hidden) {
}
@Override
public void onImpact(MagicProjectileEntity projectile, BlockHitResult hit) {
if (delegate.get() instanceof BlockHitListener listener) {
listener.onImpact(projectile, hit);
}
}
@Override
public void onImpact(MagicProjectileEntity projectile, EntityHitResult hit) {
if (delegate.get() instanceof EntityHitListener listener) {
listener.onImpact(projectile, hit);
}
}
@Override
public void configureProjectile(MagicProjectileEntity projectile, Caster<?> caster) {
if (delegate.get() instanceof ConfigurationListener listener) {
listener.configureProjectile(projectile, caster);
}
}
}

View file

@ -137,10 +137,7 @@ public class DismissSpellScreen extends GameGui {
}
private Spell getActualSpell() {
if (spell instanceof AbstractDelegatingSpell) {
return ((AbstractDelegatingSpell)spell).getDelegates().stream().findFirst().orElse(spell);
}
return spell;
return spell instanceof AbstractDelegatingSpell s && s.getDelegate() instanceof Spell p ? p : spell;
}
@Override

View file

@ -34,8 +34,9 @@ public class PlacedSpellRenderer extends SpellRenderer<PlaceableSpell> {
matrices.push();
matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(-castSpell.getYaw()));
Spell delegate = spell.getDelegate();
for (Spell delegate : spell.getDelegates()) {
if (delegate != null) {
renderAmbientEffects(matrices, vertices, spell, delegate, caster, light, animationProgress, tickDelta);
matrices.push();

View file

@ -10,7 +10,6 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellContainer.Operation;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
@ -87,9 +86,8 @@ public class SpellEffectsRenderDispatcher implements SynchronousResourceReloader
return;
}
caster.getSpellSlot().forEach(spell -> {
caster.getSpellSlot().stream().forEach(spell -> {
render(matrices, vertices, spell, caster, light, limbAngle, limbDistance, tickDelta, animationProgress, headYaw, headPitch);
return Operation.SKIP;
});
if (client.getEntityRenderDispatcher().shouldRenderHitboxes()

View file

@ -69,7 +69,7 @@ public class Creature extends Living<LivingEntity> implements WeaklyOwned.Mutabl
public Creature(LivingEntity entity) {
super(entity);
physics = new EntityPhysics<>(entity, tracker);
physics = new EntityPhysics<>(entity);
addTicker(physics);
addTicker(this::updateConsumption);

View file

@ -2,6 +2,7 @@ package com.minelittlepony.unicopia.entity;
import com.minelittlepony.unicopia.entity.mob.UEntityAttributes;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.network.track.Trackable;
import com.minelittlepony.unicopia.network.track.TrackableDataType;
import com.minelittlepony.unicopia.util.Copyable;
import com.minelittlepony.unicopia.util.Tickable;
@ -27,10 +28,9 @@ public class EntityPhysics<T extends Entity> implements Physics, Copyable<Entity
private final DataTracker tracker;
protected final DataTracker.Entry<Float> gravity;
public EntityPhysics(T entity, DataTracker tracker) {
public EntityPhysics(T entity) {
this.entity = entity;
this.tracker = tracker;
//this.gravity = gravity;
this.tracker = Trackable.of(entity).getDataTrackers().getPrimaryTracker();
gravity = tracker.startTracking(TrackableDataType.of(PacketCodec.FLOAT), 1F);
}

View file

@ -42,7 +42,7 @@ public class ItemImpl implements Equine<ItemEntity> {
this.entity = owner;
this.trackers = Trackable.of(entity).getDataTrackers();
this.tracker = trackers.getPrimaryTracker();
this.physics = new ItemPhysics(owner, tracker);
this.physics = new ItemPhysics(owner);
race = tracker.startTracking(TrackableDataType.of(Race.PACKET_CODEC), Race.HUMAN);
}

View file

@ -1,12 +1,10 @@
package com.minelittlepony.unicopia.entity;
import com.minelittlepony.unicopia.network.track.DataTracker;
import net.minecraft.entity.ItemEntity;
class ItemPhysics extends EntityPhysics<ItemEntity> {
public ItemPhysics(ItemEntity entity, DataTracker tracker) {
super(entity, tracker);
public ItemPhysics(ItemEntity entity) {
super(entity);
}
@Override

View file

@ -12,7 +12,8 @@ import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.Abilities;
import com.minelittlepony.unicopia.ability.Ability;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellContainer;
import com.minelittlepony.unicopia.ability.magic.SpellInventory;
import com.minelittlepony.unicopia.ability.magic.SpellSlots;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.AbstractDisguiseSpell;
import com.minelittlepony.unicopia.ability.magic.spell.Situation;
@ -30,7 +31,6 @@ import com.minelittlepony.unicopia.input.Interactable;
import com.minelittlepony.unicopia.item.GlassesItem;
import com.minelittlepony.unicopia.item.UItems;
import com.minelittlepony.unicopia.item.enchantment.UEnchantments;
import com.minelittlepony.unicopia.network.datasync.EffectSync;
import com.minelittlepony.unicopia.network.datasync.Transmittable;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.network.track.DataTrackerManager;
@ -78,7 +78,7 @@ import net.minecraft.util.math.Vec3d;
public abstract class Living<T extends LivingEntity> implements Equine<T>, Caster<T>, Transmittable {
protected final T entity;
private final EffectSync effectDelegate;
private final SpellInventory spells;
private final Interactable sneakingHeuristic;
private final Interactable landedHeuristic;
@ -111,7 +111,7 @@ public abstract class Living<T extends LivingEntity> implements Equine<T>, Caste
this.entity = entity;
this.trackers = Trackable.of(entity).getDataTrackers();
this.tracker = trackers.getPrimaryTracker();
this.effectDelegate = new EffectSync(this, tracker);
this.spells = SpellSlots.ofUnbounded(this);
this.sneakingHeuristic = addTicker(new Interactable(entity::isSneaking));
this.landedHeuristic = addTicker(new Interactable(entity::isOnGround));
this.jumpingHeuristic = addTicker(new Interactable(((LivingEntityDuck)entity)::isJumping));
@ -149,8 +149,8 @@ public abstract class Living<T extends LivingEntity> implements Equine<T>, Caste
}
@Override
public SpellContainer getSpellSlot() {
return effectDelegate;
public SpellSlots getSpellSlot() {
return spells.getSlots();
}
public Enchantments getEnchants() {
@ -216,7 +216,7 @@ public abstract class Living<T extends LivingEntity> implements Equine<T>, Caste
@Override
public void tick() {
tickers.forEach(Tickable::tick);
effectDelegate.tick(Situation.BODY);
spells.tick(Situation.BODY);
if (!(entity instanceof PlayerEntity)) {
if (!entity.hasVehicle() && getCarrierId().isPresent() && !asWorld().isClient && entity.age % 10 == 0) {
@ -486,14 +486,14 @@ public abstract class Living<T extends LivingEntity> implements Equine<T>, Caste
@Override
public void toNBT(NbtCompound compound) {
enchants.toNBT(compound);
effectDelegate.toNBT(compound);
spells.getSlots().toNBT(compound);
toSyncronisedNbt(compound);
}
@Override
public void fromNBT(NbtCompound compound) {
enchants.fromNBT(compound);
effectDelegate.fromNBT(compound);
spells.getSlots().fromNBT(compound);
fromSynchronizedNbt(compound);
}

View file

@ -3,7 +3,8 @@ package com.minelittlepony.unicopia.entity.mob;
import com.minelittlepony.unicopia.*;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.Levelled;
import com.minelittlepony.unicopia.ability.magic.SpellContainer;
import com.minelittlepony.unicopia.ability.magic.SpellInventory;
import com.minelittlepony.unicopia.ability.magic.SpellSlots;
import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
@ -11,9 +12,6 @@ import com.minelittlepony.unicopia.entity.EntityPhysics;
import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.entity.MagicImmune;
import com.minelittlepony.unicopia.entity.Physics;
import com.minelittlepony.unicopia.network.datasync.EffectSync;
import com.minelittlepony.unicopia.network.track.Trackable;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityDimensions;
import net.minecraft.entity.EntityPose;
@ -25,9 +23,9 @@ import net.minecraft.world.World;
public class CastSpellEntity extends LightEmittingEntity implements Caster<CastSpellEntity>, WeaklyOwned.Mutable<LivingEntity>, MagicImmune {
private final EntityPhysics<CastSpellEntity> physics = new EntityPhysics<>(this, Trackable.of(this).getDataTrackers().getPrimaryTracker());
private final EntityPhysics<CastSpellEntity> physics = new EntityPhysics<>(this);
private final EffectSync effectDelegate = new EffectSync(this, Trackable.of(this).getDataTrackers().getPrimaryTracker());
private final SpellInventory spells = SpellSlots.ofSingle(this);
private final EntityReference<LivingEntity> owner = new EntityReference<>();
@ -65,7 +63,7 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster<CastS
return;
}
if (!effectDelegate.tick(Situation.GROUND_ENTITY)) {
if (!spells.tick(Situation.GROUND_ENTITY)) {
discard();
}
}
@ -112,8 +110,8 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster<CastS
}
@Override
public SpellContainer getSpellSlot() {
return effectDelegate;
public SpellSlots getSpellSlot() {
return spells.getSlots();
}
@Override
@ -137,9 +135,7 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster<CastS
tag.put("owner", owner.toNBT());
tag.put("level", level.toNbt());
tag.put("corruption", corruption.toNbt());
getSpellSlot().get().ifPresent(effect -> {
tag.put("effect", Spell.writeNbt(effect));
});
spells.getSlots().toNBT(tag);
}
@Override
@ -147,9 +143,7 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster<CastS
if (tag.contains("owner")) {
owner.fromNBT(tag.getCompound("owner"));
}
if (tag.contains("effect")) {
getSpellSlot().put(Spell.readNbt(tag.getCompound("effect")));
}
spells.getSlots().fromNBT(tag);
level = Levelled.fromNbt(tag.getCompound("level"));
corruption = Levelled.fromNbt(tag.getCompound("corruption"));
}

View file

@ -99,7 +99,7 @@ public class PlayerPhysics extends EntityPhysics<PlayerEntity> implements Tickab
private Lerp windStrength = new Lerp(0);
public PlayerPhysics(Pony pony, DataTracker tracker) {
super(pony.asEntity(), tracker);
super(pony.asEntity());
this.pony = pony;
dimensions = new PlayerDimensions(pony, this);
}

View file

@ -11,6 +11,7 @@ import com.minelittlepony.unicopia.client.render.PlayerPoser.AnimationInstance;
import com.minelittlepony.unicopia.*;
import com.minelittlepony.unicopia.ability.*;
import com.minelittlepony.unicopia.ability.magic.*;
import com.minelittlepony.unicopia.ability.magic.SpellSlots.UpdateCallback;
import com.minelittlepony.unicopia.ability.magic.spell.AbstractDisguiseSpell;
import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod;
import com.minelittlepony.unicopia.ability.magic.spell.RageAbilitySpell;
@ -33,7 +34,6 @@ import com.minelittlepony.unicopia.item.enchantment.EnchantmentUtil;
import com.minelittlepony.unicopia.item.enchantment.UEnchantments;
import com.minelittlepony.unicopia.util.*;
import com.minelittlepony.unicopia.network.*;
import com.minelittlepony.unicopia.network.datasync.EffectSync.UpdateCallback;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.network.track.TrackableDataType;
import com.minelittlepony.unicopia.server.world.UGameRules;
@ -929,14 +929,11 @@ public class Pony extends Living<PlayerEntity> implements Copyable<Pony>, Update
}
@Override
public void onSpellSet(@Nullable Spell spell) {
if (spell != null) {
if (spell.getAffinity() == Affinity.BAD && entity.getWorld().random.nextInt(20) == 0) {
getCorruption().add(entity.getRandom().nextBetween(1, 10));
}
getCorruption().add(((int)spell.getTypeAndTraits().traits().getCorruption() * 10) + spell.getTypeAndTraits().type().getAffinity().getCorruption());
setDirty();
public void onSpellAdded(Spell spell) {
if (spell.getAffinity() == Affinity.BAD && entity.getWorld().random.nextInt(20) == 0) {
getCorruption().add(entity.getRandom().nextBetween(1, 10));
}
getCorruption().add(((int)spell.getTypeAndTraits().traits().getCorruption() * 10) + spell.getTypeAndTraits().type().getAffinity().getCorruption());
}
public boolean isClientPlayer() {

View file

@ -6,7 +6,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellContainer;
import com.minelittlepony.unicopia.ability.magic.SpellSlots;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.Item;
@ -20,6 +20,6 @@ abstract class MixinMilkBucketItem extends Item {
@Inject(method = "finishUsing", at = @At("HEAD"), cancellable = true)
private void finishUsing(ItemStack stack, World world, LivingEntity entity, CallbackInfoReturnable<ItemStack> info) {
Caster.of(entity).map(Caster::getSpellSlot).ifPresent(SpellContainer::clear);
Caster.of(entity).map(Caster::getSpellSlot).ifPresent(SpellSlots::clear);
}
}

View file

@ -1,170 +0,0 @@
package com.minelittlepony.unicopia.network.datasync;
import java.util.UUID;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellContainer;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.network.track.DataTracker;
import com.minelittlepony.unicopia.network.track.TrackableDataType;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import com.minelittlepony.unicopia.util.serialization.PacketCodec;
import net.minecraft.nbt.NbtCompound;
/**
* Synchronisation class for spells.
* Since we can't have our own serializers, we have to intelligently
* determine whether to update it from an nbt tag.
*
* @param <T> The owning entity
*/
public class EffectSync implements SpellContainer, NbtSerialisable {
private final NetworkedReferenceSet<Spell> spells;
private final Caster<?> owner;
private final DataTracker tracker;
private final DataTracker.Entry<NbtCompound> param;
public EffectSync(Caster<?> owner, DataTracker tracker) {
this.owner = owner;
this.tracker = tracker;
this.param = tracker.startTracking(TrackableDataType.of(PacketCodec.NBT), new NbtCompound());
spells = new NetworkedReferenceSet<>(Spell::getUuid, () -> new SpellNetworkedReference<>(owner));
tracker.onBeforeSend(param, () -> {
if (spells.isDirty()) {
tracker.set(param, spells.toNbt());
}
});
tracker.onReceive(param, nbt -> spells.fromNbt(nbt));
}
public boolean tick(Situation situation) {
return tick(spell -> {
if (spell.isDying()) {
spell.tickDying(owner);
return Operation.ofBoolean(!spell.isDead());
}
return Operation.ofBoolean(spell.tick(owner, situation));
});
}
public boolean tick(Function<Spell, Operation> tickAction) {
try {
return forEach(spell -> {
try {
return tickAction.apply(spell);
} catch (Throwable t) {
Unicopia.LOGGER.error("Error whilst ticking spell on entity {}", owner, t);
}
return Operation.REMOVE;
});
} catch (Exception e) {
Unicopia.LOGGER.error("Error whilst ticking spell on entity {}", owner.asEntity(), e);
}
return false;
}
@Override
public boolean contains(UUID id) {
return spells.containsReference(id) || spells.getReferences().anyMatch(s -> s.equalsOrContains(id));
}
@Override
public void put(@Nullable Spell effect) {
spells.addReference(effect);
if (owner instanceof UpdateCallback callback) {
callback.onSpellSet(effect);
}
}
@Override
public void remove(UUID id) {
discard(spells.getReference(id));
}
@Override
public boolean removeWhere(Predicate<Spell> test) {
return reduce((initial, spell) -> {
if (!test.test(spell)) {
return initial;
}
discard(spell);
return true;
});
}
private void discard(Spell spell) {
if (spell != null) {
spell.setDead();
spell.tickDying(owner);
if (spell.isDead()) {
spells.removeReference(spell);
}
}
}
@Override
public boolean forEach(Function<Spell, Operation> test) {
return reduce((initial, effect) -> {
Operation op = test.apply(effect);
if (op == Operation.REMOVE) {
spells.removeReference(effect);
} else {
initial |= op != Operation.SKIP;
}
return initial;
});
}
@SuppressWarnings("unchecked")
@Override
public <T extends Spell> Stream<T> stream(@Nullable SpellPredicate<T> type) {
if (type == null) {
return (Stream<T>)spells.getReferences();
}
return (Stream<T>)spells.getReferences().flatMap(s -> s.findMatches(type));
}
@Override
public boolean clear() {
if (spells.clear()) {
if (owner instanceof UpdateCallback c) {
c.onSpellSet(null);
}
return true;
}
return false;
}
private boolean reduce(BiFunction<Boolean, Spell, Boolean> alteration) {
return stream().toList().stream().reduce(false, alteration, (a, b) -> b);
}
@Override
public void toNBT(NbtCompound compound) {
compound.put("spells", spells.toNbt());
}
@Override
public void fromNBT(NbtCompound compound) {
spells.fromNbt(compound.getCompound("spells"));
tracker.set(param, spells.toNbt());
}
public interface UpdateCallback {
void onSpellSet(@Nullable Spell spell);
}
}

View file

@ -1,19 +0,0 @@
package com.minelittlepony.unicopia.network.datasync;
import java.util.Optional;
import org.jetbrains.annotations.Nullable;
import net.minecraft.nbt.NbtCompound;
public interface NetworkedReference<T> {
Optional<T> getReference();
void updateReference(@Nullable T newValue);
boolean fromNbt(NbtCompound comp);
NbtCompound toNbt();
boolean isDirty();
}

View file

@ -1,153 +0,0 @@
package com.minelittlepony.unicopia.network.datasync;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.nbt.NbtList;
import net.minecraft.nbt.NbtString;
public class NetworkedReferenceSet<T> {
private final List<UUID> ids = new ArrayList<>();
private final Map<UUID, NetworkedReference<T>> values = new HashMap<>();
private final Function<T, UUID> uuidConverter;
private final Supplier<NetworkedReference<T>> factory;
private boolean dirty;
private boolean reading;
public NetworkedReferenceSet(Function<T, UUID> uuidConverter, Supplier<NetworkedReference<T>> factory) {
this.uuidConverter = uuidConverter;
this.factory = factory;
}
public synchronized boolean containsReference(UUID id) {
return ids.contains(id);
}
public synchronized Stream<T> getReferences() {
return ids.stream().map(id -> values.get(id))
.filter(Objects::nonNull)
.map(a -> a.getReference())
.filter(Optional::isPresent)
.map(Optional::get);
}
public synchronized boolean clear() {
dirty |= !ids.isEmpty() || !values.isEmpty();
ids.clear();
try {
reading = true;
for (NetworkedReference<T> reference : values.values()) {
reference.updateReference(null);
}
} finally {
reading = false;
values.clear();
}
return dirty;
}
public void addReference(@Nullable T newValue) {
if (newValue != null) {
addReference(uuidConverter.apply(newValue)).updateReference(newValue);
}
}
private synchronized NetworkedReference<T> addReference(UUID newValue) {
return values.computeIfAbsent(newValue, id -> {
dirty = true;
ids.remove(id);
ids.add(0, id);
return factory.get();
});
}
public void removeReference(@Nullable T oldValue) {
if (oldValue != null) {
removeReference(uuidConverter.apply(oldValue));
}
}
@Nullable
synchronized T getReference(UUID id) {
NetworkedReference<T> i = values.get(id);
return i == null ? null : i.getReference().orElse(null);
}
synchronized void removeReference(UUID id) {
dirty |= ids.remove(id);
NetworkedReference<T> i = values.remove(id);
if (i != null) {
dirty = true;
try {
reading = true;
i.updateReference(null);
} finally {
reading = false;
}
}
}
public boolean fromNbt(NbtCompound comp) {
if (reading) {
return false;
}
reading = true;
try {
List<UUID> incoming = new ArrayList<>();
comp.getList("keys", NbtElement.STRING_TYPE).forEach(key -> {
incoming.add(UUID.fromString(key.asString()));
});
ids.stream().filter(id -> !incoming.contains(id)).toList().forEach(this::removeReference);
boolean[] send = new boolean[1];
incoming.forEach(key -> {
NetworkedReference<T> i = addReference(key);
send[0] |= i.fromNbt(comp.getCompound(key.toString()));
if (i.getReference().isEmpty()) {
removeReference(key);
}
});
dirty = send[0];
return send[0];
} finally {
reading = false;
}
}
public synchronized NbtCompound toNbt() {
NbtCompound tag = new NbtCompound();
NbtList ids = new NbtList();
this.ids.forEach(id -> {
String sid = id.toString();
NetworkedReference<?> ref = values.get(id);
if (ref != null) {
ids.add(NbtString.of(sid));
tag.put(sid, values.get(id).toNbt());
}
});
tag.put("keys", ids);
dirty = false;
return tag;
}
public synchronized boolean isDirty() {
return dirty || values.values().stream().anyMatch(NetworkedReference::isDirty);
}
}

View file

@ -1,48 +0,0 @@
package com.minelittlepony.unicopia.network.datasync;
import java.util.Optional;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.SpellReference;
import net.minecraft.nbt.NbtCompound;
public class SpellNetworkedReference<T extends Spell> implements NetworkedReference<T> {
private final SpellReference<T> currentValue = new SpellReference<>();
private final Caster<?> owner;
private boolean dirty;
public SpellNetworkedReference(Caster<?> owner) {
this.owner = owner;
}
@Override
public Optional<T> getReference() {
return Optional.ofNullable(currentValue.get());
}
@Override
public void updateReference(@Nullable T newValue) {
dirty |= currentValue.set(newValue, owner);
}
@Override
public boolean fromNbt(NbtCompound comp) {
dirty = false;
currentValue.fromNBT(comp, owner.isClient());
return isDirty();
}
@Override
public NbtCompound toNbt() {
dirty = false;
return currentValue.toNBT();
}
@Override
public boolean isDirty() {
return !owner.isClient() && (dirty || currentValue.hasDirtySpell());
}
}

View file

@ -3,21 +3,22 @@ package com.minelittlepony.unicopia.network.track;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.util.serialization.PacketCodec;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
public class DataTracker {
private final List<Pair<?>> codecs = new ObjectArrayList<>();
private final Int2ObjectOpenHashMap<Consumer<?>> loadCallbacks = new Int2ObjectOpenHashMap<>();
private final Int2ObjectOpenHashMap<Runnable> writethroughCallback = new Int2ObjectOpenHashMap<>();
private IntSet dirtyIndices = new IntOpenHashSet();
private Int2ObjectMap<TrackableObject> persistentObjects = new Int2ObjectOpenHashMap<>();
private final DataTrackerManager manager;
private boolean initial = true;
@ -29,20 +30,18 @@ public class DataTracker {
this.id = id;
}
public <T extends TrackableObject> Entry<NbtCompound> startTracking(T value) {
Entry<NbtCompound> entry = startTracking(TrackableDataType.of(PacketCodec.NBT), value.toTrackedNbt());
persistentObjects.put(entry.id(), value);
return entry;
}
public <T> Entry<T> startTracking(TrackableDataType<T> type, T initialValue) {
Entry<T> entry = new Entry<>(codecs.size());
codecs.add(new Pair<>(entry.id(), type, initialValue));
return entry;
}
public <T> void onReceive(Entry<T> entry, Consumer<T> loadCallback) {
loadCallbacks.put(entry.id(), loadCallback);
}
public <T> void onBeforeSend(Entry<T> entry, Runnable action) {
writethroughCallback.put(entry.id(), action);
}
@SuppressWarnings("unchecked")
private <T> Pair<T> getPair(Entry<T> entry) {
return (Pair<T>)codecs.get(entry.id());
@ -66,59 +65,58 @@ public class DataTracker {
}
}
@SuppressWarnings("unchecked")
@Nullable
synchronized MsgTrackedValues.TrackerEntries getDirtyPairs() {
writethroughCallback.values().forEach(Runnable::run);
synchronized void getDirtyPairs(List<MsgTrackedValues.TrackerEntries> output) {
for (var entry : persistentObjects.int2ObjectEntrySet()) {
int key = entry.getIntKey();
TrackableObject.Status status = entry.getValue().getStatus();
if (status == TrackableObject.Status.NEW || status == TrackableObject.Status.UPDATED) {
((Pair<Object>)codecs.get(key)).value = entry.getValue().toTrackedNbt();
dirtyIndices.add(key);
}
}
if (initial) {
initial = false;
dirtyIndices = new IntOpenHashSet();
return new MsgTrackedValues.TrackerEntries(id, true, codecs);
output.add(new MsgTrackedValues.TrackerEntries(id, true, codecs));
} else if (!dirtyIndices.isEmpty()) {
IntSet toSend = dirtyIndices;
dirtyIndices = new IntOpenHashSet();
List<Pair<?>> pairs = new ArrayList<>();
for (int i : toSend) {
pairs.add(codecs.get(i));
}
output.add(new MsgTrackedValues.TrackerEntries(id, false, pairs));
}
if (dirtyIndices.isEmpty()) {
return null;
}
IntSet toSend = dirtyIndices;
dirtyIndices = new IntOpenHashSet();
List<Pair<?>> pairs = new ArrayList<>();
for (int i : toSend) {
pairs.add(codecs.get(i));
}
return new MsgTrackedValues.TrackerEntries(id, false, pairs);
}
@SuppressWarnings("unchecked")
synchronized void load(boolean wipe, List<Pair<?>> values) {
if (wipe) {
synchronized void load(MsgTrackedValues.TrackerEntries values) {
if (values.wipe()) {
codecs.clear();
codecs.addAll(values);
for (var value : values) {
Consumer<?> callback = loadCallbacks.get(value.id);
if (callback != null) {
((Consumer<Object>)callback).accept(value.value);
codecs.addAll(values.values());
for (var entry : persistentObjects.int2ObjectEntrySet()) {
Pair<?> pair = codecs.get(entry.getIntKey());
if (pair != null) {
entry.getValue().readTrackedNbt((NbtCompound)pair.value);
}
}
} else {
values.forEach(value -> {
for (var value : values.values()) {
if (value.id >= 0 && value.id < codecs.size()) {
if (codecs.get(value.id).type == value.type) {
codecs.set(value.id, value);
Consumer<?> callback = loadCallbacks.get(value.id);
if (callback != null) {
((Consumer<Object>)callback).accept(value.value);
TrackableObject o = persistentObjects.get(value.id);
if (o != null) {
o.readTrackedNbt((NbtCompound)value.value);
}
}
}
});
}
}
}
public void close() {
manager.closeTracker(id);
}
public record Entry<T>(int id) {}
static class Pair<T> {
private final TrackableDataType<T> type;
@ -142,4 +140,8 @@ public class DataTracker {
type.write(buffer, value);
}
}
public interface Updater<T> {
void update(T t);
}
}

View file

@ -2,22 +2,19 @@ package com.minelittlepony.unicopia.network.track;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import com.minelittlepony.unicopia.network.Channel;
import com.minelittlepony.unicopia.util.Tickable;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.entity.Entity;
public class DataTrackerManager implements Tickable {
private final Entity entity;
final boolean isClient;
private final Int2ObjectOpenHashMap<DataTracker> trackers = new Int2ObjectOpenHashMap<>();
private IntSet discardedTrackers = new IntOpenHashSet();
private int nextId = 0;
private final List<DataTracker> trackers = new ObjectArrayList<>();
private final List<ObjectTracker<?>> objectTrackers = new ObjectArrayList<>();
private final DataTracker primaryTracker = checkoutTracker();
@ -31,20 +28,15 @@ public class DataTrackerManager implements Tickable {
}
public synchronized DataTracker checkoutTracker() {
DataTracker tracker = new DataTracker(this, nextId++);
trackers.put(tracker.id, tracker);
DataTracker tracker = new DataTracker(this, trackers.size());
trackers.add(tracker);
return tracker;
}
synchronized void closeTracker(int id) {
if (id <= 0) {
return;
}
trackers.remove(id);
if (!isClient) {
discardedTrackers.add(id);
}
public synchronized <T extends TrackableObject> ObjectTracker<T> checkoutTracker(Supplier<T> objFunction) {
ObjectTracker<T> tracker = new ObjectTracker<>(objectTrackers.size(), objFunction);
objectTrackers.add(tracker);
return tracker;
}
@Override
@ -53,35 +45,41 @@ public class DataTrackerManager implements Tickable {
return;
}
synchronized (this) {
List<MsgTrackedValues.TrackerEntries> toTransmit = new ArrayList<>();
List<MsgTrackedValues.TrackerEntries> toTransmit = new ArrayList<>();
List<MsgTrackedValues.TrackerObjects> objToTransmit = new ArrayList<>();
for (var entry : trackers.int2ObjectEntrySet()) {
MsgTrackedValues.TrackerEntries dirtyPairs = entry.getValue().getDirtyPairs();
if (dirtyPairs != null) {
toTransmit.add(dirtyPairs);
}
synchronized (this) {
for (var entry : trackers) {
entry.getDirtyPairs(toTransmit);
}
if (!toTransmit.isEmpty() || !discardedTrackers.isEmpty()) {
MsgTrackedValues packet = new MsgTrackedValues(entity.getId(), toTransmit, discardedTrackers.toIntArray());
discardedTrackers = new IntOpenHashSet();
for (var entry : objectTrackers) {
entry.getDirtyPairs(objToTransmit);
}
if (!toTransmit.isEmpty() || !objToTransmit.isEmpty()) {
MsgTrackedValues packet = new MsgTrackedValues(
entity.getId(),
objToTransmit,
toTransmit
);
Channel.SERVER_TRACKED_ENTITY_DATA.sendToSurroundingPlayers(packet, entity);
}
}
}
synchronized void load(MsgTrackedValues packet) {
for (int id : packet.removedTrackers()) {
closeTracker(id);
}
for (var update : packet.updatedTrackers()) {
DataTracker tracker = trackers.get(update.id());
if (tracker == null) {
tracker = new DataTracker(this, update.id());
trackers.put(update.id(), tracker);
if (tracker != null) {
tracker.load(update);
}
}
for (var update : packet.updatedObjects()) {
ObjectTracker<?> tracker = objectTrackers.get(update.id());
if (tracker != null) {
tracker.load(update);
}
tracker.load(update.wipe(), update.values());
}
}
}

View file

@ -1,27 +1,38 @@
package com.minelittlepony.unicopia.network.track;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import com.sollace.fabwork.api.packets.HandledPacket;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
public record MsgTrackedValues(
int owner,
List<TrackerEntries> updatedTrackers,
int[] removedTrackers
List<TrackerObjects> updatedObjects,
List<TrackerEntries> updatedTrackers
) implements HandledPacket<PlayerEntity> {
public MsgTrackedValues(PacketByteBuf buffer) {
this(buffer.readInt(), buffer.readCollection(ArrayList::new, TrackerEntries::new), buffer.readIntArray());
this(
buffer.readInt(),
buffer.readCollection(ArrayList::new, TrackerObjects::new),
buffer.readCollection(ArrayList::new, TrackerEntries::new)
);
}
@Override
public void toBuffer(PacketByteBuf buffer) {
buffer.writeInt(owner);
buffer.writeCollection(updatedObjects, (buf, obj) -> obj.write(buf));
buffer.writeCollection(updatedTrackers, (buf, tracker) -> tracker.write(buf));
buffer.writeIntArray(removedTrackers);
}
@Override
@ -32,6 +43,22 @@ public record MsgTrackedValues(
}
}
public record TrackerObjects(int id, Set<UUID> removedValues, Map<UUID, NbtCompound> values) {
public TrackerObjects(PacketByteBuf buffer) {
this(
buffer.readInt(),
buffer.readCollection(HashSet::new, PacketByteBuf::readUuid),
buffer.readMap(HashMap::new, PacketByteBuf::readUuid, PacketByteBuf::readNbt)
);
}
public void write(PacketByteBuf buffer) {
buffer.writeInt(id);
buffer.writeCollection(removedValues, PacketByteBuf::writeUuid);
buffer.writeMap(values, PacketByteBuf::writeUuid, PacketByteBuf::writeNbt);
}
}
public record TrackerEntries(int id, boolean wipe, List<DataTracker.Pair<?>> values) {
public TrackerEntries(PacketByteBuf buffer) {
this(buffer.readInt(), buffer.readBoolean(), buffer.readCollection(ArrayList::new, DataTracker.Pair::new));

View file

@ -0,0 +1,144 @@
package com.minelittlepony.unicopia.network.track;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.util.Util;
public class ObjectTracker<T extends TrackableObject> implements NbtSerialisable {
private final Map<UUID, T> trackedObjects = new Object2ObjectOpenHashMap<>();
private volatile Map<UUID, T> quickAccess = Map.of();
private final int id;
private final Supplier<T> constructor;
public ObjectTracker(int id, Supplier<T> constructor) {
this.id = id;
this.constructor = constructor;
}
public Set<UUID> keySet() {
return quickAccess.keySet();
}
public Collection<T> values() {
return quickAccess.values();
}
@Nullable
public T get(UUID id) {
return quickAccess.get(id);
}
@Nullable
public T remove(UUID id, boolean immediate) {
T entry = quickAccess.get(id);
if (entry != null) {
entry.discard(immediate);
}
return entry;
}
public boolean contains(UUID id) {
return quickAccess.containsKey(id);
}
public boolean isEmpty() {
return quickAccess.isEmpty();
}
public boolean clear(boolean immediate) {
if (isEmpty()) {
return false;
}
values().forEach(value -> value.discard(immediate));
return true;
}
public synchronized void add(T obj) {
trackedObjects.put(obj.getUuid(), obj);
quickAccess = Map.copyOf(trackedObjects);
}
synchronized void getDirtyPairs(List<MsgTrackedValues.TrackerObjects> output) {
if (!trackedObjects.isEmpty()) {
Map<UUID, NbtCompound> trackableCompounds = 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 status == TrackableObject.Status.REMOVED;
});
quickAccess = Map.copyOf(trackedObjects);
if (!trackableCompounds.isEmpty() || !removedTrackableObjects.isEmpty()) {
output.add(new MsgTrackedValues.TrackerObjects(id, removedTrackableObjects, trackableCompounds));
}
}
}
synchronized void load(MsgTrackedValues.TrackerObjects objects) {
objects.removedValues().forEach(removedId -> {
T o = trackedObjects.remove(removedId);
if (o != null) {
o.discard(true);
}
});
objects.values().forEach((id, nbt) -> {
T o = trackedObjects.get(id);
if (o == null) {
o = constructor.get();
trackedObjects.put(id, o);
}
o.readTrackedNbt(nbt);
});
quickAccess = Map.copyOf(trackedObjects);
}
@Override
public synchronized void toNBT(NbtCompound compound) {
quickAccess.forEach((id, value) -> {
compound.put(id.toString(), value.toTrackedNbt());
});
}
@Override
public void fromNBT(NbtCompound compound) {
Map<UUID, T> values = new Object2ObjectOpenHashMap<>();
compound.getKeys().forEach(key -> {
try {
UUID id = UUID.fromString(key);
if (id != null && !Util.NIL_UUID.equals(id)) {
NbtCompound nbt = compound.getCompound(key);
T entry = constructor.get();
entry.readTrackedNbt(nbt);
values.put(id, entry);
}
} catch (Throwable t) {
Unicopia.LOGGER.warn("Exception loading tracked object", t);
}
});
synchronized (this) {
trackedObjects.clear();
trackedObjects.putAll(values);
quickAccess = Map.copyOf(trackedObjects);
}
}
}

View file

@ -0,0 +1,24 @@
package com.minelittlepony.unicopia.network.track;
import java.util.UUID;
import net.minecraft.nbt.NbtCompound;
public interface TrackableObject {
UUID getUuid();
Status getStatus();
NbtCompound toTrackedNbt();
void readTrackedNbt(NbtCompound compound);
void discard(boolean immediate);
public enum Status {
DEFAULT,
NEW,
UPDATED,
REMOVED
}
}

View file

@ -9,8 +9,9 @@ import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.ability.magic.Affine;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.Levelled;
import com.minelittlepony.unicopia.ability.magic.SpellContainer;
import com.minelittlepony.unicopia.ability.magic.SpellContainer.Operation;
import com.minelittlepony.unicopia.ability.magic.SpellInventory;
import com.minelittlepony.unicopia.ability.magic.SpellInventory.Operation;
import com.minelittlepony.unicopia.ability.magic.SpellSlots;
import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.block.state.StatePredicate;
@ -18,9 +19,6 @@ import com.minelittlepony.unicopia.entity.EntityPhysics;
import com.minelittlepony.unicopia.entity.MagicImmune;
import com.minelittlepony.unicopia.entity.Physics;
import com.minelittlepony.unicopia.entity.mob.UEntities;
import com.minelittlepony.unicopia.network.datasync.EffectSync;
import com.minelittlepony.unicopia.network.track.Trackable;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.data.DataTracker;
@ -34,8 +32,8 @@ import net.minecraft.world.World;
public class MagicBeamEntity extends MagicProjectileEntity implements Caster<MagicBeamEntity>, MagicImmune {
private static final TrackedData<Boolean> HYDROPHOBIC = DataTracker.registerData(MagicBeamEntity.class, TrackedDataHandlerRegistry.BOOLEAN);
private final EffectSync effectDelegate = new EffectSync(this, Trackable.of(this).getDataTrackers().getPrimaryTracker());
private final EntityPhysics<MagicProjectileEntity> physics = new EntityPhysics<>(this, Trackable.of(this).getDataTrackers().getPrimaryTracker());
private final SpellInventory spells = SpellSlots.ofSingle(this);
private final EntityPhysics<MagicProjectileEntity> physics = new EntityPhysics<>(this);
public MagicBeamEntity(EntityType<MagicBeamEntity> type, World world) {
super(type, world);
@ -61,10 +59,9 @@ public class MagicBeamEntity extends MagicProjectileEntity implements Caster<Mag
super.tick();
if (getOwner() != null) {
effectDelegate.tick(Situation.PROJECTILE);
spells.tick(Situation.PROJECTILE);
}
if (getHydrophobic()) {
if (StatePredicate.isFluid(getWorld().getBlockState(getBlockPos()))) {
Vec3d vel = getVelocity();
@ -116,8 +113,8 @@ public class MagicBeamEntity extends MagicProjectileEntity implements Caster<Mag
}
@Override
public SpellContainer getSpellSlot() {
return effectDelegate;
public SpellSlots getSpellSlot() {
return spells.getSlots();
}
@Override
@ -139,7 +136,7 @@ public class MagicBeamEntity extends MagicProjectileEntity implements Caster<Mag
@Override
protected <T extends ProjectileDelegate> void forEachDelegates(Consumer<T> consumer, Function<Object, T> predicate) {
effectDelegate.tick(spell -> {
spells.tick(spell -> {
Optional.ofNullable(predicate.apply(spell)).ifPresent(consumer);
return Operation.SKIP;
});
@ -151,9 +148,7 @@ public class MagicBeamEntity extends MagicProjectileEntity implements Caster<Mag
super.readCustomDataFromNbt(compound);
getDataTracker().set(HYDROPHOBIC, compound.getBoolean("hydrophobic"));
physics.fromNBT(compound);
if (compound.contains("effect")) {
getSpellSlot().put(Spell.readNbt(compound.getCompound("effect")));
}
spells.getSlots().fromNBT(compound);
}
@Override
@ -161,8 +156,6 @@ public class MagicBeamEntity extends MagicProjectileEntity implements Caster<Mag
super.writeCustomDataToNbt(compound);
compound.putBoolean("hydrophobic", getHydrophobic());
physics.toNBT(compound);
getSpellSlot().get().ifPresent(effect -> {
compound.put("effect", Spell.writeNbt(effect));
});
spells.getSlots().toNBT(compound);
}
}