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();
if (compound.contains("lights", NbtElement.LIST_TYPE)) {
compound.getList("lights", NbtElement.COMPOUND_TYPE).forEach(nbt -> {
EntityReference<FairyEntity> light = new EntityReference<>();
light.fromNBT((NbtCompound)nbt);
lights.add(light);
lights.add(new EntityReference<>((NbtCompound)nbt));
});
}
}

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.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<EntityType<? extends LivingEntity>> spawnPool = new Weighted<EntityType<? extends LivingEntity>>()
.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<EntityReference<LivingEntity>> 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<? extends LivingEntity> 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<LivingEntity> 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<LivingEntity> ref = new EntityReference<>();
ref.fromNBT((NbtCompound)tag);
summonedEntities.add(ref);
compound.getList("summonedEntities", NbtElement.COMPOUND_TYPE).forEach(tag -> {
summonedEntities.add(new EntityReference<>((NbtCompound)tag));
});
}
}

View file

@ -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<Entity> notOwnerOrFriend(Spell spell, Caster<?> source) {
public static <T extends Entity> Predicate<T> notOwnerOrFriend(Affine spell, Caster<?> source) {
Entity owner = source.getMaster();
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.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);

View file

@ -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<LivingEntity> {
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);
@ -33,18 +44,72 @@ public class Creature extends Living<LivingEntity> {
private final EntityPhysics<LivingEntity> physics;
private final EntityReference<LivingEntity> 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<GoalSelector> getTargets() {
return Optional.ofNullable(targets);
}
public Optional<GoalSelector> 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<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) {
@ -53,7 +118,6 @@ public class Creature extends Living<LivingEntity> {
builder.add(PlayerAttributes.ENTITY_GRAVTY_MODIFIER);
}
@Override
public void tick() {
super.tick();
@ -99,6 +163,7 @@ public class Creature extends Living<LivingEntity> {
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<LivingEntity> {
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);
}
}

View file

@ -22,6 +22,16 @@ public class EntityReference<T extends Entity> implements NbtSerialisable {
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) {
if (entity != null) {
uuid = entity.getUuid();