diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt index 0eb6aadff..a77ea12c9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearch.kt @@ -329,10 +329,6 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay */ val screenTooltipHeader: Component get() = type.displayName - val skinIcon: SkinElement? get() = null - val stackIcon: ItemStack? get() = null - val iconText: Component? get() = null - override fun serializeNBT(): CompoundTag { return CompoundTag().also { it["researched"] = isResearched diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt index 00fb36210..c75ae8d80 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/android/AndroidResearchType.kt @@ -12,10 +12,14 @@ import net.minecraft.network.chat.ComponentContents import net.minecraft.network.chat.MutableComponent import net.minecraft.network.chat.contents.TranslatableContents import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import net.minecraftforge.registries.ForgeRegistries import ru.dbotthepony.mc.otm.client.render.SkinElement import ru.dbotthepony.mc.otm.core.ListSet import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.core.set import ru.dbotthepony.mc.otm.core.stream import ru.dbotthepony.mc.otm.core.toImmutableList @@ -79,14 +83,14 @@ class AndroidResearchType( val experienceLevels: Int = 0, private val customName: Component? = null, + // why pack these into research itself? + // because Minecraft itself does this + // check out advancements val skinIcon: SkinElement? = null, - stackIcon: ItemStack? = null, + val itemIcon: Item? = null, iconText: Component? = null, ) { - private val stackIconValue = stackIcon?.copy() private val iconTextValue = iconText?.copy() - - val stackIcon get() = stackIconValue?.copy() val iconText get() = iconTextValue?.copy() data class Reference( @@ -435,19 +439,23 @@ class AndroidResearchType( it["prerequisites"] = JsonArray().also { for (value in prerequisites) it.add(value.toJson()) } it["blocked_by"] = JsonArray().also { for (value in blockedBy) it.add(value.toJson()) } - it["required_items"] = JsonArray().also { for (item in itemCollection) it.add(ItemStackCodec.serialize(item)) } - it["feature_result"] = JsonArray().also { for (feature in features) it.add(feature.toJson()) } - it["description"] = JsonArray().also { for (line in descriptionLines) it.add(Component.Serializer.toJsonTree(line)) } - it["experience"] = JsonPrimitive(experienceLevels) if (skinIcon != null) { it["skin_icon"] = skinIcon.toJson() } + if (itemIcon != null) { + it["item_icon"] = JsonPrimitive(itemIcon.registryName!!.toString()) + } + + if (iconTextValue != null) { + it["icon_text"] = Component.Serializer.toJsonTree(iconTextValue) + } + if (customName != null) { it["custom_name"] = Component.Serializer.toJsonTree(customName) } @@ -470,10 +478,16 @@ class AndroidResearchType( buff.writeVarInt(experienceLevels) buff.writeBoolean(customName != null) + if (customName != null) buff.writeComponent(customName) - if (customName != null) { - buff.writeComponent(customName) - } + buff.writeBoolean(iconTextValue != null) + if (iconTextValue != null) buff.writeComponent(iconTextValue) + + buff.writeBoolean(skinIcon != null) + skinIcon?.toNetwork(buff) + + buff.writeBoolean(itemIcon != null) + if (itemIcon != null) buff.writeUtf(itemIcon.registryName!!.toString()) } companion object { @@ -486,12 +500,30 @@ class AndroidResearchType( val descriptionLines = buff.readCollection({ LinkedList() }, FriendlyByteBuf::readComponent) val experienceLevels = buff.readVarInt() - val customName: Component? = if (buff.readBoolean()) { + val customName = if (buff.readBoolean()) { buff.readComponent() } else { null } + val iconTextValue = if (buff.readBoolean()) { + buff.readComponent() + } else { + null + } + + val skinIcon = if (buff.readBoolean()) { + SkinElement.fromNetwork(buff) + } else { + null + } + + val itemIcon = if (buff.readBoolean()) { + ForgeRegistries.ITEMS.getValue(ResourceLocation(buff.readUtf()))?.let { if (it == Items.AIR) null else it } + } else { + null + } + return AndroidResearchType( id = id, prerequisites = prerequisites, @@ -500,7 +532,10 @@ class AndroidResearchType( features = features, descriptionLines = descriptionLines, experienceLevels = experienceLevels, - customName = customName + customName = customName, + iconText = iconTextValue, + skinIcon = skinIcon, + itemIcon = itemIcon, ) } @@ -516,6 +551,9 @@ class AndroidResearchType( val description = value["description"] as JsonArray? ?: JsonArray() val experience = value["experience"]?.asInt ?: 0 val customName = value["custom_name"]?.let(Component.Serializer::fromJson) + val iconText = value["icon_text"]?.let(Component.Serializer::fromJson) + val skinIcon = value["skin_icon"]?.let(SkinElement.Companion::fromJson) + val itemIcon = value["item_icon"]?.let { ForgeRegistries.ITEMS.getValue(ResourceLocation(it.asString)).let { if (it == Items.AIR) null else it } } return AndroidResearchType( id = id, @@ -525,7 +563,10 @@ class AndroidResearchType( items = items.stream().map { ItemStackCodec.deserialize(it) }.filter { !it.isEmpty }.toList(), descriptionLines = description.stream().map { Component.Serializer.fromJson(it) }.toList() as List, experienceLevels = experience, - customName = customName + customName = customName, + iconText = iconText, + skinIcon = skinIcon, + itemIcon = itemIcon, ) } } @@ -536,6 +577,7 @@ class AndroidResearchType( var experience: Int = 0, var customName: Component? = null, var description: MutableList? = null, + var itemIcon: Item? = null, var skinIcon: SkinElement? = null, var iconText: Component? = null, ) { @@ -545,15 +587,26 @@ class AndroidResearchType( private val features = ArrayList() - fun withIconText(icon: Component?): Builder { + fun withIconText(icon: Component? = null): Builder { this.iconText = icon return this } - fun withIconText() = withIconText(null) - - fun withIcon(icon: SkinElement): Builder { + fun withIcon(icon: SkinElement? = null): Builder { this.skinIcon = icon + this.itemIcon = null + return this + } + + fun withIcon(icon: Item? = null): Builder { + this.itemIcon = icon + this.skinIcon = null + return this + } + + fun withIcon(): Builder { + this.itemIcon = null + this.skinIcon = null return this } @@ -636,7 +689,10 @@ class AndroidResearchType( features = features, descriptionLines = description ?: listOf(), experienceLevels = experience, - customName = customName + customName = customName, + skinIcon = skinIcon, + itemIcon = itemIcon, + iconText = iconText ).also { if (validate) it.validate() } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SkinElement.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SkinElement.kt index 6e048165d..1c9415435 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SkinElement.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/SkinElement.kt @@ -14,6 +14,7 @@ import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.vertex.PoseStack +import net.minecraft.network.FriendlyByteBuf import net.minecraft.resources.ResourceLocation import ru.dbotthepony.mc.otm.client.screen.panels.DockProperty import ru.dbotthepony.mc.otm.core.set @@ -129,7 +130,31 @@ data class SkinElement @JvmOverloads constructor( } } + fun toNetwork(buff: FriendlyByteBuf) { + buff.writeUtf(texture.toString()) + buff.writeFloat(x) + buff.writeFloat(y) + buff.writeFloat(width) + buff.writeFloat(height) + buff.writeFloat(imageWidth) + buff.writeFloat(imageHeight) + buff.writeEnum(winding) + } + companion object : TypeAdapter(), JsonSerializer, JsonDeserializer { + fun fromNetwork(buff: FriendlyByteBuf): SkinElement { + val texture = ResourceLocation(buff.readUtf()) + val x = buff.readFloat() + val y = buff.readFloat() + val width = buff.readFloat() + val height = buff.readFloat() + val imageWidth = buff.readFloat() + val imageHeight = buff.readFloat() + val winding = buff.readEnum(UVWindingOrder::class.java) + + return SkinElement(texture, x, y, width, height, imageWidth, imageHeight, winding) + } + fun fromJson(value: JsonObject): SkinElement { val texture = value["texture"]?.asString ?: throw JsonSyntaxException("Missing texture element") val x = value["x"]?.asFloat ?: throw JsonSyntaxException("Missing x element") @@ -143,6 +168,10 @@ data class SkinElement @JvmOverloads constructor( return SkinElement(ResourceLocation(texture), x, y, width, height, imageWidth, imageHeight, UVWindingOrder.valueOf(winding)) } + fun fromJson(value: JsonElement): SkinElement { + return fromJson(value as? JsonObject ?: throw JsonSyntaxException("Expected JsonObject, got ${value::class.qualifiedName}")) + } + override fun write(out: JsonWriter, value: SkinElement) { TypeAdapters.JSON_ELEMENT.write(out, value.toJson()) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/AndroidStationScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/AndroidStationScreen.kt index 64f75446e..ba12944ba 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/AndroidStationScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/AndroidStationScreen.kt @@ -1,6 +1,7 @@ package ru.dbotthepony.mc.otm.client.screen import com.mojang.blaze3d.platform.InputConstants +import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.vertex.PoseStack import it.unimi.dsi.fastutil.ints.Int2FloatAVLTreeMap import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap @@ -9,6 +10,8 @@ import net.minecraft.ChatFormatting import net.minecraft.client.Minecraft import net.minecraft.network.chat.Component import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.item.ItemStack +import org.lwjgl.opengl.GL11 import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.android.AndroidResearch import ru.dbotthepony.mc.otm.android.AndroidResearchManager @@ -300,10 +303,42 @@ private class AndroidResearchButton( AndroidStationScreen.CAN_NOT_BE_RESEARCHED.setSystemColor() } - val icon = node.skinIcon + val icon = node.type.skinIcon + val itemIcon = node.type.itemIcon if (icon != null) { icon.render(stack, 0f, 0f, width, height) + } else if (itemIcon != null) { + val itemstack = ItemStack(itemIcon, 1) + val systemPoseStack = RenderSystem.getModelViewStack() + + systemPoseStack.pushPose() + systemPoseStack.translate((absoluteX + 1f).toDouble(), (absoluteY + 1f).toDouble(), 0.0) + RenderSystem.applyModelViewMatrix() + RenderSystem.depthFunc(GL11.GL_LESS) + + // Thanks Mojang + // Very cool + // (for int x, int y, which are then cast into doubles anyway) + screen.itemRenderer.blitOffset = 1f // Z pos + + screen.itemRenderer.renderAndDecorateItem( + requireNotNull(minecraft.player) { "yo, dude, what the fuck" }, + itemstack, + 0, + 0, + (absoluteX + absoluteY * 1000f).toInt() + ) + + RenderSystem.depthFunc(GL11.GL_ALWAYS) + screen.itemRenderer.renderGuiItemDecorations(screen.font, itemstack, 0, 0, null) + screen.itemRenderer.blitOffset = 0f + + // too big accumulations can lead to Z near clipping issues + systemPoseStack.popPose() + RenderSystem.applyModelViewMatrix() + + clearDepth(stack) } else { drawRect(stack, 0f, 0f, width, height) } @@ -318,7 +353,7 @@ private class AndroidResearchButton( Widgets18.FORWARD_SLASH.render(stack) } - val text = node.iconText + val text = node.type.iconText if (text != null) { font.drawShadow(stack, text, width - font.width(text), height - font.lineHeight, -0x1)