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() { var projectileName by Delegates.notNull() 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 = 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? = null var piercing = false var speed = 0.0 var power = 0.0 override fun configure(directory: String): ConfiguredProjectile { val actions = ArrayList() 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() 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() } } 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 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() var body by Delegates.notNull>() 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>() 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") } }