rework skin loading, support for new skin models

This fixes one of two crashes with 1.19.3
This commit is contained in:
Bixilon 2022-12-08 16:26:52 +01:00
parent 73f284650a
commit 29d1074c45
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
17 changed files with 335 additions and 92 deletions

View File

@ -21,7 +21,7 @@ import de.bixilon.minosoft.test.ITUtil
import org.testng.Assert import org.testng.Assert
import org.testng.annotations.Test import org.testng.annotations.Test
@Test(groups = ["pixlyzer"], dependsOnGroups = ["version"]) @Test(groups = ["pixlyzer"], dependsOnGroups = ["version"], singleThreaded = false, threadPoolSize = 8)
class PixLyzerLoadingTest { class PixLyzerLoadingTest {
private fun Version.test() { private fun Version.test() {

View File

@ -27,7 +27,7 @@ import org.testng.Assert.assertEquals
import org.testng.annotations.Test import org.testng.annotations.Test
@Test(groups = ["light"], dependsOnGroups = ["block"]) @Test(groups = ["light"], dependsOnGroups = ["block"], threadPoolSize = 8)
class BlockLightBreakIT { class BlockLightBreakIT {
fun inBlock() { fun inBlock() {

View File

@ -27,7 +27,7 @@ import org.testng.Assert.assertEquals
import org.testng.annotations.Test import org.testng.annotations.Test
@Test(groups = ["light"], dependsOnGroups = ["block"]) @Test(groups = ["light"], dependsOnGroups = ["block"], threadPoolSize = 8)
class BlockLightPlaceIT { class BlockLightPlaceIT {
fun inBlock() { fun inBlock() {

View File

@ -23,7 +23,6 @@ import de.bixilon.minosoft.util.logging.LogLevels
import de.bixilon.minosoft.util.logging.LogMessageType import de.bixilon.minosoft.util.logging.LogMessageType
import java.io.File import java.io.File
import java.net.URL import java.net.URL
import java.util.*
open class PlayerTexture( open class PlayerTexture(
val url: URL, val url: URL,
@ -85,9 +84,5 @@ open class PlayerTexture(
} }
return false return false
} }
fun UUID.isSteve(): Boolean {
return hashCode() % 2 == 0
}
} }
} }

View File

@ -17,5 +17,5 @@ import com.fasterxml.jackson.annotation.JsonInclude
data class SkinMetadata( data class SkinMetadata(
@JsonInclude(JsonInclude.Include.NON_DEFAULT) @JsonInclude(JsonInclude.Include.NON_DEFAULT)
val model: SkinModel = SkinModel.NORMAL, val model: SkinModel = SkinModel.WIDE,
) )

View File

@ -13,8 +13,11 @@
package de.bixilon.minosoft.data.entities.entities.player.properties.textures.metadata package de.bixilon.minosoft.data.entities.entities.player.properties.textures.metadata
import com.fasterxml.jackson.annotation.JsonAlias
enum class SkinModel { enum class SkinModel {
SLIM, SLIM,
NORMAL, @JsonAlias("normal")
WIDE,
; ;
} }

View File

@ -159,7 +159,7 @@ class RenderWindow(
val initLatch = CountUpAndDownLatch(1, latch) val initLatch = CountUpAndDownLatch(1, latch)
Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Generating font and gathering textures (after ${stopwatch.labTime()})..." } Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Generating font and gathering textures (after ${stopwatch.labTime()})..." }
textureManager.dynamicTextures.load(initLatch) textureManager.dynamicTextures.load(initLatch)
textureManager.loadDefaultSkins(connection) textureManager.initializeSkins(connection)
textureManager.loadDefaultTextures() textureManager.loadDefaultTextures()
font = FontLoader.load(this, initLatch) font = FontLoader.load(this, initLatch)

View File

@ -19,23 +19,23 @@ import de.bixilon.minosoft.gui.rendering.skeletal.baked.SkeletalModelStates
import de.bixilon.minosoft.gui.rendering.skeletal.instance.SkeletalInstance import de.bixilon.minosoft.gui.rendering.skeletal.instance.SkeletalInstance
abstract class SkeletalEntityModel<E : Entity>(renderer: EntityRenderer, entity: E) : EntityModel<E>(renderer, entity) { abstract class SkeletalEntityModel<E : Entity>(renderer: EntityRenderer, entity: E) : EntityModel<E>(renderer, entity) {
abstract val instance: SkeletalInstance abstract val instance: SkeletalInstance?
open val hideSkeletalModel: Boolean get() = false open val hideSkeletalModel: Boolean get() = false
override fun prepare() { override fun prepare() {
super.prepare() super.prepare()
if (instance.model.state != SkeletalModelStates.LOADED) { if (instance?.model?.state != SkeletalModelStates.LOADED) {
instance.model.preload(renderWindow) // ToDo: load async instance?.model?.preload(renderWindow) // ToDo: load async
instance.model.load() instance?.model?.load()
} }
} }
override fun draw() { override fun draw() {
super.draw() super.draw()
if (!hideSkeletalModel) { if (!hideSkeletalModel) {
instance.updatePosition(entity.cameraPosition, entity.rotation) instance?.updatePosition(entity.cameraPosition, entity.rotation)
instance.draw() instance?.draw()
} }
} }
} }

View File

@ -19,7 +19,6 @@ import de.bixilon.minosoft.data.entities.entities.player.Arms
import de.bixilon.minosoft.data.entities.entities.player.PlayerEntity import de.bixilon.minosoft.data.entities.entities.player.PlayerEntity
import de.bixilon.minosoft.data.entities.entities.player.SkinParts import de.bixilon.minosoft.data.entities.entities.player.SkinParts
import de.bixilon.minosoft.data.entities.entities.player.properties.PlayerProperties import de.bixilon.minosoft.data.entities.entities.player.properties.PlayerProperties
import de.bixilon.minosoft.data.entities.entities.player.properties.textures.PlayerTexture.Companion.isSteve
import de.bixilon.minosoft.data.entities.entities.player.properties.textures.metadata.SkinModel import de.bixilon.minosoft.data.entities.entities.player.properties.textures.metadata.SkinModel
import de.bixilon.minosoft.gui.rendering.entity.EntityRenderer import de.bixilon.minosoft.gui.rendering.entity.EntityRenderer
import de.bixilon.minosoft.gui.rendering.entity.models.SkeletalEntityModel import de.bixilon.minosoft.gui.rendering.entity.models.SkeletalEntityModel
@ -30,11 +29,12 @@ import de.bixilon.minosoft.gui.rendering.skeletal.model.elements.SkeletalElement
import de.bixilon.minosoft.gui.rendering.system.base.texture.dynamic.DynamicStateChangeCallback import de.bixilon.minosoft.gui.rendering.system.base.texture.dynamic.DynamicStateChangeCallback
import de.bixilon.minosoft.gui.rendering.system.base.texture.dynamic.DynamicTexture import de.bixilon.minosoft.gui.rendering.system.base.texture.dynamic.DynamicTexture
import de.bixilon.minosoft.gui.rendering.system.base.texture.dynamic.DynamicTextureState import de.bixilon.minosoft.gui.rendering.system.base.texture.dynamic.DynamicTextureState
import de.bixilon.minosoft.gui.rendering.system.base.texture.skin.PlayerSkin
import de.bixilon.minosoft.util.KUtil.toResourceLocation import de.bixilon.minosoft.util.KUtil.toResourceLocation
open class PlayerModel(renderer: EntityRenderer, player: PlayerEntity) : SkeletalEntityModel<PlayerEntity>(renderer, player), DynamicStateChangeCallback { open class PlayerModel(renderer: EntityRenderer, player: PlayerEntity) : SkeletalEntityModel<PlayerEntity>(renderer, player), DynamicStateChangeCallback {
private var properties = player.additional.properties private var properties = player.additional.properties
private var skin: DynamicTexture? = null private var skin: PlayerSkin? = null
protected var refreshModel = false protected var refreshModel = false
private val legAnimator = LegAnimator(this) private val legAnimator = LegAnimator(this)
@ -52,8 +52,9 @@ open class PlayerModel(renderer: EntityRenderer, player: PlayerEntity) : Skeleta
} }
private fun createModel(properties: PlayerProperties?): SkeletalInstance { private fun createModel(properties: PlayerProperties?): SkeletalInstance? {
val skinModel = properties?.textures?.skin?.metadata?.model ?: if (entity.uuid?.isSteve() == true) SkinModel.NORMAL else SkinModel.SLIM val skin = renderWindow.textureManager.skins.getSkin(entity, properties) ?: return null
val skinModel = skin.model
val unbaked = renderWindow.modelLoader.entities.loadUnbakedModel(if (skinModel == SkinModel.SLIM) SLIM_MODEL else NORMAL_MODEL) val unbaked = renderWindow.modelLoader.entities.loadUnbakedModel(if (skinModel == SkinModel.SLIM) SLIM_MODEL else NORMAL_MODEL)
val elements: MutableList<SkeletalElement> = mutableListOf() val elements: MutableList<SkeletalElement> = mutableListOf()
@ -66,15 +67,14 @@ open class PlayerModel(renderer: EntityRenderer, player: PlayerEntity) : Skeleta
} }
elements += element elements += element
} }
val skin = renderWindow.textureManager.getSkin(entity, properties) skin.texture.usages.incrementAndGet()
skin.usages.incrementAndGet() this.skin?.texture?.usages?.decrementAndGet()
this.skin?.usages?.decrementAndGet()
this.skin = skin this.skin = skin
skin += this skin.texture += this
val skinTexture = if (skin.state != DynamicTextureState.LOADED) renderWindow.textureManager.getFallbackTexture(entity.uuid) else skin val skinTexture = if (skin.texture.state != DynamicTextureState.LOADED) renderWindow.textureManager.skins.default[entity.uuid] ?: return null else skin
val model = unbaked.copy(elements = elements, animations = animations).bake(renderWindow, mapOf(0 to skinTexture)) val model = unbaked.copy(elements = elements, animations = animations).bake(renderWindow, mapOf(0 to skinTexture.texture))
val instance = SkeletalInstance(renderWindow, model) val instance = SkeletalInstance(renderWindow, model)
@ -110,13 +110,13 @@ open class PlayerModel(renderer: EntityRenderer, player: PlayerEntity) : Skeleta
} }
override fun onStateChange(texture: DynamicTexture, state: DynamicTextureState) { override fun onStateChange(texture: DynamicTexture, state: DynamicTextureState) {
if (skin === texture) { if (skin?.texture === texture) {
refreshModel = true refreshModel = true
} }
} }
override fun unload() { override fun unload() {
skin?.usages?.decrementAndGet() skin?.texture?.usages?.decrementAndGet()
} }
fun swingArm(arm: Arms) { fun swingArm(arm: Arms) {

View File

@ -81,7 +81,7 @@ class TabListEntryElement(
init { init {
background = ColorElement(guiRenderer, size, RGBColor(120, 120, 120, 130)) background = ColorElement(guiRenderer, size, RGBColor(120, 120, 120, 130))
DefaultThreadPool += { skinElement.texture = renderWindow.textureManager.getSkin(guiRenderer.connection.network.encrypted, uuid, item.properties) } DefaultThreadPool += { skinElement.texture = renderWindow.textureManager.skins.getSkin(uuid, item.properties, fetch = guiRenderer.connection.network.encrypted)?.texture }
forceSilentApply() forceSilentApply()
} }

View File

@ -15,27 +15,16 @@ package de.bixilon.minosoft.gui.rendering.system.base.texture
import de.bixilon.kotlinglm.vec2.Vec2 import de.bixilon.kotlinglm.vec2.Vec2
import de.bixilon.kotlinglm.vec2.Vec2i import de.bixilon.kotlinglm.vec2.Vec2i
import de.bixilon.minosoft.config.profile.profiles.account.AccountProfileManager
import de.bixilon.minosoft.data.entities.entities.player.PlayerEntity
import de.bixilon.minosoft.data.entities.entities.player.local.LocalPlayerEntity
import de.bixilon.minosoft.data.entities.entities.player.properties.PlayerProperties
import de.bixilon.minosoft.data.entities.entities.player.properties.textures.PlayerTexture.Companion.isSteve
import de.bixilon.minosoft.data.registries.ResourceLocation
import de.bixilon.minosoft.gui.rendering.RenderConstants import de.bixilon.minosoft.gui.rendering.RenderConstants
import de.bixilon.minosoft.gui.rendering.gui.atlas.TextureLikeTexture import de.bixilon.minosoft.gui.rendering.gui.atlas.TextureLikeTexture
import de.bixilon.minosoft.gui.rendering.system.base.shader.NativeShader import de.bixilon.minosoft.gui.rendering.system.base.shader.NativeShader
import de.bixilon.minosoft.gui.rendering.system.base.shader.ShaderUniforms import de.bixilon.minosoft.gui.rendering.system.base.shader.ShaderUniforms
import de.bixilon.minosoft.gui.rendering.system.base.texture.dynamic.DynamicTexture
import de.bixilon.minosoft.gui.rendering.system.base.texture.dynamic.DynamicTextureArray import de.bixilon.minosoft.gui.rendering.system.base.texture.dynamic.DynamicTextureArray
import de.bixilon.minosoft.gui.rendering.system.base.texture.skin.SkinManager
import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.AbstractTexture import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.AbstractTexture
import de.bixilon.minosoft.gui.rendering.textures.TextureUtil.readTexture
import de.bixilon.minosoft.gui.rendering.textures.TextureUtil.texture import de.bixilon.minosoft.gui.rendering.textures.TextureUtil.texture
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.util.KUtil.toResourceLocation import de.bixilon.minosoft.util.KUtil.minosoft
import de.bixilon.minosoft.util.logging.Log
import de.bixilon.minosoft.util.logging.LogLevels
import de.bixilon.minosoft.util.logging.LogMessageType
import java.util.*
abstract class TextureManager { abstract class TextureManager {
abstract val staticTextures: StaticTextureArray abstract val staticTextures: StaticTextureArray
@ -45,11 +34,7 @@ abstract class TextureManager {
private set private set
lateinit var whiteTexture: TextureLikeTexture lateinit var whiteTexture: TextureLikeTexture
private set private set
lateinit var steveTexture: DynamicTexture lateinit var skins: SkinManager
private set
lateinit var alexTexture: DynamicTexture
private set
lateinit var skin: DynamicTexture
private set private set
fun loadDefaultTextures() { fun loadDefaultTextures() {
@ -57,52 +42,12 @@ abstract class TextureManager {
throw IllegalStateException("Already initialized!") throw IllegalStateException("Already initialized!")
} }
debugTexture = staticTextures.createTexture(RenderConstants.DEBUG_TEXTURE_RESOURCE_LOCATION) debugTexture = staticTextures.createTexture(RenderConstants.DEBUG_TEXTURE_RESOURCE_LOCATION)
whiteTexture = TextureLikeTexture(texture = staticTextures.createTexture(ResourceLocation("minosoft:textures/white.png")), uvStart = Vec2(0.0f, 0.0f), uvEnd = Vec2(0.001f, 0.001f), size = Vec2i(16, 16)) whiteTexture = TextureLikeTexture(texture = staticTextures.createTexture(minosoft("white").texture()), uvStart = Vec2(0.0f, 0.0f), uvEnd = Vec2(0.001f, 0.001f), size = Vec2i(16, 16))
} }
fun loadDefaultSkins(connection: PlayConnection) { fun initializeSkins(connection: PlayConnection) {
steveTexture = dynamicTextures.pushBuffer(UUID(0L, 0L), true) { connection.assetsManager["minecraft:entity/steve".toResourceLocation().texture()].readTexture() }.apply { usages.incrementAndGet() } skins = SkinManager(this)
alexTexture = dynamicTextures.pushBuffer(UUID(1L, 0L), true) { connection.assetsManager["minecraft:entity/alex".toResourceLocation().texture()].readTexture() }.apply { usages.incrementAndGet() } skins.initialize(connection.account, connection.assetsManager)
skin = getSkin(connection.account.supportsSkins, connection.account.uuid, connection.account.properties, force = true).apply { usages.incrementAndGet() }
}
fun getSkin(fetchSkin: Boolean, uuid: UUID, properties: PlayerProperties?, force: Boolean = false): DynamicTexture {
var properties = properties
if (properties?.textures == null) {
for (account in AccountProfileManager.selected.entries.values) {
if (account.uuid == uuid) {
properties = account.properties
}
}
if (properties?.textures == null && fetchSkin) {
try {
properties = PlayerProperties.fetch(uuid)
} catch (ignored: Throwable) {
}
}
}
properties?.textures?.skin?.let { return dynamicTextures.pushRawArray(uuid, force) { it.read() } }
return getFallbackTexture(uuid)
}
fun getSkin(player: PlayerEntity, properties: PlayerProperties? = player.additional.properties): DynamicTexture {
if (player is LocalPlayerEntity) {
return skin
}
val uuid = player.uuid
if (uuid == null) {
Log.log(LogMessageType.OTHER, LogLevels.VERBOSE) { "Player uuid is null: $player" }
return steveTexture
}
return getSkin(true, uuid, properties)
}
fun getFallbackTexture(uuid: UUID?): DynamicTexture {
if (uuid == null || uuid.isSteve()) {
return steveTexture
}
return alexTexture
} }
fun use(shader: NativeShader, name: String = ShaderUniforms.TEXTURES) { fun use(shader: NativeShader, name: String = ShaderUniforms.TEXTURES) {

View File

@ -0,0 +1,22 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.gui.rendering.system.base.texture.skin
import de.bixilon.minosoft.data.entities.entities.player.properties.textures.metadata.SkinModel
import de.bixilon.minosoft.gui.rendering.system.base.texture.dynamic.DynamicTexture
class PlayerSkin(
val texture: DynamicTexture,
val model: SkinModel,
)

View File

@ -0,0 +1,69 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.gui.rendering.system.base.texture.skin
import de.bixilon.kutil.exception.ExceptionUtil.catchAll
import de.bixilon.minosoft.assets.AssetsManager
import de.bixilon.minosoft.config.profile.profiles.account.AccountProfileManager
import de.bixilon.minosoft.data.accounts.Account
import de.bixilon.minosoft.data.entities.entities.player.PlayerEntity
import de.bixilon.minosoft.data.entities.entities.player.local.LocalPlayerEntity
import de.bixilon.minosoft.data.entities.entities.player.properties.PlayerProperties
import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureManager
import de.bixilon.minosoft.gui.rendering.system.base.texture.skin.vanilla.DefaultSkinProvider
import java.util.*
class SkinManager(private val textureManager: TextureManager) {
lateinit var default: DefaultSkinProvider
private set
private var skin: PlayerSkin? = null
fun initialize(account: Account, assets: AssetsManager) {
default = DefaultSkinProvider(this.textureManager.dynamicTextures, assets)
skin = getSkin(account.uuid, account.properties, fetch = true, force = true)?.apply { texture.usages.incrementAndGet() }
}
private fun getAccountProperties(uuid: UUID): PlayerProperties? {
for (account in AccountProfileManager.selected.entries.values) {
if (account.uuid != uuid) {
continue
}
return account.properties
}
return null
}
private fun getProperties(player: PlayerEntity, uuid: UUID, fetch: Boolean): PlayerProperties? {
return player.additional.properties ?: getAccountProperties(uuid) ?: if (fetch) catchAll { PlayerProperties.fetch(uuid) } else null
}
private fun getSkin(uuid: UUID, properties: PlayerProperties?, force: Boolean = false): PlayerSkin? {
val texture = properties?.textures?.skin ?: return default[uuid]
return PlayerSkin(textureManager.dynamicTextures.pushRawArray(uuid, force) { texture.read() }, texture.metadata.model)
}
fun getSkin(player: PlayerEntity, properties: PlayerProperties? = null, fetch: Boolean = true, force: Boolean = false): PlayerSkin? {
if (player is LocalPlayerEntity) {
return skin
}
val uuid = player.uuid ?: return default[player]
return getSkin(uuid, properties ?: getProperties(player, uuid, fetch), force)
}
fun getSkin(uuid: UUID?, properties: PlayerProperties? = null, fetch: Boolean = true, force: Boolean = false): PlayerSkin? {
if (uuid == null) return default[null]
return getSkin(uuid, properties ?: if (fetch) catchAll { PlayerProperties.fetch(uuid) } else null, force)
}
}

View File

@ -0,0 +1,23 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.gui.rendering.system.base.texture.skin.vanilla
import de.bixilon.minosoft.data.entities.entities.player.properties.textures.metadata.SkinModel
import de.bixilon.minosoft.data.registries.ResourceLocation
open class DefaultLegacySkin(
name: ResourceLocation,
val model: SkinModel,
val fallback: Boolean,
) : DefaultSkin(name)

View File

@ -0,0 +1,20 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.gui.rendering.system.base.texture.skin.vanilla
import de.bixilon.minosoft.data.registries.ResourceLocation
open class DefaultSkin(
val name: ResourceLocation,
)

View File

@ -0,0 +1,124 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.gui.rendering.system.base.texture.skin.vanilla
import de.bixilon.kotlinglm.GLM.abs
import de.bixilon.minosoft.assets.AssetsManager
import de.bixilon.minosoft.data.entities.entities.player.PlayerEntity
import de.bixilon.minosoft.data.entities.entities.player.properties.textures.metadata.SkinModel
import de.bixilon.minosoft.data.registries.ResourceLocation
import de.bixilon.minosoft.gui.rendering.system.base.texture.dynamic.DynamicTexture
import de.bixilon.minosoft.gui.rendering.system.base.texture.dynamic.DynamicTextureArray
import de.bixilon.minosoft.gui.rendering.system.base.texture.skin.PlayerSkin
import de.bixilon.minosoft.gui.rendering.textures.TextureUtil.readTexture
import de.bixilon.minosoft.gui.rendering.textures.TextureUtil.texture
import java.util.*
class DefaultSkinProvider(
private val array: DynamicTextureArray,
private val assets: AssetsManager,
) {
private var defaultId = 0
private val slim: MutableMap<ResourceLocation, DynamicTexture> = mutableMapOf()
private val wide: MutableMap<ResourceLocation, DynamicTexture> = mutableMapOf()
private var fallback: PlayerSkin? = null
fun initialize() {
for (skin in DefaultSkins) {
load(skin)
}
}
private fun load(skin: DefaultSkin) {
var loaded = 0
load(skin.name.skin("slim").texture())?.let { slim[skin.name] = it; loaded++ }
load(skin.name.skin("wide").texture())?.let { wide[skin.name] = it; loaded++ }
if (loaded > 0) {
return
}
if (skin is DefaultLegacySkin) {
loadLegacy(skin)
}
}
private fun loadLegacy(skin: DefaultLegacySkin) {
val path = ResourceLocation(skin.name.namespace, "entity/${skin.name.path}").texture()
val texture = load(path) ?: return
this[skin.model][skin.name] = texture
if (skin.fallback) {
this.fallback = PlayerSkin(texture, skin.model)
}
}
private fun load(path: ResourceLocation): DynamicTexture? {
val data = assets.getOrNull(path)?.readTexture() ?: return null
val texture = array.pushBuffer(UUID(0L, defaultId++.toLong()), true) { data }
texture.usages.incrementAndGet()
return texture
}
private fun ResourceLocation.skin(prefix: String): ResourceLocation {
return ResourceLocation(namespace, "entity/player/$prefix/$path")
}
operator fun get(name: ResourceLocation, slim: Boolean): DynamicTexture? {
val map = if (slim) this.slim else this.wide
return map[name]
}
private operator fun get(model: SkinModel): MutableMap<ResourceLocation, DynamicTexture> {
return when (model) {
SkinModel.SLIM -> this.slim
SkinModel.WIDE -> this.wide
}
}
private fun UUID.isSteve(): Boolean {
return hashCode() % 2 == 0
}
operator fun get(skin: DefaultSkin, model: SkinModel): PlayerSkin? {
return this[model][skin.name]?.let { PlayerSkin(it, model) }
}
operator fun get(uuid: UUID?): PlayerSkin? {
if (uuid == null) {
return fallback
}
if (this.slim.size <= 1) {
return getLegacy(uuid) ?: fallback
}
// TODO: verify with vanilla
val count = DefaultSkins.SKINS.size
val hash = abs(uuid.hashCode()) % count
val model = if (hash > count / 2) SkinModel.WIDE else SkinModel.SLIM
return this[DefaultSkins.SKINS[hash / 2], model] ?: getLegacy(uuid)
}
fun getLegacy(uuid: UUID): PlayerSkin? {
val skin = if (uuid.isSteve()) DefaultSkins.STEVE else DefaultSkins.ALEX
if (skin.fallback) {
return this.fallback
}
return this[skin, skin.model]
}
operator fun get(player: PlayerEntity): PlayerSkin? {
return this[player.uuid] ?: return fallback
}
}

View File

@ -0,0 +1,42 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.gui.rendering.system.base.texture.skin.vanilla
import de.bixilon.minosoft.data.entities.entities.player.properties.textures.metadata.SkinModel
import de.bixilon.minosoft.util.KUtil.minecraft
object DefaultSkins : Iterable<DefaultSkin> {
val SKINS: MutableList<DefaultSkin> = mutableListOf()
val ALEX = DefaultLegacySkin(minecraft("alex"), SkinModel.SLIM, fallback = false).register()
val ARI = DefaultSkin(minecraft("ari")).register()
val EFE = DefaultSkin(minecraft("efe")).register()
val KAI = DefaultSkin(minecraft("kai")).register()
val MAKENA = DefaultSkin(minecraft("makena")).register()
val NOOR = DefaultSkin(minecraft("noor")).register()
val STEVE = DefaultLegacySkin(minecraft("steve"), SkinModel.WIDE, fallback = true).register()
val SUNNY = DefaultSkin(minecraft("sunny")).register()
val ZURI = DefaultSkin(minecraft("zuri")).register()
private fun <T : DefaultSkin> T.register(): T {
SKINS += this
return this
}
override fun iterator(): Iterator<DefaultSkin> {
return SKINS.iterator()
}
}