Fixed spell entities becoming detached when cancelling the spell from very far away

This commit is contained in:
Sollace 2022-09-16 18:35:13 +02:00
parent 734c256822
commit 2a9f19fc9f
3 changed files with 118 additions and 30 deletions

View file

@ -6,6 +6,7 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType;
import com.minelittlepony.unicopia.block.data.Ether;
import com.minelittlepony.unicopia.entity.CastSpellEntity; import com.minelittlepony.unicopia.entity.CastSpellEntity;
import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.entity.UEntities; import com.minelittlepony.unicopia.entity.UEntities;
@ -86,6 +87,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell {
entity.getSpellSlot().put(spell.toPlaceable()); entity.getSpellSlot().put(spell.toPlaceable());
entity.setMaster(source); entity.setMaster(source);
entity.world.spawnEntity(entity); entity.world.spawnEntity(entity);
Ether.get(entity.world).put(getType(), entity);
castEntity.set(entity); castEntity.set(entity);
setDirty(); setDirty();
@ -96,6 +98,16 @@ public class PlaceableSpell extends AbstractDelegatingSpell {
} }
if (situation == Situation.GROUND_ENTITY) { if (situation == Situation.GROUND_ENTITY) {
if (!source.isClient()) {
Ether ether = Ether.get(source.getReferenceWorld());
if (ether.getEntry(getType(), source).filter(Ether.Entry::isAlive).isEmpty()) {
ether.remove(getType(), source);
setDead();
return false;
}
}
particlEffect.update(getUuid(), source, spawner -> { particlEffect.update(getUuid(), source, spawner -> {
spawner.addParticle(new OrientedBillboardParticleEffect(UParticles.MAGIC_RUNES, 90, 0), source.getOriginVector(), Vec3d.ZERO); spawner.addParticle(new OrientedBillboardParticleEffect(UParticles.MAGIC_RUNES, 90, 0), source.getOriginVector(), Vec3d.ZERO);
}).ifPresent(p -> { }).ifPresent(p -> {
@ -108,24 +120,15 @@ public class PlaceableSpell extends AbstractDelegatingSpell {
return !isDead(); return !isDead();
} }
/**
* Detaches this spell from the placed version.
* This spell and the placed entity effectively become independent.
*
* @return The previous cast spell entity if one existed, otherwise empty.
*/
protected Optional<CastSpellEntity> detach(Caster<?> source) {
return getSpellEntity(source).map(e -> {
castEntity.set(null);
return e;
});
}
@Override @Override
public void onDestroyed(Caster<?> source) { public void onDestroyed(Caster<?> source) {
if (!source.isClient()) { if (!source.isClient()) {
castEntity.getId().ifPresent(id -> {
getWorld(source).map(Ether::get)
.flatMap(ether -> ether.getEntry(getType(), id))
.ifPresent(Ether.Entry::markDead);
});
getSpellEntity(source).ifPresent(e -> { getSpellEntity(source).ifPresent(e -> {
e.getSpellSlot().clear();
castEntity.set(null); castEntity.set(null);
}); });
} }

View file

@ -34,7 +34,7 @@ public class PortalSpell extends AbstractSpell {
if (situation == Situation.GROUND) { if (situation == Situation.GROUND) {
teleportationTarget.getPosition().ifPresentOrElse( teleportationTarget.getPosition().ifPresentOrElse(
targetPos -> tickWithTargetLink(source, targetPos), targetPos -> tickWithTargetLink(source, targetPos),
() -> advertiseAvailable(source) () -> findLink(source)
); );
if (!publishedPosition) { if (!publishedPosition) {
@ -76,15 +76,15 @@ public class PortalSpell extends AbstractSpell {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void advertiseAvailable(Caster<?> source) { private void findLink(Caster<?> source) {
Ether ether = Ether.get(source.getReferenceWorld()); Ether ether = Ether.get(source.getReferenceWorld());
ether.getIds(getType()) ether.getEntries(getType())
.stream() .stream()
.filter(id -> !id.referenceEquals(source.getEntity())) .filter(entry -> entry.isAvailable() && !entry.entity.referenceEquals(source.getEntity()))
.findAny() .findAny()
.ifPresent(ref -> { .ifPresent(entry -> {
ether.remove(getType(), ref.getId().get()); entry.setTaken(true);
teleportationTarget.copyFrom((EntityReference<CastSpellEntity>)ref); teleportationTarget.copyFrom((EntityReference<CastSpellEntity>)entry.entity);
}); });
} }
@ -106,4 +106,10 @@ public class PortalSpell extends AbstractSpell {
publishedPosition = compound.getBoolean("publishedPosition"); publishedPosition = compound.getBoolean("publishedPosition");
teleportationTarget.fromNBT(compound.getCompound("teleportationTarget")); teleportationTarget.fromNBT(compound.getCompound("teleportationTarget"));
} }
@Override
public void setDead() {
super.setDead();
particlEffect.destroy();
}
} }

View file

@ -6,6 +6,7 @@ import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.util.NbtSerialisable;
import net.minecraft.nbt.*; import net.minecraft.nbt.*;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
@ -20,7 +21,7 @@ public class Ether extends PersistentState {
return WorldOverlay.getPersistableStorage(world, ID, Ether::new, Ether::new); return WorldOverlay.getPersistableStorage(world, ID, Ether::new, Ether::new);
} }
private final Map<Identifier, Set<EntityReference<?>>> advertisingEndpoints = new HashMap<>(); private final Map<Identifier, Set<Entry>> advertisingEndpoints = new HashMap<>();
private final Object locker = new Object(); private final Object locker = new Object();
@ -29,9 +30,11 @@ public class Ether extends PersistentState {
compound.getKeys().forEach(key -> { compound.getKeys().forEach(key -> {
Identifier typeId = Identifier.tryParse(key); Identifier typeId = Identifier.tryParse(key);
if (typeId != null) { if (typeId != null) {
Set<EntityReference<?>> uuids = getIds(typeId); Set<Entry> uuids = getEntries(typeId);
compound.getList(key, NbtElement.COMPOUND_TYPE).forEach(entry -> { compound.getList(key, NbtElement.COMPOUND_TYPE).forEach(entry -> {
uuids.add(new EntityReference<>((NbtCompound)entry)); Entry e = new Entry();
e.fromNBT((NbtCompound)entry);
uuids.add(e);
}); });
} }
}); });
@ -56,7 +59,7 @@ public class Ether extends PersistentState {
public void put(SpellType<?> spellType, Caster<?> caster) { public void put(SpellType<?> spellType, Caster<?> caster) {
synchronized (locker) { synchronized (locker) {
getIds(spellType.getId()).add(new EntityReference<>(caster.getEntity())); getEntries(spellType.getId()).add(new Entry(caster));
} }
markDirty(); markDirty();
} }
@ -64,9 +67,9 @@ public class Ether extends PersistentState {
public void remove(SpellType<?> spellType, UUID id) { public void remove(SpellType<?> spellType, UUID id) {
synchronized (locker) { synchronized (locker) {
Identifier typeId = spellType.getId(); Identifier typeId = spellType.getId();
Set<EntityReference<?>> refs = advertisingEndpoints.get(typeId); Set<Entry> refs = advertisingEndpoints.get(typeId);
if (refs != null) { if (refs != null) {
refs.removeIf(ref -> ref.getId().orElse(Util.NIL_UUID).equals(id)); refs.removeIf(ref -> ref.entity.getId().orElse(Util.NIL_UUID).equals(id));
if (refs.isEmpty()) { if (refs.isEmpty()) {
advertisingEndpoints.remove(typeId); advertisingEndpoints.remove(typeId);
} }
@ -75,13 +78,89 @@ public class Ether extends PersistentState {
} }
} }
public Set<EntityReference<?>> getIds(SpellType<?> spellType) { public void remove(SpellType<?> spellType, Caster<?> caster) {
return getIds(spellType.getId()); remove(spellType, caster.getEntity().getUuid());
} }
private Set<EntityReference<?>> getIds(Identifier typeId) { public Set<Entry> getEntries(SpellType<?> spellType) {
return getEntries(spellType.getId());
}
private Set<Entry> getEntries(Identifier typeId) {
synchronized (locker) { synchronized (locker) {
return advertisingEndpoints.computeIfAbsent(typeId, i -> new HashSet<>()); return advertisingEndpoints.computeIfAbsent(typeId, i -> new HashSet<>());
} }
} }
public Optional<Entry> getEntry(SpellType<?> spellType, Caster<?> caster) {
synchronized (locker) {
return getEntries(spellType).stream().filter(e -> e.entity.referenceEquals(caster.getEntity())).findFirst();
}
}
public Optional<Entry> getEntry(SpellType<?> spellType, UUID uuid) {
synchronized (locker) {
return getEntries(spellType).stream().filter(e -> e.equals(uuid)).findFirst();
}
}
public class Entry implements NbtSerialisable {
public final EntityReference<?> entity;
private boolean removed;
private boolean taken;
public Entry() {
entity = new EntityReference<>();
}
public Entry(Caster<?> caster) {
entity = new EntityReference<>(caster.getEntity());
}
public boolean isAlive() {
return !removed;
}
public void markDead() {
removed = true;
markDirty();
}
public boolean isAvailable() {
return !removed && !taken;
}
public void setTaken(boolean taken) {
this.taken = taken;
markDirty();
}
@Override
public void toNBT(NbtCompound compound) {
entity.toNBT(compound);
compound.putBoolean("remove", removed);
compound.putBoolean("taken", taken);
}
@Override
public void fromNBT(NbtCompound compound) {
entity.fromNBT(compound);
removed = compound.getBoolean("removed");
taken = compound.getBoolean("taken");
}
@Override
public boolean equals(Object other) {
return other instanceof Entry e
&& e.equals(entity.getId().orElse(Util.NIL_UUID));
}
public boolean equals(UUID uuid) {
return entity.getId().orElse(Util.NIL_UUID).equals(uuid);
}
@Override
public int hashCode() {
return entity.getId().orElse(Util.NIL_UUID).hashCode();
}
}
} }