Improve spell lookup performance by using the Ether

This commit is contained in:
Sollace 2024-01-20 15:12:23 +00:00
parent d22dd3bdf9
commit ab3ac8cb0d
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
12 changed files with 263 additions and 198 deletions

View file

@ -1,22 +1,17 @@
package com.minelittlepony.unicopia; package com.minelittlepony.unicopia;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.ability.magic.CasterView;
import com.minelittlepony.unicopia.entity.player.dummy.DummyPlayerEntity; import com.minelittlepony.unicopia.entity.player.dummy.DummyPlayerEntity;
import com.minelittlepony.unicopia.server.world.Ether;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
public class InteractionManager { public class InteractionManager {
@ -37,13 +32,6 @@ public class InteractionManager {
return INSTANCE; return INSTANCE;
} }
public Optional<CasterView> getCasterView(BlockView view) {
if (view instanceof ServerWorld world) {
return Optional.of(Ether.get(world));
}
return Optional.empty();
}
public Map<Identifier, ?> readChapters(PacketByteBuf buf) { public Map<Identifier, ?> readChapters(PacketByteBuf buf) {
throw new RuntimeException("Method not supported"); throw new RuntimeException("Method not supported");
} }

View file

@ -11,6 +11,7 @@ import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.entity.*; import com.minelittlepony.unicopia.entity.*;
import com.minelittlepony.unicopia.entity.damage.UDamageSources; import com.minelittlepony.unicopia.entity.damage.UDamageSources;
import com.minelittlepony.unicopia.particle.ParticleSource; import com.minelittlepony.unicopia.particle.ParticleSource;
import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.server.world.ModificationType; import com.minelittlepony.unicopia.server.world.ModificationType;
import com.minelittlepony.unicopia.util.SoundEmitter; import com.minelittlepony.unicopia.util.SoundEmitter;
import com.minelittlepony.unicopia.util.VecHelper; import com.minelittlepony.unicopia.util.VecHelper;
@ -99,11 +100,7 @@ public interface Caster<E extends Entity> extends
} }
default boolean canCastAt(Vec3d pos) { default boolean canCastAt(Vec3d pos) {
return findAllSpellsInRange(500, SpellType.ARCANE_PROTECTION::isOn).noneMatch(caster -> caster return !Ether.get(asWorld()).anyMatch(SpellType.ARCANE_PROTECTION, (spell, caster) -> spell.blocksMagicFor(caster, this, pos));
.getSpellSlot().get(SpellType.ARCANE_PROTECTION, false)
.filter(spell -> spell.blocksMagicFor(caster, this, pos))
.isPresent()
);
} }
static Stream<Caster<?>> stream(Stream<Entity> entities) { static Stream<Caster<?>> stream(Stream<Entity> entities) {

View file

@ -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 <S extends Spell> Stream<Map.Entry<Caster<?>, S>> findAllSpellsInRange(BlockPos pos, double radius, SpellPredicate<S> type) {
return findAllCastersInRange(pos, radius).flatMap(caster -> {
return caster.getSpellSlot().stream(type, false).map(spell -> {
return Map.entry(caster, spell);
});
});
}
default Stream<Caster<?>> findAllCastersInRange(BlockPos pos, double radius) {
return findAllCastersInRange(pos, radius, null);
}
default Stream<Caster<?>> findAllCastersInRange(BlockPos pos, double radius, @Nullable Predicate<Entity> test) {
return Caster.stream(findAllEntitiesInRange(pos, radius, test == null ? EquinePredicates.IS_CASTER : EquinePredicates.IS_CASTER.and(test)));
}
default Stream<Entity> findAllEntitiesInRange(BlockPos pos, double radius, @Nullable Predicate<Entity> test) {
return VecHelper.findInRange(null, getWorld(), Vec3d.ofCenter(pos), radius, test).stream();
}
default Stream<Entity> findAllEntitiesInRange(BlockPos pos, double radius) {
return findAllEntitiesInRange(pos, radius, null);
}
}

View file

@ -45,6 +45,12 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
*/ */
private final ParticleHandle particlEffect = new ParticleHandle(); private final ParticleHandle particlEffect = new ParticleHandle();
/**
* ID of the placed counterpart of this spell.
*/
@Nullable
private UUID placedSpellId;
/** /**
* The cast spell entity * The cast spell entity
*/ */
@ -82,10 +88,8 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
@Override @Override
public boolean tick(Caster<?> source, Situation situation) { public boolean tick(Caster<?> source, Situation situation) {
if (situation == Situation.BODY) { if (situation == Situation.BODY) {
if (!source.isClient()) { if (!source.isClient()) {
if (dimension == null) { if (dimension == null) {
dimension = source.asWorld().getRegistryKey(); dimension = source.asWorld().getRegistryKey();
if (source instanceof Pony) { if (source instanceof Pony) {
@ -105,8 +109,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
if (situation == Situation.GROUND_ENTITY) { if (situation == Situation.GROUND_ENTITY) {
if (!source.isClient()) { if (!source.isClient()) {
Ether ether = Ether.get(source.asWorld()); if (Ether.get(source.asWorld()).get(this, source) == null) {
if (ether.getEntry(getType(), source).isEmpty()) {
setDead(); setDead();
return false; return false;
} }
@ -127,7 +130,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
} }
private void checkDetachment(Caster<?> source, EntityValues<?> target) { 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(); setDead();
} }
} }
@ -143,7 +146,8 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
entity.getSpellSlot().put(copy); entity.getSpellSlot().put(copy);
entity.setCaster(source); entity.setCaster(source);
entity.getWorld().spawnEntity(entity); entity.getWorld().spawnEntity(entity);
Ether.get(entity.getWorld()).put(getType(), entity); placedSpellId = copy.getUuid();
Ether.get(entity.getWorld()).getOrCreate(copy, entity);
castEntity.set(entity); castEntity.set(entity);
setDirty(); setDirty();
@ -174,8 +178,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
if (!source.isClient()) { if (!source.isClient()) {
castEntity.getTarget().ifPresent(target -> { castEntity.getTarget().ifPresent(target -> {
getWorld(source).map(Ether::get) getWorld(source).map(Ether::get)
.flatMap(ether -> ether.getEntry(getType(), target.uuid())) .ifPresent(ether -> ether.remove(getType(), target.uuid()));
.ifPresent(Ether.Entry::markDead);
}); });
castEntity.set(null); castEntity.set(null);
getSpellEntity(source).ifPresent(e -> { getSpellEntity(source).ifPresent(e -> {
@ -183,7 +186,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS
}); });
if (source.asEntity() instanceof CastSpellEntity spellcast) { if (source.asEntity() instanceof CastSpellEntity spellcast) {
Ether.get(source.asWorld()).remove(getType(), source); Ether.get(source.asWorld()).remove(this, source);
} }
} }
super.onDestroyed(source); super.onDestroyed(source);

View file

@ -9,6 +9,7 @@ import com.minelittlepony.unicopia.entity.mob.UEntities;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.item.FriendshipBraceletItem; import com.minelittlepony.unicopia.item.FriendshipBraceletItem;
import com.minelittlepony.unicopia.particle.MagicParticleEffect; import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.shape.Sphere; import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
@ -42,6 +43,8 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell {
source.addParticle(new MagicParticleEffect(getType().getColor()), pos, Vec3d.ZERO); 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 -> { 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(); 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. * Calculates the maximum radius of the shield. aka The area of effect.
*/ */

View file

@ -12,7 +12,7 @@ import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.util.math.Vec3d; 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 { public class DisperseIllusionSpell extends AbstractAreaEffectSpell {
protected DisperseIllusionSpell(CustomisedSpellType<?> type) { protected DisperseIllusionSpell(CustomisedSpellType<?> type) {

View file

@ -5,14 +5,16 @@ import java.util.Set;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.ability.magic.Caster; 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.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.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.advancement.UCriteria; import com.minelittlepony.unicopia.advancement.UCriteria;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.particle.UParticles;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.server.world.Ether;
import com.minelittlepony.unicopia.util.NbtSerialisable; import com.minelittlepony.unicopia.util.NbtSerialisable;
import com.minelittlepony.unicopia.util.shape.*; import com.minelittlepony.unicopia.util.shape.*;
@ -21,8 +23,10 @@ import net.minecraft.fluid.*;
import net.minecraft.nbt.*; import net.minecraft.nbt.*;
import net.minecraft.state.property.Properties; import net.minecraft.state.property.Properties;
import net.minecraft.registry.tag.TagKey; import net.minecraft.registry.tag.TagKey;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
public class HydrophobicSpell extends AbstractSpell { public class HydrophobicSpell extends AbstractSpell {
@ -41,16 +45,15 @@ public class HydrophobicSpell extends AbstractSpell {
} }
@Override @Override
public boolean apply(Caster<?> source) { public Spell prepareForCast(Caster<?> caster, CastingMethod method) {
if (getTraits().get(Trait.GENEROSITY) > 0) { if ((method == CastingMethod.DIRECT || method == CastingMethod.STAFF) && getTraits().get(Trait.GENEROSITY) > 0) {
return toPlaceable().apply(source); return toPlaceable();
} }
return super.apply(source); return this;
} }
@Override @Override
public boolean tick(Caster<?> source, Situation situation) { public boolean tick(Caster<?> source, Situation situation) {
if (!source.isClient()) { if (!source.isClient()) {
World world = source.asWorld(); World world = source.asWorld();
@ -86,7 +89,11 @@ public class HydrophobicSpell extends AbstractSpell {
setDead(); 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); BlockPos bp = BlockPos.ofFloored(pos);
if (source.asWorld().getFluidState(bp.up()).isIn(affectedFluid)) { if (source.asWorld().getFluidState(bp.up()).isIn(affectedFluid)) {
source.addParticle(UParticles.RAIN_DROPS, pos, Vec3d.ZERO); source.addParticle(UParticles.RAIN_DROPS, pos, Vec3d.ZERO);
@ -107,6 +114,7 @@ public class HydrophobicSpell extends AbstractSpell {
@Override @Override
protected void onDestroyed(Caster<?> caster) { protected void onDestroyed(Caster<?> caster) {
Ether.get(caster.asWorld()).remove(this, caster);
storedFluidPositions.removeIf(entry -> { storedFluidPositions.removeIf(entry -> {
entry.restore(caster.asWorld()); entry.restore(caster.asWorld());
return true; return true;
@ -162,13 +170,20 @@ public class HydrophobicSpell extends AbstractSpell {
} }
} }
public boolean blocksFlow(Caster<?> caster, BlockPos pos, FluidState fluid) { public boolean blocksFlow(Ether.Entry<?> entry, Vec3d center, BlockPos pos, FluidState fluid) {
return fluid.isIn(affectedFluid) && pos.isWithinDistance(caster.getOrigin(), getRange(caster) + 1); return fluid.isIn(affectedFluid) && pos.isWithinDistance(center, (double)entry.radius + 1);
} }
public static boolean blocksFluidFlow(CasterView world, BlockPos pos, FluidState state) { public static boolean blocksFluidFlow(BlockView world, BlockPos pos, FluidState state) {
return world.findAllSpellsInRange(pos, 500, SpellType.HYDROPHOBIC).anyMatch(pair -> { if (world instanceof ServerWorld sw) {
return pair.getValue().blocksFlow(pair.getKey(), pos, state); 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;
} }
} }

View file

@ -1,6 +1,9 @@
package com.minelittlepony.unicopia.ability.magic.spell.effect; package com.minelittlepony.unicopia.ability.magic.spell.effect;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.USounds;
import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.Unicopia;
@ -34,6 +37,8 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
.build(); .build();
private static final Shape PARTICLE_AREA = new Sphere(true, 2, 1, 1, 0); private static final Shape PARTICLE_AREA = new Sphere(true, 2, 1, 1, 0);
@Nullable
private UUID targetPortalId;
private final EntityReference<Entity> teleportationTarget = new EntityReference<>(); private final EntityReference<Entity> teleportationTarget = new EntityReference<>();
private boolean publishedPosition; private boolean publishedPosition;
@ -63,10 +68,7 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
Vec3d origin = source.getOriginVector(); Vec3d origin = source.getOriginVector();
ParticleEffect effect = teleportationTarget.getTarget() ParticleEffect effect = teleportationTarget.getTarget()
.map(target -> { .map(target -> (ParticleEffect)new FollowingParticleEffect(UParticles.HEALTH_DRAIN, target.pos(), 0.2F).withChild(ParticleTypes.ELECTRIC_SPARK))
getType();
return (ParticleEffect)new FollowingParticleEffect(UParticles.HEALTH_DRAIN, target.pos(), 0.2F).withChild(ParticleTypes.ELECTRIC_SPARK);
})
.orElse(ParticleTypes.ELECTRIC_SPARK); .orElse(ParticleTypes.ELECTRIC_SPARK);
source.spawnParticles(origin, particleArea, 5, pos -> { source.spawnParticles(origin, particleArea, 5, pos -> {
@ -82,7 +84,7 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
}); });
} else { } else {
teleportationTarget.getTarget().ifPresent(target -> { 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()); Unicopia.LOGGER.debug("Lost sibling, breaking connection to " + target.uuid());
teleportationTarget.set(null); teleportationTarget.set(null);
setDirty(); setDirty();
@ -96,18 +98,15 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
); );
} }
if (!publishedPosition) { var entry = Ether.get(source.asWorld()).getOrCreate(this, source);
publishedPosition = true; entry.pitch = pitch;
Ether.Entry entry = Ether.get(source.asWorld()).put(getType(), source); entry.yaw = yaw;
entry.pitch = pitch;
entry.yaw = yaw;
}
} }
return !isDead(); return !isDead();
} }
private void tickWithTargetLink(Caster<?> source, Ether.Entry destination) { private void tickWithTargetLink(Caster<?> source, Ether.Entry<?> destination) {
destination.entity.getTarget().ifPresent(target -> { destination.entity.getTarget().ifPresent(target -> {
source.findAllEntitiesInRange(1).forEach(entity -> { source.findAllEntitiesInRange(1).forEach(entity -> {
@ -142,20 +141,21 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
return; return;
} }
Ether ether = Ether.get(source.asWorld()); Ether.get(source.asWorld()).anyMatch(getType(), entry -> {
ether.getEntries(getType()) if (entry.isAvailable() && !entry.entity.referenceEquals(source.asEntity()) && entry.entity.isSet()) {
.stream()
.filter(entry -> entry.isAvailable() && !entry.entity.referenceEquals(source.asEntity()) && entry.entity.isSet())
.findAny()
.ifPresent(entry -> {
entry.setTaken(true); entry.setTaken(true);
teleportationTarget.copyFrom(entry.entity); teleportationTarget.copyFrom(entry.entity);
targetPortalId = entry.getSpellId();
setDirty(); setDirty();
}); }
return false;
});
} }
private Optional<Ether.Entry> getTarget(Caster<?> source) { @SuppressWarnings("unchecked")
return teleportationTarget.getTarget().flatMap(target -> Ether.get(source.asWorld()).getEntry(getType(), target.uuid())); private Optional<Ether.Entry<PortalSpell>> getTarget(Caster<?> source) {
return teleportationTarget.getTarget()
.map(target -> Ether.get(source.asWorld()).get((SpellType<PortalSpell>)getType(), target, targetPortalId));
} }
@Override @Override
@ -189,7 +189,7 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme
protected void onDestroyed(Caster<?> caster) { protected void onDestroyed(Caster<?> caster) {
particleEffect.destroy(); particleEffect.destroy();
Ether ether = Ether.get(caster.asWorld()); Ether ether = Ether.get(caster.asWorld());
ether.remove(getType(), caster.asEntity().getUuid()); ether.remove(getType(), caster);
getTarget(caster).ifPresent(e -> e.setTaken(false)); getTarget(caster).ifPresent(e -> e.setTaken(false));
} }

View file

@ -2,7 +2,6 @@ package com.minelittlepony.unicopia.client;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -13,14 +12,12 @@ import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.FlightType; import com.minelittlepony.unicopia.FlightType;
import com.minelittlepony.unicopia.InteractionManager; import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.USounds; 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.DismissSpellScreen;
import com.minelittlepony.unicopia.client.gui.spellbook.ClientChapters; import com.minelittlepony.unicopia.client.gui.spellbook.ClientChapters;
import com.minelittlepony.unicopia.client.sound.*; import com.minelittlepony.unicopia.client.sound.*;
import com.minelittlepony.unicopia.entity.player.PlayerPhysics; import com.minelittlepony.unicopia.entity.player.PlayerPhysics;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.entity.player.dummy.DummyClientPlayerEntity; import com.minelittlepony.unicopia.entity.player.dummy.DummyClientPlayerEntity;
import com.minelittlepony.unicopia.server.world.Ether;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; 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.player.PlayerEntity;
import net.minecraft.entity.vehicle.AbstractMinecartEntity; import net.minecraft.entity.vehicle.AbstractMinecartEntity;
import net.minecraft.network.PacketByteBuf; import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundCategory;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.random.Random; import net.minecraft.util.math.random.Random;
import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
public class ClientInteractionManager extends InteractionManager { public class ClientInteractionManager extends InteractionManager {
private final MinecraftClient client = MinecraftClient.getInstance(); private final MinecraftClient client = MinecraftClient.getInstance();
private final Optional<CasterView> clientWorld = Optional.of(() -> MinecraftClient.getInstance().world);
private final Int2ObjectMap<WeakReference<TickableSoundInstance>> playingSounds = new Int2ObjectOpenHashMap<>(); private final Int2ObjectMap<WeakReference<TickableSoundInstance>> playingSounds = new Int2ObjectOpenHashMap<>();
@Override
public Optional<CasterView> getCasterView(BlockView view) {
if (view instanceof ServerWorld world) {
return Optional.of(Ether.get(world));
}
return clientWorld;
}
@Override @Override
public Map<Identifier, ?> readChapters(PacketByteBuf buffer) { public Map<Identifier, ?> readChapters(PacketByteBuf buffer) {
return buffer.readMap(PacketByteBuf::readIdentifier, ClientChapters::loadChapter); return buffer.readMap(PacketByteBuf::readIdentifier, ClientChapters::loadChapter);

View file

@ -5,7 +5,6 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.ability.magic.spell.effect.HydrophobicSpell; import com.minelittlepony.unicopia.ability.magic.spell.effect.HydrophobicSpell;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
@ -17,7 +16,7 @@ import net.minecraft.world.BlockView;
abstract class MixinFlowableFluid { abstract class MixinFlowableFluid {
@Inject(method = "canFill", at = @At("HEAD"), cancellable = true) @Inject(method = "canFill", at = @At("HEAD"), cancellable = true)
private void onCanFill(BlockView world, BlockPos pos, BlockState state, Fluid fluid, CallbackInfoReturnable<Boolean> info) { private void onCanFill(BlockView world, BlockPos pos, BlockState state, Fluid fluid, CallbackInfoReturnable<Boolean> 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); info.setReturnValue(false);
} }
} }

View file

@ -1,88 +1,93 @@
package com.minelittlepony.unicopia.server.world; package com.minelittlepony.unicopia.server.world;
import java.lang.ref.WeakReference;
import java.util.*; 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.Unicopia;
import com.minelittlepony.unicopia.ability.magic.CasterView;
import com.minelittlepony.unicopia.ability.magic.Caster; 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.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.util.NbtSerialisable; import com.minelittlepony.unicopia.util.NbtSerialisable;
import net.minecraft.nbt.*; import net.minecraft.nbt.*;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.world.PersistentState; import net.minecraft.world.PersistentState;
import net.minecraft.world.World; 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"); private static final Identifier ID = Unicopia.id("ether");
public static Ether get(World world) { public static Ether get(World world) {
return WorldOverlay.getPersistableStorage(world, ID, Ether::new, Ether::new); return WorldOverlay.getPersistableStorage(world, ID, Ether::new, Ether::new);
} }
private final Map<Identifier, Set<Entry>> advertisingEndpoints = new HashMap<>(); private final Map<Identifier, Map<UUID, Map<UUID, Entry<?>>>> endpoints;
private final Object locker = new Object(); private final Object locker = new Object();
private final World world; private final World world;
Ether(World world, NbtCompound compound) { Ether(World world, NbtCompound compound) {
this(world); this.world = world;
compound.getKeys().forEach(key -> { this.endpoints = NbtSerialisable.readMap(compound.getCompound("endpoints"), Identifier::tryParse, typeNbt -> {
Identifier typeId = Identifier.tryParse(key); return NbtSerialisable.readMap((NbtCompound)typeNbt, UUID::fromString, entityNbt -> {
if (typeId != null) { return NbtSerialisable.readMap((NbtCompound)entityNbt, UUID::fromString, Entry::new);
Set<Entry> uuids = getEntries(typeId); });
compound.getList(key, NbtElement.COMPOUND_TYPE).forEach(entry -> {
Entry e = new Entry();
e.fromNBT((NbtCompound)entry);
uuids.add(e);
});
}
}); });
} }
Ether(World world) { Ether(World world) {
this.world = world; this.world = world;
this.endpoints = new HashMap<>();
} }
@Override @Override
public NbtCompound writeNbt(NbtCompound compound) { public NbtCompound writeNbt(NbtCompound compound) {
synchronized (locker) { synchronized (locker) {
advertisingEndpoints.forEach((id, uuids) -> { pruneNodes();
NbtList list = new NbtList(); compound.put("endpoints", NbtSerialisable.writeMap(endpoints, Identifier::toString, entities -> {
uuids.forEach(uuid -> { return NbtSerialisable.writeMap(entities, UUID::toString, spells -> {
if (uuid.isAlive()) { return NbtSerialisable.writeMap(spells, UUID::toString, Entry::toNBT);
list.add(uuid.toNBT());
}
}); });
compound.put(id.toString(), list); }));
});
return compound; return compound;
} }
} }
public Entry put(SpellType<?> spellType, Caster<?> caster) { @SuppressWarnings("unchecked")
public <T extends Spell> Entry<T> getOrCreate(T spell, Caster<?> caster) {
synchronized (locker) { synchronized (locker) {
var entry = new Entry(caster); Entry<T> entry = (Entry<T>)endpoints
getEntries(spellType.getId()).add(entry); .computeIfAbsent(spell.getType().getId(), typeId -> new HashMap<>())
markDirty(); .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; return entry;
} }
} }
public void remove(SpellType<?> spellType, UUID id) { public <T extends Spell> void remove(SpellType<T> spellType, UUID entityId) {
synchronized (locker) { synchronized (locker) {
Identifier typeId = spellType.getId(); endpoints.computeIfPresent(spellType.getId(), (typeId, entries) -> {
Set<Entry> refs = advertisingEndpoints.get(typeId); if (entries.remove(entityId) != null) {
if (refs != null) { markDirty();
refs.removeIf(ref -> ref.isDead() || ref.entity.getTarget().filter(target -> id.equals(target.uuid())).isPresent());
if (refs.isEmpty()) {
advertisingEndpoints.remove(typeId);
} }
markDirty(); return entries.isEmpty() ? null : entries;
} });
} }
} }
@ -90,72 +95,123 @@ public class Ether extends PersistentState implements CasterView {
remove(spellType, caster.asEntity().getUuid()); remove(spellType, caster.asEntity().getUuid());
} }
public Set<Entry> getEntries(SpellType<?> spellType) { public <T extends Spell> void remove(T spell, Caster<?> caster) {
return getEntries(spellType.getId()); Entry<T> entry = get(spell, caster);
if (entry != null) {
entry.markDead();
}
} }
private Set<Entry> getEntries(Identifier typeId) { @SuppressWarnings("unchecked")
public <T extends Spell> Entry<T> get(T spell, Caster<?> caster) {
return get((SpellType<T>)spell.getType(), caster.asEntity().getUuid(), spell.getUuid());
}
public <T extends Spell> Entry<T> get(SpellType<T> spell, EntityReference.EntityValues<?> entityId, @Nullable UUID spellId) {
return get(spell, entityId.uuid(), spellId);
}
@SuppressWarnings("unchecked")
@Nullable
private <T extends Spell> Entry<T> get(SpellType<T> spell, UUID entityId, @Nullable UUID spellId) {
if (spellId == null) {
return null;
}
synchronized (locker) { synchronized (locker) {
return advertisingEndpoints.compute(typeId, (k, old) -> { Entry<?> entry = endpoints
if (old == null) { .getOrDefault(spell.getId(), Map.of())
old = new HashSet<>(); .getOrDefault(entityId, Map.of())
} else { .get(spellId);
old.removeIf(Entry::isDead); return entry == null || entry.isDead() ? null : (Entry<T>)entry;
}
}
public <T extends Spell> boolean anyMatch(SpellType<T> spellType, BiPredicate<T, Caster<?>> 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 <T extends Spell> boolean anyMatch(SpellType<T> spellType, Predicate<Entry<T>> condition) {
synchronized (locker) {
for (var entries : endpoints.getOrDefault(spellType.getId(), Map.of()).values()) {
for (var entry : entries.values()) {
if (!entry.isDead() && condition.test((Entry<T>)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<Entry> getEntry(SpellType<?> spellType, Caster<?> caster) { public class Entry<T extends Spell> implements NbtSerialisable {
synchronized (locker) {
return getEntries(spellType).stream().filter(e -> e.entity.referenceEquals(caster.asEntity())).findFirst();
}
}
public Optional<Entry> 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 final EntityReference<?> entity; public final EntityReference<?> entity;
@Nullable
private UUID spellId;
private WeakReference<T> spell;
private boolean removed; private boolean removed;
private boolean taken; private boolean taken;
public float pitch; public float pitch;
public float yaw; public float yaw;
public float radius;
public Entry() { private Entry(NbtElement nbt) {
entity = new EntityReference<>(); this.entity = new EntityReference<>();
this.spell = new WeakReference<>(null);
this.fromNBT((NbtCompound)nbt);
} }
public Entry(Caster<?> caster) { public Entry(T spell, Caster<?> caster) {
entity = new EntityReference<>(caster.asEntity()); this.entity = new EntityReference<>(caster.asEntity());
this.spell = new WeakReference<>(spell);
spellId = spell.getUuid();
} }
boolean isAlive() { boolean isAlive() {
return !removed; return !isDead();
} }
boolean isDead() { boolean isDead() {
if (!removed) {
getSpell();
}
return removed; return removed;
} }
@Nullable
public UUID getSpellId() {
return spellId;
}
public void markDead() { public void markDead() {
Unicopia.LOGGER.debug("Marking " + entity.getTarget().orElse(null) + " as dead"); Unicopia.LOGGER.debug("Marking " + entity.getTarget().orElse(null) + " as dead");
removed = true; removed = true;
markDirty(); markDirty();
} }
public boolean entityMatches(UUID uuid) {
return entity.getTarget().filter(target -> uuid.equals(target.uuid())).isPresent();
}
public boolean isAvailable() { public boolean isAvailable() {
return !removed && !taken; return !isDead() && !taken;
} }
public void setTaken(boolean taken) { public void setTaken(boolean taken) {
@ -163,6 +219,43 @@ public class Ether extends PersistentState implements CasterView {
markDirty(); 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().<T>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 @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
entity.toNBT(compound); entity.toNBT(compound);
@ -170,6 +263,10 @@ public class Ether extends PersistentState implements CasterView {
compound.putBoolean("taken", taken); compound.putBoolean("taken", taken);
compound.putFloat("pitch", pitch); compound.putFloat("pitch", pitch);
compound.putFloat("yaw", yaw); compound.putFloat("yaw", yaw);
compound.putFloat("radius", radius);
if (spellId != null) {
compound.putUuid("spellId", spellId);
}
} }
@Override @Override
@ -179,20 +276,24 @@ public class Ether extends PersistentState implements CasterView {
taken = compound.getBoolean("taken"); taken = compound.getBoolean("taken");
pitch = compound.getFloat("pitch"); pitch = compound.getFloat("pitch");
yaw = compound.getFloat("yaw"); yaw = compound.getFloat("yaw");
radius = compound.getFloat("radius");
spellId = compound.containsUuid("spellid") ? compound.getUuid("spellId") : null;
} }
@Override @Override
public boolean equals(Object other) { 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) { public boolean equals(UUID entityId, UUID spellId) {
return entity.referenceEquals(uuid); return entity.referenceEquals(entityId) && spellId.equals(this.spellId);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return entity.hashCode(); return Objects.hash(entity, spell.get());
} }
} }
} }

View file

@ -2,6 +2,7 @@ package com.minelittlepony.unicopia.util;
import java.util.*; import java.util.*;
import java.util.function.*; import java.util.function.*;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.mojang.datafixers.util.Pair; import com.mojang.datafixers.util.Pair;
@ -65,6 +66,18 @@ public interface NbtSerialisable {
return parent; return parent;
} }
static <K, V> Map<K, V> readMap(NbtCompound nbt, Function<String, K> keyFunction, Function<NbtElement, V> valueFunction) {
return nbt.getKeys().stream().collect(Collectors.toMap(keyFunction, k -> valueFunction.apply(nbt.get(k))));
}
static <K, V> NbtCompound writeMap(Map<K, V> map, Function<K, String> keyFunction, Function<V, ? extends NbtElement> valueFunction) {
NbtCompound nbt = new NbtCompound();
map.forEach((k, v) -> {
nbt.put(keyFunction.apply(k), valueFunction.apply(v));
});
return nbt;
}
interface Serializer<T> { interface Serializer<T> {
T read(NbtCompound compound); T read(NbtCompound compound);