Skip to content

Commit

Permalink
splash cloud particles
Browse files Browse the repository at this point in the history
  • Loading branch information
Goby56 committed Dec 28, 2023
1 parent b902904 commit 6c1ec32
Show file tree
Hide file tree
Showing 22 changed files with 220 additions and 35 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[![Download on Modrinth](https://raw.githubusercontent.com/Prospector/badges/master/modrinth-badge-72h-padded.png)](https://modrinth.com/mod/wakes)

## DISCLAIMER
My time developing the mod will be very limited for the coming 10 months. Updates will not be as regular. I will try migrating the mod to newer versions of Minecraft but don't expect new features.
My time developing the mod will be quite limited up until June 2024. Updates will not be as regular. I will try migrating the mod to newer versions of Minecraft but don't expect new features.

# Wakes
This mod aimes to add simple wakes that fit the spirit of vanilla.
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/goby56/wakes/WakesClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@
import com.goby56.wakes.config.WakesConfig;
import com.goby56.wakes.event.WakeTicker;
import com.goby56.wakes.particle.ModParticles;
import com.goby56.wakes.particle.custom.SplashCloudParticle;
import com.goby56.wakes.render.SplashPlaneRenderer;
import com.goby56.wakes.render.WakeTextureRenderer;
import com.goby56.wakes.render.debug.WakeDebugRenderer;
import com.goby56.wakes.render.model.WakeModel;
import com.terraformersmc.modmenu.util.mod.Mod;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.particle.v1.ParticleFactoryRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.EntityModelLayerRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.fabricmc.loader.api.FabricLoader;
Expand Down
12 changes: 11 additions & 1 deletion src/main/java/com/goby56/wakes/command/DebugCommand.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.goby56.wakes.command;

import com.goby56.wakes.particle.ModParticles;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
Expand All @@ -24,7 +25,10 @@ public static void register(CommandDispatcher<FabricClientCommandSource> dispatc
.then(ClientCommandManager.literal("light")
.executes(DebugCommand::lightCoordinate))
.then(ClientCommandManager.literal("color")
.executes(DebugCommand::waterColor)));
.executes(DebugCommand::waterColor))
.then(ClientCommandManager.literal("spawn")
.then(ClientCommandManager.literal("splash_cloud_particle")
.executes(DebugCommand::spawnSplashCloudParticle))));
}

public static int lightCoordinate(CommandContext<FabricClientCommandSource> cmdCtx) throws CommandSyntaxException {
Expand All @@ -46,4 +50,10 @@ public static int waterColor(CommandContext<FabricClientCommandSource> cmdCtx) t
ColorHelper.Argb.getBlue(col))));
return 1;
}

public static int spawnSplashCloudParticle(CommandContext<FabricClientCommandSource> cmdCtx) throws CommandSyntaxException {
Vec3d pos = cmdCtx.getSource().getPosition();
cmdCtx.getSource().getWorld().addParticle(ModParticles.SPLASH_CLOUD, pos.x, pos.y, pos.z, 0, 0, 0);
return 1;
}
}
4 changes: 4 additions & 0 deletions src/main/java/com/goby56/wakes/config/WakesConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public class WakesConfig {
public int splashStrength = 100;
public double minimumProducerVelocity = 0.1;
public float waveDecay = 0.9f;
public boolean spawnParticles = true;

// Debug
public int floodFillDistance = 3;
Expand Down Expand Up @@ -95,14 +96,17 @@ public void setUpper(int upper) {
}

// Splash plane
public boolean renderSplashPlane = true;
public float splashPlaneWidth = 3f;
public float splashPlaneHeight = 1.5f;
public float splashPlaneDepth = 2f;
public int splashPlaneResolution = 5;
public float maxSplashPlaneVelocity = 1f;
public float splashPlaneScale = 1f;
public float splashPlaneOffset = 0f;

public enum WakeSpawningRule {
// TODO MORE EXHAUSTIVE CONFIG CONDITION CHECKS
WAKES_AND_SPLASHES(true, true),
ONLY_WAKES(true, false),
ONLY_SPLASHES(false, true),
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/goby56/wakes/config/YACLIntegration.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ public static Screen createScreen(Screen parent) {
.binding(1f, () -> config.splashPlaneScale, val -> config.splashPlaneScale = val)
.controller(opt -> floatSlider(opt, 0.1f, 2f, 0.1f))
.build())
.option(optionOf(Float.class, "splash_plane_offset")
.binding(0f, () -> config.splashPlaneOffset, val -> config.splashPlaneOffset = val)
.controller(opt -> floatSlider(opt, -1f, 1f, 0.1f))
.build())
.option(optionOf(Float.class, "splash_plane_width")
.binding(3f, () -> config.splashPlaneWidth, val -> config.splashPlaneWidth = val)
.controller(opt -> floatSlider(opt, 0f, 10f, 0.1f))
Expand All @@ -71,6 +75,12 @@ public static Screen createScreen(Screen parent) {
.build())
.build())
.category(configCategory("wake_behaviour")
.option(booleanOption("spawn_particles")
.binding(true, () -> config.spawnParticles, val -> config.spawnParticles = val)
.build())
.option(booleanOption("render_splash_plane")
.binding(true, () -> config.renderSplashPlane, val -> config.renderSplashPlane = val)
.build())
.option(optionOf(Float.class, "max_splash_plane_velocity")
.description(description("max_splash_plane_velocity").build())
.binding(1f, () -> config.maxSplashPlaneVelocity, val -> config.maxSplashPlaneVelocity = val)
Expand Down
11 changes: 7 additions & 4 deletions src/main/java/com/goby56/wakes/mixin/WakeSpawnerMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import net.minecraft.client.particle.Particle;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.vehicle.BoatEntity;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
Expand Down Expand Up @@ -105,17 +106,19 @@ private void tick(CallbackInfo info) {
if (!WakesClient.CONFIG_INSTANCE.spawnWakes) {
return;
}

// TODO IMPLEMENT ALL CONFIG CONDITIONAL CHECKS (BETTER AND MORE EXHAUSTIVE APPROACH)
if (this.onWaterSurface) {
if (this.producingWaterLevel == null)
this.producingWaterLevel = WakesUtils.getWaterLevel(this.world, ((Entity) (Object) this));

if (WakesClient.CONFIG_INSTANCE.getSpawningRule(((Entity) (Object) this)).spawnsWake) {
if (this.wakeParticle == null && vel.horizontalLength() > 1e-2) {
WakesUtils.spawnWakeSplashParticle(this.world, ((Entity) (Object) this));
if (this.wakeParticle == null && vel.horizontalLength() > 1e-2 && WakesClient.CONFIG_INSTANCE.renderSplashPlane) {
WakesUtils.spawnSplashPlaneParticle(this.world, ((Entity) (Object) this));
}

WakesUtils.placeWakeTrail(((Entity) (Object) this));
if (((Entity) (Object) this) instanceof BoatEntity boat && WakesClient.CONFIG_INSTANCE.spawnParticles) {
WakesUtils.spawnPaddleSplashCloudParticle(world, boat);
}
} else {
this.prevWakeProdPos = null;
}
Expand Down
12 changes: 10 additions & 2 deletions src/main/java/com/goby56/wakes/particle/ModParticles.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
package com.goby56.wakes.particle;

import com.goby56.wakes.WakesClient;
import com.goby56.wakes.particle.custom.SplashCloudParticle;
import com.goby56.wakes.particle.custom.SplashPlaneParticle;
import net.fabricmc.fabric.api.client.particle.v1.ParticleFactoryRegistry;
import net.fabricmc.fabric.api.particle.v1.FabricParticleTypes;
import net.minecraft.particle.DefaultParticleType;
import net.minecraft.particle.ParticleType;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.util.Identifier;

public class ModParticles {
public static SplashPlaneParticleType SPLASH_PLANE;
public static WithOwnerParticleType SPLASH_PLANE;
public static DefaultParticleType SPLASH_CLOUD = FabricParticleTypes.simple();

public static void registerParticles() {
SPLASH_PLANE = Registry.register(Registries.PARTICLE_TYPE, new Identifier(WakesClient.MOD_ID, "splash_plane"), new SplashPlaneParticleType(true));
SPLASH_PLANE = Registry.register(Registries.PARTICLE_TYPE, new Identifier(WakesClient.MOD_ID, "splash_plane"), new WithOwnerParticleType(true));
ParticleFactoryRegistry.getInstance().register(SPLASH_PLANE, SplashPlaneParticle.Factory::new);

Registry.register(Registries.PARTICLE_TYPE, new Identifier(WakesClient.MOD_ID, "splash_cloud"), SPLASH_CLOUD);
ParticleFactoryRegistry.getInstance().register(SPLASH_CLOUD, SplashCloudParticle.Factory::new);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import net.minecraft.entity.Entity;
import net.minecraft.particle.DefaultParticleType;

public class SplashPlaneParticleType extends DefaultParticleType {
public class WithOwnerParticleType extends DefaultParticleType {
public Entity owner;

protected SplashPlaneParticleType(boolean alwaysShow) {
protected WithOwnerParticleType(boolean alwaysShow) {
super(alwaysShow);
}

public SplashPlaneParticleType withOwner(Entity owner) {
public WithOwnerParticleType withOwner(Entity owner) {
this.owner = owner;
return this;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.goby56.wakes.particle.custom;

import com.goby56.wakes.particle.WithOwnerParticleType;
import com.goby56.wakes.utils.WakesUtils;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.client.particle.*;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.particle.DefaultParticleType;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import org.jetbrains.annotations.Nullable;

public class SplashCloudParticle extends SpriteBillboardParticle {

public SplashCloudParticle(ClientWorld world, double x, double y, double z, SpriteProvider sprites, double velocityX, double velocityY, double velocityZ) {
super(world, x, y, z, velocityX, velocityY, velocityZ);

this.velocityX += 0.001f * (world.random.nextGaussian() - 0.5f);
this.velocityY += 0.01f * Math.abs(world.random.nextGaussian() - 0.5f);
this.velocityZ += 0.001f * (world.random.nextGaussian() - 0.5f);

this.prevPosX = x;
this.prevPosY = y;
this.prevPosZ = z;

this.maxAge = 15;
this.scale *= 2;
this.setSprite(sprites.getSprite(world.random));
}

@Override
public void tick() {
this.prevPosX = this.x;
this.prevPosY = this.y;
this.prevPosZ = this.z;
if (this.age++ >= this.maxAge) {
this.markDead();
return;
}
this.alpha = 1f - (float) this.age / this.maxAge;

Vec3d pos = new Vec3d(this.x, this.y, this.z);
BlockState currBlock = this.world.getBlockState(WakesUtils.vecToBlockPos(pos));

if (currBlock.isAir()) {
this.velocityY -= 0.02f;
} else if (currBlock.isOf(Blocks.WATER)) {
this.velocityY += 0.01f;
}
this.velocityX *= 0.99f;
this.velocityY *= 0.5f;
this.velocityZ *= 0.99f;
this.move(velocityX, velocityY, velocityZ);
}

@Override
public ParticleTextureSheet getType() {
return ParticleTextureSheet.PARTICLE_SHEET_TRANSLUCENT;
}

public static class Factory implements ParticleFactory<DefaultParticleType> {
private final SpriteProvider sprites;

public Factory(SpriteProvider spriteSet) {
this.sprites = spriteSet;
}

@Nullable
@Override
public Particle createParticle(DefaultParticleType parameters, ClientWorld world, double x, double y, double z, double velocityX, double velocityY, double velocityZ) {
return new SplashCloudParticle(world, x, y, z, this.sprites, velocityX, velocityY, velocityZ);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@

import com.goby56.wakes.WakesClient;
import com.goby56.wakes.duck.ProducesWake;
import com.goby56.wakes.particle.SplashPlaneParticleType;
import com.goby56.wakes.particle.ModParticles;
import com.goby56.wakes.particle.WithOwnerParticleType;
import com.goby56.wakes.render.SplashPlaneRenderer;
import com.goby56.wakes.utils.WakesUtils;
import com.terraformersmc.modmenu.util.mod.Mod;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.particle.Particle;
import net.minecraft.client.particle.ParticleFactory;
import net.minecraft.client.particle.ParticleTextureSheet;
import net.minecraft.client.particle.SpriteProvider;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
Expand All @@ -25,22 +26,30 @@
import org.jetbrains.annotations.Nullable;

public class SplashPlaneParticle extends Particle {
RenderLayer wakeLayer;

Entity owner;
float yaw;
float prevYaw;
int ticksSinceSplash = 0;

protected SplashPlaneParticle(ClientWorld world, double x, double y, double z) {
super(world, x, y, z);
this.setMaxAge(60);
// this.setBoundingBoxSpacing(3, 0);
Identifier wakeTexture = new Identifier(WakesClient.MOD_ID, "textures/entity/wake_texture.png");
this.wakeLayer = RenderLayer.getEntityTranslucent(wakeTexture);
}

@Override
public void markDead() {
if (this.owner instanceof ProducesWake wakeOwner) {
wakeOwner.setWakeParticle(null);
}
this.owner = null;
super.markDead();
}

@Override
public void tick() {
if (!WakesClient.CONFIG_INSTANCE.renderSplashPlane) {
this.markDead();
}

this.prevPosX = this.x;
this.prevPosY = this.y;
this.prevPosZ = this.z;
Expand All @@ -55,20 +64,35 @@ public void tick() {
if (this.owner instanceof ProducesWake wakeOwner) {
if (!wakeOwner.onWaterSurface() || (this.owner instanceof PlayerEntity player && player.isSpectator())
|| this.owner.getVelocity().horizontalLength() < 1e-2) {
wakeOwner.setWakeParticle(null);
this.owner = null;
this.markDead();
} else {
Vec3d vel = this.owner.getVelocity();
this.yaw = 90 - (float) (180 / Math.PI * Math.atan2(vel.z, vel.x));
Vec3d ownerPos = this.owner.getPos().add(vel.normalize().multiply(this.owner.getWidth()));
this.setPos(ownerPos.x, wakeOwner.producingHeight(), ownerPos.z);
this.aliveTick(wakeOwner);
}
} else {
this.markDead();
}
}

private void aliveTick(ProducesWake wakeProducer) {
this.ticksSinceSplash++;

Vec3d vel = this.owner.getVelocity();
this.yaw = 90 - (float) (180 / Math.PI * Math.atan2(vel.z, vel.x));
Vec3d normVel = vel.normalize();
Vec3d planePos = this.owner.getPos().add(normVel.multiply(this.owner.getWidth() + WakesClient.CONFIG_INSTANCE.splashPlaneOffset));
this.setPos(planePos.x, wakeProducer.producingHeight(), planePos.z);

int t = (int) Math.floor(WakesClient.CONFIG_INSTANCE.maxSplashPlaneVelocity / vel.horizontalLength());
if (this.ticksSinceSplash > t && WakesClient.CONFIG_INSTANCE.spawnParticles) {
this.ticksSinceSplash = 0;
Vec3d particlePos = planePos.subtract(new Vec3d(normVel.x, 0f, normVel.z).multiply(this.owner.getWidth() / 2f));
Vec3d particleOffset = new Vec3d(-normVel.z, 0f, normVel.x).multiply(this.owner.getWidth() / 2f);
Vec3d pos = particlePos.add(particleOffset);
world.addParticle(ModParticles.SPLASH_CLOUD, pos.x, wakeProducer.producingHeight(), pos.z, vel.x, vel.y ,vel.z);
pos = particlePos.add(particleOffset.multiply(-1f));
world.addParticle(ModParticles.SPLASH_CLOUD, pos.x, wakeProducer.producingHeight(), pos.z, vel.x, vel.y ,vel.z);
}
}

@Override
public void buildGeometry(VertexConsumer vertexConsumer, Camera camera, float tickDelta) {
Expand Down Expand Up @@ -110,7 +134,7 @@ public Factory(SpriteProvider spriteSet) {
@Override
public Particle createParticle(DefaultParticleType parameters, ClientWorld world, double x, double y, double z, double velX, double velY, double velZ) {
SplashPlaneParticle wake = new SplashPlaneParticle(world, x, y, z);
if (parameters instanceof SplashPlaneParticleType type) {
if (parameters instanceof WithOwnerParticleType type) {
wake.owner = type.owner;
wake.yaw = wake.prevYaw = type.owner.getYaw();
((ProducesWake) wake.owner).setWakeParticle(wake);
Expand Down
Loading

0 comments on commit 6c1ec32

Please sign in to comment.