Make the necromancy spell better

This commit is contained in:
Sollace 2021-12-30 00:59:17 +02:00
parent fb3a642acd
commit d03c0586cf
6 changed files with 141 additions and 34 deletions

View file

@ -95,9 +95,7 @@ public class LightSpell extends AbstractSpell {
lights.clear(); lights.clear();
if (compound.contains("lights", NbtElement.LIST_TYPE)) { if (compound.contains("lights", NbtElement.LIST_TYPE)) {
compound.getList("lights", NbtElement.COMPOUND_TYPE).forEach(nbt -> { compound.getList("lights", NbtElement.COMPOUND_TYPE).forEach(nbt -> {
EntityReference<FairyEntity> light = new EntityReference<>(); lights.add(new EntityReference<>((NbtCompound)nbt));
light.fromNBT((NbtCompound)nbt);
lights.add(light);
}); });
} }
} }

View file

@ -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.Situation;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.entity.Creature;
import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.EntityReference;
import com.minelittlepony.unicopia.entity.Equine;
import com.minelittlepony.unicopia.util.Weighted; import com.minelittlepony.unicopia.util.Weighted;
import com.minelittlepony.unicopia.util.shape.Shape; import com.minelittlepony.unicopia.util.shape.Shape;
import com.minelittlepony.unicopia.util.shape.Sphere; import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.entity.EntityType; import net.minecraft.entity.EntityType;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.LivingEntity; 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.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.nbt.NbtList; import net.minecraft.nbt.NbtList;
import net.minecraft.particle.ParticleTypes; import net.minecraft.particle.ParticleTypes;
import net.minecraft.sound.SoundEvents;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.world.Difficulty; import net.minecraft.world.Difficulty;
@ -28,16 +33,17 @@ import net.minecraft.world.WorldEvents;
* An area-effect spell that summons the undead. * An area-effect spell that summons the undead.
*/ */
public class NecromancySpell extends AbstractAreaEffectSpell { public class NecromancySpell extends AbstractAreaEffectSpell {
private final Weighted<EntityType<? extends LivingEntity>> spawnPool = new Weighted<EntityType<? extends LivingEntity>>() private final Weighted<EntityType<? extends LivingEntity>> spawnPool = new Weighted<EntityType<? extends LivingEntity>>()
.put(7, EntityType.ZOMBIE) .put(7, EntityType.ZOMBIE)
.put(4, EntityType.HUSK) .put(4, EntityType.HUSK)
.put(3, EntityType.DROWNED)
.put(2, EntityType.ZOMBIFIED_PIGLIN) .put(2, EntityType.ZOMBIFIED_PIGLIN)
.put(1, EntityType.ZOMBIE_VILLAGER) .put(1, EntityType.ZOMBIE_VILLAGER);
.put(1, EntityType.ZOMBIE_HORSE);
private final List<EntityReference<LivingEntity>> summonedEntities = new ArrayList<>(); private final List<EntityReference<LivingEntity>> summonedEntities = new ArrayList<>();
private int spawnCountdown;
protected NecromancySpell(SpellType<?> type, SpellTraits traits) { protected NecromancySpell(SpellType<?> type, SpellTraits traits) {
super(type, traits); super(type, traits);
} }
@ -50,10 +56,14 @@ public class NecromancySpell extends AbstractAreaEffectSpell {
return false; return false;
} }
boolean rainy = source.getWorld().hasRain(source.getOrigin());
if (source.isClient()) { if (source.isClient()) {
source.spawnParticles(new Sphere(false, radius), 5, pos -> { source.spawnParticles(new Sphere(true, radius * 2), rainy ? 98 : 125, pos -> {
if (!source.getWorld().isAir(new BlockPos(pos).down())) { BlockPos bpos = new BlockPos(pos);
source.addParticle(ParticleTypes.FLAME, pos, Vec3d.ZERO);
if (!source.getWorld().isAir(bpos.down())) {
source.addParticle(source.getWorld().hasRain(bpos) ? ParticleTypes.SMOKE : ParticleTypes.FLAME, pos, Vec3d.ZERO);
} }
}); });
return true; return true;
@ -63,19 +73,27 @@ public class NecromancySpell extends AbstractAreaEffectSpell {
return true; 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); 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; return true;
} }
if (source.findAllEntitiesInRange(radius, e -> e instanceof ZombieEntity).count() >= 10 * (1 + additional)) { Shape affectRegion = new Sphere(false, radius);
return false;
}
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
Vec3d pos = affectRegion.computePoint(source.getWorld().random).add(source.getOriginVector()); 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<? extends LivingEntity> type) { protected void spawnMonster(Caster<?> source, Vec3d pos, EntityType<? extends LivingEntity> type) {
LivingEntity zombie = type.create(source.getWorld()); LivingEntity minion = type.create(source.getWorld());
source.subtractEnergyCost(3); source.subtractEnergyCost(3);
zombie.updatePositionAndAngles(pos.x, pos.y, pos.z, 0, 0); minion.updatePositionAndAngles(pos.x, pos.y, pos.z, 0, 0);
zombie.setVelocity(0, 0.3, 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<LivingEntity> ref = new EntityReference<>(); source.getWorld().spawnEntity(minion);
ref.set(zombie); summonedEntities.add(new EntityReference<>(minion));
summonedEntities.add(ref);
setDirty(); setDirty();
} }
@Override @Override
public void toNBT(NbtCompound compound) { public void toNBT(NbtCompound compound) {
super.toNBT(compound); super.toNBT(compound);
spawnCountdown = compound.getInt("spawnCountdown");
if (summonedEntities.size() > 0) { if (summonedEntities.size() > 0) {
NbtList list = new NbtList(); NbtList list = new NbtList();
summonedEntities.forEach(ref -> list.add(ref.toNBT())); summonedEntities.forEach(ref -> list.add(ref.toNBT()));
@ -137,12 +160,11 @@ public class NecromancySpell extends AbstractAreaEffectSpell {
@Override @Override
public void fromNBT(NbtCompound compound) { public void fromNBT(NbtCompound compound) {
super.fromNBT(compound); super.fromNBT(compound);
compound.putInt("spawnCountdown", spawnCountdown);
summonedEntities.clear(); summonedEntities.clear();
if (compound.contains("summonedEntities")) { if (compound.contains("summonedEntities")) {
compound.getList("summonedEntities", 10).forEach(tag -> { compound.getList("summonedEntities", NbtElement.COMPOUND_TYPE).forEach(tag -> {
EntityReference<LivingEntity> ref = new EntityReference<>(); summonedEntities.add(new EntityReference<>((NbtCompound)tag));
ref.fromNBT((NbtCompound)tag);
summonedEntities.add(ref);
}); });
} }
} }

View file

@ -8,6 +8,7 @@ import java.util.function.Predicate;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.minelittlepony.unicopia.EquinePredicates; 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.Caster;
import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.SpellPredicate;
import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.Spell;
@ -55,7 +56,7 @@ public class TargetSelecter {
return targets.values().stream().filter(Target::canHurt).count(); return targets.values().stream().filter(Target::canHurt).count();
} }
public static Predicate<Entity> notOwnerOrFriend(Spell spell, Caster<?> source) { public static <T extends Entity> Predicate<T> notOwnerOrFriend(Affine spell, Caster<?> source) {
Entity owner = source.getMaster(); Entity owner = source.getMaster();
boolean ownerIsValid = spell.isFriendlyTogether(source) && (EquinePredicates.PLAYER_UNICORN.test(owner)); boolean ownerIsValid = spell.isFriendlyTogether(source) && (EquinePredicates.PLAYER_UNICORN.test(owner));

View file

@ -10,6 +10,7 @@ import com.minelittlepony.unicopia.entity.Living;
import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance; import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance;
import com.minelittlepony.unicopia.entity.behaviour.FallingBlockBehaviour; import com.minelittlepony.unicopia.entity.behaviour.FallingBlockBehaviour;
import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.player.Pony;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.BlockEntity;
@ -62,7 +63,12 @@ public class WorldRenderDelegate {
matrices.push(); 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(); boolean negative = pony.getPhysics().isGravityNegative();
@ -77,7 +83,6 @@ public class WorldRenderDelegate {
if (pony instanceof Pony) { if (pony instanceof Pony) {
roll = ((Pony)pony).getCamera().calculateRoll(); roll = ((Pony)pony).getCamera().calculateRoll();
matrices.multiply(Vec3f.NEGATIVE_Y.getDegreesQuaternion(yaw)); matrices.multiply(Vec3f.NEGATIVE_Y.getDegreesQuaternion(yaw));
matrices.multiply(Vec3f.POSITIVE_Z.getDegreesQuaternion(roll)); matrices.multiply(Vec3f.POSITIVE_Z.getDegreesQuaternion(roll));
matrices.multiply(Vec3f.POSITIVE_Y.getDegreesQuaternion(yaw)); matrices.multiply(Vec3f.POSITIVE_Y.getDegreesQuaternion(yaw));
@ -91,7 +96,7 @@ public class WorldRenderDelegate {
if (pony instanceof Caster<?>) { 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 -> { return ((Caster<?>)pony).getSpellSlot().get(SpellPredicate.IS_DISGUISE, true).map(effect -> {
effect.update(pony, false); effect.update(pony, false);

View file

@ -1,17 +1,25 @@
package com.minelittlepony.unicopia.entity; 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.Affinity;
import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.ability.magic.Affine; import com.minelittlepony.unicopia.ability.magic.Affine;
import com.minelittlepony.unicopia.ability.magic.Levelled; import com.minelittlepony.unicopia.ability.magic.Levelled;
import com.minelittlepony.unicopia.ability.magic.spell.Spell; 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.BreakHeartGoal;
import com.minelittlepony.unicopia.entity.ai.DynamicTargetGoal; import com.minelittlepony.unicopia.entity.ai.DynamicTargetGoal;
import com.minelittlepony.unicopia.entity.ai.WantItTakeItGoal; import com.minelittlepony.unicopia.entity.ai.WantItTakeItGoal;
import com.minelittlepony.unicopia.entity.player.PlayerAttributes; import com.minelittlepony.unicopia.entity.player.PlayerAttributes;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity; import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.SpawnGroup; import net.minecraft.entity.SpawnGroup;
import net.minecraft.entity.ai.goal.ActiveTargetGoal;
import net.minecraft.entity.ai.goal.GoalSelector; import net.minecraft.entity.ai.goal.GoalSelector;
import net.minecraft.entity.attribute.DefaultAttributeContainer; import net.minecraft.entity.attribute.DefaultAttributeContainer;
import net.minecraft.entity.attribute.EntityAttributes; 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.DataTracker;
import net.minecraft.entity.data.TrackedData; import net.minecraft.entity.data.TrackedData;
import net.minecraft.entity.data.TrackedDataHandlerRegistry; import net.minecraft.entity.data.TrackedDataHandlerRegistry;
import net.minecraft.entity.mob.HostileEntity;
import net.minecraft.entity.mob.MobEntity; 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.NbtCompound;
import net.minecraft.nbt.NbtElement;
public class Creature extends Living<LivingEntity> { public class Creature extends Living<LivingEntity> {
private static final TrackedData<NbtCompound> EFFECT = DataTracker.registerData(LivingEntity.class, TrackedDataHandlerRegistry.TAG_COMPOUND); private static final TrackedData<NbtCompound> EFFECT = DataTracker.registerData(LivingEntity.class, TrackedDataHandlerRegistry.TAG_COMPOUND);
public static final TrackedData<Float> GRAVITY = DataTracker.registerData(LivingEntity.class, TrackedDataHandlerRegistry.FLOAT); public static final TrackedData<Float> GRAVITY = DataTracker.registerData(LivingEntity.class, TrackedDataHandlerRegistry.FLOAT);
@ -33,18 +44,72 @@ public class Creature extends Living<LivingEntity> {
private final EntityPhysics<LivingEntity> physics; private final EntityPhysics<LivingEntity> physics;
private final EntityReference<LivingEntity> master = new EntityReference<>();
@Nullable
private GoalSelector goals;
@Nullable
private GoalSelector targets;
public Creature(LivingEntity entity) { public Creature(LivingEntity entity) {
super(entity, EFFECT); super(entity, EFFECT);
physics = new EntityPhysics<>(entity, GRAVITY); 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<GoalSelector> getTargets() {
return Optional.ofNullable(targets);
}
public Optional<GoalSelector> getGoals() {
return Optional.ofNullable(goals);
}
public void initAi(GoalSelector goals, GoalSelector targets) { public void initAi(GoalSelector goals, GoalSelector targets) {
this.goals = goals;
this.targets = targets;
DynamicTargetGoal targetter = new DynamicTargetGoal((MobEntity)entity); DynamicTargetGoal targetter = new DynamicTargetGoal((MobEntity)entity);
targets.add(1, targetter); targets.add(1, targetter);
goals.add(1, new WantItTakeItGoal((MobEntity)entity, targetter)); goals.add(1, new WantItTakeItGoal((MobEntity)entity, targetter));
if (entity.getType().getSpawnGroup() == SpawnGroup.MONSTER) { if (entity.getType().getSpawnGroup() == SpawnGroup.MONSTER) {
goals.add(3, new BreakHeartGoal((MobEntity)entity, targetter)); goals.add(3, new BreakHeartGoal((MobEntity)entity, targetter));
} }
if (master.isPresent(getWorld())) {
initMinionAi();
}
}
private void initMinionAi() {
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())
.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) { public static void registerAttributes(DefaultAttributeContainer.Builder builder) {
@ -53,7 +118,6 @@ public class Creature extends Living<LivingEntity> {
builder.add(PlayerAttributes.ENTITY_GRAVTY_MODIFIER); builder.add(PlayerAttributes.ENTITY_GRAVTY_MODIFIER);
} }
@Override @Override
public void tick() { public void tick() {
super.tick(); super.tick();
@ -99,6 +163,7 @@ public class Creature extends Living<LivingEntity> {
getSpellSlot().get(true).ifPresent(effect -> { getSpellSlot().get(true).ifPresent(effect -> {
compound.put("effect", Spell.writeNbt(effect)); compound.put("effect", Spell.writeNbt(effect));
}); });
compound.put("master", master.toNBT());
physics.toNBT(compound); physics.toNBT(compound);
} }
@ -108,6 +173,12 @@ public class Creature extends Living<LivingEntity> {
if (compound.contains("effect")) { if (compound.contains("effect")) {
getSpellSlot().put(Spell.readNbt(compound.getCompound("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); physics.fromNBT(compound);
} }
} }

View file

@ -22,6 +22,16 @@ public class EntityReference<T extends Entity> implements NbtSerialisable {
private Optional<Vec3d> pos = Optional.empty(); private Optional<Vec3d> pos = Optional.empty();
public EntityReference() {}
public EntityReference(T entity) {
set(entity);
}
public EntityReference(NbtCompound nbt) {
fromNBT(nbt);
}
public void set(@Nullable T entity) { public void set(@Nullable T entity) {
if (entity != null) { if (entity != null) {
uuid = entity.getUuid(); uuid = entity.getUuid();