Range indication for bells

- Stationary Haunted Bells now show particles at the perimeter of the scanned area, regardless of light level
This commit is contained in:
simibubi 2021-07-07 19:38:32 +02:00
parent efa2bc745f
commit 10b0bdbfcf
6 changed files with 148 additions and 33 deletions

View file

@ -35,7 +35,9 @@ public enum AllParticleTypes {
BASIN_FLUID(FluidParticleData::new), BASIN_FLUID(FluidParticleData::new),
FLUID_DRIP(FluidParticleData::new), FLUID_DRIP(FluidParticleData::new),
SOUL(SoulParticle.Data::new), SOUL(SoulParticle.Data::new),
SOUL_BASE(SoulBaseParticle.Data::new) SOUL_BASE(SoulBaseParticle.Data::new),
SOUL_PERIMETER(SoulParticle.PerimeterData::new),
SOUL_EXPANDING_PERIMETER(SoulParticle.ExpandingPerimeterData::new)
; ;
private ParticleEntry<?> entry; private ParticleEntry<?> entry;

View file

@ -31,7 +31,7 @@ public class SoulBaseParticle extends CustomRotationParticle {
selectSpriteLoopingWithAge(animatedSprite); selectSpriteLoopingWithAge(animatedSprite);
BlockPos pos = new BlockPos(posX, posY, posZ); BlockPos pos = new BlockPos(posX, posY, posZ);
if (age++ >= maxAge || !SoulPulseEffect.canSpawnSoulAt(world, pos)) if (age++ >= maxAge || !SoulPulseEffect.canSpawnSoulAt(world, pos, false))
setExpired(); setExpired();
} }

View file

@ -1,13 +1,16 @@
package com.simibubi.create.content.curiosities.bell; package com.simibubi.create.content.curiosities.bell;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import com.simibubi.create.AllParticleTypes; import com.simibubi.create.AllParticleTypes;
import net.minecraft.client.particle.IAnimatedSprite; import net.minecraft.client.particle.IAnimatedSprite;
import net.minecraft.client.renderer.ActiveRenderInfo; import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.client.world.ClientWorld; import net.minecraft.client.world.ClientWorld;
import net.minecraft.particles.IParticleData;
import net.minecraft.particles.ParticleType; import net.minecraft.particles.ParticleType;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Quaternion; import net.minecraft.util.math.vector.Quaternion;
import net.minecraft.util.math.vector.Vector3f;
public class SoulParticle extends CustomRotationParticle { public class SoulParticle extends CustomRotationParticle {
@ -26,14 +29,18 @@ public class SoulParticle extends CustomRotationParticle {
protected int firstEndFrame = 33; protected int firstEndFrame = 33;
protected int endFrames = 20; protected int endFrames = 20;
protected int totalFrames = 53;
protected int ticksPerFrame = 2;
protected AnimationStage animationStage; protected AnimationStage animationStage;
protected int totalFrames = 53;
protected int ticksPerFrame = 2;
protected boolean isPerimeter = false;
protected boolean isExpandingPerimeter = false;
protected boolean isVisible = true;
protected int perimeterFrames = 8;
public SoulParticle(ClientWorld worldIn, double x, double y, double z, double vx, double vy, double vz, public SoulParticle(ClientWorld worldIn, double x, double y, double z, double vx, double vy, double vz,
IAnimatedSprite spriteSet) { IAnimatedSprite spriteSet, IParticleData data) {
super(worldIn, x, y, z, spriteSet, 0); super(worldIn, x, y, z, spriteSet, 0);
this.animatedSprite = spriteSet; this.animatedSprite = spriteSet;
this.particleScale = 0.5f; this.particleScale = 0.5f;
@ -48,18 +55,37 @@ public class SoulParticle extends CustomRotationParticle {
this.field_21507 = true; // disable movement this.field_21507 = true; // disable movement
this.mirror = this.rand.nextBoolean(); this.mirror = this.rand.nextBoolean();
this.animationStage = new StartAnimation(this); this.isPerimeter = data instanceof PerimeterData;
this.isExpandingPerimeter = data instanceof ExpandingPerimeterData;
this.animationStage = !isPerimeter ? new StartAnimation(this) : new PerimeterAnimation(this);
if (isPerimeter) {
prevPosY = posY -= .5f - 1 / 128f;
totalFrames = perimeterFrames;
isVisible = false;
}
} }
@Override @Override
public void tick() { public void tick() {
animationStage.tick(); animationStage.tick();
animationStage = animationStage.getNext(); animationStage = animationStage.getNext();
BlockPos pos = new BlockPos(posX, posY, posZ); BlockPos pos = new BlockPos(posX, posY, posZ);
if (animationStage == null || !SoulPulseEffect.canSpawnSoulAt(world, pos)) if (animationStage == null)
setExpired(); setExpired();
if (!SoulPulseEffect.canSpawnSoulAt(world, pos, false)) {
isVisible = true;
if (!isPerimeter)
setExpired();
} else if (isPerimeter)
isVisible = false;
}
@Override
public void buildGeometry(IVertexBuilder builder, ActiveRenderInfo camera, float partialTicks) {
if (!isVisible)
return;
super.buildGeometry(builder, camera, partialTicks);
} }
public void setFrame(int frame) { public void setFrame(int frame) {
@ -69,20 +95,44 @@ public class SoulParticle extends CustomRotationParticle {
@Override @Override
public Quaternion getCustomRotation(ActiveRenderInfo camera, float partialTicks) { public Quaternion getCustomRotation(ActiveRenderInfo camera, float partialTicks) {
if (isPerimeter)
return Vector3f.POSITIVE_X.getDegreesQuaternion(90);
return new Quaternion(0, -camera.getYaw(), 0, true); return new Quaternion(0, -camera.getYaw(), 0, true);
} }
public static class Data extends BasicParticleData<SoulParticle> { public static class Data extends BasicParticleData<SoulParticle> {
@Override @Override
public IBasicParticleFactory<SoulParticle> getBasicFactory() { public IBasicParticleFactory<SoulParticle> getBasicFactory() {
return SoulParticle::new; return (worldIn, x, y, z, vx, vy, vz, spriteSet) -> new SoulParticle(worldIn, x, y, z, vx, vy, vz,
spriteSet, this);
} }
@Override @Override
public ParticleType<?> getType() { public ParticleType<?> getType() {
return AllParticleTypes.SOUL.get(); return AllParticleTypes.SOUL.get();
} }
} }
public static class PerimeterData extends BasicParticleData<SoulParticle> {
@Override
public IBasicParticleFactory<SoulParticle> getBasicFactory() {
return (worldIn, x, y, z, vx, vy, vz, spriteSet) -> new SoulParticle(worldIn, x, y, z, vx, vy, vz,
spriteSet, this);
}
@Override
public ParticleType<?> getType() {
return AllParticleTypes.SOUL_PERIMETER.get();
}
}
public static class ExpandingPerimeterData extends PerimeterData {
@Override
public ParticleType<?> getType() {
return AllParticleTypes.SOUL_EXPANDING_PERIMETER.get();
}
}
public static abstract class AnimationStage { public static abstract class AnimationStage {
protected final SoulParticle particle; protected final SoulParticle particle;
@ -118,7 +168,8 @@ public class SoulParticle extends CustomRotationParticle {
public void tick() { public void tick() {
super.tick(); super.tick();
particle.setFrame(particle.firstStartFrame + (int) (getAnimAge() / (float) particle.startTicks * particle.startFrames)); particle.setFrame(
particle.firstStartFrame + (int) (getAnimAge() / (float) particle.startTicks * particle.startFrames));
} }
@Override @Override
@ -144,9 +195,11 @@ public class SoulParticle extends CustomRotationParticle {
int loopTick = getLoopTick(); int loopTick = getLoopTick();
if (loopTick == 0) loops++; if (loopTick == 0)
loops++;
particle.setFrame(particle.firstLoopFrame + loopTick);//(int) (((float) loopTick / (float) particle.loopLength) * particle.loopFrames)); particle.setFrame(particle.firstLoopFrame + loopTick);// (int) (((float) loopTick / (float)
// particle.loopLength) * particle.loopFrames));
} }
@ -173,7 +226,8 @@ public class SoulParticle extends CustomRotationParticle {
public void tick() { public void tick() {
super.tick(); super.tick();
particle.setFrame(particle.firstEndFrame + (int) ((getAnimAge() / (float) particle.endTicks) * particle.endFrames)); particle.setFrame(
particle.firstEndFrame + (int) ((getAnimAge() / (float) particle.endTicks) * particle.endFrames));
} }
@ -185,4 +239,26 @@ public class SoulParticle extends CustomRotationParticle {
return null; return null;
} }
} }
public static class PerimeterAnimation extends AnimationStage {
public PerimeterAnimation(SoulParticle particle) {
super(particle);
}
@Override
public void tick() {
super.tick();
particle.setFrame((int) getAnimAge() % particle.perimeterFrames);
}
@Override
public AnimationStage getNext() {
if (animAge < (particle.isExpandingPerimeter ? 8
: particle.startTicks + particle.endTicks + particle.numLoops * particle.loopLength))
return this;
else
return null;
}
}
} }

View file

@ -5,6 +5,9 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.simibubi.create.content.curiosities.bell.SoulParticle.ExpandingPerimeterData;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.entity.EntitySpawnPlacementRegistry; import net.minecraft.entity.EntitySpawnPlacementRegistry;
import net.minecraft.entity.EntityType; import net.minecraft.entity.EntityType;
import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.AxisAlignedBB;
@ -17,7 +20,7 @@ import net.minecraft.world.spawner.WorldEntitySpawner;
public class SoulPulseEffect { public class SoulPulseEffect {
public static final int MAX_DISTANCE = 10; public static final int MAX_DISTANCE = 11;
private static final List<List<BlockPos>> LAYERS = genLayers(); private static final List<List<BlockPos>> LAYERS = genLayers();
private static final int WAITING_TICKS = 100; private static final int WAITING_TICKS = 100;
@ -50,10 +53,10 @@ public class SoulPulseEffect {
if (ticks < 0 || ticks % TICKS_PER_LAYER != 0) if (ticks < 0 || ticks % TICKS_PER_LAYER != 0)
return null; return null;
List<BlockPos> spawns = getSoulSpawns(world); List<BlockPos> spawns = getPotentialSoulSpawns(world);
while (spawns.isEmpty() && ticks > 0) { while (spawns.isEmpty() && ticks > 0) {
ticks -= TICKS_PER_LAYER; ticks -= TICKS_PER_LAYER;
spawns.addAll(getSoulSpawns(world)); spawns.addAll(getPotentialSoulSpawns(world));
} }
return spawns; return spawns;
} }
@ -62,28 +65,30 @@ public class SoulPulseEffect {
return distance - ticks / TICKS_PER_LAYER - 1; return distance - ticks / TICKS_PER_LAYER - 1;
} }
public List<BlockPos> getSoulSpawns(World world) { public List<BlockPos> getPotentialSoulSpawns(World world) {
if (world == null) if (world == null)
return new ArrayList<>(); return new ArrayList<>();
return getLayer(currentLayerIdx()).map(p -> p.add(pos)) return getLayer(currentLayerIdx()).map(p -> p.add(pos))
.filter(p -> canSpawnSoulAt(world, p)) .filter(p -> canSpawnSoulAt(world, p, true))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
public static boolean canSpawnSoulAt(World world, BlockPos at) { public static boolean canSpawnSoulAt(World world, BlockPos at, boolean ignoreLight) {
EntityType<?> dummy = EntityType.ZOMBIE; EntityType<?> dummy = EntityType.ZOMBIE;
double dummyWidth = 0.2, dummyHeight = 0.75; double dummyWidth = 0.2, dummyHeight = 0.75;
double w2 = dummyWidth / 2; double w2 = dummyWidth / 2;
return world != null return world != null
&& WorldEntitySpawner.canCreatureTypeSpawnAtLocation( && WorldEntitySpawner
EntitySpawnPlacementRegistry.PlacementType.ON_GROUND, world, at, dummy) .canCreatureTypeSpawnAtLocation(EntitySpawnPlacementRegistry.PlacementType.ON_GROUND, world, at, dummy)
&& world.getLightLevel(LightType.BLOCK, at) < 8 && (ignoreLight || world.getLightLevel(LightType.BLOCK, at) < 8)
&& world.getBlockCollisions(null, new AxisAlignedBB( && world
at.getX() + 0.5 - w2, at.getY(), at.getZ() + 0.5 - w2, .getBlockCollisions(null,
at.getX() + 0.5 + w2, at.getY() + dummyHeight, at.getZ() + 0.5 + w2 new AxisAlignedBB(at.getX() + 0.5 - w2, at.getY(), at.getZ() + 0.5 - w2, at.getX() + 0.5 + w2,
), (a,b) -> true).allMatch(VoxelShape::isEmpty); at.getY() + dummyHeight, at.getZ() + 0.5 + w2),
(a, b) -> true)
.allMatch(VoxelShape::isEmpty);
} }
public void spawnParticles(World world, BlockPos at) { public void spawnParticles(World world, BlockPos at) {
@ -91,9 +96,16 @@ public class SoulPulseEffect {
return; return;
Vector3d p = Vector3d.of(at); Vector3d p = Vector3d.of(at);
if (canOverlap())
world.addOptionalParticle(((int) Math.round(VecHelper.getCenterOf(pos)
.distanceTo(VecHelper.getCenterOf(at)))) >= distance ? new SoulParticle.PerimeterData()
: new ExpandingPerimeterData(),
p.x + 0.5, p.y + 0.5, p.z + 0.5, 0, 0, 0);
if (world.getLightLevel(LightType.BLOCK, at) < 8) {
world.addOptionalParticle(new SoulParticle.Data(), p.x + 0.5, p.y + 0.5, p.z + 0.5, 0, 0, 0); world.addOptionalParticle(new SoulParticle.Data(), p.x + 0.5, p.y + 0.5, p.z + 0.5, 0, 0, 0);
world.addParticle(new SoulBaseParticle.Data(), p.x + 0.5, p.y + 0.01, p.z + 0.5, 0, 0, 0); world.addParticle(new SoulBaseParticle.Data(), p.x + 0.5, p.y + 0.01, p.z + 0.5, 0, 0, 0);
} }
}
private static List<List<BlockPos>> genLayers() { private static List<List<BlockPos>> genLayers() {
List<List<BlockPos>> layers = new ArrayList<>(); List<List<BlockPos>> layers = new ArrayList<>();
@ -142,7 +154,8 @@ public class SoulPulseEffect {
public static Stream<BlockPos> getLayer(int idx) { public static Stream<BlockPos> getLayer(int idx) {
if (idx < 0 || idx >= MAX_DISTANCE) if (idx < 0 || idx >= MAX_DISTANCE)
return Stream.empty(); return Stream.empty();
return LAYERS.get(idx).stream(); return LAYERS.get(idx)
.stream();
} }
} }

View file

@ -0,0 +1,12 @@
{
"textures": [
"create:soul_base_0",
"create:soul_base_1",
"create:soul_base_2",
"create:soul_base_3",
"create:soul_base_2",
"create:soul_base_1",
"create:soul_base_0",
"create:soul_base_0"
]
}

View file

@ -0,0 +1,12 @@
{
"textures": [
"create:soul_base_0",
"create:soul_base_1",
"create:soul_base_2",
"create:soul_base_3",
"create:soul_base_4",
"create:soul_base_5",
"create:soul_base_6",
"create:soul_base_7"
]
}