Merge branch 'mc-1.21.3'
This commit is contained in:
@@ -177,6 +177,7 @@ fun createIsolatedSourceSet(name: String, path: String = "compat/$name", isEnabl
|
|||||||
classpath.from(configurations.getByName(ss.compileClasspathConfigurationName))
|
classpath.from(configurations.getByName(ss.compileClasspathConfigurationName))
|
||||||
}
|
}
|
||||||
collectTranslations {
|
collectTranslations {
|
||||||
|
// TODO: this does not work, somehow
|
||||||
this.classes.from(sourceSets.main.get().kotlin.classesDirectory)
|
this.classes.from(sourceSets.main.get().kotlin.classesDirectory)
|
||||||
}
|
}
|
||||||
return ss
|
return ss
|
||||||
|
|||||||
@@ -10,3 +10,6 @@ archives_base_name=Firmament
|
|||||||
maven_group=moe.nea.firmament
|
maven_group=moe.nea.firmament
|
||||||
|
|
||||||
firmament.compiletimerepohash=a6116d945491d7c57c93d43f51250f93d62d8434
|
firmament.compiletimerepohash=a6116d945491d7c57c93d43f51250f93d62d8434
|
||||||
|
|
||||||
|
# TODO: remove after https://github.com/google/ksp/issues/2072
|
||||||
|
ksp.incremental=false
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ notenoughanimations = "BQ8qstAV"
|
|||||||
# Update from https://modrinth.com/mod/cit-resewn/versions?l=fabric
|
# Update from https://modrinth.com/mod/cit-resewn/versions?l=fabric
|
||||||
citresewn = "1.2.0+1.21"
|
citresewn = "1.2.0+1.21"
|
||||||
|
|
||||||
devauth = "1.2.0"
|
devauth = "1.2.1"
|
||||||
|
|
||||||
# Update from https://ktor.io/
|
# Update from https://ktor.io/
|
||||||
ktor = "3.0.2"
|
ktor = "3.0.2"
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
package moe.nea.firmament.compat.rei
|
package moe.nea.firmament.compat.rei
|
||||||
|
|
||||||
import me.shedaniel.math.Dimension
|
import me.shedaniel.math.Dimension
|
||||||
|
import me.shedaniel.math.FloatingDimension
|
||||||
import me.shedaniel.math.Point
|
import me.shedaniel.math.Point
|
||||||
import me.shedaniel.math.Rectangle
|
import me.shedaniel.math.Rectangle
|
||||||
import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds
|
import me.shedaniel.rei.api.client.gui.widgets.WidgetWithBounds
|
||||||
import net.minecraft.client.gui.DrawContext
|
import net.minecraft.client.gui.DrawContext
|
||||||
import net.minecraft.client.gui.Drawable
|
|
||||||
import net.minecraft.client.gui.Element
|
import net.minecraft.client.gui.Element
|
||||||
import net.minecraft.client.gui.ParentElement
|
|
||||||
import net.minecraft.entity.LivingEntity
|
import net.minecraft.entity.LivingEntity
|
||||||
import moe.nea.firmament.gui.entity.EntityRenderer
|
import moe.nea.firmament.gui.entity.EntityRenderer
|
||||||
import moe.nea.firmament.util.ErrorUtil
|
import moe.nea.firmament.util.ErrorUtil
|
||||||
|
|
||||||
|
|
||||||
class EntityWidget(val entity: LivingEntity?, val point: Point) : WidgetWithBounds() {
|
class EntityWidget(
|
||||||
|
val entity: LivingEntity?,
|
||||||
|
val point: Point,
|
||||||
|
val size: FloatingDimension = FloatingDimension(defaultSize)
|
||||||
|
) : WidgetWithBounds() {
|
||||||
override fun children(): List<Element> {
|
override fun children(): List<Element> {
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
@@ -22,18 +25,35 @@ class EntityWidget(val entity: LivingEntity?, val point: Point) : WidgetWithBoun
|
|||||||
|
|
||||||
override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
|
override fun render(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) {
|
||||||
try {
|
try {
|
||||||
if (!hasErrored)
|
context.matrices.push()
|
||||||
EntityRenderer.renderEntity(entity!!, context, point.x, point.y, mouseX.toFloat(), mouseY.toFloat())
|
if (!hasErrored) {
|
||||||
|
context.matrices.translate(point.x.toDouble(), point.y.toDouble(), 0.0)
|
||||||
|
val xScale = size.width / defaultSize.width.toDouble()
|
||||||
|
val yScale = size.height / defaultSize.height.toDouble()
|
||||||
|
context.matrices.scale(xScale.toFloat(), yScale.toFloat(), 1.0F)
|
||||||
|
EntityRenderer.renderEntity(
|
||||||
|
entity!!,
|
||||||
|
context,
|
||||||
|
0, 0,
|
||||||
|
(mouseX - point.x) * xScale,
|
||||||
|
(mouseY - point.y) * yScale)
|
||||||
|
}
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
ErrorUtil.softError("Failed to render constructed entity: $entity", ex)
|
ErrorUtil.softError("Failed to render constructed entity: $entity", ex)
|
||||||
hasErrored = true
|
hasErrored = true
|
||||||
|
} finally {
|
||||||
|
context.matrices.pop()
|
||||||
}
|
}
|
||||||
if (hasErrored) {
|
if (hasErrored) {
|
||||||
context.fill(point.x, point.y, point.x + 50, point.y + 80, 0xFFAA2222.toInt())
|
context.fill(point.x, point.y, point.x + size.width.toInt(), point.y + size.height.toInt(), 0xFFAA2222.toInt())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val defaultSize = Dimension(50, 80)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getBounds(): Rectangle {
|
override fun getBounds(): Rectangle {
|
||||||
return Rectangle(point, Dimension(50, 80))
|
return Rectangle(point, size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import moe.nea.firmament.compat.rei.recipes.SBEssenceUpgradeRecipe
|
|||||||
import moe.nea.firmament.compat.rei.recipes.SBForgeRecipe
|
import moe.nea.firmament.compat.rei.recipes.SBForgeRecipe
|
||||||
import moe.nea.firmament.compat.rei.recipes.SBKatRecipe
|
import moe.nea.firmament.compat.rei.recipes.SBKatRecipe
|
||||||
import moe.nea.firmament.compat.rei.recipes.SBMobDropRecipe
|
import moe.nea.firmament.compat.rei.recipes.SBMobDropRecipe
|
||||||
|
import moe.nea.firmament.compat.rei.recipes.SBReforgeRecipe
|
||||||
import moe.nea.firmament.events.HandledScreenPushREIEvent
|
import moe.nea.firmament.events.HandledScreenPushREIEvent
|
||||||
import moe.nea.firmament.features.inventory.CraftingOverlay
|
import moe.nea.firmament.features.inventory.CraftingOverlay
|
||||||
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen
|
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen
|
||||||
@@ -78,6 +79,7 @@ class FirmamentReiPlugin : REIClientPlugin {
|
|||||||
registry.add(SBForgeRecipe.Category)
|
registry.add(SBForgeRecipe.Category)
|
||||||
registry.add(SBMobDropRecipe.Category)
|
registry.add(SBMobDropRecipe.Category)
|
||||||
registry.add(SBKatRecipe.Category)
|
registry.add(SBKatRecipe.Category)
|
||||||
|
registry.add(SBReforgeRecipe.Category)
|
||||||
registry.add(SBEssenceUpgradeRecipe.Category)
|
registry.add(SBEssenceUpgradeRecipe.Category)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +92,10 @@ class FirmamentReiPlugin : REIClientPlugin {
|
|||||||
registry.registerDisplayGenerator(
|
registry.registerDisplayGenerator(
|
||||||
SBCraftingRecipe.Category.catIdentifier,
|
SBCraftingRecipe.Category.catIdentifier,
|
||||||
SkyblockCraftingRecipeDynamicGenerator)
|
SkyblockCraftingRecipeDynamicGenerator)
|
||||||
|
registry.registerDisplayGenerator(
|
||||||
|
SBReforgeRecipe.catIdentifier,
|
||||||
|
SBReforgeRecipe.DynamicGenerator
|
||||||
|
)
|
||||||
registry.registerDisplayGenerator(
|
registry.registerDisplayGenerator(
|
||||||
SBForgeRecipe.Category.categoryIdentifier,
|
SBForgeRecipe.Category.categoryIdentifier,
|
||||||
SkyblockForgeRecipeDynamicGenerator)
|
SkyblockForgeRecipeDynamicGenerator)
|
||||||
|
|||||||
@@ -0,0 +1,210 @@
|
|||||||
|
package moe.nea.firmament.compat.rei.recipes
|
||||||
|
|
||||||
|
import java.util.Optional
|
||||||
|
import me.shedaniel.math.Dimension
|
||||||
|
import me.shedaniel.math.FloatingDimension
|
||||||
|
import me.shedaniel.math.Point
|
||||||
|
import me.shedaniel.math.Rectangle
|
||||||
|
import me.shedaniel.rei.api.client.gui.Renderer
|
||||||
|
import me.shedaniel.rei.api.client.gui.widgets.Label
|
||||||
|
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.client.registry.display.DynamicDisplayGenerator
|
||||||
|
import me.shedaniel.rei.api.client.view.ViewSearchBuilder
|
||||||
|
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.entry.EntryIngredient
|
||||||
|
import me.shedaniel.rei.api.common.entry.EntryStack
|
||||||
|
import net.minecraft.entity.EntityType
|
||||||
|
import net.minecraft.entity.SpawnReason
|
||||||
|
import net.minecraft.text.Text
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import net.minecraft.village.VillagerProfession
|
||||||
|
import moe.nea.firmament.Firmament
|
||||||
|
import moe.nea.firmament.compat.rei.EntityWidget
|
||||||
|
import moe.nea.firmament.compat.rei.SBItemEntryDefinition
|
||||||
|
import moe.nea.firmament.gui.entity.EntityRenderer
|
||||||
|
import moe.nea.firmament.repo.Reforge
|
||||||
|
import moe.nea.firmament.repo.ReforgeStore
|
||||||
|
import moe.nea.firmament.repo.RepoItemTypeCache
|
||||||
|
import moe.nea.firmament.repo.RepoManager
|
||||||
|
import moe.nea.firmament.repo.SBItemStack
|
||||||
|
import moe.nea.firmament.util.AprilFoolsUtil
|
||||||
|
import moe.nea.firmament.util.FirmFormatters
|
||||||
|
import moe.nea.firmament.util.SkyblockId
|
||||||
|
import moe.nea.firmament.util.gold
|
||||||
|
import moe.nea.firmament.util.grey
|
||||||
|
import moe.nea.firmament.util.skyblock.ItemType
|
||||||
|
import moe.nea.firmament.util.skyblock.Rarity
|
||||||
|
import moe.nea.firmament.util.skyblock.SkyBlockItems
|
||||||
|
import moe.nea.firmament.util.skyblockId
|
||||||
|
import moe.nea.firmament.util.tr
|
||||||
|
|
||||||
|
class SBReforgeRecipe(
|
||||||
|
val reforge: Reforge,
|
||||||
|
val limitToItem: SkyblockId?,
|
||||||
|
) : Display {
|
||||||
|
companion object {
|
||||||
|
val catIdentifier = CategoryIdentifier.of<SBReforgeRecipe>(Firmament.MOD_ID, "reforge_recipe")
|
||||||
|
}
|
||||||
|
|
||||||
|
object Category : DisplayCategory<SBReforgeRecipe> {
|
||||||
|
override fun getCategoryIdentifier(): CategoryIdentifier<out SBReforgeRecipe> {
|
||||||
|
return catIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTitle(): Text {
|
||||||
|
return tr("firmament.recipecategory.reforge", "Reforge")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getIcon(): Renderer {
|
||||||
|
return SBItemEntryDefinition.getEntry(SkyBlockItems.REFORGE_ANVIL)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setupDisplay(display: SBReforgeRecipe, bounds: Rectangle): MutableList<Widget> {
|
||||||
|
val list = mutableListOf<Widget>()
|
||||||
|
list.add(Widgets.createRecipeBase(bounds))
|
||||||
|
val inputSlot = Widgets.createSlot(Point(bounds.minX + 10, bounds.centerY - 9))
|
||||||
|
.markInput().entries(display.inputItems)
|
||||||
|
list.add(inputSlot)
|
||||||
|
if (display.reforgeStone != null) {
|
||||||
|
list.add(Widgets.createSlot(Point(bounds.minX + 10 + 24, bounds.centerY - 9 - 10))
|
||||||
|
.markInput().entry(display.reforgeStone))
|
||||||
|
list.add(Widgets.withTooltip(
|
||||||
|
Widgets.withTranslate(Widgets.wrapRenderer(
|
||||||
|
Rectangle(Point(bounds.minX + 10 + 24, bounds.centerY - 9 + 10), Dimension(16, 16)),
|
||||||
|
SBItemEntryDefinition.getEntry(SkyBlockItems.REFORGE_ANVIL)), 0.0, 0.0, 150.0),
|
||||||
|
Rarity.entries.mapNotNull { rarity ->
|
||||||
|
display.reforge.reforgeCosts?.get(rarity)?.let { rarity to it }
|
||||||
|
}.map { (rarity, cost) ->
|
||||||
|
Text.literal("")
|
||||||
|
.append(rarity.text)
|
||||||
|
.append(": ")
|
||||||
|
.append(Text.literal("${FirmFormatters.formatCommas(cost, 0)} Coins").gold())
|
||||||
|
}
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
val size = if (AprilFoolsUtil.isAprilFoolsDay) 1.2 else 0.6
|
||||||
|
val dimension =
|
||||||
|
FloatingDimension(EntityWidget.defaultSize.width * size, EntityWidget.defaultSize.height * size)
|
||||||
|
list.add(Widgets.withTooltip(
|
||||||
|
EntityWidget(
|
||||||
|
EntityType.VILLAGER.create(EntityRenderer.fakeWorld, SpawnReason.COMMAND)
|
||||||
|
?.also { it.villagerData = it.villagerData.withProfession(VillagerProfession.WEAPONSMITH) },
|
||||||
|
Point(bounds.minX + 10 + 24 + 8 - dimension.width / 2, bounds.centerY - dimension.height / 2),
|
||||||
|
dimension
|
||||||
|
),
|
||||||
|
tr("firmament.recipecategory.reforge.basic",
|
||||||
|
"This is a basic reforge, available at the Blacksmith.").grey()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
list.add(Widgets.createSlot(Point(bounds.minX + 10 + 24 + 24, bounds.centerY - 9))
|
||||||
|
.markInput().entries(display.outputItems))
|
||||||
|
val statToLineMappings = mutableListOf<Pair<String, Label>>()
|
||||||
|
for ((i, statId) in display.reforge.statUniverse.withIndex()) {
|
||||||
|
val label = Widgets.createLabel(
|
||||||
|
Point(bounds.minX + 10 + 24 + 24 + 20, bounds.minY + 8 + i * 11),
|
||||||
|
SBItemStack.Companion.StatLine(SBItemStack.statIdToName(statId), null).reconstitute(7))
|
||||||
|
.horizontalAlignment(Label.LEFT_ALIGNED)
|
||||||
|
statToLineMappings.add(statId to label)
|
||||||
|
list.add(label)
|
||||||
|
}
|
||||||
|
fun updateStatLines() {
|
||||||
|
val entry = inputSlot.currentEntry?.castValue<SBItemStack>() ?: return
|
||||||
|
val stats = display.reforge.reforgeStats?.get(entry.rarity) ?: mapOf()
|
||||||
|
for ((stat, label) in statToLineMappings) {
|
||||||
|
label.message =
|
||||||
|
SBItemStack.Companion.StatLine(
|
||||||
|
SBItemStack.statIdToName(stat), null,
|
||||||
|
valueNum = stats[stat]
|
||||||
|
).reconstitute(7)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateStatLines()
|
||||||
|
inputSlot.withEntriesListener { updateStatLines() }
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object DynamicGenerator : DynamicDisplayGenerator<SBReforgeRecipe> {
|
||||||
|
fun getRecipesForSBItemStack(item: SBItemStack): Optional<List<SBReforgeRecipe>> {
|
||||||
|
val reforgeRecipes = mutableListOf<SBReforgeRecipe>()
|
||||||
|
for (reforge in ReforgeStore.findEligibleForInternalName(item.skyblockId)) {
|
||||||
|
reforgeRecipes.add(SBReforgeRecipe(reforge, item.skyblockId))
|
||||||
|
}
|
||||||
|
for (reforge in ReforgeStore.findEligibleForItem(item.itemType ?: ItemType.NIL)) {
|
||||||
|
reforgeRecipes.add(SBReforgeRecipe(reforge, item.skyblockId))
|
||||||
|
}
|
||||||
|
if (reforgeRecipes.isEmpty()) return Optional.empty()
|
||||||
|
return Optional.of(reforgeRecipes)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRecipeFor(entry: EntryStack<*>): Optional<List<SBReforgeRecipe>> {
|
||||||
|
if (entry.type != SBItemEntryDefinition.type) return Optional.empty()
|
||||||
|
val item = entry.castValue<SBItemStack>()
|
||||||
|
return getRecipesForSBItemStack(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUsageFor(entry: EntryStack<*>): Optional<List<SBReforgeRecipe>> {
|
||||||
|
if (entry.type != SBItemEntryDefinition.type) return Optional.empty()
|
||||||
|
val item = entry.castValue<SBItemStack>()
|
||||||
|
ReforgeStore.byReforgeStone[item.skyblockId]?.let { stoneReforge ->
|
||||||
|
return Optional.of(listOf(SBReforgeRecipe(stoneReforge, null)))
|
||||||
|
}
|
||||||
|
return getRecipesForSBItemStack(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun generate(builder: ViewSearchBuilder): Optional<List<SBReforgeRecipe>> {
|
||||||
|
// TODO: check builder.recipesFor and such and optionally return all reforge recipes
|
||||||
|
return Optional.empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val eligibleItems =
|
||||||
|
if (limitToItem != null) listOfNotNull(RepoManager.getNEUItem(limitToItem))
|
||||||
|
else reforge.eligibleItems.flatMap {
|
||||||
|
when (it) {
|
||||||
|
is Reforge.ReforgeEligibilityFilter.AllowsInternalName ->
|
||||||
|
listOfNotNull(RepoManager.getNEUItem(it.internalName))
|
||||||
|
|
||||||
|
is Reforge.ReforgeEligibilityFilter.AllowsItemType ->
|
||||||
|
ReforgeStore.resolveItemType(it.itemType)
|
||||||
|
.flatMap {
|
||||||
|
RepoItemTypeCache.byItemType[it] ?: listOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
is Reforge.ReforgeEligibilityFilter.AllowsVanillaItemType -> {
|
||||||
|
listOf() // TODO: add filter support for this and potentially rework this to search for the declared item type in repo, instead of remapped item type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val inputItems = eligibleItems.map { SBItemEntryDefinition.getEntry(it.skyblockId) }
|
||||||
|
private val outputItems =
|
||||||
|
inputItems.map { SBItemEntryDefinition.getEntry(it.value.copy(reforge = reforge.reforgeId)) }
|
||||||
|
private val reforgeStone = reforge.reforgeStone?.let(SBItemEntryDefinition::getEntry)
|
||||||
|
private val inputEntries =
|
||||||
|
listOf(EntryIngredient.of(inputItems)) + listOfNotNull(reforgeStone?.let(EntryIngredient::of))
|
||||||
|
private val outputEntries = listOf(EntryIngredient.of(outputItems))
|
||||||
|
|
||||||
|
override fun getInputEntries(): List<EntryIngredient> {
|
||||||
|
return inputEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getOutputEntries(): List<EntryIngredient> {
|
||||||
|
return outputEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCategoryIdentifier(): CategoryIdentifier<*> {
|
||||||
|
return catIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDisplayLocation(): Optional<Identifier> {
|
||||||
|
return Optional.empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSerializer(): DisplaySerializer<out Display>? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,9 +11,11 @@ import dev.isxander.yacl3.api.OptionGroup
|
|||||||
import dev.isxander.yacl3.api.YetAnotherConfigLib
|
import dev.isxander.yacl3.api.YetAnotherConfigLib
|
||||||
import dev.isxander.yacl3.api.controller.ControllerBuilder
|
import dev.isxander.yacl3.api.controller.ControllerBuilder
|
||||||
import dev.isxander.yacl3.api.controller.DoubleSliderControllerBuilder
|
import dev.isxander.yacl3.api.controller.DoubleSliderControllerBuilder
|
||||||
|
import dev.isxander.yacl3.api.controller.EnumControllerBuilder
|
||||||
import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder
|
import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder
|
||||||
import dev.isxander.yacl3.api.controller.StringControllerBuilder
|
import dev.isxander.yacl3.api.controller.StringControllerBuilder
|
||||||
import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder
|
import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder
|
||||||
|
import dev.isxander.yacl3.api.controller.ValueFormatter
|
||||||
import dev.isxander.yacl3.gui.YACLScreen
|
import dev.isxander.yacl3.gui.YACLScreen
|
||||||
import dev.isxander.yacl3.gui.tab.ListHolderWidget
|
import dev.isxander.yacl3.gui.tab.ListHolderWidget
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
@@ -23,8 +25,10 @@ import net.minecraft.client.gui.Element
|
|||||||
import net.minecraft.client.gui.screen.Screen
|
import net.minecraft.client.gui.screen.Screen
|
||||||
import net.minecraft.text.Text
|
import net.minecraft.text.Text
|
||||||
import moe.nea.firmament.gui.config.BooleanHandler
|
import moe.nea.firmament.gui.config.BooleanHandler
|
||||||
|
import moe.nea.firmament.gui.config.ChoiceHandler
|
||||||
import moe.nea.firmament.gui.config.ClickHandler
|
import moe.nea.firmament.gui.config.ClickHandler
|
||||||
import moe.nea.firmament.gui.config.DurationHandler
|
import moe.nea.firmament.gui.config.DurationHandler
|
||||||
|
import moe.nea.firmament.gui.config.EnumRenderer
|
||||||
import moe.nea.firmament.gui.config.FirmamentConfigScreenProvider
|
import moe.nea.firmament.gui.config.FirmamentConfigScreenProvider
|
||||||
import moe.nea.firmament.gui.config.HudMeta
|
import moe.nea.firmament.gui.config.HudMeta
|
||||||
import moe.nea.firmament.gui.config.HudMetaHandler
|
import moe.nea.firmament.gui.config.HudMetaHandler
|
||||||
@@ -89,6 +93,10 @@ class YaclIntegration : FirmamentConfigScreenProvider {
|
|||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
is ChoiceHandler<*> -> return createDefaultBinding {
|
||||||
|
createChoiceBinding(handler as ChoiceHandler<*>, managedOption as ManagedOption<*>, it as Option<*>)
|
||||||
|
}.build()
|
||||||
|
|
||||||
is BooleanHandler -> return createDefaultBinding(TickBoxControllerBuilder::create).build()
|
is BooleanHandler -> return createDefaultBinding(TickBoxControllerBuilder::create).build()
|
||||||
is StringHandler -> return createDefaultBinding(StringControllerBuilder::create).build()
|
is StringHandler -> return createDefaultBinding(StringControllerBuilder::create).build()
|
||||||
is IntegerHandler -> return createDefaultBinding {
|
is IntegerHandler -> return createDefaultBinding {
|
||||||
@@ -114,6 +122,27 @@ class YaclIntegration : FirmamentConfigScreenProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum class Sacrifice {}
|
||||||
|
|
||||||
|
private fun createChoiceBinding(
|
||||||
|
handler: ChoiceHandler<*>,
|
||||||
|
managedOption: ManagedOption<*>,
|
||||||
|
option: Option<*>
|
||||||
|
): ControllerBuilder<Any> {
|
||||||
|
val b = EnumControllerBuilder.create(option as Option<Sacrifice>)
|
||||||
|
b.enumClass(handler.enumClass as Class<Sacrifice>)
|
||||||
|
/**
|
||||||
|
* This is a function with E to avoid realizing the Sacrifice outside of a `X<E>` wrapper.
|
||||||
|
*/
|
||||||
|
fun <E : Enum<*>> makeValueFormatter(): ValueFormatter<E> {
|
||||||
|
return ValueFormatter<E> {
|
||||||
|
(handler.renderer as EnumRenderer<E>).getName(managedOption as ManagedOption<E>, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.formatValue(makeValueFormatter())
|
||||||
|
return b as ControllerBuilder<Any>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun buildConfig(): YetAnotherConfigLib {
|
fun buildConfig(): YetAnotherConfigLib {
|
||||||
return YetAnotherConfigLib.createBuilder()
|
return YetAnotherConfigLib.createBuilder()
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package moe.nea.firmament.mixins;
|
||||||
|
|
||||||
|
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||||
|
import com.llamalad7.mixinextras.sugar.Local;
|
||||||
|
import net.fabricmc.fabric.impl.command.client.ClientCommandInternals;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
|
||||||
|
@Mixin(ClientCommandInternals.class)
|
||||||
|
public class AlwaysDisplayFirmamentClientCommandErrors {
|
||||||
|
@ModifyExpressionValue(method = "executeCommand", at = @At(value = "INVOKE", target = "Lnet/fabricmc/fabric/impl/command/client/ClientCommandInternals;isIgnoredException(Lcom/mojang/brigadier/exceptions/CommandExceptionType;)Z"))
|
||||||
|
private static boolean markFirmamentExceptionsAsNotIgnores(boolean original, @Local(argsOnly = true) String command) {
|
||||||
|
if (command.startsWith("firm ") || command.equals("firm") || command.startsWith("firmament ") || command.equals("firmament")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,7 +51,7 @@ public class FirmKeybindsInVanillaControlsPatch {
|
|||||||
var config = FirmamentKeyBindings.INSTANCE.getKeyBindings().get(binding);
|
var config = FirmamentKeyBindings.INSTANCE.getKeyBindings().get(binding);
|
||||||
if (config == null) return;
|
if (config == null) return;
|
||||||
resetButton.active = false;
|
resetButton.active = false;
|
||||||
editButton.setMessage(Text.translatable("firmament.keybinding.external", config.value.format()));
|
editButton.setMessage(Text.translatable("firmament.keybinding.external", config.getValue().format()));
|
||||||
ci.cancel();
|
ci.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package moe.nea.firmament.mixins;
|
||||||
|
|
||||||
|
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
|
||||||
|
import moe.nea.firmament.features.fixes.Fixes;
|
||||||
|
import net.minecraft.client.gui.DrawContext;
|
||||||
|
import net.minecraft.client.gui.screen.ingame.InventoryScreen;
|
||||||
|
import net.minecraft.client.gui.screen.ingame.StatusEffectsDisplay;
|
||||||
|
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.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
@Mixin(InventoryScreen.class)
|
||||||
|
public abstract class HideStatusEffectsPatch {
|
||||||
|
@Shadow
|
||||||
|
public abstract boolean shouldHideStatusEffectHud();
|
||||||
|
|
||||||
|
@Inject(method = "shouldHideStatusEffectHud", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void hideStatusEffects(CallbackInfoReturnable<Boolean> cir) {
|
||||||
|
cir.setReturnValue(!Fixes.TConfig.INSTANCE.getHidePotionEffects());
|
||||||
|
}
|
||||||
|
|
||||||
|
@WrapWithCondition(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/StatusEffectsDisplay;drawStatusEffects(Lnet/minecraft/client/gui/DrawContext;IIF)V"))
|
||||||
|
private boolean conditionalRenderStatuses(StatusEffectsDisplay instance, DrawContext context, int mouseX, int mouseY, float tickDelta) {
|
||||||
|
return shouldHideStatusEffectHud() || !Fixes.TConfig.INSTANCE.getHidePotionEffects();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package moe.nea.firmament.mixins;
|
||||||
|
|
||||||
|
import moe.nea.firmament.features.chat.QuickCommands;
|
||||||
|
import net.minecraft.client.network.ClientPlayNetworkHandler;
|
||||||
|
import net.minecraft.network.packet.s2c.play.CommandTreeS2CPacket;
|
||||||
|
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(ClientPlayNetworkHandler.class)
|
||||||
|
public class SaveOriginalCommandTreePacket {
|
||||||
|
@Inject(method = "onCommandTree", at = @At(value = "RETURN"))
|
||||||
|
private void saveUnmodifiedCommandTree(CommandTreeS2CPacket packet, CallbackInfo ci) {
|
||||||
|
QuickCommands.INSTANCE.setLastReceivedTreePacket(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package moe.nea.firmament.mixins;
|
||||||
|
|
||||||
|
import moe.nea.firmament.util.mc.TolerantRegistriesOps;
|
||||||
|
import net.minecraft.registry.entry.RegistryEntryOwner;
|
||||||
|
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.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
@Mixin(RegistryEntryOwner.class)
|
||||||
|
public interface TolerateFirmamentTolerateRegistryOwners<T> {
|
||||||
|
@Inject(method = "ownerEquals", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void equalTolerantRegistryOwners(RegistryEntryOwner<T> other, CallbackInfoReturnable<Boolean> cir) {
|
||||||
|
if (other instanceof TolerantRegistriesOps.TolerantOwner<?>) {
|
||||||
|
cir.setReturnValue(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
src/main/kotlin/commands/Duration.kt
Normal file
75
src/main/kotlin/commands/Duration.kt
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package moe.nea.firmament.commands
|
||||||
|
|
||||||
|
import com.mojang.brigadier.StringReader
|
||||||
|
import com.mojang.brigadier.arguments.ArgumentType
|
||||||
|
import com.mojang.brigadier.context.CommandContext
|
||||||
|
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType
|
||||||
|
import com.mojang.brigadier.suggestion.Suggestions
|
||||||
|
import com.mojang.brigadier.suggestion.SuggestionsBuilder
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import java.util.function.Function
|
||||||
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
import kotlin.time.DurationUnit
|
||||||
|
import kotlin.time.toDuration
|
||||||
|
import moe.nea.firmament.util.tr
|
||||||
|
|
||||||
|
object DurationArgumentType : ArgumentType<Duration> {
|
||||||
|
val unknownTimeCode = DynamicCommandExceptionType { timeCode ->
|
||||||
|
tr("firmament.command-argument.duration.error",
|
||||||
|
"Unknown time code '$timeCode'")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun parse(reader: StringReader): Duration {
|
||||||
|
val start = reader.cursor
|
||||||
|
val string = reader.readUnquotedString()
|
||||||
|
val matcher = regex.matcher(string)
|
||||||
|
var s = 0
|
||||||
|
var time = 0.seconds
|
||||||
|
fun createError(till: Int) {
|
||||||
|
throw unknownTimeCode.createWithContext(
|
||||||
|
reader.also { it.cursor = start + s },
|
||||||
|
string.substring(s, till))
|
||||||
|
}
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
if (matcher.start() != s) {
|
||||||
|
createError(matcher.start())
|
||||||
|
}
|
||||||
|
s = matcher.end()
|
||||||
|
val amount = matcher.group("count").toDouble()
|
||||||
|
val what = timeSuffixes[matcher.group("what").single()]!!
|
||||||
|
time += amount.toDuration(what)
|
||||||
|
}
|
||||||
|
if (string.length != s) {
|
||||||
|
createError(string.length)
|
||||||
|
}
|
||||||
|
return time
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun <S : Any?> listSuggestions(
|
||||||
|
context: CommandContext<S>,
|
||||||
|
builder: SuggestionsBuilder
|
||||||
|
): CompletableFuture<Suggestions> {
|
||||||
|
val remaining = builder.remainingLowerCase.substringBefore(' ')
|
||||||
|
if (remaining.isEmpty()) return super.listSuggestions(context, builder)
|
||||||
|
if (remaining.last().isDigit()) {
|
||||||
|
for (timeSuffix in timeSuffixes.keys) {
|
||||||
|
builder.suggest(remaining + timeSuffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.buildFuture()
|
||||||
|
}
|
||||||
|
|
||||||
|
val timeSuffixes = mapOf(
|
||||||
|
'm' to DurationUnit.MINUTES,
|
||||||
|
's' to DurationUnit.SECONDS,
|
||||||
|
'h' to DurationUnit.HOURS,
|
||||||
|
)
|
||||||
|
val regex = "(?<count>[0-9]+)(?<what>[${timeSuffixes.keys.joinToString("")}])".toPattern()
|
||||||
|
|
||||||
|
override fun getExamples(): Collection<String> {
|
||||||
|
return listOf("3m", "20s", "1h45m")
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/main/kotlin/events/PartyMessageReceivedEvent.kt
Normal file
9
src/main/kotlin/events/PartyMessageReceivedEvent.kt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package moe.nea.firmament.events
|
||||||
|
|
||||||
|
data class PartyMessageReceivedEvent(
|
||||||
|
val from: ProcessChatEvent,
|
||||||
|
val message: String,
|
||||||
|
val name: String,
|
||||||
|
) : FirmamentEvent() {
|
||||||
|
companion object : FirmamentEventBus<PartyMessageReceivedEvent>()
|
||||||
|
}
|
||||||
134
src/main/kotlin/features/chat/PartyCommands.kt
Normal file
134
src/main/kotlin/features/chat/PartyCommands.kt
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
package moe.nea.firmament.features.chat
|
||||||
|
|
||||||
|
import com.mojang.brigadier.CommandDispatcher
|
||||||
|
import com.mojang.brigadier.StringReader
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException
|
||||||
|
import com.mojang.brigadier.tree.LiteralCommandNode
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
import net.minecraft.util.math.BlockPos
|
||||||
|
import moe.nea.firmament.annotations.Subscribe
|
||||||
|
import moe.nea.firmament.commands.CaseInsensitiveLiteralCommandNode
|
||||||
|
import moe.nea.firmament.commands.thenExecute
|
||||||
|
import moe.nea.firmament.events.CommandEvent
|
||||||
|
import moe.nea.firmament.events.PartyMessageReceivedEvent
|
||||||
|
import moe.nea.firmament.events.ProcessChatEvent
|
||||||
|
import moe.nea.firmament.gui.config.ManagedConfig
|
||||||
|
import moe.nea.firmament.util.ErrorUtil
|
||||||
|
import moe.nea.firmament.util.MC
|
||||||
|
import moe.nea.firmament.util.TimeMark
|
||||||
|
import moe.nea.firmament.util.tr
|
||||||
|
import moe.nea.firmament.util.useMatch
|
||||||
|
|
||||||
|
object PartyCommands {
|
||||||
|
|
||||||
|
val messageInChannel = "(?<channel>Party|Guild) >([^:]+?)? (?<name>[^: ]+): (?<message>.+)".toPattern()
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
fun onChat(event: ProcessChatEvent) {
|
||||||
|
messageInChannel.useMatch(event.unformattedString) {
|
||||||
|
val channel = group("channel")
|
||||||
|
val message = group("message")
|
||||||
|
val name = group("name")
|
||||||
|
if (channel == "Party") {
|
||||||
|
PartyMessageReceivedEvent.publish(PartyMessageReceivedEvent(
|
||||||
|
event, message, name
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val commandPrefixes = "!-?$.&#+~€\"@°_;:³²`'´ß\\,|".toSet()
|
||||||
|
|
||||||
|
data class PartyCommandContext(
|
||||||
|
val name: String
|
||||||
|
)
|
||||||
|
|
||||||
|
val dispatch = CommandDispatcher<PartyCommandContext>().also { dispatch ->
|
||||||
|
fun register(
|
||||||
|
name: String,
|
||||||
|
vararg alias: String,
|
||||||
|
block: CaseInsensitiveLiteralCommandNode.Builder<PartyCommandContext>.() -> Unit = {},
|
||||||
|
): LiteralCommandNode<PartyCommandContext> {
|
||||||
|
val node =
|
||||||
|
dispatch.register(CaseInsensitiveLiteralCommandNode.Builder<PartyCommandContext>(name).also(block))
|
||||||
|
alias.forEach { register(it) { redirect(node) } }
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
register("warp", "pw", "pwarp", "partywarp") {
|
||||||
|
executes {
|
||||||
|
// TODO: add check if you are the party leader
|
||||||
|
MC.sendCommand("p warp")
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register("transfer", "pt", "ptme") {
|
||||||
|
executes {
|
||||||
|
MC.sendCommand("p transfer ${it.source.name}")
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register("allinvite", "allinv") {
|
||||||
|
executes {
|
||||||
|
MC.sendCommand("p settings allinvite")
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register("coords") {
|
||||||
|
executes {
|
||||||
|
val p = MC.player?.blockPos ?: BlockPos.ORIGIN
|
||||||
|
MC.sendCommand("pc x: ${p.x}, y: ${p.y}, z: ${p.z}")
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: downtime tracker (display message again at end of dungeon)
|
||||||
|
// instance ends: kuudra, dungeons, bacte
|
||||||
|
// TODO: at TPS command
|
||||||
|
}
|
||||||
|
|
||||||
|
object TConfig : ManagedConfig("party-commands", Category.CHAT) {
|
||||||
|
val enable by toggle("enable") { false }
|
||||||
|
val cooldown by duration("cooldown", 0.seconds, 20.seconds) { 2.seconds }
|
||||||
|
val ignoreOwnCommands by toggle("ignore-own") { false }
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastCommand = TimeMark.farPast()
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
fun listPartyCommands(event: CommandEvent.SubCommand) {
|
||||||
|
event.subcommand("partycommands") {
|
||||||
|
thenExecute {
|
||||||
|
// TODO: Better help, including descriptions and redirect detection
|
||||||
|
MC.sendChat(tr("firmament.partycommands.help", "Available party commands: ${dispatch.root.children.map { it.name }}. Available prefixes: $commandPrefixes"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
fun onPartyMessage(event: PartyMessageReceivedEvent) {
|
||||||
|
if (!TConfig.enable) return
|
||||||
|
if (event.message.firstOrNull() !in commandPrefixes) return
|
||||||
|
if (event.name == MC.playerName && TConfig.ignoreOwnCommands) return
|
||||||
|
if (lastCommand.passedTime() < TConfig.cooldown) {
|
||||||
|
MC.sendChat(tr("firmament.partycommands.cooldown", "Skipping party command. Cooldown not passed."))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO: add trust levels
|
||||||
|
val commandLine = event.message.substring(1)
|
||||||
|
try {
|
||||||
|
dispatch.execute(StringReader(commandLine), PartyCommandContext(event.name))
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
if (ex is CommandSyntaxException) {
|
||||||
|
MC.sendChat(tr("firmament.partycommands.unknowncommand", "Unknown party command."))
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
MC.sendChat(tr("firmament.partycommands.unknownerror", "Unknown error during command execution."))
|
||||||
|
ErrorUtil.softError("Unknown error during command execution.", ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastCommand = TimeMark.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
|
|
||||||
|
|
||||||
package moe.nea.firmament.features.chat
|
package moe.nea.firmament.features.chat
|
||||||
|
|
||||||
|
import com.mojang.brigadier.CommandDispatcher
|
||||||
import com.mojang.brigadier.context.CommandContext
|
import com.mojang.brigadier.context.CommandContext
|
||||||
|
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback
|
||||||
|
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource
|
||||||
|
import net.fabricmc.fabric.impl.command.client.ClientCommandInternals
|
||||||
|
import net.minecraft.command.CommandRegistryAccess
|
||||||
|
import net.minecraft.network.packet.s2c.play.CommandTreeS2CPacket
|
||||||
import net.minecraft.text.Text
|
import net.minecraft.text.Text
|
||||||
import moe.nea.firmament.annotations.Subscribe
|
import moe.nea.firmament.annotations.Subscribe
|
||||||
import moe.nea.firmament.commands.DefaultSource
|
import moe.nea.firmament.commands.DefaultSource
|
||||||
@@ -12,89 +16,139 @@ import moe.nea.firmament.commands.thenArgument
|
|||||||
import moe.nea.firmament.commands.thenExecute
|
import moe.nea.firmament.commands.thenExecute
|
||||||
import moe.nea.firmament.events.CommandEvent
|
import moe.nea.firmament.events.CommandEvent
|
||||||
import moe.nea.firmament.features.FirmamentFeature
|
import moe.nea.firmament.features.FirmamentFeature
|
||||||
|
import moe.nea.firmament.gui.config.ManagedConfig
|
||||||
|
import moe.nea.firmament.gui.config.ManagedOption
|
||||||
import moe.nea.firmament.util.MC
|
import moe.nea.firmament.util.MC
|
||||||
import moe.nea.firmament.util.SBData
|
import moe.nea.firmament.util.SBData
|
||||||
|
import moe.nea.firmament.util.grey
|
||||||
|
import moe.nea.firmament.util.tr
|
||||||
|
|
||||||
object QuickCommands : FirmamentFeature {
|
object QuickCommands : FirmamentFeature {
|
||||||
override val identifier: String
|
override val identifier: String
|
||||||
get() = "quick-commands"
|
get() = "quick-commands"
|
||||||
|
|
||||||
fun removePartialPrefix(text: String, prefix: String): String? {
|
object TConfig : ManagedConfig("quick-commands", Category.CHAT) {
|
||||||
var lf: String? = null
|
val enableJoin by toggle("join") { true }
|
||||||
for (i in 1..prefix.length) {
|
val enableDh by toggle("dh") { true }
|
||||||
if (text.startsWith(prefix.substring(0, i))) {
|
override fun onChange(option: ManagedOption<*>) {
|
||||||
lf = text.substring(i)
|
reloadCommands()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return lf
|
|
||||||
}
|
|
||||||
|
|
||||||
val kuudraLevelNames = listOf("NORMAL", "HOT", "BURNING", "FIERY", "INFERNAL")
|
fun reloadCommands() {
|
||||||
val dungeonLevelNames = listOf("ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN")
|
val lastPacket = lastReceivedTreePacket ?: return
|
||||||
|
val network = MC.networkHandler ?: return
|
||||||
|
val fallback = ClientCommandInternals.getActiveDispatcher()
|
||||||
|
try {
|
||||||
|
val dispatcher = CommandDispatcher<FabricClientCommandSource>()
|
||||||
|
ClientCommandInternals.setActiveDispatcher(dispatcher)
|
||||||
|
ClientCommandRegistrationCallback.EVENT.invoker()
|
||||||
|
.register(dispatcher, CommandRegistryAccess.of(network.combinedDynamicRegistries,
|
||||||
|
network.enabledFeatures))
|
||||||
|
ClientCommandInternals.finalizeInit()
|
||||||
|
network.onCommandTree(lastPacket)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
ClientCommandInternals.setActiveDispatcher(fallback)
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
fun onCommands(it: CommandEvent) {
|
|
||||||
it.register("join") {
|
|
||||||
thenArgument("what", RestArgumentType) { what ->
|
|
||||||
thenExecute {
|
|
||||||
val what = this[what]
|
|
||||||
if (!SBData.isOnSkyblock) {
|
|
||||||
MC.sendCommand("join $what")
|
|
||||||
return@thenExecute
|
|
||||||
}
|
|
||||||
val joinName = getNameForFloor(what.replace(" ", "").lowercase())
|
|
||||||
if (joinName == null) {
|
|
||||||
source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.unknown", what))
|
|
||||||
} else {
|
|
||||||
source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.success",
|
|
||||||
joinName))
|
|
||||||
MC.sendCommand("joininstance $joinName")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
thenExecute {
|
|
||||||
source.sendFeedback(Text.translatable("firmament.quick-commands.join.explain"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun CommandContext<DefaultSource>.getNameForFloor(w: String): String? {
|
fun removePartialPrefix(text: String, prefix: String): String? {
|
||||||
val kuudraLevel = removePartialPrefix(w, "kuudratier") ?: removePartialPrefix(w, "tier")
|
var lf: String? = null
|
||||||
if (kuudraLevel != null) {
|
for (i in 1..prefix.length) {
|
||||||
val l = kuudraLevel.toIntOrNull()?.let { it - 1 } ?: kuudraLevelNames.indexOfFirst {
|
if (text.startsWith(prefix.substring(0, i))) {
|
||||||
it.startsWith(
|
lf = text.substring(i)
|
||||||
kuudraLevel,
|
}
|
||||||
true
|
}
|
||||||
)
|
return lf
|
||||||
}
|
}
|
||||||
if (l !in kuudraLevelNames.indices) {
|
|
||||||
source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.unknown-kuudra",
|
var lastReceivedTreePacket: CommandTreeS2CPacket? = null
|
||||||
kuudraLevel))
|
|
||||||
return null
|
val kuudraLevelNames = listOf("NORMAL", "HOT", "BURNING", "FIERY", "INFERNAL")
|
||||||
}
|
val dungeonLevelNames = listOf("ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN")
|
||||||
return "KUUDRA_${kuudraLevelNames[l]}"
|
|
||||||
}
|
@Subscribe
|
||||||
val masterLevel = removePartialPrefix(w, "master")
|
fun registerDh(event: CommandEvent) {
|
||||||
val normalLevel =
|
if (!TConfig.enableDh) return
|
||||||
removePartialPrefix(w, "floor") ?: removePartialPrefix(w, "catacombs") ?: removePartialPrefix(w, "dungeons")
|
event.register("dh") {
|
||||||
val dungeonLevel = masterLevel ?: normalLevel
|
thenExecute {
|
||||||
if (dungeonLevel != null) {
|
MC.sendCommand("warp dhub")
|
||||||
val l = dungeonLevel.toIntOrNull()?.let { it - 1 } ?: dungeonLevelNames.indexOfFirst {
|
}
|
||||||
it.startsWith(
|
}
|
||||||
dungeonLevel,
|
event.register("dn") {
|
||||||
true
|
thenExecute {
|
||||||
)
|
MC.sendChat(tr("firmament.quickwarp.deez-nutz", "Warping to... Deez Nuts!").grey())
|
||||||
}
|
MC.sendCommand("warp dhub")
|
||||||
if (masterLevel == null && (l == -1 || null != removePartialPrefix(w, "entrance"))) {
|
}
|
||||||
return "CATACOMBS_ENTRANCE"
|
}
|
||||||
}
|
}
|
||||||
if (l !in dungeonLevelNames.indices) {
|
|
||||||
source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.unknown-catacombs",
|
@Subscribe
|
||||||
kuudraLevel))
|
fun registerJoin(it: CommandEvent) {
|
||||||
return null
|
if (!TConfig.enableJoin) return
|
||||||
}
|
it.register("join") {
|
||||||
return "${if (masterLevel != null) "MASTER_" else ""}CATACOMBS_FLOOR_${dungeonLevelNames[l]}"
|
thenArgument("what", RestArgumentType) { what ->
|
||||||
}
|
thenExecute {
|
||||||
return null
|
val what = this[what]
|
||||||
}
|
if (!SBData.isOnSkyblock) {
|
||||||
|
MC.sendCommand("join $what")
|
||||||
|
return@thenExecute
|
||||||
|
}
|
||||||
|
val joinName = getNameForFloor(what.replace(" ", "").lowercase())
|
||||||
|
if (joinName == null) {
|
||||||
|
source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.unknown", what))
|
||||||
|
} else {
|
||||||
|
source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.success",
|
||||||
|
joinName))
|
||||||
|
MC.sendCommand("joininstance $joinName")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thenExecute {
|
||||||
|
source.sendFeedback(Text.translatable("firmament.quick-commands.join.explain"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun CommandContext<DefaultSource>.getNameForFloor(w: String): String? {
|
||||||
|
val kuudraLevel = removePartialPrefix(w, "kuudratier") ?: removePartialPrefix(w, "tier")
|
||||||
|
if (kuudraLevel != null) {
|
||||||
|
val l = kuudraLevel.toIntOrNull()?.let { it - 1 } ?: kuudraLevelNames.indexOfFirst {
|
||||||
|
it.startsWith(
|
||||||
|
kuudraLevel,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (l !in kuudraLevelNames.indices) {
|
||||||
|
source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.unknown-kuudra",
|
||||||
|
kuudraLevel))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return "KUUDRA_${kuudraLevelNames[l]}"
|
||||||
|
}
|
||||||
|
val masterLevel = removePartialPrefix(w, "master")
|
||||||
|
val normalLevel =
|
||||||
|
removePartialPrefix(w, "floor") ?: removePartialPrefix(w, "catacombs") ?: removePartialPrefix(w, "dungeons")
|
||||||
|
val dungeonLevel = masterLevel ?: normalLevel
|
||||||
|
if (dungeonLevel != null) {
|
||||||
|
val l = dungeonLevel.toIntOrNull()?.let { it - 1 } ?: dungeonLevelNames.indexOfFirst {
|
||||||
|
it.startsWith(
|
||||||
|
dungeonLevel,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (masterLevel == null && (l == -1 || null != removePartialPrefix(w, "entrance"))) {
|
||||||
|
return "CATACOMBS_ENTRANCE"
|
||||||
|
}
|
||||||
|
if (l !in dungeonLevelNames.indices) {
|
||||||
|
source.sendFeedback(Text.stringifiedTranslatable("firmament.quick-commands.join.unknown-catacombs",
|
||||||
|
kuudraLevel))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return "${if (masterLevel != null) "MASTER_" else ""}CATACOMBS_FLOOR_${dungeonLevelNames[l]}"
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,71 +1,67 @@
|
|||||||
|
|
||||||
|
|
||||||
package moe.nea.firmament.features.fixes
|
package moe.nea.firmament.features.fixes
|
||||||
|
|
||||||
import moe.nea.jarvis.api.Point
|
import moe.nea.jarvis.api.Point
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable
|
||||||
import net.minecraft.client.MinecraftClient
|
import net.minecraft.client.MinecraftClient
|
||||||
import net.minecraft.client.option.KeyBinding
|
import net.minecraft.client.option.KeyBinding
|
||||||
import net.minecraft.entity.player.PlayerEntity
|
|
||||||
import net.minecraft.text.Text
|
import net.minecraft.text.Text
|
||||||
import net.minecraft.util.Arm
|
|
||||||
import moe.nea.firmament.annotations.Subscribe
|
import moe.nea.firmament.annotations.Subscribe
|
||||||
import moe.nea.firmament.events.HudRenderEvent
|
import moe.nea.firmament.events.HudRenderEvent
|
||||||
import moe.nea.firmament.events.WorldKeyboardEvent
|
import moe.nea.firmament.events.WorldKeyboardEvent
|
||||||
import moe.nea.firmament.features.FirmamentFeature
|
import moe.nea.firmament.features.FirmamentFeature
|
||||||
import moe.nea.firmament.gui.config.ManagedConfig
|
import moe.nea.firmament.gui.config.ManagedConfig
|
||||||
import moe.nea.firmament.util.MC
|
import moe.nea.firmament.util.MC
|
||||||
import moe.nea.firmament.util.errorBoundary
|
|
||||||
|
|
||||||
object Fixes : FirmamentFeature {
|
object Fixes : FirmamentFeature {
|
||||||
override val identifier: String
|
override val identifier: String
|
||||||
get() = "fixes"
|
get() = "fixes"
|
||||||
|
|
||||||
object TConfig : ManagedConfig(identifier, Category.MISC) { // TODO: split this config
|
object TConfig : ManagedConfig(identifier, Category.MISC) { // TODO: split this config
|
||||||
val fixUnsignedPlayerSkins by toggle("player-skins") { true }
|
val fixUnsignedPlayerSkins by toggle("player-skins") { true }
|
||||||
var autoSprint by toggle("auto-sprint") { false }
|
var autoSprint by toggle("auto-sprint") { false }
|
||||||
val autoSprintKeyBinding by keyBindingWithDefaultUnbound("auto-sprint-keybinding")
|
val autoSprintKeyBinding by keyBindingWithDefaultUnbound("auto-sprint-keybinding")
|
||||||
val autoSprintHud by position("auto-sprint-hud", 80, 10) { Point(0.0, 1.0) }
|
val autoSprintHud by position("auto-sprint-hud", 80, 10) { Point(0.0, 1.0) }
|
||||||
val peekChat by keyBindingWithDefaultUnbound("peek-chat")
|
val peekChat by keyBindingWithDefaultUnbound("peek-chat")
|
||||||
}
|
val hidePotionEffects by toggle("hide-mob-effects") { false }
|
||||||
|
}
|
||||||
|
|
||||||
override val config: ManagedConfig
|
override val config: ManagedConfig
|
||||||
get() = TConfig
|
get() = TConfig
|
||||||
|
|
||||||
fun handleIsPressed(
|
fun handleIsPressed(
|
||||||
keyBinding: KeyBinding,
|
keyBinding: KeyBinding,
|
||||||
cir: CallbackInfoReturnable<Boolean>
|
cir: CallbackInfoReturnable<Boolean>
|
||||||
) {
|
) {
|
||||||
if (keyBinding === MinecraftClient.getInstance().options.sprintKey && TConfig.autoSprint && MC.player?.isSprinting != true)
|
if (keyBinding === MinecraftClient.getInstance().options.sprintKey && TConfig.autoSprint && MC.player?.isSprinting != true)
|
||||||
cir.returnValue = true
|
cir.returnValue = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
fun onRenderHud(it: HudRenderEvent) {
|
fun onRenderHud(it: HudRenderEvent) {
|
||||||
if (!TConfig.autoSprintKeyBinding.isBound) return
|
if (!TConfig.autoSprintKeyBinding.isBound) return
|
||||||
it.context.matrices.push()
|
it.context.matrices.push()
|
||||||
TConfig.autoSprintHud.applyTransformations(it.context.matrices)
|
TConfig.autoSprintHud.applyTransformations(it.context.matrices)
|
||||||
it.context.drawText(
|
it.context.drawText(
|
||||||
MC.font, Text.translatable(
|
MC.font, Text.translatable(
|
||||||
if (TConfig.autoSprint)
|
if (TConfig.autoSprint)
|
||||||
"firmament.fixes.auto-sprint.on"
|
"firmament.fixes.auto-sprint.on"
|
||||||
else if (MC.player?.isSprinting == true)
|
else if (MC.player?.isSprinting == true)
|
||||||
"firmament.fixes.auto-sprint.sprinting"
|
"firmament.fixes.auto-sprint.sprinting"
|
||||||
else
|
else
|
||||||
"firmament.fixes.auto-sprint.not-sprinting"
|
"firmament.fixes.auto-sprint.not-sprinting"
|
||||||
), 0, 0, -1, false
|
), 0, 0, -1, false
|
||||||
)
|
)
|
||||||
it.context.matrices.pop()
|
it.context.matrices.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
fun onWorldKeyboard(it: WorldKeyboardEvent) {
|
fun onWorldKeyboard(it: WorldKeyboardEvent) {
|
||||||
if (it.matches(TConfig.autoSprintKeyBinding)) {
|
if (it.matches(TConfig.autoSprintKeyBinding)) {
|
||||||
TConfig.autoSprint = !TConfig.autoSprint
|
TConfig.autoSprint = !TConfig.autoSprint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shouldPeekChat(): Boolean {
|
fun shouldPeekChat(): Boolean {
|
||||||
return TConfig.peekChat.isPressed(atLeast = true)
|
return TConfig.peekChat.isPressed(atLeast = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,18 +29,7 @@ object ItemRarityCosmetics : FirmamentFeature {
|
|||||||
override val config: ManagedConfig
|
override val config: ManagedConfig
|
||||||
get() = TConfig
|
get() = TConfig
|
||||||
|
|
||||||
private val rarityToColor = mapOf(
|
private val rarityToColor = Rarity.colourMap.mapValues {
|
||||||
Rarity.COMMON to Formatting.WHITE,
|
|
||||||
Rarity.UNCOMMON to Formatting.GREEN,
|
|
||||||
Rarity.RARE to Formatting.BLUE,
|
|
||||||
Rarity.EPIC to Formatting.DARK_PURPLE,
|
|
||||||
Rarity.LEGENDARY to Formatting.GOLD,
|
|
||||||
Rarity.MYTHIC to Formatting.LIGHT_PURPLE,
|
|
||||||
Rarity.DIVINE to Formatting.AQUA,
|
|
||||||
Rarity.SPECIAL to Formatting.RED,
|
|
||||||
Rarity.VERY_SPECIAL to Formatting.RED,
|
|
||||||
Rarity.SUPREME to Formatting.DARK_RED,
|
|
||||||
).mapValues {
|
|
||||||
val c = Color(it.value.colorValue!!)
|
val c = Color(it.value.colorValue!!)
|
||||||
c.rgb
|
c.rgb
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
package moe.nea.firmament.features.inventory
|
package moe.nea.firmament.features.inventory
|
||||||
|
|
||||||
import com.mojang.blaze3d.systems.RenderSystem
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import org.lwjgl.glfw.GLFW
|
import org.lwjgl.glfw.GLFW
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -14,6 +13,7 @@ import net.minecraft.screen.GenericContainerScreenHandler
|
|||||||
import net.minecraft.screen.slot.Slot
|
import net.minecraft.screen.slot.Slot
|
||||||
import net.minecraft.screen.slot.SlotActionType
|
import net.minecraft.screen.slot.SlotActionType
|
||||||
import net.minecraft.util.Identifier
|
import net.minecraft.util.Identifier
|
||||||
|
import net.minecraft.util.StringIdentifiable
|
||||||
import moe.nea.firmament.annotations.Subscribe
|
import moe.nea.firmament.annotations.Subscribe
|
||||||
import moe.nea.firmament.events.HandledScreenForegroundEvent
|
import moe.nea.firmament.events.HandledScreenForegroundEvent
|
||||||
import moe.nea.firmament.events.HandledScreenKeyPressedEvent
|
import moe.nea.firmament.events.HandledScreenKeyPressedEvent
|
||||||
@@ -59,6 +59,17 @@ object SlotLocking : FirmamentFeature {
|
|||||||
}
|
}
|
||||||
val slotBind by keyBinding("bind") { GLFW.GLFW_KEY_L }
|
val slotBind by keyBinding("bind") { GLFW.GLFW_KEY_L }
|
||||||
val slotBindRequireShift by toggle("require-quick-move") { true }
|
val slotBindRequireShift by toggle("require-quick-move") { true }
|
||||||
|
val slotRenderLines by choice("bind-render") { SlotRenderLinesMode.ONLY_BOXES }
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class SlotRenderLinesMode : StringIdentifiable {
|
||||||
|
EVERYTHING,
|
||||||
|
ONLY_BOXES,
|
||||||
|
NOTHING;
|
||||||
|
|
||||||
|
override fun asString(): String {
|
||||||
|
return name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val config: TConfig
|
override val config: TConfig
|
||||||
@@ -95,7 +106,7 @@ object SlotLocking : FirmamentFeature {
|
|||||||
if (handler.inventory.size() < 9) return false
|
if (handler.inventory.size() < 9) return false
|
||||||
val sellItem = handler.inventory.getStack(handler.inventory.size() - 5)
|
val sellItem = handler.inventory.getStack(handler.inventory.size() - 5)
|
||||||
if (sellItem == null) return false
|
if (sellItem == null) return false
|
||||||
if (sellItem.displayNameAccordingToNbt?.unformattedString == "Sell Item") return true
|
if (sellItem.displayNameAccordingToNbt.unformattedString == "Sell Item") return true
|
||||||
val lore = sellItem.loreAccordingToNbt
|
val lore = sellItem.loreAccordingToNbt
|
||||||
return (lore.lastOrNull() ?: return false).unformattedString == "Click to buyback!"
|
return (lore.lastOrNull() ?: return false).unformattedString == "Click to buyback!"
|
||||||
}
|
}
|
||||||
@@ -104,7 +115,7 @@ object SlotLocking : FirmamentFeature {
|
|||||||
fun onSalvageProtect(event: IsSlotProtectedEvent) {
|
fun onSalvageProtect(event: IsSlotProtectedEvent) {
|
||||||
if (event.slot == null) return
|
if (event.slot == null) return
|
||||||
if (!event.slot.hasStack()) return
|
if (!event.slot.hasStack()) return
|
||||||
if (event.slot.stack.displayNameAccordingToNbt?.unformattedString != "Salvage Items") return
|
if (event.slot.stack.displayNameAccordingToNbt.unformattedString != "Salvage Items") return
|
||||||
val inv = event.slot.inventory
|
val inv = event.slot.inventory
|
||||||
var anyBlocked = false
|
var anyBlocked = false
|
||||||
for (i in 0 until event.slot.index) {
|
for (i in 0 until event.slot.index) {
|
||||||
@@ -227,23 +238,32 @@ object SlotLocking : FirmamentFeature {
|
|||||||
val accScreen = event.screen as AccessorHandledScreen
|
val accScreen = event.screen as AccessorHandledScreen
|
||||||
val sx = accScreen.x_Firmament
|
val sx = accScreen.x_Firmament
|
||||||
val sy = accScreen.y_Firmament
|
val sy = accScreen.y_Firmament
|
||||||
boundSlots.entries.forEach {
|
for (it in boundSlots.entries) {
|
||||||
val hotbarSlot = findByIndex(it.key) ?: return@forEach
|
val hotbarSlot = findByIndex(it.key) ?: continue
|
||||||
val inventorySlot = findByIndex(it.value) ?: return@forEach
|
val inventorySlot = findByIndex(it.value) ?: continue
|
||||||
|
|
||||||
val (hotX, hotY) = hotbarSlot.lineCenter()
|
val (hotX, hotY) = hotbarSlot.lineCenter()
|
||||||
val (invX, invY) = inventorySlot.lineCenter()
|
val (invX, invY) = inventorySlot.lineCenter()
|
||||||
event.context.drawLine(
|
val anyHovered = accScreen.focusedSlot_Firmament === hotbarSlot
|
||||||
invX + sx, invY + sy,
|
|| accScreen.focusedSlot_Firmament === inventorySlot
|
||||||
hotX + sx, hotY + sy,
|
if (!anyHovered && TConfig.slotRenderLines == SlotRenderLinesMode.NOTHING)
|
||||||
|
continue
|
||||||
|
val color = if (anyHovered)
|
||||||
me.shedaniel.math.Color.ofOpaque(0x00FF00)
|
me.shedaniel.math.Color.ofOpaque(0x00FF00)
|
||||||
)
|
else
|
||||||
|
me.shedaniel.math.Color.ofTransparent(0xc0a0f000.toInt())
|
||||||
|
if (TConfig.slotRenderLines == SlotRenderLinesMode.EVERYTHING || anyHovered)
|
||||||
|
event.context.drawLine(
|
||||||
|
invX + sx, invY + sy,
|
||||||
|
hotX + sx, hotY + sy,
|
||||||
|
color
|
||||||
|
)
|
||||||
event.context.drawBorder(hotbarSlot.x + sx,
|
event.context.drawBorder(hotbarSlot.x + sx,
|
||||||
hotbarSlot.y + sy,
|
hotbarSlot.y + sy,
|
||||||
16, 16, 0xFF00FF00u.toInt())
|
16, 16, color.color)
|
||||||
event.context.drawBorder(inventorySlot.x + sx,
|
event.context.drawBorder(inventorySlot.x + sx,
|
||||||
inventorySlot.y + sy,
|
inventorySlot.y + sy,
|
||||||
16, 16, 0xFF00FF00u.toInt())
|
16, 16, color.color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
130
src/main/kotlin/features/inventory/TimerInLore.kt
Normal file
130
src/main/kotlin/features/inventory/TimerInLore.kt
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
package moe.nea.firmament.features.inventory
|
||||||
|
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.time.format.DateTimeFormatterBuilder
|
||||||
|
import java.time.format.FormatStyle
|
||||||
|
import java.time.format.TextStyle
|
||||||
|
import java.time.temporal.ChronoField
|
||||||
|
import net.minecraft.text.Text
|
||||||
|
import net.minecraft.util.StringIdentifiable
|
||||||
|
import moe.nea.firmament.annotations.Subscribe
|
||||||
|
import moe.nea.firmament.events.ItemTooltipEvent
|
||||||
|
import moe.nea.firmament.gui.config.ManagedConfig
|
||||||
|
import moe.nea.firmament.util.SBData
|
||||||
|
import moe.nea.firmament.util.aqua
|
||||||
|
import moe.nea.firmament.util.grey
|
||||||
|
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
|
||||||
|
import moe.nea.firmament.util.tr
|
||||||
|
import moe.nea.firmament.util.unformattedString
|
||||||
|
|
||||||
|
object TimerInLore {
|
||||||
|
object TConfig : ManagedConfig("lore-timers", Category.INVENTORY) {
|
||||||
|
val showTimers by toggle("show") { true }
|
||||||
|
val timerFormat by choice("format") { TimerFormat.SOCIALIST }
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class TimerFormat(val formatter: DateTimeFormatter) : StringIdentifiable {
|
||||||
|
RFC(DateTimeFormatter.RFC_1123_DATE_TIME),
|
||||||
|
LOCAL(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)),
|
||||||
|
SOCIALIST(
|
||||||
|
{
|
||||||
|
appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
|
||||||
|
appendLiteral(" ")
|
||||||
|
appendValue(ChronoField.DAY_OF_MONTH, 2)
|
||||||
|
appendLiteral(".")
|
||||||
|
appendValue(ChronoField.MONTH_OF_YEAR, 2)
|
||||||
|
appendLiteral(".")
|
||||||
|
appendValue(ChronoField.YEAR, 4)
|
||||||
|
appendLiteral(" ")
|
||||||
|
appendValue(ChronoField.HOUR_OF_DAY, 2)
|
||||||
|
appendLiteral(":")
|
||||||
|
appendValue(ChronoField.MINUTE_OF_HOUR, 2)
|
||||||
|
appendLiteral(":")
|
||||||
|
appendValue(ChronoField.SECOND_OF_MINUTE, 2)
|
||||||
|
}),
|
||||||
|
AMERICAN("EEEE, MMM d h:mm a yyyy"),
|
||||||
|
;
|
||||||
|
|
||||||
|
constructor(block: DateTimeFormatterBuilder.() -> Unit)
|
||||||
|
: this(DateTimeFormatterBuilder().also(block).toFormatter())
|
||||||
|
|
||||||
|
constructor(format: String) : this(DateTimeFormatter.ofPattern(format))
|
||||||
|
|
||||||
|
override fun asString(): String {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class CountdownTypes(
|
||||||
|
val match: String,
|
||||||
|
val label: String, // TODO: convert to a string
|
||||||
|
val isRelative: Boolean = false,
|
||||||
|
) {
|
||||||
|
STARTING("Starting in:", "Starts at"),
|
||||||
|
STARTS("Starts in:", "Starts at"),
|
||||||
|
INTEREST("Interest in:", "Interest at"),
|
||||||
|
UNTILINTEREST("Until interest:", "Interest at"),
|
||||||
|
ENDS("Ends in:", "Ends at"),
|
||||||
|
REMAINING("Remaining:", "Ends at"),
|
||||||
|
DURATION("Duration:", "Finishes at"),
|
||||||
|
TIMELEFT("Time left:", "Ends at"),
|
||||||
|
EVENTTIMELEFT("Event lasts for", "Ends at", isRelative = true),
|
||||||
|
SHENSUCKS("Auction ends in:", "Auction ends at"),
|
||||||
|
ENDS_PET_LEVELING(
|
||||||
|
"Ends:",
|
||||||
|
"Finishes at"
|
||||||
|
),
|
||||||
|
CALENDARDETAILS(" (§e", "Starts at"),
|
||||||
|
COMMUNITYPROJECTS("Contribute again", "Come back at"),
|
||||||
|
CHOCOLATEFACTORY("Next Charge", "Available at"),
|
||||||
|
STONKSAUCTION("Auction ends in", "Ends at"),
|
||||||
|
LIZSTONKREDEMPTION("Resets in:", "Resets at");
|
||||||
|
}
|
||||||
|
|
||||||
|
val regex =
|
||||||
|
"(?i)(?:(?<years>[0-9]+) ?(y|years?) )?(?:(?<days>[0-9]+) ?(d|days?))? ?(?:(?<hours>[0-9]+) ?(h|hours?))? ?(?:(?<minutes>[0-9]+) ?(m|minutes?))? ?(?:(?<seconds>[0-9]+) ?(s|seconds?))?\\b".toRegex()
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
fun modifyLore(event: ItemTooltipEvent) {
|
||||||
|
if (!TConfig.showTimers) return
|
||||||
|
var lastTimer: ZonedDateTime? = null
|
||||||
|
for (i in event.lines.indices) {
|
||||||
|
val line = event.lines[i].unformattedString
|
||||||
|
val countdownType = CountdownTypes.entries.find { it.match in line } ?: continue
|
||||||
|
if (countdownType == CountdownTypes.CALENDARDETAILS
|
||||||
|
&& !event.stack.displayNameAccordingToNbt.unformattedString.startsWith("Day ")
|
||||||
|
) continue
|
||||||
|
|
||||||
|
val countdownMatch = regex.findAll(line).filter { it.value.isNotBlank() }.lastOrNull() ?: continue
|
||||||
|
val (years, days, hours, minutes, seconds) =
|
||||||
|
listOf("years", "days", "hours", "minutes", "seconds")
|
||||||
|
.map {
|
||||||
|
countdownMatch.groups[it]?.value?.toLong() ?: 0L
|
||||||
|
}
|
||||||
|
if (years + days + hours + minutes + seconds == 0L) continue
|
||||||
|
var baseLine = ZonedDateTime.now(SBData.hypixelTimeZone)
|
||||||
|
if (countdownType.isRelative) {
|
||||||
|
if (lastTimer == null) {
|
||||||
|
event.lines.add(i + 1,
|
||||||
|
tr("firmament.loretimer.missingrelative",
|
||||||
|
"Found a relative countdown with no baseline (Firmament)").grey())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
baseLine = lastTimer
|
||||||
|
}
|
||||||
|
val timer =
|
||||||
|
baseLine.plusYears(years).plusDays(days).plusHours(hours).plusMinutes(minutes).plusSeconds(seconds)
|
||||||
|
lastTimer = timer
|
||||||
|
val localTimer = timer.withZoneSameInstant(ZoneId.systemDefault())
|
||||||
|
// TODO: install approximate time stabilization algorithm
|
||||||
|
event.lines.add(i + 1,
|
||||||
|
Text.literal("${countdownType.label}: ")
|
||||||
|
.grey()
|
||||||
|
.append(Text.literal(TConfig.timerFormat.formatter.format(localTimer)).aqua())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
package moe.nea.firmament.features.inventory.storageoverlay
|
package moe.nea.firmament.features.inventory.storageoverlay
|
||||||
|
|
||||||
import io.ktor.util.decodeBase64Bytes
|
import io.ktor.util.decodeBase64Bytes
|
||||||
@@ -19,47 +17,59 @@ import net.minecraft.nbt.NbtIo
|
|||||||
import net.minecraft.nbt.NbtList
|
import net.minecraft.nbt.NbtList
|
||||||
import net.minecraft.nbt.NbtOps
|
import net.minecraft.nbt.NbtOps
|
||||||
import net.minecraft.nbt.NbtSizeTracker
|
import net.minecraft.nbt.NbtSizeTracker
|
||||||
|
import net.minecraft.registry.RegistryOps
|
||||||
|
import moe.nea.firmament.util.ErrorUtil
|
||||||
|
import moe.nea.firmament.util.MC
|
||||||
|
import moe.nea.firmament.util.mc.TolerantRegistriesOps
|
||||||
|
|
||||||
@Serializable(with = VirtualInventory.Serializer::class)
|
@Serializable(with = VirtualInventory.Serializer::class)
|
||||||
data class VirtualInventory(
|
data class VirtualInventory(
|
||||||
val stacks: List<ItemStack>
|
val stacks: List<ItemStack>
|
||||||
) {
|
) {
|
||||||
val rows = stacks.size / 9
|
val rows = stacks.size / 9
|
||||||
|
|
||||||
init {
|
init {
|
||||||
assert(stacks.size % 9 == 0)
|
assert(stacks.size % 9 == 0)
|
||||||
assert(stacks.size / 9 in 1..5)
|
assert(stacks.size / 9 in 1..5)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
object Serializer : KSerializer<VirtualInventory> {
|
object Serializer : KSerializer<VirtualInventory> {
|
||||||
const val INVENTORY = "INVENTORY"
|
const val INVENTORY = "INVENTORY"
|
||||||
override val descriptor: SerialDescriptor
|
override val descriptor: SerialDescriptor
|
||||||
get() = PrimitiveSerialDescriptor("VirtualInventory", PrimitiveKind.STRING)
|
get() = PrimitiveSerialDescriptor("VirtualInventory", PrimitiveKind.STRING)
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): VirtualInventory {
|
override fun deserialize(decoder: Decoder): VirtualInventory {
|
||||||
val s = decoder.decodeString()
|
val s = decoder.decodeString()
|
||||||
val n = NbtIo.readCompressed(ByteArrayInputStream(s.decodeBase64Bytes()), NbtSizeTracker.of(100_000_000))
|
val n = NbtIo.readCompressed(ByteArrayInputStream(s.decodeBase64Bytes()), NbtSizeTracker.of(100_000_000))
|
||||||
val items = n.getList(INVENTORY, NbtCompound.COMPOUND_TYPE.toInt())
|
val items = n.getList(INVENTORY, NbtCompound.COMPOUND_TYPE.toInt())
|
||||||
return VirtualInventory(items.map {
|
val ops = getOps()
|
||||||
it as NbtCompound
|
return VirtualInventory(items.map {
|
||||||
if (it.isEmpty) ItemStack.EMPTY
|
it as NbtCompound
|
||||||
else runCatching {
|
if (it.isEmpty) ItemStack.EMPTY
|
||||||
ItemStack.CODEC.parse(NbtOps.INSTANCE, it).orThrow
|
else ErrorUtil.catch("Could not deserialize item") {
|
||||||
}.getOrElse { ItemStack.EMPTY }
|
ItemStack.CODEC.parse(ops, it).orThrow
|
||||||
})
|
}.or { ItemStack.EMPTY }
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: VirtualInventory) {
|
fun getOps() = TolerantRegistriesOps(NbtOps.INSTANCE, MC.currentOrDefaultRegistries)
|
||||||
val list = NbtList()
|
|
||||||
value.stacks.forEach {
|
override fun serialize(encoder: Encoder, value: VirtualInventory) {
|
||||||
if (it.isEmpty) list.add(NbtCompound())
|
val list = NbtList()
|
||||||
else list.add(runCatching { ItemStack.CODEC.encode(it, NbtOps.INSTANCE, NbtCompound()).orThrow }
|
val ops = getOps()
|
||||||
.getOrElse { NbtCompound() })
|
value.stacks.forEach {
|
||||||
}
|
if (it.isEmpty) list.add(NbtCompound())
|
||||||
val baos = ByteArrayOutputStream()
|
else list.add(ErrorUtil.catch("Could not serialize item") {
|
||||||
NbtIo.writeCompressed(NbtCompound().also { it.put(INVENTORY, list) }, baos)
|
ItemStack.CODEC.encode(it,
|
||||||
encoder.encodeString(baos.toByteArray().encodeBase64())
|
ops,
|
||||||
}
|
NbtCompound()).orThrow
|
||||||
}
|
}
|
||||||
|
.or { NbtCompound() })
|
||||||
|
}
|
||||||
|
val baos = ByteArrayOutputStream()
|
||||||
|
NbtIo.writeCompressed(NbtCompound().also { it.put(INVENTORY, list) }, baos)
|
||||||
|
encoder.encodeString(baos.toByteArray().encodeBase64())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ object PickaxeAbility : FirmamentFeature {
|
|||||||
val drillFuelBar by toggle("fuel-bar") { true }
|
val drillFuelBar by toggle("fuel-bar") { true }
|
||||||
val blockOnPrivateIsland by choice(
|
val blockOnPrivateIsland by choice(
|
||||||
"block-on-dynamic",
|
"block-on-dynamic",
|
||||||
BlockPickaxeAbility.entries,
|
|
||||||
) {
|
) {
|
||||||
BlockPickaxeAbility.ONLY_DESTRUCTIVE
|
BlockPickaxeAbility.ONLY_DESTRUCTIVE
|
||||||
}
|
}
|
||||||
@@ -99,6 +98,7 @@ object PickaxeAbility : FirmamentFeature {
|
|||||||
@Subscribe
|
@Subscribe
|
||||||
fun onPickaxeRightClick(event: UseItemEvent) {
|
fun onPickaxeRightClick(event: UseItemEvent) {
|
||||||
if (TConfig.blockOnPrivateIsland == BlockPickaxeAbility.NEVER) return
|
if (TConfig.blockOnPrivateIsland == BlockPickaxeAbility.NEVER) return
|
||||||
|
if (SBData.skyblockLocation != SkyBlockIsland.PRIVATE_ISLAND && SBData.skyblockLocation != SkyBlockIsland.GARDEN) return
|
||||||
val itemType = ItemType.fromItemStack(event.item)
|
val itemType = ItemType.fromItemStack(event.item)
|
||||||
if (itemType !in pickaxeTypes) return
|
if (itemType !in pickaxeTypes) return
|
||||||
val ability = AbilityUtils.getAbilities(event.item)
|
val ability = AbilityUtils.getAbilities(event.item)
|
||||||
|
|||||||
124
src/main/kotlin/features/misc/TimerFeature.kt
Normal file
124
src/main/kotlin/features/misc/TimerFeature.kt
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package moe.nea.firmament.features.misc
|
||||||
|
|
||||||
|
import com.mojang.brigadier.arguments.IntegerArgumentType
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
import moe.nea.firmament.Firmament
|
||||||
|
import moe.nea.firmament.annotations.Subscribe
|
||||||
|
import moe.nea.firmament.commands.DurationArgumentType
|
||||||
|
import moe.nea.firmament.commands.RestArgumentType
|
||||||
|
import moe.nea.firmament.commands.get
|
||||||
|
import moe.nea.firmament.commands.thenArgument
|
||||||
|
import moe.nea.firmament.commands.thenExecute
|
||||||
|
import moe.nea.firmament.events.CommandEvent
|
||||||
|
import moe.nea.firmament.events.TickEvent
|
||||||
|
import moe.nea.firmament.util.CommonSoundEffects
|
||||||
|
import moe.nea.firmament.util.FirmFormatters
|
||||||
|
import moe.nea.firmament.util.MC
|
||||||
|
import moe.nea.firmament.util.MinecraftDispatcher
|
||||||
|
import moe.nea.firmament.util.TimeMark
|
||||||
|
import moe.nea.firmament.util.clickCommand
|
||||||
|
import moe.nea.firmament.util.lime
|
||||||
|
import moe.nea.firmament.util.red
|
||||||
|
import moe.nea.firmament.util.tr
|
||||||
|
import moe.nea.firmament.util.yellow
|
||||||
|
|
||||||
|
object TimerFeature {
|
||||||
|
data class Timer(
|
||||||
|
val start: TimeMark,
|
||||||
|
val duration: Duration,
|
||||||
|
val message: String,
|
||||||
|
val timerId: Int,
|
||||||
|
) {
|
||||||
|
fun timeLeft() = (duration - start.passedTime()).coerceAtLeast(0.seconds)
|
||||||
|
fun isDone() = start.passedTime() >= duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Theoretically for optimal performance this could be a treeset keyed to the end time
|
||||||
|
val timers = mutableListOf<Timer>()
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
fun tick(event: TickEvent) {
|
||||||
|
timers.removeAll {
|
||||||
|
if (it.isDone()) {
|
||||||
|
MC.sendChat(tr("firmament.timer.finished",
|
||||||
|
"The timer you set ${FirmFormatters.formatTimespan(it.duration)} ago just went off: ${it.message}")
|
||||||
|
.yellow())
|
||||||
|
Firmament.coroutineScope.launch {
|
||||||
|
withContext(MinecraftDispatcher) {
|
||||||
|
repeat(5) {
|
||||||
|
CommonSoundEffects.playSuccess()
|
||||||
|
delay(0.2.seconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startTimer(duration: Duration, message: String) {
|
||||||
|
val timerId = createTimerId++
|
||||||
|
timers.add(Timer(TimeMark.now(), duration, message, timerId))
|
||||||
|
MC.sendChat(
|
||||||
|
tr("firmament.timer.start",
|
||||||
|
"Timer started for $message in ${FirmFormatters.formatTimespan(duration)}.").lime()
|
||||||
|
.append(" ")
|
||||||
|
.append(
|
||||||
|
tr("firmament.timer.cancelbutton",
|
||||||
|
"Click here to cancel the timer."
|
||||||
|
).clickCommand("/firm timer clear $timerId").red()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearTimer(timerId: Int) {
|
||||||
|
val timer = timers.indexOfFirst { it.timerId == timerId }
|
||||||
|
if (timer < 0) {
|
||||||
|
MC.sendChat(tr("firmament.timer.cancel.fail",
|
||||||
|
"Could not cancel that timer. Maybe it was already cancelled?").red())
|
||||||
|
} else {
|
||||||
|
val timerData = timers[timer]
|
||||||
|
timers.removeAt(timer)
|
||||||
|
MC.sendChat(tr("firmament.timer.cancel.done",
|
||||||
|
"Cancelled timer ${timerData.message}. It would have been done in ${
|
||||||
|
FirmFormatters.formatTimespan(timerData.timeLeft())
|
||||||
|
}.").lime())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var createTimerId = 0
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
fun onCommands(event: CommandEvent.SubCommand) {
|
||||||
|
event.subcommand("cleartimer") {
|
||||||
|
thenArgument("timerId", IntegerArgumentType.integer(0)) { timerId ->
|
||||||
|
thenExecute {
|
||||||
|
clearTimer(this[timerId])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thenExecute {
|
||||||
|
timers.map { it.timerId }.forEach {
|
||||||
|
clearTimer(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
event.subcommand("timer") {
|
||||||
|
thenArgument("time", DurationArgumentType) { duration ->
|
||||||
|
thenExecute {
|
||||||
|
startTimer(this[duration], "no message")
|
||||||
|
}
|
||||||
|
thenArgument("message", RestArgumentType) { message ->
|
||||||
|
thenExecute {
|
||||||
|
startTimer(this[duration], this[message])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import moe.nea.firmament.util.ErrorUtil
|
|||||||
import moe.nea.firmament.util.json.KJsonOps
|
import moe.nea.firmament.util.json.KJsonOps
|
||||||
|
|
||||||
class ChoiceHandler<E>(
|
class ChoiceHandler<E>(
|
||||||
|
val enumClass: Class<E>,
|
||||||
val universe: List<E>,
|
val universe: List<E>,
|
||||||
) : ManagedConfig.OptionHandler<E> where E : Enum<E>, E : StringIdentifiable {
|
) : ManagedConfig.OptionHandler<E> where E : Enum<E>, E : StringIdentifiable {
|
||||||
val codec = StringIdentifiable.createCodec {
|
val codec = StringIdentifiable.createCodec {
|
||||||
|
|||||||
@@ -117,13 +117,24 @@ abstract class ManagedConfig(
|
|||||||
|
|
||||||
protected fun <E> choice(
|
protected fun <E> choice(
|
||||||
propertyName: String,
|
propertyName: String,
|
||||||
universe: List<E>,
|
enumClass: Class<E>,
|
||||||
default: () -> E
|
default: () -> E
|
||||||
): ManagedOption<E> where E : Enum<E>, E : StringIdentifiable {
|
): ManagedOption<E> where E : Enum<E>, E : StringIdentifiable {
|
||||||
return option(propertyName, default, ChoiceHandler(universe))
|
return option(propertyName, default, ChoiceHandler(enumClass, enumClass.enumConstants.toList()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: wait on https://youtrack.jetbrains.com/issue/KT-73434
|
protected inline fun <reified E> choice(
|
||||||
|
propertyName: String,
|
||||||
|
noinline default: () -> E
|
||||||
|
): ManagedOption<E> where E : Enum<E>, E : StringIdentifiable {
|
||||||
|
return choice(propertyName, E::class.java, default)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <E> createStringIdentifiable(x: () -> Array<out E>): Codec<E> where E : Enum<E>, E : StringIdentifiable {
|
||||||
|
return StringIdentifiable.createCodec { x() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: wait on https://youtrack.jetbrains.com/issue/KT-73434
|
||||||
// protected inline fun <reified E> choice(
|
// protected inline fun <reified E> choice(
|
||||||
// propertyName: String,
|
// propertyName: String,
|
||||||
// noinline default: () -> E
|
// noinline default: () -> E
|
||||||
@@ -136,6 +147,8 @@ abstract class ManagedConfig(
|
|||||||
// default
|
// default
|
||||||
// )
|
// )
|
||||||
// }
|
// }
|
||||||
|
open fun onChange(option: ManagedOption<*>) {
|
||||||
|
}
|
||||||
|
|
||||||
protected fun duration(
|
protected fun duration(
|
||||||
propertyName: String,
|
propertyName: String,
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import kotlinx.serialization.json.JsonObject
|
|||||||
import kotlin.properties.ReadWriteProperty
|
import kotlin.properties.ReadWriteProperty
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
import net.minecraft.text.Text
|
import net.minecraft.text.Text
|
||||||
import moe.nea.firmament.Firmament
|
|
||||||
import moe.nea.firmament.util.ErrorUtil
|
import moe.nea.firmament.util.ErrorUtil
|
||||||
|
|
||||||
class ManagedOption<T : Any>(
|
class ManagedOption<T : Any>(
|
||||||
@@ -28,7 +27,13 @@ class ManagedOption<T : Any>(
|
|||||||
val descriptionTranslationKey = "firmament.config.${element.name}.${propertyName}.description"
|
val descriptionTranslationKey = "firmament.config.${element.name}.${propertyName}.description"
|
||||||
val labelDescription: Text = Text.translatable(descriptionTranslationKey)
|
val labelDescription: Text = Text.translatable(descriptionTranslationKey)
|
||||||
|
|
||||||
lateinit var value: T
|
private var actualValue: T? = null
|
||||||
|
var value: T
|
||||||
|
get() = actualValue ?: error("Lateinit variable not initialized")
|
||||||
|
set(value) {
|
||||||
|
actualValue = value
|
||||||
|
element.onChange(this)
|
||||||
|
}
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||||
this.value = value
|
this.value = value
|
||||||
|
|||||||
@@ -111,8 +111,9 @@ object EntityRenderer {
|
|||||||
renderContext: DrawContext,
|
renderContext: DrawContext,
|
||||||
posX: Int,
|
posX: Int,
|
||||||
posY: Int,
|
posY: Int,
|
||||||
mouseX: Float,
|
// TODO: Add width, height properties here
|
||||||
mouseY: Float
|
mouseX: Double,
|
||||||
|
mouseY: Double
|
||||||
) {
|
) {
|
||||||
var bottomOffset = 0.0F
|
var bottomOffset = 0.0F
|
||||||
var currentEntity = entity
|
var currentEntity = entity
|
||||||
@@ -148,15 +149,15 @@ object EntityRenderer {
|
|||||||
y2: Int,
|
y2: Int,
|
||||||
size: Float,
|
size: Float,
|
||||||
bottomOffset: Float,
|
bottomOffset: Float,
|
||||||
mouseX: Float,
|
mouseX: Double,
|
||||||
mouseY: Float,
|
mouseY: Double,
|
||||||
entity: LivingEntity
|
entity: LivingEntity
|
||||||
) {
|
) {
|
||||||
context.enableScissorWithTranslation(x1.toFloat(), y1.toFloat(), x2.toFloat(), y2.toFloat())
|
context.enableScissorWithTranslation(x1.toFloat(), y1.toFloat(), x2.toFloat(), y2.toFloat())
|
||||||
val centerX = (x1 + x2) / 2f
|
val centerX = (x1 + x2) / 2f
|
||||||
val centerY = (y1 + y2) / 2f
|
val centerY = (y1 + y2) / 2f
|
||||||
val targetYaw = atan(((centerX - mouseX) / 40.0f).toDouble()).toFloat()
|
val targetYaw = atan(((centerX - mouseX) / 40.0f)).toFloat()
|
||||||
val targetPitch = atan(((centerY - mouseY) / 40.0f).toDouble()).toFloat()
|
val targetPitch = atan(((centerY - mouseY) / 40.0f)).toFloat()
|
||||||
val rotateToFaceTheFront = Quaternionf().rotateZ(Math.PI.toFloat())
|
val rotateToFaceTheFront = Quaternionf().rotateZ(Math.PI.toFloat())
|
||||||
val rotateToFaceTheCamera = Quaternionf().rotateX(targetPitch * 20.0f * (Math.PI.toFloat() / 180))
|
val rotateToFaceTheCamera = Quaternionf().rotateX(targetPitch * 20.0f * (Math.PI.toFloat() / 180))
|
||||||
rotateToFaceTheFront.mul(rotateToFaceTheCamera)
|
rotateToFaceTheFront.mul(rotateToFaceTheCamera)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
package moe.nea.firmament.repo
|
package moe.nea.firmament.repo
|
||||||
|
|
||||||
import io.github.moulberry.repo.IReloadable
|
import io.github.moulberry.repo.IReloadable
|
||||||
@@ -6,23 +5,22 @@ import io.github.moulberry.repo.NEURepository
|
|||||||
import io.github.moulberry.repo.data.NEURecipe
|
import io.github.moulberry.repo.data.NEURecipe
|
||||||
import moe.nea.firmament.util.SkyblockId
|
import moe.nea.firmament.util.SkyblockId
|
||||||
|
|
||||||
class BetterRepoRecipeCache(val essenceRecipeProvider: EssenceRecipeProvider) : IReloadable {
|
class BetterRepoRecipeCache(vararg val extraProviders: ExtraRecipeProvider) : IReloadable {
|
||||||
var usages: Map<SkyblockId, Set<NEURecipe>> = mapOf()
|
var usages: Map<SkyblockId, Set<NEURecipe>> = mapOf()
|
||||||
var recipes: Map<SkyblockId, Set<NEURecipe>> = mapOf()
|
var recipes: Map<SkyblockId, Set<NEURecipe>> = mapOf()
|
||||||
|
|
||||||
override fun reload(repository: NEURepository) {
|
override fun reload(repository: NEURepository) {
|
||||||
val usages = mutableMapOf<SkyblockId, MutableSet<NEURecipe>>()
|
val usages = mutableMapOf<SkyblockId, MutableSet<NEURecipe>>()
|
||||||
val recipes = mutableMapOf<SkyblockId, MutableSet<NEURecipe>>()
|
val recipes = mutableMapOf<SkyblockId, MutableSet<NEURecipe>>()
|
||||||
val baseRecipes = repository.items.items.values
|
val baseRecipes = repository.items.items.values
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.flatMap { it.recipes }
|
.flatMap { it.recipes }
|
||||||
val extraRecipes = essenceRecipeProvider.recipes
|
(baseRecipes + extraProviders.flatMap { it.provideExtraRecipes() })
|
||||||
(baseRecipes + extraRecipes)
|
.forEach { recipe ->
|
||||||
.forEach { recipe ->
|
recipe.allInputs.forEach { usages.getOrPut(SkyblockId(it.itemId), ::mutableSetOf).add(recipe) }
|
||||||
recipe.allInputs.forEach { usages.getOrPut(SkyblockId(it.itemId), ::mutableSetOf).add(recipe) }
|
recipe.allOutputs.forEach { recipes.getOrPut(SkyblockId(it.itemId), ::mutableSetOf).add(recipe) }
|
||||||
recipe.allOutputs.forEach { recipes.getOrPut(SkyblockId(it.itemId), ::mutableSetOf).add(recipe) }
|
}
|
||||||
}
|
this.usages = usages
|
||||||
this.usages = usages
|
this.recipes = recipes
|
||||||
this.recipes = recipes
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
package moe.nea.firmament.repo
|
package moe.nea.firmament.repo
|
||||||
|
|
||||||
import io.github.moulberry.repo.IReloadable
|
import io.github.moulberry.repo.IReloadable
|
||||||
@@ -7,44 +6,46 @@ import io.github.moulberry.repo.data.NEUIngredient
|
|||||||
import io.github.moulberry.repo.data.NEURecipe
|
import io.github.moulberry.repo.data.NEURecipe
|
||||||
import moe.nea.firmament.util.SkyblockId
|
import moe.nea.firmament.util.SkyblockId
|
||||||
|
|
||||||
class EssenceRecipeProvider : IReloadable {
|
class EssenceRecipeProvider : IReloadable, ExtraRecipeProvider {
|
||||||
data class EssenceUpgradeRecipe(
|
data class EssenceUpgradeRecipe(
|
||||||
val itemId: SkyblockId,
|
val itemId: SkyblockId,
|
||||||
val starCountAfter: Int,
|
val starCountAfter: Int,
|
||||||
val essenceCost: Int,
|
val essenceCost: Int,
|
||||||
val essenceType: String, // TODO: replace with proper type
|
val essenceType: String, // TODO: replace with proper type
|
||||||
val extraItems: List<NEUIngredient>,
|
val extraItems: List<NEUIngredient>,
|
||||||
) : NEURecipe {
|
) : NEURecipe {
|
||||||
val essenceIngredient= NEUIngredient.fromString("${essenceType}:$essenceCost")
|
val essenceIngredient = NEUIngredient.fromString("${essenceType}:$essenceCost")
|
||||||
val allUpgradeComponents = listOf(essenceIngredient) + extraItems
|
val allUpgradeComponents = listOf(essenceIngredient) + extraItems
|
||||||
|
|
||||||
override fun getAllInputs(): Collection<NEUIngredient> {
|
override fun getAllInputs(): Collection<NEUIngredient> {
|
||||||
return listOf(NEUIngredient.fromString(itemId.neuItem + ":1")) + allUpgradeComponents
|
return listOf(NEUIngredient.fromString(itemId.neuItem + ":1")) + allUpgradeComponents
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAllOutputs(): Collection<NEUIngredient> {
|
override fun getAllOutputs(): Collection<NEUIngredient> {
|
||||||
return listOf(NEUIngredient.fromString(itemId.neuItem + ":1"))
|
return listOf(NEUIngredient.fromString(itemId.neuItem + ":1"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var recipes = listOf<EssenceUpgradeRecipe>()
|
var recipes = listOf<EssenceUpgradeRecipe>()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
override fun reload(repository: NEURepository) {
|
override fun provideExtraRecipes(): Iterable<NEURecipe> = recipes
|
||||||
val recipes = mutableListOf<EssenceUpgradeRecipe>()
|
|
||||||
for ((neuId, costs) in repository.constants.essenceCost.costs) {
|
override fun reload(repository: NEURepository) {
|
||||||
// TODO: add dungeonization costs. this is in repo, but not in the repo parser.
|
val recipes = mutableListOf<EssenceUpgradeRecipe>()
|
||||||
for ((starCountAfter, essenceCost) in costs.essenceCosts.entries) {
|
for ((neuId, costs) in repository.constants.essenceCost.costs) {
|
||||||
val items = costs.itemCosts[starCountAfter] ?: emptyList()
|
// TODO: add dungeonization costs. this is in repo, but not in the repo parser.
|
||||||
recipes.add(
|
for ((starCountAfter, essenceCost) in costs.essenceCosts.entries) {
|
||||||
EssenceUpgradeRecipe(
|
val items = costs.itemCosts[starCountAfter] ?: emptyList()
|
||||||
SkyblockId(neuId),
|
recipes.add(
|
||||||
starCountAfter,
|
EssenceUpgradeRecipe(
|
||||||
essenceCost,
|
SkyblockId(neuId),
|
||||||
"ESSENCE_" + costs.type.uppercase(), // how flimsy
|
starCountAfter,
|
||||||
items.map { NEUIngredient.fromString(it) }))
|
essenceCost,
|
||||||
}
|
"ESSENCE_" + costs.type.uppercase(), // how flimsy
|
||||||
}
|
items.map { NEUIngredient.fromString(it) }))
|
||||||
this.recipes = recipes
|
}
|
||||||
}
|
}
|
||||||
|
this.recipes = recipes
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/main/kotlin/repo/ExtraRecipeProvider.kt
Normal file
7
src/main/kotlin/repo/ExtraRecipeProvider.kt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package moe.nea.firmament.repo
|
||||||
|
|
||||||
|
import io.github.moulberry.repo.data.NEURecipe
|
||||||
|
|
||||||
|
interface ExtraRecipeProvider {
|
||||||
|
fun provideExtraRecipes(): Iterable<NEURecipe>
|
||||||
|
}
|
||||||
@@ -24,21 +24,27 @@ import net.minecraft.nbt.NbtCompound
|
|||||||
import net.minecraft.nbt.NbtElement
|
import net.minecraft.nbt.NbtElement
|
||||||
import net.minecraft.nbt.NbtOps
|
import net.minecraft.nbt.NbtOps
|
||||||
import net.minecraft.nbt.NbtString
|
import net.minecraft.nbt.NbtString
|
||||||
|
import net.minecraft.text.Style
|
||||||
import net.minecraft.text.Text
|
import net.minecraft.text.Text
|
||||||
import moe.nea.firmament.Firmament
|
import moe.nea.firmament.Firmament
|
||||||
import moe.nea.firmament.gui.config.HudMeta
|
import moe.nea.firmament.gui.config.HudMeta
|
||||||
import moe.nea.firmament.gui.config.HudPosition
|
import moe.nea.firmament.gui.config.HudPosition
|
||||||
import moe.nea.firmament.gui.hud.MoulConfigHud
|
import moe.nea.firmament.gui.hud.MoulConfigHud
|
||||||
import moe.nea.firmament.repo.RepoManager.initialize
|
import moe.nea.firmament.repo.RepoManager.initialize
|
||||||
|
import moe.nea.firmament.util.LegacyFormattingCode
|
||||||
import moe.nea.firmament.util.LegacyTagParser
|
import moe.nea.firmament.util.LegacyTagParser
|
||||||
import moe.nea.firmament.util.MC
|
import moe.nea.firmament.util.MC
|
||||||
import moe.nea.firmament.util.SkyblockId
|
import moe.nea.firmament.util.SkyblockId
|
||||||
import moe.nea.firmament.util.TestUtil
|
import moe.nea.firmament.util.TestUtil
|
||||||
|
import moe.nea.firmament.util.directLiteralStringContent
|
||||||
import moe.nea.firmament.util.mc.FirmamentDataComponentTypes
|
import moe.nea.firmament.util.mc.FirmamentDataComponentTypes
|
||||||
import moe.nea.firmament.util.mc.appendLore
|
import moe.nea.firmament.util.mc.appendLore
|
||||||
|
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
|
||||||
|
import moe.nea.firmament.util.mc.loreAccordingToNbt
|
||||||
import moe.nea.firmament.util.mc.modifyLore
|
import moe.nea.firmament.util.mc.modifyLore
|
||||||
import moe.nea.firmament.util.mc.setCustomName
|
import moe.nea.firmament.util.mc.setCustomName
|
||||||
import moe.nea.firmament.util.mc.setSkullOwner
|
import moe.nea.firmament.util.mc.setSkullOwner
|
||||||
|
import moe.nea.firmament.util.transformEachRecursively
|
||||||
|
|
||||||
object ItemCache : IReloadable {
|
object ItemCache : IReloadable {
|
||||||
private val cache: MutableMap<String, ItemStack> = ConcurrentHashMap()
|
private val cache: MutableMap<String, ItemStack> = ConcurrentHashMap()
|
||||||
@@ -94,6 +100,35 @@ object ItemCache : IReloadable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun un189Lore(lore: String): Text {
|
||||||
|
val base = Text.literal("")
|
||||||
|
base.setStyle(Style.EMPTY.withItalic(false))
|
||||||
|
var lastColorCode = Style.EMPTY
|
||||||
|
var readOffset = 0
|
||||||
|
while (readOffset < lore.length) {
|
||||||
|
var nextCode = lore.indexOf('§', readOffset)
|
||||||
|
if (nextCode < 0) {
|
||||||
|
nextCode = lore.length
|
||||||
|
}
|
||||||
|
val text = lore.substring(readOffset, nextCode)
|
||||||
|
if (text.isNotEmpty()) {
|
||||||
|
base.append(Text.literal(text).setStyle(lastColorCode))
|
||||||
|
}
|
||||||
|
readOffset = nextCode + 2
|
||||||
|
if (nextCode + 1 < lore.length) {
|
||||||
|
val colorCode = lore[nextCode + 1]
|
||||||
|
val formatting = LegacyFormattingCode.byCode[colorCode.lowercaseChar()] ?: LegacyFormattingCode.RESET
|
||||||
|
val modernFormatting = formatting.modern
|
||||||
|
if (modernFormatting.isColor) {
|
||||||
|
lastColorCode = Style.EMPTY.withColor(modernFormatting)
|
||||||
|
} else {
|
||||||
|
lastColorCode = lastColorCode.withFormatting(modernFormatting)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
private fun NEUItem.asItemStackNow(): ItemStack {
|
private fun NEUItem.asItemStackNow(): ItemStack {
|
||||||
try {
|
try {
|
||||||
val oldItemTag = get10809CompoundTag()
|
val oldItemTag = get10809CompoundTag()
|
||||||
@@ -101,6 +136,8 @@ object ItemCache : IReloadable {
|
|||||||
?: return brokenItemStack(this)
|
?: return brokenItemStack(this)
|
||||||
val itemInstance =
|
val itemInstance =
|
||||||
ItemStack.fromNbt(MC.defaultRegistries, modernItemTag).getOrNull() ?: return brokenItemStack(this)
|
ItemStack.fromNbt(MC.defaultRegistries, modernItemTag).getOrNull() ?: return brokenItemStack(this)
|
||||||
|
itemInstance.loreAccordingToNbt = lore.map { un189Lore(it) }
|
||||||
|
itemInstance.displayNameAccordingToNbt = un189Lore(displayName)
|
||||||
val extraAttributes = oldItemTag.getCompound("tag").getCompound("ExtraAttributes")
|
val extraAttributes = oldItemTag.getCompound("tag").getCompound("ExtraAttributes")
|
||||||
if (extraAttributes != null)
|
if (extraAttributes != null)
|
||||||
itemInstance.set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(extraAttributes))
|
itemInstance.set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(extraAttributes))
|
||||||
@@ -135,12 +172,13 @@ object ItemCache : IReloadable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Text.applyLoreReplacements(loreReplacements: Map<String, String>): Text {
|
fun Text.applyLoreReplacements(loreReplacements: Map<String, String>): Text {
|
||||||
assert(this.siblings.isEmpty())
|
return this.transformEachRecursively {
|
||||||
var string = this.string
|
var string = it.directLiteralStringContent ?: return@transformEachRecursively it
|
||||||
loreReplacements.forEach { (find, replace) ->
|
loreReplacements.forEach { (find, replace) ->
|
||||||
string = string.replace("{$find}", replace)
|
string = string.replace("{$find}", replace)
|
||||||
|
}
|
||||||
|
Text.literal(string).setStyle(it.style)
|
||||||
}
|
}
|
||||||
return Text.literal(string).styled { this.style }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var job: Job? = null
|
var job: Job? = null
|
||||||
|
|||||||
160
src/main/kotlin/repo/Reforge.kt
Normal file
160
src/main/kotlin/repo/Reforge.kt
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
package moe.nea.firmament.repo
|
||||||
|
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.builtins.MapSerializer
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
import kotlinx.serialization.json.JsonArray
|
||||||
|
import kotlinx.serialization.json.JsonDecoder
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
|
import kotlinx.serialization.serializer
|
||||||
|
import net.minecraft.item.Item
|
||||||
|
import net.minecraft.registry.RegistryKey
|
||||||
|
import net.minecraft.registry.RegistryKeys
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import moe.nea.firmament.util.ReforgeId
|
||||||
|
import moe.nea.firmament.util.SkyblockId
|
||||||
|
import moe.nea.firmament.util.skyblock.ItemType
|
||||||
|
import moe.nea.firmament.util.skyblock.Rarity
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Reforge(
|
||||||
|
val reforgeName: String,
|
||||||
|
@SerialName("internalName") val reforgeStone: SkyblockId? = null,
|
||||||
|
val nbtModifier: ReforgeId? = null,
|
||||||
|
val requiredRarities: List<Rarity>? = null,
|
||||||
|
val itemTypes: @Serializable(with = ReforgeEligibilityFilter.ItemTypesSerializer::class) List<ReforgeEligibilityFilter>? = null,
|
||||||
|
val allowOn: List<ReforgeEligibilityFilter>? = null,
|
||||||
|
val reforgeCosts: RarityMapped<Double>? = null,
|
||||||
|
val reforgeAbility: RarityMapped<String>? = null,
|
||||||
|
val reforgeStats: RarityMapped<Map<String, Double>>? = null,
|
||||||
|
) {
|
||||||
|
val eligibleItems get() = allowOn ?: itemTypes ?: listOf()
|
||||||
|
|
||||||
|
val statUniverse: Set<String> = Rarity.entries.flatMapTo(mutableSetOf()) {
|
||||||
|
reforgeStats?.get(it)?.keys ?: emptySet()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable(with = ReforgeEligibilityFilter.Serializer::class)
|
||||||
|
sealed interface ReforgeEligibilityFilter {
|
||||||
|
object ItemTypesSerializer : KSerializer<List<ReforgeEligibilityFilter>> {
|
||||||
|
override val descriptor: SerialDescriptor
|
||||||
|
get() = JsonElement.serializer().descriptor
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): List<ReforgeEligibilityFilter> {
|
||||||
|
decoder as JsonDecoder
|
||||||
|
val jsonElement = decoder.decodeJsonElement()
|
||||||
|
if (jsonElement is JsonPrimitive && jsonElement.isString) {
|
||||||
|
return jsonElement.content.split("/").map { AllowsItemType(ItemType.ofName(it)) }
|
||||||
|
}
|
||||||
|
if (jsonElement is JsonArray) {
|
||||||
|
return decoder.json.decodeFromJsonElement(serializer<List<ReforgeEligibilityFilter>>(), jsonElement)
|
||||||
|
}
|
||||||
|
jsonElement as JsonObject
|
||||||
|
val filters = mutableListOf<ReforgeEligibilityFilter>()
|
||||||
|
jsonElement["internalName"]?.let {
|
||||||
|
decoder.json.decodeFromJsonElement(serializer<List<SkyblockId>>(), it).forEach {
|
||||||
|
filters.add(AllowsInternalName(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsonElement["itemId"]?.let {
|
||||||
|
decoder.json.decodeFromJsonElement(serializer<List<String>>(), it).forEach {
|
||||||
|
val ident = Identifier.tryParse(it)
|
||||||
|
if (ident != null)
|
||||||
|
filters.add(AllowsVanillaItemType(RegistryKey.of(RegistryKeys.ITEM, ident)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filters
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: List<ReforgeEligibilityFilter>) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Serializer : KSerializer<ReforgeEligibilityFilter> {
|
||||||
|
override val descriptor: SerialDescriptor
|
||||||
|
get() = serializer<JsonElement>().descriptor
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): ReforgeEligibilityFilter {
|
||||||
|
val jsonObject = serializer<JsonObject>().deserialize(decoder)
|
||||||
|
jsonObject["internalName"]?.let {
|
||||||
|
return AllowsInternalName(SkyblockId((it as JsonPrimitive).content))
|
||||||
|
}
|
||||||
|
jsonObject["itemType"]?.let {
|
||||||
|
return AllowsItemType(ItemType.ofName((it as JsonPrimitive).content))
|
||||||
|
}
|
||||||
|
jsonObject["minecraftId"]?.let {
|
||||||
|
return AllowsVanillaItemType(RegistryKey.of(RegistryKeys.ITEM,
|
||||||
|
Identifier.of((it as JsonPrimitive).content)))
|
||||||
|
}
|
||||||
|
error("Unknown item type")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: ReforgeEligibilityFilter) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class AllowsItemType(val itemType: ItemType) : ReforgeEligibilityFilter
|
||||||
|
data class AllowsInternalName(val internalName: SkyblockId) : ReforgeEligibilityFilter
|
||||||
|
data class AllowsVanillaItemType(val minecraftId: RegistryKey<Item>) : ReforgeEligibilityFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val reforgeId get() = nbtModifier ?: ReforgeId(reforgeName.lowercase())
|
||||||
|
|
||||||
|
@Serializable(with = RarityMapped.Serializer::class)
|
||||||
|
sealed interface RarityMapped<T> {
|
||||||
|
fun get(rarity: Rarity?): T?
|
||||||
|
|
||||||
|
class Serializer<T>(
|
||||||
|
val values: KSerializer<T>
|
||||||
|
) : KSerializer<RarityMapped<T>> {
|
||||||
|
override val descriptor: SerialDescriptor
|
||||||
|
get() = JsonElement.serializer().descriptor
|
||||||
|
|
||||||
|
val indirect = MapSerializer(Rarity.serializer(), values)
|
||||||
|
override fun deserialize(decoder: Decoder): RarityMapped<T> {
|
||||||
|
decoder as JsonDecoder
|
||||||
|
val element = decoder.decodeJsonElement()
|
||||||
|
if (element is JsonObject) {
|
||||||
|
return PerRarity(decoder.json.decodeFromJsonElement(indirect, element))
|
||||||
|
} else {
|
||||||
|
return Direct(decoder.json.decodeFromJsonElement(values, element))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: RarityMapped<T>) {
|
||||||
|
when (value) {
|
||||||
|
is Direct<T> ->
|
||||||
|
values.serialize(encoder, value.value)
|
||||||
|
|
||||||
|
is PerRarity<T> ->
|
||||||
|
indirect.serialize(encoder, value.values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Direct<T>(val value: T) : RarityMapped<T> {
|
||||||
|
override fun get(rarity: Rarity?): T {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PerRarity<T>(val values: Map<Rarity, T>) : RarityMapped<T> {
|
||||||
|
override fun get(rarity: Rarity?): T? {
|
||||||
|
return values[rarity]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
124
src/main/kotlin/repo/ReforgeStore.kt
Normal file
124
src/main/kotlin/repo/ReforgeStore.kt
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package moe.nea.firmament.repo
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.mojang.serialization.JsonOps
|
||||||
|
import io.github.moulberry.repo.IReloadable
|
||||||
|
import io.github.moulberry.repo.NEURepoFile
|
||||||
|
import io.github.moulberry.repo.NEURepository
|
||||||
|
import io.github.moulberry.repo.NEURepositoryException
|
||||||
|
import io.github.moulberry.repo.data.NEURecipe
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.serializer
|
||||||
|
import net.minecraft.item.Item
|
||||||
|
import net.minecraft.registry.RegistryKey
|
||||||
|
import moe.nea.firmament.Firmament
|
||||||
|
import moe.nea.firmament.util.ReforgeId
|
||||||
|
import moe.nea.firmament.util.SkyblockId
|
||||||
|
import moe.nea.firmament.util.json.KJsonOps
|
||||||
|
import moe.nea.firmament.util.skyblock.ItemType
|
||||||
|
|
||||||
|
object ReforgeStore : ExtraRecipeProvider, IReloadable {
|
||||||
|
override fun provideExtraRecipes(): Iterable<NEURecipe> {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
var byType: Map<ItemType, List<Reforge>> = mapOf()
|
||||||
|
var byVanilla: Map<RegistryKey<Item>, List<Reforge>> = mapOf()
|
||||||
|
var byInternalName: Map<SkyblockId, List<Reforge>> = mapOf()
|
||||||
|
var modifierLut = mapOf<ReforgeId, Reforge>()
|
||||||
|
var byReforgeStone = mapOf<SkyblockId, Reforge>()
|
||||||
|
var allReforges = listOf<Reforge>()
|
||||||
|
|
||||||
|
fun findEligibleForItem(itemType: ItemType): List<Reforge> {
|
||||||
|
return byType[itemType] ?: listOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findEligibleForInternalName(internalName: SkyblockId): List<Reforge> {
|
||||||
|
return byInternalName[internalName] ?: listOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: return byVanillla
|
||||||
|
override fun reload(repo: NEURepository) {
|
||||||
|
val basicReforges =
|
||||||
|
repo.file("constants/reforges.json")
|
||||||
|
?.kJson(serializer<Map<String, Reforge>>())
|
||||||
|
?.values ?: emptyList()
|
||||||
|
val advancedReforges =
|
||||||
|
repo.file("constants/reforgestones.json")
|
||||||
|
?.kJson(serializer<Map<String, Reforge>>())
|
||||||
|
?.values ?: emptyList()
|
||||||
|
val allReforges = (basicReforges + advancedReforges)
|
||||||
|
modifierLut = allReforges.associateBy { it.reforgeId }
|
||||||
|
byReforgeStone = allReforges.filter { it.reforgeStone != null }
|
||||||
|
.associateBy { it.reforgeStone!! }
|
||||||
|
val byType = mutableMapOf<ItemType, MutableList<Reforge>>()
|
||||||
|
val byVanilla = mutableMapOf<RegistryKey<Item>, MutableList<Reforge>>()
|
||||||
|
val byInternalName = mutableMapOf<SkyblockId, MutableList<Reforge>>()
|
||||||
|
this.byType = byType
|
||||||
|
this.byVanilla = byVanilla
|
||||||
|
this.byInternalName = byInternalName
|
||||||
|
for (reforge in allReforges) {
|
||||||
|
for (eligibleItem in reforge.eligibleItems) {
|
||||||
|
when (eligibleItem) {
|
||||||
|
is Reforge.ReforgeEligibilityFilter.AllowsInternalName -> {
|
||||||
|
byInternalName.getOrPut(eligibleItem.internalName, ::mutableListOf).add(reforge)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Reforge.ReforgeEligibilityFilter.AllowsItemType -> {
|
||||||
|
val actualItemTypes = resolveItemType(eligibleItem.itemType)
|
||||||
|
for (itemType in actualItemTypes) {
|
||||||
|
byType.getOrPut(itemType, ::mutableListOf).add(reforge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is Reforge.ReforgeEligibilityFilter.AllowsVanillaItemType -> {
|
||||||
|
byVanilla.getOrPut(eligibleItem.minecraftId, ::mutableListOf).add(reforge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.allReforges = allReforges
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resolveItemType(itemType: ItemType): List<ItemType> {
|
||||||
|
if (ItemType.SWORD == itemType) {
|
||||||
|
return listOf(
|
||||||
|
ItemType.SWORD,
|
||||||
|
ItemType.GAUNTLET,
|
||||||
|
ItemType.LONGSWORD,// TODO: check name
|
||||||
|
ItemType.FISHING_WEAPON,// TODO: check name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (itemType == ItemType.ofName("ARMOR")) {
|
||||||
|
return listOf(
|
||||||
|
ItemType.CHESTPLATE,
|
||||||
|
ItemType.LEGGINGS,
|
||||||
|
ItemType.HELMET,
|
||||||
|
ItemType.BOOTS,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (itemType == ItemType.EQUIPMENT) {
|
||||||
|
return listOf(
|
||||||
|
ItemType.CLOAK,
|
||||||
|
ItemType.BRACELET,
|
||||||
|
ItemType.NECKLACE,
|
||||||
|
ItemType.BELT,
|
||||||
|
ItemType.GLOVES,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (itemType == ItemType.ROD) {
|
||||||
|
return listOf(ItemType.FISHING_ROD, ItemType.FISHING_WEAPON)
|
||||||
|
}
|
||||||
|
return listOf(itemType)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> NEURepoFile.kJson(serializer: KSerializer<T>): T {
|
||||||
|
val rawJson = json(JsonElement::class.java)
|
||||||
|
try {
|
||||||
|
val kJsonElement = JsonOps.INSTANCE.convertTo(KJsonOps.INSTANCE, rawJson)
|
||||||
|
return Firmament.json.decodeFromJsonElement(serializer, kJsonElement)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
throw NEURepositoryException(path, "Could not decode kotlin JSON element", ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/main/kotlin/repo/RepoItemTypeCache.kt
Normal file
15
src/main/kotlin/repo/RepoItemTypeCache.kt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package moe.nea.firmament.repo
|
||||||
|
|
||||||
|
import io.github.moulberry.repo.IReloadable
|
||||||
|
import io.github.moulberry.repo.NEURepository
|
||||||
|
import io.github.moulberry.repo.data.NEUItem
|
||||||
|
import moe.nea.firmament.util.skyblock.ItemType
|
||||||
|
|
||||||
|
object RepoItemTypeCache : IReloadable {
|
||||||
|
|
||||||
|
var byItemType: Map<ItemType?, List<NEUItem>> = mapOf()
|
||||||
|
|
||||||
|
override fun reload(repository: NEURepository) {
|
||||||
|
byItemType = repository.items.items.values.groupBy { ItemType.fromEscapeCodeLore(it.lore.lastOrNull() ?: "") }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,13 +53,16 @@ object RepoManager {
|
|||||||
var recentlyFailedToUpdateItemList = false
|
var recentlyFailedToUpdateItemList = false
|
||||||
|
|
||||||
val essenceRecipeProvider = EssenceRecipeProvider()
|
val essenceRecipeProvider = EssenceRecipeProvider()
|
||||||
val recipeCache = BetterRepoRecipeCache(essenceRecipeProvider)
|
val recipeCache = BetterRepoRecipeCache(essenceRecipeProvider, ReforgeStore)
|
||||||
|
|
||||||
fun makeNEURepository(path: Path): NEURepository {
|
fun makeNEURepository(path: Path): NEURepository {
|
||||||
return NEURepository.of(path).apply {
|
return NEURepository.of(path).apply {
|
||||||
registerReloadListener(ItemCache)
|
registerReloadListener(ItemCache)
|
||||||
|
registerReloadListener(RepoItemTypeCache)
|
||||||
registerReloadListener(ExpLadders)
|
registerReloadListener(ExpLadders)
|
||||||
registerReloadListener(ItemNameLookup)
|
registerReloadListener(ItemNameLookup)
|
||||||
|
registerReloadListener(ReforgeStore)
|
||||||
|
registerReloadListener(essenceRecipeProvider)
|
||||||
ReloadRegistrationEvent.publish(ReloadRegistrationEvent(this))
|
ReloadRegistrationEvent.publish(ReloadRegistrationEvent(this))
|
||||||
registerReloadListener {
|
registerReloadListener {
|
||||||
if (TestUtil.isInTest) return@registerReloadListener
|
if (TestUtil.isInTest) return@registerReloadListener
|
||||||
@@ -70,7 +73,6 @@ object RepoManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
registerReloadListener(essenceRecipeProvider)
|
|
||||||
registerReloadListener(recipeCache)
|
registerReloadListener(recipeCache)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,18 +9,31 @@ import net.minecraft.item.ItemStack
|
|||||||
import net.minecraft.network.RegistryByteBuf
|
import net.minecraft.network.RegistryByteBuf
|
||||||
import net.minecraft.network.codec.PacketCodec
|
import net.minecraft.network.codec.PacketCodec
|
||||||
import net.minecraft.network.codec.PacketCodecs
|
import net.minecraft.network.codec.PacketCodecs
|
||||||
|
import net.minecraft.text.Style
|
||||||
import net.minecraft.text.Text
|
import net.minecraft.text.Text
|
||||||
|
import net.minecraft.text.TextColor
|
||||||
import net.minecraft.util.Formatting
|
import net.minecraft.util.Formatting
|
||||||
import moe.nea.firmament.repo.ItemCache.asItemStack
|
import moe.nea.firmament.repo.ItemCache.asItemStack
|
||||||
import moe.nea.firmament.repo.ItemCache.withFallback
|
import moe.nea.firmament.repo.ItemCache.withFallback
|
||||||
import moe.nea.firmament.util.FirmFormatters
|
import moe.nea.firmament.util.FirmFormatters
|
||||||
import moe.nea.firmament.util.LegacyFormattingCode
|
import moe.nea.firmament.util.LegacyFormattingCode
|
||||||
|
import moe.nea.firmament.util.ReforgeId
|
||||||
import moe.nea.firmament.util.SkyblockId
|
import moe.nea.firmament.util.SkyblockId
|
||||||
|
import moe.nea.firmament.util.directLiteralStringContent
|
||||||
|
import moe.nea.firmament.util.extraAttributes
|
||||||
|
import moe.nea.firmament.util.getReforgeId
|
||||||
|
import moe.nea.firmament.util.getUpgradeStars
|
||||||
|
import moe.nea.firmament.util.grey
|
||||||
import moe.nea.firmament.util.mc.appendLore
|
import moe.nea.firmament.util.mc.appendLore
|
||||||
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
|
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
|
||||||
|
import moe.nea.firmament.util.mc.loreAccordingToNbt
|
||||||
import moe.nea.firmament.util.petData
|
import moe.nea.firmament.util.petData
|
||||||
|
import moe.nea.firmament.util.prepend
|
||||||
import moe.nea.firmament.util.skyBlockId
|
import moe.nea.firmament.util.skyBlockId
|
||||||
|
import moe.nea.firmament.util.skyblock.ItemType
|
||||||
|
import moe.nea.firmament.util.skyblock.Rarity
|
||||||
import moe.nea.firmament.util.skyblockId
|
import moe.nea.firmament.util.skyblockId
|
||||||
|
import moe.nea.firmament.util.useMatch
|
||||||
import moe.nea.firmament.util.withColor
|
import moe.nea.firmament.util.withColor
|
||||||
|
|
||||||
data class SBItemStack constructor(
|
data class SBItemStack constructor(
|
||||||
@@ -29,9 +42,9 @@ data class SBItemStack constructor(
|
|||||||
private var stackSize: Int,
|
private var stackSize: Int,
|
||||||
private var petData: PetData?,
|
private var petData: PetData?,
|
||||||
val extraLore: List<Text> = emptyList(),
|
val extraLore: List<Text> = emptyList(),
|
||||||
// TODO: grab this star data from nbt if possible
|
|
||||||
val stars: Int = 0,
|
val stars: Int = 0,
|
||||||
val fallback: ItemStack? = null,
|
val fallback: ItemStack? = null,
|
||||||
|
val reforge: ReforgeId? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun getStackSize() = stackSize
|
fun getStackSize() = stackSize
|
||||||
@@ -68,7 +81,9 @@ data class SBItemStack constructor(
|
|||||||
skyblockId,
|
skyblockId,
|
||||||
RepoManager.getNEUItem(skyblockId),
|
RepoManager.getNEUItem(skyblockId),
|
||||||
itemStack.count,
|
itemStack.count,
|
||||||
petData = itemStack.petData?.let { PetData.fromHypixel(it) }
|
petData = itemStack.petData?.let { PetData.fromHypixel(it) },
|
||||||
|
stars = itemStack.getUpgradeStars(),
|
||||||
|
reforge = itemStack.getReforgeId()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +98,153 @@ data class SBItemStack constructor(
|
|||||||
fun passthrough(itemStack: ItemStack): SBItemStack {
|
fun passthrough(itemStack: ItemStack): SBItemStack {
|
||||||
return SBItemStack(SkyblockId.NULL, null, itemStack.count, null, fallback = itemStack)
|
return SBItemStack(SkyblockId.NULL, null, itemStack.count, null, fallback = itemStack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun appendEnhancedStats(
|
||||||
|
itemStack: ItemStack,
|
||||||
|
reforgeStats: Map<String, Double>,
|
||||||
|
buffKind: BuffKind,
|
||||||
|
) {
|
||||||
|
val namedReforgeStats = reforgeStats
|
||||||
|
.mapKeysTo(mutableMapOf()) { statIdToName(it.key) }
|
||||||
|
val loreMut = itemStack.loreAccordingToNbt.toMutableList()
|
||||||
|
var statBlockLastIndex = -1
|
||||||
|
for (i in loreMut.indices) {
|
||||||
|
val statLine = parseStatLine(loreMut[i])
|
||||||
|
if (statLine == null && statBlockLastIndex >= 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (statLine == null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
statBlockLastIndex = i
|
||||||
|
val statBuff = namedReforgeStats.remove(statLine.statName) ?: continue
|
||||||
|
loreMut[i] = statLine.addStat(statBuff, buffKind).reconstitute()
|
||||||
|
}
|
||||||
|
if (namedReforgeStats.isNotEmpty() && statBlockLastIndex == -1) {
|
||||||
|
loreMut.add(0, Text.literal(""))
|
||||||
|
}
|
||||||
|
// If there is no stat block the statBlockLastIndex falls through to -1
|
||||||
|
// TODO: this is good enough for some items. some other items might have their stats at a different place.
|
||||||
|
for ((statName, statBuff) in namedReforgeStats) {
|
||||||
|
val statLine = StatLine(statName, null).addStat(statBuff, buffKind)
|
||||||
|
loreMut.add(statBlockLastIndex + 1, statLine.reconstitute())
|
||||||
|
}
|
||||||
|
itemStack.loreAccordingToNbt = loreMut
|
||||||
|
}
|
||||||
|
|
||||||
|
data class StatFormatting(
|
||||||
|
val postFix: String,
|
||||||
|
val color: Formatting,
|
||||||
|
)
|
||||||
|
|
||||||
|
val formattingOverrides = mapOf(
|
||||||
|
"Sea Creature Chance" to StatFormatting("%", Formatting.RED),
|
||||||
|
"Strength" to StatFormatting("", Formatting.RED),
|
||||||
|
"Damage" to StatFormatting("", Formatting.RED),
|
||||||
|
"Bonus Attack Speed" to StatFormatting("%", Formatting.RED),
|
||||||
|
"Shot Cooldown" to StatFormatting("s", Formatting.RED),
|
||||||
|
"Ability Damage" to StatFormatting("%", Formatting.RED),
|
||||||
|
"Crit Damage" to StatFormatting("%", Formatting.RED),
|
||||||
|
"Crit Chance" to StatFormatting("%", Formatting.RED),
|
||||||
|
"Ability Damage" to StatFormatting("%", Formatting.RED),
|
||||||
|
"Trophy Fish Chance" to StatFormatting("%", Formatting.GREEN),
|
||||||
|
"Health" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Defense" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Fishing Speed" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Double Hook Chance" to StatFormatting("%", Formatting.GREEN),
|
||||||
|
"Mining Speed" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Mining Fortune" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Heat Resistance" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Swing Range" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Rift Time" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Speed" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Farming Fortune" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"True Defense" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Mending" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Foraging Wisdom" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Farming Wisdom" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Foraging Fortune" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Magic Find" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Ferocity" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Bonus Pest Chance" to StatFormatting("%", Formatting.GREEN),
|
||||||
|
"Cold Resistance" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Pet Luck" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Fear" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Mana Regen" to StatFormatting("%", Formatting.GREEN),
|
||||||
|
"Rift Damage" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Hearts" to StatFormatting("", Formatting.GREEN),
|
||||||
|
"Vitality" to StatFormatting("", Formatting.GREEN),
|
||||||
|
// TODO: make this a repo json
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
private val statLabelRegex = "(?<statName>.*): ".toPattern()
|
||||||
|
|
||||||
|
enum class BuffKind(
|
||||||
|
val color: Formatting,
|
||||||
|
val prefix: String,
|
||||||
|
val postFix: String,
|
||||||
|
) {
|
||||||
|
REFORGE(Formatting.BLUE, "(", ")"),
|
||||||
|
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
data class StatLine(
|
||||||
|
val statName: String,
|
||||||
|
val value: Text?,
|
||||||
|
val rest: List<Text> = listOf(),
|
||||||
|
val valueNum: Double? = value?.directLiteralStringContent?.trim(' ', '%', '+')?.toDoubleOrNull()
|
||||||
|
) {
|
||||||
|
fun addStat(amount: Double, buffKind: BuffKind): StatLine {
|
||||||
|
val formattedAmount = FirmFormatters.formatCommas(amount, 1, includeSign = true)
|
||||||
|
return copy(
|
||||||
|
valueNum = (valueNum ?: 0.0) + amount,
|
||||||
|
value = null,
|
||||||
|
rest = rest +
|
||||||
|
listOf(
|
||||||
|
Text.literal(
|
||||||
|
buffKind.prefix + formattedAmount +
|
||||||
|
statFormatting.postFix +
|
||||||
|
buffKind.postFix + " ")
|
||||||
|
.withColor(buffKind.color)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun formatValue() =
|
||||||
|
Text.literal(FirmFormatters.formatCommas(valueNum ?: 0.0,
|
||||||
|
1,
|
||||||
|
includeSign = true) + statFormatting.postFix + " ")
|
||||||
|
.setStyle(Style.EMPTY.withColor(statFormatting.color))
|
||||||
|
|
||||||
|
val statFormatting = formattingOverrides[statName] ?: StatFormatting("", Formatting.GREEN)
|
||||||
|
private fun abbreviate(abbreviateTo: Int): String {
|
||||||
|
if (abbreviateTo >= statName.length) return statName
|
||||||
|
val segments = statName.split(" ")
|
||||||
|
return segments.joinToString(" ") {
|
||||||
|
it.substring(0, maxOf(1, abbreviateTo / segments.size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reconstitute(abbreviateTo: Int = Int.MAX_VALUE): Text =
|
||||||
|
Text.literal("").setStyle(Style.EMPTY.withItalic(false))
|
||||||
|
.append(Text.literal("${abbreviate(abbreviateTo)}: ").grey())
|
||||||
|
.append(value ?: formatValue())
|
||||||
|
.also { rest.forEach(it::append) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun statIdToName(statId: String): String {
|
||||||
|
val segments = statId.split("_")
|
||||||
|
return segments.joinToString(" ") { it.replaceFirstChar { it.uppercaseChar() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseStatLine(line: Text): StatLine? {
|
||||||
|
val sibs = line.siblings
|
||||||
|
val stat = sibs.firstOrNull() ?: return null
|
||||||
|
if (stat.style.color != TextColor.fromFormatting(Formatting.GRAY)) return null
|
||||||
|
val statLabel = stat.directLiteralStringContent ?: return null
|
||||||
|
val statName = statLabelRegex.useMatch(statLabel) { group("statName") } ?: return null
|
||||||
|
return StatLine(statName, sibs[1], sibs.subList(2, sibs.size))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(skyblockId: SkyblockId, petData: PetData) : this(
|
constructor(skyblockId: SkyblockId, petData: PetData) : this(
|
||||||
@@ -133,6 +295,25 @@ data class SBItemStack constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun appendReforgeInfo(
|
||||||
|
itemStack: ItemStack,
|
||||||
|
) {
|
||||||
|
val rarity = Rarity.fromItem(itemStack) ?: return
|
||||||
|
val reforgeId = this.reforge ?: return
|
||||||
|
val reforge = ReforgeStore.modifierLut[reforgeId] ?: return
|
||||||
|
val reforgeStats = reforge.reforgeStats?.get(rarity) ?: mapOf()
|
||||||
|
itemStack.displayNameAccordingToNbt = itemStack.displayNameAccordingToNbt.copy()
|
||||||
|
.prepend(Text.literal(reforge.reforgeName + " ").formatted(Rarity.colourMap[rarity] ?: Formatting.WHITE))
|
||||||
|
val data = itemStack.extraAttributes.copy()
|
||||||
|
data.putString("modifier", reforgeId.id)
|
||||||
|
itemStack.extraAttributes = data
|
||||||
|
appendEnhancedStats(itemStack, reforgeStats, BuffKind.REFORGE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: avoid instantiating the item stack here
|
||||||
|
val itemType: ItemType? get() = ItemType.fromItemStack(asImmutableItemStack())
|
||||||
|
val rarity: Rarity? get() = Rarity.fromItem(asImmutableItemStack())
|
||||||
|
|
||||||
private var itemStack_: ItemStack? = null
|
private var itemStack_: ItemStack? = null
|
||||||
|
|
||||||
private val itemStack: ItemStack
|
private val itemStack: ItemStack
|
||||||
@@ -147,6 +328,7 @@ data class SBItemStack constructor(
|
|||||||
return@run neuItem.asItemStack(idHint = skyblockId, replacementData)
|
return@run neuItem.asItemStack(idHint = skyblockId, replacementData)
|
||||||
.withFallback(fallback)
|
.withFallback(fallback)
|
||||||
.copyWithCount(stackSize)
|
.copyWithCount(stackSize)
|
||||||
|
.also { appendReforgeInfo(it) }
|
||||||
.also { it.appendLore(extraLore) }
|
.also { it.appendLore(extraLore) }
|
||||||
.also { enhanceStatsByStars(it, stars) }
|
.also { enhanceStatsByStars(it, stars) }
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/main/kotlin/util/AprilFoolsUtil.kt
Normal file
10
src/main/kotlin/util/AprilFoolsUtil.kt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package moe.nea.firmament.util
|
||||||
|
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.Month
|
||||||
|
|
||||||
|
object AprilFoolsUtil {
|
||||||
|
val isAprilFoolsDay = LocalDateTime.now().let {
|
||||||
|
it.dayOfMonth == 1 && it.month == Month.APRIL
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,21 +15,25 @@ import net.minecraft.text.Text
|
|||||||
|
|
||||||
object FirmFormatters {
|
object FirmFormatters {
|
||||||
fun formatCommas(int: Int, segments: Int = 3): String = formatCommas(int.toLong(), segments)
|
fun formatCommas(int: Int, segments: Int = 3): String = formatCommas(int.toLong(), segments)
|
||||||
fun formatCommas(long: Long, segments: Int = 3): String {
|
fun formatCommas(long: Long, segments: Int = 3, includeSign: Boolean = false): String {
|
||||||
|
if (long < 0 && long != Long.MIN_VALUE) {
|
||||||
|
return "-" + formatCommas(-long, segments, false)
|
||||||
|
}
|
||||||
|
val prefix = if (includeSign) "+" else ""
|
||||||
val α = long / 1000
|
val α = long / 1000
|
||||||
if (α != 0L) {
|
if (α != 0L) {
|
||||||
return formatCommas(α, segments) + "," + (long - α * 1000).toString().padStart(3, '0')
|
return prefix + formatCommas(α, segments) + "," + (long - α * 1000).toString().padStart(3, '0')
|
||||||
}
|
}
|
||||||
return long.toString()
|
return prefix + long.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun formatCommas(float: Float, fractionalDigits: Int): String = formatCommas(float.toDouble(), fractionalDigits)
|
fun formatCommas(float: Float, fractionalDigits: Int): String = formatCommas(float.toDouble(), fractionalDigits)
|
||||||
fun formatCommas(double: Double, fractionalDigits: Int): String {
|
fun formatCommas(double: Double, fractionalDigits: Int, includeSign: Boolean = false): String {
|
||||||
val long = double.toLong()
|
val long = double.toLong()
|
||||||
val δ = (double - long).absoluteValue
|
val δ = (double - long).absoluteValue
|
||||||
val μ = pow(10, fractionalDigits)
|
val μ = pow(10, fractionalDigits)
|
||||||
val digits = (μ * δ).toInt().toString().padStart(fractionalDigits, '0').trimEnd('0')
|
val digits = (μ * δ).toInt().toString().padStart(fractionalDigits, '0').trimEnd('0')
|
||||||
return formatCommas(long) + (if (digits.isEmpty()) "" else ".$digits")
|
return formatCommas(long, includeSign = includeSign) + (if (digits.isEmpty()) "" else ".$digits")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun formatDistance(distance: Double): String {
|
fun formatDistance(distance: Double): String {
|
||||||
|
|||||||
@@ -1,35 +1,37 @@
|
|||||||
|
|
||||||
|
|
||||||
package moe.nea.firmament.util
|
package moe.nea.firmament.util
|
||||||
|
|
||||||
import net.minecraft.util.Formatting
|
import net.minecraft.util.Formatting
|
||||||
|
|
||||||
enum class LegacyFormattingCode(val label: String, val char: Char, val index: Int) {
|
enum class LegacyFormattingCode(val label: String, val char: Char, val index: Int) {
|
||||||
BLACK("BLACK", '0', 0),
|
BLACK("BLACK", '0', 0),
|
||||||
DARK_BLUE("DARK_BLUE", '1', 1),
|
DARK_BLUE("DARK_BLUE", '1', 1),
|
||||||
DARK_GREEN("DARK_GREEN", '2', 2),
|
DARK_GREEN("DARK_GREEN", '2', 2),
|
||||||
DARK_AQUA("DARK_AQUA", '3', 3),
|
DARK_AQUA("DARK_AQUA", '3', 3),
|
||||||
DARK_RED("DARK_RED", '4', 4),
|
DARK_RED("DARK_RED", '4', 4),
|
||||||
DARK_PURPLE("DARK_PURPLE", '5', 5),
|
DARK_PURPLE("DARK_PURPLE", '5', 5),
|
||||||
GOLD("GOLD", '6', 6),
|
GOLD("GOLD", '6', 6),
|
||||||
GRAY("GRAY", '7', 7),
|
GRAY("GRAY", '7', 7),
|
||||||
DARK_GRAY("DARK_GRAY", '8', 8),
|
DARK_GRAY("DARK_GRAY", '8', 8),
|
||||||
BLUE("BLUE", '9', 9),
|
BLUE("BLUE", '9', 9),
|
||||||
GREEN("GREEN", 'a', 10),
|
GREEN("GREEN", 'a', 10),
|
||||||
AQUA("AQUA", 'b', 11),
|
AQUA("AQUA", 'b', 11),
|
||||||
RED("RED", 'c', 12),
|
RED("RED", 'c', 12),
|
||||||
LIGHT_PURPLE("LIGHT_PURPLE", 'd', 13),
|
LIGHT_PURPLE("LIGHT_PURPLE", 'd', 13),
|
||||||
YELLOW("YELLOW", 'e', 14),
|
YELLOW("YELLOW", 'e', 14),
|
||||||
WHITE("WHITE", 'f', 15),
|
WHITE("WHITE", 'f', 15),
|
||||||
OBFUSCATED("OBFUSCATED", 'k', -1),
|
OBFUSCATED("OBFUSCATED", 'k', -1),
|
||||||
BOLD("BOLD", 'l', -1),
|
BOLD("BOLD", 'l', -1),
|
||||||
STRIKETHROUGH("STRIKETHROUGH", 'm', -1),
|
STRIKETHROUGH("STRIKETHROUGH", 'm', -1),
|
||||||
UNDERLINE("UNDERLINE", 'n', -1),
|
UNDERLINE("UNDERLINE", 'n', -1),
|
||||||
ITALIC("ITALIC", 'o', -1),
|
ITALIC("ITALIC", 'o', -1),
|
||||||
RESET("RESET", 'r', -1);
|
RESET("RESET", 'r', -1);
|
||||||
|
|
||||||
val modern = Formatting.byCode(char)!!
|
companion object {
|
||||||
|
val byCode = entries.associateBy { it.char }
|
||||||
|
}
|
||||||
|
|
||||||
val formattingCode = "§$char"
|
val modern = Formatting.byCode(char)!!
|
||||||
|
|
||||||
|
val formattingCode = "§$char"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ object MC {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun sendCommand(command: String) {
|
fun sendCommand(command: String) {
|
||||||
|
// TODO: add a queue to this and sendServerChat
|
||||||
|
ErrorUtil.softCheck("Server commands have an implied /", !command.startsWith("/"))
|
||||||
player?.networkHandler?.sendCommand(command)
|
player?.networkHandler?.sendCommand(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,8 +98,9 @@ object MC {
|
|||||||
inline val camera: Entity? get() = instance.cameraEntity
|
inline val camera: Entity? get() = instance.cameraEntity
|
||||||
inline val guiAtlasManager get() = instance.guiAtlasManager
|
inline val guiAtlasManager get() = instance.guiAtlasManager
|
||||||
inline val world: ClientWorld? get() = TestUtil.unlessTesting { instance.world }
|
inline val world: ClientWorld? get() = TestUtil.unlessTesting { instance.world }
|
||||||
|
inline val playerName: String? get() = player?.name?.unformattedString
|
||||||
inline var screen: Screen?
|
inline var screen: Screen?
|
||||||
get() = TestUtil.unlessTesting{ instance.currentScreen }
|
get() = TestUtil.unlessTesting { instance.currentScreen }
|
||||||
set(value) = instance.setScreen(value)
|
set(value) = instance.setScreen(value)
|
||||||
val screenName get() = screen?.title?.unformattedString?.trim()
|
val screenName get() = screen?.title?.unformattedString?.trim()
|
||||||
inline val handledScreen: HandledScreen<*>? get() = instance.currentScreen as? HandledScreen<*>
|
inline val handledScreen: HandledScreen<*>? get() = instance.currentScreen as? HandledScreen<*>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package moe.nea.firmament.util
|
package moe.nea.firmament.util
|
||||||
|
|
||||||
|
import java.time.ZoneId
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import net.hypixel.modapi.HypixelModAPI
|
import net.hypixel.modapi.HypixelModAPI
|
||||||
import net.hypixel.modapi.packet.impl.clientbound.event.ClientboundLocationPacket
|
import net.hypixel.modapi.packet.impl.clientbound.event.ClientboundLocationPacket
|
||||||
@@ -10,63 +11,66 @@ import moe.nea.firmament.events.ProcessChatEvent
|
|||||||
import moe.nea.firmament.events.ProfileSwitchEvent
|
import moe.nea.firmament.events.ProfileSwitchEvent
|
||||||
import moe.nea.firmament.events.ServerConnectedEvent
|
import moe.nea.firmament.events.ServerConnectedEvent
|
||||||
import moe.nea.firmament.events.SkyblockServerUpdateEvent
|
import moe.nea.firmament.events.SkyblockServerUpdateEvent
|
||||||
import moe.nea.firmament.events.WorldReadyEvent
|
|
||||||
|
|
||||||
object SBData {
|
object SBData {
|
||||||
private val profileRegex = "Profile ID: ([a-z0-9\\-]+)".toRegex()
|
private val profileRegex = "Profile ID: ([a-z0-9\\-]+)".toRegex()
|
||||||
val profileSuggestTexts = listOf(
|
val profileSuggestTexts = listOf(
|
||||||
"CLICK THIS TO SUGGEST IT IN CHAT [DASHES]",
|
"CLICK THIS TO SUGGEST IT IN CHAT [DASHES]",
|
||||||
"CLICK THIS TO SUGGEST IT IN CHAT [NO DASHES]",
|
"CLICK THIS TO SUGGEST IT IN CHAT [NO DASHES]",
|
||||||
)
|
)
|
||||||
var profileId: UUID? = null
|
var profileId: UUID? = null
|
||||||
|
|
||||||
private var hasReceivedProfile = false
|
/**
|
||||||
var locraw: Locraw? = null
|
* Source: https://hypixel-skyblock.fandom.com/wiki/Time_Systems
|
||||||
val skyblockLocation: SkyBlockIsland? get() = locraw?.skyblockLocation
|
*/
|
||||||
val hasValidLocraw get() = locraw?.server !in listOf("limbo", null)
|
val hypixelTimeZone = ZoneId.of("US/Eastern")
|
||||||
val isOnSkyblock get() = locraw?.gametype == "SKYBLOCK"
|
private var hasReceivedProfile = false
|
||||||
var profileIdCommandDebounce = TimeMark.farPast()
|
var locraw: Locraw? = null
|
||||||
fun init() {
|
val skyblockLocation: SkyBlockIsland? get() = locraw?.skyblockLocation
|
||||||
ServerConnectedEvent.subscribe("SBData:onServerConnected") {
|
val hasValidLocraw get() = locraw?.server !in listOf("limbo", null)
|
||||||
HypixelModAPI.getInstance().subscribeToEventPacket(ClientboundLocationPacket::class.java)
|
val isOnSkyblock get() = locraw?.gametype == "SKYBLOCK"
|
||||||
}
|
var profileIdCommandDebounce = TimeMark.farPast()
|
||||||
HypixelModAPI.getInstance().createHandler(ClientboundLocationPacket::class.java) {
|
fun init() {
|
||||||
MC.onMainThread {
|
ServerConnectedEvent.subscribe("SBData:onServerConnected") {
|
||||||
val lastLocraw = locraw
|
HypixelModAPI.getInstance().subscribeToEventPacket(ClientboundLocationPacket::class.java)
|
||||||
locraw = Locraw(it.serverName,
|
}
|
||||||
it.serverType.getOrNull()?.name?.uppercase(),
|
HypixelModAPI.getInstance().createHandler(ClientboundLocationPacket::class.java) {
|
||||||
it.mode.getOrNull(),
|
MC.onMainThread {
|
||||||
it.map.getOrNull())
|
val lastLocraw = locraw
|
||||||
SkyblockServerUpdateEvent.publish(SkyblockServerUpdateEvent(lastLocraw, locraw))
|
locraw = Locraw(it.serverName,
|
||||||
profileIdCommandDebounce = TimeMark.now()
|
it.serverType.getOrNull()?.name?.uppercase(),
|
||||||
}
|
it.mode.getOrNull(),
|
||||||
}
|
it.map.getOrNull())
|
||||||
SkyblockServerUpdateEvent.subscribe("SBData:sendProfileId") {
|
SkyblockServerUpdateEvent.publish(SkyblockServerUpdateEvent(lastLocraw, locraw))
|
||||||
if (!hasReceivedProfile && isOnSkyblock && profileIdCommandDebounce.passedTime() > 10.seconds) {
|
profileIdCommandDebounce = TimeMark.now()
|
||||||
profileIdCommandDebounce = TimeMark.now()
|
}
|
||||||
MC.sendServerCommand("profileid")
|
}
|
||||||
}
|
SkyblockServerUpdateEvent.subscribe("SBData:sendProfileId") {
|
||||||
}
|
if (!hasReceivedProfile && isOnSkyblock && profileIdCommandDebounce.passedTime() > 10.seconds) {
|
||||||
AllowChatEvent.subscribe("SBData:hideProfileSuggest") { event ->
|
profileIdCommandDebounce = TimeMark.now()
|
||||||
if (event.unformattedString in profileSuggestTexts && profileIdCommandDebounce.passedTime() < 5.seconds) {
|
MC.sendServerCommand("profileid")
|
||||||
event.cancel()
|
}
|
||||||
}
|
}
|
||||||
}
|
AllowChatEvent.subscribe("SBData:hideProfileSuggest") { event ->
|
||||||
ProcessChatEvent.subscribe(receivesCancelled = true, "SBData:loadProfile") { event ->
|
if (event.unformattedString in profileSuggestTexts && profileIdCommandDebounce.passedTime() < 5.seconds) {
|
||||||
val profileMatch = profileRegex.matchEntire(event.unformattedString)
|
event.cancel()
|
||||||
if (profileMatch != null) {
|
}
|
||||||
val oldProfile = profileId
|
}
|
||||||
try {
|
ProcessChatEvent.subscribe(receivesCancelled = true, "SBData:loadProfile") { event ->
|
||||||
profileId = UUID.fromString(profileMatch.groupValues[1])
|
val profileMatch = profileRegex.matchEntire(event.unformattedString)
|
||||||
hasReceivedProfile = true
|
if (profileMatch != null) {
|
||||||
} catch (e: IllegalArgumentException) {
|
val oldProfile = profileId
|
||||||
profileId = null
|
try {
|
||||||
e.printStackTrace()
|
profileId = UUID.fromString(profileMatch.groupValues[1])
|
||||||
}
|
hasReceivedProfile = true
|
||||||
if (oldProfile != profileId) {
|
} catch (e: IllegalArgumentException) {
|
||||||
ProfileSwitchEvent.publish(ProfileSwitchEvent(oldProfile, profileId))
|
profileId = null
|
||||||
}
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
if (oldProfile != profileId) {
|
||||||
}
|
ProfileSwitchEvent.publish(ProfileSwitchEvent(oldProfile, profileId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ private constructor(
|
|||||||
val PRIVATE_ISLAND = forMode("dynamic")
|
val PRIVATE_ISLAND = forMode("dynamic")
|
||||||
val RIFT = forMode("rift")
|
val RIFT = forMode("rift")
|
||||||
val MINESHAFT = forMode("mineshaft")
|
val MINESHAFT = forMode("mineshaft")
|
||||||
|
val GARDEN = forMode("garden")
|
||||||
}
|
}
|
||||||
|
|
||||||
val userFriendlyName
|
val userFriendlyName
|
||||||
|
|||||||
@@ -106,7 +106,10 @@ data class HypixelPetInfo(
|
|||||||
|
|
||||||
private val jsonparser = Json { ignoreUnknownKeys = true }
|
private val jsonparser = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
val ItemStack.extraAttributes: NbtCompound
|
var ItemStack.extraAttributes: NbtCompound
|
||||||
|
set(value) {
|
||||||
|
set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(value))
|
||||||
|
}
|
||||||
get() {
|
get() {
|
||||||
val customData = get(DataComponentTypes.CUSTOM_DATA) ?: run {
|
val customData = get(DataComponentTypes.CUSTOM_DATA) ?: run {
|
||||||
val component = NbtComponent.of(NbtCompound())
|
val component = NbtComponent.of(NbtCompound())
|
||||||
@@ -125,10 +128,26 @@ val ItemStack.skyblockUUID: UUID?
|
|||||||
private val petDataCache = WeakCache.memoize<ItemStack, Optional<HypixelPetInfo>>("PetInfo") {
|
private val petDataCache = WeakCache.memoize<ItemStack, Optional<HypixelPetInfo>>("PetInfo") {
|
||||||
val jsonString = it.extraAttributes.getString("petInfo")
|
val jsonString = it.extraAttributes.getString("petInfo")
|
||||||
if (jsonString.isNullOrBlank()) return@memoize Optional.empty()
|
if (jsonString.isNullOrBlank()) return@memoize Optional.empty()
|
||||||
ErrorUtil.catch<HypixelPetInfo?>("Could not decode hypixel pet info") { jsonparser.decodeFromString<HypixelPetInfo>(jsonString) }
|
ErrorUtil.catch<HypixelPetInfo?>("Could not decode hypixel pet info") {
|
||||||
|
jsonparser.decodeFromString<HypixelPetInfo>(jsonString)
|
||||||
|
}
|
||||||
.or { null }.intoOptional()
|
.or { null }.intoOptional()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ItemStack.getUpgradeStars(): Int {
|
||||||
|
return extraAttributes.getInt("upgrade_level").takeIf { it > 0 }
|
||||||
|
?: extraAttributes.getInt("dungeon_item_level").takeIf { it > 0 }
|
||||||
|
?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@JvmInline
|
||||||
|
value class ReforgeId(val id: String)
|
||||||
|
|
||||||
|
fun ItemStack.getReforgeId(): ReforgeId? {
|
||||||
|
return extraAttributes.getString("modifier").takeIf { it.isNotBlank() }?.let(::ReforgeId)
|
||||||
|
}
|
||||||
|
|
||||||
val ItemStack.petData: HypixelPetInfo?
|
val ItemStack.petData: HypixelPetInfo?
|
||||||
get() = petDataCache(this).getOrNull()
|
get() = petDataCache(this).getOrNull()
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import net.minecraft.component.type.LoreComponent
|
|||||||
import net.minecraft.item.ItemStack
|
import net.minecraft.item.ItemStack
|
||||||
import net.minecraft.text.Text
|
import net.minecraft.text.Text
|
||||||
|
|
||||||
var ItemStack.loreAccordingToNbt
|
var ItemStack.loreAccordingToNbt: List<Text>
|
||||||
get() = get(DataComponentTypes.LORE)?.lines ?: listOf()
|
get() = get(DataComponentTypes.LORE)?.lines ?: listOf()
|
||||||
set(value) {
|
set(value) {
|
||||||
set(DataComponentTypes.LORE, LoreComponent(value))
|
set(DataComponentTypes.LORE, LoreComponent(value))
|
||||||
}
|
}
|
||||||
|
|||||||
29
src/main/kotlin/util/mc/TolerantRegistriesOps.kt
Normal file
29
src/main/kotlin/util/mc/TolerantRegistriesOps.kt
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package moe.nea.firmament.util.mc
|
||||||
|
|
||||||
|
import com.mojang.serialization.DynamicOps
|
||||||
|
import java.util.Optional
|
||||||
|
import net.minecraft.registry.Registry
|
||||||
|
import net.minecraft.registry.RegistryKey
|
||||||
|
import net.minecraft.registry.RegistryOps
|
||||||
|
import net.minecraft.registry.RegistryWrapper
|
||||||
|
import net.minecraft.registry.entry.RegistryEntryOwner
|
||||||
|
|
||||||
|
class TolerantRegistriesOps<T>(
|
||||||
|
delegate: DynamicOps<T>,
|
||||||
|
registryInfoGetter: RegistryInfoGetter
|
||||||
|
) : RegistryOps<T>(delegate, registryInfoGetter) {
|
||||||
|
constructor(delegate: DynamicOps<T>, registry: RegistryWrapper.WrapperLookup) :
|
||||||
|
this(delegate, CachedRegistryInfoGetter(registry))
|
||||||
|
|
||||||
|
class TolerantOwner<E> : RegistryEntryOwner<E> {
|
||||||
|
override fun ownerEquals(other: RegistryEntryOwner<E>?): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <E : Any?> getOwner(registryRef: RegistryKey<out Registry<out E>>?): Optional<RegistryEntryOwner<E>> {
|
||||||
|
return super.getOwner(registryRef).map {
|
||||||
|
TolerantOwner()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,13 @@ value class ItemType private constructor(val name: String) {
|
|||||||
return ItemType(name)
|
return ItemType(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val obfuscatedRegex = "§[kK].*?(§[0-9a-fA-FrR]|$)".toRegex()
|
||||||
|
fun fromEscapeCodeLore(lore: String): ItemType? {
|
||||||
|
return lore.replace(obfuscatedRegex, "").trim().substringAfter(" ", "")
|
||||||
|
.takeIf { it.isNotEmpty() }
|
||||||
|
?.let(::ofName)
|
||||||
|
}
|
||||||
|
|
||||||
fun fromItemStack(itemStack: ItemStack): ItemType? {
|
fun fromItemStack(itemStack: ItemStack): ItemType? {
|
||||||
if (itemStack.petData != null)
|
if (itemStack.petData != null)
|
||||||
return PET
|
return PET
|
||||||
@@ -26,13 +33,31 @@ value class ItemType private constructor(val name: String) {
|
|||||||
if (type.isEmpty()) return null
|
if (type.isEmpty()) return null
|
||||||
return ofName(type)
|
return ofName(type)
|
||||||
}
|
}
|
||||||
return null
|
return itemStack.loreAccordingToNbt.lastOrNull()?.directLiteralStringContent?.let(::fromEscapeCodeLore)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: some of those are not actual in game item types, but rather ones included in the repository to splat to multiple in game types. codify those somehow
|
||||||
|
|
||||||
val SWORD = ofName("SWORD")
|
val SWORD = ofName("SWORD")
|
||||||
val DRILL = ofName("DRILL")
|
val DRILL = ofName("DRILL")
|
||||||
val PICKAXE = ofName("PICKAXE")
|
val PICKAXE = ofName("PICKAXE")
|
||||||
val GAUNTLET = ofName("GAUNTLET")
|
val GAUNTLET = ofName("GAUNTLET")
|
||||||
|
val LONGSWORD = ofName("LONG SWORD")
|
||||||
|
val EQUIPMENT = ofName("EQUIPMENT")
|
||||||
|
val FISHING_WEAPON = ofName("FISHING WEAPON")
|
||||||
|
val CLOAK = ofName("CLOAK")
|
||||||
|
val BELT = ofName("BELT")
|
||||||
|
val NECKLACE = ofName("NECKLACE")
|
||||||
|
val BRACELET = ofName("BRACELET")
|
||||||
|
val GLOVES = ofName("GLOVES")
|
||||||
|
val ROD = ofName("ROD")
|
||||||
|
val FISHING_ROD = ofName("FISHING ROD")
|
||||||
|
val VACUUM = ofName("VACUUM")
|
||||||
|
val CHESTPLATE = ofName("CHESTPLATE")
|
||||||
|
val LEGGINGS = ofName("LEGGINGS")
|
||||||
|
val HELMET = ofName("HELMET")
|
||||||
|
val BOOTS = ofName("BOOTS")
|
||||||
|
val NIL = ofName("__NIL")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This one is not really official (it never shows up in game).
|
* This one is not really official (it never shows up in game).
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
package moe.nea.firmament.util.skyblock
|
package moe.nea.firmament.util.skyblock
|
||||||
|
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
import net.minecraft.item.ItemStack
|
import net.minecraft.item.ItemStack
|
||||||
|
import net.minecraft.text.Style
|
||||||
import net.minecraft.text.Text
|
import net.minecraft.text.Text
|
||||||
|
import net.minecraft.util.Formatting
|
||||||
import moe.nea.firmament.util.StringUtil.words
|
import moe.nea.firmament.util.StringUtil.words
|
||||||
import moe.nea.firmament.util.collections.lastNotNullOfOrNull
|
import moe.nea.firmament.util.collections.lastNotNullOfOrNull
|
||||||
import moe.nea.firmament.util.mc.loreAccordingToNbt
|
import moe.nea.firmament.util.mc.loreAccordingToNbt
|
||||||
@@ -10,6 +19,7 @@ import moe.nea.firmament.util.unformattedString
|
|||||||
|
|
||||||
typealias RepoRarity = io.github.moulberry.repo.data.Rarity
|
typealias RepoRarity = io.github.moulberry.repo.data.Rarity
|
||||||
|
|
||||||
|
@Serializable(with = Rarity.Serializer::class)
|
||||||
enum class Rarity(vararg altNames: String) {
|
enum class Rarity(vararg altNames: String) {
|
||||||
COMMON,
|
COMMON,
|
||||||
UNCOMMON,
|
UNCOMMON,
|
||||||
@@ -24,11 +34,37 @@ enum class Rarity(vararg altNames: String) {
|
|||||||
UNKNOWN
|
UNKNOWN
|
||||||
;
|
;
|
||||||
|
|
||||||
val names = setOf(name) + altNames
|
object Serializer : KSerializer<Rarity> {
|
||||||
|
override val descriptor: SerialDescriptor
|
||||||
|
get() = PrimitiveSerialDescriptor(Rarity::class.java.name, PrimitiveKind.STRING)
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): Rarity {
|
||||||
|
return valueOf(decoder.decodeString().replace(" ", "_"))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: Rarity) {
|
||||||
|
encoder.encodeString(value.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val names = setOf(name) + altNames
|
||||||
|
val text: Text get() = Text.literal(name).setStyle(Style.EMPTY.withColor(colourMap[this]))
|
||||||
val neuRepoRarity: RepoRarity? = RepoRarity.entries.find { it.name == name }
|
val neuRepoRarity: RepoRarity? = RepoRarity.entries.find { it.name == name }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
// TODO: inline those formattings as fields
|
||||||
|
val colourMap = mapOf(
|
||||||
|
Rarity.COMMON to Formatting.WHITE,
|
||||||
|
Rarity.UNCOMMON to Formatting.GREEN,
|
||||||
|
Rarity.RARE to Formatting.BLUE,
|
||||||
|
Rarity.EPIC to Formatting.DARK_PURPLE,
|
||||||
|
Rarity.LEGENDARY to Formatting.GOLD,
|
||||||
|
Rarity.MYTHIC to Formatting.LIGHT_PURPLE,
|
||||||
|
Rarity.DIVINE to Formatting.AQUA,
|
||||||
|
Rarity.SPECIAL to Formatting.RED,
|
||||||
|
Rarity.VERY_SPECIAL to Formatting.RED,
|
||||||
|
Rarity.SUPREME to Formatting.DARK_RED,
|
||||||
|
)
|
||||||
val byName = entries.flatMap { en -> en.names.map { it to en } }.toMap()
|
val byName = entries.flatMap { en -> en.names.map { it to en } }.toMap()
|
||||||
val fromNeuRepo = entries.associateBy { it.neuRepoRarity }
|
val fromNeuRepo = entries.associateBy { it.neuRepoRarity }
|
||||||
|
|
||||||
|
|||||||
@@ -7,4 +7,5 @@ object SkyBlockItems {
|
|||||||
val ENCHANTED_DIAMOND = SkyblockId("ENCHANTED_DIAMOND")
|
val ENCHANTED_DIAMOND = SkyblockId("ENCHANTED_DIAMOND")
|
||||||
val DIAMOND = SkyblockId("DIAMOND")
|
val DIAMOND = SkyblockId("DIAMOND")
|
||||||
val ANCESTRAL_SPADE = SkyblockId("ANCESTRAL_SPADE")
|
val ANCESTRAL_SPADE = SkyblockId("ANCESTRAL_SPADE")
|
||||||
|
val REFORGE_ANVIL = SkyblockId("REFORGE_ANVIL")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ fun MutableText.darkGreen() = withColor(Formatting.DARK_GREEN)
|
|||||||
fun MutableText.purple() = withColor(Formatting.DARK_PURPLE)
|
fun MutableText.purple() = withColor(Formatting.DARK_PURPLE)
|
||||||
fun MutableText.pink() = withColor(Formatting.LIGHT_PURPLE)
|
fun MutableText.pink() = withColor(Formatting.LIGHT_PURPLE)
|
||||||
fun MutableText.yellow() = withColor(Formatting.YELLOW)
|
fun MutableText.yellow() = withColor(Formatting.YELLOW)
|
||||||
|
fun MutableText.gold() = withColor(Formatting.GOLD)
|
||||||
fun MutableText.grey() = withColor(Formatting.GRAY)
|
fun MutableText.grey() = withColor(Formatting.GRAY)
|
||||||
fun MutableText.red() = withColor(Formatting.RED)
|
fun MutableText.red() = withColor(Formatting.RED)
|
||||||
fun MutableText.white() = withColor(Formatting.WHITE)
|
fun MutableText.white() = withColor(Formatting.WHITE)
|
||||||
@@ -142,11 +143,15 @@ fun MutableText.bold(): MutableText = styled { it.withBold(true) }
|
|||||||
fun MutableText.clickCommand(command: String): MutableText {
|
fun MutableText.clickCommand(command: String): MutableText {
|
||||||
require(command.startsWith("/"))
|
require(command.startsWith("/"))
|
||||||
return this.styled {
|
return this.styled {
|
||||||
it.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND,
|
it.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, command))
|
||||||
"/firm disablereiwarning"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun MutableText.prepend(text: Text): MutableText {
|
||||||
|
siblings.addFirst(text)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
fun Text.transformEachRecursively(function: (Text) -> Text): Text {
|
fun Text.transformEachRecursively(function: (Text) -> Text): Text {
|
||||||
val c = this.content
|
val c = this.content
|
||||||
if (c is TranslatableTextContent) {
|
if (c is TranslatableTextContent) {
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ accessible class net/minecraft/client/render/RenderLayer$MultiPhase
|
|||||||
accessible class net/minecraft/client/render/RenderLayer$MultiPhaseParameters
|
accessible class net/minecraft/client/render/RenderLayer$MultiPhaseParameters
|
||||||
accessible class net/minecraft/client/font/TextRenderer$Drawer
|
accessible class net/minecraft/client/font/TextRenderer$Drawer
|
||||||
accessible field net/minecraft/client/gui/hud/InGameHud SCOREBOARD_ENTRY_COMPARATOR Ljava/util/Comparator;
|
accessible field net/minecraft/client/gui/hud/InGameHud SCOREBOARD_ENTRY_COMPARATOR Ljava/util/Comparator;
|
||||||
|
accessible field net/minecraft/client/network/ClientPlayNetworkHandler combinedDynamicRegistries Lnet/minecraft/registry/DynamicRegistryManager$Immutable;
|
||||||
|
accessible method net/minecraft/registry/RegistryOps <init> (Lcom/mojang/serialization/DynamicOps;Lnet/minecraft/registry/RegistryOps$RegistryInfoGetter;)V
|
||||||
|
accessible class net/minecraft/registry/RegistryOps$CachedRegistryInfoGetter
|
||||||
|
|
||||||
accessible field net/minecraft/entity/mob/CreeperEntity CHARGED Lnet/minecraft/entity/data/TrackedData;
|
accessible field net/minecraft/entity/mob/CreeperEntity CHARGED Lnet/minecraft/entity/data/TrackedData;
|
||||||
accessible method net/minecraft/entity/decoration/ArmorStandEntity setSmall (Z)V
|
accessible method net/minecraft/entity/decoration/ArmorStandEntity setSmall (Z)V
|
||||||
|
|||||||
96
src/test/resources/testdata/items/hyperion.snbt
vendored
Normal file
96
src/test/resources/testdata/items/hyperion.snbt
vendored
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
{
|
||||||
|
components: {
|
||||||
|
"minecraft:attribute_modifiers": {
|
||||||
|
modifiers: [
|
||||||
|
],
|
||||||
|
show_in_tooltip: 0b
|
||||||
|
},
|
||||||
|
"minecraft:custom_data": {
|
||||||
|
ability_scroll: [
|
||||||
|
"IMPLOSION_SCROLL",
|
||||||
|
"WITHER_SHIELD_SCROLL",
|
||||||
|
"SHADOW_WARP_SCROLL"
|
||||||
|
],
|
||||||
|
art_of_war_count: 1,
|
||||||
|
champion_combat_xp: 1.3556020889209766E7d,
|
||||||
|
donated_museum: 1b,
|
||||||
|
enchantments: {
|
||||||
|
champion: 10,
|
||||||
|
cleave: 5,
|
||||||
|
critical: 6,
|
||||||
|
cubism: 5,
|
||||||
|
ender_slayer: 6,
|
||||||
|
execute: 5,
|
||||||
|
experience: 3,
|
||||||
|
fire_aspect: 2,
|
||||||
|
first_strike: 4,
|
||||||
|
giant_killer: 6,
|
||||||
|
impaling: 3,
|
||||||
|
lethality: 5,
|
||||||
|
looting: 4,
|
||||||
|
luck: 6,
|
||||||
|
scavenger: 4,
|
||||||
|
smite: 7,
|
||||||
|
syphon: 4,
|
||||||
|
thunderlord: 6,
|
||||||
|
ultimate_wise: 5,
|
||||||
|
vampirism: 5,
|
||||||
|
venomous: 5
|
||||||
|
},
|
||||||
|
hot_potato_count: 15,
|
||||||
|
id: "HYPERION",
|
||||||
|
modifier: "heroic",
|
||||||
|
rarity_upgrades: 1,
|
||||||
|
stats_book: 65934,
|
||||||
|
timestamp: 1658091600000L,
|
||||||
|
upgrade_level: 5,
|
||||||
|
uuid: "a45337aa-9eaa-4e6f-aa27-26a42f8eca95"
|
||||||
|
},
|
||||||
|
"minecraft:custom_name": '{"extra":[{"color":"light_purple","text":"Heroic Hyperion "},{"color":"gold","text":"✪✪✪✪✪"}],"italic":false,"text":""}',
|
||||||
|
"minecraft:enchantment_glint_override": 1b,
|
||||||
|
"minecraft:hide_additional_tooltip": {
|
||||||
|
},
|
||||||
|
"minecraft:lore": [
|
||||||
|
'{"extra":[{"color":"gray","text":"Gear Score: "},{"color":"light_purple","text":"1145 "},{"color":"dark_gray","text":"(4271)"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"Damage: "},{"color":"red","text":"+355 "},{"color":"yellow","text":"(+30) "},{"color":"dark_gray","text":"(+1,490.37)"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"Strength: "},{"color":"red","text":"+250 "},{"color":"yellow","text":"(+30) "},{"color":"gold","text":"[+5] "},{"color":"blue","text":"(+50) "},{"color":"dark_gray","text":"(+1,064.55)"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"Crit Damage: "},{"color":"red","text":"+70% "},{"color":"dark_gray","text":"(+317.1%)"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"Bonus Attack Speed: "},{"color":"red","text":"+7% "},{"color":"blue","text":"(+7%) "},{"color":"dark_gray","text":"(+10.5%)"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"Intelligence: "},{"color":"green","text":"+588 "},{"color":"blue","text":"(+125) "},{"color":"dark_gray","text":"(+2,505.09)"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"Ferocity: "},{"color":"green","text":"+33 "},{"color":"dark_gray","text":"(+45)"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[" ",{"color":"dark_gray","text":"["},{"color":"dark_gray","text":"✎"},{"color":"dark_gray","text":"] "},{"color":"dark_gray","text":"["},{"color":"dark_gray","text":"⚔"},{"color":"dark_gray","text":"]"}],"italic":false,"text":""}',
|
||||||
|
'{"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"bold":true,"color":"light_purple","text":""},{"bold":true,"color":"light_purple","text":"Ultimate Wise V"},{"color":"blue","text":", "},{"color":"blue","text":"Champion X"},{"color":"blue","text":", "},{"color":"blue","text":"Cleave V"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"blue","text":"Critical VI"},{"color":"blue","text":", "},{"color":"blue","text":"Cubism V"},{"color":"blue","text":", "},{"color":"blue","text":"Ender Slayer VI"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"blue","text":"Execute V"},{"color":"blue","text":", "},{"color":"blue","text":"Experience III"},{"color":"blue","text":", "},{"color":"blue","text":"Fire Aspect II"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"blue","text":"First Strike IV"},{"color":"blue","text":", "},{"color":"blue","text":"Giant Killer VI"},{"color":"blue","text":", "},{"color":"blue","text":"Impaling III"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"blue","text":"Lethality V"},{"color":"blue","text":", "},{"color":"blue","text":"Looting IV"},{"color":"blue","text":", "},{"color":"blue","text":"Luck VI"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"blue","text":"Scavenger IV"},{"color":"blue","text":", "},{"color":"blue","text":"Smite VII"},{"color":"blue","text":", "},{"color":"blue","text":"Syphon IV"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"blue","text":"Thunderlord VI"},{"color":"blue","text":", "},{"color":"blue","text":"Vampirism V"},{"color":"blue","text":", "},{"color":"blue","text":"Venomous V"}],"italic":false,"text":""}',
|
||||||
|
'{"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"Deals "},{"color":"red","text":"+50% "},{"color":"gray","text":"damage to Withers."}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"Grants "},{"color":"red","text":"+1 "},{"color":"red","text":"❁ Damage "},{"color":"gray","text":"and "},{"color":"green","text":"+2 "},{"color":"aqua","text":"✎"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"aqua","text":"Intelligence "},{"color":"gray","text":"per "},{"color":"red","text":"Catacombs "},{"color":"gray","text":"level."}],"italic":false,"text":""}',
|
||||||
|
'{"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"green","text":"Scroll Abilities:"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gold","text":"Ability: Wither Impact "},{"bold":true,"color":"yellow","text":"RIGHT CLICK"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"Teleport "},{"color":"green","text":"10 blocks"},{"color":"gray","text":" ahead of you."}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"Then implode dealing "},{"color":"red","text":"21,658 "},{"color":"gray","text":"damage"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"to nearby enemies. Also applies the"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"wither shield scroll ability reducing"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"damage taken and granting an"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"absorption shield for "},{"color":"yellow","text":"5 "},{"color":"gray","text":"seconds."}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"dark_gray","text":"Mana Cost: "},{"color":"dark_aqua","text":"150"}],"italic":false,"text":""}',
|
||||||
|
'{"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"white","text":"Kills: "},{"color":"gold","text":"65,934"}],"italic":false,"text":""}',
|
||||||
|
'{"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"bold":true,"color":"dark_gray","text":"* "},{"color":"dark_gray","text":"Co-op Soulbound "},{"bold":true,"color":"dark_gray","text":"*"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"bold":true,"color":"light_purple","obfuscated":true,"text":"a"},"",{"bold":false,"extra":[" "],"italic":false,"obfuscated":false,"strikethrough":false,"text":"","underlined":false},{"bold":true,"color":"light_purple","text":"MYTHIC DUNGEON SWORD "},{"bold":true,"color":"light_purple","obfuscated":true,"text":"a"}],"italic":false,"text":""}'
|
||||||
|
],
|
||||||
|
"minecraft:unbreakable": {
|
||||||
|
show_in_tooltip: 0b
|
||||||
|
}
|
||||||
|
},
|
||||||
|
count: 1,
|
||||||
|
id: "minecraft:iron_sword"
|
||||||
|
}
|
||||||
105
src/test/resources/testdata/items/implosion-belt.snbt
vendored
Normal file
105
src/test/resources/testdata/items/implosion-belt.snbt
vendored
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
{
|
||||||
|
components: {
|
||||||
|
"minecraft:attribute_modifiers": {
|
||||||
|
modifiers: [
|
||||||
|
],
|
||||||
|
show_in_tooltip: 0b
|
||||||
|
},
|
||||||
|
"minecraft:custom_data": {
|
||||||
|
attributes: {
|
||||||
|
dominance: 1,
|
||||||
|
experience: 1
|
||||||
|
},
|
||||||
|
id: "IMPLOSION_BELT",
|
||||||
|
timestamp: "12/5/22 5:17 PM",
|
||||||
|
uuid: "5c04f47e-7c6c-4ced-96b1-b8f83187b0a5"
|
||||||
|
},
|
||||||
|
"minecraft:custom_name": '{"extra":[{"color":"dark_purple","text":"Implosion Belt"}],"italic":false,"text":""}',
|
||||||
|
"minecraft:hide_additional_tooltip": {
|
||||||
|
},
|
||||||
|
"minecraft:lore": [
|
||||||
|
'{"extra":[{"color":"gray","text":"Defense: "},{"color":"green","text":"+70"}],"italic":false,"text":""}',
|
||||||
|
'{"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"red","text":"Dominance I ✖"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"Gain "},{"color":"red","text":"+1.5% "},{"color":"gray","text":"damage when at full health."}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"aqua","text":"Experience I"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"Gain "},{"color":"green","text":"+10% "},{"color":"gray","text":"more experience orbs"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"from killing mobs."}],"italic":false,"text":""}',
|
||||||
|
'{"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gold","text":"Ability: Consolidated "}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":""},{"color":"gray","text":"Increases all explosion damage dealt by "},{"color":"green","text":"25%"},{"color":"gray","text":"."}],"italic":false,"text":""}',
|
||||||
|
'{"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":""},{"color":"dark_gray","text":"This item can be reforged!"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"bold":true,"color":"dark_purple","text":"EPIC BELT"}],"italic":false,"text":""}'
|
||||||
|
],
|
||||||
|
"minecraft:profile": {
|
||||||
|
id: [I;
|
||||||
|
-896440193,
|
||||||
|
-59755884,
|
||||||
|
-1280665573,
|
||||||
|
-1297214643
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: "textures",
|
||||||
|
signature: "",
|
||||||
|
value: "ewogICJ0aW1lc3RhbXAiIDogMTY0MzYwMjI5OTA2MSwKICAicHJvZmlsZUlkIiA6ICI0ZTMwZjUwZTdiYWU0M2YzYWZkMmE3NDUyY2ViZTI5YyIsCiAgInByb2ZpbGVOYW1lIiA6ICJfdG9tYXRvel8iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjFkMmIwMzZkZDY2NGJiOTBjOWQ0NDNjMTk5OGZiNTI2Mzk4YWI0ZGRkZWI3OWI4NDAxYjE2YjlhNGQxMGJhMyIsCiAgICAgICJtZXRhZGF0YSIgOiB7CiAgICAgICAgIm1vZGVsIiA6ICJzbGltIgogICAgICB9CiAgICB9CiAgfQp9"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
count: 1,
|
||||||
|
id: "minecraft:player_head"
|
||||||
|
}{
|
||||||
|
components: {
|
||||||
|
"minecraft:attribute_modifiers": {
|
||||||
|
modifiers: [
|
||||||
|
],
|
||||||
|
show_in_tooltip: 0b
|
||||||
|
},
|
||||||
|
"minecraft:custom_data": {
|
||||||
|
attributes: {
|
||||||
|
dominance: 1,
|
||||||
|
experience: 1
|
||||||
|
},
|
||||||
|
id: "IMPLOSION_BELT",
|
||||||
|
timestamp: "12/5/22 5:17 PM",
|
||||||
|
uuid: "5c04f47e-7c6c-4ced-96b1-b8f83187b0a5"
|
||||||
|
},
|
||||||
|
"minecraft:custom_name": '{"extra":[{"color":"dark_purple","text":"Implosion Belt"}],"italic":false,"text":""}',
|
||||||
|
"minecraft:hide_additional_tooltip": {
|
||||||
|
},
|
||||||
|
"minecraft:lore": [
|
||||||
|
'{"extra":[{"color":"gray","text":"Defense: "},{"color":"green","text":"+70"}],"italic":false,"text":""}',
|
||||||
|
'{"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"red","text":"Dominance I ✖"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"Gain "},{"color":"red","text":"+1.5% "},{"color":"gray","text":"damage when at full health."}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"aqua","text":"Experience I"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"Gain "},{"color":"green","text":"+10% "},{"color":"gray","text":"more experience orbs"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":"from killing mobs."}],"italic":false,"text":""}',
|
||||||
|
'{"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gold","text":"Ability: Consolidated "}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":""},{"color":"gray","text":"Increases all explosion damage dealt by "},{"color":"green","text":"25%"},{"color":"gray","text":"."}],"italic":false,"text":""}',
|
||||||
|
'{"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"color":"gray","text":""},{"color":"dark_gray","text":"This item can be reforged!"}],"italic":false,"text":""}',
|
||||||
|
'{"extra":[{"bold":true,"color":"dark_purple","text":"EPIC BELT"}],"italic":false,"text":""}'
|
||||||
|
],
|
||||||
|
"minecraft:profile": {
|
||||||
|
id: [I;
|
||||||
|
-896440193,
|
||||||
|
-59755884,
|
||||||
|
-1280665573,
|
||||||
|
-1297214643
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: "textures",
|
||||||
|
signature: "",
|
||||||
|
value: "ewogICJ0aW1lc3RhbXAiIDogMTY0MzYwMjI5OTA2MSwKICAicHJvZmlsZUlkIiA6ICI0ZTMwZjUwZTdiYWU0M2YzYWZkMmE3NDUyY2ViZTI5YyIsCiAgInByb2ZpbGVOYW1lIiA6ICJfdG9tYXRvel8iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjFkMmIwMzZkZDY2NGJiOTBjOWQ0NDNjMTk5OGZiNTI2Mzk4YWI0ZGRkZWI3OWI4NDAxYjE2YjlhNGQxMGJhMyIsCiAgICAgICJtZXRhZGF0YSIgOiB7CiAgICAgICAgIm1vZGVsIiA6ICJzbGltIgogICAgICB9CiAgICB9CiAgfQp9"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
count: 1,
|
||||||
|
id: "minecraft:player_head"
|
||||||
|
}
|
||||||
@@ -112,6 +112,8 @@
|
|||||||
"firmament.config.fixes.auto-sprint-keybinding": "Auto Sprint KeyBinding",
|
"firmament.config.fixes.auto-sprint-keybinding": "Auto Sprint KeyBinding",
|
||||||
"firmament.config.fixes.auto-sprint-keybinding.description": "Toggle auto sprint via this keybinding.",
|
"firmament.config.fixes.auto-sprint-keybinding.description": "Toggle auto sprint via this keybinding.",
|
||||||
"firmament.config.fixes.auto-sprint.description": "This is different from vanilla sprint in the way that it only marks the keybinding pressed for the first tick of walking.",
|
"firmament.config.fixes.auto-sprint.description": "This is different from vanilla sprint in the way that it only marks the keybinding pressed for the first tick of walking.",
|
||||||
|
"firmament.config.fixes.hide-mob-effects": "Hide Potion Effects",
|
||||||
|
"firmament.config.fixes.hide-mob-effects.description": "Hide Potion effects on the right side of your player inventory.",
|
||||||
"firmament.config.fixes.peek-chat": "Peek Chat",
|
"firmament.config.fixes.peek-chat": "Peek Chat",
|
||||||
"firmament.config.fixes.peek-chat.description": "Hold this keybinding to view the chat as if you have it opened, but while still being able to control your character.",
|
"firmament.config.fixes.peek-chat.description": "Hold this keybinding to view the chat as if you have it opened, but while still being able to control your character.",
|
||||||
"firmament.config.fixes.player-skins": "Fix unsigned Player Skins",
|
"firmament.config.fixes.player-skins": "Fix unsigned Player Skins",
|
||||||
@@ -127,6 +129,22 @@
|
|||||||
"firmament.config.item-rarity-cosmetics.background-hotbar": "Hotbar Background Rarity",
|
"firmament.config.item-rarity-cosmetics.background-hotbar": "Hotbar Background Rarity",
|
||||||
"firmament.config.item-rarity-cosmetics.background-hotbar.description": "Show item rarity background in the hotbar.",
|
"firmament.config.item-rarity-cosmetics.background-hotbar.description": "Show item rarity background in the hotbar.",
|
||||||
"firmament.config.item-rarity-cosmetics.background.description": "Show a background behind each item, depending on its rarity.",
|
"firmament.config.item-rarity-cosmetics.background.description": "Show a background behind each item, depending on its rarity.",
|
||||||
|
"firmament.config.lore-timers": "Lore Timers",
|
||||||
|
"firmament.config.lore-timers.format": "Time Format",
|
||||||
|
"firmament.config.lore-timers.format.choice.american": "§9Ame§cri§fcan",
|
||||||
|
"firmament.config.lore-timers.format.choice.local": "System Time Format",
|
||||||
|
"firmament.config.lore-timers.format.choice.rfc": "RFC",
|
||||||
|
"firmament.config.lore-timers.format.choice.socialist": "European-ish",
|
||||||
|
"firmament.config.lore-timers.format.description": "Choose the time format in which resolved timers are displayed.",
|
||||||
|
"firmament.config.lore-timers.show": "Show Lore Timers",
|
||||||
|
"firmament.config.lore-timers.show.description": "Shows when a timer in a lore (such as interest, auction duration) would end.",
|
||||||
|
"firmament.config.party-commands": "Party Commands",
|
||||||
|
"firmament.config.party-commands.cooldown": "Cooldown",
|
||||||
|
"firmament.config.party-commands.cooldown.description": "Prevent people from spamming commands with a delay between party commands.",
|
||||||
|
"firmament.config.party-commands.enable": "Enable Party Commands",
|
||||||
|
"firmament.config.party-commands.enable.description": "Allow people in your party to use commands like !warp, !coords, !ptme and so on. See /firm partycommands for a list",
|
||||||
|
"firmament.config.party-commands.ignore-own": "Ignore Own Messages",
|
||||||
|
"firmament.config.party-commands.ignore-own.description": "Prevent your own messages from triggering party commands",
|
||||||
"firmament.config.pets": "Pets",
|
"firmament.config.pets": "Pets",
|
||||||
"firmament.config.pets.highlight-pet": "Highlight active pet",
|
"firmament.config.pets.highlight-pet": "Highlight active pet",
|
||||||
"firmament.config.pets.highlight-pet.description": "Highlight your currently selected pet in the /pets menu.",
|
"firmament.config.pets.highlight-pet.description": "Highlight your currently selected pet in the /pets menu.",
|
||||||
@@ -171,6 +189,11 @@
|
|||||||
"firmament.config.pristine-profit.position.description": "Edit the pristine profit hud location.",
|
"firmament.config.pristine-profit.position.description": "Edit the pristine profit hud location.",
|
||||||
"firmament.config.pristine-profit.timeout": "Timeout (0 = disabled)",
|
"firmament.config.pristine-profit.timeout": "Timeout (0 = disabled)",
|
||||||
"firmament.config.pristine-profit.timeout.description": "Track the profit you make from pristine gemstones while mining. Set to 0 seconds to disable the HUD.",
|
"firmament.config.pristine-profit.timeout.description": "Track the profit you make from pristine gemstones while mining. Set to 0 seconds to disable the HUD.",
|
||||||
|
"firmament.config.quick-commands": "Quick Commands",
|
||||||
|
"firmament.config.quick-commands.dh": "Enable /dh",
|
||||||
|
"firmament.config.quick-commands.dh.description": "Warps you to the dungeon hub.",
|
||||||
|
"firmament.config.quick-commands.join": "Enable /join",
|
||||||
|
"firmament.config.quick-commands.join.description": "Join various types of instances like dungeons using short hands like /join f1, /join k1, /join m7",
|
||||||
"firmament.config.repo": "Firmament Repo Settings",
|
"firmament.config.repo": "Firmament Repo Settings",
|
||||||
"firmament.config.repo.autoUpdate": "Auto Update",
|
"firmament.config.repo.autoUpdate": "Auto Update",
|
||||||
"firmament.config.repo.autoUpdate.description": "Automatically download new items for the item list on every startup.",
|
"firmament.config.repo.autoUpdate.description": "Automatically download new items for the item list on every startup.",
|
||||||
@@ -202,6 +225,11 @@
|
|||||||
"firmament.config.save-cursor-position.tolerance.description": "Select how long your cursor position last between GUIs before resetting back to the middle of the screen.",
|
"firmament.config.save-cursor-position.tolerance.description": "Select how long your cursor position last between GUIs before resetting back to the middle of the screen.",
|
||||||
"firmament.config.slot-locking": "Slot Locking",
|
"firmament.config.slot-locking": "Slot Locking",
|
||||||
"firmament.config.slot-locking.bind": "Bind Slot",
|
"firmament.config.slot-locking.bind": "Bind Slot",
|
||||||
|
"firmament.config.slot-locking.bind-render": "Show Slot Bindings",
|
||||||
|
"firmament.config.slot-locking.bind-render.choice.everything": "Always",
|
||||||
|
"firmament.config.slot-locking.bind-render.choice.nothing": "Only when hovered",
|
||||||
|
"firmament.config.slot-locking.bind-render.choice.only_boxes": "Only boxes",
|
||||||
|
"firmament.config.slot-locking.bind-render.description": "Disable rendering of the slot binding lines (or all of the slot binding rendering), unless the relevant slot is being hovered.",
|
||||||
"firmament.config.slot-locking.bind.description": "Bind a hotbar slot to another slot. This allows quick switching between the slots by shift clicking on either slot.",
|
"firmament.config.slot-locking.bind.description": "Bind a hotbar slot to another slot. This allows quick switching between the slots by shift clicking on either slot.",
|
||||||
"firmament.config.slot-locking.lock": "Lock Slot",
|
"firmament.config.slot-locking.lock": "Lock Slot",
|
||||||
"firmament.config.slot-locking.lock-uuid": "Lock UUID (Lock Item)",
|
"firmament.config.slot-locking.lock-uuid": "Lock UUID (Lock Item)",
|
||||||
@@ -303,6 +331,8 @@
|
|||||||
"firmament.recipe.mobs.name": "§8[§7Lv %d§8] §c%s",
|
"firmament.recipe.mobs.name": "§8[§7Lv %d§8] §c%s",
|
||||||
"firmament.recipe.mobs.name.nolevel": "§c%s",
|
"firmament.recipe.mobs.name.nolevel": "§c%s",
|
||||||
"firmament.recipe.novanilla": "Hypixel cannot super craft vanilla recipes",
|
"firmament.recipe.novanilla": "Hypixel cannot super craft vanilla recipes",
|
||||||
|
"firmament.recipecategory.reforge": "Reforge",
|
||||||
|
"firmament.recipecategory.reforge.basic": "This is a basic reforge, available at the Blacksmith.",
|
||||||
"firmament.reiwarning": "Firmament needs RoughlyEnoughItems to display its item list!",
|
"firmament.reiwarning": "Firmament needs RoughlyEnoughItems to display its item list!",
|
||||||
"firmament.reiwarning.disable": "Click here to disable this warning",
|
"firmament.reiwarning.disable": "Click here to disable this warning",
|
||||||
"firmament.reiwarning.disabled": "Disabled the RoughlyEnoughItems warning. Keep in mind that you will not have an item list without REI.",
|
"firmament.reiwarning.disabled": "Disabled the RoughlyEnoughItems warning. Keep in mind that you will not have an item list without REI.",
|
||||||
|
|||||||
Reference in New Issue
Block a user