Add a particle effect for dropping a cloud in a bottle

This commit is contained in:
Sollace 2021-02-19 16:56:01 +02:00
parent 82bc317bc1
commit fc5b50a975
10 changed files with 323 additions and 66 deletions

View file

@ -2,6 +2,7 @@ package com.minelittlepony.unicopia.client;
import com.minelittlepony.unicopia.UEntities; import com.minelittlepony.unicopia.UEntities;
import com.minelittlepony.unicopia.client.particle.ChangelingMagicParticle; import com.minelittlepony.unicopia.client.particle.ChangelingMagicParticle;
import com.minelittlepony.unicopia.client.particle.CloudsEscapingParticle;
import com.minelittlepony.unicopia.client.particle.DiskParticle; import com.minelittlepony.unicopia.client.particle.DiskParticle;
import com.minelittlepony.unicopia.client.particle.GroundPoundParticle; import com.minelittlepony.unicopia.client.particle.GroundPoundParticle;
import com.minelittlepony.unicopia.client.particle.HealthDrainParticle; import com.minelittlepony.unicopia.client.particle.HealthDrainParticle;
@ -43,6 +44,7 @@ public interface URenderers {
ParticleFactoryRegistry.getInstance().register(UParticles.SPHERE, SphereParticle::new); ParticleFactoryRegistry.getInstance().register(UParticles.SPHERE, SphereParticle::new);
ParticleFactoryRegistry.getInstance().register(UParticles.DISK, DiskParticle::new); ParticleFactoryRegistry.getInstance().register(UParticles.DISK, DiskParticle::new);
ParticleFactoryRegistry.getInstance().register(UParticles.GROUND_POUND, GroundPoundParticle::new); ParticleFactoryRegistry.getInstance().register(UParticles.GROUND_POUND, GroundPoundParticle::new);
ParticleFactoryRegistry.getInstance().register(UParticles.CLOUDS_ESCAPING, CloudsEscapingParticle::new);
EntityRendererRegistry.INSTANCE.register(UEntities.THROWN_ITEM, (manager, context) -> new FlyingItemEntityRenderer<>(manager, context.getItemRenderer())); EntityRendererRegistry.INSTANCE.register(UEntities.THROWN_ITEM, (manager, context) -> new FlyingItemEntityRenderer<>(manager, context.getItemRenderer()));
EntityRendererRegistry.INSTANCE.register(UEntities.FLOATING_ARTEFACT, FloatingArtefactEntityRenderer::new); EntityRendererRegistry.INSTANCE.register(UEntities.FLOATING_ARTEFACT, FloatingArtefactEntityRenderer::new);

View file

@ -0,0 +1,58 @@
package com.minelittlepony.unicopia.client.particle;
import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.particle.DefaultParticleType;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.util.math.Vec3d;
public class CloudsEscapingParticle extends GroundPoundParticle {
public CloudsEscapingParticle(DefaultParticleType effect, ClientWorld world, double x, double y, double z, double dX, double dY, double dZ) {
super(effect, world, x, y, z, dX, dY, dZ);
maxAge = 200;
collidesWithWorld = false;
}
@Override
public void tick() {
this.prevPosX = this.x;
this.prevPosY = this.y;
this.prevPosZ = this.z;
if (this.age++ >= this.maxAge) {
this.markDead();
} else {
this.velocityY -= 0.04D * this.gravityStrength;
this.move(this.velocityX, this.velocityY, this.velocityZ);
this.velocityX *= 0.9800000190734863D;
this.velocityY *= 0.9800000190734863D;
this.velocityZ *= 0.9800000190734863D;
if (this.onGround) {
this.velocityX *= 0.699999988079071D;
this.velocityZ *= 0.699999988079071D;
}
}
this.velocityY += 0.125;
Vec3d center = getPos();
double variance = age / maxAge;
Vec3d vel = new Vec3d(
(world.random.nextFloat() - 0.5) * (0.125 + variance),
velocityY * 0.2,
(world.random.nextFloat() - 0.5) * (0.125 + variance)
);
double columnHeight = 1 + age / 30;
new Sphere(true, columnHeight, 1, 1, 1)
.offset(center)
.randomPoints(random)
.forEach(point -> {
ParticleUtils.spawnParticle(ParticleTypes.CLOUD, world, point, vel);
});
}
}

View file

@ -1,6 +1,6 @@
package com.minelittlepony.unicopia.client.particle; package com.minelittlepony.unicopia.client.particle;
import com.minelittlepony.unicopia.util.shape.Shape; import com.minelittlepony.unicopia.particle.ParticleUtils;
import com.minelittlepony.unicopia.util.shape.Sphere; import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
@ -32,27 +32,18 @@ public class GroundPoundParticle extends Particle {
public void buildGeometry(VertexConsumer vertexConsumer, Camera camera, float f) { public void buildGeometry(VertexConsumer vertexConsumer, Camera camera, float f) {
} }
protected Vec3d getPos() {
return new Vec3d(x, y, z);
}
@Override @Override
public void tick() { public void tick() {
super.tick(); super.tick();
spawnParticleRing(age, 1); Vec3d vel = new Vec3d(0, (0.5 + (Math.sin(age) * 2.5)) * 5, 0);
}
new Sphere(true, age, 1, 0, 1).offset(getPos()).randomPoints(random).forEach(point -> {
private void spawnParticleRing(int timeDiff, double yVel) { BlockPos pos = new BlockPos(point).down();
Shape shape = new Sphere(true, timeDiff, 1, 0, 1);
double y = 0.5 + (Math.sin(timeDiff) * 2.5);
yVel *= y * 5;
Vec3d center = new Vec3d(x, this.y, z);
for (int i = 0; i < shape.getVolumeOfSpawnableSpace(); i++) {
Vec3d point = shape.computePoint(random).add(center);
BlockPos pos = new BlockPos(point.x, center.y - 1, point.z);
BlockState state = world.getBlockState(pos); BlockState state = world.getBlockState(pos);
if (state.isAir()) { if (state.isAir()) {
@ -62,13 +53,7 @@ public class GroundPoundParticle extends Particle {
} }
} }
world.addParticle(new BlockStateParticleEffect(ParticleTypes.BLOCK, state), ParticleUtils.spawnParticle(new BlockStateParticleEffect(ParticleTypes.BLOCK, state), world, point, vel);
point.x, });
point.y,
point.z,
0, yVel, 0
);
}
} }
} }

View file

@ -2,6 +2,7 @@ package com.minelittlepony.unicopia.item;
import com.minelittlepony.unicopia.entity.IItemEntity; import com.minelittlepony.unicopia.entity.IItemEntity;
import com.minelittlepony.unicopia.entity.ItemImpl; import com.minelittlepony.unicopia.entity.ItemImpl;
import com.minelittlepony.unicopia.particle.UParticles;
import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; import com.minelittlepony.unicopia.projectile.MagicProjectileEntity;
import com.minelittlepony.unicopia.projectile.ProjectileDelegate; import com.minelittlepony.unicopia.projectile.ProjectileDelegate;
import com.minelittlepony.unicopia.util.WorldEvent; import com.minelittlepony.unicopia.util.WorldEvent;
@ -138,6 +139,15 @@ public class JarItem extends Item implements ProjectileDelegate, ItemImpl.Tickab
if (rain || thunder) { if (rain || thunder) {
projectile.world.syncWorldEvent(WorldEvent.PROJECTILE_HIT, projectile.getBlockPos(), thunder ? 0x888888 : 0xF8F8F8); projectile.world.syncWorldEvent(WorldEvent.PROJECTILE_HIT, projectile.getBlockPos(), thunder ? 0x888888 : 0xF8F8F8);
for (int i = projectile.world.random.nextInt(3) + 1; i >= 0; i--) {
projectile.world.addParticle(UParticles.CLOUDS_ESCAPING, true,
projectile.getX(), projectile.getY(), projectile.getZ(),
projectile.world.random.nextFloat() - 0.5,
0,
projectile.world.random.nextFloat() - 0.5
);
}
} }
WorldEvent.play(WorldEvent.DESTROY_BLOCK, projectile.world, projectile.getBlockPos(), Blocks.GLASS.getDefaultState()); WorldEvent.play(WorldEvent.DESTROY_BLOCK, projectile.world, projectile.getBlockPos(), Blocks.GLASS.getDefaultState());

View file

@ -6,6 +6,7 @@ import com.minelittlepony.unicopia.util.shape.Sphere;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.particle.ParticleEffect; import net.minecraft.particle.ParticleEffect;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World; import net.minecraft.world.World;
/** /**
@ -28,12 +29,15 @@ public final class ParticleUtils {
}); });
} }
public static void spawnParticle(ParticleEffect particleId, World world, Vec3d pos, Vec3d vel) {
spawnParticle(world, particleId, pos.x, pos.y, pos.z, vel.x, vel.y, vel.z);
}
public static void spawnParticle(World world, ParticleEffect effect, double x, double y, double z, double vX, double vY, double vZ) { public static void spawnParticle(World world, ParticleEffect effect, double x, double y, double z, double vX, double vY, double vZ) {
if (world instanceof ServerWorld) { if (world instanceof ServerWorld) {
((ServerWorld)world).spawnParticles(effect, x, y, z, 1, vX, vY, vZ, 0); ((ServerWorld)world).spawnParticles(effect, x, y, z, 1, vX, vY, vZ, 0);
} else { } else {
world.addParticle(effect, x, y, z, vX, vY, vZ); world.addParticle(effect, x, y, z, vX, vY, vZ);
} }
} }
} }

View file

@ -22,6 +22,7 @@ public interface UParticles {
ParticleType<FollowingParticleEffect> HEALTH_DRAIN = register("health_drain", FabricParticleTypes.complex(true, FollowingParticleEffect.FACTORY)); ParticleType<FollowingParticleEffect> HEALTH_DRAIN = register("health_drain", FabricParticleTypes.complex(true, FollowingParticleEffect.FACTORY));
DefaultParticleType GROUND_POUND = register("ground_pound", FabricParticleTypes.simple()); DefaultParticleType GROUND_POUND = register("ground_pound", FabricParticleTypes.simple());
DefaultParticleType CLOUDS_ESCAPING = register("clouds_escaping", FabricParticleTypes.simple(true));
static <T extends ParticleType<?>> T register(String name, T type) { static <T extends ParticleType<?>> T register(String name, T type) {
return Registry.register(Registry.PARTICLE_TYPE, new Identifier("unicopia", name), type); return Registry.register(Registry.PARTICLE_TYPE, new Identifier("unicopia", name), type);

View file

@ -0,0 +1,130 @@
package com.minelittlepony.unicopia.util.shape;
import java.util.Random;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
public class Cylinder implements Shape {
private final boolean hollow;
private final double stretchX;
private final double stretchZ;
private final double height;
private final double rad;
private float yaw = 0;
private float pitch = 0;
private final double volume;
/**
* Creates a uniform cylinder.
*
* @param hollow True if this shape must be hollow.
* @param height Cylinder height
* @param radius Cylinder radius
*
*/
public Cylinder(boolean hollow, double height, double radius) {
this(hollow, height, radius, 1, 1);
}
/**
* Creates a cylinder of arbitrary dimensions.
* <p>
* Can be used to create a flat circle by setting the height to 0.
*
* @param hollow True if this shape must be hollow.
* @param height Cylinder height
* @param radius Cylinder radius
* @param stretchX Warp this shape's X-axis
* @param stretchZ Warp this shape's Z-axis
*/
public Cylinder(boolean hollow, double height, double radius, double stretchX, double stretchZ) {
this.hollow = hollow;
this.height = height;
rad = radius;
this.stretchX = stretchX;
this.stretchZ = stretchZ;
volume = computeSpawnableSpace();
}
@Override
public double getVolumeOfSpawnableSpace() {
return volume;
}
private double computeSpawnableSpace() {
if (hollow) {
if (stretchX != stretchZ) {
double result = 3 * (stretchX + stretchZ);
result -= Math.sqrt((10 * stretchX * stretchZ) + 3 * ((stretchX * stretchX) + (stretchZ * stretchZ)));
return Math.PI * result;
}
return 2 * Math.PI * rad * stretchX * height;
}
return Math.PI * (stretchX * rad * stretchZ * rad) * height;
}
@Override
public double getXOffset() {
return 0;
}
@Override
public double getYOffset() {
return 0;
}
@Override
public double getZOffset() {
return 0;
}
@Override
public Vec3d computePoint(Random rand) {
double y = MathHelper.nextDouble(rand, 0, height);
double pheta = MathHelper.nextDouble(rand, 0, Math.PI * 2);
double rho = hollow && Math.abs(y) != height/2 ? rad : MathHelper.nextDouble(rand, 0, rad);
return new Vec3d(rho * Math.cos(pheta) * stretchX, y, rho * Math.sin(pheta) * stretchZ)
.rotateY(yaw)
.rotateX(pitch);
}
@Override
public Cylinder setRotation(float u, float v) {
yaw = u;
pitch = v;
return this;
}
@Override
public boolean isPointInside(Vec3d point) {
point = point.rotateY(-yaw).rotateX(-pitch);
point = new Vec3d(point.x / stretchX, point.y, point.z / stretchZ);
double y = Math.abs(point.y);
if (y < height/2) {
double r = Math.sqrt((point.x * point.x) + (point.z * point.z));
return hollow ? r == rad : r <= rad;
}
return y == height/2;
}
@Override
public Vec3d getLowerBound() {
return new Vec3d(-rad * stretchX, 0, -rad * stretchZ)
.rotateY(yaw)
.rotateX(pitch);
}
@Override
public Vec3d getUpperBound() {
return new Vec3d(-rad * stretchX, height, -rad * stretchZ)
.rotateY(yaw)
.rotateX(pitch);
}
}

View file

@ -0,0 +1,104 @@
package com.minelittlepony.unicopia.util.shape;
import java.util.Random;
import java.util.Spliterator;
import java.util.Spliterators.AbstractSpliterator;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import com.google.common.collect.Streams;
import net.minecraft.util.math.Vec3d;
/**
*
*Interface for a 3d shape, used for spawning particles in a designated area (or anything else you need shapes for).
*/
public interface PointGenerator {
/**
* Get the volume of space filled by this shape, or the surface area if hollow.
*
* @return double volume
*/
double getVolumeOfSpawnableSpace();
/**
* Computes a random coordinate that falls within this shape's designated area.
*/
Vec3d computePoint(Random rand);
/**
* Returns a sequence of random points dealed out to uniformly fill this shape's area.
*/
default Stream<Vec3d> randomPoints(Random rand) {
return randomPoints((int)getVolumeOfSpawnableSpace(), rand);
}
/**
* Returns a sequence of N random points.
*/
default Stream<Vec3d> randomPoints(int n, Random rand) {
AtomicInteger atom = new AtomicInteger(n);
return StreamSupport.stream(new AbstractSpliterator<Vec3d>(n, Spliterator.SIZED) {
@Override
public boolean tryAdvance(Consumer<? super Vec3d> consumer) {
if (atom.decrementAndGet() >= 0) {
consumer.accept(computePoint(rand));
return true;
}
return false;
}
}, false);
}
default PointGenerator union(PointGenerator other) {
final PointGenerator source = this;
return new PointGenerator() {
@Override
public double getVolumeOfSpawnableSpace() {
return source.getVolumeOfSpawnableSpace() + other.getVolumeOfSpawnableSpace();
}
@Override
public Vec3d computePoint(Random rand) {
return source.computePoint(rand);
}
@Override
public Stream<Vec3d> randomPoints(int n, Random rand) {
double total = this.getVolumeOfSpawnableSpace();
return Streams.concat(
source.randomPoints((int)(n * (source.getVolumeOfSpawnableSpace() / total)), rand),
other.randomPoints((int)(n * (other.getVolumeOfSpawnableSpace() / total)), rand)
);
}
};
}
/**
* Returns a new point generator where all of its points are offset by the specified amount.
*/
default PointGenerator offset(Vec3d offset) {
final PointGenerator source = this;
return new PointGenerator() {
@Override
public double getVolumeOfSpawnableSpace() {
return source.getVolumeOfSpawnableSpace();
}
@Override
public Vec3d computePoint(Random rand) {
return source.computePoint(rand).add(offset);
}
};
}
}

View file

@ -1,20 +1,12 @@
package com.minelittlepony.unicopia.util.shape; package com.minelittlepony.unicopia.util.shape;
import java.util.Random;
import java.util.Spliterator;
import java.util.Spliterators.AbstractSpliterator;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
/** /**
* *
*Interface for a 3d shape, used for spawning particles in a designated area (or anything else you need shapes for). *Interface for a 3d shape, used for spawning particles in a designated area (or anything else you need shapes for).
*/ */
public interface Shape { public interface Shape extends PointGenerator {
/** /**
* Rotates this shape around it's center. * Rotates this shape around it's center.
@ -26,13 +18,6 @@ public interface Shape {
*/ */
Shape setRotation(float u, float v); Shape setRotation(float u, float v);
/**
* Get the volume of space filled by this shape, or the surface area if hollow.
*
* @return double volume
*/
double getVolumeOfSpawnableSpace();
/** /**
* X offset from the shape's origin. * X offset from the shape's origin.
* *
@ -64,33 +49,9 @@ public interface Shape {
*/ */
Vec3d getUpperBound(); Vec3d getUpperBound();
/**
* Computes a random coordinate that falls within this shape's designated area.
*/
Vec3d computePoint(Random rand);
/** /**
* Checks if the given point is on the edge, or if not hollow the inside, of this shape. * Checks if the given point is on the edge, or if not hollow the inside, of this shape.
* @return * @return
*/ */
boolean isPointInside(Vec3d point); boolean isPointInside(Vec3d point);
/**
* Returns a sequence of N random points.
*/
default Stream<Vec3d> randomPoints(int n, Random rand) {
AtomicInteger atom = new AtomicInteger(n);
return StreamSupport.stream(new AbstractSpliterator<Vec3d>(n, Spliterator.SIZED) {
@Override
public boolean tryAdvance(Consumer<? super Vec3d> consumer) {
if (atom.decrementAndGet() >= 0) {
consumer.accept(computePoint(rand));
return true;
}
return false;
}
}, false);
}
} }

View file

@ -0,0 +1,2 @@
{
}