Add shiny pig tracker

This commit is contained in:
Linnea Gräf
2024-06-14 18:34:40 +02:00
parent cd1826a498
commit e4bd69a056
17 changed files with 548 additions and 6 deletions

View File

@@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.mixins;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.events.EntityDespawnEvent;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientWorld.class)
public class EntityDespawnPatch {
@Inject(method = "removeEntity", at = @At(value = "TAIL"))
private void onRemoved(int entityId, Entity.RemovalReason removalReason, CallbackInfo ci, @Local @Nullable Entity entity) {
EntityDespawnEvent.Companion.publish(new EntityDespawnEvent(entity, entityId, removalReason));
}
}

View File

@@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.mixins;
import moe.nea.firmament.events.EntityInteractionEvent;
import net.minecraft.client.network.ClientPlayerInteractionManager;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.EntityHitResult;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ClientPlayerInteractionManager.class)
public class EntityInteractEventPatch {
@Inject(method = "attackEntity", at = @At("HEAD"))
private void onAttack(PlayerEntity player, Entity target, CallbackInfo ci) {
EntityInteractionEvent.Companion.publish(new EntityInteractionEvent(EntityInteractionEvent.InteractionKind.ATTACK, target, Hand.MAIN_HAND));
}
@Inject(method = "interactEntity", at = @At("HEAD"))
private void onInteract(PlayerEntity player, Entity entity, Hand hand, CallbackInfoReturnable<ActionResult> cir) {
EntityInteractionEvent.Companion.publish(new EntityInteractionEvent(EntityInteractionEvent.InteractionKind.INTERACT, entity, hand));
}
@Inject(method = "interactEntityAtLocation", at = @At("HEAD"))
private void onInteractAtLocation(PlayerEntity player, Entity entity, EntityHitResult hitResult, Hand hand, CallbackInfoReturnable<ActionResult> cir) {
EntityInteractionEvent.Companion.publish(new EntityInteractionEvent(EntityInteractionEvent.InteractionKind.INTERACT_AT_LOCATION, entity, hand));
}
}

View File

@@ -19,8 +19,8 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(InGameHud.class) @Mixin(InGameHud.class)
public class HudRenderEvents { public class HudRenderEventsPatch {
@Inject(method = "renderSleepOverlay", at = @At(value = "TAIL")) @Inject(method = "renderSleepOverlay", at = @At(value = "HEAD"))
public void renderCallBack(DrawContext context, float tickDelta, CallbackInfo ci) { public void renderCallBack(DrawContext context, float tickDelta, CallbackInfo ci) {
HudRenderEvent.Companion.publish(new HudRenderEvent(context, tickDelta)); HudRenderEvent.Companion.publish(new HudRenderEvent(context, tickDelta));
} }

View File

@@ -9,7 +9,7 @@ package moe.nea.firmament.commands
import com.mojang.brigadier.CommandDispatcher import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.arguments.StringArgumentType.string import com.mojang.brigadier.arguments.StringArgumentType.string
import io.ktor.client.statement.* import io.ktor.client.statement.bodyAsText
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource
import net.minecraft.text.Text import net.minecraft.text.Text
import moe.nea.firmament.apis.UrsaManager import moe.nea.firmament.apis.UrsaManager
@@ -195,6 +195,13 @@ fun firmamentCommand() = literal("firmament") {
FairySouls.TConfig.showConfigEditor() FairySouls.TConfig.showConfigEditor()
} }
} }
thenLiteral("simulate") {
thenArgument("message", RestArgumentType) { message ->
thenExecute {
MC.instance.messageHandler.onGameMessage(Text.literal(get(message)), false)
}
}
}
thenLiteral("sbdata") { thenLiteral("sbdata") {
thenExecute { thenExecute {
source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.profile", SBData.profileId)) source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.profile", SBData.profileId))

View File

@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.events
import net.minecraft.entity.Entity
data class EntityDespawnEvent(
val entity: Entity?, val entityId: Int,
val reason: Entity.RemovalReason,
) : FirmamentEvent() {
companion object: FirmamentEventBus<EntityDespawnEvent>()
}

View File

@@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.events
import net.minecraft.entity.Entity
import net.minecraft.util.Hand
data class EntityInteractionEvent(
val kind: InteractionKind,
val entity: Entity,
val hand: Hand,
) : FirmamentEvent() {
companion object : FirmamentEventBus<EntityInteractionEvent>()
enum class InteractionKind {
/**
* Is sent when left-clicking an entity
*/
ATTACK,
/**
* Is a fallback when [INTERACT_AT_LOCATION] fails
*/
INTERACT,
/**
* Is tried first on right click
*/
INTERACT_AT_LOCATION,
}
}

View File

@@ -22,6 +22,7 @@ import moe.nea.firmament.features.debug.DeveloperFeatures
import moe.nea.firmament.features.debug.MinorTrolling import moe.nea.firmament.features.debug.MinorTrolling
import moe.nea.firmament.features.debug.PowerUserTools import moe.nea.firmament.features.debug.PowerUserTools
import moe.nea.firmament.features.diana.DianaWaypoints import moe.nea.firmament.features.diana.DianaWaypoints
import moe.nea.firmament.features.events.anniversity.AnniversaryFeatures
import moe.nea.firmament.features.fixes.CompatibliltyFeatures import moe.nea.firmament.features.fixes.CompatibliltyFeatures
import moe.nea.firmament.features.fixes.Fixes import moe.nea.firmament.features.fixes.Fixes
import moe.nea.firmament.features.inventory.CraftingOverlay import moe.nea.firmament.features.inventory.CraftingOverlay
@@ -70,6 +71,7 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
loadFeature(ChatLinks) loadFeature(ChatLinks)
loadFeature(InventoryButtons) loadFeature(InventoryButtons)
loadFeature(CompatibliltyFeatures) loadFeature(CompatibliltyFeatures)
loadFeature(AnniversaryFeatures)
loadFeature(QuickCommands) loadFeature(QuickCommands)
loadFeature(SaveCursorPosition) loadFeature(SaveCursorPosition)
loadFeature(CustomSkyBlockTextures) loadFeature(CustomSkyBlockTextures)

View File

@@ -10,12 +10,16 @@ package moe.nea.firmament.features.debug
import net.minecraft.block.SkullBlock import net.minecraft.block.SkullBlock
import net.minecraft.block.entity.SkullBlockEntity import net.minecraft.block.entity.SkullBlockEntity
import net.minecraft.component.DataComponentTypes import net.minecraft.component.DataComponentTypes
import net.minecraft.entity.Entity
import net.minecraft.entity.LivingEntity
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.item.Items import net.minecraft.item.Items
import net.minecraft.text.Text import net.minecraft.text.Text
import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.hit.EntityHitResult
import net.minecraft.util.hit.HitResult import net.minecraft.util.hit.HitResult
import moe.nea.firmament.annotations.Subscribe import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.CommandEvent
import moe.nea.firmament.events.CustomItemModelEvent import moe.nea.firmament.events.CustomItemModelEvent
import moe.nea.firmament.events.HandledScreenKeyPressedEvent import moe.nea.firmament.events.HandledScreenKeyPressedEvent
import moe.nea.firmament.events.ItemTooltipEvent import moe.nea.firmament.events.ItemTooltipEvent
@@ -41,6 +45,7 @@ object PowerUserTools : FirmamentFeature {
val copyTexturePackId by keyBindingWithDefaultUnbound("copy-texture-pack-id") val copyTexturePackId by keyBindingWithDefaultUnbound("copy-texture-pack-id")
val copyNbtData by keyBindingWithDefaultUnbound("copy-nbt-data") val copyNbtData by keyBindingWithDefaultUnbound("copy-nbt-data")
val copySkullTexture by keyBindingWithDefaultUnbound("copy-skull-texture") val copySkullTexture by keyBindingWithDefaultUnbound("copy-skull-texture")
val copyEntityData by keyBindingWithDefaultUnbound("entity-data")
} }
override val config override val config
@@ -65,6 +70,37 @@ object PowerUserTools : FirmamentFeature {
} }
} }
fun debugFormat(itemStack: ItemStack): Text {
return Text.literal(itemStack.skyBlockId?.toString() ?: itemStack.toString())
}
@Subscribe
fun onEntityInfo(event: WorldKeyboardEvent) {
if (!event.matches(TConfig.copyEntityData)) return
val target = (MC.instance.crosshairTarget as? EntityHitResult)?.entity
if (target == null) {
MC.sendChat(Text.translatable("firmament.poweruser.entity.fail"))
return
}
showEntity(target)
}
fun showEntity(target: Entity) {
MC.sendChat(Text.translatable("firmament.poweruser.entity.type", target.type))
MC.sendChat(Text.translatable("firmament.poweruser.entity.name", target.name))
if (target is LivingEntity) {
MC.sendChat(Text.translatable("firmament.poweruser.entity.armor"))
for (armorItem in target.armorItems) {
MC.sendChat(Text.translatable("firmament.poweruser.entity.armor.item", debugFormat(armorItem)))
}
}
MC.sendChat(Text.stringifiedTranslatable("firmament.poweruser.entity.passengers", target.passengerList.size))
target.passengerList.forEach {
showEntity(target)
}
}
@Subscribe @Subscribe
fun copyInventoryInfo(it: HandledScreenKeyPressedEvent) { fun copyInventoryInfo(it: HandledScreenKeyPressedEvent) {
if (it.screen !is AccessorHandledScreen) return if (it.screen !is AccessorHandledScreen) return

View File

@@ -0,0 +1,229 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.features.events.anniversity
import io.github.notenoughupdates.moulconfig.observer.ObservableList
import io.github.notenoughupdates.moulconfig.xml.Bind
import moe.nea.jarvis.api.Point
import kotlin.time.Duration.Companion.seconds
import net.minecraft.entity.passive.PigEntity
import net.minecraft.util.math.BlockPos
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.EntityInteractionEvent
import moe.nea.firmament.events.ProcessChatEvent
import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.events.WorldReadyEvent
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.gui.hud.MoulConfigHud
import moe.nea.firmament.rei.SBItemEntryDefinition
import moe.nea.firmament.repo.ItemNameLookup
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SHORT_NUMBER_FORMAT
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.parseShortNumber
import moe.nea.firmament.util.useMatch
object AnniversaryFeatures : FirmamentFeature {
override val identifier: String
get() = "anniversary"
object TConfig : ManagedConfig(identifier) {
val enableShinyPigTracker by toggle("shiny-pigs") {true}
val trackPigCooldown by position("pig-hud", 200, 300) { Point(0.1, 0.2) }
}
override val config: ManagedConfig?
get() = TConfig
data class ClickedPig(
val clickedAt: TimeMark,
val startLocation: BlockPos,
val pigEntity: PigEntity
) {
@Bind("timeLeft")
fun getTimeLeft(): Double = 1 - clickedAt.passedTime() / pigDuration
}
val clickedPigs = ObservableList<ClickedPig>(mutableListOf())
var lastClickedPig: PigEntity? = null
val pigDuration = 90.seconds
@Subscribe
fun onTick(event: TickEvent) {
clickedPigs.removeIf { it.clickedAt.passedTime() > pigDuration }
}
val pattern = "SHINY! You extracted (?<reward>.*) from the piglet's orb!".toPattern()
@Subscribe
fun onChat(event: ProcessChatEvent) {
if(!TConfig.enableShinyPigTracker)return
if (event.unformattedString == "Oink! Bring the pig back to the Shiny Orb!") {
val pig = lastClickedPig ?: return
// TODO: store proper location based on the orb location, maybe
val startLocation = pig.blockPos ?: return
clickedPigs.add(ClickedPig(TimeMark.now(), startLocation, pig))
lastClickedPig = null
}
if (event.unformattedString == "SHINY! The orb is charged! Click on it for loot!") {
val player = MC.player ?: return
val lowest =
clickedPigs.minByOrNull { it.startLocation.getSquaredDistance(player.pos) } ?: return
clickedPigs.remove(lowest)
}
pattern.useMatch(event.unformattedString) {
val reward = group("reward")
val parsedReward = parseReward(reward)
addReward(parsedReward)
PigCooldown.rewards.atOnce {
PigCooldown.rewards.clear()
rewards.mapTo(PigCooldown.rewards) { PigCooldown.DisplayReward(it) }
}
}
}
fun addReward(reward: Reward) {
val it = rewards.listIterator()
while (it.hasNext()) {
val merged = reward.mergeWith(it.next()) ?: continue
it.set(merged)
return
}
rewards.add(reward)
}
val rewards = mutableListOf<Reward>()
fun <T> ObservableList<T>.atOnce(block: () -> Unit) {
val oldObserver = observer
observer = null
block()
observer = oldObserver
update()
}
sealed interface Reward {
fun mergeWith(other: Reward): Reward?
data class EXP(val amount: Double, val skill: String) : Reward {
override fun mergeWith(other: Reward): Reward? {
if (other is EXP && other.skill == skill)
return EXP(amount + other.amount, skill)
return null
}
}
data class Coins(val amount: Double) : Reward {
override fun mergeWith(other: Reward): Reward? {
if (other is Coins)
return Coins(other.amount + amount)
return null
}
}
data class Items(val amount: Int, val item: SkyblockId) : Reward {
override fun mergeWith(other: Reward): Reward? {
if (other is Items && other.item == item)
return Items(amount + other.amount, item)
return null
}
}
data class Unknown(val text: String) : Reward {
override fun mergeWith(other: Reward): Reward? {
return null
}
}
}
val expReward = "\\+(?<exp>$SHORT_NUMBER_FORMAT) (?<kind>[^ ]+) XP".toPattern()
val coinReward = "\\+(?<amount>$SHORT_NUMBER_FORMAT) coins".toPattern()
val itemReward = "(?:(?<amount>[0-9]+)x )?(?<name>.*)".toPattern()
fun parseReward(string: String): Reward {
expReward.useMatch<Unit>(string) {
val exp = parseShortNumber(group("exp"))
val kind = group("kind")
return Reward.EXP(exp, kind)
}
coinReward.useMatch<Unit>(string) {
val coins = parseShortNumber(group("amount"))
return Reward.Coins(coins)
}
itemReward.useMatch(string) {
val amount = group("amount")?.toIntOrNull() ?: 1
val name = group("name")
val item = ItemNameLookup.guessItemByName(name, false) ?: return@useMatch
return Reward.Items(amount, item)
}
return Reward.Unknown(string)
}
@Subscribe
fun onWorldClear(event: WorldReadyEvent) {
lastClickedPig = null
clickedPigs.clear()
}
@Subscribe
fun onEntityClick(event: EntityInteractionEvent) {
if (event.entity is PigEntity) {
lastClickedPig = event.entity
}
}
@Subscribe
fun init(event: WorldReadyEvent) {
PigCooldown.forceInit()
}
object PigCooldown : MoulConfigHud("anniversary_pig", TConfig.trackPigCooldown) {
override fun shouldRender(): Boolean {
return clickedPigs.isNotEmpty() && TConfig.enableShinyPigTracker
}
@Bind("pigs")
fun getPigs() = clickedPigs
class DisplayReward(val backedBy: Reward) {
@Bind
fun count(): String {
return when (backedBy) {
is Reward.Coins -> backedBy.amount
is Reward.EXP -> backedBy.amount
is Reward.Items -> backedBy.amount
is Reward.Unknown -> 0
}.toString()
}
val itemStack = if (backedBy is Reward.Items) {
SBItemEntryDefinition.getEntry(backedBy.item, backedBy.amount)
} else {
SBItemEntryDefinition.getEntry(SkyblockId.NULL)
}
@Bind
fun name(): String {
return when (backedBy) {
is Reward.Coins -> "Coins"
is Reward.EXP -> backedBy.skill
is Reward.Items -> itemStack.value.asItemStack().name.string
is Reward.Unknown -> backedBy.text
}
}
@Bind
fun isKnown() = backedBy !is Reward.Unknown
}
@get:Bind("rewards")
val rewards = ObservableList<DisplayReward>(mutableListOf())
}
}

View File

@@ -58,7 +58,7 @@ class BarComponent(
ScreenDrawing.texturedRect(context, x, y, 4, 8, texture, emptyColor.color) ScreenDrawing.texturedRect(context, x, y, 4, 8, texture, emptyColor.color)
return return
} }
val increasePerPixel = (sectionEnd - sectionStart / 4) val increasePerPixel = (sectionEnd - sectionStart) / width
var valueAtPixel = sectionStart var valueAtPixel = sectionStart
for (i in (0 until width)) { for (i in (0 until width)) {
ScreenDrawing.texturedRect( ScreenDrawing.texturedRect(

View File

@@ -30,11 +30,16 @@ abstract class MoulConfigHud(
private var fragment: GuiContext? = null private var fragment: GuiContext? = null
fun forceInit() {
}
open fun shouldRender(): Boolean { open fun shouldRender(): Boolean {
return true return true
} }
init { init {
require(name.matches("^[a-z_/]+$".toRegex()))
HudRenderEvent.subscribe { HudRenderEvent.subscribe {
if (!shouldRender()) return@subscribe if (!shouldRender()) return@subscribe
val renderContext = componentWrapper.createContext(it.context) val renderContext = componentWrapper.createContext(it.context)

View File

@@ -0,0 +1,103 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
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 java.util.NavigableMap
import java.util.TreeMap
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.removeColorCodes
import moe.nea.firmament.util.skyblockId
object ItemNameLookup : IReloadable {
fun getItemNameChunks(name: String): Set<String> {
return name.removeColorCodes().split(" ").filterTo(mutableSetOf()) { it.isNotBlank() }
}
var nameMap: NavigableMap<String, out Set<SkyblockId>> = TreeMap()
override fun reload(repository: NEURepository) {
val nameMap = TreeMap<String, MutableSet<SkyblockId>>()
repository.items.items.values.forEach { item ->
getAllNamesForItem(item).forEach { name ->
val chunks = getItemNameChunks(name)
chunks.forEach { chunk ->
val set = nameMap.getOrPut(chunk, ::mutableSetOf)
set.add(item.skyblockId)
}
}
}
this.nameMap = nameMap
}
fun getAllNamesForItem(item: NEUItem): Set<String> {
val names = mutableSetOf<String>()
names.add(item.displayName)
if (item.displayName.contains("Enchanted Book")) {
val enchantName = item.lore.firstOrNull()
if (enchantName != null) {
names.add(enchantName)
}
}
return names
}
fun findItemCandidatesByName(name: String): MutableSet<SkyblockId> {
val candidates = mutableSetOf<SkyblockId>()
for (chunk in getItemNameChunks(name)) {
val set = nameMap[chunk] ?: emptySet()
candidates.addAll(set)
}
return candidates
}
fun guessItemByName(
/**
* The display name of the item. Color codes will be ignored.
*/
name: String,
/**
* Whether the [name] may contain other text, such as reforges, master stars and such.
*/
mayBeMangled: Boolean
): SkyblockId? {
val cleanName = name.removeColorCodes()
return findBestItemFromCandidates(
findItemCandidatesByName(cleanName),
cleanName,
true
)
}
fun findBestItemFromCandidates(
candidates: Iterable<SkyblockId>,
name: String, mayBeMangled: Boolean
): SkyblockId? {
val expectedClean = name.removeColorCodes()
var bestMatch: SkyblockId? = null
var bestMatchLength = -1
for (candidate in candidates) {
val item = RepoManager.getNEUItem(candidate) ?: continue
for (name in getAllNamesForItem(item)) {
val actualClean = name.removeColorCodes()
val matches = if (mayBeMangled) expectedClean == actualClean
else expectedClean.contains(actualClean)
if (!matches) continue
if (actualClean.length > bestMatchLength) {
bestMatch = candidate
bestMatchLength = actualClean.length
}
}
}
return bestMatch
}
}

View File

@@ -63,6 +63,7 @@ object RepoManager {
val neuRepo: NEURepository = NEURepository.of(RepoDownloadManager.repoSavedLocation).apply { val neuRepo: NEURepository = NEURepository.of(RepoDownloadManager.repoSavedLocation).apply {
registerReloadListener(ItemCache) registerReloadListener(ItemCache)
registerReloadListener(ExpLadders) registerReloadListener(ExpLadders)
registerReloadListener(ItemNameLookup)
registerReloadListener { registerReloadListener {
Firmament.coroutineScope.launch(MinecraftDispatcher) { Firmament.coroutineScope.launch(MinecraftDispatcher) {
if (!trySendClientboundUpdateRecipesPacket()) { if (!trySendClientboundUpdateRecipesPacket()) {

View File

@@ -18,7 +18,7 @@ import org.w3c.dom.Element
import moe.nea.firmament.gui.BarComponent import moe.nea.firmament.gui.BarComponent
object MoulConfigUtils { object MoulConfigUtils {
val firmUrl = "http://nea.moe/Firmament" val firmUrl = "http://firmament.nea.moe/moulconfig"
val universe = XMLUniverse.getDefaultUniverse().also { uni -> val universe = XMLUniverse.getDefaultUniverse().also { uni ->
uni.registerMapper(java.awt.Color::class.java) { uni.registerMapper(java.awt.Color::class.java) {
if (it.startsWith("#")) { if (it.startsWith("#")) {

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
SPDX-License-Identifier: GPL-3.0-or-later
-->
<Root xmlns="http://notenoughupdates.org/moulconfig" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://notenoughupdates.org/moulconfig https://raw.githubusercontent.com/NotEnoughUpdates/MoulConfig/master/MoulConfig.xsd"
xmlns:firm="http://firmament.nea.moe/moulconfig"
>
<Column>
<Array data="@pigs">
<Row>
<Text text="Pig: "/>
<firm:Bar progress="@timeLeft" total="1" fillColor="#ffb6c1" emptyColor="#db7093"/>
</Row>
</Array>
<Text text="Profits:"/>
<Array data="@rewards">
<When condition="@isKnown">
<Row>
<Text text="@count"/>
<Text text="x "/>
<Text text="@name"/>
</Row>
<Row>
<Text text="Unknown reward: "/>
<Text text="@name"/>
</Row>
</When>
</Array>
</Column>
</Root>

View File

@@ -7,7 +7,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
--> -->
<Root xmlns="http://notenoughupdates.org/moulconfig" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <Root xmlns="http://notenoughupdates.org/moulconfig" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:firm="http://nea.moe/Firmament" xmlns:firm="http://firmament.nea.moe/moulconfig"
xsi:schemaLocation="http://notenoughupdates.org/moulconfig https://raw.githubusercontent.com/NotEnoughUpdates/MoulConfig/master/MoulConfig.xsd"> xsi:schemaLocation="http://notenoughupdates.org/moulconfig https://raw.githubusercontent.com/NotEnoughUpdates/MoulConfig/master/MoulConfig.xsd">
<Column> <Column>
<Row> <Row>

View File

@@ -13,6 +13,15 @@
"firmament.command.waypoint.remove.error": "Could not find waypoint with that index to delete.", "firmament.command.waypoint.remove.error": "Could not find waypoint with that index to delete.",
"firmament.command.waypoint.skip.error": "Could not skip a waypoint. Are you in ordered waypoint mode with waypoints loaded?", "firmament.command.waypoint.skip.error": "Could not skip a waypoint. Are you in ordered waypoint mode with waypoints loaded?",
"firmament.command.waypoint.skip": "Skipped 1 waypoint", "firmament.command.waypoint.skip": "Skipped 1 waypoint",
"firmament.poweruser.entity.fail": "No entity found under cursor",
"firmament.poweruser.entity.type": "Entity Type: %s",
"firmament.poweruser.entity.name": "Entity Name: %s",
"firmament.poweruser.entity.armor": "Entity Armor:",
"firmament.poweruser.entity.armor.item": " - %s",
"firmament.poweruser.entity.passengers": "%s Passengers",
"firmament.config.anniversary": "Anniversary Features",
"firmament.config.anniversary.shiny-pigs": "Shiny Pigs Tracker",
"firmament.config.anniversary.pig-hud": "Pig Tracker Hud",
"firmament.pristine-profit.collection": "Collection: %s/h", "firmament.pristine-profit.collection": "Collection: %s/h",
"firmament.pristine-profit.money": "Money: %s/h", "firmament.pristine-profit.money": "Money: %s/h",
"firmament.toggle.true": "On", "firmament.toggle.true": "On",
@@ -156,6 +165,7 @@
"firmament.modapi.event": "Received mod API event: %s", "firmament.modapi.event": "Received mod API event: %s",
"firmament.config.power-user.copy-texture-pack-id": "Copy Texture Pack Id", "firmament.config.power-user.copy-texture-pack-id": "Copy Texture Pack Id",
"firmament.config.power-user.copy-skull-texture": "Copy Placed Skull Id", "firmament.config.power-user.copy-skull-texture": "Copy Placed Skull Id",
"firmament.config.power-user.entity-data": "Show Entity Data",
"firmament.config.power-user.copy-nbt-data": "Copy NBT data", "firmament.config.power-user.copy-nbt-data": "Copy NBT data",
"firmament.config.power-user": "Power Users", "firmament.config.power-user": "Power Users",
"firmament.tooltip.skyblockid": "SkyBlock Id: %s", "firmament.tooltip.skyblockid": "SkyBlock Id: %s",