1.21.3 WIP

This commit is contained in:
Linnea Gräf
2024-11-03 01:24:24 +01:00
parent 646843ba3b
commit 22f0cc59a2
105 changed files with 2854 additions and 2646 deletions

View File

@@ -18,7 +18,7 @@ SPDX-License-Identifier = "CC-BY-4.0"
SPDX-FileCopyrightText = ["Linnea Gräf <nea@nea.moe>", "Firmament Contributors"]
[[annotations]]
path = ["src/main/resources/assets/minecraft/shaders/core/**/*"]
path = ["src/main/resources/assets/firmament/shaders/**/*"]
SPDX-License-Identifier = "GPL-3.0-or-later"
SPDX-FileCopyrightText = ["Linnea Gräf <nea@nea.moe>", "Firmament Contributors"]

View File

@@ -21,7 +21,9 @@ plugins {
alias(libs.plugins.kotlin.plugin.serialization)
alias(libs.plugins.kotlin.plugin.powerassert)
alias(libs.plugins.kotlin.plugin.ksp)
alias(libs.plugins.loom)
// alias(libs.plugins.loom)
// TODO: use arch loom once they update to 1.8
id("fabric-loom") version "1.8.9"
id("com.github.johnrengelman.shadow") version "8.1.1"
id("moe.nea.licenseextractificator")
id("moe.nea.mc-auto-translations") version "0.0.1"
@@ -104,16 +106,12 @@ fun String.capitalizeN() = replaceFirstChar { it.uppercaseChar() }
val unpackAllJars by tasks.registering
fun innerJarsOf(name: String, dependency: Dependency): Provider<FileTree> {
val task = tasks.create("unpackInnerJarsFor${name.capitalizeN()}", InnerJarsUnpacker::class) {
doFirst {
println("Unpacking JARs for $name")
}
this.inputJars.setFrom(files(configurations.detachedConfiguration(dependency)))
this.outputDir.set(layout.buildDirectory.dir("unpackedJars/$name").also {
it.get().asFile.mkdirs()
})
}
unpackAllJars { dependsOn(task) }
println("Constructed innerJars task: ${project.files(task).asFileTree.toList().map {it to it.exists()}}")
return project.provider {
project.files(task).asFileTree
}
@@ -125,15 +123,32 @@ val collectTranslations by tasks.registering(CollectTranslations::class) {
}
val compatSourceSets: MutableSet<SourceSet> = mutableSetOf()
fun createIsolatedSourceSet(name: String, path: String = "compat/$name"): SourceSet {
fun createIsolatedSourceSet(name: String, path: String = "compat/$name", isEnabled: Boolean = true): SourceSet {
val ss = sourceSets.create(name) {
this.java.setSrcDirs(listOf(layout.projectDirectory.dir("src/$path/java")))
this.kotlin.setSrcDirs(listOf(layout.projectDirectory.dir("src/$path/java")))
if (isEnabled) {
this.java.setSrcDirs(listOf(layout.projectDirectory.dir("src/$path/java")))
this.kotlin.setSrcDirs(listOf(layout.projectDirectory.dir("src/$path/java")))
} else {
this.java.setSrcDirs(listOf<File>())
this.kotlin.setSrcDirs(listOf<File>())
}
}
val mainSS = sourceSets.main.get()
val upperName = ss.name.capitalizeN()
afterEvaluate {
tasks.named("ksp${upperName}Kotlin", KspTaskJvm::class) {
this.options.add(SubpluginOption("apoption", "firmament.sourceset=${ss.name}"))
}
tasks.named("compile${upperName}Kotlin", KotlinCompile::class) {
this.enabled = isEnabled
}
tasks.named("compile${upperName}Java", JavaCompile::class) {
this.enabled = isEnabled
}
}
compatSourceSets.add(ss)
loom.createRemapConfigurations(ss)
val mainSS = sourceSets.main.get()
val upperName = ss.name.capitalizeN()
if (!isEnabled) return ss
configurations {
(ss.implementationConfigurationName) {
extendsFrom(getByName(mainSS.compileClasspathConfigurationName))
@@ -148,11 +163,6 @@ fun createIsolatedSourceSet(name: String, path: String = "compat/$name"): Source
extendsFrom(ksp.get())
}
}
afterEvaluate {
tasks.named("ksp${upperName}Kotlin", KspTaskJvm::class) {
this.options.add(SubpluginOption("apoption", "firmament.sourceset=${ss.name}"))
}
}
dependencies {
runtimeOnly(ss.output)
(ss.implementationConfigurationName)(sourceSets.main.get().output)
@@ -195,14 +205,15 @@ val nonModImplentation by configurations.creating {
}
val configuredSourceSet = createIsolatedSourceSet("configured")
val configuredSourceSet = createIsolatedSourceSet("configured",
isEnabled = false) // Wait for update (also low prio, because configured sucks)
val sodiumSourceSet = createIsolatedSourceSet("sodium")
val citResewnSourceSet = createIsolatedSourceSet("citresewn")
val citResewnSourceSet = createIsolatedSourceSet("citresewn", isEnabled = false) // TODO: Wait for update
val yaclSourceSet = createIsolatedSourceSet("yacl")
val explosiveEnhancementSourceSet = createIsolatedSourceSet("explosiveEnhancement")
val wildfireGenderSourceSet = createIsolatedSourceSet("wildfireGender")
val explosiveEnhancementSourceSet = createIsolatedSourceSet("explosiveEnhancement", isEnabled = false) // TODO: wait for their port
val wildfireGenderSourceSet = createIsolatedSourceSet("wildfireGender", isEnabled = false) // TODO: wait on their port
val modmenuSourceSet = createIsolatedSourceSet("modmenu")
val reiSourceSet = createIsolatedSourceSet("rei")
val reiSourceSet = createIsolatedSourceSet("rei") // TODO: read through https://hackmd.io/@shedaniel/rei17_primer
dependencies {
// Minecraft dependencies
@@ -299,7 +310,7 @@ loom {
compatSourceSets.joinToString(File.pathSeparator) {
File(it.output.classesDirs.asPath).absolutePath
})
property("mixin.debug", "true")
property("mixin.debug.export", "true")
parseEnvFile(file(".env")).forEach { (t, u) ->
environmentVariable(t, u)
@@ -360,7 +371,7 @@ tasks.shadowJar {
}
tasks.remapJar {
injectAccessWidener.set(true)
// injectAccessWidener.set(true)
inputFile.set(tasks.shadowJar.flatMap { it.archiveFile })
dependsOn(tasks.shadowJar)
archiveClassifier.set("")

View File

@@ -3,36 +3,36 @@
# SPDX-License-Identifier: CC0-1.0
[versions]
minecraft = "1.21"
minecraft = "1.21.3"
# Update from https://kotlinlang.org/
kotlin = "2.0.20"
kotlin = "2.0.21"
# Update from https://github.com/google/ksp/releases
kotlin_ksp = "2.0.20-1.0.24"
kotlin_ksp = "2.0.21-1.0.26"
# Update from https://linkie.shedaniel.me/dependencies?loader=fabric
fabric_loader = "0.16.3"
fabric_api = "0.100.4+1.21"
fabric_kotlin = "1.11.0+kotlin.2.0.0"
yarn = "1.21+build.7"
rei = "16.0.729"
modmenu = "11.0.1"
architectury = "13.0.3"
fabric_loader = "0.16.9"
fabric_api = "0.107.0+1.21.3"
fabric_kotlin = "1.12.3+kotlin.2.0.21"
yarn = "1.21.3+build.2"
rei = "17.0.789"
modmenu = "12.0.0-beta.1"
architectury = "14.0.4"
# Update from https://maven.architectury.dev/dev/architectury/loom/dev.architectury.loom.gradle.plugin/
loom = "1.7.412"
loom = "1.7.414"
# Update from https://modrinth.com/mod/qolify/versions?l=fabric
qolify = "1.6.0-1.21.1"
# Update from https://modrinth.com/mod/sodium/versions?l=fabric
sodium = "mc1.21-0.5.11"
sodium = "mc1.21.3-0.6.0-beta.4-fabric"
# Update from https://modrinth.com/mod/freecam/versions?l=fabric
freecammod = "vomskVK3"
# Update from https://modrinth.com/mod/no-chat-reports/versions?l=fabric
ncr = "Fabric-1.21-v2.8.0"
ncr = "Fabric-1.21.3-v2.10.0"
# Update from https://modrinth.com/mod/female-gender/versions?l=fabric
femalegender = "kJmjQvAS"
@@ -49,7 +49,7 @@ citresewn = "1.2.0+1.21"
devauth = "1.2.0"
# Update from https://ktor.io/
ktor = "2.3.12"
ktor = "3.0.1"
# Update from https://repo.nea.moe/#/releases/moe/nea/neurepoparser
neurepoparser = "1.6.0"
@@ -58,13 +58,13 @@ neurepoparser = "1.6.0"
hotswap_agent = "1.4.2-SNAPSHOT"
# Update from https://github.com/LlamaLad7/MixinExtras/tags
mixinextras = "0.3.5"
mixinextras = "0.4.1"
jarvis = "1.1.3"
nealisp = "1.0.0"
nealisp = "1.1.0"
# Update from https://github.com/NotEnoughUpdates/MoulConfig/tags
moulconfig = "3.0.0-beta.15"
moulconfig = "3.0.0"
# Update from https://www.curseforge.com/minecraft/mc-mods/configured/files/all?page=1&pageSize=20
configured = "5441234"
@@ -77,7 +77,7 @@ hypixelmodapi_fabric = "1.0.1+build.1+mc1.21"
manninghamMills = "2.4.1"
# Update from https://docs.isxander.dev/yet-another-config-lib/installing-yacl
yacl = "3.5.0+1.21-fabric"
yacl = "3.6.1+1.21.2-fabric"
# Update from https://maven.shedaniel.me/me/shedaniel/cloth/basic-math/0.6.1/
basicMath = "0.6.1"
@@ -120,10 +120,8 @@ basicMath = { module = "me.shedaniel.cloth:basic-math", version.ref = "basicMath
[bundles]
runtime_required = [
"architectury_fabric",
"rei_fabric",
"notenoughanimations",
"femalegender",
# "notenoughanimations",
"hypixelmodapi_fabric",
]
runtime_optional = [
@@ -132,7 +130,7 @@ runtime_optional = [
# "sodium",
# "qolify",
"ncr",
"citresewn",
# "citresewn",
]
[plugins]

View File

@@ -4,32 +4,32 @@ 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 moe.nea.firmament.gui.entity.EntityRenderer
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.Element
import net.minecraft.entity.LivingEntity
import moe.nea.firmament.gui.entity.EntityRenderer
class EntityWidget(val entity: LivingEntity, val point: Point) : WidgetWithBounds() {
override fun children(): List<Element> {
return emptyList()
}
override fun children(): List<Element> {
return emptyList()
}
var hasErrored = false
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 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))
}
override fun getBounds(): Rectangle {
return Rectangle(point, Dimension(50, 80))
}
}

View File

@@ -0,0 +1,10 @@
package moe.nea.firmament.compat.rei
import me.shedaniel.rei.api.common.entry.type.EntryTypeRegistry
import me.shedaniel.rei.api.common.plugins.REICommonPlugin
class FirmamentReiCommonPlugin : REICommonPlugin {
override fun registerEntryTypes(registry: EntryTypeRegistry) {
registry.register(FirmamentReiPlugin.SKYBLOCK_ITEM_TYPE_ID, SBItemEntryDefinition)
}
}

View File

@@ -11,7 +11,6 @@ import me.shedaniel.rei.api.client.registry.screen.ScreenRegistry
import me.shedaniel.rei.api.client.registry.transfer.TransferHandler
import me.shedaniel.rei.api.client.registry.transfer.TransferHandlerRegistry
import me.shedaniel.rei.api.common.entry.EntryStack
import me.shedaniel.rei.api.common.entry.type.EntryTypeRegistry
import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.gui.screen.ingame.GenericContainerScreen
@@ -20,18 +19,17 @@ import net.minecraft.item.ItemStack
import net.minecraft.text.Text
import net.minecraft.util.ActionResult
import net.minecraft.util.Identifier
import moe.nea.firmament.events.HandledScreenPushREIEvent
import moe.nea.firmament.features.inventory.CraftingOverlay
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen
import moe.nea.firmament.compat.rei.recipes.SBCraftingRecipe
import moe.nea.firmament.compat.rei.recipes.SBEssenceUpgradeRecipe
import moe.nea.firmament.compat.rei.recipes.SBForgeRecipe
import moe.nea.firmament.compat.rei.recipes.SBKatRecipe
import moe.nea.firmament.compat.rei.recipes.SBMobDropRecipe
import moe.nea.firmament.events.HandledScreenPushREIEvent
import moe.nea.firmament.features.inventory.CraftingOverlay
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen
import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.ScreenUtil
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.guessRecipeId
import moe.nea.firmament.util.skyblockId
@@ -74,9 +72,6 @@ class FirmamentReiPlugin : REIClientPlugin {
})
}
override fun registerEntryTypes(registry: EntryTypeRegistry) {
registry.register(SKYBLOCK_ITEM_TYPE_ID, SBItemEntryDefinition)
}
override fun registerCategories(registry: CategoryRegistry) {
registry.add(SBCraftingRecipe.Category)

View File

@@ -21,6 +21,7 @@ class ScreenRegistryHoveredItemStackProvider : HoveredItemStackProvider {
return entryStack.value as? ItemStack ?: entryStack.cheatsAs().value
}
}
@AutoService(HoveredItemStackProvider::class)
@CompatLoader.RequireMod("roughlyenoughitems")
class OverlayHoveredItemStackProvider : HoveredItemStackProvider {

View File

@@ -25,163 +25,128 @@ import net.minecraft.client.render.LightmapTextureManager
import net.minecraft.client.render.OverlayTexture
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.json.ModelTransformationMode
import net.minecraft.client.texture.SpriteAtlasTexture
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import net.minecraft.item.tooltip.TooltipType
import net.minecraft.item.ModelTransformationMode
import moe.nea.firmament.compat.rei.FirmamentReiPlugin.Companion.asItemEntry
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.mc.loreAccordingToNbt
object NEUItemEntryRenderer : EntryRenderer<SBItemStack>, BatchedEntryRenderer<SBItemStack, BakedModel> {
override fun render(
entry: EntryStack<SBItemStack>,
context: DrawContext,
bounds: Rectangle,
mouseX: Int,
mouseY: Int,
delta: Float
) {
entry.asItemEntry().render(context, bounds, mouseX, mouseY, delta)
}
override fun render(
entry: EntryStack<SBItemStack>,
context: DrawContext,
bounds: Rectangle,
mouseX: Int,
mouseY: Int,
delta: Float
) {
entry.asItemEntry().render(context, bounds, mouseX, mouseY, delta)
}
val minecraft = MinecraftClient.getInstance()
val minecraft = MinecraftClient.getInstance()
override fun getTooltip(entry: EntryStack<SBItemStack>, tooltipContext: TooltipContext): Tooltip? {
val stack = entry.value.asImmutableItemStack()
val lore = stack.getTooltip(
Item.TooltipContext.DEFAULT,
null,
TooltipType.BASIC
)
return Tooltip.create(lore)
}
override fun getTooltip(entry: EntryStack<SBItemStack>, tooltipContext: TooltipContext): Tooltip? {
val stack = entry.value.asImmutableItemStack()
override fun getExtraData(entry: EntryStack<SBItemStack>): BakedModel {
return minecraft.itemRenderer.getModel(entry.asItemEntry().value, minecraft.world, minecraft.player, 0)
}
val lore = mutableListOf(stack.displayNameAccordingToNbt)
lore.addAll(stack.loreAccordingToNbt)
override fun getBatchIdentifier(entry: EntryStack<SBItemStack>?, bounds: Rectangle?, extraData: BakedModel): Int {
return 1738923 + if (extraData.isSideLit) 1 else 0
}
// TODO: tags aren't sent as early now so some tooltip components that use tags will crash the game
// stack.getTooltip(
// Item.TooltipContext.create(
// tooltipContext.vanillaContext().registryLookup
// ?: MC.defaultRegistries
// ),
// MC.player,
// TooltipType.BASIC
// )
return Tooltip.create(lore)
}
override fun startBatch(
entry: EntryStack<SBItemStack>,
model: BakedModel,
graphics: DrawContext,
delta: Float
) {
val modelViewStack = RenderSystem.getModelViewStack()
modelViewStack.pushMatrix()
modelViewStack.scale(20.0f, 20.0f, 1.0f)
RenderSystem.applyModelViewMatrix()
setupGL(model)
}
override fun getExtraData(entry: EntryStack<SBItemStack>): BakedModel {
return MC.itemRenderer.getModel(entry.asItemEntry().value,
MC.world,
MC.player, 0)
fun setupGL(model: BakedModel) {
minecraft.textureManager.getTexture(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE)
.setFilter(false, false)
RenderSystem.setShaderTexture(0, SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE)
RenderSystem.enableBlend()
RenderSystem.blendFunc(SrcFactor.SRC_ALPHA, DstFactor.ONE_MINUS_SRC_ALPHA)
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f)
val sideLit = model.isSideLit
if (!sideLit) {
DiffuseLighting.disableGuiDepthLighting()
}
}
}
override fun renderBase(
entry: EntryStack<SBItemStack>,
model: BakedModel,
graphics: DrawContext,
immediate: VertexConsumerProvider.Immediate,
bounds: Rectangle,
mouseX: Int,
mouseY: Int,
delta: Float
) {
if (entry.isEmpty) return
val value = entry.asItemEntry().value
graphics.matrices.push()
graphics.matrices.translate(bounds.centerX.toFloat() / 20.0f, bounds.centerY.toFloat() / 20.0f, 0.0f)
graphics.matrices.scale(
bounds.getWidth().toFloat() / 20.0f,
-(bounds.getWidth() + bounds.getHeight()).toFloat() / 2.0f / 20.0f,
1.0f
)
minecraft
.itemRenderer
.renderItem(
value,
ModelTransformationMode.GUI,
false,
graphics.matrices,
immediate,
LightmapTextureManager.MAX_LIGHT_COORDINATE,
OverlayTexture.DEFAULT_UV,
model
)
graphics.matrices.pop()
override fun getBatchIdentifier(entry: EntryStack<SBItemStack>, bounds: Rectangle?, extraData: BakedModel): Int {
return 1738923 + if (extraData.isSideLit) 1 else 0
}
}
override fun afterBase(
entry: EntryStack<SBItemStack>,
model: BakedModel,
graphics: DrawContext,
delta: Float
) {
RenderSystem.getModelViewStack().popMatrix()
RenderSystem.applyModelViewMatrix()
this.endGL(model)
}
override fun startBatch(entryStack: EntryStack<SBItemStack>, e: BakedModel, drawContext: DrawContext, v: Float) {
MC.textureManager.getTexture(SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE)
.setFilter(false, false)
RenderSystem.setShaderTexture(0, SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE)
RenderSystem.enableBlend()
RenderSystem.blendFunc(SrcFactor.SRC_ALPHA, DstFactor.ONE_MINUS_SRC_ALPHA)
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f)
if (!e.isSideLit) {
DiffuseLighting.disableGuiDepthLighting()
}
}
fun endGL(model: BakedModel) {
RenderSystem.enableDepthTest()
val sideLit = model.isSideLit
if (!sideLit) {
DiffuseLighting.enableGuiDepthLighting()
}
}
override fun renderBase(
entryStack: EntryStack<SBItemStack>,
model: BakedModel,
drawContext: DrawContext,
immediate: VertexConsumerProvider.Immediate,
bounds: Rectangle,
i: Int,
i1: Int,
v: Float
) {
if (entryStack.isEmpty) return
drawContext.matrices.push()
drawContext.matrices.translate(bounds.centerX.toDouble(), bounds.centerY.toDouble(), 0.0)
// TODO: check the scaling here again
drawContext.matrices.scale(
bounds.width.toFloat(),
(bounds.height + bounds.height) / -2F,
(bounds.width + bounds.height) / 2f)
MC.itemRenderer.renderItem(
entryStack.value.asImmutableItemStack(),
ModelTransformationMode.GUI,
false, drawContext.matrices,
immediate, LightmapTextureManager.MAX_LIGHT_COORDINATE,
OverlayTexture.DEFAULT_UV,
model
)
drawContext.matrices.pop()
}
override fun renderOverlay(
entry: EntryStack<SBItemStack>,
extraData: BakedModel,
graphics: DrawContext,
immediate: VertexConsumerProvider.Immediate,
bounds: Rectangle,
mouseX: Int,
mouseY: Int,
delta: Float
) {
val modelViewStack = RenderSystem.getModelViewStack()
modelViewStack.pushMatrix()
modelViewStack.mul(graphics.matrices.peek().positionMatrix)
modelViewStack.translate(bounds.x.toFloat(), bounds.y.toFloat(), 0.0f)
modelViewStack.scale(
bounds.width.toFloat() / 16.0f,
-(bounds.getWidth() + bounds.getHeight()).toFloat() / 2.0f / 16.0f,
1.0f
)
RenderSystem.applyModelViewMatrix()
renderOverlay(DrawContext(minecraft, graphics.vertexConsumers), entry.asItemEntry())
modelViewStack.popMatrix()
RenderSystem.applyModelViewMatrix()
}
override fun afterBase(entryStack: EntryStack<SBItemStack>?, e: BakedModel, drawContext: DrawContext?, v: Float) {
RenderSystem.enableDepthTest()
if (!e.isSideLit)
DiffuseLighting.enableGuiDepthLighting()
}
fun renderOverlay(graphics: DrawContext, entry: EntryStack<ItemStack>) {
if (!entry.isEmpty) {
graphics.drawItemInSlot(MinecraftClient.getInstance().textRenderer, entry.value, 0, 0, null)
}
}
override fun renderOverlay(
entryStack: EntryStack<SBItemStack>,
e: BakedModel,
drawContext: DrawContext,
immediate: VertexConsumerProvider.Immediate,
bounds: Rectangle,
i: Int,
i1: Int,
v: Float
) {
if (entryStack.isEmpty) return
val modelViewStack = RenderSystem.getModelViewStack()
modelViewStack.pushMatrix()
modelViewStack.mul(drawContext.matrices.peek().positionMatrix)
modelViewStack.translate(bounds.x.toFloat(), bounds.y.toFloat(), 0F)
modelViewStack.scale(bounds.width / 16.0f,
(bounds.width + bounds.height) / 2.0f / 16.0f,
1.0f) // TODO: weird scale again
drawContext.drawStackOverlay(MC.font, entryStack.value.asImmutableItemStack(), 0, 0, null)
modelViewStack.popMatrix()
}
override fun endBatch(
entry: EntryStack<SBItemStack>?,
extraData: BakedModel?,
graphics: DrawContext?,
delta: Float
) {
}
override fun endBatch(entryStack: EntryStack<SBItemStack>?, e: BakedModel?, drawContext: DrawContext?, v: Float) {
}
}

View File

@@ -1,30 +1,17 @@
package moe.nea.firmament.compat.rei
import com.mojang.serialization.Codec
import me.shedaniel.rei.api.common.entry.EntrySerializer
import me.shedaniel.rei.api.common.entry.EntryStack
import net.minecraft.nbt.NbtCompound
import net.minecraft.network.RegistryByteBuf
import net.minecraft.network.codec.PacketCodec
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.SkyblockId
object NEUItemEntrySerializer : EntrySerializer<SBItemStack> {
const val SKYBLOCK_ID_ENTRY = "SKYBLOCK_ID"
const val SKYBLOCK_ITEM_COUNT = "SKYBLOCK_ITEM_COUNT"
override fun codec(): Codec<SBItemStack> {
return SBItemStack.CODEC
}
override fun supportSaving(): Boolean = true
override fun supportReading(): Boolean = true
override fun read(tag: NbtCompound): SBItemStack {
val id = SkyblockId(tag.getString(SKYBLOCK_ID_ENTRY))
val count = if (tag.contains(SKYBLOCK_ITEM_COUNT)) tag.getInt(SKYBLOCK_ITEM_COUNT) else 1
return SBItemStack(id, count)
}
override fun save(entry: EntryStack<SBItemStack>, value: SBItemStack): NbtCompound {
return NbtCompound().apply {
putString(SKYBLOCK_ID_ENTRY, value.skyblockId.neuItem)
putInt(SKYBLOCK_ITEM_COUNT, value.getStackSize())
}
}
override fun streamCodec(): PacketCodec<RegistryByteBuf, SBItemStack> {
return SBItemStack.PACKET_CODEC.cast()
}
}

View File

@@ -1,10 +1,8 @@
package moe.nea.firmament.compat.rei
import me.shedaniel.math.Point
operator fun Point.plus(other: Point): Point = Point(
this.x + other.x,
this.y + other.y,
this.x + other.x,
this.y + other.y,
)

View File

@@ -1,5 +1,3 @@
package moe.nea.firmament.compat.rei.recipes
import io.github.moulberry.repo.data.NEUCraftingRecipe
@@ -11,6 +9,8 @@ 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.display.Display
import me.shedaniel.rei.api.common.display.DisplaySerializer
import me.shedaniel.rei.api.common.util.EntryStacks
import net.minecraft.block.Blocks
import net.minecraft.text.Text
@@ -18,38 +18,38 @@ import moe.nea.firmament.Firmament
import moe.nea.firmament.compat.rei.SBItemEntryDefinition
class SBCraftingRecipe(override val neuRecipe: NEUCraftingRecipe) : SBRecipe() {
override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.catIdentifier
override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.catIdentifier
object Category : DisplayCategory<SBCraftingRecipe> {
val catIdentifier = CategoryIdentifier.of<SBCraftingRecipe>(Firmament.MOD_ID, "crafing_recipe")
override fun getCategoryIdentifier(): CategoryIdentifier<out SBCraftingRecipe> = catIdentifier
object Category : DisplayCategory<SBCraftingRecipe> {
val catIdentifier = CategoryIdentifier.of<SBCraftingRecipe>(Firmament.MOD_ID, "crafing_recipe")
override fun getCategoryIdentifier(): CategoryIdentifier<out SBCraftingRecipe> = catIdentifier
override fun getTitle(): Text = Text.literal("SkyBlock Crafting")
override fun getTitle(): Text = Text.literal("SkyBlock Crafting")
override fun getIcon(): Renderer = EntryStacks.of(Blocks.CRAFTING_TABLE)
override fun setupDisplay(display: SBCraftingRecipe, bounds: Rectangle): List<Widget> {
val point = Point(bounds.centerX - 58, bounds.centerY - 27)
return buildList {
add(Widgets.createRecipeBase(bounds))
add(Widgets.createArrow(Point(point.x + 60, point.y + 18)))
add(Widgets.createResultSlotBackground(Point(point.x + 95, point.y + 19)))
for (i in 0 until 3) {
for (j in 0 until 3) {
val slot = Widgets.createSlot(Point(point.x + 1 + i * 18, point.y + 1 + j * 18)).markInput()
add(slot)
val item = display.neuRecipe.inputs[i + j * 3]
if (item == NEUIngredient.SENTINEL_EMPTY) continue
slot.entry(SBItemEntryDefinition.getEntry(item)) // TODO: make use of stackable item entries
}
}
add(
Widgets.createSlot(Point(point.x + 95, point.y + 19))
.entry(SBItemEntryDefinition.getEntry(display.neuRecipe.output))
.disableBackground().markOutput()
)
}
}
override fun getIcon(): Renderer = EntryStacks.of(Blocks.CRAFTING_TABLE)
override fun setupDisplay(display: SBCraftingRecipe, bounds: Rectangle): List<Widget> {
val point = Point(bounds.centerX - 58, bounds.centerY - 27)
return buildList {
add(Widgets.createRecipeBase(bounds))
add(Widgets.createArrow(Point(point.x + 60, point.y + 18)))
add(Widgets.createResultSlotBackground(Point(point.x + 95, point.y + 19)))
for (i in 0 until 3) {
for (j in 0 until 3) {
val slot = Widgets.createSlot(Point(point.x + 1 + i * 18, point.y + 1 + j * 18)).markInput()
add(slot)
val item = display.neuRecipe.inputs[i + j * 3]
if (item == NEUIngredient.SENTINEL_EMPTY) continue
slot.entry(SBItemEntryDefinition.getEntry(item)) // TODO: make use of stackable item entries
}
}
add(
Widgets.createSlot(Point(point.x + 95, point.y + 19))
.entry(SBItemEntryDefinition.getEntry(display.neuRecipe.output))
.disableBackground().markOutput()
)
}
}
}
}
}

View File

@@ -1,4 +1,3 @@
package moe.nea.firmament.compat.rei.recipes
import me.shedaniel.math.Point
@@ -16,47 +15,47 @@ import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.SkyblockId
class SBEssenceUpgradeRecipe(override val neuRecipe: EssenceRecipeProvider.EssenceUpgradeRecipe) : SBRecipe() {
object Category : DisplayCategory<SBEssenceUpgradeRecipe> {
override fun getCategoryIdentifier(): CategoryIdentifier<SBEssenceUpgradeRecipe> =
CategoryIdentifier.of(Firmament.MOD_ID, "essence_upgrade")
object Category : DisplayCategory<SBEssenceUpgradeRecipe> {
override fun getCategoryIdentifier(): CategoryIdentifier<SBEssenceUpgradeRecipe> =
CategoryIdentifier.of(Firmament.MOD_ID, "essence_upgrade")
override fun getTitle(): Text {
return Text.literal("Essence Upgrades")
}
override fun getTitle(): Text {
return Text.literal("Essence Upgrades")
}
override fun getIcon(): Renderer {
return SBItemEntryDefinition.getEntry(SkyblockId("ESSENCE_WITHER"))
}
override fun getIcon(): Renderer {
return SBItemEntryDefinition.getEntry(SkyblockId("ESSENCE_WITHER"))
}
override fun setupDisplay(display: SBEssenceUpgradeRecipe, bounds: Rectangle): List<Widget> {
val recipe = display.neuRecipe
val list = mutableListOf<Widget>()
list.add(Widgets.createRecipeBase(bounds))
list.add(Widgets.createSlot(Point(bounds.minX + 12, bounds.centerY - 8 - 18 / 2))
.markInput()
.entry(SBItemEntryDefinition.getEntry(SBItemStack(recipe.itemId).copy(stars = recipe.starCountAfter - 1))))
list.add(Widgets.createSlot(Point(bounds.minX + 12, bounds.centerY - 8 + 18 / 2))
.markInput()
.entry(SBItemEntryDefinition.getEntry(recipe.essenceIngredient)))
list.add(Widgets.createSlot(Point(bounds.maxX - 12 - 16, bounds.centerY - 8))
.markOutput()
.entry(SBItemEntryDefinition.getEntry(SBItemStack(recipe.itemId).copy(stars = recipe.starCountAfter))))
val extraItems = recipe.extraItems
list.add(Widgets.createArrow(Point(bounds.centerX - 24 / 2,
if (extraItems.isEmpty()) bounds.centerY - 17 / 2
else bounds.centerY + 18 / 2)))
for ((index, item) in extraItems.withIndex()) {
list.add(Widgets.createSlot(
Point(bounds.centerX - extraItems.size * 16 / 2 - 2 / 2 + index * 18,
bounds.centerY - 18 / 2))
.markInput()
.entry(SBItemEntryDefinition.getEntry(item)))
}
return list
}
}
override fun setupDisplay(display: SBEssenceUpgradeRecipe, bounds: Rectangle): List<Widget> {
val recipe = display.neuRecipe
val list = mutableListOf<Widget>()
list.add(Widgets.createRecipeBase(bounds))
list.add(Widgets.createSlot(Point(bounds.minX + 12, bounds.centerY - 8 - 18 / 2))
.markInput()
.entry(SBItemEntryDefinition.getEntry(SBItemStack(recipe.itemId).copy(stars = recipe.starCountAfter - 1))))
list.add(Widgets.createSlot(Point(bounds.minX + 12, bounds.centerY - 8 + 18 / 2))
.markInput()
.entry(SBItemEntryDefinition.getEntry(recipe.essenceIngredient)))
list.add(Widgets.createSlot(Point(bounds.maxX - 12 - 16, bounds.centerY - 8))
.markOutput()
.entry(SBItemEntryDefinition.getEntry(SBItemStack(recipe.itemId).copy(stars = recipe.starCountAfter))))
val extraItems = recipe.extraItems
list.add(Widgets.createArrow(Point(bounds.centerX - 24 / 2,
if (extraItems.isEmpty()) bounds.centerY - 17 / 2
else bounds.centerY + 18 / 2)))
for ((index, item) in extraItems.withIndex()) {
list.add(Widgets.createSlot(
Point(bounds.centerX - extraItems.size * 16 / 2 - 2 / 2 + index * 18,
bounds.centerY - 18 / 2))
.markInput()
.entry(SBItemEntryDefinition.getEntry(item)))
}
return list
}
}
override fun getCategoryIdentifier(): CategoryIdentifier<*> {
return Category.categoryIdentifier
}
override fun getCategoryIdentifier(): CategoryIdentifier<*> {
return Category.categoryIdentifier
}
}

View File

@@ -1,5 +1,3 @@
package moe.nea.firmament.compat.rei.recipes
import io.github.moulberry.repo.data.NEUForgeRecipe
@@ -21,51 +19,53 @@ import moe.nea.firmament.compat.rei.SBItemEntryDefinition
import moe.nea.firmament.compat.rei.plus
class SBForgeRecipe(override val neuRecipe: NEUForgeRecipe) : SBRecipe() {
override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.categoryIdentifier
override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.categoryIdentifier
object Category : DisplayCategory<SBForgeRecipe> {
override fun getCategoryIdentifier(): CategoryIdentifier<SBForgeRecipe> =
CategoryIdentifier.of(Firmament.MOD_ID, "forge_recipe")
object Category : DisplayCategory<SBForgeRecipe> {
override fun getCategoryIdentifier(): CategoryIdentifier<SBForgeRecipe> =
CategoryIdentifier.of(Firmament.MOD_ID, "forge_recipe")
override fun getTitle(): Text = Text.literal("Forge Recipes")
override fun getDisplayHeight(): Int {
return 104
}
override fun getTitle(): Text = Text.literal("Forge Recipes")
override fun getDisplayHeight(): Int {
return 104
}
override fun getIcon(): Renderer = EntryStacks.of(Blocks.ANVIL)
override fun setupDisplay(display: SBForgeRecipe, bounds: Rectangle): List<Widget> {
return buildList {
add(Widgets.createRecipeBase(bounds))
add(Widgets.createResultSlotBackground(Point(bounds.minX + 124, bounds.minY + 46)))
val arrow = Widgets.createArrow(Point(bounds.minX + 90, bounds.minY + 54 - 18 / 2))
add(arrow)
add(Widgets.createTooltip(arrow.bounds, Text.stringifiedTranslatable("firmament.recipe.forge.time", display.neuRecipe.duration.seconds)))
val ingredientsCenter = Point(bounds.minX + 49 - 8, bounds.minY + 54 - 8)
val count = display.neuRecipe.inputs.size
if (count == 1) {
add(
Widgets.createSlot(Point(ingredientsCenter.x, ingredientsCenter.y)).markInput()
.entry(SBItemEntryDefinition.getEntry(display.neuRecipe.inputs.single()))
)
} else {
display.neuRecipe.inputs.forEachIndexed { idx, ingredient ->
val rad = Math.PI * 2 * idx / count
add(
Widgets.createSlot(
Point(
cos(rad) * 30,
sin(rad) * 30,
) + ingredientsCenter
).markInput().entry(SBItemEntryDefinition.getEntry(ingredient))
)
}
}
add(
Widgets.createSlot(Point(bounds.minX + 124, bounds.minY + 46)).markOutput().disableBackground()
.entry(SBItemEntryDefinition.getEntry(display.neuRecipe.outputStack))
)
}
}
}
override fun getIcon(): Renderer = EntryStacks.of(Blocks.ANVIL)
override fun setupDisplay(display: SBForgeRecipe, bounds: Rectangle): List<Widget> {
return buildList {
add(Widgets.createRecipeBase(bounds))
add(Widgets.createResultSlotBackground(Point(bounds.minX + 124, bounds.minY + 46)))
val arrow = Widgets.createArrow(Point(bounds.minX + 90, bounds.minY + 54 - 18 / 2))
add(arrow)
add(Widgets.createTooltip(arrow.bounds,
Text.stringifiedTranslatable("firmament.recipe.forge.time",
display.neuRecipe.duration.seconds)))
val ingredientsCenter = Point(bounds.minX + 49 - 8, bounds.minY + 54 - 8)
val count = display.neuRecipe.inputs.size
if (count == 1) {
add(
Widgets.createSlot(Point(ingredientsCenter.x, ingredientsCenter.y)).markInput()
.entry(SBItemEntryDefinition.getEntry(display.neuRecipe.inputs.single()))
)
} else {
display.neuRecipe.inputs.forEachIndexed { idx, ingredient ->
val rad = Math.PI * 2 * idx / count
add(
Widgets.createSlot(
Point(
cos(rad) * 30,
sin(rad) * 30,
) + ingredientsCenter
).markInput().entry(SBItemEntryDefinition.getEntry(ingredient))
)
}
}
add(
Widgets.createSlot(Point(bounds.minX + 124, bounds.minY + 46)).markOutput().disableBackground()
.entry(SBItemEntryDefinition.getEntry(display.neuRecipe.outputStack))
)
}
}
}
}

View File

@@ -1,4 +1,3 @@
package moe.nea.firmament.compat.rei.recipes
import io.github.moulberry.repo.data.NEUKatUpgradeRecipe
@@ -20,7 +19,6 @@ 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 kotlin.time.Duration.Companion.seconds
import net.minecraft.block.Blocks
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.gui.Element
import net.minecraft.item.Items
@@ -34,191 +32,191 @@ import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SkyblockId
class SBKatRecipe(override val neuRecipe: NEUKatUpgradeRecipe) : SBRecipe() {
override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.categoryIdentifier
override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.categoryIdentifier
object Category : DisplayCategory<SBKatRecipe> {
override fun getCategoryIdentifier(): CategoryIdentifier<SBKatRecipe> =
CategoryIdentifier.of(Firmament.MOD_ID, "kat_recipe")
object Category : DisplayCategory<SBKatRecipe> {
override fun getCategoryIdentifier(): CategoryIdentifier<SBKatRecipe> =
CategoryIdentifier.of(Firmament.MOD_ID, "kat_recipe")
override fun getTitle(): Text = Text.literal("Kat Pet Upgrade")
override fun getDisplayHeight(): Int {
return 100
}
override fun getTitle(): Text = Text.literal("Kat Pet Upgrade")
override fun getDisplayHeight(): Int {
return 100
}
override fun getIcon(): Renderer = EntryStacks.of(Items.BONE)
override fun setupDisplay(display: SBKatRecipe, bounds: Rectangle): List<Widget> {
return buildList {
val arrowWidth = 24
val recipe = display.neuRecipe
val levelValue = Property.upgrade(GetSetter.floating(0F))
val slider = SliderComponent(levelValue, 1F, 100F, 1f, 100)
val outputStack = SBItemStack(SkyblockId(recipe.output.itemId))
val inputStack = SBItemStack(SkyblockId(recipe.input.itemId))
val inputLevelLabelCenter = Point(bounds.minX + 30 - 18 + 5 + 8, bounds.minY + 25)
val inputLevelLabel = Widgets.createLabel(
inputLevelLabelCenter,
Text.literal("")).centered()
val outputLevelLabelCenter = Point(bounds.maxX - 30 + 8, bounds.minY + 25)
val outputLevelLabel = Widgets.createLabel(
outputLevelLabelCenter,
Text.literal("")).centered()
val coinStack = SBItemStack(SkyblockId.COINS, recipe.coins.toInt())
levelValue.whenChanged { oldValue, newValue ->
if (oldValue.toInt() == newValue.toInt()) return@whenChanged
val oldInput = inputStack.getPetData() ?: return@whenChanged
val newInput = PetData.forLevel(oldInput.petId, oldInput.rarity, newValue.toInt())
inputStack.setPetData(newInput)
val oldOutput = outputStack.getPetData() ?: return@whenChanged
val newOutput = PetData(oldOutput.rarity, oldOutput.petId, newInput.exp)
outputStack.setPetData(newOutput)
inputLevelLabel.message = Text.literal(newInput.levelData.currentLevel.toString())
inputLevelLabel.bounds.location = Point(
inputLevelLabelCenter.x - MC.font.getWidth(inputLevelLabel.message) / 2,
inputLevelLabelCenter.y)
outputLevelLabel.message = Text.literal(newOutput.levelData.currentLevel.toString())
outputLevelLabel.bounds.location = Point(
outputLevelLabelCenter.x - MC.font.getWidth(outputLevelLabel.message) / 2,
outputLevelLabelCenter.y)
coinStack.setStackSize((recipe.coins * (1 - 0.3 * newValue / 100)).toInt())
}
levelValue.set(1F)
add(Widgets.createRecipeBase(bounds))
add(wrapWidget(Rectangle(bounds.centerX - slider.width / 2,
bounds.maxY - 30,
slider.width,
slider.height),
slider))
add(Widgets.withTooltip(
Widgets.createArrow(Point(bounds.centerX - arrowWidth / 2, bounds.minY + 40)),
Text.literal("Upgrade time: " + FirmFormatters.formatTimespan(recipe.seconds.seconds))))
override fun getIcon(): Renderer = EntryStacks.of(Items.BONE)
override fun setupDisplay(display: SBKatRecipe, bounds: Rectangle): List<Widget> {
return buildList {
val arrowWidth = 24
val recipe = display.neuRecipe
val levelValue = Property.upgrade(GetSetter.floating(0F))
val slider = SliderComponent(levelValue, 1F, 100F, 1f, 100)
val outputStack = SBItemStack(SkyblockId(recipe.output.itemId))
val inputStack = SBItemStack(SkyblockId(recipe.input.itemId))
val inputLevelLabelCenter = Point(bounds.minX + 30 - 18 + 5 + 8, bounds.minY + 25)
val inputLevelLabel = Widgets.createLabel(
inputLevelLabelCenter,
Text.literal("")).centered()
val outputLevelLabelCenter = Point(bounds.maxX - 30 + 8, bounds.minY + 25)
val outputLevelLabel = Widgets.createLabel(
outputLevelLabelCenter,
Text.literal("")).centered()
val coinStack = SBItemStack(SkyblockId.COINS, recipe.coins.toInt())
levelValue.whenChanged { oldValue, newValue ->
if (oldValue.toInt() == newValue.toInt()) return@whenChanged
val oldInput = inputStack.getPetData() ?: return@whenChanged
val newInput = PetData.forLevel(oldInput.petId, oldInput.rarity, newValue.toInt())
inputStack.setPetData(newInput)
val oldOutput = outputStack.getPetData() ?: return@whenChanged
val newOutput = PetData(oldOutput.rarity, oldOutput.petId, newInput.exp)
outputStack.setPetData(newOutput)
inputLevelLabel.message = Text.literal(newInput.levelData.currentLevel.toString())
inputLevelLabel.bounds.location = Point(
inputLevelLabelCenter.x - MC.font.getWidth(inputLevelLabel.message) / 2,
inputLevelLabelCenter.y)
outputLevelLabel.message = Text.literal(newOutput.levelData.currentLevel.toString())
outputLevelLabel.bounds.location = Point(
outputLevelLabelCenter.x - MC.font.getWidth(outputLevelLabel.message) / 2,
outputLevelLabelCenter.y)
coinStack.setStackSize((recipe.coins * (1 - 0.3 * newValue / 100)).toInt())
}
levelValue.set(1F)
add(Widgets.createRecipeBase(bounds))
add(wrapWidget(Rectangle(bounds.centerX - slider.width / 2,
bounds.maxY - 30,
slider.width,
slider.height),
slider))
add(Widgets.withTooltip(
Widgets.createArrow(Point(bounds.centerX - arrowWidth / 2, bounds.minY + 40)),
Text.literal("Upgrade time: " + FirmFormatters.formatTimespan(recipe.seconds.seconds))))
add(Widgets.createResultSlotBackground(Point(bounds.maxX - 30, bounds.minY + 40)))
add(inputLevelLabel)
add(outputLevelLabel)
add(Widgets.createSlot(Point(bounds.maxX - 30, bounds.minY + 40)).markOutput().disableBackground()
.entry(SBItemEntryDefinition.getEntry(outputStack)))
add(Widgets.createSlot(Point(bounds.minX + 30 - 18 + 5, bounds.minY + 40)).markInput()
.entry(SBItemEntryDefinition.getEntry(inputStack)))
add(Widgets.createResultSlotBackground(Point(bounds.maxX - 30, bounds.minY + 40)))
add(inputLevelLabel)
add(outputLevelLabel)
add(Widgets.createSlot(Point(bounds.maxX - 30, bounds.minY + 40)).markOutput().disableBackground()
.entry(SBItemEntryDefinition.getEntry(outputStack)))
add(Widgets.createSlot(Point(bounds.minX + 30 - 18 + 5, bounds.minY + 40)).markInput()
.entry(SBItemEntryDefinition.getEntry(inputStack)))
val allInputs = recipe.items.map { SBItemEntryDefinition.getEntry(it) } +
listOf(SBItemEntryDefinition.getEntry(coinStack))
for ((index, item) in allInputs.withIndex()) {
add(Widgets.createSlot(
Point(bounds.centerX + index * 20 - allInputs.size * 18 / 2 - (allInputs.size - 1) * 2 / 2,
bounds.minY + 20))
.markInput()
.entry(item))
}
}
}
}
val allInputs = recipe.items.map { SBItemEntryDefinition.getEntry(it) } +
listOf(SBItemEntryDefinition.getEntry(coinStack))
for ((index, item) in allInputs.withIndex()) {
add(Widgets.createSlot(
Point(bounds.centerX + index * 20 - allInputs.size * 18 / 2 - (allInputs.size - 1) * 2 / 2,
bounds.minY + 20))
.markInput()
.entry(item))
}
}
}
}
}
fun wrapWidget(bounds: Rectangle, component: GuiComponent): Widget {
return object : WidgetWithBounds() {
override fun getBounds(): Rectangle {
return bounds
}
return object : WidgetWithBounds() {
override fun getBounds(): Rectangle {
return bounds
}
override fun children(): List<Element> {
return listOf()
}
override fun children(): List<Element> {
return listOf()
}
override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
context.matrices.push()
context.matrices.translate(bounds.minX.toFloat(), bounds.minY.toFloat(), 0F)
component.render(
GuiImmediateContext(
ModernRenderContext(context),
bounds.minX, bounds.minY,
bounds.width, bounds.height,
mouseX - bounds.minX, mouseY - bounds.minY,
mouseX, mouseY,
mouseX.toFloat(), mouseY.toFloat()
))
context.matrices.pop()
}
override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
context.matrices.push()
context.matrices.translate(bounds.minX.toFloat(), bounds.minY.toFloat(), 0F)
component.render(
GuiImmediateContext(
ModernRenderContext(context),
bounds.minX, bounds.minY,
bounds.width, bounds.height,
mouseX - bounds.minX, mouseY - bounds.minY,
mouseX, mouseY,
mouseX.toFloat(), mouseY.toFloat()
))
context.matrices.pop()
}
override fun mouseMoved(mouseX: Double, mouseY: Double) {
val mouseXInt = mouseX.toInt()
val mouseYInt = mouseY.toInt()
component.mouseEvent(MouseEvent.Move(0F, 0F),
GuiImmediateContext(
IMinecraft.instance.provideTopLevelRenderContext(),
bounds.minX, bounds.minY,
bounds.width, bounds.height,
mouseXInt - bounds.minX, mouseYInt - bounds.minY,
mouseXInt, mouseYInt,
mouseX.toFloat(), mouseY.toFloat()
))
}
override fun mouseMoved(mouseX: Double, mouseY: Double) {
val mouseXInt = mouseX.toInt()
val mouseYInt = mouseY.toInt()
component.mouseEvent(MouseEvent.Move(0F, 0F),
GuiImmediateContext(
IMinecraft.instance.provideTopLevelRenderContext(),
bounds.minX, bounds.minY,
bounds.width, bounds.height,
mouseXInt - bounds.minX, mouseYInt - bounds.minY,
mouseXInt, mouseYInt,
mouseX.toFloat(), mouseY.toFloat()
))
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
val mouseXInt = mouseX.toInt()
val mouseYInt = mouseY.toInt()
return component.mouseEvent(MouseEvent.Click(button, true),
GuiImmediateContext(
IMinecraft.instance.provideTopLevelRenderContext(),
bounds.minX, bounds.minY,
bounds.width, bounds.height,
mouseXInt - bounds.minX, mouseYInt - bounds.minY,
mouseXInt, mouseYInt,
mouseX.toFloat(), mouseY.toFloat()
))
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
val mouseXInt = mouseX.toInt()
val mouseYInt = mouseY.toInt()
return component.mouseEvent(MouseEvent.Click(button, true),
GuiImmediateContext(
IMinecraft.instance.provideTopLevelRenderContext(),
bounds.minX, bounds.minY,
bounds.width, bounds.height,
mouseXInt - bounds.minX, mouseYInt - bounds.minY,
mouseXInt, mouseYInt,
mouseX.toFloat(), mouseY.toFloat()
))
}
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
val mouseXInt = mouseX.toInt()
val mouseYInt = mouseY.toInt()
return component.mouseEvent(MouseEvent.Click(button, false),
GuiImmediateContext(
IMinecraft.instance.provideTopLevelRenderContext(),
bounds.minX, bounds.minY,
bounds.width, bounds.height,
mouseXInt - bounds.minX, mouseYInt - bounds.minY,
mouseXInt, mouseYInt,
mouseX.toFloat(), mouseY.toFloat()
))
}
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
val mouseXInt = mouseX.toInt()
val mouseYInt = mouseY.toInt()
return component.mouseEvent(MouseEvent.Click(button, false),
GuiImmediateContext(
IMinecraft.instance.provideTopLevelRenderContext(),
bounds.minX, bounds.minY,
bounds.width, bounds.height,
mouseXInt - bounds.minX, mouseYInt - bounds.minY,
mouseXInt, mouseYInt,
mouseX.toFloat(), mouseY.toFloat()
))
}
override fun mouseDragged(
mouseX: Double,
mouseY: Double,
button: Int,
deltaX: Double,
deltaY: Double
): Boolean {
val mouseXInt = mouseX.toInt()
val mouseYInt = mouseY.toInt()
return component.mouseEvent(MouseEvent.Move(deltaX.toFloat(), deltaY.toFloat()),
GuiImmediateContext(
IMinecraft.instance.provideTopLevelRenderContext(),
bounds.minX, bounds.minY,
bounds.width, bounds.height,
mouseXInt - bounds.minX, mouseYInt - bounds.minY,
mouseXInt, mouseYInt,
mouseX.toFloat(), mouseY.toFloat()
))
override fun mouseDragged(
mouseX: Double,
mouseY: Double,
button: Int,
deltaX: Double,
deltaY: Double
): Boolean {
val mouseXInt = mouseX.toInt()
val mouseYInt = mouseY.toInt()
return component.mouseEvent(MouseEvent.Move(deltaX.toFloat(), deltaY.toFloat()),
GuiImmediateContext(
IMinecraft.instance.provideTopLevelRenderContext(),
bounds.minX, bounds.minY,
bounds.width, bounds.height,
mouseXInt - bounds.minX, mouseYInt - bounds.minY,
mouseXInt, mouseYInt,
mouseX.toFloat(), mouseY.toFloat()
))
}
}
override fun mouseScrolled(
mouseX: Double,
mouseY: Double,
horizontalAmount: Double,
verticalAmount: Double
): Boolean {
val mouseXInt = mouseX.toInt()
val mouseYInt = mouseY.toInt()
return component.mouseEvent(MouseEvent.Scroll(verticalAmount.toFloat()),
GuiImmediateContext(
IMinecraft.instance.provideTopLevelRenderContext(),
bounds.minX, bounds.minY,
bounds.width, bounds.height,
mouseXInt - bounds.minX, mouseYInt - bounds.minY,
mouseXInt, mouseYInt,
mouseX.toFloat(), mouseY.toFloat()
))
}
}
override fun mouseScrolled(
mouseX: Double,
mouseY: Double,
horizontalAmount: Double,
verticalAmount: Double
): Boolean {
val mouseXInt = mouseX.toInt()
val mouseYInt = mouseY.toInt()
return component.mouseEvent(MouseEvent.Scroll(verticalAmount.toFloat()),
GuiImmediateContext(
IMinecraft.instance.provideTopLevelRenderContext(),
bounds.minX, bounds.minY,
bounds.width, bounds.height,
mouseXInt - bounds.minX, mouseYInt - bounds.minY,
mouseXInt, mouseYInt,
mouseX.toFloat(), mouseY.toFloat()
))
}
}
}

View File

@@ -1,4 +1,3 @@
package moe.nea.firmament.compat.rei.recipes
import io.github.moulberry.repo.data.NEUMobDropRecipe
@@ -14,95 +13,95 @@ import net.minecraft.item.Items
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.compat.rei.EntityWidget
import moe.nea.firmament.compat.rei.SBItemEntryDefinition
import moe.nea.firmament.gui.entity.EntityRenderer
class SBMobDropRecipe(override val neuRecipe: NEUMobDropRecipe) : SBRecipe() {
override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.categoryIdentifier
override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.categoryIdentifier
object Category : DisplayCategory<SBMobDropRecipe> {
override fun getCategoryIdentifier(): CategoryIdentifier<SBMobDropRecipe> =
CategoryIdentifier.of(Firmament.MOD_ID, "mob_drop_recipe")
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 getTitle(): Text = Text.literal("Mob Drops")
override fun getDisplayHeight(): Int {
return 100
}
override fun getIcon(): Renderer = EntryStacks.of(Items.DIAMOND_SWORD)
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.of(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
}
}
}
}
}
override fun getIcon(): Renderer = EntryStacks.of(Items.DIAMOND_SWORD)
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.of(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

@@ -2,28 +2,41 @@ package moe.nea.firmament.compat.rei.recipes
import io.github.moulberry.repo.data.NEUIngredient
import io.github.moulberry.repo.data.NEURecipe
import java.util.Optional
import me.shedaniel.rei.api.common.display.Display
import me.shedaniel.rei.api.common.display.DisplaySerializer
import me.shedaniel.rei.api.common.entry.EntryIngredient
import net.minecraft.util.Identifier
import moe.nea.firmament.compat.rei.SBItemEntryDefinition
import moe.nea.firmament.util.SkyblockId
abstract class SBRecipe : Display {
abstract val neuRecipe: NEURecipe
override fun getInputEntries(): List<EntryIngredient> {
return neuRecipe.allInputs
.filter { it.itemId != NEUIngredient.NEU_SENTINEL_EMPTY }
.map {
val entryStack = SBItemEntryDefinition.getEntry(SkyblockId(it.itemId))
EntryIngredient.of(entryStack)
}
}
override fun getDisplayLocation(): Optional<Identifier> {
// In theory, we could return a location for the neuRecipe here. (Something along the lines of neurepo:items/item_id.json/0 for the 0th recipe in the items/item_id.json recipes array).
return Optional.empty()
}
override fun getOutputEntries(): List<EntryIngredient> {
return neuRecipe.allOutputs
.filter { it.itemId != NEUIngredient.NEU_SENTINEL_EMPTY }
.map {
val entryStack = SBItemEntryDefinition.getEntry(SkyblockId(it.itemId))
EntryIngredient.of(entryStack)
}
}
override fun getSerializer(): DisplaySerializer<out Display>? {
// While returning null here is discouraged, we are fine to do so, since this recipe will never travel through the network
return null
}
abstract val neuRecipe: NEURecipe
override fun getInputEntries(): List<EntryIngredient> {
return neuRecipe.allInputs
.filter { it.itemId != NEUIngredient.NEU_SENTINEL_EMPTY }
.map {
val entryStack = SBItemEntryDefinition.getEntry(SkyblockId(it.itemId))
EntryIngredient.of(entryStack)
}
}
override fun getOutputEntries(): List<EntryIngredient> {
return neuRecipe.allOutputs
.filter { it.itemId != NEUIngredient.NEU_SENTINEL_EMPTY }
.map {
val entryStack = SBItemEntryDefinition.getEntry(SkyblockId(it.itemId))
EntryIngredient.of(entryStack)
}
}
}

View File

@@ -1,6 +1,6 @@
package moe.nea.firmament.compat.sodium
import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer
import net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer
import moe.nea.firmament.mixins.accessor.sodium.AccessorSodiumWorldRenderer
class SodiumChunkReloader : Runnable {

View File

@@ -1,7 +1,7 @@
package moe.nea.firmament.mixins.accessor.sodium;
import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer;
import me.jellysquid.mods.sodium.client.render.chunk.RenderSectionManager;
import net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer;
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSectionManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Pseudo;
import org.spongepowered.asm.mixin.gen.Accessor;
@@ -9,6 +9,6 @@ import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(SodiumWorldRenderer.class)
@Pseudo
public interface AccessorSodiumWorldRenderer {
@Accessor(value = "renderSectionManager", remap = false)
RenderSectionManager getRenderSectionManager_firmament();
@Accessor(value = "renderSectionManager", remap = false)
RenderSectionManager getRenderSectionManager_firmament();
}

View File

@@ -3,8 +3,8 @@ package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import me.jellysquid.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderMeshingTask;
import moe.nea.firmament.features.texturepack.CustomBlockTextures;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderMeshingTask;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.block.BlockModels;
import net.minecraft.client.render.model.BakedModel;
@@ -15,7 +15,7 @@ import org.spongepowered.asm.mixin.injection.At;
@Mixin(ChunkBuilderMeshingTask.class)
public class PatchBlockModelInSodiumChunkGenerator {
@WrapOperation(
method = "execute(Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuildContext;Lme/jellysquid/mods/sodium/client/util/task/CancellationToken;)Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuildOutput;",
method = "execute(Lnet/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildContext;Lnet/caffeinemc/mods/sodium/client/util/task/CancellationToken;)Lnet/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildOutput;",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/block/BlockModels;getModel(Lnet/minecraft/block/BlockState;)Lnet/minecraft/client/render/model/BakedModel;"))
private BakedModel replaceBlockModel(BlockModels instance, BlockState state, Operation<BakedModel> original,
@Local(name = "blockPos") BlockPos.Mutable pos) {

View File

@@ -14,7 +14,7 @@ import org.spongepowered.asm.mixin.injection.At;
@Mixin(DrawContext.class)
public class CustomDurabilityBarPatch {
@WrapOperation(
method = "drawItemInSlot(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V",
method = "drawItemBar",
at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;isItemBarVisible()Z")
)
private boolean onIsItemBarVisible(
@@ -29,7 +29,7 @@ public class CustomDurabilityBarPatch {
return barOverride.get() != null;
}
@WrapOperation(method = "drawItemInSlot(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V",
@WrapOperation(method = "drawItemBar",
at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;getItemBarStep()I"))
private int overrideItemStep(
ItemStack instance, Operation<Integer> original,
@@ -40,7 +40,7 @@ public class CustomDurabilityBarPatch {
return original.call(instance);
}
@WrapOperation(method = "drawItemInSlot(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V",
@WrapOperation(method = "drawItemBar",
at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;getItemBarColor()I"))
private int overrideItemColor(
ItemStack instance, Operation<Integer> original,

View File

@@ -1,49 +0,0 @@
package moe.nea.firmament.mixins;
import moe.nea.firmament.events.BakeExtraModelsEvent;
import net.minecraft.client.render.model.ModelLoader;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
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.Map;
@Mixin(ModelLoader.class)
public abstract class CustomModelBakerPatch {
@Shadow
@Final
private Map<ModelIdentifier, UnbakedModel> modelsToBake;
@Shadow
protected abstract void loadItemModel(ModelIdentifier id);
@Shadow
abstract UnbakedModel getOrLoadModel(Identifier id);
@Shadow
protected abstract void add(ModelIdentifier id, UnbakedModel model);
@Unique
private void loadNonItemModel(ModelIdentifier identifier) {
UnbakedModel unbakedModel = this.getOrLoadModel(identifier.id());
this.add(identifier, unbakedModel);
}
@Inject(method = "bake", at = @At("HEAD"))
public void onBake(ModelLoader.SpriteGetter spliteGetter, CallbackInfo ci) {
BakeExtraModelsEvent.Companion.publish(new BakeExtraModelsEvent(this::loadItemModel, this::loadNonItemModel));
modelsToBake.values().forEach(model -> model.setParents(this::getOrLoadModel));
// modelsToBake.keySet().stream()
// .filter(it -> !it.id().getNamespace().equals("minecraft"))
// .forEach(it -> System.out.println("Non minecraft texture is being loaded: " + it));
}
}

View File

@@ -7,6 +7,7 @@ import net.minecraft.client.render.item.ItemModels;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedModelManager;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -14,16 +15,15 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Map;
@Mixin(ItemModels.class)
public class CustomModelEventPatch {
@Shadow
@Final
private BakedModelManager modelManager;
@Inject(method = "getModel(Lnet/minecraft/item/ItemStack;)Lnet/minecraft/client/render/model/BakedModel;", at = @At("HEAD"), cancellable = true)
public void onGetModel(ItemStack stack, CallbackInfoReturnable<BakedModel> cir) {
var model = CustomItemModelEvent.getModel(stack, modelManager);
if (model != null)
cir.setReturnValue(model);
}
@Inject(method = "getModel(Lnet/minecraft/item/ItemStack;)Lnet/minecraft/client/render/model/BakedModel;", at = @At("HEAD"), cancellable = true)
public void onGetModel(ItemStack stack, CallbackInfoReturnable<BakedModel> cir) {
var model = CustomItemModelEvent.getModel(stack, (ItemModels) (Object) this);
if (model != null)
cir.setReturnValue(model);
}
}

View File

@@ -2,7 +2,6 @@
package moe.nea.firmament.mixins;
import com.mojang.authlib.GameProfile;
import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures;
import net.minecraft.block.SkullBlock;
import net.minecraft.client.render.RenderLayer;
@@ -15,8 +14,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(SkullBlockEntityRenderer.class)
public class CustomSkullTexturePatch {
@Inject(method = "getRenderLayer", at = @At("HEAD"), cancellable = true)
private static void onGetRenderLayer(SkullBlock.SkullType type, ProfileComponent profile, CallbackInfoReturnable<RenderLayer> cir) {
CustomSkyBlockTextures.INSTANCE.modifySkullTexture(type, profile, cir);
}
@Inject(method = "getRenderLayer", at = @At("HEAD"), cancellable = true)
private static void onGetRenderLayer(SkullBlock.SkullType type, ProfileComponent profile, CallbackInfoReturnable<RenderLayer> cir) {
CustomSkyBlockTextures.INSTANCE.modifySkullTexture(type, profile, cir);
}
}

View File

@@ -17,6 +17,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Map;
// TODO: rework this
@Mixin(EntityIdFix.class)
public abstract class DFUEntityIdFixPatch extends DataFix {
@Shadow

View File

@@ -1,31 +0,0 @@
package moe.nea.firmament.mixins;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.datafixers.util.Pair;
import moe.nea.firmament.events.RegisterCustomShadersEvent;
import net.minecraft.client.gl.ShaderProgram;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.resource.ResourceFactory;
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;
import java.util.function.Consumer;
@Mixin(GameRenderer.class)
public class InjectCustomShaderPrograms {
@Inject(method = "loadPrograms",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/GameRenderer;loadBlurPostProcessor(Lnet/minecraft/resource/ResourceFactory;)V",
shift = At.Shift.AFTER))
void addFirmamentShaders(
ResourceFactory resourceFactory, CallbackInfo ci,
@Local(index = 3) List<Pair<ShaderProgram, Consumer<ShaderProgram>>> list
) {
var event = new RegisterCustomShadersEvent(list, resourceFactory);
RegisterCustomShadersEvent.Companion.publish(event);
}
}

View File

@@ -0,0 +1,31 @@
package moe.nea.firmament.mixins;
import moe.nea.firmament.Firmament;
import moe.nea.firmament.events.DebugInstantiateEvent;
import net.minecraft.client.gui.LogoDrawer;
import net.minecraft.client.gui.screen.TitleScreen;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(TitleScreen.class)
public class MainWindowFirstLoadPatch {
@Unique
private static boolean hasInited = false;
@Inject(method = "<init>(ZLnet/minecraft/client/gui/LogoDrawer;)V", at = @At("RETURN"))
private void onCreate(boolean doBackgroundFade, LogoDrawer logoDrawer, CallbackInfo ci) {
if (!hasInited) {
try {
DebugInstantiateEvent.Companion.publish(new DebugInstantiateEvent());
} catch (Throwable t) {
Firmament.INSTANCE.getLogger().error("Failed to instantiate debug instances", t);
System.exit(1);
throw t;
}
}
hasInited = true;
}
}

View File

@@ -2,6 +2,9 @@
package moe.nea.firmament.mixins;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.events.*;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
@@ -21,6 +24,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import java.util.Iterator;
@Mixin(HandledScreen.class)
public abstract class MixinHandledScreen<T extends ScreenHandler> {
@@ -90,15 +95,12 @@ public abstract class MixinHandledScreen<T extends ScreenHandler> {
}
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawSlot(Lnet/minecraft/client/gui/DrawContext;Lnet/minecraft/screen/slot/Slot;)V", shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILHARD)
public void onAfterDrawSlot(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci, int i, int j, int k, Slot slot) {
SlotRenderEvents.After event = new SlotRenderEvents.After(context, slot, mouseX, mouseY, delta);
SlotRenderEvents.After.Companion.publish(event);
}
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawSlot(Lnet/minecraft/client/gui/DrawContext;Lnet/minecraft/screen/slot/Slot;)V", shift = At.Shift.BEFORE), locals = LocalCapture.CAPTURE_FAILHARD)
public void onBeforeDrawSlot(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci, int i, int j, int k, Slot slot) {
SlotRenderEvents.Before event = new SlotRenderEvents.Before(context, slot, mouseX, mouseY, delta);
SlotRenderEvents.Before.Companion.publish(event);
@WrapOperation(method = "drawSlots", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawSlot(Lnet/minecraft/client/gui/DrawContext;Lnet/minecraft/screen/slot/Slot;)V"))
public void onDrawSlots(HandledScreen instance, DrawContext context, Slot slot, Operation<Void> original) {
var before = new SlotRenderEvents.Before(context, slot);
SlotRenderEvents.Before.Companion.publish(before);
original.call(instance, context, slot);
var after = new SlotRenderEvents.After(context, slot);
SlotRenderEvents.After.Companion.publish(after);
}
}

View File

@@ -20,12 +20,16 @@ import org.spongepowered.asm.mixin.injection.At;
AnvilScreen.class, BeaconScreen.class})
public class ReplaceTextColorInHandledScreen {
// To my future self: double check those mixins, but don't be too concerned about errors. Some of the wrapopertions
// only apply in some of the specified subclasses.
@WrapOperation(
method = "drawForeground",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/gui/DrawContext;drawText(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/text/Text;IIIZ)I"),
expect = 0)
expect = 0,
require = 0)
private int replaceTextColorWithVariableShadow(DrawContext instance, TextRenderer textRenderer, Text text, int x, int y, int color, boolean shadow, Operation<Integer> original) {
return original.call(instance, textRenderer, text, x, y, CustomTextColors.INSTANCE.mapTextColor(text, color), shadow);
}
@@ -35,7 +39,8 @@ public class ReplaceTextColorInHandledScreen {
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/gui/DrawContext;drawTextWithShadow(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/text/Text;III)I"),
expect = 0)
expect = 0,
require = 0)
private int replaceTextColorWithShadow(DrawContext instance, TextRenderer textRenderer, Text text, int x, int y, int color, Operation<Integer> original) {
return original.call(instance, textRenderer, text, x, y, CustomTextColors.INSTANCE.mapTextColor(text, color));
}

View File

@@ -23,14 +23,13 @@ public abstract class SlotUpdateListener extends ClientCommonNetworkHandler {
@Inject(
method = "onScreenHandlerSlotUpdate",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/tutorial/TutorialManager;onSlotUpdate(Lnet/minecraft/item/ItemStack;)V"))
at = @At(value = "TAIL"))
private void onSingleSlotUpdate(
ScreenHandlerSlotUpdateS2CPacket packet,
CallbackInfo ci) {
var player = this.client.player;
assert player != null;
if (packet.getSyncId() == ScreenHandlerSlotUpdateS2CPacket.UPDATE_PLAYER_INVENTORY_SYNC_ID
|| packet.getSyncId() == 0) {
if (packet.getSyncId() == 0) {
PlayerInventoryUpdate.Companion.publish(new PlayerInventoryUpdate.Single(packet.getSlot(), packet.getStack()));
} else if (packet.getSyncId() == player.currentScreenHandler.syncId) {
ChestInventoryUpdateEvent.Companion.publish(
@@ -40,8 +39,7 @@ public abstract class SlotUpdateListener extends ClientCommonNetworkHandler {
}
@Inject(method = "onInventory",
at = @At(value = "INVOKE", target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/util/thread/ThreadExecutor;)V",
shift = At.Shift.AFTER))
at = @At("TAIL"))
private void onMultiSlotUpdate(InventoryS2CPacket packet, CallbackInfo ci) {
var player = this.client.player;
assert player != null;

View File

@@ -3,16 +3,17 @@
package moe.nea.firmament.mixins;
import moe.nea.firmament.events.WorldReadyEvent;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.DownloadingTerrainScreen;
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;
@Mixin(DownloadingTerrainScreen.class)
@Mixin(MinecraftClient.class)
public class WorldReadyEventPatch {
@Inject(method = "close", at = @At("HEAD"))
public void onClose(CallbackInfo ci) {
WorldReadyEvent.Companion.publish(new WorldReadyEvent());
}
@Inject(method = "joinWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;setWorld(Lnet/minecraft/client/world/ClientWorld;)V", shift = At.Shift.AFTER))
public void onClose(CallbackInfo ci) {
WorldReadyEvent.Companion.publish(new WorldReadyEvent());
}
}

View File

@@ -5,8 +5,10 @@ package moe.nea.firmament.mixins;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.events.WorldRenderLastEvent;
import net.minecraft.client.render.*;
import net.minecraft.client.util.Handle;
import net.minecraft.client.util.ObjectAllocator;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.world.tick.TickManager;
import net.minecraft.util.profiler.Profiler;
import org.joml.Matrix4f;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -16,22 +18,30 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(WorldRenderer.class)
public class WorldRenderLastEventPatch {
@Shadow
@Final
private BufferBuilderStorage bufferBuilders;
public abstract class WorldRenderLastEventPatch {
@Shadow
@Final
private BufferBuilderStorage bufferBuilders;
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;renderChunkDebugInfo(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;Lnet/minecraft/client/render/Camera;)V", shift = At.Shift.BEFORE))
public void onWorldRenderLast(
RenderTickCounter tickCounter, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer,
LightmapTextureManager lightmapTextureManager, Matrix4f matrix4f, Matrix4f matrix4f2,
CallbackInfo ci, @Local MatrixStack matrixStack
) {
var event = new WorldRenderLastEvent(
matrixStack, tickCounter, renderBlockOutline,
camera, gameRenderer, lightmapTextureManager,
this.bufferBuilders.getEntityVertexConsumers()
);
WorldRenderLastEvent.Companion.publish(event);
}
@Shadow
@Final
private DefaultFramebufferSet framebufferSet;
@Shadow
protected abstract void checkEmpty(MatrixStack matrices);
@Inject(method = "method_62214", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiler/Profiler;pop()V", shift = At.Shift.AFTER))
public void onWorldRenderLast(Fog fog, RenderTickCounter tickCounter, Camera camera, Profiler profiler, Matrix4f matrix4f, Matrix4f matrix4f2, Handle handle, Handle handle2, Handle handle3, Handle handle4, boolean bl, Frustum frustum, Handle handle5, CallbackInfo ci) {
var imm = this.bufferBuilders.getEntityVertexConsumers();
var stack = new MatrixStack();
// TODO: pre-cancel this event if F1 is active
var event = new WorldRenderLastEvent(
stack, tickCounter,
camera,
imm
);
WorldRenderLastEvent.Companion.publish(event);
imm.draw();
checkEmpty(stack);
}
}

View File

@@ -1,14 +0,0 @@
package moe.nea.firmament.mixins.accessor;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.GameRenderer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(GameRenderer.class)
public interface AccessorGameRenderer {
@Invoker("getFov")
double getFov_firmament(Camera camera, float tickDelta, boolean changingFov);
}

View File

@@ -14,7 +14,6 @@ import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.screen.slot.Slot;
import net.minecraft.text.Text;
import net.minecraft.util.collection.DefaultedList;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -88,29 +87,18 @@ public class PatchHandledScreen<T extends ScreenHandler> extends Screen implemen
}
@Unique
private Slot didBeforeSlotRender;
@WrapOperation(
method = "render",
method = "drawSlots",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/util/collection/DefaultedList;get(I)Ljava/lang/Object;"))
private Object beforeSlotRender(DefaultedList instance, int index, Operation<Object> original, @Local(argsOnly = true) DrawContext context) {
var slot = (Slot) original.call(instance, index);
target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawSlot(Lnet/minecraft/client/gui/DrawContext;Lnet/minecraft/screen/slot/Slot;)V"))
private void beforeSlotRender(HandledScreen instance, DrawContext context, Slot slot, Operation<Void> original) {
if (override != null) {
didBeforeSlotRender = slot;
override.beforeSlotRender(context, slot);
}
return slot;
}
@Inject(method = "render",
at = @At(value = "INVOKE", target = "Lnet/minecraft/util/collection/DefaultedList;size()I"))
private void afterSlotRender(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
if (override != null && didBeforeSlotRender != null) {
override.afterSlotRender(context, didBeforeSlotRender);
didBeforeSlotRender = null;
original.call(instance, context, slot);
if (override != null) {
override.afterSlotRender(context, slot);
}
}

View File

@@ -1,33 +1,35 @@
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
import moe.nea.firmament.features.texturepack.BakedModelExtra;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.item.ItemRenderer;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.json.ModelTransformationMode;
import net.minecraft.entity.LivingEntity;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import net.minecraft.item.ModelTransformationMode;
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;
@Mixin(ItemRenderer.class)
public class ApplyHeadModelInItemRenderer {
@WrapOperation(method = "renderItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/render/model/json/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;Lnet/minecraft/world/World;III)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/ItemRenderer;getModel(Lnet/minecraft/item/ItemStack;Lnet/minecraft/world/World;Lnet/minecraft/entity/LivingEntity;I)Lnet/minecraft/client/render/model/BakedModel;"))
private BakedModel applyHeadModel(ItemRenderer instance, ItemStack stack, World world, LivingEntity entity, int seed, Operation<BakedModel> original,
@Local(argsOnly = true) ModelTransformationMode modelTransformationMode) {
var model = original.call(instance, stack, world, entity, seed);
if (modelTransformationMode == ModelTransformationMode.HEAD
&& model instanceof BakedModelExtra extra) {
var headModel = extra.getHeadModel_firmament();
if (headModel != null) {
model = headModel;
}
}
return model;
}
@Inject(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;ZF)V",
at = @At("HEAD"))
private void applyHeadModel(ItemStack stack, ModelTransformationMode transformationMode, boolean leftHanded,
MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay,
BakedModel model, boolean useInventoryModel, float z, CallbackInfo ci,
@Local(argsOnly = true) LocalRef<BakedModel> modelMut
) {
var extra = BakedModelExtra.cast(model);
if (transformationMode == ModelTransformationMode.HEAD && extra != null) {
var headModel = extra.getHeadModel_firmament();
if (headModel != null) {
modelMut.set(headModel);
}
}
}
}

View File

@@ -8,6 +8,7 @@ import net.minecraft.client.render.model.BakedModel;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
@@ -17,13 +18,14 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ItemRenderer.class)
public abstract class GlobalModelOverridePatch {
@Shadow
public abstract ItemModels getModels();
@Shadow
@Final
private ItemModels models;
@Inject(method = "getModel", at = @At("HEAD"), cancellable = true)
private void overrideGlobalModel(
ItemStack stack, World world, LivingEntity entity,
int seed, CallbackInfoReturnable<BakedModel> cir) {
CustomGlobalTextures.replaceGlobalModel(this.getModels(), stack, cir);
}
@Inject(method = "getModel(Lnet/minecraft/item/ItemStack;Lnet/minecraft/world/World;Lnet/minecraft/entity/LivingEntity;I)Lnet/minecraft/client/render/model/BakedModel;", at = @At("HEAD"), cancellable = true)
private void overrideGlobalModel(
ItemStack stack, World world, LivingEntity entity,
int seed, CallbackInfoReturnable<BakedModel> cir) {
CustomGlobalTextures.replaceGlobalModel(this.models, stack, cir);
}
}

View File

@@ -0,0 +1,57 @@
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.features.texturepack.BakedModelExtra;
import net.minecraft.block.AbstractSkullBlock;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.entity.LivingEntityRenderer;
import net.minecraft.client.render.entity.feature.HeadFeatureRenderer;
import net.minecraft.client.render.entity.model.EntityModel;
import net.minecraft.client.render.entity.model.ModelWithHead;
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(HeadFeatureRenderer.class)
public class HeadModelReplacerPatch<S extends LivingEntityRenderState, M extends EntityModel<S> & ModelWithHead> {
/**
* This class serves to disable the replacing of head models with the vanilla block model. Vanilla first selects loads
* the model containing the head model regularly in {@link LivingEntityRenderer#updateRenderState}, but then discards
* the model in {@link HeadFeatureRenderer#render(MatrixStack, VertexConsumerProvider, int, LivingEntityRenderState, float, float)}
* if it detects a skull block. This serves to disable that functionality if a head model override is present.
*/
@WrapOperation(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/LivingEntityRenderState;FF)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/item/BlockItem;getBlock()Lnet/minecraft/block/Block;"))
private Block replaceSkull(BlockItem instance, Operation<Block> original, @Local BakedModel bakedModel) {
var oldBlock = original.call(instance);
if (oldBlock instanceof AbstractSkullBlock) {
var extra = BakedModelExtra.cast(bakedModel);
if (extra != null && extra.getHeadModel_firmament() != null)
return Blocks.ENCHANTING_TABLE; // Any non skull block. Let's choose the enchanting table because it is very distinct.
}
return oldBlock;
}
/**
* We disable the has model override, since texture packs get precedent to server data.
*/
@WrapOperation(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/LivingEntityRenderState;FF)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/feature/ArmorFeatureRenderer;hasModel(Lnet/minecraft/item/ItemStack;Lnet/minecraft/entity/EquipmentSlot;)Z"))
private boolean replaceHasModel(ItemStack stack, EquipmentSlot slot, Operation<Boolean> original,
@Local BakedModel bakedModel) {
var extra = BakedModelExtra.cast(bakedModel);
if (extra != null && extra.getHeadModel_firmament() != null)
return false;
return original.call(stack, slot);
}
}

View File

@@ -5,28 +5,30 @@ import moe.nea.firmament.features.texturepack.TintOverrides;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.item.ItemRenderer;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.json.ModelTransformationMode;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ModelTransformationMode;
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;
@Mixin(ItemRenderer.class)
@Mixin(value = ItemRenderer.class, priority = 1010)
public class ItemRendererTintContextPatch {
@Inject(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/render/model/json/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/BakedModel;getTransformation()Lnet/minecraft/client/render/model/json/ModelTransformation;"), allow = 1)
private void onStartRendering(ItemStack stack, ModelTransformationMode renderMode, boolean leftHanded, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, BakedModel model, CallbackInfo ci) {
if (model instanceof BakedModelExtra extra) {
@Inject(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;ZF)V",
at = @At(value = "HEAD"), allow = 1)
private void onStartRendering(ItemStack stack, ModelTransformationMode transformationMode, boolean leftHanded, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, BakedModel model, boolean useInventoryModel, float z, CallbackInfo ci) {
var extra = BakedModelExtra.cast(model);
if (extra != null) {
TintOverrides.Companion.enter(extra.getTintOverrides_firmament());
}
}
@Inject(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/render/model/json/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/math/MatrixStack;pop()V"), allow = 1)
private void onEndRendering(ItemStack stack, ModelTransformationMode renderMode, boolean leftHanded, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, BakedModel model, CallbackInfo ci) {
if (model instanceof BakedModelExtra extra) {
@Inject(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;ZF)V",
at = @At("TAIL"), allow = 1)
private void onEndRendering(ItemStack stack, ModelTransformationMode transformationMode, boolean leftHanded, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, BakedModel model, boolean useInventoryModel, float z, CallbackInfo ci) {
var extra = BakedModelExtra.cast(model);
if (extra != null) {
TintOverrides.Companion.exit(extra.getTintOverrides_firmament());
}
}

View File

@@ -1,11 +1,11 @@
package moe.nea.firmament.mixins.custommodels;
import com.google.gson.annotations.SerializedName;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.features.texturepack.BakedModelExtra;
import moe.nea.firmament.features.texturepack.JsonUnbakedModelFirmExtra;
import moe.nea.firmament.features.texturepack.TintOverrides;
import moe.nea.firmament.util.ErrorUtil;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.ModelRotation;
@@ -18,15 +18,20 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
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.Collection;
import java.util.Objects;
@Mixin(JsonUnbakedModel.class)
public class JsonUnbakedModelDataHolder implements JsonUnbakedModelFirmExtra {
public abstract class JsonUnbakedModelDataHolder implements JsonUnbakedModelFirmExtra {
@Shadow
@Nullable
protected JsonUnbakedModel parent;
@Shadow
public abstract String toString();
@Unique
@Nullable
public Identifier headModel;
@@ -67,31 +72,59 @@ public class JsonUnbakedModelDataHolder implements JsonUnbakedModelFirmExtra {
return ((JsonUnbakedModelFirmExtra) this.parent).getHeadModel_firmament();
}
@ModifyReturnValue(method = "getModelDependencies", at = @At("RETURN"))
private Collection<Identifier> addDependencies(Collection<Identifier> original) {
@Inject(method = "resolve", at = @At("HEAD"))
private void addDependencies(UnbakedModel.Resolver resolver, CallbackInfo ci) {
var headModel = getHeadModel_firmament();
if (headModel != null) {
original.add(headModel);
resolver.resolve(headModel);
}
}
private void addExtraBakeInfo(BakedModel bakedModel, Baker baker) {
if (!this.toString().contains("minecraft") && this.toString().contains("crimson")) {
System.out.println("Found non minecraft model " + this);
}
var extra = BakedModelExtra.cast(bakedModel);
if (extra != null) {
var headModel = getHeadModel_firmament();
if (headModel != null) {
extra.setHeadModel_firmament(baker.bake(headModel, ModelRotation.X0_Y0));
}
if (getTintOverrides_firmament().hasOverrides()) {
extra.setTintOverrides_firmament(getTintOverrides_firmament());
}
}
}
/**
* @see ProvideBakerToJsonUnbakedModelPatch
*/
@Override
public void storeExtraBaker_firmament(@NotNull Baker baker) {
this.storedBaker = baker;
}
@Unique
private Baker storedBaker;
@ModifyReturnValue(
method = "bake(Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;Z)Lnet/minecraft/client/render/model/BakedModel;",
at = @At("RETURN"))
private BakedModel bakeExtraInfoWithoutBaker(BakedModel original) {
if (storedBaker != null) {
addExtraBakeInfo(original, storedBaker);
storedBaker = null;
}
return original;
}
@ModifyReturnValue(
method = "bake(Lnet/minecraft/client/render/model/Baker;Lnet/minecraft/client/render/model/json/JsonUnbakedModel;Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;Z)Lnet/minecraft/client/render/model/BakedModel;",
method = {
"bake(Lnet/minecraft/client/render/model/Baker;Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;)Lnet/minecraft/client/render/model/BakedModel;"
},
at = @At(value = "RETURN"))
private BakedModel bakeExtraInfo(BakedModel original, @Local(argsOnly = true) Baker baker) {
if (original instanceof BakedModelExtra extra) {
var headModel = getHeadModel_firmament();
if (headModel != null) {
UnbakedModel unbakedModel = baker.getOrLoadModel(headModel);
extra.setHeadModel_firmament(
Objects.equals(unbakedModel, parent)
? null
: baker.bake(headModel, ModelRotation.X0_Y0));
}
if (getTintOverrides_firmament().hasOverrides())
extra.setTintOverrides_firmament(getTintOverrides_firmament());
}
addExtraBakeInfo(original, baker);
return original;
}
}

View File

@@ -6,26 +6,24 @@ import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.features.texturepack.CustomGlobalArmorOverrides;
import net.minecraft.client.render.entity.feature.ArmorFeatureRenderer;
import net.minecraft.item.ArmorMaterial;
import net.minecraft.component.type.EquippableComponent;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import java.util.List;
import java.util.Optional;
@Mixin(ArmorFeatureRenderer.class)
public class PatchArmorTexture {
@WrapOperation(
method = "renderArmor",
at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ArmorMaterial;layers()Ljava/util/List;"))
private List<ArmorMaterial.Layer> overrideLayers(
ArmorMaterial instance,
Operation<List<ArmorMaterial.Layer>> original,
@Local ItemStack itemStack
) {
var overrides = CustomGlobalArmorOverrides.overrideArmor(itemStack);
if (overrides == null)
return original.call(instance);
return overrides;
}
@WrapOperation(
method = "renderArmor",
at = @At(value = "INVOKE", target = "Lnet/minecraft/component/type/EquippableComponent;model()Ljava/util/Optional;"))
private Optional<Identifier> overrideLayers(
EquippableComponent instance, Operation<Optional<Identifier>> original, @Local(argsOnly = true) ItemStack itemStack
) {
// TODO: check that all armour items are naturally equippable and have the equppable component. otherwise our call here will not be reached.
var overrides = CustomGlobalArmorOverrides.overrideArmor(itemStack);
return overrides.or(() -> original.call(instance));
}
}

View File

@@ -1,45 +0,0 @@
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.features.texturepack.BakedModelExtra;
import net.minecraft.block.AbstractSkullBlock;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.client.render.entity.feature.HeadFeatureRenderer;
import net.minecraft.client.render.entity.model.EntityModel;
import net.minecraft.client.render.item.HeldItemRenderer;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemStack;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(HeadFeatureRenderer.class)
public class PatchHeadFeatureRenderer<T extends LivingEntity, M extends EntityModel<T>> {
@Shadow
@Final
private HeldItemRenderer heldItemRenderer;
@WrapOperation(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/entity/LivingEntity;FFFFFF)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/item/BlockItem;getBlock()Lnet/minecraft/block/Block;"))
private Block replaceSkull(BlockItem instance, Operation<Block> original, @Local ItemStack itemStack, @Local(argsOnly = true) T entity) {
var oldBlock = original.call(instance);
if (oldBlock instanceof AbstractSkullBlock) {
var bakedModel = this.heldItemRenderer.itemRenderer
.getModel(itemStack, entity.getWorld(), entity, 0);
if (bakedModel instanceof BakedModelExtra extra && extra.getHeadModel_firmament() != null)
return Blocks.ENCHANTING_TABLE; // Any non skull block. Let's choose the enchanting table because it is very distinct.
}
return oldBlock;
}
}

View File

@@ -0,0 +1,22 @@
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import moe.nea.firmament.features.texturepack.CustomGlobalArmorOverrides;
import net.minecraft.client.render.entity.equipment.EquipmentModelLoader;
import net.minecraft.client.render.entity.equipment.EquipmentRenderer;
import net.minecraft.item.equipment.EquipmentModel;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(EquipmentRenderer.class)
public class PatchLegacyArmorLayerSupport {
@WrapOperation(method = "render(Lnet/minecraft/item/equipment/EquipmentModel$LayerType;Lnet/minecraft/util/Identifier;Lnet/minecraft/client/model/Model;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/util/Identifier;)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/equipment/EquipmentModelLoader;get(Lnet/minecraft/util/Identifier;)Lnet/minecraft/item/equipment/EquipmentModel;"))
private EquipmentModel patchModelLayers(EquipmentModelLoader instance, Identifier id, Operation<EquipmentModel> original) {
var modelOverride = CustomGlobalArmorOverrides.overrideArmorLayer(id);
if (modelOverride != null) return modelOverride;
return original.call(instance, id);
}
}

View File

@@ -22,29 +22,29 @@ import java.util.Map;
@Mixin(ModelOverride.Deserializer.class)
public class PatchOverrideDeserializer {
@ModifyReturnValue(
method = "deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/minecraft/client/render/model/json/ModelOverride;",
at = @At(value = "RETURN"))
private ModelOverride addCustomOverrides(ModelOverride original, @Local JsonObject jsonObject) {
var originalData = (ModelOverrideData) original;
originalData.setFirmamentOverrides(CustomModelOverrideParser.parseCustomModelOverrides(jsonObject));
return original;
}
@ModifyReturnValue(
method = "deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/minecraft/client/render/model/json/ModelOverride;",
at = @At(value = "RETURN"))
private ModelOverride addCustomOverrides(ModelOverride original, @Local JsonObject jsonObject) {
var originalData = (ModelOverrideData) (Object) original;
originalData.setFirmamentOverrides(CustomModelOverrideParser.parseCustomModelOverrides(jsonObject));
return original;
}
@ModifyExpressionValue(
method = "deserializeMinPropertyValues(Lcom/google/gson/JsonObject;)Ljava/util/List;",
at = @At(value = "INVOKE", target = "Ljava/util/Map$Entry;getValue()Ljava/lang/Object;"))
private Object removeFirmamentPredicatesFromJsonIteration(Object original, @Local Map.Entry<String, JsonElement> entry) {
if (entry.getKey().startsWith("firmament:")) return new JsonPrimitive(0F);
return original;
}
@ModifyExpressionValue(
method = "deserializeMinPropertyValues(Lcom/google/gson/JsonObject;)Ljava/util/List;",
at = @At(value = "INVOKE", target = "Ljava/util/Map$Entry;getValue()Ljava/lang/Object;"))
private Object removeFirmamentPredicatesFromJsonIteration(Object original, @Local Map.Entry<String, JsonElement> entry) {
if (entry.getKey().startsWith("firmament:")) return new JsonPrimitive(0F);
return original;
}
@Inject(
method = "deserializeMinPropertyValues",
at = @At(value = "INVOKE", target = "Ljava/util/Map;entrySet()Ljava/util/Set;")
)
private void whatever(JsonObject object, CallbackInfoReturnable<List<ModelOverride.Condition>> cir,
@Local Map<Identifier, Float> maps) {
maps.entrySet().removeIf(it -> it.getKey().getNamespace().equals("firmament"));
}
@Inject(
method = "deserializeMinPropertyValues",
at = @At(value = "INVOKE", target = "Ljava/util/Map;entrySet()Ljava/util/Set;")
)
private void whatever(JsonObject object, CallbackInfoReturnable<List<ModelOverride.Condition>> cir,
@Local Map<Identifier, Float> maps) {
maps.entrySet().removeIf(it -> it.getKey().getNamespace().equals("firmament"));
}
}

View File

@@ -0,0 +1,27 @@
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import moe.nea.firmament.features.texturepack.JsonUnbakedModelFirmExtra;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.json.JsonUnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.SpriteIdentifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import java.util.function.Function;
/**
* @see JsonUnbakedModelDataHolder#storeExtraBaker_firmament
*/
@Mixin(targets = "net.minecraft.client.render.model.ModelBaker$BakerImpl")
public abstract class ProvideBakerToJsonUnbakedModelPatch implements Baker {
@WrapOperation(method = "bake(Lnet/minecraft/client/render/model/UnbakedModel;Lnet/minecraft/client/render/model/ModelBakeSettings;)Lnet/minecraft/client/render/model/BakedModel;", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/json/JsonUnbakedModel;bake(Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;Z)Lnet/minecraft/client/render/model/BakedModel;"))
private BakedModel provideExtraBakerToModel(JsonUnbakedModel instance, Function<SpriteIdentifier, Sprite> function, ModelBakeSettings modelBakeSettings, boolean bl, Operation<BakedModel> original) {
((JsonUnbakedModelFirmExtra) instance).storeExtraBaker_firmament(this);
return original.call(instance, function, modelBakeSettings, bl);
}
}

View File

@@ -0,0 +1,37 @@
package moe.nea.firmament.mixins.custommodels;
import moe.nea.firmament.events.BakeExtraModelsEvent;
import net.minecraft.client.render.model.BlockStatesLoader;
import net.minecraft.client.render.model.ItemModel;
import net.minecraft.client.render.model.ReferencedModelsCollector;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.util.ModelIdentifier;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
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.Map;
@Mixin(ReferencedModelsCollector.class)
public abstract class ReferenceCustomModelsPatch {
@Shadow
protected abstract void addTopLevelModel(ModelIdentifier modelId, UnbakedModel model);
@Shadow
@Final
private Map<Identifier, UnbakedModel> inputs;
@Inject(method = "addBlockStates", at = @At("RETURN"))
private void addFirmamentReferencedModels(
BlockStatesLoader.BlockStateDefinition definition, CallbackInfo ci
) {
inputs.keySet().stream().filter(it->it.toString().contains("firm")).forEach(System.out::println);
BakeExtraModelsEvent.Companion.publish(new BakeExtraModelsEvent(
(modelIdentifier, identifier) -> addTopLevelModel(modelIdentifier, new ItemModel(identifier))));
}
}

View File

@@ -17,33 +17,33 @@ import org.spongepowered.asm.mixin.injection.ModifyArg;
@Mixin(ModelOverrideList.class)
public class TestForFirmamentOverridePredicatesPatch {
@ModifyArg(method = "<init>(Lnet/minecraft/client/render/model/Baker;Lnet/minecraft/client/render/model/json/JsonUnbakedModel;Ljava/util/List;)V",
at = @At(
value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z"
))
public Object onInit(
Object element,
@Local ModelOverride modelOverride
) {
var bakedOverride = (ModelOverrideList.BakedOverride) element;
((BakedOverrideData) bakedOverride)
.setFirmamentOverrides(((ModelOverrideData) modelOverride).getFirmamentOverrides());
return element;
}
@ModifyArg(method = "<init>(Lnet/minecraft/client/render/model/Baker;Ljava/util/List;)V",
at = @At(
value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z"
))
public Object onInit(
Object element,
@Local ModelOverride modelOverride
) {
var bakedOverride = (ModelOverrideList.BakedOverride) element;
((BakedOverrideData) (Object) bakedOverride)
.setFirmamentOverrides(((ModelOverrideData) (Object) modelOverride).getFirmamentOverrides());
return element;
}
@ModifyExpressionValue(method = "apply", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/json/ModelOverrideList$BakedOverride;test([F)Z"))
public boolean testFirmamentOverrides(boolean originalValue,
@Local ModelOverrideList.BakedOverride bakedOverride,
@Local(argsOnly = true) ItemStack stack) {
if (!originalValue) return false;
var overrideData = (BakedOverrideData) bakedOverride;
var overrides = overrideData.getFirmamentOverrides();
if (overrides == null) return true;
if (!CustomSkyBlockTextures.TConfig.INSTANCE.getEnableModelOverrides()) return false;
for (FirmamentModelPredicate firmamentOverride : overrides) {
if (!firmamentOverride.test(stack))
return false;
}
return true;
}
@ModifyExpressionValue(method = "getModel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/json/ModelOverrideList$BakedOverride;test([F)Z"))
public boolean testFirmamentOverrides(boolean originalValue,
@Local ModelOverrideList.BakedOverride bakedOverride,
@Local(argsOnly = true) ItemStack stack) {
if (!originalValue) return false;
var overrideData = (BakedOverrideData) (Object) bakedOverride;
var overrides = overrideData.getFirmamentOverrides();
if (overrides == null) return true;
if (!CustomSkyBlockTextures.TConfig.INSTANCE.getEnableModelOverrides()) return false;
for (FirmamentModelPredicate firmamentOverride : overrides) {
if (!firmamentOverride.test(stack))
return false;
}
return true;
}
}

View File

@@ -1,21 +1,24 @@
package moe.nea.firmament.events
import java.util.function.Consumer
import java.util.function.BiConsumer
import net.minecraft.client.render.model.ReferencedModelsCollector
import net.minecraft.client.util.ModelIdentifier
import net.minecraft.util.Identifier
// TODO: Rename this event, since it is not really directly baking models anymore
class BakeExtraModelsEvent(
private val addItemModel: Consumer<ModelIdentifier>,
private val addAnyModel: Consumer<ModelIdentifier>,
private val addAnyModel: BiConsumer<ModelIdentifier, Identifier>,
) : FirmamentEvent() {
fun addNonItemModel(modelIdentifier: ModelIdentifier) {
this.addAnyModel.accept(modelIdentifier)
}
fun addNonItemModel(modelIdentifier: ModelIdentifier, identifier: Identifier) {
this.addAnyModel.accept(modelIdentifier, identifier)
}
fun addItemModel(modelIdentifier: ModelIdentifier) {
this.addItemModel.accept(modelIdentifier)
}
fun addItemModel(modelIdentifier: ModelIdentifier) {
addNonItemModel(
modelIdentifier,
modelIdentifier.id.withPrefixedPath(ReferencedModelsCollector.ITEM_DIRECTORY))
}
companion object : FirmamentEventBus<BakeExtraModelsEvent>()
companion object : FirmamentEventBus<BakeExtraModelsEvent>()
}

View File

@@ -2,35 +2,37 @@ package moe.nea.firmament.events
import java.util.Optional
import kotlin.jvm.optionals.getOrNull
import net.minecraft.client.render.item.ItemModels
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.BakedModelManager
import net.minecraft.client.util.ModelIdentifier
import net.minecraft.item.ItemStack
import moe.nea.firmament.util.ErrorUtil
import moe.nea.firmament.util.collections.WeakCache
data class CustomItemModelEvent(
val itemStack: ItemStack,
var overrideModel: ModelIdentifier? = null,
val itemStack: ItemStack,
var overrideModel: ModelIdentifier? = null,
) : FirmamentEvent() {
companion object : FirmamentEventBus<CustomItemModelEvent>() {
val cache =
WeakCache.memoize<ItemStack, BakedModelManager, Optional<BakedModel>>("CustomItemModels") { stack, models ->
val modelId = getModelIdentifier(stack) ?: return@memoize Optional.empty()
val bakedModel = models.getModel(modelId)
if (bakedModel === models.missingModel) return@memoize Optional.empty()
Optional.of(bakedModel)
}
companion object : FirmamentEventBus<CustomItemModelEvent>() {
val cache =
WeakCache.memoize<ItemStack, ItemModels, Optional<BakedModel>>("CustomItemModels") { stack, models ->
val modelId = getModelIdentifier(stack) ?: return@memoize Optional.empty()
ErrorUtil.softCheck("Model Id needs to have an inventory variant", modelId.variant() == "inventory")
val bakedModel = models.getModel(modelId.id)
if (bakedModel == null || bakedModel === models.missingModelSupplier.get()) return@memoize Optional.empty()
Optional.of(bakedModel)
}
@JvmStatic
fun getModelIdentifier(itemStack: ItemStack?): ModelIdentifier? {
if (itemStack == null) return null
return publish(CustomItemModelEvent(itemStack)).overrideModel
}
@JvmStatic
fun getModelIdentifier(itemStack: ItemStack?): ModelIdentifier? {
if (itemStack == null) return null
return publish(CustomItemModelEvent(itemStack)).overrideModel
}
@JvmStatic
fun getModel(itemStack: ItemStack?, thing: BakedModelManager): BakedModel? {
if (itemStack == null) return null
return cache.invoke(itemStack, thing).getOrNull()
}
}
@JvmStatic
fun getModel(itemStack: ItemStack?, thing: ItemModels): BakedModel? {
if (itemStack == null) return null
return cache.invoke(itemStack, thing).getOrNull()
}
}
}

View File

@@ -0,0 +1,9 @@
package moe.nea.firmament.events
/**
* Called in a devenv after minecraft has been initialized. This event should be used to force instantiation of lazy
* variables (and similar late init) to cause any possible issues to materialize.
*/
class DebugInstantiateEvent : FirmamentEvent() {
companion object : FirmamentEventBus<DebugInstantiateEvent>()
}

View File

@@ -5,31 +5,28 @@ import java.util.concurrent.Executor
import net.minecraft.resource.ReloadableResourceManagerImpl
import net.minecraft.resource.ResourceManager
import net.minecraft.resource.ResourceReloader
import net.minecraft.util.profiler.Profiler
data class FinalizeResourceManagerEvent(
val resourceManager: ReloadableResourceManagerImpl,
val resourceManager: ReloadableResourceManagerImpl,
) : FirmamentEvent() {
companion object : FirmamentEventBus<FinalizeResourceManagerEvent>()
companion object : FirmamentEventBus<FinalizeResourceManagerEvent>()
inline fun registerOnApply(name: String, crossinline function: () -> Unit) {
resourceManager.registerReloader(object : ResourceReloader {
override fun reload(
synchronizer: ResourceReloader.Synchronizer,
manager: ResourceManager?,
prepareProfiler: Profiler?,
applyProfiler: Profiler?,
prepareExecutor: Executor?,
applyExecutor: Executor
): CompletableFuture<Void> {
return CompletableFuture.completedFuture(Unit)
.thenCompose(synchronizer::whenPrepared)
.thenAcceptAsync({ function() }, applyExecutor)
}
inline fun registerOnApply(name: String, crossinline function: () -> Unit) {
resourceManager.registerReloader(object : ResourceReloader {
override fun reload(
synchronizer: ResourceReloader.Synchronizer,
manager: ResourceManager,
prepareExecutor: Executor,
applyExecutor: Executor
): CompletableFuture<Void> {
return CompletableFuture.completedFuture(Unit)
.thenCompose(synchronizer::whenPrepared)
.thenAcceptAsync({ function() }, applyExecutor)
}
override fun getName(): String {
return name
}
})
}
override fun getName(): String {
return name
}
})
}
}

View File

@@ -37,7 +37,7 @@ data class IsSlotProtectedEvent(
val event = IsSlotProtectedEvent(slot, action, false, itemStackOverride)
publish(event)
if (event.isProtected && !event.silent) {
MC.player?.sendMessage(Text.translatable("firmament.protectitem").append(event.itemStack.name))
MC.sendChat(Text.translatable("firmament.protectitem").append(event.itemStack.name))
CommonSoundEffects.playFailure()
}
return event.isProtected

View File

@@ -1,24 +0,0 @@
package moe.nea.firmament.events
import com.mojang.datafixers.util.Pair
import java.util.function.Consumer
import net.minecraft.client.gl.ShaderProgram
import net.minecraft.client.render.VertexFormat
import net.minecraft.resource.ResourceFactory
import moe.nea.firmament.Firmament
data class RegisterCustomShadersEvent(
val list: MutableList<Pair<ShaderProgram, Consumer<ShaderProgram>>>,
val resourceFactory: ResourceFactory,
) : FirmamentEvent() {
companion object : FirmamentEventBus<RegisterCustomShadersEvent>()
fun register(name: String, vertexFormat: VertexFormat, saver: Consumer<ShaderProgram>) {
require(name.startsWith("firmament_"))
try {
list.add(Pair.of(ShaderProgram(resourceFactory, name, vertexFormat), saver))
} catch (ex: Exception) {
Firmament.logger.fatal("Could not load firmament shader $name", ex)
}
}
}

View File

@@ -3,20 +3,19 @@
package moe.nea.firmament.events
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.texture.Sprite
import net.minecraft.screen.slot.Slot
import net.minecraft.util.Identifier
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.render.drawGuiTexture
interface SlotRenderEvents {
val context: DrawContext
val slot: Slot
val mouseX: Int
val mouseY: Int
val delta: Float
fun highlight(sprite: Sprite) {
context.drawSprite(
fun highlight(sprite: Identifier) {
context.drawGuiTexture(
slot.x, slot.y, 0, 16, 16,
sprite
)
@@ -24,9 +23,6 @@ interface SlotRenderEvents {
data class Before(
override val context: DrawContext, override val slot: Slot,
override val mouseX: Int,
override val mouseY: Int,
override val delta: Float
) : FirmamentEvent(),
SlotRenderEvents {
companion object : FirmamentEventBus<Before>()
@@ -34,9 +30,6 @@ interface SlotRenderEvents {
data class After(
override val context: DrawContext, override val slot: Slot,
override val mouseX: Int,
override val mouseY: Int,
override val delta: Float
) : FirmamentEvent(),
SlotRenderEvents {
companion object : FirmamentEventBus<After>()

View File

@@ -1,7 +1,10 @@
package moe.nea.firmament.events
class WorldReadyEvent : FirmamentEvent() {
companion object : FirmamentEventBus<WorldReadyEvent>()
companion object : FirmamentEventBus<WorldReadyEvent>()
// class FullyLoaded : FirmamentEvent() {
// companion object : FirmamentEventBus<FullyLoaded>() {
// TODO: check WorldLoadingState
// }
// }
}

View File

@@ -17,10 +17,7 @@ import net.minecraft.util.math.Vec3d
data class WorldRenderLastEvent(
val matrices: MatrixStack,
val tickCounter: RenderTickCounter,
val renderBlockOutline: Boolean,
val camera: Camera,
val gameRenderer: GameRenderer,
val lightmapTextureManager: LightmapTextureManager,
val vertexConsumers: VertexConsumerProvider.Immediate,
) : FirmamentEvent() {
companion object : FirmamentEventBus<WorldRenderLastEvent>()

View File

@@ -13,8 +13,10 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlin.math.min
import net.minecraft.client.gui.screen.ChatScreen
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.texture.NativeImage
import net.minecraft.client.texture.NativeImageBackedTexture
import net.minecraft.scoreboard.ScoreboardCriterion.RenderType
import net.minecraft.text.ClickEvent
import net.minecraft.text.HoverEvent
import net.minecraft.text.Style
@@ -28,6 +30,7 @@ import moe.nea.firmament.events.ScreenRenderPostEvent
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.render.drawTexture
import moe.nea.firmament.util.transformEachRecursively
import moe.nea.firmament.util.unformattedString

View File

@@ -37,10 +37,10 @@ object DeveloperFeatures : FirmamentFeature {
builder.directory(gradleDir.toFile())
builder.inheritIO()
val process = builder.start()
MC.player?.sendMessage(Text.translatable("firmament.dev.resourcerebuild.start"))
MC.sendChat(Text.translatable("firmament.dev.resourcerebuild.start"))
val startTime = TimeMark.now()
process.toHandle().onExit().thenApply {
MC.player?.sendMessage(Text.stringifiedTranslatable(
MC.sendChat(Text.stringifiedTranslatable(
"firmament.dev.resourcerebuild.done",
startTime.passedTime()))
Unit

View File

@@ -9,6 +9,7 @@ import net.minecraft.entity.Entity
import net.minecraft.entity.LivingEntity
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import net.minecraft.nbt.NbtOps
import net.minecraft.text.Text
import net.minecraft.text.TextCodecs
import net.minecraft.util.hit.BlockHitResult
@@ -54,15 +55,13 @@ object PowerUserTools : FirmamentFeature {
var lastCopiedStack: Pair<ItemStack, Text>? = null
set(value) {
field = value
if (value != null)
lastCopiedStackViewTime = true
if (value != null) lastCopiedStackViewTime = true
}
var lastCopiedStackViewTime = false
@Subscribe
fun resetLastCopiedStack(event: TickEvent) {
if (!lastCopiedStackViewTime)
lastCopiedStack = null
if (!lastCopiedStackViewTime) lastCopiedStack = null
lastCopiedStackViewTime = false
}
@@ -154,13 +153,13 @@ object PowerUserTools : FirmamentFeature {
}
ClipboardUtils.setTextContent(skullTexture.toString())
lastCopiedStack =
Pair(
item,
Text.stringifiedTranslatable("firmament.tooltip.copied.skull-id", skullTexture.toString())
)
Pair(item, Text.stringifiedTranslatable("firmament.tooltip.copied.skull-id", skullTexture.toString()))
println("Copied skull id: $skullTexture")
} else if (it.matches(TConfig.copyItemStack)) {
ClipboardUtils.setTextContent(item.encode(MC.currentOrDefaultRegistries).toPrettyString())
ClipboardUtils.setTextContent(
ItemStack.CODEC
.encodeStart(MC.currentOrDefaultRegistries.getOps(NbtOps.INSTANCE), item)
.orThrow.toPrettyString())
lastCopiedStack = Pair(item, Text.stringifiedTranslatable("firmament.tooltip.copied.stack"))
}
}

View File

@@ -104,12 +104,13 @@ object AncestralSpadeSolver : SubscriptionOwner {
if (!isEnabled()) return
RenderInWorldContext.renderInWorld(event) {
nextGuess?.let {
color(1f, 1f, 0f, 0.5f)
tinyBlock(it, 1f)
tinyBlock(it, 1f, 0x80FFFFFF.toInt())
// TODO: replace this
color(1f, 1f, 0f, 1f)
tracer(it, lineWidth = 3f)
}
if (particlePositions.size > 2 && lastDing.passedTime() < 10.seconds && nextGuess != null) {
// TODO: replace this // TODO: add toggle
color(0f, 1f, 0f, 0.7f)
line(particlePositions)
}

View File

@@ -1,6 +1,6 @@
package moe.nea.firmament.features.diana
import me.shedaniel.math.Color
import kotlin.time.Duration.Companion.seconds
import net.minecraft.particle.ParticleTypes
import net.minecraft.util.math.BlockPos
@@ -20,125 +20,125 @@ import moe.nea.firmament.util.render.RenderInWorldContext.Companion.renderInWorl
object NearbyBurrowsSolver : SubscriptionOwner {
private val recentlyDugBurrows: MutableMap<BlockPos, TimeMark> = mutableMapWithMaxSize(20)
private val recentEnchantParticles: MutableMap<BlockPos, TimeMark> = mutableMapWithMaxSize(500)
private var lastBlockClick: BlockPos? = null
private val recentlyDugBurrows: MutableMap<BlockPos, TimeMark> = mutableMapWithMaxSize(20)
private val recentEnchantParticles: MutableMap<BlockPos, TimeMark> = mutableMapWithMaxSize(500)
private var lastBlockClick: BlockPos? = null
enum class BurrowType {
START, MOB, TREASURE
}
enum class BurrowType {
START, MOB, TREASURE
}
val burrows = mutableMapOf<BlockPos, BurrowType>()
val burrows = mutableMapOf<BlockPos, BurrowType>()
@Subscribe
fun onChatEvent(event: ProcessChatEvent) {
val lastClickedBurrow = lastBlockClick ?: return
if (event.unformattedString.startsWith("You dug out a Griffin Burrow!") ||
event.unformattedString.startsWith(" ☠ You were killed by") ||
event.unformattedString.startsWith("You finished the Griffin burrow chain!")
) {
markAsDug(lastClickedBurrow)
burrows.remove(lastClickedBurrow)
}
}
@Subscribe
fun onChatEvent(event: ProcessChatEvent) {
val lastClickedBurrow = lastBlockClick ?: return
if (event.unformattedString.startsWith("You dug out a Griffin Burrow!") ||
event.unformattedString.startsWith(" ☠ You were killed by") ||
event.unformattedString.startsWith("You finished the Griffin burrow chain!")
) {
markAsDug(lastClickedBurrow)
burrows.remove(lastClickedBurrow)
}
}
fun wasRecentlyDug(blockPos: BlockPos): Boolean {
val lastDigTime = recentlyDugBurrows[blockPos] ?: TimeMark.farPast()
return lastDigTime.passedTime() < 10.seconds
}
fun wasRecentlyDug(blockPos: BlockPos): Boolean {
val lastDigTime = recentlyDugBurrows[blockPos] ?: TimeMark.farPast()
return lastDigTime.passedTime() < 10.seconds
}
fun markAsDug(blockPos: BlockPos) {
recentlyDugBurrows[blockPos] = TimeMark.now()
}
fun markAsDug(blockPos: BlockPos) {
recentlyDugBurrows[blockPos] = TimeMark.now()
}
fun wasRecentlyEnchanted(blockPos: BlockPos): Boolean {
val lastEnchantTime = recentEnchantParticles[blockPos] ?: TimeMark.farPast()
return lastEnchantTime.passedTime() < 4.seconds
}
fun wasRecentlyEnchanted(blockPos: BlockPos): Boolean {
val lastEnchantTime = recentEnchantParticles[blockPos] ?: TimeMark.farPast()
return lastEnchantTime.passedTime() < 4.seconds
}
fun markAsEnchanted(blockPos: BlockPos) {
recentEnchantParticles[blockPos] = TimeMark.now()
}
fun markAsEnchanted(blockPos: BlockPos) {
recentEnchantParticles[blockPos] = TimeMark.now()
}
@Subscribe
fun onParticles(event: ParticleSpawnEvent) {
if (!DianaWaypoints.TConfig.nearbyWaypoints) return
@Subscribe
fun onParticles(event: ParticleSpawnEvent) {
if (!DianaWaypoints.TConfig.nearbyWaypoints) return
val position: BlockPos = event.position.toBlockPos().down()
val position: BlockPos = event.position.toBlockPos().down()
if (wasRecentlyDug(position)) return
if (wasRecentlyDug(position)) return
val isEven50Spread = (event.offset.x == 0.5f && event.offset.z == 0.5f)
val isEven50Spread = (event.offset.x == 0.5f && event.offset.z == 0.5f)
if (event.particleEffect.type == ParticleTypes.ENCHANT) {
if (event.count == 5 && event.speed == 0.05F && event.offset.y == 0.4F && isEven50Spread) {
markAsEnchanted(position)
}
return
}
if (event.particleEffect.type == ParticleTypes.ENCHANT) {
if (event.count == 5 && event.speed == 0.05F && event.offset.y == 0.4F && isEven50Spread) {
markAsEnchanted(position)
}
return
}
if (!wasRecentlyEnchanted(position)) return
if (!wasRecentlyEnchanted(position)) return
if (event.particleEffect.type == ParticleTypes.ENCHANTED_HIT
&& event.count == 4
&& event.speed == 0.01F
&& event.offset.y == 0.1f
&& isEven50Spread
) {
burrows[position] = BurrowType.START
}
if (event.particleEffect.type == ParticleTypes.CRIT
&& event.count == 3
&& event.speed == 0.01F
&& event.offset.y == 0.1F
&& isEven50Spread
) {
burrows[position] = BurrowType.MOB
}
if (event.particleEffect.type == ParticleTypes.DRIPPING_LAVA
&& event.count == 2
&& event.speed == 0.01F
&& event.offset.y == 0.1F
&& event.offset.x == 0.35F && event.offset.z == 0.35f
) {
burrows[position] = BurrowType.TREASURE
}
}
if (event.particleEffect.type == ParticleTypes.ENCHANTED_HIT
&& event.count == 4
&& event.speed == 0.01F
&& event.offset.y == 0.1f
&& isEven50Spread
) {
burrows[position] = BurrowType.START
}
if (event.particleEffect.type == ParticleTypes.CRIT
&& event.count == 3
&& event.speed == 0.01F
&& event.offset.y == 0.1F
&& isEven50Spread
) {
burrows[position] = BurrowType.MOB
}
if (event.particleEffect.type == ParticleTypes.DRIPPING_LAVA
&& event.count == 2
&& event.speed == 0.01F
&& event.offset.y == 0.1F
&& event.offset.x == 0.35F && event.offset.z == 0.35f
) {
burrows[position] = BurrowType.TREASURE
}
}
@Subscribe
fun onRender(event: WorldRenderLastEvent) {
if (!DianaWaypoints.TConfig.nearbyWaypoints) return
renderInWorld(event) {
for ((location, burrow) in burrows) {
when (burrow) {
BurrowType.START -> color(.2f, .8f, .2f, 0.4f)
BurrowType.MOB -> color(0.3f, 0.4f, 0.9f, 0.4f)
BurrowType.TREASURE -> color(1f, 0.7f, 0.2f, 0.4f)
}
block(location)
}
}
}
@Subscribe
fun onRender(event: WorldRenderLastEvent) {
if (!DianaWaypoints.TConfig.nearbyWaypoints) return
renderInWorld(event) {
for ((location, burrow) in burrows) {
val color = when (burrow) {
BurrowType.START -> Color.ofRGBA(.2f, .8f, .2f, 0.4f)
BurrowType.MOB -> Color.ofRGBA(0.3f, 0.4f, 0.9f, 0.4f)
BurrowType.TREASURE -> Color.ofRGBA(1f, 0.7f, 0.2f, 0.4f)
}
block(location, color.color)
}
}
}
@Subscribe
fun onSwapWorld(worldReadyEvent: WorldReadyEvent) {
burrows.clear()
recentEnchantParticles.clear()
recentlyDugBurrows.clear()
lastBlockClick = null
}
@Subscribe
fun onSwapWorld(worldReadyEvent: WorldReadyEvent) {
burrows.clear()
recentEnchantParticles.clear()
recentlyDugBurrows.clear()
lastBlockClick = null
}
fun onBlockClick(blockPos: BlockPos) {
if (!DianaWaypoints.TConfig.nearbyWaypoints) return
burrows.remove(blockPos)
lastBlockClick = blockPos
}
fun onBlockClick(blockPos: BlockPos) {
if (!DianaWaypoints.TConfig.nearbyWaypoints) return
burrows.remove(blockPos)
lastBlockClick = blockPos
}
override val delegateFeature: FirmamentFeature
get() = DianaWaypoints
override val delegateFeature: FirmamentFeature
get() = DianaWaypoints
}
fun Position.toBlockPos(): BlockPos {
return BlockPos(MathHelper.floor(x), MathHelper.floor(y), MathHelper.floor(z))
return BlockPos(MathHelper.floor(x), MathHelper.floor(y), MathHelper.floor(z))
}

View File

@@ -69,7 +69,7 @@ object CraftingOverlay : FirmamentFeature {
if (!slot.hasStack()) {
val itemStack = SBItemStack(expectedItem)?.asImmutableItemStack() ?: return
event.context.drawItem(itemStack, event.slot.x, event.slot.y)
event.context.drawItemInSlot(
event.context.drawStackOverlay(
MC.font,
itemStack,
event.slot.x,

View File

@@ -4,6 +4,7 @@ package moe.nea.firmament.features.inventory
import java.awt.Color
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.render.RenderLayer
import net.minecraft.item.ItemStack
import net.minecraft.util.Formatting
import net.minecraft.util.Identifier
@@ -16,6 +17,7 @@ import moe.nea.firmament.util.MC
import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.collections.lastNotNullOfOrNull
import moe.nea.firmament.util.collections.memoizeIdentity
import moe.nea.firmament.util.render.drawGuiTexture
import moe.nea.firmament.util.unformattedString
object ItemRarityCosmetics : FirmamentFeature {
@@ -43,10 +45,10 @@ object ItemRarityCosmetics : FirmamentFeature {
"SUPREME" to Formatting.DARK_RED,
).mapValues {
val c = Color(it.value.colorValue!!)
Triple(c.red / 255F, c.green / 255F, c.blue / 255F)
c.rgb
}
private fun getSkyblockRarity0(itemStack: ItemStack): Triple<Float, Float, Float>? {
private fun getSkyblockRarity0(itemStack: ItemStack): Int? {
return itemStack.loreAccordingToNbt.lastNotNullOfOrNull {
val entry = it.unformattedString
rarityToColor.entries.find { (k, v) -> k in entry }?.value
@@ -57,13 +59,13 @@ object ItemRarityCosmetics : FirmamentFeature {
fun drawItemStackRarity(drawContext: DrawContext, x: Int, y: Int, item: ItemStack) {
val (r, g, b) = getSkyblockRarity(item) ?: return
drawContext.drawSprite(
val rgb = getSkyblockRarity(item) ?: return
drawContext.drawGuiTexture(
RenderLayer::getGuiTextured,
Identifier.of("firmament:item_rarity_background"),
x, y,
0,
16, 16,
MC.guiAtlasManager.getSprite(Identifier.of("firmament:item_rarity_background")),
r, g, b, 1F
rgb
)
}

View File

@@ -7,6 +7,7 @@ import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.petData
import moe.nea.firmament.util.render.drawGuiTexture
import moe.nea.firmament.util.useMatch
object PetFeatures : FirmamentFeature {
@@ -28,9 +29,9 @@ object PetFeatures : FirmamentFeature {
val stack = event.slot.stack
if (stack.petData?.active == true)
petMenuTitle.useMatch(MC.screenName ?: return) {
event.context.drawSprite(
event.context.drawGuiTexture(
event.slot.x, event.slot.y, 0, 16, 16,
MC.guiAtlasManager.getSprite(Identifier.of("firmament:selected_pet_background"))
Identifier.of("firmament:selected_pet_background")
)
}
}

View File

@@ -35,6 +35,7 @@ import moe.nea.firmament.util.mc.ScreenUtil.getSlotByIndex
import moe.nea.firmament.util.mc.SlotUtils.swapWithHotBar
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.render.GuiRenderLayers
import moe.nea.firmament.util.render.drawLine
import moe.nea.firmament.util.skyblockUUID
import moe.nea.firmament.util.unformattedString
@@ -211,6 +212,11 @@ object SlotLocking : FirmamentFeature {
}
if (it.matches(TConfig.slotBind)) {
storedLockingSlot = null
val boundSlots = DConfig.data?.boundSlots ?: return
if (slot != null)
boundSlots.entries.removeIf {
it.value == slot.index || it.key == slot.index
}
}
}
@@ -331,24 +337,22 @@ object SlotLocking : FirmamentFeature {
val isSlotLocked = it.slot.inventory is PlayerInventory && it.slot.index in (lockedSlots ?: setOf())
val isUUIDLocked = (it.slot.stack?.skyblockUUID) in (lockedUUIDs ?: setOf())
if (isSlotLocked || isUUIDLocked) {
RenderSystem.disableDepthTest()
it.context.drawSprite(
it.slot.x, it.slot.y, 0,
it.context.drawGuiTexture(
GuiRenderLayers.GUI_TEXTURED_NO_DEPTH,
when {
isSlotLocked ->
(Identifier.of("firmament:slot_locked"))
isUUIDLocked ->
(Identifier.of("firmament:uuid_locked"))
else ->
error("unreachable")
},
it.slot.x, it.slot.y,
16, 16,
MC.guiAtlasManager.getSprite(
when {
isSlotLocked ->
(Identifier.of("firmament:slot_locked"))
isUUIDLocked ->
(Identifier.of("firmament:uuid_locked"))
else ->
error("unreachable")
}
)
-1
)
RenderSystem.enableDepthTest()
}
}
}

View File

@@ -18,6 +18,7 @@ import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.collections.memoize
import moe.nea.firmament.util.render.drawGuiTexture
@Serializable
data class InventoryButton(
@@ -54,13 +55,13 @@ data class InventoryButton(
}
fun render(context: DrawContext) {
context.drawSprite(
context.drawGuiTexture(
0,
0,
0,
dimensions.width,
dimensions.height,
MC.guiAtlasManager.getSprite(Identifier.of("firmament:inventory_button_background"))
Identifier.of("firmament:inventory_button_background")
)
context.drawItem(getItem(), 1, 1)
}

View File

@@ -84,7 +84,6 @@ class InventoryButtonEditor(
context.matrices.push()
context.matrices.translate(0f, 0f, -10f)
context.fill(lastGuiRect.minX, lastGuiRect.minY, lastGuiRect.maxX, lastGuiRect.maxY, -1)
context.setShaderColor(1f, 1f, 1f, 1f)
context.matrices.pop()
for (button in buttons) {
val buttonPosition = button.getBounds(lastGuiRect)

View File

@@ -40,6 +40,7 @@ sealed interface StorageBackingHandle {
* representable as a [StorageBackingHandle], meaning another screen is open, for example the enderchest icon
* selection screen.
*/
@OptIn(ExperimentalContracts::class)
fun fromScreen(screen: Screen?): StorageBackingHandle? {
contract {
returnsNotNull() implies (screen != null)

View File

@@ -21,6 +21,7 @@ import moe.nea.firmament.util.MoulConfigUtils.clickMCComponentInPlace
import moe.nea.firmament.util.MoulConfigUtils.drawMCComponentInPlace
import moe.nea.firmament.util.assertTrueOr
import moe.nea.firmament.util.customgui.customGui
import moe.nea.firmament.util.render.drawGuiTexture
class StorageOverlayScreen : Screen(Text.literal("")) {
@@ -162,13 +163,11 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
context.drawGuiTexture(upperBackgroundSprite,
measurements.x,
measurements.y,
0,
measurements.overviewWidth,
measurements.overviewHeight)
context.drawGuiTexture(playerInventorySprite,
measurements.playerX,
measurements.playerY,
0,
PLAYER_WIDTH,
PLAYER_HEIGHT)
}
@@ -188,7 +187,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
items.withIndex().forEach { (index, item) ->
val (x, y) = getPlayerInventorySlotPosition(index)
context.drawItem(item, x, y, 0)
context.drawItemInSlot(textRenderer, item, x, y)
context.drawStackOverlay(textRenderer, item, x, y)
}
}
@@ -357,7 +356,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) {
val slotY = (index / 9) * SLOT_SIZE + y + 4 + textRenderer.fontHeight + 1
if (slots == null) {
context.drawItem(stack, slotX, slotY)
context.drawItemInSlot(textRenderer, stack, slotX, slotY)
context.drawStackOverlay(textRenderer, stack, slotX, slotY)
} else {
val slot = slots[index]
slot.x = slotX - slotOffset.x

View File

@@ -111,7 +111,7 @@ class StorageOverviewScreen() : Screen(Text.empty()) {
context.fill(x, y, x + 18, y + 18, 0x40808080.toInt())
}
context.drawItem(stack, x + 1, y + 1)
context.drawItemInSlot(MC.font, stack, x + 1, y + 1)
context.drawStackOverlay(MC.font, stack, x + 1, y + 1)
}
}

View File

@@ -1,6 +1,6 @@
package moe.nea.firmament.features.mining
import net.minecraft.util.Identifier
import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.SlotRenderEvents
import moe.nea.firmament.gui.config.ManagedConfig
@@ -19,10 +19,8 @@ object CommissionFeatures {
if (!Config.highlightCompletedCommissions) return
if (MC.screenName != "Commissions") return
val stack = event.slot.stack
if(stack.loreAccordingToNbt.any { it.unformattedString == "COMPLETED" }) {
event.highlight(
MC.guiAtlasManager.getSprite(Identifier.of("firmament:completed_commission_background"))
)
if (stack.loreAccordingToNbt.any { it.unformattedString == "COMPLETED" }) {
event.highlight(Firmament.identifier("completed_commission_background"))
}
}
}

View File

@@ -9,9 +9,7 @@ import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.item.Items
import net.minecraft.screen.GenericContainerScreenHandler
import net.minecraft.screen.ScreenHandler
import net.minecraft.screen.slot.Slot
import net.minecraft.screen.slot.SlotActionType
import net.minecraft.text.Text
import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
@@ -31,6 +29,7 @@ import moe.nea.firmament.util.customgui.customGui
import moe.nea.firmament.util.mc.CommonTextures
import moe.nea.firmament.util.mc.SlotUtils.clickRightMouseButton
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.render.drawGuiTexture
import moe.nea.firmament.util.unformattedString
import moe.nea.firmament.util.useMatch
@@ -81,7 +80,7 @@ object HotmPresets {
override fun render(drawContext: DrawContext, delta: Float, mouseX: Int, mouseY: Int) {
drawContext.drawGuiTexture(
CommonTextures.genericWidget(),
bounds.x, bounds.y, 0,
bounds.x, bounds.y,
bounds.width,
bounds.height,
)
@@ -191,7 +190,7 @@ object HotmPresets {
if (hotmInventoryName == MC.screenName
&& event.slot.stack.displayNameAccordingToNbt.unformattedString in highlightedPerks
) {
event.highlight(MC.guiAtlasManager.getSprite(Firmament.identifier("hotm_perk_preset")))
event.highlight((Firmament.identifier("hotm_perk_preset")))
}
}

View File

@@ -1,11 +1,30 @@
package moe.nea.firmament.features.texturepack
import net.fabricmc.fabric.api.renderer.v1.model.WrapperBakedModel as WrapperBakedModelFabric
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.WrapperBakedModel
import moe.nea.firmament.util.ErrorUtil
interface BakedModelExtra {
companion object {
@JvmStatic
fun cast(originalModel: BakedModel): BakedModelExtra? {
var p = originalModel
for (i in 0..256) {
p = when (p) {
is BakedModelExtra -> return p
is WrapperBakedModel -> p.wrapped
is WrapperBakedModelFabric -> WrapperBakedModelFabric.unwrap(p)
else -> break
}
}
ErrorUtil.softError("Could not find a baked model for $originalModel")
return null
}
}
var tintOverrides_firmament: TintOverrides?
fun getHeadModel_firmament(): BakedModel?
fun setHeadModel_firmament(headModel: BakedModel?)
fun setHeadModel_firmament(headModel: BakedModel?)
}

View File

@@ -244,7 +244,7 @@ object CustomBlockTextures {
.flatMap { it.lookup.values }
.flatten()
.mapTo(mutableSetOf()) { it.replacement.blockModelIdentifier }
.forEach { event.addNonItemModel(it) }
.forEach { event.addNonItemModel(it, it.id) }
}
private fun prepare(manager: ResourceManager): BakedReplacements {
@@ -263,7 +263,7 @@ object CustomBlockTextures {
val island = SkyBlockIsland.forMode(mode)
val islandMpa = map.getOrPut(island, ::mutableMapOf)
for ((blockId, replacement) in json.replacements) {
val block = MC.defaultRegistries.getWrapperOrThrow(RegistryKeys.BLOCK)
val block = MC.defaultRegistries.getOrThrow(RegistryKeys.BLOCK)
.getOptional(RegistryKey.of(RegistryKeys.BLOCK, blockId))
.getOrNull()
if (block == null) {

View File

@@ -3,13 +3,13 @@
package moe.nea.firmament.features.texturepack
import java.util.Optional
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.UseSerializers
import kotlin.jvm.optionals.getOrNull
import net.minecraft.item.ArmorMaterial
import net.minecraft.item.ItemStack
import net.minecraft.item.equipment.EquipmentModel
import net.minecraft.resource.ResourceManager
import net.minecraft.resource.SinglePreparationResourceReloader
import net.minecraft.util.Identifier
@@ -17,90 +17,139 @@ import net.minecraft.util.profiler.Profiler
import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.FinalizeResourceManagerEvent
import moe.nea.firmament.events.subscription.SubscriptionOwner
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.features.texturepack.CustomGlobalTextures.logger
import moe.nea.firmament.util.IdentifierSerializer
import moe.nea.firmament.util.collections.WeakCache
import moe.nea.firmament.util.skyBlockId
object CustomGlobalArmorOverrides : SubscriptionOwner {
@Serializable
data class ArmorOverride(
@SerialName("item_ids")
val itemIds: List<String>,
val layers: List<ArmorOverrideLayer>,
val overrides: List<ArmorOverrideOverride> = listOf(),
) {
@Transient
val bakedLayers = bakeLayers(layers)
}
object CustomGlobalArmorOverrides {
@Serializable
data class ArmorOverride(
@SerialName("item_ids")
val itemIds: List<String>,
val layers: List<ArmorOverrideLayer>? = null,
val model: Identifier? = null,
val overrides: List<ArmorOverrideOverride> = listOf(),
) {
@Transient
lateinit var modelIdentifier: Identifier
fun bake() {
modelIdentifier = bakeModel(model, layers)
overrides.forEach { it.bake() }
}
fun bakeLayers(layers: List<ArmorOverrideLayer>): List<ArmorMaterial.Layer> {
return layers.map { ArmorMaterial.Layer(it.identifier, it.suffix, it.tint) }
}
init {
require(layers != null || model != null) { "Either model or layers must be specified for armor override" }
require(layers == null || model == null) { "Can't specify both model and layers for armor override" }
}
}
@Serializable
data class ArmorOverrideLayer(
val tint: Boolean = false,
val identifier: Identifier,
val suffix: String = "",
)
@Serializable
data class ArmorOverrideLayer(
val tint: Boolean = false,
val identifier: Identifier,
val suffix: String = "",
)
@Serializable
data class ArmorOverrideOverride(
val predicate: FirmamentModelPredicate,
val layers: List<ArmorOverrideLayer>,
) {
@Transient
val bakedLayers = bakeLayers(layers)
}
@Serializable
data class ArmorOverrideOverride(
val predicate: FirmamentModelPredicate,
val layers: List<ArmorOverrideLayer>? = null,
val model: Identifier? = null,
) {
init {
require(layers != null || model != null) { "Either model or layers must be specified for armor override override" }
require(layers == null || model == null) { "Can't specify both model and layers for armor override override" }
}
override val delegateFeature: FirmamentFeature
get() = CustomSkyBlockTextures
@Transient
lateinit var modelIdentifier: Identifier
fun bake() {
modelIdentifier = bakeModel(model, layers)
}
}
val overrideCache = WeakCache.memoize<ItemStack, Optional<List<ArmorMaterial.Layer>>>("ArmorOverrides") { stack ->
val id = stack.skyBlockId ?: return@memoize Optional.empty()
val override = overrides[id.neuItem] ?: return@memoize Optional.empty()
for (suboverride in override.overrides) {
if (suboverride.predicate.test(stack)) {
return@memoize Optional.of(suboverride.bakedLayers)
}
}
return@memoize Optional.of(override.bakedLayers)
}
@JvmStatic
fun overrideArmor(stack: ItemStack): List<ArmorMaterial.Layer>? {
if (!CustomSkyBlockTextures.TConfig.enableArmorOverrides) return null
return overrideCache.invoke(stack).getOrNull()
}
val overrideCache = WeakCache.memoize<ItemStack, Optional<Identifier>>("ArmorOverrides") { stack ->
val id = stack.skyBlockId ?: return@memoize Optional.empty()
val override = overrides[id.neuItem] ?: return@memoize Optional.empty()
for (suboverride in override.overrides) {
if (suboverride.predicate.test(stack)) {
return@memoize Optional.of(suboverride.modelIdentifier)
}
}
return@memoize Optional.of(override.modelIdentifier)
}
var overrides: Map<String, ArmorOverride> = mapOf()
var overrides: Map<String, ArmorOverride> = mapOf()
private var bakedOverrides: MutableMap<Identifier, EquipmentModel> = mutableMapOf()
private val sentinelFirmRunning = AtomicInteger()
@Subscribe
fun onStart(event: FinalizeResourceManagerEvent) {
event.resourceManager.registerReloader(object :
SinglePreparationResourceReloader<Map<String, ArmorOverride>>() {
override fun prepare(manager: ResourceManager, profiler: Profiler): Map<String, ArmorOverride> {
val overrideFiles = manager.findResources("overrides/armor_models") {
it.namespace == "firmskyblock" && it.path.endsWith(".json")
}
val overrides = overrideFiles.mapNotNull {
Firmament.tryDecodeJsonFromStream<ArmorOverride>(it.value.inputStream).getOrElse { ex ->
logger.error("Failed to load armor texture override at ${it.key}", ex)
null
}
}
val associatedMap = overrides.flatMap { obj -> obj.itemIds.map { it to obj } }
.toMap()
return associatedMap
}
private fun bakeModel(model: Identifier?, layers: List<ArmorOverrideLayer>?): Identifier {
require(model == null || layers == null)
if (model != null) {
return model
} else if (layers != null) {
val idNumber = sentinelFirmRunning.incrementAndGet()
val identifier = Identifier.of("firmament:sentinel/$idNumber")
val equipmentLayers = layers.map {
EquipmentModel.Layer(
it.identifier, if (it.tint) {
Optional.of(EquipmentModel.Dyeable(Optional.empty()))
} else {
Optional.empty()
},
false
)
}
bakedOverrides[identifier] = EquipmentModel(
mapOf(
EquipmentModel.LayerType.HUMANOID to equipmentLayers,
EquipmentModel.LayerType.HUMANOID_LEGGINGS to equipmentLayers,
)
)
return identifier
} else {
error("Either model or layers must be non null")
}
}
override fun apply(prepared: Map<String, ArmorOverride>, manager: ResourceManager, profiler: Profiler) {
overrides = prepared
}
})
}
@Subscribe
fun onStart(event: FinalizeResourceManagerEvent) {
event.resourceManager.registerReloader(object :
SinglePreparationResourceReloader<Map<String, ArmorOverride>>() {
override fun prepare(manager: ResourceManager, profiler: Profiler): Map<String, ArmorOverride> {
val overrideFiles = manager.findResources("overrides/armor_models") {
it.namespace == "firmskyblock" && it.path.endsWith(".json")
}
val overrides = overrideFiles.mapNotNull {
Firmament.tryDecodeJsonFromStream<ArmorOverride>(it.value.inputStream).getOrElse { ex ->
logger.error("Failed to load armor texture override at ${it.key}", ex)
null
}
}
val associatedMap = overrides.flatMap { obj -> obj.itemIds.map { it to obj } }
.toMap()
return associatedMap
}
override fun apply(prepared: Map<String, ArmorOverride>, manager: ResourceManager, profiler: Profiler) {
bakedOverrides.clear()
prepared.forEach { it.value.bake() }
overrides = prepared
}
})
}
@JvmStatic
fun overrideArmor(itemStack: ItemStack): Optional<Identifier> {
return overrideCache.invoke(itemStack)
}
@JvmStatic
fun overrideArmorLayer(id: Identifier): EquipmentModel? {
return bakedOverrides[id]
}
}

View File

@@ -146,7 +146,7 @@ object CustomGlobalTextures : SinglePreparationResourceReloader<CustomGlobalText
it.overrides
.asSequence()
.filter { it.predicate.test(stack) }
.map { models.modelManager.getModel(ModelIdentifier(it.model, "inventory")) }
.map { models.getModel(it.model) }
.firstOrNull()
}
.intoOptional()

View File

@@ -1,9 +1,11 @@
package moe.nea.firmament.features.texturepack
import net.minecraft.client.render.model.Baker
import net.minecraft.util.Identifier
interface JsonUnbakedModelFirmExtra {
fun storeExtraBaker_firmament(baker: Baker)
fun setHeadModel_firmament(identifier: Identifier?)
fun getHeadModel_firmament(): Identifier?

View File

@@ -3,7 +3,6 @@ package moe.nea.firmament.features.texturepack
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import moe.nea.firmament.util.ErrorUtil
import moe.nea.firmament.util.assertNotNullOr
data class TintOverrides(
val layerMap: Map<Int, TintOverride> = mapOf()
@@ -14,21 +13,22 @@ data class TintOverrides(
val EMPTY = TintOverrides()
private val threadLocal = object : ThreadLocal<TintOverrides>() {}
fun enter(overrides: TintOverrides?) {
ErrorUtil.softCheck("Double entered tintOverrides") {
threadLocal.get() == null
}
ErrorUtil.softCheck("Double entered tintOverrides",
threadLocal.get() == null)
threadLocal.set(overrides ?: EMPTY)
}
fun exit(overrides: TintOverrides?) {
ErrorUtil.softCheck("Exited with non matching enter tintOverrides") {
threadLocal.get() == (overrides ?: EMPTY)
}
ErrorUtil.softCheck("Exited with non matching enter tintOverrides",
threadLocal.get() == (overrides ?: EMPTY))
threadLocal.remove()
}
fun getCurrentOverrides() =
assertNotNullOr(threadLocal.get(), "Got current tintOverrides without entering") { EMPTY }
fun getCurrentOverrides(): TintOverrides {
return ErrorUtil.notNullOr(threadLocal.get(), "Got current tintOverrides without entering") {
EMPTY
}
}
fun parse(jsonObject: JsonObject): TintOverrides {
val map = mutableMapOf<Int, TintOverride>()

View File

@@ -6,6 +6,7 @@ import io.github.moulberry.repo.data.Coordinate
import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer
import net.minecraft.text.Text
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Vec3d
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.ProcessChatEvent
@@ -98,9 +99,8 @@ object FairySouls : FirmamentFeature {
fun onWorldRender(it: WorldRenderLastEvent) {
if (!TConfig.displaySouls) return
renderInWorld(it) {
color(1F, 1F, 0F, 0.8F)
currentMissingSouls.forEach {
block(it.blockPos)
block(it.blockPos, 0x80FFFF00.toInt())
}
color(1f, 0f, 1f, 1f)
currentLocationSouls.forEach {

View File

@@ -1,5 +1,3 @@
package moe.nea.firmament.features.world
import com.mojang.brigadier.arguments.IntegerArgumentType
@@ -12,6 +10,7 @@ import kotlin.collections.set
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.seconds
import net.minecraft.command.argument.BlockPosArgumentType
import net.minecraft.server.command.CommandOutput
import net.minecraft.server.command.ServerCommandSource
import net.minecraft.text.Text
import net.minecraft.util.math.BlockPos
@@ -35,263 +34,273 @@ import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.render.RenderInWorldContext
object Waypoints : FirmamentFeature {
override val identifier: String
get() = "waypoints"
override val identifier: String
get() = "waypoints"
object TConfig : ManagedConfig(identifier, Category.MINING) { // TODO: add to misc
val tempWaypointDuration by duration("temp-waypoint-duration", 0.seconds, 1.hours) { 30.seconds }
val showIndex by toggle("show-index") { true }
val skipToNearest by toggle("skip-to-nearest") { false }
// TODO: look ahead size
}
object TConfig : ManagedConfig(identifier, Category.MINING) { // TODO: add to misc
val tempWaypointDuration by duration("temp-waypoint-duration", 0.seconds, 1.hours) { 30.seconds }
val showIndex by toggle("show-index") { true }
val skipToNearest by toggle("skip-to-nearest") { false }
// TODO: look ahead size
}
data class TemporaryWaypoint(
val pos: BlockPos,
val postedAt: TimeMark,
)
data class TemporaryWaypoint(
val pos: BlockPos,
val postedAt: TimeMark,
)
override val config get() = TConfig
override val config get() = TConfig
val temporaryPlayerWaypointList = mutableMapOf<String, TemporaryWaypoint>()
val temporaryPlayerWaypointMatcher = "(?i)x: (-?[0-9]+),? y: (-?[0-9]+),? z: (-?[0-9]+)".toPattern()
val temporaryPlayerWaypointList = mutableMapOf<String, TemporaryWaypoint>()
val temporaryPlayerWaypointMatcher = "(?i)x: (-?[0-9]+),? y: (-?[0-9]+),? z: (-?[0-9]+)".toPattern()
val waypoints = mutableListOf<BlockPos>()
var ordered = false
var orderedIndex = 0
val waypoints = mutableListOf<BlockPos>()
var ordered = false
var orderedIndex = 0
@Serializable
data class ColeWeightWaypoint(
val x: Int,
val y: Int,
val z: Int,
val r: Int = 0,
val g: Int = 0,
val b: Int = 0,
)
@Serializable
data class ColeWeightWaypoint(
val x: Int,
val y: Int,
val z: Int,
val r: Int = 0,
val g: Int = 0,
val b: Int = 0,
)
@Subscribe
fun onRenderOrderedWaypoints(event: WorldRenderLastEvent) {
if (waypoints.isEmpty()) return
RenderInWorldContext.renderInWorld(event) {
if (!ordered) {
waypoints.withIndex().forEach {
color(0f, 0.3f, 0.7f, 0.5f)
block(it.value)
color(1f, 1f, 1f, 1f)
if (TConfig.showIndex)
withFacingThePlayer(it.value.toCenterPos()) {
text(Text.literal(it.index.toString()))
}
}
} else {
orderedIndex %= waypoints.size
val firstColor = Color.ofRGBA(0, 200, 40, 180)
color(firstColor)
tracer(waypoints[orderedIndex].toCenterPos(), lineWidth = 3f)
waypoints.withIndex().toList()
.wrappingWindow(orderedIndex, 3)
.zip(
listOf(
firstColor,
Color.ofRGBA(180, 200, 40, 150),
Color.ofRGBA(180, 80, 20, 140),
)
)
.reversed()
.forEach { (waypoint, col) ->
val (index, pos) = waypoint
color(col)
block(pos)
color(1f, 1f, 1f, 1f)
if (TConfig.showIndex)
withFacingThePlayer(pos.toCenterPos()) {
text(Text.literal(index.toString()))
}
}
}
}
}
@Subscribe
fun onRenderOrderedWaypoints(event: WorldRenderLastEvent) {
if (waypoints.isEmpty()) return
RenderInWorldContext.renderInWorld(event) {
if (!ordered) {
waypoints.withIndex().forEach {
block(it.value, 0x800050A0.toInt())
if (TConfig.showIndex)
withFacingThePlayer(it.value.toCenterPos()) {
text(Text.literal(it.index.toString()))
}
}
} else {
orderedIndex %= waypoints.size
val firstColor = Color.ofRGBA(0, 200, 40, 180)
color(firstColor)
tracer(waypoints[orderedIndex].toCenterPos(), lineWidth = 3f)
waypoints.withIndex().toList()
.wrappingWindow(orderedIndex, 3)
.zip(
listOf(
firstColor,
Color.ofRGBA(180, 200, 40, 150),
Color.ofRGBA(180, 80, 20, 140),
)
)
.reversed()
.forEach { (waypoint, col) ->
val (index, pos) = waypoint
block(pos, col.color)
if (TConfig.showIndex)
withFacingThePlayer(pos.toCenterPos()) {
text(Text.literal(index.toString()))
}
}
}
}
}
@Subscribe
fun onTick(event: TickEvent) {
if (waypoints.isEmpty() || !ordered) return
orderedIndex %= waypoints.size
val p = MC.player?.pos ?: return
if (TConfig.skipToNearest) {
orderedIndex =
(waypoints.withIndex().minBy { it.value.getSquaredDistance(p) }.index + 1) % waypoints.size
} else {
if (waypoints[orderedIndex].isWithinDistance(p, 3.0)) {
orderedIndex = (orderedIndex + 1) % waypoints.size
}
}
}
@Subscribe
fun onTick(event: TickEvent) {
if (waypoints.isEmpty() || !ordered) return
orderedIndex %= waypoints.size
val p = MC.player?.pos ?: return
if (TConfig.skipToNearest) {
orderedIndex =
(waypoints.withIndex().minBy { it.value.getSquaredDistance(p) }.index + 1) % waypoints.size
} else {
if (waypoints[orderedIndex].isWithinDistance(p, 3.0)) {
orderedIndex = (orderedIndex + 1) % waypoints.size
}
}
}
@Subscribe
fun onProcessChat(it: ProcessChatEvent) {
val matcher = temporaryPlayerWaypointMatcher.matcher(it.unformattedString)
if (it.nameHeuristic != null && TConfig.tempWaypointDuration > 0.seconds && matcher.find()) {
temporaryPlayerWaypointList[it.nameHeuristic] = TemporaryWaypoint(
BlockPos(
matcher.group(1).toInt(),
matcher.group(2).toInt(),
matcher.group(3).toInt(),
),
TimeMark.now()
)
}
}
@Subscribe
fun onProcessChat(it: ProcessChatEvent) {
val matcher = temporaryPlayerWaypointMatcher.matcher(it.unformattedString)
if (it.nameHeuristic != null && TConfig.tempWaypointDuration > 0.seconds && matcher.find()) {
temporaryPlayerWaypointList[it.nameHeuristic] = TemporaryWaypoint(
BlockPos(
matcher.group(1).toInt(),
matcher.group(2).toInt(),
matcher.group(3).toInt(),
),
TimeMark.now()
)
}
}
@Subscribe
fun onCommand(event: CommandEvent.SubCommand) {
event.subcommand("waypoint") {
thenArgument("pos", BlockPosArgumentType.blockPos()) { pos ->
thenExecute {
val position = pos.get(this).toAbsoluteBlockPos(source.asFakeServer())
waypoints.add(position)
source.sendFeedback(
Text.stringifiedTranslatable(
"firmament.command.waypoint.added",
position.x,
position.y,
position.z
)
)
}
}
}
event.subcommand("waypoints") {
thenLiteral("clear") {
thenExecute {
waypoints.clear()
source.sendFeedback(Text.translatable("firmament.command.waypoint.clear"))
}
}
thenLiteral("toggleordered") {
thenExecute {
ordered = !ordered
if (ordered) {
val p = MC.player?.pos ?: Vec3d.ZERO
orderedIndex =
waypoints.withIndex().minByOrNull { it.value.getSquaredDistance(p) }?.index ?: 0
}
source.sendFeedback(Text.translatable("firmament.command.waypoint.ordered.toggle.$ordered"))
}
}
thenLiteral("skip") {
thenExecute {
if (ordered && waypoints.isNotEmpty()) {
orderedIndex = (orderedIndex + 1) % waypoints.size
source.sendFeedback(Text.translatable("firmament.command.waypoint.skip"))
} else {
source.sendError(Text.translatable("firmament.command.waypoint.skip.error"))
}
}
}
thenLiteral("remove") {
thenArgument("index", IntegerArgumentType.integer(0)) { indexArg ->
thenExecute {
val index = get(indexArg)
if (index in waypoints.indices) {
waypoints.removeAt(index)
source.sendFeedback(Text.stringifiedTranslatable(
"firmament.command.waypoint.remove",
index))
} else {
source.sendError(Text.stringifiedTranslatable("firmament.command.waypoint.remove.error"))
}
}
}
}
thenLiteral("import") {
thenExecute {
val contents = ClipboardUtils.getTextContents()
val data = try {
Firmament.json.decodeFromString<List<ColeWeightWaypoint>>(contents)
} catch (ex: Exception) {
Firmament.logger.error("Could not load waypoints from clipboard", ex)
source.sendError(Text.translatable("firmament.command.waypoint.import.error"))
return@thenExecute
}
waypoints.clear()
data.mapTo(waypoints) { BlockPos(it.x, it.y, it.z) }
source.sendFeedback(
Text.stringifiedTranslatable(
"firmament.command.waypoint.import",
data.size
)
)
}
}
}
}
@Subscribe
fun onCommand(event: CommandEvent.SubCommand) {
event.subcommand("waypoint") {
thenArgument("pos", BlockPosArgumentType.blockPos()) { pos ->
thenExecute {
val position = pos.get(this).toAbsoluteBlockPos(source.asFakeServer())
waypoints.add(position)
source.sendFeedback(
Text.stringifiedTranslatable(
"firmament.command.waypoint.added",
position.x,
position.y,
position.z
)
)
}
}
}
event.subcommand("waypoints") {
thenLiteral("clear") {
thenExecute {
waypoints.clear()
source.sendFeedback(Text.translatable("firmament.command.waypoint.clear"))
}
}
thenLiteral("toggleordered") {
thenExecute {
ordered = !ordered
if (ordered) {
val p = MC.player?.pos ?: Vec3d.ZERO
orderedIndex =
waypoints.withIndex().minByOrNull { it.value.getSquaredDistance(p) }?.index ?: 0
}
source.sendFeedback(Text.translatable("firmament.command.waypoint.ordered.toggle.$ordered"))
}
}
thenLiteral("skip") {
thenExecute {
if (ordered && waypoints.isNotEmpty()) {
orderedIndex = (orderedIndex + 1) % waypoints.size
source.sendFeedback(Text.translatable("firmament.command.waypoint.skip"))
} else {
source.sendError(Text.translatable("firmament.command.waypoint.skip.error"))
}
}
}
thenLiteral("remove") {
thenArgument("index", IntegerArgumentType.integer(0)) { indexArg ->
thenExecute {
val index = get(indexArg)
if (index in waypoints.indices) {
waypoints.removeAt(index)
source.sendFeedback(Text.stringifiedTranslatable(
"firmament.command.waypoint.remove",
index))
} else {
source.sendError(Text.stringifiedTranslatable("firmament.command.waypoint.remove.error"))
}
}
}
}
thenLiteral("import") {
thenExecute {
val contents = ClipboardUtils.getTextContents()
val data = try {
Firmament.json.decodeFromString<List<ColeWeightWaypoint>>(contents)
} catch (ex: Exception) {
Firmament.logger.error("Could not load waypoints from clipboard", ex)
source.sendError(Text.translatable("firmament.command.waypoint.import.error"))
return@thenExecute
}
waypoints.clear()
data.mapTo(waypoints) { BlockPos(it.x, it.y, it.z) }
source.sendFeedback(
Text.stringifiedTranslatable(
"firmament.command.waypoint.import",
data.size
)
)
}
}
}
}
@Subscribe
fun onRenderTemporaryWaypoints(event: WorldRenderLastEvent) {
temporaryPlayerWaypointList.entries.removeIf { it.value.postedAt.passedTime() > TConfig.tempWaypointDuration }
if (temporaryPlayerWaypointList.isEmpty()) return
RenderInWorldContext.renderInWorld(event) {
color(1f, 1f, 0f, 1f)
temporaryPlayerWaypointList.forEach { (player, waypoint) ->
block(waypoint.pos)
}
color(1f, 1f, 1f, 1f)
temporaryPlayerWaypointList.forEach { (player, waypoint) ->
val skin =
MC.networkHandler?.listedPlayerListEntries?.find { it.profile.name == player }
?.skinTextures
?.texture
withFacingThePlayer(waypoint.pos.toCenterPos()) {
waypoint(waypoint.pos, Text.stringifiedTranslatable("firmament.waypoint.temporary", player))
if (skin != null) {
matrixStack.translate(0F, -20F, 0F)
// Head front
texture(
skin, 16, 16,
1 / 8f, 1 / 8f,
2 / 8f, 2 / 8f,
)
// Head overlay
texture(
skin, 16, 16,
5 / 8f, 1 / 8f,
6 / 8f, 2 / 8f,
)
}
}
}
}
}
@Subscribe
fun onRenderTemporaryWaypoints(event: WorldRenderLastEvent) {
temporaryPlayerWaypointList.entries.removeIf { it.value.postedAt.passedTime() > TConfig.tempWaypointDuration }
if (temporaryPlayerWaypointList.isEmpty()) return
RenderInWorldContext.renderInWorld(event) {
temporaryPlayerWaypointList.forEach { (player, waypoint) ->
block(waypoint.pos, 0xFFFFFF00.toInt())
}
temporaryPlayerWaypointList.forEach { (player, waypoint) ->
val skin =
MC.networkHandler?.listedPlayerListEntries?.find { it.profile.name == player }
?.skinTextures
?.texture
withFacingThePlayer(waypoint.pos.toCenterPos()) {
waypoint(waypoint.pos, Text.stringifiedTranslatable("firmament.waypoint.temporary", player))
if (skin != null) {
matrixStack.translate(0F, -20F, 0F)
// Head front
texture(
skin, 16, 16,
1 / 8f, 1 / 8f,
2 / 8f, 2 / 8f,
)
// Head overlay
texture(
skin, 16, 16,
5 / 8f, 1 / 8f,
6 / 8f, 2 / 8f,
)
}
}
}
}
}
@Subscribe
fun onWorldReady(event: WorldReadyEvent) {
temporaryPlayerWaypointList.clear()
}
@Subscribe
fun onWorldReady(event: WorldReadyEvent) {
temporaryPlayerWaypointList.clear()
}
}
fun <E> List<E>.wrappingWindow(startIndex: Int, windowSize: Int): List<E> {
val result = ArrayList<E>(windowSize)
if (startIndex + windowSize < size) {
result.addAll(subList(startIndex, startIndex + windowSize))
} else {
result.addAll(subList(startIndex, size))
result.addAll(subList(0, minOf(windowSize - (size - startIndex), startIndex)))
}
return result
val result = ArrayList<E>(windowSize)
if (startIndex + windowSize < size) {
result.addAll(subList(startIndex, startIndex + windowSize))
} else {
result.addAll(subList(startIndex, size))
result.addAll(subList(0, minOf(windowSize - (size - startIndex), startIndex)))
}
return result
}
fun FabricClientCommandSource.asFakeServer(): ServerCommandSource {
val source = this
return ServerCommandSource(
source.player,
source.position,
source.rotation,
null,
0,
"FakeServerCommandSource",
Text.literal("FakeServerCommandSource"),
null,
source.player
)
val source = this
return ServerCommandSource(
object : CommandOutput {
override fun sendMessage(message: Text?) {
source.player.sendMessage(message, false)
}
override fun shouldReceiveFeedback(): Boolean {
return true
}
override fun shouldTrackOutput(): Boolean {
return true
}
override fun shouldBroadcastConsoleToOps(): Boolean {
return true
}
},
source.position,
source.rotation,
null,
0,
"FakeServerCommandSource",
Text.literal("FakeServerCommandSource"),
null,
source.player
)
}

View File

@@ -1,4 +1,3 @@
package moe.nea.firmament.gui
import com.mojang.blaze3d.systems.RenderSystem
@@ -10,116 +9,115 @@ import io.github.notenoughupdates.moulconfig.observer.GetSetter
import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext
import me.shedaniel.math.Color
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.render.RenderLayer
import net.minecraft.util.Identifier
import moe.nea.firmament.Firmament
class BarComponent(
val progress: GetSetter<Double>, val total: GetSetter<Double>,
val fillColor: Color,
val emptyColor: Color,
val progress: GetSetter<Double>, val total: GetSetter<Double>,
val fillColor: Color,
val emptyColor: Color,
) : GuiComponent() {
override fun getWidth(): Int {
return 80
}
override fun getWidth(): Int {
return 80
}
override fun getHeight(): Int {
return 8
}
override fun getHeight(): Int {
return 8
}
data class Texture(
val identifier: Identifier,
val u1: Float, val v1: Float,
val u2: Float, val v2: Float,
) {
fun draw(context: DrawContext, x: Int, y: Int, width: Int, height: Int, color: Color) {
context.drawTexturedQuad(
identifier,
x, y, x + width, x + height, 0,
u1, u2, v1, v2,
color.red / 255F,
color.green / 255F,
color.blue / 255F,
color.alpha / 255F,
)
}
}
data class Texture(
val identifier: Identifier,
val u1: Float, val v1: Float,
val u2: Float, val v2: Float,
) {
fun draw(context: DrawContext, x: Int, y: Int, width: Int, height: Int, color: Color) {
context.drawTexturedQuad(
RenderLayer::getGuiTextured,
identifier,
x, y, x + width, x + height,
u1, u2, v1, v2,
color.color
)
}
}
companion object {
val resource = Firmament.identifier("textures/gui/bar.png")
val left = Texture(resource, 0 / 64F, 0 / 64F, 4 / 64F, 8 / 64F)
val middle = Texture(resource, 4 / 64F, 0 / 64F, 8 / 64F, 8 / 64F)
val right = Texture(resource, 8 / 64F, 0 / 64F, 12 / 64F, 8 / 64F)
val segmentOverlay = Texture(resource, 12 / 64F, 0 / 64F, 15 / 64F, 8 / 64F)
}
companion object {
val resource = Firmament.identifier("textures/gui/bar.png")
val left = Texture(resource, 0 / 64F, 0 / 64F, 4 / 64F, 8 / 64F)
val middle = Texture(resource, 4 / 64F, 0 / 64F, 8 / 64F, 8 / 64F)
val right = Texture(resource, 8 / 64F, 0 / 64F, 12 / 64F, 8 / 64F)
val segmentOverlay = Texture(resource, 12 / 64F, 0 / 64F, 15 / 64F, 8 / 64F)
}
private fun drawSection(
context: DrawContext,
texture: Texture,
x: Int,
y: Int,
width: Int,
sectionStart: Double,
sectionEnd: Double
) {
if (sectionEnd < progress.get() && width == 4) {
texture.draw(context, x, y, 4, 8, fillColor)
return
}
if (sectionStart > progress.get() && width == 4) {
texture.draw(context, x, y, 4, 8, emptyColor)
return
}
val increasePerPixel = (sectionEnd - sectionStart) / width
var valueAtPixel = sectionStart
for (i in (0 until width)) {
val newTex =
Texture(texture.identifier, texture.u1 + i / 64F, texture.v1, texture.u1 + (i + 1) / 64F, texture.v2)
newTex.draw(
context, x + i, y, 1, 8,
if (valueAtPixel < progress.get()) fillColor else emptyColor
)
valueAtPixel += increasePerPixel
}
}
private fun drawSection(
context: DrawContext,
texture: Texture,
x: Int,
y: Int,
width: Int,
sectionStart: Double,
sectionEnd: Double
) {
if (sectionEnd < progress.get() && width == 4) {
texture.draw(context, x, y, 4, 8, fillColor)
return
}
if (sectionStart > progress.get() && width == 4) {
texture.draw(context, x, y, 4, 8, emptyColor)
return
}
val increasePerPixel = (sectionEnd - sectionStart) / width
var valueAtPixel = sectionStart
for (i in (0 until width)) {
val newTex =
Texture(texture.identifier, texture.u1 + i / 64F, texture.v1, texture.u1 + (i + 1) / 64F, texture.v2)
newTex.draw(
context, x + i, y, 1, 8,
if (valueAtPixel < progress.get()) fillColor else emptyColor
)
valueAtPixel += increasePerPixel
}
}
override fun render(context: GuiImmediateContext) {
val renderContext = (context.renderContext as ModernRenderContext).drawContext
var i = 0
val x = 0
val y = 0
while (i < context.width - 4) {
drawSection(
renderContext,
if (i == 0) left else middle,
x + i, y,
(context.width - (i + 4)).coerceAtMost(4),
i * total.get() / context.width, (i + 4) * total.get() / context.width
)
i += 4
}
drawSection(
renderContext,
right,
x + context.width - 4,
y,
4,
(context.width - 4) * total.get() / context.width,
total.get()
)
RenderSystem.setShaderColor(1F, 1F, 1F, 1F)
override fun render(context: GuiImmediateContext) {
val renderContext = (context.renderContext as ModernRenderContext).drawContext
var i = 0
val x = 0
val y = 0
while (i < context.width - 4) {
drawSection(
renderContext,
if (i == 0) left else middle,
x + i, y,
(context.width - (i + 4)).coerceAtMost(4),
i * total.get() / context.width, (i + 4) * total.get() / context.width
)
i += 4
}
drawSection(
renderContext,
right,
x + context.width - 4,
y,
4,
(context.width - 4) * total.get() / context.width,
total.get()
)
RenderSystem.setShaderColor(1F, 1F, 1F, 1F)
}
}
}
fun Identifier.toMoulConfig(): MyResourceLocation {
return MyResourceLocation(this.namespace, this.path)
return MyResourceLocation(this.namespace, this.path)
}
fun RenderContext.color(color: Color) {
color(color.red, color.green, color.blue, color.alpha)
color(color.red, color.green, color.blue, color.alpha)
}
fun RenderContext.color(red: Int, green: Int, blue: Int, alpha: Int) {
color(red / 255f, green / 255f, blue / 255f, alpha / 255f)
color(red / 255f, green / 255f, blue / 255f, alpha / 255f)
}

View File

@@ -1,4 +1,3 @@
package moe.nea.firmament.gui.entity
import com.google.gson.Gson
@@ -13,7 +12,9 @@ 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.entity.SpawnReason
import net.minecraft.util.Identifier
import net.minecraft.world.World
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.assertNotNullOr
import moe.nea.firmament.util.iterate
@@ -21,177 +22,177 @@ 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 fakeWorld: World get() = MC.lastWorld!!
private fun <T : Entity> t(entityType: EntityType<T>): () -> T {
return { entityType.create(fakeWorld, SpawnReason.LOAD)!! }
}
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 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
}
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)
}
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
)
)
}
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,
minOf(2F / maxSize, 1F) * 30,
-bottomOffset,
mouseX,
mouseY,
currentEntity
)
val next = currentEntity.firstPassenger as? LivingEntity ?: break
bottomOffset += currentEntity.getPassengerRidingPos(next).y.toFloat() * 0.75F
currentEntity = next
}
}
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,
minOf(2F / maxSize, 1F) * 30,
-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: Float,
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()
}
fun drawEntity(
context: DrawContext,
x1: Int,
y1: Int,
x2: Int,
y2: Int,
size: Float,
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

@@ -1,38 +1,37 @@
package moe.nea.firmament.gui.entity
import com.mojang.datafixers.util.Pair
import com.mojang.serialization.Lifecycle
import java.util.*
import java.util.UUID
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.gui.screen.world.SelectWorldScreen
import net.minecraft.component.type.MapIdComponent
import net.minecraft.entity.Entity
import net.minecraft.entity.damage.DamageSource
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.fluid.Fluid
import net.minecraft.item.FuelRegistry
import net.minecraft.item.map.MapState
import net.minecraft.particle.ParticleEffect
import net.minecraft.recipe.BrewingRecipeRegistry
import net.minecraft.recipe.Ingredient
import net.minecraft.recipe.RecipeManager
import net.minecraft.registry.BuiltinRegistries
import net.minecraft.recipe.RecipePropertySet
import net.minecraft.recipe.StonecuttingRecipe
import net.minecraft.recipe.display.CuttingRecipeDisplay
import net.minecraft.registry.DynamicRegistryManager
import net.minecraft.registry.Registry
import net.minecraft.registry.Registries
import net.minecraft.registry.RegistryKey
import net.minecraft.registry.RegistryKeys
import net.minecraft.registry.RegistryWrapper
import net.minecraft.registry.ServerDynamicRegistryType
import net.minecraft.registry.entry.RegistryEntry
import net.minecraft.registry.entry.RegistryEntryInfo
import net.minecraft.registry.entry.RegistryEntryList
import net.minecraft.registry.entry.RegistryEntryOwner
import net.minecraft.registry.tag.TagKey
import net.minecraft.resource.DataConfiguration
import net.minecraft.resource.ResourcePackManager
import net.minecraft.resource.featuretoggle.FeatureFlags
import net.minecraft.resource.featuretoggle.FeatureSet
import net.minecraft.scoreboard.Scoreboard
import net.minecraft.server.SaveLoading
import net.minecraft.server.command.CommandManager
import net.minecraft.sound.SoundCategory
import net.minecraft.sound.SoundEvent
import net.minecraft.util.Identifier
@@ -43,11 +42,8 @@ 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
@@ -59,430 +55,284 @@ 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.explosion.ExplosionBehavior
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 getEntryInfo(key: RegistryKey<T>?): Optional<RegistryEntryInfo> {
TODO("Not yet implemented")
}
override fun getLifecycle(): Lifecycle {
return Lifecycle.stable()
}
override fun getDefaultEntry(): Optional<RegistryEntry.Reference<T>> {
return Optional.empty()
}
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 getEntry(id: Identifier?): Optional<RegistryEntry.Reference<T>> {
TODO("Not yet implemented")
}
override fun createEntry(value: T): RegistryEntry.Reference<T> {
TODO("Not yet implemented")
}
override fun contains(key: RegistryKey<T>?): Boolean {
return getEntry(key).isPresent
}
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()])
}
}
}
import moe.nea.firmament.util.MC
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>>) }
}
}
// TODO: use SaveLoading.load() to properly load a full registry
return DynamicRegistryManager.of(Registries.REGISTRIES)
}
class FakeWorld(
registries: DynamicRegistryManager.Immutable = createDynamicRegistry(),
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.of("minecraft", "overworld")
)
),
{ DummyProfiler.INSTANCE },
true,
false,
0, 0
Properties,
RegistryKey.of(RegistryKeys.WORLD, Identifier.of("firmament", "fakeworld")),
registries,
MC.defaultRegistries.getOrThrow(RegistryKeys.DIMENSION_TYPE)
.getOrThrow(RegistryKey.of(RegistryKeys.DIMENSION_TYPE, Identifier.of("minecraft", "overworld"))),
true,
false,
0L,
0
) {
object Properties : MutableWorldProperties {
override fun getSpawnPos(): BlockPos {
return BlockPos.ORIGIN
}
object Properties : MutableWorldProperties {
override fun getSpawnPos(): BlockPos {
return BlockPos.ORIGIN
}
override fun getSpawnAngle(): Float {
return 0F
}
override fun getSpawnAngle(): Float {
return 0F
}
override fun getTime(): Long {
return 0
}
override fun getTime(): Long {
return 0
}
override fun getTimeOfDay(): Long {
return 0
}
override fun getTimeOfDay(): Long {
return 0
}
override fun isThundering(): Boolean {
return false
}
override fun isThundering(): Boolean {
return false
}
override fun isRaining(): Boolean {
return false
}
override fun isRaining(): Boolean {
return false
}
override fun setRaining(raining: Boolean) {
}
override fun setRaining(raining: Boolean) {
}
override fun isHardcore(): Boolean {
return false
}
override fun isHardcore(): Boolean {
return false
}
override fun getGameRules(): GameRules {
return GameRules()
}
override fun getDifficulty(): Difficulty {
return Difficulty.HARD
}
override fun getDifficulty(): Difficulty {
return Difficulty.HARD
}
override fun isDifficultyLocked(): Boolean {
return false
}
override fun isDifficultyLocked(): Boolean {
return false
}
override fun setSpawnPos(pos: BlockPos?, angle: Float) {}
}
override fun setSpawnPos(pos: BlockPos?, angle: Float) {}
}
override fun getPlayers(): List<PlayerEntity> {
return emptyList()
}
override fun getPlayers(): List<PlayerEntity> {
return emptyList()
}
override fun getBrightness(direction: Direction?, shaded: Boolean): Float {
return 1f
}
override fun getBrightness(direction: Direction?, shaded: Boolean): Float {
return 1f
}
override fun getGeneratorStoredBiome(biomeX: Int, biomeY: Int, biomeZ: Int): RegistryEntry<Biome> {
return registryManager.getOptionalEntry(BiomeKeys.PLAINS).get()
}
override fun getGeneratorStoredBiome(biomeX: Int, biomeY: Int, biomeZ: Int): RegistryEntry<Biome> {
return registryManager.get(RegistryKeys.BIOME).entryOf(BiomeKeys.PLAINS)
}
override fun getSeaLevel(): Int {
return 0
}
override fun getEnabledFeatures(): FeatureSet {
return FeatureFlags.VANILLA_FEATURES
}
override fun getEnabledFeatures(): FeatureSet {
return FeatureFlags.VANILLA_FEATURES
}
class FakeTickScheduler<T> : QueryableTickScheduler<T> {
override fun scheduleTick(orderedTick: OrderedTick<T>?) {
}
class FakeTickScheduler<T> : QueryableTickScheduler<T> {
override fun scheduleTick(orderedTick: OrderedTick<T>?) {
}
override fun isQueued(pos: BlockPos?, type: T): Boolean {
return true
}
override fun isQueued(pos: BlockPos?, type: T): Boolean {
return true
}
override fun getTickCount(): Int {
return 0
}
override fun getTickCount(): Int {
return 0
}
override fun isTicking(pos: BlockPos?, type: T): Boolean {
return true
}
override fun isTicking(pos: BlockPos?, type: T): Boolean {
return true
}
}
}
override fun getBlockTickScheduler(): QueryableTickScheduler<Block> {
return FakeTickScheduler()
}
override fun getBlockTickScheduler(): QueryableTickScheduler<Block> {
return FakeTickScheduler()
}
override fun getFluidTickScheduler(): QueryableTickScheduler<Fluid> {
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)
)
}
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.getOptionalEntry(BiomeKeys.PLAINS).get()
)
}
override fun getWorld(): BlockView {
return world
}
override fun getWorld(): BlockView {
return world
}
override fun tick(shouldKeepTicking: BooleanSupplier?, tickChunks: Boolean) {
}
override fun tick(shouldKeepTicking: BooleanSupplier?, tickChunks: Boolean) {
}
override fun getDebugString(): String {
return "FakeChunkManager"
}
override fun getDebugString(): String {
return "FakeChunkManager"
}
override fun getLoadedChunkCount(): Int {
return 0
}
override fun getLoadedChunkCount(): Int {
return 0
}
override fun getLightingProvider(): LightingProvider {
return FakeLightingProvider(this)
}
}
override fun getLightingProvider(): LightingProvider {
return FakeLightingProvider(this)
}
}
class FakeLightingProvider(chunkManager: FakeChunkManager) : LightingProvider(chunkManager, false, false)
class FakeLightingProvider(chunkManager: FakeChunkManager) : LightingProvider(chunkManager, false, false)
override fun getChunkManager(): ChunkManager {
return FakeChunkManager(this)
}
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 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 syncWorldEvent(player: PlayerEntity?, eventId: Int, pos: BlockPos?, data: Int) {
}
override fun emitGameEvent(event: RegistryEntry<GameEvent>?, emitterPos: Vec3d?, emitter: GameEvent.Emitter?) {
}
override fun emitGameEvent(event: RegistryEntry<GameEvent>?, emitterPos: Vec3d?, emitter: GameEvent.Emitter?) {
}
override fun updateListeners(pos: BlockPos?, oldState: BlockState?, newState: BlockState?, flags: Int) {
}
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 playSoundFromEntity(
source: PlayerEntity?,
entity: Entity?,
sound: RegistryEntry<SoundEvent>?,
category: SoundCategory?,
volume: Float,
pitch: Float,
seed: Long
) {
}
override fun asString(): String {
return "FakeWorld"
}
override fun createExplosion(
entity: Entity?,
damageSource: DamageSource?,
behavior: ExplosionBehavior?,
x: Double,
y: Double,
z: Double,
power: Float,
createFire: Boolean,
explosionSourceType: ExplosionSourceType?,
smallParticle: ParticleEffect?,
largeParticle: ParticleEffect?,
soundEvent: RegistryEntry<SoundEvent>?
) {
TODO("Not yet implemented")
}
override fun getEntityById(id: Int): Entity? {
return null
}
override fun asString(): String {
return "FakeWorld"
}
override fun getTickManager(): TickManager {
return TickManager()
}
override fun getEntityById(id: Int): Entity? {
return null
}
override fun getMapState(id: MapIdComponent?): MapState? {
return null
}
override fun getTickManager(): TickManager {
return TickManager()
}
override fun putMapState(id: MapIdComponent?, state: MapState?) {
}
override fun getMapState(id: MapIdComponent?): MapState? {
return null
}
override fun increaseAndGetMapId(): MapIdComponent {
return MapIdComponent(0)
}
override fun putMapState(id: MapIdComponent?, state: MapState?) {
}
override fun setBlockBreakingInfo(entityId: Int, pos: BlockPos?, progress: Int) {
}
override fun increaseAndGetMapId(): MapIdComponent {
return MapIdComponent(0)
}
override fun getScoreboard(): Scoreboard {
return Scoreboard()
}
override fun setBlockBreakingInfo(entityId: Int, pos: BlockPos?, progress: Int) {
}
override fun getRecipeManager(): RecipeManager {
return RecipeManager(registryManager)
}
override fun getScoreboard(): Scoreboard {
return Scoreboard()
}
object FakeEntityLookup : EntityLookup<Entity> {
override fun get(id: Int): Entity? {
return null
}
override fun getRecipeManager(): RecipeManager {
return object : RecipeManager {
override fun getPropertySet(key: RegistryKey<RecipePropertySet>?): RecipePropertySet {
return RecipePropertySet.EMPTY
}
override fun get(uuid: UUID?): Entity? {
return null
}
override fun getStonecutterRecipes(): CuttingRecipeDisplay.Grouping<StonecuttingRecipe> {
return CuttingRecipeDisplay.Grouping.empty()
}
}
}
override fun iterate(): MutableIterable<Entity> {
return mutableListOf()
}
object FakeEntityLookup : EntityLookup<Entity> {
override fun get(id: Int): Entity? {
return null
}
override fun <U : Entity?> forEachIntersects(
filter: TypeFilter<Entity, U>?,
box: Box?,
consumer: LazyIterationConsumer<U>?
) {
}
override fun get(uuid: UUID?): Entity? {
return null
}
override fun forEachIntersects(box: Box?, action: Consumer<Entity>?) {
}
override fun iterate(): MutableIterable<Entity> {
return mutableListOf()
}
override fun <U : Entity?> forEach(filter: TypeFilter<Entity, U>?, consumer: LazyIterationConsumer<U>?) {
}
override fun <U : Entity?> forEachIntersects(
filter: TypeFilter<Entity, U>?,
box: Box?,
consumer: LazyIterationConsumer<U>?
) {
}
}
override fun forEachIntersects(box: Box?, action: Consumer<Entity>?) {
}
override fun getEntityLookup(): EntityLookup<Entity> {
return FakeEntityLookup
}
override fun <U : Entity?> forEach(filter: TypeFilter<Entity, U>?, consumer: LazyIterationConsumer<U>?) {
}
override fun getBrewingRecipeRegistry(): BrewingRecipeRegistry {
return BrewingRecipeRegistry.EMPTY
}
}
override fun getEntityLookup(): EntityLookup<Entity> {
return FakeEntityLookup
}
override fun getBrewingRecipeRegistry(): BrewingRecipeRegistry {
return BrewingRecipeRegistry.EMPTY
}
override fun getFuelRegistry(): FuelRegistry {
TODO("Not yet implemented")
}
}

View File

@@ -1,8 +1,7 @@
package moe.nea.firmament.gui.entity
import com.mojang.authlib.GameProfile
import java.util.*
import java.util.UUID
import net.minecraft.client.network.AbstractClientPlayerEntity
import net.minecraft.client.util.DefaultSkinHelper
import net.minecraft.client.util.SkinTextures
@@ -15,40 +14,40 @@ 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"))
fun makeGuiPlayer(world: World): 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 isSpectator(): Boolean {
return false
}
override fun isCreative(): Boolean {
return false
}
override fun isCreative(): Boolean {
return false
}
override fun shouldRenderName(): Boolean {
return false
}
override fun shouldRenderName(): 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
)
}
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

@@ -8,6 +8,7 @@ import kotlin.experimental.inv
import kotlin.experimental.or
import net.minecraft.entity.EntityType
import net.minecraft.entity.LivingEntity
import net.minecraft.entity.SpawnReason
import net.minecraft.entity.passive.AbstractHorseEntity
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
@@ -19,11 +20,11 @@ object ModifyHorse : EntityModifier {
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)!!
"skeleton" -> EntityType.SKELETON_HORSE.create(fakeWorld, SpawnReason.LOAD)!!
"zombie" -> EntityType.ZOMBIE_HORSE.create(fakeWorld, SpawnReason.LOAD)!!
"mule" -> EntityType.MULE.create(fakeWorld, SpawnReason.LOAD)!!
"donkey" -> EntityType.DONKEY.create(fakeWorld, SpawnReason.LOAD)!!
"horse" -> EntityType.HORSE.create(fakeWorld, SpawnReason.LOAD)!!
else -> error("Unknown horse kind $it")
}
}

View File

@@ -5,7 +5,7 @@ package moe.nea.firmament.repo
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsChannel
import io.ktor.utils.io.jvm.nio.copyTo
import io.ktor.utils.io.copyTo
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Path

View File

@@ -9,10 +9,12 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents
import kotlinx.coroutines.launch
import net.minecraft.client.MinecraftClient
import net.minecraft.network.packet.s2c.play.SynchronizeRecipesS2CPacket
import net.minecraft.recipe.display.CuttingRecipeDisplay
import moe.nea.firmament.Firmament
import moe.nea.firmament.Firmament.logger
import moe.nea.firmament.events.ReloadRegistrationEvent
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.MinecraftDispatcher
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.tr
@@ -77,7 +79,7 @@ object RepoManager {
private fun trySendClientboundUpdateRecipesPacket(): Boolean {
return MinecraftClient.getInstance().world != null && MinecraftClient.getInstance().networkHandler?.onSynchronizeRecipes(
SynchronizeRecipesS2CPacket(mutableListOf())
SynchronizeRecipesS2CPacket(mutableMapOf(), CuttingRecipeDisplay.Grouping.empty())
) != null
}
@@ -92,7 +94,7 @@ object RepoManager {
fun launchAsyncUpdate(force: Boolean = false) {
Firmament.coroutineScope.launch {
ItemCache.ReloadProgressHud.reportProgress("Downloading", 0, -1) // TODO: replace with a proper boundy bar
ItemCache.ReloadProgressHud.reportProgress("Downloading", 0, -1) // TODO: replace with a proper bouncy bar
ItemCache.ReloadProgressHud.isEnabled = true
try {
RepoDownloadManager.downloadUpdate(force)
@@ -112,7 +114,7 @@ object RepoManager {
ItemCache.ReloadProgressHud.isEnabled = true
neuRepo.reload()
} catch (exc: NEURepositoryException) {
MinecraftClient.getInstance().player?.sendMessage(
MC.sendChat(
tr("firmament.repo.reloadfail",
"Failed to reload repository. This will result in some mod features not working.")
)

View File

@@ -1,9 +1,14 @@
package moe.nea.firmament.repo
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import io.github.moulberry.repo.constants.PetNumbers
import io.github.moulberry.repo.data.NEUIngredient
import io.github.moulberry.repo.data.NEUItem
import net.minecraft.item.ItemStack
import net.minecraft.network.RegistryByteBuf
import net.minecraft.network.codec.PacketCodec
import net.minecraft.network.codec.PacketCodecs
import net.minecraft.text.Text
import net.minecraft.util.Formatting
import moe.nea.firmament.repo.ItemCache.asItemStack
@@ -40,6 +45,21 @@ data class SBItemStack constructor(
}
companion object {
val PACKET_CODEC: PacketCodec<in RegistryByteBuf, SBItemStack> = PacketCodec.tuple(
SkyblockId.PACKET_CODEC, { it.skyblockId },
PacketCodecs.VAR_INT, { it.stackSize },
{ id, count -> SBItemStack(id, count) }
)
val CODEC: Codec<SBItemStack> = RecordCodecBuilder.create {
it.group(
SkyblockId.CODEC.fieldOf("skyblockId").forGetter { it.skyblockId },
Codec.INT.fieldOf("count").forGetter { it.stackSize },
).apply(it) { id, count ->
SBItemStack(id, count)
}
}
val EMPTY = SBItemStack(SkyblockId.NULL, 0)
operator fun invoke(itemStack: ItemStack): SBItemStack {
val skyblockId = itemStack.skyBlockId ?: SkyblockId.NULL
return SBItemStack(
@@ -114,6 +134,8 @@ data class SBItemStack constructor(
val itemStack = itemStack_ ?: run {
if (skyblockId == SkyblockId.COINS)
return@run ItemCache.coinItem(stackSize).also { it.appendLore(extraLore) }
if (stackSize == 0)
return@run ItemStack.EMPTY
val replacementData = mutableMapOf<String, String>()
injectReplacementDataForPets(replacementData)
return@run neuItem.asItemStack(idHint = skyblockId, replacementData)

View File

@@ -0,0 +1,19 @@
package moe.nea.firmament.repo.recipes
import io.github.moulberry.repo.NEURepository
import io.github.moulberry.repo.data.NEURecipe
import me.shedaniel.math.Rectangle
import net.minecraft.item.ItemStack
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import moe.nea.firmament.repo.SBItemStack
interface GenericRecipeRenderer<T : NEURecipe> {
fun render(recipe: T, bounds: Rectangle, layouter: RecipeLayouter)
fun getInputs(recipe: T): Collection<SBItemStack>
fun getOutputs(recipe: T): Collection<SBItemStack>
val icon: ItemStack
val title: Text
val identifier: Identifier
fun findAllRecipes(neuRepository: NEURepository): Iterable<T>
}

View File

@@ -0,0 +1,33 @@
package moe.nea.firmament.repo.recipes
import io.github.notenoughupdates.moulconfig.gui.GuiComponent
import net.minecraft.text.Text
import moe.nea.firmament.repo.SBItemStack
interface RecipeLayouter {
enum class SlotKind {
SMALL_INPUT,
SMALL_OUTPUT,
/**
* Create a bigger background and mark the slot as output. The coordinates should still refer the upper left corner of the item stack, not of the bigger background.
*/
BIG_OUTPUT,
}
fun createItemSlot(
x: Int, y: Int,
content: SBItemStack?,
slotKind: SlotKind,
)
fun createLabel(
x: Int, y: Int,
text: Text
)
fun createArrow(x: Int, y: Int)
fun createMoulConfig(x: Int, y: Int, w: Int, h: Int, component: GuiComponent)
}

View File

@@ -0,0 +1,50 @@
package moe.nea.firmament.repo.recipes
import io.github.moulberry.repo.NEURepository
import io.github.moulberry.repo.data.NEUCraftingRecipe
import me.shedaniel.math.Point
import me.shedaniel.math.Rectangle
import net.minecraft.block.Blocks
import net.minecraft.item.ItemStack
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import moe.nea.firmament.Firmament
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.tr
class SBCraftingRecipeRenderer : GenericRecipeRenderer<NEUCraftingRecipe> {
override fun render(recipe: NEUCraftingRecipe, bounds: Rectangle, layouter: RecipeLayouter) {
val point = Point(bounds.centerX - 58, bounds.centerY - 27)
layouter.createArrow(point.x + 60, point.y + 18)
for (i in 0 until 3) {
for (j in 0 until 3) {
val item = recipe.inputs[i + j * 3]
layouter.createItemSlot(point.x + 1 + i * 18,
point.y + 1 + j * 18,
SBItemStack(item),
RecipeLayouter.SlotKind.SMALL_INPUT)
}
}
layouter.createItemSlot(
point.x + 95, point.y + 19,
SBItemStack(recipe.output),
RecipeLayouter.SlotKind.BIG_OUTPUT
)
}
override fun getInputs(recipe: NEUCraftingRecipe): Collection<SBItemStack> {
return recipe.allInputs.mapNotNull { SBItemStack(it) }
}
override fun getOutputs(recipe: NEUCraftingRecipe): Collection<SBItemStack> {
return SBItemStack(recipe.output)?.let(::listOf) ?: emptyList()
}
override fun findAllRecipes(neuRepository: NEURepository): Iterable<NEUCraftingRecipe> {
return neuRepository.items.items.values.flatMap { it.recipes }.filterIsInstance<NEUCraftingRecipe>()
}
override val icon: ItemStack = ItemStack(Blocks.CRAFTING_TABLE)
override val title: Text = tr("firmament.category.crafting", "SkyBlock Crafting")
override val identifier: Identifier = Firmament.identifier("crafting_recipe")
}

View File

@@ -1,25 +1,46 @@
@file:OptIn(ExperimentalContracts::class)
package moe.nea.firmament.util
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import moe.nea.firmament.Firmament
@Suppress("NOTHING_TO_INLINE") // Suppressed since i want the logger to not pick up the ErrorUtil stack-frame
object ErrorUtil {
var aggressiveErrors = run {
Thread.currentThread().stackTrace.any { it.className.startsWith("org.junit.") } || Firmament.DEBUG
|| ErrorUtil::class.java.desiredAssertionStatus()
}
inline fun softCheck(message: String, func: () -> Boolean) {
inline fun softCheck(message: String, check: Boolean) {
if (!check) softError(message)
}
inline fun lazyCheck(message: String, func: () -> Boolean) {
contract {
callsInPlace(func, InvocationKind.AT_MOST_ONCE)
}
if (!aggressiveErrors) return
if (func()) return
error(message)
}
@Suppress("NOTHING_TO_INLINE") // Suppressed since i want the logger to not pick up the ErrorUtil stack-frame
inline fun softError(message: String, exception: Throwable) {
if (aggressiveErrors) throw IllegalStateException(message, exception)
else Firmament.logger.error(message, exception)
}
inline fun softError(message: String) {
if (aggressiveErrors) error(message)
else Firmament.logger.error(message)
}
inline fun <T : Any> notNullOr(nullable: T?, message: String, orElse: () -> T): T {
contract {
callsInPlace(orElse, InvocationKind.AT_MOST_ONCE)
}
if (nullable == null) {
softError(message)
return orElse()

View File

@@ -3,9 +3,13 @@ package moe.nea.firmament.util
import io.github.moulberry.repo.data.Coordinate
import java.util.concurrent.ConcurrentLinkedQueue
import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.screen.Screen
import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.client.option.GameOptions
import net.minecraft.client.network.ClientPlayerEntity
import net.minecraft.client.render.WorldRenderer
import net.minecraft.client.render.item.ItemRenderer
import net.minecraft.client.world.ClientWorld
import net.minecraft.entity.Entity
import net.minecraft.item.Item
import net.minecraft.network.packet.c2s.play.CommandExecutionC2SPacket
import net.minecraft.registry.BuiltinRegistries
@@ -14,7 +18,9 @@ import net.minecraft.registry.RegistryWrapper
import net.minecraft.resource.ReloadableResourceManagerImpl
import net.minecraft.text.Text
import net.minecraft.util.math.BlockPos
import net.minecraft.world.World
import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.events.WorldReadyEvent
object MC {
@@ -29,6 +35,9 @@ object MC {
(nextTickTodos.poll() ?: break).invoke()
}
}
WorldReadyEvent.subscribe("MC:ready") {
this.lastWorld
}
}
fun sendChat(text: Text) {
@@ -69,6 +78,7 @@ object MC {
inline val resourceManager get() = (instance.resourceManager as ReloadableResourceManagerImpl)
inline val itemRenderer: ItemRenderer get() = instance.itemRenderer
inline val worldRenderer: WorldRenderer get() = instance.worldRenderer
inline val networkHandler get() = player?.networkHandler
inline val instance get() = MinecraftClient.getInstance()
@@ -79,11 +89,11 @@ object MC {
inline val inGameHud get() = instance.inGameHud
inline val font get() = instance.textRenderer
inline val soundManager get() = instance.soundManager
inline val player get() = instance.player
inline val camera get() = instance.cameraEntity
inline val player: ClientPlayerEntity? get() = instance.player
inline val camera: Entity? get() = instance.cameraEntity
inline val guiAtlasManager get() = instance.guiAtlasManager
inline val world get() = instance.world
inline var screen
inline val world: ClientWorld? get() = instance.world
inline var screen: Screen?
get() = instance.currentScreen
set(value) = instance.setScreen(value)
val screenName get() = screen?.title?.unformattedString?.trim()
@@ -92,7 +102,13 @@ object MC {
inline val currentRegistries: RegistryWrapper.WrapperLookup? get() = world?.registryManager
val defaultRegistries: RegistryWrapper.WrapperLookup = BuiltinRegistries.createWrapperLookup()
inline val currentOrDefaultRegistries get() = currentRegistries ?: defaultRegistries
val defaultItems: RegistryWrapper.Impl<Item> = defaultRegistries.getWrapperOrThrow(RegistryKeys.ITEM)
val defaultItems: RegistryWrapper.Impl<Item> = defaultRegistries.getOrThrow(RegistryKeys.ITEM)
var lastWorld: World? = null
get() {
field = world ?: field
return field
}
private set
}

View File

@@ -37,7 +37,7 @@ object SBData {
it.serverType.getOrNull()?.name?.uppercase(),
it.mode.getOrNull(),
it.map.getOrNull())
SkyblockServerUpdateEvent.publish(SkyblockServerUpdateEvent(lastLocraw, null))
SkyblockServerUpdateEvent.publish(SkyblockServerUpdateEvent(lastLocraw, locraw))
profileIdCommandDebounce = TimeMark.now()
}
}

View File

@@ -2,6 +2,7 @@
package moe.nea.firmament.util
import com.mojang.serialization.Codec
import io.github.moulberry.repo.data.NEUIngredient
import io.github.moulberry.repo.data.NEUItem
import io.github.moulberry.repo.data.Rarity
@@ -16,6 +17,9 @@ import net.minecraft.component.type.NbtComponent
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import net.minecraft.nbt.NbtCompound
import net.minecraft.network.RegistryByteBuf
import net.minecraft.network.codec.PacketCodec
import net.minecraft.network.codec.PacketCodecs
import net.minecraft.util.Identifier
import moe.nea.firmament.repo.ItemCache.asItemStack
import moe.nea.firmament.repo.set
@@ -68,6 +72,9 @@ value class SkyblockId(val neuItem: String) {
val NULL: SkyblockId = SkyblockId("null")
val PET_NULL: SkyblockId = SkyblockId("null_pet")
private val illlegalPathRegex = "[^a-z0-9_.-/]".toRegex()
val CODEC = Codec.STRING.xmap({ SkyblockId(it) }, { it.neuItem })
val PACKET_CODEC: PacketCodec<in RegistryByteBuf, SkyblockId> =
PacketCodecs.STRING.xmap({ SkyblockId(it) }, { it.neuItem })
}
}

View File

@@ -1,77 +1,71 @@
package moe.nea.firmament.util.data
import java.util.concurrent.CopyOnWriteArrayList
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents
import kotlin.reflect.KClass
import net.minecraft.client.MinecraftClient
import net.minecraft.server.command.CommandOutput
import net.minecraft.text.Text
import moe.nea.firmament.Firmament
import moe.nea.firmament.events.ScreenChangeEvent
import moe.nea.firmament.util.MC
interface IDataHolder<T> {
companion object {
internal var badLoads: MutableList<String> = CopyOnWriteArrayList()
private val allConfigs: MutableMap<KClass<out IDataHolder<*>>, IDataHolder<*>> = mutableMapOf()
private val dirty: MutableSet<KClass<out IDataHolder<*>>> = mutableSetOf()
companion object {
internal var badLoads: MutableList<String> = CopyOnWriteArrayList()
private val allConfigs: MutableMap<KClass<out IDataHolder<*>>, IDataHolder<*>> = mutableMapOf()
private val dirty: MutableSet<KClass<out IDataHolder<*>>> = mutableSetOf()
internal fun <T : IDataHolder<K>, K> putDataHolder(kClass: KClass<T>, inst: IDataHolder<K>) {
allConfigs[kClass] = inst
}
internal fun <T : IDataHolder<K>, K> putDataHolder(kClass: KClass<T>, inst: IDataHolder<K>) {
allConfigs[kClass] = inst
}
fun <T : IDataHolder<K>, K> markDirty(kClass: KClass<T>) {
if (kClass !in allConfigs) {
Firmament.logger.error("Tried to markDirty '${kClass.qualifiedName}', which isn't registered as 'IConfigHolder'")
return
}
dirty.add(kClass)
}
fun <T : IDataHolder<K>, K> markDirty(kClass: KClass<T>) {
if (kClass !in allConfigs) {
Firmament.logger.error("Tried to markDirty '${kClass.qualifiedName}', which isn't registered as 'IConfigHolder'")
return
}
dirty.add(kClass)
}
private fun performSaves() {
val toSave = dirty.toList().also {
dirty.clear()
}
for (it in toSave) {
val obj = allConfigs[it]
if (obj == null) {
Firmament.logger.error("Tried to save '${it}', which isn't registered as 'ConfigHolder'")
continue
}
obj.save()
}
}
private fun performSaves() {
val toSave = dirty.toList().also {
dirty.clear()
}
for (it in toSave) {
val obj = allConfigs[it]
if (obj == null) {
Firmament.logger.error("Tried to save '${it}', which isn't registered as 'ConfigHolder'")
continue
}
obj.save()
}
}
private fun warnForResetConfigs(player: CommandOutput) {
if (badLoads.isNotEmpty()) {
player.sendMessage(
Text.literal(
"The following configs have been reset: ${badLoads.joinToString(", ")}. " +
"This can be intentional, but probably isn't."
)
)
badLoads.clear()
}
}
private fun warnForResetConfigs() {
if (badLoads.isNotEmpty()) {
MC.sendChat(
Text.literal(
"The following configs have been reset: ${badLoads.joinToString(", ")}. " +
"This can be intentional, but probably isn't."
)
)
badLoads.clear()
}
}
fun registerEvents() {
ScreenChangeEvent.subscribe("IDataHolder:saveOnScreenChange") { event ->
performSaves()
val p = MinecraftClient.getInstance().player
if (p != null) {
warnForResetConfigs(p)
}
}
ClientLifecycleEvents.CLIENT_STOPPING.register(ClientLifecycleEvents.ClientStopping {
performSaves()
})
}
fun registerEvents() {
ScreenChangeEvent.subscribe("IDataHolder:saveOnScreenChange") { event ->
performSaves()
warnForResetConfigs()
}
ClientLifecycleEvents.CLIENT_STOPPING.register(ClientLifecycleEvents.ClientStopping {
performSaves()
})
}
}
}
val data: T
fun save()
fun markDirty()
fun load()
val data: T
fun save()
fun markDirty()
fun load()
}

View File

@@ -4,12 +4,70 @@ import com.mojang.blaze3d.systems.RenderSystem
import me.shedaniel.math.Color
import org.joml.Matrix4f
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.RenderLayer.MultiPhaseParameters
import net.minecraft.client.render.RenderPhase
import net.minecraft.client.render.VertexFormat
import net.minecraft.client.render.VertexFormats
import net.minecraft.util.Identifier
import net.minecraft.util.TriState
import net.minecraft.util.Util
import moe.nea.firmament.util.MC
fun DrawContext.isUntranslatedGuiDrawContext(): Boolean {
return (matrices.peek().positionMatrix.properties() and Matrix4f.PROPERTY_IDENTITY.toInt()) != 0
}
object GuiRenderLayers {
val GUI_TEXTURED_NO_DEPTH = Util.memoize<Identifier, RenderLayer> { texture: Identifier ->
RenderLayer.of("firmament_gui_textured_no_depth",
VertexFormats.POSITION_TEXTURE_COLOR,
VertexFormat.DrawMode.QUADS,
RenderLayer.CUTOUT_BUFFER_SIZE,
MultiPhaseParameters.builder()
.texture(RenderPhase.Texture(texture, TriState.FALSE, false))
.program(RenderPhase.POSITION_TEXTURE_COLOR_PROGRAM)
.transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY)
.depthTest(RenderPhase.ALWAYS_DEPTH_TEST)
.build(false))
}
}
@Deprecated("Use the other drawGuiTexture")
fun DrawContext.drawGuiTexture(
x: Int, y: Int, z: Int, width: Int, height: Int, sprite: Identifier
) = this.drawGuiTexture(RenderLayer::getGuiTextured, sprite, x, y, width, height)
fun DrawContext.drawGuiTexture(
sprite: Identifier,
x: Int, y: Int, width: Int, height: Int
) = this.drawGuiTexture(RenderLayer::getGuiTextured, sprite, x, y, width, height)
fun DrawContext.drawTexture(
sprite: Identifier,
x: Int,
y: Int,
u: Float,
v: Float,
width: Int,
height: Int,
textureWidth: Int,
textureHeight: Int
) {
this.drawTexture(RenderLayer::getGuiTextured,
sprite,
x,
y,
u,
v,
width,
height,
width,
height,
textureWidth,
textureHeight)
}
fun DrawContext.drawLine(fromX: Int, fromY: Int, toX: Int, toY: Int, color: Color) {
// TODO: push scissors
// TODO: use matrix translations and a different render layer
@@ -18,11 +76,12 @@ fun DrawContext.drawLine(fromX: Int, fromY: Int, toX: Int, toY: Int, color: Colo
return
}
RenderSystem.lineWidth(MC.window.scaleFactor.toFloat())
val buf = this.vertexConsumers.getBuffer(RenderInWorldContext.RenderLayers.LINES)
buf.vertex(fromX.toFloat(), fromY.toFloat(), 0F).color(color.color)
.normal(toX - fromX.toFloat(), toY - fromY.toFloat(), 0F)
buf.vertex(toX.toFloat(), toY.toFloat(), 0F).color(color.color)
.normal(toX - fromX.toFloat(), toY - fromY.toFloat(), 0F)
this.draw()
draw { vertexConsumers ->
val buf = vertexConsumers.getBuffer(RenderInWorldContext.RenderLayers.LINES)
buf.vertex(fromX.toFloat(), fromY.toFloat(), 0F).color(color.color)
.normal(toX - fromX.toFloat(), toY - fromY.toFloat(), 0F)
buf.vertex(toX.toFloat(), toY.toFloat(), 0F).color(color.color)
.normal(toX - fromX.toFloat(), toY - fromY.toFloat(), 0F)
}
}

View File

@@ -76,13 +76,10 @@ class FacingThePlayerContext(val worldContext: RenderInWorldContext) {
u1: Float, v1: Float,
u2: Float, v2: Float,
) {
RenderSystem.setShaderTexture(0, texture)
RenderSystem.setShader(GameRenderer::getPositionTexColorProgram)
val buf = worldContext.vertexConsumers.getBuffer(RenderLayer.getGuiTexturedOverlay(texture))
val hw = width / 2F
val hh = height / 2F
val matrix4f: Matrix4f = worldContext.matrixStack.peek().positionMatrix
val buf = Tessellator.getInstance()
.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR)
buf.vertex(matrix4f, -hw, -hh, 0F)
.color(-1)
.texture(u1, v1).next()
@@ -95,7 +92,7 @@ class FacingThePlayerContext(val worldContext: RenderInWorldContext) {
buf.vertex(matrix4f, +hw, -hh, 0F)
.color(-1)
.texture(u2, v1).next()
BufferRenderer.drawWithGlobalProgram(buf.end())
worldContext.vertexConsumers.draw()
}
}

View File

@@ -1,23 +1,30 @@
package moe.nea.firmament.util.render
import net.minecraft.client.gl.ShaderProgram
import net.minecraft.client.gl.Defines
import net.minecraft.client.gl.ShaderProgramKey
import net.minecraft.client.render.RenderPhase
import net.minecraft.client.render.VertexFormat
import net.minecraft.client.render.VertexFormats
import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.RegisterCustomShadersEvent
import moe.nea.firmament.events.DebugInstantiateEvent
import moe.nea.firmament.util.MC
object FirmamentShaders {
val shaders = mutableListOf<ShaderProgramKey>()
private fun shader(name: String, format: VertexFormat, defines: Defines): ShaderProgramKey {
val key = ShaderProgramKey(Firmament.identifier(name), format, defines)
shaders.add(key)
return key
}
private lateinit var _LINES: ShaderProgram
val LINES = RenderPhase.ShaderProgram({ _LINES })
val LINES = RenderPhase.ShaderProgram(shader("core/rendertype_lines", VertexFormats.LINES, Defines.EMPTY))
@Subscribe
fun registerCustomShaders(event: RegisterCustomShadersEvent) {
event.register(
"firmament_rendertype_lines",
VertexFormats.LINES,
{ _LINES = it },
)
}
@Subscribe
fun debugLoad(event: DebugInstantiateEvent) {
shaders.forEach {
MC.instance.shaderLoader.getOrCreateProgram(it)
}
}
}

View File

@@ -1,4 +1,3 @@
package moe.nea.firmament.util.render
import com.mojang.blaze3d.systems.RenderSystem
@@ -9,7 +8,8 @@ import kotlin.math.atan2
import kotlin.math.tan
import net.minecraft.client.gui.DrawContext
import net.minecraft.client.render.BufferRenderer
import net.minecraft.client.render.GameRenderer
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.RenderPhase
import net.minecraft.client.render.Tessellator
import net.minecraft.client.render.VertexFormat.DrawMode
import net.minecraft.client.render.VertexFormats
@@ -17,79 +17,77 @@ import net.minecraft.util.Identifier
object RenderCircleProgress {
fun renderCircle(
drawContext: DrawContext,
texture: Identifier,
progress: Float,
u1: Float,
u2: Float,
v1: Float,
v2: Float,
) {
RenderSystem.setShaderTexture(0, texture)
RenderSystem.setShader(GameRenderer::getPositionTexColorProgram)
RenderSystem.enableBlend()
val matrix: Matrix4f = drawContext.matrices.peek().positionMatrix
val bufferBuilder = Tessellator.getInstance().begin(DrawMode.TRIANGLES, VertexFormats.POSITION_TEXTURE_COLOR)
fun renderCircle(
drawContext: DrawContext,
texture: Identifier,
progress: Float,
u1: Float,
u2: Float,
v1: Float,
v2: Float,
) {
RenderSystem.enableBlend()
drawContext.draw {
val bufferBuilder = it.getBuffer(RenderLayer.getGuiTexturedOverlay(texture))
val matrix: Matrix4f = drawContext.matrices.peek().positionMatrix
val corners = listOf(
Vector2f(0F, -1F),
Vector2f(1F, -1F),
Vector2f(1F, 0F),
Vector2f(1F, 1F),
Vector2f(0F, 1F),
Vector2f(-1F, 1F),
Vector2f(-1F, 0F),
Vector2f(-1F, -1F),
)
val corners = listOf(
Vector2f(0F, -1F),
Vector2f(1F, -1F),
Vector2f(1F, 0F),
Vector2f(1F, 1F),
Vector2f(0F, 1F),
Vector2f(-1F, 1F),
Vector2f(-1F, 0F),
Vector2f(-1F, -1F),
)
for (i in (0 until 8)) {
if (progress < i / 8F) {
break
}
val second = corners[(i + 1) % 8]
val first = corners[i]
if (progress <= (i + 1) / 8F) {
val internalProgress = 1 - (progress - i / 8F) * 8F
val angle = lerpAngle(
atan2(second.y, second.x),
atan2(first.y, first.x),
internalProgress
)
if (angle < tau / 8 || angle >= tau * 7 / 8) {
second.set(1F, tan(angle))
} else if (angle < tau * 3 / 8) {
second.set(1 / tan(angle), 1F)
} else if (angle < tau * 5 / 8) {
second.set(-1F, -tan(angle))
} else {
second.set(-1 / tan(angle), -1F)
}
}
for (i in (0 until 8)) {
if (progress < i / 8F) {
break
}
val second = corners[(i + 1) % 8]
val first = corners[i]
if (progress <= (i + 1) / 8F) {
val internalProgress = 1 - (progress - i / 8F) * 8F
val angle = lerpAngle(
atan2(second.y, second.x),
atan2(first.y, first.x),
internalProgress
)
if (angle < tau / 8 || angle >= tau * 7 / 8) {
second.set(1F, tan(angle))
} else if (angle < tau * 3 / 8) {
second.set(1 / tan(angle), 1F)
} else if (angle < tau * 5 / 8) {
second.set(-1F, -tan(angle))
} else {
second.set(-1 / tan(angle), -1F)
}
}
fun ilerp(f: Float): Float =
ilerp(-1f, 1f, f)
bufferBuilder
.vertex(matrix, second.x, second.y, 0F)
.texture(lerp(u1, u2, ilerp(second.x)), lerp(v1, v2, ilerp(second.y)))
.color(-1)
.next()
bufferBuilder
.vertex(matrix, first.x, first.y, 0F)
.texture(lerp(u1, u2, ilerp(first.x)), lerp(v1, v2, ilerp(first.y)))
.color(-1)
.next()
bufferBuilder
.vertex(matrix, 0F, 0F, 0F)
.texture(lerp(u1, u2, ilerp(0F)), lerp(v1, v2, ilerp(0F)))
.color(-1)
.next()
}
BufferRenderer.drawWithGlobalProgram(bufferBuilder.end())
RenderSystem.disableBlend()
}
fun ilerp(f: Float): Float =
ilerp(-1f, 1f, f)
bufferBuilder
.vertex(matrix, second.x, second.y, 0F)
.texture(lerp(u1, u2, ilerp(second.x)), lerp(v1, v2, ilerp(second.y)))
.color(-1)
.next()
bufferBuilder
.vertex(matrix, first.x, first.y, 0F)
.texture(lerp(u1, u2, ilerp(first.x)), lerp(v1, v2, ilerp(first.y)))
.color(-1)
.next()
bufferBuilder
.vertex(matrix, 0F, 0F, 0F)
.texture(lerp(u1, u2, ilerp(0F)), lerp(v1, v2, ilerp(0F)))
.color(-1)
.next()
}
}
RenderSystem.disableBlend()
}
}

View File

@@ -1,5 +1,3 @@
package moe.nea.firmament.util.render
import com.mojang.blaze3d.systems.RenderSystem
@@ -8,14 +6,12 @@ import java.lang.Math.pow
import org.joml.Matrix4f
import org.joml.Vector3f
import net.minecraft.client.gl.VertexBuffer
import net.minecraft.client.render.BufferBuilder
import net.minecraft.client.render.BufferRenderer
import net.minecraft.client.render.Camera
import net.minecraft.client.render.GameRenderer
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.render.RenderPhase
import net.minecraft.client.render.RenderTickCounter
import net.minecraft.client.render.Tessellator
import net.minecraft.client.render.VertexConsumer
import net.minecraft.client.render.VertexConsumerProvider
import net.minecraft.client.render.VertexFormat
import net.minecraft.client.render.VertexFormats
@@ -31,273 +27,287 @@ import moe.nea.firmament.util.MC
@RenderContextDSL
class RenderInWorldContext private constructor(
private val tesselator: Tessellator,
val matrixStack: MatrixStack,
private val camera: Camera,
private val tickCounter: RenderTickCounter,
val vertexConsumers: VertexConsumerProvider.Immediate,
private val tesselator: Tessellator,
val matrixStack: MatrixStack,
private val camera: Camera,
private val tickCounter: RenderTickCounter,
val vertexConsumers: VertexConsumerProvider.Immediate,
) {
object RenderLayers {
val TRANSLUCENT_TRIS = RenderLayer.of("firmament_translucent_tris",
VertexFormats.POSITION_COLOR,
VertexFormat.DrawMode.TRIANGLES,
RenderLayer.DEFAULT_BUFFER_SIZE,
false, true,
RenderLayer.MultiPhaseParameters.builder()
.depthTest(RenderPhase.ALWAYS_DEPTH_TEST)
.transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY)
.program(RenderPhase.COLOR_PROGRAM)
.build(false))
val LINES = RenderLayer.of("firmament_rendertype_lines",
VertexFormats.LINES,
VertexFormat.DrawMode.LINES,
RenderLayer.DEFAULT_BUFFER_SIZE,
false, false, // do we need translucent? i dont think so
RenderLayer.MultiPhaseParameters.builder()
.depthTest(RenderPhase.ALWAYS_DEPTH_TEST)
.program(FirmamentShaders.LINES)
.build(false)
)
}
object RenderLayers {
val TRANSLUCENT_TRIS = RenderLayer.of("firmament_translucent_tris",
VertexFormats.POSITION_COLOR,
VertexFormat.DrawMode.TRIANGLES,
RenderLayer.CUTOUT_BUFFER_SIZE,
false, true,
RenderLayer.MultiPhaseParameters.builder()
.depthTest(RenderPhase.ALWAYS_DEPTH_TEST)
.transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY)
.program(RenderPhase.POSITION_COLOR_PROGRAM)
.build(false))
val LINES = RenderLayer.of("firmament_rendertype_lines",
VertexFormats.LINES,
VertexFormat.DrawMode.LINES,
RenderLayer.CUTOUT_BUFFER_SIZE,
false, false, // do we need translucent? i dont think so
RenderLayer.MultiPhaseParameters.builder()
.depthTest(RenderPhase.ALWAYS_DEPTH_TEST)
.program(FirmamentShaders.LINES)
.build(false)
)
val COLORED_QUADS = RenderLayer.of(
"firmament_quads",
VertexFormats.POSITION_COLOR,
VertexFormat.DrawMode.QUADS,
RenderLayer.CUTOUT_BUFFER_SIZE,
false, true,
RenderLayer.MultiPhaseParameters.builder()
.depthTest(RenderPhase.ALWAYS_DEPTH_TEST)
.program(RenderPhase.POSITION_COLOR_PROGRAM)
.transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY)
.build(false)
)
}
fun color(color: me.shedaniel.math.Color) {
color(color.red / 255F, color.green / 255f, color.blue / 255f, color.alpha / 255f)
}
@Deprecated("stateful color management is no longer a thing")
fun color(color: me.shedaniel.math.Color) {
color(color.red / 255F, color.green / 255f, color.blue / 255f, color.alpha / 255f)
}
fun color(red: Float, green: Float, blue: Float, alpha: Float) {
RenderSystem.setShaderColor(red, green, blue, alpha)
}
@Deprecated("stateful color management is no longer a thing")
fun color(red: Float, green: Float, blue: Float, alpha: Float) {
RenderSystem.setShaderColor(red, green, blue, alpha)
}
fun block(blockPos: BlockPos) {
matrixStack.push()
matrixStack.translate(blockPos.x.toFloat(), blockPos.y.toFloat(), blockPos.z.toFloat())
buildCube(matrixStack.peek().positionMatrix, tesselator)
matrixStack.pop()
}
fun block(blockPos: BlockPos, color: Int) {
matrixStack.push()
matrixStack.translate(blockPos.x.toFloat(), blockPos.y.toFloat(), blockPos.z.toFloat())
buildCube(matrixStack.peek().positionMatrix, vertexConsumers.getBuffer(RenderLayers.COLORED_QUADS), color)
matrixStack.pop()
}
enum class VerticalAlign {
TOP, BOTTOM, CENTER;
enum class VerticalAlign {
TOP, BOTTOM, CENTER;
fun align(index: Int, count: Int): Float {
return when (this) {
CENTER -> (index - count / 2F) * (1 + MC.font.fontHeight.toFloat())
BOTTOM -> (index - count) * (1 + MC.font.fontHeight.toFloat())
TOP -> (index) * (1 + MC.font.fontHeight.toFloat())
}
}
}
fun align(index: Int, count: Int): Float {
return when (this) {
CENTER -> (index - count / 2F) * (1 + MC.font.fontHeight.toFloat())
BOTTOM -> (index - count) * (1 + MC.font.fontHeight.toFloat())
TOP -> (index) * (1 + MC.font.fontHeight.toFloat())
}
}
}
fun waypoint(position: BlockPos, vararg label: Text) {
text(
position.toCenterPos(),
*label,
Text.literal("§e${FirmFormatters.formatDistance(MC.player?.pos?.distanceTo(position.toCenterPos()) ?: 42069.0)}"),
background = 0xAA202020.toInt()
)
}
fun waypoint(position: BlockPos, vararg label: Text) {
text(
position.toCenterPos(),
*label,
Text.literal("§e${FirmFormatters.formatDistance(MC.player?.pos?.distanceTo(position.toCenterPos()) ?: 42069.0)}"),
background = 0xAA202020.toInt()
)
}
fun withFacingThePlayer(position: Vec3d, block: FacingThePlayerContext.() -> Unit) {
matrixStack.push()
matrixStack.translate(position.x, position.y, position.z)
val actualCameraDistance = position.distanceTo(camera.pos)
val distanceToMoveTowardsCamera = if (actualCameraDistance < 10) 0.0 else -(actualCameraDistance - 10.0)
val vec = position.subtract(camera.pos).multiply(distanceToMoveTowardsCamera / actualCameraDistance)
matrixStack.translate(vec.x, vec.y, vec.z)
matrixStack.multiply(camera.rotation)
matrixStack.scale(0.025F, -0.025F, 1F)
fun withFacingThePlayer(position: Vec3d, block: FacingThePlayerContext.() -> Unit) {
matrixStack.push()
matrixStack.translate(position.x, position.y, position.z)
val actualCameraDistance = position.distanceTo(camera.pos)
val distanceToMoveTowardsCamera = if (actualCameraDistance < 10) 0.0 else -(actualCameraDistance - 10.0)
val vec = position.subtract(camera.pos).multiply(distanceToMoveTowardsCamera / actualCameraDistance)
matrixStack.translate(vec.x, vec.y, vec.z)
matrixStack.multiply(camera.rotation)
matrixStack.scale(0.025F, -0.025F, 1F)
FacingThePlayerContext(this).run(block)
FacingThePlayerContext(this).run(block)
matrixStack.pop()
vertexConsumers.drawCurrentLayer()
}
matrixStack.pop()
vertexConsumers.drawCurrentLayer()
}
fun sprite(position: Vec3d, sprite: Sprite, width: Int, height: Int) {
texture(
position, sprite.atlasId, width, height, sprite.minU, sprite.minV, sprite.maxU, sprite.maxV
)
}
fun sprite(position: Vec3d, sprite: Sprite, width: Int, height: Int) {
texture(
position, sprite.atlasId, width, height, sprite.minU, sprite.minV, sprite.maxU, sprite.maxV
)
}
fun texture(
position: Vec3d, texture: Identifier, width: Int, height: Int,
u1: Float, v1: Float,
u2: Float, v2: Float,
) {
withFacingThePlayer(position) {
texture(texture, width, height, u1, v1, u2, v2)
}
}
fun texture(
position: Vec3d, texture: Identifier, width: Int, height: Int,
u1: Float, v1: Float,
u2: Float, v2: Float,
) {
withFacingThePlayer(position) {
texture(texture, width, height, u1, v1, u2, v2)
}
}
fun text(position: Vec3d, vararg texts: Text, verticalAlign: VerticalAlign = VerticalAlign.CENTER, background: Int = 0x70808080) {
withFacingThePlayer(position) {
text(*texts, verticalAlign = verticalAlign, background = background)
}
}
fun text(
position: Vec3d,
vararg texts: Text,
verticalAlign: VerticalAlign = VerticalAlign.CENTER,
background: Int = 0x70808080
) {
withFacingThePlayer(position) {
text(*texts, verticalAlign = verticalAlign, background = background)
}
}
fun tinyBlock(vec3d: Vec3d, size: Float) {
RenderSystem.setShader(GameRenderer::getPositionColorProgram)
matrixStack.push()
matrixStack.translate(vec3d.x, vec3d.y, vec3d.z)
matrixStack.scale(size, size, size)
matrixStack.translate(-.5, -.5, -.5)
buildCube(matrixStack.peek().positionMatrix, tesselator)
matrixStack.pop()
}
fun tinyBlock(vec3d: Vec3d, size: Float, color: Int) {
matrixStack.push()
matrixStack.translate(vec3d.x, vec3d.y, vec3d.z)
matrixStack.scale(size, size, size)
matrixStack.translate(-.5, -.5, -.5)
buildCube(matrixStack.peek().positionMatrix, vertexConsumers.getBuffer(RenderLayers.COLORED_QUADS), color)
matrixStack.pop()
vertexConsumers.draw()
}
fun wireframeCube(blockPos: BlockPos, lineWidth: Float = 10F) {
RenderSystem.setShader(GameRenderer::getRenderTypeLinesProgram)
matrixStack.push()
RenderSystem.lineWidth(lineWidth / pow(camera.pos.squaredDistanceTo(blockPos.toCenterPos()), 0.25).toFloat())
matrixStack.translate(blockPos.x.toFloat(), blockPos.y.toFloat(), blockPos.z.toFloat())
buildWireFrameCube(matrixStack.peek(), tesselator)
matrixStack.pop()
}
fun wireframeCube(blockPos: BlockPos, lineWidth: Float = 10F) {
val buf = vertexConsumers.getBuffer(RenderLayer.LINES)
matrixStack.push()
// TODO: this does not render through blocks (or water layers) anymore
RenderSystem.lineWidth(lineWidth / pow(camera.pos.squaredDistanceTo(blockPos.toCenterPos()), 0.25).toFloat())
matrixStack.translate(blockPos.x.toFloat(), blockPos.y.toFloat(), blockPos.z.toFloat())
buildWireFrameCube(matrixStack.peek(), buf)
matrixStack.pop()
vertexConsumers.draw()
}
fun line(vararg points: Vec3d, lineWidth: Float = 10F) {
line(points.toList(), lineWidth)
}
fun line(vararg points: Vec3d, lineWidth: Float = 10F) {
line(points.toList(), lineWidth)
}
fun tracer(toWhere: Vec3d, lineWidth: Float = 3f) {
val cameraForward = Vector3f(0f, 0f, -1f).rotate(camera.rotation)
line(camera.pos.add(Vec3d(cameraForward)), toWhere, lineWidth = lineWidth)
}
fun tracer(toWhere: Vec3d, lineWidth: Float = 3f) {
val cameraForward = Vector3f(0f, 0f, -1f).rotate(camera.rotation)
line(camera.pos.add(Vec3d(cameraForward)), toWhere, lineWidth = lineWidth)
}
fun line(points: List<Vec3d>, lineWidth: Float = 10F) {
RenderSystem.lineWidth(lineWidth)
val buffer = tesselator.begin(VertexFormat.DrawMode.LINES, VertexFormats.LINES)
fun line(points: List<Vec3d>, lineWidth: Float = 10F) {
RenderSystem.lineWidth(lineWidth)
// TODO: replace with renderlayers
val buffer = tesselator.begin(VertexFormat.DrawMode.LINES, VertexFormats.LINES)
val matrix = matrixStack.peek()
var lastNormal: Vector3f? = null
points.zipWithNext().forEach { (a, b) ->
val normal = Vector3f(b.x.toFloat(), b.y.toFloat(), b.z.toFloat())
.sub(a.x.toFloat(), a.y.toFloat(), a.z.toFloat())
.normalize()
val lastNormal0 = lastNormal ?: normal
lastNormal = normal
buffer.vertex(matrix.positionMatrix, a.x.toFloat(), a.y.toFloat(), a.z.toFloat())
.color(-1)
.normal(matrix, lastNormal0.x, lastNormal0.y, lastNormal0.z)
.next()
buffer.vertex(matrix.positionMatrix, b.x.toFloat(), b.y.toFloat(), b.z.toFloat())
.color(-1)
.normal(matrix, normal.x, normal.y, normal.z)
.next()
}
val matrix = matrixStack.peek()
var lastNormal: Vector3f? = null
points.zipWithNext().forEach { (a, b) ->
val normal = Vector3f(b.x.toFloat(), b.y.toFloat(), b.z.toFloat())
.sub(a.x.toFloat(), a.y.toFloat(), a.z.toFloat())
.normalize()
val lastNormal0 = lastNormal ?: normal
lastNormal = normal
buffer.vertex(matrix.positionMatrix, a.x.toFloat(), a.y.toFloat(), a.z.toFloat())
.color(-1)
.normal(matrix, lastNormal0.x, lastNormal0.y, lastNormal0.z)
.next()
buffer.vertex(matrix.positionMatrix, b.x.toFloat(), b.y.toFloat(), b.z.toFloat())
.color(-1)
.normal(matrix, normal.x, normal.y, normal.z)
.next()
}
RenderLayers.LINES.draw(buffer.end())
}
RenderLayers.LINES.draw(buffer.end())
}
// TODO: put the favourite icons in front of items again
companion object {
private fun doLine(
matrix: MatrixStack.Entry,
buf: BufferBuilder,
i: Float,
j: Float,
k: Float,
x: Float,
y: Float,
z: Float
) {
val normal = Vector3f(x, y, z)
.sub(i, j, k)
.normalize()
buf.vertex(matrix.positionMatrix, i, j, k)
.normal(matrix, normal.x, normal.y, normal.z)
.color(-1)
.next()
buf.vertex(matrix.positionMatrix, x, y, z)
.normal(matrix, normal.x, normal.y, normal.z)
.color(-1)
.next()
}
companion object {
private fun doLine(
matrix: MatrixStack.Entry,
buf: VertexConsumer,
i: Float,
j: Float,
k: Float,
x: Float,
y: Float,
z: Float
) {
val normal = Vector3f(x, y, z)
.sub(i, j, k)
.normalize()
buf.vertex(matrix.positionMatrix, i, j, k)
.normal(matrix, normal.x, normal.y, normal.z)
.color(-1)
.next()
buf.vertex(matrix.positionMatrix, x, y, z)
.normal(matrix, normal.x, normal.y, normal.z)
.color(-1)
.next()
}
private fun buildWireFrameCube(matrix: MatrixStack.Entry, tessellator: Tessellator) {
val buf = tessellator.begin(VertexFormat.DrawMode.LINES, VertexFormats.LINES)
private fun buildWireFrameCube(matrix: MatrixStack.Entry, buf: VertexConsumer) {
for (i in 0..1) {
for (j in 0..1) {
val i = i.toFloat()
val j = j.toFloat()
doLine(matrix, buf, 0F, i, j, 1F, i, j)
doLine(matrix, buf, i, 0F, j, i, 1F, j)
doLine(matrix, buf, i, j, 0F, i, j, 1F)
}
}
}
for (i in 0..1) {
for (j in 0..1) {
val i = i.toFloat()
val j = j.toFloat()
doLine(matrix, buf, 0F, i, j, 1F, i, j)
doLine(matrix, buf, i, 0F, j, i, 1F, j)
doLine(matrix, buf, i, j, 0F, i, j, 1F)
}
}
BufferRenderer.drawWithGlobalProgram(buf.end())
}
private fun buildCube(matrix: Matrix4f, tessellator: Tessellator) {
val buf = tessellator.begin(VertexFormat.DrawMode.TRIANGLES, VertexFormats.POSITION_COLOR)
buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next()
buf.vertex(matrix, 0.0F, 0.0F, 1.0F).color(-1).next()
buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next()
buf.vertex(matrix, 1.0F, 1.0F, 0.0F).color(-1).next()
buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next()
buf.vertex(matrix, 0.0F, 1.0F, 0.0F).color(-1).next()
buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next()
buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next()
buf.vertex(matrix, 1.0F, 0.0F, 0.0F).color(-1).next()
buf.vertex(matrix, 1.0F, 1.0F, 0.0F).color(-1).next()
buf.vertex(matrix, 1.0F, 0.0F, 0.0F).color(-1).next()
buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next()
buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next()
buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next()
buf.vertex(matrix, 0.0F, 1.0F, 0.0F).color(-1).next()
buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next()
buf.vertex(matrix, 0.0F, 0.0F, 1.0F).color(-1).next()
buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next()
buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next()
buf.vertex(matrix, 0.0F, 0.0F, 1.0F).color(-1).next()
buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next()
buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next()
buf.vertex(matrix, 1.0F, 0.0F, 0.0F).color(-1).next()
buf.vertex(matrix, 1.0F, 1.0F, 0.0F).color(-1).next()
buf.vertex(matrix, 1.0F, 0.0F, 0.0F).color(-1).next()
buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next()
buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next()
buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next()
buf.vertex(matrix, 1.0F, 1.0F, 0.0F).color(-1).next()
buf.vertex(matrix, 0.0F, 1.0F, 0.0F).color(-1).next()
buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next()
buf.vertex(matrix, 0.0F, 1.0F, 0.0F).color(-1).next()
buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next()
buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next()
buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next()
buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next()
RenderLayers.TRANSLUCENT_TRIS.draw(buf.end())
}
private fun buildCube(matrix: Matrix4f, buf: VertexConsumer, color: Int) {
// Y-
buf.vertex(matrix, 0F, 0F, 0F).color(color)
buf.vertex(matrix, 0F, 0F, 1F).color(color)
buf.vertex(matrix, 1F, 0F, 1F).color(color)
buf.vertex(matrix, 1F, 0F, 0F).color(color)
// Y+
buf.vertex(matrix, 0F, 1F, 0F).color(color)
buf.vertex(matrix, 1F, 1F, 0F).color(color)
buf.vertex(matrix, 1F, 1F, 1F).color(color)
buf.vertex(matrix, 0F, 1F, 1F).color(color)
// X-
buf.vertex(matrix, 0F, 0F, 0F).color(color)
buf.vertex(matrix, 0F, 0F, 1F).color(color)
buf.vertex(matrix, 0F, 1F, 1F).color(color)
buf.vertex(matrix, 0F, 1F, 0F).color(color)
// X+
buf.vertex(matrix, 1F, 0F, 0F).color(color)
buf.vertex(matrix, 1F, 1F, 0F).color(color)
buf.vertex(matrix, 1F, 1F, 1F).color(color)
buf.vertex(matrix, 1F, 0F, 1F).color(color)
// Z-
buf.vertex(matrix, 0F, 0F, 0F).color(color)
buf.vertex(matrix, 1F, 0F, 0F).color(color)
buf.vertex(matrix, 1F, 1F, 0F).color(color)
buf.vertex(matrix, 0F, 1F, 0F).color(color)
// Z+
buf.vertex(matrix, 0F, 0F, 1F).color(color)
buf.vertex(matrix, 0F, 1F, 1F).color(color)
buf.vertex(matrix, 1F, 1F, 1F).color(color)
buf.vertex(matrix, 1F, 0F, 1F).color(color)
}
fun renderInWorld(event: WorldRenderLastEvent, block: RenderInWorldContext. () -> Unit) {
RenderSystem.disableDepthTest()
RenderSystem.enableBlend()
RenderSystem.defaultBlendFunc()
RenderSystem.disableCull()
fun renderInWorld(event: WorldRenderLastEvent, block: RenderInWorldContext. () -> Unit) {
// TODO: there should be *no more global state*. the only thing we should be doing is render layers. that includes settings like culling, blending, shader color, and depth testing
// For now i will let these functions remain, but this needs to go before i do a full (non-beta) release
RenderSystem.disableDepthTest()
RenderSystem.enableBlend()
RenderSystem.defaultBlendFunc()
RenderSystem.disableCull()
event.matrices.push()
event.matrices.translate(-event.camera.pos.x, -event.camera.pos.y, -event.camera.pos.z)
event.matrices.push()
event.matrices.translate(-event.camera.pos.x, -event.camera.pos.y, -event.camera.pos.z)
val ctx = RenderInWorldContext(
RenderSystem.renderThreadTesselator(),
event.matrices,
event.camera,
event.tickCounter,
event.vertexConsumers
)
val ctx = RenderInWorldContext(
RenderSystem.renderThreadTesselator(),
event.matrices,
event.camera,
event.tickCounter,
event.vertexConsumers
)
block(ctx)
block(ctx)
event.matrices.pop()
RenderSystem.setShaderColor(1F, 1F, 1F, 1F)
VertexBuffer.unbind()
RenderSystem.enableDepthTest()
RenderSystem.enableCull()
RenderSystem.disableBlend()
}
}
event.matrices.pop()
event.vertexConsumers.draw()
RenderSystem.setShaderColor(1F, 1F, 1F, 1F)
VertexBuffer.unbind()
RenderSystem.enableDepthTest()
RenderSystem.enableCull()
RenderSystem.disableBlend()
}
}
}

Some files were not shown because too many files have changed in this diff Show More