Work on allowing more than one spell to be attached

This commit is contained in:
Sollace 2021-12-21 17:28:47 +02:00
parent bb50b20be4
commit f3d6c7ce28
22 changed files with 416 additions and 143 deletions

View file

@ -68,7 +68,7 @@ public class PegasusRainboomAbility implements Ability<Hit> {
if (player.getPhysics().isFlying() && !player.getSpellSlot().isPresent()) {
player.getMagicalReserves().getMana().multiply(0.1F);
player.addParticle(new OrientedBillboardParticleEffect(UParticles.RAINBOOM_RING, player.getPhysics().getMotionAngle()), player.getOriginVector(), Vec3d.ZERO);
player.setSpell(SpellType.RAINBOOM.create(SpellTraits.EMPTY));
player.getSpellSlot().put(SpellType.RAINBOOM.create(SpellTraits.EMPTY));
}
}

View file

@ -8,7 +8,6 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.Owned;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.entity.Physics;
import com.minelittlepony.unicopia.entity.PonyContainer;
import com.minelittlepony.unicopia.particle.ParticleSource;
@ -29,10 +28,6 @@ public interface Caster<E extends LivingEntity> extends Owned<E>, Levelled, Affi
SpellContainer getSpellSlot();
default void setSpell(@Nullable Spell spell) {
getSpellSlot().put(spell);
}
/**
* Gets the entity directly responsible for casting.
*/

View file

@ -1,6 +1,7 @@
package com.minelittlepony.unicopia.ability.magic;
import java.util.Optional;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
@ -15,6 +16,12 @@ public interface SpellContainer {
@Override
public void put(Spell effect) { }
@Override
public void clear() { }
@Override
public void removeIf(Predicate<Spell> effect, boolean update) { }
};
/**
@ -40,4 +47,39 @@ public interface SpellContainer {
* Sets the active effect.
*/
void put(@Nullable Spell effect);
/**
* Removes all matching active effects.
*/
void removeIf(Predicate<Spell> effect, boolean update);
/**
* Removes all effects currently active in this slot.
*/
void clear();
interface Delegate extends SpellContainer {
SpellContainer delegate();
@Override
default <T extends Spell> Optional<T> get(@Nullable SpellPredicate<T> type, boolean update) {
return delegate().get(type, update);
}
@Override
default void put(@Nullable Spell effect) {
delegate().put(effect);
}
@Override
default void removeIf(Predicate<Spell> effect, boolean update) {
delegate().removeIf(effect, update);
}
@Override
default void clear() {
delegate().clear();
}
}
}

View file

@ -63,7 +63,7 @@ public abstract class AbstractDelegatingSpell implements ProjectileSpell {
@Override
public boolean apply(Caster<?> caster) {
caster.setSpell(this);
caster.getSpellSlot().put(this);
return true;
}

View file

@ -71,7 +71,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell {
CastSpellEntity entity = UEntities.CAST_SPELL.create(source.getWorld());
Vec3d pos = castEntity.getPosition().orElse(source.getOriginVector());
entity.updatePositionAndAngles(pos.x, pos.y, pos.z, 0, 0);
entity.setSpell(this);
entity.getSpellSlot().put(this);
entity.setMaster(source.getMaster());
entity.world.spawnEntity(entity);

View file

@ -50,7 +50,7 @@ public final class ThrowableSpell extends AbstractDelegatingSpell {
MagicProjectileEntity projectile = new MagicProjectileEntity(world, entity);
projectile.setItem(GemstoneItem.enchanted(UItems.GEMSTONE.getDefaultStack(), spell.getType()));
projectile.setSpell(this);
projectile.getSpellSlot().put(this);
projectile.setProperties(entity, entity.getPitch(), entity.getYaw(), 0, 1.5F, 1);
projectile.setHydrophobic();
configureProjectile(projectile, caster);

View file

@ -67,7 +67,7 @@ public abstract class AbstractSpell implements Spell {
@Override
public final boolean apply(Caster<?> caster) {
caster.setSpell(this);
caster.getSpellSlot().put(this);
return true;
}

View file

@ -141,7 +141,7 @@ public final class SpellType<T extends Spell> implements Affine, SpellPredicate<
@Nullable
public T apply(Caster<?> caster, SpellTraits traits) {
if (isEmpty()) {
caster.setSpell(null);
caster.getSpellSlot().clear();
return null;
}

View file

@ -37,6 +37,19 @@ public class CastSpellEntity extends Entity implements Caster<LivingEntity> {
private final EntityReference<LivingEntity> owner = new EntityReference<>();
private final SpellContainer spell = new SpellContainer.Delegate() {
@Override
public SpellContainer delegate() {
return Caster.of(getMaster()).map(Caster::getSpellSlot).orElse(SpellContainer.EMPTY);
}
@Override
public void put(Spell spell) {
getDataTracker().set(SPELL, Optional.ofNullable(spell).map(Spell::getUuid));
SpellContainer.Delegate.super.put(spell);
}
};
private BlockPos lastPos;
private int orphanedTicks;
@ -94,7 +107,7 @@ public class CastSpellEntity extends Entity implements Caster<LivingEntity> {
UUID spellId = dataTracker.get(SPELL).orElse(null);
if (!c.getSpellSlot().get(true).filter(s -> s.getUuid().equals(spellId) && s.tick(this, Situation.GROUND_ENTITY)).isPresent()) {
c.setSpell(null);
c.getSpellSlot().clear();
return false;
}
@ -118,11 +131,6 @@ public class CastSpellEntity extends Entity implements Caster<LivingEntity> {
this.owner.set(owner);
}
@Override
public void setSpell(Spell spell) {
getDataTracker().set(SPELL, Optional.ofNullable(spell).map(Spell::getUuid));
}
@Override
public Entity getEntity() {
return this;
@ -150,7 +158,7 @@ public class CastSpellEntity extends Entity implements Caster<LivingEntity> {
@Override
public SpellContainer getSpellSlot() {
return Caster.of(getMaster()).map(Caster::getSpellSlot).orElse(SpellContainer.EMPTY);
return spell;
}
@Override

View file

@ -106,7 +106,7 @@ public class Creature extends Living<LivingEntity> {
public void fromNBT(NbtCompound compound) {
super.fromNBT(compound);
if (compound.contains("effect")) {
setSpell(SpellType.fromNBT(compound.getCompound("effect")));
getSpellSlot().put(SpellType.fromNBT(compound.getCompound("effect")));
}
physics.fromNBT(compound);
}

View file

@ -10,7 +10,7 @@ import com.minelittlepony.unicopia.ability.magic.SpellContainer;
import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.item.UItems;
import com.minelittlepony.unicopia.network.EffectSync;
import com.minelittlepony.unicopia.network.datasync.EffectSync;
import com.minelittlepony.unicopia.projectile.ProjectileImpactListener;
import com.minelittlepony.unicopia.util.MagicalDamageSource;
@ -85,11 +85,7 @@ public abstract class Living<T extends LivingEntity> implements Equine<T>, Caste
@Override
public void tick() {
getSpellSlot().get(true).ifPresent(effect -> {
if (!effect.tick(this, Situation.BODY)) {
setSpell(null);
}
});
getSpellSlot().removeIf(effect -> !effect.tick(this, Situation.BODY), true);
if (invinsibilityTicks > 0) {
invinsibilityTicks--;

View file

@ -196,7 +196,7 @@ public class Disguise implements NbtSerialisable, PlayerDimensions.Provider, Fli
return;
}
Caster.of(entity).ifPresent(c -> c.setSpell(null));
Caster.of(entity).ifPresent(c -> c.getSpellSlot().clear());
if (entity instanceof LivingEntity) {
((LivingEntity) entity).getAttributeInstance(PlayerAttributes.ENTITY_GRAVTY_MODIFIER).clearModifiers();

View file

@ -104,7 +104,7 @@ public class RaceChangeStatusEffect extends StatusEffect {
eq.setSpecies(species);
if (eq instanceof Caster) {
((Caster<?>)eq).setSpell(null);
((Caster<?>)eq).getSpellSlot().clear();
}
if (eq instanceof Pony) {

View file

@ -30,7 +30,8 @@ import com.minelittlepony.unicopia.item.toxin.Toxin;
import com.minelittlepony.unicopia.network.Channel;
import com.minelittlepony.unicopia.network.MsgOtherPlayerCapabilities;
import com.minelittlepony.unicopia.network.MsgRequestSpeciesChange;
import com.minelittlepony.unicopia.network.Transmittable;
import com.minelittlepony.unicopia.network.datasync.Transmittable;
import com.minelittlepony.unicopia.network.datasync.EffectSync.UpdateCallback;
import com.minelittlepony.unicopia.util.Copieable;
import com.minelittlepony.unicopia.util.MagicalDamageSource;
import com.minelittlepony.unicopia.util.Tickable;
@ -61,7 +62,7 @@ import net.minecraft.text.TranslatableText;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
public class Pony extends Living<PlayerEntity> implements Transmittable, Copieable<Pony> {
public class Pony extends Living<PlayerEntity> implements Transmittable, Copieable<Pony>, UpdateCallback {
private static final TrackedData<Integer> RACE = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.INTEGER);
@ -505,17 +506,16 @@ public class Pony extends Living<PlayerEntity> implements Transmittable, Copieab
public void copyFrom(Pony oldPlayer) {
speciesPersisted = oldPlayer.speciesPersisted;
if (!oldPlayer.getEntity().isRemoved()) {
setSpell(oldPlayer.getSpellSlot().get(true).orElse(null));
getSpellSlot().put(oldPlayer.getSpellSlot().get(true).orElse(null));
}
oldPlayer.setSpell(null);
oldPlayer.getSpellSlot().put(null);
setSpecies(oldPlayer.getSpecies());
getDiscoveries().copyFrom(oldPlayer.getDiscoveries());
setDirty();
}
@Override
public void setSpell(@Nullable Spell effect) {
super.setSpell(effect);
public void onSpellSet(@Nullable Spell spell) {
setDirty();
}

View file

@ -18,6 +18,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).ifPresent(c -> c.setSpell(null));
Caster.of(entity).ifPresent(c -> c.getSpellSlot().clear());
}
}

View file

@ -1,106 +0,0 @@
package com.minelittlepony.unicopia.network;
import java.util.Objects;
import java.util.Optional;
import org.jetbrains.annotations.Nullable;
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.Spell;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import net.minecraft.entity.data.TrackedData;
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 {
private Optional<Spell> spell = Optional.empty();
private final Caster<?> owner;
private final TrackedData<NbtCompound> param;
@Nullable
private NbtCompound lastValue;
public EffectSync(Caster<?> owner, TrackedData<NbtCompound> param) {
this.owner = owner;
this.param = param;
}
@Override
@SuppressWarnings("unchecked")
public <T extends Spell> Optional<T> get(@Nullable SpellPredicate<T> type, boolean update) {
if (update) {
sync(true);
}
if (checkReference() && (type == null || type.test(spell.get()))) {
return (Optional<T>)spell;
}
return Optional.empty();
}
@Override
public boolean isPresent() {
sync(false);
return checkReference();
}
private boolean checkReference() {
return spell.isPresent() && !spell.get().isDead();
}
private void sync(boolean force) {
@Nullable
NbtCompound comp = owner.getEntity().getDataTracker().get(param);
@Nullable
Spell effect = spell.orElse(null);
if (comp == null || !comp.contains("effect_id") || !comp.contains("uuid")) {
if (effect != null) {
updateReference(null);
}
} else if (effect == null || !effect.getUuid().equals(comp.getUuid("uuid"))) {
updateReference(SpellType.fromNBT(comp));
} else if (owner.isClient()) {
if (!Objects.equals(lastValue, comp)) {
lastValue = comp;
effect.fromNBT(comp);
}
} else if (force && effect.isDirty()) {
put(effect);
}
}
@Override
public void put(@Nullable Spell effect) {
effect = effect == null || effect.isDead() ? null : effect;
updateReference(effect);
owner.getEntity().getDataTracker().set(param, effect == null ? new NbtCompound() : SpellType.toNBT(effect));
}
private void updateReference(@Nullable Spell effect) {
@Nullable
Spell old = spell.orElse(null);
if (old != effect) {
spell = Optional.ofNullable(effect);
if (old != null && (effect == null || !old.getUuid().equals(effect.getUuid()))) {
old.setDead();
old.onDestroyed(owner);
}
}
}
}

View file

@ -0,0 +1,99 @@
package com.minelittlepony.unicopia.network.datasync;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
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.Spell;
import net.minecraft.entity.data.TrackedData;
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 {
private final NetworkedReferenceSet<Spell> spells;
private final Caster<?> owner;
private final TrackedData<NbtCompound> param;
@Nullable
private NbtCompound lastValue;
public EffectSync(Caster<?> owner, TrackedData<NbtCompound> param) {
spells = new NetworkedReferenceSet<>(Spell::getUuid, () -> new SpellNetworkedReference<>(owner));
this.owner = owner;
this.param = param;
}
@Override
@SuppressWarnings("unchecked")
public <T extends Spell> Optional<T> get(@Nullable SpellPredicate<T> type, boolean update) {
return (Optional<T>)(type == null ? read(update, true).findFirst() : read(update, true).filter(type).findFirst());
}
@Override
public boolean isPresent() {
return read(true, false).findFirst().isPresent();
}
@Override
public void put(@Nullable Spell effect) {
spells.addReference(effect);
write();
if (owner instanceof UpdateCallback) {
((UpdateCallback)owner).onSpellSet(effect);
}
}
@Override
public void removeIf(Predicate<Spell> test, boolean update) {
read(effect -> {
if (test.test(effect)) {
spells.removeReference(effect);
}
});
}
@Override
public void clear() {
put(null);
}
private Stream<Spell> read(boolean synchronize, boolean sendUpdate) {
if (synchronize && spells.fromNbt(owner.getEntity().getDataTracker().get(param)) && sendUpdate) {
write();
}
return spells.getReferences();
}
private void read(Consumer<Spell> consumer) {
spells.fromNbt(owner.getEntity().getDataTracker().get(param));
spells.getReferences().toList().forEach(consumer);
write();
}
private void write() {
if (spells.isDirty()) {
owner.getEntity().getDataTracker().set(param, spells.toNbt());
}
}
public interface UpdateCallback {
void onSpellSet(@Nullable Spell spell);
}
}

View file

@ -0,0 +1,19 @@
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();
Optional<T> updateReference(@Nullable T newValue);
boolean fromNbt(NbtCompound comp);
NbtCompound toNbt();
boolean isDirty();
}

View file

@ -0,0 +1,110 @@
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.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;
public NetworkedReferenceSet(Function<T, UUID> uuidConverter, Supplier<NetworkedReference<T>> factory) {
this.uuidConverter = uuidConverter;
this.factory = factory;
}
public Stream<T> getReferences() {
return ids.stream().map(id -> values.get(id))
.map(a -> a.getReference())
.filter(Optional::isPresent)
.map(Optional::get);
}
public void addReference(@Nullable T newValue) {
if (newValue != null) {
addReference(uuidConverter.apply(newValue));
}
}
private 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));
}
}
private void removeReference(UUID id) {
dirty |= ids.remove(id);
NetworkedReference<T> i = values.remove(id);
if (i != null) {
dirty = true;
i.updateReference(null);
}
}
public boolean fromNbt(NbtCompound comp) {
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)).forEach(this::removeReference);
boolean[] send = new boolean[0];
incoming.forEach(kept -> {
NetworkedReference<T> i = addReference(kept);
send[0] |= i.fromNbt(comp.getCompound(kept.toString()));
if (i.getReference().isEmpty()) {
removeReference(kept);
}
});
dirty = false;
return send[0];
}
public NbtCompound toNbt() {
NbtCompound tag = new NbtCompound();
NbtList ids = new NbtList();
this.ids.forEach(id -> {
String sid = id.toString();
ids.add(NbtString.of(sid));
tag.put(sid, values.get(id).toNbt());
});
tag.put("key", ids);
dirty = false;
return tag;
}
public boolean isDirty() {
return dirty || values.values().stream().anyMatch(NetworkedReference::isDirty);
}
}

View file

@ -0,0 +1,110 @@
package com.minelittlepony.unicopia.network.datasync;
import java.util.Objects;
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.effect.SpellType;
import net.minecraft.nbt.NbtCompound;
public class SpellNetworkedReference<T extends Spell> implements NetworkedReference<T> {
private Optional<T> currentValue = Optional.empty();
@Nullable
private NbtCompound lastValue;
private final Caster<?> owner;
private boolean dirty;
public SpellNetworkedReference(Caster<?> owner) {
this.owner = owner;
}
@Override
public Optional<T> getReference() {
return currentValue.filter(s -> !s.isDead());
}
private boolean mustDelete(@Nullable NbtCompound comp) {
return (comp == null || !comp.contains("effect_id") || !comp.contains("uuid")) && currentValue.isPresent();
}
private boolean mustReplace(NbtCompound comp) {
return currentValue.isEmpty() || !currentValue.get().getUuid().equals(comp.getUuid("uuid"));
}
private boolean mustUpdate(NbtCompound comp) {
if (owner.isClient() && !Objects.equals(lastValue, comp)) {
lastValue = comp;
return true;
}
return false;
}
private boolean mustSend() {
return currentValue.filter(Spell::isDirty).isPresent();
}
@Override
public Optional<T> updateReference(@Nullable T newValue) {
newValue = newValue == null || newValue.isDead() ? null : newValue;
@Nullable
T oldValue = currentValue.orElse(null);
if (oldValue != newValue) {
dirty = true;
currentValue = Optional.ofNullable(newValue);
if (oldValue != null && (newValue == null || !oldValue.getUuid().equals(newValue.getUuid()))) {
oldValue.setDead();
oldValue.onDestroyed(owner);
}
}
return currentValue;
}
@Override
@SuppressWarnings("unchecked")
public boolean fromNbt(NbtCompound comp) {
dirty = false;
if (mustDelete(comp)) {
updateReference(null);
return false;
}
if (mustReplace(comp)) {
updateReference((T)SpellType.fromNBT(comp));
return false;
}
if (mustUpdate(comp)) {
currentValue.ifPresent(s -> s.fromNBT(comp));
return false;
}
if (mustSend()) {
updateReference(getReference().orElse(null));
return true;
}
return false;
}
@Override
public NbtCompound toNbt() {
dirty = false;
return getReference().map(SpellType::toNBT).orElseGet(NbtCompound::new);
}
@Override
public boolean isDirty() {
return dirty || mustSend();
}
}

View file

@ -1,4 +1,4 @@
package com.minelittlepony.unicopia.network;
package com.minelittlepony.unicopia.network.datasync;
public interface Transmittable {
void sendCapabilities(boolean full);

View file

@ -16,8 +16,8 @@ import com.minelittlepony.unicopia.entity.Physics;
import com.minelittlepony.unicopia.entity.UEntities;
import com.minelittlepony.unicopia.item.UItems;
import com.minelittlepony.unicopia.network.Channel;
import com.minelittlepony.unicopia.network.EffectSync;
import com.minelittlepony.unicopia.network.MsgSpawnProjectile;
import com.minelittlepony.unicopia.network.datasync.EffectSync;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
@ -220,7 +220,7 @@ public class MagicProjectileEntity extends ThrownItemEntity implements Caster<Li
super.readCustomDataFromNbt(compound);
physics.fromNBT(compound);
if (compound.contains("effect")) {
setSpell(SpellType.fromNBT(compound.getCompound("effect")));
getSpellSlot().put(SpellType.fromNBT(compound.getCompound("effect")));
}
}