Refactor source layout

Introduce compat source sets and move all kotlin sources to the main directory

[no changelog]
This commit is contained in:
Linnea Gräf
2024-08-28 19:04:24 +02:00
parent a690630816
commit d2f240ff0c
251 changed files with 295 additions and 38 deletions

View File

@@ -0,0 +1,131 @@
package moe.nea.firmament.features.world
import io.github.moulberry.repo.data.Coordinate
import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer
import net.minecraft.text.Text
import net.minecraft.util.math.Vec3d
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.ProcessChatEvent
import moe.nea.firmament.events.SkyblockServerUpdateEvent
import moe.nea.firmament.events.WorldRenderLastEvent
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SBData
import moe.nea.firmament.util.SkyBlockIsland
import moe.nea.firmament.util.blockPos
import moe.nea.firmament.util.data.ProfileSpecificDataHolder
import moe.nea.firmament.util.render.RenderInWorldContext
import moe.nea.firmament.util.render.RenderInWorldContext.Companion.renderInWorld
import moe.nea.firmament.util.unformattedString
object FairySouls : FirmamentFeature {
@Serializable
data class Data(
val foundSouls: MutableMap<SkyBlockIsland, MutableSet<Int>> = mutableMapOf()
)
override val config: ManagedConfig
get() = TConfig
object DConfig : ProfileSpecificDataHolder<Data>(serializer(), "found-fairysouls", ::Data)
object TConfig : ManagedConfig("fairy-souls") {
val displaySouls by toggle("show") { false }
val resetSouls by button("reset") {
DConfig.data?.foundSouls?.clear() != null
updateMissingSouls()
}
}
override val identifier: String get() = "fairy-souls"
val playerReach = 5
val playerReachSquared = playerReach * playerReach
var currentLocationName: SkyBlockIsland? = null
var currentLocationSouls: List<Coordinate> = emptyList()
var currentMissingSouls: List<Coordinate> = emptyList()
fun updateMissingSouls() {
currentMissingSouls = emptyList()
val c = DConfig.data ?: 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.locrawMode] ?: 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.locrawMode] ?: 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 = DConfig.data ?: return
val loc = currentLocationName ?: return
val idx = currentLocationSouls.indexOf(nearestSoul)
c.foundSouls.computeIfAbsent(loc) { mutableSetOf() }.add(idx)
DConfig.markDirty()
updateMissingSouls()
}
@Subscribe
fun onWorldRender(it: WorldRenderLastEvent) {
if (!TConfig.displaySouls) return
renderInWorld(it) {
color(1F, 1F, 0F, 0.8F)
currentMissingSouls.forEach {
block(it.blockPos)
}
color(1f, 0f, 1f, 1f)
currentLocationSouls.forEach {
wireframeCube(it.blockPos)
}
}
}
@Subscribe
fun onProcessChat(it: ProcessChatEvent) {
when (it.text.unformattedString) {
"You have already found that Fairy Soul!" -> {
markNearestSoul()
}
"SOUL! You found a Fairy Soul!" -> {
markNearestSoul()
}
}
}
@Subscribe
fun onLocationChange(it: SkyblockServerUpdateEvent) {
currentLocationName = it.newLocraw?.skyblockLocation
updateWorldSouls()
updateMissingSouls()
}
}

View File

@@ -0,0 +1,40 @@
package moe.nea.firmament.features.world
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.commands.thenExecute
import moe.nea.firmament.events.CommandEvent
import moe.nea.firmament.events.ReloadRegistrationEvent
import moe.nea.firmament.util.MoulConfigUtils
import moe.nea.firmament.util.ScreenUtil
object NPCWaypoints {
var allNpcWaypoints = listOf<NavigableWaypoint>()
@Subscribe
fun onRepoReloadRegistration(event: ReloadRegistrationEvent) {
event.repo.registerReloadListener {
allNpcWaypoints = it.items.items.values
.asSequence()
.filter { !it.island.isNullOrBlank() }
.map {
NavigableWaypoint.NPCWaypoint(it)
}
.toList()
}
}
@Subscribe
fun onOpenGui(event: CommandEvent.SubCommand) {
event.subcommand("npcs") {
thenExecute {
ScreenUtil.setScreenLater(MoulConfigUtils.loadScreen(
"npc_waypoints",
NpcWaypointGui(allNpcWaypoints),
null))
}
}
}
}

View File

@@ -0,0 +1,22 @@
package moe.nea.firmament.features.world
import io.github.moulberry.repo.data.NEUItem
import net.minecraft.util.math.BlockPos
import moe.nea.firmament.util.SkyBlockIsland
abstract class NavigableWaypoint {
abstract val name: String
abstract val position: BlockPos
abstract val island: SkyBlockIsland
data class NPCWaypoint(
val item: NEUItem,
) : NavigableWaypoint() {
override val name: String
get() = item.displayName
override val position: BlockPos
get() = BlockPos(item.x, item.y, item.z)
override val island: SkyBlockIsland
get() = SkyBlockIsland.forMode(item.island)
}
}

View File

@@ -0,0 +1,121 @@
package moe.nea.firmament.features.world
import io.github.moulberry.repo.constants.Islands
import net.minecraft.text.Text
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Position
import net.minecraft.util.math.Vec3i
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.SkyblockServerUpdateEvent
import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.events.WorldRenderLastEvent
import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SBData
import moe.nea.firmament.util.SkyBlockIsland
import moe.nea.firmament.util.WarpUtil
import moe.nea.firmament.util.render.RenderInWorldContext
object NavigationHelper {
var targetWaypoint: NavigableWaypoint? = null
set(value) {
field = value
recalculateRoute()
}
var nextTeleporter: Islands.Teleporter? = null
private set
val Islands.Teleporter.toIsland get() = SkyBlockIsland.forMode(this.getTo())
val Islands.Teleporter.fromIsland get() = SkyBlockIsland.forMode(this.getFrom())
val Islands.Teleporter.blockPos get() = BlockPos(x.toInt(), y.toInt(), z.toInt())
@Subscribe
fun onWorldSwitch(event: SkyblockServerUpdateEvent) {
recalculateRoute()
}
fun recalculateRoute() {
val tp = targetWaypoint
val currentIsland = SBData.skyblockLocation
if (tp == null || currentIsland == null) {
nextTeleporter = null
return
}
val route = findRoute(currentIsland, tp.island, mutableSetOf())
nextTeleporter = route?.get(0)
}
private fun findRoute(
fromIsland: SkyBlockIsland,
targetIsland: SkyBlockIsland,
visitedIslands: MutableSet<SkyBlockIsland>
): MutableList<Islands.Teleporter>? {
var shortestChain: MutableList<Islands.Teleporter>? = null
for (it in RepoManager.neuRepo.constants.islands.teleporters) {
if (it.toIsland in visitedIslands) continue
if (it.fromIsland != fromIsland) continue
if (it.toIsland == targetIsland) return mutableListOf(it)
visitedIslands.add(fromIsland)
val nextRoute = findRoute(it.toIsland, targetIsland, visitedIslands) ?: continue
nextRoute.add(0, it)
if (shortestChain == null || shortestChain.size > nextRoute.size) {
shortestChain = nextRoute
}
visitedIslands.remove(fromIsland)
}
return shortestChain
}
@Subscribe
fun onMovement(event: TickEvent) { // TODO: add a movement tick event maybe?
val tp = targetWaypoint ?: return
val p = MC.player ?: return
if (p.squaredDistanceTo(tp.position.toCenterPos()) < 5 * 5) {
targetWaypoint = null
}
}
@Subscribe
fun drawWaypoint(event: WorldRenderLastEvent) {
val tp = targetWaypoint ?: return
val nt = nextTeleporter
RenderInWorldContext.renderInWorld(event) {
if (nt != null) {
waypoint(nt.blockPos,
Text.literal("Teleporter to " + nt.toIsland.userFriendlyName),
Text.literal("(towards " + tp.name + "§f)"))
} else if (tp.island == SBData.skyblockLocation) {
waypoint(tp.position,
Text.literal(tp.name))
}
}
}
fun tryWarpNear() {
val tp = targetWaypoint
if (tp == null) {
MC.sendChat(Text.literal("Could not find a waypoint to warp you to. Select one first."))
return
}
WarpUtil.teleportToNearestWarp(tp.island, tp.position.asPositionView())
}
}
fun Vec3i.asPositionView(): Position {
return object : Position {
override fun getX(): Double {
return this@asPositionView.x.toDouble()
}
override fun getY(): Double {
return this@asPositionView.y.toDouble()
}
override fun getZ(): Double {
return this@asPositionView.z.toDouble()
}
}
}

View File

@@ -0,0 +1,68 @@
package moe.nea.firmament.features.world
import io.github.notenoughupdates.moulconfig.observer.ObservableList
import io.github.notenoughupdates.moulconfig.xml.Bind
import moe.nea.firmament.features.events.anniversity.AnniversaryFeatures.atOnce
import moe.nea.firmament.keybindings.SavedKeyBinding
class NpcWaypointGui(
val allWaypoints: List<NavigableWaypoint>,
) {
data class NavigableWaypointW(val waypoint: NavigableWaypoint) {
@Bind
fun name() = waypoint.name
@Bind
fun isSelected() = NavigationHelper.targetWaypoint == waypoint
@Bind
fun click() {
if (SavedKeyBinding.isShiftDown()) {
NavigationHelper.targetWaypoint = waypoint
NavigationHelper.tryWarpNear()
} else if (isSelected()) {
NavigationHelper.targetWaypoint = null
} else {
NavigationHelper.targetWaypoint = waypoint
}
}
}
@JvmField
@field:Bind
var search: String = ""
var lastSearch: String? = null
@Bind("results")
fun results(): ObservableList<NavigableWaypointW> {
return results
}
@Bind
fun tick() {
if (search != lastSearch) {
updateSearch()
lastSearch = search
}
}
val results: ObservableList<NavigableWaypointW> = ObservableList(mutableListOf())
fun updateSearch() {
val split = search.split(" +".toRegex())
results.atOnce {
results.clear()
allWaypoints.filter { waypoint ->
if (search.isBlank()) {
true
} else {
split.all { waypoint.name.contains(it, ignoreCase = true) }
}
}.mapTo(results) {
NavigableWaypointW(it)
}
}
}
}

View File

@@ -0,0 +1,297 @@
package moe.nea.firmament.features.world
import com.mojang.brigadier.arguments.IntegerArgumentType
import me.shedaniel.math.Color
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource
import kotlinx.serialization.Serializable
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.seconds
import net.minecraft.command.argument.BlockPosArgumentType
import net.minecraft.server.command.ServerCommandSource
import net.minecraft.text.Text
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Vec3d
import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.commands.get
import moe.nea.firmament.commands.thenArgument
import moe.nea.firmament.commands.thenExecute
import moe.nea.firmament.commands.thenLiteral
import moe.nea.firmament.events.CommandEvent
import moe.nea.firmament.events.ProcessChatEvent
import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.events.WorldReadyEvent
import moe.nea.firmament.events.WorldRenderLastEvent
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.util.ClipboardUtils
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.render.RenderInWorldContext
object Waypoints : FirmamentFeature {
override val identifier: String
get() = "waypoints"
object TConfig : ManagedConfig(identifier) {
val tempWaypointDuration by duration("temp-waypoint-duration", 0.seconds, 1.hours) { 30.seconds }
val showIndex by toggle("show-index") { true }
val skipToNearest by toggle("skip-to-nearest") { false }
// TODO: look ahead size
}
data class TemporaryWaypoint(
val pos: BlockPos,
val postedAt: TimeMark,
)
override val config get() = TConfig
val temporaryPlayerWaypointList = mutableMapOf<String, TemporaryWaypoint>()
val temporaryPlayerWaypointMatcher = "(?i)x: (-?[0-9]+),? y: (-?[0-9]+),? z: (-?[0-9]+)".toPattern()
val waypoints = mutableListOf<BlockPos>()
var ordered = false
var orderedIndex = 0
@Serializable
data class ColeWeightWaypoint(
val x: Int,
val y: Int,
val z: Int,
val r: Int = 0,
val g: Int = 0,
val b: Int = 0,
)
@Subscribe
fun onRenderOrderedWaypoints(event: WorldRenderLastEvent) {
if (waypoints.isEmpty()) return
RenderInWorldContext.renderInWorld(event) {
if (!ordered) {
waypoints.withIndex().forEach {
color(0f, 0.3f, 0.7f, 0.5f)
block(it.value)
color(1f, 1f, 1f, 1f)
if (TConfig.showIndex)
withFacingThePlayer(it.value.toCenterPos()) {
text(Text.literal(it.index.toString()))
}
}
} else {
orderedIndex %= waypoints.size
val firstColor = Color.ofRGBA(0, 200, 40, 180)
color(firstColor)
tracer(waypoints[orderedIndex].toCenterPos(), lineWidth = 3f)
waypoints.withIndex().toList()
.wrappingWindow(orderedIndex, 3)
.zip(
listOf(
firstColor,
Color.ofRGBA(180, 200, 40, 150),
Color.ofRGBA(180, 80, 20, 140),
)
)
.reversed()
.forEach { (waypoint, col) ->
val (index, pos) = waypoint
color(col)
block(pos)
color(1f, 1f, 1f, 1f)
if (TConfig.showIndex)
withFacingThePlayer(pos.toCenterPos()) {
text(Text.literal(index.toString()))
}
}
}
}
}
@Subscribe
fun onTick(event: TickEvent) {
if (waypoints.isEmpty() || !ordered) return
orderedIndex %= waypoints.size
val p = MC.player?.pos ?: return
if (TConfig.skipToNearest) {
orderedIndex =
(waypoints.withIndex().minBy { it.value.getSquaredDistance(p) }.index + 1) % waypoints.size
} else {
if (waypoints[orderedIndex].isWithinDistance(p, 3.0)) {
orderedIndex = (orderedIndex + 1) % waypoints.size
}
}
}
@Subscribe
fun onProcessChat(it: ProcessChatEvent) {
val matcher = temporaryPlayerWaypointMatcher.matcher(it.unformattedString)
if (it.nameHeuristic != null && TConfig.tempWaypointDuration > 0.seconds && matcher.find()) {
temporaryPlayerWaypointList[it.nameHeuristic] = TemporaryWaypoint(
BlockPos(
matcher.group(1).toInt(),
matcher.group(2).toInt(),
matcher.group(3).toInt(),
),
TimeMark.now()
)
}
}
@Subscribe
fun onCommand(event: CommandEvent.SubCommand) {
event.subcommand("waypoint") {
thenArgument("pos", BlockPosArgumentType.blockPos()) { pos ->
thenExecute {
val position = pos.get(this).toAbsoluteBlockPos(source.asFakeServer())
waypoints.add(position)
source.sendFeedback(
Text.stringifiedTranslatable(
"firmament.command.waypoint.added",
position.x,
position.y,
position.z
)
)
}
}
}
event.subcommand("waypoints") {
thenLiteral("clear") {
thenExecute {
waypoints.clear()
source.sendFeedback(Text.translatable("firmament.command.waypoint.clear"))
}
}
thenLiteral("toggleordered") {
thenExecute {
ordered = !ordered
if (ordered) {
val p = MC.player?.pos ?: Vec3d.ZERO
orderedIndex =
waypoints.withIndex().minByOrNull { it.value.getSquaredDistance(p) }?.index ?: 0
}
source.sendFeedback(Text.translatable("firmament.command.waypoint.ordered.toggle.$ordered"))
}
}
thenLiteral("skip") {
thenExecute {
if (ordered && waypoints.isNotEmpty()) {
orderedIndex = (orderedIndex + 1) % waypoints.size
source.sendFeedback(Text.translatable("firmament.command.waypoint.skip"))
} else {
source.sendError(Text.translatable("firmament.command.waypoint.skip.error"))
}
}
}
thenLiteral("remove") {
thenArgument("index", IntegerArgumentType.integer(0)) { indexArg ->
thenExecute {
val index = get(indexArg)
if (index in waypoints.indices) {
waypoints.removeAt(index)
source.sendFeedback(Text.stringifiedTranslatable(
"firmament.command.waypoint.remove",
index))
} else {
source.sendError(Text.stringifiedTranslatable("firmament.command.waypoint.remove.error"))
}
}
}
}
thenLiteral("import") {
thenExecute {
val contents = ClipboardUtils.getTextContents()
val data = try {
Firmament.json.decodeFromString<List<ColeWeightWaypoint>>(contents)
} catch (ex: Exception) {
Firmament.logger.error("Could not load waypoints from clipboard", ex)
source.sendError(Text.translatable("firmament.command.waypoint.import.error"))
return@thenExecute
}
waypoints.clear()
data.mapTo(waypoints) { BlockPos(it.x, it.y, it.z) }
source.sendFeedback(
Text.stringifiedTranslatable(
"firmament.command.waypoint.import",
data.size
)
)
}
}
}
}
@Subscribe
fun onRenderTemporaryWaypoints(event: WorldRenderLastEvent) {
temporaryPlayerWaypointList.entries.removeIf { it.value.postedAt.passedTime() > TConfig.tempWaypointDuration }
if (temporaryPlayerWaypointList.isEmpty()) return
RenderInWorldContext.renderInWorld(event) {
color(1f, 1f, 0f, 1f)
temporaryPlayerWaypointList.forEach { (player, waypoint) ->
block(waypoint.pos)
}
color(1f, 1f, 1f, 1f)
temporaryPlayerWaypointList.forEach { (player, waypoint) ->
val skin =
MC.networkHandler?.listedPlayerListEntries?.find { it.profile.name == player }
?.skinTextures
?.texture
withFacingThePlayer(waypoint.pos.toCenterPos()) {
waypoint(waypoint.pos, Text.stringifiedTranslatable("firmament.waypoint.temporary", player))
if (skin != null) {
matrixStack.translate(0F, -20F, 0F)
// Head front
texture(
skin, 16, 16,
1 / 8f, 1 / 8f,
2 / 8f, 2 / 8f,
)
// Head overlay
texture(
skin, 16, 16,
5 / 8f, 1 / 8f,
6 / 8f, 2 / 8f,
)
}
}
}
}
}
@Subscribe
fun onWorldReady(event: WorldReadyEvent) {
temporaryPlayerWaypointList.clear()
}
}
fun <E> List<E>.wrappingWindow(startIndex: Int, windowSize: Int): List<E> {
val result = ArrayList<E>(windowSize)
if (startIndex + windowSize < size) {
result.addAll(subList(startIndex, startIndex + windowSize))
} else {
result.addAll(subList(startIndex, size))
result.addAll(subList(0, minOf(windowSize - (size - startIndex), startIndex)))
}
return result
}
fun FabricClientCommandSource.asFakeServer(): ServerCommandSource {
val source = this
return ServerCommandSource(
source.player,
source.position,
source.rotation,
null,
0,
"FakeServerCommandSource",
Text.literal("FakeServerCommandSource"),
null,
source.player
)
}