Change particle handles and linking to be a little more robust. Should help with some cases where duplicate particles happen

This commit is contained in:
Sollace 2021-12-24 00:20:47 +02:00
parent 3806799cf2
commit 063d4e6dd2
11 changed files with 97 additions and 82 deletions

View file

@ -1,6 +1,7 @@
package com.minelittlepony.unicopia.ability.magic; package com.minelittlepony.unicopia.ability.magic;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -30,8 +31,18 @@ public interface SpellContainer {
public boolean forEach(Function<Spell, Operation> action, boolean update) { public boolean forEach(Function<Spell, Operation> action, boolean update) {
return false; return false;
} }
@Override
public boolean contains(UUID id) {
return false;
}
}; };
/**
* Checks if a spell with the given uuid is present.
*/
boolean contains(UUID id);
/** /**
* Gets the active effect for this caster updating it if needed. * Gets the active effect for this caster updating it if needed.
*/ */
@ -79,6 +90,11 @@ public interface SpellContainer {
SpellContainer delegate(); SpellContainer delegate();
@Override
default boolean contains(UUID id) {
return delegate().contains(id);
}
@Override @Override
default <T extends Spell> Optional<T> get(@Nullable SpellPredicate<T> type, boolean update) { default <T extends Spell> Optional<T> get(@Nullable SpellPredicate<T> type, boolean update) {
return delegate().get(type, update); return delegate().get(type, update);

View file

@ -54,10 +54,10 @@ public class JoustingSpell extends AbstractSpell {
} }
if (source.isClient()) { if (source.isClient()) {
particlEffect.ifAbsent(source, spawner -> { particlEffect.ifAbsent(getUuid(), source, spawner -> {
spawner.addParticle(UParticles.RAINBOOM_TRAIL, source.getOriginVector(), Vec3d.ZERO); spawner.addParticle(UParticles.RAINBOOM_TRAIL, source.getOriginVector(), Vec3d.ZERO);
spawner.addParticle(new OrientedBillboardParticleEffect(UParticles.RAINBOOM_RING, source.getPhysics().getMotionAngle()), source.getOriginVector(), Vec3d.ZERO); spawner.addParticle(new OrientedBillboardParticleEffect(UParticles.RAINBOOM_RING, source.getPhysics().getMotionAngle()), source.getOriginVector(), Vec3d.ZERO);
}).ifPresent(p -> p.attach(source)); });
} }
LivingEntity owner = source.getMaster(); LivingEntity owner = source.getMaster();

View file

@ -82,10 +82,9 @@ public class PlaceableSpell extends AbstractDelegatingSpell {
return super.tick(source, Situation.GROUND); return super.tick(source, Situation.GROUND);
} else if (situation == Situation.GROUND_ENTITY) { } else if (situation == Situation.GROUND_ENTITY) {
particlEffect.ifAbsent(source, spawner -> { particlEffect.ifAbsent(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 -> {
p.attach(source);
p.setAttribute(1, spell.getType().getColor()); p.setAttribute(1, spell.getType().getColor());
}); });
} }

View file

@ -55,10 +55,9 @@ public class DarkVortexSpell extends AttractiveSpell {
float radius = (float)getDrawDropOffRange(source) / 2; float radius = (float)getDrawDropOffRange(source) / 2;
particlEffect.ifAbsent(source, spawner -> { particlEffect.ifAbsent(getUuid(), source, spawner -> {
spawner.addParticle(new SphereParticleEffect(getType().getColor(), 0.99F, radius), source.getOriginVector(), Vec3d.ZERO); spawner.addParticle(new SphereParticleEffect(getType().getColor(), 0.99F, radius), source.getOriginVector(), Vec3d.ZERO);
}).ifPresent(p -> { }).ifPresent(p -> {
p.attach(source);
p.setAttribute(0, radius); p.setAttribute(0, radius);
}); });
} }

View file

@ -70,10 +70,9 @@ public class ShieldSpell extends AbstractSpell {
source.addParticle(new MagicParticleEffect(getType().getColor()), pos, Vec3d.ZERO); source.addParticle(new MagicParticleEffect(getType().getColor()), pos, Vec3d.ZERO);
}); });
particlEffect.ifAbsent(source, spawner -> { particlEffect.ifAbsent(getUuid(), source, spawner -> {
spawner.addParticle(new SphereParticleEffect(getType().getColor(), 0.3F, radius), source.getOriginVector(), Vec3d.ZERO); spawner.addParticle(new SphereParticleEffect(getType().getColor(), 0.3F, radius), source.getOriginVector(), Vec3d.ZERO);
}).ifPresent(p -> { }).ifPresent(p -> {
p.attach(source);
p.setAttribute(0, radius); p.setAttribute(0, radius);
p.setAttribute(1, getType().getColor()); p.setAttribute(1, getType().getColor());
}); });

View file

@ -2,6 +2,7 @@ package com.minelittlepony.unicopia.client.particle;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
@ -22,7 +23,7 @@ public class RainbowTrailParticle extends AbstractBillboardParticle implements A
private final List<Segment> segments = new ArrayList<>(); private final List<Segment> segments = new ArrayList<>();
private final Link link = new Link(); private Optional<Link> link = Optional.empty();
public RainbowTrailParticle(DefaultParticleType effect, ClientWorld world, double x, double y, double z, double velocityX, double velocityY, double velocityZ) { public RainbowTrailParticle(DefaultParticleType effect, ClientWorld world, double x, double y, double z, double velocityX, double velocityY, double velocityZ) {
super(world, x, y, z, velocityX, velocityY, velocityZ); super(world, x, y, z, velocityX, velocityY, velocityZ);
@ -41,13 +42,13 @@ public class RainbowTrailParticle extends AbstractBillboardParticle implements A
} }
@Override @Override
public void attach(Caster<?> caster) { public void attach(Link link) {
link.attach(caster); this.link = Optional.of(link);
} }
@Override @Override
public void detach() { public void detach() {
link.detach(); link = Optional.empty();
} }
@Override @Override
@ -90,9 +91,9 @@ public class RainbowTrailParticle extends AbstractBillboardParticle implements A
public void tick() { public void tick() {
super.tick(); super.tick();
if (link.linked()) { if (link.isPresent()) {
age = 0; age = 0;
link.ifAbsent(() -> {}).ifPresent(this::follow); link.flatMap(Link::get).ifPresent(this::follow);
} else if (!dead) { } else if (!dead) {
follow(Pony.of(MinecraftClient.getInstance().player)); follow(Pony.of(MinecraftClient.getInstance().player));
} }

View file

@ -1,5 +1,7 @@
package com.minelittlepony.unicopia.client.particle; package com.minelittlepony.unicopia.client.particle;
import java.util.Optional;
import com.minelittlepony.common.util.Color; import com.minelittlepony.common.util.Color;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.particle.OrientedBillboardParticleEffect; import com.minelittlepony.unicopia.particle.OrientedBillboardParticleEffect;
@ -33,7 +35,7 @@ public class RunesParticle extends OrientedBillboardParticle implements Attachme
private float prevRotationAngle; private float prevRotationAngle;
private float rotationAngle; private float rotationAngle;
private final Link link = new Link(); private Optional<Link> link = Optional.empty();
private int stasisAge = -1; private int stasisAge = -1;
@ -52,18 +54,18 @@ public class RunesParticle extends OrientedBillboardParticle implements Attachme
} }
@Override @Override
public void attach(Caster<?> caster) { public void attach(Link link) {
link.attach(caster); this.link = Optional.of(link);
velocityX = 0; velocityX = 0;
velocityY = 0; velocityY = 0;
velocityZ = 0; velocityZ = 0;
Vec3d pos = caster.getOriginVector(); Vec3d pos = link.get().map(Caster::getOriginVector).orElse(Vec3d.ZERO);
setPos(pos.x, pos.y, pos.z); setPos(pos.x, pos.y, pos.z);
} }
@Override @Override
public void detach() { public void detach() {
link.detach(); link = Optional.empty();
} }
@Override @Override
@ -135,16 +137,14 @@ public class RunesParticle extends OrientedBillboardParticle implements Attachme
public void tick() { public void tick() {
super.tick(); super.tick();
if (link.linked()) { link.flatMap(Link::get).map(Caster::getEntity).ifPresentOrElse(e -> {
link.ifAbsent(this::detach).map(Caster::getEntity).ifPresent(e -> {
if (getAlphaScale() >= 0.9F) { if (getAlphaScale() >= 0.9F) {
if (stasisAge < 0) { if (stasisAge < 0) {
stasisAge = age; stasisAge = age;
} }
age = stasisAge; age = stasisAge;
} }
}); }, this::detach);
}
prevBaseSize = baseSize; prevBaseSize = baseSize;
if (baseSize < 3) { if (baseSize < 3) {

View file

@ -17,6 +17,9 @@ import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment;
import com.minelittlepony.unicopia.particle.ParticleHandle.Link; import com.minelittlepony.unicopia.particle.ParticleHandle.Link;
import com.minelittlepony.unicopia.util.ColorHelper; import com.minelittlepony.unicopia.util.ColorHelper;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
import java.util.Optional;
import com.minelittlepony.common.util.Color; import com.minelittlepony.common.util.Color;
public class SphereParticle extends Particle implements Attachment { public class SphereParticle extends Particle implements Attachment {
@ -28,7 +31,7 @@ public class SphereParticle extends Particle implements Attachment {
protected float lerpIncrement; protected float lerpIncrement;
protected float toRadius; protected float toRadius;
private final Link link = new Link(); private Optional<Link> link = Optional.empty();
private static final SphereModel MODEL = new SphereModel(); private static final SphereModel MODEL = new SphereModel();
@ -58,9 +61,9 @@ public class SphereParticle extends Particle implements Attachment {
} }
@Override @Override
public void attach(Caster<?> caster) { public void attach(Link link) {
setMaxAge(50000); setMaxAge(50000);
link.attach(caster); this.link = Optional.of(link);
} }
@Override @Override
@ -92,14 +95,14 @@ public class SphereParticle extends Particle implements Attachment {
public void tick() { public void tick() {
super.tick(); super.tick();
if (link.linked()) { if (link.isPresent()) {
link.ifAbsent(this::markDead).map(Caster::getEntity).ifPresent(e -> { link.flatMap(Link::get).map(Caster::getEntity).ifPresentOrElse(e -> {
setPos(e.getX(), e.getY() + 0.5, e.getZ()); setPos(e.getX(), e.getY() + 0.5, e.getZ());
prevPosX = e.lastRenderX; prevPosX = e.lastRenderX;
prevPosY = e.lastRenderY + 0.5; prevPosY = e.lastRenderY + 0.5;
prevPosZ = e.lastRenderZ; prevPosZ = e.lastRenderZ;
}); }, this::detach);
if (steps-- > 0) { if (steps-- > 0) {
radius += lerpIncrement; radius += lerpIncrement;

View file

@ -1,6 +1,7 @@
package com.minelittlepony.unicopia.network.datasync; package com.minelittlepony.unicopia.network.datasync;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -39,6 +40,11 @@ public class EffectSync implements SpellContainer {
this.param = param; this.param = param;
} }
@Override
public boolean contains(UUID id) {
return spells.containsReference(id);
}
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T extends Spell> Optional<T> get(@Nullable SpellPredicate<T> type, boolean update) { public <T extends Spell> Optional<T> get(@Nullable SpellPredicate<T> type, boolean update) {

View file

@ -34,6 +34,10 @@ public class NetworkedReferenceSet<T> {
this.factory = factory; this.factory = factory;
} }
public boolean containsReference(UUID id) {
return ids.contains(id);
}
public Stream<T> getReferences() { public Stream<T> getReferences() {
return ids.stream().map(id -> values.get(id)) return ids.stream().map(id -> values.get(id))
.map(a -> a.getReference()) .map(a -> a.getReference())

View file

@ -1,17 +1,18 @@
package com.minelittlepony.unicopia.particle; package com.minelittlepony.unicopia.particle;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.WeakHashMap;
import java.util.function.Consumer; import java.util.function.Consumer;
import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import net.fabricmc.api.EnvType; import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.particle.Particle; import net.minecraft.client.particle.Particle;
import net.minecraft.entity.Entity;
import net.minecraft.particle.ParticleEffect; import net.minecraft.particle.ParticleEffect;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
@ -19,30 +20,41 @@ import net.minecraft.util.math.Vec3d;
* A connection class for updating and persisting an attached particle effect. * A connection class for updating and persisting an attached particle effect.
*/ */
public class ParticleHandle { public class ParticleHandle {
private Optional<WeakReference<Attachment>> particleEffect = Optional.empty();
private Optional<Attachment> particleEffect = Optional.empty(); public Optional<Attachment> ifAbsent(UUID id, ParticleSource source, Consumer<ParticleSpawner> constructor) {
return get().or(() -> {
public Optional<Attachment> ifAbsent(ParticleSource source, Consumer<ParticleSpawner> constructor) {
particleEffect.filter(Attachment::isStillAlive).orElseGet(() -> {
if (source.getWorld().isClient) { if (source.getWorld().isClient) {
constructor.accept(this::addParticle); constructor.accept((effect, pos, vel) -> new ClientHandle().addParticle(id, source, effect, pos, vel));
} }
return null; return get();
}); });
return particleEffect;
} }
public void destroy() { public void destroy() {
particleEffect.ifPresent(Attachment::detach); get().ifPresent(Attachment::detach);
} }
private Optional<Attachment> get() {
return particleEffect.map(WeakReference::get).filter(Attachment::isStillAlive);
}
private final class ClientHandle {
private static final Map<UUID, Particle> SPAWNED_PARTICLES = new WeakHashMap<>();
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
private void addParticle(ParticleEffect effect, Vec3d pos, Vec3d vel) { private void addParticle(UUID id, ParticleSource source, ParticleEffect effect, Vec3d pos, Vec3d vel) {
Particle p = MinecraftClient.getInstance().particleManager.addParticle(effect, pos.x, pos.y, pos.z, vel.x, vel.y, vel.z); Particle p = SPAWNED_PARTICLES.computeIfAbsent(id, i -> {
Particle pp = MinecraftClient.getInstance().particleManager.addParticle(effect, pos.x, pos.y, pos.z, vel.x, vel.y, vel.z);
if (pp instanceof Attachment && source instanceof Caster<?>) {
((Attachment) pp).attach(new Link(id, (Caster<?>)source));
}
return pp;
});
if (p instanceof Attachment) { if (p instanceof Attachment) {
particleEffect = Optional.of((Attachment)p); particleEffect = Optional.of(new WeakReference<>((Attachment)p));
}
} }
} }
@ -50,7 +62,7 @@ public class ParticleHandle {
boolean isStillAlive(); boolean isStillAlive();
void attach(Caster<?> caster); void attach(Link link);
void detach(); void detach();
@ -58,41 +70,17 @@ public class ParticleHandle {
} }
public static final class Link { public static final class Link {
private Optional<WeakReference<Caster<?>>> caster = Optional.empty();
private Optional<Caster<?>> caster = Optional.empty();
private UUID effect; private UUID effect;
private boolean linked;
public void attach(Caster<?> caster) { private Link(UUID effect, Caster<?> caster) {
this.linked = true; this.caster = Optional.of(new WeakReference<>(caster));
this.caster = Optional.of(caster); this.effect = effect;
this.effect = caster.getSpellSlot().get(false).map(Spell::getUuid).orElse(null);
} }
public void detach() { public Optional<Caster<?>> get() {
caster = Optional.empty(); caster = caster.filter(r -> r.get() != null && r.get().getSpellSlot().contains(effect));
} return caster.map(WeakReference::get);
public boolean linked() {
return linked;
}
public Optional<Caster<?>> ifAbsent(Runnable action) {
caster = caster.filter(c -> {
Entity e = c.getEntity();
return Caster.of(e).orElse(null) == c
&& c.getSpellSlot().get(false)
.filter(s -> s.getUuid().equals(effect))
.isPresent()
&& e != null
&& c.getWorld().getEntityById(e.getId()) != null;
});
if (!caster.isPresent()) {
action.run();
}
return caster;
} }
} }
} }