KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/defs/projectile/Configurable.kt
2022-02-22 09:22:43 +07:00

244 lines
7.7 KiB
Kotlin

package ru.dbotthepony.kstarbound.defs.projectile
import com.google.common.collect.ImmutableList
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.*
import ru.dbotthepony.kstarbound.io.ConfigurableTypeAdapter
import ru.dbotthepony.kstarbound.io.KTypeAdapter
import ru.dbotthepony.kstarbound.io.CustomEnumTypeAdapter
import ru.dbotthepony.kvector.vector.Color
import kotlin.properties.Delegates
class ConfigurableProjectile : ConfigurableDefinition<ConfigurableProjectile, ConfiguredProjectile>() {
var projectileName by Delegates.notNull<String>()
var physics: ProjectilePhysics = ProjectilePhysics.DEFAULT
var damageKindImage: String? = null
var damageType = DamageType.NORMAL
var damageKind: String? = null
var pointLight: Boolean = false
var animationLoops: Boolean = true
var lightColor: Color? = null
var onlyHitTerrain: Boolean = false
var orientationLocked: Boolean = false
var image: String? = null
var timeToLive: Double = Double.POSITIVE_INFINITY
var animationCycle: Double = Double.POSITIVE_INFINITY
var bounces: Int = -1
var frameNumber: Int = 1
var scripts: Array<String> = Array(0) { "" }
var hydrophobic: Boolean = false
// we can't have concrete type here, since final class is commanded by `action` property of each entry
var actionOnReap: Array<JsonObject>? = null
var piercing = false
var speed = 0.0
var power = 0.0
override fun configure(directory: String): ConfiguredProjectile {
val actions = ArrayList<IActionOnReap>()
if (actionOnReap != null) {
for (action in actionOnReap!!) {
val configurable = constructAction(action)
if (configurable != null) {
actions.add(configurable.configure(directory))
}
}
}
if (timeToLive.isInfinite() && animationCycle.isFinite() && !animationLoops) {
timeToLive = animationCycle * (frameNumber - 1)
LOGGER.warn("{} has no time to live defined, assuming it live as long as its animation plays: {}", projectileName, timeToLive)
}
check(timeToLive >= 0.0) { "Invalid time to live $timeToLive" }
return ConfiguredProjectile(
json = enroll(),
projectileName = projectileName,
physics = physics,
damageKindImage = damageKindImage,
damageType = damageType,
damageKind = damageKind,
pointLight = pointLight,
lightColor = lightColor,
onlyHitTerrain = onlyHitTerrain,
orientationLocked = orientationLocked,
image = IFrameGrid.loadFrameStrip(ensureAbsolutePath(requireNotNull(image) { "image is null" }, directory), weak = true),
timeToLive = timeToLive,
animationCycle = animationCycle,
bounces = bounces,
frameNumber = frameNumber,
scripts = scripts,
actionOnReap = ImmutableList.copyOf(actions),
animationLoops = animationLoops,
hydrophobic = hydrophobic,
piercing = piercing,
speed = speed,
power = power,
)
}
companion object {
val ADAPTER = ConfigurableTypeAdapter(
::ConfigurableProjectile,
ConfigurableProjectile::projectileName,
ConfigurableProjectile::physics,
ConfigurableProjectile::damageKindImage,
ConfigurableProjectile::damageType,
ConfigurableProjectile::damageKind,
ConfigurableProjectile::pointLight,
ConfigurableProjectile::lightColor,
ConfigurableProjectile::onlyHitTerrain,
ConfigurableProjectile::orientationLocked,
ConfigurableProjectile::image,
ConfigurableProjectile::timeToLive,
ConfigurableProjectile::animationCycle,
ConfigurableProjectile::bounces,
ConfigurableProjectile::frameNumber,
ConfigurableProjectile::scripts,
ConfigurableProjectile::actionOnReap,
ConfigurableProjectile::animationLoops,
ConfigurableProjectile::hydrophobic,
ConfigurableProjectile::piercing,
ConfigurableProjectile::speed,
ConfigurableProjectile::power,
)
fun registerGson(gson: GsonBuilder) {
gson.registerTypeAdapter(ConfigurableProjectile::class.java, ADAPTER)
gson.registerTypeAdapter(ProjectilePhysics::class.java, CustomEnumTypeAdapter(ProjectilePhysics.values()).nullSafe())
gson.registerTypeAdapter(ActionConfig::class.java, ActionConfig.ADAPTER)
gson.registerTypeAdapter(ActionProjectile::class.java, ActionProjectile.ADAPTER)
gson.registerTypeAdapter(ActionSound::class.java, ActionSound.ADAPTER)
gson.registerTypeAdapter(ActionLoop::class.java, ActionLoop.ADAPTER)
gson.registerTypeAdapter(ActionActions::class.java, ActionActions.ADAPTER)
}
}
}
/////////////////////////////////
// Action on Reap
/////////////////////////////////
interface IConfigurableAction {
fun configure(directory: String = ""): IActionOnReap
}
private val MISSING_ACTIONS = ObjectArraySet<String>()
private val LOGGER = LogManager.getLogger()
private fun constructAction(input: JsonObject): IConfigurableAction? {
return when (val elem = (input["action"] ?: throw IllegalArgumentException("Action has no, well, `action` key to specify whatever is it.")).asString) {
"config" -> Starbound.gson.fromJson(input, ActionConfig::class.java)
"projectile" -> Starbound.gson.fromJson(input, ActionProjectile::class.java)
"sound" -> Starbound.gson.fromJson(input, ActionSound::class.java)
"loop" -> Starbound.gson.fromJson(input, ActionLoop::class.java)
"actions" -> Starbound.gson.fromJson(input, ActionActions::class.java)
else -> {
if (!MISSING_ACTIONS.contains(elem)) {
MISSING_ACTIONS.add(elem)
LOGGER.error("No projectile action on reap handler is registered for '{}'!", elem)
}
return null
}
}
}
class ActionConfig : IConfigurableAction {
lateinit var file: String
override fun configure(directory: String): IActionOnReap {
return cache.computeIfAbsent(ensureAbsolutePath(file, directory)) {
if (!Starbound.pathExists(it)) {
LOGGER.error("Config $it does not exist")
return@computeIfAbsent CActionConfig(file, null)
}
return@computeIfAbsent CActionConfig(file, constructAction(Starbound.loadJson(it) as JsonObject)?.configure())
}
}
companion object {
val ADAPTER = KTypeAdapter(::ActionConfig, ActionConfig::file).ignoreProperty("action")
private val cache = HashMap<String, CActionConfig>()
}
}
class ActionProjectile : IConfigurableAction {
lateinit var type: String
var angle = 0.0
var inheritDamageFactor = 1.0
override fun configure(directory: String): IActionOnReap {
return CActionProjectile(type, angle, inheritDamageFactor)
}
companion object {
val ADAPTER = KTypeAdapter(::ActionProjectile,
ActionProjectile::type,
ActionProjectile::angle,
ActionProjectile::inheritDamageFactor,
).ignoreProperty("action").missingPropertiesAreFatal(false)
}
}
class ActionSound : IConfigurableAction {
lateinit var options: Array<String>
override fun configure(directory: String): IActionOnReap {
return CActionSound(ImmutableList.copyOf(options))
}
companion object {
val ADAPTER = KTypeAdapter(::ActionSound,
ActionSound::options,
).ignoreProperty("action")
}
}
class ActionLoop : IConfigurableAction {
var count by Delegates.notNull<Int>()
var body by Delegates.notNull<Array<JsonObject>>()
override fun configure(directory: String): IActionOnReap {
return CActionLoop(count, ImmutableList.copyOf(body.mapNotNull { constructAction(it)?.configure() }))
}
companion object {
val ADAPTER = KTypeAdapter(::ActionLoop,
ActionLoop::count,
ActionLoop::body,
).ignoreProperty("action")
}
}
class ActionActions : IConfigurableAction {
var list by Delegates.notNull<Array<JsonObject>>()
override fun configure(directory: String): IActionOnReap {
return CActionActions(ImmutableList.copyOf(list.mapNotNull { constructAction(it)?.configure() }))
}
companion object {
val ADAPTER = KTypeAdapter(::ActionActions,
ActionActions::list,
).ignoreProperty("action")
}
}