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
|
||||
about: Create a report to help us improve
|
||||
about: Create a report to help us squash pests
|
||||
title: ''
|
||||
labels: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
@@ -10,6 +10,9 @@ assignees: ''
|
||||
**Describe the bug**
|
||||
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**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
@@ -23,16 +26,5 @@ A clear and concise description of what you expected to happen.
|
||||
**Screenshots**
|
||||
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**
|
||||
Add any other context about the problem here.
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
labels: enhancement
|
||||
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 [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
**Describe the result you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**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**
|
||||
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.
|
||||
|
||||
<!-- 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 with those commands are **temporary** and get removed 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, removed, or changed with those commands are **temporary** and get reverted 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
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.bytedice.bde_particles.client
|
||||
import com.bytedice.bde_particles.commands.GiveEmitterTool
|
||||
import com.bytedice.bde_particles.commands.KillAllEmitters
|
||||
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.onRightClick
|
||||
import com.bytedice.bde_particles.tick
|
||||
@@ -38,8 +40,9 @@ class Bde_particlesClient : ClientModInitializer {
|
||||
|
||||
CommandRegistrationCallback.EVENT.register { dispatcher, registryAccess, _ ->
|
||||
GiveEmitterTool.register(dispatcher, registryAccess)
|
||||
ManageEmitters .register(dispatcher)
|
||||
ManageEmitters .register(dispatcher, registryAccess)
|
||||
KillAllEmitters.register(dispatcher)
|
||||
SpawnEmitter .register(dispatcher)
|
||||
}
|
||||
|
||||
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.KillAllEmitters
|
||||
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.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.ModInitializer
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents
|
||||
import net.fabricmc.fabric.api.event.player.UseItemCallback
|
||||
import net.fabricmc.fabric.api.gamerule.v1.GameRuleFactory
|
||||
import net.fabricmc.fabric.api.gamerule.v1.GameRuleRegistry
|
||||
import net.fabricmc.loader.api.FabricLoader
|
||||
import net.minecraft.entity.EntityType
|
||||
import net.minecraft.entity.decoration.DisplayEntity.BlockDisplayEntity
|
||||
import net.minecraft.entity.decoration.DisplayEntity.ItemDisplayEntity
|
||||
import net.minecraft.entity.player.PlayerEntity
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.nbt.NbtCompound
|
||||
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.world.ServerWorld
|
||||
import net.minecraft.text.Style
|
||||
import net.minecraft.text.Text
|
||||
import net.minecraft.util.ActionResult
|
||||
import net.minecraft.util.Hand
|
||||
import net.minecraft.util.TypedActionResult
|
||||
import net.minecraft.world.GameRules
|
||||
import net.minecraft.world.World
|
||||
import org.joml.Vector2f
|
||||
import java.awt.Color
|
||||
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()
|
||||
val sessionUuid: UUID = UUID.randomUUID()
|
||||
var LIVING_PARTICLE_COUNT: Int = 0
|
||||
val SESSION_UUID: UUID = UUID.randomUUID()
|
||||
|
||||
|
||||
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() {
|
||||
if (FabricLoader.getInstance().environmentType != EnvType.SERVER) {
|
||||
return
|
||||
@@ -39,18 +84,15 @@ class Bde_particles : ModInitializer {
|
||||
|
||||
println("BPS - Initializing on ${FabricLoader.getInstance().environmentType}")
|
||||
|
||||
ServerLifecycleEvents.SERVER_STARTED.register { _ ->
|
||||
init()
|
||||
}
|
||||
|
||||
ServerTickEvents.START_SERVER_TICK.register { server ->
|
||||
tick(server)
|
||||
}
|
||||
|
||||
CommandRegistrationCallback.EVENT.register { dispatcher, registryAccess, _ ->
|
||||
GiveEmitterTool.register(dispatcher, registryAccess)
|
||||
ManageEmitters .register(dispatcher)
|
||||
ManageEmitters .register(dispatcher, registryAccess)
|
||||
KillAllEmitters.register(dispatcher)
|
||||
SpawnEmitter .register(dispatcher)
|
||||
}
|
||||
|
||||
UseItemCallback.EVENT.register(UseItemCallback { player: PlayerEntity, world: World, hand: Hand ->
|
||||
@@ -61,6 +103,8 @@ class Bde_particles : ModInitializer {
|
||||
})
|
||||
|
||||
ServerLifecycleEvents.SERVER_STARTED.register(ServerLifecycleEvents.ServerStarted { _ ->
|
||||
init()
|
||||
|
||||
println("BPS - Progress bar filled :3")
|
||||
println(
|
||||
"\n____________ _____ _____ \n" +
|
||||
@@ -76,30 +120,40 @@ class Bde_particles : ModInitializer {
|
||||
|
||||
|
||||
fun init() {
|
||||
addToEmitterRegister("DEFAULT", EmitterParams.DEFAULT)
|
||||
addToEmitterRegister("FIRE_GEYSER", EmitterParams.FIRE_GEYSER)
|
||||
addToEmitterRegister("RING_EXPLOSION", EmitterParams.RING_EXPLOSION)
|
||||
addToRegister("DEFAULT", EmitterParams.DEFAULT)
|
||||
addToRegister("DEBUG", EmitterParams.DEBUG)
|
||||
addToRegister("STRESS_TEST", EmitterParams.STRESS_TEST)
|
||||
}
|
||||
|
||||
|
||||
fun tick(server: MinecraftServer) {
|
||||
for (world in server.worlds) {
|
||||
for (entity in world.iterateEntities()) {
|
||||
if (entity.type == EntityType.BLOCK_DISPLAY) {
|
||||
if (displayEntityContainsTag(entity as BlockDisplayEntity, "BPS_UUID")
|
||||
&& !displayEntityContainsTag(entity, sessionUuid.toString())) {
|
||||
runBlocking {
|
||||
server.worlds.map { world ->
|
||||
async {
|
||||
val displayEntities = world.iterateEntities()
|
||||
.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) {
|
||||
if (emitter.isDead) {
|
||||
ALL_PARTICLE_EMITTERS = ALL_PARTICLE_EMITTERS.toMutableList().apply { remove(emitter) }.toTypedArray()
|
||||
}
|
||||
else { emitter.tick() }
|
||||
runBlocking {
|
||||
ALL_PARTICLE_EMITTERS.map { emitter ->
|
||||
async {
|
||||
if (emitter.isDead) {
|
||||
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 (isEmitterTool, emitterId) = ParticleEmitterTool.getToolDetails(handItem)
|
||||
val hitResult = raycastFromPlayer(player as ServerPlayerEntity, 200.0)
|
||||
val emitterParams = getEmitterParams(emitterId)
|
||||
val emitterParams = getEmitterDataById(emitterId)
|
||||
|
||||
if (
|
||||
!isEmitterTool
|
||||
@@ -118,53 +172,77 @@ fun onRightClick(player: PlayerEntity, world: ServerWorld, hand: Hand) : TypedAc
|
||||
return TypedActionResult(ActionResult.PASS, handItem)
|
||||
}
|
||||
|
||||
val emitter = ParticleEmitter(hitResult.pos, world, emitterParams)
|
||||
ALL_PARTICLE_EMITTERS += emitter
|
||||
val debug = world.gameRules.getBoolean(Bde_particles.SHOW_PARTICLE_DEBUG)
|
||||
|
||||
val emitter = ParticleEmitter(
|
||||
hitResult.pos,
|
||||
Vector2f(0.0f, 0.0f),
|
||||
world,
|
||||
emitterParams,
|
||||
debug
|
||||
)
|
||||
spawnEmitter(emitter)
|
||||
|
||||
return TypedActionResult(ActionResult.PASS, handItem)
|
||||
}
|
||||
|
||||
|
||||
fun emitterParamsToJson(params: EmitterParams) : Map<String, Any> {
|
||||
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 {
|
||||
fun displayEntityContainsTag(entity: ItemDisplayEntity, tag: String) : Boolean {
|
||||
val nbt = NbtCompound()
|
||||
entity.writeNbt(nbt)
|
||||
val tagsNbtList = nbt.getList("Tags", 8)
|
||||
|
||||
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
|
||||
|
||||
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.NbtFloat
|
||||
import net.minecraft.nbt.NbtList
|
||||
import net.minecraft.nbt.NbtString
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
|
||||
class DisplayEntity(private var properties: DisplayEntityProperties?) {
|
||||
class DisplayEntity(var properties: DisplayEntityProperties?) {
|
||||
private var hasSpawned = false
|
||||
private var entity: BlockDisplayEntity? = null
|
||||
private var entity: ItemDisplayEntity? = null
|
||||
|
||||
fun spawn(world: ServerWorld) {
|
||||
if (hasSpawned || properties == null) { return }
|
||||
|
||||
entity = BlockDisplayEntity(EntityType.BLOCK_DISPLAY, world)
|
||||
entity = ItemDisplayEntity(EntityType.ITEM_DISPLAY, world)
|
||||
updateProperties(properties!!)
|
||||
|
||||
world.spawnEntity(entity)
|
||||
@@ -34,7 +34,13 @@ class DisplayEntity(private var properties: DisplayEntityProperties?) {
|
||||
|
||||
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 {
|
||||
val offsetList = NbtList().apply {
|
||||
@@ -65,6 +71,22 @@ class DisplayEntity(private var properties: DisplayEntityProperties?) {
|
||||
put("scale", scaleList)
|
||||
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)
|
||||
@@ -75,7 +97,7 @@ class DisplayEntity(private var properties: DisplayEntityProperties?) {
|
||||
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,28 @@ import org.joml.Vector3f
|
||||
import org.joml.Vector4f
|
||||
|
||||
data class DisplayEntityProperties (
|
||||
val pos: Vec3d = Vec3d(0.0, 0.0, 0.0),
|
||||
val rot: Vector2f = Vector2f(0.0f, 0.0f),
|
||||
var pos: Vec3d = Vec3d(0.0, 0.0, 0.0),
|
||||
var rot: Vector2f = Vector2f(0.0f, 0.0f),
|
||||
|
||||
val blockType: String = "minecraft:purple_concrete",
|
||||
val tags: Array<String> = emptyArray(),
|
||||
var model: String = "minecraft:purple_concrete",
|
||||
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),
|
||||
val leftRotation: Vector4f = Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
|
||||
val scale: Vector3f = Vector3f(1.0f, 1.0f, 1.0f),
|
||||
val rightRotation: Vector4f = Vector4f(0.0f, 0.0f, 0.0f, 1.0f)
|
||||
var translation: Vector3f = Vector3f(0.0f, 0.0f, 0.0f),
|
||||
var leftRotation: Vector4f = Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
|
||||
var scale: Vector3f = Vector3f(1.0f, 1.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.world.RaycastContext
|
||||
import net.minecraft.world.World
|
||||
import org.joml.Vector2f
|
||||
import org.joml.Vector3f
|
||||
import org.joml.Vector4f
|
||||
import kotlin.math.*
|
||||
import kotlin.random.Random
|
||||
|
||||
|
||||
class InterpolationCurves(val function: (Float) -> Float) {
|
||||
class LerpCurves(val function: (Float) -> Float) {
|
||||
companion object {
|
||||
val LINEAR = InterpolationCurves { y -> y }
|
||||
val SQRT = InterpolationCurves { y -> sqrt(y) }
|
||||
val EXPONENT = InterpolationCurves { y -> y.pow(2.0f) }
|
||||
val CUBIC = InterpolationCurves { y -> y.pow(3.0f) }
|
||||
val SINE = InterpolationCurves { y -> sin(y * PI.toFloat() / 2) }
|
||||
val COSINE = InterpolationCurves { y -> 1 - cos(y * PI.toFloat() / 2) }
|
||||
val INVERSE = InterpolationCurves { y -> 1 - y }
|
||||
val LOG = InterpolationCurves { y -> if (y > 0) ln(y + 1) else 0f }
|
||||
val EXP = InterpolationCurves { y -> exp(y) - 1 }
|
||||
val BOUNCE = InterpolationCurves { y ->
|
||||
val Linear = LerpCurves { y -> y }
|
||||
val Sqrt = LerpCurves { y -> sqrt(y) }
|
||||
val Exponent = LerpCurves { y -> y.pow(2.0f) }
|
||||
val Cubic = LerpCurves { y -> y.pow(3.0f) }
|
||||
val Sine = LerpCurves { y -> sin(y * PI.toFloat() / 2) }
|
||||
val Cosine = LerpCurves { y -> 1 - cos(y * PI.toFloat() / 2) }
|
||||
val Inverse = LerpCurves { y -> 1 - y }
|
||||
val Log = LerpCurves { y -> if (y > 0) ln(y + 1) else 0f }
|
||||
val Exp = LerpCurves { y -> exp(y) - 1 }
|
||||
val Bounce = LerpCurves { y ->
|
||||
val n1 = 7.5625f
|
||||
val d1 = 2.75f
|
||||
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 {
|
||||
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 {
|
||||
return Random.nextInt(min, max)
|
||||
}
|
||||
|
||||
|
||||
fun randomBetweenVector3f(min: Vector3f, max: Vector3f) : Vector3f {
|
||||
return Vector3f(
|
||||
randomFloatBetween(min.x, max.x),
|
||||
randomFloatBetween(min.y, max.y),
|
||||
randomFloatBetween(min.z, max.z)
|
||||
)
|
||||
return if (min == max) { max }
|
||||
else if (min > max) { Random.nextInt(min - max) + max }
|
||||
else { Random.nextInt(max - min) + min }
|
||||
}
|
||||
|
||||
|
||||
fun eulerToQuat(euler: Vector3f): Vector4f {
|
||||
val roll = euler.x
|
||||
val pitch = euler.y
|
||||
val yaw = euler.z
|
||||
val roll = euler.x * (Math.PI / 180).toFloat()
|
||||
val pitch = euler.y * (Math.PI / 180).toFloat()
|
||||
val yaw = euler.z * (Math.PI / 180).toFloat()
|
||||
|
||||
val cy = cos(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 {
|
||||
val x = offset.x
|
||||
val y = offset.y
|
||||
@@ -205,22 +164,14 @@ fun transformOffsetByScale(offset: Vector3f, scale: Vector3f): Vector3f {
|
||||
|
||||
|
||||
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 {
|
||||
val clampedT = t.coerceIn(0.0f, 1.0f)
|
||||
|
||||
val curvedT = curve.function(clampedT)
|
||||
|
||||
val interpolatedVec = Vector3f(
|
||||
lerp(start.x, end.x, curvedT),
|
||||
lerp(start.y, end.y, curvedT),
|
||||
lerp(start.z, end.z, curvedT)
|
||||
)
|
||||
|
||||
return interpolatedVec
|
||||
fun lerpArray(array: Array<Any>, t: Float, curve: LerpCurves = LerpCurves.Linear) : Any {
|
||||
val idx = round(lerp(0.0f, array.lastIndex.toFloat(), t) * curve.function(t)).toInt()
|
||||
return array[idx.coerceIn(0, array.lastIndex)]
|
||||
}
|
||||
|
||||
|
||||
@@ -234,7 +185,6 @@ fun sdfSphere(pos: Vector3f, radius: Float, objectPos: Vector3f): Float {
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun sdfCube(pos: Vector3f, size: Vector3f, objectPos: Vector3f): Float {
|
||||
val px = objectPos.x - pos.x
|
||||
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
|
||||
|
||||
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.CommandDispatcher
|
||||
import com.mojang.brigadier.arguments.StringArgumentType
|
||||
import com.mojang.brigadier.context.CommandContext
|
||||
import net.minecraft.command.CommandRegistryAccess
|
||||
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.ServerCommandSource
|
||||
import net.minecraft.text.Style
|
||||
@@ -18,9 +22,9 @@ import java.util.concurrent.CompletableFuture
|
||||
object GiveEmitterTool {
|
||||
fun register(dispatcher: CommandDispatcher<ServerCommandSource>, registryAccess: CommandRegistryAccess) {
|
||||
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 itemTypeArg = CommandManager.argument("Item Type", ItemStackArgumentType.itemStack(registryAccess))
|
||||
@@ -33,26 +37,42 @@ object GiveEmitterTool {
|
||||
CompletableFuture.completedFuture(builder.build())
|
||||
}
|
||||
|
||||
dispatcher.register(
|
||||
command
|
||||
.then(itemNameArg
|
||||
.then(itemTypeArg
|
||||
.then(emitterIdSuggestion
|
||||
.executes { context ->
|
||||
val name = StringArgumentType.getString(context, "Item Name")
|
||||
val item = ItemStackArgumentType.getItemStackArgument(context, "Item Type")
|
||||
val emitterId = StringArgumentType.getString(context, "Emitter ID")
|
||||
dispatcher.register(command
|
||||
.then(emitterIdSuggestion
|
||||
.then(itemTypeArg
|
||||
.then(itemNameArg
|
||||
.executes { context ->
|
||||
val name = StringArgumentType.getString(context, "Item Name")
|
||||
val item = ItemStackArgumentType.getItemStackArgument(context, "Item Type")
|
||||
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}\".")
|
||||
.setStyle(Style.EMPTY.withColor(TextColor.fromRgb(Color(0, 200, 0).rgb)))
|
||||
|
||||
context.source.player?.inventory?.insertStack(ParticleEmitterTool.makeData(item.item, name, emitterId))
|
||||
context.source.sendFeedback({ feedback }, false)
|
||||
Command.SINGLE_SUCCESS
|
||||
}
|
||||
)
|
||||
exec(context, emitterId, item.item, name)
|
||||
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(
|
||||
command.executes { context ->
|
||||
var amountEmitters = 0
|
||||
for (emitter in ALL_PARTICLE_EMITTERS) {
|
||||
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))
|
||||
|
||||
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
|
||||
|
||||
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.positiveFeedback
|
||||
import com.mojang.brigadier.Command
|
||||
import com.mojang.brigadier.CommandDispatcher
|
||||
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 net.minecraft.component.DataComponentTypes
|
||||
import net.minecraft.component.type.NbtComponent
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.item.Items
|
||||
import net.minecraft.nbt.NbtCompound
|
||||
import com.mojang.brigadier.context.CommandContext
|
||||
import net.minecraft.command.CommandRegistryAccess
|
||||
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 net.minecraft.text.TextColor
|
||||
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
|
||||
// <create / remove / config / list>
|
||||
// <create / remove / config / list / copy>
|
||||
// create
|
||||
// <emitter id>
|
||||
// <preset emitter id>
|
||||
@@ -36,9 +64,8 @@ import java.util.concurrent.CompletableFuture
|
||||
|
||||
// config
|
||||
// <emitter id>
|
||||
// <PARTICLE / EMITTER>
|
||||
// <param key> // HARDCODED KEYS, if EMITTER use emitter params.
|
||||
// <new param value> // HARDCODED TYPES
|
||||
// <param key>
|
||||
// <new param value>
|
||||
// output -> update the selected parameter of the selected emitter id to the new value
|
||||
|
||||
// list
|
||||
@@ -50,219 +77,121 @@ import java.util.concurrent.CompletableFuture
|
||||
|
||||
|
||||
object ManageEmitters {
|
||||
fun register(dispatcher: CommandDispatcher<ServerCommandSource>) {
|
||||
|
||||
fun register(dispatcher: CommandDispatcher<ServerCommandSource>, registryAccess: CommandRegistryAccess) {
|
||||
val command = CommandManager.literal("ManageEmitters")
|
||||
.requires { source -> source.hasPermissionLevel(4) }
|
||||
.requires { source -> source.hasPermissionLevel(2) }
|
||||
|
||||
dispatcher.register(
|
||||
command
|
||||
// <create / remove / config / list / copy>
|
||||
.then(argCreate())
|
||||
.then(argRemove())
|
||||
.then(argConfig())
|
||||
.then(argList())
|
||||
.then(argCopy())
|
||||
)
|
||||
}
|
||||
}
|
||||
.then(CommandManager.literal("create").then(create()))
|
||||
.then(CommandManager.literal("remove").then(remove()))
|
||||
.then(CommandManager.literal("config").then(config(registryAccess)))
|
||||
.then(CommandManager.literal("list")
|
||||
.executes { context ->
|
||||
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
|
||||
|
||||
// TODO: perhaps make this a Command.literal() if possible
|
||||
val emitterIdSuggestion: RequiredArgumentBuilder<ServerCommandSource, String> = CommandManager.argument("Registered Emitter ID", StringArgumentType.string())
|
||||
.suggests { _, builder ->
|
||||
allEmitterIds.forEach { key ->
|
||||
builder.suggest(key)
|
||||
}
|
||||
CompletableFuture.completedFuture(builder.build())
|
||||
context.source.sendFeedback({ feedback }, false)
|
||||
Command.SINGLE_SUCCESS
|
||||
}
|
||||
)
|
||||
//.then(CommandManager.literal("copy"))
|
||||
)
|
||||
}
|
||||
|
||||
val emitterConfigKeys = ArgConfigEmitterKeys()
|
||||
val particleConfigKeys = ArgConfigParticleKeys()
|
||||
|
||||
|
||||
// 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
|
||||
private fun create() : RequiredArgumentBuilder<ServerCommandSource, String> {
|
||||
return CommandManager.argument("New Emitter ID", StringArgumentType.string())
|
||||
.then(emitterListArg("Preset Emitter ID")
|
||||
.executes { context ->
|
||||
val emitterIdVal = StringArgumentType.getString(context, "Emitter ID")
|
||||
val emitterPresetVal = StringArgumentType.getString(context, "Registered Emitter ID") ?: "DEFAULT"
|
||||
val emitterPresetParams = getEmitterParams(emitterPresetVal)
|
||||
val newEmitterId = StringArgumentType.getString(context, "New Emitter ID")
|
||||
val presetEmitterId = StringArgumentType.getString(context, "Preset Emitter ID")
|
||||
|
||||
var newEmitterId = "NULL"
|
||||
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 )
|
||||
createExec(context, newEmitterId, presetEmitterId)
|
||||
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 ->
|
||||
val emitterIdVal = StringArgumentType.getString(context, "Registered Emitter ID") ?: "NULL"
|
||||
val isRemoved = removeFromEmitterRegister(emitterIdVal)
|
||||
val newEmitterId = StringArgumentType.getString(context, "New Emitter ID")
|
||||
|
||||
val feedback = if (isRemoved) {
|
||||
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 )
|
||||
createExec(context, newEmitterId, "")
|
||||
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
|
||||
// output -> all registered emitters
|
||||
fun argList() : LiteralArgumentBuilder<ServerCommandSource> {
|
||||
return CommandManager.literal("list")
|
||||
.executes { context ->
|
||||
val allEmittersString = allEmitterIds.joinToString(", ")
|
||||
if (usedDefault && result.second) {
|
||||
positiveFeedback(
|
||||
"Successfully added new Emitter with ID \"${result.first}\" to register.\n" +
|
||||
"(DEFAULT preset was used because the specified Preset Emitter ID wasn't valid.)",
|
||||
context
|
||||
)
|
||||
}
|
||||
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")
|
||||
.setStyle(Style.EMPTY.withColor(TextColor.fromRgb(Color(0, 200, 0).rgb)))
|
||||
private fun remove() : RequiredArgumentBuilder<ServerCommandSource, String> {
|
||||
return emitterListArg("Emitter ID")
|
||||
.executes { context ->
|
||||
val emitterId = StringArgumentType.getString(context, "Emitter ID")
|
||||
|
||||
val feedbackVar = Text.literal(allEmittersString)
|
||||
.setStyle(Style.EMPTY.withColor(TextColor.fromRgb(Color(255, 255, 255).rgb)))
|
||||
val result = removeFromRegister(emitterId)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
/**
|
||||
* EmitterParams defines the parameters for controlling how particles are emitted,
|
||||
* including settings for looping, spawn frequency, and maximum particle count.
|
||||
*
|
||||
* @param maxCount The maximum number of particles that can exist. 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.
|
||||
*/
|
||||
import com.bytedice.bde_particles.LerpCurves
|
||||
import org.joml.Vector3f
|
||||
|
||||
|
||||
data class EmitterParams (
|
||||
var maxCount: Int = 200,
|
||||
var spawnsPerTick: Int = 1,
|
||||
var loopDur: Int = 25,
|
||||
var loopDelay: Int = 0,
|
||||
var loopCount: Int = 0,
|
||||
var particle: ParticleParams = ParticleParams.DEFAULT,
|
||||
// spawning
|
||||
var maxCount: Int,
|
||||
var spawnRate: Int,
|
||||
var spawnChance: Float,
|
||||
var spawnDuration: ParamClasses.Duration,
|
||||
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 {
|
||||
val DEFAULT = EmitterParams()
|
||||
val FIRE_GEYSER = EmitterParams(
|
||||
200,
|
||||
1,
|
||||
25,
|
||||
0,
|
||||
0,
|
||||
ParticleParams.FIRE_GEYSER
|
||||
val DEFAULT = EmitterParams(
|
||||
maxCount = 200,
|
||||
spawnRate = 50,
|
||||
spawnChance = 1.0f,
|
||||
spawnDuration = ParamClasses.Duration.SingleBurst(4),
|
||||
spawnPosOffset = Vector3f(0.0f, 0.0f, 0.0f),
|
||||
lifeTime = ParamClasses.PairInt(25, 40),
|
||||
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(
|
||||
200,
|
||||
150,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
ParticleParams.RING_EXPLOSION
|
||||
val DEBUG = EmitterParams(
|
||||
maxCount = 50,
|
||||
spawnRate = 10,
|
||||
spawnChance = 1.0f,
|
||||
spawnDuration = ParamClasses.Duration.SingleBurst(5),
|
||||
spawnPosOffset = Vector3f(0.0f, 2.0f, 0.0f),
|
||||
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
|
||||
|
||||
data class ForceField (
|
||||
val name: String = "DEFAULT",
|
||||
val pos: Vector3f = Vector3f(0.0f, 5.0f, 0.0f),
|
||||
val shape: ForceFieldShape = ForceFieldShape.SPHERE()
|
||||
)
|
||||
val pos: Vector3f,
|
||||
val shape: ForceFieldShape
|
||||
) {
|
||||
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
|
||||
|
||||
import com.bytedice.bde_particles.*
|
||||
import net.minecraft.particle.DustParticleEffect
|
||||
import net.minecraft.server.world.ServerWorld
|
||||
import net.minecraft.util.math.Vec3d
|
||||
import org.joml.Vector2f
|
||||
import org.joml.Vector3f
|
||||
import org.joml.Vector4f
|
||||
import kotlin.math.*
|
||||
|
||||
|
||||
// TODO: consider moving most params to emitter (such as shape, and forceFields) to store less data
|
||||
|
||||
|
||||
class Particle(private val particleParams: ParticleParams) {
|
||||
private val initOffset = Vector3f(-0.5f, -0.5f, -0.5f)
|
||||
private var offset = Vector3f(0.0f, 0.0f, 0.0f)
|
||||
private var pos = Vec3d(0.0, 0.0, 0.0)
|
||||
|
||||
private var 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
|
||||
class Particle(
|
||||
private val emitterParams: EmitterParams,
|
||||
private var entityPos: Vec3d,
|
||||
private var entityRot: Vector2f,
|
||||
private val debug: Boolean = false
|
||||
) {
|
||||
private var pos = Vector3f(0.0f, 0.0f, 0.0f)
|
||||
private var timeAlive = 0
|
||||
|
||||
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) {
|
||||
this.pos = pos
|
||||
|
||||
if ( particleParams.uniformSize ) {
|
||||
val randomUniform = randomFloatBetween(particleParams.sizeRandom.first.x, particleParams.sizeRandom.second.x)
|
||||
scale.x = randomUniform
|
||||
scale.y = randomUniform
|
||||
scale.z = randomUniform
|
||||
fun init() {
|
||||
val so = Vector3f(emitterParams.spawnPosOffset)
|
||||
if (so.x + so.y + so.z != 0.0f) {
|
||||
entityPos = entityPos.add(so.x.toDouble(), so.y.toDouble(), so.z.toDouble())
|
||||
}
|
||||
|
||||
val shape = particleParams.shape
|
||||
val newSpawnPos: Vector3f
|
||||
val shape = emitterParams.shape
|
||||
val newSpawnPos = shape.random(shape)
|
||||
|
||||
when (shape) {
|
||||
is SpawningShape.CIRCLE -> { val posInCircle = randomInCircle(shape.radius); newSpawnPos = Vector3f(posInCircle.x, 0.0f, posInCircle.y) }
|
||||
is SpawningShape.SPHERE -> { newSpawnPos = randomInSphere(shape.radius) }
|
||||
is SpawningShape.RECT -> { val posInRect = randomInRect(shape.size); newSpawnPos = Vector3f(posInRect.x, 0.0f, posInRect.y) }
|
||||
is SpawningShape.CUBE -> { newSpawnPos = randomInCube(shape.size) }
|
||||
is SpawningShape.POINT -> { newSpawnPos = Vector3f(0.0f, 0.0f, 0.0f) }
|
||||
}
|
||||
pos = newSpawnPos
|
||||
|
||||
offset = newSpawnPos
|
||||
val 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(
|
||||
pos = this.pos,
|
||||
blockType = particleParams.blockCurve[0],
|
||||
translation = newOffset.add(offset),
|
||||
leftRotation = quatRot,
|
||||
scale = scale,
|
||||
tags = arrayOf("BPS_Particle", "BPS_UUID", sessionUuid.toString())
|
||||
pos = entityPos,
|
||||
rot = entityRot,
|
||||
model = if (emitterParams.modelCurve.array.isNotEmpty()) { emitterParams.modelCurve.lerp(0.0f) } else "none",
|
||||
translation = newPos.add(pos),
|
||||
leftRotation = quatRot,
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
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.") }
|
||||
}
|
||||
|
||||
@@ -81,9 +114,9 @@ class Particle(private val particleParams: ParticleParams) {
|
||||
|
||||
val newProperties = calcNewProperties()
|
||||
|
||||
blockDisplay?.updateProperties(newProperties)
|
||||
particleEntity.updateProperties(newProperties)
|
||||
|
||||
if (timeAlive > lifeTime) { isDead = true; blockDisplay?.kill() }
|
||||
if (timeAlive > lifeTime) { kill() }
|
||||
|
||||
timeAlive += 1
|
||||
}
|
||||
@@ -103,100 +136,130 @@ class Particle(private val particleParams: ParticleParams) {
|
||||
|
||||
|
||||
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 newBlock = calcBlockCurve(timeAliveClamped)
|
||||
val (newVel, newOffset) = calcOffset()
|
||||
val newRot = calcRot(timeAliveClamped)
|
||||
val newScale = calcSize(timeAliveClamped)
|
||||
val newModel = if (emitterParams.modelCurve.array.isNotEmpty()) { emitterParams.modelCurve.lerp(timeAliveClamped) } else "none"
|
||||
val (newVel, newPos) = calcPos()
|
||||
val newOriginOffset = Vector3f(originOffset.mul(emitterParams.offsetCurve.lerpToVector3f(timeAliveClamped)))
|
||||
val newBrightness = emitterParams.brightnessCurve.lerpToInt(timeAliveClamped)
|
||||
|
||||
vel = newVel
|
||||
offset = newOffset
|
||||
rot = newRot
|
||||
val TWVScale: Vector3f
|
||||
//val TWVRot: Vector3f
|
||||
val newRot: Vector3f
|
||||
|
||||
for (forceField in particleParams.forceFields) {
|
||||
if (forceField.shape is ForceFieldShape.SPHERE) { vel.add(sphereSdfVel(forceField)) }
|
||||
else if (forceField.shape is ForceFieldShape.CUBE) { vel.add(cubeSdfVel(forceField)) }
|
||||
when (emitterParams.transformWithVel) {
|
||||
is ParamClasses.TransformWithVel.RotOnly -> {
|
||||
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 combinedOffset = transformOffset.add(newOffset)
|
||||
val newScale = TWVScale.mul(calcScale(timeAliveClamped))
|
||||
//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(
|
||||
pos = pos,
|
||||
blockType = newBlock,
|
||||
translation = combinedOffset,
|
||||
leftRotation = quatRot,
|
||||
scale = newScale,
|
||||
tags = arrayOf("BPS_Particle", "BPS_UUID", sessionUuid.toString())
|
||||
pos = entityPos,
|
||||
rot = entityRot,
|
||||
model = newModel,
|
||||
translation = newTranslate,
|
||||
leftRotation = quatRot,
|
||||
scale = newScale,
|
||||
tags = arrayOf("BPS_Particle", "BPS_UUID", SESSION_UUID.toString()),
|
||||
brightnessOverride = newBrightness
|
||||
)
|
||||
|
||||
if (debug) { debugTool.update(entityRot, entityPos, newTranslate, vel, newScale) }
|
||||
|
||||
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() {
|
||||
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
|
||||
|
||||
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.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 timeAlive = 0
|
||||
private var loopCount = 0
|
||||
private var loopDelay = 0
|
||||
private var loopDur = 0
|
||||
private var loopDelayActive = false
|
||||
private var timeEmitted = 0
|
||||
private var timePaused = 0
|
||||
private var timesLooped = 0
|
||||
private var isPaused = false
|
||||
|
||||
private var isTicking = true
|
||||
|
||||
var isDead = false
|
||||
|
||||
private var debugTool = EmitterDebug(rot, pos, params.spawnPosOffset, params.shape)
|
||||
|
||||
fun tick() {
|
||||
|
||||
suspend fun tick() {
|
||||
if (isTicking) { count() }
|
||||
if (isTicking) { addParticle() }
|
||||
if (!isTicking && allParticles.isEmpty()) { isDead = true }
|
||||
if (!isTicking && allParticles.isEmpty()) { isDead = true; kill() }
|
||||
|
||||
for (particle in allParticles) {
|
||||
if (particle.isDead) { allParticles = allParticles.toMutableList().apply { remove(particle) }.toTypedArray() }
|
||||
else { particle.tick() }
|
||||
processParticlesInBatches(100)
|
||||
|
||||
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() {
|
||||
if (loopDelayActive) { return }
|
||||
if (isPaused) { return }
|
||||
|
||||
repeat(this.emitterParams.spawnsPerTick) {
|
||||
if (allParticles.size >= emitterParams.maxCount) { return }
|
||||
repeat(params.spawnRate) {
|
||||
if (allParticles.size >= params.maxCount) { return }
|
||||
|
||||
val particle = Particle(emitterParams.particle)
|
||||
particle.init(this.emitterPos)
|
||||
particle.spawn(emitterWorld)
|
||||
val maxParticles = world.gameRules.getInt(Bde_particles.GLOBAL_MAX_PARTICLES)
|
||||
if (LIVING_PARTICLE_COUNT >= maxParticles) { return }
|
||||
|
||||
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
|
||||
}
|
||||
@@ -46,44 +93,50 @@ class ParticleEmitter(private val emitterPos: Vec3d, private val emitterWorld: S
|
||||
|
||||
|
||||
private fun count() {
|
||||
// this is the only section I have comments
|
||||
// because im so damn good at cooking spaghetti
|
||||
if (params.spawnDuration is ParamClasses.Duration.SingleBurst) {
|
||||
val loopDur = (params.spawnDuration as ParamClasses.Duration.SingleBurst).loopDur
|
||||
if (timeEmitted >= loopDur) { isTicking = false; return }
|
||||
|
||||
// if max duration is 0 or less, skip counting, its infinite
|
||||
if (emitterParams.loopDur <= 0) { return }
|
||||
timeEmitted += 1
|
||||
}
|
||||
|
||||
// if the duration is greater than max duration, add 1 loopCount
|
||||
// return if max loopCount is 0
|
||||
// if the delay is greater than 0 then enable it
|
||||
if (loopDur >= emitterParams.loopDur) {
|
||||
if (loopCount == 0) {
|
||||
isTicking = false
|
||||
return
|
||||
else if (params.spawnDuration is ParamClasses.Duration.MultiBurst) {
|
||||
val loopDur = (params.spawnDuration as ParamClasses.Duration.MultiBurst).loopDur
|
||||
val loopDelay = (params.spawnDuration as ParamClasses.Duration.MultiBurst).loopDelay
|
||||
val loopCount = (params.spawnDuration as ParamClasses.Duration.MultiBurst).loopCount
|
||||
|
||||
if (timeEmitted >= loopDur && timesLooped + 1 >= loopCount) { isTicking = false; return }
|
||||
|
||||
if (timePaused >= loopDelay) {
|
||||
isPaused = false
|
||||
timePaused = 0
|
||||
timesLooped += 1
|
||||
}
|
||||
loopCount += 1
|
||||
loopDur = 0
|
||||
if (emitterParams.loopDelay > 0) { loopDelayActive = true }
|
||||
if (timeEmitted >= loopDur && loopDelay > 0) {
|
||||
isPaused = true
|
||||
timeEmitted = 0
|
||||
}
|
||||
|
||||
if (!isPaused) { timeEmitted += 1 }
|
||||
else { timePaused += 1 }
|
||||
}
|
||||
|
||||
// if the delay is greater than max delay, disable the delay
|
||||
else if (loopDelay >= emitterParams.loopDelay) {
|
||||
loopDelay = 0
|
||||
loopDelayActive = false
|
||||
else if (params.spawnDuration is ParamClasses.Duration.InfiniteLoop) {
|
||||
val loopDur = (params.spawnDuration as ParamClasses.Duration.InfiniteLoop).loopDur
|
||||
val loopDelay = (params.spawnDuration as ParamClasses.Duration.InfiniteLoop).loopDelay
|
||||
|
||||
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()
|
||||
allParticles = allParticles.toMutableList().apply { remove(particle) }.toTypedArray()
|
||||
}
|
||||
if (debug) { debugTool.kill() }
|
||||
isTicking = false
|
||||
isDead = true
|
||||
}
|
||||
|
||||
|
||||
fun stopTicking() {
|
||||
isTicking = false
|
||||
}
|
||||
}
|
||||
@@ -1,134 +1,97 @@
|
||||
@file:Suppress("UNCHECKED_CAST")
|
||||
|
||||
package com.bytedice.bde_particles.particles
|
||||
|
||||
import com.bytedice.bde_particles.InterpolationCurves
|
||||
import org.joml.Vector3f
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.KProperty1
|
||||
import kotlin.reflect.full.isSubclassOf
|
||||
|
||||
|
||||
val emitterIdRegister: MutableMap<String, EmitterParams> = mutableMapOf()
|
||||
val forbiddenIds = arrayOf("", "DEFAULT", "NULL")
|
||||
val idRegister: MutableMap<String, EmitterParams> = mutableMapOf()
|
||||
val forbiddenIds = arrayOf("", "NULL")
|
||||
|
||||
|
||||
fun addToEmitterRegister(id: String, params: EmitterParams) : Pair<String, Boolean> {
|
||||
val newId = id.replace(" ", "_")
|
||||
fun addToRegister(id: String, params: EmitterParams) : Pair<String, Boolean> {
|
||||
val newId = replaceForbiddenChars(id.replace(" ", "_"))
|
||||
|
||||
if (newId in forbiddenIds && newId != "DEFAULT") { return Pair(newId, false) }
|
||||
if (emitterIdRegister.containsKey(newId)) { return Pair(newId, false) }
|
||||
if (idRegister.containsKey(newId)) {
|
||||
println("BPS - Failed to register Emitter ID \"$newId\" as it already exists!")
|
||||
return Pair(newId, false)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
fun removeFromEmitterRegister(id: String) : Boolean {
|
||||
if (id in forbiddenIds) { return false }
|
||||
if (!emitterIdRegister.containsKey(id)) { return false }
|
||||
fun removeFromRegister(id: String) : Boolean {
|
||||
if (!idRegister.containsKey(id)) {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
fun getEmitterParams(id: String) : EmitterParams? {
|
||||
return emitterIdRegister[id]
|
||||
fun getEmitterDataById(id: String) : EmitterParams? {
|
||||
return idRegister[id]
|
||||
}
|
||||
|
||||
|
||||
fun updateEmitterParams(id: String, newParams: EmitterParams) {
|
||||
emitterIdRegister[id] = newParams
|
||||
fun updateRegistered(id: String, newParams: EmitterParams) {
|
||||
idRegister[id] = newParams
|
||||
}
|
||||
|
||||
|
||||
fun updateSingleEmitterParam(id: String, paramName: String, paramValue: Any) : Boolean {
|
||||
val emitter = getEmitterParams(id) ?: return false
|
||||
val updatedValues = updateSingleEmitterParam(emitter, paramName, paramValue)
|
||||
updateEmitterParams(id, updatedValues.first)
|
||||
fun updateParam(id: String, paramAccess: KProperty1<EmitterParams, *>, paramValue: Any) : Boolean {
|
||||
val emitter = getEmitterDataById(id) ?: return false
|
||||
val updatedValues = updateParams(emitter, paramAccess as KProperty1<EmitterParams, Any>, paramValue)
|
||||
|
||||
updateRegistered(id, updatedValues.first)
|
||||
|
||||
return updatedValues.second
|
||||
}
|
||||
|
||||
|
||||
fun updateSingleParticleParam(emitterId: String, paramName: String, paramValue: Any) : Boolean {
|
||||
val emitter = getEmitterParams(emitterId) ?: return false
|
||||
val particle = emitter.particle
|
||||
|
||||
val updatedValues = updateSingleParticleParam(particle, paramName, paramValue)
|
||||
|
||||
emitter.particle = updatedValues.first
|
||||
updateEmitterParams(emitterId, emitter)
|
||||
|
||||
return updatedValues.second
|
||||
}
|
||||
|
||||
|
||||
fun updateSingleEmitterParam(params: EmitterParams, paramName: String, paramValue: Any) : Pair<EmitterParams, Boolean> {
|
||||
val value: Any? = when (paramName) {
|
||||
"maxCount" -> paramValue as Int
|
||||
"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) }
|
||||
|
||||
fun updateParams(params: EmitterParams, paramAccess: KProperty1<EmitterParams, Any>, paramValue: Any): Pair<EmitterParams, Boolean> {
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
return Pair(newParams, true)
|
||||
return Pair(params, false)
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
fun replaceForbiddenChars(input: String): String {
|
||||
val regex = "[^a-zA-Z0-9_-]".toRegex()
|
||||
return input.replace(regex, "-")
|
||||
}
|
||||
@@ -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
|
||||
|
||||
import com.bytedice.bde_particles.randomFloatBetween
|
||||
import com.bytedice.bde_particles.randomIntBetween
|
||||
import org.joml.Vector2f
|
||||
import org.joml.Vector3f
|
||||
import kotlin.math.*
|
||||
import kotlin.random.Random
|
||||
|
||||
sealed class SpawningShape {
|
||||
data class CIRCLE(val radius: Float = 1.0f) : SpawningShape()
|
||||
data class RECT (val size: Vector2f = Vector2f(1.0f, 1.0f)) : SpawningShape()
|
||||
data class CUBE (val size: Vector3f = Vector3f(1.0f, 1.0f, 1.0f)) : SpawningShape()
|
||||
data class SPHERE(val radius: Float = 1.0f) : SpawningShape()
|
||||
data object POINT : SpawningShape()
|
||||
class Circle(val radius: Float, val spawnOnEdge: Boolean) : SpawningShape() {
|
||||
fun randomWithin(): Vector3f {
|
||||
val randRadius = radius * sqrt(randomFloatBetween(0.0f, 1.0f))
|
||||
val randAngle = randomFloatBetween(0.0f, Math.PI.toFloat() * 2)
|
||||
|
||||
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 {
|
||||
data class SPHERE(
|
||||
val radius: Float = 3.0f,
|
||||
val force: Pair<Float, Float> = Pair(0.0f, 0.02f),
|
||||
data class Sphere(
|
||||
val radius: Float,
|
||||
val force: Pair<Float, Float>,
|
||||
) : ForceFieldShape()
|
||||
data class CUBE(
|
||||
val size: Vector3f = Vector3f(1.0f, 1.0f, 1.0f),
|
||||
val forceDir: Vector3f = Vector3f(0.0f, 0.0f, 0.0f)
|
||||
|
||||
data class Cube(
|
||||
val size: Vector3f,
|
||||
val force: Vector3f
|
||||
) : 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