diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/registries/versions/PixLyzerLoadingTest.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/registries/versions/PixLyzerLoadingTest.kt index 368fee15c..bd4a64698 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/data/registries/versions/PixLyzerLoadingTest.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/registries/versions/PixLyzerLoadingTest.kt @@ -21,7 +21,7 @@ import de.bixilon.minosoft.test.ITUtil import org.testng.Assert import org.testng.annotations.Test -@Test(groups = ["pixlyzer"], dependsOnGroups = ["version"]) +@Test(groups = ["pixlyzer"], dependsOnGroups = ["version"], singleThreaded = false, threadPoolSize = 8) class PixLyzerLoadingTest { private fun Version.test() { diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/breaking/block/BlockLightBreakIT.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/breaking/block/BlockLightBreakIT.kt index dde156663..f9e35f253 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/breaking/block/BlockLightBreakIT.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/breaking/block/BlockLightBreakIT.kt @@ -27,7 +27,7 @@ import org.testng.Assert.assertEquals import org.testng.annotations.Test -@Test(groups = ["light"], dependsOnGroups = ["block"]) +@Test(groups = ["light"], dependsOnGroups = ["block"], threadPoolSize = 8) class BlockLightBreakIT { fun inBlock() { diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/block/BlockLightPlaceIT.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/block/BlockLightPlaceIT.kt index fa82d1f81..72ec166d2 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/block/BlockLightPlaceIT.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/block/BlockLightPlaceIT.kt @@ -27,7 +27,7 @@ import org.testng.Assert.assertEquals import org.testng.annotations.Test -@Test(groups = ["light"], dependsOnGroups = ["block"]) +@Test(groups = ["light"], dependsOnGroups = ["block"], threadPoolSize = 8) class BlockLightPlaceIT { fun inBlock() { diff --git a/src/main/java/de/bixilon/minosoft/data/entities/entities/player/properties/textures/PlayerTexture.kt b/src/main/java/de/bixilon/minosoft/data/entities/entities/player/properties/textures/PlayerTexture.kt index 4cff8e1b9..5516a416a 100644 --- a/src/main/java/de/bixilon/minosoft/data/entities/entities/player/properties/textures/PlayerTexture.kt +++ b/src/main/java/de/bixilon/minosoft/data/entities/entities/player/properties/textures/PlayerTexture.kt @@ -23,7 +23,6 @@ import de.bixilon.minosoft.util.logging.LogLevels import de.bixilon.minosoft.util.logging.LogMessageType import java.io.File import java.net.URL -import java.util.* open class PlayerTexture( val url: URL, @@ -85,9 +84,5 @@ open class PlayerTexture( } return false } - - fun UUID.isSteve(): Boolean { - return hashCode() % 2 == 0 - } } } diff --git a/src/main/java/de/bixilon/minosoft/data/entities/entities/player/properties/textures/metadata/SkinMetadata.kt b/src/main/java/de/bixilon/minosoft/data/entities/entities/player/properties/textures/metadata/SkinMetadata.kt index b0bdab93c..1f7ee43a8 100644 --- a/src/main/java/de/bixilon/minosoft/data/entities/entities/player/properties/textures/metadata/SkinMetadata.kt +++ b/src/main/java/de/bixilon/minosoft/data/entities/entities/player/properties/textures/metadata/SkinMetadata.kt @@ -17,5 +17,5 @@ import com.fasterxml.jackson.annotation.JsonInclude data class SkinMetadata( @JsonInclude(JsonInclude.Include.NON_DEFAULT) - val model: SkinModel = SkinModel.NORMAL, + val model: SkinModel = SkinModel.WIDE, ) diff --git a/src/main/java/de/bixilon/minosoft/data/entities/entities/player/properties/textures/metadata/SkinModel.kt b/src/main/java/de/bixilon/minosoft/data/entities/entities/player/properties/textures/metadata/SkinModel.kt index 61a064793..5290d9f8e 100644 --- a/src/main/java/de/bixilon/minosoft/data/entities/entities/player/properties/textures/metadata/SkinModel.kt +++ b/src/main/java/de/bixilon/minosoft/data/entities/entities/player/properties/textures/metadata/SkinModel.kt @@ -13,8 +13,11 @@ package de.bixilon.minosoft.data.entities.entities.player.properties.textures.metadata +import com.fasterxml.jackson.annotation.JsonAlias + enum class SkinModel { SLIM, - NORMAL, + @JsonAlias("normal") + WIDE, ; } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.kt index 68bf72a36..fececd9f0 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.kt @@ -159,7 +159,7 @@ class RenderWindow( val initLatch = CountUpAndDownLatch(1, latch) Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Generating font and gathering textures (after ${stopwatch.labTime()})..." } textureManager.dynamicTextures.load(initLatch) - textureManager.loadDefaultSkins(connection) + textureManager.initializeSkins(connection) textureManager.loadDefaultTextures() font = FontLoader.load(this, initLatch) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/entity/models/SkeletalEntityModel.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/entity/models/SkeletalEntityModel.kt index c67dd20e7..44a44c7dc 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/entity/models/SkeletalEntityModel.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/entity/models/SkeletalEntityModel.kt @@ -19,23 +19,23 @@ import de.bixilon.minosoft.gui.rendering.skeletal.baked.SkeletalModelStates import de.bixilon.minosoft.gui.rendering.skeletal.instance.SkeletalInstance abstract class SkeletalEntityModel(renderer: EntityRenderer, entity: E) : EntityModel(renderer, entity) { - abstract val instance: SkeletalInstance + abstract val instance: SkeletalInstance? open val hideSkeletalModel: Boolean get() = false override fun prepare() { super.prepare() - if (instance.model.state != SkeletalModelStates.LOADED) { - instance.model.preload(renderWindow) // ToDo: load async - instance.model.load() + if (instance?.model?.state != SkeletalModelStates.LOADED) { + instance?.model?.preload(renderWindow) // ToDo: load async + instance?.model?.load() } } override fun draw() { super.draw() if (!hideSkeletalModel) { - instance.updatePosition(entity.cameraPosition, entity.rotation) - instance.draw() + instance?.updatePosition(entity.cameraPosition, entity.rotation) + instance?.draw() } } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/entity/models/minecraft/player/PlayerModel.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/entity/models/minecraft/player/PlayerModel.kt index 889c17fd2..a9dd64efe 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/entity/models/minecraft/player/PlayerModel.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/entity/models/minecraft/player/PlayerModel.kt @@ -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.SkinParts 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.gui.rendering.entity.EntityRenderer 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.DynamicTexture 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 open class PlayerModel(renderer: EntityRenderer, player: PlayerEntity) : SkeletalEntityModel(renderer, player), DynamicStateChangeCallback { private var properties = player.additional.properties - private var skin: DynamicTexture? = null + private var skin: PlayerSkin? = null protected var refreshModel = false private val legAnimator = LegAnimator(this) @@ -52,8 +52,9 @@ open class PlayerModel(renderer: EntityRenderer, player: PlayerEntity) : Skeleta } - private fun createModel(properties: PlayerProperties?): SkeletalInstance { - val skinModel = properties?.textures?.skin?.metadata?.model ?: if (entity.uuid?.isSteve() == true) SkinModel.NORMAL else SkinModel.SLIM + private fun createModel(properties: PlayerProperties?): SkeletalInstance? { + 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 elements: MutableList = mutableListOf() @@ -66,15 +67,14 @@ open class PlayerModel(renderer: EntityRenderer, player: PlayerEntity) : Skeleta } elements += element } - val skin = renderWindow.textureManager.getSkin(entity, properties) - skin.usages.incrementAndGet() - this.skin?.usages?.decrementAndGet() + skin.texture.usages.incrementAndGet() + this.skin?.texture?.usages?.decrementAndGet() 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) @@ -110,13 +110,13 @@ open class PlayerModel(renderer: EntityRenderer, player: PlayerEntity) : Skeleta } override fun onStateChange(texture: DynamicTexture, state: DynamicTextureState) { - if (skin === texture) { + if (skin?.texture === texture) { refreshModel = true } } override fun unload() { - skin?.usages?.decrementAndGet() + skin?.texture?.usages?.decrementAndGet() } fun swingArm(arm: Arms) { diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/tab/TabListEntryElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/tab/TabListEntryElement.kt index f3d1eb489..dbb94c2e1 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/tab/TabListEntryElement.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/elements/tab/TabListEntryElement.kt @@ -81,7 +81,7 @@ class TabListEntryElement( init { 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() } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/TextureManager.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/TextureManager.kt index a1151c0dd..83806701a 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/TextureManager.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/TextureManager.kt @@ -15,27 +15,16 @@ package de.bixilon.minosoft.gui.rendering.system.base.texture import de.bixilon.kotlinglm.vec2.Vec2 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.gui.atlas.TextureLikeTexture 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.texture.dynamic.DynamicTexture 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.textures.TextureUtil.readTexture import de.bixilon.minosoft.gui.rendering.textures.TextureUtil.texture import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection -import de.bixilon.minosoft.util.KUtil.toResourceLocation -import de.bixilon.minosoft.util.logging.Log -import de.bixilon.minosoft.util.logging.LogLevels -import de.bixilon.minosoft.util.logging.LogMessageType -import java.util.* +import de.bixilon.minosoft.util.KUtil.minosoft abstract class TextureManager { abstract val staticTextures: StaticTextureArray @@ -45,11 +34,7 @@ abstract class TextureManager { private set lateinit var whiteTexture: TextureLikeTexture private set - lateinit var steveTexture: DynamicTexture - private set - lateinit var alexTexture: DynamicTexture - private set - lateinit var skin: DynamicTexture + lateinit var skins: SkinManager private set fun loadDefaultTextures() { @@ -57,52 +42,12 @@ abstract class TextureManager { throw IllegalStateException("Already initialized!") } 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) { - steveTexture = dynamicTextures.pushBuffer(UUID(0L, 0L), true) { connection.assetsManager["minecraft:entity/steve".toResourceLocation().texture()].readTexture() }.apply { usages.incrementAndGet() } - alexTexture = dynamicTextures.pushBuffer(UUID(1L, 0L), true) { connection.assetsManager["minecraft:entity/alex".toResourceLocation().texture()].readTexture() }.apply { usages.incrementAndGet() } - 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 initializeSkins(connection: PlayConnection) { + skins = SkinManager(this) + skins.initialize(connection.account, connection.assetsManager) } fun use(shader: NativeShader, name: String = ShaderUniforms.TEXTURES) { diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/skin/PlayerSkin.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/skin/PlayerSkin.kt new file mode 100644 index 000000000..e822d6bcd --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/skin/PlayerSkin.kt @@ -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 . + * + * 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, +) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/skin/SkinManager.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/skin/SkinManager.kt new file mode 100644 index 000000000..ff35482f7 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/skin/SkinManager.kt @@ -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 . + * + * 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) + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/skin/vanilla/DefaultLegacySkin.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/skin/vanilla/DefaultLegacySkin.kt new file mode 100644 index 000000000..30aa3e60b --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/skin/vanilla/DefaultLegacySkin.kt @@ -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 . + * + * 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) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/skin/vanilla/DefaultSkin.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/skin/vanilla/DefaultSkin.kt new file mode 100644 index 000000000..96b80ebed --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/skin/vanilla/DefaultSkin.kt @@ -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 . + * + * 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, +) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/skin/vanilla/DefaultSkinProvider.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/skin/vanilla/DefaultSkinProvider.kt new file mode 100644 index 000000000..b7a06dace --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/skin/vanilla/DefaultSkinProvider.kt @@ -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 . + * + * 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 = mutableMapOf() + private val wide: MutableMap = 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 { + 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 + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/skin/vanilla/DefaultSkins.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/skin/vanilla/DefaultSkins.kt new file mode 100644 index 000000000..58f323474 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/system/base/texture/skin/vanilla/DefaultSkins.kt @@ -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 . + * + * 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 { + val SKINS: MutableList = 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.register(): T { + SKINS += this + + return this + } + + override fun iterator(): Iterator { + return SKINS.iterator() + } +}