From 1545210efa524e3b43f6fe5e91111b128ac5e590 Mon Sep 17 00:00:00 2001 From: Sollace Date: Mon, 22 Apr 2024 17:21:12 +0100 Subject: [PATCH] Added wind trail particles --- .../unicopia/block/WeatherVaneBlock.java | 39 +++++-- .../unicopia/client/URenderers.java | 2 + .../client/particle/RainbowTrailParticle.java | 2 +- .../client/particle/WindParticle.java | 103 ++++++++++++++++++ .../unicopia/client/render/bezier/Trail.java | 7 +- .../unicopia/entity/player/PlayerPhysics.java | 10 ++ .../particle/TargetBoundParticleEffect.java | 6 +- .../unicopia/particle/UParticles.java | 1 + .../server/world/WeatherConditions.java | 6 +- .../assets/unicopia/particles/wind.json | 2 + .../unicopia/textures/particle/wind.png | Bin 0 -> 8178 bytes 11 files changed, 164 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/client/particle/WindParticle.java create mode 100644 src/main/resources/assets/unicopia/particles/wind.json create mode 100644 src/main/resources/assets/unicopia/textures/particle/wind.png diff --git a/src/main/java/com/minelittlepony/unicopia/block/WeatherVaneBlock.java b/src/main/java/com/minelittlepony/unicopia/block/WeatherVaneBlock.java index 14d39f7f..39e526bb 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/WeatherVaneBlock.java +++ b/src/main/java/com/minelittlepony/unicopia/block/WeatherVaneBlock.java @@ -3,6 +3,8 @@ package com.minelittlepony.unicopia.block; import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.USounds; +import com.minelittlepony.unicopia.particle.TargetBoundParticleEffect; +import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.server.world.WeatherConditions; import net.minecraft.block.*; @@ -51,6 +53,9 @@ public class WeatherVaneBlock extends BlockWithEntity { private float clientAngle; private float prevAngle; + private float lastAngle; + + private Vec3d airflow = Vec3d.ZERO; public WeatherVane(BlockPos pos, BlockState state) { super(UBlockEntities.WEATHER_VANE, pos, state); @@ -63,11 +68,14 @@ public class WeatherVaneBlock extends BlockWithEntity { @Override public void readNbt(NbtCompound nbt) { angle = nbt.getFloat("angle"); + airflow = new Vec3d(nbt.getDouble("windX"), 0, nbt.getDouble("windZ")); } @Override protected void writeNbt(NbtCompound nbt) { nbt.putFloat("angle", angle); + nbt.putDouble("windX", airflow.x); + nbt.putDouble("windZ", airflow.z); } @Override @@ -82,21 +90,24 @@ public class WeatherVaneBlock extends BlockWithEntity { public static void serverTick(World world, BlockPos pos, BlockState state, WeatherVane entity) { Vec3d airflow = WeatherConditions.get(world).getWindDirection(); - float angle = (float)Math.atan2(airflow.x, airflow.z) + MathHelper.PI; - if (Math.signum(entity.angle) != Math.signum(angle)) { - angle = MathHelper.PI - angle; - } - angle %= MathHelper.PI; + float angle = (WeatherConditions.get(world).getWindYaw() % MathHelper.PI); + entity.lastAngle = entity.prevAngle; + entity.prevAngle = entity.angle; if (angle != entity.angle) { entity.angle = angle; + + entity.airflow = airflow; entity.markDirty(); - if (world instanceof ServerWorld serverWorld) { - serverWorld.getChunkManager().markForUpdate(pos); + if (world instanceof ServerWorld sw) { + sw.getChunkManager().markForUpdate(pos); } - world.playSound(null, pos.getX(), pos.getY(), pos.getZ(), USounds.BLOCK_WEATHER_VANE_ROTATE, SoundCategory.BLOCKS, 1, 0.5F + (float)world.random.nextGaussian()); + if (entity.lastAngle == entity.prevAngle) { + world.playSound(null, pos.getX(), pos.getY(), pos.getZ(), USounds.BLOCK_WEATHER_VANE_ROTATE, SoundCategory.BLOCKS, 1, 0.5F + (float)world.random.nextGaussian()); + } } + } public static void clientTick(World world, BlockPos pos, BlockState state, WeatherVane entity) { @@ -111,6 +122,18 @@ public class WeatherVaneBlock extends BlockWithEntity { } else if (entity.clientAngle > angle) { entity.clientAngle -= step; } + + if (world.random.nextInt(3) == 0) { + float radius = 10; + for (int i = 0; i < 5; i++) { + world.addImportantParticle(new TargetBoundParticleEffect(UParticles.WIND, null), + world.getRandom().nextTriangular(pos.getX(), radius), + world.getRandom().nextTriangular(pos.getY(), radius), + world.getRandom().nextTriangular(pos.getZ(), radius), + entity.airflow.x / 10F, 0, entity.airflow.z / 10F + ); + } + } } } } diff --git a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java index b4f09f99..a0d9a21c 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java +++ b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java @@ -22,6 +22,7 @@ import com.minelittlepony.unicopia.client.particle.RainbowTrailParticle; import com.minelittlepony.unicopia.client.particle.RaindropsParticle; import com.minelittlepony.unicopia.client.particle.ShockwaveParticle; import com.minelittlepony.unicopia.client.particle.SphereParticle; +import com.minelittlepony.unicopia.client.particle.WindParticle; import com.minelittlepony.unicopia.client.render.*; import com.minelittlepony.unicopia.client.render.entity.*; import com.minelittlepony.unicopia.client.render.shader.UShaders; @@ -81,6 +82,7 @@ public interface URenderers { ParticleFactoryRegistry.getInstance().register(UParticles.FOOTPRINT, createFactory(FootprintParticle::new)); ParticleFactoryRegistry.getInstance().register(UParticles.RAINBOOM_RING, RainboomParticle::new); ParticleFactoryRegistry.getInstance().register(UParticles.RAINBOOM_TRAIL, RainbowTrailParticle::new); + ParticleFactoryRegistry.getInstance().register(UParticles.WIND, WindParticle::new); ParticleFactoryRegistry.getInstance().register(UParticles.SHOCKWAVE, ShockwaveParticle::new); ParticleFactoryRegistry.getInstance().register(UParticles.SPHERE, SphereParticle::new); ParticleFactoryRegistry.getInstance().register(UParticles.DISK, DiskParticle::new); diff --git a/src/main/java/com/minelittlepony/unicopia/client/particle/RainbowTrailParticle.java b/src/main/java/com/minelittlepony/unicopia/client/particle/RainbowTrailParticle.java index 978fede1..4a8d7b26 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/particle/RainbowTrailParticle.java +++ b/src/main/java/com/minelittlepony/unicopia/client/particle/RainbowTrailParticle.java @@ -29,7 +29,7 @@ public class RainbowTrailParticle extends AbstractBillboardParticle { public RainbowTrailParticle(TargetBoundParticleEffect effect, ClientWorld world, double x, double y, double z, double velocityX, double velocityY, double velocityZ) { super(world, x, y, z, velocityX, velocityY, velocityZ); - trail = new Trail(new Vec3d(x, y, z)); + trail = new Trail(new Vec3d(x, y, z), 1); setMaxAge(300); this.velocityX = velocityX; this.velocityY = velocityY; diff --git a/src/main/java/com/minelittlepony/unicopia/client/particle/WindParticle.java b/src/main/java/com/minelittlepony/unicopia/client/particle/WindParticle.java new file mode 100644 index 00000000..222fdfed --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/particle/WindParticle.java @@ -0,0 +1,103 @@ +package com.minelittlepony.unicopia.client.particle; + +import java.util.List; +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.client.render.bezier.BezierSegment; +import com.minelittlepony.unicopia.client.render.bezier.Trail; +import com.minelittlepony.unicopia.particle.TargetBoundParticleEffect; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.BufferBuilder; +import net.minecraft.client.render.Tessellator; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.Entity; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; + +public class WindParticle extends AbstractBillboardParticle { + private static final Identifier TEXTURE = Unicopia.id("textures/particle/wind.png"); + + private final Trail trail; + + @Nullable + private Entity target; + + private int attachmentTicks; + + private final Vec3d offset; + private final boolean passive; + + public WindParticle(TargetBoundParticleEffect effect, ClientWorld world, double x, double y, double z, double velocityX, double velocityY, double velocityZ) { + super(world, x, y, z, velocityX, velocityY, velocityZ); + trail = new Trail(new Vec3d(x, y, z), 0.02F); + setMaxAge(300); + this.alpha = 0.15F; + this.velocityX = velocityX; + this.velocityY = velocityY; + this.velocityZ = velocityZ; + this.attachmentTicks = (int)world.random.nextTriangular(15, 12); + this.passive = effect.getTargetId() <= 0; + + if (effect.getTargetId() > 0) { + this.target = world.getEntityById(effect.getTargetId()); + } + offset = target == null ? Vec3d.ZERO : new Vec3d(x, y, z).subtract(target.getPos()); + } + + @Override + protected Identifier getTexture() { + return TEXTURE; + } + + @Override + public boolean isAlive() { + return age < getMaxAge() && (!dead || !trail.getSegments().isEmpty()); + } + + @Override + protected void renderQuads(Tessellator te, BufferBuilder buffer, float x, float y, float z, float tickDelta) { + float alpha = this.alpha * (1 - (float)age / maxAge); + + List segments = trail.getSegments(); + + for (int i = 0; i < segments.size() - 1; i++) { + BezierSegment corners = segments.get(i).getPlane(segments.get(i + 1)); + float scale = getScale(tickDelta); + + corners.forEachCorner(corner -> { + corner.position().mul(scale).add(x, y, z); + }); + + renderQuad(te, buffer, corners.corners(), segments.get(i).getAlpha() * alpha, tickDelta); + } + } + + @Override + public void tick() { + super.tick(); + + float animationFrame = age + MinecraftClient.getInstance().getTickDelta(); + + float sin = MathHelper.sin(animationFrame / 5F) * 0.1F; + float cos = MathHelper.cos(animationFrame / 10F) * 0.2F; + + if (passive) { + trail.update(new Vec3d(x + cos, y + sin, z - cos)); + } else { + if (target != null && target.isAlive()) { + trail.update(target.getPos().add(offset).add(cos, sin, -cos)); + + if (attachmentTicks > 0 && --attachmentTicks <= 0) { + target = null; + } + } + } + + if (trail.tick()) { + markDead(); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/bezier/Trail.java b/src/main/java/com/minelittlepony/unicopia/client/render/bezier/Trail.java index f130fbcc..8bfd4c5d 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/bezier/Trail.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/bezier/Trail.java @@ -12,8 +12,11 @@ public class Trail { public final Vec3d pos; - public Trail(Vec3d pos) { + private final float height; + + public Trail(Vec3d pos, float height) { this.pos = pos; + this.height = height; segments.add(new Segment(pos)); } @@ -61,7 +64,7 @@ public class Trail { } public BezierSegment getPlane(Segment to) { - return new BezierSegment(offset, to.offset, 1); + return new BezierSegment(offset, to.offset, height); } } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java index 0cb1f1b4..de13f11d 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java @@ -649,6 +649,14 @@ public class PlayerPhysics extends EntityPhysics implements Tickab velocity.x += - forward * MathHelper.sin(entity.getYaw() * 0.017453292F); velocity.z += forward * MathHelper.cos(entity.getYaw() * 0.017453292F); + if (pony.isClient()) { + float effectChance = 1F - (float)(MathHelper.clamp(velocity.horizontalLengthSquared(), 0, 1)); + + if (entity.getWorld().random.nextInt(1 + (int)(120 * effectChance)) == 0) { + pony.spawnParticles(new TargetBoundParticleEffect(UParticles.WIND, pony.asEntity()), 3); + } + } + if (entity.getWorld().hasRain(entity.getBlockPos())) { applyTurbulance(velocity); } else { @@ -820,6 +828,8 @@ public class PlayerPhysics extends EntityPhysics implements Tickab pony.updateVelocity(); + pony.spawnParticles(new TargetBoundParticleEffect(UParticles.WIND, pony.asEntity()), 4); + if (isFlying()) { playSound(USounds.ENTITY_PLAYER_PEGASUS_DASH, 1, 1); } else { diff --git a/src/main/java/com/minelittlepony/unicopia/particle/TargetBoundParticleEffect.java b/src/main/java/com/minelittlepony/unicopia/particle/TargetBoundParticleEffect.java index 15171015..76d84b03 100644 --- a/src/main/java/com/minelittlepony/unicopia/particle/TargetBoundParticleEffect.java +++ b/src/main/java/com/minelittlepony/unicopia/particle/TargetBoundParticleEffect.java @@ -3,6 +3,8 @@ package com.minelittlepony.unicopia.particle; import java.util.Locale; +import org.jetbrains.annotations.Nullable; + import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -29,9 +31,9 @@ public class TargetBoundParticleEffect implements ParticleEffect { this.targetId = buf.readInt(); } - public TargetBoundParticleEffect(ParticleType type, Entity target) { + public TargetBoundParticleEffect(ParticleType type, @Nullable Entity target) { this.type = type; - this.targetId = target.getId(); + this.targetId = target == null ? -1 : target.getId(); } public int getTargetId() { diff --git a/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java b/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java index da302010..0c440ee6 100644 --- a/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java +++ b/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java @@ -19,6 +19,7 @@ public interface UParticles { ParticleType RAINBOOM_RING = register("rainboom_ring", FabricParticleTypes.complex(OrientedBillboardParticleEffect.FACTORY)); ParticleType RAINBOOM_TRAIL = register("rainboom_trail", FabricParticleTypes.complex(TargetBoundParticleEffect.FACTORY)); + ParticleType WIND = register("wind", FabricParticleTypes.complex(TargetBoundParticleEffect.FACTORY)); DefaultParticleType RAIN_DROPS = register("rain_drops", FabricParticleTypes.simple()); diff --git a/src/main/java/com/minelittlepony/unicopia/server/world/WeatherConditions.java b/src/main/java/com/minelittlepony/unicopia/server/world/WeatherConditions.java index 75e0b392..d392b684 100644 --- a/src/main/java/com/minelittlepony/unicopia/server/world/WeatherConditions.java +++ b/src/main/java/com/minelittlepony/unicopia/server/world/WeatherConditions.java @@ -114,8 +114,12 @@ public class WeatherConditions extends PersistentState implements Tickable { return MathHelper.lerp(interpolation / (float)maxInterpolation, prevWindYaw, windYaw); } + public int getWindInterpolation() { + return interpolation; + } + public Vec3d getWindDirection() { - return Vec3d.fromPolar(0, windYaw).normalize(); + return Vec3d.fromPolar(0, getWindYaw()).normalize(); } @Override diff --git a/src/main/resources/assets/unicopia/particles/wind.json b/src/main/resources/assets/unicopia/particles/wind.json new file mode 100644 index 00000000..2c63c085 --- /dev/null +++ b/src/main/resources/assets/unicopia/particles/wind.json @@ -0,0 +1,2 @@ +{ +} diff --git a/src/main/resources/assets/unicopia/textures/particle/wind.png b/src/main/resources/assets/unicopia/textures/particle/wind.png new file mode 100644 index 0000000000000000000000000000000000000000..0926e002e1c00d81256cc21fa5345daed5cb24a3 GIT binary patch literal 8178 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 od~#9+T?%B<8h6=d>bSBbL~ar`Mb)o50J