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 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 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