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

View File

@@ -0,0 +1,17 @@
package moe.nea.firmament.features.events.carnival
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.gui.config.ManagedConfig
object CarnivalFeatures : FirmamentFeature {
object TConfig : ManagedConfig(identifier) {
val enableBombSolver by toggle("bombs-solver") { true }
val displayTutorials by toggle("tutorials") { true }
}
override val config: ManagedConfig?
get() = TConfig
override val identifier: String
get() = "carnival"
}

View File

@@ -0,0 +1,276 @@
package moe.nea.firmament.features.events.carnival
import io.github.notenoughupdates.moulconfig.observer.ObservableList
import io.github.notenoughupdates.moulconfig.platform.ModernItemStack
import io.github.notenoughupdates.moulconfig.xml.Bind
import java.util.UUID
import net.minecraft.block.Blocks
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import net.minecraft.item.Items
import net.minecraft.text.ClickEvent
import net.minecraft.text.Text
import net.minecraft.util.math.BlockPos
import net.minecraft.world.WorldAccess
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.commands.thenExecute
import moe.nea.firmament.events.AttackBlockEvent
import moe.nea.firmament.events.CommandEvent
import moe.nea.firmament.events.EntityUpdateEvent
import moe.nea.firmament.events.ProcessChatEvent
import moe.nea.firmament.events.WorldReadyEvent
import moe.nea.firmament.events.WorldRenderLastEvent
import moe.nea.firmament.features.debug.DebugLogger
import moe.nea.firmament.util.LegacyFormattingCode
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.MoulConfigUtils
import moe.nea.firmament.util.ScreenUtil
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.item.createSkullItem
import moe.nea.firmament.util.render.RenderInWorldContext
import moe.nea.firmament.util.setSkyBlockFirmamentUiId
import moe.nea.firmament.util.skyBlockId
import moe.nea.firmament.util.useMatch
object MinesweeperHelper {
val sandBoxLow = BlockPos(-112, 72, -11)
val sandBoxHigh = BlockPos(-106, 72, -5)
val boardSize = Pair(sandBoxHigh.x - sandBoxLow.x, sandBoxHigh.z - sandBoxLow.z)
val gameStartMessage = "[NPC] Carnival Pirateman: Good luck, matey!"
val gameEndMessage = "Fruit Digging"
val bombPattern = "MINES! There (are|is) (?<bombCount>[0-8]) bombs? hidden nearby\\.".toPattern()
val startGameQuestion = "[NPC] Carnival Pirateman: Would ye like to do some Fruit Digging?"
enum class Piece(
@get:Bind("fruitName")
val fruitName: String,
val points: Int,
val specialAbility: String,
val totalPerBoard: Int,
val textureHash: String,
val fruitColor: LegacyFormattingCode,
) {
COCONUT("Coconut",
200,
"Prevents a bomb from exploding next turn",
3,
"10ceb1455b471d016a9f06d25f6e468df9fcf223e2c1e4795b16e84fcca264ee",
LegacyFormattingCode.DARK_PURPLE),
APPLE("Apple",
100,
"Gains 100 points for each apple dug up",
8,
"17ea278d6225c447c5943d652798d0bbbd1418434ce8c54c54fdac79994ddd6c",
LegacyFormattingCode.GREEN),
WATERMELON("Watermelon",
100,
"Blows up an adjacent fruit for half the points",
4,
"efe4ef83baf105e8dee6cf03dfe7407f1911b3b9952c891ae34139560f2931d6",
LegacyFormattingCode.DARK_BLUE),
DURIAN("Durian",
800,
"Halves the points earned in the next turn",
2,
"ac268d36c2c6047ffeec00124096376b56dbb4d756a55329363a1b27fcd659cd",
LegacyFormattingCode.DARK_PURPLE),
MANGO("Mango",
300,
"Just an ordinary fruit",
10,
"f363a62126a35537f8189343a22660de75e810c6ac004a7d3da65f1c040a839",
LegacyFormattingCode.GREEN),
DRAGON_FRUIT("Dragonfruit",
1200,
"Halves the points earned in the next turn",
1,
"3cc761bcb0579763d9b8ab6b7b96fa77eb6d9605a804d838fec39e7b25f95591",
LegacyFormattingCode.LIGHT_PURPLE),
POMEGRANATE("Pomegranate",
200,
"Grants an extra 50% more points in the next turn",
4,
"40824d18079042d5769f264f44394b95b9b99ce689688cc10c9eec3f882ccc08",
LegacyFormattingCode.DARK_BLUE),
CHERRY("Cherry",
200,
"The second cherry grants 300 bonus points",
2,
"c92b099a62cd2fbf8ada09dec145c75d7fda4dc57b968bea3a8fa11e37aa48b2",
LegacyFormattingCode.DARK_PURPLE),
BOMB("Bomb",
-1,
"Destroys nearby fruit",
15,
"a76a2811d1e176a07b6d0a657b910f134896ce30850f6e80c7c83732d85381ea",
LegacyFormattingCode.DARK_RED),
RUM("Rum",
-1,
"Stops your dowsing ability for one turn",
5,
"407b275d28b927b1bf7f6dd9f45fbdad2af8571c54c8f027d1bff6956fbf3c16",
LegacyFormattingCode.YELLOW),
;
val textureUrl = "http://textures.minecraft.net/texture/$textureHash"
val itemStack = createSkullItem(UUID.randomUUID(), textureUrl)
.setSkyBlockFirmamentUiId("MINESWEEPER_$name")
@Bind
fun getIcon() = ModernItemStack.of(itemStack)
@Bind
fun pieceLabel() = fruitColor.formattingCode + fruitName
@Bind
fun boardLabel() = "§a$totalPerBoard§7/§rboard"
@Bind("description")
fun getDescription() = buildString {
append(specialAbility)
if (points >= 0) {
append(" Default points: $points.")
}
}
}
object TutorialScreen {
@get:Bind("pieces")
val pieces = ObservableList(Piece.entries.toList().reversed())
@get:Bind("modes")
val modes = ObservableList(DowsingMode.entries.toList())
}
enum class DowsingMode(
val itemType: Item,
@get:Bind("feature")
val feature: String,
@get:Bind("description")
val description: String,
) {
MINES(Items.IRON_SHOVEL, "Bomb detection", "Tells you how many bombs are near the block"),
ANCHOR(Items.DIAMOND_SHOVEL, "Lowest fruit", "Shows you which block nearby contains the lowest scoring fruit"),
TREASURE(Items.GOLDEN_SHOVEL, "Highest fruit", "Tells you which kind of fruit is the highest scoring nearby"),
;
@Bind("itemType")
fun getItemStack() = ModernItemStack.of(ItemStack(itemType))
companion object {
val id = SkyblockId("CARNIVAL_SHOVEL")
fun fromItem(itemStack: ItemStack): DowsingMode? {
if (itemStack.skyBlockId != id) return null
return DowsingMode.entries.find { it.itemType == itemStack.item }
}
}
}
data class BoardPosition(
val x: Int,
val y: Int
) {
fun toBlockPos() = BlockPos(sandBoxLow.x + x, sandBoxLow.y, sandBoxLow.z + y)
fun getBlock(world: WorldAccess) = world.getBlockState(toBlockPos()).block
fun isUnopened(world: WorldAccess) = getBlock(world) == Blocks.SAND
fun isOpened(world: WorldAccess) = getBlock(world) == Blocks.SANDSTONE
fun isScorched(world: WorldAccess) = getBlock(world) == Blocks.SANDSTONE_STAIRS
companion object {
fun fromBlockPos(blockPos: BlockPos): BoardPosition? {
if (blockPos.y != sandBoxLow.y) return null
val x = blockPos.x - sandBoxLow.x
val y = blockPos.z - sandBoxLow.z
if (x < 0 || x >= boardSize.first) return null
if (y < 0 || y >= boardSize.second) return null
return BoardPosition(x, y)
}
}
}
data class GameState(
val nearbyBombs: MutableMap<BoardPosition, Int> = mutableMapOf(),
val knownBombPositions: MutableSet<BoardPosition> = mutableSetOf(),
var lastClickedPosition: BoardPosition? = null,
var lastDowsingMode: DowsingMode? = null,
)
var gameState: GameState? = null
val log = DebugLogger("minesweeper")
@Subscribe
fun onCommand(event: CommandEvent.SubCommand) {
event.subcommand("minesweepertutorial") {
thenExecute {
ScreenUtil.setScreenLater(MoulConfigUtils.loadScreen("carnival/minesweeper_tutorial",
TutorialScreen,
null))
}
}
}
@Subscribe
fun onWorldChange(event: WorldReadyEvent) {
gameState = null
}
@Subscribe
fun onChat(event: ProcessChatEvent) {
if (CarnivalFeatures.TConfig.displayTutorials && event.unformattedString == startGameQuestion) {
MC.sendChat(Text.translatable("firmament.carnival.tutorial.minesweeper").styled {
it.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, "/firm minesweepertutorial"))
})
}
if (!CarnivalFeatures.TConfig.enableBombSolver) {
gameState = null // TODO: replace this which a watchable property
return
}
if (event.unformattedString == gameStartMessage) {
gameState = GameState()
log.log { "Game started" }
}
if (event.unformattedString.trim() == gameEndMessage) {
gameState = null // TODO: add a loot tracker maybe? probably not, i dont think people care
log.log { "Finished game" }
}
val gs = gameState ?: return
bombPattern.useMatch(event.unformattedString) {
val bombCount = group("bombCount").toInt()
log.log { "Marking ${gs.lastClickedPosition} as having $bombCount nearby" }
val pos = gs.lastClickedPosition ?: return
gs.nearbyBombs[pos] = bombCount
}
}
@Subscribe
fun onMobChange(event: EntityUpdateEvent) {
val gs = gameState ?: return
if (event !is EntityUpdateEvent.TrackedDataUpdate) return
// TODO: listen to state
}
@Subscribe
fun onBlockClick(event: AttackBlockEvent) {
val gs = gameState ?: return
val boardPosition = BoardPosition.fromBlockPos(event.blockPos)
log.log { "Breaking block at ${event.blockPos} ($boardPosition)" }
gs.lastClickedPosition = boardPosition
gs.lastDowsingMode = DowsingMode.fromItem(event.player.inventory.mainHandStack)
}
@Subscribe
fun onRender(event: WorldRenderLastEvent) {
val gs = gameState ?: return
RenderInWorldContext.renderInWorld(event) {
for ((pos, bombCount) in gs.nearbyBombs) {
this.text(pos.toBlockPos().up().toCenterPos(), Text.literal("§a$bombCount \uD83D\uDCA3"))
}
}
}
}