Make pickaxe ability display use AbilityUtils
[no changelog]
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
package moe.nea.firmament
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher
|
||||
@@ -33,7 +31,6 @@ import kotlinx.coroutines.plus
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import net.minecraft.client.render.chunk.SectionBuilder
|
||||
import net.minecraft.command.CommandRegistryAccess
|
||||
import net.minecraft.util.Identifier
|
||||
import moe.nea.firmament.commands.registerFirmamentCommand
|
||||
@@ -51,98 +48,98 @@ import moe.nea.firmament.util.SBData
|
||||
import moe.nea.firmament.util.data.IDataHolder
|
||||
|
||||
object Firmament {
|
||||
const val MOD_ID = "firmament"
|
||||
const val MOD_ID = "firmament"
|
||||
|
||||
val DEBUG = System.getProperty("firmament.debug") == "true"
|
||||
val DATA_DIR: Path = Path.of(".firmament").also { Files.createDirectories(it) }
|
||||
val CONFIG_DIR: Path = Path.of("config/firmament").also { Files.createDirectories(it) }
|
||||
val logger: Logger = LogManager.getLogger("Firmament")
|
||||
private val metadata: ModMetadata by lazy {
|
||||
FabricLoader.getInstance().getModContainer(MOD_ID).orElseThrow().metadata
|
||||
}
|
||||
val version: Version by lazy { metadata.version }
|
||||
val DEBUG = System.getProperty("firmament.debug") == "true"
|
||||
val DATA_DIR: Path = Path.of(".firmament").also { Files.createDirectories(it) }
|
||||
val CONFIG_DIR: Path = Path.of("config/firmament").also { Files.createDirectories(it) }
|
||||
val logger: Logger = LogManager.getLogger("Firmament")
|
||||
private val metadata: ModMetadata by lazy {
|
||||
FabricLoader.getInstance().getModContainer(MOD_ID).orElseThrow().metadata
|
||||
}
|
||||
val version: Version by lazy { metadata.version }
|
||||
|
||||
val json = Json {
|
||||
prettyPrint = DEBUG
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
encodeDefaults = true
|
||||
}
|
||||
val json = Json {
|
||||
prettyPrint = DEBUG
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
encodeDefaults = true
|
||||
}
|
||||
|
||||
val httpClient by lazy {
|
||||
HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json(json)
|
||||
}
|
||||
install(ContentEncoding) {
|
||||
gzip()
|
||||
deflate()
|
||||
}
|
||||
install(UserAgent) {
|
||||
agent = "Firmament/$version"
|
||||
}
|
||||
if (DEBUG)
|
||||
install(Logging) {
|
||||
level = LogLevel.INFO
|
||||
}
|
||||
install(HttpCache)
|
||||
}
|
||||
}
|
||||
val httpClient by lazy {
|
||||
HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json(json)
|
||||
}
|
||||
install(ContentEncoding) {
|
||||
gzip()
|
||||
deflate()
|
||||
}
|
||||
install(UserAgent) {
|
||||
agent = "Firmament/$version"
|
||||
}
|
||||
if (DEBUG)
|
||||
install(Logging) {
|
||||
level = LogLevel.INFO
|
||||
}
|
||||
install(HttpCache)
|
||||
}
|
||||
}
|
||||
|
||||
val globalJob = Job()
|
||||
val coroutineScope =
|
||||
CoroutineScope(EmptyCoroutineContext + CoroutineName("Firmament")) + SupervisorJob(globalJob)
|
||||
val globalJob = Job()
|
||||
val coroutineScope =
|
||||
CoroutineScope(EmptyCoroutineContext + CoroutineName("Firmament")) + SupervisorJob(globalJob)
|
||||
|
||||
private fun registerCommands(
|
||||
dispatcher: CommandDispatcher<FabricClientCommandSource>,
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
ctx: CommandRegistryAccess
|
||||
) {
|
||||
registerFirmamentCommand(dispatcher)
|
||||
CommandEvent.publish(CommandEvent(dispatcher, ctx, MC.networkHandler?.commandDispatcher))
|
||||
}
|
||||
private fun registerCommands(
|
||||
dispatcher: CommandDispatcher<FabricClientCommandSource>,
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
ctx: CommandRegistryAccess
|
||||
) {
|
||||
registerFirmamentCommand(dispatcher)
|
||||
CommandEvent.publish(CommandEvent(dispatcher, ctx, MC.networkHandler?.commandDispatcher))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun onInitialize() {
|
||||
}
|
||||
@JvmStatic
|
||||
fun onInitialize() {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun onClientInitialize() {
|
||||
FeatureManager.subscribeEvents()
|
||||
var tick = 0
|
||||
ClientTickEvents.END_CLIENT_TICK.register(ClientTickEvents.EndTick { instance ->
|
||||
TickEvent.publish(TickEvent(tick++))
|
||||
})
|
||||
IDataHolder.registerEvents()
|
||||
RepoManager.initialize()
|
||||
SBData.init()
|
||||
FeatureManager.autoload()
|
||||
HypixelStaticData.spawnDataCollectionLoop()
|
||||
ClientCommandRegistrationCallback.EVENT.register(this::registerCommands)
|
||||
ClientLifecycleEvents.CLIENT_STARTED.register(ClientLifecycleEvents.ClientStarted {
|
||||
ClientStartedEvent.publish(ClientStartedEvent())
|
||||
})
|
||||
ClientLifecycleEvents.CLIENT_STOPPING.register(ClientLifecycleEvents.ClientStopping {
|
||||
logger.info("Shutting down Firmament coroutines")
|
||||
globalJob.cancel()
|
||||
})
|
||||
registerFirmamentEvents()
|
||||
ItemTooltipCallback.EVENT.register { stack, context, type, lines ->
|
||||
ItemTooltipEvent.publish(ItemTooltipEvent(stack, context, type, lines))
|
||||
}
|
||||
ScreenEvents.AFTER_INIT.register(ScreenEvents.AfterInit { client, screen, scaledWidth, scaledHeight ->
|
||||
ScreenEvents.afterRender(screen)
|
||||
.register(ScreenEvents.AfterRender { screen, drawContext, mouseX, mouseY, tickDelta ->
|
||||
ScreenRenderPostEvent.publish(ScreenRenderPostEvent(screen, mouseX, mouseY, tickDelta, drawContext))
|
||||
})
|
||||
})
|
||||
}
|
||||
@JvmStatic
|
||||
fun onClientInitialize() {
|
||||
FeatureManager.subscribeEvents()
|
||||
var tick = 0
|
||||
ClientTickEvents.END_CLIENT_TICK.register(ClientTickEvents.EndTick { instance ->
|
||||
TickEvent.publish(TickEvent(tick++))
|
||||
})
|
||||
IDataHolder.registerEvents()
|
||||
RepoManager.initialize()
|
||||
SBData.init()
|
||||
FeatureManager.autoload()
|
||||
HypixelStaticData.spawnDataCollectionLoop()
|
||||
ClientCommandRegistrationCallback.EVENT.register(this::registerCommands)
|
||||
ClientLifecycleEvents.CLIENT_STARTED.register(ClientLifecycleEvents.ClientStarted {
|
||||
ClientStartedEvent.publish(ClientStartedEvent())
|
||||
})
|
||||
ClientLifecycleEvents.CLIENT_STOPPING.register(ClientLifecycleEvents.ClientStopping {
|
||||
logger.info("Shutting down Firmament coroutines")
|
||||
globalJob.cancel()
|
||||
})
|
||||
registerFirmamentEvents()
|
||||
ItemTooltipCallback.EVENT.register { stack, context, type, lines ->
|
||||
ItemTooltipEvent.publish(ItemTooltipEvent(stack, context, type, lines))
|
||||
}
|
||||
ScreenEvents.AFTER_INIT.register(ScreenEvents.AfterInit { client, screen, scaledWidth, scaledHeight ->
|
||||
ScreenEvents.afterRender(screen)
|
||||
.register(ScreenEvents.AfterRender { screen, drawContext, mouseX, mouseY, tickDelta ->
|
||||
ScreenRenderPostEvent.publish(ScreenRenderPostEvent(screen, mouseX, mouseY, tickDelta, drawContext))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
fun identifier(path: String) = Identifier.of(MOD_ID, path)
|
||||
inline fun <reified T : Any> tryDecodeJsonFromStream(inputStream: InputStream): Result<T> {
|
||||
return runCatching {
|
||||
json.decodeFromStream<T>(inputStream)
|
||||
}
|
||||
}
|
||||
fun identifier(path: String) = Identifier.of(MOD_ID, path)
|
||||
inline fun <reified T : Any> tryDecodeJsonFromStream(inputStream: InputStream): Result<T> {
|
||||
return runCatching {
|
||||
json.decodeFromStream<T>(inputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,14 @@ package moe.nea.firmament.events
|
||||
|
||||
import net.minecraft.client.gui.DrawContext
|
||||
import net.minecraft.client.render.RenderTickCounter
|
||||
import net.minecraft.world.GameMode
|
||||
import moe.nea.firmament.util.MC
|
||||
|
||||
/**
|
||||
* Called when hud elements should be rendered, before the screen, but after the world.
|
||||
*/
|
||||
data class HudRenderEvent(val context: DrawContext, val tickDelta: RenderTickCounter) : FirmamentEvent() {
|
||||
val isRenderingHud = !MC.options.hudHidden
|
||||
val isRenderingCursor = MC.interactionManager?.currentGameMode != GameMode.SPECTATOR && isRenderingHud
|
||||
companion object : FirmamentEventBus<HudRenderEvent>()
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import moe.nea.firmament.mixins.accessor.AccessorHandledScreen
|
||||
import moe.nea.firmament.util.ClipboardUtils
|
||||
import moe.nea.firmament.util.MC
|
||||
import moe.nea.firmament.util.focusedItemStack
|
||||
import moe.nea.firmament.util.mc.SNbtFormatter.Companion.toPrettyString
|
||||
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
|
||||
import moe.nea.firmament.util.mc.loreAccordingToNbt
|
||||
import moe.nea.firmament.util.skyBlockId
|
||||
@@ -44,6 +45,7 @@ object PowerUserTools : FirmamentFeature {
|
||||
val copyLoreData by keyBindingWithDefaultUnbound("copy-lore")
|
||||
val copySkullTexture by keyBindingWithDefaultUnbound("copy-skull-texture")
|
||||
val copyEntityData by keyBindingWithDefaultUnbound("entity-data")
|
||||
val copyItemStack by keyBindingWithDefaultUnbound("copy-item-stack")
|
||||
}
|
||||
|
||||
override val config
|
||||
@@ -125,7 +127,7 @@ object PowerUserTools : FirmamentFeature {
|
||||
Pair(item, Text.stringifiedTranslatable("firmament.tooltip.copied.modelid", model.toString()))
|
||||
} else if (it.matches(TConfig.copyNbtData)) {
|
||||
// TODO: copy full nbt
|
||||
val nbt = item.get(DataComponentTypes.CUSTOM_DATA)?.nbt?.toString() ?: "<empty>"
|
||||
val nbt = item.get(DataComponentTypes.CUSTOM_DATA)?.nbt?.toPrettyString() ?: "<empty>"
|
||||
ClipboardUtils.setTextContent(nbt)
|
||||
lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.nbt"))
|
||||
} else if (it.matches(TConfig.copyLoreData)) {
|
||||
@@ -157,6 +159,9 @@ object PowerUserTools : FirmamentFeature {
|
||||
Text.stringifiedTranslatable("firmament.tooltip.copied.skull-id", skullTexture.toString())
|
||||
)
|
||||
println("Copied skull id: $skullTexture")
|
||||
} else if (it.matches(TConfig.copyItemStack)) {
|
||||
ClipboardUtils.setTextContent(item.encode(MC.currentOrDefaultRegistries).toPrettyString())
|
||||
lastCopiedStack = Pair(item, Text.stringifiedTranslatable("firmament.tooltip.copied.stack"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
package moe.nea.firmament.features.mining
|
||||
|
||||
import java.util.regex.Pattern
|
||||
@@ -30,155 +29,146 @@ import moe.nea.firmament.util.parseShortNumber
|
||||
import moe.nea.firmament.util.parseTimePattern
|
||||
import moe.nea.firmament.util.render.RenderCircleProgress
|
||||
import moe.nea.firmament.util.render.lerp
|
||||
import moe.nea.firmament.util.skyblock.AbilityUtils
|
||||
import moe.nea.firmament.util.toShedaniel
|
||||
import moe.nea.firmament.util.unformattedString
|
||||
import moe.nea.firmament.util.useMatch
|
||||
|
||||
object PickaxeAbility : FirmamentFeature {
|
||||
override val identifier: String
|
||||
get() = "pickaxe-info"
|
||||
override val identifier: String
|
||||
get() = "pickaxe-info"
|
||||
|
||||
|
||||
object TConfig : ManagedConfig(identifier) {
|
||||
val cooldownEnabled by toggle("ability-cooldown") { true }
|
||||
val cooldownScale by integer("ability-scale", 16, 64) { 16 }
|
||||
val drillFuelBar by toggle("fuel-bar") { true }
|
||||
}
|
||||
object TConfig : ManagedConfig(identifier) {
|
||||
val cooldownEnabled by toggle("ability-cooldown") { true }
|
||||
val cooldownScale by integer("ability-scale", 16, 64) { 16 }
|
||||
val drillFuelBar by toggle("fuel-bar") { true }
|
||||
}
|
||||
|
||||
var lobbyJoinTime = TimeMark.farPast()
|
||||
var lastUsage = mutableMapOf<String, TimeMark>()
|
||||
var abilityOverride: String? = null
|
||||
var defaultAbilityDurations = mutableMapOf<String, Duration>(
|
||||
"Mining Speed Boost" to 120.seconds,
|
||||
"Pickobulus" to 110.seconds,
|
||||
"Gemstone Infusion" to 140.seconds,
|
||||
"Hazardous Miner" to 140.seconds,
|
||||
"Maniac Miner" to 59.seconds,
|
||||
"Vein Seeker" to 60.seconds
|
||||
)
|
||||
var lobbyJoinTime = TimeMark.farPast()
|
||||
var lastUsage = mutableMapOf<String, TimeMark>()
|
||||
var abilityOverride: String? = null
|
||||
var defaultAbilityDurations = mutableMapOf<String, Duration>(
|
||||
"Mining Speed Boost" to 120.seconds,
|
||||
"Pickobulus" to 110.seconds,
|
||||
"Gemstone Infusion" to 140.seconds,
|
||||
"Hazardous Miner" to 140.seconds,
|
||||
"Maniac Miner" to 59.seconds,
|
||||
"Vein Seeker" to 60.seconds
|
||||
)
|
||||
|
||||
override val config: ManagedConfig
|
||||
get() = TConfig
|
||||
override val config: ManagedConfig
|
||||
get() = TConfig
|
||||
|
||||
fun getCooldownPercentage(name: String, cooldown: Duration): Double {
|
||||
val sinceLastUsage = lastUsage[name]?.passedTime() ?: Duration.INFINITE
|
||||
val sinceLobbyJoin = lobbyJoinTime.passedTime()
|
||||
fun getCooldownPercentage(name: String, cooldown: Duration): Double {
|
||||
val sinceLastUsage = lastUsage[name]?.passedTime() ?: Duration.INFINITE
|
||||
val sinceLobbyJoin = lobbyJoinTime.passedTime()
|
||||
if (SBData.skyblockLocation == SkyBlockIsland.MINESHAFT) {
|
||||
if (sinceLobbyJoin < sinceLastUsage) {
|
||||
return 1.0
|
||||
}
|
||||
}
|
||||
if (sinceLastUsage < cooldown)
|
||||
return sinceLastUsage / cooldown
|
||||
return 1.0
|
||||
}
|
||||
if (sinceLastUsage < cooldown)
|
||||
return sinceLastUsage / cooldown
|
||||
return 1.0
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onSlotClick(it: SlotClickEvent) {
|
||||
if (MC.screen?.title?.unformattedString == "Heart of the Mountain") {
|
||||
val name = it.stack.displayNameAccordingToNbt?.unformattedString ?: return
|
||||
val cooldown = it.stack.loreAccordingToNbt.firstNotNullOfOrNull {
|
||||
cooldownPattern.useMatch(it.unformattedString) {
|
||||
parseTimePattern(group("cooldown"))
|
||||
}
|
||||
} ?: return
|
||||
defaultAbilityDurations[name] = cooldown
|
||||
}
|
||||
}
|
||||
@Subscribe
|
||||
fun onSlotClick(it: SlotClickEvent) {
|
||||
if (MC.screen?.title?.unformattedString == "Heart of the Mountain") {
|
||||
val name = it.stack.displayNameAccordingToNbt.unformattedString
|
||||
val cooldown = it.stack.loreAccordingToNbt.firstNotNullOfOrNull {
|
||||
cooldownPattern.useMatch(it.unformattedString) {
|
||||
parseTimePattern(group("cooldown"))
|
||||
}
|
||||
} ?: return
|
||||
defaultAbilityDurations[name] = cooldown
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onDurabilityBar(it: DurabilityBarEvent) {
|
||||
if (!TConfig.drillFuelBar) return
|
||||
val lore = it.item.loreAccordingToNbt
|
||||
if (lore.lastOrNull()?.unformattedString?.contains("DRILL") != true) return
|
||||
val maxFuel = lore.firstNotNullOfOrNull {
|
||||
fuelPattern.useMatch(it.unformattedString) {
|
||||
parseShortNumber(group("maxFuel"))
|
||||
}
|
||||
} ?: return
|
||||
val extra = it.item.extraAttributes
|
||||
if (!extra.contains("drill_fuel")) return
|
||||
val fuel = extra.getInt("drill_fuel")
|
||||
val percentage = fuel / maxFuel.toFloat()
|
||||
it.barOverride = DurabilityBarEvent.DurabilityBar(
|
||||
lerp(
|
||||
DyeColor.RED.toShedaniel(),
|
||||
DyeColor.GREEN.toShedaniel(),
|
||||
percentage
|
||||
), percentage
|
||||
)
|
||||
}
|
||||
@Subscribe
|
||||
fun onDurabilityBar(it: DurabilityBarEvent) {
|
||||
if (!TConfig.drillFuelBar) return
|
||||
val lore = it.item.loreAccordingToNbt
|
||||
if (lore.lastOrNull()?.unformattedString?.contains("DRILL") != true) return
|
||||
val maxFuel = lore.firstNotNullOfOrNull {
|
||||
fuelPattern.useMatch(it.unformattedString) {
|
||||
parseShortNumber(group("maxFuel"))
|
||||
}
|
||||
} ?: return
|
||||
val extra = it.item.extraAttributes
|
||||
if (!extra.contains("drill_fuel")) return
|
||||
val fuel = extra.getInt("drill_fuel")
|
||||
val percentage = fuel / maxFuel.toFloat()
|
||||
it.barOverride = DurabilityBarEvent.DurabilityBar(
|
||||
lerp(
|
||||
DyeColor.RED.toShedaniel(),
|
||||
DyeColor.GREEN.toShedaniel(),
|
||||
percentage
|
||||
), percentage
|
||||
)
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onChatMessage(it: ProcessChatEvent) {
|
||||
abilityUsePattern.useMatch(it.unformattedString) {
|
||||
lastUsage[group("name")] = TimeMark.now()
|
||||
}
|
||||
abilitySwitchPattern.useMatch(it.unformattedString) {
|
||||
abilityOverride = group("ability")
|
||||
}
|
||||
}
|
||||
@Subscribe
|
||||
fun onChatMessage(it: ProcessChatEvent) {
|
||||
abilityUsePattern.useMatch(it.unformattedString) {
|
||||
lastUsage[group("name")] = TimeMark.now()
|
||||
}
|
||||
abilitySwitchPattern.useMatch(it.unformattedString) {
|
||||
abilityOverride = group("ability")
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onWorldReady(event: WorldReadyEvent) {
|
||||
lobbyJoinTime = TimeMark.now()
|
||||
abilityOverride = null
|
||||
}
|
||||
@Subscribe
|
||||
fun onWorldReady(event: WorldReadyEvent) {
|
||||
lobbyJoinTime = TimeMark.now()
|
||||
abilityOverride = null
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onProfileSwitch(event: ProfileSwitchEvent) {
|
||||
lastUsage.clear()
|
||||
}
|
||||
|
||||
val abilityUsePattern = Pattern.compile("You used your (?<name>.*) Pickaxe Ability!")
|
||||
val fuelPattern = Pattern.compile("Fuel: .*/(?<maxFuel>$SHORT_NUMBER_FORMAT)")
|
||||
val abilityUsePattern = Pattern.compile("You used your (?<name>.*) Pickaxe Ability!")
|
||||
val fuelPattern = Pattern.compile("Fuel: .*/(?<maxFuel>$SHORT_NUMBER_FORMAT)")
|
||||
val pickaxeAbilityCooldownPattern = Pattern.compile("Your pickaxe ability is on cooldown for (?<remainingCooldown>$TIME_PATTERN)\\.")
|
||||
|
||||
data class PickaxeAbilityData(
|
||||
val name: String,
|
||||
val cooldown: Duration,
|
||||
)
|
||||
data class PickaxeAbilityData(
|
||||
val name: String,
|
||||
val cooldown: Duration,
|
||||
)
|
||||
|
||||
fun getCooldownFromLore(itemStack: ItemStack): PickaxeAbilityData? {
|
||||
val lore = itemStack.loreAccordingToNbt
|
||||
if (!lore.any { it.unformattedString.contains("Breaking Power") })
|
||||
return null
|
||||
val cooldown = lore.firstNotNullOfOrNull {
|
||||
cooldownPattern.useMatch(it.unformattedString) {
|
||||
parseTimePattern(group("cooldown"))
|
||||
}
|
||||
} ?: return null
|
||||
val name = lore.firstNotNullOfOrNull {
|
||||
abilityPattern.useMatch(it.unformattedString) {
|
||||
group("name")
|
||||
}
|
||||
} ?: return null
|
||||
return PickaxeAbilityData(name, cooldown)
|
||||
}
|
||||
fun getCooldownFromLore(itemStack: ItemStack): PickaxeAbilityData? {
|
||||
val lore = itemStack.loreAccordingToNbt
|
||||
if (!lore.any { it.unformattedString.contains("Breaking Power") })
|
||||
return null
|
||||
val ability = AbilityUtils.getAbilities(itemStack).firstOrNull() ?: return null
|
||||
return PickaxeAbilityData(ability.name, ability.cooldown ?: return null)
|
||||
}
|
||||
|
||||
val cooldownPattern = Pattern.compile("Cooldown: (?<cooldown>$TIME_PATTERN)")
|
||||
val abilitySwitchPattern =
|
||||
Pattern.compile("You selected (?<ability>.*) as your Pickaxe Ability\\. This ability will apply to all of your pickaxes!")
|
||||
|
||||
val cooldownPattern = Pattern.compile("Cooldown: (?<cooldown>$TIME_PATTERN)")
|
||||
val abilityPattern = Pattern.compile("(⦾ )?Ability: (?<name>.*) {2}RIGHT CLICK")
|
||||
val abilitySwitchPattern =
|
||||
Pattern.compile("You selected (?<ability>.*) as your Pickaxe Ability\\. This ability will apply to all of your pickaxes!")
|
||||
|
||||
|
||||
@Subscribe
|
||||
fun renderHud(event: HudRenderEvent) {
|
||||
if (!TConfig.cooldownEnabled) return
|
||||
var ability = getCooldownFromLore(MC.player?.getStackInHand(Hand.MAIN_HAND) ?: return) ?: return
|
||||
defaultAbilityDurations[ability.name] = ability.cooldown
|
||||
val ao = abilityOverride
|
||||
if (ao != ability.name && ao != null) {
|
||||
ability = PickaxeAbilityData(ao, defaultAbilityDurations[ao] ?: 120.seconds)
|
||||
}
|
||||
event.context.matrices.push()
|
||||
event.context.matrices.translate(MC.window.scaledWidth / 2F, MC.window.scaledHeight / 2F, 0F)
|
||||
event.context.matrices.scale(TConfig.cooldownScale.toFloat(), TConfig.cooldownScale.toFloat(), 1F)
|
||||
RenderCircleProgress.renderCircle(
|
||||
event.context, Identifier.of("firmament", "textures/gui/circle.png"),
|
||||
getCooldownPercentage(ability.name, ability.cooldown).toFloat(),
|
||||
0f, 1f, 0f, 1f
|
||||
)
|
||||
event.context.matrices.pop()
|
||||
}
|
||||
@Subscribe
|
||||
fun renderHud(event: HudRenderEvent) {
|
||||
if (!TConfig.cooldownEnabled) return
|
||||
if (!event.isRenderingCursor) return
|
||||
var ability = getCooldownFromLore(MC.player?.getStackInHand(Hand.MAIN_HAND) ?: return) ?: return
|
||||
defaultAbilityDurations[ability.name] = ability.cooldown
|
||||
val ao = abilityOverride
|
||||
if (ao != ability.name && ao != null) {
|
||||
ability = PickaxeAbilityData(ao, defaultAbilityDurations[ao] ?: 120.seconds)
|
||||
}
|
||||
event.context.matrices.push()
|
||||
event.context.matrices.translate(MC.window.scaledWidth / 2F, MC.window.scaledHeight / 2F, 0F)
|
||||
event.context.matrices.scale(TConfig.cooldownScale.toFloat(), TConfig.cooldownScale.toFloat(), 1F)
|
||||
RenderCircleProgress.renderCircle(
|
||||
event.context, Identifier.of("firmament", "textures/gui/circle.png"),
|
||||
getCooldownPercentage(ability.name, ability.cooldown).toFloat(),
|
||||
0f, 1f, 0f, 1f
|
||||
)
|
||||
event.context.matrices.pop()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
package moe.nea.firmament.features.texturepack
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
@@ -6,120 +5,120 @@ import com.google.gson.JsonPrimitive
|
||||
import moe.nea.firmament.util.useMatch
|
||||
|
||||
abstract class NumberMatcher {
|
||||
abstract fun test(number: Number): Boolean
|
||||
abstract fun test(number: Number): Boolean
|
||||
|
||||
|
||||
companion object {
|
||||
fun parse(jsonElement: JsonElement): NumberMatcher? {
|
||||
if (jsonElement is JsonPrimitive) {
|
||||
if (jsonElement.isString) {
|
||||
val string = jsonElement.asString
|
||||
return parseRange(string) ?: parseOperator(string)
|
||||
}
|
||||
if (jsonElement.isNumber) {
|
||||
val number = jsonElement.asNumber
|
||||
val hasDecimals = (number.toString().contains("."))
|
||||
return MatchNumberExact(if (hasDecimals) number.toLong() else number.toDouble())
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
companion object {
|
||||
fun parse(jsonElement: JsonElement): NumberMatcher? {
|
||||
if (jsonElement is JsonPrimitive) {
|
||||
if (jsonElement.isString) {
|
||||
val string = jsonElement.asString
|
||||
return parseRange(string) ?: parseOperator(string)
|
||||
}
|
||||
if (jsonElement.isNumber) {
|
||||
val number = jsonElement.asNumber
|
||||
val hasDecimals = (number.toString().contains("."))
|
||||
return MatchNumberExact(if (hasDecimals) number.toLong() else number.toDouble())
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private val intervalSpec =
|
||||
"(?<beginningOpen>[\\[\\(])(?<beginning>[0-9.]+)?,(?<ending>[0-9.]+)?(?<endingOpen>[\\]\\)])"
|
||||
.toPattern()
|
||||
private val intervalSpec =
|
||||
"(?<beginningOpen>[\\[\\(])(?<beginning>[0-9.]+)?,(?<ending>[0-9.]+)?(?<endingOpen>[\\]\\)])"
|
||||
.toPattern()
|
||||
|
||||
fun parseRange(string: String): RangeMatcher? {
|
||||
intervalSpec.useMatch<Nothing>(string) {
|
||||
// Open in the set-theory sense, meaning does not include its end.
|
||||
val beginningOpen = group("beginningOpen") == "("
|
||||
val endingOpen = group("endingOpen") == ")"
|
||||
val beginning = group("beginning")?.toDouble()
|
||||
val ending = group("ending")?.toDouble()
|
||||
return RangeMatcher(beginning, !beginningOpen, ending, !endingOpen)
|
||||
}
|
||||
return null
|
||||
}
|
||||
fun parseRange(string: String): RangeMatcher? {
|
||||
intervalSpec.useMatch<Nothing>(string) {
|
||||
// Open in the set-theory sense, meaning does not include its end.
|
||||
val beginningOpen = group("beginningOpen") == "("
|
||||
val endingOpen = group("endingOpen") == ")"
|
||||
val beginning = group("beginning")?.toDouble()
|
||||
val ending = group("ending")?.toDouble()
|
||||
return RangeMatcher(beginning, !beginningOpen, ending, !endingOpen)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
enum class Operator(val operator: String) {
|
||||
LESS("<") {
|
||||
override fun matches(comparisonResult: Int): Boolean {
|
||||
return comparisonResult < 0
|
||||
}
|
||||
},
|
||||
LESS_EQUALS("<=") {
|
||||
override fun matches(comparisonResult: Int): Boolean {
|
||||
return comparisonResult <= 0
|
||||
}
|
||||
},
|
||||
GREATER(">") {
|
||||
override fun matches(comparisonResult: Int): Boolean {
|
||||
return comparisonResult > 0
|
||||
}
|
||||
},
|
||||
GREATER_EQUALS(">=") {
|
||||
override fun matches(comparisonResult: Int): Boolean {
|
||||
return comparisonResult >= 0
|
||||
}
|
||||
},
|
||||
;
|
||||
enum class Operator(val operator: String) {
|
||||
LESS("<") {
|
||||
override fun matches(comparisonResult: Int): Boolean {
|
||||
return comparisonResult < 0
|
||||
}
|
||||
},
|
||||
LESS_EQUALS("<=") {
|
||||
override fun matches(comparisonResult: Int): Boolean {
|
||||
return comparisonResult <= 0
|
||||
}
|
||||
},
|
||||
GREATER(">") {
|
||||
override fun matches(comparisonResult: Int): Boolean {
|
||||
return comparisonResult > 0
|
||||
}
|
||||
},
|
||||
GREATER_EQUALS(">=") {
|
||||
override fun matches(comparisonResult: Int): Boolean {
|
||||
return comparisonResult >= 0
|
||||
}
|
||||
},
|
||||
;
|
||||
|
||||
abstract fun matches(comparisonResult: Int): Boolean
|
||||
}
|
||||
abstract fun matches(comparisonResult: Int): Boolean
|
||||
}
|
||||
|
||||
private val operatorPattern = "(?<operator>${Operator.entries.joinToString("|") {it.operator}})(?<value>[0-9.]+)".toPattern()
|
||||
private val operatorPattern =
|
||||
"(?<operator>${Operator.entries.joinToString("|") { it.operator }})(?<value>[0-9.]+)".toPattern()
|
||||
|
||||
fun parseOperator(string: String): OperatorMatcher? {
|
||||
operatorPattern.useMatch<Nothing>(string) {
|
||||
val operatorName = group("operator")
|
||||
val operator = Operator.entries.find { it.operator == operatorName }!!
|
||||
val value = group("value").toDouble()
|
||||
return OperatorMatcher(operator, value)
|
||||
}
|
||||
return null
|
||||
}
|
||||
fun parseOperator(string: String): OperatorMatcher? {
|
||||
return operatorPattern.useMatch(string) {
|
||||
val operatorName = group("operator")
|
||||
val operator = Operator.entries.find { it.operator == operatorName }!!
|
||||
val value = group("value").toDouble()
|
||||
OperatorMatcher(operator, value)
|
||||
}
|
||||
}
|
||||
|
||||
data class OperatorMatcher(val operator: Operator, val value: Double) : NumberMatcher() {
|
||||
override fun test(number: Number): Boolean {
|
||||
return operator.matches(number.toDouble().compareTo(value))
|
||||
}
|
||||
}
|
||||
data class OperatorMatcher(val operator: Operator, val value: Double) : NumberMatcher() {
|
||||
override fun test(number: Number): Boolean {
|
||||
return operator.matches(number.toDouble().compareTo(value))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class MatchNumberExact(val number: Number) : NumberMatcher() {
|
||||
override fun test(number: Number): Boolean {
|
||||
return when (this.number) {
|
||||
is Double -> number.toDouble() == this.number.toDouble()
|
||||
else -> number.toLong() == this.number.toLong()
|
||||
}
|
||||
}
|
||||
}
|
||||
data class MatchNumberExact(val number: Number) : NumberMatcher() {
|
||||
override fun test(number: Number): Boolean {
|
||||
return when (this.number) {
|
||||
is Double -> number.toDouble() == this.number.toDouble()
|
||||
else -> number.toLong() == this.number.toLong()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class RangeMatcher(
|
||||
val beginning: Double?,
|
||||
val beginningInclusive: Boolean,
|
||||
val ending: Double?,
|
||||
val endingInclusive: Boolean,
|
||||
) : NumberMatcher() {
|
||||
override fun test(number: Number): Boolean {
|
||||
val value = number.toDouble()
|
||||
if (beginning != null) {
|
||||
if (beginningInclusive) {
|
||||
if (value < beginning) return false
|
||||
} else {
|
||||
if (value <= beginning) return false
|
||||
}
|
||||
}
|
||||
if (ending != null) {
|
||||
if (endingInclusive) {
|
||||
if (value > ending) return false
|
||||
} else {
|
||||
if (value >= ending) return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
data class RangeMatcher(
|
||||
val beginning: Double?,
|
||||
val beginningInclusive: Boolean,
|
||||
val ending: Double?,
|
||||
val endingInclusive: Boolean,
|
||||
) : NumberMatcher() {
|
||||
override fun test(number: Number): Boolean {
|
||||
val value = number.toDouble()
|
||||
if (beginning != null) {
|
||||
if (beginningInclusive) {
|
||||
if (value < beginning) return false
|
||||
} else {
|
||||
if (value <= beginning) return false
|
||||
}
|
||||
}
|
||||
if (ending != null) {
|
||||
if (endingInclusive) {
|
||||
if (value > ending) return false
|
||||
} else {
|
||||
if (value >= ending) return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
16
src/main/kotlin/util/ErrorUtil.kt
Normal file
16
src/main/kotlin/util/ErrorUtil.kt
Normal file
@@ -0,0 +1,16 @@
|
||||
package moe.nea.firmament.util
|
||||
|
||||
import moe.nea.firmament.Firmament
|
||||
|
||||
object ErrorUtil {
|
||||
var aggressiveErrors = run {
|
||||
Thread.currentThread().stackTrace.any { it.className.startsWith("org.junit.") } || Firmament.DEBUG
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE") // Suppressed since i want the logger to not pick up the ErrorUtil stack-frame
|
||||
inline fun softError(message: String) {
|
||||
if (aggressiveErrors) error(message)
|
||||
else Firmament.logger.error(message)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,7 +4,9 @@ import io.github.moulberry.repo.data.Coordinate
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.gui.screen.ingame.HandledScreen
|
||||
import net.minecraft.client.option.GameOptions
|
||||
import net.minecraft.client.render.WorldRenderer
|
||||
import net.minecraft.item.Item
|
||||
import net.minecraft.network.packet.c2s.play.CommandExecutionC2SPacket
|
||||
import net.minecraft.registry.BuiltinRegistries
|
||||
import net.minecraft.registry.RegistryKeys
|
||||
@@ -16,79 +18,82 @@ import moe.nea.firmament.events.TickEvent
|
||||
|
||||
object MC {
|
||||
|
||||
private val messageQueue = ConcurrentLinkedQueue<Text>()
|
||||
private val messageQueue = ConcurrentLinkedQueue<Text>()
|
||||
|
||||
init {
|
||||
TickEvent.subscribe("MC:push") {
|
||||
while (true) {
|
||||
inGameHud.chatHud.addMessage(messageQueue.poll() ?: break)
|
||||
}
|
||||
while (true) {
|
||||
(nextTickTodos.poll() ?: break).invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
init {
|
||||
TickEvent.subscribe("MC:push") {
|
||||
while (true) {
|
||||
inGameHud.chatHud.addMessage(messageQueue.poll() ?: break)
|
||||
}
|
||||
while (true) {
|
||||
(nextTickTodos.poll() ?: break).invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sendChat(text: Text) {
|
||||
if (instance.isOnThread)
|
||||
inGameHud.chatHud.addMessage(text)
|
||||
else
|
||||
messageQueue.add(text)
|
||||
}
|
||||
fun sendChat(text: Text) {
|
||||
if (instance.isOnThread)
|
||||
inGameHud.chatHud.addMessage(text)
|
||||
else
|
||||
messageQueue.add(text)
|
||||
}
|
||||
|
||||
fun sendServerCommand(command: String) {
|
||||
val nh = player?.networkHandler ?: return
|
||||
nh.sendPacket(
|
||||
CommandExecutionC2SPacket(
|
||||
command,
|
||||
)
|
||||
)
|
||||
}
|
||||
fun sendServerCommand(command: String) {
|
||||
val nh = player?.networkHandler ?: return
|
||||
nh.sendPacket(
|
||||
CommandExecutionC2SPacket(
|
||||
command,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun sendServerChat(text: String) {
|
||||
player?.networkHandler?.sendChatMessage(text)
|
||||
}
|
||||
fun sendServerChat(text: String) {
|
||||
player?.networkHandler?.sendChatMessage(text)
|
||||
}
|
||||
|
||||
fun sendCommand(command: String) {
|
||||
player?.networkHandler?.sendCommand(command)
|
||||
}
|
||||
fun sendCommand(command: String) {
|
||||
player?.networkHandler?.sendCommand(command)
|
||||
}
|
||||
|
||||
fun onMainThread(block: () -> Unit) {
|
||||
if (instance.isOnThread)
|
||||
block()
|
||||
else
|
||||
instance.send(block)
|
||||
}
|
||||
fun onMainThread(block: () -> Unit) {
|
||||
if (instance.isOnThread)
|
||||
block()
|
||||
else
|
||||
instance.send(block)
|
||||
}
|
||||
|
||||
private val nextTickTodos = ConcurrentLinkedQueue<() -> Unit>()
|
||||
fun nextTick(function: () -> Unit) {
|
||||
nextTickTodos.add(function)
|
||||
}
|
||||
private val nextTickTodos = ConcurrentLinkedQueue<() -> Unit>()
|
||||
fun nextTick(function: () -> Unit) {
|
||||
nextTickTodos.add(function)
|
||||
}
|
||||
|
||||
|
||||
inline val resourceManager get() = (instance.resourceManager as ReloadableResourceManagerImpl)
|
||||
inline val worldRenderer: WorldRenderer get() = instance.worldRenderer
|
||||
inline val networkHandler get() = player?.networkHandler
|
||||
inline val instance get() = MinecraftClient.getInstance()
|
||||
inline val keyboard get() = instance.keyboard
|
||||
inline val textureManager get() = instance.textureManager
|
||||
inline val inGameHud get() = instance.inGameHud
|
||||
inline val font get() = instance.textRenderer
|
||||
inline val soundManager get() = instance.soundManager
|
||||
inline val player get() = instance.player
|
||||
inline val camera get() = instance.cameraEntity
|
||||
inline val guiAtlasManager get() = instance.guiAtlasManager
|
||||
inline val world get() = instance.world
|
||||
inline var screen
|
||||
get() = instance.currentScreen
|
||||
set(value) = instance.setScreen(value)
|
||||
inline val handledScreen: HandledScreen<*>? get() = instance.currentScreen as? HandledScreen<*>
|
||||
inline val window get() = instance.window
|
||||
inline val currentRegistries: RegistryWrapper.WrapperLookup? get() = world?.registryManager
|
||||
val defaultRegistries: RegistryWrapper.WrapperLookup = BuiltinRegistries.createWrapperLookup()
|
||||
val defaultItems = defaultRegistries.getWrapperOrThrow(RegistryKeys.ITEM)
|
||||
inline val resourceManager get() = (instance.resourceManager as ReloadableResourceManagerImpl)
|
||||
inline val worldRenderer: WorldRenderer get() = instance.worldRenderer
|
||||
inline val networkHandler get() = player?.networkHandler
|
||||
inline val instance get() = MinecraftClient.getInstance()
|
||||
inline val keyboard get() = instance.keyboard
|
||||
inline val interactionManager get() = instance.interactionManager
|
||||
inline val textureManager get() = instance.textureManager
|
||||
inline val options get() = instance.options
|
||||
inline val inGameHud get() = instance.inGameHud
|
||||
inline val font get() = instance.textRenderer
|
||||
inline val soundManager get() = instance.soundManager
|
||||
inline val player get() = instance.player
|
||||
inline val camera get() = instance.cameraEntity
|
||||
inline val guiAtlasManager get() = instance.guiAtlasManager
|
||||
inline val world get() = instance.world
|
||||
inline var screen
|
||||
get() = instance.currentScreen
|
||||
set(value) = instance.setScreen(value)
|
||||
inline val handledScreen: HandledScreen<*>? get() = instance.currentScreen as? HandledScreen<*>
|
||||
inline val window get() = instance.window
|
||||
inline val currentRegistries: RegistryWrapper.WrapperLookup? get() = world?.registryManager
|
||||
val defaultRegistries: RegistryWrapper.WrapperLookup = BuiltinRegistries.createWrapperLookup()
|
||||
inline val currentOrDefaultRegistries get() = currentRegistries ?: defaultRegistries
|
||||
val defaultItems: RegistryWrapper.Impl<Item> = defaultRegistries.getWrapperOrThrow(RegistryKeys.ITEM)
|
||||
}
|
||||
|
||||
|
||||
val Coordinate.blockPos: BlockPos
|
||||
get() = BlockPos(x, y, z)
|
||||
get() = BlockPos(x, y, z)
|
||||
|
||||
@@ -1,44 +1,52 @@
|
||||
|
||||
|
||||
package moe.nea.firmament.util
|
||||
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
class TimeMark private constructor(private val timeMark: Long) : Comparable<TimeMark> {
|
||||
fun passedTime() = if (timeMark == 0L) Duration.INFINITE else (System.currentTimeMillis() - timeMark).milliseconds
|
||||
fun passedTime() =
|
||||
if (timeMark == 0L) Duration.INFINITE
|
||||
else (System.currentTimeMillis() - timeMark).milliseconds
|
||||
|
||||
operator fun minus(other: TimeMark): Duration {
|
||||
if (other.timeMark == timeMark)
|
||||
return 0.milliseconds
|
||||
if (other.timeMark == 0L)
|
||||
return Duration.INFINITE
|
||||
if (timeMark == 0L)
|
||||
return -Duration.INFINITE
|
||||
return (timeMark - other.timeMark).milliseconds
|
||||
}
|
||||
fun passedAt(fakeNow: TimeMark) =
|
||||
if (timeMark == 0L) Duration.INFINITE
|
||||
else (fakeNow.timeMark - timeMark).milliseconds
|
||||
|
||||
companion object {
|
||||
fun now() = TimeMark(System.currentTimeMillis())
|
||||
fun farPast() = TimeMark(0L)
|
||||
fun ago(timeDelta: Duration): TimeMark {
|
||||
if (timeDelta.isFinite()) {
|
||||
return TimeMark(System.currentTimeMillis() - timeDelta.inWholeMilliseconds)
|
||||
}
|
||||
require(timeDelta.isPositive())
|
||||
return farPast()
|
||||
}
|
||||
}
|
||||
operator fun minus(other: TimeMark): Duration {
|
||||
if (other.timeMark == timeMark)
|
||||
return 0.milliseconds
|
||||
if (other.timeMark == 0L)
|
||||
return Duration.INFINITE
|
||||
if (timeMark == 0L)
|
||||
return -Duration.INFINITE
|
||||
return (timeMark - other.timeMark).milliseconds
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return timeMark.hashCode()
|
||||
}
|
||||
companion object {
|
||||
fun now() = TimeMark(System.currentTimeMillis())
|
||||
fun farPast() = TimeMark(0L)
|
||||
fun ago(timeDelta: Duration): TimeMark {
|
||||
if (timeDelta.isFinite()) {
|
||||
return TimeMark(System.currentTimeMillis() - timeDelta.inWholeMilliseconds)
|
||||
}
|
||||
require(timeDelta.isPositive())
|
||||
return farPast()
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is TimeMark && other.timeMark == timeMark
|
||||
}
|
||||
override fun hashCode(): Int {
|
||||
return timeMark.hashCode()
|
||||
}
|
||||
|
||||
override fun compareTo(other: TimeMark): Int {
|
||||
return this.timeMark.compareTo(other.timeMark)
|
||||
}
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is TimeMark && other.timeMark == timeMark
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "https://time.is/$timeMark"
|
||||
}
|
||||
|
||||
override fun compareTo(other: TimeMark): Int {
|
||||
return this.timeMark.compareTo(other.timeMark)
|
||||
}
|
||||
}
|
||||
|
||||
138
src/main/kotlin/util/mc/SNbtFormatter.kt
Normal file
138
src/main/kotlin/util/mc/SNbtFormatter.kt
Normal file
@@ -0,0 +1,138 @@
|
||||
package moe.nea.firmament.util.mc
|
||||
|
||||
import net.minecraft.nbt.NbtByte
|
||||
import net.minecraft.nbt.NbtByteArray
|
||||
import net.minecraft.nbt.NbtCompound
|
||||
import net.minecraft.nbt.NbtDouble
|
||||
import net.minecraft.nbt.NbtElement
|
||||
import net.minecraft.nbt.NbtEnd
|
||||
import net.minecraft.nbt.NbtFloat
|
||||
import net.minecraft.nbt.NbtInt
|
||||
import net.minecraft.nbt.NbtIntArray
|
||||
import net.minecraft.nbt.NbtList
|
||||
import net.minecraft.nbt.NbtLong
|
||||
import net.minecraft.nbt.NbtLongArray
|
||||
import net.minecraft.nbt.NbtShort
|
||||
import net.minecraft.nbt.NbtString
|
||||
import net.minecraft.nbt.visitor.NbtElementVisitor
|
||||
|
||||
class SNbtFormatter private constructor() : NbtElementVisitor {
|
||||
private val result = StringBuilder()
|
||||
private var indent = 0
|
||||
private fun writeIndent() {
|
||||
result.append("\t".repeat(indent))
|
||||
}
|
||||
|
||||
private fun pushIndent() {
|
||||
indent++
|
||||
}
|
||||
|
||||
private fun popIndent() {
|
||||
indent--
|
||||
}
|
||||
|
||||
fun apply(element: NbtElement): StringBuilder {
|
||||
element.accept(this)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
override fun visitString(element: NbtString) {
|
||||
result.append(NbtString.escape(element.asString()))
|
||||
}
|
||||
|
||||
override fun visitByte(element: NbtByte) {
|
||||
result.append(element.numberValue()).append("b")
|
||||
}
|
||||
|
||||
override fun visitShort(element: NbtShort) {
|
||||
result.append(element.shortValue()).append("s")
|
||||
}
|
||||
|
||||
override fun visitInt(element: NbtInt) {
|
||||
result.append(element.intValue())
|
||||
}
|
||||
|
||||
override fun visitLong(element: NbtLong) {
|
||||
result.append(element.longValue()).append("L")
|
||||
}
|
||||
|
||||
override fun visitFloat(element: NbtFloat) {
|
||||
result.append(element.floatValue()).append("f")
|
||||
}
|
||||
|
||||
override fun visitDouble(element: NbtDouble) {
|
||||
result.append(element.doubleValue()).append("d")
|
||||
}
|
||||
|
||||
private fun visitArrayContents(array: List<NbtElement>) {
|
||||
array.forEachIndexed { index, element ->
|
||||
writeIndent()
|
||||
element.accept(this)
|
||||
if (array.size != index + 1) {
|
||||
result.append(",")
|
||||
}
|
||||
result.append("\n")
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeArray(arrayTypeTag: String, array: List<NbtElement>) {
|
||||
result.append("[").append(arrayTypeTag).append("\n")
|
||||
pushIndent()
|
||||
visitArrayContents(array)
|
||||
popIndent()
|
||||
writeIndent()
|
||||
result.append("]")
|
||||
|
||||
}
|
||||
|
||||
override fun visitByteArray(element: NbtByteArray) {
|
||||
writeArray("B;", element)
|
||||
}
|
||||
|
||||
override fun visitIntArray(element: NbtIntArray) {
|
||||
writeArray("I;", element)
|
||||
}
|
||||
|
||||
override fun visitLongArray(element: NbtLongArray) {
|
||||
writeArray("L;", element)
|
||||
}
|
||||
|
||||
override fun visitList(element: NbtList) {
|
||||
writeArray("", element)
|
||||
}
|
||||
|
||||
override fun visitCompound(compound: NbtCompound) {
|
||||
result.append("{\n")
|
||||
pushIndent()
|
||||
val keys = compound.keys.sorted()
|
||||
keys.forEachIndexed { index, key ->
|
||||
writeIndent()
|
||||
val element = compound[key] ?: error("Key '$key' found but not present in compound: $compound")
|
||||
val escapedName = if (key.matches(SIMPLE_NAME)) key else NbtString.escape(key)
|
||||
result.append(escapedName).append(": ")
|
||||
element.accept(this)
|
||||
if (keys.size != index + 1) {
|
||||
result.append(",")
|
||||
}
|
||||
result.append("\n")
|
||||
}
|
||||
popIndent()
|
||||
writeIndent()
|
||||
result.append("}")
|
||||
}
|
||||
|
||||
override fun visitEnd(element: NbtEnd) {
|
||||
result.append("END")
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun prettify(nbt: NbtElement): String {
|
||||
return SNbtFormatter().apply(nbt).toString()
|
||||
}
|
||||
|
||||
fun NbtElement.toPrettyString() = prettify(this)
|
||||
|
||||
private val SIMPLE_NAME = "[A-Za-z0-9._+-]+".toRegex()
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
@file:OptIn(ExperimentalTypeInference::class, ExperimentalContracts::class)
|
||||
|
||||
package moe.nea.firmament.util
|
||||
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
import org.intellij.lang.annotations.Language
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.experimental.ExperimentalTypeInference
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
@@ -10,10 +16,14 @@ import kotlin.time.Duration.Companion.seconds
|
||||
inline fun <T> String.ifMatches(regex: Regex, block: (MatchResult) -> T): T? =
|
||||
regex.matchEntire(this)?.let(block)
|
||||
|
||||
inline fun <T> Pattern.useMatch(string: String, block: Matcher.() -> T): T? =
|
||||
matcher(string)
|
||||
inline fun <T> Pattern.useMatch(string: String, block: Matcher.() -> T): T? {
|
||||
contract {
|
||||
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
return matcher(string)
|
||||
.takeIf(Matcher::matches)
|
||||
?.let(block)
|
||||
}
|
||||
|
||||
@Language("RegExp")
|
||||
val TIME_PATTERN = "[0-9]+[ms]"
|
||||
|
||||
138
src/main/kotlin/util/skyblock/AbilityUtils.kt
Normal file
138
src/main/kotlin/util/skyblock/AbilityUtils.kt
Normal file
@@ -0,0 +1,138 @@
|
||||
package moe.nea.firmament.util.skyblock
|
||||
|
||||
import kotlin.time.Duration
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.text.Text
|
||||
import moe.nea.firmament.util.ErrorUtil
|
||||
import moe.nea.firmament.util.directLiteralStringContent
|
||||
import moe.nea.firmament.util.mc.loreAccordingToNbt
|
||||
import moe.nea.firmament.util.parseShortNumber
|
||||
import moe.nea.firmament.util.parseTimePattern
|
||||
import moe.nea.firmament.util.unformattedString
|
||||
import moe.nea.firmament.util.useMatch
|
||||
|
||||
object AbilityUtils {
|
||||
data class ItemAbility(
|
||||
val name: String,
|
||||
val hasPowerScroll: Boolean,
|
||||
val activation: AbilityActivation,
|
||||
val manaCost: Int?,
|
||||
val descriptionLines: List<Text>,
|
||||
val cooldown: Duration?,
|
||||
)
|
||||
|
||||
@JvmInline
|
||||
value class AbilityActivation(
|
||||
val label: String
|
||||
) {
|
||||
companion object {
|
||||
val RIGHT_CLICK = AbilityActivation("RIGHT CLICK")
|
||||
val SNEAK_RIGHT_CLICK = AbilityActivation("SNEAK RIGHT CLICK")
|
||||
val SNEAK = AbilityActivation("SNEAK")
|
||||
val EMPTY = AbilityActivation("")
|
||||
fun of(text: String?): AbilityActivation {
|
||||
val trimmed = text?.trim()
|
||||
if (trimmed.isNullOrBlank())
|
||||
return EMPTY
|
||||
return AbilityActivation(trimmed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val abilityNameRegex = "Ability: (?<name>.*?) *".toPattern()
|
||||
private fun findAbility(iterator: ListIterator<Text>): ItemAbility? {
|
||||
if (!iterator.hasNext()) {
|
||||
return null
|
||||
}
|
||||
val line = iterator.next()
|
||||
// The actual information about abilities is stored in the siblings
|
||||
if (line.directLiteralStringContent != "") return null
|
||||
var powerScroll: Boolean = false // This should instead determine the power scroll based on text colour
|
||||
var abilityName: String? = null
|
||||
var activation: String? = null
|
||||
var hasProcessedActivation = false
|
||||
for (sibling in line.siblings) {
|
||||
val directContent = sibling.directLiteralStringContent ?: continue
|
||||
if (directContent == "⦾ ") {
|
||||
powerScroll = true
|
||||
continue
|
||||
}
|
||||
if (!hasProcessedActivation && abilityName != null) {
|
||||
hasProcessedActivation = true
|
||||
activation = directContent
|
||||
continue
|
||||
}
|
||||
abilityNameRegex.useMatch<Nothing>(directContent) {
|
||||
abilityName = group("name")
|
||||
continue
|
||||
}
|
||||
if (abilityName != null) {
|
||||
ErrorUtil.softError("Found abilityName $abilityName without finding but encountered unprocessable element in: $line")
|
||||
}
|
||||
return null
|
||||
}
|
||||
if (abilityName == null) return null
|
||||
val descriptionLines = mutableListOf<Text>()
|
||||
var manaCost: Int? = null
|
||||
var cooldown: Duration? = null
|
||||
while (iterator.hasNext()) {
|
||||
val descriptionLine = iterator.next()
|
||||
if (descriptionLine.unformattedString == "") break
|
||||
var nextIsManaCost = false
|
||||
var isSpecialLine = false
|
||||
var nextIsDuration = false
|
||||
for (sibling in descriptionLine.siblings) {
|
||||
val directContent = sibling.directLiteralStringContent ?: continue
|
||||
if ("Mana Cost: " == directContent) { // TODO: 'Soulflow Cost: ' support (or maybe a generic 'XXX Cost: ')
|
||||
nextIsManaCost = true
|
||||
isSpecialLine = true
|
||||
continue
|
||||
}
|
||||
if ("Cooldown: " == directContent) {
|
||||
nextIsDuration = true
|
||||
isSpecialLine = true
|
||||
continue
|
||||
}
|
||||
if (nextIsDuration) {
|
||||
nextIsDuration = false
|
||||
cooldown = parseTimePattern(directContent)
|
||||
continue
|
||||
}
|
||||
if (nextIsManaCost) {
|
||||
nextIsManaCost = false
|
||||
manaCost = parseShortNumber(directContent).toInt()
|
||||
continue
|
||||
}
|
||||
if (isSpecialLine) {
|
||||
ErrorUtil.softError("Unknown special line segment: '$sibling' in '$descriptionLine'")
|
||||
}
|
||||
}
|
||||
if (!isSpecialLine) {
|
||||
descriptionLines.add(descriptionLine)
|
||||
}
|
||||
}
|
||||
return ItemAbility(
|
||||
abilityName,
|
||||
powerScroll,
|
||||
AbilityActivation.of(activation),
|
||||
manaCost,
|
||||
descriptionLines,
|
||||
cooldown
|
||||
)
|
||||
}
|
||||
|
||||
fun getAbilities(lore: List<Text>): List<ItemAbility> {
|
||||
val iterator = lore.listIterator()
|
||||
val abilities = mutableListOf<ItemAbility>()
|
||||
while (iterator.hasNext()) {
|
||||
findAbility(iterator)?.let(abilities::add)
|
||||
}
|
||||
|
||||
return abilities
|
||||
}
|
||||
|
||||
fun getAbilities(itemStack: ItemStack): List<ItemAbility> {
|
||||
return getAbilities(itemStack.loreAccordingToNbt)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -90,6 +90,8 @@ fun CharSequence.removeColorCodes(keepNonColorCodes: Boolean = false): String {
|
||||
val Text.unformattedString: String
|
||||
get() = string.removeColorCodes()
|
||||
|
||||
val Text.directLiteralStringContent: String? get() = (this.content as? PlainTextContent)?.string()
|
||||
|
||||
fun Text.allSiblings(): List<Text> = listOf(this) + siblings.flatMap { it.allSiblings() }
|
||||
|
||||
fun MutableText.withColor(formatting: Formatting) = this.styled { it.withColor(formatting).withItalic(false) }
|
||||
|
||||
Reference in New Issue
Block a user