Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2423efbd92 | ||
|
|
46498d25f4 | ||
|
|
c416432f7b | ||
|
|
a7212dbe42 | ||
|
|
fdea77092b | ||
|
|
22027610ea | ||
|
|
4c051ec292 | ||
|
|
adfa1b7bf2 | ||
|
|
45ff607f6a | ||
|
|
3ff032db1d | ||
|
|
3b47ad870f | ||
|
|
ddd78dcef4 | ||
|
|
b1832825a8 | ||
|
|
3e08bd4d81 | ||
|
|
7716ba30f8 | ||
|
|
5c1b1b07ce | ||
|
|
46cc808889 | ||
|
|
0b17f8688c | ||
|
|
ad9ef87874 | ||
|
|
e2049fd204 | ||
|
|
6eea92033d | ||
|
|
13113e4c18 | ||
|
|
c5cc910be3 | ||
|
|
2edb23e9ec | ||
|
|
828297aef2 | ||
|
|
8ad6cb9ffb | ||
|
|
be00598efe | ||
|
|
939867934b | ||
|
|
6728445280 | ||
|
|
5b43c12c15 | ||
|
|
af7d29ce7d |
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us squash pests
|
||||||
title: ''
|
title: ''
|
||||||
labels: ''
|
labels: bug
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -10,6 +10,9 @@ assignees: ''
|
|||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**Minecraft version**
|
||||||
|
Provide your Minecraft version, even if the mod is only for a single version.
|
||||||
|
|
||||||
**To Reproduce**
|
**To Reproduce**
|
||||||
Steps to reproduce the behavior:
|
Steps to reproduce the behavior:
|
||||||
1. Go to '...'
|
1. Go to '...'
|
||||||
@@ -23,16 +26,5 @@ A clear and concise description of what you expected to happen.
|
|||||||
**Screenshots**
|
**Screenshots**
|
||||||
If applicable, add screenshots to help explain your problem.
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
|
||||||
- OS: [e.g. iOS]
|
|
||||||
- Browser [e.g. chrome, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Smartphone (please complete the following information):**
|
|
||||||
- Device: [e.g. iPhone6]
|
|
||||||
- OS: [e.g. iOS8.1]
|
|
||||||
- Browser [e.g. stock browser, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
Add any other context about the problem here.
|
Add any other context about the problem here.
|
||||||
|
|||||||
@@ -2,19 +2,19 @@
|
|||||||
name: Feature request
|
name: Feature request
|
||||||
about: Suggest an idea for this project
|
about: Suggest an idea for this project
|
||||||
title: ''
|
title: ''
|
||||||
labels: ''
|
labels: enhancement
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
**Is your feature request related to a problem?**
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
**Describe the result you'd like**
|
||||||
A clear and concise description of what you want to happen.
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
**Describe alternatives you've considered**
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
A clear and concise description of any alternative features you've considered.
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
Add any other context or screenshots about the feature request here.
|
Add any other context or screenshots about the feature request here.
|
||||||
|
|||||||
@@ -3,8 +3,15 @@
|
|||||||
|
|
||||||
BDE_ParticleSys is a highly customizable **server-side** particle system that uses `Block Display Entities` (BDEs) as particles. It is meant to be used as a library for other mods, but can be used as a standalone mod too.
|
BDE_ParticleSys is a highly customizable **server-side** particle system that uses `Block Display Entities` (BDEs) as particles. It is meant to be used as a library for other mods, but can be used as a standalone mod too.
|
||||||
|
|
||||||
<!-- change GiveParticleEmitter to GiveEmitterTool -->
|
If BDE_ParticleSys is used as a standalone mod, you will only be able to use it with the custom in-game commands `/GiveEmitterTool`, `/KillAllEmitters`, and `/ManageEmitters`. Any particles made, removed, or changed with those commands are **temporary** and get reverted once the server shuts down, as they are meant for quick testing.
|
||||||
If BDE_ParticleSys is used as a standalone mod, you will only be able to use it with the custom in-game commands `/GiveEmitterTool`, `/KillAllEmitters` and `/ManageEmitters`. Any particles made with those commands are **temporary** and get removed once the server shuts down, as they are meant for quick testing.
|
|
||||||
|
### In-game commands (does not save):
|
||||||
|
* `/GiveEmitterTool`
|
||||||
|
* `/SpawnEmitter`
|
||||||
|
* `/KillAllEmitters`
|
||||||
|
* `/ManageEmitters`
|
||||||
|
* `GlobalMaxParticles` (GameRule)
|
||||||
|
* `ShowParticleDebug` (GameRule)
|
||||||
|
|
||||||
# Open-source - Copyright
|
# Open-source - Copyright
|
||||||
|
|
||||||
@@ -15,4 +22,4 @@ If BDE_ParticleSys is used as a standalone mod, you will only be able to use it
|
|||||||
### All particles showcased here are made with BDE_ParticleSys and without any added logic.
|
### All particles showcased here are made with BDE_ParticleSys and without any added logic.
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.bytedice.bde_particles.client
|
|||||||
import com.bytedice.bde_particles.commands.GiveEmitterTool
|
import com.bytedice.bde_particles.commands.GiveEmitterTool
|
||||||
import com.bytedice.bde_particles.commands.KillAllEmitters
|
import com.bytedice.bde_particles.commands.KillAllEmitters
|
||||||
import com.bytedice.bde_particles.commands.ManageEmitters
|
import com.bytedice.bde_particles.commands.ManageEmitters
|
||||||
|
import com.bytedice.bde_particles.commands.SpawnEmitter
|
||||||
|
//import com.bytedice.bde_particles.commands.ManageEmitters
|
||||||
import com.bytedice.bde_particles.init
|
import com.bytedice.bde_particles.init
|
||||||
import com.bytedice.bde_particles.onRightClick
|
import com.bytedice.bde_particles.onRightClick
|
||||||
import com.bytedice.bde_particles.tick
|
import com.bytedice.bde_particles.tick
|
||||||
@@ -38,8 +40,9 @@ class Bde_particlesClient : ClientModInitializer {
|
|||||||
|
|
||||||
CommandRegistrationCallback.EVENT.register { dispatcher, registryAccess, _ ->
|
CommandRegistrationCallback.EVENT.register { dispatcher, registryAccess, _ ->
|
||||||
GiveEmitterTool.register(dispatcher, registryAccess)
|
GiveEmitterTool.register(dispatcher, registryAccess)
|
||||||
ManageEmitters .register(dispatcher)
|
ManageEmitters .register(dispatcher, registryAccess)
|
||||||
KillAllEmitters.register(dispatcher)
|
KillAllEmitters.register(dispatcher)
|
||||||
|
SpawnEmitter .register(dispatcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
UseItemCallback.EVENT.register(UseItemCallback { player: PlayerEntity, world: World, hand: Hand ->
|
UseItemCallback.EVENT.register(UseItemCallback { player: PlayerEntity, world: World, hand: Hand ->
|
||||||
|
|||||||
@@ -3,35 +3,80 @@ package com.bytedice.bde_particles
|
|||||||
import com.bytedice.bde_particles.commands.GiveEmitterTool
|
import com.bytedice.bde_particles.commands.GiveEmitterTool
|
||||||
import com.bytedice.bde_particles.commands.KillAllEmitters
|
import com.bytedice.bde_particles.commands.KillAllEmitters
|
||||||
import com.bytedice.bde_particles.commands.ManageEmitters
|
import com.bytedice.bde_particles.commands.ManageEmitters
|
||||||
|
import com.bytedice.bde_particles.commands.SpawnEmitter
|
||||||
import com.bytedice.bde_particles.items.ParticleEmitterTool
|
import com.bytedice.bde_particles.items.ParticleEmitterTool
|
||||||
import com.bytedice.bde_particles.particles.*
|
import com.bytedice.bde_particles.particles.*
|
||||||
|
import com.mojang.brigadier.arguments.StringArgumentType
|
||||||
|
import com.mojang.brigadier.builder.RequiredArgumentBuilder
|
||||||
|
import com.mojang.brigadier.context.CommandContext
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import net.fabricmc.api.EnvType
|
import net.fabricmc.api.EnvType
|
||||||
import net.fabricmc.api.ModInitializer
|
import net.fabricmc.api.ModInitializer
|
||||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback
|
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.ServerLifecycleEvents
|
||||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents
|
||||||
import net.fabricmc.fabric.api.event.player.UseItemCallback
|
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.fabricmc.loader.api.FabricLoader
|
||||||
import net.minecraft.entity.EntityType
|
import net.minecraft.entity.EntityType
|
||||||
import net.minecraft.entity.decoration.DisplayEntity.BlockDisplayEntity
|
import net.minecraft.entity.decoration.DisplayEntity.ItemDisplayEntity
|
||||||
import net.minecraft.entity.player.PlayerEntity
|
import net.minecraft.entity.player.PlayerEntity
|
||||||
import net.minecraft.item.ItemStack
|
import net.minecraft.item.ItemStack
|
||||||
import net.minecraft.nbt.NbtCompound
|
import net.minecraft.nbt.NbtCompound
|
||||||
import net.minecraft.server.MinecraftServer
|
import net.minecraft.server.MinecraftServer
|
||||||
|
import net.minecraft.server.command.CommandManager
|
||||||
|
import net.minecraft.server.command.ServerCommandSource
|
||||||
import net.minecraft.server.network.ServerPlayerEntity
|
import net.minecraft.server.network.ServerPlayerEntity
|
||||||
import net.minecraft.server.world.ServerWorld
|
import net.minecraft.server.world.ServerWorld
|
||||||
|
import net.minecraft.text.Style
|
||||||
|
import net.minecraft.text.Text
|
||||||
import net.minecraft.util.ActionResult
|
import net.minecraft.util.ActionResult
|
||||||
import net.minecraft.util.Hand
|
import net.minecraft.util.Hand
|
||||||
import net.minecraft.util.TypedActionResult
|
import net.minecraft.util.TypedActionResult
|
||||||
|
import net.minecraft.world.GameRules
|
||||||
import net.minecraft.world.World
|
import net.minecraft.world.World
|
||||||
|
import org.joml.Vector2f
|
||||||
|
import java.awt.Color
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import kotlin.reflect.full.companionObject
|
||||||
|
import kotlin.reflect.full.companionObjectInstance
|
||||||
|
import kotlin.reflect.full.declaredMemberProperties
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: (future additions)
|
||||||
|
// Cylinder and cone force field shape.
|
||||||
|
// Copying particle params in-game.
|
||||||
|
// Custom curve equation command args (parse from strings).
|
||||||
|
// Force field "config" option.
|
||||||
|
// add rotation offset to transformWithVel
|
||||||
|
|
||||||
|
// TODO: (now)
|
||||||
|
// nothing lmao
|
||||||
|
|
||||||
|
|
||||||
var ALL_PARTICLE_EMITTERS: Array<ParticleEmitter> = emptyArray()
|
var ALL_PARTICLE_EMITTERS: Array<ParticleEmitter> = emptyArray()
|
||||||
val sessionUuid: UUID = UUID.randomUUID()
|
var LIVING_PARTICLE_COUNT: Int = 0
|
||||||
|
val SESSION_UUID: UUID = UUID.randomUUID()
|
||||||
|
|
||||||
|
|
||||||
class Bde_particles : ModInitializer {
|
class Bde_particles : ModInitializer {
|
||||||
|
companion object {
|
||||||
|
val GLOBAL_MAX_PARTICLES: GameRules.Key<GameRules.IntRule> = GameRuleRegistry.register(
|
||||||
|
"GlobalMaxParticles",
|
||||||
|
GameRules.Category.UPDATES,
|
||||||
|
GameRuleFactory.createIntRule(3000, 0)
|
||||||
|
)
|
||||||
|
val SHOW_PARTICLE_DEBUG: GameRules.Key<GameRules.BooleanRule> = GameRuleRegistry.register(
|
||||||
|
"ShowParticleDebug",
|
||||||
|
GameRules.Category.UPDATES,
|
||||||
|
GameRuleFactory.createBooleanRule(false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onInitialize() {
|
override fun onInitialize() {
|
||||||
if (FabricLoader.getInstance().environmentType != EnvType.SERVER) {
|
if (FabricLoader.getInstance().environmentType != EnvType.SERVER) {
|
||||||
return
|
return
|
||||||
@@ -39,18 +84,15 @@ class Bde_particles : ModInitializer {
|
|||||||
|
|
||||||
println("BPS - Initializing on ${FabricLoader.getInstance().environmentType}")
|
println("BPS - Initializing on ${FabricLoader.getInstance().environmentType}")
|
||||||
|
|
||||||
ServerLifecycleEvents.SERVER_STARTED.register { _ ->
|
|
||||||
init()
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerTickEvents.START_SERVER_TICK.register { server ->
|
ServerTickEvents.START_SERVER_TICK.register { server ->
|
||||||
tick(server)
|
tick(server)
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandRegistrationCallback.EVENT.register { dispatcher, registryAccess, _ ->
|
CommandRegistrationCallback.EVENT.register { dispatcher, registryAccess, _ ->
|
||||||
GiveEmitterTool.register(dispatcher, registryAccess)
|
GiveEmitterTool.register(dispatcher, registryAccess)
|
||||||
ManageEmitters .register(dispatcher)
|
ManageEmitters .register(dispatcher, registryAccess)
|
||||||
KillAllEmitters.register(dispatcher)
|
KillAllEmitters.register(dispatcher)
|
||||||
|
SpawnEmitter .register(dispatcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
UseItemCallback.EVENT.register(UseItemCallback { player: PlayerEntity, world: World, hand: Hand ->
|
UseItemCallback.EVENT.register(UseItemCallback { player: PlayerEntity, world: World, hand: Hand ->
|
||||||
@@ -61,6 +103,8 @@ class Bde_particles : ModInitializer {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ServerLifecycleEvents.SERVER_STARTED.register(ServerLifecycleEvents.ServerStarted { _ ->
|
ServerLifecycleEvents.SERVER_STARTED.register(ServerLifecycleEvents.ServerStarted { _ ->
|
||||||
|
init()
|
||||||
|
|
||||||
println("BPS - Progress bar filled :3")
|
println("BPS - Progress bar filled :3")
|
||||||
println(
|
println(
|
||||||
"\n____________ _____ _____ \n" +
|
"\n____________ _____ _____ \n" +
|
||||||
@@ -76,30 +120,40 @@ class Bde_particles : ModInitializer {
|
|||||||
|
|
||||||
|
|
||||||
fun init() {
|
fun init() {
|
||||||
addToEmitterRegister("DEFAULT", EmitterParams.DEFAULT)
|
addToRegister("DEFAULT", EmitterParams.DEFAULT)
|
||||||
addToEmitterRegister("FIRE_GEYSER", EmitterParams.FIRE_GEYSER)
|
addToRegister("DEBUG", EmitterParams.DEBUG)
|
||||||
addToEmitterRegister("RING_EXPLOSION", EmitterParams.RING_EXPLOSION)
|
addToRegister("STRESS_TEST", EmitterParams.STRESS_TEST)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun tick(server: MinecraftServer) {
|
fun tick(server: MinecraftServer) {
|
||||||
for (world in server.worlds) {
|
runBlocking {
|
||||||
for (entity in world.iterateEntities()) {
|
server.worlds.map { world ->
|
||||||
if (entity.type == EntityType.BLOCK_DISPLAY) {
|
async {
|
||||||
if (displayEntityContainsTag(entity as BlockDisplayEntity, "BPS_UUID")
|
val displayEntities = world.iterateEntities()
|
||||||
&& !displayEntityContainsTag(entity, sessionUuid.toString())) {
|
.filter { it.type == EntityType.ITEM_DISPLAY }
|
||||||
|
for (entity in displayEntities) {
|
||||||
|
if (displayEntityContainsTag(entity as ItemDisplayEntity, "BPS_UUID")
|
||||||
|
&& !displayEntityContainsTag(entity, SESSION_UUID.toString())) {
|
||||||
|
|
||||||
entity.kill()
|
entity.kill()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}.awaitAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
for (emitter in ALL_PARTICLE_EMITTERS) {
|
runBlocking {
|
||||||
if (emitter.isDead) {
|
ALL_PARTICLE_EMITTERS.map { emitter ->
|
||||||
ALL_PARTICLE_EMITTERS = ALL_PARTICLE_EMITTERS.toMutableList().apply { remove(emitter) }.toTypedArray()
|
async {
|
||||||
}
|
if (emitter.isDead) {
|
||||||
else { emitter.tick() }
|
synchronized(ALL_PARTICLE_EMITTERS) {
|
||||||
|
ALL_PARTICLE_EMITTERS = ALL_PARTICLE_EMITTERS.toMutableList().apply { remove(emitter) }.toTypedArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { emitter.tick() }
|
||||||
|
}
|
||||||
|
}.awaitAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +162,7 @@ fun onRightClick(player: PlayerEntity, world: ServerWorld, hand: Hand) : TypedAc
|
|||||||
val handItem = player.getStackInHand(hand)
|
val handItem = player.getStackInHand(hand)
|
||||||
val (isEmitterTool, emitterId) = ParticleEmitterTool.getToolDetails(handItem)
|
val (isEmitterTool, emitterId) = ParticleEmitterTool.getToolDetails(handItem)
|
||||||
val hitResult = raycastFromPlayer(player as ServerPlayerEntity, 200.0)
|
val hitResult = raycastFromPlayer(player as ServerPlayerEntity, 200.0)
|
||||||
val emitterParams = getEmitterParams(emitterId)
|
val emitterParams = getEmitterDataById(emitterId)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!isEmitterTool
|
!isEmitterTool
|
||||||
@@ -118,53 +172,77 @@ fun onRightClick(player: PlayerEntity, world: ServerWorld, hand: Hand) : TypedAc
|
|||||||
return TypedActionResult(ActionResult.PASS, handItem)
|
return TypedActionResult(ActionResult.PASS, handItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
val emitter = ParticleEmitter(hitResult.pos, world, emitterParams)
|
val debug = world.gameRules.getBoolean(Bde_particles.SHOW_PARTICLE_DEBUG)
|
||||||
ALL_PARTICLE_EMITTERS += emitter
|
|
||||||
|
val emitter = ParticleEmitter(
|
||||||
|
hitResult.pos,
|
||||||
|
Vector2f(0.0f, 0.0f),
|
||||||
|
world,
|
||||||
|
emitterParams,
|
||||||
|
debug
|
||||||
|
)
|
||||||
|
spawnEmitter(emitter)
|
||||||
|
|
||||||
return TypedActionResult(ActionResult.PASS, handItem)
|
return TypedActionResult(ActionResult.PASS, handItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun emitterParamsToJson(params: EmitterParams) : Map<String, Any> {
|
fun displayEntityContainsTag(entity: ItemDisplayEntity, tag: String) : Boolean {
|
||||||
val allParticleParamsJSON: MutableList<Map<String, Any?>> = mutableListOf()
|
|
||||||
val particle = params.particle
|
|
||||||
|
|
||||||
val particleParamsJSON = mapOf(
|
|
||||||
"shape" to particle.shape,
|
|
||||||
"blockCurve" to particle.blockCurve,
|
|
||||||
"rotRandom" to particle.rotRandom,
|
|
||||||
"rotVelRandom" to particle.rotVelRandom,
|
|
||||||
"rotVelCurve" to particle.rotVelCurve,
|
|
||||||
"sizeRandom" to particle.sizeRandom,
|
|
||||||
"uniformSize" to particle.uniformSize,
|
|
||||||
"sizeCurve" to particle.sizeCurve,
|
|
||||||
"velRandom" to particle.velRandom,
|
|
||||||
"forceFields" to particle.forceFields,
|
|
||||||
"gravity" to particle.gravity,
|
|
||||||
"drag" to particle.drag,
|
|
||||||
"minVel" to particle.minVel,
|
|
||||||
"lifeTime" to particle.lifeTime
|
|
||||||
)
|
|
||||||
|
|
||||||
allParticleParamsJSON.add(particleParamsJSON)
|
|
||||||
|
|
||||||
val emitterParamsJSON = mapOf(
|
|
||||||
"maxCount" to params.maxCount,
|
|
||||||
"spawnsPerTick" to params.spawnsPerTick,
|
|
||||||
"loopDur" to params.loopDur,
|
|
||||||
"loopDelay" to params.loopDelay,
|
|
||||||
"loopCount" to params.loopCount,
|
|
||||||
"particleTypes" to allParticleParamsJSON
|
|
||||||
)
|
|
||||||
|
|
||||||
return emitterParamsJSON
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun displayEntityContainsTag(entity: BlockDisplayEntity, tag: String) : Boolean {
|
|
||||||
val nbt = NbtCompound()
|
val nbt = NbtCompound()
|
||||||
entity.writeNbt(nbt)
|
entity.writeNbt(nbt)
|
||||||
val tagsNbtList = nbt.getList("Tags", 8)
|
val tagsNbtList = nbt.getList("Tags", 8)
|
||||||
|
|
||||||
return tagsNbtList?.any { it.asString() == tag } == true
|
return tagsNbtList?.any { it.asString() == tag } == true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun emitterListArg(argName: String) : RequiredArgumentBuilder<ServerCommandSource, String> {
|
||||||
|
return CommandManager.argument(argName, StringArgumentType.string())
|
||||||
|
.suggests { _, builder ->
|
||||||
|
idRegister.keys.forEach { key ->
|
||||||
|
builder.suggest(key)
|
||||||
|
}
|
||||||
|
CompletableFuture.completedFuture(builder.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun curveListArg(argName: String): RequiredArgumentBuilder<ServerCommandSource, String> {
|
||||||
|
return CommandManager.argument(argName, StringArgumentType.string())
|
||||||
|
.suggests { _, builder ->
|
||||||
|
LerpCurves.Companion::class.members
|
||||||
|
.filterIsInstance<kotlin.reflect.KProperty1<LerpCurves.Companion, *>>()
|
||||||
|
.filter { it.returnType.classifier == LerpCurves::class }
|
||||||
|
.forEach { property ->
|
||||||
|
builder.suggest(property.name)
|
||||||
|
}
|
||||||
|
CompletableFuture.completedFuture(builder.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun stringToCurve(curveName: String): LerpCurves? {
|
||||||
|
val companion = LerpCurves::class.companionObjectInstance
|
||||||
|
val properties = LerpCurves::class.companionObject?.declaredMemberProperties
|
||||||
|
|
||||||
|
return properties?.firstOrNull { it.name == curveName }?.getter?.call(companion) as? LerpCurves
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun spawnEmitter(emitter: ParticleEmitter) {
|
||||||
|
ALL_PARTICLE_EMITTERS += emitter
|
||||||
|
}
|
||||||
|
|
||||||
|
fun positiveFeedback(text: String, context: CommandContext<ServerCommandSource>) {
|
||||||
|
val feedback = Text.literal("BPS - $text")
|
||||||
|
.setStyle(Style.EMPTY.withColor(Color(0, 200, 0).rgb))
|
||||||
|
|
||||||
|
context.source.sendFeedback({ feedback }, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun negativeFeedback(text: String, context: CommandContext<ServerCommandSource>) {
|
||||||
|
val feedback = Text.literal("BPS - $text")
|
||||||
|
.setStyle(Style.EMPTY.withColor(Color(200, 0, 0).rgb))
|
||||||
|
|
||||||
|
context.source.sendFeedback({ feedback }, false)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
package com.bytedice.bde_particles
|
package com.bytedice.bde_particles
|
||||||
|
|
||||||
import net.minecraft.entity.EntityType
|
import net.minecraft.entity.EntityType
|
||||||
import net.minecraft.entity.decoration.DisplayEntity.BlockDisplayEntity
|
import net.minecraft.entity.decoration.DisplayEntity.ItemDisplayEntity
|
||||||
import net.minecraft.nbt.NbtCompound
|
import net.minecraft.nbt.NbtCompound
|
||||||
import net.minecraft.nbt.NbtFloat
|
import net.minecraft.nbt.NbtFloat
|
||||||
import net.minecraft.nbt.NbtList
|
import net.minecraft.nbt.NbtList
|
||||||
import net.minecraft.nbt.NbtString
|
import net.minecraft.nbt.NbtString
|
||||||
import net.minecraft.server.world.ServerWorld
|
import net.minecraft.server.world.ServerWorld
|
||||||
|
|
||||||
class DisplayEntity(private var properties: DisplayEntityProperties?) {
|
class DisplayEntity(var properties: DisplayEntityProperties?) {
|
||||||
private var hasSpawned = false
|
private var hasSpawned = false
|
||||||
private var entity: BlockDisplayEntity? = null
|
private var entity: ItemDisplayEntity? = null
|
||||||
|
|
||||||
fun spawn(world: ServerWorld) {
|
fun spawn(world: ServerWorld) {
|
||||||
if (hasSpawned || properties == null) { return }
|
if (hasSpawned || properties == null) { return }
|
||||||
|
|
||||||
entity = BlockDisplayEntity(EntityType.BLOCK_DISPLAY, world)
|
entity = ItemDisplayEntity(EntityType.ITEM_DISPLAY, world)
|
||||||
updateProperties(properties!!)
|
updateProperties(properties!!)
|
||||||
|
|
||||||
world.spawnEntity(entity)
|
world.spawnEntity(entity)
|
||||||
@@ -34,7 +34,13 @@ class DisplayEntity(private var properties: DisplayEntityProperties?) {
|
|||||||
|
|
||||||
put("Tags", tagList)
|
put("Tags", tagList)
|
||||||
|
|
||||||
put("block_state", NbtCompound().apply { putString("Name", properties.blockType) })
|
if (properties.model != "none") {
|
||||||
|
put("item", NbtCompound().apply {
|
||||||
|
putString("id", properties.model)
|
||||||
|
putInt("count", 1)
|
||||||
|
put("components", NbtCompound().apply { putInt("minecraft:custom_model_data", properties.customModel) })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
put("transformation", NbtCompound().apply {
|
put("transformation", NbtCompound().apply {
|
||||||
val offsetList = NbtList().apply {
|
val offsetList = NbtList().apply {
|
||||||
@@ -65,6 +71,22 @@ class DisplayEntity(private var properties: DisplayEntityProperties?) {
|
|||||||
put("scale", scaleList)
|
put("scale", scaleList)
|
||||||
put("right_rotation", rightRotList)
|
put("right_rotation", rightRotList)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
putFloat("view_range", properties.viewRange)
|
||||||
|
|
||||||
|
putString("billboard", properties.billboard)
|
||||||
|
|
||||||
|
if (properties.customName != null) {
|
||||||
|
putString("CustomName", "{\"text\":\"${properties.customName}\"}")
|
||||||
|
putByte("CustomNameVisible", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties.brightnessOverride != null) {
|
||||||
|
put("brightness", NbtCompound().apply {
|
||||||
|
putInt("block", properties.brightnessOverride!!)
|
||||||
|
putInt("sky", properties.brightnessOverride!!)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
entity?.readNbt(nbt)
|
entity?.readNbt(nbt)
|
||||||
@@ -75,7 +97,7 @@ class DisplayEntity(private var properties: DisplayEntityProperties?) {
|
|||||||
|
|
||||||
|
|
||||||
fun kill() {
|
fun kill() {
|
||||||
if (entity == null) { println("BPS - Particle Emitter entity is null!"); return }
|
if (entity == null) { println("BPS - Failed to kill BDE. Entity is null!"); return }
|
||||||
entity?.kill()
|
entity?.kill()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,14 +6,28 @@ import org.joml.Vector3f
|
|||||||
import org.joml.Vector4f
|
import org.joml.Vector4f
|
||||||
|
|
||||||
data class DisplayEntityProperties (
|
data class DisplayEntityProperties (
|
||||||
val pos: Vec3d = Vec3d(0.0, 0.0, 0.0),
|
var pos: Vec3d = Vec3d(0.0, 0.0, 0.0),
|
||||||
val rot: Vector2f = Vector2f(0.0f, 0.0f),
|
var rot: Vector2f = Vector2f(0.0f, 0.0f),
|
||||||
|
|
||||||
val blockType: String = "minecraft:purple_concrete",
|
var model: String = "minecraft:purple_concrete",
|
||||||
val tags: Array<String> = emptyArray(),
|
var customModel: Int = 0,
|
||||||
|
var tags: Array<String> = emptyArray(),
|
||||||
|
var viewRange: Float = 2.0f,
|
||||||
|
var customName: String? = null,
|
||||||
|
var brightnessOverride: Int? = null,
|
||||||
|
var billboard: String = Billboard.FIXED,
|
||||||
|
|
||||||
val translation: Vector3f = Vector3f(-0.5f, -0.5f, -0.5f),
|
var translation: Vector3f = Vector3f(0.0f, 0.0f, 0.0f),
|
||||||
val leftRotation: Vector4f = Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
|
var leftRotation: Vector4f = Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
|
||||||
val scale: Vector3f = Vector3f(1.0f, 1.0f, 1.0f),
|
var scale: Vector3f = Vector3f(1.0f, 1.0f, 1.0f),
|
||||||
val rightRotation: Vector4f = Vector4f(0.0f, 0.0f, 0.0f, 1.0f)
|
var rightRotation: Vector4f = Vector4f(0.0f, 0.0f, 0.0f, 1.0f)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Billboard () {
|
||||||
|
companion object {
|
||||||
|
const val FIXED: String = "fixed"
|
||||||
|
const val VERTICAL: String = "vertical"
|
||||||
|
const val HORIZONTAL: String = "horizontal"
|
||||||
|
const val CENTER: String = "center"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,25 +5,24 @@ import net.minecraft.util.hit.HitResult
|
|||||||
import net.minecraft.util.math.Vec3d
|
import net.minecraft.util.math.Vec3d
|
||||||
import net.minecraft.world.RaycastContext
|
import net.minecraft.world.RaycastContext
|
||||||
import net.minecraft.world.World
|
import net.minecraft.world.World
|
||||||
import org.joml.Vector2f
|
|
||||||
import org.joml.Vector3f
|
import org.joml.Vector3f
|
||||||
import org.joml.Vector4f
|
import org.joml.Vector4f
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
class InterpolationCurves(val function: (Float) -> Float) {
|
class LerpCurves(val function: (Float) -> Float) {
|
||||||
companion object {
|
companion object {
|
||||||
val LINEAR = InterpolationCurves { y -> y }
|
val Linear = LerpCurves { y -> y }
|
||||||
val SQRT = InterpolationCurves { y -> sqrt(y) }
|
val Sqrt = LerpCurves { y -> sqrt(y) }
|
||||||
val EXPONENT = InterpolationCurves { y -> y.pow(2.0f) }
|
val Exponent = LerpCurves { y -> y.pow(2.0f) }
|
||||||
val CUBIC = InterpolationCurves { y -> y.pow(3.0f) }
|
val Cubic = LerpCurves { y -> y.pow(3.0f) }
|
||||||
val SINE = InterpolationCurves { y -> sin(y * PI.toFloat() / 2) }
|
val Sine = LerpCurves { y -> sin(y * PI.toFloat() / 2) }
|
||||||
val COSINE = InterpolationCurves { y -> 1 - cos(y * PI.toFloat() / 2) }
|
val Cosine = LerpCurves { y -> 1 - cos(y * PI.toFloat() / 2) }
|
||||||
val INVERSE = InterpolationCurves { y -> 1 - y }
|
val Inverse = LerpCurves { y -> 1 - y }
|
||||||
val LOG = InterpolationCurves { y -> if (y > 0) ln(y + 1) else 0f }
|
val Log = LerpCurves { y -> if (y > 0) ln(y + 1) else 0f }
|
||||||
val EXP = InterpolationCurves { y -> exp(y) - 1 }
|
val Exp = LerpCurves { y -> exp(y) - 1 }
|
||||||
val BOUNCE = InterpolationCurves { y ->
|
val Bounce = LerpCurves { y ->
|
||||||
val n1 = 7.5625f
|
val n1 = 7.5625f
|
||||||
val d1 = 2.75f
|
val d1 = 2.75f
|
||||||
when {
|
when {
|
||||||
@@ -43,7 +42,11 @@ class InterpolationCurves(val function: (Float) -> Float) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun custom(equation: (Float) -> Float) = InterpolationCurves(equation)
|
fun custom(equation: (Float) -> Float) = LerpCurves(equation)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deepCopy() : LerpCurves {
|
||||||
|
return LerpCurves(this.function)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,28 +76,23 @@ fun raycastFromPlayer(player: ServerPlayerEntity, maxDistance: Double): HitResul
|
|||||||
|
|
||||||
|
|
||||||
fun randomFloatBetween(min: Float, max: Float) : Float {
|
fun randomFloatBetween(min: Float, max: Float) : Float {
|
||||||
return Random.nextFloat() * (max - min) + min
|
return if (min == max) { max }
|
||||||
|
else if (min > max) { Random.nextFloat() * (min - max) + max }
|
||||||
|
else { Random.nextFloat() * (max - min) + min }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun randomIntBetween(min: Int, max: Int) : Int {
|
fun randomIntBetween(min: Int, max: Int) : Int {
|
||||||
return Random.nextInt(min, max)
|
return if (min == max) { max }
|
||||||
}
|
else if (min > max) { Random.nextInt(min - max) + max }
|
||||||
|
else { Random.nextInt(max - min) + min }
|
||||||
|
|
||||||
fun randomBetweenVector3f(min: Vector3f, max: Vector3f) : Vector3f {
|
|
||||||
return Vector3f(
|
|
||||||
randomFloatBetween(min.x, max.x),
|
|
||||||
randomFloatBetween(min.y, max.y),
|
|
||||||
randomFloatBetween(min.z, max.z)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun eulerToQuat(euler: Vector3f): Vector4f {
|
fun eulerToQuat(euler: Vector3f): Vector4f {
|
||||||
val roll = euler.x
|
val roll = euler.x * (Math.PI / 180).toFloat()
|
||||||
val pitch = euler.y
|
val pitch = euler.y * (Math.PI / 180).toFloat()
|
||||||
val yaw = euler.z
|
val yaw = euler.z * (Math.PI / 180).toFloat()
|
||||||
|
|
||||||
val cy = cos(yaw * 0.5)
|
val cy = cos(yaw * 0.5)
|
||||||
val sy = sin(yaw * 0.5)
|
val sy = sin(yaw * 0.5)
|
||||||
@@ -137,45 +135,6 @@ fun quatToEuler(quat: Vector4f): Vector3f {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun randomInCircle(r: Float) : Vector2f {
|
|
||||||
val randRadius = r * sqrt(randomFloatBetween(0.0f, 1.0f))
|
|
||||||
val randAngle = randomFloatBetween(0.0f, Math.PI.toFloat() * 2)
|
|
||||||
|
|
||||||
val randomPos = Vector2f(randRadius * cos(randAngle), randRadius * sin(randAngle))
|
|
||||||
return randomPos
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun randomInSphere(r: Float) : Vector3f {
|
|
||||||
val randRadius = r * Random.nextDouble().pow(1.0 / 3.0)
|
|
||||||
|
|
||||||
val phi = Random.nextDouble(0.0, 2 * Math.PI)
|
|
||||||
|
|
||||||
val cosTheta = Random.nextDouble(-1.0, 1.0)
|
|
||||||
val theta = acos(cosTheta)
|
|
||||||
|
|
||||||
val x = randRadius * sin(theta) * cos(phi)
|
|
||||||
val y = randRadius * sin(theta) * sin(phi)
|
|
||||||
val z = randRadius * cos(theta)
|
|
||||||
|
|
||||||
return Vector3f(x.toFloat(), y.toFloat(), z.toFloat())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun randomInRect(size: Vector2f) : Vector2f {
|
|
||||||
return Vector2f(randomFloatBetween(0.0f, size.x), randomFloatBetween(0.0f, size.y))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun randomInCube(size: Vector3f) : Vector3f {
|
|
||||||
return Vector3f(
|
|
||||||
randomFloatBetween(0.0f, size.x),
|
|
||||||
randomFloatBetween(0.0f, size.y),
|
|
||||||
randomFloatBetween(0.0f, size.z)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun transformOffsetByQuat(offset: Vector3f, rotation: Vector4f): Vector3f {
|
fun transformOffsetByQuat(offset: Vector3f, rotation: Vector4f): Vector3f {
|
||||||
val x = offset.x
|
val x = offset.x
|
||||||
val y = offset.y
|
val y = offset.y
|
||||||
@@ -205,22 +164,14 @@ fun transformOffsetByScale(offset: Vector3f, scale: Vector3f): Vector3f {
|
|||||||
|
|
||||||
|
|
||||||
fun lerp(x: Float, y: Float, t: Float) : Float {
|
fun lerp(x: Float, y: Float, t: Float) : Float {
|
||||||
return x + (y - x) * t
|
val clampedT = t.coerceIn(0f, 1f)
|
||||||
|
return x + (y - x) * clampedT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun interpolateCurve(start: Vector3f, end: Vector3f, t: Float, curve: InterpolationCurves): Vector3f {
|
fun lerpArray(array: Array<Any>, t: Float, curve: LerpCurves = LerpCurves.Linear) : Any {
|
||||||
val clampedT = t.coerceIn(0.0f, 1.0f)
|
val idx = round(lerp(0.0f, array.lastIndex.toFloat(), t) * curve.function(t)).toInt()
|
||||||
|
return array[idx.coerceIn(0, array.lastIndex)]
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -234,7 +185,6 @@ fun sdfSphere(pos: Vector3f, radius: Float, objectPos: Vector3f): Float {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fun sdfCube(pos: Vector3f, size: Vector3f, objectPos: Vector3f): Float {
|
fun sdfCube(pos: Vector3f, size: Vector3f, objectPos: Vector3f): Float {
|
||||||
val px = objectPos.x - pos.x
|
val px = objectPos.x - pos.x
|
||||||
val py = objectPos.y - pos.y
|
val py = objectPos.y - pos.y
|
||||||
|
|||||||
@@ -0,0 +1,914 @@
|
|||||||
|
@file:Suppress("UNCHECKED_CAST")
|
||||||
|
|
||||||
|
package com.bytedice.bde_particles.commands
|
||||||
|
|
||||||
|
import com.bytedice.bde_particles.curveListArg
|
||||||
|
import com.bytedice.bde_particles.negativeFeedback
|
||||||
|
import com.bytedice.bde_particles.particles.*
|
||||||
|
import com.bytedice.bde_particles.positiveFeedback
|
||||||
|
import com.bytedice.bde_particles.stringToCurve
|
||||||
|
import com.mojang.brigadier.Command
|
||||||
|
import com.mojang.brigadier.arguments.BoolArgumentType
|
||||||
|
import com.mojang.brigadier.arguments.FloatArgumentType
|
||||||
|
import com.mojang.brigadier.arguments.IntegerArgumentType
|
||||||
|
import com.mojang.brigadier.arguments.StringArgumentType
|
||||||
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder
|
||||||
|
import com.mojang.brigadier.builder.RequiredArgumentBuilder
|
||||||
|
import com.mojang.brigadier.context.CommandContext
|
||||||
|
import net.minecraft.command.CommandRegistryAccess
|
||||||
|
import net.minecraft.command.argument.ItemStackArgumentType
|
||||||
|
import net.minecraft.registry.Registries
|
||||||
|
import net.minecraft.server.command.CommandManager
|
||||||
|
import net.minecraft.server.command.ServerCommandSource
|
||||||
|
import org.joml.Vector2f
|
||||||
|
import org.joml.Vector3f
|
||||||
|
import kotlin.reflect.*
|
||||||
|
import kotlin.reflect.full.memberProperties
|
||||||
|
|
||||||
|
|
||||||
|
typealias CommandArgumentBuilder = (
|
||||||
|
access: KProperty1<EmitterParams, *>,
|
||||||
|
config: LiteralArgumentBuilder<ServerCommandSource>,
|
||||||
|
reg: CommandRegistryAccess
|
||||||
|
) -> LiteralArgumentBuilder<ServerCommandSource>?
|
||||||
|
|
||||||
|
|
||||||
|
fun listConfigArgs(configArgs: Array<String>,
|
||||||
|
rootArg: RequiredArgumentBuilder<ServerCommandSource, *>,
|
||||||
|
registryAccess: CommandRegistryAccess
|
||||||
|
): RequiredArgumentBuilder<ServerCommandSource, *>
|
||||||
|
{
|
||||||
|
configArgs.forEach { arg ->
|
||||||
|
var configArg = CommandManager.literal(arg)
|
||||||
|
|
||||||
|
val argAccess = parseArgNameToAccess(arg)!!
|
||||||
|
val func = parseArgTypeToFunc(argAccess.returnType, configArg)
|
||||||
|
|
||||||
|
if (func != null) {
|
||||||
|
configArg = func(argAccess, configArg, registryAccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootArg.then(configArg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootArg
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fun dataClassToArray(dataClass: KClass<*>): Array<String> {
|
||||||
|
return dataClass.memberProperties.map { it.name }.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun parseArgNameToAccess(name: String) : KProperty1<EmitterParams, *>? {
|
||||||
|
val access = EmitterParams::class.memberProperties
|
||||||
|
.firstOrNull { it.name == name }
|
||||||
|
|
||||||
|
return access
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseArgTypeToFunc(type: KType, configArg: LiteralArgumentBuilder<ServerCommandSource>): CommandArgumentBuilder? {
|
||||||
|
return when (type.classifier) {
|
||||||
|
Int::class -> { access, _, _ ->
|
||||||
|
configArg.then(intArg(access as KProperty1<EmitterParams, Int>))
|
||||||
|
}
|
||||||
|
Float::class -> { access, _, _ ->
|
||||||
|
configArg.then(floatArg(access as KProperty1<EmitterParams, Float>))
|
||||||
|
}
|
||||||
|
String::class -> { access, _, _ ->
|
||||||
|
configArg.then(stringArg(access as KProperty1<EmitterParams, String>))
|
||||||
|
}
|
||||||
|
ParamClasses.Duration::class -> { access, config, _ ->
|
||||||
|
durArg(access as KProperty1<EmitterParams, ParamClasses.Duration>, config)
|
||||||
|
}
|
||||||
|
Vector3f::class -> { access, _, _ ->
|
||||||
|
configArg.then(vec3fArg(access as KProperty1<EmitterParams, Vector3f>))
|
||||||
|
}
|
||||||
|
ParamClasses.PairInt::class -> { access, _, _ ->
|
||||||
|
configArg.then(pairIntArg(access as KProperty1<EmitterParams, ParamClasses.PairInt>))
|
||||||
|
}
|
||||||
|
SpawningShape::class -> { access, config, _ ->
|
||||||
|
shapeArg(access as KProperty1<EmitterParams, SpawningShape>, config)
|
||||||
|
}
|
||||||
|
ParamClasses.PairVec3f::class -> { access, config, _ ->
|
||||||
|
pairVec3fArg(access as KProperty1<EmitterParams, ParamClasses.PairVec3f>, config)
|
||||||
|
}
|
||||||
|
ParamClasses.PairFloat::class -> { access, _, _ ->
|
||||||
|
configArg.then(pairFloatArg(access as KProperty1<EmitterParams, ParamClasses.PairFloat>))
|
||||||
|
}
|
||||||
|
ParamClasses.TransformWithVel::class -> { access, config, _ ->
|
||||||
|
transformWithVelArg(access as KProperty1<EmitterParams, ParamClasses.TransformWithVel>, config)
|
||||||
|
}
|
||||||
|
ParamClasses.StringCurve::class -> { access, config, reg ->
|
||||||
|
stringCurveArg(access as KProperty1<EmitterParams, ParamClasses.StringCurve>, config, reg)
|
||||||
|
}
|
||||||
|
ParamClasses.LerpValVec3f::class -> { access, config, _ ->
|
||||||
|
lerpValVec3fArg(access as KProperty1<EmitterParams, ParamClasses.LerpValVec3f>, config)
|
||||||
|
}
|
||||||
|
ParamClasses.ForceFieldArray::class -> { access, config, _ ->
|
||||||
|
forceFieldArg(access as KProperty1<EmitterParams, ParamClasses.ForceFieldArray>, config)
|
||||||
|
}
|
||||||
|
ParamClasses.LerpValInt::class -> { access, config, _ ->
|
||||||
|
lerpValIntArg(access as KProperty1<EmitterParams, ParamClasses.LerpValInt>, config)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun successText(access: KProperty1<EmitterParams, *>, value: String, id: String, context: CommandContext<ServerCommandSource>) {
|
||||||
|
positiveFeedback("Successfully updated parameter \"${access.name}\" to \"$value\" of Emitter ID \"$id\"", context)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun intArg(access: KProperty1<EmitterParams, Int>) : RequiredArgumentBuilder<ServerCommandSource, Int> {
|
||||||
|
return CommandManager.argument("Int", IntegerArgumentType.integer())
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val value = IntegerArgumentType.getInteger(context, "Int")
|
||||||
|
|
||||||
|
updateParam(id, access, value)
|
||||||
|
|
||||||
|
successText(access, value.toString(), id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun floatArg(access: KProperty1<EmitterParams, Float>) : RequiredArgumentBuilder<ServerCommandSource, Float> {
|
||||||
|
return CommandManager.argument("Float", FloatArgumentType.floatArg())
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val value = FloatArgumentType.getFloat(context, "Float")
|
||||||
|
|
||||||
|
updateParam(id, access, value)
|
||||||
|
|
||||||
|
successText(access, value.toString(), id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stringArg(access: KProperty1<EmitterParams, String>) : RequiredArgumentBuilder<ServerCommandSource, String> {
|
||||||
|
return CommandManager.argument("String", StringArgumentType.string())
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val value = StringArgumentType.getString(context, "String")
|
||||||
|
|
||||||
|
updateParam(id, access, value)
|
||||||
|
|
||||||
|
successText(access, value.toString(), id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun durArg(access: KProperty1<EmitterParams, ParamClasses.Duration>,
|
||||||
|
configArg: LiteralArgumentBuilder<ServerCommandSource>
|
||||||
|
) : LiteralArgumentBuilder<ServerCommandSource>
|
||||||
|
{
|
||||||
|
return configArg
|
||||||
|
.then(CommandManager.literal("SingleBurst")
|
||||||
|
.then(CommandManager.argument("loopDur", IntegerArgumentType.integer())
|
||||||
|
.executes { context -> durArgExec("SingleBurst", access, context); Command.SINGLE_SUCCESS }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("MultiBurst")
|
||||||
|
.then(CommandManager.argument("loopDur", IntegerArgumentType.integer())
|
||||||
|
.then(CommandManager.argument("loopDelay", IntegerArgumentType.integer())
|
||||||
|
.then(CommandManager.argument("loopCount", IntegerArgumentType.integer())
|
||||||
|
.executes { context -> durArgExec("MultiBurst", access, context); Command.SINGLE_SUCCESS }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("InfiniteLoop")
|
||||||
|
.then(CommandManager.argument("loopDur", IntegerArgumentType.integer())
|
||||||
|
.then(CommandManager.argument("loopDelay", IntegerArgumentType.integer())
|
||||||
|
.executes { context -> durArgExec("InfiniteLoop", access, context); Command.SINGLE_SUCCESS }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun durArgExec(paramName: String, access: KProperty1<EmitterParams, ParamClasses.Duration>, context: CommandContext<ServerCommandSource>) {
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val loopDur = IntegerArgumentType.getInteger(context, "loopDur")
|
||||||
|
|
||||||
|
|
||||||
|
when (paramName) {
|
||||||
|
"SingleBurst" -> {
|
||||||
|
updateParam(id, access, ParamClasses.Duration.SingleBurst(loopDur))
|
||||||
|
}
|
||||||
|
"MultiBurst" -> {
|
||||||
|
val loopDelay = IntegerArgumentType.getInteger(context, "loopDelay")
|
||||||
|
val loopCount = IntegerArgumentType.getInteger(context, "loopCount")
|
||||||
|
updateParam(id, access, ParamClasses.Duration.MultiBurst(loopDur, loopDelay, loopCount))
|
||||||
|
}
|
||||||
|
"InfiniteLoop" -> {
|
||||||
|
val loopDelay = IntegerArgumentType.getInteger(context, "loopDelay")
|
||||||
|
updateParam(id, access, ParamClasses.Duration.InfiniteLoop(loopDur, loopDelay))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
successText(access, access::class.simpleName ?: "UNKNOWN", id, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun vec3fArg(access: KProperty1<EmitterParams, Vector3f>) : RequiredArgumentBuilder<ServerCommandSource, Float> {
|
||||||
|
return CommandManager.argument("X", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Y", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Z", FloatArgumentType.floatArg())
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val valueX = FloatArgumentType.getFloat(context, "X")
|
||||||
|
val valueY = FloatArgumentType.getFloat(context, "Y")
|
||||||
|
val valueZ = FloatArgumentType.getFloat(context, "Z")
|
||||||
|
|
||||||
|
updateParam(id, access, Vector3f(valueX, valueY, valueZ))
|
||||||
|
|
||||||
|
successText(access, "[$valueX, $valueY, $valueZ]", id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pairIntArg(access: KProperty1<EmitterParams, ParamClasses.PairInt>) : RequiredArgumentBuilder<ServerCommandSource, Int> {
|
||||||
|
return CommandManager.argument("Min Int", IntegerArgumentType.integer())
|
||||||
|
.then(CommandManager.argument("Max Int", IntegerArgumentType.integer())
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val valueX = IntegerArgumentType.getInteger(context, "Min Int")
|
||||||
|
val valueY = IntegerArgumentType.getInteger(context, "Max Int")
|
||||||
|
|
||||||
|
updateParam(id, access, ParamClasses.PairInt(valueX, valueY))
|
||||||
|
successText(access, "[$valueX, $valueY]", id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shapeArg(access: KProperty1<EmitterParams, SpawningShape>,
|
||||||
|
configArg: LiteralArgumentBuilder<ServerCommandSource>
|
||||||
|
) : LiteralArgumentBuilder<ServerCommandSource>
|
||||||
|
{
|
||||||
|
return configArg
|
||||||
|
.then(CommandManager.literal("Circle")
|
||||||
|
.then(CommandManager.argument("Radius", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Spawn On Edge", BoolArgumentType.bool())
|
||||||
|
.executes { context -> shapeArgExec("Circle", access, context); Command.SINGLE_SUCCESS }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("Rect")
|
||||||
|
.then(CommandManager.argument("X", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Y", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Spawn On Edge", BoolArgumentType.bool())
|
||||||
|
.executes { context -> shapeArgExec("Rect", access, context); Command.SINGLE_SUCCESS }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("Cube")
|
||||||
|
.then(CommandManager.argument("X", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Y", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Z", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Spawn On Edge", BoolArgumentType.bool())
|
||||||
|
.executes { context -> shapeArgExec("Cube", access, context); Command.SINGLE_SUCCESS }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("Sphere")
|
||||||
|
.then(CommandManager.argument("Radius", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Spawn On Edge", BoolArgumentType.bool())
|
||||||
|
.executes { context -> shapeArgExec("Sphere", access, context); Command.SINGLE_SUCCESS }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("Point")
|
||||||
|
.executes { context -> shapeArgExec("Point", access, context); Command.SINGLE_SUCCESS }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shapeArgExec(paramName: String, access: KProperty1<EmitterParams, SpawningShape>, context: CommandContext<ServerCommandSource>) {
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
|
||||||
|
when (paramName) {
|
||||||
|
"Circle" -> {
|
||||||
|
val radius = FloatArgumentType.getFloat(context, "Radius")
|
||||||
|
val onEdge = BoolArgumentType.getBool(context, "Spawn On Edge")
|
||||||
|
updateParam(id, access, SpawningShape.Circle(radius, onEdge))
|
||||||
|
successText(access, "Circle", id, context)
|
||||||
|
}
|
||||||
|
"Rect" -> {
|
||||||
|
val onEdge = BoolArgumentType.getBool(context, "Spawn On Edge")
|
||||||
|
val x = FloatArgumentType.getFloat(context, "X")
|
||||||
|
val y = FloatArgumentType.getFloat(context, "Y")
|
||||||
|
updateParam(id, access, SpawningShape.Rect(Vector2f(x, y), onEdge))
|
||||||
|
successText(access, "Rect", id, context)
|
||||||
|
}
|
||||||
|
"Cube" -> {
|
||||||
|
val onEdge = BoolArgumentType.getBool(context, "Spawn On Edge")
|
||||||
|
val x = FloatArgumentType.getFloat(context, "X")
|
||||||
|
val y = FloatArgumentType.getFloat(context, "Y")
|
||||||
|
val z = FloatArgumentType.getFloat(context, "Z")
|
||||||
|
updateParam(id, access, SpawningShape.Cube(Vector3f(x, y, z), onEdge))
|
||||||
|
successText(access, "Cube", id, context)
|
||||||
|
}
|
||||||
|
"Sphere" -> {
|
||||||
|
val radius = FloatArgumentType.getFloat(context, "Radius")
|
||||||
|
val onEdge = BoolArgumentType.getBool(context, "Spawn On Edge")
|
||||||
|
updateParam(id, access, SpawningShape.Sphere(radius, onEdge))
|
||||||
|
successText(access, "Sphere", id, context)
|
||||||
|
}
|
||||||
|
"Point" -> {
|
||||||
|
updateParam(id, access, SpawningShape.Point)
|
||||||
|
successText(access, "Point", id, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pairVec3fArg(access: KProperty1<EmitterParams,ParamClasses.PairVec3f>,
|
||||||
|
configArg: LiteralArgumentBuilder<ServerCommandSource>
|
||||||
|
) : LiteralArgumentBuilder<ServerCommandSource>
|
||||||
|
{
|
||||||
|
return configArg
|
||||||
|
.then(CommandManager.literal("NonUniform")
|
||||||
|
.then(CommandManager.argument("Min X", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Min Y", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Min Z", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Max X", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Max Y", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Max Z", FloatArgumentType.floatArg())
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val minX = FloatArgumentType.getFloat(context, "Min X")
|
||||||
|
val minY = FloatArgumentType.getFloat(context, "Min Y")
|
||||||
|
val minZ = FloatArgumentType.getFloat(context, "Min Z")
|
||||||
|
val maxX = FloatArgumentType.getFloat(context, "Max X")
|
||||||
|
val maxY = FloatArgumentType.getFloat(context, "Max Y")
|
||||||
|
val maxZ = FloatArgumentType.getFloat(context, "Max Z")
|
||||||
|
|
||||||
|
updateParam(id, access, ParamClasses.PairVec3f.NonUniform(minX, minY, minZ, maxX, maxY, maxZ))
|
||||||
|
|
||||||
|
successText(access, "NonUniform", id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("Uniform")
|
||||||
|
.then(CommandManager.argument("X", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Y", FloatArgumentType.floatArg())
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val min = FloatArgumentType.getFloat(context, "X")
|
||||||
|
val max = FloatArgumentType.getFloat(context, "Y")
|
||||||
|
|
||||||
|
updateParam(id, access, ParamClasses.PairVec3f.Uniform(min, max))
|
||||||
|
|
||||||
|
successText(access, "Uniform", id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("Constant")
|
||||||
|
.then(CommandManager.argument("X", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Y", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Z", FloatArgumentType.floatArg())
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val x = FloatArgumentType.getFloat(context, "X")
|
||||||
|
val y = FloatArgumentType.getFloat(context, "Y")
|
||||||
|
val z = FloatArgumentType.getFloat(context, "Z")
|
||||||
|
|
||||||
|
updateParam(id, access, ParamClasses.PairVec3f.Constant(Vector3f(x, y, z)))
|
||||||
|
|
||||||
|
successText(access, "Constant", id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("Null")
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
|
||||||
|
updateParam(id, access, ParamClasses.PairVec3f.Null)
|
||||||
|
|
||||||
|
successText(access, "Null", id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pairFloatArg(access: KProperty1<EmitterParams, ParamClasses.PairFloat>) : RequiredArgumentBuilder<ServerCommandSource, Float> {
|
||||||
|
return CommandManager.argument("Min Float", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Max Float", FloatArgumentType.floatArg())
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val valueX = FloatArgumentType.getFloat(context, "Min Float")
|
||||||
|
val valueY = FloatArgumentType.getFloat(context, "Max Float")
|
||||||
|
|
||||||
|
updateParam(id, access, ParamClasses.PairFloat(valueX, valueY))
|
||||||
|
successText(access, "[$valueX, $valueY]", id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun transformWithVelArg(access: KProperty1<EmitterParams, ParamClasses.TransformWithVel>,
|
||||||
|
configArg: LiteralArgumentBuilder<ServerCommandSource>
|
||||||
|
) : LiteralArgumentBuilder<ServerCommandSource>
|
||||||
|
{
|
||||||
|
return configArg
|
||||||
|
.then(CommandManager.literal("RotOnly")
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
|
||||||
|
updateParam(id, access, ParamClasses.TransformWithVel.RotOnly)
|
||||||
|
successText(access, "RotOnly", id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("ScaleAndRot")
|
||||||
|
.then(CommandManager.argument("Scale Multiplier", FloatArgumentType.floatArg())
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val scaleMul = FloatArgumentType.getFloat(context, "Scale Multiplier")
|
||||||
|
|
||||||
|
updateParam(id, access, ParamClasses.TransformWithVel.ScaleAndRot(scaleMul))
|
||||||
|
successText(access, "ScaleAndRot", id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("Null")
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
|
||||||
|
updateParam(id, access, ParamClasses.TransformWithVel.Null)
|
||||||
|
successText(access, "Null", id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun stringCurveArg(access: KProperty1<EmitterParams, ParamClasses.StringCurve>,
|
||||||
|
configArg: LiteralArgumentBuilder<ServerCommandSource>,
|
||||||
|
registryAccess: CommandRegistryAccess
|
||||||
|
) : LiteralArgumentBuilder<ServerCommandSource>
|
||||||
|
{
|
||||||
|
return configArg
|
||||||
|
.then(CommandManager.literal("Add")
|
||||||
|
.then(CommandManager.argument("Item", ItemStackArgumentType.itemStack(registryAccess))
|
||||||
|
.then(CommandManager.argument("Index", IntegerArgumentType.integer())
|
||||||
|
.executes { context ->
|
||||||
|
val index = IntegerArgumentType.getInteger(context, "Index")
|
||||||
|
stringCurveAddArg(context, access, index)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.executes { context ->
|
||||||
|
stringCurveAddArg(context, access, null)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("Remove")
|
||||||
|
.then(CommandManager.argument("Index", IntegerArgumentType.integer())
|
||||||
|
.executes { context ->
|
||||||
|
val index = IntegerArgumentType.getInteger(context, "Index")
|
||||||
|
stringCurveRemoveArg(context, access, index)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.executes { context ->
|
||||||
|
stringCurveRemoveArg(context, access, null)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("Curve")
|
||||||
|
.then(curveListArg("Curve")
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val curveName = StringArgumentType.getString(context, "Curve")
|
||||||
|
|
||||||
|
val curve = stringToCurve(curveName)
|
||||||
|
val modelCurve = getEmitterDataById(id)?.modelCurve
|
||||||
|
|
||||||
|
if (curve != null && modelCurve != null) {
|
||||||
|
modelCurve.curve = curve
|
||||||
|
updateParam(id, access, modelCurve)
|
||||||
|
successText(access, curveName, id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
negativeFeedback("Failed to update param \"modelCurve.curve\"! The curve is invalid!", context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fun stringCurveAddArg(context: CommandContext<ServerCommandSource>,
|
||||||
|
access: KProperty1<EmitterParams, ParamClasses.StringCurve>,
|
||||||
|
index: Int?
|
||||||
|
) {
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val item = ItemStackArgumentType.getItemStackArgument(context, "Item").item
|
||||||
|
val itemId = Registries.ITEM.getId(item).toString()
|
||||||
|
|
||||||
|
val modelCurve = getEmitterDataById(id)?.modelCurve
|
||||||
|
|
||||||
|
if (index == null && modelCurve != null) {
|
||||||
|
modelCurve.array = modelCurve.array.toMutableList().apply { add(itemId) }.toTypedArray()
|
||||||
|
updateParam(id, access, modelCurve)
|
||||||
|
}
|
||||||
|
else if (modelCurve != null) {
|
||||||
|
modelCurve.array = modelCurve.array.toMutableList().apply { add(index!!.coerceIn(0, modelCurve.array.lastIndex), itemId) }.toTypedArray()
|
||||||
|
updateParam(id, access, modelCurve)
|
||||||
|
}
|
||||||
|
|
||||||
|
successText(access, "++$item", id, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stringCurveRemoveArg(context: CommandContext<ServerCommandSource>,
|
||||||
|
access: KProperty1<EmitterParams, ParamClasses.StringCurve>,
|
||||||
|
index: Int?
|
||||||
|
) {
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
|
||||||
|
val modelCurve = getEmitterDataById(id)?.modelCurve
|
||||||
|
|
||||||
|
val item: String
|
||||||
|
|
||||||
|
if (index == null && modelCurve != null && modelCurve.array.isNotEmpty()) {
|
||||||
|
item = modelCurve.array.last()
|
||||||
|
modelCurve.array = modelCurve.array.toMutableList().apply { removeLast() }.toTypedArray()
|
||||||
|
updateParam(id, access, modelCurve)
|
||||||
|
}
|
||||||
|
else if (modelCurve != null && modelCurve.array.isNotEmpty()) {
|
||||||
|
item = modelCurve.array[index!!.coerceIn(0, modelCurve.array.lastIndex)]
|
||||||
|
modelCurve.array = modelCurve.array.toMutableList().apply { removeAt(index.coerceIn(0, modelCurve.array.lastIndex)) }.toTypedArray()
|
||||||
|
updateParam(id, access, modelCurve)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
negativeFeedback("Couldn't remove model. There are no models in the Emitter.", context)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
successText(access, "--$item", id, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lerpValVec3fArg(access: KProperty1<EmitterParams,ParamClasses.LerpValVec3f>,
|
||||||
|
configArg: LiteralArgumentBuilder<ServerCommandSource>
|
||||||
|
) : LiteralArgumentBuilder<ServerCommandSource>
|
||||||
|
{
|
||||||
|
return configArg
|
||||||
|
.then(CommandManager.literal("LerpVec3f")
|
||||||
|
.then(CommandManager.argument("Min X", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Min Y", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Min Z", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Max X", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Max Y", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Max Z", FloatArgumentType.floatArg())
|
||||||
|
.then(curveListArg("Curve")
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val minX = FloatArgumentType.getFloat(context, "Min X")
|
||||||
|
val minY = FloatArgumentType.getFloat(context, "Min Y")
|
||||||
|
val minZ = FloatArgumentType.getFloat(context, "Min Z")
|
||||||
|
val maxX = FloatArgumentType.getFloat(context, "Max X")
|
||||||
|
val maxY = FloatArgumentType.getFloat(context, "Max Y")
|
||||||
|
val maxZ = FloatArgumentType.getFloat(context, "Max Z")
|
||||||
|
val curveName = StringArgumentType.getString(context, "Curve")
|
||||||
|
|
||||||
|
val curve = stringToCurve(curveName)
|
||||||
|
|
||||||
|
updateParam(id, access, ParamClasses.LerpValVec3f.LerpVec3f(minX, minY, minZ, maxX, maxY, maxZ, curve!!))
|
||||||
|
|
||||||
|
successText(access, "LerpVec3f", id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("MultiLerpVec3f")
|
||||||
|
.then(CommandManager.argument("Min X", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Min Y", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Min Z", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Max X", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Max Y", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Max Z", FloatArgumentType.floatArg())
|
||||||
|
.then(curveListArg("CurveX")
|
||||||
|
.then(curveListArg("CurveY")
|
||||||
|
.then(curveListArg("CurveZ")
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val minX = FloatArgumentType.getFloat(context, "Min X")
|
||||||
|
val minY = FloatArgumentType.getFloat(context, "Min Y")
|
||||||
|
val minZ = FloatArgumentType.getFloat(context, "Min Z")
|
||||||
|
val maxX = FloatArgumentType.getFloat(context, "Max X")
|
||||||
|
val maxY = FloatArgumentType.getFloat(context, "Max Y")
|
||||||
|
val maxZ = FloatArgumentType.getFloat(context, "Max Z")
|
||||||
|
val curveNameX = StringArgumentType.getString(context, "CurveX")
|
||||||
|
val curveNameY = StringArgumentType.getString(context, "CurveY")
|
||||||
|
val curveNameZ = StringArgumentType.getString(context, "CurveZ")
|
||||||
|
|
||||||
|
val curveX = stringToCurve(curveNameX)
|
||||||
|
val curveY = stringToCurve(curveNameY)
|
||||||
|
val curveZ = stringToCurve(curveNameZ)
|
||||||
|
|
||||||
|
updateParam(id, access, ParamClasses.LerpValVec3f.MultiLerpVec3f(minX, minY, minZ, maxX, maxY, maxZ, curveX!!, curveY!!, curveZ!!))
|
||||||
|
|
||||||
|
successText(access, "MultiLerpVec3f", id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("LerpUniform")
|
||||||
|
.then(CommandManager.argument("Min", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Max", FloatArgumentType.floatArg())
|
||||||
|
.then(curveListArg("Curve")
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val min = FloatArgumentType.getFloat(context, "Min")
|
||||||
|
val max = FloatArgumentType.getFloat(context, "Max")
|
||||||
|
val curveName = StringArgumentType.getString(context, "Curve")
|
||||||
|
|
||||||
|
val curve = stringToCurve(curveName)
|
||||||
|
|
||||||
|
updateParam(id, access, ParamClasses.LerpValVec3f.LerpUniform(min, max, curve!!))
|
||||||
|
|
||||||
|
successText(access, "LerpUniform", id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("Constant")
|
||||||
|
.then(CommandManager.argument("X", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Y", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Z", FloatArgumentType.floatArg())
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val x = FloatArgumentType.getFloat(context, "X")
|
||||||
|
val y = FloatArgumentType.getFloat(context, "Y")
|
||||||
|
val z = FloatArgumentType.getFloat(context, "Z")
|
||||||
|
|
||||||
|
updateParam(id, access, ParamClasses.LerpValVec3f.Constant(Vector3f(x, y, z)))
|
||||||
|
|
||||||
|
successText(access, "Constant", id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("Null")
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
|
||||||
|
updateParam(id, access, ParamClasses.LerpValVec3f.Null)
|
||||||
|
|
||||||
|
successText(access, "Null", id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun forceFieldArg(access: KProperty1<EmitterParams,ParamClasses.ForceFieldArray>,
|
||||||
|
configArg: LiteralArgumentBuilder<ServerCommandSource>
|
||||||
|
) : LiteralArgumentBuilder<ServerCommandSource>
|
||||||
|
{
|
||||||
|
return configArg
|
||||||
|
.then(CommandManager.literal("Add")
|
||||||
|
.then(CommandManager.argument("X", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Y", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Z", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.literal("Sphere")
|
||||||
|
.then(CommandManager.argument("Radius", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Min Force", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Max Force", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Index", IntegerArgumentType.integer())
|
||||||
|
.executes { context ->
|
||||||
|
val index = IntegerArgumentType.getInteger(context, "Index")
|
||||||
|
forceFieldAddArg(context, access, "Sphere", index)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.executes { context ->
|
||||||
|
forceFieldAddArg(context, access, "Sphere", null)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("Cube")
|
||||||
|
.then(CommandManager.argument("Size X", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Size Y", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Size Z", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Force X", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Force Y", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Force Z", FloatArgumentType.floatArg())
|
||||||
|
.then(CommandManager.argument("Index", IntegerArgumentType.integer())
|
||||||
|
.executes { context ->
|
||||||
|
val index = IntegerArgumentType.getInteger(context, "Index")
|
||||||
|
forceFieldAddArg(context, access, "Cube", index)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.executes { context ->
|
||||||
|
forceFieldAddArg(context, access, "Cube", null)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.then(CommandManager.literal("Remove")
|
||||||
|
.then(CommandManager.argument("Index", IntegerArgumentType.integer())
|
||||||
|
.executes { context ->
|
||||||
|
val index = IntegerArgumentType.getInteger(context, "Index")
|
||||||
|
forceFieldRemoveArg(context, access, index)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.executes { context ->
|
||||||
|
forceFieldRemoveArg(context, access, null)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun forceFieldAddArg(context: CommandContext<ServerCommandSource>,
|
||||||
|
access: KProperty1<EmitterParams, ParamClasses.ForceFieldArray>,
|
||||||
|
shape: String,
|
||||||
|
index: Int?
|
||||||
|
) {
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val x = FloatArgumentType.getFloat(context, "X")
|
||||||
|
val y = FloatArgumentType.getFloat(context, "Y")
|
||||||
|
val z = FloatArgumentType.getFloat(context, "Z")
|
||||||
|
|
||||||
|
|
||||||
|
val forceFields = getEmitterDataById(id)?.forceFields
|
||||||
|
var forceField: ForceField? = null
|
||||||
|
|
||||||
|
if (shape == "Sphere") {
|
||||||
|
val radius = FloatArgumentType.getFloat(context, "Radius")
|
||||||
|
val minForce = FloatArgumentType.getFloat(context, "Min Force")
|
||||||
|
val maxForce = FloatArgumentType.getFloat(context, "Max Force")
|
||||||
|
|
||||||
|
forceField = ForceField(
|
||||||
|
Vector3f(x, y, z),
|
||||||
|
ForceFieldShape.Sphere(radius, Pair(minForce, maxForce))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else if (shape == "Cube") {
|
||||||
|
val sizeX = FloatArgumentType.getFloat(context, "Size X")
|
||||||
|
val sizeY = FloatArgumentType.getFloat(context, "Size Y")
|
||||||
|
val sizeZ = FloatArgumentType.getFloat(context, "Size Z")
|
||||||
|
val forceX = FloatArgumentType.getFloat(context, "Force X")
|
||||||
|
val forceY = FloatArgumentType.getFloat(context, "Force Y")
|
||||||
|
val forceZ = FloatArgumentType.getFloat(context, "Force Z")
|
||||||
|
|
||||||
|
forceField = ForceField(
|
||||||
|
Vector3f(x, y, z),
|
||||||
|
ForceFieldShape.Cube(Vector3f(sizeX, sizeY, sizeZ), Vector3f(forceX, forceY, forceZ))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forceFields != null && forceField != null) {
|
||||||
|
if (index == null) {
|
||||||
|
forceFields.array = forceFields.array.toMutableList().apply { add(forceField) }.toTypedArray()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val newIndex = index.coerceIn(0, forceFields.array.size)
|
||||||
|
forceFields.array = forceFields.array.toMutableList().apply { add(newIndex, forceField) }.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
val calcIndex = index?.coerceIn(0, forceFields.array.size)
|
||||||
|
|
||||||
|
updateParam(id, access, forceFields)
|
||||||
|
successText(access, "++ForceField at $calcIndex", id, context)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
negativeFeedback("Failed to update param \"forceFields\"! The Emitter ID is most likely invalid!", context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun forceFieldRemoveArg(context: CommandContext<ServerCommandSource>,
|
||||||
|
access: KProperty1<EmitterParams, ParamClasses.ForceFieldArray>,
|
||||||
|
index: Int?
|
||||||
|
) {
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
|
||||||
|
val forceFields = getEmitterDataById(id)?.forceFields
|
||||||
|
|
||||||
|
if (index == null && forceFields != null && forceFields.array.isNotEmpty()) {
|
||||||
|
forceFields.array = forceFields.array.toMutableList().apply { removeLast() }.toTypedArray()
|
||||||
|
updateParam(id, access, forceFields)
|
||||||
|
}
|
||||||
|
else if (forceFields != null && forceFields.array.isNotEmpty()) {
|
||||||
|
val newIndex = index?.coerceIn(0, forceFields.array.lastIndex) ?: forceFields.array.lastIndex
|
||||||
|
forceFields.array = forceFields.array.toMutableList().apply { removeAt(newIndex) }.toTypedArray()
|
||||||
|
updateParam(id, access, forceFields)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
negativeFeedback("Couldn't remove ForceField. There are no ForceFields in the Emitter.", context)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val calcIndex = index?.coerceIn(0, forceFields.array.size) ?: forceFields.array.size
|
||||||
|
|
||||||
|
successText(access, "--ForceField at $calcIndex", id, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lerpValIntArg(access: KProperty1<EmitterParams, ParamClasses.LerpValInt>,
|
||||||
|
configArg: LiteralArgumentBuilder<ServerCommandSource>
|
||||||
|
) : LiteralArgumentBuilder<ServerCommandSource>
|
||||||
|
{
|
||||||
|
return configArg
|
||||||
|
.then(CommandManager.literal("LerpInt")
|
||||||
|
.then(CommandManager.argument("From", IntegerArgumentType.integer())
|
||||||
|
.then(CommandManager.argument("To", IntegerArgumentType.integer())
|
||||||
|
.then(curveListArg("Curve")
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val from = IntegerArgumentType.getInteger(context, "From")
|
||||||
|
val to = IntegerArgumentType.getInteger(context, "To")
|
||||||
|
val curveName = StringArgumentType.getString(context, "Curve")
|
||||||
|
|
||||||
|
val curve = stringToCurve(curveName)
|
||||||
|
|
||||||
|
updateParam(id, access, ParamClasses.LerpValInt.LerpInt(from, to, curve!!))
|
||||||
|
|
||||||
|
successText(access, "LerpInt", id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("Constant")
|
||||||
|
.then(CommandManager.argument("Value", IntegerArgumentType.integer())
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val value = IntegerArgumentType.getInteger(context, "Value")
|
||||||
|
|
||||||
|
updateParam(id, access, ParamClasses.LerpValInt.Constant(value))
|
||||||
|
|
||||||
|
successText(access, "Constant", id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(CommandManager.literal("Null")
|
||||||
|
.executes { context ->
|
||||||
|
val id = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
|
||||||
|
updateParam(id, access, ParamClasses.LerpValInt.Null)
|
||||||
|
|
||||||
|
successText(access, "Null", id, context)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
package com.bytedice.bde_particles.commands
|
package com.bytedice.bde_particles.commands
|
||||||
|
|
||||||
import com.bytedice.bde_particles.items.ParticleEmitterTool
|
import com.bytedice.bde_particles.items.ParticleEmitterTool
|
||||||
import com.bytedice.bde_particles.particles.emitterIdRegister
|
import com.bytedice.bde_particles.particles.idRegister
|
||||||
import com.mojang.brigadier.Command
|
import com.mojang.brigadier.Command
|
||||||
import com.mojang.brigadier.CommandDispatcher
|
import com.mojang.brigadier.CommandDispatcher
|
||||||
import com.mojang.brigadier.arguments.StringArgumentType
|
import com.mojang.brigadier.arguments.StringArgumentType
|
||||||
|
import com.mojang.brigadier.context.CommandContext
|
||||||
import net.minecraft.command.CommandRegistryAccess
|
import net.minecraft.command.CommandRegistryAccess
|
||||||
import net.minecraft.command.argument.ItemStackArgumentType
|
import net.minecraft.command.argument.ItemStackArgumentType
|
||||||
|
import net.minecraft.item.Item
|
||||||
|
import net.minecraft.item.ItemStack
|
||||||
|
import net.minecraft.item.Items
|
||||||
import net.minecraft.server.command.CommandManager
|
import net.minecraft.server.command.CommandManager
|
||||||
import net.minecraft.server.command.ServerCommandSource
|
import net.minecraft.server.command.ServerCommandSource
|
||||||
import net.minecraft.text.Style
|
import net.minecraft.text.Style
|
||||||
@@ -18,9 +22,9 @@ import java.util.concurrent.CompletableFuture
|
|||||||
object GiveEmitterTool {
|
object GiveEmitterTool {
|
||||||
fun register(dispatcher: CommandDispatcher<ServerCommandSource>, registryAccess: CommandRegistryAccess) {
|
fun register(dispatcher: CommandDispatcher<ServerCommandSource>, registryAccess: CommandRegistryAccess) {
|
||||||
val command = CommandManager.literal("GiveEmitterTool")
|
val command = CommandManager.literal("GiveEmitterTool")
|
||||||
.requires { source -> source.hasPermissionLevel(4) }
|
.requires { source -> source.hasPermissionLevel(2) }
|
||||||
|
|
||||||
val allEmitterIds = emitterIdRegister.keys
|
val allEmitterIds = idRegister.keys
|
||||||
|
|
||||||
val itemNameArg = CommandManager.argument("Item Name", StringArgumentType.string())
|
val itemNameArg = CommandManager.argument("Item Name", StringArgumentType.string())
|
||||||
val itemTypeArg = CommandManager.argument("Item Type", ItemStackArgumentType.itemStack(registryAccess))
|
val itemTypeArg = CommandManager.argument("Item Type", ItemStackArgumentType.itemStack(registryAccess))
|
||||||
@@ -33,26 +37,42 @@ object GiveEmitterTool {
|
|||||||
CompletableFuture.completedFuture(builder.build())
|
CompletableFuture.completedFuture(builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatcher.register(
|
dispatcher.register(command
|
||||||
command
|
.then(emitterIdSuggestion
|
||||||
.then(itemNameArg
|
.then(itemTypeArg
|
||||||
.then(itemTypeArg
|
.then(itemNameArg
|
||||||
.then(emitterIdSuggestion
|
.executes { context ->
|
||||||
.executes { context ->
|
val name = StringArgumentType.getString(context, "Item Name")
|
||||||
val name = StringArgumentType.getString(context, "Item Name")
|
val item = ItemStackArgumentType.getItemStackArgument(context, "Item Type")
|
||||||
val item = ItemStackArgumentType.getItemStackArgument(context, "Item Type")
|
val emitterId = StringArgumentType.getString(context, "Emitter ID")
|
||||||
val emitterId = StringArgumentType.getString(context, "Emitter ID")
|
|
||||||
|
|
||||||
val feedback = Text.literal("BPS - You like fancy particles. Don't you?\nBPS - gave particle emitter, bound to ID: \"${emitterId}\".")
|
exec(context, emitterId, item.item, name)
|
||||||
.setStyle(Style.EMPTY.withColor(TextColor.fromRgb(Color(0, 200, 0).rgb)))
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
context.source.player?.inventory?.insertStack(ParticleEmitterTool.makeData(item.item, name, emitterId))
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
.executes { context ->
|
||||||
|
val item = ItemStackArgumentType.getItemStackArgument(context, "Item Type")
|
||||||
|
val emitterId = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
|
||||||
|
exec(context, emitterId, item.item, emitterId)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
.executes { context ->
|
||||||
|
val emitterId = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
|
||||||
|
exec(context, emitterId, Items.AMETHYST_SHARD, emitterId)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun exec(context: CommandContext<ServerCommandSource>, emitterId: String, itemType: Item, itemName: String) {
|
||||||
|
val feedback = Text.literal("BPS - You like fancy particles. Don't you?\nBPS - gave particle emitter, bound to ID: \"${emitterId}\".")
|
||||||
|
.setStyle(Style.EMPTY.withColor(TextColor.fromRgb(Color(0, 200, 0).rgb)))
|
||||||
|
|
||||||
|
context.source.player?.inventory?.insertStack(ParticleEmitterTool.makeData(itemType, itemName, emitterId))
|
||||||
|
context.source.sendFeedback({ feedback }, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,11 +16,13 @@ object KillAllEmitters {
|
|||||||
|
|
||||||
dispatcher.register(
|
dispatcher.register(
|
||||||
command.executes { context ->
|
command.executes { context ->
|
||||||
|
var amountEmitters = 0
|
||||||
for (emitter in ALL_PARTICLE_EMITTERS) {
|
for (emitter in ALL_PARTICLE_EMITTERS) {
|
||||||
emitter.kill()
|
emitter.kill()
|
||||||
|
amountEmitters += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
val feedback = Text.literal("BPS - Killed all living Emitters.")
|
val feedback = Text.literal("BPS - Killed all living Emitters. [$amountEmitters]")
|
||||||
.setStyle(Style.EMPTY.withColor(Color(0, 200, 0).rgb))
|
.setStyle(Style.EMPTY.withColor(Color(0, 200, 0).rgb))
|
||||||
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
context.source.sendFeedback({ feedback }, false)
|
||||||
|
|||||||
@@ -1,602 +0,0 @@
|
|||||||
package com.bytedice.bde_particles.commands
|
|
||||||
|
|
||||||
import com.bytedice.bde_particles.InterpolationCurves
|
|
||||||
import com.bytedice.bde_particles.particles.*
|
|
||||||
import com.mojang.brigadier.Command
|
|
||||||
import com.mojang.brigadier.arguments.BoolArgumentType
|
|
||||||
import com.mojang.brigadier.arguments.FloatArgumentType
|
|
||||||
import com.mojang.brigadier.arguments.IntegerArgumentType
|
|
||||||
import com.mojang.brigadier.arguments.StringArgumentType
|
|
||||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder
|
|
||||||
import com.mojang.brigadier.context.CommandContext
|
|
||||||
import net.minecraft.server.command.CommandManager
|
|
||||||
import net.minecraft.server.command.ServerCommandSource
|
|
||||||
import net.minecraft.text.MutableText
|
|
||||||
import net.minecraft.text.Style
|
|
||||||
import net.minecraft.text.Text
|
|
||||||
import org.joml.Vector2f
|
|
||||||
import org.joml.Vector3f
|
|
||||||
import java.awt.Color
|
|
||||||
import java.util.concurrent.CompletableFuture
|
|
||||||
|
|
||||||
|
|
||||||
// CLOSE THIS FILE IMMEDIATELY, YOU ARE ENTERING EGYPTIAN (pyramid) AND ITALIAN (spaghetti) TERRITORY
|
|
||||||
|
|
||||||
val curves = arrayOf("LINEAR", "SQRT", "EXPONENT", "CUBIC", "SINE", "COSINE", "INVERSE", "LOG", "EXP", "BOUNCE")
|
|
||||||
|
|
||||||
|
|
||||||
fun updateEmitterParam(context: CommandContext<ServerCommandSource>, paramName: String, typeValue: Any) : MutableText {
|
|
||||||
val emitterIdVal = StringArgumentType.getString(context, "Registered Emitter ID")
|
|
||||||
val success = updateSingleEmitterParam(emitterIdVal, paramName, typeValue)
|
|
||||||
|
|
||||||
val feedback = if (success) {
|
|
||||||
Text.literal("BPS - Successfully updated Emitter ID \"$emitterIdVal\": Parameter \"$paramName\", value \"$typeValue\"")
|
|
||||||
.setStyle(Style.EMPTY.withColor(Color(0, 200, 0).rgb))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Text.literal("BPS - Failed to update Emitter ID \"$emitterIdVal\": Parameter \"$paramName\", value \"$typeValue\": Unknown Error\n" +
|
|
||||||
"Try checking if the emitter and parameter exist, and the value is the correct type.")
|
|
||||||
.setStyle(Style.EMPTY.withColor(Color(200, 0, 0).rgb))
|
|
||||||
}
|
|
||||||
|
|
||||||
return feedback
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun updateParticleParam(context: CommandContext<ServerCommandSource>, paramName: String, typeValue: Any) : MutableText {
|
|
||||||
val emitterIdVal = StringArgumentType.getString(context, "Registered Emitter ID")
|
|
||||||
val success = updateSingleParticleParam(emitterIdVal, paramName, typeValue)
|
|
||||||
|
|
||||||
val feedback = if (success) {
|
|
||||||
Text.literal("BPS - Successfully updated Particle of Emitter ID \"$emitterIdVal\": parameter \"$paramName\", value \"$typeValue\"")
|
|
||||||
.setStyle(Style.EMPTY.withColor(Color(0, 200, 0).rgb))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Text.literal("BPS - Failed to update Emitter ID \"$emitterIdVal\": parameter \"$paramName\", value \"$typeValue\": Unknown Error\n" +
|
|
||||||
"Try checking if the Emitter ID and parameter exist, and the value is the correct type.")
|
|
||||||
.setStyle(Style.EMPTY.withColor(Color(200, 0, 0).rgb))
|
|
||||||
}
|
|
||||||
|
|
||||||
return feedback
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ArgConfigEmitterKeys {
|
|
||||||
fun maxCount() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("maxCount")
|
|
||||||
.then(CommandManager.argument("Int", IntegerArgumentType.integer())
|
|
||||||
.executes { context ->
|
|
||||||
val typeValue = IntegerArgumentType.getInteger(context, "Int")
|
|
||||||
val feedback = updateEmitterParam(context, "maxCount", typeValue)
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fun spawnsPerTick() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("spawnsPerTick")
|
|
||||||
.then(CommandManager.argument("Int", IntegerArgumentType.integer())
|
|
||||||
.executes { context ->
|
|
||||||
val typeValue = IntegerArgumentType.getInteger(context, "Int")
|
|
||||||
val feedback = updateEmitterParam(context, "spawnsPerTick", typeValue)
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fun loopDur() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("loopDur")
|
|
||||||
.then(CommandManager.argument("Int", IntegerArgumentType.integer())
|
|
||||||
.executes { context ->
|
|
||||||
val typeValue = IntegerArgumentType.getInteger(context, "Int")
|
|
||||||
val feedback = updateEmitterParam(context, "loopDur", typeValue)
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fun loopDelay() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("loopDelay")
|
|
||||||
.then(CommandManager.argument("Int", IntegerArgumentType.integer())
|
|
||||||
.executes { context ->
|
|
||||||
val typeValue = IntegerArgumentType.getInteger(context, "Int")
|
|
||||||
val feedback = updateEmitterParam(context, "loopDelay", typeValue)
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fun loopCount() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("loopCount")
|
|
||||||
.then(CommandManager.argument("Int", IntegerArgumentType.integer())
|
|
||||||
.executes { context ->
|
|
||||||
val typeValue = IntegerArgumentType.getInteger(context, "Int")
|
|
||||||
val feedback = updateEmitterParam(context, "loopCount", typeValue)
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ArgConfigParticleKeys {
|
|
||||||
fun shape() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("shape")
|
|
||||||
.then(CommandManager.literal("circle")
|
|
||||||
.then(CommandManager.argument("Radius", FloatArgumentType.floatArg())
|
|
||||||
.executes { context ->
|
|
||||||
val radius = FloatArgumentType.getFloat(context, "Radius")
|
|
||||||
val feedback = updateParticleParam(context, "shape", SpawningShape.CIRCLE(radius))
|
|
||||||
context.source.sendFeedback( { feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then(CommandManager.literal("rect")
|
|
||||||
.then(CommandManager.argument("Width", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Height", FloatArgumentType.floatArg())
|
|
||||||
.executes { context ->
|
|
||||||
val w = FloatArgumentType.getFloat(context, "Width")
|
|
||||||
val h = FloatArgumentType.getFloat(context, "Height")
|
|
||||||
val feedback = updateParticleParam(context, "shape", SpawningShape.RECT(Vector2f(w, h)))
|
|
||||||
context.source.sendFeedback( { feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then(CommandManager.literal("cube")
|
|
||||||
.then(CommandManager.argument("X", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Y", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Z", FloatArgumentType.floatArg())
|
|
||||||
.executes { context ->
|
|
||||||
val x = FloatArgumentType.getFloat(context, "X")
|
|
||||||
val y = FloatArgumentType.getFloat(context, "Y")
|
|
||||||
val z = FloatArgumentType.getFloat(context, "Z")
|
|
||||||
val feedback = updateParticleParam(context, "shape", SpawningShape.CUBE(Vector3f(x, y, z)))
|
|
||||||
context.source.sendFeedback( { feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then(CommandManager.literal("sphere")
|
|
||||||
.then(CommandManager.argument("Radius", FloatArgumentType.floatArg())
|
|
||||||
.executes { context ->
|
|
||||||
val radius = FloatArgumentType.getFloat(context, "Radius")
|
|
||||||
val feedback = updateParticleParam(context, "shape", SpawningShape.SPHERE(radius))
|
|
||||||
context.source.sendFeedback( { feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then(CommandManager.literal("point")
|
|
||||||
.executes { context ->
|
|
||||||
val feedback = updateParticleParam(context, "shape", SpawningShape.POINT)
|
|
||||||
context.source.sendFeedback( { feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fun blockCurve() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("blockCurve")
|
|
||||||
.then(CommandManager.argument("Array (separate by comma)", StringArgumentType.string())
|
|
||||||
.executes { context ->
|
|
||||||
val typeValue = StringArgumentType.getString(context, "Array (separate by comma)")
|
|
||||||
val typeArray = typeValue.split(",").toTypedArray()
|
|
||||||
val feedback = updateParticleParam(context, "blockCurve", typeArray)
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fun rotRandom() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("rotRandom")
|
|
||||||
.then(CommandManager.argument("Min X", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Min Y", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Min Z", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Max X", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Max Y", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Max Z", FloatArgumentType.floatArg())
|
|
||||||
.executes { context ->
|
|
||||||
val vectors = x1x2ToPairVector3f(context)
|
|
||||||
val feedback = updateParticleParam(context, "rotRandom", vectors)
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fun rotVelRandom() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("rotVelRandom")
|
|
||||||
.then(CommandManager.argument("Min X", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Min Y", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Min Z", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Max X", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Max Y", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Max Z", FloatArgumentType.floatArg())
|
|
||||||
.executes { context ->
|
|
||||||
val vectors = x1x2ToPairVector3f(context)
|
|
||||||
val feedback = updateParticleParam(context, "rotVelRandom", vectors)
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fun sizeRandom() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("sizeRandom")
|
|
||||||
.then(CommandManager.argument("Min X", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Min Y", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Min Z", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Max X", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Max Y", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Max Z", FloatArgumentType.floatArg())
|
|
||||||
.executes { context ->
|
|
||||||
val vectors = x1x2ToPairVector3f(context)
|
|
||||||
val feedback = updateParticleParam(context, "sizeRandom", vectors)
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fun uniformSize() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("uniformSize")
|
|
||||||
.then(CommandManager.argument("Bool", BoolArgumentType.bool())
|
|
||||||
.executes { context ->
|
|
||||||
val uniform = BoolArgumentType.getBool(context, "Bool")
|
|
||||||
val feedback = updateParticleParam(context, "uniformSize", uniform)
|
|
||||||
context.source.sendFeedback( { feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fun velRandom() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("velRandom")
|
|
||||||
.then(CommandManager.argument("Min X", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Min Y", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Min Z", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Max X", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Max Y", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Max Z", FloatArgumentType.floatArg())
|
|
||||||
.executes { context ->
|
|
||||||
val vectors = x1x2ToPairVector3f(context)
|
|
||||||
val feedback = updateParticleParam(context, "velRandom", vectors)
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fun forceFields() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("forceFields")
|
|
||||||
.then(argAddForceField())
|
|
||||||
.then(argRemoveForceField())
|
|
||||||
}
|
|
||||||
fun gravity() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("gravity")
|
|
||||||
.then(CommandManager.argument("X", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Y", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Z", FloatArgumentType.floatArg())
|
|
||||||
.executes { context ->
|
|
||||||
val x1 = FloatArgumentType.getFloat(context, "X")
|
|
||||||
val y1 = FloatArgumentType.getFloat(context, "Y")
|
|
||||||
val z1 = FloatArgumentType.getFloat(context, "Z")
|
|
||||||
|
|
||||||
val feedback = updateParticleParam(context, "gravity", Vector3f(x1, y1, z1))
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fun drag() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("drag")
|
|
||||||
.then(CommandManager.argument("Float", FloatArgumentType.floatArg())
|
|
||||||
.executes { context ->
|
|
||||||
val value = FloatArgumentType.getFloat(context, "Float")
|
|
||||||
val feedback = updateParticleParam(context, "drag", value)
|
|
||||||
context.source.sendFeedback( { feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fun minVel() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("minVel")
|
|
||||||
.then(CommandManager.argument("Float", FloatArgumentType.floatArg())
|
|
||||||
.executes { context ->
|
|
||||||
val value = FloatArgumentType.getFloat(context, "Float")
|
|
||||||
val feedback = updateParticleParam(context, "minVel", value)
|
|
||||||
context.source.sendFeedback( { feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fun lifeTime() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("lifeTime")
|
|
||||||
.then(CommandManager.argument("min", IntegerArgumentType.integer())
|
|
||||||
.then(CommandManager.argument("max", IntegerArgumentType.integer())
|
|
||||||
.executes { context ->
|
|
||||||
val min = IntegerArgumentType.getInteger(context, "min")
|
|
||||||
val max = IntegerArgumentType.getInteger(context, "max")
|
|
||||||
val feedback = updateParticleParam(context, "lifeTime", Pair(min, max))
|
|
||||||
context.source.sendFeedback( { feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fun rotVelCurve() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("rotVelCurve")
|
|
||||||
.then(CommandManager.argument("Min X", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Min Y", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Min Z", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Max X", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Max Y", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Max Z", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Curve", StringArgumentType.string())
|
|
||||||
.suggests { _, builder ->
|
|
||||||
curves.forEach { builder.suggest(it) }
|
|
||||||
CompletableFuture.completedFuture(builder.build())
|
|
||||||
}
|
|
||||||
.executes { context ->
|
|
||||||
val vectors = x1x2ToPairVector3f(context)
|
|
||||||
val curveString = StringArgumentType.getString(context, "Curve")
|
|
||||||
|
|
||||||
val feedback = updateParticleParam(
|
|
||||||
context,
|
|
||||||
"rotVelCurve",
|
|
||||||
Triple(vectors.first, vectors.second, getCurveByString(curveString)))
|
|
||||||
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fun sizeCurve() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("sizeCurve")
|
|
||||||
.then(CommandManager.argument("Min X", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Min Y", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Min Z", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Max X", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Max Y", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Max Z", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Curve", StringArgumentType.string())
|
|
||||||
.suggests { _, builder ->
|
|
||||||
curves.forEach { builder.suggest(it) }
|
|
||||||
CompletableFuture.completedFuture(builder.build())
|
|
||||||
}
|
|
||||||
.executes { context ->
|
|
||||||
val vectors = x1x2ToPairVector3f(context)
|
|
||||||
val curveString = StringArgumentType.getString(context, "Curve")
|
|
||||||
|
|
||||||
val feedback = updateParticleParam(
|
|
||||||
context,
|
|
||||||
"sizeCurve",
|
|
||||||
Triple(vectors.first, vectors.second, getCurveByString(curveString)))
|
|
||||||
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun getCurveByString(str: String) : InterpolationCurves {
|
|
||||||
return when (str) {
|
|
||||||
"LINEAR" -> InterpolationCurves.LINEAR
|
|
||||||
"SQRT" -> InterpolationCurves.SQRT
|
|
||||||
"EXPONENT" -> InterpolationCurves.EXPONENT
|
|
||||||
"CUBIC" -> InterpolationCurves.CUBIC
|
|
||||||
"SINE" -> InterpolationCurves.SINE
|
|
||||||
"COSINE" -> InterpolationCurves.COSINE
|
|
||||||
"INVERSE" -> InterpolationCurves.INVERSE
|
|
||||||
"LOG" -> InterpolationCurves.LOG
|
|
||||||
"EXP" -> InterpolationCurves.EXP
|
|
||||||
"BOUNCE" -> InterpolationCurves.BOUNCE
|
|
||||||
else -> InterpolationCurves.LINEAR
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun x1x2ToPairVector3f(context: CommandContext<ServerCommandSource>) : Pair<Vector3f, Vector3f> {
|
|
||||||
val x1 = FloatArgumentType.getFloat(context, "Min X")
|
|
||||||
val y1 = FloatArgumentType.getFloat(context, "Min Y")
|
|
||||||
val z1 = FloatArgumentType.getFloat(context, "Min Z")
|
|
||||||
val x2 = FloatArgumentType.getFloat(context, "Max X")
|
|
||||||
val y2 = FloatArgumentType.getFloat(context, "Max Y")
|
|
||||||
val z2 = FloatArgumentType.getFloat(context, "Max Z")
|
|
||||||
return Pair(Vector3f(x1, y1, z1), Vector3f(x2, y2, z2))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun argAddForceField() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("add")
|
|
||||||
.then(CommandManager.argument("Name", StringArgumentType.string())
|
|
||||||
.then(CommandManager.argument("X Pos", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Y Pos", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Z Pos", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.literal("sphere")
|
|
||||||
.then(CommandManager.argument("Radius", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Min Force", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Max Force", FloatArgumentType.floatArg())
|
|
||||||
.executes { context -> forceFieldAddSphereExec(context) }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then(CommandManager.literal("cube")
|
|
||||||
.then(CommandManager.argument("X Size", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Y Size", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Z Size", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("X Force", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Y Force", FloatArgumentType.floatArg())
|
|
||||||
.then(CommandManager.argument("Z Force", FloatArgumentType.floatArg())
|
|
||||||
.executes { context -> forceFieldAddCubeExec(context) }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun argRemoveForceField() : LiteralArgumentBuilder<ServerCommandSource> { // TODO fix this error somehow
|
|
||||||
return CommandManager.literal("remove")
|
|
||||||
.then(CommandManager.argument("Force Field Name", StringArgumentType.string())
|
|
||||||
.suggests { context, builder ->
|
|
||||||
val emitterIdVal = StringArgumentType.getString(context, "Registered Emitter ID")
|
|
||||||
|
|
||||||
val params = getEmitterParams(emitterIdVal)
|
|
||||||
val forceFields = params!!.particle.forceFields
|
|
||||||
|
|
||||||
for (forceField in forceFields) {
|
|
||||||
builder.suggest(forceField.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
CompletableFuture.completedFuture(builder.build())
|
|
||||||
}
|
|
||||||
.executes { context ->
|
|
||||||
val ffName = StringArgumentType.getString(context, "Force Field Name")
|
|
||||||
val emitterIdVal = StringArgumentType.getString(context, "Registered Emitter ID")
|
|
||||||
|
|
||||||
val params = getEmitterParams(emitterIdVal)
|
|
||||||
val forceFields = params!!.particle.forceFields
|
|
||||||
val newForceFields: MutableList<ForceField> = forceFields.toMutableList()
|
|
||||||
|
|
||||||
if (forceFields.isEmpty()) {
|
|
||||||
val feedback = Text.literal("BPS - There are no force fields for Emitter ID \"$emitterIdVal\".")
|
|
||||||
.setStyle(Style.EMPTY.withColor(Color(200, 0, 0).rgb))
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
for (forceField in forceFields) {
|
|
||||||
if (forceField.name == ffName) {
|
|
||||||
newForceFields.apply { remove(forceField) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val feedback = updateParticleParam(
|
|
||||||
context,
|
|
||||||
"forceFields",
|
|
||||||
newForceFields.toTypedArray()
|
|
||||||
)
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun forceFieldAddSphereExec(context: CommandContext<ServerCommandSource>) : Int {
|
|
||||||
val emitterIdVal = StringArgumentType.getString(context, "Registered Emitter ID")
|
|
||||||
|
|
||||||
val radius = FloatArgumentType.getFloat(context, "Radius")
|
|
||||||
val minForce = FloatArgumentType.getFloat(context, "Min Force")
|
|
||||||
val maxForce = FloatArgumentType.getFloat(context, "Max Force")
|
|
||||||
|
|
||||||
val shape = ForceFieldShape.SPHERE(radius, Pair(minForce, maxForce))
|
|
||||||
val forceField = forceFieldGenericParams(context, shape)
|
|
||||||
|
|
||||||
val currentParams = getEmitterParams(emitterIdVal)
|
|
||||||
val forceFields = currentParams!!.particle.forceFields
|
|
||||||
val addedArray = forceFields.toMutableList().apply { add(forceField) }.toTypedArray()
|
|
||||||
|
|
||||||
val feedback = updateParticleParam(
|
|
||||||
context,
|
|
||||||
"forceFields",
|
|
||||||
addedArray
|
|
||||||
)
|
|
||||||
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
|
|
||||||
return Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun forceFieldAddCubeExec(context: CommandContext<ServerCommandSource>) : Int {
|
|
||||||
val xSize = FloatArgumentType.getFloat(context, "X Size")
|
|
||||||
val ySize = FloatArgumentType.getFloat(context, "Y Size")
|
|
||||||
val zSize = FloatArgumentType.getFloat(context, "Z Size")
|
|
||||||
val xForce = FloatArgumentType.getFloat(context, "X Force")
|
|
||||||
val yForce = FloatArgumentType.getFloat(context, "Y Force")
|
|
||||||
val zForce = FloatArgumentType.getFloat(context, "Z Force")
|
|
||||||
val emitterIdVal = StringArgumentType.getString(context, "Registered Emitter ID")
|
|
||||||
|
|
||||||
val shape = ForceFieldShape.CUBE(Vector3f(xSize, ySize, zSize), Vector3f(xForce, yForce, zForce))
|
|
||||||
val forceField = forceFieldGenericParams(context, shape)
|
|
||||||
|
|
||||||
val currentParams = getEmitterParams(emitterIdVal)
|
|
||||||
val forceFields = currentParams!!.particle.forceFields
|
|
||||||
val addedArray = forceFields.toMutableList().apply { add(forceField) }.toTypedArray()
|
|
||||||
|
|
||||||
|
|
||||||
val feedback = updateParticleParam(
|
|
||||||
context,
|
|
||||||
"forceFields",
|
|
||||||
addedArray
|
|
||||||
)
|
|
||||||
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
|
|
||||||
return Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun forceFieldGenericParams(context: CommandContext<ServerCommandSource>, shape: ForceFieldShape) : ForceField {
|
|
||||||
val name = StringArgumentType.getString(context, "Name")
|
|
||||||
val posX = FloatArgumentType.getFloat(context, "X Pos")
|
|
||||||
val posY = FloatArgumentType.getFloat(context, "Y Pos")
|
|
||||||
val posZ = FloatArgumentType.getFloat(context, "Z Pos")
|
|
||||||
|
|
||||||
return ForceField(
|
|
||||||
name.replace(" ", "_"),
|
|
||||||
Vector3f(posX, posY, posZ),
|
|
||||||
shape
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,30 +1,58 @@
|
|||||||
package com.bytedice.bde_particles.commands
|
package com.bytedice.bde_particles.commands
|
||||||
|
|
||||||
import com.bytedice.bde_particles.emitterParamsToJson
|
import com.bytedice.bde_particles.emitterListArg
|
||||||
|
import com.bytedice.bde_particles.negativeFeedback
|
||||||
import com.bytedice.bde_particles.particles.*
|
import com.bytedice.bde_particles.particles.*
|
||||||
|
import com.bytedice.bde_particles.positiveFeedback
|
||||||
import com.mojang.brigadier.Command
|
import com.mojang.brigadier.Command
|
||||||
import com.mojang.brigadier.CommandDispatcher
|
import com.mojang.brigadier.CommandDispatcher
|
||||||
import com.mojang.brigadier.arguments.IntegerArgumentType
|
|
||||||
import com.mojang.brigadier.arguments.StringArgumentType
|
import com.mojang.brigadier.arguments.StringArgumentType
|
||||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder
|
|
||||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder
|
import com.mojang.brigadier.builder.RequiredArgumentBuilder
|
||||||
import net.minecraft.component.DataComponentTypes
|
import com.mojang.brigadier.context.CommandContext
|
||||||
import net.minecraft.component.type.NbtComponent
|
import net.minecraft.command.CommandRegistryAccess
|
||||||
import net.minecraft.item.ItemStack
|
|
||||||
import net.minecraft.item.Items
|
|
||||||
import net.minecraft.nbt.NbtCompound
|
|
||||||
import net.minecraft.server.command.CommandManager
|
import net.minecraft.server.command.CommandManager
|
||||||
import net.minecraft.server.command.ServerCommandSource
|
import net.minecraft.server.command.ServerCommandSource
|
||||||
import net.minecraft.text.MutableText
|
|
||||||
import net.minecraft.text.Style
|
import net.minecraft.text.Style
|
||||||
import net.minecraft.text.Text
|
import net.minecraft.text.Text
|
||||||
import net.minecraft.text.TextColor
|
|
||||||
import java.awt.Color
|
import java.awt.Color
|
||||||
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<ForceField>,
|
||||||
|
constVel: Vector3f,
|
||||||
|
drag: Float,
|
||||||
|
minVel: Float,
|
||||||
|
// curves
|
||||||
|
offsetCurve: ParamClasses.LerpVal,
|
||||||
|
rotVelCurve: ParamClasses.LerpVal,
|
||||||
|
scaleCurve: ParamClasses.LerpVal,
|
||||||
|
blockCurve: Pair<Array<String>, LerpCurves>,
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
// ManageEmitters
|
// ManageEmitters
|
||||||
// <create / remove / config / list>
|
// <create / remove / config / list / copy>
|
||||||
// create
|
// create
|
||||||
// <emitter id>
|
// <emitter id>
|
||||||
// <preset emitter id>
|
// <preset emitter id>
|
||||||
@@ -36,9 +64,8 @@ import java.util.concurrent.CompletableFuture
|
|||||||
|
|
||||||
// config
|
// config
|
||||||
// <emitter id>
|
// <emitter id>
|
||||||
// <PARTICLE / EMITTER>
|
// <param key>
|
||||||
// <param key> // HARDCODED KEYS, if EMITTER use emitter params.
|
// <new param value>
|
||||||
// <new param value> // HARDCODED TYPES
|
|
||||||
// output -> update the selected parameter of the selected emitter id to the new value
|
// output -> update the selected parameter of the selected emitter id to the new value
|
||||||
|
|
||||||
// list
|
// list
|
||||||
@@ -50,219 +77,121 @@ import java.util.concurrent.CompletableFuture
|
|||||||
|
|
||||||
|
|
||||||
object ManageEmitters {
|
object ManageEmitters {
|
||||||
fun register(dispatcher: CommandDispatcher<ServerCommandSource>) {
|
|
||||||
|
fun register(dispatcher: CommandDispatcher<ServerCommandSource>, registryAccess: CommandRegistryAccess) {
|
||||||
val command = CommandManager.literal("ManageEmitters")
|
val command = CommandManager.literal("ManageEmitters")
|
||||||
.requires { source -> source.hasPermissionLevel(4) }
|
.requires { source -> source.hasPermissionLevel(2) }
|
||||||
|
|
||||||
dispatcher.register(
|
dispatcher.register(
|
||||||
command
|
command
|
||||||
// <create / remove / config / list / copy>
|
.then(CommandManager.literal("create").then(create()))
|
||||||
.then(argCreate())
|
.then(CommandManager.literal("remove").then(remove()))
|
||||||
.then(argRemove())
|
.then(CommandManager.literal("config").then(config(registryAccess)))
|
||||||
.then(argConfig())
|
.then(CommandManager.literal("list")
|
||||||
.then(argList())
|
.executes { context ->
|
||||||
.then(argCopy())
|
val emitterIdList = idRegister.keys.sorted().joinToString(" ")
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
val feedback = Text.literal("List of all registered Emitter IDs:\n$emitterIdList")
|
||||||
|
.setStyle(Style.EMPTY.withColor(Color(0, 200, 0).rgb))
|
||||||
|
|
||||||
val allEmitterIds = emitterIdRegister.keys
|
context.source.sendFeedback({ feedback }, false)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
// TODO: perhaps make this a Command.literal() if possible
|
}
|
||||||
val emitterIdSuggestion: RequiredArgumentBuilder<ServerCommandSource, String> = CommandManager.argument("Registered Emitter ID", StringArgumentType.string())
|
)
|
||||||
.suggests { _, builder ->
|
//.then(CommandManager.literal("copy"))
|
||||||
allEmitterIds.forEach { key ->
|
)
|
||||||
builder.suggest(key)
|
|
||||||
}
|
|
||||||
CompletableFuture.completedFuture(builder.build())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val emitterConfigKeys = ArgConfigEmitterKeys()
|
private fun create() : RequiredArgumentBuilder<ServerCommandSource, String> {
|
||||||
val particleConfigKeys = ArgConfigParticleKeys()
|
return CommandManager.argument("New Emitter ID", StringArgumentType.string())
|
||||||
|
.then(emitterListArg("Preset Emitter ID")
|
||||||
|
|
||||||
// create
|
|
||||||
// <emitter id>
|
|
||||||
// <preset emitter id>
|
|
||||||
// output -> register new emitter with a preset
|
|
||||||
fun argCreate() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("create")
|
|
||||||
.then(CommandManager.argument("Emitter ID", StringArgumentType.string())
|
|
||||||
.then(emitterIdSuggestion
|
|
||||||
.executes { context ->
|
.executes { context ->
|
||||||
val emitterIdVal = StringArgumentType.getString(context, "Emitter ID")
|
val newEmitterId = StringArgumentType.getString(context, "New Emitter ID")
|
||||||
val emitterPresetVal = StringArgumentType.getString(context, "Registered Emitter ID") ?: "DEFAULT"
|
val presetEmitterId = StringArgumentType.getString(context, "Preset Emitter ID")
|
||||||
val emitterPresetParams = getEmitterParams(emitterPresetVal)
|
|
||||||
|
|
||||||
var newEmitterId = "NULL"
|
createExec(context, newEmitterId, presetEmitterId)
|
||||||
val emitterRegisterData: Pair<String, Boolean>
|
|
||||||
|
|
||||||
if (emitterPresetParams != null) {
|
|
||||||
emitterRegisterData = addToEmitterRegister(emitterIdVal, emitterPresetParams)
|
|
||||||
newEmitterId = emitterRegisterData.first
|
|
||||||
}
|
|
||||||
else { emitterRegisterData = Pair("NULL", false) }
|
|
||||||
|
|
||||||
val feedback = if (emitterRegisterData.second) {
|
|
||||||
Text.literal("BPS - Created particle emitter with ID \"$newEmitterId\".\n" +
|
|
||||||
"BPS - This emitter will be removed on server restart! Use \"/ManageEmitters copy\" to save the data to your clipboard!")
|
|
||||||
.setStyle(Style.EMPTY.withColor(TextColor.fromRgb(Color(0, 200, 0).rgb)))
|
|
||||||
}
|
|
||||||
else if (newEmitterId !in allEmitterIds) {
|
|
||||||
Text.literal("BPS - Preset Emitter ID \"$emitterPresetVal\" doesn't exist!\n" +
|
|
||||||
"BPS - Use \"/ManageEmitters list\" to view all Emitter ID's.")
|
|
||||||
.setStyle(Style.EMPTY.withColor(TextColor.fromRgb(Color(200, 0, 0).rgb)))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Text.literal("BPS - Emitter ID \"$newEmitterId\" already exists\n" +
|
|
||||||
"BPS - Use \"/ManageEmitters list\" to view all Emitter ID's.")
|
|
||||||
.setStyle(Style.EMPTY.withColor(TextColor.fromRgb(Color(200, 0, 0).rgb)))
|
|
||||||
}
|
|
||||||
|
|
||||||
context.source.sendFeedback( { feedback }, false )
|
|
||||||
Command.SINGLE_SUCCESS
|
Command.SINGLE_SUCCESS
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove
|
|
||||||
// <emitter id>
|
|
||||||
// output -> removes the particle with that id
|
|
||||||
fun argRemove() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("remove")
|
|
||||||
.then(emitterIdSuggestion
|
|
||||||
.executes { context ->
|
.executes { context ->
|
||||||
val emitterIdVal = StringArgumentType.getString(context, "Registered Emitter ID") ?: "NULL"
|
val newEmitterId = StringArgumentType.getString(context, "New Emitter ID")
|
||||||
val isRemoved = removeFromEmitterRegister(emitterIdVal)
|
|
||||||
|
|
||||||
val feedback = if (isRemoved) {
|
createExec(context, newEmitterId, "")
|
||||||
Text.literal("BPS - Removed particle emitter with ID \"$emitterIdVal\".")
|
|
||||||
.setStyle(Style.EMPTY.withColor(TextColor.fromRgb(Color(0, 200, 0).rgb)))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Text.literal("BPS - Emitter ID \"$emitterIdVal\" doesn't exist or is reserved!\n" +
|
|
||||||
"BPS - Use \"/ManageEmitters list\" to view all Emitter ID's.")
|
|
||||||
.setStyle(Style.EMPTY.withColor(TextColor.fromRgb(Color(200, 0, 0).rgb)))
|
|
||||||
}
|
|
||||||
|
|
||||||
context.source.sendFeedback( { feedback }, false )
|
|
||||||
Command.SINGLE_SUCCESS
|
Command.SINGLE_SUCCESS
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createExec(context: CommandContext<ServerCommandSource>, newEmitterId: String, presetEmitterId: String) {
|
||||||
|
val params = getEmitterDataById(presetEmitterId)
|
||||||
|
val usedDefault = params == null
|
||||||
|
|
||||||
|
val result = addToRegister(
|
||||||
|
newEmitterId,
|
||||||
|
(params ?: EmitterParams.DEFAULT).deepCopy(),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// list
|
if (usedDefault && result.second) {
|
||||||
// output -> all registered emitters
|
positiveFeedback(
|
||||||
fun argList() : LiteralArgumentBuilder<ServerCommandSource> {
|
"Successfully added new Emitter with ID \"${result.first}\" to register.\n" +
|
||||||
return CommandManager.literal("list")
|
"(DEFAULT preset was used because the specified Preset Emitter ID wasn't valid.)",
|
||||||
.executes { context ->
|
context
|
||||||
val allEmittersString = allEmitterIds.joinToString(", ")
|
)
|
||||||
|
}
|
||||||
|
else if (!usedDefault && result.second) {
|
||||||
|
positiveFeedback(
|
||||||
|
"Successfully added new Emitter with ID \"${result.first}\" and Preset Emitter ID \"$presetEmitterId\" to register.",
|
||||||
|
context
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
negativeFeedback(
|
||||||
|
"Failed to add new Emitter with ID \"${result.first}\" to register.\n" +
|
||||||
|
"The provided Emitter ID is either reserved for system use or already in use.",
|
||||||
|
context
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val feedbackStatic = Text.literal("BPS - List of all available Emitter IDs:\n")
|
private fun remove() : RequiredArgumentBuilder<ServerCommandSource, String> {
|
||||||
.setStyle(Style.EMPTY.withColor(TextColor.fromRgb(Color(0, 200, 0).rgb)))
|
return emitterListArg("Emitter ID")
|
||||||
|
.executes { context ->
|
||||||
|
val emitterId = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
|
||||||
val feedbackVar = Text.literal(allEmittersString)
|
val result = removeFromRegister(emitterId)
|
||||||
.setStyle(Style.EMPTY.withColor(TextColor.fromRgb(Color(255, 255, 255).rgb)))
|
|
||||||
|
|
||||||
val feedback = feedbackStatic.append(feedbackVar)
|
if (result) {
|
||||||
|
positiveFeedback(
|
||||||
|
"Successfully removed Emitter ID \"$emitterId\" from register.",
|
||||||
|
context
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
negativeFeedback(
|
||||||
|
"Failed to remove Emitter ID \"$emitterId\" from register.\nThe provided Emitter ID is either reserved for system use or already in use.",
|
||||||
|
context
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun config(registryAccess: CommandRegistryAccess) : RequiredArgumentBuilder<ServerCommandSource, String> {
|
||||||
|
val emitterList = emitterListArg("Emitter ID")
|
||||||
|
|
||||||
|
val configArg = listConfigArgs(dataClassToArray(EmitterParams::class), emitterList, registryAccess) as RequiredArgumentBuilder<ServerCommandSource, String>
|
||||||
|
|
||||||
|
return configArg
|
||||||
|
}
|
||||||
|
|
||||||
|
// I am not going to torture myself by converting this to a json.
|
||||||
|
/*
|
||||||
|
private fun copy() : RequiredArgumentBuilder<ServerCommandSource, String> {
|
||||||
|
return emitterList("Emitter ID").executes { context ->
|
||||||
|
val emitterId = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
Command.SINGLE_SUCCESS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
// copy
|
|
||||||
// <emitter id>
|
|
||||||
// output -> give player a command block with the particle params (should be paste-able in kotlin)
|
|
||||||
fun argCopy() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("copy")
|
|
||||||
.then(emitterIdSuggestion
|
|
||||||
.executes { context ->
|
|
||||||
val emitterIdVal = StringArgumentType.getString(context, "Registered Emitter ID")
|
|
||||||
val commandBlock = makeCommandBlock(
|
|
||||||
emitterParamsToJson(
|
|
||||||
getEmitterParams(
|
|
||||||
emitterIdVal
|
|
||||||
)!!
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val feedback: MutableText
|
|
||||||
|
|
||||||
if (context.source.server.areCommandBlocksEnabled()) {
|
|
||||||
feedback = Text.literal("BPS - Copied Emitter data as a JSON. Place down the command block and copy it to your clipboard to use the value.")
|
|
||||||
.setStyle(Style.EMPTY.withColor(Color(0, 200, 0).rgb))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
feedback = Text.literal("BPS - Could not copy Emitter data: Command blocks aren't enabled. Go to server.properties and enable command blocks before retrying.")
|
|
||||||
.setStyle(Style.EMPTY.withColor(Color(200, 0, 0).rgb))
|
|
||||||
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
context.source.player?.inventory?.insertStack(commandBlock)
|
|
||||||
context.source.sendFeedback({ feedback }, false)
|
|
||||||
Command.SINGLE_SUCCESS
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// config
|
|
||||||
// <emitter id>
|
|
||||||
// <PARTICLE / EMITTER>
|
|
||||||
// <param key> // HARDCODED KEYS, if EMITTER use emitter params.
|
|
||||||
// <new param value> // HARDCODED TYPES
|
|
||||||
// output -> update the selected parameter of the selected emitter id to the new value
|
|
||||||
fun argConfig() : LiteralArgumentBuilder<ServerCommandSource> {
|
|
||||||
return CommandManager.literal("config")
|
|
||||||
.then(emitterIdSuggestion
|
|
||||||
.then(CommandManager.literal("emitter")
|
|
||||||
.then(emitterConfigKeys.maxCount())
|
|
||||||
.then(emitterConfigKeys.spawnsPerTick())
|
|
||||||
.then(emitterConfigKeys.loopDur())
|
|
||||||
.then(emitterConfigKeys.loopDelay())
|
|
||||||
.then(emitterConfigKeys.loopCount())
|
|
||||||
)
|
|
||||||
.then(CommandManager.literal("particle")
|
|
||||||
.then(particleConfigKeys.shape())
|
|
||||||
.then(particleConfigKeys.blockCurve())
|
|
||||||
.then(particleConfigKeys.rotRandom())
|
|
||||||
.then(particleConfigKeys.rotVelRandom())
|
|
||||||
.then(particleConfigKeys.sizeRandom())
|
|
||||||
.then(particleConfigKeys.uniformSize())
|
|
||||||
.then(particleConfigKeys.velRandom())
|
|
||||||
.then(particleConfigKeys.forceFields())
|
|
||||||
.then(particleConfigKeys.gravity())
|
|
||||||
.then(particleConfigKeys.drag())
|
|
||||||
.then(particleConfigKeys.minVel())
|
|
||||||
.then(particleConfigKeys.lifeTime())
|
|
||||||
.then(particleConfigKeys.rotVelCurve())
|
|
||||||
.then(particleConfigKeys.sizeCurve())
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun makeCommandBlock(emitterData: Map<String, Any>): ItemStack {
|
|
||||||
val i = ItemStack(Items.COMMAND_BLOCK)
|
|
||||||
|
|
||||||
val nbt = NbtCompound()
|
|
||||||
nbt.putString("Command", emitterData.toString())
|
|
||||||
nbt.putString("id", "minecraft:command_block")
|
|
||||||
nbt.putByte("powered", 0)
|
|
||||||
nbt.putByte("auto", 0)
|
|
||||||
nbt.putByte("UpdateLastExecution", 1)
|
|
||||||
nbt.putByte("conditionMet", 0)
|
|
||||||
nbt.putByte("TrackOutput", 1)
|
|
||||||
nbt.putInt("SuccessCount", 0)
|
|
||||||
|
|
||||||
|
|
||||||
val component: NbtComponent = NbtComponent.of(nbt)
|
|
||||||
|
|
||||||
i.set(DataComponentTypes.BLOCK_ENTITY_DATA, component)
|
|
||||||
|
|
||||||
return i
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.bytedice.bde_particles.commands
|
||||||
|
|
||||||
|
import com.bytedice.bde_particles.*
|
||||||
|
import com.bytedice.bde_particles.particles.ParticleEmitter
|
||||||
|
import com.bytedice.bde_particles.particles.getEmitterDataById
|
||||||
|
import com.mojang.brigadier.Command
|
||||||
|
import com.mojang.brigadier.CommandDispatcher
|
||||||
|
import com.mojang.brigadier.arguments.StringArgumentType
|
||||||
|
import com.mojang.brigadier.context.CommandContext
|
||||||
|
import net.minecraft.command.argument.Vec3ArgumentType
|
||||||
|
import net.minecraft.server.command.CommandManager
|
||||||
|
import net.minecraft.server.command.ServerCommandSource
|
||||||
|
import net.minecraft.util.math.Vec3d
|
||||||
|
import org.joml.Vector2f
|
||||||
|
|
||||||
|
|
||||||
|
object SpawnEmitter {
|
||||||
|
fun register(dispatcher: CommandDispatcher<ServerCommandSource>) {
|
||||||
|
val command = CommandManager.literal("SpawnEmitter")
|
||||||
|
.requires { source -> source.hasPermissionLevel(2) }
|
||||||
|
|
||||||
|
dispatcher.register(
|
||||||
|
command.then(emitterListArg("Emitter ID")
|
||||||
|
.executes { context ->
|
||||||
|
execute(context, context.source.position)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
.then(CommandManager.argument("Pos", Vec3ArgumentType.vec3())
|
||||||
|
.executes { context ->
|
||||||
|
val pos = Vec3ArgumentType.getVec3(context, "Pos")
|
||||||
|
execute(context, pos)
|
||||||
|
Command.SINGLE_SUCCESS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun execute(context: CommandContext<ServerCommandSource>, pos: Vec3d) {
|
||||||
|
val emitterId = StringArgumentType.getString(context, "Emitter ID")
|
||||||
|
val emitterParams = getEmitterDataById(emitterId)
|
||||||
|
val world = context.source.world
|
||||||
|
|
||||||
|
if (emitterParams != null) {
|
||||||
|
val emitter = ParticleEmitter(
|
||||||
|
pos,
|
||||||
|
Vector2f(0.0f, 0.0f),
|
||||||
|
world,
|
||||||
|
emitterParams,
|
||||||
|
world.gameRules.getBoolean(Bde_particles.SHOW_PARTICLE_DEBUG)
|
||||||
|
)
|
||||||
|
|
||||||
|
spawnEmitter(emitter)
|
||||||
|
|
||||||
|
positiveFeedback("Successfully spawned Emitter ID \"$emitterId\"", context)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
negativeFeedback("Failed to spawn Emitter ID \"$emitterId\". Emitter ID doesn't exist!", context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package com.bytedice.bde_particles.particles
|
||||||
|
|
||||||
|
import com.bytedice.bde_particles.Billboard
|
||||||
|
import com.bytedice.bde_particles.DisplayEntity
|
||||||
|
import com.bytedice.bde_particles.DisplayEntityProperties
|
||||||
|
import com.bytedice.bde_particles.SESSION_UUID
|
||||||
|
import net.minecraft.server.world.ServerWorld
|
||||||
|
import net.minecraft.util.math.Vec3d
|
||||||
|
import org.joml.Vector2f
|
||||||
|
import org.joml.Vector3f
|
||||||
|
import org.joml.Vector4f
|
||||||
|
|
||||||
|
class EmitterDebug(
|
||||||
|
rot: Vector2f,
|
||||||
|
eOrigin: Vec3d,
|
||||||
|
spawnPosOffset: Vector3f,
|
||||||
|
shape: SpawningShape,
|
||||||
|
) {
|
||||||
|
private val eOriginDebug = EOrigin(rot, eOrigin)
|
||||||
|
private val shapeDebug = ShapeDebug(eOrigin, spawnPosOffset, shape)
|
||||||
|
private val forceFieldDebug = ForceFieldDebug()
|
||||||
|
|
||||||
|
fun spawn(world: ServerWorld, eOrigin: Vec3d, forceFields: ParamClasses.ForceFieldArray, spawnPosOffset: Vector3f) {
|
||||||
|
eOriginDebug.spawn(world)
|
||||||
|
shapeDebug.spawn(world)
|
||||||
|
forceFieldDebug.makeArray(eOrigin, forceFields, spawnPosOffset)
|
||||||
|
forceFieldDebug.spawn(world)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(
|
||||||
|
rot: Vector2f,
|
||||||
|
eOrigin: Vec3d,
|
||||||
|
spawnPosOffset: Vector3f
|
||||||
|
) {
|
||||||
|
eOriginDebug.update(rot, eOrigin)
|
||||||
|
shapeDebug.update(rot, eOrigin, spawnPosOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun kill() {
|
||||||
|
eOriginDebug.kill()
|
||||||
|
shapeDebug.kill()
|
||||||
|
forceFieldDebug.kill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class EOrigin(rot: Vector2f, eOrigin: Vec3d) {
|
||||||
|
val eOriginDE = DisplayEntity(
|
||||||
|
DisplayEntityProperties(
|
||||||
|
pos = eOrigin,
|
||||||
|
rot = rot,
|
||||||
|
model = "minecraft:red_concrete",
|
||||||
|
tags = arrayOf("DEBUG", "E_DEBUG", "eOrigin_DEBUG", "BPS_UUID", SESSION_UUID.toString()),
|
||||||
|
brightnessOverride = 15,
|
||||||
|
scale = Vector3f(0.25f, 0.25f, 0.25f)
|
||||||
|
))
|
||||||
|
|
||||||
|
fun spawn(world: ServerWorld) {
|
||||||
|
eOriginDE.spawn(world)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(rot: Vector2f, eOrigin: Vec3d) {
|
||||||
|
val newProperties = eOriginDE.properties
|
||||||
|
newProperties?.rot = rot
|
||||||
|
newProperties?.pos = eOrigin
|
||||||
|
|
||||||
|
eOriginDE.updateProperties(newProperties!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun kill() {
|
||||||
|
eOriginDE.kill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShapeDebug(eOrigin: Vec3d, spawnPosOffset: Vector3f, shape: SpawningShape) {
|
||||||
|
val shapeScale: Vector3f? = when (shape) {
|
||||||
|
is SpawningShape.Circle -> Vector3f(shape.radius * 2 * 2, shape.radius * 2 * 2, 0.0001f)
|
||||||
|
is SpawningShape.Sphere -> Vector3f(shape.radius * 2 * 2, shape.radius * 2 * 2, 0.0001f)
|
||||||
|
is SpawningShape.Rect -> Vector3f(shape.size.x, 0.0001f, shape.size.y)
|
||||||
|
is SpawningShape.Cube -> Vector3f(shape.size.x, shape.size.y, shape.size.z)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
val shapeRot = when (shape) {
|
||||||
|
is SpawningShape.Circle -> Vector4f(-0.707f, 0.0f, 0.0f, 0.707f)
|
||||||
|
else -> Vector4f(0.0f, 0.0f, 0.0f, 1.0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun determineModel(shape: SpawningShape): String =
|
||||||
|
when (shape) {
|
||||||
|
is SpawningShape.Circle, is SpawningShape.Sphere -> "minecraft:sunflower"
|
||||||
|
else -> "minecraft:yellow_stained_glass"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun determineBillboard(shape: SpawningShape): String =
|
||||||
|
if (shape !is SpawningShape.Sphere) Billboard.FIXED else Billboard.CENTER
|
||||||
|
|
||||||
|
val shapeDE: DisplayEntity? = if (shape !is SpawningShape.Point) {
|
||||||
|
val position = eOrigin.add(
|
||||||
|
spawnPosOffset.x.toDouble(),
|
||||||
|
spawnPosOffset.y.toDouble(),
|
||||||
|
spawnPosOffset.z.toDouble()
|
||||||
|
)
|
||||||
|
|
||||||
|
DisplayEntity(DisplayEntityProperties(
|
||||||
|
pos = position.add(spawnPosOffset.x.toDouble(), spawnPosOffset.y.toDouble(), spawnPosOffset.z.toDouble()),
|
||||||
|
model = determineModel(shape),
|
||||||
|
tags = arrayOf("DEBUG", "P_DEBUG", "shape_DEBUG", "BPS_UUID", SESSION_UUID.toString()),
|
||||||
|
brightnessOverride = 15,
|
||||||
|
billboard = determineBillboard(shape),
|
||||||
|
leftRotation = shapeRot,
|
||||||
|
scale = shapeScale ?: Vector3f(1f, 1f, 1f)
|
||||||
|
))
|
||||||
|
} else null
|
||||||
|
|
||||||
|
fun spawn(world: ServerWorld) {
|
||||||
|
shapeDE?.spawn(world)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(rot: Vector2f, eOrigin: Vec3d, spawnPosOffset: Vector3f) {
|
||||||
|
val newProperties = shapeDE?.properties
|
||||||
|
newProperties?.rot = rot
|
||||||
|
newProperties?.pos = eOrigin
|
||||||
|
newProperties?.translation = spawnPosOffset
|
||||||
|
|
||||||
|
shapeDE?.updateProperties(newProperties!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun kill() {
|
||||||
|
shapeDE?.kill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ForceFieldDebug {
|
||||||
|
var FFDEArray: Array<DisplayEntity> = emptyArray()
|
||||||
|
|
||||||
|
fun makeArray(eOrigin: Vec3d, forceFieldArray: ParamClasses.ForceFieldArray, spawnPosOffset: Vector3f) {
|
||||||
|
forceFieldArray.array.forEachIndexed { index, it ->
|
||||||
|
val shapeScale = when (it.shape) {
|
||||||
|
is ForceFieldShape.Sphere -> Vector3f(1.333f * it.shape.radius * 2, 1.333f * it.shape.radius * 2, 0.0001f)
|
||||||
|
is ForceFieldShape.Cube -> it.shape.size
|
||||||
|
}
|
||||||
|
|
||||||
|
val newOffset = Vector3f(spawnPosOffset).add(it.pos)
|
||||||
|
|
||||||
|
val displayEntity = DisplayEntity(DisplayEntityProperties(
|
||||||
|
pos = eOrigin.add(newOffset.x.toDouble(), newOffset.y.toDouble(), newOffset.z.toDouble()),
|
||||||
|
model = if (it.shape is ForceFieldShape.Sphere) { "minecraft:snowball" } else { "minecraft:white_stained_glass" },
|
||||||
|
tags = arrayOf("DEBUG", "E_DEBUG", "eOrigin_DEBUG", "BPS_UUID", SESSION_UUID.toString()),
|
||||||
|
customName = "forceField index: $index",
|
||||||
|
brightnessOverride = 15,
|
||||||
|
billboard = if (it.shape is ForceFieldShape.Sphere) { Billboard.CENTER } else { Billboard.FIXED },
|
||||||
|
scale = shapeScale
|
||||||
|
))
|
||||||
|
|
||||||
|
FFDEArray += displayEntity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun spawn(world: ServerWorld) {
|
||||||
|
FFDEArray.forEach { it.spawn(world) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun kill() {
|
||||||
|
FFDEArray.forEach { it.kill() }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,42 +1,159 @@
|
|||||||
package com.bytedice.bde_particles.particles
|
package com.bytedice.bde_particles.particles
|
||||||
|
|
||||||
/**
|
import com.bytedice.bde_particles.LerpCurves
|
||||||
* EmitterParams defines the parameters for controlling how particles are emitted,
|
import org.joml.Vector3f
|
||||||
* including settings for looping, spawn frequency, and maximum particle count.
|
|
||||||
*
|
|
||||||
* @param maxCount The maximum number of particles that can exist. If set to 0 or below, no particles will spawn.
|
|
||||||
* @param spawnsPerTick The number of particles spawned per tick. If set to 0 or below, no particles will spawn.
|
|
||||||
* @param loopDur The duration for which the loop runs, in ticks. A value 0 or below means the loop is infinite, and `loopDelay` and `loopCount` will be ignored.
|
|
||||||
* @param loopDelay The delay between each loop cycle, in ticks. Negative values are treated as 0, meaning no delay.
|
|
||||||
* @param loopCount The number of times the loop will repeat. 0 means it will shoot a single burst of particles. A value below 0 means it will repeat indefinitely.
|
|
||||||
* @param particle The particle params of the particle that will spawn.
|
|
||||||
*/
|
|
||||||
data class EmitterParams (
|
data class EmitterParams (
|
||||||
var maxCount: Int = 200,
|
// spawning
|
||||||
var spawnsPerTick: Int = 1,
|
var maxCount: Int,
|
||||||
var loopDur: Int = 25,
|
var spawnRate: Int,
|
||||||
var loopDelay: Int = 0,
|
var spawnChance: Float,
|
||||||
var loopCount: Int = 0,
|
var spawnDuration: ParamClasses.Duration,
|
||||||
var particle: ParticleParams = ParticleParams.DEFAULT,
|
var spawnPosOffset: Vector3f,
|
||||||
|
var lifeTime: ParamClasses.PairInt,
|
||||||
|
var shape: SpawningShape,
|
||||||
|
// init transforms
|
||||||
|
var offset: ParamClasses.PairVec3f,
|
||||||
|
var initRot: ParamClasses.PairVec3f,
|
||||||
|
var rotVel: ParamClasses.PairVec3f,
|
||||||
|
var initVel: ParamClasses.PairVec3f,
|
||||||
|
var initCenterVel: ParamClasses.PairFloat,
|
||||||
|
var initScale: ParamClasses.PairVec3f,
|
||||||
|
var transformWithVel: ParamClasses.TransformWithVel,
|
||||||
|
// velocity
|
||||||
|
var forceFields: ParamClasses.ForceFieldArray,
|
||||||
|
var constVel: Vector3f,
|
||||||
|
var drag: Float,
|
||||||
|
var minVel: Float,
|
||||||
|
// curves
|
||||||
|
var offsetCurve: ParamClasses.LerpValVec3f,
|
||||||
|
var rotVelCurve: ParamClasses.LerpValVec3f,
|
||||||
|
var scaleCurve: ParamClasses.LerpValVec3f,
|
||||||
|
var modelCurve: ParamClasses.StringCurve,
|
||||||
|
var brightnessCurve: ParamClasses.LerpValInt,
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
fun deepCopy() : EmitterParams {
|
||||||
|
val cModelCurveArray = this.modelCurve.deepCopy()
|
||||||
|
val cForceFieldArray = this.forceFields.deepCopy()
|
||||||
|
val cParams = this.copy()
|
||||||
|
|
||||||
|
cParams.modelCurve = cModelCurveArray
|
||||||
|
cParams.forceFields = cForceFieldArray
|
||||||
|
return cParams
|
||||||
|
}
|
||||||
|
|
||||||
companion object Presets {
|
companion object Presets {
|
||||||
val DEFAULT = EmitterParams()
|
val DEFAULT = EmitterParams(
|
||||||
val FIRE_GEYSER = EmitterParams(
|
maxCount = 200,
|
||||||
200,
|
spawnRate = 50,
|
||||||
1,
|
spawnChance = 1.0f,
|
||||||
25,
|
spawnDuration = ParamClasses.Duration.SingleBurst(4),
|
||||||
0,
|
spawnPosOffset = Vector3f(0.0f, 0.0f, 0.0f),
|
||||||
0,
|
lifeTime = ParamClasses.PairInt(25, 40),
|
||||||
ParticleParams.FIRE_GEYSER
|
shape = SpawningShape.Sphere(1.0f, true),
|
||||||
|
offset = ParamClasses.PairVec3f.Uniform(0.0f, 0.0f),
|
||||||
|
initRot = ParamClasses.PairVec3f.NonUniform(0.0f, 0.0f, 0.0f, 360.0f, 360.0f, 360.0f),
|
||||||
|
rotVel = ParamClasses.PairVec3f.NonUniform(-0.2f, -0.2f, -0.2f, 0.2f, 0.2f, 0.2f),
|
||||||
|
initVel = ParamClasses.PairVec3f.Null,
|
||||||
|
initCenterVel = ParamClasses.PairFloat(0.4f, 0.5f),
|
||||||
|
initScale = ParamClasses.PairVec3f.Uniform(0.5f, 1.5f),
|
||||||
|
transformWithVel = ParamClasses.TransformWithVel.Null,
|
||||||
|
forceFields = ParamClasses.ForceFieldArray(emptyArray()),
|
||||||
|
constVel = Vector3f(0.0f, 0.0f, 0.0f),
|
||||||
|
drag = 0.075f,
|
||||||
|
minVel = 0.0f,
|
||||||
|
offsetCurve = ParamClasses.LerpValVec3f.Null,
|
||||||
|
rotVelCurve = ParamClasses.LerpValVec3f.LerpUniform(1.0f, 0.0f, LerpCurves.Sqrt),
|
||||||
|
scaleCurve = ParamClasses.LerpValVec3f.LerpUniform(1.0f, 2.5f, LerpCurves.Linear),
|
||||||
|
modelCurve = ParamClasses.StringCurve(
|
||||||
|
arrayOf(
|
||||||
|
"shroomlight",
|
||||||
|
"orange_concrete",
|
||||||
|
"orange_stained_glass",
|
||||||
|
"gray_wool",
|
||||||
|
"gray_stained_glass",
|
||||||
|
"light_gray_stained_glass"
|
||||||
|
),
|
||||||
|
LerpCurves.Sqrt
|
||||||
|
),
|
||||||
|
brightnessCurve = ParamClasses.LerpValInt.LerpInt(15, 5, LerpCurves.Sqrt)
|
||||||
)
|
)
|
||||||
val RING_EXPLOSION = EmitterParams(
|
val DEBUG = EmitterParams(
|
||||||
200,
|
maxCount = 50,
|
||||||
150,
|
spawnRate = 10,
|
||||||
1,
|
spawnChance = 1.0f,
|
||||||
0,
|
spawnDuration = ParamClasses.Duration.SingleBurst(5),
|
||||||
0,
|
spawnPosOffset = Vector3f(0.0f, 2.0f, 0.0f),
|
||||||
ParticleParams.RING_EXPLOSION
|
lifeTime = ParamClasses.PairInt(25, 40),
|
||||||
|
shape = SpawningShape.Circle(1.0f, true),
|
||||||
|
offset = ParamClasses.PairVec3f.Uniform(0.0f, 0.0f),
|
||||||
|
initRot = ParamClasses.PairVec3f.Null,
|
||||||
|
rotVel = ParamClasses.PairVec3f.Null,
|
||||||
|
initVel = ParamClasses.PairVec3f.NonUniform(0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f),
|
||||||
|
initCenterVel = ParamClasses.PairFloat(1.5f, 1.5f),
|
||||||
|
initScale = ParamClasses.PairVec3f.Uniform(2.0f, 2.0f),
|
||||||
|
transformWithVel = ParamClasses.TransformWithVel.ScaleAndRot(0.75f),
|
||||||
|
forceFields = ParamClasses.ForceFieldArray(emptyArray()),
|
||||||
|
constVel = Vector3f(0.0f, -0.05f, 0.0f),
|
||||||
|
drag = 0.075f,
|
||||||
|
minVel = 0.0f,
|
||||||
|
offsetCurve = ParamClasses.LerpValVec3f.Null,
|
||||||
|
rotVelCurve = ParamClasses.LerpValVec3f.LerpUniform(1.0f, 0.0f, LerpCurves.Linear),
|
||||||
|
scaleCurve = ParamClasses.LerpValVec3f.LerpUniform(1.0f, 1.0f, LerpCurves.Linear),
|
||||||
|
modelCurve = ParamClasses.StringCurve(
|
||||||
|
arrayOf(
|
||||||
|
"shroomlight",
|
||||||
|
"orange_concrete",
|
||||||
|
"orange_stained_glass",
|
||||||
|
"gray_wool",
|
||||||
|
"gray_stained_glass",
|
||||||
|
"light_gray_stained_glass"
|
||||||
|
),
|
||||||
|
LerpCurves.Linear
|
||||||
|
),
|
||||||
|
brightnessCurve = ParamClasses.LerpValInt.LerpInt(15, 5, LerpCurves.Sqrt)
|
||||||
|
)
|
||||||
|
val STRESS_TEST = EmitterParams(
|
||||||
|
maxCount = 10000,
|
||||||
|
spawnRate = 50,
|
||||||
|
spawnChance = 1.0f,
|
||||||
|
spawnDuration = ParamClasses.Duration.SingleBurst(100),
|
||||||
|
spawnPosOffset = Vector3f(0.0f, 0.0f, 0.0f),
|
||||||
|
lifeTime = ParamClasses.PairInt(75, 100),
|
||||||
|
shape = SpawningShape.Circle(10.0f, true),
|
||||||
|
offset = ParamClasses.PairVec3f.Uniform(0.0f, 0.0f),
|
||||||
|
initRot = ParamClasses.PairVec3f.NonUniform(0.0f, 0.0f, 0.0f, 360.0f, 360.0f, 360.0f),
|
||||||
|
rotVel = ParamClasses.PairVec3f.NonUniform(-0.2f, -0.2f, -0.2f, 0.2f, 0.2f, 0.2f),
|
||||||
|
initVel = ParamClasses.PairVec3f.NonUniform(0.0f, 4.0f, 0.0f, 0.0f, 8.0f, 0.0f),
|
||||||
|
initCenterVel = ParamClasses.PairFloat(-1.5f, -1.5f),
|
||||||
|
initScale = ParamClasses.PairVec3f.Uniform(10.0f, 15.0f),
|
||||||
|
transformWithVel = ParamClasses.TransformWithVel.Null,
|
||||||
|
forceFields = ParamClasses.ForceFieldArray(
|
||||||
|
arrayOf(ForceField(
|
||||||
|
Vector3f(0.0f, 75.0f, 0.0f),
|
||||||
|
ForceFieldShape.Sphere(15.0f, Pair(0.1f, 0.2f))
|
||||||
|
))
|
||||||
|
),
|
||||||
|
constVel = Vector3f(0.0f, 0.00001f, 0.0f),
|
||||||
|
drag = 0.075f,
|
||||||
|
minVel = 0.0f,
|
||||||
|
offsetCurve = ParamClasses.LerpValVec3f.Null,
|
||||||
|
rotVelCurve = ParamClasses.LerpValVec3f.LerpUniform(1.0f, 0.0f, LerpCurves.Linear),
|
||||||
|
scaleCurve = ParamClasses.LerpValVec3f.LerpUniform(1.0f, 1.5f, LerpCurves.Linear),
|
||||||
|
modelCurve = ParamClasses.StringCurve(
|
||||||
|
arrayOf(
|
||||||
|
"shroomlight",
|
||||||
|
"orange_concrete",
|
||||||
|
"orange_stained_glass",
|
||||||
|
"gray_wool",
|
||||||
|
"gray_stained_glass",
|
||||||
|
"light_gray_stained_glass"
|
||||||
|
),
|
||||||
|
LerpCurves.Linear
|
||||||
|
),
|
||||||
|
brightnessCurve = ParamClasses.LerpValInt.Null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,13 @@ package com.bytedice.bde_particles.particles
|
|||||||
import org.joml.Vector3f
|
import org.joml.Vector3f
|
||||||
|
|
||||||
data class ForceField (
|
data class ForceField (
|
||||||
val name: String = "DEFAULT",
|
val pos: Vector3f,
|
||||||
val pos: Vector3f = Vector3f(0.0f, 5.0f, 0.0f),
|
val shape: ForceFieldShape
|
||||||
val shape: ForceFieldShape = ForceFieldShape.SPHERE()
|
) {
|
||||||
)
|
fun deepCopy(): ForceField {
|
||||||
|
return ForceField(
|
||||||
|
Vector3f(pos),
|
||||||
|
shape.deepCopy()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
package com.bytedice.bde_particles.particles
|
||||||
|
|
||||||
|
import com.bytedice.bde_particles.LerpCurves
|
||||||
|
import com.bytedice.bde_particles.lerpArray
|
||||||
|
import com.bytedice.bde_particles.randomFloatBetween
|
||||||
|
import com.bytedice.bde_particles.randomIntBetween
|
||||||
|
import org.joml.Vector3f
|
||||||
|
import kotlin.math.atan2
|
||||||
|
import kotlin.math.pow
|
||||||
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
|
class ParamClasses {
|
||||||
|
sealed class PairVec3f {
|
||||||
|
class Uniform (
|
||||||
|
private val min: Float,
|
||||||
|
private val max: Float,
|
||||||
|
) : PairVec3f() {
|
||||||
|
fun randomize() : Vector3f {
|
||||||
|
val x = randomFloatBetween(min, max)
|
||||||
|
return Vector3f(x, x, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class NonUniform (
|
||||||
|
private val minX: Float,
|
||||||
|
private val minY: Float,
|
||||||
|
private val minZ: Float,
|
||||||
|
private val maxX: Float,
|
||||||
|
private val maxY: Float,
|
||||||
|
private val maxZ: Float
|
||||||
|
) : PairVec3f() {
|
||||||
|
fun randomize(): Vector3f {
|
||||||
|
return Vector3f(
|
||||||
|
randomFloatBetween(minX, maxX),
|
||||||
|
randomFloatBetween(minY, maxY),
|
||||||
|
randomFloatBetween(minZ, maxZ),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data class Constant(val value: Vector3f) : PairVec3f()
|
||||||
|
data object Null : PairVec3f()
|
||||||
|
}
|
||||||
|
|
||||||
|
class PairInt (
|
||||||
|
val min: Int,
|
||||||
|
val max: Int,
|
||||||
|
) {
|
||||||
|
fun randomize() : Int { return randomIntBetween(min, max) }
|
||||||
|
}
|
||||||
|
|
||||||
|
class PairFloat(
|
||||||
|
val min: Float,
|
||||||
|
val max: Float,
|
||||||
|
) {
|
||||||
|
fun randomize() : Float { return randomFloatBetween(min, max) }
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class LerpValVec3f {
|
||||||
|
class LerpVec3f(
|
||||||
|
private val fromX: Float,
|
||||||
|
private val fromY: Float,
|
||||||
|
private val fromZ: Float,
|
||||||
|
private val toX: Float,
|
||||||
|
private val toY: Float,
|
||||||
|
private val toZ: Float,
|
||||||
|
private val curve: LerpCurves,
|
||||||
|
) : LerpValVec3f() {
|
||||||
|
fun lerp(t: Float): Vector3f {
|
||||||
|
val x = com.bytedice.bde_particles.lerp(fromX, toX, curve.function(t))
|
||||||
|
val y = com.bytedice.bde_particles.lerp(fromY, toY, curve.function(t))
|
||||||
|
val z = com.bytedice.bde_particles.lerp(fromZ, toZ, curve.function(t))
|
||||||
|
return Vector3f(x, y, z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class MultiLerpVec3f(
|
||||||
|
private val fromX: Float,
|
||||||
|
private val fromY: Float,
|
||||||
|
private val fromZ: Float,
|
||||||
|
private val toX: Float,
|
||||||
|
private val toY: Float,
|
||||||
|
private val toZ: Float,
|
||||||
|
private val curveX: LerpCurves,
|
||||||
|
private val curveY: LerpCurves,
|
||||||
|
private val curveZ: LerpCurves
|
||||||
|
) : LerpValVec3f() {
|
||||||
|
fun lerp(t: Float): Vector3f {
|
||||||
|
val x = com.bytedice.bde_particles.lerp(fromX, toX, curveX.function(t))
|
||||||
|
val y = com.bytedice.bde_particles.lerp(fromY, toY, curveY.function(t))
|
||||||
|
val z = com.bytedice.bde_particles.lerp(fromZ, toZ, curveZ.function(t))
|
||||||
|
return Vector3f(x, y, z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class LerpUniform(
|
||||||
|
private val from: Float,
|
||||||
|
private val to: Float,
|
||||||
|
private val curve: LerpCurves,
|
||||||
|
) : LerpValVec3f() {
|
||||||
|
fun lerp(t: Float) : Float {
|
||||||
|
return com.bytedice.bde_particles.lerp(from, to, curve.function(t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data class Constant(val value: Vector3f) : LerpValVec3f()
|
||||||
|
data object Null : LerpValVec3f()
|
||||||
|
|
||||||
|
fun lerpToVector3f(t: Float) : Vector3f {
|
||||||
|
when (this) {
|
||||||
|
is LerpVec3f -> return this.lerp(t)
|
||||||
|
is MultiLerpVec3f -> return this.lerp(t)
|
||||||
|
is LerpUniform -> {
|
||||||
|
val x = this.lerp(t)
|
||||||
|
return Vector3f(x, x, x)
|
||||||
|
}
|
||||||
|
is Constant -> return this.value
|
||||||
|
is Null -> return Vector3f(1.0f, 1.0f, 1.0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class LerpValInt {
|
||||||
|
class LerpInt(
|
||||||
|
private val from: Int,
|
||||||
|
private val to: Int,
|
||||||
|
private val curve: LerpCurves,
|
||||||
|
) : LerpValInt() {
|
||||||
|
fun lerp(t: Float): Int {
|
||||||
|
return Math.round(com.bytedice.bde_particles.lerp(from.toFloat(), to.toFloat(), curve.function(t)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data class Constant(val value: Int) : LerpValInt()
|
||||||
|
data object Null: LerpValInt()
|
||||||
|
|
||||||
|
fun lerpToInt(t: Float) : Int? {
|
||||||
|
return when (this) {
|
||||||
|
is LerpInt -> this.lerp(t)
|
||||||
|
is Constant -> this.value
|
||||||
|
is Null -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Duration {
|
||||||
|
data class SingleBurst(
|
||||||
|
val loopDur: Int
|
||||||
|
) : Duration()
|
||||||
|
data class MultiBurst(
|
||||||
|
val loopDur: Int,
|
||||||
|
val loopDelay: Int,
|
||||||
|
val loopCount: Int,
|
||||||
|
) : Duration()
|
||||||
|
data class InfiniteLoop(
|
||||||
|
val loopDur: Int,
|
||||||
|
val loopDelay: Int
|
||||||
|
) : Duration()
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class TransformWithVel {
|
||||||
|
data object RotOnly : TransformWithVel() {
|
||||||
|
fun velToRot(velocity: Vector3f): Vector3f {
|
||||||
|
val normalizedVelocity = Vector3f(velocity).normalize()
|
||||||
|
|
||||||
|
val yaw = Math.toDegrees(atan2(normalizedVelocity.x.toDouble(), normalizedVelocity.z.toDouble())).toFloat()
|
||||||
|
|
||||||
|
val horizontalMag = sqrt(normalizedVelocity.x.toDouble().pow(2) + normalizedVelocity.z.toDouble().pow(2))
|
||||||
|
val pitch = Math.toDegrees(atan2(-normalizedVelocity.y.toDouble(), horizontalMag)).toFloat()
|
||||||
|
|
||||||
|
return Vector3f(pitch, yaw, 0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class ScaleAndRot(private val scaleMul: Float) : TransformWithVel() {
|
||||||
|
fun velToRot(velocity: Vector3f) : Vector3f {
|
||||||
|
val normalizedVelocity = Vector3f(velocity).normalize()
|
||||||
|
|
||||||
|
val yaw = Math.toDegrees(atan2(normalizedVelocity.x.toDouble(), normalizedVelocity.z.toDouble())).toFloat()
|
||||||
|
|
||||||
|
val horizontalMag = sqrt(normalizedVelocity.x.toDouble().pow(2) + normalizedVelocity.z.toDouble().pow(2))
|
||||||
|
val pitch = Math.toDegrees(atan2(-normalizedVelocity.y.toDouble(), horizontalMag)).toFloat()
|
||||||
|
|
||||||
|
return Vector3f(pitch, yaw, 0f)
|
||||||
|
}
|
||||||
|
fun velToScale(velocity: Vector3f): Vector3f {
|
||||||
|
val velocityMagnitude = velocity.length()
|
||||||
|
val stretchFactor = velocityMagnitude * scaleMul + 1.0f
|
||||||
|
val contractFactor = 1.0f / stretchFactor
|
||||||
|
|
||||||
|
return Vector3f(contractFactor, contractFactor, stretchFactor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data object Null : TransformWithVel()
|
||||||
|
}
|
||||||
|
|
||||||
|
class StringCurve (
|
||||||
|
var array: Array<String>,
|
||||||
|
var curve: LerpCurves
|
||||||
|
) {
|
||||||
|
fun lerp(t: Float) : String { return lerpArray(array as Array<Any>, t, curve) as String }
|
||||||
|
|
||||||
|
fun deepCopy(): StringCurve {
|
||||||
|
return StringCurve(array.copyOf(), curve.deepCopy())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ForceFieldArray (
|
||||||
|
var array: Array<ForceField>
|
||||||
|
) {
|
||||||
|
fun deepCopy(): ForceFieldArray {
|
||||||
|
return ForceFieldArray(array.map { it.deepCopy() }.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,77 +1,110 @@
|
|||||||
package com.bytedice.bde_particles.particles
|
package com.bytedice.bde_particles.particles
|
||||||
|
|
||||||
import com.bytedice.bde_particles.*
|
import com.bytedice.bde_particles.*
|
||||||
|
import net.minecraft.particle.DustParticleEffect
|
||||||
import net.minecraft.server.world.ServerWorld
|
import net.minecraft.server.world.ServerWorld
|
||||||
import net.minecraft.util.math.Vec3d
|
import net.minecraft.util.math.Vec3d
|
||||||
|
import org.joml.Vector2f
|
||||||
import org.joml.Vector3f
|
import org.joml.Vector3f
|
||||||
import org.joml.Vector4f
|
import org.joml.Vector4f
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
|
||||||
|
|
||||||
// TODO: consider moving most params to emitter (such as shape, and forceFields) to store less data
|
class Particle(
|
||||||
|
private val emitterParams: EmitterParams,
|
||||||
|
private var entityPos: Vec3d,
|
||||||
class Particle(private val particleParams: ParticleParams) {
|
private var entityRot: Vector2f,
|
||||||
private val initOffset = Vector3f(-0.5f, -0.5f, -0.5f)
|
private val debug: Boolean = false
|
||||||
private var offset = Vector3f(0.0f, 0.0f, 0.0f)
|
) {
|
||||||
private var pos = Vec3d(0.0, 0.0, 0.0)
|
private var pos = Vector3f(0.0f, 0.0f, 0.0f)
|
||||||
|
|
||||||
private var rot = randomBetweenVector3f(particleParams.rotRandom.first, particleParams.rotRandom.second)
|
|
||||||
private val rotVel = randomBetweenVector3f(particleParams.rotVelRandom.first, particleParams.rotVelRandom.second)
|
|
||||||
private var vel = randomBetweenVector3f(particleParams.velRandom.first, particleParams.velRandom.second)
|
|
||||||
private var scale = randomBetweenVector3f(particleParams.sizeRandom.first, particleParams.sizeRandom.second)
|
|
||||||
|
|
||||||
private var blockDisplay: DisplayEntity? = null
|
|
||||||
private var lifeTime = randomIntBetween(particleParams.lifeTime.first, particleParams.lifeTime.second)
|
|
||||||
|
|
||||||
var isDead = false
|
|
||||||
private var timeAlive = 0
|
private var timeAlive = 0
|
||||||
|
|
||||||
private var isInit = false
|
private var isInit = false
|
||||||
|
var isDead = false
|
||||||
|
|
||||||
|
// params
|
||||||
|
private val originOffset = when (val offset = emitterParams.offset) {
|
||||||
|
is ParamClasses.PairVec3f.Uniform -> offset.randomize()
|
||||||
|
is ParamClasses.PairVec3f.NonUniform -> offset.randomize()
|
||||||
|
is ParamClasses.PairVec3f.Constant -> offset.value
|
||||||
|
is 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()
|
||||||
|
is ParamClasses.PairVec3f.Constant -> initRot.value
|
||||||
|
is 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()
|
||||||
|
is ParamClasses.PairVec3f.Constant -> initRotVel.value
|
||||||
|
is 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()
|
||||||
|
is ParamClasses.PairVec3f.Constant -> initVel.value
|
||||||
|
is 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()
|
||||||
|
is ParamClasses.PairVec3f.Constant -> initScale.value
|
||||||
|
is ParamClasses.PairVec3f.Null -> Vector3f(0.0f, 0.0f, 0.0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var lifeTime = emitterParams.lifeTime.randomize()
|
||||||
|
|
||||||
|
private lateinit var particleEntity: DisplayEntity
|
||||||
|
|
||||||
|
private val debugTool = ParticleDebug(entityRot, entityPos, pos, vel, scale)
|
||||||
|
|
||||||
|
|
||||||
fun init(pos: Vec3d) {
|
fun init() {
|
||||||
this.pos = pos
|
val so = Vector3f(emitterParams.spawnPosOffset)
|
||||||
|
if (so.x + so.y + so.z != 0.0f) {
|
||||||
if ( particleParams.uniformSize ) {
|
entityPos = entityPos.add(so.x.toDouble(), so.y.toDouble(), so.z.toDouble())
|
||||||
val randomUniform = randomFloatBetween(particleParams.sizeRandom.first.x, particleParams.sizeRandom.second.x)
|
|
||||||
scale.x = randomUniform
|
|
||||||
scale.y = randomUniform
|
|
||||||
scale.z = randomUniform
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val shape = particleParams.shape
|
val shape = emitterParams.shape
|
||||||
val newSpawnPos: Vector3f
|
val newSpawnPos = shape.random(shape)
|
||||||
|
|
||||||
when (shape) {
|
pos = newSpawnPos
|
||||||
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) }
|
|
||||||
}
|
|
||||||
|
|
||||||
offset = newSpawnPos
|
val posDouble = Vec3d(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble())
|
||||||
|
val dirFromCenter = posDouble.normalize()
|
||||||
|
val forceFromCenter = dirFromCenter.multiply(emitterParams.initCenterVel.randomize().toDouble())
|
||||||
|
vel.add(forceFromCenter.x.toFloat(), forceFromCenter.y.toFloat(), forceFromCenter.z.toFloat())
|
||||||
|
|
||||||
val (quatRot, newOffset) = calcTransformOffset(rot, initOffset, scale)
|
val (quatRot, newPos) = calcTransformOffset(this.rot, originOffset, scale)
|
||||||
|
|
||||||
val properties = DisplayEntityProperties(
|
val properties = DisplayEntityProperties(
|
||||||
pos = this.pos,
|
pos = entityPos,
|
||||||
blockType = particleParams.blockCurve[0],
|
rot = entityRot,
|
||||||
translation = newOffset.add(offset),
|
model = if (emitterParams.modelCurve.array.isNotEmpty()) { emitterParams.modelCurve.lerp(0.0f) } else "none",
|
||||||
leftRotation = quatRot,
|
translation = newPos.add(pos),
|
||||||
scale = scale,
|
leftRotation = quatRot,
|
||||||
tags = arrayOf("BPS_Particle", "BPS_UUID", sessionUuid.toString())
|
scale = Vector3f(scale).mul(emitterParams.scaleCurve.lerpToVector3f(0.0f)),
|
||||||
|
tags = arrayOf("BPS_Particle", "BPS_UUID", SESSION_UUID.toString()),
|
||||||
|
brightnessOverride = emitterParams.brightnessCurve.lerpToInt(0.0f)
|
||||||
)
|
)
|
||||||
|
|
||||||
blockDisplay = DisplayEntity(properties)
|
particleEntity = DisplayEntity(properties)
|
||||||
|
|
||||||
isInit = true
|
isInit = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun spawn(world: ServerWorld) {
|
fun spawn(world: ServerWorld) {
|
||||||
if (isInit) { blockDisplay?.spawn(world) }
|
if (isInit) {
|
||||||
|
particleEntity.spawn(world)
|
||||||
|
if (debug) { debugTool.spawn(world) }
|
||||||
|
LIVING_PARTICLE_COUNT += 1
|
||||||
|
}
|
||||||
else { error("Particle is not initialized before being spawned. Please use Particle.init() to initialize it.") }
|
else { error("Particle is not initialized before being spawned. Please use Particle.init() to initialize it.") }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,9 +114,9 @@ class Particle(private val particleParams: ParticleParams) {
|
|||||||
|
|
||||||
val newProperties = calcNewProperties()
|
val newProperties = calcNewProperties()
|
||||||
|
|
||||||
blockDisplay?.updateProperties(newProperties)
|
particleEntity.updateProperties(newProperties)
|
||||||
|
|
||||||
if (timeAlive > lifeTime) { isDead = true; blockDisplay?.kill() }
|
if (timeAlive > lifeTime) { kill() }
|
||||||
|
|
||||||
timeAlive += 1
|
timeAlive += 1
|
||||||
}
|
}
|
||||||
@@ -103,100 +136,130 @@ class Particle(private val particleParams: ParticleParams) {
|
|||||||
|
|
||||||
|
|
||||||
private fun calcNewProperties() : DisplayEntityProperties {
|
private fun calcNewProperties() : DisplayEntityProperties {
|
||||||
|
|
||||||
fun calcBlockCurve(timeAliveClamped: Float) : String {
|
|
||||||
val newBlockIdx = round(lerp(0.0f, particleParams.blockCurve.lastIndex.toFloat(), timeAliveClamped)).toInt()
|
|
||||||
return particleParams.blockCurve[newBlockIdx]
|
|
||||||
}
|
|
||||||
|
|
||||||
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 normalizedSdf = normalizeSdf(sdfVal, shape.radius)
|
|
||||||
val velDir = Vector3f(offset).sub(forceField.pos).normalize()
|
|
||||||
val velMul = if (sdfVal > 0) { 0.0f }
|
|
||||||
else { lerp(shape.force.second, shape.force.second, 1 - normalizedSdf) }
|
|
||||||
|
|
||||||
return velDir.mul(velMul)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cubeSdfVel(forceField: ForceField) : Vector3f {
|
|
||||||
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 velDir = if (sdfVal < 0) { shape.forceDir }
|
|
||||||
else { Vector3f(0.0f, 0.0f, 0.0f) }
|
|
||||||
|
|
||||||
return velDir
|
|
||||||
}
|
|
||||||
|
|
||||||
fun calcRot(timeAliveClamped: Float) : Vector3f {
|
|
||||||
val newRot = Vector3f(rot)
|
|
||||||
val newVel = Vector3f(rotVel)
|
|
||||||
val rotCurve = particleParams.rotVelCurve
|
|
||||||
|
|
||||||
newVel.mul(interpolateCurve(rotCurve.first, rotCurve.second, timeAliveClamped, rotCurve.third))
|
|
||||||
newRot.add(newVel)
|
|
||||||
return newRot
|
|
||||||
}
|
|
||||||
|
|
||||||
fun calcOffset() : Pair<Vector3f, Vector3f> {
|
|
||||||
var vel = Vector3f(vel)
|
|
||||||
vel = vel.add(particleParams.gravity)
|
|
||||||
vel = vel.mul(1.0f - particleParams.drag)
|
|
||||||
|
|
||||||
if (abs(vel.x) < particleParams.minVel) { vel.x = 0.0f }
|
|
||||||
if (abs(vel.y) < particleParams.minVel) { vel.y = 0.0f }
|
|
||||||
if (abs(vel.z) < particleParams.minVel) { vel.z = 0.0f }
|
|
||||||
|
|
||||||
var newOffset = Vector3f(offset)
|
|
||||||
newOffset = newOffset.add(vel)
|
|
||||||
return Pair(vel, newOffset)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun calcSize(timeAliveClamped: Float) : Vector3f {
|
|
||||||
val scale = Vector3f(scale)
|
|
||||||
val sizeCurve = particleParams.sizeCurve
|
|
||||||
scale.mul(interpolateCurve(sizeCurve.first, sizeCurve.second, timeAliveClamped, sizeCurve.third))
|
|
||||||
return scale
|
|
||||||
}
|
|
||||||
|
|
||||||
val timeAliveClamped = (timeAlive.toFloat() / lifeTime.toFloat()).coerceIn(0.0f, 1.0f)
|
val timeAliveClamped = (timeAlive.toFloat() / lifeTime.toFloat()).coerceIn(0.0f, 1.0f)
|
||||||
|
|
||||||
val newBlock = calcBlockCurve(timeAliveClamped)
|
val newModel = if (emitterParams.modelCurve.array.isNotEmpty()) { emitterParams.modelCurve.lerp(timeAliveClamped) } else "none"
|
||||||
val (newVel, newOffset) = calcOffset()
|
val (newVel, newPos) = calcPos()
|
||||||
val newRot = calcRot(timeAliveClamped)
|
val newOriginOffset = Vector3f(originOffset.mul(emitterParams.offsetCurve.lerpToVector3f(timeAliveClamped)))
|
||||||
val newScale = calcSize(timeAliveClamped)
|
val newBrightness = emitterParams.brightnessCurve.lerpToInt(timeAliveClamped)
|
||||||
|
|
||||||
vel = newVel
|
val TWVScale: Vector3f
|
||||||
offset = newOffset
|
//val TWVRot: Vector3f
|
||||||
rot = newRot
|
val newRot: Vector3f
|
||||||
|
|
||||||
for (forceField in particleParams.forceFields) {
|
when (emitterParams.transformWithVel) {
|
||||||
if (forceField.shape is ForceFieldShape.SPHERE) { vel.add(sphereSdfVel(forceField)) }
|
is ParamClasses.TransformWithVel.RotOnly -> {
|
||||||
else if (forceField.shape is ForceFieldShape.CUBE) { vel.add(cubeSdfVel(forceField)) }
|
TWVScale = Vector3f(1.0f, 1.0f, 1.0f)
|
||||||
|
newRot = (emitterParams.transformWithVel as ParamClasses.TransformWithVel.RotOnly).velToRot(vel)
|
||||||
|
}
|
||||||
|
is ParamClasses.TransformWithVel.ScaleAndRot -> {
|
||||||
|
TWVScale = (emitterParams.transformWithVel as ParamClasses.TransformWithVel.ScaleAndRot).velToScale(vel)
|
||||||
|
newRot = (emitterParams.transformWithVel as ParamClasses.TransformWithVel.ScaleAndRot).velToRot(vel)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
TWVScale = Vector3f(1.0f, 1.0f, 1.0f)
|
||||||
|
newRot = calcRot(timeAliveClamped)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val (quatRot, transformOffset) = calcTransformOffset(newRot, initOffset, newScale)
|
val newScale = TWVScale.mul(calcScale(timeAliveClamped))
|
||||||
val combinedOffset = transformOffset.add(newOffset)
|
//val newRot = TWVRot.add(calcRot(timeAliveClamped))
|
||||||
|
rot = newRot
|
||||||
|
|
||||||
|
vel = newVel
|
||||||
|
pos = newPos
|
||||||
|
|
||||||
|
for (forceField in emitterParams.forceFields.array) {
|
||||||
|
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, originOffset, newScale)
|
||||||
|
val combinedOffset = transformOffset.add(newPos)
|
||||||
|
val newTranslate = combinedOffset.add(newOriginOffset)
|
||||||
|
|
||||||
val newProperties = DisplayEntityProperties(
|
val newProperties = DisplayEntityProperties(
|
||||||
pos = pos,
|
pos = entityPos,
|
||||||
blockType = newBlock,
|
rot = entityRot,
|
||||||
translation = combinedOffset,
|
model = newModel,
|
||||||
leftRotation = quatRot,
|
translation = newTranslate,
|
||||||
scale = newScale,
|
leftRotation = quatRot,
|
||||||
tags = arrayOf("BPS_Particle", "BPS_UUID", sessionUuid.toString())
|
scale = newScale,
|
||||||
|
tags = arrayOf("BPS_Particle", "BPS_UUID", SESSION_UUID.toString()),
|
||||||
|
brightnessOverride = newBrightness
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (debug) { debugTool.update(entityRot, entityPos, newTranslate, vel, newScale) }
|
||||||
|
|
||||||
return newProperties
|
return newProperties
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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, pos)
|
||||||
|
val normalizedSdf = normalizeSdf(sdfVal, shape.radius)
|
||||||
|
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) }
|
||||||
|
|
||||||
|
return velDir.mul(velMul)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun cubeSdfVel(forceField: ForceField) : Vector3f {
|
||||||
|
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, pos)
|
||||||
|
val velDir = if (sdfVal < 0) { shape.force }
|
||||||
|
else { Vector3f(0.0f, 0.0f, 0.0f) }
|
||||||
|
|
||||||
|
return velDir
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun calcRot(t: Float) : Vector3f {
|
||||||
|
val newRot = Vector3f(rot)
|
||||||
|
val newVel = Vector3f(rotVel)
|
||||||
|
|
||||||
|
newVel.mul(emitterParams.rotVelCurve.lerpToVector3f(t))
|
||||||
|
return newRot.add(newVel)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun calcPos() : Pair<Vector3f, Vector3f> {
|
||||||
|
var vel = Vector3f(vel)
|
||||||
|
val constVel = emitterParams.constVel
|
||||||
|
if (constVel.x + constVel.y + constVel.z != 0.0f) {
|
||||||
|
vel = vel.add(emitterParams.constVel)
|
||||||
|
}
|
||||||
|
vel = vel.mul(1.0f - emitterParams.drag)
|
||||||
|
|
||||||
|
if (abs(vel.x) < emitterParams.minVel) { vel.x = 0.0f }
|
||||||
|
if (abs(vel.y) < emitterParams.minVel) { vel.y = 0.0f }
|
||||||
|
if (abs(vel.z) < emitterParams.minVel) { vel.z = 0.0f }
|
||||||
|
|
||||||
|
var newPos = Vector3f(pos)
|
||||||
|
newPos = newPos.add(vel)
|
||||||
|
return Pair(vel, newPos)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun calcScale(t: Float) : Vector3f {
|
||||||
|
val newScale = Vector3f(scale)
|
||||||
|
return newScale.mul(emitterParams.scaleCurve.lerpToVector3f(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun kill() {
|
fun kill() {
|
||||||
blockDisplay?.kill()
|
if (!isDead) {
|
||||||
|
particleEntity.kill()
|
||||||
|
if (debug) { debugTool.kill() }
|
||||||
|
LIVING_PARTICLE_COUNT -= 1
|
||||||
|
isDead = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package com.bytedice.bde_particles.particles
|
||||||
|
|
||||||
|
import com.bytedice.bde_particles.*
|
||||||
|
import net.minecraft.server.world.ServerWorld
|
||||||
|
import net.minecraft.util.math.Vec3d
|
||||||
|
import org.joml.Vector2f
|
||||||
|
import org.joml.Vector3f
|
||||||
|
import kotlin.math.atan2
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.pow
|
||||||
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
|
|
||||||
|
class ParticleDebug(
|
||||||
|
rot: Vector2f,
|
||||||
|
eOrigin: Vec3d,
|
||||||
|
pOrigin: Vector3f,
|
||||||
|
pVel: Vector3f,
|
||||||
|
pScale: Vector3f
|
||||||
|
) {
|
||||||
|
private val pOriginDebug = POrigin(rot, eOrigin, pOrigin)
|
||||||
|
private val pVelDebug = PVel(rot, eOrigin, pOrigin, pVel, pScale)
|
||||||
|
|
||||||
|
fun spawn(world: ServerWorld) {
|
||||||
|
pOriginDebug.spawn(world)
|
||||||
|
pVelDebug.spawn(world)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(
|
||||||
|
rot: Vector2f,
|
||||||
|
eOrigin: Vec3d,
|
||||||
|
pOrigin: Vector3f,
|
||||||
|
pVel: Vector3f,
|
||||||
|
pScale: Vector3f
|
||||||
|
) {
|
||||||
|
pOriginDebug.update(rot, eOrigin, pOrigin)
|
||||||
|
pVelDebug.update(rot, eOrigin, pOrigin, pVel, pScale)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun kill() {
|
||||||
|
pOriginDebug.kill()
|
||||||
|
pVelDebug.kill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class POrigin(rot: Vector2f, eOrigin: Vec3d, pOrigin: Vector3f) {
|
||||||
|
val pOriginDE = DisplayEntity(DisplayEntityProperties(
|
||||||
|
pos = eOrigin,
|
||||||
|
rot = rot,
|
||||||
|
model = "minecraft:lime_concrete",
|
||||||
|
tags = arrayOf("DEBUG", "P_DEBUG", "pOrigin_DEBUG", "BPS_UUID", SESSION_UUID.toString()),
|
||||||
|
brightnessOverride = 15,
|
||||||
|
translation = Vector3f(pOrigin),
|
||||||
|
scale = Vector3f(0.15f, 0.15f, 0.15f)
|
||||||
|
))
|
||||||
|
|
||||||
|
fun spawn(world: ServerWorld) {
|
||||||
|
pOriginDE.spawn(world)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(rot: Vector2f, eOrigin: Vec3d, pOrigin: Vector3f) {
|
||||||
|
val newProperties = pOriginDE.properties
|
||||||
|
newProperties?.rot = rot
|
||||||
|
newProperties?.pos = eOrigin
|
||||||
|
newProperties?.translation = Vector3f(pOrigin)
|
||||||
|
|
||||||
|
pOriginDE.updateProperties(newProperties!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun kill() {
|
||||||
|
pOriginDE.kill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PVel(rot: Vector2f, eOrigin: Vec3d, pOrigin: Vector3f, pVel: Vector3f, pScale: Vector3f) {
|
||||||
|
val pVelDE = DisplayEntity(DisplayEntityProperties(
|
||||||
|
pos = eOrigin,
|
||||||
|
rot = rot,
|
||||||
|
model = "minecraft:red_nether_brick_slab", //"minecraft:red_stained_glass",
|
||||||
|
tags = arrayOf("DEBUG", "P_DEBUG", "pVel_DEBUG", "BPS_UUID", SESSION_UUID.toString()),
|
||||||
|
brightnessOverride = 15,
|
||||||
|
translation = Vector3f(pOrigin),
|
||||||
|
scale = Vector3f(0.05f, max(max(pScale.x, pScale.y), pScale.z) + (pVel.x + pVel.y + pVel.z), 0.05f)
|
||||||
|
))
|
||||||
|
|
||||||
|
fun spawn(world: ServerWorld) {
|
||||||
|
pVelDE.spawn(world)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(rot: Vector2f, eOrigin: Vec3d, pOrigin: Vector3f, pVel: Vector3f, pScale: Vector3f) {
|
||||||
|
val newProperties = pVelDE.properties
|
||||||
|
val scale = (max(max(pScale.x, pScale.y), pScale.z) + (pVel.x + pVel.y + pVel.z)) * 2.0f
|
||||||
|
val scaleVec3f = Vector3f(0.05f, scale, 0.05f)
|
||||||
|
val leftRotEuler = velToRot(pVel)
|
||||||
|
|
||||||
|
newProperties?.rot = rot
|
||||||
|
newProperties?.pos = eOrigin
|
||||||
|
newProperties?.translation = Vector3f(pOrigin)
|
||||||
|
newProperties?.leftRotation = eulerToQuat(leftRotEuler)
|
||||||
|
newProperties?.scale = scaleVec3f
|
||||||
|
|
||||||
|
pVelDE.updateProperties(newProperties!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun kill() {
|
||||||
|
pVelDE.kill()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun velToRot(velocity: Vector3f): Vector3f {
|
||||||
|
val normalizedVelocity = Vector3f(velocity).normalize()
|
||||||
|
|
||||||
|
val yaw = Math.toDegrees(atan2(normalizedVelocity.x.toDouble(), normalizedVelocity.z.toDouble())).toFloat()
|
||||||
|
|
||||||
|
val horizontalMag = sqrt(normalizedVelocity.x.toDouble().pow(2) + normalizedVelocity.z.toDouble().pow(2))
|
||||||
|
val pitch = Math.toDegrees(atan2(-normalizedVelocity.y.toDouble(), horizontalMag)).toFloat() + -90.0f
|
||||||
|
|
||||||
|
return Vector3f(pitch, yaw, 0.0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,44 +1,91 @@
|
|||||||
package com.bytedice.bde_particles.particles
|
package com.bytedice.bde_particles.particles
|
||||||
|
|
||||||
|
import com.bytedice.bde_particles.Bde_particles
|
||||||
|
import com.bytedice.bde_particles.LIVING_PARTICLE_COUNT
|
||||||
|
import com.bytedice.bde_particles.randomFloatBetween
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
import net.minecraft.server.world.ServerWorld
|
import net.minecraft.server.world.ServerWorld
|
||||||
import net.minecraft.util.math.Vec3d
|
import net.minecraft.util.math.Vec3d
|
||||||
|
import org.joml.Vector2f
|
||||||
|
|
||||||
class ParticleEmitter(private val emitterPos: Vec3d, private val emitterWorld: ServerWorld, private val emitterParams: EmitterParams) {
|
class ParticleEmitter(
|
||||||
|
var pos: Vec3d,
|
||||||
|
var rot: Vector2f,
|
||||||
|
val world: ServerWorld,
|
||||||
|
val params: EmitterParams,
|
||||||
|
private val debug: Boolean = false
|
||||||
|
) {
|
||||||
private var allParticles: Array<Particle> = emptyArray()
|
private var allParticles: Array<Particle> = emptyArray()
|
||||||
|
|
||||||
private var timeAlive = 0
|
private var timeAlive = 0
|
||||||
private var loopCount = 0
|
private var timeEmitted = 0
|
||||||
private var loopDelay = 0
|
private var timePaused = 0
|
||||||
private var loopDur = 0
|
private var timesLooped = 0
|
||||||
private var loopDelayActive = false
|
private var isPaused = false
|
||||||
|
|
||||||
private var isTicking = true
|
private var isTicking = true
|
||||||
|
|
||||||
var isDead = false
|
var isDead = false
|
||||||
|
|
||||||
|
private var debugTool = EmitterDebug(rot, pos, params.spawnPosOffset, params.shape)
|
||||||
|
|
||||||
fun tick() {
|
|
||||||
|
suspend fun tick() {
|
||||||
if (isTicking) { count() }
|
if (isTicking) { count() }
|
||||||
if (isTicking) { addParticle() }
|
if (isTicking) { addParticle() }
|
||||||
if (!isTicking && allParticles.isEmpty()) { isDead = true }
|
if (!isTicking && allParticles.isEmpty()) { isDead = true; kill() }
|
||||||
|
|
||||||
for (particle in allParticles) {
|
processParticlesInBatches(100)
|
||||||
if (particle.isDead) { allParticles = allParticles.toMutableList().apply { remove(particle) }.toTypedArray() }
|
|
||||||
else { particle.tick() }
|
if (timeAlive == 0 && debug) { debugTool.spawn(world, pos, params.forceFields, params.spawnPosOffset) }
|
||||||
|
if (debug) { debugTool.update(rot, pos, params.spawnPosOffset) }
|
||||||
|
|
||||||
|
timeAlive += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private suspend fun processParticlesInBatches(
|
||||||
|
batchSize: Int
|
||||||
|
) = coroutineScope {
|
||||||
|
val batches = allParticles.toMutableList().chunked(batchSize)
|
||||||
|
|
||||||
|
val jobs = batches.map { batch ->
|
||||||
|
async {
|
||||||
|
val toRemove = mutableListOf<Particle>()
|
||||||
|
batch.forEach { particle ->
|
||||||
|
if (particle.isDead) {
|
||||||
|
toRemove.add(particle)
|
||||||
|
} else {
|
||||||
|
particle.tick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
synchronized(allParticles) {
|
||||||
|
allParticles = allParticles.toMutableList().apply { removeAll(toRemove) }.toTypedArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jobs.awaitAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun addParticle() {
|
private fun addParticle() {
|
||||||
if (loopDelayActive) { return }
|
if (isPaused) { return }
|
||||||
|
|
||||||
repeat(this.emitterParams.spawnsPerTick) {
|
repeat(params.spawnRate) {
|
||||||
if (allParticles.size >= emitterParams.maxCount) { return }
|
if (allParticles.size >= params.maxCount) { return }
|
||||||
|
|
||||||
val particle = Particle(emitterParams.particle)
|
val maxParticles = world.gameRules.getInt(Bde_particles.GLOBAL_MAX_PARTICLES)
|
||||||
particle.init(this.emitterPos)
|
if (LIVING_PARTICLE_COUNT >= maxParticles) { return }
|
||||||
particle.spawn(emitterWorld)
|
|
||||||
|
val randomSpawnChance = randomFloatBetween(0.0f, params.spawnChance.coerceIn(0.0f, 1.0f))
|
||||||
|
if (randomSpawnChance > params.spawnChance) { return }
|
||||||
|
|
||||||
|
val particle = Particle(params, pos, rot, debug)
|
||||||
|
particle.init()
|
||||||
|
particle.spawn(world)
|
||||||
|
|
||||||
allParticles += particle
|
allParticles += particle
|
||||||
}
|
}
|
||||||
@@ -46,44 +93,50 @@ class ParticleEmitter(private val emitterPos: Vec3d, private val emitterWorld: S
|
|||||||
|
|
||||||
|
|
||||||
private fun count() {
|
private fun count() {
|
||||||
// this is the only section I have comments
|
if (params.spawnDuration is ParamClasses.Duration.SingleBurst) {
|
||||||
// because im so damn good at cooking spaghetti
|
val loopDur = (params.spawnDuration as ParamClasses.Duration.SingleBurst).loopDur
|
||||||
|
if (timeEmitted >= loopDur) { isTicking = false; return }
|
||||||
|
|
||||||
// if max duration is 0 or less, skip counting, its infinite
|
timeEmitted += 1
|
||||||
if (emitterParams.loopDur <= 0) { return }
|
}
|
||||||
|
|
||||||
// if the duration is greater than max duration, add 1 loopCount
|
else if (params.spawnDuration is ParamClasses.Duration.MultiBurst) {
|
||||||
// return if max loopCount is 0
|
val loopDur = (params.spawnDuration as ParamClasses.Duration.MultiBurst).loopDur
|
||||||
// if the delay is greater than 0 then enable it
|
val loopDelay = (params.spawnDuration as ParamClasses.Duration.MultiBurst).loopDelay
|
||||||
if (loopDur >= emitterParams.loopDur) {
|
val loopCount = (params.spawnDuration as ParamClasses.Duration.MultiBurst).loopCount
|
||||||
if (loopCount == 0) {
|
|
||||||
isTicking = false
|
if (timeEmitted >= loopDur && timesLooped + 1 >= loopCount) { isTicking = false; return }
|
||||||
return
|
|
||||||
|
if (timePaused >= loopDelay) {
|
||||||
|
isPaused = false
|
||||||
|
timePaused = 0
|
||||||
|
timesLooped += 1
|
||||||
}
|
}
|
||||||
loopCount += 1
|
if (timeEmitted >= loopDur && loopDelay > 0) {
|
||||||
loopDur = 0
|
isPaused = true
|
||||||
if (emitterParams.loopDelay > 0) { loopDelayActive = true }
|
timeEmitted = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPaused) { timeEmitted += 1 }
|
||||||
|
else { timePaused += 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the delay is greater than max delay, disable the delay
|
else if (params.spawnDuration is ParamClasses.Duration.InfiniteLoop) {
|
||||||
else if (loopDelay >= emitterParams.loopDelay) {
|
val loopDur = (params.spawnDuration as ParamClasses.Duration.InfiniteLoop).loopDur
|
||||||
loopDelay = 0
|
val loopDelay = (params.spawnDuration as ParamClasses.Duration.InfiniteLoop).loopDelay
|
||||||
loopDelayActive = false
|
|
||||||
|
if (timePaused >= loopDelay) {
|
||||||
|
isPaused = false
|
||||||
|
timePaused = 0
|
||||||
|
}
|
||||||
|
if (timeEmitted >= loopDur && loopDelay > 0) {
|
||||||
|
isPaused = true
|
||||||
|
timeEmitted = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPaused) { timeEmitted += 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 >= emitterParams.loopCount) {
|
|
||||||
isTicking = false
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -92,7 +145,13 @@ class ParticleEmitter(private val emitterPos: Vec3d, private val emitterWorld: S
|
|||||||
particle.kill()
|
particle.kill()
|
||||||
allParticles = allParticles.toMutableList().apply { remove(particle) }.toTypedArray()
|
allParticles = allParticles.toMutableList().apply { remove(particle) }.toTypedArray()
|
||||||
}
|
}
|
||||||
|
if (debug) { debugTool.kill() }
|
||||||
isTicking = false
|
isTicking = false
|
||||||
isDead = true
|
isDead = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun stopTicking() {
|
||||||
|
isTicking = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,134 +1,97 @@
|
|||||||
@file:Suppress("UNCHECKED_CAST")
|
|
||||||
|
|
||||||
package com.bytedice.bde_particles.particles
|
package com.bytedice.bde_particles.particles
|
||||||
|
|
||||||
import com.bytedice.bde_particles.InterpolationCurves
|
import kotlin.reflect.KClass
|
||||||
import org.joml.Vector3f
|
import kotlin.reflect.KMutableProperty1
|
||||||
|
import kotlin.reflect.KProperty1
|
||||||
|
import kotlin.reflect.full.isSubclassOf
|
||||||
|
|
||||||
|
|
||||||
val emitterIdRegister: MutableMap<String, EmitterParams> = mutableMapOf()
|
val idRegister: MutableMap<String, EmitterParams> = mutableMapOf()
|
||||||
val forbiddenIds = arrayOf("", "DEFAULT", "NULL")
|
val forbiddenIds = arrayOf("", "NULL")
|
||||||
|
|
||||||
|
|
||||||
fun addToEmitterRegister(id: String, params: EmitterParams) : Pair<String, Boolean> {
|
fun addToRegister(id: String, params: EmitterParams) : Pair<String, Boolean> {
|
||||||
val newId = id.replace(" ", "_")
|
val newId = replaceForbiddenChars(id.replace(" ", "_"))
|
||||||
|
|
||||||
if (newId in forbiddenIds && newId != "DEFAULT") { return Pair(newId, false) }
|
if (idRegister.containsKey(newId)) {
|
||||||
if (emitterIdRegister.containsKey(newId)) { return Pair(newId, false) }
|
println("BPS - Failed to register Emitter ID \"$newId\" as it already exists!")
|
||||||
|
return Pair(newId, false)
|
||||||
|
}
|
||||||
|
|
||||||
emitterIdRegister[id] = params
|
if (newId in forbiddenIds) {
|
||||||
|
println("BPS - Failed to register Emitter ID \"$newId\" as it's been reserved for system use!")
|
||||||
|
return Pair(newId, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
val newModels: MutableList<String> = mutableListOf()
|
||||||
|
|
||||||
|
for (model in params.modelCurve.array) {
|
||||||
|
val modelModIdRegex = ".*:".toRegex()
|
||||||
|
if (modelModIdRegex.containsMatchIn(model)) { newModels.add(model) }
|
||||||
|
else { newModels.add("minecraft:$model") }
|
||||||
|
}
|
||||||
|
|
||||||
|
params.modelCurve.array = newModels.toTypedArray()
|
||||||
|
|
||||||
|
idRegister[newId] = params
|
||||||
|
println("BPS - Registered Emitter ID \"$newId\".")
|
||||||
return Pair(newId, true)
|
return Pair(newId, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun removeFromEmitterRegister(id: String) : Boolean {
|
fun removeFromRegister(id: String) : Boolean {
|
||||||
if (id in forbiddenIds) { return false }
|
if (!idRegister.containsKey(id)) {
|
||||||
if (!emitterIdRegister.containsKey(id)) { return false }
|
println("BPS - Failed to remove Emitter ID \"$id\" as it doesn't exist!")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (id in forbiddenIds) {
|
||||||
|
println("BPS - Failed to remove Emitter ID \"$id\" as it's been reserved for system use!")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
emitterIdRegister.remove(id)
|
idRegister.remove(id)
|
||||||
|
println("BPS - Removed Emitter ID \"$id\" from register.")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getEmitterParams(id: String) : EmitterParams? {
|
fun getEmitterDataById(id: String) : EmitterParams? {
|
||||||
return emitterIdRegister[id]
|
return idRegister[id]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun updateEmitterParams(id: String, newParams: EmitterParams) {
|
fun updateRegistered(id: String, newParams: EmitterParams) {
|
||||||
emitterIdRegister[id] = newParams
|
idRegister[id] = newParams
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun updateSingleEmitterParam(id: String, paramName: String, paramValue: Any) : Boolean {
|
fun updateParam(id: String, paramAccess: KProperty1<EmitterParams, *>, paramValue: Any) : Boolean {
|
||||||
val emitter = getEmitterParams(id) ?: return false
|
val emitter = getEmitterDataById(id) ?: return false
|
||||||
val updatedValues = updateSingleEmitterParam(emitter, paramName, paramValue)
|
val updatedValues = updateParams(emitter, paramAccess as KProperty1<EmitterParams, Any>, paramValue)
|
||||||
updateEmitterParams(id, updatedValues.first)
|
|
||||||
|
updateRegistered(id, updatedValues.first)
|
||||||
|
|
||||||
return updatedValues.second
|
return updatedValues.second
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun updateSingleParticleParam(emitterId: String, paramName: String, paramValue: Any) : Boolean {
|
fun updateParams(params: EmitterParams, paramAccess: KProperty1<EmitterParams, Any>, paramValue: Any): Pair<EmitterParams, Boolean> {
|
||||||
val emitter = getEmitterParams(emitterId) ?: return false
|
val newParams = params.copy()
|
||||||
val particle = emitter.particle
|
|
||||||
|
|
||||||
val updatedValues = updateSingleParticleParam(particle, paramName, paramValue)
|
if (paramAccess.returnType.classifier == paramValue::class
|
||||||
|
|| (paramAccess.returnType.classifier as? KClass<*>)?.let { paramValue::class.isSubclassOf(it) } == true
|
||||||
|
)
|
||||||
|
{
|
||||||
|
paramAccess as KMutableProperty1<EmitterParams, Any>
|
||||||
|
paramAccess.set(newParams, paramValue)
|
||||||
|
return Pair(newParams, true)
|
||||||
|
}
|
||||||
|
|
||||||
emitter.particle = updatedValues.first
|
return Pair(params, false)
|
||||||
updateEmitterParams(emitterId, emitter)
|
|
||||||
|
|
||||||
return updatedValues.second
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun updateSingleEmitterParam(params: EmitterParams, paramName: String, paramValue: Any) : Pair<EmitterParams, Boolean> {
|
fun replaceForbiddenChars(input: String): String {
|
||||||
val value: Any? = when (paramName) {
|
val regex = "[^a-zA-Z0-9_-]".toRegex()
|
||||||
"maxCount" -> paramValue as Int
|
return input.replace(regex, "-")
|
||||||
"spawnsPerTick" -> paramValue as Int
|
|
||||||
"loopDur" -> paramValue as Int
|
|
||||||
"loopDelay" -> paramValue as Int
|
|
||||||
"loopCount" -> paramValue as Int
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value == null) { return Pair(params, false) }
|
|
||||||
|
|
||||||
val newParams = params.copy()
|
|
||||||
|
|
||||||
when (paramName) {
|
|
||||||
"maxCount" -> newParams.maxCount = value as Int
|
|
||||||
"spawnsPerTick" -> newParams.spawnsPerTick = value as Int
|
|
||||||
"loopDur" -> newParams.loopDur = value as Int
|
|
||||||
"loopDelay" -> newParams.loopDelay = value as Int
|
|
||||||
"loopCount" -> newParams.loopCount = value as Int
|
|
||||||
else -> return Pair(params, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Pair(newParams, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun updateSingleParticleParam(params: ParticleParams, paramName: String, paramValue: Any) : Pair<ParticleParams, Boolean> {
|
|
||||||
val value: Any? = when (paramName) {
|
|
||||||
"shape" -> paramValue as SpawningShape?
|
|
||||||
"blockCurve" -> paramValue as Array<String>
|
|
||||||
"rotRandom" -> paramValue as Pair<Vector3f, Vector3f>
|
|
||||||
"rotVelRandom" -> paramValue as Pair<Vector3f, Vector3f>
|
|
||||||
"sizeRandom" -> paramValue as Pair<Vector3f, Vector3f>
|
|
||||||
"uniformSize" -> paramValue as Boolean
|
|
||||||
"velRandom" -> paramValue as Pair<Vector3f, Vector3f>
|
|
||||||
"forceFields" -> paramValue as Array<ForceField>
|
|
||||||
"gravity" -> paramValue as Vector3f
|
|
||||||
"drag" -> paramValue as Float
|
|
||||||
"minVel" -> paramValue as Float
|
|
||||||
"lifeTime" -> paramValue as Pair<Int, Int>
|
|
||||||
"rotVelCurve" -> paramValue as Triple<Vector3f, Vector3f, InterpolationCurves>
|
|
||||||
"sizeCurve" -> paramValue as Triple<Vector3f, Vector3f, InterpolationCurves>
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value == null) { return Pair(params, false) }
|
|
||||||
|
|
||||||
val newParams = params.copy()
|
|
||||||
|
|
||||||
when (paramName) {
|
|
||||||
"shape" -> newParams.shape = value as SpawningShape
|
|
||||||
"blockCurve" -> newParams.blockCurve = value as Array<String>
|
|
||||||
"rotRandom" -> newParams.rotRandom = value as Pair<Vector3f, Vector3f>
|
|
||||||
"rotVelRandom" -> newParams.rotVelRandom = value as Pair<Vector3f, Vector3f>
|
|
||||||
"sizeRandom" -> newParams.sizeRandom = value as Pair<Vector3f, Vector3f>
|
|
||||||
"uniformSize" -> newParams.uniformSize = value as Boolean
|
|
||||||
"velRandom" -> newParams.velRandom = value as Pair<Vector3f, Vector3f>
|
|
||||||
"forceFields" -> newParams.forceFields = value as Array<ForceField>
|
|
||||||
"gravity" -> newParams.gravity = value as Vector3f
|
|
||||||
"drag" -> newParams.drag = value as Float
|
|
||||||
"minVel" -> newParams.minVel = value as Float
|
|
||||||
"lifeTime" -> newParams.lifeTime = value as Pair<Int, Int>
|
|
||||||
"rotVelCurve" -> newParams.rotVelCurve = value as Triple<Vector3f, Vector3f, InterpolationCurves>
|
|
||||||
"sizeCurve" -> newParams.sizeCurve = value as Triple<Vector3f, Vector3f, InterpolationCurves>
|
|
||||||
else -> return Pair(params, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Pair(newParams, true)
|
|
||||||
}
|
}
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
package com.bytedice.bde_particles.particles
|
|
||||||
|
|
||||||
import com.bytedice.bde_particles.InterpolationCurves
|
|
||||||
import org.joml.Vector3f
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ParticleParams defines the parameters for particle behavior, including shape, size, velocity, and other physics properties.
|
|
||||||
*
|
|
||||||
* @param shape The shape of the particle's spawning area, `null` is a single point.
|
|
||||||
* @param blockCurve An array of block names that gets cycled through during the particle's lifetime. Empty defaults to "minecraft:air".
|
|
||||||
* @param rotRandom The random ***initial*** rotation range for the particle's rotation in X, Y, and Z axes.
|
|
||||||
* @param rotVelRandom The random velocity for particle rotation in the X, Y, and Z axes.
|
|
||||||
* @param sizeRandom The random ***initial*** size range for the particle. If `uniformSize` is true, this will be uniform (cubic).
|
|
||||||
* @param uniformSize If true, particles will have a uniform (cubic) size. Otherwise, their sizes on different axis can vary.
|
|
||||||
* @param velRandom The random ***initial*** velocity range for the particle in the X, Y, and Z axes.
|
|
||||||
* @param forceFields An array of force fields applied to the particle.
|
|
||||||
* @param gravity A constant force applied to the particle every tick. It's often used as gravity.
|
|
||||||
* @param drag A drag force that affects the speed overtime. The formula is (velocity * (1.0 - drag))
|
|
||||||
* @param minVel If particles travel slower than this their speed is automatically 0. Values less than or equal to 0 mean the particle can travel at any speed.
|
|
||||||
* @param lifeTime A range for the particle's lifetime in ticks. A value below 1 will result in the particle not spawning.
|
|
||||||
* @param rotVelCurve A curve which the rotation velocity gets multiplied by during the particle's lifetime.
|
|
||||||
* @param sizeCurve A curve which the size gets multiplied by during the particle's lifetime.
|
|
||||||
*/
|
|
||||||
data class ParticleParams (
|
|
||||||
var shape: SpawningShape = SpawningShape.CIRCLE(3.0f),
|
|
||||||
var blockCurve: Array<String> = arrayOf("minecraft:purple_concrete", "minecraft:purple_stained_glass"),
|
|
||||||
var rotRandom: Pair<Vector3f, Vector3f> = Pair(Vector3f(0.0f, 0.0f, 0.0f), Vector3f(360.0f, 360.0f, 360.0f)),
|
|
||||||
var rotVelRandom: Pair<Vector3f, Vector3f> = Pair(Vector3f(-0.2f, -0.2f, -0.2f), Vector3f(0.2f, 0.2f, 0.2f)),
|
|
||||||
var sizeRandom: Pair<Vector3f, Vector3f> = Pair(Vector3f(0.5f, 0.5f, 0.5f), Vector3f(1.0f, 1.0f, 1.0f)),
|
|
||||||
var uniformSize: Boolean = true,
|
|
||||||
var velRandom: Pair<Vector3f, Vector3f> = Pair(Vector3f(-0.1f, 0.3f, -0.1f), Vector3f(0.1f, 0.6f, 0.1f)),
|
|
||||||
var forceFields: Array<ForceField> = emptyArray(),
|
|
||||||
var gravity: Vector3f = Vector3f(0.0f, -0.01f, 0.0f),
|
|
||||||
var drag: Float = 0.075f,
|
|
||||||
var minVel: Float = 0.0f,
|
|
||||||
var lifeTime: Pair<Int, Int> = Pair(15, 45),
|
|
||||||
var rotVelCurve: Triple<Vector3f, Vector3f, InterpolationCurves> = Triple(Vector3f(1.0f, 1.0f, 1.0f), Vector3f(0.0f, 0.0f, 0.0f), InterpolationCurves.SQRT),
|
|
||||||
var sizeCurve: Triple<Vector3f, Vector3f, InterpolationCurves> = Triple(Vector3f(1.0f, 1.0f, 1.0f), Vector3f(0.5f, 0.5f, 0.5f), InterpolationCurves.LINEAR),
|
|
||||||
) {
|
|
||||||
companion object Presets {
|
|
||||||
val DEFAULT = ParticleParams()
|
|
||||||
val FIRE_GEYSER = ParticleParams(
|
|
||||||
SpawningShape.POINT,
|
|
||||||
arrayOf("minecraft:shroomlight", "minecraft:orange_concrete", "minecraft:orange_stained_glass", "minecraft:gray_stained_glass", "minecraft:light_gray_stained_glass"),
|
|
||||||
Pair(Vector3f(0.0f, 0.0f, 0.0f), Vector3f(360.0f, 360.0f, 360.0f)),
|
|
||||||
Pair(Vector3f(-0.2f, -0.2f, -0.2f), Vector3f(0.2f, 0.2f, 0.2f)),
|
|
||||||
Pair(Vector3f(0.5f, 0.5f, 0.5f), Vector3f(1.0f, 1.0f, 1.0f)),
|
|
||||||
true,
|
|
||||||
Pair(Vector3f(-0.1f, 0.4f, -0.1f), Vector3f(0.1f, 0.8f, 0.1f)),
|
|
||||||
emptyArray(),
|
|
||||||
Vector3f(0.0f, -0.01f, 0.0f),
|
|
||||||
0.075f,
|
|
||||||
0.0f,
|
|
||||||
Pair(15, 45),
|
|
||||||
Triple(Vector3f(1.0f, 1.0f, 1.0f), Vector3f(0.0f, 0.0f, 0.0f), InterpolationCurves.SQRT),
|
|
||||||
Triple(Vector3f(1.0f, 1.0f, 1.0f), Vector3f(0.5f, 0.5f, 0.5f), InterpolationCurves.SQRT)
|
|
||||||
)
|
|
||||||
val RING_EXPLOSION = ParticleParams(
|
|
||||||
SpawningShape.CIRCLE(0.1f),
|
|
||||||
arrayOf("minecraft:shroomlight", "minecraft:orange_concrete", "minecraft:orange_stained_glass", "minecraft:gray_stained_glass", "minecraft:light_gray_stained_glass"),
|
|
||||||
Pair(Vector3f(0.0f, 0.0f, 0.0f), Vector3f(360.0f, 360.0f, 360.0f)),
|
|
||||||
Pair(Vector3f(-0.2f, -0.2f, -0.2f), Vector3f(0.2f, 0.2f, 0.2f)),
|
|
||||||
Pair(Vector3f(10.0f, 10.0f, 10.0f), Vector3f(15.0f, 15.0f, 15.0f)),
|
|
||||||
true,
|
|
||||||
Pair(Vector3f(0.0f, 0.0f, 0.0f), Vector3f(0.0f, 0.0f, 0.0f)),
|
|
||||||
arrayOf(ForceField("init", Vector3f(0.0f, -0.0001f, 0.0f), ForceFieldShape.SPHERE(0.1f, Pair(4.0f, 4.0f)))),
|
|
||||||
Vector3f(0.0f, 0.0f, 0.0f),
|
|
||||||
0.0075f,
|
|
||||||
0.0f,
|
|
||||||
Pair(50, 100),
|
|
||||||
Triple(Vector3f(1.0f, 1.0f, 1.0f), Vector3f(0.0f, 0.0f, 0.0f), InterpolationCurves.SQRT),
|
|
||||||
Triple(Vector3f(0.0f, 0.0f, 0.0f), Vector3f(3.0f, 3.0f, 3.0f), InterpolationCurves.SQRT)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,136 @@
|
|||||||
package com.bytedice.bde_particles.particles
|
package com.bytedice.bde_particles.particles
|
||||||
|
|
||||||
|
import com.bytedice.bde_particles.randomFloatBetween
|
||||||
|
import com.bytedice.bde_particles.randomIntBetween
|
||||||
import org.joml.Vector2f
|
import org.joml.Vector2f
|
||||||
import org.joml.Vector3f
|
import org.joml.Vector3f
|
||||||
|
import kotlin.math.*
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
sealed class SpawningShape {
|
sealed class SpawningShape {
|
||||||
data class CIRCLE(val radius: Float = 1.0f) : SpawningShape()
|
class Circle(val radius: Float, val spawnOnEdge: Boolean) : SpawningShape() {
|
||||||
data class RECT (val size: Vector2f = Vector2f(1.0f, 1.0f)) : SpawningShape()
|
fun randomWithin(): Vector3f {
|
||||||
data class CUBE (val size: Vector3f = Vector3f(1.0f, 1.0f, 1.0f)) : SpawningShape()
|
val randRadius = radius * sqrt(randomFloatBetween(0.0f, 1.0f))
|
||||||
data class SPHERE(val radius: Float = 1.0f) : SpawningShape()
|
val randAngle = randomFloatBetween(0.0f, Math.PI.toFloat() * 2)
|
||||||
data object POINT : SpawningShape()
|
|
||||||
|
val randomPos = Vector3f(randRadius * cos(randAngle), 0.0f, randRadius * sin(randAngle))
|
||||||
|
return randomPos
|
||||||
|
}
|
||||||
|
|
||||||
|
fun randomOnEdge(): Vector3f {
|
||||||
|
val randAngle = randomFloatBetween(0.0f, Math.PI.toFloat() * 2)
|
||||||
|
return Vector3f(radius * cos(randAngle), 0.0f, radius * sin(randAngle))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Rect(val size: Vector2f, val spawnOnEdge: Boolean) : SpawningShape() {
|
||||||
|
fun randomWithin(): Vector3f {
|
||||||
|
return Vector3f(randomFloatBetween(0.0f, size.x), 0.0f, randomFloatBetween(0.0f, size.y))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun randomOnEdge(): Vector3f {
|
||||||
|
val perimeter = 2 * (size.x + size.y)
|
||||||
|
val randPos = randomFloatBetween(0.0f, perimeter)
|
||||||
|
|
||||||
|
return when {
|
||||||
|
randPos <= size.x -> Vector3f(randPos, 0.0f, 0.0f)
|
||||||
|
randPos <= size.x + size.y -> Vector3f(size.x, 0.0f, randPos - size.x)
|
||||||
|
randPos <= 2 * size.x + size.y -> Vector3f(size.x - (randPos - size.x - size.y), 0.0f, size.y)
|
||||||
|
else -> Vector3f(0.0f, 0.0f, size.y - (randPos - 2 * size.x - size.y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Cube(val size: Vector3f, val spawnOnEdge: Boolean) : SpawningShape() {
|
||||||
|
fun randomWithin(): Vector3f {
|
||||||
|
return Vector3f(
|
||||||
|
randomFloatBetween(0.0f, size.x),
|
||||||
|
randomFloatBetween(0.0f, size.y),
|
||||||
|
randomFloatBetween(0.0f, size.z)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun randomOnEdge(): Vector3f {
|
||||||
|
val edgeIndex = randomIntBetween(0, 12) // 12 edges
|
||||||
|
val randPos = randomFloatBetween(0.0f, 1.0f)
|
||||||
|
|
||||||
|
return when (edgeIndex) {
|
||||||
|
0 -> Vector3f(randPos * size.x, 0.0f, 0.0f)
|
||||||
|
1 -> Vector3f(size.x, 0.0f, randPos * size.z)
|
||||||
|
2 -> Vector3f(size.x - randPos * size.x, 0.0f, size.z)
|
||||||
|
3 -> Vector3f(0.0f, 0.0f, size.z - randPos * size.z)
|
||||||
|
4 -> Vector3f(randPos * size.x, size.y, 0.0f)
|
||||||
|
5 -> Vector3f(size.x, size.y, randPos * size.z)
|
||||||
|
6 -> Vector3f(size.x - randPos * size.x, size.y, size.z)
|
||||||
|
7 -> Vector3f(0.0f, size.y, size.z - randPos * size.z)
|
||||||
|
8 -> Vector3f(0.0f, randPos * size.y, 0.0f)
|
||||||
|
9 -> Vector3f(size.x, randPos * size.y, 0.0f)
|
||||||
|
10 -> Vector3f(size.x, randPos * size.y, size.z)
|
||||||
|
11 -> Vector3f(0.0f, randPos * size.y, size.z)
|
||||||
|
|
||||||
|
else -> Vector3f(0.0f, 0.0f, 0.0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Sphere(val radius: Float, val spawnOnEdge: Boolean) : SpawningShape() {
|
||||||
|
fun randomWithin(): Vector3f {
|
||||||
|
val randRadius = radius * Random.nextDouble().pow(1.0 / 3.0)
|
||||||
|
|
||||||
|
val phi = Random.nextDouble(0.0, 2 * Math.PI)
|
||||||
|
|
||||||
|
val cosTheta = Random.nextDouble(-1.0, 1.0)
|
||||||
|
val theta = acos(cosTheta)
|
||||||
|
|
||||||
|
val x = randRadius * sin(theta) * cos(phi)
|
||||||
|
val y = randRadius * sin(theta) * sin(phi)
|
||||||
|
val z = randRadius * cos(theta)
|
||||||
|
|
||||||
|
return Vector3f(x.toFloat(), y.toFloat(), z.toFloat())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun randomOnEdge(): Vector3f {
|
||||||
|
val phi = randomFloatBetween(0.0f, Math.PI.toFloat() * 2)
|
||||||
|
val cosTheta = randomFloatBetween(-1.0f, 1.0f)
|
||||||
|
val sinTheta = sqrt(1 - cosTheta * cosTheta)
|
||||||
|
|
||||||
|
val x = radius * sinTheta * cos(phi)
|
||||||
|
val y = radius * sinTheta * sin(phi)
|
||||||
|
val z = radius * cosTheta
|
||||||
|
|
||||||
|
return Vector3f(x, y, z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() }.sub(Vector3f(shape.size.x, 0.0f, shape.size.y).div(2.0f))
|
||||||
|
is Cube -> if (!shape.spawnOnEdge) { shape.randomWithin() } else { shape.randomOnEdge() }.sub(Vector3f(shape.size.x, shape.size.y, shape.size.z).div(2.0f))
|
||||||
|
is Point -> Vector3f(0.0f, 0.0f, 0.0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sealed class ForceFieldShape {
|
sealed class ForceFieldShape {
|
||||||
data class SPHERE(
|
data class Sphere(
|
||||||
val radius: Float = 3.0f,
|
val radius: Float,
|
||||||
val force: Pair<Float, Float> = Pair(0.0f, 0.02f),
|
val force: Pair<Float, Float>,
|
||||||
) : ForceFieldShape()
|
) : ForceFieldShape()
|
||||||
data class CUBE(
|
|
||||||
val size: Vector3f = Vector3f(1.0f, 1.0f, 1.0f),
|
data class Cube(
|
||||||
val forceDir: Vector3f = Vector3f(0.0f, 0.0f, 0.0f)
|
val size: Vector3f,
|
||||||
|
val force: Vector3f
|
||||||
) : ForceFieldShape()
|
) : ForceFieldShape()
|
||||||
|
|
||||||
|
fun deepCopy() : ForceFieldShape {
|
||||||
|
return when (this) {
|
||||||
|
is Sphere -> Sphere(this.radius, this.force)
|
||||||
|
is Cube -> Cube(Vector3f(this.size), Vector3f(this.force))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user