Fairy souls

This commit is contained in:
nea
2022-09-28 12:45:56 +02:00
parent ec66c82198
commit 4d73331a44
28 changed files with 837 additions and 205 deletions

View File

@@ -4,3 +4,11 @@
### Building your own
Use Java 17.
This depends on [neurepoparsing](https://git.nea.moe/nea/neurepoparsing/). Please clone that repository and make it available in your local maven repository using `./gradlew publishToMavenLocal`. This will be automated at a later stage.
Afterwards, running `./gradlew build` will create a mod jar in `build/libs`

View File

@@ -8,8 +8,7 @@ plugins {
id("dev.architectury.loom") version "0.12.0.+"
id("com.github.johnrengelman.shadow") version "7.1.2"
id("moe.nea.licenseextractificator") version "fffc76c"
id("com.github.eutro.hierarchical-lang") version "1.1.3"
id("io.github.juuxel.loom-quiltflower") version "1.7.2"
id("io.github.juuxel.loom-quiltflower") version "1.7.3"
}
loom {
@@ -24,6 +23,12 @@ loom {
property("notenoughupdates.debug", "true")
}
}
runs {
named("client") {
vmArg("-XX:+AllowEnhancedClassRedefinition")
vmArg("-XX:HotswapAgent=fatjar")
}
}
}
repositories {
@@ -59,12 +64,16 @@ dependencies {
modImplementation("net.fabricmc:fabric-loader:${project.property("fabric_loader_version")}")
modApi("net.fabricmc.fabric-api:fabric-api:${project.property("fabric_api_version")}")
modImplementation("net.fabricmc:fabric-language-kotlin:${project.property("fabric_kotlin_version")}")
modApi("dev.architectury:architectury:6.2.46")
// Actual dependencies
modCompileOnly("me.shedaniel:RoughlyEnoughItems-api:${rootProject.property("rei_version")}")
modCompileOnly("me.shedaniel:RoughlyEnoughItems-api:${rootProject.property("rei_version")}") {
exclude(module = "architectury")
exclude(module = "architectury-fabric")
}
shadowMe("io.github.moulberry:neurepoparser:0.0.1")
shadowMe("com.github.hypfvieh:dbus-java-core:4.1.0")
shadowMe("com.github.hypfvieh:dbus-java-transport-native-unixsocket:4.1.0")
shadowMe("com.github.hypfvieh:dbus-java-core:${rootProject.property("dbus_java_version")}")
shadowMe("com.github.hypfvieh:dbus-java-transport-native-unixsocket:${rootProject.property("dbus_java_version")}")
fun ktor(mod: String) = "io.ktor:ktor-$mod-jvm:${project.property("ktor_version")}"
transInclude(implementation(ktor("client-core"))!!)
@@ -74,7 +83,11 @@ dependencies {
modImplementation(include("io.github.cottonmc:LibGui:${project.property("libgui_version")}")!!)
// Dev environment preinstalled mods
modRuntimeOnly("me.shedaniel:RoughlyEnoughItems-fabric:${project.property("rei_version")}")
modRuntimeOnly("dev.architectury:architectury-fabric:6.2.46")
modRuntimeOnly("me.shedaniel:RoughlyEnoughItems-fabric:${project.property("rei_version")}") {
exclude(module = "architectury")
exclude(module = "architectury-fabric")
}
modRuntimeOnly("me.djtheredstoner:DevAuth-fabric:${project.property("devauth_version")}")
modRuntimeOnly("maven.modrinth:modmenu:${project.property("modmenu_version")}")
@@ -126,7 +139,7 @@ tasks.processResources {
)
}
filesMatching("**/lang/*.json") {
flattenJson(this)
// flattenJson(this)
}
}

View File

@@ -8,15 +8,15 @@ archives_base_name=notenoughupdates
mod_version=1.0.0
maven_group=moe.nea.notenoughupdates
architectury_version=5.10.33
fabric_loader_version=0.14.8
fabric_api_version=0.58.0+1.19
fabric_loader_version=0.14.9
fabric_api_version=0.60.0+1.19.2
fabric_kotlin_version=1.8.2+kotlin.1.7.10
yarn_version=1.19.2+build.9
libgui_version=6.0.0+1.19
rei_version=9.1.518
libgui_version=6.1.0+1.19
rei_version=9.1.537
devauth_version=1.0.0
modmenu_version=4.0.4
modmenu_version=4.0.6
ktor_version=2.0.3
dbus_java_version=4.2.0

View File

@@ -5,12 +5,8 @@ import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.*
import kotlinx.serialization.json.Json
import moe.nea.notenoughupdates.commands.registerNeuCommand
import moe.nea.notenoughupdates.dbus.NEUDbusObject
import moe.nea.notenoughupdates.repo.RepoManager
import moe.nea.notenoughupdates.util.ConfigHolder
import java.nio.file.Files
import java.nio.file.Path
import net.fabricmc.api.ClientModInitializer
import net.fabricmc.api.ModInitializer
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback
@@ -19,13 +15,18 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents
import net.fabricmc.loader.api.FabricLoader
import net.fabricmc.loader.api.Version
import net.fabricmc.loader.api.metadata.ModMetadata
import net.minecraft.command.CommandRegistryAccess
import org.apache.logging.log4j.LogManager
import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder
import java.nio.file.Files
import java.nio.file.Path
import kotlinx.coroutines.*
import kotlinx.serialization.json.Json
import kotlin.coroutines.EmptyCoroutineContext
import net.minecraft.command.CommandRegistryAccess
import moe.nea.notenoughupdates.commands.registerNeuCommand
import moe.nea.notenoughupdates.dbus.NEUDbusObject
import moe.nea.notenoughupdates.features.FeatureManager
import moe.nea.notenoughupdates.repo.RepoManager
import moe.nea.notenoughupdates.util.SBData
import moe.nea.notenoughupdates.util.config.IConfigHolder
object NotEnoughUpdates : ModInitializer, ClientModInitializer {
const val MOD_ID = "notenoughupdates"
@@ -59,7 +60,6 @@ object NotEnoughUpdates : ModInitializer, ClientModInitializer {
.build()
val coroutineScope =
CoroutineScope(EmptyCoroutineContext + CoroutineName("NotEnoughUpdates")) + SupervisorJob(globalJob)
val coroutineScopeIo = coroutineScope + Dispatchers.IO + SupervisorJob(globalJob)
private fun registerCommands(
dispatcher: CommandDispatcher<FabricClientCommandSource>,
@@ -72,8 +72,9 @@ object NotEnoughUpdates : ModInitializer, ClientModInitializer {
override fun onInitialize() {
dbusConnection.requestBusName("moe.nea.notenoughupdates")
dbusConnection.exportObject(NEUDbusObject)
ConfigHolder.registerEvents()
IConfigHolder.registerEvents()
RepoManager.initialize()
SBData.init()
FeatureManager.autoload()
ClientCommandRegistrationCallback.EVENT.register(this::registerCommands)
ClientLifecycleEvents.CLIENT_STOPPING.register(ClientLifecycleEvents.ClientStopping {

View File

@@ -2,11 +2,12 @@ package moe.nea.notenoughupdates.commands
import com.mojang.brigadier.CommandDispatcher
import io.github.cottonmc.cotton.gui.client.CottonClientScreen
import moe.nea.notenoughupdates.gui.repoGui
import moe.nea.notenoughupdates.repo.RepoManager
import moe.nea.notenoughupdates.util.ScreenUtil.setScreenLater
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource
import net.minecraft.text.Text
import moe.nea.notenoughupdates.gui.repoGui
import moe.nea.notenoughupdates.repo.RepoManager
import moe.nea.notenoughupdates.util.SBData
import moe.nea.notenoughupdates.util.ScreenUtil.setScreenLater
fun neuCommand() = literal("neu") {
@@ -27,6 +28,23 @@ fun neuCommand() = literal("neu") {
setScreenLater(CottonClientScreen(repoGui()))
}
}
thenLiteral("dev") {
val sbData = thenLiteral("sbdata") {
thenExecute {
source.sendFeedback(Text.translatable("notenoughupdates.sbinfo.profile", SBData.profileCuteName))
val locrawInfo = SBData.locraw
if (locrawInfo == null) {
source.sendFeedback(Text.translatable("notenoughupdates.sbinfo.nolocraw"))
} else {
source.sendFeedback(Text.translatable("notenoughupdates.sbinfo.server", locrawInfo.server))
source.sendFeedback(Text.translatable("notenoughupdates.sbinfo.gametype", locrawInfo.gametype))
source.sendFeedback(Text.translatable("notenoughupdates.sbinfo.mode", locrawInfo.mode))
source.sendFeedback(Text.translatable("notenoughupdates.sbinfo.map", locrawInfo.map))
}
}
}
}
}

View File

@@ -1,7 +1,36 @@
package moe.nea.notenoughupdates.events
/**
* An event that can be fired by a [NEUEventBus].
*
* Typically, that event bus is implemented as a companion object
*
* ```
* class SomeEvent : NEUEvent() {
* companion object : NEUEventBus<SomeEvent>()
* }
* ```
*/
abstract class NEUEvent {
/**
* A [NEUEvent] that can be [cancelled]
*/
abstract class Cancellable : NEUEvent() {
/**
* Cancels this is event.
*
* @see cancelled
*/
fun cancel() {
cancelled = true
}
/**
* Whether this event is cancelled.
*
* Cancelled events will bypass handlers unless otherwise specified and will prevent the action that this
* event was originally fired for.
*/
var cancelled: Boolean = false
}
}

View File

@@ -1,7 +1,14 @@
package moe.nea.notenoughupdates.events
import java.util.concurrent.CopyOnWriteArrayList
import moe.nea.notenoughupdates.NotEnoughUpdates
/**
* A pubsub event bus.
*
* [subscribe] to events [publish]ed on this event bus.
* Subscriptions may not necessarily be delivered in the order or registering.
*/
open class NEUEventBus<T : NEUEvent> {
data class Handler<T>(val invocation: (T) -> Unit, val receivesCancelled: Boolean)
@@ -17,7 +24,11 @@ open class NEUEventBus<T : NEUEvent> {
fun publish(event: T): T {
for (function in toHandle) {
if (function.receivesCancelled || event !is NEUEvent.Cancellable || !event.cancelled) {
try {
function.invocation(event)
} catch (e: Exception) {
NotEnoughUpdates.logger.error("Caught exception during processing event $event", e)
}
}
}
return event

View File

@@ -0,0 +1,13 @@
package moe.nea.notenoughupdates.events
import net.minecraft.text.Text
import moe.nea.notenoughupdates.util.unformattedString
/**
* This event gets published whenever the client receives a chat message from the server.
*/
data class ServerChatLineReceivedEvent(val text: Text) : NEUEvent.Cancellable() {
companion object : NEUEventBus<ServerChatLineReceivedEvent>()
val unformattedString = text.unformattedString
}

View File

@@ -0,0 +1,13 @@
package moe.nea.notenoughupdates.events
import moe.nea.notenoughupdates.util.Locraw
/**
* This event gets published whenever `/locraw` is queried and HyPixel returns a location different to the old one.
*
* **N.B.:** This event may get fired multiple times while on the server (for example, first to null, then to the
* correct location).
*/
data class SkyblockServerUpdateEvent(val oldLocraw: Locraw?, val newLocraw: Locraw?) : NEUEvent() {
companion object : NEUEventBus<SkyblockServerUpdateEvent>()
}

View File

@@ -0,0 +1,22 @@
package moe.nea.notenoughupdates.events
import net.minecraft.client.render.Camera
import net.minecraft.client.render.GameRenderer
import net.minecraft.client.render.LightmapTextureManager
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.util.math.Matrix4f
/**
* This event is called after all world rendering is done, but before any GUI rendering (including hand) has been done.
*/
data class WorldRenderLastEvent(
val matrices: MatrixStack,
val tickDelta: Float,
val renderBlockOutline: Boolean,
val camera: Camera,
val gameRenderer: GameRenderer,
val lightmapTextureManager: LightmapTextureManager,
val positionMatrix: Matrix4f,
) : NEUEvent() {
companion object : NEUEventBus<WorldRenderLastEvent>()
}

View File

@@ -1,27 +1,42 @@
package moe.nea.notenoughupdates.features
import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer
import moe.nea.notenoughupdates.NotEnoughUpdates
import moe.nea.notenoughupdates.features.world.FairySouls
import moe.nea.notenoughupdates.util.ConfigHolder
import moe.nea.notenoughupdates.util.config.ConfigHolder
object FeatureManager : ConfigHolder<FeatureManager.Config>(serializer(), "features", ::Config) {
@Serializable
data class Config(
val enabledFeatures: MutableMap<String, Boolean> = mutableMapOf()
)
private val features = mutableMapOf<String, NEUFeature>()
private var hasAutoloaded = false
init {
autoload()
}
fun autoload() {
synchronized(this) {
if (hasAutoloaded) return
loadFeature(FairySouls)
hasAutoloaded = true
}
}
fun loadFeature(feature: NEUFeature) {
synchronized(features) {
if (feature.identifier in features) {
NotEnoughUpdates.logger.error("Double registering feature ${feature.identifier}. Ignoring second instance $feature")
return
}
features[feature.identifier] = feature
feature.onLoad()
}
}
fun isEnabled(identifier: String): Boolean? =

View File

@@ -1,12 +1,103 @@
package moe.nea.notenoughupdates.features.world
import io.github.moulberry.repo.data.Coordinate
import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer
import net.minecraft.util.math.BlockPos
import moe.nea.notenoughupdates.events.ServerChatLineReceivedEvent
import moe.nea.notenoughupdates.events.SkyblockServerUpdateEvent
import moe.nea.notenoughupdates.events.WorldRenderLastEvent
import moe.nea.notenoughupdates.features.NEUFeature
import moe.nea.notenoughupdates.repo.RepoManager
import moe.nea.notenoughupdates.util.MC
import moe.nea.notenoughupdates.util.SBData
import moe.nea.notenoughupdates.util.config.ProfileSpecificConfigHolder
import moe.nea.notenoughupdates.util.render.RenderBlockContext.Companion.renderBlocks
import moe.nea.notenoughupdates.util.unformattedString
val Coordinate.blockPos: BlockPos
get() = BlockPos(x, y, z)
object FairySouls : NEUFeature,
ProfileSpecificConfigHolder<FairySouls.Config>(serializer(), "fairy-souls.json", ::Config) {
@Serializable
data class Config(
val foundSouls: MutableMap<String, MutableSet<Int>> = mutableMapOf()
)
object FairySouls : NEUFeature {
override val name: String get() = "Fairy Souls"
override val identifier: String get() = "fairy-souls"
override fun onLoad() {
val playerReach = 5
val playerReachSquared = playerReach * playerReach
var currentLocationName: String? = null
var currentLocationSouls: List<Coordinate> = emptyList()
var currentMissingSouls: List<Coordinate> = emptyList()
fun updateMissingSouls() {
currentMissingSouls = emptyList()
val c = config ?: return
val fi = c.foundSouls[currentLocationName] ?: setOf()
val cms = currentLocationSouls.toMutableList()
fi.asSequence().sortedDescending().filter { it in cms.indices }.forEach { cms.removeAt(it) }
currentMissingSouls = cms
}
fun updateWorldSouls() {
currentLocationSouls = emptyList()
val loc = currentLocationName ?: return
currentLocationSouls = RepoManager.neuRepo.constants.fairySouls.soulLocations[loc] ?: return
}
fun findNearestClickableSoul(): Coordinate? {
val player = MC.player ?: return null
val pos = player.pos
val location = SBData.skyblockLocation ?: return null
val soulLocations: List<Coordinate> =
RepoManager.neuRepo.constants.fairySouls.soulLocations[location] ?: return null
return soulLocations
.map { it to it.blockPos.getSquaredDistance(pos) }
.filter { it.second < playerReachSquared }
.minByOrNull { it.second }
?.first
}
private fun markNearestSoul() {
val nearestSoul = findNearestClickableSoul() ?: return
val c = config ?: return
val loc = currentLocationName ?: return
val idx = currentLocationSouls.indexOf(nearestSoul)
c.foundSouls.computeIfAbsent(loc) { mutableSetOf() }.add(idx)
markDirty()
updateMissingSouls()
}
override fun onLoad() {
SkyblockServerUpdateEvent.subscribe {
currentLocationName = it.newLocraw?.skyblockLocation
updateWorldSouls()
updateMissingSouls()
}
ServerChatLineReceivedEvent.subscribe {
when (it.text.unformattedString) {
"You have already found that Fairy Soul!" -> {
markNearestSoul()
}
"SOUL! You found a Fairy Soul!" -> {
markNearestSoul()
}
}
}
WorldRenderLastEvent.subscribe {
renderBlocks(it.camera) {
color(1F, 1F, 0F, 0.8F)
currentMissingSouls.forEach {
block(it.blockPos)
}
}
}
}
}

View File

@@ -9,7 +9,7 @@ import io.github.cottonmc.cotton.gui.widget.data.HorizontalAlignment
import io.github.cottonmc.cotton.gui.widget.data.Insets
import io.github.cottonmc.cotton.gui.widget.data.VerticalAlignment
import moe.nea.notenoughupdates.NotEnoughUpdates
import moe.nea.notenoughupdates.util.ConfigHolder
import moe.nea.notenoughupdates.util.config.ConfigHolder
import net.minecraft.text.Text
import kotlin.reflect.KMutableProperty1

View File

@@ -0,0 +1,33 @@
package moe.nea.notenoughupdates.mixins
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 net.minecraft.client.network.message.MessageHandler
import net.minecraft.network.message.MessageType
import net.minecraft.network.message.SignedMessage
import net.minecraft.text.Text
import moe.nea.notenoughupdates.events.ServerChatLineReceivedEvent
@Mixin(MessageHandler::class)
class MixinMessageHandler {
@Inject(method = ["onChatMessage"], at = [At("HEAD")], cancellable = true)
fun onOnChatMessage(message: SignedMessage, params: MessageType.Parameters, ci: CallbackInfo) {
val decoratedText = params.applyChatDecoration(message.unsignedContent.orElse(message.content))
val event = ServerChatLineReceivedEvent(decoratedText)
if (ServerChatLineReceivedEvent.publish(event).cancelled) {
ci.cancel()
}
}
@Inject(method = ["onGameMessage"], at = [At("HEAD")], cancellable = true)
fun onOnGameMessage(message: Text, overlay: Boolean, ci: CallbackInfo) {
if (!overlay) {
val event = ServerChatLineReceivedEvent(message)
if (ServerChatLineReceivedEvent.publish(event).cancelled) {
ci.cancel()
}
}
}
}

View File

@@ -0,0 +1,41 @@
package moe.nea.notenoughupdates.mixins
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 net.minecraft.client.render.Camera
import net.minecraft.client.render.GameRenderer
import net.minecraft.client.render.LightmapTextureManager
import net.minecraft.client.render.WorldRenderer
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.util.math.Matrix4f
import moe.nea.notenoughupdates.events.WorldRenderLastEvent
@Mixin(WorldRenderer::class)
class MixinWorldRenderer {
@Inject(
method = ["render"],
at = [At("INVOKE", target = "renderChunkDebugInfo", shift = At.Shift.AFTER)],
)
fun onWorldRenderLast(
matrices: MatrixStack,
tickDelta: Float,
arg2: Long,
renderBlockOutline: Boolean,
camera: Camera,
gameRenderer: GameRenderer,
lightmapTextureManager: LightmapTextureManager,
positionMatrix: Matrix4f,
ci: CallbackInfo
) {
val event = WorldRenderLastEvent(
matrices, tickDelta, renderBlockOutline,
camera, gameRenderer, lightmapTextureManager,
positionMatrix
)
WorldRenderLastEvent.publish(event)
}
}

View File

@@ -9,7 +9,7 @@ import kotlinx.serialization.serializer
import moe.nea.notenoughupdates.NotEnoughUpdates
import moe.nea.notenoughupdates.NotEnoughUpdates.logger
import moe.nea.notenoughupdates.hud.ProgressBar
import moe.nea.notenoughupdates.util.ConfigHolder
import moe.nea.notenoughupdates.util.config.ConfigHolder
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents
import net.minecraft.client.MinecraftClient
import net.minecraft.network.packet.s2c.play.SynchronizeRecipesS2CPacket

View File

@@ -1,131 +0,0 @@
package moe.nea.notenoughupdates.util
import java.io.IOException
import java.nio.file.Path
import java.util.concurrent.CopyOnWriteArrayList
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationException
import kotlin.io.path.exists
import kotlin.io.path.readText
import kotlin.io.path.writeText
import kotlin.reflect.KClass
import net.minecraft.client.MinecraftClient
import net.minecraft.server.command.CommandOutput
import net.minecraft.text.Text
import moe.nea.notenoughupdates.NotEnoughUpdates
import moe.nea.notenoughupdates.events.ScreenOpenEvent
abstract class ConfigHolder<T>(
val serializer: KSerializer<T>,
val name: String,
val default: () -> T
) {
var config: T
private set
init {
config = readValueOrDefault()
putConfig(this::class, this)
}
val file: Path get() = NotEnoughUpdates.CONFIG_DIR.resolve("$name.json")
protected fun readValueOrDefault(): T {
if (file.exists())
try {
return NotEnoughUpdates.json.decodeFromString(
serializer,
file.readText()
)
} catch (e: IOException) {
badLoads.add(name)
NotEnoughUpdates.logger.error(
"IO exception during loading of config file $name. This will reset this config.",
e
)
} catch (e: SerializationException) {
badLoads.add(name)
NotEnoughUpdates.logger.error(
"Serialization exception during loading of config file $name. This will reset this config.",
e
)
}
return default()
}
private fun writeValue(t: T) {
file.writeText(NotEnoughUpdates.json.encodeToString(serializer, t))
}
fun save() {
writeValue(config)
}
fun load() {
config = readValueOrDefault()
}
fun markDirty() {
Companion.markDirty(this::class)
}
companion object {
private var badLoads: MutableList<String> = CopyOnWriteArrayList()
private val allConfigs: MutableMap<KClass<out ConfigHolder<*>>, ConfigHolder<*>> = mutableMapOf()
private val dirty: MutableSet<KClass<out ConfigHolder<*>>> = mutableSetOf()
private fun <T : ConfigHolder<K>, K> putConfig(kClass: KClass<T>, inst: ConfigHolder<K>) {
allConfigs[kClass] = inst
}
fun <T : ConfigHolder<K>, K> markDirty(kClass: KClass<T>) {
if (kClass !in allConfigs) {
NotEnoughUpdates.logger.error("Tried to markDirty '${kClass.qualifiedName}', which isn't registered as 'ConfigHolder'")
return
}
dirty.add(kClass)
}
private fun performSaves() {
val toSave = dirty.toList().also {
dirty.clear()
}
for (it in toSave) {
val obj = allConfigs[it]
if (obj == null) {
NotEnoughUpdates.logger.error("Tried to save '${it}', which isn't registered as 'ConfigHolder'")
continue
}
obj.save()
}
}
private fun warnForResetConfigs(player: CommandOutput) {
if (badLoads.isNotEmpty()) {
player.sendMessage(
Text.literal(
"The following configs have been reset: ${badLoads.joinToString(", ")}. " +
"This can be intentional, but probably isn't."
)
)
badLoads.clear()
}
}
fun registerEvents() {
ScreenOpenEvent.subscribe { event ->
performSaves()
val p = MinecraftClient.getInstance().player
if (p != null) {
warnForResetConfigs(p)
}
}
ClientLifecycleEvents.CLIENT_STOPPING.register(ClientLifecycleEvents.ClientStopping {
performSaves()
})
}
}
}

View File

@@ -0,0 +1,8 @@
package moe.nea.notenoughupdates.util
import kotlinx.serialization.Serializable
@Serializable
data class Locraw(val server: String, val gametype: String? = null, val mode: String? = null, val map: String? = null) {
val skyblockLocation = if (gametype == "SKYBLOCK") mode else null
}

View File

@@ -0,0 +1,7 @@
package moe.nea.notenoughupdates.util
import net.minecraft.client.MinecraftClient
object MC {
inline val player get() = MinecraftClient.getInstance().player
}

View File

@@ -0,0 +1,63 @@
package moe.nea.notenoughupdates.util
import dev.architectury.event.events.client.ClientPlayerEvent
import kotlinx.serialization.SerializationException
import kotlinx.serialization.decodeFromString
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.ExperimentalTime
import kotlin.time.TimeSource
import moe.nea.notenoughupdates.NotEnoughUpdates
import moe.nea.notenoughupdates.events.ServerChatLineReceivedEvent
import moe.nea.notenoughupdates.events.SkyblockServerUpdateEvent
@OptIn(ExperimentalTime::class)
object SBData {
val profileRegex = "(?:Your profile was changed to: |You are playing on profile: )(.+)".toRegex()
var profileCuteName: String? = null
private var lastLocrawSent: TimeSource.Monotonic.ValueTimeMark? = null
private val locrawRoundtripTime: Duration = 5.seconds
var locraw: Locraw? = null
val skyblockLocation get() = locraw?.skyblockLocation
fun init() {
ServerChatLineReceivedEvent.subscribe { event ->
val profileMatch = profileRegex.matchEntire(event.unformattedString)
if (profileMatch != null) {
profileCuteName = profileMatch.groupValues[1]
}
if (event.unformattedString.startsWith("{")) {
val lLS = lastLocrawSent
if (tryReceiveLocraw(event.unformattedString) && lLS != null && lLS.elapsedNow() < locrawRoundtripTime) {
lastLocrawSent = null
event.cancel()
}
}
}
ClientPlayerEvent.CLIENT_PLAYER_JOIN.register(ClientPlayerEvent.ClientPlayerJoin {
locraw = null
sendLocraw()
})
}
private fun tryReceiveLocraw(unformattedString: String): Boolean = try {
val lastLocraw = locraw
locraw = NotEnoughUpdates.json.decodeFromString<Locraw>(unformattedString)
SkyblockServerUpdateEvent.publish(SkyblockServerUpdateEvent(lastLocraw, locraw))
true
} catch (e: SerializationException) {
false
} catch (e: IllegalArgumentException) {
false
}
fun sendLocraw() {
lastLocrawSent = TimeSource.Monotonic.markNow()
MC.player?.sendCommand("locraw")
}
}

View File

@@ -0,0 +1,60 @@
package moe.nea.notenoughupdates.util.config
import java.nio.file.Path
import kotlinx.serialization.KSerializer
import kotlin.io.path.exists
import kotlin.io.path.readText
import kotlin.io.path.writeText
import moe.nea.notenoughupdates.NotEnoughUpdates
abstract class ConfigHolder<T>(
val serializer: KSerializer<T>,
val name: String,
val default: () -> T
) : IConfigHolder<T> {
final override var config: T
private set
init {
config = readValueOrDefault()
IConfigHolder.putConfig(this::class, this)
}
private val file: Path get() = NotEnoughUpdates.CONFIG_DIR.resolve("$name.json")
protected fun readValueOrDefault(): T {
if (file.exists())
try {
return NotEnoughUpdates.json.decodeFromString(
serializer,
file.readText()
)
} catch (e: Exception) {/* Expecting IOException and SerializationException, but Kotlin doesn't allow multi catches*/
IConfigHolder.badLoads.add(name)
NotEnoughUpdates.logger.error(
"Exception during loading of config file $name. This will reset this config.",
e
)
}
return default()
}
private fun writeValue(t: T) {
file.writeText(NotEnoughUpdates.json.encodeToString(serializer, t))
}
override fun save() {
writeValue(config)
}
override fun load() {
config = readValueOrDefault()
}
override fun markDirty() {
IConfigHolder.markDirty(this::class)
}
}

View File

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

View File

@@ -0,0 +1,81 @@
package moe.nea.notenoughupdates.util.config
import java.nio.file.Path
import kotlinx.serialization.KSerializer
import kotlin.io.path.createDirectories
import kotlin.io.path.deleteExisting
import kotlin.io.path.exists
import kotlin.io.path.extension
import kotlin.io.path.listDirectoryEntries
import kotlin.io.path.nameWithoutExtension
import kotlin.io.path.readText
import kotlin.io.path.writeText
import moe.nea.notenoughupdates.NotEnoughUpdates
import moe.nea.notenoughupdates.util.SBData
abstract class ProfileSpecificConfigHolder<S>(
private val configSerializer: KSerializer<S>,
val configName: String,
private val configDefault: () -> S
) : IConfigHolder<S?> {
var allConfigs: MutableMap<String, S>
override val config: S?
get() = SBData.profileCuteName?.let {
allConfigs.computeIfAbsent(it) { configDefault() }
}
init {
allConfigs = readValues()
readValues()
}
private val configDirectory: Path get() = NotEnoughUpdates.CONFIG_DIR.resolve("profiles")
private fun readValues(): MutableMap<String, S> {
if (!configDirectory.exists()) {
configDirectory.createDirectories()
}
val profileFiles = configDirectory.listDirectoryEntries()
return profileFiles
.filter { it.extension == "json" }
.mapNotNull {
try {
it.nameWithoutExtension to NotEnoughUpdates.json.decodeFromString(configSerializer, it.readText())
} catch (e: Exception) { /* Expecting IOException and SerializationException, but Kotlin doesn't allow multi catches*/
IConfigHolder.badLoads.add(configName)
NotEnoughUpdates.logger.error(
"Exception during loading of profile specific config file $it ($configName). This will reset that profiles config.",
e
)
null
}
}.toMap().toMutableMap()
}
override fun save() {
if (!configDirectory.exists()) {
configDirectory.createDirectories()
}
val c = allConfigs
configDirectory.listDirectoryEntries().forEach {
if (it.nameWithoutExtension !in c) {
it.deleteExisting()
}
}
c.forEach { (name, value) ->
val f = configDirectory.resolve("$name.json")
f.writeText(NotEnoughUpdates.json.encodeToString(configSerializer, value))
}
}
override fun markDirty() {
IConfigHolder.markDirty(this::class)
}
override fun load() {
allConfigs = readValues()
}
}

View File

@@ -0,0 +1,91 @@
package moe.nea.notenoughupdates.util.render
import com.mojang.blaze3d.systems.RenderSystem
import net.minecraft.client.gl.VertexBuffer
import net.minecraft.client.render.BufferBuilder
import net.minecraft.client.render.Camera
import net.minecraft.client.render.GameRenderer
import net.minecraft.client.render.Tessellator
import net.minecraft.client.render.VertexFormat
import net.minecraft.client.render.VertexFormats
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Vec3d
class RenderBlockContext(val tesselator: Tessellator, val camPos: Vec3d) {
val buffer = tesselator.buffer
fun color(red: Float, green: Float, blue: Float, alpha: Float) {
RenderSystem.setShaderColor(red, green, blue, alpha)
}
fun block(blockPos: BlockPos) {
val matrixStack = RenderSystem.getModelViewStack()
matrixStack.push()
matrixStack.translate(blockPos.x - camPos.x, blockPos.y - camPos.y, blockPos.z - camPos.z)
RenderSystem.applyModelViewMatrix()
RenderSystem.setShader(GameRenderer::getPositionColorShader)
buildCube(buffer)
tesselator.draw()
matrixStack.pop()
}
companion object {
fun buildCube(buf: BufferBuilder) {
buf.begin(VertexFormat.DrawMode.TRIANGLES, VertexFormats.POSITION_COLOR)
buf.fixedColor(255, 255, 255, 255)
buf.vertex(0.0, 0.0, 0.0).next()
buf.vertex(0.0, 0.0, 1.0).next()
buf.vertex(0.0, 1.0, 1.0).next()
buf.vertex(1.0, 1.0, 0.0).next()
buf.vertex(0.0, 0.0, 0.0).next()
buf.vertex(0.0, 1.0, 0.0).next()
buf.vertex(1.0, 0.0, 1.0).next()
buf.vertex(0.0, 0.0, 0.0).next()
buf.vertex(1.0, 0.0, 0.0).next()
buf.vertex(1.0, 1.0, 0.0).next()
buf.vertex(1.0, 0.0, 0.0).next()
buf.vertex(0.0, 0.0, 0.0).next()
buf.vertex(0.0, 0.0, 0.0).next()
buf.vertex(0.0, 1.0, 1.0).next()
buf.vertex(0.0, 1.0, 0.0).next()
buf.vertex(1.0, 0.0, 1.0).next()
buf.vertex(0.0, 0.0, 1.0).next()
buf.vertex(0.0, 0.0, 0.0).next()
buf.vertex(0.0, 1.0, 1.0).next()
buf.vertex(0.0, 0.0, 1.0).next()
buf.vertex(1.0, 0.0, 1.0).next()
buf.vertex(1.0, 1.0, 1.0).next()
buf.vertex(1.0, 0.0, 0.0).next()
buf.vertex(1.0, 1.0, 0.0).next()
buf.vertex(1.0, 0.0, 0.0).next()
buf.vertex(1.0, 1.0, 1.0).next()
buf.vertex(1.0, 0.0, 1.0).next()
buf.vertex(1.0, 1.0, 1.0).next()
buf.vertex(1.0, 1.0, 0.0).next()
buf.vertex(0.0, 1.0, 0.0).next()
buf.vertex(1.0, 1.0, 1.0).next()
buf.vertex(0.0, 1.0, 0.0).next()
buf.vertex(0.0, 1.0, 1.0).next()
buf.vertex(1.0, 1.0, 1.0).next()
buf.vertex(0.0, 1.0, 1.0).next()
buf.vertex(1.0, 0.0, 1.0).next()
buf.unfixColor()
}
fun renderBlocks(camera: Camera, block: RenderBlockContext. () -> Unit) {
RenderSystem.disableDepthTest()
RenderSystem.disableTexture()
RenderSystem.enableBlend()
RenderSystem.defaultBlendFunc()
val ctx = RenderBlockContext(Tessellator.getInstance(), camera.pos)
block(ctx)
VertexBuffer.unbind()
RenderSystem.enableDepthTest()
RenderSystem.enableTexture()
RenderSystem.disableBlend()
}
}
}

View File

@@ -0,0 +1,70 @@
package moe.nea.notenoughupdates.util
import net.minecraft.text.LiteralTextContent
import net.minecraft.text.Text
import net.minecraft.text.TextContent
import moe.nea.notenoughupdates.NotEnoughUpdates
class TextMatcher(text: Text) {
data class State(
var iterator: MutableList<Text>,
var currentText: Text?,
var offset: Int,
var textContent: String,
)
var state = State(
mutableListOf(text),
null,
0,
""
)
fun pollChunk(): Boolean {
val firstOrNull = state.iterator.removeFirstOrNull() ?: return false
state.offset = 0
state.currentText = firstOrNull
state.textContent = when (val content = firstOrNull.content) {
is LiteralTextContent -> content.string
TextContent.EMPTY -> ""
else -> {
NotEnoughUpdates.logger.warn("TextContent of type ${content.javaClass} not understood.")
return false
}
}
state.iterator.addAll(0, firstOrNull.siblings)
return true
}
fun pollChunks(): Boolean {
while (state.offset !in state.textContent.indices) {
if (!pollChunk()) {
return false
}
}
return true
}
fun pollChar(): Char? {
if (!pollChunks()) return null
return state.textContent[state.offset++]
}
fun expectString(string: String): Boolean {
var found = ""
while (found.length < string.length) {
if (!pollChunks()) return false
val takeable = state.textContent.drop(state.offset).take(string.length - found.length)
state.offset += takeable.length
found += takeable
}
return found == string
}
}
val Text.unformattedString
get() = string.replace("§.".toRegex(), "")

View File

@@ -1,26 +1,22 @@
{
"notenoughupdates": {
"repo": {
"reload": {
"network": "Trying to redownload the repository",
"disk": "Reloading repository from disk. This may lag a bit."
},
"cache": "Recaching items",
"brokenitem": "Failed to render item: %s"
},
"gui": {
"repo": {
"title": "NotEnoughUpdates Repo Settings",
"autoupdate": "Auto Update",
"username": "Repo Username",
"hint.username": "NotEnoughUpdates",
"reponame": "Repo Name",
"hint.reponame": "NotEnoughUpdates-REPO",
"branch": "Repo Branch",
"hint.branch": "dangerous",
"reset": "Reset",
"reset.label": "Reset to Defaults"
}
}
}
"notenoughupdates.repo.reload.network": "Trying to redownload the repository",
"notenoughupdates.repo.reload.disk": "Reloading repository from disk. This may lag a bit.",
"notenoughupdates.repo.cache": "Recaching items",
"notenoughupdates.repo.brokenitem": "Failed to render item: %s",
"notenoughupdates.gui.repo.title": "NotEnoughUpdates Repo Settings",
"notenoughupdates.gui.repo.autoupdate": "Auto Update",
"notenoughupdates.gui.repo.username": "Repo Username",
"notenoughupdates.gui.repo.hint.username": "NotEnoughUpdates",
"notenoughupdates.gui.repo.reponame": "Repo Name",
"notenoughupdates.gui.repo.hint.reponame": "NotEnoughUpdates-REPO",
"notenoughupdates.gui.repo.branch": "Repo Branch",
"notenoughupdates.gui.repo.hint.branch": "dangerous",
"notenoughupdates.gui.repo.reset": "Reset",
"notenoughupdates.gui.repo.reset.label": "Reset to Defaults",
"notenoughupdates.sbinfo.nolocraw": "No locraw data available",
"notenoughupdates.sbinfo.profile": "Current profile cutename: %s",
"notenoughupdates.sbinfo.server": "Locraw Server: %s",
"notenoughupdates.sbinfo.gametype": "Locraw Gametype: %s",
"notenoughupdates.sbinfo.mode": "Locraw Mode: %s",
"notenoughupdates.sbinfo.map": "Locraw Map: %s"
}

View File

@@ -0,0 +1,2 @@
disabledPlugins=Log4j2,Proxy
LOGGER=info

View File

@@ -3,7 +3,9 @@
"package": "moe.nea.notenoughupdates.mixins",
"compatibilityLevel": "JAVA_16",
"client": [
"MixinMinecraft"
"MixinMessageHandler",
"MixinMinecraft",
"MixinWorldRenderer"
],
"mixins": [
],