Added a proper summoning method to spawn sombra

This commit is contained in:
Sollace 2023-08-26 21:55:08 +01:00
parent 0320976d47
commit e08820b921
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
8 changed files with 303 additions and 12 deletions

View file

@ -77,7 +77,7 @@ public interface TreeType {
* Recursively locates the base of the tree.
*/
default Optional<BlockPos> findBase(World w, BlockPos pos) {
return findBase(new HashSet<BlockPos>(), 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<BlockPos> findBase(Set<BlockPos> 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);
}

View file

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

View file

@ -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> altar = Optional.empty();
public SpellbookEntity(EntityType<SpellbookEntity> 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);
}
}

View file

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

View file

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

View file

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

View file

@ -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<BlockPos> pillars) {
private static final Direction[] HORIZONTALS = { Direction.SOUTH, Direction.WEST, Direction.NORTH, Direction.EAST };
private static final Predicate<Entity> IS_PARTICIPANT = EntityPredicates.VALID_ENTITY.and(e -> e instanceof FloatingArtefactEntity || e instanceof SpellbookEntity);
public static final NbtSerialisable.Serializer<Altar> 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<BlockPos> PILLAR_OFFSETS = Arrays.stream(BlockRotation.values()).flatMap(rotation -> {
return Stream.of(PILLAR_A.rotate(rotation), PILLAR_B.rotate(rotation));
}).toList();
public static Optional<Altar> 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<BlockPos> 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<BlockPos> 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<BlockPos> 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)));
}
}

View file

@ -37,14 +37,18 @@ public interface PosHelper {
}
static void all(BlockPos origin, Consumer<BlockPos> 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<BlockPos> 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;
}
}