From e08820b921d1911068713667f93a8e9c0646d423 Mon Sep 17 00:00:00 2001 From: Sollace Date: Sat, 26 Aug 2023 21:55:08 +0100 Subject: [PATCH] Added a proper summoning method to spawn sombra --- .../unicopia/ability/data/tree/TreeType.java | 4 +- .../entity/FloatingArtefactEntity.java | 23 ++- .../unicopia/entity/SpellbookEntity.java | 62 ++++++ .../unicopia/item/AlicornAmuletItem.java | 25 ++- .../unicopia/item/SpellbookItem.java | 6 + .../unicopia/particle/ParticleUtils.java | 4 +- .../unicopia/server/world/Altar.java | 183 ++++++++++++++++++ .../unicopia/util/PosHelper.java | 8 +- 8 files changed, 303 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/server/world/Altar.java diff --git a/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeType.java b/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeType.java index bef0d6ce..2f461d2b 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeType.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/data/tree/TreeType.java @@ -77,7 +77,7 @@ public interface TreeType { * Recursively locates the base of the tree. */ default Optional findBase(World w, BlockPos pos) { - return findBase(new HashSet(), w, new BlockPos.Mutable(pos.getX(), pos.getY(), pos.getZ())); + return findBase(new HashSet<>(), w, new BlockPos.Mutable(pos.getX(), pos.getY(), pos.getZ())); } private Optional findBase(Set done, World w, BlockPos.Mutable pos) { @@ -91,7 +91,7 @@ public interface TreeType { } if (isWide()) { - PosHelper.all(pos.toImmutable(), p -> findBase(done, w, new BlockPos.Mutable(p.getX(), p.getY(), p.getZ())) + PosHelper.all(pos.toImmutable(), p -> findBase(done, w, p.mutableCopy()) .filter(a -> a.getY() < pos.getY()) .ifPresent(pos::set), PosHelper.HORIZONTAL); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/FloatingArtefactEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/FloatingArtefactEntity.java index 771b5aba..9685a524 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/FloatingArtefactEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/FloatingArtefactEntity.java @@ -1,8 +1,11 @@ package com.minelittlepony.unicopia.entity; +import java.util.Optional; + import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.entity.damage.UDamageSources; import com.minelittlepony.unicopia.item.UItems; +import com.minelittlepony.unicopia.server.world.Altar; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; @@ -36,6 +39,8 @@ public class FloatingArtefactEntity extends Entity implements UDamageSources { private float spinChange; private float spinChangeProgress; + private Optional altar = Optional.empty(); + public FloatingArtefactEntity(EntityType entityType, World world) { super(entityType, world); @@ -49,6 +54,10 @@ public class FloatingArtefactEntity extends Entity implements UDamageSources { dataTracker.startTracking(SPIN, 1F); } + public void setAltar(Altar altar) { + this.altar = Optional.of(altar); + } + public ItemStack getStack() { return dataTracker.get(ITEM); } @@ -144,6 +153,7 @@ public class FloatingArtefactEntity extends Entity implements UDamageSources { setStack(ItemStack.fromNbt(compound.getCompound("Item"))); setState(State.valueOf(compound.getInt("State"))); setSpin(compound.getFloat("spin")); + altar = Altar.SERIALIZER.readOptional("altar", compound); } @Override @@ -154,6 +164,7 @@ public class FloatingArtefactEntity extends Entity implements UDamageSources { } compound.putInt("State", getState().ordinal()); compound.putFloat("spin", getSpin()); + Altar.SERIALIZER.writeOptional("altar", compound, altar); } @Override @@ -170,14 +181,22 @@ public class FloatingArtefactEntity extends Entity implements UDamageSources { ItemStack stack = getStack(); - if (!(stack.getItem() instanceof Artifact) || ((Artifact)stack.getItem()).onArtifactDestroyed(this) != ActionResult.SUCCESS) { - dropStack(stack); + if (altar.isEmpty()) { + if (!(stack.getItem() instanceof Artifact) || ((Artifact)stack.getItem()).onArtifactDestroyed(this) != ActionResult.SUCCESS) { + dropStack(stack); + } } } return false; } + @Override + public void remove(RemovalReason reason) { + super.remove(reason); + altar.ifPresent(altar -> altar.tearDown(this, getWorld())); + } + @Override public boolean canHit() { return true; diff --git a/src/main/java/com/minelittlepony/unicopia/entity/SpellbookEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/SpellbookEntity.java index aae8b9fe..47087135 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/SpellbookEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/SpellbookEntity.java @@ -1,5 +1,7 @@ package com.minelittlepony.unicopia.entity; +import java.util.Optional; + import com.minelittlepony.unicopia.EquinePredicates; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.UTags; @@ -9,9 +11,11 @@ import com.minelittlepony.unicopia.entity.player.MeteorlogicalUtil; import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.network.Channel; import com.minelittlepony.unicopia.network.MsgSpellbookStateChanged; +import com.minelittlepony.unicopia.server.world.Altar; import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory; import net.fabricmc.fabric.api.util.TriState; +import net.minecraft.block.Blocks; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.damage.DamageSource; @@ -27,6 +31,7 @@ import net.minecraft.network.PacketByteBuf; import net.minecraft.particle.ParticleTypes; import net.minecraft.screen.*; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; import net.minecraft.sound.BlockSoundGroup; import net.minecraft.sound.SoundCategory; import net.minecraft.text.Text; @@ -47,6 +52,8 @@ public class SpellbookEntity extends MobEntity { private final SpellbookState state = new SpellbookState(); + private Optional altar = Optional.empty(); + public SpellbookEntity(EntityType type, World world) { super(type, world); setPersistent(); @@ -92,6 +99,10 @@ public class SpellbookEntity extends MobEntity { return false; } + public void setAltar(Altar altar) { + this.altar = Optional.of(altar); + } + public boolean isAltered() { return dataTracker.get(ALTERED); } @@ -177,6 +188,49 @@ public class SpellbookEntity extends MobEntity { clearForcedState(); } } + + if (!getWorld().isClient && age % 15 == 0) { + altar.ifPresent(altar -> { + + if (!altar.isValid(getWorld())) { + altar.tearDown(null, getWorld()); + return; + } + + altar.pillars().forEach(pillar -> { + Vec3d center = pillar.toCenterPos().add( + random.nextTriangular(0.5, 0.2), + random.nextTriangular(0.5, 0.2), + random.nextTriangular(0.5, 0.2) + ); + + ((ServerWorld)getWorld()).spawnParticles( + ParticleTypes.SOUL_FIRE_FLAME, + center.x - 0.5, center.y + 0.5, center.z - 0.5, + 0, + 0.5, 0.5, 0.5, 0); + + if (random.nextInt(12) != 0) { + return; + } + + Vec3d vel = center.subtract(this.altar.get().origin().toCenterPos()).normalize(); + + ((ServerWorld)getWorld()).spawnParticles( + ParticleTypes.SOUL_FIRE_FLAME, + center.x - 0.5, center.y + 0.5, center.z - 0.5, + 0, + vel.x, vel.y, vel.z, -0.2); + + if (random.nextInt(2000) == 0) { + if (getWorld().getBlockState(pillar).isOf(Blocks.CRYING_OBSIDIAN)) { + pillar = pillar.down(); + } + getWorld().setBlockState(pillar, Blocks.CRYING_OBSIDIAN.getDefaultState()); + } + }); + }); + } } private boolean shouldBeSleeping() { @@ -199,6 +253,12 @@ public class SpellbookEntity extends MobEntity { return false; } + @Override + public void remove(RemovalReason reason) { + super.remove(reason); + altar.ifPresent(altar -> altar.tearDown(this, getWorld())); + } + @Override public ActionResult interactAt(PlayerEntity player, Vec3d vec, Hand hand) { if (player.isSneaking()) { @@ -251,6 +311,7 @@ public class SpellbookEntity extends MobEntity { setAltered(compound.getBoolean("altered")); setForcedState(compound.contains("locked") ? TriState.of(compound.getBoolean("locked")) : TriState.DEFAULT); state.fromNBT(compound.getCompound("spellbookState")); + altar = Altar.SERIALIZER.readOptional("altar", compound); } @Override @@ -264,5 +325,6 @@ public class SpellbookEntity extends MobEntity { compound.putBoolean("locked", t); return null; }); + Altar.SERIALIZER.writeOptional("altar", compound, altar); } } diff --git a/src/main/java/com/minelittlepony/unicopia/item/AlicornAmuletItem.java b/src/main/java/com/minelittlepony/unicopia/item/AlicornAmuletItem.java index 61c8dc68..08e32c48 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/AlicornAmuletItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/AlicornAmuletItem.java @@ -14,6 +14,7 @@ import com.minelittlepony.unicopia.entity.player.*; import com.minelittlepony.unicopia.particle.FollowingParticleEffect; import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.particle.UParticles; +import com.minelittlepony.unicopia.server.world.Altar; import com.minelittlepony.unicopia.trinkets.TrinketsDelegate; import com.minelittlepony.unicopia.util.VecHelper; @@ -21,6 +22,7 @@ import it.unimi.dsi.fastutil.floats.Float2ObjectFunction; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.item.v1.FabricItemSettings; +import net.minecraft.block.Blocks; import net.minecraft.client.MinecraftClient; import net.minecraft.client.item.TooltipContext; import net.minecraft.enchantment.EnchantmentHelper; @@ -46,6 +48,7 @@ import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.random.Random; import net.minecraft.world.LocalDifficulty; import net.minecraft.world.World; +import net.minecraft.world.World.ExplosionSourceType; public class AlicornAmuletItem extends AmuletItem implements ItemTracker.Trackable, ItemImpl.ClingyItem, ItemImpl.GroundTickCallback { private static final UUID EFFECT_UUID = UUID.fromString("c0a870f5-99ef-4716-a23e-f320ee834b26"); @@ -186,6 +189,23 @@ public class AlicornAmuletItem extends AmuletItem implements ItemTracker.Trackab return; } + if (entity instanceof PlayerEntity) { + if (entity.isOnFire() && world.getBlockState(entity.getBlockPos().up()).isOf(Blocks.SOUL_FIRE)) { + if (Altar.of(entity.getBlockPos().up()).isValid(world)) { + if (living.asEntity().getHealth() < 2) { + entity.setFireTicks(0); + world.removeBlock(entity.getBlockPos().up(), false); + stack.decrement(1); + world.createExplosion(null, entity.getX(), entity.getY(), entity.getZ(), 0, ExplosionSourceType.NONE); + world.playSound(null, entity.getBlockPos(), USounds.ENTITY_SOMBRA_LAUGH, SoundCategory.AMBIENT, 10, 1); + + SombraEntity.startEncounter(world, entity.getBlockPos()); + } + return; + } + } + } + final long attachedTicks = living.getArmour().getTicks(this); final long daysAttached = attachedTicks / ItemTracker.DAYS; final boolean fullSecond = attachedTicks % ItemTracker.SECONDS == 0; @@ -257,11 +277,6 @@ public class AlicornAmuletItem extends AmuletItem implements ItemTracker.Trackab player.getHungerManager().addExhaustion(90F); float healthDrop = MathHelper.clamp(player.getMaxHealth() - player.getHealth(), 2, 5); player.damage(pony.damageOf(UDamageTypes.ALICORN_AMULET), healthDrop); - - if (player.getHealth() < 2) { - stack.decrement(1); - SombraEntity.startEncounter(player.getWorld(), player.getBlockPos()); - } } return; diff --git a/src/main/java/com/minelittlepony/unicopia/item/SpellbookItem.java b/src/main/java/com/minelittlepony/unicopia/item/SpellbookItem.java index 4a53fbe0..f2e73ca4 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/SpellbookItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/SpellbookItem.java @@ -4,6 +4,7 @@ import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.entity.SpellbookEntity; import com.minelittlepony.unicopia.entity.UEntities; +import com.minelittlepony.unicopia.server.world.Altar; import com.minelittlepony.unicopia.util.Dispensable; import net.minecraft.block.DispenserBlock; @@ -71,6 +72,11 @@ public class SpellbookItem extends BookItem implements Dispensable { } world.spawnEntity(book); + + Altar.locateAltar(world, book.getBlockPos()).ifPresent(altar -> { + book.setAltar(altar); + altar.generateDecorations(world); + }); } } diff --git a/src/main/java/com/minelittlepony/unicopia/particle/ParticleUtils.java b/src/main/java/com/minelittlepony/unicopia/particle/ParticleUtils.java index 27a7caff..08e8ab0d 100644 --- a/src/main/java/com/minelittlepony/unicopia/particle/ParticleUtils.java +++ b/src/main/java/com/minelittlepony/unicopia/particle/ParticleUtils.java @@ -37,7 +37,9 @@ public interface ParticleUtils { static void spawnParticle(World world, ParticleEffect effect, double x, double y, double z, double vX, double vY, double vZ) { if (world instanceof ServerWorld sw) { - sw.spawnParticles(effect, x, y, z, 1, vX, vY, vZ, 0); + Vec3d vel = new Vec3d(vX, vY, vZ); + + sw.spawnParticles(effect, x, y, z, 1, vX, vY, vZ, vel.length()); } else { world.addParticle(effect, x, y, z, vX, vY, vZ); } diff --git a/src/main/java/com/minelittlepony/unicopia/server/world/Altar.java b/src/main/java/com/minelittlepony/unicopia/server/world/Altar.java new file mode 100644 index 00000000..0b7f1147 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/server/world/Altar.java @@ -0,0 +1,183 @@ +package com.minelittlepony.unicopia.server.world; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.entity.FloatingArtefactEntity; +import com.minelittlepony.unicopia.entity.SpellbookEntity; +import com.minelittlepony.unicopia.entity.UEntities; +import com.minelittlepony.unicopia.item.UItems; +import com.minelittlepony.unicopia.util.NbtSerialisable; +import com.minelittlepony.unicopia.util.PosHelper; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.entity.Entity; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.predicate.entity.EntityPredicates; +import net.minecraft.registry.tag.BlockTags; +import net.minecraft.util.BlockRotation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.World; + +public record Altar(BlockPos origin, Set pillars) { + private static final Direction[] HORIZONTALS = { Direction.SOUTH, Direction.WEST, Direction.NORTH, Direction.EAST }; + private static final Predicate IS_PARTICIPANT = EntityPredicates.VALID_ENTITY.and(e -> e instanceof FloatingArtefactEntity || e instanceof SpellbookEntity); + public static final NbtSerialisable.Serializer SERIALIZER = NbtSerialisable.Serializer.of(nbt -> { + return new Altar( + NbtSerialisable.BLOCK_POS.read(nbt.getCompound("origin")), + new HashSet<>(NbtSerialisable.BLOCK_POS.readAll(nbt.getList("pillars", NbtElement.COMPOUND_TYPE)).toList()) + ); + }, altar -> { + NbtCompound compound = new NbtCompound(); + compound.put("origin", NbtSerialisable.BLOCK_POS.write(altar.origin)); + compound.put("pillars", NbtSerialisable.BLOCK_POS.writeAll(altar.pillars)); + return compound; + }); + + private static final int INNER_RADIUS = 4; + private static final int PILLAR_OFFSET_FROM_CENTER = 2; + + private static final BlockPos PILLAR_A = new BlockPos(INNER_RADIUS, 0, PILLAR_OFFSET_FROM_CENTER); + private static final BlockPos PILLAR_B = new BlockPos(INNER_RADIUS, 0, -PILLAR_OFFSET_FROM_CENTER); + + private static final List PILLAR_OFFSETS = Arrays.stream(BlockRotation.values()).flatMap(rotation -> { + return Stream.of(PILLAR_A.rotate(rotation), PILLAR_B.rotate(rotation)); + }).toList(); + + public static Optional locateAltar(World world, BlockPos startingPoint) { + + BlockPos.Mutable mutable = startingPoint.mutableCopy(); + + if (!world.isSkyVisible(mutable)) { + return Optional.empty(); + } + + mutable.move(Direction.DOWN); + if (!world.getBlockState(mutable).isOf(Blocks.LODESTONE)) { + return Optional.empty(); + } + + for (int i = 0; i < 4; i++) { + mutable.set(startingPoint); + mutable.move(Direction.DOWN); + mutable.move(Direction.fromHorizontal(i), 2); + + if (world.getBlockState(mutable).isOf(Blocks.SOUL_SAND)) { + if (checkSlab(world, mutable)) { + mutable.move(Direction.UP); + BlockState fireState = world.getBlockState(mutable); + if (!(fireState.isAir() || fireState.isIn(BlockTags.FIRE))) { + return Optional.empty(); + } + BlockPos firePos = mutable.toImmutable(); + mutable.move(Direction.DOWN); + + final Set pillars = new HashSet<>(); + + if (checkPillarPair(world, mutable, BlockRotation.NONE, pillars::add) + && checkPillarPair(world, mutable, BlockRotation.CLOCKWISE_90, pillars::add) + && checkPillarPair(world, mutable, BlockRotation.COUNTERCLOCKWISE_90, pillars::add) + && checkPillarPair(world, mutable, BlockRotation.CLOCKWISE_180, pillars::add)) { + + return Optional.of(new Altar(firePos, pillars)); + } + + } + } + } + + return Optional.empty(); + } + + public static Altar of(BlockPos center) { + return new Altar(center, new HashSet<>(PILLAR_OFFSETS.stream().map(p -> p.add(center).up()).toList())); + } + + private static boolean checkSlab(World world, BlockPos pos) { + return !PosHelper.any(pos, p -> !isObsidian(world, p), HORIZONTALS); + } + + private static boolean checkPillarPair(World world, BlockPos.Mutable center, BlockRotation rotation, Consumer pillarPosCollector) { + return checkPillar(world, center, PILLAR_A.rotate(rotation), pillarPosCollector) + && checkPillar(world, center, PILLAR_B.rotate(rotation), pillarPosCollector); + } + + private static boolean checkPillar(World world, BlockPos.Mutable pos, Vec3i pillarPos, Consumer pillarPosCollector) { + int x = pos.getX(); + int y = pos.getY(); + int z = pos.getZ(); + + if (isObsidian(world, pos.move(pillarPos)) + && isObsidian(world, pos.move(Direction.UP)) + && isObsidian(world, pos.move(Direction.UP))) { + pillarPosCollector.accept(pos.toImmutable()); + pos.set(x, y, z); + return true; + } + + return false; + } + + private static boolean isObsidian(World world, BlockPos pos) { + BlockState state = world.getBlockState(pos); + return state.isOf(Blocks.OBSIDIAN) || state.isOf(Blocks.CRYING_OBSIDIAN); + } + + private static boolean checkState(World world, BlockPos pos, Block block) { + return world.getBlockState(pos).isOf(block); + } + + public void generateDecorations(World world) { + world.setBlockState(origin, Blocks.SOUL_FIRE.getDefaultState(), Block.FORCE_STATE | Block.NOTIFY_ALL); + pillars.forEach(pillar -> { + /* + if (world.random.nextInt(3) == 0) { + world.setBlockState(pillar, Blocks.CRYING_OBSIDIAN.getDefaultState()); + } else if (world.random.nextInt(3) == 0) { + world.setBlockState(pillar.down(), Blocks.CRYING_OBSIDIAN.getDefaultState()); + } + */ + + FloatingArtefactEntity artefact = UEntities.FLOATING_ARTEFACT.create(world); + artefact.setStack(UItems.ALICORN_BADGE.getDefaultStack()); + artefact.setPosition(pillar.up().toCenterPos()); + artefact.setInvulnerable(true); + artefact.setAltar(this); + artefact.addSpin(2, 9000); + removeExisting(null, world, pillar); + world.spawnEntity(artefact); + }); + } + + public void tearDown(@Nullable Entity except, World world) { + if (!(except instanceof SpellbookEntity)) { + world.getOtherEntities(except, new Box(origin).expand(3), IS_PARTICIPANT).forEach(Entity::kill); + } + pillars.forEach(pillar -> removeExisting(except, world, pillar)); + } + + private void removeExisting(@Nullable Entity except, World world, BlockPos pillar) { + world.getOtherEntities(except, new Box(pillar.up()), IS_PARTICIPANT).forEach(Entity::kill); + } + + public boolean isValid(World world) { + return checkState(world, origin, Blocks.SOUL_FIRE) + && checkState(world, origin.down(), Blocks.SOUL_SAND) + && checkSlab(world, origin.down()) + && pillars.stream().allMatch(pillar -> isObsidian(world, pillar) && isObsidian(world, pillar.down()) && isObsidian(world, pillar.down(2))); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/util/PosHelper.java b/src/main/java/com/minelittlepony/unicopia/util/PosHelper.java index 603a2c2c..be63ea62 100644 --- a/src/main/java/com/minelittlepony/unicopia/util/PosHelper.java +++ b/src/main/java/com/minelittlepony/unicopia/util/PosHelper.java @@ -37,14 +37,18 @@ public interface PosHelper { } static void all(BlockPos origin, Consumer consumer, Direction... directions) { + BlockPos.Mutable mutable = origin.mutableCopy(); for (Direction facing : directions) { - consumer.accept(origin.offset(facing)); + mutable.set(origin); + consumer.accept(mutable.move(facing)); } } static boolean any(BlockPos origin, Predicate consumer, Direction... directions) { + BlockPos.Mutable mutable = origin.mutableCopy(); for (Direction facing : directions) { - if (consumer.test(origin.offset(facing))) { + mutable.set(origin); + if (consumer.test(mutable.move(facing))) { return true; } }