From 3bfec3033e9d905514d5c1c6c62953c2a1646af0 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Fri, 1 Mar 2024 21:31:48 +0100 Subject: Add mob drop viewer to item list --- build.gradle.kts | 11 + gradle/libs.versions.toml | 13 +- .../java/moe/nea/firmament/init/EarlyRiser.java | 78 ++++ .../firmament/mixins/AppendRepoAsResourcePack.java | 33 ++ .../AccessorAbstractClientPlayerEntity.java | 18 + src/main/kotlin/moe/nea/firmament/commands/rome.kt | 41 +- .../moe/nea/firmament/gui/entity/EntityModifier.kt | 14 + .../moe/nea/firmament/gui/entity/EntityRenderer.kt | 202 +++++++++ .../moe/nea/firmament/gui/entity/EntityWidget.kt | 40 ++ .../moe/nea/firmament/gui/entity/FakeWorld.kt | 491 +++++++++++++++++++++ .../moe/nea/firmament/gui/entity/GuiPlayer.kt | 55 +++ .../moe/nea/firmament/gui/entity/ModifyAge.kt | 30 ++ .../moe/nea/firmament/gui/entity/ModifyCharged.kt | 19 + .../nea/firmament/gui/entity/ModifyEquipment.kt | 59 +++ .../moe/nea/firmament/gui/entity/ModifyHorse.kt | 66 +++ .../nea/firmament/gui/entity/ModifyInvisible.kt | 18 + .../moe/nea/firmament/gui/entity/ModifyName.kt | 19 + .../nea/firmament/gui/entity/ModifyPlayerSkin.kt | 32 ++ .../moe/nea/firmament/gui/entity/ModifyRiding.kt | 20 + .../moe/nea/firmament/gui/entity/ModifyWither.kt | 25 ++ .../moe/nea/firmament/rei/FirmamentReiPlugin.kt | 4 + .../moe/nea/firmament/rei/SBItemEntryDefinition.kt | 8 +- .../rei/SkyblockCraftingRecipeDynamicGenerator.kt | 7 + .../nea/firmament/rei/recipes/SBMobDropRecipe.kt | 113 +++++ .../moe/nea/firmament/repo/RepoModResourcePack.kt | 101 +++++ src/main/kotlin/moe/nea/firmament/util/ItemUtil.kt | 2 + .../kotlin/moe/nea/firmament/util/LoadResource.kt | 25 ++ .../kotlin/moe/nea/firmament/util/assertions.kt | 1 + .../moe/nea/firmament/util/item/SkullItemData.kt | 40 +- .../firmament/util/render/TranslatedScissors.kt | 27 ++ .../resources/assets/firmament/lang/en_us.json | 6 + src/main/resources/fabric.mod.json | 3 + src/main/resources/firmament.accesswidener | 6 +- 33 files changed, 1611 insertions(+), 16 deletions(-) create mode 100644 src/main/java/moe/nea/firmament/init/EarlyRiser.java create mode 100644 src/main/java/moe/nea/firmament/mixins/AppendRepoAsResourcePack.java create mode 100644 src/main/java/moe/nea/firmament/mixins/accessor/AccessorAbstractClientPlayerEntity.java create mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/EntityModifier.kt create mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/EntityRenderer.kt create mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/EntityWidget.kt create mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/FakeWorld.kt create mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/GuiPlayer.kt create mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/ModifyAge.kt create mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/ModifyCharged.kt create mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/ModifyEquipment.kt create mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/ModifyHorse.kt create mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/ModifyInvisible.kt create mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/ModifyName.kt create mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/ModifyPlayerSkin.kt create mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/ModifyRiding.kt create mode 100644 src/main/kotlin/moe/nea/firmament/gui/entity/ModifyWither.kt create mode 100644 src/main/kotlin/moe/nea/firmament/rei/recipes/SBMobDropRecipe.kt create mode 100644 src/main/kotlin/moe/nea/firmament/repo/RepoModResourcePack.kt create mode 100644 src/main/kotlin/moe/nea/firmament/util/LoadResource.kt create mode 100644 src/main/kotlin/moe/nea/firmament/util/render/TranslatedScissors.kt diff --git a/build.gradle.kts b/build.gradle.kts index 38c1b9f..c8c2af7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -73,6 +73,15 @@ repositories { maven("https://maven.notenoughupdates.org/releases") } +kotlin { + sourceSets.all { + languageSettings { +// languageVersion = "2.0" + enableLanguageFeature("BreakContinueInInlineLambdas") + } + } +} + val shadowMe by configurations.creating { exclude(group = "org.jetbrains.kotlin") exclude(group = "org.jetbrains.kotlinx") @@ -109,8 +118,10 @@ dependencies { modImplementation(libs.modmenu) modImplementation(libs.libgui) modImplementation(libs.moulconfig) + modImplementation(libs.manninghamMills) modCompileOnly(libs.explosiveenhancement) include(libs.libgui) + include(libs.manninghamMills) include(libs.moulconfig) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 91f6c00..388d5c6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -27,7 +27,8 @@ jarvis = "1.1.1" nealisp = "1.0.0" explosiveenhancement = "1.2.2-1.20.x" moulconfig = "3.0.0-beta.5" - +manninghamMills = "2.4.1" +notenoughanimations = "ZLjUeuU8" [libraries] minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" } @@ -46,8 +47,9 @@ jarvis_api = { module = "moe.nea.jarvis:jarvis-api", version.ref = "jarvis" } jarvis_fabric = { module = "moe.nea.jarvis:jarvis-fabric", version.ref = "jarvis" } nealisp = { module = "moe.nea:nealisp", version.ref = "nealisp" } explosiveenhancement = { module = "maven.modrinth:explosive-enhancement", version.ref = "explosiveenhancement" } - +manninghamMills = { module = "me.shedaniel:mm", version.ref = "manninghamMills" } # Runtime: +notenoughanimations = { module = "maven.modrinth:not-enough-animations", version.ref = "notenoughanimations" } hotswap = { module = "virtual.github.hotswapagent:hotswap-agent", version.ref = "hotswap_agent" } architectury_fabric = { module = "dev.architectury:architectury-fabric", version.ref = "architectury" } rei_fabric = { module = "me.shedaniel:RoughlyEnoughItems-fabric", version.ref = "rei" } @@ -61,7 +63,12 @@ freecammod = { module = "maven.modrinth:freecam", version.ref = "freecammod" } [bundles] dbus = ["dbus_java_core", "dbus_java_unixsocket"] -runtime_required = ["architectury_fabric", "rei_fabric"] +runtime_required = [ + "architectury_fabric", + "rei_fabric", + "notenoughanimations", + +] runtime_optional = [ "devauth", # "freecammod", diff --git a/src/main/java/moe/nea/firmament/init/EarlyRiser.java b/src/main/java/moe/nea/firmament/init/EarlyRiser.java new file mode 100644 index 0000000..268e3f3 --- /dev/null +++ b/src/main/java/moe/nea/firmament/init/EarlyRiser.java @@ -0,0 +1,78 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.init; + +import me.shedaniel.mm.api.ClassTinkerers; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.MappingResolver; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.*; + +import java.lang.reflect.Modifier; +import java.util.Objects; + +public class EarlyRiser implements Runnable { + MappingResolver remapper = FabricLoader.getInstance().getMappingResolver(); + String PlayerEntity = remapper.mapClassName("intermediary", "net.minecraft.class_1657"); + String World = remapper.mapClassName("intermediary", "net.minecraft.class_1937"); + String GameProfile = "com.mojang.authlib.GameProfile"; + String BlockPos = remapper.mapClassName("intermediary", "net.minecraft.class_2338"); + String AbstractClientPlayerEntity = remapper.mapClassName("intermediary", "net.minecraft.class_742"); + String GuiPlayer = "moe.nea.firmament.gui.entity.GuiPlayer"; + // World world, BlockPos pos, float yaw, GameProfile gameProfile + Type constructorDescriptor = Type.getMethodType(Type.VOID_TYPE, getTypeForClassName(World), getTypeForClassName(BlockPos), Type.FLOAT_TYPE, getTypeForClassName(GameProfile)); + + @Override + public void run() { + ClassTinkerers.addTransformation(AbstractClientPlayerEntity, it -> mapClassNode(it, getTypeForClassName(PlayerEntity))); + ClassTinkerers.addTransformation(GuiPlayer, it -> mapClassNode(it, getTypeForClassName(AbstractClientPlayerEntity))); + } + + private void mapClassNode(ClassNode classNode, Type superClass) { + for (MethodNode method : classNode.methods) { + if (Objects.equals(method.name, "") && Type.getMethodType(method.desc).equals(constructorDescriptor)) { + modifyConstructor(method, superClass); + return; + } + } + var node = new MethodNode(Opcodes.ASM9, "", constructorDescriptor.getDescriptor(), null, null); + classNode.methods.add(node); + modifyConstructor(node, superClass); + } + + private Type getTypeForClassName(String className) { + return Type.getObjectType(className.replace('.', '/')); + } + + private void modifyConstructor(MethodNode method, Type superClass) { + method.access = (method.access | Modifier.PUBLIC) & ~Modifier.PRIVATE & ~Modifier.PROTECTED; + if (method.instructions.size() != 0) return; // Some other mod has already made a constructor here + + // World world, BlockPos pos, float yaw, GameProfile gameProfile + // ALOAD this + method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + + // ALOAD World + method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); + + // ALOAD BlockPos + method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 2)); + + // ALOAD yaw + method.instructions.add(new VarInsnNode(Opcodes.FLOAD, 3)); + + // ALOAD gameProfile + method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 4)); + + // Call super + method.instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, superClass.getInternalName(), "", constructorDescriptor.getDescriptor(), false)); + + // Return + method.instructions.add(new InsnNode(Opcodes.RETURN)); + } +} diff --git a/src/main/java/moe/nea/firmament/mixins/AppendRepoAsResourcePack.java b/src/main/java/moe/nea/firmament/mixins/AppendRepoAsResourcePack.java new file mode 100644 index 0000000..729b9b8 --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/AppendRepoAsResourcePack.java @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.mixins; + +import moe.nea.firmament.repo.RepoModResourcePack; +import net.fabricmc.fabric.api.resource.ModResourcePack; +import net.fabricmc.fabric.impl.resource.loader.ModResourcePackUtil; +import net.minecraft.resource.ResourceType; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; + +@Mixin(ModResourcePackUtil.class) +public class AppendRepoAsResourcePack { + @Inject(method = "appendModResourcePacks", at = @At("TAIL")) + private static void onAppendModResourcePack( + List packs, + ResourceType type, + @Nullable String subPath, + CallbackInfo ci + ) { + RepoModResourcePack.Companion.append(packs); + } + +} diff --git a/src/main/java/moe/nea/firmament/mixins/accessor/AccessorAbstractClientPlayerEntity.java b/src/main/java/moe/nea/firmament/mixins/accessor/AccessorAbstractClientPlayerEntity.java new file mode 100644 index 0000000..efa4a56 --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/accessor/AccessorAbstractClientPlayerEntity.java @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.mixins.accessor; + +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.network.PlayerListEntry; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(AbstractClientPlayerEntity.class) +public interface AccessorAbstractClientPlayerEntity { + @Accessor("playerListEntry") + void setPlayerListEntry_firmament(PlayerListEntry playerListEntry); +} diff --git a/src/main/kotlin/moe/nea/firmament/commands/rome.kt b/src/main/kotlin/moe/nea/firmament/commands/rome.kt index 91bdf47..7df39b3 100644 --- a/src/main/kotlin/moe/nea/firmament/commands/rome.kt +++ b/src/main/kotlin/moe/nea/firmament/commands/rome.kt @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf * * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -52,7 +53,12 @@ fun firmamentCommand() = literal("firmament") { val configObj = AllConfigsGui.allConfigs.find { it.name == config } if (configObj == null) { - source.sendFeedback(Text.stringifiedTranslatable("firmament.command.toggle.no-config-found", config)) + source.sendFeedback( + Text.stringifiedTranslatable( + "firmament.command.toggle.no-config-found", + config + ) + ) return@thenExecute } val propertyObj = configObj.allOptions[property] @@ -72,9 +78,11 @@ fun firmamentCommand() = literal("firmament") { propertyObj.value = !propertyObj.value configObj.save() source.sendFeedback( - Text.stringifiedTranslatable("firmament.command.toggle.toggled",configObj.labelText, - propertyObj.labelText, - Text.translatable("firmament.toggle.${propertyObj.value}")) + Text.stringifiedTranslatable( + "firmament.command.toggle.toggled", configObj.labelText, + propertyObj.labelText, + Text.translatable("firmament.toggle.${propertyObj.value}") + ) ) } } @@ -144,22 +152,37 @@ fun firmamentCommand() = literal("firmament") { Text.stringifiedTranslatable("firmament.price.bazaar.productid", bazaarData.productId.bazaarId) ) source.sendFeedback( - Text.stringifiedTranslatable("firmament.price.bazaar.buy.price", FirmFormatters.formatCurrency(bazaarData.quickStatus.buyPrice, 1)) + Text.stringifiedTranslatable( + "firmament.price.bazaar.buy.price", + FirmFormatters.formatCurrency(bazaarData.quickStatus.buyPrice, 1) + ) ) source.sendFeedback( - Text.stringifiedTranslatable("firmament.price.bazaar.buy.order", bazaarData.quickStatus.buyOrders) + Text.stringifiedTranslatable( + "firmament.price.bazaar.buy.order", + bazaarData.quickStatus.buyOrders + ) ) source.sendFeedback( - Text.stringifiedTranslatable("firmament.price.bazaar.sell.price", FirmFormatters.formatCurrency(bazaarData.quickStatus.sellPrice, 1)) + Text.stringifiedTranslatable( + "firmament.price.bazaar.sell.price", + FirmFormatters.formatCurrency(bazaarData.quickStatus.sellPrice, 1) + ) ) source.sendFeedback( - Text.stringifiedTranslatable("firmament.price.bazaar.sell.order", bazaarData.quickStatus.sellOrders) + Text.stringifiedTranslatable( + "firmament.price.bazaar.sell.order", + bazaarData.quickStatus.sellOrders + ) ) } val lowestBin = HypixelStaticData.lowestBin[itemName] if (lowestBin != null) { source.sendFeedback( - Text.stringifiedTranslatable("firmament.price.lowestbin", FirmFormatters.formatCurrency(lowestBin, 1)) + Text.stringifiedTranslatable( + "firmament.price.lowestbin", + FirmFormatters.formatCurrency(lowestBin, 1) + ) ) } } diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/EntityModifier.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/EntityModifier.kt new file mode 100644 index 0000000..fae3a4b --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/gui/entity/EntityModifier.kt @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.entity.LivingEntity + +fun interface EntityModifier { + fun apply(entity: LivingEntity, info: JsonObject): LivingEntity +} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/EntityRenderer.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/EntityRenderer.kt new file mode 100644 index 0000000..d645e5b --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/gui/entity/EntityRenderer.kt @@ -0,0 +1,202 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.gui.entity + +import com.google.gson.Gson +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import org.apache.logging.log4j.LogManager +import org.joml.Quaternionf +import org.joml.Vector3f +import kotlin.math.atan +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.screen.ingame.InventoryScreen +import net.minecraft.entity.Entity +import net.minecraft.entity.EntityType +import net.minecraft.entity.LivingEntity +import net.minecraft.util.Identifier +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.assertNotNullOr +import moe.nea.firmament.util.iterate +import moe.nea.firmament.util.openFirmamentResource +import moe.nea.firmament.util.render.enableScissorWithTranslation + +object EntityRenderer { + val fakeWorld = FakeWorld() + private fun t(entityType: EntityType): () -> T { + return { entityType.create(fakeWorld)!! } + } + + val entityIds: Map LivingEntity> = mapOf( + "Zombie" to t(EntityType.ZOMBIE), + "Chicken" to t(EntityType.CHICKEN), + "Slime" to t(EntityType.SLIME), + "Wolf" to t(EntityType.WOLF), + "Skeleton" to t(EntityType.SKELETON), + "Creeper" to t(EntityType.CREEPER), + "Ocelot" to t(EntityType.OCELOT), + "Blaze" to t(EntityType.BLAZE), + "Rabbit" to t(EntityType.RABBIT), + "Sheep" to t(EntityType.SHEEP), + "Horse" to t(EntityType.HORSE), + "Eisengolem" to t(EntityType.IRON_GOLEM), + "Silverfish" to t(EntityType.SILVERFISH), + "Witch" to t(EntityType.WITCH), + "Endermite" to t(EntityType.ENDERMITE), + "Snowman" to t(EntityType.SNOW_GOLEM), + "Villager" to t(EntityType.VILLAGER), + "Guardian" to t(EntityType.GUARDIAN), + "ArmorStand" to t(EntityType.ARMOR_STAND), + "Squid" to t(EntityType.SQUID), + "Bat" to t(EntityType.BAT), + "Spider" to t(EntityType.SPIDER), + "CaveSpider" to t(EntityType.CAVE_SPIDER), + "Pigman" to t(EntityType.ZOMBIFIED_PIGLIN), + "Ghast" to t(EntityType.GHAST), + "MagmaCube" to t(EntityType.MAGMA_CUBE), + "Wither" to t(EntityType.WITHER), + "Enderman" to t(EntityType.ENDERMAN), + "Mooshroom" to t(EntityType.MOOSHROOM), + "WitherSkeleton" to t(EntityType.WITHER_SKELETON), + "Cow" to t(EntityType.COW), + "Dragon" to t(EntityType.ENDER_DRAGON), + "Player" to { makeGuiPlayer(fakeWorld) }, + "Pig" to t(EntityType.PIG), + "Giant" to t(EntityType.GIANT), + ) + val entityModifiers: Map = mapOf( + "playerdata" to ModifyPlayerSkin, + "equipment" to ModifyEquipment, + "riding" to ModifyRiding, + "charged" to ModifyCharged, + "witherdata" to ModifyWither, + "invisible" to ModifyInvisible, + "age" to ModifyAge, + "horse" to ModifyHorse, + "name" to ModifyName, + ) + + val logger = LogManager.getLogger("Firmament.Entity") + fun applyModifiers(entityId: String, modifiers: List): LivingEntity? { + val entityType = assertNotNullOr(entityIds[entityId]) { + logger.error("Could not create entity with id $entityId") + return null + } + var entity = entityType() + for (modifierJson in modifiers) { + val modifier = assertNotNullOr(modifierJson["type"]?.asString?.let(entityModifiers::get)) { + logger.error("Unknown modifier $modifierJson") + return null + } + entity = modifier.apply(entity, modifierJson) + } + return entity + } + + fun constructEntity(info: JsonObject): LivingEntity? { + val modifiers = (info["modifiers"] as JsonArray?)?.map { it.asJsonObject } ?: emptyList() + val entityType = assertNotNullOr(info["entity"]?.asString) { + logger.error("Missing entity type on entity object") + return null + } + return applyModifiers(entityType, modifiers) + } + + private val gson = Gson() + fun constructEntity(location: Identifier): LivingEntity? { + return constructEntity( + gson.fromJson( + location.openFirmamentResource().bufferedReader(), JsonObject::class.java + ) + ) + } + + fun renderEntity( + entity: LivingEntity, + renderContext: DrawContext, + posX: Int, + posY: Int, + mouseX: Float, + mouseY: Float + ) { + var bottomOffset = 0.0F + var currentEntity = entity + val maxSize = entity.iterate { it.firstPassenger as? LivingEntity } + .map { it.height } + .sum() + while (true) { + currentEntity.age = MC.player?.age ?: 0 + drawEntity( + renderContext, + posX, + posY, + posX + 50, + posY + 80, + (2F / maxSize * 30).toInt(), + -bottomOffset, + mouseX, + mouseY, + currentEntity + ) + val next = currentEntity.firstPassenger as? LivingEntity ?: break + bottomOffset += currentEntity.getPassengerRidingPos(next).y.toFloat() * 0.75F + currentEntity = next + } + } + + + fun drawEntity( + context: DrawContext, + x1: Int, + y1: Int, + x2: Int, + y2: Int, + size: Int, + bottomOffset: Float, + mouseX: Float, + mouseY: Float, + entity: LivingEntity + ) { + context.enableScissorWithTranslation(x1.toFloat(), y1.toFloat(), x2.toFloat(), y2.toFloat()) + val centerX = (x1 + x2) / 2f + val centerY = (y1 + y2) / 2f + val targetYaw = atan(((centerX - mouseX) / 40.0f).toDouble()).toFloat() + val targetPitch = atan(((centerY - mouseY) / 40.0f).toDouble()).toFloat() + val rotateToFaceTheFront = Quaternionf().rotateZ(Math.PI.toFloat()) + val rotateToFaceTheCamera = Quaternionf().rotateX(targetPitch * 20.0f * (Math.PI.toFloat() / 180)) + rotateToFaceTheFront.mul(rotateToFaceTheCamera) + val oldBodyYaw = entity.bodyYaw + val oldYaw = entity.yaw + val oldPitch = entity.pitch + val oldPrevHeadYaw = entity.prevHeadYaw + val oldHeadYaw = entity.headYaw + entity.bodyYaw = 180.0f + targetYaw * 20.0f + entity.yaw = 180.0f + targetYaw * 40.0f + entity.pitch = -targetPitch * 20.0f + entity.headYaw = entity.yaw + entity.prevHeadYaw = entity.yaw + val vector3f = Vector3f(0.0f, entity.height / 2.0f + bottomOffset, 0.0f) + InventoryScreen.drawEntity( + context, + centerX, + centerY, + size, + vector3f, + rotateToFaceTheFront, + rotateToFaceTheCamera, + entity + ) + entity.bodyYaw = oldBodyYaw + entity.yaw = oldYaw + entity.pitch = oldPitch + entity.prevHeadYaw = oldPrevHeadYaw + entity.headYaw = oldHeadYaw + context.disableScissor() + } + + +} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/EntityWidget.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/EntityWidget.kt new file mode 100644 index 0000000..42fa485 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/gui/entity/EntityWidget.kt @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.gui.entity + +import me.shedaniel.math.Dimension +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.Element +import net.minecraft.entity.LivingEntity + +class EntityWidget(val entity: LivingEntity, val point: Point) : WidgetWithBounds() { + override fun children(): List { + return emptyList() + } + + var hasErrored = false + + override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + try { + if (!hasErrored) + EntityRenderer.renderEntity(entity, context, point.x, point.y, mouseX.toFloat(), mouseY.toFloat()) + } catch (ex: Exception) { + EntityRenderer.logger.error("Failed to render constructed entity: $entity", ex) + hasErrored = true + } + if (hasErrored) { + context.fill(point.x, point.y, point.x + 50, point.y + 80, 0xFFAA2222.toInt()) + } + } + + override fun getBounds(): Rectangle { + return Rectangle(point, Dimension(50, 80)) + } +} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/FakeWorld.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/FakeWorld.kt new file mode 100644 index 0000000..4cdfc45 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/gui/entity/FakeWorld.kt @@ -0,0 +1,491 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.gui.entity + +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Lifecycle +import java.util.* +import java.util.function.BooleanSupplier +import java.util.function.Consumer +import java.util.stream.Stream +import kotlin.jvm.optionals.getOrNull +import kotlin.streams.asSequence +import net.minecraft.block.Block +import net.minecraft.block.BlockState +import net.minecraft.client.world.ClientWorld +import net.minecraft.entity.Entity +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.fluid.Fluid +import net.minecraft.item.map.MapState +import net.minecraft.recipe.RecipeManager +import net.minecraft.registry.BuiltinRegistries +import net.minecraft.registry.DynamicRegistryManager +import net.minecraft.registry.Registry +import net.minecraft.registry.RegistryKey +import net.minecraft.registry.RegistryKeys +import net.minecraft.registry.RegistryWrapper +import net.minecraft.registry.entry.RegistryEntry +import net.minecraft.registry.entry.RegistryEntryList +import net.minecraft.registry.entry.RegistryEntryOwner +import net.minecraft.registry.tag.TagKey +import net.minecraft.resource.featuretoggle.FeatureFlag +import net.minecraft.resource.featuretoggle.FeatureFlags +import net.minecraft.resource.featuretoggle.FeatureSet +import net.minecraft.scoreboard.Scoreboard +import net.minecraft.sound.SoundCategory +import net.minecraft.sound.SoundEvent +import net.minecraft.util.Identifier +import net.minecraft.util.TypeFilter +import net.minecraft.util.function.LazyIterationConsumer +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Box +import net.minecraft.util.math.ChunkPos +import net.minecraft.util.math.Direction +import net.minecraft.util.math.Vec3d +import net.minecraft.util.math.random.Random +import net.minecraft.util.profiler.DummyProfiler +import net.minecraft.world.BlockView +import net.minecraft.world.Difficulty +import net.minecraft.world.GameRules +import net.minecraft.world.MutableWorldProperties +import net.minecraft.world.World +import net.minecraft.world.biome.Biome +import net.minecraft.world.biome.BiomeKeys +import net.minecraft.world.chunk.Chunk +import net.minecraft.world.chunk.ChunkManager +import net.minecraft.world.chunk.ChunkStatus +import net.minecraft.world.chunk.EmptyChunk +import net.minecraft.world.chunk.light.LightingProvider +import net.minecraft.world.entity.EntityLookup +import net.minecraft.world.event.GameEvent +import net.minecraft.world.tick.OrderedTick +import net.minecraft.world.tick.QueryableTickScheduler +import net.minecraft.world.tick.TickManager + +fun makeRegistry(registryWrapper: RegistryWrapper.Impl, key: RegistryKey>): Registry { + val inverseLookup = registryWrapper.streamEntries() + .asSequence().map { it.value() to it.registryKey() } + .toMap() + val idLookup = registryWrapper.streamEntries() + .asSequence() + .map { it.registryKey() } + .withIndex() + .associate { it.value to it.index } + val map = registryWrapper.streamEntries().asSequence().map { it.registryKey() to it.value() }.toMap(mutableMapOf()) + val inverseIdLookup = idLookup.asIterable().associate { (k, v) -> v to k } + return object : Registry { + override fun get(key: RegistryKey?): T? { + return registryWrapper.getOptional(key).getOrNull()?.value() + } + + override fun iterator(): MutableIterator { + return object : MutableIterator { + val iterator = registryWrapper.streamEntries().iterator() + override fun hasNext(): Boolean { + return iterator.hasNext() + } + + override fun next(): T { + return iterator.next().value() + } + + override fun remove() { + TODO("Not yet implemented") + } + } + } + + override fun getRawId(value: T?): Int { + return idLookup[inverseLookup[value ?: return -1] ?: return -1] ?: return -1 + } + + override fun get(id: Identifier?): T? { + return get(RegistryKey.of(key, id)) + } + + override fun get(index: Int): T? { + return get(inverseIdLookup[index] ?: return null) + } + + override fun size(): Int { + return idLookup.size + } + + override fun getKey(): RegistryKey> { + return key + } + + override fun getLifecycle(): Lifecycle { + return Lifecycle.stable() + } + + override fun getIds(): MutableSet { + return idLookup.keys.mapTo(mutableSetOf()) { it.value } + } + + override fun getEntrySet(): MutableSet, T>> { + return map.entries + } + + override fun getKeys(): MutableSet> { + return map.keys + } + + override fun getRandom(random: Random?): Optional> { + return registryWrapper.streamEntries().findFirst() + } + + override fun containsId(id: Identifier?): Boolean { + return idLookup.containsKey(RegistryKey.of(key, id ?: return false)) + } + + override fun freeze(): Registry { + return this + } + + override fun getEntry(rawId: Int): Optional> { + val x = inverseIdLookup[rawId] ?: return Optional.empty() + return Optional.of(RegistryEntry.Reference.standAlone(registryWrapper, x)) + } + + override fun streamEntries(): Stream> { + return registryWrapper.streamEntries() + } + + override fun streamTagsAndEntries(): Stream, RegistryEntryList.Named>> { + return streamTags().map { Pair(it, getOrCreateEntryList(it)) } + } + + override fun streamTags(): Stream> { + return registryWrapper.streamTagKeys() + } + + override fun clearTags() { + } + + override fun getEntryOwner(): RegistryEntryOwner { + return registryWrapper + } + + override fun getReadOnlyWrapper(): RegistryWrapper.Impl { + return registryWrapper + } + + override fun populateTags(tagEntries: MutableMap, MutableList>>?) { + } + + override fun getOrCreateEntryList(tag: TagKey?): RegistryEntryList.Named { + return getEntryList(tag).orElseGet { RegistryEntryList.of(registryWrapper, tag) } + } + + override fun getEntryList(tag: TagKey?): Optional> { + return registryWrapper.getOptional(tag ?: return Optional.empty()) + } + + override fun getEntry(value: T): RegistryEntry { + return registryWrapper.getOptional(inverseLookup[value]!!).get() + } + + override fun getEntry(key: RegistryKey?): Optional> { + return registryWrapper.getOptional(key ?: return Optional.empty()) + } + + override fun createEntry(value: T): RegistryEntry.Reference { + TODO() + } + + override fun contains(key: RegistryKey?): Boolean { + return getEntry(key).isPresent + } + + override fun getEntryLifecycle(entry: T): Lifecycle { + return Lifecycle.stable() + } + + override fun getId(value: T): Identifier? { + return (inverseLookup[value] ?: return null).value + } + + override fun getKey(entry: T): Optional> { + return Optional.ofNullable(inverseLookup[entry ?: return Optional.empty()]) + } + } +} + +fun createDynamicRegistry(): DynamicRegistryManager.Immutable { + val wrapperLookup = BuiltinRegistries.createWrapperLookup() + return object : DynamicRegistryManager.Immutable { + override fun getOptional(key: RegistryKey>): Optional> { + val lookup = wrapperLookup.getOptionalWrapper(key).getOrNull() ?: return Optional.empty() + val registry = makeRegistry(lookup, key as RegistryKey>) + return Optional.of(registry) + } + + fun entry(reg: RegistryKey>): DynamicRegistryManager.Entry { + return DynamicRegistryManager.Entry(reg, getOptional(reg).get()) + } + + override fun streamAllRegistries(): Stream> { + return wrapperLookup.streamAllRegistryKeys() + .map { entry(it as RegistryKey>) } + } + } +} + +class FakeWorld(registries: DynamicRegistryManager.Immutable = createDynamicRegistry()) : World( + Properties, + RegistryKey.of(RegistryKeys.WORLD, Identifier.of("firmament", "fakeworld")), + registries, + registries[RegistryKeys.DIMENSION_TYPE].entryOf( + RegistryKey.of( + RegistryKeys.DIMENSION_TYPE, + Identifier("minecraft", "overworld") + ) + ), + { DummyProfiler.INSTANCE }, + true, + false, + 0, 0 +) { + object Properties : MutableWorldProperties { + override fun getSpawnX(): Int { + return 0 + } + + override fun getSpawnY(): Int { + return 0 + } + + override fun getSpawnZ(): Int { + return 0 + } + + override fun getSpawnAngle(): Float { + return 0F + } + + override fun getTime(): Long { + return 0 + } + + override fun getTimeOfDay(): Long { + return 0 + } + + override fun isThundering(): Boolean { + return false + } + + override fun isRaining(): Boolean { + return false + } + + override fun setRaining(raining: Boolean) { + } + + override fun isHardcore(): Boolean { + return false + } + + override fun getGameRules(): GameRules { + return GameRules() + } + + override fun getDifficulty(): Difficulty { + return Difficulty.HARD + } + + override fun isDifficultyLocked(): Boolean { + return false + } + + override fun setSpawnX(spawnX: Int) { + } + + override fun setSpawnY(spawnY: Int) { + } + + override fun setSpawnZ(spawnZ: Int) { + } + + override fun setSpawnAngle(spawnAngle: Float) { + } + } + + override fun getPlayers(): List { + return emptyList() + } + + override fun getBrightness(direction: Direction?, shaded: Boolean): Float { + return 1f + } + + override fun getGeneratorStoredBiome(biomeX: Int, biomeY: Int, biomeZ: Int): RegistryEntry { + return registryManager.get(RegistryKeys.BIOME).entryOf(BiomeKeys.PLAINS) + } + + override fun getEnabledFeatures(): FeatureSet { + return FeatureFlags.VANILLA_FEATURES + } + + class FakeTickScheduler : QueryableTickScheduler { + override fun scheduleTick(orderedTick: OrderedTick?) { + } + + override fun isQueued(pos: BlockPos?, type: T): Boolean { + return true + } + + override fun getTickCount(): Int { + return 0 + } + + override fun isTicking(pos: BlockPos?, type: T): Boolean { + return true + } + + } + + override fun getBlockTickScheduler(): QueryableTickScheduler { + return FakeTickScheduler() + } + + override fun getFluidTickScheduler(): QueryableTickScheduler { + return FakeTickScheduler() + } + + + class FakeChunkManager(val world: FakeWorld) : ChunkManager() { + override fun getChunk(x: Int, z: Int, leastStatus: ChunkStatus?, create: Boolean): Chunk { + return EmptyChunk(world, ChunkPos(x,z), world.registryManager.get(RegistryKeys.BIOME).entryOf(BiomeKeys.PLAINS)) + } + + override fun getWorld(): BlockView { + return world + } + + override fun tick(shouldKeepTicking: BooleanSupplier?, tickChunks: Boolean) { + } + + override fun getDebugString(): String { + return "FakeChunkManager" + } + + override fun getLoadedChunkCount(): Int { + return 0 + } + + override fun getLightingProvider(): LightingProvider { + return FakeLightingProvider(this) + } + } + + class FakeLightingProvider(chunkManager: FakeChunkManager) : LightingProvider(chunkManager, false, false) + + override fun getChunkManager(): ChunkManager { + return FakeChunkManager(this) + } + + override fun playSound( + source: PlayerEntity?, + x: Double, + y: Double, + z: Double, + sound: RegistryEntry?, + category: SoundCategory?, + volume: Float, + pitch: Float, + seed: Long + ) { + } + + override fun syncWorldEvent(player: PlayerEntity?, eventId: Int, pos: BlockPos?, data: Int) { + } + + override fun emitGameEvent(event: GameEvent?, emitterPos: Vec3d?, emitter: GameEvent.Emitter?) { + } + + override fun updateListeners(pos: BlockPos?, oldState: BlockState?, newState: BlockState?, flags: Int) { + } + + override fun playSoundFromEntity( + source: PlayerEntity?, + entity: Entity?, + sound: RegistryEntry?, + category: SoundCategory?, + volume: Float, + pitch: Float, + seed: Long + ) { + } + + override fun asString(): String { + return "FakeWorld" + } + + override fun getEntityById(id: Int): Entity? { + return null + } + + override fun getTickManager(): TickManager { + return TickManager() + } + + override fun getMapState(id: String?): MapState? { + return null + } + + override fun putMapState(id: String?, state: MapState?) { + } + + override fun getNextMapId(): Int { + return 0 + } + + override fun setBlockBreakingInfo(entityId: Int, pos: BlockPos?, progress: Int) { + } + + override fun getScoreboard(): Scoreboard { + return Scoreboard() + } + + override fun getRecipeManager(): RecipeManager { + return RecipeManager() + } + + object FakeEntityLookup : EntityLookup { + override fun get(id: Int): Entity? { + return null + } + + override fun get(uuid: UUID?): Entity? { + return null + } + + override fun iterate(): MutableIterable { + return mutableListOf() + } + + override fun forEachIntersects( + filter: TypeFilter?, + box: Box?, + consumer: LazyIterationConsumer? + ) { + } + + override fun forEachIntersects(box: Box?, action: Consumer?) { + } + + override fun forEach(filter: TypeFilter?, consumer: LazyIterationConsumer?) { + } + + } + + override fun getEntityLookup(): EntityLookup { + return FakeEntityLookup + } +} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/GuiPlayer.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/GuiPlayer.kt new file mode 100644 index 0000000..5f88098 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/gui/entity/GuiPlayer.kt @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.gui.entity + +import com.mojang.authlib.GameProfile +import java.util.* +import net.minecraft.client.network.AbstractClientPlayerEntity +import net.minecraft.client.util.DefaultSkinHelper +import net.minecraft.client.util.SkinTextures +import net.minecraft.client.util.SkinTextures.Model +import net.minecraft.client.world.ClientWorld +import net.minecraft.util.Identifier +import net.minecraft.util.math.BlockPos +import net.minecraft.world.World + +/** + * @see moe.nea.firmament.init.EarlyRiser + */ +fun makeGuiPlayer(world: FakeWorld): GuiPlayer { + val constructor = GuiPlayer::class.java.getDeclaredConstructor( + World::class.java, + BlockPos::class.java, + Float::class.javaPrimitiveType, + GameProfile::class.java + ) + return constructor.newInstance(world, BlockPos.ORIGIN, 0F, GameProfile(UUID.randomUUID(), "Linnea")) +} + +class GuiPlayer(world: ClientWorld?, profile: GameProfile?) : AbstractClientPlayerEntity(world, profile) { + override fun isSpectator(): Boolean { + return false + } + + override fun isCreative(): Boolean { + return false + } + + var skinTexture: Identifier = DefaultSkinHelper.getSkinTextures(this.getUuid()).texture + var capeTexture: Identifier? = null + var model: Model = Model.WIDE + override fun getSkinTextures(): SkinTextures { + return SkinTextures( + skinTexture, + null, + capeTexture, + null, + model, + true + ) + } +} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyAge.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyAge.kt new file mode 100644 index 0000000..7b80e88 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyAge.kt @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.entity.LivingEntity +import net.minecraft.entity.decoration.ArmorStandEntity +import net.minecraft.entity.mob.ZombieEntity +import net.minecraft.entity.passive.PassiveEntity + +object ModifyAge : EntityModifier { + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + val isBaby = info["baby"]?.asBoolean ?: false + if (entity is PassiveEntity) { + entity.breedingAge = if (isBaby) -1 else 1 + } else if (entity is ZombieEntity) { + entity.isBaby = isBaby + } else if (entity is ArmorStandEntity) { + entity.isSmall = isBaby + } else { + error("Cannot set age for $entity") + } + return entity + } + +} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyCharged.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyCharged.kt new file mode 100644 index 0000000..225a8ca --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyCharged.kt @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.entity.LivingEntity +import net.minecraft.entity.mob.CreeperEntity + +object ModifyCharged : EntityModifier { + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + require(entity is CreeperEntity) + entity.dataTracker.set(CreeperEntity.CHARGED, true) + return entity + } +} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyEquipment.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyEquipment.kt new file mode 100644 index 0000000..e438f59 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyEquipment.kt @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.entity.EquipmentSlot +import net.minecraft.entity.LivingEntity +import net.minecraft.item.DyeableArmorItem +import net.minecraft.item.Item +import net.minecraft.item.ItemStack +import net.minecraft.item.Items +import moe.nea.firmament.rei.SBItemStack +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.item.setEncodedSkullOwner +import moe.nea.firmament.util.item.zeroUUID + +object ModifyEquipment : EntityModifier { + val names = mapOf( + "hand" to EquipmentSlot.MAINHAND, + "helmet" to EquipmentSlot.HEAD, + "chestplate" to EquipmentSlot.CHEST, + "leggings" to EquipmentSlot.LEGS, + "feet" to EquipmentSlot.FEET, + ) + + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + names.forEach { (key, slot) -> + info[key]?.let { + entity.equipStack(slot, createItem(it.asString)) + } + } + return entity + } + + private fun createItem(item: String): ItemStack { + val split = item.split("#") + if (split.size != 2) return SBItemStack(SkyblockId(item)).asImmutableItemStack() + val (type, data) = split + return when (type) { + "SKULL" -> ItemStack(Items.PLAYER_HEAD).also { it.setEncodedSkullOwner(zeroUUID, data) } + "LEATHER_LEGGINGS" -> coloredLeatherArmor(Items.LEATHER_LEGGINGS, data) + "LEATHER_BOOTS" -> coloredLeatherArmor(Items.LEATHER_BOOTS, data) + "LEATHER_HELMET" -> coloredLeatherArmor(Items.LEATHER_HELMET, data) + "LEATHER_CHESTPLATE" -> coloredLeatherArmor(Items.LEATHER_CHESTPLATE, data) + else -> error("Unknown leather piece: $type") + } + } + + private fun coloredLeatherArmor(leatherArmor: Item, data: String): ItemStack { + require(leatherArmor is DyeableArmorItem) + val stack = ItemStack(leatherArmor) + leatherArmor.setColor(stack, data.toInt(16)) + return stack + } +} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyHorse.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyHorse.kt new file mode 100644 index 0000000..4c49510 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyHorse.kt @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonNull +import com.google.gson.JsonObject +import kotlin.experimental.and +import kotlin.experimental.inv +import kotlin.experimental.or +import net.minecraft.entity.EntityType +import net.minecraft.entity.LivingEntity +import net.minecraft.entity.passive.AbstractHorseEntity +import net.minecraft.item.ItemStack +import net.minecraft.item.Items +import moe.nea.firmament.gui.entity.EntityRenderer.fakeWorld + +object ModifyHorse : EntityModifier { + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + require(entity is AbstractHorseEntity) + var entity: AbstractHorseEntity = entity + info["kind"]?.let { + entity = when (it.asString) { + "skeleton" -> EntityType.SKELETON_HORSE.create(fakeWorld)!! + "zombie" -> EntityType.ZOMBIE_HORSE.create(fakeWorld)!! + "mule" -> EntityType.MULE.create(fakeWorld)!! + "donkey" -> EntityType.DONKEY.create(fakeWorld)!! + "horse" -> EntityType.HORSE.create(fakeWorld)!! + else -> error("Unknown horse kind $it") + } + } + info["armor"]?.let { + if (it is JsonNull) { + entity.setHorseArmor(ItemStack.EMPTY) + } else { + when (it.asString) { + "iron" -> entity.setHorseArmor(ItemStack(Items.IRON_HORSE_ARMOR)) + "golden" -> entity.setHorseArmor(ItemStack(Items.GOLDEN_HORSE_ARMOR)) + "diamond" -> entity.setHorseArmor(ItemStack(Items.DIAMOND_HORSE_ARMOR)) + else -> error("Unknown horse armor $it") + } + } + } + info["saddled"]?.let { + entity.setIsSaddled(it.asBoolean) + } + return entity + } + +} + +fun AbstractHorseEntity.setIsSaddled(shouldBeSaddled: Boolean) { + val oldFlag = dataTracker.get(AbstractHorseEntity.HORSE_FLAGS) + dataTracker.set( + AbstractHorseEntity.HORSE_FLAGS, + if (shouldBeSaddled) oldFlag or AbstractHorseEntity.SADDLED_FLAG.toByte() + else oldFlag and AbstractHorseEntity.SADDLED_FLAG.toByte().inv() + ) +} + +fun AbstractHorseEntity.setHorseArmor(itemStack: ItemStack) { + items.setStack(1, itemStack) +} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyInvisible.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyInvisible.kt new file mode 100644 index 0000000..07c6617 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyInvisible.kt @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.entity.LivingEntity + +object ModifyInvisible : EntityModifier { + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + entity.isInvisible = info.get("invisible")?.asBoolean ?: true + return entity + } + +} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyName.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyName.kt new file mode 100644 index 0000000..c74e2e5 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyName.kt @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.entity.LivingEntity +import net.minecraft.text.Text + +object ModifyName : EntityModifier { + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + entity.customName = Text.literal(info.get("name").asString) + return entity + } + +} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyPlayerSkin.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyPlayerSkin.kt new file mode 100644 index 0000000..886a17e --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyPlayerSkin.kt @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.client.util.SkinTextures +import net.minecraft.entity.LivingEntity +import net.minecraft.util.Identifier + +object ModifyPlayerSkin : EntityModifier { + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + require(entity is GuiPlayer) + info["cape"]?.let { + entity.capeTexture = Identifier(it.asString) + } + info["skin"]?.let { + entity.skinTexture = Identifier(it.asString) + } + info["slim"]?.let { + entity.model = if (it.asBoolean) SkinTextures.Model.SLIM else SkinTextures.Model.WIDE + } + info["parts"]?.let { + // TODO: support parts + } + return entity + } + +} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyRiding.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyRiding.kt new file mode 100644 index 0000000..b9c462e --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyRiding.kt @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.entity.LivingEntity + +object ModifyRiding : EntityModifier { + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + val newEntity = EntityRenderer.constructEntity(info) + require(newEntity != null) + newEntity.startRiding(entity, true) + return entity + } + +} diff --git a/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyWither.kt b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyWither.kt new file mode 100644 index 0000000..a0ddcdd --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/gui/entity/ModifyWither.kt @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.gui.entity + +import com.google.gson.JsonObject +import net.minecraft.entity.LivingEntity +import net.minecraft.entity.boss.WitherEntity + +object ModifyWither : EntityModifier { + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + require(entity is WitherEntity) + info["tiny"]?.let { + entity.setInvulTimer(if (it.asBoolean) 800 else 0) + } + info["armored"]?.let { + entity.health = if (it.asBoolean) 1F else entity.maxHealth + } + return entity + } + +} diff --git a/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt b/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt index d643cbf..7e22a1e 100644 --- a/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt +++ b/src/main/kotlin/moe/nea/firmament/rei/FirmamentReiPlugin.kt @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf * * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -31,6 +32,7 @@ import net.minecraft.client.gui.screen.ingame.HandledScreen import net.minecraft.item.ItemStack import net.minecraft.text.Text import net.minecraft.util.Identifier +import moe.nea.firmament.rei.recipes.SBMobDropRecipe class FirmamentReiPlugin : REIClientPlugin { @@ -62,6 +64,7 @@ class FirmamentReiPlugin : REIClientPlugin { override fun registerCategories(registry: CategoryRegistry) { registry.add(SBCraftingRecipe.Category) registry.add(SBForgeRecipe.Category) + registry.add(SBMobDropRecipe.Category) } override fun registerExclusionZones(zones: ExclusionZones) { @@ -77,6 +80,7 @@ class FirmamentReiPlugin : REIClientPlugin { SBForgeRecipe.Category.categoryIdentifier, SkyblockForgeRecipeDynamicGenerator ) + registry.registerDisplayGenerator(SBMobDropRecipe.Category.categoryIdentifier, SkyblockMobDropRecipeDynamicGenerator) } override fun registerCollapsibleEntries(registry: CollapsibleEntryRegistry) { diff --git a/src/main/kotlin/moe/nea/firmament/rei/SBItemEntryDefinition.kt b/src/main/kotlin/moe/nea/firmament/rei/SBItemEntryDefinition.kt index 77e329e..0cdb17e 100644 --- a/src/main/kotlin/moe/nea/firmament/rei/SBItemEntryDefinition.kt +++ b/src/main/kotlin/moe/nea/firmament/rei/SBItemEntryDefinition.kt @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf * * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -30,6 +31,7 @@ import moe.nea.firmament.repo.RepoManager import moe.nea.firmament.util.FirmFormatters import moe.nea.firmament.util.HypixelPetInfo import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.appendLore import moe.nea.firmament.util.petData import moe.nea.firmament.util.skyBlockId @@ -54,6 +56,7 @@ data class SBItemStack( val neuItem: NEUItem?, val stackSize: Int, val petData: PetData?, + val extraLore: List = emptyList(), ) { constructor(skyblockId: SkyblockId, petData: PetData) : this( skyblockId, @@ -102,12 +105,13 @@ data class SBItemStack( } } - private val itemStack by lazy(LazyThreadSafetyMode.NONE) { + private val itemStack: ItemStack by lazy(LazyThreadSafetyMode.NONE) { if (skyblockId == SkyblockId.COINS) - return@lazy ItemCache.coinItem(stackSize) + return@lazy ItemCache.coinItem(stackSize).also { it.appendLore(extraLore) } val replacementData = mutableMapOf() injectReplacementDataForPets(replacementData) return@lazy neuItem.asItemStack(idHint = skyblockId, replacementData).copyWithCount(stackSize) + .also { it.appendLore(extraLore) } } fun asImmutableItemStack(): ItemStack { diff --git a/src/main/kotlin/moe/nea/firmament/rei/SkyblockCraftingRecipeDynamicGenerator.kt b/src/main/kotlin/moe/nea/firmament/rei/SkyblockCraftingRecipeDynamicGenerator.kt index 7bff82b..ac5a1fc 100644 --- a/src/main/kotlin/moe/nea/firmament/rei/SkyblockCraftingRecipeDynamicGenerator.kt +++ b/src/main/kotlin/moe/nea/firmament/rei/SkyblockCraftingRecipeDynamicGenerator.kt @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf * * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -8,6 +9,7 @@ package moe.nea.firmament.rei import io.github.moulberry.repo.data.NEUCraftingRecipe import io.github.moulberry.repo.data.NEUForgeRecipe +import io.github.moulberry.repo.data.NEUMobDropRecipe import io.github.moulberry.repo.data.NEURecipe import java.util.* import me.shedaniel.rei.api.client.registry.display.DynamicDisplayGenerator @@ -16,6 +18,7 @@ import me.shedaniel.rei.api.common.display.Display import me.shedaniel.rei.api.common.entry.EntryStack import moe.nea.firmament.rei.recipes.SBCraftingRecipe import moe.nea.firmament.rei.recipes.SBForgeRecipe +import moe.nea.firmament.rei.recipes.SBMobDropRecipe import moe.nea.firmament.repo.RepoManager @@ -27,6 +30,10 @@ val SkyblockForgeRecipeDynamicGenerator = neuDisplayGenerator { + SBMobDropRecipe(it) +} + inline fun neuDisplayGenerator(noinline mapper: (T) -> D) = object : DynamicDisplayGenerator { override fun getRecipeFor(entry: EntryStack<*>): Optional> { diff --git a/src/main/kotlin/moe/nea/firmament/rei/recipes/SBMobDropRecipe.kt b/src/main/kotlin/moe/nea/firmament/rei/recipes/SBMobDropRecipe.kt new file mode 100644 index 0000000..5af1f9e --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/rei/recipes/SBMobDropRecipe.kt @@ -0,0 +1,113 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.rei.recipes + +import io.github.moulberry.repo.data.NEUMobDropRecipe +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import me.shedaniel.rei.api.client.gui.Renderer +import me.shedaniel.rei.api.client.gui.widgets.Widget +import me.shedaniel.rei.api.client.gui.widgets.Widgets +import me.shedaniel.rei.api.client.registry.display.DisplayCategory +import me.shedaniel.rei.api.common.category.CategoryIdentifier +import me.shedaniel.rei.api.common.util.EntryStacks +import net.minecraft.block.Blocks +import net.minecraft.text.Text +import net.minecraft.util.Identifier +import moe.nea.firmament.Firmament +import moe.nea.firmament.gui.entity.EntityRenderer +import moe.nea.firmament.gui.entity.EntityWidget +import moe.nea.firmament.rei.SBItemEntryDefinition + +class SBMobDropRecipe(override val neuRecipe: NEUMobDropRecipe) : SBRecipe() { + override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.categoryIdentifier + + object Category : DisplayCategory { + override fun getCategoryIdentifier(): CategoryIdentifier = + CategoryIdentifier.of(Firmament.MOD_ID, "mob_drop_recipe") + + override fun getTitle(): Text = Text.literal("Mob Drops") + override fun getDisplayHeight(): Int { + return 100 + } + + override fun getIcon(): Renderer = EntryStacks.of(Blocks.ANVIL) + override fun setupDisplay(display: SBMobDropRecipe, bounds: Rectangle): List { + return buildList { + add(Widgets.createRecipeBase(bounds)) + val source = display.neuRecipe.render + val entity = if (source.startsWith("@")) { + EntityRenderer.constructEntity(Identifier(source.substring(1))) + } else { + EntityRenderer.applyModifiers(source, listOf()) + } + if (entity != null) { + val level = display.neuRecipe.level + val fullMobName = + if (level > 0) Text.translatable("firmament.recipe.mobs.name", level, display.neuRecipe.name) + else Text.translatable("firmament.recipe.mobs.name.nolevel", display.neuRecipe.name) + val tt = mutableListOf() + tt.add((fullMobName)) + tt.add(Text.literal("")) + if (display.neuRecipe.coins > 0) { + tt.add(Text.stringifiedTranslatable("firmament.recipe.mobs.coins", display.neuRecipe.coins)) + } + if (display.neuRecipe.combatExperience > 0) { + tt.add( + Text.stringifiedTranslatable( + "firmament.recipe.mobs.combat", + display.neuRecipe.combatExperience + ) + ) + } + if (display.neuRecipe.enchantingExperience > 0) { + tt.add( + Text.stringifiedTranslatable( + "firmament.recipe.mobs.exp", + display.neuRecipe.enchantingExperience + ) + ) + } + if (display.neuRecipe.extra != null) + display.neuRecipe.extra.mapTo(tt) { Text.literal(it) } + if (tt.size == 2) + tt.removeAt(1) + add( + Widgets.withTooltip( + EntityWidget(entity, Point(bounds.minX + 5, bounds.minY + 15)), + tt + ) + ) + } + add( + Widgets.createLabel(Point(bounds.minX + 15, bounds.minY + 5), Text.literal(display.neuRecipe.name)) + .leftAligned() + ) + var x = bounds.minX + 60 + var y = bounds.minY + 20 + for (drop in display.neuRecipe.drops) { + val lore = drop.extra.mapTo(mutableListOf()) { Text.literal(it) } + if (drop.chance != null) { + lore += listOf(Text.translatable("firmament.recipe.mobs.drops", drop.chance)) + } + val item = SBItemEntryDefinition.getEntry(drop.dropItem) + .value.copy(extraLore = lore) + add( + Widgets.createSlot(Point(x, y)).markOutput() + .entries(listOf(SBItemEntryDefinition.getEntry(item))) + ) + x += 18 + if (x > bounds.maxX - 30) { + x = bounds.minX + 60 + y += 18 + } + } + } + } + } + +} diff --git a/src/main/kotlin/moe/nea/firmament/repo/RepoModResourcePack.kt b/src/main/kotlin/moe/nea/firmament/repo/RepoModResourcePack.kt new file mode 100644 index 0000000..c511c90 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/repo/RepoModResourcePack.kt @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.repo + +import java.io.InputStream +import java.nio.file.Files +import java.nio.file.Path +import net.fabricmc.fabric.api.resource.ModResourcePack +import net.fabricmc.loader.api.FabricLoader +import net.fabricmc.loader.api.metadata.ModMetadata +import kotlin.io.path.exists +import kotlin.io.path.isRegularFile +import kotlin.io.path.relativeTo +import kotlin.streams.asSequence +import net.minecraft.resource.AbstractFileResourcePack +import net.minecraft.resource.InputSupplier +import net.minecraft.resource.ResourcePack +import net.minecraft.resource.ResourceType +import net.minecraft.resource.metadata.ResourceMetadataReader +import net.minecraft.util.Identifier +import net.minecraft.util.PathUtil + +class RepoModResourcePack(val basePath: Path) : ModResourcePack { + companion object { + fun append(packs: MutableList) { + packs.add(RepoModResourcePack(RepoDownloadManager.repoSavedLocation)) + } + } + + override fun close() { + } + + override fun openRoot(vararg segments: String): InputSupplier? { + return getFile(segments)?.let { InputSupplier.create(it) } + } + + fun getFile(segments: Array): Path? { + PathUtil.validatePath(*segments) + val path = segments.fold(basePath, Path::resolve) + if (!path.isRegularFile()) return null + return path + } + + override fun open(type: ResourceType?, id: Identifier): InputSupplier? { + if (type != ResourceType.CLIENT_RESOURCES) return null + if (id.namespace != "neurepo") return null + val file = getFile(id.path.split("/").toTypedArray()) + return file?.let { InputSupplier.create(it) } + } + + override fun findResources( + type: ResourceType?, + namespace: String, + prefix: String, + consumer: ResourcePack.ResultConsumer + ) { + if (namespace != "neurepo") return + if (type != ResourceType.CLIENT_RESOURCES) return + + val prefixPath = basePath.resolve(prefix) + if (!prefixPath.exists()) + return + Files.walk(prefixPath) + .asSequence() + .map { it.relativeTo(basePath) } + .forEach { + consumer.accept(Identifier.of("neurepo", it.toString()), InputSupplier.create(it)) + } + } + + override fun getNamespaces(type: ResourceType?): Set { + if (type != ResourceType.CLIENT_RESOURCES) return emptySet() + return setOf("neurepo") + } + + override fun parseMetadata(metaReader: ResourceMetadataReader): T? { + return AbstractFileResourcePack.parseMetadata( + metaReader, """ +{ + "pack": { + "pack_format": 12, + "description": "NEU Repo Resources" + } +} +""".trimIndent().byteInputStream() + ) + } + + override fun getName(): String { + return "NEU Repo Resources" + } + + override fun getFabricModMetadata(): ModMetadata { + return FabricLoader.getInstance().getModContainer("firmament") + .get().metadata + } +} diff --git a/src/main/kotlin/moe/nea/firmament/util/ItemUtil.kt b/src/main/kotlin/moe/nea/firmament/util/ItemUtil.kt index 4f8c90c..5a7a116 100644 --- a/src/main/kotlin/moe/nea/firmament/util/ItemUtil.kt +++ b/src/main/kotlin/moe/nea/firmament/util/ItemUtil.kt @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf * * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -14,6 +15,7 @@ import net.minecraft.text.Text fun ItemStack.appendLore(args: List) { + if (args.isEmpty()) return val compoundTag = getOrCreateSubNbt("display") val loreList = compoundTag.getOrCreateList("Lore", NbtString.STRING_TYPE) for (arg in args) { diff --git a/src/main/kotlin/moe/nea/firmament/util/LoadResource.kt b/src/main/kotlin/moe/nea/firmament/util/LoadResource.kt new file mode 100644 index 0000000..5a8bfbf --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/util/LoadResource.kt @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.util + +import java.io.InputStream +import kotlin.io.path.inputStream +import kotlin.jvm.optionals.getOrNull +import net.minecraft.util.Identifier +import moe.nea.firmament.repo.RepoDownloadManager + + +fun Identifier.openFirmamentResource(): InputStream { + val resource = MC.resourceManager.getResource(this).getOrNull() + if (resource == null) { + if (namespace == "neurepo") + return RepoDownloadManager.repoSavedLocation.resolve(path).inputStream() + error("Could not read resource $this") + } + return resource.inputStream +} + diff --git a/src/main/kotlin/moe/nea/firmament/util/assertions.kt b/src/main/kotlin/moe/nea/firmament/util/assertions.kt index 5505422..7f06955 100644 --- a/src/main/kotlin/moe/nea/firmament/util/assertions.kt +++ b/src/main/kotlin/moe/nea/firmament/util/assertions.kt @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf * * SPDX-License-Identifier: GPL-3.0-or-later */ diff --git a/src/main/kotlin/moe/nea/firmament/util/item/SkullItemData.kt b/src/main/kotlin/moe/nea/firmament/util/item/SkullItemData.kt index 4b72c5e..a061ee4 100644 --- a/src/main/kotlin/moe/nea/firmament/util/item/SkullItemData.kt +++ b/src/main/kotlin/moe/nea/firmament/util/item/SkullItemData.kt @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf * * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -18,8 +19,13 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.UseSerializers import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString -import net.minecraft.client.texture.PlayerSkinProvider +import net.minecraft.block.entity.SkullBlockEntity +import net.minecraft.item.ItemStack +import net.minecraft.item.Items +import net.minecraft.nbt.NbtCompound +import net.minecraft.nbt.NbtHelper import moe.nea.firmament.Firmament +import moe.nea.firmament.repo.set import moe.nea.firmament.util.assertTrueOr import moe.nea.firmament.util.json.DashlessUUIDSerializer import moe.nea.firmament.util.json.InstantAsLongSerializer @@ -46,6 +52,38 @@ fun GameProfile.setTextures(textures: MinecraftTexturesPayloadKt) { } private val propertyTextures = "textures" +fun String.padBase64(): String { + return this + "=".repeat((4 - (this.length % 4)) % 4) +} + +fun ItemStack.setEncodedSkullOwner(uuid: UUID, encodedData: String) { + assert(this.item == Items.PLAYER_HEAD) + val gameProfile = GameProfile(uuid, "LameGuy123") + gameProfile.properties.put(propertyTextures, Property(propertyTextures, encodedData.padBase64())) + val nbt: NbtCompound = this.orCreateNbt + nbt[SkullBlockEntity.SKULL_OWNER_KEY] = NbtHelper.writeGameProfile( + NbtCompound(), + gameProfile + ) +} + +val zeroUUID = UUID.fromString("d3cb85e2-3075-48a1-b213-a9bfb62360c1") +fun ItemStack.setSkullOwner(uuid: UUID, url: String) { + assert(this.item == Items.PLAYER_HEAD) + val gameProfile = GameProfile(uuid, "LameGuy123") + gameProfile.setTextures( + MinecraftTexturesPayloadKt( + mapOf(MinecraftProfileTexture.Type.SKIN to MinecraftProfileTextureKt(url)) + ) + ) + val nbt: NbtCompound = this.orCreateNbt + nbt[SkullBlockEntity.SKULL_OWNER_KEY] = NbtHelper.writeGameProfile( + NbtCompound(), + gameProfile + ) + +} + fun decodeProfileTextureProperty(property: Property): MinecraftTexturesPayloadKt? { assertTrueOr(property.name == propertyTextures) { return null } diff --git a/src/main/kotlin/moe/nea/firmament/util/render/TranslatedScissors.kt b/src/main/kotlin/moe/nea/firmament/util/render/TranslatedScissors.kt new file mode 100644 index 0000000..8f80f1b --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/util/render/TranslatedScissors.kt @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.util.render + +import org.joml.Vector4f +import net.minecraft.client.gui.DrawContext + +fun DrawContext.enableScissorWithTranslation(x1: Float, y1: Float, x2: Float, y2: Float) { + val pMat = matrices.peek().positionMatrix + val target = Vector4f() + + target.set(x1, y1, 0f, 1f) + target.mul(pMat) + val scissorX1 = target.x + val scissorY1 = target.y + + target.set(x2, y2, 0f, 1f) + target.mul(pMat) + val scissorX2 = target.x + val scissorY2 = target.y + + enableScissor(scissorX1.toInt(), scissorY1.toInt(), scissorX2.toInt(), scissorY2.toInt()) +} diff --git a/src/main/resources/assets/firmament/lang/en_us.json b/src/main/resources/assets/firmament/lang/en_us.json index 824a4a0..3c2df22 100644 --- a/src/main/resources/assets/firmament/lang/en_us.json +++ b/src/main/resources/assets/firmament/lang/en_us.json @@ -80,6 +80,12 @@ "firmament.config.waypoints": "Waypoints", "firmament.config.waypoints.temp-waypoint-duration": "Temporary Waypoint Duration", "firmament.recipe.forge.time": "Forging Time: %s", + "firmament.recipe.mobs.drops": "§e§lDrop Chance: %s", + "firmament.recipe.mobs.name": "§8[§7Lv %d§8] §c%s", + "firmament.recipe.mobs.name.nolevel": "§c%s", + "firmament.recipe.mobs.coins": "§eCoins: %s", + "firmament.recipe.mobs.combat": "§bCombat Experience: %s", + "firmament.recipe.mobs.exp": "§6Experience: %s", "firmament.pv.skills": "Skills", "firmament.pv.skills.farming": "Farming", "firmament.pv.skills.foraging": "Foraging", diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index fcb98c2..055a841 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -24,6 +24,9 @@ "main": [ "moe.nea.firmament.Firmament::onInitialize" ], + "mm_shedaniel:early_risers": [ + "moe.nea.firmament.init.EarlyRiser" + ], "client": [ "moe.nea.firmament.Firmament::onClientInitialize" ], diff --git a/src/main/resources/firmament.accesswidener b/src/main/resources/firmament.accesswidener index 92f4fe4..541b152 100644 --- a/src/main/resources/firmament.accesswidener +++ b/src/main/resources/firmament.accesswidener @@ -4,9 +4,13 @@ accessible class net/minecraft/client/render/RenderLayer$MultiPhaseParameters accessible class net/minecraft/client/font/TextRenderer$Drawer accessible class net/minecraft/client/render/model/ModelLoader$BakerImpl accessible method net/minecraft/client/render/model/ModelLoader$BakerImpl (Lnet/minecraft/client/render/model/ModelLoader;Ljava/util/function/BiFunction;Lnet/minecraft/util/Identifier;)V -#accessible field net/minecraft/client/texture/PlayerSkinProvider TEXTURES Ljava/lang/String; accessible field net/minecraft/client/network/ClientPlayNetworkHandler lastSeenMessagesCollector Lnet/minecraft/network/message/LastSeenMessagesCollector; accessible field net/minecraft/client/gui/hud/InGameHud SCOREBOARD_ENTRY_COMPARATOR Ljava/util/Comparator; accessible class net/minecraft/client/render/model/json/ModelOverride$Deserializer accessible class net/minecraft/client/render/model/json/ModelOverrideList$BakedOverride +accessible field net/minecraft/entity/mob/CreeperEntity CHARGED Lnet/minecraft/entity/data/TrackedData; +accessible method net/minecraft/entity/decoration/ArmorStandEntity setSmall (Z)V +accessible field net/minecraft/entity/passive/AbstractHorseEntity items Lnet/minecraft/inventory/SimpleInventory; +accessible field net/minecraft/entity/passive/AbstractHorseEntity SADDLED_FLAG I +accessible field net/minecraft/entity/passive/AbstractHorseEntity HORSE_FLAGS Lnet/minecraft/entity/data/TrackedData; -- cgit