diff --git a/src/main/java/com/minelittlepony/unicopia/Owned.java b/src/main/java/com/minelittlepony/unicopia/Owned.java
index a5ce3e7a..2eecb8c7 100644
--- a/src/main/java/com/minelittlepony/unicopia/Owned.java
+++ b/src/main/java/com/minelittlepony/unicopia/Owned.java
@@ -1,19 +1,55 @@
package com.minelittlepony.unicopia;
+import java.util.Optional;
+import java.util.UUID;
+
+import org.jetbrains.annotations.Nullable;
+
+import net.minecraft.entity.Entity;
+
/**
- * Interface for things that can be owned.
+ * Interface for things that can be owned by an entity.
+ *
+ * Ownership is retained so long as the owner is still active. If the owner leaves or dies, the link is broken.
*
* @param The type of object that owns us.
*/
-public interface Owned {
+public interface Owned {
+ /**
+ * Gets the owner that holds this object.
+ */
+ @Nullable
+ E getMaster();
+
+ /**
+ * Gets the unique entity id of the entity that holds this object.
+ *
+ * Since {@link Owned#getMaster()} will only return if the owner is loaded, use this to perform checks
+ * in the owner's absence.
+ */
+ default Optional getMasterId() {
+ return Optional.of(getMaster()).map(Entity::getUuid);
+ }
/**
* Updates the owner of this object.
*/
- void setMaster(E owner);
+ void setMaster(@Nullable E owner);
/**
- * Gets the owner that holds this object.
+ * Updated the owner of this object to be the same as another.
+ *
+ * @param sibling
*/
- E getMaster();
+ default void setMaster(Owned extends E> sibling) {
+ setMaster(sibling.getMaster());
+ }
+
+ default boolean isOwnedBy(@Nullable Object owner) {
+ return owner instanceof Entity e && getMasterId().isPresent() && e.getUuid().equals(getMasterId().get());
+ }
+
+ default boolean hasCommonOwner(Owned> sibling) {
+ return getMasterId().isPresent() && getMasterId().equals(sibling.getMasterId());
+ }
}
diff --git a/src/main/java/com/minelittlepony/unicopia/WeaklyOwned.java b/src/main/java/com/minelittlepony/unicopia/WeaklyOwned.java
new file mode 100644
index 00000000..54475b3f
--- /dev/null
+++ b/src/main/java/com/minelittlepony/unicopia/WeaklyOwned.java
@@ -0,0 +1,54 @@
+package com.minelittlepony.unicopia;
+
+import java.util.Optional;
+import java.util.UUID;
+
+import org.jetbrains.annotations.Nullable;
+
+import com.minelittlepony.unicopia.entity.EntityReference;
+
+import net.minecraft.entity.Entity;
+import net.minecraft.world.World;
+
+/**
+ * Interface for things that can be weakly owned (by an entity).
+ * Ownership links for these kinds of owned instances are preserved even if the owner is not present to oversee it.
+ *
+ * @param The type of object that owns us.
+ */
+public interface WeaklyOwned extends Owned {
+
+ World getWorld();
+
+ EntityReference getMasterReference();
+
+ /**
+ * Updated the owner of this object to be the same as another.
+ *
+ * @param sibling
+ */
+ @SuppressWarnings("unchecked")
+ default void setMaster(WeaklyOwned extends E> sibling) {
+ if (sibling instanceof WeaklyOwned) {
+ getMasterReference().copyFrom(((WeaklyOwned)sibling).getMasterReference());
+ } else {
+ setMaster(sibling.getMaster());
+ }
+ }
+
+ @Nullable
+ @Override
+ default E getMaster() {
+ return getMasterReference().get(getWorld());
+ }
+
+ @Override
+ default void setMaster(E master) {
+ getMasterReference().set(master);
+ }
+
+ @Override
+ default Optional getMasterId() {
+ return getMasterReference().getId();
+ }
+}
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java
index 33f24ab4..31c933d1 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java
@@ -62,7 +62,7 @@ public class UnicornDispellAbility implements Ability {
@Override
public double getCostEstimate(Pony player) {
return getTarget(player)
- .filter(caster -> caster.getMaster() != player.getMaster())
+ .filter(caster -> !caster.hasCommonOwner(player))
.isPresent() ? 10 : 0;
}
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/UnicornTeleportAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/UnicornTeleportAbility.java
index 0c19a704..2cb1d04b 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/UnicornTeleportAbility.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/UnicornTeleportAbility.java
@@ -129,12 +129,17 @@ public class UnicornTeleportAbility implements Ability {
}
protected void teleport(Pony teleporter, Caster> teleportee, Pos destination) {
+
+ LivingEntity player = teleportee.getMaster();
+
+ if (player == null) {
+ return;
+ }
+
teleportee.getWorld().playSound(null, teleportee.getOrigin(), SoundEvents.ENTITY_ITEM_PICKUP, SoundCategory.PLAYERS, 1, 1);
double distance = destination.distanceTo(teleportee) / 10;
- LivingEntity player = teleportee.getMaster();
-
if (player.hasVehicle()) {
Entity mount = player.getVehicle();
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 7ceb0623..f622cc9a 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
@@ -2,6 +2,7 @@ package com.minelittlepony.unicopia.ability.magic.spell;
import java.util.Collection;
import java.util.List;
+import java.util.Optional;
import org.jetbrains.annotations.Nullable;
@@ -18,6 +19,9 @@ import com.minelittlepony.unicopia.particle.UParticles;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Vec3d;
+import net.minecraft.util.registry.Registry;
+import net.minecraft.util.registry.RegistryKey;
+import net.minecraft.world.World;
/**
* A spell that can be attached to a specific location in the world.
@@ -26,13 +30,25 @@ import net.minecraft.util.math.Vec3d;
* spell loses affect until they return.
*/
public class PlaceableSpell extends AbstractDelegatingSpell {
+ /**
+ * Dimension the spell was originally cast in
+ */
@Nullable
- private Identifier dimension;
+ private RegistryKey dimension;
+ /**
+ * The visual effect
+ */
private final ParticleHandle particlEffect = new ParticleHandle();
+ /**
+ * The cast spell entity
+ */
private final EntityReference castEntity = new EntityReference<>();
+ /**
+ * The spell being cast
+ */
private Spell spell;
public PlaceableSpell(SpellType> type, SpellTraits traits) {
@@ -59,19 +75,18 @@ public class PlaceableSpell extends AbstractDelegatingSpell {
public boolean tick(Caster> source, Situation situation) {
if (situation == Situation.BODY) {
if (!source.isClient()) {
+
if (dimension == null) {
- dimension = source.getWorld().getRegistryKey().getValue();
+ dimension = source.getWorld().getRegistryKey();
setDirty();
- } else if (!source.getWorld().getRegistryKey().getValue().equals(dimension)) {
- return false;
}
- if (!castEntity.isPresent(source.getWorld())) {
+ if (getSpellEntity(source).isEmpty()) {
CastSpellEntity entity = UEntities.CAST_SPELL.create(source.getWorld());
Vec3d pos = castEntity.getPosition().orElse(source.getOriginVector());
entity.updatePositionAndAngles(pos.x, pos.y, pos.z, 0, 0);
entity.getSpellSlot().put(this);
- entity.setMaster(source.getMaster());
+ entity.setMaster(source);
entity.world.spawnEntity(entity);
castEntity.set(entity);
@@ -92,14 +107,33 @@ public class PlaceableSpell extends AbstractDelegatingSpell {
return super.tick(source, Situation.GROUND);
}
+ this.onDestroyed(source);
+
return !isDead();
}
+ @Override
+ public void onDestroyed(Caster> source) {
+ if (!source.isClient()) {
+ getSpellEntity(source).ifPresent(e -> {
+ e.getSpellSlot().clear();
+ castEntity.set(null);
+ });
+ }
+ super.onDestroyed(source);
+ }
+
+ protected Optional getSpellEntity(Caster> source) {
+ return Optional.ofNullable(dimension)
+ .map(dim -> source.getWorld().getServer().getWorld(dimension))
+ .map(castEntity::get);
+ }
+
@Override
public void toNBT(NbtCompound compound) {
super.toNBT(compound);
if (dimension != null) {
- compound.putString("dimension", dimension.toString());
+ compound.putString("dimension", dimension.getValue().toString());
}
compound.put("castEntity", castEntity.toNBT());
compound.put("spell", Spell.writeNbt(spell));
@@ -109,7 +143,10 @@ public class PlaceableSpell extends AbstractDelegatingSpell {
public void fromNBT(NbtCompound compound) {
super.fromNBT(compound);
if (compound.contains("dimension")) {
- dimension = new Identifier(compound.getString("dimension"));
+ Identifier id = Identifier.tryParse(compound.getString("dimension"));
+ if (id != null) {
+ dimension = RegistryKey.of(Registry.WORLD_KEY, id);
+ }
}
if (compound.contains("castEntity")) {
castEntity.fromNBT(compound.getCompound("castEntity"));
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/RainboomAbilitySpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/RainboomAbilitySpell.java
index 5ea7eeb3..a0812175 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/RainboomAbilitySpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/RainboomAbilitySpell.java
@@ -62,6 +62,10 @@ public class RainboomAbilitySpell extends AbstractSpell {
LivingEntity owner = source.getMaster();
+ if (owner == null) {
+ return false;
+ }
+
source.findAllEntitiesInRange(rad).forEach(e -> {
e.damage(MagicalDamageSource.create("rainboom", source), 6);
});
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/ThrowableSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/ThrowableSpell.java
index 72be9d68..2d87a3ac 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/ThrowableSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/ThrowableSpell.java
@@ -53,6 +53,10 @@ public final class ThrowableSpell extends AbstractDelegatingSpell {
LivingEntity entity = caster.getMaster();
+ if (entity == null) {
+ return Optional.empty();
+ }
+
caster.playSound(SoundEvents.ITEM_CHORUS_FRUIT_TELEPORT, 0.7F, 0.4F / (world.random.nextFloat() * 0.4F + 0.8F));
if (!caster.isClient()) {
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CatapultSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CatapultSpell.java
index d41c8aa1..871bed50 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CatapultSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CatapultSpell.java
@@ -86,7 +86,7 @@ public class CatapultSpell extends AbstractSpell implements ProjectileSpell {
double maxDistance = 2 + (getTraits().get(Trait.FOCUS) - 50) * 8;
- HitResult ray = RayTraceHelper.doTrace(caster.getMaster(), maxDistance, 1, EntityPredicates.CAN_COLLIDE).getResult();
+ HitResult ray = RayTraceHelper.doTrace(caster.getEntity(), maxDistance, 1, EntityPredicates.CAN_COLLIDE).getResult();
if (ray.getType() == HitResult.Type.ENTITY) {
EntityHitResult result = (EntityHitResult)ray;
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DarkVortexSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DarkVortexSpell.java
index e3fc5b38..3ffefcb7 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DarkVortexSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DarkVortexSpell.java
@@ -1,5 +1,7 @@
package com.minelittlepony.unicopia.ability.magic.spell.effect;
+import org.jetbrains.annotations.Nullable;
+
import com.minelittlepony.unicopia.ability.magic.Affine;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.Situation;
@@ -198,13 +200,18 @@ public class DarkVortexSpell extends AttractiveSpell {
if (distance <= getEventHorizonRadius()) {
target.setVelocity(target.getVelocity().multiply(distance / (2 * radius)));
+ @Nullable
+ Entity master = source.getMaster();
+
if (target instanceof MagicProjectileEntity) {
Item item = ((MagicProjectileEntity)target).getStack().getItem();
- if (item instanceof ProjectileDelegate) {
- ((ProjectileDelegate) item).onImpact(((MagicProjectileEntity)target), source.getMaster());
+ if (item instanceof ProjectileDelegate && master != null) {
+ ((ProjectileDelegate) item).onImpact(((MagicProjectileEntity)target), master);
}
} else if (target instanceof PersistentProjectileEntity) {
- source.getMaster().damage(DamageSource.thrownProjectile(target, ((PersistentProjectileEntity)target).getOwner()), 4);
+ if (master != null) {
+ master.damage(DamageSource.thrownProjectile(target, ((PersistentProjectileEntity)target).getOwner()), 4);
+ }
target.discard();
return;
}
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FeatherFallSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FeatherFallSpell.java
index 5251ebaf..d2d879df 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FeatherFallSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FeatherFallSpell.java
@@ -58,13 +58,12 @@ public class FeatherFallSpell extends AbstractSpell {
final float strength = 1F / (getTraits().get(Trait.STRENGTH, 2, 9) / targets.size());
final float generosity = getTraits().get(Trait.GENEROSITY, 1, MAX_GENEROSITY_FACTOR) / MAX_GENEROSITY_FACTOR;
- Entity master = caster.getMaster();
Entity entity = caster.getEntity();
Vec3d masterVelocity = caster.getEntity().getVelocity().multiply(0.1);
targets.forEach(target -> {
if (target.getVelocity().y < 0) {
- boolean isSelf = target == master || target == entity;
+ boolean isSelf = caster.isOwnedBy(target) || target == entity;
float delta = strength * (isSelf ? (1F - generosity) : generosity);
if (!isSelf || generosity < 0.5F) {
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireSpell.java
index a452fb84..cf0e3e8e 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireSpell.java
@@ -1,5 +1,7 @@
package com.minelittlepony.unicopia.ability.magic.spell.effect;
+import org.jetbrains.annotations.Nullable;
+
import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.spell.Situation;
@@ -48,7 +50,7 @@ public class FireSpell extends AbstractAreaEffectSpell implements ProjectileSpel
@Override
public void onImpact(MagicProjectileEntity projectile, BlockPos pos, BlockState state) {
if (!projectile.isClient()) {
- projectile.getWorld().createExplosion(projectile.getMaster(), pos.getX(), pos.getY(), pos.getZ(), 2, DestructionType.DESTROY);
+ projectile.getWorld().createExplosion(projectile.getEntity(), pos.getX(), pos.getY(), pos.getZ(), 2, DestructionType.DESTROY);
}
}
@@ -112,11 +114,11 @@ public class FireSpell extends AbstractAreaEffectSpell implements ProjectileSpel
return false;
}
- protected boolean applyEntities(Entity owner, World world, Vec3d pos) {
+ protected boolean applyEntities(@Nullable Entity owner, World world, Vec3d pos) {
return !VecHelper.findInRange(owner, world, pos, Math.max(0, 3 + getTraits().get(Trait.POWER)), i -> applyEntitySingle(owner, world, i)).isEmpty();
}
- protected boolean applyEntitySingle(Entity owner, World world, Entity e) {
+ protected boolean applyEntitySingle(@Nullable Entity owner, World world, Entity e) {
if ((!e.equals(owner) ||
(owner instanceof PlayerEntity && !EquinePredicates.PLAYER_UNICORN.test(owner))) && !(e instanceof ItemEntity)
&& !(e instanceof Caster>)) {
@@ -129,7 +131,7 @@ public class FireSpell extends AbstractAreaEffectSpell implements ProjectileSpel
return false;
}
- protected DamageSource getDamageCause(Entity target, LivingEntity attacker) {
+ protected DamageSource getDamageCause(Entity target, @Nullable LivingEntity attacker) {
return MagicalDamageSource.create("fire", attacker);
}
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/IceSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/IceSpell.java
index b26402b3..57238e39 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/IceSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/IceSpell.java
@@ -43,12 +43,10 @@ public class IceSpell extends AbstractSpell {
@Override
public boolean tick(Caster> source, Situation situation) {
- LivingEntity owner = source.getMaster();
-
boolean submerged = source.getEntity().isSubmergedInWater() || source.getEntity().isSubmergedIn(FluidTags.LAVA);
long blocksAffected = PosHelper.getAllInRegionMutable(source.getOrigin(), outerRange).filter(i -> {
- if (source.canModifyAt(i) && applyBlockSingle(owner, source.getWorld(), i, situation)) {
+ if (source.canModifyAt(i) && applyBlockSingle(source.getEntity(), source.getWorld(), i, situation)) {
if (submerged & source.getOrigin().isWithinDistance(i, rad - 1)) {
BlockState state = source.getWorld().getBlockState(i);
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/LightSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/LightSpell.java
index 18ff18ee..24a2df5c 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/LightSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/LightSpell.java
@@ -59,9 +59,9 @@ public class LightSpell extends AbstractSpell {
if (!ref.isPresent(caster.getWorld())) {
FairyEntity entity = UEntities.TWITTERMITE.create(caster.getWorld());
entity.setPosition(ref.getPosition().orElseGet(() -> {
- return caster.getMaster().getPos().add(VecHelper.supply(() -> caster.getWorld().random.nextInt(3) - 1));
+ return caster.getOriginVector().add(VecHelper.supply(() -> caster.getWorld().random.nextInt(3) - 1));
}));
- entity.setMaster(caster.getMaster());
+ entity.setMaster(caster);
entity.world.spawnEntity(entity);
ref.set(entity);
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/NecromancySpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/NecromancySpell.java
index 75f4a5e2..0b9864ca 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/NecromancySpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/NecromancySpell.java
@@ -139,7 +139,7 @@ public class NecromancySpell extends AbstractAreaEffectSpell {
minion.equipStack(EquipmentSlot.HEAD, Items.IRON_HELMET.getDefaultStack());
Equine.of(minion).filter(eq -> eq instanceof Creature).ifPresent(eq -> {
- ((Creature)eq).setMaster(source.getMaster());
+ ((Creature)eq).setMaster(source);
});
source.getWorld().spawnEntity(minion);
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ShieldSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ShieldSpell.java
index 174eba23..698fabbf 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ShieldSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ShieldSpell.java
@@ -109,7 +109,7 @@ public class ShieldSpell extends AbstractSpell {
* Calculates the maximum radius of the shield. aka The area of effect.
*/
public double getDrawDropOffRange(Caster> source) {
- float multiplier = source.getMaster().isSneaking() ? 1 : 2;
+ float multiplier = source.getMaster() != null && source.getMaster().isSneaking() ? 1 : 2;
float min = 4 + getTraits().get(Trait.POWER);
return (min + (source.getLevel().get() * 2)) / multiplier;
}
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SiphoningSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SiphoningSpell.java
index cc71f51e..9f801b89 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SiphoningSpell.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SiphoningSpell.java
@@ -109,7 +109,7 @@ public class SiphoningSpell extends AbstractAreaEffectSpell {
private void collectHealth(Caster> source) {
LivingEntity owner = source.getMaster();
- float maxHealthGain = owner.getMaxHealth() - owner.getHealth();
+ float maxHealthGain = owner == null ? 0 : owner.getMaxHealth() - owner.getHealth();
if (maxHealthGain == 0) {
return;
diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/TargetSelecter.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/TargetSelecter.java
index 254ef24c..8936bb9e 100644
--- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/TargetSelecter.java
+++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/TargetSelecter.java
@@ -29,22 +29,10 @@ public class TargetSelecter {
public Stream getEntities(Caster> source, double radius, BiPredicate, Entity> filter) {
targets.values().removeIf(Target::tick);
- Entity owner = source.getMaster();
-
- boolean ownerIsValid = spell.isFriendlyTogether(source) && (EquinePredicates.PLAYER_UNICORN.test(owner));
+ Predicate ownerCheck = isOwnerOrFriend(spell, source);
return source.findAllEntitiesInRange(radius)
- .filter(entity -> entity.isAlive() && !entity.isRemoved())
- .filter(entity -> {
- boolean hasShield = SpellPredicate.IS_SHIELD_LIKE.isOn(entity);
- boolean isOwnerOrFriend = Pony.equal(entity, owner) || owner.isConnectedThroughVehicle(entity) || FriendshipBraceletItem.isComrade(source, entity);
-
- if (!ownerIsValid && isOwnerOrFriend) {
- return true;
- }
-
- return !hasShield && (!ownerIsValid || !isOwnerOrFriend);
- })
+ .filter(entity -> entity.isAlive() && !entity.isRemoved() && !ownerCheck.test(entity) && !SpellPredicate.IS_SHIELD_LIKE.isOn(entity))
.filter(e -> filter.test(source, e))
.map(i -> {
targets.computeIfAbsent(i.getUuid(), Target::new);
@@ -57,16 +45,18 @@ public class TargetSelecter {
}
public static Predicate notOwnerOrFriend(Affine spell, Caster> source) {
+ return TargetSelecter.isOwnerOrFriend(spell, source).negate();
+ }
+
+ public static Predicate isOwnerOrFriend(Affine spell, Caster> source) {
Entity owner = source.getMaster();
- boolean ownerIsValid = spell.isFriendlyTogether(source) && (EquinePredicates.PLAYER_UNICORN.test(owner));
-
- if (!ownerIsValid) {
- return e -> true;
+ if (!(spell.isFriendlyTogether(source) && EquinePredicates.PLAYER_UNICORN.test(owner))) {
+ return e -> FriendshipBraceletItem.isComrade(source, e);
}
return entity -> {
- return !ownerIsValid || !(Pony.equal(entity, owner) || owner.isConnectedThroughVehicle(entity) || FriendshipBraceletItem.isComrade(source, entity));
+ return FriendshipBraceletItem.isComrade(source, entity) || (owner != null && (Pony.equal(entity, owner) || owner.isConnectedThroughVehicle(entity)));
};
}
diff --git a/src/main/java/com/minelittlepony/unicopia/entity/CastSpellEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/CastSpellEntity.java
index e76b6361..d3547abc 100644
--- a/src/main/java/com/minelittlepony/unicopia/entity/CastSpellEntity.java
+++ b/src/main/java/com/minelittlepony/unicopia/entity/CastSpellEntity.java
@@ -1,9 +1,7 @@
package com.minelittlepony.unicopia.entity;
-import java.util.Optional;
-import java.util.UUID;
-
import com.minelittlepony.unicopia.Affinity;
+import com.minelittlepony.unicopia.WeaklyOwned;
import com.minelittlepony.unicopia.ability.magic.Caster;
import com.minelittlepony.unicopia.ability.magic.Levelled;
import com.minelittlepony.unicopia.ability.magic.SpellContainer;
@@ -12,6 +10,7 @@ import com.minelittlepony.unicopia.ability.magic.spell.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
import com.minelittlepony.unicopia.network.Channel;
import com.minelittlepony.unicopia.network.MsgSpawnProjectile;
+import com.minelittlepony.unicopia.network.datasync.EffectSync;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
@@ -25,45 +24,25 @@ import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText;
import net.minecraft.world.World;
-public class CastSpellEntity extends LightEmittingEntity implements Caster {
+public class CastSpellEntity extends LightEmittingEntity implements Caster, WeaklyOwned {
private static final TrackedData GRAVITY = DataTracker.registerData(CastSpellEntity.class, TrackedDataHandlerRegistry.FLOAT);
- private static final TrackedData> SPELL = DataTracker.registerData(CastSpellEntity.class, TrackedDataHandlerRegistry.OPTIONAL_UUID);
+ private static final TrackedData EFFECT = DataTracker.registerData(CastSpellEntity.class, TrackedDataHandlerRegistry.TAG_COMPOUND);
private static final LevelStore LEVELS = Levelled.fixed(0);
private final EntityPhysics physics = new EntityPhysics<>(this, GRAVITY);
- private final SpellContainer spell = new SpellContainer.Delegate() {
- @Override
- public SpellContainer delegate() {
- return Caster.of(getMaster()).map(Caster::getSpellSlot).orElse(SpellContainer.EMPTY);
- }
-
- @Override
- public void put(Spell spell) {
- getDataTracker().set(SPELL, Optional.ofNullable(spell).map(Spell::getUuid));
- SpellContainer.Delegate.super.put(spell);
- }
-
- @Override
- public boolean clear() {
- return getDataTracker().get(SPELL).map(id -> {
- return delegate().removeIf(spell -> spell.getUuid().equals(id), true);
- }).orElse(false);
- }
- };
+ private final EffectSync effectDelegate = new EffectSync(this, EFFECT);
private final EntityReference owner = new EntityReference<>();
- private int orphanedTicks;
-
public CastSpellEntity(EntityType> type, World world) {
super(type, world);
}
@Override
protected void initDataTracker() {
- getDataTracker().startTracking(SPELL, Optional.empty());
+ getDataTracker().startTracking(EFFECT, new NbtCompound());
}
@Override
@@ -88,35 +67,14 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster 0) {
- return;
- }
- discard();
- return;
- }
-
- orphanedTicks = 0;
-
- if (dataTracker.get(SPELL).filter(spellId -> {
- return getSpellSlot().forEach(spell -> {
- return spell.getUuid().equals(spellId) ? Operation.ofBoolean(spell.tick(this, Situation.GROUND_ENTITY)) : Operation.SKIP;
- }, true);
- }).isEmpty()) {
+ if (!getSpellSlot().forEach(spell -> Operation.ofBoolean(spell.tick(this, Situation.GROUND_ENTITY)), true)) {
discard();
}
}
@Override
- public void setMaster(LivingEntity owner) {
- this.owner.set(owner);
- }
-
- @Override
- public LivingEntity getMaster() {
- return owner.get(((Entity)this).world);
+ public EntityReference getMasterReference() {
+ return owner;
}
@Override
@@ -141,7 +99,7 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster {
- tag.putUuid("spellId", spellId);
+ getSpellSlot().get(true).ifPresent(effect -> {
+ tag.put("effect", Spell.writeNbt(effect));
});
}
@@ -162,9 +120,8 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster {
+public class Creature extends Living implements WeaklyOwned {
private static final TrackedData EFFECT = DataTracker.registerData(LivingEntity.class, TrackedDataHandlerRegistry.TAG_COMPOUND);
private static final TrackedData MASTER = DataTracker.registerData(LivingEntity.class, TrackedDataHandlerRegistry.TAG_COMPOUND);
public static final TrackedData GRAVITY = DataTracker.registerData(LivingEntity.class, TrackedDataHandlerRegistry.FLOAT);
@@ -68,17 +71,27 @@ public class Creature extends Living {
}
public boolean isMinion() {
- Entity master = getMaster();
- return master != null && master != getEntity();
+ return master.getId().isPresent();
}
@Override
+ public World getWorld() {
+ return super.getWorld();
+ }
+
+ @Override
+ @NotNull
public LivingEntity getMaster() {
NbtCompound data = entity.getDataTracker().get(MASTER);
master.fromNBT(data);
return master.getOrEmpty(getWorld()).orElse(entity);
}
+ @Override
+ public EntityReference getMasterReference() {
+ return master;
+ }
+
@Override
public Entity getEntity() {
return entity;
@@ -112,7 +125,7 @@ public class Creature extends Living {
Predicate filter = TargetSelecter.notOwnerOrFriend(this, this).and(e -> {
return Equine.of(e)
.filter(eq -> eq instanceof Creature)
- .filter(eq -> ((Creature)eq).getMaster() == getMaster())
+ .filter(eq -> ((Creature)eq).hasCommonOwner(this))
.isEmpty();
});
diff --git a/src/main/java/com/minelittlepony/unicopia/entity/EntityReference.java b/src/main/java/com/minelittlepony/unicopia/entity/EntityReference.java
index d0bbf640..af2cc36c 100644
--- a/src/main/java/com/minelittlepony/unicopia/entity/EntityReference.java
+++ b/src/main/java/com/minelittlepony/unicopia/entity/EntityReference.java
@@ -17,7 +17,7 @@ import net.minecraft.world.World;
public class EntityReference implements NbtSerialisable {
- private UUID uuid;
+ private Optional uuid = Optional.empty();
private int clientId;
private Optional pos = Optional.empty();
@@ -32,18 +32,28 @@ public class EntityReference implements NbtSerialisable {
fromNBT(nbt);
}
+ public void copyFrom(EntityReference other) {
+ uuid = other.uuid;
+ clientId = other.clientId;
+ pos = other.pos;
+ }
+
public void set(@Nullable T entity) {
if (entity != null) {
- uuid = entity.getUuid();
+ uuid = Optional.of(entity.getUuid());
clientId = entity.getId();
pos = Optional.of(entity.getPos());
} else {
- uuid = null;
+ uuid = Optional.empty();
clientId = 0;
pos = Optional.empty();
}
}
+ public Optional getId() {
+ return uuid;
+ }
+
/**
* Gets the position the last known position of the assigned entity.
*/
@@ -66,8 +76,8 @@ public class EntityReference implements NbtSerialisable {
@SuppressWarnings("unchecked")
public Optional getOrEmpty(World world) {
- if (uuid != null && world instanceof ServerWorld) {
- return Optional.ofNullable((T)((ServerWorld)world).getEntity(uuid)).filter(this::checkReference);
+ if (uuid.isPresent() && world instanceof ServerWorld) {
+ return uuid.map(((ServerWorld)world)::getEntity).map(e -> (T)e).filter(this::checkReference);
}
if (clientId != 0) {
@@ -84,18 +94,14 @@ public class EntityReference implements NbtSerialisable {
@Override
public void toNBT(NbtCompound tag) {
- if (uuid != null) {
- tag.putUuid("uuid", uuid);
- }
- pos.ifPresent(p -> {
- tag.put("pos", NbtSerialisable.writeVector(p));
- });
+ uuid.ifPresent(uuid -> tag.putUuid("uuid", uuid));
+ pos.ifPresent(p -> tag.put("pos", NbtSerialisable.writeVector(p)));
tag.putInt("clientId", clientId);
}
@Override
public void fromNBT(NbtCompound tag) {
- uuid = tag.containsUuid("uuid") ? tag.getUuid("uuid") : null;
+ uuid = tag.containsUuid("uuid") ? Optional.of(tag.getUuid("uuid")) : Optional.empty();
pos = tag.contains("pos") ? Optional.ofNullable(NbtSerialisable.readVector(tag.getList("pos", NbtElement.DOUBLE_TYPE))) : Optional.empty();
clientId = tag.getInt("clientId");
}
diff --git a/src/main/java/com/minelittlepony/unicopia/entity/FairyEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/FairyEntity.java
index f4018d77..c5de80f7 100644
--- a/src/main/java/com/minelittlepony/unicopia/entity/FairyEntity.java
+++ b/src/main/java/com/minelittlepony/unicopia/entity/FairyEntity.java
@@ -5,8 +5,8 @@ import java.util.Optional;
import org.jetbrains.annotations.Nullable;
-import com.minelittlepony.unicopia.Owned;
import com.minelittlepony.unicopia.USounds;
+import com.minelittlepony.unicopia.WeaklyOwned;
import com.minelittlepony.unicopia.particle.MagicParticleEffect;
import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.minelittlepony.unicopia.particle.UParticles;
@@ -42,7 +42,7 @@ import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraft.world.event.GameEvent;
-public class FairyEntity extends PathAwareEntity implements DynamicLightSource, Owned {
+public class FairyEntity extends PathAwareEntity implements DynamicLightSource, WeaklyOwned {
private final EntityReference owner = new EntityReference<>();
private final EntityReference assignment = new EntityReference<>();
@@ -101,13 +101,8 @@ public class FairyEntity extends PathAwareEntity implements DynamicLightSource,
}
@Override
- public void setMaster(LivingEntity owner) {
- this.owner.set(owner);
- }
-
- @Override
- public LivingEntity getMaster() {
- return owner.get(((Entity)this).world);
+ public EntityReference getMasterReference() {
+ return owner;
}
@Override
diff --git a/src/main/java/com/minelittlepony/unicopia/entity/ItemImpl.java b/src/main/java/com/minelittlepony/unicopia/entity/ItemImpl.java
index 042b432c..fd344a5b 100644
--- a/src/main/java/com/minelittlepony/unicopia/entity/ItemImpl.java
+++ b/src/main/java/com/minelittlepony/unicopia/entity/ItemImpl.java
@@ -2,6 +2,8 @@ package com.minelittlepony.unicopia.entity;
import java.util.Random;
+import org.jetbrains.annotations.NotNull;
+
import com.minelittlepony.unicopia.Owned;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.UTags;
@@ -132,12 +134,12 @@ public class ItemImpl implements Equine, Owned {
@Override
public Race getSpecies() {
- return Race.fromId(getMaster().getDataTracker().get(ITEM_RACE));
+ return Race.fromId(owner.getDataTracker().get(ITEM_RACE));
}
@Override
public void setSpecies(Race race) {
- getMaster().getDataTracker().set(ITEM_RACE, race.ordinal());
+ owner.getDataTracker().set(ITEM_RACE, race.ordinal());
}
@Override
@@ -158,6 +160,7 @@ public class ItemImpl implements Equine, Owned {
}
@Override
+ @NotNull
public ItemEntity getMaster() {
return owner;
}
diff --git a/src/main/java/com/minelittlepony/unicopia/entity/Living.java b/src/main/java/com/minelittlepony/unicopia/entity/Living.java
index 1a4c7a3c..753a9830 100644
--- a/src/main/java/com/minelittlepony/unicopia/entity/Living.java
+++ b/src/main/java/com/minelittlepony/unicopia/entity/Living.java
@@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.entity;
import java.util.Optional;
import java.util.stream.Stream;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Unicopia;
@@ -81,13 +82,13 @@ public abstract class Living implements Equine, Caste
}
@Override
+ @NotNull
public T getMaster() {
return entity;
}
@Override
public void tick() {
-
try {
getSpellSlot().forEach(spell -> Operation.ofBoolean(spell.tick(this, Situation.BODY)), true);
} catch (Exception e) {
diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Disguise.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Disguise.java
index d87686ac..f6790006 100644
--- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Disguise.java
+++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Disguise.java
@@ -101,7 +101,7 @@ public interface Disguise extends FlightType.Provider, PlayerDimensions.Provider
player.setInvisible(true);
if (entity instanceof Owned) {
- ((Owned)entity).setMaster(player.getMaster());
+ ((Owned)entity).setMaster(player);
}
if (entity instanceof PlayerEntity) {
diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/MobBehaviour.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/MobBehaviour.java
index 7cb297f1..ca94cf0f 100644
--- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/MobBehaviour.java
+++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/MobBehaviour.java
@@ -27,7 +27,7 @@ public class MobBehaviour extends EntityBehaviour {
protected LivingEntity findTarget(Pony player, T entity) {
return RayTraceHelper.findEntity(player.getEntity(), 6, 1,
- e -> e instanceof LivingEntity && e != entity && e != player.getMaster())
+ e -> e instanceof LivingEntity && e != entity && !player.isOwnedBy(e))
.orElseGet(() -> getDummy(entity));
}
diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/dummy/DummyClientPlayerEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/player/dummy/DummyClientPlayerEntity.java
index e2258b01..af23246e 100644
--- a/src/main/java/com/minelittlepony/unicopia/entity/player/dummy/DummyClientPlayerEntity.java
+++ b/src/main/java/com/minelittlepony/unicopia/entity/player/dummy/DummyClientPlayerEntity.java
@@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.entity.player.dummy;
import java.util.List;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.Owned;
@@ -66,6 +67,7 @@ public class DummyClientPlayerEntity extends AbstractClientPlayerEntity implemen
}
@Override
+ @Nullable
public PlayerEntity getMaster() {
return owner;
}
diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/dummy/DummyPlayerEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/player/dummy/DummyPlayerEntity.java
index de9f870a..a4caa6a4 100644
--- a/src/main/java/com/minelittlepony/unicopia/entity/player/dummy/DummyPlayerEntity.java
+++ b/src/main/java/com/minelittlepony/unicopia/entity/player/dummy/DummyPlayerEntity.java
@@ -1,5 +1,7 @@
package com.minelittlepony.unicopia.entity.player.dummy;
+import org.jetbrains.annotations.Nullable;
+
import com.minelittlepony.unicopia.InteractionManager;
import com.minelittlepony.unicopia.Owned;
import com.mojang.authlib.GameProfile;
@@ -23,6 +25,7 @@ public class DummyPlayerEntity extends PlayerEntity implements Owned list, TooltipContext tooltipContext) {
if (isSigned(stack)) {
- list.add(new TranslatableText("item.unicopia.friendship_bracelet.issuer", getSignature(stack)));
+ list.add(new TranslatableText("item.unicopia.friendship_bracelet.issuer", getSignatorName(stack)));
}
if (isGlowing(stack)) {
list.add(new TranslatableText("item.unicopia.friendship_bracelet.glowing").formatted(Formatting.ITALIC, Formatting.GRAY));
@@ -78,16 +80,25 @@ public class FriendshipBraceletItem extends WearableItem implements DyeableItem,
}
private boolean checkSignature(ItemStack stack, PlayerEntity player) {
- return player.getName().asString().contentEquals(getSignature(stack));
+ return checkSignature(stack, player.getUuid());
+ }
+
+ private boolean checkSignature(ItemStack stack, UUID player) {
+ return player.equals(getSignatorId(stack));
}
@Nullable
- public static String getSignature(ItemStack stack) {
+ public static String getSignatorName(ItemStack stack) {
return isSigned(stack) ? stack.getNbt().getString("issuer") : null;
}
+ @Nullable
+ public static UUID getSignatorId(ItemStack stack) {
+ return isSigned(stack) ? stack.getNbt().getUuid("issuer_id") : null;
+ }
+
public static boolean isSigned(ItemStack stack) {
- return stack.hasNbt() && stack.getNbt().contains("issuer");
+ return stack.hasNbt() && stack.getNbt().contains("issuer_id");
}
public static boolean isSignedBy(ItemStack stack, PlayerEntity player) {
@@ -95,13 +106,18 @@ public class FriendshipBraceletItem extends WearableItem implements DyeableItem,
&& ((FriendshipBraceletItem)stack.getItem()).checkSignature(stack, player);
}
- public static boolean isComrade(Caster> caster, Entity entity) {
- Entity master = caster.getMaster();
- if (master instanceof PlayerEntity && entity instanceof LivingEntity) {
- return isSignedBy(((LivingEntity)entity).getOffHandStack(), (PlayerEntity)master)
- || isSignedBy(((LivingEntity)entity).getEquippedStack(EquipmentSlot.CHEST), (PlayerEntity)master);
- }
+ public static boolean isSignedBy(ItemStack stack, UUID player) {
+ return stack.getItem() instanceof FriendshipBraceletItem
+ && ((FriendshipBraceletItem)stack.getItem()).checkSignature(stack, player);
+ }
+ public static boolean isComrade(Caster> caster, Entity entity) {
+ if (entity instanceof LivingEntity) {
+ return caster.getMasterId().filter(id -> {
+ return isSignedBy(((LivingEntity)entity).getOffHandStack(), id)
+ || isSignedBy(((LivingEntity)entity).getEquippedStack(EquipmentSlot.CHEST), id);
+ }).isPresent();
+ }
return false;
}
}
diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinDamageSource.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinDamageSource.java
index baf6b929..570eb4c0 100644
--- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinDamageSource.java
+++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinDamageSource.java
@@ -15,13 +15,14 @@ import net.minecraft.text.TranslatableText;
@Mixin(DamageSource.class)
abstract class MixinDamageSource {
+ @SuppressWarnings("unchecked")
@Inject(method = "getDeathMessage", at = @At("RETURN"), cancellable = true)
private void onGetDeathMessage(LivingEntity entity, CallbackInfoReturnable info) {
Equine.of(entity).map(Equine::getAttacker).ifPresent(attacker -> {
DamageSource self = (DamageSource)(Object)this;
Entity prime = entity.getPrimeAdversary();
- if (prime != null && !(attacker instanceof Owned> && ((Owned>)attacker).getMaster() == prime)) {
+ if (prime != null && !(attacker instanceof Owned> && ((Owned)attacker).isOwnedBy(prime))) {
info.setReturnValue(new TranslatableText("death.attack.generic.and_also", info.getReturnValue(), attacker.getDisplayName()));
return;
}
diff --git a/src/main/java/com/minelittlepony/unicopia/projectile/MagicProjectileEntity.java b/src/main/java/com/minelittlepony/unicopia/projectile/MagicProjectileEntity.java
index bfc433bf..a0792595 100644
--- a/src/main/java/com/minelittlepony/unicopia/projectile/MagicProjectileEntity.java
+++ b/src/main/java/com/minelittlepony/unicopia/projectile/MagicProjectileEntity.java
@@ -66,7 +66,7 @@ public class MagicProjectileEntity extends ThrownItemEntity implements Caster