diff --git a/src/main/java/com/minelittlepony/unicopia/InteractionManager.java b/src/main/java/com/minelittlepony/unicopia/InteractionManager.java index 0e507331..05a101d2 100644 --- a/src/main/java/com/minelittlepony/unicopia/InteractionManager.java +++ b/src/main/java/com/minelittlepony/unicopia/InteractionManager.java @@ -1,22 +1,17 @@ package com.minelittlepony.unicopia; import java.util.Map; -import java.util.Optional; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import com.minelittlepony.unicopia.ability.magic.CasterView; import com.minelittlepony.unicopia.entity.player.dummy.DummyPlayerEntity; -import com.minelittlepony.unicopia.server.world.Ether; import com.mojang.authlib.GameProfile; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.network.PacketByteBuf; -import net.minecraft.server.world.ServerWorld; import net.minecraft.util.Identifier; -import net.minecraft.world.BlockView; import net.minecraft.world.World; public class InteractionManager { @@ -37,13 +32,6 @@ public class InteractionManager { return INSTANCE; } - public Optional getCasterView(BlockView view) { - if (view instanceof ServerWorld world) { - return Optional.of(Ether.get(world)); - } - return Optional.empty(); - } - public Map readChapters(PacketByteBuf buf) { throw new RuntimeException("Method not supported"); } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java index 98264836..150b1004 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java @@ -11,6 +11,7 @@ import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.entity.*; import com.minelittlepony.unicopia.entity.damage.UDamageSources; import com.minelittlepony.unicopia.particle.ParticleSource; +import com.minelittlepony.unicopia.server.world.Ether; import com.minelittlepony.unicopia.server.world.ModificationType; import com.minelittlepony.unicopia.util.SoundEmitter; import com.minelittlepony.unicopia.util.VecHelper; @@ -99,11 +100,7 @@ public interface Caster extends } default boolean canCastAt(Vec3d pos) { - return findAllSpellsInRange(500, SpellType.ARCANE_PROTECTION::isOn).noneMatch(caster -> caster - .getSpellSlot().get(SpellType.ARCANE_PROTECTION, false) - .filter(spell -> spell.blocksMagicFor(caster, this, pos)) - .isPresent() - ); + return !Ether.get(asWorld()).anyMatch(SpellType.ARCANE_PROTECTION, (spell, caster) -> spell.blocksMagicFor(caster, this, pos)); } static Stream> stream(Stream entities) { diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/CasterView.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/CasterView.java deleted file mode 100644 index 572f4542..00000000 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/CasterView.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.minelittlepony.unicopia.ability.magic; - -import java.util.Map; -import java.util.function.Predicate; -import java.util.stream.Stream; - -import org.jetbrains.annotations.Nullable; - -import com.minelittlepony.unicopia.EquinePredicates; -import com.minelittlepony.unicopia.ability.magic.spell.Spell; -import com.minelittlepony.unicopia.util.VecHelper; - -import net.minecraft.entity.Entity; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Vec3d; -import net.minecraft.world.*; - -public interface CasterView { - EntityView getWorld(); - - default Stream, S>> findAllSpellsInRange(BlockPos pos, double radius, SpellPredicate type) { - return findAllCastersInRange(pos, radius).flatMap(caster -> { - return caster.getSpellSlot().stream(type, false).map(spell -> { - return Map.entry(caster, spell); - }); - }); - } - - default Stream> findAllCastersInRange(BlockPos pos, double radius) { - return findAllCastersInRange(pos, radius, null); - } - - default Stream> findAllCastersInRange(BlockPos pos, double radius, @Nullable Predicate test) { - return Caster.stream(findAllEntitiesInRange(pos, radius, test == null ? EquinePredicates.IS_CASTER : EquinePredicates.IS_CASTER.and(test))); - } - - default Stream findAllEntitiesInRange(BlockPos pos, double radius, @Nullable Predicate test) { - return VecHelper.findInRange(null, getWorld(), Vec3d.ofCenter(pos), radius, test).stream(); - } - - default Stream findAllEntitiesInRange(BlockPos pos, double radius) { - return findAllEntitiesInRange(pos, radius, null); - } -} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java index 7154056a..e3736e59 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java @@ -45,6 +45,12 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS */ private final ParticleHandle particlEffect = new ParticleHandle(); + /** + * ID of the placed counterpart of this spell. + */ + @Nullable + private UUID placedSpellId; + /** * The cast spell entity */ @@ -82,10 +88,8 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS @Override public boolean tick(Caster source, Situation situation) { - if (situation == Situation.BODY) { if (!source.isClient()) { - if (dimension == null) { dimension = source.asWorld().getRegistryKey(); if (source instanceof Pony) { @@ -105,8 +109,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS if (situation == Situation.GROUND_ENTITY) { if (!source.isClient()) { - Ether ether = Ether.get(source.asWorld()); - if (ether.getEntry(getType(), source).isEmpty()) { + if (Ether.get(source.asWorld()).get(this, source) == null) { setDead(); return false; } @@ -127,7 +130,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS } private void checkDetachment(Caster source, EntityValues target) { - if (getWorld(source).map(Ether::get).flatMap(ether -> ether.getEntry(getType(), target.uuid())).isEmpty()) { + if (getWorld(source).map(Ether::get).map(ether -> ether.get(getType(), target, placedSpellId)).isEmpty()) { setDead(); } } @@ -143,7 +146,8 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS entity.getSpellSlot().put(copy); entity.setCaster(source); entity.getWorld().spawnEntity(entity); - Ether.get(entity.getWorld()).put(getType(), entity); + placedSpellId = copy.getUuid(); + Ether.get(entity.getWorld()).getOrCreate(copy, entity); castEntity.set(entity); setDirty(); @@ -174,8 +178,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS if (!source.isClient()) { castEntity.getTarget().ifPresent(target -> { getWorld(source).map(Ether::get) - .flatMap(ether -> ether.getEntry(getType(), target.uuid())) - .ifPresent(Ether.Entry::markDead); + .ifPresent(ether -> ether.remove(getType(), target.uuid())); }); castEntity.set(null); getSpellEntity(source).ifPresent(e -> { @@ -183,7 +186,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS }); if (source.asEntity() instanceof CastSpellEntity spellcast) { - Ether.get(source.asWorld()).remove(getType(), source); + Ether.get(source.asWorld()).remove(this, source); } } super.onDestroyed(source); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AreaProtectionSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AreaProtectionSpell.java index 75615785..920c5bd5 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AreaProtectionSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AreaProtectionSpell.java @@ -9,6 +9,7 @@ import com.minelittlepony.unicopia.entity.mob.UEntities; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.item.FriendshipBraceletItem; import com.minelittlepony.unicopia.particle.MagicParticleEffect; +import com.minelittlepony.unicopia.server.world.Ether; import com.minelittlepony.unicopia.util.shape.Sphere; import net.minecraft.entity.Entity; @@ -42,6 +43,8 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell { source.addParticle(new MagicParticleEffect(getType().getColor()), pos, Vec3d.ZERO); } }); + } else { + Ether.get(source.asWorld()).getOrCreate(this, source); } source.findAllSpellsInRange(radius, e -> isValidTarget(source, e)).filter(caster -> !caster.hasCommonOwner(source)).forEach(caster -> { @@ -51,6 +54,11 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell { return !isDead(); } + @Override + protected void onDestroyed(Caster caster) { + Ether.get(caster.asWorld()).remove(this, caster); + } + /** * Calculates the maximum radius of the shield. aka The area of effect. */ diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisperseIllusionSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisperseIllusionSpell.java index 4806d10d..9affae3e 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisperseIllusionSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisperseIllusionSpell.java @@ -12,7 +12,7 @@ import com.minelittlepony.unicopia.util.shape.Sphere; import net.minecraft.util.math.Vec3d; /** - * An area-effect spell that disperses illussions. + * An area-effect spell that disperses illusions. */ public class DisperseIllusionSpell extends AbstractAreaEffectSpell { protected DisperseIllusionSpell(CustomisedSpellType type) { diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java index 5ff51120..d88d9dd0 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java @@ -5,14 +5,16 @@ import java.util.Set; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.magic.Caster; -import com.minelittlepony.unicopia.ability.magic.CasterView; +import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; import com.minelittlepony.unicopia.ability.magic.spell.Situation; +import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.advancement.UCriteria; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; +import com.minelittlepony.unicopia.server.world.Ether; import com.minelittlepony.unicopia.util.NbtSerialisable; import com.minelittlepony.unicopia.util.shape.*; @@ -21,8 +23,10 @@ import net.minecraft.fluid.*; import net.minecraft.nbt.*; import net.minecraft.state.property.Properties; import net.minecraft.registry.tag.TagKey; +import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; +import net.minecraft.world.BlockView; import net.minecraft.world.World; public class HydrophobicSpell extends AbstractSpell { @@ -41,16 +45,15 @@ public class HydrophobicSpell extends AbstractSpell { } @Override - public boolean apply(Caster source) { - if (getTraits().get(Trait.GENEROSITY) > 0) { - return toPlaceable().apply(source); + public Spell prepareForCast(Caster caster, CastingMethod method) { + if ((method == CastingMethod.DIRECT || method == CastingMethod.STAFF) && getTraits().get(Trait.GENEROSITY) > 0) { + return toPlaceable(); } - return super.apply(source); + return this; } @Override public boolean tick(Caster source, Situation situation) { - if (!source.isClient()) { World world = source.asWorld(); @@ -86,7 +89,11 @@ public class HydrophobicSpell extends AbstractSpell { setDead(); } - source.spawnParticles(new Sphere(true, getRange(source)), 10, pos -> { + double range = getRange(source); + var entry = Ether.get(source.asWorld()).getOrCreate(this, source); + entry.radius = (float)range; + + source.spawnParticles(new Sphere(true, range), 10, pos -> { BlockPos bp = BlockPos.ofFloored(pos); if (source.asWorld().getFluidState(bp.up()).isIn(affectedFluid)) { source.addParticle(UParticles.RAIN_DROPS, pos, Vec3d.ZERO); @@ -107,6 +114,7 @@ public class HydrophobicSpell extends AbstractSpell { @Override protected void onDestroyed(Caster caster) { + Ether.get(caster.asWorld()).remove(this, caster); storedFluidPositions.removeIf(entry -> { entry.restore(caster.asWorld()); return true; @@ -162,13 +170,20 @@ public class HydrophobicSpell extends AbstractSpell { } } - public boolean blocksFlow(Caster caster, BlockPos pos, FluidState fluid) { - return fluid.isIn(affectedFluid) && pos.isWithinDistance(caster.getOrigin(), getRange(caster) + 1); + public boolean blocksFlow(Ether.Entry entry, Vec3d center, BlockPos pos, FluidState fluid) { + return fluid.isIn(affectedFluid) && pos.isWithinDistance(center, (double)entry.radius + 1); } - public static boolean blocksFluidFlow(CasterView world, BlockPos pos, FluidState state) { - return world.findAllSpellsInRange(pos, 500, SpellType.HYDROPHOBIC).anyMatch(pair -> { - return pair.getValue().blocksFlow(pair.getKey(), pos, state); - }); + public static boolean blocksFluidFlow(BlockView world, BlockPos pos, FluidState state) { + if (world instanceof ServerWorld sw) { + return Ether.get(sw).anyMatch(SpellType.HYDROPHOBIC, entry -> { + var spell = entry.getSpell(); + var target = entry.entity.getTarget().orElse(null); + return spell != null && target != null && spell.blocksFlow(entry, target.pos(), pos, state); + }); + } + + return false; + } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/PortalSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/PortalSpell.java index 680a0ff1..8b25fbe9 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/PortalSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/PortalSpell.java @@ -1,6 +1,9 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; import java.util.Optional; +import java.util.UUID; + +import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.Unicopia; @@ -34,6 +37,8 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme .build(); private static final Shape PARTICLE_AREA = new Sphere(true, 2, 1, 1, 0); + @Nullable + private UUID targetPortalId; private final EntityReference teleportationTarget = new EntityReference<>(); private boolean publishedPosition; @@ -63,10 +68,7 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme Vec3d origin = source.getOriginVector(); ParticleEffect effect = teleportationTarget.getTarget() - .map(target -> { - getType(); - return (ParticleEffect)new FollowingParticleEffect(UParticles.HEALTH_DRAIN, target.pos(), 0.2F).withChild(ParticleTypes.ELECTRIC_SPARK); - }) + .map(target -> (ParticleEffect)new FollowingParticleEffect(UParticles.HEALTH_DRAIN, target.pos(), 0.2F).withChild(ParticleTypes.ELECTRIC_SPARK)) .orElse(ParticleTypes.ELECTRIC_SPARK); source.spawnParticles(origin, particleArea, 5, pos -> { @@ -82,7 +84,7 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme }); } else { teleportationTarget.getTarget().ifPresent(target -> { - if (Ether.get(source.asWorld()).getEntry(getType(), target.uuid()).isEmpty()) { + if (Ether.get(source.asWorld()).get(getType(), target, targetPortalId) != null) { Unicopia.LOGGER.debug("Lost sibling, breaking connection to " + target.uuid()); teleportationTarget.set(null); setDirty(); @@ -96,18 +98,15 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme ); } - if (!publishedPosition) { - publishedPosition = true; - Ether.Entry entry = Ether.get(source.asWorld()).put(getType(), source); - entry.pitch = pitch; - entry.yaw = yaw; - } + var entry = Ether.get(source.asWorld()).getOrCreate(this, source); + entry.pitch = pitch; + entry.yaw = yaw; } return !isDead(); } - private void tickWithTargetLink(Caster source, Ether.Entry destination) { + private void tickWithTargetLink(Caster source, Ether.Entry destination) { destination.entity.getTarget().ifPresent(target -> { source.findAllEntitiesInRange(1).forEach(entity -> { @@ -142,20 +141,21 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme return; } - Ether ether = Ether.get(source.asWorld()); - ether.getEntries(getType()) - .stream() - .filter(entry -> entry.isAvailable() && !entry.entity.referenceEquals(source.asEntity()) && entry.entity.isSet()) - .findAny() - .ifPresent(entry -> { + Ether.get(source.asWorld()).anyMatch(getType(), entry -> { + if (entry.isAvailable() && !entry.entity.referenceEquals(source.asEntity()) && entry.entity.isSet()) { entry.setTaken(true); teleportationTarget.copyFrom(entry.entity); + targetPortalId = entry.getSpellId(); setDirty(); - }); + } + return false; + }); } - private Optional getTarget(Caster source) { - return teleportationTarget.getTarget().flatMap(target -> Ether.get(source.asWorld()).getEntry(getType(), target.uuid())); + @SuppressWarnings("unchecked") + private Optional> getTarget(Caster source) { + return teleportationTarget.getTarget() + .map(target -> Ether.get(source.asWorld()).get((SpellType)getType(), target, targetPortalId)); } @Override @@ -189,7 +189,7 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme protected void onDestroyed(Caster caster) { particleEffect.destroy(); Ether ether = Ether.get(caster.asWorld()); - ether.remove(getType(), caster.asEntity().getUuid()); + ether.remove(getType(), caster); getTarget(caster).ifPresent(e -> e.setTaken(false)); } diff --git a/src/main/java/com/minelittlepony/unicopia/client/ClientInteractionManager.java b/src/main/java/com/minelittlepony/unicopia/client/ClientInteractionManager.java index 0be0da61..68cf31a5 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/ClientInteractionManager.java +++ b/src/main/java/com/minelittlepony/unicopia/client/ClientInteractionManager.java @@ -2,7 +2,6 @@ package com.minelittlepony.unicopia.client; import java.lang.ref.WeakReference; import java.util.Map; -import java.util.Optional; import java.util.function.Predicate; import java.util.function.Supplier; @@ -13,14 +12,12 @@ import com.minelittlepony.unicopia.EquinePredicates; import com.minelittlepony.unicopia.FlightType; import com.minelittlepony.unicopia.InteractionManager; import com.minelittlepony.unicopia.USounds; -import com.minelittlepony.unicopia.ability.magic.CasterView; import com.minelittlepony.unicopia.client.gui.DismissSpellScreen; import com.minelittlepony.unicopia.client.gui.spellbook.ClientChapters; import com.minelittlepony.unicopia.client.sound.*; import com.minelittlepony.unicopia.entity.player.PlayerPhysics; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.dummy.DummyClientPlayerEntity; -import com.minelittlepony.unicopia.server.world.Ether; import com.mojang.authlib.GameProfile; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -37,29 +34,17 @@ import net.minecraft.entity.passive.BeeEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.vehicle.AbstractMinecartEntity; import net.minecraft.network.PacketByteBuf; -import net.minecraft.server.world.ServerWorld; import net.minecraft.sound.SoundCategory; import net.minecraft.util.Identifier; import net.minecraft.util.math.random.Random; -import net.minecraft.world.BlockView; import net.minecraft.world.World; public class ClientInteractionManager extends InteractionManager { private final MinecraftClient client = MinecraftClient.getInstance(); - private final Optional clientWorld = Optional.of(() -> MinecraftClient.getInstance().world); - private final Int2ObjectMap> playingSounds = new Int2ObjectOpenHashMap<>(); - @Override - public Optional getCasterView(BlockView view) { - if (view instanceof ServerWorld world) { - return Optional.of(Ether.get(world)); - } - return clientWorld; - } - @Override public Map readChapters(PacketByteBuf buffer) { return buffer.readMap(PacketByteBuf::readIdentifier, ClientChapters::loadChapter); diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinFlowableFluid.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinFlowableFluid.java index dff7a8f9..14a9a84d 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinFlowableFluid.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinFlowableFluid.java @@ -5,7 +5,6 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import com.minelittlepony.unicopia.InteractionManager; import com.minelittlepony.unicopia.ability.magic.spell.effect.HydrophobicSpell; import net.minecraft.block.BlockState; @@ -17,7 +16,7 @@ import net.minecraft.world.BlockView; abstract class MixinFlowableFluid { @Inject(method = "canFill", at = @At("HEAD"), cancellable = true) private void onCanFill(BlockView world, BlockPos pos, BlockState state, Fluid fluid, CallbackInfoReturnable info) { - if (InteractionManager.instance().getCasterView(world).filter(view -> HydrophobicSpell.blocksFluidFlow(view, pos, fluid.getDefaultState())).isPresent()) { + if (HydrophobicSpell.blocksFluidFlow(world, pos, fluid.getDefaultState())) { info.setReturnValue(false); } } diff --git a/src/main/java/com/minelittlepony/unicopia/server/world/Ether.java b/src/main/java/com/minelittlepony/unicopia/server/world/Ether.java index b6eb0eea..8d2e309c 100644 --- a/src/main/java/com/minelittlepony/unicopia/server/world/Ether.java +++ b/src/main/java/com/minelittlepony/unicopia/server/world/Ether.java @@ -1,88 +1,93 @@ package com.minelittlepony.unicopia.server.world; +import java.lang.ref.WeakReference; import java.util.*; +import java.util.function.BiPredicate; +import java.util.function.Predicate; + +import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.Unicopia; -import com.minelittlepony.unicopia.ability.magic.CasterView; 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 com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.util.NbtSerialisable; - import net.minecraft.nbt.*; import net.minecraft.util.Identifier; import net.minecraft.world.PersistentState; import net.minecraft.world.World; -public class Ether extends PersistentState implements CasterView { +public class Ether extends PersistentState { private static final Identifier ID = Unicopia.id("ether"); public static Ether get(World world) { return WorldOverlay.getPersistableStorage(world, ID, Ether::new, Ether::new); } - private final Map> advertisingEndpoints = new HashMap<>(); + private final Map>>> endpoints; private final Object locker = new Object(); private final World world; Ether(World world, NbtCompound compound) { - this(world); - compound.getKeys().forEach(key -> { - Identifier typeId = Identifier.tryParse(key); - if (typeId != null) { - Set uuids = getEntries(typeId); - compound.getList(key, NbtElement.COMPOUND_TYPE).forEach(entry -> { - Entry e = new Entry(); - e.fromNBT((NbtCompound)entry); - uuids.add(e); - }); - } + this.world = world; + this.endpoints = NbtSerialisable.readMap(compound.getCompound("endpoints"), Identifier::tryParse, typeNbt -> { + return NbtSerialisable.readMap((NbtCompound)typeNbt, UUID::fromString, entityNbt -> { + return NbtSerialisable.readMap((NbtCompound)entityNbt, UUID::fromString, Entry::new); + }); }); } Ether(World world) { this.world = world; + this.endpoints = new HashMap<>(); } @Override public NbtCompound writeNbt(NbtCompound compound) { synchronized (locker) { - advertisingEndpoints.forEach((id, uuids) -> { - NbtList list = new NbtList(); - uuids.forEach(uuid -> { - if (uuid.isAlive()) { - list.add(uuid.toNBT()); - } + pruneNodes(); + compound.put("endpoints", NbtSerialisable.writeMap(endpoints, Identifier::toString, entities -> { + return NbtSerialisable.writeMap(entities, UUID::toString, spells -> { + return NbtSerialisable.writeMap(spells, UUID::toString, Entry::toNBT); }); - compound.put(id.toString(), list); - }); - + })); return compound; } } - public Entry put(SpellType spellType, Caster caster) { + @SuppressWarnings("unchecked") + public Entry getOrCreate(T spell, Caster caster) { synchronized (locker) { - var entry = new Entry(caster); - getEntries(spellType.getId()).add(entry); - markDirty(); + Entry entry = (Entry)endpoints + .computeIfAbsent(spell.getType().getId(), typeId -> new HashMap<>()) + .computeIfAbsent(caster.asEntity().getUuid(), entityId -> new HashMap<>()) + .computeIfAbsent(spell.getUuid(), spellid -> { + markDirty(); + return new Entry<>(spell, caster); + }); + if (entry.removed) { + entry.removed = false; + markDirty(); + } + if (entry.spell.get() != spell) { + entry.spell = new WeakReference<>(spell); + markDirty(); + } return entry; } } - public void remove(SpellType spellType, UUID id) { + public void remove(SpellType spellType, UUID entityId) { synchronized (locker) { - Identifier typeId = spellType.getId(); - Set refs = advertisingEndpoints.get(typeId); - if (refs != null) { - refs.removeIf(ref -> ref.isDead() || ref.entity.getTarget().filter(target -> id.equals(target.uuid())).isPresent()); - if (refs.isEmpty()) { - advertisingEndpoints.remove(typeId); + endpoints.computeIfPresent(spellType.getId(), (typeId, entries) -> { + if (entries.remove(entityId) != null) { + markDirty(); } - markDirty(); - } + return entries.isEmpty() ? null : entries; + }); } } @@ -90,72 +95,123 @@ public class Ether extends PersistentState implements CasterView { remove(spellType, caster.asEntity().getUuid()); } - public Set getEntries(SpellType spellType) { - return getEntries(spellType.getId()); + public void remove(T spell, Caster caster) { + Entry entry = get(spell, caster); + if (entry != null) { + entry.markDead(); + } } - private Set getEntries(Identifier typeId) { + @SuppressWarnings("unchecked") + public Entry get(T spell, Caster caster) { + return get((SpellType)spell.getType(), caster.asEntity().getUuid(), spell.getUuid()); + } + + public Entry get(SpellType spell, EntityReference.EntityValues entityId, @Nullable UUID spellId) { + return get(spell, entityId.uuid(), spellId); + } + + @SuppressWarnings("unchecked") + @Nullable + private Entry get(SpellType spell, UUID entityId, @Nullable UUID spellId) { + if (spellId == null) { + return null; + } synchronized (locker) { - return advertisingEndpoints.compute(typeId, (k, old) -> { - if (old == null) { - old = new HashSet<>(); - } else { - old.removeIf(Entry::isDead); + Entry entry = endpoints + .getOrDefault(spell.getId(), Map.of()) + .getOrDefault(entityId, Map.of()) + .get(spellId); + return entry == null || entry.isDead() ? null : (Entry)entry; + } + } + + public boolean anyMatch(SpellType spellType, BiPredicate> condition) { + return anyMatch(spellType, entry -> { + var spell = entry.getSpell(); + var caster = entry.getCaster(); + return spell != null && caster != null && condition.test(spell, caster); + }); + } + + @SuppressWarnings("unchecked") + public boolean anyMatch(SpellType spellType, Predicate> condition) { + synchronized (locker) { + for (var entries : endpoints.getOrDefault(spellType.getId(), Map.of()).values()) { + for (var entry : entries.values()) { + if (!entry.isDead() && condition.test((Entry)entry)) { + return true; + } } - return old; + } + } + return false; + } + + private void pruneNodes() { + this.endpoints.values().removeIf(entities -> { + entities.values().removeIf(spells -> { + spells.values().removeIf(Entry::isDead); + return spells.isEmpty(); }); - } + return entities.isEmpty(); + }); } - public Optional getEntry(SpellType spellType, Caster caster) { - synchronized (locker) { - return getEntries(spellType).stream().filter(e -> e.entity.referenceEquals(caster.asEntity())).findFirst(); - } - } - - public Optional getEntry(SpellType spellType, UUID uuid) { - synchronized (locker) { - return getEntries(spellType).stream().filter(e -> e.equals(uuid)).findFirst(); - } - } - - @Override - public World getWorld() { - return world; - } - - public class Entry implements NbtSerialisable { + public class Entry implements NbtSerialisable { public final EntityReference entity; + + @Nullable + private UUID spellId; + private WeakReference spell; + private boolean removed; private boolean taken; public float pitch; public float yaw; + public float radius; - public Entry() { - entity = new EntityReference<>(); + private Entry(NbtElement nbt) { + this.entity = new EntityReference<>(); + this.spell = new WeakReference<>(null); + this.fromNBT((NbtCompound)nbt); } - public Entry(Caster caster) { - entity = new EntityReference<>(caster.asEntity()); + public Entry(T spell, Caster caster) { + this.entity = new EntityReference<>(caster.asEntity()); + this.spell = new WeakReference<>(spell); + spellId = spell.getUuid(); } boolean isAlive() { - return !removed; + return !isDead(); } boolean isDead() { + if (!removed) { + getSpell(); + } return removed; } + @Nullable + public UUID getSpellId() { + return spellId; + } + public void markDead() { Unicopia.LOGGER.debug("Marking " + entity.getTarget().orElse(null) + " as dead"); removed = true; markDirty(); } + public boolean entityMatches(UUID uuid) { + return entity.getTarget().filter(target -> uuid.equals(target.uuid())).isPresent(); + } + public boolean isAvailable() { - return !removed && !taken; + return !isDead() && !taken; } public void setTaken(boolean taken) { @@ -163,6 +219,43 @@ public class Ether extends PersistentState implements CasterView { markDirty(); } + @Nullable + public T getSpell() { + if (removed) { + return null; + } + T spell = this.spell.get(); + if (spell == null) { + if (spellId != null) { + spell = entity + .getOrEmpty(world) + .flatMap(Caster::of) + .flatMap(caster -> caster.getSpellSlot().get(s -> s.getUuid().equals(spellId), true)) + .orElse(null); + + if (spell != null) { + this.spell = new WeakReference<>(spell); + } + } + } + + if (spell != null && spell.isDead()) { + spellId = null; + spell = null; + markDead(); + } + + return spell; + } + + @Nullable + public Caster getCaster() { + if (removed) { + return null; + } + return Caster.of(this.entity.get(world)).orElse(null); + } + @Override public void toNBT(NbtCompound compound) { entity.toNBT(compound); @@ -170,6 +263,10 @@ public class Ether extends PersistentState implements CasterView { compound.putBoolean("taken", taken); compound.putFloat("pitch", pitch); compound.putFloat("yaw", yaw); + compound.putFloat("radius", radius); + if (spellId != null) { + compound.putUuid("spellId", spellId); + } } @Override @@ -179,20 +276,24 @@ public class Ether extends PersistentState implements CasterView { taken = compound.getBoolean("taken"); pitch = compound.getFloat("pitch"); yaw = compound.getFloat("yaw"); + radius = compound.getFloat("radius"); + spellId = compound.containsUuid("spellid") ? compound.getUuid("spellId") : null; } @Override public boolean equals(Object other) { - return other instanceof Entry e && e.entity.referenceEquals(entity); + return other instanceof Entry e + && e.entity.referenceEquals(entity) + && Objects.equals(e.spell.get(), spell.get()); } - public boolean equals(UUID uuid) { - return entity.referenceEquals(uuid); + public boolean equals(UUID entityId, UUID spellId) { + return entity.referenceEquals(entityId) && spellId.equals(this.spellId); } @Override public int hashCode() { - return entity.hashCode(); + return Objects.hash(entity, spell.get()); } } } diff --git a/src/main/java/com/minelittlepony/unicopia/util/NbtSerialisable.java b/src/main/java/com/minelittlepony/unicopia/util/NbtSerialisable.java index 7f40eb69..a346c460 100644 --- a/src/main/java/com/minelittlepony/unicopia/util/NbtSerialisable.java +++ b/src/main/java/com/minelittlepony/unicopia/util/NbtSerialisable.java @@ -2,6 +2,7 @@ package com.minelittlepony.unicopia.util; import java.util.*; import java.util.function.*; +import java.util.stream.Collectors; import java.util.stream.Stream; import com.mojang.datafixers.util.Pair; @@ -65,6 +66,18 @@ public interface NbtSerialisable { return parent; } + static Map readMap(NbtCompound nbt, Function keyFunction, Function valueFunction) { + return nbt.getKeys().stream().collect(Collectors.toMap(keyFunction, k -> valueFunction.apply(nbt.get(k)))); + } + + static NbtCompound writeMap(Map map, Function keyFunction, Function valueFunction) { + NbtCompound nbt = new NbtCompound(); + map.forEach((k, v) -> { + nbt.put(keyFunction.apply(k), valueFunction.apply(v)); + }); + return nbt; + } + interface Serializer { T read(NbtCompound compound);