Allow spells and entities to retain their owner if the owner is offline (multiplayer) or in another dimension (nether), and handle situations where the owner is not around (getOwner() == null)

This commit is contained in:
Sollace 2022-01-01 22:08:50 +02:00
parent e3d1f8c973
commit e6b0ad9fa4
32 changed files with 290 additions and 153 deletions

View file

@ -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.
* <p>
* Ownership is retained so long as the owner is still active. If the owner leaves or dies, the link is broken.
*
* @param <E> The type of object that owns us.
*/
public interface Owned<E> {
public interface Owned<E extends Entity> {
/**
* Gets the owner that holds this object.
*/
@Nullable
E getMaster();
/**
* Gets the unique entity id of the entity that holds this object.
* <p>
* Since {@link Owned#getMaster()} will only return if the owner is loaded, use this to perform checks
* in the owner's absence.
*/
default Optional<UUID> 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());
}
}

View file

@ -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 <E> The type of object that owns us.
*/
public interface WeaklyOwned<E extends Entity> extends Owned<E> {
World getWorld();
EntityReference<E> 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<E>)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<UUID> getMasterId() {
return getMasterReference().getId();
}
}

View file

@ -62,7 +62,7 @@ public class UnicornDispellAbility implements Ability<Pos> {
@Override
public double getCostEstimate(Pony player) {
return getTarget(player)
.filter(caster -> caster.getMaster() != player.getMaster())
.filter(caster -> !caster.hasCommonOwner(player))
.isPresent() ? 10 : 0;
}

View file

@ -129,12 +129,17 @@ public class UnicornTeleportAbility implements Ability<Pos> {
}
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();

View file

@ -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<World> dimension;
/**
* The visual effect
*/
private final ParticleHandle particlEffect = new ParticleHandle();
/**
* The cast spell entity
*/
private final EntityReference<CastSpellEntity> 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<CastSpellEntity> 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"));

View file

@ -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);
});

View file

@ -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()) {

View file

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

View file

@ -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;
}

View file

@ -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) {

View file

@ -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);
}

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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;
}

View file

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

View file

@ -29,22 +29,10 @@ public class TargetSelecter {
public Stream<Entity> getEntities(Caster<?> source, double radius, BiPredicate<Caster<?>, Entity> filter) {
targets.values().removeIf(Target::tick);
Entity owner = source.getMaster();
boolean ownerIsValid = spell.isFriendlyTogether(source) && (EquinePredicates.PLAYER_UNICORN.test(owner));
Predicate<Entity> 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 <T extends Entity> Predicate<T> notOwnerOrFriend(Affine spell, Caster<?> source) {
return TargetSelecter.<T>isOwnerOrFriend(spell, source).negate();
}
public static <T extends Entity> Predicate<T> 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)));
};
}

View file

@ -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<LivingEntity> {
public class CastSpellEntity extends LightEmittingEntity implements Caster<LivingEntity>, WeaklyOwned<LivingEntity> {
private static final TrackedData<Float> GRAVITY = DataTracker.registerData(CastSpellEntity.class, TrackedDataHandlerRegistry.FLOAT);
private static final TrackedData<Optional<UUID>> SPELL = DataTracker.registerData(CastSpellEntity.class, TrackedDataHandlerRegistry.OPTIONAL_UUID);
private static final TrackedData<NbtCompound> EFFECT = DataTracker.registerData(CastSpellEntity.class, TrackedDataHandlerRegistry.TAG_COMPOUND);
private static final LevelStore LEVELS = Levelled.fixed(0);
private final EntityPhysics<CastSpellEntity> 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<LivingEntity> 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<Livin
return;
}
LivingEntity master = getMaster();
if (master == null || master.isRemoved()) {
if (orphanedTicks-- > 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<LivingEntity> getMasterReference() {
return owner;
}
@Override
@ -141,7 +99,7 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster<Livin
@Override
public SpellContainer getSpellSlot() {
return spell;
return effectDelegate;
}
@Override
@ -152,8 +110,8 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster<Livin
@Override
protected void writeCustomDataToNbt(NbtCompound tag) {
tag.put("owner", owner.toNBT());
dataTracker.get(SPELL).ifPresent(spellId -> {
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<Livin
if (tag.contains("owner")) {
owner.fromNBT(tag.getCompound("owner"));
}
orphanedTicks = 60;
if (tag.contains("spellId")) {
dataTracker.set(SPELL, Optional.ofNullable(tag.getUuid("spellId")));
if (tag.contains("effect")) {
getSpellSlot().put(Spell.readNbt(tag.getCompound("effect")));
}
}

View file

@ -3,10 +3,12 @@ package com.minelittlepony.unicopia.entity;
import java.util.Optional;
import java.util.function.Predicate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.Affinity;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.WeaklyOwned;
import com.minelittlepony.unicopia.ability.magic.Affine;
import com.minelittlepony.unicopia.ability.magic.Levelled;
import com.minelittlepony.unicopia.ability.magic.spell.Spell;
@ -33,8 +35,9 @@ import net.minecraft.entity.mob.SlimeEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.world.World;
public class Creature extends Living<LivingEntity> {
public class Creature extends Living<LivingEntity> implements WeaklyOwned<LivingEntity> {
private static final TrackedData<NbtCompound> EFFECT = DataTracker.registerData(LivingEntity.class, TrackedDataHandlerRegistry.TAG_COMPOUND);
private static final TrackedData<NbtCompound> MASTER = DataTracker.registerData(LivingEntity.class, TrackedDataHandlerRegistry.TAG_COMPOUND);
public static final TrackedData<Float> GRAVITY = DataTracker.registerData(LivingEntity.class, TrackedDataHandlerRegistry.FLOAT);
@ -68,17 +71,27 @@ public class Creature extends Living<LivingEntity> {
}
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<LivingEntity> getMasterReference() {
return master;
}
@Override
public Entity getEntity() {
return entity;
@ -112,7 +125,7 @@ public class Creature extends Living<LivingEntity> {
Predicate<LivingEntity> filter = TargetSelecter.<LivingEntity>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();
});

View file

@ -17,7 +17,7 @@ import net.minecraft.world.World;
public class EntityReference<T extends Entity> implements NbtSerialisable {
private UUID uuid;
private Optional<UUID> uuid = Optional.empty();
private int clientId;
private Optional<Vec3d> pos = Optional.empty();
@ -32,18 +32,28 @@ public class EntityReference<T extends Entity> implements NbtSerialisable {
fromNBT(nbt);
}
public void copyFrom(EntityReference<T> 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<UUID> getId() {
return uuid;
}
/**
* Gets the position the last known position of the assigned entity.
*/
@ -66,8 +76,8 @@ public class EntityReference<T extends Entity> implements NbtSerialisable {
@SuppressWarnings("unchecked")
public Optional<T> 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<T extends Entity> 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");
}

View file

@ -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<LivingEntity> {
public class FairyEntity extends PathAwareEntity implements DynamicLightSource, WeaklyOwned<LivingEntity> {
private final EntityReference<LivingEntity> owner = new EntityReference<>();
private final EntityReference<LivingEntity> 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<LivingEntity> getMasterReference() {
return owner;
}
@Override

View file

@ -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<ItemEntity>, Owned<ItemEntity> {
@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<ItemEntity>, Owned<ItemEntity> {
}
@Override
@NotNull
public ItemEntity getMaster() {
return owner;
}

View file

@ -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<T extends LivingEntity> implements Equine<T>, 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) {

View file

@ -101,7 +101,7 @@ public interface Disguise extends FlightType.Provider, PlayerDimensions.Provider
player.setInvisible(true);
if (entity instanceof Owned) {
((Owned<LivingEntity>)entity).setMaster(player.getMaster());
((Owned<LivingEntity>)entity).setMaster(player);
}
if (entity instanceof PlayerEntity) {

View file

@ -27,7 +27,7 @@ public class MobBehaviour<T extends MobEntity> extends EntityBehaviour<T> {
protected LivingEntity findTarget(Pony player, T entity) {
return RayTraceHelper.<LivingEntity>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));
}

View file

@ -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;
}

View file

@ -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<PlayerEntit
}
@Override
@Nullable
public PlayerEntity getMaster() {
return owner;
}

View file

@ -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;
@ -29,6 +31,7 @@ public class DummyServerPlayerEntity extends ServerPlayerEntity implements Owned
}
@Override
@Nullable
public PlayerEntity getMaster() {
return owner;
}

View file

@ -1,6 +1,7 @@
package com.minelittlepony.unicopia.item;
import java.util.List;
import java.util.UUID;
import org.jetbrains.annotations.Nullable;
@ -41,6 +42,7 @@ public class FriendshipBraceletItem extends WearableItem implements DyeableItem,
ItemStack result = stack.copy();
result.setCount(1);
result.getOrCreateNbt().putString("issuer", player.getName().asString());
result.getOrCreateNbt().putUuid("issuer_id", player.getUuid());
if (!player.getAbilities().creativeMode) {
stack.decrement(1);
@ -65,7 +67,7 @@ public class FriendshipBraceletItem extends WearableItem implements DyeableItem,
@Environment(EnvType.CLIENT)
public void appendTooltip(ItemStack stack, @Nullable World world, List<Text> 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;
}
}

View file

@ -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<Text> 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<Entity>)attacker).isOwnedBy(prime))) {
info.setReturnValue(new TranslatableText("death.attack.generic.and_also", info.getReturnValue(), attacker.getDisplayName()));
return;
}

View file

@ -66,7 +66,7 @@ public class MagicProjectileEntity extends ThrownItemEntity implements Caster<Li
super(type, world);
}
public MagicProjectileEntity(World world, LivingEntity thrower) {
public MagicProjectileEntity(World world, @Nullable LivingEntity thrower) {
super(UEntities.THROWN_ITEM, thrower, world);
}
@ -103,6 +103,7 @@ public class MagicProjectileEntity extends ThrownItemEntity implements Caster<Li
}
@Override
@Nullable
public LivingEntity getMaster() {
return (LivingEntity)getOwner();
}

View file

@ -31,7 +31,7 @@ public class MagicalDamageSource extends EntityDamageSource {
return new MagicalDamageSource(type, null, null, false, false);
}
public static DamageSource create(String type, LivingEntity source) {
public static DamageSource create(String type, @Nullable LivingEntity source) {
return new MagicalDamageSource(type, source, null, false, false);
}