diff --git a/src/main/kotlin/com/bytedice/bde_particles/Bde_particles.kt b/src/main/kotlin/com/bytedice/bde_particles/Bde_particles.kt index 504964c..dcccf1f 100644 --- a/src/main/kotlin/com/bytedice/bde_particles/Bde_particles.kt +++ b/src/main/kotlin/com/bytedice/bde_particles/Bde_particles.kt @@ -5,12 +5,17 @@ import com.bytedice.bde_particles.commands.KillAllEmitters import com.bytedice.bde_particles.commands.ManageEmitters import com.bytedice.bde_particles.items.ParticleEmitterTool import com.bytedice.bde_particles.particles.* +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.runBlocking import net.fabricmc.api.EnvType import net.fabricmc.api.ModInitializer import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents import net.fabricmc.fabric.api.event.player.UseItemCallback +import net.fabricmc.fabric.api.gamerule.v1.GameRuleFactory +import net.fabricmc.fabric.api.gamerule.v1.GameRuleRegistry import net.fabricmc.loader.api.FabricLoader import net.minecraft.entity.EntityType import net.minecraft.entity.decoration.DisplayEntity.BlockDisplayEntity @@ -23,6 +28,7 @@ import net.minecraft.server.world.ServerWorld import net.minecraft.util.ActionResult import net.minecraft.util.Hand import net.minecraft.util.TypedActionResult +import net.minecraft.world.GameRules import net.minecraft.world.World import org.joml.Vector2f import java.util.* @@ -34,19 +40,9 @@ import java.util.* // force field "config" option // blockCurve array not needing "minecraft:" -// Adding a parameter for an initial velocity from an origin point (you currently have to use force fields) - // Parameters - // originOffset (Vec3f) - // velocity based, relative coordinate based, or rotation based - - // originOffsetCurve (curve) - // spawnOffset (Vec3f) - // velRot (bool) - // rotates the particle to face the velocity - - // spawnChance (Float) - // shape: spawnOnlyOnEdge (Boolean) + // rotWithVel / scaleWithVel (bool) + // rotates/scales the particle to face the velocity // Custom curve equation command args (parse from strings) // Cylinder and cone force field shape. @@ -59,7 +55,12 @@ import java.util.* var ALL_PARTICLE_EMITTERS: Array = emptyArray() -val sessionUuid: UUID = UUID.randomUUID() +val SESSION_UUID: UUID = UUID.randomUUID() +val GLOBAL_MAX_PARTICLES = GameRuleRegistry.register( + "GlobalMaxParticles", + GameRules.Category.UPDATES, + GameRuleFactory.createIntRule(3000, 0) +) class Bde_particles : ModInitializer { @@ -112,18 +113,23 @@ fun init() { fun tick(server: MinecraftServer) { - for (world in server.worlds) { - for (entity in world.iterateEntities()) { - if (entity.type == EntityType.BLOCK_DISPLAY) { - if (displayEntityContainsTag(entity as BlockDisplayEntity, "BPS_UUID") - && !displayEntityContainsTag(entity, sessionUuid.toString())) { + runBlocking { + server.worlds.map { world -> + async { + val blockDisplayEntities = world.iterateEntities() + .filter { it.type == EntityType.BLOCK_DISPLAY } + for (entity in blockDisplayEntities) { + if (displayEntityContainsTag(entity as BlockDisplayEntity, "BPS_UUID") + && !displayEntityContainsTag(entity, SESSION_UUID.toString())) { - entity.kill() + entity.kill() + } } } - } + }.awaitAll() } + for (emitter in ALL_PARTICLE_EMITTERS) { if (emitter.isDead) { ALL_PARTICLE_EMITTERS = ALL_PARTICLE_EMITTERS.toMutableList().apply { remove(emitter) }.toTypedArray() @@ -154,6 +160,7 @@ fun onRightClick(player: PlayerEntity, world: ServerWorld, hand: Hand) : TypedAc } +/* fun emitterParamsToJson(params: EmitterParams) : Map { // TODO: map to new params val allParamsJson: MutableList> = mutableListOf() @@ -161,7 +168,7 @@ fun emitterParamsToJson(params: EmitterParams) : Map { // TODO: map "shape" to params.shape ) - allParamsJson.add(paramsJson) + allParamsJson.add(paramsJson) val emitterParamsJSON = mapOf( "maxCount" to params.maxCount, @@ -169,6 +176,7 @@ fun emitterParamsToJson(params: EmitterParams) : Map { // TODO: map return emitterParamsJSON } +*/ fun displayEntityContainsTag(entity: BlockDisplayEntity, tag: String) : Boolean { diff --git a/src/main/kotlin/com/bytedice/bde_particles/Math.kt b/src/main/kotlin/com/bytedice/bde_particles/Math.kt index d4dfc75..23c43ab 100644 --- a/src/main/kotlin/com/bytedice/bde_particles/Math.kt +++ b/src/main/kotlin/com/bytedice/bde_particles/Math.kt @@ -170,18 +170,9 @@ fun lerp(x: Float, y: Float, t: Float) : Float { } -fun interpolateCurve(start: Vector3f, end: Vector3f, t: Float, curve: LerpCurves): Vector3f { - val clampedT = t.coerceIn(0.0f, 1.0f) - - val curvedT = curve.function(clampedT) - - val interpolatedVec = Vector3f( - lerp(start.x, end.x, curvedT), - lerp(start.y, end.y, curvedT), - lerp(start.z, end.z, curvedT) - ) - - return interpolatedVec +fun lerpArray(array: Array, t: Float, curve: LerpCurves = LerpCurves.Linear) : Any { + val idx = round(lerp(0.0f, array.lastIndex.toFloat(), t) * curve.function(t)).toInt() + return array[idx] } diff --git a/src/main/kotlin/com/bytedice/bde_particles/commands/ManageEmitters.kt b/src/main/kotlin/com/bytedice/bde_particles/commands/ManageEmitters.kt index 23bab20..fc45cd3 100644 --- a/src/main/kotlin/com/bytedice/bde_particles/commands/ManageEmitters.kt +++ b/src/main/kotlin/com/bytedice/bde_particles/commands/ManageEmitters.kt @@ -25,6 +25,37 @@ import java.util.concurrent.CompletableFuture // TODO: completely redesign +/* +// spawning +maxCount: Int, +spawnRate: Int, +spawnChance: Float, +spawnDuration: ParamClasses.Duration, +spawnPosOffset: Vector3f, +lifeTime: ParamClasses.PairInt, +shape: SpawningShape, +// init transforms +offset: ParamClasses.PairVec3f, +initRot: ParamClasses.PairVec3f, +rotVel: ParamClasses.PairVec3f, +rotWithVel: Boolean, +initVel: ParamClasses.PairVec3f, +initCenterVel: ParamClasses.PairFloat, +initScale: ParamClasses.PairVec3f, +scaleWithVel: Boolean, +// velocity +forceFields: Array, +constVel: Vector3f, +drag: Float, +minVel: Float, +// curves +offsetCurve: ParamClasses.LerpVal, +rotVelCurve: ParamClasses.LerpVal, +scaleCurve: ParamClasses.LerpVal, +blockCurve: Pair, LerpCurves>, +*/ + + // ManageEmitters // // create diff --git a/src/main/kotlin/com/bytedice/bde_particles/particles/EmitterParams.kt b/src/main/kotlin/com/bytedice/bde_particles/particles/EmitterParams.kt index f627997..bc1515e 100644 --- a/src/main/kotlin/com/bytedice/bde_particles/particles/EmitterParams.kt +++ b/src/main/kotlin/com/bytedice/bde_particles/particles/EmitterParams.kt @@ -4,32 +4,6 @@ import com.bytedice.bde_particles.LerpCurves import org.joml.Vector3f -/** - * EmitterParams defines the parameters for controlling how particles are emitted, - * including settings for looping, spawn frequency, and maximum particle count. - * - * @param maxCount The maximum number of particles that can exist at any time. If set to 0 or below, no particles will spawn. - * @param spawnRate The number of particles to spawn each tick. If set to 0 or below, no particles will spawn. - * @param spawnChance The chance for a particle to spawn. 0.5 means a particle has a 50% chance of spawning. Values are between 0.0 and 1.0. - * @param loopDur The duration, in ticks, that each emission loop lasts. A value of 0 or below makes the loop infinite, ignoring `loopDelay` and `loopCount`. - * @param loopDelay The delay, in ticks, between consecutive loops. Negative values are treated as 0 (no delay). - * @param loopCount The number of times the loop repeats. A value of 0 results in a single burst of particles. Values below 0 make the loop repeat indefinitely. - * @param shape The spawning shape for the particles, such as `SpawningShape.Sphere()`. - * @param initRot Specifies the initial random rotation range for the particles, in degrees, for the X, Y, and Z axes. - * @param rotVel Specifies the random range for rotational velocity, in degrees per tick, for the X, Y, and Z axes. - * @param rotTowardVel If true, particles will face the direction of velocity. It uses `initRot` as an offset rotation and ignores `rotVel`. - * @param initVel Specifies the random range for the initial velocity, in units per tick, for the X, Y, and Z axes. - * @param initScale Defines the random range for the initial particle size. - * @param scaleTowardVel If true, particles will scale to the direction of velocity. - * @param forceFields An array of force fields applied to particles, affecting their motion during their lifetime. - * @param constVel A constant velocity vector applied to particles each tick, useful for effects like gravity. - * @param drag A drag coefficient that slows down particle velocity over time. Calculated as `velocity * (1.0 - drag)`. - * @param minVel The minimum speed a particle must maintain. Particles slower than this value are stopped. Values <= 0 disable this check. - * @param lifeTime Specifies the range for particle lifetime, in ticks. Values below 1 prevent particles from spawning. - * @param rotVelCurve A curve defining how rotational velocity evolves during the particle's lifetime. Multiplies the `rotVel` values. - * @param scaleCurve A curve defining how the particle's size evolves over its lifetime. Multiplies the `initScale` values. - * @param blockCurve Specifies an array of block names that the particle transitions through during its lifetime, combined with a curve to control transitions. Defaults to "minecraft:air" if empty. - */ data class EmitterParams ( // spawning var maxCount: Int, @@ -68,7 +42,7 @@ data class EmitterParams ( spawnDuration = ParamClasses.Duration.SingleBurst(25), spawnPosOffset = Vector3f(0.0f, 0.0f, 0.0f), lifeTime = ParamClasses.PairInt(25, 40), - shape = SpawningShape.Circle(3.0f), + shape = SpawningShape.Circle(3.0f, false), offset = ParamClasses.PairVec3f.Uniform(-0.5f, -0.5f), initRot = ParamClasses.PairVec3f.Null, rotVel = ParamClasses.PairVec3f.NonUniform(-0.2f, -0.2f, -0.2f, 0.2f, 0.2f, 0.2f), diff --git a/src/main/kotlin/com/bytedice/bde_particles/particles/Particle.kt b/src/main/kotlin/com/bytedice/bde_particles/particles/Particle.kt index 7352d46..1603c0b 100644 --- a/src/main/kotlin/com/bytedice/bde_particles/particles/Particle.kt +++ b/src/main/kotlin/com/bytedice/bde_particles/particles/Particle.kt @@ -9,61 +9,77 @@ import org.joml.Vector4f import kotlin.math.* -// TODO: consider moving most params to emitter (such as shape, and forceFields) to store less data +// TODO: add logic for new params (goes for ParticleEmitter too) +/* Changed or new params (haven't been implemented yet) +rotWithVel: Boolean (NEW) +scaleWithVel: Boolean (NEW) +*/ -class Particle(private val emitterParams: EmitterParams) { - private val initOffset = Vector3f(-0.5f, -0.5f, -0.5f) - private var offset = Vector3f(0.0f, 0.0f, 0.0f) - private var pos = Vec3d(0.0, 0.0, 0.0) - private var entityRot = Vector2f(0.0f, 0.0f) +class Particle(private val emitterParams: EmitterParams, private var entityPos: Vec3d, private val entityRot: Vector2f) { + private var pos = Vector3f(0.0f, 0.0f, 0.0f) + private var blockDisplay: DisplayEntity = DisplayEntity(DisplayEntityProperties()) + private var timeAlive = 0 + private var isInit = false + var isDead = false - private var rot = emitterParams.initRot.randomize() - private val rotVel = emitterParams.rotVel.randomize() - private var vel = emitterParams.initVel.randomize() - - private val scaleAny: Any = when (emitterParams.initScale) { - is ParamClasses.RandScale.Uniform -> { (emitterParams.initScale as ParamClasses.RandScale.Uniform).randomize() } - is ParamClasses.RandScale.NonUniform -> { (emitterParams.initScale as ParamClasses.RandScale.NonUniform).randomize() } + // params + private val originOffset = when (val offset = emitterParams.offset) { + is ParamClasses.PairVec3f.Uniform -> offset.randomize() + is ParamClasses.PairVec3f.NonUniform -> offset.randomize() + ParamClasses.PairVec3f.Null -> Vector3f(0.0f, 0.0f, 0.0f) + } + + private var rot = when (val initRot = emitterParams.initRot) { + is ParamClasses.PairVec3f.Uniform -> initRot.randomize() + is ParamClasses.PairVec3f.NonUniform -> initRot.randomize() + ParamClasses.PairVec3f.Null -> Vector3f(0.0f, 0.0f, 0.0f) + } + + private var rotVel = when (val initRotVel = emitterParams.rotVel) { + is ParamClasses.PairVec3f.Uniform -> initRotVel.randomize() + is ParamClasses.PairVec3f.NonUniform -> initRotVel.randomize() + ParamClasses.PairVec3f.Null -> Vector3f(0.0f, 0.0f, 0.0f) + } + + private var vel = when (val initVel = emitterParams.initVel) { + is ParamClasses.PairVec3f.Uniform -> initVel.randomize() + is ParamClasses.PairVec3f.NonUniform -> initVel.randomize() + ParamClasses.PairVec3f.Null -> Vector3f(0.0f, 0.0f, 0.0f) + } + + private var scale = when (val initScale = emitterParams.initScale) { + is ParamClasses.PairVec3f.Uniform -> initScale.randomize() + is ParamClasses.PairVec3f.NonUniform -> initScale.randomize() + ParamClasses.PairVec3f.Null -> Vector3f(0.0f, 0.0f, 0.0f) } - private var scale = if (scaleAny is Vector3f) { scaleAny } else { Vector3f(scaleAny as Float, scaleAny, scaleAny) } - private var blockDisplay: DisplayEntity? = null private var lifeTime = emitterParams.lifeTime.randomize() - var isDead = false - private var timeAlive = 0 - private var isInit = false - - - fun init(pos: Vec3d, rot: Vector2f) { // TODO: replace initOffset with originOffset param - this.pos = pos - this.entityRot = rot + fun init() { + val so = emitterParams.spawnPosOffset + entityPos = entityPos.add(so.x.toDouble(), so.y.toDouble(), so.z.toDouble()) val shape = emitterParams.shape - val newSpawnPos: Vector3f + val newSpawnPos = shape.random(shape) - when (shape) { - is SpawningShape.Circle -> { val posInCircle = randomInCircle(shape.radius); newSpawnPos = Vector3f(posInCircle.x, 0.0f, posInCircle.y) } - is SpawningShape.Sphere -> { newSpawnPos = randomInSphere(shape.radius) } - is SpawningShape.Rect -> { val posInRect = randomInRect(shape.size); newSpawnPos = Vector3f(posInRect.x, 0.0f, posInRect.y) } - is SpawningShape.Cube -> { newSpawnPos = randomInCube(shape.size) } - is SpawningShape.Point -> { newSpawnPos = Vector3f(0.0f, 0.0f, 0.0f) } - } + pos = newSpawnPos - offset = newSpawnPos + val dirFromCenter = entityPos.subtract(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble()).normalize() + val forceFromCenter = dirFromCenter.multiply(emitterParams.initCenterVel.randomize().toDouble()) + vel.add(forceFromCenter.x.toFloat(), forceFromCenter.y.toFloat(), forceFromCenter.z.toFloat()) - val (quatRot, newOffset) = calcTransformOffset(this.rot, initOffset, scale) + val (quatRot, newPos) = calcTransformOffset(this.rot, originOffset, scale) val properties = DisplayEntityProperties( - pos = this.pos, - rot = rot, - blockType = emitterParams.blockCurve.first[0], // TODO: curve this - translation = newOffset.add(offset), + pos = entityPos, + rot = entityRot, + blockType = lerpArray(emitterParams.blockCurve.first as Array, 0.0f, emitterParams.blockCurve.second) as String, + translation = newPos.add(pos), leftRotation = quatRot, scale = scale, - tags = arrayOf("BPS_Particle", "BPS_UUID", sessionUuid.toString()) + tags = arrayOf("BPS_Particle", "BPS_UUID", SESSION_UUID.toString()) ) blockDisplay = DisplayEntity(properties) @@ -73,7 +89,7 @@ class Particle(private val emitterParams: EmitterParams) { fun spawn(world: ServerWorld) { - if (isInit) { blockDisplay?.spawn(world) } + if (isInit) { blockDisplay.spawn(world) } else { error("Particle is not initialized before being spawned. Please use Particle.init() to initialize it.") } } @@ -83,9 +99,9 @@ class Particle(private val emitterParams: EmitterParams) { val newProperties = calcNewProperties() - blockDisplay?.updateProperties(newProperties) + blockDisplay.updateProperties(newProperties) - if (timeAlive > lifeTime) { isDead = true; blockDisplay?.kill() } + if (timeAlive > lifeTime) { isDead = true; blockDisplay.kill() } timeAlive += 1 } @@ -105,52 +121,46 @@ class Particle(private val emitterParams: EmitterParams) { private fun calcNewProperties() : DisplayEntityProperties { - val timeAliveClamped = (timeAlive.toFloat() / lifeTime.toFloat()).coerceIn(0.0f, 1.0f) - val newBlock = calcBlockCurve(timeAliveClamped) - val (newVel, newOffset) = calcOffset() - val newRot = calcRot(timeAliveClamped) - val newScale = calcSize(timeAliveClamped) + val newBlock = lerpArray(emitterParams.blockCurve.first as Array, timeAliveClamped, emitterParams.blockCurve.second) as String + val (newVel, newPos) = calcPos() + val newRot = calcRot(timeAliveClamped) + val newScale = calcScale(timeAliveClamped) + val newOriginOffset = Vector3f(originOffset.mul(emitterParams.offsetCurve.lerpToVector3f(timeAliveClamped))) - vel = newVel - offset = newOffset - rot = newRot + vel = newVel + pos = newPos + rot = newRot for (forceField in emitterParams.forceFields) { if (forceField.shape is ForceFieldShape.Sphere) { vel.add(sphereSdfVel(forceField)) } else if (forceField.shape is ForceFieldShape.Cube) { vel.add(cubeSdfVel(forceField)) } } - val (quatRot, transformOffset) = calcTransformOffset(newRot, initOffset, newScale) - val combinedOffset = transformOffset.add(newOffset) + val (quatRot, transformOffset) = calcTransformOffset(newRot, originOffset, newScale) + val combinedOffset = transformOffset.add(newPos) val newProperties = DisplayEntityProperties( - pos = pos, + pos = entityPos, blockType = newBlock, translation = combinedOffset, leftRotation = quatRot, scale = newScale, - tags = arrayOf("BPS_Particle", "BPS_UUID", sessionUuid.toString()) + tags = arrayOf("BPS_Particle", "BPS_UUID", SESSION_UUID.toString()) ) return newProperties } - private fun calcBlockCurve(t: Float) : String { - val newBlockIdx = round(lerp(0.0f, emitterParams.blockCurve.first.lastIndex.toFloat(), t)).toInt() - return emitterParams.blockCurve.first[newBlockIdx] - } - - private fun sphereSdfVel(forceField: ForceField) : Vector3f { if (forceField.shape !is ForceFieldShape.Sphere) { return Vector3f(0.0f, 0.0f, 0.0f) } val shape = forceField.shape - val sdfVal = sdfSphere(forceField.pos, shape.radius, offset) + val sdfVal = sdfSphere(forceField.pos, shape.radius, pos) val normalizedSdf = normalizeSdf(sdfVal, shape.radius) - val velDir = Vector3f(offset).sub(forceField.pos).normalize() + val velDir = Vector3f(pos).sub(forceField.pos).normalize() val velMul = if (sdfVal > 0) { 0.0f } else { lerp(shape.force.second, shape.force.second, 1 - normalizedSdf) } @@ -162,7 +172,7 @@ class Particle(private val emitterParams: EmitterParams) { if (forceField.shape !is ForceFieldShape.Cube) { return Vector3f(0.0f, 0.0f, 0.0f) } val shape = forceField.shape - val sdfVal = sdfCube(forceField.pos, shape.size, offset) + val sdfVal = sdfCube(forceField.pos, shape.size, pos) val velDir = if (sdfVal < 0) { shape.dir.mul(shape.force) } else { Vector3f(0.0f, 0.0f, 0.0f) } @@ -181,7 +191,7 @@ class Particle(private val emitterParams: EmitterParams) { } - private fun calcOffset() : Pair { + private fun calcPos() : Pair { var vel = Vector3f(vel) vel = vel.add(emitterParams.constVel) vel = vel.mul(1.0f - emitterParams.drag) @@ -190,13 +200,13 @@ class Particle(private val emitterParams: EmitterParams) { if (abs(vel.y) < emitterParams.minVel) { vel.y = 0.0f } if (abs(vel.z) < emitterParams.minVel) { vel.z = 0.0f } - var newOffset = Vector3f(offset) - newOffset = newOffset.add(vel) - return Pair(vel, newOffset) + var newPos = Vector3f(pos) + newPos = newPos.add(vel) + return Pair(vel, newPos) } - private fun calcSize(t: Float) : Vector3f { + private fun calcScale(t: Float) : Vector3f { val newScale = Vector3f(scale) newScale.mul(emitterParams.scaleCurve.lerpToVector3f(t)) return scale @@ -204,6 +214,6 @@ class Particle(private val emitterParams: EmitterParams) { fun kill() { - if (!isDead) { blockDisplay?.kill() } + if (!isDead) { blockDisplay.kill() } } } \ No newline at end of file diff --git a/src/main/kotlin/com/bytedice/bde_particles/particles/ParticleEmitter.kt b/src/main/kotlin/com/bytedice/bde_particles/particles/ParticleEmitter.kt index 11a9f01..7305cc4 100644 --- a/src/main/kotlin/com/bytedice/bde_particles/particles/ParticleEmitter.kt +++ b/src/main/kotlin/com/bytedice/bde_particles/particles/ParticleEmitter.kt @@ -1,19 +1,18 @@ package com.bytedice.bde_particles.particles +import com.bytedice.bde_particles.randomFloatBetween import net.minecraft.server.world.ServerWorld import net.minecraft.util.math.Vec3d import org.joml.Vector2f -import org.joml.Vector3f class ParticleEmitter(private val pos: Vec3d, private val rot: Vector2f, private val world: ServerWorld, private val params: EmitterParams) { private var allParticles: Array = emptyArray() private var timeAlive = 0 - private var loopCount = 0 - private var loopDelay = 0 - private var loopDur = 0 - private var loopDelayActive = false + private var timePaused = 0 + private var timesLooped = 0 + private var isPaused = false private var isTicking = true @@ -33,13 +32,16 @@ class ParticleEmitter(private val pos: Vec3d, private val rot: Vector2f, private private fun addParticle() { - if (loopDelayActive) { return } + if (isPaused) { return } repeat(params.spawnRate) { if (allParticles.size >= params.maxCount) { return } - val particle = Particle(params) - particle.init(pos, rot) + val randomSpawnChance = randomFloatBetween(0.0f, params.spawnChance.coerceIn(0.0f, 1.0f)) + if (randomSpawnChance > params.spawnChance) { return } + + val particle = Particle(params, pos, rot) + particle.init() particle.spawn(world) allParticles += particle @@ -48,44 +50,30 @@ class ParticleEmitter(private val pos: Vec3d, private val rot: Vector2f, private private fun count() { - // this is the only section I have comments - // because im so damn good at cooking spaghetti + if (params.spawnDuration is ParamClasses.Duration.SingleBurst) { + val loopDur = (params.spawnDuration as ParamClasses.Duration.SingleBurst).loopDur + if (timeAlive >= loopDur) { isTicking = false; return } - // if max duration is 0 or less, skip counting, its infinite - if (params.loopDur <= 0) { return } - - // if the duration is greater than max duration, add 1 loopCount - // return if max loopCount is 0 - // if the delay is greater than 0 then enable it - if (loopDur >= params.loopDur) { - if (loopCount == 0) { - isTicking = false - return - } - loopCount += 1 - loopDur = 0 - if (params.loopDelay > 0) { loopDelayActive = true } + timeAlive += 1 } - // if the delay is greater than max delay, disable the delay - else if (loopDelay >= params.loopDelay) { - loopDelay = 0 - loopDelayActive = false + else if (params.spawnDuration is ParamClasses.Duration.MultiBurst) { + val loopDur = (params.spawnDuration as ParamClasses.Duration.MultiBurst).loopDur + val loopDelay = (params.spawnDuration as ParamClasses.Duration.MultiBurst).loopDelay + val loopCount = (params.spawnDuration as ParamClasses.Duration.MultiBurst).loopCount + + if (timeAlive >= loopDur && timesLooped >= loopCount) { isTicking = false; return } + + if (timePaused >= loopDelay) { isPaused = false; timePaused = 0 } + if (timeAlive >= loopDur && loopDelay > 0) { isPaused = true; timeAlive = 0 } + + if (!isPaused) { timeAlive += 1 } + else { timePaused += 1 } } - // if loopCount is greater than max loopCount then stop immediately - // if max loopCount is below 0 then don't do anything, loop infinitely - if (loopCount > 0 && loopCount >= params.loopCount) { - isTicking = false + else if (params.spawnDuration is ParamClasses.Duration.InfiniteLoop) { return } - - // if delay isn't active, add 1 to duration - // if delay is active, add 1 to delay - if (!loopDelayActive) { loopDur += 1 } - else { loopDelay += 1 } - - timeAlive += 1 } @@ -97,4 +85,7 @@ class ParticleEmitter(private val pos: Vec3d, private val rot: Vector2f, private isTicking = false isDead = true } + fun stopTicking() { + isTicking = false + } } \ No newline at end of file diff --git a/src/main/kotlin/com/bytedice/bde_particles/particles/ParticleIdRegister.kt b/src/main/kotlin/com/bytedice/bde_particles/particles/ParticleIdRegister.kt index 0c40cf0..5bb2302 100644 --- a/src/main/kotlin/com/bytedice/bde_particles/particles/ParticleIdRegister.kt +++ b/src/main/kotlin/com/bytedice/bde_particles/particles/ParticleIdRegister.kt @@ -1,5 +1,3 @@ -@file:Suppress("UNCHECKED_CAST") - package com.bytedice.bde_particles.particles @@ -10,9 +8,13 @@ val forbiddenIds = arrayOf("", "NULL") fun addToRegister(id: String, params: EmitterParams) : Pair { val newId = replaceForbiddenChars(id.replace(" ", "_")) - if (idRegister.containsKey(newId)) { return Pair(newId, false) } + if (idRegister.containsKey(newId)) { + println("BPS - Failed to register Emitter ID \"$newId\" as it already exists!") + return Pair(newId, false) + } idRegister[id] = params + println("BPS - Registered Emitter ID \"$newId\".") return Pair(newId, true) } diff --git a/src/main/kotlin/com/bytedice/bde_particles/particles/Shapes.kt b/src/main/kotlin/com/bytedice/bde_particles/particles/Shapes.kt index 8775d9e..8949e7e 100644 --- a/src/main/kotlin/com/bytedice/bde_particles/particles/Shapes.kt +++ b/src/main/kotlin/com/bytedice/bde_particles/particles/Shapes.kt @@ -7,8 +7,8 @@ import org.joml.Vector3f import kotlin.math.* import kotlin.random.Random -sealed class SpawningShape { - class Circle(val radius: Float = 1.0f) : SpawningShape() { +sealed class SpawningShape() { + class Circle(private val radius: Float, val spawnOnEdge: Boolean) : SpawningShape() { fun randomWithin(): Vector3f { val randRadius = radius * sqrt(randomFloatBetween(0.0f, 1.0f)) val randAngle = randomFloatBetween(0.0f, Math.PI.toFloat() * 2) @@ -23,7 +23,7 @@ sealed class SpawningShape { } } - class Rect(val size: Vector2f = Vector2f(1.0f, 1.0f)) : SpawningShape() { + class Rect(private val size: Vector2f, val spawnOnEdge: Boolean) : SpawningShape() { fun randomWithin(): Vector3f { return Vector3f(randomFloatBetween(0.0f, size.x), 0.0f, randomFloatBetween(0.0f, size.y)) } @@ -41,7 +41,7 @@ sealed class SpawningShape { } } - class Cube(val size: Vector3f = Vector3f(1.0f, 1.0f, 1.0f)) : SpawningShape() { + class Cube(private val size: Vector3f, val spawnOnEdge: Boolean) : SpawningShape() { fun randomWithin(): Vector3f { return Vector3f( randomFloatBetween(0.0f, size.x), @@ -73,7 +73,7 @@ sealed class SpawningShape { } } - class Sphere(val radius: Float = 1.0f) : SpawningShape() { + class Sphere(private val radius: Float, val spawnOnEdge: Boolean) : SpawningShape() { fun randomWithin(): Vector3f { val randRadius = radius * Random.nextDouble().pow(1.0 / 3.0) @@ -103,6 +103,16 @@ sealed class SpawningShape { } data object Point : SpawningShape() + + fun random(shape: SpawningShape) : Vector3f { + return when (shape) { + is Circle -> if (!shape.spawnOnEdge) { shape.randomWithin() } else { shape.randomOnEdge() } + is Sphere -> if (!shape.spawnOnEdge) { shape.randomWithin() } else { shape.randomOnEdge() } + is Rect -> if (!shape.spawnOnEdge) { shape.randomWithin() } else { shape.randomOnEdge() } + is Cube -> if (!shape.spawnOnEdge) { shape.randomWithin() } else { shape.randomOnEdge() } + is Point -> Vector3f(0.0f, 0.0f, 0.0f) + } + } }