From d03c0586cf68a32ce43509bc20b2f06f91ced20e Mon Sep 17 00:00:00 2001 From: Sollace Date: Thu, 30 Dec 2021 00:59:17 +0200 Subject: [PATCH] Make the necromancy spell better --- .../magic/spell/effect/LightSpell.java | 4 +- .../magic/spell/effect/NecromancySpell.java | 72 +++++++++++------- .../magic/spell/effect/TargetSelecter.java | 3 +- .../client/render/WorldRenderDelegate.java | 11 ++- .../unicopia/entity/Creature.java | 75 ++++++++++++++++++- .../unicopia/entity/EntityReference.java | 10 +++ 6 files changed, 141 insertions(+), 34 deletions(-) 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 6bbdd725..27c08931 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 @@ -95,9 +95,7 @@ public class LightSpell extends AbstractSpell { lights.clear(); if (compound.contains("lights", NbtElement.LIST_TYPE)) { compound.getList("lights", NbtElement.COMPOUND_TYPE).forEach(nbt -> { - EntityReference light = new EntityReference<>(); - light.fromNBT((NbtCompound)nbt); - lights.add(light); + lights.add(new EntityReference<>((NbtCompound)nbt)); }); } } 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 cbd349eb..89a7c7fe 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 @@ -8,17 +8,22 @@ import com.minelittlepony.unicopia.ability.magic.spell.AbstractAreaEffectSpell; import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; +import com.minelittlepony.unicopia.entity.Creature; import com.minelittlepony.unicopia.entity.EntityReference; +import com.minelittlepony.unicopia.entity.Equine; import com.minelittlepony.unicopia.util.Weighted; import com.minelittlepony.unicopia.util.shape.Shape; import com.minelittlepony.unicopia.util.shape.Sphere; import net.minecraft.entity.EntityType; +import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.mob.ZombieEntity; +import net.minecraft.item.Items; import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; import net.minecraft.nbt.NbtList; import net.minecraft.particle.ParticleTypes; +import net.minecraft.sound.SoundEvents; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.world.Difficulty; @@ -28,16 +33,17 @@ import net.minecraft.world.WorldEvents; * An area-effect spell that summons the undead. */ public class NecromancySpell extends AbstractAreaEffectSpell { - private final Weighted> spawnPool = new Weighted>() .put(7, EntityType.ZOMBIE) .put(4, EntityType.HUSK) + .put(3, EntityType.DROWNED) .put(2, EntityType.ZOMBIFIED_PIGLIN) - .put(1, EntityType.ZOMBIE_VILLAGER) - .put(1, EntityType.ZOMBIE_HORSE); + .put(1, EntityType.ZOMBIE_VILLAGER); private final List> summonedEntities = new ArrayList<>(); + private int spawnCountdown; + protected NecromancySpell(SpellType type, SpellTraits traits) { super(type, traits); } @@ -50,10 +56,14 @@ public class NecromancySpell extends AbstractAreaEffectSpell { return false; } + boolean rainy = source.getWorld().hasRain(source.getOrigin()); + if (source.isClient()) { - source.spawnParticles(new Sphere(false, radius), 5, pos -> { - if (!source.getWorld().isAir(new BlockPos(pos).down())) { - source.addParticle(ParticleTypes.FLAME, pos, Vec3d.ZERO); + source.spawnParticles(new Sphere(true, radius * 2), rainy ? 98 : 125, pos -> { + BlockPos bpos = new BlockPos(pos); + + if (!source.getWorld().isAir(bpos.down())) { + source.addParticle(source.getWorld().hasRain(bpos) ? ParticleTypes.SMOKE : ParticleTypes.FLAME, pos, Vec3d.ZERO); } }); return true; @@ -63,19 +73,27 @@ public class NecromancySpell extends AbstractAreaEffectSpell { return true; } - summonedEntities.removeIf(ref -> !ref.isPresent(source.getWorld())); + summonedEntities.removeIf(ref -> ref.getOrEmpty(source.getWorld()).filter(e -> { + if (e.getPos().distanceTo(source.getOriginVector()) > radius * 2) { + e.world.sendEntityStatus(e, (byte)60); + e.discard(); + return false; + } + return true; + }).isEmpty()); float additional = source.getWorld().getLocalDifficulty(source.getOrigin()).getLocalDifficulty() + getTraits().get(Trait.CHAOS, 0, 10); - Shape affectRegion = new Sphere(false, radius); + if (--spawnCountdown > 0) { + return true; + } + spawnCountdown = source.getWorld().random.nextInt(rainy ? 2000 : 100); - if (source.getWorld().random.nextInt(100) != 0) { + if (summonedEntities.size() > 10 + additional) { return true; } - if (source.findAllEntitiesInRange(radius, e -> e instanceof ZombieEntity).count() >= 10 * (1 + additional)) { - return false; - } + Shape affectRegion = new Sphere(false, radius); for (int i = 0; i < 10; i++) { Vec3d pos = affectRegion.computePoint(source.getWorld().random).add(source.getOriginVector()); @@ -107,26 +125,31 @@ public class NecromancySpell extends AbstractAreaEffectSpell { } protected void spawnMonster(Caster source, Vec3d pos, EntityType type) { - LivingEntity zombie = type.create(source.getWorld()); + LivingEntity minion = type.create(source.getWorld()); source.subtractEnergyCost(3); - zombie.updatePositionAndAngles(pos.x, pos.y, pos.z, 0, 0); - zombie.setVelocity(0, 0.3, 0); + minion.updatePositionAndAngles(pos.x, pos.y, pos.z, 0, 0); + minion.setVelocity(0, 0.3, 0); - source.getWorld().syncWorldEvent(WorldEvents.ZOMBIE_BREAKS_WOODEN_DOOR, zombie.getBlockPos(), 0); + source.getWorld().syncWorldEvent(WorldEvents.DRAGON_BREATH_CLOUD_SPAWNS, minion.getBlockPos(), 0); + source.playSound(SoundEvents.BLOCK_BELL_USE, 1, 0.3F); + source.spawnParticles(ParticleTypes.LARGE_SMOKE, 10); + minion.equipStack(EquipmentSlot.HEAD, Items.IRON_HELMET.getDefaultStack()); - source.getWorld().spawnEntity(zombie); + Equine.of(minion).filter(eq -> eq instanceof Creature).ifPresent(eq -> { + ((Creature)eq).setMaster(source.getMaster()); + }); - EntityReference ref = new EntityReference<>(); - ref.set(zombie); - summonedEntities.add(ref); + source.getWorld().spawnEntity(minion); + summonedEntities.add(new EntityReference<>(minion)); setDirty(); } @Override public void toNBT(NbtCompound compound) { super.toNBT(compound); + spawnCountdown = compound.getInt("spawnCountdown"); if (summonedEntities.size() > 0) { NbtList list = new NbtList(); summonedEntities.forEach(ref -> list.add(ref.toNBT())); @@ -137,12 +160,11 @@ public class NecromancySpell extends AbstractAreaEffectSpell { @Override public void fromNBT(NbtCompound compound) { super.fromNBT(compound); + compound.putInt("spawnCountdown", spawnCountdown); summonedEntities.clear(); if (compound.contains("summonedEntities")) { - compound.getList("summonedEntities", 10).forEach(tag -> { - EntityReference ref = new EntityReference<>(); - ref.fromNBT((NbtCompound)tag); - summonedEntities.add(ref); + compound.getList("summonedEntities", NbtElement.COMPOUND_TYPE).forEach(tag -> { + summonedEntities.add(new EntityReference<>((NbtCompound)tag)); }); } } 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 00f47cef..7966bc30 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 @@ -8,6 +8,7 @@ import java.util.function.Predicate; import java.util.stream.Stream; import com.minelittlepony.unicopia.EquinePredicates; +import com.minelittlepony.unicopia.ability.magic.Affine; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.spell.Spell; @@ -55,7 +56,7 @@ public class TargetSelecter { return targets.values().stream().filter(Target::canHurt).count(); } - public static Predicate notOwnerOrFriend(Spell spell, Caster source) { + public static Predicate notOwnerOrFriend(Affine spell, Caster source) { Entity owner = source.getMaster(); boolean ownerIsValid = spell.isFriendlyTogether(source) && (EquinePredicates.PLAYER_UNICORN.test(owner)); diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/WorldRenderDelegate.java b/src/main/java/com/minelittlepony/unicopia/client/render/WorldRenderDelegate.java index 80e78e96..fbe8ebcd 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/WorldRenderDelegate.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/WorldRenderDelegate.java @@ -10,6 +10,7 @@ import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance; import com.minelittlepony.unicopia.entity.behaviour.FallingBlockBehaviour; import com.minelittlepony.unicopia.entity.player.Pony; +import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.block.BlockState; import net.minecraft.block.entity.BlockEntity; @@ -62,7 +63,12 @@ public class WorldRenderDelegate { matrices.push(); - Entity owner = pony.getMaster(); + Entity owner = pony.getEntity(); + Entity master = pony.getMaster(); + + if (master != owner) { + RenderSystem.setShaderColor(0, 0, 1, 1); + } boolean negative = pony.getPhysics().isGravityNegative(); @@ -77,7 +83,6 @@ public class WorldRenderDelegate { if (pony instanceof Pony) { roll = ((Pony)pony).getCamera().calculateRoll(); - matrices.multiply(Vec3f.NEGATIVE_Y.getDegreesQuaternion(yaw)); matrices.multiply(Vec3f.POSITIVE_Z.getDegreesQuaternion(roll)); matrices.multiply(Vec3f.POSITIVE_Y.getDegreesQuaternion(yaw)); @@ -91,7 +96,7 @@ public class WorldRenderDelegate { if (pony instanceof Caster) { - int fireTicks = pony.getMaster().doesRenderOnFire() ? 1 : 0; + int fireTicks = owner.doesRenderOnFire() ? 1 : 0; return ((Caster)pony).getSpellSlot().get(SpellPredicate.IS_DISGUISE, true).map(effect -> { effect.update(pony, false); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/Creature.java b/src/main/java/com/minelittlepony/unicopia/entity/Creature.java index 0567350f..ce0fc27b 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/Creature.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/Creature.java @@ -1,17 +1,25 @@ package com.minelittlepony.unicopia.entity; +import java.util.Optional; +import java.util.function.Predicate; + +import org.jetbrains.annotations.Nullable; + import com.minelittlepony.unicopia.Affinity; import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.ability.magic.Affine; import com.minelittlepony.unicopia.ability.magic.Levelled; import com.minelittlepony.unicopia.ability.magic.spell.Spell; +import com.minelittlepony.unicopia.ability.magic.spell.effect.TargetSelecter; import com.minelittlepony.unicopia.entity.ai.BreakHeartGoal; import com.minelittlepony.unicopia.entity.ai.DynamicTargetGoal; import com.minelittlepony.unicopia.entity.ai.WantItTakeItGoal; import com.minelittlepony.unicopia.entity.player.PlayerAttributes; +import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.SpawnGroup; +import net.minecraft.entity.ai.goal.ActiveTargetGoal; import net.minecraft.entity.ai.goal.GoalSelector; import net.minecraft.entity.attribute.DefaultAttributeContainer; import net.minecraft.entity.attribute.EntityAttributes; @@ -19,11 +27,14 @@ import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.data.DataTracker; import net.minecraft.entity.data.TrackedData; import net.minecraft.entity.data.TrackedDataHandlerRegistry; +import net.minecraft.entity.mob.HostileEntity; import net.minecraft.entity.mob.MobEntity; +import net.minecraft.entity.mob.SlimeEntity; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; public class Creature extends Living { - private static final TrackedData EFFECT = DataTracker.registerData(LivingEntity.class, TrackedDataHandlerRegistry.TAG_COMPOUND); public static final TrackedData GRAVITY = DataTracker.registerData(LivingEntity.class, TrackedDataHandlerRegistry.FLOAT); @@ -33,18 +44,72 @@ public class Creature extends Living { private final EntityPhysics physics; + private final EntityReference master = new EntityReference<>(); + + @Nullable + private GoalSelector goals; + @Nullable + private GoalSelector targets; + public Creature(LivingEntity entity) { super(entity, EFFECT); physics = new EntityPhysics<>(entity, GRAVITY); } + @Override + public void setMaster(LivingEntity owner) { + master.set(owner); + if (targets != null && owner != null) { + initMinionAi(); + } + } + + @Override + public LivingEntity getMaster() { + return master.getOrEmpty(getWorld()).orElse(entity); + } + + @Override + public Entity getEntity() { + return entity; + } + + public Optional getTargets() { + return Optional.ofNullable(targets); + } + + public Optional getGoals() { + return Optional.ofNullable(goals); + } + public void initAi(GoalSelector goals, GoalSelector targets) { + this.goals = goals; + this.targets = targets; + DynamicTargetGoal targetter = new DynamicTargetGoal((MobEntity)entity); targets.add(1, targetter); goals.add(1, new WantItTakeItGoal((MobEntity)entity, targetter)); if (entity.getType().getSpawnGroup() == SpawnGroup.MONSTER) { goals.add(3, new BreakHeartGoal((MobEntity)entity, targetter)); } + + if (master.isPresent(getWorld())) { + initMinionAi(); + } + } + + private void initMinionAi() { + Predicate filter = TargetSelecter.notOwnerOrFriend(this, this).and(e -> { + return Equine.of(e) + .filter(eq -> eq instanceof Creature) + .filter(eq -> ((Creature)eq).getMaster() == getMaster()) + .isEmpty(); + }); + + targets.clear(); + targets.add(2, new ActiveTargetGoal<>((MobEntity)entity, PlayerEntity.class, true, filter)); + targets.add(2, new ActiveTargetGoal<>((MobEntity)entity, HostileEntity.class, true, filter)); + targets.add(2, new ActiveTargetGoal<>((MobEntity)entity, SlimeEntity.class, true, filter)); } public static void registerAttributes(DefaultAttributeContainer.Builder builder) { @@ -53,7 +118,6 @@ public class Creature extends Living { builder.add(PlayerAttributes.ENTITY_GRAVTY_MODIFIER); } - @Override public void tick() { super.tick(); @@ -99,6 +163,7 @@ public class Creature extends Living { getSpellSlot().get(true).ifPresent(effect -> { compound.put("effect", Spell.writeNbt(effect)); }); + compound.put("master", master.toNBT()); physics.toNBT(compound); } @@ -108,6 +173,12 @@ public class Creature extends Living { if (compound.contains("effect")) { getSpellSlot().put(Spell.readNbt(compound.getCompound("effect"))); } + if (compound.contains("master", NbtElement.COMPOUND_TYPE)) { + master.fromNBT(compound.getCompound("master")); + if (master.isPresent(getWorld()) && targets != null) { + initMinionAi(); + } + } physics.fromNBT(compound); } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/EntityReference.java b/src/main/java/com/minelittlepony/unicopia/entity/EntityReference.java index 0648ac06..d0bbf640 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/EntityReference.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/EntityReference.java @@ -22,6 +22,16 @@ public class EntityReference implements NbtSerialisable { private Optional pos = Optional.empty(); + public EntityReference() {} + + public EntityReference(T entity) { + set(entity); + } + + public EntityReference(NbtCompound nbt) { + fromNBT(nbt); + } + public void set(@Nullable T entity) { if (entity != null) { uuid = entity.getUuid();