Add mob drop viewer to item list

This commit is contained in:
Linnea Gräf
2024-03-01 21:31:48 +01:00
parent f28dee0ef3
commit 3bfec3033e
33 changed files with 1611 additions and 16 deletions

View File

@@ -73,6 +73,15 @@ repositories {
maven("https://maven.notenoughupdates.org/releases") maven("https://maven.notenoughupdates.org/releases")
} }
kotlin {
sourceSets.all {
languageSettings {
// languageVersion = "2.0"
enableLanguageFeature("BreakContinueInInlineLambdas")
}
}
}
val shadowMe by configurations.creating { val shadowMe by configurations.creating {
exclude(group = "org.jetbrains.kotlin") exclude(group = "org.jetbrains.kotlin")
exclude(group = "org.jetbrains.kotlinx") exclude(group = "org.jetbrains.kotlinx")
@@ -109,8 +118,10 @@ dependencies {
modImplementation(libs.modmenu) modImplementation(libs.modmenu)
modImplementation(libs.libgui) modImplementation(libs.libgui)
modImplementation(libs.moulconfig) modImplementation(libs.moulconfig)
modImplementation(libs.manninghamMills)
modCompileOnly(libs.explosiveenhancement) modCompileOnly(libs.explosiveenhancement)
include(libs.libgui) include(libs.libgui)
include(libs.manninghamMills)
include(libs.moulconfig) include(libs.moulconfig)

View File

@@ -27,7 +27,8 @@ jarvis = "1.1.1"
nealisp = "1.0.0" nealisp = "1.0.0"
explosiveenhancement = "1.2.2-1.20.x" explosiveenhancement = "1.2.2-1.20.x"
moulconfig = "3.0.0-beta.5" moulconfig = "3.0.0-beta.5"
manninghamMills = "2.4.1"
notenoughanimations = "ZLjUeuU8"
[libraries] [libraries]
minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" } 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" } jarvis_fabric = { module = "moe.nea.jarvis:jarvis-fabric", version.ref = "jarvis" }
nealisp = { module = "moe.nea:nealisp", version.ref = "nealisp" } nealisp = { module = "moe.nea:nealisp", version.ref = "nealisp" }
explosiveenhancement = { module = "maven.modrinth:explosive-enhancement", version.ref = "explosiveenhancement" } explosiveenhancement = { module = "maven.modrinth:explosive-enhancement", version.ref = "explosiveenhancement" }
manninghamMills = { module = "me.shedaniel:mm", version.ref = "manninghamMills" }
# Runtime: # Runtime:
notenoughanimations = { module = "maven.modrinth:not-enough-animations", version.ref = "notenoughanimations" }
hotswap = { module = "virtual.github.hotswapagent:hotswap-agent", version.ref = "hotswap_agent" } hotswap = { module = "virtual.github.hotswapagent:hotswap-agent", version.ref = "hotswap_agent" }
architectury_fabric = { module = "dev.architectury:architectury-fabric", version.ref = "architectury" } architectury_fabric = { module = "dev.architectury:architectury-fabric", version.ref = "architectury" }
rei_fabric = { module = "me.shedaniel:RoughlyEnoughItems-fabric", version.ref = "rei" } rei_fabric = { module = "me.shedaniel:RoughlyEnoughItems-fabric", version.ref = "rei" }
@@ -61,7 +63,12 @@ freecammod = { module = "maven.modrinth:freecam", version.ref = "freecammod" }
[bundles] [bundles]
dbus = ["dbus_java_core", "dbus_java_unixsocket"] dbus = ["dbus_java_core", "dbus_java_unixsocket"]
runtime_required = ["architectury_fabric", "rei_fabric"] runtime_required = [
"architectury_fabric",
"rei_fabric",
"notenoughanimations",
]
runtime_optional = [ runtime_optional = [
"devauth", "devauth",
# "freecammod", # "freecammod",

View File

@@ -0,0 +1,78 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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, "<init>") && Type.getMethodType(method.desc).equals(constructorDescriptor)) {
modifyConstructor(method, superClass);
return;
}
}
var node = new MethodNode(Opcodes.ASM9, "<init>", 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(), "<init>", constructorDescriptor.getDescriptor(), false));
// Return
method.instructions.add(new InsnNode(Opcodes.RETURN));
}
}

View File

@@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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<ModResourcePack> packs,
ResourceType type,
@Nullable String subPath,
CallbackInfo ci
) {
RepoModResourcePack.Companion.append(packs);
}
}

View File

@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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);
}

View File

@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
* *
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
@@ -52,7 +53,12 @@ fun firmamentCommand() = literal("firmament") {
val configObj = AllConfigsGui.allConfigs.find { it.name == config } val configObj = AllConfigsGui.allConfigs.find { it.name == config }
if (configObj == null) { 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 return@thenExecute
} }
val propertyObj = configObj.allOptions[property] val propertyObj = configObj.allOptions[property]
@@ -72,9 +78,11 @@ fun firmamentCommand() = literal("firmament") {
propertyObj.value = !propertyObj.value propertyObj.value = !propertyObj.value
configObj.save() configObj.save()
source.sendFeedback( source.sendFeedback(
Text.stringifiedTranslatable("firmament.command.toggle.toggled",configObj.labelText, Text.stringifiedTranslatable(
propertyObj.labelText, "firmament.command.toggle.toggled", configObj.labelText,
Text.translatable("firmament.toggle.${propertyObj.value}")) 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) Text.stringifiedTranslatable("firmament.price.bazaar.productid", bazaarData.productId.bazaarId)
) )
source.sendFeedback( 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( source.sendFeedback(
Text.stringifiedTranslatable("firmament.price.bazaar.buy.order", bazaarData.quickStatus.buyOrders) Text.stringifiedTranslatable(
"firmament.price.bazaar.buy.order",
bazaarData.quickStatus.buyOrders
)
) )
source.sendFeedback( 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( 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] val lowestBin = HypixelStaticData.lowestBin[itemName]
if (lowestBin != null) { if (lowestBin != null) {
source.sendFeedback( source.sendFeedback(
Text.stringifiedTranslatable("firmament.price.lowestbin", FirmFormatters.formatCurrency(lowestBin, 1)) Text.stringifiedTranslatable(
"firmament.price.lowestbin",
FirmFormatters.formatCurrency(lowestBin, 1)
)
) )
} }
} }

View File

@@ -0,0 +1,14 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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
}

View File

@@ -0,0 +1,202 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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 : Entity> t(entityType: EntityType<T>): () -> T {
return { entityType.create(fakeWorld)!! }
}
val entityIds: Map<String, () -> 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<String, EntityModifier> = 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<JsonObject>): 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()
}
}

View File

@@ -0,0 +1,40 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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<Element> {
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))
}
}

View File

@@ -0,0 +1,491 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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 <T> makeRegistry(registryWrapper: RegistryWrapper.Impl<T>, key: RegistryKey<out Registry<T>>): Registry<T> {
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<T> {
override fun get(key: RegistryKey<T>?): T? {
return registryWrapper.getOptional(key).getOrNull()?.value()
}
override fun iterator(): MutableIterator<T> {
return object : MutableIterator<T> {
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<out Registry<T>> {
return key
}
override fun getLifecycle(): Lifecycle {
return Lifecycle.stable()
}
override fun getIds(): MutableSet<Identifier> {
return idLookup.keys.mapTo(mutableSetOf()) { it.value }
}
override fun getEntrySet(): MutableSet<MutableMap.MutableEntry<RegistryKey<T>, T>> {
return map.entries
}
override fun getKeys(): MutableSet<RegistryKey<T>> {
return map.keys
}
override fun getRandom(random: Random?): Optional<RegistryEntry.Reference<T>> {
return registryWrapper.streamEntries().findFirst()
}
override fun containsId(id: Identifier?): Boolean {
return idLookup.containsKey(RegistryKey.of(key, id ?: return false))
}
override fun freeze(): Registry<T> {
return this
}
override fun getEntry(rawId: Int): Optional<RegistryEntry.Reference<T>> {
val x = inverseIdLookup[rawId] ?: return Optional.empty()
return Optional.of(RegistryEntry.Reference.standAlone(registryWrapper, x))
}
override fun streamEntries(): Stream<RegistryEntry.Reference<T>> {
return registryWrapper.streamEntries()
}
override fun streamTagsAndEntries(): Stream<Pair<TagKey<T>, RegistryEntryList.Named<T>>> {
return streamTags().map { Pair(it, getOrCreateEntryList(it)) }
}
override fun streamTags(): Stream<TagKey<T>> {
return registryWrapper.streamTagKeys()
}
override fun clearTags() {
}
override fun getEntryOwner(): RegistryEntryOwner<T> {
return registryWrapper
}
override fun getReadOnlyWrapper(): RegistryWrapper.Impl<T> {
return registryWrapper
}
override fun populateTags(tagEntries: MutableMap<TagKey<T>, MutableList<RegistryEntry<T>>>?) {
}
override fun getOrCreateEntryList(tag: TagKey<T>?): RegistryEntryList.Named<T> {
return getEntryList(tag).orElseGet { RegistryEntryList.of(registryWrapper, tag) }
}
override fun getEntryList(tag: TagKey<T>?): Optional<RegistryEntryList.Named<T>> {
return registryWrapper.getOptional(tag ?: return Optional.empty())
}
override fun getEntry(value: T): RegistryEntry<T> {
return registryWrapper.getOptional(inverseLookup[value]!!).get()
}
override fun getEntry(key: RegistryKey<T>?): Optional<RegistryEntry.Reference<T>> {
return registryWrapper.getOptional(key ?: return Optional.empty())
}
override fun createEntry(value: T): RegistryEntry.Reference<T> {
TODO()
}
override fun contains(key: RegistryKey<T>?): 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<RegistryKey<T>> {
return Optional.ofNullable(inverseLookup[entry ?: return Optional.empty()])
}
}
}
fun createDynamicRegistry(): DynamicRegistryManager.Immutable {
val wrapperLookup = BuiltinRegistries.createWrapperLookup()
return object : DynamicRegistryManager.Immutable {
override fun <E : Any?> getOptional(key: RegistryKey<out Registry<out E>>): Optional<Registry<E>> {
val lookup = wrapperLookup.getOptionalWrapper(key).getOrNull() ?: return Optional.empty()
val registry = makeRegistry(lookup, key as RegistryKey<out Registry<E>>)
return Optional.of(registry)
}
fun <T> entry(reg: RegistryKey<out Registry<T>>): DynamicRegistryManager.Entry<T> {
return DynamicRegistryManager.Entry(reg, getOptional(reg).get())
}
override fun streamAllRegistries(): Stream<DynamicRegistryManager.Entry<*>> {
return wrapperLookup.streamAllRegistryKeys()
.map { entry(it as RegistryKey<out Registry<Any>>) }
}
}
}
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<PlayerEntity> {
return emptyList()
}
override fun getBrightness(direction: Direction?, shaded: Boolean): Float {
return 1f
}
override fun getGeneratorStoredBiome(biomeX: Int, biomeY: Int, biomeZ: Int): RegistryEntry<Biome> {
return registryManager.get(RegistryKeys.BIOME).entryOf(BiomeKeys.PLAINS)
}
override fun getEnabledFeatures(): FeatureSet {
return FeatureFlags.VANILLA_FEATURES
}
class FakeTickScheduler<T> : QueryableTickScheduler<T> {
override fun scheduleTick(orderedTick: OrderedTick<T>?) {
}
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<Block> {
return FakeTickScheduler()
}
override fun getFluidTickScheduler(): QueryableTickScheduler<Fluid> {
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<SoundEvent>?,
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<SoundEvent>?,
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<Entity> {
override fun get(id: Int): Entity? {
return null
}
override fun get(uuid: UUID?): Entity? {
return null
}
override fun iterate(): MutableIterable<Entity> {
return mutableListOf()
}
override fun <U : Entity?> forEachIntersects(
filter: TypeFilter<Entity, U>?,
box: Box?,
consumer: LazyIterationConsumer<U>?
) {
}
override fun forEachIntersects(box: Box?, action: Consumer<Entity>?) {
}
override fun <U : Entity?> forEach(filter: TypeFilter<Entity, U>?, consumer: LazyIterationConsumer<U>?) {
}
}
override fun getEntityLookup(): EntityLookup<Entity> {
return FakeEntityLookup
}
}

View File

@@ -0,0 +1,55 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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
)
}
}

View File

@@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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
}
}

View File

@@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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
}
}

View File

@@ -0,0 +1,59 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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
}
}

View File

@@ -0,0 +1,66 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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)
}

View File

@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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
}
}

View File

@@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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
}
}

View File

@@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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
}
}

View File

@@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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
}
}

View File

@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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
}
}

View File

@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
* *
* SPDX-License-Identifier: GPL-3.0-or-later * 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.item.ItemStack
import net.minecraft.text.Text import net.minecraft.text.Text
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import moe.nea.firmament.rei.recipes.SBMobDropRecipe
class FirmamentReiPlugin : REIClientPlugin { class FirmamentReiPlugin : REIClientPlugin {
@@ -62,6 +64,7 @@ class FirmamentReiPlugin : REIClientPlugin {
override fun registerCategories(registry: CategoryRegistry) { override fun registerCategories(registry: CategoryRegistry) {
registry.add(SBCraftingRecipe.Category) registry.add(SBCraftingRecipe.Category)
registry.add(SBForgeRecipe.Category) registry.add(SBForgeRecipe.Category)
registry.add(SBMobDropRecipe.Category)
} }
override fun registerExclusionZones(zones: ExclusionZones) { override fun registerExclusionZones(zones: ExclusionZones) {
@@ -77,6 +80,7 @@ class FirmamentReiPlugin : REIClientPlugin {
SBForgeRecipe.Category.categoryIdentifier, SBForgeRecipe.Category.categoryIdentifier,
SkyblockForgeRecipeDynamicGenerator SkyblockForgeRecipeDynamicGenerator
) )
registry.registerDisplayGenerator(SBMobDropRecipe.Category.categoryIdentifier, SkyblockMobDropRecipeDynamicGenerator)
} }
override fun registerCollapsibleEntries(registry: CollapsibleEntryRegistry) { override fun registerCollapsibleEntries(registry: CollapsibleEntryRegistry) {

View File

@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
* *
* SPDX-License-Identifier: GPL-3.0-or-later * 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.FirmFormatters
import moe.nea.firmament.util.HypixelPetInfo import moe.nea.firmament.util.HypixelPetInfo
import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.appendLore
import moe.nea.firmament.util.petData import moe.nea.firmament.util.petData
import moe.nea.firmament.util.skyBlockId import moe.nea.firmament.util.skyBlockId
@@ -54,6 +56,7 @@ data class SBItemStack(
val neuItem: NEUItem?, val neuItem: NEUItem?,
val stackSize: Int, val stackSize: Int,
val petData: PetData?, val petData: PetData?,
val extraLore: List<Text> = emptyList(),
) { ) {
constructor(skyblockId: SkyblockId, petData: PetData) : this( constructor(skyblockId: SkyblockId, petData: PetData) : this(
skyblockId, 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) if (skyblockId == SkyblockId.COINS)
return@lazy ItemCache.coinItem(stackSize) return@lazy ItemCache.coinItem(stackSize).also { it.appendLore(extraLore) }
val replacementData = mutableMapOf<String, String>() val replacementData = mutableMapOf<String, String>()
injectReplacementDataForPets(replacementData) injectReplacementDataForPets(replacementData)
return@lazy neuItem.asItemStack(idHint = skyblockId, replacementData).copyWithCount(stackSize) return@lazy neuItem.asItemStack(idHint = skyblockId, replacementData).copyWithCount(stackSize)
.also { it.appendLore(extraLore) }
} }
fun asImmutableItemStack(): ItemStack { fun asImmutableItemStack(): ItemStack {

View File

@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
* *
* SPDX-License-Identifier: GPL-3.0-or-later * 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.NEUCraftingRecipe
import io.github.moulberry.repo.data.NEUForgeRecipe import io.github.moulberry.repo.data.NEUForgeRecipe
import io.github.moulberry.repo.data.NEUMobDropRecipe
import io.github.moulberry.repo.data.NEURecipe import io.github.moulberry.repo.data.NEURecipe
import java.util.* import java.util.*
import me.shedaniel.rei.api.client.registry.display.DynamicDisplayGenerator 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 me.shedaniel.rei.api.common.entry.EntryStack
import moe.nea.firmament.rei.recipes.SBCraftingRecipe import moe.nea.firmament.rei.recipes.SBCraftingRecipe
import moe.nea.firmament.rei.recipes.SBForgeRecipe import moe.nea.firmament.rei.recipes.SBForgeRecipe
import moe.nea.firmament.rei.recipes.SBMobDropRecipe
import moe.nea.firmament.repo.RepoManager import moe.nea.firmament.repo.RepoManager
@@ -27,6 +30,10 @@ val SkyblockForgeRecipeDynamicGenerator = neuDisplayGenerator<SBForgeRecipe, NEU
SBForgeRecipe(it) SBForgeRecipe(it)
} }
val SkyblockMobDropRecipeDynamicGenerator = neuDisplayGenerator<SBMobDropRecipe, NEUMobDropRecipe> {
SBMobDropRecipe(it)
}
inline fun <D : Display, reified T : NEURecipe> neuDisplayGenerator(noinline mapper: (T) -> D) = inline fun <D : Display, reified T : NEURecipe> neuDisplayGenerator(noinline mapper: (T) -> D) =
object : DynamicDisplayGenerator<D> { object : DynamicDisplayGenerator<D> {
override fun getRecipeFor(entry: EntryStack<*>): Optional<List<D>> { override fun getRecipeFor(entry: EntryStack<*>): Optional<List<D>> {

View File

@@ -0,0 +1,113 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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<SBMobDropRecipe> {
override fun getCategoryIdentifier(): CategoryIdentifier<SBMobDropRecipe> =
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<Widget> {
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<Text>()
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
}
}
}
}
}
}

View File

@@ -0,0 +1,101 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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<in ModResourcePack>) {
packs.add(RepoModResourcePack(RepoDownloadManager.repoSavedLocation))
}
}
override fun close() {
}
override fun openRoot(vararg segments: String): InputSupplier<InputStream>? {
return getFile(segments)?.let { InputSupplier.create(it) }
}
fun getFile(segments: Array<out String>): 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<InputStream>? {
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<String> {
if (type != ResourceType.CLIENT_RESOURCES) return emptySet()
return setOf("neurepo")
}
override fun <T> parseMetadata(metaReader: ResourceMetadataReader<T>): 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
}
}

View File

@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
* *
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
@@ -14,6 +15,7 @@ import net.minecraft.text.Text
fun ItemStack.appendLore(args: List<Text>) { fun ItemStack.appendLore(args: List<Text>) {
if (args.isEmpty()) return
val compoundTag = getOrCreateSubNbt("display") val compoundTag = getOrCreateSubNbt("display")
val loreList = compoundTag.getOrCreateList("Lore", NbtString.STRING_TYPE) val loreList = compoundTag.getOrCreateList("Lore", NbtString.STRING_TYPE)
for (arg in args) { for (arg in args) {

View File

@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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
}

View File

@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
* *
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */

View File

@@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
* *
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
@@ -18,8 +19,13 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString 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.Firmament
import moe.nea.firmament.repo.set
import moe.nea.firmament.util.assertTrueOr import moe.nea.firmament.util.assertTrueOr
import moe.nea.firmament.util.json.DashlessUUIDSerializer import moe.nea.firmament.util.json.DashlessUUIDSerializer
import moe.nea.firmament.util.json.InstantAsLongSerializer import moe.nea.firmament.util.json.InstantAsLongSerializer
@@ -46,6 +52,38 @@ fun GameProfile.setTextures(textures: MinecraftTexturesPayloadKt) {
} }
private val propertyTextures = "textures" 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? { fun decodeProfileTextureProperty(property: Property): MinecraftTexturesPayloadKt? {
assertTrueOr(property.name == propertyTextures) { return null } assertTrueOr(property.name == propertyTextures) { return null }

View File

@@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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())
}

View File

@@ -80,6 +80,12 @@
"firmament.config.waypoints": "Waypoints", "firmament.config.waypoints": "Waypoints",
"firmament.config.waypoints.temp-waypoint-duration": "Temporary Waypoint Duration", "firmament.config.waypoints.temp-waypoint-duration": "Temporary Waypoint Duration",
"firmament.recipe.forge.time": "Forging Time: %s", "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": "Skills",
"firmament.pv.skills.farming": "Farming", "firmament.pv.skills.farming": "Farming",
"firmament.pv.skills.foraging": "Foraging", "firmament.pv.skills.foraging": "Foraging",

View File

@@ -24,6 +24,9 @@
"main": [ "main": [
"moe.nea.firmament.Firmament::onInitialize" "moe.nea.firmament.Firmament::onInitialize"
], ],
"mm_shedaniel:early_risers": [
"moe.nea.firmament.init.EarlyRiser"
],
"client": [ "client": [
"moe.nea.firmament.Firmament::onClientInitialize" "moe.nea.firmament.Firmament::onClientInitialize"
], ],

View File

@@ -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/font/TextRenderer$Drawer
accessible class net/minecraft/client/render/model/ModelLoader$BakerImpl accessible class net/minecraft/client/render/model/ModelLoader$BakerImpl
accessible method net/minecraft/client/render/model/ModelLoader$BakerImpl <init> (Lnet/minecraft/client/render/model/ModelLoader;Ljava/util/function/BiFunction;Lnet/minecraft/util/Identifier;)V accessible method net/minecraft/client/render/model/ModelLoader$BakerImpl <init> (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/network/ClientPlayNetworkHandler lastSeenMessagesCollector Lnet/minecraft/network/message/LastSeenMessagesCollector;
accessible field net/minecraft/client/gui/hud/InGameHud SCOREBOARD_ENTRY_COMPARATOR Ljava/util/Comparator; 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/ModelOverride$Deserializer
accessible class net/minecraft/client/render/model/json/ModelOverrideList$BakedOverride 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;