Merge branch 'mc-1.21.3'

This commit is contained in:
Linnea Gräf
2024-12-31 16:52:29 +01:00
53 changed files with 2182 additions and 370 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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)
} }
} }

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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()

View File

@@ -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;
}
}

View File

@@ -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();
} }

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View 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")
}
}

View 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>()
}

View 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()
}
}

View File

@@ -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,13 +16,44 @@ 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"
object TConfig : ManagedConfig("quick-commands", Category.CHAT) {
val enableJoin by toggle("join") { true }
val enableDh by toggle("dh") { true }
override fun onChange(option: ManagedOption<*>) {
reloadCommands()
}
}
fun reloadCommands() {
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
}
}
fun removePartialPrefix(text: String, prefix: String): String? { fun removePartialPrefix(text: String, prefix: String): String? {
var lf: String? = null var lf: String? = null
for (i in 1..prefix.length) { for (i in 1..prefix.length) {
@@ -29,11 +64,30 @@ object QuickCommands : FirmamentFeature {
return lf return lf
} }
var lastReceivedTreePacket: CommandTreeS2CPacket? = null
val kuudraLevelNames = listOf("NORMAL", "HOT", "BURNING", "FIERY", "INFERNAL") val kuudraLevelNames = listOf("NORMAL", "HOT", "BURNING", "FIERY", "INFERNAL")
val dungeonLevelNames = listOf("ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN") val dungeonLevelNames = listOf("ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN")
@Subscribe @Subscribe
fun onCommands(it: CommandEvent) { fun registerDh(event: CommandEvent) {
if (!TConfig.enableDh) return
event.register("dh") {
thenExecute {
MC.sendCommand("warp dhub")
}
}
event.register("dn") {
thenExecute {
MC.sendChat(tr("firmament.quickwarp.deez-nutz", "Warping to... Deez Nuts!").grey())
MC.sendCommand("warp dhub")
}
}
}
@Subscribe
fun registerJoin(it: CommandEvent) {
if (!TConfig.enableJoin) return
it.register("join") { it.register("join") {
thenArgument("what", RestArgumentType) { what -> thenArgument("what", RestArgumentType) { what ->
thenExecute { thenExecute {

View File

@@ -1,21 +1,16 @@
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
@@ -27,6 +22,7 @@ object Fixes : FirmamentFeature {
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

View File

@@ -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
} }

View File

@@ -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()
val anyHovered = accScreen.focusedSlot_Firmament === hotbarSlot
|| accScreen.focusedSlot_Firmament === inventorySlot
if (!anyHovered && TConfig.slotRenderLines == SlotRenderLinesMode.NOTHING)
continue
val color = if (anyHovered)
me.shedaniel.math.Color.ofOpaque(0x00FF00)
else
me.shedaniel.math.Color.ofTransparent(0xc0a0f000.toInt())
if (TConfig.slotRenderLines == SlotRenderLinesMode.EVERYTHING || anyHovered)
event.context.drawLine( event.context.drawLine(
invX + sx, invY + sy, invX + sx, invY + sy,
hotX + sx, hotY + sy, hotX + sx, hotY + sy,
me.shedaniel.math.Color.ofOpaque(0x00FF00) 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)
} }
} }

View 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())
)
}
}
}

View File

@@ -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,6 +17,10 @@ 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(
@@ -41,21 +43,29 @@ data class 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())
val ops = getOps()
return VirtualInventory(items.map { return VirtualInventory(items.map {
it as NbtCompound it as NbtCompound
if (it.isEmpty) ItemStack.EMPTY if (it.isEmpty) ItemStack.EMPTY
else runCatching { else ErrorUtil.catch("Could not deserialize item") {
ItemStack.CODEC.parse(NbtOps.INSTANCE, it).orThrow ItemStack.CODEC.parse(ops, it).orThrow
}.getOrElse { ItemStack.EMPTY } }.or { ItemStack.EMPTY }
}) })
} }
fun getOps() = TolerantRegistriesOps(NbtOps.INSTANCE, MC.currentOrDefaultRegistries)
override fun serialize(encoder: Encoder, value: VirtualInventory) { override fun serialize(encoder: Encoder, value: VirtualInventory) {
val list = NbtList() val list = NbtList()
val ops = getOps()
value.stacks.forEach { value.stacks.forEach {
if (it.isEmpty) list.add(NbtCompound()) if (it.isEmpty) list.add(NbtCompound())
else list.add(runCatching { ItemStack.CODEC.encode(it, NbtOps.INSTANCE, NbtCompound()).orThrow } else list.add(ErrorUtil.catch("Could not serialize item") {
.getOrElse { NbtCompound() }) ItemStack.CODEC.encode(it,
ops,
NbtCompound()).orThrow
}
.or { NbtCompound() })
} }
val baos = ByteArrayOutputStream() val baos = ByteArrayOutputStream()
NbtIo.writeCompressed(NbtCompound().also { it.put(INVENTORY, list) }, baos) NbtIo.writeCompressed(NbtCompound().also { it.put(INVENTORY, list) }, baos)

View File

@@ -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)

View 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])
}
}
}
}
}
}

View File

@@ -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 {

View File

@@ -117,10 +117,21 @@ 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()))
}
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 // TODO: wait on https://youtrack.jetbrains.com/issue/KT-73434
@@ -136,6 +147,8 @@ abstract class ManagedConfig(
// default // default
// ) // )
// } // }
open fun onChange(option: ManagedOption<*>) {
}
protected fun duration( protected fun duration(
propertyName: String, propertyName: String,

View File

@@ -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

View File

@@ -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)

View File

@@ -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,7 +5,7 @@ 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()
@@ -16,8 +15,7 @@ class BetterRepoRecipeCache(val essenceRecipeProvider: EssenceRecipeProvider) :
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) }

View File

@@ -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,7 +6,7 @@ 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,
@@ -30,6 +29,8 @@ class EssenceRecipeProvider : IReloadable {
var recipes = listOf<EssenceUpgradeRecipe>() var recipes = listOf<EssenceUpgradeRecipe>()
private set private set
override fun provideExtraRecipes(): Iterable<NEURecipe> = recipes
override fun reload(repository: NEURepository) { override fun reload(repository: NEURepository) {
val recipes = mutableListOf<EssenceUpgradeRecipe>() val recipes = mutableListOf<EssenceUpgradeRecipe>()
for ((neuId, costs) in repository.constants.essenceCost.costs) { for ((neuId, costs) in repository.constants.essenceCost.costs) {

View File

@@ -0,0 +1,7 @@
package moe.nea.firmament.repo
import io.github.moulberry.repo.data.NEURecipe
interface ExtraRecipeProvider {
fun provideExtraRecipes(): Iterable<NEURecipe>
}

View File

@@ -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)
} }
return Text.literal(string).styled { this.style } Text.literal(string).setStyle(it.style)
}
} }
var job: Job? = null var job: Job? = null

View 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]
}
}
}
}

View 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)
}
}
}

View 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() ?: "") }
}
}

View File

@@ -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)
} }
} }

View File

@@ -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) }
} }

View 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
}
}

View File

@@ -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 {

View File

@@ -1,5 +1,3 @@
package moe.nea.firmament.util package moe.nea.firmament.util
import net.minecraft.util.Formatting import net.minecraft.util.Formatting
@@ -28,6 +26,10 @@ enum class LegacyFormattingCode(val label: String, val char: Char, val index: In
ITALIC("ITALIC", 'o', -1), ITALIC("ITALIC", 'o', -1),
RESET("RESET", 'r', -1); RESET("RESET", 'r', -1);
companion object {
val byCode = entries.associateBy { it.char }
}
val modern = Formatting.byCode(char)!! val modern = Formatting.byCode(char)!!
val formattingCode = "§$char" val formattingCode = "§$char"

View File

@@ -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,6 +98,7 @@ 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)

View File

@@ -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,7 +11,6 @@ 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()
@@ -20,6 +20,10 @@ object SBData {
) )
var profileId: UUID? = null var profileId: UUID? = null
/**
* Source: https://hypixel-skyblock.fandom.com/wiki/Time_Systems
*/
val hypixelTimeZone = ZoneId.of("US/Eastern")
private var hasReceivedProfile = false private var hasReceivedProfile = false
var locraw: Locraw? = null var locraw: Locraw? = null
val skyblockLocation: SkyBlockIsland? get() = locraw?.skyblockLocation val skyblockLocation: SkyBlockIsland? get() = locraw?.skyblockLocation

View File

@@ -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

View File

@@ -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()

View File

@@ -5,7 +5,7 @@ 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))

View 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()
}
}
}

View File

@@ -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).

View File

@@ -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 }

View File

@@ -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")
} }

View File

@@ -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) {

View File

@@ -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

View 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"
}

View 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"
}

View File

@@ -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.",