mirror of
https://github.com/Sollace/Unicopia.git
synced 2025-02-17 10:24:23 +01:00
Sync each spell individually
This commit is contained in:
parent
771da650c4
commit
88cf90842f
33 changed files with 762 additions and 699 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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() + "]";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue