ImageReference, SpriteAnimator и почти избавились от старого FrameGrid

This commit is contained in:
DBotThePony 2023-01-01 18:07:46 +07:00
parent 9357835f4e
commit 69a5061e9e
Signed by: DBot
GPG Key ID: DCC23B5715498507
11 changed files with 207 additions and 59 deletions

View File

@ -15,6 +15,7 @@ import ru.dbotthepony.kstarbound.api.NonExistingFile
import ru.dbotthepony.kstarbound.api.PhysicalFile import ru.dbotthepony.kstarbound.api.PhysicalFile
import ru.dbotthepony.kstarbound.api.explore import ru.dbotthepony.kstarbound.api.explore
import ru.dbotthepony.kstarbound.defs.* import ru.dbotthepony.kstarbound.defs.*
import ru.dbotthepony.kstarbound.defs.animation.AtlasConfiguration
import ru.dbotthepony.kstarbound.defs.animation.SpriteReference import ru.dbotthepony.kstarbound.defs.animation.SpriteReference
import ru.dbotthepony.kstarbound.defs.item.ItemDefinition import ru.dbotthepony.kstarbound.defs.item.ItemDefinition
import ru.dbotthepony.kstarbound.defs.item.ItemRarity import ru.dbotthepony.kstarbound.defs.item.ItemRarity
@ -65,6 +66,9 @@ object Starbound {
private set private set
fun readingFolderTransformer(input: String): String { fun readingFolderTransformer(input: String): String {
val readingFolder = readingFolder
require(readingFolder != null) { "Not reading an asset on current thread" }
if (input[0] == '/') if (input[0] == '/')
return input return input
@ -72,6 +76,8 @@ object Starbound {
} }
fun readingFolderTransformerNullable(input: String?): String? { fun readingFolderTransformerNullable(input: String?): String? {
require(readingFolder != null) { "Not reading an asset on current thread" }
if (input != null) if (input != null)
return readingFolderTransformer(input) return readingFolderTransformer(input)
@ -158,6 +164,7 @@ object Starbound {
.also(ItemDefinition::registerGson) .also(ItemDefinition::registerGson)
.also(ItemRarity::registerGson) .also(ItemRarity::registerGson)
.also(SpriteReference::registerGson) .also(SpriteReference::registerGson)
.also(AtlasConfiguration::registerGson)
.registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe()) .registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe())
@ -371,6 +378,7 @@ object Starbound {
try { try {
callback("Loading $listedFile") callback("Loading $listedFile")
readingFolder = listedFile.computeDirectory()
val def = gson.fromJson(listedFile.reader(), ConfigurableProjectile::class.java).assemble(listedFile.computeDirectory()) val def = gson.fromJson(listedFile.reader(), ConfigurableProjectile::class.java).assemble(listedFile.computeDirectory())
check(projectiles[def.projectileName] == null) { "Already has projectile with ID ${def.projectileName} loaded!" } check(projectiles[def.projectileName] == null) { "Already has projectile with ID ${def.projectileName} loaded!" }
projectiles[def.projectileName] = def projectiles[def.projectileName] = def
@ -384,6 +392,8 @@ object Starbound {
} }
} }
} }
readingFolder = null
} }
private fun loadFunctions(callback: (String) -> Unit) { private fun loadFunctions(callback: (String) -> Unit) {
@ -392,6 +402,7 @@ object Starbound {
try { try {
callback("Loading $listedFile") callback("Loading $listedFile")
readingFolder = listedFile.computeDirectory()
val readObject = JsonParser.parseReader(listedFile.reader()) as JsonObject val readObject = JsonParser.parseReader(listedFile.reader()) as JsonObject
for (key in readObject.keySet()) { for (key in readObject.keySet()) {
@ -407,6 +418,8 @@ object Starbound {
} }
} }
} }
readingFolder = null
} }
private fun loadParallax(callback: (String) -> Unit) { private fun loadParallax(callback: (String) -> Unit) {
@ -415,6 +428,7 @@ object Starbound {
try { try {
callback("Loading $listedFile") callback("Loading $listedFile")
readingFolder = listedFile.computeDirectory()
val def = gson.fromJson(listedFile.reader(), ParallaxPrototype::class.java) val def = gson.fromJson(listedFile.reader(), ParallaxPrototype::class.java)
parallax[listedFile.name.substringBefore('.')] = def parallax[listedFile.name.substringBefore('.')] = def
} catch(err: Throwable) { } catch(err: Throwable) {
@ -426,6 +440,8 @@ object Starbound {
} }
} }
} }
readingFolder = null
} }
private fun loadMaterialModifiers(callback: (String) -> Unit) { private fun loadMaterialModifiers(callback: (String) -> Unit) {
@ -478,6 +494,8 @@ object Starbound {
} }
} }
} }
readingFolder = null
} }
private fun loadItemDefinitions(callback: (String) -> Unit) { private fun loadItemDefinitions(callback: (String) -> Unit) {
@ -502,5 +520,7 @@ object Starbound {
} }
} }
} }
readingFolder = null
} }
} }

View File

@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.client.render
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.client.gl.GLTexture2D import ru.dbotthepony.kstarbound.client.gl.GLTexture2D
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
import ru.dbotthepony.kstarbound.defs.animation.AtlasConfiguration
import ru.dbotthepony.kstarbound.defs.animation.IUVCoordinates import ru.dbotthepony.kstarbound.defs.animation.IUVCoordinates
import ru.dbotthepony.kstarbound.defs.animation.SpriteReference import ru.dbotthepony.kstarbound.defs.animation.SpriteReference
@ -10,20 +11,18 @@ import ru.dbotthepony.kstarbound.defs.animation.SpriteReference
* Связка текстуры-атласа + координат спрайта на ней * Связка текстуры-атласа + координат спрайта на ней
*/ */
class BoundSprite( class BoundSprite(
val reference: SpriteReference, val sprite: AtlasConfiguration.Sprite,
val texture: GLTexture2D val texture: GLTexture2D
) : IUVCoordinates { ) : IUVCoordinates {
inline val sprite get() = reference.sprite
/** /**
* Настоящая ширина спрайта, в пикселях * Настоящая ширина спрайта, в пикселях
*/ */
inline val width get() = reference.sprite.width(texture.width) inline val width get() = sprite.width(texture.width)
/** /**
* Настоящая высота спрайта, в пикселях * Настоящая высота спрайта, в пикселях
*/ */
inline val height get() = reference.sprite.height(texture.height) inline val height get() = sprite.height(texture.height)
override val u0: Float override val u0: Float
override val v0: Float override val v0: Float
@ -31,7 +30,7 @@ class BoundSprite(
override val v1: Float override val v1: Float
init { init {
val coords = reference.sprite.compute(texture) val coords = sprite.compute(texture)
this.u0 = coords.u0 this.u0 = coords.u0
this.v0 = coords.v0 this.v0 = coords.v0
@ -47,12 +46,19 @@ class BoundSprite(
* Создаёт связку текстуры-атласа + координгат спрайта на ней * Создаёт связку текстуры-атласа + координгат спрайта на ней
*/ */
fun SpriteReference.bind(texture: GLTexture2D): BoundSprite { fun SpriteReference.bind(texture: GLTexture2D): BoundSprite {
return BoundSprite(this, texture) return BoundSprite(sprite, texture)
} }
/** /**
* Создаёт связку текстуры-атласа, которая загружается через [GLStateTracker.loadNamedTextureSafe] + координгат спрайта на ней * Создаёт связку текстуры-атласа, которая загружается через [GLStateTracker.loadNamedTextureSafe] + координгат спрайта на ней
*/ */
fun SpriteReference.bind(state: GLStateTracker): BoundSprite { fun SpriteReference.bind(state: GLStateTracker): BoundSprite {
return BoundSprite(this, state.loadNamedTextureSafe(path)) return BoundSprite(sprite, state.loadNamedTextureSafe(image))
}
/**
* Создаёт связку текстуры-атласа + координгат спрайта на ней
*/
fun AtlasConfiguration.Sprite.bind(texture: GLTexture2D): BoundSprite {
return BoundSprite(this, texture)
} }

View File

@ -1,13 +1,20 @@
package ru.dbotthepony.kstarbound.client.render package ru.dbotthepony.kstarbound.client.render
import org.lwjgl.glfw.GLFW.glfwGetTime import org.lwjgl.glfw.GLFW.glfwGetTime
import ru.dbotthepony.kstarbound.defs.FrameSet
/** /**
* Анимирует заданный FrameSet * Таймер для анимирования набора спрайтов
*/ */
class FrameSetAnimator( open class FrameAnimator(
val set: FrameSet, /**
* Первый кадр в анимации
*/
var firstFrame: Int = 0,
/**
* Последний кадр в анимации
*/
var lastFrame: Int,
/** /**
* Сколько времени занимает один кадр * Сколько времени занимает один кадр
@ -17,23 +24,11 @@ class FrameSetAnimator(
/** /**
* Зациклить ли анимацию * Зациклить ли анимацию
*/ */
var animationLoops: Boolean, var animationLoops: Boolean = true,
) { ) {
/**
* Последний кадр анимации
*/
var lastFrame = set.frameCount - 1
/**
* Первый кадр анимации
*/
var firstFrame = 0
var frame = 0 var frame = 0
private set private set
val frameObj get() = set.frames[frame + firstFrame]
/** /**
* Возвращает разницу между последним и первым кадром анимации * Возвращает разницу между последним и первым кадром анимации
*/ */

View File

@ -0,0 +1,56 @@
package ru.dbotthepony.kstarbound.client.render
import com.google.common.collect.ImmutableList
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.client.gl.GLTexture2D
import ru.dbotthepony.kstarbound.defs.animation.AtlasConfiguration
import ru.dbotthepony.kstarbound.defs.animation.ImageReference
class SpriteAnimator(
val sprites: List<BoundSprite>,
animationCycle: Double,
animationLoops: Boolean = true,
firstFrame: Int = 0,
lastFrame: Int = sprites.size - 1
) : FrameAnimator(
firstFrame = firstFrame,
lastFrame = lastFrame,
animationCycle = animationCycle,
animationLoops = animationLoops,
) {
constructor(
image: GLTexture2D,
atlas: AtlasConfiguration,
animationCycle: Double,
animationLoops: Boolean = true,
firstFrame: Int = 0,
lastFrame: Int = atlas.spriteList.size - 1
) : this(
atlas.spriteList.stream().map { it.bind(image) }.collect(ImmutableList.toImmutableList()),
animationCycle = animationCycle,
animationLoops = animationLoops,
firstFrame = firstFrame,
lastFrame = lastFrame,
)
constructor(
image: ImageReference,
state: GLStateTracker,
animationCycle: Double,
animationLoops: Boolean = true,
firstFrame: Int = 0,
lastFrame: Int = image.config.spriteList.size - 1
) : this(state.loadNamedTextureSafe(image.image), image.config, animationCycle, animationLoops, firstFrame, lastFrame)
val sprite get() = sprites[frame]
}
fun ImageReference.makeSpriteAnimator(
state: GLStateTracker,
animationCycle: Double,
animationLoops: Boolean = true,
firstFrame: Int = 0,
lastFrame: Int = config.spriteList.size - 1
): SpriteAnimator {
return SpriteAnimator(this, state, animationCycle, animationLoops, firstFrame, lastFrame)
}

View File

@ -5,39 +5,36 @@ import ru.dbotthepony.kstarbound.client.ClientChunk
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers import ru.dbotthepony.kstarbound.client.gl.vertex.QuadTransformers
import ru.dbotthepony.kstarbound.client.gl.vertex.quadRotatedZ import ru.dbotthepony.kstarbound.client.gl.vertex.quadRotatedZ
import ru.dbotthepony.kstarbound.client.render.FrameSetAnimator import ru.dbotthepony.kstarbound.client.render.FrameAnimator
import ru.dbotthepony.kstarbound.client.render.SpriteAnimator
import ru.dbotthepony.kstarbound.client.render.makeSpriteAnimator
import ru.dbotthepony.kstarbound.world.entities.projectile.Projectile import ru.dbotthepony.kstarbound.world.entities.projectile.Projectile
import ru.dbotthepony.kvector.matrix.Matrix4fStack import ru.dbotthepony.kvector.matrix.Matrix4fStack
open class ProjectileRenderer(state: GLStateTracker, entity: Projectile, chunk: ClientChunk?) : EntityRenderer(state, entity, chunk) { open class ProjectileRenderer(state: GLStateTracker, entity: Projectile, chunk: ClientChunk?) : EntityRenderer(state, entity, chunk) {
private val def = entity.def private val def = entity.def
private val texture = state.loadNamedTextureSafe(def.image.texture) private val animator = def.image.makeSpriteAnimator(state, def.animationCycle, def.animationLoops)
private val animator = FrameSetAnimator(def.image, def.animationCycle, entity.def.animationLoops)
override fun render(stack: Matrix4fStack) { override fun render(stack: Matrix4fStack) {
state.shaderVertexTexture.use() state.shaderVertexTexture.use()
state.shaderVertexTexture.transform.set(stack.last) state.shaderVertexTexture.transform.set(stack.last)
state.activeTexture = 0 state.activeTexture = 0
state.shaderVertexTexture["_texture"] = 0 state.shaderVertexTexture["_texture"] = 0
texture.bind()
animator.advance() animator.advance()
val sprite = animator.sprite
sprite.texture.bind()
val builder = state.flat2DTexturedQuads.small val builder = state.flat2DTexturedQuads.small
builder.begin() builder.begin()
val (u0, v0) = texture.pixelToUV(animator.frameObj.texturePosition) val width = (sprite.width / PIXELS_IN_STARBOUND_UNITf) / 2f
val (u1, v1) = texture.pixelToUV(animator.frameObj.textureEndPosition) val height = (sprite.height / PIXELS_IN_STARBOUND_UNITf) / 2f
val width = (animator.frameObj.width / PIXELS_IN_STARBOUND_UNITf) / 2f builder.quadRotatedZ(-width, -height, width, height, 5f, 0f, 0f, entity.movement.angle, sprite.transformer)
val height = (animator.frameObj.height / PIXELS_IN_STARBOUND_UNITf) / 2f
builder.quadRotatedZ(-width, -height, width, height, 5f, 0f, 0f, entity.movement.angle,
QuadTransformers.uv(u0, v0, u1, v1)
)
builder.upload() builder.upload()
builder.draw() builder.draw()
} }
} }

View File

@ -2,15 +2,19 @@ package ru.dbotthepony.kstarbound.defs.animation
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableMap
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonNull import com.google.gson.JsonNull
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.internal.bind.TypeAdapters import com.google.gson.internal.bind.TypeAdapters
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.gl.GLTexture2D import ru.dbotthepony.kstarbound.client.gl.GLTexture2D
import ru.dbotthepony.kstarbound.io.json.stream import ru.dbotthepony.kstarbound.io.json.stream
import ru.dbotthepony.kstarbound.io.json.transform
import ru.dbotthepony.kstarbound.registerTypeAdapter
import ru.dbotthepony.kvector.vector.nint.Vector2i import ru.dbotthepony.kvector.vector.nint.Vector2i
import ru.dbotthepony.kvector.vector.nint.Vector4i import ru.dbotthepony.kvector.vector.nint.Vector4i
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@ -22,7 +26,7 @@ import java.util.concurrent.ConcurrentHashMap
* *
* Несмотря на название, НЕ ЯВЛЯЕТСЯ изображением, а лишь метаданными для *множества* возможных изображений * Несмотря на название, НЕ ЯВЛЯЕТСЯ изображением, а лишь метаданными для *множества* возможных изображений
*/ */
class AtlasDefinition private constructor( class AtlasConfiguration private constructor(
/** /**
* Имя данного атласа (путь к файлу) * Имя данного атласа (путь к файлу)
*/ */
@ -149,14 +153,14 @@ class AtlasDefinition private constructor(
} }
companion object { companion object {
val EMPTY: AtlasDefinition val EMPTY: AtlasConfiguration
init { init {
val sprite = Sprite("root", Vector4i(0, 0, 0, 0)) val sprite = Sprite("root", Vector4i(0, 0, 0, 0))
EMPTY = AtlasDefinition("null", ImmutableMap.of("root", sprite, "default", sprite, "0", sprite), ImmutableList.of(sprite)) EMPTY = AtlasConfiguration("null", ImmutableMap.of("root", sprite, "default", sprite, "0", sprite), ImmutableList.of(sprite))
} }
private val cache = ConcurrentHashMap<String, AtlasDefinition>() private val cache = ConcurrentHashMap<String, AtlasConfiguration>()
private fun generateFakeNames(dimensions: Vector2i): JsonArray { private fun generateFakeNames(dimensions: Vector2i): JsonArray {
return JsonArray(dimensions.y).also { return JsonArray(dimensions.y).also {
@ -175,7 +179,7 @@ class AtlasDefinition private constructor(
} }
} }
private fun parseFrames(input: JsonReader, name: String): AtlasDefinition { private fun parseFrames(input: JsonReader, name: String): AtlasConfiguration {
val read = TypeAdapters.JSON_ELEMENT.read(input) val read = TypeAdapters.JSON_ELEMENT.read(input)
if (read !is JsonObject) { if (read !is JsonObject) {
@ -241,10 +245,10 @@ class AtlasDefinition private constructor(
sprites[k] = sprites[v.asString] ?: throw JsonSyntaxException("$k want to refer to sprite $v, but it does not exist") sprites[k] = sprites[v.asString] ?: throw JsonSyntaxException("$k want to refer to sprite $v, but it does not exist")
} }
return AtlasDefinition(name, ImmutableMap.copyOf(sprites), spriteList) return AtlasConfiguration(name, ImmutableMap.copyOf(sprites), spriteList)
} }
private fun recursiveGet(name: String, folder: String): AtlasDefinition? { private fun recursiveGet(name: String, folder: String): AtlasConfiguration? {
var current = folder var current = folder
while (current != "/" && current != "") { while (current != "/" && current != "") {
@ -272,10 +276,24 @@ class AtlasDefinition private constructor(
return null return null
} }
fun get(path: String): AtlasDefinition { /**
* Пытается найти конфигурацию (файл "frames") атласа для заданного пути (заданного изображения/атласа)
*
* Алгоритм поиска конфигурации таков:
* * Проверяется папка с целевым файлом на наличие файла с таким же именем, но с расширением frames;
* * Если файл не был найден, процесс повторяется переходом в папку выше, пока не будет достигнут корень файловой системы;
* * Если файл не был найден, процесс начинается заново, внутри целевой папки, но уже происходит поиск файла с именем default.frames;
* * Процесс повторяется как в пункте 2 пока не будет найден файл в одной из вышестоящих папок.
*
* Если ни один файл не был найден, то возвращается [EMPTY]
*
* Данная функция кеширует результаты поиска
*
*/
fun get(path: String): AtlasConfiguration {
require(path[0] == '/') { "$path is not an absolute path" } require(path[0] == '/') { "$path is not an absolute path" }
val folder = path.substringBeforeLast('/').lowercase() val folder = path.substringBeforeLast('/').lowercase()
val filename = path.substringAfterLast('/').substringBefore('.').lowercase() val filename = path.substringAfterLast('/').substringBefore(':').substringBefore('.').lowercase()
val direct = recursiveGet(filename, folder) val direct = recursiveGet(filename, folder)
if (direct != null) return direct if (direct != null) return direct
@ -285,5 +303,11 @@ class AtlasDefinition private constructor(
return EMPTY return EMPTY
} }
val ADAPTER: TypeAdapter<AtlasConfiguration?> = Starbound.stringTypeAdapter.transform(read = read@{ get(it ?: return@read it as AtlasConfiguration?) }, write = write@{ it?.name })
fun registerGson(gsonBuilder: GsonBuilder) {
gsonBuilder.registerTypeAdapter(ADAPTER)
}
} }
} }

View File

@ -0,0 +1,45 @@
package ru.dbotthepony.kstarbound.defs.animation
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kstarbound.Starbound
/**
* Хранит данные (пару) вида "/example/animated.png" у которого, вероятнее всего, есть "/example/animated.frames"
*
* @see [AtlasConfiguration.Companion.get]
*/
data class ImageReference(
val image: String,
val config: AtlasConfiguration,
) {
/**
* Вызывает [AtlasConfiguration.Companion.get] автоматически
*
* @see ImageReference
*/
constructor(image: String) : this(image, AtlasConfiguration.get(image))
companion object : TypeAdapter<ImageReference>() {
override fun write(out: JsonWriter, value: ImageReference) {
out.value(value.image)
}
override fun read(`in`: JsonReader): ImageReference {
if (`in`.peek() == JsonToken.STRING) {
val image = Starbound.readingFolderTransformer(`in`.nextString())
if (image.contains(':')) {
throw JsonSyntaxException("Expected atlas/image reference, but got sprite reference: $image")
}
return ImageReference(image, AtlasConfiguration.get(image))
}
throw JsonSyntaxException("Expected atlas/image reference, but got: ${`in`.peek()}")
}
}
}

View File

@ -7,13 +7,16 @@ import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.registerTypeAdapter import ru.dbotthepony.kstarbound.registerTypeAdapter
/**
* Хранит данные (пару) вида "/example/image.png:sprite.name"
*/
data class SpriteReference( data class SpriteReference(
val path: String, val image: String,
val sprite: AtlasDefinition.Sprite val sprite: AtlasConfiguration.Sprite
) { ) {
companion object : TypeAdapter<SpriteReference>() { companion object : TypeAdapter<SpriteReference>() {
fun parse(input: String): SpriteReference { fun parse(input: String): SpriteReference {
val grid = AtlasDefinition.get(input.substringBefore(':')) val grid = AtlasConfiguration.get(input.substringBefore(':'))
return when (input.count { it == ':' }) { return when (input.count { it == ':' }) {
0 -> SpriteReference(input, grid.any()) 0 -> SpriteReference(input, grid.any())
@ -23,7 +26,7 @@ data class SpriteReference(
} }
override fun write(out: JsonWriter, value: SpriteReference) { override fun write(out: JsonWriter, value: SpriteReference) {
out.value(value.path + ":" + value.sprite.name) out.value(value.image + ":" + value.sprite.name)
} }
override fun read(`in`: JsonReader): SpriteReference { override fun read(`in`: JsonReader): SpriteReference {

View File

@ -7,6 +7,7 @@ import it.unimi.dsi.fastutil.objects.ObjectArraySet
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.* import ru.dbotthepony.kstarbound.defs.*
import ru.dbotthepony.kstarbound.defs.animation.ImageReference
import ru.dbotthepony.kstarbound.io.json.BuilderAdapter import ru.dbotthepony.kstarbound.io.json.BuilderAdapter
import ru.dbotthepony.kstarbound.io.json.CustomEnumTypeAdapter import ru.dbotthepony.kstarbound.io.json.CustomEnumTypeAdapter
import ru.dbotthepony.kstarbound.registerTypeAdapter import ru.dbotthepony.kstarbound.registerTypeAdapter
@ -79,7 +80,7 @@ class ConfigurableProjectile : RawPrototype<ConfigurableProjectile, ConfiguredPr
lightColor = lightColor, lightColor = lightColor,
onlyHitTerrain = onlyHitTerrain, onlyHitTerrain = onlyHitTerrain,
orientationLocked = orientationLocked, orientationLocked = orientationLocked,
image = IFrameGrid.loadFrameStrip(ensureAbsolutePath(requireNotNull(image) { "image is null" }, directory), weak = true), image = ImageReference(Starbound.readingFolderTransformer(requireNotNull(image) { "image is null" })),
timeToLive = timeToLive, timeToLive = timeToLive,
animationCycle = animationCycle, animationCycle = animationCycle,
bounces = bounces, bounces = bounces,

View File

@ -6,6 +6,7 @@ import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.AssembledPrototype import ru.dbotthepony.kstarbound.defs.AssembledPrototype
import ru.dbotthepony.kstarbound.defs.DamageType import ru.dbotthepony.kstarbound.defs.DamageType
import ru.dbotthepony.kstarbound.defs.FrameSet import ru.dbotthepony.kstarbound.defs.FrameSet
import ru.dbotthepony.kstarbound.defs.animation.ImageReference
import ru.dbotthepony.kstarbound.world.entities.projectile.AbstractProjectileMovementController import ru.dbotthepony.kstarbound.world.entities.projectile.AbstractProjectileMovementController
import ru.dbotthepony.kstarbound.world.entities.projectile.Projectile import ru.dbotthepony.kstarbound.world.entities.projectile.Projectile
import ru.dbotthepony.kvector.vector.Color import ru.dbotthepony.kvector.vector.Color
@ -21,7 +22,7 @@ class ConfiguredProjectile(
val lightColor: Color?, val lightColor: Color?,
val onlyHitTerrain: Boolean, val onlyHitTerrain: Boolean,
val orientationLocked: Boolean, val orientationLocked: Boolean,
val image: FrameSet, val image: ImageReference,
val timeToLive: Double, val timeToLive: Double,
val animationCycle: Double, val animationCycle: Double,
val bounces: Int, val bounces: Int,

View File

@ -29,14 +29,14 @@ fun <T> TypeAdapter<T>.transformWrite(transformer: (T) -> T): TypeAdapter<T> {
} }
} }
fun <T> TypeAdapter<T>.transform(transformRead: (T) -> T, transformWrite: (T) -> T): TypeAdapter<T> { fun <In, Out> TypeAdapter<In>.transform(read: (In) -> Out, write: (Out) -> In): TypeAdapter<Out> {
return object : TypeAdapter<T>() { return object : TypeAdapter<Out>() {
override fun write(out: JsonWriter, value: T) { override fun write(out: JsonWriter, value: Out) {
return this@transform.write(out, transformWrite(value)) return this@transform.write(out, write(value))
} }
override fun read(`in`: JsonReader): T { override fun read(`in`: JsonReader): Out {
return transformRead(this@transform.read(`in`)) return read(this@transform.read(`in`))
} }
} }
} }