diff options
Diffstat (limited to 'src/main/kotlin/moe/nea/notenoughupdates')
22 files changed, 765 insertions, 154 deletions
diff --git a/src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt b/src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt index a2949a7..bf999bd 100644 --- a/src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt +++ b/src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt @@ -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 { diff --git a/src/main/kotlin/moe/nea/notenoughupdates/commands/rome.kt b/src/main/kotlin/moe/nea/notenoughupdates/commands/rome.kt index 55f2c50..6c75f20 100644 --- a/src/main/kotlin/moe/nea/notenoughupdates/commands/rome.kt +++ b/src/main/kotlin/moe/nea/notenoughupdates/commands/rome.kt @@ -2,11 +2,12 @@ package moe.nea.notenoughupdates.commands import com.mojang.brigadier.CommandDispatcher import io.github.cottonmc.cotton.gui.client.CottonClientScreen +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 -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource -import net.minecraft.text.Text 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)) + } + + } + } + } } diff --git a/src/main/kotlin/moe/nea/notenoughupdates/events/NEUEvent.kt b/src/main/kotlin/moe/nea/notenoughupdates/events/NEUEvent.kt index 278282e..81dc6fc 100644 --- a/src/main/kotlin/moe/nea/notenoughupdates/events/NEUEvent.kt +++ b/src/main/kotlin/moe/nea/notenoughupdates/events/NEUEvent.kt @@ -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 } } diff --git a/src/main/kotlin/moe/nea/notenoughupdates/events/NEUEventBus.kt b/src/main/kotlin/moe/nea/notenoughupdates/events/NEUEventBus.kt index eba71bc..7eb0ecd 100644 --- a/src/main/kotlin/moe/nea/notenoughupdates/events/NEUEventBus.kt +++ b/src/main/kotlin/moe/nea/notenoughupdates/events/NEUEventBus.kt @@ -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) { - function.invocation(event) + try { + function.invocation(event) + } catch (e: Exception) { + NotEnoughUpdates.logger.error("Caught exception during processing event $event", e) + } } } return event diff --git a/src/main/kotlin/moe/nea/notenoughupdates/events/ServerChatLineReceivedEvent.kt b/src/main/kotlin/moe/nea/notenoughupdates/events/ServerChatLineReceivedEvent.kt new file mode 100644 index 0000000..681ec6c --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/events/ServerChatLineReceivedEvent.kt @@ -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 +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/events/SkyblockServerUpdateEvent.kt b/src/main/kotlin/moe/nea/notenoughupdates/events/SkyblockServerUpdateEvent.kt new file mode 100644 index 0000000..541b4c6 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/events/SkyblockServerUpdateEvent.kt @@ -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>() +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/events/WorldRenderLastEvent.kt b/src/main/kotlin/moe/nea/notenoughupdates/events/WorldRenderLastEvent.kt new file mode 100644 index 0000000..c230c9c --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/events/WorldRenderLastEvent.kt @@ -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>() +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/features/FeatureManager.kt b/src/main/kotlin/moe/nea/notenoughupdates/features/FeatureManager.kt index 766cf1f..0ffaad5 100644 --- a/src/main/kotlin/moe/nea/notenoughupdates/features/FeatureManager.kt +++ b/src/main/kotlin/moe/nea/notenoughupdates/features/FeatureManager.kt @@ -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() { - loadFeature(FairySouls) + synchronized(this) { + if (hasAutoloaded) return + loadFeature(FairySouls) + hasAutoloaded = true + } } fun loadFeature(feature: NEUFeature) { - if (feature.identifier in features) { - NotEnoughUpdates.logger.error("Double registering feature ${feature.identifier}. Ignoring second instance $feature") - return + 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() } - features[feature.identifier] = feature } fun isEnabled(identifier: String): Boolean? = diff --git a/src/main/kotlin/moe/nea/notenoughupdates/features/world/FairySouls.kt b/src/main/kotlin/moe/nea/notenoughupdates/features/world/FairySouls.kt index b928f3d..9bd1a1f 100644 --- a/src/main/kotlin/moe/nea/notenoughupdates/features/world/FairySouls.kt +++ b/src/main/kotlin/moe/nea/notenoughupdates/features/world/FairySouls.kt @@ -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" + 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) + } + } + } } } diff --git a/src/main/kotlin/moe/nea/notenoughupdates/gui/ConfigGui.kt b/src/main/kotlin/moe/nea/notenoughupdates/gui/ConfigGui.kt index 3792d80..a5453a9 100644 --- a/src/main/kotlin/moe/nea/notenoughupdates/gui/ConfigGui.kt +++ b/src/main/kotlin/moe/nea/notenoughupdates/gui/ConfigGui.kt @@ -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 diff --git a/src/main/kotlin/moe/nea/notenoughupdates/mixins/MixinMessageHandler.kt b/src/main/kotlin/moe/nea/notenoughupdates/mixins/MixinMessageHandler.kt new file mode 100644 index 0000000..b6151b9 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/mixins/MixinMessageHandler.kt @@ -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() + } + } + } +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/mixins/MixinWorldRenderer.kt b/src/main/kotlin/moe/nea/notenoughupdates/mixins/MixinWorldRenderer.kt new file mode 100644 index 0000000..ac9ea15 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/mixins/MixinWorldRenderer.kt @@ -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) + } + +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoManager.kt b/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoManager.kt index 72603d9..26bca51 100644 --- a/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoManager.kt +++ b/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoManager.kt @@ -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 diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/ConfigHolder.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/ConfigHolder.kt deleted file mode 100644 index 50a3d9b..0000000 --- a/src/main/kotlin/moe/nea/notenoughupdates/util/ConfigHolder.kt +++ /dev/null @@ -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() - }) - } - - } -} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/Locraw.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/Locraw.kt new file mode 100644 index 0000000..400842f --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/util/Locraw.kt @@ -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 +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/MC.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/MC.kt new file mode 100644 index 0000000..da06c68 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/util/MC.kt @@ -0,0 +1,7 @@ +package moe.nea.notenoughupdates.util + +import net.minecraft.client.MinecraftClient + +object MC { + inline val player get() = MinecraftClient.getInstance().player +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/SBData.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/SBData.kt new file mode 100644 index 0000000..5391206 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/util/SBData.kt @@ -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") + } + + +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/config/ConfigHolder.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/config/ConfigHolder.kt new file mode 100644 index 0000000..e8a9649 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/util/config/ConfigHolder.kt @@ -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) + } + +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/config/IConfigHolder.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/config/IConfigHolder.kt new file mode 100644 index 0000000..2acc99d --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/util/config/IConfigHolder.kt @@ -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() +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/config/ProfileSpecificConfigHolder.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/config/ProfileSpecificConfigHolder.kt new file mode 100644 index 0000000..44a79c4 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/util/config/ProfileSpecificConfigHolder.kt @@ -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() + } + +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/render/block.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/render/block.kt new file mode 100644 index 0000000..9b5432a --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/util/render/block.kt @@ -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() + } + } +} + + diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/textutil.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/textutil.kt new file mode 100644 index 0000000..ac640be --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/util/textutil.kt @@ -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(), "") + |